diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5980745..81c19e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,24 @@ concurrency: cancel-in-progress: true jobs: + website: + name: Website build and download checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 10.30.3 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + - run: pnpm install --filter @goagent/website... --frozen-lockfile --ignore-scripts + - run: pnpm website:build + - run: pnpm check:website + - run: pnpm --dir website exec astro check + - run: node --test website/tests/*.test.mjs + build: name: Typecheck and build (${{ matrix.os }}) runs-on: ${{ matrix.os }} diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml index 1fecf68..11e65e5 100644 --- a/.github/workflows/deploy-website.yml +++ b/.github/workflows/deploy-website.yml @@ -7,6 +7,7 @@ on: paths: - "website/**" - "scripts/check_website.mjs" + - "pnpm-lock.yaml" - ".github/workflows/deploy-website.yml" workflow_dispatch: @@ -50,11 +51,20 @@ jobs: - name: Check website contract run: pnpm check:website + - name: Check website types and download behavior + run: | + pnpm --dir website exec astro check + node --test website/tests/*.test.mjs + - name: Report missing Cloudflare secrets if: ${{ env.CLOUDFLARE_API_TOKEN == '' || env.CLOUDFLARE_ACCOUNT_ID == '' }} run: | echo "::warning title=Cloudflare deploy skipped::Set repository secrets CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID to enable automatic Pages deploys." + - name: Verify Cloudflare authentication + if: ${{ env.CLOUDFLARE_API_TOKEN != '' && env.CLOUDFLARE_ACCOUNT_ID != '' }} + run: pnpm dlx wrangler@4.118.0 whoami + - name: Deploy to Cloudflare Pages if: ${{ env.CLOUDFLARE_API_TOKEN != '' && env.CLOUDFLARE_ACCOUNT_ID != '' }} run: pnpm dlx wrangler@4.118.0 pages deploy website/dist --project-name=goagent --branch=${{ github.ref_name }} --commit-hash=${{ github.sha }} diff --git a/design-qa.md b/design-qa.md index 9944b1a..cb15497 100644 --- a/design-qa.md +++ b/design-qa.md @@ -1,32 +1,59 @@ -# Download Center Design QA +# Official Website Design QA -## Scope +final result: passed -- Official homepage download entry -- Simplified Chinese download center -- Traditional Chinese, English, Japanese, Korean, Thai, and Vietnamese download pages -- Live stable catalog integration +Date: 2026-10-02. Scope: local implementation and browser verification, not production deployment. -## Visual Review +## Reference and comparison -- [x] The page uses the existing GoAgent site header, footer, typography, and color system. -- [x] Windows and macOS choices are separated into clear platform panels. -- [x] NVIDIA, RTX 50, TensorRT, OpenCL, CPU, and no-engine choices are visible without technical setup copy. -- [x] Apple silicon and Intel Mac choices are clearly distinguished. -- [x] TensorRT is presented as two required download parts. -- [x] The small Windows update is visually secondary to full downloads. -- [x] Download sizes and the current stable tag are loaded from the live catalog. -- [x] Empty, loading, and retry states remain understandable without exposing infrastructure details. +- Selected direction: option 1, warm ivory paper, forest-green actions, Go-room photography, editorial serif headings. +- [Selected reference](website/qa/2026-10-official-site/selected-reference.png) +- [Rendered homepage, 1003 CSS px](website/qa/2026-10-official-site/home-final-1003.png) +- [Rendered homepage, 390 CSS px](website/qa/2026-10-official-site/home-final-390.png) +- [Rendered downloads, 1003 CSS px](website/qa/2026-10-official-site/download-final-1003.png) +- Reference and rendered page were inspected together at matching width, then the hero was compared again in readable, matching 1003 x 625 crops. +- Checked hierarchy, type scale, line breaks, color, imagery, spacing, section boundaries and real interactions. No unresolved P0/P1/P2 findings in this scope. -## Interaction Review +Intentional adaptations: use an actual recent application capture rather than the mockup's old UI; use library icons; show live release and size data; retain FAQ, privacy and community access; clarify that external AI service usage is not included. Page height adapts to readable copy rather than stretching a screenshot to match the reference. The full download page preserves advanced variants without crowding the homepage. -- [x] All 10 generated download actions use HTTPS and the official download host. -- [x] Catalog URLs are validated before being assigned to buttons. -- [x] The catalog failure state offers retry and a direct download-page fallback. -- [x] Navigation and footer download links stay inside the official website. -- [x] Localized pages reuse one component, preventing layout and asset drift between languages. -- [x] Interactive controls have visible labels and generated accessible names. +## Findings fixed and retested -## Result +- Primary download URL with a trailing slash returned 404 in development. Accept both slash forms, retain the official download canonical. +- Newly added image was absent in a stale dev server. Restarted and verified all image requests; no broken images in final capture. +- Reduced header, workflow and download spacing after the first visual comparison. +- Caption was low-contrast on photographic background. Added a small opaque-backed caption. +- macOS/Linux tabs showed the Windows core-update link. Now shown only on Windows; mouse and keyboard retest passed. +- Localhost is intentionally outside the production catalog CORS allowlist. A dev-only fixed-path proxy uses the real catalog; production output continues using the unchanged official endpoint. +- Catalog loading now times out and retries a bounded number of times; failed loading provides retry and GitHub backup. +- TensorRT buttons require distinct matching .001/.002 volumes. Invalid URLs, missing sizes or incomplete archives cannot become enabled downloads. +- Guides and FAQ no longer instruct users to install another application just to use AI commentary. -Passed local Astro build, website contract checks, desktop browser review, localized content review, and live catalog population checks. +## Verification + +- 46 static routes built successfully. +- Astro check: 0 errors, 0 warnings, 0 hints. +- 13 automated tests passed: catalog URL safety, schema, flavor/architecture selection, multipart validation, retries, timeout, size formatting, seven-language keys, built routes, local links/assets and duplicate IDs. +- Website contract check and git diff --check passed. +- Seven languages: homepage and downloads checked at 390px; all homepages checked at 1440px. No horizontal overflow or missing images. Chinese and English additionally checked at 320px; reference comparison at 1003px. +- Browser-tested homepage download, language navigation, mobile menu, Escape/focus restoration, OS tabs, ArrowLeft/ArrowRight/Home/End selection and Windows-only update visibility. +- Final browser log inspection: no warnings or errors. +- Live catalog: next-2026-09-26.2. All ten displayed binary/volume URLs returned HTTP 200 via HEAD, with Content-Length matching the catalog. No multi-gigabyte full downloads were performed. +- Production-origin CORS response explicitly allows https://goagent.top. + +## Boundaries + +No release assets, update protocol, R2 objects, production site or DNS were changed. No PR or deployment was performed. Browser checks used the local macOS in-app browser; this is not a Windows, Safari or screen-reader certification. + +The application image reuses the real 2026-10-01 local acceptance capture. A fresh isolated Java process was started, but the native automation surface could not attach to its window; it was stopped without changing existing user configuration. No application UI was synthesized into the image. + +## Reproduce + +```sh +pnpm install --filter @goagent/website... --frozen-lockfile --ignore-scripts +pnpm website:build +pnpm --dir website exec astro check +node --test website/tests/*.test.mjs +pnpm check:website +git diff --check +pnpm --dir website dev --host 127.0.0.1 --port 4173 +``` diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4fc0483..8db1882 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -87,6 +87,9 @@ importers: astro: specifier: ^5.16.4 version: 5.18.1(@types/node@24.12.0)(jiti@2.6.1)(rollup@4.60.1)(tsx@4.23.5)(typescript@5.9.3)(yaml@2.8.4) + bootstrap-icons: + specifier: 1.13.1 + version: 1.13.1 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -1546,6 +1549,9 @@ packages: resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + bootstrap-icons@1.13.1: + resolution: {integrity: sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==} + boxen@8.0.1: resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} engines: {node: '>=18'} @@ -5411,6 +5417,8 @@ snapshots: boolean@3.2.0: {} + bootstrap-icons@1.13.1: {} + boxen@8.0.1: dependencies: ansi-align: 3.0.1 diff --git a/scripts/check_website.mjs b/scripts/check_website.mjs index ea43871..75e3a8e 100644 --- a/scripts/check_website.mjs +++ b/scripts/check_website.mjs @@ -25,6 +25,7 @@ function walk(dir) { if (!existsSync(dir)) return [] const entries = [] for (const name of readdirSync(dir)) { + if (['node_modules', '.astro', 'dist', '.git'].includes(name)) continue const path = join(dir, name) const stat = statSync(path) if (stat.isDirectory()) entries.push(...walk(path)) @@ -57,6 +58,8 @@ requireFile('website/public/ai.txt') requireFile('website/public/_worker.js') const index = read('website/src/pages/index.astro') +const home = read('website/src/components/HomePage.astro') +const homeCopy = read('website/src/data/home-copy.mjs') const downloadPage = read('website/src/pages/download.astro') const docsPage = read('website/src/pages/docs.astro') const faqPage = read('website/src/pages/faq.astro') @@ -64,6 +67,7 @@ const changelogPage = read('website/src/pages/changelog.astro') const localizedHome = read('website/src/pages/[locale].astro') const localizedPages = read('website/src/pages/[locale]/[page].astro') const downloadChooser = read('website/src/components/DownloadChooser.astro') +const downloadImplementation = downloadChooser + read('website/src/lib/download-catalog.mjs') + read('website/src/lib/download-ui.mjs') const layout = read('website/src/layouts/BaseLayout.astro') const privacy = read('website/src/pages/privacy.astro') const deployment = read('website/DEPLOYMENT.md') @@ -76,21 +80,19 @@ const ai = read('website/public/ai.txt') const edgeWorker = read('website/public/_worker.js') const chooserCopy = ['不会选', '也没关系'].join('') -if (!index.includes('LizzieYzy Next')) fail('homepage must contain LizzieYzy Next') -if (!index.includes('想复盘围棋')) fail('homepage must use the simple Go review hero headline') -if (!index.includes('首推')) fail('homepage must present LizzieYzy Next as the recommended product') -if (!index.includes('实验围棋智能体')) fail('homepage must position GoAgent as an experimental Go agent') -for (const keyword of ['KataGo 官方推荐', '免费开源', '解压即用', '快速复盘']) { - if (!index.includes(keyword)) fail(`homepage must focus LizzieYzy Next on the new core value prop: ${keyword}`) +if (!index.includes('')) fail('download page must render the unified download chooser') if (!downloadPage.includes('canonical="https://goagent.top/download/"')) fail('download page must use the trailing-slash canonical URL') for (const keyword of [ @@ -118,7 +120,7 @@ for (const keyword of [ 'Intel 芯片', '下载小更新', ]) { - if (!downloadChooser.includes(keyword)) fail(`download chooser must contain: ${keyword}`) + if (!downloadImplementation.includes(keyword)) fail(`download chooser must contain: ${keyword}`) } const nvidiaRow = downloadChooser.indexOf("['nvidia', 'windows-portable', 'nvidia'") const amdRow = downloadChooser.indexOf("['amd', 'amd-rocm-experimental', 'rocm-gfx120x', 'x64'") @@ -172,18 +174,8 @@ for (const keyword of ['我应该先下载哪个?', '我该点哪个下载?' for (const keyword of ['官网首页更简单', '多语言页面同步更新', '普通用户不用先理解项目区别']) { if (!changelogPage.includes(keyword)) fail(`changelog page must use user-facing copy: ${keyword}`) } -for (const keyword of [ - 'homeCopy', - 'Review Go games?', - 'officialDownloadCopy', - 'home-proof-strip', - 'hero-actions', - 'Officially recommended by KataGo', - 'Free, open source, unzip-and-run', - 'lizzieDownload', - 'Tải LizzieYzy Next' -]) { - if (!localizedHome.includes(keyword)) fail(`localized homepage must use the simplified homepage system: ${keyword}`) +for (const keyword of ['HomePage', 'localeSlugs', 'getStaticPaths']) { + if (!localizedHome.includes(keyword)) fail(`localized homepage must share the same components: ${keyword}`) } for (const forbidden of [ 'lizzieBaidu', diff --git a/website/DEPLOYMENT.md b/website/DEPLOYMENT.md index b606dde..dd7f8ae 100644 --- a/website/DEPLOYMENT.md +++ b/website/DEPLOYMENT.md @@ -98,12 +98,13 @@ website/dist ## 6. GitHub Actions 自动部署 -仓库提供 `.github/workflows/deploy-website.yml`。当 `main` 分支里的 `website/**`、`scripts/check_website.mjs` 或部署 workflow 变化时,GitHub Actions 会: +仓库提供 `.github/workflows/deploy-website.yml`。当 `main` 分支里的 `website/**`、`scripts/check_website.mjs`、`pnpm-lock.yaml` 或部署 workflow 变化时,GitHub Actions 会: 1. 安装网站依赖。 2. 构建 Astro 静态站。 3. 执行 `pnpm check:website`。 -4. 使用 Cloudflare Wrangler Direct Upload 部署到 Pages 项目 `goagent`。 +4. 执行 Astro 类型检查、七语构建页面检查和下载逻辑测试;PR 阶段也执行相同门禁。 +5. 使用 `wrangler whoami` 验证部署授权,再通过 Direct Upload 部署到 Pages 项目 `goagent`。 Cloudflare Pages 项目当前是 Direct Upload 项目,不依赖 Pages 控制台的 Git 绑定。自动部署需要 GitHub 仓库 secrets: diff --git a/website/DESIGN.md b/website/DESIGN.md new file mode 100644 index 0000000..c1c4241 --- /dev/null +++ b/website/DESIGN.md @@ -0,0 +1,21 @@ +# Official Website Design + +The selected direction is a calm Go room: warm paper, forest green, restrained gold and readable editorial headings. The homepage has one primary product and one primary action, followed by a three-step review journey and three common Windows builds. The full download page keeps every supported variant. + +## Assets + +- `public/images/review-room.webp`: generated photographic background, 2026-10-02. Brief: ivory left-hand negative space, soft greenery and a wooden Go table on the right, no text, no interface, no logo. +- `public/images/review-download.webp`: generated restrained ink-landscape and Go-stone background, 2026-10-02. Brief: quiet ivory left side, decorative landscape on the right, no text or interface. +- `public/images/lizzie-review.webp`: real LizzieYzy Next local-engine acceptance screenshot from 2026-10-01, converted to WebP without synthesized UI. No credentials or personal game names are shown. +- `SiteIcon.astro`: Bootstrap Icons 1.13.1 (MIT), imported from the pinned dependency. Existing brand and download icons are retained. +- The selected mockup and final screenshots are in `qa/2026-10-official-site/`; these QA files are not public website assets. + +## Maintenance + +Homepage copy is shared across seven locales in `src/data/home-copy.mjs`. Explanatory copy in `guide-copy.mjs` keeps AI setup and privacy statements consistent. Headings and body copy remain real HTML, not image text. + +`DownloadChooser.astro` is used by both the homepage and download routes. Live release selection stays in `download-catalog.mjs`; UI status, retry and keyboard navigation stay in `download-ui.mjs`. Do not replace the live catalog with hardcoded sample versions or sizes. + +The dev-only catalog proxy is fixed to one public endpoint. Production uses the existing direct endpoint and CORS policy. Tests live under `website/tests`, separate from desktop-application tests, and run after the website build. + +See the repository-root `design-qa.md` for verification evidence and scope limits. Deploy only through the existing Cloudflare Pages workflow after review; this redesign does not migrate hosting or alter release assets. diff --git a/website/astro.config.mjs b/website/astro.config.mjs index a788d19..0a604f0 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -3,5 +3,17 @@ import { defineConfig } from 'astro/config' export default defineConfig({ site: 'https://goagent.top', output: 'static', - trailingSlash: 'never' + trailingSlash: 'ignore', + devToolbar: { enabled: false }, + vite: { + server: { + proxy: { + '/__dev/stable/catalog.json': { + target: 'https://download.goagent.top', + changeOrigin: true, + rewrite: () => '/channels/stable/catalog.json' + } + } + } + } }) diff --git a/website/package.json b/website/package.json index 54121a8..fd8d57a 100644 --- a/website/package.json +++ b/website/package.json @@ -11,6 +11,7 @@ "dependencies": { "@astrojs/check": "^0.9.4", "astro": "^5.16.4", + "bootstrap-icons": "1.13.1", "typescript": "^5.9.3" } } diff --git a/website/public/images/lizzie-review.webp b/website/public/images/lizzie-review.webp new file mode 100644 index 0000000..d3c432c Binary files /dev/null and b/website/public/images/lizzie-review.webp differ diff --git a/website/public/images/review-download.webp b/website/public/images/review-download.webp new file mode 100644 index 0000000..89bad57 Binary files /dev/null and b/website/public/images/review-download.webp differ diff --git a/website/public/images/review-room.webp b/website/public/images/review-room.webp new file mode 100644 index 0000000..95855e3 Binary files /dev/null and b/website/public/images/review-room.webp differ diff --git a/website/qa/2026-10-official-site/download-final-1003.png b/website/qa/2026-10-official-site/download-final-1003.png new file mode 100644 index 0000000..482877a Binary files /dev/null and b/website/qa/2026-10-official-site/download-final-1003.png differ diff --git a/website/qa/2026-10-official-site/home-final-1003.png b/website/qa/2026-10-official-site/home-final-1003.png new file mode 100644 index 0000000..ea8cfce Binary files /dev/null and b/website/qa/2026-10-official-site/home-final-1003.png differ diff --git a/website/qa/2026-10-official-site/home-final-390.png b/website/qa/2026-10-official-site/home-final-390.png new file mode 100644 index 0000000..ef90be8 Binary files /dev/null and b/website/qa/2026-10-official-site/home-final-390.png differ diff --git a/website/qa/2026-10-official-site/selected-reference.png b/website/qa/2026-10-official-site/selected-reference.png new file mode 100644 index 0000000..b843eb6 Binary files /dev/null and b/website/qa/2026-10-official-site/selected-reference.png differ diff --git a/website/src/components/DownloadChooser.astro b/website/src/components/DownloadChooser.astro index e523c31..5c97e5c 100644 --- a/website/src/components/DownloadChooser.astro +++ b/website/src/components/DownloadChooser.astro @@ -1,12 +1,18 @@ --- +import SiteIcon from './SiteIcon.astro' +import { homeCopy } from '../data/home-copy.mjs' +import '../styles/download-refresh.css' type SiteLang = 'zh-CN' | 'zh-Hant' | 'en' | 'ja' | 'ko' | 'th' | 'vi' interface Props { lang?: SiteLang + compact?: boolean + title?: string + lead?: string } -const { lang = 'zh-CN' } = Astro.props -const catalogUrl = 'https://download.goagent.top/channels/stable/catalog.json' +const { lang = 'zh-CN', compact = false, title, lead } = Astro.props +const catalogUrl = import.meta.env.DEV ? '/__dev/stable/catalog.json' : 'https://download.goagent.top/channels/stable/catalog.json' const releaseHistory = 'https://github.com/wimi321/lizzieyzy-next/releases' const copyByLang = { @@ -95,6 +101,8 @@ const copyByLang = { } as const const copy = copyByLang[lang] ?? copyByLang.en +const home = homeCopy[lang] +const Heading = compact ? 'h2' : 'h1' const localeSlug = { 'zh-CN': '', 'zh-Hant': 'zh-hant', en: 'en', ja: 'ja', ko: 'ko', th: 'th', vi: 'vi' }[lang] const helpUrl = localeSlug ? `/${localeSlug}/docs` : '/docs' const rows = [ @@ -112,9 +120,10 @@ const macRows = [ ---

LizzieYzy Next

-

{copy.title}

- {copy.lead} + {title || copy.title} + {lead || copy.lead}
{copy.loading}
@@ -143,14 +152,20 @@ const macRows = [ +
-
+

Windows

{copy.windowsLead}

    - {rows.map(([key, category, flavor, arch, icon, recommended]) => ( + {rows.filter(([key]) => !compact || ['nvidia', 'opencl', 'cpu'].includes(key)).map(([key, category, flavor, arch, icon, recommended]) => (
  • @@ -168,13 +183,13 @@ const macRows = [
  • ))}
- }
-
+

macOS

{copy.macLead}

@@ -200,418 +215,34 @@ const macRows = [

{copy.macTip}

+
+

Linux

{home.linuxLead}

+ {home.linuxAction} +
-
+ {!compact &&
{copy.updateTitle}{copy.updateText} · —
- +
-
+
} - } + {compact &&

{home.allDownloads}

}
- - - diff --git a/website/src/components/HomePage.astro b/website/src/components/HomePage.astro new file mode 100644 index 0000000..23c7c1c --- /dev/null +++ b/website/src/components/HomePage.astro @@ -0,0 +1,46 @@ +--- +import BaseLayout from '../layouts/BaseLayout.astro' +import DownloadChooser from './DownloadChooser.astro' +import SiteIcon from './SiteIcon.astro' +import { homeCopy, localeSlugs } from '../data/home-copy.mjs' +interface Props { lang?: keyof typeof homeCopy } +const { lang = 'zh-CN' } = Astro.props +const copy = homeCopy[lang] +const prefix = localeSlugs[lang] ? `/${localeSlugs[lang]}` : '' +const download = `${prefix}/download/` +const icons = ['folder', 'search', 'chart'] as const +--- + +
+
+
+

LizzieYzy Next

+

{copy.headline.map(line => {line})}

+

{copy.lead}

+ +

{copy.free} · Windows / macOS / Linux

+
+
+ + {copy.screenshot} + +
{copy.caption}
+
+

{copy.quote.map(line => {line})}

+
+
+
+

{copy.stepsTitle}

{copy.stepsLead}

+
    + {copy.steps.map(([title, description], index) =>
  1. + +

    {title}

    {description}

    +
  2. )} +
+
+ +
diff --git a/website/src/components/SiteIcon.astro b/website/src/components/SiteIcon.astro new file mode 100644 index 0000000..f1c4215 --- /dev/null +++ b/website/src/components/SiteIcon.astro @@ -0,0 +1,17 @@ +--- +import download from 'bootstrap-icons/icons/download.svg?url' +import folder from 'bootstrap-icons/icons/folder2-open.svg?url' +import search from 'bootstrap-icons/icons/search.svg?url' +import chart from 'bootstrap-icons/icons/bar-chart-line-fill.svg?url' +import windows from 'bootstrap-icons/icons/windows.svg?url' +import apple from 'bootstrap-icons/icons/apple.svg?url' +import linux from 'bootstrap-icons/icons/ubuntu.svg?url' +import globe from 'bootstrap-icons/icons/globe2.svg?url' +import menu from 'bootstrap-icons/icons/list.svg?url' +import chevron from 'bootstrap-icons/icons/chevron-down.svg?url' +import arrow from 'bootstrap-icons/icons/arrow-right.svg?url' +const icons = { download, folder, search, chart, windows, apple, linux, globe, menu, chevron, arrow } +interface Props { name: keyof typeof icons; size?: number; class?: string } +const { name, size = 22, class: className = '' } = Astro.props +--- + diff --git a/website/src/data/guide-copy.mjs b/website/src/data/guide-copy.mjs new file mode 100644 index 0000000..993c592 --- /dev/null +++ b/website/src/data/guide-copy.mjs @@ -0,0 +1,37 @@ +export const guideCopy = { + 'zh-CN': { + ai: ['使用 AI 解说', '在 LizzieYzy Next 顶部点击“AI 解说”,连接自己的服务后使用。不需要另外安装 GoAgent;服务用量与费用以提供方为准。'], + separate: '不需要。LizzieYzy Next 已提供 AI 解说和 AI 陪练。GoAgent 是另一个实验项目,只在想尝鲜时选择。', + privacy: '官网不上传棋谱。本地引擎在电脑上分析;使用远程算力或 AI 解说时,必要的棋局内容会发送给你选择的服务。' + }, + 'zh-Hant': { + ai: ['使用 AI 解說', '點擊 LizzieYzy Next 頂部的「AI 解說」,連接自己的服務即可使用,不必另裝 GoAgent。用量與費用以服務商為準。'], + separate: '不需要。LizzieYzy Next 已提供 AI 解說與 AI 陪練。GoAgent 是另一個可選的實驗專案。', + privacy: '官網不上傳棋譜。本機引擎在電腦上分析;使用遠端算力或 AI 解說時,必要棋局內容會傳送到你選擇的服務。' + }, + en: { + ai: ['Use AI commentary', 'Choose AI Commentary at the top of LizzieYzy Next and connect your own service. No separate GoAgent installation is needed. Usage and fees depend on your provider.'], + separate: 'No. LizzieYzy Next already includes AI commentary and practice. GoAgent is a separate, optional experimental project.', + privacy: 'This website does not upload games. Local engines analyze on your computer. Remote engines and AI commentary send the necessary game context to the service you choose.' + }, + ja: { + ai: ['AI 解説を使う', 'LizzieYzy Next 上部の AI 解説から、自分のサービスに接続します。GoAgent の追加インストールは不要です。利用枠と料金はサービス提供元に従います。'], + separate: '不要です。LizzieYzy Next に AI 解説と練習機能があります。GoAgent は別の実験的プロジェクトです。', + privacy: 'このサイトは棋譜を送信しません。ローカルエンジンは本機で分析します。遠隔エンジンや AI 解説では、必要な棋譜情報を選択したサービスに送信します。' + }, + ko: { + ai: ['AI 해설 사용', 'LizzieYzy Next 상단의 AI 해설에서 본인의 서비스에 연결하세요. GoAgent를 따로 설치할 필요가 없습니다. 사용량과 요금은 서비스 제공업체 기준입니다.'], + separate: '아니요. LizzieYzy Next에 AI 해설과 연습 기능이 있습니다. GoAgent는 별도의 선택형 실험 프로젝트입니다.', + privacy: '이 사이트는 기보를 업로드하지 않습니다. 로컬 엔진은 컴퓨터에서 분석합니다. 원격 엔진이나 AI 해설을 쓰면 필요한 기보 정보가 선택한 서비스로 전송됩니다.' + }, + th: { + ai: ['ใช้คำอธิบาย AI', 'กด AI Commentary ด้านบนของ LizzieYzy Next แล้วเชื่อมต่อบริการของคุณ ไม่ต้องติดตั้ง GoAgent เพิ่ม โควตาและค่าบริการเป็นไปตามผู้ให้บริการ'], + separate: 'ไม่จำเป็น LizzieYzy Next มีคำอธิบาย AI และคู่ซ้อมอยู่แล้ว GoAgent เป็นโครงการทดลองแยกต่างหากสำหรับผู้ที่สนใจ', + privacy: 'เว็บไซต์ไม่อัปโหลดคีฟู เอนจินภายในเครื่องวิเคราะห์บนคอมพิวเตอร์ของคุณ เมื่อใช้เอนจินระยะไกลหรือคำอธิบาย AI ข้อมูลเกมที่จำเป็นจะถูกส่งไปยังบริการที่คุณเลือก' + }, + vi: { + ai: ['Dùng giải thích AI', 'Chọn AI Commentary ở đầu LizzieYzy Next và kết nối dịch vụ của bạn. Không cần cài thêm GoAgent. Hạn mức và phí theo nhà cung cấp dịch vụ.'], + separate: 'Không cần. LizzieYzy Next đã có giải thích AI và luyện tập. GoAgent là một dự án thử nghiệm riêng, không bắt buộc.', + privacy: 'Website không tải kỳ phổ lên. Engine cục bộ phân tích trên máy. Khi dùng engine từ xa hoặc giải thích AI, thông tin ván đấu cần thiết được gửi tới dịch vụ bạn chọn.' + } +} diff --git a/website/src/data/home-copy.mjs b/website/src/data/home-copy.mjs new file mode 100644 index 0000000..553d806 --- /dev/null +++ b/website/src/data/home-copy.mjs @@ -0,0 +1,80 @@ +export const localeSlugs = { 'zh-CN': '', 'zh-Hant': 'zh-hant', en: 'en', ja: 'ja', ko: 'ko', th: 'th', vi: 'vi' } +export const homeCopy = { + 'zh-CN': { + title: 'LizzieYzy Next · 看懂每一盘棋', description: '免费开源的围棋复盘软件。导入棋谱,查看 KataGo 分析、关键失误与变化,使用 AI 陪练和解说。支持 Windows、macOS 和 Linux。', + headline: ['下过的每一盘,', '都值得看懂。'], lead: '导入棋谱,找到关键失误。复盘、陪练、AI 解说,在一个软件里完成。', + download: '免费下载', tour: '看看如何复盘', free: '软件免费开源', quote: ['棋如人生,', '多一分看见,多一分从容。'], + screenshot: 'LizzieYzy Next 实际界面:棋盘、胜率曲线与候选变化', caption: '真实软件界面 · KataGo 分析', + stepsTitle: '从今天的一盘棋开始', stepsLead: '只需三步,找到值得复盘的关键手。', + steps: [['找到棋谱', '打开本地棋谱,或从野狐等平台下载后导入,支持 SGF 格式。'], ['自动分析', '用 KataGo 查看胜率和目差变化,按自己的节奏逐手分析。'], ['看懂关键手', '对照实战与推荐变化;也可以配置 AI 解说,帮助理解。']], + downloadTitle: '选对版本,开始复盘', downloadLead: '根据电脑配置选择。更多版本与安装说明,请前往下载页面。', + allDownloads: '全部版本与安装帮助', platformLabel: '选择电脑系统', linuxLead: 'Linux 版本在 GitHub 提供,包含 CPU、OpenCL 和 CUDA。', linuxAction: '查看 Linux 下载', + menu: '导航菜单', language: '选择语言', nav: '网站导航', tagline: '围棋,让更多人看见 AI 的可能。', experimental: 'GoAgent 实验项目', footerNote: '开源 · 自由 · 更好的围棋工具', community: '交流社区', aiNote: 'AI 解说需自行连接服务,服务用量与费用以提供方为准。' + }, + 'zh-Hant': { + title: 'LizzieYzy Next · 看懂每一盤棋', description: '免費開源的圍棋復盤軟體。匯入棋譜,查看 KataGo 分析、關鍵失誤與變化,使用 AI 陪練和解說。', + headline: ['下過的每一盤,', '都值得看懂。'], lead: '匯入棋譜,找到關鍵失誤。復盤、陪練、AI 解說,在一個軟體裡完成。', + download: '免費下載', tour: '看看如何復盤', free: '軟體免費開源', quote: ['棋如人生,', '多一分看見,多一分從容。'], + screenshot: 'LizzieYzy Next 實際介面:棋盤、勝率曲線與候選變化', caption: '真實軟體介面 · KataGo 分析', + stepsTitle: '從今天的一盤棋開始', stepsLead: '只需三步,找到值得復盤的關鍵手。', + steps: [['找到棋譜', '開啟本機棋譜,或從野狐等平台下載後匯入,支援 SGF 格式。'], ['自動分析', '用 KataGo 查看勝率和目差變化,按自己的節奏逐手分析。'], ['看懂關鍵手', '對照實戰與推薦變化;也可以設定 AI 解說,幫助理解。']], + downloadTitle: '選對版本,開始復盤', downloadLead: '根據電腦配備選擇。更多版本與安裝說明,請前往下載頁面。', + allDownloads: '全部版本與安裝說明', platformLabel: '選擇電腦系統', linuxLead: 'Linux 版本在 GitHub 提供,包含 CPU、OpenCL 和 CUDA。', linuxAction: '查看 Linux 下載', + menu: '導覽選單', language: '選擇語言', nav: '網站導覽', tagline: '圍棋,讓更多人看見 AI 的可能。', experimental: 'GoAgent 實驗專案', footerNote: '開源 · 自由 · 更好的圍棋工具', community: '交流社群', aiNote: 'AI 解說需自行連接服務,用量與費用以服務提供者為準。' + }, + en: { + title: 'LizzieYzy Next · Understand every game', description: 'Free, open-source Go review with KataGo analysis, key mistakes, AI practice and explanations. For Windows, macOS and Linux.', + headline: ['Every game played.', 'A little more understood.'], lead: 'Open a game, find the turning points. Review, practice and AI explanations in one place.', + download: 'Download free', tour: 'See how review works', free: 'Free, open-source software', quote: ['A clearer view of the board.', 'A more thoughtful next move.'], + screenshot: 'Real LizzieYzy Next interface with board, win-rate graph and variations', caption: 'Real application · KataGo analysis', + stepsTitle: 'Start with a game you played today', stepsLead: 'Three steps to the moves worth a second look.', + steps: [['Find your game', 'Open an SGF file, or import a game downloaded from Fox or another platform.'], ['Run analysis', 'Use KataGo to examine win rate and score changes, one move at your own pace.'], ['Understand key moves', 'Compare your moves with suggested variations. Connect an AI service for explanations.']], + downloadTitle: 'The right version. Your next review.', downloadLead: 'Choose for your computer. Find more versions and installation help on the download page.', + allDownloads: 'All versions & installation help', platformLabel: 'Choose your operating system', linuxLead: 'Linux builds are available on GitHub, including CPU, OpenCL and CUDA.', linuxAction: 'Get Linux builds', + menu: 'Navigation menu', language: 'Choose language', nav: 'Site navigation', tagline: 'A clearer view of Go, with AI.', experimental: 'GoAgent experiments', footerNote: 'Open source. Better tools for Go.', community: 'Community', aiNote: 'AI explanations require your own service connection. Provider usage limits and fees apply.' + }, + ja: { + title: 'LizzieYzy Next · 一局ごとの理解を深める', description: '無料・オープンソースの囲碁復盤ソフト。KataGo 分析、AI 対局練習と解説を Windows・macOS・Linux で。', + headline: ['打った一局から、', '次の一手が見えてくる。'], lead: '棋譜を開いて、重要な一手を見つける。復盤も練習も AI 解説も、ひとつのソフトで。', + download: '無料ダウンロード', tour: '復盤の流れを見る', free: '無料・オープンソース', quote: ['盤面が見えれば、', '次の一手に余裕が生まれる。'], + screenshot: 'LizzieYzy Next の実画面:盤面・勝率グラフ・候補手', caption: '実際の画面 · KataGo 分析', + stepsTitle: '今日の一局から始めよう', stepsLead: '三つのステップで、振り返りたい一手へ。', + steps: [['棋譜を開く', '手元の SGF や、野狐などから取得した棋譜を読み込みます。'], ['自動分析', '勝率と目差の変化を KataGo で確認。自分のペースで一手ずつ。'], ['重要な一手を理解', '実戦と推奨変化を比較。AI サービスを接続すると解説も利用できます。']], + downloadTitle: '環境に合う版で、復盤を始める', downloadLead: 'パソコンに合わせて選択。その他の版や導入方法はダウンロードページへ。', + allDownloads: 'すべての版とインストール案内', platformLabel: 'OS を選択', linuxLead: 'Linux の CPU・OpenCL・CUDA 版は GitHub で提供しています。', linuxAction: 'Linux 版を見る', + menu: 'ナビゲーション', language: '言語を選択', nav: 'サイト案内', tagline: 'AI とともに、囲碁をもっと深く。', experimental: 'GoAgent 実験プロジェクト', footerNote: 'オープンソースで、囲碁をもっと身近に。', community: 'コミュニティ', aiNote: 'AI 解説にはサービス接続が必要です。利用条件と料金は提供元に従います。' + }, + ko: { + title: 'LizzieYzy Next · 한 판마다 깊어지는 이해', description: '무료 오픈소스 바둑 복기 도구. KataGo 분석, AI 연습 대국과 해설을 Windows, macOS, Linux에서 사용하세요.', + headline: ['한 판의 경험을,', '다음 수의 배움으로.'], lead: '기보를 열고 중요한 실수를 찾아보세요. 복기, 연습, AI 해설을 한곳에서.', + download: '무료 다운로드', tour: '복기 과정 보기', free: '무료 오픈소스 소프트웨어', quote: ['더 잘 보이는 바둑판,', '더 여유로운 다음 한 수.'], + screenshot: 'LizzieYzy Next 실제 화면: 바둑판, 승률 그래프와 변화도', caption: '실제 프로그램 · KataGo 분석', + stepsTitle: '오늘 둔 한 판부터 시작하세요', stepsLead: '세 단계로 다시 볼 중요한 수를 찾습니다.', + steps: [['기보 찾기', 'SGF 파일을 열거나 야호 등에서 받은 기보를 불러옵니다.'], ['자동 분석', 'KataGo로 승률과 집 차이를 살피며 원하는 속도로 분석합니다.'], ['중요한 수 이해하기', '실전과 추천 변화도를 비교합니다. AI 서비스를 연결하면 해설도 이용할 수 있습니다.']], + downloadTitle: '맞는 버전으로 복기 시작', downloadLead: '컴퓨터에 맞게 선택하세요. 다른 버전과 설치 안내는 다운로드 페이지에서 확인하세요.', + allDownloads: '모든 버전 및 설치 안내', platformLabel: '운영체제 선택', linuxLead: 'Linux CPU, OpenCL, CUDA 버전은 GitHub에서 제공됩니다.', linuxAction: 'Linux 버전 보기', + menu: '탐색 메뉴', language: '언어 선택', nav: '사이트 탐색', tagline: 'AI와 함께 더 깊이 보는 바둑.', experimental: 'GoAgent 실험 프로젝트', footerNote: '오픈소스, 더 나은 바둑 도구.', community: '커뮤니티', aiNote: 'AI 해설은 서비스 연결이 필요하며 제공업체의 이용 한도와 요금이 적용됩니다.' + }, + th: { + title: 'LizzieYzy Next · เข้าใจทุกเกมที่เล่น', description: 'ซอฟต์แวร์ทบทวนหมากล้อมฟรีและโอเพนซอร์ส พร้อม KataGo คู่ซ้อม AI และคำอธิบาย สำหรับ Windows, macOS และ Linux', + headline: ['ทุกเกมที่เล่น', 'มีสิ่งให้เข้าใจมากขึ้น'], lead: 'เปิดบันทึกเกม ค้นหาจุดเปลี่ยน ทบทวน ฝึกเล่น และใช้คำอธิบาย AI ในโปรแกรมเดียว', + download: 'ดาวน์โหลดฟรี', tour: 'ดูวิธีทบทวนเกม', free: 'ซอฟต์แวร์ฟรีและโอเพนซอร์ส', quote: ['มองกระดานชัดขึ้น', 'ตัดสินใจตาถัดไปอย่างมั่นใจ'], + screenshot: 'หน้าจอจริงของ LizzieYzy Next แสดงกระดาน กราฟโอกาสชนะ และแนวเดิน', caption: 'หน้าจอโปรแกรมจริง · วิเคราะห์ด้วย KataGo', + stepsTitle: 'เริ่มจากเกมที่เล่นวันนี้', stepsLead: 'สามขั้นตอนเพื่อค้นหาตาที่ควรกลับมาทบทวน', + steps: [['เลือกบันทึกเกม', 'เปิดไฟล์ SGF หรือนำเข้าเกมที่ดาวน์โหลดจาก Fox หรือแพลตฟอร์มอื่น'], ['วิเคราะห์อัตโนมัติ', 'ดูโอกาสชนะและผลต่างคะแนนด้วย KataGo วิเคราะห์ทีละตาตามจังหวะของคุณ'], ['เข้าใจตาสำคัญ', 'เทียบตาที่เล่นกับแนวเดินแนะนำ เชื่อมต่อบริการ AI เพื่ออ่านคำอธิบายเพิ่มเติม']], + downloadTitle: 'เลือกเวอร์ชัน แล้วเริ่มทบทวน', downloadLead: 'เลือกตามคอมพิวเตอร์ ดูเวอร์ชันอื่นและวิธีติดตั้งได้ที่หน้าดาวน์โหลด', + allDownloads: 'ทุกเวอร์ชันและวิธีติดตั้ง', platformLabel: 'เลือกระบบปฏิบัติการ', linuxLead: 'รุ่น Linux สำหรับ CPU, OpenCL และ CUDA มีให้ดาวน์โหลดบน GitHub', linuxAction: 'ดูรุ่น Linux', + menu: 'เมนูนำทาง', language: 'เลือกภาษา', nav: 'การนำทางเว็บไซต์', tagline: 'เข้าใจหมากล้อมมากขึ้นด้วย AI', experimental: 'โครงการทดลอง GoAgent', footerNote: 'โอเพนซอร์ส เพื่อเครื่องมือหมากล้อมที่ดีขึ้น', community: 'ชุมชน', aiNote: 'คำอธิบาย AI ต้องเชื่อมต่อบริการของคุณเอง โดยมีขีดจำกัดและค่าบริการตามผู้ให้บริการ' + }, + vi: { + title: 'LizzieYzy Next · Hiểu rõ từng ván cờ', description: 'Phần mềm xem lại cờ vây miễn phí, mã nguồn mở với KataGo, luyện đấu và giải thích AI. Dành cho Windows, macOS và Linux.', + headline: ['Mỗi ván đã chơi,', 'một điều mới để hiểu.'], lead: 'Mở kỳ phổ, tìm nước sai quan trọng. Xem lại, luyện tập và giải thích AI trong cùng một phần mềm.', + download: 'Tải miễn phí', tour: 'Xem cách phân tích', free: 'Phần mềm miễn phí, mã nguồn mở', quote: ['Nhìn rõ bàn cờ hơn,', 'bình tĩnh hơn ở nước tiếp theo.'], + screenshot: 'Giao diện thật LizzieYzy Next với bàn cờ, đồ thị tỷ lệ thắng và biến thể', caption: 'Giao diện thật · Phân tích KataGo', + stepsTitle: 'Bắt đầu từ ván cờ hôm nay', stepsLead: 'Ba bước để tìm những nước đáng xem lại.', + steps: [['Tìm kỳ phổ', 'Mở tệp SGF hoặc nhập kỳ phổ đã tải từ Fox và các nền tảng khác.'], ['Phân tích tự động', 'Dùng KataGo xem tỷ lệ thắng và chênh lệch điểm, theo nhịp độ của bạn.'], ['Hiểu nước quan trọng', 'So sánh nước thực chiến với biến thể đề xuất. Kết nối dịch vụ AI để xem giải thích.']], + downloadTitle: 'Chọn đúng phiên bản, bắt đầu xem lại', downloadLead: 'Chọn theo máy tính. Các phiên bản khác và hướng dẫn cài đặt có trên trang tải xuống.', + allDownloads: 'Mọi phiên bản và hướng dẫn cài đặt', platformLabel: 'Chọn hệ điều hành', linuxLead: 'Các bản Linux CPU, OpenCL và CUDA được cung cấp trên GitHub.', linuxAction: 'Xem bản Linux', + menu: 'Menu điều hướng', language: 'Chọn ngôn ngữ', nav: 'Điều hướng trang', tagline: 'Hiểu cờ vây rõ hơn cùng AI.', experimental: 'Dự án thử nghiệm GoAgent', footerNote: 'Mã nguồn mở. Công cụ cờ vây tốt hơn.', community: 'Cộng đồng', aiNote: 'Giải thích AI cần kết nối dịch vụ riêng. Giới hạn sử dụng và phí do nhà cung cấp quy định.' + } +} diff --git a/website/src/layouts/BaseLayout.astro b/website/src/layouts/BaseLayout.astro index 64b67c6..9516394 100644 --- a/website/src/layouts/BaseLayout.astro +++ b/website/src/layouts/BaseLayout.astro @@ -1,5 +1,8 @@ --- import '../styles/global.css' +import '../styles/review-site.css' +import SiteIcon from '../components/SiteIcon.astro' +import { homeCopy } from '../data/home-copy.mjs' interface Props { title?: string @@ -29,9 +32,7 @@ const { jsonLd } = Astro.props -const goAgentUrl = 'https://github.com/wimi321/GoAgent' const goAgentReleases = 'https://github.com/wimi321/GoAgent/releases' -const goAgentIssues = `${goAgentUrl}/issues` const lizzieUrl = 'https://github.com/wimi321/lizzieyzy-next' const homeByLang = { @@ -265,23 +266,7 @@ const navItems = [ { href: localizedPath(siteLang, 'changelog'), label: changelogLabels[siteLang] }, { href: localizedPath(siteLang, 'faq'), label: copy.nav.faq } ] - -const languageItems = [ - { href: localizedPath('zh-CN', languagePageKey), label: '简' }, - { href: localizedPath('zh-Hant', languagePageKey), label: '繁' }, - { href: localizedPath('en', languagePageKey), label: 'EN' }, - { href: localizedPath('ja', languagePageKey), label: '日' }, - { href: localizedPath('ko', languagePageKey), label: '한' }, - { href: localizedPath('th', languagePageKey), label: 'TH' }, - { href: localizedPath('vi', languagePageKey), label: 'VI' } -] - -const resourceItems = [ - { href: '/katago-review', label: copy.resources.katago }, - { href: '/fox-go-review', label: copy.resources.fox }, - { href: '/ai-go-review', label: copy.resources.ai }, - { href: '/compare', label: copy.resources.compare } -] +const siteText = homeCopy[siteLang] const alternateItems = localizedPageKeys.has(pageKey) ? [ @@ -323,55 +308,61 @@ const alternateItems = localizedPageKeys.has(pageKey)
+ + diff --git a/website/src/lib/download-catalog.mjs b/website/src/lib/download-catalog.mjs new file mode 100644 index 0000000..f61469f --- /dev/null +++ b/website/src/lib/download-catalog.mjs @@ -0,0 +1,64 @@ +const allowedHosts = new Set(['download.goagent.top', 'github.com']) +export const catalogFetchAttempts = 3 + +export function safeDownloadUrl(value) { + try { + const url = new URL(value) + if (url.protocol !== 'https:' || !allowedHosts.has(url.hostname) || url.username || url.password || url.port) return null + const validPath = url.hostname === 'github.com' + ? url.pathname.startsWith('/wimi321/lizzieyzy-next/releases/download/') + : url.pathname.startsWith('/releases/') + return validPath ? url.href : null + } catch { return null } +} + +export function safeGithubReleaseUrl(value) { + try { + const url = new URL(value) + return url.protocol === 'https:' && url.hostname === 'github.com' && !url.username && !url.password && !url.port && /^\/wimi321\/lizzieyzy-next\/releases\/tag\/[^/]+$/.test(url.pathname) ? url.href : null + } catch { return null } +} + +export function validateCatalog(catalog) { + if (!catalog || catalog.schemaVersion !== 1 || !Array.isArray(catalog.assets) || typeof catalog.releaseTag !== 'string' || !catalog.releaseTag.trim()) throw new Error('Invalid catalog') + return catalog +} + +export async function fetchCatalogWithRetry(url, { fetcher = fetch, timeoutMs = 8000, retryDelayMs = 450 } = {}) { + let lastError + for (let attempt = 1; attempt <= catalogFetchAttempts; attempt += 1) { + const controller = new AbortController() + const timer = setTimeout(() => controller.abort(), timeoutMs) + try { + const response = await fetcher(url, { signal: controller.signal, cache: 'no-store', headers: { Accept: 'application/json' } }) + if (!response.ok) throw new Error(`HTTP ${response.status}`) + return validateCatalog(await response.json()) + } catch (error) { + lastError = error + } finally { clearTimeout(timer) } + if (attempt < catalogFetchAttempts) await new Promise(resolve => setTimeout(resolve, retryDelayMs * attempt)) + } + throw lastError +} + +export function formatSize(bytes) { + if (!Number.isFinite(bytes) || bytes <= 0) return '—' + const units = ['B', 'KB', 'MB', 'GB'] + let value = bytes + let unit = 0 + while (value >= 1024 && unit < units.length - 1) { value /= 1024; unit += 1 } + return `${value.toFixed(unit === 0 ? 0 : 1)} ${units[unit]}` +} + +export function selectAssets(catalog, { category, flavor = '', arch = '', multipart = 'false' }) { + let assets = catalog.assets.filter(asset => asset && asset.category === category && (!flavor || asset.flavor === flavor) && (!arch || asset.arch === arch)) + if (multipart === 'true') { + assets = assets.filter(asset => typeof asset.name === 'string' && /\.7z\.(001|002)$/.test(asset.name)).sort((a, b) => a.name.localeCompare(b.name)) + // Both distinct volumes are necessary; two copies of part 1 are not a complete archive. + if (assets.length !== 2 || !assets[0].name.endsWith('.001') || assets[1].name !== assets[0].name.slice(0, -3) + '002') return [] + } else { assets = assets.slice(0, 1) } + const usable = assets.map(asset => ({ asset, url: safeDownloadUrl(asset.downloadUrl) })) + .filter(item => item.url && Number.isSafeInteger(item.asset.sizeBytes) && item.asset.sizeBytes > 0) + const requiredAssets = multipart === 'true' ? 2 : 1 + return usable.length !== requiredAssets ? [] : usable +} diff --git a/website/src/lib/download-ui.mjs b/website/src/lib/download-ui.mjs new file mode 100644 index 0000000..6226cbd --- /dev/null +++ b/website/src/lib/download-ui.mjs @@ -0,0 +1,82 @@ +import { fetchCatalogWithRetry, formatSize, safeGithubReleaseUrl, selectAssets } from './download-catalog.mjs' + +function fillEntry(hub, entry, catalog) { + const actions = [...entry.querySelectorAll('[data-action]')] + const usable = selectAssets(catalog, entry.dataset) + actions.forEach(action => { action.hidden = true; action.removeAttribute('href'); action.setAttribute('aria-disabled', 'true') }) + const unavailable = entry.querySelector('[data-unavailable]') + if (unavailable) unavailable.hidden = usable.length > 0 + const size = entry.querySelector('[data-size]') + if (size) size.textContent = formatSize(usable.reduce((sum, item) => sum + item.asset.sizeBytes, 0)) + usable.forEach((item, index) => { + const action = actions[index] + if (!action) return + action.href = item.url + action.hidden = false + action.removeAttribute('aria-disabled') + action.textContent = entry.dataset.multipart === 'true' + ? (index === 0 ? hub.dataset.volumeOneLabel : hub.dataset.volumeTwoLabel) + : (action.dataset.label || hub.dataset.downloadLabel) + const title = entry.querySelector('h3')?.childNodes[0]?.textContent?.trim() || entry.getAttribute('aria-label') || '' + action.setAttribute('aria-label', `${action.textContent} ${title}`.trim()) + }) +} + +function setupPlatforms(hub) { + const tablist = hub.querySelector('[data-platform-tabs]') + const tabs = [...hub.querySelectorAll('[data-platform-tab]')] + const panels = [...hub.querySelectorAll('[data-platform-panel]')] + if (!tablist) return + function select(tab) { + tabs.forEach(item => { item.setAttribute('aria-selected', String(item === tab)); item.tabIndex = item === tab ? 0 : -1 }) + panels.forEach(panel => { panel.hidden = panel.dataset.platformPanel !== tab.dataset.platformTab }) + hub.querySelectorAll('[data-platform-only]').forEach(item => { item.hidden = item.dataset.platformOnly !== tab.dataset.platformTab }) + } + tabs.forEach((tab, index) => { + tab.addEventListener('click', () => select(tab)) + tab.addEventListener('keydown', event => { + const next = { ArrowRight: (index + 1) % tabs.length, ArrowLeft: (index + tabs.length - 1) % tabs.length, Home: 0, End: tabs.length - 1 }[event.key] + if (next === undefined) return + event.preventDefault() + select(tabs[next]); tabs[next].focus() + }) + }) + select(tabs[0]) + tablist.hidden = false +} + +export function initDownloads(root) { + root.querySelectorAll('[data-download-hub]').forEach(hub => { + if (hub.dataset.initialized) return + hub.dataset.initialized = 'true' + setupPlatforms(hub) + let busy = false + async function loadCatalog() { + if (busy) return + busy = true + const error = hub.querySelector('[data-download-error]') + const status = hub.querySelector('[data-release-status]') + const retry = hub.querySelector('[data-download-retry]') + hub.setAttribute('aria-busy', 'true') + if (retry) retry.disabled = true + if (error) error.hidden = true + if (status) status.textContent = hub.dataset.loadingLabel + try { + const catalog = await fetchCatalogWithRetry(hub.dataset.catalogUrl) + hub.querySelectorAll('[data-entry]').forEach(entry => fillEntry(hub, entry, catalog)) + const releaseUrl = safeGithubReleaseUrl(catalog.releaseUrl) + if (releaseUrl) hub.querySelectorAll('[data-amd-release-link], [data-linux-release-link]').forEach(link => { link.href = releaseUrl }) + if (status) status.textContent = `${hub.dataset.releaseLabel} · ${catalog.releaseTag}` + } catch { + if (status) status.textContent = hub.dataset.errorLabel + if (error) error.hidden = false + } finally { + busy = false + if (retry) retry.disabled = false + hub.setAttribute('aria-busy', 'false') + } + } + hub.querySelector('[data-download-retry]')?.addEventListener('click', loadCatalog) + loadCatalog() + }) +} diff --git a/website/src/pages/[locale].astro b/website/src/pages/[locale].astro index 8c3eb5f..9800ff3 100644 --- a/website/src/pages/[locale].astro +++ b/website/src/pages/[locale].astro @@ -1,440 +1,9 @@ --- -import BaseLayout from '../layouts/BaseLayout.astro' - -const goAgentGithub = 'https://github.com/wimi321/GoAgent' -const goAgentReleases = 'https://github.com/wimi321/GoAgent/releases' -const lizzieGithub = 'https://github.com/wimi321/lizzieyzy-next' -const lizzieReleases = 'https://github.com/wimi321/lizzieyzy-next/releases' - -const locales = { - 'zh-hant': { - lang: 'zh-Hant', - title: 'LizzieYzy Next - 專業圍棋復盤工作台', - description: 'LizzieYzy Next 是 KataGo 官方推薦的 Lizzie 系列復盤工具,免費開源、解壓即用、適合快速復盤;GoAgent 是實驗中的圍棋智能體。', - h1: '先把一盤棋復盤清楚', - lead: '首推 LizzieYzy Next:KataGo 官方推薦的 Lizzie 系列復盤工具。免費開源,解壓即用,導入棋譜就能快速看勝率、目差和關鍵失誤;想嘗試 AI 講棋,再試 GoAgent。', - trust: ['KataGo 官方推薦', '免費開源,解壓即用', '快速復盤'], - firstCta: '下載 LizzieYzy Next', - secondCta: '試用 GoAgent', - differenceCta: '選擇適合你的工具', - productsTitle: '大多數用戶,先選 LizzieYzy Next', - productsLead: 'KataGo 官方推薦,免費開源,解壓即用,導入棋譜就能快速復盤;GoAgent 留給想嘗鮮的時候再試。', - products: [ - { - title: 'LizzieYzy Next', - label: '首推 · KataGo 官方推薦', - image: '/images/lizzieyzy-next-workbench.jpg', - alt: 'LizzieYzy Next 全盤分析工作台', - summary: 'KataGo 官方推薦的 Lizzie 系列復盤工具。免費開源、解壓即用,導入棋譜就能快速看勝率、目差和關鍵失誤。', - points: ['KataGo 官方推薦', '免費開源', '解壓即用', '快速復盤'], - primary: '下載 LizzieYzy Next', - secondary: '查看代碼', - release: lizzieReleases, - repo: lizzieGithub, - featured: true - }, - { - title: 'GoAgent', - label: '實驗項目 · 圍棋智能體', - image: '/images/goagent-workbench.jpg', - alt: 'GoAgent 圍棋講解工作台', - summary: '適合願意嘗鮮的用戶。它嘗試把棋盤、KataGo 證據、知識庫和 AI 問答放在一起。', - points: ['當前手講解實驗', 'KataGo 證據鏈', 'AI 問答工作流', 'Windows / macOS'], - primary: '試用 GoAgent', - secondary: '查看代碼', - release: goAgentReleases, - repo: goAgentGithub, - featured: false - } - ], - workflowTitle: '普通棋友的正確打開方式', - workflowLead: '先完成一次清晰的全盤復盤,再決定要不要加上 AI 講解。', - workflow: [['下載並解壓 LizzieYzy Next', '免費開源,解壓即用,不用先研究複雜配置。'], ['導入棋譜快速復盤', '用勝率、目差和關鍵失誤快速定位問題手。'], ['需要講解再試 GoAgent', '如果想用自然語言解釋關鍵手,再嘗試這個實驗圍棋智能體。']], - downloadTitle: '免費下載', - downloadLead: '首推 LizzieYzy Next。免費開源、解壓即用,從 GitHub 選擇適合你電腦系統的安裝包。', - openReleases: '從 GitHub 下載' - }, - en: { - lang: 'en', - title: 'LizzieYzy Next - Professional Go Review Workbench', - description: 'LizzieYzy Next is an officially recommended Lizzie-family KataGo review tool. It is free, open source, unzip-and-run, and built for fast review. GoAgent is experimental.', - h1: 'Review the whole game clearly first', - lead: 'Start with LizzieYzy Next: an officially recommended Lizzie-family KataGo review tool. It is free and open source, unzip-and-run, and lets you review game records quickly; try GoAgent later if you want experimental AI explanations.', - trust: ['Officially recommended by KataGo', 'Free, open source, unzip-and-run', 'Fast game review'], - firstCta: 'Download LizzieYzy Next', - secondCta: 'Try GoAgent', - differenceCta: 'Choose your tool', - productsTitle: 'Most players should start with LizzieYzy Next', - productsLead: 'Officially recommended by KataGo, free and open source, unzip-and-run, and built for fast game review. GoAgent is for experiments later.', - products: [ - { - title: 'LizzieYzy Next', - label: 'Officially recommended by KataGo', - image: '/images/lizzieyzy-next-workbench.jpg', - alt: 'LizzieYzy Next whole-game analysis workbench', - summary: 'An officially recommended Lizzie-family KataGo review tool. It is free, open source, unzip-and-run, and helps you review game records quickly.', - points: ['Officially recommended by KataGo', 'Free and open source', 'Unzip and run', 'Fast game review'], - primary: 'Download LizzieYzy Next', - secondary: 'View source', - release: lizzieReleases, - repo: lizzieGithub, - featured: true - }, - { - title: 'GoAgent', - label: 'Experimental Go agent', - image: '/images/goagent-workbench.jpg', - alt: 'GoAgent teaching workbench', - summary: 'For users who want to try the AI explanation workflow. It experiments with board state, KataGo evidence, knowledge cards, and Q&A.', - points: ['Move explanation experiment', 'KataGo evidence chain', 'AI Q&A workflow', 'Windows / macOS'], - primary: 'Try GoAgent', - secondary: 'View source', - release: goAgentReleases, - repo: goAgentGithub, - featured: false - } - ], - workflowTitle: 'The practical path for everyday players', - workflowLead: 'Complete a clear whole-game review first, then decide whether you need AI narration.', - workflow: [['Download and unzip LizzieYzy Next', 'Free and open source, unzip-and-run, with no complex setup to read first.'], ['Review games quickly', 'Use winrate, score lead, and key mistakes to find the moves that matter.'], ['Try GoAgent for explanations', 'When you want natural-language reasoning, explore the experimental Go agent.']], - downloadTitle: 'Download free', - downloadLead: 'LizzieYzy Next is the recommended download: free, open source, unzip-and-run. Choose the package for your computer on GitHub.', - openReleases: 'Download from GitHub' - }, - ja: { - lang: 'ja', - title: 'LizzieYzy Next - 囲碁復盤ワークベンチ', - description: 'LizzieYzy Next は KataGo 公式推奨の Lizzie 系復盤ツール。無料・オープンソースで、解凍してすぐ使え、すばやく復盤できます。', - h1: 'まず一局全体を読みやすく復盤', - lead: 'まずは LizzieYzy Next。KataGo 公式推奨の Lizzie 系復盤ツールです。無料・オープンソースで、解凍してすぐ使え、棋譜を開くとすばやく復盤できます。AI 解説を試したい時だけ GoAgent を使います。', - trust: ['KataGo 公式推奨', '無料・オープンソース', '解凍してすぐ復盤'], - firstCta: 'LizzieYzy Next をダウンロード', - secondCta: 'GoAgent を試す', - differenceCta: 'ツールを選ぶ', - productsTitle: '多くのユーザーは LizzieYzy Next から', - productsLead: 'KataGo 公式推奨、無料・オープンソース、解凍してすぐ使える高速復盤。GoAgent は試したい時だけで大丈夫です。', - products: [ - { title: 'LizzieYzy Next', label: 'KataGo 公式推奨', image: '/images/lizzieyzy-next-workbench.jpg', alt: 'LizzieYzy Next analysis workbench', summary: 'KataGo 公式推奨の Lizzie 系復盤ツール。無料・オープンソースで、解凍してすぐ使え、すばやく復盤できます。', points: ['KataGo 公式推奨', '無料・オープンソース', '解凍して使える', '高速復盤'], primary: 'LizzieYzy Next をダウンロード', secondary: 'コードを見る', release: lizzieReleases, repo: lizzieGithub, featured: true }, - { title: 'GoAgent', label: '実験プロジェクト · 囲碁エージェント', image: '/images/goagent-workbench.jpg', alt: 'GoAgent teaching workbench', summary: 'AI 解説ワークフローを試したいユーザー向けです。盤面、KataGo 根拠、知識カード、Q&A を組み合わせます。', points: ['一手解説の実験', 'KataGo 根拠', 'AI Q&A', 'Windows / macOS'], primary: 'GoAgent を試す', secondary: 'コードを見る', release: goAgentReleases, repo: goAgentGithub, featured: false } - ], - workflowTitle: '普通の棋士の使い方', - workflowLead: 'まず全局を明確に復盤し、その後で AI 解説が必要か判断します。', - workflow: [['LizzieYzy Next を入手して解凍', '無料・オープンソースで、複雑な設定を読む前に始められます。'], ['棋譜をすばやく復盤', '勝率、目差、重要なミスを見て転換点を探します。'], ['必要なら GoAgent', '自然言語の理由づけを試したい時に実験エージェントを使います。']], - downloadTitle: '無料ダウンロード', - downloadLead: '推奨は LizzieYzy Next です。無料・オープンソースで、解凍してすぐ使えます。GitHub で自分の OS に合うパッケージを選びます。', - openReleases: 'GitHub からダウンロード' - }, - ko: { - lang: 'ko', - title: 'LizzieYzy Next - 바둑 복기 워크벤치', - description: 'LizzieYzy Next는 KataGo 공식 추천 Lizzie 계열 복기 도구입니다. 무료 오픈소스이고, 압축을 풀면 바로 빠른 복기를 시작할 수 있습니다.', - h1: '먼저 한 판 전체를 선명하게 복기하세요', - lead: '대부분의 사용자는 LizzieYzy Next부터 시작하면 됩니다. KataGo 공식 추천 Lizzie 계열 복기 도구이고, 무료 오픈소스이며 압축을 풀면 바로 빠른 복기를 시작할 수 있습니다. AI 설명은 나중에 GoAgent로 시험하세요.', - trust: ['KataGo 공식 추천', '무료 오픈소스', '압축 해제 후 빠른 복기'], - firstCta: 'LizzieYzy Next 다운로드', - secondCta: 'GoAgent 시험하기', - differenceCta: '도구 선택', - productsTitle: '대부분은 LizzieYzy Next부터', - productsLead: 'KataGo 공식 추천, 무료 오픈소스, 압축 해제 후 바로 사용, 빠른 복기. GoAgent는 나중에 실험해도 됩니다.', - products: [ - { title: 'LizzieYzy Next', label: 'KataGo 공식 추천', image: '/images/lizzieyzy-next-workbench.jpg', alt: 'LizzieYzy Next workbench', summary: 'KataGo 공식 추천 Lizzie 계열 복기 도구입니다. 무료 오픈소스이고, 압축을 풀면 바로 승률과 집 차이를 보며 빠르게 복기할 수 있습니다.', points: ['KataGo 공식 추천', '무료 오픈소스', '압축 해제 후 실행', '빠른 복기'], primary: 'LizzieYzy Next 다운로드', secondary: '소스 보기', release: lizzieReleases, repo: lizzieGithub, featured: true }, - { title: 'GoAgent', label: '실험 프로젝트 · 바둑 에이전트', image: '/images/goagent-workbench.jpg', alt: 'GoAgent workbench', summary: 'AI 설명 워크플로를 시험하고 싶은 사용자를 위한 실험 도구입니다. 바둑판, KataGo 근거, 지식 카드와 Q&A를 결합합니다.', points: ['현재 수 설명 실험', 'KataGo 근거', 'AI Q&A', 'Windows / macOS'], primary: 'GoAgent 시험하기', secondary: '소스 보기', release: goAgentReleases, repo: goAgentGithub, featured: false } - ], - workflowTitle: '일상 사용자의 추천 흐름', - workflowLead: '전판 복기를 먼저 끝내고, 필요할 때 AI 설명을 추가하세요.', - workflow: [['LizzieYzy Next 다운로드와 압축 해제', '무료 오픈소스이며 복잡한 설정을 먼저 읽지 않아도 시작할 수 있습니다.'], ['기보를 빠르게 복기', '승률, 집 차이, 핵심 실수로 중요한 수를 찾습니다.'], ['필요하면 GoAgent', '자연어 설명을 원할 때 실험 에이전트를 사용합니다.']], - downloadTitle: '무료 다운로드', - downloadLead: '추천 다운로드는 LizzieYzy Next입니다. 무료 오픈소스이며 압축을 풀면 바로 사용할 수 있습니다. GitHub에서 내 컴퓨터에 맞는 파일을 선택하세요.', - openReleases: 'GitHub에서 다운로드' - }, - th: { - lang: 'th', - title: 'LizzieYzy Next - พื้นที่ทบทวนหมากล้อม', - description: 'LizzieYzy Next เป็นเครื่องมือทบทวนตระกูล Lizzie ที่ KataGo แนะนำอย่างเป็นทางการ ฟรี โอเพนซอร์ส แตกไฟล์แล้วใช้ได้ทันที และเหมาะกับการทบทวนเร็ว', - h1: 'ดูภาพรวมทั้งเกมให้ชัดก่อน', - lead: 'เริ่มจาก LizzieYzy Next: เครื่องมือทบทวนตระกูล Lizzie ที่ KataGo แนะนำอย่างเป็นทางการ ฟรี โอเพนซอร์ส แตกไฟล์แล้วใช้ได้ทันที เปิดคีฟูแล้วทบทวนได้เร็ว ถ้าอยากลองคำอธิบายด้วย AI ค่อยใช้ GoAgent', - trust: ['KataGo แนะนำอย่างเป็นทางการ', 'ฟรี โอเพนซอร์ส', 'แตกไฟล์แล้วทบทวนเร็ว'], - firstCta: 'ดาวน์โหลด LizzieYzy Next', - secondCta: 'ลอง GoAgent', - differenceCta: 'เลือกเครื่องมือ', - productsTitle: 'ผู้ใช้ส่วนใหญ่ควรเริ่มที่ LizzieYzy Next', - productsLead: 'KataGo แนะนำอย่างเป็นทางการ ฟรี โอเพนซอร์ส แตกไฟล์แล้วใช้ได้ทันที และเหมาะกับการทบทวนเร็ว ส่วน GoAgent ค่อยลองภายหลัง', - products: [ - { title: 'LizzieYzy Next', label: 'KataGo แนะนำอย่างเป็นทางการ', image: '/images/lizzieyzy-next-workbench.jpg', alt: 'LizzieYzy Next workbench', summary: 'เครื่องมือทบทวนตระกูล Lizzie ที่ KataGo แนะนำอย่างเป็นทางการ ฟรี โอเพนซอร์ส แตกไฟล์แล้วใช้ได้ทันที และช่วยทบทวนเกมได้เร็ว', points: ['KataGo แนะนำอย่างเป็นทางการ', 'ฟรีและโอเพนซอร์ส', 'แตกไฟล์แล้วใช้ได้', 'ทบทวนเร็ว'], primary: 'ดาวน์โหลด LizzieYzy Next', secondary: 'ดูโค้ด', release: lizzieReleases, repo: lizzieGithub, featured: true }, - { title: 'GoAgent', label: 'โปรเจกต์ทดลอง · Go agent', image: '/images/goagent-workbench.jpg', alt: 'GoAgent workbench', summary: 'เหมาะสำหรับคนที่อยากลอง workflow คำอธิบายด้วย AI รวมกระดาน หลักฐาน KataGo และ Q&A', points: ['ทดลองอธิบายหมาก', 'หลักฐาน KataGo', 'ถามตอบ AI', 'Windows / macOS'], primary: 'ลอง GoAgent', secondary: 'ดูโค้ด', release: goAgentReleases, repo: goAgentGithub, featured: false } - ], - workflowTitle: 'วิธีเริ่มที่เหมาะกับผู้เล่นทั่วไป', - workflowLead: 'ทบทวนทั้งเกมให้ชัดก่อน แล้วค่อยเพิ่มคำอธิบาย AI ถ้าต้องการ', - workflow: [['ดาวน์โหลดและแตกไฟล์ LizzieYzy Next', 'ฟรี โอเพนซอร์ส และเริ่มได้โดยไม่ต้องอ่านตั้งค่าซับซ้อนก่อน'], ['ทบทวนคีฟูได้เร็ว', 'ดูอัตราชนะ คะแนน และจุดผิดสำคัญเพื่อหาจุดเปลี่ยน'], ['ลอง GoAgent เมื่อจำเป็น', 'ถ้าอยากได้คำอธิบายภาษาคน ให้ลอง agent ทดลองนี้']], - downloadTitle: 'ดาวน์โหลดฟรี', - downloadLead: 'แนะนำให้ดาวน์โหลด LizzieYzy Next ก่อน ฟรี โอเพนซอร์ส แตกไฟล์แล้วใช้ได้ทันที เลือกไฟล์ให้ตรงกับคอมพิวเตอร์ของคุณจาก GitHub', - openReleases: 'ดาวน์โหลดจาก GitHub' - }, - vi: { - lang: 'vi', - title: 'LizzieYzy Next - Bàn làm việc ôn tập cờ vây', - description: 'LizzieYzy Next là công cụ ôn tập họ Lizzie được KataGo chính thức khuyên dùng, miễn phí, mã nguồn mở, giải nén là dùng và phù hợp để ôn tập nhanh.', - h1: 'Trước hết hãy ôn tập rõ toàn ván', - lead: 'Hãy bắt đầu với LizzieYzy Next: công cụ ôn tập họ Lizzie được KataGo chính thức khuyên dùng. Nó miễn phí, mã nguồn mở, giải nén là dùng, mở kỳ phổ là có thể ôn tập nhanh; nếu muốn lời giải thích AI, hãy thử GoAgent sau.', - trust: ['KataGo chính thức khuyên dùng', 'Miễn phí, mã nguồn mở', 'Giải nén là ôn tập nhanh'], - firstCta: 'Tải LizzieYzy Next', - secondCta: 'Thử GoAgent', - differenceCta: 'Chọn công cụ', - productsTitle: 'Đa số người dùng nên bắt đầu với LizzieYzy Next', - productsLead: 'KataGo chính thức khuyên dùng, miễn phí, mã nguồn mở, giải nén là dùng và phù hợp để ôn tập nhanh. GoAgent dành cho thử nghiệm sau.', - products: [ - { title: 'LizzieYzy Next', label: 'KataGo chính thức khuyên dùng', image: '/images/lizzieyzy-next-workbench.jpg', alt: 'LizzieYzy Next workbench', summary: 'Công cụ ôn tập họ Lizzie được KataGo chính thức khuyên dùng. Miễn phí, mã nguồn mở, giải nén là dùng và giúp ôn tập kỳ phổ nhanh.', points: ['KataGo chính thức khuyên dùng', 'Miễn phí, mã nguồn mở', 'Giải nén là dùng', 'Ôn tập nhanh'], primary: 'Tải LizzieYzy Next', secondary: 'Xem mã nguồn', release: lizzieReleases, repo: lizzieGithub, featured: true }, - { title: 'GoAgent', label: 'Dự án thử nghiệm · Go agent', image: '/images/goagent-workbench.jpg', alt: 'GoAgent workbench', summary: 'Dành cho người muốn thử workflow giải thích AI, kết hợp bàn cờ, bằng chứng KataGo, tri thức và hỏi đáp.', points: ['Thử nghiệm giải thích nước đi', 'Bằng chứng KataGo', 'Hỏi đáp AI', 'Windows / macOS'], primary: 'Thử GoAgent', secondary: 'Xem mã nguồn', release: goAgentReleases, repo: goAgentGithub, featured: false } - ], - workflowTitle: 'Cách bắt đầu cho người chơi hằng ngày', - workflowLead: 'Hoàn tất một lượt ôn tập toàn ván rõ ràng trước, rồi quyết định có cần lời giải thích AI không.', - workflow: [['Tải và giải nén LizzieYzy Next', 'Miễn phí, mã nguồn mở, bắt đầu được mà không cần đọc cấu hình phức tạp trước.'], ['Ôn tập kỳ phổ nhanh', 'Dùng tỉ lệ thắng, điểm và lỗi quan trọng để tìm bước ngoặt.'], ['Thử GoAgent nếu cần', 'Khi muốn lý giải bằng ngôn ngữ tự nhiên, hãy thử agent cờ vây thử nghiệm.']], - downloadTitle: 'Tải miễn phí', - downloadLead: 'LizzieYzy Next là bản tải khuyên dùng: miễn phí, mã nguồn mở, giải nén là dùng. Chọn gói phù hợp với máy tính của bạn trên GitHub.', - openReleases: 'Tải từ GitHub' - } -} - -const homeCopy = { - 'zh-hant': { - title: 'LizzieYzy Next - 專業圍棋復盤工作台', - description: 'LizzieYzy Next 是 KataGo 官方推薦的 Lizzie 系列復盤工具,免費開源、解壓即用、適合快速復盤;GoAgent 是實驗圍棋智能體。', - line1: '想復盤圍棋,', - line2Before: '先下載', - line2After: '', - lead: 'KataGo 官方推薦 LizzieYzy Next。它免費開源,下載後解壓即用,導入棋譜就能快速看勝率、目差和關鍵失誤;想嘗試 AI 講棋時,再試 GoAgent。', - githubCta: '從 GitHub 下載', - goAgentCta: '想嘗鮮:試用 GoAgent', - note: '免費開源、解壓即用,從 GitHub 選擇適合 Windows、macOS 或 Linux 的安裝包。', - proofs: ['KataGo 官方推薦', '免費開源,解壓即用', '導入棋譜,快速復盤'], - downloadLead: '先下載 LizzieYzy Next。免費開源、解壓即用,從 GitHub 選擇適合你電腦系統的安裝包。' - }, - en: { - title: 'LizzieYzy Next - Professional Go Review Workbench', - description: 'LizzieYzy Next is an officially recommended Lizzie-family KataGo review tool: free, open source, unzip-and-run, and built for fast review. GoAgent is experimental.', - line1: 'Review Go games?', - line2Before: 'Download', - line2After: ' first', - lead: 'KataGo officially recommends LizzieYzy Next as a Lizzie-family review tool. It is free and open source, unzip-and-run, and lets you review winrate, score lead, and key mistakes quickly; try GoAgent later when you want experimental AI explanations.', - githubCta: 'GitHub download', - goAgentCta: 'Try GoAgent', - note: 'Free, open source, unzip-and-run. Choose the package for Windows, macOS, or Linux on GitHub.', - proofs: ['Officially recommended by KataGo', 'Free, open source, unzip-and-run', 'Fast game review'], - downloadLead: 'Download LizzieYzy Next first. It is free, open source, unzip-and-run; choose the package for your computer on GitHub.' - }, - ja: { - title: 'LizzieYzy Next - 囲碁復盤ワークベンチ', - description: 'LizzieYzy Next は KataGo 公式推奨の Lizzie 系復盤ツール。無料・オープンソースで、解凍してすぐ使え、すばやく復盤できます。', - line1: '囲碁を復盤したいなら、', - line2Before: 'まず', - line2After: 'をダウンロード', - lead: 'KataGo 公式推奨の LizzieYzy Next。無料・オープンソースで、解凍してすぐ使え、棋譜を開くと勝率、目差、重要なミスをすばやく確認できます。AI 解説を試したい時だけ GoAgent を使います。', - githubCta: 'GitHub からダウンロード', - goAgentCta: 'GoAgent を試す', - note: '無料・オープンソースで、解凍してすぐ使えます。GitHub で自分に合うものを選びます。', - proofs: ['KataGo 公式推奨', '無料・オープンソース', '解凍してすぐ復盤'], - downloadLead: 'まず LizzieYzy Next をダウンロードしてください。無料・オープンソースで、解凍してすぐ使えます。' - }, - ko: { - title: 'LizzieYzy Next - 바둑 복기 워크벤치', - description: 'LizzieYzy Next는 KataGo 공식 추천 Lizzie 계열 복기 도구입니다. 무료 오픈소스이고, 압축을 풀면 바로 빠른 복기를 시작할 수 있습니다.', - line1: '바둑을 복기하려면,', - line2Before: '먼저', - line2After: '를 다운로드', - lead: 'KataGo 공식 추천 LizzieYzy Next. 무료 오픈소스이고, 압축을 풀면 바로 실행할 수 있으며, 기보를 열어 승률, 집 차이, 핵심 실수를 빠르게 확인합니다. AI 설명은 나중에 GoAgent로 시험하세요.', - githubCta: 'GitHub에서 다운로드', - goAgentCta: 'GoAgent 시험하기', - note: '무료 오픈소스이며 압축을 풀면 바로 사용할 수 있습니다. GitHub에서 맞는 파일을 선택하세요.', - proofs: ['KataGo 공식 추천', '무료 오픈소스', '압축 해제 후 빠른 복기'], - downloadLead: '먼저 LizzieYzy Next를 다운로드하세요. 무료 오픈소스이며 압축을 풀면 바로 사용할 수 있습니다.' - }, - th: { - title: 'LizzieYzy Next - พื้นที่ทบทวนหมากล้อม', - description: 'LizzieYzy Next เป็นเครื่องมือทบทวนตระกูล Lizzie ที่ KataGo แนะนำอย่างเป็นทางการ ฟรี โอเพนซอร์ส แตกไฟล์แล้วใช้ได้ทันที และเหมาะกับการทบทวนเร็ว', - line1: 'อยากทบทวนเกมหมากล้อม', - line2Before: 'ให้ดาวน์โหลด', - line2After: ' ก่อน', - lead: 'KataGo แนะนำ LizzieYzy Next อย่างเป็นทางการ มันฟรี โอเพนซอร์ส แตกไฟล์แล้วใช้ได้ทันที เปิดคีฟูแล้วดูอัตราชนะ คะแนน และจุดผิดสำคัญได้เร็ว ถ้าอยากลองคำอธิบายด้วย AI ค่อยใช้ GoAgent', - githubCta: 'ดาวน์โหลดจาก GitHub', - goAgentCta: 'ลอง GoAgent', - note: 'ฟรี โอเพนซอร์ส แตกไฟล์แล้วใช้ได้ทันที เลือกไฟล์จาก GitHub ให้ตรงกับคอมพิวเตอร์ของคุณ', - proofs: ['KataGo แนะนำอย่างเป็นทางการ', 'ฟรี โอเพนซอร์ส', 'แตกไฟล์แล้วทบทวนเร็ว'], - downloadLead: 'ดาวน์โหลด LizzieYzy Next ก่อน ฟรี โอเพนซอร์ส แตกไฟล์แล้วใช้ได้ทันที แล้วเลือกไฟล์ให้ตรงกับคอมพิวเตอร์ของคุณจาก GitHub' - }, - vi: { - title: 'LizzieYzy Next - Bàn làm việc ôn tập cờ vây', - description: 'LizzieYzy Next là công cụ ôn tập họ Lizzie được KataGo chính thức khuyên dùng, miễn phí, mã nguồn mở, giải nén là dùng và phù hợp để ôn tập nhanh.', - line1: 'Muốn ôn tập cờ vây,', - line2Before: 'hãy tải', - line2After: ' trước', - lead: 'KataGo chính thức khuyên dùng LizzieYzy Next trong nhóm công cụ họ Lizzie. Nó miễn phí, mã nguồn mở, giải nén là dùng, mở kỳ phổ là xem nhanh tỉ lệ thắng, điểm và lỗi quan trọng; khi muốn thử giải thích bằng AI thì dùng GoAgent.', - githubCta: 'Tải từ GitHub', - goAgentCta: 'Thử GoAgent', - note: 'Miễn phí, mã nguồn mở, giải nén là dùng. Chọn gói Windows, macOS hoặc Linux trên GitHub.', - proofs: ['KataGo chính thức khuyên dùng', 'Miễn phí, mã nguồn mở', 'Giải nén là ôn tập nhanh'], - downloadLead: 'Tải LizzieYzy Next trước. Miễn phí, mã nguồn mở, giải nén là dùng; chọn gói phù hợp với máy tính của bạn trên GitHub.' - } -} as const - +import HomePage from '../components/HomePage.astro' +import { localeSlugs } from '../data/home-copy.mjs' export function getStaticPaths() { - return ['zh-hant', 'en', 'ja', 'ko', 'th', 'vi'].map((locale) => ({ params: { locale } })) + return Object.entries(localeSlugs).filter(([, slug]) => slug).map(([lang, locale]) => ({ params: { locale }, props: { lang } })) } - -const locale = Astro.params.locale as keyof typeof locales -const page = locales[locale] ?? locales.en -const home = homeCopy[locale] ?? homeCopy.en -const lizzieDownload = `/${locale}/download` -const officialDownloadCopy = { - 'zh-hant': { note: 'Windows 與 macOS 版本已依硬體整理好,直接選擇即可。', lead: '進入官方下載中心,選擇與顯示卡或 Mac 晶片相符的 LizzieYzy Next。' }, - en: { note: 'Windows and macOS builds are organized by hardware.', lead: 'Open the official download center and choose the LizzieYzy Next build for your graphics card or Mac chip.' }, - ja: { note: 'Windows と macOS の各版をハードウェア別に整理しています。', lead: '公式ダウンロードセンターで GPU または Mac チップに合う LizzieYzy Next を選んでください。' }, - ko: { note: 'Windows와 macOS 버전을 하드웨어별로 정리했습니다.', lead: '공식 다운로드 센터에서 그래픽 카드 또는 Mac 칩에 맞는 LizzieYzy Next를 선택하세요.' }, - th: { note: 'จัดรุ่น Windows และ macOS ตามฮาร์ดแวร์ให้เลือกได้ทันที', lead: 'เปิดศูนย์ดาวน์โหลดอย่างเป็นทางการ แล้วเลือกรุ่น LizzieYzy Next ให้ตรงกับการ์ดจอหรือชิป Mac' }, - vi: { note: 'Các bản Windows và macOS đã được sắp theo phần cứng.', lead: 'Mở trung tâm tải xuống chính thức và chọn LizzieYzy Next theo GPU hoặc chip Mac của bạn.' } -} as const -const officialDownload = officialDownloadCopy[locale] ?? officialDownloadCopy.en +const lang = Astro.props.lang as keyof typeof localeSlugs --- - -
-
-

- {home.line1} - {home.line2Before} LizzieYzy Next{home.line2After} -

-

{home.lead}

- -

- - {officialDownload.note} -

-
-
- -
-
-
- -

{home.proofs[0]}

-
-
- -

{home.proofs[1]}

-
-
- -

{home.proofs[2]}

-
-
-
- -
-
-

{page.productsTitle}

-

{page.productsLead}

-
-
- {page.products.map((product) => ( - - ))} -
-
- -
-
-
-

{page.workflowTitle}

-

{page.workflowLead}

-
-
-
- {page.workflow.map(([title, text], index) => ( -
- {index + 1} -

{title}

-

{text}

-
- ))} -
-
- -
-
-

{page.downloadTitle}

-

{officialDownload.lead}

-
-
- - -
-
-
+ diff --git a/website/src/pages/[locale]/[page].astro b/website/src/pages/[locale]/[page].astro index cdcf34f..bb256d6 100644 --- a/website/src/pages/[locale]/[page].astro +++ b/website/src/pages/[locale]/[page].astro @@ -1,6 +1,8 @@ --- import BaseLayout from '../../layouts/BaseLayout.astro' import DownloadChooser from '../../components/DownloadChooser.astro' +import { homeCopy } from '../../data/home-copy.mjs' +import { guideCopy } from '../../data/guide-copy.mjs' const localeKeys = ['zh-hant', 'en', 'ja', 'ko', 'th', 'vi'] as const const pageKeys = ['download', 'docs', 'privacy', 'faq', 'changelog'] as const @@ -393,8 +395,19 @@ export function getStaticPaths() { const locale = Astro.params.locale as LocaleKey const page = Astro.params.page as PageKey const sourceData = localized[locale][page] -const data = (page === 'download' ? downloadMeta[locale] : ((friendlyPages as any)[locale]?.[page] ?? sourceData)) as any const meta = localeMeta[locale] +const baseData = (page === 'download' ? downloadMeta[locale] : ((friendlyPages as any)[locale]?.[page] ?? sourceData)) as any +const guide = guideCopy[meta.lang] +const data = page === 'docs' ? { + ...baseData, + description: homeCopy[meta.lang].description, + lead: homeCopy[meta.lang].lead, + steps: baseData.steps.map((step: string[], index: number) => index === 4 ? guide.ai : step), + qualityItems: [homeCopy[meta.lang].free, guide.privacy, homeCopy[meta.lang].aiNote] +} : page === 'faq' ? { + ...baseData, + items: baseData.items.map((item: string[], index: number) => index === 2 ? [item[0], guide.separate] : index === 4 ? [item[0], guide.privacy] : item) +} : baseData const canonical = `https://goagent.top/${locale}/${page}` const jsonLd = page === 'faq' ? { diff --git a/website/src/pages/ai-go-review.astro b/website/src/pages/ai-go-review.astro index 188454f..974ded7 100644 --- a/website/src/pages/ai-go-review.astro +++ b/website/src/pages/ai-go-review.astro @@ -1,7 +1,8 @@ --- import BaseLayout from '../layouts/BaseLayout.astro' +import { guideCopy } from '../data/guide-copy.mjs' -const lizzieReleases = 'https://github.com/wimi321/lizzieyzy-next/releases' +const lizzieReleases = '/download/' const goAgentReleases = 'https://github.com/wimi321/GoAgent/releases' const principles = [ @@ -11,8 +12,8 @@ const principles = [ ] const faq = [ - ['围棋 AI 复盘是不是直接问聊天机器人?', '不建议。聊天机器人没有棋盘和 KataGo 证据时容易编造。更好的顺序是先用 LizzieYzy Next 分析,再用 GoAgent 尝试讲解。'], - ['LizzieYzy Next 和 GoAgent 怎么搭配?', 'LizzieYzy Next 负责成熟稳定的全盘复盘;GoAgent 负责实验性的自然语言讲棋。'], + ['围棋 AI 复盘是不是直接问聊天机器人?', '不建议。先用 KataGo 分析,再使用 LizzieYzy Next 内的 AI 解说,让解释有棋盘和分析结果作为依据。'], + ['还需要另外安装 GoAgent 吗?', guideCopy['zh-CN'].separate], ['AI 讲棋应该相信什么?', '优先相信可验证的 KataGo 候选点、胜率、目差和变化图。自然语言解释应该服务这些证据。'] ] @@ -39,7 +40,7 @@ const jsonLd = { --- @@ -57,9 +58,9 @@ const jsonLd = {

推荐顺序

-

先用 LizzieYzy Next 做成熟稳定的 KataGo 全盘复盘;当你已经定位关键手,再用 GoAgent 尝试 AI 讲棋。这个顺序能减少空泛解释,也更适合日常进步。

+

先用 KataGo 分析整盘棋,找到胜率或目差明显变化的局面,再点击软件顶部的 AI 解说。{guideCopy['zh-CN'].ai[1]}

- GoAgent 围棋 AI 讲解工作台 + LizzieYzy Next 实际分析界面
diff --git a/website/src/pages/docs.astro b/website/src/pages/docs.astro index b80c56f..d19b8a3 100644 --- a/website/src/pages/docs.astro +++ b/website/src/pages/docs.astro @@ -1,21 +1,22 @@ --- import BaseLayout from '../layouts/BaseLayout.astro' +import { guideCopy } from '../data/guide-copy.mjs' const docs = [ ['第一步:先下载 LizzieYzy Next', '大多数棋友先装这个就够了。进入官网下载中心,先选 Windows 或 macOS,再按显卡或芯片选择版本。'], ['第二步:选对你的电脑系统', 'Windows 用户中,NVIDIA RTX 20 / 30 / 40 / 50 选统一 CUDA 版,AMD、Intel 或旧 NVIDIA 显卡选 OpenCL 版,没有合适独立显卡选 CPU 版;Mac 用户按芯片选择 Apple Silicon 或 Intel 版。'], ['第三步:打开棋谱开始复盘', '安装后导入 SGF 棋谱,先看胜率、目差和关键失误。不要一开始研究太多设置,先把一盘棋看明白。'], ['第四步:先看三个重点', '先看哪几手胜率掉得多,再看目差变化,最后看 KataGo 推荐的候选点。普通复盘先抓这三件事就够用。'], - ['第五步:想听 AI 讲棋,再试 GoAgent', 'GoAgent 是实验功能,适合想尝试 AI 自然语言讲解的人。只想稳定复盘,不用一开始就安装 GoAgent。'], + guideCopy['zh-CN'].ai, ['第六步:下载或安装遇到问题', '下载不顺时可以换另一个入口或网络。Windows 提示安全拦截时,请确认文件来自官网链接后再继续。'], - ['第七步:隐私怎么理解', 'LizzieYzy Next 和 GoAgent 都是桌面工具,棋谱复盘优先在你的电脑上完成。GoAgent 只有在你自己配置 AI 服务并发起讲解时,才会把必要内容发给你配置的服务。'], + ['第七步:隐私怎么理解', guideCopy['zh-CN'].privacy], ['第八步:需要帮助时', '先看常见问题;还解决不了,可以去 GitHub Issues,或加入 QQ 群 1030632742 / 299419120 交流。'] ] --- - +

使用指南

-

不用研究术语。先下载 LizzieYzy Next,打开棋谱,看胜率、目差和关键失误;想尝试 AI 讲棋时,再下载 GoAgent。

+

不用研究术语。打开棋谱,分析一盘棋,再逐手看懂关键失误。AI 解说和陪练都在同一个软件里。

{docs.map(([title, text], index) => ( @@ -32,7 +33,7 @@ const docs = [
  • 只想复盘:先用 LizzieYzy Next。
  • 下载软件:从官网下载中心选择适合电脑的版本。
  • -
  • 想尝试 AI 讲棋:再试 GoAgent。
  • +
  • {guideCopy['zh-CN'].ai[1]}
diff --git a/website/src/pages/faq.astro b/website/src/pages/faq.astro index f4aaeee..4e25636 100644 --- a/website/src/pages/faq.astro +++ b/website/src/pages/faq.astro @@ -1,16 +1,17 @@ --- import BaseLayout from '../layouts/BaseLayout.astro' +import { guideCopy } from '../data/guide-copy.mjs' const faqs = [ ['我应该先下载哪个?', '先下载 LizzieYzy Next。它是首推的稳定复盘工具,适合大多数棋友日常看胜率、目差和关键失误。'], ['我该点哪个下载?', '直接进入官网下载中心。NVIDIA RTX 20 / 30 / 40 / 50 选 CUDA 版,AMD、Intel 或旧 NVIDIA 显卡选 OpenCL 版,没有合适独立显卡再选 CPU 版。'], - ['GoAgent 要不要一起下载?', '不用一开始就下载。GoAgent 是实验围棋智能体,适合想尝试 AI 讲棋、AI 问答的人。只想稳定复盘,先不用管它。'], + ['GoAgent 要不要一起下载?', guideCopy['zh-CN'].separate], ['下载后我该做什么?', '安装 LizzieYzy Next,打开或导入 SGF 棋谱,然后先看胜率曲线、目差变化和关键失误。'], ['支持哪些电脑?', 'LizzieYzy Next 支持 Windows、macOS、Linux。GoAgent 目前更适合想尝鲜的 Windows / macOS 用户。'], ['需要看英文 GitHub 页面吗?', '普通用户不需要。官网下载中心已经把稳定版按电脑类型整理好,直接点击对应版本即可。'], ['安装包从哪里下载?', '官网下载中心会提供当前稳定版的直接下载按钮;历史版本和开源代码仍可从 GitHub 查看。'], - ['会不会上传我的棋谱?', '官网本身不上传棋谱。LizzieYzy Next 是桌面复盘工具;GoAgent 只有在你自己配置 AI 服务并主动使用讲解时,才会调用你配置的服务。'], - ['KataGo、AI、LLM 这些词我不懂怎么办?', '不用先懂。日常复盘先看 LizzieYzy Next 的胜率、目差和推荐点就够了;想深入时再了解 GoAgent 和 AI 讲棋。'], + ['会不会上传我的棋谱?', guideCopy['zh-CN'].privacy], + ['KataGo、AI、LLM 这些词我不懂怎么办?', '不用先懂。日常复盘先看胜率、目差和推荐点就够了;想听解释时,再连接自己的 AI 服务。'], ['如何反馈问题?', '可以在 GitHub Issues 反馈,也欢迎加入 GoAgent QQ 1030632742 或 LizzieYzy Next QQ 299419120 交流。'] ] diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro index adce084..730c5da 100644 --- a/website/src/pages/index.astro +++ b/website/src/pages/index.astro @@ -1,278 +1,4 @@ --- -import BaseLayout from '../layouts/BaseLayout.astro' - -const goAgentGithub = 'https://github.com/wimi321/GoAgent' -const goAgentReleases = 'https://github.com/wimi321/GoAgent/releases' -const lizzieGithub = 'https://github.com/wimi321/lizzieyzy-next' -const lizzieReleases = 'https://github.com/wimi321/lizzieyzy-next/releases' -const lizzieDownload = '/download' - -const products = [ - { - title: 'LizzieYzy Next', - label: '首推 · KataGo 官方推荐', - image: '/images/lizzieyzy-next-workbench.jpg', - alt: 'LizzieYzy Next 全盘分析工作台', - summary: 'KataGo 官方推荐的 Lizzie 系列复盘工具。免费开源,下载后解压即用,导入棋谱就能快速看胜率、目差和关键失误。', - points: ['KataGo 官方推荐', '免费开源', '解压即用', '快速复盘'], - primary: '下载 LizzieYzy Next', - secondary: '查看代码', - release: lizzieDownload, - repo: lizzieGithub, - featured: true - }, - { - title: 'GoAgent', - label: '实验项目 · 围棋智能体', - image: '/images/goagent-workbench.jpg', - alt: 'GoAgent 围棋讲解工作台', - summary: '想试 AI 讲棋时再用。它是实验功能,会尝试把局面和 KataGo 结果讲成更容易听懂的话。', - points: ['实验功能', 'AI 讲棋', '适合尝鲜', 'Windows / macOS'], - primary: '试用 GoAgent', - secondary: '查看代码', - release: goAgentReleases, - repo: goAgentGithub, - featured: false - } -] - -const workflow = [ - ['下载并解压 LizzieYzy Next', '免费开源,解压即用,不用先研究复杂配置。'], - ['导入棋谱快速复盘', '看胜率、目差和关键失误,快速知道这盘棋哪里下坏了。'], - ['想尝鲜再试 GoAgent', '想让 AI 用自然语言讲棋时,再试 GoAgent 这个实验功能。'] -] - -const downloads = [ - { - title: '推荐:官网下载中心', - text: '先选 Windows 或 macOS,再按显卡和芯片直接下载适合你的版本。', - href: lizzieDownload, - note: '普通用户从这里开始', - cta: '选择适合我的版本', - label: '推荐先点这个', - icon: 'cloud', - featured: true - }, - { - title: 'LizzieYzy Next 开源项目', - text: '想查看源代码、提交问题或参与项目,可以进入 GitHub 仓库。', - href: lizzieGithub, - note: '开源与社区', - cta: '查看开源项目', - label: '', - icon: 'github', - featured: false - }, - { - title: '想尝鲜:GoAgent', - text: 'GoAgent 是实验围棋智能体,适合想试 AI 讲棋的用户。只想稳定复盘,先不用管它。', - href: goAgentReleases, - note: '实验功能', - cta: '试用 GoAgent', - label: '', - icon: 'agent', - featured: false - } -] - -const jsonLd = { - '@context': 'https://schema.org', - '@type': 'WebSite', - name: 'LizzieYzy Next', - alternateName: '专业围棋复盘工作台', - url: 'https://goagent.top/', - description: 'LizzieYzy Next 是 KataGo 官方推荐的 Lizzie 系列复盘工具,免费开源、解压即用、适合快速复盘;GoAgent 是实验中的围棋智能体。', - hasPart: [ - { - '@type': 'SoftwareApplication', - name: 'LizzieYzy Next', - applicationCategory: 'GameApplication', - operatingSystem: 'Windows, macOS, Linux', - url: lizzieGithub, - downloadUrl: ['https://goagent.top/download', lizzieReleases], - offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' } - }, - { - '@type': 'SoftwareApplication', - name: 'GoAgent', - applicationCategory: 'EducationalApplication', - operatingSystem: 'Windows, macOS', - url: goAgentGithub, - downloadUrl: goAgentReleases, - offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' } - } - ] -} +import HomePage from '../components/HomePage.astro' --- - -
-
-

- 想复盘围棋, - 先下载 LizzieYzy Next -

-

- KataGo 官方推荐 LizzieYzy Next。它免费开源,下载后解压即用,导入棋谱就能快速看胜率、目差和关键失误;想尝试 AI 讲棋时,再试 GoAgent。 -

- -

- - Windows 和 macOS 版本已按硬件整理好,直接选择即可。 -

-
-
- -
-
-
- -

KataGo 官方推荐

-
-
- -

免费开源,解压即用

-
-
- -

导入棋谱,快速复盘

-
-
-
- -
-
-

为什么先装 LizzieYzy Next?

-

KataGo 官方推荐,免费开源,解压即用,导入棋谱就能快速复盘。GoAgent 是实验功能,想尝鲜时再试。

-
-
- {products.map((product) => ( - - ))} -
-
- -
-
-
-

普通棋友的正确打开方式

-

先完成一次清晰的全盘复盘,再决定要不要加上 AI 讲解。

-
-
-
- {workflow.map(([title, text], index) => ( -
- {index + 1} -

{title}

-

{text}

-
- ))} -
-
- -
-
-

下载顺序很简单

-

先进入官网下载中心选择适合电脑的版本。LizzieYzy Next 免费开源、解压即用;GoAgent 是实验功能,想试 AI 讲棋再下载。

-
-
- {downloads.map((download) => ( -
- -
- {download.label &&

{download.label}

} -

{download.title}

-

{download.text}

- {download.note} -
- {download.cta} -
- ))} -
-
-
+ diff --git a/website/src/styles/download-refresh.css b/website/src/styles/download-refresh.css new file mode 100644 index 0000000..c4a280b --- /dev/null +++ b/website/src/styles/download-refresh.css @@ -0,0 +1,110 @@ +.download-hub [hidden] { display: none !important; } +.download-hub { width: 100vw; margin-inline: calc(50% - 50vw); background: #faf8f3 url('/images/review-download.webp') center top / 100% auto no-repeat; border-block: 1px solid #dfdfd4; } +.download-hub-inner { width: min(1296px, calc(100% - 96px)); margin: auto; padding: 54px 0 38px; } +.download-hub-hero { display: flex; gap: 20px; align-items: center; flex-wrap: wrap; margin-bottom: 32px; } +.download-hub-hero > img { width: 68px; height: 68px; } +.download-hub-hero > div:nth-child(2) { flex: 1; min-width: 220px; } +.download-hub-hero p { margin: 0 0 8px; font: 700 23px/1.3 Georgia, serif; color: #225a47; } +.download-hub-hero h1, .download-hub-hero h2 { margin: 0; color: #102d25; font: 700 42px/1.25 var(--review-serif); } +.download-hub-hero span { display: block; margin-top: 12px; font-size: 16px; color: #56665b; line-height: 1.6; } +.release-status { color: #405d50; font-size: 13px; line-height: 1.6; } +.download-tabs { display: flex; gap: 5px; border-bottom: 1px solid #becabf; } +.download-tabs button { border: 1px solid #d5d9ce; border-bottom: 0; border-radius: 12px 12px 0 0; min-width: 160px; padding: 14px 24px; min-height: 50px; display: inline-flex; align-items: center; justify-content: center; gap: 12px; font-family: inherit; font-size: 17px; font-weight: 600; line-height: 1.3; color: #354c41; background: #faf8f3; cursor: pointer; } +.download-tabs button[aria-selected='true'] { color: white; background: #0b513f; border-color: #0b513f; } +.download-tabs button[aria-selected='true'] .site-icon { filter: invert(1); } +.download-tabs button:hover:not([aria-selected='true']) { background: #eaf0e8; } +.download-platform-panel { padding: 26px 0 0; } +.platform-heading { display: flex; gap: 12px; align-items: flex-start; margin-bottom: 22px; } +.platform-heading h2 { margin: 0; color: #174734; font: 700 25px/1.3 Georgia, serif; } +.platform-heading p { margin: 5px 0 0; color: #536257; font-size: 15px; } +.download-option-list { list-style: none; padding: 0; margin: 0; } +.download-option { display: grid; grid-template-columns: minmax(0, 1fr) 85px 190px; align-items: center; gap: 22px; border-bottom: 1px solid #dedfd5; padding: 18px 14px; min-height: 90px; } +.option-main { display: grid; grid-template-columns: 37px minmax(0, 1fr); align-items: center; gap: 16px; } +.option-main > img { width: 34px; height: 34px; } +.option-main h3 { margin: 0; color: #172e24; font-size: 18px; font-weight: 650; line-height: 1.5; } +.option-main h3 span { display: inline-block; padding: 2px 7px; margin-left: 6px; border-radius: 4px; color: #83521c; background: #f3e9d7; font-size: 12px; vertical-align: middle; } +.option-main p { margin: 4px 0 0; color: #56665b; font-size: 14px; line-height: 1.65; max-width: 56em; } +.option-size { color: #506356; font-size: 14px; font-variant-numeric: tabular-nums; white-space: nowrap; } +.option-actions { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 8px; } +.option-actions a, .linux-download { display: inline-flex; justify-content: center; align-items: center; min-height: 44px; padding: 12px 22px; border-radius: 6px; color: white; background: #0b513f; font-size: 15px; font-weight: 600; line-height: 1.4; text-align: center; transition: background 160ms ease; } +.option-actions a:hover, .linux-download:hover { background: #063b2e; } +.option-actions > span { font-size: 14px; color: #726044; } +.amd-more { margin: 20px 0 0; display: flex; flex-wrap: wrap; align-items: center; gap: 14px; font-size: 14px; color: #56665b; } +.amd-more a { color: #0b513f; text-decoration: underline; text-underline-offset: 4px; } +.mac-install-tip { margin: 20px 0 0; color: #536356; font-size: 15px; } +.linux-panel { padding: 34px 0; } +.linux-panel h2 { display: inline-block; margin: 0 0 0 14px; color: #174734; } +.linux-panel p { font-size: 16px; } +.linux-download { gap: 12px; } +.linux-download .site-icon { filter: invert(1); } +.core-update { display: grid; grid-template-columns: 32px minmax(0, 1fr) auto; align-items: center; gap: 20px; margin-top: 30px; padding: 22px 0; border-block: 1px solid #d6dccc; } +.core-update strong { font-size: 16px; color: #294c3c; } +.core-update div > span { margin-left: 12px; color: #57665b; font-size: 14px; } +.core-update .option-actions a { color: #755127; background: transparent; border: 1px solid #a18153; } +.core-update .option-actions a:hover { background: #f1e9da; } +.download-support { display: flex; justify-content: center; flex-wrap: wrap; gap: 12px 28px; margin-top: 22px; } +.download-support a { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; color: #315940; font-size: 14px; } +.download-error { background: #f7eddc; border-left: 3px solid #9a702e; padding: 18px 22px; margin: 0 0 24px; color: #65461f; } +.download-error strong { color: inherit; line-height: 1.6; } +.download-error > div { display: flex; flex-wrap: wrap; gap: 12px 20px; margin-top: 12px; } +.download-error button, .download-error a { padding: 10px 16px; min-height: 44px; font: inherit; color: #64471e; background: #fffdf5; border: 1px solid #b69868; border-radius: 5px; cursor: pointer; } +.download-error button:disabled { opacity: .6; cursor: wait; } +.noscript-note { text-align: center; } +.noscript-note a { text-decoration: underline; } +.download-hub.compact .download-hub-hero > img, .download-hub.compact .download-hub-hero p, .download-hub.compact .platform-heading { display: none; } +.download-hub.compact .download-hub-hero h2 { font-size: 37px; } +.download-hub.compact .download-hub-inner { padding-block: 38px 24px; } +.download-hub.compact .release-status { flex-basis: 100%; margin-top: -10px; font-size: 12px; } +.download-hub.compact .download-hub-hero { margin-bottom: 18px; gap: 18px; } +.download-hub.compact .download-option-list { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); } +.download-hub.compact .mac-panel .download-option-list { grid-template-columns: repeat(2, minmax(0, 1fr)); } +.download-hub.compact .download-option { display: flex; flex-direction: column; align-items: stretch; gap: 10px; padding: 2px 24px; border-bottom: 0; border-left: 1px solid #dadcd1; } +.download-hub.compact .download-option:first-child { border-left: 0; padding-left: 12px; } +.download-hub.compact .option-main { align-items: start; } +.download-hub.compact .option-size { padding-left: 53px; } +.download-hub.compact .option-actions { justify-content: flex-start; margin: auto 0 0 53px; } +.download-hub.compact .option-actions a { min-width: 0; width: 100%; } +.download-hub.compact .option-main h3 span { display: none; } +.all-downloads { text-align: center; margin: 22px 0 0; } +.all-downloads a { display: inline-flex; gap: 12px; align-items: center; min-height: 44px; text-decoration: underline; text-underline-offset: 8px; color: #0b513f; font-size: 16px; font-weight: 600; } +@media (max-width: 900px) { + .download-hub-inner { width: calc(100% - 48px); padding: 38px 0 30px; } + .download-tabs button { min-width: 140px; padding: 16px 22px; } + .download-hub.compact .download-option { padding: 8px 18px; } + .download-hub.compact .option-main { display: flex; flex-direction: column; gap: 12px; } + .download-hub.compact .option-size { padding: 0; } + .download-hub.compact .option-actions { margin-left: 0; } + .download-hub.compact .option-actions a { min-width: 0; width: 100%; } + .download-option { grid-template-columns: minmax(0, 1fr) 70px 150px; gap: 16px; } +} +@media (max-width: 600px) { + .download-hub { background-image: linear-gradient(#faf8f330, #faf8f3aa), url('/images/review-download.webp'); } + .download-hub-inner { width: calc(100% - 40px); } + .download-hub-hero { gap: 14px; margin-bottom: 24px; } + .download-hub-hero > img { display: none; } + .download-hub-hero h1, .download-hub-hero h2, .download-hub.compact .download-hub-hero h2 { font-size: 30px; } + .download-hub-hero span { font-size: 15px; } + .download-hub-hero p { font-size: 22px; } + .release-status { flex-basis: 100%; font-size: 12px; } + .download-tabs { gap: 4px; } + .download-tabs button { min-width: 0; flex: 1; gap: 8px; padding: 14px 8px; font-size: 15px; min-height: 54px; } + .download-tabs .site-icon { width: 19px; height: 19px; } + .download-option { grid-template-columns: minmax(0, 1fr) auto; padding: 18px 0; gap: 10px 14px; } + .option-main { grid-template-columns: 30px minmax(0, 1fr); gap: 12px; } + .option-main h3 { font-size: 16px; } + .option-main > img { width: 28px; height: 28px; } + .option-size { grid-column: 1; padding-left: 42px; font-size: 13px; } + .option-actions { grid-column: 2; grid-row: 1 / 3; flex-direction: column; align-items: stretch; } + .option-actions a { padding: 11px 14px; font-size: 14px; } + .download-hub.compact .download-option-list, .download-hub.compact .mac-panel .download-option-list { grid-template-columns: 1fr; } + .download-hub.compact .download-option, .download-hub.compact .download-option:first-child { display: grid; grid-template-columns: minmax(0, 1fr) auto; border-left: 0; border-bottom: 1px solid #dedfd5; padding: 20px 0; } + .download-hub.compact .option-main { display: grid; } + .download-hub.compact .option-size { padding-left: 42px; } + .download-hub.compact .option-actions { margin: 0; } + .all-downloads { margin-top: 22px; } + .all-downloads a { font-size: 15px; } + .core-update { grid-template-columns: 28px minmax(0, 1fr); gap: 12px; } + .core-update div > span { display: block; margin: 6px 0 0; } + .core-update .option-actions { grid-row: auto; grid-column: 1 / -1; } + .core-update .option-actions a { width: 100%; } +} diff --git a/website/src/styles/review-site.css b/website/src/styles/review-site.css new file mode 100644 index 0000000..6ed0e97 --- /dev/null +++ b/website/src/styles/review-site.css @@ -0,0 +1,155 @@ +:root { + --review-paper: #faf8f3; + --review-green: #0b513f; + --review-ink: #102d25; + --review-line: #dfdfd4; + --review-serif: 'Noto Serif SC', 'Songti SC', 'Songti TC', 'Noto Serif CJK SC', 'Yu Mincho', Georgia, 'SimSun', serif; + font-family: 'PingFang SC', 'Microsoft YaHei', 'Hiragino Sans', 'Noto Sans', sans-serif; +} +body { background: var(--review-paper); } +button, summary, a { -webkit-tap-highlight-color: transparent; } +a:focus-visible, button:focus-visible, summary:focus-visible, [tabindex]:focus-visible { + outline: 3px solid #9a6a2e; outline-offset: 5px; +} +.site-icon { display: inline-block; vertical-align: middle; flex-shrink: 0; } +.site-shell { width: min(1296px, calc(100% - 96px)); } +.site-header { + width: auto; margin: 0 calc((min(1296px, 100vw - 96px) - 100vw) / 2); + padding: 8px max(48px, calc((100vw - 1296px) / 2)); + grid-template-columns: auto minmax(0, 1fr) auto; gap: 28px; + background: rgba(250, 248, 243, .97); min-height: 68px; + backdrop-filter: none; +} +.site-logo { min-width: 0; gap: 12px; font-size: 28px; letter-spacing: -.8px; } +.site-logo img { width: 42px; height: 42px; } +.site-header .desktop-nav { display: flex; justify-content: center; gap: clamp(16px, 2.2vw, 34px); } +.site-header .desktop-nav a { font-size: 15px; font-weight: 600; white-space: nowrap; padding: 12px 0; } +.site-header .desktop-nav a:hover { color: var(--review-green); text-decoration: underline; text-underline-offset: 7px; } +.header-actions { gap: 16px; } +.language-menu summary { min-height: 44px; gap: 10px; font-size: 14px; white-space: nowrap; } +.language-menu-list { min-width: 160px; right: 0; padding: 8px; } +.language-menu-list a { min-height: 40px; padding: 10px 12px; } +.mobile-nav { display: none; } +.full-bleed { width: 100vw; margin-inline: calc(50% - 50vw); } +.review-hero { background: var(--review-paper) url('/images/review-room.webp') center / cover no-repeat; border-bottom: 1px solid var(--review-line); } +.review-hero-inner { max-width: 1440px; margin: auto; padding: 90px 48px 52px max(48px, calc((1440px - 1296px) / 2)); display: grid; grid-template-columns: minmax(0, .82fr) minmax(0, 1fr); align-items: center; gap: 44px 32px; } +.review-intro { min-width: 0; animation: review-enter 600ms ease both; } +.review-eyebrow { margin: 0 0 14px; font: 700 29px/1.35 Georgia, serif; color: var(--review-green); } +.review-intro h1 { margin: 0; color: #072f25; font-family: var(--review-serif); font-weight: 700; font-size: clamp(42px, 4.5vw, 67px); line-height: 1.2; letter-spacing: -.035em; } +.review-intro h1 span { display: block; } +.review-intro h1 span, .review-workflow h2 { text-wrap: balance; } +.review-lead { margin: 28px 0 0; max-width: 31em; color: #444d46; font-size: 18px; line-height: 1.8; } +.review-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 20px 35px; margin: 34px 0 0; } +.review-download { display: inline-flex; align-items: center; justify-content: center; gap: 14px; min-height: 64px; padding: 18px 40px; color: white; background: var(--review-green); border-radius: 8px; font-weight: 650; font-size: 18px; box-shadow: 0 7px 14px #0b513f20; transition: background 180ms ease; } +.review-download .site-icon { filter: invert(1); } +.review-download:hover { background: #063c2e; } +.review-tour { display: inline-flex; align-items: center; gap: 12px; min-height: 44px; color: var(--review-green); font-weight: 600; text-decoration: underline; text-underline-offset: 8px; font-size: 16px; } +.review-platforms { margin: 27px 0 0; display: flex; align-items: center; flex-wrap: wrap; gap: 10px 18px; color: #59635b; font-size: 13px; } +.review-platforms > span { display: inline-flex; gap: 16px; } +.review-platforms .site-icon { width: 24px; height: 24px; } +.review-preview { margin: 0; min-width: 0; animation: review-enter 650ms 100ms ease both; } +.review-preview > a { display: block; border-radius: 5px; } +.review-preview img { width: 100%; height: auto; border-radius: 5px; border: 1px solid #9faaa1; box-shadow: 0 17px 30px #24281e32; } +.review-preview figcaption { width: fit-content; max-width: 100%; margin: 12px 0 0 auto; padding: 3px 7px; border-radius: 3px; text-align: right; font-size: 12px; color: #35453b; background: #faf8f3e8; } +.review-quote { margin: 4px 0 0; grid-column: 1 / -1; color: #275d4b; font: 20px/1.55 var(--review-serif); padding-left: 68px; border-left: 1px solid #bc9560; } +.review-quote span { display: block; } +.review-workflow { padding: 44px 0 42px; scroll-margin-top: 90px; } +.review-workflow > header { text-align: center; scroll-margin-top: 105px; } +.review-workflow h2 { margin: 0; font: 700 37px/1.3 var(--review-serif); letter-spacing: -.02em; color: #131d17; } +.review-workflow > header p { font-size: 16px; margin: 8px 0 0; color: #626b64; } +.review-steps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); list-style: none; margin: 32px 0 0; padding: 0; } +.review-steps li { display: grid; grid-template-columns: auto 1fr; gap: 26px; padding: 0 36px; border-left: 1px solid var(--review-line); } +.review-steps li:first-child { padding-left: 0; border-left: 0; } +.review-steps li:last-child { padding-right: 0; } +.step-number { font: italic 53px/1 Georgia, serif; color: #b18c58; } +.review-steps .site-icon { filter: invert(37%) sepia(29%) saturate(688%) hue-rotate(356deg) brightness(88%); } +.review-steps h3 { margin: 14px 0 8px; font-size: 20px; line-height: 1.4; color: #152b22; } +.review-steps p { margin: 0; color: #5f6a62; font-size: 15px; line-height: 1.8; } +.site-footer { display: block; padding: 34px 0 22px; gap: 0; border: 0; } +.footer-main { display: flex; justify-content: space-between; gap: 30px; align-items: center; } +.footer-brand { display: flex; align-items: center; gap: 14px; } +.footer-brand > img { border-radius: 10px; } +.footer-brand strong { font-size: 25px; color: #15221b; } +.footer-brand p { margin: 5px 0 0; font-size: 13px; color: #59655c; } +.site-footer .footer-links { justify-content: flex-end; gap: 20px; flex-wrap: wrap; max-width: 720px; } +.site-footer .footer-links a { min-height: 44px; display: flex; align-items: center; font-size: 13px; color: #43564b; } +.site-footer .footer-links a:hover { text-decoration: underline; text-underline-offset: 5px; } +.footer-meta { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--review-line); gap: 12px 26px; } +.site-footer .copyright { margin: 0; font-size: 12px; color: #667168; } +.footer-meta details { font-size: 12px; color: #52665b; } +.footer-meta summary { cursor: pointer; padding: 10px 0; } +.footer-meta p { margin: 4px 0; font-size: 12px; } +.footer-ai-note { font-size: 12px; margin: 10px 0 0; color: #626e66; } +@keyframes review-enter { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } } +@media (min-width: 1600px) { .review-hero-inner { padding-left: 72px; padding-right: 72px; } } +@media (max-width: 1120px) { + .site-header { gap: 16px; } + .site-logo { font-size: 23px; } + .site-header .desktop-nav { gap: 15px; } + .site-header .desktop-nav a { font-size: 14px; } + .review-hero-inner { padding: 65px 24px 38px 50px; gap: 44px 26px; grid-template-columns: minmax(0, .73fr) minmax(0, 1fr); } + .review-intro h1 { font-size: 53px; } + .review-lead { font-size: 16px; } + .review-actions { gap: 14px 24px; } + .review-download { padding: 16px 28px; min-height: 56px; } + .review-steps li { padding: 0 24px; gap: 18px; } + .review-steps p { font-size: 14px; line-height: 1.65; } + .footer-main { align-items: flex-start; } +} +@media (max-width: 900px) { + .site-shell { width: calc(100% - 48px); } + .site-header { margin-inline: -24px; padding: 10px 24px; min-height: 74px; grid-template-columns: 1fr auto; } + .site-header .desktop-nav { display: none; } + .site-header .mobile-nav { display: block; } + .mobile-nav > summary { list-style: none; display: flex; justify-content: center; align-items: center; width: 44px; height: 44px; cursor: pointer; } + .mobile-nav > summary::-webkit-details-marker { display: none; } + .mobile-nav nav { position: absolute; top: 100%; left: 0; right: 0; padding: 16px 24px; background: var(--review-paper); border-bottom: 1px solid var(--review-line); display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px 24px; box-shadow: 0 14px 22px #20342513; } + .mobile-nav nav a { min-height: 44px; display: flex; align-items: center; padding: 8px 10px; font-size: 16px; } + .review-hero-inner { grid-template-columns: 1fr; padding: 44px 24px 30px; gap: 34px; } + .review-intro { max-width: 620px; } + .review-intro h1 { font-size: clamp(38px, 6.7vw, 59px); } + .review-eyebrow { font-size: 25px; } + .review-preview { max-width: 720px; } + .review-quote { font-size: 18px; padding-left: 25px; } + .review-workflow { padding: 38px 0; } + .review-workflow h2 { font-size: 30px; } + .review-steps { gap: 26px; margin-top: 30px; } + .review-steps li { display: block; padding: 0 16px; } + .step-number { display: block; margin-bottom: 18px; font-size: 38px; } + .review-steps h3 { font-size: 18px; } + .footer-main { flex-direction: column; } + .site-footer .footer-links { justify-content: flex-start; gap: 4px 20px; } +} +@media (max-width: 540px) { + .site-shell { width: calc(100% - 40px); } + .site-header { margin-inline: -20px; padding: 8px 20px; min-height: 70px; } + .site-logo { font-size: 23px; gap: 9px; } + .site-logo img { width: 35px; height: 35px; } + .header-actions { gap: 8px; } + .language-menu summary { padding: 8px; } + .language-menu summary > span, .language-menu summary .chevron { display: none; } + .review-hero-inner { padding: 35px 20px 24px; } + .review-intro h1 { font-size: clamp(33px, 8.5vw, 45px); line-height: 1.28; } + .review-eyebrow { font-size: 23px; margin-bottom: 12px; } + .review-lead { margin-top: 20px; line-height: 1.75; } + .review-actions { margin-top: 24px; gap: 12px 20px; } + .review-download { padding: 15px 26px; font-size: 16px; } + .review-tour { font-size: 14px; gap: 7px; } + .review-platforms { font-size: 12px; gap: 10px; margin-top: 20px; } + .review-platforms > span { gap: 10px; } + .review-platforms .site-icon { width: 18px; height: 18px; } + .review-quote { font-size: 16px; } + .review-workflow h2 { font-size: 28px; } + .review-workflow > header { text-align: left; } + .review-workflow > header p { font-size: 15px; } + .review-steps { grid-template-columns: 1fr; gap: 0; } + .review-steps li, .review-steps li:first-child, .review-steps li:last-child { display: grid; grid-template-columns: 48px 1fr; padding: 24px 0; border-left: 0; border-bottom: 1px solid var(--review-line); gap: 18px; } + .review-steps li:last-child { border-bottom: 0; padding-bottom: 0; } + .review-steps li .site-icon { width: 27px; height: 27px; } + .review-steps h3 { margin: 10px 0 6px; } + .review-steps p { font-size: 15px; } +} +@media (prefers-reduced-motion: reduce) { + html { scroll-behavior: auto; } + *, *::before, *::after { animation: none !important; transition: none !important; } +} diff --git a/website/tests/build.test.mjs b/website/tests/build.test.mjs new file mode 100644 index 0000000..04d16df --- /dev/null +++ b/website/tests/build.test.mjs @@ -0,0 +1,42 @@ +import test from 'node:test' +import assert from 'node:assert/strict' +import { existsSync, readFileSync, readdirSync } from 'node:fs' +import { resolve, dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { localeSlugs } from '../src/data/home-copy.mjs' + +const dist = resolve(dirname(fileURLToPath(import.meta.url)), '../dist') +const read = path => readFileSync(join(dist,path),'utf8') +const walk = dir => readdirSync(dir,{withFileTypes:true}).flatMap(e=>e.isDirectory()?walk(join(dir,e.name)):[join(dir,e.name)]) + +test('built homepage and download routes share the official catalog in seven languages', () => { + for (const [lang, slug] of Object.entries(localeSlugs)) { + const prefix = slug ? `${slug}/` : '' + for (const path of [`${prefix}index.html`,`${prefix}download/index.html`]) { + const html=read(path) + assert.ok(html.includes(`lang="${lang}"`),path) + assert.ok(html.includes('https://download.goagent.top/channels/stable/catalog.json'),path) + assert.ok(!html.includes('/__dev/'),path) + assert.equal((html.match(/)/g)||[]).length,1,path) + assert.ok(html.includes('data-platform-tab="linux"'),path) + assert.ok(html.includes('data-download-retry'),path) + assert.ok(html.includes('