dsh-deepseek-style-theme 2.0.82 → 2.0.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/lib/client.js +188 -67
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# DeepSeek Style Theme
|
|
1
|
+
# DeepSeek Style Theme
|
|
2
2
|
|
|
3
3
|
为 DeepSeek Harness Web GUI 复刻 DeepSeek 官网视觉的主题皮肤插件。
|
|
4
4
|
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
- **标题栏**:默认透明,悬停变毛玻璃;栏内按钮统一为真圆角(覆盖产品全局 corner-shape:superellipse 造成的方角观感),会话标题小胶囊化
|
|
25
25
|
- **悬停动画**:中/E、「打开方式」、「对话 / 轨迹」标签悬停时上浮 + 品牌色辉光;中/E 短标签「中/E」平滑展开为「中文/EN」(max-width 过渡,非 display 硬切);「打开方式」展开菜单时箭头翻转,切换标签时选中项弹出
|
|
26
26
|
- **DeepSeek 品牌链接**:点击侧边栏 DeepSeek 标识跳转 `https://www.deepseek.com/`
|
|
27
|
-
- **轨迹视图**:「对话 /
|
|
27
|
+
- **轨迹视图**:「对话 / 轨迹」标签在标题栏悬停(`.dshome-swap`)或轨迹视图存在时显示,**不是常驻**——会话视图下实测 `[class*="wSkVaW_tabs"]` 默认 `display:none`(2.0.84 按实机报告 B4 勘误)
|
|
28
28
|
- **运行中子代理面板**:有子代理启动时,右下角浮出玻璃面板,实时列出正在运行的子代理——名称、已运行时长、token 用量与流动进度条;点击条目直接跳转到该子代理会话,可折叠收起
|
|
29
29
|
- **跨平台**:打开工作区目录时按系统调用文件管理器(Windows Explorer / macOS Finder / Linux 默认文件管理器)
|
|
30
30
|
- **模型目录同步(1.40.0+)**:每次插件启动向 DeepSeek 端点询问一次模型列表,与 `llm-deepseek` 目录比对后按需对齐——「选择器里有哪些模型」由接口说了算,但**绝不臆造能力位、绝不清空你的条目**,移除项会逐个记日志;可用 `catalogSync: auto | add | off` 控制(详见下文)
|
|
@@ -318,7 +318,8 @@ dsh plugin --profile web remove dsh-deepseek-style-theme
|
|
|
318
318
|
| `symptom-probe-selftest.mjs` | 上面那份探针的自测(19 项):在一个合成页面上跑(我们自己的 canvas + 带超宽子元素的消息列 + 每 60 ms 改一次 transform 的卡片 + CDP 派发真实鼠标移动),断言环境报告的字段形状、`run()` 的采样与逐字段 diff、五个开关的实际效果(含 `clip` 在 `overflow-y:auto` 旁会计算成 `hidden` 这条 CSS Overflow 3 规则) | `chrome --headless=new --remote-debugging-port=9333 --user-data-dir=%TEMP%\dsh-probe-chrome about:blank`,然后 `node tools/symptom-probe-selftest.mjs` |
|
|
319
319
|
| `bridge-smoke.mjs` | 宿主端私有 RPC 通道:协议、错误路径,以及完整围栏(非回环对端 / 缺 `Host` / DNS rebinding / 跨站来源 / `Origin` 不匹配 / 非 JSON 类型 / UNC 路径 / `file.*` 端点识别与校验,24 项检查;加 `--open` 为 25 项,会真的用系统默认应用打开一个临时文件,**会在桌面弹出窗口**,默认不启用) | `node tools/bridge-smoke.mjs [已安装的 lib/index.js] [--open]` |
|
|
320
320
|
| `dstt-schema-smoke.mjs` | DSTT 设置 schema 与写入路径的一致性:**每个能写进 `settings.yaml` 的值都必须过 `register()` 的校验**(1.43.2 那次 schema 缺 `wide` 的发布阻塞就靠它守)。含背景方式四档的往返、未知 id 拒写、自定义背景的归一化(控制字符→空格、去首尾、长度上限)、动态背景开关的读写与布尔回读,以及「客户端枚举 = 宿主枚举」的跨文件检查(29 项) | `node tools/dstt-schema-smoke.mjs [lib/index.js]` |
|
|
321
|
-
| `bg-recipes-smoke.mjs` | 客户端源码层的数据与契约断言(2.0.73+,随每版增补):① 必须含纯白 + 近白、② 只有两个颜色参与、`bold` 浅色无 `#FFFFFF`、③ 的基底/让位规则与选择器守卫、壁纸引擎标记与「提示而非自动切换」、桌面壁纸端点与 `WallpaperStyle` 映射、`darkSync` 作用域、两条反引号守卫(整表扫描)、发送按钮 hover 不再位移、流场 16F
|
|
321
|
+
| `bg-recipes-smoke.mjs` | 客户端源码层的数据与契约断言(2.0.73+,随每版增补):① 必须含纯白 + 近白、② 只有两个颜色参与、`bold` 浅色无 `#FFFFFF`、③ 的基底/让位规则与选择器守卫、壁纸引擎标记与「提示而非自动切换」、桌面壁纸端点与 `WallpaperStyle` 映射、`darkSync` 作用域、两条反引号守卫(整表扫描)、发送按钮 hover 不再位移、流场 16F 探测与回退、悬停倾斜的抬升 1.01 与 `TILT_CONTROLS` 不含文字录入、进入控件时冻结而非释放、释放只在离开卡片矩形、两套玻璃配方都生效(2.0.82)、横向裁切覆盖消息列/对话框/转写区、**8 个 CSS 契约名必须已声明 + 模板插值不得早于 const 声明(2.0.84:`ATTR` 被误删、`USER_BUBBLE` 提前插值都属于这一类,`node --check` 看不见)**(73 项) | `node tools/bg-recipes-smoke.mjs [lib/client.js]` |
|
|
322
|
+
| `parse-smoke.mjs` | **每个文件都必须能解析**(2.0.84+):`lib/` 与 `tools/` 下每个 `.js/.mjs` 过一遍 `node --check`。看着多余,其实不是——已经有三次"注释里的反引号把模板字符串提前结束"的事故(`lib/client.js` 两次、`tools/gui-probe.mjs` 一次,后者 Node 的报错还不带文件名)。纯解析门,不做 lint | `node tools/parse-smoke.mjs` |
|
|
322
323
|
| `fluid-precision-test.mjs` | **流场存储精度对照实验**(2.0.81+):把 `lib/client.js` 里的真实 `VERTEX_SHADER` / `FLOW_SHADER` 抠出来,在无头 Chrome 里对同一场景各跑 240 步,分别存 RGBA8 与 RGBA16F,读回统计"取值分布 / 落在 1/255 网格上的层级 / max / mean",并判定 16F 是否真的摆脱了量化(5 项)。**需要 Chromium 系浏览器**,且 Chrome 在受限进程里起不来,请从普通终端跑 | `node tools/fluid-precision-test.mjs [--chrome <路径>] [--keep]` |
|
|
323
324
|
| `catalog-sync-smoke.mjs` | 模型目录同步的全部分支:一致 / 可描述漂移 / 聚合网关目录 / 只追加 / `off` / baseURL 解析顺序 / 无密钥 / 端点故障 / 命名空间未就绪 / 版本冲突(27 项检查,全用替身,无需凭据与网络) | `node tools/catalog-sync-smoke.mjs [已安装的 lib/index.js]` |
|
|
324
325
|
| `catalog-sync-live.mjs` | 用**真实端点 + 真实密钥**跑一遍同步:A 场景(目录已一致)应 0 写入,B 场景(人为制造漂移)应恰好 1 次写入并复原条目;写入被拦下,不落盘 | `DEEPSEEK_API_KEY=... node tools/catalog-sync-live.mjs [--profile web] [--drift-id deepseek-v4-pro]` |
|
package/lib/client.js
CHANGED
|
@@ -499,6 +499,15 @@ void main() {
|
|
|
499
499
|
let previous = 0;
|
|
500
500
|
const step = 1e3 / 30;
|
|
501
501
|
const frame = (now) => {
|
|
502
|
+
// Self-cleanup: a canvas that has left the DOM must not keep
|
|
503
|
+
// simulating. This is the backstop for a mount that threw midway --
|
|
504
|
+
// B1 in the 2026-09-16 report leaked one full-resolution simulator per
|
|
505
|
+
// 动态背景 flip because the disposer never came back.
|
|
506
|
+
if (!canvas.isConnected) {
|
|
507
|
+
running = false;
|
|
508
|
+
cancelAnimationFrame(raf);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
502
511
|
raf = requestAnimationFrame(frame);
|
|
503
512
|
if (now - previous < step) return;
|
|
504
513
|
previous = now - (now - previous) % step;
|
|
@@ -690,6 +699,21 @@ void main() {
|
|
|
690
699
|
var FILTER_ID = "dshome-glass-dispersion";
|
|
691
700
|
var TINT_ID = "dshome-glass-dispersion-tint";
|
|
692
701
|
var REFRACT_ID = "dshome-glass-dispersion-refract";
|
|
702
|
+
/**
|
|
703
|
+
* The opt-in marker on <html> that the stylesheets chain `url(#…)` behind.
|
|
704
|
+
*
|
|
705
|
+
* This declaration was DELETED in d862487 by accident and stayed missing
|
|
706
|
+
* from 1.43.6 through 2.0.82, which is the whole of B1 in the 2026-09-16 bug
|
|
707
|
+
* report: `startGlassDispersion()` threw a ReferenceError on its first line
|
|
708
|
+
* (`root.hasAttribute(ATTR)`), so the refraction never mounted on any
|
|
709
|
+
* version -- and because `startAmbient()` calls into this function, the
|
|
710
|
+
* ambient mount died with it, taking the disposer with it (the 动态背景
|
|
711
|
+
* toggle stopped unmounting, and each flip leaked a full-resolution fluid
|
|
712
|
+
* simulator). Two lessons are encoded here: the constant is declared next to
|
|
713
|
+
* the ids it belongs to, and the ambient mount no longer swallows a throw
|
|
714
|
+
* silently (see its try/catch and the caller's console.error).
|
|
715
|
+
*/
|
|
716
|
+
var ATTR = "data-dshome-dispersion";
|
|
693
717
|
var TALL_FILTER_ID = "dshome-glass-dispersion-tall";
|
|
694
718
|
var TALL_TINT_ID = "dshome-glass-dispersion-tall-tint";
|
|
695
719
|
var TALL_REFRACT_ID = "dshome-glass-dispersion-tall-refract";
|
|
@@ -1222,10 +1246,47 @@ body[data-dshome-dark] .dshome-sa-item.dshome-sa-stopping .dshome-sa-bar{backgro
|
|
|
1222
1246
|
@media (max-width:760px){.dshome-sa{right:10px;bottom:10px;width:min(286px,calc(100vw - 20px))}}
|
|
1223
1247
|
`;
|
|
1224
1248
|
|
|
1249
|
+
// ── USER_BUBBLE: the one selector both recipes glass user bubbles with ────
|
|
1250
|
+
// Declared HERE, above PATCH_BLOCKS and GLASS_CSS, because both interpolate
|
|
1251
|
+
// it while they are being built: as a `const` declared further down it threw
|
|
1252
|
+
// `Cannot access 'USER_BUBBLE' before initialization` the moment the factory
|
|
1253
|
+
// ran, which took the entire theme down (no style tags at all). Same failure
|
|
1254
|
+
// class as B1 -- an identifier that resolves to nothing at activation time --
|
|
1255
|
+
// and the reason tools/factory-smoke.mjs exists.
|
|
1256
|
+
//
|
|
1257
|
+
// Two properties are load-bearing, and both were learned the hard way
|
|
1258
|
+
// (B5 / B2 of the 2026-09-16 bug report):
|
|
1259
|
+
//
|
|
1260
|
+
// 1. It is ONE compound selector — no top-level comma. Used as a prefix in
|
|
1261
|
+
// `html[data-dshome-glass="liquid"] :is(USER_BUBBLE){…}` rules, a comma
|
|
1262
|
+
// list is a trap: the prefix binds only to the branch it touches and every
|
|
1263
|
+
// later branch escapes the gate entirely. That is exactly what happened.
|
|
1264
|
+
// A second branch, meant to cover a just-typed bubble, also matched the
|
|
1265
|
+
// product's TOOLTIP (`_bubble_<hash>_<n>`, `position:fixed`), and the
|
|
1266
|
+
// theme's `position:relative` turned that floating tooltip into an
|
|
1267
|
+
// in-flow element — 78px of width inside the composer, 48px inside a
|
|
1268
|
+
// message action bar — so hovering the send or copy button shoved the
|
|
1269
|
+
// button out from under the cursor, the tooltip unmounted, the button
|
|
1270
|
+
// came back, and the loop fed itself at up to 60Hz. That WAS the user's
|
|
1271
|
+
// 「按钮跳动」. Every usage is wrapped in `:is(…)` now, so a future second
|
|
1272
|
+
// branch cannot escape the gate again.
|
|
1273
|
+
// 2. The tooltip must be excluded by SHAPE. Message bubbles are
|
|
1274
|
+
// `<hash>_bubble` (`Sixlwa_bubble`, `oRe1gG_bubble`); the tooltip is the
|
|
1275
|
+
// leading-underscore form `_bubble_…`, which is what the first `:not()`
|
|
1276
|
+
// keys on. `:not([role="tooltip"])` is belt-and-braces for the day the
|
|
1277
|
+
// product starts labelling it.
|
|
1278
|
+
//
|
|
1279
|
+
// The gap the deleted branch covered — a message typed while the agent is
|
|
1280
|
+
// still working sits outside any flow-kind container until the turn picks it
|
|
1281
|
+
// up — is knowingly left unglazed for that moment: a transient unglazed
|
|
1282
|
+
// bubble is a cosmetic wait, a teleporting button is not.
|
|
1283
|
+
const USER_BUBBLE = ':is([data-chat-flow-kind="user"],[data-chat-flow-kind="steering"]) [class*="bubble" i]:not([class*="_bubble_"]):not([role="tooltip"])';
|
|
1284
|
+
|
|
1225
1285
|
// PATCH_BLOCKS: every rule group that targets product build-time class
|
|
1226
1286
|
// names. `anchor` is the class that must exist in the DOM before the
|
|
1227
1287
|
// block is injected; each block degrades independently when its surface
|
|
1228
|
-
// renames in a product rebuild
|
|
1288
|
+
// renames in a product rebuild, and `anchor` may be a list of
|
|
1289
|
+
// alternatives when a surface has already been renamed once.
|
|
1229
1290
|
const PATCH_BLOCKS = [
|
|
1230
1291
|
// Frame / sidebar / details columns.
|
|
1231
1292
|
{ anchor: ".pI_x6G_frame", css: `
|
|
@@ -1314,7 +1375,7 @@ body[data-dshome-dark] .wSkVaW_tabActive{box-shadow:0 3px 12px color-mix(in srgb
|
|
|
1314
1375
|
/* Header chips: the product's global corner-shape:superellipse(1.5) squares off
|
|
1315
1376
|
even pill radii, so every control in the bar is restored to a true round end
|
|
1316
1377
|
(and the session-title crumb, 12px on a 28px box, becomes a real pill). */
|
|
1317
|
-
.wSkVaW_header .SVAs4q_label,.wSkVaW_header .cubgiG_seat,.wSkVaW_header .CAgGvG_split,.wSkVaW_header .dshome-locale,.wSkVaW_header [data-sidebar-right-expand],.wSkVaW_header
|
|
1378
|
+
.wSkVaW_header .SVAs4q_label,.wSkVaW_header .cubgiG_seat,.wSkVaW_header .CAgGvG_split,.wSkVaW_header .dshome-locale,.wSkVaW_header [data-sidebar-right-expand],.wSkVaW_header [class*="_moreButton"],.wSkVaW_header .wSkVaW_crumb{corner-shape:round}
|
|
1318
1379
|
.wSkVaW_header .wSkVaW_crumb{border-radius:999px}
|
|
1319
1380
|
/* Open-in-app split button: hover lift with a brand tint, and the chevron turns
|
|
1320
1381
|
over while its menu is open. */
|
|
@@ -1328,13 +1389,15 @@ body[data-dshome-dark] .wSkVaW_tabActive{box-shadow:0 3px 12px color-mix(in srgb
|
|
|
1328
1389
|
body[data-dshome-dark] .wSkVaW_header .CAgGvG_split:hover{border-color:color-mix(in srgb,var(--ds-brand) 52%,transparent);background:color-mix(in srgb,var(--ds-brand) 16%,transparent);box-shadow:0 4px 16px color-mix(in srgb,var(--ds-brand) 30%,transparent)}
|
|
1329
1390
|
body[data-dshome-dark] .wSkVaW_header .CAgGvG_split:hover .CAgGvG_main,body[data-dshome-dark] .wSkVaW_header .CAgGvG_split:hover .CAgGvG_chevron{background:color-mix(in srgb,var(--ds-brand) 24%,transparent);color:#fff}
|
|
1330
1391
|
` },
|
|
1331
|
-
// Session-log button (hover expand).
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1392
|
+
// Session-log button (hover expand). The product renamed it to
|
|
1393
|
+
// `nL4_yW_moreButton` on the current build (B2, 2026-09-16), so anchor and
|
|
1394
|
+
// rules both key on the suffix, the way `[class*="_fade" i]` already does.
|
|
1395
|
+
{ anchor: '[class*="_moreButton"]', css: `
|
|
1396
|
+
:is([class*="_moreButton"]){min-width:0!important;width:28px;height:28px;padding:0!important;border-radius:50%!important;border:none!important;background:color-mix(in srgb,var(--ds-brand) 8%,transparent)!important;color:var(--ds-brand)!important;overflow:hidden;transition:width .2s ease,padding .2s ease,border-radius .2s ease}
|
|
1397
|
+
:is([class*="_moreButton"]) span{max-width:0;opacity:0;overflow:hidden;transition:max-width .2s ease,opacity .2s ease}
|
|
1398
|
+
.wSkVaW_header:hover :is([class*="_moreButton"]){width:auto;padding:0 12px!important;border-radius:18px!important}
|
|
1399
|
+
.wSkVaW_header:hover :is([class*="_moreButton"]) span{max-width:90px;opacity:1}
|
|
1400
|
+
body[data-dshome-dark] :is([class*="_moreButton"]){color:var(--ds-brand-light)!important}
|
|
1338
1401
|
` },
|
|
1339
1402
|
// Glass cards with spinning gradient border — upgraded to liquid glass:
|
|
1340
1403
|
// specular top edge, saturated blur, brighter inner rim.
|
|
@@ -1386,17 +1449,21 @@ body[data-dshome-dark] .uV2eYG_card::before{background:conic-gradient(from var(-
|
|
|
1386
1449
|
{ anchor: ".wSkVaW_scrollBody", css: `
|
|
1387
1450
|
[class*="scrollBody"],[class*="composerSeat"],[class*="viewArea"]{overflow-x:clip}
|
|
1388
1451
|
` },
|
|
1389
|
-
// Empty-state headline + preview badge.
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1452
|
+
// Empty-state headline + preview badge. The product renamed
|
|
1453
|
+
// `pXSMma_headlineText` to `pXSMma_headline` (B2, 2026-09-16), which
|
|
1454
|
+
// silently killed this block and left the headline at 26px/500 with no
|
|
1455
|
+
// gradient. Keyed on the suffix now, along with the two brand marks that
|
|
1456
|
+
// live in the same family.
|
|
1457
|
+
{ anchor: '[class*="_headline"]', css: `
|
|
1458
|
+
:is([class*="_headline"]){font-size:34px;line-height:42px;font-weight:700;letter-spacing:-.01em;background:linear-gradient(120deg,#152443 0%,var(--ds-brand-deep) 55%,var(--ds-brand) 100%);-webkit-background-clip:text;background-clip:text;color:transparent}
|
|
1459
|
+
body[data-dshome-dark] :is([class*="_headline"]){background:linear-gradient(120deg,#fff 0%,var(--ds-brand-light) 60%,var(--ds-brand) 100%);-webkit-background-clip:text;background-clip:text}
|
|
1460
|
+
:is([class*="_previewBadge"]){border-radius:999px!important;background:color-mix(in srgb,var(--ds-brand) 10%,transparent)!important;border:1px solid color-mix(in srgb,var(--ds-brand) 25%,transparent)!important;color:var(--ds-brand)!important}
|
|
1461
|
+
:is([class*="_fish"]){color:var(--ds-brand)!important}
|
|
1395
1462
|
` },
|
|
1396
1463
|
// Chat bubbles — liquid glass panes.
|
|
1397
|
-
{ anchor: ".
|
|
1398
|
-
|
|
1399
|
-
body[data-dshome-dark]
|
|
1464
|
+
{ anchor: ".wSkVaW_scrollBody", css: `
|
|
1465
|
+
:is(${USER_BUBBLE}){background:rgba(255,255,255,.6)!important;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border:1px solid rgba(0,0,0,.05);box-shadow:0 4px 16px rgba(13,30,60,.06)}
|
|
1466
|
+
body[data-dshome-dark] :is(${USER_BUBBLE}){background:rgba(21,42,86,.65)!important;border:1px solid rgba(255,255,255,.1);box-shadow:0 4px 16px rgba(0,0,0,.25)}
|
|
1400
1467
|
` },
|
|
1401
1468
|
// Trajectory view bar.
|
|
1402
1469
|
{ anchor: ".fV0t5q_root", css: `
|
|
@@ -1470,7 +1537,7 @@ body[data-dshome-dark]{
|
|
|
1470
1537
|
}
|
|
1471
1538
|
html .hHd-Xa_root::before,
|
|
1472
1539
|
html .uV2eYG_card::after,
|
|
1473
|
-
html
|
|
1540
|
+
html :is(${USER_BUBBLE}),
|
|
1474
1541
|
html .fV0t5q_root,
|
|
1475
1542
|
html [data-composer-card],
|
|
1476
1543
|
html .VOzbGW_panel,
|
|
@@ -1489,27 +1556,27 @@ html .dshome-file-menu{
|
|
|
1489
1556
|
--dshome-spec-x/y (written per element by startSpecularParallax). */
|
|
1490
1557
|
html [data-dshome-spot]:hover{--dshome-glass-spec:1;}
|
|
1491
1558
|
html [data-composer-card]:hover{--dshome-glass-spec:1;--dshome-glass-sheen:.12;}
|
|
1492
|
-
html
|
|
1559
|
+
html :is(${USER_BUBBLE}):hover,html .dshome-sa:hover,html .dshome-file-menu:hover{
|
|
1493
1560
|
background-color:rgb(255 255 255 / calc(var(--dshome-glass-sheen) + .16))!important;
|
|
1494
1561
|
}
|
|
1495
|
-
body[data-dshome-dark]
|
|
1562
|
+
body[data-dshome-dark] :is(${USER_BUBBLE}):hover,
|
|
1496
1563
|
body[data-dshome-dark] .dshome-sa:hover,
|
|
1497
1564
|
body[data-dshome-dark] .dshome-file-menu:hover{
|
|
1498
1565
|
background-color:rgb(255 255 255 / calc(var(--dshome-glass-sheen) + .06))!important;
|
|
1499
1566
|
}
|
|
1500
1567
|
html[data-dshome-dispersion] .hHd-Xa_root::before,
|
|
1501
|
-
html[data-dshome-dispersion]
|
|
1568
|
+
html[data-dshome-dispersion] :is(${USER_BUBBLE}),
|
|
1502
1569
|
html[data-dshome-dispersion] .VOzbGW_panel{
|
|
1503
1570
|
-webkit-backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-glass-blur)) saturate(var(--dshome-glass-saturate))!important;
|
|
1504
1571
|
backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-glass-blur)) saturate(var(--dshome-glass-saturate))!important;
|
|
1505
1572
|
}
|
|
1506
1573
|
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
|
|
1507
|
-
html .hHd-Xa_root::before,html .uV2eYG_card::after,html
|
|
1574
|
+
html .hHd-Xa_root::before,html .uV2eYG_card::after,html :is(${USER_BUBBLE}),html [data-composer-card],html .VOzbGW_panel,html .dshome-sa,html .dshome-file-menu{
|
|
1508
1575
|
background-color:rgba(255,255,255,.86)!important;
|
|
1509
1576
|
}
|
|
1510
1577
|
body[data-dshome-dark] .hHd-Xa_root::before,
|
|
1511
1578
|
body[data-dshome-dark] .uV2eYG_card::after,
|
|
1512
|
-
body[data-dshome-dark]
|
|
1579
|
+
body[data-dshome-dark] :is(${USER_BUBBLE}),
|
|
1513
1580
|
body[data-dshome-dark] [data-composer-card],
|
|
1514
1581
|
body[data-dshome-dark] .VOzbGW_panel,
|
|
1515
1582
|
body[data-dshome-dark] .dshome-sa,
|
|
@@ -1560,14 +1627,9 @@ html[data-dshome-dispersion] .VOzbGW_panel{
|
|
|
1560
1627
|
// a transcript can contain ONLY steering bubbles — anchoring the selector
|
|
1561
1628
|
// on "user" alone silently skipped every bubble on the page.
|
|
1562
1629
|
//
|
|
1563
|
-
//
|
|
1564
|
-
//
|
|
1565
|
-
//
|
|
1566
|
-
// two anchored selectors miss it and it renders unglazed until the turn
|
|
1567
|
-
// consumes it. Rather than enumerate the container it lands in, this side
|
|
1568
|
-
// takes any bubble that is NOT part of an assistant step — which is the
|
|
1569
|
-
// only place a bubble must not be glassed.
|
|
1570
|
-
const LIQUID_BUBBLE = ':is([data-chat-flow-kind="user"],[data-chat-flow-kind="steering"]) [class*="bubble" i],[class*="bubble" i]:not([data-chat-flow-kind="assistant-step"] *)';
|
|
1630
|
+
// (USER_BUBBLE itself is declared above PATCH_BLOCKS: this sheet and the
|
|
1631
|
+
// patch blocks both interpolate it, so it has to exist before either is
|
|
1632
|
+
// built.)
|
|
1571
1633
|
const LIQUID_CSS = `
|
|
1572
1634
|
@property --dshome-liquid-sweep{syntax:"<angle>";inherits:false;initial-value:0deg}
|
|
1573
1635
|
@keyframes dshome-liquid-sweep{from{--dshome-liquid-sweep:0deg}to{--dshome-liquid-sweep:360deg}}
|
|
@@ -1635,7 +1697,7 @@ body[data-dshome-dark]{
|
|
|
1635
1697
|
html[data-dshome-glass="liquid"] body .hHd-Xa_root::before,
|
|
1636
1698
|
html[data-dshome-glass="liquid"] body [data-composer-card],
|
|
1637
1699
|
html[data-dshome-glass="liquid"] body .wSkVaW_header:hover::before,
|
|
1638
|
-
html[data-dshome-glass="liquid"] body ${
|
|
1700
|
+
html[data-dshome-glass="liquid"] body :is(${USER_BUBBLE}){
|
|
1639
1701
|
background-color:var(--dshome-liquid-fill)!important;
|
|
1640
1702
|
background-image:
|
|
1641
1703
|
radial-gradient(360px circle at calc(50% + var(--dshome-spec-x,0) * 50%) calc(50% + var(--dshome-spec-y,0) * 50%), rgb(255 255 255 / calc(var(--dshome-glass-spec) * .24)), transparent 60%),
|
|
@@ -1688,8 +1750,11 @@ html[data-dshome-glass="liquid"] body .hHd-Xa_root::before{
|
|
|
1688
1750
|
radial-gradient(360px circle at calc(50% + var(--dshome-spec-x,0) * 50%) calc(50% + var(--dshome-spec-y,0) * 50%), rgb(255 255 255 / calc(var(--dshome-glass-spec) * .24)), transparent 60%),
|
|
1689
1751
|
linear-gradient(180deg, rgba(255,255,255,var(--dshome-liquid-sheen)), rgba(255,255,255,.015) 34%, rgba(255,255,255,0))!important;
|
|
1690
1752
|
}
|
|
1691
|
-
/* The message bubble is the one surface that is not already positioned.
|
|
1692
|
-
|
|
1753
|
+
/* The message bubble is the one surface that is not already positioned. The
|
|
1754
|
+
tooltip is not in this list any more -- that was B5: making a
|
|
1755
|
+
position:fixed tooltip relative turned it into an in-flow 78px block that
|
|
1756
|
+
shoved the send button out from under the cursor. See USER_BUBBLE. */
|
|
1757
|
+
html[data-dshome-glass="liquid"] :is(${USER_BUBBLE}){
|
|
1693
1758
|
position:relative;
|
|
1694
1759
|
transition:background-color .18s ease,box-shadow .18s ease;
|
|
1695
1760
|
}
|
|
@@ -1701,10 +1766,12 @@ html[data-dshome-glass="liquid"] [data-composer-card]{
|
|
|
1701
1766
|
transition:transform .24s cubic-bezier(.2,.8,.2,1),background-color .18s ease,box-shadow .18s ease!important;
|
|
1702
1767
|
will-change:transform;
|
|
1703
1768
|
}
|
|
1704
|
-
html[data-dshome-glass="liquid"] ${
|
|
1769
|
+
html[data-dshome-glass="liquid"] :is(${USER_BUBBLE}):hover{
|
|
1705
1770
|
background-color:rgb(255 255 255 / .18)!important;
|
|
1706
1771
|
}
|
|
1707
|
-
|
|
1772
|
+
/* Liquid's dark hover used to sit OUTSIDE the gate, so it also repainted the
|
|
1773
|
+
frosted recipe's bubbles with liquid's values. Gated like its siblings. */
|
|
1774
|
+
html[data-dshome-glass="liquid"] body[data-dshome-dark] :is(${USER_BUBBLE}):hover{
|
|
1708
1775
|
background-color:rgb(255 255 255 / .10)!important;
|
|
1709
1776
|
}
|
|
1710
1777
|
html[data-dshome-glass="liquid"] body .hHd-Xa_root::after{
|
|
@@ -1741,7 +1808,7 @@ html[data-dshome-glass="liquid"] .hHd-Xa_root::after{
|
|
|
1741
1808
|
continuous background a horizontal displacement has nothing to show, which
|
|
1742
1809
|
the user accepted. */
|
|
1743
1810
|
html[data-dshome-dispersion][data-dshome-glass="liquid"] body .wSkVaW_header:hover::before,
|
|
1744
|
-
html[data-dshome-dispersion][data-dshome-glass="liquid"] body ${
|
|
1811
|
+
html[data-dshome-dispersion][data-dshome-glass="liquid"] body :is(${USER_BUBBLE}){
|
|
1745
1812
|
-webkit-backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-liquid-blur)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1746
1813
|
backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-liquid-blur)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1747
1814
|
}
|
|
@@ -1798,56 +1865,90 @@ html[data-dshome-blur="off"] body{
|
|
|
1798
1865
|
html[data-dshome-glass="liquid"] body .hHd-Xa_root::before,
|
|
1799
1866
|
html[data-dshome-glass="liquid"] body [data-composer-card],
|
|
1800
1867
|
html[data-dshome-glass="liquid"] body .wSkVaW_header:hover::before,
|
|
1801
|
-
html[data-dshome-glass="liquid"] body ${
|
|
1868
|
+
html[data-dshome-glass="liquid"] body :is(${USER_BUBBLE}){
|
|
1802
1869
|
background-color:rgba(255,255,255,.86)!important;
|
|
1803
1870
|
}
|
|
1804
1871
|
body[data-dshome-dark] .hHd-Xa_root::before,
|
|
1805
1872
|
body[data-dshome-dark] [data-composer-card],
|
|
1806
1873
|
body[data-dshome-dark] .wSkVaW_header:hover::before,
|
|
1807
|
-
body[data-dshome-dark] ${
|
|
1874
|
+
html[data-dshome-glass="liquid"] body[data-dshome-dark] :is(${USER_BUBBLE}){
|
|
1808
1875
|
background-color:rgba(13,30,60,.92)!important;
|
|
1809
1876
|
}
|
|
1810
1877
|
}
|
|
1811
1878
|
`;
|
|
1812
1879
|
|
|
1813
1880
|
/**
|
|
1814
|
-
* Inject `css`
|
|
1815
|
-
*
|
|
1816
|
-
*
|
|
1817
|
-
*
|
|
1818
|
-
*
|
|
1881
|
+
* Inject `css` while `anchor` (a selector, or a list of alternatives --
|
|
1882
|
+
* several product surfaces have been renamed at least once) matches the DOM.
|
|
1883
|
+
*
|
|
1884
|
+
* The wait is NOT time-boxed. It used to give up after 30s, which quietly
|
|
1885
|
+
* broke every surface the user opens late: B3 in the 2026-09-16 report
|
|
1886
|
+
* measured the same code injecting the session-row patch when the sidebar was
|
|
1887
|
+
* expanded at ~4s, and never injecting it when the sidebar was expanded after
|
|
1888
|
+
* 30s (the default is collapsed, so for most sessions the patch simply never
|
|
1889
|
+
* existed). Panels behind a click -- settings, the subagent panel, the
|
|
1890
|
+
* trajectory view -- were all in that set.
|
|
1891
|
+
*
|
|
1892
|
+
* The observer therefore lives as long as the plugin does, and the cost is
|
|
1893
|
+
* bounded instead of unbounded: re-checks are throttled to one per animation
|
|
1894
|
+
* frame, a batch that finds nothing schedules nothing, and the observer
|
|
1895
|
+
* disconnects the moment the block lands. A block that never lands costs one
|
|
1896
|
+
* `querySelector` per frame at worst, and logs once, with the anchors it tried.
|
|
1819
1897
|
*/
|
|
1820
1898
|
function installGuarded(anchor, css) {
|
|
1899
|
+
const selectors = Array.isArray(anchor) ? anchor : [anchor];
|
|
1900
|
+
const label = selectors.join('|');
|
|
1821
1901
|
let dispose = null;
|
|
1822
1902
|
const applyNow = () => {
|
|
1823
1903
|
if (dispose !== null) return true;
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1904
|
+
for (const selector of selectors) {
|
|
1905
|
+
if (document.querySelector(selector) === null) continue;
|
|
1906
|
+
dispose = injectStyle(css, 'patch' + selectors[0]);
|
|
1907
|
+
return true;
|
|
1908
|
+
}
|
|
1909
|
+
return false;
|
|
1827
1910
|
};
|
|
1828
1911
|
if (applyNow()) {
|
|
1829
1912
|
return () => { if (dispose !== null) dispose(); };
|
|
1830
1913
|
}
|
|
1831
|
-
let
|
|
1832
|
-
let
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1914
|
+
let queued = false;
|
|
1915
|
+
let missed = false;
|
|
1916
|
+
let mo = null;
|
|
1917
|
+
const schedule = () => {
|
|
1918
|
+
if (queued || dispose !== null) return;
|
|
1919
|
+
queued = true;
|
|
1920
|
+
try {
|
|
1921
|
+
requestAnimationFrame(() => {
|
|
1922
|
+
queued = false;
|
|
1923
|
+
if (applyNow() && mo !== null) {
|
|
1924
|
+
mo.disconnect();
|
|
1925
|
+
mo = null;
|
|
1926
|
+
}
|
|
1927
|
+
});
|
|
1928
|
+
} catch (error) {
|
|
1929
|
+
queued = false;
|
|
1930
|
+
}
|
|
1931
|
+
};
|
|
1932
|
+
mo = new MutationObserver(schedule);
|
|
1837
1933
|
try {
|
|
1838
1934
|
mo.observe(document.body, { childList: true, subtree: true });
|
|
1839
1935
|
} catch (error) {
|
|
1840
1936
|
mo = null;
|
|
1841
1937
|
}
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1938
|
+
// One diagnostic line per block, on first sight of a shell that has
|
|
1939
|
+
// rendered and still does not carry any of the alternatives.
|
|
1940
|
+
const announce = () => {
|
|
1941
|
+
if (missed || dispose !== null) return;
|
|
1942
|
+
missed = true;
|
|
1943
|
+
console.info('[' + PLUGIN_ID + '] patch waiting: none of ' + label + ' in the DOM yet (renamed product surface? the guard keeps watching)');
|
|
1944
|
+
};
|
|
1945
|
+
const timer = setTimeout(() => {
|
|
1946
|
+
if (missed || dispose !== null) return;
|
|
1947
|
+
announce();
|
|
1948
|
+
}, 15000);
|
|
1848
1949
|
return () => {
|
|
1950
|
+
if (timer !== 0) clearTimeout(timer);
|
|
1849
1951
|
if (mo !== null) mo.disconnect();
|
|
1850
|
-
clearTimeout(timer);
|
|
1851
1952
|
if (dispose !== null) dispose();
|
|
1852
1953
|
};
|
|
1853
1954
|
}
|
|
@@ -2225,7 +2326,7 @@ html[data-dshome-blur="off"] body{
|
|
|
2225
2326
|
* `.gdEzaW_bubble` this list used to carry matches nothing on the current
|
|
2226
2327
|
* build, which is how the old entry went dead without anyone noticing.
|
|
2227
2328
|
*/
|
|
2228
|
-
const SPOT_SURFACES = `${
|
|
2329
|
+
const SPOT_SURFACES = `${USER_BUBBLE},.uV2eYG_card,.dshome-sa,.dshome-file-menu,.hHd-Xa_root,[data-composer-card]`;
|
|
2229
2330
|
function startSpecularSpotter() {
|
|
2230
2331
|
const onOver = (event) => {
|
|
2231
2332
|
const target = event.target;
|
|
@@ -2430,7 +2531,7 @@ html[data-dshome-blur="off"] body{
|
|
|
2430
2531
|
if (!supportsWebgl2()) return startParticles();
|
|
2431
2532
|
// Which build is running, readable from the page (see README). One line in
|
|
2432
2533
|
// the console answers "is the bundle I just installed actually live?".
|
|
2433
|
-
try { window.__dshomeBuild = '2.0.
|
|
2534
|
+
try { window.__dshomeBuild = '2.0.84 bug-report-fixes'; } catch (error) {}
|
|
2434
2535
|
const canvas = document.createElement('canvas');
|
|
2435
2536
|
canvas.setAttribute('data-dsh-deepseek-canvas', '');
|
|
2436
2537
|
const style = canvas.style;
|
|
@@ -2535,7 +2636,7 @@ html[data-dshome-blur="off"] body{
|
|
|
2535
2636
|
}
|
|
2536
2637
|
|
|
2537
2638
|
function startParticles() {
|
|
2538
|
-
try { window.__dshomeBuild = '2.0.
|
|
2639
|
+
try { window.__dshomeBuild = '2.0.84 bug-report-fixes'; } catch (error) {}
|
|
2539
2640
|
const canvas = document.createElement('canvas');
|
|
2540
2641
|
canvas.setAttribute('data-dsh-deepseek-canvas', '');
|
|
2541
2642
|
const style = canvas.style;
|
|
@@ -2607,6 +2708,11 @@ html[data-dshome-blur="off"] body{
|
|
|
2607
2708
|
|
|
2608
2709
|
function step() {
|
|
2609
2710
|
if (!running) return;
|
|
2711
|
+
// Same self-cleanup as the fluid loop: no canvas in the DOM, no work.
|
|
2712
|
+
if (!canvas.isConnected) {
|
|
2713
|
+
running = false;
|
|
2714
|
+
return;
|
|
2715
|
+
}
|
|
2610
2716
|
g.clearRect(0, 0, W, H);
|
|
2611
2717
|
const base = linkBase();
|
|
2612
2718
|
for (let i = 0; i < parts.length; i++) {
|
|
@@ -3937,7 +4043,10 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
3937
4043
|
// set they asked to keep. Ids match the host schema's `backgroundMode`
|
|
3938
4044
|
// enum; the free-text value for 方式3 is `customBackground`.
|
|
3939
4045
|
const DSTT_BACKGROUNDS = ["classic", "white", "custom", "bold"];
|
|
3940
|
-
|
|
4046
|
+
// `bold` by default: ① classic averages (soft lead + white + near-white) into a
|
|
4047
|
+
// wash the eye reads as blue-white whatever the colour token says, so it is a
|
|
4048
|
+
// poor first impression -- it stays selectable for anyone who wants it.
|
|
4049
|
+
const DSTT_BACKGROUND_DEFAULT = "bold";
|
|
3941
4050
|
const DSTT_BACKGROUND_LABELS = {
|
|
3942
4051
|
classic: { zh: "① 主色+纯白+近白", en: "1 · Lead + white + near-white" },
|
|
3943
4052
|
white: { zh: "② 主色+纯白", en: "2 · Lead + white" },
|
|
@@ -4578,7 +4687,7 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
4578
4687
|
React.createElement('div', { className: 'dshome-dstt-row-title' }, isZh ? '背景方式' : 'Background recipe'),
|
|
4579
4688
|
React.createElement('div', { className: 'dshome-dstt-row-hint' }, isZh
|
|
4580
4689
|
? '① 主色+纯白+近白(默认,旧版那套同色系柔和观感);② 主色+纯白(只有两个色参与,最干净);③ 自选背景:我们的流体与主题底色全部让位(没别的在画时是纯白 / 纯黑);装了 dsh-plugin-wallpaper-engine 且它正在渲染时,连纯白也不画,背景交给它。颜色只驱动按钮之类。输入框可填图片 URL / 任意 CSS 值,或点「桌面壁纸」用本机壁纸;浓三色是 1.43.12 那套,先留着。'
|
|
4581
|
-
: '1 Lead + white + near-white (
|
|
4690
|
+
: 'Bold (default: the lead tone carries real chroma, so all six backgrounds read at a glance). 1 Lead + white + near-white (the soft single-family look this theme shipped before). 2 Lead + white (only two colours participate, the cleanest). 3 Custom background: our fluid and our themed background both stand down (flat white / black when nothing else paints); while dsh-plugin-wallpaper-engine is rendering we do not even paint that, and the background is its layer. The colour drives the buttons and such only. Type an image URL / any CSS value, or press Desktop for the Windows wallpaper.'),
|
|
4582
4691
|
React.createElement('div', { className: 'dshome-dstt-seg dshome-dstt-seg-row', role: 'radiogroup', 'aria-label': isZh ? '背景方式' : 'Background recipe' },
|
|
4583
4692
|
DSTT_BACKGROUNDS.map((id) => React.createElement('button', {
|
|
4584
4693
|
key: id,
|
|
@@ -4811,7 +4920,19 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
4811
4920
|
try { stop(); } catch (error) {}
|
|
4812
4921
|
return;
|
|
4813
4922
|
}
|
|
4814
|
-
try {
|
|
4923
|
+
try {
|
|
4924
|
+
dispose = startAmbient();
|
|
4925
|
+
} catch (error) {
|
|
4926
|
+
// NEVER silent. A throw inside the mount used to be swallowed right
|
|
4927
|
+
// here, which is how one undeclared identifier (ATTR, deleted in
|
|
4928
|
+
// d862487) hid for nineteen versions: no refraction anywhere, a
|
|
4929
|
+
// 动态背景 toggle that stopped unmounting, and one leaked
|
|
4930
|
+
// simulator per flip. Sweep the orphan canvas too -- the fluid
|
|
4931
|
+
// loop also self-cancels when its canvas leaves the DOM.
|
|
4932
|
+
console.error('[dsh-deepseek-style-theme] ambient background failed to mount:', error);
|
|
4933
|
+
for (const stray of document.querySelectorAll('canvas[data-dsh-deepseek-canvas]')) stray.remove();
|
|
4934
|
+
dispose = () => {};
|
|
4935
|
+
}
|
|
4815
4936
|
};
|
|
4816
4937
|
syncAmbient();
|
|
4817
4938
|
const unsubscribe = dsttSubscribe(syncAmbient);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-deepseek-style-theme",
|
|
3
3
|
"description": "DeepSeek official-home style theme: WebGL fluid-flow background, liquid glass with SVG edge refraction, glass sidebar and composer, frosted header, running-subagent progress panel, and DeepSeek brand link for the dsh web GUI",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.84",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"deepseek",
|