Chapter 01 · Frontend

# 前端架构

原则：性能、稳定性、用户体验优先；不同平台允许不同技术栈，但视觉与交互必须 100% 还原 `design/` 设计系统——以 `ui_kits/` React 原型为像素验收标准，以 `colors_and_type.css` 为令牌唯一真相源。

## §1分端技术栈选型

端 | 技术栈 | 选型理由（性能 / 稳定 / UX） | 像素基准
--- | --- | --- | ---
移动 App iOS / Android | Flutter + sing-box `libbox`（gomobile 桥接） iOS NetworkExtension / Android VpnService | 需要系统级隧道能力，Web 栈不可行；Flutter 自绘渲染保证两端像素一致，`design/flutter/` 起步包（theme + widgets + main）拿来即跑。 | `ui_kits/mobile/`
iPad 客户端 | Flutter——与移动 App 同一工程，宽度 ≥900 自适应切换为侧栏分栏布局 | 不是第五套代码：复用移动端全部原子组件与内核层，仅布局开关；横屏 1180×820 基准，左侧栏导航（触控尺寸，行高 ≥48px），连接页双栏（左大连接键 / 右信息列），节点双列网格。 | `ui_kits/tablet/`
桌面客户端 Windows / macOS | Flutter desktop + sing-box 子进程（TUN 模式） | 与移动端共享 ~90% UI 代码（连接键、节点列表、统计、账户全复用），920×600 固定窗 + 侧栏布局照搬原型；避免 Electron 的内存与启动开销。 | `ui_kits/desktop/`
官网 | Astro（SSG）→ Cloudflare Pages | 纯静态 = 极致加载性能 + 整站可秒级复制到任意备用域名（灾备关键，见 05 章）；Astro 直接吃 JSX，`ui_kits/website` 组件近乎逐字迁移。 | `ui_kits/website/`
Web 用户中心 | Next.js（React，静态导出 + 客户端取数） | `ui_kits/usercenter` 本身是 React——组件直接复用是保真上限；静态导出后同样可多镜像部署，动态数据全走 API。 | `ui_kits/usercenter/`

为什么不强行统一一个栈 客户端离不开系统级隧道与开机自启，必须是原生壳；Web 端的两个原型本身就是 React，重写成 Flutter Web 反而损失保真与首屏性能。五个产品面、四套实现（iPad 归入移动端工程的自适应断点），通过同一套设计令牌（CSS 变量 ↔ `pangolin_theme.dart` 一一对应）保证视觉同源——栈不同，像素相同。

## §2客户端分层架构（移动 + 桌面共享）

UI 层（共享 ~90%）flutter/widgets/：连接键三态 · 智能选择推荐卡 · 免费额度卡 · 节点列表 · 统计 · 账户；明暗双主题 + 中英单显；宽度 ≥900 自适应切侧栏分栏布局（iPad / 桌面同构）
状态层Riverpod：连接状态机（off / connecting / on）· 会话 · 节点目录（带 version）· 用量额度
服务层API client（域名池 + 重试退避 + 响应签名校验）· 节点目录缓存 · 订阅凭证存储（Keychain / Keystore）
内核层sing-box libbox（移动：gomobile AAR/XCFramework；桌面：子进程 + TUN）· URLTest 智能选线 · Kill-switch
平台壳iOS NetworkExtension · Android VpnService（前台服务）· macOS/Win TUN 设备 + 开机自启
UI 与内核之间仅通过状态层通信；内核崩溃不拖垮 UI，UI 重启不掉隧道

- 连接状态机是核心：UI 三态（off / connecting / on）严格对应内核事件，禁止 UI 侧"乐观显示已连接"。
- 智能选择（默认选中）：sing-box URLTest 组在本地探测延迟自动选优，节点列表置顶推荐卡与原型一致。
- 免费版额度：本地倒计时仅作展示，权威额度以 API 为准（见 02 章 `/v1/ads/unlock` 与 connect 校验）。
- 默认安全：Kill-switch 默认开启；DNS 走 DoH 防泄露；阻断 WebRTC 直连泄露（桌面端注入策略）。

## §3100% 还原策略

### 3.1 令牌同源

端 | 令牌载体 | 同步方式
--- | --- | ---
官网 / 用户中心 | `colors_and_type.css` 直接链入 | 原样引用，不复制不改写
移动 / 桌面 | `flutter/pangolin_theme.dart` | 与 CSS 一一对应的 Dart 镜像；改令牌必须两处同改（design/CLAUDE.md §6）

### 3.2 像素基准与组件对照

每个界面动手前先打开对应 React 原型比对；以下关键组件逐一对照验收：

- 核心连接键：off 暖灰底 + 虚线轨道环 / connecting 旋转弧 / on 绿底满环 + 圆内计时 + 光晕（过渡只用 `box-shadow`/`background-color`，禁 `transition: all`）
- 智能选择推荐卡：常驻 accent-subtle 底 + clay 渐变 zap 图标 + 「推荐」胶囊，默认选中
- 免费额度卡：剩余分钟 + 进度条（≤3 分钟变 warning 色）+「看广告开始使用」→ 解锁后变绿
- 国家码块（2 字母，无 emoji 国旗）、状态胶囊（色点 + 文字，无 emoji）、Lucide 细线图标
- Tab 滑动切换：>60px 且横向位移明显大于纵向，200ms 方向感知滑入；子页不响应
- iPad 分栏布局（对照 `ui_kits/tablet/`）：左侧栏导航行高 ≥48px 触控尺寸；连接页双栏（左大连接键 / 右额度卡→当前节点→实时速率）；节点页置顶智能推荐卡 + 双列网格（同桌面）

### 3.3 提交前验收清单（每个界面，引用 design/CLAUDE.md §9）

- 颜色全部来自语义 token，无硬编码十六进制
- 明 / 暗两主题、中 / 英两语言（单显不并排）共四态验证
- 文案无铁律 13 红线词；套餐数字与 design/CLAUDE.md §7 一致
- App / 官网内无任何支付表单，购买只引导到外部渠道

## §4客户端断网弹性

设计目标：API 全灭、域名全被污染时，客户端仍能连上节点。详见 06 章断网应对矩阵，此处为客户端侧实现。

机制 | 实现 | 覆盖的故障
--- | --- | ---
节点目录缓存 | 每次成功拉取 `/v1/nodes` 后落盘（带 version 与时间戳）；启动时先用缓存渲染并尝试连接，后台再刷新 | API 暂时不可达
API 端点池 | 客户端内置：主域名 + 备用域名 N 个 + IP 直连兜底；按序故障转移，成功的端点置顶记忆 | 单个域名被墙 / 被污染
DoH 解析 | 域名解析优先走 DoH（多个提供方），绕开本地污染 | DNS 污染
签名端点更新 | 定期从多镜像静态文件（Cloudflare Pages / GitHub 等）拉取 Ed25519 签名的端点列表，验签后合并进端点池 | 内置端点全部失效
紧急逃生配置 | 安装包内置 1–2 个应急节点参数（低速、仅够拉新目录），所有在线途径失效时启用 | 极端封锁
应急公告 | 客户端内公告位从签名静态 JSON 读取（多镜像），可引导用户更新或切换渠道 | 需要人工广播时

注意所有兜底参数（IP、应急节点、签名公钥）属于敏感资产：随版本轮换、按渠道分包（不同分发渠道内置不同 IP 子集），泄露一个渠道不烧全部。