opencroc 1.8.0 → 1.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/cli/index.js +1107 -49
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/index.d.ts +128 -1
  4. package/dist/index.js +548 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/web/dist/assets/main-Ccg3eDNK.js +1 -0
  7. package/dist/web/dist/assets/office-runtime-B3iNctxE.css +1 -0
  8. package/dist/web/dist/assets/office-runtime-BsCh82Pj.js +183 -0
  9. package/dist/web/dist/assets/pixel-page-3BYGm7dH.js +470 -0
  10. package/dist/web/dist/assets/react-vendor-C8RhVn0h.js +49 -0
  11. package/dist/web/dist/assets/studio-page-BInoyoV2.css +1 -0
  12. package/dist/web/dist/assets/studio-page-o3SCvE_v.js +351 -0
  13. package/dist/web/dist/assets/three-addons-BdrPp04O.js +470 -0
  14. package/dist/web/dist/assets/three-core-CsxM1PCY.js +4057 -0
  15. package/dist/web/dist/index.html +15 -0
  16. package/dist/web/index.html +11 -572
  17. package/dist/web/public/botreview/char_0.png +0 -0
  18. package/dist/web/public/botreview/char_1.png +0 -0
  19. package/dist/web/public/botreview/char_2.png +0 -0
  20. package/dist/web/public/botreview/coffee-machine.gif +0 -0
  21. package/dist/web/public/botreview/server.gif +0 -0
  22. package/dist/web/public/botreview/walls.png +0 -0
  23. package/dist/web/public/star/desk-v3.webp +0 -0
  24. package/dist/web/public/star/office_bg_small.webp +0 -0
  25. package/dist/web/public/star/star-idle-v5.png +0 -0
  26. package/dist/web/public/star/star-working-spritesheet-grid.webp +0 -0
  27. package/dist/web/src/app/AppLayout.tsx +34 -0
  28. package/dist/web/src/app/AppRouter.tsx +46 -0
  29. package/dist/web/src/app/bootstrap.tsx +22 -0
  30. package/dist/web/src/app/routes.tsx +52 -0
  31. package/dist/web/src/features/office/runtime/index.ts +1 -0
  32. package/dist/web/src/features/office/runtime/mount.ts +809 -0
  33. package/dist/web/src/features/pixel/runtime/index.ts +1 -0
  34. package/dist/web/src/features/pixel/runtime/mount.ts +728 -0
  35. package/dist/web/src/features/studio/runtime/index.ts +1 -0
  36. package/dist/web/src/features/studio/runtime/mount.ts +664 -0
  37. package/dist/web/src/features/three/engine/index.ts +1 -0
  38. package/dist/web/src/main.tsx +7 -0
  39. package/dist/web/src/pages/office/index.ts +1 -0
  40. package/dist/web/src/pages/office/page.tsx +283 -0
  41. package/dist/web/src/pages/pixel/index.ts +1 -0
  42. package/dist/web/src/pages/pixel/page.tsx +564 -0
  43. package/dist/web/src/pages/studio/index.ts +1 -0
  44. package/dist/web/src/pages/studio/page.tsx +446 -0
  45. package/dist/web/{js/agents.js → src/runtime/agents.ts} +304 -31
  46. package/dist/web/{js/camera.js → src/runtime/camera.ts} +12 -5
  47. package/dist/web/{js/dataviz.js → src/runtime/dataviz.ts} +38 -14
  48. package/dist/web/{js/effects.js → src/runtime/effects.ts} +139 -2
  49. package/dist/web/{js/engine.js → src/runtime/engine.ts} +45 -6
  50. package/dist/web/{js/office.js → src/runtime/office.ts} +136 -20
  51. package/dist/web/{js/ui.js → src/runtime/ui.ts} +11 -7
  52. package/dist/web/src/shared/assets.ts +4 -0
  53. package/dist/web/src/shared/navigation.ts +47 -0
  54. package/dist/web/src/styles/app-layout.css +19 -0
  55. package/dist/web/src/styles/office.css +268 -0
  56. package/dist/web/tsconfig.json +28 -0
  57. package/dist/web/vite.config.ts +93 -0
  58. package/package.json +11 -2
  59. package/dist/web/index-studio.html +0 -804
  60. package/dist/web/index-v2-pixel.html +0 -1571
  61. /package/dist/web/{assets → dist}/botreview/char_0.png +0 -0
  62. /package/dist/web/{assets → dist}/botreview/char_1.png +0 -0
  63. /package/dist/web/{assets → dist}/botreview/char_2.png +0 -0
  64. /package/dist/web/{assets → dist}/botreview/coffee-machine.gif +0 -0
  65. /package/dist/web/{assets → dist}/botreview/server.gif +0 -0
  66. /package/dist/web/{assets → dist}/botreview/walls.png +0 -0
  67. /package/dist/web/{assets → dist}/star/desk-v3.webp +0 -0
  68. /package/dist/web/{assets → dist}/star/office_bg_small.webp +0 -0
  69. /package/dist/web/{assets → dist}/star/star-idle-v5.png +0 -0
  70. /package/dist/web/{assets → dist}/star/star-working-spritesheet-grid.webp +0 -0
  71. /package/dist/web/{js/state.js → src/runtime/state.ts} +0 -0
@@ -1,573 +1,12 @@
1
- <!DOCTYPE html>
2
- <html lang="zh-CN" data-theme="light">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width,initial-scale=1.0">
6
- <title>OpenCroc Studio · 3D</title>
7
- <style>
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
- }
49
-
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
- }
71
-
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}}
312
- </style>
313
- </head>
314
- <body>
315
-
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>
355
-
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>
361
-
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>
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>
375
-
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>
380
- </div>
381
-
382
- <!-- File Preview -->
383
- <div class="file-preview" id="file-preview">
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>
386
- </div>
387
-
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 -->
392
- <div class="shortcut-legend" id="shortcut-legend">
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>
398
- </div>
399
-
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>',
426
- };
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:['有失败...','需要修复'],
433
- };
434
- function esc(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;')}
435
-
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;
440
-
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()});
447
-
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=''}
451
-
452
- ui.setLoading(10,'Initializing 3D engine…');
453
- const canvas=document.getElementById('three-canvas');
454
- await createEngine(canvas,theme);
455
-
456
- ui.setLoading(25,'Building office…');
457
- await createOffice(theme);
458
-
459
- ui.setLoading(40,'Setting up camera…');
460
- camCtrl=new CameraController(canvas,getCamera(),getScene());
461
-
462
- ui.setLoading(50,'Creating agents…');
463
- agentMgr=new AgentManager(getScene());
464
-
465
- ui.setLoading(60,'Creating effects…');
466
- particleMgr=new ParticleManager(getScene());
467
-
468
- ui.setLoading(70,'Creating data visualization…');
469
- graphViz=new GraphViz(getScene());
470
- hologram=new HologramDisplay(getScene());
471
-
472
- ui.setLoading(80,'Connecting…');
473
- ui.init({doScan,doPipeline,doReset,doRunTests,doReports,toggleTheme,setView,openFilePreview,openReportPreview});
474
- await fetchProject();
475
-
476
- ui.setLoading(90,'Starting WebSocket…');
477
- connectWS();
478
-
479
- ui.setLoading(100,'Ready!');
480
- setTimeout(()=>document.getElementById('loading-overlay').classList.add('hidden'),400);
481
-
482
- requestAnimationFrame(renderLoop);
483
- ui.addLog('OpenCroc Studio 3D ready — press ? for shortcuts','info',true);
484
- }
485
-
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());
497
- }
498
-
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')}
505
- }
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()}}
511
-
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){}};
519
- }
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;
535
- }
536
- }
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);
544
- }
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')}
550
-
551
- /* ─── Events ───────────────────────────────────────────────────────────────── */
552
- document.getElementById('btn-scan').addEventListener('click',doScan);
553
- document.getElementById('btn-pipeline').addEventListener('click',doPipeline);
554
- document.getElementById('btn-reset').addEventListener('click',doReset);
555
- document.getElementById('btn-run-tests').addEventListener('click',doRunTests);
556
- document.getElementById('btn-reports').addEventListener('click',doReports);
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'));
560
- document.getElementById('theme-toggle').addEventListener('click',toggleTheme);
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));
571
- </script>
572
- </body>
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN" data-theme="light">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1.0">
6
+ <title>OpenCroc Studio</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
573
12
  </html>