Compare commits

...

2 Commits

Author SHA1 Message Date
wangjia 6def3edaed feat(ds-flow): Phase 1.4 — 原型图标 SVG sprite 单源 icons.js
design/prototype/icons.js:57 个 Lucide 图标的 SVG sprite 单源(零依赖,注入
隐藏 <svg> + <symbol id="i-*">,用法 <svg class="ic"><use href="#i-power"/>)。

路径全部来自权威来源、零手写:35 个取自 usercenter icons.tsx 内联路径、
22 个从 web/website/node_modules/lucide-react 包提取。覆盖三处并集
(usercenter 全键 ∪ Flutter pangolin_icons.dart ∪ website lucide 用量);
别名(loader-circle/chart-no-axes-column/play-circle/more-vertical 等)已解析。

供 Phase 2 check-l1-sync ③「三端图标 ⊆ 原型 sprite」同源闸校验。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 00:10:57 +08:00
wangjia 56f673791e feat(ds-flow): Phase 1.3 — 原型公用组件原子层 atoms.css
design/prototype/atoms.css:把分散在 preview 规格 / usercenter shared.tsx /
website css / Flutter widgets 的组件样式沉淀成 canonical 原子类,只引
var(--token)。含 .btn(primary/ghost/subtle/danger + 尺寸)、.card、.input
/.field、.pill/.badge(success/warning/danger/neutral 状态 + accent/outline)、
.langsel/.menu(对齐刚统一的自控语言下拉)。

明暗双主题靠语义 token 自动适配,文件内无 [data-theme] 分支。
验证:零硬编码色;45 个 token 引用全部在 prototype/tokens.css 有定义。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 00:04:52 +08:00
3 changed files with 374 additions and 2 deletions
+272
View File
@@ -0,0 +1,272 @@
/* =============================================================
穿山甲 VPN · Pangolin VPN — Prototype Atoms
design/prototype/atoms.css
-------------------------------------------------------------
本文件是「原型公用组件原子层」的唯一真相源(single source)。
canonical CSS 原子类,沉淀自 design/preview/*.html 组件规格、
web/usercenter/components/shared.tsx、web/website/src/styles/website.css
与 client/lib/widgets/*.dart 的交互态语义。
铁律(写/改本文件必守,对齐 design/CLAUDE.md §1):
1. 颜色 / 圆角 / 间距 / 字号 / 阴影 / 字体 一律走 var(--token)
token 定义在 tokens.css)。禁任何硬编码 hex / rgb / 魔法数。
2. 明暗双主题「自动」适配:原子类只引语义 token--surface / --fg1 …),
主题切换由 tokens.css 的 [data-theme="dark"] 负责——
本文件里不写任何 [data-theme] 分支。
3. 暖大地色 · 大圆角(按钮全胶囊 radius-full / 卡片 lgxl / 输入 md)·
柔和暖阴影 · 状态用「色点 + 文字胶囊」非 emoji。
4. 交互态语义:hover 主色加深一档 · press 缩放 .97 · focus clay 光环
shadow-focus)· disabled sand-200 底 + sand-400 字。
5. 每新增一个原子,须在 design/prototype/index.html 登记(登记簿单源)。
使用前先在页面 <head> 引入:tokens.css(令牌)+ 本文件(原子)。
============================================================= */
/* =============================================================
BUTTONS · .btn + variants
全胶囊圆角 · hover 加深 · press 收缩 .97 · focus clay 光环
变体:.btn-primary(实心 clay) · .btn-ghost(描边/幽灵) ·
.btn-subtle(次要描边) · .btn-danger · 尺寸 .btn-lg · .btn-icon
============================================================= */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
font-family: var(--font-sans);
font-weight: 600;
font-size: var(--text-sm);
line-height: 1;
border: none;
border-radius: var(--radius-full);
padding: var(--space-3) var(--space-5);
cursor: pointer;
white-space: nowrap;
transition: background-color var(--dur-fast) var(--ease-out),
color var(--dur-fast) var(--ease-out),
border-color var(--dur-fast) var(--ease-out),
transform var(--dur-fast) var(--ease-out),
box-shadow var(--dur-fast) var(--ease-out);
}
.btn > svg { width: 16px; height: 16px; flex-shrink: 0; }
.btn:focus-visible {
outline: none;
box-shadow: var(--shadow-focus);
}
.btn:active { transform: scale(0.97); }
.btn:disabled,
.btn.is-disabled {
background: var(--sand-200);
color: var(--sand-400);
cursor: not-allowed;
transform: none;
box-shadow: none;
border-color: transparent;
}
/* Primary — 实心黏土铜 */
.btn-primary { background: var(--accent); color: var(--fg-on-accent); }
.btn-primary:hover { background: var(--accent-hover); }
.btn-primary:active { background: var(--accent-press); }
/* Ghost — 透明底、clay 字,hover 上 accent-subtle */
.btn-ghost { background: transparent; color: var(--accent); }
.btn-ghost:hover { background: var(--accent-subtle); }
/* Subtle — surface 底 + 强描边(次要动作) */
.btn-subtle {
background: var(--surface);
color: var(--fg1);
border: 1.5px solid var(--border-strong);
}
.btn-subtle:hover { border-color: var(--accent); color: var(--accent); }
/* Danger — 危险动作(断开 / 移除设备) */
.btn-danger { background: var(--danger-subtle); color: var(--red-600); }
.btn-danger:hover { background: var(--danger); color: var(--fg-on-accent); }
/* 尺寸 / 形态修饰 */
.btn-lg { padding: var(--space-4) var(--space-6); font-size: var(--text-body); }
.btn-icon { padding: var(--space-3); } /* 正方形图标按钮(配 radius-full 成圆) */
.btn-block { width: 100%; }
/* =============================================================
CARD · .card
surface 底 + border + radius-xl + shadow-sm
============================================================= */
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-sm);
padding: var(--space-5);
}
.card-flush { padding: 0; } /* 列表容器(server-row 组)用,内容自带 padding */
/* =============================================================
INPUT · .input (+ .field / .flabel)
强描边 + radius-md + focus clay 光环
============================================================= */
.field { display: block; }
.flabel {
display: block;
font-family: var(--font-sans);
font-size: var(--text-caption);
font-weight: 600;
color: var(--fg2);
margin-bottom: var(--space-2);
}
.input {
width: 100%;
box-sizing: border-box;
font-family: var(--font-sans);
font-size: var(--text-sm);
color: var(--fg1);
background: var(--surface);
border: 1.5px solid var(--border-strong);
border-radius: var(--radius-md);
padding: var(--space-3) var(--space-4);
transition: border-color var(--dur-fast) var(--ease-out),
box-shadow var(--dur-fast) var(--ease-out);
}
.input::placeholder { color: var(--fg3); }
.input:focus,
.input.is-focused {
outline: none;
border-color: var(--accent);
box-shadow: var(--shadow-focus);
}
.input:disabled {
background: var(--bg-subtle);
color: var(--fg3);
cursor: not-allowed;
}
.input.is-error { border-color: var(--danger); }
.input.is-error:focus { box-shadow: 0 0 0 4px var(--danger-subtle); }
/* =============================================================
BADGE / PILL · .pill (+ status modifiers)
状态胶囊:色点 + 文字(非 emoji)。全胶囊圆角。
状态:.is-success / .is-warning / .is-danger / .is-neutral
变体:.pill-accent(实心 clay,如 PRO) · .pill-outline(accent 描边,如节点属性)
============================================================= */
.pill {
display: inline-flex;
align-items: center;
gap: var(--space-2);
font-family: var(--font-sans);
font-size: var(--text-caption);
font-weight: 600;
line-height: 1;
padding: var(--space-2) var(--space-3);
border-radius: var(--radius-full);
background: var(--sand-100);
color: var(--fg2);
}
.pill .dot {
width: 7px;
height: 7px;
border-radius: var(--radius-full);
background: var(--sand-400);
flex-shrink: 0;
}
/* alias.badge === .pill(语义等价,命名习惯不同) */
.badge { }
.pill.is-success { background: var(--success-subtle); color: var(--green-600); }
.pill.is-success .dot { background: var(--success); }
.pill.is-warning { background: var(--warning-subtle); color: var(--amber-600); }
.pill.is-warning .dot { background: var(--warning); }
.pill.is-danger { background: var(--danger-subtle); color: var(--red-600); }
.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); }
/* 实心 accentPRO 会员等强调标签) */
.pill-accent { background: var(--accent); color: var(--fg-on-accent); }
/* accent 描边(节点属性:流媒体 / P2P 等) */
.pill-outline {
background: var(--accent-subtle);
color: var(--accent);
border: 1px solid var(--accent-border);
}
/* =============================================================
LANGUAGE SELECT · .langsel (触发药丸) + .menu (绝对定位菜单)
对齐 usercenter shared.tsx::LangSeg / website 的自定义下拉:
不依赖原生 <select>(会漂移),按钮 + 自控绝对定位菜单。
============================================================= */
.langsel {
position: relative;
display: inline-block;
}
/* 触发按钮:药丸形,bg-subtle 底 + 细描边 */
.langsel-trigger {
display: inline-flex;
align-items: center;
gap: var(--space-2);
font-family: var(--font-sans);
font-size: var(--text-caption);
font-weight: 700;
color: var(--fg2);
background: var(--bg-subtle);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: var(--space-2) var(--space-3);
cursor: pointer;
box-shadow: var(--shadow-sm);
transition: border-color var(--dur-fast) var(--ease-out),
color var(--dur-fast) var(--ease-out);
}
.langsel-trigger:hover { border-color: var(--border-strong); color: var(--fg1); }
.langsel-trigger:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.langsel-trigger > svg { width: 11px; height: 11px; }
/* 展开态:caret 翻转(配合 .is-open 或 [aria-expanded="true"] */
.langsel-trigger[aria-expanded="true"] > svg,
.langsel.is-open .langsel-trigger > svg { transform: rotate(180deg); }
/* 绝对定位菜单(下拉列表容器) */
.menu {
position: absolute;
top: calc(100% + var(--space-2));
right: 0;
min-width: 132px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
padding: var(--space-1);
display: flex;
flex-direction: column;
gap: 1px;
z-index: 60;
}
.menu-item {
display: block;
width: 100%;
text-align: left;
border: none;
background: transparent;
cursor: pointer;
font-family: var(--font-sans);
font-size: var(--text-sm);
font-weight: 500;
color: var(--fg1);
padding: var(--space-2) var(--space-3);
border-radius: var(--radius-sm);
white-space: nowrap;
transition: background-color var(--dur-fast) var(--ease-out);
}
.menu-item:hover { background: var(--bg-subtle); }
/* 选中项:accent-subtle 底 + clay 字 */
.menu-item.is-selected,
.menu-item[aria-selected="true"] {
background: var(--accent-subtle);
color: var(--accent);
font-weight: 700;
}
+100
View File
@@ -0,0 +1,100 @@
// icons.js — SVG sprite 单源(Lucide 细线条,viewBox 0 0 24 24 / stroke-width 2 / 圆角端点,ISC 许可)。
// 用法:<svg class="ic"><use href="#i-power"/></svg>.ic{ width/height/color } 即可控制尺寸与颜色。
// 收敛三处分散的 Lucide 图标为一份权威 sprite
// · usercenter —— web/usercenter/components/icons.tsx 的内联路径(权威副本,逐字照抄)。
// · lucide-react —— web/website/node_modules/lucide-reactusercenter 没有的从包源提取)。
// 新增图标先在此登记 symbol,再用 <use> 引用;勿手写/瞎猜路径。
(function () {
// key = kebab-namevalue = 该图标的 lucide 子元素串(照搬权威来源)。
var ICONS = {
// ── 源自 usercenterweb/usercenter/components/icons.tsx 内联,逐字照抄)──
'layout-dashboard': '<rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/>',
'link': '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>',
'ticket': '<path d="M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"/><path d="M13 5v2"/><path d="M13 17v2"/><path d="M13 11v2"/>',
'users': '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
'copy': '<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>',
'refresh-cw': '<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M3 21v-5h5"/>',
'qr-code': '<rect width="5" height="5" x="3" y="3" rx="1"/><rect width="5" height="5" x="16" y="3" rx="1"/><rect width="5" height="5" x="3" y="16" rx="1"/><path d="M21 16h-3a2 2 0 0 0-2 2v3"/><path d="M21 21v.01"/><path d="M12 7v3a2 2 0 0 1-2 2H7"/><path d="M3 12h.01"/><path d="M12 3h.01"/><path d="M12 16v.01"/><path d="M16 12h1"/><path d="M21 12v.01"/><path d="M12 21v-1"/>',
'download': '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/>',
'check': '<path d="M20 6 9 17l-5-5"/>',
'check-circle': '<path d="M21.801 10A10 10 0 1 1 17 3.335"/><path d="m9 11 3 3L22 4"/>',
'zap': '<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>',
'clock': '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
'shield-check': '<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="m9 12 2 2 4-4"/>',
'shield': '<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/>',
'crown': '<path d="M11.562 3.266a.5.5 0 0 1 .876 0L15.39 8.87a1 1 0 0 0 1.516.294L21.183 5.5a.5.5 0 0 1 .798.519l-2.834 10.246a1 1 0 0 1-.956.734H5.81a1 1 0 0 1-.957-.734L2.02 6.02a.5.5 0 0 1 .798-.519l4.276 3.664a1 1 0 0 0 1.516-.294z"/><path d="M5 21h14"/>',
'credit-card': '<rect width="20" height="14" x="2" y="5" rx="2"/><line x1="2" x2="22" y1="10" y2="10"/>',
'send': '<path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/><path d="m21.854 2.147-10.94 10.939"/>',
'message-circle': '<path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/>',
'mail': '<rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/>',
'shopping-bag': '<path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4Z"/><path d="M3 6h18"/><path d="M16 10a4 4 0 0 1-8 0"/>',
'log-out': '<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" x2="9" y1="12" y2="12"/>',
'external-link': '<path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>',
'gift': '<rect x="3" y="8" width="18" height="4" rx="1"/><path d="M12 8v13"/><path d="M19 12v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-7"/><path d="M7.5 8a2.5 2.5 0 0 1 0-5A4.8 8 0 0 1 12 8a4.8 8 0 0 1 4.5-5 2.5 2.5 0 0 1 0 5"/>',
'smartphone': '<rect width="14" height="20" x="5" y="2" rx="2" ry="2"/><path d="M12 18h.01"/>',
'lock': '<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
'chevron-right': '<path d="m9 18 6-6-6-6"/>',
'arrow-down': '<path d="M12 5v14"/><path d="m19 12-7 7-7-7"/>',
'settings': '<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/>',
'trash-2': '<path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/>',
'monitor': '<rect width="20" height="14" x="2" y="3" rx="2"/><line x1="8" x2="16" y1="21" y2="21"/><line x1="12" x2="12" y1="17" y2="21"/>',
'key': '<path d="m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4"/><path d="m21 2-9.6 9.6"/><circle cx="7.5" cy="15.5" r="5.5"/>',
'sun': '<circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/>',
'moon': '<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>',
'alert-triangle': '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/>',
'x': '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
// ── 源自 lucide-react 包(web/website/node_modules/lucide-react v0.469.0usercenter 未含)──
'power': '<path d="M12 2v10"/><path d="M18.4 6.6a9 9 0 1 1-12.77.04"/>',
'loader-circle': '<path d="M21 12a9 9 0 1 1-6.219-8.56"/>',
'globe': '<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>',
'user': '<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>',
'search': '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
'chevron-down': '<path d="m6 9 6 6 6-6"/>',
'arrow-left': '<path d="m12 19-7-7 7-7"/><path d="M19 12H5"/>',
'arrow-up': '<path d="m5 12 7-7 7 7"/><path d="M12 19V5"/>',
'arrow-right': '<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>',
'compass': '<path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"/><circle cx="12" cy="12" r="10"/>',
'chart-no-axes-column': '<line x1="18" x2="18" y1="20" y2="10"/><line x1="12" x2="12" y1="20" y2="4"/><line x1="6" x2="6" y1="20" y2="14"/>',
'map-pin': '<path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"/><circle cx="12" cy="10" r="3"/>',
'laptop': '<path d="M20 16V7a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v9m16 0H4m16 0 1.28 2.55a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45L4 16"/>',
'monitor-smartphone': '<path d="M18 8V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h8"/><path d="M10 19v-3.96 3.15"/><path d="M7 19h5"/><rect width="6" height="10" x="16" y="12" rx="2"/>',
'play-circle': '<circle cx="12" cy="12" r="10"/><polygon points="10 8 16 12 10 16 10 8"/>',
'wifi': '<path d="M12 20h.01"/><path d="M2 8.82a15 15 0 0 1 20 0"/><path d="M5 12.859a10 10 0 0 1 14 0"/><path d="M8.5 16.429a5 5 0 0 1 7 0"/>',
'eye': '<path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/>',
'eye-off': '<path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"/><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"/><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"/><path d="m2 2 20 20"/>',
'more-vertical': '<circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/>',
'pencil': '<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/><path d="m15 5 4 4"/>',
'terminal': '<polyline points="4 17 10 11 4 5"/><line x1="12" x2="20" y1="19" y2="19"/>',
'menu': '<line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/>'
};
// 组装隐藏 sprite:每个图标一个 <symbol>,符号上带 lucide 标准描边属性,故 .ic 只需管 width/height/color。
function buildSprite() {
var parts = ['<svg xmlns="http://www.w3.org/2000/svg" style="position:absolute;width:0;height:0;overflow:hidden" aria-hidden="true">'];
for (var name in ICONS) {
if (!Object.prototype.hasOwnProperty.call(ICONS, name)) continue;
parts.push(
'<symbol id="i-' + name + '" viewBox="0 0 24 24" fill="none" stroke="currentColor" ' +
'stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + ICONS[name] + '</symbol>'
);
}
parts.push('</svg>');
return parts.join('');
}
function inject() {
if (document.getElementById('pangolin-icon-sprite')) return;
var d = document.createElement('div');
d.id = 'pangolin-icon-sprite';
d.style.cssText = 'position:absolute;width:0;height:0';
d.innerHTML = buildSprite();
document.body.insertBefore(d, document.body.firstChild);
}
if (document.readyState !== 'loading') inject();
else document.addEventListener('DOMContentLoaded', inject);
// 便于调试/校验:暴露原始映射。
if (typeof window !== 'undefined') window.PANGOLIN_ICONS = ICONS;
})();
@@ -41,9 +41,9 @@
- [x] 1.2 `design/prototype/tokens.css`:从现有 `colors_and_type.css` 迁移/规整为
「基础 `:root`(主题无关标量:间距/圆角/字号/字体/阴影/动效)+ `[data-theme=dark]` 颜色覆盖块」结构。
**保持数值不变**,只重排为 ds-flow 结构;`colors_and_type.css` 作为兼容别名或迁移为薄封装(不破坏现有 codegen)
- [ ] 1.3 `design/prototype/atoms.css`:把按钮/卡片/输入/**语言下拉**/徽章/状态药丸等公用原子类沉淀为
- [x] 1.3 `design/prototype/atoms.css`:把按钮/卡片/输入/**语言下拉**/徽章/状态药丸等公用原子类沉淀为
只引 `var(--token)` 的 CSS(镜像 `design/preview/` 现有规格 + client widgets 实现语义)
- [ ] 1.4 `design/prototype/icons.js`SVG sprite 单源(`<symbol id="i-*">`),
- [x] 1.4 `design/prototype/icons.js`SVG sprite 单源(`<symbol id="i-*">`),
收敛现有分散图标(website Icon.astro / usercenter icons.tsx / Flutter pangolin_icons.dart 三处的图标集)
- [ ] 1.5 `design/prototype/index.html`:活登记页——三…两主题(light/dark)切换 + `data-swatches` 声明式色板 +
字号梯度 + 圆角/间距/阴影 + 全部公用组件原子展示卡 + 图标库全展示。**每个 atom 必须在此登记**