dsh-dock 0.11.2 → 0.11.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/LICENSE +18 -18
- package/README.md +255 -396
- package/client.js +596 -98
- package/cordis.patch.yml +2 -2
- package/features/animation/host.js +119 -119
- package/features/animation/view.jsx +1662 -1662
- package/features/balance/host.js +494 -494
- package/features/balance/view.js +351 -351
- package/features/games/games/breakout.jsx +227 -227
- package/features/games/games/gomoku.jsx +244 -244
- package/features/games/games/racer.jsx +169 -169
- package/features/games/games/shared.jsx +118 -118
- package/features/games/games/snake.jsx +144 -144
- package/features/games/games/sokoban.jsx +180 -180
- package/features/games/games/tank.jsx +128 -128
- package/features/games/games/tetris.jsx +308 -308
- package/features/games/games/xiangqi.jsx +277 -277
- package/features/games/tank-core.js +152 -152
- package/features/games/view.jsx +639 -639
- package/features/games/xiangqi-core.js +299 -299
- package/features/heartbeat/view.js +35 -35
- package/features/mobile-relay/gateway.js +472 -472
- package/features/mobile-relay/host.js +622 -570
- package/features/mobile-relay/view.jsx +380 -300
- package/features/modelconfig/host.js +406 -406
- package/features/modelconfig/view.js +490 -490
- package/features/notify/host.js +374 -374
- package/features/notify/view.jsx +751 -751
- package/features/theme/view.js +61 -61
- package/features/tokenlog/host.js +882 -883
- package/features/tokenlog/view.jsx +1369 -932
- package/features/visionproxy/host.js +315 -315
- package/index.js +195 -195
- package/package.json +66 -64
- package/screenshots/dock-tokenlog-charts-2.png +0 -0
- package/screenshots/dock-tokenlog-charts-3.png +0 -0
- package/screenshots/dock-tokenlog-charts.png +0 -0
- package/src/client.jsx +677 -677
- package/src/host-core.js +207 -207
- package/src/shared.js +192 -192
- package/src/task-track.js +354 -359
|
@@ -1,1662 +1,1662 @@
|
|
|
1
|
-
// dsh-dock · 功能模块【任务动画】· 客户端视图
|
|
2
|
-
//
|
|
3
|
-
// 三部分:
|
|
4
|
-
// 1. View —— 功能坞面板页:动画开关 + 19 种模式选择(带缩微预览);
|
|
5
|
-
// 2. HomeStat —— 首页总揽概要(当前动效模式 + 开关状态);
|
|
6
|
-
// 3. Overlay —— 全局浮层(shell.overlay 常驻,功能启用即挂载):轮询 Host 状态,
|
|
7
|
-
// 任务进行中渲染克制的动效(顶部流光细线 / 呼吸光点 / 轨道光环)+ 右下角状态徽标,
|
|
8
|
-
// 完成瞬间一缕流光掠过(星际远征另有货运舰归航)。
|
|
9
|
-
//
|
|
10
|
-
// 设计取向:不做满屏粒子彩带,动效全部走主题变量(暗/亮色自适应)、低透明度、慢节奏。
|
|
11
|
-
// 通知(页内卡片 / 提示音 / 系统通知 / 钉钉飞书推送)独立在 features/notify/,
|
|
12
|
-
// 运行状态(进行中任务与最近完成)独立在 features/runstate/:三个功能各自启停、互不依赖,
|
|
13
|
-
// 会话级任务追踪在宿主侧共用一份。
|
|
14
|
-
//
|
|
15
|
-
// Host 通信:fetch('/dsh-dock/animation/<method>')(见 features/animation/host.js)。
|
|
16
|
-
import { useState, useEffect, useRef, useCallback, useMemo } from "react";
|
|
17
|
-
import { openPanel, panelNav, subscribePanel } from "../../src/shared.js";
|
|
18
|
-
|
|
19
|
-
// ---------- Host RPC 桥接 ----------
|
|
20
|
-
function rpcCall(method, args) {
|
|
21
|
-
return fetch("/dsh-dock/animation/" + method, {
|
|
22
|
-
method: "POST",
|
|
23
|
-
headers: { "content-type": "application/json" },
|
|
24
|
-
body: JSON.stringify(args === undefined ? {} : args),
|
|
25
|
-
})
|
|
26
|
-
.then(async (res) => {
|
|
27
|
-
const data = await res.json().catch(() => ({}));
|
|
28
|
-
if (res.ok && data && data.ok === true) return data.data;
|
|
29
|
-
// 宿主侧没有动画路由(405/404):宿主进程可能是旧版本,或本功能在宿主侧未 setup
|
|
30
|
-
if (res.status === 405 || res.status === 404) {
|
|
31
|
-
throw new Error("宿主侧没有「任务动画」接口:宿主进程可能是旧版本,或该功能在宿主侧未启用(重启 dsh web,或把功能坞里的开关关掉再打开)");
|
|
32
|
-
}
|
|
33
|
-
if (data && data.ok === false) throw new Error((data.error && data.error.message) || ("HTTP " + res.status));
|
|
34
|
-
throw new Error("HTTP " + res.status + (data && data.error && data.error.message ? ": " + data.error.message : ""));
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// ---------- 动画模式 ----------
|
|
39
|
-
const EFFECT_MODES = [
|
|
40
|
-
{ id: "flow", name: "流光细线", desc: "顶部细线流光往返,速度随任务吞吐加快" },
|
|
41
|
-
{ id: "breathe", name: "呼吸光点", desc: "醒目呼吸光点带扩散光晕:颜色随任务阶段(思考蓝/输出绿/代码橙/查资料青),呼吸随吞吐加快" },
|
|
42
|
-
{ id: "ring", name: "轨道光环", desc: "细环绕圆点旋转,转速随任务速度" },
|
|
43
|
-
{ id: "orbit", name: "环屏巡航", desc: "一颗光点沿屏幕边缘巡航整圈,醒目不遮挡" },
|
|
44
|
-
{ id: "robot", name: "桌面伙伴", desc: "真人化双工位:查资料看左屏、写入/改代码移到右屏;座椅和转头随实时任务节奏加速" },
|
|
45
|
-
{ id: "matrix", name: "代码雨", desc: "字符沿屏幕缓落如数据流,速度随任务吞吐,克制的低透明度" },
|
|
46
|
-
{ id: "stars", name: "星野", desc: "细碎星点缓慢飘移闪烁,安静耐看的背景氛围" },
|
|
47
|
-
{ id: "aurora", name: "极光", desc: "屏幕顶部柔光带缓慢呼吸流动,像极光拂过" },
|
|
48
|
-
{ id: "space", name: "星际远征", desc: "任务中星球与飞船巡航;完成后货运旗舰携实际输出量停靠输入框" },
|
|
49
|
-
{ id: "nebula", name: "星云潮汐", desc: "四团彩色星云在屏幕边缘缓慢潮汐,阶段色随任务改变" },
|
|
50
|
-
{ id: "warp", name: "曲速航道", desc: "光束从屏幕中心向外拉伸,吞吐越高曲速越快" },
|
|
51
|
-
{ id: "radar", name: "量子雷达", desc: "三座边缘雷达持续扫描,任务活动化作跳动信标" },
|
|
52
|
-
{ id: "constellation", name: "星座网络", desc: "节点与连线逐段点亮,像任务知识图谱在生长" },
|
|
53
|
-
{ id: "fireflies", name: "数据萤火", desc: "轻盈光点在空白区游弋,活跃阶段会更明亮" },
|
|
54
|
-
{ id: "ocean", name: "深海脉动", desc: "低透明度波层与气泡缓慢上浮,安静但富有生命感" },
|
|
55
|
-
{ id: "prism", name: "棱镜光谱", desc: "彩色光束从屏幕边缘折射穿行,形成克制的玻璃光感" },
|
|
56
|
-
{ id: "circuit", name: "神经电路", desc: "电路路径依次通电,脉冲速度跟随代码与工具活动" },
|
|
57
|
-
{ id: "gravity", name: "引力涟漪", desc: "多个引力源向外扩散波纹,任务越快涟漪越紧密" },
|
|
58
|
-
{ id: "lantern", name: "灵感天灯", desc: "微型灵感灯从屏幕底部缓慢升起,留下温暖光迹" },
|
|
59
|
-
];
|
|
60
|
-
|
|
61
|
-
const AMBIENT_EFFECT_MODES = new Set(["matrix", "stars", "aurora", "space", "nebula", "warp", "radar", "constellation", "fireflies", "ocean", "prism", "circuit", "gravity", "lantern"]);
|
|
62
|
-
|
|
63
|
-
// 任务阶段 → 机器人行为/文案(host 按 chunk/事件实时推导:
|
|
64
|
-
// reasoning-delta=think、text-delta=write、tool 名分类=search/code)
|
|
65
|
-
const PHASE_LABELS = { think: "思考中", write: "输出中", code: "编写代码", search: "查资料" };
|
|
66
|
-
function phaseLabel(p) { return PHASE_LABELS[p] || "工作中"; }
|
|
67
|
-
// 阶段配色(徽标呼吸点/光环随任务阶段变色,--dkan-phase 下发到 CSS)
|
|
68
|
-
const PHASE_COLORS = { think: "#2f6fed", write: "#0d9488", code: "#b45309", search: "#0e7490" };
|
|
69
|
-
function phaseColor(p) { return PHASE_COLORS[p] || "#2f6fed"; }
|
|
70
|
-
|
|
71
|
-
// ---------- 结束原因 ----------
|
|
72
|
-
// (结束原因的展示文案随「运行状态」页一起搬去 features/runstate/,这里只保留动效判断用的成功判定)
|
|
73
|
-
function isSuccessReason(reason) {
|
|
74
|
-
return !reason || reason === "completed";
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ---------- 格式化 ----------
|
|
78
|
-
function fmtCompact(n) {
|
|
79
|
-
n = Number(n) || 0;
|
|
80
|
-
if (n >= 1e6) return (n / 1e6).toFixed(2) + "M";
|
|
81
|
-
if (n >= 1e3) return (n / 1e3).toFixed(1) + "K";
|
|
82
|
-
return String(Math.round(n));
|
|
83
|
-
}
|
|
84
|
-
// 毫秒 → "12:34" / "1:02:11"(徽标计时)
|
|
85
|
-
function fmtClock(ms) {
|
|
86
|
-
const s = Math.max(0, Math.floor((ms || 0) / 1000));
|
|
87
|
-
const h = Math.floor(s / 3600), m = Math.floor((s % 3600) / 60), sec = s % 60;
|
|
88
|
-
const p = (x) => String(x).padStart(2, "0");
|
|
89
|
-
return h > 0 ? h + ":" + p(m) + ":" + p(sec) : p(m) + ":" + p(sec);
|
|
90
|
-
}
|
|
91
|
-
function truncate(str, max) {
|
|
92
|
-
if (!str) return "";
|
|
93
|
-
return String(str).length > max ? String(str).slice(0, max) + "…" : str;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ---------- 共享快照:浮层(轮询) / 面板页 / 首页概要共用一份数据 ----------
|
|
97
|
-
const animationStore = {
|
|
98
|
-
snap: { status: null, loading: false, error: null },
|
|
99
|
-
listeners: new Set(),
|
|
100
|
-
subscribe(fn) { this.listeners.add(fn); return () => { this.listeners.delete(fn); }; },
|
|
101
|
-
emit() { for (const fn of this.listeners) fn(); },
|
|
102
|
-
applyConfig(cfg) {
|
|
103
|
-
if (this.snap.status) {
|
|
104
|
-
this.snap = Object.assign({}, this.snap, { status: Object.assign({}, this.snap.status, { config: cfg }) });
|
|
105
|
-
this.emit();
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
refresh() {
|
|
109
|
-
if (this.snap.loading) return Promise.resolve();
|
|
110
|
-
this.snap = Object.assign({}, this.snap, { loading: true });
|
|
111
|
-
this.emit();
|
|
112
|
-
return rpcCall("status")
|
|
113
|
-
.then((data) => {
|
|
114
|
-
this.snap = { status: data, loading: false, error: null };
|
|
115
|
-
this.emit();
|
|
116
|
-
})
|
|
117
|
-
.catch((e) => {
|
|
118
|
-
this.snap = Object.assign({}, this.snap, { loading: false, error: (e && e.message) || String(e) });
|
|
119
|
-
this.emit();
|
|
120
|
-
});
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
function useAnimation() {
|
|
124
|
-
const [snap, setSnap] = useState(animationStore.snap);
|
|
125
|
-
useEffect(() => animationStore.subscribe(() => setSnap(animationStore.snap)), []);
|
|
126
|
-
return snap;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// 秒级滴答(徽标计时用;ctx.interval 优先,退回 setInterval)
|
|
130
|
-
function useTicker(ctx, active) {
|
|
131
|
-
const [, bump] = useState(0);
|
|
132
|
-
useEffect(() => {
|
|
133
|
-
if (!active) return;
|
|
134
|
-
if (ctx && typeof ctx.interval === "function") return ctx.interval(bump, 1000);
|
|
135
|
-
const t = setInterval(bump, 1000);
|
|
136
|
-
return () => clearInterval(t);
|
|
137
|
-
}, [active]);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// ---------- 桌面场景(CSS 3D:长方体拼装的 3D 动漫人物,侧身面向镜头、面朝三屏) ----------
|
|
141
|
-
// data-phase 驱动四态(think/write/code/search,host 由 assistant/chunk 流级事件实时同步);
|
|
142
|
-
// --dkan-speed 随任务吞吐加速;卡片可整卡自由拖拽(位置 localStorage 持久化)。
|
|
143
|
-
// Box3 = 六面长方体(w×h×d),faces: 前(+Z)/后/右(+X)/左/顶/底;世界层统一转角取 3/4 视角。
|
|
144
|
-
function Box3(props) {
|
|
145
|
-
const w = props.w, h = props.h, d = props.d;
|
|
146
|
-
const faces = [
|
|
147
|
-
[w, h, "translate(-50%,-50%) translateZ(" + (d / 2) + "px)"],
|
|
148
|
-
[w, h, "translate(-50%,-50%) rotateY(180deg) translateZ(" + (d / 2) + "px)"],
|
|
149
|
-
[d, h, "translate(-50%,-50%) rotateY(90deg) translateZ(" + (w / 2) + "px)"],
|
|
150
|
-
[d, h, "translate(-50%,-50%) rotateY(-90deg) translateZ(" + (w / 2) + "px)"],
|
|
151
|
-
[w, d, "translate(-50%,-50%) rotateX(90deg) translateZ(" + (h / 2) + "px)"],
|
|
152
|
-
[w, d, "translate(-50%,-50%) rotateX(-90deg) translateZ(" + (h / 2) + "px)"],
|
|
153
|
-
];
|
|
154
|
-
return (
|
|
155
|
-
<span className={"dk3-box " + (props.cls || "")}
|
|
156
|
-
style={Object.assign({ width: w, height: h, left: props.x - w / 2, top: props.y - h / 2 },
|
|
157
|
-
props.z ? { transform: "translateZ(" + props.z + "px)" } : null, props.style)}>
|
|
158
|
-
{faces.map((f, i) => (
|
|
159
|
-
<span key={i} className="dk3-face" style={{ width: f[0], height: f[1], transform: f[2] }} />
|
|
160
|
-
))}
|
|
161
|
-
{props.children}
|
|
162
|
-
</span>
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function Monitor3(props) {
|
|
167
|
-
const w = props.w, h = props.h;
|
|
168
|
-
const searchScreen = props.mode === "search";
|
|
169
|
-
// 屏幕内容:10 行代码(5 行×2 循环)+ 光标,整体向上滚动(速度随 --dkan-speed)
|
|
170
|
-
const lines = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5];
|
|
171
|
-
return (
|
|
172
|
-
<span className={"dk3-mon3 " + (props.cls || "")}
|
|
173
|
-
style={{ left: props.x - w / 2, top: props.y - h / 2, width: w, height: h, transform: "translateZ(" + (props.z || -3) + "px) rotateY(" + (props.ry || 0) + "deg)" }}>
|
|
174
|
-
<Box3 w={w} h={h} d={5} cls="dk3-frame" x={w / 2} y={h / 2} />
|
|
175
|
-
<span className="dk3-screen" style={{ width: w - 4, height: h - 4, transform: "translate(-50%,-50%) translateZ(3.1px)" }}>
|
|
176
|
-
{searchScreen ? <span className="dk3-search-ui">
|
|
177
|
-
<span className="dk3-search-box"><b /></span>
|
|
178
|
-
<span className="dk3-search-results">{lines.slice(0, 6).map((n, i) => <i key={i} />)}</span>
|
|
179
|
-
</span> : <span className="dk3-code">
|
|
180
|
-
{lines.map((n, i) => <i key={i} />)}
|
|
181
|
-
<span className="dk3-cur" />
|
|
182
|
-
</span>}
|
|
183
|
-
</span>
|
|
184
|
-
</span>
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// 3D 动漫人物:更拟人的比例与细节(圆角头/ volumetric 头发/五官/脖颈/弯肘/腿脚鞋)。
|
|
189
|
-
// 紧凑双工位:左侧专做资料检索,右侧专做写作/改文件;顶部小屏放任务状态。
|
|
190
|
-
// 人物坐滚轮椅在两侧工位间滑动,思考时停在两屏之间。
|
|
191
|
-
function RobotScene(props) {
|
|
192
|
-
const phase = props && props.phase ? props.phase : "code";
|
|
193
|
-
const tasks = Array.isArray(props && props.tasks) ? props.tasks : null;
|
|
194
|
-
const n = tasks ? Math.max(1, Math.min(3, tasks.length)) : 1;
|
|
195
|
-
// 左工位只做资料检索;正文输出和工具写入都在右工位,思考回到中位。
|
|
196
|
-
const station = phase === "search" ? "left" : (phase === "write" || phase === "code" ? "right" : "center");
|
|
197
|
-
return (
|
|
198
|
-
<div className="dkan-bot-scene" data-phase={phase} data-station={station} data-tasks={tasks ? String(n) : undefined} aria-hidden="true">
|
|
199
|
-
<div className="dk3-world">
|
|
200
|
-
{/* 紧凑单人书桌:只留左右两个真实工作位,消除左侧空桌面。 */}
|
|
201
|
-
<Box3 w={134} h={7} d={38} cls="dk3-desk" x={145} y={76} />
|
|
202
|
-
<Box3 w={5} h={24} d={30} cls="dk3-metal dk3-leg" x={84} y={89} />
|
|
203
|
-
<Box3 w={5} h={24} d={30} cls="dk3-metal dk3-leg" x={206} y={89} />
|
|
204
|
-
{/* 左检索、右编码,顶部小屏显示任务进度。 */}
|
|
205
|
-
<Monitor3 w={34} h={24} x={140} y={59} ry={20} cls="left" mode="search" />
|
|
206
|
-
<Monitor3 w={42} h={30} x={180} y={57} ry={-16} cls="right" />
|
|
207
|
-
<Monitor3 w={28} h={18} x={160} y={31} ry={0} cls="top" />
|
|
208
|
-
{/* 两套键鼠紧贴相应屏幕,中央仅保留咖啡杯。 */}
|
|
209
|
-
<Box3 w={16} h={2.5} d={9} cls="dk3-metal dk3-kb3" x={130} y={71} z={14} />
|
|
210
|
-
<Box3 w={4} h={2.5} d={6} cls="dk3-metal dk3-mouse3" x={141} y={71} z={14} />
|
|
211
|
-
<Box3 w={4} h={5} d={4} cls="dk3-mug" x={154} y={69} z={14} />
|
|
212
|
-
<Box3 w={16} h={2.5} d={9} cls="dk3-metal dk3-kb3" x={170} y={71} z={14} />
|
|
213
|
-
<Box3 w={4} h={2.5} d={6} cls="dk3-metal dk3-mouse3" x={181} y={71} z={14} />
|
|
214
|
-
{/* 人物组:按当前任务在左检索/中思考/右编码两个工位间平滑移动。 */}
|
|
215
|
-
<div className="dk3-person">
|
|
216
|
-
{/* 椅子:靠背只到肩胛、降低支柱和底盘,贴近真实坐姿。 */}
|
|
217
|
-
<Box3 w={4} h={20} d={20} cls="dk3-chairback" x={2} y={47} />
|
|
218
|
-
<Box3 w={18} h={4} d={20} cls="dk3-chairseat" x={12} y={59} />
|
|
219
|
-
<Box3 w={3} h={9} d={3} cls="dk3-metal" x={12} y={66} />
|
|
220
|
-
<Box3 w={13} h={2} d={13} cls="dk3-metal" x={12} y={71} />
|
|
221
|
-
{/* 椅子滚轮(左右各一,滑动时可见) */}
|
|
222
|
-
<Box3 w={3} h={3} d={3} cls="dk3-wheel" x={6} y={74} />
|
|
223
|
-
<Box3 w={3} h={3} d={3} cls="dk3-wheel" x={18} y={74} />
|
|
224
|
-
{/* 腿:大腿/小腿/鞋 */}
|
|
225
|
-
<Box3 w={12} h={5} d={9} cls="dk3-pants" x={20} y={54} />
|
|
226
|
-
<Box3 w={4} h={10} d={4} cls="dk3-pants" x={26} y={62} />
|
|
227
|
-
<Box3 w={6} h={3} d={5} cls="dk3-shoe" x={28} y={70} />
|
|
228
|
-
{/* 上身整体始终面向桌面;阶段动作只改变前倾、肩肘腕和视线。 */}
|
|
229
|
-
<div className="dk3-upper3">
|
|
230
|
-
{/* 肩线、胸腔和腰部做成上宽下窄,摆脱直筒积木感。 */}
|
|
231
|
-
<Box3 w={18} h={5} d={12} cls="dk3-hood dk3-shoulders3" x={13} y={30} />
|
|
232
|
-
<Box3 w={15} h={14} d={11} cls="dk3-hood dk3-torso dk3-chest3" x={12} y={38} />
|
|
233
|
-
<Box3 w={12} h={7} d={10} cls="dk3-hood dk3-torso dk3-waist3" x={12} y={47} />
|
|
234
|
-
<Box3 w={5} h={8} d={9} cls="dk3-hood dk3-hoodbump" x={5} y={32} />
|
|
235
|
-
{/* 颈部加长并与下颌、衣领重叠,避免悬空。 */}
|
|
236
|
-
<Box3 w={5} h={7} d={5} cls="dk3-skin dk3-neck3" x={14} y={24} />
|
|
237
|
-
<Box3 w={8} h={3} d={8} cls="dk3-collar3" x={14} y={28} />
|
|
238
|
-
{/* 头组与颈部同轴:颅骨 + 收窄下颌 + 短侧渐层发型。 */}
|
|
239
|
-
<div className="dk3-head3">
|
|
240
|
-
<Box3 w={12} h={11} d={11} cls="dk3-skin dk3-cranium" x={8} y={8}>
|
|
241
|
-
<span className="dk3-brows" style={{ width: 9, height: 4, transform: "translate(-50%,-50%) rotateY(90deg) translateZ(5.8px)" }}><i /><i /></span>
|
|
242
|
-
<span className="dk3-eyes" style={{ width: 9, height: 6, transform: "translate(-50%,-50%) rotateY(90deg) translateZ(5.7px)" }}><i /><i /></span>
|
|
243
|
-
<span className="dk3-nose" style={{ transform: "translate(-50%,-50%) rotateY(90deg) translateZ(6.1px)" }} />
|
|
244
|
-
</Box3>
|
|
245
|
-
<Box3 w={9} h={7} d={9} cls="dk3-skin dk3-jaw3" x={8.5} y={14}>
|
|
246
|
-
<span className="dk3-mouth" style={{ transform: "translate(-50%,-50%) rotateY(90deg) translateZ(4.7px)" }} />
|
|
247
|
-
</Box3>
|
|
248
|
-
<Box3 w={12} h={5} d={12} cls="dk3-hair dk3-hair-crown" x={8} y={3.5} />
|
|
249
|
-
<Box3 w={4} h={8} d={11} cls="dk3-hair dk3-hair-back" x={3} y={7.5} />
|
|
250
|
-
<span className="dk3-fringe3"><i /><i /><i /></span>
|
|
251
|
-
<span className="dk3-ear" />
|
|
252
|
-
</div>
|
|
253
|
-
{/* 近侧手臂:肩、肘、腕、手指分段动作。 */}
|
|
254
|
-
<div className="dk3-arm3 dk3-near">
|
|
255
|
-
<Box3 w={4} h={11} d={4} cls="dk3-hood dk3-uarm" x={2} y={6} />
|
|
256
|
-
<div className="dk3-elbow">
|
|
257
|
-
<Box3 w={10} h={3.5} d={3.5} cls="dk3-hood dk3-farm" x={5.5} y={2} />
|
|
258
|
-
<div className="dk3-wrist3">
|
|
259
|
-
<Box3 w={3.5} h={3} d={3} cls="dk3-skin dk3-hand" x={2} y={2} />
|
|
260
|
-
<span className="dk3-fingers3"><i /><i /></span>
|
|
261
|
-
</div>
|
|
262
|
-
</div>
|
|
263
|
-
</div>
|
|
264
|
-
{/* 远侧手臂:在桌面深处,和近侧手错开节奏。 */}
|
|
265
|
-
<div className="dk3-arm3 dk3-far">
|
|
266
|
-
<Box3 w={4} h={11} d={4} cls="dk3-hood dk3-uarm" x={2} y={6} />
|
|
267
|
-
<div className="dk3-elbow">
|
|
268
|
-
<Box3 w={10} h={3.5} d={3.5} cls="dk3-hood dk3-farm" x={5.5} y={2} />
|
|
269
|
-
<div className="dk3-wrist3">
|
|
270
|
-
<Box3 w={3.5} h={3} d={3} cls="dk3-skin dk3-hand" x={2} y={2} />
|
|
271
|
-
<span className="dk3-fingers3"><i /><i /></span>
|
|
272
|
-
</div>
|
|
273
|
-
</div>
|
|
274
|
-
</div>
|
|
275
|
-
</div>
|
|
276
|
-
</div>
|
|
277
|
-
</div>
|
|
278
|
-
{/* 思考泡泡(贴镜头的 2D 层,think 阶段显示) */}
|
|
279
|
-
<span className="dkan-bubble"><i /><i /><i /></span>
|
|
280
|
-
</div>
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// ---------- 交互反馈层:对用户操作即时回应(纸飞机/火花/流光/微光/光涌/彩带) ----------
|
|
285
|
-
// 事件来源:document 点击监听(发送/新会话/切会话/切工作目录)+ 任务状态变化(开始/完成)。
|
|
286
|
-
// 每个 burst = { id, type, x, y, bits },bits 为预生成的随机粒子参数(内联样式驱动 CSS 动画)。
|
|
287
|
-
function makeBits(n, fn) {
|
|
288
|
-
const arr = [];
|
|
289
|
-
for (let i = 0; i < n; i++) arr.push(fn(i));
|
|
290
|
-
return arr;
|
|
291
|
-
}
|
|
292
|
-
function BurstLayer(props) {
|
|
293
|
-
const bursts = props.bursts || [];
|
|
294
|
-
return (
|
|
295
|
-
<div className="dkan-fxwrap" aria-hidden="true">
|
|
296
|
-
{bursts.map((b) => {
|
|
297
|
-
if (b.type === "plane") {
|
|
298
|
-
return <span key={b.id} className="dkan-fx dkan-fx-plane" style={{ left: b.x, top: b.y }}>➤</span>;
|
|
299
|
-
}
|
|
300
|
-
if (b.type === "spark") {
|
|
301
|
-
return (
|
|
302
|
-
<span key={b.id} className="dkan-fx dkan-fx-spark" style={{ left: b.x, top: b.y }}>
|
|
303
|
-
{b.bits.map((p, i) => <i key={i} style={{ "--dx": p.dx + "px", "--dy": p.dy + "px", background: p.c, boxShadow: "0 0 6px " + p.c }} />)}
|
|
304
|
-
</span>
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
if (b.type === "streak") return <span key={b.id} className="dkan-fx dkan-fx-streak" />;
|
|
308
|
-
if (b.type === "flash") return <span key={b.id} className="dkan-fx dkan-fx-flash" />;
|
|
309
|
-
if (b.type === "surge") return <span key={b.id} className="dkan-fx dkan-fx-surge" />;
|
|
310
|
-
if (b.type === "confetti") {
|
|
311
|
-
return (
|
|
312
|
-
<span key={b.id} className="dkan-fx dkan-fx-confetti">
|
|
313
|
-
{b.bits.map((p, i) => <i key={i} style={{ left: p.l + "%", "--dx": p.dx + "px", "--r": p.r + "deg", background: p.c, boxShadow: "0 0 4px " + p.c, animationDelay: p.d + "s" }} />)}
|
|
314
|
-
</span>
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
return null;
|
|
318
|
-
})}
|
|
319
|
-
</div>
|
|
320
|
-
);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// ---------- 氛围动效:代码雨 / 星野 / 极光(粒子配置 useMemo 稳定,避免重渲染抖动) ----------
|
|
324
|
-
const MATRIX_CHARS = "01</>;{}=+*#";
|
|
325
|
-
function AmbientLayer(props) {
|
|
326
|
-
const mode = props.mode;
|
|
327
|
-
const speed = props.speed;
|
|
328
|
-
// 一个模式只创建自己需要的粒子;星际模式不再额外生成代码雨和星野的 114 个无用节点。
|
|
329
|
-
const particles = useMemo(() => {
|
|
330
|
-
if (mode === "matrix") return { matrix: makeBits(34, () => ({
|
|
331
|
-
l: Math.random() * 100, d: 4 + Math.random() * 6, delay: -Math.random() * 8, o: 0.28 + Math.random() * 0.34, s: 11 + Math.round(Math.random() * 4),
|
|
332
|
-
c: MATRIX_CHARS[Math.floor(Math.random() * MATRIX_CHARS.length)] + MATRIX_CHARS[Math.floor(Math.random() * MATRIX_CHARS.length)] + MATRIX_CHARS[Math.floor(Math.random() * MATRIX_CHARS.length)],
|
|
333
|
-
})) };
|
|
334
|
-
if (mode === "stars") return { stars: makeBits(80, () => ({
|
|
335
|
-
l: Math.random() * 100, t: Math.random() * 100, sz: 1.5 + Math.random() * 2, d: 2 + Math.random() * 4, delay: -Math.random() * 6, dx: (Math.random() - 0.5) * 40, dy: (Math.random() - 0.5) * 24,
|
|
336
|
-
})) };
|
|
337
|
-
if (mode === "space") return { dust: makeBits(22, () => ({
|
|
338
|
-
l: Math.random() * 100, t: Math.random() * 82, sz: 1 + Math.random() * 2.2, d: 2.4 + Math.random() * 3.8, delay: -Math.random() * 6,
|
|
339
|
-
})) };
|
|
340
|
-
if (mode === "warp") return { warp: makeBits(28, () => ({
|
|
341
|
-
a: Math.random() * 360, len: 54 + Math.random() * 130, d: 1.8 + Math.random() * 2.4, delay: -Math.random() * 4,
|
|
342
|
-
})) };
|
|
343
|
-
if (mode === "fireflies") return { fireflies: makeBits(28, () => ({
|
|
344
|
-
l: 4 + Math.random() * 92, t: 8 + Math.random() * 78, sz: 3 + Math.random() * 5, dx: -42 + Math.random() * 84, dy: -32 + Math.random() * 64, d: 4.8 + Math.random() * 5.4, delay: -Math.random() * 8,
|
|
345
|
-
})) };
|
|
346
|
-
if (mode === "ocean") return { bubbles: makeBits(18, () => ({
|
|
347
|
-
l: 3 + Math.random() * 94, sz: 4 + Math.random() * 12, d: 5 + Math.random() * 6, delay: -Math.random() * 9, drift: -34 + Math.random() * 68,
|
|
348
|
-
})) };
|
|
349
|
-
if (mode === "lantern") return { lanterns: makeBits(18, () => ({
|
|
350
|
-
l: 4 + Math.random() * 92, sz: 8 + Math.random() * 9, d: 7 + Math.random() * 7, delay: -Math.random() * 12, drift: -48 + Math.random() * 96,
|
|
351
|
-
})) };
|
|
352
|
-
return {};
|
|
353
|
-
}, [mode]);
|
|
354
|
-
const ambientStyle = { "--dkan-speed": speed, "--dkan-phase": phaseColor(props.phase) };
|
|
355
|
-
if (mode === "matrix") {
|
|
356
|
-
return (
|
|
357
|
-
<div className="dkan-amb dkan-matrix" style={{ "--dkan-speed": speed }} aria-hidden="true">
|
|
358
|
-
{particles.matrix.map((p, i) => (
|
|
359
|
-
<span key={i} style={{ left: p.l + "%", animationDuration: "calc(" + p.d + "s / var(--dkan-speed,1))", animationDelay: p.delay + "s", opacity: p.o, fontSize: p.s }}>{p.c}</span>
|
|
360
|
-
))}
|
|
361
|
-
</div>
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
if (mode === "stars") {
|
|
365
|
-
return (
|
|
366
|
-
<div className="dkan-amb dkan-stars" style={{ "--dkan-speed": speed }} aria-hidden="true">
|
|
367
|
-
{particles.stars.map((p, i) => (
|
|
368
|
-
<i key={i} className="dkan-star" style={{ left: p.l + "%", top: p.t + "%", width: p.sz, height: p.sz, animationDuration: "calc(" + p.d + "s / var(--dkan-speed,1))", animationDelay: p.delay + "s", "--dx": p.dx + "px", "--dy": p.dy + "px" }} />
|
|
369
|
-
))}
|
|
370
|
-
<span className="dkan-moon" />
|
|
371
|
-
<i className="dkan-meteor" style={{ top: "12%", left: "72%", animationDelay: "0s" }} />
|
|
372
|
-
<i className="dkan-meteor" style={{ top: "28%", left: "88%", animationDelay: "2.8s" }} />
|
|
373
|
-
<i className="dkan-meteor" style={{ top: "6%", left: "42%", animationDelay: "5.6s" }} />
|
|
374
|
-
</div>
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
if (mode === "aurora") {
|
|
378
|
-
return (
|
|
379
|
-
<div className="dkan-amb dkan-aurora" style={{ "--dkan-speed": speed }} aria-hidden="true">
|
|
380
|
-
<i /><i /><i />
|
|
381
|
-
</div>
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
if (mode === "nebula") {
|
|
385
|
-
return <div className="dkan-amb dkan-nebula" style={ambientStyle} aria-hidden="true"><i /><i /><i /><i /></div>;
|
|
386
|
-
}
|
|
387
|
-
if (mode === "warp") {
|
|
388
|
-
return <div className="dkan-amb dkan-warp" style={ambientStyle} aria-hidden="true">
|
|
389
|
-
{particles.warp.map((p, i) => <i key={i} style={{ "--a": p.a + "deg", "--len": p.len + "px", animationDuration: "calc(" + p.d + "s / var(--dkan-speed,1))", animationDelay: p.delay + "s" }} />)}
|
|
390
|
-
</div>;
|
|
391
|
-
}
|
|
392
|
-
if (mode === "radar") {
|
|
393
|
-
return <div className="dkan-amb dkan-radar" style={ambientStyle} aria-hidden="true">
|
|
394
|
-
{["a", "b", "c"].map((id) => <span className={"dkan-radar-station " + id} key={id}><i /><b /></span>)}
|
|
395
|
-
</div>;
|
|
396
|
-
}
|
|
397
|
-
if (mode === "constellation") {
|
|
398
|
-
return <div className="dkan-amb dkan-constellation" style={ambientStyle} aria-hidden="true">
|
|
399
|
-
<svg viewBox="0 0 100 100" preserveAspectRatio="none">
|
|
400
|
-
<path pathLength="1" d="M6 68 L18 30 L31 48 L45 18 L58 42 L73 25 L91 58 L78 78 L58 42 L31 48 L18 76 L6 68" />
|
|
401
|
-
{[[6,68],[18,30],[31,48],[45,18],[58,42],[73,25],[91,58],[78,78],[18,76]].map(([cx, cy], i) => <circle key={i} cx={cx} cy={cy} r={i % 3 === 0 ? 1.1 : .7} style={{ animationDelay: (-i * .24) + "s" }} />)}
|
|
402
|
-
</svg>
|
|
403
|
-
</div>;
|
|
404
|
-
}
|
|
405
|
-
if (mode === "fireflies") {
|
|
406
|
-
return <div className="dkan-amb dkan-fireflies" style={ambientStyle} aria-hidden="true">
|
|
407
|
-
{particles.fireflies.map((p, i) => <i key={i} style={{ left: p.l + "%", top: p.t + "%", width: p.sz, height: p.sz, "--dx": p.dx + "px", "--dy": p.dy + "px", animationDuration: "calc(" + p.d + "s / var(--dkan-speed,1))", animationDelay: p.delay + "s" }} />)}
|
|
408
|
-
</div>;
|
|
409
|
-
}
|
|
410
|
-
if (mode === "ocean") {
|
|
411
|
-
return <div className="dkan-amb dkan-ocean" style={ambientStyle} aria-hidden="true">
|
|
412
|
-
<span className="dkan-ocean-waves">{Array.from({ length: 5 }, (_, i) => <i key={i} />)}</span>
|
|
413
|
-
{particles.bubbles.map((p, i) => <b key={i} style={{ left: p.l + "%", width: p.sz, height: p.sz, "--drift": p.drift + "px", animationDuration: "calc(" + p.d + "s / var(--dkan-speed,1))", animationDelay: p.delay + "s" }} />)}
|
|
414
|
-
</div>;
|
|
415
|
-
}
|
|
416
|
-
if (mode === "prism") {
|
|
417
|
-
return <div className="dkan-amb dkan-prism" style={ambientStyle} aria-hidden="true">{Array.from({ length: 7 }, (_, i) => <i key={i} />)}</div>;
|
|
418
|
-
}
|
|
419
|
-
if (mode === "circuit") {
|
|
420
|
-
return <div className="dkan-amb dkan-circuit" style={ambientStyle} aria-hidden="true">
|
|
421
|
-
<svg viewBox="0 0 1000 600" preserveAspectRatio="none">
|
|
422
|
-
{["M0 92 H180 V210 H390 V128 H610 V260 H820 V104 H1000", "M0 486 H144 V358 H334 V470 H520 V338 H748 V448 H1000", "M82 0 V126 H264 V286 H472 V196 H694 V320 H914 V600", "M936 0 V148 H776 V278 H566 V410 H350 V548 H112 V600"].map((d, i) => <path key={i} pathLength="1" d={d} style={{ animationDelay: (-i * .7) + "s" }} />)}
|
|
423
|
-
{[[180,210],[390,128],[610,260],[144,358],[520,338],[264,286],[694,320],[566,410]].map(([cx, cy], i) => <circle key={i} cx={cx} cy={cy} r="5" />)}
|
|
424
|
-
</svg>
|
|
425
|
-
</div>;
|
|
426
|
-
}
|
|
427
|
-
if (mode === "gravity") {
|
|
428
|
-
return <div className="dkan-amb dkan-gravity" style={ambientStyle} aria-hidden="true">
|
|
429
|
-
{["a", "b", "c"].map((id) => <span className={"dkan-gravity-source " + id} key={id}><b />{Array.from({ length: 4 }, (_, i) => <i key={i} style={{ animationDelay: "calc(" + (-i * .9) + "s / var(--dkan-speed,1))" }} />)}</span>)}
|
|
430
|
-
</div>;
|
|
431
|
-
}
|
|
432
|
-
if (mode === "lantern") {
|
|
433
|
-
return <div className="dkan-amb dkan-lantern" style={ambientStyle} aria-hidden="true">
|
|
434
|
-
{particles.lanterns.map((p, i) => <i key={i} style={{ left: p.l + "%", width: p.sz, height: p.sz * 1.28, "--drift": p.drift + "px", animationDuration: "calc(" + p.d + "s / var(--dkan-speed,1))", animationDelay: p.delay + "s" }} />)}
|
|
435
|
-
</div>;
|
|
436
|
-
}
|
|
437
|
-
if (mode === "space") {
|
|
438
|
-
const taskCount = Math.max(1, Math.min(3, Array.isArray(props.tasks) ? props.tasks.length : 1));
|
|
439
|
-
// 任务期间每条航线至少有两艘巡航船,三座星系之间始终有可见往返;并发任务越多,舰队越密。
|
|
440
|
-
// 所有飞船仍只使用 transform/opacity 合成动画,速度跟随任务活动速率。
|
|
441
|
-
const runnerCount = Math.max(6, Math.min(9, taskCount * 3));
|
|
442
|
-
const galaxies = props.galaxies;
|
|
443
|
-
if (!galaxies) return null;
|
|
444
|
-
return (
|
|
445
|
-
<div className="dkan-amb dkan-space" style={{
|
|
446
|
-
"--dkan-speed": speed, "--dkan-space-ships": taskCount,
|
|
447
|
-
"--dkan-alpha-x": galaxies.alpha.cx + "px", "--dkan-alpha-y": galaxies.alpha.cy + "px",
|
|
448
|
-
"--dkan-beta-x": galaxies.beta.cx + "px", "--dkan-beta-y": galaxies.beta.cy + "px",
|
|
449
|
-
"--dkan-gamma-x": galaxies.gamma.cx + "px", "--dkan-gamma-y": galaxies.gamma.cy + "px",
|
|
450
|
-
}} aria-hidden="true">
|
|
451
|
-
{particles.dust.map((p, i) => <i key={i} className="dkan-space-dust" style={{ left: p.l + "%", top: p.t + "%", width: p.sz, height: p.sz, animationDuration: "calc(" + p.d + "s / var(--dkan-speed,1))", animationDelay: p.delay + "s" }} />)}
|
|
452
|
-
{["alpha", "beta", "gamma"].map((galaxy) => <span className={"dkan-space-system " + galaxy} key={galaxy} style={{ left: galaxies[galaxy].x, top: galaxies[galaxy].y, "--dkan-system-scale": galaxies[galaxy].scale }}>
|
|
453
|
-
<i className="dkan-space-sun" />
|
|
454
|
-
<span className="dkan-space-orbit a"><i /></span>
|
|
455
|
-
<span className="dkan-space-orbit b"><i /></span>
|
|
456
|
-
<span className="dkan-space-orbit c"><i /></span>
|
|
457
|
-
</span>)}
|
|
458
|
-
{Array.from({ length: runnerCount }, (_, i) => <span className={"dkan-space-runner route-" + (i % 3)} key={i} style={{ "--dkan-run-delay": (-i * 1.8) + "s" }}><FreighterHull units={1} empty /></span>)}
|
|
459
|
-
</div>
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
|
-
return null;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// ---------- 完成货运舰:从任务航线归航到会话输入框上方,货舱量使用真实输出 Token ----------
|
|
466
|
-
function outputCargo(outputTokens) {
|
|
467
|
-
const tokens = Math.max(0, Number(outputTokens) || 0);
|
|
468
|
-
// 每个可见货舱约代表 900 输出 Token;保留 1 个基础舱,防止无 usage 时舰体塌陷。
|
|
469
|
-
return { tokens, units: Math.max(1, Math.min(8, Math.ceil(tokens / 900))) };
|
|
470
|
-
}
|
|
471
|
-
function fallbackComposerDockPoint(side = "right") {
|
|
472
|
-
const vw = typeof window === "undefined" ? 1280 : window.innerWidth;
|
|
473
|
-
const vh = typeof window === "undefined" ? 900 : window.innerHeight;
|
|
474
|
-
return {
|
|
475
|
-
// 待命位贴近输入框右上;右侧远端只保留给旧的通用停靠计算。
|
|
476
|
-
x: side === "left" ? Math.max(304, Math.min(520, Math.round(vw * .18)))
|
|
477
|
-
: side === "ready" ? Math.max(316, vw - 640)
|
|
478
|
-
: Math.max(16, vw - 232),
|
|
479
|
-
y: Math.max(32, vh - 218),
|
|
480
|
-
};
|
|
481
|
-
}
|
|
482
|
-
function visibleRect(el) {
|
|
483
|
-
if (!el || typeof window === "undefined") return null;
|
|
484
|
-
const r = el.getBoundingClientRect();
|
|
485
|
-
return r.width > 0 && r.height > 0 && r.bottom > 0 && r.top < window.innerHeight ? r : null;
|
|
486
|
-
}
|
|
487
|
-
// 当前正在看的会话标题(侧栏高亮行)。dsh 会话树行 = role="treeitem" + aria-selected,
|
|
488
|
-
// 行文本即会话标题;据此把"需确认/卡住"这类会话级卡片限定到当前会话,别的会话等待时不打扰。
|
|
489
|
-
function currentSessionTitle() {
|
|
490
|
-
if (typeof document === "undefined") return "";
|
|
491
|
-
for (const el of document.querySelectorAll('[role="treeitem"][aria-selected="true"]')) {
|
|
492
|
-
const r = el.getBoundingClientRect();
|
|
493
|
-
// 侧栏在左:跳过正文区可能出现的同名高亮节点(右上搜索结果等)
|
|
494
|
-
if (r.width > 0 && r.height > 0 && r.right <= window.innerWidth * .35) {
|
|
495
|
-
const text = (el.textContent || "").trim();
|
|
496
|
-
if (text) return text;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
return "";
|
|
500
|
-
}
|
|
501
|
-
// 指定任务是否属于当前正在看的会话(按标题对齐;侧栏不可见时退回"是"避免整层失效)
|
|
502
|
-
function taskOnScreen(task, titleCache) {
|
|
503
|
-
const title = task && (task.title || "");
|
|
504
|
-
if (!title) return true;
|
|
505
|
-
const current = titleCache !== undefined ? titleCache : currentSessionTitle();
|
|
506
|
-
if (!current) return true;
|
|
507
|
-
return current === title || current.indexOf(title) !== -1 || title.indexOf(current) !== -1;
|
|
508
|
-
}
|
|
509
|
-
const COMPOSER_SEND_SELECTOR = 'button[aria-label="Send message"],button[aria-label*="发送"],button[data-testid*="send"],button[data-testid*="submit"],button[type="submit"]';
|
|
510
|
-
function conversationComposer() {
|
|
511
|
-
if (typeof document === "undefined" || typeof window === "undefined") return null;
|
|
512
|
-
// 发送键只会位于真实会话输入卡内。由它逐层向上找卡片,比遍历历史消息的编辑节点可靠。
|
|
513
|
-
const sendButtons = document.querySelectorAll(COMPOSER_SEND_SELECTOR);
|
|
514
|
-
for (const button of sendButtons) {
|
|
515
|
-
let el = button.parentElement;
|
|
516
|
-
for (let depth = 0; el && depth < 9; depth += 1, el = el.parentElement) {
|
|
517
|
-
const r = visibleRect(el);
|
|
518
|
-
if (r && r.width >= 360 && r.height >= 72 && r.height <= 360 && r.top > window.innerHeight * .42) return { element: el, rect: r };
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
return null;
|
|
522
|
-
}
|
|
523
|
-
function conversationComposerRect() {
|
|
524
|
-
const composer = conversationComposer();
|
|
525
|
-
return composer ? composer.rect : null;
|
|
526
|
-
}
|
|
527
|
-
function composerPointFromRect(composer, side) {
|
|
528
|
-
return {
|
|
529
|
-
x: side === "ready" ? Math.max(composer.left + 16, composer.right - 210) : fallbackComposerDockPoint(side).x,
|
|
530
|
-
y: Math.max(22, composer.top - 92),
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
function composerDockPoint(side = "right") {
|
|
534
|
-
if (typeof document === "undefined" || typeof window === "undefined") return fallbackComposerDockPoint(side);
|
|
535
|
-
const composer = conversationComposerRect();
|
|
536
|
-
if (composer) return composerPointFromRect(composer, side);
|
|
537
|
-
const selectors = [
|
|
538
|
-
'[data-testid*="composer"]', '[data-testid*="conversation-input"]', '[data-slot*="conversation.input"]',
|
|
539
|
-
'textarea[placeholder*="Message"]', 'textarea[placeholder*="消息"]', 'textarea',
|
|
540
|
-
'[contenteditable="true"][data-testid*="composer"]', '[contenteditable="true"][aria-label*="消息"]', '[contenteditable="true"][data-slot*="conversation.input"]',
|
|
541
|
-
];
|
|
542
|
-
const seen = new Set();
|
|
543
|
-
const candidates = [];
|
|
544
|
-
for (const selector of selectors) {
|
|
545
|
-
for (const el of document.querySelectorAll(selector)) {
|
|
546
|
-
if (seen.has(el)) continue;
|
|
547
|
-
seen.add(el);
|
|
548
|
-
const r = el.getBoundingClientRect();
|
|
549
|
-
if (r.width > 80 && r.height > 16 && r.bottom > window.innerHeight * .48) candidates.push({ el, r });
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
const best = candidates.sort((a, b) => b.r.bottom - a.r.bottom)[0];
|
|
553
|
-
if (!best) return fallbackComposerDockPoint(side);
|
|
554
|
-
const r = best.r;
|
|
555
|
-
// 候补输入节点没有卡片边界时,仅待命位贴近其右侧;其他位置仍由视口锚点决定。
|
|
556
|
-
const x = side === "ready" ? Math.max(r.left + 16, r.right - 210) : fallbackComposerDockPoint(side).x;
|
|
557
|
-
return {
|
|
558
|
-
// 纵向以真实输入控件上沿停靠,飞船底部和输入框保留间隙。
|
|
559
|
-
x,
|
|
560
|
-
y: Math.max(22, r.top - 92),
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
// 刷新时先让会话本身完成首帧绘制,再在空闲期读取输入框布局。
|
|
564
|
-
// 避免星际模式恢复时 querySelectorAll + getBoundingClientRect 抢占主线程。
|
|
565
|
-
function useSpaceDocks(enabled) {
|
|
566
|
-
const [docks, setDocks] = useState(null);
|
|
567
|
-
useEffect(() => {
|
|
568
|
-
if (!enabled || typeof window === "undefined") { setDocks(null); return undefined; }
|
|
569
|
-
let disposed = false;
|
|
570
|
-
let frame = 0;
|
|
571
|
-
let idle = 0;
|
|
572
|
-
let timer = 0;
|
|
573
|
-
let retries = 0;
|
|
574
|
-
let pending = false;
|
|
575
|
-
let observedComposer = null;
|
|
576
|
-
let resizeObserver = null;
|
|
577
|
-
const samePoint = (a, b) => !!a && !!b && Math.abs(a.x - b.x) < 1 && Math.abs(a.y - b.y) < 1;
|
|
578
|
-
const bindComposerResize = (element) => {
|
|
579
|
-
if (!resizeObserver || observedComposer === element) return;
|
|
580
|
-
if (observedComposer) resizeObserver.unobserve(observedComposer);
|
|
581
|
-
observedComposer = element;
|
|
582
|
-
if (observedComposer) resizeObserver.observe(observedComposer);
|
|
583
|
-
};
|
|
584
|
-
const measure = () => {
|
|
585
|
-
if (disposed) return;
|
|
586
|
-
const found = conversationComposer();
|
|
587
|
-
// 历史会话切换时输入卡可能比浮层更晚挂载;没有真实卡片就不显示,绝不猜到正文中。
|
|
588
|
-
if (!found) {
|
|
589
|
-
bindComposerResize(null);
|
|
590
|
-
setDocks(null);
|
|
591
|
-
if (retries++ < 20) schedule(140);
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
retries = 0;
|
|
595
|
-
bindComposerResize(found.element);
|
|
596
|
-
const composer = found.rect;
|
|
597
|
-
const right = composerPointFromRect(composer, "right");
|
|
598
|
-
// 三个停泊位共用同一输入框上沿,避免重复扫描整个页面读取布局。
|
|
599
|
-
const next = {
|
|
600
|
-
right,
|
|
601
|
-
left: composerPointFromRect(composer, "left"),
|
|
602
|
-
ready: composerPointFromRect(composer, "ready"),
|
|
603
|
-
};
|
|
604
|
-
setDocks((current) => current && samePoint(current.right, next.right) && samePoint(current.left, next.left) && samePoint(current.ready, next.ready) ? current : next);
|
|
605
|
-
};
|
|
606
|
-
function schedule(delay = 0, deferUntilIdle = false) {
|
|
607
|
-
if (disposed || pending) return;
|
|
608
|
-
pending = true;
|
|
609
|
-
const queueFrame = () => {
|
|
610
|
-
frame = window.requestAnimationFrame(() => {
|
|
611
|
-
pending = false;
|
|
612
|
-
measure();
|
|
613
|
-
});
|
|
614
|
-
};
|
|
615
|
-
if (delay > 0) timer = window.setTimeout(queueFrame, delay);
|
|
616
|
-
else if (deferUntilIdle && typeof window.requestIdleCallback === "function") idle = window.requestIdleCallback(queueFrame, { timeout: 350 });
|
|
617
|
-
else queueFrame();
|
|
618
|
-
};
|
|
619
|
-
if (typeof window.ResizeObserver === "function") resizeObserver = new window.ResizeObserver(() => schedule());
|
|
620
|
-
// 会话是 SPA 切换:输入框节点会被整体替换,但 effect 本身不会重新挂载。仅在包含发送键的子树增删时重测。
|
|
621
|
-
const mutationObserver = typeof window.MutationObserver === "function" ? new window.MutationObserver((mutations) => {
|
|
622
|
-
const hasComposerChange = mutations.some((mutation) => Array.from(mutation.addedNodes).concat(Array.from(mutation.removedNodes)).some((node) => {
|
|
623
|
-
if (!node || node.nodeType !== 1) return false;
|
|
624
|
-
return (node.matches && node.matches(COMPOSER_SEND_SELECTOR)) || (node.querySelector && node.querySelector(COMPOSER_SEND_SELECTOR));
|
|
625
|
-
}));
|
|
626
|
-
if (hasComposerChange) schedule(40);
|
|
627
|
-
}) : null;
|
|
628
|
-
if (mutationObserver && document.body) mutationObserver.observe(document.body, { childList: true, subtree: true });
|
|
629
|
-
schedule(0, true);
|
|
630
|
-
// 部分会话会复用同一输入框节点、只改变祖先布局;低频校准覆盖这种无 DOM 替换的位移。
|
|
631
|
-
// 校准只在任务活动时才值得跑:空闲期每 700ms 一次全页 querySelectorAll + getBoundingClientRect
|
|
632
|
-
// 是持续掉帧的来源(每次都强制布局)。空闲 20s 后降频到 5s,任务恢复立即回到快节奏。
|
|
633
|
-
let calibrationInterval = 700;
|
|
634
|
-
let lastActivityAt = Date.now();
|
|
635
|
-
const markActivity = () => { lastActivityAt = Date.now(); if (calibrationInterval !== 700) { calibrationInterval = 700; restartPoll(); } };
|
|
636
|
-
const unsubscribeActivity = animationStore.subscribe(markActivity);
|
|
637
|
-
let pollTimer = 0;
|
|
638
|
-
function restartPoll() {
|
|
639
|
-
if (pollTimer) window.clearInterval(pollTimer);
|
|
640
|
-
pollTimer = window.setInterval(() => {
|
|
641
|
-
if (Date.now() - lastActivityAt > 20000 && calibrationInterval !== 5000) calibrationInterval = 5000;
|
|
642
|
-
schedule();
|
|
643
|
-
}, calibrationInterval);
|
|
644
|
-
}
|
|
645
|
-
restartPoll();
|
|
646
|
-
const onResize = () => {
|
|
647
|
-
if (idle && typeof window.cancelIdleCallback === "function") window.cancelIdleCallback(idle);
|
|
648
|
-
if (timer) window.clearTimeout(timer);
|
|
649
|
-
pending = false;
|
|
650
|
-
schedule();
|
|
651
|
-
};
|
|
652
|
-
window.addEventListener("resize", onResize, { passive: true });
|
|
653
|
-
return () => {
|
|
654
|
-
disposed = true;
|
|
655
|
-
window.cancelAnimationFrame(frame);
|
|
656
|
-
if (idle && typeof window.cancelIdleCallback === "function") window.cancelIdleCallback(idle);
|
|
657
|
-
if (timer) window.clearTimeout(timer);
|
|
658
|
-
if (pollTimer) window.clearInterval(pollTimer);
|
|
659
|
-
if (mutationObserver) mutationObserver.disconnect();
|
|
660
|
-
if (resizeObserver) resizeObserver.disconnect();
|
|
661
|
-
unsubscribeActivity();
|
|
662
|
-
window.removeEventListener("resize", onResize);
|
|
663
|
-
};
|
|
664
|
-
}, [enabled]);
|
|
665
|
-
return docks;
|
|
666
|
-
}
|
|
667
|
-
// 三个星系分处内容区右上、左上、左下,路线有足够的飞行距离且避开侧栏和输入框。
|
|
668
|
-
// 返回中心点还会被巡航线路和归航飞船共用,避免“从空气里飞回来”。
|
|
669
|
-
function spaceGalaxyLayout() {
|
|
670
|
-
const vw = typeof window === "undefined" ? 1280 : window.innerWidth;
|
|
671
|
-
const vh = typeof window === "undefined" ? 900 : window.innerHeight;
|
|
672
|
-
const contentLeft = 316;
|
|
673
|
-
const make = (x, y, scale) => ({ x, y, scale, cx: x + 123, cy: y + 123 });
|
|
674
|
-
return {
|
|
675
|
-
alpha: make(Math.max(contentLeft + 420, vw - 360), 72, .50),
|
|
676
|
-
beta: make(contentLeft, Math.max(142, Math.min(232, vh * .18)), .48),
|
|
677
|
-
gamma: make(contentLeft + 18, Math.max(420, vh - 382), .48),
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
function freighterAnchorAt(point) {
|
|
681
|
-
// 飞船外框为 210×88,飞船主体中心约在 (88, 32),对齐到星系中心而不是左上角。
|
|
682
|
-
return { x: Math.round(point.x - 88), y: Math.round(point.y - 32) };
|
|
683
|
-
}
|
|
684
|
-
function flightAngle(from, to) {
|
|
685
|
-
return Math.round(Math.atan2(to.y - from.y, to.x - from.x) * 180 / Math.PI);
|
|
686
|
-
}
|
|
687
|
-
function FreighterHull(props) {
|
|
688
|
-
const units = Math.max(1, Number(props.units) || 1);
|
|
689
|
-
return <span className="dkan-freighter">
|
|
690
|
-
<i className="dkan-freighter-engine"><i /><i /></i>
|
|
691
|
-
<i className="dkan-freighter-wing left" /><i className="dkan-freighter-wing right" /><i className="dkan-freighter-fin" />
|
|
692
|
-
<i className="dkan-freighter-hull" /><i className="dkan-freighter-canopy" /><i className="dkan-freighter-nose" />
|
|
693
|
-
<span className={"dkan-freighter-cargo" + (props.empty ? " empty" : "")}>{Array.from({ length: units }, (_, i) => <i key={i} />)}</span>
|
|
694
|
-
</span>;
|
|
695
|
-
}
|
|
696
|
-
function DeliveryFreighter(props) {
|
|
697
|
-
const d = props.delivery;
|
|
698
|
-
return (
|
|
699
|
-
<div className="dkan-delivery" style={{
|
|
700
|
-
"--dkan-from-x": d.from.x + "px", "--dkan-from-y": d.from.y + "px",
|
|
701
|
-
"--dkan-to-x": d.to.x + "px", "--dkan-to-y": d.to.y + "px",
|
|
702
|
-
"--dkan-delivery-duration": d.duration + "ms",
|
|
703
|
-
}} aria-hidden="true">
|
|
704
|
-
<span className="dkan-delivery-trail" />
|
|
705
|
-
<FreighterHull units={d.units} />
|
|
706
|
-
<span className="dkan-delivery-label"><b>归航货运舰</b><span>输出 {fmtCompact(d.tokens)} Tokens · {d.units} 舱</span></span>
|
|
707
|
-
</div>
|
|
708
|
-
);
|
|
709
|
-
}
|
|
710
|
-
function ReadyFreighter(props) {
|
|
711
|
-
const dock = props.dock || fallbackComposerDockPoint("ready");
|
|
712
|
-
return <div className="dkan-ready-freighter" style={{ left: dock.x, top: dock.y }} aria-hidden="true"><FreighterHull units={3} empty /><span>待命 · 等待下一条任务</span></div>;
|
|
713
|
-
}
|
|
714
|
-
function MissionDeparture(props) {
|
|
715
|
-
const d = props.departure;
|
|
716
|
-
return <div className="dkan-departure" style={{
|
|
717
|
-
"--dkan-from-x": d.from.x + "px", "--dkan-from-y": d.from.y + "px",
|
|
718
|
-
"--dkan-to-x": d.to.x + "px", "--dkan-to-y": d.to.y + "px",
|
|
719
|
-
"--dkan-flight-angle": d.angle + "deg", "--dkan-departure-duration": d.duration + "ms",
|
|
720
|
-
}} aria-hidden="true"><FreighterHull units={3} empty /></div>;
|
|
721
|
-
}
|
|
722
|
-
function ProgrammingStuck(props) {
|
|
723
|
-
const waiting = !!props.waiting;
|
|
724
|
-
return <div className={"dkan-space-stuck" + (waiting ? " waiting" : "")} aria-hidden="true"><FreighterHull units={3} empty /><span className="dkan-stuck-code"><i />{`{ }`}</span><strong>{waiting ? "等待确认" : "编程等待中"}</strong><small>{truncate((props.task && props.task.title) || "工具暂无新响应", 28)}</small></div>;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// ---------- 全局浮层:轮询 + 动效(功能启用即常驻;通知在 features/notify/) ----------
|
|
728
|
-
export function AnimationOverlay(props) {
|
|
729
|
-
const ctx = props && props.ctx;
|
|
730
|
-
const snap = useAnimation();
|
|
731
|
-
const [flourish, setFlourish] = useState(null); // { key, err } 完成瞬间的一次性流光
|
|
732
|
-
const [bursts, setBursts] = useState([]); // 交互反馈动画队列
|
|
733
|
-
const [deliveries, setDeliveries] = useState([]); // 星际模式完成时停泊在输入框上方的归航货运舰
|
|
734
|
-
const [departures, setDepartures] = useState([]); // 从输入框起飞的出航舰
|
|
735
|
-
const prevActiveRef = useRef(null);
|
|
736
|
-
const burstIdRef = useRef(0);
|
|
737
|
-
const deliveryIdRef = useRef(0);
|
|
738
|
-
const departureTimersRef = useRef(new Map());
|
|
739
|
-
const lastImmediateDepartureRef = useRef(0);
|
|
740
|
-
|
|
741
|
-
useEffect(() => () => {
|
|
742
|
-
for (const timer of departureTimersRef.current.values()) clearTimeout(timer);
|
|
743
|
-
departureTimersRef.current.clear();
|
|
744
|
-
}, []);
|
|
745
|
-
|
|
746
|
-
// 触发一个交互反馈动画(自动清理)
|
|
747
|
-
const pushBurst = useCallback((type, x, y, bits) => {
|
|
748
|
-
const id = ++burstIdRef.current;
|
|
749
|
-
setBursts((prev) => prev.concat([{ id, type, x, y, bits }]).slice(-6));
|
|
750
|
-
setTimeout(() => setBursts((prev) => prev.filter((b) => b.id !== id)), 2600);
|
|
751
|
-
}, []);
|
|
752
|
-
// 发送即出发:从左侧归航位先缓慢滑行,再逐段加速,最终并入右上角星系。
|
|
753
|
-
const launchMission = useCallback(() => {
|
|
754
|
-
const cfg = animationStore.snap.status && animationStore.snap.status.config;
|
|
755
|
-
if (!cfg || !cfg.animationEnabled || cfg.effectMode !== "space") return false;
|
|
756
|
-
const id = ++deliveryIdRef.current;
|
|
757
|
-
const from = composerDockPoint("left");
|
|
758
|
-
const targetGalaxy = spaceGalaxyLayout().alpha;
|
|
759
|
-
const to = freighterAnchorAt({ x: targetGalaxy.cx, y: targetGalaxy.cy });
|
|
760
|
-
const departure = {
|
|
761
|
-
id, from, to,
|
|
762
|
-
angle: flightAngle(from, to),
|
|
763
|
-
duration: 8600,
|
|
764
|
-
};
|
|
765
|
-
lastImmediateDepartureRef.current = Date.now();
|
|
766
|
-
setDeliveries([]); // 满载归港舰重新出航前离开停泊位
|
|
767
|
-
setDepartures((prev) => prev.concat([departure]).slice(-3));
|
|
768
|
-
departureTimersRef.current.set(id, setTimeout(() => {
|
|
769
|
-
departureTimersRef.current.delete(id);
|
|
770
|
-
setDepartures((prev) => prev.filter((item) => item.id !== id));
|
|
771
|
-
}, departure.duration));
|
|
772
|
-
return true;
|
|
773
|
-
}, []);
|
|
774
|
-
|
|
775
|
-
// 点击监听:识别用户操作 → 即时反馈(发送/新会话/切会话/切工作目录)
|
|
776
|
-
useEffect(() => {
|
|
777
|
-
if (typeof document === "undefined") return;
|
|
778
|
-
const onClick = (e) => {
|
|
779
|
-
const t = e.target;
|
|
780
|
-
if (!t || !t.closest) return;
|
|
781
|
-
const sendBtn = t.closest('button[aria-label="Send message"], button[aria-label*="发送"]');
|
|
782
|
-
const newSession = t.closest('button[aria-label*="New session"], button[aria-label*="新会话"], button[aria-label*="New Session"]');
|
|
783
|
-
const workspace = t.closest('button[aria-label*="Choose workspace"], button[aria-label*="工作目录"], button[aria-label*="workspace"]');
|
|
784
|
-
const sessionItem = t.closest('[role="treeitem"]');
|
|
785
|
-
if (sendBtn) {
|
|
786
|
-
pushBurst("plane", e.clientX, e.clientY);
|
|
787
|
-
launchMission();
|
|
788
|
-
} else if (newSession) {
|
|
789
|
-
pushBurst("spark", e.clientX, e.clientY, makeBits(10, () => ({
|
|
790
|
-
dx: (Math.random() - 0.5) * 70, dy: (Math.random() - 0.5) * 70,
|
|
791
|
-
c: ["#2563eb", "#0d9488", "#b45309", "#be185d"][Math.floor(Math.random() * 4)],
|
|
792
|
-
})));
|
|
793
|
-
} else if (workspace) {
|
|
794
|
-
pushBurst("flash");
|
|
795
|
-
} else if (sessionItem) {
|
|
796
|
-
pushBurst("streak");
|
|
797
|
-
}
|
|
798
|
-
};
|
|
799
|
-
document.addEventListener("click", onClick, true);
|
|
800
|
-
return () => document.removeEventListener("click", onClick, true);
|
|
801
|
-
}, [pushBurst, launchMission]);
|
|
802
|
-
|
|
803
|
-
// 任务状态变化:没有直接发送事件时,仍由状态变化兜底从左侧起飞。
|
|
804
|
-
const taskCountRef = useRef(0);
|
|
805
|
-
useEffect(() => {
|
|
806
|
-
const st = snap.status;
|
|
807
|
-
if (!st || !st.active) return;
|
|
808
|
-
const n = st.active.length;
|
|
809
|
-
if (n > taskCountRef.current) {
|
|
810
|
-
pushBurst("surge");
|
|
811
|
-
const cfg = st.config || {};
|
|
812
|
-
if (cfg.animationEnabled && cfg.effectMode === "space" && Date.now() - lastImmediateDepartureRef.current > 10000) launchMission();
|
|
813
|
-
}
|
|
814
|
-
taskCountRef.current = n;
|
|
815
|
-
}, [snap.status, pushBurst, launchMission]);
|
|
816
|
-
|
|
817
|
-
// 轮询:任务中 850ms,实时捕捉 chunk/工具活动;空闲 6s / 出错 15s;页面切回立即刷新。
|
|
818
|
-
useEffect(() => {
|
|
819
|
-
let stopped = false;
|
|
820
|
-
let timer = null;
|
|
821
|
-
const loop = async () => {
|
|
822
|
-
await animationStore.refresh();
|
|
823
|
-
if (stopped) return;
|
|
824
|
-
const s = animationStore.snap;
|
|
825
|
-
timer = setTimeout(loop, s.error ? 15000 : (s.status && s.status.active && s.status.active.length > 0 ? 850 : 6000));
|
|
826
|
-
};
|
|
827
|
-
loop();
|
|
828
|
-
const onVisible = () => {
|
|
829
|
-
if (!stopped && typeof document !== "undefined" && document.visibilityState === "visible") animationStore.refresh();
|
|
830
|
-
};
|
|
831
|
-
document.addEventListener("visibilitychange", onVisible);
|
|
832
|
-
return () => { stopped = true; if (timer) clearTimeout(timer); document.removeEventListener("visibilitychange", onVisible); };
|
|
833
|
-
}, []);
|
|
834
|
-
|
|
835
|
-
// 任务结束检测:上一轮活跃、本轮消失 → 完成瞬间的流光(找最近完成记录补全信息)
|
|
836
|
-
useEffect(() => {
|
|
837
|
-
const st = snap.status;
|
|
838
|
-
if (!st || !st.active) return;
|
|
839
|
-
const prev = prevActiveRef.current;
|
|
840
|
-
if (prev) {
|
|
841
|
-
const currIds = new Set(st.active.map((x) => x.sessionId));
|
|
842
|
-
for (const task of prev) {
|
|
843
|
-
if (currIds.has(task.sessionId)) continue;
|
|
844
|
-
const record = (st.recent || []).find((r) => r.sessionId === task.sessionId) || null;
|
|
845
|
-
handleTaskEnd(task, record, st.config || {});
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
prevActiveRef.current = st.active.slice();
|
|
849
|
-
}, [snap.status]);
|
|
850
|
-
|
|
851
|
-
const handleTaskEnd = (task, record, cfg) => {
|
|
852
|
-
if (!cfg.animationEnabled) return;
|
|
853
|
-
const success = isSuccessReason((record && record.endReason) || "");
|
|
854
|
-
setFlourish({ key: Date.now(), err: !success });
|
|
855
|
-
// 星际远征把完成结果送回用户正在输入的位置。速度继承最后一帧任务节奏;
|
|
856
|
-
// 货舱数量及标签只取 Host 已记录的真实 outputTokens。
|
|
857
|
-
if (cfg.effectMode === "space") {
|
|
858
|
-
const cargo = outputCargo((record && record.outputTokens) || task.outputTokens);
|
|
859
|
-
const id = ++deliveryIdRef.current;
|
|
860
|
-
const taskSpeed = Math.max(.9, Number(speed) || 1);
|
|
861
|
-
const duration = Math.round(Math.max(8000, Math.min(9400, 10000 / Math.sqrt(taskSpeed))));
|
|
862
|
-
// 从三个星系中随机挑选归航起点,任务完成时才能知道本次货舰从哪里回来。
|
|
863
|
-
const galaxies = spaceGalaxyLayout();
|
|
864
|
-
const returnGalaxy = galaxies[["alpha", "beta", "gamma"][Math.floor(Math.random() * 3)]];
|
|
865
|
-
const from = freighterAnchorAt({ x: returnGalaxy.cx, y: returnGalaxy.cy });
|
|
866
|
-
const to = composerDockPoint("left");
|
|
867
|
-
// 满载舰最终停入左侧归航位,和右上出发星系形成清晰的往返方向。
|
|
868
|
-
const delivery = Object.assign({ id, from, to, duration }, cargo);
|
|
869
|
-
setDeliveries([delivery]);
|
|
870
|
-
}
|
|
871
|
-
// 成功时顶部彩带庆祝(通知卡片由【任务通知】模块负责,与本模块无关)
|
|
872
|
-
if (success) {
|
|
873
|
-
pushBurst("confetti", 0, 0, makeBits(18, (i) => ({
|
|
874
|
-
l: 4 + (i / 18) * 92 + Math.random() * 3,
|
|
875
|
-
dx: (Math.random() - 0.5) * 60,
|
|
876
|
-
r: Math.random() * 720 - 360,
|
|
877
|
-
d: Math.random() * 0.35,
|
|
878
|
-
c: ["#2563eb", "#0d9488", "#b45309", "#be185d", "#7c3aed"][i % 5],
|
|
879
|
-
})));
|
|
880
|
-
}
|
|
881
|
-
};
|
|
882
|
-
|
|
883
|
-
const st = snap.status;
|
|
884
|
-
const cfg = st && st.config;
|
|
885
|
-
const active = st && st.active ? st.active : [];
|
|
886
|
-
const animOn = !!(cfg && cfg.animationEnabled && active.length > 0);
|
|
887
|
-
const mode = cfg && cfg.effectMode ? cfg.effectMode : "flow";
|
|
888
|
-
// 机器人阶段:取最近发生阶段变化的活跃任务。
|
|
889
|
-
const phase = active.length > 0
|
|
890
|
-
? active.reduce((best, x) => (!best || (x.phaseAt || 0) > (best.phaseAt || 0) ? x : best), null).phase
|
|
891
|
-
: "think";
|
|
892
|
-
// 有会话在等用户确认时,状态文字让位给"等待确认"(优先级最高:不动手就卡住)。
|
|
893
|
-
// 会话级卡片(等待确认/编程等待中)只对"当前正在看的会话"出现,别的会话等待不打扰。
|
|
894
|
-
const waitingTask = active.find((x) => Array.isArray(x.approvals) && x.approvals.length > 0) || null;
|
|
895
|
-
const sessionTitleRef = useRef({ at: 0, title: "" });
|
|
896
|
-
const currentTitle = useMemo(() => {
|
|
897
|
-
// 标题查询要 getBoundingClientRect(强制布局),限频 1s;面板页/任务行为不受此限制
|
|
898
|
-
const c = sessionTitleRef.current;
|
|
899
|
-
if (typeof window === "undefined") return "";
|
|
900
|
-
if (Date.now() - c.at < 1000) return c.title;
|
|
901
|
-
c.at = Date.now();
|
|
902
|
-
c.title = currentSessionTitle();
|
|
903
|
-
return c.title;
|
|
904
|
-
}, [snap.status]);
|
|
905
|
-
const onScreenTasks = useMemo(() => active.filter((x) => taskOnScreen(x, currentTitle)), [active, currentTitle]);
|
|
906
|
-
const waitingTaskOnScreen = onScreenTasks.find((x) => Array.isArray(x.approvals) && x.approvals.length > 0) || null;
|
|
907
|
-
useTicker(ctx, animOn);
|
|
908
|
-
// 徽标计时:以最早开始的任务为基准
|
|
909
|
-
const now = Date.now();
|
|
910
|
-
const elapsed = active.length > 0 ? fmtClock(now - Math.min(...active.map((x) => x.startTime || now))) : "";
|
|
911
|
-
// 面板打开时隐藏环境动效(徽标/细线/巡航/机器人),避免与弹窗重叠错位
|
|
912
|
-
const [panelOpen, setPanelOpen] = useState(false);
|
|
913
|
-
useEffect(() => {
|
|
914
|
-
setPanelOpen(panelNav.open);
|
|
915
|
-
return subscribePanel(() => setPanelOpen(panelNav.open));
|
|
916
|
-
}, []);
|
|
917
|
-
// 任务速度:优先使用 host 从 chunk/tool 实时累积的活动脉冲;
|
|
918
|
-
// token usage 只在完整消息后才到达,不能再作为唯一速度来源。
|
|
919
|
-
const [speed, setSpeed] = useState(1);
|
|
920
|
-
const speedRef = useRef({ ticks: -1, at: 0 });
|
|
921
|
-
useEffect(() => {
|
|
922
|
-
if (!st || active.length === 0) { speedRef.current = { ticks: -1, at: 0 }; setSpeed(1); return; }
|
|
923
|
-
const ticks = active.reduce((a, x) => a + (x.motionTicks || 0), 0);
|
|
924
|
-
const t = Date.now();
|
|
925
|
-
const prev = speedRef.current;
|
|
926
|
-
if (prev.ticks < 0 || t - prev.at < 300) { speedRef.current = { ticks, at: t }; return; }
|
|
927
|
-
const rate = Math.max(0, (ticks - prev.ticks) / ((t - prev.at) / 1000));
|
|
928
|
-
const phaseBoost = phase === "write" || phase === "code" ? .28 : (phase === "search" ? .18 : 0);
|
|
929
|
-
const next = Math.max(.9, Math.min(4, Number((.9 + rate / 14 + phaseBoost).toFixed(2))));
|
|
930
|
-
setSpeed((current) => Math.abs(current - next) < .03 ? current : next);
|
|
931
|
-
speedRef.current = { ticks, at: t };
|
|
932
|
-
}, [snap.status]);
|
|
933
|
-
const ambientOn = animOn && !panelOpen;
|
|
934
|
-
const spaceModeOn = !!(cfg && cfg.animationEnabled && mode === "space" && !panelOpen);
|
|
935
|
-
const spaceDocks = useSpaceDocks(spaceModeOn);
|
|
936
|
-
const spaceGalaxies = spaceModeOn && spaceDocks ? spaceGalaxyLayout() : null;
|
|
937
|
-
// Host 在编程阶段持续 18 秒没有 chunk/tool 活动,才把它视为卡住;正常长任务不会误触发。
|
|
938
|
-
// 卡住卡片同样只对当前会话出现。
|
|
939
|
-
const stuckTask = onScreenTasks.find((task) => task.phase === "code" && now - (task.lastActivityAt || task.phaseAt || now) >= 18000) || null;
|
|
940
|
-
const robotScaleFromConfig = Math.max(.85, Math.min(2.2, Number(cfg && cfg.robotScale) || 1.35));
|
|
941
|
-
const speedStyle = { "--dkan-speed": Number(speed).toFixed(2) };
|
|
942
|
-
const saveRobotScale = useCallback((robotScale) => {
|
|
943
|
-
rpcCall("config", { robotScale })
|
|
944
|
-
.then((d) => animationStore.applyConfig(d && d.config))
|
|
945
|
-
.catch(() => { /* 浮层保存失败不打断任务动画;面板页会显示可操作的错误 */ });
|
|
946
|
-
}, []);
|
|
947
|
-
|
|
948
|
-
// ===== 机器人卡片自由拖拽:整卡可拖到屏幕任意位置(localStorage 持久化),防止固定遮挡 =====
|
|
949
|
-
const [botPos, setBotPos] = useState(null); // null = 停泊默认位(右下、输入卡上方)
|
|
950
|
-
const botPosRef = useRef(null);
|
|
951
|
-
const botDragRef = useRef(null); // { sx, sy, ox, oy, moved }
|
|
952
|
-
const botResizeRef = useRef(null); // { sx, sy, scale }
|
|
953
|
-
const [botScaleDraft, setBotScaleDraft] = useState(null);
|
|
954
|
-
const botClickBlockRef = useRef(false);
|
|
955
|
-
useEffect(() => {
|
|
956
|
-
try {
|
|
957
|
-
if (typeof localStorage === "undefined") return;
|
|
958
|
-
const raw = localStorage.getItem("dsh-dock/anim/robot-pos/v1");
|
|
959
|
-
const p = raw ? JSON.parse(raw) : null;
|
|
960
|
-
if (p && typeof p.x === "number" && typeof p.y === "number" && typeof window !== "undefined") {
|
|
961
|
-
// 恢复时钳制回视口内(窗口可能变小了)
|
|
962
|
-
const card = document.querySelector(".dkan-botcard");
|
|
963
|
-
const w = card ? card.offsetWidth : 200, h = card ? card.offsetHeight : 160;
|
|
964
|
-
const x = Math.min(Math.max(8, p.x), Math.max(8, window.innerWidth - w - 8));
|
|
965
|
-
const y = Math.min(Math.max(8, p.y), Math.max(8, window.innerHeight - h - 8));
|
|
966
|
-
botPosRef.current = { x, y };
|
|
967
|
-
setBotPos({ x, y });
|
|
968
|
-
}
|
|
969
|
-
} catch { /* localStorage 不可用时停默认位 */ }
|
|
970
|
-
}, []);
|
|
971
|
-
const onBotPointerDown = (e) => {
|
|
972
|
-
if (e.button !== 0) return;
|
|
973
|
-
const r = e.currentTarget.getBoundingClientRect();
|
|
974
|
-
botDragRef.current = { sx: e.clientX, sy: e.clientY, ox: r.left, oy: r.top, moved: false };
|
|
975
|
-
try { e.currentTarget.setPointerCapture(e.pointerId); } catch { /* 无指针捕获也能拖 */ }
|
|
976
|
-
};
|
|
977
|
-
const onBotPointerMove = (e) => {
|
|
978
|
-
const d = botDragRef.current;
|
|
979
|
-
if (!d || typeof window === "undefined") return;
|
|
980
|
-
const dx = e.clientX - d.sx, dy = e.clientY - d.sy;
|
|
981
|
-
if (!d.moved && Math.hypot(dx, dy) < 4) return;
|
|
982
|
-
d.moved = true;
|
|
983
|
-
const el = e.currentTarget;
|
|
984
|
-
const w = el.offsetWidth || 200, h = el.offsetHeight || 160;
|
|
985
|
-
const x = Math.min(Math.max(8, d.ox + dx), window.innerWidth - w - 8);
|
|
986
|
-
const y = Math.min(Math.max(8, d.oy + dy), window.innerHeight - h - 8);
|
|
987
|
-
botPosRef.current = { x, y };
|
|
988
|
-
setBotPos({ x, y });
|
|
989
|
-
};
|
|
990
|
-
const onBotPointerUp = () => {
|
|
991
|
-
const d = botDragRef.current;
|
|
992
|
-
botDragRef.current = null;
|
|
993
|
-
if (d && d.moved) {
|
|
994
|
-
try { if (typeof localStorage !== "undefined") localStorage.setItem("dsh-dock/anim/robot-pos/v1", JSON.stringify(botPosRef.current)); } catch { /* 持久化失败静默 */ }
|
|
995
|
-
botClickBlockRef.current = true; // 拖动结束的 pointerup 不触发面板跳转
|
|
996
|
-
setTimeout(() => { botClickBlockRef.current = false; }, 0);
|
|
997
|
-
}
|
|
998
|
-
};
|
|
999
|
-
const onBotResizePointerDown = (e) => {
|
|
1000
|
-
if (e.button !== 0) return;
|
|
1001
|
-
e.preventDefault();
|
|
1002
|
-
e.stopPropagation();
|
|
1003
|
-
botResizeRef.current = { sx: e.clientX, sy: e.clientY, scale: botScaleDraft == null ? robotScaleFromConfig : botScaleDraft };
|
|
1004
|
-
try { e.currentTarget.setPointerCapture(e.pointerId); } catch { /* 无指针捕获也能缩放 */ }
|
|
1005
|
-
};
|
|
1006
|
-
const onBotResizePointerMove = (e) => {
|
|
1007
|
-
const d = botResizeRef.current;
|
|
1008
|
-
if (!d) return;
|
|
1009
|
-
e.preventDefault();
|
|
1010
|
-
e.stopPropagation();
|
|
1011
|
-
// 保持场景比例;以右下角手柄向外拖动放大,向内拖动缩小。
|
|
1012
|
-
const delta = ((e.clientX - d.sx) + (e.clientY - d.sy)) / 2;
|
|
1013
|
-
const next = Math.max(.85, Math.min(2.2, Math.round((d.scale + delta / 220) * 100) / 100));
|
|
1014
|
-
setBotScaleDraft(next);
|
|
1015
|
-
// 已拖到自定义位置时,放大也始终留在可视区域内。
|
|
1016
|
-
if (botPosRef.current && typeof window !== "undefined") {
|
|
1017
|
-
const w = 220 * next + 24, h = 132 * next + 46;
|
|
1018
|
-
const x = Math.min(Math.max(8, botPosRef.current.x), Math.max(8, window.innerWidth - w - 8));
|
|
1019
|
-
const y = Math.min(Math.max(8, botPosRef.current.y), Math.max(8, window.innerHeight - h - 8));
|
|
1020
|
-
botPosRef.current = { x, y };
|
|
1021
|
-
setBotPos({ x, y });
|
|
1022
|
-
}
|
|
1023
|
-
};
|
|
1024
|
-
const onBotResizePointerUp = (e) => {
|
|
1025
|
-
if (!botResizeRef.current) return;
|
|
1026
|
-
e.preventDefault();
|
|
1027
|
-
e.stopPropagation();
|
|
1028
|
-
const next = botScaleDraft == null ? robotScaleFromConfig : botScaleDraft;
|
|
1029
|
-
botResizeRef.current = null;
|
|
1030
|
-
saveRobotScale(next);
|
|
1031
|
-
try { if (botPosRef.current && typeof localStorage !== "undefined") localStorage.setItem("dsh-dock/anim/robot-pos/v1", JSON.stringify(botPosRef.current)); } catch { /* 位置保存失败不影响缩放 */ }
|
|
1032
|
-
setBotScaleDraft(null);
|
|
1033
|
-
};
|
|
1034
|
-
const onBotClick = () => {
|
|
1035
|
-
if (botClickBlockRef.current) return;
|
|
1036
|
-
openPanel("animation");
|
|
1037
|
-
};
|
|
1038
|
-
const displayRobotScale = botScaleDraft == null ? robotScaleFromConfig : botScaleDraft;
|
|
1039
|
-
const botCardStyle = botPos
|
|
1040
|
-
? Object.assign({}, speedStyle, { "--dkan-bot-scale": displayRobotScale, left: botPos.x, top: botPos.y, right: "auto", bottom: "auto" })
|
|
1041
|
-
: Object.assign({}, speedStyle, { "--dkan-bot-scale": displayRobotScale });
|
|
1042
|
-
|
|
1043
|
-
// Host 不可用(旧宿主未重启等):浮层整体静默,面板页会给提示
|
|
1044
|
-
if (!st) return null;
|
|
1045
|
-
return (<>
|
|
1046
|
-
{/* 顶部流光细线(flow 模式 / 任务进行中) */}
|
|
1047
|
-
{ambientOn && mode === "flow" ? <div className="dkan-line" style={speedStyle} aria-hidden="true" /> : null}
|
|
1048
|
-
{/* 氛围动效(代码雨/星野/极光/星际远征,任务进行中渲染,速度随吞吐) */}
|
|
1049
|
-
{ambientOn && AMBIENT_EFFECT_MODES.has(mode) && (mode !== "space" || spaceGalaxies)
|
|
1050
|
-
? <AmbientLayer mode={mode} phase={phase} speed={Number(speed).toFixed(2)} tasks={active} galaxies={spaceGalaxies} /> : null}
|
|
1051
|
-
{/* 星际模式:空闲时在输入框上方待命;任务卡住时在中央呈现编程等待状态。 */}
|
|
1052
|
-
{spaceModeOn && spaceDocks && active.length === 0 && deliveries.length === 0 ? <ReadyFreighter dock={spaceDocks.ready} /> : null}
|
|
1053
|
-
{spaceModeOn && stuckTask ? <ProgrammingStuck task={stuckTask} waiting={!!(stuckTask.approvals && stuckTask.approvals.length)} /> : null}
|
|
1054
|
-
{!panelOpen ? departures.map((departure) => <MissionDeparture key={departure.id} departure={departure} />) : null}
|
|
1055
|
-
{/* 环屏巡航:一颗光点沿屏幕边缘转圈(orbit 模式) */}
|
|
1056
|
-
{ambientOn && mode === "orbit" ? <div className="dkan-orbit" style={speedStyle} aria-hidden="true" /> : null}
|
|
1057
|
-
{/* 交互反馈层:纸飞机/火花/流光/微光/光涌/彩带(与动画开关独立,只要功能启用即回应操作) */}
|
|
1058
|
-
<BurstLayer bursts={bursts} />
|
|
1059
|
-
{/* 桌面伙伴:背侧视角紧凑双工位人物,阶段与任务同步;整卡可拖到任意位置(robot 模式) */}
|
|
1060
|
-
{ambientOn && mode === "robot" ? (
|
|
1061
|
-
<div className="dkan-botcard" role="button" tabIndex={0} style={Object.assign({}, botCardStyle, waitingTask ? { "--dkan-phase": "#b45309" } : null)}
|
|
1062
|
-
title={active.length + " 个任务 · " + (waitingTask ? "等待确认" : phaseLabel(phase)) + " · 点击查看任务动画页 · 可拖动到任意位置"}
|
|
1063
|
-
onPointerDown={onBotPointerDown}
|
|
1064
|
-
onPointerMove={onBotPointerMove}
|
|
1065
|
-
onPointerUp={onBotPointerUp}
|
|
1066
|
-
onPointerCancel={onBotPointerUp}
|
|
1067
|
-
onClick={onBotClick}
|
|
1068
|
-
onKeyDown={(e) => { if (e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) { e.preventDefault(); onBotClick(); } }}>
|
|
1069
|
-
<RobotScene phase={phase} tasks={active} />
|
|
1070
|
-
<span className="dkan-bot-cap">
|
|
1071
|
-
<span className="n">{active.length}</span> 个任务{elapsed ? " · " + elapsed : ""} · {waitingTask ? "等待确认" : phaseLabel(phase)}
|
|
1072
|
-
</span>
|
|
1073
|
-
<button type="button" className="dkan-bot-resize" aria-label="调整桌面伙伴大小"
|
|
1074
|
-
title="拖动调整桌面伙伴大小"
|
|
1075
|
-
onPointerDown={onBotResizePointerDown}
|
|
1076
|
-
onPointerMove={onBotResizePointerMove}
|
|
1077
|
-
onPointerUp={onBotResizePointerUp}
|
|
1078
|
-
onPointerCancel={onBotResizePointerUp}
|
|
1079
|
-
onClick={(e) => e.stopPropagation()}
|
|
1080
|
-
onKeyDown={(e) => {
|
|
1081
|
-
if (e.key !== "ArrowUp" && e.key !== "ArrowRight" && e.key !== "ArrowDown" && e.key !== "ArrowLeft") return;
|
|
1082
|
-
e.preventDefault(); e.stopPropagation();
|
|
1083
|
-
const d = e.key === "ArrowUp" || e.key === "ArrowRight" ? .05 : -.05;
|
|
1084
|
-
saveRobotScale(Math.max(.85, Math.min(2.2, Math.round((displayRobotScale + d) * 100) / 100)));
|
|
1085
|
-
}} />
|
|
1086
|
-
</div>
|
|
1087
|
-
) : null}
|
|
1088
|
-
{/* 右下角状态徽标:dot 随模式变化(breathe/ring 装饰),颜色随任务阶段、呼吸随吞吐;点击进功能坞动画页 */}
|
|
1089
|
-
{ambientOn && mode !== "robot" ? (
|
|
1090
|
-
<button type="button" className={"dkan-badge" + (mode === "breathe" ? " dkan-badge-breathe" : "")}
|
|
1091
|
-
style={Object.assign({}, speedStyle, { "--dkan-phase": phaseColor(waitingTask ? "code" : phase) })}
|
|
1092
|
-
title={active.length + " 个任务进行中 · " + (waitingTask ? "等待确认" : phaseLabel(phase)) + " · 点击查看任务动画页"}
|
|
1093
|
-
onClick={() => openPanel("animation")}>
|
|
1094
|
-
<span className="dkan-dotwrap">
|
|
1095
|
-
<span className="dkan-dot" />
|
|
1096
|
-
{mode === "breathe" ? <span className="dkan-halo" /> : null}
|
|
1097
|
-
{mode === "ring" ? <span className="dkan-ring" /> : null}
|
|
1098
|
-
</span>
|
|
1099
|
-
<span className="dkan-badge-txt"><span className="n">{active.length}</span> 个任务{elapsed ? " · " + elapsed : ""} · {waitingTask ? "等待确认" : phaseLabel(phase)}</span>
|
|
1100
|
-
</button>
|
|
1101
|
-
) : null}
|
|
1102
|
-
{/* 完成瞬间的一次性流光(成功绿 / 异常红),动画结束自动清场 */}
|
|
1103
|
-
{flourish ? (
|
|
1104
|
-
<div key={flourish.key} className={"dkan-done" + (flourish.err ? " err" : "")}
|
|
1105
|
-
onAnimationEnd={() => setFlourish(null)} aria-hidden="true" />
|
|
1106
|
-
) : null}
|
|
1107
|
-
{/* 星际任务完成:货运舰沿航线飞到输入框上方并保持停泊,直到下一次任务出航。 */}
|
|
1108
|
-
{!panelOpen ? deliveries.map((delivery) => <DeliveryFreighter key={delivery.id} delivery={delivery} />) : null}
|
|
1109
|
-
</>);
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
// ---------- 面板页 ----------
|
|
1113
|
-
function ModePreview({ id }) {
|
|
1114
|
-
if (id === "flow") return <span className="dkan-prev"><span className="dkan-prev-flow" /></span>;
|
|
1115
|
-
if (id === "ring") {
|
|
1116
|
-
return (
|
|
1117
|
-
<span className="dkan-prev">
|
|
1118
|
-
<span className="dkan-dotwrap"><span className="dkan-dot" /><span className="dkan-ring" /></span>
|
|
1119
|
-
</span>
|
|
1120
|
-
);
|
|
1121
|
-
}
|
|
1122
|
-
if (id === "orbit") {
|
|
1123
|
-
return (
|
|
1124
|
-
<span className="dkan-prev dkan-prev-box">
|
|
1125
|
-
<span className="dkan-prev-orbit" />
|
|
1126
|
-
</span>
|
|
1127
|
-
);
|
|
1128
|
-
}
|
|
1129
|
-
if (id === "matrix") {
|
|
1130
|
-
return (
|
|
1131
|
-
<span className="dkan-prev dkan-prev-matrix">
|
|
1132
|
-
<i /><i /><i /><i /><i />
|
|
1133
|
-
</span>
|
|
1134
|
-
);
|
|
1135
|
-
}
|
|
1136
|
-
if (id === "stars") {
|
|
1137
|
-
return (
|
|
1138
|
-
<span className="dkan-prev dkan-prev-stars">
|
|
1139
|
-
<i /><i /><i /><i /><i /><i />
|
|
1140
|
-
</span>
|
|
1141
|
-
);
|
|
1142
|
-
}
|
|
1143
|
-
if (id === "space") {
|
|
1144
|
-
return <span className="dkan-prev dkan-prev-space"><i /><b /><em /></span>;
|
|
1145
|
-
}
|
|
1146
|
-
if (id === "aurora") {
|
|
1147
|
-
return (
|
|
1148
|
-
<span className="dkan-prev dkan-prev-aurora">
|
|
1149
|
-
<i /><i />
|
|
1150
|
-
</span>
|
|
1151
|
-
);
|
|
1152
|
-
}
|
|
1153
|
-
if (["nebula", "warp", "radar", "constellation", "fireflies", "ocean", "prism", "circuit", "gravity", "lantern"].includes(id)) {
|
|
1154
|
-
return <span className={"dkan-prev dkan-prev-new dkan-prev-" + id}>{Array.from({ length: id === "warp" ? 8 : 6 }, (_, i) => <i key={i} />)}</span>;
|
|
1155
|
-
}
|
|
1156
|
-
if (id === "robot") {
|
|
1157
|
-
return (
|
|
1158
|
-
<span className="dkan-prev dkan-prev-bot">
|
|
1159
|
-
<RobotScene phase="code" />
|
|
1160
|
-
</span>
|
|
1161
|
-
);
|
|
1162
|
-
}
|
|
1163
|
-
return (
|
|
1164
|
-
<span className="dkan-prev">
|
|
1165
|
-
<span className="dkan-dotwrap dkan-breathe"><span className="dkan-dot" /><span className="dkan-halo" /></span>
|
|
1166
|
-
</span>
|
|
1167
|
-
);
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
function AnimationView(props) {
|
|
1171
|
-
const ctx = props && props.ctx;
|
|
1172
|
-
const snap = useAnimation(ctx);
|
|
1173
|
-
const [cfg, setCfg] = useState(null); // null = 尚未加载
|
|
1174
|
-
const [saveErr, setSaveErr] = useState("");
|
|
1175
|
-
const cfgRef = useRef(null);
|
|
1176
|
-
const pendingSavesRef = useRef(0); // 进行中的保存(轮询回包不覆盖乐观值)
|
|
1177
|
-
// 拉到新配置(含保存回包)后同步本地编辑态;保存中不覆盖
|
|
1178
|
-
useEffect(() => {
|
|
1179
|
-
const c = snap.status && snap.status.config;
|
|
1180
|
-
if (!c) return;
|
|
1181
|
-
if (pendingSavesRef.current > 0) return;
|
|
1182
|
-
if (c !== cfgRef.current) {
|
|
1183
|
-
cfgRef.current = c;
|
|
1184
|
-
setCfg(Object.assign({}, c));
|
|
1185
|
-
}
|
|
1186
|
-
}, [snap.status]);
|
|
1187
|
-
// 面板打开时兜底拉一次(浮层通常已在轮询)
|
|
1188
|
-
useEffect(() => {
|
|
1189
|
-
if (!animationStore.snap.status) animationStore.refresh();
|
|
1190
|
-
}, []);
|
|
1191
|
-
// 乐观更新 + 立即持久化(每个开关独立保存)
|
|
1192
|
-
const patch = (p) => {
|
|
1193
|
-
setSaveErr("");
|
|
1194
|
-
setCfg(Object.assign({}, cfg, p));
|
|
1195
|
-
pendingSavesRef.current++;
|
|
1196
|
-
return rpcCall("config", p)
|
|
1197
|
-
.then((d) => {
|
|
1198
|
-
pendingSavesRef.current--;
|
|
1199
|
-
animationStore.applyConfig(d && d.config);
|
|
1200
|
-
})
|
|
1201
|
-
.catch((e) => {
|
|
1202
|
-
pendingSavesRef.current--;
|
|
1203
|
-
setSaveErr("保存失败:" + ((e && e.message) || String(e)));
|
|
1204
|
-
});
|
|
1205
|
-
};
|
|
1206
|
-
|
|
1207
|
-
const rows = [];
|
|
1208
|
-
if (!cfg) {
|
|
1209
|
-
rows.push(<div key="load" className="dkan-note">{snap.error ? "状态不可用:" + snap.error : (snap.loading ? "正在拉取任务状态…" : "等待任务状态")}</div>);
|
|
1210
|
-
} else {
|
|
1211
|
-
rows.push(
|
|
1212
|
-
<div key="anim" className="dkan-sec">
|
|
1213
|
-
<div className="dkan-sec-head">
|
|
1214
|
-
<span className="dkan-sec-title">运行动画</span>
|
|
1215
|
-
<span className="dkan-sec-sub">任务进行中才出现;克制的动效,暗/亮色自适应</span>
|
|
1216
|
-
<span className="dkan-sec-sw">
|
|
1217
|
-
<span className={"dkan-sec-swlabel" + (cfg.animationEnabled ? " on" : "")}>{cfg.animationEnabled ? "已开启" : "已关闭"}</span>
|
|
1218
|
-
<button type="button" className={"dock-sw" + (cfg.animationEnabled ? " on" : "")}
|
|
1219
|
-
role="switch" aria-checked={cfg.animationEnabled} aria-label="开关运行动画"
|
|
1220
|
-
title={cfg.animationEnabled ? "关闭运行动画" : "开启运行动画"}
|
|
1221
|
-
onClick={() => patch({ animationEnabled: !cfg.animationEnabled })} />
|
|
1222
|
-
</span>
|
|
1223
|
-
</div>
|
|
1224
|
-
{cfg.animationEnabled ? (
|
|
1225
|
-
<>
|
|
1226
|
-
<div className="dkan-modes">
|
|
1227
|
-
{EFFECT_MODES.map((m) => (
|
|
1228
|
-
<button type="button" key={m.id}
|
|
1229
|
-
className={"dkan-mode" + (cfg.effectMode === m.id ? " on" : "")}
|
|
1230
|
-
onClick={() => patch({ effectMode: m.id })}>
|
|
1231
|
-
<span className="dkan-mode-name">{m.name}</span>
|
|
1232
|
-
<ModePreview id={m.id} />
|
|
1233
|
-
<span className="dkan-mode-desc">{m.desc}</span>
|
|
1234
|
-
</button>
|
|
1235
|
-
))}
|
|
1236
|
-
</div>
|
|
1237
|
-
{cfg.effectMode === "robot" ? <div className="dkan-robot-controls">
|
|
1238
|
-
<div className="dkan-robot-controls-head">
|
|
1239
|
-
<span>桌面伙伴大小</span>
|
|
1240
|
-
<strong>{Math.round((Number(cfg.robotScale) || 1.35) * 100)}%</strong>
|
|
1241
|
-
</div>
|
|
1242
|
-
<div className="dkan-robot-size-options" role="group" aria-label="桌面伙伴大小预设">
|
|
1243
|
-
{[[1, "紧凑"], [1.35, "默认"], [1.7, "加大"], [2.1, "特大"]].map(([scale, label]) => (
|
|
1244
|
-
<button type="button" key={scale} className={Math.abs((Number(cfg.robotScale) || 1.35) - scale) < .03 ? "on" : ""}
|
|
1245
|
-
onClick={() => patch({ robotScale: scale })}>{label}</button>
|
|
1246
|
-
))}
|
|
1247
|
-
</div>
|
|
1248
|
-
<div className="dkan-robot-slider-row">
|
|
1249
|
-
<input type="range" min="0.85" max="2.2" step="0.05" value={Number(cfg.robotScale) || 1.35}
|
|
1250
|
-
aria-label="桌面伙伴大小百分比"
|
|
1251
|
-
onChange={(e) => patch({ robotScale: Number(e.target.value) })} />
|
|
1252
|
-
<span>浮层右下角也可直接拖动缩放,大小会自动保存。</span>
|
|
1253
|
-
</div>
|
|
1254
|
-
</div> : null}
|
|
1255
|
-
</>
|
|
1256
|
-
) : <div className="dkan-note">动画已关闭——任务进行中不会有任何动效(通知设置见左侧「任务通知」)。</div>}
|
|
1257
|
-
</div>
|
|
1258
|
-
);
|
|
1259
|
-
}
|
|
1260
|
-
if (saveErr) rows.push(<div key="err" className="dkan-note dkan-err">{saveErr}</div>);
|
|
1261
|
-
// 运行状态(进行中任务 / 最近完成 / 等待确认)已独立成左侧菜单项,这里只留一句指路
|
|
1262
|
-
rows.push(
|
|
1263
|
-
<div key="hint" className="dkan-note">
|
|
1264
|
-
任务运行状态(进行中任务、阶段、Token、等待确认与最近完成)已独立成左侧「运行状态」菜单;本页只负责动效。
|
|
1265
|
-
</div>
|
|
1266
|
-
);
|
|
1267
|
-
return <div className="dkan-root">{rows}</div>;
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
// ---------- 首页总揽概要 ----------
|
|
1271
|
-
// 任务进行中 / 空闲这类任务信息归【运行状态】模块,这里只报动画自身的状态:
|
|
1272
|
-
// 动效开关 + 当前模式(桌面伙伴另报大小),与面板页开关一一对应。
|
|
1273
|
-
function AnimationStat(props) {
|
|
1274
|
-
const snap = useAnimation(props && props.ctx);
|
|
1275
|
-
const st = snap.status;
|
|
1276
|
-
if (snap.error) return <span className="dkan-err">动画状态不可用(宿主需重启加载动画路由)</span>;
|
|
1277
|
-
if (!st) return <span>等待动画配置…</span>;
|
|
1278
|
-
const cfg = st.config;
|
|
1279
|
-
if (!cfg) return <span>等待动画配置…</span>;
|
|
1280
|
-
if (!cfg.animationEnabled) return <span>动效已关闭 · 任务进行中不显示动画</span>;
|
|
1281
|
-
const mode = EFFECT_MODES.find((m) => m.id === cfg.effectMode) || EFFECT_MODES[0];
|
|
1282
|
-
const scale = Math.round((Number(cfg.robotScale) || 1.35) * 100);
|
|
1283
|
-
return <span>{"动效:" + mode.name + (cfg.effectMode === "robot" ? " · 大小 " + scale + "%" : "")}</span>;
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
// ---------- 样式(dkan- 前缀;全部走主题变量,暗/亮色自适应;动效时长 = 基准 / --dkan-speed) ----------
|
|
1287
|
-
const css = [
|
|
1288
|
-
// 顶部流光细线(3px,比首版更醒目)
|
|
1289
|
-
".dkan-line{position:fixed;top:0;left:0;right:0;height:3px;z-index:9990;pointer-events:none;background:color-mix(in srgb,var(--dk-accent) 30%,transparent);}",
|
|
1290
|
-
".dkan-line::after{content:\"\";position:absolute;top:0;bottom:0;left:-42%;width:42%;background:linear-gradient(90deg,transparent,var(--dk-accent) 60%,#fff);box-shadow:0 0 10px 1px color-mix(in srgb,var(--dk-accent) 70%,transparent);animation:dkan-flow calc(1.9s / var(--dkan-speed,1)) linear infinite;}",
|
|
1291
|
-
"@keyframes dkan-flow{to{left:100%}}",
|
|
1292
|
-
// 完成瞬间的一次性流光(成功绿 / 异常红),动画结束自动清场
|
|
1293
|
-
".dkan-done{position:fixed;top:0;left:0;right:0;height:3px;z-index:9991;pointer-events:none;background:linear-gradient(90deg,transparent,var(--dsw-alias-state-success-primary,#34d399) 50%,#fff);background-size:50% 100%;background-repeat:no-repeat;animation:dkan-done 1.1s var(--ds-ease-in-out) forwards;}",
|
|
1294
|
-
".dkan-done.err{background-image:linear-gradient(90deg,transparent,var(--dsw-alias-state-error-primary,#f87171) 50%,#fff);}",
|
|
1295
|
-
"@keyframes dkan-done{0%{background-position:-60% 0;opacity:0}25%{opacity:1}100%{background-position:160% 0;opacity:0}}",
|
|
1296
|
-
// 环屏巡航:光点沿屏幕边缘转一整圈
|
|
1297
|
-
".dkan-orbit{position:fixed;top:0;left:0;width:12px;height:12px;z-index:9990;pointer-events:none;border-radius:50%;background:var(--dk-accent);box-shadow:0 0 14px 4px color-mix(in srgb,var(--dk-accent) 60%,transparent),0 0 30px 8px color-mix(in srgb,var(--dk-accent) 25%,transparent);animation:dkan-orbit calc(14s / var(--dkan-speed,1)) linear infinite;}",
|
|
1298
|
-
"@keyframes dkan-orbit{0%{top:0;left:0}25%{top:0;left:calc(100vw - 12px)}50%{top:calc(100vh - 12px);left:calc(100vw - 12px)}75%{top:calc(100vh - 12px);left:0}100%{top:0;left:0}}",
|
|
1299
|
-
// ===== 氛围动效 =====
|
|
1300
|
-
".dkan-amb{position:fixed;inset:0;z-index:9989;pointer-events:none;overflow:hidden;}",
|
|
1301
|
-
// pointer-events 不继承:全屏装饰层的子粒子(星点/流星/极光/火花/彩带)必须各自透明于点击
|
|
1302
|
-
".dkan-amb *,.dkan-fxwrap *{pointer-events:none;}",
|
|
1303
|
-
// 代码雨:字符列缓落(提亮加大,速度随吞吐)
|
|
1304
|
-
".dkan-matrix span{position:absolute;top:-12%;writing-mode:vertical-rl;font-family:var(--ds-font-family-code,monospace);color:var(--dk-accent);text-shadow:0 0 6px color-mix(in srgb,var(--dk-accent) 60%,transparent);animation:dkan-fall linear infinite;will-change:transform;}",
|
|
1305
|
-
"@keyframes dkan-fall{to{transform:translateY(125vh)}}",
|
|
1306
|
-
// 星野:星点缓慢飘移 + 闪烁(加亮加发光)+ 月亮 + 流星
|
|
1307
|
-
".dkan-stars .dkan-star{position:absolute;border-radius:50%;background:#cfe3ff;box-shadow:0 0 4px 1px color-mix(in srgb,#8ab6ff 55%,transparent);animation:dkan-star ease-in-out infinite alternate;}",
|
|
1308
|
-
"@keyframes dkan-star{0%{opacity:.3;transform:translate(0,0)}50%{opacity:1}100%{opacity:.45;transform:translate(var(--dx),var(--dy))}}",
|
|
1309
|
-
// 月亮:右上弯月(radial-gradient 咬出月牙,透明区不遮挡背景;外圈柔光晕)
|
|
1310
|
-
".dkan-moon{position:absolute;top:9%;right:10%;width:36px;height:36px;border-radius:50%;background:radial-gradient(circle at 66% 34%,transparent 0 44%,#fdf6d8 46%,#e8d9a0 78%,#d8c78a);box-shadow:0 0 18px 4px color-mix(in srgb,#fdf6d8 35%,transparent),0 0 44px 12px color-mix(in srgb,#fdf6d8 15%,transparent);}",
|
|
1311
|
-
// 流星:斜向划过的光带(亮头 + 渐隐尾),周期出现
|
|
1312
|
-
".dkan-meteor{position:absolute;width:110px;height:2px;border-radius:2px;background:linear-gradient(90deg,#fff,color-mix(in srgb,#8ab6ff 60%,transparent) 40%,transparent);transform:rotate(35deg);transform-origin:left center;opacity:0;animation:dkan-meteor 8.4s linear infinite;}",
|
|
1313
|
-
".dkan-meteor::before{content:\"\";position:absolute;left:-2px;top:-2px;width:6px;height:6px;border-radius:50%;background:#fff;box-shadow:0 0 8px 2px color-mix(in srgb,#cfe3ff 70%,transparent);}",
|
|
1314
|
-
"@keyframes dkan-meteor{0%{opacity:0;transform:rotate(35deg) translateX(0)}3%{opacity:1}10%{opacity:0;transform:rotate(35deg) translateX(46vh)}100%{opacity:0;transform:rotate(35deg) translateX(46vh)}}",
|
|
1315
|
-
// 星际远征:星尘、行星轨道和任务飞船都只在任务运行时出现,统一由 --dkan-speed 加速。
|
|
1316
|
-
".dkan-space{background:radial-gradient(ellipse 34% 28% at 84% 11%,color-mix(in srgb,#426aaf 15%,transparent),transparent 72%),radial-gradient(ellipse 26% 22% at 14% 58%,color-mix(in srgb,#7c4dff 10%,transparent),transparent 74%);}",
|
|
1317
|
-
".dkan-space-dust{position:absolute;border-radius:50%;background:#dbeafe;box-shadow:0 0 5px color-mix(in srgb,#93c5fd 62%,transparent);opacity:.2;animation:dkan-space-dust ease-in-out infinite alternate;will-change:transform,opacity;}",
|
|
1318
|
-
"@keyframes dkan-space-dust{0%{opacity:.14;transform:translate3d(0,0,0) scale(.7)}55%{opacity:.78}100%{opacity:.26;transform:translate3d(22px,-8px,0) scale(1.15)}}",
|
|
1319
|
-
".dkan-space-system{position:fixed;width:246px;height:246px;border-radius:50%;opacity:.78;transform:translateZ(0) scale(var(--dkan-system-scale,1));transform-origin:center;contain:layout paint style;}.dkan-space-system.alpha{opacity:.76}.dkan-space-system.beta{opacity:.68}.dkan-space-system.gamma{opacity:.64}",
|
|
1320
|
-
".dkan-space-sun{position:absolute;left:50%;top:50%;width:34px;height:34px;border-radius:50%;transform:translate(-50%,-50%);background:radial-gradient(circle at 32% 28%,#fff7c2 0 9%,#ffc85a 30%,#e76f2e 74%);box-shadow:0 0 12px 4px color-mix(in srgb,#ffc85a 58%,transparent),0 0 36px 12px color-mix(in srgb,#f97316 20%,transparent);}",
|
|
1321
|
-
".dkan-space-orbit{position:absolute;left:50%;top:50%;border:1px solid color-mix(in srgb,#93c5fd 28%,transparent);border-radius:50%;animation:dkan-space-orbit linear infinite;transform-origin:center;}",
|
|
1322
|
-
".dkan-space-orbit i{position:absolute;top:50%;left:-5px;width:10px;height:10px;border-radius:50%;transform:translateY(-50%);box-shadow:0 0 9px 2px currentColor;}",
|
|
1323
|
-
".dkan-space-orbit.a{width:82px;height:50px;margin:-25px 0 0 -41px;animation-duration:calc(5.4s / var(--dkan-speed,1));}.dkan-space-orbit.a i{color:#67e8f9;background:#22d3ee;}",
|
|
1324
|
-
".dkan-space-orbit.b{width:142px;height:86px;margin:-43px 0 0 -71px;animation-duration:calc(9.2s / var(--dkan-speed,1));animation-direction:reverse;}.dkan-space-orbit.b i{width:15px;height:15px;color:#c4b5fd;background:radial-gradient(circle at 30% 28%,#ede9fe,#8b5cf6 72%);}",
|
|
1325
|
-
".dkan-space-orbit.c{width:212px;height:130px;margin:-65px 0 0 -106px;animation-duration:calc(15.6s / var(--dkan-speed,1));}.dkan-space-orbit.c i{width:18px;height:18px;color:#fda4af;background:radial-gradient(circle at 32% 28%,#ffe4e6,#e11d48 74%);}",
|
|
1326
|
-
"@keyframes dkan-space-orbit{to{transform:rotate(360deg)}}",
|
|
1327
|
-
".dkan-space-runner{position:fixed;left:0;top:0;width:108px;height:56px;opacity:0;animation-duration:calc(10.8s / var(--dkan-speed,1));animation-timing-function:linear;animation-iteration-count:infinite;animation-delay:var(--dkan-run-delay,0s);will-change:transform,opacity;contain:layout paint style;backface-visibility:hidden;}.dkan-space-runner .dkan-freighter{left:0;top:0;transform:translateZ(0) scale(.52);transform-origin:top left;filter:none}.dkan-space-runner .dkan-freighter-engine i{animation:none;opacity:.76;box-shadow:-6px 0 8px #22d3ee}.dkan-space-runner .dkan-freighter-nose{filter:none}.dkan-space-runner.route-0{animation-name:dkan-space-route-a}.dkan-space-runner.route-1{animation-name:dkan-space-route-b}.dkan-space-runner.route-2{animation-name:dkan-space-route-c}",
|
|
1328
|
-
"@keyframes dkan-space-route-a{0%{opacity:0;transform:translate3d(var(--dkan-alpha-x),var(--dkan-alpha-y),0) rotate(151deg)}7%,86%{opacity:.9}100%{opacity:0;transform:translate3d(var(--dkan-beta-x),var(--dkan-beta-y),0) rotate(151deg)}}@keyframes dkan-space-route-b{0%{opacity:0;transform:translate3d(var(--dkan-beta-x),var(--dkan-beta-y),0) rotate(-90deg)}7%,86%{opacity:.9}100%{opacity:0;transform:translate3d(var(--dkan-gamma-x),var(--dkan-gamma-y),0) rotate(-90deg)}}@keyframes dkan-space-route-c{0%{opacity:0;transform:translate3d(var(--dkan-gamma-x),var(--dkan-gamma-y),0) rotate(-18deg)}7%,86%{opacity:.9}100%{opacity:0;transform:translate3d(var(--dkan-alpha-x),var(--dkan-alpha-y),0) rotate(-18deg)}}",
|
|
1329
|
-
// 完成归航:一艘货运旗舰从航线飞到真实会话输入框上方,货舱逐格表达实际输出量。
|
|
1330
|
-
".dkan-delivery{position:fixed;left:0;top:0;width:210px;height:88px;z-index:9994;pointer-events:none;will-change:transform,opacity;contain:layout paint style;isolation:isolate;animation:dkan-delivery-flight var(--dkan-delivery-duration,8600ms) cubic-bezier(.22,.61,.36,1) forwards;}",
|
|
1331
|
-
"@keyframes dkan-delivery-flight{0%{opacity:0;transform:translate3d(var(--dkan-from-x),var(--dkan-from-y),0) scale(.5) rotate(-12deg)}6%{opacity:1;transform:translate3d(var(--dkan-from-x),var(--dkan-from-y),0) scale(.56) rotate(-10deg)}100%{opacity:1;transform:translate3d(var(--dkan-to-x),var(--dkan-to-y),0) scale(1) rotate(0)}}",
|
|
1332
|
-
".dkan-delivery-trail{position:absolute;left:-132px;top:26px;width:154px;height:4px;border-radius:999px;background:linear-gradient(90deg,transparent,#34d399 48%,#d9f99d 78%,transparent);opacity:.9;animation:dkan-delivery-trail var(--dkan-delivery-duration,8600ms) linear forwards;}@keyframes dkan-delivery-trail{0%,82%{opacity:.9}100%{opacity:0}}.dkan-freighter{position:absolute;left:10px;top:10px;width:176px;height:45px;display:block;filter:drop-shadow(0 0 9px color-mix(in srgb,#7dd3fc 62%,transparent));}.dkan-delivery .dkan-freighter,.dkan-departure .dkan-freighter{filter:none}.dkan-delivery .dkan-freighter-engine i,.dkan-departure .dkan-freighter-engine i{animation:none;opacity:.76;box-shadow:-6px 0 8px #22d3ee}.dkan-delivery .dkan-freighter-nose,.dkan-departure .dkan-freighter-nose{filter:none}.dkan-freighter-hull{position:absolute;z-index:3;left:21px;top:7px;width:126px;height:31px;background:linear-gradient(150deg,#f8fbff 0%,#9cc8ff 30%,#385d91 72%,#1d3150);clip-path:polygon(0 30%,43% 0,83% 17%,100% 50%,83% 83%,43% 100%,0 70%,12% 50%);box-shadow:inset 0 1px 0 rgb(255 255 255 / .7),inset -7px -5px 9px rgb(8 24 49 / .38);}.dkan-freighter-nose{position:absolute;z-index:4;right:10px;top:16px;width:28px;height:14px;background:linear-gradient(90deg,#a9d1ff,#e8f5ff);clip-path:polygon(0 0,100% 50%,0 100%,22% 50%);filter:drop-shadow(5px 0 5px color-mix(in srgb,#93c5fd 62%,transparent));}.dkan-freighter-canopy{position:absolute;z-index:5;left:77px;top:10px;width:39px;height:11px;border-radius:70% 48% 42% 32%;background:linear-gradient(135deg,#ecfeff 0%,#67e8f9 32%,#2563eb 75%);transform:skewX(-18deg);box-shadow:inset 0 2px 2px rgb(255 255 255 / .7),0 0 8px color-mix(in srgb,#67e8f9 42%,transparent);}.dkan-freighter-fin{position:absolute;z-index:2;left:48px;top:3px;width:24px;height:17px;background:linear-gradient(135deg,#31557f,#a7caf7);clip-path:polygon(0 100%,58% 0,100% 100%);}.dkan-freighter-wing{position:absolute;z-index:1;left:48px;width:60px;height:18px;background:linear-gradient(135deg,#24436d,#8ebaf4);}.dkan-freighter-wing.left{top:2px;clip-path:polygon(0 100%,100% 0,68% 100%)}.dkan-freighter-wing.right{top:26px;clip-path:polygon(0 0,68% 0,100% 100%)}",
|
|
1333
|
-
".dkan-freighter-cargo{position:absolute;z-index:6;left:37px;top:31px;width:74px;height:7px;display:flex;align-items:center;gap:2px;padding:1px 3px;border-radius:4px;background:#172842;border:1px solid color-mix(in srgb,#b8d8ff 48%,transparent);box-shadow:inset 0 1px 0 rgb(255 255 255 / .2);}.dkan-freighter-cargo i{display:block;flex:1;min-width:4px;height:5px;border-radius:1px;background:linear-gradient(135deg,#fef3c7,#f59e0b 58%,#b45309);box-shadow:0 0 4px color-mix(in srgb,#fbbf24 58%,transparent);}.dkan-freighter-cargo.empty i{background:linear-gradient(135deg,#dbeafe,#38bdf8 58%,#1d4ed8);box-shadow:0 0 4px color-mix(in srgb,#38bdf8 55%,transparent);}.dkan-freighter-engine{position:absolute;z-index:0;left:5px;top:19px;display:flex;gap:3px;align-items:center;height:11px;}.dkan-freighter-engine i{width:18px;height:5px;border-radius:60% 0 0 60%;background:linear-gradient(90deg,transparent,#67e8f9);box-shadow:-9px 0 12px #22d3ee;animation:dkan-engine .34s ease-in-out infinite alternate;}.dkan-freighter-engine i:nth-child(2){animation-delay:-.14s}",
|
|
1334
|
-
"@keyframes dkan-engine{to{transform:scaleX(.55);opacity:.5}}",
|
|
1335
|
-
".dkan-delivery-label{position:absolute;left:21px;top:58px;display:flex;align-items:baseline;gap:6px;padding:4px 8px;border:1px solid color-mix(in srgb,#93c5fd 30%,var(--dsw-alias-border-l1));border-radius:999px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 82%,transparent);backdrop-filter:blur(10px);color:var(--dsw-alias-label-secondary);font:10px/1.2 var(--ds-font-family,system-ui,sans-serif);white-space:nowrap;}.dkan-delivery-label b{color:var(--dsw-alias-label-primary);font-weight:650}.dkan-delivery-label span{color:var(--dk-accent);}",
|
|
1336
|
-
"@media (max-width:1100px){.dkan-space-system,.dkan-space-runner{display:none}}",
|
|
1337
|
-
".dkan-ready-freighter{position:fixed;width:210px;height:88px;z-index:9994;pointer-events:none;filter:drop-shadow(0 10px 14px rgb(0 0 0 / .18));}.dkan-ready-freighter>span:last-child{position:absolute;left:21px;top:58px;padding:4px 8px;border:1px solid color-mix(in srgb,#93c5fd 28%,var(--dsw-alias-border-l1));border-radius:999px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 80%,transparent);backdrop-filter:blur(10px);color:var(--dsw-alias-label-secondary);font:10px/1.2 var(--ds-font-family,system-ui,sans-serif);white-space:nowrap;}.dkan-departure{position:fixed;left:0;top:0;width:210px;height:88px;z-index:9994;pointer-events:none;will-change:transform,opacity;contain:layout paint style;isolation:isolate;animation:dkan-departure-flight var(--dkan-departure-duration,8600ms) cubic-bezier(.42,0,.85,.45) forwards;}@keyframes dkan-departure-flight{0%{opacity:0;transform:translate3d(var(--dkan-from-x),var(--dkan-from-y),0) scale(1) rotate(var(--dkan-flight-angle,0deg))}6%{opacity:1;transform:translate3d(var(--dkan-from-x),var(--dkan-from-y),0) scale(1) rotate(var(--dkan-flight-angle,0deg))}100%{opacity:0;transform:translate3d(var(--dkan-to-x),var(--dkan-to-y),0) scale(.24) rotate(var(--dkan-flight-angle,0deg))}}",
|
|
1338
|
-
".dkan-space-stuck{position:fixed;right:20px;top:88px;width:244px;height:160px;z-index:9993;pointer-events:none;border:1px solid color-mix(in srgb,#f59e0b 48%,var(--dsw-alias-border-l1));border-radius:20px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 88%,transparent);backdrop-filter:blur(16px);box-shadow:0 18px 54px rgb(0 0 0 / .28),0 0 30px color-mix(in srgb,#f59e0b 15%,transparent);}.dkan-space-stuck .dkan-freighter{left:37px;top:11px;transform:scale(.9);transform-origin:top left;}.dkan-stuck-code{position:absolute;left:17px;right:17px;top:60px;height:36px;border-radius:7px;background:linear-gradient(90deg,color-mix(in srgb,#f59e0b 18%,transparent),transparent);color:var(--dk-warn);font:700 14px/36px var(--ds-font-family-code,monospace);letter-spacing:.12em;text-align:center;overflow:hidden;}.dkan-stuck-code::after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,transparent,color-mix(in srgb,#fef3c7 70%,transparent),transparent);transform:translateX(-110%);animation:dkan-stuck-scan 1.25s linear infinite;}.dkan-stuck-code i{display:inline-block;width:7px;height:7px;margin-right:8px;border-radius:50%;background:#f59e0b;box-shadow:0 0 9px #f59e0b;animation:dkan-stuck-pulse .8s ease-in-out infinite alternate;}.dkan-space-stuck strong{position:absolute;left:0;right:0;top:108px;color:var(--dsw-alias-label-primary);font:650 13px/1.2 var(--ds-font-family,system-ui,sans-serif);text-align:center;}.dkan-space-stuck small{position:absolute;left:18px;right:18px;top:128px;color:var(--dsw-alias-label-secondary);font:11px/1.25 var(--ds-font-family,system-ui,sans-serif);text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}@keyframes dkan-stuck-scan{to{transform:translateX(110%)}}@keyframes dkan-stuck-pulse{to{transform:scale(.55);opacity:.45}}",
|
|
1339
|
-
// 窄屏(手机/分屏)贴右会溢出:回落到水平居中、屏幕上方,避开正文重心
|
|
1340
|
-
"@media (max-width:760px){.dkan-space-stuck{right:50%;transform:translateX(50%);top:64px;}}",
|
|
1341
|
-
// 极光:顶部柔光带呼吸流动(加浓)
|
|
1342
|
-
".dkan-aurora i{position:absolute;top:-40%;left:-20%;width:70%;height:80%;border-radius:50%;filter:blur(50px);opacity:.32;animation:dkan-aurora ease-in-out infinite alternate;}",
|
|
1343
|
-
".dkan-aurora i:nth-child(1){background:#4d9fff;}",
|
|
1344
|
-
".dkan-aurora i:nth-child(2){background:#34d399;left:20%;animation-delay:-2s;}",
|
|
1345
|
-
".dkan-aurora i:nth-child(3){background:#a78bfa;left:55%;animation-delay:-4s;}",
|
|
1346
|
-
"@keyframes dkan-aurora{0%{transform:translateX(-8%) scaleY(1)}100%{transform:translateX(10%) scaleY(1.3)}}",
|
|
1347
|
-
// 星云潮汐:无动态模糊,柔边由径向渐变生成,移动只发生在合成层。
|
|
1348
|
-
".dkan-nebula{contain:strict}.dkan-nebula i{position:absolute;width:48vw;aspect-ratio:1;border-radius:50%;opacity:.22;background:radial-gradient(circle,color-mix(in srgb,var(--dkan-phase,#60a5fa) 48%,transparent),color-mix(in srgb,#8b5cf6 18%,transparent) 42%,transparent 70%);animation:dkan-nebula-drift calc(18s / var(--dkan-speed,1)) cubic-bezier(.45,0,.55,1) infinite alternate;will-change:transform,opacity}.dkan-nebula i:nth-child(1){left:-18%;top:-24%}.dkan-nebula i:nth-child(2){right:-18%;top:4%;background:radial-gradient(circle,color-mix(in srgb,#22d3ee 34%,transparent),color-mix(in srgb,#3b82f6 15%,transparent) 45%,transparent 72%);animation-delay:-5s}.dkan-nebula i:nth-child(3){left:4%;bottom:-34%;background:radial-gradient(circle,color-mix(in srgb,#f472b6 28%,transparent),color-mix(in srgb,#8b5cf6 14%,transparent) 48%,transparent 72%);animation-delay:-10s}.dkan-nebula i:nth-child(4){right:8%;bottom:-26%;width:36vw;animation-delay:-14s}",
|
|
1349
|
-
"@keyframes dkan-nebula-drift{0%{opacity:.14;transform:translate3d(-3vw,-2vh,0) scale(.92)}100%{opacity:.3;transform:translate3d(5vw,4vh,0) scale(1.12)}}",
|
|
1350
|
-
// 曲速航道:从视口中心放射的独立光束。
|
|
1351
|
-
".dkan-warp{background:radial-gradient(circle at center,color-mix(in srgb,var(--dkan-phase,#60a5fa) 10%,transparent),transparent 34%);contain:strict}.dkan-warp i{position:absolute;left:50%;top:50%;width:var(--len);height:1px;border-radius:999px;transform-origin:left center;background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--dkan-phase,#60a5fa) 48%,transparent),#e0f2fe);opacity:0;animation-name:dkan-warp-ray;animation-timing-function:cubic-bezier(.3,.1,.8,1);animation-iteration-count:infinite;will-change:transform,opacity}",
|
|
1352
|
-
"@keyframes dkan-warp-ray{0%{opacity:0;transform:rotate(var(--a)) translate3d(18px,0,0) scaleX(.05)}18%{opacity:.6}100%{opacity:0;transform:rotate(var(--a)) translate3d(58vw,0,0) scaleX(1.55)}}",
|
|
1353
|
-
// 量子雷达:三个边缘站点各自扫描,避免占据会话正文中心。
|
|
1354
|
-
".dkan-radar{contain:strict}.dkan-radar-station{position:absolute;width:176px;height:176px;border-radius:50%;opacity:.44;background:repeating-radial-gradient(circle,color-mix(in srgb,var(--dkan-phase,#60a5fa) 28%,transparent) 0 1px,transparent 1px 29px);border:1px solid color-mix(in srgb,var(--dkan-phase,#60a5fa) 22%,transparent)}.dkan-radar-station.a{right:4%;top:8%}.dkan-radar-station.b{left:1%;top:38%;transform:scale(.72)}.dkan-radar-station.c{right:7%;bottom:9%;transform:scale(.58)}.dkan-radar-station i{position:absolute;inset:0;border-radius:50%;background:conic-gradient(from 0deg,transparent 0 76%,color-mix(in srgb,var(--dkan-phase,#60a5fa) 44%,transparent) 92%,transparent);animation:dkan-radar-sweep calc(4.8s / var(--dkan-speed,1)) linear infinite;will-change:transform}.dkan-radar-station b{position:absolute;left:67%;top:31%;width:7px;height:7px;border-radius:50%;background:var(--dkan-phase,#60a5fa);box-shadow:0 0 12px 4px color-mix(in srgb,var(--dkan-phase,#60a5fa) 58%,transparent);animation:dkan-radar-blip calc(2.4s / var(--dkan-speed,1)) ease-in-out infinite}",
|
|
1355
|
-
"@keyframes dkan-radar-sweep{to{transform:rotate(360deg)}}@keyframes dkan-radar-blip{0%,70%,100%{opacity:.15;transform:scale(.65)}78%{opacity:1;transform:scale(1.25)}}",
|
|
1356
|
-
// 星座网络:轻量 SVG 线网分段点亮。
|
|
1357
|
-
".dkan-constellation svg{position:absolute;left:8%;top:7%;width:86%;height:78%;overflow:visible}.dkan-constellation path{fill:none;stroke:color-mix(in srgb,var(--dkan-phase,#60a5fa) 42%,transparent);stroke-width:.16;vector-effect:non-scaling-stroke;stroke-dasharray:.08 .025;animation:dkan-constellation-line calc(7s / var(--dkan-speed,1)) linear infinite}.dkan-constellation circle{fill:var(--dkan-phase,#60a5fa);filter:drop-shadow(0 0 2px var(--dkan-phase,#60a5fa));animation:dkan-constellation-star calc(2.8s / var(--dkan-speed,1)) ease-in-out infinite alternate;transform-box:fill-box;transform-origin:center}",
|
|
1358
|
-
"@keyframes dkan-constellation-line{to{stroke-dashoffset:-1}}@keyframes dkan-constellation-star{0%{opacity:.28;transform:scale(.68)}100%{opacity:.92;transform:scale(1.22)}}",
|
|
1359
|
-
// 数据萤火:二维漂移和明暗呼吸组合在同一个 transform 动画中。
|
|
1360
|
-
".dkan-fireflies{contain:strict}.dkan-fireflies i{position:absolute;border-radius:50%;background:color-mix(in srgb,var(--dkan-phase,#60a5fa) 78%,#fff);box-shadow:0 0 8px 2px color-mix(in srgb,var(--dkan-phase,#60a5fa) 46%,transparent);opacity:.2;animation-name:dkan-firefly;animation-timing-function:cubic-bezier(.45,0,.55,1);animation-iteration-count:infinite;animation-direction:alternate;will-change:transform,opacity}",
|
|
1361
|
-
"@keyframes dkan-firefly{0%{opacity:.12;transform:translate3d(0,0,0) scale(.65)}48%{opacity:.86}100%{opacity:.26;transform:translate3d(var(--dx),var(--dy),0) scale(1.18)}}",
|
|
1362
|
-
// 深海脉动:波层在底部横向漂移,气泡只做向上合成位移。
|
|
1363
|
-
".dkan-ocean{background:linear-gradient(0deg,color-mix(in srgb,#0284c7 12%,transparent),transparent 46%);contain:strict}.dkan-ocean-waves{position:absolute;left:-12%;right:-12%;bottom:0;height:42%}.dkan-ocean-waves i{position:absolute;left:0;width:112%;height:52%;border-top:1px solid color-mix(in srgb,#67e8f9 28%,transparent);border-radius:50%;animation:dkan-ocean-wave calc(9s / var(--dkan-speed,1)) ease-in-out infinite alternate;will-change:transform}.dkan-ocean-waves i:nth-child(1){top:2%}.dkan-ocean-waves i:nth-child(2){top:18%;animation-delay:-2s}.dkan-ocean-waves i:nth-child(3){top:34%;animation-delay:-4s}.dkan-ocean-waves i:nth-child(4){top:50%;animation-delay:-6s}.dkan-ocean-waves i:nth-child(5){top:66%;animation-delay:-8s}.dkan-ocean>b{position:absolute;bottom:-20px;border:1px solid color-mix(in srgb,#a5f3fc 54%,transparent);border-radius:50%;opacity:0;animation-name:dkan-ocean-bubble;animation-timing-function:linear;animation-iteration-count:infinite;will-change:transform,opacity}",
|
|
1364
|
-
"@keyframes dkan-ocean-wave{to{transform:translate3d(7%,5px,0) scaleY(1.08)}}@keyframes dkan-ocean-bubble{0%{opacity:0;transform:translate3d(0,0,0) scale(.7)}14%{opacity:.58}100%{opacity:0;transform:translate3d(var(--drift),-82vh,0) scale(1.18)}}",
|
|
1365
|
-
// 棱镜光谱:七条低透明度光带错峰穿过视口。
|
|
1366
|
-
".dkan-prism{contain:strict}.dkan-prism i{position:absolute;left:-32vw;width:160vw;height:2px;border-radius:999px;opacity:0;background:linear-gradient(90deg,transparent,#ef4444,#f59e0b,#facc15,#22c55e,#22d3ee,#6366f1,#a855f7,transparent);animation:dkan-prism-ray calc(8s / var(--dkan-speed,1)) cubic-bezier(.45,0,.55,1) infinite;will-change:transform,opacity}.dkan-prism i:nth-child(1){top:12%;animation-delay:0s}.dkan-prism i:nth-child(2){top:25%;animation-delay:-1.1s}.dkan-prism i:nth-child(3){top:38%;animation-delay:-2.2s}.dkan-prism i:nth-child(4){top:51%;animation-delay:-3.3s}.dkan-prism i:nth-child(5){top:64%;animation-delay:-4.4s}.dkan-prism i:nth-child(6){top:77%;animation-delay:-5.5s}.dkan-prism i:nth-child(7){top:90%;animation-delay:-6.6s}",
|
|
1367
|
-
"@keyframes dkan-prism-ray{0%{opacity:0;transform:translate3d(-12vw,0,0) rotate(-7deg) scaleX(.7)}34%{opacity:.2}66%{opacity:.36}100%{opacity:0;transform:translate3d(20vw,0,0) rotate(-7deg) scaleX(1.05)}}",
|
|
1368
|
-
// 神经电路:少量 SVG 路径的虚线脉冲,节点保持低透明度。
|
|
1369
|
-
".dkan-circuit svg{position:absolute;inset:5% 3% 9% 8%;width:89%;height:86%;overflow:visible}.dkan-circuit path{fill:none;stroke:color-mix(in srgb,var(--dkan-phase,#60a5fa) 40%,transparent);stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:.045 .12;animation:dkan-circuit-pulse calc(5.6s / var(--dkan-speed,1)) linear infinite}.dkan-circuit circle{fill:var(--dkan-phase,#60a5fa);opacity:.58;filter:drop-shadow(0 0 4px var(--dkan-phase,#60a5fa));animation:dkan-circuit-node calc(2.2s / var(--dkan-speed,1)) ease-in-out infinite alternate}",
|
|
1370
|
-
"@keyframes dkan-circuit-pulse{to{stroke-dashoffset:-1}}@keyframes dkan-circuit-node{to{opacity:.16}}",
|
|
1371
|
-
// 引力涟漪:三个边缘引力源按错峰节奏扩散。
|
|
1372
|
-
".dkan-gravity{contain:strict}.dkan-gravity-source{position:absolute;width:260px;height:260px}.dkan-gravity-source.a{right:2%;top:5%}.dkan-gravity-source.b{left:-4%;top:39%;transform:scale(.76)}.dkan-gravity-source.c{right:17%;bottom:-10%;transform:scale(.62)}.dkan-gravity-source b{position:absolute;left:50%;top:50%;width:12px;height:12px;margin:-6px;border-radius:50%;background:var(--dkan-phase,#60a5fa);box-shadow:0 0 16px 5px color-mix(in srgb,var(--dkan-phase,#60a5fa) 42%,transparent)}.dkan-gravity-source i{position:absolute;left:50%;top:50%;width:26px;height:26px;margin:-13px;border:1px solid color-mix(in srgb,var(--dkan-phase,#60a5fa) 42%,transparent);border-radius:50%;opacity:0;animation:dkan-gravity-wave calc(4.6s / var(--dkan-speed,1)) cubic-bezier(.2,.55,.35,1) infinite;will-change:transform,opacity}",
|
|
1373
|
-
"@keyframes dkan-gravity-wave{0%{opacity:.64;transform:scale(.3)}100%{opacity:0;transform:scale(9)}}",
|
|
1374
|
-
// 灵感天灯:暖色灯体从屏幕底部缓慢升起,尾迹由伪元素提供。
|
|
1375
|
-
".dkan-lantern{contain:strict}.dkan-lantern i{position:absolute;bottom:-30px;border-radius:45% 45% 34% 34%;background:linear-gradient(150deg,#fff7c2 0%,#fbbf24 46%,#f97316 100%);box-shadow:0 0 10px 3px color-mix(in srgb,#fbbf24 36%,transparent);opacity:0;animation-name:dkan-lantern-rise;animation-timing-function:cubic-bezier(.34,.02,.6,1);animation-iteration-count:infinite;will-change:transform,opacity}.dkan-lantern i::after{content:'';position:absolute;left:42%;top:100%;width:16%;height:13px;background:linear-gradient(#f59e0b,transparent)}",
|
|
1376
|
-
"@keyframes dkan-lantern-rise{0%{opacity:0;transform:translate3d(0,0,0) rotate(-3deg) scale(.72)}12%{opacity:.72}78%{opacity:.48}100%{opacity:0;transform:translate3d(var(--drift),-104vh,0) rotate(4deg) scale(1.04)}}",
|
|
1377
|
-
// ===== 交互反馈层 =====
|
|
1378
|
-
".dkan-fxwrap{position:fixed;inset:0;z-index:9992;pointer-events:none;overflow:hidden;}",
|
|
1379
|
-
".dkan-fx{position:absolute;}",
|
|
1380
|
-
// 纸飞机:从点击处向右上飞出淡出(加大加亮)
|
|
1381
|
-
".dkan-fx-plane{color:var(--dk-accent);font-size:24px;text-shadow:0 0 8px color-mix(in srgb,var(--dk-accent) 70%,transparent);animation:dkan-plane 1.2s var(--ds-ease-in-out) forwards;}",
|
|
1382
|
-
"@keyframes dkan-plane{0%{opacity:0;transform:translate(0,0) rotate(-20deg) scale(.6)}15%{opacity:1;transform:translate(20px,-14px) rotate(-20deg) scale(1.1)}100%{opacity:0;transform:translate(240px,-160px) rotate(-20deg) scale(1)}}",
|
|
1383
|
-
// 火花:粒子四散(加大加多散得更开)
|
|
1384
|
-
".dkan-fx-spark i{position:absolute;width:7px;height:7px;border-radius:50%;box-shadow:0 0 6px currentColor;animation:dkan-spark .9s var(--ds-ease-in-out) forwards;}",
|
|
1385
|
-
"@keyframes dkan-spark{0%{opacity:1;transform:translate(0,0) scale(1)}100%{opacity:0;transform:translate(var(--dx),var(--dy)) scale(.4)}}",
|
|
1386
|
-
// 流光:横向扫过(切会话,加高加亮)
|
|
1387
|
-
".dkan-fx-streak{top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,transparent,var(--dk-accent) 50%,#fff);background-size:45% 100%;background-repeat:no-repeat;box-shadow:0 0 12px 2px color-mix(in srgb,var(--dk-accent) 50%,transparent);animation:dkan-streak 1s var(--ds-ease-in-out) forwards;}",
|
|
1388
|
-
"@keyframes dkan-streak{0%{background-position:-50% 0;opacity:0}20%{opacity:1}100%{background-position:150% 0;opacity:0}}",
|
|
1389
|
-
// 微光:全屏柔光一闪(切工作目录,加浓)
|
|
1390
|
-
".dkan-fx-flash{inset:0;background:radial-gradient(ellipse at center,color-mix(in srgb,var(--dk-accent) 26%,transparent),transparent 75%);animation:dkan-flash .8s var(--ds-ease-in-out) forwards;}",
|
|
1391
|
-
"@keyframes dkan-flash{0%{opacity:0}30%{opacity:1}100%{opacity:0}}",
|
|
1392
|
-
// 光涌:底部向上涌起(任务开始,加高加浓)
|
|
1393
|
-
".dkan-fx-surge{left:0;right:0;bottom:0;height:42%;background:linear-gradient(0deg,color-mix(in srgb,var(--dsw-alias-state-success-primary,#34d399) 38%,transparent),transparent);animation:dkan-surge 1.2s var(--ds-ease-in-out) forwards;}",
|
|
1394
|
-
"@keyframes dkan-surge{0%{opacity:0;transform:translateY(45%)}30%{opacity:1}100%{opacity:0;transform:translateY(-35%)}}",
|
|
1395
|
-
// 彩带:顶部飘落(任务完成庆祝,加大加多)
|
|
1396
|
-
".dkan-fx-confetti{inset:0;}",
|
|
1397
|
-
".dkan-fx-confetti i{position:absolute;top:-14px;width:8px;height:13px;border-radius:2px;box-shadow:0 0 4px currentColor;animation:dkan-confetti 2s var(--ds-ease-in-out) forwards;}",
|
|
1398
|
-
"@keyframes dkan-confetti{0%{opacity:1;transform:translateY(0) rotate(0)}100%{opacity:0;transform:translateY(75vh) translateX(var(--dx)) rotate(var(--r))}}",
|
|
1399
|
-
// 右下角状态徽标(玻璃拟态,可点击);抬到 dsh 输入卡上方避免重叠错位
|
|
1400
|
-
".dkan-badge{position:fixed;right:20px;bottom:calc(var(--dsh-composer-height,152px) + 20px);z-index:9990;display:inline-flex;align-items:center;gap:8px;padding:7px 14px;border-radius:999px;cursor:pointer;border:1px solid var(--dsw-alias-border-l1);background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 78%,transparent);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);box-shadow:0 6px 24px rgb(0 0 0 / .16);color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:12px;line-height:18px;pointer-events:auto;animation:dkan-rise .3s var(--ds-ease-in-out);transition:color .15s var(--ds-ease-in-out);}",
|
|
1401
|
-
".dkan-badge:hover{color:var(--dsw-alias-label-primary);}",
|
|
1402
|
-
".dkan-badge .n{color:var(--dsw-alias-label-primary);font-weight:600;}",
|
|
1403
|
-
"@keyframes dkan-rise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}",
|
|
1404
|
-
// 徽标圆点与两种动效(呼吸 / 轨道环)
|
|
1405
|
-
".dkan-dotwrap{position:relative;width:20px;height:20px;flex:none;display:inline-flex;align-items:center;justify-content:center;}",
|
|
1406
|
-
".dkan-dot{width:12px;height:12px;border-radius:50%;background:var(--dkan-phase,var(--dk-accent));box-shadow:0 0 10px color-mix(in srgb,var(--dkan-phase,var(--dk-accent)) 70%,transparent);transition:background .4s var(--ds-ease-in-out),box-shadow .4s var(--ds-ease-in-out);}",
|
|
1407
|
-
".dkan-breathe .dkan-dot{animation:dkan-breathe calc(2.2s / var(--dkan-speed,1)) ease-in-out infinite;}",
|
|
1408
|
-
"@keyframes dkan-breathe{0%,100%{transform:scale(1);opacity:.5}50%{transform:scale(1.7);opacity:1}}",
|
|
1409
|
-
// 呼吸光晕:向外扩散的波纹环,与圆点同频(颜色随阶段)
|
|
1410
|
-
".dkan-halo{position:absolute;inset:0;border-radius:50%;border:2px solid var(--dkan-phase,var(--dk-accent));animation:dkan-halo calc(2.2s / var(--dkan-speed,1)) ease-out infinite;}",
|
|
1411
|
-
"@keyframes dkan-halo{0%{transform:scale(.5);opacity:.9}70%{transform:scale(1.5);opacity:.25}100%{transform:scale(1.7);opacity:0}}",
|
|
1412
|
-
// breathe 模式徽标更醒目:阶段色描边 + 呼吸辉光
|
|
1413
|
-
".dkan-badge-breathe{border-color:color-mix(in srgb,var(--dkan-phase,#4d9fff) 55%,transparent);animation:dkan-rise .3s var(--ds-ease-in-out),dkan-badge-breathe calc(2.2s / var(--dkan-speed,1)) ease-in-out infinite;}",
|
|
1414
|
-
"@keyframes dkan-badge-breathe{0%,100%{box-shadow:0 0 6px color-mix(in srgb,var(--dkan-phase,#4d9fff) 22%,transparent),0 6px 24px rgb(0 0 0 / .16)}50%{box-shadow:0 0 18px color-mix(in srgb,var(--dkan-phase,#4d9fff) 55%,transparent),0 6px 24px rgb(0 0 0 / .16)}}",
|
|
1415
|
-
".dkan-ring{position:absolute;inset:0;border-radius:50%;background:conic-gradient(from 0deg,transparent 0 68%,var(--dk-accent) 92%,#fff);-webkit-mask:radial-gradient(farthest-side,transparent calc(100% - 2px),#000 calc(100% - 2px));mask:radial-gradient(farthest-side,transparent calc(100% - 2px),#000 calc(100% - 2px));animation:dkan-spin calc(2.2s / var(--dkan-speed,1)) linear infinite;opacity:.9;}",
|
|
1416
|
-
"@keyframes dkan-spin{to{transform:rotate(360deg)}}",
|
|
1417
|
-
// 面板页布局
|
|
1418
|
-
".dkan-root{display:flex;flex-direction:column;gap:10px;}",
|
|
1419
|
-
".dkan-note{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.6;}",
|
|
1420
|
-
".dkan-err{color:var(--dsw-alias-state-error-primary);}",
|
|
1421
|
-
".dkan-sec{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:10px;padding:10px 12px;display:flex;flex-direction:column;gap:8px;}",
|
|
1422
|
-
".dkan-sec-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}",
|
|
1423
|
-
".dkan-sec-title{font-weight:600;font-size:13px;color:var(--dsw-alias-label-primary);flex:none;}",
|
|
1424
|
-
".dkan-sec-sub{font-size:11px;color:var(--dsw-alias-label-tertiary);flex:1;min-width:120px;}",
|
|
1425
|
-
".dkan-sec-sw{margin-left:auto;flex:none;display:inline-flex;align-items:center;gap:8px;}",
|
|
1426
|
-
".dkan-sec-swlabel{font-size:11px;color:var(--dsw-alias-label-tertiary);white-space:nowrap;}",
|
|
1427
|
-
".dkan-sec-swlabel.on{color:var(--dsw-alias-state-success-primary);}",
|
|
1428
|
-
// 模式选择卡(带缩微预览)
|
|
1429
|
-
".dkan-modes{display:flex;gap:8px;flex-wrap:wrap;}",
|
|
1430
|
-
".dkan-mode{flex:1;min-width:150px;display:flex;flex-direction:column;gap:6px;padding:10px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-2);cursor:pointer;font-family:inherit;text-align:left;transition:border-color .15s var(--ds-ease-in-out);}",
|
|
1431
|
-
".dkan-mode:hover{border-color:var(--dk-accent);}",
|
|
1432
|
-
".dkan-mode.on{border-color:var(--dk-accent);box-shadow:0 0 0 1px color-mix(in srgb,var(--dk-accent) 40%,transparent);}",
|
|
1433
|
-
".dkan-mode-name{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;gap:6px;}",
|
|
1434
|
-
".dkan-mode.on .dkan-mode-name::after{content:\"✓\";color:var(--dk-accent);}",
|
|
1435
|
-
".dkan-mode-desc{font-size:11px;color:var(--dsw-alias-label-tertiary);line-height:1.5;}",
|
|
1436
|
-
".dkan-robot-controls{display:flex;flex-direction:column;gap:8px;padding:9px 10px;border:1px solid color-mix(in srgb,var(--dk-accent) 28%,var(--dsw-alias-border-l1));border-radius:9px;background:color-mix(in srgb,var(--dk-accent) 5%,var(--dsw-alias-bg-layer-2));}",
|
|
1437
|
-
".dkan-robot-controls-head{display:flex;align-items:center;justify-content:space-between;font-size:12px;color:var(--dsw-alias-label-primary);}",
|
|
1438
|
-
".dkan-robot-controls-head strong{font-variant-numeric:tabular-nums;color:var(--dk-accent);}",
|
|
1439
|
-
".dkan-robot-size-options{display:flex;gap:6px;flex-wrap:wrap;}",
|
|
1440
|
-
".dkan-robot-size-options button{cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;padding:3px 10px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font:inherit;font-size:11px;}",
|
|
1441
|
-
".dkan-robot-size-options button:hover,.dkan-robot-size-options button.on{border-color:var(--dk-accent);color:var(--dsw-alias-label-primary);}",
|
|
1442
|
-
".dkan-robot-slider-row{display:flex;align-items:center;gap:9px;flex-wrap:wrap;font-size:11px;color:var(--dsw-alias-label-tertiary);}",
|
|
1443
|
-
".dkan-robot-slider-row input{accent-color:var(--dk-accent);width:min(240px,100%);}",
|
|
1444
|
-
".dkan-prev{height:30px;border-radius:6px;background:var(--dsw-alias-bg-layer-1);position:relative;overflow:hidden;display:flex;align-items:center;justify-content:center;}",
|
|
1445
|
-
".dkan-prev-flow{position:absolute;top:0;left:0;right:0;height:3px;background:color-mix(in srgb,var(--dk-accent) 30%,transparent);}",
|
|
1446
|
-
".dkan-prev-flow::after{content:\"\";position:absolute;top:0;bottom:0;left:-40%;width:40%;background:linear-gradient(90deg,transparent,var(--dk-accent));animation:dkan-flow 1.9s linear infinite;}",
|
|
1447
|
-
".dkan-prev-box{--dkan-speed:1;}",
|
|
1448
|
-
".dkan-prev-orbit{position:absolute;top:3px;left:3px;width:7px;height:7px;border-radius:50%;background:var(--dk-accent);box-shadow:0 0 7px 2px color-mix(in srgb,var(--dk-accent) 55%,transparent);animation:dkan-prev-orbit 3s linear infinite;}",
|
|
1449
|
-
"@keyframes dkan-prev-orbit{0%{top:3px;left:3px}25%{top:3px;left:calc(100% - 10px)}50%{top:calc(100% - 10px);left:calc(100% - 10px)}75%{top:calc(100% - 10px);left:3px}100%{top:3px;left:3px}}",
|
|
1450
|
-
// 预览:代码雨(字符列下落)
|
|
1451
|
-
".dkan-prev-matrix i{position:absolute;top:-100%;writing-mode:vertical-rl;font-size:8px;font-family:var(--ds-font-family-code,monospace);color:var(--dk-accent);opacity:.5;animation:dkan-prev-fall 2.2s linear infinite;}",
|
|
1452
|
-
".dkan-prev-matrix i:nth-child(1){left:12%;}",
|
|
1453
|
-
".dkan-prev-matrix i:nth-child(2){left:32%;animation-delay:-.6s;}",
|
|
1454
|
-
".dkan-prev-matrix i:nth-child(3){left:52%;animation-delay:-1.2s;}",
|
|
1455
|
-
".dkan-prev-matrix i:nth-child(4){left:72%;animation-delay:-.3s;}",
|
|
1456
|
-
".dkan-prev-matrix i:nth-child(5){left:88%;animation-delay:-1.6s;}",
|
|
1457
|
-
".dkan-prev-matrix i::after{content:\"01</>\";}",
|
|
1458
|
-
"@keyframes dkan-prev-fall{to{transform:translateY(300%)}}",
|
|
1459
|
-
// 预览:星野(星点闪烁)
|
|
1460
|
-
".dkan-prev-stars i{position:absolute;width:2px;height:2px;border-radius:50%;background:var(--dsw-alias-label-secondary);animation:dkan-prev-star 1.6s ease-in-out infinite alternate;}",
|
|
1461
|
-
".dkan-prev-stars i:nth-child(1){left:15%;top:25%;}",
|
|
1462
|
-
".dkan-prev-stars i:nth-child(2){left:40%;top:60%;animation-delay:-.4s;}",
|
|
1463
|
-
".dkan-prev-stars i:nth-child(3){left:60%;top:30%;animation-delay:-.8s;}",
|
|
1464
|
-
".dkan-prev-stars i:nth-child(4){left:80%;top:55%;animation-delay:-1.2s;}",
|
|
1465
|
-
".dkan-prev-stars i:nth-child(5){left:28%;top:70%;animation-delay:-.6s;}",
|
|
1466
|
-
".dkan-prev-stars i:nth-child(6){left:70%;top:75%;animation-delay:-1s;}",
|
|
1467
|
-
"@keyframes dkan-prev-star{0%{opacity:.2}100%{opacity:.9}}",
|
|
1468
|
-
// 预览:星际远征(行星、轨道与飞船)
|
|
1469
|
-
".dkan-prev-space i{position:absolute;left:49%;top:50%;width:9px;height:9px;border-radius:50%;background:#fbbf24;box-shadow:0 0 6px #fbbf24;}.dkan-prev-space b{position:absolute;left:21%;top:23%;width:15px;height:8px;border:1px solid #93c5fd;border-radius:50%;animation:dkan-prev-space-orbit 2.4s linear infinite;}.dkan-prev-space b::after{content:'';position:absolute;left:-3px;top:0;width:5px;height:5px;border-radius:50%;background:#a78bfa;box-shadow:0 0 4px #a78bfa;}.dkan-prev-space em{position:absolute;left:5%;top:65%;width:19px;height:4px;border-radius:50% 0 0 50%;background:linear-gradient(90deg,#22d3ee,#dbeafe);animation:dkan-prev-space-ship 1.8s linear infinite;}.dkan-prev-space em::after{content:'';position:absolute;right:-7px;top:-3px;border-left:9px solid #93c5fd;border-top:5px solid transparent;border-bottom:5px solid transparent;}.dkan-prev-space{background:radial-gradient(ellipse at 78% 20%,color-mix(in srgb,#6366f1 18%,transparent),transparent 58%);}.dkan-prev-space i,.dkan-prev-space b,.dkan-prev-space em{font-style:normal;}.dkan-prev-space b{transform-origin:34px 11px;}@keyframes dkan-prev-space-orbit{to{transform:rotate(360deg)}}@keyframes dkan-prev-space-ship{to{transform:translateX(105px)}}",
|
|
1470
|
-
// 预览:极光(柔光带呼吸)
|
|
1471
|
-
".dkan-prev-aurora i{position:absolute;top:-30%;width:60%;height:120%;border-radius:50%;filter:blur(8px);opacity:.35;animation:dkan-prev-aurora 2.4s ease-in-out infinite alternate;}",
|
|
1472
|
-
".dkan-prev-aurora i:nth-child(1){left:5%;background:#4d9fff;}",
|
|
1473
|
-
".dkan-prev-aurora i:nth-child(2){left:45%;background:#34d399;animation-delay:-1.2s;}",
|
|
1474
|
-
"@keyframes dkan-prev-aurora{0%{transform:translateX(-10%)}100%{transform:translateX(15%)}}",
|
|
1475
|
-
// 十种新增模式的缩微预览:复用主体运动语言,但把节点数和振幅压到卡片内。
|
|
1476
|
-
".dkan-prev-new{--dkan-phase:var(--dk-accent)}.dkan-prev-new i{position:absolute;display:block}",
|
|
1477
|
-
".dkan-prev-nebula i{width:48px;height:48px;border-radius:50%;background:radial-gradient(circle,color-mix(in srgb,var(--dkan-phase) 48%,transparent),transparent 70%);animation:dkan-prev-nebula 3s ease-in-out infinite alternate}.dkan-prev-nebula i:nth-child(1){left:2%;top:-60%}.dkan-prev-nebula i:nth-child(2){right:4%;bottom:-65%;background:radial-gradient(circle,color-mix(in srgb,#a855f7 42%,transparent),transparent 70%);animation-delay:-1.4s}.dkan-prev-nebula i:nth-child(n+3){display:none}@keyframes dkan-prev-nebula{to{transform:translateX(24px) scale(1.18)}}",
|
|
1478
|
-
".dkan-prev-warp i{left:50%;top:50%;width:36px;height:1px;transform-origin:left;background:linear-gradient(90deg,transparent,var(--dkan-phase),#fff);animation:dkan-prev-warp 1.8s linear infinite;opacity:0}.dkan-prev-warp i:nth-child(1){--a:0deg}.dkan-prev-warp i:nth-child(2){--a:45deg;animation-delay:-.2s}.dkan-prev-warp i:nth-child(3){--a:90deg;animation-delay:-.4s}.dkan-prev-warp i:nth-child(4){--a:135deg;animation-delay:-.6s}.dkan-prev-warp i:nth-child(5){--a:180deg;animation-delay:-.8s}.dkan-prev-warp i:nth-child(6){--a:225deg;animation-delay:-1s}.dkan-prev-warp i:nth-child(7){--a:270deg;animation-delay:-1.2s}.dkan-prev-warp i:nth-child(8){--a:315deg;animation-delay:-1.4s}@keyframes dkan-prev-warp{0%{opacity:0;transform:rotate(var(--a)) translateX(2px) scaleX(.1)}35%{opacity:.8}100%{opacity:0;transform:rotate(var(--a)) translateX(40px) scaleX(1.3)}}",
|
|
1479
|
-
".dkan-prev-radar i{left:50%;top:50%;width:24px;height:24px;margin:-12px;border:1px solid color-mix(in srgb,var(--dkan-phase) 48%,transparent);border-radius:50%;animation:dkan-prev-radar 2.6s ease-out infinite;opacity:0}.dkan-prev-radar i:nth-child(2){animation-delay:-.85s}.dkan-prev-radar i:nth-child(3){animation-delay:-1.7s}.dkan-prev-radar i:nth-child(n+4){display:none}@keyframes dkan-prev-radar{0%{opacity:.8;transform:scale(.2)}100%{opacity:0;transform:scale(1.25)}}",
|
|
1480
|
-
".dkan-prev-constellation::after{content:'';position:absolute;left:12%;right:12%;top:50%;height:1px;background:linear-gradient(90deg,transparent,var(--dkan-phase),transparent);transform:rotate(-9deg)}.dkan-prev-constellation i,.dkan-prev-fireflies i{width:4px;height:4px;border-radius:50%;background:var(--dkan-phase);box-shadow:0 0 5px var(--dkan-phase);animation:dkan-prev-twinkle 1.8s ease-in-out infinite alternate}.dkan-prev-constellation i:nth-child(1),.dkan-prev-fireflies i:nth-child(1){left:12%;top:62%}.dkan-prev-constellation i:nth-child(2),.dkan-prev-fireflies i:nth-child(2){left:28%;top:26%;animation-delay:-.3s}.dkan-prev-constellation i:nth-child(3),.dkan-prev-fireflies i:nth-child(3){left:45%;top:52%;animation-delay:-.6s}.dkan-prev-constellation i:nth-child(4),.dkan-prev-fireflies i:nth-child(4){left:61%;top:18%;animation-delay:-.9s}.dkan-prev-constellation i:nth-child(5),.dkan-prev-fireflies i:nth-child(5){left:76%;top:58%;animation-delay:-1.2s}.dkan-prev-constellation i:nth-child(6),.dkan-prev-fireflies i:nth-child(6){left:88%;top:34%;animation-delay:-1.5s}@keyframes dkan-prev-twinkle{to{opacity:.2;transform:translateY(4px) scale(.65)}}",
|
|
1481
|
-
".dkan-prev-ocean i{left:-10%;width:120%;height:18px;border-top:1px solid color-mix(in srgb,#22d3ee 52%,transparent);border-radius:50%;animation:dkan-prev-wave 2.8s ease-in-out infinite alternate}.dkan-prev-ocean i:nth-child(1){top:15%}.dkan-prev-ocean i:nth-child(2){top:35%;animation-delay:-.5s}.dkan-prev-ocean i:nth-child(3){top:55%;animation-delay:-1s}.dkan-prev-ocean i:nth-child(4){top:75%;animation-delay:-1.5s}.dkan-prev-ocean i:nth-child(n+5){display:none}@keyframes dkan-prev-wave{to{transform:translateX(12px) scaleY(1.2)}}",
|
|
1482
|
-
".dkan-prev-prism i{left:-10%;width:120%;height:1px;background:linear-gradient(90deg,transparent,#ef4444,#fbbf24,#22c55e,#22d3ee,#8b5cf6,transparent);transform:rotate(-6deg);animation:dkan-prev-prism 2.8s ease-in-out infinite}.dkan-prev-prism i:nth-child(1){top:18%}.dkan-prev-prism i:nth-child(2){top:34%;animation-delay:-.4s}.dkan-prev-prism i:nth-child(3){top:50%;animation-delay:-.8s}.dkan-prev-prism i:nth-child(4){top:66%;animation-delay:-1.2s}.dkan-prev-prism i:nth-child(5){top:82%;animation-delay:-1.6s}.dkan-prev-prism i:nth-child(n+6){display:none}@keyframes dkan-prev-prism{0%,100%{opacity:.12;transform:translateX(-8px) rotate(-6deg)}50%{opacity:.72;transform:translateX(10px) rotate(-6deg)}}",
|
|
1483
|
-
".dkan-prev-circuit i{height:1px;background:var(--dkan-phase);box-shadow:0 0 4px var(--dkan-phase);animation:dkan-prev-circuit 2.4s linear infinite}.dkan-prev-circuit i:nth-child(1){left:5%;top:30%;width:42%}.dkan-prev-circuit i:nth-child(2){left:47%;top:30%;width:1px;height:45%}.dkan-prev-circuit i:nth-child(3){left:47%;top:74%;width:38%}.dkan-prev-circuit i:nth-child(4){left:68%;top:15%;width:1px;height:60%}.dkan-prev-circuit i:nth-child(5){left:68%;top:15%;width:26%}.dkan-prev-circuit i:nth-child(6){left:12%;top:62%;width:35%}@keyframes dkan-prev-circuit{0%,100%{opacity:.18}50%{opacity:.9}}",
|
|
1484
|
-
".dkan-prev-gravity i{left:50%;top:50%;width:18px;height:18px;margin:-9px;border:1px solid var(--dkan-phase);border-radius:50%;opacity:0;animation:dkan-prev-gravity 2.6s ease-out infinite}.dkan-prev-gravity i:nth-child(2){animation-delay:-.65s}.dkan-prev-gravity i:nth-child(3){animation-delay:-1.3s}.dkan-prev-gravity i:nth-child(4){animation-delay:-1.95s}.dkan-prev-gravity i:nth-child(n+5){display:none}@keyframes dkan-prev-gravity{0%{opacity:.8;transform:scale(.15)}100%{opacity:0;transform:scale(2.4)}}",
|
|
1485
|
-
".dkan-prev-lantern i{bottom:-8px;width:6px;height:8px;border-radius:3px;background:#fbbf24;box-shadow:0 0 5px #f59e0b;animation:dkan-prev-lantern 3.2s linear infinite}.dkan-prev-lantern i:nth-child(1){left:12%}.dkan-prev-lantern i:nth-child(2){left:27%;animation-delay:-.6s}.dkan-prev-lantern i:nth-child(3){left:43%;animation-delay:-1.2s}.dkan-prev-lantern i:nth-child(4){left:61%;animation-delay:-1.8s}.dkan-prev-lantern i:nth-child(5){left:76%;animation-delay:-2.4s}.dkan-prev-lantern i:nth-child(6){left:90%;animation-delay:-2.8s}@keyframes dkan-prev-lantern{0%{opacity:0;transform:translateY(0)}20%{opacity:.9}100%{opacity:0;transform:translateY(-38px)}}",
|
|
1486
|
-
".dkan-prev-bot{height:92px;justify-content:center;}",
|
|
1487
|
-
".dkan-prev-bot .dkan-bot-scene{--dkan-bot-scale:1;transform:scale(.56);transform-origin:center;}",
|
|
1488
|
-
// ===== 桌面伙伴:具象人物 + 紧凑双工位(尺寸可调,整卡可拖拽) =====
|
|
1489
|
-
// 卡片:玻璃拟态;默认停泊右下(dsh 输入卡上方),拖动后位置持久化、可放屏幕任意处
|
|
1490
|
-
".dkan-botcard{--dkan-bot-scale:1.35;position:fixed;right:20px;bottom:calc(var(--dsh-composer-height,152px) + 20px);z-index:9990;display:flex;flex-direction:column;gap:6px;padding:10px 12px;border-radius:16px;cursor:grab;border:1px solid var(--dsw-alias-border-l1);background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 86%,transparent);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);box-shadow:0 12px 36px rgb(0 0 0 / .22);color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:12px;pointer-events:auto;touch-action:none;user-select:none;animation:dkan-rise .3s var(--ds-ease-in-out);}",
|
|
1491
|
-
".dkan-botcard:hover,.dkan-botcard:focus-visible{border-color:var(--dk-accent);outline:none;}",
|
|
1492
|
-
".dkan-botcard.dkan-dragging{cursor:grabbing;animation:none;}",
|
|
1493
|
-
".dkan-botcard .n{color:var(--dsw-alias-label-primary);font-weight:600;}",
|
|
1494
|
-
".dkan-bot-cap{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:18px;}",
|
|
1495
|
-
".dkan-bot-resize{position:absolute;right:7px;bottom:7px;width:15px;height:15px;padding:0;border:0;border-radius:4px;background:linear-gradient(135deg,transparent 0 42%,var(--dsw-alias-label-tertiary) 44% 51%,transparent 53% 62%,var(--dsw-alias-label-tertiary) 64% 71%,transparent 73%);cursor:nwse-resize;touch-action:none;opacity:.72;}",
|
|
1496
|
-
".dkan-bot-resize:hover,.dkan-bot-resize:focus-visible{opacity:1;outline:1px solid var(--dk-accent);outline-offset:1px;}",
|
|
1497
|
-
// ===== 3D 场景(CSS 长方体拼装:世界层统一 3/4 视角——俯视 + 侧身面向镜头) =====
|
|
1498
|
-
".dkan-bot-scene{position:relative;width:calc(220px * var(--dkan-bot-scale,1));height:calc(132px * var(--dkan-bot-scale,1));perspective:calc(620px * var(--dkan-bot-scale,1));perspective-origin:55% 18%;--dk3-side:#7c8ba0;--dk3-side-hi:#a9b8ca;--dk3-side-dk:#4c5870;--dk3-screen:#0d1526;--dkan-code-a:#4ade80;--dkan-code-b:#60a5fa;--dkan-code-c:#fbbf24;}",
|
|
1499
|
-
".dk3-world{position:absolute;left:0;top:0;width:220px;height:132px;transform-style:preserve-3d;transform-origin:top left;transform:scale(var(--dkan-bot-scale,1)) rotateX(-15deg) rotateY(-30deg);}",
|
|
1500
|
-
".dk3-box{position:absolute;transform-style:preserve-3d;}",
|
|
1501
|
-
".dk3-face{position:absolute;left:50%;top:50%;display:block;background:var(--dk3-side);backface-visibility:hidden;border-radius:1px;}",
|
|
1502
|
-
// 金属件着色:顶面亮、底面暗,正/侧面中调(3D 光感)
|
|
1503
|
-
".dk3-metal .dk3-face{background:linear-gradient(180deg,#93a5ba,#71809a);}",
|
|
1504
|
-
".dk3-metal .dk3-face:nth-child(5){background:linear-gradient(180deg,var(--dk3-side-hi),#9dadc2);}",
|
|
1505
|
-
".dk3-metal .dk3-face:nth-child(6){background:var(--dk3-side-dk);}",
|
|
1506
|
-
// 书桌与桌腿
|
|
1507
|
-
".dk3-desk .dk3-face{background:linear-gradient(180deg,#9db0c6,#7f92aa);}",
|
|
1508
|
-
".dk3-desk .dk3-face:nth-child(5){background:#c3d2e2;}",
|
|
1509
|
-
// 显示器(薄盒 + 贴在前面的屏幕;组内扇形微转,整组后撤出桌沿)
|
|
1510
|
-
".dk3-mon3{position:absolute;transform-style:preserve-3d;}",
|
|
1511
|
-
".dk3-frame .dk3-face{background:#3a465b;}",
|
|
1512
|
-
".dk3-frame .dk3-face:nth-child(5){background:#55647d;}",
|
|
1513
|
-
".dk3-screen{position:absolute;left:50%;top:50%;background:var(--dk3-screen);border-radius:2px;overflow:hidden;transition:opacity .4s var(--ds-ease-in-out),box-shadow .4s var(--ds-ease-in-out);}",
|
|
1514
|
-
// 屏幕内容:代码块整体向上滚动(10 行=5 行×2 循环,滚半程无缝接回)+ 闪烁光标,速度随 --dkan-speed
|
|
1515
|
-
".dk3-code{position:absolute;left:0;top:0;right:0;display:block;animation:dk3-scroll calc(5s / var(--dkan-speed,1)) linear infinite;}",
|
|
1516
|
-
".dk3-code i{display:block;height:2px;border-radius:1px;margin:2px 3px 0;background:var(--dkan-code-b);opacity:.55;}",
|
|
1517
|
-
".dk3-code i:nth-child(5n+1){width:58%;background:var(--dkan-code-a);}",
|
|
1518
|
-
".dk3-code i:nth-child(5n+2){width:82%;}",
|
|
1519
|
-
".dk3-code i:nth-child(5n+3){width:46%;background:var(--dkan-code-c);}",
|
|
1520
|
-
".dk3-code i:nth-child(5n+4){width:72%;}",
|
|
1521
|
-
".dk3-code i:nth-child(5n){width:54%;background:var(--dkan-code-a);}",
|
|
1522
|
-
".dk3-cur{position:absolute;left:3px;bottom:2px;width:4px;height:2px;background:var(--dkan-code-b);animation:dkan-blink3 1s steps(1) infinite;}",
|
|
1523
|
-
// 左屏检索视图:搜索框、放大镜与结果条,和右侧代码编辑器明确区分
|
|
1524
|
-
".dk3-search-ui{position:absolute;inset:3px;display:block;overflow:hidden;}",
|
|
1525
|
-
".dk3-search-box{display:block;height:5px;margin:0 1px 3px;border:1px solid color-mix(in srgb,var(--dkan-code-a) 72%,#d7fff4);border-radius:2px;box-sizing:border-box;}",
|
|
1526
|
-
".dk3-search-box b{display:block;width:2px;height:2px;margin:1px 0 0 2px;border:1px solid var(--dkan-code-a);border-radius:50%;position:relative;}",
|
|
1527
|
-
".dk3-search-box b:after{content:'';position:absolute;width:2px;height:1px;background:var(--dkan-code-a);right:-2px;bottom:-1px;transform:rotate(45deg);transform-origin:left center;}",
|
|
1528
|
-
".dk3-search-results{display:block;animation:dk3-search-scroll calc(2.1s / var(--dkan-speed,1)) linear infinite;}",
|
|
1529
|
-
".dk3-search-results i{display:block;height:2px;margin:2px 1px 0;border-radius:1px;background:var(--dkan-code-a);opacity:.72;}",
|
|
1530
|
-
".dk3-search-results i:nth-child(2n){width:74%;background:var(--dkan-code-b);}",
|
|
1531
|
-
".dk3-search-results i:nth-child(3n){width:48%;background:var(--dkan-code-c);}",
|
|
1532
|
-
"@keyframes dk3-search-scroll{to{transform:translateY(-5px)}}",
|
|
1533
|
-
"@keyframes dk3-scroll{to{transform:translateY(-50%)}}",
|
|
1534
|
-
// 人物与椅子始终正对桌面:工位切换只横移,不再旋转整个人和座椅。
|
|
1535
|
-
".dk3-person{--dk3-seat-x:22px;position:absolute;left:98px;top:14px;width:44px;height:61px;transform-style:preserve-3d;transform:translateZ(30px) translateX(var(--dk3-seat-x));transition:transform max(.18s,calc(.38s / var(--dkan-speed,1))) cubic-bezier(.23,1,.32,1);will-change:transform;}",
|
|
1536
|
-
".dkan-bot-scene[data-station=left] .dk3-person{--dk3-seat-x:0px;}",
|
|
1537
|
-
".dkan-bot-scene[data-station=center] .dk3-person{--dk3-seat-x:22px;}",
|
|
1538
|
-
".dkan-bot-scene[data-station=right] .dk3-person{--dk3-seat-x:44px;}",
|
|
1539
|
-
// 上身以髋部为旋转原点;只做前后倾,始终朝向桌面。
|
|
1540
|
-
".dk3-upper3{position:absolute;inset:0;transform-style:preserve-3d;transform-origin:12px 52px;transform:translateZ(0);transition:transform .24s cubic-bezier(.77,0,.175,1);will-change:transform;}",
|
|
1541
|
-
// 动漫人物配色:皮肤/头发/卫衣/裤子
|
|
1542
|
-
".dk3-skin .dk3-face{background:linear-gradient(180deg,#ffd9b8,#f3b98d);}",
|
|
1543
|
-
".dk3-skin .dk3-face:nth-child(5){background:#ffe3c9;}",
|
|
1544
|
-
".dk3-hair .dk3-face{background:linear-gradient(180deg,#5b4632,#3f3021);}",
|
|
1545
|
-
".dk3-hair .dk3-face:nth-child(5){background:#6b543c;}",
|
|
1546
|
-
".dk3-hood .dk3-face{background:linear-gradient(180deg,#7c8ba0,#5b6a80);}",
|
|
1547
|
-
".dk3-hood .dk3-face:nth-child(5){background:#93a5ba;}",
|
|
1548
|
-
".dk3-pants .dk3-face{background:linear-gradient(180deg,#3d4a5c,#2c3646);}",
|
|
1549
|
-
".dk3-pants .dk3-face:nth-child(5){background:#4a5a70;}",
|
|
1550
|
-
// 人物细节:收窄下颌、连续肩颈、短侧渐层发型
|
|
1551
|
-
".dk3-cranium .dk3-face{border-radius:42% 46% 38% 36%;}",
|
|
1552
|
-
".dk3-jaw3 .dk3-face{border-radius:30% 30% 48% 48%;}",
|
|
1553
|
-
".dk3-neck3 .dk3-face{border-radius:2px;}",
|
|
1554
|
-
".dk3-collar3 .dk3-face{background:linear-gradient(180deg,#516176,#374459);border-radius:3px;}",
|
|
1555
|
-
".dk3-shoulders3 .dk3-face{border-radius:4px;}",
|
|
1556
|
-
".dk3-chest3 .dk3-face{border-radius:4px;}",
|
|
1557
|
-
".dk3-waist3 .dk3-face{border-radius:3px;}",
|
|
1558
|
-
".dk3-hair .dk3-face{border-radius:2px;}",
|
|
1559
|
-
".dk3-hair-crown .dk3-face{border-radius:48% 42% 28% 30%;}",
|
|
1560
|
-
".dk3-hair-back .dk3-face{background:linear-gradient(180deg,#493727,#2f241b);border-radius:46% 24% 38% 44%;}",
|
|
1561
|
-
".dk3-shoe .dk3-face{background:#20262f;border-radius:2px;}",
|
|
1562
|
-
".dk3-mug .dk3-face{background:#c2703d;border-radius:1px;}",
|
|
1563
|
-
".dk3-mug .dk3-face:nth-child(5){background:#e08a52;}",
|
|
1564
|
-
".dk3-mouth{position:absolute;left:50%;top:64%;width:3.5px;height:1px;border-radius:2px;background:#a9635c;}",
|
|
1565
|
-
// 椅子
|
|
1566
|
-
".dk3-chairback .dk3-face{background:linear-gradient(180deg,#3f4c60,#2c3648);}",
|
|
1567
|
-
".dk3-chairseat .dk3-face{background:#33415a;}",
|
|
1568
|
-
// 头组与颈部中心均为 x=14;只让头部观察左右屏,身体和椅子不转向。
|
|
1569
|
-
".dk3-head3{--dk3-head-turn:rotateY(0deg);position:absolute;left:6px;top:3px;width:17px;height:21px;transform-style:preserve-3d;transform-origin:8px 18px;transform:var(--dk3-head-turn);transition:transform max(.18s,calc(.22s / var(--dkan-speed,1))) cubic-bezier(.23,1,.32,1);will-change:transform;}",
|
|
1570
|
-
".dkan-bot-scene[data-station=left] .dk3-head3{--dk3-head-turn:rotateY(-18deg);}",
|
|
1571
|
-
".dkan-bot-scene[data-station=center] .dk3-head3{--dk3-head-turn:rotateY(0deg);}",
|
|
1572
|
-
".dkan-bot-scene[data-station=right] .dk3-head3{--dk3-head-turn:rotateY(14deg);}",
|
|
1573
|
-
".dk3-brows{position:absolute;left:50%;top:29%;display:flex;gap:2.5px;align-items:center;justify-content:center;}",
|
|
1574
|
-
".dk3-brows i{width:3.5px;height:1px;border-radius:2px;background:#3a2a21;transform:rotate(-7deg);}",
|
|
1575
|
-
".dk3-brows i+ i{transform:rotate(7deg);}",
|
|
1576
|
-
".dk3-eyes{position:absolute;left:50%;top:51%;display:flex;gap:2.5px;align-items:center;justify-content:center;}",
|
|
1577
|
-
".dk3-eyes i{width:3.5px;height:2.5px;border-radius:50%;background:radial-gradient(circle at 62% 30%,#fff 0 .5px,#263141 .8px 1.7px,#111827 1.9px);animation:dkan-blink3 4.8s infinite;}",
|
|
1578
|
-
".dk3-nose{position:absolute;left:50%;top:66%;width:2px;height:3px;border-radius:60% 40% 55% 45%;background:linear-gradient(90deg,#e7a67e,#c77f67);}",
|
|
1579
|
-
".dk3-ear{position:absolute;left:1px;top:10px;width:3px;height:5px;border-radius:52% 44% 48% 52%;background:linear-gradient(90deg,#edaa82,#ffd2ad);transform:translateZ(2px);}",
|
|
1580
|
-
".dk3-fringe3{position:absolute;left:13px;top:4px;width:5px;height:7px;transform-style:preserve-3d;transform:rotateY(90deg) translateZ(5.8px);}",
|
|
1581
|
-
".dk3-fringe3 i{position:absolute;top:0;width:2px;height:6px;border-radius:70% 25% 60% 30%;background:linear-gradient(180deg,#6b5239,#392b20);transform-origin:top center;}",
|
|
1582
|
-
".dk3-fringe3 i:nth-child(1){left:0;transform:rotate(-18deg);height:5px;}",
|
|
1583
|
-
".dk3-fringe3 i:nth-child(2){left:1.8px;transform:rotate(-6deg);height:6px;}",
|
|
1584
|
-
".dk3-fringe3 i:nth-child(3){left:3.5px;transform:rotate(12deg);height:4px;}",
|
|
1585
|
-
"@keyframes dkan-blink3{0%,91%,100%{transform:scaleY(1)}94%{transform:scaleY(.15)}}",
|
|
1586
|
-
// 手臂按肩→肘→腕分段,旋转原点位于真实关节连接处。
|
|
1587
|
-
".dk3-arm3{position:absolute;left:20px;top:30px;width:16px;height:16px;transform-style:preserve-3d;transform-origin:2px 2px;will-change:transform;}",
|
|
1588
|
-
".dk3-elbow{position:absolute;left:0;top:10px;width:15px;height:5px;transform-style:preserve-3d;transform-origin:1px 2px;will-change:transform;}",
|
|
1589
|
-
".dk3-wrist3{position:absolute;left:9.5px;top:0;width:6px;height:5px;transform-style:preserve-3d;transform-origin:1px 2px;will-change:transform;}",
|
|
1590
|
-
".dk3-fingers3{position:absolute;left:3px;top:1px;width:4px;height:3px;transform:translateZ(2px);}",
|
|
1591
|
-
".dk3-fingers3 i{position:absolute;left:0;width:4px;height:.8px;border-radius:2px;background:#f2b58d;}",
|
|
1592
|
-
".dk3-fingers3 i+ i{top:1.4px;width:3.5px;}",
|
|
1593
|
-
".dk3-far{transform:translateZ(-7px);filter:brightness(.72);}",
|
|
1594
|
-
// 思考泡泡(贴镜头 2D 层,think 阶段显示)
|
|
1595
|
-
".dkan-bubble{position:absolute;left:80px;top:4px;display:none;gap:3px;padding:4px 6px;border-radius:8px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);box-shadow:0 2px 6px rgb(0 0 0 / .15);z-index:3;}",
|
|
1596
|
-
".dkan-bubble i{width:4px;height:4px;border-radius:50%;background:var(--dsw-alias-label-tertiary);animation:dkan-bubdot 1.2s ease-in-out infinite;}",
|
|
1597
|
-
".dkan-bubble i:nth-child(2){animation-delay:.2s;}",
|
|
1598
|
-
".dkan-bubble i:nth-child(3){animation-delay:.4s;}",
|
|
1599
|
-
"@keyframes dkan-bubdot{0%,100%{opacity:.25;transform:translateY(0)}50%{opacity:1;transform:translateY(-2px)}}",
|
|
1600
|
-
// ===== 多任务工位(data-tasks=活跃任务数,上限 3) =====
|
|
1601
|
-
// 椅子滚轮
|
|
1602
|
-
".dk3-wheel .dk3-face{background:#1c232e;border-radius:50%;}",
|
|
1603
|
-
// 有活跃任务时,未轮到的侧工位稍暗;多任务则两个工位均保持可见。
|
|
1604
|
-
".dkan-bot-scene[data-tasks] .dk3-mon3.left .dk3-screen,.dkan-bot-scene[data-tasks] .dk3-mon3.right .dk3-screen{opacity:.3;}",
|
|
1605
|
-
// 2 个及以上任务:左右工位同时点亮(各自带辉光、代码滚动加速)。
|
|
1606
|
-
".dkan-bot-scene[data-tasks=\"2\"] .dk3-mon3.left .dk3-screen,.dkan-bot-scene[data-tasks=\"2\"] .dk3-mon3.right .dk3-screen,.dkan-bot-scene[data-tasks=\"3\"] .dk3-mon3.left .dk3-screen,.dkan-bot-scene[data-tasks=\"3\"] .dk3-mon3.right .dk3-screen{opacity:1;box-shadow:0 0 8px color-mix(in srgb,var(--dkan-code-b,#60a5fa) 42%,transparent);}",
|
|
1607
|
-
".dkan-bot-scene[data-tasks=\"2\"] .dk3-mon3.left .dk3-code,.dkan-bot-scene[data-tasks=\"2\"] .dk3-mon3.right .dk3-code,.dkan-bot-scene[data-tasks=\"3\"] .dk3-mon3.left .dk3-code,.dkan-bot-scene[data-tasks=\"3\"] .dk3-mon3.right .dk3-code{animation-duration:calc(2.6s / var(--dkan-speed,1));}",
|
|
1608
|
-
// 座椅移动期间滚轮跟随任务节奏转动;思考中停下,避免无意义的常驻运动。
|
|
1609
|
-
".dkan-bot-scene[data-station=left] .dk3-wheel,.dkan-bot-scene[data-station=right] .dk3-wheel{animation:dkan-wheel calc(.46s / var(--dkan-speed,1)) linear infinite;transform-origin:center;}",
|
|
1610
|
-
"@keyframes dkan-wheel{to{transform:rotateZ(360deg)}}",
|
|
1611
|
-
// ===== 阶段驱动(data-phase 四态,host 由 chunk 流实时同步) =====
|
|
1612
|
-
// think:上身轻靠、近侧手托下巴、远侧手留在桌面;动作有停顿而不是机械往返。
|
|
1613
|
-
".dkan-bot-scene[data-phase=think] .dk3-screen{opacity:.32;}",
|
|
1614
|
-
".dkan-bot-scene[data-phase=think] .dkan-bubble{display:inline-flex;}",
|
|
1615
|
-
".dkan-bot-scene[data-phase=think] .dk3-upper3{animation:dkan-think-upper max(1.8s,calc(2.4s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1616
|
-
".dkan-bot-scene[data-phase=think] .dk3-head3{animation:dkan-think-head max(1.8s,calc(2.4s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1617
|
-
".dkan-bot-scene[data-phase=think] .dk3-arm3.dk3-near{animation:dkan-think-near max(1.8s,calc(2.4s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1618
|
-
".dkan-bot-scene[data-phase=think] .dk3-arm3.dk3-far{transform:translateZ(-7px) rotateZ(3deg);}",
|
|
1619
|
-
".dkan-bot-scene[data-phase=think] .dk3-near .dk3-elbow{transform:rotateZ(-28deg) translateY(-1px);}",
|
|
1620
|
-
".dkan-bot-scene[data-phase=think] .dk3-near .dk3-wrist3{transform:rotateZ(-18deg) translateY(-1px);}",
|
|
1621
|
-
"@keyframes dkan-think-upper{0%,44%{transform:rotateZ(-1deg) translateY(0)}62%,100%{transform:rotateZ(-1.8deg) translateY(-.5px)}}",
|
|
1622
|
-
"@keyframes dkan-think-head{0%,44%{transform:var(--dk3-head-turn) rotateZ(-3deg)}62%,100%{transform:var(--dk3-head-turn) rotateZ(-6deg) translateY(-.5px)}}",
|
|
1623
|
-
"@keyframes dkan-think-near{0%,44%{transform:rotateZ(-31deg) translate(-8px,-14.5px)}62%,100%{transform:rotateZ(-34deg) translate(-8.5px,-15px)}}",
|
|
1624
|
-
// write/code:身体仍面向桌面,仅轻微前倾;主要由手腕和手指敲击。
|
|
1625
|
-
".dkan-bot-scene[data-phase=write] .dk3-mon3.right .dk3-screen,.dkan-bot-scene[data-phase=code] .dk3-mon3.right .dk3-screen{opacity:1;box-shadow:0 0 11px color-mix(in srgb,var(--dkan-code-b,#60a5fa) 52%,transparent);}",
|
|
1626
|
-
".dkan-bot-scene[data-phase=write] .dk3-mon3.right .dk3-code,.dkan-bot-scene[data-phase=code] .dk3-mon3.right .dk3-code{animation-duration:calc(1.35s / var(--dkan-speed,1));}",
|
|
1627
|
-
".dkan-bot-scene[data-phase=write] .dk3-upper3,.dkan-bot-scene[data-phase=code] .dk3-upper3{animation:dkan-code-upper max(1.2s,calc(2.4s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1628
|
-
".dkan-bot-scene[data-phase=write] .dk3-arm3.dk3-near,.dkan-bot-scene[data-phase=code] .dk3-arm3.dk3-near{transform:rotateZ(3deg);}",
|
|
1629
|
-
".dkan-bot-scene[data-phase=write] .dk3-arm3.dk3-far,.dkan-bot-scene[data-phase=code] .dk3-arm3.dk3-far{transform:translateZ(-7px) rotateZ(1deg);}",
|
|
1630
|
-
".dkan-bot-scene[data-phase=write] .dk3-elbow,.dkan-bot-scene[data-phase=code] .dk3-elbow{animation:dkan-type-elbow max(.28s,calc(.58s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1631
|
-
".dkan-bot-scene[data-phase=write] .dk3-wrist3,.dkan-bot-scene[data-phase=code] .dk3-wrist3{animation:dkan-type-wrist max(.18s,calc(.29s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1632
|
-
".dkan-bot-scene[data-phase=write] .dk3-far .dk3-elbow,.dkan-bot-scene[data-phase=code] .dk3-far .dk3-elbow{animation-delay:-.09s;animation-duration:max(.3s,calc(.63s / var(--dkan-speed,1)));}",
|
|
1633
|
-
".dkan-bot-scene[data-phase=write] .dk3-far .dk3-wrist3,.dkan-bot-scene[data-phase=code] .dk3-far .dk3-wrist3{animation-delay:-.09s;animation-duration:max(.2s,calc(.33s / var(--dkan-speed,1)));}",
|
|
1634
|
-
".dkan-bot-scene[data-phase=write] .dk3-head3,.dkan-bot-scene[data-phase=code] .dk3-head3{animation:dkan-output-head max(1.2s,calc(2.4s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1635
|
-
"@keyframes dkan-code-upper{0%,38%{transform:rotateZ(2deg) translate(1px,0)}62%,100%{transform:rotateZ(3deg) translate(1px,1px)}}",
|
|
1636
|
-
"@keyframes dkan-type-elbow{0%,38%{transform:rotateZ(-4deg)}62%,100%{transform:rotateZ(5deg) translateY(1px)}}",
|
|
1637
|
-
"@keyframes dkan-type-wrist{0%,34%{transform:rotateZ(-2deg) translateY(0)}62%,100%{transform:rotateZ(3deg) translateY(1.5px)}}",
|
|
1638
|
-
"@keyframes dkan-output-head{0%,46%,100%{transform:var(--dk3-head-turn) rotateZ(3deg)}58%{transform:var(--dk3-head-turn) rotateZ(5deg) translateY(1px)}}",
|
|
1639
|
-
// search:身体正对桌面,头看左屏,近侧手腕小幅操作鼠标。
|
|
1640
|
-
".dkan-bot-scene[data-phase=search] .dk3-mon3.left .dk3-screen{opacity:1;box-shadow:0 0 10px color-mix(in srgb,var(--dkan-code-a,#4ade80) 52%,transparent);}",
|
|
1641
|
-
".dkan-bot-scene[data-phase=search] .dk3-mon3.left .dk3-search-results{animation-duration:calc(1.05s / var(--dkan-speed,1));}",
|
|
1642
|
-
".dkan-bot-scene[data-phase=search] .dk3-upper3{transform:rotateZ(2deg) translateY(1px);}",
|
|
1643
|
-
".dkan-bot-scene[data-phase=search] .dk3-head3{animation:dkan-search-head max(.72s,calc(1.15s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1644
|
-
".dkan-bot-scene[data-phase=search] .dk3-near .dk3-wrist3{animation:dkan-search-wrist max(.45s,calc(.72s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1645
|
-
".dkan-bot-scene[data-phase=search] .dk3-far{transform:translateZ(-7px) rotateZ(2deg);}",
|
|
1646
|
-
"@keyframes dkan-search-head{0%,28%{transform:var(--dk3-head-turn) rotateZ(1deg)}48%,68%{transform:var(--dk3-head-turn) rotateZ(6deg) translate(1px,1px)}88%,100%{transform:var(--dk3-head-turn) rotateZ(2deg)}}",
|
|
1647
|
-
"@keyframes dkan-search-wrist{0%,34%{transform:translateX(-1px)}62%,100%{transform:translateX(1.5px)}}",
|
|
1648
|
-
// 减少动态时停止空间位移,保留屏幕亮暗与思考状态的淡入反馈。
|
|
1649
|
-
"@media (prefers-reduced-motion:reduce){.dkan-amb *,.dkan-prev *,.dkan-space-system,.dkan-space-dust,.dkan-space-runner,.dkan-delivery,.dkan-departure,.dkan-freighter-engine i,.dkan-stuck-code::after,.dkan-stuck-code i{animation:none!important}.dkan-space-runner,.dkan-departure{display:none}.dkan-delivery{opacity:1;transform:translate3d(var(--dkan-to-x),var(--dkan-to-y),0)}.dkan-delivery-trail{display:none}.dk3-person,.dk3-upper3,.dk3-head3,.dk3-arm3,.dk3-elbow,.dk3-wrist3,.dk3-wheel,.dk3-code,.dk3-search-results{animation:none!important;transition:none!important}.dk3-screen,.dkan-bubble{transition:opacity .2s cubic-bezier(.23,1,.32,1)!important}}",
|
|
1650
|
-
].join("\n");
|
|
1651
|
-
|
|
1652
|
-
export const feature = {
|
|
1653
|
-
id: "animation",
|
|
1654
|
-
name: "任务动画",
|
|
1655
|
-
order: 130,
|
|
1656
|
-
accent: "#f472b6",
|
|
1657
|
-
description: "19 种任务运行动画:速度随任务活动联动,配置持久化(通知见「任务通知」,任务状态见「运行状态」)",
|
|
1658
|
-
css,
|
|
1659
|
-
View: AnimationView,
|
|
1660
|
-
HomeStat: AnimationStat,
|
|
1661
|
-
Overlay: AnimationOverlay,
|
|
1662
|
-
};
|
|
1
|
+
// dsh-dock · 功能模块【任务动画】· 客户端视图
|
|
2
|
+
//
|
|
3
|
+
// 三部分:
|
|
4
|
+
// 1. View —— 功能坞面板页:动画开关 + 19 种模式选择(带缩微预览);
|
|
5
|
+
// 2. HomeStat —— 首页总揽概要(当前动效模式 + 开关状态);
|
|
6
|
+
// 3. Overlay —— 全局浮层(shell.overlay 常驻,功能启用即挂载):轮询 Host 状态,
|
|
7
|
+
// 任务进行中渲染克制的动效(顶部流光细线 / 呼吸光点 / 轨道光环)+ 右下角状态徽标,
|
|
8
|
+
// 完成瞬间一缕流光掠过(星际远征另有货运舰归航)。
|
|
9
|
+
//
|
|
10
|
+
// 设计取向:不做满屏粒子彩带,动效全部走主题变量(暗/亮色自适应)、低透明度、慢节奏。
|
|
11
|
+
// 通知(页内卡片 / 提示音 / 系统通知 / 钉钉飞书推送)独立在 features/notify/,
|
|
12
|
+
// 运行状态(进行中任务与最近完成)独立在 features/runstate/:三个功能各自启停、互不依赖,
|
|
13
|
+
// 会话级任务追踪在宿主侧共用一份。
|
|
14
|
+
//
|
|
15
|
+
// Host 通信:fetch('/dsh-dock/animation/<method>')(见 features/animation/host.js)。
|
|
16
|
+
import { useState, useEffect, useRef, useCallback, useMemo } from "react";
|
|
17
|
+
import { openPanel, panelNav, subscribePanel } from "../../src/shared.js";
|
|
18
|
+
|
|
19
|
+
// ---------- Host RPC 桥接 ----------
|
|
20
|
+
function rpcCall(method, args) {
|
|
21
|
+
return fetch("/dsh-dock/animation/" + method, {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: { "content-type": "application/json" },
|
|
24
|
+
body: JSON.stringify(args === undefined ? {} : args),
|
|
25
|
+
})
|
|
26
|
+
.then(async (res) => {
|
|
27
|
+
const data = await res.json().catch(() => ({}));
|
|
28
|
+
if (res.ok && data && data.ok === true) return data.data;
|
|
29
|
+
// 宿主侧没有动画路由(405/404):宿主进程可能是旧版本,或本功能在宿主侧未 setup
|
|
30
|
+
if (res.status === 405 || res.status === 404) {
|
|
31
|
+
throw new Error("宿主侧没有「任务动画」接口:宿主进程可能是旧版本,或该功能在宿主侧未启用(重启 dsh web,或把功能坞里的开关关掉再打开)");
|
|
32
|
+
}
|
|
33
|
+
if (data && data.ok === false) throw new Error((data.error && data.error.message) || ("HTTP " + res.status));
|
|
34
|
+
throw new Error("HTTP " + res.status + (data && data.error && data.error.message ? ": " + data.error.message : ""));
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ---------- 动画模式 ----------
|
|
39
|
+
const EFFECT_MODES = [
|
|
40
|
+
{ id: "flow", name: "流光细线", desc: "顶部细线流光往返,速度随任务吞吐加快" },
|
|
41
|
+
{ id: "breathe", name: "呼吸光点", desc: "醒目呼吸光点带扩散光晕:颜色随任务阶段(思考蓝/输出绿/代码橙/查资料青),呼吸随吞吐加快" },
|
|
42
|
+
{ id: "ring", name: "轨道光环", desc: "细环绕圆点旋转,转速随任务速度" },
|
|
43
|
+
{ id: "orbit", name: "环屏巡航", desc: "一颗光点沿屏幕边缘巡航整圈,醒目不遮挡" },
|
|
44
|
+
{ id: "robot", name: "桌面伙伴", desc: "真人化双工位:查资料看左屏、写入/改代码移到右屏;座椅和转头随实时任务节奏加速" },
|
|
45
|
+
{ id: "matrix", name: "代码雨", desc: "字符沿屏幕缓落如数据流,速度随任务吞吐,克制的低透明度" },
|
|
46
|
+
{ id: "stars", name: "星野", desc: "细碎星点缓慢飘移闪烁,安静耐看的背景氛围" },
|
|
47
|
+
{ id: "aurora", name: "极光", desc: "屏幕顶部柔光带缓慢呼吸流动,像极光拂过" },
|
|
48
|
+
{ id: "space", name: "星际远征", desc: "任务中星球与飞船巡航;完成后货运旗舰携实际输出量停靠输入框" },
|
|
49
|
+
{ id: "nebula", name: "星云潮汐", desc: "四团彩色星云在屏幕边缘缓慢潮汐,阶段色随任务改变" },
|
|
50
|
+
{ id: "warp", name: "曲速航道", desc: "光束从屏幕中心向外拉伸,吞吐越高曲速越快" },
|
|
51
|
+
{ id: "radar", name: "量子雷达", desc: "三座边缘雷达持续扫描,任务活动化作跳动信标" },
|
|
52
|
+
{ id: "constellation", name: "星座网络", desc: "节点与连线逐段点亮,像任务知识图谱在生长" },
|
|
53
|
+
{ id: "fireflies", name: "数据萤火", desc: "轻盈光点在空白区游弋,活跃阶段会更明亮" },
|
|
54
|
+
{ id: "ocean", name: "深海脉动", desc: "低透明度波层与气泡缓慢上浮,安静但富有生命感" },
|
|
55
|
+
{ id: "prism", name: "棱镜光谱", desc: "彩色光束从屏幕边缘折射穿行,形成克制的玻璃光感" },
|
|
56
|
+
{ id: "circuit", name: "神经电路", desc: "电路路径依次通电,脉冲速度跟随代码与工具活动" },
|
|
57
|
+
{ id: "gravity", name: "引力涟漪", desc: "多个引力源向外扩散波纹,任务越快涟漪越紧密" },
|
|
58
|
+
{ id: "lantern", name: "灵感天灯", desc: "微型灵感灯从屏幕底部缓慢升起,留下温暖光迹" },
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const AMBIENT_EFFECT_MODES = new Set(["matrix", "stars", "aurora", "space", "nebula", "warp", "radar", "constellation", "fireflies", "ocean", "prism", "circuit", "gravity", "lantern"]);
|
|
62
|
+
|
|
63
|
+
// 任务阶段 → 机器人行为/文案(host 按 chunk/事件实时推导:
|
|
64
|
+
// reasoning-delta=think、text-delta=write、tool 名分类=search/code)
|
|
65
|
+
const PHASE_LABELS = { think: "思考中", write: "输出中", code: "编写代码", search: "查资料" };
|
|
66
|
+
function phaseLabel(p) { return PHASE_LABELS[p] || "工作中"; }
|
|
67
|
+
// 阶段配色(徽标呼吸点/光环随任务阶段变色,--dkan-phase 下发到 CSS)
|
|
68
|
+
const PHASE_COLORS = { think: "#2f6fed", write: "#0d9488", code: "#b45309", search: "#0e7490" };
|
|
69
|
+
function phaseColor(p) { return PHASE_COLORS[p] || "#2f6fed"; }
|
|
70
|
+
|
|
71
|
+
// ---------- 结束原因 ----------
|
|
72
|
+
// (结束原因的展示文案随「运行状态」页一起搬去 features/runstate/,这里只保留动效判断用的成功判定)
|
|
73
|
+
function isSuccessReason(reason) {
|
|
74
|
+
return !reason || reason === "completed";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---------- 格式化 ----------
|
|
78
|
+
function fmtCompact(n) {
|
|
79
|
+
n = Number(n) || 0;
|
|
80
|
+
if (n >= 1e6) return (n / 1e6).toFixed(2) + "M";
|
|
81
|
+
if (n >= 1e3) return (n / 1e3).toFixed(1) + "K";
|
|
82
|
+
return String(Math.round(n));
|
|
83
|
+
}
|
|
84
|
+
// 毫秒 → "12:34" / "1:02:11"(徽标计时)
|
|
85
|
+
function fmtClock(ms) {
|
|
86
|
+
const s = Math.max(0, Math.floor((ms || 0) / 1000));
|
|
87
|
+
const h = Math.floor(s / 3600), m = Math.floor((s % 3600) / 60), sec = s % 60;
|
|
88
|
+
const p = (x) => String(x).padStart(2, "0");
|
|
89
|
+
return h > 0 ? h + ":" + p(m) + ":" + p(sec) : p(m) + ":" + p(sec);
|
|
90
|
+
}
|
|
91
|
+
function truncate(str, max) {
|
|
92
|
+
if (!str) return "";
|
|
93
|
+
return String(str).length > max ? String(str).slice(0, max) + "…" : str;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------- 共享快照:浮层(轮询) / 面板页 / 首页概要共用一份数据 ----------
|
|
97
|
+
const animationStore = {
|
|
98
|
+
snap: { status: null, loading: false, error: null },
|
|
99
|
+
listeners: new Set(),
|
|
100
|
+
subscribe(fn) { this.listeners.add(fn); return () => { this.listeners.delete(fn); }; },
|
|
101
|
+
emit() { for (const fn of this.listeners) fn(); },
|
|
102
|
+
applyConfig(cfg) {
|
|
103
|
+
if (this.snap.status) {
|
|
104
|
+
this.snap = Object.assign({}, this.snap, { status: Object.assign({}, this.snap.status, { config: cfg }) });
|
|
105
|
+
this.emit();
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
refresh() {
|
|
109
|
+
if (this.snap.loading) return Promise.resolve();
|
|
110
|
+
this.snap = Object.assign({}, this.snap, { loading: true });
|
|
111
|
+
this.emit();
|
|
112
|
+
return rpcCall("status")
|
|
113
|
+
.then((data) => {
|
|
114
|
+
this.snap = { status: data, loading: false, error: null };
|
|
115
|
+
this.emit();
|
|
116
|
+
})
|
|
117
|
+
.catch((e) => {
|
|
118
|
+
this.snap = Object.assign({}, this.snap, { loading: false, error: (e && e.message) || String(e) });
|
|
119
|
+
this.emit();
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
function useAnimation() {
|
|
124
|
+
const [snap, setSnap] = useState(animationStore.snap);
|
|
125
|
+
useEffect(() => animationStore.subscribe(() => setSnap(animationStore.snap)), []);
|
|
126
|
+
return snap;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 秒级滴答(徽标计时用;ctx.interval 优先,退回 setInterval)
|
|
130
|
+
function useTicker(ctx, active) {
|
|
131
|
+
const [, bump] = useState(0);
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (!active) return;
|
|
134
|
+
if (ctx && typeof ctx.interval === "function") return ctx.interval(bump, 1000);
|
|
135
|
+
const t = setInterval(bump, 1000);
|
|
136
|
+
return () => clearInterval(t);
|
|
137
|
+
}, [active]);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ---------- 桌面场景(CSS 3D:长方体拼装的 3D 动漫人物,侧身面向镜头、面朝三屏) ----------
|
|
141
|
+
// data-phase 驱动四态(think/write/code/search,host 由 assistant/chunk 流级事件实时同步);
|
|
142
|
+
// --dkan-speed 随任务吞吐加速;卡片可整卡自由拖拽(位置 localStorage 持久化)。
|
|
143
|
+
// Box3 = 六面长方体(w×h×d),faces: 前(+Z)/后/右(+X)/左/顶/底;世界层统一转角取 3/4 视角。
|
|
144
|
+
function Box3(props) {
|
|
145
|
+
const w = props.w, h = props.h, d = props.d;
|
|
146
|
+
const faces = [
|
|
147
|
+
[w, h, "translate(-50%,-50%) translateZ(" + (d / 2) + "px)"],
|
|
148
|
+
[w, h, "translate(-50%,-50%) rotateY(180deg) translateZ(" + (d / 2) + "px)"],
|
|
149
|
+
[d, h, "translate(-50%,-50%) rotateY(90deg) translateZ(" + (w / 2) + "px)"],
|
|
150
|
+
[d, h, "translate(-50%,-50%) rotateY(-90deg) translateZ(" + (w / 2) + "px)"],
|
|
151
|
+
[w, d, "translate(-50%,-50%) rotateX(90deg) translateZ(" + (h / 2) + "px)"],
|
|
152
|
+
[w, d, "translate(-50%,-50%) rotateX(-90deg) translateZ(" + (h / 2) + "px)"],
|
|
153
|
+
];
|
|
154
|
+
return (
|
|
155
|
+
<span className={"dk3-box " + (props.cls || "")}
|
|
156
|
+
style={Object.assign({ width: w, height: h, left: props.x - w / 2, top: props.y - h / 2 },
|
|
157
|
+
props.z ? { transform: "translateZ(" + props.z + "px)" } : null, props.style)}>
|
|
158
|
+
{faces.map((f, i) => (
|
|
159
|
+
<span key={i} className="dk3-face" style={{ width: f[0], height: f[1], transform: f[2] }} />
|
|
160
|
+
))}
|
|
161
|
+
{props.children}
|
|
162
|
+
</span>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function Monitor3(props) {
|
|
167
|
+
const w = props.w, h = props.h;
|
|
168
|
+
const searchScreen = props.mode === "search";
|
|
169
|
+
// 屏幕内容:10 行代码(5 行×2 循环)+ 光标,整体向上滚动(速度随 --dkan-speed)
|
|
170
|
+
const lines = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5];
|
|
171
|
+
return (
|
|
172
|
+
<span className={"dk3-mon3 " + (props.cls || "")}
|
|
173
|
+
style={{ left: props.x - w / 2, top: props.y - h / 2, width: w, height: h, transform: "translateZ(" + (props.z || -3) + "px) rotateY(" + (props.ry || 0) + "deg)" }}>
|
|
174
|
+
<Box3 w={w} h={h} d={5} cls="dk3-frame" x={w / 2} y={h / 2} />
|
|
175
|
+
<span className="dk3-screen" style={{ width: w - 4, height: h - 4, transform: "translate(-50%,-50%) translateZ(3.1px)" }}>
|
|
176
|
+
{searchScreen ? <span className="dk3-search-ui">
|
|
177
|
+
<span className="dk3-search-box"><b /></span>
|
|
178
|
+
<span className="dk3-search-results">{lines.slice(0, 6).map((n, i) => <i key={i} />)}</span>
|
|
179
|
+
</span> : <span className="dk3-code">
|
|
180
|
+
{lines.map((n, i) => <i key={i} />)}
|
|
181
|
+
<span className="dk3-cur" />
|
|
182
|
+
</span>}
|
|
183
|
+
</span>
|
|
184
|
+
</span>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// 3D 动漫人物:更拟人的比例与细节(圆角头/ volumetric 头发/五官/脖颈/弯肘/腿脚鞋)。
|
|
189
|
+
// 紧凑双工位:左侧专做资料检索,右侧专做写作/改文件;顶部小屏放任务状态。
|
|
190
|
+
// 人物坐滚轮椅在两侧工位间滑动,思考时停在两屏之间。
|
|
191
|
+
function RobotScene(props) {
|
|
192
|
+
const phase = props && props.phase ? props.phase : "code";
|
|
193
|
+
const tasks = Array.isArray(props && props.tasks) ? props.tasks : null;
|
|
194
|
+
const n = tasks ? Math.max(1, Math.min(3, tasks.length)) : 1;
|
|
195
|
+
// 左工位只做资料检索;正文输出和工具写入都在右工位,思考回到中位。
|
|
196
|
+
const station = phase === "search" ? "left" : (phase === "write" || phase === "code" ? "right" : "center");
|
|
197
|
+
return (
|
|
198
|
+
<div className="dkan-bot-scene" data-phase={phase} data-station={station} data-tasks={tasks ? String(n) : undefined} aria-hidden="true">
|
|
199
|
+
<div className="dk3-world">
|
|
200
|
+
{/* 紧凑单人书桌:只留左右两个真实工作位,消除左侧空桌面。 */}
|
|
201
|
+
<Box3 w={134} h={7} d={38} cls="dk3-desk" x={145} y={76} />
|
|
202
|
+
<Box3 w={5} h={24} d={30} cls="dk3-metal dk3-leg" x={84} y={89} />
|
|
203
|
+
<Box3 w={5} h={24} d={30} cls="dk3-metal dk3-leg" x={206} y={89} />
|
|
204
|
+
{/* 左检索、右编码,顶部小屏显示任务进度。 */}
|
|
205
|
+
<Monitor3 w={34} h={24} x={140} y={59} ry={20} cls="left" mode="search" />
|
|
206
|
+
<Monitor3 w={42} h={30} x={180} y={57} ry={-16} cls="right" />
|
|
207
|
+
<Monitor3 w={28} h={18} x={160} y={31} ry={0} cls="top" />
|
|
208
|
+
{/* 两套键鼠紧贴相应屏幕,中央仅保留咖啡杯。 */}
|
|
209
|
+
<Box3 w={16} h={2.5} d={9} cls="dk3-metal dk3-kb3" x={130} y={71} z={14} />
|
|
210
|
+
<Box3 w={4} h={2.5} d={6} cls="dk3-metal dk3-mouse3" x={141} y={71} z={14} />
|
|
211
|
+
<Box3 w={4} h={5} d={4} cls="dk3-mug" x={154} y={69} z={14} />
|
|
212
|
+
<Box3 w={16} h={2.5} d={9} cls="dk3-metal dk3-kb3" x={170} y={71} z={14} />
|
|
213
|
+
<Box3 w={4} h={2.5} d={6} cls="dk3-metal dk3-mouse3" x={181} y={71} z={14} />
|
|
214
|
+
{/* 人物组:按当前任务在左检索/中思考/右编码两个工位间平滑移动。 */}
|
|
215
|
+
<div className="dk3-person">
|
|
216
|
+
{/* 椅子:靠背只到肩胛、降低支柱和底盘,贴近真实坐姿。 */}
|
|
217
|
+
<Box3 w={4} h={20} d={20} cls="dk3-chairback" x={2} y={47} />
|
|
218
|
+
<Box3 w={18} h={4} d={20} cls="dk3-chairseat" x={12} y={59} />
|
|
219
|
+
<Box3 w={3} h={9} d={3} cls="dk3-metal" x={12} y={66} />
|
|
220
|
+
<Box3 w={13} h={2} d={13} cls="dk3-metal" x={12} y={71} />
|
|
221
|
+
{/* 椅子滚轮(左右各一,滑动时可见) */}
|
|
222
|
+
<Box3 w={3} h={3} d={3} cls="dk3-wheel" x={6} y={74} />
|
|
223
|
+
<Box3 w={3} h={3} d={3} cls="dk3-wheel" x={18} y={74} />
|
|
224
|
+
{/* 腿:大腿/小腿/鞋 */}
|
|
225
|
+
<Box3 w={12} h={5} d={9} cls="dk3-pants" x={20} y={54} />
|
|
226
|
+
<Box3 w={4} h={10} d={4} cls="dk3-pants" x={26} y={62} />
|
|
227
|
+
<Box3 w={6} h={3} d={5} cls="dk3-shoe" x={28} y={70} />
|
|
228
|
+
{/* 上身整体始终面向桌面;阶段动作只改变前倾、肩肘腕和视线。 */}
|
|
229
|
+
<div className="dk3-upper3">
|
|
230
|
+
{/* 肩线、胸腔和腰部做成上宽下窄,摆脱直筒积木感。 */}
|
|
231
|
+
<Box3 w={18} h={5} d={12} cls="dk3-hood dk3-shoulders3" x={13} y={30} />
|
|
232
|
+
<Box3 w={15} h={14} d={11} cls="dk3-hood dk3-torso dk3-chest3" x={12} y={38} />
|
|
233
|
+
<Box3 w={12} h={7} d={10} cls="dk3-hood dk3-torso dk3-waist3" x={12} y={47} />
|
|
234
|
+
<Box3 w={5} h={8} d={9} cls="dk3-hood dk3-hoodbump" x={5} y={32} />
|
|
235
|
+
{/* 颈部加长并与下颌、衣领重叠,避免悬空。 */}
|
|
236
|
+
<Box3 w={5} h={7} d={5} cls="dk3-skin dk3-neck3" x={14} y={24} />
|
|
237
|
+
<Box3 w={8} h={3} d={8} cls="dk3-collar3" x={14} y={28} />
|
|
238
|
+
{/* 头组与颈部同轴:颅骨 + 收窄下颌 + 短侧渐层发型。 */}
|
|
239
|
+
<div className="dk3-head3">
|
|
240
|
+
<Box3 w={12} h={11} d={11} cls="dk3-skin dk3-cranium" x={8} y={8}>
|
|
241
|
+
<span className="dk3-brows" style={{ width: 9, height: 4, transform: "translate(-50%,-50%) rotateY(90deg) translateZ(5.8px)" }}><i /><i /></span>
|
|
242
|
+
<span className="dk3-eyes" style={{ width: 9, height: 6, transform: "translate(-50%,-50%) rotateY(90deg) translateZ(5.7px)" }}><i /><i /></span>
|
|
243
|
+
<span className="dk3-nose" style={{ transform: "translate(-50%,-50%) rotateY(90deg) translateZ(6.1px)" }} />
|
|
244
|
+
</Box3>
|
|
245
|
+
<Box3 w={9} h={7} d={9} cls="dk3-skin dk3-jaw3" x={8.5} y={14}>
|
|
246
|
+
<span className="dk3-mouth" style={{ transform: "translate(-50%,-50%) rotateY(90deg) translateZ(4.7px)" }} />
|
|
247
|
+
</Box3>
|
|
248
|
+
<Box3 w={12} h={5} d={12} cls="dk3-hair dk3-hair-crown" x={8} y={3.5} />
|
|
249
|
+
<Box3 w={4} h={8} d={11} cls="dk3-hair dk3-hair-back" x={3} y={7.5} />
|
|
250
|
+
<span className="dk3-fringe3"><i /><i /><i /></span>
|
|
251
|
+
<span className="dk3-ear" />
|
|
252
|
+
</div>
|
|
253
|
+
{/* 近侧手臂:肩、肘、腕、手指分段动作。 */}
|
|
254
|
+
<div className="dk3-arm3 dk3-near">
|
|
255
|
+
<Box3 w={4} h={11} d={4} cls="dk3-hood dk3-uarm" x={2} y={6} />
|
|
256
|
+
<div className="dk3-elbow">
|
|
257
|
+
<Box3 w={10} h={3.5} d={3.5} cls="dk3-hood dk3-farm" x={5.5} y={2} />
|
|
258
|
+
<div className="dk3-wrist3">
|
|
259
|
+
<Box3 w={3.5} h={3} d={3} cls="dk3-skin dk3-hand" x={2} y={2} />
|
|
260
|
+
<span className="dk3-fingers3"><i /><i /></span>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
{/* 远侧手臂:在桌面深处,和近侧手错开节奏。 */}
|
|
265
|
+
<div className="dk3-arm3 dk3-far">
|
|
266
|
+
<Box3 w={4} h={11} d={4} cls="dk3-hood dk3-uarm" x={2} y={6} />
|
|
267
|
+
<div className="dk3-elbow">
|
|
268
|
+
<Box3 w={10} h={3.5} d={3.5} cls="dk3-hood dk3-farm" x={5.5} y={2} />
|
|
269
|
+
<div className="dk3-wrist3">
|
|
270
|
+
<Box3 w={3.5} h={3} d={3} cls="dk3-skin dk3-hand" x={2} y={2} />
|
|
271
|
+
<span className="dk3-fingers3"><i /><i /></span>
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
{/* 思考泡泡(贴镜头的 2D 层,think 阶段显示) */}
|
|
279
|
+
<span className="dkan-bubble"><i /><i /><i /></span>
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ---------- 交互反馈层:对用户操作即时回应(纸飞机/火花/流光/微光/光涌/彩带) ----------
|
|
285
|
+
// 事件来源:document 点击监听(发送/新会话/切会话/切工作目录)+ 任务状态变化(开始/完成)。
|
|
286
|
+
// 每个 burst = { id, type, x, y, bits },bits 为预生成的随机粒子参数(内联样式驱动 CSS 动画)。
|
|
287
|
+
function makeBits(n, fn) {
|
|
288
|
+
const arr = [];
|
|
289
|
+
for (let i = 0; i < n; i++) arr.push(fn(i));
|
|
290
|
+
return arr;
|
|
291
|
+
}
|
|
292
|
+
function BurstLayer(props) {
|
|
293
|
+
const bursts = props.bursts || [];
|
|
294
|
+
return (
|
|
295
|
+
<div className="dkan-fxwrap" aria-hidden="true">
|
|
296
|
+
{bursts.map((b) => {
|
|
297
|
+
if (b.type === "plane") {
|
|
298
|
+
return <span key={b.id} className="dkan-fx dkan-fx-plane" style={{ left: b.x, top: b.y }}>➤</span>;
|
|
299
|
+
}
|
|
300
|
+
if (b.type === "spark") {
|
|
301
|
+
return (
|
|
302
|
+
<span key={b.id} className="dkan-fx dkan-fx-spark" style={{ left: b.x, top: b.y }}>
|
|
303
|
+
{b.bits.map((p, i) => <i key={i} style={{ "--dx": p.dx + "px", "--dy": p.dy + "px", background: p.c, boxShadow: "0 0 6px " + p.c }} />)}
|
|
304
|
+
</span>
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
if (b.type === "streak") return <span key={b.id} className="dkan-fx dkan-fx-streak" />;
|
|
308
|
+
if (b.type === "flash") return <span key={b.id} className="dkan-fx dkan-fx-flash" />;
|
|
309
|
+
if (b.type === "surge") return <span key={b.id} className="dkan-fx dkan-fx-surge" />;
|
|
310
|
+
if (b.type === "confetti") {
|
|
311
|
+
return (
|
|
312
|
+
<span key={b.id} className="dkan-fx dkan-fx-confetti">
|
|
313
|
+
{b.bits.map((p, i) => <i key={i} style={{ left: p.l + "%", "--dx": p.dx + "px", "--r": p.r + "deg", background: p.c, boxShadow: "0 0 4px " + p.c, animationDelay: p.d + "s" }} />)}
|
|
314
|
+
</span>
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
318
|
+
})}
|
|
319
|
+
</div>
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ---------- 氛围动效:代码雨 / 星野 / 极光(粒子配置 useMemo 稳定,避免重渲染抖动) ----------
|
|
324
|
+
const MATRIX_CHARS = "01</>;{}=+*#";
|
|
325
|
+
function AmbientLayer(props) {
|
|
326
|
+
const mode = props.mode;
|
|
327
|
+
const speed = props.speed;
|
|
328
|
+
// 一个模式只创建自己需要的粒子;星际模式不再额外生成代码雨和星野的 114 个无用节点。
|
|
329
|
+
const particles = useMemo(() => {
|
|
330
|
+
if (mode === "matrix") return { matrix: makeBits(34, () => ({
|
|
331
|
+
l: Math.random() * 100, d: 4 + Math.random() * 6, delay: -Math.random() * 8, o: 0.28 + Math.random() * 0.34, s: 11 + Math.round(Math.random() * 4),
|
|
332
|
+
c: MATRIX_CHARS[Math.floor(Math.random() * MATRIX_CHARS.length)] + MATRIX_CHARS[Math.floor(Math.random() * MATRIX_CHARS.length)] + MATRIX_CHARS[Math.floor(Math.random() * MATRIX_CHARS.length)],
|
|
333
|
+
})) };
|
|
334
|
+
if (mode === "stars") return { stars: makeBits(80, () => ({
|
|
335
|
+
l: Math.random() * 100, t: Math.random() * 100, sz: 1.5 + Math.random() * 2, d: 2 + Math.random() * 4, delay: -Math.random() * 6, dx: (Math.random() - 0.5) * 40, dy: (Math.random() - 0.5) * 24,
|
|
336
|
+
})) };
|
|
337
|
+
if (mode === "space") return { dust: makeBits(22, () => ({
|
|
338
|
+
l: Math.random() * 100, t: Math.random() * 82, sz: 1 + Math.random() * 2.2, d: 2.4 + Math.random() * 3.8, delay: -Math.random() * 6,
|
|
339
|
+
})) };
|
|
340
|
+
if (mode === "warp") return { warp: makeBits(28, () => ({
|
|
341
|
+
a: Math.random() * 360, len: 54 + Math.random() * 130, d: 1.8 + Math.random() * 2.4, delay: -Math.random() * 4,
|
|
342
|
+
})) };
|
|
343
|
+
if (mode === "fireflies") return { fireflies: makeBits(28, () => ({
|
|
344
|
+
l: 4 + Math.random() * 92, t: 8 + Math.random() * 78, sz: 3 + Math.random() * 5, dx: -42 + Math.random() * 84, dy: -32 + Math.random() * 64, d: 4.8 + Math.random() * 5.4, delay: -Math.random() * 8,
|
|
345
|
+
})) };
|
|
346
|
+
if (mode === "ocean") return { bubbles: makeBits(18, () => ({
|
|
347
|
+
l: 3 + Math.random() * 94, sz: 4 + Math.random() * 12, d: 5 + Math.random() * 6, delay: -Math.random() * 9, drift: -34 + Math.random() * 68,
|
|
348
|
+
})) };
|
|
349
|
+
if (mode === "lantern") return { lanterns: makeBits(18, () => ({
|
|
350
|
+
l: 4 + Math.random() * 92, sz: 8 + Math.random() * 9, d: 7 + Math.random() * 7, delay: -Math.random() * 12, drift: -48 + Math.random() * 96,
|
|
351
|
+
})) };
|
|
352
|
+
return {};
|
|
353
|
+
}, [mode]);
|
|
354
|
+
const ambientStyle = { "--dkan-speed": speed, "--dkan-phase": phaseColor(props.phase) };
|
|
355
|
+
if (mode === "matrix") {
|
|
356
|
+
return (
|
|
357
|
+
<div className="dkan-amb dkan-matrix" style={{ "--dkan-speed": speed }} aria-hidden="true">
|
|
358
|
+
{particles.matrix.map((p, i) => (
|
|
359
|
+
<span key={i} style={{ left: p.l + "%", animationDuration: "calc(" + p.d + "s / var(--dkan-speed,1))", animationDelay: p.delay + "s", opacity: p.o, fontSize: p.s }}>{p.c}</span>
|
|
360
|
+
))}
|
|
361
|
+
</div>
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
if (mode === "stars") {
|
|
365
|
+
return (
|
|
366
|
+
<div className="dkan-amb dkan-stars" style={{ "--dkan-speed": speed }} aria-hidden="true">
|
|
367
|
+
{particles.stars.map((p, i) => (
|
|
368
|
+
<i key={i} className="dkan-star" style={{ left: p.l + "%", top: p.t + "%", width: p.sz, height: p.sz, animationDuration: "calc(" + p.d + "s / var(--dkan-speed,1))", animationDelay: p.delay + "s", "--dx": p.dx + "px", "--dy": p.dy + "px" }} />
|
|
369
|
+
))}
|
|
370
|
+
<span className="dkan-moon" />
|
|
371
|
+
<i className="dkan-meteor" style={{ top: "12%", left: "72%", animationDelay: "0s" }} />
|
|
372
|
+
<i className="dkan-meteor" style={{ top: "28%", left: "88%", animationDelay: "2.8s" }} />
|
|
373
|
+
<i className="dkan-meteor" style={{ top: "6%", left: "42%", animationDelay: "5.6s" }} />
|
|
374
|
+
</div>
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
if (mode === "aurora") {
|
|
378
|
+
return (
|
|
379
|
+
<div className="dkan-amb dkan-aurora" style={{ "--dkan-speed": speed }} aria-hidden="true">
|
|
380
|
+
<i /><i /><i />
|
|
381
|
+
</div>
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
if (mode === "nebula") {
|
|
385
|
+
return <div className="dkan-amb dkan-nebula" style={ambientStyle} aria-hidden="true"><i /><i /><i /><i /></div>;
|
|
386
|
+
}
|
|
387
|
+
if (mode === "warp") {
|
|
388
|
+
return <div className="dkan-amb dkan-warp" style={ambientStyle} aria-hidden="true">
|
|
389
|
+
{particles.warp.map((p, i) => <i key={i} style={{ "--a": p.a + "deg", "--len": p.len + "px", animationDuration: "calc(" + p.d + "s / var(--dkan-speed,1))", animationDelay: p.delay + "s" }} />)}
|
|
390
|
+
</div>;
|
|
391
|
+
}
|
|
392
|
+
if (mode === "radar") {
|
|
393
|
+
return <div className="dkan-amb dkan-radar" style={ambientStyle} aria-hidden="true">
|
|
394
|
+
{["a", "b", "c"].map((id) => <span className={"dkan-radar-station " + id} key={id}><i /><b /></span>)}
|
|
395
|
+
</div>;
|
|
396
|
+
}
|
|
397
|
+
if (mode === "constellation") {
|
|
398
|
+
return <div className="dkan-amb dkan-constellation" style={ambientStyle} aria-hidden="true">
|
|
399
|
+
<svg viewBox="0 0 100 100" preserveAspectRatio="none">
|
|
400
|
+
<path pathLength="1" d="M6 68 L18 30 L31 48 L45 18 L58 42 L73 25 L91 58 L78 78 L58 42 L31 48 L18 76 L6 68" />
|
|
401
|
+
{[[6,68],[18,30],[31,48],[45,18],[58,42],[73,25],[91,58],[78,78],[18,76]].map(([cx, cy], i) => <circle key={i} cx={cx} cy={cy} r={i % 3 === 0 ? 1.1 : .7} style={{ animationDelay: (-i * .24) + "s" }} />)}
|
|
402
|
+
</svg>
|
|
403
|
+
</div>;
|
|
404
|
+
}
|
|
405
|
+
if (mode === "fireflies") {
|
|
406
|
+
return <div className="dkan-amb dkan-fireflies" style={ambientStyle} aria-hidden="true">
|
|
407
|
+
{particles.fireflies.map((p, i) => <i key={i} style={{ left: p.l + "%", top: p.t + "%", width: p.sz, height: p.sz, "--dx": p.dx + "px", "--dy": p.dy + "px", animationDuration: "calc(" + p.d + "s / var(--dkan-speed,1))", animationDelay: p.delay + "s" }} />)}
|
|
408
|
+
</div>;
|
|
409
|
+
}
|
|
410
|
+
if (mode === "ocean") {
|
|
411
|
+
return <div className="dkan-amb dkan-ocean" style={ambientStyle} aria-hidden="true">
|
|
412
|
+
<span className="dkan-ocean-waves">{Array.from({ length: 5 }, (_, i) => <i key={i} />)}</span>
|
|
413
|
+
{particles.bubbles.map((p, i) => <b key={i} style={{ left: p.l + "%", width: p.sz, height: p.sz, "--drift": p.drift + "px", animationDuration: "calc(" + p.d + "s / var(--dkan-speed,1))", animationDelay: p.delay + "s" }} />)}
|
|
414
|
+
</div>;
|
|
415
|
+
}
|
|
416
|
+
if (mode === "prism") {
|
|
417
|
+
return <div className="dkan-amb dkan-prism" style={ambientStyle} aria-hidden="true">{Array.from({ length: 7 }, (_, i) => <i key={i} />)}</div>;
|
|
418
|
+
}
|
|
419
|
+
if (mode === "circuit") {
|
|
420
|
+
return <div className="dkan-amb dkan-circuit" style={ambientStyle} aria-hidden="true">
|
|
421
|
+
<svg viewBox="0 0 1000 600" preserveAspectRatio="none">
|
|
422
|
+
{["M0 92 H180 V210 H390 V128 H610 V260 H820 V104 H1000", "M0 486 H144 V358 H334 V470 H520 V338 H748 V448 H1000", "M82 0 V126 H264 V286 H472 V196 H694 V320 H914 V600", "M936 0 V148 H776 V278 H566 V410 H350 V548 H112 V600"].map((d, i) => <path key={i} pathLength="1" d={d} style={{ animationDelay: (-i * .7) + "s" }} />)}
|
|
423
|
+
{[[180,210],[390,128],[610,260],[144,358],[520,338],[264,286],[694,320],[566,410]].map(([cx, cy], i) => <circle key={i} cx={cx} cy={cy} r="5" />)}
|
|
424
|
+
</svg>
|
|
425
|
+
</div>;
|
|
426
|
+
}
|
|
427
|
+
if (mode === "gravity") {
|
|
428
|
+
return <div className="dkan-amb dkan-gravity" style={ambientStyle} aria-hidden="true">
|
|
429
|
+
{["a", "b", "c"].map((id) => <span className={"dkan-gravity-source " + id} key={id}><b />{Array.from({ length: 4 }, (_, i) => <i key={i} style={{ animationDelay: "calc(" + (-i * .9) + "s / var(--dkan-speed,1))" }} />)}</span>)}
|
|
430
|
+
</div>;
|
|
431
|
+
}
|
|
432
|
+
if (mode === "lantern") {
|
|
433
|
+
return <div className="dkan-amb dkan-lantern" style={ambientStyle} aria-hidden="true">
|
|
434
|
+
{particles.lanterns.map((p, i) => <i key={i} style={{ left: p.l + "%", width: p.sz, height: p.sz * 1.28, "--drift": p.drift + "px", animationDuration: "calc(" + p.d + "s / var(--dkan-speed,1))", animationDelay: p.delay + "s" }} />)}
|
|
435
|
+
</div>;
|
|
436
|
+
}
|
|
437
|
+
if (mode === "space") {
|
|
438
|
+
const taskCount = Math.max(1, Math.min(3, Array.isArray(props.tasks) ? props.tasks.length : 1));
|
|
439
|
+
// 任务期间每条航线至少有两艘巡航船,三座星系之间始终有可见往返;并发任务越多,舰队越密。
|
|
440
|
+
// 所有飞船仍只使用 transform/opacity 合成动画,速度跟随任务活动速率。
|
|
441
|
+
const runnerCount = Math.max(6, Math.min(9, taskCount * 3));
|
|
442
|
+
const galaxies = props.galaxies;
|
|
443
|
+
if (!galaxies) return null;
|
|
444
|
+
return (
|
|
445
|
+
<div className="dkan-amb dkan-space" style={{
|
|
446
|
+
"--dkan-speed": speed, "--dkan-space-ships": taskCount,
|
|
447
|
+
"--dkan-alpha-x": galaxies.alpha.cx + "px", "--dkan-alpha-y": galaxies.alpha.cy + "px",
|
|
448
|
+
"--dkan-beta-x": galaxies.beta.cx + "px", "--dkan-beta-y": galaxies.beta.cy + "px",
|
|
449
|
+
"--dkan-gamma-x": galaxies.gamma.cx + "px", "--dkan-gamma-y": galaxies.gamma.cy + "px",
|
|
450
|
+
}} aria-hidden="true">
|
|
451
|
+
{particles.dust.map((p, i) => <i key={i} className="dkan-space-dust" style={{ left: p.l + "%", top: p.t + "%", width: p.sz, height: p.sz, animationDuration: "calc(" + p.d + "s / var(--dkan-speed,1))", animationDelay: p.delay + "s" }} />)}
|
|
452
|
+
{["alpha", "beta", "gamma"].map((galaxy) => <span className={"dkan-space-system " + galaxy} key={galaxy} style={{ left: galaxies[galaxy].x, top: galaxies[galaxy].y, "--dkan-system-scale": galaxies[galaxy].scale }}>
|
|
453
|
+
<i className="dkan-space-sun" />
|
|
454
|
+
<span className="dkan-space-orbit a"><i /></span>
|
|
455
|
+
<span className="dkan-space-orbit b"><i /></span>
|
|
456
|
+
<span className="dkan-space-orbit c"><i /></span>
|
|
457
|
+
</span>)}
|
|
458
|
+
{Array.from({ length: runnerCount }, (_, i) => <span className={"dkan-space-runner route-" + (i % 3)} key={i} style={{ "--dkan-run-delay": (-i * 1.8) + "s" }}><FreighterHull units={1} empty /></span>)}
|
|
459
|
+
</div>
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ---------- 完成货运舰:从任务航线归航到会话输入框上方,货舱量使用真实输出 Token ----------
|
|
466
|
+
function outputCargo(outputTokens) {
|
|
467
|
+
const tokens = Math.max(0, Number(outputTokens) || 0);
|
|
468
|
+
// 每个可见货舱约代表 900 输出 Token;保留 1 个基础舱,防止无 usage 时舰体塌陷。
|
|
469
|
+
return { tokens, units: Math.max(1, Math.min(8, Math.ceil(tokens / 900))) };
|
|
470
|
+
}
|
|
471
|
+
function fallbackComposerDockPoint(side = "right") {
|
|
472
|
+
const vw = typeof window === "undefined" ? 1280 : window.innerWidth;
|
|
473
|
+
const vh = typeof window === "undefined" ? 900 : window.innerHeight;
|
|
474
|
+
return {
|
|
475
|
+
// 待命位贴近输入框右上;右侧远端只保留给旧的通用停靠计算。
|
|
476
|
+
x: side === "left" ? Math.max(304, Math.min(520, Math.round(vw * .18)))
|
|
477
|
+
: side === "ready" ? Math.max(316, vw - 640)
|
|
478
|
+
: Math.max(16, vw - 232),
|
|
479
|
+
y: Math.max(32, vh - 218),
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function visibleRect(el) {
|
|
483
|
+
if (!el || typeof window === "undefined") return null;
|
|
484
|
+
const r = el.getBoundingClientRect();
|
|
485
|
+
return r.width > 0 && r.height > 0 && r.bottom > 0 && r.top < window.innerHeight ? r : null;
|
|
486
|
+
}
|
|
487
|
+
// 当前正在看的会话标题(侧栏高亮行)。dsh 会话树行 = role="treeitem" + aria-selected,
|
|
488
|
+
// 行文本即会话标题;据此把"需确认/卡住"这类会话级卡片限定到当前会话,别的会话等待时不打扰。
|
|
489
|
+
function currentSessionTitle() {
|
|
490
|
+
if (typeof document === "undefined") return "";
|
|
491
|
+
for (const el of document.querySelectorAll('[role="treeitem"][aria-selected="true"]')) {
|
|
492
|
+
const r = el.getBoundingClientRect();
|
|
493
|
+
// 侧栏在左:跳过正文区可能出现的同名高亮节点(右上搜索结果等)
|
|
494
|
+
if (r.width > 0 && r.height > 0 && r.right <= window.innerWidth * .35) {
|
|
495
|
+
const text = (el.textContent || "").trim();
|
|
496
|
+
if (text) return text;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return "";
|
|
500
|
+
}
|
|
501
|
+
// 指定任务是否属于当前正在看的会话(按标题对齐;侧栏不可见时退回"是"避免整层失效)
|
|
502
|
+
function taskOnScreen(task, titleCache) {
|
|
503
|
+
const title = task && (task.title || "");
|
|
504
|
+
if (!title) return true;
|
|
505
|
+
const current = titleCache !== undefined ? titleCache : currentSessionTitle();
|
|
506
|
+
if (!current) return true;
|
|
507
|
+
return current === title || current.indexOf(title) !== -1 || title.indexOf(current) !== -1;
|
|
508
|
+
}
|
|
509
|
+
const COMPOSER_SEND_SELECTOR = 'button[aria-label="Send message"],button[aria-label*="发送"],button[data-testid*="send"],button[data-testid*="submit"],button[type="submit"]';
|
|
510
|
+
function conversationComposer() {
|
|
511
|
+
if (typeof document === "undefined" || typeof window === "undefined") return null;
|
|
512
|
+
// 发送键只会位于真实会话输入卡内。由它逐层向上找卡片,比遍历历史消息的编辑节点可靠。
|
|
513
|
+
const sendButtons = document.querySelectorAll(COMPOSER_SEND_SELECTOR);
|
|
514
|
+
for (const button of sendButtons) {
|
|
515
|
+
let el = button.parentElement;
|
|
516
|
+
for (let depth = 0; el && depth < 9; depth += 1, el = el.parentElement) {
|
|
517
|
+
const r = visibleRect(el);
|
|
518
|
+
if (r && r.width >= 360 && r.height >= 72 && r.height <= 360 && r.top > window.innerHeight * .42) return { element: el, rect: r };
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
function conversationComposerRect() {
|
|
524
|
+
const composer = conversationComposer();
|
|
525
|
+
return composer ? composer.rect : null;
|
|
526
|
+
}
|
|
527
|
+
function composerPointFromRect(composer, side) {
|
|
528
|
+
return {
|
|
529
|
+
x: side === "ready" ? Math.max(composer.left + 16, composer.right - 210) : fallbackComposerDockPoint(side).x,
|
|
530
|
+
y: Math.max(22, composer.top - 92),
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
function composerDockPoint(side = "right") {
|
|
534
|
+
if (typeof document === "undefined" || typeof window === "undefined") return fallbackComposerDockPoint(side);
|
|
535
|
+
const composer = conversationComposerRect();
|
|
536
|
+
if (composer) return composerPointFromRect(composer, side);
|
|
537
|
+
const selectors = [
|
|
538
|
+
'[data-testid*="composer"]', '[data-testid*="conversation-input"]', '[data-slot*="conversation.input"]',
|
|
539
|
+
'textarea[placeholder*="Message"]', 'textarea[placeholder*="消息"]', 'textarea',
|
|
540
|
+
'[contenteditable="true"][data-testid*="composer"]', '[contenteditable="true"][aria-label*="消息"]', '[contenteditable="true"][data-slot*="conversation.input"]',
|
|
541
|
+
];
|
|
542
|
+
const seen = new Set();
|
|
543
|
+
const candidates = [];
|
|
544
|
+
for (const selector of selectors) {
|
|
545
|
+
for (const el of document.querySelectorAll(selector)) {
|
|
546
|
+
if (seen.has(el)) continue;
|
|
547
|
+
seen.add(el);
|
|
548
|
+
const r = el.getBoundingClientRect();
|
|
549
|
+
if (r.width > 80 && r.height > 16 && r.bottom > window.innerHeight * .48) candidates.push({ el, r });
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
const best = candidates.sort((a, b) => b.r.bottom - a.r.bottom)[0];
|
|
553
|
+
if (!best) return fallbackComposerDockPoint(side);
|
|
554
|
+
const r = best.r;
|
|
555
|
+
// 候补输入节点没有卡片边界时,仅待命位贴近其右侧;其他位置仍由视口锚点决定。
|
|
556
|
+
const x = side === "ready" ? Math.max(r.left + 16, r.right - 210) : fallbackComposerDockPoint(side).x;
|
|
557
|
+
return {
|
|
558
|
+
// 纵向以真实输入控件上沿停靠,飞船底部和输入框保留间隙。
|
|
559
|
+
x,
|
|
560
|
+
y: Math.max(22, r.top - 92),
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
// 刷新时先让会话本身完成首帧绘制,再在空闲期读取输入框布局。
|
|
564
|
+
// 避免星际模式恢复时 querySelectorAll + getBoundingClientRect 抢占主线程。
|
|
565
|
+
function useSpaceDocks(enabled) {
|
|
566
|
+
const [docks, setDocks] = useState(null);
|
|
567
|
+
useEffect(() => {
|
|
568
|
+
if (!enabled || typeof window === "undefined") { setDocks(null); return undefined; }
|
|
569
|
+
let disposed = false;
|
|
570
|
+
let frame = 0;
|
|
571
|
+
let idle = 0;
|
|
572
|
+
let timer = 0;
|
|
573
|
+
let retries = 0;
|
|
574
|
+
let pending = false;
|
|
575
|
+
let observedComposer = null;
|
|
576
|
+
let resizeObserver = null;
|
|
577
|
+
const samePoint = (a, b) => !!a && !!b && Math.abs(a.x - b.x) < 1 && Math.abs(a.y - b.y) < 1;
|
|
578
|
+
const bindComposerResize = (element) => {
|
|
579
|
+
if (!resizeObserver || observedComposer === element) return;
|
|
580
|
+
if (observedComposer) resizeObserver.unobserve(observedComposer);
|
|
581
|
+
observedComposer = element;
|
|
582
|
+
if (observedComposer) resizeObserver.observe(observedComposer);
|
|
583
|
+
};
|
|
584
|
+
const measure = () => {
|
|
585
|
+
if (disposed) return;
|
|
586
|
+
const found = conversationComposer();
|
|
587
|
+
// 历史会话切换时输入卡可能比浮层更晚挂载;没有真实卡片就不显示,绝不猜到正文中。
|
|
588
|
+
if (!found) {
|
|
589
|
+
bindComposerResize(null);
|
|
590
|
+
setDocks(null);
|
|
591
|
+
if (retries++ < 20) schedule(140);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
retries = 0;
|
|
595
|
+
bindComposerResize(found.element);
|
|
596
|
+
const composer = found.rect;
|
|
597
|
+
const right = composerPointFromRect(composer, "right");
|
|
598
|
+
// 三个停泊位共用同一输入框上沿,避免重复扫描整个页面读取布局。
|
|
599
|
+
const next = {
|
|
600
|
+
right,
|
|
601
|
+
left: composerPointFromRect(composer, "left"),
|
|
602
|
+
ready: composerPointFromRect(composer, "ready"),
|
|
603
|
+
};
|
|
604
|
+
setDocks((current) => current && samePoint(current.right, next.right) && samePoint(current.left, next.left) && samePoint(current.ready, next.ready) ? current : next);
|
|
605
|
+
};
|
|
606
|
+
function schedule(delay = 0, deferUntilIdle = false) {
|
|
607
|
+
if (disposed || pending) return;
|
|
608
|
+
pending = true;
|
|
609
|
+
const queueFrame = () => {
|
|
610
|
+
frame = window.requestAnimationFrame(() => {
|
|
611
|
+
pending = false;
|
|
612
|
+
measure();
|
|
613
|
+
});
|
|
614
|
+
};
|
|
615
|
+
if (delay > 0) timer = window.setTimeout(queueFrame, delay);
|
|
616
|
+
else if (deferUntilIdle && typeof window.requestIdleCallback === "function") idle = window.requestIdleCallback(queueFrame, { timeout: 350 });
|
|
617
|
+
else queueFrame();
|
|
618
|
+
};
|
|
619
|
+
if (typeof window.ResizeObserver === "function") resizeObserver = new window.ResizeObserver(() => schedule());
|
|
620
|
+
// 会话是 SPA 切换:输入框节点会被整体替换,但 effect 本身不会重新挂载。仅在包含发送键的子树增删时重测。
|
|
621
|
+
const mutationObserver = typeof window.MutationObserver === "function" ? new window.MutationObserver((mutations) => {
|
|
622
|
+
const hasComposerChange = mutations.some((mutation) => Array.from(mutation.addedNodes).concat(Array.from(mutation.removedNodes)).some((node) => {
|
|
623
|
+
if (!node || node.nodeType !== 1) return false;
|
|
624
|
+
return (node.matches && node.matches(COMPOSER_SEND_SELECTOR)) || (node.querySelector && node.querySelector(COMPOSER_SEND_SELECTOR));
|
|
625
|
+
}));
|
|
626
|
+
if (hasComposerChange) schedule(40);
|
|
627
|
+
}) : null;
|
|
628
|
+
if (mutationObserver && document.body) mutationObserver.observe(document.body, { childList: true, subtree: true });
|
|
629
|
+
schedule(0, true);
|
|
630
|
+
// 部分会话会复用同一输入框节点、只改变祖先布局;低频校准覆盖这种无 DOM 替换的位移。
|
|
631
|
+
// 校准只在任务活动时才值得跑:空闲期每 700ms 一次全页 querySelectorAll + getBoundingClientRect
|
|
632
|
+
// 是持续掉帧的来源(每次都强制布局)。空闲 20s 后降频到 5s,任务恢复立即回到快节奏。
|
|
633
|
+
let calibrationInterval = 700;
|
|
634
|
+
let lastActivityAt = Date.now();
|
|
635
|
+
const markActivity = () => { lastActivityAt = Date.now(); if (calibrationInterval !== 700) { calibrationInterval = 700; restartPoll(); } };
|
|
636
|
+
const unsubscribeActivity = animationStore.subscribe(markActivity);
|
|
637
|
+
let pollTimer = 0;
|
|
638
|
+
function restartPoll() {
|
|
639
|
+
if (pollTimer) window.clearInterval(pollTimer);
|
|
640
|
+
pollTimer = window.setInterval(() => {
|
|
641
|
+
if (Date.now() - lastActivityAt > 20000 && calibrationInterval !== 5000) calibrationInterval = 5000;
|
|
642
|
+
schedule();
|
|
643
|
+
}, calibrationInterval);
|
|
644
|
+
}
|
|
645
|
+
restartPoll();
|
|
646
|
+
const onResize = () => {
|
|
647
|
+
if (idle && typeof window.cancelIdleCallback === "function") window.cancelIdleCallback(idle);
|
|
648
|
+
if (timer) window.clearTimeout(timer);
|
|
649
|
+
pending = false;
|
|
650
|
+
schedule();
|
|
651
|
+
};
|
|
652
|
+
window.addEventListener("resize", onResize, { passive: true });
|
|
653
|
+
return () => {
|
|
654
|
+
disposed = true;
|
|
655
|
+
window.cancelAnimationFrame(frame);
|
|
656
|
+
if (idle && typeof window.cancelIdleCallback === "function") window.cancelIdleCallback(idle);
|
|
657
|
+
if (timer) window.clearTimeout(timer);
|
|
658
|
+
if (pollTimer) window.clearInterval(pollTimer);
|
|
659
|
+
if (mutationObserver) mutationObserver.disconnect();
|
|
660
|
+
if (resizeObserver) resizeObserver.disconnect();
|
|
661
|
+
unsubscribeActivity();
|
|
662
|
+
window.removeEventListener("resize", onResize);
|
|
663
|
+
};
|
|
664
|
+
}, [enabled]);
|
|
665
|
+
return docks;
|
|
666
|
+
}
|
|
667
|
+
// 三个星系分处内容区右上、左上、左下,路线有足够的飞行距离且避开侧栏和输入框。
|
|
668
|
+
// 返回中心点还会被巡航线路和归航飞船共用,避免“从空气里飞回来”。
|
|
669
|
+
function spaceGalaxyLayout() {
|
|
670
|
+
const vw = typeof window === "undefined" ? 1280 : window.innerWidth;
|
|
671
|
+
const vh = typeof window === "undefined" ? 900 : window.innerHeight;
|
|
672
|
+
const contentLeft = 316;
|
|
673
|
+
const make = (x, y, scale) => ({ x, y, scale, cx: x + 123, cy: y + 123 });
|
|
674
|
+
return {
|
|
675
|
+
alpha: make(Math.max(contentLeft + 420, vw - 360), 72, .50),
|
|
676
|
+
beta: make(contentLeft, Math.max(142, Math.min(232, vh * .18)), .48),
|
|
677
|
+
gamma: make(contentLeft + 18, Math.max(420, vh - 382), .48),
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
function freighterAnchorAt(point) {
|
|
681
|
+
// 飞船外框为 210×88,飞船主体中心约在 (88, 32),对齐到星系中心而不是左上角。
|
|
682
|
+
return { x: Math.round(point.x - 88), y: Math.round(point.y - 32) };
|
|
683
|
+
}
|
|
684
|
+
function flightAngle(from, to) {
|
|
685
|
+
return Math.round(Math.atan2(to.y - from.y, to.x - from.x) * 180 / Math.PI);
|
|
686
|
+
}
|
|
687
|
+
function FreighterHull(props) {
|
|
688
|
+
const units = Math.max(1, Number(props.units) || 1);
|
|
689
|
+
return <span className="dkan-freighter">
|
|
690
|
+
<i className="dkan-freighter-engine"><i /><i /></i>
|
|
691
|
+
<i className="dkan-freighter-wing left" /><i className="dkan-freighter-wing right" /><i className="dkan-freighter-fin" />
|
|
692
|
+
<i className="dkan-freighter-hull" /><i className="dkan-freighter-canopy" /><i className="dkan-freighter-nose" />
|
|
693
|
+
<span className={"dkan-freighter-cargo" + (props.empty ? " empty" : "")}>{Array.from({ length: units }, (_, i) => <i key={i} />)}</span>
|
|
694
|
+
</span>;
|
|
695
|
+
}
|
|
696
|
+
function DeliveryFreighter(props) {
|
|
697
|
+
const d = props.delivery;
|
|
698
|
+
return (
|
|
699
|
+
<div className="dkan-delivery" style={{
|
|
700
|
+
"--dkan-from-x": d.from.x + "px", "--dkan-from-y": d.from.y + "px",
|
|
701
|
+
"--dkan-to-x": d.to.x + "px", "--dkan-to-y": d.to.y + "px",
|
|
702
|
+
"--dkan-delivery-duration": d.duration + "ms",
|
|
703
|
+
}} aria-hidden="true">
|
|
704
|
+
<span className="dkan-delivery-trail" />
|
|
705
|
+
<FreighterHull units={d.units} />
|
|
706
|
+
<span className="dkan-delivery-label"><b>归航货运舰</b><span>输出 {fmtCompact(d.tokens)} Tokens · {d.units} 舱</span></span>
|
|
707
|
+
</div>
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
function ReadyFreighter(props) {
|
|
711
|
+
const dock = props.dock || fallbackComposerDockPoint("ready");
|
|
712
|
+
return <div className="dkan-ready-freighter" style={{ left: dock.x, top: dock.y }} aria-hidden="true"><FreighterHull units={3} empty /><span>待命 · 等待下一条任务</span></div>;
|
|
713
|
+
}
|
|
714
|
+
function MissionDeparture(props) {
|
|
715
|
+
const d = props.departure;
|
|
716
|
+
return <div className="dkan-departure" style={{
|
|
717
|
+
"--dkan-from-x": d.from.x + "px", "--dkan-from-y": d.from.y + "px",
|
|
718
|
+
"--dkan-to-x": d.to.x + "px", "--dkan-to-y": d.to.y + "px",
|
|
719
|
+
"--dkan-flight-angle": d.angle + "deg", "--dkan-departure-duration": d.duration + "ms",
|
|
720
|
+
}} aria-hidden="true"><FreighterHull units={3} empty /></div>;
|
|
721
|
+
}
|
|
722
|
+
function ProgrammingStuck(props) {
|
|
723
|
+
const waiting = !!props.waiting;
|
|
724
|
+
return <div className={"dkan-space-stuck" + (waiting ? " waiting" : "")} aria-hidden="true"><FreighterHull units={3} empty /><span className="dkan-stuck-code"><i />{`{ }`}</span><strong>{waiting ? "等待确认" : "编程等待中"}</strong><small>{truncate((props.task && props.task.title) || "工具暂无新响应", 28)}</small></div>;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// ---------- 全局浮层:轮询 + 动效(功能启用即常驻;通知在 features/notify/) ----------
|
|
728
|
+
export function AnimationOverlay(props) {
|
|
729
|
+
const ctx = props && props.ctx;
|
|
730
|
+
const snap = useAnimation();
|
|
731
|
+
const [flourish, setFlourish] = useState(null); // { key, err } 完成瞬间的一次性流光
|
|
732
|
+
const [bursts, setBursts] = useState([]); // 交互反馈动画队列
|
|
733
|
+
const [deliveries, setDeliveries] = useState([]); // 星际模式完成时停泊在输入框上方的归航货运舰
|
|
734
|
+
const [departures, setDepartures] = useState([]); // 从输入框起飞的出航舰
|
|
735
|
+
const prevActiveRef = useRef(null);
|
|
736
|
+
const burstIdRef = useRef(0);
|
|
737
|
+
const deliveryIdRef = useRef(0);
|
|
738
|
+
const departureTimersRef = useRef(new Map());
|
|
739
|
+
const lastImmediateDepartureRef = useRef(0);
|
|
740
|
+
|
|
741
|
+
useEffect(() => () => {
|
|
742
|
+
for (const timer of departureTimersRef.current.values()) clearTimeout(timer);
|
|
743
|
+
departureTimersRef.current.clear();
|
|
744
|
+
}, []);
|
|
745
|
+
|
|
746
|
+
// 触发一个交互反馈动画(自动清理)
|
|
747
|
+
const pushBurst = useCallback((type, x, y, bits) => {
|
|
748
|
+
const id = ++burstIdRef.current;
|
|
749
|
+
setBursts((prev) => prev.concat([{ id, type, x, y, bits }]).slice(-6));
|
|
750
|
+
setTimeout(() => setBursts((prev) => prev.filter((b) => b.id !== id)), 2600);
|
|
751
|
+
}, []);
|
|
752
|
+
// 发送即出发:从左侧归航位先缓慢滑行,再逐段加速,最终并入右上角星系。
|
|
753
|
+
const launchMission = useCallback(() => {
|
|
754
|
+
const cfg = animationStore.snap.status && animationStore.snap.status.config;
|
|
755
|
+
if (!cfg || !cfg.animationEnabled || cfg.effectMode !== "space") return false;
|
|
756
|
+
const id = ++deliveryIdRef.current;
|
|
757
|
+
const from = composerDockPoint("left");
|
|
758
|
+
const targetGalaxy = spaceGalaxyLayout().alpha;
|
|
759
|
+
const to = freighterAnchorAt({ x: targetGalaxy.cx, y: targetGalaxy.cy });
|
|
760
|
+
const departure = {
|
|
761
|
+
id, from, to,
|
|
762
|
+
angle: flightAngle(from, to),
|
|
763
|
+
duration: 8600,
|
|
764
|
+
};
|
|
765
|
+
lastImmediateDepartureRef.current = Date.now();
|
|
766
|
+
setDeliveries([]); // 满载归港舰重新出航前离开停泊位
|
|
767
|
+
setDepartures((prev) => prev.concat([departure]).slice(-3));
|
|
768
|
+
departureTimersRef.current.set(id, setTimeout(() => {
|
|
769
|
+
departureTimersRef.current.delete(id);
|
|
770
|
+
setDepartures((prev) => prev.filter((item) => item.id !== id));
|
|
771
|
+
}, departure.duration));
|
|
772
|
+
return true;
|
|
773
|
+
}, []);
|
|
774
|
+
|
|
775
|
+
// 点击监听:识别用户操作 → 即时反馈(发送/新会话/切会话/切工作目录)
|
|
776
|
+
useEffect(() => {
|
|
777
|
+
if (typeof document === "undefined") return;
|
|
778
|
+
const onClick = (e) => {
|
|
779
|
+
const t = e.target;
|
|
780
|
+
if (!t || !t.closest) return;
|
|
781
|
+
const sendBtn = t.closest('button[aria-label="Send message"], button[aria-label*="发送"]');
|
|
782
|
+
const newSession = t.closest('button[aria-label*="New session"], button[aria-label*="新会话"], button[aria-label*="New Session"]');
|
|
783
|
+
const workspace = t.closest('button[aria-label*="Choose workspace"], button[aria-label*="工作目录"], button[aria-label*="workspace"]');
|
|
784
|
+
const sessionItem = t.closest('[role="treeitem"]');
|
|
785
|
+
if (sendBtn) {
|
|
786
|
+
pushBurst("plane", e.clientX, e.clientY);
|
|
787
|
+
launchMission();
|
|
788
|
+
} else if (newSession) {
|
|
789
|
+
pushBurst("spark", e.clientX, e.clientY, makeBits(10, () => ({
|
|
790
|
+
dx: (Math.random() - 0.5) * 70, dy: (Math.random() - 0.5) * 70,
|
|
791
|
+
c: ["#2563eb", "#0d9488", "#b45309", "#be185d"][Math.floor(Math.random() * 4)],
|
|
792
|
+
})));
|
|
793
|
+
} else if (workspace) {
|
|
794
|
+
pushBurst("flash");
|
|
795
|
+
} else if (sessionItem) {
|
|
796
|
+
pushBurst("streak");
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
document.addEventListener("click", onClick, true);
|
|
800
|
+
return () => document.removeEventListener("click", onClick, true);
|
|
801
|
+
}, [pushBurst, launchMission]);
|
|
802
|
+
|
|
803
|
+
// 任务状态变化:没有直接发送事件时,仍由状态变化兜底从左侧起飞。
|
|
804
|
+
const taskCountRef = useRef(0);
|
|
805
|
+
useEffect(() => {
|
|
806
|
+
const st = snap.status;
|
|
807
|
+
if (!st || !st.active) return;
|
|
808
|
+
const n = st.active.length;
|
|
809
|
+
if (n > taskCountRef.current) {
|
|
810
|
+
pushBurst("surge");
|
|
811
|
+
const cfg = st.config || {};
|
|
812
|
+
if (cfg.animationEnabled && cfg.effectMode === "space" && Date.now() - lastImmediateDepartureRef.current > 10000) launchMission();
|
|
813
|
+
}
|
|
814
|
+
taskCountRef.current = n;
|
|
815
|
+
}, [snap.status, pushBurst, launchMission]);
|
|
816
|
+
|
|
817
|
+
// 轮询:任务中 850ms,实时捕捉 chunk/工具活动;空闲 6s / 出错 15s;页面切回立即刷新。
|
|
818
|
+
useEffect(() => {
|
|
819
|
+
let stopped = false;
|
|
820
|
+
let timer = null;
|
|
821
|
+
const loop = async () => {
|
|
822
|
+
await animationStore.refresh();
|
|
823
|
+
if (stopped) return;
|
|
824
|
+
const s = animationStore.snap;
|
|
825
|
+
timer = setTimeout(loop, s.error ? 15000 : (s.status && s.status.active && s.status.active.length > 0 ? 850 : 6000));
|
|
826
|
+
};
|
|
827
|
+
loop();
|
|
828
|
+
const onVisible = () => {
|
|
829
|
+
if (!stopped && typeof document !== "undefined" && document.visibilityState === "visible") animationStore.refresh();
|
|
830
|
+
};
|
|
831
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
832
|
+
return () => { stopped = true; if (timer) clearTimeout(timer); document.removeEventListener("visibilitychange", onVisible); };
|
|
833
|
+
}, []);
|
|
834
|
+
|
|
835
|
+
// 任务结束检测:上一轮活跃、本轮消失 → 完成瞬间的流光(找最近完成记录补全信息)
|
|
836
|
+
useEffect(() => {
|
|
837
|
+
const st = snap.status;
|
|
838
|
+
if (!st || !st.active) return;
|
|
839
|
+
const prev = prevActiveRef.current;
|
|
840
|
+
if (prev) {
|
|
841
|
+
const currIds = new Set(st.active.map((x) => x.sessionId));
|
|
842
|
+
for (const task of prev) {
|
|
843
|
+
if (currIds.has(task.sessionId)) continue;
|
|
844
|
+
const record = (st.recent || []).find((r) => r.sessionId === task.sessionId) || null;
|
|
845
|
+
handleTaskEnd(task, record, st.config || {});
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
prevActiveRef.current = st.active.slice();
|
|
849
|
+
}, [snap.status]);
|
|
850
|
+
|
|
851
|
+
const handleTaskEnd = (task, record, cfg) => {
|
|
852
|
+
if (!cfg.animationEnabled) return;
|
|
853
|
+
const success = isSuccessReason((record && record.endReason) || "");
|
|
854
|
+
setFlourish({ key: Date.now(), err: !success });
|
|
855
|
+
// 星际远征把完成结果送回用户正在输入的位置。速度继承最后一帧任务节奏;
|
|
856
|
+
// 货舱数量及标签只取 Host 已记录的真实 outputTokens。
|
|
857
|
+
if (cfg.effectMode === "space") {
|
|
858
|
+
const cargo = outputCargo((record && record.outputTokens) || task.outputTokens);
|
|
859
|
+
const id = ++deliveryIdRef.current;
|
|
860
|
+
const taskSpeed = Math.max(.9, Number(speed) || 1);
|
|
861
|
+
const duration = Math.round(Math.max(8000, Math.min(9400, 10000 / Math.sqrt(taskSpeed))));
|
|
862
|
+
// 从三个星系中随机挑选归航起点,任务完成时才能知道本次货舰从哪里回来。
|
|
863
|
+
const galaxies = spaceGalaxyLayout();
|
|
864
|
+
const returnGalaxy = galaxies[["alpha", "beta", "gamma"][Math.floor(Math.random() * 3)]];
|
|
865
|
+
const from = freighterAnchorAt({ x: returnGalaxy.cx, y: returnGalaxy.cy });
|
|
866
|
+
const to = composerDockPoint("left");
|
|
867
|
+
// 满载舰最终停入左侧归航位,和右上出发星系形成清晰的往返方向。
|
|
868
|
+
const delivery = Object.assign({ id, from, to, duration }, cargo);
|
|
869
|
+
setDeliveries([delivery]);
|
|
870
|
+
}
|
|
871
|
+
// 成功时顶部彩带庆祝(通知卡片由【任务通知】模块负责,与本模块无关)
|
|
872
|
+
if (success) {
|
|
873
|
+
pushBurst("confetti", 0, 0, makeBits(18, (i) => ({
|
|
874
|
+
l: 4 + (i / 18) * 92 + Math.random() * 3,
|
|
875
|
+
dx: (Math.random() - 0.5) * 60,
|
|
876
|
+
r: Math.random() * 720 - 360,
|
|
877
|
+
d: Math.random() * 0.35,
|
|
878
|
+
c: ["#2563eb", "#0d9488", "#b45309", "#be185d", "#7c3aed"][i % 5],
|
|
879
|
+
})));
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
const st = snap.status;
|
|
884
|
+
const cfg = st && st.config;
|
|
885
|
+
const active = st && st.active ? st.active : [];
|
|
886
|
+
const animOn = !!(cfg && cfg.animationEnabled && active.length > 0);
|
|
887
|
+
const mode = cfg && cfg.effectMode ? cfg.effectMode : "flow";
|
|
888
|
+
// 机器人阶段:取最近发生阶段变化的活跃任务。
|
|
889
|
+
const phase = active.length > 0
|
|
890
|
+
? active.reduce((best, x) => (!best || (x.phaseAt || 0) > (best.phaseAt || 0) ? x : best), null).phase
|
|
891
|
+
: "think";
|
|
892
|
+
// 有会话在等用户确认时,状态文字让位给"等待确认"(优先级最高:不动手就卡住)。
|
|
893
|
+
// 会话级卡片(等待确认/编程等待中)只对"当前正在看的会话"出现,别的会话等待不打扰。
|
|
894
|
+
const waitingTask = active.find((x) => Array.isArray(x.approvals) && x.approvals.length > 0) || null;
|
|
895
|
+
const sessionTitleRef = useRef({ at: 0, title: "" });
|
|
896
|
+
const currentTitle = useMemo(() => {
|
|
897
|
+
// 标题查询要 getBoundingClientRect(强制布局),限频 1s;面板页/任务行为不受此限制
|
|
898
|
+
const c = sessionTitleRef.current;
|
|
899
|
+
if (typeof window === "undefined") return "";
|
|
900
|
+
if (Date.now() - c.at < 1000) return c.title;
|
|
901
|
+
c.at = Date.now();
|
|
902
|
+
c.title = currentSessionTitle();
|
|
903
|
+
return c.title;
|
|
904
|
+
}, [snap.status]);
|
|
905
|
+
const onScreenTasks = useMemo(() => active.filter((x) => taskOnScreen(x, currentTitle)), [active, currentTitle]);
|
|
906
|
+
const waitingTaskOnScreen = onScreenTasks.find((x) => Array.isArray(x.approvals) && x.approvals.length > 0) || null;
|
|
907
|
+
useTicker(ctx, animOn);
|
|
908
|
+
// 徽标计时:以最早开始的任务为基准
|
|
909
|
+
const now = Date.now();
|
|
910
|
+
const elapsed = active.length > 0 ? fmtClock(now - Math.min(...active.map((x) => x.startTime || now))) : "";
|
|
911
|
+
// 面板打开时隐藏环境动效(徽标/细线/巡航/机器人),避免与弹窗重叠错位
|
|
912
|
+
const [panelOpen, setPanelOpen] = useState(false);
|
|
913
|
+
useEffect(() => {
|
|
914
|
+
setPanelOpen(panelNav.open);
|
|
915
|
+
return subscribePanel(() => setPanelOpen(panelNav.open));
|
|
916
|
+
}, []);
|
|
917
|
+
// 任务速度:优先使用 host 从 chunk/tool 实时累积的活动脉冲;
|
|
918
|
+
// token usage 只在完整消息后才到达,不能再作为唯一速度来源。
|
|
919
|
+
const [speed, setSpeed] = useState(1);
|
|
920
|
+
const speedRef = useRef({ ticks: -1, at: 0 });
|
|
921
|
+
useEffect(() => {
|
|
922
|
+
if (!st || active.length === 0) { speedRef.current = { ticks: -1, at: 0 }; setSpeed(1); return; }
|
|
923
|
+
const ticks = active.reduce((a, x) => a + (x.motionTicks || 0), 0);
|
|
924
|
+
const t = Date.now();
|
|
925
|
+
const prev = speedRef.current;
|
|
926
|
+
if (prev.ticks < 0 || t - prev.at < 300) { speedRef.current = { ticks, at: t }; return; }
|
|
927
|
+
const rate = Math.max(0, (ticks - prev.ticks) / ((t - prev.at) / 1000));
|
|
928
|
+
const phaseBoost = phase === "write" || phase === "code" ? .28 : (phase === "search" ? .18 : 0);
|
|
929
|
+
const next = Math.max(.9, Math.min(4, Number((.9 + rate / 14 + phaseBoost).toFixed(2))));
|
|
930
|
+
setSpeed((current) => Math.abs(current - next) < .03 ? current : next);
|
|
931
|
+
speedRef.current = { ticks, at: t };
|
|
932
|
+
}, [snap.status]);
|
|
933
|
+
const ambientOn = animOn && !panelOpen;
|
|
934
|
+
const spaceModeOn = !!(cfg && cfg.animationEnabled && mode === "space" && !panelOpen);
|
|
935
|
+
const spaceDocks = useSpaceDocks(spaceModeOn);
|
|
936
|
+
const spaceGalaxies = spaceModeOn && spaceDocks ? spaceGalaxyLayout() : null;
|
|
937
|
+
// Host 在编程阶段持续 18 秒没有 chunk/tool 活动,才把它视为卡住;正常长任务不会误触发。
|
|
938
|
+
// 卡住卡片同样只对当前会话出现。
|
|
939
|
+
const stuckTask = onScreenTasks.find((task) => task.phase === "code" && now - (task.lastActivityAt || task.phaseAt || now) >= 18000) || null;
|
|
940
|
+
const robotScaleFromConfig = Math.max(.85, Math.min(2.2, Number(cfg && cfg.robotScale) || 1.35));
|
|
941
|
+
const speedStyle = { "--dkan-speed": Number(speed).toFixed(2) };
|
|
942
|
+
const saveRobotScale = useCallback((robotScale) => {
|
|
943
|
+
rpcCall("config", { robotScale })
|
|
944
|
+
.then((d) => animationStore.applyConfig(d && d.config))
|
|
945
|
+
.catch(() => { /* 浮层保存失败不打断任务动画;面板页会显示可操作的错误 */ });
|
|
946
|
+
}, []);
|
|
947
|
+
|
|
948
|
+
// ===== 机器人卡片自由拖拽:整卡可拖到屏幕任意位置(localStorage 持久化),防止固定遮挡 =====
|
|
949
|
+
const [botPos, setBotPos] = useState(null); // null = 停泊默认位(右下、输入卡上方)
|
|
950
|
+
const botPosRef = useRef(null);
|
|
951
|
+
const botDragRef = useRef(null); // { sx, sy, ox, oy, moved }
|
|
952
|
+
const botResizeRef = useRef(null); // { sx, sy, scale }
|
|
953
|
+
const [botScaleDraft, setBotScaleDraft] = useState(null);
|
|
954
|
+
const botClickBlockRef = useRef(false);
|
|
955
|
+
useEffect(() => {
|
|
956
|
+
try {
|
|
957
|
+
if (typeof localStorage === "undefined") return;
|
|
958
|
+
const raw = localStorage.getItem("dsh-dock/anim/robot-pos/v1");
|
|
959
|
+
const p = raw ? JSON.parse(raw) : null;
|
|
960
|
+
if (p && typeof p.x === "number" && typeof p.y === "number" && typeof window !== "undefined") {
|
|
961
|
+
// 恢复时钳制回视口内(窗口可能变小了)
|
|
962
|
+
const card = document.querySelector(".dkan-botcard");
|
|
963
|
+
const w = card ? card.offsetWidth : 200, h = card ? card.offsetHeight : 160;
|
|
964
|
+
const x = Math.min(Math.max(8, p.x), Math.max(8, window.innerWidth - w - 8));
|
|
965
|
+
const y = Math.min(Math.max(8, p.y), Math.max(8, window.innerHeight - h - 8));
|
|
966
|
+
botPosRef.current = { x, y };
|
|
967
|
+
setBotPos({ x, y });
|
|
968
|
+
}
|
|
969
|
+
} catch { /* localStorage 不可用时停默认位 */ }
|
|
970
|
+
}, []);
|
|
971
|
+
const onBotPointerDown = (e) => {
|
|
972
|
+
if (e.button !== 0) return;
|
|
973
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
974
|
+
botDragRef.current = { sx: e.clientX, sy: e.clientY, ox: r.left, oy: r.top, moved: false };
|
|
975
|
+
try { e.currentTarget.setPointerCapture(e.pointerId); } catch { /* 无指针捕获也能拖 */ }
|
|
976
|
+
};
|
|
977
|
+
const onBotPointerMove = (e) => {
|
|
978
|
+
const d = botDragRef.current;
|
|
979
|
+
if (!d || typeof window === "undefined") return;
|
|
980
|
+
const dx = e.clientX - d.sx, dy = e.clientY - d.sy;
|
|
981
|
+
if (!d.moved && Math.hypot(dx, dy) < 4) return;
|
|
982
|
+
d.moved = true;
|
|
983
|
+
const el = e.currentTarget;
|
|
984
|
+
const w = el.offsetWidth || 200, h = el.offsetHeight || 160;
|
|
985
|
+
const x = Math.min(Math.max(8, d.ox + dx), window.innerWidth - w - 8);
|
|
986
|
+
const y = Math.min(Math.max(8, d.oy + dy), window.innerHeight - h - 8);
|
|
987
|
+
botPosRef.current = { x, y };
|
|
988
|
+
setBotPos({ x, y });
|
|
989
|
+
};
|
|
990
|
+
const onBotPointerUp = () => {
|
|
991
|
+
const d = botDragRef.current;
|
|
992
|
+
botDragRef.current = null;
|
|
993
|
+
if (d && d.moved) {
|
|
994
|
+
try { if (typeof localStorage !== "undefined") localStorage.setItem("dsh-dock/anim/robot-pos/v1", JSON.stringify(botPosRef.current)); } catch { /* 持久化失败静默 */ }
|
|
995
|
+
botClickBlockRef.current = true; // 拖动结束的 pointerup 不触发面板跳转
|
|
996
|
+
setTimeout(() => { botClickBlockRef.current = false; }, 0);
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
const onBotResizePointerDown = (e) => {
|
|
1000
|
+
if (e.button !== 0) return;
|
|
1001
|
+
e.preventDefault();
|
|
1002
|
+
e.stopPropagation();
|
|
1003
|
+
botResizeRef.current = { sx: e.clientX, sy: e.clientY, scale: botScaleDraft == null ? robotScaleFromConfig : botScaleDraft };
|
|
1004
|
+
try { e.currentTarget.setPointerCapture(e.pointerId); } catch { /* 无指针捕获也能缩放 */ }
|
|
1005
|
+
};
|
|
1006
|
+
const onBotResizePointerMove = (e) => {
|
|
1007
|
+
const d = botResizeRef.current;
|
|
1008
|
+
if (!d) return;
|
|
1009
|
+
e.preventDefault();
|
|
1010
|
+
e.stopPropagation();
|
|
1011
|
+
// 保持场景比例;以右下角手柄向外拖动放大,向内拖动缩小。
|
|
1012
|
+
const delta = ((e.clientX - d.sx) + (e.clientY - d.sy)) / 2;
|
|
1013
|
+
const next = Math.max(.85, Math.min(2.2, Math.round((d.scale + delta / 220) * 100) / 100));
|
|
1014
|
+
setBotScaleDraft(next);
|
|
1015
|
+
// 已拖到自定义位置时,放大也始终留在可视区域内。
|
|
1016
|
+
if (botPosRef.current && typeof window !== "undefined") {
|
|
1017
|
+
const w = 220 * next + 24, h = 132 * next + 46;
|
|
1018
|
+
const x = Math.min(Math.max(8, botPosRef.current.x), Math.max(8, window.innerWidth - w - 8));
|
|
1019
|
+
const y = Math.min(Math.max(8, botPosRef.current.y), Math.max(8, window.innerHeight - h - 8));
|
|
1020
|
+
botPosRef.current = { x, y };
|
|
1021
|
+
setBotPos({ x, y });
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
const onBotResizePointerUp = (e) => {
|
|
1025
|
+
if (!botResizeRef.current) return;
|
|
1026
|
+
e.preventDefault();
|
|
1027
|
+
e.stopPropagation();
|
|
1028
|
+
const next = botScaleDraft == null ? robotScaleFromConfig : botScaleDraft;
|
|
1029
|
+
botResizeRef.current = null;
|
|
1030
|
+
saveRobotScale(next);
|
|
1031
|
+
try { if (botPosRef.current && typeof localStorage !== "undefined") localStorage.setItem("dsh-dock/anim/robot-pos/v1", JSON.stringify(botPosRef.current)); } catch { /* 位置保存失败不影响缩放 */ }
|
|
1032
|
+
setBotScaleDraft(null);
|
|
1033
|
+
};
|
|
1034
|
+
const onBotClick = () => {
|
|
1035
|
+
if (botClickBlockRef.current) return;
|
|
1036
|
+
openPanel("animation");
|
|
1037
|
+
};
|
|
1038
|
+
const displayRobotScale = botScaleDraft == null ? robotScaleFromConfig : botScaleDraft;
|
|
1039
|
+
const botCardStyle = botPos
|
|
1040
|
+
? Object.assign({}, speedStyle, { "--dkan-bot-scale": displayRobotScale, left: botPos.x, top: botPos.y, right: "auto", bottom: "auto" })
|
|
1041
|
+
: Object.assign({}, speedStyle, { "--dkan-bot-scale": displayRobotScale });
|
|
1042
|
+
|
|
1043
|
+
// Host 不可用(旧宿主未重启等):浮层整体静默,面板页会给提示
|
|
1044
|
+
if (!st) return null;
|
|
1045
|
+
return (<>
|
|
1046
|
+
{/* 顶部流光细线(flow 模式 / 任务进行中) */}
|
|
1047
|
+
{ambientOn && mode === "flow" ? <div className="dkan-line" style={speedStyle} aria-hidden="true" /> : null}
|
|
1048
|
+
{/* 氛围动效(代码雨/星野/极光/星际远征,任务进行中渲染,速度随吞吐) */}
|
|
1049
|
+
{ambientOn && AMBIENT_EFFECT_MODES.has(mode) && (mode !== "space" || spaceGalaxies)
|
|
1050
|
+
? <AmbientLayer mode={mode} phase={phase} speed={Number(speed).toFixed(2)} tasks={active} galaxies={spaceGalaxies} /> : null}
|
|
1051
|
+
{/* 星际模式:空闲时在输入框上方待命;任务卡住时在中央呈现编程等待状态。 */}
|
|
1052
|
+
{spaceModeOn && spaceDocks && active.length === 0 && deliveries.length === 0 ? <ReadyFreighter dock={spaceDocks.ready} /> : null}
|
|
1053
|
+
{spaceModeOn && stuckTask ? <ProgrammingStuck task={stuckTask} waiting={!!(stuckTask.approvals && stuckTask.approvals.length)} /> : null}
|
|
1054
|
+
{!panelOpen ? departures.map((departure) => <MissionDeparture key={departure.id} departure={departure} />) : null}
|
|
1055
|
+
{/* 环屏巡航:一颗光点沿屏幕边缘转圈(orbit 模式) */}
|
|
1056
|
+
{ambientOn && mode === "orbit" ? <div className="dkan-orbit" style={speedStyle} aria-hidden="true" /> : null}
|
|
1057
|
+
{/* 交互反馈层:纸飞机/火花/流光/微光/光涌/彩带(与动画开关独立,只要功能启用即回应操作) */}
|
|
1058
|
+
<BurstLayer bursts={bursts} />
|
|
1059
|
+
{/* 桌面伙伴:背侧视角紧凑双工位人物,阶段与任务同步;整卡可拖到任意位置(robot 模式) */}
|
|
1060
|
+
{ambientOn && mode === "robot" ? (
|
|
1061
|
+
<div className="dkan-botcard" role="button" tabIndex={0} style={Object.assign({}, botCardStyle, waitingTask ? { "--dkan-phase": "#b45309" } : null)}
|
|
1062
|
+
title={active.length + " 个任务 · " + (waitingTask ? "等待确认" : phaseLabel(phase)) + " · 点击查看任务动画页 · 可拖动到任意位置"}
|
|
1063
|
+
onPointerDown={onBotPointerDown}
|
|
1064
|
+
onPointerMove={onBotPointerMove}
|
|
1065
|
+
onPointerUp={onBotPointerUp}
|
|
1066
|
+
onPointerCancel={onBotPointerUp}
|
|
1067
|
+
onClick={onBotClick}
|
|
1068
|
+
onKeyDown={(e) => { if (e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) { e.preventDefault(); onBotClick(); } }}>
|
|
1069
|
+
<RobotScene phase={phase} tasks={active} />
|
|
1070
|
+
<span className="dkan-bot-cap">
|
|
1071
|
+
<span className="n">{active.length}</span> 个任务{elapsed ? " · " + elapsed : ""} · {waitingTask ? "等待确认" : phaseLabel(phase)}
|
|
1072
|
+
</span>
|
|
1073
|
+
<button type="button" className="dkan-bot-resize" aria-label="调整桌面伙伴大小"
|
|
1074
|
+
title="拖动调整桌面伙伴大小"
|
|
1075
|
+
onPointerDown={onBotResizePointerDown}
|
|
1076
|
+
onPointerMove={onBotResizePointerMove}
|
|
1077
|
+
onPointerUp={onBotResizePointerUp}
|
|
1078
|
+
onPointerCancel={onBotResizePointerUp}
|
|
1079
|
+
onClick={(e) => e.stopPropagation()}
|
|
1080
|
+
onKeyDown={(e) => {
|
|
1081
|
+
if (e.key !== "ArrowUp" && e.key !== "ArrowRight" && e.key !== "ArrowDown" && e.key !== "ArrowLeft") return;
|
|
1082
|
+
e.preventDefault(); e.stopPropagation();
|
|
1083
|
+
const d = e.key === "ArrowUp" || e.key === "ArrowRight" ? .05 : -.05;
|
|
1084
|
+
saveRobotScale(Math.max(.85, Math.min(2.2, Math.round((displayRobotScale + d) * 100) / 100)));
|
|
1085
|
+
}} />
|
|
1086
|
+
</div>
|
|
1087
|
+
) : null}
|
|
1088
|
+
{/* 右下角状态徽标:dot 随模式变化(breathe/ring 装饰),颜色随任务阶段、呼吸随吞吐;点击进功能坞动画页 */}
|
|
1089
|
+
{ambientOn && mode !== "robot" ? (
|
|
1090
|
+
<button type="button" className={"dkan-badge" + (mode === "breathe" ? " dkan-badge-breathe" : "")}
|
|
1091
|
+
style={Object.assign({}, speedStyle, { "--dkan-phase": phaseColor(waitingTask ? "code" : phase) })}
|
|
1092
|
+
title={active.length + " 个任务进行中 · " + (waitingTask ? "等待确认" : phaseLabel(phase)) + " · 点击查看任务动画页"}
|
|
1093
|
+
onClick={() => openPanel("animation")}>
|
|
1094
|
+
<span className="dkan-dotwrap">
|
|
1095
|
+
<span className="dkan-dot" />
|
|
1096
|
+
{mode === "breathe" ? <span className="dkan-halo" /> : null}
|
|
1097
|
+
{mode === "ring" ? <span className="dkan-ring" /> : null}
|
|
1098
|
+
</span>
|
|
1099
|
+
<span className="dkan-badge-txt"><span className="n">{active.length}</span> 个任务{elapsed ? " · " + elapsed : ""} · {waitingTask ? "等待确认" : phaseLabel(phase)}</span>
|
|
1100
|
+
</button>
|
|
1101
|
+
) : null}
|
|
1102
|
+
{/* 完成瞬间的一次性流光(成功绿 / 异常红),动画结束自动清场 */}
|
|
1103
|
+
{flourish ? (
|
|
1104
|
+
<div key={flourish.key} className={"dkan-done" + (flourish.err ? " err" : "")}
|
|
1105
|
+
onAnimationEnd={() => setFlourish(null)} aria-hidden="true" />
|
|
1106
|
+
) : null}
|
|
1107
|
+
{/* 星际任务完成:货运舰沿航线飞到输入框上方并保持停泊,直到下一次任务出航。 */}
|
|
1108
|
+
{!panelOpen ? deliveries.map((delivery) => <DeliveryFreighter key={delivery.id} delivery={delivery} />) : null}
|
|
1109
|
+
</>);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// ---------- 面板页 ----------
|
|
1113
|
+
function ModePreview({ id }) {
|
|
1114
|
+
if (id === "flow") return <span className="dkan-prev"><span className="dkan-prev-flow" /></span>;
|
|
1115
|
+
if (id === "ring") {
|
|
1116
|
+
return (
|
|
1117
|
+
<span className="dkan-prev">
|
|
1118
|
+
<span className="dkan-dotwrap"><span className="dkan-dot" /><span className="dkan-ring" /></span>
|
|
1119
|
+
</span>
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
if (id === "orbit") {
|
|
1123
|
+
return (
|
|
1124
|
+
<span className="dkan-prev dkan-prev-box">
|
|
1125
|
+
<span className="dkan-prev-orbit" />
|
|
1126
|
+
</span>
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
if (id === "matrix") {
|
|
1130
|
+
return (
|
|
1131
|
+
<span className="dkan-prev dkan-prev-matrix">
|
|
1132
|
+
<i /><i /><i /><i /><i />
|
|
1133
|
+
</span>
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
if (id === "stars") {
|
|
1137
|
+
return (
|
|
1138
|
+
<span className="dkan-prev dkan-prev-stars">
|
|
1139
|
+
<i /><i /><i /><i /><i /><i />
|
|
1140
|
+
</span>
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
if (id === "space") {
|
|
1144
|
+
return <span className="dkan-prev dkan-prev-space"><i /><b /><em /></span>;
|
|
1145
|
+
}
|
|
1146
|
+
if (id === "aurora") {
|
|
1147
|
+
return (
|
|
1148
|
+
<span className="dkan-prev dkan-prev-aurora">
|
|
1149
|
+
<i /><i />
|
|
1150
|
+
</span>
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
if (["nebula", "warp", "radar", "constellation", "fireflies", "ocean", "prism", "circuit", "gravity", "lantern"].includes(id)) {
|
|
1154
|
+
return <span className={"dkan-prev dkan-prev-new dkan-prev-" + id}>{Array.from({ length: id === "warp" ? 8 : 6 }, (_, i) => <i key={i} />)}</span>;
|
|
1155
|
+
}
|
|
1156
|
+
if (id === "robot") {
|
|
1157
|
+
return (
|
|
1158
|
+
<span className="dkan-prev dkan-prev-bot">
|
|
1159
|
+
<RobotScene phase="code" />
|
|
1160
|
+
</span>
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
return (
|
|
1164
|
+
<span className="dkan-prev">
|
|
1165
|
+
<span className="dkan-dotwrap dkan-breathe"><span className="dkan-dot" /><span className="dkan-halo" /></span>
|
|
1166
|
+
</span>
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
function AnimationView(props) {
|
|
1171
|
+
const ctx = props && props.ctx;
|
|
1172
|
+
const snap = useAnimation(ctx);
|
|
1173
|
+
const [cfg, setCfg] = useState(null); // null = 尚未加载
|
|
1174
|
+
const [saveErr, setSaveErr] = useState("");
|
|
1175
|
+
const cfgRef = useRef(null);
|
|
1176
|
+
const pendingSavesRef = useRef(0); // 进行中的保存(轮询回包不覆盖乐观值)
|
|
1177
|
+
// 拉到新配置(含保存回包)后同步本地编辑态;保存中不覆盖
|
|
1178
|
+
useEffect(() => {
|
|
1179
|
+
const c = snap.status && snap.status.config;
|
|
1180
|
+
if (!c) return;
|
|
1181
|
+
if (pendingSavesRef.current > 0) return;
|
|
1182
|
+
if (c !== cfgRef.current) {
|
|
1183
|
+
cfgRef.current = c;
|
|
1184
|
+
setCfg(Object.assign({}, c));
|
|
1185
|
+
}
|
|
1186
|
+
}, [snap.status]);
|
|
1187
|
+
// 面板打开时兜底拉一次(浮层通常已在轮询)
|
|
1188
|
+
useEffect(() => {
|
|
1189
|
+
if (!animationStore.snap.status) animationStore.refresh();
|
|
1190
|
+
}, []);
|
|
1191
|
+
// 乐观更新 + 立即持久化(每个开关独立保存)
|
|
1192
|
+
const patch = (p) => {
|
|
1193
|
+
setSaveErr("");
|
|
1194
|
+
setCfg(Object.assign({}, cfg, p));
|
|
1195
|
+
pendingSavesRef.current++;
|
|
1196
|
+
return rpcCall("config", p)
|
|
1197
|
+
.then((d) => {
|
|
1198
|
+
pendingSavesRef.current--;
|
|
1199
|
+
animationStore.applyConfig(d && d.config);
|
|
1200
|
+
})
|
|
1201
|
+
.catch((e) => {
|
|
1202
|
+
pendingSavesRef.current--;
|
|
1203
|
+
setSaveErr("保存失败:" + ((e && e.message) || String(e)));
|
|
1204
|
+
});
|
|
1205
|
+
};
|
|
1206
|
+
|
|
1207
|
+
const rows = [];
|
|
1208
|
+
if (!cfg) {
|
|
1209
|
+
rows.push(<div key="load" className="dkan-note">{snap.error ? "状态不可用:" + snap.error : (snap.loading ? "正在拉取任务状态…" : "等待任务状态")}</div>);
|
|
1210
|
+
} else {
|
|
1211
|
+
rows.push(
|
|
1212
|
+
<div key="anim" className="dkan-sec">
|
|
1213
|
+
<div className="dkan-sec-head">
|
|
1214
|
+
<span className="dkan-sec-title">运行动画</span>
|
|
1215
|
+
<span className="dkan-sec-sub">任务进行中才出现;克制的动效,暗/亮色自适应</span>
|
|
1216
|
+
<span className="dkan-sec-sw">
|
|
1217
|
+
<span className={"dkan-sec-swlabel" + (cfg.animationEnabled ? " on" : "")}>{cfg.animationEnabled ? "已开启" : "已关闭"}</span>
|
|
1218
|
+
<button type="button" className={"dock-sw" + (cfg.animationEnabled ? " on" : "")}
|
|
1219
|
+
role="switch" aria-checked={cfg.animationEnabled} aria-label="开关运行动画"
|
|
1220
|
+
title={cfg.animationEnabled ? "关闭运行动画" : "开启运行动画"}
|
|
1221
|
+
onClick={() => patch({ animationEnabled: !cfg.animationEnabled })} />
|
|
1222
|
+
</span>
|
|
1223
|
+
</div>
|
|
1224
|
+
{cfg.animationEnabled ? (
|
|
1225
|
+
<>
|
|
1226
|
+
<div className="dkan-modes">
|
|
1227
|
+
{EFFECT_MODES.map((m) => (
|
|
1228
|
+
<button type="button" key={m.id}
|
|
1229
|
+
className={"dkan-mode" + (cfg.effectMode === m.id ? " on" : "")}
|
|
1230
|
+
onClick={() => patch({ effectMode: m.id })}>
|
|
1231
|
+
<span className="dkan-mode-name">{m.name}</span>
|
|
1232
|
+
<ModePreview id={m.id} />
|
|
1233
|
+
<span className="dkan-mode-desc">{m.desc}</span>
|
|
1234
|
+
</button>
|
|
1235
|
+
))}
|
|
1236
|
+
</div>
|
|
1237
|
+
{cfg.effectMode === "robot" ? <div className="dkan-robot-controls">
|
|
1238
|
+
<div className="dkan-robot-controls-head">
|
|
1239
|
+
<span>桌面伙伴大小</span>
|
|
1240
|
+
<strong>{Math.round((Number(cfg.robotScale) || 1.35) * 100)}%</strong>
|
|
1241
|
+
</div>
|
|
1242
|
+
<div className="dkan-robot-size-options" role="group" aria-label="桌面伙伴大小预设">
|
|
1243
|
+
{[[1, "紧凑"], [1.35, "默认"], [1.7, "加大"], [2.1, "特大"]].map(([scale, label]) => (
|
|
1244
|
+
<button type="button" key={scale} className={Math.abs((Number(cfg.robotScale) || 1.35) - scale) < .03 ? "on" : ""}
|
|
1245
|
+
onClick={() => patch({ robotScale: scale })}>{label}</button>
|
|
1246
|
+
))}
|
|
1247
|
+
</div>
|
|
1248
|
+
<div className="dkan-robot-slider-row">
|
|
1249
|
+
<input type="range" min="0.85" max="2.2" step="0.05" value={Number(cfg.robotScale) || 1.35}
|
|
1250
|
+
aria-label="桌面伙伴大小百分比"
|
|
1251
|
+
onChange={(e) => patch({ robotScale: Number(e.target.value) })} />
|
|
1252
|
+
<span>浮层右下角也可直接拖动缩放,大小会自动保存。</span>
|
|
1253
|
+
</div>
|
|
1254
|
+
</div> : null}
|
|
1255
|
+
</>
|
|
1256
|
+
) : <div className="dkan-note">动画已关闭——任务进行中不会有任何动效(通知设置见左侧「任务通知」)。</div>}
|
|
1257
|
+
</div>
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
if (saveErr) rows.push(<div key="err" className="dkan-note dkan-err">{saveErr}</div>);
|
|
1261
|
+
// 运行状态(进行中任务 / 最近完成 / 等待确认)已独立成左侧菜单项,这里只留一句指路
|
|
1262
|
+
rows.push(
|
|
1263
|
+
<div key="hint" className="dkan-note">
|
|
1264
|
+
任务运行状态(进行中任务、阶段、Token、等待确认与最近完成)已独立成左侧「运行状态」菜单;本页只负责动效。
|
|
1265
|
+
</div>
|
|
1266
|
+
);
|
|
1267
|
+
return <div className="dkan-root">{rows}</div>;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// ---------- 首页总揽概要 ----------
|
|
1271
|
+
// 任务进行中 / 空闲这类任务信息归【运行状态】模块,这里只报动画自身的状态:
|
|
1272
|
+
// 动效开关 + 当前模式(桌面伙伴另报大小),与面板页开关一一对应。
|
|
1273
|
+
function AnimationStat(props) {
|
|
1274
|
+
const snap = useAnimation(props && props.ctx);
|
|
1275
|
+
const st = snap.status;
|
|
1276
|
+
if (snap.error) return <span className="dkan-err">动画状态不可用(宿主需重启加载动画路由)</span>;
|
|
1277
|
+
if (!st) return <span>等待动画配置…</span>;
|
|
1278
|
+
const cfg = st.config;
|
|
1279
|
+
if (!cfg) return <span>等待动画配置…</span>;
|
|
1280
|
+
if (!cfg.animationEnabled) return <span>动效已关闭 · 任务进行中不显示动画</span>;
|
|
1281
|
+
const mode = EFFECT_MODES.find((m) => m.id === cfg.effectMode) || EFFECT_MODES[0];
|
|
1282
|
+
const scale = Math.round((Number(cfg.robotScale) || 1.35) * 100);
|
|
1283
|
+
return <span>{"动效:" + mode.name + (cfg.effectMode === "robot" ? " · 大小 " + scale + "%" : "")}</span>;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// ---------- 样式(dkan- 前缀;全部走主题变量,暗/亮色自适应;动效时长 = 基准 / --dkan-speed) ----------
|
|
1287
|
+
const css = [
|
|
1288
|
+
// 顶部流光细线(3px,比首版更醒目)
|
|
1289
|
+
".dkan-line{position:fixed;top:0;left:0;right:0;height:3px;z-index:9990;pointer-events:none;background:color-mix(in srgb,var(--dk-accent) 30%,transparent);}",
|
|
1290
|
+
".dkan-line::after{content:\"\";position:absolute;top:0;bottom:0;left:-42%;width:42%;background:linear-gradient(90deg,transparent,var(--dk-accent) 60%,#fff);box-shadow:0 0 10px 1px color-mix(in srgb,var(--dk-accent) 70%,transparent);animation:dkan-flow calc(1.9s / var(--dkan-speed,1)) linear infinite;}",
|
|
1291
|
+
"@keyframes dkan-flow{to{left:100%}}",
|
|
1292
|
+
// 完成瞬间的一次性流光(成功绿 / 异常红),动画结束自动清场
|
|
1293
|
+
".dkan-done{position:fixed;top:0;left:0;right:0;height:3px;z-index:9991;pointer-events:none;background:linear-gradient(90deg,transparent,var(--dsw-alias-state-success-primary,#34d399) 50%,#fff);background-size:50% 100%;background-repeat:no-repeat;animation:dkan-done 1.1s var(--ds-ease-in-out) forwards;}",
|
|
1294
|
+
".dkan-done.err{background-image:linear-gradient(90deg,transparent,var(--dsw-alias-state-error-primary,#f87171) 50%,#fff);}",
|
|
1295
|
+
"@keyframes dkan-done{0%{background-position:-60% 0;opacity:0}25%{opacity:1}100%{background-position:160% 0;opacity:0}}",
|
|
1296
|
+
// 环屏巡航:光点沿屏幕边缘转一整圈
|
|
1297
|
+
".dkan-orbit{position:fixed;top:0;left:0;width:12px;height:12px;z-index:9990;pointer-events:none;border-radius:50%;background:var(--dk-accent);box-shadow:0 0 14px 4px color-mix(in srgb,var(--dk-accent) 60%,transparent),0 0 30px 8px color-mix(in srgb,var(--dk-accent) 25%,transparent);animation:dkan-orbit calc(14s / var(--dkan-speed,1)) linear infinite;}",
|
|
1298
|
+
"@keyframes dkan-orbit{0%{top:0;left:0}25%{top:0;left:calc(100vw - 12px)}50%{top:calc(100vh - 12px);left:calc(100vw - 12px)}75%{top:calc(100vh - 12px);left:0}100%{top:0;left:0}}",
|
|
1299
|
+
// ===== 氛围动效 =====
|
|
1300
|
+
".dkan-amb{position:fixed;inset:0;z-index:9989;pointer-events:none;overflow:hidden;}",
|
|
1301
|
+
// pointer-events 不继承:全屏装饰层的子粒子(星点/流星/极光/火花/彩带)必须各自透明于点击
|
|
1302
|
+
".dkan-amb *,.dkan-fxwrap *{pointer-events:none;}",
|
|
1303
|
+
// 代码雨:字符列缓落(提亮加大,速度随吞吐)
|
|
1304
|
+
".dkan-matrix span{position:absolute;top:-12%;writing-mode:vertical-rl;font-family:var(--ds-font-family-code,monospace);color:var(--dk-accent);text-shadow:0 0 6px color-mix(in srgb,var(--dk-accent) 60%,transparent);animation:dkan-fall linear infinite;will-change:transform;}",
|
|
1305
|
+
"@keyframes dkan-fall{to{transform:translateY(125vh)}}",
|
|
1306
|
+
// 星野:星点缓慢飘移 + 闪烁(加亮加发光)+ 月亮 + 流星
|
|
1307
|
+
".dkan-stars .dkan-star{position:absolute;border-radius:50%;background:#cfe3ff;box-shadow:0 0 4px 1px color-mix(in srgb,#8ab6ff 55%,transparent);animation:dkan-star ease-in-out infinite alternate;}",
|
|
1308
|
+
"@keyframes dkan-star{0%{opacity:.3;transform:translate(0,0)}50%{opacity:1}100%{opacity:.45;transform:translate(var(--dx),var(--dy))}}",
|
|
1309
|
+
// 月亮:右上弯月(radial-gradient 咬出月牙,透明区不遮挡背景;外圈柔光晕)
|
|
1310
|
+
".dkan-moon{position:absolute;top:9%;right:10%;width:36px;height:36px;border-radius:50%;background:radial-gradient(circle at 66% 34%,transparent 0 44%,#fdf6d8 46%,#e8d9a0 78%,#d8c78a);box-shadow:0 0 18px 4px color-mix(in srgb,#fdf6d8 35%,transparent),0 0 44px 12px color-mix(in srgb,#fdf6d8 15%,transparent);}",
|
|
1311
|
+
// 流星:斜向划过的光带(亮头 + 渐隐尾),周期出现
|
|
1312
|
+
".dkan-meteor{position:absolute;width:110px;height:2px;border-radius:2px;background:linear-gradient(90deg,#fff,color-mix(in srgb,#8ab6ff 60%,transparent) 40%,transparent);transform:rotate(35deg);transform-origin:left center;opacity:0;animation:dkan-meteor 8.4s linear infinite;}",
|
|
1313
|
+
".dkan-meteor::before{content:\"\";position:absolute;left:-2px;top:-2px;width:6px;height:6px;border-radius:50%;background:#fff;box-shadow:0 0 8px 2px color-mix(in srgb,#cfe3ff 70%,transparent);}",
|
|
1314
|
+
"@keyframes dkan-meteor{0%{opacity:0;transform:rotate(35deg) translateX(0)}3%{opacity:1}10%{opacity:0;transform:rotate(35deg) translateX(46vh)}100%{opacity:0;transform:rotate(35deg) translateX(46vh)}}",
|
|
1315
|
+
// 星际远征:星尘、行星轨道和任务飞船都只在任务运行时出现,统一由 --dkan-speed 加速。
|
|
1316
|
+
".dkan-space{background:radial-gradient(ellipse 34% 28% at 84% 11%,color-mix(in srgb,#426aaf 15%,transparent),transparent 72%),radial-gradient(ellipse 26% 22% at 14% 58%,color-mix(in srgb,#7c4dff 10%,transparent),transparent 74%);}",
|
|
1317
|
+
".dkan-space-dust{position:absolute;border-radius:50%;background:#dbeafe;box-shadow:0 0 5px color-mix(in srgb,#93c5fd 62%,transparent);opacity:.2;animation:dkan-space-dust ease-in-out infinite alternate;will-change:transform,opacity;}",
|
|
1318
|
+
"@keyframes dkan-space-dust{0%{opacity:.14;transform:translate3d(0,0,0) scale(.7)}55%{opacity:.78}100%{opacity:.26;transform:translate3d(22px,-8px,0) scale(1.15)}}",
|
|
1319
|
+
".dkan-space-system{position:fixed;width:246px;height:246px;border-radius:50%;opacity:.78;transform:translateZ(0) scale(var(--dkan-system-scale,1));transform-origin:center;contain:layout paint style;}.dkan-space-system.alpha{opacity:.76}.dkan-space-system.beta{opacity:.68}.dkan-space-system.gamma{opacity:.64}",
|
|
1320
|
+
".dkan-space-sun{position:absolute;left:50%;top:50%;width:34px;height:34px;border-radius:50%;transform:translate(-50%,-50%);background:radial-gradient(circle at 32% 28%,#fff7c2 0 9%,#ffc85a 30%,#e76f2e 74%);box-shadow:0 0 12px 4px color-mix(in srgb,#ffc85a 58%,transparent),0 0 36px 12px color-mix(in srgb,#f97316 20%,transparent);}",
|
|
1321
|
+
".dkan-space-orbit{position:absolute;left:50%;top:50%;border:1px solid color-mix(in srgb,#93c5fd 28%,transparent);border-radius:50%;animation:dkan-space-orbit linear infinite;transform-origin:center;}",
|
|
1322
|
+
".dkan-space-orbit i{position:absolute;top:50%;left:-5px;width:10px;height:10px;border-radius:50%;transform:translateY(-50%);box-shadow:0 0 9px 2px currentColor;}",
|
|
1323
|
+
".dkan-space-orbit.a{width:82px;height:50px;margin:-25px 0 0 -41px;animation-duration:calc(5.4s / var(--dkan-speed,1));}.dkan-space-orbit.a i{color:#67e8f9;background:#22d3ee;}",
|
|
1324
|
+
".dkan-space-orbit.b{width:142px;height:86px;margin:-43px 0 0 -71px;animation-duration:calc(9.2s / var(--dkan-speed,1));animation-direction:reverse;}.dkan-space-orbit.b i{width:15px;height:15px;color:#c4b5fd;background:radial-gradient(circle at 30% 28%,#ede9fe,#8b5cf6 72%);}",
|
|
1325
|
+
".dkan-space-orbit.c{width:212px;height:130px;margin:-65px 0 0 -106px;animation-duration:calc(15.6s / var(--dkan-speed,1));}.dkan-space-orbit.c i{width:18px;height:18px;color:#fda4af;background:radial-gradient(circle at 32% 28%,#ffe4e6,#e11d48 74%);}",
|
|
1326
|
+
"@keyframes dkan-space-orbit{to{transform:rotate(360deg)}}",
|
|
1327
|
+
".dkan-space-runner{position:fixed;left:0;top:0;width:108px;height:56px;opacity:0;animation-duration:calc(10.8s / var(--dkan-speed,1));animation-timing-function:linear;animation-iteration-count:infinite;animation-delay:var(--dkan-run-delay,0s);will-change:transform,opacity;contain:layout paint style;backface-visibility:hidden;}.dkan-space-runner .dkan-freighter{left:0;top:0;transform:translateZ(0) scale(.52);transform-origin:top left;filter:none}.dkan-space-runner .dkan-freighter-engine i{animation:none;opacity:.76;box-shadow:-6px 0 8px #22d3ee}.dkan-space-runner .dkan-freighter-nose{filter:none}.dkan-space-runner.route-0{animation-name:dkan-space-route-a}.dkan-space-runner.route-1{animation-name:dkan-space-route-b}.dkan-space-runner.route-2{animation-name:dkan-space-route-c}",
|
|
1328
|
+
"@keyframes dkan-space-route-a{0%{opacity:0;transform:translate3d(var(--dkan-alpha-x),var(--dkan-alpha-y),0) rotate(151deg)}7%,86%{opacity:.9}100%{opacity:0;transform:translate3d(var(--dkan-beta-x),var(--dkan-beta-y),0) rotate(151deg)}}@keyframes dkan-space-route-b{0%{opacity:0;transform:translate3d(var(--dkan-beta-x),var(--dkan-beta-y),0) rotate(-90deg)}7%,86%{opacity:.9}100%{opacity:0;transform:translate3d(var(--dkan-gamma-x),var(--dkan-gamma-y),0) rotate(-90deg)}}@keyframes dkan-space-route-c{0%{opacity:0;transform:translate3d(var(--dkan-gamma-x),var(--dkan-gamma-y),0) rotate(-18deg)}7%,86%{opacity:.9}100%{opacity:0;transform:translate3d(var(--dkan-alpha-x),var(--dkan-alpha-y),0) rotate(-18deg)}}",
|
|
1329
|
+
// 完成归航:一艘货运旗舰从航线飞到真实会话输入框上方,货舱逐格表达实际输出量。
|
|
1330
|
+
".dkan-delivery{position:fixed;left:0;top:0;width:210px;height:88px;z-index:9994;pointer-events:none;will-change:transform,opacity;contain:layout paint style;isolation:isolate;animation:dkan-delivery-flight var(--dkan-delivery-duration,8600ms) cubic-bezier(.22,.61,.36,1) forwards;}",
|
|
1331
|
+
"@keyframes dkan-delivery-flight{0%{opacity:0;transform:translate3d(var(--dkan-from-x),var(--dkan-from-y),0) scale(.5) rotate(-12deg)}6%{opacity:1;transform:translate3d(var(--dkan-from-x),var(--dkan-from-y),0) scale(.56) rotate(-10deg)}100%{opacity:1;transform:translate3d(var(--dkan-to-x),var(--dkan-to-y),0) scale(1) rotate(0)}}",
|
|
1332
|
+
".dkan-delivery-trail{position:absolute;left:-132px;top:26px;width:154px;height:4px;border-radius:999px;background:linear-gradient(90deg,transparent,#34d399 48%,#d9f99d 78%,transparent);opacity:.9;animation:dkan-delivery-trail var(--dkan-delivery-duration,8600ms) linear forwards;}@keyframes dkan-delivery-trail{0%,82%{opacity:.9}100%{opacity:0}}.dkan-freighter{position:absolute;left:10px;top:10px;width:176px;height:45px;display:block;filter:drop-shadow(0 0 9px color-mix(in srgb,#7dd3fc 62%,transparent));}.dkan-delivery .dkan-freighter,.dkan-departure .dkan-freighter{filter:none}.dkan-delivery .dkan-freighter-engine i,.dkan-departure .dkan-freighter-engine i{animation:none;opacity:.76;box-shadow:-6px 0 8px #22d3ee}.dkan-delivery .dkan-freighter-nose,.dkan-departure .dkan-freighter-nose{filter:none}.dkan-freighter-hull{position:absolute;z-index:3;left:21px;top:7px;width:126px;height:31px;background:linear-gradient(150deg,#f8fbff 0%,#9cc8ff 30%,#385d91 72%,#1d3150);clip-path:polygon(0 30%,43% 0,83% 17%,100% 50%,83% 83%,43% 100%,0 70%,12% 50%);box-shadow:inset 0 1px 0 rgb(255 255 255 / .7),inset -7px -5px 9px rgb(8 24 49 / .38);}.dkan-freighter-nose{position:absolute;z-index:4;right:10px;top:16px;width:28px;height:14px;background:linear-gradient(90deg,#a9d1ff,#e8f5ff);clip-path:polygon(0 0,100% 50%,0 100%,22% 50%);filter:drop-shadow(5px 0 5px color-mix(in srgb,#93c5fd 62%,transparent));}.dkan-freighter-canopy{position:absolute;z-index:5;left:77px;top:10px;width:39px;height:11px;border-radius:70% 48% 42% 32%;background:linear-gradient(135deg,#ecfeff 0%,#67e8f9 32%,#2563eb 75%);transform:skewX(-18deg);box-shadow:inset 0 2px 2px rgb(255 255 255 / .7),0 0 8px color-mix(in srgb,#67e8f9 42%,transparent);}.dkan-freighter-fin{position:absolute;z-index:2;left:48px;top:3px;width:24px;height:17px;background:linear-gradient(135deg,#31557f,#a7caf7);clip-path:polygon(0 100%,58% 0,100% 100%);}.dkan-freighter-wing{position:absolute;z-index:1;left:48px;width:60px;height:18px;background:linear-gradient(135deg,#24436d,#8ebaf4);}.dkan-freighter-wing.left{top:2px;clip-path:polygon(0 100%,100% 0,68% 100%)}.dkan-freighter-wing.right{top:26px;clip-path:polygon(0 0,68% 0,100% 100%)}",
|
|
1333
|
+
".dkan-freighter-cargo{position:absolute;z-index:6;left:37px;top:31px;width:74px;height:7px;display:flex;align-items:center;gap:2px;padding:1px 3px;border-radius:4px;background:#172842;border:1px solid color-mix(in srgb,#b8d8ff 48%,transparent);box-shadow:inset 0 1px 0 rgb(255 255 255 / .2);}.dkan-freighter-cargo i{display:block;flex:1;min-width:4px;height:5px;border-radius:1px;background:linear-gradient(135deg,#fef3c7,#f59e0b 58%,#b45309);box-shadow:0 0 4px color-mix(in srgb,#fbbf24 58%,transparent);}.dkan-freighter-cargo.empty i{background:linear-gradient(135deg,#dbeafe,#38bdf8 58%,#1d4ed8);box-shadow:0 0 4px color-mix(in srgb,#38bdf8 55%,transparent);}.dkan-freighter-engine{position:absolute;z-index:0;left:5px;top:19px;display:flex;gap:3px;align-items:center;height:11px;}.dkan-freighter-engine i{width:18px;height:5px;border-radius:60% 0 0 60%;background:linear-gradient(90deg,transparent,#67e8f9);box-shadow:-9px 0 12px #22d3ee;animation:dkan-engine .34s ease-in-out infinite alternate;}.dkan-freighter-engine i:nth-child(2){animation-delay:-.14s}",
|
|
1334
|
+
"@keyframes dkan-engine{to{transform:scaleX(.55);opacity:.5}}",
|
|
1335
|
+
".dkan-delivery-label{position:absolute;left:21px;top:58px;display:flex;align-items:baseline;gap:6px;padding:4px 8px;border:1px solid color-mix(in srgb,#93c5fd 30%,var(--dsw-alias-border-l1));border-radius:999px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 82%,transparent);backdrop-filter:blur(10px);color:var(--dsw-alias-label-secondary);font:10px/1.2 var(--ds-font-family,system-ui,sans-serif);white-space:nowrap;}.dkan-delivery-label b{color:var(--dsw-alias-label-primary);font-weight:650}.dkan-delivery-label span{color:var(--dk-accent);}",
|
|
1336
|
+
"@media (max-width:1100px){.dkan-space-system,.dkan-space-runner{display:none}}",
|
|
1337
|
+
".dkan-ready-freighter{position:fixed;width:210px;height:88px;z-index:9994;pointer-events:none;filter:drop-shadow(0 10px 14px rgb(0 0 0 / .18));}.dkan-ready-freighter>span:last-child{position:absolute;left:21px;top:58px;padding:4px 8px;border:1px solid color-mix(in srgb,#93c5fd 28%,var(--dsw-alias-border-l1));border-radius:999px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 80%,transparent);backdrop-filter:blur(10px);color:var(--dsw-alias-label-secondary);font:10px/1.2 var(--ds-font-family,system-ui,sans-serif);white-space:nowrap;}.dkan-departure{position:fixed;left:0;top:0;width:210px;height:88px;z-index:9994;pointer-events:none;will-change:transform,opacity;contain:layout paint style;isolation:isolate;animation:dkan-departure-flight var(--dkan-departure-duration,8600ms) cubic-bezier(.42,0,.85,.45) forwards;}@keyframes dkan-departure-flight{0%{opacity:0;transform:translate3d(var(--dkan-from-x),var(--dkan-from-y),0) scale(1) rotate(var(--dkan-flight-angle,0deg))}6%{opacity:1;transform:translate3d(var(--dkan-from-x),var(--dkan-from-y),0) scale(1) rotate(var(--dkan-flight-angle,0deg))}100%{opacity:0;transform:translate3d(var(--dkan-to-x),var(--dkan-to-y),0) scale(.24) rotate(var(--dkan-flight-angle,0deg))}}",
|
|
1338
|
+
".dkan-space-stuck{position:fixed;right:20px;top:88px;width:244px;height:160px;z-index:9993;pointer-events:none;border:1px solid color-mix(in srgb,#f59e0b 48%,var(--dsw-alias-border-l1));border-radius:20px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 88%,transparent);backdrop-filter:blur(16px);box-shadow:0 18px 54px rgb(0 0 0 / .28),0 0 30px color-mix(in srgb,#f59e0b 15%,transparent);}.dkan-space-stuck .dkan-freighter{left:37px;top:11px;transform:scale(.9);transform-origin:top left;}.dkan-stuck-code{position:absolute;left:17px;right:17px;top:60px;height:36px;border-radius:7px;background:linear-gradient(90deg,color-mix(in srgb,#f59e0b 18%,transparent),transparent);color:var(--dk-warn);font:700 14px/36px var(--ds-font-family-code,monospace);letter-spacing:.12em;text-align:center;overflow:hidden;}.dkan-stuck-code::after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,transparent,color-mix(in srgb,#fef3c7 70%,transparent),transparent);transform:translateX(-110%);animation:dkan-stuck-scan 1.25s linear infinite;}.dkan-stuck-code i{display:inline-block;width:7px;height:7px;margin-right:8px;border-radius:50%;background:#f59e0b;box-shadow:0 0 9px #f59e0b;animation:dkan-stuck-pulse .8s ease-in-out infinite alternate;}.dkan-space-stuck strong{position:absolute;left:0;right:0;top:108px;color:var(--dsw-alias-label-primary);font:650 13px/1.2 var(--ds-font-family,system-ui,sans-serif);text-align:center;}.dkan-space-stuck small{position:absolute;left:18px;right:18px;top:128px;color:var(--dsw-alias-label-secondary);font:11px/1.25 var(--ds-font-family,system-ui,sans-serif);text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}@keyframes dkan-stuck-scan{to{transform:translateX(110%)}}@keyframes dkan-stuck-pulse{to{transform:scale(.55);opacity:.45}}",
|
|
1339
|
+
// 窄屏(手机/分屏)贴右会溢出:回落到水平居中、屏幕上方,避开正文重心
|
|
1340
|
+
"@media (max-width:760px){.dkan-space-stuck{right:50%;transform:translateX(50%);top:64px;}}",
|
|
1341
|
+
// 极光:顶部柔光带呼吸流动(加浓)
|
|
1342
|
+
".dkan-aurora i{position:absolute;top:-40%;left:-20%;width:70%;height:80%;border-radius:50%;filter:blur(50px);opacity:.32;animation:dkan-aurora ease-in-out infinite alternate;}",
|
|
1343
|
+
".dkan-aurora i:nth-child(1){background:#4d9fff;}",
|
|
1344
|
+
".dkan-aurora i:nth-child(2){background:#34d399;left:20%;animation-delay:-2s;}",
|
|
1345
|
+
".dkan-aurora i:nth-child(3){background:#a78bfa;left:55%;animation-delay:-4s;}",
|
|
1346
|
+
"@keyframes dkan-aurora{0%{transform:translateX(-8%) scaleY(1)}100%{transform:translateX(10%) scaleY(1.3)}}",
|
|
1347
|
+
// 星云潮汐:无动态模糊,柔边由径向渐变生成,移动只发生在合成层。
|
|
1348
|
+
".dkan-nebula{contain:strict}.dkan-nebula i{position:absolute;width:48vw;aspect-ratio:1;border-radius:50%;opacity:.22;background:radial-gradient(circle,color-mix(in srgb,var(--dkan-phase,#60a5fa) 48%,transparent),color-mix(in srgb,#8b5cf6 18%,transparent) 42%,transparent 70%);animation:dkan-nebula-drift calc(18s / var(--dkan-speed,1)) cubic-bezier(.45,0,.55,1) infinite alternate;will-change:transform,opacity}.dkan-nebula i:nth-child(1){left:-18%;top:-24%}.dkan-nebula i:nth-child(2){right:-18%;top:4%;background:radial-gradient(circle,color-mix(in srgb,#22d3ee 34%,transparent),color-mix(in srgb,#3b82f6 15%,transparent) 45%,transparent 72%);animation-delay:-5s}.dkan-nebula i:nth-child(3){left:4%;bottom:-34%;background:radial-gradient(circle,color-mix(in srgb,#f472b6 28%,transparent),color-mix(in srgb,#8b5cf6 14%,transparent) 48%,transparent 72%);animation-delay:-10s}.dkan-nebula i:nth-child(4){right:8%;bottom:-26%;width:36vw;animation-delay:-14s}",
|
|
1349
|
+
"@keyframes dkan-nebula-drift{0%{opacity:.14;transform:translate3d(-3vw,-2vh,0) scale(.92)}100%{opacity:.3;transform:translate3d(5vw,4vh,0) scale(1.12)}}",
|
|
1350
|
+
// 曲速航道:从视口中心放射的独立光束。
|
|
1351
|
+
".dkan-warp{background:radial-gradient(circle at center,color-mix(in srgb,var(--dkan-phase,#60a5fa) 10%,transparent),transparent 34%);contain:strict}.dkan-warp i{position:absolute;left:50%;top:50%;width:var(--len);height:1px;border-radius:999px;transform-origin:left center;background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--dkan-phase,#60a5fa) 48%,transparent),#e0f2fe);opacity:0;animation-name:dkan-warp-ray;animation-timing-function:cubic-bezier(.3,.1,.8,1);animation-iteration-count:infinite;will-change:transform,opacity}",
|
|
1352
|
+
"@keyframes dkan-warp-ray{0%{opacity:0;transform:rotate(var(--a)) translate3d(18px,0,0) scaleX(.05)}18%{opacity:.6}100%{opacity:0;transform:rotate(var(--a)) translate3d(58vw,0,0) scaleX(1.55)}}",
|
|
1353
|
+
// 量子雷达:三个边缘站点各自扫描,避免占据会话正文中心。
|
|
1354
|
+
".dkan-radar{contain:strict}.dkan-radar-station{position:absolute;width:176px;height:176px;border-radius:50%;opacity:.44;background:repeating-radial-gradient(circle,color-mix(in srgb,var(--dkan-phase,#60a5fa) 28%,transparent) 0 1px,transparent 1px 29px);border:1px solid color-mix(in srgb,var(--dkan-phase,#60a5fa) 22%,transparent)}.dkan-radar-station.a{right:4%;top:8%}.dkan-radar-station.b{left:1%;top:38%;transform:scale(.72)}.dkan-radar-station.c{right:7%;bottom:9%;transform:scale(.58)}.dkan-radar-station i{position:absolute;inset:0;border-radius:50%;background:conic-gradient(from 0deg,transparent 0 76%,color-mix(in srgb,var(--dkan-phase,#60a5fa) 44%,transparent) 92%,transparent);animation:dkan-radar-sweep calc(4.8s / var(--dkan-speed,1)) linear infinite;will-change:transform}.dkan-radar-station b{position:absolute;left:67%;top:31%;width:7px;height:7px;border-radius:50%;background:var(--dkan-phase,#60a5fa);box-shadow:0 0 12px 4px color-mix(in srgb,var(--dkan-phase,#60a5fa) 58%,transparent);animation:dkan-radar-blip calc(2.4s / var(--dkan-speed,1)) ease-in-out infinite}",
|
|
1355
|
+
"@keyframes dkan-radar-sweep{to{transform:rotate(360deg)}}@keyframes dkan-radar-blip{0%,70%,100%{opacity:.15;transform:scale(.65)}78%{opacity:1;transform:scale(1.25)}}",
|
|
1356
|
+
// 星座网络:轻量 SVG 线网分段点亮。
|
|
1357
|
+
".dkan-constellation svg{position:absolute;left:8%;top:7%;width:86%;height:78%;overflow:visible}.dkan-constellation path{fill:none;stroke:color-mix(in srgb,var(--dkan-phase,#60a5fa) 42%,transparent);stroke-width:.16;vector-effect:non-scaling-stroke;stroke-dasharray:.08 .025;animation:dkan-constellation-line calc(7s / var(--dkan-speed,1)) linear infinite}.dkan-constellation circle{fill:var(--dkan-phase,#60a5fa);filter:drop-shadow(0 0 2px var(--dkan-phase,#60a5fa));animation:dkan-constellation-star calc(2.8s / var(--dkan-speed,1)) ease-in-out infinite alternate;transform-box:fill-box;transform-origin:center}",
|
|
1358
|
+
"@keyframes dkan-constellation-line{to{stroke-dashoffset:-1}}@keyframes dkan-constellation-star{0%{opacity:.28;transform:scale(.68)}100%{opacity:.92;transform:scale(1.22)}}",
|
|
1359
|
+
// 数据萤火:二维漂移和明暗呼吸组合在同一个 transform 动画中。
|
|
1360
|
+
".dkan-fireflies{contain:strict}.dkan-fireflies i{position:absolute;border-radius:50%;background:color-mix(in srgb,var(--dkan-phase,#60a5fa) 78%,#fff);box-shadow:0 0 8px 2px color-mix(in srgb,var(--dkan-phase,#60a5fa) 46%,transparent);opacity:.2;animation-name:dkan-firefly;animation-timing-function:cubic-bezier(.45,0,.55,1);animation-iteration-count:infinite;animation-direction:alternate;will-change:transform,opacity}",
|
|
1361
|
+
"@keyframes dkan-firefly{0%{opacity:.12;transform:translate3d(0,0,0) scale(.65)}48%{opacity:.86}100%{opacity:.26;transform:translate3d(var(--dx),var(--dy),0) scale(1.18)}}",
|
|
1362
|
+
// 深海脉动:波层在底部横向漂移,气泡只做向上合成位移。
|
|
1363
|
+
".dkan-ocean{background:linear-gradient(0deg,color-mix(in srgb,#0284c7 12%,transparent),transparent 46%);contain:strict}.dkan-ocean-waves{position:absolute;left:-12%;right:-12%;bottom:0;height:42%}.dkan-ocean-waves i{position:absolute;left:0;width:112%;height:52%;border-top:1px solid color-mix(in srgb,#67e8f9 28%,transparent);border-radius:50%;animation:dkan-ocean-wave calc(9s / var(--dkan-speed,1)) ease-in-out infinite alternate;will-change:transform}.dkan-ocean-waves i:nth-child(1){top:2%}.dkan-ocean-waves i:nth-child(2){top:18%;animation-delay:-2s}.dkan-ocean-waves i:nth-child(3){top:34%;animation-delay:-4s}.dkan-ocean-waves i:nth-child(4){top:50%;animation-delay:-6s}.dkan-ocean-waves i:nth-child(5){top:66%;animation-delay:-8s}.dkan-ocean>b{position:absolute;bottom:-20px;border:1px solid color-mix(in srgb,#a5f3fc 54%,transparent);border-radius:50%;opacity:0;animation-name:dkan-ocean-bubble;animation-timing-function:linear;animation-iteration-count:infinite;will-change:transform,opacity}",
|
|
1364
|
+
"@keyframes dkan-ocean-wave{to{transform:translate3d(7%,5px,0) scaleY(1.08)}}@keyframes dkan-ocean-bubble{0%{opacity:0;transform:translate3d(0,0,0) scale(.7)}14%{opacity:.58}100%{opacity:0;transform:translate3d(var(--drift),-82vh,0) scale(1.18)}}",
|
|
1365
|
+
// 棱镜光谱:七条低透明度光带错峰穿过视口。
|
|
1366
|
+
".dkan-prism{contain:strict}.dkan-prism i{position:absolute;left:-32vw;width:160vw;height:2px;border-radius:999px;opacity:0;background:linear-gradient(90deg,transparent,#ef4444,#f59e0b,#facc15,#22c55e,#22d3ee,#6366f1,#a855f7,transparent);animation:dkan-prism-ray calc(8s / var(--dkan-speed,1)) cubic-bezier(.45,0,.55,1) infinite;will-change:transform,opacity}.dkan-prism i:nth-child(1){top:12%;animation-delay:0s}.dkan-prism i:nth-child(2){top:25%;animation-delay:-1.1s}.dkan-prism i:nth-child(3){top:38%;animation-delay:-2.2s}.dkan-prism i:nth-child(4){top:51%;animation-delay:-3.3s}.dkan-prism i:nth-child(5){top:64%;animation-delay:-4.4s}.dkan-prism i:nth-child(6){top:77%;animation-delay:-5.5s}.dkan-prism i:nth-child(7){top:90%;animation-delay:-6.6s}",
|
|
1367
|
+
"@keyframes dkan-prism-ray{0%{opacity:0;transform:translate3d(-12vw,0,0) rotate(-7deg) scaleX(.7)}34%{opacity:.2}66%{opacity:.36}100%{opacity:0;transform:translate3d(20vw,0,0) rotate(-7deg) scaleX(1.05)}}",
|
|
1368
|
+
// 神经电路:少量 SVG 路径的虚线脉冲,节点保持低透明度。
|
|
1369
|
+
".dkan-circuit svg{position:absolute;inset:5% 3% 9% 8%;width:89%;height:86%;overflow:visible}.dkan-circuit path{fill:none;stroke:color-mix(in srgb,var(--dkan-phase,#60a5fa) 40%,transparent);stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:.045 .12;animation:dkan-circuit-pulse calc(5.6s / var(--dkan-speed,1)) linear infinite}.dkan-circuit circle{fill:var(--dkan-phase,#60a5fa);opacity:.58;filter:drop-shadow(0 0 4px var(--dkan-phase,#60a5fa));animation:dkan-circuit-node calc(2.2s / var(--dkan-speed,1)) ease-in-out infinite alternate}",
|
|
1370
|
+
"@keyframes dkan-circuit-pulse{to{stroke-dashoffset:-1}}@keyframes dkan-circuit-node{to{opacity:.16}}",
|
|
1371
|
+
// 引力涟漪:三个边缘引力源按错峰节奏扩散。
|
|
1372
|
+
".dkan-gravity{contain:strict}.dkan-gravity-source{position:absolute;width:260px;height:260px}.dkan-gravity-source.a{right:2%;top:5%}.dkan-gravity-source.b{left:-4%;top:39%;transform:scale(.76)}.dkan-gravity-source.c{right:17%;bottom:-10%;transform:scale(.62)}.dkan-gravity-source b{position:absolute;left:50%;top:50%;width:12px;height:12px;margin:-6px;border-radius:50%;background:var(--dkan-phase,#60a5fa);box-shadow:0 0 16px 5px color-mix(in srgb,var(--dkan-phase,#60a5fa) 42%,transparent)}.dkan-gravity-source i{position:absolute;left:50%;top:50%;width:26px;height:26px;margin:-13px;border:1px solid color-mix(in srgb,var(--dkan-phase,#60a5fa) 42%,transparent);border-radius:50%;opacity:0;animation:dkan-gravity-wave calc(4.6s / var(--dkan-speed,1)) cubic-bezier(.2,.55,.35,1) infinite;will-change:transform,opacity}",
|
|
1373
|
+
"@keyframes dkan-gravity-wave{0%{opacity:.64;transform:scale(.3)}100%{opacity:0;transform:scale(9)}}",
|
|
1374
|
+
// 灵感天灯:暖色灯体从屏幕底部缓慢升起,尾迹由伪元素提供。
|
|
1375
|
+
".dkan-lantern{contain:strict}.dkan-lantern i{position:absolute;bottom:-30px;border-radius:45% 45% 34% 34%;background:linear-gradient(150deg,#fff7c2 0%,#fbbf24 46%,#f97316 100%);box-shadow:0 0 10px 3px color-mix(in srgb,#fbbf24 36%,transparent);opacity:0;animation-name:dkan-lantern-rise;animation-timing-function:cubic-bezier(.34,.02,.6,1);animation-iteration-count:infinite;will-change:transform,opacity}.dkan-lantern i::after{content:'';position:absolute;left:42%;top:100%;width:16%;height:13px;background:linear-gradient(#f59e0b,transparent)}",
|
|
1376
|
+
"@keyframes dkan-lantern-rise{0%{opacity:0;transform:translate3d(0,0,0) rotate(-3deg) scale(.72)}12%{opacity:.72}78%{opacity:.48}100%{opacity:0;transform:translate3d(var(--drift),-104vh,0) rotate(4deg) scale(1.04)}}",
|
|
1377
|
+
// ===== 交互反馈层 =====
|
|
1378
|
+
".dkan-fxwrap{position:fixed;inset:0;z-index:9992;pointer-events:none;overflow:hidden;}",
|
|
1379
|
+
".dkan-fx{position:absolute;}",
|
|
1380
|
+
// 纸飞机:从点击处向右上飞出淡出(加大加亮)
|
|
1381
|
+
".dkan-fx-plane{color:var(--dk-accent);font-size:24px;text-shadow:0 0 8px color-mix(in srgb,var(--dk-accent) 70%,transparent);animation:dkan-plane 1.2s var(--ds-ease-in-out) forwards;}",
|
|
1382
|
+
"@keyframes dkan-plane{0%{opacity:0;transform:translate(0,0) rotate(-20deg) scale(.6)}15%{opacity:1;transform:translate(20px,-14px) rotate(-20deg) scale(1.1)}100%{opacity:0;transform:translate(240px,-160px) rotate(-20deg) scale(1)}}",
|
|
1383
|
+
// 火花:粒子四散(加大加多散得更开)
|
|
1384
|
+
".dkan-fx-spark i{position:absolute;width:7px;height:7px;border-radius:50%;box-shadow:0 0 6px currentColor;animation:dkan-spark .9s var(--ds-ease-in-out) forwards;}",
|
|
1385
|
+
"@keyframes dkan-spark{0%{opacity:1;transform:translate(0,0) scale(1)}100%{opacity:0;transform:translate(var(--dx),var(--dy)) scale(.4)}}",
|
|
1386
|
+
// 流光:横向扫过(切会话,加高加亮)
|
|
1387
|
+
".dkan-fx-streak{top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,transparent,var(--dk-accent) 50%,#fff);background-size:45% 100%;background-repeat:no-repeat;box-shadow:0 0 12px 2px color-mix(in srgb,var(--dk-accent) 50%,transparent);animation:dkan-streak 1s var(--ds-ease-in-out) forwards;}",
|
|
1388
|
+
"@keyframes dkan-streak{0%{background-position:-50% 0;opacity:0}20%{opacity:1}100%{background-position:150% 0;opacity:0}}",
|
|
1389
|
+
// 微光:全屏柔光一闪(切工作目录,加浓)
|
|
1390
|
+
".dkan-fx-flash{inset:0;background:radial-gradient(ellipse at center,color-mix(in srgb,var(--dk-accent) 26%,transparent),transparent 75%);animation:dkan-flash .8s var(--ds-ease-in-out) forwards;}",
|
|
1391
|
+
"@keyframes dkan-flash{0%{opacity:0}30%{opacity:1}100%{opacity:0}}",
|
|
1392
|
+
// 光涌:底部向上涌起(任务开始,加高加浓)
|
|
1393
|
+
".dkan-fx-surge{left:0;right:0;bottom:0;height:42%;background:linear-gradient(0deg,color-mix(in srgb,var(--dsw-alias-state-success-primary,#34d399) 38%,transparent),transparent);animation:dkan-surge 1.2s var(--ds-ease-in-out) forwards;}",
|
|
1394
|
+
"@keyframes dkan-surge{0%{opacity:0;transform:translateY(45%)}30%{opacity:1}100%{opacity:0;transform:translateY(-35%)}}",
|
|
1395
|
+
// 彩带:顶部飘落(任务完成庆祝,加大加多)
|
|
1396
|
+
".dkan-fx-confetti{inset:0;}",
|
|
1397
|
+
".dkan-fx-confetti i{position:absolute;top:-14px;width:8px;height:13px;border-radius:2px;box-shadow:0 0 4px currentColor;animation:dkan-confetti 2s var(--ds-ease-in-out) forwards;}",
|
|
1398
|
+
"@keyframes dkan-confetti{0%{opacity:1;transform:translateY(0) rotate(0)}100%{opacity:0;transform:translateY(75vh) translateX(var(--dx)) rotate(var(--r))}}",
|
|
1399
|
+
// 右下角状态徽标(玻璃拟态,可点击);抬到 dsh 输入卡上方避免重叠错位
|
|
1400
|
+
".dkan-badge{position:fixed;right:20px;bottom:calc(var(--dsh-composer-height,152px) + 20px);z-index:9990;display:inline-flex;align-items:center;gap:8px;padding:7px 14px;border-radius:999px;cursor:pointer;border:1px solid var(--dsw-alias-border-l1);background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 78%,transparent);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);box-shadow:0 6px 24px rgb(0 0 0 / .16);color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:12px;line-height:18px;pointer-events:auto;animation:dkan-rise .3s var(--ds-ease-in-out);transition:color .15s var(--ds-ease-in-out);}",
|
|
1401
|
+
".dkan-badge:hover{color:var(--dsw-alias-label-primary);}",
|
|
1402
|
+
".dkan-badge .n{color:var(--dsw-alias-label-primary);font-weight:600;}",
|
|
1403
|
+
"@keyframes dkan-rise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}",
|
|
1404
|
+
// 徽标圆点与两种动效(呼吸 / 轨道环)
|
|
1405
|
+
".dkan-dotwrap{position:relative;width:20px;height:20px;flex:none;display:inline-flex;align-items:center;justify-content:center;}",
|
|
1406
|
+
".dkan-dot{width:12px;height:12px;border-radius:50%;background:var(--dkan-phase,var(--dk-accent));box-shadow:0 0 10px color-mix(in srgb,var(--dkan-phase,var(--dk-accent)) 70%,transparent);transition:background .4s var(--ds-ease-in-out),box-shadow .4s var(--ds-ease-in-out);}",
|
|
1407
|
+
".dkan-breathe .dkan-dot{animation:dkan-breathe calc(2.2s / var(--dkan-speed,1)) ease-in-out infinite;}",
|
|
1408
|
+
"@keyframes dkan-breathe{0%,100%{transform:scale(1);opacity:.5}50%{transform:scale(1.7);opacity:1}}",
|
|
1409
|
+
// 呼吸光晕:向外扩散的波纹环,与圆点同频(颜色随阶段)
|
|
1410
|
+
".dkan-halo{position:absolute;inset:0;border-radius:50%;border:2px solid var(--dkan-phase,var(--dk-accent));animation:dkan-halo calc(2.2s / var(--dkan-speed,1)) ease-out infinite;}",
|
|
1411
|
+
"@keyframes dkan-halo{0%{transform:scale(.5);opacity:.9}70%{transform:scale(1.5);opacity:.25}100%{transform:scale(1.7);opacity:0}}",
|
|
1412
|
+
// breathe 模式徽标更醒目:阶段色描边 + 呼吸辉光
|
|
1413
|
+
".dkan-badge-breathe{border-color:color-mix(in srgb,var(--dkan-phase,#4d9fff) 55%,transparent);animation:dkan-rise .3s var(--ds-ease-in-out),dkan-badge-breathe calc(2.2s / var(--dkan-speed,1)) ease-in-out infinite;}",
|
|
1414
|
+
"@keyframes dkan-badge-breathe{0%,100%{box-shadow:0 0 6px color-mix(in srgb,var(--dkan-phase,#4d9fff) 22%,transparent),0 6px 24px rgb(0 0 0 / .16)}50%{box-shadow:0 0 18px color-mix(in srgb,var(--dkan-phase,#4d9fff) 55%,transparent),0 6px 24px rgb(0 0 0 / .16)}}",
|
|
1415
|
+
".dkan-ring{position:absolute;inset:0;border-radius:50%;background:conic-gradient(from 0deg,transparent 0 68%,var(--dk-accent) 92%,#fff);-webkit-mask:radial-gradient(farthest-side,transparent calc(100% - 2px),#000 calc(100% - 2px));mask:radial-gradient(farthest-side,transparent calc(100% - 2px),#000 calc(100% - 2px));animation:dkan-spin calc(2.2s / var(--dkan-speed,1)) linear infinite;opacity:.9;}",
|
|
1416
|
+
"@keyframes dkan-spin{to{transform:rotate(360deg)}}",
|
|
1417
|
+
// 面板页布局
|
|
1418
|
+
".dkan-root{display:flex;flex-direction:column;gap:10px;}",
|
|
1419
|
+
".dkan-note{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.6;}",
|
|
1420
|
+
".dkan-err{color:var(--dsw-alias-state-error-primary);}",
|
|
1421
|
+
".dkan-sec{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:10px;padding:10px 12px;display:flex;flex-direction:column;gap:8px;}",
|
|
1422
|
+
".dkan-sec-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}",
|
|
1423
|
+
".dkan-sec-title{font-weight:600;font-size:13px;color:var(--dsw-alias-label-primary);flex:none;}",
|
|
1424
|
+
".dkan-sec-sub{font-size:11px;color:var(--dsw-alias-label-tertiary);flex:1;min-width:120px;}",
|
|
1425
|
+
".dkan-sec-sw{margin-left:auto;flex:none;display:inline-flex;align-items:center;gap:8px;}",
|
|
1426
|
+
".dkan-sec-swlabel{font-size:11px;color:var(--dsw-alias-label-tertiary);white-space:nowrap;}",
|
|
1427
|
+
".dkan-sec-swlabel.on{color:var(--dsw-alias-state-success-primary);}",
|
|
1428
|
+
// 模式选择卡(带缩微预览)
|
|
1429
|
+
".dkan-modes{display:flex;gap:8px;flex-wrap:wrap;}",
|
|
1430
|
+
".dkan-mode{flex:1;min-width:150px;display:flex;flex-direction:column;gap:6px;padding:10px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-2);cursor:pointer;font-family:inherit;text-align:left;transition:border-color .15s var(--ds-ease-in-out);}",
|
|
1431
|
+
".dkan-mode:hover{border-color:var(--dk-accent);}",
|
|
1432
|
+
".dkan-mode.on{border-color:var(--dk-accent);box-shadow:0 0 0 1px color-mix(in srgb,var(--dk-accent) 40%,transparent);}",
|
|
1433
|
+
".dkan-mode-name{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;gap:6px;}",
|
|
1434
|
+
".dkan-mode.on .dkan-mode-name::after{content:\"✓\";color:var(--dk-accent);}",
|
|
1435
|
+
".dkan-mode-desc{font-size:11px;color:var(--dsw-alias-label-tertiary);line-height:1.5;}",
|
|
1436
|
+
".dkan-robot-controls{display:flex;flex-direction:column;gap:8px;padding:9px 10px;border:1px solid color-mix(in srgb,var(--dk-accent) 28%,var(--dsw-alias-border-l1));border-radius:9px;background:color-mix(in srgb,var(--dk-accent) 5%,var(--dsw-alias-bg-layer-2));}",
|
|
1437
|
+
".dkan-robot-controls-head{display:flex;align-items:center;justify-content:space-between;font-size:12px;color:var(--dsw-alias-label-primary);}",
|
|
1438
|
+
".dkan-robot-controls-head strong{font-variant-numeric:tabular-nums;color:var(--dk-accent);}",
|
|
1439
|
+
".dkan-robot-size-options{display:flex;gap:6px;flex-wrap:wrap;}",
|
|
1440
|
+
".dkan-robot-size-options button{cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;padding:3px 10px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font:inherit;font-size:11px;}",
|
|
1441
|
+
".dkan-robot-size-options button:hover,.dkan-robot-size-options button.on{border-color:var(--dk-accent);color:var(--dsw-alias-label-primary);}",
|
|
1442
|
+
".dkan-robot-slider-row{display:flex;align-items:center;gap:9px;flex-wrap:wrap;font-size:11px;color:var(--dsw-alias-label-tertiary);}",
|
|
1443
|
+
".dkan-robot-slider-row input{accent-color:var(--dk-accent);width:min(240px,100%);}",
|
|
1444
|
+
".dkan-prev{height:30px;border-radius:6px;background:var(--dsw-alias-bg-layer-1);position:relative;overflow:hidden;display:flex;align-items:center;justify-content:center;}",
|
|
1445
|
+
".dkan-prev-flow{position:absolute;top:0;left:0;right:0;height:3px;background:color-mix(in srgb,var(--dk-accent) 30%,transparent);}",
|
|
1446
|
+
".dkan-prev-flow::after{content:\"\";position:absolute;top:0;bottom:0;left:-40%;width:40%;background:linear-gradient(90deg,transparent,var(--dk-accent));animation:dkan-flow 1.9s linear infinite;}",
|
|
1447
|
+
".dkan-prev-box{--dkan-speed:1;}",
|
|
1448
|
+
".dkan-prev-orbit{position:absolute;top:3px;left:3px;width:7px;height:7px;border-radius:50%;background:var(--dk-accent);box-shadow:0 0 7px 2px color-mix(in srgb,var(--dk-accent) 55%,transparent);animation:dkan-prev-orbit 3s linear infinite;}",
|
|
1449
|
+
"@keyframes dkan-prev-orbit{0%{top:3px;left:3px}25%{top:3px;left:calc(100% - 10px)}50%{top:calc(100% - 10px);left:calc(100% - 10px)}75%{top:calc(100% - 10px);left:3px}100%{top:3px;left:3px}}",
|
|
1450
|
+
// 预览:代码雨(字符列下落)
|
|
1451
|
+
".dkan-prev-matrix i{position:absolute;top:-100%;writing-mode:vertical-rl;font-size:8px;font-family:var(--ds-font-family-code,monospace);color:var(--dk-accent);opacity:.5;animation:dkan-prev-fall 2.2s linear infinite;}",
|
|
1452
|
+
".dkan-prev-matrix i:nth-child(1){left:12%;}",
|
|
1453
|
+
".dkan-prev-matrix i:nth-child(2){left:32%;animation-delay:-.6s;}",
|
|
1454
|
+
".dkan-prev-matrix i:nth-child(3){left:52%;animation-delay:-1.2s;}",
|
|
1455
|
+
".dkan-prev-matrix i:nth-child(4){left:72%;animation-delay:-.3s;}",
|
|
1456
|
+
".dkan-prev-matrix i:nth-child(5){left:88%;animation-delay:-1.6s;}",
|
|
1457
|
+
".dkan-prev-matrix i::after{content:\"01</>\";}",
|
|
1458
|
+
"@keyframes dkan-prev-fall{to{transform:translateY(300%)}}",
|
|
1459
|
+
// 预览:星野(星点闪烁)
|
|
1460
|
+
".dkan-prev-stars i{position:absolute;width:2px;height:2px;border-radius:50%;background:var(--dsw-alias-label-secondary);animation:dkan-prev-star 1.6s ease-in-out infinite alternate;}",
|
|
1461
|
+
".dkan-prev-stars i:nth-child(1){left:15%;top:25%;}",
|
|
1462
|
+
".dkan-prev-stars i:nth-child(2){left:40%;top:60%;animation-delay:-.4s;}",
|
|
1463
|
+
".dkan-prev-stars i:nth-child(3){left:60%;top:30%;animation-delay:-.8s;}",
|
|
1464
|
+
".dkan-prev-stars i:nth-child(4){left:80%;top:55%;animation-delay:-1.2s;}",
|
|
1465
|
+
".dkan-prev-stars i:nth-child(5){left:28%;top:70%;animation-delay:-.6s;}",
|
|
1466
|
+
".dkan-prev-stars i:nth-child(6){left:70%;top:75%;animation-delay:-1s;}",
|
|
1467
|
+
"@keyframes dkan-prev-star{0%{opacity:.2}100%{opacity:.9}}",
|
|
1468
|
+
// 预览:星际远征(行星、轨道与飞船)
|
|
1469
|
+
".dkan-prev-space i{position:absolute;left:49%;top:50%;width:9px;height:9px;border-radius:50%;background:#fbbf24;box-shadow:0 0 6px #fbbf24;}.dkan-prev-space b{position:absolute;left:21%;top:23%;width:15px;height:8px;border:1px solid #93c5fd;border-radius:50%;animation:dkan-prev-space-orbit 2.4s linear infinite;}.dkan-prev-space b::after{content:'';position:absolute;left:-3px;top:0;width:5px;height:5px;border-radius:50%;background:#a78bfa;box-shadow:0 0 4px #a78bfa;}.dkan-prev-space em{position:absolute;left:5%;top:65%;width:19px;height:4px;border-radius:50% 0 0 50%;background:linear-gradient(90deg,#22d3ee,#dbeafe);animation:dkan-prev-space-ship 1.8s linear infinite;}.dkan-prev-space em::after{content:'';position:absolute;right:-7px;top:-3px;border-left:9px solid #93c5fd;border-top:5px solid transparent;border-bottom:5px solid transparent;}.dkan-prev-space{background:radial-gradient(ellipse at 78% 20%,color-mix(in srgb,#6366f1 18%,transparent),transparent 58%);}.dkan-prev-space i,.dkan-prev-space b,.dkan-prev-space em{font-style:normal;}.dkan-prev-space b{transform-origin:34px 11px;}@keyframes dkan-prev-space-orbit{to{transform:rotate(360deg)}}@keyframes dkan-prev-space-ship{to{transform:translateX(105px)}}",
|
|
1470
|
+
// 预览:极光(柔光带呼吸)
|
|
1471
|
+
".dkan-prev-aurora i{position:absolute;top:-30%;width:60%;height:120%;border-radius:50%;filter:blur(8px);opacity:.35;animation:dkan-prev-aurora 2.4s ease-in-out infinite alternate;}",
|
|
1472
|
+
".dkan-prev-aurora i:nth-child(1){left:5%;background:#4d9fff;}",
|
|
1473
|
+
".dkan-prev-aurora i:nth-child(2){left:45%;background:#34d399;animation-delay:-1.2s;}",
|
|
1474
|
+
"@keyframes dkan-prev-aurora{0%{transform:translateX(-10%)}100%{transform:translateX(15%)}}",
|
|
1475
|
+
// 十种新增模式的缩微预览:复用主体运动语言,但把节点数和振幅压到卡片内。
|
|
1476
|
+
".dkan-prev-new{--dkan-phase:var(--dk-accent)}.dkan-prev-new i{position:absolute;display:block}",
|
|
1477
|
+
".dkan-prev-nebula i{width:48px;height:48px;border-radius:50%;background:radial-gradient(circle,color-mix(in srgb,var(--dkan-phase) 48%,transparent),transparent 70%);animation:dkan-prev-nebula 3s ease-in-out infinite alternate}.dkan-prev-nebula i:nth-child(1){left:2%;top:-60%}.dkan-prev-nebula i:nth-child(2){right:4%;bottom:-65%;background:radial-gradient(circle,color-mix(in srgb,#a855f7 42%,transparent),transparent 70%);animation-delay:-1.4s}.dkan-prev-nebula i:nth-child(n+3){display:none}@keyframes dkan-prev-nebula{to{transform:translateX(24px) scale(1.18)}}",
|
|
1478
|
+
".dkan-prev-warp i{left:50%;top:50%;width:36px;height:1px;transform-origin:left;background:linear-gradient(90deg,transparent,var(--dkan-phase),#fff);animation:dkan-prev-warp 1.8s linear infinite;opacity:0}.dkan-prev-warp i:nth-child(1){--a:0deg}.dkan-prev-warp i:nth-child(2){--a:45deg;animation-delay:-.2s}.dkan-prev-warp i:nth-child(3){--a:90deg;animation-delay:-.4s}.dkan-prev-warp i:nth-child(4){--a:135deg;animation-delay:-.6s}.dkan-prev-warp i:nth-child(5){--a:180deg;animation-delay:-.8s}.dkan-prev-warp i:nth-child(6){--a:225deg;animation-delay:-1s}.dkan-prev-warp i:nth-child(7){--a:270deg;animation-delay:-1.2s}.dkan-prev-warp i:nth-child(8){--a:315deg;animation-delay:-1.4s}@keyframes dkan-prev-warp{0%{opacity:0;transform:rotate(var(--a)) translateX(2px) scaleX(.1)}35%{opacity:.8}100%{opacity:0;transform:rotate(var(--a)) translateX(40px) scaleX(1.3)}}",
|
|
1479
|
+
".dkan-prev-radar i{left:50%;top:50%;width:24px;height:24px;margin:-12px;border:1px solid color-mix(in srgb,var(--dkan-phase) 48%,transparent);border-radius:50%;animation:dkan-prev-radar 2.6s ease-out infinite;opacity:0}.dkan-prev-radar i:nth-child(2){animation-delay:-.85s}.dkan-prev-radar i:nth-child(3){animation-delay:-1.7s}.dkan-prev-radar i:nth-child(n+4){display:none}@keyframes dkan-prev-radar{0%{opacity:.8;transform:scale(.2)}100%{opacity:0;transform:scale(1.25)}}",
|
|
1480
|
+
".dkan-prev-constellation::after{content:'';position:absolute;left:12%;right:12%;top:50%;height:1px;background:linear-gradient(90deg,transparent,var(--dkan-phase),transparent);transform:rotate(-9deg)}.dkan-prev-constellation i,.dkan-prev-fireflies i{width:4px;height:4px;border-radius:50%;background:var(--dkan-phase);box-shadow:0 0 5px var(--dkan-phase);animation:dkan-prev-twinkle 1.8s ease-in-out infinite alternate}.dkan-prev-constellation i:nth-child(1),.dkan-prev-fireflies i:nth-child(1){left:12%;top:62%}.dkan-prev-constellation i:nth-child(2),.dkan-prev-fireflies i:nth-child(2){left:28%;top:26%;animation-delay:-.3s}.dkan-prev-constellation i:nth-child(3),.dkan-prev-fireflies i:nth-child(3){left:45%;top:52%;animation-delay:-.6s}.dkan-prev-constellation i:nth-child(4),.dkan-prev-fireflies i:nth-child(4){left:61%;top:18%;animation-delay:-.9s}.dkan-prev-constellation i:nth-child(5),.dkan-prev-fireflies i:nth-child(5){left:76%;top:58%;animation-delay:-1.2s}.dkan-prev-constellation i:nth-child(6),.dkan-prev-fireflies i:nth-child(6){left:88%;top:34%;animation-delay:-1.5s}@keyframes dkan-prev-twinkle{to{opacity:.2;transform:translateY(4px) scale(.65)}}",
|
|
1481
|
+
".dkan-prev-ocean i{left:-10%;width:120%;height:18px;border-top:1px solid color-mix(in srgb,#22d3ee 52%,transparent);border-radius:50%;animation:dkan-prev-wave 2.8s ease-in-out infinite alternate}.dkan-prev-ocean i:nth-child(1){top:15%}.dkan-prev-ocean i:nth-child(2){top:35%;animation-delay:-.5s}.dkan-prev-ocean i:nth-child(3){top:55%;animation-delay:-1s}.dkan-prev-ocean i:nth-child(4){top:75%;animation-delay:-1.5s}.dkan-prev-ocean i:nth-child(n+5){display:none}@keyframes dkan-prev-wave{to{transform:translateX(12px) scaleY(1.2)}}",
|
|
1482
|
+
".dkan-prev-prism i{left:-10%;width:120%;height:1px;background:linear-gradient(90deg,transparent,#ef4444,#fbbf24,#22c55e,#22d3ee,#8b5cf6,transparent);transform:rotate(-6deg);animation:dkan-prev-prism 2.8s ease-in-out infinite}.dkan-prev-prism i:nth-child(1){top:18%}.dkan-prev-prism i:nth-child(2){top:34%;animation-delay:-.4s}.dkan-prev-prism i:nth-child(3){top:50%;animation-delay:-.8s}.dkan-prev-prism i:nth-child(4){top:66%;animation-delay:-1.2s}.dkan-prev-prism i:nth-child(5){top:82%;animation-delay:-1.6s}.dkan-prev-prism i:nth-child(n+6){display:none}@keyframes dkan-prev-prism{0%,100%{opacity:.12;transform:translateX(-8px) rotate(-6deg)}50%{opacity:.72;transform:translateX(10px) rotate(-6deg)}}",
|
|
1483
|
+
".dkan-prev-circuit i{height:1px;background:var(--dkan-phase);box-shadow:0 0 4px var(--dkan-phase);animation:dkan-prev-circuit 2.4s linear infinite}.dkan-prev-circuit i:nth-child(1){left:5%;top:30%;width:42%}.dkan-prev-circuit i:nth-child(2){left:47%;top:30%;width:1px;height:45%}.dkan-prev-circuit i:nth-child(3){left:47%;top:74%;width:38%}.dkan-prev-circuit i:nth-child(4){left:68%;top:15%;width:1px;height:60%}.dkan-prev-circuit i:nth-child(5){left:68%;top:15%;width:26%}.dkan-prev-circuit i:nth-child(6){left:12%;top:62%;width:35%}@keyframes dkan-prev-circuit{0%,100%{opacity:.18}50%{opacity:.9}}",
|
|
1484
|
+
".dkan-prev-gravity i{left:50%;top:50%;width:18px;height:18px;margin:-9px;border:1px solid var(--dkan-phase);border-radius:50%;opacity:0;animation:dkan-prev-gravity 2.6s ease-out infinite}.dkan-prev-gravity i:nth-child(2){animation-delay:-.65s}.dkan-prev-gravity i:nth-child(3){animation-delay:-1.3s}.dkan-prev-gravity i:nth-child(4){animation-delay:-1.95s}.dkan-prev-gravity i:nth-child(n+5){display:none}@keyframes dkan-prev-gravity{0%{opacity:.8;transform:scale(.15)}100%{opacity:0;transform:scale(2.4)}}",
|
|
1485
|
+
".dkan-prev-lantern i{bottom:-8px;width:6px;height:8px;border-radius:3px;background:#fbbf24;box-shadow:0 0 5px #f59e0b;animation:dkan-prev-lantern 3.2s linear infinite}.dkan-prev-lantern i:nth-child(1){left:12%}.dkan-prev-lantern i:nth-child(2){left:27%;animation-delay:-.6s}.dkan-prev-lantern i:nth-child(3){left:43%;animation-delay:-1.2s}.dkan-prev-lantern i:nth-child(4){left:61%;animation-delay:-1.8s}.dkan-prev-lantern i:nth-child(5){left:76%;animation-delay:-2.4s}.dkan-prev-lantern i:nth-child(6){left:90%;animation-delay:-2.8s}@keyframes dkan-prev-lantern{0%{opacity:0;transform:translateY(0)}20%{opacity:.9}100%{opacity:0;transform:translateY(-38px)}}",
|
|
1486
|
+
".dkan-prev-bot{height:92px;justify-content:center;}",
|
|
1487
|
+
".dkan-prev-bot .dkan-bot-scene{--dkan-bot-scale:1;transform:scale(.56);transform-origin:center;}",
|
|
1488
|
+
// ===== 桌面伙伴:具象人物 + 紧凑双工位(尺寸可调,整卡可拖拽) =====
|
|
1489
|
+
// 卡片:玻璃拟态;默认停泊右下(dsh 输入卡上方),拖动后位置持久化、可放屏幕任意处
|
|
1490
|
+
".dkan-botcard{--dkan-bot-scale:1.35;position:fixed;right:20px;bottom:calc(var(--dsh-composer-height,152px) + 20px);z-index:9990;display:flex;flex-direction:column;gap:6px;padding:10px 12px;border-radius:16px;cursor:grab;border:1px solid var(--dsw-alias-border-l1);background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 86%,transparent);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);box-shadow:0 12px 36px rgb(0 0 0 / .22);color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:12px;pointer-events:auto;touch-action:none;user-select:none;animation:dkan-rise .3s var(--ds-ease-in-out);}",
|
|
1491
|
+
".dkan-botcard:hover,.dkan-botcard:focus-visible{border-color:var(--dk-accent);outline:none;}",
|
|
1492
|
+
".dkan-botcard.dkan-dragging{cursor:grabbing;animation:none;}",
|
|
1493
|
+
".dkan-botcard .n{color:var(--dsw-alias-label-primary);font-weight:600;}",
|
|
1494
|
+
".dkan-bot-cap{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:18px;}",
|
|
1495
|
+
".dkan-bot-resize{position:absolute;right:7px;bottom:7px;width:15px;height:15px;padding:0;border:0;border-radius:4px;background:linear-gradient(135deg,transparent 0 42%,var(--dsw-alias-label-tertiary) 44% 51%,transparent 53% 62%,var(--dsw-alias-label-tertiary) 64% 71%,transparent 73%);cursor:nwse-resize;touch-action:none;opacity:.72;}",
|
|
1496
|
+
".dkan-bot-resize:hover,.dkan-bot-resize:focus-visible{opacity:1;outline:1px solid var(--dk-accent);outline-offset:1px;}",
|
|
1497
|
+
// ===== 3D 场景(CSS 长方体拼装:世界层统一 3/4 视角——俯视 + 侧身面向镜头) =====
|
|
1498
|
+
".dkan-bot-scene{position:relative;width:calc(220px * var(--dkan-bot-scale,1));height:calc(132px * var(--dkan-bot-scale,1));perspective:calc(620px * var(--dkan-bot-scale,1));perspective-origin:55% 18%;--dk3-side:#7c8ba0;--dk3-side-hi:#a9b8ca;--dk3-side-dk:#4c5870;--dk3-screen:#0d1526;--dkan-code-a:#4ade80;--dkan-code-b:#60a5fa;--dkan-code-c:#fbbf24;}",
|
|
1499
|
+
".dk3-world{position:absolute;left:0;top:0;width:220px;height:132px;transform-style:preserve-3d;transform-origin:top left;transform:scale(var(--dkan-bot-scale,1)) rotateX(-15deg) rotateY(-30deg);}",
|
|
1500
|
+
".dk3-box{position:absolute;transform-style:preserve-3d;}",
|
|
1501
|
+
".dk3-face{position:absolute;left:50%;top:50%;display:block;background:var(--dk3-side);backface-visibility:hidden;border-radius:1px;}",
|
|
1502
|
+
// 金属件着色:顶面亮、底面暗,正/侧面中调(3D 光感)
|
|
1503
|
+
".dk3-metal .dk3-face{background:linear-gradient(180deg,#93a5ba,#71809a);}",
|
|
1504
|
+
".dk3-metal .dk3-face:nth-child(5){background:linear-gradient(180deg,var(--dk3-side-hi),#9dadc2);}",
|
|
1505
|
+
".dk3-metal .dk3-face:nth-child(6){background:var(--dk3-side-dk);}",
|
|
1506
|
+
// 书桌与桌腿
|
|
1507
|
+
".dk3-desk .dk3-face{background:linear-gradient(180deg,#9db0c6,#7f92aa);}",
|
|
1508
|
+
".dk3-desk .dk3-face:nth-child(5){background:#c3d2e2;}",
|
|
1509
|
+
// 显示器(薄盒 + 贴在前面的屏幕;组内扇形微转,整组后撤出桌沿)
|
|
1510
|
+
".dk3-mon3{position:absolute;transform-style:preserve-3d;}",
|
|
1511
|
+
".dk3-frame .dk3-face{background:#3a465b;}",
|
|
1512
|
+
".dk3-frame .dk3-face:nth-child(5){background:#55647d;}",
|
|
1513
|
+
".dk3-screen{position:absolute;left:50%;top:50%;background:var(--dk3-screen);border-radius:2px;overflow:hidden;transition:opacity .4s var(--ds-ease-in-out),box-shadow .4s var(--ds-ease-in-out);}",
|
|
1514
|
+
// 屏幕内容:代码块整体向上滚动(10 行=5 行×2 循环,滚半程无缝接回)+ 闪烁光标,速度随 --dkan-speed
|
|
1515
|
+
".dk3-code{position:absolute;left:0;top:0;right:0;display:block;animation:dk3-scroll calc(5s / var(--dkan-speed,1)) linear infinite;}",
|
|
1516
|
+
".dk3-code i{display:block;height:2px;border-radius:1px;margin:2px 3px 0;background:var(--dkan-code-b);opacity:.55;}",
|
|
1517
|
+
".dk3-code i:nth-child(5n+1){width:58%;background:var(--dkan-code-a);}",
|
|
1518
|
+
".dk3-code i:nth-child(5n+2){width:82%;}",
|
|
1519
|
+
".dk3-code i:nth-child(5n+3){width:46%;background:var(--dkan-code-c);}",
|
|
1520
|
+
".dk3-code i:nth-child(5n+4){width:72%;}",
|
|
1521
|
+
".dk3-code i:nth-child(5n){width:54%;background:var(--dkan-code-a);}",
|
|
1522
|
+
".dk3-cur{position:absolute;left:3px;bottom:2px;width:4px;height:2px;background:var(--dkan-code-b);animation:dkan-blink3 1s steps(1) infinite;}",
|
|
1523
|
+
// 左屏检索视图:搜索框、放大镜与结果条,和右侧代码编辑器明确区分
|
|
1524
|
+
".dk3-search-ui{position:absolute;inset:3px;display:block;overflow:hidden;}",
|
|
1525
|
+
".dk3-search-box{display:block;height:5px;margin:0 1px 3px;border:1px solid color-mix(in srgb,var(--dkan-code-a) 72%,#d7fff4);border-radius:2px;box-sizing:border-box;}",
|
|
1526
|
+
".dk3-search-box b{display:block;width:2px;height:2px;margin:1px 0 0 2px;border:1px solid var(--dkan-code-a);border-radius:50%;position:relative;}",
|
|
1527
|
+
".dk3-search-box b:after{content:'';position:absolute;width:2px;height:1px;background:var(--dkan-code-a);right:-2px;bottom:-1px;transform:rotate(45deg);transform-origin:left center;}",
|
|
1528
|
+
".dk3-search-results{display:block;animation:dk3-search-scroll calc(2.1s / var(--dkan-speed,1)) linear infinite;}",
|
|
1529
|
+
".dk3-search-results i{display:block;height:2px;margin:2px 1px 0;border-radius:1px;background:var(--dkan-code-a);opacity:.72;}",
|
|
1530
|
+
".dk3-search-results i:nth-child(2n){width:74%;background:var(--dkan-code-b);}",
|
|
1531
|
+
".dk3-search-results i:nth-child(3n){width:48%;background:var(--dkan-code-c);}",
|
|
1532
|
+
"@keyframes dk3-search-scroll{to{transform:translateY(-5px)}}",
|
|
1533
|
+
"@keyframes dk3-scroll{to{transform:translateY(-50%)}}",
|
|
1534
|
+
// 人物与椅子始终正对桌面:工位切换只横移,不再旋转整个人和座椅。
|
|
1535
|
+
".dk3-person{--dk3-seat-x:22px;position:absolute;left:98px;top:14px;width:44px;height:61px;transform-style:preserve-3d;transform:translateZ(30px) translateX(var(--dk3-seat-x));transition:transform max(.18s,calc(.38s / var(--dkan-speed,1))) cubic-bezier(.23,1,.32,1);will-change:transform;}",
|
|
1536
|
+
".dkan-bot-scene[data-station=left] .dk3-person{--dk3-seat-x:0px;}",
|
|
1537
|
+
".dkan-bot-scene[data-station=center] .dk3-person{--dk3-seat-x:22px;}",
|
|
1538
|
+
".dkan-bot-scene[data-station=right] .dk3-person{--dk3-seat-x:44px;}",
|
|
1539
|
+
// 上身以髋部为旋转原点;只做前后倾,始终朝向桌面。
|
|
1540
|
+
".dk3-upper3{position:absolute;inset:0;transform-style:preserve-3d;transform-origin:12px 52px;transform:translateZ(0);transition:transform .24s cubic-bezier(.77,0,.175,1);will-change:transform;}",
|
|
1541
|
+
// 动漫人物配色:皮肤/头发/卫衣/裤子
|
|
1542
|
+
".dk3-skin .dk3-face{background:linear-gradient(180deg,#ffd9b8,#f3b98d);}",
|
|
1543
|
+
".dk3-skin .dk3-face:nth-child(5){background:#ffe3c9;}",
|
|
1544
|
+
".dk3-hair .dk3-face{background:linear-gradient(180deg,#5b4632,#3f3021);}",
|
|
1545
|
+
".dk3-hair .dk3-face:nth-child(5){background:#6b543c;}",
|
|
1546
|
+
".dk3-hood .dk3-face{background:linear-gradient(180deg,#7c8ba0,#5b6a80);}",
|
|
1547
|
+
".dk3-hood .dk3-face:nth-child(5){background:#93a5ba;}",
|
|
1548
|
+
".dk3-pants .dk3-face{background:linear-gradient(180deg,#3d4a5c,#2c3646);}",
|
|
1549
|
+
".dk3-pants .dk3-face:nth-child(5){background:#4a5a70;}",
|
|
1550
|
+
// 人物细节:收窄下颌、连续肩颈、短侧渐层发型
|
|
1551
|
+
".dk3-cranium .dk3-face{border-radius:42% 46% 38% 36%;}",
|
|
1552
|
+
".dk3-jaw3 .dk3-face{border-radius:30% 30% 48% 48%;}",
|
|
1553
|
+
".dk3-neck3 .dk3-face{border-radius:2px;}",
|
|
1554
|
+
".dk3-collar3 .dk3-face{background:linear-gradient(180deg,#516176,#374459);border-radius:3px;}",
|
|
1555
|
+
".dk3-shoulders3 .dk3-face{border-radius:4px;}",
|
|
1556
|
+
".dk3-chest3 .dk3-face{border-radius:4px;}",
|
|
1557
|
+
".dk3-waist3 .dk3-face{border-radius:3px;}",
|
|
1558
|
+
".dk3-hair .dk3-face{border-radius:2px;}",
|
|
1559
|
+
".dk3-hair-crown .dk3-face{border-radius:48% 42% 28% 30%;}",
|
|
1560
|
+
".dk3-hair-back .dk3-face{background:linear-gradient(180deg,#493727,#2f241b);border-radius:46% 24% 38% 44%;}",
|
|
1561
|
+
".dk3-shoe .dk3-face{background:#20262f;border-radius:2px;}",
|
|
1562
|
+
".dk3-mug .dk3-face{background:#c2703d;border-radius:1px;}",
|
|
1563
|
+
".dk3-mug .dk3-face:nth-child(5){background:#e08a52;}",
|
|
1564
|
+
".dk3-mouth{position:absolute;left:50%;top:64%;width:3.5px;height:1px;border-radius:2px;background:#a9635c;}",
|
|
1565
|
+
// 椅子
|
|
1566
|
+
".dk3-chairback .dk3-face{background:linear-gradient(180deg,#3f4c60,#2c3648);}",
|
|
1567
|
+
".dk3-chairseat .dk3-face{background:#33415a;}",
|
|
1568
|
+
// 头组与颈部中心均为 x=14;只让头部观察左右屏,身体和椅子不转向。
|
|
1569
|
+
".dk3-head3{--dk3-head-turn:rotateY(0deg);position:absolute;left:6px;top:3px;width:17px;height:21px;transform-style:preserve-3d;transform-origin:8px 18px;transform:var(--dk3-head-turn);transition:transform max(.18s,calc(.22s / var(--dkan-speed,1))) cubic-bezier(.23,1,.32,1);will-change:transform;}",
|
|
1570
|
+
".dkan-bot-scene[data-station=left] .dk3-head3{--dk3-head-turn:rotateY(-18deg);}",
|
|
1571
|
+
".dkan-bot-scene[data-station=center] .dk3-head3{--dk3-head-turn:rotateY(0deg);}",
|
|
1572
|
+
".dkan-bot-scene[data-station=right] .dk3-head3{--dk3-head-turn:rotateY(14deg);}",
|
|
1573
|
+
".dk3-brows{position:absolute;left:50%;top:29%;display:flex;gap:2.5px;align-items:center;justify-content:center;}",
|
|
1574
|
+
".dk3-brows i{width:3.5px;height:1px;border-radius:2px;background:#3a2a21;transform:rotate(-7deg);}",
|
|
1575
|
+
".dk3-brows i+ i{transform:rotate(7deg);}",
|
|
1576
|
+
".dk3-eyes{position:absolute;left:50%;top:51%;display:flex;gap:2.5px;align-items:center;justify-content:center;}",
|
|
1577
|
+
".dk3-eyes i{width:3.5px;height:2.5px;border-radius:50%;background:radial-gradient(circle at 62% 30%,#fff 0 .5px,#263141 .8px 1.7px,#111827 1.9px);animation:dkan-blink3 4.8s infinite;}",
|
|
1578
|
+
".dk3-nose{position:absolute;left:50%;top:66%;width:2px;height:3px;border-radius:60% 40% 55% 45%;background:linear-gradient(90deg,#e7a67e,#c77f67);}",
|
|
1579
|
+
".dk3-ear{position:absolute;left:1px;top:10px;width:3px;height:5px;border-radius:52% 44% 48% 52%;background:linear-gradient(90deg,#edaa82,#ffd2ad);transform:translateZ(2px);}",
|
|
1580
|
+
".dk3-fringe3{position:absolute;left:13px;top:4px;width:5px;height:7px;transform-style:preserve-3d;transform:rotateY(90deg) translateZ(5.8px);}",
|
|
1581
|
+
".dk3-fringe3 i{position:absolute;top:0;width:2px;height:6px;border-radius:70% 25% 60% 30%;background:linear-gradient(180deg,#6b5239,#392b20);transform-origin:top center;}",
|
|
1582
|
+
".dk3-fringe3 i:nth-child(1){left:0;transform:rotate(-18deg);height:5px;}",
|
|
1583
|
+
".dk3-fringe3 i:nth-child(2){left:1.8px;transform:rotate(-6deg);height:6px;}",
|
|
1584
|
+
".dk3-fringe3 i:nth-child(3){left:3.5px;transform:rotate(12deg);height:4px;}",
|
|
1585
|
+
"@keyframes dkan-blink3{0%,91%,100%{transform:scaleY(1)}94%{transform:scaleY(.15)}}",
|
|
1586
|
+
// 手臂按肩→肘→腕分段,旋转原点位于真实关节连接处。
|
|
1587
|
+
".dk3-arm3{position:absolute;left:20px;top:30px;width:16px;height:16px;transform-style:preserve-3d;transform-origin:2px 2px;will-change:transform;}",
|
|
1588
|
+
".dk3-elbow{position:absolute;left:0;top:10px;width:15px;height:5px;transform-style:preserve-3d;transform-origin:1px 2px;will-change:transform;}",
|
|
1589
|
+
".dk3-wrist3{position:absolute;left:9.5px;top:0;width:6px;height:5px;transform-style:preserve-3d;transform-origin:1px 2px;will-change:transform;}",
|
|
1590
|
+
".dk3-fingers3{position:absolute;left:3px;top:1px;width:4px;height:3px;transform:translateZ(2px);}",
|
|
1591
|
+
".dk3-fingers3 i{position:absolute;left:0;width:4px;height:.8px;border-radius:2px;background:#f2b58d;}",
|
|
1592
|
+
".dk3-fingers3 i+ i{top:1.4px;width:3.5px;}",
|
|
1593
|
+
".dk3-far{transform:translateZ(-7px);filter:brightness(.72);}",
|
|
1594
|
+
// 思考泡泡(贴镜头 2D 层,think 阶段显示)
|
|
1595
|
+
".dkan-bubble{position:absolute;left:80px;top:4px;display:none;gap:3px;padding:4px 6px;border-radius:8px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);box-shadow:0 2px 6px rgb(0 0 0 / .15);z-index:3;}",
|
|
1596
|
+
".dkan-bubble i{width:4px;height:4px;border-radius:50%;background:var(--dsw-alias-label-tertiary);animation:dkan-bubdot 1.2s ease-in-out infinite;}",
|
|
1597
|
+
".dkan-bubble i:nth-child(2){animation-delay:.2s;}",
|
|
1598
|
+
".dkan-bubble i:nth-child(3){animation-delay:.4s;}",
|
|
1599
|
+
"@keyframes dkan-bubdot{0%,100%{opacity:.25;transform:translateY(0)}50%{opacity:1;transform:translateY(-2px)}}",
|
|
1600
|
+
// ===== 多任务工位(data-tasks=活跃任务数,上限 3) =====
|
|
1601
|
+
// 椅子滚轮
|
|
1602
|
+
".dk3-wheel .dk3-face{background:#1c232e;border-radius:50%;}",
|
|
1603
|
+
// 有活跃任务时,未轮到的侧工位稍暗;多任务则两个工位均保持可见。
|
|
1604
|
+
".dkan-bot-scene[data-tasks] .dk3-mon3.left .dk3-screen,.dkan-bot-scene[data-tasks] .dk3-mon3.right .dk3-screen{opacity:.3;}",
|
|
1605
|
+
// 2 个及以上任务:左右工位同时点亮(各自带辉光、代码滚动加速)。
|
|
1606
|
+
".dkan-bot-scene[data-tasks=\"2\"] .dk3-mon3.left .dk3-screen,.dkan-bot-scene[data-tasks=\"2\"] .dk3-mon3.right .dk3-screen,.dkan-bot-scene[data-tasks=\"3\"] .dk3-mon3.left .dk3-screen,.dkan-bot-scene[data-tasks=\"3\"] .dk3-mon3.right .dk3-screen{opacity:1;box-shadow:0 0 8px color-mix(in srgb,var(--dkan-code-b,#60a5fa) 42%,transparent);}",
|
|
1607
|
+
".dkan-bot-scene[data-tasks=\"2\"] .dk3-mon3.left .dk3-code,.dkan-bot-scene[data-tasks=\"2\"] .dk3-mon3.right .dk3-code,.dkan-bot-scene[data-tasks=\"3\"] .dk3-mon3.left .dk3-code,.dkan-bot-scene[data-tasks=\"3\"] .dk3-mon3.right .dk3-code{animation-duration:calc(2.6s / var(--dkan-speed,1));}",
|
|
1608
|
+
// 座椅移动期间滚轮跟随任务节奏转动;思考中停下,避免无意义的常驻运动。
|
|
1609
|
+
".dkan-bot-scene[data-station=left] .dk3-wheel,.dkan-bot-scene[data-station=right] .dk3-wheel{animation:dkan-wheel calc(.46s / var(--dkan-speed,1)) linear infinite;transform-origin:center;}",
|
|
1610
|
+
"@keyframes dkan-wheel{to{transform:rotateZ(360deg)}}",
|
|
1611
|
+
// ===== 阶段驱动(data-phase 四态,host 由 chunk 流实时同步) =====
|
|
1612
|
+
// think:上身轻靠、近侧手托下巴、远侧手留在桌面;动作有停顿而不是机械往返。
|
|
1613
|
+
".dkan-bot-scene[data-phase=think] .dk3-screen{opacity:.32;}",
|
|
1614
|
+
".dkan-bot-scene[data-phase=think] .dkan-bubble{display:inline-flex;}",
|
|
1615
|
+
".dkan-bot-scene[data-phase=think] .dk3-upper3{animation:dkan-think-upper max(1.8s,calc(2.4s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1616
|
+
".dkan-bot-scene[data-phase=think] .dk3-head3{animation:dkan-think-head max(1.8s,calc(2.4s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1617
|
+
".dkan-bot-scene[data-phase=think] .dk3-arm3.dk3-near{animation:dkan-think-near max(1.8s,calc(2.4s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1618
|
+
".dkan-bot-scene[data-phase=think] .dk3-arm3.dk3-far{transform:translateZ(-7px) rotateZ(3deg);}",
|
|
1619
|
+
".dkan-bot-scene[data-phase=think] .dk3-near .dk3-elbow{transform:rotateZ(-28deg) translateY(-1px);}",
|
|
1620
|
+
".dkan-bot-scene[data-phase=think] .dk3-near .dk3-wrist3{transform:rotateZ(-18deg) translateY(-1px);}",
|
|
1621
|
+
"@keyframes dkan-think-upper{0%,44%{transform:rotateZ(-1deg) translateY(0)}62%,100%{transform:rotateZ(-1.8deg) translateY(-.5px)}}",
|
|
1622
|
+
"@keyframes dkan-think-head{0%,44%{transform:var(--dk3-head-turn) rotateZ(-3deg)}62%,100%{transform:var(--dk3-head-turn) rotateZ(-6deg) translateY(-.5px)}}",
|
|
1623
|
+
"@keyframes dkan-think-near{0%,44%{transform:rotateZ(-31deg) translate(-8px,-14.5px)}62%,100%{transform:rotateZ(-34deg) translate(-8.5px,-15px)}}",
|
|
1624
|
+
// write/code:身体仍面向桌面,仅轻微前倾;主要由手腕和手指敲击。
|
|
1625
|
+
".dkan-bot-scene[data-phase=write] .dk3-mon3.right .dk3-screen,.dkan-bot-scene[data-phase=code] .dk3-mon3.right .dk3-screen{opacity:1;box-shadow:0 0 11px color-mix(in srgb,var(--dkan-code-b,#60a5fa) 52%,transparent);}",
|
|
1626
|
+
".dkan-bot-scene[data-phase=write] .dk3-mon3.right .dk3-code,.dkan-bot-scene[data-phase=code] .dk3-mon3.right .dk3-code{animation-duration:calc(1.35s / var(--dkan-speed,1));}",
|
|
1627
|
+
".dkan-bot-scene[data-phase=write] .dk3-upper3,.dkan-bot-scene[data-phase=code] .dk3-upper3{animation:dkan-code-upper max(1.2s,calc(2.4s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1628
|
+
".dkan-bot-scene[data-phase=write] .dk3-arm3.dk3-near,.dkan-bot-scene[data-phase=code] .dk3-arm3.dk3-near{transform:rotateZ(3deg);}",
|
|
1629
|
+
".dkan-bot-scene[data-phase=write] .dk3-arm3.dk3-far,.dkan-bot-scene[data-phase=code] .dk3-arm3.dk3-far{transform:translateZ(-7px) rotateZ(1deg);}",
|
|
1630
|
+
".dkan-bot-scene[data-phase=write] .dk3-elbow,.dkan-bot-scene[data-phase=code] .dk3-elbow{animation:dkan-type-elbow max(.28s,calc(.58s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1631
|
+
".dkan-bot-scene[data-phase=write] .dk3-wrist3,.dkan-bot-scene[data-phase=code] .dk3-wrist3{animation:dkan-type-wrist max(.18s,calc(.29s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1632
|
+
".dkan-bot-scene[data-phase=write] .dk3-far .dk3-elbow,.dkan-bot-scene[data-phase=code] .dk3-far .dk3-elbow{animation-delay:-.09s;animation-duration:max(.3s,calc(.63s / var(--dkan-speed,1)));}",
|
|
1633
|
+
".dkan-bot-scene[data-phase=write] .dk3-far .dk3-wrist3,.dkan-bot-scene[data-phase=code] .dk3-far .dk3-wrist3{animation-delay:-.09s;animation-duration:max(.2s,calc(.33s / var(--dkan-speed,1)));}",
|
|
1634
|
+
".dkan-bot-scene[data-phase=write] .dk3-head3,.dkan-bot-scene[data-phase=code] .dk3-head3{animation:dkan-output-head max(1.2s,calc(2.4s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1635
|
+
"@keyframes dkan-code-upper{0%,38%{transform:rotateZ(2deg) translate(1px,0)}62%,100%{transform:rotateZ(3deg) translate(1px,1px)}}",
|
|
1636
|
+
"@keyframes dkan-type-elbow{0%,38%{transform:rotateZ(-4deg)}62%,100%{transform:rotateZ(5deg) translateY(1px)}}",
|
|
1637
|
+
"@keyframes dkan-type-wrist{0%,34%{transform:rotateZ(-2deg) translateY(0)}62%,100%{transform:rotateZ(3deg) translateY(1.5px)}}",
|
|
1638
|
+
"@keyframes dkan-output-head{0%,46%,100%{transform:var(--dk3-head-turn) rotateZ(3deg)}58%{transform:var(--dk3-head-turn) rotateZ(5deg) translateY(1px)}}",
|
|
1639
|
+
// search:身体正对桌面,头看左屏,近侧手腕小幅操作鼠标。
|
|
1640
|
+
".dkan-bot-scene[data-phase=search] .dk3-mon3.left .dk3-screen{opacity:1;box-shadow:0 0 10px color-mix(in srgb,var(--dkan-code-a,#4ade80) 52%,transparent);}",
|
|
1641
|
+
".dkan-bot-scene[data-phase=search] .dk3-mon3.left .dk3-search-results{animation-duration:calc(1.05s / var(--dkan-speed,1));}",
|
|
1642
|
+
".dkan-bot-scene[data-phase=search] .dk3-upper3{transform:rotateZ(2deg) translateY(1px);}",
|
|
1643
|
+
".dkan-bot-scene[data-phase=search] .dk3-head3{animation:dkan-search-head max(.72s,calc(1.15s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1644
|
+
".dkan-bot-scene[data-phase=search] .dk3-near .dk3-wrist3{animation:dkan-search-wrist max(.45s,calc(.72s / var(--dkan-speed,1))) cubic-bezier(.77,0,.175,1) infinite;}",
|
|
1645
|
+
".dkan-bot-scene[data-phase=search] .dk3-far{transform:translateZ(-7px) rotateZ(2deg);}",
|
|
1646
|
+
"@keyframes dkan-search-head{0%,28%{transform:var(--dk3-head-turn) rotateZ(1deg)}48%,68%{transform:var(--dk3-head-turn) rotateZ(6deg) translate(1px,1px)}88%,100%{transform:var(--dk3-head-turn) rotateZ(2deg)}}",
|
|
1647
|
+
"@keyframes dkan-search-wrist{0%,34%{transform:translateX(-1px)}62%,100%{transform:translateX(1.5px)}}",
|
|
1648
|
+
// 减少动态时停止空间位移,保留屏幕亮暗与思考状态的淡入反馈。
|
|
1649
|
+
"@media (prefers-reduced-motion:reduce){.dkan-amb *,.dkan-prev *,.dkan-space-system,.dkan-space-dust,.dkan-space-runner,.dkan-delivery,.dkan-departure,.dkan-freighter-engine i,.dkan-stuck-code::after,.dkan-stuck-code i{animation:none!important}.dkan-space-runner,.dkan-departure{display:none}.dkan-delivery{opacity:1;transform:translate3d(var(--dkan-to-x),var(--dkan-to-y),0)}.dkan-delivery-trail{display:none}.dk3-person,.dk3-upper3,.dk3-head3,.dk3-arm3,.dk3-elbow,.dk3-wrist3,.dk3-wheel,.dk3-code,.dk3-search-results{animation:none!important;transition:none!important}.dk3-screen,.dkan-bubble{transition:opacity .2s cubic-bezier(.23,1,.32,1)!important}}",
|
|
1650
|
+
].join("\n");
|
|
1651
|
+
|
|
1652
|
+
export const feature = {
|
|
1653
|
+
id: "animation",
|
|
1654
|
+
name: "任务动画",
|
|
1655
|
+
order: 130,
|
|
1656
|
+
accent: "#f472b6",
|
|
1657
|
+
description: "19 种任务运行动画:速度随任务活动联动,配置持久化(通知见「任务通知」,任务状态见「运行状态」)",
|
|
1658
|
+
css,
|
|
1659
|
+
View: AnimationView,
|
|
1660
|
+
HomeStat: AnimationStat,
|
|
1661
|
+
Overlay: AnimationOverlay,
|
|
1662
|
+
};
|