dsh-conversation-aurora 0.1.0 → 0.2.0
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 +57 -182
- package/client.js +193 -1002
- package/locale/en.json +2 -2
- package/locale/zh.json +2 -2
- package/package.json +2 -2
- package/preview.html +4 -59
package/client.js
CHANGED
|
@@ -2,630 +2,48 @@ window.__ModuleLoader__.load({
|
|
|
2
2
|
id: 'dsh-conversation-aurora',
|
|
3
3
|
factory() {
|
|
4
4
|
/*
|
|
5
|
-
*
|
|
5
|
+
* dsh-conversation-aurora — 对话区整屏动效背景(Client 半区)
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* 形态:在对话画布内部垫一层 z-index:-1 的 canvas,把整块画布当背景。
|
|
8
|
+
* - 只有"在干活"时才播:任何工具在跑 / 回合进行中;
|
|
9
|
+
* - 空闲、切会话、窗口隐藏时立刻停掉 rAF,画布淡出;
|
|
10
|
+
* - 每个会话按 session id 分配不同主题,Alt+T 手动轮换;
|
|
11
|
+
* - 文字可读性由 .dsh-stage__veil 保证:按正文列宽罩一层纱。
|
|
8
12
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* web 向外:光纤心跳 + 一圈向外扩散的光晕(联网 / 派活)
|
|
12
|
-
* write 向上:光纤自下而上逐段点亮(写码 / 跑命令)
|
|
13
|
-
* think idle 的呼吸加快,不新增手势
|
|
14
|
-
* wait 全体压到 ~2%、转灰蓝、缓慢向列内推
|
|
15
|
-
* error 红色泛光,之后留一点暗红到下一次动作
|
|
16
|
-
*
|
|
17
|
-
* 几何:动效层挂在对话画布内部([data-conversation-content]),z-index:-1。
|
|
18
|
-
* 左右各一个「翼」(.dsh-aurora__wing),翼宽正好等于正文列以外的空白带,
|
|
19
|
-
* 且 overflow:hidden —— 会扩散的元素(光晕、脉冲环)都被裁在列边界之外,
|
|
20
|
-
* 中间文字区零覆盖,这是几何保证,不是渐变赌出来的。
|
|
21
|
-
*
|
|
22
|
-
* 状态信号全部来自宿主已有的语义属性,不读类名:
|
|
23
|
-
* [data-tool][data-state=running|preparing|error|stopped]
|
|
24
|
-
* [data-chat-running] [data-streaming]
|
|
13
|
+
* 状态信号全部来自宿主已有语义属性,不读类名:
|
|
14
|
+
* [data-tool][data-state=running|preparing] [data-chat-running] [data-streaming]
|
|
25
15
|
*/
|
|
26
16
|
|
|
27
|
-
/* =================
|
|
17
|
+
/* ================= 可调常量 ================= */
|
|
28
18
|
|
|
29
|
-
/** 时序与节流;视觉参数在下方 CSS 的 .dsh-aurora 块里。 */
|
|
30
19
|
const TUNE = {
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
/** 切换瞬间光纤亮一下的时长与幅度。 */
|
|
34
|
-
pulseMs: 420,
|
|
35
|
-
pulseRise: 0.16,
|
|
36
|
-
/** error 保持多久,之后转入残留暗红。 */
|
|
37
|
-
errorHoldMs: 1300,
|
|
20
|
+
/** 停止后延迟多久真正淡出(防止工具间隙闪一下)。 */
|
|
21
|
+
holdIdleMs: 420,
|
|
38
22
|
/** 最多跟踪多少个工具行(只留尾部,长会话也不拖慢)。 */
|
|
39
23
|
maxTracked: 48,
|
|
40
|
-
/**
|
|
24
|
+
/** 首次挂载只回看最近多少行。 */
|
|
41
25
|
seedRows: 24,
|
|
42
26
|
/** React 重挂载引发的反复摘挂保护。 */
|
|
43
27
|
remountGuard: 10,
|
|
44
28
|
remountWindowMs: 2000,
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
keyCode: 'b',
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
/** 优先级:高者可以打断低者的最短驻留。 */
|
|
51
|
-
const MODE_PRIORITY = { idle: 0, think: 1, read: 2, web: 3, write: 4, wait: 5, error: 6 };
|
|
52
|
-
|
|
53
|
-
/** 每种模式的主侧;另一侧以 60% 强度 + 0.7s 延迟跟随。 */
|
|
54
|
-
const MODE_PRIMARY = {
|
|
55
|
-
idle: 'both',
|
|
56
|
-
think: 'both',
|
|
57
|
-
read: 'left',
|
|
58
|
-
web: 'right',
|
|
59
|
-
write: 'left',
|
|
60
|
-
wait: 'both',
|
|
61
|
-
error: 'both',
|
|
29
|
+
/** 切会话后的静默期,避免把上一轮的残留行当成"正在工作"。 */
|
|
30
|
+
switchQuietMs: 600,
|
|
62
31
|
};
|
|
63
32
|
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
idle: 'STANDBY',
|
|
67
|
-
think: 'THINK',
|
|
68
|
-
read: 'SCAN',
|
|
69
|
-
web: 'NET',
|
|
70
|
-
write: 'BUILD',
|
|
71
|
-
wait: 'HOLD',
|
|
72
|
-
error: 'ERR',
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
/** 工具名 → 手势。表里没有的按 data-variant 归类,再兜底 read。 */
|
|
76
|
-
const TOOL_MODE = {
|
|
77
|
-
web_search: 'web',
|
|
78
|
-
web_fetch: 'web',
|
|
79
|
-
subagent: 'web',
|
|
80
|
-
workflow: 'web',
|
|
81
|
-
ralph: 'web',
|
|
82
|
-
spawn_teammate: 'web',
|
|
83
|
-
wait_agent: 'web',
|
|
84
|
-
send_message: 'web',
|
|
85
|
-
list_agents: 'web',
|
|
86
|
-
grep: 'read',
|
|
87
|
-
glob: 'read',
|
|
88
|
-
read: 'read',
|
|
89
|
-
read_image: 'read',
|
|
90
|
-
lsp: 'read',
|
|
91
|
-
session_search: 'read',
|
|
92
|
-
session_trace: 'read',
|
|
93
|
-
session_event_read: 'read',
|
|
94
|
-
session_event_search: 'read',
|
|
95
|
-
session_event_trace: 'read',
|
|
96
|
-
cordis_inspect_list: 'read',
|
|
97
|
-
cordis_inspect_query: 'read',
|
|
98
|
-
cordis_inspect_self: 'read',
|
|
99
|
-
cordis_package_inspect: 'read',
|
|
100
|
-
cordis_runtime_inspect: 'read',
|
|
101
|
-
job_list: 'read',
|
|
102
|
-
job_output: 'read',
|
|
103
|
-
get_goal: 'read',
|
|
104
|
-
schedule_list: 'read',
|
|
105
|
-
list_subagent_models: 'read',
|
|
106
|
-
write: 'write',
|
|
107
|
-
edit: 'write',
|
|
108
|
-
run_code: 'write',
|
|
109
|
-
pwsh: 'write',
|
|
110
|
-
bash: 'write',
|
|
111
|
-
terminal_open: 'write',
|
|
112
|
-
terminal_read: 'write',
|
|
113
|
-
terminal_list: 'write',
|
|
114
|
-
terminal_signal: 'write',
|
|
115
|
-
terminal_close: 'write',
|
|
116
|
-
job_kill: 'write',
|
|
117
|
-
create_goal: 'write',
|
|
118
|
-
update_goal: 'write',
|
|
119
|
-
todo_write: 'write',
|
|
120
|
-
schedule_create: 'write',
|
|
121
|
-
schedule_update: 'write',
|
|
122
|
-
schedule_delete: 'write',
|
|
123
|
-
interrupt_agent: 'write',
|
|
124
|
-
cordis_run: 'write',
|
|
125
|
-
cordis_stop: 'write',
|
|
126
|
-
cordis_undefine: 'write',
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const VARIANT_MODE = {
|
|
130
|
-
web: 'web',
|
|
131
|
-
search: 'read',
|
|
132
|
-
read: 'read',
|
|
133
|
-
write: 'write',
|
|
134
|
-
edit: 'write',
|
|
135
|
-
bash: 'write',
|
|
136
|
-
code: 'write',
|
|
137
|
-
others: 'write',
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const NS = 'dsh-aurora';
|
|
141
|
-
const HOST_CLASS = 'dsh-aurora-host';
|
|
142
|
-
const FIBER_CLASS = NS + '__fiber';
|
|
33
|
+
const NS = 'dsh-stage';
|
|
34
|
+
const HOST_CLASS = 'dsh-stage-host';
|
|
143
35
|
const ROW_SELECTOR = '[data-tool][data-state]';
|
|
36
|
+
const STORAGE = { theme: 'dsh.conversation-aurora.theme' };
|
|
144
37
|
|
|
145
|
-
/* =================
|
|
38
|
+
/* ================= 样式 ================= */
|
|
146
39
|
|
|
147
40
|
const CSS = `
|
|
148
|
-
.dsh-
|
|
149
|
-
|
|
150
|
-
/* 图层根:所有视觉参数集中在这个块,README「调参」一节有对照表。 */
|
|
151
|
-
.dsh-aurora{
|
|
152
|
-
--fiber-width:1.5px;
|
|
153
|
-
--fiber-color:65,118,230; /* DeepSeek 蓝 #4176E6 */
|
|
154
|
-
--glow-a:65,118,230;
|
|
155
|
-
--glow-b:124,92,255; /* 柔紫 #7C5CFF */
|
|
156
|
-
--glow-c:46,211,183; /* 青 #2ED3B7 */
|
|
157
|
-
--glow-opacity:.22; /* 柔光强度:整条空白带的底色 */
|
|
158
|
-
--wash-color:78,98,150; /* 底光色:低饱和冷灰蓝,像一层浅浅的面板 */
|
|
159
|
-
--fiber-opacity:.16; /* 静息发丝亮度 */
|
|
160
|
-
--gesture-opacity:.5; /* 手势亮度:检索的亮段 / 写码的生长条 */
|
|
161
|
-
--pulse-opacity:.4; /* 联网时向外扩散的那圈光 */
|
|
162
|
-
--secondary-strength:.6; /* 副侧强度 */
|
|
163
|
-
--hud-ink:15,17,21; /* HUD 墨色:近黑,像印在纸上的仪器标记 */
|
|
164
|
-
--hud-opacity:1; /* HUD 层的总开关:角标 / 遥测 / 扫描纹 / 数据柱 */
|
|
165
|
-
--side-delay:0ms;
|
|
166
|
-
--crossfade:700ms;
|
|
167
|
-
--intensity:1;
|
|
168
|
-
--aurora-shift:0px; /* 滚动条造成的列偏移,由 JS 量出来 */
|
|
169
|
-
--drift-slow:40s; /* 两团柔光的周期,互不成整数比 */
|
|
170
|
-
--drift-fast:52s;
|
|
171
|
-
position:absolute;
|
|
172
|
-
inset:0;
|
|
173
|
-
z-index:8; /* 盖过输入区那条白色渐变,才能铺到窗口底边;拖宽手柄同为 8 且在 DOM 之后,仍在上面 */
|
|
174
|
-
overflow:hidden;
|
|
175
|
-
pointer-events:none;
|
|
176
|
-
transition:opacity 600ms ease, visibility 0s linear 0s;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/* 空闲 = 什么都没有:整层淡出并停掉动画,两侧回到普通白纸。
|
|
180
|
-
一旦有输出或工具在跑(think / read / web / write / wait / error)才重新浮出来。 */
|
|
181
|
-
.dsh-aurora[data-mode=idle]{
|
|
182
|
-
opacity:0;
|
|
183
|
-
visibility:hidden;
|
|
184
|
-
transition:opacity 600ms ease, visibility 0s linear 600ms;
|
|
185
|
-
}
|
|
186
|
-
.dsh-aurora[data-mode=idle] *{animation-play-state:paused}
|
|
187
|
-
|
|
188
|
-
.dsh-aurora[data-off]{display:none}
|
|
189
|
-
|
|
190
|
-
body[data-ds-dark-theme] .dsh-aurora{
|
|
191
|
-
--fiber-color:110,160,255;
|
|
192
|
-
--glow-a:110,160,255;
|
|
193
|
-
--glow-b:160,130,255;
|
|
194
|
-
--glow-c:80,225,200;
|
|
195
|
-
--glow-opacity:.3;
|
|
196
|
-
--wash-color:40,62,110;
|
|
197
|
-
--fiber-opacity:.22;
|
|
198
|
-
--gesture-opacity:.56;
|
|
199
|
-
--pulse-opacity:.46;
|
|
200
|
-
--hud-ink:190,214,255;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/* ---------- 翼:正文列之外的空白带,最外层的裁切框 ---------- */
|
|
204
|
-
|
|
205
|
-
.dsh-aurora__wing{
|
|
206
|
-
position:absolute;
|
|
207
|
-
top:0;
|
|
208
|
-
bottom:0;
|
|
209
|
-
overflow:hidden;
|
|
210
|
-
pointer-events:none;
|
|
211
|
-
}
|
|
212
|
-
.dsh-aurora__wing[data-side=left]{
|
|
213
|
-
left:0;
|
|
214
|
-
width:max(0px, calc((100% - var(--dsh-chat-content-width, 748px)) / 2 - var(--aurora-shift, 0px)));
|
|
215
|
-
--side:1;
|
|
216
|
-
--push:26px;
|
|
217
|
-
}
|
|
218
|
-
.dsh-aurora__wing[data-side=right]{
|
|
219
|
-
right:0;
|
|
220
|
-
width:max(0px, calc((100% - var(--dsh-chat-content-width, 748px)) / 2 + var(--aurora-shift, 0px)));
|
|
221
|
-
--side:1;
|
|
222
|
-
--push:-26px;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/* ---------- 柔光:铺满整条空白带,向列边界方向淡出 ---------- */
|
|
226
|
-
|
|
227
|
-
.dsh-aurora__glow{
|
|
228
|
-
position:absolute;
|
|
229
|
-
inset:0;
|
|
230
|
-
overflow:hidden;
|
|
231
|
-
opacity:calc(var(--glow-opacity) * var(--side) * var(--intensity));
|
|
232
|
-
transition:opacity var(--crossfade) ease;
|
|
233
|
-
}
|
|
234
|
-
.dsh-aurora__wing[data-side=left] .dsh-aurora__glow{
|
|
235
|
-
-webkit-mask-image:linear-gradient(to right,#000 0 55%,#0000 100%);
|
|
236
|
-
mask-image:linear-gradient(to right,#000 0 55%,#0000 100%);
|
|
237
|
-
}
|
|
238
|
-
.dsh-aurora__wing[data-side=right] .dsh-aurora__glow{
|
|
239
|
-
-webkit-mask-image:linear-gradient(to left,#000 0 55%,#0000 100%);
|
|
240
|
-
mask-image:linear-gradient(to left,#000 0 55%,#0000 100%);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/* 底光:贯穿整条带子的静态渐变,保证中间不会空掉 */
|
|
244
|
-
/* 底光:从窗口边淡向正文列,让空白带有一点纵深,但不变成一块色板 */
|
|
245
|
-
.dsh-aurora__wash{
|
|
246
|
-
position:absolute;
|
|
247
|
-
inset:0;
|
|
248
|
-
background:linear-gradient(to right,rgba(var(--wash-color),.5),rgba(var(--wash-color),0) 88%);
|
|
249
|
-
}
|
|
250
|
-
.dsh-aurora__wing[data-side=right] .dsh-aurora__wash{
|
|
251
|
-
background:linear-gradient(to left,rgba(var(--wash-color),.5),rgba(var(--wash-color),0) 88%);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/* 漂移光团:尺寸按色带自身的宽高算,画布再高也不会只剩两个角 */
|
|
255
|
-
.dsh-aurora__blob{
|
|
256
|
-
position:absolute;
|
|
257
|
-
border-radius:50%;
|
|
258
|
-
}
|
|
259
|
-
.dsh-aurora__blob[data-k=a]{
|
|
260
|
-
width:150%;
|
|
261
|
-
height:62%;
|
|
262
|
-
background:radial-gradient(ellipse at 50% 50%, rgba(var(--glow-a),.6), rgba(var(--glow-a),0) 68%);
|
|
263
|
-
}
|
|
264
|
-
.dsh-aurora__blob[data-k=b]{
|
|
265
|
-
width:165%;
|
|
266
|
-
height:56%;
|
|
267
|
-
background:radial-gradient(ellipse at 50% 50%, rgba(var(--glow-c),.55), rgba(var(--glow-c),0) 70%);
|
|
268
|
-
}
|
|
269
|
-
.dsh-aurora__wing[data-side=right] .dsh-aurora__blob[data-k=a]{background-image:radial-gradient(ellipse at 50% 50%, rgba(var(--glow-b),.62), rgba(var(--glow-b),0) 68%)}
|
|
270
|
-
.dsh-aurora__wing[data-side=right] .dsh-aurora__blob[data-k=b]{background-image:radial-gradient(ellipse at 50% 50%, rgba(var(--glow-a),.5), rgba(var(--glow-a),0) 70%)}
|
|
271
|
-
|
|
272
|
-
.dsh-aurora__wing[data-side=left] .dsh-aurora__blob[data-k=a]{top:-16%;left:-34%;animation:aurora-drift-a var(--drift-slow) ease-in-out infinite}
|
|
273
|
-
.dsh-aurora__wing[data-side=left] .dsh-aurora__blob[data-k=b]{bottom:-14%;right:-34%;animation:aurora-drift-b var(--drift-fast) ease-in-out infinite;animation-delay:-13s}
|
|
274
|
-
.dsh-aurora__wing[data-side=right] .dsh-aurora__blob[data-k=a]{top:-12%;right:-36%;animation:aurora-drift-b var(--drift-fast) ease-in-out infinite;animation-delay:-7s}
|
|
275
|
-
.dsh-aurora__wing[data-side=right] .dsh-aurora__blob[data-k=b]{bottom:-12%;left:-34%;animation:aurora-drift-a var(--drift-slow) ease-in-out infinite;animation-delay:-21s}
|
|
276
|
-
|
|
277
|
-
/* ---------- 列边光纤:所有手势的载体,离正文列 20px ---------- */
|
|
278
|
-
|
|
279
|
-
.dsh-aurora__fiber{
|
|
280
|
-
position:absolute;
|
|
281
|
-
top:0;
|
|
282
|
-
bottom:0;
|
|
283
|
-
width:var(--fiber-width);
|
|
284
|
-
animation-delay:var(--side-delay);
|
|
285
|
-
}
|
|
286
|
-
.dsh-aurora__wing[data-side=left] .dsh-aurora__fiber{right:20px}
|
|
287
|
-
.dsh-aurora__wing[data-side=right] .dsh-aurora__fiber{left:20px}
|
|
288
|
-
|
|
289
|
-
.dsh-aurora__fiber i{
|
|
290
|
-
position:absolute;
|
|
291
|
-
inset:0;
|
|
292
|
-
display:block;
|
|
293
|
-
transition:opacity var(--crossfade) ease;
|
|
294
|
-
animation-delay:var(--side-delay);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/* 静息发丝:几乎看不见,靠呼吸幅度让人察觉 */
|
|
298
|
-
.dsh-aurora__base{
|
|
299
|
-
opacity:calc(var(--fiber-opacity) * var(--side) * var(--intensity));
|
|
300
|
-
background:linear-gradient(to bottom,#0000,rgb(var(--fiber-color)) 12% 88%,#0000);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/* 检索:柔和的亮段向下流动(首尾都透明,循环处不会跳) */
|
|
304
|
-
.dsh-aurora__scan{
|
|
305
|
-
top:-56px;
|
|
306
|
-
bottom:-56px;
|
|
307
|
-
opacity:0;
|
|
308
|
-
background-image:repeating-linear-gradient(to bottom,
|
|
309
|
-
rgba(var(--fiber-color),0) 0px,
|
|
310
|
-
rgba(var(--fiber-color),0) 12px,
|
|
311
|
-
rgba(var(--fiber-color),.9) 20px,
|
|
312
|
-
rgba(var(--fiber-color),.9) 36px,
|
|
313
|
-
rgba(var(--fiber-color),0) 46px,
|
|
314
|
-
rgba(var(--fiber-color),0) 56px);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/* 写码:从下往上长出来 */
|
|
318
|
-
.dsh-aurora__fill{
|
|
319
|
-
opacity:0;
|
|
320
|
-
transform-origin:50% 100%;
|
|
321
|
-
background-image:linear-gradient(to top,rgb(var(--fiber-color)),#0000 80%);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/* 联网:向外扩散的一圈光 */
|
|
325
|
-
.dsh-aurora__pulse{
|
|
326
|
-
position:absolute;
|
|
327
|
-
top:50%;
|
|
328
|
-
width:280px;
|
|
329
|
-
height:280px;
|
|
330
|
-
margin-top:-140px;
|
|
331
|
-
border-radius:50%;
|
|
332
|
-
opacity:0;
|
|
333
|
-
pointer-events:none;
|
|
334
|
-
background:radial-gradient(circle,#0000 52%,rgba(var(--glow-b),1) 58%,#0000 66%);
|
|
335
|
-
animation-delay:var(--side-delay);
|
|
336
|
-
}
|
|
337
|
-
.dsh-aurora__wing[data-side=left] .dsh-aurora__pulse{
|
|
338
|
-
right:20px;
|
|
339
|
-
margin-right:-140px;
|
|
340
|
-
-webkit-mask-image:linear-gradient(to right,#000 0 52%,#0000 58%);
|
|
341
|
-
mask-image:linear-gradient(to right,#000 0 52%,#0000 58%);
|
|
342
|
-
}
|
|
343
|
-
.dsh-aurora__wing[data-side=right] .dsh-aurora__pulse{
|
|
344
|
-
left:20px;
|
|
345
|
-
margin-left:-140px;
|
|
346
|
-
-webkit-mask-image:linear-gradient(to left,#000 0 52%,#0000 58%);
|
|
347
|
-
mask-image:linear-gradient(to left,#000 0 52%,#0000 58%);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/* ---------- HUD:让两侧读起来像一块仪表,而不是一团渐变 ---------- */
|
|
351
|
-
|
|
352
|
-
/* 扫描纹:静态 CRT 质感,只铺在空白带里 */
|
|
353
|
-
.dsh-aurora__grid{
|
|
354
|
-
position:absolute;
|
|
355
|
-
inset:0;
|
|
356
|
-
background-image:
|
|
357
|
-
repeating-linear-gradient(to bottom,rgba(var(--hud-ink),.04) 0 1px,#0000 1px 4px),
|
|
358
|
-
repeating-linear-gradient(to right,rgba(var(--hud-ink),.05) 0 1px,#0000 1px 96px);
|
|
359
|
-
opacity:calc(var(--hud-opacity) * var(--side) * var(--intensity));
|
|
360
|
-
transition:opacity var(--crossfade) ease;
|
|
361
|
-
}
|
|
362
|
-
body[data-ds-dark-theme] .dsh-aurora__grid{
|
|
363
|
-
background-image:
|
|
364
|
-
repeating-linear-gradient(to bottom,rgba(var(--hud-ink),.05) 0 1px,#0000 1px 4px),
|
|
365
|
-
repeating-linear-gradient(to right,rgba(var(--hud-ink),.06) 0 1px,#0000 1px 96px);
|
|
366
|
-
}
|
|
367
|
-
.dsh-aurora__wing[data-side=left] .dsh-aurora__grid{
|
|
368
|
-
-webkit-mask-image:linear-gradient(to right,#000 0 60%,#0000 100%);
|
|
369
|
-
mask-image:linear-gradient(to right,#000 0 60%,#0000 100%);
|
|
370
|
-
}
|
|
371
|
-
.dsh-aurora__wing[data-side=right] .dsh-aurora__grid{
|
|
372
|
-
-webkit-mask-image:linear-gradient(to left,#000 0 60%,#0000 100%);
|
|
373
|
-
mask-image:linear-gradient(to left,#000 0 60%,#0000 100%);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/* 取景角标:把正文列框成一块"屏幕" */
|
|
377
|
-
.dsh-aurora__corner{
|
|
378
|
-
position:absolute;
|
|
379
|
-
width:20px;
|
|
380
|
-
height:20px;
|
|
381
|
-
border:0 solid rgba(var(--hud-ink),.55);
|
|
382
|
-
opacity:calc(var(--hud-opacity) * var(--side) * var(--intensity));
|
|
383
|
-
transition:opacity var(--crossfade) ease;
|
|
384
|
-
}
|
|
385
|
-
.dsh-aurora__wing[data-side=left] .dsh-aurora__corner{right:20px}
|
|
386
|
-
.dsh-aurora__wing[data-side=right] .dsh-aurora__corner{left:20px}
|
|
387
|
-
.dsh-aurora__wing[data-side=left] .dsh-aurora__corner[data-at=top]{top:14px;border-top-width:1.5px;border-right-width:1.5px}
|
|
388
|
-
.dsh-aurora__wing[data-side=left] .dsh-aurora__corner[data-at=bottom]{bottom:14px;border-bottom-width:1.5px;border-right-width:1.5px}
|
|
389
|
-
.dsh-aurora__wing[data-side=right] .dsh-aurora__corner[data-at=top]{top:14px;border-top-width:1.5px;border-left-width:1.5px}
|
|
390
|
-
.dsh-aurora__wing[data-side=right] .dsh-aurora__corner[data-at=bottom]{bottom:14px;border-bottom-width:1.5px;border-left-width:1.5px}
|
|
391
|
-
|
|
392
|
-
/* 扫描光带:一直在扫,有动作时更亮 */
|
|
393
|
-
.dsh-aurora__sweep{
|
|
394
|
-
position:absolute;
|
|
395
|
-
left:0;
|
|
396
|
-
right:0;
|
|
397
|
-
top:-10px;
|
|
398
|
-
height:1.5px;
|
|
399
|
-
opacity:calc(.22 * var(--hud-opacity) * var(--side) * var(--intensity));
|
|
400
|
-
background:linear-gradient(to right,rgba(var(--hud-ink),0),rgba(var(--hud-ink),.55),rgba(var(--hud-ink),0));
|
|
401
|
-
transition:opacity var(--crossfade) ease;
|
|
402
|
-
animation:aurora-sweep 3.4s linear infinite;
|
|
403
|
-
animation-delay:var(--side-delay);
|
|
404
|
-
}
|
|
405
|
-
.dsh-aurora[data-mode=think] .dsh-aurora__sweep,
|
|
406
|
-
.dsh-aurora[data-mode=read] .dsh-aurora__sweep,
|
|
407
|
-
.dsh-aurora[data-mode=web] .dsh-aurora__sweep,
|
|
408
|
-
.dsh-aurora[data-mode=write] .dsh-aurora__sweep{
|
|
409
|
-
opacity:calc(.55 * var(--hud-opacity) * var(--side) * var(--intensity));
|
|
410
|
-
}
|
|
411
|
-
.dsh-aurora[data-mode=wait] .dsh-aurora__sweep,
|
|
412
|
-
.dsh-aurora[data-mode=error] .dsh-aurora__sweep{opacity:0}
|
|
413
|
-
|
|
414
|
-
/* 数据柱:一直在流,只是"动手"时更亮更快 */
|
|
415
|
-
.dsh-aurora__bars{
|
|
416
|
-
position:absolute;
|
|
417
|
-
inset:0;
|
|
418
|
-
overflow:hidden;
|
|
419
|
-
opacity:calc(.45 * var(--hud-opacity) * var(--side) * var(--intensity));
|
|
420
|
-
transition:opacity var(--crossfade) ease;
|
|
421
|
-
}
|
|
422
|
-
.dsh-aurora[data-mode=write] .dsh-aurora__bars{
|
|
423
|
-
opacity:calc(.7 * var(--hud-opacity) * var(--side) * var(--intensity));
|
|
424
|
-
}
|
|
425
|
-
.dsh-aurora__bars i{
|
|
426
|
-
position:absolute;
|
|
427
|
-
bottom:-90px;
|
|
428
|
-
width:1px;
|
|
429
|
-
height:var(--h,42px);
|
|
430
|
-
background:repeating-linear-gradient(to top,rgba(var(--fiber-color),.85) 0 4px,#0000 4px 10px);
|
|
431
|
-
-webkit-mask-image:linear-gradient(to top,#0000,#000 18%,#000 82%,#0000);
|
|
432
|
-
mask-image:linear-gradient(to top,#0000,#000 18%,#000 82%,#0000);
|
|
433
|
-
animation:aurora-rise var(--dur,5s) linear infinite;
|
|
434
|
-
animation-delay:var(--del,0s);
|
|
435
|
-
}
|
|
436
|
-
.dsh-aurora[data-mode=write] .dsh-aurora__bars i{animation-duration:calc(var(--dur,5s) * .55)}
|
|
437
|
-
.dsh-aurora__bars i:nth-child(1){left:5%;--h:34px;--dur:4.6s;--del:-1.1s}
|
|
438
|
-
.dsh-aurora__bars i:nth-child(2){left:16%;--h:62px;--dur:6.2s;--del:-3.4s}
|
|
439
|
-
.dsh-aurora__bars i:nth-child(3){left:27%;--h:26px;--dur:3.9s;--del:-0.6s}
|
|
440
|
-
.dsh-aurora__bars i:nth-child(4){left:38%;--h:74px;--dur:7.1s;--del:-5.2s}
|
|
441
|
-
.dsh-aurora__bars i:nth-child(5){left:50%;--h:44px;--dur:5.4s;--del:-2.3s}
|
|
442
|
-
.dsh-aurora__bars i:nth-child(6){left:62%;--h:30px;--dur:4.2s;--del:-3.9s}
|
|
443
|
-
.dsh-aurora__bars i:nth-child(7){left:74%;--h:58px;--dur:6.6s;--del:-1.8s}
|
|
444
|
-
.dsh-aurora__bars i:nth-child(8){left:87%;--h:38px;--dur:5.1s;--del:-4.6s}
|
|
445
|
-
|
|
446
|
-
/* 遥测:竖排小字,读起来像设备读数 */
|
|
447
|
-
.dsh-aurora__telemetry{
|
|
448
|
-
position:absolute;
|
|
449
|
-
bottom:22px;
|
|
450
|
-
z-index:2;
|
|
451
|
-
font-family:ui-monospace,SFMono-Regular,Consolas,monospace;
|
|
452
|
-
font-size:9px;
|
|
453
|
-
line-height:1;
|
|
454
|
-
letter-spacing:.2em;
|
|
455
|
-
color:rgba(var(--hud-ink),.62);
|
|
456
|
-
writing-mode:vertical-rl;
|
|
457
|
-
white-space:nowrap;
|
|
458
|
-
opacity:0;
|
|
459
|
-
transition:opacity var(--crossfade) ease;
|
|
460
|
-
pointer-events:none;
|
|
461
|
-
}
|
|
462
|
-
.dsh-aurora__telemetry b{
|
|
463
|
-
font-weight:500;
|
|
464
|
-
display:inline-block;
|
|
465
|
-
padding:0 2px;
|
|
466
|
-
}
|
|
467
|
-
.dsh-aurora__telemetry b + b{margin-inline-start:.85em}
|
|
468
|
-
.dsh-aurora__telemetry .dsh-aurora__t-hex{color:rgba(var(--hud-ink),.34)}
|
|
469
|
-
.dsh-aurora__telemetry .dsh-aurora__t-cur{animation:aurora-blink 1.2s steps(2,start) infinite}
|
|
470
|
-
@keyframes aurora-blink{0%,100%{opacity:1}50%{opacity:.12}}
|
|
471
|
-
.dsh-aurora__wing[data-side=left] .dsh-aurora__telemetry{left:10px;transform:rotate(180deg)}
|
|
472
|
-
.dsh-aurora__wing[data-side=right] .dsh-aurora__telemetry{right:10px}
|
|
473
|
-
.dsh-aurora:not([data-mode=idle]) .dsh-aurora__telemetry{
|
|
474
|
-
opacity:calc(var(--hud-opacity) * var(--side) * var(--intensity));
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/* glitch:状态切换和活动期间偶发一次,青/品红分离 + 轻微错位 */
|
|
478
|
-
.dsh-aurora__fiber::before,
|
|
479
|
-
.dsh-aurora__fiber::after{
|
|
480
|
-
content:"";
|
|
481
|
-
position:absolute;
|
|
482
|
-
inset:0;
|
|
483
|
-
opacity:0;
|
|
484
|
-
pointer-events:none;
|
|
485
|
-
transition:opacity 90ms linear;
|
|
486
|
-
}
|
|
487
|
-
.dsh-aurora__fiber::before{
|
|
488
|
-
transform:translateX(-2px);
|
|
489
|
-
background:linear-gradient(to bottom,#0000,rgba(0,229,255,.95) 12% 88%,#0000);
|
|
490
|
-
}
|
|
491
|
-
.dsh-aurora__fiber::after{
|
|
492
|
-
transform:translateX(2px);
|
|
493
|
-
background:linear-gradient(to bottom,#0000,rgba(255,0,132,.9) 12% 88%,#0000);
|
|
494
|
-
}
|
|
495
|
-
.dsh-aurora[data-glitch] .dsh-aurora__fiber::before{opacity:.6}
|
|
496
|
-
.dsh-aurora[data-glitch] .dsh-aurora__fiber::after{opacity:.5}
|
|
497
|
-
.dsh-aurora[data-glitch] .dsh-aurora__corner,
|
|
498
|
-
.dsh-aurora[data-glitch] .dsh-aurora__bars{animation:aurora-jitter .14s steps(2,end) 1}
|
|
499
|
-
@keyframes aurora-jitter{
|
|
500
|
-
0%{transform:translate3d(0,0,0)}
|
|
501
|
-
50%{transform:translate3d(1.5px,0,0)}
|
|
502
|
-
100%{transform:translate3d(0,0,0)}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
/* ---------- 主侧 / 副侧:只改强度与相位,不改语义 ---------- */
|
|
506
|
-
|
|
507
|
-
.dsh-aurora[data-primary=left] .dsh-aurora__wing[data-side=right],
|
|
508
|
-
.dsh-aurora[data-primary=right] .dsh-aurora__wing[data-side=left]{
|
|
509
|
-
--side:var(--secondary-strength);
|
|
510
|
-
--side-delay:700ms;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
/* ---------- 手势 ---------- */
|
|
514
|
-
|
|
515
|
-
.dsh-aurora[data-mode=idle] .dsh-aurora__base{animation:aurora-breathe 9s ease-in-out infinite}
|
|
516
|
-
.dsh-aurora[data-mode=think] .dsh-aurora__base{animation:aurora-breathe 3s ease-in-out infinite}
|
|
517
|
-
|
|
518
|
-
.dsh-aurora[data-mode=read] .dsh-aurora__scan{
|
|
519
|
-
--g:calc(var(--gesture-opacity) * var(--side) * var(--intensity));
|
|
520
|
-
opacity:var(--g);
|
|
521
|
-
animation:aurora-scan 1.1s linear infinite;
|
|
522
|
-
}
|
|
523
|
-
.dsh-aurora[data-mode=write] .dsh-aurora__fill{
|
|
524
|
-
--g:calc(var(--gesture-opacity) * var(--side) * var(--intensity));
|
|
525
|
-
animation:aurora-build 1.2s ease-in-out infinite;
|
|
526
|
-
}
|
|
527
|
-
.dsh-aurora[data-mode=web] .dsh-aurora__pulse{
|
|
528
|
-
--g:calc(var(--pulse-opacity) * var(--side) * var(--intensity));
|
|
529
|
-
animation:aurora-pulse 1.6s ease-out infinite;
|
|
530
|
-
}
|
|
531
|
-
.dsh-aurora[data-mode=web] .dsh-aurora__base{animation:aurora-breathe 1.1s ease-in-out infinite}
|
|
532
|
-
|
|
533
|
-
/* ---------- 全局调制 ---------- */
|
|
534
|
-
|
|
535
|
-
.dsh-aurora[data-mode=wait]{
|
|
536
|
-
--glow-opacity:.018;
|
|
537
|
-
--fiber-opacity:.02;
|
|
538
|
-
--fiber-color:150,170,200;
|
|
539
|
-
}
|
|
540
|
-
.dsh-aurora[data-mode=wait] .dsh-aurora__base{animation:aurora-wait 4s ease-in-out infinite}
|
|
541
|
-
.dsh-aurora[data-mode=wait] .dsh-aurora__scan,
|
|
542
|
-
.dsh-aurora[data-mode=wait] .dsh-aurora__fill,
|
|
543
|
-
.dsh-aurora[data-mode=wait] .dsh-aurora__pulse{opacity:0;animation:none}
|
|
544
|
-
|
|
545
|
-
.dsh-aurora[data-mode=error],
|
|
546
|
-
.dsh-aurora[data-tint=error]{--fiber-color:229,72,77}
|
|
547
|
-
.dsh-aurora[data-mode=error]{--glow-a:229,72,77;--glow-b:229,72,77;--fiber-opacity:.08}
|
|
548
|
-
.dsh-aurora[data-mode=error] .dsh-aurora__base{animation:aurora-breathe 1.2s ease-in-out infinite}
|
|
549
|
-
|
|
550
|
-
/* ---------- 动画:只碰 transform / opacity ---------- */
|
|
551
|
-
|
|
552
|
-
@keyframes aurora-drift-a{
|
|
553
|
-
0%{transform:translate3d(0,0,0) scale(1)}
|
|
554
|
-
50%{transform:translate3d(7%,-9%,0) scale(1.12)}
|
|
555
|
-
100%{transform:translate3d(0,0,0) scale(1)}
|
|
556
|
-
}
|
|
557
|
-
@keyframes aurora-drift-b{
|
|
558
|
-
0%{transform:translate3d(0,0,0) scale(1.1)}
|
|
559
|
-
50%{transform:translate3d(-6%,7%,0) scale(.94)}
|
|
560
|
-
100%{transform:translate3d(0,0,0) scale(1.1)}
|
|
561
|
-
}
|
|
562
|
-
@keyframes aurora-breathe{
|
|
563
|
-
0%,100%{opacity:calc(var(--fiber-opacity) * var(--side) * var(--intensity) * 1.2)}
|
|
564
|
-
50%{opacity:calc(var(--fiber-opacity) * var(--side) * var(--intensity) * 3.2)}
|
|
565
|
-
}
|
|
566
|
-
@keyframes aurora-scan{
|
|
567
|
-
from{transform:translate3d(0,-56px,0)}
|
|
568
|
-
to{transform:translate3d(0,0,0)}
|
|
569
|
-
}
|
|
570
|
-
@keyframes aurora-build{
|
|
571
|
-
0%{transform:scaleY(0);opacity:var(--g)}
|
|
572
|
-
70%{transform:scaleY(1);opacity:var(--g)}
|
|
573
|
-
100%{transform:scaleY(1);opacity:0}
|
|
574
|
-
}
|
|
575
|
-
@keyframes aurora-pulse{
|
|
576
|
-
0%{transform:scale(.35);opacity:var(--g)}
|
|
577
|
-
70%{opacity:calc(var(--g) * .22)}
|
|
578
|
-
100%{transform:scale(1.7);opacity:0}
|
|
579
|
-
}
|
|
580
|
-
@keyframes aurora-wait{
|
|
581
|
-
0%,100%{transform:translate3d(0,0,0)}
|
|
582
|
-
50%{transform:translate3d(var(--push),0,0)}
|
|
583
|
-
}
|
|
584
|
-
@keyframes aurora-sweep{
|
|
585
|
-
from{transform:translate3d(0,0,0)}
|
|
586
|
-
to{transform:translate3d(0,106vh,0)}
|
|
587
|
-
}
|
|
588
|
-
@keyframes aurora-rise{
|
|
589
|
-
from{transform:translate3d(0,0,0)}
|
|
590
|
-
to{transform:translate3d(0,-125vh,0)}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
/* ---------- 降级 ---------- */
|
|
594
|
-
|
|
595
|
-
.dsh-aurora[data-paused],
|
|
596
|
-
.dsh-aurora[data-paused] *{animation-play-state:paused !important}
|
|
597
|
-
|
|
598
|
-
@media (prefers-reduced-motion:reduce){
|
|
599
|
-
.dsh-aurora,
|
|
600
|
-
.dsh-aurora *{animation:none !important;transition:none !important}
|
|
601
|
-
.dsh-stage{transition:none !important}
|
|
602
|
-
.dsh-aurora__glow{opacity:calc(var(--glow-opacity) * var(--side) * var(--intensity) * 1.5)}
|
|
603
|
-
.dsh-aurora__base{opacity:calc(var(--fiber-opacity) * var(--side) * var(--intensity) * 2)}
|
|
604
|
-
.dsh-aurora[data-mode=read] .dsh-aurora__scan{
|
|
605
|
-
opacity:calc(var(--gesture-opacity) * var(--side) * .6);
|
|
606
|
-
transform:translate3d(0,-28px,0);
|
|
607
|
-
}
|
|
608
|
-
.dsh-aurora[data-mode=write] .dsh-aurora__fill{
|
|
609
|
-
opacity:calc(var(--gesture-opacity) * var(--side) * .55);
|
|
610
|
-
transform:scaleY(.45);
|
|
611
|
-
}
|
|
612
|
-
.dsh-aurora[data-mode=web] .dsh-aurora__pulse{
|
|
613
|
-
opacity:calc(var(--pulse-opacity) * var(--side) * .7);
|
|
614
|
-
transform:scale(.9);
|
|
615
|
-
}
|
|
616
|
-
.dsh-aurora[data-mode=wait]{--glow-opacity:.02;--fiber-opacity:.02}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
/* ==========================================================================
|
|
620
|
-
* 预设 B:整块画布当背景(stage)
|
|
621
|
-
* 与预设 A 完全独立:自己的根元素、自己的 canvas、自己的开关,
|
|
622
|
-
* 互斥显示,所以 A 的任何行为都不会被这套影响。
|
|
623
|
-
* ========================================================================== */
|
|
41
|
+
.dsh-stage-host{position:relative;isolation:isolate}
|
|
624
42
|
|
|
625
43
|
.dsh-stage{
|
|
626
44
|
position:absolute;
|
|
627
45
|
inset:0;
|
|
628
|
-
z-index:-1; /*
|
|
46
|
+
z-index:-1; /* 在正文之下:文字永远压在背景上 */
|
|
629
47
|
overflow:hidden;
|
|
630
48
|
pointer-events:none;
|
|
631
49
|
opacity:0;
|
|
@@ -634,7 +52,7 @@ body[data-ds-dark-theme] .dsh-aurora__grid{
|
|
|
634
52
|
.dsh-stage[data-on]{opacity:1}
|
|
635
53
|
.dsh-stage canvas{position:absolute;inset:0;width:100%;height:100%;display:block}
|
|
636
54
|
|
|
637
|
-
/*
|
|
55
|
+
/* 中间的纸面:按正文列宽罩住文字区,两侧留亮 */
|
|
638
56
|
.dsh-stage__veil{
|
|
639
57
|
position:absolute;
|
|
640
58
|
inset:-1px;
|
|
@@ -655,21 +73,58 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
655
73
|
rgba(14,16,21,.4) calc(50% + var(--dsh-chat-content-width, 748px) / 2 + 110px),
|
|
656
74
|
rgba(14,16,21,0) 100%);
|
|
657
75
|
}
|
|
76
|
+
|
|
77
|
+
@media (prefers-reduced-motion:reduce){
|
|
78
|
+
.dsh-stage{transition:none !important}
|
|
79
|
+
}
|
|
658
80
|
`;
|
|
659
81
|
|
|
660
82
|
/* ================= 工具函数 ================= */
|
|
661
83
|
|
|
84
|
+
function readStore(key, fallback) {
|
|
85
|
+
try {
|
|
86
|
+
return window.localStorage.getItem(key) || fallback;
|
|
87
|
+
} catch {
|
|
88
|
+
return fallback;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function writeStore(key, value) {
|
|
93
|
+
try {
|
|
94
|
+
window.localStorage.setItem(key, value);
|
|
95
|
+
} catch {
|
|
96
|
+
/* 隐私模式下只在本次会话生效 */
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function hashString(text) {
|
|
101
|
+
let h = 2166136261;
|
|
102
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
103
|
+
h ^= text.charCodeAt(i);
|
|
104
|
+
h = Math.imul(h, 16777619);
|
|
105
|
+
}
|
|
106
|
+
return h >>> 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** 浅色用墨色 + multiply;深色用亮色 + lighter。 */
|
|
110
|
+
function stagePalette() {
|
|
111
|
+
const dark = document.body.hasAttribute('data-ds-dark-theme');
|
|
112
|
+
return dark
|
|
113
|
+
? { a: '120,190,255', b: '170,140,255', c: '110,235,215', bg: 'rgba(12,14,19,0.14)', blend: 'lighter' }
|
|
114
|
+
: { a: '38,78,205', b: '112,44,214', c: '10,132,120', bg: 'rgba(255,255,255,0.14)', blend: 'multiply' };
|
|
115
|
+
}
|
|
116
|
+
|
|
662
117
|
function addStyle() {
|
|
663
118
|
const style = document.createElement('style');
|
|
664
|
-
style.dataset.pluginCss = '
|
|
119
|
+
style.dataset.pluginCss = 'dsh-conversation-aurora/stage.css';
|
|
665
120
|
style.textContent = CSS;
|
|
666
121
|
document.head.appendChild(style);
|
|
667
122
|
return style;
|
|
668
123
|
}
|
|
669
124
|
|
|
670
125
|
/**
|
|
671
|
-
*
|
|
672
|
-
*
|
|
126
|
+
* 找对话画布:优先用宿主稳定的语义属性,同时存在多个时取可见面积最大的;
|
|
127
|
+
* 找不到就退回「从输入框往上找不透明背景」。
|
|
673
128
|
*/
|
|
674
129
|
function findCanvas() {
|
|
675
130
|
const all = document.querySelectorAll('[data-conversation-content]');
|
|
@@ -699,84 +154,17 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
699
154
|
return null;
|
|
700
155
|
}
|
|
701
156
|
|
|
702
|
-
function
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
const wing = (side) =>
|
|
709
|
-
'<div class="' + NS + '__wing" data-side="' + side + '">' +
|
|
710
|
-
'<div class="' + NS + '__glow"><i class="' + NS + '__wash"></i><i class="' + NS + '__blob" data-k="a"></i><i class="' + NS + '__blob" data-k="b"></i></div>' +
|
|
711
|
-
'<div class="' + NS + '__grid"></div>' +
|
|
712
|
-
'<div class="' + NS + '__bars"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>' +
|
|
713
|
-
'<div class="' + NS + '__sweep"></div>' +
|
|
714
|
-
'<div class="' + NS + '__corner" data-at="top"></div>' +
|
|
715
|
-
'<div class="' + NS + '__corner" data-at="bottom"></div>' +
|
|
716
|
-
'<div class="' + NS + '__telemetry">' +
|
|
717
|
-
'<b class="' + NS + '__t-tag"></b>' +
|
|
718
|
-
'<b class="' + NS + '__t-tool"></b>' +
|
|
719
|
-
'<b class="' + NS + '__t-hex"></b>' +
|
|
720
|
-
'<b class="' + NS + '__t-cur">\u258C</b>' +
|
|
721
|
-
'</div>' +
|
|
722
|
-
'<div class="' + FIBER_CLASS + '">' +
|
|
723
|
-
'<i class="' + NS + '__base"></i>' +
|
|
724
|
-
'<i class="' + NS + '__fill"></i>' +
|
|
725
|
-
'<i class="' + NS + '__scan"></i>' +
|
|
726
|
-
'</div>' +
|
|
727
|
-
'<div class="' + NS + '__pulse"></div>' +
|
|
728
|
-
'</div>';
|
|
729
|
-
layer.innerHTML = wing('left') + wing('right');
|
|
730
|
-
return layer;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
function toolMode(tool, variant) {
|
|
734
|
-
return TOOL_MODE[tool] || VARIANT_MODE[variant || ''] || 'read';
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
/* ================= 预设 B:整屏背景的主题库 ================= */
|
|
738
|
-
|
|
739
|
-
const STORAGE = {
|
|
740
|
-
preset: 'dsh.conversation-aurora.preset',
|
|
741
|
-
theme: 'dsh.conversation-aurora.theme',
|
|
742
|
-
};
|
|
743
|
-
|
|
744
|
-
function readStore(key, fallback) {
|
|
745
|
-
try {
|
|
746
|
-
return window.localStorage.getItem(key) || fallback;
|
|
747
|
-
} catch {
|
|
748
|
-
return fallback;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
function writeStore(key, value) {
|
|
753
|
-
try {
|
|
754
|
-
window.localStorage.setItem(key, value);
|
|
755
|
-
} catch {
|
|
756
|
-
/* 隐私模式等拿不到存储时,就只在本次会话生效 */
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
function hashString(text) {
|
|
761
|
-
let h = 2166136261;
|
|
762
|
-
for (let i = 0; i < text.length; i += 1) {
|
|
763
|
-
h ^= text.charCodeAt(i);
|
|
764
|
-
h = Math.imul(h, 16777619);
|
|
765
|
-
}
|
|
766
|
-
return h >>> 0;
|
|
157
|
+
function createStage() {
|
|
158
|
+
const el = document.createElement('div');
|
|
159
|
+
el.className = NS;
|
|
160
|
+
el.setAttribute('aria-hidden', 'true');
|
|
161
|
+
el.innerHTML = '<canvas></canvas><div class="' + NS + '__veil"></div>';
|
|
162
|
+
return el;
|
|
767
163
|
}
|
|
768
164
|
|
|
769
|
-
|
|
770
|
-
const dark = document.body.hasAttribute('data-ds-dark-theme');
|
|
771
|
-
// 浅色:用更饱和的“墨色”,配合 multiply(正片叠底)画在白纸上
|
|
772
|
-
// 深色:亮色 + lighter(加色),才会有发光感
|
|
773
|
-
return dark
|
|
774
|
-
? { a: '120,190,255', b: '170,140,255', c: '110,235,215', bg: 'rgba(12,14,19,0.14)', blend: 'lighter' }
|
|
775
|
-
: { a: '38,78,205', b: '112,44,214', c: '10,132,120', bg: 'rgba(255,255,255,0.14)', blend: 'multiply' };
|
|
776
|
-
}
|
|
165
|
+
/* ================= 主题库 ================= */
|
|
777
166
|
|
|
778
|
-
|
|
779
|
-
function themeWarp(g) {
|
|
167
|
+
function themeWarp(g) {
|
|
780
168
|
const spawn = (z) => ({
|
|
781
169
|
x: Math.random() * 2 - 1,
|
|
782
170
|
y: Math.random() * 2 - 1,
|
|
@@ -1349,24 +737,14 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1349
737
|
{ id: 'bubbles', name: '肥皂泡', create: themeBubbles },
|
|
1350
738
|
];
|
|
1351
739
|
|
|
1352
|
-
function createStage() {
|
|
1353
|
-
const el = document.createElement('div');
|
|
1354
|
-
el.className = 'dsh-stage';
|
|
1355
|
-
el.setAttribute('aria-hidden', 'true');
|
|
1356
|
-
el.innerHTML = '<canvas></canvas><div class="dsh-stage__veil"></div>';
|
|
1357
|
-
return el;
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
740
|
/* ================= 安装 / 拆卸 ================= */
|
|
1361
741
|
|
|
1362
742
|
function install() {
|
|
1363
743
|
if (typeof document === 'undefined' || typeof window === 'undefined') return function () {};
|
|
1364
744
|
|
|
1365
745
|
const style = addStyle();
|
|
1366
|
-
const layer = createLayer();
|
|
1367
746
|
const stage = createStage();
|
|
1368
747
|
const tracked = new Set();
|
|
1369
|
-
const errorSeen = new WeakSet();
|
|
1370
748
|
const media = window.matchMedia ? window.matchMedia('(prefers-reduced-motion: reduce)') : null;
|
|
1371
749
|
|
|
1372
750
|
let host = null;
|
|
@@ -1374,53 +752,124 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1374
752
|
let pending = 0;
|
|
1375
753
|
let disposed = false;
|
|
1376
754
|
let gaveUp = false;
|
|
1377
|
-
let primed = false;
|
|
1378
755
|
let booted = false;
|
|
1379
|
-
let
|
|
1380
|
-
let
|
|
1381
|
-
let errorUntil = 0;
|
|
756
|
+
let busy = false;
|
|
757
|
+
let idleTimer = 0;
|
|
1382
758
|
let remountCount = 0;
|
|
1383
759
|
let remountAt = 0;
|
|
1384
|
-
let intensity = 0;
|
|
1385
|
-
let errorTimer = 0;
|
|
1386
|
-
let currentTool = '';
|
|
1387
760
|
let sessionKey = null;
|
|
1388
|
-
let
|
|
1389
|
-
let preset = readStore(STORAGE.preset, 'edge') === 'stage' ? 'stage' : 'edge';
|
|
761
|
+
let quietUntil = 0;
|
|
1390
762
|
let forcedTheme = readStore(STORAGE.theme, '');
|
|
1391
763
|
let stageRaf = 0;
|
|
1392
764
|
let stageTheme = null;
|
|
1393
|
-
let hexTimer = 0;
|
|
1394
|
-
let hexSeed = 0x2f3a;
|
|
1395
|
-
let hexTicks = 0;
|
|
1396
|
-
let glitchTimer = 0;
|
|
1397
765
|
|
|
1398
766
|
const reduced = () => media !== null && media.matches;
|
|
1399
767
|
|
|
768
|
+
/* ---------- 挂载 ---------- */
|
|
769
|
+
|
|
770
|
+
function ensureHost() {
|
|
771
|
+
if (disposed || gaveUp) return;
|
|
772
|
+
if (host && host.isConnected && host.contains(stage)) return;
|
|
773
|
+
|
|
774
|
+
const canvas = findCanvas();
|
|
775
|
+
if (!canvas) return;
|
|
776
|
+
if (canvas === host && canvas.contains(stage)) return;
|
|
777
|
+
|
|
778
|
+
const now = Date.now();
|
|
779
|
+
if (now - remountAt > TUNE.remountWindowMs) {
|
|
780
|
+
remountAt = now;
|
|
781
|
+
remountCount = 0;
|
|
782
|
+
}
|
|
783
|
+
remountCount += 1;
|
|
784
|
+
if (remountCount > TUNE.remountGuard) {
|
|
785
|
+
gaveUp = true;
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const previous = host;
|
|
790
|
+
if (host && host !== canvas) host.classList.remove(HOST_CLASS);
|
|
791
|
+
host = canvas;
|
|
792
|
+
host.classList.add(HOST_CLASS);
|
|
793
|
+
if (stage.parentNode !== host) host.insertBefore(stage, host.firstChild);
|
|
794
|
+
if (previous !== canvas) resetForSwitch(previous !== null);
|
|
795
|
+
}
|
|
796
|
+
|
|
1400
797
|
/**
|
|
1401
|
-
*
|
|
1402
|
-
*
|
|
798
|
+
* 切会话:上一轮的工具行可能还挂着,历史里的失败行也会被当成新事件。
|
|
799
|
+
* 清空跟踪表、静默一小段时间,等新会话渲染稳定。
|
|
1403
800
|
*/
|
|
1404
|
-
function resetForSwitch(
|
|
801
|
+
function resetForSwitch(quiet) {
|
|
1405
802
|
tracked.clear();
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
803
|
+
busy = false;
|
|
804
|
+
if (idleTimer) {
|
|
805
|
+
window.clearTimeout(idleTimer);
|
|
806
|
+
idleTimer = 0;
|
|
807
|
+
}
|
|
808
|
+
quietUntil = quiet ? Date.now() + TUNE.switchQuietMs : 0;
|
|
1410
809
|
sessionKey = host ? host.getAttribute('data-conversation-session') || '' : null;
|
|
1411
|
-
|
|
1412
|
-
applyMode('idle');
|
|
1413
|
-
syncTelemetry('idle');
|
|
1414
|
-
syncStage('idle');
|
|
810
|
+
syncStage();
|
|
1415
811
|
}
|
|
1416
812
|
|
|
1417
|
-
/* ----------
|
|
813
|
+
/* ---------- 工具行跟踪 ---------- */
|
|
1418
814
|
|
|
1419
|
-
function
|
|
1420
|
-
|
|
1421
|
-
|
|
815
|
+
function track(node) {
|
|
816
|
+
if (!node || node.nodeType !== 1) return;
|
|
817
|
+
if (node.matches && node.matches(ROW_SELECTOR)) tracked.add(node);
|
|
818
|
+
if (!node.querySelectorAll) return;
|
|
819
|
+
const inner = node.querySelectorAll(ROW_SELECTOR);
|
|
820
|
+
for (let i = 0; i < inner.length; i += 1) tracked.add(inner[i]);
|
|
1422
821
|
}
|
|
1423
822
|
|
|
823
|
+
function prune() {
|
|
824
|
+
for (const el of tracked) {
|
|
825
|
+
if (!el.isConnected) tracked.delete(el);
|
|
826
|
+
}
|
|
827
|
+
while (tracked.size > TUNE.maxTracked) {
|
|
828
|
+
tracked.delete(tracked.values().next().value);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function seed() {
|
|
833
|
+
const scope = document.querySelector('[data-conversation-scroll]') || document;
|
|
834
|
+
const all = scope.querySelectorAll(ROW_SELECTOR);
|
|
835
|
+
const from = Math.max(0, all.length - TUNE.seedRows);
|
|
836
|
+
for (let i = from; i < all.length; i += 1) tracked.add(all[i]);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/* ---------- 状态判定:只关心"在不在干活" ---------- */
|
|
840
|
+
|
|
841
|
+
function detectBusy() {
|
|
842
|
+
for (const el of tracked) {
|
|
843
|
+
if (!el.isConnected) continue;
|
|
844
|
+
const state = el.getAttribute('data-state');
|
|
845
|
+
if (state === 'running' || state === 'preparing') return true;
|
|
846
|
+
}
|
|
847
|
+
return document.querySelector('[data-chat-running], [data-streaming]') !== null;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function setBusy(next) {
|
|
851
|
+
if (disposed) return;
|
|
852
|
+
if (next) {
|
|
853
|
+
if (idleTimer) {
|
|
854
|
+
window.clearTimeout(idleTimer);
|
|
855
|
+
idleTimer = 0;
|
|
856
|
+
}
|
|
857
|
+
if (!busy) {
|
|
858
|
+
busy = true;
|
|
859
|
+
syncStage();
|
|
860
|
+
}
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
if (!busy || idleTimer) return;
|
|
864
|
+
idleTimer = window.setTimeout(() => {
|
|
865
|
+
idleTimer = 0;
|
|
866
|
+
busy = false;
|
|
867
|
+
syncStage();
|
|
868
|
+
}, TUNE.holdIdleMs);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/* ---------- 背景播放 ---------- */
|
|
872
|
+
|
|
1424
873
|
function stageThemeId() {
|
|
1425
874
|
for (let i = 0; i < STAGE_THEMES.length; i += 1) {
|
|
1426
875
|
if (forcedTheme && STAGE_THEMES[i].id === forcedTheme) return forcedTheme;
|
|
@@ -1437,9 +886,10 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1437
886
|
|
|
1438
887
|
function startStage() {
|
|
1439
888
|
stopStage();
|
|
1440
|
-
if (!host
|
|
889
|
+
if (!host) return;
|
|
1441
890
|
const canvas = stage.querySelector('canvas');
|
|
1442
891
|
if (!canvas) return;
|
|
892
|
+
|
|
1443
893
|
const rect = host.getBoundingClientRect();
|
|
1444
894
|
const dpr = Math.min(2, window.devicePixelRatio || 1);
|
|
1445
895
|
const w = Math.max(1, Math.round(rect.width));
|
|
@@ -1449,6 +899,7 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1449
899
|
const ctx = canvas.getContext('2d');
|
|
1450
900
|
if (!ctx) return;
|
|
1451
901
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
902
|
+
|
|
1452
903
|
const pal = stagePalette();
|
|
1453
904
|
ctx.globalCompositeOperation = pal.blend;
|
|
1454
905
|
|
|
@@ -1459,10 +910,10 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1459
910
|
}
|
|
1460
911
|
stageTheme = factory.create({ ctx, w, h, pal });
|
|
1461
912
|
|
|
1462
|
-
//
|
|
913
|
+
// 预热若干帧:第一帧不空,减少动态效果时也有完整静止画面
|
|
1463
914
|
for (let i = 0; i < 24; i += 1) stageTheme.step(16);
|
|
1464
|
-
|
|
1465
915
|
if (reduced()) return;
|
|
916
|
+
|
|
1466
917
|
let last = 0;
|
|
1467
918
|
const frame = (now) => {
|
|
1468
919
|
if (!stageRaf) return;
|
|
@@ -1478,9 +929,9 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1478
929
|
stageRaf = window.requestAnimationFrame(frame);
|
|
1479
930
|
}
|
|
1480
931
|
|
|
1481
|
-
/** 只有"在干活"
|
|
1482
|
-
function syncStage(
|
|
1483
|
-
const want =
|
|
932
|
+
/** 只有"在干活 + 窗口可见"才播;其它情况彻底停下。 */
|
|
933
|
+
function syncStage() {
|
|
934
|
+
const want = busy && !document.hidden && !disposed && Date.now() >= quietUntil;
|
|
1484
935
|
if (!want) {
|
|
1485
936
|
delete stage.dataset.on;
|
|
1486
937
|
stopStage();
|
|
@@ -1490,13 +941,6 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1490
941
|
if (!stageRaf) startStage();
|
|
1491
942
|
}
|
|
1492
943
|
|
|
1493
|
-
function togglePreset() {
|
|
1494
|
-
preset = preset === 'edge' ? 'stage' : 'edge';
|
|
1495
|
-
writeStore(STORAGE.preset, preset);
|
|
1496
|
-
applyPresetClass();
|
|
1497
|
-
syncStage(currentMode);
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
944
|
function cycleTheme() {
|
|
1501
945
|
const ids = [];
|
|
1502
946
|
for (let i = 0; i < STAGE_THEMES.length; i += 1) ids.push(STAGE_THEMES[i].id);
|
|
@@ -1504,228 +948,13 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1504
948
|
if (at < 0) at = ids.indexOf(stageThemeId());
|
|
1505
949
|
forcedTheme = ids[(at + 1) % ids.length];
|
|
1506
950
|
writeStore(STORAGE.theme, forcedTheme);
|
|
1507
|
-
if (
|
|
951
|
+
if (stageRaf || busy) {
|
|
1508
952
|
stopStage();
|
|
1509
|
-
|
|
953
|
+
if (busy && !document.hidden) startStage();
|
|
1510
954
|
}
|
|
1511
955
|
}
|
|
1512
956
|
|
|
1513
|
-
/* ----------
|
|
1514
|
-
|
|
1515
|
-
/**
|
|
1516
|
-
* 正文列在滚动容器里居中,但滚动容器右侧有滚动条 + 2px 外边距,
|
|
1517
|
-
* 于是列中心比画布中心偏左一点。量出来补到 --aurora-shift,
|
|
1518
|
-
* 让左右光纤到列的距离真正相等。
|
|
1519
|
-
*/
|
|
1520
|
-
function updateShift() {
|
|
1521
|
-
if (!host) return;
|
|
1522
|
-
try {
|
|
1523
|
-
const scroller = host.querySelector('[data-conversation-scroll]');
|
|
1524
|
-
if (!scroller) return;
|
|
1525
|
-
const shift = (scroller.offsetWidth - scroller.clientWidth + 2) / 2;
|
|
1526
|
-
layer.style.setProperty('--aurora-shift', shift.toFixed(1) + 'px');
|
|
1527
|
-
} catch {
|
|
1528
|
-
/* 量不到就按 0 处理,最多差几像素 */
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
function ensureHost() {
|
|
1533
|
-
if (disposed || gaveUp) return;
|
|
1534
|
-
// 快路径:宿主还在、图层还挂着,就不要每帧重新找画布(避免强制布局)。
|
|
1535
|
-
if (host && host.isConnected && host.contains(layer)) return;
|
|
1536
|
-
|
|
1537
|
-
const canvas = findCanvas();
|
|
1538
|
-
if (!canvas) return;
|
|
1539
|
-
if (canvas === host && canvas.contains(layer)) return;
|
|
1540
|
-
|
|
1541
|
-
const now = Date.now();
|
|
1542
|
-
if (now - remountAt > TUNE.remountWindowMs) {
|
|
1543
|
-
remountAt = now;
|
|
1544
|
-
remountCount = 0;
|
|
1545
|
-
}
|
|
1546
|
-
remountCount += 1;
|
|
1547
|
-
if (remountCount > TUNE.remountGuard) {
|
|
1548
|
-
// 宿主在和我们抢节点:放弃,宁可没有效果也不要抖动。
|
|
1549
|
-
gaveUp = true;
|
|
1550
|
-
return;
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
const previous = host;
|
|
1554
|
-
if (host && host !== canvas) host.classList.remove(HOST_CLASS);
|
|
1555
|
-
host = canvas;
|
|
1556
|
-
host.classList.add(HOST_CLASS);
|
|
1557
|
-
if (layer.parentNode !== host) host.insertBefore(layer, host.firstChild);
|
|
1558
|
-
if (stage.parentNode !== host) host.insertBefore(stage, layer.nextSibling);
|
|
1559
|
-
updateShift();
|
|
1560
|
-
applyPresetClass();
|
|
1561
|
-
if (previous !== canvas) resetForSwitch(previous !== null);
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
/* ---------- 跟踪工具行 ---------- */
|
|
1565
|
-
|
|
1566
|
-
function track(node) {
|
|
1567
|
-
if (!node || node.nodeType !== 1) return;
|
|
1568
|
-
if (node.matches && node.matches(ROW_SELECTOR)) tracked.add(node);
|
|
1569
|
-
if (!node.querySelectorAll) return;
|
|
1570
|
-
const inner = node.querySelectorAll(ROW_SELECTOR);
|
|
1571
|
-
for (let i = 0; i < inner.length; i += 1) tracked.add(inner[i]);
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
function prune() {
|
|
1575
|
-
for (const el of tracked) {
|
|
1576
|
-
if (!el.isConnected) tracked.delete(el);
|
|
1577
|
-
}
|
|
1578
|
-
while (tracked.size > TUNE.maxTracked) {
|
|
1579
|
-
tracked.delete(tracked.values().next().value);
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
function seed() {
|
|
1584
|
-
const scope = document.querySelector('[data-conversation-scroll]') || document;
|
|
1585
|
-
const all = scope.querySelectorAll(ROW_SELECTOR);
|
|
1586
|
-
const from = Math.max(0, all.length - TUNE.seedRows);
|
|
1587
|
-
for (let i = from; i < all.length; i += 1) tracked.add(all[i]);
|
|
1588
|
-
}
|
|
1589
|
-
|
|
1590
|
-
/* ---------- 状态判定 ---------- */
|
|
1591
|
-
|
|
1592
|
-
function detectError() {
|
|
1593
|
-
let fresh = false;
|
|
1594
|
-
for (const el of tracked) {
|
|
1595
|
-
if (!el.isConnected) continue;
|
|
1596
|
-
const state = el.getAttribute('data-state');
|
|
1597
|
-
if (state !== 'error' && state !== 'stopped') continue;
|
|
1598
|
-
if (errorSeen.has(el)) continue;
|
|
1599
|
-
errorSeen.add(el);
|
|
1600
|
-
if (primed) fresh = true;
|
|
1601
|
-
}
|
|
1602
|
-
return fresh;
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
function resolveMode() {
|
|
1606
|
-
currentTool = '';
|
|
1607
|
-
const rows = [];
|
|
1608
|
-
for (const el of tracked) if (el.isConnected) rows.push(el);
|
|
1609
|
-
for (let i = rows.length - 1; i >= 0; i -= 1) {
|
|
1610
|
-
const el = rows[i];
|
|
1611
|
-
const state = el.getAttribute('data-state');
|
|
1612
|
-
if (state !== 'running' && state !== 'preparing') continue;
|
|
1613
|
-
const tool = el.getAttribute('data-tool') || '';
|
|
1614
|
-
currentTool = tool;
|
|
1615
|
-
if (tool === 'ask_user_question') return 'wait';
|
|
1616
|
-
return toolMode(tool, el.getAttribute('data-variant'));
|
|
1617
|
-
}
|
|
1618
|
-
if (document.querySelector('[data-chat-running], [data-streaming]')) return 'think';
|
|
1619
|
-
return 'idle';
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
function applyMode(next) {
|
|
1623
|
-
if (disposed || !host) return;
|
|
1624
|
-
const now = Date.now();
|
|
1625
|
-
if (next === currentMode) return;
|
|
1626
|
-
if (now - lastSwitch < TUNE.minDwellMs && MODE_PRIORITY[next] <= MODE_PRIORITY[currentMode]) {
|
|
1627
|
-
// 被最短驻留挡住时,补一次定时重判,否则可能一直停在旧状态(没有后续 DOM 变更)
|
|
1628
|
-
if (!pending) {
|
|
1629
|
-
pending = window.setTimeout(scan, TUNE.minDwellMs - (now - lastSwitch) + 10);
|
|
1630
|
-
}
|
|
1631
|
-
return;
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
currentMode = next;
|
|
1635
|
-
lastSwitch = now;
|
|
1636
|
-
layer.dataset.mode = next;
|
|
1637
|
-
layer.dataset.primary = MODE_PRIMARY[next] || 'both';
|
|
1638
|
-
|
|
1639
|
-
if (next === 'error') layer.dataset.tint = 'error';
|
|
1640
|
-
else if (next !== 'idle') delete layer.dataset.tint;
|
|
1641
|
-
|
|
1642
|
-
if (next !== 'idle') flash();
|
|
1643
|
-
syncStage(next);
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
|
-
/** 一次 130ms 的 glitch:光纤青/品红分离 + HUD 轻微错位。 */
|
|
1647
|
-
function glitch() {
|
|
1648
|
-
if (disposed || document.hidden || reduced() || layer.dataset.off === '1') return;
|
|
1649
|
-
layer.dataset.glitch = '1';
|
|
1650
|
-
if (glitchTimer) window.clearTimeout(glitchTimer);
|
|
1651
|
-
glitchTimer = window.setTimeout(() => {
|
|
1652
|
-
glitchTimer = 0;
|
|
1653
|
-
delete layer.dataset.glitch;
|
|
1654
|
-
}, 130);
|
|
1655
|
-
}
|
|
1656
|
-
|
|
1657
|
-
/** 遥测里的十六进制读数:有动作时才跳,静止时一个定时器都不留。 */
|
|
1658
|
-
function startHex() {
|
|
1659
|
-
if (hexTimer || document.hidden) return;
|
|
1660
|
-
const el = layer.querySelector('.' + NS + '__t-hex');
|
|
1661
|
-
if (!el) return;
|
|
1662
|
-
const paint = () => {
|
|
1663
|
-
hexSeed = (hexSeed * 1103515245 + 12345) & 0x7fffffff;
|
|
1664
|
-
el.textContent = '0x' + (hexSeed & 0xffff).toString(16).toUpperCase().padStart(4, '0');
|
|
1665
|
-
hexTicks += 1;
|
|
1666
|
-
if (hexTicks % 10 === 0) glitch();
|
|
1667
|
-
};
|
|
1668
|
-
paint();
|
|
1669
|
-
hexTimer = window.setInterval(paint, 600);
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
function stopHex() {
|
|
1673
|
-
if (!hexTimer) return;
|
|
1674
|
-
window.clearInterval(hexTimer);
|
|
1675
|
-
hexTimer = 0;
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
/** 同步竖排遥测文字:只在内容真的变了时才写 DOM。 */
|
|
1679
|
-
function syncTelemetry(mode) {
|
|
1680
|
-
try {
|
|
1681
|
-
const tag = layer.querySelector('.' + NS + '__t-tag');
|
|
1682
|
-
const toolEl = layer.querySelector('.' + NS + '__t-tool');
|
|
1683
|
-
const tagText = MODE_TAG[mode] || '';
|
|
1684
|
-
const toolText = mode === 'idle' || mode === 'wait' ? '' : String(currentTool || '').slice(0, 14).toUpperCase();
|
|
1685
|
-
if (tag && tag.textContent !== tagText) tag.textContent = tagText;
|
|
1686
|
-
if (toolEl && toolEl.textContent !== toolText) toolEl.textContent = toolText;
|
|
1687
|
-
if (mode === 'idle') stopHex();
|
|
1688
|
-
else startHex();
|
|
1689
|
-
} catch {
|
|
1690
|
-
/* 遥测只是装饰,失败不影响主效果 */
|
|
1691
|
-
}
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
/** 状态切换时给光纤一次单次亮脉冲(WAAPI,不占常驻动画)。 */
|
|
1695
|
-
function flash() {
|
|
1696
|
-
if (!host || document.hidden || reduced() || layer.dataset.off === '1') return;
|
|
1697
|
-
glitch();
|
|
1698
|
-
const bases = layer.querySelectorAll('.' + NS + '__base');
|
|
1699
|
-
for (let i = 0; i < bases.length; i += 1) {
|
|
1700
|
-
const base = bases[i];
|
|
1701
|
-
const from = Number.parseFloat(window.getComputedStyle(base).opacity) || 0;
|
|
1702
|
-
const peak = Math.min(1, from + TUNE.pulseRise);
|
|
1703
|
-
try {
|
|
1704
|
-
const anim = base.animate(
|
|
1705
|
-
[{ opacity: String(from) }, { opacity: String(peak) }, { opacity: String(from) }],
|
|
1706
|
-
{ duration: TUNE.pulseMs, easing: 'cubic-bezier(.2,.7,.2,1)' },
|
|
1707
|
-
);
|
|
1708
|
-
anim.addEventListener('finish', () => anim.cancel());
|
|
1709
|
-
} catch {
|
|
1710
|
-
/* 老引擎不支持 WAAPI 时不闪,不影响其它状态 */
|
|
1711
|
-
}
|
|
1712
|
-
}
|
|
1713
|
-
// 角标一次轻微位移:像镜头重新对上焦
|
|
1714
|
-
const corners = layer.querySelectorAll('.' + NS + '__corner');
|
|
1715
|
-
for (let i = 0; i < corners.length; i += 1) {
|
|
1716
|
-
try {
|
|
1717
|
-
const anim = corners[i].animate(
|
|
1718
|
-
[{ transform: 'translateX(0)' }, { transform: 'translateX(2px)' }, { transform: 'translateX(0)' }],
|
|
1719
|
-
{ duration: 150, easing: 'ease-out' },
|
|
1720
|
-
);
|
|
1721
|
-
anim.addEventListener('finish', () => anim.cancel());
|
|
1722
|
-
} catch {
|
|
1723
|
-
/* 同上 */
|
|
1724
|
-
}
|
|
1725
|
-
}
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
/* ---------- 每帧扫描 ---------- */
|
|
957
|
+
/* ---------- 扫描 ---------- */
|
|
1729
958
|
|
|
1730
959
|
function scan() {
|
|
1731
960
|
pending = 0;
|
|
@@ -1734,44 +963,24 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1734
963
|
ensureHost();
|
|
1735
964
|
if (!host) return;
|
|
1736
965
|
|
|
1737
|
-
// 切换会话:等新会话挂载稳定再判断,避免把上一轮/历史行当成"正在工作"
|
|
1738
966
|
const key = host.getAttribute('data-conversation-session') || '';
|
|
1739
967
|
if (sessionKey === null) sessionKey = key;
|
|
1740
968
|
else if (key !== sessionKey) {
|
|
1741
969
|
resetForSwitch(true);
|
|
1742
970
|
return;
|
|
1743
971
|
}
|
|
1744
|
-
if (Date.now() <
|
|
1745
|
-
|
|
1746
|
-
syncTelemetry('idle');
|
|
972
|
+
if (Date.now() < quietUntil) {
|
|
973
|
+
syncStage();
|
|
1747
974
|
return;
|
|
1748
975
|
}
|
|
1749
976
|
|
|
1750
977
|
prune();
|
|
1751
|
-
|
|
1752
|
-
const now = Date.now();
|
|
1753
|
-
if (fresh) {
|
|
1754
|
-
errorUntil = now + TUNE.errorHoldMs;
|
|
1755
|
-
// 如果没有后续变更,红光也必须自己退掉,否则会一直停在错误态。
|
|
1756
|
-
if (errorTimer) window.clearTimeout(errorTimer);
|
|
1757
|
-
errorTimer = window.setTimeout(() => {
|
|
1758
|
-
errorTimer = 0;
|
|
1759
|
-
requestScan();
|
|
1760
|
-
}, TUNE.errorHoldMs + 40);
|
|
1761
|
-
}
|
|
1762
|
-
primed = true;
|
|
1763
|
-
const mode = now < errorUntil ? 'error' : resolveMode();
|
|
1764
|
-
applyMode(mode);
|
|
1765
|
-
syncTelemetry(mode);
|
|
978
|
+
setBusy(detectBusy());
|
|
1766
979
|
} catch {
|
|
1767
980
|
/* 任何一步失败都只是不生效,不影响宿主 */
|
|
1768
981
|
}
|
|
1769
982
|
}
|
|
1770
983
|
|
|
1771
|
-
/**
|
|
1772
|
-
* 用短定时器而不是 rAF 合并高频变更:状态判定不需要帧精度,
|
|
1773
|
-
* 而 rAF 在窗口被遮挡 / 无头环境里会被节流甚至停摆,导致状态卡住。
|
|
1774
|
-
*/
|
|
1775
984
|
function requestScan() {
|
|
1776
985
|
if (disposed || pending) return;
|
|
1777
986
|
pending = window.setTimeout(scan, 48);
|
|
@@ -1804,37 +1013,24 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1804
1013
|
|
|
1805
1014
|
function onVisibility() {
|
|
1806
1015
|
if (document.hidden) {
|
|
1807
|
-
layer.dataset.paused = '1';
|
|
1808
|
-
stopHex();
|
|
1809
1016
|
stopStage();
|
|
1810
1017
|
} else {
|
|
1811
|
-
|
|
1018
|
+
syncStage();
|
|
1812
1019
|
requestScan();
|
|
1813
|
-
syncStage(currentMode);
|
|
1814
1020
|
}
|
|
1815
1021
|
}
|
|
1816
1022
|
|
|
1817
1023
|
function onResize() {
|
|
1818
|
-
|
|
1024
|
+
if (stageRaf) {
|
|
1025
|
+
stopStage();
|
|
1026
|
+
startStage();
|
|
1027
|
+
}
|
|
1819
1028
|
}
|
|
1820
1029
|
|
|
1821
1030
|
function onKeyDown(event) {
|
|
1822
1031
|
if (!event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
togglePreset();
|
|
1826
|
-
return;
|
|
1827
|
-
}
|
|
1828
|
-
if (key === 't') {
|
|
1829
|
-
cycleTheme();
|
|
1830
|
-
return;
|
|
1831
|
-
}
|
|
1832
|
-
if (key !== TUNE.keyCode) return;
|
|
1833
|
-
intensity = (intensity + 1) % TUNE.intensity.length;
|
|
1834
|
-
const value = TUNE.intensity[intensity];
|
|
1835
|
-
layer.style.setProperty('--intensity', String(value));
|
|
1836
|
-
if (value === 0) layer.dataset.off = '1';
|
|
1837
|
-
else delete layer.dataset.off;
|
|
1032
|
+
if (String(event.key || '').toLowerCase() !== TUNE.themeKey) return;
|
|
1033
|
+
cycleTheme();
|
|
1838
1034
|
}
|
|
1839
1035
|
|
|
1840
1036
|
/* ---------- 启动 ---------- */
|
|
@@ -1843,11 +1039,10 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1843
1039
|
if (disposed || booted) return;
|
|
1844
1040
|
booted = true;
|
|
1845
1041
|
ensureHost();
|
|
1846
|
-
applyPresetClass();
|
|
1847
1042
|
try {
|
|
1848
1043
|
seed();
|
|
1849
1044
|
} catch {
|
|
1850
|
-
/*
|
|
1045
|
+
/* 会话还没渲染出来时没有可跟踪的行 */
|
|
1851
1046
|
}
|
|
1852
1047
|
requestScan();
|
|
1853
1048
|
observe();
|
|
@@ -1866,9 +1061,7 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1866
1061
|
if (disposed) return;
|
|
1867
1062
|
disposed = true;
|
|
1868
1063
|
if (pending) window.clearTimeout(pending);
|
|
1869
|
-
if (
|
|
1870
|
-
if (glitchTimer) window.clearTimeout(glitchTimer);
|
|
1871
|
-
stopHex();
|
|
1064
|
+
if (idleTimer) window.clearTimeout(idleTimer);
|
|
1872
1065
|
if (observer) observer.disconnect();
|
|
1873
1066
|
document.removeEventListener('DOMContentLoaded', boot);
|
|
1874
1067
|
document.removeEventListener('visibilitychange', onVisibility);
|
|
@@ -1876,7 +1069,6 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1876
1069
|
window.removeEventListener('keydown', onKeyDown, true);
|
|
1877
1070
|
stopStage();
|
|
1878
1071
|
if (stage.parentNode) stage.parentNode.removeChild(stage);
|
|
1879
|
-
if (layer.parentNode) layer.parentNode.removeChild(layer);
|
|
1880
1072
|
if (host) host.classList.remove(HOST_CLASS);
|
|
1881
1073
|
style.remove();
|
|
1882
1074
|
};
|
|
@@ -1885,9 +1077,8 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1885
1077
|
/* ================= 插件入口 ================= */
|
|
1886
1078
|
|
|
1887
1079
|
/**
|
|
1888
|
-
* Alt+
|
|
1889
|
-
*
|
|
1890
|
-
* theme 服务不在时不激活这个键,插件其余部分不受影响。
|
|
1080
|
+
* Alt+T 换背景主题。
|
|
1081
|
+
* Alt+D 切浅色/深色:走宿主 theme 服务,不直接改 body,和「设置 → 外观」始终一致。
|
|
1891
1082
|
*/
|
|
1892
1083
|
function bindThemeHotkey(ctx) {
|
|
1893
1084
|
if (!ctx || typeof ctx.inject !== 'function') return;
|