> For the complete documentation index, see [llms.txt](https://tinyhumans.gitbook.io/openhuman/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tinyhumans.gitbook.io/openhuman/zh/kai-fa/theming.md).

# 主题化（令牌系统）

支撑每个主题的 CSS token 系统：token 如何定义、Tailwind 如何使用它们，以及如何编写遵循它的组件。

你可以在运行时为 OpenHuman 重新换肤。CSS 变量（“令牌”）驱动颜色和字体，因此主题就是这些变量的一组取值。本页面是令牌系统的贡献者参考。

## 工作原理

1. 令牌： `app/src/styles/tokens.css` 将每个可主题化颜色定义为以空格分隔的 RGB 通道三元组（例如 `--surface: 255 255 255;`），再加上字体角色变量（`--font-title/heading/body/mono/serif`）。浅色调色板位于 `:root` ，深色调色板位于 `:root.dark`.
2. 。Tailwind 连接方式：应用运行的是 Tailwind v4，因此没有 `tailwind.config.js`。 `@theme` 块在 `app/src/index.css` 将每个令牌暴露为实用颜色（`--color-surface: rgb(var(--surface));` 等等）。Tailwind 的透明度修饰符随后处理 `bg-surface/50` 和 `bg-primary-500/10` 而无需额外工作。之所以能这样做，只因为令牌是 RGB 三元组。Hex 字符串无法以这种方式与透明度修饰符组合。
3. 运行时应用： `app/src/providers/ThemeProvider.tsx` 解析当前激活的 `Theme` ，并将其覆盖值写入内联 `--token` 和 `--font-<role>` 变量到 `<html>`。它会切换 `.dark` 来自 `theme.isDark`。主题未覆盖的变量会回退到 `tokens.css` 默认值。切换主题时，前一个主题残留的变量会被移除。
4. 状态： `app/src/store/themeSlice.ts` 保存 `activeThemeId` 和 `customThemes`。内置预设位于 `app/src/lib/theme/presets.ts`。用户在 Settings > Theme Studio（`app/src/components/settings/panels/ThemeStudioPanel.tsx`).

## 令牌分类

| 分组  | 令牌                                                                                                                   | Tailwind 工具类                                      |
| --- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
| 表面  | `surface`, `surface-canvas`, `surface-muted`, `surface-subtle`, `surface-strong`, `surface-hover`, `surface-overlay` | `bg-surface`, `bg-surface-muted`, ...             |
| 文本  | `content`, `content-secondary`, `content-muted`, `content-faint`, `content-inverted`                                 | `text-content`, `text-content-muted`, ...         |
| 边框  | `line`, `line-strong`, `line-subtle`                                                                                 | `border-line`, `border-line-strong`, ...          |
| 强调色 | `primary-*`, `sage-*`, `amber-*`, `coral-*` （50 到 950 的色阶）                                                           | `bg-primary-500`, `text-coral-600`，……（由变量支持，可主题化） |
| 字体  | `font-title`, `font-heading`, `font-body`, `font-mono`, `font-serif`                                                 | `font-title`, `font-heading`, `font-body`, ...    |

旧版 `--cmd-*` 和 `--color-*` 变量集只是这些令牌的薄别名。不要在那里添加新颜色。

## 编写组件

* 对中性表面、文本和边框使用语义化工具类（`bg-surface`, `text-content`, `border-line`），而不是 `bg-white dark:bg-neutral-900`。你很少需要 `dark:` 变体，因为令牌会替你切换。
* 使用强调色调色板（`primary`, `sage`, `amber`, `coral`）来表示语义颜色。它们无需额外工作即可主题化。
* 避免在 `className` 或内联 `style`中硬编码 hex。那会绕过主题系统。

## 颜色即身份：四条渐变的上限

某些查找表会用颜色回答“这是什么东西？”。例如技能类别、事件日志域、通知提供方和目录来源。现成的 Tailwind 渐变会不断渗入这些表，因为一个有九行的表想要九种色相，而应用只提供四种。

恰好只有四条可主题化渐变： `primary`, `sage`, `amber` 和 `coral`。Tailwind 默认调色板中的其他所有颜色（`emerald`, `violet`, `sky`, `teal`, `indigo`, `cyan`, `rose`, `pink`, `purple` 以及更多）都会解析为固定的 oklch 值，不会理会当前主题。基于这些色相构建的表在默认外观下看起来不错，但在其他主题中就会崩掉。

### 规则

1. 将现成渐变映射为同一色阶上其可主题化的对应项： `red` 如果你要部署预发布环境，请将 `coral`, `green` 和 `emerald` 如果你要部署预发布环境，请将 `sage`, `orange` 如果你要部署预发布环境，请将 `amber`, `blue` 如果你要部署预发布环境，请将 `primary`. `bg-emerald-50 text-emerald-700` 变成 `bg-sage-50 text-sage-700`.
2. 没有对应项的色相就不要对应项。 `violet`, `teal`, `sky`, `cyan`, `indigo`, `pink` 和 `purple` 不是“几乎 primary”或“几乎 sage”。不要发明第五条渐变，不要以新名字复制现有渐变，也不要转而使用 `--accent-lavender` 之类的东西。那些是固定 hex，不是渐变。
3. 当一张表需要超过四种不同色相时，把多出来的行交给这张表已经定义好的中性对（`bg-surface-subtle text-content-secondary`，或者它的“未知”或“其他”行所使用的样式）。永远不要让两行共享同一条渐变。两个显示得一模一样的域会摧毁这张表本该展示的区分，这比把其中一个渲染为中性更糟。
4. 根据读者会依据哪种区分采取行动，决定哪些行保留色相。徽章通常会打印自己的标签，因此颜色只是辅助快速识别，而不是信息本身。把这四条渐变花在会改变人们行动的判断上，其余的就保持中性。保持语义真实： `coral` 读起来像失败，因此把普通行涂成 coral 会让常规状态看起来像坏掉了。留下一条渐变不分配是可以的。

树中的示例：

| 表                                                         | 行         | 保留了色相                                                                                 | 原因                                                                |
| --------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `skills/skillIcons.tsx` `CATEGORY_META`                   | 9         | `内置` （primary）、 `Productivity` （sage）、 `Social` （coral）、 `Tools & Automation` （amber） | `通道`, `聊天` 和 `平台` 共享 `All` / `Other`                              |
| `skills/SkillsExplorerTab.tsx` `SOURCE_COLORS`            | 6         | `内置` （sage）、 `可选` （primary）                                                           | 四个远程目录都会打印自己的名称；来源层级才是关键区别                                        |
| `skills/SkillsExplorerTab.tsx` `FORMAT_MAP`               | 5 行，3 种色调 | Hermes 系列（primary）、ClawHub 系列（sage）、 `旧版` （amber）                                     | 三种色调可以容纳在上限之内，所以不会丢失任何信息                                          |
| `settings/panels/EventLogPanel.tsx` `DOMAIN_BADGE_COLORS` | 11        | `工具` （primary）、 `agent` （sage）、 `approval` （amber）                                    | 谁执行了操作，以及什么在等待人工处理。coral 保持未分配，因为没有对应域就意味着失败                      |
| `notifications/NotificationCard.tsx` 提供方徽章                | 6         | 无                                                                                     | 同一行中的重要性徽章已经把 coral/amber/sage 用在高/中/低上；如果提供方也用 coral，会被读成一条失败的通知 |

### 品牌色调是另一个问题

有些卡片使用的是第三方的品牌色，而不是应用色相：Telegram 的 `#249CD8`、Discord 的 `#5865F2` 以及 iMessage 的 `#34C759` 在 `skills/skillIcons.tsx`。把它们压平为 `bg-surface-subtle` 会把它们抹成旁边那个通用徽章，所以它们有意保留为 hex。为它们提供可主题化归宿意味着要添加品牌令牌，这属于产品决策，而不是清理工作。通知卡片中的提供方徽章也是一样： `NotificationCard.tsx`；去找现成渐变并不是解决办法。

### 不要重绘原始组件的变体

`<Button variant="primary" className="bg-violet-500">` 是同一个问题。该变体已经绘制了强调色渐变，而覆盖会冻结颜色并破坏悬停、聚焦和禁用状态。删除覆盖，改为重新给按钮周围的表面着色。

## 迁移 codemod

`scripts/theme-codemod/` 把经过审计的浅色和 `dark:` Tailwind 配对折叠为语义化工具类。它是幂等的，并且默认以 dry-run 运行：

```bash
node scripts/theme-codemod/migrate.mjs            # dry-run + 报告
node scripts/theme-codemod/migrate.mjs --write    # 应用
node scripts/theme-codemod/migrate.mjs --selftest # 夹具断言
```

它只会重写相邻配对，绝不会触及带透明度后缀的工具类或测试文件。映射表在 `scripts/theme-codemod/map.mjs`.
