opencroc 1.6.8 โ 1.6.9
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/web/index.html +981 -367
- package/package.json +1 -1
package/dist/web/index.html
CHANGED
|
@@ -1,375 +1,714 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="zh-CN">
|
|
2
|
+
<html lang="zh-CN" data-theme="dark">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>OpenCroc Studio
|
|
6
|
+
<title>OpenCroc Studio</title>
|
|
7
7
|
<style>
|
|
8
|
+
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
9
|
+
Design System โ CSS Variables (borrowed from OpenClaw)
|
|
10
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
8
11
|
:root {
|
|
9
|
-
--bg
|
|
10
|
-
--bg-panel:
|
|
11
|
-
--bg-
|
|
12
|
-
--bg-
|
|
13
|
-
--bg-soft:
|
|
14
|
-
--accent:
|
|
15
|
-
--accent-dim: #
|
|
16
|
-
--
|
|
17
|
-
--
|
|
18
|
-
--
|
|
19
|
-
--
|
|
20
|
-
--
|
|
21
|
-
--
|
|
22
|
-
--
|
|
23
|
-
--
|
|
24
|
-
--
|
|
25
|
-
--
|
|
12
|
+
--bg: #0a0f1a;
|
|
13
|
+
--bg-panel: #111827;
|
|
14
|
+
--bg-card: #1a2332;
|
|
15
|
+
--bg-hover: #243044;
|
|
16
|
+
--bg-soft: rgba(30, 41, 59, 0.5);
|
|
17
|
+
--accent: #34d399;
|
|
18
|
+
--accent-dim: #059669;
|
|
19
|
+
--accent-bg: rgba(52, 211, 153, 0.12);
|
|
20
|
+
--red: #f87171;
|
|
21
|
+
--red-bg: rgba(248, 113, 113, 0.12);
|
|
22
|
+
--orange: #fbbf24;
|
|
23
|
+
--orange-bg: rgba(251, 191, 36, 0.12);
|
|
24
|
+
--blue: #60a5fa;
|
|
25
|
+
--blue-bg: rgba(96, 165, 250, 0.12);
|
|
26
|
+
--purple: #a78bfa;
|
|
27
|
+
--purple-bg: rgba(167, 139, 250, 0.12);
|
|
28
|
+
--text: #f1f5f9;
|
|
29
|
+
--text-dim: #94a3b8;
|
|
30
|
+
--text-subtle:#64748b;
|
|
31
|
+
--border: rgba(148, 163, 184, 0.15);
|
|
32
|
+
--border-accent: rgba(52, 211, 153, 0.3);
|
|
33
|
+
--shadow-lg: 0 20px 40px rgba(0, 0, 0, 0.4);
|
|
34
|
+
--shadow-md: 0 8px 24px rgba(0, 0, 0, 0.3);
|
|
35
|
+
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
36
|
+
--radius-lg: 14px;
|
|
37
|
+
--radius-md: 10px;
|
|
38
|
+
--radius-sm: 6px;
|
|
39
|
+
--transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
26
40
|
}
|
|
41
|
+
[data-theme="light"] {
|
|
42
|
+
--bg: #f1f5f9;
|
|
43
|
+
--bg-panel: #ffffff;
|
|
44
|
+
--bg-card: #f8fafc;
|
|
45
|
+
--bg-hover: #e2e8f0;
|
|
46
|
+
--bg-soft: rgba(241, 245, 249, 0.8);
|
|
47
|
+
--accent: #059669;
|
|
48
|
+
--accent-dim: #047857;
|
|
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
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
27
71
|
* { margin:0; padding:0; box-sizing:border-box; }
|
|
28
72
|
body {
|
|
29
|
-
background:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
height:100vh;
|
|
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;
|
|
37
80
|
}
|
|
38
81
|
|
|
39
|
-
|
|
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
|
+
}
|
|
40
128
|
|
|
129
|
+
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
130
|
+
Header
|
|
131
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
41
132
|
.header {
|
|
42
|
-
grid-
|
|
43
|
-
background:
|
|
44
|
-
border:var(--
|
|
45
|
-
border-radius:
|
|
46
|
-
display:flex;
|
|
47
|
-
align-items:center;
|
|
48
|
-
padding:0
|
|
49
|
-
gap:
|
|
50
|
-
box-shadow:var(--shadow-
|
|
51
|
-
backdrop-filter: blur(8px);
|
|
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);
|
|
52
142
|
}
|
|
53
143
|
.header .logo {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
.header
|
|
64
|
-
.header
|
|
65
|
-
.header .
|
|
66
|
-
.header .
|
|
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 */
|
|
67
159
|
.view-switch {
|
|
68
|
-
display:flex;
|
|
69
|
-
background:
|
|
70
|
-
border:1px solid
|
|
71
|
-
border-radius:
|
|
72
|
-
padding:2px;
|
|
73
|
-
margin-right:
|
|
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;
|
|
74
166
|
}
|
|
75
167
|
.view-switch button {
|
|
76
|
-
background:transparent;
|
|
77
|
-
color:var(--text-dim);
|
|
78
|
-
border:none;
|
|
79
|
-
font-family:inherit;
|
|
80
|
-
font-size:
|
|
81
|
-
padding:5px
|
|
82
|
-
border-radius:
|
|
83
|
-
cursor:pointer;
|
|
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;
|
|
84
178
|
}
|
|
179
|
+
.view-switch button:hover { color: var(--text); background: var(--bg-hover); }
|
|
85
180
|
.view-switch button.active {
|
|
86
|
-
background:
|
|
87
|
-
color:var(--accent);
|
|
88
|
-
|
|
181
|
+
background: var(--accent-bg);
|
|
182
|
+
color: var(--accent);
|
|
183
|
+
box-shadow: 0 0 0 1px var(--border-accent);
|
|
89
184
|
}
|
|
185
|
+
|
|
186
|
+
/* Buttons */
|
|
90
187
|
.btn {
|
|
91
|
-
background:
|
|
92
|
-
color
|
|
93
|
-
border:1px solid
|
|
94
|
-
padding:
|
|
95
|
-
font-family:inherit;
|
|
96
|
-
font-size:11px;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
.btn:
|
|
105
|
-
.btn
|
|
106
|
-
.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; }
|
|
107
207
|
.mode-select {
|
|
108
|
-
background:var(--bg-card);
|
|
109
|
-
color:var(--text);
|
|
110
|
-
border:1px solid
|
|
111
|
-
border-radius:
|
|
112
|
-
font-family:inherit;
|
|
113
|
-
font-size:
|
|
114
|
-
padding:
|
|
115
|
-
min-width:
|
|
116
|
-
|
|
117
|
-
|
|
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; }
|
|
118
221
|
.header .stats > div {
|
|
119
|
-
background:
|
|
120
|
-
border:1px solid
|
|
121
|
-
border-radius:
|
|
122
|
-
padding:
|
|
123
|
-
min-width:
|
|
124
|
-
text-align:center;
|
|
125
|
-
}
|
|
126
|
-
.header .stats
|
|
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; }
|
|
127
231
|
.conn-dot {
|
|
128
|
-
width:10px;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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);
|
|
134
248
|
}
|
|
135
|
-
.
|
|
249
|
+
.theme-toggle:hover { background: var(--bg-hover); color: var(--text); }
|
|
250
|
+
.theme-toggle svg { width: 16px; height: 16px; }
|
|
136
251
|
|
|
252
|
+
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
253
|
+
Sidebar
|
|
254
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
137
255
|
.sidebar {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
border
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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; }
|
|
146
270
|
.mod-item {
|
|
147
|
-
padding:7px 10px;
|
|
148
|
-
border-radius:
|
|
149
|
-
font-size:
|
|
150
|
-
cursor:pointer;
|
|
151
|
-
display:flex;
|
|
152
|
-
align-items:center;
|
|
153
|
-
gap:
|
|
154
|
-
transition:
|
|
155
|
-
margin-bottom:
|
|
156
|
-
border:1px solid transparent;
|
|
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);
|
|
157
282
|
}
|
|
158
283
|
.mod-item:hover {
|
|
159
|
-
background:
|
|
160
|
-
border-color:
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
.dot
|
|
169
|
-
|
|
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) } }
|
|
170
301
|
|
|
302
|
+
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
303
|
+
Main Content
|
|
304
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
171
305
|
.main {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
border
|
|
177
|
-
|
|
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);
|
|
178
313
|
}
|
|
179
314
|
#graph-canvas { width:100%; height:100%; display:block; cursor:grab; }
|
|
180
|
-
.view {
|
|
181
|
-
position:absolute;
|
|
182
|
-
inset:0;
|
|
183
|
-
}
|
|
315
|
+
.view { position:absolute; inset:0; }
|
|
184
316
|
.view.hidden { display:none; }
|
|
317
|
+
|
|
318
|
+
/* Pixel Office Stage */
|
|
185
319
|
.pixel-stage {
|
|
186
|
-
position:relative;
|
|
187
|
-
width:100%;
|
|
188
|
-
height:100%;
|
|
189
|
-
overflow:hidden;
|
|
320
|
+
position: relative; width:100%; height:100%; overflow:hidden;
|
|
190
321
|
background:
|
|
191
|
-
linear-gradient(rgba(8,12,20,.
|
|
322
|
+
linear-gradient(rgba(8,12,20,.15), rgba(8,12,20,.4)),
|
|
192
323
|
url('./assets/star/office_bg_small.webp') center/cover no-repeat;
|
|
193
324
|
}
|
|
194
325
|
.pixel-stage::before {
|
|
195
|
-
content:'';
|
|
196
|
-
position:absolute;
|
|
197
|
-
inset:0;
|
|
326
|
+
content:''; position:absolute; inset:0;
|
|
198
327
|
background-image:
|
|
199
|
-
linear-gradient(rgba(
|
|
200
|
-
linear-gradient(90deg, rgba(
|
|
201
|
-
background-size:
|
|
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;
|
|
202
331
|
pointer-events:none;
|
|
203
332
|
}
|
|
204
333
|
.pixel-stage .asset {
|
|
205
|
-
position:absolute;
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
filter: drop-shadow(0 6px 10px rgba(0,0,0,.35));
|
|
209
|
-
opacity:.96;
|
|
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;
|
|
210
337
|
}
|
|
211
338
|
.pixel-stage .desk-asset { width:180px; bottom:90px; left:40px; }
|
|
212
339
|
.pixel-stage .server-asset { width:110px; top:24px; right:34px; }
|
|
213
340
|
.pixel-stage .coffee-asset { width:72px; top:100px; right:180px; }
|
|
214
|
-
.pixel-stage .walls-asset { width:220px; top:10px; left:46%; transform:translateX(-50%); opacity:.
|
|
215
|
-
.pixel-agent-layer {
|
|
216
|
-
position:absolute;
|
|
217
|
-
inset:0;
|
|
218
|
-
z-index:3;
|
|
219
|
-
}
|
|
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; }
|
|
220
343
|
.pixel-agent {
|
|
221
|
-
position:absolute;
|
|
222
|
-
|
|
223
|
-
|
|
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;
|
|
224
352
|
image-rendering:pixelated;
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
.pixel-agent.
|
|
229
|
-
.pixel-agent.
|
|
230
|
-
.pixel-agent.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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 */
|
|
235
370
|
.pixel-label {
|
|
236
|
-
position:absolute;
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
border:
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
color:#e9f3ff;
|
|
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);
|
|
244
378
|
white-space:nowrap;
|
|
245
379
|
z-index:4;
|
|
380
|
+
box-shadow: var(--shadow-sm);
|
|
381
|
+
backdrop-filter: blur(4px);
|
|
246
382
|
}
|
|
247
|
-
.pixel-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
top:14px;
|
|
251
|
-
display:flex;
|
|
252
|
-
gap:8px;
|
|
253
|
-
z-index:4;
|
|
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;
|
|
254
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; }
|
|
255
394
|
.pixel-kpi {
|
|
256
|
-
min-width:
|
|
257
|
-
background:
|
|
258
|
-
border:1px solid
|
|
259
|
-
border-radius:
|
|
260
|
-
padding:
|
|
261
|
-
box-shadow:var(--shadow-md);
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
.pixel-kpi .
|
|
265
|
-
.
|
|
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
|
+
}
|
|
266
439
|
.tooltip.visible { display:block; }
|
|
440
|
+
.tooltip b { color: var(--accent); }
|
|
267
441
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
.panel
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
.
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
.
|
|
287
|
-
|
|
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; }
|
|
288
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
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
289
540
|
.office {
|
|
290
|
-
grid-
|
|
291
|
-
background:
|
|
292
|
-
|
|
293
|
-
border:var(--
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
box-shadow:var(--shadow-md);
|
|
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);
|
|
300
550
|
}
|
|
301
551
|
.desk {
|
|
302
|
-
flex:0 0
|
|
303
|
-
background:
|
|
304
|
-
border:1px solid
|
|
305
|
-
border-radius:
|
|
306
|
-
padding:10px;
|
|
307
|
-
display:flex;
|
|
308
|
-
flex-direction:column;
|
|
309
|
-
align-items:center;
|
|
310
|
-
gap:
|
|
311
|
-
position:relative;
|
|
312
|
-
overflow:hidden;
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
.desk
|
|
317
|
-
.desk
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
.desk
|
|
321
|
-
|
|
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; }
|
|
322
581
|
@keyframes croc-idle { 0%,90%,100%{transform:translateY(0)} 95%{transform:translateY(-3px)} }
|
|
323
|
-
@keyframes croc-work { from{transform:translateY(0) rotate(-
|
|
324
|
-
@keyframes croc-think { 0%,100%{transform:scale(1) rotate(0)} 25%{transform:scale(1.
|
|
325
|
-
@keyframes croc-done { 0%{transform:scale(1)} 50%{transform:scale(1.
|
|
326
|
-
@keyframes croc-error { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-
|
|
327
|
-
.desk .croc-name { font-size:
|
|
328
|
-
.desk .croc-role {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
.desk .
|
|
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} }
|
|
612
|
+
|
|
613
|
+
/* Typewriter cursor */
|
|
614
|
+
.typewriter-cursor {
|
|
615
|
+
display:inline-block; width:1px; height:1em; background:var(--accent);
|
|
616
|
+
margin-left:2px; vertical-align:text-bottom;
|
|
617
|
+
animation: cursor-blink .6s steps(1) infinite;
|
|
618
|
+
}
|
|
619
|
+
@keyframes cursor-blink { 0%,100%{opacity:1} 50%{opacity:0} }
|
|
620
|
+
|
|
621
|
+
/* Keyboard shortcuts legend */
|
|
622
|
+
.shortcut-legend {
|
|
623
|
+
position:fixed; bottom:12px; right:12px; z-index:90;
|
|
624
|
+
background:var(--bg-panel); border:1px solid var(--border);
|
|
625
|
+
border-radius:var(--radius-md); padding:10px 14px;
|
|
626
|
+
font-size:10px; color:var(--text-dim);
|
|
627
|
+
box-shadow:var(--shadow-md); opacity:0; pointer-events:none;
|
|
628
|
+
transition:opacity .2s;
|
|
629
|
+
display:grid; grid-template-columns:auto auto; gap:3px 12px;
|
|
630
|
+
}
|
|
631
|
+
.shortcut-legend.visible { opacity:1; pointer-events:auto; }
|
|
632
|
+
.shortcut-legend kbd {
|
|
633
|
+
display:inline-block; min-width:18px; text-align:center;
|
|
634
|
+
background:var(--bg-card); border:1px solid var(--border);
|
|
635
|
+
border-radius:3px; padding:1px 5px; font-family:inherit;
|
|
636
|
+
font-size:9px; font-weight:600; color:var(--text);
|
|
637
|
+
}
|
|
333
638
|
</style>
|
|
334
639
|
</head>
|
|
335
640
|
<body>
|
|
336
641
|
<div class="app">
|
|
337
642
|
<header class="header">
|
|
338
|
-
<div class="logo"
|
|
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>
|
|
339
646
|
<div class="title-wrap">
|
|
340
647
|
<h1>OpenCroc Studio</h1>
|
|
341
648
|
<div class="subtitle">Pixel Ops Dashboard ยท Real-time Multi-Agent Runtime</div>
|
|
342
649
|
</div>
|
|
343
650
|
<div class="actions">
|
|
344
651
|
<div class="view-switch">
|
|
345
|
-
<button id="view-dashboard" class="active">
|
|
346
|
-
|
|
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>
|
|
347
660
|
</div>
|
|
348
|
-
<button class="btn" id="btn-scan" title="Scan project"
|
|
349
|
-
|
|
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>
|
|
350
669
|
<select id="run-mode" class="mode-select" title="Test run mode">
|
|
351
670
|
<option value="auto">Auto</option>
|
|
352
671
|
<option value="reuse">Reuse</option>
|
|
353
672
|
<option value="managed">Managed</option>
|
|
354
673
|
</select>
|
|
355
|
-
<button class="btn" id="btn-run-tests" title="Run generated tests" disabled
|
|
356
|
-
|
|
357
|
-
|
|
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>
|
|
358
686
|
</div>
|
|
687
|
+
<div class="spacer"></div>
|
|
359
688
|
<div class="stats">
|
|
360
|
-
<div>Modules
|
|
361
|
-
<div>Models
|
|
362
|
-
<div>APIs
|
|
363
|
-
<div>Tests
|
|
364
|
-
<div id="s-results-wrap" style="display:none">Results
|
|
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>
|
|
365
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>
|
|
366
699
|
<div class="conn-dot" id="conn-dot" title="WebSocket"></div>
|
|
367
700
|
</header>
|
|
368
701
|
|
|
369
702
|
<aside class="sidebar">
|
|
370
|
-
<h3
|
|
703
|
+
<h3>
|
|
704
|
+
<svg viewBox="0 0 16 16" fill="currentColor"><path d="M1 2h14v2H1zm0 4h10v2H1zm0 4h12v2H1zm0 4h8v2H1z"/></svg>
|
|
705
|
+
Modules
|
|
706
|
+
</h3>
|
|
371
707
|
<div id="mod-list"></div>
|
|
372
|
-
<h3 style="margin-top:12px"
|
|
708
|
+
<h3 style="margin-top:12px">
|
|
709
|
+
<svg viewBox="0 0 16 16" fill="var(--accent)"><rect x="4" y="2" width="8" height="3" rx="1"/><rect x="3" y="5" width="10" height="6" rx="2" opacity=".8"/><rect x="5" y="11" width="2" height="2" opacity=".6"/><rect x="9" y="11" width="2" height="2" opacity=".6"/></svg>
|
|
710
|
+
Agents
|
|
711
|
+
</h3>
|
|
373
712
|
<div id="agent-sidebar"></div>
|
|
374
713
|
</aside>
|
|
375
714
|
|
|
@@ -385,8 +724,8 @@
|
|
|
385
724
|
<img class="asset walls-asset" src="./assets/botreview/walls.png" alt="walls">
|
|
386
725
|
<div class="pixel-kpis">
|
|
387
726
|
<div class="pixel-kpi"><div class="t">Working</div><div class="v" id="kpi-working">0</div></div>
|
|
388
|
-
<div class="pixel-kpi"><div class="t">Errors</div><div class="v" id="kpi-errors">0</div></div>
|
|
389
|
-
<div class="pixel-kpi"><div class="t">Done</div><div class="v" id="kpi-done">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>
|
|
390
729
|
</div>
|
|
391
730
|
<div class="pixel-agent-layer" id="pixel-agent-layer"></div>
|
|
392
731
|
</div>
|
|
@@ -396,10 +735,22 @@
|
|
|
396
735
|
|
|
397
736
|
<div class="log-panel">
|
|
398
737
|
<div class="panel-tabs">
|
|
399
|
-
<button class="tab active" data-tab="log"
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
738
|
+
<button class="tab active" data-tab="log">
|
|
739
|
+
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor"><rect x="1" y="2" width="14" height="12" rx="2" opacity=".2"/><line x1="4" y1="5" x2="12" y2="5" stroke="currentColor" stroke-width="1.2"/><line x1="4" y1="8" x2="10" y2="8" stroke="currentColor" stroke-width="1.2"/><line x1="4" y1="11" x2="8" y2="11" stroke="currentColor" stroke-width="1.2"/></svg>
|
|
740
|
+
Log
|
|
741
|
+
</button>
|
|
742
|
+
<button class="tab" data-tab="files">
|
|
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>
|
|
403
754
|
</div>
|
|
404
755
|
<div class="log-list" id="log-list"></div>
|
|
405
756
|
<div class="file-list" id="file-list" style="display:none"></div>
|
|
@@ -415,15 +766,144 @@
|
|
|
415
766
|
<pre id="fp-code"></pre>
|
|
416
767
|
</div>
|
|
417
768
|
|
|
769
|
+
<!-- Keyboard shortcut legend -->
|
|
770
|
+
<div class="shortcut-legend" id="shortcut-legend">
|
|
771
|
+
<kbd>1</kbd><span>Dashboard</span>
|
|
772
|
+
<kbd>2</kbd><span>Pixel Office</span>
|
|
773
|
+
<kbd>S</kbd><span>Scan</span>
|
|
774
|
+
<kbd>P</kbd><span>Pipeline</span>
|
|
775
|
+
<kbd>T</kbd><span>Run Tests</span>
|
|
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>
|
|
781
|
+
</div>
|
|
782
|
+
|
|
418
783
|
<script>
|
|
784
|
+
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
785
|
+
SVG Icon Library (replaces emoji) โ inspired by OpenClaw PixelSvg
|
|
786
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
787
|
+
const ICONS = {
|
|
788
|
+
croc: `<svg viewBox="0 0 16 16" fill="none"><rect x="4" y="1" width="8" height="3" rx="1" fill="var(--accent)"/><rect x="3" y="4" width="10" height="7" rx="2" fill="var(--accent)" opacity=".85"/><rect x="5" y="5" width="2" height="2" rx="1" fill="var(--bg)"/><rect x="9" y="5" width="2" height="2" rx="1" fill="var(--bg)"/><rect x="6" y="8" width="4" height="1" fill="var(--bg)" opacity=".4"/><rect x="5" y="11" width="2" height="3" rx="1" fill="var(--accent)" opacity=".6"/><rect x="9" y="11" width="2" height="3" rx="1" fill="var(--accent)" opacity=".6"/></svg>`,
|
|
789
|
+
parser: `<svg viewBox="0 0 16 16" fill="none" stroke="var(--blue)" stroke-width="1.3"><rect x="2" y="2" width="12" height="12" rx="2"/><line x1="5" y1="6" x2="11" y2="6"/><line x1="5" y1="9" x2="9" y2="9"/></svg>`,
|
|
790
|
+
analyzer: `<svg viewBox="0 0 16 16" fill="none" stroke="var(--purple)" stroke-width="1.3"><rect x="1" y="11" width="3" height="4"/><rect x="5" y="7" width="3" height="8"/><rect x="9" y="3" width="3" height="12"/><rect x="13" y="1" width="2" height="14"/></svg>`,
|
|
791
|
+
tester: `<svg viewBox="0 0 16 16" fill="none" stroke="var(--accent)" stroke-width="1.3"><path d="M6 2v4l-3 8h10l-3-8V2"/><line x1="4" y1="2" x2="12" y2="2"/></svg>`,
|
|
792
|
+
healer: `<svg viewBox="0 0 16 16" fill="none" stroke="var(--orange)" stroke-width="1.5"><path d="M5 3l-2 5h4l-2 5 7-7h-4l2-3z"/></svg>`,
|
|
793
|
+
planner: `<svg viewBox="0 0 16 16" fill="none" stroke="var(--blue)" stroke-width="1.3"><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="8" y2="11"/></svg>`,
|
|
794
|
+
reporter: `<svg viewBox="0 0 16 16" fill="none" stroke="var(--accent)" stroke-width="1.3"><rect x="1" y="2" width="14" height="12" rx="2"/><polyline points="4,10 6,6 8,9 10,4 12,7"/></svg>`,
|
|
795
|
+
};
|
|
796
|
+
const ROLE_ICONS = { parser:ICONS.parser, analyzer:ICONS.analyzer, tester:ICONS.tester, healer:ICONS.healer, planner:ICONS.planner, reporter:ICONS.reporter };
|
|
797
|
+
|
|
798
|
+
/* Bubble messages (Star-Office style) */
|
|
799
|
+
const BUBBLE_TEXTS = {
|
|
800
|
+
working: ['ๆญฃๅจๆง่ก...', 'ๅฟซไบๅฟซไบ', 'ๅค็ไธญ...', 'ๅ ๆฒน ๐ช'],
|
|
801
|
+
testing: ['่ทๆต่ฏไธญ...', '้ช่ฏ API...', '็ญ็ปๆ...'],
|
|
802
|
+
thinking: ['่ฎฉๆๆณๆณ...', 'ๅๆไธญ...', 'ๆจ็...', '๐ค'],
|
|
803
|
+
error: ['ๅบ้ไบ!', 'ไฟฎๅคไธญ...', '็ณ็ณ...', 'ๆๆฅ้ฎ้ข...'],
|
|
804
|
+
idle: ['ๆธ้ฑผไธญ~', '็ญไปปๅก...', 'โ ๅๅๅก', 'zzZ'],
|
|
805
|
+
done: ['ๆๅฎ!', 'ๅฎๆ โ', 'ไธไธไธช!'],
|
|
806
|
+
passed: ['ๅ
จ็ปฟ โ', 'ๆต่ฏ้่ฟ!'],
|
|
807
|
+
failed: ['ๆๅคฑ่ดฅ...', '้่ฆไฟฎๅค'],
|
|
808
|
+
};
|
|
809
|
+
|
|
419
810
|
const S = {
|
|
420
811
|
project:null, graph:{nodes:[],edges:[]}, agents:[], ws:null,
|
|
421
812
|
pan:{x:0,y:0}, zoom:1, dragging:false, dragStart:{x:0,y:0},
|
|
422
813
|
nodePos:new Map(), hoveredNode:null, running:false, _userPanned:false,
|
|
423
814
|
generatedFiles:[], testMetrics:null, testQuality:null, reports:[], runMode:'auto',
|
|
424
|
-
currentView:'dashboard'
|
|
815
|
+
currentView:'dashboard', theme:'dark', bubbleTimers:new Map(),
|
|
816
|
+
spriteSheets:new Map(), spriteAnimFrames:new Map(), spriteAnimTimers:new Map()
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
820
|
+
Spritesheet Animation Engine (inspired by Star-Office Phaser)
|
|
821
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
|
822
|
+
const SPRITE_CONFIG = {
|
|
823
|
+
// Each character sprite: image src, frame grid, frame count, FPS per status
|
|
824
|
+
chars: [
|
|
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 },
|
|
425
831
|
};
|
|
426
832
|
|
|
833
|
+
// Preload sprite images
|
|
834
|
+
function preloadSprites(){
|
|
835
|
+
SPRITE_CONFIG.chars.forEach(cfg=>{
|
|
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
|
+
}
|
|
841
|
+
|
|
842
|
+
// Render a single frame of a sprite to a canvas element
|
|
843
|
+
function renderSpriteFrame(canvas, spriteKey, frameIdx){
|
|
844
|
+
const sheet=S.spriteSheets.get(spriteKey);
|
|
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
|
+
}
|
|
855
|
+
|
|
856
|
+
// Start/manage sprite animation loop for an agent
|
|
857
|
+
function startSpriteAnim(canvasId, spriteKey, status){
|
|
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
|
+
}
|
|
879
|
+
|
|
880
|
+
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
881
|
+
Typewriter Effect (inspired by Star-Office)
|
|
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
|
+
}
|
|
906
|
+
|
|
427
907
|
async function fetchProject(){
|
|
428
908
|
try{
|
|
429
909
|
const r=await fetch('/api/project'); S.project=await r.json();
|
|
@@ -433,23 +913,23 @@ async function fetchProject(){
|
|
|
433
913
|
}
|
|
434
914
|
async function doScan(){
|
|
435
915
|
if(S.running)return; S.running=true; updateBtns();
|
|
436
|
-
addLog('๐ Starting scan...');
|
|
916
|
+
addLog('๐ Starting scan...','info',true);
|
|
437
917
|
try{await fetch('/api/scan',{method:'POST'});}
|
|
438
918
|
catch(e){addLog('Scan failed: '+e.message,'error');S.running=false;updateBtns();}
|
|
439
919
|
}
|
|
440
920
|
async function doPipeline(){
|
|
441
921
|
if(S.running)return; S.running=true; updateBtns();
|
|
442
|
-
addLog('โถ Starting pipeline...');
|
|
922
|
+
addLog('โถ Starting pipeline...','info',true);
|
|
443
923
|
try{await fetch('/api/pipeline',{method:'POST'});}
|
|
444
924
|
catch(e){addLog('Pipeline failed: '+e.message,'error');S.running=false;updateBtns();}
|
|
445
925
|
}
|
|
446
926
|
async function doReset(){
|
|
447
927
|
try{await fetch('/api/reset',{method:'POST'});}catch(e){addLog('Reset failed','error');}
|
|
448
|
-
S.running=false; updateBtns(); addLog('โน Agents reset');
|
|
928
|
+
S.running=false; updateBtns(); addLog('โน Agents reset','info',true);
|
|
449
929
|
}
|
|
450
930
|
async function doRunTests(){
|
|
451
931
|
if(S.running)return; S.running=true; updateBtns();
|
|
452
|
-
addLog('๐งช Starting test execution ('+S.runMode+')...');
|
|
932
|
+
addLog('๐งช Starting test execution ('+S.runMode+')...','info',true);
|
|
453
933
|
try{
|
|
454
934
|
const r=await fetch('/api/run-tests',{
|
|
455
935
|
method:'POST',
|
|
@@ -465,7 +945,7 @@ async function doRunTests(){
|
|
|
465
945
|
}
|
|
466
946
|
async function doReports(){
|
|
467
947
|
if(S.running)return; S.running=true; updateBtns();
|
|
468
|
-
addLog('๐ Generating reports...');
|
|
948
|
+
addLog('๐ Generating reports...','info',true);
|
|
469
949
|
try{await fetch('/api/reports/generate',{method:'POST'});}
|
|
470
950
|
catch(e){addLog('Report gen failed: '+e.message,'error');S.running=false;updateBtns();}
|
|
471
951
|
}
|
|
@@ -498,8 +978,8 @@ function connectWS(){
|
|
|
498
978
|
badge.textContent=S.generatedFiles.length;badge.style.display='inline';
|
|
499
979
|
}else if(m.type==='pipeline:complete'){
|
|
500
980
|
S.running=false; updateBtns();
|
|
501
|
-
if(m.payload.status==='success') addLog('โ
Pipeline complete!');
|
|
502
|
-
else addLog('โ Pipeline failed: '+(m.payload.error||''),'error');
|
|
981
|
+
if(m.payload.status==='success') addLog('โ
Pipeline complete!','info',true);
|
|
982
|
+
else addLog('โ Pipeline failed: '+(m.payload.error||''),'error',true);
|
|
503
983
|
setTimeout(fetchProject,500);
|
|
504
984
|
}else if(m.type==='test:complete'){
|
|
505
985
|
S.running=false; S.testMetrics=m.payload.metrics||null; S.testQuality=m.payload.quality||null; updateBtns(); renderResults();
|
|
@@ -514,7 +994,7 @@ function connectWS(){
|
|
|
514
994
|
}
|
|
515
995
|
const rb=document.getElementById('result-badge');
|
|
516
996
|
rb.textContent=m.payload.total||0; rb.style.display='inline';
|
|
517
|
-
rb.
|
|
997
|
+
rb.className='tab-badge'+((met&&met.failed>0)?' alert':'');
|
|
518
998
|
}else if(m.type==='reports:generated'){
|
|
519
999
|
S.running=false; S.reports=m.payload||[]; updateBtns(); renderReports();
|
|
520
1000
|
addLog('๐ '+S.reports.length+' reports generated');
|
|
@@ -524,11 +1004,17 @@ function connectWS(){
|
|
|
524
1004
|
S.ws.onclose=()=>{document.getElementById('conn-dot').classList.remove('on');setTimeout(connectWS,3000);};
|
|
525
1005
|
}
|
|
526
1006
|
|
|
527
|
-
function addLog(msg,level){
|
|
1007
|
+
function addLog(msg,level,useTypewriter){
|
|
528
1008
|
level=level||'info';
|
|
1009
|
+
// Use typewriter for important messages (pipeline, scan, reset, test)
|
|
1010
|
+
if(useTypewriter){
|
|
1011
|
+
typewriterLog(msg,level,30);
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
529
1014
|
const el=document.getElementById('log-list');
|
|
530
1015
|
const d=document.createElement('div'); d.className='log-entry '+level;
|
|
531
|
-
|
|
1016
|
+
const ts=document.createElement('span'); ts.className='timestamp'; ts.textContent=new Date().toLocaleTimeString();
|
|
1017
|
+
d.appendChild(ts); d.appendChild(document.createTextNode(msg));
|
|
532
1018
|
el.appendChild(d); el.scrollTop=el.scrollHeight;
|
|
533
1019
|
}
|
|
534
1020
|
function updateStats(){
|
|
@@ -620,79 +1106,81 @@ function renderCanvas(){
|
|
|
620
1106
|
canvas.width=canvas.clientWidth*dpr; canvas.height=canvas.clientHeight*dpr;
|
|
621
1107
|
ctx.scale(dpr,dpr);
|
|
622
1108
|
const w=canvas.clientWidth,h=canvas.clientHeight;
|
|
623
|
-
|
|
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);
|
|
624
1115
|
// Subtle grid
|
|
625
|
-
ctx.strokeStyle='
|
|
1116
|
+
ctx.strokeStyle=isDark?'rgba(148,163,184,.06)':'rgba(100,116,139,.08)';ctx.lineWidth=.5;
|
|
626
1117
|
const gridStep=40;
|
|
627
1118
|
for(let x=-2000;x<4000;x+=gridStep){ctx.beginPath();ctx.moveTo(x,-2000);ctx.lineTo(x,4000);ctx.stroke();}
|
|
628
1119
|
for(let y=-2000;y<4000;y+=gridStep){ctx.beginPath();ctx.moveTo(-2000,y);ctx.lineTo(4000,y);ctx.stroke();}
|
|
629
1120
|
|
|
630
1121
|
const edges=S.graph.edges||[],nodes=S.graph.nodes||[];
|
|
631
|
-
const largeGraph=nodes.length>80;
|
|
632
1122
|
|
|
633
1123
|
// Draw module cluster backgrounds
|
|
634
1124
|
if(S.modMeta){
|
|
635
1125
|
for(const[name,m] of S.modMeta){
|
|
636
1126
|
ctx.beginPath(); ctx.arc(m.cx,m.cy,m.radius,0,Math.PI*2);
|
|
637
|
-
ctx.fillStyle=m.color+'
|
|
638
|
-
ctx.strokeStyle=m.color+'
|
|
639
|
-
|
|
640
|
-
ctx.
|
|
641
|
-
ctx.fillText(name+' ('+m.count+')',m.cx,m.cy-m.radius-
|
|
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);
|
|
642
1132
|
}
|
|
643
1133
|
}
|
|
644
1134
|
|
|
645
|
-
// Draw
|
|
1135
|
+
// Draw edges with curved lines + gradient
|
|
646
1136
|
for(const e of edges){
|
|
647
|
-
if(e.relation==='contains') continue;
|
|
1137
|
+
if(e.relation==='contains') continue;
|
|
648
1138
|
const s=S.nodePos.get(e.source),t=S.nodePos.get(e.target);if(!s||!t)continue;
|
|
649
|
-
const dx=t.x-s.x,dy=t.y-s.y
|
|
650
|
-
// Curved edge via quadratic bezier, perpendicular offset
|
|
1139
|
+
const dx=t.x-s.x,dy=t.y-s.y;
|
|
651
1140
|
const mx=(s.x+t.x)/2+dy*0.15, my=(s.y+t.y)/2-dx*0.15;
|
|
652
1141
|
const grad=ctx.createLinearGradient(s.x,s.y,t.x,t.y);
|
|
653
|
-
grad.addColorStop(0,'rgba(
|
|
654
|
-
grad.addColorStop(1,'rgba(
|
|
655
|
-
ctx.strokeStyle=grad;ctx.lineWidth=
|
|
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;
|
|
656
1145
|
ctx.beginPath();ctx.moveTo(s.x,s.y);ctx.quadraticCurveTo(mx,my,t.x,t.y);ctx.stroke();
|
|
657
|
-
// Arrowhead
|
|
658
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;
|
|
659
|
-
const a=Math.atan2(t.y-at2y,t.x-at2x),al=
|
|
660
|
-
ctx.fillStyle='rgba(
|
|
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);
|
|
661
1149
|
ctx.lineTo(t.x-al*Math.cos(a-.4),t.y-al*Math.sin(a-.4));
|
|
662
1150
|
ctx.lineTo(t.x-al*Math.cos(a+.4),t.y-al*Math.sin(a+.4));ctx.closePath();ctx.fill();
|
|
663
1151
|
}
|
|
664
1152
|
|
|
665
1153
|
// Draw nodes
|
|
666
|
-
const tc={model:'#
|
|
667
|
-
const sc={idle:'#
|
|
1154
|
+
const tc={model:'#34d399',controller:'#f87171',api:'#fbbf24',dto:'#60a5fa',module:'#a78bfa'};
|
|
1155
|
+
const sc={idle:isDark?'#475569':'#94a3b8',testing:'#fbbf24',passed:'#34d399',failed:'#f87171'};
|
|
668
1156
|
for(const n of nodes){
|
|
669
|
-
if(n.type==='module') continue;
|
|
1157
|
+
if(n.type==='module') continue;
|
|
670
1158
|
const p=S.nodePos.get(n.id);if(!p)continue;
|
|
671
|
-
const sz=
|
|
672
|
-
|
|
673
|
-
if(n.status==='
|
|
674
|
-
else if(n.status==='
|
|
675
|
-
else if(n.status==='failed'){ctx.shadowColor=sc.failed;ctx.shadowBlur=12;}
|
|
676
|
-
// Node circle instead of rect for cleaner look
|
|
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;}
|
|
677
1163
|
ctx.beginPath();ctx.arc(p.x,p.y,sz,0,Math.PI*2);
|
|
678
1164
|
ctx.fillStyle=c;ctx.fill();
|
|
679
1165
|
ctx.shadowBlur=0;
|
|
680
|
-
ctx.strokeStyle=hov?'#fff':ol;ctx.lineWidth=hov?
|
|
681
|
-
//
|
|
682
|
-
ctx.font=(sz)+'px serif';ctx.textAlign='center';ctx.textBaseline='middle';
|
|
683
|
-
ctx.fillText(n.type==='model'?'๐ฆ':'๐ฎ',p.x,p.y);
|
|
684
|
-
// Label โ show at higher zoom or on hover
|
|
1166
|
+
ctx.strokeStyle=hov?(isDark?'#fff':'#0f172a'):ol;ctx.lineWidth=hov?2.5:1.2;ctx.stroke();
|
|
1167
|
+
// Label
|
|
685
1168
|
if(S.zoom>0.4||hov){
|
|
686
|
-
ctx.font='9px "
|
|
687
|
-
ctx.
|
|
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);
|
|
688
1173
|
}
|
|
689
1174
|
}
|
|
690
1175
|
ctx.restore();
|
|
691
1176
|
|
|
692
1177
|
// HUD legend
|
|
693
|
-
ctx.font='10px "
|
|
694
|
-
const
|
|
695
|
-
|
|
1178
|
+
ctx.font='500 10px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';ctx.textAlign='left';
|
|
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);}
|
|
696
1184
|
}
|
|
697
1185
|
|
|
698
1186
|
function setupCanvas(){
|
|
@@ -727,8 +1215,7 @@ function updateAll(){
|
|
|
727
1215
|
}
|
|
728
1216
|
function renderModList(){
|
|
729
1217
|
const el=document.getElementById('mod-list'),mods=S.graph.nodes.filter(n=>n.type==='module');
|
|
730
|
-
if(!mods.length){el.innerHTML='<div style="padding:
|
|
731
|
-
// Sort by member count descending
|
|
1218
|
+
if(!mods.length){el.innerHTML='<div style="padding:12px;color:var(--text-subtle);font-size:11px">No modules found</div>';return;}
|
|
732
1219
|
const sorted=mods.sort((a,b)=>{
|
|
733
1220
|
const ca=S.modMeta&&S.modMeta.get(a.label)?S.modMeta.get(a.label).count:0;
|
|
734
1221
|
const cb=S.modMeta&&S.modMeta.get(b.label)?S.modMeta.get(b.label).count:0;
|
|
@@ -737,10 +1224,10 @@ function renderModList(){
|
|
|
737
1224
|
el.innerHTML=sorted.map(m=>{
|
|
738
1225
|
const meta=S.modMeta&&S.modMeta.get(m.label);
|
|
739
1226
|
const cnt=meta?meta.count:'';
|
|
740
|
-
const col=meta?meta.color:'
|
|
741
|
-
return '<div class="mod-item" data-mod="'+esc(m.label)+'" style="border-left:3px solid '+col+'
|
|
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+'">'
|
|
742
1229
|
+'<div class="dot '+m.status+'"></div>'
|
|
743
|
-
+esc(m.label)+'
|
|
1230
|
+
+esc(m.label)+'<span class="mod-count">'+cnt+'</span></div>';
|
|
744
1231
|
}).join('');
|
|
745
1232
|
// Click to navigate
|
|
746
1233
|
el.querySelectorAll('.mod-item').forEach(item=>{
|
|
@@ -759,19 +1246,19 @@ function renderModList(){
|
|
|
759
1246
|
}
|
|
760
1247
|
function renderAgentSB(){
|
|
761
1248
|
document.getElementById('agent-sidebar').innerHTML=S.agents.map(a=>
|
|
762
|
-
'<div class="mod-item"><div class="dot '+a.status+'"></div>'+esc(a.name)+'
|
|
1249
|
+
'<div class="mod-item"><div class="dot '+a.status+'"></div>'+esc(a.name)+'<span class="mod-count">'+a.status+'</span></div>'
|
|
763
1250
|
).join('');
|
|
764
1251
|
}
|
|
765
1252
|
function renderOffice(){
|
|
766
|
-
const dd={parser:'๐ป',analyzer:'๐',tester:'๐งช',healer:'๐ง',planner:'๐',reporter:'๐'};
|
|
767
1253
|
document.getElementById('croc-office').innerHTML=S.agents.map(a=>{
|
|
768
1254
|
const prog=typeof a.progress==='number'?a.progress:0;
|
|
1255
|
+
const roleIcon=ROLE_ICONS[a.role]||ICONS.croc;
|
|
769
1256
|
return '<div class="desk '+a.status+'"><div class="badge dot '+a.status+'"></div>'+
|
|
770
|
-
'<div class="croc-sprite"
|
|
1257
|
+
'<div class="croc-sprite">'+ICONS.croc+'</div><div class="croc-name">'+esc(a.name)+'</div>'+
|
|
771
1258
|
'<div class="croc-role">'+esc(a.role)+'</div>'+
|
|
772
1259
|
'<div class="croc-task">'+(a.currentTask?esc(a.currentTask):'')+'</div>'+
|
|
773
1260
|
'<div class="progress-bar"><div class="fill" style="width:'+prog+'%"></div></div>'+
|
|
774
|
-
'<div class="desk-
|
|
1261
|
+
'<div class="desk-icon">'+roleIcon+'</div></div>';
|
|
775
1262
|
}).join('');
|
|
776
1263
|
}
|
|
777
1264
|
function renderPixelOffice(){
|
|
@@ -790,15 +1277,70 @@ function renderPixelOffice(){
|
|
|
790
1277
|
document.getElementById('kpi-errors').textContent=String(errors);
|
|
791
1278
|
document.getElementById('kpi-done').textContent=String(done);
|
|
792
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();
|
|
1284
|
+
|
|
793
1285
|
el.innerHTML=S.agents.map((a,i)=>{
|
|
794
1286
|
const p=presets[i%presets.length];
|
|
795
1287
|
const x=Math.max(16,Math.round(stageWidth*p.x));
|
|
796
1288
|
const y=Math.max(20,Math.round(stageHeight*p.y));
|
|
797
1289
|
const roleSprite=roles[i%roles.length];
|
|
798
1290
|
const labelY=Math.max(16,y-12);
|
|
799
|
-
|
|
800
|
-
|
|
1291
|
+
const statusClass=(['working','testing'].includes(a.status)?'working':
|
|
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>';
|
|
801
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();
|
|
1312
|
+
}
|
|
1313
|
+
function scheduleBubbles(){
|
|
1314
|
+
S.agents.forEach((a,i)=>{
|
|
1315
|
+
if(S.bubbleTimers.has(a.name)) return;
|
|
1316
|
+
const texts=BUBBLE_TEXTS[a.status]||BUBBLE_TEXTS.idle;
|
|
1317
|
+
const show=()=>{
|
|
1318
|
+
const el=document.getElementById('pixel-agent-layer');
|
|
1319
|
+
if(!el||S.currentView!=='office') return;
|
|
1320
|
+
const presets=[
|
|
1321
|
+
{x:.18,y:.64},{x:.3,y:.66},{x:.42,y:.62},{x:.56,y:.6},{x:.72,y:.63},{x:.82,y:.52},
|
|
1322
|
+
{x:.22,y:.45},{x:.48,y:.42},{x:.66,y:.4},{x:.78,y:.72}
|
|
1323
|
+
];
|
|
1324
|
+
const stageW=(document.getElementById('pixel-view').clientWidth||900)-80;
|
|
1325
|
+
const stageH=(document.getElementById('pixel-view').clientHeight||520)-120;
|
|
1326
|
+
const p=presets[i%presets.length];
|
|
1327
|
+
const x=Math.max(16,Math.round(stageW*p.x));
|
|
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);}
|
|
1343
|
+
}
|
|
802
1344
|
}
|
|
803
1345
|
function setView(view){
|
|
804
1346
|
S.currentView=view;
|
|
@@ -841,7 +1383,7 @@ document.querySelectorAll('.panel-tabs .tab').forEach(tab=>{
|
|
|
841
1383
|
// File list rendering
|
|
842
1384
|
function renderFileList(){
|
|
843
1385
|
const el=document.getElementById('file-list');
|
|
844
|
-
if(!S.generatedFiles.length){el.innerHTML='<div style="padding:12px;color
|
|
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;}
|
|
845
1387
|
el.innerHTML=S.generatedFiles.map((f,i)=>
|
|
846
1388
|
'<div class="file-item" data-idx="'+i+'"><div class="fname">'+esc(f.filePath.split('/').pop()||f.filePath)+'</div>'+
|
|
847
1389
|
'<div class="fmeta">'+esc(f.module)+' / '+esc(f.chain)+' โ '+f.lines+' lines</div></div>'
|
|
@@ -870,12 +1412,12 @@ document.addEventListener('keydown',e=>{
|
|
|
870
1412
|
// Test Results rendering
|
|
871
1413
|
function renderResults(){
|
|
872
1414
|
const el=document.getElementById('results-panel');
|
|
873
|
-
if(!S.testMetrics&&!S.testQuality){el.innerHTML='<div style="padding:12px;color
|
|
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;}
|
|
874
1416
|
const q=S.testQuality;
|
|
875
1417
|
if(!S.testMetrics&&q){
|
|
876
1418
|
el.innerHTML='<div style="padding:10px">'
|
|
877
1419
|
+'<div style="font-size:13px;font-weight:bold;margin-bottom:8px">๐งช Test Execution Results</div>'
|
|
878
|
-
+'<div style="padding:10px;border-radius:
|
|
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>'
|
|
879
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>'
|
|
880
1422
|
+'<div style="font-size:10px;color:var(--text-dim)">Auth: '+q.authStatus+' | Backend: '+q.backendStatus+'</div>'
|
|
881
1423
|
+'<div style="font-size:10px;color:var(--text-dim);margin-top:4px">Reasons: '+(q.reasons&&q.reasons.length?q.reasons.join(', '):'-')+'</div>'
|
|
@@ -888,15 +1430,15 @@ function renderResults(){
|
|
|
888
1430
|
let qualityHtml='';
|
|
889
1431
|
if(q){
|
|
890
1432
|
const gateColor=q.level==='fail'?'var(--red)':q.level==='warn'?'var(--orange)':'var(--accent)';
|
|
891
|
-
qualityHtml='<div style="margin-top:12px;padding-top:10px;border-top:1px solid
|
|
1433
|
+
qualityHtml='<div style="margin-top:12px;padding-top:10px;border-top:1px solid var(--border)">'
|
|
892
1434
|
+'<div style="font-size:12px;font-weight:bold;margin-bottom:8px">๐งญ Execution Quality</div>'
|
|
893
|
-
+'<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;font-size:
|
|
894
|
-
+'<div style="background
|
|
895
|
-
+'<div style="background
|
|
896
|
-
+'<div style="background
|
|
897
|
-
+'<div style="background
|
|
898
|
-
+'<div style="background
|
|
899
|
-
+'<div style="background
|
|
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>'
|
|
900
1442
|
+'</div>'
|
|
901
1443
|
+'<div style="font-size:10px;color:var(--text-dim);margin-top:6px">Reasons: '+(q.reasons&&q.reasons.length?q.reasons.join(', '):'-')+'</div>'
|
|
902
1444
|
+'</div>';
|
|
@@ -904,12 +1446,12 @@ function renderResults(){
|
|
|
904
1446
|
el.innerHTML='<div style="padding:10px">'
|
|
905
1447
|
+'<div style="font-size:13px;font-weight:bold;margin-bottom:8px">๐งช Test Execution Results</div>'
|
|
906
1448
|
+'<div style="display:flex;gap:12px;margin-bottom:10px">'
|
|
907
|
-
+'<div style="flex:1;text-align:center;background
|
|
908
|
-
+'<div style="flex:1;text-align:center;background
|
|
909
|
-
+'<div style="flex:1;text-align:center;background
|
|
910
|
-
+'<div style="flex:1;text-align:center;background
|
|
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>'
|
|
911
1453
|
+'</div>'
|
|
912
|
-
+'<div style="background
|
|
1454
|
+
+'<div style="background:var(--bg-hover);border-radius:4px;height:8px;overflow:hidden">'
|
|
913
1455
|
+'<div style="height:100%;width:'+passRate+'%;background:'+barColor+';transition:width .5s"></div></div>'
|
|
914
1456
|
+'<div style="text-align:center;font-size:10px;color:var(--text-dim);margin-top:4px">Pass Rate: '+passRate+'% ('+total+' total)</div>'
|
|
915
1457
|
+qualityHtml
|
|
@@ -919,7 +1461,7 @@ function renderResults(){
|
|
|
919
1461
|
// Reports rendering
|
|
920
1462
|
function renderReports(){
|
|
921
1463
|
const el=document.getElementById('reports-panel');
|
|
922
|
-
if(!S.reports.length){el.innerHTML='<div style="padding:12px;color
|
|
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;}
|
|
923
1465
|
el.innerHTML='<div style="padding:10px"><div style="font-size:13px;font-weight:bold;margin-bottom:8px">๐ Generated Reports</div>'
|
|
924
1466
|
+S.reports.map(r=>{
|
|
925
1467
|
const icon=r.format==='html'?'๐':r.format==='json'?'๐':'๐';
|
|
@@ -947,8 +1489,80 @@ function renderReports(){
|
|
|
947
1489
|
});
|
|
948
1490
|
}
|
|
949
1491
|
|
|
950
|
-
|
|
951
|
-
|
|
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
|
+
}
|
|
1501
|
+
document.getElementById('theme-toggle').addEventListener('click',toggleTheme);
|
|
1502
|
+
|
|
1503
|
+
// Restore saved theme
|
|
1504
|
+
(function initTheme(){
|
|
1505
|
+
const saved=localStorage.getItem('opencroc-theme');
|
|
1506
|
+
if(saved&&saved==='light'){S.theme='light';document.documentElement.setAttribute('data-theme','light');
|
|
1507
|
+
document.getElementById('theme-icon-dark').style.display='none';
|
|
1508
|
+
document.getElementById('theme-icon-light').style.display='';
|
|
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
|
+
})();
|
|
952
1566
|
</script>
|
|
953
1567
|
</body>
|
|
954
1568
|
</html>
|