dsh-bloom-theme
界面与体验webkubor/dsh-bloom-theme
DeepSeek Harness (DSH) 主题插件:Bloom 莫兰迪配色 4 变体,OKLCH 调色,明暗双主题,顶栏一键切换,全部达 WCAG AA
- accessibility
- color-scheme
- dark-theme
- deepseek
- deepseek-harness
- dsh
- dsh-plugin
- morandi
- oklch
- theme
- ui-theme
- web-ui
README
Bloom 是什么
Bloom 原是一套 Typora 主题,核心不在「换个颜色」,而在一整套莫兰迪质感语言: 低饱和的氛围渐变、冷调的发光细线、长距柔和的投影、克制的圆角与间距。
这个插件把那套语言完整移植到 DSH——包括它最容易被忽略的一半:
莫兰迪的气质不在
--accent,在--accent-rgb。
详见 双轨色。
为什么用 Bloom
| 特性 | 说明 |
|---|---|
| 双轨配色 | 可读轨保对比度,气质轨专供氛围渐变,两轨分工不混用 |
| 质感层完整 | 氛围渐变、冷光线条、纸感投影、Markdown 排版装饰,而非仅替换色值 |
| OKLCH 调色 | 感知均匀的色彩空间,明暗切换不跳变 |
| WCAG AA | 8 个「主色 + 底色」组合全部实测 ≥ 4.5:1 |
| 零依赖 | 纯客户端注入,不引入任何运行时依赖 |
| 不抢占原生控件 | 切换器挂进 DSH 顶栏工具区,与原生按钮并排共存 |
| 主题色推理动效 | Deep diving… 以当前主题的三色光谱流动,不再固定 DeepSeek 蓝;减少动态效果时自动静止 |
| 氛围层(v0.4,默认关) | 同色系壁纸 + 磨砂玻璃一键开启;关闭后零残留,回到纯 Bloom |
| 主题包(v0.4) | 变体 + 氛围配置导出 JSON 分享,导入白名单校验 |
主题一览
4 套配色,每套都有浅色与深色两个版本。
顶栏下拉一键切换,选择记在 localStorage:
推理中的等待,也属于主题
Deep diving… 不再是固定的 DeepSeek 蓝色 shimmer。Bloom 为每个变体准备了以主色为锚的
三色光谱,并在文字上缓慢流动:雾蓝是蓝 → 青 → 紫蓝,朱砂是朱砂 → 琥珀 → 玫红,
花瓣是粉 → 紫 → 珊瑚,涟漪是青 → 湛蓝 → 薄荷。切换主题时动效同步切换;
prefers-reduced-motion 下自动停为静态渐变。
v0.4 起可选开启氛围层——壁纸与磨砂玻璃随变体联动(默认关闭,克制审美不妥协):
快速安装
dsh plugin --profile web add @kubor/dsh-bloom-theme
然后把包名加进 ~/.dsh/profiles/web/package.json 的 bundles:
{
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
"@kubor/dsh-bloom-theme"
]
}
}
}
重启 DSH,顶栏出现主题下拉即生效。
DSH Desktop 使用相同的包与 boot graph:把上面命令和路径中的 web 换成 desktop,
然后退出并重新打开原生 Desktop App。
插件自带 cordis.patch.yml 并通过 dsh.bundle 声明,列进 bundles 后会自动 insert
进 boot graph,不需要手动编辑 cordis.patch.yml。
如果不想改 bundles,在 ~/.dsh/profiles/web/cordis.patch.yml 里手动 insert 同样可行:
- insert:
- id: bloom-theme
name: '@kubor/dsh-bloom-theme'
从源码装:dsh plugin --profile web add github:webkubor/dsh-bloom-theme
配置
Bloom 不读取环境变量,也不把偏好发到服务端;所有设置都保存在当前浏览器的 localStorage:
| 设置 | 键 | 默认值 | 说明 |
|---|---|---|---|
| 主题变体 | dsh-bloom-variant | mist | 顶栏下拉选择;可选 mist、cinnabar、petal、ripple |
| 氛围层 | dsh-bloom-ambience | 关闭 | 壁纸、玻璃、压暗度、模糊度与自定义壁纸 URL;可通过主题包导入/导出 |
明暗模式仍由 DSH 自身的「设置 → 外观」控制;Bloom 会为当前变体自动应用对应的亮色或暗色 token。
设计原理:双轨色
原版 Bloom 每个变体都有两套色,root-mist.css 的注释写得很直白:
/* --- Morandi Mist (Blue) - Deepened for better contrast --- */
--accent: oklch(50% 0.08 240); /* 可读轨:被刻意加深过,为过对比度 */
--accent-rgb: 146, 168, 179; /* 气质轨:真正的莫兰迪色,发灰、低饱和 */
两轨分工不能混:
- 可读轨 → 文字、按钮填充、边框描边。它是加深版,直接拿来铺大面积会显得艳、脏。
- 气质轨 → 只用于
rgba(morandi, 0.05~0.2)的大面积氛围渐变与冷光。原版 14 处 gradient 全部用它,从不用可读轨铺面。
移植时若只搬 --accent(一个很自然的想当然),petal 会从藕粉 #e8859b 变成
荧光洋红 #e63f9f——色是对的,莫兰迪感没了。
| 变体 | 气质轨 | 可读轨(浅 / 深) | 浅色对比度 |
|---|---|---|---|
mist | #92a8b3 | oklch(50%) / oklch(72%) | 5.28:1 |
cinnabar | #d74b4b | oklch(55%) / oklch(72%) | 4.87:1 |
petal | #e8859b | oklch(58%) / oklch(75%) | 4.55:1 |
ripple | #5fa8b2 | oklch(51%) / oklch(75%) | 4.61:1 |
浅色可读轨的 L 值按 WCAG AA 反推校准过——压暗之后反而更贴莫兰迪,
这正是原作者对 mist 做过的事。
质感层
只搬色板得到的是「换了色的原界面」。原版 root-*.css(色板)89 行,
base-light/dark.css(质感)2968 行——差距全在这里。
| 手法 | 实现 |
|---|---|
| 氛围渐变 | body 四层莫兰迪光晕叠加,background-attachment: fixed |
| 冷光线条 | 侧栏竖线、卡片描边、tabs 下沿、顶部内高光 |
| 纸感 | 长距柔影三档 + inset 0 1px 0 内高光 |
| Markdown | 标题渐变短横、hr 两端消隐、引用块主色条、代码块冷光描边 |
| 侧栏 | 顶部氛围淡染、会话项冷光态、选中态主色标记 |
氛围层(v0.4,可选)
Bloom 的默认审美是克制的——氛围层因此默认关闭,在顶栏下拉的「氛围」区一键开启:
- 配套壁纸:4 套与变体同色系的莫兰迪壁纸(mist 雾蓝 / cinnabar 陶土 / petal 藕粉 / ripple 雾青),
默认随变体自动切换;也可固定某套,或填自定义 URL(
http(s)/data:,完全离线可行)。 - 压暗滑杆(0–70%):在壁纸上盖一层纱,正文可读性优先——这是对比度护栏在氛围层的延伸。
- 磨砂玻璃:侧栏、气泡、输入卡、菜单变为半透明 +
backdrop-filter模糊(4–32px 可调), 底色取当前变体的主题 token 混透明度,不是死白死黑。 - 主题包:全部配置(变体 + 氛围)导出为一个 JSON 文件,分享给同事一键导入; 导入走白名单合并,未知字段丢弃,坏文件安全报错。
四变体 × 明暗在氛围层下的一致表现:
设置面板就挂在顶栏下拉的下半部,开关、滑杆、主题包导入导出都在这一处:
关闭总开关后,壁纸层 DOM 不渲染、玻璃规则不命中——回到 v0.3 的纯 Bloom,零残留。
架构
lib/index.js node 半侧(cordis plugin),空实现 —— 本插件是纯客户端主题
lib/client.js 浏览器半侧,全部逻辑在这
├─ PALETTE 4 变体 × 双轨色板
├─ bloomTokens() → --bloom-* 自有 token(质感层的唯一色源 / SSOT)
├─ mistLight/Dark() → mist 完整接管 DSH 的 alias + specific 变量体系
├─ variantBlock() → 其余 3 变体只覆盖主色与背景调,灰阶骨架继承 mist
├─ COMPONENT_CSS → 质感层(一份 CSS,4 变体 × 明暗自动适配)
├─ AMBIENCE_CSS → 氛围层(壁纸 + 磨砂玻璃,body[data-bloom-*] 驱动,默认不生效)
├─ renderAmbience() → 氛围层状态 → DOM 的唯一写入口(SSOT)
└─ SWITCHER_CSS → 顶栏下拉切换器(下半部即「氛围」设置区)
开发
npm run dev # 监听 lib/ 自动部署到 web profile,保存即刷新浏览器
npm run deploy # 手动部署一次
皮肤在浏览器端注入,且 CSS 由 client.js 运行时生成——没有「只热更 CSS」这条路,
必须重新执行脚本,也就必须刷新页面。npm run dev 已代劳(按 a 可切换,
或设 DSH_BLOOM_NO_AUTORELOAD=1 关闭)。
桌面 profile:本地联调与生产切换
桌面 profile 有两种明确模式,避免发布版长期引用开发工作树:
npm run desktop:link # 本地联调:桌面 profile 直接链接当前仓库
npm run publish:desktop # 发布 npm 包后,自动切到刚发布的精确版本
也可以在已发布后单独执行 npm run desktop:production。该命令会先确认当前
package.json 的版本已存在于 npm;未发布则拒绝切换,避免桌面端退回旧版。
切换后重载桌面 DSH 页面或重启其进程即可生效。
常见问题
先确认包名已列进 ~/.dsh/profiles/web/package.json 的 dsh.profile.bundles,然后
重启 DSH 服务(不是刷新页面):
launchctl kickstart -k gui/$(id -u)/ai.deepseek.dsh # macOS LaunchAgent
boot graph 在进程启动时就已确定,仅刷新页面不会重新读取 profile 配置。
皮肤在浏览器端注入,且 CSS 由 client.js 在运行时生成——没有「只热更 CSS」这条路,
必须重新执行脚本,也就必须刷新页面(Cmd+R)。npm run dev 会在保存后自动刷新。
两种常见成因:
- 改过包名,但
~/.dsh/profiles/web/下仍有旧包名的残留。需要一并清理node_modules/<旧scope>/、cordis.patch.yml与node_modules/.package-map.json, 然后重启服务。 - client factory 返回了裸
{}。DSH 要求返回函数或带apply方法的对象。 该报错出现在浏览器端,与lib/index.js的 ESM 导出格式无关。
可以。切换器只是写 body[data-bloom-variant] 并存 localStorage,
你也可以直接在自己的 CSS 里固定该属性,或改 VARIANTS 只保留一项。
明暗跟随 DSH 自身的主题设置(设置 → 外观),本插件的四套配色在明暗下各有一版, 会自动适配,不需要单独切换。
已知限制
- 流式输出过程中
<think>标签会短暂可见。 输出进行时标签与内容处在同一个文本节点, 等输出结束、markdown 重新渲染拆成独立段落后才会被规则捕获。最终状态正确,只是过程中会闪现。 要根治需在 LLM provider 适配层把思考内容解析成 reasoning 字段,交给 DSH 原生的ReasoningRow渲染——那不属于主题的职责。 - 依赖 CSS Modules 的语义类名。 DSH 的类名形如
wSkVaW_root(<hash>_<语义名>), hash 会随 DSH 构建变化,语义名相对稳定,因此本插件用[class*="_语义名"]匹配。 DSH 改版导致失配时,效果会退回纯色——不会错位或不可用,属安全降级。 --dsw-alias-toast-bg/tooltip-bg未逐变体覆盖,所有配色沿用 mist 的蓝灰色相。 暗色下取值与背景差距偏小,尚未在真实 toast 上验证过对比度。- 适配 web 与 DSH Desktop profile。 tui / headless profile 不涉及浏览器渲染,本插件不生效。
踩过的坑
完整复盘见 DEV_NOTES.md,含每个坑的现象 → 根因 → 修法 → 教训。 几条最值得先读的:
- client factory 必须返回带
apply的对象,返回裸{}会让整个 DSH 启动白屏。 该报错出现在浏览器端,与lib/index.js的 ESM 导出格式无关。 - 前景色 token 不能当背景/阴影用。暗色的前景是近白,拿它
color-mix出的 「阴影」会是一团白雾;markdown-inline-code按文字色给值会得到 1.2:1 的浅底白字。 - DSH 用 CSS Modules,类名形如
wSkVaW_root。只能用[class*="_语义名"]匹配, 且必须限定div(否则命中 SVG)、必须数命中量(裸_card会命中 30+ 个消息卡)。 - 描边只能给有实色背景的那一层,加在内层透明元素上会形成「框中框」。
License
Author
@webkubor · 同系列:Bloom for Typora