dsh-mini-remote 0.1.4 → 0.1.5

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/lib/page.html CHANGED
@@ -3,29 +3,90 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
6
- <meta name="theme-color" content="#0f1115">
6
+ <meta name="theme-color" content="#0a0f16">
7
7
  <meta name="mobile-web-app-capable" content="yes">
8
8
  <meta name="apple-mobile-web-app-capable" content="yes">
9
9
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
10
- <title>DSH mini remote</title>
10
+ <title>极简遥控器</title>
11
11
  <style>
12
+ /* ---------- 设计令牌 ----------
13
+ 规格来自用户 2026-09-27 定的六关键词翻译表,改之前先读这一段:
14
+ · 圆角只有两档:卡片一律 2px,状态点用 999px/50%。**没有中间档**。
15
+ · 阴影只有两档,且都极浅;层次主要靠 --line 这条 1px 细线和间距建立。
16
+ · 强调色分两个角色,加起来**一屏最多两处**:
17
+ --gold 是**当字/点**用的(刻度上"当前这一步");--act 是**当底**用的(主按钮)。
18
+ 深色下两个都是暖金 #c9a55c;浅色下 --gold 压深成 #8a6529(白底才读得清),
19
+ --act 换成宣传图的宝蓝 #3b6fc4——用户 2026-09-27 看过对照图后定的:浅色那套
20
+ 底色本来就是从宣传图取的,冷底上再压一块饱和暖金,比在深色下更像外来物。
21
+ 需要"显眼一点"的地方一律用鲸鱼娘的冰蓝 --ice,别动这两个。
22
+ · --ok/--run/--err 是状态点的语义色,占的是 8px 一个小点,不算强调色。
23
+ · 配色以鲸鱼娘为准心:深海墨蓝打底,她的冰蓝作次级色。
24
+ · 两套主题:这套(默认,深色)和 [data-theme="light"],色调跟宣传图
25
+ (docs/promo.png)统一。**颜色令牌两边必须一一对应**——缺一个,就有元素
26
+ 在某个主题下"隐身",而且只在那个主题下才看得出来。守卫在 test/ui.test.mjs。 */
12
27
  :root {
13
- --bg: #0f1115;
14
- --panel: #171a21;
15
- --panel-2: #1e222b;
16
- --line: #2a2f3a;
17
- --fg: #e8eaed;
18
- --muted: #8b93a1;
19
- --accent: #4c8dff;
20
- --ok: #35c46b;
21
- --warn: #e0a33a;
22
- --err: #e5604d;
23
- --radius: 14px;
28
+ color-scheme: dark;
29
+ --bg: #0a0f16;
30
+ --bg-top: #101a27;
31
+ --bg-deep: #070b11;
32
+ --panel: #121a25;
33
+ --panel-2: #18222f;
34
+ --line: #22303f;
35
+ --fg: #e3ebf4;
36
+ --muted: #7b8a9b;
37
+ --dim: #6c7a8b;
38
+ --ice: #a9c8e8;
39
+ --gold: #c9a55c; /* 强调色当**字/点**用:刻度上"当前这一步" */
40
+ --act: #c9a55c; /* 强调色当**底**用:一屏一个的主按钮 */
41
+ --ok: #4fb477;
42
+ --run: #a9c8e8;
43
+ --err: #d4614f;
44
+ --r: 2px;
45
+ --sh-1: 0 1px 2px rgba(0, 0, 0, .28);
46
+ --sh-2: 0 2px 6px rgba(0, 0, 0, .34);
47
+ --glass: rgba(18, 26, 37, .72);
48
+ --tint: rgba(24, 34, 47, .5); /* 队列那块比面板再深一点的底 */
49
+ --pop: rgba(24, 34, 47, .97); /* 浮起来的小面板(指令菜单等) */
50
+ --scrim: rgba(0, 0, 0, .55); /* 弹出层背后压暗的那层;浅色主题里它得轻得多 */
51
+ --on-act: #10161f; /* 实心主按钮上的字:金底配白字读不清 */
52
+ }
53
+ /* 浅色主题:色调跟宣传图(docs/promo.png)统一——近白的冰蓝底、深宝蓝的字与状态色、
54
+ 白色卡片、极浅的蓝投影。深色那边的规矩一条不改:圆角两档、层次靠 1px 细线。
55
+ 强调色在浅色下分了家:当字/点用的金压深到 #8a6529,当底用的主按钮换成宣传图的宝蓝
56
+ (#3b6fc4 配白字)——用户 2026-09-27 看过对照图后定的。
57
+ 切换入口在设置页「外观」;选择存在 localStorage,首屏之前就定好,不会闪一下。 */
58
+ [data-theme="light"] {
59
+ color-scheme: light;
60
+ --bg: #eaf3fc;
61
+ --bg-top: #f7fbff;
62
+ --bg-deep: #dbe9f8;
63
+ --panel: #ffffff;
64
+ --panel-2: #f2f8ff;
65
+ --line: #d3e2f3;
66
+ --fg: #1b2c49;
67
+ --muted: #6b7f9b;
68
+ --dim: #8496ae;
69
+ --ice: #3b6fc4;
70
+ --gold: #8a6529; /* 白底上当字用,得压深才读得清 */
71
+ --act: #3b6fc4; /* 主按钮底:用宣传图那个宝蓝(用户 2026-09-27 裁决) */
72
+ --ok: #2e9a5f;
73
+ --run: #3b6fc4;
74
+ --err: #c0392b;
75
+ --sh-1: 0 1px 2px rgba(28, 60, 110, .07);
76
+ --sh-2: 0 2px 8px rgba(28, 60, 110, .12);
77
+ --glass: rgba(255, 255, 255, .74);
78
+ --tint: rgba(59, 111, 196, .07);
79
+ --pop: rgba(255, 255, 255, .98);
80
+ --scrim: rgba(23, 52, 95, .30);
81
+ --on-act: #ffffff; /* 宝蓝底上的字 */
24
82
  }
25
83
  * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
26
84
  html, body {
27
85
  margin: 0; height: 100%; overflow: hidden;
28
- background: var(--bg); color: var(--fg);
86
+ /* 纵深:顶部略亮、底部更深,做出"空间"而不是一整块平色。
87
+ 设计稿参考的是沉浸式暗色流媒体那一套——界面浮在深色基底上,不是被切成几块。 */
88
+ background: linear-gradient(180deg, var(--bg-top) 0%, var(--bg) 55%, var(--bg-deep) 100%);
89
+ color: var(--fg);
29
90
  font: 16px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
30
91
  "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
31
92
  overscroll-behavior: none;
@@ -33,24 +94,32 @@
33
94
  body { display: flex; flex-direction: column; }
34
95
 
35
96
  /* ---------- 顶栏 ---------- */
97
+ /* 顶栏和底栏是两块**悬浮毛玻璃**:内容从它们底下淡出,界面像浮着,
98
+ 而不是上下两条实心色带把屏幕切成三块。 */
36
99
  header {
37
100
  flex: 0 0 auto; display: flex; align-items: center; gap: 10px;
38
101
  padding: calc(env(safe-area-inset-top) + 10px) 14px 10px;
39
- background: var(--panel); border-bottom: 1px solid var(--line);
102
+ background: var(--glass); border-bottom: 1px solid var(--line);
103
+ -webkit-backdrop-filter: blur(14px) saturate(1.1);
104
+ backdrop-filter: blur(14px) saturate(1.1);
40
105
  }
41
106
  .brand { font-weight: 600; letter-spacing: .3px; }
42
107
  .dot {
43
108
  width: 8px; height: 8px; border-radius: 50%;
44
- background: var(--muted); flex: 0 0 auto; transition: background .2s;
109
+ background: var(--muted); flex: 0 0 auto;
110
+ /* 状态变化是**瞬时**的:数据在变,界面不表演。
111
+ 这里原来挂着一圈 1.2s 的呼吸闪动,2026-09-27 按规格删掉了——
112
+ "正在思考"的脉动圆点是那几年的视觉套话;而"它在动"这件事,下面那块正在执行区
113
+ 里她说的那句话和秒数已经说得更准,也更安静。160ms 是本页唯一保留的过渡节奏。 */
114
+ transition: background .16s;
45
115
  }
46
116
  .dot.ok { background: var(--ok); }
47
- .dot.run { background: var(--warn); animation: pulse 1.2s ease-in-out infinite; }
117
+ .dot.run { background: var(--run); }
48
118
  .dot.err { background: var(--err); }
49
- @keyframes pulse { 50% { opacity: .25; } }
50
119
  .status { color: var(--muted); font-size: 13px; flex: 1 1 auto;
51
120
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
52
121
  .icon-btn {
53
- flex: 0 0 auto; width: 36px; height: 36px; border-radius: 10px;
122
+ flex: 0 0 auto; width: 36px; height: 36px; border-radius: var(--r);
54
123
  border: 1px solid var(--line); background: var(--panel-2); color: var(--fg);
55
124
  font-size: 16px; display: grid; place-items: center; cursor: pointer;
56
125
  }
@@ -64,11 +133,11 @@
64
133
  white-space: pre-wrap; word-break: break-word;
65
134
  font-size: 16px; line-height: 1.75;
66
135
  }
67
- #reply a { color: var(--accent); }
136
+ #reply a { color: var(--ice); }
68
137
  .placeholder { color: var(--muted); font-size: 15px; text-align: center; line-height: 1.9; }
69
138
  .placeholder kbd {
70
139
  background: var(--panel-2); border: 1px solid var(--line);
71
- border-radius: 6px; padding: 1px 6px; font-size: 13px; font-family: inherit;
140
+ border-radius: var(--r); padding: 1px 6px; font-size: 13px; font-family: inherit;
72
141
  }
73
142
 
74
143
  /* ---------- 聊天模式 ---------- */
@@ -79,7 +148,7 @@
79
148
  两种气泡都要,只改一边的话,短回复那边还是会露出大方块,看着像坏了。 */
80
149
  .bubble {
81
150
  width: fit-content;
82
- max-width: 86%; padding: 10px 13px; border-radius: var(--radius);
151
+ max-width: 86%; padding: 10px 13px; border-radius: var(--r);
83
152
  /* 18px,和 .said 的下边距一致。原来是 10px,用户 2026-09-22 实机提「指令气泡和
84
153
  回答正文第一行之间贴得太紧」——10px 是「同一条消息内部」的距离,
85
154
  而这里跨的是「我说的话」和「它答的话」,是更大的一个断点,该给更多。 */
@@ -88,12 +157,12 @@
88
157
  }
89
158
  .bubble.user { margin-left: auto; background: var(--panel-2); border: 1px solid var(--line); }
90
159
  .bubble.assistant { margin-right: auto; background: var(--panel); border: 1px solid var(--line); }
91
- .bubble a { color: var(--accent); }
160
+ .bubble a { color: var(--ice); }
92
161
  .bubble .meta { display: block; margin-top: 6px; font-size: 11px; color: var(--muted); }
93
162
  /* 「已停止」标签:被按停的那一轮,手机上收到的只是**已经吐出来的那半句**。
94
163
  不标出来的话,它看着就像模型答崩了。 */
95
164
  .stopped {
96
- display: inline-block; padding: 1px 7px; border-radius: 999px; font-size: 11px;
165
+ display: inline-block; padding: 1px 7px; border-radius: var(--r); font-size: 11px;
97
166
  color: var(--muted); border: 1px solid var(--line); vertical-align: middle;
98
167
  }
99
168
  .reply-bar .stopped { margin-right: auto; }
@@ -101,6 +170,12 @@
101
170
  /* 外层是 pre-wrap(用户消息和旧内容靠它),markdown 输出的是真块级元素,
102
171
  所以要在这里把 pre-wrap 收掉,否则段落之间会多出一堆空行。 */
103
172
  .md { white-space: normal; }
173
+ /* 回复里的图。地址有两种写法,见下面 mdInline() 里那段注释。
174
+ 宽度只封顶不写死:窄屏上占满可用宽度、按比例缩,圆角沿用全站那 2 像素。 */
175
+ .md-img {
176
+ display: block; max-width: 100%; height: auto; margin: 10px 0;
177
+ border-radius: var(--r); border: 1px solid var(--line); background: var(--panel-2);
178
+ }
104
179
  .md > :first-child { margin-top: 0; }
105
180
  .md > :last-child { margin-bottom: 0; }
106
181
  .md p { margin: 0 0 .7em; }
@@ -113,7 +188,7 @@
113
188
  .md h4, .md h5, .md h6 { font-size: 1em; }
114
189
  .md ul, .md ol { margin: 0 0 .7em; padding-left: 1.4em; }
115
190
  .md li { margin: .18em 0; }
116
- .md a { color: var(--accent); word-break: break-all; }
191
+ .md a { color: var(--ice); word-break: break-all; }
117
192
  .md strong { font-weight: 650; }
118
193
  .md hr { border: none; border-top: 1px solid var(--line); margin: 1em 0; }
119
194
  .md blockquote {
@@ -121,12 +196,12 @@
121
196
  border-left: 3px solid var(--line); color: var(--muted);
122
197
  }
123
198
  .md code {
124
- background: var(--panel-2); border: 1px solid var(--line); border-radius: 5px;
199
+ background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--r);
125
200
  padding: .1em .35em; font-size: .88em;
126
201
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
127
202
  }
128
203
  .md .code {
129
- margin: 0 0 .7em; border: 1px solid var(--line); border-radius: 10px;
204
+ margin: 0 0 .7em; border: 1px solid var(--line); border-radius: var(--r);
130
205
  background: var(--panel-2); overflow: hidden;
131
206
  }
132
207
  .md .code-head {
@@ -150,7 +225,7 @@
150
225
  三个地方共用:单帧模式的整篇、聊天模式的气泡、代码块。 */
151
226
  .copy {
152
227
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
153
- width: 28px; height: 28px; padding: 0; border-radius: 8px; cursor: pointer;
228
+ width: 28px; height: 28px; padding: 0; border-radius: var(--r); cursor: pointer;
154
229
  border: 1px solid var(--line); background: var(--panel-2); color: var(--muted);
155
230
  }
156
231
  .copy svg {
@@ -158,7 +233,7 @@
158
233
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
159
234
  }
160
235
  .copy:active { transform: scale(.95); }
161
- .copy.done { color: var(--accent); border-color: var(--accent); }
236
+ .copy.done { color: var(--ice); border-color: var(--ice); }
162
237
  .bubble .meta .copy { margin-left: 8px; vertical-align: middle; }
163
238
  /* 上面那条只放「已停止」标签——它是给正文打的预防针,得在正文之前出现。
164
239
  复制按钮挪到正文下面去了(.reply-foot)。 */
@@ -177,7 +252,7 @@
177
252
  display: flex; align-items: baseline; gap: 8px;
178
253
  width: fit-content; max-width: 100%;
179
254
  margin: 0 0 14px; padding: 4px 10px 4px 9px;
180
- border-left: 2px solid var(--line); border-radius: 0 8px 8px 0;
255
+ border-left: 2px solid var(--line); border-radius: 0 var(--r) var(--r) 0;
181
256
  background: var(--panel-2); font-size: 13px; line-height: 1.5;
182
257
  }
183
258
  /* 名字加参数排一行,太长就省略——它是「做了哪件事」的标签,不是正文。 */
@@ -202,7 +277,7 @@
202
277
  .live .md > *:last-child::after {
203
278
  content: ''; display: inline-block; width: 2px; height: 1em;
204
279
  margin-left: 2px; vertical-align: -2px;
205
- background: var(--accent, #2563eb);
280
+ background: var(--ice);
206
281
  animation: caret 1s steps(2, start) infinite;
207
282
  }
208
283
  @keyframes caret { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }
@@ -239,10 +314,9 @@
239
314
  .work-stage {
240
315
  position: relative; flex: 0 0 auto;
241
316
  width: 122px; height: 106px;
242
- /* 整块再轻轻浮动一下。它的节奏和翻帧错开,两下叠起来才像活的。 */
243
- animation: workBob 2.4s ease-in-out infinite;
317
+ /* 这里原来还叠着一层 2.4s 的上下浮动(`workBob`),2026-09-27 删掉:
318
+ 她的翻帧本身就是"活着"的证据,再叠一层漂浮就变成界面在表演了。 */
244
319
  }
245
- @keyframes workBob { 50% { transform: translateY(-3px); } }
246
320
  .pose {
247
321
  position: absolute; inset: 0;
248
322
  background-repeat: no-repeat;
@@ -278,7 +352,7 @@
278
352
  max-width: calc(100% - 128px);
279
353
  align-self: flex-start; margin-top: 6px;
280
354
  background: var(--panel-2); border: 1px solid var(--line);
281
- border-radius: 12px; padding: 9px 12px;
355
+ border-radius: var(--r); padding: 9px 12px;
282
356
  font-size: 14.5px; line-height: 1.5; color: var(--fg);
283
357
  max-height: 50vh; overflow-y: auto;
284
358
  /* 长英文串、URL 不认换行,会把气泡顶宽。中文本来就能任意断行,这条是给它们兜的。 */
@@ -299,28 +373,40 @@
299
373
  /* 气泡里的话走和回答区同一套行内渲染(`mdInline`):`**加粗**`、`` `代码` ``、链接会变成样式。
300
374
  代码的底色特意不用 --panel-2——那是气泡自己的底色,套上去等于看不见。 */
301
375
  .work-bubble strong { font-weight: 650; }
302
- .work-bubble a { color: var(--accent); word-break: break-all; }
376
+ .work-bubble a { color: var(--ice); word-break: break-all; }
303
377
  .work-bubble code {
304
- background: rgba(127, 127, 127, .18); border: 1px solid var(--line); border-radius: 5px;
378
+ background: rgba(127, 127, 127, .18); border: 1px solid var(--line); border-radius: var(--r);
305
379
  padding: .1em .35em; font-size: .88em;
306
380
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
307
381
  }
308
382
 
309
- /* 进度条故意做成**不确定进度**的流动条,不是百分比。
383
+ /* 进度条是**不确定进度**,不是百分比。
310
384
  插件按设计只收「指令 + 最终回复」,拿不到 Agent 此刻跑到哪一步,
311
- 所以画一个填到 80% 的假进度条等于撒谎。它只表达「在跑」,不表达「跑到哪了」。 */
385
+ 所以画一个填到 80% 的假进度条等于撒谎。它只表达「在跑」,不表达「跑到哪了」。
386
+
387
+ 这一段前后改过三次,方向来回,写清楚免得再兜圈子:
388
+ ① 最初是一条来回扫的流光(`workSweep`),按 UI 规格删掉——流光属于"看起来在动"
389
+ 的廉价套话;
390
+ ② 删完剩一根**定在 38% 的静态标记**。结果更糟:用户实机一看就问「底部的进度条
391
+ 好像不会动」,而一根停在三成八的线,比一个会动的它**更**像在谎报百分比;
392
+ ③ 现在:宽度写死 38% 永不改变,动的是**位置**——十六格一轮、每格 160ms,
393
+ 也就是全页那一个节奏。推进式(steps)而不是匀速流淌。
394
+ 它仍然不知道跑了多少;它只是不再假装停住。 */
312
395
  .work-progress {
313
- margin-top: 10px; height: 4px; border-radius: 2px;
314
- background: var(--panel-2); overflow: hidden;
396
+ margin-top: 10px; height: 1px;
397
+ background: var(--line); overflow: hidden;
315
398
  }
316
399
  .work-progress i {
317
- display: block; width: 38%; height: 100%; border-radius: 2px;
318
- background: linear-gradient(90deg, transparent, var(--accent) 45%, var(--accent) 55%, transparent);
319
- animation: workSweep 1.5s ease-in-out infinite;
400
+ display: block; width: 38%; height: 100%;
401
+ background: var(--ice);
402
+ animation: barStep 2.56s steps(16, end) infinite;
320
403
  }
321
- @keyframes workSweep {
322
- from { transform: translateX(-105%); }
323
- to { transform: translateX(265%); }
404
+ /* 从左外侧进来、从右外侧出去。38% 宽的块想走完一整条轨道,位移得是自身宽度的
405
+ 263%(100 ÷ 38 ≈ 2.63)。用 steps 而不是匀速:一格一格地推进,
406
+ 跟上面那把刻度尺是同一种秩序感。 */
407
+ @keyframes barStep {
408
+ from { transform: translateX(-100%); }
409
+ to { transform: translateX(263%); }
324
410
  }
325
411
  /* 秒数在左、停止按钮在右。秒数原来是自己一行右对齐,现在跟按钮同一行。 */
326
412
  .work-foot {
@@ -334,7 +420,7 @@
334
420
  按下去之后自己禁用并改文案——cancel() 只是递请求,真正停下要等状态翻,
335
421
  这中间必须让用户看见「已经按到了」,否则会连按好几下。 */
336
422
  .stop {
337
- padding: 5px 14px; border-radius: 999px; font-size: 13px;
423
+ padding: 5px 14px; border-radius: var(--r); font-size: 13px;
338
424
  border: 1px solid var(--line); background: transparent; color: var(--fg);
339
425
  transition: opacity .15s;
340
426
  }
@@ -343,11 +429,44 @@
343
429
  /* 排队中的指令。跑着任务时接着发的那几条排在这儿,等这一轮跑完再跑。
344
430
  单帧模式下你发出去的那句话在别处一个字都看不见(那一模式只显示回复),
345
431
  所以这块是唯一的反馈,不能省。 */
432
+ /* ---------- 工序刻度尺 ----------
433
+ **它代表的是"队列",不是 Agent 的工序。** 插件按设计只收「指令 + 最终回复」,
434
+ 根本看不到 Agent 此刻跑到哪一步(见上面进度条那一段)。所以刻度上每个点都对应
435
+ 一条真实存在的指令:第一个点是**正在跑的那条**(暖金),后面是排着队的(空心)。
436
+ 设计稿里画的是一条理想中的工序进度尺;落到真实实现,只有队列是插件真知道的事,
437
+ 就按队列画——界面上不出现编出来的数据。 */
346
438
  .queue {
347
- margin-top: 12px; padding: 8px 10px; border-radius: 10px;
348
- background: var(--panel-2); border: 1px solid var(--line);
349
- }
350
- .queue .q-head { font-size: 11.5px; color: var(--muted); margin-bottom: 6px; }
439
+ margin-top: 12px; padding: 12px;
440
+ border: 1px solid var(--line); border-radius: var(--r);
441
+ background: var(--tint);
442
+ }
443
+ .queue .q-head { font-size: 11.5px; color: var(--muted); margin-bottom: 10px; }
444
+ .queue .q-scale {
445
+ position: relative; display: flex; align-items: center; height: 24px; margin: 2px 0 10px;
446
+ }
447
+ /* 刻度之间的那条细线:垫在点底下,两端各留 3px,不顶到边。 */
448
+ .queue .q-scale::before {
449
+ content: ''; position: absolute; left: 3px; right: 3px; top: 6px;
450
+ height: 1px; background: var(--line);
451
+ }
452
+ .queue .q-node {
453
+ position: relative; flex: 1 1 0; display: flex; flex-direction: column;
454
+ align-items: center; gap: 5px;
455
+ }
456
+ .queue .q-node i {
457
+ display: block; width: 7px; height: 7px; border-radius: 50%;
458
+ box-sizing: content-box; border: 1px solid var(--line); background: var(--bg);
459
+ /* 全页唯一允许的动效:节点由"待"变"过"时 160ms 颜色过渡。 */
460
+ transition: background .16s ease, border-color .16s ease;
461
+ }
462
+ /* 刻度上"当前这一步"是**当字用**的那个强调色(--gold):浅色主题底下它要压深,
463
+ 所以这里是 --gold 而不是 --act——--act 是"当底用"的,浅色下已经换成宝蓝了。 */
464
+ .queue .q-node.cur i { background: var(--gold); border-color: var(--gold); }
465
+ .queue .q-node b {
466
+ font-weight: 400; font-size: 10.5px; line-height: 1;
467
+ color: var(--muted); font-variant-numeric: tabular-nums;
468
+ }
469
+ .queue .q-node.cur b { color: var(--gold); }
351
470
  .queue .q-row {
352
471
  display: flex; align-items: center; gap: 8px; padding: 3px 0;
353
472
  font-size: 13.5px; line-height: 1.45;
@@ -360,41 +479,51 @@
360
479
  }
361
480
  .queue .q-drop {
362
481
  flex: 0 0 auto; width: 24px; height: 24px; padding: 0; line-height: 1;
363
- border-radius: 999px; font-size: 12px;
482
+ border-radius: var(--r); font-size: 12px;
364
483
  border: 1px solid var(--line); background: transparent; color: var(--muted);
365
484
  }
485
+ .queue .q-drop svg {
486
+ width: 12px; height: 12px; fill: none; stroke: currentColor;
487
+ stroke-width: 1.6; stroke-linecap: round;
488
+ }
366
489
 
367
490
  /* 系统里设了「减少动态效果」的人:不浮动、不翻帧、不轮播、进度条常亮。
368
491
  秒数照常走——那是信息,不是装饰。 */
369
492
  @media (prefers-reduced-motion: reduce) {
370
493
  .work-stage { animation: none; }
371
494
  .pose { animation: none; transition: none; }
372
- .work-progress i { animation: none; width: 100%; opacity: .45; }
495
+ .work-progress i { width: 100%; opacity: .45; animation: none; }
496
+ .queue .q-node i { transition: none; }
497
+ .dot { transition: none; }
373
498
  }
374
499
 
375
500
  /* ---------- 底栏 ---------- */
376
501
  footer {
377
502
  flex: 0 0 auto; display: flex; align-items: flex-end; gap: 8px;
378
503
  padding: 10px 12px calc(env(safe-area-inset-bottom) + 10px);
379
- background: var(--panel); border-top: 1px solid var(--line);
504
+ background: var(--glass); border-top: 1px solid var(--line);
505
+ -webkit-backdrop-filter: blur(14px) saturate(1.1);
506
+ backdrop-filter: blur(14px) saturate(1.1);
380
507
  }
381
508
  #input {
382
509
  flex: 1 1 auto; resize: none; min-height: 42px; max-height: 140px;
383
- padding: 10px 12px; border-radius: 12px; border: 1px solid var(--line);
510
+ padding: 10px 12px; border-radius: var(--r); border: 1px solid var(--line);
384
511
  background: var(--panel-2); color: var(--fg); font: inherit; font-size: 16px;
385
512
  line-height: 1.5; outline: none;
386
513
  }
387
- #input:focus { border-color: var(--accent); }
514
+ #input:focus { border-color: var(--ice); }
515
+ /* 发送键是这一屏**唯一的暖金**(另一处是刻度上"正在跑的那一步")。
516
+ 金色底配白字读不清,所以字用近黑的 --on-act。(浅色下 --act 是宝蓝、--on-act 是白字。) */
388
517
  .send {
389
- flex: 0 0 auto; height: 42px; padding: 0 16px; border-radius: 12px;
390
- border: none; background: var(--accent); color: #fff;
518
+ flex: 0 0 auto; height: 42px; padding: 0 16px; border-radius: var(--r);
519
+ border: none; background: var(--act); color: var(--on-act);
391
520
  font-size: 15px; font-weight: 600; cursor: pointer;
392
521
  }
393
522
  .send:disabled { opacity: .4; }
394
523
 
395
524
  /* ---------- 上传附件 ---------- */
396
525
  .attach {
397
- flex: 0 0 auto; width: 42px; height: 42px; padding: 0; border-radius: 12px;
526
+ flex: 0 0 auto; width: 42px; height: 42px; padding: 0; border-radius: var(--r);
398
527
  border: 1px solid var(--line); background: var(--panel-2); color: var(--muted);
399
528
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
400
529
  }
@@ -407,12 +536,14 @@
407
536
  /* 附件小条:排在输入框上面,让你发之前看得见自己带了什么。 */
408
537
  .attach-bar {
409
538
  flex: 0 0 auto; display: flex; flex-wrap: wrap; gap: 6px;
410
- padding: 8px 12px 0; background: var(--panel); border-top: 1px solid var(--line);
539
+ padding: 8px 12px 0; background: var(--glass); border-top: 1px solid var(--line);
540
+ -webkit-backdrop-filter: blur(14px) saturate(1.1);
541
+ backdrop-filter: blur(14px) saturate(1.1);
411
542
  }
412
543
  .attach-bar[hidden] { display: none; }
413
544
  .chip {
414
545
  display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
415
- padding: 5px 6px 5px 10px; border-radius: 999px;
546
+ padding: 5px 6px 5px 10px; border-radius: var(--r);
416
547
  border: 1px solid var(--line); background: var(--panel-2);
417
548
  font-size: 12.5px; color: var(--fg);
418
549
  }
@@ -431,17 +562,19 @@
431
562
  .cmd-menu {
432
563
  flex: 0 0 auto; max-height: 42vh; overflow-y: auto;
433
564
  -webkit-overflow-scrolling: touch;
434
- padding: 6px 8px; background: var(--panel); border-top: 1px solid var(--line);
565
+ padding: 6px 8px; background: var(--glass); border-top: 1px solid var(--line);
566
+ -webkit-backdrop-filter: blur(14px) saturate(1.1);
567
+ backdrop-filter: blur(14px) saturate(1.1);
435
568
  }
436
569
  .cmd-menu[hidden] { display: none; }
437
570
  .cmd-menu .cmd-row {
438
571
  display: flex; align-items: baseline; gap: 10px; width: 100%;
439
- padding: 9px 8px; border: none; border-radius: 8px; background: none;
572
+ padding: 9px 8px; border: none; border-radius: var(--r); background: none;
440
573
  color: var(--fg); font: inherit; font-size: 14.5px; text-align: left; cursor: pointer;
441
574
  }
442
575
  .cmd-menu .cmd-row:active { background: var(--panel-2); }
443
576
  .cmd-menu .cmd-name {
444
- flex: 0 0 auto; color: var(--accent);
577
+ flex: 0 0 auto; color: var(--ice);
445
578
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
446
579
  }
447
580
  /* 说明收成一行:这一屏是「扫一眼挑一个」,不是读文档的地方。 */
@@ -456,18 +589,20 @@
456
589
  /* ---------- 左侧导航栏 ---------- */
457
590
  #nav {
458
591
  position: fixed; inset: 0; z-index: 60; display: none;
459
- background: rgba(0, 0, 0, .5);
592
+ background: rgba(4, 7, 11, .62);
460
593
  }
461
594
  #nav.open { display: block; }
462
595
  .nav-panel {
463
596
  position: absolute; top: 0; bottom: 0; left: 0; width: min(86vw, 350px);
464
597
  display: flex; flex-direction: column;
465
- background: var(--panel); border-right: 1px solid var(--line);
598
+ background: var(--glass); border-right: 1px solid var(--line);
466
599
  padding-top: env(safe-area-inset-top);
467
600
  padding-bottom: env(safe-area-inset-bottom);
468
- animation: navIn .18s ease-out;
601
+ -webkit-backdrop-filter: blur(14px) saturate(1.1);
602
+ backdrop-filter: blur(14px) saturate(1.1);
603
+ /* 原来抽出来时有一条 0.18s 的滑入(`navIn`),2026-09-27 删掉:
604
+ 状态变化是瞬时的,抽屉不该表演怎么出现。 */
469
605
  }
470
- @keyframes navIn { from { transform: translateX(-100%); } }
471
606
  .nav-head {
472
607
  flex: 0 0 auto; display: flex; align-items: center; gap: 8px;
473
608
  padding: 12px 12px 12px 16px; border-bottom: 1px solid var(--line);
@@ -499,19 +634,23 @@
499
634
  font: inherit; font-size: 14px; text-align: left; cursor: pointer;
500
635
  }
501
636
  .sess:active { background: var(--panel-2); }
502
- .sess.cur { background: var(--panel-2); box-shadow: inset 3px 0 0 var(--accent); }
637
+ /* "当前会话"用左边一条 2px 冰蓝线,不用投影:层次靠细线,不靠阴影。 */
638
+ .sess.cur { background: var(--panel-2); border-left: 2px solid var(--ice); }
503
639
  .sess .s-title { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
504
640
  .sess .s-meta { flex: 0 0 auto; color: var(--muted); font-size: 11.5px; }
641
+ /* 「正在跑」这个点是**状态**,和主屏顶栏那颗是同一种意思,所以用同一个冰蓝。
642
+ 这里原来跟着暖金走,结果抽屉一屏能出现三处金(两个点 + 「就在这个工作区」),
643
+ 破了"一屏最多两处"。金只留给"该动手的地方"和"当前这一步"。 */
505
644
  .sess .s-dot, .ws-head .s-dot {
506
645
  flex: 0 0 auto; width: 6px; height: 6px; border-radius: 50%;
507
- background: var(--warn); animation: pulse 1.2s ease-in-out infinite;
646
+ background: var(--run);
508
647
  }
509
648
  /* 「新建会话」那一行。跟会话条目同宽、同缩进,但用强调色——
510
649
  它不是一个会话,是一个动作。长得跟会话一样会让人以为点进去就是那条会话。 */
511
650
  .ws-new {
512
651
  display: flex; align-items: center; gap: 6px; width: 100%;
513
652
  padding: 10px 14px 10px 30px; background: none; border: none;
514
- color: var(--accent); font: inherit; font-size: 14px; text-align: left; cursor: pointer;
653
+ color: var(--ice); font: inherit; font-size: 14px; text-align: left; cursor: pointer;
515
654
  }
516
655
  .ws-new:active { background: var(--panel-2); }
517
656
  .ws-new[disabled] { color: var(--muted); cursor: default; }
@@ -521,7 +660,7 @@
521
660
  .nav-cta {
522
661
  display: flex; align-items: center; gap: 6px; width: 100%;
523
662
  padding: 12px 14px; background: none; border: none; border-bottom: 1px solid var(--line);
524
- color: var(--accent); font: inherit; font-size: 14px; text-align: left; cursor: pointer;
663
+ color: var(--ice); font: inherit; font-size: 14px; text-align: left; cursor: pointer;
525
664
  }
526
665
  .nav-cta:active { background: var(--panel-2); }
527
666
 
@@ -531,8 +670,8 @@
531
670
  overflow-x: auto; white-space: nowrap; border-bottom: 1px solid var(--line);
532
671
  }
533
672
  .crumb button {
534
- background: none; border: none; color: var(--accent); font: inherit;
535
- font-size: 13px; padding: 4px 6px; cursor: pointer; border-radius: 6px;
673
+ background: none; border: none; color: var(--ice); font: inherit;
674
+ font-size: 13px; padding: 4px 6px; cursor: pointer; border-radius: var(--r);
536
675
  }
537
676
  .crumb button:active { background: var(--panel-2); }
538
677
  .crumb .sep { color: var(--muted); font-size: 12px; }
@@ -541,8 +680,8 @@
541
680
  /* 「就选这个目录」——这一屏唯一的主按钮,所以给它实心底。 */
542
681
  .pick-here {
543
682
  display: block; width: calc(100% - 24px); margin: 10px 12px;
544
- padding: 11px; border: none; border-radius: var(--radius);
545
- background: var(--accent); color: #fff; font: inherit; font-size: 14px; cursor: pointer;
683
+ padding: 11px; border: none; border-radius: var(--r);
684
+ background: var(--act); color: var(--on-act); font: inherit; font-size: 14px; cursor: pointer;
546
685
  }
547
686
  .pick-here:active { opacity: .85; }
548
687
  .pick-here[disabled] { background: var(--line); color: var(--muted); cursor: default; }
@@ -553,12 +692,12 @@
553
692
  .mkdir-row input {
554
693
  flex: 1 1 auto; min-width: 0; padding: 9px 10px; font: inherit; font-size: 14px;
555
694
  background: var(--panel-2); border: 1px solid var(--line);
556
- border-radius: 10px; color: var(--fg);
695
+ border-radius: var(--r); color: var(--fg);
557
696
  }
558
697
  .mkdir-row button {
559
698
  flex: 0 0 auto; padding: 9px 12px; font: inherit; font-size: 13px; cursor: pointer;
560
699
  background: var(--panel-2); border: 1px solid var(--line);
561
- border-radius: 10px; color: var(--fg);
700
+ border-radius: var(--r); color: var(--fg);
562
701
  }
563
702
  .mkdir-row button:active { background: var(--line); }
564
703
 
@@ -569,20 +708,22 @@
569
708
  /* 「这不是全部」:垫在聊天内容最上面,往上翻到顶才看得见,不抢内容。 */
570
709
  .history-note {
571
710
  margin: 0 0 14px; padding: 8px 12px; border: 1px dashed var(--line);
572
- border-radius: 10px; color: var(--muted); font-size: 12.5px; line-height: 1.6;
711
+ border-radius: var(--r); color: var(--muted); font-size: 12.5px; line-height: 1.6;
573
712
  }
574
713
 
575
714
  /* ---------- 设置抽屉 ---------- */
576
715
  #settings {
577
- position: fixed; inset: 0; background: rgba(0,0,0,.55);
716
+ position: fixed; inset: 0; background: var(--scrim);
578
717
  display: none; align-items: flex-end; z-index: 50;
579
718
  }
580
719
  #settings.open { display: flex; }
581
720
  .sheet {
582
721
  width: 100%; max-height: 86vh; overflow-y: auto;
583
- background: var(--panel); border-radius: 18px 18px 0 0;
722
+ background: var(--glass); border-radius: var(--r) var(--r) 0 0;
584
723
  padding: 18px 16px calc(env(safe-area-inset-bottom) + 18px);
585
724
  border-top: 1px solid var(--line);
725
+ -webkit-backdrop-filter: blur(14px) saturate(1.1);
726
+ backdrop-filter: blur(14px) saturate(1.1);
586
727
  }
587
728
  .sheet h2 { margin: 0 0 14px; font-size: 16px; }
588
729
  .row {
@@ -595,18 +736,18 @@
595
736
  /* flex-shrink: 0 不能省。它是弹性项,左边的说明文字一长就会被压缩;
596
737
  而它自己带着 overflow: hidden,所以被压掉的那一段不是跑到屏幕外,是**被裁掉**——
597
738
  2026-09-25 用户截图里那个「关」字就是这么没的。 */
598
- .seg { display: flex; flex-shrink: 0; border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
739
+ .seg { display: flex; flex-shrink: 0; border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; }
599
740
  .seg button {
600
741
  padding: 7px 12px; border: none; background: var(--panel-2); color: var(--muted);
601
742
  font-size: 13px; cursor: pointer;
602
743
  }
603
- .seg button.active { background: var(--accent); color: #fff; }
744
+ .seg button.active { background: var(--ice); color: var(--on-act); }
604
745
  select, .btn {
605
- padding: 8px 12px; border-radius: 10px; border: 1px solid var(--line);
746
+ padding: 8px 12px; border-radius: var(--r); border: 1px solid var(--line);
606
747
  background: var(--panel-2); color: var(--fg); font: inherit; font-size: 14px;
607
748
  }
608
749
  .btn { cursor: pointer; }
609
- .btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
750
+ .btn.primary { background: var(--act); border-color: var(--act); color: var(--on-act); font-weight: 600; }
610
751
  .sheet .foot { margin-top: 18px; display: flex; gap: 10px; }
611
752
  .sheet .foot .btn { flex: 1 1 0; text-align: center; }
612
753
 
@@ -618,19 +759,20 @@
618
759
  #gate h1 { font-size: 20px; margin: 0; }
619
760
  #gate p { color: var(--muted); font-size: 14px; margin: 0; }
620
761
  #gate input {
621
- padding: 12px 14px; border-radius: 12px; border: 1px solid var(--line);
762
+ padding: 12px 14px; border-radius: var(--r); border: 1px solid var(--line);
622
763
  background: var(--panel-2); color: var(--fg); font: inherit; font-size: 16px;
623
764
  }
624
765
  #gate button {
625
- padding: 13px; border-radius: 12px; border: none; background: var(--accent);
626
- color: #fff; font-size: 16px; font-weight: 600;
766
+ padding: 13px; border-radius: var(--r); border: none; background: var(--act);
767
+ color: var(--on-act); font-size: 16px; font-weight: 600;
627
768
  }
628
769
  #gate .err { color: var(--err); font-size: 13px; min-height: 18px; }
629
770
 
630
771
  #toast {
631
772
  position: fixed; left: 50%; bottom: 84px; transform: translateX(-50%) translateY(8px);
632
- background: rgba(40,44,54,.97); color: var(--fg); padding: 9px 16px;
633
- border-radius: 20px; font-size: 13.5px; opacity: 0; pointer-events: none;
773
+ background: var(--pop); color: var(--fg); padding: 9px 16px;
774
+ border-radius: var(--r); font-size: 13.5px; opacity: 0; pointer-events: none;
775
+ border: 1px solid var(--line);
634
776
  transition: opacity .2s, transform .2s; z-index: 200; max-width: 86vw; text-align: center;
635
777
  }
636
778
  #toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
@@ -639,25 +781,48 @@
639
781
  整屏遮住聊天区:手机上一次只专心答一题,答完自动消失。
640
782
  之所以遮全屏而不是弹个小条:非技术用户看到"电脑在等你回答"时,
641
783
  最怕的是不知道该点哪里;整屏只剩问题本身和按钮,没有第二个选择。 */
642
- #askCard { position: fixed; inset: 0; z-index: 300; background: #0e1116;
784
+ #askCard { position: fixed; inset: 0; z-index: 300; background: var(--bg);
643
785
  display: none; flex-direction: column; padding: 18px 16px calc(18px + env(safe-area-inset-bottom)); overflow-y: auto; }
644
786
  #askCard.on { display: flex; }
645
- #askCard .qhead { font-size: 13px; opacity: .6; margin-bottom: 12px; }
787
+ #askCard .qhead { font-size: 13px; color: var(--muted); margin-bottom: 12px; }
646
788
  #askCard .qtext { font-size: 18px; line-height: 1.6; margin-bottom: 10px; }
647
- #askCard .qdetail { font-size: 14px; line-height: 1.65; opacity: .75; margin-bottom: 16px; white-space: pre-wrap; }
789
+ #askCard .qdetail { font-size: 14px; line-height: 1.65; color: var(--muted); margin-bottom: 16px; white-space: pre-wrap; }
648
790
  #askCard .opt { display: block; width: 100%; text-align: left; margin: 0 0 10px; padding: 15px 16px;
649
- font-size: 16px; line-height: 1.5; border-radius: 12px; border: 1px solid #2a3140;
650
- background: #161b23; color: inherit; font-family: inherit; }
651
- #askCard .opt.sel { border-color: #3b82f6; background: #17243a; }
652
- #askCard .opt small { display: block; opacity: .7; font-size: 13px; margin-top: 5px; }
791
+ font-size: 16px; line-height: 1.5; border-radius: var(--r); border: 1px solid var(--line);
792
+ background: var(--panel-2); color: inherit; font-family: inherit; }
793
+ /* 选中的那一条用冰蓝描边:它是"你选的",不是"你要按的主按钮"——主按钮是暖金。 */
794
+ #askCard .opt.sel { border-color: var(--ice); background: rgba(169, 200, 232, .1); }
795
+ #askCard .opt small { display: block; color: var(--muted); font-size: 13px; margin-top: 5px; }
653
796
  #askCard textarea { width: 100%; box-sizing: border-box; font-size: 16px; line-height: 1.6;
654
- border-radius: 12px; border: 1px solid #2a3140; background: #161b23; color: inherit;
797
+ border-radius: var(--r); border: 1px solid var(--line); background: var(--panel-2); color: inherit;
655
798
  padding: 12px; font-family: inherit; resize: vertical; }
656
799
  #askCard .qfoot { margin-top: auto; display: flex; gap: 10px; padding-top: 16px; }
657
- #askCard .qfoot button { flex: 1; padding: 15px 8px; font-size: 16px; border-radius: 12px;
658
- border: 1px solid #2a3140; background: #161b23; color: inherit; font-family: inherit; }
659
- #askCard .qfoot button#askNext { background: #2563eb; border-color: #2563eb; color: #fff; }
800
+ #askCard .qfoot button { flex: 1; padding: 15px 8px; font-size: 16px; border-radius: var(--r);
801
+ border: 1px solid var(--line); background: var(--panel-2); color: inherit; font-family: inherit; }
802
+ /* 这一屏唯一的主按钮,暖金。 */
803
+ #askCard .qfoot button#askNext { background: var(--act); border-color: var(--act); color: var(--on-act); font-weight: 600; }
804
+ /* 没有毛玻璃的浏览器:退回实色面板。别让界面变成一片糊住的半透明。 */
805
+ @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
806
+ header, footer, .attach-bar, .cmd-menu, .nav-panel, .sheet { background: var(--panel); }
807
+ }
808
+ /* 细线图标:和回形针、复制按钮那套同一副笔法(1.5px 描边、圆头圆角)。 */
809
+ .icon-btn svg {
810
+ width: 18px; height: 18px; fill: none; stroke: currentColor;
811
+ stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round;
812
+ }
660
813
  </style>
814
+ <script>
815
+ // 主题必须在**首屏画出来之前**定下来,否则浅色手机打开会先闪一下深色底。
816
+ // 所以这一段留在 <head> 里、样式表后面,**别挪到页面底部那段脚本区去**。
817
+ (function () {
818
+ try {
819
+ var pick = localStorage.getItem('dshMiniTheme') || 'dark';
820
+ var dark = !window.matchMedia || window.matchMedia('(prefers-color-scheme: dark)').matches;
821
+ document.documentElement.setAttribute('data-theme',
822
+ pick === 'auto' ? (dark ? 'dark' : 'light') : pick);
823
+ } catch (e) { /* 无痕模式下 localStorage 会抛;按默认深色走,不影响页面 */ }
824
+ })();
825
+ </script>
661
826
  </head>
662
827
  <body>
663
828
 
@@ -678,11 +843,11 @@
678
843
  </div>
679
844
 
680
845
  <header>
681
- <button class="icon-btn" id="btnNav" aria-label="工作区与会话">☰</button>
846
+ <button class="icon-btn" id="btnNav" aria-label="工作区与会话"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 6h18M3 12h18M3 18h18"/></svg></button>
682
847
  <span class="dot" id="dot"></span>
683
- <span class="brand">DSH mini remote</span>
848
+ <span class="brand">极简遥控器</span>
684
849
  <span class="status" id="status">连接中…</span>
685
- <button class="icon-btn" id="btnSettings" aria-label="设置">⚙</button>
850
+ <button class="icon-btn" id="btnSettings" aria-label="设置"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h8M16 7h4M4 17h4M12 17h8"/><circle cx="14" cy="7" r="2.2"/><circle cx="10" cy="17" r="2.2"/></svg></button>
686
851
  </header>
687
852
 
688
853
  <!-- 模型与思考强度(2026-09-25 用户要求:手机上要能改每个会话的模型和强度)。
@@ -746,8 +911,8 @@
746
911
  <aside class="nav-panel">
747
912
  <div class="nav-head">
748
913
  <span>工作区与会话</span>
749
- <button class="icon-btn" id="btnNavRefresh" aria-label="刷新">⟳</button>
750
- <button class="icon-btn" id="btnNavClose" aria-label="关闭">✕</button>
914
+ <button class="icon-btn" id="btnNavRefresh" aria-label="刷新"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 11a8 8 0 1 0-2.3 5.7"/><path d="M20 5v6h-6"/></svg></button>
915
+ <button class="icon-btn" id="btnNavClose" aria-label="关闭"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
751
916
  </div>
752
917
  <div class="nav-body" id="navBody"></div>
753
918
  </aside>
@@ -768,6 +933,20 @@
768
933
  </div>
769
934
  </div>
770
935
 
936
+ <!-- 外观。深色是原来的样子;浅色的色调跟宣传图(docs/promo.png)统一:
937
+ 近白冰蓝的底、深宝蓝的字与状态色、白色卡片。选择记在这台设备上。 -->
938
+ <div class="row">
939
+ <div>
940
+ <div class="label">外观</div>
941
+ <div class="hint">浅色的色调跟宣传图一致</div>
942
+ </div>
943
+ <div class="seg" id="segTheme">
944
+ <button data-theme="dark">深色</button>
945
+ <button data-theme="light">浅色</button>
946
+ <button data-theme="auto">跟随系统</button>
947
+ </div>
948
+ </div>
949
+
771
950
  <!-- 权限档位。整块由脚本填:这台电脑有哪些档位是**运行时读**的,一个都不写死。
772
951
  服务拿不到时整块不显示——它是附加能力,缺了遥控本身照常。 -->
773
952
  <div id="permBlock" hidden>
@@ -880,6 +1059,9 @@
880
1059
  var state = {
881
1060
  token: params.get('token') || localStorage.getItem('dshMiniToken') || '',
882
1061
  mode: localStorage.getItem('dshMiniMode') || DEFAULT_MODE || 'minimal',
1062
+ // 外观:'dark' | 'light' | 'auto'。默认深色——不给老用户换掉现在的样子;
1063
+ // 要浅色或跟着系统走,去设置页「外观」点一下。
1064
+ theme: localStorage.getItem('dshMiniTheme') || 'dark',
883
1065
  sound: localStorage.getItem('dshMiniSound') !== 'off',
884
1066
  // 锁屏也能提醒(系统通知)。默认关着——它要用户主动点一下才会申请权限,
885
1067
  // 不能替他决定。明文连接下这一项永远是空转的,见下面「锁屏也能提醒」那一段。
@@ -965,6 +1147,18 @@
965
1147
  return CODE_SLOT + (codes.length - 1) + CODE_SLOT;
966
1148
  });
967
1149
  out = out
1150
+ // 图片:![说明](地址)。地址两种写法——
1151
+ // ① 完整 http(s) 链接:原样用,手机直接去那个网站取图;
1152
+ // ② 一个名字:走本机的图片通道 /mini/art/<名字>.webp,跟立绘同一条路、
1153
+ // 同一样要密码,也只认 lib/art/ 下「小写字母数字连字符 + .webp」的文件名。
1154
+ // 想让手机看到电脑上的图,把图放进 lib/art/、名字以 out- 开头即可。
1155
+ // 必须排在下面那条链接规则前面:否则 `![x](y)` 里的 `[x](y)` 会先被链接规则
1156
+ // 吃掉,只剩下一个光秃秃的感叹号。
1157
+ .replace(/!\[([^\]]*)\]\(([^)\s]+)\)/g, function (_, alt, src) {
1158
+ var url = /^https?:\/\//.test(src) ? src : artUrl(src.replace(/\.webp$/, ''));
1159
+ return '<img class="md-img" src="' + url + '" alt="' + alt + '"'
1160
+ + ' loading="lazy" referrerpolicy="no-referrer">';
1161
+ })
968
1162
  .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
969
1163
  .replace(/(^|[^*\w])\*([^*\n]+)\*/g, '$1<em>$2</em>')
970
1164
  .replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g,
@@ -1571,16 +1765,31 @@
1571
1765
  function paintQueue() {
1572
1766
  var el = $('queue');
1573
1767
  var list = state.queued || [];
1768
+ // 刻度尺上第一个点永远是**正在跑的那条**,后面每个点对应一条真排在队里的指令。
1769
+ //
1770
+ // 2026-09-27 用户裁决:**队列是空的就整块收起来,不显示。**
1771
+ // 上一版这里是「只要在跑就留着」,理由是一个金点表示"你这一轮正是第 1 步";
1772
+ // 用户实际看到的是「排队中 0 条」配一个孤零零的点,判定为噪音。撤销。
1773
+ // (上面那段注释里"哪怕只有一格,秩序也在"的说法同样作废。)
1574
1774
  if (!list.length) { el.hidden = true; el.innerHTML = ''; return; }
1575
1775
  el.hidden = false;
1776
+ // 最多画 12 个点:再长这条尺子就成噪点了,超出缀一个省略号。
1777
+ var nodes = '<div class="q-node cur"><i></i><b>1</b></div>';
1778
+ var shown = Math.min(list.length, 11);
1779
+ for (var n = 0; n < shown; n += 1) {
1780
+ nodes += '<div class="q-node"><i></i><b>' + (n + 2) + '</b></div>';
1781
+ }
1782
+ if (list.length > shown) nodes += '<div class="q-node"><i></i><b>…</b></div>';
1576
1783
  var rows = '';
1577
1784
  for (var i = 0; i < list.length; i += 1) {
1578
1785
  rows += '<div class="q-row">'
1579
1786
  + '<span class="q-text">' + escapeHtml(list[i].text) + '</span>'
1580
1787
  + '<button class="q-drop" type="button" data-drop="' + escapeHtml(list[i].id) + '"'
1581
- + ' aria-label="撤掉这条">✕</button></div>';
1788
+ + ' aria-label="撤掉这条"><svg viewBox="0 0 24 24" aria-hidden="true">'
1789
+ + '<path d="M6 6l12 12M18 6L6 18"/></svg></button></div>';
1582
1790
  }
1583
- el.innerHTML = '<div class="q-head">排队中 ' + list.length + ' 条 · 跑完这轮就轮到它们</div>' + rows;
1791
+ el.innerHTML = '<div class="q-head">排队中 ' + list.length + ' 条 · 跑完这轮就轮到它们</div>'
1792
+ + '<div class="q-scale">' + nodes + '</div>' + rows;
1584
1793
  }
1585
1794
 
1586
1795
  /**
@@ -3110,6 +3319,39 @@
3110
3319
  syncSeg();
3111
3320
  });
3112
3321
  });
3322
+
3323
+ // ---------------- 外观(深色 / 浅色 / 跟随系统) ----------------
3324
+ // 只改 <html> 上的 data-theme,样式表里两套令牌各管各的。这里不碰任何具体颜色,
3325
+ // 免得以后加第三套主题还要回来改这一段。
3326
+ // 首屏那次由 <head> 里那一小段负责(不然会闪);这一段管切换和"跟着系统变"。
3327
+ function resolvedTheme() {
3328
+ if (state.theme !== 'auto') return state.theme;
3329
+ var dark = !window.matchMedia || window.matchMedia('(prefers-color-scheme: dark)').matches;
3330
+ return dark ? 'dark' : 'light';
3331
+ }
3332
+ function applyTheme() {
3333
+ var use = resolvedTheme() === 'light' ? 'light' : 'dark';
3334
+ document.documentElement.setAttribute('data-theme', use);
3335
+ // 手机状态栏那块颜色也跟着走,否则浅色页配一条深色状态栏,像没画完。
3336
+ var meta = document.querySelector('meta[name="theme-color"]');
3337
+ if (meta) meta.setAttribute('content', use === 'light' ? '#eaf3fc' : '#0a0f16');
3338
+ }
3339
+ Array.prototype.forEach.call($('segTheme').children, function (btn) {
3340
+ btn.addEventListener('click', function () {
3341
+ state.theme = btn.dataset.theme;
3342
+ localStorage.setItem('dshMiniTheme', state.theme);
3343
+ applyTheme();
3344
+ syncSeg();
3345
+ });
3346
+ });
3347
+ if (window.matchMedia) {
3348
+ var themeMq = window.matchMedia('(prefers-color-scheme: dark)');
3349
+ var onThemeMq = function () { if (state.theme === 'auto') applyTheme(); };
3350
+ if (themeMq.addEventListener) themeMq.addEventListener('change', onThemeMq);
3351
+ else if (themeMq.addListener) themeMq.addListener(onThemeMq);
3352
+ }
3353
+ applyTheme(); // 补上首屏那次没做的一件事:把状态栏颜色对齐
3354
+
3113
3355
  function syncSeg() {
3114
3356
  Array.prototype.forEach.call($('segMode').children, function (b) {
3115
3357
  b.classList.toggle('active', b.dataset.mode === state.mode);
@@ -3117,6 +3359,9 @@
3117
3359
  Array.prototype.forEach.call($('segSound').children, function (b) {
3118
3360
  b.classList.toggle('active', (b.dataset.sound === 'on') === state.sound);
3119
3361
  });
3362
+ Array.prototype.forEach.call($('segTheme').children, function (b) {
3363
+ b.classList.toggle('active', b.dataset.theme === state.theme);
3364
+ });
3120
3365
  }
3121
3366
 
3122
3367
  // 会话切换挪到左侧导航栏了(原来这里是设置里的下拉框)。
@@ -3410,14 +3655,14 @@
3410
3655
  + 'color:inherit;padding:11px 2px;border-bottom:1px solid var(--line,#333)'
3411
3656
  var HEAD = 'padding:14px 2px 4px;font-size:12px;color:var(--dim,#888)'
3412
3657
  var CHIP = 'margin:0 6px 6px 0;padding:5px 13px;border:1px solid var(--line,#444);'
3413
- + 'border-radius:999px;background:none;font:inherit;color:inherit;font-size:13px'
3658
+ + 'border-radius:var(--r,2px);background:none;font:inherit;color:inherit;font-size:13px'
3414
3659
 
3415
3660
  // 面板用代码建出来、挂在 body 末尾,而不是写进页面结构里——这样这一整块的
3416
3661
  // 改动全部落在脚本内部,不用动 HTML,也就不会碰到别的部分。
3417
- var mask = el('div', 'position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:69;display:none')
3662
+ var mask = el('div', 'position:fixed;inset:0;background:var(--scrim);z-index:69;display:none')
3418
3663
  var sheet = el('div', 'position:fixed;left:0;right:0;bottom:0;z-index:70;display:none;'
3419
3664
  + 'max-height:72vh;overflow-y:auto;background:var(--bg,#1b1b1b);'
3420
- + 'border-top:1px solid var(--line,#444);border-radius:14px 14px 0 0;'
3665
+ + 'border-top:1px solid var(--line,#444);border-radius:var(--r,2px) var(--r,2px) 0 0;'
3421
3666
  + 'padding:10px 14px calc(16px + env(safe-area-inset-bottom))')
3422
3667
  document.body.appendChild(mask)
3423
3668
  document.body.appendChild(sheet)