lh15626127422-cloud/dsh-cp2077-theme

dsh-cp2077-theme

Cyberpunk 2077 theme plugin for DeepSeek Harness — NIGHT CITY palette, chamfered HUD panels, CRT scanlines, cyberpunk cursors, live Wallpaper Engine backdrops, all tunable from a settings panel.

Uncategorizedcordis-plugincsscyberpunk-2077

赛博朋克 2077 主题(cp2077-theme)

给 DeepSeek Harness Web GUI 用的赛博朋克 2077 风格主题插件。English → README.en.md

夜之城配色:黑紫底 + 2077 黄(#FCEE0A)+ 霓虹青(#00E5FF)+ 品红;切角面板、HUD 字体、全高渐隐网格、CRT 扫描线、四角青色角标。

暗色 · 壁纸模式 · 浅色 · 光标与菜单 · ·

功能

  • 夜之城配色:黑紫底 + 2077 黄 #FCEE0A + 霓虹青 #00E5FF + 品红,覆盖 130+ 个主题 token,暗色与浅色各一套
  • 切角与 HUD:输入框、代码块、气泡、发送按钮统一切角;四角青色角标、全高渐隐网格、CRT 扫描线、低频扫描光束(都可在面板关掉)
  • 赛博朋克光标:箭头 / 指针 / 文本插入符三套 SVG,尺寸可调,浅色背景下带深色描边保证可见
  • Logo 动效:鲸鱼呼吸辉光 + wordmark 的像素级撕裂(SVG feTurbulence + feDisplacementMap,不是整块平移)
  • Wallpaper Engine 壁纸背景:自动识别 Steam 创意工坊库,一键把壁纸铺成整窗背景;视频类壁纸可生成动态 WebP 循环,另有透明度、缓存上限与清理
  • 控制面板:设置 → 赛博朋克主题,光标大小、网格/扫描线强度、切角尺寸、动画总闸、壁纸选择与缓存,全部即时生效并记忆
  • 不会拖垮核心:所有自愈与动画都有生命周期守卫,卸载后不复活;test/harness-test.mjs(19 项)与 test/wallpaper-route.mjs(17 项)覆盖异常输入、销毁顺序与路由

安装

需要已经装好的 DeepSeek Harness(Web GUI,0.2.x)。本项目零 npm 依赖。

git clone https://github.com/<you>/<repo>.git ~/dsh-plugins/cp2077-theme
cd ~/dsh-plugins/cp2077-theme
node scripts/install.mjs            # 链接进 ~/.dsh/profiles/web

脚本会把整个目录软链到 ~/.dsh/profiles/web/node_modules/@local/cp2077-theme,并打印需要写进 profile package.json 的那一行:

{
  "dependencies": {
    "@local/cp2077-theme": "link:<克隆下来的绝对路径>"
  }
}

然后重启 DeepSeek Harness,刷新页面;打开 设置 → 赛博朋克主题 即可调参数。

配置

唯一需要配置的是壁纸库位置,其余都能在面板里调。默认会自动探测 Steam(Windows 读注册表 HKCU\Software\Valve\Steam 再展开 libraryfolders.vdf;macOS / Linux 走惯例路径),探测不到时才需要手动指定:

cordis.patch.yml:

- insert:
    - id: cp2077-theme
      name: '@local/cp2077-theme'
      config:
        workshopRoot: 'D:/Steam/steamapps/workshop/content/431960'

也可以用环境变量 CP2077_WORKSHOP_ROOT 覆盖。优先级:插件配置 → 环境变量 → 自动探测。

依赖与限制

  • ffmpeg 可选:只用于「视频壁纸抽帧」与「动态 WebP 循环」。没有 ffmpeg 时视频壁纸显示封面图,开关会自动置灰并提示。
  • Wallpaper Engine 可选:没装也能用整个主题,只是壁纸功能为空,面板会显示「未检测到 Wallpaper Engine 创意工坊目录」。
  • 样式依赖 DSH 内部类名后缀(centerCol / sessionRow / data-menu-material / data-slot 等)。DSH 改版后个别选择器可能失效,后果是某块样式没打上,不会报错也不影响使用;data-* 属性选择器比哈希类名更稳定,所以优先用它们。

更新日志

2.0.0

可移植性:不再是「只在我这台机器上能用」。之前壁纸库路径写死成 G:/steam/...,别人的 Steam 装在 C 盘、或者根本没装 Wallpaper Engine,插件就只会返回空列表。

问题 · 原因 · 处理 换台机器就找不到壁纸 · DEFAULT_ROOT 硬编码了本机 G 盘路径 · 改为三级探测:插件配置 workshopRoot → 环境变量 CP2077_WORKSHOP_ROOT → 从 Steam 注册表 + libraryfolders.vdf 枚举所有库目录(Windows 走注册表,macOS/Linux 走惯例路径) 探测结果看不见,不知道是自己填错还是没装 · 面板只显示「共 N 张」 · /index 增加 rootSource(config / config-missing / env / env-missing / detected / fallback)与 ffmpeg;库目录不是自动探测来的就在面板里显示完整路径 没有 ffmpeg 的机器上「动态播放」开关形同虚设 · execFile("ffmpeg") 只在编码时才失败 · 启动时异步探测一次 ffmpeg -version,缺失时开关显示「动态播放 · 需要 ffmpeg」并置灰,视频壁纸自动退回封面 别人没法安装 · private: true + @local/ 私有 scope · 去掉 private、补 license/keywords;cordis.patch.yml 里写了 workshopRoot 的配置示例;README 补安装步骤

安装到另一台机器:

# 1) 把整个目录放到任意位置,例如 ~/dsh-plugins/cp2077-theme
# 2) 让它进 profile(软链,改文件立即生效)
ln -s ~/dsh-plugins/cp2077-theme ~/.dsh/profiles/web/node_modules/@local/cp2077-theme
# 3) 在 ~/.dsh/profiles/web/package.json 里加一行
#    "@local/cp2077-theme": "link:~/dsh-plugins/cp2077-theme"
# 4) 重启 DSH,刷新页面

壁纸库没有自动认出来时,在 cordis.patch.yml 里指名:

config:
  workshopRoot: 'D:/Steam/steamapps/workshop/content/431960'

依赖说明:无需任何 npm 依赖;ffmpeg 可选(只影响视频壁纸的抽帧与动态循环)。DSH 侧用到 webServer 服务与 theme / slots 两个 client 服务,0.2.x 可用。

1.9.5

问题 · 原因 · 处理 换过的壁纸在系统临时目录里越攒越多(实测已 60 MB,单个 21.6 MB) · cp2077-anim / cp2077-stills 只写不删,按用过的张数累积 · 加 200 MB 上限,写入后按最后使用时间淘汰最旧的文件(pruneCache) 不清楚占了多少、也没法清 · 缓存只存在于磁盘里,界面看不到 · 控制面板新增「壁纸缓存」行:已用容量 / 文件数 / 上限,带「清理」按钮(GET、POST /cp2077-wallpaper/cache) 一张 8K 源能压出 20 MB+ 的循环 · 编码规格固定,超大源片没有二次收缩 · 改成两级编码:先 1600px/12fps/q68,产物超过 8 MB 再用 1280px/10fps/q52 重编一遍;scale=min(1600,iw) 也不再放大小尺寸源

内存结论(实测同一渲染进程 A/B):插件本体约 14 MB,静态封面壁纸再加约 28 MB,动态壁纸再加约 40 MB;缓存上限只作用于磁盘,不改变运行内存。

1.9.4

稳定性收口:把「每帧都在重复做的写入」和「卸载后还留在队列里的任务」都收掉。

隐患 · 为什么会发生 · 修法 流式回答时 CPU 占用偏高 · self-heal 由 MutationObserver + rAF 驱动,写作过程中每秒约 60 次;它每次都会调 applyOptions(),而后者无条件重写四个 --cp-opt-* 变量并重建光标样式表(含 data URI),每次写入都触发整页样式重算 · applyOptions 改为按选项签名比对:值没变、节点还在就直接返回;光标样式表内容相同也不再赋值 壁纸变量被反复改写 · ensureWallpaper() 每次 heal 都重设 --cp-wallpaper-image 与 data-cp2077-wallpaper,而画布背景依赖这个变量,等于每帧让整页重新绘制 · 记住当前图源 key(a: 动态 / s: 静态 / off),同一张图只做「标记丢失才补」;body 重建换到新节点时才重画一次 插件卸载后仍有延迟任务在跑 · 220/700/1600/3200ms 的壁纸补画与洗白重试都是裸 setTimeout,卸载时没人清 · 统一走 later() 注册表;卸载时 cancelPending() 一次性 clearTimeout + 移除监听 DOM 监听散落注册 · pointerdown / resize / visibilitychange 各自 addEventListener,卸载后残留并把闭包钉住 · on() 注册表,卸载时一并 removeEventListener 页面还在预热时警告可能刷屏 · 每次 heal 失败都 console.warn · warnOnce(key, …) 按来源去重,每类只报一次

自愈语义不变:节点丢了照旧重建(光标表、装饰层、filter host、壁纸宿主节点),只是值没变时不再重复写。

1.9.3

快捷键提示里的键帽统一收平。

现象 · 原因 · 修法 菜单、对话框里的 Ctrl + Alt + N 是一串互相争抢的黄色方块 · DSH 把每个修饰键与 + 都渲染成独立 kbd,主题第 8 节给 kbd 的黄底黄框在这里变成并排色块 · 第 8 节改为全局收平:body kbd 无背景、无边框、无圆角、无阴影,只留等宽黄字 padding: 0 1px;浅色用 #7A5C00 保证浅底可读 1.9.2 里针对菜单的 kbd 覆盖 · 全局规则生效后它变成冗余 · 删除,减少选择器冲突

侧边栏「新建会话」右上角那张悬停卡里的键帽不在此列:它渲染在独立浮层文档里,主题样式表作用不到。已验证的手段包括给 html body * 全量染色、对 keycap / shortcut / chord / badge / pill 等十余个类名做探针、以及用 --dsw-alias-brand-primary 等 token 覆盖,全部无效,且该卡在整页刷新后依旧停在那里。

1.9.2

修掉壁纸模式带出的一串「表面材质」问题:弹出层半透明导致文字互穿,侧栏浮层还会残留成一张透明的鬼影。

现象 · 原因 · 修法 账号菜单、工作区菜单弹出后文字重叠 · 1.8.0 为了让壁纸透出来,把 --dsw-alias-bg-base 与 --dsw-specific-sidebar-fill 覆盖成半透明 rgba;1.8.1 又为治「设置界面变糊」把 --dsw-menu-backdrop-filter 设成 none。菜单原先靠 blur 磨砂遮住下层,blur 一撤、底色又半透,下层会话名与工作区名直接穿透上来 · 菜单表面强制实心 body [data-menu-material] → #12101A(浅色 #FFFFFF);同时把侧栏 token 钉成 alpha=1(内容列仍半透,照片只从正文区透出) 菜单快捷键被拆成五个争抢的黄块 · DSH 把 Ctrl、+、Alt、+、, 都渲染成独立 kbd,主题给 kbd 的黄底黄框在菜单里变成并排色块 · 菜单内的 kbd 收平为纯等宽黄字:无背景、无边框、无圆角、padding: 0 1px 页面中部浮着一块半透明的「侧栏幽灵」,跨刷新不消失 · 画布背景用了 background-attachment: fixed,WebView2 为全视口固定背景单独合成一层,再叠加带 backdrop-filter 的玻璃面板时留下过期瓦片 · 改回 scroll。聊天区本来就滚动在内部容器里,scroll 与 fixed 视觉完全一致,但不再触发独立合成层 玻璃面板失去磨砂 · 上一版把 --dsw-menu-backdrop-filter 整体设为 none,浮层少了 blur 就更透 · 恢复 blur(20px) saturate(140%);只有遮罩 --dsw-mask-blur 保持 none,避免设置页把壁纸糊掉

弹层本身是原生组件,这里只改表面材质,不动它的定位与开合逻辑。

1.9.1

壁纸模式再往「沉浸」推一档:内容区收成杂志版心,照片从顶部淡入。

  • 版心:壁纸模式下把 --dsh-chat-content-width 与 --dsh-composer-card-max-width 改成 min(calc(100% - 76px), 840px)。窗口宽时两侧各留出 190px 以上的照片带,窗口窄时至少保留 38px/边的呼吸位;输入框与正文同宽,不破坏对齐。
  • 顶部淡入:画布背景从 3 层变 4 层,最上面加一条 linear-gradient(180deg, rgba(10,9,16,.94) 0, rgba(10,9,16,.88) 54px, transparent 170px)(浅色对应米白)。标题栏因此有一条实心压底,照片在其下方 170px 内渐显,不再与标签栏硬碰。
  • 只在 html[data-cp2077-wallpaper="on"] 下生效,关掉壁纸即回到原本的全宽版式。

1.9.0

壁纸模式整体打磨,顺手换掉了整套「把照片画在哪里」的实现。

现象 · 原因 · 修法 壁纸只出现一会儿 / 完全不出现 · 照片画在 z-index:-1 的子节点上,能否透出来取决于有多少祖先各自刷了不透明底色、谁开了堆叠上下文,完全不可控 · 改画到文档画布:html 保持透明,照片与遮罩都放在 body 的 background-image 上(CSS 会把 body 背景传播到 canvas),永远在所有内容之下 壁纸模式下正文压在照片上、读不清 · 白字落在人物亮发、亮沙上,对比不足 · 壁纸模式给正文加双 text-shadow(可继承,正文/表格/列表一次覆盖)+ 中部径向光晕;不做整列不透明底板,边缘保留照片质感 装饰层和照片纹理打架 · 网格 / 扫描线 / 地面网格 / 角标 / 光束全亮度叠加在照片上 · 壁纸模式下统一降透明度:网格 .38 / 扫描线 .40 / 地面 .20 / 光束 .50 / 角标 .46 代码块、表格、输入框透出照片 · 这些面本该是实心的 · 壁纸模式强制实心:输入框 rgba(10,9,16,.88)、pre .84、表头 .72(浅色各有对应值) 面板缩略图第 4 列被裁掉、「动态播放」被挤出面板 · .cp2077-row-pick 是横向 flex(原本放控件+值),壁纸选择器把网格塞进去后变成并排 · :has() 命中后改成纵向铺满,子项 width:100%; min-width:0

结构变化:ensureWallpaper() 现在只负责写 --cp-wallpaper-image 与 data-cp2077-wallpaper,照片的绘制全部交给 CSS 第 29 节;旧的挂载节点保留但不再显示,仅用于老版本自清理。

1.8.4

动态壁纸改用 animated WebP:Wallpaper Engine 的 video 类壁纸不再只有静止封面。

现象 · 原因 · 修法 壁纸不会动 · 这套内嵌 WebView 从不合成 ` 图层(drawImage 取帧正常、画面却不出现),1.8.3 只能退回静态抽帧 · host 新增 /anim/,用 ffmpeg libwebp_anim 把原片编码成 1600×900 / 12fps / 8s 的动画 WebP 并缓存;client 对 video 类壁纸改用它。实测该 WebView 的 background-image 能正常播放动画 WebP 面板没有「动态播放」开关 · 1.7.9 移除 video 路径时一并删掉了 · 恢复开关,仅对 video 类条目显示;关掉则退回静态抽帧 /still/ 老用户开关恒为关 · 1.7.x 把 wallpaperMotion: false 存进了 localStorage · 加 wallpaperMotionV2` 迁移标记,读取到旧值时忽略一次并回写

编码参数:-ss 2 -t 8 -vf fps=12,scale=1600:-2:flags=lanczos -c:v libwebp_anim -lossless 0 -q:v 68 -loop 0 -an,缓存到 %TEMP%/cp2077-anim/.webp(4K 源约 4.4MB,首次编码约 5–8s,之后直接命中)。

1.8.3

视频类壁纸改用视频原片抽帧,同时把叠了两层的暗罩砍掉一层。

现象 · 原因 · 修法 视频类壁纸看着糊 · 背景用的是 preview.jpg / preview.gif,多数只有 1024px,铺满 1418 CSS 宽(1772 设备像素)要放大 1.7 倍 · Host 新增 GET /cp2077-wallpaper/still/,用 ffmpeg 从原视频抽一帧(-ss 1.5 -frames:v 1 -vf scale=min(2560,iw)),缓存到 %TEMP%/cp2077-stills/.jpg 抽帧失败 · 系统里可能没有 ffmpeg · 端口内部回落:返回原封面图,client 无需感知 抽帧慢 · 4K 片源解码要几百毫秒 · 按 id + 源文件 mtime 缓存,第二次直接命中;抽取期间背景仍是封面 画面发灰发闷 · 主题底色洗白(wash)与壁纸 veil 两层暗罩叠加,合计压掉约 80% 亮度 · veil 从 .58/.80 降到 .08/.26;washAlpha() 改为 0.92 - t * 0.58

启动后 client 只请求一次索引拿到 hasVideo 集合:视频类壁纸走 /still/,静态图类仍走 /file/,取不到索引时退回封面。

1.8.2

壁纸「一直很糊」的真正原因找到了。

  • cp2077-v12.css 第 22 节还留着 1.5.x 时代的 .cp2077-wallpaper::before { inset: -16px; filter: blur(3px) brightness(.78) saturate(.92) }。它是伪元素,画在图层背景之上 —— 所以无论后面怎么调,屏幕上看到的永远是那张被糊掉、压暗 22% 的图。
  • 修法:整段删掉这个伪元素,只保留第 25 节「容器自身 background-image + cover」。为兼顾正文可读性,把无模糊的遮罩从 .16/.44 加深到 .24/.52(浅色 .26/.56)—— 变暗但不失细节。
  • 顺带:1.8.1 关掉的设置遮罩 backdrop-filter: blur(10px) 与菜单 blur(40px) 也属于同一类问题。

封面分辨率提醒:Wallpaper Engine 的 preview.jpg 只有 1024x1024,铺到 1408 宽的视口会放大 1.375×,边缘略软 —— 这是素材上限,不是渲染问题。

1.8.1

设置界面与壁纸选择器的收尾。

问题 · 根因 · 修法 进设置后壁纸变模糊 · 设置遮罩带 backdrop-filter: blur(10px),把背后的壁纸糊掉 · 关掉 mask 与 menu 的 blur,改用更实的遮罩色(0.82 / 0.88) 壁纸选择器是几个巨大空块 · 缩略图网格的 CSS 在一次截断里丢了,只剩 JSX 类名 · 补回第 27 节:96px 最小列宽、76px 高、黄框选中、VIDEO 角标、标题渐隐 选择器挤在右半边 · 该行沿用「左标签右控件」的横排 · 用 :has() 定位到含网格的行,改成纵向堆叠并铺满整行 残留「动态播放」开关 · 视频路径已移除,开关没了意义 · 删掉该开关

1.8.0

壁纸渲染机制整体替换。旧机制(找宿主 + 逐个打穿容器底色)在这套多层外壳上不成立,改为「一个底层图层 + 让外壳自己变透」。

问题 · 根因 · 现在的做法 壁纸几乎从不显示 · 外壳每一层都用 bg-base token 实心填充,压在背景图层之上 · 把该 token 覆盖成半透明 rgba,alpha 随透明度滑块在 0.94-0.40 之间变化 覆盖不全 / 时有时无 · 宿主探测依赖布局,右栏或侧栏一变就选错元素 · 不再探测宿主:position fixed、inset 0、z-index -1 的图层挂在 body 下 切换会话后消失 · 宿主节点随路由重建 · 图层与宿主无关,挂载、自愈、改设置、切换后都幂等重放 跟内容一起滚 · 图层原先挂在滚动容器内部 · fixed 定位 + html/body 背景透明,滚动只动内容

  • 侧栏 alpha 额外提高 0.50,避免图标文字压在亮部画面上。
  • 壁纸透明度滑块语义:100 = 最清晰,0 = 几乎压掉,默认 72。
  • 视频类壁纸不再播放:该 WebView 不合成 video 图层(canvas 能取帧,屏幕上是黑的),统一用封面图。
  • 旧实现保留为 ensureWallpaperLegacy(),不再调用。

1.7.7

修掉「壁纸消失后不再出现」。

问题 · 根因 · 修法 切会话 / 开关设置页后壁纸不再回来 · 对话列在切换瞬间会塌成 0x0,图层被隐藏只靠 requestAnimationFrame 重试;窗口在后台或标签隐藏时 rAF 被节流,重试链断掉就永久不可见 · 改用有界的 setTimeout 链(240ms)重试,隐藏方式从 display: none 换成 visibility: hidden(不触发重排、可逆),并给对话列挂 ResizeObserver,尺寸恢复时立刻同步

1.7.6

壁纸终于能稳定铺满对话列,切换会话、滚动、窗口缩放都不再丢失。

问题 · 根因 · 修法 壁纸看不见 · 对话列的每个大容器都自己刷了一层不透明底色,画在祖先上的背景全被盖住 · punchThrough() 遍历列子树,把宽度 ≥ 列宽 80% 且底色不透明的容器逐个改成 transparent,并记录原值以便关闭时还原 壁纸跟内容一起滚 · 壁纸挂在使用 overflow: auto 的滚动容器里 · 壁纸层改为 position: absolute; inset: 0,锚点改成 [data-composer-input] 向上找到的对话列,不再依赖会滚动的祖先 壁纸盖住侧边栏 · 早期用视口定位,越过了列的裁剪范围 · 只覆盖 pI_x6G_centerCol(对话列)矩形,syncVideoRect() 不再写几何,只维护 relative / isolation 与零尺寸时隐藏 切换会话后消失 · 宿主被换成新的 DOM 节点,旧的壁纸层留在废节点上 · 每次自愈都重新锚定;壁纸节点在宿主改变时迁移而不是重建,播放进度保留

调试过程中确认的另一件事:bsk_page reload(含 hard)拿不到新 bundle,必须 navigate 到 http://localhost:3080/?cpbust=N 才能让浏览器重新拉取最新的 client 资源。

1.6.7

  • 壁纸不再挑单一宿主:把所有覆盖视口 50% 以上的祖先容器全部铺上壁纸,切换对话后无论留下哪一层都在。
  • 设置页恢复一行状态,显示已铺层数与视频播放状态。

1.6.6

  • 壁纸宿主改为 最内层的大不透明容器(此前取最外层,画上去会被自己的子元素盖住,导致切换对话后壁纸消失)。
  • 移除设置页里的开发者诊断行。