wezard 2.1.3 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +15 -8
  2. package/dist/daemon/cc-session.js +14 -1
  3. package/dist/daemon/cc-session.js.map +1 -1
  4. package/dist/daemon/detail.js +40 -7
  5. package/dist/daemon/detail.js.map +1 -1
  6. package/dist/daemon/handoff.js +132 -0
  7. package/dist/daemon/handoff.js.map +1 -0
  8. package/dist/daemon/inbound.js +13 -4
  9. package/dist/daemon/inbound.js.map +1 -1
  10. package/dist/daemon/index.js +191 -99
  11. package/dist/daemon/index.js.map +1 -1
  12. package/dist/daemon/mirror-bridge.js +161 -12
  13. package/dist/daemon/mirror-bridge.js.map +1 -1
  14. package/dist/daemon/peers.js +114 -34
  15. package/dist/daemon/peers.js.map +1 -1
  16. package/dist/daemon/receipts.js +190 -0
  17. package/dist/daemon/receipts.js.map +1 -0
  18. package/dist/daemon/route.js +92 -0
  19. package/dist/daemon/route.js.map +1 -0
  20. package/dist/daemon/wizard.js +38 -19
  21. package/dist/daemon/wizard.js.map +1 -1
  22. package/dist/daemon/ws.js +3 -0
  23. package/dist/daemon/ws.js.map +1 -1
  24. package/dist/mcp/server.js +60 -25
  25. package/dist/mcp/server.js.map +1 -1
  26. package/dist/shared/chat-http.js +101 -21
  27. package/dist/shared/chat-http.js.map +1 -1
  28. package/dist/shared/chat-render.js +0 -1
  29. package/dist/shared/chat-render.js.map +1 -1
  30. package/dist/shared/chat-view.js +2 -9
  31. package/dist/shared/chat-view.js.map +1 -1
  32. package/dist/shared/config.js +6 -0
  33. package/dist/shared/config.js.map +1 -1
  34. package/dist/shared/detail-render.js +3 -2
  35. package/dist/shared/detail-render.js.map +1 -1
  36. package/dist/shared/detail-store.js +1 -1
  37. package/dist/shared/detail-store.js.map +1 -1
  38. package/dist/shared/keepalive.js +46 -0
  39. package/dist/shared/keepalive.js.map +1 -0
  40. package/dist/shared/reminder.js +13 -0
  41. package/dist/shared/reminder.js.map +1 -1
  42. package/dist/shared/role-render.js +2 -1
  43. package/dist/shared/role-render.js.map +1 -1
  44. package/dist/shared/role-search.js +100 -0
  45. package/dist/shared/role-search.js.map +1 -0
  46. package/dist/shared/role-view.js +85 -26
  47. package/dist/shared/role-view.js.map +1 -1
  48. package/dist/shared/world.js +38 -8
  49. package/dist/shared/world.js.map +1 -1
  50. package/dist/svr/index.js +1 -1
  51. package/dist/svr/index.js.map +1 -1
  52. package/package.json +1 -1
  53. package/web/chat.css +420 -327
  54. package/web/chat.js +924 -728
package/web/chat.css CHANGED
@@ -18,6 +18,10 @@
18
18
  --mine-line: #c9d7f3;
19
19
  --accent: #2f55c9; /* 视角色: 名字的点、选中态、自己的气泡描边 */
20
20
  --priv: #9a5b10; /* 私聊 */
21
+ /* 头像底: 不透明, 且由 card/accent 推出 —— 叠在选中/高亮/关系图任何底色上都不变色, 换主题时随 token 走。 */
22
+ --av-bg: var(--card);
23
+ --av-line: var(--line);
24
+ --av-hot: color-mix(in srgb, var(--accent) 14%, var(--card));
21
25
  }
22
26
 
23
27
  html { height: 100% }
@@ -47,59 +51,73 @@ button { font: inherit; color: inherit }
47
51
 
48
52
  /* ── 左栏顶: 当前 role 的名片 (身份); 关系/日程入口是名片下、会话列表上的一排 ──── */
49
53
  .rolebar {
50
- position: relative; flex: none; padding: 16px 16px 14px; border-bottom: 1px solid var(--line);
54
+ /* margin 归零: 通用 header 规则带着 8px 下边距, 会把下面的入口条顶得上宽下窄。 */
55
+ position: relative; flex: none; margin: 0; padding: 16px 16px 14px; border-bottom: 1px solid var(--line);
51
56
  display: flex; flex-direction: column; gap: 8px; background: var(--card);
57
+ /* 通用 header 规则带着 flex-wrap:wrap + align-items:baseline —— 竖轴上那会让名片按内容撑宽 (max-content),
58
+ 长名字 / 长路径就冲出侧栏。 */
59
+ flex-wrap: nowrap; align-items: stretch;
52
60
  }
61
+ .rolebar > .who { min-width: 0 }
53
62
  .rolebar .id { display: flex; gap: 10px; align-items: center; min-width: 0 }
54
63
  .rolebar .id .av { font-size: 26px; line-height: 1; flex: none }
55
64
  .rolebar .id .l { min-width: 0; display: flex; flex-direction: column; gap: 2px }
56
65
  .rolebar .id .nm { font-size: 18px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
57
- .rolebar .id .k { font-size: 11.5px; color: var(--mute) }
58
- /* 名片上的状态: 只有执行中带颜色与呼吸, 空闲 / 已关闭是一个安静的词。 */
59
- .rolebar .st { margin-left: 8px; white-space: nowrap }
60
- .rolebar .st::before {
61
- content: ""; display: inline-block; width: 7px; height: 7px; margin-right: 4px;
62
- border-radius: 50%; background: #b1bac4; vertical-align: 1px;
63
- }
64
- .rolebar .st.run { color: #1a7f37 }
65
- .rolebar .st.run::before { background: #1a7f37; animation: pulse 1.4s infinite }
66
- .rolebar .st.off { color: var(--faint) }
67
- .rolebar .st.off::before { background: transparent; box-shadow: inset 0 0 0 1.5px #b1bac4 }
68
- .rolebar .job { margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--ink); opacity: .8 }
69
- .rolebar .facts { display: flex; gap: 6px 12px; flex-wrap: wrap; font-size: 11.5px; color: var(--mute) }
70
- .rolebar .facts span { white-space: nowrap }
66
+ .rolebar .id .k { font-size: 11.5px; color: var(--mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
67
+ /* 职责是一句话, 至多两行; 截掉的悬停看全文 (chat.js 的截断提示)。 */
68
+ .rolebar .job {
69
+ margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--ink); opacity: .8; word-break: break-word;
70
+ display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
71
+ }
72
+ .rolebar .facts { display: flex; gap: 6px 12px; flex-wrap: wrap; font-size: 11.5px; color: var(--mute); min-width: 0 }
73
+ /* 每条事实自身不折行, 一行排不下就落到下一行; 独占一行仍超宽 (长路径 / 长群名) 才省略。 */
74
+ .rolebar .facts span { white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis }
71
75
  .rolebar .go { cursor: pointer; color: var(--accent) }
72
76
  .rolebar .go:hover { text-decoration: underline }
73
- .acts { flex: none; display: flex; align-items: center; padding: 9px 6px; border-bottom: 1px solid var(--line) }
77
+ /* 分隔线两端收进来, 与上面搜索框的左右边距对齐, 不顶满侧栏。 */
78
+ .acts { flex: none; display: flex; justify-content: center; align-items: center; gap: 2px; margin: 0 10px; padding: 4px 0 8px; border-bottom: 1px solid var(--line) }
74
79
  .acts:empty { display: none }
75
80
  /* 旧外壳 (daemon 未重启) 里它还在名片内 —— 那里不需要自己的内边距与分隔线。 */
76
- .rolebar .acts { padding: 0; border: 0 }
77
- /* 文本按钮: 无底无框, 行高就是两者之间那条分割线的高度。 */
81
+ .rolebar .acts { margin: 0; padding: 0; border: 0 }
82
+ /* 入口: 图标 + 名字 + 淡色副标题, 无框无底; hover 才浮出一层浅底, 打开着的染视角色。 */
78
83
  .bd {
79
- font-size: 12.5px; line-height: 14px; padding: 0 10px; border: 0; cursor: pointer;
80
- background: none; color: var(--mute); white-space: nowrap;
81
- }
82
- .bd + .bd { border-left: 1px solid var(--line) }
83
- .bd:hover, .bd.on { color: var(--accent) }
84
- .bd b { font-weight: 600; margin-left: 4px; font-variant-numeric: tabular-nums }
85
- /* session 切换: 按钮 + 浮层列表, 取代原生 <select>。 */
86
- .sp { position: relative; flex: 1 1 100%; min-width: 0 }
84
+ flex: none; padding: 1px 4px; border: 0; border-radius: 4px; background: none; cursor: pointer; white-space: nowrap;
85
+ font-size: 12px; color: var(--mute); transition: color .12s;
86
+ }
87
+ .bd:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 3px }
88
+ .bd.on { color: var(--accent); font-weight: 600 }
89
+ .bd.bad { color: #cf222e }
90
+ .acts .sep { color: var(--line); font-size: 12px; user-select: none }
91
+ /* session 切换: name 右边一个小号文字触发器 + 浮层列表。name 先省略, 触发器不让位。 */
92
+ .rolebar .id .l { flex: 1 }
93
+ .rolebar .nl { display: flex; align-items: center; gap: 8px; min-width: 0 }
94
+ #rb-st { flex: none; display: inline-flex }
95
+ .rolebar .nl .nm { min-width: 0; flex: 0 1 auto }
96
+ .rolebar .nl .cp { min-width: 0; flex: 0 1 auto; display: inline-flex; cursor: copy }
97
+ .rolebar .nl .cp:hover .nm { text-decoration: underline dotted var(--faint); text-underline-offset: 3px }
98
+ .toast {
99
+ position: fixed; left: 50%; bottom: 28px; z-index: 1000; transform: translate(-50%, 8px);
100
+ padding: 6px 12px; border-radius: 8px; font-size: 12.5px; color: #fff; background: rgba(28, 36, 48, .88);
101
+ opacity: 0; pointer-events: none; transition: opacity .18s, transform .18s;
102
+ }
103
+ .toast.on { opacity: 1; transform: translate(-50%, 0) }
104
+ #rb-sp { flex: none; display: inline-flex }
105
+ .sp { position: relative; display: inline-flex }
87
106
  .sp-btn {
88
- width: 100%; display: flex; align-items: center; gap: 8px; cursor: pointer; text-align: left;
89
- font-size: 12px; background: var(--card); border: 1px solid var(--line); border-radius: 8px; padding: 5px 10px;
90
- transition: border-color .12s, box-shadow .12s;
91
- }
92
- .sp-btn:hover, .sp.open .sp-btn { border-color: var(--accent) }
93
- .sp.open .sp-btn { box-shadow: 0 0 0 3px #2f55c91f }
94
- .sp-btn .lb { font-size: 10px; color: var(--faint); text-transform: uppercase; letter-spacing: .4px; flex: none }
95
- .sp-btn .v { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-variant-numeric: tabular-nums }
107
+ display: inline-flex; align-items: center; gap: 2px; cursor: pointer; white-space: nowrap;
108
+ font-size: 11.5px; color: var(--mute); background: none; border: 0; border-radius: 5px; padding: 1px 4px;
109
+ font-variant-numeric: tabular-nums; transition: background .12s, color .12s;
110
+ }
111
+ .sp-btn:hover, .sp.open .sp-btn { background: var(--paper); color: var(--ink) }
112
+ /* 箭头用边框画, 不用 ▾ 字形 —— 字形贴着基线, 放大后总是偏下。 */
96
113
  .sp-btn .car {
97
- flex: none; width: 6px; height: 6px; margin: -3px 2px 0 0; border: solid var(--mute); border-width: 0 1.5px 1.5px 0;
98
- transform: rotate(45deg); transition: transform .15s;
114
+ width: 0; height: 0; margin-left: 3px; border: 5px solid transparent; border-top: 6px solid var(--faint); border-bottom: 0;
115
+ transition: transform .15s;
99
116
  }
100
- .sp.open .sp-btn .car { transform: rotate(-135deg); margin-top: 3px }
117
+ .sp.open .sp-btn .car { transform: rotate(180deg) }
101
118
  .sp-list {
102
- position: absolute; z-index: 20; left: 0; right: 0; top: calc(100% + 4px);
119
+ /* 以触发器左下角为锚向右下展开 —— 触发器贴着 name 右边, 朝左开会被侧栏左沿裁掉。 */
120
+ position: absolute; z-index: 20; left: 0; top: calc(100% + 4px); width: 196px; max-width: calc(100vw - 24px);
103
121
  max-height: 280px; overflow-y: auto; overscroll-behavior: contain;
104
122
  background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 4px;
105
123
  box-shadow: 0 8px 24px #1c24301f, 0 2px 6px #1c24300f;
@@ -107,45 +125,46 @@ button { font: inherit; color: inherit }
107
125
  }
108
126
  .sp.open .sp-list { opacity: 1; transform: none; pointer-events: auto }
109
127
  .sp-it {
110
- width: 100%; display: grid; grid-template-columns: auto 1fr auto; gap: 10px; align-items: baseline;
128
+ width: 100%; display: flex; gap: 10px; align-items: baseline;
111
129
  background: none; border: 0; border-radius: 6px; padding: 6px 8px; cursor: pointer; text-align: left; font-size: 12px;
112
130
  }
113
131
  .sp-it:hover { background: var(--paper) }
114
132
  .sp-it.on { background: var(--mine); color: var(--accent) }
115
- .sp-it .id { font-family: ui-monospace, Menlo, monospace; font-weight: 600 }
116
- .sp-it .ago { color: var(--mute) }
117
- .sp-it .n { color: var(--faint); font-variant-numeric: tabular-nums; grid-column: 3 }
133
+ .sp-it .id { flex: 1; min-width: 0; font-variant-numeric: tabular-nums }
134
+ .sp-it .n { flex: none; color: var(--faint); font-variant-numeric: tabular-nums }
118
135
  .sp-it.all { border-bottom: 1px solid var(--line); border-radius: 6px 6px 0 0; margin-bottom: 3px }
119
- .sp-it.all .id { font-family: inherit }
120
-
121
- /* 左栏底: session 切换 —— 轮次的事, 与顶上的身份分开, 钉在会话列表下面。 */
122
- .sidefoot { flex: none; border-top: 1px solid var(--line); padding: 10px 12px; background: var(--paper) }
123
- /* 在底部, 浮层往上开。 */
124
- .sidefoot .sp-list { top: auto; bottom: calc(100% + 4px); transform: translateY(4px) }
125
- .sidefoot .sp.open .sp-list { transform: none }
126
136
 
127
137
  /* ── 用量条: 一组轮次的总账 ─────────────────────────────────────────
128
138
  一个组件两处挂: 整页页脚 (wizard 的总账) / 聊天窗口底 (人与某个 wizard 这段往来的账)。
129
- 宽屏一行排完: 模型 · 指标 · I/O 条 + 图例。每一组自身不折行, 一行排不下时
130
- 整组落到下一行 —— 窄屏自然变成两三行, 不需要另一套结构。 */
139
+ 永远单行: 主人 · 模型 · 指标 · 图例, I/O 分布是贴底的色带。排不下时 chat.js 的 fitUsage
140
+ 按容器宽度一级级加 f-* 藏掉次要的 (分布文字 → 名字 → 耗时), 不按视口断点。 */
131
141
  .usage {
132
- flex: none; display: flex; align-items: center; flex-wrap: wrap; gap: 6px 20px;
133
- padding: 7px 20px; border-top: 1px solid var(--line); background: var(--paper);
134
- font-family: ui-monospace, Menlo, monospace; font-size: 11px; color: var(--mute);
142
+ /* 整条只有一个字号、一个间隔: 指标、模型名、主人、图例都用它。 */
143
+ --u-gap: 14px;
144
+ flex: none; display: flex; align-items: center; flex-wrap: nowrap; gap: var(--u-gap); overflow: hidden; white-space: nowrap;
145
+ position: relative; padding: 7px 20px 9px; border-top: 1px solid var(--line); background: var(--paper);
146
+ font-family: ui-monospace, Menlo, monospace; font-size: 11.5px; color: var(--mute);
135
147
  font-variant-numeric: tabular-nums;
136
148
  }
137
- .usage b { font-weight: 700; color: var(--ink) }
138
- .usage .u-lb { max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
139
- .usage .u-kvs { display: flex; gap: 4px 16px }
140
- .usage .kv { white-space: nowrap; font-size: 9.5px; color: var(--faint); text-transform: uppercase; letter-spacing: .4px }
141
- .usage .kv b { font-size: 12.5px; margin-right: 4px; letter-spacing: 0; text-transform: none }
142
- .usage .u-io { display: flex; align-items: center; gap: 12px; min-width: 0 }
143
- .usage .bar { flex: none; width: 140px; display: flex; height: 6px; border-radius: 3px; overflow: hidden; background: var(--line) }
144
- .usage .bar .seg { height: 100% }
145
- .usage .leg { display: flex; gap: 4px 12px }
149
+ .usage b { font-weight: 400; color: var(--ink) }
150
+ .usage > * { flex: none }
151
+ .usage .u-who { display: flex; align-items: center; gap: 4px; max-width: 200px }
152
+ .usage .u-nm { overflow: hidden; text-overflow: ellipsis; color: var(--faint) }
153
+ .usage .u-lb { max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 700; color: var(--accent) }
154
+ .usage .u-ic { font-style: normal; margin-right: 4px }
155
+ .usage .u-kvs, .usage .leg { display: flex; gap: var(--u-gap) }
156
+ .usage .kv { white-space: nowrap }
157
+ /* I/O 分布 = bar 的底边: 横跨整宽的细色带, 不占行; 悬停加粗一点好瞄准某一段。 */
158
+ .usage .bar {
159
+ position: absolute; left: 0; right: 0; bottom: 0; height: 3px; display: flex;
160
+ background: var(--line); transition: height .12s;
161
+ }
162
+ .usage .bar:hover { height: 6px }
163
+ .usage .bar .seg { flex: 1 1 0; min-width: 2px; height: 100% }
164
+ .usage .leg { margin-left: auto }
146
165
  .usage .lg { display: flex; align-items: center; gap: 4px; white-space: nowrap }
147
- .usage .lg i { width: 8px; height: 8px; border-radius: 2px; flex: none }
148
- .usage .lg b { margin-left: 1px }
166
+ .usage .lg i { width: 7px; height: 7px; border-radius: 50%; flex: none }
167
+ .usage.f-leg .leg, .usage.f-nm .u-nm, .usage.f-time .kv-time { display: none }
149
168
 
150
169
  /* 群聊头像 = 参与者头像拼图: 1 格居中, 2 格左右, 3/4 格田字。 */
151
170
  .mosaic {
@@ -153,7 +172,7 @@ button { font: inherit; color: inherit }
153
172
  background: #eef1f6; border: 1px solid var(--line); gap: 1px; padding: 1px;
154
173
  font-size: 13px !important; line-height: 1 !important;
155
174
  }
156
- .mosaic i { font-style: normal; display: grid; place-items: center; background: #fff; min-width: 0; min-height: 0 }
175
+ .mosaic i { font-style: normal; display: grid; place-items: center; background: var(--av-bg); min-width: 0; min-height: 0 }
157
176
  .mosaic.n1 { grid-template: 1fr / 1fr; font-size: 20px !important }
158
177
  .mosaic.n2 { grid-template: 1fr / 1fr 1fr; font-size: 14px !important }
159
178
  .mosaic.n3, .mosaic.n4 { grid-template: 1fr 1fr / 1fr 1fr }
@@ -167,50 +186,42 @@ button { font: inherit; color: inherit }
167
186
  .convs h2:first-child { margin-top: 6px }
168
187
  .convs h2 span { font-weight: 400; color: var(--faint); font-variant-numeric: tabular-nums }
169
188
 
170
- .ci {
171
- display: flex; gap: 10px; align-items: flex-start; width: 100%; text-align: left; overflow: hidden;
189
+ /* 群下的子项 (.si) 与私聊项 (.ci) 同一套尺寸 —— 只有一份。 */
190
+ .ci, .si {
191
+ display: flex; gap: 10px; align-items: center; width: 100%; text-align: left; overflow: hidden;
172
192
  padding: 8px 10px; border: 0; border-radius: 9px; background: transparent; cursor: pointer;
173
193
  }
174
- .ci:hover { background: #f4f6f9 }
175
- .ci.on { background: #eaf0fc }
176
- .ci { align-items: center }
177
- .ci > .av { font-size: 22px; line-height: 1; flex: none; width: 34px; text-align: center }
178
- .ci .b { min-width: 0; flex: 1; overflow: hidden }
179
- .ci .pv, .ci .l1 { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
180
- .ci .l1 { display: flex }
181
- .ci .l1 { display: flex; align-items: baseline; gap: 6px }
182
- .ci .t { font-size: 13.5px; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
183
- .ci .ts { margin-left: auto; flex: none; font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums }
184
- .ci .pv { font-size: 12px; color: var(--mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
185
- .ci .pv:empty { display: none }
194
+ .ci.on, .si.on { background: #eaf0fc }
195
+ /* 下分割线: 每一项自己的下边线 (侧栏项、群下子项、关系图卡片一律), 两端收到内容边距。 */
196
+ .ci, .si { position: relative }
197
+ .ci::after, .si::after {
198
+ content: ""; position: absolute; left: 10px; right: 10px; bottom: 0; height: 1px;
199
+ background: var(--line); opacity: .6; pointer-events: none;
200
+ }
201
+ .ci > .av, .si > .av {
202
+ flex: none; display: grid; place-items: center; width: 34px; height: 34px; border-radius: 10px;
203
+ background: var(--av-bg); box-shadow: inset 0 0 0 1px var(--av-line); font-size: 20px; line-height: 1;
204
+ }
205
+ .ci .b, .si .b { min-width: 0; flex: 1; overflow: hidden }
206
+ .ci .l1, .si .l1 { display: flex; align-items: baseline; gap: 6px; overflow: hidden; white-space: nowrap }
207
+ .ci .t, .si .t { font-size: 13.5px; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
208
+ .ci .ts, .si .ts { margin-left: auto; flex: none; font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums }
209
+ .ci .pv, .si .pv { display: block; font-size: 12px; color: var(--mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
210
+ .ci .pv:empty, .si .pv:empty { display: none }
186
211
  .ci .tag { font-size: 10.5px; border-radius: 4px; padding: 0 4px; flex: none }
187
212
  /* 头像与名字是进入那个 role 视角的入口 —— 侧栏项、会话头、消息行同一种读法。
188
- 它们嵌在整行那颗按钮里, 所以悬停态必须压过行: 指到入口上时行自己的悬停底色
189
- 让位 (:has), 否则读起来像是要选中这一行。z-index 保证没有兄弟盖住命中区。 */
213
+ 它们嵌在整行那颗按钮里; 行本身悬停不变底色, 只有入口自己亮起。z-index 保证没有兄弟盖住命中区。 */
190
214
  .nm.go, .av.go { position: relative; z-index: 1; cursor: pointer }
191
215
  .nm.go:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 2px }
192
216
  span.av.go { border-radius: 8px; transition: background .12s, box-shadow .12s }
193
- span.av.go:hover { background: #2f55c924; box-shadow: 0 0 0 3px #2f55c924 }
194
- .ci:not(.on):has(.go:hover), .si:not(.on):has(.go:hover) { background: transparent }
217
+ span.av.go:hover { background: var(--av-hot); box-shadow: 0 0 0 3px var(--av-hot) }
195
218
  .ci .tag.priv { color: var(--priv); background: #9a5b1014 }
196
219
 
197
- /* 群里的其他 role —— 挂在群下, 与会话项同一种读法 (名字 · 时刻 / 最近一句), 只压一档。
220
+ /* 群里的其他 role —— 挂在群下, 与会话项同一种读法、同一套尺寸 (名字 · 时刻 / 最近一句), 只靠缩进分层级。
198
221
  点开是「我与它在这个群里」的往来。 */
199
- .subs { margin: 0 0 6px 16px; border-left: 1px solid var(--line); padding-left: 2px }
200
- .si {
201
- display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; overflow: hidden;
202
- padding: 5px 8px; border: 0; border-radius: 8px; background: transparent; cursor: pointer; color: var(--ink);
203
- }
204
- .si:hover { background: #f4f6f9 }
205
- .si.on { background: #eaf0fc }
206
- .si > .av { font-size: 17px; line-height: 1; flex: none; width: 24px; text-align: center }
207
- .si .b { min-width: 0; flex: 1; overflow: hidden }
208
- .si .l1 { display: flex; align-items: baseline; gap: 6px }
209
- .si .t { font-size: 12.5px; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
210
- .si.on .t { color: var(--accent) }
211
- .si .ts { margin-left: auto; flex: none; font-size: 10.5px; color: var(--faint); font-variant-numeric: tabular-nums }
212
- .si .pv { display: block; font-size: 11.5px; color: var(--mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
213
- .si .pv:empty { display: none }
222
+ /* 竖线的 x 与关系图里的竖向连线对齐: 都在项左缘 +10px (关系树 ul 缩进 20 - 线距 li 10)。 */
223
+ .subs { margin: 0 0 6px 10px; border-left: 1px solid var(--line); padding-left: 2px }
224
+ .si { color: var(--ink) }
214
225
  .si-more {
215
226
  display: block; width: 100%; text-align: center; padding: 4px 8px; border: 0;
216
227
  background: transparent; cursor: pointer; font-size: 11.5px; color: var(--mute);
@@ -226,24 +237,31 @@ span.av.go:hover { background: #2f55c924; box-shadow: 0 0 0 3px #2f55c924 }
226
237
  font-variant-numeric: tabular-nums;
227
238
  }
228
239
 
229
- /* 执行中的 wizard: 名字右边一盏呼吸灯, 会话与子项同一位置, 与回复头上的 .tg-dot 同大。 */
230
- .ci .l1 .live, .si .l1 .live {
231
- flex: none; align-self: center; width: 7px; height: 7px;
232
- border-radius: 50%; background: #1a7f37; animation: pulse 1.4s infinite;
233
- }
234
240
 
235
241
  /* ── 右栏头: 这个群聊 / 私聊是什么 ───────────────────────────────── */
236
242
  .main { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; background: var(--card) }
237
243
  .chathead {
238
- flex: none; display: flex; align-items: center; gap: 12px; padding: 10px 20px; min-height: 58px;
244
+ flex: none; display: flex; align-items: center; gap: 12px; margin: 0; padding: 10px 20px; min-height: 58px;
239
245
  background: var(--card); border-bottom: 1px solid var(--line);
240
246
  }
241
247
  .chathead .back { display: none; border: 0; background: none; font-size: 22px; color: var(--mute); cursor: pointer; padding: 0 4px }
242
248
  .ch-who { display: flex; gap: 11px; align-items: center; min-width: 0; flex: 1 }
243
- .ch-who .t { font-size: 16px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
244
- .ch-who > .t:not(:only-child) { flex: none }
245
- .ch-who .sub { font-size: 11.5px; color: var(--mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
246
- .ch-acts { display: flex; gap: 8px; align-items: center; flex: none; font-size: 12px; color: var(--mute) }
249
+ /* 一对一的两端头像: 同单个角色头像的画法, 后一个略压住前一个。 */
250
+ .ch-who .pair { display: flex; flex: none; align-items: center }
251
+ .ch-who .pair .av {
252
+ display: grid; place-items: center; width: 34px; height: 34px; border-radius: 10px;
253
+ background: #fff; border: 1px solid var(--line); font-size: 20px; line-height: 1;
254
+ }
255
+ .ch-who .pair .av + .av { margin-left: -8px; box-shadow: -2px 0 0 var(--card) }
256
+ .ch-who .pair .av.go { cursor: pointer }
257
+ .ch-who .pair .av.go:hover { border-color: var(--accent) }
258
+ /* 标题先让位 (min-width:0), 两端头像与右侧按钮不让。 */
259
+ .ch-who .t { min-width: 0; font-size: 16px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
260
+ .ch-who > .t:not(:only-child) { flex: 0 1 auto }
261
+ .ch-who .sub { min-width: 0; font-size: 11.5px; color: var(--mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
262
+ .ch-acts { display: flex; gap: 8px; align-items: center; flex: 0 1 auto; min-width: 0; font-size: 12px; color: var(--mute) }
263
+ .ch-acts .with { min-width: 0; max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
264
+ .ch-acts .vb { flex: none }
247
265
  .vb {
248
266
  font-size: 12.5px; background: var(--card); border: 1px solid var(--line); border-radius: 7px;
249
267
  padding: 4px 11px; cursor: pointer; white-space: nowrap;
@@ -278,7 +296,8 @@ span.av.go:hover { background: #2f55c924; box-shadow: 0 0 0 3px #2f55c924 }
278
296
  .mrow.mine { flex-direction: row-reverse }
279
297
  .mrow.mine .mcol { align-items: flex-end }
280
298
  .mwho { font-size: 11.5px; color: var(--mute); display: flex; gap: 6px; align-items: center; flex-wrap: wrap }
281
- .mwho .to { color: var(--faint) }
299
+ .mwho .to { color: var(--faint); display: inline-flex; gap: 6px; align-items: center; min-width: 0; max-width: 100% }
300
+ .mwho .nm { min-width: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
282
301
  .mwho .ch { font-size: 10.5px; border-radius: 4px; padding: 0 4px }
283
302
  .mwho .ch.priv { color: var(--priv); background: #9a5b1014 }
284
303
  /* 时刻只此一处 —— 气泡外, 跟着名字走。等宽数字让一列时间对得齐。 */
@@ -294,6 +313,10 @@ span.av.go:hover { background: #2f55c924; box-shadow: 0 0 0 3px #2f55c924 }
294
313
  .say-cap { font-size: 11.5px; color: var(--mute) }
295
314
  .mb .mq .mmeta { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; padding: 8px 14px 0; font-size: 11px; color: var(--faint) }
296
315
  .mb .mq .mmeta + .md-body { padding-top: 4px }
316
+ /* 随这句话来的图: 详情页那份是裁成缩略的 (cover), 这里要整张看得见 —— 按原比例缩进气泡, 不裁。 */
317
+ .mb .mq .q-imgs { padding: 10px 14px 4px }
318
+ .mb .mq .q-imgs a { display: block; max-width: 100% }
319
+ .mb .mq .q-imgs img { max-width: min(100%, 420px); max-height: 360px; width: auto; height: auto; object-fit: contain }
297
320
  .mchip { border-radius: 4px; padding: 0 5px; background: #f1f3f6; color: var(--mute) }
298
321
  .mchip.priv { color: var(--priv); background: #9a5b1014 }
299
322
  .mchip.graph { color: #6639ba; background: #8250df12 }
@@ -339,8 +362,40 @@ span.av.go:hover { background: #2f55c924; box-shadow: 0 0 0 3px #2f55c924 }
339
362
  .mrow.mine .flip .fi { flex-direction: row-reverse }
340
363
  .mrow.mine .flip .fc { transform: scaleX(-1) }
341
364
  .flip .fc { flex: none }
365
+ .flip .fi { min-width: 0; max-width: 100%; box-sizing: border-box; padding: 4px 8px; gap: 6px }
366
+ /* 要去谁那里: 名字可以折行, 不撑出门外。 */
367
+ .flip .fn { min-width: 0; overflow-wrap: anywhere }
342
368
  .flip:hover, .flip:focus-visible { background: #2f55c90d; color: var(--accent) }
343
369
  .flip[disabled] { cursor: default; background: transparent !important; color: transparent !important }
370
+ /* 说给谁: 贴着气泡写在门里, 与气泡首行对齐。不吃点击 —— 点到它就是点门。
371
+ 门竖排: 描述在上、换视角箭头在下, 描述折几行箭头就跟着往下让, 两者不叠。
372
+ contain: size —— 门的内容不参与这一行的高度, 描述再长也不撑高一行。 */
373
+ .flip:has(.dest) {
374
+ flex-direction: column; justify-content: flex-start; gap: 6px; contain: size;
375
+ padding: calc(var(--av) + 12px) 4px 0;
376
+ }
377
+ /* 只留向下的吸附: 原先的 bottom 吸附会把箭头往上拽到视窗中线, 越过上面的描述。 */
378
+ .flip:has(.dest) .fi { bottom: auto; flex: none }
379
+ /* 门比描述矮 (一行短气泡) 时不许把描述压扁, 宁可溢到行外。 */
380
+ .flip:has(.dest) .dest { flex: none }
381
+ /* 压过 .mrow.mine .flip 的 flex-end: 竖排后那条是纵轴, 会把描述推到行底。 */
382
+ .mrow .flip:has(.dest) { justify-content: flex-start }
383
+ .mrow.them .flip:has(.dest) { align-items: flex-start }
384
+ .mrow.mine .flip:has(.dest) { align-items: flex-end }
385
+ .flip .dest {
386
+ max-width: 100%; font-size: 11px; line-height: 1.45; color: var(--faint); pointer-events: none;
387
+ display: flex; align-items: center; gap: 4px; max-height: calc(3 * 1.45em); overflow: hidden;
388
+ }
389
+ .flip .dest .arr { font-style: normal; flex: none }
390
+ .flip .dest .dav {
391
+ flex: none; width: 18px; height: 18px; border-radius: 50%; border: 1px solid var(--line); background: var(--card);
392
+ display: grid; place-items: center; font-size: 11px; line-height: 1;
393
+ }
394
+ .flip .dest .dn { min-width: 0; overflow-wrap: anywhere }
395
+ .mrow.them .flip .dest { text-align: left }
396
+ .mrow.mine .flip .dest { text-align: right; justify-content: flex-end }
397
+ .flip[disabled] .dest { color: var(--faint) }
398
+ .mwho .to { display: none }
344
399
  .mwho .av.go { cursor: pointer }
345
400
  .mwho .av.go:hover { border-color: var(--accent); background: #2f55c914 }
346
401
 
@@ -379,6 +434,8 @@ span.av.go:hover { background: #2f55c924; box-shadow: 0 0 0 3px #2f55c924 }
379
434
  保温 ping 不是对话, 但它是真花销 —— 所以留在时间轴上, 只是收成一行虚线。 */
380
435
  /* summary 的灰底 / 底线是 SHARED_CSS 里通用 details 的 —— 圆角得自己裁, 底线只在展开时才是分割线。 */
381
436
  .ping-fold { border: 1px dashed #d8dee4; border-radius: 10px; background: #fff; overflow: hidden }
437
+ /* 暂时整条藏起来 —— 行仍折在 details 里, 折叠 / FLIP 逻辑照旧, 删掉这行即恢复。 */
438
+ .ping-fold { display: none }
382
439
  .ping-fold:not([open]) > .ping-sum { border-bottom: 0 }
383
440
  .ping-fold > .ping-sum {
384
441
  display: flex; align-items: center; gap: 8px; padding: 6px 12px; background: #fff;
@@ -401,6 +458,7 @@ span.av.go:hover { background: #2f55c924; box-shadow: 0 0 0 3px #2f55c924 }
401
458
  }
402
459
  .ping-fold .mcol { max-width: 100% }
403
460
  .ping-fold .flip { display: none }
461
+ .ping-fold .mwho .to { display: inline-flex }
404
462
  /* 折叠里的气泡同是白底 —— 描一圈虚线跟对话气泡分开, 与外框一个语气。 */
405
463
  .ping-fold .mb :is(.bubble.mq, .say > .bubble) { border: 1px dashed var(--bub-line) }
406
464
 
@@ -415,11 +473,10 @@ span.av.go:hover { background: #2f55c924; box-shadow: 0 0 0 3px #2f55c924 }
415
473
  .thread { padding: 12px }
416
474
  .mcol { max-width: calc(86% + var(--av)) }
417
475
  .flip { min-width: 16px; padding: 0 4px; font-size: 0 }
476
+ .flip .dest { display: none }
477
+ .mwho .to { display: inline-flex }
418
478
  .thread { padding-bottom: calc(12px + env(safe-area-inset-bottom)) }
419
- .usage { padding: 8px 12px; gap: 6px 14px }
420
- .usage .u-kvs, .usage .leg { flex-wrap: wrap }
421
- .usage .u-io { flex: 1 1 100%; flex-wrap: wrap; gap: 6px 12px }
422
- .usage .bar { flex: 1 1 100%; width: auto }
479
+ .usage { padding: 8px 12px }
423
480
  .usage.page { padding-bottom: calc(8px + env(safe-area-inset-bottom)) }
424
481
  }
425
482
 
@@ -435,139 +492,12 @@ span.av.go:hover { background: #2f55c924; box-shadow: 0 0 0 3px #2f55c924 }
435
492
  padding: 8px 20px; background: #fff; border-bottom: 1px solid #d0d7de;
436
493
  font-size: 11.5px; color: #57606a;
437
494
  }
438
- .wlegend { display: flex; gap: 6px; flex-wrap: wrap }
439
- /* 图例同时是开关 —— 边一多就得能关掉几种; 把"图例"和"过滤器"做成两样东西
440
- 只会让人在两处之间对照颜色。 */
441
- .wtools .chip {
442
- display: flex; align-items: center; gap: 5px; font: inherit; cursor: pointer;
443
- background: #fff; border: 1px solid #d0d7de; border-radius: 13px; padding: 2px 10px;
444
- color: #8c959f;
445
- }
446
- .wtools .chip i { width: 14px; height: 0; border-top: 2px solid var(--c); opacity: .35 }
447
- .wtools .chip b { font-variant-numeric: tabular-nums; font-weight: 700 }
448
- .wtools .chip.on { color: #1f2328; border-color: var(--c) }
449
- .wtools .chip.on i { opacity: 1 }
450
- .wtools .chip.clear, .wtools .chip.only { border-color: #d0d7de; color: #57606a }
451
- .wtools .chip.only.on { border-color: #0969da; color: #0550ae; background: #ddf4ff }
452
- .wtools .wstat { margin-left: auto; color: #8c959f; font-variant-numeric: tabular-nums }
453
- .wtools .wstat b { color: #0969da }
495
+ .wtools .wstat { flex: 1; display: flex; gap: 6px; align-items: baseline; color: #8c959f }
496
+ .wtools .rng { color: var(--ink) }
454
497
  .wtools .warn { color: #9a6700 }
455
- .wtools .hint { margin-left: 10px; color: #b1bac4 }
456
-
457
- /* position 不是装饰: 力导向的画布是 `position:absolute; inset:0`, 没有这一行它
458
- 就贴到更外层的定位祖先上去 —— 坐标原点跑到顶栏, 节点飘到工具条上面, 而
459
- overflow:hidden 也拦不住一个容器块在自己之外的绝对定位子元素。 */
498
+ .wtools .hint { margin-left: auto; color: #b1bac4 }
460
499
  .wscroll { position: relative; flex: 1; min-height: 0; overflow: auto; -webkit-overflow-scrolling: touch }
461
500
  .wmap { position: relative; padding: 18px 20px 28px; min-width: min-content }
462
- /* 有关系的节点带一道左侧色条 —— 76 个会话里哪几个真的在协作, 不点开也看得见。 */
463
- .wnode.rel::before {
464
- content: ""; position: absolute; left: 1px; top: 8px; bottom: 8px;
465
- width: 2px; border-radius: 1px; background: #0969da66;
466
- }
467
- /* 连线层铺满整张图并且不吃事件 —— 节点的点击/双击必须照常穿过去。
468
- z-index 把它抬到卡片之上: 卡片是不透明的, 压在下面时一条跨卡片的边会被中途
469
- 吃掉半截, 读出来像是断的。.wgrid 不设 z-index (保持 auto) 才不会自成一个层叠
470
- 上下文 —— 那样悬停/聚焦的节点就再也抬不过这条线了。 */
471
- .wedges { position: absolute; inset: 0; pointer-events: none; overflow: visible; z-index: 2 }
472
- /* 反过来: 正在看的那个节点要压过连线, 否则文字被线划着读。只抬这两种 —— 一直
473
- 抬着某个节点等于把"线被卡片吃掉"这个毛病在它身上留一份。 */
474
- .wnode:hover, .wnode.sel { z-index: 3 }
475
- /* 多列流式而不是 grid: 聊天卡片的高度天差地别 (一个群 2 个 wizard, 另一个 20 个),
476
- grid 的行高按本行最高的那张对齐, 于是矮卡片下面全是空白 —— 图看着比实际稀疏
477
- 一倍。CSS columns 是砌砖式的: 卡片顺着列往下码, 码满了换列, 没有行的概念。
478
- 列宽钉在 320 附近也顺带收住了"两张卡片各占半个屏"的过度拉伸。 */
479
- .wgrid { position: relative; columns: 320px; column-gap: 16px }
480
-
481
- .wchat {
482
- background: #fff; border: 1px solid #d0d7de; border-radius: 12px; overflow: hidden;
483
- /* 一张卡片不能被切成两列 —— 断在半截的家谱读不出父子关系。 */
484
- break-inside: avoid; -webkit-column-break-inside: avoid; page-break-inside: avoid;
485
- margin-bottom: 16px;
486
- }
487
- /* 卡片头可点 = 进那个群 (见 chat.js 的 openNode)。 */
488
- .wch.go { cursor: pointer }
489
- .wch.go:hover .nm { color: #0969da; text-decoration: underline }
490
- /* 当前聊天是读图的锚点, 给它一道明确的边 —— 其余的群一律等价, 不再分轻重。 */
491
- .wchat.self { border-color: #0969da66; box-shadow: 0 0 0 3px #0969da10 }
492
- .wch {
493
- display: flex; align-items: baseline; gap: 8px; padding: 9px 13px;
494
- border-bottom: 1px solid #eaeef2; background: #f6f8fa;
495
- }
496
- .wchat.self .wch { background: #f0f6ff }
497
- .wch .nm {
498
- font-size: 13px; font-weight: 700; min-width: 0; overflow: hidden;
499
- text-overflow: ellipsis; white-space: nowrap;
500
- }
501
- .wch .here {
502
- font-size: 10px; color: #0969da; background: #0969da14; border-radius: 4px; padding: 1px 5px;
503
- }
504
- .wch .ct {
505
- margin-left: auto; font-size: 11px; color: #8c959f; flex: none;
506
- font-family: ui-monospace, Menlo, monospace;
507
- }
508
- .wch .ct em { color: #1a7f37; font-style: normal; font-weight: 700 }
509
- /* 左内边距是同卡片内连线的车道 —— 一条 `#a → #b` 的派活边从这里绕过去, 不必
510
- 拐出卡片 (拐出去就会被滚动容器裁掉)。 */
511
- .wrows { padding: 7px 10px 10px 22px; display: flex; flex-direction: column; gap: 3px }
512
-
513
- .wnode {
514
- position: relative; display: flex; gap: 8px; align-items: flex-start;
515
- padding: 6px 8px; border-radius: 8px; cursor: pointer;
516
- border: 1px solid transparent; transition: opacity .15s, background .12s;
517
- }
518
- .wnode:hover { background: #f6f8fa }
519
- .wnode.self { background: #fffbe8 }
520
- .wnode.sel { background: #eef4fc; border-color: #0969da55 }
521
- /* 聚焦时无关节点压暗而不是移除 —— 位置不动, 连点几个节点版面也不跳。 */
522
- .wnode.dim { opacity: .3 }
523
- /* 双击能走进去的那些 —— 外聊天的节点现在也算 (整页跳过去)。 */
524
- .wnode.go { cursor: pointer }
525
- .wnode.cold .wav { filter: grayscale(1); opacity: .55 }
526
- /* 缩进本身就是家谱; 这道折线只是把"缩给谁看"指实。 */
527
- .wnode .lin {
528
- position: absolute; left: -9px; top: 0; bottom: 50%; width: 9px;
529
- border-left: 1.5px solid #d8cdf0; border-bottom: 1.5px solid #d8cdf0;
530
- border-bottom-left-radius: 6px;
531
- }
532
- /* 子 wizard 不是 fork: 折线换成虚线、换色, 与分身一眼分开 (同关系图的 spawn 边)。 */
533
- .wnode .lin.sp { border-style: dashed; border-color: #f0d2bd }
534
- .wav {
535
- position: relative; width: 26px; height: 26px; flex: none; border-radius: 8px;
536
- background: #f1f3f6; border: 1px solid #eaeef2;
537
- display: flex; align-items: center; justify-content: center; font-size: 14px;
538
- }
539
- .wav .live {
540
- position: absolute; right: -3px; bottom: -3px; width: 9px; height: 9px;
541
- border-radius: 50%; background: #1a7f37; border: 2px solid #fff;
542
- animation: pulse 1.4s infinite;
543
- }
544
- .wbody { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 1px }
545
- .wl1 { display: flex; align-items: baseline; gap: 6px; min-width: 0 }
546
- .wname {
547
- font-size: 12.5px; font-weight: 600; min-width: 0; overflow: hidden;
548
- text-overflow: ellipsis; white-space: nowrap;
549
- }
550
- .wnode.run .wname { color: #1a7f37 }
551
- .wtag {
552
- flex: none; font-size: 10.5px; color: #57606a; background: #f1f3f6;
553
- border-radius: 4px; padding: 0 5px; font-family: ui-monospace, Menlo, monospace;
554
- }
555
- .wtag.def { color: #8c959f }
556
- .wih { flex: none; font-size: 11px; color: #6639ba }
557
- .wts {
558
- margin-left: auto; flex: none; font-size: 10.5px; color: #8c959f;
559
- font-family: ui-monospace, Menlo, monospace;
560
- }
561
- .wjob { font-size: 11.5px; color: #0550ae; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
562
- .wprev {
563
- font-size: 11.5px; color: #656d76; overflow: hidden; line-height: 1.4;
564
- display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
565
- }
566
- .wmeta {
567
- font-size: 10.5px; color: #8c959f; font-family: ui-monospace, Menlo, monospace;
568
- display: flex; gap: 4px; flex-wrap: wrap; margin-top: 1px;
569
- }
570
- .wmeta .sep { opacity: .45 }
571
501
 
572
502
  /* ══ 日程 ═══════════════════════════════════════════════════════════ */
573
503
  .plan-in { flex: 1; min-height: 0; overflow-y: auto; padding: 18px 20px 28px }
@@ -602,32 +532,18 @@ span.av.go:hover { background: #2f55c924; box-shadow: 0 0 0 3px #2f55c924 }
602
532
  position: relative; height: 8px; border-radius: 4px; background: #eaeef2; margin: 3px 0 7px;
603
533
  }
604
534
  .strip .pin {
605
- position: absolute; top: -3px; width: 8px; height: 14px; margin-left: -4px;
606
- border-radius: 3px; background: #9a6700; box-shadow: 0 0 0 2px #fff;
607
- }
608
- .strip .cap { font-size: 11px; color: #8c959f }
609
-
610
- .prow {
611
- display: flex; gap: 16px; background: #fff; border: 1px solid #d0d7de;
612
- border-radius: 10px; padding: 12px 16px; margin-bottom: 10px;
613
- }
614
- .prow .pl { flex: none; width: 152px; border-right: 1px dashed #eaeef2; padding-right: 14px }
615
- .pwhen { font-size: 11.5px; color: #9a6700; font-weight: 600 }
616
- .pnext {
617
- display: flex; align-items: baseline; gap: 6px; margin-top: 5px;
618
- font-family: ui-monospace, Menlo, monospace;
535
+ position: absolute; top: -4px; width: 10px; height: 16px; margin-left: -5px; padding: 0; cursor: pointer;
536
+ border: 0; border-radius: 3px; background: #9a6700; box-shadow: 0 0 0 2px #fff;
619
537
  }
620
- .pnext b { font-size: 12.5px; font-variant-numeric: tabular-nums }
621
- .pnext span { font-size: 10.5px; color: #8c959f }
622
- .prow .pr { min-width: 0; flex: 1 }
623
- .ph { display: flex; align-items: center; gap: 7px; flex-wrap: wrap }
624
- .ph .note { font-size: 11px; color: #57606a }
625
- .ph .pid {
626
- margin-left: auto; font-size: 10px; color: #b1bac4;
627
- font-family: ui-monospace, Menlo, monospace;
538
+ .strip .pin span {
539
+ position: absolute; bottom: 19px; left: 50%; transform: translateX(-50%); white-space: nowrap;
540
+ font: 600 9.5px ui-monospace, Menlo, monospace; color: #9a6700; opacity: 0; transition: opacity .12s; pointer-events: none;
628
541
  }
629
- .ptext { font-size: 12px; color: #1f2328; margin-top: 6px; line-height: 1.5; word-break: break-word }
630
- .pfoot { font-size: 10.5px; color: #8c959f; margin-top: 6px }
542
+ .strip .pin:hover span, .strip .pin:focus-visible span { opacity: 1 }
543
+ .strip .pin:hover { transform: scaleY(1.15) }
544
+ .strip .pin.bad { background: #cf222e }
545
+ .strip .pin.bad span { color: #cf222e }
546
+ .strip .cap { font-size: 11px; color: #8c959f }
631
547
 
632
548
  /* wizard 徽标 —— 日程/工单里凡是提到一个 wizard 都用它, 同聊天的可点进线程。 */
633
549
  .wchip {
@@ -671,69 +587,15 @@ span.av.go:hover { background: #2f55c924; box-shadow: 0 0 0 3px #2f55c924 }
671
587
  @media (max-width: 760px) {
672
588
  .chathead .vb { padding: 3px 8px }
673
589
  .wmap { padding: 12px }
674
- .wgrid { columns: 1 }
675
590
  .plan-in { padding: 12px }
676
- .prow { flex-direction: column; gap: 6px }
677
- .prow .pl { width: auto; border-right: 0; border-bottom: 1px dashed #eaeef2; padding: 0 0 8px }
678
- .pnext { margin-top: 1px }
679
- }
680
-
681
- /* 相关性筛掉的那些 —— 说一声它们还在, 但不占图的位置 (见 shared/world 的说明)。 */
682
- .wmore {
683
- padding: 5px 13px 9px; font-size: 10.5px; color: #b1bac4;
684
- border-top: 1px dashed #eaeef2; margin-top: 2px;
685
- }
686
-
687
- /* ══ 关系视图 · 力导向布局 ═════════════════════════════════════════
688
- 卡片布局之外的另一种读法: 节点只画 wizard, 聊天不再是容器而是节点身上的一
689
- 道色条 + 一行群名。见 web/chat.js 的说明。 */
690
- .wscroll.fx { overflow: hidden }
691
- .wmap.force { position: absolute; inset: 0; padding: 0; min-width: 0 }
692
- .fxn {
693
- position: absolute; left: 0; top: 0; z-index: 2;
694
- display: flex; align-items: center; gap: 7px; max-width: 210px;
695
- padding: 5px 10px 5px 7px; border-radius: 10px;
696
- background: #fff; border: 1px solid #d0d7de; border-left: 3px solid var(--c);
697
- box-shadow: 0 1px 3px #1b1f2412; cursor: grab; touch-action: none;
698
- user-select: none; -webkit-user-select: none;
699
- transition: opacity .15s, box-shadow .12s;
700
- will-change: transform;
701
- }
702
- .fxn .wav { width: 22px; height: 22px; border-radius: 7px; font-size: 12px }
703
- .fxn .wav .live { width: 8px; height: 8px; right: -2px; bottom: -2px }
704
- .fxb { min-width: 0; display: flex; flex-direction: column; gap: 0 }
705
- .fxn .wname { font-size: 12px }
706
- /* 群名压得比名字轻 —— 它是节点的属性, 不是节点本身。 */
707
- .fxc {
708
- font-size: 9.5px; color: var(--c); opacity: .85; overflow: hidden;
709
- text-overflow: ellipsis; white-space: nowrap;
710
- }
711
- .fxn.self { background: #fffbe8 }
712
- .fxn.cold { opacity: .72 }
713
- .fxn.cold .wav { filter: grayscale(1); opacity: .55 }
714
- .fxn.run .wname { color: #1a7f37 }
715
- .fxn.dim { opacity: .22 }
716
- .fxn.sel { box-shadow: 0 0 0 3px var(--c), 0 1px 3px #1b1f2412; }
717
- .fxn.sel .fxc { opacity: 1 }
718
- .fxn.drag { cursor: grabbing; box-shadow: 0 6px 16px #1b1f2426 }
719
- /* 钉住 = 这个位置是人摆的, 力不再动它。 */
720
- .fxn.pin::after {
721
- content: "📌"; position: absolute; right: -5px; top: -7px; font-size: 9px;
722
- opacity: .8; pointer-events: none;
723
- }
724
- /* 布局开关 —— 两种读法并列, 不是复选。 */
725
- .wtools .wlay { display: inline-flex; border: 1px solid #d0d7de; border-radius: 7px; overflow: hidden }
726
- .wtools .lb {
727
- border: 0; background: #fff; cursor: pointer; padding: 3px 9px;
728
- font-size: 11.5px; color: #57606a; font-family: inherit;
729
- }
730
- .wtools .lb + .lb { border-left: 1px solid #d0d7de }
731
- .wtools .lb.on { background: #ddf4ff; color: #0550ae; font-weight: 600 }
591
+ .tcard { flex-direction: column; gap: 8px }
592
+ .tc-time { width: auto; flex-direction: row; align-items: baseline; gap: 8px; border-right: 0; padding: 0 }
593
+ }
732
594
 
733
595
  /* ══ 关系 / 日程 ═══════════════════════════════════════════════════ */
734
596
  /* 都是当前 role 的另一面, 侧栏 (它的会话) 留着 —— 点一个会话就回到对话。
735
597
  会话标题条与页脚总账只属于对话视图。 */
736
- .pane { flex: 1; min-height: 0; display: flex; flex-direction: column; background: #fafbfc }
598
+ .pane { position: relative; flex: 1; min-height: 0; display: flex; flex-direction: column; background: #fafbfc }
737
599
 
738
600
  /* 过程框里的工具行是 nowrap 的 —— 不裁一刀会把整列撑出横向滚动。 */
739
601
  .mb > .turn-group { overflow: hidden }
@@ -746,3 +608,234 @@ span.av.go:hover { background: #2f55c924; box-shadow: 0 0 0 3px #2f55c924 }
746
608
  .rem.raw { flex-basis: 100% }
747
609
  .rem.raw .rem-raw { display: block }
748
610
  .rem.raw .rem-tag { display: none }
611
+
612
+ /* ══ 忙闲: 名字右边唯一的状态位 (名片 · 关系树 · 侧栏共用 stTag) ══
613
+ 只有执行中带颜色与呼吸; 空字 (侧栏) 就只剩那盏灯。 */
614
+ .wst {
615
+ flex: none; align-self: center; display: inline-flex; align-items: center; gap: 4px; font-size: 10px; line-height: 16px;
616
+ padding: 0 6px; border-radius: 8px; background: #eef0f3; color: var(--mute); white-space: nowrap; font-weight: 500;
617
+ }
618
+ .wst::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: #b1bac4 }
619
+ .wst.run { background: #dafbe1; color: #1a7f37 }
620
+ .wst.run::before { background: #1a7f37; animation: pulse 1.4s infinite }
621
+ .wst.off { background: transparent; color: var(--faint); box-shadow: inset 0 0 0 1px var(--line) }
622
+ .wst.off::before { background: transparent; box-shadow: inset 0 0 0 1.5px #b1bac4 }
623
+ .wst:empty { padding: 0; background: none; box-shadow: none }
624
+ .wst:empty::before { width: 7px; height: 7px }
625
+ /* ══ 关系图 · 关系树 (家谱与派活合成一棵, 以当前 role 为中心) ══════════
626
+ 它替换的是侧栏的会话列表: 标题、图例与会话列表的分组标题同一种读法, 卡片就是会话项 (.ci)。 */
627
+ .tview .tall {
628
+ margin-left: auto; border: 0; background: none; padding: 0 2px; cursor: pointer; font-size: 11px; color: var(--faint);
629
+ text-decoration: underline dotted; text-underline-offset: 3px;
630
+ }
631
+ .tview .tall:hover { color: var(--accent) }
632
+ .tview h2 .warn { color: #9a6700; font-weight: 400 }
633
+ .tree.all > li + li { margin-top: 10px; padding-top: 10px; border-top: 1px dashed #eaeef2 }
634
+ .tfold { flex: none; font-size: 10px; font-weight: 400; color: var(--faint); border: 1px dashed var(--line); border-radius: 8px; padding: 0 5px; line-height: 14px }
635
+ .tsolo { font-size: 11.5px; color: var(--faint); margin: 0 8px 10px }
636
+ .tview .pempty { padding: 18px 8px }
637
+
638
+ /* 树: 每条入边是一道实线, 从父卡片底下一路落到自己卡片的头像旁, 末端一枚箭头, 颜色 = 关系种类。
639
+ 兄弟共用的那段竖干上, 后画的 (更下方的节点) 盖住先画的; 竖线往上伸到 ul 顶为止 (clip-path 只裁上边)。 */
640
+ .tree, .tree ul { list-style: none; margin: 0; padding: 0 }
641
+ .tree ul { padding-left: 20px; clip-path: inset(0 -100vw -100vh -100vw) }
642
+ .tree li { position: relative; padding-top: 14px }
643
+ .tree > li { padding-top: 0 }
644
+ .tree ul > li::before {
645
+ content: ""; position: absolute; left: -10px; top: -9999px; height: calc(9999px + 39px); width: 10px;
646
+ border-left: 1.5px solid var(--lc); border-bottom: 1.5px solid var(--lc); border-bottom-left-radius: 7px;
647
+ }
648
+ .tree ul > li::after {
649
+ content: ""; position: absolute; left: -6px; top: 34px; border: 4.5px solid transparent; border-right: 0;
650
+ border-left: 7px solid var(--lc);
651
+ }
652
+ .tree li.spawn { --lc: #d4700f }
653
+ .tree li.clone { --lc: #8250df }
654
+ .tree li.peer { --lc: #0969da }
655
+ .tree li.job { --lc: #bf8700 }
656
+ .tree li.graph { --lc: #0a7d6b }
657
+ /* 卡片 = 会话项; label 骑在它左上角, 紧挨箭头。 */
658
+ .tci { position: relative; overflow: visible }
659
+ .tci.me .t .nm { color: var(--accent) }
660
+ /* 连接用的祖先: 没和视角对过话, 只为把对过话的后代挂回家谱 —— 淡一档。 */
661
+ .tci.via { opacity: .55 }
662
+ .tci.via:hover { opacity: 1 }
663
+ .tlab { position: absolute; left: 4px; top: -8px; display: flex; gap: 3px; z-index: 2 }
664
+ .tlab .ek { box-shadow: 0 0 0 2px var(--card) }
665
+
666
+ /* 边的 label: 辅助信息, 淡色; 一条边可能同时带几种 (既是子 wizard 又被派过活), 各一枚。 */
667
+ .ek {
668
+ flex: none; font-size: 10px; line-height: 15px; padding: 0 5px; border-radius: 4px; white-space: nowrap;
669
+ font-weight: 500; color: var(--mute); background: #f1f3f6;
670
+ }
671
+ .ek.spawn { color: #a8642a; background: #d4700f12 }
672
+ .ek.clone { color: #7a5bb8; background: #8250df12 }
673
+ .ek.peer { color: #4a74b0; background: #0969da10 }
674
+ .ek.job { color: #94741f; background: #bf870014 }
675
+ .ek.graph { color: #3d8577; background: #0a7d6b12 }
676
+ .ek.cross { font-weight: 400; background: #fff; box-shadow: inset 0 0 0 1px var(--line) }
677
+
678
+ /* ══ 日程 · 总览 / 任务卡 ════════════════════════════════════════════ */
679
+ .pstats {
680
+ max-width: 900px; margin: 0 auto 18px; display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px;
681
+ }
682
+ .pst {
683
+ background: #fff; border: 1px solid var(--line); border-radius: 12px; padding: 10px 14px;
684
+ display: flex; flex-direction: column; gap: 2px; min-width: 0;
685
+ }
686
+ .pst .k { font-size: 11px; color: var(--mute) }
687
+ .pst b { font-size: 20px; line-height: 26px; font-variant-numeric: tabular-nums; font-family: ui-monospace, Menlo, monospace }
688
+ .pst b small { font-size: 12px; color: var(--faint); font-weight: 400 }
689
+ .pst .s { font-size: 11px; color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
690
+ .pst.bad { border-color: #cf222e66; background: #fff6f6 }
691
+ .pst.bad b, .pst.bad .k { color: #cf222e }
692
+
693
+ .tcard {
694
+ display: flex; gap: 16px; background: #fff; border: 1px solid var(--line); border-radius: 12px;
695
+ padding: 12px 16px; margin-bottom: 10px; transition: box-shadow .2s, border-color .2s;
696
+ }
697
+ .tcard:hover { border-color: #b9c3d3 }
698
+ .tcard.bad { border-color: #cf222e55; border-left: 3px solid #cf222e }
699
+ .tcard.flash { animation: flash 1.2s ease-out }
700
+ @keyframes flash { 0% { box-shadow: 0 0 0 4px #9a670055 } 100% { box-shadow: 0 0 0 4px #9a670000 } }
701
+ .tc-time {
702
+ flex: none; width: 92px; display: flex; flex-direction: column; gap: 1px;
703
+ border-right: 1px dashed #eaeef2; padding-right: 14px;
704
+ }
705
+ .tc-time b { font: 700 22px/28px ui-monospace, Menlo, monospace; font-variant-numeric: tabular-nums; color: var(--ink) }
706
+ .tc-time span { font-size: 11px; color: var(--mute) }
707
+ .tc-time em { font-style: normal; font-size: 11px; color: #9a6700; font-weight: 600 }
708
+ .tc-main { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 7px }
709
+ .perr {
710
+ font-size: 11.5px; color: #82071e; background: #ffebe9; border: 1px solid #ff818266; border-radius: 8px;
711
+ padding: 6px 10px; line-height: 1.5;
712
+ }
713
+ .perr b { font-weight: 700 }
714
+ .perr code {
715
+ display: block; margin-top: 3px; font: 11px/1.5 ui-monospace, Menlo, monospace; color: #a40e26;
716
+ white-space: pre-wrap; word-break: break-word;
717
+ }
718
+ .prule { display: flex; gap: 5px; flex-wrap: wrap }
719
+ .rl { font-size: 11px; line-height: 18px; padding: 0 7px; border-radius: 9px; white-space: nowrap }
720
+ .rl.clock { color: #7d4e00; background: #fff8c5; border: 1px solid #d4a72c66; font-weight: 600 }
721
+ .rl.guard { color: var(--mute); background: #f1f3f6; border: 1px solid #e4e8ee }
722
+ .ptitle { font-size: 13px; font-weight: 600; line-height: 1.45; word-break: break-word }
723
+ .pflow { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; font-size: 11.5px }
724
+ .pflow .fl { color: var(--faint) }
725
+ .pflow .ar { color: var(--faint) }
726
+ .pflow .fresh { color: #6639ba; background: #8250df12; border-radius: 10px; padding: 0 7px; line-height: 18px }
727
+ .pprompt { font-size: 11.5px; border-radius: 8px; background: #f6f8fa; border: 1px solid #eaeef2 }
728
+ .pprompt summary {
729
+ text-transform: none; letter-spacing: 0; font-weight: 400; cursor: pointer; padding: 5px 10px; color: var(--mute); display: flex; gap: 8px; min-width: 0; list-style: none;
730
+ }
731
+ .pprompt summary::-webkit-details-marker { display: none }
732
+ .pprompt summary::before { content: "▸"; color: var(--faint); transition: transform .12s }
733
+ .pprompt[open] summary::before { transform: rotate(90deg) }
734
+ .pprompt summary span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); opacity: .75 }
735
+ .pprompt pre {
736
+ margin: 0; padding: 8px 12px 10px; border-top: 1px solid #eaeef2; max-height: 280px; overflow: auto;
737
+ font: 11.5px/1.55 ui-monospace, Menlo, monospace; white-space: pre-wrap; word-break: break-word;
738
+ }
739
+ .pfoot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 10.5px; color: var(--faint) }
740
+ .pfoot .pid { margin-left: auto; font-family: ui-monospace, Menlo, monospace; color: #b1bac4 }
741
+ .gt { border-radius: 4px; padding: 0 6px; line-height: 16px; font-weight: 600 }
742
+ .gt.none { color: var(--faint); background: #f1f3f6; font-weight: 400 }
743
+ .gt.go { color: #1a7f37; background: #dafbe1 }
744
+ .gt.skip { color: #57606a; background: #eaeef2 }
745
+ .gt.err { color: #fff; background: #cf222e }
746
+ .jh .jst { border-radius: 4px; padding: 0 6px; background: #eaeef2 }
747
+ .jrow.open .jh .jst { background: #dafbe1 }
748
+ .jmm .wst { font-size: 9.5px }
749
+ .jmm .tmp, .jmm .wchip { flex: none; white-space: nowrap }
750
+
751
+ /* ── 搜索 (⌘K): 名片下、关系/日程入口上的搜索框 + 居中浮层 ──────────────
752
+ 入口与会话项同圆角, 安静的浅灰底; 浮层的结果行沿用会话项的两行读法。 */
753
+ .sbox {
754
+ flex: none; display: flex; align-items: center; gap: 8px; margin: 10px 10px 4px; padding: 7px 10px;
755
+ border: 1px solid var(--line); border-radius: 9px; background: var(--paper); color: var(--faint);
756
+ cursor: pointer; text-align: left; transition: border-color .12s, background .12s, color .12s;
757
+ }
758
+ .sbox:hover { border-color: #c4cbd6; background: var(--card); color: var(--mute) }
759
+ .sbox .ic, .sk-in .ic { flex: none; display: inline-flex }
760
+ .sbox .ic svg, .sk-in .ic svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round }
761
+ .sbox .lb { flex: 1; min-width: 0; font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
762
+ .sbox kbd, .sk kbd {
763
+ flex: none; font: 500 10.5px/1 ui-sans-serif, system-ui, -apple-system, sans-serif; color: var(--mute);
764
+ padding: 3px 5px; border: 1px solid var(--line); border-bottom-width: 2px; border-radius: 5px; background: var(--card);
765
+ }
766
+
767
+ .sk {
768
+ position: fixed; inset: 0; z-index: 900; display: flex; justify-content: center; align-items: flex-start;
769
+ padding: 12vh 16px 16px; background: #1c243038; animation: sk-in .12s ease-out;
770
+ }
771
+ .sk[hidden] { display: none }
772
+ @keyframes sk-in { from { opacity: 0 } }
773
+ .sk-panel {
774
+ width: 600px; max-width: 100%; max-height: 70vh; display: flex; flex-direction: column; overflow: hidden;
775
+ background: var(--card); border: 1px solid var(--line); border-radius: 14px;
776
+ box-shadow: 0 24px 64px #1c243033, 0 4px 14px #1c24301a;
777
+ }
778
+ .sk-in { flex: none; display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-bottom: 1px solid var(--line); color: var(--faint) }
779
+ .sk-in .ic svg { width: 17px; height: 17px }
780
+ .sk-in input {
781
+ flex: 1; min-width: 0; border: 0; outline: 0; background: none; font: inherit; font-size: 15px; color: var(--ink);
782
+ }
783
+ .sk-in input::-webkit-search-cancel-button { display: none }
784
+ .sk-list { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 4px 8px 8px }
785
+ .sk-list h3 {
786
+ margin: 10px 8px 4px; font-size: 11.5px; font-weight: 600; color: var(--mute);
787
+ display: flex; align-items: baseline; gap: 6px;
788
+ }
789
+ .sk-list h3 span { font-weight: 400; color: var(--faint); font-variant-numeric: tabular-nums }
790
+ .sk-it {
791
+ display: flex; gap: 10px; align-items: center; width: 100%; text-align: left; overflow: hidden;
792
+ padding: 7px 10px; border: 0; border-radius: 9px; background: transparent; cursor: pointer;
793
+ }
794
+ .sk-it.on { background: #eaf0fc }
795
+ .sk-it.msg { align-items: flex-start }
796
+ .sk-it.msg > .av { margin-top: 1px }
797
+ .sk-it > .av {
798
+ flex: none; display: grid; place-items: center; width: 30px; height: 30px; border-radius: 9px;
799
+ background: #f1f3f6; font-size: 17px; line-height: 1;
800
+ }
801
+ .sk-it .b { min-width: 0; flex: 1; overflow: hidden }
802
+ .sk-it .l1 { display: flex; align-items: baseline; gap: 8px; overflow: hidden; white-space: nowrap }
803
+ .sk-it .nm { font-size: 13.5px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
804
+ .sk-it .k { margin-left: auto; flex: none; font-size: 11px; color: var(--faint); opacity: 0 }
805
+ .sk-it.on .k { opacity: 1; color: var(--accent) }
806
+ .sk-it .ts { margin-left: auto; flex: none; font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums }
807
+ .sk-it .pv { display: block; font-size: 12px; color: var(--mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
808
+ .sk-it .l1 .nm + .nm, .sk-it .arr + .nm { font-weight: 500; color: var(--mute) }
809
+ .sk-it .arr { flex: none; color: var(--faint); font-size: 11.5px }
810
+ .sk-it .tag { flex: none; font-size: 10.5px; color: var(--mute); background: var(--paper); border-radius: 4px; padding: 1px 5px }
811
+ /* 命中的那一截: 正文色, 最多两行; 命中词是这一页的视角色淡底 —— 与选中态一个色系。 */
812
+ .sk-it .sn {
813
+ display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
814
+ margin-top: 2px; font-size: 12.5px; line-height: 1.5; color: var(--ink); word-break: break-word;
815
+ }
816
+ .sk-it mark { background: #2f55c91f; color: var(--accent); font-weight: 600; border-radius: 3px; padding: 0 1px }
817
+ .sk-it.on mark { background: #2f55c92e }
818
+ .sk-it .l3 { display: flex; align-items: center; gap: 8px; margin-top: 3px; min-width: 0 }
819
+ .sk-it .where {
820
+ display: inline-flex; align-items: center; gap: 4px; min-width: 0; max-width: 70%;
821
+ font-size: 11px; color: var(--mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
822
+ }
823
+ .sk-it .where i { font-style: normal; font-size: 12px; line-height: 1 }
824
+ .sk-it .where.priv { color: var(--priv) }
825
+ .sk-it .cnt { flex: none; font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums }
826
+ .sk-empty { padding: 18px 10px; text-align: center; font-size: 12.5px; color: var(--faint) }
827
+ .sk-foot {
828
+ flex: none; display: flex; gap: 16px; padding: 8px 16px; border-top: 1px solid var(--line);
829
+ font-size: 11px; color: var(--faint); background: #fafbfc;
830
+ }
831
+ .sk-foot span { display: inline-flex; align-items: center; gap: 4px }
832
+ .sk-foot kbd { padding: 2px 4px }
833
+ @media (max-width: 760px) {
834
+ .sk { padding-top: 8px }
835
+ .sk-panel { max-height: calc(100dvh - 16px) }
836
+ .sk-foot { display: none }
837
+ }
838
+
839
+ /* 搜索跳到的那一句: 气泡闪一圈视角色。 */
840
+ .mrow.hit .mb > * { animation: hit 1.6s ease-out }
841
+ @keyframes hit { 0%, 30% { box-shadow: 0 0 0 3px #2f55c955 } 100% { box-shadow: 0 0 0 3px #2f55c900 } }