dsh-conversation-aurora 0.1.0 → 0.2.1
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 +203 -1009
- 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',
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
/** 遥测小字用的短标签。 */
|
|
65
|
-
const MODE_TAG = {
|
|
66
|
-
idle: 'STANDBY',
|
|
67
|
-
think: 'THINK',
|
|
68
|
-
read: 'SCAN',
|
|
69
|
-
web: 'NET',
|
|
70
|
-
write: 'BUILD',
|
|
71
|
-
wait: 'HOLD',
|
|
72
|
-
error: 'ERR',
|
|
29
|
+
/** 切会话后的静默期,避免把上一轮的残留行当成"正在工作"。 */
|
|
30
|
+
switchQuietMs: 600,
|
|
73
31
|
};
|
|
74
32
|
|
|
75
|
-
|
|
76
|
-
const
|
|
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,49 @@ 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
|
+
/** 浅色用墨色 + multiply;深色用亮色 + lighter。 */
|
|
101
|
+
function stagePalette() {
|
|
102
|
+
const dark = document.body.hasAttribute('data-ds-dark-theme');
|
|
103
|
+
return dark
|
|
104
|
+
? { a: '120,190,255', b: '170,140,255', c: '110,235,215', bg: 'rgba(12,14,19,0.14)', blend: 'lighter' }
|
|
105
|
+
: { a: '38,78,205', b: '112,44,214', c: '10,132,120', bg: 'rgba(255,255,255,0.14)', blend: 'multiply' };
|
|
106
|
+
}
|
|
107
|
+
|
|
662
108
|
function addStyle() {
|
|
663
109
|
const style = document.createElement('style');
|
|
664
|
-
style.dataset.pluginCss = '
|
|
110
|
+
style.dataset.pluginCss = 'dsh-conversation-aurora/stage.css';
|
|
665
111
|
style.textContent = CSS;
|
|
666
112
|
document.head.appendChild(style);
|
|
667
113
|
return style;
|
|
668
114
|
}
|
|
669
115
|
|
|
670
116
|
/**
|
|
671
|
-
*
|
|
672
|
-
*
|
|
117
|
+
* 找对话画布:优先用宿主稳定的语义属性,同时存在多个时取可见面积最大的;
|
|
118
|
+
* 找不到就退回「从输入框往上找不透明背景」。
|
|
673
119
|
*/
|
|
674
120
|
function findCanvas() {
|
|
675
121
|
const all = document.querySelectorAll('[data-conversation-content]');
|
|
@@ -699,84 +145,17 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
699
145
|
return null;
|
|
700
146
|
}
|
|
701
147
|
|
|
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
|
-
}
|
|
148
|
+
function createStage() {
|
|
149
|
+
const el = document.createElement('div');
|
|
150
|
+
el.className = NS;
|
|
151
|
+
el.setAttribute('aria-hidden', 'true');
|
|
152
|
+
el.innerHTML = '<canvas></canvas><div class="' + NS + '__veil"></div>';
|
|
153
|
+
return el;
|
|
758
154
|
}
|
|
759
155
|
|
|
760
|
-
|
|
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;
|
|
767
|
-
}
|
|
156
|
+
/* ================= 主题库 ================= */
|
|
768
157
|
|
|
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
|
-
}
|
|
777
|
-
|
|
778
|
-
/** 星空跃迁:星点从中心向外加速飞出。 */
|
|
779
|
-
function themeWarp(g) {
|
|
158
|
+
function themeWarp(g) {
|
|
780
159
|
const spawn = (z) => ({
|
|
781
160
|
x: Math.random() * 2 - 1,
|
|
782
161
|
y: Math.random() * 2 - 1,
|
|
@@ -1349,24 +728,14 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1349
728
|
{ id: 'bubbles', name: '肥皂泡', create: themeBubbles },
|
|
1350
729
|
];
|
|
1351
730
|
|
|
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
731
|
/* ================= 安装 / 拆卸 ================= */
|
|
1361
732
|
|
|
1362
733
|
function install() {
|
|
1363
734
|
if (typeof document === 'undefined' || typeof window === 'undefined') return function () {};
|
|
1364
735
|
|
|
1365
736
|
const style = addStyle();
|
|
1366
|
-
const layer = createLayer();
|
|
1367
737
|
const stage = createStage();
|
|
1368
738
|
const tracked = new Set();
|
|
1369
|
-
const errorSeen = new WeakSet();
|
|
1370
739
|
const media = window.matchMedia ? window.matchMedia('(prefers-reduced-motion: reduce)') : null;
|
|
1371
740
|
|
|
1372
741
|
let host = null;
|
|
@@ -1374,169 +743,29 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1374
743
|
let pending = 0;
|
|
1375
744
|
let disposed = false;
|
|
1376
745
|
let gaveUp = false;
|
|
1377
|
-
let primed = false;
|
|
1378
746
|
let booted = false;
|
|
1379
|
-
let
|
|
1380
|
-
let
|
|
1381
|
-
let errorUntil = 0;
|
|
747
|
+
let busy = false;
|
|
748
|
+
let idleTimer = 0;
|
|
1382
749
|
let remountCount = 0;
|
|
1383
750
|
let remountAt = 0;
|
|
1384
|
-
let intensity = 0;
|
|
1385
|
-
let errorTimer = 0;
|
|
1386
|
-
let currentTool = '';
|
|
1387
751
|
let sessionKey = null;
|
|
1388
|
-
let
|
|
1389
|
-
let preset = readStore(STORAGE.preset, 'edge') === 'stage' ? 'stage' : 'edge';
|
|
752
|
+
let quietUntil = 0;
|
|
1390
753
|
let forcedTheme = readStore(STORAGE.theme, '');
|
|
754
|
+
let activeTheme = '';
|
|
1391
755
|
let stageRaf = 0;
|
|
1392
756
|
let stageTheme = null;
|
|
1393
|
-
let hexTimer = 0;
|
|
1394
|
-
let hexSeed = 0x2f3a;
|
|
1395
|
-
let hexTicks = 0;
|
|
1396
|
-
let glitchTimer = 0;
|
|
1397
757
|
|
|
1398
758
|
const reduced = () => media !== null && media.matches;
|
|
1399
759
|
|
|
1400
|
-
/**
|
|
1401
|
-
* 切换会话:上一轮的工具行可能还挂在 DOM 上,历史里的失败行也会被当成"新事件"。
|
|
1402
|
-
* 这里把跟踪表清空、重新"预热",并静默一小段时间,等宿主把新会话渲染稳定。
|
|
1403
|
-
*/
|
|
1404
|
-
function resetForSwitch(withSuppress) {
|
|
1405
|
-
tracked.clear();
|
|
1406
|
-
primed = false;
|
|
1407
|
-
errorUntil = 0;
|
|
1408
|
-
currentTool = '';
|
|
1409
|
-
suppressUntil = withSuppress ? Date.now() + 700 : 0;
|
|
1410
|
-
sessionKey = host ? host.getAttribute('data-conversation-session') || '' : null;
|
|
1411
|
-
currentMode = '';
|
|
1412
|
-
applyMode('idle');
|
|
1413
|
-
syncTelemetry('idle');
|
|
1414
|
-
syncStage('idle');
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
/* ---------- 预设 B:整屏背景 ---------- */
|
|
1418
|
-
|
|
1419
|
-
function applyPresetClass() {
|
|
1420
|
-
layer.style.display = preset === 'edge' ? '' : 'none';
|
|
1421
|
-
stage.style.display = preset === 'stage' ? '' : 'none';
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
function stageThemeId() {
|
|
1425
|
-
for (let i = 0; i < STAGE_THEMES.length; i += 1) {
|
|
1426
|
-
if (forcedTheme && STAGE_THEMES[i].id === forcedTheme) return forcedTheme;
|
|
1427
|
-
}
|
|
1428
|
-
const key = (host && host.getAttribute('data-conversation-session')) || 'default';
|
|
1429
|
-
return STAGE_THEMES[hashString(key) % STAGE_THEMES.length].id;
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
function stopStage() {
|
|
1433
|
-
if (stageRaf) window.cancelAnimationFrame(stageRaf);
|
|
1434
|
-
stageRaf = 0;
|
|
1435
|
-
stageTheme = null;
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
function startStage() {
|
|
1439
|
-
stopStage();
|
|
1440
|
-
if (!host || preset !== 'stage') return;
|
|
1441
|
-
const canvas = stage.querySelector('canvas');
|
|
1442
|
-
if (!canvas) return;
|
|
1443
|
-
const rect = host.getBoundingClientRect();
|
|
1444
|
-
const dpr = Math.min(2, window.devicePixelRatio || 1);
|
|
1445
|
-
const w = Math.max(1, Math.round(rect.width));
|
|
1446
|
-
const h = Math.max(1, Math.round(rect.height));
|
|
1447
|
-
canvas.width = Math.round(w * dpr);
|
|
1448
|
-
canvas.height = Math.round(h * dpr);
|
|
1449
|
-
const ctx = canvas.getContext('2d');
|
|
1450
|
-
if (!ctx) return;
|
|
1451
|
-
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
1452
|
-
const pal = stagePalette();
|
|
1453
|
-
ctx.globalCompositeOperation = pal.blend;
|
|
1454
|
-
|
|
1455
|
-
const id = stageThemeId();
|
|
1456
|
-
let factory = STAGE_THEMES[0];
|
|
1457
|
-
for (let i = 0; i < STAGE_THEMES.length; i += 1) {
|
|
1458
|
-
if (STAGE_THEMES[i].id === id) factory = STAGE_THEMES[i];
|
|
1459
|
-
}
|
|
1460
|
-
stageTheme = factory.create({ ctx, w, h, pal });
|
|
1461
|
-
|
|
1462
|
-
// 预热若干帧:第一帧不空,静止降级时也有完整画面
|
|
1463
|
-
for (let i = 0; i < 24; i += 1) stageTheme.step(16);
|
|
1464
|
-
|
|
1465
|
-
if (reduced()) return;
|
|
1466
|
-
let last = 0;
|
|
1467
|
-
const frame = (now) => {
|
|
1468
|
-
if (!stageRaf) return;
|
|
1469
|
-
stageRaf = window.requestAnimationFrame(frame);
|
|
1470
|
-
if (document.hidden || !stageTheme) {
|
|
1471
|
-
last = now;
|
|
1472
|
-
return;
|
|
1473
|
-
}
|
|
1474
|
-
const dt = last ? Math.min(64, now - last) : 16;
|
|
1475
|
-
last = now;
|
|
1476
|
-
stageTheme.step(dt);
|
|
1477
|
-
};
|
|
1478
|
-
stageRaf = window.requestAnimationFrame(frame);
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
/** 只有"在干活"时才播;空闲、隐藏、或者不在这个预设里,就彻底停下。 */
|
|
1482
|
-
function syncStage(mode) {
|
|
1483
|
-
const want = preset === 'stage' && mode !== 'idle' && !document.hidden && !disposed;
|
|
1484
|
-
if (!want) {
|
|
1485
|
-
delete stage.dataset.on;
|
|
1486
|
-
stopStage();
|
|
1487
|
-
return;
|
|
1488
|
-
}
|
|
1489
|
-
stage.dataset.on = '1';
|
|
1490
|
-
if (!stageRaf) startStage();
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
function togglePreset() {
|
|
1494
|
-
preset = preset === 'edge' ? 'stage' : 'edge';
|
|
1495
|
-
writeStore(STORAGE.preset, preset);
|
|
1496
|
-
applyPresetClass();
|
|
1497
|
-
syncStage(currentMode);
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
function cycleTheme() {
|
|
1501
|
-
const ids = [];
|
|
1502
|
-
for (let i = 0; i < STAGE_THEMES.length; i += 1) ids.push(STAGE_THEMES[i].id);
|
|
1503
|
-
let at = ids.indexOf(forcedTheme);
|
|
1504
|
-
if (at < 0) at = ids.indexOf(stageThemeId());
|
|
1505
|
-
forcedTheme = ids[(at + 1) % ids.length];
|
|
1506
|
-
writeStore(STORAGE.theme, forcedTheme);
|
|
1507
|
-
if (preset === 'stage') {
|
|
1508
|
-
stopStage();
|
|
1509
|
-
syncStage(currentMode);
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
760
|
/* ---------- 挂载 ---------- */
|
|
1514
761
|
|
|
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
762
|
function ensureHost() {
|
|
1533
763
|
if (disposed || gaveUp) return;
|
|
1534
|
-
|
|
1535
|
-
if (host && host.isConnected && host.contains(layer)) return;
|
|
764
|
+
if (host && host.isConnected && host.contains(stage)) return;
|
|
1536
765
|
|
|
1537
766
|
const canvas = findCanvas();
|
|
1538
767
|
if (!canvas) return;
|
|
1539
|
-
if (canvas === host && canvas.contains(
|
|
768
|
+
if (canvas === host && canvas.contains(stage)) return;
|
|
1540
769
|
|
|
1541
770
|
const now = Date.now();
|
|
1542
771
|
if (now - remountAt > TUNE.remountWindowMs) {
|
|
@@ -1545,7 +774,6 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1545
774
|
}
|
|
1546
775
|
remountCount += 1;
|
|
1547
776
|
if (remountCount > TUNE.remountGuard) {
|
|
1548
|
-
// 宿主在和我们抢节点:放弃,宁可没有效果也不要抖动。
|
|
1549
777
|
gaveUp = true;
|
|
1550
778
|
return;
|
|
1551
779
|
}
|
|
@@ -1554,14 +782,27 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1554
782
|
if (host && host !== canvas) host.classList.remove(HOST_CLASS);
|
|
1555
783
|
host = canvas;
|
|
1556
784
|
host.classList.add(HOST_CLASS);
|
|
1557
|
-
if (
|
|
1558
|
-
if (stage.parentNode !== host) host.insertBefore(stage, layer.nextSibling);
|
|
1559
|
-
updateShift();
|
|
1560
|
-
applyPresetClass();
|
|
785
|
+
if (stage.parentNode !== host) host.insertBefore(stage, host.firstChild);
|
|
1561
786
|
if (previous !== canvas) resetForSwitch(previous !== null);
|
|
1562
787
|
}
|
|
1563
788
|
|
|
1564
|
-
|
|
789
|
+
/**
|
|
790
|
+
* 切会话:上一轮的工具行可能还挂着,历史里的失败行也会被当成新事件。
|
|
791
|
+
* 清空跟踪表、静默一小段时间,等新会话渲染稳定。
|
|
792
|
+
*/
|
|
793
|
+
function resetForSwitch(quiet) {
|
|
794
|
+
tracked.clear();
|
|
795
|
+
busy = false;
|
|
796
|
+
if (idleTimer) {
|
|
797
|
+
window.clearTimeout(idleTimer);
|
|
798
|
+
idleTimer = 0;
|
|
799
|
+
}
|
|
800
|
+
quietUntil = quiet ? Date.now() + TUNE.switchQuietMs : 0;
|
|
801
|
+
sessionKey = host ? host.getAttribute('data-conversation-session') || '' : null;
|
|
802
|
+
syncStage();
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/* ---------- 工具行跟踪 ---------- */
|
|
1565
806
|
|
|
1566
807
|
function track(node) {
|
|
1567
808
|
if (!node || node.nodeType !== 1) return;
|
|
@@ -1587,145 +828,136 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1587
828
|
for (let i = from; i < all.length; i += 1) tracked.add(all[i]);
|
|
1588
829
|
}
|
|
1589
830
|
|
|
1590
|
-
/* ----------
|
|
831
|
+
/* ---------- 状态判定:只关心"在不在干活" ---------- */
|
|
1591
832
|
|
|
1592
|
-
function
|
|
1593
|
-
let fresh = false;
|
|
833
|
+
function detectBusy() {
|
|
1594
834
|
for (const el of tracked) {
|
|
1595
835
|
if (!el.isConnected) continue;
|
|
1596
836
|
const state = el.getAttribute('data-state');
|
|
1597
|
-
if (state
|
|
1598
|
-
if (errorSeen.has(el)) continue;
|
|
1599
|
-
errorSeen.add(el);
|
|
1600
|
-
if (primed) fresh = true;
|
|
837
|
+
if (state === 'running' || state === 'preparing') return true;
|
|
1601
838
|
}
|
|
1602
|
-
return
|
|
839
|
+
return document.querySelector('[data-chat-running], [data-streaming]') !== null;
|
|
1603
840
|
}
|
|
1604
841
|
|
|
1605
|
-
function
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
if (
|
|
1613
|
-
|
|
1614
|
-
|
|
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);
|
|
842
|
+
function setBusy(next) {
|
|
843
|
+
if (disposed) return;
|
|
844
|
+
if (next) {
|
|
845
|
+
if (idleTimer) {
|
|
846
|
+
window.clearTimeout(idleTimer);
|
|
847
|
+
idleTimer = 0;
|
|
848
|
+
}
|
|
849
|
+
if (!busy) {
|
|
850
|
+
busy = true;
|
|
851
|
+
syncStage();
|
|
1630
852
|
}
|
|
1631
853
|
return;
|
|
1632
854
|
}
|
|
855
|
+
if (!busy || idleTimer) return;
|
|
856
|
+
idleTimer = window.setTimeout(() => {
|
|
857
|
+
idleTimer = 0;
|
|
858
|
+
busy = false;
|
|
859
|
+
syncStage();
|
|
860
|
+
}, TUNE.holdIdleMs);
|
|
861
|
+
}
|
|
1633
862
|
|
|
1634
|
-
|
|
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;
|
|
863
|
+
/* ---------- 背景播放 ---------- */
|
|
1641
864
|
|
|
1642
|
-
|
|
1643
|
-
|
|
865
|
+
/**
|
|
866
|
+
* 每次开工重新挑一个主题(随机,且不和上一次重复)。
|
|
867
|
+
* 按 Alt+T 固定过主题时,就一直用它。
|
|
868
|
+
*/
|
|
869
|
+
function pickTheme() {
|
|
870
|
+
const ids = [];
|
|
871
|
+
for (let i = 0; i < STAGE_THEMES.length; i += 1) ids.push(STAGE_THEMES[i].id);
|
|
872
|
+
if (forcedTheme && ids.indexOf(forcedTheme) >= 0) return forcedTheme;
|
|
873
|
+
let id = ids[(Math.random() * ids.length) | 0];
|
|
874
|
+
if (ids.length > 1 && id === activeTheme) id = ids[(ids.indexOf(id) + 1) % ids.length];
|
|
875
|
+
return id;
|
|
1644
876
|
}
|
|
1645
877
|
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
if (glitchTimer) window.clearTimeout(glitchTimer);
|
|
1651
|
-
glitchTimer = window.setTimeout(() => {
|
|
1652
|
-
glitchTimer = 0;
|
|
1653
|
-
delete layer.dataset.glitch;
|
|
1654
|
-
}, 130);
|
|
878
|
+
function stopStage() {
|
|
879
|
+
if (stageRaf) window.cancelAnimationFrame(stageRaf);
|
|
880
|
+
stageRaf = 0;
|
|
881
|
+
stageTheme = null;
|
|
1655
882
|
}
|
|
1656
883
|
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
if (
|
|
1660
|
-
const
|
|
1661
|
-
if (!
|
|
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
|
-
}
|
|
884
|
+
function startStage() {
|
|
885
|
+
stopStage();
|
|
886
|
+
if (!host) return;
|
|
887
|
+
const canvas = stage.querySelector('canvas');
|
|
888
|
+
if (!canvas) return;
|
|
1671
889
|
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
890
|
+
const rect = host.getBoundingClientRect();
|
|
891
|
+
const dpr = Math.min(2, window.devicePixelRatio || 1);
|
|
892
|
+
const w = Math.max(1, Math.round(rect.width));
|
|
893
|
+
const h = Math.max(1, Math.round(rect.height));
|
|
894
|
+
canvas.width = Math.round(w * dpr);
|
|
895
|
+
canvas.height = Math.round(h * dpr);
|
|
896
|
+
const ctx = canvas.getContext('2d');
|
|
897
|
+
if (!ctx) return;
|
|
898
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
1677
899
|
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
if (
|
|
1686
|
-
if (toolEl && toolEl.textContent !== toolText) toolEl.textContent = toolText;
|
|
1687
|
-
if (mode === 'idle') stopHex();
|
|
1688
|
-
else startHex();
|
|
1689
|
-
} catch {
|
|
1690
|
-
/* 遥测只是装饰,失败不影响主效果 */
|
|
900
|
+
const pal = stagePalette();
|
|
901
|
+
ctx.globalCompositeOperation = pal.blend;
|
|
902
|
+
|
|
903
|
+
const id = activeTheme || pickTheme();
|
|
904
|
+
activeTheme = id;
|
|
905
|
+
let factory = STAGE_THEMES[0];
|
|
906
|
+
for (let i = 0; i < STAGE_THEMES.length; i += 1) {
|
|
907
|
+
if (STAGE_THEMES[i].id === id) factory = STAGE_THEMES[i];
|
|
1691
908
|
}
|
|
1692
|
-
|
|
909
|
+
stageTheme = factory.create({ ctx, w, h, pal });
|
|
1693
910
|
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
if (
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
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 时不闪,不影响其它状态 */
|
|
911
|
+
// 预热若干帧:第一帧不空,减少动态效果时也有完整静止画面
|
|
912
|
+
for (let i = 0; i < 24; i += 1) stageTheme.step(16);
|
|
913
|
+
if (reduced()) return;
|
|
914
|
+
|
|
915
|
+
let last = 0;
|
|
916
|
+
const frame = (now) => {
|
|
917
|
+
if (!stageRaf) return;
|
|
918
|
+
stageRaf = window.requestAnimationFrame(frame);
|
|
919
|
+
if (document.hidden || !stageTheme) {
|
|
920
|
+
last = now;
|
|
921
|
+
return;
|
|
1711
922
|
}
|
|
923
|
+
const dt = last ? Math.min(64, now - last) : 16;
|
|
924
|
+
last = now;
|
|
925
|
+
stageTheme.step(dt);
|
|
926
|
+
};
|
|
927
|
+
stageRaf = window.requestAnimationFrame(frame);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/** 只有"在干活 + 窗口可见"才播;其它情况彻底停下。 */
|
|
931
|
+
function syncStage() {
|
|
932
|
+
const want = busy && !document.hidden && !disposed && Date.now() >= quietUntil;
|
|
933
|
+
if (!want) {
|
|
934
|
+
delete stage.dataset.on;
|
|
935
|
+
stopStage();
|
|
936
|
+
return;
|
|
1712
937
|
}
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
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
|
-
}
|
|
938
|
+
stage.dataset.on = '1';
|
|
939
|
+
if (!stageRaf) {
|
|
940
|
+
activeTheme = pickTheme();
|
|
941
|
+
startStage();
|
|
1725
942
|
}
|
|
1726
943
|
}
|
|
1727
944
|
|
|
1728
|
-
|
|
945
|
+
function cycleTheme() {
|
|
946
|
+
const ids = [];
|
|
947
|
+
for (let i = 0; i < STAGE_THEMES.length; i += 1) ids.push(STAGE_THEMES[i].id);
|
|
948
|
+
let at = ids.indexOf(activeTheme);
|
|
949
|
+
if (at < 0) at = ids.indexOf(forcedTheme);
|
|
950
|
+
if (at < 0) at = 0;
|
|
951
|
+
forcedTheme = ids[(at + 1) % ids.length];
|
|
952
|
+
writeStore(STORAGE.theme, forcedTheme);
|
|
953
|
+
activeTheme = forcedTheme;
|
|
954
|
+
if (stageRaf || busy) {
|
|
955
|
+
stopStage();
|
|
956
|
+
if (busy && !document.hidden) startStage();
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/* ---------- 扫描 ---------- */
|
|
1729
961
|
|
|
1730
962
|
function scan() {
|
|
1731
963
|
pending = 0;
|
|
@@ -1734,44 +966,24 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1734
966
|
ensureHost();
|
|
1735
967
|
if (!host) return;
|
|
1736
968
|
|
|
1737
|
-
// 切换会话:等新会话挂载稳定再判断,避免把上一轮/历史行当成"正在工作"
|
|
1738
969
|
const key = host.getAttribute('data-conversation-session') || '';
|
|
1739
970
|
if (sessionKey === null) sessionKey = key;
|
|
1740
971
|
else if (key !== sessionKey) {
|
|
1741
972
|
resetForSwitch(true);
|
|
1742
973
|
return;
|
|
1743
974
|
}
|
|
1744
|
-
if (Date.now() <
|
|
1745
|
-
|
|
1746
|
-
syncTelemetry('idle');
|
|
975
|
+
if (Date.now() < quietUntil) {
|
|
976
|
+
syncStage();
|
|
1747
977
|
return;
|
|
1748
978
|
}
|
|
1749
979
|
|
|
1750
980
|
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);
|
|
981
|
+
setBusy(detectBusy());
|
|
1766
982
|
} catch {
|
|
1767
983
|
/* 任何一步失败都只是不生效,不影响宿主 */
|
|
1768
984
|
}
|
|
1769
985
|
}
|
|
1770
986
|
|
|
1771
|
-
/**
|
|
1772
|
-
* 用短定时器而不是 rAF 合并高频变更:状态判定不需要帧精度,
|
|
1773
|
-
* 而 rAF 在窗口被遮挡 / 无头环境里会被节流甚至停摆,导致状态卡住。
|
|
1774
|
-
*/
|
|
1775
987
|
function requestScan() {
|
|
1776
988
|
if (disposed || pending) return;
|
|
1777
989
|
pending = window.setTimeout(scan, 48);
|
|
@@ -1804,37 +1016,24 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1804
1016
|
|
|
1805
1017
|
function onVisibility() {
|
|
1806
1018
|
if (document.hidden) {
|
|
1807
|
-
layer.dataset.paused = '1';
|
|
1808
|
-
stopHex();
|
|
1809
1019
|
stopStage();
|
|
1810
1020
|
} else {
|
|
1811
|
-
|
|
1021
|
+
syncStage();
|
|
1812
1022
|
requestScan();
|
|
1813
|
-
syncStage(currentMode);
|
|
1814
1023
|
}
|
|
1815
1024
|
}
|
|
1816
1025
|
|
|
1817
1026
|
function onResize() {
|
|
1818
|
-
|
|
1027
|
+
if (stageRaf) {
|
|
1028
|
+
stopStage();
|
|
1029
|
+
startStage();
|
|
1030
|
+
}
|
|
1819
1031
|
}
|
|
1820
1032
|
|
|
1821
1033
|
function onKeyDown(event) {
|
|
1822
1034
|
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;
|
|
1035
|
+
if (String(event.key || '').toLowerCase() !== TUNE.themeKey) return;
|
|
1036
|
+
cycleTheme();
|
|
1838
1037
|
}
|
|
1839
1038
|
|
|
1840
1039
|
/* ---------- 启动 ---------- */
|
|
@@ -1843,11 +1042,10 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1843
1042
|
if (disposed || booted) return;
|
|
1844
1043
|
booted = true;
|
|
1845
1044
|
ensureHost();
|
|
1846
|
-
applyPresetClass();
|
|
1847
1045
|
try {
|
|
1848
1046
|
seed();
|
|
1849
1047
|
} catch {
|
|
1850
|
-
/*
|
|
1048
|
+
/* 会话还没渲染出来时没有可跟踪的行 */
|
|
1851
1049
|
}
|
|
1852
1050
|
requestScan();
|
|
1853
1051
|
observe();
|
|
@@ -1866,9 +1064,7 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1866
1064
|
if (disposed) return;
|
|
1867
1065
|
disposed = true;
|
|
1868
1066
|
if (pending) window.clearTimeout(pending);
|
|
1869
|
-
if (
|
|
1870
|
-
if (glitchTimer) window.clearTimeout(glitchTimer);
|
|
1871
|
-
stopHex();
|
|
1067
|
+
if (idleTimer) window.clearTimeout(idleTimer);
|
|
1872
1068
|
if (observer) observer.disconnect();
|
|
1873
1069
|
document.removeEventListener('DOMContentLoaded', boot);
|
|
1874
1070
|
document.removeEventListener('visibilitychange', onVisibility);
|
|
@@ -1876,7 +1072,6 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1876
1072
|
window.removeEventListener('keydown', onKeyDown, true);
|
|
1877
1073
|
stopStage();
|
|
1878
1074
|
if (stage.parentNode) stage.parentNode.removeChild(stage);
|
|
1879
|
-
if (layer.parentNode) layer.parentNode.removeChild(layer);
|
|
1880
1075
|
if (host) host.classList.remove(HOST_CLASS);
|
|
1881
1076
|
style.remove();
|
|
1882
1077
|
};
|
|
@@ -1885,9 +1080,8 @@ body[data-ds-dark-theme] .dsh-stage__veil{
|
|
|
1885
1080
|
/* ================= 插件入口 ================= */
|
|
1886
1081
|
|
|
1887
1082
|
/**
|
|
1888
|
-
* Alt+
|
|
1889
|
-
*
|
|
1890
|
-
* theme 服务不在时不激活这个键,插件其余部分不受影响。
|
|
1083
|
+
* Alt+T 换背景主题。
|
|
1084
|
+
* Alt+D 切浅色/深色:走宿主 theme 服务,不直接改 body,和「设置 → 外观」始终一致。
|
|
1891
1085
|
*/
|
|
1892
1086
|
function bindThemeHotkey(ctx) {
|
|
1893
1087
|
if (!ctx || typeof ctx.inject !== 'function') return;
|