dsh-plugin-backdrop — DeepSeek Harness 动态背景插件
把 DeepSeek Harness 官网 hero 的完整氛围(流体 + 平滑鲸鱼 + 发光鱼群 + 点线网格)
注入 dsh Web UI 作为动态背景。当前实现为混合渲染:
- 流体背景沿用 WebGL2(原官网 GLSL 逆向)
- 鲸鱼 / 鱼群使用 Canvas 2D 预渲染精灵,全分辨率平滑渲染
- 点线网格沿用 Canvas 2D
完整技术原理见 docs/ARCHITECTURE.md;
新版 Canvas 渲染说明见 docs/CANVAS_RENDERING.md。
目录
效果一览
截图:docs/screenshot.png(效果预览,可用浏览器 F12 对 3090 实例截图后放入)。
- 流体背景:WebGL2 flowmap 双缓冲 + fbm/curl-noise 域扭曲,5 层深蓝-暖金渐变,
自发光 bloom + 虚拟光源(跟随鼠标 X)+ 颗粒噪点 + 暗角
- 路径复刻鲸鱼:从你给的 MP4 中提取 25 个关键帧轮廓,转成 SVG/Path2D 路径;
用代码插值播放,完整还原视频里的游动/尾巴动画;
鲸鱼在整个界面里弧线漫游、边界转向、转弯侧倾、上浮下潜
- 发光鱼群:55 条预渲染平滑鱼形精灵(4 配色 × 6 帧尾摆),
鱼群中心弧线漫游,鱼在群内绕圈跟随;带转弯侧倾、呼吸缩放、深度透明、气泡
- 点线网格:90px 点阵 + 邻点连线 + 鼠标波纹推开(canvas 2D)
- UI 融入:dsh 页面级容器透明化(已知类名 + 全视口扫描 fallback),
强制 dsh 官方暗色主题保证文字可读性(实测对比度 9~19:1)
- 性能:四层全部 30fps 节流 + 视口外停帧 + 静止自停 + dpr 上限 1.5
安装启用
先理解插件解析机制(盲测发现新用户卡在这):
cordis.patch.yml / --patch 里的 name: 'dsh-plugin-backdrop' 会先尝试从 dsh 安装
解析,再从 profile 的 node_modules(~/.dsh/profiles/web/node_modules)解析。
所以本地测试必须先把包放进该目录(下面第 1 步),否则启动会报
plugin not found。
方式 A:本地源码开发(推荐,改代码立即生效)
前提:仓库源码在你机器上,例如 C:\Users\www13\Documents\AAA项目集\dsh-plugins\dsh-plugin-backdrop。
- 链接到 profile(junction,指向源码目录;改源码 build 后无需重装):
New-Item -ItemType Junction -Path "$env:USERPROFILE\.dsh\profiles\web\node_modules\dsh-plugin-backdrop" -Target "C:\Users\www13\Documents\AAA项目集\dsh-plugins\dsh-plugin-backdrop"
- 挂载到配置:编辑
~/.dsh/profiles/web/cordis.patch.yml:
- insert:
- id: backdrop
name: 'dsh-plugin-backdrop'
- 重启
dsh web(重启会断开当前会话,挑空闲时操作),刷新页面。
方式 B:发布安装(插件已发 npm 时)
cd ~/.dsh/profiles/web
pnpm add dsh-plugin-backdrop
# 然后同样在 cordis.patch.yml 加 insert 片段,重启
快速预览(不动正式实例)
在插件已按方式 A 链接好的前提下:
# 1. 建 overlay 文件(放哪都行,建议 profile 目录,命令里用绝对路径)
# C:\Users\www13\.dsh\profiles\web\overlay-backdrop.yml 内容:
# - insert:
# - id: backdrop
# name: 'dsh-plugin-backdrop'
# 2. 起预览实例
dsh --patch "C:\Users\www13\.dsh\profiles\web\overlay-backdrop.yml" --profile web --port 3090
# 3. 浏览器打开 http://127.0.0.1:3090/
注意:如果 cordis.patch.yml 已经包含 backdrop,不要再加 --patch,
否则报 duplicate loader entry id: backdrop。此时直接 dsh --profile web --port 3090 即可。
配置
配置存 localStorage['dsh-backdrop-config'](JSON)。首次加载用默认值;
通过 window.__backdrop.setConfig() 修改会自动持久化。
{
"enabled": true,
"opacity": 1,
"layers": { "fluid": true, "whale": true, "fish": true, "grid": true },
"fluid": {
"glowIntensity": 0.07, "lightCore": 0.1, "lightHalo": 0.12,
"vignette": 0.42, "interactive": false
},
"whale": {
"density": 60, "spin": false, "loose": 1,
"swim": true, "swimSpeed": 1.35, "swimTurn": 0.6,
"light": { "x": 4.5, "y": 5.5, "z": 3, "range": 14, "shadeMin": 0.2, "shadeMax": 1.35, "followX": 1.05 },
"mouse": { "radius": 4.9, "strength": 0.8, "decay": 0.2, "distort": 5 },
"offsetRight": 0
},
"fish": {
"count": 55, "density": 40, "scaleMin": 0.22, "scaleMax": 0.5,
"speedBase": 2.6, "speedVary": 1.3,
"schoolSpeed": 1.25, "schoolRadius": 6.0, "opacity": 0.95,
"light": { "x": 4.5, "y": 5.5, "z": 3, "range": 14, "shadeMin": 0.3, "shadeMax": 1.5, "followX": 1.05 },
"color": { "r": 0.62, "g": 0.82, "b": 1.0 }
},
"grid": { "lineOpacity": 0.06, "dotOpacity": 0.12 },
"themeMode": "force-dark"
}
字段说明
layers.* | 四层独立开关 | whale 与 fish 可同时开 |
opacity | 背景层整体透明度 | 0~1,聊天时调低可降存在感 |
fluid.interactive | 流体鼠标笔刷(flowmap) | 聊天场景必须 false,否则"一团光斑跟手" |
fluid.glowIntensity | 鼠标光晕强度 | 0 关闭 |
whale.swim | 是否让鲸鱼自由漫游 | false 时鲸鱼停在初始位置 |
whale.swimSpeed | 鲸鱼漫游速度系数 | 默认 1.35,越大游得越快 |
whale.swimTurn | 鲸鱼转弯/漫游幅度 | 默认 0.6 |
whale.density/light/mouse/offsetRight | 旧版 WebGL 瓦片参数 | 新版 Canvas 渲染已不使用,保留仅为兼容旧配置 |
fish.count | 鱼数量 | 默认 55 条 |
fish.scaleMin/Max | 鱼的大小比例 | 相对视口尺寸,默认 0.22~0.5 |
fish.speedBase/Vary | 游动速度系数 | 默认 2.6 / 1.3 |
fish.schoolSpeed | 鱼群中心漫游速度 | 默认 1.25 |
fish.schoolRadius | 鱼群散布半径 | 默认 6.0,越大鱼群越散 |
fish.opacity | 鱼群整体透明度 | 默认 0.95 |
fish.density/light | 旧版瓦片参数 | 新版 Canvas 渲染已不使用,保留仅为兼容旧配置 |
themeMode | force-dark 强制暗色 / follow 跟随用户主题 | force-dark 才能保证可读性 |
最小配置示例
只要鲸鱼、其他全关:
__backdrop.setConfig({
layers: { fluid: false, whale: true, fish: false, grid: false },
opacity: 0.8,
});
只要鱼群、鲸鱼也留:
__backdrop.setConfig({
layers: { fluid: false, whale: true, fish: true, grid: false },
});
调试 API
浏览器 F12 控制台:
window.__backdrop.toggleLayer('whale', false)
window.__backdrop.toggleLayer('fish', true)
window.__backdrop.setOpacity(0.6)
window.__backdrop.setConfig({
whale: { density: 80 },
fish: { count: 30, scaleMax: 0.8 },
fluid: { interactive: true },
});
window.__backdrop.setConfig({ whale: { src: 'http://host/cat.svg' } });
window.__backdrop.config
window.__backdrop.dispose()
whale.src 接受 Image.src 支持的格式:URL、data URI(如 data:image/svg+xml;utf8,...)。
换 src 会自动重新采样并重建瓦片,无需刷新。
工作原理
┌─ body
│ ├─ #root → dsh UI(容器透明化后透出背景)
│ └─ .backdrop-root(z-index:0,pointer-events:none) ← 插件注入
│ ├─ canvas#fluid 流体背景(WebGL2,mask 底部渐隐)
│ ├─ canvas#whale 瓦片鲸鱼(Three.js,screen 混合,右移)
│ ├─ canvas#fish 瓦片鱼群(Three.js,screen 混合,全屏)
│ └─ canvas#grid 点线网格(canvas 2D,mask 渐隐)
- 注入:
client-source.js 在 apply() 时向 body 插入背景容器 + 覆盖 CSS,不依赖 React
- 透明化:硬编码 rc.6 类名(
.pI_x6G_frame 等)+ transparentizeFullscreenSolids()
全视口扫描兜底(覆盖 ≥90% 视口且实底的容器置透明)
- 主题:默认给
<body> 挂 data-ds-dark-theme(dsh 官方暗色属性),
组件级配色整体切暗、文字变白,可读性由 dsh 自己的样式保证;
MutationObserver 在用户手动切主题时把属性挂回去
- 可读性实测:新会话按钮 9.18:1 / 侧边栏 13.14:1 / 输入框 18.95:1(WCAG ≥4.5 达标)
详见 docs/ARCHITECTURE.md。
常见问题 FAQ
Q: 字看不清 / 界面还是亮的?
A: 确认 themeMode: "force-dark"(默认)。旧版本(强制文字 token 方案)会导致白字浅底,
已废弃。改配置后 location.reload()。
Q: 鼠标拖着"一大坨光斑"跑?
A: fluid.interactive 被设成了 true。设为 false(默认)并刷新。
官网在 Windows 上本来就默认关闭此交互。
Q: 鱼不像鱼 / 鱼在甩头?
A: 确认 fish.density ≥ 30。旧版尾摆方向与鱼形相反(鱼头朝右但 shader 按尾巴在 +x 摆动),
已修复为 smoothstep(-4.5, -0.5, x)。还觉得丑就把 FISH_SVG 换成自己画的剪影(src/engine/fish-school.js)。
Q: 想换成别的形状(logo、猫、狗)?
A: 运行时换(不刷新):
__backdrop.setConfig({ whale: { src: 'https://example.com/cat.svg' } })
__backdrop.setConfig({ whale: { src: 'data:image/svg+xml;utf8,' + encodeURIComponent('<svg>…</svg>') } })
或改 src/hero-whale.svg 后 build。剪影要求:纯白填充、轮廓饱满(瓦片化会抹平细枝末节),viewBox 比例 2:1 左右。
Q: 升级 dsh 后背景不显示了 / 容器又变白?
A: dsh 的容器类名是 CSS Modules hash,升级可能变化。fallback 扫描会自动透明化全视口实底容器;
若失效,把新类名补进 client-source.js 的 CSS 覆盖列表。
Q: 想完全还原回默认界面?
A: localStorage.removeItem('dsh-backdrop-config'); location.reload(),
再在 cordis.patch.yml 里删掉 backdrop 条目并重启。
开发指南
# 改代码后重新构建(产出 lib/index.js + lib/client.js)
node build.mjs
# 重启预览实例(两种情形二选一):
# · cordis.patch.yml 已含 backdrop → 直接起
dsh --profile web --port 3090
# · patch 未写入正式配置 → 用 overlay(见"安装启用 → 快速预览")
dsh --patch "C:\Users\www13\.dsh\profiles\web\overlay-backdrop.yml" --profile web --port 3090
# headless 验证注入状态(backdrop-root/canvas 数/容器透明/主题)
node verify-inject.mjs "http://127.0.0.1:3090/" out.png 25000
本地开发要点
- 引擎在
src/engine/,全部 ESM、零框架依赖;client-source.js 负责注入与配置
- client bundle 会把 three.js + 四引擎 + GLSL + 鲸鱼 SVG(dataurl) 全部内联(~780KB),
改引擎后必须
node build.mjs 再重启
- 想加第五层:写引擎 →
client-source.js 里创建 canvas + 启动 + 加进 toggleLayer map → build
踩坑记录
setConfig 覆盖引用 bug:Object.assign(cfg, patch) 会把 patch.light 整体覆盖
cfg.light 引用,导致 uLightPos = (NaN, undefined, z),鲸鱼变全透明但"看起来没渲染"。
必须逐字段合并(见 whale-engine.js 的 setConfig)。
- three.min.js(UMD)无法被 esbuild 打包导出:
import * as THREE 拿到空对象。
改用 npm three@0.150.1(纯 ESM)。
- 尾摆方向:官网 shader 假设尾巴在 +x;鱼形头朝右时尾摆反了(鱼甩头)。
fish 引擎里把 tail 因子替换为
smoothstep(-4.5, -0.5, x)。
- light 模式可读性:强制文字 token 变白但组件背景仍是浅色 → 白字浅底看不清。
正确做法是挂
data-ds-dark-theme 让 dsh 自己的暗色样式接管。
- headless 验证:SwiftShader 下
readPixels 极慢且 present 后缓冲不可读;
用 CDP 截图 + DOM 计算(背景色/对比度)验证,别读像素。
- Windows 中文路径:
new URL(import.meta.url).pathname 是 percent-encoded,
静态服务器需 decodeURIComponent,否则 404。
- PowerShell 内联后台任务:
Start-Job 随调用进程退出被杀死;长驻进程
必须用工具的后台任务机制。
- 重复挂载报错
duplicate loader entry id: backdrop:cordis.patch.yml 和
--patch overlay 同时挂同一插件导致;二选一。
文件结构
dsh-plugin-backdrop/
├── package.json # dsh.bundle.patch + dsh.client 声明
├── cordis.patch.yml # 插件挂载片段
├── build.mjs # esbuild 打包(host ESM + client CJS 包装)
├── README.md # 本文档
├── docs/
│ ├── ARCHITECTURE.md # 架构与技术原理(含旧版瓦片逆向记录)
│ └── CANVAS_RENDERING.md # 新版 Canvas 鲸鱼/鱼群渲染说明
├── verify-inject.mjs # headless 验证工具(注入状态 + 截图)
├── probe-*.mjs # 调试探针(对比度/鱼群/交互开关)
└── src/
├── index.js # host 端(挂载点,预留配置服务)
├── client-source.js # client 端:DOM 注入 + 透明化 + 主题联动 + 配置
├── hero-whale.svg # 官网原版鲸鱼剪影
└── engine/
├── whale-canvas.js # 新版 Canvas 路径复刻鲸鱼(视频轮廓 Path2D 插值)
├── whale-video-paths.js # 从视频提取的鲸鱼关键帧轮廓数据
├── fish-canvas.js # 新版 Canvas 发光鱼群(预渲染精灵)
├── fluid-background.js # 流体背景(WebGL2 flowmap 双缓冲)
├── grid-background.js # 点线网格(canvas 2D)
├── fluid-shaders.js # 流体 GLSL(官网 page chunk 原样提取)
├── whale-engine.js # 旧版瓦片鲸鱼(保留,未使用)
├── fish-school.js # 旧版瓦片鱼群(保留,未使用)
└── shaders.js # 旧版瓦片 shader(保留,未使用)
已知限制
- UI 容器类名是 CSS Modules hash(rc.6 硬编码),dsh 升级后可能失效;
fallback 扫描兜底,极端情况下可能误伤全屏弹层(有白名单可调)
force-dark 会覆盖用户手动选择的亮色主题(可在设置里改 themeMode: "follow",但亮色下背景融入效果差)
- 鱼群共享几何导致每条鱼内部瓦片抖动相位一致(mesh 级尾摆仍有差异),
追求个体差异可改为每鱼独立几何
- 官网原版在 Windows 上默认关鼠标扰动;本插件流体笔刷默认关闭,鲸鱼/网格交互保留
逆向来源
- deepseek.com/harness → chunk
776.7b3219fa93f8a656.js(HeroDigitileR3F 瓦片鲸鱼)
- deepseek.com/harness → page chunk
u 组件(WebGL2 流体)、m 组件(点线网格)
- 完整逆向过程与独立 demo:见同仓库
whale-demo/(浏览器直接打开可玩)