diff --git a/design/CLAUDE.md b/design/CLAUDE.md
index 6ad46dc..bff10e7 100644
--- a/design/CLAUDE.md
+++ b/design/CLAUDE.md
@@ -96,7 +96,7 @@ SKILL.md ← Agent Skill 入口
- 登录/注册(`AuthFlow` / `auth_screen.dart`):登录=邮箱+密码;注册=邮箱→发验证码→填码→设密码(多端登录)
- 首次引导(`Onboarding` / `onboarding_screen.dart`):3 屏(连接全球→授权配置→安全无日志),可跳过
- 主框架 4 Tab(`home_shell.dart`):**连接 / 节点 / 统计 / 账户**;内容区支持**左右滑动切换 Tab**(>60px 且横向位移明显大于纵向,200ms 方向感知滑入;子页不响应)
- - 连接:核心连接键三态(off 暖灰底+虚线轨道环 / connecting 旋转弧 / on 绿底+满环+计时)+ 当前节点卡 + 实时速率;**免费版额外显示额度卡**(今日剩余分钟 + 进度条(≤3 分钟变 warning 色)+ 「看广告开始使用」→ 点击后变绿色「已解锁 · 今日可用」)
+ - 连接:核心连接键**四态**(off 暖灰底+虚线轨道环 / connecting 旋转弧 / on 绿底+满环+计时 / **disabled 免费到点切断:暖灰底+lock 图标+not-allowed,点击弹「看广告加时」**)+ 当前节点卡 + 实时速率;**免费版额外显示额度卡**(今日剩余分钟 + 进度条(≤3 分钟变 warning、耗尽变 danger)+ 「看广告加时」;**连接期用倒计时收缩条表达剩余,无「已解锁」绿态** —— 旧「点击后变绿色已解锁」描述已废,以线上 `quota_card.dart` 为准)
- 节点:顶部置顶**「智能选择」推荐卡**(常驻 accent-subtle 底 + clay 渐变 zap 图标 + 「推荐」胶囊 + 文案「根据当前网络环境,自动选择最优节点」,默认选中);其下才是搜索 + 节点列表(国家码块·延迟·信号条·选中态)
- 统计:流量/延迟/时长指标卡 + 本周柱状图
- 账户:套餐横幅(免费版/PRO 自适应)+ 账户信息(邮箱/密码/退出)+ 设备管理 + 兑换/联系入口
@@ -108,10 +108,11 @@ SKILL.md ← Agent Skill 入口
**Web 用户中心**(`ui_kits/usercenter/`):概览(套餐状态/用量)· 订阅导入(订阅链接+二维码+一键导入三方客户端)· 兑换 · 邀请返利 · 设置(含 **2FA/TOTP 开关**,用户可自选开启)。响应式,移动端底部 Tab + 左右滑动切换。
-**核心连接键**(最重要的组件,务必还原):圆形,三态——
-- off:暖灰底 `sand-100` + clay power 图标 + 虚线轨道环 + 「点击连接」
+**核心连接键**(最重要的组件,务必还原):圆形,**四态**(对齐线上 `connect_button.dart`)——
+- off:暖灰底 `bg-subtle` + clay power 图标 + 虚线轨道环 + 「点击连接」
- connecting:clay 实底 + 旋转进度弧 + 旋转 loader
- on:success 实底 + 白盾勾 + 满白环 + 圆内计时 + 「已加密」+ 绿光晕
+- **disabled**(免费到点切断/额度耗尽):`bg-subtle` 底 + `fg3` 前景 + **lock 图标**(非 power)+ not-allowed;点击走「看广告加时」。原型原子 `.connect-btn.is-disabled`
> 注意:背景在渐变↔纯色间切换时,过渡只用 `box-shadow`/`background-color`,**不要 `transition: all`**(会导致背景计算失效)。
---
@@ -169,5 +170,5 @@ SKILL.md ← Agent Skill 入口
- [ ] 明/暗两主题均验证过;中/英两语言均验证过(单显不并排)
- [ ] 文案无铁律 13 红线词;套餐数字与 §7 一致
- [ ] 图标只用 Lucide;国家码块无 emoji 国旗;状态胶囊无 emoji
-- [ ] 连接键三态、智能选择默认选中、免费版额度/广告解锁流程与 React 原型一致
+- [ ] 连接键四态(含免费到点 disabled 锁态)、智能选择默认选中、免费版额度(倒计时收缩条)/看广告加时流程与原型单源一致
- [ ] App/官网内无任何支付表单,购买只引导到外部渠道
diff --git a/design/codegen/gen_flutter_icons.mjs b/design/codegen/gen_flutter_icons.mjs
new file mode 100644
index 0000000..b6fd06b
--- /dev/null
+++ b/design/codegen/gen_flutter_icons.mjs
@@ -0,0 +1,45 @@
+#!/usr/bin/env node
+// gen_flutter_icons.mjs —— 图标单源 codegen(ds-flow step 2a)。
+// 图标真源 = design/prototype/icons.js(Lucide sprite)。读其 key 集,kebab→camel,
+// 生成 client/lib/widgets/pangolin_icons.dart(每个 sprite key 一行 static const)。
+// 零依赖(Node fs)。运行:node design/codegen/gen_flutter_icons.mjs
+import { readFileSync, writeFileSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { dirname, resolve } from 'node:path';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const root = resolve(__dirname, '..', '..');
+const SPRITE = resolve(root, 'design/prototype/icons.js');
+const OUT = resolve(root, 'client/lib/widgets/pangolin_icons.dart');
+
+const src = readFileSync(SPRITE, 'utf8');
+// 抽 key:形如 'kebab-name': '<...
+const keys = [];
+const re = /'([a-z0-9-]+)'\s*:\s*' (seen.has(k) ? false : (seen.add(k), true)));
+
+const camel = (k) => k.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
+
+const lines = ordered.map((k) => {
+ const c = camel(k);
+ return ` static const ${c} = LucideIcons.${c};`;
+});
+
+const out = `// GENERATED by design/codegen/gen_flutter_icons.mjs —— 勿手改;图标真源=design/prototype/icons.js
+// 重新生成:node design/codegen/gen_flutter_icons.mjs
+import 'package:lucide_icons_flutter/lucide_icons.dart';
+
+class PangolinIcons {
+ PangolinIcons._();
+
+${lines.join('\n')}
+}
+`;
+
+writeFileSync(OUT, out);
+console.log(`generated ${OUT} (${ordered.length} icons)`);
diff --git a/design/prototype/assets b/design/prototype/assets
new file mode 120000
index 0000000..ec2e4be
--- /dev/null
+++ b/design/prototype/assets
@@ -0,0 +1 @@
+../assets
\ No newline at end of file
diff --git a/design/prototype/atoms.css b/design/prototype/atoms.css
index cbc0307..f45d424 100644
--- a/design/prototype/atoms.css
+++ b/design/prototype/atoms.css
@@ -82,8 +82,8 @@
}
.btn-subtle:hover { border-color: var(--accent); color: var(--accent); }
-/* Danger — 危险动作(断开 / 移除设备) */
-.btn-danger { background: var(--danger-subtle); color: var(--red-600); }
+/* Danger — 危险动作(断开 / 移除设备)。文字用语义 --danger(对齐线上 c.danger,随主题翻色) */
+.btn-danger { background: var(--danger-subtle); color: var(--danger); }
.btn-danger:hover { background: var(--danger); color: var(--fg-on-accent); }
/* 尺寸 / 形态修饰 */
@@ -94,12 +94,13 @@
/* =============================================================
CARD · .card
- surface 底 + border + radius-xl + shadow-sm
+ surface 底 + border + radius-lg + shadow-sm
+ 圆角对齐线上 cardTheme(PangolinRadius.lg=14);阴影保留 shadow-sm(暖阴影铁律)。
============================================================= */
.card {
background: var(--surface);
border: 1px solid var(--border);
- border-radius: var(--radius-xl);
+ border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
padding: var(--space-5);
}
@@ -164,27 +165,29 @@
line-height: 1;
padding: var(--space-2) var(--space-3);
border-radius: var(--radius-full);
- background: var(--sand-100);
+ background: var(--bg-subtle);
color: var(--fg2);
}
.pill .dot {
width: 7px;
height: 7px;
border-radius: var(--radius-full);
- background: var(--sand-400);
+ background: var(--fg3);
flex-shrink: 0;
}
/* alias:.badge === .pill(语义等价,命名习惯不同) */
.badge { }
-.pill.is-success { background: var(--success-subtle); color: var(--green-600); }
+/* 状态色对齐线上 status_pill.dart:文字用语义档 --success/--warning/--danger(非 -600 深档),
+ neutral 用 --bg-subtle/--fg3(原 sand-100/sand-400 硬编码在深色不翻色,已修)。 */
+.pill.is-success { background: var(--success-subtle); color: var(--success); }
.pill.is-success .dot { background: var(--success); }
-.pill.is-warning { background: var(--warning-subtle); color: var(--amber-600); }
+.pill.is-warning { background: var(--warning-subtle); color: var(--warning); }
.pill.is-warning .dot { background: var(--warning); }
-.pill.is-danger { background: var(--danger-subtle); color: var(--red-600); }
+.pill.is-danger { background: var(--danger-subtle); color: var(--danger); }
.pill.is-danger .dot { background: var(--danger); }
-.pill.is-neutral { background: var(--sand-100); color: var(--fg2); }
-.pill.is-neutral .dot { background: var(--sand-400); }
+.pill.is-neutral { background: var(--bg-subtle); color: var(--fg2); }
+.pill.is-neutral .dot { background: var(--fg3); }
/* 实心 accent(PRO 会员等强调标签) */
.pill-accent { background: var(--accent); color: var(--fg-on-accent); }
@@ -246,6 +249,8 @@
gap: 1px;
z-index: 60;
}
+/* hidden 属性生效:菜单默认收起,靠 JS 去/加 hidden 或 .is-open 控制显隐 */
+.menu[hidden] { display: none; }
.menu-item {
display: block;
width: 100%;
@@ -270,3 +275,430 @@
color: var(--accent);
font-weight: 700;
}
+
+
+/* =============================================================
+ PLAN CARD · .plan-card (购买套餐卡)
+ surface 底常规档;.is-featured 走 clay 渐变高亮(年付/最优惠)。
+ 镜像 client/lib/widgets/plan_card.dart 的视觉配方(原型先行单源)。
+ 子件:.plan-name / .plan-price / .plan-per / .plan-feats / .plan-feat
+ / .plan-pop(顶部「最受欢迎」浮标)。
+ ============================================================= */
+.plan-card {
+ position: relative;
+ background: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-xl);
+ box-shadow: var(--shadow-sm);
+ padding: var(--space-6) var(--space-5) var(--space-5);
+}
+.plan-name {
+ font-family: var(--font-sans);
+ font-size: var(--text-sm);
+ font-weight: 600;
+ color: var(--fg2);
+}
+.plan-price-row { display: flex; align-items: baseline; gap: var(--space-2); margin-top: var(--space-2); }
+.plan-price {
+ font-family: var(--font-display);
+ font-size: var(--text-display);
+ font-weight: 700;
+ line-height: 1;
+ letter-spacing: var(--tracking-tight);
+ color: var(--fg1);
+}
+.plan-per { font-family: var(--font-sans); font-size: var(--text-sm); color: var(--fg3); }
+.plan-feats { margin: var(--space-4) 0 var(--space-5); display: flex; flex-direction: column; gap: var(--space-2); }
+.plan-feat { display: flex; align-items: center; gap: var(--space-3); font-family: var(--font-sans); font-size: var(--text-sm); color: var(--fg2); }
+.plan-feat > svg { width: 15px; height: 15px; flex-shrink: 0; color: var(--success); }
+
+/* 「最受欢迎」浮标:白底胶囊悬于卡顶 */
+.plan-pop {
+ position: absolute;
+ top: calc(-1 * var(--space-3));
+ left: 50%;
+ transform: translateX(-50%);
+ background: #fff;
+ color: var(--clay-700);
+ font-family: var(--font-sans);
+ font-size: var(--text-caption);
+ font-weight: 700;
+ padding: var(--space-1) var(--space-3);
+ border-radius: var(--radius-full);
+ box-shadow: var(--shadow-sm);
+ white-space: nowrap;
+}
+
+/* Featured — clay 渐变高亮(年付/最优惠)。渐变内文字恒为白,靠 opacity 分级。 */
+.plan-card.is-featured {
+ background: linear-gradient(135deg, var(--clay-600), var(--clay-800));
+ border-color: transparent;
+ box-shadow: var(--shadow-md);
+}
+.plan-card.is-featured .plan-name { color: #fff; opacity: 0.85; }
+.plan-card.is-featured .plan-price { color: #fff; }
+.plan-card.is-featured .plan-per { color: #fff; opacity: 0.85; }
+.plan-card.is-featured .plan-feat { color: #fff; opacity: 0.92; }
+.plan-card.is-featured .plan-feat > svg { color: #fff; }
+/* Featured 卡内 CTA 反色:白底 clay 字 */
+.plan-card.is-featured .btn-primary { background: #fff; color: var(--clay-700); }
+.plan-card.is-featured .btn-primary:hover { background: #fff; opacity: 0.92; }
+
+
+/* =============================================================
+ PAY METHOD · .pay-method (选择支付方式列表项)
+ 底部弹层里的一行:leading 图标 + 标题 + trailing chevron。
+ 资金外流:点选后拉起外部渠道 / 展示地址,App 内无卡号录入表单(铁律 #10)。
+ ============================================================= */
+.pay-method {
+ display: flex;
+ align-items: center;
+ gap: var(--space-4);
+ width: 100%;
+ padding: var(--space-4) var(--space-4);
+ border: none;
+ background: transparent;
+ border-radius: var(--radius-md);
+ cursor: pointer;
+ text-align: left;
+ transition: background-color var(--dur-fast) var(--ease-out);
+}
+.pay-method:hover { background: var(--bg-subtle); }
+.pay-method > .pm-ic { width: 22px; height: 22px; flex-shrink: 0; color: var(--fg2); }
+.pay-method > .pm-title { flex: 1; font-family: var(--font-sans); font-size: var(--text-body); color: var(--fg1); }
+.pay-method > .pm-chev { width: 18px; height: 18px; flex-shrink: 0; color: var(--fg3); }
+
+
+/* =============================================================
+ KV ROW · .kv-row (可复制键值行 — 支付明细:网络/地址/金额)
+ label(次要)+ 等宽值 + 复制按钮。用于 crypto_address / qr 支付态。
+ ============================================================= */
+.kv-row { display: flex; align-items: flex-start; gap: var(--space-3); padding: var(--space-2) 0; }
+.kv-label { width: 76px; flex-shrink: 0; font-family: var(--font-sans); font-size: var(--text-sm); color: var(--fg3); padding-top: 2px; }
+.kv-val { flex: 1; min-width: 0; font-family: var(--font-mono); font-size: var(--text-sm); color: var(--fg1); word-break: break-all; font-feature-settings: 'tnum' 1; }
+.kv-copy {
+ flex-shrink: 0;
+ border: none;
+ background: transparent;
+ color: var(--fg3);
+ cursor: pointer;
+ padding: var(--space-1);
+ border-radius: var(--radius-sm);
+ transition: color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
+}
+.kv-copy:hover { color: var(--accent); background: var(--accent-subtle); }
+.kv-copy > svg { width: 16px; height: 16px; display: block; }
+
+
+/* =============================================================
+ RESULT STATE · .result (支付成功 / 失败终态)
+ 居中大图标 + 标题 + 说明。成功用 --success,失败用 --danger。
+ ============================================================= */
+.result { display: flex; flex-direction: column; align-items: center; text-align: center; padding: var(--space-8) var(--space-6); }
+.result-icon {
+ width: 72px; height: 72px;
+ border-radius: var(--radius-full);
+ display: flex; align-items: center; justify-content: center;
+ background: var(--success-subtle);
+ color: var(--success);
+ margin-bottom: var(--space-4);
+}
+.result-icon > svg { width: 38px; height: 38px; }
+.result.is-fail .result-icon { background: var(--danger-subtle); color: var(--danger); }
+.result-title { font-family: var(--font-display); font-size: var(--text-h2); font-weight: 700; color: var(--fg1); letter-spacing: var(--tracking-snug); }
+.result.is-fail .result-title { color: var(--danger); }
+.result-sub { margin-top: var(--space-2); font-family: var(--font-sans); font-size: var(--text-sm); color: var(--fg2); }
+
+
+/* =============================================================
+ CONNECT BUTTON · .connect-btn (核心连接键 — 产品招牌组件)
+ 圆形一键连接键,严格三态:.is-off / .is-connecting / .is-on。
+ 镜像 client/lib/widgets/connect_button.dart 的视觉配方(原型先行单源)。
+ ⚠️ 铁律(design/CLAUDE.md §5):背景在渐变↔纯色间切换,过渡只走
+ box-shadow / background-color,禁 transition:all(否则背景计算失效)。
+ 注:圆内文字/图标/环在 clay·green 实底上恒为白(对齐 Flutter PangolinColors.white),
+ 故用白名单 #fff;轨道环半透明白靠 stroke-opacity 属性表达(非 rgba 硬编码色)。
+ ============================================================= */
+.connect-btn {
+ position: relative;
+ width: 140px;
+ height: 140px;
+ border: none;
+ border-radius: var(--radius-full);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: var(--space-1);
+ cursor: pointer;
+ transition: background-color var(--dur-slow) var(--ease-out),
+ box-shadow var(--dur-slow) var(--ease-out);
+}
+.connect-btn:active { transform: scale(0.97); }
+.connect-btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
+
+/* 轨道环:铺满按钮的 svg,垫在圆内文字/图标之下 */
+.cb-ring { position: absolute; inset: 0; z-index: 0; }
+.cb-ring circle { fill: none; }
+.cb-icon, .cb-label, .cb-timer, .cb-sub { position: relative; z-index: 1; }
+.cb-icon { width: 38px; height: 38px; color: currentColor; }
+.cb-label {
+ font-family: var(--font-sans);
+ font-size: var(--text-caption);
+ font-weight: 700;
+ letter-spacing: var(--tracking-wide);
+}
+.cb-timer {
+ font-family: var(--font-mono);
+ font-size: var(--text-body-lg);
+ font-weight: 600;
+ line-height: 1;
+ font-feature-settings: 'tnum' 1;
+}
+.cb-sub {
+ font-family: var(--font-sans);
+ font-size: var(--text-caption);
+ font-weight: 700;
+ letter-spacing: var(--tracking-caps);
+ opacity: 0.85;
+}
+@keyframes cb-spin { to { transform: rotate(360deg); } }
+.cb-spin { animation: cb-spin 1.1s linear infinite; transform-origin: 50% 50%; }
+
+/* OFF:暖灰底(微渐变)+ clay 前景 + 虚线轨道环 */
+.connect-btn.is-off {
+ background: radial-gradient(circle at 50% 38%, var(--sand-50), var(--sand-100));
+ color: var(--accent);
+ box-shadow: inset 0 0 0 1px var(--sand-200), var(--shadow-md);
+}
+.connect-btn.is-off .cb-sub { color: var(--fg3); opacity: 1; }
+.cb-track { stroke: var(--sand-200); stroke-width: 3; stroke-linecap: round; stroke-dasharray: 2 7; }
+
+/* CONNECTING:clay 实底 + accent 光晕 + 旋转进度弧 */
+.connect-btn.is-connecting {
+ background: var(--accent);
+ color: #fff;
+ box-shadow: 0 0 0 8px var(--accent-subtle), var(--shadow-lg);
+}
+.cb-arc-track { stroke: #fff; stroke-opacity: 0.3; stroke-width: 4; }
+.cb-arc { stroke: #fff; stroke-width: 4; stroke-linecap: round; stroke-dasharray: 100 302; }
+
+/* ON:success 实底 + 绿光晕 + 满白环 + 圆内计时 */
+.connect-btn.is-on {
+ background: var(--success);
+ color: #fff;
+ box-shadow: 0 0 0 8px var(--success-subtle), var(--shadow-lg);
+}
+.cb-ring-full { stroke: #fff; stroke-opacity: 0.85; stroke-width: 4; stroke-linecap: round; }
+
+/* DISABLED(免费到点切断 / 额度耗尽)——对齐线上 connect_button.dart 第 4 态:
+ bg-subtle 底 + fg3 前景 + lock 图标 + not-allowed;点击走「看广告加时」。仅 off 场景触发。 */
+.connect-btn.is-disabled {
+ background: var(--bg-subtle);
+ color: var(--fg3);
+ box-shadow: var(--shadow-md);
+ cursor: not-allowed;
+}
+.connect-btn.is-disabled .cb-track { stroke: var(--border-strong); }
+
+
+/* =============================================================
+ SERVER ROW · .server-row (节点列表行)
+ 镜像 client/lib/widgets/server_tile.dart + design/preview/comp-server-row.html。
+ 放进 .card.card-flush 列表容器;一行 = 国家码块 + 名称/子名 + 属性胶囊
+ (.pill-outline) + 延迟(mono·按值变色) + 信号条 + 选中/推荐/不可用态。
+ ============================================================= */
+.server-row {
+ display: flex;
+ align-items: center;
+ gap: var(--space-3);
+ padding: var(--space-3) var(--space-4);
+ border-bottom: 1px solid var(--border);
+ cursor: pointer;
+ transition: background-color var(--dur-fast) var(--ease-out);
+}
+.server-row:last-child { border-bottom: none; }
+.server-row:hover { background: var(--bg-subtle); }
+
+/* 国家码块(2 字母码,绝不用 emoji 国旗 · 铁律 #8) */
+.sr-cc {
+ width: 34px; height: 34px; flex-shrink: 0;
+ display: flex; align-items: center; justify-content: center;
+ border-radius: var(--radius-md);
+ background: var(--sand-100);
+ font-family: var(--font-mono); font-size: var(--text-caption); font-weight: 600;
+ color: var(--fg2);
+}
+.sr-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
+.sr-title { font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 600; color: var(--fg1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+.sr-sub { font-family: var(--font-sans); font-size: var(--text-caption); font-weight: 400; color: var(--fg3); }
+.sr-tags { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: 2px; }
+.sr-tags .pill { padding: 3px var(--space-2); font-size: 11px; }
+
+/* 延迟(等宽 · tabular nums)——对齐线上 server_tile:**恒 fg2,不按阈值变色**;
+ 快慢改由信号条格数编码。旧 .is-good/.is-warn/.is-bad 一律回落 fg2(兼容不变色)。 */
+.sr-latency { flex-shrink: 0; font-family: var(--font-mono); font-size: var(--text-caption); color: var(--fg2); font-feature-settings: 'tnum' 1; }
+.sr-latency.is-good, .sr-latency.is-warn, .sr-latency.is-bad { color: var(--fg2); }
+
+/* 信号条(纯 div + token 色)——默认暗格 border-strong,按等级点亮 success:
+ lv3=3 格(ping<40) / lv2=前 2 格(<90) / lv1=第 1 格(≥90)。对齐线上 SignalBars。 */
+.sr-signal { display: flex; align-items: flex-end; gap: 2px; height: 14px; flex-shrink: 0; }
+.sr-signal i { width: 3px; border-radius: 1px; background: var(--border-strong); }
+.sr-signal i:nth-child(1) { height: 5px; }
+.sr-signal i:nth-child(2) { height: 9px; }
+.sr-signal i:nth-child(3) { height: 14px; }
+.sr-signal.lv3 i,
+.sr-signal.lv2 i:nth-child(-n+2),
+.sr-signal.lv1 i:nth-child(1) { background: var(--success); }
+.sr-signal i.is-off { background: var(--border-strong); }
+
+.sr-check { flex-shrink: 0; color: var(--accent); display: inline-flex; }
+.sr-check svg { width: 18px; height: 18px; }
+
+/* 选中态:accent-subtle 底 + 码块反色实心 clay */
+.server-row.is-selected { background: var(--accent-subtle); }
+.server-row.is-selected:hover { background: var(--accent-subtle); }
+.server-row.is-selected .sr-cc { background: var(--accent); color: var(--fg-on-accent); }
+
+/* 不可用态(agent 离线) */
+.server-row.is-down { opacity: 0.55; cursor: not-allowed; }
+.server-row.is-down:hover { background: transparent; }
+.sr-unavail { flex-shrink: 0; font-family: var(--font-sans); font-size: var(--text-caption); font-weight: 600; color: var(--danger); }
+
+/* 智能选择推荐行(节点页置顶 · design/CLAUDE.md §5) */
+.server-row.is-smart { background: var(--accent-subtle); }
+.sr-smart-ic {
+ width: 34px; height: 34px; flex-shrink: 0;
+ display: flex; align-items: center; justify-content: center;
+ border-radius: var(--radius-md);
+ background: linear-gradient(135deg, var(--clay-500), var(--clay-700)); /* 对齐线上 smart_select_card */
+ color: #fff; /* ds-allow: clay 渐变块上恒为白,对齐 .ds-brand .mk / plan-card featured */
+}
+.sr-smart-ic svg { width: 18px; height: 18px; }
+
+
+/* =============================================================
+ 线上对齐补建原子(2026-07 回填:atoms 对齐 client/lib/widgets)
+ ============================================================= */
+
+/* ICON BOX · accent-subtle 圆角图标框(设备/设置行前导) */
+.icon-box { width: 36px; height: 36px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; border-radius: var(--radius-md); background: var(--accent-subtle); color: var(--accent); }
+.icon-box > svg { width: 18px; height: 18px; }
+.icon-box.is-danger { background: var(--danger-subtle); color: var(--danger); }
+
+/* STATUS DOT · 独立状态点 */
+.status-dot { width: 8px; height: 8px; border-radius: var(--radius-full); background: var(--fg3); flex-shrink: 0; display: inline-block; }
+.status-dot.is-online { background: var(--success); }
+.status-dot.is-offline { background: var(--fg3); }
+.status-dot.is-busy { background: var(--warning); }
+
+/* TOGGLE · 开关(对齐线上 dtoggle) */
+.toggle { width: 42px; height: 24px; flex-shrink: 0; border-radius: var(--radius-full); background: var(--border-strong); border: none; cursor: pointer; position: relative; transition: background-color var(--dur-fast) var(--ease-out); }
+.toggle > i { position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: var(--radius-full); background: #fff; box-shadow: var(--shadow-sm); transition: transform var(--dur-fast) var(--ease-out); } /* ds-allow: 开关滑块恒为白 */
+.toggle.is-on { background: var(--accent); }
+.toggle.is-on > i { transform: translateX(18px); }
+
+/* SEGSWITCH · 二选一段控开关(支付渠道/币种等的横向 switch;iOS 式滑块高亮) */
+.segswitch { display: flex; background: var(--bg-subtle); border: 1px solid var(--border); border-radius: var(--radius-full); padding: var(--space-1); gap: var(--space-1); }
+.segswitch > .segswitch-opt { flex: 1; border: none; background: transparent; cursor: pointer; font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 600; color: var(--fg2); padding: var(--space-2) var(--space-4); border-radius: var(--radius-full); display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out); }
+.segswitch > .segswitch-opt > svg { width: 16px; height: 16px; flex-shrink: 0; }
+.segswitch > .segswitch-opt.is-active { background: var(--surface); color: var(--fg1); box-shadow: var(--shadow-sm); }
+
+/* FIELD BOX · 复合输入(前置图标 + 裸 input + 后置槽;对齐 pangolin_field.dart) */
+.field-box { display: flex; align-items: center; gap: var(--space-3); background: var(--surface); border: 1.5px solid var(--border-strong); border-radius: var(--radius-md); padding: 0 var(--space-4); transition: border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out); }
+.field-box.is-focused { border-color: var(--accent); box-shadow: var(--shadow-focus); }
+.field-box .fb-lead { width: 18px; height: 18px; flex-shrink: 0; color: var(--fg3); }
+.field-box .fb-input { flex: 1; min-width: 0; border: none; background: transparent; font-family: var(--font-sans); font-size: var(--text-sm); color: var(--fg1); padding: var(--space-3) 0; }
+.field-box .fb-input:focus { outline: none; }
+.field-box .fb-input::placeholder { color: var(--fg3); }
+.field-box .fb-trailing { flex-shrink: 0; display: inline-flex; color: var(--fg3); }
+.field-box.is-plain { border-color: transparent; background: var(--bg-subtle); box-shadow: none; }
+
+/* TOAST · 轻提示(对齐 pangolin_toast / snackBarTheme) */
+.toast { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); text-align: center; background: var(--surface); color: var(--fg1); border: 1px solid var(--border); border-radius: var(--radius-md); box-shadow: var(--shadow-lg); padding: var(--space-3) var(--space-4); font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 600; }
+.toast > svg { width: 14px; height: 14px; flex-shrink: 0; }
+
+/* QUOTA CARD · 免费版今日额度(对齐 quota_card.dart:倒计时收缩条 + 免费版标签 + 三档阈值;无绿色「已解锁」态) */
+.quota-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); padding: var(--space-3) var(--space-4); display: flex; flex-direction: column; gap: var(--space-2); }
+.quota-head { display: flex; align-items: center; gap: var(--space-2); }
+.quota-head > svg { width: 15px; height: 15px; color: var(--accent); flex-shrink: 0; }
+.quota-card.is-exhausted .quota-head > svg { color: var(--danger); }
+.quota-label { font-family: var(--font-sans); font-size: var(--text-caption); font-weight: 600; color: var(--fg2); }
+.quota-val { font-family: var(--font-mono); font-size: 14px; font-weight: 600; color: var(--fg1); font-feature-settings: 'tnum' 1; }
+.quota-tag { margin-left: auto; background: var(--bg-subtle); color: var(--fg3); font-family: var(--font-sans); font-size: 11px; font-weight: 600; padding: 3px var(--space-2); border-radius: var(--radius-full); }
+.quota-bar { height: 6px; border-radius: var(--radius-full); background: var(--bg-subtle); overflow: hidden; }
+.quota-bar > i { display: block; height: 100%; border-radius: var(--radius-full); background: var(--accent); transition: width var(--dur-base) var(--ease-out); }
+.quota-bar.is-low > i { background: var(--warning); }
+.quota-bar.is-exhausted > i { background: var(--danger); }
+
+/* METRIC · 统计指标瓦片 */
+.metric { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); padding: var(--space-3); }
+.metric .m-ic { width: 26px; height: 26px; border-radius: var(--radius-sm); background: var(--accent-subtle); color: var(--accent); display: flex; align-items: center; justify-content: center; margin-bottom: var(--space-2); }
+.metric .m-ic > svg { width: 15px; height: 15px; }
+.metric .m-val { font-family: var(--font-mono); font-size: var(--text-body); font-weight: 600; color: var(--fg1); font-feature-settings: 'tnum' 1; }
+.metric .m-label { font-family: var(--font-sans); font-size: 11px; color: var(--fg3); margin-top: 2px; }
+
+/* DEVICE ROW · 设备管理行(对齐 DevicesScreen:icon-box + 名 + 最后在线 + 本机/移除/⋯) */
+.device-row { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border); }
+.device-row:last-child { border-bottom: none; }
+.device-row .dv-body { flex: 1; min-width: 0; }
+.device-row .dv-name { font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 600; color: var(--fg1); }
+.device-row .dv-sub { font-family: var(--font-sans); font-size: var(--text-caption); color: var(--fg3); margin-top: 2px; display: flex; align-items: center; gap: var(--space-2); }
+.device-row .dv-trail { flex-shrink: 0; display: inline-flex; align-items: center; gap: var(--space-2); }
+
+/* SETTING ROW · 设置/导航行(对齐 settings _Row/_NavRow:icon-box + 标题 + 副标 + trailing) */
+.setting-row { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border); }
+.setting-row:last-child { border-bottom: none; }
+.setting-row.is-tap { cursor: pointer; transition: background-color var(--dur-fast) var(--ease-out); }
+.setting-row.is-tap:hover { background: var(--bg-subtle); }
+.setting-row .st-body { flex: 1; min-width: 0; }
+.setting-row .st-title { font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 600; color: var(--fg1); }
+.setting-row .st-sub { font-family: var(--font-sans); font-size: var(--text-caption); color: var(--fg3); margin-top: 2px; }
+.setting-row .st-trail { flex-shrink: 0; display: inline-flex; align-items: center; gap: var(--space-2); color: var(--fg3); }
+.setting-row .st-trail > svg { width: 18px; height: 18px; }
+.setting-row .st-val { font-family: var(--font-sans); font-size: var(--text-sm); color: var(--fg2); }
+
+/* PLAN BANNER · 账户页套餐横幅(免费=surface / .is-pro=clay 渐变;对齐 _PlanBanner) */
+.plan-banner { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); padding: var(--space-5); }
+.plan-banner .pbn-row { display: flex; align-items: center; gap: var(--space-3); }
+.plan-banner .pbn-info { flex: 1 1 auto; min-width: 0; }
+.plan-banner .pbn-row > .btn { flex-shrink: 0; }
+.plan-banner .pbn-badge { width: 44px; height: 44px; flex-shrink: 0; border-radius: var(--radius-lg); display: flex; align-items: center; justify-content: center; background: var(--accent-subtle); color: var(--accent); }
+.plan-banner .pbn-badge > svg { width: 24px; height: 24px; }
+.plan-banner .pbn-name { font-family: var(--font-display); font-size: var(--text-h3); font-weight: 700; color: var(--fg1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+.plan-banner .pbn-meta { font-family: var(--font-sans); font-size: var(--text-caption); color: var(--fg3); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+.plan-banner.is-pro { background: linear-gradient(135deg, var(--clay-600), var(--clay-800)); border-color: transparent; box-shadow: var(--shadow-md); }
+.plan-banner.is-pro .pbn-badge { background: transparent; border: 1.5px solid #fff; color: #fff; }
+.plan-banner.is-pro .pbn-name { color: #fff; }
+.plan-banner.is-pro .pbn-meta { color: #fff; opacity: 0.85; }
+.plan-banner.is-pro .btn-primary { background: #fff; color: var(--clay-700); }
+
+/* PLAN BADGE · 桌面侧栏底部会员卡(对齐 plan_badge_card.dart) */
+.plan-badge { display: flex; align-items: center; gap: var(--space-2); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md); padding: var(--space-2) var(--space-3); cursor: pointer; transition: background-color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out); }
+.plan-badge.is-active { background: var(--accent-subtle); border-color: var(--accent); }
+.plan-badge .plb-crown { width: 30px; height: 30px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; border-radius: var(--radius-full); background: linear-gradient(135deg, var(--clay-500), var(--clay-700)); color: #fff; } /* ds-allow: clay 渐变块上恒为白 */
+.plan-badge .plb-crown > svg { width: 15px; height: 15px; }
+.plan-badge .plb-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
+.plan-badge .plb-name { font-family: var(--font-sans); font-size: var(--text-caption); font-weight: 600; color: var(--fg1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+.plan-badge .plb-sub { font-family: var(--font-sans); font-size: var(--text-caption); color: var(--fg3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+.plan-badge .plb-chev { width: 14px; height: 14px; flex-shrink: 0; color: var(--fg3); }
+
+/* UPDATE BANNER · jiu 式更新顶部横条(对齐 UpdateBanner;非强制态) */
+.update-banner { display: flex; align-items: center; gap: var(--space-3); background: var(--accent-subtle); padding: var(--space-2) var(--space-3) var(--space-2) var(--space-4); }
+.update-banner .ub-ic { width: 17px; height: 17px; flex-shrink: 0; color: var(--accent); }
+.update-banner .ub-title { flex: 1; min-width: 0; font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 700; color: var(--fg1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+.update-banner .ub-cta { flex-shrink: 0; background: var(--accent); color: var(--fg-on-accent); border: none; border-radius: var(--radius-full); padding: var(--space-1) var(--space-4); font-family: var(--font-sans); font-size: var(--text-caption); font-weight: 700; cursor: pointer; }
+.update-banner .ub-close { flex-shrink: 0; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; border: none; background: transparent; color: var(--fg3); cursor: pointer; border-radius: var(--radius-sm); }
+.update-banner .ub-close > svg { width: 16px; height: 16px; }
+
+/* TAB BAR · 移动端底部 4-Tab(对齐 bottom_tab_bar.dart) */
+.tabbar { display: flex; height: 60px; border-top: 1px solid var(--border); background: var(--surface); }
+.tab { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; border: none; background: none; cursor: pointer; color: var(--fg3); font-family: var(--font-sans); font-size: 11px; font-weight: 600; }
+.tab > svg { width: 21px; height: 21px; }
+.tab.is-active { color: var(--accent); }
+
+/* NAV ITEM · 桌面/iPad 左侧栏导航项(对齐 nav_sidebar.dart) */
+.nav-item { display: flex; align-items: center; gap: var(--space-3); width: 100%; padding: var(--space-3); border: none; background: transparent; border-radius: var(--radius-md); cursor: pointer; text-align: left; color: var(--fg2); font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 600; transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out); }
+.nav-item > svg { width: 20px; height: 20px; flex-shrink: 0; }
+.nav-item:hover { background: var(--bg-subtle); color: var(--fg1); }
+.nav-item.is-active { background: var(--accent-subtle); color: var(--accent); }
diff --git a/design/prototype/icons.js b/design/prototype/icons.js
index 470444a..b2faa60 100644
--- a/design/prototype/icons.js
+++ b/design/prototype/icons.js
@@ -56,7 +56,6 @@
'arrow-right': '