agent-Specialization/static/src/styles/base/_tokens.scss
JOJO b4030c386e refactor(frontend): 颜色 token 系统两层重构 + 三主题填色 + 去半透明
将前端配色从散落的硬编码/双命名体系(--claude-* / --theme-*)重构为
两层 token 结构(原始层 + 中性语义层),并按"经典抄 Claude、浅色抄
ChatGPT、深色自研"的定位完成三主题填色,同时建立 stylelint 防回退栏杆。
代码大量由 AI 生成的历史遗留配色问题(同语义多值、跨主题塌缩、强调色
撞文字色、fallback 第二真相源等)在此一并收口。

== 一、Token 结构重构(_tokens.scss)==
- 重写为两层:原始层 + 语义层;组件只引用中性语义名
  (--surface-* / --text-* / --border-* / --accent* / --state-* 等)。
- --claude-* / --theme-* 降级为兼容别名(41 个转发),迁移完成后删除。
- 三主题(classic / light / dark)+ 首屏回退(:root:not([data-theme]),
  与经典同值)全部补齐,结构对齐、不再缺项。

== 二、三主题填色 ==
- 经典 = Claude 官网亮色盘:暖奶油表面阶梯
  canvas #faf9f5 < soft #f5f0e8 < card #efe9de < cream-strong #e8e0d2
  < 纯白浮起;文字 #141413;hairline #e6dfd8;暖橙 primary #cc785c。
- 浅色 = ChatGPT 亮色盘:冷白 #ffffff / 侧栏 #f9f9f9 / 嵌套 #f3f3f3,
  中性灰文字,近黑 primary #181818。
- 深色 = 自研中性灰阶,本轮完全不动(原样保留)。
- 修复塌缩 bug:强调色在 light/dark 原被误填成灰(CTA 失去品牌色),
  现统一回品牌 primary;light 表面原全塌成 #ffffff(过于平面),
  现按灰阶拉开层次。
- 强调色克制(CTA-only voltage):primary 仅用于发送按钮/logo 等极少
  数处;hover / 选中 / 运行态一律走中性灰(运行态指示器从 amber 改灰)。
- 对话区背景从纯白改为暖奶油 canvas;个人空间下拉菜单、git 状态条、
  二级菜单等收口到统一语义色。

== 三、全面去半透明 ==
- 所有实体面板/二级菜单背景改为不透明(surface token 由半透明白改实色,
  --mobile-menu 三亮色主题改实色)。
- 移除已不透明面板上失效的 backdrop-filter 磨砂(移动端菜单、右键菜单、
  模型模式下拉等)。
- 遮罩 scrim(--overlay-scrim)与刻意玻璃质感装饰保留半透明。

== 四、块展开底色统一 ==
- 思考/工具块展开区底色统一为对话背景色:.collapsible-block 及其 header
  背景由 --surface-soft 改 transparent,与堆叠块(stacked-block)一致,
  消除"单块奶油底 vs 多块透明"的不一致。

== 五、布局修复 ==
- 工作区隐藏后残留空白:.workspace-panel--collapsed 补 flex-basis:0 +
  width/min-width:0,修正 flex-basis:auto 被子元素内容宽度撑开导致内联
  width:0 失效、collapsed 后仍占 ~300px 的问题。

== 六、防回退栏杆(新增)==
- 新增 .stylelintrc.cjs:三条规则拦截裸 hex / rgb()|hsl() 字面色 /
  var(--x, fallback) 兜底 / prefers-color-scheme;存量未清理文件用
  BASELINE_EXEMPT 临时豁免(现仅剩 5 个 admin 页面,本轮按用户要求不处理),
  清理一个移除一个、不再回退。
- package.json:build 脚本接入 stylelint(tsc → stylelint → vite),
  新增 lint:css 脚本及 stylelint/postcss-scss/postcss-html 依赖。
- 修复 25 处 \!important 笔误(zsh 历史扩展污染导致的无效 CSS)。

== 七、辅助脚本(新增 scripts/)==
- lint_check.py:Node 环境不可用时的 stylelint 替身(复刻三规则 + 括号
  校验,支持多行声明检测)。
- color_map.py:硬编码色 → token 反查,判定可映射/野色。
- strip_fallback.py:安全剥离已定义 token 的冗余 fallback。

== 八、文档 ==
- CLAUDE.md / AGENTS.md 前端设计规范更新:颜色规则扩写为三主题定位 +
  两层 token + 表面层次 + 强调色克制 + color-mix 派生 + stylelint 栏杆;
  新增"实体面板禁止半透明"规则;"禁止边缘光晕"补充"仅在用户明确允许
  或要求时才可使用"。

注:前端构建验证(npm run build / lint:css)由用户本机完成;AI 工具
上下文 Node v24 段错误无法运行 node/npm/vite/stylelint。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 16:20:58 +08:00

319 lines
11 KiB
SCSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 设计 Token —— 两层结构:语义层(按主题取值)+ 兼容别名层 */
/* 规范见 doc/frontend/color_token_spec.md。本文件为结构重构产物值与重构前逐一致零视觉变化。 */
:root {
color-scheme: light;
/* 主题无关的布局变量 */
--app-viewport: 100vh;
--app-bottom-inset: env(safe-area-inset-bottom, 0px);
/* 装饰色主题无关固定品牌色macOS 风格窗口关闭红绿灯 */
--mac-close: #ff5f57;
--mac-close-hover: #ff3b30;
--mac-close-border: #e0443e;
/* 媒体上的文字可读性阴影(主题无关,需固定强暗影压住任意底图) */
--text-shadow-legible: rgba(0, 0, 0, 0.8);
/* 彩蛋装饰色主题无关固定festive色 */
--decorative-gold: #ffd700;
--decorative-pink: #ff69b4;
/* 玻璃质感/实验视觉装饰光斑(主题无关,固定品牌渐变色,刻意不随主题变) */
--glass-tint-blue: rgba(104, 147, 255, 0.2);
--glass-tint-blue-soft: rgba(128, 183, 255, 0.18);
--glass-tint-blue-mid: rgba(140, 180, 255, 0.35);
--glass-tint-amber: rgba(255, 200, 150, 0.22);
--glass-base-deep: rgba(28, 27, 41, 0.95);
--glass-shadow-deep: rgba(24, 20, 37, 0.45);
/* 进度环未完成轨道(主题无关,固定中性灰) */
--progress-track: rgba(127, 135, 146, 0.28);
/* 拖拽预览浮层投影(主题无关,固定黑投影) */
--drag-preview-shadow: rgba(0, 0, 0, 0.35);
/* === 兼容别名(旧 token 名 → 新语义 token迁移完成后删除 === */
--claude-bg: var(--surface-base);
--claude-panel: var(--surface-panel);
--claude-left-rail: var(--surface-rail);
--claude-sidebar: var(--surface-sidebar);
--theme-surface-card: var(--surface-card);
--theme-surface-strong: var(--surface-raised);
--theme-surface-soft: var(--surface-soft);
--theme-surface-muted: var(--surface-muted);
--claude-text: var(--text-primary);
--claude-text-secondary: var(--text-secondary);
--claude-text-tertiary: var(--text-tertiary);
--claude-muted: var(--text-muted);
--claude-border: var(--border-default);
--claude-border-strong: var(--border-strong);
--theme-card-border-strong: var(--border-card-strong);
--theme-chip-border: var(--chip-border);
--claude-accent: var(--accent);
--claude-accent-strong: var(--accent-strong);
--claude-button-hover: var(--accent-hover);
--claude-button-active: var(--accent-active);
--claude-deep: var(--accent-deep);
--claude-deep-strong: var(--accent-deep-strong);
--claude-success: var(--state-success);
--claude-warning: var(--state-warning);
--claude-danger: var(--state-danger);
--claude-highlight: var(--highlight);
--theme-tab-active: var(--tab-active);
--theme-chip-bg: var(--chip-bg);
--theme-badge-bg: var(--badge-bg);
--theme-switch-track: var(--switch-track);
--claude-shadow: var(--shadow-card);
--theme-shadow-soft: var(--shadow-soft);
--theme-shadow-mid: var(--shadow-mid);
--theme-shadow-strong: var(--shadow-strong);
--theme-overlay-scrim: var(--overlay-scrim);
--theme-mobile-menu: var(--mobile-menu);
--theme-mobile-menu-shadow: var(--mobile-menu-shadow);
--git-diff-add-text: var(--diff-add-text);
--git-diff-del-text: var(--diff-del-text);
/* 合并项:下列旧名与上面某新名同值,统一指向合并后的语义 token */
--theme-control-border: var(--border-default);
--theme-control-border-strong: var(--border-strong);
}
:root[data-theme='classic'] {
color-scheme: light;
/* Surface 表面层级 —— Claude 亮色暖奶油盘canvas<soft<card<cream-strongraised纯白浮起 */
--surface-base: #faf9f5;
--surface-panel: #faf9f5;
--surface-rail: #f5f0e8;
--surface-sidebar: #f5f0e8;
--surface-card: #efe9de;
--surface-raised: #ffffff;
--surface-soft: #f5f0e8;
--surface-muted: #e8e0d2;
/* Text 文字 —— Claude */
--text-primary: #141413;
--text-secondary: #3d3d3a;
--text-tertiary: #6c6a64;
--text-muted: rgba(108, 106, 100, 0.55);
/* Border 边框 —— Claude hairline */
--border-default: #e6dfd8;
--border-strong: #d8cfc4;
--border-card-strong: #e6dfd8;
--chip-border: #ebe6df;
/* Accent 强调 —— Claude primary克制 CTA-only */
--accent: #cc785c;
--accent-strong: #a9583e;
--accent-hover: #bd6a4f;
--accent-active: #a9583e;
--accent-deep: #6c6a64;
--accent-deep-strong: #3d3d3a;
--on-accent: #ffffff;
/* State 状态 —— Claude semantic */
--state-success: #5db872;
--state-warning: #d4a017;
--state-danger: #c64545;
--state-danger-strong: #a83838;
--status-offline: #c64545;
--state-info: #5db8a6;
/* Highlight / 交互态 —— 中性暖灰(不带橙) */
--highlight: rgba(20, 20, 19, 0.05);
--tab-active: rgba(20, 20, 19, 0.07);
--chip-bg: #f5f0e8;
--badge-bg: #efe9de;
--switch-track: #d8cfc4;
--hover-bg: rgba(20, 20, 19, 0.05);
--pulse-ring: rgba(0, 0, 0, 0.1);
/* Shadow 阴影 */
--shadow-card: 0 14px 36px rgba(61, 57, 41, 0.12);
--shadow-soft: 0 12px 24px rgba(38, 28, 18, 0.12);
--shadow-mid: 0 20px 45px rgba(16, 24, 40, 0.08);
--shadow-strong: 0 28px 60px rgba(38, 28, 18, 0.25);
--shadow-color: rgba(0, 0, 0, 0.1);
/* Overlay / 移动端 */
--overlay-scrim: rgba(33, 24, 14, 0.55);
--mobile-menu: #f5f0e8;
--mobile-menu-shadow: 0 12px 30px rgba(38, 28, 18, 0.15);
/* 专用色 */
--diff-add-text: #00e000;
--diff-del-text: #ff1a1a;
--diff-add-bg: rgba(22, 163, 74, 0.12);
--diff-del-bg: rgba(220, 38, 38, 0.12);
}
:root[data-theme='light'] {
color-scheme: light;
/* Surface 表面层级 —— ChatGPT 亮色冷白盘 */
--surface-base: #ffffff;
--surface-panel: #ffffff;
--surface-rail: #f9f9f9;
--surface-sidebar: #f9f9f9;
--surface-card: #ffffff;
--surface-raised: #ffffff;
--surface-soft: #f9f9f9;
--surface-muted: #f3f3f3;
/* Text 文字 —— ChatGPT */
--text-primary: #0d0d0d;
--text-secondary: #5d5d5d;
--text-tertiary: #8f8f8f;
--text-muted: rgba(13, 13, 13, 0.35);
/* Border 边框 —— ChatGPT hairline */
--border-default: rgba(13, 13, 13, 0.1);
--border-strong: rgba(13, 13, 13, 0.15);
--border-card-strong: rgba(13, 13, 13, 0.1);
--chip-border: rgba(13, 13, 13, 0.05);
/* Accent 强调 —— ChatGPT primary近黑克制 CTA-only */
--accent: #181818;
--accent-strong: #181818;
--accent-hover: #303030;
--accent-active: #414141;
--accent-deep: #5d5d5d;
--accent-deep-strong: #414141;
--on-accent: #ffffff;
/* State 状态 —— ChatGPT semantic */
--state-success: #00a240;
--state-warning: #e25507;
--state-danger: #e02e2a;
--state-danger-strong: #911e1b;
--status-offline: #e02e2a;
--state-info: #0285ff;
/* Highlight / 交互态 —— 中性冷灰 */
--highlight: rgba(13, 13, 13, 0.05);
--tab-active: rgba(13, 13, 13, 0.05);
--chip-bg: #f3f3f3;
--badge-bg: #f3f3f3;
--switch-track: #ececec;
--hover-bg: rgba(13, 13, 13, 0.05);
--pulse-ring: rgba(0, 0, 0, 0.1);
/* Shadow 阴影 */
--shadow-card: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-soft: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-mid: 0 1px 3px rgba(0, 0, 0, 0.06);
--shadow-strong: 0 2px 4px rgba(0, 0, 0, 0.08);
--shadow-color: rgba(0, 0, 0, 0.1);
/* Overlay / 移动端 */
--overlay-scrim: rgba(0, 0, 0, 0.5);
--mobile-menu: #ffffff;
--mobile-menu-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
/* 专用色 */
--diff-add-text: #00e000;
--diff-del-text: #ff1a1a;
--diff-add-bg: rgba(22, 163, 74, 0.12);
--diff-del-bg: rgba(220, 38, 38, 0.12);
}
:root[data-theme='dark'] {
color-scheme: dark;
/* Surface 表面层级 */
--surface-base: #1a1a1a;
--surface-panel: #1a1a1a;
--surface-rail: #181818;
--surface-sidebar: #181818;
--surface-card: #0a0a0a;
--surface-raised: #1a1a1a;
--surface-soft: #0f0f0f;
--surface-muted: #141414;
/* Text 文字 */
--text-primary: #ffffff;
--text-secondary: #a0a0a0;
--text-tertiary: #707070;
--text-muted: rgba(255, 255, 255, 0.3);
/* Border 边框 */
--border-default: rgba(255, 255, 255, 0.08);
--border-strong: rgba(255, 255, 255, 0.12);
--border-card-strong: rgba(255, 255, 255, 0.1);
--chip-border: rgba(255, 255, 255, 0.08);
/* Accent 强调(含运行模式 deep */
--accent: #606060;
--accent-strong: #505050;
--accent-hover: #707070;
--accent-active: #505050;
--accent-deep: #808080;
--accent-deep-strong: #606060;
--on-accent: #ffffff;
/* State 状态 */
--state-success: #10b981;
--state-warning: #f59e0b;
--state-danger: #dc2626;
--state-danger-strong: #b91c1c;
--status-offline: #e34d4d;
--state-info: #60a5fa;
/* Highlight / 交互态 */
--highlight: rgba(255, 255, 255, 0.05);
--tab-active: rgba(255, 255, 255, 0.05);
--chip-bg: #2a2a2a;
--badge-bg: #2a2a2a;
--switch-track: #2a2a2a;
--hover-bg: rgba(255, 255, 255, 0.06);
--pulse-ring: rgba(255, 255, 255, 0.1);
/* Shadow 阴影 */
--shadow-card: 0 2px 8px rgba(0, 0, 0, 0.5);
--shadow-soft: 0 2px 6px rgba(0, 0, 0, 0.4);
--shadow-mid: 0 3px 8px rgba(0, 0, 0, 0.5);
--shadow-strong: 0 4px 12px rgba(0, 0, 0, 0.6);
--shadow-color: rgba(0, 0, 0, 0.4);
/* Overlay / 移动端 */
--overlay-scrim: rgba(0, 0, 0, 0.8);
--mobile-menu: rgba(10, 10, 10, 0.95);
--mobile-menu-shadow: 0 4px 12px rgba(0, 0, 0, 0.6);
/* 专用色 */
--diff-add-text: #00e000;
--diff-del-text: #ff1a1a;
--diff-add-bg: rgba(22, 163, 74, 0.12);
--diff-del-bg: rgba(220, 38, 38, 0.12);
}
/* 首屏JS 设置 data-theme 之前)回退到 classic 取值 */
:root:not([data-theme]) {
/* Surface 表面层级 —— 同 classicClaude 亮色暖奶油盘) */
--surface-base: #faf9f5;
--surface-panel: #faf9f5;
--surface-rail: #f5f0e8;
--surface-sidebar: #f5f0e8;
--surface-card: #efe9de;
--surface-raised: #ffffff;
--surface-soft: #f5f0e8;
--surface-muted: #e8e0d2;
/* Text 文字 */
--text-primary: #141413;
--text-secondary: #3d3d3a;
--text-tertiary: #6c6a64;
--text-muted: rgba(108, 106, 100, 0.55);
/* Border 边框 */
--border-default: #e6dfd8;
--border-strong: #d8cfc4;
--border-card-strong: #e6dfd8;
--chip-border: #ebe6df;
/* Accent 强调 */
--accent: #cc785c;
--accent-strong: #a9583e;
--accent-hover: #bd6a4f;
--accent-active: #a9583e;
--accent-deep: #6c6a64;
--accent-deep-strong: #3d3d3a;
--on-accent: #ffffff;
/* State 状态 */
--state-success: #5db872;
--state-warning: #d4a017;
--state-danger: #c64545;
--state-danger-strong: #a83838;
--status-offline: #c64545;
--state-info: #5db8a6;
/* Highlight / 交互态 —— 中性暖灰 */
--highlight: rgba(20, 20, 19, 0.05);
--tab-active: rgba(20, 20, 19, 0.07);
--chip-bg: #f5f0e8;
--badge-bg: #efe9de;
--switch-track: #d8cfc4;
--hover-bg: rgba(20, 20, 19, 0.05);
--pulse-ring: rgba(0, 0, 0, 0.1);
/* Shadow 阴影 */
--shadow-card: 0 14px 36px rgba(61, 57, 41, 0.12);
--shadow-soft: 0 12px 24px rgba(38, 28, 18, 0.12);
--shadow-mid: 0 20px 45px rgba(16, 24, 40, 0.08);
--shadow-strong: 0 28px 60px rgba(38, 28, 18, 0.25);
--shadow-color: rgba(0, 0, 0, 0.1);
/* Overlay / 移动端 */
--overlay-scrim: rgba(33, 24, 14, 0.55);
--mobile-menu: #f5f0e8;
--mobile-menu-shadow: 0 12px 30px rgba(38, 28, 18, 0.15);
/* 专用色 */
--diff-add-text: #00e000;
--diff-del-text: #ff1a1a;
--diff-add-bg: rgba(22, 163, 74, 0.12);
--diff-del-bg: rgba(220, 38, 38, 0.12);
}