diff --git a/.claude/launch.json b/.claude/launch.json index 501a8fe..1e41728 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -19,23 +19,6 @@ "./frontend/e2e/wails-server.mjs" ], "port": 34123 - }, - { - "name": "oneagent-site", - "runtimeExecutable": "pnpm", - "runtimeArgs": [ - "--dir", - "site", - "run", - "dev", - "--port", - "4322" - ], - "env": { - "SITE_URL": "http://localhost:4322" - }, - "port": 4322, - "url": "http://localhost:4322" } ] } diff --git a/.gitignore b/.gitignore index fe3ce62..12ff389 100644 --- a/.gitignore +++ b/.gitignore @@ -23,13 +23,13 @@ output/ # Go CLI 与桌面壳的本地构建产物;scripts/install.* 只转发,不构建。 bin/ -# 公开站点:构建缓存与测试产物 -site/node_modules/ -site/dist/ -site/.astro/ -site/coverage/ -site/test-results/ -site/playwright-report/ +# 公开站已迁出到 MaimoryLab/OneAgent-site。这一条留着是因为迁出前克隆过本仓库的 +# 机器上,site/ 仍然躺在工作区里:不忽略它,git status 会把一整个 Astro 项目 +# 报成未跟踪文件。 +site/ .task -.env \ No newline at end of file +.env + +# 本机代码索引。是否建立索引由每个开发者自己决定,重建只需 `codegraph index .` +.codegraph/ diff --git a/CLAUDE.md b/CLAUDE.md index a3aae27..99bee9a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,11 +9,13 @@ - `internal/config`:TOML/JSON/JSONC 适配器、配置发现和 golden fixtures。 - `internal/install`:默认最新、可选精确版本的 Agent 包安装,registry 选择、Node.js/uv 运行时引导(下载、校验、解压、写入 PATH)和 Aider Python 管理边界。 - `internal/profile`、`internal/securefs`:profile、secret、备份、权限和原子写。 +- `internal/binding`:Wails 暴露给前端的五个 service,是 React 与 Go 的唯一接缝。改这里的 DTO 必须重新生成 `frontend/bindings` 并同步 `frontend/src/backend/wails.ts`。 - `cmd/oneagent` - `cmd/oneagent-release`:原生 Wails/Go/React 发布包、notice、manifest 和 SHA-256。 - `cmd/oneagent-rc`、`cmd/oneagent-provider-smoke`:发行候选的真实 Agent/Provider 检查。 - `frontend/bindings`:Wails 生成物,禁止手工编辑。 -- `site`:独立 Astro 公开站。 + +公开站已迁出到 [MaimoryLab/OneAgent-site](https://github.com/MaimoryLab/OneAgent-site),本仓库不再有 `site/`。它把 `agents.lock.json` 和 `providers.lock.json` vendor 到自己的 `data/` 下,从发行 tag 而不是本仓库 `main` 刷新——改这两个文件不会自动反映到站上,也不应该:站描述的是已发布版本支持什么。 `providers.lock.json` 是内置 Provider 端点、fallback model 和公开站商业披露字段的真源;用户 Provider 与内置覆盖保存在 `~/.oneagent/providers.json`。 @@ -49,6 +51,18 @@ go run ./cmd/oneagent-provider-smoke --provider all --timeout 30s 普通测试、Wails 构建、站点构建和发布工具不需要 Python。安装 Aider 需要 Python 3.12,但不再要求本机预装:uv 自己解析解释器,本机有匹配版本就复用,否则下载一份托管 CPython 到 `~/.oneagent/runtimes/python`。Python 仍然不进发行包。 +## CodeGraph + +本仓库已建索引(`.codegraph/`,不提交,重建用 `codegraph index .`,约 0.5s)。定位或理解代码时**先用它,别 grep**: + +```bash +codegraph explore "binding Service Install" +``` + +对这个仓库最有用的一点:它把 Go 与前端连起来。查 `AgentService` 会同时列出 `internal/binding/services.go`、生成的 `frontend/bindings/.../index.ts` 和手写的 `frontend/src/backend/wails.ts`——也就是改一个后端 DTO 需要一起动的三处。`frontend/src/types/api.ts` 里的类型是手写的而非从 bindings 导入,所以后端 DTO 与它是**两份真源**,索引是发现漏改的最快方式。 + +**一个已知局限**:blast radius 的「⚠️ no covering tests found」只看**直接**调用者,被上层函数内部调用的实现会被误标成无测试。不要照着这个提示补已经存在的测试,先确认调用链。 + ## 代码边界 - `agents.lock.json` 是 Agent 元数据唯一真源,但不保存 Agent 版本或包哈希。新增自动配置 Agent 时先补包名和元数据,再添加对应 config adapter 和 Go 测试。 diff --git a/README.md b/README.md index 5f64686..860ac2e 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,14 @@ Status / Provider / Agent / Profile services | catalog / provider / install / config / profile / securefs -Pure Go CLI --------------------^ Astro site ---- release metadata +Pure Go CLI --------------------^ ``` +公开站不在这张图里,也不在这个仓库里:它已迁出到 +[MaimoryLab/OneAgent-site](https://github.com/MaimoryLab/OneAgent-site)。它从 +GitHub Releases API 读取下载信息,并把 `agents.lock.json`、`providers.lock.json` +vendor 到自己仓库,从发行 tag 刷新——所以改本仓库这两个文件不会自动改变站上内容。 + - `cmd/oneagent-desktop`:Wails 桌面入口。 - `cmd/oneagent`:纯 Go headless CLI。 - `cmd/oneagent-release`:构建、notice、manifest、SHA-256 和发行包检查。 @@ -32,7 +37,6 @@ Pure Go CLI --------------------^ Astro site ---- release metadata - `cmd/oneagent-provider-smoke`:PPIO/Novita 三协议 RC smoke。 - `internal/`:桌面、CLI 和 RC 工具共用的 Go 核心。 - `frontend/`:React 应用;发行包只携带构建后的静态资源。 -- `site/`:独立 Astro 公开站,不进入桌面包体。 - `agents.lock.json`:Agent 包名、来源、配置适配器和许可证的唯一清单;不固定 Agent 版本或包哈希。 - `providers.lock.json`:内置 Provider 端点、fallback probe model 和公开站披露字段清单;桌面端用户 Provider 保存在本机 `~/.oneagent/providers.json`。 diff --git a/docs/decisions/ADR-006-public-site-and-generated-release-index.md b/docs/decisions/ADR-006-public-site-and-generated-release-index.md index cee1969..b4b1901 100644 --- a/docs/decisions/ADR-006-public-site-and-generated-release-index.md +++ b/docs/decisions/ADR-006-public-site-and-generated-release-index.md @@ -1,6 +1,10 @@ # ADR-006:独立公开站与 GitHub Release 事实源 -- 状态:Accepted(2026-07-31 修订) +- 状态:Partially Superseded(2026-08-04)——决策 1 中「在同一仓库维护」已被推翻,站点 + 迁出到 [MaimoryLab/OneAgent-site](https://github.com/MaimoryLab/OneAgent-site); + 同一决策中「不把营销路由加入本地 Launcher」以及决策 2–5 仍然有效。决策 4 的读取路径 + 变为站点仓库 vendor 的 `data/` 副本,从发行 tag 刷新。当前操作指南见 + [public-site-operations.md](../public-site-operations.md),本文件只作背景保留。 - 日期:2026-07-28 ## 背景 diff --git a/docs/public-site-operations.md b/docs/public-site-operations.md index a3adece..da52ea6 100644 --- a/docs/public-site-operations.md +++ b/docs/public-site-operations.md @@ -1,58 +1,39 @@ # OneAgent 公开站运营与发布手册 -状态:已实施。 +状态:已迁出。站点自己的构建命令、环境变量和部署步骤见 +[MaimoryLab/OneAgent-site](https://github.com/MaimoryLab/OneAgent-site) 的 README。 -## 架构边界 +公开站曾经是本仓库的 `site/` 目录,现在是独立仓库。本文件只保留仍然约束本仓库的部分, +不再重复站点侧的操作步骤——两处各写一份必然慢慢分叉。 -- `frontend/` 是随桌面 App 打包的 React 客户端。 -- `site/` 是独立构建和部署的 Astro 静态站,不进入 App 包体。 -- `.github/workflows/technical-preview.yml` 只构建 Go/Wails App 资产并创建 Draft GitHub Release。 -- `.github/workflows/site.yml` 只测试、构建和部署 GitHub Pages。 +本文原先描述的 `.github/workflows/technical-preview.yml` 和 `.github/workflows/site.yml` +都已不存在(本仓库当前只有 `build-artifacts.yml`),按那两个工作流写的发布顺序因此 +已经失效,不要照着执行。 -两个工作流没有 artifact 或 job 依赖。发布者人工审核并发布 Draft Release 后,`release.published` 事件会触发站点重建。 +## 仍然由本仓库承担的部分 -## 版本事实源 +**GitHub Release 是公开版本与资产的事实源。** 站点在构建时调用 GitHub Releases API, +只读取已发布、非 Draft 的 Release;页面上的版本标签、发布日期、下载地址、文件大小和 +SHA-256 都来自那里,站点不读取本地 `release/` 目录,不复制下载资产,也不维护版本回退值。 +所以本仓库这边的义务是:Release 一旦发布就是公开事实,资产、校验和、签名状态必须在发布 +**之前**检查完毕。 -公开站在构建时调用 GitHub Releases API,只读取已发布、非 Draft 的 Release。页面上的版本标签、发布日期、下载地址、文件大小和可用的 SHA-256 digest 均来自该 API;没有 Release 时页面明确显示尚未发布。 +**`providers.lock.json` 是商业披露字段的真源。** Provider 的 `relationship`、 +`disclosure`、`referral_url` 在这里维护,且不能影响 Agent rank、兼容性结论、默认选择 +或连接测试。这条边界属于本仓库,站点只把结果展示出来。 -站点不读取 App 的本地 `release/` 目录,不复制下载资产,也不维护版本回退值。Agent 目录直接读取 `agents.lock.json`,Provider 目录直接读取根目录的 `providers.lock.json`;运行时端点和公开披露字段由同一份清单管理。 +**改 lock 文件不会自动改变站上内容。** 站点把 `agents.lock.json` 和 +`providers.lock.json` vendor 到它自己的 `data/` 目录,从发行 tag 刷新而不是跟随本仓库 +`main`。这是刻意的:站描述的是已发布版本支持什么,跟着 `main` 会把已合并但未发布的 +Agent 宣传成可用。新增 Agent 或调整披露字段后,需要到站点仓库按其 `data/README.md` +刷新一次。 -私有仓库构建需要提供具有 `contents:read` 权限的 `GITHUB_TOKEN`。独立 Pages 工作流使用当前任务的 GitHub token;未提供 token 的本地构建若无法读取私有仓库,会渲染“尚无已发布版本”。 +**Stable 门禁不变。** 各平台的签名、公证和原生 cleanroom 门禁仍是 App 发布流程的要求, +GitHub Release 不替代产物验证。 -## 本地验证 +## 历史背景 -```bash -cd site -pnpm install --frozen-lockfile -pnpm test -pnpm run build -pnpm exec playwright install chromium -pnpm run test:e2e -``` - -模拟 GitHub Pages 子路径部署: - -```bash -SITE_URL=https://example.com BASE_PATH=/OneAgent pnpm run build -``` - -该子路径产物的绝对 `` 只适用于配置的 origin。恢复本地预览时重新运行普通 `pnpm run build`。 - -## 发布顺序 - -1. 运行 `Technical Preview Packages`,构建并验证各平台 App 资产。 -2. 工作流以新 tag 创建不可变 Draft prerelease;已有 tag 会直接失败,不覆盖资产。 -3. 人工检查资产、校验和、签名状态和发行说明后发布 Release。 -4. `Public Site` 工作流自动从默认分支构建站点,从该 Release 读取版本与下载信息并部署 Pages。 -5. 仅修改站点、Agent 目录或 Provider 披露时,合入 `main` 即可独立部署,不触发 App 构建。 - -GitHub repository variables: - -- `ONEAGENT_PUBLIC_SUPPORT_URL`:公开支持入口;为空时不展示虚构地址。 -- `ONEAGENT_PUBLIC_BUSINESS_EMAIL`:公开商务邮箱;为空时不展示虚构邮箱。 - -## Provider 与稳定版边界 - -Provider 商业数据保存在 `providers.lock.json`,不能影响 Agent rank、兼容性结论、默认选择或连接测试。 - -Stable 仍需按平台满足签名、公证和原生 cleanroom 门禁。GitHub Release 是公开版本与资产的事实源,不替代 App 发布流程中的产物验证。 +设计决策记录在 +[ADR-006](decisions/ADR-006-public-site-and-generated-release-index.md)。该 ADR 中 +「在同一仓库维护 `site/`」的部分已被本次拆分取代;不把营销路由加入本地 Launcher 的 +结论仍然有效。 diff --git a/docs/wails-v3-migration-plan.md b/docs/wails-v3-migration-plan.md index 98dc7c3..0198b66 100644 --- a/docs/wails-v3-migration-plan.md +++ b/docs/wails-v3-migration-plan.md @@ -32,6 +32,10 @@ cmd/oneagent-provider-smoke Provider protocol RC checks site/ independent Astro release site ``` +(`site/` 于 2026-08-04 迁出到 +[MaimoryLab/OneAgent-site](https://github.com/MaimoryLab/OneAgent-site)。上面的布局是本文 +验收当时的事实,保留原样。) + 生产桌面构建不使用 `server` tag,不监听业务端口。浏览器 E2E 才使用 Wails server/e2e runner。 ## 3. 已完成的交付 diff --git a/site/astro.config.mjs b/site/astro.config.mjs deleted file mode 100644 index c0f90fe..0000000 --- a/site/astro.config.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import { defineConfig } from "astro/config"; -import sitemap from "@astrojs/sitemap"; - -const site = process.env.SITE_URL ?? "http://localhost:4321"; -const rawBase = process.env.BASE_PATH ?? "/"; -const base = rawBase.endsWith("/") ? rawBase : `${rawBase}/`; - -export default defineConfig({ - site, - base, - output: "static", - // Chinese stays unprefixed so every published URL keeps working; English is - // additive under /en/. - i18n: { - defaultLocale: "zh-CN", - locales: ["zh-CN", "en"], - routing: { prefixDefaultLocale: false }, - }, - integrations: [sitemap()], - build: { - assets: "_assets", - }, - vite: { - build: { - sourcemap: false, - }, - }, -}); diff --git a/site/e2e/site.spec.ts b/site/e2e/site.spec.ts deleted file mode 100644 index 8c1f689..0000000 --- a/site/e2e/site.spec.ts +++ /dev/null @@ -1,829 +0,0 @@ -import { test, expect, type Page } from "@playwright/test"; -import axe from "axe-core"; - -const criticalPages = ["/", "/explore/", "/downloads/", "/agents/", "/security/"]; - -/* Mirrors translatedRoutes in src/i18n/index.ts. It cannot be imported here: - that module reads import.meta.env.BASE_URL, which only exists under Vite, and - Playwright runs this file in plain Node. The englishPages list below is the - guard — every route named here has to have an /en/ page that gets audited, so - the two drifting apart fails rather than going unnoticed. */ -const translatedRoutes = ["", "downloads/", "quickstart/", "explore/", "security/"]; - -test("critical pages fit the viewport without horizontal scrolling", async ({ page }) => { - for (const path of criticalPages) { - await page.goto(path); - const dimensions = await page.evaluate(() => ({ - viewport: document.documentElement.clientWidth, - content: document.documentElement.scrollWidth, - })); - expect(dimensions.content, `${path} overflows its ${dimensions.viewport}px viewport`).toBeLessThanOrEqual(dimensions.viewport); - } -}); - -test("home presents one activation entry without claiming a real scan", async ({ page }) => { - await page.goto("/"); - await expect(page.getByRole("heading", { level: 1 })).toContainText("激活你的 AI 开发环境"); - await expect(page.locator(".hero-actions").getByRole("button", { name: "开始激活演示" })).toBeVisible(); - await expect(page.locator(".hero-actions").getByRole("link")).toHaveCount(0); - /* The console used to sit at idle until clicked. It now plays itself once it - scrolls into view, so the successor guarantee is that the page drives the - demo — the button stays as the replay and takeover entry. Scrolled here - explicitly because whether the console starts on screen varies by viewport, - and this test is not the one about the trigger threshold. */ - const console = page.locator("#activation-console"); - await console.scrollIntoViewIfNeeded(); - await expect(console).toHaveAttribute("data-autoplaying", "true"); - await expect(page.getByText("示例环境", { exact: true }).first()).toBeVisible(); - await expect(page.getByText("不访问设备", { exact: true })).toBeVisible(); - await expect(page.locator('input[type="password"], input[name*="key" i]')).toHaveCount(0); - await expect(page.locator(".hero-note")).toContainText("未签名技术预览版"); -}); - -test("activation demo reaches Ready only for a supported managed combination", async ({ page }) => { - const fetches: string[] = []; - page.on("request", (request) => { - if (["fetch", "xhr"].includes(request.resourceType())) fetches.push(request.url()); - }); - await page.goto("/"); - const console = page.locator("#activation-console"); - await page.getByRole("button", { name: "开始激活演示" }).click(); - await expect(console).toHaveAttribute("data-phase", "agent"); - await console.getByRole("button", { name: /Claude Code/ }).click(); - await expect(console).toHaveAttribute("data-phase", "mode"); - await console.getByRole("button", { name: /配置模型服务/ }).click(); - await expect(console).toHaveAttribute("data-phase", "provider"); - await console.getByRole("button", { name: /PPIO/ }).click(); - await console.getByRole("button", { name: "验证示例连接" }).click(); - await expect(console).toHaveAttribute("data-phase", "model"); - await console.getByRole("button", { name: "deepseek/deepseek-v3" }).click(); - await console.getByRole("button", { name: "确认激活" }).click(); - await expect(console).toHaveAttribute("data-phase", "ready"); - await expect(console.getByRole("heading", { name: "示例环境已 Ready" })).toBeVisible(); - await expect(console.getByRole("link", { name: "下载 OneAgent" })).toBeVisible(); - await expect(console.getByRole("link", { name: "打开完整配置目录" })).toBeVisible(); - await page.addScriptTag({ content: axe.source }); - const result = await page.evaluate(async () => { - const axeApi = (window as typeof window & { axe: typeof axe }).axe; - return axeApi.run(document.querySelector("#activation-console")!, { - runOnly: { type: "tag", values: ["wcag2a", "wcag2aa"] }, - }); - }); - const serious = result.violations.filter( - (violation) => violation.impact === "serious" || violation.impact === "critical", - ); - expect(serious, JSON.stringify(serious, null, 2)).toEqual([]); - expect(fetches, "the public demo must not call a local or remote activation API").toEqual([]); -}); - -test("activation demo preserves guide-only and preview-gate boundaries", async ({ page }) => { - await page.goto("/"); - const console = page.locator("#activation-console"); - - await page.getByRole("button", { name: "开始激活演示" }).click(); - await expect(console).toHaveAttribute("data-phase", "agent"); - await console.getByRole("button", { name: /Cursor/ }).click(); - await expect(console).toHaveAttribute("data-phase", "guide-only"); - await expect(console.getByRole("heading", { name: "转入官方设置" })).toBeVisible(); - await expect(console.getByRole("link", { name: "下载 OneAgent" })).toBeHidden(); - - await console.getByRole("button", { name: "重置演示" }).click(); - await page.getByRole("button", { name: "开始激活演示" }).click(); - await expect(console).toHaveAttribute("data-phase", "agent"); - await console.getByRole("button", { name: /Codex/ }).click(); - await console.getByRole("button", { name: /配置模型服务/ }).click(); - await console.getByRole("button", { name: /PPIO/ }).click(); - await console.getByRole("button", { name: "验证示例连接" }).click(); - await expect(console).toHaveAttribute("data-phase", "preview-gate"); - await expect(console.getByRole("heading", { name: "仍需通过发布门禁" })).toBeVisible(); - await expect(console.getByRole("link", { name: "下载 OneAgent" })).toBeHidden(); - await expect(console.getByRole("link", { name: "发行政策" })).toBeVisible(); -}); - -// The custom endpoint is the demo's only typed input, and its whole point is -// that the browser applies the same rules as oneagent/providers.py. A field that -// accepted anything would teach visitors an endpoint works when the app rejects it. -test("activation demo validates a custom endpoint the way the app does", async ({ page }) => { - const fetches: string[] = []; - page.on("request", (request) => { - if (["fetch", "xhr"].includes(request.resourceType())) fetches.push(request.url()); - }); - await page.goto("/"); - const console = page.locator("#activation-console"); - await page.getByRole("button", { name: "开始激活演示" }).click(); - await console.getByRole("button", { name: /Claude Code/ }).click(); - await console.getByRole("button", { name: /配置模型服务/ }).click(); - await console.getByRole("button", { name: /自定义端点/ }).click(); - - const url = console.getByLabel("Base URL"); - const verify = console.getByRole("button", { name: "验证示例连接" }); - await expect(url).toBeVisible(); - await expect(verify).toBeDisabled(); - - for (const [value, message] of [ - ["ftp://api.example.com", "需要以 http:// 或 https:// 开头。"], - ["https://user:secret@api.example.com", "请从地址中去掉用户名或密码。"], - ]) { - await url.fill(value); - await expect(console.locator("[data-custom-hint]")).toHaveText(message); - await expect(url).toHaveAttribute("aria-invalid", "true"); - await expect(verify, `${value} must not be verifiable`).toBeDisabled(); - } - - await url.fill("https://api.example.com/openai"); - await expect(console.locator("[data-custom-hint]")).toHaveText("端点可用"); - await expect(verify).toBeEnabled(); - await verify.click(); - - // A custom endpoint publishes no catalog, so the demo shows the app's real - // recovery — type the id — rather than inventing a discovered list. - await expect(console).toHaveAttribute("data-phase", "model"); - await expect(console.locator("[data-model-grid]")).toBeHidden(); - await console.getByLabel("模型 ID").fill("deepseek/deepseek-v3"); - await console.getByRole("button", { name: "确认激活" }).click(); - await expect(console).toHaveAttribute("data-phase", "ready"); - await expect(console.locator("[data-result-provider]")).toHaveText("api.example.com"); - expect(fetches, "typing an endpoint must not make the page call it").toEqual([]); -}); - -// Reusing an existing account is a real branch in the product's ConfigModePage, -// and it exists precisely because no new credential is introduced — so the demo -// must not walk it through provider and model steps it genuinely skips. -test("activation demo skips provider and model for an existing account", async ({ page }) => { - await page.goto("/"); - const console = page.locator("#activation-console"); - await page.getByRole("button", { name: "开始激活演示" }).click(); - await console.getByRole("button", { name: /Claude Code/ }).click(); - await expect(console).toHaveAttribute("data-phase", "mode"); - await console.getByRole("button", { name: /使用已有账号或配置/ }).click(); - - await expect(console).toHaveAttribute("data-phase", "ready"); - await expect(console.getByRole("heading", { name: "保留现有账号" })).toBeVisible(); - await expect(console.locator(".activation-steps li.is-skipped")).toHaveCount(2); - await expect(console.locator("[data-result-provider]")).toHaveText("已跳过"); - await expect(console.locator("[data-log]")).toContainText("Provider 与模型步骤已跳过"); -}); - -test("activation demo keeps a complete event history under reduced motion", async ({ page }) => { - await page.emulateMedia({ reducedMotion: "reduce" }); - await page.goto("/"); - const console = page.locator("#activation-console"); - /* Advancing the interface on its own is motion, so reduced motion means the - console waits to be asked. That is also what keeps the rest of this test - valid: nothing has moved off idle before the first click. */ - await console.scrollIntoViewIfNeeded(); - await expect(console).toHaveAttribute("data-phase", "idle"); - await expect(console).not.toHaveAttribute("data-autoplaying", "true"); - await page.getByRole("button", { name: "开始激活演示" }).click(); - await expect(console).toHaveAttribute("data-phase", "agent"); - await expect(console.locator("[data-log] li")).toHaveCount(2); - await console.getByRole("button", { name: /Claude Code/ }).click(); - await console.getByRole("button", { name: /配置模型服务/ }).click(); - await console.getByRole("button", { name: /PPIO/ }).click(); - await console.getByRole("button", { name: "验证示例连接" }).click(); - await expect(console).toHaveAttribute("data-phase", "model"); - await console.getByRole("button", { name: "deepseek/deepseek-v3" }).click(); - await console.getByRole("button", { name: "确认激活" }).click(); - await expect(console).toHaveAttribute("data-phase", "ready"); - await expect(console.locator("[data-log]")).toContainText("示例协议验证完成"); - const animations = await console.evaluate((element) => - element.getAnimations({ subtree: true }).filter((animation) => animation.playState === "running").length, - ); - expect(animations).toBe(0); -}); - -/* The landing page has to show what activation looks like to someone who clicks - nothing at all — that is the whole reason autoplay exists. */ -test("activation demo plays itself through to Ready without a click", async ({ page }) => { - const fetches: string[] = []; - page.on("request", (request) => { - if (["fetch", "xhr"].includes(request.resourceType())) fetches.push(request.url()); - }); - await page.goto("/"); - const console = page.locator("#activation-console"); - await console.scrollIntoViewIfNeeded(); - - await expect(console).toHaveAttribute("data-phase", "ready"); - await expect(console.getByRole("heading", { name: "示例环境已 Ready" })).toBeVisible(); - // Reaching the end clears the flag, so the visitor is in charge from here. - await expect(console).not.toHaveAttribute("data-autoplaying", "true"); - await expect(console.locator("[data-log]")).toContainText("示例环境可进入 Ready"); - expect(fetches, "an unattended demo must not call anything either").toEqual([]); -}); - -// Autoplay is a demonstration, not a ride: the moment someone reaches for the -// console it belongs to them, and it must not resume behind their back. -test("interacting during autoplay stops it where it stands", async ({ page }) => { - await page.goto("/"); - const console = page.locator("#activation-console"); - await console.scrollIntoViewIfNeeded(); - await expect(console).toHaveAttribute("data-autoplaying", "true"); - - /* Takes over at a step that is waiting on a choice, then asserts the phase - stops changing. - * - * `scanning` has to be excluded, and not because of timing: taking over mid-scan - * still lands on `agent`, since start()'s own 650ms timer resolves the scan the - * same way a clicked run would. Leaving a spinner up forever would be the worse - * behaviour, so that transition is correct — it just is not "autoplay resumed", - * which is what this test is about. - * - * Reading the phase and comparing it against itself, rather than waiting for one - * named step and asserting that name later, is what keeps this stable. The - * earlier version waited for `agent` and expected `agent` 2.5s on; but `agent` - * lasts only ~850ms (scan resolves into it near t=1070ms, the script leaves it - * at t=1920ms), so under the full suite's three-viewport load the click landed - * after the script had moved on and the assertion failed on a name mismatch. */ - /* Waits for a step that is genuinely waiting on input. `idle` and `scanning` - both fail the "not scanning" test for different reasons — one has not started, - the other resolves on its own — so the wait is written as a positive match on - the steps where the demo is parked on a choice. Whichever one the script has - reached by click time is fine; the assertion below compares against it rather - than against a fixed name. */ - await expect(console).toHaveAttribute("data-phase", /^(agent|mode|provider|model|review)$/); - await console.locator("[data-log]").click(); - await expect(console).not.toHaveAttribute("data-autoplaying", "true"); - const takenOverAt = await console.getAttribute("data-phase"); - expect(takenOverAt, "takeover must be measured at a decision point").not.toBe("scanning"); - // Longer than the widest gap in AUTOPLAY_SCRIPT (1500ms), so a resumed script - // would have advanced at least one step by the time this returns. - await page.waitForTimeout(2500); - expect(await console.getAttribute("data-phase"), "autoplay resumed after takeover").toBe(takenOverAt); - - // Taken over, not broken: the trigger still replays from the top. - await page.getByRole("button", { name: "开始激活演示" }).click(); - await expect(console).toHaveAttribute("data-phase", "agent"); - await expect(console).not.toHaveAttribute("data-autoplaying", "true"); -}); - -/* The failure this guards is specific and easy to reintroduce: every step calls - focusPanel, and if autoplay keeps doing that it steals focus from whatever the - visitor is reading or tabbing through — on a page they never interacted with. */ -test("autoplay never takes keyboard focus", async ({ page }) => { - await page.goto("/"); - const console = page.locator("#activation-console"); - await console.scrollIntoViewIfNeeded(); - - const insideConsole = () => - page.evaluate(() => Boolean(document.querySelector("#activation-console")?.contains(document.activeElement))); - for (let sample = 0; sample < 12; sample += 1) { - expect(await insideConsole(), "autoplay moved focus into the console").toBe(false); - await page.waitForTimeout(250); - } - await expect(console).toHaveAttribute("data-phase", "ready"); -}); - -test("Explorer restores shareable state and returns focus after the drawer closes", async ({ page }) => { - await page.goto("/explore/?agent=claude-code&provider=ppio&platform=macos&protocol=anthropic"); - const explorer = page.locator("compatibility-explorer"); - const drawer = explorer.locator("[data-explorer-drawer]"); - await expect(drawer).toBeVisible(); - await expect(drawer.getByRole("heading", { name: "Claude Code" })).toBeVisible(); - await expect(explorer.locator('[data-filter="platform"]')).toHaveValue("macos"); - await expect(explorer.locator('[data-filter="provider"]')).toHaveValue("ppio"); - await expect(explorer.locator('[data-filter="protocol"]')).toHaveValue("anthropic"); - - await drawer.getByRole("button", { name: "关闭详情" }).click(); - await expect(page).not.toHaveURL(/agent=/); - - const card = explorer.locator('[data-agent-card][data-agent-id="claude-code"]'); - await card.focus(); - await page.keyboard.press("Enter"); - await expect(drawer).toBeVisible(); - await page.keyboard.press("Escape"); - await expect(drawer).toBeHidden(); - await expect(card).toBeFocused(); -}); - -test("Explorer filters only catalog-backed combinations", async ({ page }) => { - await page.goto("/explore/"); - const explorer = page.locator("compatibility-explorer"); - await explorer.locator('[data-filter="platform"]').selectOption("windows"); - await explorer.locator('[data-filter="protocol"]').selectOption("responses"); - await explorer.locator('[data-filter="provider"]').selectOption("ppio"); - const visible = explorer.locator("[data-agent-card]:visible"); - await expect(visible).toHaveCount(1); - await expect(visible).toHaveAttribute("data-agent-id", "codex"); - await expect(page).toHaveURL(/platform=windows/); - await expect(page).toHaveURL(/protocol=responses/); - await expect(page).toHaveURL(/provider=ppio/); -}); - -/* The download page renders from whatever the GitHub Releases feed returned at - build time. With no published release the whole platform picker is replaced by - a "not published yet" notice, which is the correct page — but it means the - picker tests have nothing to drive. - * - * They skip rather than being deleted or loosened. A release exists in the repo - * this site ships from, so these assertions are the ones that catch a regression - * there; silently dropping them would mean the download flow rots unnoticed the - * next time a build does have artifacts. The skip reason names the cause so a red - * run is not mistaken for a broken picker. - */ -async function skipWithoutPublishedRelease(page: Page) { - await page.goto("/downloads/"); - const hasPicker = await page.locator("[data-platform-picker]").count(); - test.skip(hasPicker === 0, "no release is published, so the page renders the unavailable notice"); -} - -/* The expected digest used to be read from the site's own release-index.json and - compared against the page. That artifact is gone — release data now comes from - GitHub Releases at build time — so there is no second copy to cross-check - against, and asserting the shape of what the page prints is what remains. A - 64-hex digest and a real download link are still worth gating: an empty or - truncated checksum is exactly what a reader cannot verify with. */ -test("download center recommends an available artifact but keeps manual choices", async ({ page }) => { - await skipWithoutPublishedRelease(page); - const picker = page.getByRole("group", { name: "选择平台与架构" }); - await expect(picker).toBeVisible(); - const platform = (id: string) => picker.locator(`input[type="radio"][value="${id}"]`); - await platform("windows-x64").check(); - await expect(page.getByRole("heading", { name: "这个平台尚未公开发行" })).toBeVisible(); - await platform("macos-arm64").check(); - await expect(page.getByRole("link", { name: "下载 macOS 预览版" })).toBeVisible(); - await expect(page.locator("[data-release-panel].is-active .hash-value")).toHaveText(/^[a-f0-9]{64}$/); - await expect(page.getByText("未签名、未公证", { exact: true })).toBeVisible(); -}); - -/* Security and enterprise came out of the chrome, but the pages did not go - anywhere. The security page is where the release evidence, the privacy - statement and the Stable gate are written down, and the footer is now the only - route to it, so removing the nav entry and keeping the page reachable are - asserted together — otherwise a later cleanup drops the page and the trust - claims leave with it. The #privacy and #release-evidence anchors are checked - because other documents cite them directly, not just the page. */ -test("security and enterprise leave the navigation but stay reachable", async ({ page }) => { - for (const path of ["/", "/en/"]) { - await page.goto(path); - await expect(page.locator(".site-header").getByRole("link", { name: /安全|Security/ })).toHaveCount(0); - await expect(page.locator(".site-header").getByRole("link", { name: /企业服务|Enterprise/ })).toHaveCount(0); - await expect(page.locator(".site-footer").getByRole("link", { name: /安全与隐私|Security & privacy/ })).toHaveCount(0); - await expect(page.locator(".site-footer").getByRole("link", { name: /企业服务|Enterprise/ })).toHaveCount(0); - // The footer's other two entries are the only route to them, so they stay. - await expect(page.locator(".site-footer").getByRole("link", { name: /支持与反馈|Support & feedback/ })).toBeVisible(); - await expect(page.locator(".site-footer").getByRole("link", { name: "GitHub Releases" })).toBeVisible(); - } - - for (const path of ["/security/", "/en/security/", "/enterprise/"]) { - const response = await page.goto(path); - expect(response?.status(), `${path} must stay published`).toBe(200); - await expect(page.getByRole("heading", { level: 1 })).toBeVisible(); - } - await page.goto("/security/"); - await expect(page.locator("#privacy")).toBeAttached(); - await expect(page.locator("#release-evidence")).toBeAttached(); -}); - -// The picker is a real radio group rather than a styled listbox, so arrow keys -// have to move the selection — that behaviour is the reason for the markup. -test("platform picker is keyboard operable", async ({ page }) => { - await skipWithoutPublishedRelease(page); - const picker = page.getByRole("group", { name: "选择平台与架构" }); - await picker.locator('input[value="macos-arm64"]').focus(); - await page.keyboard.press("ArrowDown"); - await expect(picker.locator('input[value="macos-x64"]')).toBeChecked(); - await expect(page.locator('[data-release-panel="macos-x64"]')).toHaveClass(/is-active/); - await page.keyboard.press("ArrowUp"); - await expect(picker.locator('input[value="macos-arm64"]')).toBeChecked(); - await expect(page.getByRole("link", { name: "下载 macOS 预览版" })).toBeVisible(); - // The ring belongs to the card, not the 16px dot inside it. - await expect(picker.locator('input[value="macos-arm64"]')).toHaveCSS("outline-style", "none"); -}); - -// The detection note is a server-rendered localised template that the client -// fills in from the DOM. Reading those templates off the wrong element leaves -// every branch as an empty string, which blanks the note instead of failing -// loudly — so both locales assert real text with no placeholder left behind. -for (const [route, expected] of [ - ["/downloads/", "已识别为"], - ["/en/downloads/", "Detected as"], -] as const) { - test(`the download page explains its platform detection in the page's language (${route})`, async ({ page }) => { - await page.goto(route); - const note = page.locator("[data-detected-note]"); - // The note lives inside the platform picker, which is absent when no release - // is published. Same reason as skipWithoutPublishedRelease above. - test.skip((await note.count()) === 0, "no release is published, so the page renders the unavailable notice"); - await expect(note).toContainText(expected); - await expect(note).not.toContainText("{"); - }); -} - -test("guide-only compatibility remains distinct from managed installation", async ({ page }) => { - await page.goto("/agents/cursor/"); - await expect(page.getByText("按官方方式安装", { exact: true })).toBeVisible(); - await expect(page.getByText("由 Agent 官方流程管理", { exact: true })).toBeVisible(); - await expect(page.getByText("OneAgent 可管理安装", { exact: true })).toHaveCount(0); -}); - -/* Replaces a test that fetched the site's own release-index.json and asserted the - artifact it published. The site no longer publishes that file, so the guarantee - worth keeping is the one a reader depends on: the download page states the - channel honestly, prints a digest they can check, and sends them to the - official release rather than a mirror the site invented. */ -test("download page states the channel and links to the official release", async ({ page }) => { - await skipWithoutPublishedRelease(page); - /* Scoped to the active panel throughout. All four platform panels are in the - DOM and only CSS hides the inactive ones, so an unscoped text match resolves - against a hidden copy first and fails on visibility. */ - const active = page.locator("[data-release-panel].is-active"); - await expect(active.getByText("未签名技术预览版", { exact: true })).toBeVisible(); - await expect(active.locator(".hash-value")).toHaveText(/^[a-f0-9]{64}$/); - const download = active.getByRole("link", { name: /下载 .* 预览版/ }); - await expect(download).toHaveAttribute("href", /^https:\/\/github\.com\/[^/]+\/[^/]+\/releases\/download\//); -}); - -test("serves its own stylesheet and Agent marks rather than 404ing on them", async ({ page }) => { - // A base path build (SITE_URL/BASE_PATH, as the Pages job uses) emits an - // absolute , and the CSP declares base-uri 'self'. Serving such a - // build from a different origin makes the browser reject the base tag, every - // asset path 404s, and each page still answers 200 as unstyled HTML. Only the - // rendered result catches that, so assert on it. - const missing: string[] = []; - page.on("response", (response) => { - if (response.status() >= 400) missing.push(`${response.status()} ${new URL(response.url()).pathname}`); - }); - await page.goto("/agents/", { waitUntil: "networkidle" }); - expect(missing, "every asset the page asks for must exist").toEqual([]); - // A stylesheet that failed to load leaves the UA default, not this palette. - await expect(page.locator("body")).toHaveCSS("background-color", "rgb(236, 236, 239)"); - const brokenMarks = await page.evaluate( - () => document.images.length && [...document.images].filter((image) => !image.complete || image.naturalWidth === 0).length, - ); - expect(brokenMarks, "Agent marks must render").toBe(0); -}); - -test.describe("hero particles", () => { - const frameDigest = (path: string) => - `(() => { const c = document.querySelector('${path}'); const d = c.getContext('2d').getImageData(0, 0, c.width, c.height).data; let h = 0; for (let i = 0; i < d.length; i += 97) h = (h * 31 + d[i]) | 0; return h; })()`; - - test("animates by default", async ({ page }) => { - await page.goto("/"); - const canvas = page.locator("[data-hero-particles]"); - await expect(canvas).toHaveAttribute("aria-hidden", "true"); - const first = await page.evaluate(frameDigest("[data-hero-particles]")); - await page.waitForTimeout(400); - expect(await page.evaluate(frameDigest("[data-hero-particles]"))).not.toBe(first); - }); - - // The CSS prefers-reduced-motion block only clamps CSS animations, so the - // canvas has to opt out in script. Only a real reduced-motion context proves it. - test("paints a static frame under reduced motion", async ({ browser }) => { - const page = await browser.newPage({ reducedMotion: "reduce" }); - await page.goto("/"); - const first = await page.evaluate(frameDigest("[data-hero-particles]")); - await page.waitForTimeout(400); - expect(await page.evaluate(frameDigest("[data-hero-particles]"))).toBe(first); - await page.close(); - }); -}); - -test("ships a local-only content policy and no third-party scripts", async ({ page }) => { - await page.goto("/"); - const policy = await page.locator('meta[http-equiv="Content-Security-Policy"]').getAttribute("content"); - expect(policy).toContain("default-src 'self'"); - expect(policy).toContain("object-src 'none'"); - expect(policy).toContain("form-action 'self'"); - await expect(page.locator('script[src^="http://"], script[src^="https://"]')).toHaveCount(0); - await expect(page.locator('iframe, object, embed')).toHaveCount(0); -}); - -for (const path of criticalPages) { - test(`has no serious accessibility violations: ${path}`, async ({ page }) => { - await page.goto(path); - await page.addScriptTag({ content: axe.source }); - const result = await page.evaluate(async () => { - const axeApi = (window as typeof window & { axe: typeof axe }).axe; - return axeApi.run(document, { runOnly: { type: "tag", values: ["wcag2a", "wcag2aa"] } }); - }); - const serious = result.violations.filter((violation) => violation.impact === "serious" || violation.impact === "critical"); - expect(serious, JSON.stringify(serious, null, 2)).toEqual([]); - }); -} - -// axe treats as an image node and gives up resolving the background -// behind it, so every hero and header text node comes back "incomplete" rather -// than pass — the particle layer hides exactly the copy most worth checking. -// Dropping the decoration first is what actually gates those colours. -test("hero text contrast is proven once the decorative canvas is removed", async ({ page }) => { - await page.goto("/"); - await page.addScriptTag({ content: axe.source }); - const result = await page.evaluate(async () => { - document.querySelector("[data-hero-particles]")?.remove(); - const axeApi = (window as typeof window & { axe: typeof axe }).axe; - return axeApi.run(document, { runOnly: ["color-contrast"] }); - }); - expect(result.violations, JSON.stringify(result.violations, null, 2)).toEqual([]); - expect(result.incomplete, JSON.stringify(result.incomplete, null, 2)).toEqual([]); -}); - -// Navigation animates via the native cross-document path, so the proof is that -// the outgoing document reports a live transition on pageswap. Asserting on the -// CSS alone would still pass if the browser declined to run it. -// Below 920px the nav collapses into a disclosure menu, and the transition is a -// document-level behaviour that does not vary by viewport — one width proves it. -test("navigating between pages runs a cross-document view transition", async ({ page, viewport }) => { - test.skip((viewport?.width ?? 0) < 920, "nav links are collapsed at this width"); - await page.goto("/"); - await page.evaluate(() => { - window.addEventListener("pageswap", (event) => { - sessionStorage.setItem("vt-ran", (event as PageSwapEvent).viewTransition ? "yes" : "no"); - }); - }); - await page.getByLabel("主导航").getByRole("link", { name: "配置", exact: true }).click(); - await expect(page).toHaveURL(/\/explore\/$/); - expect(await page.evaluate(() => sessionStorage.getItem("vt-ran"))).toBe("yes"); - // The shared chrome opts out of the crossfade by being named on both pages. - await expect(page.locator(".site-header")).toHaveCSS("view-transition-name", "site-header"); -}); - -test.describe("hero entrance", () => { - const heroParts = [".eyebrow", ".display", ".lede", ".hero-actions", ".hero-note", ".product-shot"]; - - test("staggers the hero into place on first paint", async ({ page, viewport }) => { - await page.goto("/"); - const delays = await page.evaluate(() => - document - .getAnimations() - .filter((animation) => (animation as CSSAnimation).animationName === "hero-rise") - .map((animation) => Number((animation.effect as KeyframeEffect).getTiming().delay)) - .sort((a, b) => a - b), - ); - if ((viewport?.width ?? 0) <= 680) { - // Stacked layout moves the block, not the lines — see the note in global.css. - expect(delays).toEqual([0, 180]); - } else { - expect(delays).toEqual([0, 70, 160, 230, 290, 360]); - } - }); - - // The previous attempt faded these in, which dropped the largest type on the - // site below AA for the whole animation. Auditing only the settled page would - // not have caught it, so sample while the entrance is still running. - test("keeps hero text readable while the entrance runs", async ({ page }) => { - await page.goto("/"); - await page.addScriptTag({ content: axe.source }); - const worst = await page.evaluate(async (parts) => { - document.querySelector("[data-hero-particles]")?.remove(); - for (const selector of [...parts, ".hero-copy"]) { - const element = document.querySelector(selector); - if (!element) continue; - element.style.animation = "none"; - void element.offsetHeight; - element.style.animation = ""; - } - const axeApi = (window as typeof window & { axe: typeof axe }).axe; - let violations = 0; - for (let sample = 0; sample < 4; sample += 1) { - await new Promise((resolve) => setTimeout(resolve, 90)); - const result = await axeApi.run(document, { runOnly: ["color-contrast"] }); - violations += result.violations.reduce((total, entry) => total + entry.nodes.length, 0); - } - return violations; - }, heroParts); - expect(worst, "hero copy must clear AA at every frame of the entrance").toBe(0); - }); -}); - -test.describe("english locale", () => { - // Derived from the route list rather than written out, so adding a translation - // without auditing its English page is not possible. - const englishPages = translatedRoutes.map((route) => `/en/${route}`); - - for (const path of englishPages) { - test(`has no serious accessibility violations: ${path}`, async ({ page }) => { - await page.goto(path); - await expect(page.locator("html")).toHaveAttribute("lang", "en"); - await page.addScriptTag({ content: axe.source }); - const result = await page.evaluate(async () => { - document.querySelector("[data-hero-particles]")?.remove(); - const axeApi = (window as typeof window & { axe: typeof axe }).axe; - return axeApi.run(document, { runOnly: { type: "tag", values: ["wcag2a", "wcag2aa"] } }); - }); - const serious = result.violations.filter( - (violation) => violation.impact === "serious" || violation.impact === "critical", - ); - expect(serious, JSON.stringify(serious, null, 2)).toEqual([]); - }); - } - - test("declares reciprocal hreflang alternates with an x-default", async ({ page }) => { - for (const path of ["/", "/en/", "/explore/", "/en/explore/", "/security/", "/en/security/"]) { - await page.goto(path); - const codes = await page.evaluate(() => - [...document.querySelectorAll('link[rel="alternate"][hreflang]')].map((link) => link.getAttribute("hreflang")), - ); - expect(new Set(codes), `alternates on ${path}`).toEqual(new Set(["zh-CN", "en", "x-default"])); - } - }); - - // An untranslated route falls back to Chinese rather than 404ing, which is the - // right call — but silently swapping the reader's language mid-navigation is - // not. Every link that does it has to say so before the click. - test("marks links that fall back to Chinese, and only on English pages", async ({ page }) => { - await page.goto("/en/"); - /* Security and enterprise left the nav, so the specific links this used to - name are gone. The sweep below is the real guarantee and covers whatever - is in the nav now — including the CTA band's untranslated team-services - link, which is where the hint has to appear on this page. */ - - // Every link leaving /en/ for an untranslated route carries the hint. - const unmarked = await page.evaluate((translated) => { - return [...document.querySelectorAll("a[href^='/']")] - .filter((link) => { - const href = link.getAttribute("href") ?? ""; - if (href.startsWith("/en/") || /\.(json|txt|webmanifest)$/.test(href)) return false; - const route = href.replace(/^\//, ""); - return !translated.includes(route); - }) - .filter((link) => !link.querySelector(".lang-hint")) - .map((link) => link.getAttribute("href")); - }, translatedRoutes); - expect(unmarked, "these links change language without saying so").toEqual([]); - - // The hint is about leaving English; a Chinese reader is already there. - await page.goto("/"); - await expect(page.locator(".lang-hint")).toHaveCount(0); - }); - - // Switching language has to keep the reader on the page they were reading; - // dropping them on the home page is the usual failure here. - test("language switch stays on the equivalent page", async ({ page, viewport }) => { - test.skip((viewport?.width ?? 0) < 920, "header actions are collapsed at this width"); - await page.goto("/downloads/"); - await page.getByRole("link", { name: "切换语言" }).click(); - await expect(page).toHaveURL(/\/en\/downloads\/$/); - await expect(page.locator("html")).toHaveAttribute("lang", "en"); - - await page.getByRole("link", { name: "Change language" }).click(); - await expect(page).toHaveURL(/\/downloads\/$/); - await expect(page.locator("html")).toHaveAttribute("lang", "zh-CN"); - }); - - test("language switch preserves the Explorer route", async ({ page, viewport }) => { - test.skip((viewport?.width ?? 0) < 920, "header actions are collapsed at this width"); - await page.goto("/explore/?platform=macos"); - await page.getByRole("link", { name: "切换语言" }).click(); - await expect(page).toHaveURL(/\/en\/explore\/$/); - await expect(page.locator("html")).toHaveAttribute("lang", "en"); - await page.getByRole("link", { name: "Change language" }).click(); - await expect(page).toHaveURL(/\/explore\/$/); - }); - - // Every link on an English page must resolve; an untranslated destination is - // expected to fall back to Chinese rather than 404 under /en/. - test("navigation from an english page never lands on a missing route", async ({ page }) => { - const missing: string[] = []; - page.on("response", (response) => { - if (response.status() >= 400) missing.push(`${response.status()} ${new URL(response.url()).pathname}`); - }); - await page.goto("/en/", { waitUntil: "networkidle" }); - const targets = await page.evaluate(() => - [...document.querySelectorAll("a[href]")] - .map((anchor) => anchor.href) - .filter((href) => href.startsWith(location.origin)), - ); - for (const target of new Set(targets)) { - const response = await page.request.get(target); - expect(response.status(), `${target} from /en/`).toBeLessThan(400); - } - expect(missing, "assets on /en/ must all exist").toEqual([]); - }); -}); - -test.describe("dark scheme", () => { - // The light palette needed five values darkened to clear AA; the dark one is a - // second, independent set of colours over different grounds, so it needs the - // same gate rather than an assumption that inverting is safe. - for (const path of criticalPages) { - test(`has no serious accessibility violations in the dark: ${path}`, async ({ page }) => { - await page.emulateMedia({ colorScheme: "dark" }); - await page.goto(path); - await expect(page.locator("html")).toHaveClass(/theme-dark/); - await page.addScriptTag({ content: axe.source }); - const result = await page.evaluate(async () => { - document.querySelector("[data-hero-particles]")?.remove(); - const axeApi = (window as typeof window & { axe: typeof axe }).axe; - return axeApi.run(document, { runOnly: { type: "tag", values: ["wcag2a", "wcag2aa"] } }); - }); - const serious = result.violations.filter( - (violation) => violation.impact === "serious" || violation.impact === "critical", - ); - expect(serious, JSON.stringify(serious, null, 2)).toEqual([]); - }); - } - - // axe does not measure image contrast, so nothing above would have caught the - // Codex and OpenCode marks going invisible: they are fill="currentColor" and an - // resolves that to black regardless of the page. - test("keeps currentColor agent marks visible without touching brand marks", async ({ page }) => { - await page.emulateMedia({ colorScheme: "dark" }); - await page.goto("/agents/"); - const filters = await page.evaluate(() => - [...document.querySelectorAll(".agent-mark-wrap img")].map((image) => ({ - file: image.src.split("/").pop() ?? "", - monochrome: image.hasAttribute("data-monochrome"), - filter: getComputedStyle(image).filter, - })), - ); - const monochrome = filters.filter((entry) => entry.monochrome); - expect(monochrome.map((entry) => entry.file).sort()).toEqual(["codex.svg", "opencode.svg"]); - for (const entry of monochrome) expect(entry.filter, entry.file).toBe("invert(1)"); - for (const entry of filters.filter((entry) => !entry.monochrome)) { - expect(entry.filter, `${entry.file} carries its own colours`).toBe("none"); - } - - await page.emulateMedia({ colorScheme: "light" }); - await page.goto("/agents/"); - const light = await page.evaluate(() => - [...document.querySelectorAll(".agent-mark-wrap img")].map( - (image) => getComputedStyle(image).filter, - ), - ); - expect(new Set(light), "no mark is inverted in the light scheme").toEqual(new Set(["none"])); - }); - - test("remembers an explicit choice over the system preference", async ({ page }) => { - await page.emulateMedia({ colorScheme: "light" }); - await page.goto("/"); - await expect(page.locator("html")).not.toHaveClass(/theme-dark/); - - const toggle = page.getByRole("button", { name: "切换深色模式" }); - await toggle.click(); - await expect(page.locator("html")).toHaveClass(/theme-dark/); - await expect(toggle).toHaveAttribute("aria-pressed", "true"); - - // The inline head script is what makes the choice survive without a flash. - await page.reload(); - await expect(page.locator("html")).toHaveClass(/theme-dark/); - await expect(page.getByRole("button", { name: "切换深色模式" })).toHaveAttribute("aria-pressed", "true"); - - await page.getByRole("button", { name: "切换深色模式" }).click(); - await expect(page.locator("html")).not.toHaveClass(/theme-dark/); - await page.reload(); - await expect(page.locator("html")).not.toHaveClass(/theme-dark/); - }); - - test("paints the resolved theme before first contentful paint", async ({ page }) => { - await page.emulateMedia({ colorScheme: "dark" }); - await page.goto("/", { waitUntil: "commit" }); - // Sampling at commit catches a theme applied late: if the class were set by - // the deferred module script, the light palette would paint first. - expect(await page.evaluate(() => document.documentElement.className)).toContain("theme-dark"); - }); -}); - -// The interactive console must share the hero copy measure and size to its own -// content at every breakpoint. A clipped wizard can visually cover the next -// section even when overflow hides the pixels. -test("the activation console sits on the same measure as the copy", async ({ page }) => { - await page.goto("/"); - await page.evaluate(() => - Promise.all( - document - .getAnimations() - .filter((animation) => (animation as CSSAnimation).animationName === "hero-rise") - .map((animation) => animation.finished), - ), - ); - const layout = await page.evaluate(() => { - const edges = (selector: string) => { - const rect = document.querySelector(selector)!.getBoundingClientRect(); - return { left: Math.round(rect.left), right: Math.round(rect.right) }; - }; - const panel = document.querySelector("[data-console-window]")!; - const rect = panel.getBoundingClientRect(); - return { - copy: edges(".hero-copy"), - console: edges(".product-shot"), - contentOverflow: panel.scrollHeight - Math.round(rect.height), - gapBelowConsole: Math.round( - document.querySelector(".hero + .section")!.getBoundingClientRect().top - - document.querySelector(".product-shot")!.getBoundingClientRect().bottom, - ), - }; - }); - expect(layout.console).toEqual(layout.copy); - expect(layout.contentOverflow, "the activation console must not clip its own content").toBeLessThanOrEqual(1); - expect(layout.gapBelowConsole, "the console must not butt against the next section").toBeGreaterThan(16); -}); - -// Synthetic mouse events never set :hover, so the resting-vs-hovered difference -// is only observable by moving a real pointer. -test("interactive rows and cards respond to hover", async ({ page, viewport }) => { - test.skip((viewport?.width ?? 0) < 920, "hover is not the primary input at this width"); - - await page.goto("/agents/"); - const row = page.locator(".agent-row").first(); - await expect(row).toHaveCSS("background-color", "rgba(0, 0, 0, 0)"); - await row.hover(); - await expect(row).toHaveCSS("background-color", "rgb(255, 255, 255)"); - await expect(row).not.toHaveCSS("box-shadow", "none"); - - await page.goto("/providers/"); - const card = page.locator(".provider-card").first(); - const restingBorder = await card.evaluate((el) => getComputedStyle(el).borderColor); - await card.hover(); - await expect(card).not.toHaveCSS("border-color", restingBorder); - await expect(card).not.toHaveCSS("transform", "none"); -}); diff --git a/site/package.json b/site/package.json deleted file mode 100644 index 6aea6b1..0000000 --- a/site/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "oneagent-public-site", - "private": true, - "type": "module", - "packageManager": "pnpm@11.17.0", - "scripts": { - "dev": "astro dev", - "build": "pnpm run check && astro build && pnpm run validate", - "check": "astro check", - "test": "vitest run", - "test:e2e": "playwright test", - "preview": "astro preview", - "validate": "node scripts/validate-build.mjs", - "test:all": "pnpm test && pnpm run build && pnpm run test:e2e" - }, - "dependencies": { - "@astrojs/sitemap": "3.7.3", - "astro": "7.1.4" - }, - "devDependencies": { - "@astrojs/check": "0.9.10", - "@playwright/test": "1.62.0", - "axe-core": "4.12.1", - "jsdom": "30.0.0", - "typescript": "6.0.3", - "vitest": "4.1.10" - } -} diff --git a/site/playwright.config.ts b/site/playwright.config.ts deleted file mode 100644 index be8506a..0000000 --- a/site/playwright.config.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { defineConfig, devices } from "@playwright/test"; - -export default defineConfig({ - testDir: "./e2e", - fullyParallel: true, - reporter: "list", - /* Trace teardown writes its zip inside the per-test budget, and on a full - three-project parallel run that write was itself timing out — turning - passing tests into failures whose only error was "Fixture 'trace recording' - timeout during teardown". The suite is ~1.4 min without it and was 4-7 min - with it. Traces are still captured on failure; they just get their own room - to finish, and the per-test budget is no longer shared with the recorder. */ - timeout: 60_000, - expect: { timeout: 10_000 }, - use: { - baseURL: "http://127.0.0.1:4321", - trace: "retain-on-failure", - }, - webServer: { - command: "pnpm run build && pnpm run preview --host 127.0.0.1 --port 4321", - url: "http://127.0.0.1:4321", - reuseExistingServer: process.env.CI !== "true", - }, - projects: [ - { name: "desktop", use: { ...devices["Desktop Chrome"], viewport: { width: 1440, height: 1000 } } }, - { name: "tablet", use: { ...devices["Desktop Chrome"], viewport: { width: 768, height: 900 } } }, - { name: "mobile", use: { ...devices["Desktop Chrome"], viewport: { width: 375, height: 812 } } }, - ], -}); diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml deleted file mode 100644 index 27696a8..0000000 --- a/site/pnpm-lock.yaml +++ /dev/null @@ -1,4024 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@astrojs/sitemap': - specifier: 3.7.3 - version: 3.7.3 - astro: - specifier: 7.1.4 - version: 7.1.4(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(@types/node@24.13.3)(yaml@2.9.0) - devDependencies: - '@astrojs/check': - specifier: 0.9.10 - version: 0.9.10(prettier@3.9.6)(typescript@6.0.3) - '@playwright/test': - specifier: 1.62.0 - version: 1.62.0 - axe-core: - specifier: 4.12.1 - version: 4.12.1 - jsdom: - specifier: 30.0.0 - version: 30.0.0 - typescript: - specifier: 6.0.3 - version: 6.0.3 - vitest: - specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(jsdom@30.0.0)(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(yaml@2.9.0)) - -packages: - - '@asamuzakjp/css-color@6.0.5': - resolution: {integrity: sha512-mbhpPMmnw/kwW19aRNmSUl1QzLbdGo1SCuE49BT98MNwqF6zaHb3o2owssFc/PEO/4t2UjqtCNwocuDtJornzA==} - engines: {node: ^22.13.0 || >=24.0.0} - - '@asamuzakjp/dom-selector@8.3.2': - resolution: {integrity: sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==} - engines: {node: ^22.13.0 || >=24.0.0} - - '@astrojs/check@0.9.10': - resolution: {integrity: sha512-zgx/UQMozdjOa3bOxjgeCFdtpE3c9rRX6xHwa+2QXvy8z8Akifu2AtubHyv/zzC2znO8dl8fFWL4K+Ba9kS8HQ==} - hasBin: true - peerDependencies: - typescript: ^5.0.0 || ^6.0.0 - - '@astrojs/compiler-binding-darwin-arm64@0.3.2': - resolution: {integrity: sha512-MM8tn8CSimcfytaOla4b6acN8mKWiL/rlAA1fpT3/Wl7dNGSE4y8FjTN/zJVNnb63CsLWG5zZwCt01TXtDKh9g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@astrojs/compiler-binding-darwin-x64@0.3.2': - resolution: {integrity: sha512-2lXOlzf8xb7jLomRsf/aswh61/NnGusynB2OwFkK6k4pmOtpfXMYnG0PLfXrEvxXYj69NdCnmUYXtHDd+JOOag==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@astrojs/compiler-binding-linux-arm64-gnu@0.3.2': - resolution: {integrity: sha512-BmU3kWj7qnLrd4vzm49zFEPJ5oFnn1tCT4Vt9hZbqdU5Cmb8GZl7fn6VFsnNfe7B18a2gIFtVzbLINtYl5kBjQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@astrojs/compiler-binding-linux-arm64-musl@0.3.2': - resolution: {integrity: sha512-f0heT9ZZEseSu5bHCeb80eL2DH07ArE6U9xi1WT/PEusNjzPmEr3GJsjG1tRLo5VYUUYX7h3ScaqGmGrMOVGmw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@astrojs/compiler-binding-linux-x64-gnu@0.3.2': - resolution: {integrity: sha512-M8fOUt0itRpqiGyoEA/ij184s8O+hqbCz3+YozRusOOM3osgGljpDThhbKAJjqh82wOo6FioQ4w8PBvU1XMD5Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@astrojs/compiler-binding-linux-x64-musl@0.3.2': - resolution: {integrity: sha512-/Kebk8sO6HnLeSd691JkaAPfN7CqR9/KEXmWvyNPkaKNGmj8rTZ/lf2uXtnPu92Lan84UrKdIKVPy1fSo2encQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@astrojs/compiler-binding-wasm32-wasi@0.3.2': - resolution: {integrity: sha512-pUA6xbcOSB7DhfzIArB8BCAkFfAIqriiR7zl5zOStd6oU2G0kIKj+GUdGnyXyhfiv881Hffyk5tC0mR18sDjDw==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@astrojs/compiler-binding-win32-arm64-msvc@0.3.2': - resolution: {integrity: sha512-ESruf+6Qkl1trHUFxI6GSf6t52j8yN2kCNSzMWdzt7V/T09tFHrYzrVaJQohb2C9bJUH76pNvX6Zb51+xCQc9Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@astrojs/compiler-binding-win32-x64-msvc@0.3.2': - resolution: {integrity: sha512-wzzVrEbOwbsLWOdEbocskjMRx2aZPxJ7ZbmL+jnpBamFwmigm+2M/wzuM6JWncocgYwLic1csSpalBh96kQKXA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@astrojs/compiler-binding@0.3.2': - resolution: {integrity: sha512-8w/9CWmYrAJJ8N0SY3O43ws2BgxoW6u3QsD8u2mE140lMYAlwh+tlNoUeSBq22wVheFuiBbR212l6ixZ2IIgCQ==} - engines: {node: ^20.19.0 || >=22.12.0} - - '@astrojs/compiler-rs@0.3.2': - resolution: {integrity: sha512-xlx/T7JovIKduu4ucbTQUxQ5+Q8wxkHxhLjnZk3VlJbhbQ9RLvvuDk1p2YYFYFQ5y14dVm3FGGO4isQXa4F+Tg==} - engines: {node: '>=22.12.0'} - - '@astrojs/compiler@2.13.1': - resolution: {integrity: sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==} - - '@astrojs/internal-helpers@0.10.1': - resolution: {integrity: sha512-5phcroT/vmOOrYuuAxtkbPixy5hePtlz9i8K4OeDv3dNK6/UQRuXPOSRTxIOBbUY5Sonw2UaxjbuVc43Mcir6Q==} - - '@astrojs/language-server@2.16.13': - resolution: {integrity: sha512-ekOa+CYprEq5n4EJC1qTIAhLk49HZIUQuFwrEuF+3JK/pdMaYnWoREFUI2A0KEPOJiFA2kamBzKzbYljDvUxLg==} - hasBin: true - peerDependencies: - prettier: ^3.0.0 - prettier-plugin-astro: '>=0.11.0' - peerDependenciesMeta: - prettier: - optional: true - prettier-plugin-astro: - optional: true - - '@astrojs/markdown-satteri@0.3.4': - resolution: {integrity: sha512-6Lvt/bQZEBW+zzdhPblvfZEy5PGEYJaUsUqaCgwHeRPxZJL1gc9I+DRLKWJjjYTWDzVUTzXlMq4WwSK+X34CVw==} - - '@astrojs/prism@4.0.2': - resolution: {integrity: sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA==} - engines: {node: '>=22.12.0'} - - '@astrojs/sitemap@3.7.3': - resolution: {integrity: sha512-f8euLVsyeAmAkSm/1M2Kb8sL8byQmfgbvBNaHFItCheTj/IpiJYSEWVcqDHZ/yEHxiS7+w87mQkzwZaPHmk5GA==} - - '@astrojs/telemetry@3.3.3': - resolution: {integrity: sha512-C1TLn5sPJr0x4vk56piHWKbnqlEB8BKyte5Y45V02U+D7BGO5eMqZDH5aPjnkXQWJggvmsTXxH03QMZ9NgWLzQ==} - engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} - - '@astrojs/yaml2ts@0.2.4': - resolution: {integrity: sha512-8oddpOae35pJsXPQXhTkM0ypfKPskVsh2bCxRtbf7e+/Epw2nReakFYpLKjZMEr75CsoF203PMnCocpfz0s69A==} - - '@babel/helper-string-parser@7.29.7': - resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.29.7': - resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.29.8': - resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/types@7.29.8': - resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} - engines: {node: '>=6.9.0'} - - '@bramus/specificity@2.4.2': - resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} - hasBin: true - - '@bruits/satteri-darwin-arm64@0.9.5': - resolution: {integrity: sha512-iw4nZgx9v30lWo/MTngQqi1pI78KI0DnkSm+lVJGYdmPLgAyDNJigVhpG42/Iq55A6c1Ll8q66ljyyRiQUxwow==} - cpu: [arm64] - os: [darwin] - - '@bruits/satteri-darwin-x64@0.9.5': - resolution: {integrity: sha512-6T26Z5Kf3cFW2PSlk9p7zT7yVxvuBSiJvYyz9u8KjYwMTqZyIDOj2wDyNpxKV4+6yUVG7rddq2QwvG/8LJA2+Q==} - cpu: [x64] - os: [darwin] - - '@bruits/satteri-linux-arm64-gnu@0.9.5': - resolution: {integrity: sha512-u51id17uJwNEMK9nBlICsq6U31c+XVqQueVBkwRIzZG+gMpS8TOJctt5h5Wz33Z8xnMdTd+adtACVz0yHgGuOA==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@bruits/satteri-linux-arm64-musl@0.9.5': - resolution: {integrity: sha512-v39HxiwGC5Rqm01HksP6+5Y+xKLPlsuVFgIgpEAo+SiQ22c+mJVhS3u7Z6ePAKdhL5NJoK1xq70kLz3L13AhpQ==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@bruits/satteri-linux-x64-gnu@0.9.5': - resolution: {integrity: sha512-F3uO8uFp3pAP5ZGXttwvh57GS7s0lL953tnNdyI2gRyP4kOOkp6pyGojNJzCjkDvWI2Cvb9iNrKok3aqQPauAw==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@bruits/satteri-linux-x64-musl@0.9.5': - resolution: {integrity: sha512-bicEqglLlz++mWyADaZoP0JY20s4vDfLjaPYgQqC+NI4zZLTOOg1T4GB8aqtc822Pqji8SQBmSrTb7CrP8i08Q==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@bruits/satteri-wasm32-wasi@0.9.5': - resolution: {integrity: sha512-zauAuMwfPnKPUkd4AFixRFpXdgKwP2mKgxrIIo2gJzW0/ZneF9dbHnLkojSpaBnCCp7VUL1hIi5WWZvB1CqmAQ==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@bruits/satteri-win32-arm64-msvc@0.9.5': - resolution: {integrity: sha512-SrfE7NEsgZjBvU3c+RR6oQRu0ToXY5uVJEbieXEF0YTctIV2zAVlbaMjWLts074QCgh3a+XHWkR/lWh2VH2LUg==} - cpu: [arm64] - os: [win32] - - '@bruits/satteri-win32-x64-msvc@0.9.5': - resolution: {integrity: sha512-5Kw9ZAtTGS8WHizyn+CJhjjfIQrw+7jcZodpmpXJjefnO15M8UexIi6JR2E5thyvsmHyhL6ZDDMUNR4bKJPd4g==} - cpu: [x64] - os: [win32] - - '@capsizecss/unpack@4.0.1': - resolution: {integrity: sha512-CuNiSqg7+e1cO/GjffyMOm5Tt2jUF9CWHHnvQ/UkqvtkGfHdgwEC0wpmq7fkN3gxwpRnrAN0WzO3vREKmNolMQ==} - engines: {node: '>=18'} - - '@clack/core@1.4.3': - resolution: {integrity: sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ==} - engines: {node: '>= 20.12.0'} - - '@clack/prompts@1.7.0': - resolution: {integrity: sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==} - engines: {node: '>= 20.12.0'} - - '@csstools/color-helpers@6.1.0': - resolution: {integrity: sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==} - engines: {node: '>=20.19.0'} - - '@csstools/css-calc@3.3.0': - resolution: {integrity: sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==} - engines: {node: '>=20.19.0'} - peerDependencies: - '@csstools/css-parser-algorithms': ^4.0.0 - '@csstools/css-tokenizer': ^4.0.0 - - '@csstools/css-color-parser@4.1.10': - resolution: {integrity: sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==} - engines: {node: '>=20.19.0'} - peerDependencies: - '@csstools/css-parser-algorithms': ^4.0.0 - '@csstools/css-tokenizer': ^4.0.0 - - '@csstools/css-parser-algorithms@4.0.0': - resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} - engines: {node: '>=20.19.0'} - peerDependencies: - '@csstools/css-tokenizer': ^4.0.0 - - '@csstools/css-syntax-patches-for-csstree@1.1.7': - resolution: {integrity: sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==} - peerDependencies: - css-tree: ^3.2.1 - peerDependenciesMeta: - css-tree: - optional: true - - '@csstools/css-tokenizer@4.0.0': - resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} - engines: {node: '>=20.19.0'} - - '@emmetio/abbreviation@2.3.3': - resolution: {integrity: sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==} - - '@emmetio/css-abbreviation@2.1.8': - resolution: {integrity: sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==} - - '@emmetio/css-parser@0.4.1': - resolution: {integrity: sha512-2bC6m0MV/voF4CTZiAbG5MWKbq5EBmDPKu9Sb7s7nVcEzNQlrZP6mFFFlIaISM8X6514H9shWMme1fCm8cWAfQ==} - - '@emmetio/html-matcher@1.3.0': - resolution: {integrity: sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==} - - '@emmetio/scanner@1.0.4': - resolution: {integrity: sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==} - - '@emmetio/stream-reader-utils@0.1.0': - resolution: {integrity: sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==} - - '@emmetio/stream-reader@2.2.0': - resolution: {integrity: sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==} - - '@emnapi/core@1.11.1': - resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} - - '@emnapi/core@2.0.0-alpha.3': - resolution: {integrity: sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==} - - '@emnapi/runtime@1.11.1': - resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} - - '@emnapi/runtime@1.11.3': - resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} - - '@emnapi/runtime@2.0.0-alpha.3': - resolution: {integrity: sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==} - - '@emnapi/wasi-threads@1.2.2': - resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} - - '@emnapi/wasi-threads@2.0.1': - resolution: {integrity: sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==} - - '@esbuild/aix-ppc64@0.28.1': - resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.28.1': - resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.28.1': - resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.28.1': - resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.28.1': - resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.28.1': - resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.28.1': - resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.28.1': - resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.28.1': - resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.28.1': - resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.28.1': - resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.28.1': - resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.28.1': - resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.28.1': - resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.28.1': - resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.28.1': - resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.28.1': - resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.28.1': - resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.28.1': - resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.28.1': - resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.28.1': - resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.28.1': - resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.28.1': - resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.28.1': - resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.28.1': - resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.28.1': - resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@exodus/bytes@1.15.1': - resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - peerDependencies: - '@noble/hashes': ^1.8.0 || ^2.0.0 - peerDependenciesMeta: - '@noble/hashes': - optional: true - - '@img/colour@1.1.0': - resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} - engines: {node: '>=18'} - - '@img/sharp-darwin-arm64@0.35.3': - resolution: {integrity: sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==} - engines: {node: '>=20.9.0'} - cpu: [arm64] - os: [darwin] - - '@img/sharp-darwin-x64@0.35.3': - resolution: {integrity: sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==} - engines: {node: '>=20.9.0'} - cpu: [x64] - os: [darwin] - - '@img/sharp-freebsd-wasm32@0.35.3': - resolution: {integrity: sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==} - engines: {node: '>=20.9.0'} - os: [freebsd] - - '@img/sharp-libvips-darwin-arm64@1.3.2': - resolution: {integrity: sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==} - cpu: [arm64] - os: [darwin] - - '@img/sharp-libvips-darwin-x64@1.3.2': - resolution: {integrity: sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-linux-arm64@1.3.2': - resolution: {integrity: sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-arm@1.3.2': - resolution: {integrity: sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-ppc64@1.3.2': - resolution: {integrity: sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-riscv64@1.3.2': - resolution: {integrity: sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-s390x@1.3.2': - resolution: {integrity: sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-x64@1.3.2': - resolution: {integrity: sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linuxmusl-arm64@1.3.2': - resolution: {integrity: sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@img/sharp-libvips-linuxmusl-x64@1.3.2': - resolution: {integrity: sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@img/sharp-linux-arm64@0.35.3': - resolution: {integrity: sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==} - engines: {node: '>=20.9.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-arm@0.35.3': - resolution: {integrity: sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==} - engines: {node: '>=20.9.0'} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-ppc64@0.35.3': - resolution: {integrity: sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==} - engines: {node: '>=20.9.0'} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-riscv64@0.35.3': - resolution: {integrity: sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==} - engines: {node: '>=20.9.0'} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-s390x@0.35.3': - resolution: {integrity: sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==} - engines: {node: '>=20.9.0'} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-x64@0.35.3': - resolution: {integrity: sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==} - engines: {node: '>=20.9.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@img/sharp-linuxmusl-arm64@0.35.3': - resolution: {integrity: sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==} - engines: {node: '>=20.9.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@img/sharp-linuxmusl-x64@0.35.3': - resolution: {integrity: sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==} - engines: {node: '>=20.9.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@img/sharp-wasm32@0.35.3': - resolution: {integrity: sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==} - engines: {node: '>=20.9.0'} - - '@img/sharp-webcontainers-wasm32@0.35.3': - resolution: {integrity: sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==} - engines: {node: '>=20.9.0'} - cpu: [wasm32] - - '@img/sharp-win32-arm64@0.35.3': - resolution: {integrity: sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==} - engines: {node: '>=20.9.0'} - cpu: [arm64] - os: [win32] - - '@img/sharp-win32-ia32@0.35.3': - resolution: {integrity: sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==} - engines: {node: ^20.9.0} - cpu: [ia32] - os: [win32] - - '@img/sharp-win32-x64@0.35.3': - resolution: {integrity: sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==} - engines: {node: '>=20.9.0'} - cpu: [x64] - os: [win32] - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@napi-rs/wasm-runtime@1.2.2': - resolution: {integrity: sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} - peerDependencies: - '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.3 - '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.3 - - '@oslojs/encoding@1.1.0': - resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} - - '@oxc-project/types@0.142.0': - resolution: {integrity: sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==} - - '@playwright/test@1.62.0': - resolution: {integrity: sha512-9zOJ6ZQRAena31MpOH9VSzIz8Ou3YJ/wtY/eQm5T2uhfhG7/U3COrMS8xOtUrZrp9OgdmzEnIYODye3nY1VqzA==} - engines: {node: '>=20'} - hasBin: true - - '@rolldown/binding-android-arm64@1.2.1': - resolution: {integrity: sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.2.1': - resolution: {integrity: sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.2.1': - resolution: {integrity: sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.2.1': - resolution: {integrity: sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.2.1': - resolution: {integrity: sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.2.1': - resolution: {integrity: sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-arm64-musl@1.2.1': - resolution: {integrity: sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rolldown/binding-linux-ppc64-gnu@1.2.1': - resolution: {integrity: sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-s390x-gnu@1.2.1': - resolution: {integrity: sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-gnu@1.2.1': - resolution: {integrity: sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-musl@1.2.1': - resolution: {integrity: sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rolldown/binding-openharmony-arm64@1.2.1': - resolution: {integrity: sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-wasm32-wasi@1.2.1': - resolution: {integrity: sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==} - engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} - - '@rolldown/binding-win32-arm64-msvc@1.2.1': - resolution: {integrity: sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.2.1': - resolution: {integrity: sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@rolldown/pluginutils@1.0.1': - resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - - '@rollup/pluginutils@5.4.0': - resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@shikijs/core@4.4.1': - resolution: {integrity: sha512-VeR2CY6Nn9/WbisoYLOQZ7HZOnwTrpBuOw4wExjqLnBCi62BNWynBUO6K2uPIASPFJwAv7cX1fUu+LrPlSstcw==} - engines: {node: '>=20'} - - '@shikijs/engine-javascript@4.4.1': - resolution: {integrity: sha512-6U4lJBh8LTvIkEVqRHv/rr3ruwtO6IweFQt1ME1ntHJMGHS+6N86vfYGO1o8c/DtOCTia2lfhdQBtBrps1sDfQ==} - engines: {node: '>=20'} - - '@shikijs/engine-oniguruma@4.4.1': - resolution: {integrity: sha512-p23RugMKss0r5DAtRJW1yAXUDl60JvhQYV20yuxei//26JyDSJefV3umyWzzwep2weblMnJGDYahuti6XkcMgA==} - engines: {node: '>=20'} - - '@shikijs/langs@4.4.1': - resolution: {integrity: sha512-xb2kCMloBCIraIy2fS5MW0t/BxVY3q2nDyQKBoeSeq6KNrQbShHetCFlw2n35fGIJ6t3+hXDLQogP5ir9O9bvA==} - engines: {node: '>=20'} - - '@shikijs/primitive@4.4.1': - resolution: {integrity: sha512-ko2OfDoG89YuQ7xL5LtcQiWKb7NIv1Ephb7g48TVU198OzAMLC8lXVEwaJGHK4sUMYrfAGJDqYmNLOLiW/Kz8w==} - engines: {node: '>=20'} - - '@shikijs/themes@4.4.1': - resolution: {integrity: sha512-wudOaoFro+/Zl9gQv2W1Ur5XlVduqvTuYLI483Xi0wgc1A+cy1hfB2r6ac6ufBgF+ID7KJEW7L41MHrzQ4wH+w==} - engines: {node: '>=20'} - - '@shikijs/types@4.4.1': - resolution: {integrity: sha512-GOwCLQDHM5EjGUWNPrhzJbr6JP8V/Dx/CDVkWvbZ1Avw5JFnNUckrgbLmE07qtg4WlW7Q7QFndhjIkeU9XMPvw==} - engines: {node: '>=20'} - - '@shikijs/vscode-textmate@10.0.2': - resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - - '@tybys/wasm-util@0.10.3': - resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} - - '@types/chai@5.2.3': - resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - - '@types/estree-jsx@1.0.5': - resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} - - '@types/estree@1.0.9': - resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - - '@types/hast@3.0.5': - resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} - - '@types/mdast@4.0.4': - resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - - '@types/nlcst@2.0.3': - resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} - - '@types/node@24.13.3': - resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} - - '@types/sax@1.2.7': - resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} - - '@types/unist@3.0.3': - resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - - '@ungap/structured-clone@1.3.3': - resolution: {integrity: sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==} - - '@vitest/expect@4.1.10': - resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} - - '@vitest/mocker@4.1.10': - resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@4.1.10': - resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} - - '@vitest/runner@4.1.10': - resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} - - '@vitest/snapshot@4.1.10': - resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} - - '@vitest/spy@4.1.10': - resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} - - '@vitest/utils@4.1.10': - resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} - - '@volar/kit@2.4.28': - resolution: {integrity: sha512-cKX4vK9dtZvDRaAzeoUdaAJEew6IdxHNCRrdp5Kvcl6zZOqb6jTOfk3kXkIkG3T7oTFXguEMt5+9ptyqYR84Pg==} - peerDependencies: - typescript: '*' - - '@volar/language-core@2.4.28': - resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} - - '@volar/language-server@2.4.28': - resolution: {integrity: sha512-NqcLnE5gERKuS4PUFwlhMxf6vqYo7hXtbMFbViXcbVkbZ905AIVWhnSo0ZNBC2V127H1/2zP7RvVOVnyITFfBw==} - - '@volar/language-service@2.4.28': - resolution: {integrity: sha512-Rh/wYCZJrI5vCwMk9xyw/Z+MsWxlJY1rmMZPsxUoJKfzIRjS/NF1NmnuEcrMbEVGja00aVpCsInJfixQTMdvLw==} - - '@volar/source-map@2.4.28': - resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} - - '@volar/typescript@2.4.28': - resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} - - '@vscode/emmet-helper@2.11.0': - resolution: {integrity: sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==} - - '@vscode/l10n@0.0.18': - resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} - - ajv-draft-04@1.0.0: - resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} - peerDependencies: - ajv: ^8.5.0 - peerDependenciesMeta: - ajv: - optional: true - - ajv-i18n@4.2.0: - resolution: {integrity: sha512-v/ei2UkCEeuKNXh8RToiFsUclmU+G57LO1Oo22OagNMENIw+Yb8eMwvHu7Vn9fmkjJyv6XclhJ8TbuigSglPkg==} - peerDependencies: - ajv: ^8.0.0-beta.0 - - ajv@8.20.0: - resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} - - am-i-vibing@0.4.0: - resolution: {integrity: sha512-MxT4XZL7pzLHpuvhDKdMaQHMGGkJDLluKBLsbstn+8wv9sWcFT6h+0ve9qkml95amVTZtZV83gQe2hY+ojgHLg==} - hasBin: true - - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} - - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - arg@5.0.2: - resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - aria-query@5.3.2: - resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} - engines: {node: '>= 0.4'} - - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - astro@7.1.4: - resolution: {integrity: sha512-e0gkBReJECAZuuTgpEB5JMUc6J4mM6boD6wuVE1pBf/fMywG47f8qm9XQoA6kZB1RWHiHmUlLuQ2jd9Q5+72HQ==} - engines: {node: '>=22.12.0', npm: '>=9.6.5', pnpm: '>=7.1.0'} - hasBin: true - peerDependencies: - '@astrojs/markdown-remark': 7.2.1 - peerDependenciesMeta: - '@astrojs/markdown-remark': - optional: true - - axe-core@4.12.1: - resolution: {integrity: sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==} - engines: {node: '>=4'} - - axobject-query@4.1.0: - resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} - engines: {node: '>= 0.4'} - - bail@2.0.2: - resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} - - bidi-js@1.0.3: - resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} - - boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - - ccount@2.0.1: - resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} - engines: {node: '>=18'} - - character-entities-html4@2.1.0: - resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} - - character-entities-legacy@3.0.0: - resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} - - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} - - chokidar@5.0.0: - resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} - engines: {node: '>= 20.19.0'} - - ci-info@4.4.0: - resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} - engines: {node: '>=8'} - - cliui@9.0.1: - resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} - engines: {node: '>=20'} - - clsx@2.1.1: - resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} - engines: {node: '>=6'} - - comma-separated-tokens@2.0.3: - resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - - commander@11.1.0: - resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} - engines: {node: '>=16'} - - common-ancestor-path@2.0.0: - resolution: {integrity: sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==} - engines: {node: '>= 18'} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - cookie-es@1.2.3: - resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==} - - cookie@2.0.1: - resolution: {integrity: sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==} - engines: {node: '>=22'} - - crossws@0.3.5: - resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} - - css-select@5.1.0: - resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} - - css-tree@2.2.0: - resolution: {integrity: sha512-7y32czN0VBL8WkevhC/mrHnoHOmQaJ1Wvp8sjRuTz6/n9cjL83jQaUru2MvP7kzjpGVwrSy5CE4XyQObWGIHQQ==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - - css-tree@3.2.1: - resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - - css-what@6.1.0: - resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} - engines: {node: '>= 6'} - - csso@5.0.5: - resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - - data-urls@7.0.0: - resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - - decimal.js@10.6.0: - resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - - defu@6.1.7: - resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} - - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - - destr@2.0.5: - resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - devalue@5.9.0: - resolution: {integrity: sha512-RWrqdArjvPbsATEhOPUo6Wndc/iWnkWKlhIrdlF3zMMYo/c3CVtoaVAyLtWxz5h8nSlkHzxnzV2uLydPXmtF+A==} - - devlop@1.1.0: - resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - - diff@8.0.4: - resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} - engines: {node: '>=0.3.1'} - - dom-serializer@2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} - - domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - - domhandler@5.0.2: - resolution: {integrity: sha512-pr8ToPIuwBonzUy42STpc5Cf0m69zsQ7gtCLLvKrTbhVRnRohT2pLiJmGp3PAh16nDVWpYpcRpdjuk1vFmnQUg==} - engines: {node: '>= 4'} - - domutils@3.0.1: - resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==} - - dset@3.1.4: - resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} - engines: {node: '>=4'} - - emmet@2.4.11: - resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==} - - emoji-regex@10.6.0: - resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} - - entities@4.2.0: - resolution: {integrity: sha512-wEJa03bJgqEwPnkUqYdgmcfUXfm6+4hePQhntIvRy/1/+C4dFuhYHsgKBRjbQ6OWBh42P+VhAoCDO77DUh0e/Q==} - engines: {node: '>=0.12'} - - entities@4.3.0: - resolution: {integrity: sha512-/iP1rZrSEJ0DTlPiX+jbzlA3eVkY/e8L8SozroF395fIqE3TYF/Nz7YOMAawta+vLmyJ/hkGNNPcSbMADCCXbg==} - engines: {node: '>=0.12'} - - entities@8.0.0: - resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} - engines: {node: '>=20.19.0'} - - es-module-lexer@2.3.1: - resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} - - esbuild@0.28.1: - resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} - engines: {node: '>=18'} - hasBin: true - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - eventemitter3@5.0.4: - resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} - - expect-type@1.4.0: - resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} - engines: {node: '>=12.0.0'} - - extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-string-truncated-width@3.0.3: - resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} - - fast-string-width@3.0.2: - resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} - - fast-uri@3.1.5: - resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} - - fast-wrap-ansi@0.2.2: - resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - flattie@1.1.1: - resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} - engines: {node: '>=8'} - - fontace@0.4.1: - resolution: {integrity: sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==} - - fontkitten@1.0.3: - resolution: {integrity: sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==} - engines: {node: '>=20'} - - fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-east-asian-width@1.6.0: - resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} - engines: {node: '>=18'} - - get-tsconfig@5.0.0-beta.4: - resolution: {integrity: sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ==} - engines: {node: '>=20.20.0'} - - github-slugger@2.0.0: - resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} - - h3@1.15.11: - resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==} - - hast-util-from-html@2.0.3: - resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} - - hast-util-from-parse5@8.0.3: - resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} - - hast-util-parse-selector@4.0.0: - resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} - - hast-util-to-html@9.0.5: - resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} - - hast-util-whitespace@3.0.0: - resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} - - hastscript@9.0.1: - resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} - - html-encoding-sniffer@6.0.0: - resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - - html-escaper@3.0.3: - resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} - - html-void-elements@3.0.0: - resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - - http-cache-semantics@4.2.0: - resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} - - iron-webcrypto@1.2.1: - resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} - - is-docker@4.0.0: - resolution: {integrity: sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA==} - engines: {node: '>=20'} - hasBin: true - - is-plain-obj@4.1.0: - resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} - engines: {node: '>=12'} - - is-potential-custom-element-name@1.0.1: - resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - - js-yaml@4.3.1: - resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} - hasBin: true - - jsdom@30.0.0: - resolution: {integrity: sha512-JQHfRGmmKmaZoUAvIgff5jjG/0SzTQlGz8c7t72KzBzo8ZULEjAjnYE0sNwBOUA4QtWwYE2xoYitg8NFsmiYxA==} - engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} - peerDependencies: - canvas: ^3.2.3 - peerDependenciesMeta: - canvas: - optional: true - - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - - jsonc-parser@2.3.0: - resolution: {integrity: sha512-b0EBt8SWFNnixVdvoR2ZtEGa9ZqLhbJnOjezn+WP+8kspFm+PFYDN8Z4Bc7pRlDjvuVcADSUkroIuTWWn/YiIA==} - - jsonc-parser@3.0.0: - resolution: {integrity: sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==} - - jsonc-parser@3.3.1: - resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} - - kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - - lightningcss-android-arm64@1.33.0: - resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.33.0: - resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.33.0: - resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.33.0: - resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.33.0: - resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.33.0: - resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - lightningcss-linux-arm64-musl@1.33.0: - resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - lightningcss-linux-x64-gnu@1.33.0: - resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - lightningcss-linux-x64-musl@1.33.0: - resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - lightningcss-win32-arm64-msvc@1.33.0: - resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.33.0: - resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.33.0: - resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} - engines: {node: '>= 12.0.0'} - - lru-cache@11.5.2: - resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} - engines: {node: 20 || >=22} - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - magic-string@1.1.0: - resolution: {integrity: sha512-kS3VHe0nEPST2saQV4Rbkchcd3UBRkVTQHo1D3h/ZTwFDhai/mfKkmtPAtD129EOI7K3HlHIsFOt0WrI2/oU9g==} - - magicast@0.5.4: - resolution: {integrity: sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==} - - mdast-util-to-hast@13.2.1: - resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} - - mdn-data@2.0.28: - resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} - - mdn-data@2.27.1: - resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} - - micromark-util-character@2.0.0: - resolution: {integrity: sha512-DtHHqgTRz32ylwiGSZwoK+BTx4gaq//ig71g20pwBwRLxo3CfInyD+NyTrriLOhPz5WQOfAOSLLbKOaaGXWz9Q==} - - micromark-util-encode@2.0.0: - resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==} - - micromark-util-sanitize-uri@2.0.0: - resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==} - - micromark-util-symbol@2.0.0: - resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==} - - micromark-util-types@2.0.0: - resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==} - - mrmime@2.0.1: - resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} - engines: {node: '>=10'} - - muggle-string@0.4.1: - resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - - nanoid@3.3.16: - resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - neotraverse@1.0.1: - resolution: {integrity: sha512-WmmLty1YWwJl9yZi77v2dVIV6X2kuYV8YYBI/G3LWGKdGHmHUvL1z7FW0iDvEvGAwNEoc5x1tOOOyDnf5jJw/w==} - engines: {node: '>= 10'} - - nlcst-to-string@4.0.0: - resolution: {integrity: sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==} - - node-fetch-native@1.6.7: - resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} - - node-mock-http@1.0.5: - resolution: {integrity: sha512-KQyt/wLjG3TAc7DOUhpqWzgd4ERxR80JOlTK5VE5R1S12IaPVN5qkj4klBce9HPG1Njuup4Sb5bljaT34lIyjw==} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - nth-check@2.0.1: - resolution: {integrity: sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==} - - obug@2.1.4: - resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} - engines: {node: '>=12.20.0'} - - ofetch@1.5.1: - resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} - - ohash@2.0.11: - resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} - - oniguruma-parser@0.12.2: - resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} - - oniguruma-to-es@4.3.6: - resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} - - p-limit@7.3.1: - resolution: {integrity: sha512-0trZaiG7Y7kN/Egy9a8j47t9osC0Tch4PaIWd9yGF6bvmlk7muExRvGNYb8sXBwEKMoNKsbNN9P8EefuQekE4Q==} - engines: {node: '>=20'} - - p-queue@9.3.3: - resolution: {integrity: sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA==} - engines: {node: '>=20'} - - p-timeout@7.0.1: - resolution: {integrity: sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==} - engines: {node: '>=20'} - - package-manager-detector@1.8.0: - resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} - - parse5@7.0.0: - resolution: {integrity: sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==} - - parse5@8.0.1: - resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} - - path-browserify@1.0.1: - resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - piccolore@0.1.3: - resolution: {integrity: sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.2: - resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} - engines: {node: '>=8.6'} - - picomatch@4.0.5: - resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} - engines: {node: '>=12'} - - playwright-core@1.62.0: - resolution: {integrity: sha512-nsNRyq0r2zsG8AcRHWknc9QRA5XCueC7gWMrs+Gx2tlZn9hcl8zudfh00lhJPY1DE7NmZ6bDsT9g2yey8mXljA==} - engines: {node: '>=20'} - hasBin: true - - playwright@1.62.0: - resolution: {integrity: sha512-Z14dG305dgaLu6foB1TXQagFiW8JfSUIUaUuPaKQ6NtBPKF1P/qXcqfh6c6K/icPqdy37JmjbiBXf6JNg6Sylw==} - engines: {node: '>=20'} - hasBin: true - - postcss@8.5.25: - resolution: {integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==} - engines: {node: ^10 || ^12 || >=14} - - prettier@3.9.6: - resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} - engines: {node: '>=14'} - hasBin: true - - prismjs@1.30.0: - resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} - engines: {node: '>=6'} - - process-ancestry@0.1.0: - resolution: {integrity: sha512-tGqJW/UnclpYASFcM6Xh8D8l/BMtaQ9+CSG0vlJSJTcdMM4lDRv4c6H0Pdcsfted+bVczdYSfk2fdukg2gQkZg==} - engines: {node: '>=18.0.0'} - - property-information@7.2.0: - resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - radix3@1.1.2: - resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} - - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} - - readdirp@5.0.0: - resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} - engines: {node: '>= 20.19.0'} - - regex-recursion@6.0.2: - resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} - - regex-utilities@2.3.0: - resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} - - regex@6.1.0: - resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} - - request-light@0.5.8: - resolution: {integrity: sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==} - - request-light@0.7.0: - resolution: {integrity: sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==} - - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - retext-smartypants@6.2.0: - resolution: {integrity: sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==} - - rolldown@1.2.1: - resolution: {integrity: sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - satteri@0.9.5: - resolution: {integrity: sha512-ZuWVl+vnM64y+/TtX8Kosv2c00W+hLQiiwnEL6H0UKVVrxFqMw4D2CJHHQaouVd89OAhtBBfjWLqhKi3TVUV4w==} - - sax@1.6.1: - resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==} - engines: {node: '>=11.0.0'} - - saxes@6.0.0: - resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} - engines: {node: '>=v12.22.7'} - - semver@7.8.5: - resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} - engines: {node: '>=10'} - hasBin: true - - sharp@0.35.3: - resolution: {integrity: sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==} - engines: {node: '>=20.9.0'} - peerDependencies: - '@types/node': '*' - peerDependenciesMeta: - '@types/node': - optional: true - - shiki@4.4.1: - resolution: {integrity: sha512-rFP+iYKzjLEIqiMiKANhARqiAbk4deDhWnBtnUO/K0D0dPxMGDH4N0FVfBY/VeI+lPrV4wNGCHQZp7EOr7NNBw==} - engines: {node: '>=20'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - - sitemap@9.0.1: - resolution: {integrity: sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==} - engines: {node: '>=20.19.5', npm: '>=10.8.2'} - hasBin: true - - smol-toml@1.7.1: - resolution: {integrity: sha512-PPlsspAZ4jbMBu5DMFhfUGDQLu/vrL4SyBROVS37x8ynnVmFIs1VPBz1Co8Xks3TvpIaZXmU85y4DrQ+UyVFoQ==} - engines: {node: '>= 18'} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - space-separated-tokens@2.0.2: - resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - std-env@4.2.0: - resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} - - stream-replace-string@2.0.0: - resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==} - - string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} - engines: {node: '>=18'} - - string-width@8.2.2: - resolution: {integrity: sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==} - engines: {node: '>=20'} - - stringify-entities@4.0.4: - resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} - - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} - engines: {node: '>=12'} - - svgo@4.0.2: - resolution: {integrity: sha512-ekx94z1rRc5LDi6oSUaeRnYhd0UOJxdtQCL2rF8xpWxD3TPAsISWOrxezqGovqS38GRZOdpDfvQe3ts6F7nsng==} - engines: {node: '>=16'} - hasBin: true - - symbol-tree@3.2.4: - resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - - tiny-inflate@1.0.3: - resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyclip@0.1.15: - resolution: {integrity: sha512-uo33abH+Ays0xYaDysoBt494Hb3hsEczMpcC0MwFl773pazORx4fmvKhclhR1wonUbB6vvpRsvVMwnhfqeMc+A==} - engines: {node: ^16.14.0 || >= 17.3.0} - - tinyexec@1.3.0: - resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} - engines: {node: '>=18'} - - tinyglobby@0.2.17: - resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} - engines: {node: '>=12.0.0'} - - tinyrainbow@3.1.1: - resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} - engines: {node: '>=14.0.0'} - - tldts-core@7.4.10: - resolution: {integrity: sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==} - - tldts@7.4.10: - resolution: {integrity: sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==} - hasBin: true - - tough-cookie@6.0.2: - resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} - engines: {node: '>=16'} - - tr46@6.0.0: - resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} - engines: {node: '>=20'} - - trim-lines@3.0.1: - resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - - trough@2.2.0: - resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - typesafe-path@0.2.2: - resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} - - typescript-auto-import-cache@0.3.6: - resolution: {integrity: sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==} - - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} - hasBin: true - - ufo@1.6.4: - resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} - - ultrahtml@1.7.0: - resolution: {integrity: sha512-2xRd0VHoAQE4M+vF/DvFFB7pUV0ZxTW1TLi7lHQWnF/Sb5TPeEUV/l+hxcNnGO00ZXGnR0voCMmYRKQf+rvJ2g==} - - uncrypto@0.1.3: - resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} - - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} - - undici@8.9.0: - resolution: {integrity: sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==} - engines: {node: '>=22.19.0'} - - unified@11.0.5: - resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} - - unifont@0.7.4: - resolution: {integrity: sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==} - - unist-util-is@6.0.1: - resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} - - unist-util-position@5.0.0: - resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} - - unist-util-stringify-position@4.0.0: - resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - - unist-util-visit-parents@6.0.2: - resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} - - unist-util-visit@5.1.0: - resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} - - unstorage@1.17.5: - resolution: {integrity: sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==} - peerDependencies: - '@azure/app-configuration': ^1.8.0 - '@azure/cosmos': ^4.2.0 - '@azure/data-tables': ^13.3.0 - '@azure/identity': ^4.6.0 - '@azure/keyvault-secrets': ^4.9.0 - '@azure/storage-blob': ^12.26.0 - '@capacitor/preferences': ^6 || ^7 || ^8 - '@deno/kv': '>=0.9.0' - '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 - '@planetscale/database': ^1.19.0 - '@upstash/redis': ^1.34.3 - '@vercel/blob': '>=0.27.1' - '@vercel/functions': ^2.2.12 || ^3.0.0 - '@vercel/kv': ^1 || ^2 || ^3 - aws4fetch: ^1.0.20 - db0: '>=0.2.1' - idb-keyval: ^6.2.1 - ioredis: ^5.4.2 - uploadthing: ^7.4.4 - peerDependenciesMeta: - '@azure/app-configuration': - optional: true - '@azure/cosmos': - optional: true - '@azure/data-tables': - optional: true - '@azure/identity': - optional: true - '@azure/keyvault-secrets': - optional: true - '@azure/storage-blob': - optional: true - '@capacitor/preferences': - optional: true - '@deno/kv': - optional: true - '@netlify/blobs': - optional: true - '@planetscale/database': - optional: true - '@upstash/redis': - optional: true - '@vercel/blob': - optional: true - '@vercel/functions': - optional: true - '@vercel/kv': - optional: true - aws4fetch: - optional: true - db0: - optional: true - idb-keyval: - optional: true - ioredis: - optional: true - uploadthing: - optional: true - - vfile-location@5.0.3: - resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} - - vfile-message@4.0.3: - resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} - - vfile@6.0.3: - resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - - vite@8.2.0: - resolution: {integrity: sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.4.0 - 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 - - vitefu@1.1.3: - resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==} - peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - vite: - optional: true - - vitest@4.1.10: - resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.10 - '@vitest/browser-preview': 4.1.10 - '@vitest/browser-webdriverio': 4.1.10 - '@vitest/coverage-istanbul': 4.1.10 - '@vitest/coverage-v8': 4.1.10 - '@vitest/ui': 4.1.10 - happy-dom: '*' - jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@opentelemetry/api': - optional: true - '@types/node': - optional: true - '@vitest/browser-playwright': - optional: true - '@vitest/browser-preview': - optional: true - '@vitest/browser-webdriverio': - optional: true - '@vitest/coverage-istanbul': - optional: true - '@vitest/coverage-v8': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - volar-service-css@0.0.71: - resolution: {integrity: sha512-wRRFt9BpjMKCazcgOh67MSjUjiWUCAh99DyYSDIOTuxaRjEtDC7PpB0k1Y1wbJIW/pVtMUSVbpPo3UGSm0Byxw==} - peerDependencies: - '@volar/language-service': ~2.4.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true - - volar-service-emmet@0.0.71: - resolution: {integrity: sha512-zqjzt6bN95e3CUstBm0PBFAJnrfz0ZAARka87fart46/gNCLLuP3Vujy8V/J8HEziTFLnfkgIASLFYPUhonJcA==} - peerDependencies: - '@volar/language-service': ~2.4.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true - - volar-service-html@0.0.71: - resolution: {integrity: sha512-e8tHPhgQ7ooLfudAEIku+kgd9pWkq3SSz8RbnQDI1+Eb8wbenkLGHqoirLqz5ORLV6wIMr2Iv08RWBG5eOcgpw==} - peerDependencies: - '@volar/language-service': ~2.4.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true - - volar-service-prettier@0.0.71: - resolution: {integrity: sha512-Rz7JVH3qD108UCdmIEiZvOBNljMt2nLFdbN8AXcDfn7xD9F5I2aCIsDVqBbXw21PsnxG0b7MfwtNF+zPS/NKUg==} - peerDependencies: - '@volar/language-service': ~2.4.0 - prettier: ^2.2 || ^3.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true - prettier: - optional: true - - volar-service-typescript-twoslash-queries@0.0.71: - resolution: {integrity: sha512-9K2k72s4n7rV9s4bX0MyjbX9iBribvKZbBJKuEmTCZfeWJXs6Yh7bGpY4eoc7UufAjvpheBqwyZCOIPBvxCv0A==} - peerDependencies: - '@volar/language-service': ~2.4.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true - - volar-service-typescript@0.0.71: - resolution: {integrity: sha512-yTtM/BVT6hoyEYnDtaCyAtNhdNeS/mhTTABlBOdw3NNiRBUin3IznFJpgfjer4c6RYopiPjjQjc9VFhxVl1mLw==} - peerDependencies: - '@volar/language-service': ~2.4.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true - - volar-service-yaml@0.0.71: - resolution: {integrity: sha512-qYGWGuVpUTnZGu5P/CR4KLK4aIR8RrcVnmfZ2eRcj9q/I8VZCoC5yy9FtEvfNvnDp4MU17yhdJcvpQPIqhJS2Q==} - peerDependencies: - '@volar/language-service': ~2.4.0 - peerDependenciesMeta: - '@volar/language-service': - optional: true - - vscode-css-languageservice@6.3.10: - resolution: {integrity: sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA==} - - vscode-html-languageservice@5.6.2: - resolution: {integrity: sha512-ulCrSnFnfQ16YzvwnYUgEbUEl/ZG7u2eV27YhvLObSHKkb8fw1Z9cgsnUwjTEeDIdJDoTDTDpxuhQwoenoLNMg==} - - vscode-json-languageservice@4.1.8: - resolution: {integrity: sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==} - engines: {npm: '>=7.0.0'} - - vscode-jsonrpc@8.2.0: - resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} - engines: {node: '>=14.0.0'} - - vscode-jsonrpc@9.0.1: - resolution: {integrity: sha512-rfuA6T75H6m5EkbhtEPzre9pT0HPcDI2MMy4+nPFIBks5J8JBAUHD4tRYSgaBOijIEC7SRkC1kKyXTLqbmh9jw==} - engines: {node: '>=14.0.0'} - - vscode-languageserver-protocol@3.17.4: - resolution: {integrity: sha512-IpaHLPft+UBWf4dOIH15YEgydTbXGz52EMU2h16SfFpYu/yOQt3pY14049mtpJu+4CBHn+hq7S67e7O0AwpRqQ==} - - vscode-languageserver-protocol@3.17.5: - resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} - - vscode-languageserver-protocol@3.18.2: - resolution: {integrity: sha512-XRyDbT0Pp3sSNti3JmxVEUMySWCSi1hhM+/KUlCy1hV1zmrqpM1OwO12EAki8blhmLuIMpaJrYbo0OzGVfK2Qg==} - - vscode-languageserver-textdocument@1.0.13: - resolution: {integrity: sha512-nx0ZHwMGIsVkzFG3/VLeJYBLTaFBRuNdGDvevvjuoayU5EOS2fEYazOhtCM3PI9ClMMg5igc0uwXtAq4tJj+Dw==} - - vscode-languageserver-types@3.17.4: - resolution: {integrity: sha512-9YXi5pA3XF2V+NUQg6g+lulNS0ncRCKASYdK3Cs7kiH9sVFXWq27prjkC/B8M/xJLRPPRSPCHVMuBTgRNFh2sQ==} - - vscode-languageserver-types@3.17.5: - resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} - - vscode-languageserver-types@3.18.0: - resolution: {integrity: sha512-8TsGPNMIMiiBdkORgRSvLjuiEIiAFtO+KssmYWxQ+uSVvlf7RjK8YKCOjPzZ+YA04jXEV7+7LvkSmHkhpNS99g==} - - vscode-languageserver@9.0.0: - resolution: {integrity: sha512-npT72Iu28Tjsm94MsCbwJmIu5ycCF3UEPj3Eb3725T1Hqf4d+Vj2W4GC+F8l4n9yNItJuvE/AHYvomvAs9Dj8A==} - hasBin: true - - vscode-languageserver@9.0.1: - resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} - hasBin: true - - vscode-nls@5.2.0: - resolution: {integrity: sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==} - - vscode-uri@3.1.0: - resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} - - w3c-xmlserializer@5.0.0: - resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} - engines: {node: '>=18'} - - web-namespaces@2.0.1: - resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} - - webidl-conversions@8.0.1: - resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} - engines: {node: '>=20'} - - whatwg-mimetype@5.0.0: - resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} - engines: {node: '>=20'} - - whatwg-url@16.0.1: - resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - - whatwg-url@17.1.0: - resolution: {integrity: sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==} - engines: {node: ^22.14.0 || >=24.0.0} - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - wrap-ansi@9.0.0: - resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} - engines: {node: '>=18'} - - xml-name-validator@5.0.0: - resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} - engines: {node: '>=18'} - - xmlchars@2.2.0: - resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - - xxhash-wasm@1.1.0: - resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} - - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yaml-language-server@1.23.0: - resolution: {integrity: sha512-3qVyCOexLCWw06PQa5kRPwvMWMZ/eZeCRWUvgD6a0OkqL/4iCnxy2WumbWifa937Uo5xhyWJ0uxlU39ljhNh7A==} - hasBin: true - - yaml@2.8.3: - resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} - engines: {node: '>= 14.6'} - hasBin: true - - yaml@2.9.0: - resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} - engines: {node: '>= 14.6'} - hasBin: true - - yargs-parser@22.0.0: - resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} - engines: {node: ^20.19.0 || ^22.12.0 || >=23} - - yargs@18.1.0: - resolution: {integrity: sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=23} - - yocto-queue@1.2.2: - resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} - engines: {node: '>=12.20'} - - zod@4.4.3: - resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} - - zwitch@2.0.4: - resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - -snapshots: - - '@asamuzakjp/css-color@6.0.5': - dependencies: - '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-color-parser': 4.1.10(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-tokenizer': 4.0.0 - lru-cache: 11.5.2 - - '@asamuzakjp/dom-selector@8.3.2': - dependencies: - bidi-js: 1.0.3 - css-tree: 3.2.1 - is-potential-custom-element-name: 1.0.1 - lru-cache: 11.5.2 - - '@astrojs/check@0.9.10(prettier@3.9.6)(typescript@6.0.3)': - dependencies: - '@astrojs/language-server': 2.16.13(prettier@3.9.6)(typescript@6.0.3) - chokidar: 4.0.3 - kleur: 4.1.5 - typescript: 6.0.3 - yargs: 18.1.0 - transitivePeerDependencies: - - prettier - - prettier-plugin-astro - - '@astrojs/compiler-binding-darwin-arm64@0.3.2': - optional: true - - '@astrojs/compiler-binding-darwin-x64@0.3.2': - optional: true - - '@astrojs/compiler-binding-linux-arm64-gnu@0.3.2': - optional: true - - '@astrojs/compiler-binding-linux-arm64-musl@0.3.2': - optional: true - - '@astrojs/compiler-binding-linux-x64-gnu@0.3.2': - optional: true - - '@astrojs/compiler-binding-linux-x64-musl@0.3.2': - optional: true - - '@astrojs/compiler-binding-wasm32-wasi@0.3.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)': - dependencies: - '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3) - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' - optional: true - - '@astrojs/compiler-binding-win32-arm64-msvc@0.3.2': - optional: true - - '@astrojs/compiler-binding-win32-x64-msvc@0.3.2': - optional: true - - '@astrojs/compiler-binding@0.3.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)': - optionalDependencies: - '@astrojs/compiler-binding-darwin-arm64': 0.3.2 - '@astrojs/compiler-binding-darwin-x64': 0.3.2 - '@astrojs/compiler-binding-linux-arm64-gnu': 0.3.2 - '@astrojs/compiler-binding-linux-arm64-musl': 0.3.2 - '@astrojs/compiler-binding-linux-x64-gnu': 0.3.2 - '@astrojs/compiler-binding-linux-x64-musl': 0.3.2 - '@astrojs/compiler-binding-wasm32-wasi': 0.3.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3) - '@astrojs/compiler-binding-win32-arm64-msvc': 0.3.2 - '@astrojs/compiler-binding-win32-x64-msvc': 0.3.2 - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' - - '@astrojs/compiler-rs@0.3.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)': - dependencies: - '@astrojs/compiler-binding': 0.3.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3) - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' - - '@astrojs/compiler@2.13.1': {} - - '@astrojs/internal-helpers@0.10.1': - dependencies: - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - js-yaml: 4.3.1 - picomatch: 4.0.5 - retext-smartypants: 6.2.0 - shiki: 4.4.1 - smol-toml: 1.7.1 - unified: 11.0.5 - - '@astrojs/language-server@2.16.13(prettier@3.9.6)(typescript@6.0.3)': - dependencies: - '@astrojs/compiler': 2.13.1 - '@astrojs/yaml2ts': 0.2.4 - '@jridgewell/sourcemap-codec': 1.5.5 - '@volar/kit': 2.4.28(typescript@6.0.3) - '@volar/language-core': 2.4.28 - '@volar/language-server': 2.4.28 - '@volar/language-service': 2.4.28 - muggle-string: 0.4.1 - tinyglobby: 0.2.17 - volar-service-css: 0.0.71(@volar/language-service@2.4.28) - volar-service-emmet: 0.0.71(@volar/language-service@2.4.28) - volar-service-html: 0.0.71(@volar/language-service@2.4.28) - volar-service-prettier: 0.0.71(@volar/language-service@2.4.28)(prettier@3.9.6) - volar-service-typescript: 0.0.71(@volar/language-service@2.4.28) - volar-service-typescript-twoslash-queries: 0.0.71(@volar/language-service@2.4.28) - volar-service-yaml: 0.0.71(@volar/language-service@2.4.28) - vscode-html-languageservice: 5.6.2 - vscode-uri: 3.1.0 - optionalDependencies: - prettier: 3.9.6 - transitivePeerDependencies: - - typescript - - '@astrojs/markdown-satteri@0.3.4': - dependencies: - '@astrojs/internal-helpers': 0.10.1 - '@astrojs/prism': 4.0.2 - github-slugger: 2.0.0 - hast-util-from-html: 2.0.3 - satteri: 0.9.5 - - '@astrojs/prism@4.0.2': - dependencies: - prismjs: 1.30.0 - - '@astrojs/sitemap@3.7.3': - dependencies: - sitemap: 9.0.1 - stream-replace-string: 2.0.0 - zod: 4.4.3 - - '@astrojs/telemetry@3.3.3': - dependencies: - ci-info: 4.4.0 - dset: 3.1.4 - is-docker: 4.0.0 - package-manager-detector: 1.8.0 - - '@astrojs/yaml2ts@0.2.4': - dependencies: - yaml: 2.9.0 - - '@babel/helper-string-parser@7.29.7': {} - - '@babel/helper-validator-identifier@7.29.7': {} - - '@babel/parser@7.29.8': - dependencies: - '@babel/types': 7.29.8 - - '@babel/types@7.29.8': - dependencies: - '@babel/helper-string-parser': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - - '@bramus/specificity@2.4.2': - dependencies: - css-tree: 3.2.1 - - '@bruits/satteri-darwin-arm64@0.9.5': - optional: true - - '@bruits/satteri-darwin-x64@0.9.5': - optional: true - - '@bruits/satteri-linux-arm64-gnu@0.9.5': - optional: true - - '@bruits/satteri-linux-arm64-musl@0.9.5': - optional: true - - '@bruits/satteri-linux-x64-gnu@0.9.5': - optional: true - - '@bruits/satteri-linux-x64-musl@0.9.5': - optional: true - - '@bruits/satteri-wasm32-wasi@0.9.5': - dependencies: - '@emnapi/core': 1.11.1 - '@emnapi/runtime': 1.11.1 - '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) - optional: true - - '@bruits/satteri-win32-arm64-msvc@0.9.5': - optional: true - - '@bruits/satteri-win32-x64-msvc@0.9.5': - optional: true - - '@capsizecss/unpack@4.0.1': - dependencies: - fontkitten: 1.0.3 - - '@clack/core@1.4.3': - dependencies: - fast-wrap-ansi: 0.2.2 - sisteransi: 1.0.5 - - '@clack/prompts@1.7.0': - dependencies: - '@clack/core': 1.4.3 - fast-string-width: 3.0.2 - fast-wrap-ansi: 0.2.2 - sisteransi: 1.0.5 - - '@csstools/color-helpers@6.1.0': {} - - '@csstools/css-calc@3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': - dependencies: - '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-tokenizer': 4.0.0 - - '@csstools/css-color-parser@4.1.10(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': - dependencies: - '@csstools/color-helpers': 6.1.0 - '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-tokenizer': 4.0.0 - - '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': - dependencies: - '@csstools/css-tokenizer': 4.0.0 - - '@csstools/css-syntax-patches-for-csstree@1.1.7(css-tree@3.2.1)': - optionalDependencies: - css-tree: 3.2.1 - - '@csstools/css-tokenizer@4.0.0': {} - - '@emmetio/abbreviation@2.3.3': - dependencies: - '@emmetio/scanner': 1.0.4 - - '@emmetio/css-abbreviation@2.1.8': - dependencies: - '@emmetio/scanner': 1.0.4 - - '@emmetio/css-parser@0.4.1': - dependencies: - '@emmetio/stream-reader': 2.2.0 - '@emmetio/stream-reader-utils': 0.1.0 - - '@emmetio/html-matcher@1.3.0': - dependencies: - '@emmetio/scanner': 1.0.4 - - '@emmetio/scanner@1.0.4': {} - - '@emmetio/stream-reader-utils@0.1.0': {} - - '@emmetio/stream-reader@2.2.0': {} - - '@emnapi/core@1.11.1': - dependencies: - '@emnapi/wasi-threads': 1.2.2 - tslib: 2.8.1 - optional: true - - '@emnapi/core@2.0.0-alpha.3': - dependencies: - '@emnapi/wasi-threads': 2.0.1 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.11.1': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.11.3': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@2.0.0-alpha.3': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.2.2': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@2.0.1': - dependencies: - tslib: 2.8.1 - optional: true - - '@esbuild/aix-ppc64@0.28.1': - optional: true - - '@esbuild/android-arm64@0.28.1': - optional: true - - '@esbuild/android-arm@0.28.1': - optional: true - - '@esbuild/android-x64@0.28.1': - optional: true - - '@esbuild/darwin-arm64@0.28.1': - optional: true - - '@esbuild/darwin-x64@0.28.1': - optional: true - - '@esbuild/freebsd-arm64@0.28.1': - optional: true - - '@esbuild/freebsd-x64@0.28.1': - optional: true - - '@esbuild/linux-arm64@0.28.1': - optional: true - - '@esbuild/linux-arm@0.28.1': - optional: true - - '@esbuild/linux-ia32@0.28.1': - optional: true - - '@esbuild/linux-loong64@0.28.1': - optional: true - - '@esbuild/linux-mips64el@0.28.1': - optional: true - - '@esbuild/linux-ppc64@0.28.1': - optional: true - - '@esbuild/linux-riscv64@0.28.1': - optional: true - - '@esbuild/linux-s390x@0.28.1': - optional: true - - '@esbuild/linux-x64@0.28.1': - optional: true - - '@esbuild/netbsd-arm64@0.28.1': - optional: true - - '@esbuild/netbsd-x64@0.28.1': - optional: true - - '@esbuild/openbsd-arm64@0.28.1': - optional: true - - '@esbuild/openbsd-x64@0.28.1': - optional: true - - '@esbuild/openharmony-arm64@0.28.1': - optional: true - - '@esbuild/sunos-x64@0.28.1': - optional: true - - '@esbuild/win32-arm64@0.28.1': - optional: true - - '@esbuild/win32-ia32@0.28.1': - optional: true - - '@esbuild/win32-x64@0.28.1': - optional: true - - '@exodus/bytes@1.15.1': {} - - '@img/colour@1.1.0': - optional: true - - '@img/sharp-darwin-arm64@0.35.3': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.3.2 - optional: true - - '@img/sharp-darwin-x64@0.35.3': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.3.2 - optional: true - - '@img/sharp-freebsd-wasm32@0.35.3': - dependencies: - '@img/sharp-wasm32': 0.35.3 - optional: true - - '@img/sharp-libvips-darwin-arm64@1.3.2': - optional: true - - '@img/sharp-libvips-darwin-x64@1.3.2': - optional: true - - '@img/sharp-libvips-linux-arm64@1.3.2': - optional: true - - '@img/sharp-libvips-linux-arm@1.3.2': - optional: true - - '@img/sharp-libvips-linux-ppc64@1.3.2': - optional: true - - '@img/sharp-libvips-linux-riscv64@1.3.2': - optional: true - - '@img/sharp-libvips-linux-s390x@1.3.2': - optional: true - - '@img/sharp-libvips-linux-x64@1.3.2': - optional: true - - '@img/sharp-libvips-linuxmusl-arm64@1.3.2': - optional: true - - '@img/sharp-libvips-linuxmusl-x64@1.3.2': - optional: true - - '@img/sharp-linux-arm64@0.35.3': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.3.2 - optional: true - - '@img/sharp-linux-arm@0.35.3': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.3.2 - optional: true - - '@img/sharp-linux-ppc64@0.35.3': - optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.3.2 - optional: true - - '@img/sharp-linux-riscv64@0.35.3': - optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.3.2 - optional: true - - '@img/sharp-linux-s390x@0.35.3': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.3.2 - optional: true - - '@img/sharp-linux-x64@0.35.3': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.3.2 - optional: true - - '@img/sharp-linuxmusl-arm64@0.35.3': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.3.2 - optional: true - - '@img/sharp-linuxmusl-x64@0.35.3': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.3.2 - optional: true - - '@img/sharp-wasm32@0.35.3': - dependencies: - '@emnapi/runtime': 1.11.3 - optional: true - - '@img/sharp-webcontainers-wasm32@0.35.3': - dependencies: - '@img/sharp-wasm32': 0.35.3 - optional: true - - '@img/sharp-win32-arm64@0.35.3': - optional: true - - '@img/sharp-win32-ia32@0.35.3': - optional: true - - '@img/sharp-win32-x64@0.35.3': - optional: true - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': - dependencies: - '@emnapi/core': 1.11.1 - '@emnapi/runtime': 1.11.1 - '@tybys/wasm-util': 0.10.3 - optional: true - - '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)': - dependencies: - '@emnapi/core': 2.0.0-alpha.3 - '@emnapi/runtime': 2.0.0-alpha.3 - '@tybys/wasm-util': 0.10.3 - optional: true - - '@oslojs/encoding@1.1.0': {} - - '@oxc-project/types@0.142.0': {} - - '@playwright/test@1.62.0': - dependencies: - playwright: 1.62.0 - - '@rolldown/binding-android-arm64@1.2.1': - optional: true - - '@rolldown/binding-darwin-arm64@1.2.1': - optional: true - - '@rolldown/binding-darwin-x64@1.2.1': - optional: true - - '@rolldown/binding-freebsd-x64@1.2.1': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.2.1': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.2.1': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.2.1': - optional: true - - '@rolldown/binding-linux-ppc64-gnu@1.2.1': - optional: true - - '@rolldown/binding-linux-s390x-gnu@1.2.1': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.2.1': - optional: true - - '@rolldown/binding-linux-x64-musl@1.2.1': - optional: true - - '@rolldown/binding-openharmony-arm64@1.2.1': - optional: true - - '@rolldown/binding-wasm32-wasi@1.2.1': - dependencies: - '@emnapi/core': 2.0.0-alpha.3 - '@emnapi/runtime': 2.0.0-alpha.3 - '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3) - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.2.1': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.2.1': - optional: true - - '@rolldown/pluginutils@1.0.1': {} - - '@rollup/pluginutils@5.4.0': - dependencies: - '@types/estree': 1.0.9 - estree-walker: 2.0.2 - picomatch: 4.0.5 - - '@shikijs/core@4.4.1': - dependencies: - '@shikijs/primitive': 4.4.1 - '@shikijs/types': 4.4.1 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.5 - hast-util-to-html: 9.0.5 - - '@shikijs/engine-javascript@4.4.1': - dependencies: - '@shikijs/types': 4.4.1 - '@shikijs/vscode-textmate': 10.0.2 - oniguruma-to-es: 4.3.6 - - '@shikijs/engine-oniguruma@4.4.1': - dependencies: - '@shikijs/types': 4.4.1 - '@shikijs/vscode-textmate': 10.0.2 - - '@shikijs/langs@4.4.1': - dependencies: - '@shikijs/types': 4.4.1 - - '@shikijs/primitive@4.4.1': - dependencies: - '@shikijs/types': 4.4.1 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.5 - - '@shikijs/themes@4.4.1': - dependencies: - '@shikijs/types': 4.4.1 - - '@shikijs/types@4.4.1': - dependencies: - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.5 - - '@shikijs/vscode-textmate@10.0.2': {} - - '@standard-schema/spec@1.1.0': {} - - '@tybys/wasm-util@0.10.3': - dependencies: - tslib: 2.8.1 - optional: true - - '@types/chai@5.2.3': - dependencies: - '@types/deep-eql': 4.0.2 - assertion-error: 2.0.1 - - '@types/deep-eql@4.0.2': {} - - '@types/estree-jsx@1.0.5': - dependencies: - '@types/estree': 1.0.9 - - '@types/estree@1.0.9': {} - - '@types/hast@3.0.5': - dependencies: - '@types/unist': 3.0.3 - - '@types/mdast@4.0.4': - dependencies: - '@types/unist': 3.0.3 - - '@types/nlcst@2.0.3': - dependencies: - '@types/unist': 3.0.3 - - '@types/node@24.13.3': - dependencies: - undici-types: 7.18.2 - - '@types/sax@1.2.7': - dependencies: - '@types/node': 24.13.3 - - '@types/unist@3.0.3': {} - - '@ungap/structured-clone@1.3.3': {} - - '@vitest/expect@4.1.10': - dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.10 - '@vitest/utils': 4.1.10 - chai: 6.2.2 - tinyrainbow: 3.1.1 - - '@vitest/mocker@4.1.10(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(yaml@2.9.0))': - dependencies: - '@vitest/spy': 4.1.10 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(yaml@2.9.0) - - '@vitest/pretty-format@4.1.10': - dependencies: - tinyrainbow: 3.1.1 - - '@vitest/runner@4.1.10': - dependencies: - '@vitest/utils': 4.1.10 - pathe: 2.0.3 - - '@vitest/snapshot@4.1.10': - dependencies: - '@vitest/pretty-format': 4.1.10 - '@vitest/utils': 4.1.10 - magic-string: 0.30.21 - pathe: 2.0.3 - - '@vitest/spy@4.1.10': {} - - '@vitest/utils@4.1.10': - dependencies: - '@vitest/pretty-format': 4.1.10 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.1 - - '@volar/kit@2.4.28(typescript@6.0.3)': - dependencies: - '@volar/language-service': 2.4.28 - '@volar/typescript': 2.4.28 - typesafe-path: 0.2.2 - typescript: 6.0.3 - vscode-languageserver-textdocument: 1.0.13 - vscode-uri: 3.1.0 - - '@volar/language-core@2.4.28': - dependencies: - '@volar/source-map': 2.4.28 - - '@volar/language-server@2.4.28': - dependencies: - '@volar/language-core': 2.4.28 - '@volar/language-service': 2.4.28 - '@volar/typescript': 2.4.28 - path-browserify: 1.0.1 - request-light: 0.7.0 - vscode-languageserver: 9.0.1 - vscode-languageserver-protocol: 3.18.2 - vscode-languageserver-textdocument: 1.0.13 - vscode-uri: 3.1.0 - - '@volar/language-service@2.4.28': - dependencies: - '@volar/language-core': 2.4.28 - vscode-languageserver-protocol: 3.18.2 - vscode-languageserver-textdocument: 1.0.13 - vscode-uri: 3.1.0 - - '@volar/source-map@2.4.28': {} - - '@volar/typescript@2.4.28': - dependencies: - '@volar/language-core': 2.4.28 - path-browserify: 1.0.1 - vscode-uri: 3.1.0 - - '@vscode/emmet-helper@2.11.0': - dependencies: - emmet: 2.4.11 - jsonc-parser: 2.3.0 - vscode-languageserver-textdocument: 1.0.13 - vscode-languageserver-types: 3.18.0 - vscode-uri: 3.1.0 - - '@vscode/l10n@0.0.18': {} - - ajv-draft-04@1.0.0(ajv@8.20.0): - optionalDependencies: - ajv: 8.20.0 - - ajv-i18n@4.2.0(ajv@8.20.0): - dependencies: - ajv: 8.20.0 - - ajv@8.20.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.5 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - - am-i-vibing@0.4.0: - dependencies: - process-ancestry: 0.1.0 - - ansi-regex@6.2.2: {} - - ansi-styles@6.2.1: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.2 - - arg@5.0.2: {} - - argparse@2.0.1: {} - - aria-query@5.3.2: {} - - assertion-error@2.0.1: {} - - astro@7.1.4(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(@types/node@24.13.3)(yaml@2.9.0): - dependencies: - '@astrojs/compiler-rs': 0.3.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3) - '@astrojs/internal-helpers': 0.10.1 - '@astrojs/markdown-satteri': 0.3.4 - '@astrojs/telemetry': 3.3.3 - '@capsizecss/unpack': 4.0.1 - '@clack/prompts': 1.7.0 - '@oslojs/encoding': 1.1.0 - '@rollup/pluginutils': 5.4.0 - am-i-vibing: 0.4.0 - aria-query: 5.3.2 - axobject-query: 4.1.0 - ci-info: 4.4.0 - clsx: 2.1.1 - common-ancestor-path: 2.0.0 - cookie: 2.0.1 - devalue: 5.9.0 - diff: 8.0.4 - dset: 3.1.4 - es-module-lexer: 2.3.1 - esbuild: 0.28.1 - flattie: 1.1.1 - fontace: 0.4.1 - get-tsconfig: 5.0.0-beta.4 - github-slugger: 2.0.0 - html-escaper: 3.0.3 - http-cache-semantics: 4.2.0 - js-yaml: 4.3.1 - jsonc-parser: 3.3.1 - magic-string: 1.1.0 - magicast: 0.5.4 - mrmime: 2.0.1 - neotraverse: 1.0.1 - obug: 2.1.4 - p-limit: 7.3.1 - p-queue: 9.3.3 - package-manager-detector: 1.8.0 - piccolore: 0.1.3 - picomatch: 4.0.5 - semver: 7.8.5 - shiki: 4.4.1 - smol-toml: 1.7.1 - svgo: 4.0.2 - tinyclip: 0.1.15 - tinyexec: 1.3.0 - tinyglobby: 0.2.17 - ultrahtml: 1.7.0 - unifont: 0.7.4 - unstorage: 1.17.5 - vite: 8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(yaml@2.9.0) - vitefu: 1.1.3(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(yaml@2.9.0)) - xxhash-wasm: 1.1.0 - yargs-parser: 22.0.0 - zod: 4.4.3 - optionalDependencies: - sharp: 0.35.3(@types/node@24.13.3) - transitivePeerDependencies: - - '@azure/app-configuration' - - '@azure/cosmos' - - '@azure/data-tables' - - '@azure/identity' - - '@azure/keyvault-secrets' - - '@azure/storage-blob' - - '@capacitor/preferences' - - '@deno/kv' - - '@emnapi/core' - - '@emnapi/runtime' - - '@netlify/blobs' - - '@planetscale/database' - - '@types/node' - - '@upstash/redis' - - '@vercel/blob' - - '@vercel/functions' - - '@vercel/kv' - - '@vitejs/devtools' - - aws4fetch - - db0 - - idb-keyval - - ioredis - - jiti - - less - - rollup - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - uploadthing - - yaml - - axe-core@4.12.1: {} - - axobject-query@4.1.0: {} - - bail@2.0.2: {} - - bidi-js@1.0.3: - dependencies: - require-from-string: 2.0.2 - - boolbase@1.0.0: {} - - ccount@2.0.1: {} - - chai@6.2.2: {} - - character-entities-html4@2.1.0: {} - - character-entities-legacy@3.0.0: {} - - chokidar@4.0.3: - dependencies: - readdirp: 4.1.2 - - chokidar@5.0.0: - dependencies: - readdirp: 5.0.0 - - ci-info@4.4.0: {} - - cliui@9.0.1: - dependencies: - string-width: 7.2.0 - strip-ansi: 7.2.0 - wrap-ansi: 9.0.0 - - clsx@2.1.1: {} - - comma-separated-tokens@2.0.3: {} - - commander@11.1.0: {} - - common-ancestor-path@2.0.0: {} - - convert-source-map@2.0.0: {} - - cookie-es@1.2.3: {} - - cookie@2.0.1: {} - - crossws@0.3.5: - dependencies: - uncrypto: 0.1.3 - - css-select@5.1.0: - dependencies: - boolbase: 1.0.0 - css-what: 6.1.0 - domhandler: 5.0.2 - domutils: 3.0.1 - nth-check: 2.0.1 - - css-tree@2.2.0: - dependencies: - mdn-data: 2.0.28 - source-map-js: 1.2.1 - - css-tree@3.2.1: - dependencies: - mdn-data: 2.27.1 - source-map-js: 1.2.1 - - css-what@6.1.0: {} - - csso@5.0.5: - dependencies: - css-tree: 2.2.0 - - data-urls@7.0.0: - dependencies: - whatwg-mimetype: 5.0.0 - whatwg-url: 16.0.1 - transitivePeerDependencies: - - '@noble/hashes' - - decimal.js@10.6.0: {} - - defu@6.1.7: {} - - dequal@2.0.3: {} - - destr@2.0.5: {} - - detect-libc@2.1.2: {} - - devalue@5.9.0: {} - - devlop@1.1.0: - dependencies: - dequal: 2.0.3 - - diff@8.0.4: {} - - dom-serializer@2.0.0: - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.2 - entities: 4.2.0 - - domelementtype@2.3.0: {} - - domhandler@5.0.2: - dependencies: - domelementtype: 2.3.0 - - domutils@3.0.1: - dependencies: - dom-serializer: 2.0.0 - domelementtype: 2.3.0 - domhandler: 5.0.2 - - dset@3.1.4: {} - - emmet@2.4.11: - dependencies: - '@emmetio/abbreviation': 2.3.3 - '@emmetio/css-abbreviation': 2.1.8 - - emoji-regex@10.6.0: {} - - entities@4.2.0: {} - - entities@4.3.0: {} - - entities@8.0.0: {} - - es-module-lexer@2.3.1: {} - - esbuild@0.28.1: - optionalDependencies: - '@esbuild/aix-ppc64': 0.28.1 - '@esbuild/android-arm': 0.28.1 - '@esbuild/android-arm64': 0.28.1 - '@esbuild/android-x64': 0.28.1 - '@esbuild/darwin-arm64': 0.28.1 - '@esbuild/darwin-x64': 0.28.1 - '@esbuild/freebsd-arm64': 0.28.1 - '@esbuild/freebsd-x64': 0.28.1 - '@esbuild/linux-arm': 0.28.1 - '@esbuild/linux-arm64': 0.28.1 - '@esbuild/linux-ia32': 0.28.1 - '@esbuild/linux-loong64': 0.28.1 - '@esbuild/linux-mips64el': 0.28.1 - '@esbuild/linux-ppc64': 0.28.1 - '@esbuild/linux-riscv64': 0.28.1 - '@esbuild/linux-s390x': 0.28.1 - '@esbuild/linux-x64': 0.28.1 - '@esbuild/netbsd-arm64': 0.28.1 - '@esbuild/netbsd-x64': 0.28.1 - '@esbuild/openbsd-arm64': 0.28.1 - '@esbuild/openbsd-x64': 0.28.1 - '@esbuild/openharmony-arm64': 0.28.1 - '@esbuild/sunos-x64': 0.28.1 - '@esbuild/win32-arm64': 0.28.1 - '@esbuild/win32-ia32': 0.28.1 - '@esbuild/win32-x64': 0.28.1 - - escalade@3.2.0: {} - - estree-walker@2.0.2: {} - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.9 - - eventemitter3@5.0.4: {} - - expect-type@1.4.0: {} - - extend@3.0.2: {} - - fast-deep-equal@3.1.3: {} - - fast-string-truncated-width@3.0.3: {} - - fast-string-width@3.0.2: - dependencies: - fast-string-truncated-width: 3.0.3 - - fast-uri@3.1.5: {} - - fast-wrap-ansi@0.2.2: - dependencies: - fast-string-width: 3.0.2 - - fdir@6.5.0(picomatch@4.0.5): - optionalDependencies: - picomatch: 4.0.5 - - flattie@1.1.1: {} - - fontace@0.4.1: - dependencies: - fontkitten: 1.0.3 - - fontkitten@1.0.3: - dependencies: - tiny-inflate: 1.0.3 - - fsevents@2.3.2: - optional: true - - fsevents@2.3.3: - optional: true - - get-caller-file@2.0.5: {} - - get-east-asian-width@1.6.0: {} - - get-tsconfig@5.0.0-beta.4: - dependencies: - resolve-pkg-maps: 1.0.0 - - github-slugger@2.0.0: {} - - h3@1.15.11: - dependencies: - cookie-es: 1.2.3 - crossws: 0.3.5 - defu: 6.1.7 - destr: 2.0.5 - iron-webcrypto: 1.2.1 - node-mock-http: 1.0.5 - radix3: 1.1.2 - ufo: 1.6.4 - uncrypto: 0.1.3 - - hast-util-from-html@2.0.3: - dependencies: - '@types/hast': 3.0.5 - devlop: 1.1.0 - hast-util-from-parse5: 8.0.3 - parse5: 7.0.0 - vfile: 6.0.3 - vfile-message: 4.0.3 - - hast-util-from-parse5@8.0.3: - dependencies: - '@types/hast': 3.0.5 - '@types/unist': 3.0.3 - devlop: 1.1.0 - hastscript: 9.0.1 - property-information: 7.2.0 - vfile: 6.0.3 - vfile-location: 5.0.3 - web-namespaces: 2.0.1 - - hast-util-parse-selector@4.0.0: - dependencies: - '@types/hast': 3.0.5 - - hast-util-to-html@9.0.5: - dependencies: - '@types/hast': 3.0.5 - '@types/unist': 3.0.3 - ccount: 2.0.1 - comma-separated-tokens: 2.0.3 - hast-util-whitespace: 3.0.0 - html-void-elements: 3.0.0 - mdast-util-to-hast: 13.2.1 - property-information: 7.2.0 - space-separated-tokens: 2.0.2 - stringify-entities: 4.0.4 - zwitch: 2.0.4 - - hast-util-whitespace@3.0.0: - dependencies: - '@types/hast': 3.0.5 - - hastscript@9.0.1: - dependencies: - '@types/hast': 3.0.5 - comma-separated-tokens: 2.0.3 - hast-util-parse-selector: 4.0.0 - property-information: 7.2.0 - space-separated-tokens: 2.0.2 - - html-encoding-sniffer@6.0.0: - dependencies: - '@exodus/bytes': 1.15.1 - transitivePeerDependencies: - - '@noble/hashes' - - html-escaper@3.0.3: {} - - html-void-elements@3.0.0: {} - - http-cache-semantics@4.2.0: {} - - iron-webcrypto@1.2.1: {} - - is-docker@4.0.0: {} - - is-plain-obj@4.1.0: {} - - is-potential-custom-element-name@1.0.1: {} - - js-yaml@4.3.1: - dependencies: - argparse: 2.0.1 - - jsdom@30.0.0: - dependencies: - '@asamuzakjp/css-color': 6.0.5 - '@asamuzakjp/dom-selector': 8.3.2 - '@bramus/specificity': 2.4.2 - '@csstools/css-syntax-patches-for-csstree': 1.1.7(css-tree@3.2.1) - '@exodus/bytes': 1.15.1 - css-tree: 3.2.1 - data-urls: 7.0.0 - decimal.js: 10.6.0 - html-encoding-sniffer: 6.0.0 - is-potential-custom-element-name: 1.0.1 - lru-cache: 11.5.2 - parse5: 8.0.1 - saxes: 6.0.0 - symbol-tree: 3.2.4 - tough-cookie: 6.0.2 - undici: 8.9.0 - w3c-xmlserializer: 5.0.0 - webidl-conversions: 8.0.1 - whatwg-mimetype: 5.0.0 - whatwg-url: 17.1.0 - xml-name-validator: 5.0.0 - transitivePeerDependencies: - - '@noble/hashes' - - json-schema-traverse@1.0.0: {} - - jsonc-parser@2.3.0: {} - - jsonc-parser@3.0.0: {} - - jsonc-parser@3.3.1: {} - - kleur@4.1.5: {} - - lightningcss-android-arm64@1.33.0: - optional: true - - lightningcss-darwin-arm64@1.33.0: - optional: true - - lightningcss-darwin-x64@1.33.0: - optional: true - - lightningcss-freebsd-x64@1.33.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.33.0: - optional: true - - lightningcss-linux-arm64-gnu@1.33.0: - optional: true - - lightningcss-linux-arm64-musl@1.33.0: - optional: true - - lightningcss-linux-x64-gnu@1.33.0: - optional: true - - lightningcss-linux-x64-musl@1.33.0: - optional: true - - lightningcss-win32-arm64-msvc@1.33.0: - optional: true - - lightningcss-win32-x64-msvc@1.33.0: - optional: true - - lightningcss@1.33.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.33.0 - lightningcss-darwin-arm64: 1.33.0 - lightningcss-darwin-x64: 1.33.0 - lightningcss-freebsd-x64: 1.33.0 - lightningcss-linux-arm-gnueabihf: 1.33.0 - lightningcss-linux-arm64-gnu: 1.33.0 - lightningcss-linux-arm64-musl: 1.33.0 - lightningcss-linux-x64-gnu: 1.33.0 - lightningcss-linux-x64-musl: 1.33.0 - lightningcss-win32-arm64-msvc: 1.33.0 - lightningcss-win32-x64-msvc: 1.33.0 - - lru-cache@11.5.2: {} - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - magic-string@1.1.0: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - magicast@0.5.4: - dependencies: - '@babel/parser': 7.29.8 - '@babel/types': 7.29.8 - source-map-js: 1.2.1 - - mdast-util-to-hast@13.2.1: - dependencies: - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.3.3 - devlop: 1.1.0 - micromark-util-sanitize-uri: 2.0.0 - trim-lines: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit: 5.1.0 - vfile: 6.0.3 - - mdn-data@2.0.28: {} - - mdn-data@2.27.1: {} - - micromark-util-character@2.0.0: - dependencies: - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 - - micromark-util-encode@2.0.0: {} - - micromark-util-sanitize-uri@2.0.0: - dependencies: - micromark-util-character: 2.0.0 - micromark-util-encode: 2.0.0 - micromark-util-symbol: 2.0.0 - - micromark-util-symbol@2.0.0: {} - - micromark-util-types@2.0.0: {} - - mrmime@2.0.1: {} - - muggle-string@0.4.1: {} - - nanoid@3.3.16: {} - - neotraverse@1.0.1: {} - - nlcst-to-string@4.0.0: - dependencies: - '@types/nlcst': 2.0.3 - - node-fetch-native@1.6.7: {} - - node-mock-http@1.0.5: {} - - normalize-path@3.0.0: {} - - nth-check@2.0.1: - dependencies: - boolbase: 1.0.0 - - obug@2.1.4: {} - - ofetch@1.5.1: - dependencies: - destr: 2.0.5 - node-fetch-native: 1.6.7 - ufo: 1.6.4 - - ohash@2.0.11: {} - - oniguruma-parser@0.12.2: {} - - oniguruma-to-es@4.3.6: - dependencies: - oniguruma-parser: 0.12.2 - regex: 6.1.0 - regex-recursion: 6.0.2 - - p-limit@7.3.1: - dependencies: - yocto-queue: 1.2.2 - - p-queue@9.3.3: - dependencies: - eventemitter3: 5.0.4 - p-timeout: 7.0.1 - - p-timeout@7.0.1: {} - - package-manager-detector@1.8.0: {} - - parse5@7.0.0: - dependencies: - entities: 4.3.0 - - parse5@8.0.1: - dependencies: - entities: 8.0.0 - - path-browserify@1.0.1: {} - - pathe@2.0.3: {} - - piccolore@0.1.3: {} - - picocolors@1.1.1: {} - - picomatch@2.3.2: {} - - picomatch@4.0.5: {} - - playwright-core@1.62.0: {} - - playwright@1.62.0: - dependencies: - playwright-core: 1.62.0 - optionalDependencies: - fsevents: 2.3.2 - - postcss@8.5.25: - dependencies: - nanoid: 3.3.16 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - prettier@3.9.6: {} - - prismjs@1.30.0: {} - - process-ancestry@0.1.0: {} - - property-information@7.2.0: {} - - punycode@2.3.1: {} - - radix3@1.1.2: {} - - readdirp@4.1.2: {} - - readdirp@5.0.0: {} - - regex-recursion@6.0.2: - dependencies: - regex-utilities: 2.3.0 - - regex-utilities@2.3.0: {} - - regex@6.1.0: - dependencies: - regex-utilities: 2.3.0 - - request-light@0.5.8: {} - - request-light@0.7.0: {} - - require-from-string@2.0.2: {} - - resolve-pkg-maps@1.0.0: {} - - retext-smartypants@6.2.0: - dependencies: - '@types/nlcst': 2.0.3 - nlcst-to-string: 4.0.0 - unist-util-visit: 5.1.0 - - rolldown@1.2.1: - dependencies: - '@oxc-project/types': 0.142.0 - '@rolldown/pluginutils': 1.0.1 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.2.1 - '@rolldown/binding-darwin-arm64': 1.2.1 - '@rolldown/binding-darwin-x64': 1.2.1 - '@rolldown/binding-freebsd-x64': 1.2.1 - '@rolldown/binding-linux-arm-gnueabihf': 1.2.1 - '@rolldown/binding-linux-arm64-gnu': 1.2.1 - '@rolldown/binding-linux-arm64-musl': 1.2.1 - '@rolldown/binding-linux-ppc64-gnu': 1.2.1 - '@rolldown/binding-linux-s390x-gnu': 1.2.1 - '@rolldown/binding-linux-x64-gnu': 1.2.1 - '@rolldown/binding-linux-x64-musl': 1.2.1 - '@rolldown/binding-openharmony-arm64': 1.2.1 - '@rolldown/binding-wasm32-wasi': 1.2.1 - '@rolldown/binding-win32-arm64-msvc': 1.2.1 - '@rolldown/binding-win32-x64-msvc': 1.2.1 - - satteri@0.9.5: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - optionalDependencies: - '@bruits/satteri-darwin-arm64': 0.9.5 - '@bruits/satteri-darwin-x64': 0.9.5 - '@bruits/satteri-linux-arm64-gnu': 0.9.5 - '@bruits/satteri-linux-arm64-musl': 0.9.5 - '@bruits/satteri-linux-x64-gnu': 0.9.5 - '@bruits/satteri-linux-x64-musl': 0.9.5 - '@bruits/satteri-wasm32-wasi': 0.9.5 - '@bruits/satteri-win32-arm64-msvc': 0.9.5 - '@bruits/satteri-win32-x64-msvc': 0.9.5 - - sax@1.6.1: {} - - saxes@6.0.0: - dependencies: - xmlchars: 2.2.0 - - semver@7.8.5: {} - - sharp@0.35.3(@types/node@24.13.3): - dependencies: - '@img/colour': 1.1.0 - detect-libc: 2.1.2 - semver: 7.8.5 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.35.3 - '@img/sharp-darwin-x64': 0.35.3 - '@img/sharp-freebsd-wasm32': 0.35.3 - '@img/sharp-libvips-darwin-arm64': 1.3.2 - '@img/sharp-libvips-darwin-x64': 1.3.2 - '@img/sharp-libvips-linux-arm': 1.3.2 - '@img/sharp-libvips-linux-arm64': 1.3.2 - '@img/sharp-libvips-linux-ppc64': 1.3.2 - '@img/sharp-libvips-linux-riscv64': 1.3.2 - '@img/sharp-libvips-linux-s390x': 1.3.2 - '@img/sharp-libvips-linux-x64': 1.3.2 - '@img/sharp-libvips-linuxmusl-arm64': 1.3.2 - '@img/sharp-libvips-linuxmusl-x64': 1.3.2 - '@img/sharp-linux-arm': 0.35.3 - '@img/sharp-linux-arm64': 0.35.3 - '@img/sharp-linux-ppc64': 0.35.3 - '@img/sharp-linux-riscv64': 0.35.3 - '@img/sharp-linux-s390x': 0.35.3 - '@img/sharp-linux-x64': 0.35.3 - '@img/sharp-linuxmusl-arm64': 0.35.3 - '@img/sharp-linuxmusl-x64': 0.35.3 - '@img/sharp-webcontainers-wasm32': 0.35.3 - '@img/sharp-win32-arm64': 0.35.3 - '@img/sharp-win32-ia32': 0.35.3 - '@img/sharp-win32-x64': 0.35.3 - '@types/node': 24.13.3 - optional: true - - shiki@4.4.1: - dependencies: - '@shikijs/core': 4.4.1 - '@shikijs/engine-javascript': 4.4.1 - '@shikijs/engine-oniguruma': 4.4.1 - '@shikijs/langs': 4.4.1 - '@shikijs/themes': 4.4.1 - '@shikijs/types': 4.4.1 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.5 - - siginfo@2.0.0: {} - - sisteransi@1.0.5: {} - - sitemap@9.0.1: - dependencies: - '@types/node': 24.13.3 - '@types/sax': 1.2.7 - arg: 5.0.2 - sax: 1.6.1 - - smol-toml@1.7.1: {} - - source-map-js@1.2.1: {} - - space-separated-tokens@2.0.2: {} - - stackback@0.0.2: {} - - std-env@4.2.0: {} - - stream-replace-string@2.0.0: {} - - string-width@7.2.0: - dependencies: - emoji-regex: 10.6.0 - get-east-asian-width: 1.6.0 - strip-ansi: 7.2.0 - - string-width@8.2.2: - dependencies: - get-east-asian-width: 1.6.0 - strip-ansi: 7.2.0 - - stringify-entities@4.0.4: - dependencies: - character-entities-html4: 2.1.0 - character-entities-legacy: 3.0.0 - - strip-ansi@7.2.0: - dependencies: - ansi-regex: 6.2.2 - - svgo@4.0.2: - dependencies: - commander: 11.1.0 - css-select: 5.1.0 - css-tree: 3.2.1 - css-what: 6.1.0 - csso: 5.0.5 - picocolors: 1.1.1 - sax: 1.6.1 - - symbol-tree@3.2.4: {} - - tiny-inflate@1.0.3: {} - - tinybench@2.9.0: {} - - tinyclip@0.1.15: {} - - tinyexec@1.3.0: {} - - tinyglobby@0.2.17: - dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 - - tinyrainbow@3.1.1: {} - - tldts-core@7.4.10: {} - - tldts@7.4.10: - dependencies: - tldts-core: 7.4.10 - - tough-cookie@6.0.2: - dependencies: - tldts: 7.4.10 - - tr46@6.0.0: - dependencies: - punycode: 2.3.1 - - trim-lines@3.0.1: {} - - trough@2.2.0: {} - - tslib@2.8.1: - optional: true - - typesafe-path@0.2.2: {} - - typescript-auto-import-cache@0.3.6: - dependencies: - semver: 7.8.5 - - typescript@6.0.3: {} - - ufo@1.6.4: {} - - ultrahtml@1.7.0: {} - - uncrypto@0.1.3: {} - - undici-types@7.18.2: {} - - undici@8.9.0: {} - - unified@11.0.5: - dependencies: - '@types/unist': 3.0.3 - bail: 2.0.2 - devlop: 1.1.0 - extend: 3.0.2 - is-plain-obj: 4.1.0 - trough: 2.2.0 - vfile: 6.0.3 - - unifont@0.7.4: - dependencies: - css-tree: 3.2.1 - ofetch: 1.5.1 - ohash: 2.0.11 - - unist-util-is@6.0.1: - dependencies: - '@types/unist': 3.0.3 - - unist-util-position@5.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-stringify-position@4.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-visit-parents@6.0.2: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - - unist-util-visit@5.1.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - unist-util-visit-parents: 6.0.2 - - unstorage@1.17.5: - dependencies: - anymatch: 3.1.3 - chokidar: 5.0.0 - destr: 2.0.5 - h3: 1.15.11 - lru-cache: 11.5.2 - node-fetch-native: 1.6.7 - ofetch: 1.5.1 - ufo: 1.6.4 - - vfile-location@5.0.3: - dependencies: - '@types/unist': 3.0.3 - vfile: 6.0.3 - - vfile-message@4.0.3: - dependencies: - '@types/unist': 3.0.3 - unist-util-stringify-position: 4.0.0 - - vfile@6.0.3: - dependencies: - '@types/unist': 3.0.3 - vfile-message: 4.0.3 - - vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(yaml@2.9.0): - dependencies: - lightningcss: 1.33.0 - picomatch: 4.0.5 - postcss: 8.5.25 - rolldown: 1.2.1 - tinyglobby: 0.2.17 - optionalDependencies: - '@types/node': 24.13.3 - esbuild: 0.28.1 - fsevents: 2.3.3 - yaml: 2.9.0 - - vitefu@1.1.3(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(yaml@2.9.0)): - optionalDependencies: - vite: 8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(yaml@2.9.0) - - vitest@4.1.10(@types/node@24.13.3)(jsdom@30.0.0)(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(yaml@2.9.0)): - dependencies: - '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.10 - '@vitest/runner': 4.1.10 - '@vitest/snapshot': 4.1.10 - '@vitest/spy': 4.1.10 - '@vitest/utils': 4.1.10 - es-module-lexer: 2.3.1 - expect-type: 1.4.0 - magic-string: 0.30.21 - obug: 2.1.4 - pathe: 2.0.3 - picomatch: 4.0.5 - std-env: 4.2.0 - tinybench: 2.9.0 - tinyexec: 1.3.0 - tinyglobby: 0.2.17 - tinyrainbow: 3.1.1 - vite: 8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(yaml@2.9.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 24.13.3 - jsdom: 30.0.0 - transitivePeerDependencies: - - msw - - volar-service-css@0.0.71(@volar/language-service@2.4.28): - dependencies: - vscode-css-languageservice: 6.3.10 - vscode-languageserver-textdocument: 1.0.13 - vscode-uri: 3.1.0 - optionalDependencies: - '@volar/language-service': 2.4.28 - - volar-service-emmet@0.0.71(@volar/language-service@2.4.28): - dependencies: - '@emmetio/css-parser': 0.4.1 - '@emmetio/html-matcher': 1.3.0 - '@vscode/emmet-helper': 2.11.0 - vscode-uri: 3.1.0 - optionalDependencies: - '@volar/language-service': 2.4.28 - - volar-service-html@0.0.71(@volar/language-service@2.4.28): - dependencies: - vscode-html-languageservice: 5.6.2 - vscode-languageserver-textdocument: 1.0.13 - vscode-uri: 3.1.0 - optionalDependencies: - '@volar/language-service': 2.4.28 - - volar-service-prettier@0.0.71(@volar/language-service@2.4.28)(prettier@3.9.6): - dependencies: - vscode-uri: 3.1.0 - optionalDependencies: - '@volar/language-service': 2.4.28 - prettier: 3.9.6 - - volar-service-typescript-twoslash-queries@0.0.71(@volar/language-service@2.4.28): - dependencies: - vscode-uri: 3.1.0 - optionalDependencies: - '@volar/language-service': 2.4.28 - - volar-service-typescript@0.0.71(@volar/language-service@2.4.28): - dependencies: - path-browserify: 1.0.1 - semver: 7.8.5 - typescript-auto-import-cache: 0.3.6 - vscode-languageserver-textdocument: 1.0.13 - vscode-nls: 5.2.0 - vscode-uri: 3.1.0 - optionalDependencies: - '@volar/language-service': 2.4.28 - - volar-service-yaml@0.0.71(@volar/language-service@2.4.28): - dependencies: - vscode-uri: 3.1.0 - yaml-language-server: 1.23.0 - optionalDependencies: - '@volar/language-service': 2.4.28 - - vscode-css-languageservice@6.3.10: - dependencies: - '@vscode/l10n': 0.0.18 - vscode-languageserver-textdocument: 1.0.13 - vscode-languageserver-types: 3.17.5 - vscode-uri: 3.1.0 - - vscode-html-languageservice@5.6.2: - dependencies: - '@vscode/l10n': 0.0.18 - vscode-languageserver-textdocument: 1.0.13 - vscode-languageserver-types: 3.18.0 - vscode-uri: 3.1.0 - - vscode-json-languageservice@4.1.8: - dependencies: - jsonc-parser: 3.0.0 - vscode-languageserver-textdocument: 1.0.13 - vscode-languageserver-types: 3.18.0 - vscode-nls: 5.2.0 - vscode-uri: 3.1.0 - - vscode-jsonrpc@8.2.0: {} - - vscode-jsonrpc@9.0.1: {} - - vscode-languageserver-protocol@3.17.4: - dependencies: - vscode-jsonrpc: 8.2.0 - vscode-languageserver-types: 3.17.4 - - vscode-languageserver-protocol@3.17.5: - dependencies: - vscode-jsonrpc: 8.2.0 - vscode-languageserver-types: 3.17.5 - - vscode-languageserver-protocol@3.18.2: - dependencies: - vscode-jsonrpc: 9.0.1 - vscode-languageserver-types: 3.18.0 - - vscode-languageserver-textdocument@1.0.13: {} - - vscode-languageserver-types@3.17.4: {} - - vscode-languageserver-types@3.17.5: {} - - vscode-languageserver-types@3.18.0: {} - - vscode-languageserver@9.0.0: - dependencies: - vscode-languageserver-protocol: 3.17.4 - - vscode-languageserver@9.0.1: - dependencies: - vscode-languageserver-protocol: 3.17.5 - - vscode-nls@5.2.0: {} - - vscode-uri@3.1.0: {} - - w3c-xmlserializer@5.0.0: - dependencies: - xml-name-validator: 5.0.0 - - web-namespaces@2.0.1: {} - - webidl-conversions@8.0.1: {} - - whatwg-mimetype@5.0.0: {} - - whatwg-url@16.0.1: - dependencies: - '@exodus/bytes': 1.15.1 - tr46: 6.0.0 - webidl-conversions: 8.0.1 - transitivePeerDependencies: - - '@noble/hashes' - - whatwg-url@17.1.0: - dependencies: - '@exodus/bytes': 1.15.1 - tr46: 6.0.0 - webidl-conversions: 8.0.1 - transitivePeerDependencies: - - '@noble/hashes' - - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - - wrap-ansi@9.0.0: - dependencies: - ansi-styles: 6.2.1 - string-width: 7.2.0 - strip-ansi: 7.2.0 - - xml-name-validator@5.0.0: {} - - xmlchars@2.2.0: {} - - xxhash-wasm@1.1.0: {} - - y18n@5.0.8: {} - - yaml-language-server@1.23.0: - dependencies: - '@vscode/l10n': 0.0.18 - ajv: 8.20.0 - ajv-draft-04: 1.0.0(ajv@8.20.0) - ajv-i18n: 4.2.0(ajv@8.20.0) - prettier: 3.9.6 - request-light: 0.5.8 - vscode-json-languageservice: 4.1.8 - vscode-languageserver: 9.0.0 - vscode-languageserver-textdocument: 1.0.13 - vscode-languageserver-types: 3.18.0 - vscode-uri: 3.1.0 - yaml: 2.8.3 - - yaml@2.8.3: {} - - yaml@2.9.0: {} - - yargs-parser@22.0.0: {} - - yargs@18.1.0: - dependencies: - cliui: 9.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - string-width: 8.2.2 - y18n: 5.0.8 - yargs-parser: 22.0.0 - - yocto-queue@1.2.2: {} - - zod@4.4.3: {} - - zwitch@2.0.4: {} diff --git a/site/pnpm-workspace.yaml b/site/pnpm-workspace.yaml deleted file mode 100644 index 5ed0b5a..0000000 --- a/site/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -allowBuilds: - esbuild: true diff --git a/site/public/_headers b/site/public/_headers deleted file mode 100644 index f70554d..0000000 --- a/site/public/_headers +++ /dev/null @@ -1,5 +0,0 @@ -/* - X-Content-Type-Options: nosniff - Referrer-Policy: strict-origin-when-cross-origin - Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=() - Content-Security-Policy: default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none' diff --git a/site/public/favicon.svg b/site/public/favicon.svg deleted file mode 100644 index ec260bf..0000000 --- a/site/public/favicon.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/site/public/images/agents/aider.png b/site/public/images/agents/aider.png deleted file mode 100644 index 0a92130..0000000 Binary files a/site/public/images/agents/aider.png and /dev/null differ diff --git a/site/public/images/agents/claude-code.svg b/site/public/images/agents/claude-code.svg deleted file mode 100644 index 26d103d..0000000 --- a/site/public/images/agents/claude-code.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/site/public/images/agents/codex.svg b/site/public/images/agents/codex.svg deleted file mode 100644 index 78caf4f..0000000 --- a/site/public/images/agents/codex.svg +++ /dev/null @@ -1 +0,0 @@ -OpenAI \ No newline at end of file diff --git a/site/public/images/agents/cursor.svg b/site/public/images/agents/cursor.svg deleted file mode 100644 index f814fdc..0000000 --- a/site/public/images/agents/cursor.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/site/public/images/agents/hermes.png b/site/public/images/agents/hermes.png deleted file mode 100644 index 2d629d2..0000000 Binary files a/site/public/images/agents/hermes.png and /dev/null differ diff --git a/site/public/images/agents/kilo-cli.svg b/site/public/images/agents/kilo-cli.svg deleted file mode 100644 index c4b6bb8..0000000 --- a/site/public/images/agents/kilo-cli.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/public/images/agents/openclaw.svg b/site/public/images/agents/openclaw.svg deleted file mode 100644 index 7bfb7fc..0000000 --- a/site/public/images/agents/openclaw.svg +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/site/public/images/agents/opencode.svg b/site/public/images/agents/opencode.svg deleted file mode 100644 index 9c4cb89..0000000 --- a/site/public/images/agents/opencode.svg +++ /dev/null @@ -1 +0,0 @@ -opencode \ No newline at end of file diff --git a/site/public/images/oneagent-overview.jpg b/site/public/images/oneagent-overview.jpg deleted file mode 100644 index 6749edf..0000000 Binary files a/site/public/images/oneagent-overview.jpg and /dev/null differ diff --git a/site/public/llms.txt b/site/public/llms.txt deleted file mode 100644 index a2d9e78..0000000 --- a/site/public/llms.txt +++ /dev/null @@ -1,38 +0,0 @@ -# OneAgent - -> A trustworthy local AI development environment activator. It detects, installs -> and configures CLI coding agents to point at an OpenAI- or Anthropic-compatible -> provider of your choosing. Everything runs locally. - -Current release: 0.2.0-dev, published only as `technical-preview-unsigned`. -The build is unsigned and unnotarised, and is never described as stable. - -## Product boundary - -- Runs locally. Configuration and backups stay on the user's device. -- Bring your own key. There is no shared key and no unified model gateway. -- Model requests are never proxied through OneAgent. -- Third-party agent binaries are not redistributed; agents come from their - official sources. -- Every distribution channel serves a byte-identical build with the same - SHA-256. - -## Pages - -- /: overview, first-success path, agent compatibility -- /downloads/: per-platform artifacts with size, build date and SHA-256 -- /quickstart/: download through to a verified first agent configuration -- /agents/: which agents OneAgent can install and configure, stated separately -- /providers/: provider protocol support and commercial-relationship disclosure -- /security/: local execution, key handling, backups, release integrity -- /changelog/: shipped changes only -- /enterprise/: team enablement and environment baselines -- /release-index.json: machine-readable release index - -English translations exist for /en/, /en/downloads/ and /en/quickstart/. - -## Notes for machine readers - -Support is stated as three separate facts — managed install, managed -configuration, and protocol — because a single "supported" checkmark would -misrepresent guide-only agents. Do not collapse them. diff --git a/site/public/site.webmanifest b/site/public/site.webmanifest deleted file mode 100644 index 70f2b9b..0000000 --- a/site/public/site.webmanifest +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "OneAgent", - "short_name": "OneAgent", - "description": "A trustworthy local AI development environment activator.", - "start_url": "./", - "display": "browser", - "background_color": "#ececef", - "theme_color": "#ececef", - "icons": [{ "src": "favicon.svg", "type": "image/svg+xml", "sizes": "any" }] -} diff --git a/site/scripts/validate-build.mjs b/site/scripts/validate-build.mjs deleted file mode 100644 index 38090e7..0000000 --- a/site/scripts/validate-build.mjs +++ /dev/null @@ -1,102 +0,0 @@ -import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"; -import { join, relative } from "node:path"; - -const root = new URL("../dist/", import.meta.url); -const rootPath = root.pathname; -const failures = []; -const htmlFiles = []; -const configuredBase = (process.env.BASE_PATH ?? "/").replace(/^\/+|\/+$/g, ""); -const configuredBasePrefix = configuredBase ? `/${configuredBase}/` : "/"; - -function walk(directory) { - for (const name of readdirSync(directory)) { - const path = join(directory, name); - if (statSync(path).isDirectory()) walk(path); - else if (name.endsWith(".html")) htmlFiles.push(path); - else if (name.endsWith(".map")) failures.push(`Source map is forbidden: ${relative(rootPath, path)}`); - } -} - -function localTarget(href) { - const clean = href.split("#", 1)[0].split("?", 1)[0]; - if (!clean || clean.startsWith("mailto:") || clean.startsWith("http://") || clean.startsWith("https://") || clean.startsWith("data:")) return null; - let normalized = clean.startsWith("/") ? clean.slice(1) : clean; - if (configuredBase && (normalized === configuredBase || normalized.startsWith(`${configuredBase}/`))) { - normalized = normalized.slice(configuredBase.length).replace(/^\//, ""); - } - if (!normalized) return join(rootPath, "index.html"); - const direct = join(rootPath, normalized); - if (existsSync(direct) && statSync(direct).isFile()) return direct; - if (existsSync(direct) && statSync(direct).isDirectory()) return join(direct, "index.html"); - if (!normalized.includes(".")) return join(rootPath, normalized, "index.html"); - return direct; -} - -walk(rootPath); -// The /en/ entries are listed for the same reason as the rest: a translated page -// silently dropping out of the build is otherwise invisible, since the link -// checker below only sees links that were actually emitted. -// -// release-index.json is no longer among them: the site reads release data from -// GitHub Releases at build time instead of republishing a locally generated -// index, so there is no such artifact to require. -for (const required of ["index.html", "downloads/index.html", "quickstart/index.html", "agents/index.html", "providers/index.html", "security/index.html", "explore/index.html", "en/index.html", "en/downloads/index.html", "en/quickstart/index.html", "en/explore/index.html", "en/security/index.html", "llms.txt", "site.webmanifest"]) { - if (!existsSync(join(rootPath, required))) failures.push(`Missing required output: ${required}`); -} - -for (const path of htmlFiles) { - const text = readFileSync(path, "utf8"); - const label = relative(rootPath, path); - for (const required of ["", 'name="description"', 'rel="canonical"', "<h1"]) { - if (!text.includes(required)) failures.push(`${label} is missing ${required}`); - } - const baseHref = text.match(/<base href="([^"]+)"/)?.[1]; - if (!baseHref) failures.push(`${label} is missing a base URL`); - else if (new URL(baseHref).pathname !== configuredBasePrefix) failures.push(`${label} has the wrong base URL: ${baseHref}`); - const socialImage = text.match(/<meta property="og:image" content="([^"]+)"/)?.[1]; - if (!socialImage) failures.push(`${label} is missing an Open Graph image`); - else if (!new URL(socialImage).pathname.startsWith(`${configuredBasePrefix}images/`)) failures.push(`${label} has an Open Graph image outside the base path: ${socialImage}`); - if (/<script[^>]+src=["']https?:\/\//i.test(text) || /<img[^>]+src=["']https?:\/\//i.test(text) || /<link[^>]+rel=["']stylesheet["'][^>]+href=["']https?:\/\//i.test(text)) failures.push(`${label} loads a remote script, image, or stylesheet`); - if (/sk-[A-Za-z0-9_-]{20,}/.test(text)) failures.push(`${label} appears to contain an API key`); - // A page's declared language has to match the directory it was emitted into, - // and any hreflang set has to be reciprocal and carry an x-default. Neither is - // visible to the link check below, which only sees hrefs. - const declaredLang = text.match(/<html lang="([^"]+)"/)?.[1]; - const expectedLang = label.startsWith("en/") || label === "en.html" ? "en" : "zh-CN"; - if (!declaredLang) failures.push(`${label} is missing a lang attribute`); - else if (declaredLang !== expectedLang) failures.push(`${label} declares lang="${declaredLang}" but sits under ${expectedLang}`); - const alternates = [...text.matchAll(/<link rel="alternate" hreflang="([^"]+)"/g)].map(([, code]) => code); - if (alternates.length && !alternates.includes("x-default")) { - failures.push(`${label} declares hreflang alternates without an x-default`); - } - if (alternates.length && !(alternates.includes("en") && alternates.includes("zh-CN"))) { - failures.push(`${label} declares an incomplete hreflang set: ${alternates.join(", ")}`); - } - const matches = text.matchAll(/(?:href|src)="([^"]+)"/g); - for (const [, href] of matches) { - if (configuredBase && href.startsWith("/") && !href.startsWith(configuredBasePrefix) && !href.startsWith("//")) { - failures.push(`${label} escapes the configured base path: ${href}`); - } - const target = localTarget(href); - if (target && !existsSync(target)) failures.push(`${label} has a broken local reference: ${href}`); - } -} - -/* This used to re-hash every artifact in dist/downloads/ and compare it against - * the digest the release index claimed, catching a page that printed a checksum - * the file did not actually have. - * - * That check has no subject any more. The site no longer hosts the artifacts — - * it links to GitHub Releases and prints the digest the API reported, so there - * is no local file to hash and nothing to compare a second opinion against. Be - * aware of what that costs: a wrong digest from the release feed now reaches the - * page unchallenged, where previously it could not survive the build. - * - * Restoring an equivalent gate means fetching each asset during validation and - * hashing it, which is a network round trip per artifact. - */ -if (failures.length) { - console.error(failures.join("\n")); - process.exit(1); -} -console.log(`Validated ${htmlFiles.length} HTML pages.`); diff --git a/site/src/components/ActivationConsole.astro b/site/src/components/ActivationConsole.astro deleted file mode 100644 index fb2bc43..0000000 --- a/site/src/components/ActivationConsole.astro +++ /dev/null @@ -1,1485 +0,0 @@ ---- -import AgentMark from "./AgentMark.astro"; -import { catalog } from "../lib/catalog"; -import { bestLocaleFor, localeFromPath, localePath, switchesLanguage } from "../i18n"; -import { DEMO_CUSTOM_MODEL_HINT, demoStateFor } from "../lib/demo-environment"; -import { recommendedCombination } from "../lib/explorer"; - -interface Props { - id?: string; -} - -const { id = "activation-console" } = Astro.props; -const locale = localeFromPath(Astro.url.pathname); -const featuredAgents = catalog.agents.slice(0, 4); -const recommendation = recommendedCombination(catalog); -const href = (path: string) => localePath(bestLocaleFor(locale, path), path); -const copy = locale === "en" - ? { - label: "Interactive product evidence", - sample: "Demo environment", - title: "Activation console", - subtitle: "A guided example. This page never scans your device or asks for an API key.", - idleTitle: "Ready when you are.", - idleBody: "Start from the hero action. OneAgent will walk through an example environment without touching this computer.", - scanningTitle: "Scanning the example environment", - scanningBody: "Reading a deterministic demo scenario — not your browser, files or local services.", - agentTitle: "Choose an agent", - agentBody: "Capabilities and package sources come from the repository catalog. Machine state is explicitly illustrative.", - providerTitle: "Choose a provider", - providerBody: "Compatibility is calculated from the protocol registry. A preview gate never becomes Ready.", - verify: "Verify sample connection", - verifyingTitle: "Verifying the sample protocol", - verifyingBody: "No credential is submitted. The outcome follows the selected catalog compatibility level.", - resultLabel: "Example outcome", - reset: "Reset demo", - download: "Download OneAgent", - explore: "Open full Explorer", - security: "Release policy", - installed: "Installed", - notInstalled: "Not installed", - configured: "Configured", - needsSetup: "Needs setup", - officialGuide: "Official guide", - recommended: "Recommended path", - locked: "Version", - noLockedVersion: "Latest official", - command: "Launch", - managed: "Managed config", - guide: "Official setup", - facts: ["No device access", "No API key field", "Registry-derived compatibility"], - steps: ["Agent", "Setup", "Provider", "Model", "Confirm"], - modeTitle: "How should this agent be configured?", - modeBody: "OneAgent can point the agent at a model service, or leave an account you already use in place.", - modeProvider: "Configure a model service", - modeProviderHint: "Choose a provider or your own endpoint, then verify the protocol before anything is written.", - modeExisting: "Keep an existing account or config", - modeExistingHint: "Already signed in, or already configured? The provider and model steps are skipped.", - customName: "Custom endpoint", - customVerdict: "Your own OpenAI-compatible URL", - customLabel: "Base URL", - customPlaceholder: "https://api.example.com/openai", - customHint: "Any OpenAI-compatible endpoint. Validated the same way the app validates it.", - register: "Need a key? Open PPIO", - registerNote: "Opens ppio.com in a new tab. OneAgent has no account of its own.", - urlRequired: "Enter a base URL.", - urlScheme: "Must start with http:// or https://", - urlCredentials: "Remove the username or password from the URL.", - urlControl: "Remove control characters from the URL.", - urlOk: "Endpoint accepted", - modelTitle: "Choose a model", - modelBody: "The app reads this list from the endpoint itself. Pick one, or type an id if discovery comes back empty.", - modelManual: "Discovery empty? Enter an id", - modelManualLabel: "Model id", - confirm: "Confirm activation", - skipped: "Skipped", - /* The real window's sidebar is a workspace nav, not a progress list — the - stepper lives in the page header. These are its actual four entries. */ - navItems: ["Activate", "Overview", "Providers", "Templates"], - sidebarNote: "Configuration stays on this machine", - footerIdle: "The demo never touches this device", - chineseOnly: "in Chinese", - } - : { - label: "可操作的产品证据", - sample: "示例环境", - title: "激活控制台", - subtitle: "这是引导演示。页面不会扫描你的设备,也不会要求输入 API Key。", - idleTitle: "等待你启动演示。", - idleBody: "从首屏主按钮开始,OneAgent 将演示一次完整激活路径,但不会访问这台电脑。", - scanningTitle: "正在扫描示例环境", - scanningBody: "读取确定性的演示场景,不读取浏览器、本机文件或本地服务。", - agentTitle: "选择一个 Agent", - agentBody: "能力与包来源来自仓库目录;安装和配置状态明确属于示例。", - providerTitle: "选择一个 Provider", - providerBody: "兼容结果来自协议注册表;预览门禁不会被升级成 Ready。", - verify: "验证示例连接", - verifyingTitle: "正在验证示例协议", - verifyingBody: "不会提交任何凭证;结果严格跟随所选组合的兼容等级。", - resultLabel: "示例结果", - reset: "重置演示", - download: "下载 OneAgent", - explore: "打开完整配置目录", - security: "发行政策", - installed: "已安装", - notInstalled: "未安装", - configured: "已配置", - needsSetup: "待配置", - officialGuide: "官方引导", - recommended: "推荐路径", - locked: "版本", - noLockedVersion: "官方最新版本", - command: "启动命令", - managed: "托管配置", - guide: "官方设置", - facts: ["不访问设备", "不提供 Key 输入框", "兼容结论来自注册表"], - steps: ["Agent", "配置", "Provider", "模型", "确认"], - modeTitle: "这个 Agent 怎么配置?", - modeBody: "可以由 OneAgent 指向某个模型服务,也可以保留你已经在用的账号。", - modeProvider: "配置模型服务", - modeProviderHint: "选择 Provider 或你自己的端点,写入前先验证协议。", - modeExisting: "使用已有账号或配置", - modeExistingHint: "已经登录、或已经配置好?Provider 与模型两步会跳过。", - customName: "自定义端点", - customVerdict: "你自己的 OpenAI 兼容地址", - customLabel: "Base URL", - customPlaceholder: "https://api.example.com/openai", - customHint: "任何 OpenAI 兼容端点。校验规则与应用内一致。", - register: "还没有 Key?打开 PPIO", - registerNote: "在新标签页打开 ppio.com。OneAgent 自身没有账号体系。", - urlRequired: "请填写 Base URL。", - urlScheme: "需要以 http:// 或 https:// 开头。", - urlCredentials: "请从地址中去掉用户名或密码。", - urlControl: "请去掉地址中的控制字符。", - urlOk: "端点可用", - modelTitle: "选择模型", - modelBody: "应用会从端点自身读取这个列表。选一个,或在发现结果为空时手动输入 ID。", - modelManual: "列表为空?手动输入 ID", - modelManualLabel: "模型 ID", - confirm: "确认激活", - skipped: "已跳过", - /* The real window's sidebar is a workspace nav, not a progress list — the - stepper lives in the page header. These are its actual four entries. */ - navItems: ["激活环境", "环境总览", "Provider", "配置模板"], - sidebarNote: "配置只保存在本机", - footerIdle: "演示不会访问这台设备", - chineseOnly: "仅中文", - }; ---- -<div class="product-shot activation-shell"> - <activation-console id={id} data-locale={locale} data-phase="idle"> - <div class="activation-window" tabindex="-1" data-console-window> - <header class="activation-topbar"> - <div> - <span class="activation-kicker">{copy.label}</span> - <strong>{copy.title}</strong> - </div> - <span class="activation-sample"><span aria-hidden="true"></span>{copy.sample}</span> - </header> - - <div class="activation-body"> - <aside class="activation-rail" aria-label={locale === "en" ? "Workspace" : "工作区"}> - <div class="activation-rail-brand"> - <span class="activation-monogram" aria-hidden="true">OA</span> - <div><strong>OneAgent</strong><span>{copy.sample}</span></div> - </div> - {/* Presentational: the real sidebar navigates a workspace, but nothing - here is navigable, so these are not links or buttons. */} - <div class="activation-nav" aria-hidden="true"> - {copy.navItems.map((item, index) => ( - <span class:list={["activation-nav-item", { "is-active": index === 0 }]}>{item}</span> - ))} - </div> - <p class="activation-rail-note">{copy.sidebarNote}</p> - </aside> - - <div class="activation-main"> - <div class="activation-heading"> - <div> - <span class="activation-state-label" data-state-label>{copy.sample}</span> - <h2>{copy.title}</h2> - <p class="activation-subtitle">{copy.subtitle}</p> - </div> - <span class="activation-status" data-status><span aria-hidden="true"></span><b>Idle</b></span> - </div> - <ol class="activation-steps"> - {copy.steps.map((step, index) => ( - <li data-step={index + 1}> - <span class="activation-step-marker">{index + 1}</span> - <strong>{step}</strong> - </li> - ))} - </ol> - - <section class="activation-panel" data-panel="idle" tabindex="-1"> - <div class="activation-empty-mark" aria-hidden="true"><span></span><span></span><span></span></div> - <div> - <h3>{copy.idleTitle}</h3> - <p>{copy.idleBody}</p> - </div> - </section> - - <section class="activation-panel activation-progress" data-panel="scanning" tabindex="-1" hidden> - <div class="activation-scan" aria-hidden="true"><span></span></div> - <div> - <h3>{copy.scanningTitle}</h3> - <p>{copy.scanningBody}</p> - </div> - </section> - - <section class="activation-panel activation-choice-panel" data-panel="agent" tabindex="-1" hidden> - <div class="activation-panel-copy"> - <h3>{copy.agentTitle}</h3> - <p>{copy.agentBody}</p> - </div> - <div class="activation-agent-grid" role="group" aria-label={copy.agentTitle}> - {featuredAgents.map((agent) => { - const demo = demoStateFor(agent); - return ( - <button - type="button" - class="activation-agent" - data-agent-id={agent.id} - aria-pressed="false" - > - <AgentMark id={agent.id} name={agent.name} size={28} /> - <span class="activation-agent-copy"> - <strong>{agent.name}</strong> - <small> - {demo.installed ? copy.installed : copy.notInstalled} - {agent.lockedVersion ? ` · ${copy.locked} ${agent.lockedVersion}` : ` · ${copy.noLockedVersion}`} - </small> - </span> - <span class:list={["activation-agent-state", `is-${demo.status}`]}> - {agent.support.officialInstallGuide ? copy.officialGuide : demo.configured ? copy.configured : copy.needsSetup} - </span> - {recommendation?.agentId === agent.id && <span class="activation-recommended">{copy.recommended}</span>} - </button> - ); - })} - </div> - </section> - - <section class="activation-panel activation-choice-panel" data-panel="mode" tabindex="-1" hidden> - <div class="activation-panel-copy"> - <h3>{copy.modeTitle}</h3> - <p>{copy.modeBody}</p> - </div> - <div class="activation-mode-grid" role="group" aria-label={copy.modeTitle}> - <button type="button" class="activation-mode" data-mode="provider" aria-pressed="false"> - <strong>{copy.modeProvider}</strong> - <small>{copy.modeProviderHint}</small> - </button> - <button type="button" class="activation-mode" data-mode="existing-account" aria-pressed="false"> - <strong>{copy.modeExisting}</strong> - <small>{copy.modeExistingHint}</small> - </button> - </div> - </section> - - <section class="activation-panel activation-choice-panel" data-panel="provider" tabindex="-1" hidden> - <div class="activation-panel-copy"> - <h3>{copy.providerTitle}</h3> - <p>{copy.providerBody}</p> - </div> - <div class="activation-selection-line"> - <span data-selected-agent>—</span> - <span aria-hidden="true">→</span> - <span data-selected-protocol>—</span> - </div> - <div class="activation-provider-grid" role="group" aria-label={copy.providerTitle}> - {catalog.providers.map((provider) => ( - <button type="button" class="activation-provider" data-provider-id={provider.id} aria-pressed="false"> - <span><strong>{provider.name}</strong><small data-provider-verdict>—</small></span> - <span class="activation-provider-arrow" aria-hidden="true">↗</span> - </button> - ))} - <button type="button" class="activation-provider is-custom" data-provider-id="custom" aria-pressed="false"> - <span><strong>{copy.customName}</strong><small>{copy.customVerdict}</small></span> - <span class="activation-provider-arrow" aria-hidden="true">+</span> - </button> - </div> - <div class="activation-custom" data-custom-field hidden> - <label for={`${id}-base-url`}>{copy.customLabel}</label> - <input - id={`${id}-base-url`} - type="url" - inputmode="url" - autocomplete="off" - spellcheck="false" - placeholder={copy.customPlaceholder} - data-custom-input - aria-describedby={`${id}-base-url-hint`} - /> - <p class="activation-custom-hint" id={`${id}-base-url-hint`} data-custom-hint data-idle={copy.customHint} data-kind="idle">{copy.customHint}</p> - <a class="activation-register" href="https://ppio.com/" target="_blank" rel="noopener noreferrer"> - {copy.register} - <small>{copy.registerNote}</small> - </a> - </div> - </section> - - <section class="activation-panel activation-choice-panel" data-panel="model" tabindex="-1" hidden> - <div class="activation-panel-copy"> - <h3>{copy.modelTitle}</h3> - <p>{copy.modelBody}</p> - </div> - <div class="activation-model-grid" role="group" aria-label={copy.modelTitle} data-model-grid></div> - <details class="activation-manual"> - <summary>{copy.modelManual}</summary> - <label for={`${id}-model-id`}>{copy.modelManualLabel}</label> - <input - id={`${id}-model-id`} - type="text" - autocomplete="off" - spellcheck="false" - placeholder={DEMO_CUSTOM_MODEL_HINT} - data-model-input - /> - </details> - </section> - - <section class="activation-panel activation-progress" data-panel="verifying" tabindex="-1" hidden> - <div class="activation-scan is-verify" aria-hidden="true"><span></span></div> - <div> - <h3>{copy.verifyingTitle}</h3> - <p>{copy.verifyingBody}</p> - <div class="activation-verifying-pair"><strong data-verifying-agent>—</strong><span>×</span><strong data-verifying-provider>—</strong></div> - </div> - </section> - - <section class="activation-panel activation-result" data-panel="result" tabindex="-1" hidden> - <div class="activation-result-icon" aria-hidden="true" data-result-icon><svg viewBox="0 0 32 32"><path data-result-path d="M7 17l6 6L26 9"></path></svg></div> - <div> - <span class="activation-state-label">{copy.resultLabel}</span> - <h3 data-result-title>—</h3> - <p data-result-body>—</p> - <div class="activation-result-meta"><strong data-result-agent>—</strong><span>→</span><strong data-result-provider>—</strong></div> - <div class="activation-result-actions"> - <a class="button button-primary" href={href("downloads/")} data-ready-action hidden>{copy.download}</a> - <a class="button button-secondary" href={href("explore/")} data-explore-action>{copy.explore}</a> - <a class="button button-text" href={href("security/")} data-security-action hidden> - {copy.security} - {switchesLanguage(locale, "security/") && <span class="lang-hint">{copy.chineseOnly}</span>} - </a> - </div> - </div> - </section> - - <div class="activation-log-wrap"> - <span>{locale === "en" ? "EVENT LOG" : "事件日志"}</span> - <ol class="activation-log" data-log aria-live="polite" aria-relevant="additions" tabindex="0" aria-label={locale === "en" ? "Activation demo event log" : "激活演示事件日志"}> - <li>{locale === "en" ? "Demo idle. No device access requested." : "演示待机,未请求设备访问。"}</li> - </ol> - </div> - </div> - - {/* The real window keeps its primary action in a fixed footer bar rather - than inside the scrolling body, so the next step is always in the same - place. The note on the left is where the app puts its own context. */} - <footer class="activation-footer"> - <p class="activation-footer-note" data-footer-note>{copy.footerIdle}</p> - <div class="activation-footer-actions"> - <button class="activation-reset" type="button" data-reset hidden>{copy.reset}</button> - <button class="button button-small button-primary" type="button" data-verify hidden disabled>{copy.verify}</button> - <button class="button button-small button-primary" type="button" data-confirm hidden disabled>{copy.confirm}</button> - </div> - </footer> - </div> - - <footer class="activation-facts"> - {copy.facts.map((fact) => <span><i aria-hidden="true"></i>{fact}</span>)} - </footer> - </div> - </activation-console> -</div> - -<script> - import { catalog } from "../lib/catalog"; - import { - CUSTOM_PROVIDER_ID, - activationReducer, - initialActivationState, - validateDemoBaseUrl, - type ActivationState, - type ConfigMode, - } from "../lib/activation"; - import { demoModelsFor } from "../lib/demo-environment"; - import { compatibilityFor, recommendedCombination } from "../lib/explorer"; - - - const messages = { - "zh-CN": { - status: { - idle: "待机", - scanning: "扫描示例", - agent: "选择 Agent", - mode: "配置方式", - provider: "选择 Provider", - model: "选择模型", - verifying: "验证示例", - ready: "Ready", - "guide-only": "官方引导", - "preview-gate": "预览门禁", - unsupported: "不兼容", - error: "演示错误", - }, - log: { - scanning: "开始扫描示例环境;未访问本机。", - scanned: "示例扫描完成,可以选择 Agent。", - agent: (name: string) => `已选择 ${name}。`, - guide: (name: string) => `${name} 需要使用官方设置流程。`, - modeProvider: "将由 OneAgent 配置模型服务。", - modeExisting: "沿用已有账号或配置;Provider 与模型步骤已跳过。", - provider: (name: string, verdict: string) => `已选择 ${name}:${verdict}。`, - custom: "已选择自定义端点;地址通过与应用一致的校验后才能验证。", - customRejected: (reason: string) => `端点未通过校验:${reason}`, - customAccepted: (url: string) => `端点校验通过:${url}`, - verifying: "开始验证示例连接;未提交任何凭证。", - verified: "示例协议验证完成,可以选择模型。", - models: (count: number) => `端点返回 ${count} 个示例模型。`, - model: (id: string) => `已选择模型 ${id}。`, - ready: "示例环境可进入 Ready。", - preview: "连接路径可演示,但公开状态仍要求发布候选验证。", - unsupported: "所选 Provider 不支持该 Agent 所需协议。", - error: "演示状态无效,请重置后重试。", - }, - compatibility: { - verified: "注册表已验证", - supported: "实现支持", - "preview-gate": "需要发布候选验证", - unsupported: "协议不兼容", - }, - result: { - ready: ["示例环境已 Ready", "所选组合可按当前实现路径完成配置与协议验证。真实激活将在本地 OneAgent 中完成。"], - existing: ["保留现有账号", "OneAgent 只把这个 Agent 标记为已就绪,不改动它现有的登录状态,也不写入新的凭证。"], - guide: ["转入官方设置", "这个 Agent 不由 OneAgent 写入私有配置。请按官方方式安装或登录,再回到 OneAgent 查看环境。"], - preview: ["仍需通过发布门禁", "示例连接可以继续,但该协议组合仍标记为需要发布候选验证,因此不会被宣称为 Ready。"], - unsupported: ["协议组合不兼容", "请返回并选择支持该 Agent 协议的 Provider。OneAgent 不会静默降级到其他协议。"], - error: ["演示无法继续", "状态不完整或目录发生变化。请重置演示后重新选择。"], - }, - protocol: { openai: "Chat Completions", anthropic: "Anthropic Messages", responses: "OpenAI Responses" }, - url: { - required: "请填写 Base URL。", - scheme: "需要以 http:// 或 https:// 开头。", - credentials: "请从地址中去掉用户名或密码。", - "control-characters": "请去掉地址中的控制字符。", - ok: "端点可用", - }, - skipped: "已跳过", - /* The app puts step context in the footer bar next to the action. */ - footer: { - idle: "演示不会访问这台设备", - scanning: "正在读取示例环境", - agent: "从上面选一个 Agent", - mode: "选择配置方式", - provider: "选择 Provider 或填入你自己的端点", - model: "选择一个模型后确认", - verifying: "正在验证,未提交凭证", - ready: "示例流程已完成", - "guide-only": "这个 Agent 走官方流程", - "preview-gate": "组合可演示,但未通过发布门禁", - unsupported: "请换一个支持该协议的 Provider", - error: "请重置演示后重试", - }, - }, - en: { - status: { - idle: "Idle", - scanning: "Scanning demo", - agent: "Choose agent", - mode: "Setup", - provider: "Choose provider", - model: "Choose model", - verifying: "Verifying demo", - ready: "Ready", - "guide-only": "Official guide", - "preview-gate": "Preview gate", - unsupported: "Unsupported", - error: "Demo error", - }, - log: { - scanning: "Started scanning the example environment; no device access requested.", - scanned: "Example scan complete. Choose an agent.", - agent: (name: string) => `Selected ${name}.`, - guide: (name: string) => `${name} uses its official setup flow.`, - modeProvider: "OneAgent will configure a model service.", - modeExisting: "Keeping the existing account or config; provider and model steps skipped.", - provider: (name: string, verdict: string) => `Selected ${name}: ${verdict}.`, - custom: "Selected a custom endpoint. The URL is validated the same way the app validates it.", - customRejected: (reason: string) => `Endpoint rejected: ${reason}`, - customAccepted: (url: string) => `Endpoint accepted: ${url}`, - verifying: "Started sample verification; no credential was submitted.", - verified: "Sample protocol verification completed. Choose a model.", - models: (count: number) => `The endpoint returned ${count} example models.`, - model: (id: string) => `Selected model ${id}.`, - ready: "The example environment may reach Ready.", - preview: "The path can be demonstrated, but the public status still requires release-candidate verification.", - unsupported: "The selected provider does not support the protocol this agent requires.", - error: "The demo state is invalid. Reset and try again.", - }, - compatibility: { - verified: "Registry verified", - supported: "Implementation supported", - "preview-gate": "Release candidate required", - unsupported: "Protocol incompatible", - }, - result: { - ready: ["Example environment is Ready", "This combination can follow the current configuration and protocol-verification path. Real activation happens in the local OneAgent app."], - existing: ["Existing account kept", "OneAgent marks this agent ready without touching its current sign-in or writing a new credential."], - guide: ["Continue with official setup", "OneAgent does not write private configuration for this agent. Install or sign in through the official flow, then return to review the environment."], - preview: ["A release gate remains", "The sample path can continue, but this protocol combination still requires release-candidate verification and is not presented as Ready."], - unsupported: ["Protocol combination unsupported", "Go back and choose a provider that supports the agent protocol. OneAgent never silently falls back to another protocol."], - error: ["The demo cannot continue", "The selection is incomplete or the catalog changed. Reset the demo and choose again."], - }, - protocol: { openai: "Chat Completions", anthropic: "Anthropic Messages", responses: "OpenAI Responses" }, - url: { - required: "Enter a base URL.", - scheme: "Must start with http:// or https://", - credentials: "Remove the username or password from the URL.", - "control-characters": "Remove control characters from the URL.", - ok: "Endpoint accepted", - }, - skipped: "Skipped", - /* The app puts step context in the footer bar next to the action. */ - footer: { - idle: "The demo never touches this device", - scanning: "Reading the example environment", - agent: "Choose an agent above", - mode: "Choose how to configure it", - provider: "Choose a provider, or enter your own endpoint", - model: "Choose a model, then confirm", - verifying: "Verifying — no credential submitted", - ready: "The example flow is complete", - "guide-only": "This agent uses its official flow", - "preview-gate": "Demonstrable, but the release gate remains", - unsupported: "Choose a provider that supports this protocol", - error: "Reset the demo and try again", - }, - }, - } as const; - - /* The unattended walkthrough. Selection steps get a beat long enough to read - the panel that just appeared; the two probe steps reuse the timings the - manual path already uses, so an autoplayed run and a clicked one look the - same rather than one feeling rushed. */ - type AutoplayStep = { delay: number; run: (element: ActivationConsoleElement) => void }; - const AUTOPLAY_SCRIPT: AutoplayStep[] = [ - { delay: 420, run: (element) => element.start() }, - { delay: 1500, run: (element) => element.selectAgent("claude-code") }, - { delay: 900, run: (element) => element.selectMode("provider") }, - { delay: 900, run: (element) => element.selectProvider("ppio") }, - { delay: 900, run: (element) => element.verify() }, - { delay: 1100, run: (element) => element.selectModel(element.models[0] ?? "", false) }, - { delay: 900, run: (element) => element.confirm() }, - ]; - - class ActivationConsoleElement extends HTMLElement { - state: ActivationState = initialActivationState(); - timer: number | null = null; - abort = new AbortController(); - /* Autoplay owns the state machine only until the visitor touches it. Both - flags are one-way: `autoplaying` suppresses the focus and scroll side - effects that would hijack the page, and `autoplayDone` makes takeover and - completion permanent so nothing re-arms behind the visitor's back. */ - autoplaying = false; - autoplayDone = false; - autoplayTimer: number | null = null; - observer: IntersectionObserver | null = null; - - connectedCallback() { - if (this.dataset.initialized === "true") return; - this.dataset.initialized = "true"; - const signal = this.abort.signal; - this.querySelectorAll<HTMLButtonElement>("[data-agent-id]").forEach((button) => { - button.addEventListener("click", () => this.selectAgent(button.dataset.agentId || ""), { signal }); - }); - this.querySelectorAll<HTMLButtonElement>("[data-mode]").forEach((button) => { - button.addEventListener("click", () => this.selectMode(button.dataset.mode as ConfigMode), { signal }); - }); - this.querySelectorAll<HTMLButtonElement>("[data-provider-id]").forEach((button) => { - button.addEventListener("click", () => this.selectProvider(button.dataset.providerId || ""), { signal }); - }); - this.querySelector<HTMLInputElement>("[data-custom-input]")?.addEventListener("input", (event) => { - this.setCustomBaseUrl((event.currentTarget as HTMLInputElement).value); - }, { signal }); - this.querySelector<HTMLInputElement>("[data-model-input]")?.addEventListener("input", (event) => { - this.selectModel((event.currentTarget as HTMLInputElement).value.trim(), false); - }, { signal }); - this.querySelector<HTMLButtonElement>("[data-confirm]")?.addEventListener("click", () => this.confirm(), { signal }); - this.querySelector<HTMLButtonElement>("[data-verify]")?.addEventListener("click", () => this.verify(), { signal }); - this.querySelector<HTMLButtonElement>("[data-reset]")?.addEventListener("click", () => this.reset(), { signal }); - document.querySelectorAll<HTMLButtonElement>(`[data-activation-trigger="${this.id}"]`).forEach((button) => { - // The trigger sits in the hero, outside this element, so it misses the - // takeover listeners below. Pressing it is the clearest takeover there - // is: after autoplay has run it is the replay entry. - button.addEventListener("click", () => { - this.cancelAutoplay(); - this.start(); - }, { signal }); - }); - /* Any of the three means the visitor is driving. They are captured on the - way down so takeover is recorded before the click handler above runs its - own step, and they cover the keyboard and screen-reader paths that never - produce a pointer event. */ - for (const type of ["pointerdown", "keydown", "focusin"] as const) { - this.addEventListener(type, () => this.cancelAutoplay(), { signal, capture: true }); - } - this.render(); - this.armAutoplay(); - } - - disconnectedCallback() { - if (this.timer !== null) window.clearTimeout(this.timer); - if (this.autoplayTimer !== null) window.clearTimeout(this.autoplayTimer); - this.observer?.disconnect(); - this.abort.abort(); - } - - /* Autoplay waits for the console to be half on screen: running it on load - would finish before a visitor scrolling down ever saw it. Reduced motion - opts out entirely — advancing the interface on its own is motion, and the - manual trigger still works. */ - armAutoplay() { - if (this.reducedMotion || this.dataset.autoplay === "off") return; - if (typeof IntersectionObserver !== "function") return; - this.observer = new IntersectionObserver((entries) => { - for (const entry of entries) { - if (!entry.isIntersecting) continue; - this.observer?.disconnect(); - this.observer = null; - this.startAutoplay(); - } - }, { threshold: 0.5 }); - this.observer.observe(this); - } - - startAutoplay() { - if (this.autoplayDone || this.state.phase !== "idle") return; - this.autoplaying = true; - this.dataset.autoplaying = "true"; - this.queueAutoplay(0); - } - - queueAutoplay(index: number) { - const step = AUTOPLAY_SCRIPT[index]; - if (!step) return this.finishAutoplay(); - this.autoplayTimer = window.setTimeout(() => { - this.autoplayTimer = null; - if (!this.autoplaying) return; - step.run(this); - // A step that lands somewhere the script did not plan for — a catalog - // change turning the combination into a gate — stops here rather than - // dispatching actions the reducer would reject. - if (!this.autoplaying) return; - this.queueAutoplay(index + 1); - }, step.delay); - } - - /** Autoplay reached the end on its own: stop, and never re-arm. */ - finishAutoplay() { - this.autoplaying = false; - this.autoplayDone = true; - delete this.dataset.autoplaying; - } - - /** The visitor took over. Whatever step is showing is where it stays. */ - cancelAutoplay() { - this.observer?.disconnect(); - this.observer = null; - if (this.autoplayTimer !== null) window.clearTimeout(this.autoplayTimer); - this.autoplayTimer = null; - this.autoplaying = false; - this.autoplayDone = true; - delete this.dataset.autoplaying; - } - - get locale() { - return this.dataset.locale === "en" ? "en" : "zh-CN"; - } - - get copy() { - return messages[this.locale]; - } - - get reducedMotion() { - return window.matchMedia("(prefers-reduced-motion: reduce)").matches; - } - - dispatch(action: Parameters<typeof activationReducer>[1]) { - this.state = activationReducer(this.state, action, catalog); - this.render(); - } - - start() { - if (this.state.phase !== "idle") this.reset(false); - this.dispatch({ type: "start" }); - this.replaceLog(this.copy.log.scanning); - // Autoplay only begins once the console is already half on screen, so - // scrolling it to centre would move the page out from under a reader who - // asked for nothing. - if (!this.autoplaying) this.scrollIntoView({ behavior: this.reducedMotion ? "auto" : "smooth", block: "center" }); - this.focusPanel("scanning"); - this.timer = window.setTimeout(() => { - this.dispatch({ type: "scan-complete" }); - this.appendLog(this.copy.log.scanned); - this.focusPanel("agent"); - }, this.reducedMotion ? 0 : 650); - } - - reset(focus = true) { - if (this.timer !== null) window.clearTimeout(this.timer); - this.timer = null; - this.dispatch({ type: "reset" }); - this.replaceLog(this.locale === "en" ? "Demo idle. No device access requested." : "演示待机,未请求设备访问。"); - if (focus) this.focusPanel("idle"); - } - - selectAgent(agentId: string) { - const agent = catalog.agents.find((candidate) => candidate.id === agentId); - if (!agent) return this.fail(); - this.dispatch({ type: "select-agent", agentId }); - this.appendLog(this.copy.log.agent(agent.name)); - if (this.state.phase === "guide-only") { - this.appendLog(this.copy.log.guide(agent.name)); - this.focusPanel("result"); - } else { - this.updateProviders(agentId); - this.focusPanel("mode"); - } - } - - selectMode(mode: ConfigMode) { - if (mode !== "provider" && mode !== "existing-account") return this.fail(); - this.dispatch({ type: "select-mode", mode }); - if (this.state.phase === "ready") { - this.appendLog(this.copy.log.modeExisting); - this.focusPanel("result"); - return; - } - this.appendLog(this.copy.log.modeProvider); - this.focusPanel("provider"); - } - - selectProvider(providerId: string) { - if (providerId === CUSTOM_PROVIDER_ID) { - this.dispatch({ type: "select-provider", providerId }); - this.appendLog(this.copy.log.custom); - this.querySelector<HTMLInputElement>("[data-custom-input]")?.focus(); - return; - } - const provider = catalog.providers.find((candidate) => candidate.id === providerId); - if (!provider) return this.fail(); - this.dispatch({ type: "select-provider", providerId }); - const verdict = this.state.compatibility ? this.copy.compatibility[this.state.compatibility] : this.copy.compatibility.unsupported; - this.appendLog(this.copy.log.provider(provider.name, verdict)); - if (this.state.phase === "unsupported") { - this.appendLog(this.copy.log.unsupported); - this.focusPanel("result"); - } - } - - setCustomBaseUrl(value: string) { - this.dispatch({ type: "set-custom-base-url", value }); - } - - selectModel(model: string, fromList = true) { - if (!model) return; - this.dispatch({ type: "select-model", model }); - if (this.state.model === model) this.appendLog(this.copy.log.model(model)); - if (fromList) this.querySelector<HTMLButtonElement>("[data-confirm]")?.focus(); - } - - confirm() { - this.dispatch({ type: "confirm" }); - if (this.state.phase !== "ready") return; - this.appendLog(this.copy.log.ready); - this.focusPanel("result"); - } - - verify() { - const before = this.state.phase; - this.dispatch({ type: "verify" }); - if (this.state.phase === "error") return this.fail(false); - // A rejected endpoint leaves the phase untouched rather than erroring, the - // same non-event as the product's disabled probe button. - if (this.state.phase === before) return; - this.appendLog(this.copy.log.verifying); - this.focusPanel("verifying"); - this.timer = window.setTimeout(() => { - this.dispatch({ type: "verify-complete" }); - if (this.state.phase === "model") { - this.appendLog(this.copy.log.verified); - this.renderModels(); - this.appendLog(this.copy.log.models(this.models.length)); - this.focusPanel("model"); - return; - } - if (this.state.phase === "preview-gate") this.appendLog(this.copy.log.preview); - this.focusPanel("result"); - }, this.reducedMotion ? 0 : 700); - } - - get models() { - return this.state.providerId === CUSTOM_PROVIDER_ID ? [] : demoModelsFor(this.state.providerId); - } - - footerNoteFor(phase: ActivationState["phase"]) { - return this.copy.footer[phase]; - } - - customHost() { - const check = validateDemoBaseUrl(this.state.customBaseUrl); - if (!check.ok) return ""; - try { - return new URL(check.value).host; - } catch { - return ""; - } - } - - renderModels() { - const grid = this.querySelector<HTMLElement>("[data-model-grid]"); - if (!grid) return; - grid.replaceChildren(); - for (const model of this.models) { - const button = document.createElement("button"); - button.type = "button"; - button.className = "activation-model"; - button.dataset.model = model; - button.setAttribute("aria-pressed", "false"); - button.textContent = model; - button.addEventListener("click", () => this.selectModel(model), { signal: this.abort.signal }); - grid.append(button); - } - // A custom endpoint publishes no list, so the manual entry is the path - // rather than a fallback — open it instead of showing an empty group. - const manual = this.querySelector<HTMLDetailsElement>(".activation-manual"); - if (manual) manual.open = this.models.length === 0; - grid.hidden = this.models.length === 0; - } - - fail(dispatch = true) { - if (dispatch) this.dispatch({ type: "fail" }); - this.appendLog(this.copy.log.error); - this.focusPanel("result"); - } - - updateProviders(agentId: string) { - const agent = catalog.agents.find((candidate) => candidate.id === agentId); - if (!agent) return; - const recommendation = recommendedCombination(catalog); - this.querySelectorAll<HTMLButtonElement>("[data-provider-id]").forEach((button) => { - const provider = catalog.providers.find((candidate) => candidate.id === button.dataset.providerId); - if (!provider) return; - const compatibility = compatibilityFor(agent, provider); - button.dataset.compatibility = compatibility; - button.classList.toggle("is-recommended", recommendation?.agentId === agent.id && recommendation.providerId === provider.id); - const verdict = button.querySelector<HTMLElement>("[data-provider-verdict]"); - if (verdict) verdict.textContent = this.copy.compatibility[compatibility]; - button.setAttribute("aria-label", `${provider.name}: ${this.copy.compatibility[compatibility]}`); - }); - } - - replaceLog(message: string) { - const log = this.querySelector<HTMLOListElement>("[data-log]"); - if (!log) return; - log.replaceChildren(); - this.appendLog(message); - } - - appendLog(message: string) { - const log = this.querySelector<HTMLOListElement>("[data-log]"); - if (!log) return; - const item = document.createElement("li"); - item.textContent = message; - log.append(item); - log.scrollTop = log.scrollHeight; - } - - /* Moving focus is right when the visitor asked for the step — it carries a - keyboard user forward. During autoplay nobody asked, so taking focus would - pull it out of whatever they were reading or tabbing through. The panels - stay an aria-live region either way, so the steps are still announced. */ - focusPanel(name: string) { - if (this.autoplaying) return; - window.requestAnimationFrame(() => this.querySelector<HTMLElement>(`[data-panel="${name}"]`)?.focus({ preventScroll: true })); - } - - render() { - const phase = this.state.phase; - this.dataset.phase = phase; - const panel = ["idle", "scanning", "agent", "mode", "provider", "model", "verifying"].includes(phase) ? phase : "result"; - this.querySelectorAll<HTMLElement>("[data-panel]").forEach((element) => { - element.hidden = element.dataset.panel !== panel; - }); - - const status = this.querySelector<HTMLElement>("[data-status]"); - if (status) { - status.dataset.kind = phase; - const label = status.querySelector("b"); - if (label) label.textContent = this.copy.status[phase]; - } - const reset = this.querySelector<HTMLButtonElement>("[data-reset]"); - if (reset) reset.hidden = phase === "idle"; - - /* Steps mirror the product's stepper: Agent, Setup, Provider, Model, Confirm. - Choosing an existing account skips 3 and 4, so those are marked skipped - rather than done — the product labels them the same way. */ - const stepOf: Partial<Record<typeof phase, number>> = { - idle: 0, - scanning: 1, - agent: 1, - "guide-only": 1, - mode: 2, - provider: 3, - verifying: 3, - unsupported: 3, - "preview-gate": 3, - model: 4, - ready: 5, - }; - const current = stepOf[phase] ?? 5; - const skipped = this.state.configMode === "existing-account"; - this.querySelectorAll<HTMLElement>("[data-step]").forEach((step) => { - const index = Number(step.dataset.step || 0); - const isSkipped = skipped && (index === 3 || index === 4); - step.classList.toggle("is-current", index === current && !isSkipped); - step.classList.toggle("is-done", !isSkipped && (current > index || phase === "ready")); - step.classList.toggle("is-skipped", isSkipped); - if (index === current && !isSkipped) step.setAttribute("aria-current", "step"); - else step.removeAttribute("aria-current"); - }); - - this.querySelectorAll<HTMLButtonElement>("[data-agent-id]").forEach((button) => { - button.setAttribute("aria-pressed", String(button.dataset.agentId === this.state.agentId)); - }); - this.querySelectorAll<HTMLButtonElement>("[data-provider-id]").forEach((button) => { - button.setAttribute("aria-pressed", String(button.dataset.providerId === this.state.providerId)); - }); - - const agent = catalog.agents.find((candidate) => candidate.id === this.state.agentId) ?? null; - const provider = catalog.providers.find((candidate) => candidate.id === this.state.providerId) ?? null; - this.querySelectorAll<HTMLElement>("[data-selected-agent], [data-verifying-agent], [data-result-agent]").forEach((element) => { - element.textContent = agent?.name ?? "—"; - }); - /* A custom endpoint has no catalog name, so it shows the host the visitor - typed — the specific thing they chose. An existing account has no - provider at all, which is the point, so it says so. */ - const providerLabel = provider?.name - ?? (this.state.providerId === CUSTOM_PROVIDER_ID - ? this.customHost() || this.copy.status.provider - : this.state.configMode === "existing-account" - ? this.copy.skipped - : "—"); - this.querySelectorAll<HTMLElement>("[data-verifying-provider], [data-result-provider]").forEach((element) => { - element.textContent = providerLabel; - }); - const selectedProtocol = this.querySelector<HTMLElement>("[data-selected-protocol]"); - if (selectedProtocol) selectedProtocol.textContent = agent?.protocol ? this.copy.protocol[agent.protocol] : "—"; - - this.querySelectorAll<HTMLButtonElement>("[data-mode]").forEach((button) => { - button.setAttribute("aria-pressed", String(button.dataset.mode === this.state.configMode)); - }); - this.querySelectorAll<HTMLButtonElement>("[data-model]").forEach((button) => { - button.setAttribute("aria-pressed", String(button.dataset.model === this.state.model)); - }); - - const isCustom = this.state.providerId === CUSTOM_PROVIDER_ID; - const customField = this.querySelector<HTMLElement>("[data-custom-field]"); - if (customField) customField.hidden = !isCustom; - const check = isCustom ? validateDemoBaseUrl(this.state.customBaseUrl) : null; - const customHint = this.querySelector<HTMLElement>("[data-custom-hint]"); - const customInput = this.querySelector<HTMLInputElement>("[data-custom-input]"); - if (customHint && customInput && check) { - // An empty field is the resting state, not an error to shout about. - const untouched = this.state.customBaseUrl === ""; - customHint.textContent = untouched - ? customHint.dataset.idle || customHint.textContent - : check.ok - ? this.copy.url.ok - : this.copy.url[check.reason]; - customHint.dataset.kind = untouched ? "idle" : check.ok ? "ok" : "error"; - customInput.setAttribute("aria-invalid", String(!untouched && !check.ok)); - } - - /* Both primary actions live in the footer bar, so each is shown only on the - step it belongs to rather than moved between panels. */ - const verify = this.querySelector<HTMLButtonElement>("[data-verify]"); - if (verify) { - verify.hidden = phase !== "provider"; - verify.disabled = - !this.state.providerId || - this.state.compatibility === "unsupported" || - (isCustom && !(check && check.ok)); - } - const confirm = this.querySelector<HTMLButtonElement>("[data-confirm]"); - if (confirm) { - confirm.hidden = phase !== "model"; - confirm.disabled = !this.state.model; - } - const footerNote = this.querySelector<HTMLElement>("[data-footer-note]"); - if (footerNote) footerNote.textContent = this.footerNoteFor(phase); - - if (panel === "result") this.renderResult(phase); - } - - renderResult(phase: ActivationState["phase"]) { - const title = this.querySelector<HTMLElement>("[data-result-title]"); - const body = this.querySelector<HTMLElement>("[data-result-body]"); - const icon = this.querySelector<HTMLElement>("[data-result-icon]"); - const readyAction = this.querySelector<HTMLElement>("[data-ready-action]"); - const securityAction = this.querySelector<HTMLElement>("[data-security-action]"); - if (!title || !body || !icon || !readyAction || !securityAction) return; - - const result = phase === "ready" - ? this.state.configMode === "existing-account" - ? this.copy.result.existing - : this.copy.result.ready - : phase === "guide-only" - ? this.copy.result.guide - : phase === "preview-gate" - ? this.copy.result.preview - : phase === "unsupported" - ? this.copy.result.unsupported - : this.copy.result.error; - title.textContent = result[0]; - body.textContent = result[1]; - const path = icon.querySelector<SVGPathElement>("[data-result-path]"); - if (path) { - path.setAttribute( - "d", - phase === "ready" - ? "M7 17l6 6L26 9" - : phase === "guide-only" - ? "M8 24L24 8M13 8h11v11" - : phase === "preview-gate" - ? "M16 7v12M16 24v1" - : "M9 9l14 14M23 9L9 23", - ); - } - icon.dataset.kind = phase; - readyAction.hidden = phase !== "ready"; - securityAction.hidden = phase !== "preview-gate"; - } - } - - if (!customElements.get("activation-console")) { - customElements.define("activation-console", ActivationConsoleElement); - } -</script> - -<style> - activation-console { display: block; } - .activation-shell { position: relative; z-index: 1; } - /* Geometry and colour measured off the running app (frontend/src/styles): - 12px window radius, 52px title bar, 232px sidebar, 68px footer, and the - --shadow-window value verbatim. The app is light-only, so these use this - page's tokens rather than its hex values — that is the one difference. */ - .activation-window { - overflow: hidden; - border: 1px solid var(--line-strong); - border-radius: var(--radius-window); - background: var(--surface); - color: var(--ink); - box-shadow: 0 22px 55px rgba(42, 42, 46, .16), 0 2px 8px rgba(42, 42, 46, .08); - } - .activation-topbar { - min-height: 52px; - padding: 0 18px; - display: flex; - align-items: center; - justify-content: space-between; - gap: 20px; - border-bottom: 1px solid var(--line); - background: var(--surface-muted); - } - .activation-topbar > div { display: grid; gap: 2px; } - .activation-topbar strong { font-size: 14px; } - .activation-kicker, .activation-state-label { - color: var(--ink-faint); - font: 700 10px/1.2 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - letter-spacing: .12em; - text-transform: uppercase; - } - .activation-sample, .activation-status { - display: inline-flex; - align-items: center; - gap: 8px; - color: var(--ink-soft); - font-size: 12px; - font-weight: 650; - } - .activation-sample > span, .activation-status > span { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--orange); - box-shadow: 0 0 0 4px var(--orange-soft); - } - /* Two columns, two rows: the sidebar and body share the first row, and the - footer spans both so the primary action stays put while the body scrolls — - the same shape as the app's 232px/auto + content/68px grid. */ - .activation-body { - min-height: 560px; - display: grid; - grid-template-columns: 232px minmax(0, 1fr); - grid-template-rows: minmax(0, 1fr) auto; - } - .activation-rail { - padding: 22px 12px 16px; - display: flex; - flex-direction: column; - color: var(--ink); - background: var(--surface-muted); - border-right: 1px solid var(--line); - } - /* Static labels, not controls — the demo has nowhere to navigate to. */ - .activation-nav { display: grid; gap: 4px; } - .activation-nav-item { - min-height: 38px; - padding: 0 10px; - display: flex; - align-items: center; - border-radius: var(--radius-control); - color: var(--ink-soft); - font-size: 13px; - } - .activation-nav-item.is-active { background: var(--blue-deep); color: var(--on-accent); font-weight: 600; } - .activation-rail-note { margin-top: auto; padding: 12px 10px 0; color: var(--ink-faint); font-size: 11px; } - .activation-footer { - min-height: 68px; - padding: 0 42px; - display: flex; - align-items: center; - justify-content: space-between; - gap: 20px; - grid-column: 1 / -1; - border-top: 1px solid var(--line); - background: var(--surface-muted); - } - .activation-footer-note { color: var(--ink-soft); font-size: 13px; } - .activation-footer-actions { display: flex; align-items: center; gap: 12px; } - .activation-rail-brand { padding: 0 10px 18px; display: flex; align-items: center; gap: 11px; } - .activation-rail-brand > div { display: grid; gap: 2px; } - .activation-rail-brand strong { font-size: 13px; } - /* Scoped to the subtitle rather than every span, so it cannot reach the - monogram — that one carries white on the accent fill and a broader - selector here would win on specificity and make it unreadable. */ - .activation-rail-brand > div span { color: var(--ink-faint); font-size: 11px; } - .activation-monogram { - width: 26px; - height: 26px; - display: grid; - place-items: center; - border-radius: 7px; - color: var(--on-accent); - background: var(--blue-deep); - font: 750 10px/1 ui-monospace, SFMono-Regular, Menlo, monospace; - letter-spacing: .04em; - } - /* The app's stepper: a horizontal row in the page header, each step a 27px - numbered circle above an 11px label. Nothing is a card and nothing is dark. */ - .activation-steps { - margin: 22px 0 0; - padding: 0; - display: grid; - grid-auto-flow: column; - grid-auto-columns: minmax(0, 124px); - justify-content: start; - list-style: none; - } - .activation-steps li { display: grid; gap: 5px; justify-items: start; color: var(--ink-faint); font-size: 11px; } - .activation-step-marker { - width: 27px; - height: 27px; - display: grid; - place-items: center; - border: 1px solid var(--line-strong); - border-radius: 50%; - color: var(--ink-soft); - background: var(--surface); - font-size: 13px; - font-weight: 600; - } - /* The app keeps every label at 400 and signals the current step through the - marker fill alone, so the row stays even rather than jumping as you advance. */ - .activation-steps li strong { font-size: 11px; font-weight: 400; } - /* The marker fill is the only signal that the step changed, so it is worth - easing rather than cutting — it is also what the eye tracks when nobody - clicked. Matches the panel's 240ms so the two read as one movement. */ - @media not (prefers-reduced-motion: reduce) { - .activation-steps li { transition: color 240ms ease; } - .activation-step-marker { - transition: background-color 240ms ease, border-color 240ms ease, color 240ms ease; - } - } - .activation-steps li.is-current { color: var(--ink); } - .activation-steps li.is-current .activation-step-marker { - border-color: var(--blue-deep); - background: var(--blue-deep); - color: var(--on-accent); - } - .activation-steps li.is-done .activation-step-marker { - border-color: var(--green); - color: var(--green); - background: var(--green-soft); - } - .activation-reset { - padding: 8px 2px; - border: 0; - color: var(--ink-soft); - background: transparent; - font: 600 12px/1.2 inherit; - cursor: pointer; - } - .activation-reset:hover { color: var(--ink); } - /* 42px gutters and a header/body split, matching the app's page-scaffold. */ - .activation-main { min-width: 0; padding: 26px 42px 24px; display: flex; flex-direction: column; } - .activation-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; } - .activation-heading > div { display: grid; gap: 6px; } - .activation-heading h2 { margin: 0; font-size: 24px; line-height: 1.15; letter-spacing: -.01em; } - .activation-status { padding: 8px 10px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface-muted); } - .activation-status > span { width: 7px; height: 7px; background: var(--ink-faint); box-shadow: none; } - .activation-status[data-kind="ready"] > span { background: var(--green); box-shadow: 0 0 0 4px var(--green-soft); } - .activation-status[data-kind="preview-gate"] > span, - .activation-status[data-kind="guide-only"] > span { background: var(--orange); box-shadow: 0 0 0 4px var(--orange-soft); } - .activation-status[data-kind="unsupported"] > span, - .activation-status[data-kind="error"] > span { background: var(--red); box-shadow: 0 0 0 4px var(--red-soft); } - .activation-subtitle { margin: 10px 0 0; max-width: 720px; color: var(--ink-soft); font-size: 13px; line-height: 1.5; } - .activation-panel { - min-height: 272px; - margin-top: 24px; - padding: 24px; - border: 1px solid var(--line); - border-radius: var(--radius-panel); - background: var(--paper-strong); - outline: none; - } - .activation-panel:focus-visible { box-shadow: 0 0 0 3px var(--blue-soft); } - /* Steps replace each other by toggling [hidden], which is a hard cut. Fading - the arriving panel up from slightly below and slightly small reads as one - step advancing rather than two panels swapping — and the scale is 0.985, not - a slide: this is a product window, not a carousel. - - @starting-style is what makes the entry animate at all: the panel goes from - display:none straight to its final state, so without a declared starting - value there is nothing to transition from. - - Transform only, deliberately — no fade. Text mid-opacity fails the contrast - audit for as long as the transition runs, and this page already holds itself - to keeping copy readable while the hero entrance plays. Movement carries the - step change without ever rendering the panel's own text at low contrast. - - Only the entry is animated. Transitioning `display` on the way out would - keep the outgoing panel in flow for the whole 240ms, and since the panels - are siblings in normal flow the console would double in height every step. */ - @media not (prefers-reduced-motion: reduce) { - .activation-panel:not([hidden]) { - transition: transform 240ms ease; - transform: none; - } - } - @starting-style { - @media not (prefers-reduced-motion: reduce) { - .activation-panel:not([hidden]) { - transform: translateY(8px) scale(0.985); - } - } - } - .activation-panel[data-panel="idle"], .activation-progress, .activation-result { - display: grid; - grid-template-columns: 92px minmax(0, 1fr); - align-items: center; - gap: 28px; - } - .activation-panel h3 { margin: 0; font-size: 22px; line-height: 1.18; } - .activation-panel p { margin: 9px 0 0; max-width: 640px; color: var(--ink-soft); font-size: 14px; line-height: 1.55; } - .activation-empty-mark { width: 84px; height: 84px; position: relative; display: grid; place-items: center; } - .activation-empty-mark::before, .activation-empty-mark::after, .activation-empty-mark span { - content: ""; - position: absolute; - border-radius: 50%; - border: 1px solid var(--line-strong); - } - .activation-empty-mark::before { inset: 0; } - .activation-empty-mark::after { inset: 16px; border-color: var(--blue); } - .activation-empty-mark span:nth-child(1) { width: 8px; height: 8px; border: 0; background: var(--blue); } - .activation-empty-mark span:nth-child(2) { inset: 7px; border-style: dashed; } - .activation-empty-mark span:nth-child(3) { width: 42px; height: 1px; border: 0; border-radius: 0; background: var(--line-strong); transform: rotate(-35deg); } - .activation-scan { width: 84px; height: 84px; position: relative; border: 1px solid var(--line-strong); border-radius: 50%; } - .activation-scan::before, .activation-scan::after { content: ""; position: absolute; inset: 13px; border: 1px solid var(--line); border-radius: 50%; } - .activation-scan::after { inset: 29px; background: var(--blue); border: 0; box-shadow: 0 0 0 8px var(--blue-soft); } - .activation-scan span { position: absolute; inset: -1px; border-top: 2px solid var(--blue); border-right: 2px solid transparent; border-radius: 50%; animation: activation-spin 1s linear infinite; } - .activation-scan.is-verify::after { background: var(--green); box-shadow: 0 0 0 8px var(--green-soft); } - .activation-panel-copy { margin-bottom: 18px; } - .activation-choice-panel { padding: 20px; } - .activation-agent-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; } - .activation-agent { - min-width: 0; - min-height: 74px; - padding: 11px 12px; - position: relative; - display: grid; - grid-template-columns: 30px minmax(0, 1fr) auto; - align-items: center; - gap: 10px; - text-align: left; - color: var(--ink); - border: 1px solid var(--line); - border-radius: 12px; - background: var(--surface); - cursor: pointer; - } - .activation-agent:hover, .activation-agent[aria-pressed="true"] { border-color: var(--blue); background: var(--blue-soft); } - .activation-agent-copy { min-width: 0; display: grid; gap: 3px; } - .activation-agent-copy strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; } - .activation-agent-copy small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-faint); font-size: 10px; } - .activation-agent-state { padding: 5px 7px; border-radius: 999px; color: var(--ink-soft); background: var(--surface-muted); font-size: 9px; font-weight: 700; white-space: nowrap; } - .activation-agent-state.is-ready { color: var(--green); background: var(--green-soft); } - .activation-agent-state.is-attention, .activation-agent-state.is-guide-only { color: var(--orange); background: var(--orange-soft); } - .activation-recommended { position: absolute; top: -7px; right: 9px; padding: 2px 6px; border-radius: 999px; color: var(--on-accent); background: var(--blue-deep); font-size: 8px; font-weight: 750; } - .activation-selection-line, .activation-verifying-pair, .activation-result-meta { - margin: 14px 0; - display: flex; - align-items: center; - gap: 10px; - color: var(--ink-soft); - font: 650 12px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace; - } - .activation-selection-line span:first-child { color: var(--ink); } - .activation-provider-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; } - .activation-provider { - min-height: 66px; - padding: 11px 13px; - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - color: var(--ink); - text-align: left; - border: 1px solid var(--line); - border-radius: 12px; - background: var(--surface); - cursor: pointer; - } - .activation-provider > span:first-child { min-width: 0; display: grid; gap: 4px; } - .activation-provider strong { font-size: 13px; } - .activation-provider small { color: var(--ink-faint); font-size: 10px; } - .activation-provider:hover, .activation-provider[aria-pressed="true"] { border-color: var(--blue); background: var(--blue-soft); } - .activation-provider[data-compatibility="preview-gate"] small { color: var(--orange); } - .activation-provider[data-compatibility="unsupported"] small { color: var(--red); } - .activation-provider.is-recommended::after { content: "★"; color: var(--blue); font-size: 10px; } - .activation-provider-arrow { color: var(--ink-faint); } - .activation-mode-grid { display: grid; gap: 10px; } - .activation-mode { - padding: 13px 15px; - display: grid; - gap: 5px; - color: var(--ink); - text-align: left; - border: 1px solid var(--line); - border-radius: 12px; - background: var(--surface); - cursor: pointer; - } - .activation-mode strong { font-size: 13px; } - .activation-mode small { color: var(--ink-faint); font-size: 11px; line-height: 1.45; } - .activation-mode:hover, .activation-mode[aria-pressed="true"] { border-color: var(--blue); background: var(--blue-soft); } - - /* The custom endpoint is the one place the demo takes typed input, so it gets - a real label and a live verdict rather than a placeholder-only field. */ - .activation-custom { margin-top: 12px; display: grid; gap: 6px; } - .activation-custom label { font-size: 11px; font-weight: 600; color: var(--ink-soft); } - .activation-custom input { - padding: 9px 11px; - color: var(--ink); - border: 1px solid var(--line-strong); - border-radius: 10px; - background: var(--surface); - font: 500 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; - } - .activation-custom input:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; } - .activation-custom-hint { font-size: 11px; color: var(--ink-faint); } - .activation-custom-hint[data-kind="ok"] { color: var(--green); } - .activation-custom-hint[data-kind="error"] { color: var(--red); } - .activation-register { display: grid; gap: 2px; font-size: 11px; color: var(--blue); } - .activation-register small { color: var(--ink-faint); } - - .activation-model-grid { display: grid; gap: 8px; } - .activation-model { - padding: 10px 12px; - color: var(--ink); - text-align: left; - border: 1px solid var(--line); - border-radius: 10px; - background: var(--surface); - cursor: pointer; - font: 500 12px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace; - } - .activation-model:hover, .activation-model[aria-pressed="true"] { border-color: var(--blue); background: var(--blue-soft); } - .activation-manual { margin-top: 12px; display: grid; gap: 6px; font-size: 11px; } - .activation-manual summary { color: var(--ink-soft); cursor: pointer; } - .activation-manual label { font-weight: 600; color: var(--ink-soft); } - .activation-manual input { - padding: 8px 11px; - color: var(--ink); - border: 1px solid var(--line-strong); - border-radius: 10px; - background: var(--surface); - font: 500 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; - } - .activation-manual input:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; } - .activation-steps li.is-skipped { opacity: 0.55; } - .activation-steps li.is-skipped strong::after { content: " ·"; } - .activation-verify { margin-top: 14px; } - .activation-verifying-pair strong, .activation-result-meta strong { color: var(--ink); } - .activation-result-icon { - width: 78px; - height: 78px; - display: grid; - place-items: center; - border-radius: 22px; - color: var(--green); - background: var(--green-soft); - font-size: 34px; - font-weight: 650; - } - .activation-result-icon svg { width: 36px; height: 36px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; } - .activation-result-icon[data-kind="preview-gate"], .activation-result-icon[data-kind="guide-only"] { color: var(--orange); background: var(--orange-soft); } - .activation-result-icon[data-kind="unsupported"], .activation-result-icon[data-kind="error"] { color: var(--red); background: var(--red-soft); } - .activation-result-actions { margin-top: 18px; display: flex; flex-wrap: wrap; align-items: center; gap: 9px; } - .activation-log-wrap { min-height: 62px; margin-top: 16px; display: grid; grid-template-columns: 82px 1fr; gap: 12px; } - .activation-log-wrap > span { padding-top: 4px; color: var(--ink-faint); font: 700 9px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .1em; } - .activation-log { max-height: 58px; margin: 0; padding: 0; overflow: auto; list-style: none; color: var(--ink-soft); font: 11px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; } - .activation-log li::before { content: "> "; color: var(--blue); } - .activation-facts { min-height: 48px; padding: 10px 18px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; border-top: 1px solid var(--line); background: var(--paper-strong); } - .activation-facts span { display: flex; align-items: center; gap: 8px; color: var(--ink-soft); font-size: 11px; } - .activation-facts i { - width: 10px; - height: 6px; - display: inline-block; - border-left: 2px solid var(--green); - border-bottom: 2px solid var(--green); - transform: rotate(-45deg) translateY(-1px); - } - - @media not (prefers-reduced-motion: reduce) { - .activation-agent, .activation-provider { transition: transform var(--dur-micro) var(--ease-apple), border-color var(--dur-micro) var(--ease-apple), background-color var(--dur-micro) var(--ease-apple); } - .activation-agent:hover, .activation-provider:hover { transform: translateY(-1px); } - } - @media (prefers-reduced-motion: reduce) { - .activation-scan span { animation: none; border: 2px solid var(--blue); } - } - @keyframes activation-spin { to { transform: rotate(360deg); } } - - /* The sidebar is the first thing to go: it is workspace chrome, and the demo - never navigates. The stepper stays, since it is the actual progress. */ - @media (max-width: 820px) { - .activation-body { grid-template-columns: 1fr; grid-template-rows: minmax(0, 1fr) auto; } - .activation-rail { display: none; } - .activation-footer { padding: 0 20px; } - } - @media (max-width: 680px) { - .activation-shell { width: min(100% - 28px, 1180px); } - .activation-topbar { padding: 10px 12px; } - .activation-kicker { display: none; } - .activation-body { min-height: 0; } - .activation-main { padding: 20px 14px 16px; } - .activation-heading h2 { font-size: 20px; } - .activation-subtitle { font-size: 12px; } - /* Labels drop out and the circles compress to a row of five markers. */ - .activation-steps { margin-top: 16px; grid-auto-columns: minmax(0, 1fr); } - .activation-steps li strong { display: none; } - .activation-footer { min-height: 60px; padding: 0 14px; gap: 10px; } - .activation-footer-note { font-size: 11px; } - .activation-panel { min-height: 250px; margin-top: 18px; padding: 18px; } - .activation-panel[data-panel="idle"], .activation-progress, .activation-result { grid-template-columns: 1fr; gap: 16px; } - .activation-empty-mark, .activation-scan { width: 62px; height: 62px; } - .activation-panel h3 { font-size: 19px; } - .activation-agent-grid, .activation-provider-grid { grid-template-columns: 1fr; } - .activation-agent { min-height: 68px; } - .activation-facts { grid-template-columns: 1fr; gap: 8px; } - .activation-log-wrap { grid-template-columns: 1fr; gap: 4px; } - } -</style> diff --git a/site/src/components/AgentMark.astro b/site/src/components/AgentMark.astro deleted file mode 100644 index e59ab40..0000000 --- a/site/src/components/AgentMark.astro +++ /dev/null @@ -1,28 +0,0 @@ ---- -interface Props { - id: string; - name: string; - size?: number; -} -const { id, name, size = 24 } = Astro.props; -const extensions: Record<string, string> = { - codex: "svg", - "claude-code": "svg", - cursor: "svg", - opencode: "svg", - openclaw: "svg", - hermes: "png", - "kilo-cli": "svg", - aider: "png", -}; -/* These two marks ship as fill="currentColor". Loaded through <img> the keyword - has no page to inherit from and falls back to black, which disappears on a - dark ground — so they get inverted there while the marks carrying their own - brand colours are left alone. */ -const monochrome = new Set(["codex", "opencode"]); -const extension = extensions[id]; -const imageSource = extension ? `${import.meta.env.BASE_URL}images/agents/${id}.${extension}` : null; ---- -<span class="agent-mark-wrap" style={`--mark-size:${size}px`} aria-hidden="true"> - {imageSource ? <img src={imageSource} alt="" width={size} height={size} loading="lazy" data-monochrome={monochrome.has(id) ? "" : undefined} /> : <span class="agent-fallback">{name.slice(0, 1)}</span>} -</span> diff --git a/site/src/components/BrandMark.astro b/site/src/components/BrandMark.astro deleted file mode 100644 index 1beeba6..0000000 --- a/site/src/components/BrandMark.astro +++ /dev/null @@ -1,11 +0,0 @@ ---- -interface Props { - size?: number; -} -const { size = 28 } = Astro.props; ---- -<svg width={size} height={size} viewBox="0 0 32 32" fill="none" aria-hidden="true"> - <path d="M16 3.5 26.5 9.4v12L16 27.5 5.5 21.4v-12L16 3.5Z" stroke="currentColor" stroke-width="1.7"/> - <path d="m5.9 9.7 10.1 5.8 10.1-5.8M16 15.5v11.4" stroke="currentColor" stroke-width="1.7"/> - <path d="m10.7 6.6 10.4 6v6.8l-5.2 3-5.2-3v-6.8l10.4-6" stroke="currentColor" stroke-width="1.35" opacity=".62"/> -</svg> diff --git a/site/src/components/CompatibilityExplorer.astro b/site/src/components/CompatibilityExplorer.astro deleted file mode 100644 index 922307a..0000000 --- a/site/src/components/CompatibilityExplorer.astro +++ /dev/null @@ -1,609 +0,0 @@ ---- -import AgentMark from "./AgentMark.astro"; -import { catalog } from "../lib/catalog"; -import { localeFromPath, localePath } from "../i18n"; -import { useCatalogLabels } from "../i18n/catalog"; -import { protocolLabels } from "../lib/content"; -import { demoStateFor } from "../lib/demo-environment"; -import { compatibilityFor, type Compatibility } from "../lib/explorer"; - -const locale = localeFromPath(Astro.url.pathname); -const { agentDescriptions, groupLabels, agentFallbackDescription } = useCatalogLabels(locale); -const technicalHref = (agentId: string) => localePath("zh-CN", `agents/${agentId}/`); -const providerHref = (providerId: string) => localePath("zh-CN", `providers/${providerId}/`); -const copy = locale === "en" - ? { - filters: "Explorer filters", - platform: "Platform", - setup: "Install path", - config: "Configuration", - protocol: "Protocol", - provider: "Provider", - demo: "Demo state", - all: "All", - managedInstall: "Managed install", - officialGuide: "Official guide", - managedConfig: "Managed config", - officialConfig: "Official config", - ready: "Ready", - attention: "Needs attention", - notInstalled: "Not installed", - clear: "Clear filters", - results: "agents shown", - noResults: "No agent matches this combination.", - noResultsBody: "Clear a filter or choose a provider that implements the selected protocol.", - sample: "Demo state", - locked: "Version", - officialRelease: "Latest official", - open: "Open details for", - close: "Close details", - overview: "Environment example", - installState: "Example install state", - configState: "Example config state", - lockedVersion: "Install version", - configPath: "Configuration path", - command: "Launch command", - backup: "Example backup", - backupYes: "Available", - backupNo: "None yet", - installed: "Installed", - configured: "Configured", - needsSetup: "Needs setup", - providerCompatibility: "Provider compatibility", - capability: "Activation boundary", - managedBoundary: "OneAgent can manage this agent's configuration and backs up an existing file before a managed write.", - guideBoundary: "This agent stays in its official install, sign-in or extension flow. OneAgent does not write private configuration for it.", - technical: "Technical reference (Chinese)", - source: "Upstream source", - providerReference: "Provider reference (Chinese)", - illustrative: "Illustrative machine state; catalog capabilities are real.", - compatibility: { - verified: "Verified", - supported: "Implementation supported", - "preview-gate": "Release candidate required", - unsupported: "Unsupported", - } as Record<Compatibility, string>, - } - : { - filters: "配置筛选", - platform: "平台", - setup: "安装路径", - config: "配置方式", - protocol: "协议", - provider: "Provider", - demo: "示例状态", - all: "全部", - managedInstall: "托管安装", - officialGuide: "官方引导", - managedConfig: "托管配置", - officialConfig: "官方配置", - ready: "Ready", - attention: "需处理", - notInstalled: "未安装", - clear: "清除筛选", - results: "个 Agent", - noResults: "没有 Agent 匹配这组条件。", - noResultsBody: "清除一个筛选,或选择实现了目标协议的 Provider。", - sample: "示例状态", - locked: "版本", - officialRelease: "官方最新版本", - open: "打开详情:", - close: "关闭详情", - overview: "环境示例", - installState: "示例安装状态", - configState: "示例配置状态", - lockedVersion: "安装版本", - configPath: "配置位置", - command: "启动命令", - backup: "示例备份", - backupYes: "已有", - backupNo: "暂无", - installed: "已安装", - configured: "已配置", - needsSetup: "待配置", - providerCompatibility: "Provider 兼容性", - capability: "激活边界", - managedBoundary: "OneAgent 可以管理这个 Agent 的配置,并会在托管写入前备份已有文件。", - guideBoundary: "这个 Agent 保留官方安装、登录或扩展内流程;OneAgent 不写入它的私有配置。", - technical: "技术详情", - source: "上游源码", - providerReference: "Provider 详情", - illustrative: "机器状态属于示例;目录能力来自真实数据。", - compatibility: { - verified: "已验证", - supported: "实现支持", - "preview-gate": "需发布候选验证", - unsupported: "不支持", - } as Record<Compatibility, string>, - }; - -const demoLabel = (status: string) => status === "ready" - ? copy.ready - : status === "attention" - ? copy.attention - : status === "guide-only" - ? copy.officialGuide - : copy.notInstalled; ---- -<compatibility-explorer data-locale={locale}> - <section class="explorer-toolbar" aria-label={copy.filters}> - <label class="explorer-filter"> - <span>{copy.platform}</span> - <select data-filter="platform"> - <option value="">{copy.all}</option> - <option value="macos">macOS</option> - <option value="linux">Linux</option> - <option value="windows">Windows</option> - </select> - </label> - <label class="explorer-filter"> - <span>{copy.setup}</span> - <select data-filter="setup"> - <option value="">{copy.all}</option> - <option value="managed">{copy.managedInstall}</option> - <option value="guide">{copy.officialGuide}</option> - </select> - </label> - <label class="explorer-filter"> - <span>{copy.config}</span> - <select data-filter="config"> - <option value="">{copy.all}</option> - <option value="managed">{copy.managedConfig}</option> - <option value="official">{copy.officialConfig}</option> - </select> - </label> - <label class="explorer-filter"> - <span>{copy.protocol}</span> - <select data-filter="protocol"> - <option value="">{copy.all}</option> - <option value="openai">{protocolLabels.openai}</option> - <option value="anthropic">{protocolLabels.anthropic}</option> - <option value="responses">{protocolLabels.responses}</option> - </select> - </label> - <label class="explorer-filter"> - <span>{copy.provider}</span> - <select data-filter="provider"> - <option value="">{copy.all}</option> - {catalog.providers.map((provider) => <option value={provider.id}>{provider.name}</option>)} - </select> - </label> - <label class="explorer-filter"> - <span>{copy.demo}</span> - <select data-filter="demo"> - <option value="">{copy.all}</option> - <option value="ready">{copy.ready}</option> - <option value="attention">{copy.attention}</option> - <option value="guide-only">{copy.officialGuide}</option> - <option value="not-installed">{copy.notInstalled}</option> - </select> - </label> - <button class="button button-secondary explorer-clear" type="button" data-clear-filters>{copy.clear}</button> - </section> - - <div class="explorer-result-line" aria-live="polite"> - <strong data-result-count>{catalog.agents.length}</strong> - <span>{copy.results}</span> - <span aria-hidden="true">·</span> - <span>{copy.illustrative}</span> - </div> - - <div class="explorer-grid" data-explorer-grid> - {catalog.agents.map((agent) => { - const demo = demoStateFor(agent); - const providers = catalog.providers - .filter((provider) => compatibilityFor(agent, provider) !== "unsupported") - .map((provider) => provider.id); - return ( - <button - type="button" - class="explorer-card" - data-agent-card - data-agent-id={agent.id} - data-platforms={agent.platforms.join(" ")} - data-setup={agent.support.managedInstall ? "managed" : "guide"} - data-config={agent.support.managedConfig ? "managed" : "official"} - data-protocol={agent.protocol ?? ""} - data-providers={providers.join(" ")} - data-demo-status={demo.status} - aria-label={`${copy.open} ${agent.name}`} - > - <span class="explorer-card-head"> - <AgentMark id={agent.id} name={agent.name} size={34} /> - <span><strong>{agent.name}</strong><small>{groupLabels[agent.group] ?? agent.group}</small></span> - <span class:list={["explorer-demo-state", `is-${demo.status}`]}>{demoLabel(demo.status)}</span> - </span> - <span class="explorer-description">{agentDescriptions[agent.id] ?? agentFallbackDescription}</span> - <span class="explorer-chips"> - <span class:list={["chip", agent.support.managedInstall && "chip-green"]}>{agent.support.managedInstall ? copy.managedInstall : copy.officialGuide}</span> - <span class:list={["chip", agent.support.managedConfig && "chip-blue"]}>{agent.support.managedConfig ? copy.managedConfig : copy.officialConfig}</span> - {agent.protocol && <span class="chip">{protocolLabels[agent.protocol]}</span>} - </span> - <span class="explorer-card-foot"> - <span>{agent.lockedVersion ? `${copy.locked} ${agent.lockedVersion}` : copy.officialRelease}</span> - <span aria-hidden="true">↗</span> - </span> - </button> - ); - })} - </div> - - <div class="explorer-empty" data-explorer-empty hidden> - <strong>{copy.noResults}</strong> - <p>{copy.noResultsBody}</p> - </div> - - <div hidden data-detail-templates> - {catalog.agents.map((agent) => { - const demo = demoStateFor(agent); - const version = demo.version === "locked" - ? agent.lockedVersion - : demo.version === "behind" && agent.lockedVersion - ? `< ${agent.lockedVersion}` - : agent.lockedVersion; - return ( - <template data-agent-template={agent.id}> - <article class="drawer-content"> - <header class="drawer-head"> - <AgentMark id={agent.id} name={agent.name} size={44} /> - <div> - <span class="activation-state-label">{copy.sample}</span> - <h2 id="explorer-drawer-title">{agent.name}</h2> - <p>{agentDescriptions[agent.id] ?? agentFallbackDescription}</p> - </div> - <span class:list={["explorer-demo-state", `is-${demo.status}`]}>{demoLabel(demo.status)}</span> - </header> - - <section class="drawer-section"> - <h3>{copy.overview}</h3> - <dl class="drawer-facts"> - <div><dt>{copy.installState}</dt><dd>{demo.installed ? copy.installed : copy.notInstalled}</dd></div> - <div><dt>{copy.configState}</dt><dd>{demo.configured ? copy.configured : agent.support.officialInstallGuide ? copy.officialGuide : copy.needsSetup}</dd></div> - <div><dt>{copy.lockedVersion}</dt><dd>{version ?? copy.officialRelease}</dd></div> - <div><dt>{copy.backup}</dt><dd>{demo.hasBackup ? copy.backupYes : copy.backupNo}</dd></div> - <div><dt>{copy.configPath}</dt><dd><code>{agent.configPath ?? "—"}</code></dd></div> - <div><dt>{copy.command}</dt><dd><code>{agent.command ?? "—"}</code></dd></div> - </dl> - </section> - - <section class="drawer-section"> - <h3>{copy.providerCompatibility}</h3> - <div class="drawer-provider-list"> - {catalog.providers.map((provider) => { - const compatibility = compatibilityFor(agent, provider); - return ( - <a href={providerHref(provider.id)} aria-label={`${provider.name} — ${copy.providerReference}`}> - <span><strong>{provider.name}</strong><small>{agent.protocol ? protocolLabels[agent.protocol] : copy.officialGuide}{locale === "en" ? " · Chinese reference" : ""}</small></span> - <span class:list={["drawer-compatibility", `is-${compatibility}`]}>{copy.compatibility[compatibility]}</span> - </a> - ); - })} - </div> - </section> - - <section class="drawer-section drawer-boundary"> - <h3>{copy.capability}</h3> - <p>{agent.support.managedConfig ? copy.managedBoundary : copy.guideBoundary}</p> - </section> - - <footer class="drawer-actions"> - <a class="button button-primary" href={technicalHref(agent.id)}>{copy.technical}</a> - {agent.source && <a class="button button-secondary" href={agent.source} rel="noreferrer">{copy.source}</a>} - </footer> - </article> - </template> - ); - })} - </div> - - <dialog class="explorer-drawer" data-explorer-drawer aria-labelledby="explorer-drawer-title"> - <div class="drawer-shell"> - <button class="drawer-close" type="button" data-close-drawer aria-label={copy.close}>×</button> - <div data-drawer-slot></div> - </div> - </dialog> -</compatibility-explorer> - -<script> - import { catalog } from "../lib/catalog"; - import { - parseExplorerSearch, - serializeExplorerSearch, - type ExplorerSearchState, - } from "../lib/explorer"; - - - class CompatibilityExplorerElement extends HTMLElement { - abort = new AbortController(); - selectedAgent: string | null = null; - lastTrigger: HTMLElement | null = null; - suppressCloseUrl = false; - - connectedCallback() { - if (this.dataset.initialized === "true") return; - this.dataset.initialized = "true"; - const signal = this.abort.signal; - this.querySelectorAll<HTMLSelectElement>("[data-filter]").forEach((control) => { - control.addEventListener("change", () => this.filtersChanged(), { signal }); - }); - this.querySelector<HTMLButtonElement>("[data-clear-filters]")?.addEventListener("click", () => this.clearFilters(), { signal }); - this.querySelectorAll<HTMLButtonElement>("[data-agent-card]").forEach((card) => { - card.addEventListener("click", () => this.openAgent(card.dataset.agentId || "", card), { signal }); - }); - this.querySelector<HTMLButtonElement>("[data-close-drawer]")?.addEventListener("click", () => this.drawer?.close(), { signal }); - this.drawer?.addEventListener("close", () => this.drawerClosed(), { signal }); - window.addEventListener("popstate", () => this.applyUrl(), { signal }); - this.applyUrl(); - } - - disconnectedCallback() { - this.abort.abort(); - } - - get drawer() { - return this.querySelector<HTMLDialogElement>("[data-explorer-drawer]"); - } - - get locale() { - return this.dataset.locale === "en" ? "en" : "zh-CN"; - } - - control(name: string) { - return this.querySelector<HTMLSelectElement>(`[data-filter="${name}"]`); - } - - applyUrl() { - const state = parseExplorerSearch(window.location.search, catalog); - this.setControl("platform", state.platform ?? ""); - this.setControl("protocol", state.protocol ?? ""); - this.setControl("provider", state.provider ?? ""); - this.selectedAgent = state.agent; - this.filterCards(); - if (state.agent) this.openAgent(state.agent, null, false); - else if (this.drawer?.open) { - this.suppressCloseUrl = true; - this.drawer.close(); - } - } - - setControl(name: string, value: string) { - const control = this.control(name); - if (control) control.value = value; - } - - currentSearch(agent = this.selectedAgent): ExplorerSearchState { - return { - agent, - provider: this.control("provider")?.value || null, - platform: (this.control("platform")?.value || null) as ExplorerSearchState["platform"], - protocol: (this.control("protocol")?.value || null) as ExplorerSearchState["protocol"], - }; - } - - filtersChanged() { - const sanitized = parseExplorerSearch(`?${serializeExplorerSearch(this.currentSearch())}`, catalog); - this.selectedAgent = sanitized.agent; - this.setControl("platform", sanitized.platform ?? ""); - this.setControl("protocol", sanitized.protocol ?? ""); - this.setControl("provider", sanitized.provider ?? ""); - this.writeUrl(sanitized); - this.filterCards(); - } - - clearFilters() { - this.querySelectorAll<HTMLSelectElement>("[data-filter]").forEach((control) => { - control.value = ""; - }); - this.selectedAgent = null; - if (this.drawer?.open) this.drawer.close(); - this.writeUrl(this.currentSearch(null)); - this.filterCards(); - this.control("platform")?.focus(); - } - - filterCards() { - const platform = this.control("platform")?.value || ""; - const setup = this.control("setup")?.value || ""; - const config = this.control("config")?.value || ""; - const protocol = this.control("protocol")?.value || ""; - const provider = this.control("provider")?.value || ""; - const demo = this.control("demo")?.value || ""; - let visible = 0; - - this.querySelectorAll<HTMLButtonElement>("[data-agent-card]").forEach((card) => { - const matches = (!platform || (card.dataset.platforms || "").split(" ").includes(platform)) - && (!setup || card.dataset.setup === setup) - && (!config || card.dataset.config === config) - && (!protocol || card.dataset.protocol === protocol) - && (!provider || (card.dataset.providers || "").split(" ").includes(provider)) - && (!demo || card.dataset.demoStatus === demo); - card.hidden = !matches; - card.classList.toggle("is-selected", card.dataset.agentId === this.selectedAgent); - if (matches) visible += 1; - }); - - const count = this.querySelector<HTMLElement>("[data-result-count]"); - if (count) count.textContent = String(visible); - const empty = this.querySelector<HTMLElement>("[data-explorer-empty]"); - if (empty) empty.hidden = visible !== 0; - } - - openAgent(agentId: string, trigger: HTMLElement | null, updateUrl = true) { - const agent = catalog.agents.find((candidate) => candidate.id === agentId); - const template = this.querySelector<HTMLTemplateElement>(`template[data-agent-template="${agentId}"]`); - const slot = this.querySelector<HTMLElement>("[data-drawer-slot]"); - if (!agent || !template || !slot || !this.drawer) return; - - const sanitized = parseExplorerSearch(`?${serializeExplorerSearch(this.currentSearch(agentId))}`, catalog); - this.selectedAgent = agentId; - this.setControl("platform", sanitized.platform ?? ""); - this.setControl("protocol", sanitized.protocol ?? ""); - this.setControl("provider", sanitized.provider ?? ""); - slot.replaceChildren(template.content.cloneNode(true)); - this.lastTrigger = trigger; - this.filterCards(); - if (!this.drawer.open) this.drawer.showModal(); - this.querySelector<HTMLButtonElement>("[data-close-drawer]")?.focus(); - if (updateUrl) this.writeUrl({ ...sanitized, agent: agentId }); - } - - drawerClosed() { - if (this.suppressCloseUrl) { - this.suppressCloseUrl = false; - return; - } - this.selectedAgent = null; - this.filterCards(); - this.writeUrl(this.currentSearch(null)); - this.lastTrigger?.focus(); - this.lastTrigger = null; - } - - writeUrl(state: ExplorerSearchState) { - const query = serializeExplorerSearch(state); - const next = `${window.location.pathname}${query ? `?${query}` : ""}`; - window.history.replaceState({}, "", next); - } - } - - if (!customElements.get("compatibility-explorer")) { - customElements.define("compatibility-explorer", CompatibilityExplorerElement); - } -</script> - -<style> - compatibility-explorer { display: block; } - .explorer-toolbar { - padding: 18px; - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)) auto; - gap: 12px; - align-items: end; - border: 1px solid var(--line); - border-radius: var(--radius-panel); - background: var(--surface); - box-shadow: var(--shadow-lift); - } - .explorer-filter { min-width: 0; display: grid; gap: 7px; } - .explorer-filter > span { color: var(--ink-faint); font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; } - .explorer-filter select { - width: 100%; - min-height: 42px; - padding: 0 34px 0 11px; - color: var(--ink); - border: 1px solid var(--line-strong); - border-radius: var(--radius-control); - background: var(--paper-strong); - font: 600 13px/1 system-ui, sans-serif; - } - .explorer-clear { grid-column: 4; grid-row: 1 / span 2; align-self: stretch; } - .explorer-result-line { margin: 24px 0 14px; display: flex; flex-wrap: wrap; align-items: center; gap: 7px; color: var(--ink-faint); font-size: 13px; } - .explorer-result-line strong { color: var(--ink); font-size: 16px; } - .explorer-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; } - .explorer-card { - min-width: 0; - min-height: 254px; - padding: 18px; - display: flex; - flex-direction: column; - text-align: left; - color: var(--ink); - border: 1px solid var(--line); - border-radius: var(--radius-panel); - background: var(--surface); - cursor: pointer; - } - .explorer-card:hover, .explorer-card.is-selected { border-color: var(--blue); box-shadow: var(--shadow-lift); } - .explorer-card-head { display: grid; grid-template-columns: 38px minmax(0, 1fr) auto; align-items: center; gap: 11px; } - .explorer-card-head > span:nth-child(2) { min-width: 0; display: grid; gap: 3px; } - .explorer-card-head strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 16px; } - .explorer-card-head small { color: var(--ink-faint); font-size: 11px; } - .explorer-demo-state { padding: 5px 7px; border-radius: 999px; color: var(--ink-soft); background: var(--surface-muted); font-size: 9px; font-weight: 750; white-space: nowrap; } - .explorer-demo-state.is-ready { color: var(--green); background: var(--green-soft); } - .explorer-demo-state.is-attention, .explorer-demo-state.is-guide-only { color: var(--orange); background: var(--orange-soft); } - .explorer-description { margin-top: 18px; color: var(--ink-soft); font-size: 13px; line-height: 1.5; } - .explorer-chips { margin-top: 16px; display: flex; flex-wrap: wrap; gap: 6px; } - .explorer-card-foot { margin-top: auto; padding-top: 18px; display: flex; justify-content: space-between; gap: 12px; color: var(--ink-faint); font: 600 11px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace; } - .explorer-empty { padding: 64px 24px; text-align: center; border: 1px dashed var(--line-strong); border-radius: var(--radius-panel); } - .explorer-empty strong { font-size: 20px; } - .explorer-empty p { margin: 8px 0 0; color: var(--ink-soft); } - .explorer-drawer { - width: min(580px, 100%); - height: 100%; - max-width: none; - max-height: none; - margin: 0 0 0 auto; - padding: 0; - overflow: hidden; - color: var(--ink); - border: 0; - border-left: 1px solid var(--line-strong); - background: var(--surface); - box-shadow: -24px 0 70px rgba(0, 0, 0, .22); - } - .explorer-drawer::backdrop { background: rgba(8, 13, 17, .52); backdrop-filter: blur(3px); } - .drawer-shell { height: 100%; position: relative; overflow: auto; } - .drawer-close { - width: 38px; - height: 38px; - position: sticky; - top: 14px; - float: right; - margin: 14px 16px -52px 0; - z-index: 2; - border: 1px solid var(--line); - border-radius: 50%; - color: var(--ink); - background: color-mix(in srgb, var(--surface) 88%, transparent); - backdrop-filter: blur(12px); - font-size: 24px; - cursor: pointer; - } - .drawer-content { padding: 54px 34px 36px; } - .drawer-head { display: grid; grid-template-columns: 48px minmax(0, 1fr) auto; align-items: start; gap: 14px; } - .drawer-head h2 { margin: 5px 0 0; font-size: 30px; line-height: 1.1; } - .drawer-head p { margin: 10px 0 0; color: var(--ink-soft); font-size: 14px; line-height: 1.5; } - .drawer-section { margin-top: 34px; padding-top: 24px; border-top: 1px solid var(--line); } - .drawer-section h3 { margin: 0 0 15px; font-size: 17px; } - .drawer-facts { margin: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px; border: 1px solid var(--line); background: var(--line); } - .drawer-facts div { min-width: 0; padding: 13px; background: var(--paper-strong); } - .drawer-facts dt { color: var(--ink-faint); font-size: 10px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; } - .drawer-facts dd { margin: 6px 0 0; overflow-wrap: anywhere; font-size: 13px; } - .drawer-facts code { font-size: 11px; } - .drawer-provider-list { display: grid; border-top: 1px solid var(--line); } - .drawer-provider-list a { padding: 13px 2px; display: flex; align-items: center; justify-content: space-between; gap: 16px; border-bottom: 1px solid var(--line); } - .drawer-provider-list a > span:first-child { display: grid; gap: 3px; } - .drawer-provider-list strong { font-size: 13px; } - .drawer-provider-list small { color: var(--ink-faint); font-size: 10px; } - .drawer-compatibility { font-size: 10px; font-weight: 750; } - .drawer-compatibility.is-verified, .drawer-compatibility.is-supported { color: var(--green); } - .drawer-compatibility.is-preview-gate { color: var(--orange); } - .drawer-compatibility.is-unsupported { color: var(--red); } - .drawer-boundary { padding: 18px; border: 1px solid var(--line); border-radius: var(--radius-panel); background: var(--paper-strong); } - .drawer-boundary p { margin: 0; color: var(--ink-soft); font-size: 13px; line-height: 1.55; } - .drawer-actions { margin-top: 28px; display: flex; flex-wrap: wrap; gap: 9px; } - - @media not (prefers-reduced-motion: reduce) { - .explorer-card { transition: transform var(--dur-micro) var(--ease-apple), border-color var(--dur-micro) var(--ease-apple), box-shadow var(--dur-micro) var(--ease-apple); } - .explorer-card:hover { transform: translateY(-2px); } - .explorer-drawer[open] { animation: drawer-in 260ms var(--ease-enter) both; } - } - @keyframes drawer-in { from { transform: translateX(28px); } } - - @media (max-width: 980px) { - .explorer-toolbar { grid-template-columns: repeat(3, minmax(0, 1fr)); } - .explorer-clear { grid-column: auto; grid-row: auto; } - .explorer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } - } - @media (max-width: 680px) { - .explorer-toolbar { grid-template-columns: 1fr 1fr; padding: 14px; } - .explorer-clear { grid-column: 1 / -1; } - .explorer-grid { grid-template-columns: 1fr; } - .explorer-card { min-height: 228px; } - .explorer-drawer { width: 100%; border-left: 0; } - .drawer-content { padding: 56px 18px 28px; } - .drawer-head { grid-template-columns: 48px minmax(0, 1fr); } - .drawer-head > .explorer-demo-state { grid-column: 2; justify-self: start; } - .drawer-facts { grid-template-columns: 1fr; } - } -</style> diff --git a/site/src/components/DownloadSelector.astro b/site/src/components/DownloadSelector.astro deleted file mode 100644 index 52935bf..0000000 --- a/site/src/components/DownloadSelector.astro +++ /dev/null @@ -1,269 +0,0 @@ ---- -import { bestLocaleFor, localeFromPath, localePath } from "../i18n"; -import { formatBytes, formatDate } from "../lib/downloads"; -import { binaryArtifact, getPreviewChannel, primaryDownload } from "../lib/release-channel"; - -/* Everything below reads off a channel: the platform picker, the checksum, the - size. With no release published there is nothing to pick between, so the - component renders a single "not published yet" notice instead of four empty - panels that look like a broken page. */ -const channel = await getPreviewChannel(); -const defaultTarget = channel?.targets.find((target) => target.status === "available") ?? channel?.targets[0] ?? null; -const locale = localeFromPath(Astro.url.pathname); -const href = (path: string) => localePath(bestLocaleFor(locale, path), path); -const c = locale === "en" - ? { - legend: "Choose platform and architecture", - available: "Available", - pending: "Verifying", - detected: "Showing the platform that is currently downloadable; you can switch at any time.", - notStableTitle: "This is not stable", - notStableBody: "The current package is unsigned and unnotarised. OneAgent does not document ways around your operating system's security policy.", - verified: "Verified and downloadable", - version: "Version", - channelLabel: "Release channel", - size: "File size", - built: "Build date", - signing: "Signing status", - unsigned: "Unsigned, unnotarised", - verification: "Verification", - verificationValue: "Native build + cleanroom passed", - download: (platform: string) => `Download the ${platform} preview`, - quickstart: "Read the quickstart", - sameBuildTitle: "You get the verified official build", - sameBuildBody: "This site, GitHub Releases and any mirror must serve the identical SHA-256. Repackaging is not permitted.", - checksumTitle: "macOS verification command", - copy: "Copy", - copied: "Copied", - cleanroomNote: "The cleanroom evidence applies to this file's exact SHA-256; any change to the package requires re-verification.", - unavailableTitle: "This platform has no public release yet", - unavailableBody: "The build workflow is in place, but until the native build, cleanroom evidence and release metadata are all present there is no empty download button and no CI artifact described as generally available.", - progress: "See release progress", - channelValue: "Unsigned technical preview", - fallbackPlatform: "current platform", - detectExact: "Detected as {full}. You can still switch manually.", - detectArchOnly: "Detected as {name} {arch}, but there is no exact build for that architecture here — do not run a build for a mismatched architecture.", - detectPlatformOnly: "Detected as {name}, but the browser cannot reliably tell the chip architecture. Confirm the option before downloading.", - } - : { - legend: "选择平台与架构", - available: "可下载", - pending: "验证中", - detected: "已优先显示当前可下载的平台;你可以随时手动切换。", - notStableTitle: "这不是 Stable", - notStableBody: "当前包未签名、未公证。OneAgent 不提供绕过操作系统安全策略的说明。", - verified: "已验证可下载", - version: "版本", - channelLabel: "发行渠道", - size: "文件大小", - built: "构建日期", - signing: "签名状态", - unsigned: "未签名、未公证", - verification: "验证状态", - verificationValue: "原生构建 + cleanroom 通过", - download: (platform: string) => `下载 ${platform} 预览版`, - quickstart: "查看快速开始", - sameBuildTitle: "下载即得到被校验的官方同包产物", - sameBuildBody: "任何官网、GitHub Release 或镜像渠道都必须保持相同 SHA-256,禁止二次打包。", - checksumTitle: "macOS 校验命令", - copy: "复制", - copied: "已复制", - cleanroomNote: "cleanroom 证据只对应此文件的精确 SHA-256;包体变化后必须重新验证。", - unavailableTitle: "这个平台尚未公开发行", - unavailableBody: "构建工作流已经保留,但在原生构建、cleanroom 证据和发行元数据齐备前,不提供空下载按钮,也不把 CI 产物描述为正式可用。", - progress: "查看发行进度", - channelValue: "未签名技术预览版", - fallbackPlatform: "当前平台", - detectExact: "已识别为 {full};你仍可手动切换。", - detectArchOnly: "已识别为 {name} {arch},当前目录没有完全匹配的构建;请勿运行架构不匹配的包。", - detectPlatformOnly: "已识别为 {name},但浏览器无法可靠判断芯片架构;请确认选项后下载。", - }; ---- -{!channel || !defaultTarget ? ( - <div class="notice notice-warning"> - <span class="notice-mark">!</span> - <div> - <strong>{c.unavailableTitle}</strong> - <p>{c.unavailableBody}</p> - </div> - </div> -) : ( -<div class="download-layout" data-download-selector> - <aside class="platform-control"> - <fieldset class="platform-picker" data-platform-picker> - <legend>{c.legend}</legend> - {channel.targets.map((target) => ( - <label class="platform-option"> - <input - type="radio" - name="platform-target" - value={target.id} - checked={target.id === defaultTarget.id} - data-platform-radio - /> - <span class="platform-option-body"> - <span class="platform-option-name">{target.platformLabel}</span> - <span class="platform-option-arch">{target.archLabel}</span> - </span> - {target.status === "available" - ? <span class="chip chip-green">{c.available}</span> - : <span class="chip chip-orange">{c.pending}</span>} - </label> - ))} - </fieldset> - <p - class="detected-note" - data-detected-note - data-detect-exact={c.detectExact} - data-detect-arch={c.detectArchOnly} - data-detect-platform={c.detectPlatformOnly} - data-fallback={c.fallbackPlatform} - >{c.detected}</p> - <div class="notice notice-warning" style="margin-top:22px"> - <span class="notice-mark">!</span> - <div> - <strong>{c.notStableTitle}</strong> - <p>{c.notStableBody}</p> - </div> - </div> - </aside> - - <div> - {channel.targets.map((target) => { - const artifact = binaryArtifact(target); - const download = artifact ? primaryDownload(artifact) : null; - return ( - <section class:list={["release-panel", { "is-active": target.id === defaultTarget.id }]} data-release-panel={target.id} aria-hidden={target.id === defaultTarget.id ? "false" : "true"}> - <div class="release-head"> - <div> - <h2>{target.platformLabel}</h2> - <p>{target.archLabel}</p> - </div> - {target.status === "available" ? <span class="chip chip-green">{c.verified}</span> : <span class="chip chip-orange">{c.pending}</span>} - </div> - - {target.status === "available" && artifact && download ? ( - <> - <dl class="release-meta-grid"> - <div><dt>{c.version}</dt><dd>{channel.version}</dd></div> - <div><dt>{c.channelLabel}</dt><dd>{c.channelValue}</dd></div> - <div><dt>{c.size}</dt><dd>{formatBytes(artifact.bytes)}</dd></div> - <div><dt>{c.built}</dt><dd>{formatDate(target.built_at)}</dd></div> - <div><dt>{c.signing}</dt><dd>{c.unsigned}</dd></div> - <div><dt>{c.verification}</dt><dd>{c.verificationValue}</dd></div> - </dl> - <div class="download-action"> - <a class="button button-primary" href={download.url} download>{c.download(target.platformLabel)}</a> - <a class="button button-secondary" href={href("quickstart/")}>{c.quickstart}</a> - </div> - <div class="notice notice-info"> - <span class="notice-mark">i</span> - <div> - <strong>{c.sameBuildTitle}</strong> - <p>{c.sameBuildBody}</p> - </div> - </div> - <div class="hash-block"> - <p><strong>SHA-256</strong></p> - <div class="hash-row"> - {/* Both this and the command below scroll horizontally on narrow - screens, so they need to be reachable without a pointer - (WCAG 2.1.1). The label names which value has focus. */} - <code class="hash-value" tabindex="0" role="group" aria-label="SHA-256">{artifact.sha256}</code> - <button class="copy-button" type="button" data-copy={artifact.sha256} data-copy-label={c.copy} data-copied-label={c.copied}>{c.copy}</button> - </div> - </div> - <div class="hash-block"> - <p><strong>{c.checksumTitle}</strong></p> - <pre class="code-block" tabindex="0" role="group" aria-label={c.checksumTitle}><code>shasum -a 256 {artifact.file}</code></pre> - </div> - <p class="hero-note">{c.cleanroomNote}</p> - </> - ) : ( - <div class="unavailable-panel"> - <h3>{c.unavailableTitle}</h3> - <p>{c.unavailableBody}</p> - <a class="button button-secondary" href={href("changelog/")}>{c.progress}</a> - </div> - )} - </section> - ); - })} - </div> -</div> -)} - -<script> - import { detectTargetFromUserAgent } from "../lib/downloads"; - - const root = document.querySelector<HTMLElement>("[data-download-selector]"); - const radios = root ? Array.from(root.querySelectorAll<HTMLInputElement>("[data-platform-radio]")) : []; - const note = root?.querySelector<HTMLElement>("[data-detected-note]"); - const panels = root ? Array.from(root.querySelectorAll<HTMLElement>("[data-release-panel]")) : []; - - const show = (id: string) => { - panels.forEach((panel) => { - const active = panel.dataset.releasePanel === id; - panel.classList.toggle("is-active", active); - panel.setAttribute("aria-hidden", active ? "false" : "true"); - }); - }; - - // The detection notes are localised templates passed from the server through - // data attributes, so an English reader never sees the Chinese fallback that a - // hardcoded string would leave behind. {full}/{name}/{arch} are filled client- - // side because the platform name only exists in the rendered DOM. - const tplExact = note?.dataset.detectExact ?? ""; - const tplArch = note?.dataset.detectArch ?? ""; - const tplPlatform = note?.dataset.detectPlatform ?? ""; - const fallbackPlatform = note?.dataset.fallback ?? ""; - const fill = (template: string, vars: Record<string, string>) => - template.replace(/\{(\w+)\}/g, (_, key: string) => vars[key] ?? ""); - - const labelFor = (radio: HTMLInputElement) => { - const name = radio.parentElement?.querySelector(".platform-option-name")?.textContent?.trim() ?? fallbackPlatform; - const arch = radio.parentElement?.querySelector(".platform-option-arch")?.textContent?.trim() ?? ""; - return { name, full: arch ? `${name} · ${arch}` : name }; - }; - - if (radios.length) { - const detected = detectTargetFromUserAgent(navigator.userAgent); - if (detected) { - const exact = radios.find((radio) => radio.value === `${detected.platform}-${detected.arch}`); - const samePlatform = radios.find((radio) => radio.value.startsWith(`${detected.platform}-`)); - const match = exact ?? samePlatform; - if (match) { - match.checked = true; - show(match.value); - if (note) { - const { name, full } = labelFor(match); - if (exact) note.textContent = fill(tplExact, { full, name }); - else if (detected.arch) { - const architecture = detected.arch === "arm64" ? "ARM64" : "x64"; - note.textContent = fill(tplArch, { name, arch: architecture }); - } else { - note.textContent = fill(tplPlatform, { name }); - } - } - } - } - // Radio groups fire change on the newly selected input, and arrow-key - // navigation moves selection too, so one listener per input covers both - // pointer and keyboard without any roving-tabindex bookkeeping. - radios.forEach((radio) => { - radio.addEventListener("change", () => { - if (radio.checked) show(radio.value); - }); - }); - } - - root?.querySelectorAll<HTMLButtonElement>("[data-copy]").forEach((button) => { - button.addEventListener("click", async () => { - const value = button.dataset.copy ?? ""; - await navigator.clipboard.writeText(value); - const idle = button.dataset.copyLabel ?? button.textContent ?? ""; - button.textContent = button.dataset.copiedLabel ?? idle; - window.setTimeout(() => { button.textContent = idle; }, 1500); - }); - }); -</script> diff --git a/site/src/components/Footer.astro b/site/src/components/Footer.astro deleted file mode 100644 index f29178b..0000000 --- a/site/src/components/Footer.astro +++ /dev/null @@ -1,69 +0,0 @@ ---- -import BrandMark from "./BrandMark.astro"; -import { bestLocaleFor, localeFromPath, localePath, switchesLanguage } from "../i18n"; -import { useTranslations } from "../i18n/ui"; -import { releasesPageUrl } from "../lib/downloads"; - -const year = new Date().getUTCFullYear(); -const locale = localeFromPath(Astro.url.pathname); -const t = useTranslations(locale); -const href = (path: string) => localePath(bestLocaleFor(locale, path), path); -/* Column links, grouped as rendered. A `path` is resolved per locale and gets a - hint when the target has no translation; an `href` is an absolute artifact URL - that is the same in every locale. */ -type FooterLink = { path: string; label: string } | { href: string; label: string }; -const columns: { heading: string; links: FooterLink[] }[] = [ - { - heading: t("footer.start"), - links: [ - { path: "downloads/", label: t("footer.downloadCenter") }, - { path: "quickstart/", label: t("nav.quickstart") }, - { path: "changelog/", label: t("nav.changelog") }, - ], - }, - { - heading: t("footer.capability"), - links: [ - { path: "explore/", label: t("nav.explorer") }, - { path: "agents/", label: t("footer.agentCatalog") }, - { path: "providers/", label: t("footer.providerCatalog") }, - ], - }, - { - heading: t("footer.trust"), - links: [ - { path: "support/", label: t("footer.supportFeedback") }, - // A published artifact rather than a page: same URL in every locale, so it - // is exempt from the locale resolution and the hint. - { href: releasesPageUrl, label: t("footer.releaseIndex") }, - ], - }, -]; ---- -<footer class="site-footer"> - <div class="shell footer-grid"> - <div class="footer-brand"> - <a class="wordmark" href={href("")}> - <span class="wordmark-mark"><BrandMark size={22} /></span> - <span>OneAgent</span> - </a> - <p>{t("footer.tagline")}</p> - <p class="footer-fine">{t("footer.boundary")}</p> - </div> - {columns.map((column) => ( - <div> - <strong>{column.heading}</strong> - {column.links.map((link) => ( - <a href={"path" in link ? href(link.path) : link.href}> - {link.label} - {"path" in link && switchesLanguage(locale, link.path) && <span class="lang-hint">{t("nav.chineseOnly")}</span>} - </a> - ))} - </div> - ))} - </div> - <div class="shell footer-bottom"> - <span>© {year} OneAgent</span> - <span>{t("footer.channel")}</span> - </div> -</footer> diff --git a/site/src/components/Header.astro b/site/src/components/Header.astro deleted file mode 100644 index 3d5b162..0000000 --- a/site/src/components/Header.astro +++ /dev/null @@ -1,58 +0,0 @@ ---- -import BrandMark from "./BrandMark.astro"; -import ThemeToggle from "./ThemeToggle.astro"; -import LocaleSwitch from "./LocaleSwitch.astro"; -import { bestLocaleFor, localeFromPath, localePath, routeWithoutLocale, switchesLanguage } from "../i18n"; -import { useTranslations } from "../i18n/ui"; - -const pathname = Astro.url.pathname; -const locale = localeFromPath(pathname); -const t = useTranslations(locale); -const href = (path: string) => localePath(bestLocaleFor(locale, path), path); -/* The security and enterprise pages stay published and linked from the footer, - the release index and the demo's preview-gate result — they are just not - worth a top-level nav slot. */ -const nav = [ - { path: "downloads/", label: t("nav.downloads") }, - { path: "quickstart/", label: t("nav.quickstart") }, - { path: "explore/", label: t("nav.explorer") }, -]; -// Compared on the locale-stripped route so /en/agents/ marks the same item as -// /agents/ rather than matching on a substring of the full path. -const route = routeWithoutLocale(pathname); -const active = (path: string) => - route === path || - (path === "explore/" && (route === "agents/" || route.startsWith("agents/") || route === "providers/" || route.startsWith("providers/"))); ---- -<header class="site-header"> - <div class="shell header-inner"> - <a class="wordmark" href={href("")} aria-label={t("nav.home")}> - <span class="wordmark-mark"><BrandMark size={24} /></span> - <span>OneAgent</span> - </a> - <nav class="desktop-nav" aria-label={t("nav.primary")}> - {nav.map((item) => ( - <a href={href(item.path)} aria-current={active(item.path) ? "page" : undefined}> - {item.label} - {switchesLanguage(locale, item.path) && <span class="lang-hint">{t("nav.chineseOnly")}</span>} - </a> - ))} - </nav> - <div class="header-actions"> - <LocaleSwitch /> - <ThemeToggle /> - <a class:list={["button", "button-small", route === "" ? "button-secondary" : "button-primary"]} href={href("downloads/")}>{t("cta.download")}</a> - <details class="mobile-menu"> - <summary aria-label={t("nav.openMenu")}>{t("nav.menu")}</summary> - <nav aria-label={t("nav.mobile")}> - {[...nav, { path: "support/", label: t("nav.support") }].map((item) => ( - <a href={href(item.path)}> - {item.label} - {switchesLanguage(locale, item.path) && <span class="lang-hint">{t("nav.chineseOnly")}</span>} - </a> - ))} - </nav> - </details> - </div> - </div> -</header> diff --git a/site/src/components/HeroParticles.astro b/site/src/components/HeroParticles.astro deleted file mode 100644 index 6f442ca..0000000 --- a/site/src/components/HeroParticles.astro +++ /dev/null @@ -1,132 +0,0 @@ ---- ---- -<canvas class="hero-particles" data-hero-particles aria-hidden="true"></canvas> - -<style> - .hero-particles { - display: block; - width: 100%; - height: 100%; - position: absolute; - inset: 0; - z-index: 0; - pointer-events: none; - } -</style> - -<script> - const canvas = document.querySelector<HTMLCanvasElement>("[data-hero-particles]"); - const context = canvas?.getContext("2d"); - - if (canvas && context) { - const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); - const COUNT = 56; - const particles: { x: number; y: number; vx: number; vy: number; r: number; a: number }[] = []; - let width = 0; - let height = 0; - let frame = 0; - - const resize = () => { - const ratio = Math.min(window.devicePixelRatio || 1, 2); - const box = canvas.getBoundingClientRect(); - width = box.width; - height = box.height; - canvas.width = Math.round(width * ratio); - canvas.height = Math.round(height * ratio); - context.setTransform(ratio, 0, 0, ratio, 0, 0); - }; - - const seed = () => { - particles.length = 0; - for (let i = 0; i < COUNT; i += 1) { - particles.push({ - x: Math.random() * width, - y: Math.random() * height, - vx: (Math.random() - 0.5) * 0.18, - vy: (Math.random() - 0.5) * 0.18, - r: 0.8 + Math.random() * 1.6, - a: 0.16 + Math.random() * 0.3, - }); - } - }; - - // Canvas cannot resolve var() itself, so the theme's channel triple is read - // off the element and cached — re-reading it per particle per frame would - // force a style recalc 56 times a frame. - let channels = "0, 98, 204"; - const readChannels = () => { - const value = getComputedStyle(canvas).getPropertyValue("--particle").trim(); - if (value) channels = value; - }; - - const paint = () => { - context.clearRect(0, 0, width, height); - for (const p of particles) { - context.beginPath(); - context.arc(p.x, p.y, p.r, 0, Math.PI * 2); - context.fillStyle = `rgba(${channels}, ${p.a})`; - context.fill(); - } - }; - - const tick = () => { - for (const p of particles) { - p.x += p.vx; - p.y += p.vy; - if (p.x < 0) p.x = width; - else if (p.x > width) p.x = 0; - if (p.y < 0) p.y = height; - else if (p.y > height) p.y = 0; - } - paint(); - frame = window.requestAnimationFrame(tick); - }; - - const stop = () => { - if (frame) window.cancelAnimationFrame(frame); - frame = 0; - }; - - // The CSS prefers-reduced-motion block only clamps CSS animations, so the - // loop has to opt out on its own: paint one static frame and never start. - const start = () => { - stop(); - if (!reduceMotion.matches) frame = window.requestAnimationFrame(tick); - }; - - resize(); - readChannels(); - seed(); - paint(); - start(); - - // Under reduced motion nothing repaints on its own, so a theme switch has to - // trigger the redraw or the particles keep the previous scheme's colour. - document.addEventListener("oneagent:themechange", () => { - readChannels(); - if (!frame) paint(); - }); - - new IntersectionObserver((entries) => { - for (const entry of entries) { - if (entry.isIntersecting) start(); - else stop(); - } - }).observe(canvas); - - window.addEventListener("resize", () => { - resize(); - seed(); - paint(); - }); - - reduceMotion.addEventListener("change", () => { - if (reduceMotion.matches) { - stop(); - paint(); - } else { - start(); - } - }); - } -</script> diff --git a/site/src/components/LocaleSwitch.astro b/site/src/components/LocaleSwitch.astro deleted file mode 100644 index 7d0800f..0000000 --- a/site/src/components/LocaleSwitch.astro +++ /dev/null @@ -1,32 +0,0 @@ ---- -import { localeFromPath, localePath, locales, localeNames, routeWithoutLocale, translatedRoutes } from "../i18n"; -import { useTranslations } from "../i18n/ui"; - -const current = localeFromPath(Astro.url.pathname); -const route = routeWithoutLocale(Astro.url.pathname); -const t = useTranslations(current); -const target = locales.find((locale) => locale !== current)!; -// Switching keeps the reader on the same page where that page is translated, -// and falls back to the home page rather than a 404 where it is not. -const targetHref = localePath(target, translatedRoutes.has(route) ? route : ""); ---- -<a class="locale-switch" href={targetHref} hreflang={target} lang={target} aria-label={t("lang.switch")}> - {localeNames[target]} -</a> - -<style> - .locale-switch { - min-height: 34px; - padding: 0 10px; - display: inline-flex; - align-items: center; - color: var(--ink-soft); - border: 1px solid var(--line); - border-radius: var(--radius-control); - font-size: 13px; - font-weight: 560; - transition: color var(--dur-micro) var(--ease-apple), background-color var(--dur-micro) var(--ease-apple), border-color var(--dur-micro) var(--ease-apple); - } - .locale-switch:hover { color: var(--ink); background: var(--surface); border-color: var(--line-strong); } - .locale-switch:active { background: var(--surface-pressed); transition-duration: 100ms; } -</style> diff --git a/site/src/components/ThemeToggle.astro b/site/src/components/ThemeToggle.astro deleted file mode 100644 index 238f14a..0000000 --- a/site/src/components/ThemeToggle.astro +++ /dev/null @@ -1,80 +0,0 @@ ---- -import { localeFromPath } from "../i18n"; -import { useTranslations } from "../i18n/ui"; - -const t = useTranslations(localeFromPath(Astro.url.pathname)); -const label = t("theme.toggle"); ---- -<button - class="theme-toggle" - type="button" - data-theme-toggle - aria-pressed="false" - aria-label={label} - title={label} -> - <svg class="theme-icon-sun" width="16" height="16" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"> - <circle cx="8" cy="8" r="3.1" /> - <path d="M8 1.4v1.5M8 13.1v1.5M1.4 8h1.5M13.1 8h1.5M3.3 3.3l1.1 1.1M11.6 11.6l1.1 1.1M12.7 3.3l-1.1 1.1M4.4 11.6l-1.1 1.1" /> - </svg> - <svg class="theme-icon-moon" width="16" height="16" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"> - <path d="M13.4 9.9A5.9 5.9 0 0 1 6.1 2.6a5.9 5.9 0 1 0 7.3 7.3Z" /> - </svg> -</button> - -<style> - .theme-toggle { - width: 34px; - height: 34px; - display: grid; - place-items: center; - color: var(--ink-soft); - background: transparent; - border: 1px solid var(--line); - border-radius: var(--radius-control); - cursor: pointer; - transition: color var(--dur-micro) var(--ease-apple), background-color var(--dur-micro) var(--ease-apple), border-color var(--dur-micro) var(--ease-apple); - } - .theme-toggle:hover { color: var(--ink); background: var(--surface); border-color: var(--line-strong); } - .theme-toggle:active { background: var(--surface-pressed); transition-duration: 100ms; } - .theme-toggle svg { grid-area: 1 / 1; } - /* Only one glyph shows at a time; which one is decided by the theme class the - inline head script has already applied, so there is no swap on hydration. */ - .theme-icon-moon { display: none; } - :global(.theme-dark) .theme-icon-sun { display: none; } - :global(.theme-dark) .theme-icon-moon { display: block; } -</style> - -<script> - const STORAGE_KEY = "oneagent-theme"; - const button = document.querySelector<HTMLButtonElement>("[data-theme-toggle]"); - const system = window.matchMedia("(prefers-color-scheme: dark)"); - - const isDark = () => document.documentElement.classList.contains("theme-dark"); - - const sync = () => { - button?.setAttribute("aria-pressed", isDark() ? "true" : "false"); - document.dispatchEvent(new CustomEvent("oneagent:themechange")); - }; - - const apply = (dark: boolean) => { - document.documentElement.classList.toggle("theme-dark", dark); - sync(); - }; - - button?.addEventListener("click", () => { - const next = !isDark(); - // Storing the resolved choice means a later system flip cannot silently - // override what the reader picked. - window.localStorage.setItem(STORAGE_KEY, next ? "dark" : "light"); - apply(next); - }); - - // A system change should still win while no explicit choice is stored. - system.addEventListener("change", (event) => { - if (window.localStorage.getItem(STORAGE_KEY)) return; - apply(event.matches); - }); - - sync(); -</script> diff --git a/site/src/env.d.ts b/site/src/env.d.ts deleted file mode 100644 index f964fe0..0000000 --- a/site/src/env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// <reference types="astro/client" /> diff --git a/site/src/i18n/catalog.test.ts b/site/src/i18n/catalog.test.ts deleted file mode 100644 index f2958c2..0000000 --- a/site/src/i18n/catalog.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { catalog } from "../lib/catalog"; -import { locales, switchesLanguage, translatedRoutes, type Locale } from "./index"; -import { useCatalogLabels } from "./catalog"; - - -// Every enum the catalog can emit needs a label in every locale. A missing entry -// falls back to the raw id, which reads as the wrong language rather than as a -// bug — the Chinese `gateway` label shipped as the English "Gateway Agent" that -// way, and nothing failed. -describe("catalogue labels cover every locale", () => { - const cjk = /[一-鿿]/; - - for (const locale of locales) { - describe(locale, () => { - const labels = useCatalogLabels(locale); - - it("labels every group the catalog uses", () => { - const used = [...new Set(catalog.agents.map((agent) => agent.group))].sort(); - for (const group of used) { - expect(labels.groupLabels[group], `${locale} is missing group "${group}"`).toBeTruthy(); - } - }); - - it("labels every protocol status the providers publish", () => { - const used = [...new Set(catalog.providers.flatMap((p) => p.protocols.map((entry) => entry.status)))].sort(); - for (const status of used) { - expect(labels.protocolStatusLabels[status], `${locale} is missing status "${status}"`).toBeTruthy(); - } - }); - - it("writes group labels in the locale's own language", () => { - for (const [group, label] of Object.entries(labels.groupLabels)) { - // Proper nouns are allowed to stay Latin, so the check is directional: - // a Chinese label must contain some Chinese, and an English one none. - if (locale === "zh-CN") { - expect(cjk.test(label), `zh-CN group "${group}" has no Chinese: ${label}`).toBe(true); - } else { - expect(cjk.test(label), `en group "${group}" contains Chinese: ${label}`).toBe(false); - } - } - }); - }); - } -}); - -describe("cross-language link detection", () => { - it("flags only the routes with no translation", () => { - for (const route of translatedRoutes) { - expect(switchesLanguage("en", route), `${route} is translated`).toBe(false); - } - for (const route of ["enterprise/", "support/", "agents/", "providers/", "changelog/"]) { - expect(switchesLanguage("en", route), `${route} has no English page`).toBe(true); - } - }); - - it("never flags a link for a Chinese reader, since Chinese is the fallback", () => { - for (const route of [...translatedRoutes, "enterprise/", "agents/"]) { - expect(switchesLanguage("zh-CN" as Locale, route)).toBe(false); - } - }); -}); diff --git a/site/src/i18n/catalog.ts b/site/src/i18n/catalog.ts deleted file mode 100644 index ccdfe60..0000000 --- a/site/src/i18n/catalog.ts +++ /dev/null @@ -1,75 +0,0 @@ -import type { Locale } from "./index"; - -/** - * Catalogue enum labels. protocolLabels and platformLabels are absent on - * purpose — their values are proper nouns ("OpenAI Chat Completions", "macOS") - * and stay in src/lib/content.ts, shared by both languages. - */ -const catalog = { - "zh-CN": { - agentDescriptions: { - codex: "OpenAI 的终端编码 Agent,OneAgent 管理 Responses 协议配置。", - "claude-code": "Anthropic 的终端编码 Agent,使用 Anthropic-compatible 配置。", - cursor: "AI 编辑器;OneAgent 只提供官方安装与登录引导,不写入私有配置。", - opencode: "开源终端编码 Agent,使用 OpenAI-compatible 协议。", - openclaw: "多渠道 Agent 网关;按官方流程安装、onboard 与管理服务。", - hermes: "自我成长型 Agent 框架;模型与运行配置由官方工具管理。", - "gemini-cli": "Google 官方 CLI;使用 Google 登录、Gemini API Key 或 Vertex AI。", - "kilo-cli": "多模型命令行 Agent,OneAgent 可管理安装与 OpenAI-compatible 配置。", - aider: "结对编程式仓库编辑 Agent,模型在启动命令中明确选择。", - kiro: "官方账号型开发 Agent;使用官方登录与配置流程。", - cline: "IDE 扩展;在扩展界面选择兼容 Provider。", - continue: "IDE 扩展;在 Continue 配置中添加兼容模型。", - "qwen-code": "Qwen 的编码工具;按官方文档完成安装和 Provider 配置。", - "kilo-vscode": "Kilo Code 的 VS Code 扩展;由扩展界面管理 Provider。", - } as Record<string, string>, - groupLabels: { - auto: "OneAgent 可管理", - gateway: "网关 Agent", - platform: "官方账号 Agent", - ide: "IDE 扩展", - } as Record<string, string>, - protocolStatusLabels: { - "implementation-supported": "实现已接入", - "release-candidate-required": "需 Release Candidate 实证", - verified: "已验证", - unsupported: "不支持", - } as Record<string, string>, - agentFallbackDescription: "按项目公开能力提供安装或配置支持。", - }, - en: { - agentDescriptions: { - codex: "OpenAI's terminal coding agent; OneAgent manages its Responses protocol configuration.", - "claude-code": "Anthropic's terminal coding agent, configured through an Anthropic-compatible endpoint.", - cursor: "AI editor. OneAgent only points you at the official install and sign-in, and writes no private config.", - opencode: "Open-source terminal coding agent using the OpenAI-compatible protocol.", - openclaw: "Multi-channel agent gateway; install, onboard and run it through the official flow.", - hermes: "Self-improving agent framework; its own tooling owns model and runtime configuration.", - "gemini-cli": "Google's official CLI, using Google sign-in, a Gemini API key or Vertex AI.", - "kilo-cli": "Multi-model command-line agent. OneAgent can manage both install and OpenAI-compatible config.", - aider: "Pair-programming repository editor; the model is named explicitly at launch.", - kiro: "Account-based development agent using the vendor's own sign-in and configuration.", - cline: "IDE extension; choose a compatible provider inside the extension.", - continue: "IDE extension; add a compatible model in Continue's own configuration.", - "qwen-code": "Qwen's coding tool; follow the official docs for install and provider setup.", - "kilo-vscode": "Kilo Code's VS Code extension; providers are managed in the extension UI.", - } as Record<string, string>, - groupLabels: { - auto: "OneAgent-managed", - gateway: "Gateway agent", - platform: "Vendor-account agent", - ide: "IDE extension", - } as Record<string, string>, - protocolStatusLabels: { - "implementation-supported": "Implemented", - "release-candidate-required": "Needs release-candidate evidence", - verified: "Verified", - unsupported: "Unsupported", - } as Record<string, string>, - agentFallbackDescription: "Install or configuration support follows the project's published capabilities.", - }, -} as const; - -export function useCatalogLabels(locale: Locale) { - return catalog[locale]; -} diff --git a/site/src/i18n/index.ts b/site/src/i18n/index.ts deleted file mode 100644 index aaa8459..0000000 --- a/site/src/i18n/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -export const locales = ["zh-CN", "en"] as const; -export type Locale = (typeof locales)[number]; -export const defaultLocale: Locale = "zh-CN"; - -/** Prefix carried in the URL for each locale; the default locale has none. */ -const prefixes: Record<Locale, string> = { "zh-CN": "", en: "en" }; - -export const htmlLang: Record<Locale, string> = { "zh-CN": "zh-CN", en: "en" }; -export const ogLocale: Record<Locale, string> = { "zh-CN": "zh_CN", en: "en_US" }; -export const localeNames: Record<Locale, string> = { "zh-CN": "中文", en: "English" }; - -const base = import.meta.env.BASE_URL; -const baseSegments = base.split("/").filter(Boolean); - -/** - * Reads the locale out of a pathname. Works under a configured base path, where - * the locale segment is not the first one. - */ -export function localeFromPath(pathname: string): Locale { - const segments = pathname.split("/").filter(Boolean).slice(baseSegments.length); - const candidate = segments[0]; - const match = locales.find((locale) => prefixes[locale] && prefixes[locale] === candidate); - return match ?? defaultLocale; -} - -/** - * Builds an absolute, locale-aware path. - * - * Every link has to go through this. Relative hrefs resolve against <base>, - * which knows nothing about locales, so a relative link on an English page - * silently lands on the Chinese one — and validate-build.mjs anchors its - * link check at the root, so it cannot see that happen. - */ -export function localePath(locale: Locale, path = ""): string { - const clean = path.replace(/^\/+/, ""); - const prefix = prefixes[locale]; - return `${base}${prefix ? `${prefix}/` : ""}${clean}`; -} - -/** - * Routes that exist in every locale. Anything outside this set is Chinese-only - * for now, and linking it under /en/ would point at a path that was never built - * — which validate-build.mjs fails the build over. Add a route here in the same - * change that adds its translation. - */ -export const translatedRoutes = new Set(["", "downloads/", "quickstart/", "explore/", "security/"]); - -/** Resolves a link to the current locale when translated, Chinese otherwise. */ -export function bestLocaleFor(locale: Locale, route: string): Locale { - return translatedRoutes.has(route) ? locale : defaultLocale; -} - -/** - * True when following this link from `locale` lands on a different language. - * - * Untranslated routes fall back to Chinese, which is better than a 404 but is a - * surprise if nothing says so first. Callers use this to mark the link, so the - * language change is visible before the click rather than after it. - */ -export function switchesLanguage(locale: Locale, route: string): boolean { - return bestLocaleFor(locale, route) !== locale; -} - -/** Strips base and locale, yielding the route shared across languages. */ -export function routeWithoutLocale(pathname: string): string { - const segments = pathname.split("/").filter(Boolean).slice(baseSegments.length); - const first = segments[0]; - if (locales.some((locale) => prefixes[locale] && prefixes[locale] === first)) segments.shift(); - return segments.length ? `${segments.join("/")}/` : ""; -} diff --git a/site/src/i18n/ui.ts b/site/src/i18n/ui.ts deleted file mode 100644 index b6b9448..0000000 --- a/site/src/i18n/ui.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type { Locale } from "./index"; - -/** - * Chrome and shared-component copy. Page prose stays in the page templates — - * only strings reused across pages, or read by more than one component, belong - * here. - */ -const strings = { - "zh-CN": { - "site.title": "OneAgent — 可信的本地 AI 开发环境激活器", - "site.description": "用一个可信的本地流程,激活你自己的 Agent、账号和 Provider。", - "skip.main": "跳到主要内容", - "nav.home": "OneAgent 首页", - "nav.downloads": "下载", - "nav.quickstart": "快速开始", - "nav.explorer": "配置", - "nav.agents": "Agent", - "nav.providers": "Provider", - "nav.support": "支持", - "nav.changelog": "更新日志", - "nav.primary": "主导航", - "nav.mobile": "移动端导航", - "nav.openMenu": "打开导航菜单", - /* Only ever shown on an English page, where it warns that the target has no - translation yet. A Chinese reader never sees it, but the key exists in both - dictionaries so useTranslations stays exhaustive. */ - "nav.chineseOnly": "仅中文", - "nav.menu": "菜单", - "cta.download": "下载预览版", - "theme.toggle": "切换深色模式", - "lang.switch": "切换语言", - "breadcrumb.label": "面包屑", - "breadcrumb.home": "首页", - "footer.tagline": "可信的本地 AI 开发环境激活器。", - "footer.boundary": "不提供共享 Key,不代理模型请求,不重新分发第三方 Agent。", - "footer.start": "开始", - "footer.capability": "能力", - "footer.trust": "信任", - "footer.downloadCenter": "下载中心", - "footer.agentCatalog": "Agent 兼容目录", - "footer.providerCatalog": "Provider 目录", - "footer.supportFeedback": "支持与反馈", - "footer.releaseIndex": "GitHub Releases", - "footer.channel": "当前公开渠道:technical-preview-unsigned", - }, - en: { - "site.title": "OneAgent — a trustworthy local AI development environment activator", - "site.description": "Activate your own agents, accounts and providers through one auditable local flow.", - "skip.main": "Skip to main content", - "nav.home": "OneAgent home", - "nav.downloads": "Downloads", - "nav.quickstart": "Quickstart", - "nav.explorer": "Explorer", - "nav.agents": "Agents", - "nav.providers": "Providers", - "nav.support": "Support", - "nav.changelog": "Changelog", - "nav.primary": "Primary navigation", - "nav.mobile": "Mobile navigation", - "nav.openMenu": "Open navigation menu", - "nav.chineseOnly": "in Chinese", - "nav.menu": "Menu", - "cta.download": "Get the preview", - "theme.toggle": "Toggle dark mode", - "lang.switch": "Change language", - "breadcrumb.label": "Breadcrumb", - "breadcrumb.home": "Home", - "footer.tagline": "A trustworthy local AI development environment activator.", - "footer.boundary": "No shared keys, no proxied model requests, no redistribution of third-party agents.", - "footer.start": "Start", - "footer.capability": "Capabilities", - "footer.trust": "Trust", - "footer.downloadCenter": "Download centre", - "footer.agentCatalog": "Agent compatibility", - "footer.providerCatalog": "Provider catalogue", - "footer.supportFeedback": "Support & feedback", - "footer.releaseIndex": "GitHub Releases", - "footer.channel": "Current public channel: technical-preview-unsigned", - }, -} as const; - -export type UIKey = keyof (typeof strings)["zh-CN"]; - -export function useTranslations(locale: Locale) { - return (key: UIKey): string => strings[locale][key] ?? strings["zh-CN"][key]; -} diff --git a/site/src/layouts/BaseLayout.astro b/site/src/layouts/BaseLayout.astro deleted file mode 100644 index 6720b81..0000000 --- a/site/src/layouts/BaseLayout.astro +++ /dev/null @@ -1,113 +0,0 @@ ---- -import Header from "../components/Header.astro"; -import Footer from "../components/Footer.astro"; -import { defaultLocale, htmlLang, localeFromPath, localePath, locales, ogLocale, routeWithoutLocale, translatedRoutes } from "../i18n"; -import { useTranslations } from "../i18n/ui"; -import { getPreviewChannel } from "../lib/release-channel"; -import "../styles/global.css"; - -interface Props { - title?: string; - description?: string; - image?: string; - noindex?: boolean; -} - -const locale = localeFromPath(Astro.url.pathname); -const t = useTranslations(locale); -const { - title = t("site.title"), - description = t("site.description"), - image = "images/oneagent-overview.jpg", - noindex = false, -} = Astro.props; -const canonicalOrigin = Astro.site ?? Astro.url.origin; -const canonical = new URL(Astro.url.pathname, canonicalOrigin); -// Only pages that exist in both languages get alternates; pointing hreflang at -// an unbuilt path would advertise a 404 to crawlers. -const route = routeWithoutLocale(Astro.url.pathname); -const alternates = translatedRoutes.has(route) - ? locales.map((code) => ({ - code, - href: new URL(localePath(code, route), canonicalOrigin).toString(), - })) - : []; -const runtimeBaseUrl = new URL(import.meta.env.BASE_URL, Astro.url.origin); -const canonicalBaseUrl = new URL(import.meta.env.BASE_URL, canonicalOrigin); -const imageUrl = new URL(image.replace(/^\/+/, ""), canonicalBaseUrl); -const preview = await getPreviewChannel(); -const availableSystems = preview?.targets - .filter((target) => target.status === "available") - .map((target) => `${target.platformLabel} ${target.archLabel}`) ?? []; -const structuredData = { - "@context": "https://schema.org", - "@type": "SoftwareApplication", - name: "OneAgent", - applicationCategory: "DeveloperApplication", - operatingSystem: availableSystems.join(", ") || "No public build", - softwareVersion: preview?.version, - url: canonical, - description, - offers: { "@type": "Offer", price: "0", priceCurrency: "CNY" }, -}; ---- -<!doctype html> -<html lang={htmlLang[locale]}> - <head> - <meta charset="UTF-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <meta - http-equiv="Content-Security-Policy" - content="default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-src 'none'; worker-src 'none'" - /> - <meta name="description" content={description} /> - <meta name="theme-color" content="#ececef" media="(prefers-color-scheme: light)" /> - <meta name="theme-color" content="#0f1418" media="(prefers-color-scheme: dark)" /> - {/* Runs before first paint and before the stylesheet, so the resolved theme - is on <html> by the time anything is drawn. Deferring this to the module - script would paint the light palette first and flash. */} - <script is:inline> - (function () { - try { - var stored = window.localStorage.getItem("oneagent-theme"); - var dark = stored ? stored === "dark" : window.matchMedia("(prefers-color-scheme: dark)").matches; - if (dark) document.documentElement.classList.add("theme-dark"); - } catch (error) { - /* Private mode can throw on localStorage; the light default is fine. */ - } - })(); - </script> - {noindex && <meta name="robots" content="noindex" />} - <base href={runtimeBaseUrl.toString()} /> - <link rel="canonical" href={canonical} /> - {alternates.map((alternate) => ( - <link rel="alternate" hreflang={alternate.code} href={alternate.href} /> - ))} - {alternates.length > 0 && ( - <link - rel="alternate" - hreflang="x-default" - href={alternates.find((alternate) => alternate.code === defaultLocale)!.href} - /> - )} - <link rel="icon" href="favicon.svg" type="image/svg+xml" /> - <link rel="manifest" href="site.webmanifest" /> - <link rel="alternate" type="text/plain" href="llms.txt" title="llms.txt" /> - <meta property="og:type" content="website" /> - <meta property="og:locale" content={ogLocale[locale]} /> - <meta property="og:site_name" content="OneAgent" /> - <meta property="og:title" content={title} /> - <meta property="og:description" content={description} /> - <meta property="og:url" content={canonical} /> - <meta property="og:image" content={imageUrl} /> - <meta name="twitter:card" content="summary_large_image" /> - <title>{title} -