Files
pangolin/design/CONTRACT.md
wangjia 39324c46a7 feat(ds-flow): Phase 3 — Web 硬编码色清零 + 原子台账收敛
「各自实现 + 同源闸」决策落地(不建跨端组件包):

- website.css 页脚恒暗区灰阶就近吸附主题无关 sand 原色阶(cfc6b8→sand-300、
  a89e8e→sand-400、8a8070→sand-500),消除 5 处硬编码。
- check-l1-sync 白名单加品牌 logo 色 f4efe8(Brand.astro 内联 SVG fill)。
- usercenter 13 处硬编码全为 #fff(白名单),已干净。
- CONTRACT.md 增「§6 ds-flow Web 原子清单 + 屏级三态台账」:公用原子两端映射表、
  图标三端⊆原型、L2 屏级三态(Flutter 快照/Web 代码先行/原子层同步)、硬编码白名单。

同源闸 4 道全绿(token 值 · 图标⊆原型 · Web 无硬编码色 · 幂等零 diff)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 01:09:16 +08:00

123 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 设计契约 · iPad / Tabletdesign-distill
> 像素基准:`design/ui_kits/tablet/tabapp.jsx`1180×820 横屏侧栏布局)+ `index.html`。
> 数值取自原型源码,非目测。token 单源:`design/colors_and_type.css` → `client/lib/pangolin_tokens.gen.dart`(勿手改)。
> 目标实现:`client/lib/shell/tablet_shell.dart` + 四页 `isWide` 分支 + `client/lib/widgets/`。
> 验收闸:`client/test/golden/tablet_pages_golden_test.dart`1180×820 / iOS 平台 → formFactor=tablet)。
## 0. 归一化产物
- 类型 A(结构化包):token 单源已存在、codegen 已接(`design/codegen/gen_flutter_tokens.mjs`)。本契约只补 **tablet 页面像素规格 + 验收清单**
- 断点:触屏宽 **≥900 → tablet 侧栏**,否则 mobile 底 Tab`client/lib/core/responsive/form_factor.dart`,对齐 README「≥900 切侧栏」)。
## 1. token 映射(复用既有语义层)
| 原型 CSS var | Flutter 语义层 |
|---|---|
| `--bg` / `--bg-subtle` | `c.bg` / `c.bgSubtle` |
| `--surface` | `c.surface` |
| `--fg1/2/3` | `c.fg1/fg2/fg3` |
| `--accent` / `--accent-subtle` / `--accent-border` | `c.accent` / `c.accentSubtle` / `c.accentBorder` |
| `--success` `--warning` | `c.success` `c.warning` |
| `--border` / `--border-strong` | `c.border` / `c.borderStrong` |
| `--radius-{sm,md,lg,xl,full}` | `PangolinRadius.{sm,md,lg,xl,full}` |
| `--shadow-{sm,md,lg}` | `PangolinShadow.{sm,md,lg}` |
| `--font-{display,sans,mono}` | `PangolinText.{h*/body...}` / `PangolinText.mono` |
## 2. 组件映射
| 原型 | Flutter | 状态 |
|---|---|---|
| `TabRail` 侧栏 | `TabletShell` + `widgets/nav_sidebar.dart` | 已接,宽 232、行高≥48 |
| 连接键三态 | `widgets/connect_button.dart` | 复用(off 态 canonical 走「虚线轨道环」,见 design/CLAUDE.md §5,优先于原型实心环简化) |
| 免费额度卡 | `widgets/quota_card.dart` | 复用 |
| 智能选择卡 | `widgets/smart_select_card.dart` | 复用 |
| 节点行 | `widgets/server_tile.dart` / `nodes_page` `_NodeGridTile` | 双列网格 |
| 指标卡 | `stats_page` `_MetricCard` | 复用 |
| 段控/开关/行 | `account_page` 内组件 | 复用 |
| Toast / 通知条 | `widgets/pangolin_toast.dart` `showPangolinToast` | 新增组件(无原型) |
### Toast / 通知条规格
跨平台轻提示(如自动连接「正在自动连接到 X」)。**无 React 原型 → 此处即真相源**;全部走语义 token,明暗自动适配。
**外观**:底 `--surface`(浅=白 / 深=`#221E19`)+ 文字 `--fg1`(Manrope 14/600,居中)+ 描边 `--border` 1px + 圆角 `md`(10)+ 柔和暖阴影(`shadow-sm`);淡入并轻微上移(240ms ease-out)。绝不用 Material 默认黑底。
**布局**(铁律):
- **只在主显示区内**,不覆盖左侧栏(left = 侧栏宽:desktop 204 / tablet 232 / mobile 0)。
- **宽度自适应内容**,上限 = 主显示区宽度的 **80%**;超过则**文本多行**。
- 在主显示区内**水平居中**,贴底(bottom 28)。
- Canonical 实现:`widgets/pangolin_toast.dart` `showPangolinToast(context, msg)`(Overlay 实现,非 SnackBar,以完全控制上述布局)。`pangolin_theme``snackBarTheme` 只作普通 SnackBar 的兜底底色样式。
## 3. 页面像素规格(取自 tabapp.jsx
### 外壳 TabletApp
- 内屏 1180×820`data-theme` 切明暗。侧栏 + 内容列(顶栏 56 + 主体)。
- 内容顶栏:高 56padding `0 28`,标题 `font-display 700 / 19 / fg1`,右侧连接状态(mono 12.5on=success「● {code}」/ off=fg3「○」)。
### TabRail 侧栏
- 宽 232`bg-subtle`,右 1px `border`padding `18 14 16`
- 品牌头:Mark 30 + 「穿山甲」(display 700/17) + 「PANGOLIN」(9/600/字距0.2em/accent)gap 10padding `0 8 20`
- 导航项:gap 4;每项 padding `13 14`、radius md、字号 15、active 700+accent+accent-subtle 底 / inactive 500+fg2、**minHeight 48**、图标 20active stroke 2.4)、gap 12。
- 底部套餐徽章:free=surface+border / pro=`linear-gradient(150deg,clay-600,clay-800)`+白字;radius lgpadding `12 13`;头像圈 32 + 名(12.5/700) + 邮箱(10.5/opacity.65)。
### 连接视图 TabConnect(双栏)
- 左列 flex **1.25**,居中,gap 26:按钮 216×216 圆、`box-shadow 0 0 0 11px {ring}, shadow-lg`、图标 60、label mono 700/14/字距0.08em;下方 caption(16/600/fg2)on 态计时 mono 28/fg1。
- 右列宽 **332**gap 14,垂直居中,paddingRight 28
- 免费额度卡(card padding `14 16`)clock + 「今日剩余 {n} 分钟」+ 「免费版」pill + 进度条(高6,≤3 变 warning) + 看广告按钮(accent-subtle/accent-border/full/minHeight44)。
- 当前节点卡(padding `13 15`, minHeight 48)CC 码块 + 「当前节点」(11/600/fg3) + 名(15/600) + 副(12/fg3) + chevron。
- on 态速率行:3 卡(下载/上传/延迟),各 padding `11 13`、值 mono 16。
### 节点视图 TabNodes
- padding `6 28 24`overflow auto。
- 智能卡:accent-subtle 底、border(active=accent)、radius xl、padding `15 16`、minHeight 56、marginBottom 14zap 在 clay 渐变盒 42×42 + 「智能选择」(15.5/700) + 「推荐」pill + 副(12/fg2) + active check。
- 搜索框:bg-subtle、radius md、padding `11 14`、maxWidth 380、marginBottom 14。
- 网格:**2 列 `1fr 1fr`gap 11**;每格 padding `13 15`、minHeight 56、active=accent-subtle+accent-borderCC + 名(14.5/600) + 副 + ping(mono 12) + Signal + active check。
### 统计视图 TabStats(上聚合 · 下分设备)
- padding `6 28 24`
- **上聚合**3 指标卡一行,gap 14marginBottom 20;各 padding `18 20`label(12.5/fg3/600) + 值(mono 26/fg1)。周柱卡 padding `20 22`:标题(13.5/600) + 7 柱(gap 22, 容器高150, 柱 maxWidth 40, accent opacity .85, radius `6 6 0 0`) + 值(mono 10.5) + 星期(11.5)。
- **下分设备**「设备明细 / By device」(统一组件 `DeviceStatRow`,四端复用):周柱卡下 marginTop 20,标题(13/600/fg1);设备卡 surface+border+radius-lg+shadow-sm,行内分隔线 border。每行 padding `14 16/15 18`:左平台图标盒 38×38(accent-subtle, radius md, Lucide smartphone/laptop/monitor-smartphone, clay 2.2px) + 中(名 14.515/600 省略号 + 占比迷你条 height 4 radius 999, track=border, fill=accent .85, 宽=本设备流量/最忙设备) + 右(流量 mono 14.515 + " GB" caption;时长 caption "X h")。流量降序;空数据显示占位「暂无设备用量 / No device usage yet」。
- 数据源:`GET /v1/usage/devices?days=N`(账户内每设备窗口聚合,busiest-first)→ `deviceUsageProvider`
### 账户视图 TabAccount
- padding `6 28 24`maxWidth 640(生产页功能更丰富,沿用同设计语言:套餐横幅 + 分区卡 + 行 + 段控/开关)。
- 套餐横幅 radius xl padding 20;行 minHeight 48,图标盒 34 radius sm accent-subtleTabToggle 48×29(on=accent)。
## 4. 逐屏验收清单(每屏 × 明/暗 × 中/英)
- [ ] 连接 / 节点 / 统计 / 账户 四视图 × light/dark × zh/en:无溢出、侧栏 232 比例正确、触控目标 ≥48。
- [ ] 连接页双栏左右比例 ≈ 1.25 : 332;on 态速率行 3 卡出现。
- [ ] 节点页 2 列网格 + 置顶智能卡;搜索框 maxWidth 380。
- [ ] 颜色全部语义 token、无硬编码 hexdesign/CLAUDE.md §1)。
- [ ] 文案无红线词(§13)、双语单显不并排。
- [ ] golden 回归闸 `tablet_pages_golden_test.dart` 通过;真机/模拟器 mini/11″/12.9″ 横竖屏目测过。
## 5. 已知差异(记录,非缺陷)
- 连接键 off 态:原型用实心 11px 环,Flutter 用「虚线轨道环」(design/CLAUDE.md §5 canonical 连接键),以 §5 为准。
- 账户页:生产实现比原型多设备管理/兑换/联系/协议行,属真实功能扩展,沿用同设计语言。
- golden 测试环境未打包 Noto Sans SC → CJK 显示为方块,仅影响 golden 文字、不影响真机;布局可判。节点网格/周柱在未登录测试态无数据(需注入演示数据方显内容)。
---
## 6. ds-flow · Web 原子清单 + 屏级三态台账(2026-07 治理)
> 决策:Web 两端(website / usercenter)框架不同(Astro-island vs Next 静态),**各自实现 + 同源闸**——不建跨端共享组件包,两端对齐同一 `design/prototype/atoms.css` 语义,靠 `tools/check-l1-sync.mjs` 强制 token 值 + 图标同源不漂移。
### 6.1 公用原子清单(canonical = `design/prototype/atoms.css`
| 原子 | 原型 class | website 实现 | usercenter 实现 |
|---|---|---|---|
| 按钮 | `.btn`/`-primary`/`-ghost`/`-subtle`/`-danger` | `website.css .btn*`class | `shared.tsx`CSSProperties |
| 卡片 | `.card` | `.card`/`.plan` | `shared.tsx card` |
| 输入 | `.input`/`.field` | 表单 class | `shared.tsx input` |
| 徽章/药丸 | `.pill`/`.badge`(状态/accent/outline | `.tag`/`.plan` 徽章 | 内联 pill |
| 语言下拉 | `.langsel`/`.menu` | `Header.jsx` 自控下拉 | `shared.tsx LangSeg` |
> 两端语言下拉已统一为「自控菜单」(非原生 select,防 macOS 弹层漂移),行为一致、均对齐 `.langsel`/`.menu` 语义。
### 6.2 图标:三端 ⊆ 原型 sprite
`design/prototype/icons.js`58 Lucide)是图标单源。usercenter `LUCIDE`(键+路径)、Flutter `pangolin_icons._byName`(键)由 `check-l1-sync ③` 强制 ⊆ 原型;website 经 lucide-react 构建期内联。
### 6.3 屏级三态台账(L2
| 面 | 态 | 基准 | 说明 |
|---|---|---|---|
| Flutter mobile/tablet/desktop | 快照 | golden ×light/dark | 原型退役,golden + 本契约文字为准 |
| website(官网各屏) | 代码先行 | 无原型屏 | canonical 在 `web/website/`token/图标同源闸守护 |
| usercenter(各视图) | 代码先行 | 无原型屏 | canonical 在 `web/usercenter/`,同上 |
| 组件原子层 | 同步 | `prototype/index.html` 登记页 | L1 无例外先原型;`check-ds`Phase 5)强制登记 |
### 6.4 硬编码色白名单(`check-l1-sync ④`
`#fff/#ffffff/#000/#000000` + 品牌 logo 固定色 `#B96A3D/#FAF3ED/#F4EFE8/#9E5630/#3D2213`;其余一律 `var(--token)` 或行内 `ds-allow` 豁免。website 页脚恒暗区灰阶已就近吸附 `--sand-300/400/500`