# 穿山甲 · Pangolin 官网（Astro 纯静态 / SSG）

获客入口官网，从 `design/ui_kits/website/` 迁移到 **Astro 纯静态站点（零 SSR）**。
设计目标：极致加载性能 + **整站可秒级复制到任意备用域名**（灾备关键，doc/01 §1、doc/05）。

- 完全静态、无 API 依赖、无第三方统计/CDN（隐私承诺）。
- i18n 双路由单显：`/`（中文）、`/en/`（English），不并排（设计铁律 6）。
- 与后端全部任务解耦，可独立并行开发与部署。

---

## 快速开始

```bash
cd web/website
npm ci            # 干净、可复现安装（含自托管字体 @fontsource、Astro、React）
npm run dev       # 本地预览 http://localhost:4321
npm run build     # 产物到 dist/（含 token 同源生成 + CSP 哈希注入）
npm run preview   # 预览构建产物
```

要求 Node `^18.20.8 || ^20.3.0 || >=22.0.0`。

### 常用脚本
| 命令 | 作用 |
|---|---|
| `npm run build` | 构建静态产物到 `dist/`（`prebuild` 生成令牌，`postbuild` 注入 CSP 哈希） |
| `npm run lint` | `astro check` 类型检查 |
| `npm run scan:redline` | 扫描 `dist/` 红线词（脱敏铁律 13） |
| `npm run hash:dist` | 打印 `dist/` 确定性指纹（主站/镜像一致性比对） |
| `npm test` | 一键：生成令牌 → 构建 → CSP 哈希 → 红线扫描 |

---

## 目录结构

```
web/website/
├─ src/
│  ├─ layouts/Site.astro        整页布局（按 lang 单显），两路由复用
│  ├─ pages/index.astro         /     → lang="zh"
│  ├─ pages/en/index.astro      /en/  → lang="en"
│  ├─ components/
│  │  ├─ *.jsx                  交互组件（React island，@astrojs/react）：
│  │  │                         Header / AnnouncementBar / SignupForm / PricingPlans
│  │  ├─ *.astro                纯展示块（构建期渲染，零运行时 JS）
│  │  ├─ Icon.astro             Lucide 图标 → 构建期内联 SVG（替代原型的 unpkg CDN）
│  │  └─ Brand.astro            品牌「行走穿山甲」内联 SVG
│  ├─ i18n/strings.ts           中/EN 字典（逐字沿用 ui_kits/website/site.js 脱敏文案）
│  └─ styles/
│     ├─ tokens.gen.css         由 design/colors_and_type.css 生成（见下）
│     ├─ website.css            逐字迁自 ui_kits/website/website.css
│     └─ site-extra.css         仅承载「原型内联 style= → 等价 class」
├─ public/_headers              Cloudflare Pages 安全头（严格 CSP + HSTS）
├─ scripts/
│  ├─ build-tokens.mjs          令牌同源生成器
│  ├─ csp-hashes.mjs            构建后注入内联片段 sha256 到 CSP
│  ├─ check-redline.mjs         红线词扫描
│  └─ dist-hash.mjs             产物指纹
└─ .gitea/workflows/website.yml CI：构建+校验 → 同时发布主站与镜像
```

---

## 关键决策（务必理解）

### 1. 令牌同源（单一真相源）
颜色/字体/圆角/阴影等令牌的唯一真相源是仓库根 **`design/colors_and_type.css`**（铁律 1）。
`scripts/build-tokens.mjs` 在每次构建前把它读入并写出 `src/styles/tokens.gen.css`，
**只移除其中加载 Google Fonts 的 `@import url('https://fonts.googleapis.com/...')` 一行**
（任务硬性要求：不引第三方字体 CDN）。**令牌数值一字未改**，仍是单一来源、可随时再生。
> 若只拷贝了 `web/website/` 子目录到干净环境（源文件缺失），生成器自动沿用已提交的
> `tokens.gen.css`，构建照常。

### 2. 字体全部自托管
原型用 Google Fonts CDN；本站改为 **`@fontsource/*`** 本地打包
（Sora / Manrope / Noto Sans SC / JetBrains Mono），woff2 随 `npm ci` 落地、
经构建产出到 `dist/_astro/`，由站点自身提供（`font-src 'self'`）。
**无任何第三方 CDN 请求** —— 性能 + 隐私 + 可达性。

### 3. 安全头（严格 CSP / HSTS）
`public/_headers` 下发严格 CSP：`default-src 'self'`，`script-src`/`style-src`/`font-src`/
`img-src`/`connect-src` 全部收敛到 `self`（白名单仅 self + 自托管资源），外加 HSTS（含 preload）、
`X-Content-Type-Options`、`X-Frame-Options: DENY`、`Referrer-Policy`、`Permissions-Policy`。
Astro island 水合产生的少量**内联** `<script>`/`<style>` 由 `postbuild` 的 `csp-hashes.mjs`
以 `'sha256-...'` 精确放行，**不开 `'unsafe-inline'`**。

curl 验证（部署后）：
```bash
curl -sI https://<域名>/ | grep -iE 'content-security-policy|strict-transport-security'
```

### 4. 脱敏红线（铁律 13）
任何页面不得出现红线词（VPN/翻墙/科学上网/突破封锁/自由穿越/Go anywhere）。
`scripts/check-redline.mjs` 扫描 `dist/**/*.html`（含正文、属性、aria-label、meta），
例外白名单与仓库 `ci/scan-redline.sh` 一致（外部渠道 handle `@PangolinVPN_bot`/`@pangolinvpn`）。
CI 命中即失败。演示渠道/价格为占位，落地替换（见 design/CLAUDE.md §8）。

### 5. 无支付表单
定价区 CTA 一律「获取激活码 / 免费下载」，仅引导到外部渠道，页面内无任何支付表单（铁律 10）。

---

## 部署：Cloudflare Pages 主站 + ≥1 活跃镜像

主站与镜像各是一个 Cloudflare Pages 项目，**部署同一份 `dist/`**，绑定不同域名
（域名归属 #16 域名池体系）。镜像用于「主域名被墙」场景（doc/05 Runbook）。

手动部署（任一项目）：
```bash
cd web/website
npm ci && npm run build
npx wrangler pages deploy dist --project-name <PAGES_PROJECT> --branch main
```

CI（`.gitea/workflows/website.yml`）：`push web/website/**` → 构建（红线 + lint + CSP 哈希）
→ 同时 `wrangler pages deploy` 主站与镜像。需配 Secrets：`CF_API_TOKEN` / `CF_ACCOUNT_ID` /
`CF_PAGES_PROJECT_MAIN` / `CF_PAGES_PROJECT_MIRROR`，并把 `runs-on` 改成实际 runner label。

> `_headers`（CSP/HSTS）由 Cloudflare Pages 原生支持；换其它静态托管时需用各自机制下发等价头。
> 主站与镜像构建时 **SITE_URL 取同值**，使 canonical 始终指向主站（避免 SEO 竞争），
> 且两边产物 hash 完全一致。

### 主站 ↔ 镜像内容一致性校验
产物为内容哈希、构建确定性，相同源码 → 相同 `dist` → 相同指纹：
```bash
npm run hash:dist     # 主站与镜像两路构建后比对此值，应完全相同
```

---

## 灾备重放（RTO 秒级）

从**任意干净环境**（仅需 Node + 本目录）即可重建并部署到**任意**静态托管/新域名：

```bash
# 1) 取本目录（git 仓库即备份）
git clone <repo> && cd <repo>/web/website
# 2) 干净安装 + 构建（无网络外部依赖，除 npm registry）
npm ci
npm run build
# 3) 把 dist/ 整目录上传到任意静态托管 / 新 Pages 项目 / 对象存储+CDN，绑定新域名即可。
#    切换镜像/新域名只改「绑定」，无需改代码。
```

- 产物是纯相对路径静态文件，不依赖特定域名，可直接放到任意主机。
- 严格 CSP 全部 `self`，换域名无需调整。
- `_headers` 随 `dist/` 一并产出（Cloudflare Pages 自动识别）。

### 演练记录
- **2026-06-13**：在本仓库 worktree 干净环境执行 `npm ci && npm run build` 跑通，
  `dist/` 产出 2 个页面（`/`、`/en/`）+ 自托管字体 + `_headers`；两次连续干净构建
  `npm run hash:dist` 指纹一致（`c1f6691e…cb65`），验证可复现 / 主站镜像一致性。
  上传 `dist/` 到新静态托管为纯文件拷贝，<10 分钟可完成（受限于上传带宽，无构建/配置阻塞）。
