diff --git a/docs/design-system.md b/docs/design-system.md new file mode 100644 index 0000000..a06fe09 --- /dev/null +++ b/docs/design-system.md @@ -0,0 +1,178 @@ +# ok-script-toolkit · 全局 UI 统一设计规范 + +> 本文件是全项目 UI 的唯一权威规范。所有 webview 面板(console / templatePanel / +> templateAssetPanel / tempScreenshots / annotationPanel / characterManager)必须遵循。 +> 实现真源:`media/shared/tokens.css`(Design Tokens)+ `media/shared/controls.css`(共享控件), +> **页面只负责组合这些类,不得自行重定义视觉规则。** + +## 0. 核心要求 + +> **不是把每个页面分别做得好看,而是让整个 UI 看起来像由同一套设计系统设计出来的。** + +--- + +## 1. 统一设计语言 + +必须建立并遵循统一 Design System,覆盖:页面背景 / 容器背景 / 卡片 / 按钮 / 输入框 / +下拉框 / Checkbox·Radio·Switch / Tab / 标签 / 列表 / 分隔线 / 图标 / Tooltip / +Dialog·Modal / 状态提示 / Loading / 空状态 / 错误状态。同类组件必须使用同一种视觉语言。 + +## 2. 统一颜色体系 + +统一颜色层级(页面背景 > 一级容器 > 控件面 > Hover/Active),不得按组件单独定义颜色。 + +| 语义 | Token | 说明 | +|---|---|---| +| 页面背景 | `--bg-page` | 最底层 | +| 一级容器 | `--bg-container` / `--bg-container-raised` | 面板 / 卡片 | +| 控件面(真按钮) | `--bg-control` | Button / IconButton 的**默认**底色(配描边) | +| 行级面(组头、折叠标题行) | `--bg-row` | 行级可点击区的极浅底色——默认可识别但不与按钮同权重 | +| Hover / Active | `--bg-control-hover` / `--bg-control-active` / `--bg-row-hover` / `--surface-hover` | 交互态 | +| 边框 | `--border` / `--border-strong` | 常规 / 聚焦 | +| 主文字 | `--text-primary` | | +| 次要文字 | `--text-muted` | | +| 禁用文字 | `--text-disabled` | | +| 语义色 | `--ok` / `--run` / `--warn` / `--pause` / `--err` | 禁止另造色值 | + +**禁止**:页面/组件直引 `--vscode-*` 变量(仅 `tokens.css` 允许)、硬编码 hex/rgba。 + +## 3. 所有可点击控件统一表现 + +可点击控件**默认状态**即须有明确可点击表现: + +- ❌ 默认只是普通文字 / ❌ 默认与容器融为一体 / ❌ 只有 Hover 后才出现背景 +- ✅ 默认即有底色,Hover 是**增强**,不承担「让用户第一次发现这是按钮」的职责 + +**两类控件分层**(避免整屏层层方块): + +| 类别 | 用法 | 默认态 | +|---|---|---| +| 真按钮 | 动作类:保存/同步/重置/启用/关闭/图标按钮 | `background: var(--bg-control)` + `border: 1px solid var(--border)` | +| 行级可点击区 | 组头、折叠标题行、列表内的可点击行 | `background: var(--bg-row)`(极浅色调)+ **无描边**,Hover 用 `--bg-row-hover` | + +同类按钮须一致:高度(`--control-h-sm/md/lg`)、内边距(`--space-*`)、字体、圆角 +(`--radius-*`)、边框、背景、图标尺寸、图标与文字间距、Hover/Active/Focus/Disabled。 + +## 4. 统一尺寸体系 + +- 间距:`--space-xs(4) / sm(6) / md(10) / lg(14) / xl(20)` +- 控件高度:`--control-h-sm(24) / md(30) / lg(34)` +- 圆角:`--radius-sm(5) / md(8) / lg(11) / pill` +- 字号:`--font-xs(11) / sm(12) / md(13) / lg(15)`;字重仅 `--weight-regular(400)` / `--weight-medium(500)` + +禁止无依据的 32/35/38/30px 按钮高度混用,禁止 4/6/10/不圆的圆角混用。 + +## 5. 统一 Typography + +字体继承 `--vscode-font-family`;层级:页面标题 / Section 标题(`.panel-section-title`)/ +正文 / 辅助文字(`.panel-hint`) / Label / Button Text / Caption / Error·Warning Text。 +字重仅 400 / 500。 + +## 6. 统一圆角、边框和阴影 + +- Border Radius:仅 `--radius-sm/md/lg/pill` +- Border Width:`--border-width`(1px);Border Color:`--border` / `--border-strong` +- Shadow:仅 `--shadow`,且仅用于浮层(抽屉 / 弹窗 / 悬浮卡) + +## 7. 统一交互状态 + +所有交互组件遵循:`Default / Hover / Active / Focus / Disabled / Loading`。 +`Focus` 必须用 `--border-strong` 或 outline 呈现;`Disabled` 统一 `opacity: .45` + `not-allowed`。 + +## 8. 同功能必须使用同组件 + +保存 / 取消 / 删除 / 添加 / 编辑 / 刷新 / 设置 / 关闭 / 确认 → 复用共享 Button·IconButton; +输入框 → 统一 Input;下拉 → 统一 Select;弹窗 → 统一 Dialog;标签 → 统一 Tag; +提示 → 统一 Toast·Alert;开关 → 统一 Switch。**优先复用现有组件,而不是复制 CSS。** + +## 9. 统一布局 + +页面边距、内容最大宽度、Section 间距、Header 高度、Toolbar 高度(`.panel-toolbar`)、 +卡片间距、Dialog 布局、操作区布局保持一致;跨页面切换应感觉属于同一应用。 + +## 10. 不允许局部「特立独行」 + +消除:按钮特别大 / 没有边框 / Hover 才出现背景 / 不同圆角 / 不同字体 / 不同颜色体系 / +同功能不同外观 / 相同组件多套实现。无明确 UX 理由不得为单页面创造特殊样式。 + +## 11. 设计系统资产 + +``` +media/shared/tokens.css Colors · Typography · Spacing · Radius · Border · Shadow · Sizes +media/shared/controls.css Button(主/次/迷你/幽灵/图标) · Input · Select · Checkbox/Switch + · Tag · Card · Toolbar · SectionTitle · Empty/Broken/Hint · Divider + · 可点击性硬规范([role=button]、真按钮/行级区分层清单) +``` + +新增面板接入方式(**三步,缺一步样式就静默失效**): + +1. `index.html` 按顺序引入 `__SHARED_TOKENS_URI__` → `__SHARED_CONTROLS_URI__` → 面板自身样式 + (顺序即层叠优先级:面板可以覆盖共享层); +2. 宿主 `buildHtml` 用 `applySharedAssets(webview, extensionUri, html)` 替换两个占位符 + (`src/webviewHtml.ts`,唯一实现;不要在面板里再抄一份 `asWebviewUri` 组装); +3. `localResourceRoots` 若没有放行整个 `extensionUri`,必须补 `sharedResourceRoot(extensionUri)` + (收紧到 `media/<面板>` 的面板否则加载不到 shared 资源)。 + +**自动审计**:`node scripts/test_design_system.js` —— 校验上面三步 + 面板 CSS 无字面量色 / +无 `--vscode-*` 直引 / 圆角字号走 scale / `var()` 全部有定义。 +例外:canvas 绘图色(标注框描边等)属于**图像内容**而非 UI 主题,不受色值约束。 + +## 12. 最终验收标准(全局 UI Audit 清单) + +1. 所有页面使用同一套设计语言 +2. 同类组件视觉一致 +3. 所有可点击控件默认状态即可识别 +4. Hover 不再承担「显示这是按钮」的职责 +5. 没有裸文字按钮 +6. 没有与容器完全融为一体的按钮 +7. 颜色、字体、间距、圆角、边框、阴影统一 +8. 相同功能复用相同组件 +9. 不存在只在某一个页面使用的无必要特殊样式 +10. 从任意页面切换到另一个页面,都应明显感觉属于同一个完整的产品 + +--- + +## 落地进度 + +| 阶段 | 内容 | 状态 | +|---|---|---| +| S1 | 建立 `media/shared/{tokens,controls}.css`;console 接入(视觉零变化,4 个 jsdom 测试守护);宿主 `localResourceRoots` 放行 | ✅ 已完成 | +| S2 | templatePanel / templateAssetPanel 迁移 | ✅ 已完成 | +| S3 | tempScreenshots / annotationPanel 迁移 | ✅ 已完成 | +| S4 | characterManager 迁移(token 别名化 + 基础控件去重 + 标签页/圆角/字号 scale 化) | ✅ 已完成 | +| S5 | 全局 Audit:`scripts/test_design_system.js` 静态审计已就位;剩 i18n 硬编码清理、真实主题截图人工复核 | 🔄 进行中 | + +**六个面板全部接入共享层**(2026-09-23,`feat/sidebar-styling`): +`console` / `templatePanel` / `templateAssetPanel` / `tempScreenshots` / `annotationPanel` / +`characterManager` —— 全部走同一套 token 与控件层。收口成果(自动审计断言): + +| 指标 | 迁移前 | 迁移后 | +|---|---|---| +| 面板 CSS 直引 `--vscode-*` | 6 个文件、共 40+ 处 | **0**(唯一允许处 = `media/shared/tokens.css`) | +| 面板 CSS 字面量色(hex/rgb) | 6 个文件 | **0**(canvas 绘图色除外,见上) | +| 裸 px 圆角 / 字号 | 17 处 / 多处 | **0**(`--radius-*` / `--font-*`) | +| 工具条按钮 | 各自一套 `padding/border/background` | 共享 `.mini-btn` / `.icon-btn` / `.mini-btn.is-active` | + +**迁移踩到的坑(已修)**:`src/webviewHtml.ts` 顶层 `import vscode` 会让 `scripts/` +下的纯函数单测崩在 `Cannot find module 'vscode'` —— 改为类型导入 + 函数体内延迟 +`require`,纯函数保持可独立测试。 + +**已完成的具体整改**(console): +- 「⚙ 参数」按钮:移除 `--warn` 黄色警示语义,全部状态中性化(默认 = 控件面 + 描边; + 已修改 = 描边强化;打开中 = 控件面加深) +- 可点击元素规范(分层,2026-09-23 定案): + - **真按钮** = `--bg-control` + 描边 → 分组折叠按键 / `.btn-mini` / 图标按钮 / 危险按钮 + - **行级可点击区** = `--bg-row` 极浅色调 + 无描边 → 启动设置折叠头 / 任务分组头 / + kind 级组头 / 卡片头 / 未选中分段 tab / 游戏状态行 + - 首版曾把 `--bg-control` 一刀切应用到全部可点击元素,导致侧栏成为层层方块墙 + (用户反馈「好丑」)——按钮与行头是两种权重,不可同色同框 +- `[hidden]` 兜底、折叠状态持久化(uiState)、i18n 六语言键对等 + +**其他面板的具体整改**: +- 缩略图/tile 上的悬浮按钮(`templatePanel.open-btn`、`templateAssetPanel.actions`、 + `tempScreenshots` 卡片操作)**由「仅 hover 可见」改为常驻可见**,底色改控件面 + 描边, + 去掉了 `rgba(0,0,0,.55)+#fff`(浅色主题下是黑块) +- 模态遮罩/投影统一走 `--scrim` / `--shadow-sm` / `--shadow-lg` +- `annotationPanel` 工具条 ↔ 模态按钮改用共享 `.mini-btn`;`characterManager` 移除本地 + `:root` token 定义(保留短别名指向共享 token)、基础 `button/input` 声明去重、标签页 + 改为「未选中浅底色 / 选中控件面」 diff --git a/media/annotationPanel/index.html b/media/annotationPanel/index.html index b22f391..623da08 100644 --- a/media/annotationPanel/index.html +++ b/media/annotationPanel/index.html @@ -3,19 +3,21 @@ + + -
- - - - - +
+ + + + +
- - + +
@@ -40,8 +42,8 @@

Bounding Box

- - + +
diff --git a/media/annotationPanel/style.css b/media/annotationPanel/style.css index 9e9c41b..19423e8 100644 --- a/media/annotationPanel/style.css +++ b/media/annotationPanel/style.css @@ -1,120 +1,89 @@ - * { box-sizing: border-box; margin: 0; padding: 0; } - body { - color: var(--vscode-editor-foreground); - background: var(--vscode-editor-background); - font-family: var(--vscode-font-family); - font-size: var(--vscode-font-size, 13px); - overflow: hidden; - height: 100vh; - display: flex; - flex-direction: column; - } - .toolbar { - display: flex; - align-items: center; - gap: 6px; - padding: 6px 10px; - border-bottom: 1px solid var(--vscode-panel-border); - flex-shrink: 0; - flex-wrap: wrap; - } - .toolbar button { - padding: 4px 10px; - border-radius: 3px; - border: 1px solid var(--vscode-button-secondaryBackground, rgba(128,128,128,.3)); - background: var(--vscode-button-secondaryBackground); - color: var(--vscode-button-secondaryForeground); - cursor: pointer; - font-size: 12px; - white-space: nowrap; - } - .toolbar button:hover { background: var(--vscode-button-secondaryHoverBackground); } - .toolbar button.active { - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); - border-color: var(--vscode-button-background); - } - .toolbar .spacer { flex: 1; } - .toolbar .info { font-size: 11px; opacity: .7; white-space: nowrap; } - .canvas-wrap { - flex: 1; - position: relative; - overflow: hidden; - } - canvas { - display: block; - width: 100%; - height: 100%; - cursor: crosshair; - } - .color-bar { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 10px; - border-top: 1px solid var(--vscode-panel-border); - font-size: 11px; - flex-shrink: 0; - } - .color-swatch { - width: 14px; height: 14px; - border: 1px solid gray; - border-radius: 2px; - flex-shrink: 0; - } - .modal-overlay { - display: none; - position: fixed; - inset: 0; - background: rgba(0,0,0,.45); - z-index: 100; - align-items: center; - justify-content: center; - } - .modal-overlay.visible { display: flex; } - .modal { - background: var(--vscode-editorWidget-background); - border: 1px solid var(--vscode-panel-border); - border-radius: 8px; - padding: 18px 22px; - min-width: 340px; - box-shadow: 0 8px 32px rgba(0,0,0,.4); - } - .modal h3 { margin-bottom: 12px; font-size: 14px; } - .modal .row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; } - .modal label { width: 70px; text-align: right; font-size: 12px; opacity: .8; } - .modal input[type="text"], - .modal input[type="number"] { - flex: 1; - padding: 4px 8px; - border-radius: 3px; - border: 1px solid var(--vscode-input-border, transparent); - background: var(--vscode-input-background); - color: var(--vscode-input-foreground); - font-size: 13px; - outline: none; - } - .modal input:focus { border-color: var(--vscode-focusBorder); } - .modal .error { color: var(--vscode-errorForeground); font-size: 11px; margin-top: 4px; } - .modal .actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; } - .modal .actions button { - padding: 5px 16px; - border-radius: 3px; - border: 1px solid var(--vscode-button-secondaryBackground, rgba(128,128,128,.3)); - background: var(--vscode-button-secondaryBackground); - color: var(--vscode-button-secondaryForeground); - cursor: pointer; - font-size: 12px; - } - .modal .actions button.primary { - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); - border-color: var(--vscode-button-background); - } - .empty { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - opacity: .5; - } +/* 标注面板(共享层见 media/shared/tokens.css + controls.css) + 工具条/模态按钮用共享 .mini-btn / .icon-btn;本文件保留画布、色条与模态排版。 */ + +* { box-sizing: border-box; margin: 0; padding: 0; } + +body { + color: var(--text-primary); + background: var(--bg-page); + font-family: var(--font-ui); + font-size: var(--font-size-root); + overflow: hidden; + height: 100vh; + display: flex; + flex-direction: column; +} + +.toolbar { + border-bottom: var(--border-width) solid var(--border); + flex-shrink: 0; +} +.toolbar .spacer { flex: 1; } +.toolbar .info { color: var(--text-muted); font-size: var(--font-xs); white-space: nowrap; } + +.canvas-wrap { + flex: 1; + position: relative; + overflow: hidden; +} + +canvas { + display: block; + width: 100%; + height: 100%; + cursor: crosshair; +} + +.color-bar { + display: flex; + align-items: center; + gap: var(--space-sm); + padding: var(--space-xs) var(--space-md); + border-top: var(--border-width) solid var(--border); + color: var(--text-muted); + font-size: var(--font-xs); + flex-shrink: 0; +} +.color-swatch { + width: 14px; + height: 14px; + border: var(--border-width) solid var(--border); + border-radius: var(--radius-sm); + flex-shrink: 0; +} + +/* ── 模态(BBox 输入)────────────────────────────────────────────────── */ +.modal-overlay { + display: none; + position: fixed; + inset: 0; + background: var(--scrim); + z-index: 100; + align-items: center; + justify-content: center; +} +.modal-overlay.visible { display: flex; } + +.modal { + background: var(--bg-container-raised); + border: var(--border-width) solid var(--border); + border-radius: var(--radius-lg); + padding: var(--space-lg) 22px; + min-width: 340px; + box-shadow: var(--shadow-lg); +} +.modal h3 { margin-bottom: var(--space-sm); font-size: var(--font-lg); font-weight: var(--weight-bold); } +.modal .row { display: flex; align-items: center; gap: var(--space-sm); margin-bottom: var(--space-sm); } +.modal label { width: 70px; text-align: right; color: var(--text-muted); font-size: var(--font-sm); } +.modal input[type="text"], +.modal input[type="number"] { flex: 1; } +.modal .error { color: var(--err); font-size: var(--font-xs); margin-top: var(--space-xs); } +.modal .actions { display: flex; justify-content: flex-end; gap: var(--space-sm); margin-top: var(--space-lg); } + +.empty { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-muted); +} diff --git a/media/characterManager/characterManager.css b/media/characterManager/characterManager.css index 5556bd2..5323e71 100644 --- a/media/characterManager/characterManager.css +++ b/media/characterManager/characterManager.css @@ -1,52 +1,42 @@ :root { - --border: var(--vscode-panel-border, rgba(128,128,128,.28)); - --muted: var(--vscode-descriptionForeground, rgba(180,180,180,.75)); - --surface: var(--vscode-editorWidget-background, rgba(128,128,128,.06)); - --surface2: var(--vscode-sideBar-background, rgba(128,128,128,.05)); - --selected: var(--vscode-list-activeSelectionBackground, rgba(0,122,204,.28)); - --selected-fg: var(--vscode-list-activeSelectionForeground, inherit); - --hover: var(--vscode-list-hoverBackground, rgba(128,128,128,.12)); - --accent: var(--vscode-focusBorder, #007acc); - --danger: var(--vscode-errorForeground, #f48771); - --warning: var(--vscode-editorWarning-foreground, #cca700); - --info: var(--vscode-editorInfo-foreground, #75beff); + /* 面板内沿用短别名,全部指向共享 token(media/shared/tokens.css)—— + 颜色只有一个真源,这里的名字只是历史可读性写法。 + --border / --muted / --surface 直接来自共享层。 */ + --surface2: var(--bg-container-raised); + --selected: var(--selected-bg); + --hover: var(--surface-hover); + --accent: var(--border-strong); + --danger: var(--err); + --warning: var(--warn); + --info: var(--run); } * { box-sizing: border-box; } html, body { height: 100%; } body { margin: 0; - color: var(--vscode-editor-foreground); - background: var(--vscode-editor-background); - font-family: var(--vscode-font-family); - font-size: var(--vscode-font-size, 13px); + color: var(--text-primary); + background: var(--bg-page); + font-family: var(--font-ui); + font-size: var(--font-size-root); overflow: hidden; } -button, input, select, textarea { font: inherit; color: inherit; } -button { - border: 1px solid transparent; - border-radius: 4px; - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); - padding: 5px 10px; - cursor: pointer; -} -button:hover { background: var(--vscode-button-hoverBackground); } -button.secondary { background: var(--vscode-button-secondaryBackground); color: var(--vscode-button-secondaryForeground); } -button.secondary:hover { background: var(--vscode-button-secondaryHoverBackground); } +/* 基础 button / input / select / textarea 视觉由共享层提供(media/shared/controls.css), + 这里只保留本面板的语义变体与图标按钮尺寸。 */ +button, input, select, textarea { font: inherit; } button.icon { padding: 4px 7px; min-width: 28px; } button.utility-button { width: 30px; height: 30px; border-color: var(--border); - background: var(--surface); - color: var(--vscode-foreground); + background: var(--bg-control); + color: var(--text-primary); padding: 0; - font-size: 15px; - box-shadow: 0 1px 2px rgba(0,0,0,.16); + font-size: var(--font-lg); + box-shadow: var(--shadow-sm); } button.utility-button:hover { border-color: color-mix(in srgb, var(--accent) 65%, var(--border)); - background: var(--hover); + background: var(--bg-control-hover); } button.action-button { display: inline-flex; @@ -56,85 +46,79 @@ button.action-button { min-height: 30px; border-color: var(--border); padding: 4px 10px; - font-weight: 600; + font-weight: var(--weight-medium); line-height: 1.2; white-space: nowrap; - box-shadow: 0 1px 2px rgba(0,0,0,.2); + box-shadow: var(--shadow-sm); } -button.action-button:hover { box-shadow: 0 2px 5px rgba(0,0,0,.24); } +button.action-button:hover { box-shadow: var(--shadow); } button.action-button:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; } -button.action-button.compact { min-height: 26px; padding: 3px 8px; font-size: 11px; } +button.action-button.compact { min-height: 26px; padding: 3px 8px; font-size: var(--font-xs); } button.action-add, button.action-save { border-color: color-mix(in srgb, var(--accent) 75%, var(--border)); - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); + background: var(--btn-bg); + color: var(--btn-fg); } -button.action-add:hover, button.action-save:hover { background: var(--vscode-button-hoverBackground); } +button.action-add:hover, button.action-save:hover { background: var(--btn-bg-hover); } button.action-edit { border-color: color-mix(in srgb, var(--accent) 65%, var(--border)); - background: var(--vscode-button-secondaryBackground); - color: var(--vscode-button-secondaryForeground); + background: var(--btn-secondary-bg); + color: var(--btn-secondary-fg); } -button.action-edit:hover { background: var(--vscode-button-secondaryHoverBackground); } +button.action-edit:hover { background: var(--btn-secondary-bg-hover); } button.action-open { border-color: color-mix(in srgb, var(--info) 60%, var(--border)); background: color-mix(in srgb, var(--info) 9%, var(--surface)); - color: var(--vscode-foreground); + color: var(--text-primary); } button.action-open:hover { background: color-mix(in srgb, var(--info) 18%, var(--hover)); } button.action-cancel { border-color: var(--border); - background: var(--vscode-button-secondaryBackground); - color: var(--vscode-button-secondaryForeground); + background: var(--btn-secondary-bg); + color: var(--btn-secondary-fg); } -button.action-cancel:hover { background: var(--vscode-button-secondaryHoverBackground); } +button.action-cancel:hover { background: var(--btn-secondary-bg-hover); } button.action-delete { border-color: color-mix(in srgb, var(--danger) 70%, var(--border)); - background: var(--vscode-inputValidation-errorBackground, #5a1d1d); - color: var(--vscode-errorForeground, #fff); -} -button.action-delete:hover { background: color-mix(in srgb, var(--danger) 32%, var(--vscode-inputValidation-errorBackground, #5a1d1d)); } -input, select, textarea { - border: 1px solid var(--vscode-input-border, transparent); - border-radius: 3px; - background: var(--vscode-input-background); - color: var(--vscode-input-foreground); - padding: 5px 8px; - outline: none; + background: var(--danger-bg); + color: var(--btn-fg); +} +button.action-delete:hover { + background: color-mix(in srgb, var(--danger) 32%, var(--danger-bg)); } input:focus, select:focus, textarea:focus { border-color: var(--accent); } -input::placeholder, textarea::placeholder { color: var(--vscode-input-placeholderForeground); } +input::placeholder, textarea::placeholder { color: var(--input-placeholder); } #app { height: 100%; display: flex; flex-direction: column; } .topbar { flex: 0 0 auto; border-bottom: 1px solid var(--border); - background: var(--vscode-titleBar-activeBackground, var(--vscode-editor-background)); + background: var(--bg-container-raised); } .title-row { min-height: 42px; display: flex; gap: 10px; align-items: center; padding: 7px 12px; } -.title { font-size: 15px; font-weight: 700; white-space: nowrap; } -#project { color: var(--muted); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.title { font-size: var(--font-lg); font-weight: var(--weight-bold); white-space: nowrap; } +#project { color: var(--muted); font-size: var(--font-xs); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .spacer { flex: 1; } #globalSearch { width: min(360px, 35vw); } .tabs { display: flex; gap: 2px; padding: 0 10px; } .tab { border: none; border-bottom: 2px solid transparent; - border-radius: 0; - background: transparent; + border-radius: var(--radius-sm) var(--radius-sm) 0 0; + background: var(--bg-row); color: var(--muted); padding: 7px 11px 6px; } -.tab:hover { background: var(--hover); color: inherit; } -.tab.active { border-bottom-color: var(--accent); color: inherit; font-weight: 600; } +.tab:hover { background: var(--bg-row-hover); color: var(--text-primary); } +.tab.active { border-bottom-color: var(--accent); background: var(--bg-control); color: var(--text-primary); font-weight: var(--weight-medium); } .badge { display: inline-block; min-width: 18px; margin-left: 5px; - border-radius: 10px; + border-radius: var(--radius-pill); padding: 0 5px; - background: var(--vscode-badge-background); - color: var(--vscode-badge-foreground); - font-size: 10px; + background: var(--badge-bg); + color: var(--badge-fg); + font-size: var(--font-2xs); line-height: 17px; text-align: center; } @@ -147,9 +131,9 @@ input::placeholder, textarea::placeholder { color: var(--vscode-input-placeholde border-bottom: 1px solid var(--border); background: var(--surface2); } -.metric { border: 1px solid var(--border); border-radius: 5px; background: var(--surface); padding: 6px 8px; } -.metric-value { font-size: 17px; font-weight: 700; } -.metric-label { color: var(--muted); font-size: 10px; margin-top: 1px; } +.metric { border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); padding: 6px 8px; } +.metric-value { font-size: var(--font-xl); font-weight: var(--weight-bold); } +.metric-label { color: var(--muted); font-size: var(--font-2xs); margin-top: 1px; } .metric.error .metric-value { color: var(--danger); } .metric.warning .metric-value { color: var(--warning); } .content { flex: 1 1 auto; min-height: 0; position: relative; } @@ -172,14 +156,14 @@ input::placeholder, textarea::placeholder { color: var(--vscode-input-placeholde .filter-check { grid-column: 1 / -1; color: var(--muted); - font-size: 11px; + font-size: var(--font-xs); display: flex; align-items: center; gap: 5px; } .filter-check input { width: auto; } -.list-head { display: flex; align-items: center; padding: 6px 9px; color: var(--muted); font-size: 11px; border-bottom: 1px solid var(--border); } -.scroll { overflow: auto; min-height: 0; scrollbar-color: var(--vscode-scrollbarSlider-background) transparent; } +.list-head { display: flex; align-items: center; padding: 6px 9px; color: var(--muted); font-size: var(--font-xs); border-bottom: 1px solid var(--border); } +.scroll { overflow: auto; min-height: 0; scrollbar-color: var(--scrollbar) transparent; } #characterList { flex: 1 1 auto; } .character-item { display: grid; @@ -187,7 +171,7 @@ input::placeholder, textarea::placeholder { color: var(--vscode-input-placeholde gap: 8px; align-items: center; padding: 7px 9px; - border-bottom: 1px solid rgba(128,128,128,.1); + border-bottom: var(--border-width) solid var(--border); cursor: pointer; } .character-item:hover { background: var(--hover); } @@ -199,26 +183,26 @@ input::placeholder, textarea::placeholder { color: var(--vscode-input-placeholde align-items: center; justify-content: center; border-radius: 50%; - font-weight: 700; + font-weight: var(--weight-bold); background: color-mix(in srgb, var(--accent) 22%, transparent); border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent); overflow: hidden; flex: 0 0 auto; } .avatar img { display: block; width: 100%; height: 100%; object-fit: contain; } -.avatar-large { width: 64px; height: 64px; font-size: 22px; } +.avatar-large { width: 64px; height: 64px; font-size: var(--font-xl); } .item-main { min-width: 0; } -.item-name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.item-sub { color: var(--muted); font-size: 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.item-count { color: var(--muted); font-size: 10px; } +.item-name { font-weight: var(--weight-medium); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.item-sub { color: var(--muted); font-size: var(--font-2xs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.item-count { color: var(--muted); font-size: var(--font-2xs); } .issue-dot { display: inline-block; min-width: 16px; padding: 0 4px; margin-left: 4px; text-align: center; - border-radius: 8px; - color: #fff; + border-radius: var(--radius-md); + color: var(--on-accent); background: var(--warning); } .issue-dot.error { background: var(--danger); } @@ -235,18 +219,18 @@ input::placeholder, textarea::placeholder { color: var(--vscode-input-placeholde } .character-identity { display: flex; align-items: flex-start; gap: 12px; min-width: 0; } .character-actions { display: flex; gap: 6px; flex-wrap: wrap; } -.character-title { font-size: 23px; font-weight: 700; } -.character-id { color: var(--muted); font-family: var(--vscode-editor-font-family); font-size: 11px; } +.character-title { font-size: var(--font-xl); font-weight: var(--weight-bold); } +.character-id { color: var(--muted); font-family: var(--font-mono); font-size: var(--font-xs); } .chips { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; } .chip { display: inline-flex; align-items: center; gap: 4px; border: 1px solid var(--border); - border-radius: 12px; + border-radius: var(--radius-pill); background: var(--surface); padding: 2px 7px; - font-size: 11px; + font-size: var(--font-xs); } .chip.effect { cursor: pointer; padding-right: 5px; } .chip.effect:hover { border-color: var(--accent); } @@ -258,14 +242,14 @@ input::placeholder, textarea::placeholder { color: var(--vscode-input-placeholde color: var(--danger); background: color-mix(in srgb, var(--danger) 10%, var(--surface)); } -.effect-chip-name { font-weight: 600; } +.effect-chip-name { font-weight: var(--weight-medium); } .effect-chip-id, .effect-chip-count { border-left: 1px solid var(--border); padding-left: 5px; color: var(--muted); - font-family: var(--vscode-editor-font-family); - font-size: 9px; + font-family: var(--font-mono); + font-size: var(--font-2xs); } .chip.effect.negative .effect-chip-id, .chip.effect.negative .effect-chip-count { @@ -273,31 +257,31 @@ input::placeholder, textarea::placeholder { color: var(--vscode-input-placeholde color: inherit; } .locales-inline { display: grid; grid-template-columns: repeat(auto-fit, minmax(145px, 1fr)); gap: 4px 10px; margin-top: 8px; } -.locale-name { display: flex; gap: 6px; min-width: 0; font-size: 11px; } +.locale-name { display: flex; gap: 6px; min-width: 0; font-size: var(--font-xs); } .locale-key { color: var(--muted); width: 48px; flex: 0 0 auto; } .locale-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.section-title { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 700; margin: 13px 0 7px; } +.section-title { display: flex; align-items: center; gap: 6px; font-size: var(--font-md); font-weight: var(--weight-bold); margin: 13px 0 7px; } .section-title > .action-button { margin-left: 3px; } -.skill-card { border: 1px solid var(--border); border-radius: 6px; background: var(--surface); margin-bottom: 8px; overflow: hidden; } +.skill-card { border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface); margin-bottom: 8px; overflow: hidden; } .skill-head { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; padding: 8px 10px; border-bottom: 1px solid var(--border); } -.skill-name { font-size: 14px; font-weight: 700; } -.skill-id { color: var(--muted); font-family: var(--vscode-editor-font-family); font-size: 10px; } +.skill-name { font-size: var(--font-lg); font-weight: var(--weight-bold); } +.skill-id { color: var(--muted); font-family: var(--font-mono); font-size: var(--font-2xs); } .clickable-id, .clickable-name { cursor: pointer; } .skill-meta { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 4px; } .skill-meta .action-button { align-self: center; } .skill-body { padding: 8px 10px 10px; } .description { line-height: 1.55; white-space: pre-wrap; } -.kv-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 5px 12px; margin: 7px 0; font-size: 11px; } +.kv-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 5px 12px; margin: 7px 0; font-size: var(--font-xs); } .kv-key { color: var(--muted); margin-right: 5px; } .effect-line { margin-top: 7px; } -.line-label { color: var(--muted); font-size: 11px; margin-bottom: 4px; } +.line-label { color: var(--muted); font-size: var(--font-xs); margin-bottom: 4px; } .trigger-mode-wrap { display: flex; align-items: flex-start; gap: 0; margin-top: 6px; border: 1px solid; - border-radius: 6px; + border-radius: var(--radius-md); overflow: hidden; } .trigger-mode-wrap.mode-any { @@ -333,8 +317,8 @@ input::placeholder, textarea::placeholder { color: var(--vscode-input-placeholde .trigger-mode-wrap.mode-any .trigger-mode-icon svg { stroke: var(--warning); } .trigger-mode-wrap.mode-all .trigger-mode-icon svg { stroke: var(--info); } .trigger-mode-label { - font-size: 9px; - font-weight: 700; + font-size: var(--font-2xs); + font-weight: var(--weight-bold); letter-spacing: .8px; text-transform: uppercase; margin-top: 3px; @@ -357,19 +341,19 @@ input::placeholder, textarea::placeholder { color: var(--vscode-input-placeholde border-radius: 0 4px 4px 0; } .enhancement-head { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; min-height: 30px; } -.enhancement-name { flex: 1 1 auto; min-width: 0; font-weight: 700; } +.enhancement-name { flex: 1 1 auto; min-width: 0; font-weight: var(--weight-bold); } .enhancement-actions { display: flex; flex: 0 0 auto; flex-wrap: wrap; gap: 6px; } .enhancement-actions .action-button { min-width: 72px; } .enhancement-trigger { margin-top: 4px; line-height: 1.4; } -.pulse { color: var(--info); font-size: 10px; margin-left: 5px; } +.pulse { color: var(--info); font-size: var(--font-2xs); margin-left: 5px; } .add-enhancement-action { margin-top: 8px; } .empty { padding: 36px 20px; text-align: center; color: var(--muted); line-height: 1.7; } .table-wrap { width: 100%; overflow: auto; } -table { border-collapse: collapse; width: 100%; font-size: 11px; } +table { border-collapse: collapse; width: 100%; font-size: var(--font-xs); } th, td { border: 1px solid var(--border); padding: 6px 8px; text-align: left; vertical-align: top; } -th { position: sticky; top: 0; z-index: 2; background: var(--vscode-editorWidget-background); white-space: nowrap; } -td.sticky, th.sticky { position: sticky; left: 0; z-index: 3; background: var(--vscode-editor-background); min-width: 175px; } -th.sticky { z-index: 4; background: var(--vscode-editorWidget-background); } +th { position: sticky; top: 0; z-index: 2; background: var(--bg-container); white-space: nowrap; } +td.sticky, th.sticky { position: sticky; left: 0; z-index: 3; background: var(--bg-page); min-width: 175px; } +th.sticky { z-index: 4; background: var(--bg-container); } .missing-cell { background: color-mix(in srgb, var(--danger) 12%, transparent); color: var(--danger); } .effect-toolbar, .issues-toolbar, .locale-toolbar { flex: 0 0 auto; @@ -389,19 +373,19 @@ th.sticky { z-index: 4; background: var(--vscode-editorWidget-background); } padding: 8px 7px; } .effect-card:hover { background: var(--hover); } -.effect-name { font-size: 13px; font-weight: 700; } -.effect-id { font-family: var(--vscode-editor-font-family); font-weight: 700; word-break: break-all; } +.effect-name { font-size: var(--font-md); font-weight: var(--weight-bold); } +.effect-id { font-family: var(--font-mono); font-weight: var(--weight-bold); word-break: break-all; } .effect-desc { line-height: 1.4; } -.usage-list { margin-top: 5px; color: var(--muted); font-size: 10px; } +.usage-list { margin-top: 5px; color: var(--muted); font-size: var(--font-2xs); } .usage { cursor: pointer; margin: 2px 0; } .usage:hover { color: inherit; text-decoration: underline; } -.severity { font-weight: 700; text-transform: uppercase; font-size: 10px; } +.severity { font-weight: var(--weight-bold); text-transform: uppercase; font-size: var(--font-2xs); } .severity.error { color: var(--danger); } .severity.warning { color: var(--warning); } .severity.info { color: var(--info); } .issue-row { display: grid; grid-template-columns: 62px 145px minmax(0, 1fr) auto; gap: 9px; align-items: start; padding: 7px 6px; border-bottom: 1px solid var(--border); } .issue-row:hover { background: var(--hover); } -.issue-code { color: var(--muted); font-family: var(--vscode-editor-font-family); font-size: 10px; } +.issue-code { color: var(--muted); font-family: var(--font-mono); font-size: var(--font-2xs); } #loading, #fatal { position: absolute; inset: 0; @@ -409,7 +393,7 @@ th.sticky { z-index: 4; background: var(--vscode-editorWidget-background); } display: none; align-items: center; justify-content: center; - background: color-mix(in srgb, var(--vscode-editor-background) 92%, transparent); + background: color-mix(in srgb, var(--bg-page) 92%, transparent); padding: 24px; text-align: center; } @@ -423,7 +407,7 @@ th.sticky { z-index: 4; background: var(--vscode-editorWidget-background); } align-items: center; justify-content: center; padding: 24px; - background: rgba(0,0,0,.58); + background: var(--scrim); } .modal-backdrop.show { display: flex; } .modal { @@ -432,27 +416,27 @@ th.sticky { z-index: 4; background: var(--vscode-editorWidget-background); } display: flex; flex-direction: column; border: 1px solid var(--border); - border-radius: 7px; - background: var(--vscode-editorWidget-background); - box-shadow: 0 12px 36px rgba(0,0,0,.42); + border-radius: var(--radius-lg); + background: var(--bg-container); + box-shadow: var(--shadow-lg); } .modal-head, .modal-actions { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--border); } .modal-actions { justify-content: flex-end; border-bottom: none; border-top: 1px solid var(--border); } -.modal-title { font-size: 15px; font-weight: 700; } +.modal-title { font-size: var(--font-lg); font-weight: var(--weight-bold); } .modal-body { overflow: auto; padding: 12px; } .modal-error { padding: 0 12px; } .form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 9px 12px; } .form-field { min-width: 0; } .form-field.wide { grid-column: 1 / -1; } -.form-field label { display: block; margin-bottom: 3px; color: var(--muted); font-size: 11px; } +.form-field label { display: block; margin-bottom: 3px; color: var(--muted); font-size: var(--font-xs); } .form-field input, .form-field select, .form-field textarea { width: 100%; } .form-field textarea { min-height: 76px; resize: vertical; } -.form-field textarea.json { min-height: 125px; font-family: var(--vscode-editor-font-family); font-size: 11px; } +.form-field textarea.json { min-height: 125px; font-family: var(--font-mono); font-size: var(--font-xs); } .locked-notice { grid-column: 1 / -1; border: 1px solid color-mix(in srgb, var(--info) 55%, var(--border)); border-left: 3px solid var(--info); - border-radius: 4px; + border-radius: var(--radius-sm); background: color-mix(in srgb, var(--info) 8%, transparent); padding: 8px 10px; line-height: 1.45; @@ -461,8 +445,8 @@ th.sticky { z-index: 4; background: var(--vscode-editorWidget-background); } min-height: 30px; border: 1px solid var(--border); border-left: 3px solid var(--muted); - border-radius: 3px; - background: color-mix(in srgb, var(--vscode-input-background) 55%, transparent); + border-radius: var(--radius-sm); + background: color-mix(in srgb, var(--input-bg) 55%, transparent); padding: 6px 8px; color: var(--muted); cursor: default; @@ -470,14 +454,14 @@ th.sticky { z-index: 4; background: var(--vscode-editorWidget-background); } } .readonly-value.multiline { min-height: 76px; line-height: 1.5; white-space: pre-wrap; } .effect-editor { display: grid; grid-template-columns: minmax(230px, .8fr) minmax(300px, 1.2fr); gap: 10px; } -.effect-select { min-height: 210px; font-family: var(--vscode-editor-font-family); font-size: 11px; } -.effect-editor-hint { color: var(--muted); font-size: 10px; margin-top: 4px; } +.effect-select { min-height: 210px; font-family: var(--font-mono); font-size: var(--font-xs); } +.effect-editor-hint { color: var(--muted); font-size: var(--font-2xs); margin-top: 4px; } .effect-params { display: flex; flex-direction: column; gap: 6px; max-height: 310px; overflow: auto; } -.effect-param-row { border: 1px solid var(--border); border-radius: 5px; padding: 6px 7px; background: var(--surface); } -.effect-param-title { font-family: var(--vscode-editor-font-family); font-size: 11px; font-weight: 700; margin-bottom: 5px; } -.effect-param-desc { color: var(--muted); font-size: 10px; margin-bottom: 5px; } +.effect-param-row { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 6px 7px; background: var(--surface); } +.effect-param-title { font-family: var(--font-mono); font-size: var(--font-xs); font-weight: var(--weight-bold); margin-bottom: 5px; } +.effect-param-desc { color: var(--muted); font-size: var(--font-2xs); margin-bottom: 5px; } .effect-param-grid { display: grid; grid-template-columns: repeat(4, minmax(70px, 1fr)); gap: 5px; } -.effect-param-grid label { color: var(--muted); font-size: 9px; } +.effect-param-grid label { color: var(--muted); font-size: var(--font-2xs); } .effect-param-grid input, .effect-param-grid select { width: 100%; margin-top: 2px; } .form-check { display: flex; align-items: center; gap: 6px; min-height: 30px; } .form-check input { width: auto; } diff --git a/media/characterManager/index.html b/media/characterManager/index.html index 5e81662..c4eb05f 100644 --- a/media/characterManager/index.html +++ b/media/characterManager/index.html @@ -5,7 +5,9 @@ - + + +
diff --git a/media/console/console.css b/media/console/console.css index fa15be8..f54c2dd 100644 --- a/media/console/console.css +++ b/media/console/console.css @@ -1,58 +1,4 @@ -:root { - color-scheme: light dark; - --surface: var(--vscode-editorWidget-background, color-mix(in srgb, var(--vscode-editor-background) 92%, var(--vscode-foreground) 8%)); - --surface-raised: var(--vscode-sideBar-background, var(--surface)); - --surface-hover: var(--vscode-list-hoverBackground, color-mix(in srgb, var(--surface) 86%, var(--vscode-focusBorder) 14%)); - --border: var(--vscode-panel-border, color-mix(in srgb, var(--vscode-foreground) 18%, transparent)); - --border-strong: var(--vscode-focusBorder, #007acc); - --muted: var(--vscode-descriptionForeground, color-mix(in srgb, var(--vscode-editor-foreground) 64%, transparent)); - --ok: var(--vscode-charts-green, #3fb950); - --run: var(--vscode-charts-blue, #4a9eff); - --warn: var(--vscode-charts-yellow, #d7a12c); - --pause: var(--vscode-charts-orange, #d18616); - --err: var(--vscode-errorForeground, #f44); - --radius-sm: 5px; - --radius-md: 8px; - --radius-lg: 11px; - --shadow: 0 4px 14px color-mix(in srgb, #000 15%, transparent); -} - -* { box-sizing: border-box; } - -html, body { min-height: 100%; } - -body { - margin: 0; - color: var(--vscode-editor-foreground); - background: var(--vscode-editor-background); - font-family: var(--vscode-font-family); - font-size: var(--vscode-font-size, 13px); -} - -button, -input, -select, -textarea { font: inherit; } - -button { - min-height: 30px; - border: 1px solid transparent; - border-radius: var(--radius-sm); - padding: 5px 11px; - cursor: pointer; - color: var(--vscode-button-foreground); - background: var(--vscode-button-background); - transition: background-color .12s ease, border-color .12s ease, opacity .12s ease; -} - -button:hover:not(:disabled) { background: var(--vscode-button-hoverBackground); } -button:focus-visible { outline: 1px solid var(--border-strong); outline-offset: 2px; } -button:disabled { cursor: not-allowed; opacity: .45; } -button.secondary { - color: var(--vscode-button-secondaryForeground); - background: var(--vscode-button-secondaryBackground); -} -button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondaryHoverBackground); } +/* 依赖 media/shared/tokens.css + media/shared/controls.css(由宿主在 index.html 注入,顺序在前) */ /* ═══ 控制台骨架 ═══════════════════════════════════════════════ */ @@ -72,7 +18,7 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar margin: 0 -12px 4px; padding: 8px 12px; border-bottom: 1px solid var(--border); - background: color-mix(in srgb, var(--vscode-editor-background) 94%, transparent); + background: color-mix(in srgb, var(--bg-page) 94%, transparent); backdrop-filter: blur(10px); } @@ -86,7 +32,7 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar .gbar__row--link { cursor: pointer; border-radius: var(--radius-sm); } .gbar__row--link:hover { background: var(--surface-hover); } -.gbar__row--link:hover .gbar__val { color: var(--vscode-editor-foreground); } +.gbar__row--link:hover .gbar__val { color: var(--text-primary); } .gbar__ico { flex: 0 0 auto; @@ -109,11 +55,11 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar opacity: .4; } -.gbar__row.is-ok { color: var(--vscode-editor-foreground); } +.gbar__row.is-ok { color: var(--text-primary); } .gbar__row.is-ok .gbar__dot { background: var(--ok); opacity: 1; box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 18%, transparent); } -.gbar__row.is-run { color: var(--vscode-editor-foreground); } +.gbar__row.is-run { color: var(--text-primary); } .gbar__row.is-run .gbar__dot { background: var(--run); opacity: 1; box-shadow: 0 0 0 3px color-mix(in srgb, var(--run) 18%, transparent); animation: gbar-pulse 1.8s ease-in-out infinite; } -.gbar__row.is-warn { color: var(--vscode-editor-foreground); } +.gbar__row.is-warn { color: var(--text-primary); } .gbar__row.is-warn .gbar__dot { background: var(--pause); opacity: 1; box-shadow: 0 0 0 3px color-mix(in srgb, var(--pause) 18%, transparent); } @keyframes gbar-pulse { @@ -124,8 +70,8 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar flex: 1; min-width: 0; overflow: hidden; - font-size: 12px; - font-weight: 600; + font-size: var(--font-sm); + font-weight: var(--weight-medium); text-overflow: ellipsis; white-space: nowrap; } @@ -140,40 +86,13 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar .gbar__sub { padding-left: 31px; color: var(--muted); - font-size: 11px; + font-size: var(--font-xs); line-height: 1.4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -/* 状态条迷你按钮(随执行器状态机整组显隐) */ -.mini-btn { - min-height: 24px; - padding: 2px 10px; - border-radius: var(--radius-sm); - color: var(--vscode-button-secondaryForeground); - background: var(--vscode-button-secondaryBackground); - font-size: 11px; - font-weight: 500; - white-space: nowrap; -} -.mini-btn:hover:not(:disabled) { background: var(--vscode-button-secondaryHoverBackground); } -.mini-btn--primary { - color: var(--vscode-button-foreground); - background: var(--vscode-button-background); -} -.mini-btn--primary:hover:not(:disabled) { background: var(--vscode-button-hoverBackground); } -.mini-btn--danger { - color: var(--err); - background: transparent; - border-color: var(--border); -} -.mini-btn--danger:hover:not(:disabled) { - background: color-mix(in srgb, var(--err) 12%, transparent); - border-color: color-mix(in srgb, var(--err) 45%, var(--border)); -} - /* 执行器状态文本:颜色随状态(圆点由 .gbar__dot 呈现) */ .executor-state { color: var(--muted); } .executor-state.is-running { color: var(--run); } @@ -195,20 +114,19 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar padding: 4px 0; color: var(--muted); background: transparent; - font-size: 12px; - font-weight: 600; + font-size: var(--font-sm); + font-weight: var(--weight-medium); } .seg__btn:hover:not(.is-active) { background: var(--surface-hover); - color: var(--vscode-editor-foreground); + color: var(--text-primary); } .seg__btn.is-active { border-color: var(--border); - background: var(--surface-raised); - color: var(--vscode-editor-foreground); - box-shadow: 0 1px 2px color-mix(in srgb, #000 6%, transparent); + background: var(--bg-control); + color: var(--text-primary); } /* ── 搜索框 ───────────────────────────────────────────────────── */ @@ -242,12 +160,12 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar .search input { width: 100%; min-height: 30px; - border: 1px solid var(--vscode-input-border, var(--border)); + border: 1px solid var(--input-border); border-radius: var(--radius-sm); padding: 5px 9px 5px 28px; outline: none; - color: var(--vscode-input-foreground); - background: var(--vscode-input-background); + color: var(--input-fg); + background: var(--input-bg); } .search input:focus { border-color: var(--border-strong); } @@ -261,8 +179,8 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar margin: 12px 2px 7px; cursor: pointer; color: var(--muted); - font-size: 11px; - font-weight: 700; + font-size: var(--font-xs); + font-weight: var(--weight-bold); letter-spacing: .03em; user-select: none; } @@ -290,7 +208,7 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); - box-shadow: 0 1px 0 color-mix(in srgb, var(--vscode-foreground) 4%, transparent); + box-shadow: 0 1px 0 color-mix(in srgb, var(--text-primary) 4%, transparent); transition: border-color .14s ease, box-shadow .14s ease; } @@ -317,32 +235,32 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - font-size: 14px; + font-size: var(--font-lg); font-weight: 650; } .task-card__kind { flex: 0 0 auto; border: 1px solid var(--border); - border-radius: 999px; + border-radius: var(--radius-pill); padding: 1px 7px; color: var(--muted); background: color-mix(in srgb, var(--surface-raised) 82%, transparent); - font-size: 10px; + font-size: var(--font-2xs); font-weight: 500; } .task-card__status { flex: 0 0 auto; border: 1px solid currentColor; - border-radius: 999px; + border-radius: var(--radius-pill); padding: 1px 7px; - font-size: 10px; + font-size: var(--font-2xs); font-weight: 500; white-space: nowrap; } .task-card__status.is-disabled { color: var(--muted); } -.task-card__status.is-armed { color: var(--vscode-editor-foreground); border-style: dashed; } +.task-card__status.is-armed { color: var(--text-primary); border-style: dashed; } .task-card__status.is-enqueued { color: var(--run); } .task-card__status.is-polling { color: var(--ok); } .task-card__status.is-queued { color: var(--warn); } @@ -355,7 +273,7 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar min-height: 30px; padding: 0 4px; cursor: pointer; - font-size: 12px; + font-size: var(--font-sm); white-space: nowrap; } .task-card__enable input[type='checkbox'] { @@ -370,8 +288,8 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar margin-top: 3px; overflow: hidden; color: var(--muted); - font-family: var(--vscode-editor-font-family); - font-size: 10px; + font-family: var(--font-mono); + font-size: var(--font-2xs); text-overflow: ellipsis; white-space: nowrap; } @@ -379,7 +297,7 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar .task-card__description { margin-top: 7px; color: var(--muted); - font-size: 11px; + font-size: var(--font-xs); line-height: 1.55; white-space: pre-wrap; } @@ -392,32 +310,31 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar .task-card__actions button { white-space: nowrap; } -/* 参数入口:badge 样式按钮,点击把参数表单搬进抽屉 */ +/* 参数入口:统一「中性图标按钮」形态(design-system 第 3 条:默认态即可识别, + 不靠 hover 首次显形)。全部状态保持中性,不用警示色——「已修改」以描边强化表达。 */ .task-card__config { display: inline-flex; align-items: center; gap: 5px; - min-height: 24px; - border: 1px solid color-mix(in srgb, var(--warn) 45%, var(--border)); - border-radius: 999px; + min-height: var(--control-h-sm, 24px); + border: var(--border-width, 1px) solid var(--border); + border-radius: var(--radius-pill, 999px); padding: 2px 10px; - color: var(--warn); - background: color-mix(in srgb, var(--warn) 10%, transparent); - font-size: 11px; - font-weight: 600; + color: var(--text-primary); + background: var(--bg-control); + font-size: var(--font-xs, 11px); + font-weight: var(--weight-medium, 500); } .task-card__config:hover:not(:disabled) { - background: color-mix(in srgb, var(--warn) 18%, transparent); + background: var(--bg-control-hover); + border-color: var(--border-strong); } .task-card__config.has-overrides { - color: var(--vscode-editor-foreground); - border-color: var(--border); - background: var(--surface-raised); + border-color: var(--border-strong); } .task-card__config.is-open { border-color: var(--border-strong); - color: var(--vscode-editor-foreground); - background: var(--surface-raised); + background: var(--bg-control-active); } /* 卡片内嵌的参数表单:不再就地展开,等待被搬运进抽屉 */ @@ -460,28 +377,28 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar background: color-mix(in srgb, var(--ok) 14%, transparent); } -.connect-card__title { font-size: 13.5px; font-weight: 650; } +.connect-card__title { font-size: var(--font-md); font-weight: 650; } .connect-card__sub { margin: 4px auto 12px; max-width: 340px; color: var(--muted); - font-size: 11px; + font-size: var(--font-xs); line-height: 1.55; } .primary-btn { width: 100%; min-height: 32px; - font-weight: 600; + font-weight: var(--weight-medium); } .secondary-btn { width: 100%; min-height: 32px; - color: var(--vscode-button-secondaryForeground); - background: var(--vscode-button-secondaryBackground); - font-weight: 600; + color: var(--btn-secondary-fg); + background: var(--btn-secondary-bg); + font-weight: var(--weight-medium); } -.secondary-btn:hover:not(:disabled) { background: var(--vscode-button-secondaryHoverBackground); } +.secondary-btn:hover:not(:disabled) { background: var(--btn-secondary-bg-hover); } .tool-row { display: flex; @@ -495,11 +412,11 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar } .tool-row__body { flex: 1; min-width: 0; } -.tool-row__title { font-size: 12.5px; font-weight: 650; } +.tool-row__title { font-size: var(--font-sm); font-weight: 650; } .tool-row__hint { margin-top: 3px; color: var(--muted); - font-size: 10.5px; + font-size: var(--font-2xs); line-height: 1.5; } @@ -525,8 +442,8 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar .switch__track { position: absolute; inset: 0; - border-radius: 9px; - background: color-mix(in srgb, var(--vscode-foreground) 28%, transparent); + border-radius: var(--radius-lg); + background: color-mix(in srgb, var(--text-primary) 28%, transparent); transition: background-color .15s ease; pointer-events: none; } @@ -538,8 +455,8 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar width: 14px; height: 14px; border-radius: 50%; - background: var(--vscode-editor-background); - box-shadow: 0 1px 2px color-mix(in srgb, #000 30%, transparent); + background: var(--bg-page); + box-shadow: var(--shadow-sm); transition: left .15s ease; } .switch input:checked + .switch__track { background: var(--border-strong); } @@ -553,11 +470,11 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar align-items: flex-start; margin-top: 14px; padding: 9px 11px; - border: 1px solid var(--vscode-inputValidation-infoBorder, var(--border)); + border: 1px solid var(--border-strong); border-radius: var(--radius-md); - background: var(--vscode-inputValidation-infoBackground, transparent); + background: var(--info-bg); color: var(--muted); - font-size: 11px; + font-size: var(--font-xs); line-height: 1.5; } .hint-strip svg { @@ -576,7 +493,7 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar position: fixed; inset: 0; z-index: 90; - background: color-mix(in srgb, #000 38%, transparent); + background: var(--scrim); opacity: 0; transition: opacity .18s ease; } @@ -593,7 +510,7 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar width: min(430px, 100%); border-left: 1px solid var(--border); background: var(--surface-raised); - box-shadow: -10px 0 26px color-mix(in srgb, #000 22%, transparent); + box-shadow: var(--shadow-lg); transform: translateX(102%); transition: transform .2s ease; } @@ -607,7 +524,7 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar border-bottom: 1px solid var(--border); } .drawer__title { - font-size: 13.5px; + font-size: var(--font-md); font-weight: 650; overflow: hidden; text-overflow: ellipsis; @@ -616,24 +533,10 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar .drawer__sub { margin-top: 2px; color: var(--muted); - font-size: 10.5px; + font-size: var(--font-2xs); } -.icon-btn { - display: flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - width: 28px; - height: 28px; - min-height: 28px; - margin-left: auto; - padding: 0; - border-radius: var(--radius-sm); - color: var(--muted); - background: transparent; -} -.icon-btn:hover:not(:disabled) { background: var(--surface-hover); color: var(--vscode-editor-foreground); } +.icon-btn:hover:not(:disabled) { background: var(--surface-hover); color: var(--text-primary); } .icon-btn svg { width: 14px; height: 14px; @@ -674,8 +577,8 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar gap: 7px; margin: 14px 1px 7px; color: var(--muted); - font-size: 10px; - font-weight: 700; + font-size: var(--font-2xs); + font-weight: var(--weight-bold); letter-spacing: .055em; text-transform: uppercase; } @@ -683,11 +586,11 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar .config-saved { padding: 1px 8px; - border-radius: 999px; + border-radius: var(--radius-pill); color: var(--ok); background: color-mix(in srgb, var(--ok) 14%, transparent); - font-size: 10px; - font-weight: 600; + font-size: var(--font-2xs); + font-weight: var(--weight-medium); letter-spacing: .02em; text-transform: none; } @@ -708,13 +611,13 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar .config-field__label { display: block; margin-bottom: 4px; - font-size: 12px; - font-weight: 600; + font-size: var(--font-sm); + font-weight: var(--weight-medium); } .config-field__description, .config-field__hint { color: var(--muted); - font-size: 10px; + font-size: var(--font-2xs); line-height: 1.45; white-space: pre-wrap; } @@ -727,12 +630,12 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar .config-field textarea { width: 100%; min-height: 30px; - border: 1px solid var(--vscode-input-border, var(--border)); + border: 1px solid var(--input-border); border-radius: var(--radius-sm); padding: 5px 7px; outline: none; - color: var(--vscode-input-foreground); - background: var(--vscode-input-background); + color: var(--input-fg); + background: var(--input-bg); } .config-field textarea { min-height: 78px; resize: vertical; line-height: 1.45; } .config-field select[multiple] { min-height: 112px; } @@ -748,7 +651,7 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar min-height: 28px; cursor: pointer; } -.switch-field__state { color: var(--muted); font-size: 11px; } +.switch-field__state { color: var(--muted); font-size: var(--font-xs); } .config-group { overflow: hidden; @@ -763,21 +666,21 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar gap: 8px; min-height: 38px; padding: 5px 7px 5px 10px; - background: color-mix(in srgb, var(--vscode-sideBarSectionHeader-background, var(--surface)) 88%, transparent); + background: var(--bg-container-raised); } .config-group__header:hover { background: var(--surface-hover); } .config-group__header > .config-field { flex: 1; margin: -4px 0; padding: 5px 3px; } .config-group__header > .config-field:hover { border-color: transparent; background: transparent; } -.config-group__title { flex: 1; min-width: 0; font-size: 12px; font-weight: 650; } +.config-group__title { flex: 1; min-width: 0; font-size: var(--font-sm); font-weight: 650; } .config-group__toggle { flex: 0 0 auto; min-width: 30px; min-height: 27px; padding: 3px 7px; - color: var(--vscode-button-secondaryForeground); + color: var(--btn-secondary-fg); background: transparent; } -.config-group__toggle:hover:not(:disabled) { background: var(--vscode-button-secondaryHoverBackground); } +.config-group__toggle:hover:not(:disabled) { background: var(--btn-secondary-bg-hover); } .config-group__body { display: none; padding: 8px; border-top: 1px solid var(--border); } .config-group.open > .config-group__body { display: block; } @@ -791,12 +694,12 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar margin: 14px -12px -12px; padding: 9px 12px; border-top: 1px solid var(--border); - background: color-mix(in srgb, var(--vscode-editor-background) 94%, transparent); + background: color-mix(in srgb, var(--bg-page) 94%, transparent); backdrop-filter: blur(8px); } .config-actions button { min-width: 112px; } -.config-json-error { border-color: var(--vscode-inputValidation-errorBorder, #be1100) !important; } +.config-json-error { border-color: var(--err) !important; } .config-broken, .config-empty { margin: 4px 0; @@ -812,13 +715,13 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar border: 1px solid var(--border); border-radius: var(--radius-md); padding: 8px 10px; - font-size: 11px; + font-size: var(--font-xs); line-height: 1.45; background: var(--surface-raised); } -.status-banner.warn { border-color: var(--vscode-inputValidation-warningBorder, #b89500); background: var(--vscode-inputValidation-warningBackground, var(--surface-raised)); } -.status-banner.error { border-color: var(--vscode-inputValidation-errorBorder, #be1100); background: var(--vscode-inputValidation-errorBackground, var(--surface-raised)); } -.status-banner.ok { border-color: var(--vscode-inputValidation-infoBorder, var(--border-strong)); background: var(--vscode-inputValidation-infoBackground, var(--surface-raised)); } +.status-banner.warn { border-color: var(--warn); background: var(--warn-bg); } +.status-banner.error { border-color: var(--err); background: var(--danger-bg); } +.status-banner.ok { border-color: var(--border-strong); background: var(--info-bg); } .status-banner.info { border-color: var(--border); background: var(--surface-raised); } .empty-state { @@ -864,7 +767,7 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar align-items: center; justify-content: center; padding: 18px; - background: color-mix(in srgb, #000 45%, transparent); + background: var(--scrim); } .list-dialog { display: flex; @@ -879,7 +782,7 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar } .list-dialog__title { padding: 12px 14px 9px; - font-size: 13px; + font-size: var(--font-md); font-weight: 650; border-bottom: 1px solid var(--border); } @@ -900,8 +803,8 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar min-height: 0; } .list-dialog__pane--narrow { flex: 0 0 44%; } -.list-dialog__subtitle { font-size: 12px; font-weight: 650; } -.list-dialog__hint { color: var(--muted); font-size: 11px; } +.list-dialog__subtitle { font-size: var(--font-sm); font-weight: 650; } +.list-dialog__hint { color: var(--muted); font-size: var(--font-xs); } .list-dialog__search { min-height: 28px; } .list-dialog__options { flex: 1; @@ -912,7 +815,7 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar gap: 6px; padding: 2px; } -.list-dialog__option { min-height: 26px; padding: 3px 9px; font-size: 12px; } +.list-dialog__option { min-height: 26px; padding: 3px 9px; font-size: var(--font-sm); } .list-dialog__columns { display: flex; gap: 8px; @@ -965,11 +868,11 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar /* ── 配置接管:全局配置组卡片 + 任务二级分组小节 ── */ .takeover-banner { - border: 1px solid color-mix(in srgb, #3794ff 45%, transparent); - background: color-mix(in srgb, #3794ff 12%, transparent); + border: var(--border-width) solid color-mix(in srgb, var(--border-strong) 45%, transparent); + background: color-mix(in srgb, var(--border-strong) 12%, transparent); border-radius: var(--radius-sm); padding: 8px 10px; - font-size: 11.5px; + font-size: var(--font-xs); line-height: 1.55; margin-bottom: 10px; } @@ -988,10 +891,10 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar border-bottom: 1px solid var(--border); background: color-mix(in srgb, var(--surface-hover, var(--surface)) 55%, transparent); } -.gconfig-card__name { font-weight: 600; font-size: 12.5px; flex: 0 0 auto; min-width: 0; } +.gconfig-card__name { font-weight: var(--weight-medium); font-size: var(--font-sm); flex: 0 0 auto; min-width: 0; } .gconfig-card__desc { flex: 1; - font-size: 11px; + font-size: var(--font-xs); color: var(--muted); min-width: 0; overflow: hidden; @@ -999,24 +902,14 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar white-space: nowrap; } .gconfig-card__actions { display: flex; gap: 6px; flex: 0 0 auto; } -.btn-mini { - border: 1px solid var(--border); - background: transparent; - color: var(--muted); - border-radius: var(--radius-sm); - padding: 3px 8px; - font-size: 11px; - cursor: pointer; -} -.btn-mini:hover { background: var(--surface-hover); color: inherit; } .gconfig-card__body { padding: 2px 0 4px; } .subgroup-head { display: flex; align-items: center; gap: 6px; padding: 8px 2px 4px; - font-size: 11.5px; - font-weight: 600; + font-size: var(--font-xs); + font-weight: var(--weight-medium); color: var(--muted); cursor: pointer; user-select: none; @@ -1025,17 +918,17 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar .subgroup-head__chev { width: 11px; } .subgroup-head__count { font-weight: 400; - font-size: 10.5px; + font-size: var(--font-2xs); background: var(--surface-hover, var(--surface)); - border-radius: 8px; + border-radius: var(--radius-md); padding: 1px 7px; } .subgroup-body { display: flex; flex-direction: column; gap: 8px; } -.task-card__snap { min-width: 30px; font-size: 12px; } +.task-card__snap { min-width: 30px; font-size: var(--font-sm); } .tag--store { - font-size: 10px; + font-size: var(--font-2xs); border: 1px solid var(--border-strong, var(--border)); - border-radius: 8px; + border-radius: var(--radius-md); padding: 1px 7px; color: var(--muted); margin-left: 6px; @@ -1057,16 +950,15 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar cursor: pointer; user-select: none; transition: background-color .12s ease; } -.gconfig-card__head--toggle:hover { background: var(--surface-hover); } -.gconfig-card__head--toggle:hover .gconfig-card__chev { color: var(--vscode-focusBorder); } +.gconfig-card__head--toggle:hover .gconfig-card__chev { color: var(--border-strong); } .gconfig-card__head--toggle .gconfig-card__chev { transition: color .12s ease; } .gconfig-card__chev { width: 11px; color: var(--muted); flex: 0 0 auto; } .gconfig-card__meta { flex: 0 0 auto; - font-size: 10.5px; + font-size: var(--font-2xs); color: var(--muted); background: var(--surface-hover, var(--surface)); - border-radius: 8px; + border-radius: var(--radius-md); padding: 1px 7px; } .account-override-form .config-panel { border-top: 1px solid var(--border); } @@ -1089,11 +981,11 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar padding: 3px 6px; border-radius: var(--radius-sm); cursor: pointer; - font-size: 12px; + font-size: var(--font-sm); color: inherit; } .ms-list__item:hover { background: var(--surface-hover); } -.ms-list__item input { accent-color: #3794ff; margin: 0; } +.ms-list__item input { accent-color: var(--border-strong); margin: 0; } .config-section-title--toggle { cursor: pointer; user-select: none; } .config-section-title__chev { display: inline-block; width: 11px; margin-right: 2px; color: var(--muted); } @@ -1112,7 +1004,7 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar max-height: 100vh; padding: 6px 6px 6px 0; border-right: 1px solid var(--border); - background: var(--vscode-editor-background); + background: var(--bg-page); } .lay-side__item { position: relative; @@ -1122,25 +1014,25 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar padding: 7px 12px; border-radius: var(--radius-sm); color: var(--muted); - font-size: 13px; + font-size: var(--font-md); cursor: pointer; user-select: none; } -.lay-side__item:hover { background: var(--surface-hover); color: var(--vscode-editor-foreground); } +.lay-side__item:hover { background: var(--surface-hover); color: var(--text-primary); } .lay-side__item.is-active { background: var(--surface-hover); - color: var(--vscode-editor-foreground); + color: var(--text-primary); box-shadow: inset 2px 0 0 var(--border-strong); } .lay-side__ico { width: 14px; height: 14px; flex: 0 0 auto; - border-radius: 4px; + border-radius: var(--radius-sm); background: var(--border-strong); opacity: .7; } .lay-side__item.is-active .lay-side__ico { opacity: 1; } -.lay-side__foot { margin-top: auto; padding: 10px 12px; font-size: 11px; color: var(--muted); } +.lay-side__foot { margin-top: auto; padding: 10px 12px; font-size: var(--font-xs); color: var(--muted); } .lay-main { flex: 1; min-width: 0; } /* ═══ 配置健康度条(任务分段顶部,真实快照数据) ══════════════ */ @@ -1156,11 +1048,11 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar background: var(--surface); } .rc-health__head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px; flex: 1; min-width: 0; } -.rc-health__title { font-size: 12px; font-weight: 700; } +.rc-health__title { font-size: var(--font-sm); font-weight: var(--weight-bold); } .rc-health__cells { display: flex; gap: 8px; flex: 1; min-width: 0; } .rc-cell { flex: 1; min-width: 96px; padding: 6px 12px; border-radius: var(--radius-sm); background: var(--surface-hover); } -.rc-cell__label { font-size: 10px; color: var(--muted); } -.rc-cell__value { font-size: 16px; font-weight: 700; } +.rc-cell__label { font-size: var(--font-2xs); color: var(--muted); } +.rc-cell__value { font-size: var(--font-lg); font-weight: var(--weight-bold); } .rc-cell.is-ok .rc-cell__value { color: var(--ok); } .rc-cell.is-warn .rc-cell__value { color: var(--warn); } @@ -1177,14 +1069,14 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar background: var(--surface); } .rc-queue__head { display: flex; align-items: baseline; gap: 8px; } -.rc-queue__head b { font-size: 12px; } +.rc-queue__head b { font-size: var(--font-sm); } .rc-queue__chips { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; flex: 1; min-width: 0; } .rc-queue__chip { padding: 3px 10px; border: 1px solid var(--border); - border-radius: 999px; + border-radius: var(--radius-pill); background: var(--surface-hover); - font-size: 11px; + font-size: var(--font-xs); } /* ═══ 运行状态灯(任务卡标题行) ══════════════════════════════ */ @@ -1215,11 +1107,11 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar overflow-y: auto; padding: 10px 12px; border: 1px solid var(--border-strong); - border-radius: 10px; + border-radius: var(--radius-lg); background: color-mix(in srgb, var(--surface-raised, var(--surface)) 88%, transparent); backdrop-filter: blur(18px) saturate(1.5); -webkit-backdrop-filter: blur(18px) saturate(1.5); - box-shadow: 0 14px 38px color-mix(in srgb, #000 40%, transparent); + box-shadow: var(--shadow-lg); opacity: 0; visibility: hidden; transform: translateY(4px) scale(.98); @@ -1237,50 +1129,50 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar .gpop::-webkit-scrollbar { width: 8px; } .gpop::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--border-strong) 80%, transparent); - border-radius: 4px; + border-radius: var(--radius-pill); } .gpop::-webkit-scrollbar-thumb:hover { background: var(--border-strong); } .gpop::-webkit-scrollbar-track { background: transparent; } /* 条件组(field.type.sub_configs 显隐组):与静态组一视同仁,蓝色系区分语义 */ .gpop .grp-tag { padding: 1px 7px; - border-radius: 999px; - font-size: 10px; - background: color-mix(in srgb, var(--vscode-charts-blue, #3794ff) 16%, transparent); - color: var(--vscode-charts-blue, #3794ff); + border-radius: var(--radius-pill); + font-size: var(--font-2xs); + background: color-mix(in srgb, var(--run) 16%, transparent); + color: var(--run); } .gpop .it--rule { - border-left-color: var(--vscode-charts-blue, #3794ff); - background: color-mix(in srgb, var(--vscode-charts-blue, #3794ff) 10%, transparent); - font-weight: 600; + border-left-color: var(--run); + background: color-mix(in srgb, var(--run) 10%, transparent); + font-weight: var(--weight-medium); } /* 条件组内的嵌套子组(条件组套条件组 / 静态组)保持节奏一致的间距 */ .gpop .grp-body .grp { margin: 6px 0 2px; } @keyframes popfade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } } .gpop .pop-title { display: flex; align-items: baseline; justify-content: space-between; - margin-bottom: 2px; font-size: 12px; font-weight: 700; + margin-bottom: 2px; font-size: var(--font-sm); font-weight: var(--weight-bold); animation: popfade .24s ease both; } -.gpop .pop-sub { font-size: 10px; font-weight: 400; color: var(--muted); } +.gpop .pop-sub { font-size: var(--font-2xs); font-weight: var(--weight-regular); color: var(--muted); } .gpop .grp { margin: 8px 0; animation: popfade .24s ease both; } .gpop .grp-head { display: flex; align-items: center; justify-content: space-between; padding: 0 2px 2px; - font-size: 10px; letter-spacing: .08em; color: var(--muted); + font-size: var(--font-2xs); letter-spacing: .08em; color: var(--muted); } -.gpop .grp-head b { font-size: 12px; font-weight: 600; letter-spacing: 0; color: var(--vscode-editor-foreground); } +.gpop .grp-head b { font-size: var(--font-sm); font-weight: var(--weight-medium); letter-spacing: 0; color: var(--text-primary); } .gpop .grp-head .cnt { - padding: 1px 8px; border-radius: 999px; + padding: 1px 8px; border-radius: var(--radius-pill); background: color-mix(in srgb, var(--run) 14%, transparent); - color: var(--run); font-size: 10px; + color: var(--run); font-size: var(--font-2xs); } .gpop .grp-body { position: relative; margin: 2px 0 0 8px; padding-left: 12px; } .gpop .grp-body::before { content: ''; position: absolute; top: 2px; bottom: 6px; left: 0; - width: 2px; border-radius: 1px; + width: 2px; border-radius: var(--radius-pill); background: linear-gradient(180deg, var(--border-strong), transparent); } .gpop .it { @@ -1288,9 +1180,9 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar display: flex; align-items: center; margin: 4px 0; padding: 4px 9px; border-left: 2px solid var(--border-strong); - border-radius: 6px; + border-radius: var(--radius-sm); background: color-mix(in srgb, var(--surface-hover) 72%, transparent); - font-size: 12px; overflow: hidden; + font-size: var(--font-sm); overflow: hidden; } .gpop .it::before { content: ''; @@ -1307,10 +1199,10 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar flex: 1 1 100%; min-width: 0; display: block; - font-size: 10px; + font-size: var(--font-2xs); line-height: 1.4; max-height: 2.8em; /* 最长两条规则摘要封顶两行 */ - color: var(--vscode-charts-blue, #3794ff); + color: var(--run); white-space: normal; word-break: break-all; overflow: hidden; @@ -1326,3 +1218,63 @@ button.secondary:hover:not(:disabled) { background: var(--vscode-button-secondar 会覆盖 UA 的 [hidden]{display:none},导致所有基于 hidden 的折叠/显隐在真机上失效 (jsdom 测试只查属性所以测不出来)。!important 确保 hidden 永远生效。 */ [hidden] { display: none !important; } + +/* ═══ 可点击元素适配(组件级;通用规范与控件基线见 shared/controls.css)═══ */ + +/* 启动设置折叠头:行级可点击区(极浅色调 + 无描边)——有底色但不做方块 */ +.config-section-title--toggle { + border: 0; + border-radius: var(--radius-sm); + background: var(--bg-row); + padding: 4px 9px; + margin: var(--space-md, 10px) 1px var(--space-sm, 7px); +} +.config-section-title--toggle::after { display: none; } +.config-section-title--toggle:hover { + background: var(--bg-row-hover); + color: var(--text-primary); +} + +/* 任务业务分组头:行级可点击区(同上) */ +.subgroup-head { + border: 0; + border-radius: var(--radius-sm); + background: var(--bg-row); + padding: 5px 9px; + margin: 3px 0 4px; +} +.subgroup-head:hover { + background: var(--bg-row-hover); + color: var(--text-primary); +} + +/* 卡片头折叠(全局组卡片 / 账号覆盖卡) */ +.gconfig-card__head--toggle:hover { background: var(--bg-row-hover); } +.gconfig-card__head--toggle:focus-visible { outline: 1px solid var(--border-strong); outline-offset: -1px; } + +/* 配置分组折叠按键:只有 ▲/▼ 符号,描边 + 静态底色使其成为可见控件 */ +.config-group__toggle { + border: var(--border-width, 1px) solid var(--border); + background: var(--bg-control); +} +.config-group__toggle:hover:not(:disabled) { border-color: var(--border-strong); } + +/* kind 级任务组头(触发任务/一次性):行级可点击区(同上) */ +.group-head { + border: 0; + border-radius: var(--radius-sm); + background: var(--bg-row); + padding: 6px 9px; + margin: 7px 0; +} +.group-head:hover { + background: var(--bg-row-hover); + color: var(--text-primary); +} + +/* 行级可点击(游戏状态行等) */ +.gbar__row--link { + padding: 2px 7px; + border-radius: var(--radius-sm); + background: var(--bg-row); +} diff --git a/media/console/index.html b/media/console/index.html index 30be6c2..8798c5e 100644 --- a/media/console/index.html +++ b/media/console/index.html @@ -5,6 +5,8 @@ + + diff --git a/media/shared/controls.css b/media/shared/controls.css new file mode 100644 index 0000000..8aaac89 --- /dev/null +++ b/media/shared/controls.css @@ -0,0 +1,214 @@ +/* ═══════════════════════════════════════════════════════════════════════ + ok-script-toolkit · 共享控件层(依赖 tokens.css,须在其后引入) + + 提供全项目唯一的控件视觉语言:按钮家族、表单控件、卡片、标签、提示、 + 空态/错误态、工具条。页面只允许组合这些类,不得自行重定义。 + 详见 docs/design-system.md 第 3 / 7 / 8 条。 + ═══════════════════════════════════════════════════════════════════════ */ + +/* ── Button(主)────────────────────────────────────────────────────── */ +button { + min-height: var(--control-h-md); + border: var(--border-width) solid transparent; + border-radius: var(--radius-sm); + padding: 5px 11px; + cursor: pointer; + font: inherit; + color: var(--btn-fg); + background: var(--btn-bg); + transition: background-color .12s ease, border-color .12s ease, opacity .12s ease; +} +button:hover:not(:disabled) { background: var(--btn-bg-hover); } +button:focus-visible { outline: 1px solid var(--border-strong); outline-offset: 2px; } +button:disabled { cursor: not-allowed; opacity: .45; } + +/* ── Button(次)────────────────────────────────────────────────────── */ +button.secondary { + color: var(--btn-secondary-fg); + background: var(--btn-secondary-bg); +} +button.secondary:hover:not(:disabled) { background: var(--btn-secondary-bg-hover); } + +/* ── Button(迷你 / 工具条内)───────────────────────────────────────── */ +.mini-btn { + min-height: var(--control-h-sm); + padding: 2px 10px; + border-radius: var(--radius-sm); + font-size: var(--font-xs); + font-weight: var(--weight-medium); + white-space: nowrap; + color: var(--btn-secondary-fg); + background: var(--btn-secondary-bg); +} +.mini-btn:hover:not(:disabled) { background: var(--btn-secondary-bg-hover); } +.mini-btn--primary { + color: var(--btn-fg); + background: var(--btn-bg); +} +.mini-btn--primary:hover:not(:disabled) { background: var(--btn-bg-hover); } +/* 紧凑按钮的「已激活」态(工具条里的开关式按钮,如 绘制/坐标/轮播) */ +.mini-btn.is-active { + background: var(--btn-bg); + color: var(--btn-fg); +} +.mini-btn.is-active:hover:not(:disabled) { background: var(--btn-bg-hover); } +.mini-btn--danger { + color: var(--err); + border-color: color-mix(in srgb, var(--err) 40%, var(--border)); + background: var(--bg-control); +} +.mini-btn--danger:hover:not(:disabled) { background: var(--bg-control-hover); } + +/* ── Button(描边幽灵,静态底色 + 描边)────────────────────────────── */ +.btn-mini { + border: var(--border-width) solid var(--border); + border-radius: var(--radius-sm); + padding: 3px 8px; + font-size: var(--font-xs); + color: var(--text-muted); + background: var(--bg-control); +} +.btn-mini:hover { background: var(--bg-control-hover); color: var(--text-primary); border-color: var(--border-strong); } + +/* ── IconButton ─────────────────────────────────────────────────────── */ +.icon-btn { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + min-height: 28px; + padding: 0; + border: var(--border-width) solid transparent; + border-radius: var(--radius-sm); + color: var(--text-muted); + background: var(--bg-control); + cursor: pointer; +} +.icon-btn:hover { background: var(--bg-control-hover); color: var(--text-primary); } + +/* ── 表单控件 ───────────────────────────────────────────────────────── */ +input, select, textarea { font: inherit; } + +input[type='text'], +input[type='number'], +input[type='search'], +select, +textarea { + min-height: var(--control-h-md); + border: var(--border-width) solid var(--border); + border-radius: var(--radius-sm); + padding: 4px 8px; + color: var(--input-fg); + background: var(--input-bg); + outline: none; +} +input:focus, select:focus, textarea:focus { border-color: var(--border-strong); } +input:disabled, select:disabled, textarea:disabled { opacity: .55; cursor: not-allowed; } + +/* ── Checkbox / Radio / Switch(统一 accent)────────────────────────── */ +input[type='checkbox'], +input[type='radio'] { accent-color: var(--border-strong); } + +/* ── Tag / Pill(计数、状态徽标)───────────────────────────────────── */ +.pill { + display: inline-flex; + align-items: center; + border-radius: var(--radius-pill); + padding: 1px 8px; + font-size: var(--font-xs); + color: var(--text-muted); + background: var(--bg-control); +} + +/* ── Card ───────────────────────────────────────────────────────────── */ +.card { + border: var(--border-width) solid var(--border); + border-radius: var(--radius-lg); + background: var(--bg-container); +} + +/* ── 工具条(吸顶)──────────────────────────────────────────────────── */ +.panel-toolbar { + position: sticky; + top: 0; + z-index: 10; + display: flex; + flex-wrap: wrap; + gap: var(--space-sm); + align-items: center; + padding: var(--space-sm) 0; + background: var(--bg-page); +} + +/* ── 区块标题 ───────────────────────────────────────────────────────── */ +.panel-section-title { + display: flex; + align-items: center; + gap: 7px; + margin: var(--space-md) 1px var(--space-sm); + color: var(--text-muted); + font-size: var(--font-xs); + font-weight: var(--weight-medium); + letter-spacing: .04em; + text-transform: uppercase; +} + +/* ── 空态 / 错误态 / 提示 ───────────────────────────────────────────── */ +.panel-empty { + padding: var(--space-xl) var(--space-md); + text-align: center; + color: var(--text-muted); + font-size: var(--font-sm); + white-space: pre-line; + line-height: 1.8; +} +.panel-broken { + border: var(--border-width) solid color-mix(in srgb, var(--err) 45%, var(--border)); + border-radius: var(--radius-md); + padding: var(--space-md); + color: var(--err); + font-size: var(--font-sm); + white-space: pre-wrap; +} +.panel-hint { + color: var(--text-muted); + font-size: var(--font-xs); + line-height: 1.6; +} + +/* ── 分隔线 ─────────────────────────────────────────────────────────── */ +.hr { + height: 1px; + border: 0; + background: var(--border); +} + +/* ═══ 可点击性硬规范(docs/design-system.md 第 3 条)═══════════════════ + ① 可点击控件必须与容器异色或有描边; + ② 不得只呈现一个光秃文字; + ③ 未悬浮状态就必须有底色; + ④ 行级可点击区必须有 hover 反馈。 + 分层(2026-09-23 定案):真按钮 = --bg-control + 描边;行级可点击区(组头/折叠 + 标题行)= --bg-row(极浅色调、无描边)——两者都「默认可识别」,但权重不同, + 避免整个侧栏变成层层方块。 */ + +[role='button'] { cursor: pointer; } +[role='button']:focus-visible { outline: 1px solid var(--border-strong); outline-offset: 1px; } + +/* 真按钮:控件面 + 描边 */ +.mini-btn--danger, +.btn-mini, +.icon-btn, +.config-group__toggle { + background: var(--bg-control); +} + +/* 行级可点击区(组头 / 折叠标题行 / tab):极浅色调,不参与按钮权重 */ +.seg__btn:not(.is-active), +.gbar__row--link, +.group-head, +.subgroup-head, +.config-section-title--toggle { + background: var(--bg-row); +} diff --git a/media/shared/tokens.css b/media/shared/tokens.css new file mode 100644 index 0000000..e76dd96 --- /dev/null +++ b/media/shared/tokens.css @@ -0,0 +1,137 @@ +/* ═══════════════════════════════════════════════════════════════════════ + ok-script-toolkit · Design Tokens(全面板唯一视觉真源) + + 规则(见 docs/design-system.md): + - 颜色、间距、尺寸、圆角、边框、阴影一律引用本文件 token; + - 页面 / 组件不得自造颜色与尺寸,不得直引 --vscode-* 变量(本文件除外); + - 层级:页面背景 > 一级容器 > 控件面 > Hover/Active; + - 真按钮(Button/IconButton)默认态用 --bg-control(控件面 + 描边); + - 行级可点击区(组头 / 折叠标题行)用 --bg-row(极浅色调、无描边)—— + 同样「未悬浮即有底色」,但不与按钮同权重,避免层层方块(2026-09-23 定案)。 + ═══════════════════════════════════════════════════════════════════════ */ + +:root { + color-scheme: light dark; + + /* ── 1. 骨架与容器 ───────────────────────────────────────────────── */ + --bg-page: var(--vscode-editor-background, #1e1e1e); + --bg-container: var(--vscode-editorWidget-background, color-mix(in srgb, var(--vscode-editor-background) 92%, var(--vscode-foreground) 8%)); + --bg-container-raised: var(--vscode-sideBar-background, var(--bg-container)); + + /* 兼容别名(既有样式沿用,新代码请用上面的语义名) */ + --surface: var(--bg-container); + --surface-raised: var(--bg-container-raised); + + /* ── 2. 控件面与交互态 ───────────────────────────────────────────── */ + --bg-control: color-mix(in srgb, var(--vscode-button-secondaryBackground, var(--bg-container-raised)) 80%, var(--vscode-foreground) 14%); + --bg-control-hover: color-mix(in srgb, var(--bg-control) 82%, var(--vscode-foreground) 18%); + --bg-control-active: color-mix(in srgb, var(--bg-control) 72%, var(--vscode-foreground) 26%); + --surface-hover: var(--vscode-list-hoverBackground, color-mix(in srgb, var(--bg-container) 86%, var(--vscode-focusBorder) 14%)); + + /* 行级可点击区(组头 / 折叠标题行):极浅色调——默认可识别,但不形成方块墙 */ + --bg-row: color-mix(in srgb, var(--bg-control) 42%, transparent); + --bg-row-hover: color-mix(in srgb, var(--bg-control) 70%, transparent); + + /* ── 6. VS Code 主题映射(本文件是全项目唯一允许直引 --vscode-* 的地方)── + 面板 CSS 一律用下面这些语义 token,便于换主题/换宿主时一处调整。 */ + --font-ui: var(--vscode-font-family, system-ui, 'Segoe UI', sans-serif); + --font-mono: var(--vscode-editor-font-family, ui-monospace, monospace); + --font-size-root: var(--vscode-font-size, 13px); + --btn-bg: var(--vscode-button-background, #0e639c); + --btn-fg: var(--vscode-button-foreground, #ffffff); + --btn-bg-hover: var(--vscode-button-hoverBackground, #1177bb); + --btn-secondary-bg: var(--vscode-button-secondaryBackground, color-mix(in srgb, var(--text-primary) 12%, transparent)); + --btn-secondary-fg: var(--vscode-button-secondaryForeground, var(--text-primary)); + --btn-secondary-bg-hover: var(--vscode-button-secondaryHoverBackground, color-mix(in srgb, var(--text-primary) 18%, transparent)); + --input-bg: var(--vscode-input-background, var(--bg-container)); + --input-fg: var(--vscode-input-foreground, var(--text-primary)); + --input-border: var(--vscode-input-border, var(--border)); + --input-placeholder: var(--vscode-input-placeholderForeground, var(--text-muted)); + --badge-bg: var(--vscode-badge-background, var(--border-strong)); + --badge-fg: var(--vscode-badge-foreground, var(--text-primary)); + --selected-bg: var(--vscode-list-activeSelectionBackground, color-mix(in srgb, var(--border-strong) 26%, transparent)); + --selected-fg: var(--vscode-list-activeSelectionForeground, var(--text-primary)); + --danger-bg: var(--vscode-inputValidation-errorBackground, color-mix(in srgb, var(--err) 30%, var(--bg-container))); + --warn-bg: var(--vscode-inputValidation-warningBackground, color-mix(in srgb, var(--warn) 14%, var(--bg-container))); + --info-bg: var(--vscode-inputValidation-infoBackground, color-mix(in srgb, var(--border-strong) 12%, var(--bg-container))); + --scrollbar: var(--vscode-scrollbarSlider-background, color-mix(in srgb, var(--text-muted) 35%, transparent)); + + /* ── 遮罩与投影(跨主题统一,故不走语义色)───────────────────────── */ + --scrim: color-mix(in srgb, #000 55%, transparent); + --scrim-strong: color-mix(in srgb, #000 82%, transparent); + --on-accent: color-mix(in srgb, #000 78%, transparent); + --shadow-sm: 0 1px 2px color-mix(in srgb, #000 18%, transparent); + --shadow-lg: 0 12px 36px color-mix(in srgb, #000 42%, transparent); + + /* 语法糖别名(历史命名,与上面等价) */ + --clickable-bg: var(--bg-control); + --clickable-bg-hover: var(--bg-control-hover); + + /* ── 3. 边框 ────────────────────────────────────────────────────── */ + --border: var(--vscode-panel-border, color-mix(in srgb, var(--vscode-foreground) 18%, transparent)); + --border-strong: var(--vscode-focusBorder, #007acc); + --border-width: 1px; + + /* ── 4. 文字 ────────────────────────────────────────────────────── */ + --text-primary: var(--vscode-editor-foreground); + --text-muted: var(--vscode-descriptionForeground, color-mix(in srgb, var(--vscode-editor-foreground) 64%, transparent)); + --text-disabled: color-mix(in srgb, var(--vscode-editor-foreground) 38%, transparent); + + /* 兼容别名 */ + --muted: var(--text-muted); + --fg: var(--text-primary); + + /* ── 5. 语义状态色 ──────────────────────────────────────────────── */ + --ok: var(--vscode-charts-green, #3fb950); + --run: var(--vscode-charts-blue, #4a9eff); + --warn: var(--vscode-charts-yellow, #d7a12c); + --pause: var(--vscode-charts-orange, #d18616); + --err: var(--vscode-errorForeground, #f44); + + /* ── 6. 间距(spacing scale)─────────────────────────────────────── */ + --space-xs: 4px; + --space-sm: 6px; + --space-md: 10px; + --space-lg: 14px; + --space-xl: 20px; + + /* ── 7. 圆角与阴影 ──────────────────────────────────────────────── */ + --radius-sm: 5px; + --radius-md: 8px; + --radius-lg: 11px; + --radius-pill: 999px; + --shadow: 0 4px 14px color-mix(in srgb, #000 15%, transparent); + + /* ── 8. 控件尺寸(高度)──────────────────────────────────────────── */ + --control-h-sm: 24px; + --control-h-md: 30px; + --control-h-lg: 34px; + + /* ── 9. 字号与字重 ──────────────────────────────────────────────── */ + --font-2xs: 10px; + --font-xs: 11px; + --font-sm: 12px; + --font-md: 13px; + --font-lg: 15px; + --font-xl: 20px; + --weight-regular: 400; + --weight-medium: 500; + --weight-bold: 700; +} + +* { box-sizing: border-box; } + +html, body { min-height: 100%; } + +body { + margin: 0; + color: var(--text-primary); + background: var(--bg-page); + font-family: var(--vscode-font-family); + font-size: var(--vscode-font-size, var(--font-md)); + line-height: 1.5; +} + +/* hidden 属性兜底:作者样式里的 display 会覆盖 UA 的 [hidden]{display:none}, + 导致所有基于 hidden 的折叠/显隐在真机上失效(jsdom 测试只查属性所以测不出来)。 */ +[hidden] { display: none !important; } diff --git a/media/tempScreenshots/index.html b/media/tempScreenshots/index.html index 94bc5b3..5c4a30a 100644 --- a/media/tempScreenshots/index.html +++ b/media/tempScreenshots/index.html @@ -3,14 +3,16 @@ + + -
- - +
+ + - +
@@ -34,8 +36,8 @@
- - + +
@@ -48,8 +50,8 @@
- - + +
diff --git a/media/tempScreenshots/style.css b/media/tempScreenshots/style.css index b804021..3f87cd7 100644 --- a/media/tempScreenshots/style.css +++ b/media/tempScreenshots/style.css @@ -1,297 +1,266 @@ - * { box-sizing: border-box; margin: 0; padding: 0; } - html, body { height: 100%; } - body { - color: var(--vscode-foreground); - background: var(--vscode-sideBar-background, var(--vscode-editor-background)); - font-family: var(--vscode-font-family); - font-size: var(--vscode-font-size, 13px); - overflow: hidden; - display: flex; - flex-direction: column; - } +/* 临时截图面板(共享层见 media/shared/tokens.css + controls.css) + 工具条/底部操作条按钮用共享 .mini-btn;本文件保留舞台、选框、缩略图与提示条。 */ - /* ---------- 工具栏 ---------- */ - .toolbar { - display: flex; - align-items: center; - gap: 4px; - padding: 5px 6px; - flex-shrink: 0; - flex-wrap: wrap; - } - .toolbar button, - .stage-bar button { - padding: 3px 8px; - border-radius: 3px; - border: 1px solid var(--vscode-button-secondaryBackground, rgba(128,128,128,.35)); - background: var(--vscode-button-secondaryBackground); - color: var(--vscode-button-secondaryForeground); - cursor: pointer; - font-size: 11px; - white-space: nowrap; - } - .toolbar button:hover, - .stage-bar button:hover { background: var(--vscode-button-secondaryHoverBackground); } - .toolbar button.active, - .stage-bar button.active { - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); - border-color: var(--vscode-button-background); - } - .spacer { flex: 1; } - .toolbar .fg-check { - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 12px; - color: var(--vscode-foreground); - cursor: pointer; - user-select: none; - white-space: nowrap; - } - .toolbar .fg-check input { margin: 0; } - .count { font-size: 11px; opacity: .65; } +* { box-sizing: border-box; margin: 0; padding: 0; } +html, body { height: 100%; } +body { + color: var(--text-primary); + background: var(--bg-page); + font-family: var(--font-ui); + font-size: var(--font-size-root); + overflow: hidden; + display: flex; + flex-direction: column; +} - /* ---------- 舞台(单图预览 / 0.1s 轮播) ---------- */ - .stage { - position: relative; - flex: 1 1 45%; - min-height: 110px; - margin: 0 6px; - overflow: hidden; - border: 1px solid var(--vscode-panel-border, rgba(128,128,128,.3)); - border-radius: 4px; - background: - repeating-conic-gradient(rgba(128,128,128,.12) 0% 25%, transparent 0% 50%) 50% / 16px 16px; - cursor: crosshair; - } - .stage-inner { - position: absolute; - left: 0; top: 0; - transform-origin: 0 0; - } - .stage-inner img { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - object-fit: contain; - display: none; - image-rendering: auto; - -webkit-user-drag: none; - user-select: none; - } - .stage-inner img.active { display: block; } - .stage-overlay { - position: absolute; - inset: 0; - z-index: 2; - } - .sel-box { - position: absolute; - display: none; - border: 1px dashed var(--vscode-charts-green, #3fb950); - background: rgba(63,185,80,.16); - pointer-events: none; - } - .sel-box.visible { display: block; } - /* 8 向手柄,纯视觉(命中判定在 JS 里按坐标做),由 CSS 自行定位 */ - .sel-handle { - position: absolute; - width: 8px; - height: 8px; - margin: -4px 0 0 -4px; - border-radius: 2px; - background: var(--vscode-charts-green, #3fb950); - border: 1px solid rgba(0,0,0,.45); - } - .sel-handle[data-h="tl"] { left: 0; top: 0; } - .sel-handle[data-h="t"] { left: 50%; top: 0; } - .sel-handle[data-h="tr"] { left: 100%; top: 0; } - .sel-handle[data-h="r"] { left: 100%; top: 50%; } - .sel-handle[data-h="br"] { left: 100%; top: 100%; } - .sel-handle[data-h="b"] { left: 50%; top: 100%; } - .sel-handle[data-h="bl"] { left: 0; top: 100%; } - .sel-handle[data-h="l"] { left: 0; top: 50%; } - /* 拖拽预览阶段不显示手柄 */ - .sel-box.preview .sel-handle, - .sel-box.preview .sel-coord { display: none; } - .sel-coord { - position: absolute; - left: 0; - top: -15px; - font: bold 11px var(--vscode-editor-font-family, monospace); - color: var(--vscode-charts-green, #3fb950); - white-space: nowrap; - text-shadow: 0 0 3px rgba(0,0,0,.85); - } - .stage-empty { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - text-align: center; - padding: 8px; - font-size: 11px; - line-height: 1.5; - opacity: .55; - pointer-events: none; - white-space: pre-line; - } +/* ── 工具栏 / 底部操作条 ─────────────────────────────────────────────── */ +.toolbar { flex-shrink: 0; } +.toolbar .fg-check { + display: inline-flex; + align-items: center; + gap: var(--space-xs); + color: var(--text-primary); + font-size: var(--font-sm); + cursor: pointer; + user-select: none; + white-space: nowrap; +} +.toolbar .fg-check input { margin: 0; } +.spacer { flex: 1; } +.count { color: var(--text-muted); font-size: var(--font-xs); } - /* ---------- 舞台操作条 ---------- */ - .stage-bar { - display: flex; - align-items: center; - gap: 4px; - padding: 5px 6px; - flex-shrink: 0; - } - .coord { - font-size: 10px; - opacity: .8; - font-family: var(--vscode-editor-font-family, monospace); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 60%; - } +.stage-bar { + display: flex; + align-items: center; + gap: var(--space-xs); + padding: 5px 6px; + flex-shrink: 0; +} - /* ---------- 缩略图网格 ---------- */ - .grid { - flex: 1 1 55%; - overflow-y: auto; - overflow-x: hidden; - padding: 0 6px 8px; - display: grid; - grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); - gap: 6px; - align-content: start; - } - .card { - position: relative; - border: 1px solid var(--vscode-panel-border, rgba(128,128,128,.3)); - border-radius: 4px; - overflow: hidden; - background: var(--vscode-editor-background); - cursor: pointer; - user-select: none; - } - .card.active { border-color: var(--vscode-focusBorder, #0078d4); box-shadow: 0 0 0 1px var(--vscode-focusBorder, #0078d4); } - .card.dragging { opacity: .45; } - .card img { - display: block; - width: 100%; - height: 56px; - object-fit: contain; - background: rgba(128,128,128,.1); - -webkit-user-drag: none; - } - .card .name { - font-size: 9px; - opacity: .6; - padding: 2px 3px; - text-align: center; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - /* 操作按钮常驻可见:跨 webview 拖拽在 VS Code 里不可用, - 「→ 添加到标注管理」是导入的主入口,不能只在 hover 时才露出 */ - .card .actions { - position: absolute; - top: 2px; - right: 2px; - display: flex; - gap: 2px; - opacity: .82; - transition: opacity .12s ease; - } - .card:hover .actions { opacity: 1; } - .card .actions button { - width: 18px; - height: 18px; - line-height: 1; - padding: 0; - font-size: 12px; - border-radius: 3px; - border: none; - cursor: pointer; - background: rgba(0,0,0,.6); - color: #fff; - } - .card .actions button:hover { background: rgba(0,0,0,.85); } - .card .actions button.send { background: rgba(0,90,158,.85); } - .card .actions button.send:hover { background: rgba(0,120,212,.95); } +/* ── 舞台(单图预览 / 0.1s 轮播)─────────────────────────────────────── */ +.stage { + position: relative; + flex: 1 1 45%; + min-height: 110px; + margin: 0 6px; + overflow: hidden; + border: var(--border-width) solid var(--border); + border-radius: var(--radius-md); + background: repeating-conic-gradient( + color-mix(in srgb, var(--text-muted) 12%, transparent) 0% 25%, transparent 0% 50% + ) 50% / 16px 16px; + cursor: crosshair; +} +.stage-inner { + position: absolute; + left: 0; top: 0; + transform-origin: 0 0; +} +.stage-inner img { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: contain; + display: none; + image-rendering: auto; + -webkit-user-drag: none; + user-select: none; +} +.stage-inner img.active { display: block; } +.stage-overlay { + position: absolute; + inset: 0; + z-index: 2; +} +.sel-box { + position: absolute; + display: none; + border: 1px dashed var(--ok); + background: color-mix(in srgb, var(--ok) 16%, transparent); + pointer-events: none; +} +.sel-box.visible { display: block; } +/* 8 向手柄,纯视觉(命中判定在 JS 里按坐标做),由 CSS 自行定位 */ +.sel-handle { + position: absolute; + width: 8px; + height: 8px; + margin: -4px 0 0 -4px; + border-radius: var(--radius-sm); + background: var(--ok); + border: 1px solid var(--scrim); +} +.sel-handle[data-h="tl"] { left: 0; top: 0; } +.sel-handle[data-h="t"] { left: 50%; top: 0; } +.sel-handle[data-h="tr"] { left: 100%; top: 0; } +.sel-handle[data-h="r"] { left: 100%; top: 50%; } +.sel-handle[data-h="br"] { left: 100%; top: 100%; } +.sel-handle[data-h="b"] { left: 50%; top: 100%; } +.sel-handle[data-h="bl"] { left: 0; top: 100%; } +.sel-handle[data-h="l"] { left: 0; top: 50%; } +/* 拖拽预览阶段不显示手柄 */ +.sel-box.preview .sel-handle, +.sel-box.preview .sel-coord { display: none; } +.sel-coord { + position: absolute; + left: 0; + top: -15px; + font: var(--weight-bold) var(--font-xs) var(--font-mono); + color: var(--ok); + white-space: nowrap; + /* 文字压在截图上,两种主题都需要暗色底衬 */ + text-shadow: 0 0 3px var(--scrim-strong); +} +.stage-empty { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + padding: var(--space-sm); + color: var(--text-muted); + font-size: var(--font-xs); + line-height: 1.5; + pointer-events: none; + white-space: pre-line; +} - .empty { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - text-align: center; - padding: 12px; - font-size: 11px; - line-height: 1.6; - opacity: .55; - white-space: pre-line; - } +.coord { + color: var(--text-muted); + font-size: var(--font-2xs); + font-family: var(--font-mono); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 60%; +} - /* ---------- 提示条 ---------- */ - .toast { - position: fixed; - left: 6px; - right: 6px; - bottom: 6px; - padding: 5px 8px; - border-radius: 4px; - font-size: 11px; - background: var(--vscode-editorWidget-background, #252526); - color: var(--vscode-editorWidget-foreground, inherit); - border: 1px solid var(--vscode-panel-border, rgba(128,128,128,.4)); - box-shadow: 0 2px 10px rgba(0,0,0,.3); - opacity: 0; - transform: translateY(6px); - transition: opacity .15s ease, transform .15s ease; - pointer-events: none; - word-break: break-all; - } - .toast.visible { opacity: 1; transform: translateY(0); } - .toast.error { border-color: var(--vscode-errorForeground, #f14c4c); } +/* ── 缩略图网格 ─────────────────────────────────────────────────────── */ +.grid { + flex: 1 1 55%; + overflow-y: auto; + overflow-x: hidden; + padding: 0 6px var(--space-sm); + display: grid; + grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); + gap: var(--space-sm); + align-content: start; +} +.card { + position: relative; + overflow: hidden; + background: var(--bg-container); + cursor: pointer; + user-select: none; +} +.card.active { + border-color: var(--border-strong); + box-shadow: 0 0 0 1px var(--border-strong); +} +.card.dragging { opacity: .45; } +.card img { + display: block; + width: 100%; + height: 56px; + object-fit: contain; + background: color-mix(in srgb, var(--text-muted) 10%, transparent); + -webkit-user-drag: none; +} +.card .name { + font-size: var(--font-2xs); + color: var(--text-muted); + padding: 2px 3px; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +/* 操作按钮常驻可见:跨 webview 拖拽在 VS Code 里不可用, + 「→ 添加到标注管理」是导入的主入口,不能只在 hover 时才露出 */ +.card .actions { + position: absolute; + top: 2px; + right: 2px; + display: flex; + gap: 2px; + opacity: .85; + transition: opacity .12s ease; +} +.card:hover .actions { opacity: 1; } +.card .actions button { + width: 18px; + height: 18px; + min-height: 18px; + line-height: 1; + padding: 0; + font-size: var(--font-sm); + border: var(--border-width) solid var(--border); + border-radius: var(--radius-sm); + background: var(--bg-control); + color: var(--text-primary); +} +.card .actions button:hover { background: var(--bg-control-hover); border-color: var(--border-strong); } +.card .actions button.send { + border-color: color-mix(in srgb, var(--run) 55%, var(--border)); + background: color-mix(in srgb, var(--run) 22%, var(--bg-control)); +} +.card .actions button.send:hover { background: color-mix(in srgb, var(--run) 34%, var(--bg-control-hover)); } - /* ---------- 内联确认条(webview 中不使用 window.confirm) ---------- */ - .confirm { - position: fixed; - left: 6px; - right: 6px; - bottom: 6px; - display: none; - flex-direction: column; - gap: 6px; - padding: 8px; - border-radius: 4px; - font-size: 11px; - line-height: 1.5; - background: var(--vscode-editorWidget-background, #252526); - color: var(--vscode-editorWidget-foreground, inherit); - border: 1px solid var(--vscode-panel-border, rgba(128,128,128,.4)); - box-shadow: 0 2px 10px rgba(0,0,0,.35); - z-index: 20; - } - .confirm.visible { display: flex; } - .confirm-actions { display: flex; justify-content: flex-end; gap: 6px; } - .confirm-actions button { - padding: 3px 10px; - border-radius: 3px; - font-size: 11px; - cursor: pointer; - border: 1px solid var(--vscode-button-secondaryBackground, rgba(128,128,128,.35)); - background: var(--vscode-button-secondaryBackground); - color: var(--vscode-button-secondaryForeground); - } - .confirm-actions button.danger { - background: var(--vscode-inputValidation-errorBorder, #a1260d); - border-color: var(--vscode-inputValidation-errorBorder, #a1260d); - color: #fff; - } +.empty { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + padding: var(--space-md); + color: var(--text-muted); + font-size: var(--font-xs); + line-height: 1.6; + white-space: pre-line; +} + +/* ── 提示条 ─────────────────────────────────────────────────────────── */ +.toast { + position: fixed; + left: 6px; + right: 6px; + bottom: 6px; + padding: 5px var(--space-sm); + border-radius: var(--radius-md); + font-size: var(--font-xs); + background: var(--bg-container-raised); + color: var(--text-primary); + border: var(--border-width) solid var(--border); + box-shadow: var(--shadow); + opacity: 0; + transform: translateY(6px); + transition: opacity .15s ease, transform .15s ease; + pointer-events: none; + word-break: break-all; +} +.toast.visible { opacity: 1; transform: translateY(0); } +.toast.error { border-color: var(--err); } + +/* ── 内联确认条(webview 中不使用 window.confirm)───────────────────── */ +.confirm { + position: fixed; + left: 6px; + right: 6px; + bottom: 6px; + display: none; + flex-direction: column; + gap: var(--space-sm); + padding: var(--space-sm); + border-radius: var(--radius-md); + font-size: var(--font-xs); + line-height: 1.5; + background: var(--bg-container-raised); + color: var(--text-primary); + border: var(--border-width) solid var(--border); + box-shadow: var(--shadow); + z-index: 20; +} +.confirm.visible { display: flex; } +.confirm-actions { display: flex; justify-content: flex-end; gap: var(--space-sm); } diff --git a/media/templateAssetPanel/index.html b/media/templateAssetPanel/index.html index 64e774b..71a4ba6 100644 --- a/media/templateAssetPanel/index.html +++ b/media/templateAssetPanel/index.html @@ -3,14 +3,16 @@ + + -
- - +
+ + - +
diff --git a/media/templateAssetPanel/style.css b/media/templateAssetPanel/style.css index bb2e633..31d17b5 100644 --- a/media/templateAssetPanel/style.css +++ b/media/templateAssetPanel/style.css @@ -1,107 +1,127 @@ - :root { --thumb-h: 96px; } - body { - color: var(--vscode-editor-foreground); - background: var(--vscode-editor-background); - font-family: var(--vscode-font-family); - font-size: var(--vscode-font-size, 13px); - margin: 0; padding: 10px 12px 20px; - } - .toolbar { - position: sticky; top: 0; z-index: 10; - display: flex; flex-wrap: wrap; gap: 6px; align-items: center; - padding: 6px 0; - background: var(--vscode-editor-background); - } - .toolbar button { - padding: 4px 10px; border-radius: 3px; - border: 1px solid var(--vscode-button-secondaryBackground, rgba(128,128,128,.3)); - background: var(--vscode-button-secondaryBackground); - color: var(--vscode-button-secondaryForeground); - cursor: pointer; font-size: 12px; white-space: nowrap; - } - .toolbar button:hover { background: var(--vscode-button-secondaryHoverBackground); } - .toolbar button.primary { - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); - border-color: var(--vscode-button-background); - } - .toolbar button.primary:hover { opacity: .9; } - #search { - flex: 1; min-width: 120px; padding: 4px 8px; border-radius: 3px; - border: 1px solid var(--vscode-input-border, transparent); - background: var(--vscode-input-background); color: var(--vscode-input-foreground); - outline: none; font-size: 12px; - } - #search:focus { border-color: var(--vscode-focusBorder); } - #count { opacity: .75; font-size: 11px; white-space: nowrap; } - .spacer { flex: 1; } - .toolbar .fg-check { - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 12px; - color: var(--vscode-foreground); - cursor: pointer; - user-select: none; - white-space: nowrap; - } - .toolbar .fg-check input { margin: 0; } - #grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); - gap: 8px; margin-top: 8px; - } - .card { - border: 1px solid var(--vscode-panel-border, rgba(128,128,128,.25)); - border-radius: 6px; overflow: hidden; cursor: pointer; - background: var(--vscode-editorWidget-background, rgba(128,128,128,.08)); - transition: transform .08s ease, border-color .08s ease; - user-select: none; - } - .card:hover { transform: translateY(-2px); border-color: var(--vscode-focusBorder); } - .thumb-box { - height: var(--thumb-h); - display: flex; align-items: center; justify-content: center; - position: relative; - background: repeating-conic-gradient(rgba(128,128,128,.14) 0% 25%, transparent 0% 50%) 0 0/16px 16px; - } - .thumb-box img { max-width: 100%; max-height: 100%; image-rendering: pixelated; } - .placeholder { opacity: .35; font-size: 11px; } - .meta { padding: 5px 7px 6px; } - .name { - font-weight: 600; white-space: nowrap; overflow: hidden; - text-overflow: ellipsis; margin-bottom: 2px; font-size: 11px; - } - .cats { opacity: .65; font-size: 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } - .size { opacity: .5; font-size: 10px; } - .actions { - position: absolute; bottom: 3px; right: 3px; - display: flex; gap: 2px; opacity: 0; transition: opacity .12s ease; z-index: 2; - } - .card:hover .actions { opacity: 1; } - .actions button { - width: 22px; height: 22px; border-radius: 4px; - background: rgba(0,0,0,.6); color: #fff; border: none; - cursor: pointer; font-size: 12px; line-height: 22px; - text-align: center; padding: 0; - } - .actions button:hover { background: rgba(0,0,0,.85); } - .empty { - margin-top: 32px; text-align: center; opacity: .6; - line-height: 1.8; white-space: pre-line; padding: 0 12px; - } - /* 拖拽临时截图进入时的高亮提示 */ - .drop-hint { - display: none; - position: fixed; inset: 6px; - border: 2px dashed var(--vscode-focusBorder, #0078d4); - border-radius: 8px; - background: rgba(0,120,212,.08); - align-items: center; justify-content: center; - text-align: center; padding: 16px; - font-size: 13px; font-weight: 600; - color: var(--vscode-focusBorder, #0078d4); - pointer-events: none; - z-index: 50; - } - body.drop-active .drop-hint { display: flex; } +/* 模板资源面板(共享层见 media/shared/tokens.css + controls.css) + 工具条按钮用共享 .mini-btn 类;本文件只保留 tile / 悬浮操作 / 拖拽提示等特有部分。 */ + +:root { --thumb-h: 96px; } + +body { + margin: 0; + padding: var(--space-md) 12px var(--space-xl); + color: var(--text-primary); + background: var(--bg-page); + font-family: var(--font-ui); + font-size: var(--font-size-root); +} + +#search { flex: 1; min-width: 120px; } +#count { color: var(--text-muted); font-size: var(--font-xs); white-space: nowrap; } +.spacer { flex: 1; } + +.toolbar .fg-check { + display: inline-flex; + align-items: center; + gap: var(--space-xs); + color: var(--text-primary); + font-size: var(--font-sm); + cursor: pointer; + user-select: none; + white-space: nowrap; +} +.toolbar .fg-check input { margin: 0; } + +#grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); + gap: var(--space-sm); + margin-top: var(--space-sm); +} + +/* 缩略图 tile:底子沿用共享 .card,这里只加裁切与交互反馈 */ +.card { + overflow: hidden; + cursor: pointer; + user-select: none; + transition: transform .08s ease, border-color .08s ease; +} +.card:hover { transform: translateY(-2px); border-color: var(--border-strong); } + +.thumb-box { + height: var(--thumb-h); + display: flex; + align-items: center; + justify-content: center; + position: relative; + background: repeating-conic-gradient( + color-mix(in srgb, var(--text-muted) 14%, transparent) 0% 25%, transparent 0% 50% + ) 0 0 / 16px 16px; +} +.thumb-box img { max-width: 100%; max-height: 100%; image-rendering: pixelated; } +.placeholder { color: var(--text-muted); font-size: var(--font-xs); } + +.meta { padding: 5px 7px 6px; } +.name { + font-weight: var(--weight-medium); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 2px; + font-size: var(--font-xs); +} +.cats { color: var(--text-muted); font-size: var(--font-2xs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.size { color: var(--text-muted); font-size: var(--font-2xs); } + +/* tile 上的操作按钮组:常驻可见(默认 85%),底色/文字走 token, + 不再用 rgba(0,0,0,.6)+#fff(浅色主题下是黑块) */ +.actions { + position: absolute; + bottom: 3px; + right: 3px; + display: flex; + gap: 2px; + opacity: .85; + transition: opacity .12s ease; + z-index: 2; +} +.card:hover .actions { opacity: 1; } +.actions button { + width: 22px; + height: 22px; + min-height: 22px; + padding: 0; + border: var(--border-width) solid var(--border); + border-radius: var(--radius-sm); + background: var(--bg-control); + color: var(--text-primary); + font-size: var(--font-sm); + line-height: 1; + text-align: center; +} +.actions button:hover { background: var(--bg-control-hover); border-color: var(--border-strong); } + +.empty { + margin-top: var(--space-xl); + text-align: center; + color: var(--text-muted); + line-height: 1.8; + white-space: pre-line; + padding: 0 12px; +} + +/* 拖拽临时截图进入时的高亮提示 */ +.drop-hint { + display: none; + position: fixed; + inset: 6px; + border: 2px dashed var(--border-strong); + border-radius: var(--radius-lg); + background: color-mix(in srgb, var(--border-strong) 8%, transparent); + align-items: center; + justify-content: center; + text-align: center; + padding: var(--space-lg); + font-size: var(--font-md); + font-weight: var(--weight-medium); + color: var(--border-strong); + pointer-events: none; + z-index: 50; +} +body.drop-active .drop-hint { display: flex; } diff --git a/media/templatePanel/index.html b/media/templatePanel/index.html index da10992..d87e20e 100644 --- a/media/templatePanel/index.html +++ b/media/templatePanel/index.html @@ -3,10 +3,12 @@ + + -
+
diff --git a/media/templatePanel/style.css b/media/templatePanel/style.css index b7c31e2..d128b71 100644 --- a/media/templatePanel/style.css +++ b/media/templatePanel/style.css @@ -1,102 +1,91 @@ - :root { --thumb-h: 96px; } - body { - color: var(--vscode-editor-foreground); - background: var(--vscode-editor-background); - font-family: var(--vscode-font-family); - font-size: var(--vscode-font-size, 13px); - margin: 0; - padding: 10px 12px 20px; - } - .toolbar { - position: sticky; - top: 0; - z-index: 10; - display: flex; - flex-wrap: wrap; - gap: 8px; - align-items: center; - padding: 6px 0; - background: var(--vscode-editor-background); - } - #search { - flex: 1; - min-width: 120px; - padding: 4px 8px; - border-radius: 3px; - border: 1px solid var(--vscode-input-border, transparent); - background: var(--vscode-input-background); - color: var(--vscode-input-foreground); - outline: none; - } - #search:focus { border-color: var(--vscode-focusBorder); } - #count { opacity: .75; font-size: 11px; white-space: nowrap; } - .hint { opacity: .6; font-size: 11px; width: 100%; } - #grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); - gap: 8px; - margin-top: 4px; - } - .card { - border: 1px solid var(--vscode-panel-border, rgba(128,128,128,.25)); - border-radius: 6px; - overflow: hidden; - cursor: pointer; - background: var(--vscode-editorWidget-background, rgba(128,128,128,.08)); - transition: transform .08s ease, border-color .08s ease; - user-select: none; - } - .card:hover { transform: translateY(-2px); border-color: var(--vscode-focusBorder); } - .thumb-box { - height: var(--thumb-h); - display: flex; - align-items: center; - justify-content: center; - position: relative; - background: - repeating-conic-gradient(rgba(128,128,128,.14) 0% 25%, transparent 0% 50%) 0 0/16px 16px; - } - .thumb-box img { - max-width: 100%; - max-height: 100%; - image-rendering: pixelated; - } - .open-btn { - position: absolute; - top: 3px; - right: 3px; - width: 22px; - height: 22px; - border-radius: 4px; - background: rgba(0,0,0,.55); - color: #fff; - border: none; - cursor: pointer; - font-size: 13px; - line-height: 22px; - text-align: center; - padding: 0; - opacity: 0; - transition: opacity .12s ease; - z-index: 2; - } - .thumb-box:hover .open-btn { opacity: 1; } - .open-btn:hover { background: rgba(0,0,0,.8); } - .placeholder { opacity: .35; font-size: 11px; } - .meta { padding: 5px 7px 6px; } - .name { - font-weight: 600; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - margin-bottom: 2px; - } - .size { opacity: .65; font-size: 11px; } - .empty { - margin-top: 32px; - text-align: center; - opacity: .6; - line-height: 1.8; - white-space: pre-line; - padding: 0 12px; - } +/* 模板画廊面板(共享层见 media/shared/tokens.css + controls.css) + 本文件只保留面板特有部分:缩略图 tile、悬浮打开按钮、网格与空态排版。 + 颜色 / 圆角 / 间距 / 字号一律引用共享 token。 */ + +:root { --thumb-h: 96px; } + +body { + margin: 0; + padding: var(--space-md) 12px var(--space-xl); + color: var(--text-primary); + background: var(--bg-page); + font-family: var(--font-ui); + font-size: var(--font-size-root); +} + +#search { flex: 1; min-width: 120px; } +#count { color: var(--text-muted); font-size: var(--font-xs); white-space: nowrap; } +.hint { color: var(--text-muted); font-size: var(--font-xs); width: 100%; } + +#grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); + gap: var(--space-sm); + margin-top: var(--space-xs); +} + +/* 缩略图 tile:视觉底子沿用共享 .card(容器底色 + --radius-lg + 描边), + 这里只加裁切与交互反馈 */ +.card { + overflow: hidden; + cursor: pointer; + user-select: none; + transition: transform .08s ease, border-color .08s ease; +} +.card:hover { transform: translateY(-2px); border-color: var(--border-strong); } + +.thumb-box { + height: var(--thumb-h); + display: flex; + align-items: center; + justify-content: center; + position: relative; + background: repeating-conic-gradient( + color-mix(in srgb, var(--text-muted) 14%, transparent) 0% 25%, transparent 0% 50% + ) 0 0 / 16px 16px; +} +.thumb-box img { max-width: 100%; max-height: 100%; image-rendering: pixelated; } + +/* 打开按钮:常驻可见(默认约 85% 不透明度)——可点击控件不能只在 hover 时现身; + 底色用控件面、文字用前景色,避免原先 rgba(0,0,0,.55)+#fff 在浅色主题成为黑块 */ +.open-btn { + position: absolute; + top: 3px; + right: 3px; + width: 22px; + height: 22px; + min-height: 22px; + padding: 0; + border: var(--border-width) solid var(--border); + border-radius: var(--radius-sm); + background: var(--bg-control); + color: var(--text-primary); + font-size: var(--font-md); + line-height: 1; + opacity: .85; + transition: opacity .12s ease, background-color .12s ease; + z-index: 2; +} +.open-btn:hover, +.open-btn:focus-visible { opacity: 1; background: var(--bg-control-hover); border-color: var(--border-strong); } + +.placeholder { color: var(--text-muted); font-size: var(--font-xs); } + +.meta { padding: 5px 7px 6px; } +.name { + font-weight: var(--weight-medium); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 2px; +} +.size { color: var(--text-muted); font-size: var(--font-xs); } + +.empty { + margin-top: var(--space-xl); + text-align: center; + color: var(--text-muted); + line-height: 1.8; + white-space: pre-line; + padding: 0 12px; +} diff --git a/scripts/test_console_accountseg.js b/scripts/test_console_accountseg.js index e6ff952..7a85380 100644 --- a/scripts/test_console_accountseg.js +++ b/scripts/test_console_accountseg.js @@ -31,6 +31,10 @@ html = html .replaceAll('__CSP_NONCE__', 'test') .replaceAll('__CSP_SOURCE__', "'self'") .replaceAll('__I18N_JSON__', JSON.stringify(dictionary)) + .replace('', + ``) + .replace('', + ``) .replace('', ``); for (const [marker, file] of [ ['__CORE_SCRIPT_URI__', 'core.js'], @@ -156,8 +160,12 @@ assert(fieldsHost && fieldsHost.hidden === false, '字段区常驻显示'); console.log('6. CSS 兜底:hidden 必须压过作者样式的 display(真机折叠生效的前提)'); const css = fs.readFileSync(path.join(componentRoot, 'console.css'), 'utf8'); +const sharedTokens = fs.readFileSync(path.join(root, 'media', 'shared', 'tokens.css'), 'utf8'); +const sharedControls = fs.readFileSync(path.join(root, 'media', 'shared', 'controls.css'), 'utf8'); assert(/\[hidden\]\s*\{[^}]*display:\s*none\s*!important/.test(css), 'console.css 必须包含 [hidden] { display: none !important }(否则 .config-fields 的 grid 会顶掉 hidden)'); +assert(/\[hidden\]\s*\{[^}]*display:\s*none\s*!important/.test(sharedTokens), + 'shared/tokens.css 必须包含 [hidden] { display: none !important }(否则 .config-fields 的 grid 会顶掉 hidden)'); console.log('7. 折叠状态持久化:覆盖卡点击折叠发出 saveUiState,注入 uiState 后初始即收起'); sent.length = 0; @@ -200,4 +208,20 @@ const emptyStoreCards = document.querySelectorAll('#accountList .gconfig-card'); assert(emptyStoreCards.length === 3, '无数据文件但有 store 模块时仍显示三张编辑卡'); assert(emptyStoreCards[0].querySelector('textarea'), '空 store 仍可编辑账号列表'); +console.log('10. 可点击规范:真按钮=控件面+描边,行级区=极浅底色(无描边)'); +assert(sharedControls.includes('可点击性硬规范'), 'shared/controls.css 含可点击规范段落'); +assert(sharedTokens.includes('--bg-control') && sharedTokens.includes('--bg-control-hover'), + 'tokens 定义控件面底色(未悬浮即有底色)'); +assert(sharedTokens.includes('--bg-row') && sharedTokens.includes('--bg-row-hover'), + 'tokens 定义行级可点击区的极浅底色 + hover'); +assert(/\.config-group__toggle\s*\{[^}]*border:/.test(css), '分组折叠按键(真按钮)有描边'); +assert(/\.config-section-title--toggle\s*\{[^}]*background:\s*var\(--bg-row\)/.test(css), + '启动设置折叠头用行级浅底色'); +assert(/\.subgroup-head\s*\{[^}]*background:\s*var\(--bg-row\)/.test(css), + '任务分组头用行级浅底色'); +assert(/\.group-head\s*\{[^}]*background:\s*var\(--bg-row\)/.test(css), + 'kind 级任务组头用行级浅底色'); +assert(!/\.group-head\s*\{[^}]*border:\s*var\(--border-width/.test(css), '行级组头不再有描边(避免方块墙)'); +assert(!/\.subgroup-head\s*\{[^}]*border:\s*var\(--border-width/.test(css), '业务分组头不再有描边'); + console.log('\n全部通过'); diff --git a/scripts/test_console_configseg.js b/scripts/test_console_configseg.js index f094bd7..46ca6c5 100644 --- a/scripts/test_console_configseg.js +++ b/scripts/test_console_configseg.js @@ -29,6 +29,10 @@ html = html .replaceAll('__CSP_NONCE__', 'test') .replaceAll('__CSP_SOURCE__', "'self'") .replaceAll('__I18N_JSON__', JSON.stringify(dictionary)) + .replace('', + ``) + .replace('', + ``) .replace('', ``); for (const [marker, file] of [ ['__CORE_SCRIPT_URI__', 'core.js'], diff --git a/scripts/test_design_system.js b/scripts/test_design_system.js new file mode 100644 index 0000000..4a15604 --- /dev/null +++ b/scripts/test_design_system.js @@ -0,0 +1,126 @@ +/** + * 全局 UI 一致性审计(docs/design-system.md 第 11/12 条)。 + * + * 这是一个**静态结构审计**,不启动 webview:它校验「有没有接上共享设计层」以及 + * 「有没有绕过 token 自己造视觉」。这类问题此前都是人肉 review 才发现的 + * (例如某面板 4px 圆角、某面板 rgba(0,0,0,.55) 黑块、某面板忘了引 shared)。 + * + * 校验项: + * 1. 每个面板 HTML 必须按 顺序 引入 shared/tokens.css → shared/controls.css → 自身样式; + * 2. 每个面板宿主必须调用 applySharedAssets(否则占位符留在 HTML 里,样式静默失效); + * 3. 面板 CSS 不得出现 hex/rgb 字面量、不得直引 --vscode-*(唯一例外是 shared/tokens.css); + * 4. 面板 CSS 的圆角/字号不得写裸 px(必须走 radius / font token); + * 5. 面板 CSS 里用到的 var(--x) 必须在 shared 或本文件内有定义(防拼写漂移)。 + * + * 例外:canvas 绘图色(如标注框描边)属于**图像内容**而非 UI 主题,不受第 3 条约束。 + */ +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +const root = path.resolve(__dirname, '..'); +const mediaDir = path.join(root, 'media'); +const sharedDir = path.join(mediaDir, 'shared'); + +/** 面板目录 → 宿主源文件(用于校验 applySharedAssets 调用) */ +const PANEL_HOST = { + console: 'src/consolePanel.ts', + templatePanel: 'src/templatePanel.ts', + templateAssetPanel: 'src/templateAssetPanel.ts', + tempScreenshots: 'src/tempScreenshotPanel.ts', + annotationPanel: 'src/annotationPanel.ts', + characterManager: 'src/characterPanel.ts', +}; + +const read = (p) => fs.readFileSync(p, 'utf8'); +/** 去掉注释后再做字面量扫描(注释里举例说明旧写法是允许的) */ +const stripComments = (css) => css.replace(/\/\*[\s\S]*?\*\//g, ''); + +const sharedTokens = stripComments(read(path.join(sharedDir, 'tokens.css'))); +const sharedControls = stripComments(read(path.join(sharedDir, 'controls.css'))); +const sharedAll = sharedTokens + '\n' + sharedControls; + +const panels = fs.readdirSync(mediaDir).filter((name) => { + const dir = path.join(mediaDir, name); + return fs.statSync(dir).isDirectory() && fs.existsSync(path.join(dir, 'index.html')); +}); + +assert(panels.length >= 6, `面板数量异常:${panels.join(', ')}`); +console.log(`1. 发现 ${panels.length} 个面板:${panels.join(', ')}`); + +for (const panel of panels) { + const dir = path.join(mediaDir, panel); + const html = read(path.join(dir, 'index.html')); + + console.log(`2. [${panel}] 共享层引入顺序与宿主接线`); + const iTokens = html.indexOf('__SHARED_TOKENS_URI__'); + const iControls = html.indexOf('__SHARED_CONTROLS_URI__'); + const iStyle = html.indexOf('__STYLE_URI__'); + assert(iTokens >= 0, `${panel}/index.html 未引入 shared/tokens.css`); + assert(iControls >= 0, `${panel}/index.html 未引入 shared/controls.css`); + assert(iTokens < iControls, `${panel}: tokens.css 必须在 controls.css 之前`); + assert(iControls < iStyle, `${panel}: 面板自身样式必须排在共享层之后(否则覆盖不生效)`); + + const hostRel = PANEL_HOST[panel]; + assert(hostRel, `${panel} 未登记宿主源文件(新增面板请补 PANEL_HOST)`); + const host = read(path.join(root, hostRel)); + assert(host.includes('applySharedAssets'), `${hostRel} 未调用 applySharedAssets(占位符不会被替换)`); + assert(host.includes('__STYLE_URI__'), `${hostRel} 未注入面板样式 URI`); + + // 收紧 localResourceRoots 的面板必须显式放行 media/shared + const rootsLine = host.match(/localResourceRoots:\s*\[([\s\S]*?)\]/g) || []; + const narrowed = rootsLine.filter((block) => !block.includes('extensionUri')); + if (narrowed.length) { + assert(host.includes('sharedResourceRoot'), + `${hostRel} 的 localResourceRoots 未放行 extensionUri,必须加 sharedResourceRoot(...)`); + } +} + +for (const panel of panels) { + const dir = path.join(mediaDir, panel); + const cssFiles = fs.readdirSync(dir).filter((f) => f.endsWith('.css')); + for (const file of cssFiles) { + const rel = `media/${panel}/${file}`; + const css = stripComments(read(path.join(dir, file))); + + console.log(`3. [${rel}] 无字面量色 / 无直引 --vscode-*`); + const literals = css.match(/#[0-9a-fA-F]{3,8}\b|rgba?\(/g) || []; + assert(literals.length === 0, `${rel} 仍有硬编码颜色:${literals.join(', ')}(改用 tokens)`); + const vscodeVars = [...new Set(css.match(/--vscode-[a-z-]+/g) || [])]; + assert(vscodeVars.length === 0, + `${rel} 直引了 VS Code 变量:${vscodeVars.join(', ')}(唯一允许处是 media/shared/tokens.css)`); + + console.log(`4. [${rel}] 圆角/字号走 scale`); + const rawRadius = css.match(/border-radius:\s*\d+(?:\.\d+)?px/g) || []; + assert(rawRadius.length === 0, `${rel} 有裸 px 圆角:${rawRadius.join(', ')}(改用 --radius-*)`); + const rawFont = css.match(/font-size:\s*\d+(?:\.\d+)?px/g) || []; + assert(rawFont.length === 0, `${rel} 有裸 px 字号:${rawFont.join(', ')}(改用 --font-*)`); + + console.log(`5. [${rel}] var() 全部有定义`); + const defined = new Set(); + for (const src of [sharedAll, css]) { + for (const m of src.matchAll(/(--[a-z0-9-]+)\s*:/g)) defined.add(m[1]); + } + // VS Code 主题变量由宿主提供,不算未定义 + const missing = [...new Set((css.match(/var\((--[a-z0-9-]+)/g) || []).map((v) => v.slice(4)))] + .filter((name) => !defined.has(name) && !name.startsWith('--vscode-')); + assert(missing.length === 0, `${rel} 使用了未定义的变量:${missing.join(', ')}`); + } +} + +console.log('6. shared/tokens.css 仍是唯一直引 --vscode-* 的文件'); +const offenders = []; +for (const panel of panels) { + const dir = path.join(mediaDir, panel); + for (const file of fs.readdirSync(dir)) { + if (!file.endsWith('.css')) continue; + if (stripComments(read(path.join(dir, file))).includes('--vscode-')) offenders.push(`media/${panel}/${file}`); + } +} +for (const file of fs.readdirSync(sharedDir)) { + if (!file.endsWith('.css') || file === 'tokens.css') continue; + if (stripComments(read(path.join(sharedDir, file))).includes('--vscode-')) offenders.push(`media/shared/${file}`); +} +assert(offenders.length === 0, `以下文件直引了 --vscode-*:${offenders.join(', ')}`); + +console.log('\n全部通过'); diff --git a/scripts/test_task_launcher_executor.js b/scripts/test_task_launcher_executor.js index f5d9ca4..8f535cb 100644 --- a/scripts/test_task_launcher_executor.js +++ b/scripts/test_task_launcher_executor.js @@ -32,6 +32,10 @@ html = html .replaceAll('__CSP_NONCE__', 'test') .replaceAll('__CSP_SOURCE__', "'self'") .replaceAll('__I18N_JSON__', JSON.stringify(dictionary)) + .replace('', + ``) + .replace('', + ``) .replace('', ``); for (const [marker, file] of [ ['__CORE_SCRIPT_URI__', 'core.js'], diff --git a/scripts/test_task_launcher_subconfigs.js b/scripts/test_task_launcher_subconfigs.js index 569fbad..777d34b 100644 --- a/scripts/test_task_launcher_subconfigs.js +++ b/scripts/test_task_launcher_subconfigs.js @@ -30,6 +30,10 @@ html = html .replaceAll('__CSP_NONCE__', 'test') .replaceAll('__CSP_SOURCE__', "'self'") .replaceAll('__I18N_JSON__', JSON.stringify(dictionary)) + .replace('', + ``) + .replace('', + ``) .replace('', ``); for (const [marker, file] of [ ['__CORE_SCRIPT_URI__', 'core.js'], diff --git a/src/annotationPanel.ts b/src/annotationPanel.ts index 20331b6..88f11cb 100644 --- a/src/annotationPanel.ts +++ b/src/annotationPanel.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { TemplateAssetData } from './templateAssetData'; import { injectWebviewLocalization, tr } from './localization'; -import { getNonce } from './webviewHtml'; +import { applySharedAssets, getNonce } from './webviewHtml'; /* ---------------- 标注数据类型 ---------------- */ @@ -252,11 +252,11 @@ function annotationHtml(cspSource: string, extensionUri: vscode.Uri, webview: vs const resource = (name: string) => webview.asWebviewUri( vscode.Uri.joinPath(extensionUri, 'media', 'annotationPanel', name), ).toString(true); - return injectWebviewLocalization( + return injectWebviewLocalization(applySharedAssets(webview, extensionUri, fs.readFileSync(file, 'utf-8') .split('__CSP_NONCE__').join(nonce) .split('__CSP_SOURCE__').join(cspSource) .split('__STYLE_URI__').join(resource('style.css')) .split('__APP_SCRIPT_URI__').join(resource('app.js')), - ); + )); } diff --git a/src/characterPanel.ts b/src/characterPanel.ts index 5a14b68..233f333 100644 --- a/src/characterPanel.ts +++ b/src/characterPanel.ts @@ -28,7 +28,7 @@ import { warmCropCache, THUMB_HEIGHT, } from './pngCrop'; -import { errorPage, getNonce } from './webviewHtml'; +import { applySharedAssets, errorPage, getNonce, sharedResourceRoot } from './webviewHtml'; export interface CharacterManagerDependencies { extensionUri: vscode.Uri; @@ -173,6 +173,7 @@ export class CharacterManagerPanel implements vscode.Disposable { localResourceRoots: [ vscode.Uri.joinPath(dependencies.extensionUri, 'media', 'characterManager'), vscode.Uri.file(dependencies.thumbDir), + sharedResourceRoot(dependencies.extensionUri), ], }, ); @@ -226,13 +227,13 @@ export class CharacterManagerPanel implements vscode.Disposable { const resource = (name: string) => webview.asWebviewUri( vscode.Uri.joinPath(this.dependencies.extensionUri, 'media', 'characterManager', name), ).toString(true); - return injectWebviewLocalization( + return injectWebviewLocalization(applySharedAssets(webview, this.dependencies.extensionUri, fs.readFileSync(file, 'utf-8') .split('__CSP_NONCE__').join(nonce) .split('__CSP_SOURCE__').join(webview.cspSource) .split('__STYLE_URI__').join(resource('characterManager.css')) .split('__APP_SCRIPT_URI__').join(resource('app.js')), - ); + )); } catch (error) { // 错误消息里会带文件路径,路径可合法包含 < > & " —— 必须转义后再拼进 HTML return errorPage( diff --git a/src/consolePanel.ts b/src/consolePanel.ts index 206878b..a7e99b6 100644 --- a/src/consolePanel.ts +++ b/src/consolePanel.ts @@ -6,7 +6,7 @@ import { injectWebviewLocalization, projectLocale, tr } from './localization'; import { i18nPoDirectorySetting, resolveProjectDir } from './projectConfig'; import { loadToolboxState, notifyExecutorRunning, onToolboxStateChange, saveToolboxState } from './toolboxState'; import { GameConnectService } from './toolboxConnect'; -import { errorPage, getNonce } from './webviewHtml'; +import { applySharedAssets, errorPage, getNonce } from './webviewHtml'; /** 单个任务的元信息 */ interface TaskInfo { @@ -418,7 +418,10 @@ export class ConsoleViewProvider implements vscode.WebviewViewProvider { this.view = view; view.webview.options = { enableScripts: true, - localResourceRoots: [vscode.Uri.joinPath(this.extensionUri, 'media', 'console')], + localResourceRoots: [ + vscode.Uri.joinPath(this.extensionUri, 'media', 'console'), + vscode.Uri.joinPath(this.extensionUri, 'media', 'shared'), + ], }; view.webview.html = this.buildHtml(view.webview); @@ -1557,8 +1560,7 @@ export class ConsoleViewProvider implements vscode.WebviewViewProvider { const resource = (name: string) => webview.asWebviewUri( vscode.Uri.joinPath(this.extensionUri, 'media', 'console', name), ).toString(true); - return injectWebviewLocalization( - html + return injectWebviewLocalization(applySharedAssets(webview, this.extensionUri, html .split('__CSP_NONCE__').join(nonce) .split('__CSP_SOURCE__').join(webview.cspSource) .split('__STYLE_URI__').join(resource('console.css')) @@ -1568,6 +1570,6 @@ export class ConsoleViewProvider implements vscode.WebviewViewProvider { .split('__TASK_CARD_SCRIPT_URI__').join(resource('taskCard.js')) .split('__CONSOLE_SCRIPT_URI__').join(resource('console.js')) .split('__APP_SCRIPT_URI__').join(resource('app.js')), - ); + )); } } diff --git a/src/tempScreenshotPanel.ts b/src/tempScreenshotPanel.ts index 6cd5faa..e0b66e0 100644 --- a/src/tempScreenshotPanel.ts +++ b/src/tempScreenshotPanel.ts @@ -9,7 +9,7 @@ import { captureGameWindow } from './screenshotCapture'; import { clearPendingDrag, setPendingDrag } from './tempDrag'; import { cropTemplateThumbFileAsync, readImageSize, removeTemplateThumbFile, THUMB_HEIGHT } from './pngCrop'; import { injectWebviewLocalization, tr } from './localization'; -import { getNonce } from './webviewHtml'; +import { applySharedAssets, getNonce } from './webviewHtml'; /* ---------------- 剪贴板读图(Windows 回退路径) ---------------- */ @@ -317,11 +317,11 @@ function tempScreenshotHtml(webview: vscode.Webview, extensionUri: vscode.Uri): const resource = (name: string) => webview.asWebviewUri( vscode.Uri.joinPath(extensionUri, 'media', 'tempScreenshots', name), ).toString(true); - return injectWebviewLocalization( + return injectWebviewLocalization(applySharedAssets(webview, extensionUri, fs.readFileSync(file, 'utf-8') .split('__CSP_NONCE__').join(nonce) .split('__CSP_SOURCE__').join(webview.cspSource) .split('__STYLE_URI__').join(resource('style.css')) .split('__APP_SCRIPT_URI__').join(resource('app.js')), - ); + )); } diff --git a/src/templateAssetPanel.ts b/src/templateAssetPanel.ts index 769f7f0..3706dcd 100644 --- a/src/templateAssetPanel.ts +++ b/src/templateAssetPanel.ts @@ -11,7 +11,7 @@ import { takePendingDrag } from './tempDrag'; import { currentWorkspaceFolderUri, ideSetting, labelEnumClassName, labelEnumPathInputError, labelEnumPathSetting, normalizeLabelEnumPathInput, setIdeSetting, templatesDirectory } from './projectConfig'; import { labelEnumRenameImpact, labelEnumRenameMessage, referencingFiles, writableClassName } from './labelEnumGuard'; import { derivedEnumPath, isPathInsideRoot, needsEnumPathPrompt, SaveTarget, saveToAssetsItems } from './saveToAssetsPure'; -import { getNonce } from './webviewHtml'; +import { applySharedAssets, getNonce } from './webviewHtml'; /* ---------------- 控制器 ---------------- */ @@ -593,11 +593,11 @@ function assetGalleryHtml(webview: vscode.Webview, extensionUri: vscode.Uri): st const resource = (name: string) => webview.asWebviewUri( vscode.Uri.joinPath(extensionUri, 'media', 'templateAssetPanel', name), ).toString(true); - return injectWebviewLocalization( + return injectWebviewLocalization(applySharedAssets(webview, extensionUri, fs.readFileSync(file, 'utf-8') .split('__CSP_NONCE__').join(nonce) .split('__CSP_SOURCE__').join(webview.cspSource) .split('__STYLE_URI__').join(resource('style.css')) .split('__APP_SCRIPT_URI__').join(resource('app.js')), - ); + )); } diff --git a/src/templatePanel.ts b/src/templatePanel.ts index 844535b..a719342 100644 --- a/src/templatePanel.ts +++ b/src/templatePanel.ts @@ -5,7 +5,7 @@ import { FeatureData } from './featureData'; import { cropTemplateThumbFileAsync, openAnnotatedImage, THUMB_HEIGHT } from './pngCrop'; import { featureAliases } from './providers'; import { injectWebviewLocalization, tr } from './localization'; -import { getNonce } from './webviewHtml'; +import { applySharedAssets, getNonce } from './webviewHtml'; /** 发送给 webview 的模板元数据(不含图片) */ interface TemplateMeta { @@ -293,11 +293,11 @@ function galleryHtml(webview: vscode.Webview, cspSource: string, extensionUri: v const resource = (name: string) => webview.asWebviewUri( vscode.Uri.joinPath(extensionUri, 'media', 'templatePanel', name), ).toString(true); - return injectWebviewLocalization( + return injectWebviewLocalization(applySharedAssets(webview, extensionUri, fs.readFileSync(file, 'utf-8') .split('__CSP_NONCE__').join(nonce) .split('__CSP_SOURCE__').join(webview.cspSource) .split('__STYLE_URI__').join(resource('style.css')) .split('__APP_SCRIPT_URI__').join(resource('app.js')), - ); + )); } diff --git a/src/webviewHtml.ts b/src/webviewHtml.ts index 2f3d377..56e7821 100644 --- a/src/webviewHtml.ts +++ b/src/webviewHtml.ts @@ -1,4 +1,5 @@ import * as crypto from 'crypto'; +import type * as vscode from 'vscode'; /** * Webview HTML 组装的公共约定。 @@ -6,13 +7,60 @@ import * as crypto from 'crypto'; * 这个文件收拢了两条**看起来像样板、其实是安全边界**的规则。它们原先各自散在 * 5 个 panel 文件里逐份复制,于是既出现了实现漂移(nonce 长度 24/32 混用), * 也出现了漏改(有的地方把错误消息直接拼进 HTML)。 + * + * `vscode` 只做类型导入 + 函数体内延迟 require:本文件里的 `escapeHtml` / + * `getNonce` / `errorPage` 是**纯函数**,要能在扩展宿主之外(如 scripts/ 下的 + * 单测)被 require;顶层 `import ... from 'vscode'` 会让这些测试直接崩在 + * `Cannot find module 'vscode'`。 */ +interface VscodeApi { + Uri: { joinPath(base: vscode.Uri, ...segments: string[]): vscode.Uri }; +} + +function vscodeApi(): VscodeApi { + // eslint-disable-next-line @typescript-eslint/no-var-requires + return require('vscode') as VscodeApi; +} + /** CSP nonce 的长度(字符数)。历史上有 24 与 32 两种,统一取 32。 */ const NONCE_LENGTH = 32; const NONCE_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; +/** + * 共享设计层(`media/shared`)的 webview 资源根。 + * + * 各面板的 `localResourceRoots` 必须包含它,否则 `asWebviewUri` 出来的 + * tokens.css / controls.css 会被 webview 拒绝加载(面板样式静默失效)。 + * 放行整个 extensionUri 的面板天然包含它,收紧到 media/<面板> 的面板必须显式加。 + */ +export function sharedResourceRoot(extensionUri: vscode.Uri): vscode.Uri { + return vscodeApi().Uri.joinPath(extensionUri, 'media', 'shared'); +} + +/** + * 把共享设计层的 URI 注入面板 HTML 的两个占位符。 + * + * 顺序语义由面板 HTML 保证:`__SHARED_TOKENS_URI__` / `__SHARED_CONTROLS_URI__` + * 两个 `` 必须排在面板自身样式之前,面板才能覆盖共享层。 + * 收在这里是为了不让 6 个面板各自抄一遍 `asWebviewUri` 组装逻辑(历史上正是 + * 这种逐份复制导致过 nonce 长度与转义漂移)。 + */ +export function applySharedAssets( + webview: vscode.Webview, + extensionUri: vscode.Uri, + html: string, +): string { + const joinPath = vscodeApi().Uri.joinPath; + const uri = (name: string) => webview.asWebviewUri( + joinPath(extensionUri, 'media', 'shared', name), + ).toString(true); + return html + .split('__SHARED_TOKENS_URI__').join(uri('tokens.css')) + .split('__SHARED_CONTROLS_URI__').join(uri('controls.css')); +} + /** * 生成 CSP nonce。 *