opencroc 1.6.9 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +2520 -53
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +379 -1
- package/dist/index.js +2115 -35
- package/dist/index.js.map +1 -1
- package/dist/web/index-studio.html +804 -0
- package/dist/web/index-v2-pixel.html +1571 -0
- package/dist/web/index.html +517 -1512
- package/dist/web/js/agents.js +465 -0
- package/dist/web/js/camera.js +125 -0
- package/dist/web/js/dataviz.js +288 -0
- package/dist/web/js/effects.js +345 -0
- package/dist/web/js/engine.js +489 -0
- package/dist/web/js/office.js +816 -0
- package/dist/web/js/state.js +37 -0
- package/dist/web/js/ui.js +384 -0
- package/package.json +9 -3
package/dist/web/index.html
CHANGED
|
@@ -1,1568 +1,573 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="zh-CN" data-theme="
|
|
2
|
+
<html lang="zh-CN" data-theme="light">
|
|
3
3
|
<head>
|
|
4
|
-
<meta charset="
|
|
5
|
-
<meta name="viewport" content="width=device-width,
|
|
6
|
-
<title>OpenCroc Studio</title>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
6
|
+
<title>OpenCroc Studio · 3D</title>
|
|
7
7
|
<style>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
--accent-bg: rgba(5, 150, 105, 0.1);
|
|
50
|
-
--red: #dc2626;
|
|
51
|
-
--red-bg: rgba(220, 38, 38, 0.08);
|
|
52
|
-
--orange: #d97706;
|
|
53
|
-
--orange-bg: rgba(217, 119, 6, 0.08);
|
|
54
|
-
--blue: #2563eb;
|
|
55
|
-
--blue-bg: rgba(37, 99, 235, 0.08);
|
|
56
|
-
--purple: #7c3aed;
|
|
57
|
-
--purple-bg: rgba(124, 58, 237, 0.08);
|
|
58
|
-
--text: #0f172a;
|
|
59
|
-
--text-dim: #475569;
|
|
60
|
-
--text-subtle:#94a3b8;
|
|
61
|
-
--border: rgba(100, 116, 139, 0.2);
|
|
62
|
-
--border-accent: rgba(5, 150, 105, 0.35);
|
|
63
|
-
--shadow-lg: 0 20px 40px rgba(0, 0, 0, 0.08);
|
|
64
|
-
--shadow-md: 0 8px 24px rgba(0, 0, 0, 0.06);
|
|
65
|
-
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.04);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/* ═══════════════════════════════════════════════════════
|
|
69
|
-
Base Reset & Typography (system font stack)
|
|
70
|
-
═══════════════════════════════════════════════════════ */
|
|
71
|
-
* { margin:0; padding:0; box-sizing:border-box; }
|
|
72
|
-
body {
|
|
73
|
-
background: var(--bg);
|
|
74
|
-
color: var(--text);
|
|
75
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", "PingFang SC", sans-serif;
|
|
76
|
-
overflow: hidden;
|
|
77
|
-
height: 100vh;
|
|
78
|
-
line-height: 1.5;
|
|
79
|
-
-webkit-font-smoothing: antialiased;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/* Scrollbar styling */
|
|
83
|
-
::-webkit-scrollbar { width: 5px; height: 5px; }
|
|
84
|
-
::-webkit-scrollbar-track { background: transparent; }
|
|
85
|
-
::-webkit-scrollbar-thumb { background: var(--text-subtle); border-radius: 4px; }
|
|
86
|
-
::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }
|
|
87
|
-
|
|
88
|
-
/* ═══════════════════════════════════════════════════════
|
|
89
|
-
Layout Grid — Responsive
|
|
90
|
-
═══════════════════════════════════════════════════════ */
|
|
91
|
-
.app {
|
|
92
|
-
display: grid;
|
|
93
|
-
grid-template-rows: 56px 1fr 220px;
|
|
94
|
-
grid-template-columns: 240px 1fr 320px;
|
|
95
|
-
grid-template-areas:
|
|
96
|
-
"header header header"
|
|
97
|
-
"sidebar main panel"
|
|
98
|
-
"office office office";
|
|
99
|
-
height: 100vh;
|
|
100
|
-
gap: 6px;
|
|
101
|
-
padding: 6px;
|
|
102
|
-
}
|
|
103
|
-
@media (max-width: 1200px) {
|
|
104
|
-
.app {
|
|
105
|
-
grid-template-columns: 200px 1fr 280px;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
@media (max-width: 960px) {
|
|
109
|
-
.app {
|
|
110
|
-
grid-template-columns: 1fr;
|
|
111
|
-
grid-template-rows: 56px 1fr 200px;
|
|
112
|
-
grid-template-areas:
|
|
113
|
-
"header"
|
|
114
|
-
"main"
|
|
115
|
-
"office";
|
|
116
|
-
}
|
|
117
|
-
.sidebar, .log-panel { display: none; }
|
|
118
|
-
.sidebar.mobile-open, .log-panel.mobile-open {
|
|
119
|
-
display: flex;
|
|
120
|
-
position: fixed;
|
|
121
|
-
top: 62px; bottom: 0;
|
|
122
|
-
width: 280px;
|
|
123
|
-
z-index: 150;
|
|
124
|
-
}
|
|
125
|
-
.sidebar.mobile-open { left: 0; }
|
|
126
|
-
.log-panel.mobile-open { right: 0; }
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/* ═══════════════════════════════════════════════════════
|
|
130
|
-
Header
|
|
131
|
-
═══════════════════════════════════════════════════════ */
|
|
132
|
-
.header {
|
|
133
|
-
grid-area: header;
|
|
134
|
-
background: var(--bg-panel);
|
|
135
|
-
border: 1px solid var(--border);
|
|
136
|
-
border-radius: var(--radius-lg);
|
|
137
|
-
display: flex;
|
|
138
|
-
align-items: center;
|
|
139
|
-
padding: 0 14px;
|
|
140
|
-
gap: 10px;
|
|
141
|
-
box-shadow: var(--shadow-sm);
|
|
142
|
-
}
|
|
143
|
-
.header .logo {
|
|
144
|
-
width: 36px; height: 36px;
|
|
145
|
-
border-radius: var(--radius-md);
|
|
146
|
-
display: grid; place-items: center;
|
|
147
|
-
background: var(--accent-bg);
|
|
148
|
-
border: 1px solid var(--border-accent);
|
|
149
|
-
flex-shrink: 0;
|
|
150
|
-
}
|
|
151
|
-
.header .logo svg { width: 22px; height: 22px; }
|
|
152
|
-
.header .title-wrap { display:flex; flex-direction:column; gap:1px; min-width:0; }
|
|
153
|
-
.header h1 { font-size:14px; font-weight:700; color:var(--accent); letter-spacing:.3px; white-space:nowrap; }
|
|
154
|
-
.header .subtitle { font-size:10px; color:var(--text-subtle); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
155
|
-
.header .actions { display:flex; gap:5px; flex-shrink:0; }
|
|
156
|
-
.header .spacer { flex:1; }
|
|
157
|
-
|
|
158
|
-
/* View Switch */
|
|
159
|
-
.view-switch {
|
|
160
|
-
display: flex;
|
|
161
|
-
background: var(--bg-card);
|
|
162
|
-
border: 1px solid var(--border);
|
|
163
|
-
border-radius: var(--radius-md);
|
|
164
|
-
padding: 2px;
|
|
165
|
-
margin-right: 2px;
|
|
166
|
-
}
|
|
167
|
-
.view-switch button {
|
|
168
|
-
background: transparent;
|
|
169
|
-
color: var(--text-dim);
|
|
170
|
-
border: none;
|
|
171
|
-
font-family: inherit;
|
|
172
|
-
font-size: 11px;
|
|
173
|
-
padding: 5px 12px;
|
|
174
|
-
border-radius: var(--radius-sm);
|
|
175
|
-
cursor: pointer;
|
|
176
|
-
transition: all var(--transition);
|
|
177
|
-
display: flex; align-items: center; gap: 4px;
|
|
178
|
-
}
|
|
179
|
-
.view-switch button:hover { color: var(--text); background: var(--bg-hover); }
|
|
180
|
-
.view-switch button.active {
|
|
181
|
-
background: var(--accent-bg);
|
|
182
|
-
color: var(--accent);
|
|
183
|
-
box-shadow: 0 0 0 1px var(--border-accent);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/* Buttons */
|
|
187
|
-
.btn {
|
|
188
|
-
background: var(--accent-dim);
|
|
189
|
-
color: #fff;
|
|
190
|
-
border: 1px solid var(--border-accent);
|
|
191
|
-
padding: 5px 12px;
|
|
192
|
-
font-family: inherit;
|
|
193
|
-
font-size: 11px;
|
|
194
|
-
font-weight: 500;
|
|
195
|
-
border-radius: var(--radius-sm);
|
|
196
|
-
cursor: pointer;
|
|
197
|
-
transition: all var(--transition);
|
|
198
|
-
display: flex; align-items: center; gap: 4px;
|
|
199
|
-
white-space: nowrap;
|
|
200
|
-
}
|
|
201
|
-
.btn:hover { background: var(--accent); transform: translateY(-1px); box-shadow: var(--shadow-sm); }
|
|
202
|
-
.btn:active { transform: translateY(0); }
|
|
203
|
-
.btn:disabled { opacity: .35; cursor: not-allowed; transform: none; }
|
|
204
|
-
.btn.danger { background: #991b1b; border-color: rgba(248,113,113,.3); }
|
|
205
|
-
.btn.danger:hover { background: #b91c1c; }
|
|
206
|
-
.btn svg { width: 14px; height: 14px; }
|
|
207
|
-
.mode-select {
|
|
208
|
-
background: var(--bg-card);
|
|
209
|
-
color: var(--text);
|
|
210
|
-
border: 1px solid var(--border);
|
|
211
|
-
border-radius: var(--radius-sm);
|
|
212
|
-
font-family: inherit;
|
|
213
|
-
font-size: 11px;
|
|
214
|
-
padding: 5px 8px;
|
|
215
|
-
min-width: 80px;
|
|
216
|
-
cursor: pointer;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/* Stats */
|
|
220
|
-
.header .stats { display:flex; gap:6px; font-size:10px; color:var(--text-dim); flex-shrink:0; }
|
|
221
|
-
.header .stats > div {
|
|
222
|
-
background: var(--bg-card);
|
|
223
|
-
border: 1px solid var(--border);
|
|
224
|
-
border-radius: var(--radius-sm);
|
|
225
|
-
padding: 4px 8px;
|
|
226
|
-
min-width: 60px;
|
|
227
|
-
text-align: center;
|
|
228
|
-
}
|
|
229
|
-
.header .stats .label { font-size:9px; color:var(--text-subtle); text-transform:uppercase; letter-spacing:.5px; }
|
|
230
|
-
.header .stats .value { display:block; color:var(--accent); font-weight:700; font-size:13px; margin-top:1px; }
|
|
231
|
-
.conn-dot {
|
|
232
|
-
width: 10px; height: 10px;
|
|
233
|
-
border-radius: 50%;
|
|
234
|
-
background: var(--red);
|
|
235
|
-
box-shadow: 0 0 0 3px var(--red-bg);
|
|
236
|
-
transition: all .3s;
|
|
237
|
-
flex-shrink: 0;
|
|
238
|
-
}
|
|
239
|
-
.conn-dot.on { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-bg); animation: conn-pulse 2s infinite; }
|
|
240
|
-
@keyframes conn-pulse { 0%,100%{ box-shadow: 0 0 0 3px var(--accent-bg) } 50%{ box-shadow: 0 0 0 6px var(--accent-bg) } }
|
|
241
|
-
|
|
242
|
-
/* Theme toggle */
|
|
243
|
-
.theme-toggle {
|
|
244
|
-
background: none; border: 1px solid var(--border); color: var(--text-dim);
|
|
245
|
-
width: 32px; height: 32px; border-radius: var(--radius-sm);
|
|
246
|
-
display: grid; place-items: center; cursor: pointer;
|
|
247
|
-
transition: all var(--transition);
|
|
248
|
-
}
|
|
249
|
-
.theme-toggle:hover { background: var(--bg-hover); color: var(--text); }
|
|
250
|
-
.theme-toggle svg { width: 16px; height: 16px; }
|
|
251
|
-
|
|
252
|
-
/* ═══════════════════════════════════════════════════════
|
|
253
|
-
Sidebar
|
|
254
|
-
═══════════════════════════════════════════════════════ */
|
|
255
|
-
.sidebar {
|
|
256
|
-
grid-area: sidebar;
|
|
257
|
-
background: var(--bg-panel);
|
|
258
|
-
border: 1px solid var(--border);
|
|
259
|
-
border-radius: var(--radius-lg);
|
|
260
|
-
overflow-y: auto;
|
|
261
|
-
padding: 10px;
|
|
262
|
-
box-shadow: var(--shadow-sm);
|
|
263
|
-
}
|
|
264
|
-
.sidebar h3 {
|
|
265
|
-
font-size: 10px; text-transform: uppercase; color: var(--text-subtle);
|
|
266
|
-
padding: 10px 4px 6px; letter-spacing: 1.2px; font-weight: 600;
|
|
267
|
-
display: flex; align-items: center; gap: 6px;
|
|
268
|
-
}
|
|
269
|
-
.sidebar h3 svg { width: 14px; height: 14px; opacity: 0.6; }
|
|
270
|
-
.mod-item {
|
|
271
|
-
padding: 7px 10px;
|
|
272
|
-
border-radius: var(--radius-sm);
|
|
273
|
-
font-size: 12px;
|
|
274
|
-
cursor: pointer;
|
|
275
|
-
display: flex;
|
|
276
|
-
align-items: center;
|
|
277
|
-
gap: 8px;
|
|
278
|
-
transition: all var(--transition);
|
|
279
|
-
margin-bottom: 2px;
|
|
280
|
-
border: 1px solid transparent;
|
|
281
|
-
color: var(--text-dim);
|
|
282
|
-
}
|
|
283
|
-
.mod-item:hover {
|
|
284
|
-
background: var(--bg-hover);
|
|
285
|
-
border-color: var(--border);
|
|
286
|
-
color: var(--text);
|
|
287
|
-
transform: translateX(2px);
|
|
288
|
-
}
|
|
289
|
-
.mod-count {
|
|
290
|
-
margin-left: auto; font-size: 9px; color: var(--text-subtle);
|
|
291
|
-
background: var(--bg-card); padding: 1px 6px; border-radius: 8px;
|
|
292
|
-
}
|
|
293
|
-
.dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; transition: background .3s; }
|
|
294
|
-
.dot.idle { background: var(--text-subtle); }
|
|
295
|
-
.dot.testing, .dot.working { background: var(--orange); animation: dot-pulse 1s infinite; }
|
|
296
|
-
.dot.thinking { background: var(--blue); animation: dot-pulse 1.4s infinite; }
|
|
297
|
-
.dot.passed, .dot.done { background: var(--accent); }
|
|
298
|
-
.dot.failed, .dot.error { background: var(--red); animation: dot-shake .3s infinite; }
|
|
299
|
-
@keyframes dot-pulse { 0%,100%{ opacity:1; transform:scale(1) } 50%{ opacity:.5; transform:scale(.85) } }
|
|
300
|
-
@keyframes dot-shake { 0%,100%{ transform:translateX(0) } 25%{ transform:translateX(-1px) } 75%{ transform:translateX(1px) } }
|
|
301
|
-
|
|
302
|
-
/* ═══════════════════════════════════════════════════════
|
|
303
|
-
Main Content
|
|
304
|
-
═══════════════════════════════════════════════════════ */
|
|
305
|
-
.main {
|
|
306
|
-
grid-area: main;
|
|
307
|
-
position: relative;
|
|
308
|
-
overflow: hidden;
|
|
309
|
-
background: var(--bg-panel);
|
|
310
|
-
border: 1px solid var(--border);
|
|
311
|
-
border-radius: var(--radius-lg);
|
|
312
|
-
box-shadow: var(--shadow-md);
|
|
313
|
-
}
|
|
314
|
-
#graph-canvas { width:100%; height:100%; display:block; cursor:grab; }
|
|
315
|
-
.view { position:absolute; inset:0; }
|
|
316
|
-
.view.hidden { display:none; }
|
|
317
|
-
|
|
318
|
-
/* Pixel Office Stage */
|
|
319
|
-
.pixel-stage {
|
|
320
|
-
position: relative; width:100%; height:100%; overflow:hidden;
|
|
321
|
-
background:
|
|
322
|
-
linear-gradient(rgba(8,12,20,.15), rgba(8,12,20,.4)),
|
|
323
|
-
url('./assets/star/office_bg_small.webp') center/cover no-repeat;
|
|
324
|
-
}
|
|
325
|
-
.pixel-stage::before {
|
|
326
|
-
content:''; position:absolute; inset:0;
|
|
327
|
-
background-image:
|
|
328
|
-
linear-gradient(rgba(148,163,184,.08) 1px, transparent 1px),
|
|
329
|
-
linear-gradient(90deg, rgba(148,163,184,.08) 1px, transparent 1px);
|
|
330
|
-
background-size: 32px 32px;
|
|
331
|
-
pointer-events:none;
|
|
332
|
-
}
|
|
333
|
-
.pixel-stage .asset {
|
|
334
|
-
position:absolute; image-rendering:pixelated; z-index:2;
|
|
335
|
-
filter: drop-shadow(0 8px 16px rgba(0,0,0,.4));
|
|
336
|
-
transition: opacity .3s;
|
|
337
|
-
}
|
|
338
|
-
.pixel-stage .desk-asset { width:180px; bottom:90px; left:40px; }
|
|
339
|
-
.pixel-stage .server-asset { width:110px; top:24px; right:34px; }
|
|
340
|
-
.pixel-stage .coffee-asset { width:72px; top:100px; right:180px; }
|
|
341
|
-
.pixel-stage .walls-asset { width:220px; top:10px; left:46%; transform:translateX(-50%); opacity:.4; }
|
|
342
|
-
.pixel-agent-layer { position:absolute; inset:0; z-index:3; }
|
|
343
|
-
.pixel-agent {
|
|
344
|
-
position:absolute; width:44px;
|
|
345
|
-
transition: left .6s cubic-bezier(.4,0,.2,1), top .6s cubic-bezier(.4,0,.2,1);
|
|
346
|
-
image-rendering:pixelated;
|
|
347
|
-
filter: drop-shadow(0 6px 10px rgba(0,0,0,.4));
|
|
348
|
-
}
|
|
349
|
-
/* Sprite-canvas replaces static img for animated agents */
|
|
350
|
-
.pixel-sprite-canvas {
|
|
351
|
-
position:absolute; width:48px; height:48px;
|
|
352
|
-
image-rendering:pixelated;
|
|
353
|
-
transition: left .6s cubic-bezier(.4,0,.2,1), top .6s cubic-bezier(.4,0,.2,1);
|
|
354
|
-
filter: drop-shadow(0 6px 10px rgba(0,0,0,.4));
|
|
355
|
-
}
|
|
356
|
-
.pixel-agent.working,.pixel-agent.testing { animation: agent-bob .5s ease-in-out infinite alternate; }
|
|
357
|
-
.pixel-agent.thinking { animation: agent-think 1.2s ease-in-out infinite; }
|
|
358
|
-
.pixel-agent.error,.pixel-agent.failed { animation: agent-shake .25s ease-in-out infinite; }
|
|
359
|
-
.pixel-agent.done,.pixel-agent.passed { animation: agent-pulse .9s ease-in-out infinite; }
|
|
360
|
-
.pixel-sprite-canvas.working,.pixel-sprite-canvas.testing { animation: agent-bob .5s ease-in-out infinite alternate; }
|
|
361
|
-
.pixel-sprite-canvas.thinking { animation: agent-think 1.2s ease-in-out infinite; }
|
|
362
|
-
.pixel-sprite-canvas.error,.pixel-sprite-canvas.failed { animation: agent-shake .25s ease-in-out infinite; }
|
|
363
|
-
.pixel-sprite-canvas.done,.pixel-sprite-canvas.passed { animation: agent-pulse .9s ease-in-out infinite; }
|
|
364
|
-
@keyframes agent-bob { from{transform:translateY(0)} to{transform:translateY(-4px)} }
|
|
365
|
-
@keyframes agent-think { 0%,100%{transform:rotate(0) scale(1)} 50%{transform:rotate(-3deg) scale(1.02)} }
|
|
366
|
-
@keyframes agent-shake { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-3px)} 75%{transform:translateX(3px)} }
|
|
367
|
-
@keyframes agent-pulse { 0%,100%{transform:scale(1)} 50%{transform:scale(1.08)} }
|
|
368
|
-
|
|
369
|
-
/* Pixel Agent Label — enhanced with status bubble */
|
|
370
|
-
.pixel-label {
|
|
371
|
-
position:absolute; transform:translate(-50%,-50%);
|
|
372
|
-
background: var(--bg-panel);
|
|
373
|
-
border: 1px solid var(--border-accent);
|
|
374
|
-
border-radius: var(--radius-sm);
|
|
375
|
-
padding: 3px 8px;
|
|
376
|
-
font-size: 10px;
|
|
377
|
-
color: var(--text);
|
|
378
|
-
white-space:nowrap;
|
|
379
|
-
z-index:4;
|
|
380
|
-
box-shadow: var(--shadow-sm);
|
|
381
|
-
backdrop-filter: blur(4px);
|
|
382
|
-
}
|
|
383
|
-
.pixel-label .status-tag {
|
|
384
|
-
display:inline-block; font-size:8px; padding:1px 4px; border-radius:4px; margin-left:4px;
|
|
385
|
-
font-weight:600; text-transform:uppercase; letter-spacing:.3px;
|
|
386
|
-
}
|
|
387
|
-
.pixel-label .status-tag.working { background:var(--orange-bg); color:var(--orange); }
|
|
388
|
-
.pixel-label .status-tag.thinking { background:var(--blue-bg); color:var(--blue); }
|
|
389
|
-
.pixel-label .status-tag.done { background:var(--accent-bg); color:var(--accent); }
|
|
390
|
-
.pixel-label .status-tag.error { background:var(--red-bg); color:var(--red); }
|
|
391
|
-
|
|
392
|
-
/* Pixel KPIs */
|
|
393
|
-
.pixel-kpis { position:absolute; left:14px; top:14px; display:flex; gap:6px; z-index:4; }
|
|
394
|
-
.pixel-kpi {
|
|
395
|
-
min-width:80px;
|
|
396
|
-
background: var(--bg-panel);
|
|
397
|
-
border: 1px solid var(--border);
|
|
398
|
-
border-radius: var(--radius-md);
|
|
399
|
-
padding: 8px 10px;
|
|
400
|
-
box-shadow: var(--shadow-md);
|
|
401
|
-
backdrop-filter: blur(8px);
|
|
402
|
-
}
|
|
403
|
-
.pixel-kpi .t { font-size:9px; color:var(--text-subtle); text-transform:uppercase; letter-spacing:.5px; }
|
|
404
|
-
.pixel-kpi .v { font-size:16px; color:var(--accent); font-weight:700; margin-top:2px; }
|
|
405
|
-
.pixel-kpi.errors .v { color:var(--red); }
|
|
406
|
-
.pixel-kpi.done .v { color:var(--accent); }
|
|
407
|
-
|
|
408
|
-
/* Bubble messages (Star-Office style) */
|
|
409
|
-
.pixel-bubble {
|
|
410
|
-
position:absolute; z-index:5; pointer-events:none;
|
|
411
|
-
background: var(--bg-panel);
|
|
412
|
-
border: 1px solid var(--border-accent);
|
|
413
|
-
border-radius: var(--radius-sm) var(--radius-sm) var(--radius-sm) 2px;
|
|
414
|
-
padding: 4px 10px;
|
|
415
|
-
font-size: 10px;
|
|
416
|
-
color: var(--text);
|
|
417
|
-
box-shadow: var(--shadow-sm);
|
|
418
|
-
animation: bubble-in .3s ease-out, bubble-out .3s ease-in 2.7s forwards;
|
|
419
|
-
white-space: nowrap;
|
|
420
|
-
}
|
|
421
|
-
@keyframes bubble-in { from{opacity:0;transform:translateY(6px) scale(.9)} to{opacity:1;transform:translateY(0) scale(1)} }
|
|
422
|
-
@keyframes bubble-out { from{opacity:1} to{opacity:0;transform:translateY(-4px)} }
|
|
423
|
-
|
|
424
|
-
/* Tooltip */
|
|
425
|
-
.tooltip {
|
|
426
|
-
position:absolute;
|
|
427
|
-
background: var(--bg-panel);
|
|
428
|
-
border: 1px solid var(--border-accent);
|
|
429
|
-
border-radius: var(--radius-md);
|
|
430
|
-
padding: 10px 14px;
|
|
431
|
-
font-size: 12px;
|
|
432
|
-
pointer-events:none;
|
|
433
|
-
z-index:100;
|
|
434
|
-
display:none;
|
|
435
|
-
max-width:300px;
|
|
436
|
-
box-shadow: var(--shadow-lg);
|
|
437
|
-
line-height: 1.6;
|
|
438
|
-
}
|
|
439
|
-
.tooltip.visible { display:block; }
|
|
440
|
-
.tooltip b { color: var(--accent); }
|
|
441
|
-
|
|
442
|
-
/* ═══════════════════════════════════════════════════════
|
|
443
|
-
Right Panel (Log/Tests/Results/Reports)
|
|
444
|
-
═══════════════════════════════════════════════════════ */
|
|
445
|
-
.log-panel {
|
|
446
|
-
grid-area: panel;
|
|
447
|
-
background: var(--bg-panel);
|
|
448
|
-
border: 1px solid var(--border);
|
|
449
|
-
border-radius: var(--radius-lg);
|
|
450
|
-
display: flex; flex-direction: column;
|
|
451
|
-
overflow: hidden;
|
|
452
|
-
box-shadow: var(--shadow-sm);
|
|
453
|
-
}
|
|
454
|
-
.panel-tabs {
|
|
455
|
-
display: flex;
|
|
456
|
-
border-bottom: 1px solid var(--border);
|
|
457
|
-
background: var(--bg-card);
|
|
458
|
-
flex-shrink: 0;
|
|
459
|
-
}
|
|
460
|
-
.panel-tabs .tab {
|
|
461
|
-
background: none; border: none;
|
|
462
|
-
color: var(--text-dim);
|
|
463
|
-
font-family: inherit;
|
|
464
|
-
font-size: 11px;
|
|
465
|
-
padding: 10px 12px;
|
|
466
|
-
cursor: pointer;
|
|
467
|
-
border-bottom: 2px solid transparent;
|
|
468
|
-
transition: all var(--transition);
|
|
469
|
-
display: flex; align-items: center; gap: 4px;
|
|
470
|
-
}
|
|
471
|
-
.panel-tabs .tab:hover { color: var(--text); background: var(--bg-hover); }
|
|
472
|
-
.panel-tabs .tab.active { color: var(--accent); border-bottom-color: var(--accent); }
|
|
473
|
-
.panel-tabs .tab svg { width: 14px; height: 14px; }
|
|
474
|
-
.tab-badge {
|
|
475
|
-
display:inline-block; background:var(--accent); color:var(--bg);
|
|
476
|
-
border-radius:8px; padding:0 5px; font-size:9px; font-weight:700;
|
|
477
|
-
}
|
|
478
|
-
.tab-badge.alert { background:var(--red); color:#fff; }
|
|
479
|
-
|
|
480
|
-
.file-list { flex:1; overflow-y:auto; padding:6px 8px; font-size:11px; }
|
|
481
|
-
.file-item {
|
|
482
|
-
padding: 8px 10px;
|
|
483
|
-
border-bottom: 1px solid var(--border);
|
|
484
|
-
cursor: pointer;
|
|
485
|
-
border-radius: var(--radius-sm);
|
|
486
|
-
transition: all var(--transition);
|
|
487
|
-
margin-bottom:2px;
|
|
488
|
-
}
|
|
489
|
-
.file-item:hover { background: var(--bg-hover); }
|
|
490
|
-
.file-item .fname { color: var(--accent); font-weight: 600; font-size:12px; }
|
|
491
|
-
.file-item .fmeta { color: var(--text-dim); font-size: 10px; margin-top: 3px; }
|
|
492
|
-
.file-preview {
|
|
493
|
-
position: fixed; top: 50%; left: 50%;
|
|
494
|
-
transform: translate(-50%, -50%);
|
|
495
|
-
width: min(800px, 90vw);
|
|
496
|
-
max-height: 80vh;
|
|
497
|
-
background: var(--bg-panel);
|
|
498
|
-
border: 1px solid var(--border-accent);
|
|
499
|
-
border-radius: var(--radius-lg);
|
|
500
|
-
z-index: 200;
|
|
501
|
-
display: none; flex-direction: column;
|
|
502
|
-
box-shadow: var(--shadow-lg);
|
|
503
|
-
}
|
|
504
|
-
.file-preview.visible { display: flex; }
|
|
505
|
-
.file-preview .fp-header {
|
|
506
|
-
display: flex; justify-content: space-between; align-items: center;
|
|
507
|
-
padding: 12px 16px;
|
|
508
|
-
border-bottom: 1px solid var(--border);
|
|
509
|
-
}
|
|
510
|
-
.file-preview .fp-header h4 { font-size: 13px; color: var(--accent); font-weight: 600; }
|
|
511
|
-
.file-preview .fp-close {
|
|
512
|
-
background: none; border: none; color: var(--text-dim);
|
|
513
|
-
font-size: 20px; cursor: pointer; padding: 4px 8px; border-radius: var(--radius-sm);
|
|
514
|
-
transition: all var(--transition);
|
|
515
|
-
}
|
|
516
|
-
.file-preview .fp-close:hover { background: var(--bg-hover); color: var(--text); }
|
|
517
|
-
.file-preview pre {
|
|
518
|
-
flex:1; overflow:auto; padding:16px; margin:0;
|
|
519
|
-
font-size: 12px; line-height: 1.6;
|
|
520
|
-
font-family: "Cascadia Code", "Fira Code", "JetBrains Mono", Consolas, monospace;
|
|
521
|
-
color: var(--text); background: var(--bg);
|
|
522
|
-
border-radius: 0 0 var(--radius-lg) var(--radius-lg);
|
|
523
|
-
}
|
|
524
|
-
.log-list { flex:1; overflow-y:auto; padding:6px 10px; font-size:11px; line-height:1.7; }
|
|
525
|
-
.log-list .log-entry {
|
|
526
|
-
padding: 4px 0;
|
|
527
|
-
border-bottom: 1px solid var(--border);
|
|
528
|
-
word-break: break-word;
|
|
529
|
-
font-family: "Cascadia Code", "Fira Code", Consolas, monospace;
|
|
530
|
-
font-size: 10px;
|
|
531
|
-
}
|
|
532
|
-
.log-list .log-entry .timestamp { color: var(--text-subtle); margin-right: 6px; }
|
|
533
|
-
.log-list .log-entry.warn { color: var(--orange); }
|
|
534
|
-
.log-list .log-entry.error { color: var(--red); }
|
|
535
|
-
.log-list .log-entry.info { color: var(--text-dim); }
|
|
536
|
-
|
|
537
|
-
/* ═══════════════════════════════════════════════════════
|
|
538
|
-
Bottom: Croc Office (Agent Desks)
|
|
539
|
-
═══════════════════════════════════════════════════════ */
|
|
540
|
-
.office {
|
|
541
|
-
grid-area: office;
|
|
542
|
-
background: var(--bg-panel);
|
|
543
|
-
border: 1px solid var(--border);
|
|
544
|
-
border-radius: var(--radius-lg);
|
|
545
|
-
display: flex;
|
|
546
|
-
overflow-x: auto;
|
|
547
|
-
padding: 10px;
|
|
548
|
-
gap: 8px;
|
|
549
|
-
box-shadow: var(--shadow-sm);
|
|
550
|
-
}
|
|
551
|
-
.desk {
|
|
552
|
-
flex: 0 0 180px;
|
|
553
|
-
background: var(--bg-card);
|
|
554
|
-
border: 1px solid var(--border);
|
|
555
|
-
border-radius: var(--radius-md);
|
|
556
|
-
padding: 12px 10px;
|
|
557
|
-
display: flex;
|
|
558
|
-
flex-direction: column;
|
|
559
|
-
align-items: center;
|
|
560
|
-
gap: 4px;
|
|
561
|
-
position: relative;
|
|
562
|
-
overflow: hidden;
|
|
563
|
-
transition: all var(--transition);
|
|
564
|
-
box-shadow: var(--shadow-sm);
|
|
565
|
-
}
|
|
566
|
-
.desk:hover { border-color: var(--border-accent); transform: translateY(-2px); box-shadow: var(--shadow-md); }
|
|
567
|
-
.desk .badge { position:absolute; top:8px; right:8px; width:8px; height:8px; border-radius:50%; }
|
|
568
|
-
|
|
569
|
-
/* SVG Croc sprites replace emoji */
|
|
570
|
-
.desk .croc-sprite {
|
|
571
|
-
width: 48px; height: 48px;
|
|
572
|
-
position: relative; z-index: 1;
|
|
573
|
-
display: grid; place-items: center;
|
|
574
|
-
}
|
|
575
|
-
.desk .croc-sprite svg { width:44px; height:44px; }
|
|
576
|
-
.desk.idle .croc-sprite { animation: croc-idle 3s ease-in-out infinite; }
|
|
577
|
-
.desk.working .croc-sprite, .desk.testing .croc-sprite { animation: croc-work .5s ease-in-out infinite alternate; }
|
|
578
|
-
.desk.thinking .croc-sprite { animation: croc-think 1.2s ease-in-out infinite; }
|
|
579
|
-
.desk.done .croc-sprite, .desk.passed .croc-sprite { animation: croc-done .8s ease-out 1; }
|
|
580
|
-
.desk.error .croc-sprite, .desk.failed .croc-sprite { animation: croc-error .3s ease-in-out 3; }
|
|
581
|
-
@keyframes croc-idle { 0%,90%,100%{transform:translateY(0)} 95%{transform:translateY(-3px)} }
|
|
582
|
-
@keyframes croc-work { from{transform:translateY(0) rotate(-2deg)} to{transform:translateY(-5px) rotate(2deg)} }
|
|
583
|
-
@keyframes croc-think { 0%,100%{transform:scale(1) rotate(0)} 25%{transform:scale(1.04) rotate(-2deg)} 75%{transform:scale(1.04) rotate(2deg)} }
|
|
584
|
-
@keyframes croc-done { 0%{transform:scale(1)} 50%{transform:scale(1.15) translateY(-6px)} 100%{transform:scale(1)} }
|
|
585
|
-
@keyframes croc-error { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-3px)} 75%{transform:translateX(3px)} }
|
|
586
|
-
.desk .croc-name { font-size: 12px; font-weight: 600; color: var(--accent); }
|
|
587
|
-
.desk .croc-role {
|
|
588
|
-
font-size: 9px; color: var(--text-subtle); text-transform: uppercase;
|
|
589
|
-
letter-spacing: .5px; font-weight: 500;
|
|
590
|
-
}
|
|
591
|
-
.desk .croc-task {
|
|
592
|
-
font-size: 10px; color: var(--orange); text-align: center;
|
|
593
|
-
max-width: 160px; overflow: hidden; text-overflow: ellipsis;
|
|
594
|
-
white-space: nowrap; min-height: 14px;
|
|
595
|
-
}
|
|
596
|
-
.desk .progress-bar {
|
|
597
|
-
width: 90%; height: 3px; background: var(--bg-hover);
|
|
598
|
-
border-radius: 2px; margin-top: 3px; overflow: hidden;
|
|
599
|
-
}
|
|
600
|
-
.desk .progress-bar .fill {
|
|
601
|
-
height: 100%; background: linear-gradient(90deg, var(--accent-dim), var(--accent));
|
|
602
|
-
transition: width .4s cubic-bezier(.4,0,.2,1); border-radius: 2px;
|
|
603
|
-
}
|
|
604
|
-
.desk .desk-icon {
|
|
605
|
-
position: absolute; bottom: 6px; right: 8px; opacity: .25;
|
|
606
|
-
}
|
|
607
|
-
.desk .desk-icon svg { width: 16px; height: 16px; }
|
|
608
|
-
|
|
609
|
-
/* Loading skeleton */
|
|
610
|
-
.skeleton { background: linear-gradient(90deg, var(--bg-card) 25%, var(--bg-hover) 50%, var(--bg-card) 75%); background-size: 200% 100%; animation: shimmer 1.5s infinite; border-radius: var(--radius-sm); }
|
|
611
|
-
@keyframes shimmer { 0%{background-position:200% 0} 100%{background-position:-200% 0} }
|
|
8
|
+
/* ═══════════════════════════════════════════════════════════════════════════════
|
|
9
|
+
OpenCroc Studio 3D — Glass-morphism Design System
|
|
10
|
+
═══════════════════════════════════════════════════════════════════════════════ */
|
|
11
|
+
|
|
12
|
+
/* ─── CSS Reset ─────────────────────────────────────────────────────────────── */
|
|
13
|
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
14
|
+
html,body{width:100%;height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Noto Sans SC","Helvetica Neue",Arial,sans-serif}
|
|
15
|
+
body{background:#000;color:var(--text)}
|
|
16
|
+
::-webkit-scrollbar{width:6px;height:6px}
|
|
17
|
+
::-webkit-scrollbar-track{background:transparent}
|
|
18
|
+
::-webkit-scrollbar-thumb{background:var(--text-subtle);border-radius:3px}
|
|
19
|
+
::-webkit-scrollbar-thumb:hover{background:var(--text-dim)}
|
|
20
|
+
::selection{background:var(--accent);color:#000}
|
|
21
|
+
|
|
22
|
+
/* ─── Design Tokens: Dark (Default) ────────────────────────────────────────── */
|
|
23
|
+
:root{
|
|
24
|
+
--bg-void:#050510;--bg-deep:#0a0f1e;
|
|
25
|
+
--bg-panel:rgba(12,18,36,0.75);--bg-panel-solid:#0c1224;
|
|
26
|
+
--bg-card:rgba(18,26,50,0.6);--bg-hover:rgba(30,42,72,0.5);
|
|
27
|
+
--bg-glass:rgba(15,22,42,0.55);--bg-glass-strong:rgba(15,22,42,0.8);
|
|
28
|
+
--bg-frosted:rgba(20,28,52,0.45);--bg-input:rgba(15,22,44,0.6);
|
|
29
|
+
--accent:#34d399;--accent-rgb:52,211,153;--accent-dim:#059669;
|
|
30
|
+
--accent-glow:rgba(52,211,153,0.4);--accent-bg:rgba(52,211,153,0.1);
|
|
31
|
+
--red:#f87171;--red-rgb:248,113,113;--red-glow:rgba(248,113,113,0.35);--red-bg:rgba(248,113,113,0.1);
|
|
32
|
+
--orange:#fbbf24;--orange-rgb:251,191,36;--orange-glow:rgba(251,191,36,0.35);--orange-bg:rgba(251,191,36,0.1);
|
|
33
|
+
--blue:#60a5fa;--blue-rgb:96,165,250;--blue-glow:rgba(96,165,250,0.35);--blue-bg:rgba(96,165,250,0.1);
|
|
34
|
+
--purple:#a78bfa;--purple-rgb:167,139,250;--purple-glow:rgba(167,139,250,0.35);--purple-bg:rgba(167,139,250,0.1);
|
|
35
|
+
--cyan:#22d3ee;--cyan-rgb:34,211,238;--cyan-glow:rgba(34,211,238,0.35);--cyan-bg:rgba(34,211,238,0.1);
|
|
36
|
+
--pink:#f472b6;--pink-rgb:244,114,182;
|
|
37
|
+
--text:#f1f5f9;--text-dim:#94a3b8;--text-subtle:#4a5568;--text-muted:#2d3748;
|
|
38
|
+
--border:rgba(148,163,184,0.12);--border-accent:rgba(52,211,153,0.25);--border-glass:rgba(255,255,255,0.06);
|
|
39
|
+
--shadow-xl:0 25px 50px -12px rgba(0,0,0,0.6);--shadow-lg:0 20px 40px rgba(0,0,0,0.4);
|
|
40
|
+
--shadow-md:0 8px 24px rgba(0,0,0,0.3);--shadow-sm:0 2px 8px rgba(0,0,0,0.2);
|
|
41
|
+
--shadow-glow:0 0 30px rgba(52,211,153,0.15);--shadow-inset:inset 0 1px 0 rgba(255,255,255,0.05);
|
|
42
|
+
--blur-lg:blur(24px);--blur-md:blur(16px);--blur-sm:blur(8px);--blur-xs:blur(4px);
|
|
43
|
+
--radius-xl:20px;--radius-lg:14px;--radius-md:10px;--radius-sm:6px;--radius-xs:4px;--radius-full:9999px;
|
|
44
|
+
--ease-out-expo:cubic-bezier(0.16,1,0.3,1);--ease-spring:cubic-bezier(0.175,0.885,0.32,1.275);
|
|
45
|
+
--transition-fast:0.15s var(--ease-out-expo);--transition:0.25s var(--ease-out-expo);
|
|
46
|
+
--transition-slow:0.4s var(--ease-out-expo);--transition-spring:0.5s var(--ease-spring);
|
|
47
|
+
--z-canvas:0;--z-overlay:10;--z-panel:20;--z-header:30;--z-modal:40;--z-tooltip:50;--z-notify:60;
|
|
48
|
+
}
|
|
612
49
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
50
|
+
/* ─── Design Tokens: Light ─────────────────────────────────────────────────── */
|
|
51
|
+
[data-theme="light"]{
|
|
52
|
+
--bg-void:#e8ecf4;--bg-deep:#f0f4fa;
|
|
53
|
+
--bg-panel:rgba(255,255,255,0.78);--bg-panel-solid:#ffffff;
|
|
54
|
+
--bg-card:rgba(248,250,255,0.7);--bg-hover:rgba(226,232,240,0.6);
|
|
55
|
+
--bg-glass:rgba(255,255,255,0.6);--bg-glass-strong:rgba(255,255,255,0.85);
|
|
56
|
+
--bg-frosted:rgba(255,255,255,0.5);--bg-input:rgba(241,245,249,0.8);
|
|
57
|
+
--accent:#059669;--accent-rgb:5,150,105;--accent-dim:#047857;
|
|
58
|
+
--accent-glow:rgba(5,150,105,0.25);--accent-bg:rgba(5,150,105,0.08);
|
|
59
|
+
--red:#dc2626;--red-rgb:220,38,38;--red-glow:rgba(220,38,38,0.2);--red-bg:rgba(220,38,38,0.06);
|
|
60
|
+
--orange:#d97706;--orange-rgb:217,119,6;--orange-glow:rgba(217,119,6,0.2);--orange-bg:rgba(217,119,6,0.06);
|
|
61
|
+
--blue:#2563eb;--blue-rgb:37,99,235;--blue-glow:rgba(37,99,235,0.2);--blue-bg:rgba(37,99,235,0.06);
|
|
62
|
+
--purple:#7c3aed;--purple-rgb:124,58,237;--purple-glow:rgba(124,58,237,0.2);--purple-bg:rgba(124,58,237,0.06);
|
|
63
|
+
--cyan:#0891b2;--cyan-rgb:8,145,178;--cyan-glow:rgba(8,145,178,0.2);--cyan-bg:rgba(8,145,178,0.06);
|
|
64
|
+
--pink:#db2777;--pink-rgb:219,39,119;
|
|
65
|
+
--text:#0f172a;--text-dim:#475569;--text-subtle:#94a3b8;--text-muted:#cbd5e1;
|
|
66
|
+
--border:rgba(100,116,139,0.15);--border-accent:rgba(5,150,105,0.3);--border-glass:rgba(0,0,0,0.04);
|
|
67
|
+
--shadow-xl:0 25px 50px -12px rgba(0,0,0,0.12);--shadow-lg:0 20px 40px rgba(0,0,0,0.06);
|
|
68
|
+
--shadow-md:0 8px 24px rgba(0,0,0,0.05);--shadow-sm:0 2px 8px rgba(0,0,0,0.03);
|
|
69
|
+
--shadow-glow:0 0 30px rgba(5,150,105,0.08);--shadow-inset:inset 0 1px 0 rgba(255,255,255,0.8);
|
|
70
|
+
}
|
|
620
71
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
72
|
+
/* ─── 3D Canvas ────────────────────────────────────────────────────────────── */
|
|
73
|
+
#three-canvas{position:fixed;inset:0;width:100%;height:100%;z-index:var(--z-canvas);display:block}
|
|
74
|
+
|
|
75
|
+
/* ─── Glass Panel Base ─────────────────────────────────────────────────────── */
|
|
76
|
+
.glass{background:var(--bg-glass);backdrop-filter:var(--blur-md);-webkit-backdrop-filter:var(--blur-md);border:1px solid var(--border-glass);box-shadow:var(--shadow-md),var(--shadow-inset);border-radius:var(--radius-lg);transition:background var(--transition),border-color var(--transition),box-shadow var(--transition)}
|
|
77
|
+
.glass:hover{background:var(--bg-glass-strong);border-color:var(--border);box-shadow:var(--shadow-lg),var(--shadow-inset)}
|
|
78
|
+
.glass-strong{background:var(--bg-glass-strong);backdrop-filter:var(--blur-lg);-webkit-backdrop-filter:var(--blur-lg);border:1px solid var(--border);box-shadow:var(--shadow-lg),var(--shadow-inset);border-radius:var(--radius-lg)}
|
|
79
|
+
|
|
80
|
+
/* ─── Header ───────────────────────────────────────────────────────────────── */
|
|
81
|
+
.header{position:fixed;top:12px;left:12px;right:12px;height:52px;z-index:var(--z-header);display:flex;align-items:center;gap:8px;padding:0 16px;background:var(--bg-glass-strong);backdrop-filter:var(--blur-lg);-webkit-backdrop-filter:var(--blur-lg);border:1px solid var(--border-glass);border-radius:var(--radius-xl);box-shadow:var(--shadow-lg),var(--shadow-inset);transition:all var(--transition)}
|
|
82
|
+
.header::before{content:'';position:absolute;inset:0;border-radius:var(--radius-xl);background:linear-gradient(135deg,rgba(var(--accent-rgb),0.05),transparent 60%);pointer-events:none}
|
|
83
|
+
.logo{width:32px;height:32px;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--accent-bg);border:1px solid var(--border-accent);border-radius:var(--radius-md);transition:all var(--transition)}
|
|
84
|
+
.logo:hover{transform:scale(1.08) rotate(-3deg);box-shadow:var(--shadow-glow)}
|
|
85
|
+
.logo svg{width:20px;height:20px}
|
|
86
|
+
.title-wrap{display:flex;flex-direction:column;gap:0;margin-right:16px;min-width:0}
|
|
87
|
+
.title-wrap h1{font-size:14px;font-weight:700;letter-spacing:-0.02em;color:var(--text);line-height:1.2;white-space:nowrap}
|
|
88
|
+
.title-wrap .subtitle{font-size:10px;color:var(--text-subtle);letter-spacing:0.05em;text-transform:uppercase;line-height:1}
|
|
89
|
+
.h-divider{width:1px;height:28px;flex-shrink:0;background:var(--border);margin:0 4px}
|
|
90
|
+
|
|
91
|
+
/* View Switch */
|
|
92
|
+
.view-switch{display:flex;gap:2px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-md);padding:2px}
|
|
93
|
+
.view-switch button{display:flex;align-items:center;gap:5px;padding:5px 12px;border:none;border-radius:var(--radius-sm);font-size:12px;font-weight:500;color:var(--text-dim);cursor:pointer;background:transparent;transition:all var(--transition);white-space:nowrap}
|
|
94
|
+
.view-switch button:hover{color:var(--text);background:var(--bg-hover)}
|
|
95
|
+
.view-switch button.active{color:var(--text);background:var(--bg-panel);box-shadow:var(--shadow-sm)}
|
|
96
|
+
.view-switch button svg{width:14px;height:14px;flex-shrink:0}
|
|
97
|
+
|
|
98
|
+
/* Actions */
|
|
99
|
+
.actions{display:flex;gap:4px;align-items:center;flex-shrink:0}
|
|
100
|
+
.btn{display:inline-flex;align-items:center;gap:5px;padding:6px 14px;border:1px solid var(--border);border-radius:var(--radius-sm);cursor:pointer;font-size:12px;font-weight:600;transition:all var(--transition);white-space:nowrap;position:relative;overflow:hidden}
|
|
101
|
+
.btn::before{content:'';position:absolute;inset:0;background:linear-gradient(180deg,rgba(255,255,255,0.06),transparent);pointer-events:none}
|
|
102
|
+
.btn-primary{background:var(--accent);color:#000;border-color:var(--accent-dim)}
|
|
103
|
+
.btn-primary:hover{background:var(--accent-dim);transform:translateY(-1px);box-shadow:0 4px 16px var(--accent-glow)}
|
|
104
|
+
.btn-danger{background:var(--red-bg);color:var(--red);border-color:rgba(var(--red-rgb),0.25)}
|
|
105
|
+
.btn-danger:hover{background:rgba(var(--red-rgb),0.18);transform:translateY(-1px);box-shadow:0 4px 16px var(--red-glow)}
|
|
106
|
+
.btn-secondary{background:var(--bg-card);color:var(--text-dim)}
|
|
107
|
+
.btn-secondary:hover{background:var(--bg-hover);color:var(--text);transform:translateY(-1px)}
|
|
108
|
+
.btn:disabled{opacity:0.4;cursor:not-allowed;transform:none!important;box-shadow:none!important}
|
|
109
|
+
.btn svg{width:14px;height:14px;flex-shrink:0}
|
|
110
|
+
.btn:active{transform:translateY(0)}
|
|
111
|
+
|
|
112
|
+
/* Run Mode */
|
|
113
|
+
.run-mode-wrap{position:relative}
|
|
114
|
+
.run-mode-wrap select{appearance:none;background:var(--bg-card);color:var(--text-dim);border:1px solid var(--border);border-radius:var(--radius-sm);padding:5px 28px 5px 10px;font-size:12px;font-weight:500;cursor:pointer;transition:all var(--transition);outline:none}
|
|
115
|
+
.run-mode-wrap select:hover{border-color:var(--border-accent);color:var(--text)}
|
|
116
|
+
.run-mode-wrap::after{content:'▾';position:absolute;right:8px;top:50%;transform:translateY(-50%);color:var(--text-subtle);font-size:10px;pointer-events:none}
|
|
117
|
+
|
|
118
|
+
/* Stats */
|
|
119
|
+
.stats{display:flex;gap:2px;margin-left:auto;flex-shrink:0}
|
|
120
|
+
.stat-box{display:flex;flex-direction:column;align-items:center;padding:4px 12px;min-width:56px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-sm);transition:all var(--transition)}
|
|
121
|
+
.stat-box:first-child{border-radius:var(--radius-sm) var(--radius-xs) var(--radius-xs) var(--radius-sm)}
|
|
122
|
+
.stat-box:last-child{border-radius:var(--radius-xs) var(--radius-sm) var(--radius-sm) var(--radius-xs)}
|
|
123
|
+
.stat-box:hover{background:var(--bg-hover);border-color:var(--border-accent)}
|
|
124
|
+
.stat-label{font-size:9px;text-transform:uppercase;letter-spacing:0.06em;color:var(--text-subtle);line-height:1}
|
|
125
|
+
.stat-value{font-size:16px;font-weight:700;color:var(--accent);line-height:1.3;font-variant-numeric:tabular-nums}
|
|
126
|
+
|
|
127
|
+
/* Header end */
|
|
128
|
+
.header-end{display:flex;align-items:center;gap:6px;margin-left:8px}
|
|
129
|
+
#theme-toggle{width:32px;height:32px;border:none;background:var(--bg-card);border-radius:var(--radius-sm);cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text-dim);transition:all var(--transition);border:1px solid var(--border)}
|
|
130
|
+
#theme-toggle:hover{background:var(--bg-hover);color:var(--text);transform:scale(1.05)}
|
|
131
|
+
#theme-toggle svg{width:16px;height:16px}
|
|
132
|
+
#conn-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;background:var(--text-subtle);transition:background 0.3s}
|
|
133
|
+
#conn-dot.connected{background:var(--accent);box-shadow:0 0 8px var(--accent-glow);animation:conn-pulse 2s infinite}
|
|
134
|
+
|
|
135
|
+
/* ─── Sidebar ──────────────────────────────────────────────────────────────── */
|
|
136
|
+
.sidebar{position:fixed;top:76px;left:12px;bottom:240px;width:220px;z-index:var(--z-panel);display:flex;flex-direction:column;overflow:hidden;background:var(--bg-glass);backdrop-filter:var(--blur-md);-webkit-backdrop-filter:var(--blur-md);border:1px solid var(--border-glass);border-radius:var(--radius-lg);box-shadow:var(--shadow-md),var(--shadow-inset);transition:all var(--transition-slow)}
|
|
137
|
+
.sidebar.collapsed{width:48px}
|
|
138
|
+
.sidebar.collapsed .sidebar-content{opacity:0;pointer-events:none}
|
|
139
|
+
.sidebar.collapsed .sidebar-toggle svg{transform:rotate(180deg)}
|
|
140
|
+
.sidebar-header{display:flex;align-items:center;justify-content:space-between;padding:12px 14px 8px;flex-shrink:0}
|
|
141
|
+
.sidebar-header h3{font-size:11px;text-transform:uppercase;letter-spacing:0.08em;color:var(--text-subtle);font-weight:600}
|
|
142
|
+
.sidebar-toggle{width:24px;height:24px;border:none;background:transparent;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text-subtle);border-radius:var(--radius-xs);transition:all var(--transition)}
|
|
143
|
+
.sidebar-toggle:hover{background:var(--bg-hover);color:var(--text)}
|
|
144
|
+
.sidebar-toggle svg{width:14px;height:14px;transition:transform var(--transition)}
|
|
145
|
+
.sidebar-content{flex:1;overflow-y:auto;padding:0 8px 8px;transition:opacity var(--transition)}
|
|
146
|
+
.mod-item{display:flex;align-items:center;gap:8px;padding:7px 10px;margin-bottom:2px;border-radius:var(--radius-sm);cursor:pointer;transition:all var(--transition)}
|
|
147
|
+
.mod-item:hover{background:var(--bg-hover)}
|
|
148
|
+
.mod-item.active{background:var(--accent-bg);color:var(--accent)}
|
|
149
|
+
.mod-item .dot{width:6px;height:6px;border-radius:50%;flex-shrink:0;background:var(--accent);opacity:0.6;transition:all var(--transition)}
|
|
150
|
+
.mod-item:hover .dot{opacity:1;transform:scale(1.3)}
|
|
151
|
+
.mod-item .name{flex:1;font-size:12px;font-weight:500;color:var(--text-dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:color var(--transition)}
|
|
152
|
+
.mod-item:hover .name{color:var(--text)}
|
|
153
|
+
.mod-item .count{font-size:11px;color:var(--text-subtle);font-variant-numeric:tabular-nums;padding:1px 6px;background:var(--bg-card);border-radius:var(--radius-xs);font-weight:500;transition:all var(--transition)}
|
|
154
|
+
.mod-item:hover .count{background:var(--bg-hover);color:var(--text-dim)}
|
|
155
|
+
.agent-item{display:flex;align-items:center;gap:8px;padding:8px 10px;margin-bottom:2px;border-radius:var(--radius-sm);transition:all var(--transition)}
|
|
156
|
+
.agent-item .status-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;transition:all 0.3s}
|
|
157
|
+
.agent-item .status-dot.working{background:var(--accent);box-shadow:0 0 6px var(--accent-glow);animation:dot-pulse 1s infinite}
|
|
158
|
+
.agent-item .status-dot.testing{background:var(--blue);box-shadow:0 0 6px var(--blue-glow);animation:dot-pulse 1s infinite}
|
|
159
|
+
.agent-item .status-dot.thinking{background:var(--purple);box-shadow:0 0 6px var(--purple-glow);animation:dot-pulse 1.2s infinite}
|
|
160
|
+
.agent-item .status-dot.error,.agent-item .status-dot.failed{background:var(--red);box-shadow:0 0 6px var(--red-glow);animation:dot-shake 0.3s infinite}
|
|
161
|
+
.agent-item .status-dot.done,.agent-item .status-dot.passed{background:var(--accent);box-shadow:0 0 6px var(--accent-glow)}
|
|
162
|
+
.agent-item .status-dot.idle{background:var(--text-subtle)}
|
|
163
|
+
.agent-item .agent-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
|
|
164
|
+
.agent-item .agent-name{font-size:12px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
165
|
+
.agent-item .agent-role{font-size:10px;color:var(--text-subtle);text-transform:uppercase;letter-spacing:0.04em}
|
|
166
|
+
|
|
167
|
+
/* ─── Right Panel ──────────────────────────────────────────────────────────── */
|
|
168
|
+
.right-panel{position:fixed;top:76px;right:12px;bottom:240px;width:320px;z-index:var(--z-panel);display:flex;flex-direction:column;background:var(--bg-glass);backdrop-filter:var(--blur-md);-webkit-backdrop-filter:var(--blur-md);border:1px solid var(--border-glass);border-radius:var(--radius-lg);box-shadow:var(--shadow-md),var(--shadow-inset);overflow:hidden;transition:all var(--transition-slow)}
|
|
169
|
+
.panel-tabs{display:flex;gap:0;padding:6px 6px 0;flex-shrink:0;border-bottom:1px solid var(--border)}
|
|
170
|
+
.tab{flex:1;display:flex;align-items:center;justify-content:center;gap:5px;padding:8px 4px;border:none;background:transparent;font-size:12px;font-weight:500;color:var(--text-subtle);cursor:pointer;position:relative;border-radius:var(--radius-sm) var(--radius-sm) 0 0;transition:all var(--transition);white-space:nowrap}
|
|
171
|
+
.tab:hover{color:var(--text-dim);background:var(--bg-hover)}
|
|
172
|
+
.tab.active{color:var(--accent);background:var(--bg-card)}
|
|
173
|
+
.tab.active::after{content:'';position:absolute;bottom:0;left:20%;right:20%;height:2px;background:var(--accent);border-radius:1px}
|
|
174
|
+
.tab svg{width:13px;height:13px;flex-shrink:0}
|
|
175
|
+
.tab-badge{font-size:9px;font-weight:700;min-width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;background:var(--accent-bg);color:var(--accent);border-radius:var(--radius-full);padding:0 4px;font-variant-numeric:tabular-nums}
|
|
176
|
+
.tab-badge.alert{background:var(--red-bg);color:var(--red)}
|
|
177
|
+
.panel-content{flex:1;overflow-y:auto;padding:8px}
|
|
178
|
+
.panel-content.hidden{display:none}
|
|
179
|
+
|
|
180
|
+
/* Log */
|
|
181
|
+
.log-entry{padding:5px 10px;margin-bottom:2px;border-radius:var(--radius-xs);font-size:12px;line-height:1.5;font-family:"SF Mono","Fira Code","Cascadia Code",Consolas,monospace;transition:background var(--transition);display:flex;gap:8px;align-items:flex-start}
|
|
182
|
+
.log-entry:hover{background:var(--bg-hover)}
|
|
183
|
+
.log-entry .timestamp{color:var(--text-subtle);font-size:10px;flex-shrink:0;font-variant-numeric:tabular-nums;padding-top:1px}
|
|
184
|
+
.log-entry .message{color:var(--text-dim);word-break:break-word;flex:1}
|
|
185
|
+
.log-entry.warn .message{color:var(--orange)}
|
|
186
|
+
.log-entry.error .message{color:var(--red)}
|
|
187
|
+
.log-entry.success .message{color:var(--accent)}
|
|
188
|
+
.tw-cursor{display:inline-block;width:2px;height:1em;background:var(--accent);margin-left:2px;vertical-align:text-bottom;animation:tw-blink 0.6s step-end infinite}
|
|
189
|
+
@keyframes tw-blink{0%,100%{opacity:1}50%{opacity:0}}
|
|
190
|
+
|
|
191
|
+
/* Files */
|
|
192
|
+
.file-item{display:flex;align-items:center;gap:8px;padding:8px 10px;margin-bottom:2px;border-radius:var(--radius-sm);cursor:pointer;transition:all var(--transition)}
|
|
193
|
+
.file-item:hover{background:var(--bg-hover)}
|
|
194
|
+
.file-item svg{width:14px;height:14px;flex-shrink:0;color:var(--text-subtle)}
|
|
195
|
+
.file-item .file-name{flex:1;font-size:12px;color:var(--text-dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
196
|
+
.file-item:hover .file-name{color:var(--text)}
|
|
197
|
+
|
|
198
|
+
/* Results */
|
|
199
|
+
.results-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;padding:4px}
|
|
200
|
+
.result-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-md);padding:12px;display:flex;flex-direction:column;gap:4px;transition:all var(--transition)}
|
|
201
|
+
.result-card:hover{border-color:var(--border-accent);background:var(--bg-hover)}
|
|
202
|
+
.result-card .label{font-size:10px;text-transform:uppercase;letter-spacing:0.06em;color:var(--text-subtle)}
|
|
203
|
+
.result-card .value{font-size:24px;font-weight:700;font-variant-numeric:tabular-nums;line-height:1}
|
|
204
|
+
.result-card.passed .value{color:var(--accent)}
|
|
205
|
+
.result-card.failed .value{color:var(--red)}
|
|
206
|
+
.result-card.skipped .value{color:var(--orange)}
|
|
207
|
+
.result-card.timeout .value{color:var(--purple)}
|
|
208
|
+
.pass-rate-bar{height:6px;background:var(--bg-card);border-radius:3px;overflow:hidden;margin:8px 4px;border:1px solid var(--border)}
|
|
209
|
+
.pass-rate-bar .fill{height:100%;border-radius:3px;background:linear-gradient(90deg,var(--accent),var(--cyan));transition:width 0.8s var(--ease-out-expo)}
|
|
210
|
+
.quality-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px;padding:4px}
|
|
211
|
+
.quality-item{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-sm);padding:8px;text-align:center;transition:all var(--transition)}
|
|
212
|
+
.quality-item:hover{border-color:var(--border-accent)}
|
|
213
|
+
.quality-item .q-label{font-size:9px;text-transform:uppercase;letter-spacing:0.04em;color:var(--text-subtle)}
|
|
214
|
+
.quality-item .q-value{font-size:14px;font-weight:700;color:var(--blue);margin-top:2px}
|
|
215
|
+
|
|
216
|
+
/* ─── Bottom Bar ───────────────────────────────────────────────────────────── */
|
|
217
|
+
.bottom-bar{position:fixed;bottom:12px;left:12px;right:12px;height:216px;z-index:var(--z-panel);display:flex;flex-direction:column;background:var(--bg-glass);backdrop-filter:var(--blur-md);-webkit-backdrop-filter:var(--blur-md);border:1px solid var(--border-glass);border-radius:var(--radius-lg);box-shadow:var(--shadow-lg),var(--shadow-inset);overflow:hidden;transition:all var(--transition-slow)}
|
|
218
|
+
.bottom-bar.collapsed{height:40px}
|
|
219
|
+
.bottom-bar.collapsed .desk-row{opacity:0;pointer-events:none}
|
|
220
|
+
.bottom-bar-header{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;flex-shrink:0;border-bottom:1px solid var(--border);cursor:pointer}
|
|
221
|
+
.bottom-bar-header h3{font-size:11px;text-transform:uppercase;letter-spacing:0.08em;color:var(--text-subtle);font-weight:600}
|
|
222
|
+
.bottom-bar-header .toggle-icon{color:var(--text-subtle);transition:transform var(--transition)}
|
|
223
|
+
.bottom-bar.collapsed .toggle-icon{transform:rotate(180deg)}
|
|
224
|
+
.desk-row{flex:1;display:flex;gap:8px;padding:8px 12px;overflow-x:auto;overflow-y:hidden;transition:opacity var(--transition)}
|
|
225
|
+
|
|
226
|
+
/* Desk Cards */
|
|
227
|
+
.desk-card{flex:0 0 200px;display:flex;flex-direction:column;gap:6px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-md);padding:12px;position:relative;overflow:hidden;transition:all var(--transition)}
|
|
228
|
+
.desk-card::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:var(--accent);opacity:0;transition:opacity var(--transition)}
|
|
229
|
+
.desk-card:hover{border-color:var(--border-accent);transform:translateY(-2px);box-shadow:var(--shadow-md)}
|
|
230
|
+
.desk-card:hover::before{opacity:1}
|
|
231
|
+
.desk-card.working::before{background:var(--accent);opacity:1}
|
|
232
|
+
.desk-card.error::before,.desk-card.failed::before{background:var(--red);opacity:1}
|
|
233
|
+
.desk-card.thinking::before{background:var(--purple);opacity:1}
|
|
234
|
+
.desk-card.testing::before{background:var(--blue);opacity:1}
|
|
235
|
+
.desk-card .card-top{display:flex;align-items:center;gap:8px}
|
|
236
|
+
.desk-card .card-avatar{width:36px;height:36px;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--bg-hover);border:1px solid var(--border);border-radius:var(--radius-sm)}
|
|
237
|
+
.desk-card .card-avatar svg{width:20px;height:20px}
|
|
238
|
+
.desk-card .card-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
|
|
239
|
+
.desk-card .card-name{font-size:13px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
240
|
+
.desk-card .card-role{font-size:10px;color:var(--text-subtle);text-transform:uppercase;letter-spacing:0.04em}
|
|
241
|
+
.desk-card .status-badge{position:absolute;top:8px;right:8px;font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:0.04em;padding:2px 6px;border-radius:var(--radius-xs)}
|
|
242
|
+
.desk-card .status-badge.working{background:var(--accent-bg);color:var(--accent)}
|
|
243
|
+
.desk-card .status-badge.testing{background:var(--blue-bg);color:var(--blue)}
|
|
244
|
+
.desk-card .status-badge.thinking{background:var(--purple-bg);color:var(--purple)}
|
|
245
|
+
.desk-card .status-badge.error,.desk-card .status-badge.failed{background:var(--red-bg);color:var(--red)}
|
|
246
|
+
.desk-card .status-badge.done,.desk-card .status-badge.passed{background:var(--accent-bg);color:var(--accent)}
|
|
247
|
+
.desk-card .status-badge.idle{background:var(--bg-hover);color:var(--text-subtle)}
|
|
248
|
+
.desk-card .card-task{font-size:11px;color:var(--text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-height:16px}
|
|
249
|
+
.desk-card .progress-bar{height:3px;background:var(--bg-hover);border-radius:2px;overflow:hidden}
|
|
250
|
+
.desk-card .progress-bar .fill{height:100%;border-radius:2px;background:linear-gradient(90deg,var(--accent),var(--cyan));transition:width 0.4s var(--ease-out-expo)}
|
|
251
|
+
|
|
252
|
+
/* ─── File Preview Modal ───────────────────────────────────────────────────── */
|
|
253
|
+
.file-preview{position:fixed;inset:0;z-index:var(--z-modal);display:flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity var(--transition)}
|
|
254
|
+
.file-preview.visible{opacity:1;pointer-events:auto}
|
|
255
|
+
.file-preview .backdrop{position:absolute;inset:0;background:rgba(0,0,0,0.5);backdrop-filter:var(--blur-sm)}
|
|
256
|
+
.file-preview .fp-dialog{position:relative;width:min(800px,90vw);height:min(600px,80vh);background:var(--bg-glass-strong);backdrop-filter:var(--blur-lg);border:1px solid var(--border);border-radius:var(--radius-xl);box-shadow:var(--shadow-xl);display:flex;flex-direction:column;overflow:hidden;transform:scale(0.95) translateY(10px);transition:transform var(--transition-spring)}
|
|
257
|
+
.file-preview.visible .fp-dialog{transform:scale(1) translateY(0)}
|
|
258
|
+
.fp-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);flex-shrink:0}
|
|
259
|
+
.fp-header .fp-title{font-size:13px;font-weight:600;color:var(--text)}
|
|
260
|
+
.fp-header .fp-close{width:28px;height:28px;border:none;background:var(--bg-card);border-radius:var(--radius-sm);cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text-dim);transition:all var(--transition);border:1px solid var(--border)}
|
|
261
|
+
.fp-header .fp-close:hover{background:var(--red-bg);color:var(--red);border-color:rgba(var(--red-rgb),0.25)}
|
|
262
|
+
.fp-code{flex:1;overflow:auto;padding:16px;font-family:"SF Mono","Fira Code","Cascadia Code",Consolas,monospace;font-size:12px;line-height:1.6;color:var(--text-dim);white-space:pre-wrap;word-break:break-all;tab-size:2}
|
|
263
|
+
|
|
264
|
+
/* ─── Tooltip ──────────────────────────────────────────────────────────────── */
|
|
265
|
+
.tooltip{position:fixed;z-index:var(--z-tooltip);padding:6px 10px;background:var(--bg-glass-strong);backdrop-filter:var(--blur-md);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-md);font-size:11px;color:var(--text);pointer-events:none;opacity:0;transition:opacity 0.15s;max-width:280px}
|
|
266
|
+
.tooltip.visible{opacity:1}
|
|
267
|
+
|
|
268
|
+
/* ─── Shortcut Legend ──────────────────────────────────────────────────────── */
|
|
269
|
+
.shortcut-legend{position:fixed;bottom:240px;right:12px;z-index:var(--z-notify);display:grid;grid-template-columns:auto 1fr;gap:4px 10px;padding:12px 16px;background:var(--bg-glass-strong);backdrop-filter:var(--blur-lg);border:1px solid var(--border);border-radius:var(--radius-md);box-shadow:var(--shadow-lg);opacity:0;transform:translateY(8px) scale(0.96);pointer-events:none;transition:all var(--transition-spring)}
|
|
270
|
+
.shortcut-legend.visible{opacity:1;transform:translateY(0) scale(1);pointer-events:auto}
|
|
271
|
+
.shortcut-legend kbd{display:inline-flex;align-items:center;justify-content:center;min-width:22px;height:22px;padding:0 6px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-xs);font-size:11px;font-weight:600;color:var(--text);font-family:inherit;box-shadow:0 1px 2px rgba(0,0,0,0.15)}
|
|
272
|
+
.shortcut-legend span{font-size:12px;color:var(--text-dim);display:flex;align-items:center}
|
|
273
|
+
|
|
274
|
+
/* ─── Loading ──────────────────────────────────────────────────────────────── */
|
|
275
|
+
.loading-overlay{position:fixed;inset:0;z-index:100;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:20px;background:var(--bg-void);transition:opacity 0.8s var(--ease-out-expo)}
|
|
276
|
+
.loading-overlay.hidden{opacity:0;pointer-events:none}
|
|
277
|
+
.loading-bar{width:200px;height:3px;background:var(--bg-card);border-radius:2px;overflow:hidden}
|
|
278
|
+
.loading-bar .fill{width:0%;height:100%;background:linear-gradient(90deg,var(--accent),var(--cyan));border-radius:2px;transition:width 0.3s var(--ease-out-expo)}
|
|
279
|
+
.loading-text{font-size:12px;color:var(--text-subtle);letter-spacing:0.04em}
|
|
280
|
+
|
|
281
|
+
/* ─── Toast ────────────────────────────────────────────────────────────────── */
|
|
282
|
+
.toast-container{position:fixed;top:76px;right:12px;z-index:var(--z-notify);display:flex;flex-direction:column;gap:8px;pointer-events:none}
|
|
283
|
+
.toast{display:flex;align-items:center;gap:8px;padding:10px 16px;background:var(--bg-glass-strong);backdrop-filter:var(--blur-md);border:1px solid var(--border);border-radius:var(--radius-md);box-shadow:var(--shadow-lg);font-size:12px;color:var(--text);pointer-events:auto;animation:toast-in 0.4s var(--ease-spring),toast-out 0.3s ease-in 3.7s forwards;max-width:320px}
|
|
284
|
+
.toast.success{border-left:3px solid var(--accent)}
|
|
285
|
+
.toast.error{border-left:3px solid var(--red)}
|
|
286
|
+
.toast.warning{border-left:3px solid var(--orange)}
|
|
287
|
+
.toast.info{border-left:3px solid var(--blue)}
|
|
288
|
+
|
|
289
|
+
/* ─── 3D HUD ───────────────────────────────────────────────────────────────── */
|
|
290
|
+
.bubble-3d{padding:6px 12px;background:var(--bg-glass-strong);backdrop-filter:var(--blur-sm);border:1px solid var(--border-accent);border-radius:12px 12px 12px 2px;font-size:12px;color:var(--text);box-shadow:var(--shadow-sm);animation:bubble-3d-in 0.3s var(--ease-spring);pointer-events:none;white-space:nowrap}
|
|
291
|
+
.agent-label-3d{padding:3px 8px;background:var(--bg-glass);backdrop-filter:var(--blur-xs);border:1px solid var(--border-glass);border-radius:var(--radius-sm);font-size:10px;font-weight:600;color:var(--text);text-align:center;pointer-events:none;box-shadow:var(--shadow-sm);white-space:nowrap}
|
|
292
|
+
.agent-label-3d .role{display:block;font-size:8px;font-weight:400;color:var(--text-subtle);text-transform:uppercase;letter-spacing:0.04em}
|
|
293
|
+
|
|
294
|
+
/* ─── Animations ───────────────────────────────────────────────────────────── */
|
|
295
|
+
@keyframes conn-pulse{0%{box-shadow:0 0 0 0 var(--accent-glow)}70%{box-shadow:0 0 0 6px transparent}100%{box-shadow:0 0 0 0 transparent}}
|
|
296
|
+
@keyframes dot-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:0.5;transform:scale(0.85)}}
|
|
297
|
+
@keyframes dot-shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-1px)}75%{transform:translateX(1px)}}
|
|
298
|
+
@keyframes toast-in{from{opacity:0;transform:translateX(40px) scale(0.9)}to{opacity:1;transform:translateX(0) scale(1)}}
|
|
299
|
+
@keyframes toast-out{from{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(40px)}}
|
|
300
|
+
@keyframes bubble-3d-in{from{opacity:0;transform:translateY(6px) scale(0.9)}to{opacity:1;transform:translateY(0) scale(1)}}
|
|
301
|
+
@keyframes skeleton-pulse{0%{background-position:200% 0}100%{background-position:-200% 0}}
|
|
302
|
+
@keyframes float-gentle{0%,100%{transform:translateY(0)}50%{transform:translateY(-4px)}}
|
|
303
|
+
@keyframes rotate-slow{from{transform:rotate(0)}to{transform:rotate(360deg)}}
|
|
304
|
+
@keyframes glow-pulse{0%,100%{opacity:0.6}50%{opacity:1}}
|
|
305
|
+
.skeleton{background:linear-gradient(90deg,var(--bg-card) 25%,var(--bg-hover) 50%,var(--bg-card) 75%);background-size:200% 100%;animation:skeleton-pulse 1.5s ease infinite;border-radius:var(--radius-sm)}
|
|
306
|
+
|
|
307
|
+
/* ─── Responsive ───────────────────────────────────────────────────────────── */
|
|
308
|
+
@media(max-width:1200px){.sidebar{width:180px}.right-panel{width:280px}.stats{display:none}}
|
|
309
|
+
@media(max-width:960px){.sidebar{display:none}.right-panel{width:260px}}
|
|
310
|
+
@media(max-width:768px){.header{padding:0 10px;gap:4px}.right-panel{display:none}.bottom-bar{height:180px}}
|
|
311
|
+
@media print{.header,.sidebar,.bottom-bar,.right-panel,.shortcut-legend,.tooltip,.file-preview,.loading-overlay,.toast-container{display:none!important}#three-canvas{position:static;width:100%;height:auto}}
|
|
638
312
|
</style>
|
|
639
313
|
</head>
|
|
640
314
|
<body>
|
|
641
|
-
<div class="app">
|
|
642
|
-
<header class="header">
|
|
643
|
-
<div class="logo">
|
|
644
|
-
<svg viewBox="0 0 16 16" fill="none"><rect x="4" y="2" width="8" height="3" rx="1" fill="var(--accent)"/><rect x="3" y="5" width="10" height="6" rx="2" fill="var(--accent)" opacity=".8"/><rect x="5" y="11" width="2" height="2" fill="var(--accent)" opacity=".6"/><rect x="9" y="11" width="2" height="2" fill="var(--accent)" opacity=".6"/><rect x="5" y="6" width="2" height="2" rx="1" fill="var(--bg)"/><rect x="9" y="6" width="2" height="2" rx="1" fill="var(--bg)"/><rect x="6" y="9" width="4" height="1" fill="var(--bg)" opacity=".5"/></svg>
|
|
645
|
-
</div>
|
|
646
|
-
<div class="title-wrap">
|
|
647
|
-
<h1>OpenCroc Studio</h1>
|
|
648
|
-
<div class="subtitle">Pixel Ops Dashboard · Real-time Multi-Agent Runtime</div>
|
|
649
|
-
</div>
|
|
650
|
-
<div class="actions">
|
|
651
|
-
<div class="view-switch">
|
|
652
|
-
<button id="view-dashboard" class="active">
|
|
653
|
-
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor"><rect x="1" y="1" width="6" height="6" rx="1"/><rect x="9" y="1" width="6" height="3" rx="1"/><rect x="9" y="6" width="6" height="6" rx="1" opacity=".6"/><rect x="1" y="9" width="6" height="3" rx="1" opacity=".6"/></svg>
|
|
654
|
-
Dashboard
|
|
655
|
-
</button>
|
|
656
|
-
<button id="view-office">
|
|
657
|
-
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor"><rect x="2" y="8" width="12" height="6" rx="1"/><rect x="5" y="3" width="6" height="5" rx="1" opacity=".6"/><rect x="4" y="14" width="2" height="1"/><rect x="10" y="14" width="2" height="1"/></svg>
|
|
658
|
-
Pixel Office
|
|
659
|
-
</button>
|
|
660
|
-
</div>
|
|
661
|
-
<button class="btn" id="btn-scan" title="Scan project">
|
|
662
|
-
<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="7" cy="7" r="4"/><line x1="10" y1="10" x2="14" y2="14"/></svg>
|
|
663
|
-
Scan
|
|
664
|
-
</button>
|
|
665
|
-
<button class="btn" id="btn-pipeline" title="Run full pipeline">
|
|
666
|
-
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor"><polygon points="4,2 14,8 4,14"/></svg>
|
|
667
|
-
Pipeline
|
|
668
|
-
</button>
|
|
669
|
-
<select id="run-mode" class="mode-select" title="Test run mode">
|
|
670
|
-
<option value="auto">Auto</option>
|
|
671
|
-
<option value="reuse">Reuse</option>
|
|
672
|
-
<option value="managed">Managed</option>
|
|
673
|
-
</select>
|
|
674
|
-
<button class="btn" id="btn-run-tests" title="Run generated tests" disabled>
|
|
675
|
-
<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 8l3 3 7-7"/></svg>
|
|
676
|
-
Tests
|
|
677
|
-
</button>
|
|
678
|
-
<button class="btn" id="btn-reports" title="Generate reports" disabled>
|
|
679
|
-
<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="1" width="12" height="14" rx="2"/><line x1="5" y1="5" x2="11" y2="5"/><line x1="5" y1="8" x2="11" y2="8"/><line x1="5" y1="11" x2="9" y2="11"/></svg>
|
|
680
|
-
Reports
|
|
681
|
-
</button>
|
|
682
|
-
<button class="btn danger" id="btn-reset" title="Reset agents">
|
|
683
|
-
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor"><rect x="3" y="3" width="10" height="10" rx="2"/></svg>
|
|
684
|
-
Reset
|
|
685
|
-
</button>
|
|
686
|
-
</div>
|
|
687
|
-
<div class="spacer"></div>
|
|
688
|
-
<div class="stats">
|
|
689
|
-
<div><div class="label">Modules</div><span class="value" id="s-mod">-</span></div>
|
|
690
|
-
<div><div class="label">Models</div><span class="value" id="s-mdl">-</span></div>
|
|
691
|
-
<div><div class="label">APIs</div><span class="value" id="s-api">-</span></div>
|
|
692
|
-
<div><div class="label">Tests</div><span class="value" id="s-files">-</span></div>
|
|
693
|
-
<div id="s-results-wrap" style="display:none"><div class="label">Results</div><span class="value" id="s-results">-</span></div>
|
|
694
|
-
</div>
|
|
695
|
-
<button class="theme-toggle" id="theme-toggle" title="Toggle theme">
|
|
696
|
-
<svg id="theme-icon-dark" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1a7 7 0 1 0 0 14A5 5 0 0 1 8 1z"/></svg>
|
|
697
|
-
<svg id="theme-icon-light" viewBox="0 0 16 16" fill="currentColor" style="display:none"><circle cx="8" cy="8" r="3"/><g stroke="currentColor" stroke-width="1.2"><line x1="8" y1="1" x2="8" y2="3"/><line x1="8" y1="13" x2="8" y2="15"/><line x1="1" y1="8" x2="3" y2="8"/><line x1="13" y1="8" x2="15" y2="8"/><line x1="3.05" y1="3.05" x2="4.46" y2="4.46"/><line x1="11.54" y1="11.54" x2="12.95" y2="12.95"/><line x1="3.05" y1="12.95" x2="4.46" y2="11.54"/><line x1="11.54" y1="4.46" x2="12.95" y2="3.05"/></g></svg>
|
|
698
|
-
</button>
|
|
699
|
-
<div class="conn-dot" id="conn-dot" title="WebSocket"></div>
|
|
700
|
-
</header>
|
|
701
315
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
316
|
+
<!-- Loading Overlay -->
|
|
317
|
+
<div class="loading-overlay" id="loading-overlay">
|
|
318
|
+
<svg width="48" height="48" viewBox="0 0 16 16" fill="none" style="opacity:0.8"><rect x="2" y="4" width="12" height="10" rx="1" fill="none" stroke="var(--accent)" stroke-width="1.5"/><circle cx="8" cy="9" r="2.5" fill="var(--accent)"/><rect x="5" y="2" width="6" height="2" rx="0.5" fill="var(--accent)" opacity="0.6"/></svg>
|
|
319
|
+
<div class="loading-bar"><div class="fill" id="loading-fill"></div></div>
|
|
320
|
+
<div class="loading-text" id="loading-text">Initializing 3D Engine…</div>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
<!-- 3D Canvas -->
|
|
324
|
+
<canvas id="three-canvas"></canvas>
|
|
325
|
+
|
|
326
|
+
<!-- Header -->
|
|
327
|
+
<div class="header" id="header">
|
|
328
|
+
<div class="logo"><svg viewBox="0 0 16 16" fill="none"><rect x="2" y="4" width="12" height="10" rx="1" fill="none" stroke="var(--accent)" stroke-width="1.5"/><circle cx="8" cy="9" r="2.5" fill="var(--accent)"/><rect x="5" y="2" width="6" height="2" rx="0.5" fill="var(--accent)" opacity="0.6"/></svg></div>
|
|
329
|
+
<div class="title-wrap"><h1>OpenCroc Studio</h1><span class="subtitle">3D Ops Dashboard · Real-time Multi-Agent Runtime</span></div>
|
|
330
|
+
<div class="h-divider"></div>
|
|
331
|
+
<div class="view-switch">
|
|
332
|
+
<button id="view-3d" class="active"><svg viewBox="0 0 16 16" fill="none"><path d="M8 1L14.5 5v6L8 15 1.5 11V5z" stroke="currentColor" stroke-width="1.2"/><path d="M8 1v14M1.5 5L8 9l6.5-4" stroke="currentColor" stroke-width="1" opacity="0.5"/></svg>3D Office</button>
|
|
333
|
+
<button id="view-graph"><svg viewBox="0 0 16 16" fill="none"><circle cx="4" cy="4" r="2" fill="currentColor" opacity="0.6"/><circle cx="12" cy="4" r="2" fill="currentColor" opacity="0.6"/><circle cx="8" cy="12" r="2" fill="currentColor" opacity="0.6"/><line x1="4" y1="6" x2="8" y2="10" stroke="currentColor" stroke-width="1"/><line x1="12" y1="6" x2="8" y2="10" stroke="currentColor" stroke-width="1"/></svg>Graph</button>
|
|
334
|
+
</div>
|
|
335
|
+
<div class="h-divider"></div>
|
|
336
|
+
<div class="actions">
|
|
337
|
+
<button class="btn btn-secondary" id="btn-scan"><svg viewBox="0 0 16 16" fill="none"><path d="M2 8h12M4 4h8M6 12h4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>Scan</button>
|
|
338
|
+
<button class="btn btn-primary" id="btn-pipeline"><svg viewBox="0 0 16 16" fill="none"><path d="M2 8h3l2-4 2 8 2-4h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>Pipeline</button>
|
|
339
|
+
<div class="run-mode-wrap"><select id="run-mode"><option value="auto">Auto</option><option value="reuse">Reuse</option><option value="managed">Managed</option></select></div>
|
|
340
|
+
<button class="btn btn-secondary" id="btn-run-tests"><svg viewBox="0 0 16 16" fill="none"><path d="M4 2v12l8-6z" fill="currentColor" opacity="0.7"/></svg>Tests</button>
|
|
341
|
+
<button class="btn btn-secondary" id="btn-reports"><svg viewBox="0 0 16 16" fill="none"><rect x="3" y="1" width="10" height="14" rx="1" stroke="currentColor" stroke-width="1.2"/><line x1="5" y1="5" x2="11" y2="5" stroke="currentColor" stroke-width="1" opacity="0.5"/><line x1="5" y1="8" x2="11" y2="8" stroke="currentColor" stroke-width="1" opacity="0.5"/><line x1="5" y1="11" x2="9" y2="11" stroke="currentColor" stroke-width="1" opacity="0.5"/></svg>Reports</button>
|
|
342
|
+
<button class="btn btn-danger" id="btn-reset"><svg viewBox="0 0 16 16" fill="none"><path d="M2 8a6 6 0 0111.3-2.8M14 8a6 6 0 01-11.3 2.8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M14 2v4h-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>Reset</button>
|
|
343
|
+
</div>
|
|
344
|
+
<div class="stats">
|
|
345
|
+
<div class="stat-box"><span class="stat-label">MODULES</span><span class="stat-value" id="s-mod">0</span></div>
|
|
346
|
+
<div class="stat-box"><span class="stat-label">MODELS</span><span class="stat-value" id="s-mdl">0</span></div>
|
|
347
|
+
<div class="stat-box"><span class="stat-label">APIs</span><span class="stat-value" id="s-api">0</span></div>
|
|
348
|
+
<div class="stat-box"><span class="stat-label">TESTS</span><span class="stat-value" id="s-files">-</span></div>
|
|
349
|
+
</div>
|
|
350
|
+
<div class="header-end">
|
|
351
|
+
<button id="theme-toggle" title="Toggle theme"><svg id="theme-icon-dark" viewBox="0 0 16 16" fill="none"><path d="M8 1a7 7 0 100 14 5 5 0 010-14z" fill="currentColor"/></svg><svg id="theme-icon-light" viewBox="0 0 16 16" fill="none" style="display:none"><circle cx="8" cy="8" r="3" fill="currentColor"/><g stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><line x1="8" y1="1" x2="8" y2="3"/><line x1="8" y1="13" x2="8" y2="15"/><line x1="1" y1="8" x2="3" y2="8"/><line x1="13" y1="8" x2="15" y2="8"/><line x1="3.05" y1="3.05" x2="4.46" y2="4.46"/><line x1="11.54" y1="11.54" x2="12.95" y2="12.95"/><line x1="3.05" y1="12.95" x2="4.46" y2="11.54"/><line x1="11.54" y1="4.46" x2="12.95" y2="3.05"/></g></svg></button>
|
|
352
|
+
<div id="conn-dot" title="WebSocket"></div>
|
|
353
|
+
</div>
|
|
354
|
+
</div>
|
|
714
355
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
<div class="pixel-stage">
|
|
721
|
-
<img class="asset desk-asset" src="./assets/star/desk-v3.webp" alt="desk">
|
|
722
|
-
<img class="asset server-asset" src="./assets/botreview/server.gif" alt="server">
|
|
723
|
-
<img class="asset coffee-asset" src="./assets/botreview/coffee-machine.gif" alt="coffee">
|
|
724
|
-
<img class="asset walls-asset" src="./assets/botreview/walls.png" alt="walls">
|
|
725
|
-
<div class="pixel-kpis">
|
|
726
|
-
<div class="pixel-kpi"><div class="t">Working</div><div class="v" id="kpi-working">0</div></div>
|
|
727
|
-
<div class="pixel-kpi errors"><div class="t">Errors</div><div class="v" id="kpi-errors">0</div></div>
|
|
728
|
-
<div class="pixel-kpi done"><div class="t">Done</div><div class="v" id="kpi-done">0</div></div>
|
|
729
|
-
</div>
|
|
730
|
-
<div class="pixel-agent-layer" id="pixel-agent-layer"></div>
|
|
731
|
-
</div>
|
|
732
|
-
</div>
|
|
733
|
-
<div class="tooltip" id="tooltip"></div>
|
|
734
|
-
</main>
|
|
356
|
+
<!-- Sidebar -->
|
|
357
|
+
<div class="sidebar" id="sidebar">
|
|
358
|
+
<div class="sidebar-header"><h3>Modules</h3><button class="sidebar-toggle" id="sidebar-toggle"><svg viewBox="0 0 16 16" fill="none"><path d="M10 3L5 8l5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></button></div>
|
|
359
|
+
<div class="sidebar-content" id="sidebar-content"><div id="mod-list"></div><div id="agent-sidebar" style="margin-top:12px"></div></div>
|
|
360
|
+
</div>
|
|
735
361
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor"><path d="M3 1h7l3 3v11H3V1z" opacity=".3"/><path d="M10 1v3h3" fill="none" stroke="currentColor" stroke-width="1"/></svg>
|
|
744
|
-
Tests <span id="file-badge" class="tab-badge" style="display:none">0</span>
|
|
745
|
-
</button>
|
|
746
|
-
<button class="tab" data-tab="results">
|
|
747
|
-
<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.3"><path d="M3 8l3 3 7-7"/></svg>
|
|
748
|
-
Results <span id="result-badge" class="tab-badge" style="display:none">0</span>
|
|
749
|
-
</button>
|
|
750
|
-
<button class="tab" data-tab="reports">
|
|
751
|
-
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor" opacity=".7"><rect x="1" y="3" width="5" height="10" rx="1"/><rect x="7" y="1" width="8" height="14" rx="1"/></svg>
|
|
752
|
-
Reports
|
|
753
|
-
</button>
|
|
754
|
-
</div>
|
|
755
|
-
<div class="log-list" id="log-list"></div>
|
|
756
|
-
<div class="file-list" id="file-list" style="display:none"></div>
|
|
757
|
-
<div class="file-list" id="results-panel" style="display:none"></div>
|
|
758
|
-
<div class="file-list" id="reports-panel" style="display:none"></div>
|
|
362
|
+
<!-- Right Panel -->
|
|
363
|
+
<div class="right-panel" id="right-panel">
|
|
364
|
+
<div class="panel-tabs">
|
|
365
|
+
<button class="tab active" data-tab="log"><svg viewBox="0 0 16 16" fill="none"><path d="M2 3h12M2 7h8M2 11h10" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>Log</button>
|
|
366
|
+
<button class="tab" data-tab="files"><svg viewBox="0 0 16 16" fill="none"><path d="M3 1h6l4 4v10H3z" stroke="currentColor" stroke-width="1.2"/><path d="M9 1v4h4" stroke="currentColor" stroke-width="1" opacity="0.5"/></svg>Tests</button>
|
|
367
|
+
<button class="tab" data-tab="results"><svg viewBox="0 0 16 16" fill="none"><polyline points="2,12 5,6 9,10 14,3" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>Results</button>
|
|
368
|
+
<button class="tab" data-tab="reports"><svg viewBox="0 0 16 16" fill="none"><rect x="2" y="2" width="12" height="12" rx="1" stroke="currentColor" stroke-width="1.2"/><path d="M5 10V7M8 10V5M11 10V8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>Reports</button>
|
|
759
369
|
</div>
|
|
370
|
+
<div class="panel-content" id="log-list"></div>
|
|
371
|
+
<div class="panel-content hidden" id="file-list"></div>
|
|
372
|
+
<div class="panel-content hidden" id="results-panel"></div>
|
|
373
|
+
<div class="panel-content hidden" id="reports-panel"></div>
|
|
374
|
+
</div>
|
|
760
375
|
|
|
761
|
-
|
|
376
|
+
<!-- Bottom Bar -->
|
|
377
|
+
<div class="bottom-bar" id="bottom-bar">
|
|
378
|
+
<div class="bottom-bar-header" id="bottom-bar-toggle"><h3>Agent Desks</h3><svg class="toggle-icon" width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M4 10l4-4 4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
|
|
379
|
+
<div class="desk-row" id="desk-row"></div>
|
|
762
380
|
</div>
|
|
763
381
|
|
|
382
|
+
<!-- File Preview -->
|
|
764
383
|
<div class="file-preview" id="file-preview">
|
|
765
|
-
<div class="
|
|
766
|
-
<pre id="fp-code"></pre>
|
|
384
|
+
<div class="backdrop" id="fp-backdrop"></div>
|
|
385
|
+
<div class="fp-dialog"><div class="fp-header"><span class="fp-title" id="fp-title">File Preview</span><button class="fp-close" id="fp-close"><svg viewBox="0 0 16 16" width="14" height="14" fill="none"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></button></div><pre class="fp-code" id="fp-code"></pre></div>
|
|
767
386
|
</div>
|
|
768
387
|
|
|
769
|
-
<!--
|
|
388
|
+
<!-- Tooltip -->
|
|
389
|
+
<div class="tooltip" id="tooltip"><div class="tt-name"></div><div class="tt-module"></div><div class="tt-type"></div></div>
|
|
390
|
+
|
|
391
|
+
<!-- Shortcut Legend -->
|
|
770
392
|
<div class="shortcut-legend" id="shortcut-legend">
|
|
771
|
-
<kbd>1</kbd><span>
|
|
772
|
-
<kbd>
|
|
773
|
-
<kbd>
|
|
774
|
-
<kbd>
|
|
775
|
-
<kbd>
|
|
776
|
-
<kbd>R</kbd><span>Reports</span>
|
|
777
|
-
<kbd>X</kbd><span>Reset</span>
|
|
778
|
-
<kbd>D</kbd><span>Dark/Light</span>
|
|
779
|
-
<kbd>?</kbd><span>Show shortcuts</span>
|
|
780
|
-
<kbd>Esc</kbd><span>Close panel</span>
|
|
393
|
+
<kbd>1</kbd><span>3D Office</span><kbd>2</kbd><span>Graph View</span>
|
|
394
|
+
<kbd>S</kbd><span>Scan</span><kbd>P</kbd><span>Pipeline</span>
|
|
395
|
+
<kbd>T</kbd><span>Run Tests</span><kbd>R</kbd><span>Reports</span>
|
|
396
|
+
<kbd>X</kbd><span>Reset</span><kbd>D</kbd><span>Dark/Light</span>
|
|
397
|
+
<kbd>?</kbd><span>Shortcuts</span><kbd>Esc</kbd><span>Close</span>
|
|
781
398
|
</div>
|
|
782
399
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
400
|
+
<!-- Toast Container -->
|
|
401
|
+
<div class="toast-container" id="toast-container"></div>
|
|
402
|
+
|
|
403
|
+
<!-- Import Map -->
|
|
404
|
+
<script type="importmap">{"imports":{"three":"https://unpkg.com/three@0.170.0/build/three.module.js","three/addons/":"https://unpkg.com/three@0.170.0/examples/jsm/"}}</script>
|
|
405
|
+
|
|
406
|
+
<!-- Main App -->
|
|
407
|
+
<script type="module">
|
|
408
|
+
import { createEngine, resizeEngine, getRenderer, getScene, getCamera, getComposer, getClock } from './js/engine.js';
|
|
409
|
+
import { createOffice, updateOfficeLighting, getFloorY } from './js/office.js';
|
|
410
|
+
import { AgentManager } from './js/agents.js';
|
|
411
|
+
import { ParticleManager } from './js/effects.js';
|
|
412
|
+
import { CameraController } from './js/camera.js';
|
|
413
|
+
import { GraphViz, HologramDisplay } from './js/dataviz.js';
|
|
414
|
+
import { StateManager } from './js/state.js';
|
|
415
|
+
import { UIManager } from './js/ui.js';
|
|
416
|
+
|
|
417
|
+
/* ─── Constants ────────────────────────────────────────────────────────────── */
|
|
418
|
+
const ICONS={
|
|
419
|
+
croc:'<svg viewBox="0 0 16 16" fill="none"><rect x="2" y="4" width="12" height="10" rx="1" stroke="currentColor" stroke-width="1.5"/><circle cx="8" cy="9" r="2.5" fill="currentColor"/><rect x="5" y="2" width="6" height="2" rx="0.5" fill="currentColor" opacity="0.6"/></svg>',
|
|
420
|
+
parser:'<svg viewBox="0 0 16 16" fill="none"><rect x="2" y="9" width="3" height="5" rx="0.5" fill="currentColor" opacity="0.7"/><rect x="6.5" y="5" width="3" height="9" rx="0.5" fill="currentColor" opacity="0.8"/><rect x="11" y="2" width="3" height="12" rx="0.5" fill="currentColor"/></svg>',
|
|
421
|
+
analyzer:'<svg viewBox="0 0 16 16" fill="none"><rect x="2" y="8" width="2.5" height="6" rx="0.5" fill="currentColor" opacity="0.5"/><rect x="5.5" y="5" width="2.5" height="9" rx="0.5" fill="currentColor" opacity="0.7"/><rect x="9" y="3" width="2.5" height="11" rx="0.5" fill="currentColor" opacity="0.85"/><rect x="12.5" y="6" width="2.5" height="8" rx="0.5" fill="currentColor"/></svg>',
|
|
422
|
+
tester:'<svg viewBox="0 0 16 16" fill="none"><path d="M6 2h4v4l3 7a1 1 0 01-1 1H4a1 1 0 01-1-1l3-7V2z" stroke="currentColor" stroke-width="1.2"/><line x1="5" y1="2" x2="11" y2="2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>',
|
|
423
|
+
healer:'<svg viewBox="0 0 16 16" fill="none"><path d="M8 2L3 9h4l-1 5 6-7H8l1-5z" fill="currentColor" opacity="0.8"/></svg>',
|
|
424
|
+
planner:'<svg viewBox="0 0 16 16" fill="none"><rect x="3" y="1" width="10" height="14" rx="1" stroke="currentColor" stroke-width="1.2"/><path d="M6 1V3M10 1V3" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/><line x1="5" y1="6" x2="11" y2="6" stroke="currentColor" stroke-width="1" opacity="0.4"/><line x1="5" y1="9" x2="11" y2="9" stroke="currentColor" stroke-width="1" opacity="0.4"/><line x1="5" y1="12" x2="9" y2="12" stroke="currentColor" stroke-width="1" opacity="0.4"/></svg>',
|
|
425
|
+
reporter:'<svg viewBox="0 0 16 16" fill="none"><polyline points="2,12 5,6 8,9 11,4 14,7" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
795
426
|
};
|
|
796
|
-
const ROLE_ICONS
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
thinking: ['让我想想...', '分析中...', '推理...', '🤔'],
|
|
803
|
-
error: ['出错了!', '修复中...', '糟糕...', '排查问题...'],
|
|
804
|
-
idle: ['摸鱼中~', '等任务...', '☕ 喝咖啡', 'zzZ'],
|
|
805
|
-
done: ['搞定!', '完成 ✓', '下一个!'],
|
|
806
|
-
passed: ['全绿 ✓', '测试通过!'],
|
|
807
|
-
failed: ['有失败...', '需要修复'],
|
|
427
|
+
const ROLE_ICONS={parser:ICONS.parser,analyzer:ICONS.analyzer,tester:ICONS.tester,healer:ICONS.healer,planner:ICONS.planner,reporter:ICONS.reporter};
|
|
428
|
+
const BUBBLE_TEXTS={
|
|
429
|
+
working:['正在执行...','快了快了','处理中...','加油 💪'],testing:['跑测试中...','验证 API...','等结果...'],
|
|
430
|
+
thinking:['让我想想...','分析中...','推理...','🤔'],error:['出错了!','修复中...','糟糕...','排查问题...'],
|
|
431
|
+
idle:['摸鱼中~','等任务...','☕ 喝咖啡','zzZ'],done:['搞定!','完成 ✓','下一个!'],
|
|
432
|
+
passed:['全绿 ✓','测试通过!'],failed:['有失败...','需要修复'],
|
|
808
433
|
};
|
|
434
|
+
function esc(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"')}
|
|
809
435
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
generatedFiles:[], testMetrics:null, testQuality:null, reports:[], runMode:'auto',
|
|
815
|
-
currentView:'dashboard', theme:'dark', bubbleTimers:new Map(),
|
|
816
|
-
spriteSheets:new Map(), spriteAnimFrames:new Map(), spriteAnimTimers:new Map()
|
|
817
|
-
};
|
|
436
|
+
/* ─── Managers ─────────────────────────────────────────────────────────────── */
|
|
437
|
+
const state=new StateManager();
|
|
438
|
+
const ui=new UIManager(state,{ICONS,ROLE_ICONS,BUBBLE_TEXTS,esc});
|
|
439
|
+
let agentMgr, particleMgr, camCtrl, graphViz, hologram;
|
|
818
440
|
|
|
819
|
-
/*
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
{ key:'char_0', src:'./assets/botreview/char_0.png', cols:1, rows:1, total:1 },
|
|
826
|
-
{ key:'char_1', src:'./assets/botreview/char_1.png', cols:1, rows:1, total:1 },
|
|
827
|
-
{ key:'char_2', src:'./assets/botreview/char_2.png', cols:1, rows:1, total:1 },
|
|
828
|
-
],
|
|
829
|
-
// FPS by status — active states get faster animation
|
|
830
|
-
fps: { idle:2, working:8, testing:8, thinking:4, error:10, failed:10, done:4, passed:4 },
|
|
831
|
-
};
|
|
441
|
+
/* ─── Boot ─────────────────────────────────────────────────────────────────── */
|
|
442
|
+
async function boot(){
|
|
443
|
+
ui.setLoading(5,'Creating state…');
|
|
444
|
+
state.set({project:null,graph:{nodes:[],edges:[]},agents:[],ws:null,running:false,
|
|
445
|
+
generatedFiles:[],testMetrics:null,testQuality:null,reports:[],runMode:'auto',
|
|
446
|
+
currentView:'3d',theme:localStorage.getItem('opencroc-theme')||'light',modMeta:new Map(),nodePos:new Map()});
|
|
832
447
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
if(S.spriteSheets.has(cfg.key)) return;
|
|
837
|
-
const img=new Image(); img.src=cfg.src;
|
|
838
|
-
img.onload=()=>{ S.spriteSheets.set(cfg.key, {img,cols:cfg.cols,rows:cfg.rows,total:cfg.total}); };
|
|
839
|
-
});
|
|
840
|
-
}
|
|
448
|
+
const theme=state.get('theme');
|
|
449
|
+
document.documentElement.setAttribute('data-theme',theme);
|
|
450
|
+
if(theme==='light'){document.getElementById('theme-icon-dark').style.display='none';document.getElementById('theme-icon-light').style.display=''}
|
|
841
451
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
if(!sheet||!canvas) return;
|
|
846
|
-
const ctx=canvas.getContext('2d');
|
|
847
|
-
const fw=sheet.img.naturalWidth/sheet.cols;
|
|
848
|
-
const fh=sheet.img.naturalHeight/sheet.rows;
|
|
849
|
-
const col=frameIdx%sheet.cols;
|
|
850
|
-
const row=Math.floor(frameIdx/sheet.cols);
|
|
851
|
-
ctx.clearRect(0,0,canvas.width,canvas.height);
|
|
852
|
-
ctx.imageSmoothingEnabled=false; // pixel-art crisp
|
|
853
|
-
ctx.drawImage(sheet.img, col*fw,row*fh,fw,fh, 0,0,canvas.width,canvas.height);
|
|
854
|
-
}
|
|
452
|
+
ui.setLoading(10,'Initializing 3D engine…');
|
|
453
|
+
const canvas=document.getElementById('three-canvas');
|
|
454
|
+
await createEngine(canvas,theme);
|
|
855
455
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
// Stop existing timer
|
|
859
|
-
if(S.spriteAnimTimers.has(canvasId)){
|
|
860
|
-
clearInterval(S.spriteAnimTimers.get(canvasId));
|
|
861
|
-
}
|
|
862
|
-
const sheet=S.spriteSheets.get(spriteKey);
|
|
863
|
-
if(!sheet) return;
|
|
864
|
-
const fps=SPRITE_CONFIG.fps[status]||SPRITE_CONFIG.fps.idle;
|
|
865
|
-
let frame=S.spriteAnimFrames.get(canvasId)||0;
|
|
866
|
-
const canvas=document.getElementById(canvasId);
|
|
867
|
-
if(!canvas) return;
|
|
868
|
-
renderSpriteFrame(canvas, spriteKey, frame);
|
|
869
|
-
if(sheet.total<=1){ S.spriteAnimTimers.delete(canvasId); return; }
|
|
870
|
-
const timer=setInterval(()=>{
|
|
871
|
-
frame=(frame+1)%sheet.total;
|
|
872
|
-
S.spriteAnimFrames.set(canvasId, frame);
|
|
873
|
-
const c=document.getElementById(canvasId);
|
|
874
|
-
if(c) renderSpriteFrame(c, spriteKey, frame);
|
|
875
|
-
else { clearInterval(timer); S.spriteAnimTimers.delete(canvasId); }
|
|
876
|
-
}, 1000/fps);
|
|
877
|
-
S.spriteAnimTimers.set(canvasId, timer);
|
|
878
|
-
}
|
|
456
|
+
ui.setLoading(25,'Building office…');
|
|
457
|
+
await createOffice(theme);
|
|
879
458
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
═══════════════════════════════════════════════════════ */
|
|
883
|
-
function typewriterLog(msg, level, charDelay){
|
|
884
|
-
level=level||'info';
|
|
885
|
-
charDelay=charDelay||35;
|
|
886
|
-
const el=document.getElementById('log-list');
|
|
887
|
-
const d=document.createElement('div'); d.className='log-entry '+level;
|
|
888
|
-
const ts=document.createElement('span'); ts.className='timestamp';
|
|
889
|
-
ts.textContent=new Date().toLocaleTimeString();
|
|
890
|
-
d.appendChild(ts);
|
|
891
|
-
const textSpan=document.createElement('span');
|
|
892
|
-
const cursor=document.createElement('span'); cursor.className='typewriter-cursor';
|
|
893
|
-
d.appendChild(textSpan); d.appendChild(cursor);
|
|
894
|
-
el.appendChild(d); el.scrollTop=el.scrollHeight;
|
|
895
|
-
let i=0;
|
|
896
|
-
const interval=setInterval(()=>{
|
|
897
|
-
if(i<msg.length){
|
|
898
|
-
textSpan.textContent+=msg[i]; i++;
|
|
899
|
-
el.scrollTop=el.scrollHeight;
|
|
900
|
-
}else{
|
|
901
|
-
clearInterval(interval); cursor.remove();
|
|
902
|
-
}
|
|
903
|
-
}, charDelay);
|
|
904
|
-
return d;
|
|
905
|
-
}
|
|
459
|
+
ui.setLoading(40,'Setting up camera…');
|
|
460
|
+
camCtrl=new CameraController(canvas,getCamera(),getScene());
|
|
906
461
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
const r=await fetch('/api/project'); S.project=await r.json();
|
|
910
|
-
S.graph=S.project.graph||{nodes:[],edges:[]}; S.agents=S.project.agents||[];
|
|
911
|
-
layoutGraph(); updateAll();
|
|
912
|
-
}catch(e){addLog('Failed to load: '+e.message,'error');}
|
|
913
|
-
}
|
|
914
|
-
async function doScan(){
|
|
915
|
-
if(S.running)return; S.running=true; updateBtns();
|
|
916
|
-
addLog('🔍 Starting scan...','info',true);
|
|
917
|
-
try{await fetch('/api/scan',{method:'POST'});}
|
|
918
|
-
catch(e){addLog('Scan failed: '+e.message,'error');S.running=false;updateBtns();}
|
|
919
|
-
}
|
|
920
|
-
async function doPipeline(){
|
|
921
|
-
if(S.running)return; S.running=true; updateBtns();
|
|
922
|
-
addLog('▶ Starting pipeline...','info',true);
|
|
923
|
-
try{await fetch('/api/pipeline',{method:'POST'});}
|
|
924
|
-
catch(e){addLog('Pipeline failed: '+e.message,'error');S.running=false;updateBtns();}
|
|
925
|
-
}
|
|
926
|
-
async function doReset(){
|
|
927
|
-
try{await fetch('/api/reset',{method:'POST'});}catch(e){addLog('Reset failed','error');}
|
|
928
|
-
S.running=false; updateBtns(); addLog('⏹ Agents reset','info',true);
|
|
929
|
-
}
|
|
930
|
-
async function doRunTests(){
|
|
931
|
-
if(S.running)return; S.running=true; updateBtns();
|
|
932
|
-
addLog('🧪 Starting test execution ('+S.runMode+')...','info',true);
|
|
933
|
-
try{
|
|
934
|
-
const r=await fetch('/api/run-tests',{
|
|
935
|
-
method:'POST',
|
|
936
|
-
headers:{'content-type':'application/json'},
|
|
937
|
-
body:JSON.stringify({mode:S.runMode}),
|
|
938
|
-
});
|
|
939
|
-
if(!r.ok){
|
|
940
|
-
const body=await r.json().catch(()=>({error:'unknown error'}));
|
|
941
|
-
throw new Error(body.error||('HTTP '+r.status));
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
catch(e){addLog('Run tests failed: '+e.message,'error');S.running=false;updateBtns();}
|
|
945
|
-
}
|
|
946
|
-
async function doReports(){
|
|
947
|
-
if(S.running)return; S.running=true; updateBtns();
|
|
948
|
-
addLog('📊 Generating reports...','info',true);
|
|
949
|
-
try{await fetch('/api/reports/generate',{method:'POST'});}
|
|
950
|
-
catch(e){addLog('Report gen failed: '+e.message,'error');S.running=false;updateBtns();}
|
|
951
|
-
}
|
|
952
|
-
function updateBtns(){
|
|
953
|
-
document.getElementById('btn-scan').disabled=S.running;
|
|
954
|
-
document.getElementById('btn-pipeline').disabled=S.running;
|
|
955
|
-
document.getElementById('run-mode').disabled=S.running||S.generatedFiles.length===0;
|
|
956
|
-
document.getElementById('btn-run-tests').disabled=S.running||S.generatedFiles.length===0;
|
|
957
|
-
document.getElementById('btn-reports').disabled=S.running||S.generatedFiles.length===0;
|
|
958
|
-
}
|
|
462
|
+
ui.setLoading(50,'Creating agents…');
|
|
463
|
+
agentMgr=new AgentManager(getScene());
|
|
959
464
|
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
S.ws=new WebSocket(p+'//'+location.host+'/ws');
|
|
963
|
-
S.ws.onopen=()=>{document.getElementById('conn-dot').classList.add('on');};
|
|
964
|
-
S.ws.onmessage=(e)=>{
|
|
965
|
-
try{
|
|
966
|
-
const m=JSON.parse(e.data);
|
|
967
|
-
if(m.type==='agent:update'&&Array.isArray(m.payload)){
|
|
968
|
-
S.agents=m.payload; renderOffice(); renderAgentSB(); renderPixelOffice();
|
|
969
|
-
}else if(m.type==='graph:update'){
|
|
970
|
-
S.graph=m.payload; layoutGraph(); renderCanvas(); renderModList(); updateStats();
|
|
971
|
-
}else if(m.type==='log'){
|
|
972
|
-
addLog(m.payload.message, m.payload.level);
|
|
973
|
-
}else if(m.type==='files:generated'){
|
|
974
|
-
S.generatedFiles=m.payload||[];
|
|
975
|
-
renderFileList();
|
|
976
|
-
document.getElementById('s-files').textContent=S.generatedFiles.length;
|
|
977
|
-
const badge=document.getElementById('file-badge');
|
|
978
|
-
badge.textContent=S.generatedFiles.length;badge.style.display='inline';
|
|
979
|
-
}else if(m.type==='pipeline:complete'){
|
|
980
|
-
S.running=false; updateBtns();
|
|
981
|
-
if(m.payload.status==='success') addLog('✅ Pipeline complete!','info',true);
|
|
982
|
-
else addLog('❌ Pipeline failed: '+(m.payload.error||''),'error',true);
|
|
983
|
-
setTimeout(fetchProject,500);
|
|
984
|
-
}else if(m.type==='test:complete'){
|
|
985
|
-
S.running=false; S.testMetrics=m.payload.metrics||null; S.testQuality=m.payload.quality||null; updateBtns(); renderResults();
|
|
986
|
-
const met=m.payload.metrics;
|
|
987
|
-
document.getElementById('s-results-wrap').style.display='';
|
|
988
|
-
if(met){
|
|
989
|
-
document.getElementById('s-results').textContent=met.passed+'✓ '+met.failed+'✗';
|
|
990
|
-
document.getElementById('s-results').style.color=met.failed>0?'var(--red)':'var(--accent)';
|
|
991
|
-
}else{
|
|
992
|
-
document.getElementById('s-results').textContent='setup fail';
|
|
993
|
-
document.getElementById('s-results').style.color='var(--red)';
|
|
994
|
-
}
|
|
995
|
-
const rb=document.getElementById('result-badge');
|
|
996
|
-
rb.textContent=m.payload.total||0; rb.style.display='inline';
|
|
997
|
-
rb.className='tab-badge'+((met&&met.failed>0)?' alert':'');
|
|
998
|
-
}else if(m.type==='reports:generated'){
|
|
999
|
-
S.running=false; S.reports=m.payload||[]; updateBtns(); renderReports();
|
|
1000
|
-
addLog('📊 '+S.reports.length+' reports generated');
|
|
1001
|
-
}
|
|
1002
|
-
}catch{}
|
|
1003
|
-
};
|
|
1004
|
-
S.ws.onclose=()=>{document.getElementById('conn-dot').classList.remove('on');setTimeout(connectWS,3000);};
|
|
1005
|
-
}
|
|
465
|
+
ui.setLoading(60,'Creating effects…');
|
|
466
|
+
particleMgr=new ParticleManager(getScene());
|
|
1006
467
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
if(useTypewriter){
|
|
1011
|
-
typewriterLog(msg,level,30);
|
|
1012
|
-
return;
|
|
1013
|
-
}
|
|
1014
|
-
const el=document.getElementById('log-list');
|
|
1015
|
-
const d=document.createElement('div'); d.className='log-entry '+level;
|
|
1016
|
-
const ts=document.createElement('span'); ts.className='timestamp'; ts.textContent=new Date().toLocaleTimeString();
|
|
1017
|
-
d.appendChild(ts); d.appendChild(document.createTextNode(msg));
|
|
1018
|
-
el.appendChild(d); el.scrollTop=el.scrollHeight;
|
|
1019
|
-
}
|
|
1020
|
-
function updateStats(){
|
|
1021
|
-
document.getElementById('s-mod').textContent=S.graph.nodes.filter(n=>n.type==='module').length;
|
|
1022
|
-
document.getElementById('s-mdl').textContent=S.graph.nodes.filter(n=>n.type==='model').length;
|
|
1023
|
-
document.getElementById('s-api').textContent=S.graph.nodes.filter(n=>n.type==='controller'||n.type==='api').length;
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
function layoutGraph(){
|
|
1027
|
-
const nodes=S.graph.nodes; if(!nodes.length)return;
|
|
1028
|
-
const c=document.getElementById('graph-canvas');
|
|
1029
|
-
const w=c.clientWidth||800,h=c.clientHeight||500;
|
|
1030
|
-
// Group non-module nodes by module
|
|
1031
|
-
const mods=new Map();
|
|
1032
|
-
S.modMeta=new Map();
|
|
1033
|
-
for(const n of nodes){
|
|
1034
|
-
if(n.type==='module') continue;
|
|
1035
|
-
const m=n.module||'other';
|
|
1036
|
-
if(!mods.has(m)) mods.set(m,[]);
|
|
1037
|
-
mods.get(m).push(n);
|
|
1038
|
-
}
|
|
1039
|
-
// Sort by size descending
|
|
1040
|
-
const keys=[...mods.keys()].sort((a,b)=>(mods.get(b).length-mods.get(a).length));
|
|
1041
|
-
const nMods=keys.length||1;
|
|
1042
|
-
const palette=['#4ecca3','#e94560','#f39c12','#3498db','#9b59b6','#1abc9c','#e67e22','#2ecc71','#e84393','#00cec9'];
|
|
1043
|
-
const RING_CAP=24; // max nodes per ring layer
|
|
1044
|
-
|
|
1045
|
-
// Calculate each module's outer radius (for spacing)
|
|
1046
|
-
const modRadii=[];
|
|
1047
|
-
for(let i=0;i<keys.length;i++){
|
|
1048
|
-
const cnt=mods.get(keys[i]).length;
|
|
1049
|
-
const rings=Math.ceil(cnt/RING_CAP);
|
|
1050
|
-
const outerR=rings===1?Math.max(40,cnt*6):rings*32+20;
|
|
1051
|
-
modRadii.push(outerR);
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
// Adaptive base ring: sum of all module diameters / (2*PI) gives minimum perimeter
|
|
1055
|
-
const totalDiam=modRadii.reduce((s,r)=>s+r*2+60,0); // 60px gap between modules
|
|
1056
|
-
const baseRadius=Math.max(400, totalDiam/(Math.PI*2));
|
|
1057
|
-
|
|
1058
|
-
// Place modules with angular spacing proportional to their size
|
|
1059
|
-
const totalWeight=modRadii.reduce((s,r)=>s+r+30,0);
|
|
1060
|
-
let angle=-Math.PI/2;
|
|
1061
|
-
for(let i=0;i<keys.length;i++){
|
|
1062
|
-
const mn=mods.get(keys[i]); if(!mn)continue;
|
|
1063
|
-
const cnt=mn.length;
|
|
1064
|
-
const col=palette[i%palette.length];
|
|
1065
|
-
const rings=Math.ceil(cnt/RING_CAP);
|
|
1066
|
-
const outerR=modRadii[i];
|
|
1067
|
-
|
|
1068
|
-
// Angular span for this module proportional to its size
|
|
1069
|
-
const span=((outerR+30)/totalWeight)*Math.PI*2;
|
|
1070
|
-
const modAngle=angle+span/2;
|
|
1071
|
-
angle+=span;
|
|
468
|
+
ui.setLoading(70,'Creating data visualization…');
|
|
469
|
+
graphViz=new GraphViz(getScene());
|
|
470
|
+
hologram=new HologramDisplay(getScene());
|
|
1072
471
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
const modNodeId='module:'+keys[i];
|
|
1077
|
-
S.nodePos.set(modNodeId,{x:mcx,y:mcy});
|
|
1078
|
-
S.modMeta.set(keys[i],{cx:mcx,cy:mcy,radius:outerR+20,color:col,count:cnt});
|
|
1079
|
-
|
|
1080
|
-
// Spread nodes across concentric rings
|
|
1081
|
-
let placed=0;
|
|
1082
|
-
for(let ring=0;ring<rings;ring++){
|
|
1083
|
-
const nodesInRing=Math.min(RING_CAP,cnt-placed);
|
|
1084
|
-
const r=rings===1?Math.max(40,nodesInRing*6):(ring+1)*32;
|
|
1085
|
-
for(let j=0;j<nodesInRing;j++){
|
|
1086
|
-
const na=(j/nodesInRing)*Math.PI*2;
|
|
1087
|
-
S.nodePos.set(mn[placed].id,{x:mcx+Math.cos(na)*r,y:mcy+Math.sin(na)*r});
|
|
1088
|
-
placed++;
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
// Auto-center
|
|
1094
|
-
if(nodes.length>0&&!S._userPanned){
|
|
1095
|
-
let minX=Infinity,maxX=-Infinity,minY=Infinity,maxY=-Infinity;
|
|
1096
|
-
for(const[,p] of S.nodePos){minX=Math.min(minX,p.x);maxX=Math.max(maxX,p.x);minY=Math.min(minY,p.y);maxY=Math.max(maxY,p.y);}
|
|
1097
|
-
const gw=maxX-minX+300,gh=maxY-minY+300,gcx=minX+(maxX-minX)/2,gcy=minY+(maxY-minY)/2;
|
|
1098
|
-
S.zoom=Math.min(1, Math.min(w/gw, h/gh));
|
|
1099
|
-
S.pan.x=w/2-gcx*S.zoom;S.pan.y=h/2-gcy*S.zoom;
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
function renderCanvas(){
|
|
1104
|
-
const canvas=document.getElementById('graph-canvas'),ctx=canvas.getContext('2d');
|
|
1105
|
-
const dpr=window.devicePixelRatio||1;
|
|
1106
|
-
canvas.width=canvas.clientWidth*dpr; canvas.height=canvas.clientHeight*dpr;
|
|
1107
|
-
ctx.scale(dpr,dpr);
|
|
1108
|
-
const w=canvas.clientWidth,h=canvas.clientHeight;
|
|
1109
|
-
const isDark=S.theme==='dark';
|
|
1110
|
-
ctx.clearRect(0,0,w,h);
|
|
1111
|
-
// Background fill
|
|
1112
|
-
ctx.fillStyle=isDark?'#0a0f1a':'#f8fafc';
|
|
1113
|
-
ctx.fillRect(0,0,w,h);
|
|
1114
|
-
ctx.save(); ctx.translate(S.pan.x,S.pan.y); ctx.scale(S.zoom,S.zoom);
|
|
1115
|
-
// Subtle grid
|
|
1116
|
-
ctx.strokeStyle=isDark?'rgba(148,163,184,.06)':'rgba(100,116,139,.08)';ctx.lineWidth=.5;
|
|
1117
|
-
const gridStep=40;
|
|
1118
|
-
for(let x=-2000;x<4000;x+=gridStep){ctx.beginPath();ctx.moveTo(x,-2000);ctx.lineTo(x,4000);ctx.stroke();}
|
|
1119
|
-
for(let y=-2000;y<4000;y+=gridStep){ctx.beginPath();ctx.moveTo(-2000,y);ctx.lineTo(4000,y);ctx.stroke();}
|
|
1120
|
-
|
|
1121
|
-
const edges=S.graph.edges||[],nodes=S.graph.nodes||[];
|
|
1122
|
-
|
|
1123
|
-
// Draw module cluster backgrounds
|
|
1124
|
-
if(S.modMeta){
|
|
1125
|
-
for(const[name,m] of S.modMeta){
|
|
1126
|
-
ctx.beginPath(); ctx.arc(m.cx,m.cy,m.radius,0,Math.PI*2);
|
|
1127
|
-
ctx.fillStyle=m.color+(isDark?'0a':'08'); ctx.fill();
|
|
1128
|
-
ctx.strokeStyle=m.color+(isDark?'25':'20'); ctx.lineWidth=1.5; ctx.setLineDash([6,4]); ctx.stroke(); ctx.setLineDash([]);
|
|
1129
|
-
ctx.font='600 12px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
|
|
1130
|
-
ctx.fillStyle=m.color+(isDark?'cc':'aa'); ctx.textAlign='center'; ctx.textBaseline='bottom';
|
|
1131
|
-
ctx.fillText(name+' ('+m.count+')',m.cx,m.cy-m.radius-8);
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
472
|
+
ui.setLoading(80,'Connecting…');
|
|
473
|
+
ui.init({doScan,doPipeline,doReset,doRunTests,doReports,toggleTheme,setView,openFilePreview,openReportPreview});
|
|
474
|
+
await fetchProject();
|
|
1134
475
|
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
if(e.relation==='contains') continue;
|
|
1138
|
-
const s=S.nodePos.get(e.source),t=S.nodePos.get(e.target);if(!s||!t)continue;
|
|
1139
|
-
const dx=t.x-s.x,dy=t.y-s.y;
|
|
1140
|
-
const mx=(s.x+t.x)/2+dy*0.15, my=(s.y+t.y)/2-dx*0.15;
|
|
1141
|
-
const grad=ctx.createLinearGradient(s.x,s.y,t.x,t.y);
|
|
1142
|
-
grad.addColorStop(0,isDark?'rgba(248,113,113,.35)':'rgba(220,38,38,.25)');
|
|
1143
|
-
grad.addColorStop(1,isDark?'rgba(52,211,153,.35)':'rgba(5,150,105,.25)');
|
|
1144
|
-
ctx.strokeStyle=grad;ctx.lineWidth=1.5;
|
|
1145
|
-
ctx.beginPath();ctx.moveTo(s.x,s.y);ctx.quadraticCurveTo(mx,my,t.x,t.y);ctx.stroke();
|
|
1146
|
-
const t2=0.95,at2x=(1-t2)*(1-t2)*s.x+2*(1-t2)*t2*mx+t2*t2*t.x,at2y=(1-t2)*(1-t2)*s.y+2*(1-t2)*t2*my+t2*t2*t.y;
|
|
1147
|
-
const a=Math.atan2(t.y-at2y,t.x-at2x),al=7;
|
|
1148
|
-
ctx.fillStyle=isDark?'rgba(52,211,153,.5)':'rgba(5,150,105,.4)';ctx.beginPath();ctx.moveTo(t.x,t.y);
|
|
1149
|
-
ctx.lineTo(t.x-al*Math.cos(a-.4),t.y-al*Math.sin(a-.4));
|
|
1150
|
-
ctx.lineTo(t.x-al*Math.cos(a+.4),t.y-al*Math.sin(a+.4));ctx.closePath();ctx.fill();
|
|
1151
|
-
}
|
|
476
|
+
ui.setLoading(90,'Starting WebSocket…');
|
|
477
|
+
connectWS();
|
|
1152
478
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
const sc={idle:isDark?'#475569':'#94a3b8',testing:'#fbbf24',passed:'#34d399',failed:'#f87171'};
|
|
1156
|
-
for(const n of nodes){
|
|
1157
|
-
if(n.type==='module') continue;
|
|
1158
|
-
const p=S.nodePos.get(n.id);if(!p)continue;
|
|
1159
|
-
const sz=9,c=tc[n.type]||'#888',ol=sc[n.status]||sc.idle,hov=S.hoveredNode===n.id;
|
|
1160
|
-
if(n.status==='testing'){ctx.shadowColor=sc.testing;ctx.shadowBlur=12;}
|
|
1161
|
-
else if(n.status==='passed'){ctx.shadowColor=sc.passed;ctx.shadowBlur=8;}
|
|
1162
|
-
else if(n.status==='failed'){ctx.shadowColor=sc.failed;ctx.shadowBlur=10;}
|
|
1163
|
-
ctx.beginPath();ctx.arc(p.x,p.y,sz,0,Math.PI*2);
|
|
1164
|
-
ctx.fillStyle=c;ctx.fill();
|
|
1165
|
-
ctx.shadowBlur=0;
|
|
1166
|
-
ctx.strokeStyle=hov?(isDark?'#fff':'#0f172a'):ol;ctx.lineWidth=hov?2.5:1.2;ctx.stroke();
|
|
1167
|
-
// Label
|
|
1168
|
-
if(S.zoom>0.4||hov){
|
|
1169
|
-
ctx.font='500 9px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
|
|
1170
|
-
ctx.fillStyle=hov?(isDark?'#fff':'#0f172a'):(isDark?'#94a3b8':'#64748b');
|
|
1171
|
-
ctx.textAlign='center';ctx.textBaseline='top';
|
|
1172
|
-
ctx.fillText((n.label||n.id.split(':').pop()).substring(0,18),p.x,p.y+sz+4);
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
ctx.restore();
|
|
479
|
+
ui.setLoading(100,'Ready!');
|
|
480
|
+
setTimeout(()=>document.getElementById('loading-overlay').classList.add('hidden'),400);
|
|
1176
481
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
const legBg=isDark?'rgba(10,15,26,.8)':'rgba(255,255,255,.85)';
|
|
1180
|
-
ctx.fillStyle=legBg; ctx.beginPath(); ctx.roundRect(6,h-56,120,48,6); ctx.fill();
|
|
1181
|
-
ctx.strokeStyle=isDark?'rgba(148,163,184,.15)':'rgba(100,116,139,.2)';ctx.lineWidth=1;ctx.stroke();
|
|
1182
|
-
const leg=[['● Model','#34d399'],['● Controller','#f87171'],['─ uses',isDark?'rgba(200,150,160,.7)':'rgba(150,80,100,.6)']];
|
|
1183
|
-
for(let i=0;i<leg.length;i++){ctx.fillStyle=leg[i][1];ctx.fillText(leg[i][0],14,h-42+i*14);}
|
|
482
|
+
requestAnimationFrame(renderLoop);
|
|
483
|
+
ui.addLog('OpenCroc Studio 3D ready — press ? for shortcuts','info',true);
|
|
1184
484
|
}
|
|
1185
485
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
const sc={idle:'#888',testing:'#f39c12',passed:'#4ecca3',failed:'#e94560'};
|
|
1198
|
-
tt.innerHTML='<b>'+esc(hit.label||hit.id)+'</b><br>Type: '+hit.type+'<br>Status: <span style="color:'+(sc[hit.status]||'#888')+'">'+hit.status+'</span>'+(hit.module?'<br>Module: '+esc(hit.module):'');
|
|
1199
|
-
tt.style.left=(e.clientX+12)+'px';tt.style.top=(e.clientY+12)+'px';tt.classList.add('visible');c.style.cursor='pointer';
|
|
1200
|
-
}else{if(S.hoveredNode)S.hoveredNode=null;tt.classList.remove('visible');if(!S.dragging)c.style.cursor='grab';}
|
|
1201
|
-
renderCanvas();
|
|
1202
|
-
});
|
|
1203
|
-
c.addEventListener('mouseup',()=>{S.dragging=false;c.style.cursor='grab';});
|
|
1204
|
-
c.addEventListener('mouseleave',()=>{S.dragging=false;document.getElementById('tooltip').classList.remove('visible');});
|
|
1205
|
-
c.addEventListener('wheel',e=>{e.preventDefault();S.zoom=Math.max(.2,Math.min(3,S.zoom*(e.deltaY>0?.92:1.08)));renderCanvas();},{passive:false});
|
|
486
|
+
/* ─── Render Loop ──────────────────────────────────────────────────────────── */
|
|
487
|
+
function renderLoop(){
|
|
488
|
+
requestAnimationFrame(renderLoop);
|
|
489
|
+
const dt=getClock().getDelta();
|
|
490
|
+
if(dt>0.1) return;
|
|
491
|
+
if(camCtrl) camCtrl.update(dt);
|
|
492
|
+
if(particleMgr) particleMgr.update(dt);
|
|
493
|
+
if(agentMgr) agentMgr.update(dt);
|
|
494
|
+
if(hologram) hologram.update(dt,state.get('graph'));
|
|
495
|
+
const composer=getComposer();
|
|
496
|
+
if(composer) composer.render(dt); else getRenderer().render(getScene(),getCamera());
|
|
1206
497
|
}
|
|
1207
498
|
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
}
|
|
1214
|
-
renderModList();renderOffice();renderAgentSB();renderPixelOffice();renderCanvas();
|
|
1215
|
-
}
|
|
1216
|
-
function renderModList(){
|
|
1217
|
-
const el=document.getElementById('mod-list'),mods=S.graph.nodes.filter(n=>n.type==='module');
|
|
1218
|
-
if(!mods.length){el.innerHTML='<div style="padding:12px;color:var(--text-subtle);font-size:11px">No modules found</div>';return;}
|
|
1219
|
-
const sorted=mods.sort((a,b)=>{
|
|
1220
|
-
const ca=S.modMeta&&S.modMeta.get(a.label)?S.modMeta.get(a.label).count:0;
|
|
1221
|
-
const cb=S.modMeta&&S.modMeta.get(b.label)?S.modMeta.get(b.label).count:0;
|
|
1222
|
-
return cb-ca;
|
|
1223
|
-
});
|
|
1224
|
-
el.innerHTML=sorted.map(m=>{
|
|
1225
|
-
const meta=S.modMeta&&S.modMeta.get(m.label);
|
|
1226
|
-
const cnt=meta?meta.count:'';
|
|
1227
|
-
const col=meta?meta.color:'var(--text-subtle)';
|
|
1228
|
-
return '<div class="mod-item" data-mod="'+esc(m.label)+'" style="border-left:3px solid '+col+'">'
|
|
1229
|
-
+'<div class="dot '+m.status+'"></div>'
|
|
1230
|
-
+esc(m.label)+'<span class="mod-count">'+cnt+'</span></div>';
|
|
1231
|
-
}).join('');
|
|
1232
|
-
// Click to navigate
|
|
1233
|
-
el.querySelectorAll('.mod-item').forEach(item=>{
|
|
1234
|
-
item.addEventListener('click',()=>{
|
|
1235
|
-
const name=item.getAttribute('data-mod');
|
|
1236
|
-
const meta=S.modMeta&&S.modMeta.get(name);
|
|
1237
|
-
if(meta){
|
|
1238
|
-
const c=document.getElementById('graph-canvas');
|
|
1239
|
-
S.zoom=0.8;S._userPanned=true;
|
|
1240
|
-
S.pan.x=c.clientWidth/2-meta.cx*S.zoom;
|
|
1241
|
-
S.pan.y=c.clientHeight/2-meta.cy*S.zoom;
|
|
1242
|
-
renderCanvas();
|
|
1243
|
-
}
|
|
1244
|
-
});
|
|
1245
|
-
});
|
|
1246
|
-
}
|
|
1247
|
-
function renderAgentSB(){
|
|
1248
|
-
document.getElementById('agent-sidebar').innerHTML=S.agents.map(a=>
|
|
1249
|
-
'<div class="mod-item"><div class="dot '+a.status+'"></div>'+esc(a.name)+'<span class="mod-count">'+a.status+'</span></div>'
|
|
1250
|
-
).join('');
|
|
1251
|
-
}
|
|
1252
|
-
function renderOffice(){
|
|
1253
|
-
document.getElementById('croc-office').innerHTML=S.agents.map(a=>{
|
|
1254
|
-
const prog=typeof a.progress==='number'?a.progress:0;
|
|
1255
|
-
const roleIcon=ROLE_ICONS[a.role]||ICONS.croc;
|
|
1256
|
-
return '<div class="desk '+a.status+'"><div class="badge dot '+a.status+'"></div>'+
|
|
1257
|
-
'<div class="croc-sprite">'+ICONS.croc+'</div><div class="croc-name">'+esc(a.name)+'</div>'+
|
|
1258
|
-
'<div class="croc-role">'+esc(a.role)+'</div>'+
|
|
1259
|
-
'<div class="croc-task">'+(a.currentTask?esc(a.currentTask):'')+'</div>'+
|
|
1260
|
-
'<div class="progress-bar"><div class="fill" style="width:'+prog+'%"></div></div>'+
|
|
1261
|
-
'<div class="desk-icon">'+roleIcon+'</div></div>';
|
|
1262
|
-
}).join('');
|
|
499
|
+
/* ─── API ──────────────────────────────────────────────────────────────────── */
|
|
500
|
+
async function fetchProject(){
|
|
501
|
+
try{const r=await fetch('/api/project');const d=await r.json();
|
|
502
|
+
state.set({project:d,graph:d.graph||state.get('graph'),agents:d.agents||state.get('agents')});
|
|
503
|
+
updateAll();
|
|
504
|
+
}catch(e){ui.addLog('Failed to fetch project: '+e.message,'error')}
|
|
1263
505
|
}
|
|
1264
|
-
function
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
{x:.18,y:.64},{x:.3,y:.66},{x:.42,y:.62},{x:.56,y:.6},{x:.72,y:.63},{x:.82,y:.52},
|
|
1270
|
-
{x:.22,y:.45},{x:.48,y:.42},{x:.66,y:.4},{x:.78,y:.72}
|
|
1271
|
-
];
|
|
1272
|
-
const roles=['char_0','char_1','char_2'];
|
|
1273
|
-
const working=S.agents.filter(a=>a.status==='working'||a.status==='testing').length;
|
|
1274
|
-
const errors=S.agents.filter(a=>a.status==='error'||a.status==='failed').length;
|
|
1275
|
-
const done=S.agents.filter(a=>a.status==='done'||a.status==='passed').length;
|
|
1276
|
-
document.getElementById('kpi-working').textContent=String(working);
|
|
1277
|
-
document.getElementById('kpi-errors').textContent=String(errors);
|
|
1278
|
-
document.getElementById('kpi-done').textContent=String(done);
|
|
1279
|
-
if(!S.agents.length){el.innerHTML='';return;}
|
|
1280
|
-
|
|
1281
|
-
// Stop all existing sprite timers
|
|
1282
|
-
for(const [id,timer] of S.spriteAnimTimers){ clearInterval(timer); }
|
|
1283
|
-
S.spriteAnimTimers.clear();
|
|
506
|
+
async function doScan(){if(state.get('running'))return;state.set({running:true});updateBtns();ui.addLog('🔍 Starting codebase scan…','info',true);try{await fetch('/api/scan',{method:'POST'})}catch(e){ui.addLog('Scan error: '+e.message,'error');state.set({running:false});updateBtns()}}
|
|
507
|
+
async function doPipeline(){if(state.get('running'))return;state.set({running:true});updateBtns();ui.addLog('⚡ Pipeline started…','info',true);try{await fetch('/api/pipeline',{method:'POST'})}catch(e){ui.addLog('Pipeline error: '+e.message,'error');state.set({running:false});updateBtns()}}
|
|
508
|
+
async function doReset(){try{await fetch('/api/reset',{method:'POST'})}catch(e){ui.addLog('Reset error: '+e.message,'error')}state.set({running:false});updateBtns();ui.addLog('♻️ Agents reset','info',true)}
|
|
509
|
+
async function doRunTests(){if(state.get('running'))return;state.set({running:true});updateBtns();ui.addLog('🧪 Running tests (mode: '+state.get('runMode')+')…','info',true);try{const r=await fetch('/api/run-tests',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({mode:state.get('runMode')})});const d=await r.json();if(d.error){ui.addLog('Test error: '+d.error,'error');state.set({running:false});updateBtns()}}catch(e){ui.addLog('Test error: '+e.message,'error');state.set({running:false});updateBtns()}}
|
|
510
|
+
async function doReports(){if(state.get('running'))return;state.set({running:true});updateBtns();ui.addLog('📊 Generating reports…','info',true);try{await fetch('/api/reports/generate',{method:'POST'})}catch(e){ui.addLog('Report error: '+e.message,'error');state.set({running:false});updateBtns()}}
|
|
1284
511
|
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
['done','passed'].includes(a.status)?'done':
|
|
1293
|
-
['error','failed'].includes(a.status)?'error':
|
|
1294
|
-
a.status==='thinking'?'thinking':'');
|
|
1295
|
-
const canvasId='sprite-'+i;
|
|
1296
|
-
// Use canvas element for spritesheet-based rendering
|
|
1297
|
-
return '<canvas id="'+canvasId+'" class="pixel-sprite-canvas '+a.status+'" width="48" height="48" style="left:'+x+'px;top:'+y+'px"></canvas>'
|
|
1298
|
-
+'<div class="pixel-label" style="left:'+(x+24)+'px;top:'+labelY+'px">'+esc(a.name)
|
|
1299
|
-
+(statusClass?'<span class="status-tag '+statusClass+'">'+esc(a.status)+'</span>':'')+'</div>';
|
|
1300
|
-
}).join('');
|
|
1301
|
-
|
|
1302
|
-
// Start sprite animations for each agent
|
|
1303
|
-
S.agents.forEach((a,i)=>{
|
|
1304
|
-
const roleSprite=roles[i%roles.length];
|
|
1305
|
-
const canvasId='sprite-'+i;
|
|
1306
|
-
// Delay to ensure DOM is ready
|
|
1307
|
-
requestAnimationFrame(()=>startSpriteAnim(canvasId, roleSprite, a.status));
|
|
1308
|
-
});
|
|
1309
|
-
|
|
1310
|
-
// Trigger bubbles for active agents (Star-Office style)
|
|
1311
|
-
scheduleBubbles();
|
|
512
|
+
/* ─── WebSocket ────────────────────────────────────────────────────────────── */
|
|
513
|
+
function connectWS(){
|
|
514
|
+
const prot=location.protocol==='https:'?'wss:':'ws:';
|
|
515
|
+
const ws=new WebSocket(prot+'//'+location.host);
|
|
516
|
+
ws.onopen=()=>{document.getElementById('conn-dot').classList.add('connected');state.set({ws})};
|
|
517
|
+
ws.onclose=()=>{document.getElementById('conn-dot').classList.remove('connected');setTimeout(connectWS,3000)};
|
|
518
|
+
ws.onmessage=ev=>{try{handleWS(JSON.parse(ev.data))}catch(e){}};
|
|
1312
519
|
}
|
|
1313
|
-
function
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
const y=Math.max(20,Math.round(stageH*p.y));
|
|
1329
|
-
const bubble=document.createElement('div');
|
|
1330
|
-
bubble.className='pixel-bubble';
|
|
1331
|
-
bubble.textContent=texts[Math.floor(Math.random()*texts.length)];
|
|
1332
|
-
bubble.style.left=(x+50)+'px'; bubble.style.top=(y-20)+'px';
|
|
1333
|
-
el.appendChild(bubble);
|
|
1334
|
-
setTimeout(()=>bubble.remove(),3000);
|
|
1335
|
-
};
|
|
1336
|
-
const interval=setInterval(show, 6000+Math.random()*8000);
|
|
1337
|
-
S.bubbleTimers.set(a.name, interval);
|
|
1338
|
-
setTimeout(show, 1000+Math.random()*3000);
|
|
1339
|
-
});
|
|
1340
|
-
// Clear timers for agents that no longer exist
|
|
1341
|
-
for(const [name,timer] of S.bubbleTimers){
|
|
1342
|
-
if(!S.agents.find(a=>a.name===name)){clearInterval(timer);S.bubbleTimers.delete(name);}
|
|
520
|
+
function handleWS(msg){
|
|
521
|
+
switch(msg.type){
|
|
522
|
+
case'agent:update':state.set({agents:msg.data});agentMgr.sync(msg.data);ui.updateDeskCards(msg.data);ui.updateSidebar(null,msg.data);break;
|
|
523
|
+
case'graph:update':state.set({graph:msg.data});graphViz.update(msg.data);ui.updateSidebar(msg.data,null);ui.updateStats(msg.data);break;
|
|
524
|
+
case'log':ui.addLog(msg.data.message,msg.data.level);break;
|
|
525
|
+
case'files:generated':state.set({generatedFiles:msg.data});ui.updateFileList(msg.data);break;
|
|
526
|
+
case'pipeline:complete':state.set({running:false});updateBtns();
|
|
527
|
+
if(msg.data.error)ui.addLog('Pipeline failed: '+msg.data.error,'error');
|
|
528
|
+
else{ui.addLog('✅ Pipeline complete','success',true);ui.showToast('Pipeline completed','success');particleMgr.triggerCelebration()}
|
|
529
|
+
fetchProject();break;
|
|
530
|
+
case'test:complete':state.set({running:false,testMetrics:msg.data.metrics,testQuality:msg.data.quality});updateBtns();
|
|
531
|
+
ui.updateResults(msg.data);ui.addLog(`Tests: ${msg.data.metrics?.passed||0}✓ ${msg.data.metrics?.failed||0}✗`,'info',true);
|
|
532
|
+
ui.showToast(`Tests: ${msg.data.metrics?.passed||0}✓ ${msg.data.metrics?.failed||0}✗`,msg.data.metrics?.failed?'warning':'success');break;
|
|
533
|
+
case'reports:generated':state.set({running:false,reports:msg.data});updateBtns();
|
|
534
|
+
ui.updateReports(msg.data);ui.addLog('📊 '+msg.data.length+' reports generated','success',true);break;
|
|
1343
535
|
}
|
|
1344
536
|
}
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
document.getElementById('tooltip').classList.remove('visible');
|
|
1353
|
-
if(dashboard){
|
|
1354
|
-
setTimeout(()=>{layoutGraph();renderCanvas();},0);
|
|
1355
|
-
}else{
|
|
1356
|
-
renderPixelOffice();
|
|
1357
|
-
}
|
|
537
|
+
|
|
538
|
+
/* ─── Helpers ──────────────────────────────────────────────────────────────── */
|
|
539
|
+
function updateAll(){
|
|
540
|
+
const g=state.get('graph'),a=state.get('agents');
|
|
541
|
+
ui.updateSidebar(g,a);ui.updateStats(g);ui.updateDeskCards(a);
|
|
542
|
+
if(a) agentMgr.sync(a);
|
|
543
|
+
if(g) graphViz.update(g);
|
|
1358
544
|
}
|
|
1359
|
-
function
|
|
545
|
+
function updateBtns(){const r=state.get('running');['btn-scan','btn-pipeline','btn-reset','btn-run-tests','btn-reports'].forEach(id=>document.getElementById(id).disabled=r)}
|
|
546
|
+
async function openFilePreview(idx){try{const r=await fetch('/api/files/'+idx);const d=await r.json();document.getElementById('fp-title').textContent=d.filePath||'File';document.getElementById('fp-code').textContent=d.content||'';document.getElementById('file-preview').classList.add('visible')}catch(e){ui.showToast('Failed to load file','error')}}
|
|
547
|
+
async function openReportPreview(fmt){try{const r=await fetch('/api/reports/'+fmt);const t=await r.text();if(fmt==='html'){const w=window.open('','_blank');if(w){w.document.write(t);w.document.close()}return}document.getElementById('fp-title').textContent='Report: '+fmt;document.getElementById('fp-code').textContent=t;document.getElementById('file-preview').classList.add('visible')}catch(e){ui.showToast('Failed to load report','error')}}
|
|
548
|
+
function toggleTheme(){const cur=state.get('theme');const nxt=cur==='dark'?'light':'dark';state.set({theme:nxt});document.documentElement.setAttribute('data-theme',nxt);localStorage.setItem('opencroc-theme',nxt);document.getElementById('theme-icon-dark').style.display=nxt==='dark'?'':'none';document.getElementById('theme-icon-light').style.display=nxt==='light'?'':'none';updateOfficeLighting(nxt)}
|
|
549
|
+
function setView(v){state.set({currentView:v});document.getElementById('view-3d').classList.toggle('active',v==='3d');document.getElementById('view-graph').classList.toggle('active',v==='graph');if(v==='3d')camCtrl.flyTo('office');else camCtrl.flyTo('graph')}
|
|
1360
550
|
|
|
551
|
+
/* ─── Events ───────────────────────────────────────────────────────────────── */
|
|
1361
552
|
document.getElementById('btn-scan').addEventListener('click',doScan);
|
|
1362
553
|
document.getElementById('btn-pipeline').addEventListener('click',doPipeline);
|
|
1363
554
|
document.getElementById('btn-reset').addEventListener('click',doReset);
|
|
1364
555
|
document.getElementById('btn-run-tests').addEventListener('click',doRunTests);
|
|
1365
556
|
document.getElementById('btn-reports').addEventListener('click',doReports);
|
|
1366
|
-
document.getElementById('run-mode').addEventListener('change',e=>{
|
|
1367
|
-
document.getElementById('view-
|
|
1368
|
-
document.getElementById('view-
|
|
1369
|
-
|
|
1370
|
-
// Tab switching
|
|
1371
|
-
document.querySelectorAll('.panel-tabs .tab').forEach(tab=>{
|
|
1372
|
-
tab.addEventListener('click',()=>{
|
|
1373
|
-
document.querySelectorAll('.panel-tabs .tab').forEach(t=>t.classList.remove('active'));
|
|
1374
|
-
tab.classList.add('active');
|
|
1375
|
-
const t=tab.getAttribute('data-tab');
|
|
1376
|
-
document.getElementById('log-list').style.display=t==='log'?'':'none';
|
|
1377
|
-
document.getElementById('file-list').style.display=t==='files'?'':'none';
|
|
1378
|
-
document.getElementById('results-panel').style.display=t==='results'?'':'none';
|
|
1379
|
-
document.getElementById('reports-panel').style.display=t==='reports'?'':'none';
|
|
1380
|
-
});
|
|
1381
|
-
});
|
|
1382
|
-
|
|
1383
|
-
// File list rendering
|
|
1384
|
-
function renderFileList(){
|
|
1385
|
-
const el=document.getElementById('file-list');
|
|
1386
|
-
if(!S.generatedFiles.length){el.innerHTML='<div style="padding:12px;color:var(--text-subtle);font-size:11px">No test files generated yet. Run Pipeline first.</div>';return;}
|
|
1387
|
-
el.innerHTML=S.generatedFiles.map((f,i)=>
|
|
1388
|
-
'<div class="file-item" data-idx="'+i+'"><div class="fname">'+esc(f.filePath.split('/').pop()||f.filePath)+'</div>'+
|
|
1389
|
-
'<div class="fmeta">'+esc(f.module)+' / '+esc(f.chain)+' — '+f.lines+' lines</div></div>'
|
|
1390
|
-
).join('');
|
|
1391
|
-
el.querySelectorAll('.file-item').forEach(item=>{
|
|
1392
|
-
item.addEventListener('click',async()=>{
|
|
1393
|
-
const idx=item.getAttribute('data-idx');
|
|
1394
|
-
try{
|
|
1395
|
-
const r=await fetch('/api/files/'+idx); const file=await r.json();
|
|
1396
|
-
document.getElementById('fp-title').textContent=file.filePath;
|
|
1397
|
-
document.getElementById('fp-code').textContent=file.content;
|
|
1398
|
-
document.getElementById('file-preview').classList.add('visible');
|
|
1399
|
-
}catch(e){addLog('Failed to load file: '+e.message,'error');}
|
|
1400
|
-
});
|
|
1401
|
-
});
|
|
1402
|
-
}
|
|
1403
|
-
|
|
1404
|
-
// File preview close
|
|
1405
|
-
document.getElementById('fp-close').addEventListener('click',()=>{
|
|
1406
|
-
document.getElementById('file-preview').classList.remove('visible');
|
|
1407
|
-
});
|
|
1408
|
-
document.addEventListener('keydown',e=>{
|
|
1409
|
-
if(e.key==='Escape') document.getElementById('file-preview').classList.remove('visible');
|
|
1410
|
-
});
|
|
1411
|
-
|
|
1412
|
-
// Test Results rendering
|
|
1413
|
-
function renderResults(){
|
|
1414
|
-
const el=document.getElementById('results-panel');
|
|
1415
|
-
if(!S.testMetrics&&!S.testQuality){el.innerHTML='<div style="padding:12px;color:var(--text-subtle);font-size:11px">No test results yet. Run Tests first.</div>';return;}
|
|
1416
|
-
const q=S.testQuality;
|
|
1417
|
-
if(!S.testMetrics&&q){
|
|
1418
|
-
el.innerHTML='<div style="padding:10px">'
|
|
1419
|
-
+'<div style="font-size:13px;font-weight:bold;margin-bottom:8px">🧪 Test Execution Results</div>'
|
|
1420
|
-
+'<div style="padding:10px;border-radius:var(--radius-sm);background:var(--red-bg);color:var(--red);font-size:11px;margin-bottom:10px">Setup failed before test execution.</div>'
|
|
1421
|
-
+'<div style="font-size:11px;margin-bottom:6px">Gate Level: <span style="color:'+(q.level==='fail'?'var(--red)':q.level==='warn'?'var(--orange)':'var(--accent)')+';font-weight:bold">'+q.level.toUpperCase()+'</span></div>'
|
|
1422
|
-
+'<div style="font-size:10px;color:var(--text-dim)">Auth: '+q.authStatus+' | Backend: '+q.backendStatus+'</div>'
|
|
1423
|
-
+'<div style="font-size:10px;color:var(--text-dim);margin-top:4px">Reasons: '+(q.reasons&&q.reasons.length?q.reasons.join(', '):'-')+'</div>'
|
|
1424
|
-
+'</div>';
|
|
1425
|
-
return;
|
|
1426
|
-
}
|
|
1427
|
-
const m=S.testMetrics, total=m.passed+m.failed+m.skipped+m.timedOut;
|
|
1428
|
-
const passRate=total>0?Math.round(m.passed/total*100):0;
|
|
1429
|
-
const barColor=m.failed>0?'var(--red)':'var(--accent)';
|
|
1430
|
-
let qualityHtml='';
|
|
1431
|
-
if(q){
|
|
1432
|
-
const gateColor=q.level==='fail'?'var(--red)':q.level==='warn'?'var(--orange)':'var(--accent)';
|
|
1433
|
-
qualityHtml='<div style="margin-top:12px;padding-top:10px;border-top:1px solid var(--border)">'
|
|
1434
|
-
+'<div style="font-size:12px;font-weight:bold;margin-bottom:8px">🧭 Execution Quality</div>'
|
|
1435
|
-
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;font-size:11px">'
|
|
1436
|
-
+'<div style="background:var(--bg-card);padding:8px;border-radius:var(--radius-sm);border:1px solid var(--border)">Gate: <span style="color:'+gateColor+';font-weight:600">'+q.level.toUpperCase()+'</span></div>'
|
|
1437
|
-
+'<div style="background:var(--bg-card);padding:8px;border-radius:var(--radius-sm);border:1px solid var(--border)">Setup Fail: '+q.setupFail+'</div>'
|
|
1438
|
-
+'<div style="background:var(--bg-card);padding:8px;border-radius:var(--radius-sm);border:1px solid var(--border)">Skip Ratio: '+Math.round((q.skipRatio||0)*100)+'%</div>'
|
|
1439
|
-
+'<div style="background:var(--bg-card);padding:8px;border-radius:var(--radius-sm);border:1px solid var(--border)">Auth Fail Ratio: '+Math.round((q.authFailRatio||0)*100)+'%</div>'
|
|
1440
|
-
+'<div style="background:var(--bg-card);padding:8px;border-radius:var(--radius-sm);border:1px solid var(--border)">Effective Rate: '+Math.round((q.effectiveExecutionRate||0)*100)+'%</div>'
|
|
1441
|
-
+'<div style="background:var(--bg-card);padding:8px;border-radius:var(--radius-sm);border:1px solid var(--border)">Auth/Backend: '+q.authStatus+' / '+q.backendStatus+'</div>'
|
|
1442
|
-
+'</div>'
|
|
1443
|
-
+'<div style="font-size:10px;color:var(--text-dim);margin-top:6px">Reasons: '+(q.reasons&&q.reasons.length?q.reasons.join(', '):'-')+'</div>'
|
|
1444
|
-
+'</div>';
|
|
1445
|
-
}
|
|
1446
|
-
el.innerHTML='<div style="padding:10px">'
|
|
1447
|
-
+'<div style="font-size:13px;font-weight:bold;margin-bottom:8px">🧪 Test Execution Results</div>'
|
|
1448
|
-
+'<div style="display:flex;gap:12px;margin-bottom:10px">'
|
|
1449
|
-
+'<div style="flex:1;text-align:center;background:var(--accent-bg);padding:10px;border-radius:var(--radius-sm);border:1px solid var(--border)"><div style="font-size:22px;font-weight:700;color:var(--accent)">'+m.passed+'</div><div style="font-size:9px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.5px;margin-top:2px">PASSED</div></div>'
|
|
1450
|
-
+'<div style="flex:1;text-align:center;background:var(--red-bg);padding:10px;border-radius:var(--radius-sm);border:1px solid var(--border)"><div style="font-size:22px;font-weight:700;color:var(--red)">'+m.failed+'</div><div style="font-size:9px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.5px;margin-top:2px">FAILED</div></div>'
|
|
1451
|
-
+'<div style="flex:1;text-align:center;background:var(--orange-bg);padding:10px;border-radius:var(--radius-sm);border:1px solid var(--border)"><div style="font-size:22px;font-weight:700;color:var(--orange)">'+m.skipped+'</div><div style="font-size:9px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.5px;margin-top:2px">SKIPPED</div></div>'
|
|
1452
|
-
+'<div style="flex:1;text-align:center;background:var(--blue-bg);padding:10px;border-radius:var(--radius-sm);border:1px solid var(--border)"><div style="font-size:22px;font-weight:700;color:var(--blue)">'+m.timedOut+'</div><div style="font-size:9px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.5px;margin-top:2px">TIMEOUT</div></div>'
|
|
1453
|
-
+'</div>'
|
|
1454
|
-
+'<div style="background:var(--bg-hover);border-radius:4px;height:8px;overflow:hidden">'
|
|
1455
|
-
+'<div style="height:100%;width:'+passRate+'%;background:'+barColor+';transition:width .5s"></div></div>'
|
|
1456
|
-
+'<div style="text-align:center;font-size:10px;color:var(--text-dim);margin-top:4px">Pass Rate: '+passRate+'% ('+total+' total)</div>'
|
|
1457
|
-
+qualityHtml
|
|
1458
|
-
+'</div>';
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
// Reports rendering
|
|
1462
|
-
function renderReports(){
|
|
1463
|
-
const el=document.getElementById('reports-panel');
|
|
1464
|
-
if(!S.reports.length){el.innerHTML='<div style="padding:12px;color:var(--text-subtle);font-size:11px">No reports generated yet. Click Reports to generate.</div>';return;}
|
|
1465
|
-
el.innerHTML='<div style="padding:10px"><div style="font-size:13px;font-weight:bold;margin-bottom:8px">📊 Generated Reports</div>'
|
|
1466
|
-
+S.reports.map(r=>{
|
|
1467
|
-
const icon=r.format==='html'?'🌐':r.format==='json'?'📋':'📝';
|
|
1468
|
-
const sizeKB=(r.size/1024).toFixed(1);
|
|
1469
|
-
return '<div class="file-item" data-format="'+esc(r.format)+'" style="cursor:pointer">'
|
|
1470
|
-
+'<div class="fname">'+icon+' '+esc(r.filename)+'</div>'
|
|
1471
|
-
+'<div class="fmeta">'+r.format.toUpperCase()+' — '+sizeKB+' KB</div></div>';
|
|
1472
|
-
}).join('')+'</div>';
|
|
1473
|
-
el.querySelectorAll('.file-item').forEach(item=>{
|
|
1474
|
-
item.addEventListener('click',async()=>{
|
|
1475
|
-
const fmt=item.getAttribute('data-format');
|
|
1476
|
-
try{
|
|
1477
|
-
const r=await fetch('/api/reports/'+fmt);
|
|
1478
|
-
const content=await r.text();
|
|
1479
|
-
if(fmt==='html'){
|
|
1480
|
-
const w=window.open('','_blank','width=900,height=700');
|
|
1481
|
-
w.document.write(content);w.document.close();
|
|
1482
|
-
}else{
|
|
1483
|
-
document.getElementById('fp-title').textContent='report.'+fmt;
|
|
1484
|
-
document.getElementById('fp-code').textContent=content;
|
|
1485
|
-
document.getElementById('file-preview').classList.add('visible');
|
|
1486
|
-
}
|
|
1487
|
-
}catch(e){addLog('Failed to load report: '+e.message,'error');}
|
|
1488
|
-
});
|
|
1489
|
-
});
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
|
-
// Theme toggle (borrowed from OpenClaw)
|
|
1493
|
-
function toggleTheme(){
|
|
1494
|
-
S.theme=S.theme==='dark'?'light':'dark';
|
|
1495
|
-
document.documentElement.setAttribute('data-theme',S.theme);
|
|
1496
|
-
localStorage.setItem('opencroc-theme',S.theme);
|
|
1497
|
-
document.getElementById('theme-icon-dark').style.display=S.theme==='dark'?'':'none';
|
|
1498
|
-
document.getElementById('theme-icon-light').style.display=S.theme==='light'?'':'none';
|
|
1499
|
-
renderCanvas();
|
|
1500
|
-
}
|
|
557
|
+
document.getElementById('run-mode').addEventListener('change',e=>state.set({runMode:e.target.value}));
|
|
558
|
+
document.getElementById('view-3d').addEventListener('click',()=>setView('3d'));
|
|
559
|
+
document.getElementById('view-graph').addEventListener('click',()=>setView('graph'));
|
|
1501
560
|
document.getElementById('theme-toggle').addEventListener('click',toggleTheme);
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
(
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
/* ═══════════════════════════════════════════════════════
|
|
1513
|
-
Keyboard Shortcuts System
|
|
1514
|
-
═══════════════════════════════════════════════════════ */
|
|
1515
|
-
let shortcutLegendTimer=null;
|
|
1516
|
-
function showShortcutLegend(){
|
|
1517
|
-
const el=document.getElementById('shortcut-legend');
|
|
1518
|
-
el.classList.add('visible');
|
|
1519
|
-
if(shortcutLegendTimer) clearTimeout(shortcutLegendTimer);
|
|
1520
|
-
shortcutLegendTimer=setTimeout(()=>el.classList.remove('visible'), 4000);
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
document.addEventListener('keydown',e=>{
|
|
1524
|
-
// Don't capture when typing in inputs/selects
|
|
1525
|
-
const tag=e.target.tagName.toLowerCase();
|
|
1526
|
-
if(tag==='input'||tag==='textarea'||tag==='select') return;
|
|
1527
|
-
|
|
1528
|
-
const key=e.key.toLowerCase();
|
|
1529
|
-
|
|
1530
|
-
// Escape — close file preview, close shortcut legend
|
|
1531
|
-
if(e.key==='Escape'){
|
|
1532
|
-
document.getElementById('file-preview').classList.remove('visible');
|
|
1533
|
-
document.getElementById('shortcut-legend').classList.remove('visible');
|
|
1534
|
-
return;
|
|
1535
|
-
}
|
|
1536
|
-
// ? — Show shortcut legend
|
|
1537
|
-
if(key==='?'||e.key==='/'&&e.shiftKey){
|
|
1538
|
-
e.preventDefault(); showShortcutLegend(); return;
|
|
1539
|
-
}
|
|
1540
|
-
// 1 — Dashboard view
|
|
1541
|
-
if(key==='1'){ e.preventDefault(); setView('dashboard'); return; }
|
|
1542
|
-
// 2 — Pixel Office view
|
|
1543
|
-
if(key==='2'){ e.preventDefault(); setView('office'); return; }
|
|
1544
|
-
// S — Scan
|
|
1545
|
-
if(key==='s'&&!e.ctrlKey&&!e.metaKey){ e.preventDefault(); doScan(); return; }
|
|
1546
|
-
// P — Pipeline
|
|
1547
|
-
if(key==='p'&&!e.ctrlKey&&!e.metaKey){ e.preventDefault(); doPipeline(); return; }
|
|
1548
|
-
// T — Tests
|
|
1549
|
-
if(key==='t'&&!e.ctrlKey&&!e.metaKey){ e.preventDefault(); doRunTests(); return; }
|
|
1550
|
-
// R — Reports
|
|
1551
|
-
if(key==='r'&&!e.ctrlKey&&!e.metaKey){ e.preventDefault(); doReports(); return; }
|
|
1552
|
-
// X — Reset
|
|
1553
|
-
if(key==='x'&&!e.ctrlKey&&!e.metaKey){ e.preventDefault(); doReset(); return; }
|
|
1554
|
-
// D — Dark/Light toggle
|
|
1555
|
-
if(key==='d'&&!e.ctrlKey&&!e.metaKey){ e.preventDefault(); toggleTheme(); return; }
|
|
1556
|
-
});
|
|
1557
|
-
|
|
1558
|
-
(async()=>{
|
|
1559
|
-
preloadSprites();
|
|
1560
|
-
setupCanvas();
|
|
1561
|
-
await fetchProject();
|
|
1562
|
-
connectWS();
|
|
1563
|
-
addLog('OpenCroc Studio ready — press ? for shortcuts','info',true);
|
|
1564
|
-
window.addEventListener('resize',()=>{layoutGraph();renderCanvas();renderPixelOffice();});
|
|
1565
|
-
})();
|
|
561
|
+
document.getElementById('sidebar-toggle').addEventListener('click',()=>document.getElementById('sidebar').classList.toggle('collapsed'));
|
|
562
|
+
document.getElementById('bottom-bar-toggle').addEventListener('click',()=>document.getElementById('bottom-bar').classList.toggle('collapsed'));
|
|
563
|
+
document.getElementById('fp-close').addEventListener('click',()=>document.getElementById('file-preview').classList.remove('visible'));
|
|
564
|
+
document.getElementById('fp-backdrop').addEventListener('click',()=>document.getElementById('file-preview').classList.remove('visible'));
|
|
565
|
+
document.querySelectorAll('.panel-tabs .tab').forEach(tab=>{tab.addEventListener('click',()=>{document.querySelectorAll('.panel-tabs .tab').forEach(t=>t.classList.remove('active'));tab.classList.add('active');const tgt=tab.dataset.tab;document.getElementById('log-list').classList.toggle('hidden',tgt!=='log');document.getElementById('file-list').classList.toggle('hidden',tgt!=='files');document.getElementById('results-panel').classList.toggle('hidden',tgt!=='results');document.getElementById('reports-panel').classList.toggle('hidden',tgt!=='reports')})});
|
|
566
|
+
window.addEventListener('resize',()=>resizeEngine());
|
|
567
|
+
let slt=null;
|
|
568
|
+
document.addEventListener('keydown',e=>{const tag=e.target.tagName.toLowerCase();if(tag==='input'||tag==='textarea'||tag==='select')return;const k=e.key.toLowerCase();if(e.key==='Escape'){document.getElementById('file-preview').classList.remove('visible');document.getElementById('shortcut-legend').classList.remove('visible');return}if(k==='?'||(e.key==='/'&&e.shiftKey)){e.preventDefault();const el=document.getElementById('shortcut-legend');el.classList.add('visible');if(slt)clearTimeout(slt);slt=setTimeout(()=>el.classList.remove('visible'),4000);return}if(k==='1'){e.preventDefault();setView('3d');return}if(k==='2'){e.preventDefault();setView('graph');return}if(k==='s'&&!e.ctrlKey&&!e.metaKey){e.preventDefault();doScan();return}if(k==='p'&&!e.ctrlKey&&!e.metaKey){e.preventDefault();doPipeline();return}if(k==='t'&&!e.ctrlKey&&!e.metaKey){e.preventDefault();doRunTests();return}if(k==='r'&&!e.ctrlKey&&!e.metaKey){e.preventDefault();doReports();return}if(k==='x'&&!e.ctrlKey&&!e.metaKey){e.preventDefault();doReset();return}if(k==='d'&&!e.ctrlKey&&!e.metaKey){e.preventDefault();toggleTheme();return}});
|
|
569
|
+
|
|
570
|
+
boot().catch(e=>console.error('Boot failed:',e));
|
|
1566
571
|
</script>
|
|
1567
572
|
</body>
|
|
1568
573
|
</html>
|