vfven commited on
Commit
556d544
Β·
verified Β·
1 Parent(s): 5295a50

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +1177 -596
templates/index.html CHANGED
@@ -1,251 +1,601 @@
1
  <!DOCTYPE html>
2
- <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
  <title>Mission Control AI</title>
7
  <style>
8
  @import url('https://fonts.googleapis.com/css2?family=VT323&family=Share+Tech+Mono&display=swap');
9
- :root{
10
- --bg:#0a0e14;--floor:#0d1320;--wall:#101828;--wall2:#131f2e;
11
- --carpet:#080f1a;--carpet2:#0a1220;--border:#1e2d42;--border2:#2a3d58;
12
- --text:#c8d8f0;--text2:#5a7a9a;--text3:#2a3d58;
13
- --G:#00e5a0;--Gdim:#00995a;--B:#4a9eff;--Bdim:#1a5aaa;
14
- --A:#ffcc44;--Adim:#aa8800;--R:#ff4455;--P:#cc88ff;
15
- --wood:#7a5c0a;--woodl:#9a7a18;--metal:#1a2535;--metall:#243040;
16
- --shadow:0 8px 32px rgba(0,0,0,.6);
17
- }
18
- *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
19
- body{font-family:'Share Tech Mono',monospace;background:var(--bg);color:var(--text);min-height:100vh;display:flex;flex-direction:column;overflow-x:hidden}
20
- body::after{content:'';position:fixed;inset:0;background:repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(0,0,0,.06) 2px,rgba(0,0,0,.06) 4px);pointer-events:none;z-index:500}
21
-
22
- /* TOPBAR */
23
- .topbar{background:#050810;border-bottom:2px solid var(--border2);height:48px;padding:0 20px;display:flex;align-items:center;justify-content:space-between;flex-shrink:0;position:sticky;top:0;z-index:200}
24
- .tb-logo{font-family:'VT323',monospace;font-size:22px;color:var(--G);letter-spacing:3px;text-shadow:0 0 12px rgba(0,229,160,.3)}
25
- .tb-ver{font-size:10px;color:var(--text3);margin-left:4px}
26
- .tb-right{display:flex;align-items:center;gap:18px}
27
- .tb-stat{font-size:11px;color:var(--text2);display:flex;align-items:center;gap:5px}
28
- .sdot{width:7px;height:7px;border-radius:50%}
29
- .dg{background:var(--G);animation:dg 2s ease infinite}
30
- .da{background:var(--A);animation:dg 1.2s ease infinite}
31
- .dr{background:var(--R);animation:bdr .5s step-end infinite}
32
- .dd{background:var(--text3)}
33
- @keyframes dg{0%,100%{box-shadow:0 0 0 0 currentColor}50%{box-shadow:0 0 0 4px transparent}}
34
- @keyframes bdr{0%,100%{opacity:1}50%{opacity:0}}
35
- .clock{font-family:'VT323',monospace;font-size:22px;color:var(--G);letter-spacing:2px}
36
-
37
- /* PROGRESS */
38
- .prog{height:2px;background:var(--floor);overflow:hidden;display:none;flex-shrink:0}
39
- .prog.on{display:block}
40
- .prog::after{content:'';display:block;height:100%;width:30%;background:var(--G);animation:prun 1.4s ease-in-out infinite}
41
- @keyframes prun{0%{margin-left:-30%}100%{margin-left:110%}}
42
-
43
- /* MISSION BAR */
44
- .mbar{background:#050810;border-bottom:1px solid var(--border);padding:10px 20px;display:flex;gap:10px;flex-shrink:0;flex-wrap:wrap}
45
- .minput{flex:1;min-width:200px;height:36px;background:var(--floor);border:1px solid var(--border2);color:var(--text);font-family:'Share Tech Mono',monospace;font-size:12px;padding:0 12px;outline:none;transition:border-color .15s}
46
- .minput:focus{border-color:var(--G)}
47
- .minput::placeholder{color:var(--text3)}
48
- .launch{height:36px;padding:0 18px;background:transparent;border:2px solid var(--G);color:var(--G);font-family:'VT323',monospace;font-size:18px;letter-spacing:2px;cursor:pointer;transition:all .15s;display:flex;align-items:center;gap:6px}
49
- .launch:hover{background:rgba(0,229,160,.1);box-shadow:0 0 12px rgba(0,229,160,.2)}
50
- .launch:active{transform:scale(.97)}
51
- .launch:disabled{border-color:var(--text3);color:var(--text3);cursor:not-allowed}
52
- .add-btn{height:36px;padding:0 14px;background:transparent;border:1px solid var(--border2);color:var(--text2);font-family:'Share Tech Mono',monospace;font-size:11px;cursor:pointer;transition:all .15s}
53
- .add-btn:hover{border-color:var(--B);color:var(--B)}
54
- .mc{font-family:'VT323',monospace;font-size:17px;color:var(--text3)}
55
-
56
- /* MAIN */
57
- .main{display:grid;grid-template-columns:1fr 260px;flex:1;min-height:0;overflow:hidden}
58
- @media(max-width:900px){.main{grid-template-columns:1fr}.sidebar{display:none}}
59
-
60
- /* OFFICE */
61
- .office{background:var(--carpet);overflow-y:auto;padding:16px;position:relative}
62
- .office::before{content:'';position:fixed;inset:0;background-image:linear-gradient(rgba(255,255,255,.02) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.02) 1px,transparent 1px);background-size:32px 32px;pointer-events:none;z-index:0}
63
- .floor-hdr{font-family:'VT323',monospace;font-size:13px;letter-spacing:4px;color:var(--text3);margin-bottom:14px;display:flex;align-items:center;justify-content:space-between;position:relative;z-index:1}
64
- .floor-sub{font-size:10px;color:var(--Gdim);letter-spacing:2px}
65
-
66
- /* OFFICE GRID */
67
- .ogrid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;position:relative;z-index:1}
68
- @media(max-width:1100px){.ogrid{grid-template-columns:repeat(2,1fr)}}
69
- @media(max-width:700px){.ogrid{grid-template-columns:1fr}}
70
-
71
- /* ROOM */
72
- .room{background:var(--wall);border:1px solid var(--border);display:flex;flex-direction:column;min-height:200px;transition:border-color .3s,box-shadow .3s;overflow:hidden}
73
- .room.working{border-color:var(--G);box-shadow:0 0 16px rgba(0,229,160,.1)}
74
- .room.active{border-color:var(--Gdim)}
75
- .room.resting{border-color:var(--A)}
76
- .room.visiting{border-color:var(--B);box-shadow:0 0 16px rgba(74,158,255,.14);animation:vp 1s ease-in-out infinite}
77
- @keyframes vp{0%,100%{box-shadow:0 0 8px rgba(74,158,255,.08)}50%{box-shadow:0 0 20px rgba(74,158,255,.22)}}
78
-
79
- /* ROOM HEADER */
80
- .rwall{background:var(--wall2);height:28px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;padding:0 10px;flex-shrink:0}
81
- .rname{font-family:'VT323',monospace;font-size:15px;letter-spacing:2px}
82
- .rbadge{font-family:'VT323',monospace;font-size:11px;padding:1px 6px;border:1px solid;letter-spacing:1px}
83
- .bi{color:var(--text3);border-color:var(--text3)}.bw{color:var(--G);border-color:var(--G);animation:bp .9s ease infinite}.ba{color:var(--Gdim);border-color:var(--Gdim);background:rgba(0,229,160,.07)}.br{color:var(--A);border-color:var(--A)}.bv{color:var(--B);border-color:var(--B);animation:bp .6s ease infinite}
84
- @keyframes bp{0%,100%{opacity:1}50%{opacity:.4}}
85
-
86
- /* ROOM SCENE β€” compact, no overflow */
87
- .rscene{
88
- height:90px;background:var(--carpet2);position:relative;
89
- display:flex;align-items:flex-end;padding:6px 8px;gap:6px;
90
- overflow:hidden;flex-shrink:0;
91
- }
92
- .rscene::before{content:'';position:absolute;inset:0;background-image:linear-gradient(45deg,rgba(255,255,255,.01) 25%,transparent 25%),linear-gradient(-45deg,rgba(255,255,255,.01) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(255,255,255,.01) 75%),linear-gradient(-45deg,transparent 75%,rgba(255,255,255,.01) 75%);background-size:16px 16px}
93
-
94
- /* SERVER RACK β€” slim */
95
- .srack{width:30px;flex-shrink:0;align-self:flex-end;margin-bottom:3px;background:#070e18;border:1px solid #1a2a40;padding:3px 2px;display:flex;flex-direction:column;gap:2px;position:relative;z-index:2}
96
- .ru{height:7px;background:#0d1525;border:1px solid #1a2a3a;display:flex;align-items:center;gap:2px;padding:0 2px}
97
- .rl{width:3px;height:3px;border-radius:50%}
98
- .lg{background:var(--G);animation:led var(--d,1.5s) ease-in-out infinite}
99
- .la{background:var(--A);animation:led var(--d,2s) ease-in-out infinite}
100
- .lb{background:var(--B);animation:led var(--d,.9s) ease-in-out infinite}
101
- .lo{background:#0d1525;border:1px solid #1a2a3a}
102
- .rb{flex:1;height:2px;background:#1a2a3a}
103
- .rb.on{background:var(--B);animation:rba .4s ease-in-out infinite alternate}
104
- @keyframes led{0%,100%{opacity:.25}50%{opacity:1;box-shadow:0 0 4px currentColor}}
105
- @keyframes rba{0%{opacity:.3}100%{opacity:1}}
106
-
107
- /* DESK AREA β€” fixed width */
108
- .deskarea{width:88px;flex-shrink:0;display:flex;flex-direction:column;gap:2px;position:relative;z-index:2}
109
-
110
- /* Agent + chair sit next to desk on the left */
111
- .agent-seat{
112
- display:flex;flex-direction:column;align-items:center;
113
- flex-shrink:0;position:relative;z-index:3;
114
- gap:0;
115
- }
116
-
117
- /* Monitor β€” small and clean */
118
- .monitor{
119
- width:88px;height:38px;background:#030608;
120
- border:1px solid var(--metall);display:flex;flex-direction:column;
121
- position:relative;overflow:hidden;transition:border-color .3s;
122
- }
123
- .monitor.thinking{border-color:var(--B)}
124
- .monitor.done{border-color:var(--Gdim)}
125
- .monitor.away{border-color:var(--Adim)}
126
- .mscreen{flex:1;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden}
127
-
128
- /* Thinking dots */
129
- .tdots{display:flex;gap:3px;align-items:center}
130
- .td{width:4px;height:4px;border-radius:50%;background:var(--B);animation:tdb .8s ease-in-out infinite}
131
- .td:nth-child(2){animation-delay:.15s}.td:nth-child(3){animation-delay:.3s}
132
- @keyframes tdb{0%,80%,100%{transform:translateY(0);opacity:.3}40%{transform:translateY(-4px);opacity:1}}
133
-
134
- /* Active: just a blinking cursor, no lines */
135
- .scur{width:4px;height:10px;background:var(--G);animation:cur .7s step-end infinite}
136
- @keyframes cur{0%,100%{opacity:1}50%{opacity:0}}
137
-
138
- /* Idle/resting text */
139
- .mscreen-txt{font-family:'VT323',monospace;font-size:10px;letter-spacing:1px;opacity:.5}
140
-
141
- .mbase{height:4px;background:var(--metal)}
142
- .mbase::after{content:'';display:block;width:12px;height:3px;background:var(--metall);margin:0 auto;position:relative;top:-1px}
143
- .dtop{height:6px;background:var(--woodl);border-top:1px solid #c09828}
144
- .kbd{height:5px;background:#c0b090;border:1px solid #a09070;display:flex;gap:1px;padding:1px 2px;align-items:center}
145
- .kk{flex:1;height:3px;background:#a09070}
146
-
147
- /* Coffee β€” small, on desk corner */
148
- .coffee-on-desk{position:absolute;bottom:17px;right:6px;z-index:3;opacity:.8}
149
-
150
- /* OOO */
151
- .ooo{position:absolute;inset:0;background:rgba(20,14,0,.78);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;z-index:10}
152
- .ooot{font-family:'VT323',monospace;font-size:15px;color:var(--A);letter-spacing:2px}
153
- .ooos{font-size:9px;color:rgba(255,204,68,.5)}
154
-
155
- /* SPEECH BUBBLE */
156
- .bubble{position:absolute;top:5px;left:38px;background:#050810;border:1px solid var(--border2);padding:3px 7px;font-size:10px;color:var(--text);max-width:120px;line-height:1.3;z-index:20;opacity:0;transform:translateY(-4px);pointer-events:none;transition:opacity .25s,transform .25s}
157
- .bubble.show{opacity:1;transform:translateY(0)}
158
- .bubble::before{content:'';position:absolute;left:-5px;top:7px;border:4px solid transparent;border-right-color:var(--border2)}
159
-
160
- /* RESULT AREA */
161
- .rresult{background:#060d18;border-top:1px solid var(--border);padding:8px 12px;flex-shrink:0;min-height:52px;flex:1}
162
- .rph{font-size:11px;color:var(--text3);font-style:italic}
163
- .rtxt{font-size:11px;color:var(--text);line-height:1.6;word-break:break-word}
164
- .rmodel{font-size:10px;color:var(--text3);margin-top:4px;display:flex;align-items:center;gap:4px}
165
- .dl-btn{display:inline-flex;align-items:center;gap:5px;margin-top:6px;padding:4px 12px;background:rgba(0,229,160,.07);border:1px solid var(--G);color:var(--G);font-size:10px;cursor:pointer;text-decoration:none;transition:all .15s;font-family:'Share Tech Mono',monospace}
166
- .dl-btn:hover{background:rgba(0,229,160,.16)}
167
-
168
- /* IMAGE PREVIEW inside result area */
169
- .img-preview-wrap{margin-top:6px;display:flex;flex-wrap:wrap;gap:6px}
170
- .img-thumb{width:72px;height:52px;object-fit:cover;border:1px solid var(--border2);cursor:pointer;transition:border-color .15s}
171
- .img-thumb:hover{border-color:var(--G)}
172
-
173
- /* IMAGE LIGHTBOX */
174
- .lightbox{position:fixed;inset:0;background:rgba(0,0,0,.88);z-index:800;display:none;align-items:center;justify-content:center;flex-direction:column;gap:12px}
175
- .lightbox.show{display:flex}
176
- .lightbox img{max-width:85vw;max-height:75vh;border:1px solid var(--border2)}
177
- .lightbox-close{font-family:'VT323',monospace;font-size:16px;color:var(--text2);cursor:pointer;letter-spacing:2px;padding:4px 14px;border:1px solid var(--border);transition:color .15s}
178
- .lightbox-close:hover{color:var(--text)}
179
- .lightbox-dl{display:inline-flex;align-items:center;gap:5px;padding:5px 14px;background:rgba(0,229,160,.08);border:1px solid var(--G);color:var(--G);font-size:11px;cursor:pointer;text-decoration:none;font-family:'Share Tech Mono',monospace}
180
-
181
- /* SERVER ROOM STRIP */
182
- .srv-strip{grid-column:1/-1;background:var(--wall2);border:1px solid var(--border);padding:8px 16px;display:flex;align-items:center;gap:14px;position:relative;overflow:hidden}
183
- .srv-strip::before{content:'SERVER ROOM';position:absolute;top:6px;left:14px;font-family:'VT323',monospace;font-size:11px;color:var(--text3);letter-spacing:3px}
184
- .sroom-racks{display:flex;gap:7px;margin-top:14px;flex-wrap:wrap}
185
- .big-rack{background:#070e18;border:1px solid #1a2a40;padding:3px;width:40px;display:flex;flex-direction:column;gap:2px}
186
- .big-unit{height:7px;background:#0d1525;border:1px solid #1a2a3a;display:flex;align-items:center;gap:2px;padding:0 2px}
187
- .srv-stats{margin-left:auto;display:flex;flex-direction:column;gap:4px;font-size:10px;color:var(--text2)}
188
- .srv-stat{display:flex;align-items:center;gap:5px}
189
-
190
- /* ORCH LOG */
191
- .orch-log{grid-column:1/-1;background:var(--wall2);border:1px solid var(--border);padding:8px 14px;display:none}
192
- .orch-log.show{display:block}
193
- .orch-title{font-family:'VT323',monospace;font-size:12px;color:var(--text2);letter-spacing:3px;margin-bottom:5px}
194
- .orch-ev{font-size:10px;color:var(--text2);display:flex;gap:8px;padding:1px 0}
195
- .orch-t{color:var(--text3);flex-shrink:0}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
197
  /* WALK OVERLAY */
198
- .walk-overlay{position:fixed;inset:0;background:rgba(5,8,16,.93);z-index:400;display:none;align-items:center;justify-content:center;flex-direction:column;gap:14px}
199
- .walk-overlay.show{display:flex}
200
- .walk-wrap{position:relative;width:min(680px,95vw);height:min(380px,55vh);background:var(--wall);border:2px solid var(--border2);overflow:hidden}
201
- .walk-lbl{font-family:'VT323',monospace;font-size:14px;color:var(--G);letter-spacing:3px;text-align:center}
202
- .walk-log{font-family:'Share Tech Mono',monospace;font-size:11px;color:var(--text2);text-align:center;height:16px}
203
-
204
- /* ADD AGENT MODAL */
205
- .modal-bg{position:fixed;inset:0;background:rgba(0,0,0,.82);z-index:300;display:none;align-items:center;justify-content:center}
206
- .modal-bg.show{display:flex}
207
- .modal{background:var(--wall);border:1px solid var(--border2);padding:22px;width:370px;box-shadow:var(--shadow)}
208
- .modal-t{font-family:'VT323',monospace;font-size:20px;color:var(--G);letter-spacing:2px;margin-bottom:14px}
209
- .field{margin-bottom:10px}
210
- .field label{display:block;font-size:10px;color:var(--text2);letter-spacing:2px;margin-bottom:3px;text-transform:uppercase}
211
- .field input,.field select,.field textarea{width:100%;background:var(--floor);border:1px solid var(--border2);color:var(--text);font-family:'Share Tech Mono',monospace;font-size:11px;padding:6px 10px;outline:none;transition:border-color .15s}
212
- .field input:focus,.field select:focus,.field textarea:focus{border-color:var(--G)}
213
- .field textarea{resize:vertical;min-height:52px}
214
- .field select option{background:var(--floor)}
215
- .modal-acts{display:flex;gap:8px;justify-content:flex-end;margin-top:12px}
216
- .mbtn{padding:6px 16px;font-family:'Share Tech Mono',monospace;font-size:11px;cursor:pointer;border:1px solid;transition:all .15s;background:transparent}
217
- .mbtn-ok{border-color:var(--G);color:var(--G)}.mbtn-ok:hover{background:rgba(0,229,160,.1)}
218
- .mbtn-no{border-color:var(--border2);color:var(--text2)}.mbtn-no:hover{border-color:var(--text2)}
 
219
 
220
  /* NOTIFICATION */
221
- .notif{position:fixed;top:56px;right:16px;background:var(--wall);border:1px solid var(--border);border-left:3px solid var(--G);padding:10px 16px;max-width:270px;z-index:600;transform:translateX(290px);transition:transform .3s ease;box-shadow:var(--shadow);font-size:12px}
222
- .notif.show{transform:translateX(0)}
223
- .notif-t{font-family:'VT323',monospace;font-size:17px;letter-spacing:1px}
224
- .notif-m{color:var(--text2);margin-top:2px;line-height:1.4;font-size:11px}
 
 
 
 
 
 
225
 
226
  /* SIDEBAR */
227
- .sidebar{background:#050810;border-left:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden}
228
- .sb-sec{border-bottom:1px solid var(--border);flex-shrink:0}
229
- .sb-t{font-family:'VT323',monospace;font-size:13px;letter-spacing:3px;color:var(--text2);padding:8px 14px 7px;border-bottom:1px solid var(--border)}
230
- .act-scroll{flex:1;overflow-y:auto;max-height:260px}
231
- .act-item{padding:6px 14px;border-bottom:1px solid rgba(30,45,66,.4);display:flex;gap:7px}
232
- .act-dot{width:5px;height:5px;border-radius:50%;margin-top:4px;flex-shrink:0}
233
- .act-msg{font-size:11px;color:var(--text);line-height:1.4}
234
- .act-time{font-size:10px;color:var(--text3);margin-top:1px}
235
- .hist-scroll{overflow-y:auto;max-height:200px}
236
- .hist-item{padding:8px 14px;border-bottom:1px solid rgba(30,45,66,.4);cursor:pointer;transition:background .1s}
237
- .hist-item:hover{background:var(--wall)}
238
- .hist-task{font-size:11px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:500}
239
- .hist-meta{font-size:10px;color:var(--text3);margin-top:2px;display:flex;gap:8px}
240
- ::-webkit-scrollbar{width:4px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}
241
- @keyframes arm-type{0%{transform:translateY(0)rotate(0)}100%{transform:translateY(2px)rotate(-5deg)}}
 
242
  </style>
243
  </head>
244
  <body>
 
 
245
  <header class="topbar">
246
  <div style="display:flex;align-items:center;gap:8px">
247
  <span class="tb-logo">MISSION CONTROL AI</span>
248
- <span class="tb-ver">v5</span>
249
  </div>
250
  <div class="tb-right">
251
  <div class="tb-stat"><div class="sdot dg"></div><span id="top-active">0 active</span></div>
@@ -253,51 +603,123 @@ body::after{content:'';position:fixed;inset:0;background:repeating-linear-gradie
253
  <div class="clock" id="clock">00:00:00</div>
254
  </div>
255
  </header>
 
256
  <div class="prog" id="prog"></div>
 
 
257
  <div class="mbar">
258
- <input class="minput" id="task-input" placeholder="Mission... (e.g. 'cat image', 'excel template for school', 'python hello world with HTML')" autocomplete="off"/>
259
  <button class="launch" id="launch-btn" onclick="launchMission()">&#9658; LAUNCH</button>
260
  <button class="add-btn" onclick="openModal()">+ Agent</button>
261
- <span class="mc" id="mc-count">MISSIONS: 0</span>
262
  </div>
 
 
263
  <div class="main">
264
- <div class="office">
265
- <div class="floor-hdr">
266
- <span style="font-family:'VT323',monospace;font-size:13px;letter-spacing:4px">&#9632; OPERATIONS FLOOR</span>
267
- <span class="floor-sub" id="floor-sub">WAITING FOR MISSION</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  </div>
269
- <div class="ogrid" id="ogrid"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  </div>
 
 
271
  <aside class="sidebar">
272
  <div class="sb-sec"><div class="sb-t">ACTIVITY</div><div class="act-scroll" id="act-log"></div></div>
273
- <div class="sb-sec"><div class="sb-t">MISSIONS</div><div class="hist-scroll" id="hist-log"><div style="padding:14px;font-size:11px;color:var(--text3);text-align:center">No missions yet</div></div></div>
 
 
 
 
274
  </aside>
275
  </div>
276
 
277
  <!-- WALK OVERLAY -->
278
- <div class="walk-overlay" id="walk-overlay">
279
- <div class="walk-lbl">MANAGER BRIEFING TEAM</div>
280
  <div class="walk-wrap"><canvas id="walk-canvas"></canvas></div>
281
- <div class="walk-log" id="walk-log">Planning...</div>
282
  </div>
283
 
284
  <!-- LIGHTBOX -->
285
- <div class="lightbox" id="lightbox" onclick="if(event.target===this)closeLightbox()">
286
- <img id="lightbox-img" src="" alt=""/>
287
- <div style="display:flex;gap:10px;align-items:center">
288
- <a class="lightbox-dl" id="lightbox-dl" href="#" download>&#8595; Download image</a>
289
- <div class="lightbox-close" onclick="closeLightbox()">&#10005; CLOSE</div>
290
  </div>
291
  </div>
292
 
293
  <!-- ADD AGENT MODAL -->
294
  <div class="modal-bg" id="modal-bg" onclick="if(event.target===this)closeModal()">
295
  <div class="modal">
296
- <div class="modal-t">+ NEW AGENT</div>
297
- <div class="field"><label>Key (unique ID)</label><input id="m-key" placeholder="e.g. researcher"/></div>
298
- <div class="field"><label>Display Name</label><input id="m-name" placeholder="e.g. Researcher"/></div>
299
  <div class="field"><label>Role</label><textarea id="m-role" placeholder="Especialista en..."></textarea></div>
300
- <div class="field"><label>Provider</label><select id="m-prov"><option value="openrouter">OpenRouter (free)</option><option value="gemini">Google Gemini</option><option value="groq">Groq</option></select></div>
 
 
 
 
 
 
301
  <div class="modal-acts">
302
  <button class="mbtn mbtn-no" onclick="closeModal()">Cancel</button>
303
  <button class="mbtn mbtn-ok" onclick="submitAgent()">Add Agent</button>
@@ -307,221 +729,305 @@ body::after{content:'';position:fixed;inset:0;background:repeating-linear-gradie
307
 
308
  <!-- NOTIFICATION -->
309
  <div class="notif" id="notif">
310
- <div class="notif-t" id="notif-t">Mission Complete</div>
311
  <div class="notif-m" id="notif-m"></div>
312
  </div>
313
 
314
  <script>
315
- const PROV_MODELS={openrouter:['meta-llama/llama-3.3-70b-instruct:free','qwen/qwen3-4b:free'],gemini:['gemini-2.5-flash-preview-04-17','gemini-2.0-flash'],groq:['llama-3.3-70b-versatile','gemma2-9b-it']};
316
- const COLORS={manager:'#00e5a0',backend_dev:'#4a9eff',frontend_dev:'#cc88ff',analyst:'#ffcc44',writer:'#a0d8ff',image_agent:'#ff6688',developer:'#4a9eff'};
317
- const SPEECH={manager:['Analyzing task...','Planning...','Coordinating...'],backend_dev:['Writing code...','Building backend...'],frontend_dev:['Coding HTML...','Styling UI...'],analyst:['Analyzing...','Reviewing...'],writer:['Drafting...','Writing...'],image_agent:['Searching images...','Finding visuals...']};
318
- let agentDefs=[],states={},activity=[],history=[],mCount=0,bubbleT={};
319
-
320
- async function loadAgents(){
321
- const r=await fetch('/api/agents'),d=await r.json();
322
- agentDefs=d.agents;
323
- agentDefs.forEach(a=>{if(!states[a.key])states[a.key]={status:'idle',message:'',model:''};});
324
- renderOffice();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
  }
326
 
327
- setInterval(()=>{
328
- const n=new Date();
329
- document.getElementById('clock').textContent=[n.getHours(),n.getMinutes(),n.getSeconds()].map(x=>String(x).padStart(2,'0')).join(':');
330
- },1000);
 
 
 
 
 
331
 
332
- // ── SVG BUILDERS ─────────────────────────────────────────────────────────
333
- function personSVG(color,status,w=22,h=36){
334
- const op=status==='resting'?'0.15':'1';
335
- const a=status==='working'?`style="animation:arm-type .28s ease-in-out infinite alternate;transform-origin:13px 18px"`:'';
336
- return`<svg viewBox="0 0 32 52" width="${w}" height="${h}" style="image-rendering:pixelated;display:block;opacity:${op}">
337
- <rect x="11" y="0" width="10" height="3" fill="#1a1a2e"/>
338
- <rect x="9" y="3" width="14" height="2" fill="#1a1a2e"/>
339
- <rect x="9" y="3" width="2" height="5" fill="#1a1a2e"/>
340
- <rect x="21" y="3" width="2" height="5" fill="#1a1a2e"/>
341
- <rect x="9" y="5" width="14" height="12" fill="#f0c8a0"/>
342
- <rect x="7" y="8" width="2" height="4" fill="#f0c8a0"/>
343
- <rect x="23" y="8" width="2" height="4" fill="#f0c8a0"/>
344
- <rect x="11" y="9" width="4" height="3" fill="${color}" opacity=".9"/>
345
- <rect x="17" y="9" width="4" height="3" fill="${color}" opacity=".9"/>
346
- <rect x="12" y="10" width="2" height="2" fill="#111"/>
347
- <rect x="18" y="10" width="2" height="2" fill="#111"/>
348
- <rect x="15" y="10" width="2" height="1" fill="${color}"/>
349
- <rect x="13" y="14" width="6" height="1" fill="#c88060" opacity=".5"/>
350
- <rect x="9" y="17" width="14" height="11" fill="${color}" opacity=".85"/>
351
- <rect x="11" y="17" width="10" height="3" fill="${color}"/>
352
- <g ${a}>
353
- <rect x="4" y="18" width="5" height="8" fill="${color}" opacity=".85"/>
354
- <rect x="23" y="18" width="5" height="8" fill="${color}" opacity=".85"/>
355
- <rect x="3" y="25" width="5" height="4" fill="#f0c8a0"/>
356
- <rect x="24" y="25" width="5" height="4" fill="#f0c8a0"/>
357
- </g>
358
- <rect x="9" y="28" width="14" height="12" fill="#1e2535"/>
359
- <rect x="13" y="40" width="5" height="7" fill="#1e2535"/>
360
- <rect x="9" y="40" width="5" height="7" fill="#1e2535"/>
361
- <rect x="7" y="47" width="7" height="4" fill="#111"/>
362
- <rect x="18" y="47" width="7" height="4" fill="#111"/>
363
- </svg>`;
364
- }
365
-
366
- function chairSVG(empty,w=22,h=26){
367
- const c=empty?'#0a0f18':'#182030', s=empty?'#060b12':'#0d1a28';
368
- return`<svg viewBox="0 0 26 30" width="${w}" height="${h}" style="image-rendering:pixelated;display:block">
369
- <rect x="3" y="3" width="20" height="13" fill="${c}" rx="1"/>
370
- <rect x="4" y="4" width="18" height="11" fill="${s}"/>
371
- <rect x="3" y="16" width="20" height="4" fill="${c}"/>
372
- <rect x="3" y="0" width="4" height="16" fill="${c}"/>
373
- <rect x="5" y="20" width="3" height="8" fill="${c}"/>
374
- <rect x="18" y="20" width="3" height="8" fill="${c}"/>
375
- <rect x="3" y="27" width="5" height="3" fill="#050a10"/>
376
- <rect x="18" y="27" width="5" height="3" fill="#050a10"/>
377
- </svg>`;
378
- }
379
-
380
- function coffeeSVG(){
381
- return`<svg viewBox="0 0 12 12" width="11" height="11" style="image-rendering:pixelated;display:block">
382
- <rect x="2" y="4" width="6" height="6" fill="#5c3a1e"/>
383
- <rect x="2" y="4" width="6" height="2" fill="#7a4e2a"/>
384
- <rect x="8" y="5" width="2" height="3" fill="#5c3a1e"/>
385
- <rect x="1" y="10" width="8" height="2" fill="#4a2e14"/>
386
- <rect x="4" y="2" width="1" height="2" fill="#555" opacity=".5"/>
387
- </svg>`;
388
- }
389
-
390
- function sRack(){
391
- const us=[['lg','lb','1.1s','.7s',true],['la','lb','2.3s','.9s',true],['lg','lo','1.8s',null,false],['lb','lg','.7s','2.1s',true],['la','la','1.4s','1.9s',false]];
392
- return us.map(([l1,l2,d1,d2,a])=>`<div class="ru"><div class="rl ${l1}" style="--d:${d1}"></div><div class="rl ${l2}" ${d2?`style="--d:${d2}"`:''} ></div><div class="rb ${a?'on':''}"></div></div>`).join('');
393
- }
394
-
395
- function screenContent(status){
396
- if(status==='working') return`<div class="tdots"><div class="td"></div><div class="td"></div><div class="td"></div></div>`;
397
- if(status==='active') return`<div class="scur"></div>`;
398
- if(status==='resting') return`<div class="mscreen-txt" style="color:var(--Adim)">AWAY</div>`;
399
- return`<div class="mscreen-txt" style="color:var(--text3)">IDLE</div>`;
400
- }
401
-
402
- // ── BUILD ROOM ─────────────────────────────────────────────────────────────
403
- function buildRoom(agent){
404
- const st=states[agent.key]||{status:'idle',message:'',model:''};
405
- const status=st.status, color=COLORS[agent.key]||'#00e5a0', empty=status==='resting';
406
- const badge={idle:'IDLE',working:'PROCESSING',active:'DONE',resting:'AWAY',visiting:'BRIEFING',error:'ERROR'};
407
- const bcls={idle:'bi',working:'bw',active:'ba',resting:'br',visiting:'bv',error:'br'};
408
- const rcls={idle:'idle',working:'working',active:'active',resting:'resting',visiting:'visiting',error:'resting'};
409
- const mcls=status==='working'?'thinking':status==='active'?'done':status==='resting'?'away':'';
410
-
411
- // Result HTML β€” handles images specially
412
- let resultHTML='';
413
- if(status==='idle'){
414
- resultHTML=`<div class="rph">Waiting for mission...</div>`;
415
- } else if(status==='working'||status==='visiting'){
416
- resultHTML=`<div class="rph" style="color:var(--B)">Processing...</div>`;
417
- } else if(status==='resting'){
418
- resultHTML=`<div class="rph" style="color:var(--A)">Rate limited β€” back soon</div>`;
419
- } else {
420
- // Build download buttons per agent
421
- let downloads='';
422
- if(st.doc_file){
423
- const label = st.doc_file.endsWith('.xlsx') ? '&#8595; Download .xlsx'
424
- : st.doc_file.endsWith('.docx') ? '&#8595; Download .docx'
425
- : st.doc_file.endsWith('.html') ? '&#8595; Download .html'
426
- : st.doc_file.endsWith('.py') ? '&#8595; Download .py'
427
- : st.doc_file.endsWith('.groovy')? '&#8595; Download .groovy'
428
- : st.doc_file.endsWith('.jpg') ? null // images shown as thumbs
429
- : '&#8595; Download file';
430
- if(label) downloads += `<a class="dl-btn" href="/api/docs/${st.doc_file}" download>${label}</a>`;
431
- }
432
 
433
- // Image thumbnails for image_agent
434
- let imgThumbs='';
435
- if(agent.key==='image_agent' && st.images && st.images.length){
436
- const thumbs=st.images.map((src,i)=>
437
- `<img class="img-thumb" src="${src}" onclick="openLightbox('${src}','img_${i}.jpg')" title="Click to enlarge"/>`
438
- ).join('');
439
- imgThumbs=`<div class="img-preview-wrap">${thumbs}</div>`;
440
- imgThumbs+=`<div style="font-size:10px;color:var(--text3);margin-top:4px">${st.images.length} image(s) β€” click to enlarge & download</div>`;
441
- }
 
 
 
442
 
443
- resultHTML=`<div class="rtxt" id="result-${agent.key}"></div>
444
- ${st.model?`<div class="rmodel"><div class="sdot dg" style="width:5px;height:5px;margin:0"></div>${st.model}</div>`:''}
445
- ${imgThumbs}
446
- ${downloads}`;
 
 
 
 
 
 
447
  }
 
 
448
 
449
- return`<div class="room ${rcls[status]||'idle'}" id="room-${agent.key}">
450
- <div class="rwall">
451
- <span class="rname" style="color:${color}">${agent.name}</span>
452
- <span class="rbadge ${bcls[status]||'bi'}">${badge[status]||'IDLE'}</span>
453
- </div>
454
- <div class="rscene" id="scene-${agent.key}">
455
- <div class="srack">${sRack()}</div>
456
- <!-- agent seat: person + chair, left side -->
457
- <div class="agent-seat">
458
- ${!empty ? personSVG(color,status) : ''}
459
- ${chairSVG(empty)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  </div>
461
- <!-- desk + monitor, right side -->
462
- <div class="deskarea">
463
- <div class="monitor ${mcls}">
464
- <div class="mscreen">${screenContent(status)}</div>
465
- <div class="mbase"></div>
466
- </div>
467
- <div class="dtop"></div>
468
- <div class="kbd"><div class="kk"></div><div class="kk"></div><div class="kk"></div><div class="kk"></div></div>
469
  </div>
470
- <!-- coffee on desk -->
471
- <div class="coffee-on-desk">${coffeeSVG()}</div>
472
- ${empty?`<div class="ooo"><div class="ooot">&#9749; AWAY</div><div class="ooos">Rate limited</div></div>`:''}
473
- <div class="bubble" id="bubble-${agent.key}"></div>
474
- </div>
475
- <div class="rresult" id="result-area-${agent.key}">${resultHTML}</div>
476
- </div>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
  }
478
 
479
- function buildSrvRoom(){
480
- const racks=Array.from({length:6},()=>`<div class="big-rack">${[['lg','.9s'],['lb','1.3s'],['la','2s'],['lg','1.6s'],['lb','.6s']].map(([c,d])=>`<div class="big-unit"><div class="rl ${c}" style="--d:${d}"></div><div class="rb on"></div></div>`).join('')}</div>`).join('');
481
- return`<div class="srv-strip"><div class="sroom-racks">${racks}</div><div class="srv-stats"><div class="srv-stat"><div class="sdot dg"></div><span>FastAPI OK</span></div><div class="srv-stat"><div class="sdot dg"></div><span>Gemini</span></div><div class="srv-stat"><div class="sdot dg"></div><span>OpenRouter</span></div><div class="srv-stat"><div class="sdot dg"></div><span>Groq backup</span></div></div></div>`;
482
  }
483
 
484
- function renderOffice(orchEvents){
485
- const grid=document.getElementById('ogrid');
486
- let html=agentDefs.map(buildRoom).join('')+buildSrvRoom();
487
- if(orchEvents&&orchEvents.length){
488
- const ev=orchEvents.map(e=>`<div class="orch-ev"><span class="orch-t">${e.time}</span><span>${e.msg}</span></div>`).join('');
489
- html+=`<div class="orch-log show"><div class="orch-title">ORCHESTRATION LOG</div>${ev}</div>`;
 
 
 
 
490
  }
491
- grid.innerHTML=html;
492
- agentDefs.forEach(a=>{
493
- const st=states[a.key];
494
- if(st&&st.status==='active'&&st.message&&st._tw){
495
- const el=document.getElementById(`result-${a.key}`);
496
- if(el) typeWriter(el,st.message);
497
- }
498
- });
499
- document.getElementById('top-active').textContent=`${agentDefs.filter(a=>(states[a.key]||{}).status==='working').length} active`;
 
 
500
  }
501
 
502
- // LIGHTBOX
503
- function openLightbox(src, filename){
504
- document.getElementById('lightbox-img').src=src;
505
- document.getElementById('lightbox-dl').href=src;
506
- document.getElementById('lightbox-dl').download=filename;
507
- document.getElementById('lightbox').classList.add('show');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
508
  }
509
- function closeLightbox(){ document.getElementById('lightbox').classList.remove('show'); }
510
 
511
- function typeWriter(el,text,speed=14){
512
- el.textContent=''; let i=0;
513
- const iv=setInterval(()=>{if(i<text.length)el.textContent+=text[i++];else clearInterval(iv);},speed);
 
 
 
 
 
 
 
 
 
 
 
 
514
  }
515
 
516
- function showBubble(key,text,ms=2600){
517
- const b=document.getElementById(`bubble-${key}`);
518
- if(!b)return;
519
- if(bubbleT[key])clearTimeout(bubbleT[key]);
520
- b.textContent=text; b.classList.add('show');
521
- bubbleT[key]=setTimeout(()=>b.classList.remove('show'),ms);
522
  }
523
 
524
- function addAct(msg,color='#4a9eff'){
 
525
  const n=new Date();
526
  const t=[n.getHours(),n.getMinutes(),n.getSeconds()].map(x=>String(x).padStart(2,'0')).join(':');
527
  activity.unshift({msg,t,color});
@@ -530,138 +1036,211 @@ function addAct(msg,color='#4a9eff'){
530
  ).join('');
531
  }
532
 
533
- function notify(title,msg,color='#00e5a0'){
534
  document.getElementById('notif-t').textContent=title;
535
  document.getElementById('notif-t').style.color=color;
536
  document.getElementById('notif-m').textContent=msg;
537
  document.getElementById('notif').style.borderLeftColor=color;
538
- const el=document.getElementById('notif');
539
- el.classList.add('show');
540
- setTimeout(()=>el.classList.remove('show'),5500);
541
  }
542
 
543
  function updateHistory(){
544
  const list=document.getElementById('hist-log');
545
- if(!history.length){list.innerHTML=`<div style="padding:14px;font-size:11px;color:var(--text3);text-align:center">No missions yet</div>`;return;}
546
- list.innerHTML=[...history].reverse().map(m=>
547
- `<div class="hist-item"><div class="hist-task">${m.task}</div><div class="hist-meta"><span>${m.time}</span><span style="color:${m.ok?'var(--G)':'var(--A)'}">${m.ok?'Done':'Partial'}</span>${m.doc_file?`<span style="color:var(--P)">file</span>`:''}</div></div>`
548
- ).join('');
549
  }
550
 
551
- // ── WALK ANIMATION ─────────────────────────────────────────────────────────
552
  function runWalkAnimation(task, delegates, onDone){
553
- const overlay=document.getElementById('walk-overlay');
554
- const canvas=document.getElementById('walk-canvas');
555
  const logEl=document.getElementById('walk-log');
556
- const wrap=canvas.parentElement;
557
- canvas.width=wrap.clientWidth||640; canvas.height=wrap.clientHeight||360;
558
- const W=canvas.width, H=canvas.height, ctx=canvas.getContext('2d');
559
- overlay.classList.add('show');
560
-
561
- const agentColors={manager:'#00e5a0',backend_dev:'#4a9eff',frontend_dev:'#cc88ff',analyst:'#ffcc44',writer:'#a0d8ff',image_agent:'#ff6688',developer:'#4a9eff'};
562
- const rooms=agentDefs.filter(a=>a.key!=='manager').slice(0,5);
563
- const rW=Math.min(118,Math.floor((W-60)/Math.max(rooms.length,1)));
564
- const rH=120, rY=H/2-rH/2+20;
565
- const rPos=rooms.map((r,i)=>({x:30+i*(rW+12),y:rY,w:rW,h:rH,key:r.key,name:r.name,color:agentColors[r.key]||'#4a9eff'}));
566
  const delSet=new Set(delegates);
567
- const manColor='#00e5a0';
568
- let mx=W/2-8, my=6, frame=0, stepI=0, phase='walking', talkF=0;
569
- const TALK=80, SPD=3;
570
- const msgs={writer:`Write report:\n"${task.substring(0,24)}..."`,analyst:`Review the\ndocument.`,backend_dev:`Build backend:\n"${task.substring(0,22)}..."`,frontend_dev:`Build HTML:\n"${task.substring(0,22)}..."`,image_agent:`Find images:\n"${task.substring(0,24)}..."`};
571
- const steps=[
572
- {key:'__top',x:W/2-8,y:H*.12,msg:'Planning...'},
573
- ...rPos.filter(r=>delSet.has(r.key)).map(r=>({key:r.key,x:r.x+r.w/2-8,y:r.y+r.h-30,msg:msgs[r.key]||'Delegating...'})),
574
- {key:'__back',x:W/2-8,y:H*.12,msg:'Team briefed!'}
575
- ];
 
 
 
 
 
 
 
576
  const visited=new Set(); let curMsg='';
577
 
578
- function drawBg(){
579
- ctx.fillStyle='#080f1a'; ctx.fillRect(0,0,W,H);
580
- ctx.strokeStyle='rgba(255,255,255,.025)'; ctx.lineWidth=1;
581
- for(let x=0;x<W;x+=32){ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,H);ctx.stroke();}
582
- for(let y=0;y<H;y+=32){ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(W,y);ctx.stroke();}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
583
  }
584
 
585
- function drawRoom(r,vis){
586
- ctx.fillStyle=vis?'#0d1a2a':'#08101c'; ctx.strokeStyle=vis?r.color:'#1e2d42'; ctx.lineWidth=vis?1.5:1;
587
- ctx.fillRect(r.x,r.y,r.w,r.h); ctx.strokeRect(r.x,r.y,r.w,r.h);
588
- ctx.fillStyle=vis?r.color+'22':'#0c1522'; ctx.fillRect(r.x,r.y,r.w,18);
589
- ctx.fillStyle=vis?r.color:'#3a5a7a'; ctx.font=`9px "Share Tech Mono",monospace`; ctx.textAlign='center';
590
- ctx.fillText(r.name.toUpperCase(),r.x+r.w/2,r.y+13);
591
- // desk
592
- ctx.fillStyle='#7a5c0a'; ctx.fillRect(r.x+8,r.y+r.h-32,r.w-16,5);
593
- // monitor
594
- ctx.fillStyle='#030608'; ctx.strokeStyle='#1a2535'; ctx.lineWidth=1;
595
- ctx.fillRect(r.x+14,r.y+r.h-54,r.w-28,20); ctx.strokeRect(r.x+14,r.y+r.h-54,r.w-28,20);
596
- if(vis){
597
- ctx.fillStyle=r.color; ctx.globalAlpha=.5+.4*Math.sin(frame*.12);
598
- ctx.fillRect(r.x+17,r.y+r.h-50,(r.w-34)*.75,3);
599
- ctx.globalAlpha=1;
600
- }
601
- // agent figure
602
- if(!delSet.has(r.key))return;
603
- const px=r.x+12, py=r.y+r.h-28;
604
- ctx.fillStyle='#f0c8a0'; ctx.fillRect(px+3,py,7,7);
605
- ctx.fillStyle=r.color; ctx.fillRect(px,py+7,13,7);
606
- ctx.fillStyle='#1e2535'; ctx.fillRect(px+2,py+14,4,6); ctx.fillRect(px+7,py+14,4,6);
607
- // LEDs
608
- ['#00e5a0','#4a9eff','#ffcc44'].forEach((c,i)=>{
609
- ctx.beginPath(); ctx.arc(r.x+r.w-14+i*6,r.y+r.h-8,2,0,Math.PI*2);
610
- ctx.fillStyle=c; ctx.globalAlpha=.3+.7*((Math.sin(frame*.09+i*1.4)+1)/2); ctx.fill();
611
- }); ctx.globalAlpha=1;
612
- }
613
-
614
- function drawManager(x,y,walking){
615
- const bob=walking?Math.sin(frame*.45)*2:0;
616
- ctx.fillStyle='#1a1a2e'; ctx.fillRect(x+4,y,8,3);
617
- ctx.fillStyle='#f0c8a0'; ctx.fillRect(x+3,y+3,10,8);
618
- ctx.fillStyle=manColor+'bb'; ctx.fillRect(x,y+3,2,4); ctx.fillRect(x+13,y+3,2,4);
619
- ctx.fillStyle=manColor; ctx.fillRect(x+2,y+11,12,9);
620
- ctx.fillStyle='#1e2535';
621
- ctx.fillRect(x+3,y+20,5,6+bob); ctx.fillRect(x+8,y+20,5,6-bob);
622
- ctx.fillStyle='#111'; ctx.fillRect(x+2,y+26+bob,5,3); ctx.fillRect(x+9,y+26-bob,5,3);
623
  }
624
 
625
  function drawBubble(x,y,lines){
626
- const mxW=Math.max(...lines.map(l=>l.length))*6+16, bh=lines.length*13+14;
627
- const bx=Math.min(Math.max(x+14,4),W-mxW-4), by=Math.max(y-bh-6,4);
628
- ctx.fillStyle='#0d1828'; ctx.strokeStyle='#2a3d58'; ctx.lineWidth=1;
629
- ctx.fillRect(bx,by,mxW,bh); ctx.strokeRect(bx,by,mxW,bh);
630
- ctx.fillStyle='#c8d8f0'; ctx.font='10px "Share Tech Mono",monospace'; ctx.textAlign='left';
631
- lines.forEach((l,i)=>ctx.fillText(l,bx+8,by+12+i*13+2));
 
 
 
632
  }
633
 
634
  function tick(){
635
- drawBg(); rPos.forEach(r=>drawRoom(r,visited.has(r.key)));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
636
  if(stepI<steps.length){
637
  const step=steps[stepI];
638
- const dx=step.x-mx, dy=step.y-my, dist=Math.sqrt(dx*dx+dy*dy);
639
- if(dist>3&&phase==='walking'){mx+=dx/dist*SPD; my+=dy/dist*SPD;}
640
  else if(phase==='walking'){
641
- phase='talking'; talkF=0; curMsg=step.msg;
642
  if(step.key!=='__top'&&step.key!=='__back'){
643
  visited.add(step.key);
644
  logEl.textContent=`Briefing ${step.key}...`;
645
- addAct(`Manager β†’ ${step.key}`,'#4a9eff');
646
  } else if(step.key==='__back'){
647
- logEl.textContent='Team ready β€” processing...';
648
  }
649
  }
650
  if(phase==='talking'){talkF++;if(talkF>TALK){phase='walking';stepI++;curMsg='';}}
651
  } else {
652
- setTimeout(()=>{overlay.classList.remove('show');onDone();},300); return;
653
  }
654
- drawManager(Math.round(mx),Math.round(my),phase==='walking');
655
- if(phase==='talking'&&curMsg) drawBubble(Math.round(mx),Math.round(my),curMsg.split('\n'));
656
- frame++; requestAnimationFrame(tick);
657
  }
658
  tick();
659
  }
660
 
661
- // ── MODAL ──────────────────────────────────────────────────────────────────
662
- function openModal(){ document.getElementById('modal-bg').classList.add('show'); }
663
- function closeModal(){ document.getElementById('modal-bg').classList.remove('show'); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
664
 
 
 
 
 
 
 
 
665
  async function submitAgent(){
666
  const key=document.getElementById('m-key').value.trim();
667
  const name=document.getElementById('m-name').value.trim()||key;
@@ -672,8 +1251,10 @@ async function submitAgent(){
672
  const d=await r.json();
673
  if(d.success){
674
  agentDefs.push({key:d.agent.key,name:d.agent.name,role:d.agent.role});
675
- states[d.agent.key]={status:'idle',message:'',model:''};
676
- renderOffice(); addAct(`Agent "${name}" added`,'#cc88ff'); closeModal();
 
 
677
  ['m-key','m-name','m-role'].forEach(id=>document.getElementById(id).value='');
678
  }
679
  }
@@ -687,109 +1268,109 @@ async function launchMission(){
687
  document.getElementById('prog').classList.add('on');
688
  document.getElementById('top-status').textContent='Running';
689
  document.getElementById('top-dot').className='sdot da';
690
- document.getElementById('floor-sub').textContent=`MISSION: "${task.substring(0,45).toUpperCase()}${task.length>45?'...':''}"`;
 
691
 
692
- agentDefs.forEach(a=>{states[a.key]={status:'working',message:'',model:'',images:null};});
693
- renderOffice();
694
- agentDefs.forEach((a,i)=>{
695
- setTimeout(()=>{const ph=SPEECH[a.key]||['Working...'];showBubble(a.key,ph[Math.floor(Math.random()*ph.length)],2500);},i*500);
 
 
 
 
696
  });
697
- addAct(`Mission: "${task.substring(0,40)}"`,'#4a9eff');
698
 
699
- // Start API call
700
  let apiData=null;
701
- const fetchP=fetch('/api/mission',{
702
- method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({task})
703
- }).then(r=>r.json()).then(d=>{apiData=d;}).catch(e=>{apiData={error:e.message};});
704
 
705
- // Quick delegate guess for walk animation
706
  const lo=task.toLowerCase();
707
  let qd=[];
708
- if(/imagen|image|foto|gato|cat|dog|perro|picture|dibuja/.test(lo)) qd.push('image_agent');
709
  if(/informe|report|word|documento/.test(lo)){qd.push('writer');qd.push('analyst');}
710
  if(/excel|planilla|spreadsheet|registro/.test(lo)) qd.push('backend_dev');
711
  if(/python|script|groovy|jenkins|backend|api/.test(lo)) qd.push('backend_dev');
712
  if(/html|css|frontend|web|interfaz/.test(lo)) qd.push('frontend_dev');
713
- if(!qd.length) qd=['backend_dev','analyst'];
714
 
715
  runWalkAnimation(task, qd, async()=>{
 
 
 
 
 
 
 
 
716
  await fetchP;
717
 
718
  if(!apiData||apiData.error){
719
- agentDefs.forEach(a=>{states[a.key]={status:'resting',message:apiData?.error||'Error',model:''};});
720
- renderOffice();
721
- addAct(`Error: ${apiData?.error}`,'#ff4455');
722
- notify('Mission Failed',apiData?.error||'Error','#ff4455');
 
 
 
723
  document.getElementById('top-dot').className='sdot dr';
724
  document.getElementById('top-status').textContent='Error';
725
- btn.disabled=false; document.getElementById('prog').classList.remove('on');
726
- return;
727
  }
728
 
729
- let anyResting=false, hasFile=false;
730
-
731
  agentDefs.forEach(a=>{
732
  const r=apiData.results[a.key];
733
- if(!r){states[a.key]={status:'idle',message:'',model:'',images:null};return;}
734
  if(r.status==='resting') anyResting=true;
735
- const docFile=r.doc_file||null;
736
- if(docFile) hasFile=true;
737
-
738
- // For image_agent: build preview URLs using img_count + img_base
739
- let images=null;
740
- if(a.key==='image_agent' && r.img_base && r.img_count>0){
741
- images=[];
742
- for(let i=1;i<=r.img_count;i++){
743
- images.push(`/api/docs/${r.img_base}_img${i}.jpg`);
744
- }
745
- }
746
-
747
- states[a.key]={
748
- status: r.status==='resting'?'resting': r.status==='idle'?'idle':'active',
749
- message: r.message||'',
750
- model: r.model||'',
751
- doc_file: docFile,
752
- images: images,
753
- _tw: true,
754
  };
755
- });
756
-
757
- renderOffice(apiData.events);
758
-
759
- // Stagger completion bubbles
760
- agentDefs.forEach((a,i)=>{
761
- if((states[a.key]||{}).status==='active'){
762
- setTimeout(()=>{
763
- showBubble(a.key,'Done βœ“',2000);
764
- addAct(`${a.name}: complete`,'#00e5a0');
765
- },i*400);
766
  }
767
  });
768
 
769
- if(hasFile) addAct('Files ready for download','#cc88ff');
 
 
770
  mCount++;
771
  document.getElementById('mc-count').textContent=`MISSIONS: ${mCount}`;
 
772
  history.push({task,time:new Date().toLocaleTimeString(),ok:!anyResting,doc_file:apiData.doc_file});
773
  updateHistory();
 
774
 
775
  setTimeout(()=>notify(
776
- anyResting?'Mission Partial': hasFile?'Files Ready βœ“':'Mission Complete βœ“',
777
- anyResting?'Some agents rate-limited.':hasFile?'Download files below!':'All agents responded.',
778
- anyResting?'#ffcc44':hasFile?'#cc88ff':'#00e5a0'
779
- ), agentDefs.length*400+300);
780
 
781
  document.getElementById('top-status').textContent=anyResting?'Partial':'Done';
782
  document.getElementById('top-dot').className=`sdot ${anyResting?'da':'dg'}`;
783
- document.getElementById('floor-sub').textContent=anyResting?'PARTIAL RESULTS':'MISSION COMPLETE';
784
- btn.disabled=false;
785
- document.getElementById('prog').classList.remove('on');
786
  });
787
  }
788
 
789
  document.getElementById('task-input').addEventListener('keydown',e=>{if(e.key==='Enter')launchMission();});
790
- loadAgents();
791
- addAct('System online','#00e5a0');
792
- addAct('Server rack nominal','#4a9eff');
 
 
 
 
 
 
793
  </script>
794
  </body>
795
- </html>
 
1
  <!DOCTYPE html>
2
+ <html lang="es">
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
  <title>Mission Control AI</title>
7
  <style>
8
  @import url('https://fonts.googleapis.com/css2?family=VT323&family=Share+Tech+Mono&display=swap');
9
+
10
+ :root {
11
+ --wall:#f2ede0; --wall2:#e8e2d2; --floor:#d4cdb8; --floor2:#ccc5ae;
12
+ --desk:#c8a96e; --desk2:#b8996a; --desk-dark:#a07840;
13
+ --monitor-bg:#2a3545; --monitor-screen:#1a4a2a;
14
+ --skin:#f5c9a0; --hair-dark:#4a2e18; --hair-med:#7a4828;
15
+ --chair:#8a7a6a; --chair2:#6a5a4a;
16
+ --accent:#2563eb; --accent2:#1d4ed8;
17
+ --server:#1a2035; --server2:#0d1520;
18
+ --green:#22c55e; --amber:#f59e0b; --red:#ef4444; --blue:#3b82f6; --purple:#a855f7;
19
+ --text:#1a1a2a; --text2:#4a5a6a; --text3:#8a9aaa;
20
+ --shadow:0 8px 32px rgba(0,0,0,.18);
21
+ --shadow-sm:0 2px 8px rgba(0,0,0,.12);
22
+ }
23
+
24
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
25
+ html, body { height: 100%; overflow: hidden; }
26
+
27
+ body {
28
+ font-family: 'Share Tech Mono', system-ui, monospace;
29
+ background: var(--floor);
30
+ color: var(--text);
31
+ display: flex;
32
+ flex-direction: column;
33
+ user-select: none;
34
+ }
35
+
36
+ /* ── TOPBAR ── */
37
+ .topbar {
38
+ height: 44px;
39
+ background: #1e2535;
40
+ border-bottom: 2px solid #2a3550;
41
+ display: flex;
42
+ align-items: center;
43
+ justify-content: space-between;
44
+ padding: 0 16px;
45
+ flex-shrink: 0;
46
+ z-index: 100;
47
+ }
48
+ .tb-logo { font-family: 'VT323', monospace; font-size: 20px; color: #4ade80; letter-spacing: 3px; }
49
+ .tb-right { display: flex; align-items: center; gap: 14px; }
50
+ .tb-stat { font-size: 10px; color: #5a7a9a; display: flex; align-items: center; gap: 4px; }
51
+ .sdot { width: 6px; height: 6px; border-radius: 50%; }
52
+ .dg { background: #22c55e; animation: dg 2s ease infinite; }
53
+ .da { background: #f59e0b; animation: dg 1.2s ease infinite; }
54
+ .dr { background: #ef4444; animation: bdr .5s step-end infinite; }
55
+ .dd { background: #334; }
56
+ @keyframes dg { 0%,100%{box-shadow:0 0 0 0 currentColor}50%{box-shadow:0 0 0 3px transparent} }
57
+ @keyframes bdr { 0%,100%{opacity:1}50%{opacity:0} }
58
+ .clock { font-family: 'VT323', monospace; font-size: 20px; color: #4ade80; letter-spacing: 2px; }
59
+
60
+ /* ── PROGRESS ── */
61
+ .prog { height: 2px; background: #1e2535; overflow: hidden; display: none; flex-shrink: 0; }
62
+ .prog.on { display: block; }
63
+ .prog::after { content:''; display:block; height:100%; width:30%; background:#4ade80; animation:prun 1.4s ease-in-out infinite; }
64
+ @keyframes prun { 0%{margin-left:-30%}100%{margin-left:110%} }
65
+
66
+ /* ── MISSION BAR ── */
67
+ .mbar {
68
+ background: #1e2535;
69
+ border-bottom: 1px solid #2a3550;
70
+ padding: 7px 16px;
71
+ display: flex;
72
+ gap: 8px;
73
+ flex-shrink: 0;
74
+ flex-wrap: wrap;
75
+ z-index: 100;
76
+ }
77
+ .minput {
78
+ flex: 1; min-width: 180px; height: 30px;
79
+ background: #111825; border: 1px solid #2a3550;
80
+ color: #c8d8f0; font-family: 'Share Tech Mono', monospace; font-size: 11px;
81
+ padding: 0 10px; outline: none; border-radius: 2px;
82
+ transition: border-color .15s;
83
+ }
84
+ .minput:focus { border-color: #4ade80; }
85
+ .minput::placeholder { color: #2a3d58; }
86
+ .launch {
87
+ height: 30px; padding: 0 14px;
88
+ background: #22c55e; border: none; color: #0a1a0a;
89
+ font-family: 'VT323', monospace; font-size: 16px; letter-spacing: 2px;
90
+ cursor: pointer; border-radius: 2px; transition: background .15s; white-space: nowrap;
91
+ }
92
+ .launch:hover { background: #16a34a; }
93
+ .launch:disabled { background: #1a3a25; color: #2a4a35; cursor: not-allowed; }
94
+ .add-btn {
95
+ height: 30px; padding: 0 12px;
96
+ background: transparent; border: 1px solid #2a3550;
97
+ color: #5a7a9a; font-size: 10px; cursor: pointer;
98
+ border-radius: 2px; transition: all .15s; white-space: nowrap;
99
+ }
100
+ .add-btn:hover { border-color: #3b82f6; color: #3b82f6; }
101
+ .mc-count { font-family: 'VT323', monospace; font-size: 15px; color: #2a3d58; white-space: nowrap; align-self: center; }
102
+
103
+ /* ── MAIN ── */
104
+ .main { display: grid; grid-template-columns: 1fr 230px; flex: 1; min-height: 0; overflow: hidden; }
105
+ @media(max-width:780px){ .main{grid-template-columns:1fr} .sidebar{display:none} }
106
+
107
+ /* ── OFFICE SCENE ── */
108
+ .office {
109
+ position: relative;
110
+ overflow: hidden;
111
+ background: var(--floor);
112
+ }
113
+
114
+ /* WALL */
115
+ .wall {
116
+ position: absolute; top: 0; left: 0; right: 0; height: 38%;
117
+ background: linear-gradient(180deg, var(--wall) 0%, var(--wall2) 100%);
118
+ box-shadow: inset 0 -8px 20px rgba(0,0,0,.08);
119
+ }
120
+
121
+ /* Wall trim */
122
+ .wall::after {
123
+ content: '';
124
+ position: absolute; bottom: 0; left: 0; right: 0; height: 8px;
125
+ background: #c8bc9a;
126
+ box-shadow: 0 2px 6px rgba(0,0,0,.1);
127
+ }
128
+
129
+ /* FLOOR */
130
+ .floor-area {
131
+ position: absolute; bottom: 0; left: 0; right: 0; top: 38%;
132
+ background: var(--floor);
133
+ }
134
+ /* Parquet pattern */
135
+ .floor-area::before {
136
+ content: '';
137
+ position: absolute; inset: 0;
138
+ background-image:
139
+ linear-gradient(90deg, rgba(0,0,0,.04) 1px, transparent 1px),
140
+ linear-gradient(rgba(0,0,0,.04) 1px, transparent 1px);
141
+ background-size: 60px 40px;
142
+ }
143
+
144
+ /* BIG SCREEN β€” back wall */
145
+ .big-screen {
146
+ position: absolute;
147
+ left: 50%; top: 3%;
148
+ transform: translateX(-50%);
149
+ width: min(38%, 340px);
150
+ aspect-ratio: 16/9;
151
+ background: var(--monitor-bg);
152
+ border: 6px solid #8a9aaa;
153
+ border-radius: 6px;
154
+ box-shadow: var(--shadow), inset 0 0 40px rgba(0,0,0,.3);
155
+ overflow: hidden;
156
+ z-index: 5;
157
+ }
158
+ .big-screen-inner {
159
+ width: 100%; height: 100%;
160
+ background: var(--monitor-screen);
161
+ display: flex;
162
+ flex-direction: column;
163
+ align-items: center;
164
+ justify-content: center;
165
+ gap: 4px;
166
+ padding: 8px;
167
+ position: relative;
168
+ overflow: hidden;
169
+ }
170
+ .bs-title {
171
+ font-family: 'VT323', monospace;
172
+ font-size: 11px;
173
+ color: #4ade80;
174
+ letter-spacing: 3px;
175
+ text-align: center;
176
+ }
177
+ .bs-content {
178
+ font-size: 9px;
179
+ color: #a0d4b0;
180
+ text-align: center;
181
+ line-height: 1.5;
182
+ padding: 0 4px;
183
+ word-break: break-word;
184
+ max-height: 60%;
185
+ overflow: hidden;
186
+ }
187
+ .bs-scan {
188
+ position: absolute; left: 0; right: 0; height: 1px;
189
+ background: rgba(74,222,128,.3);
190
+ animation: scan-line 3s linear infinite;
191
+ }
192
+ @keyframes scan-line { 0%{top:0}100%{top:100%} }
193
+ .bs-corner {
194
+ position: absolute; width: 8px; height: 8px;
195
+ border-color: #4ade80; border-style: solid;
196
+ }
197
+ .bs-corner.tl{top:4px;left:4px;border-width:2px 0 0 2px}
198
+ .bs-corner.tr{top:4px;right:4px;border-width:2px 2px 0 0}
199
+ .bs-corner.bl{bottom:4px;left:4px;border-width:0 0 2px 2px}
200
+ .bs-corner.br{bottom:4px;right:4px;border-width:0 2px 2px 0}
201
+
202
+ /* Screen stand */
203
+ .screen-stand {
204
+ position: absolute;
205
+ left: 50%; transform: translateX(-50%);
206
+ width: 60px; height: 8px;
207
+ background: #8a9aaa;
208
+ border-radius: 0 0 4px 4px;
209
+ z-index: 4;
210
+ }
211
+ .screen-base {
212
+ position: absolute;
213
+ left: 50%; transform: translateX(-50%);
214
+ width: 100px; height: 5px;
215
+ background: #7a8a9a;
216
+ border-radius: 4px;
217
+ z-index: 4;
218
+ }
219
+
220
+ /* WINDOWS β€” back wall sides */
221
+ .window {
222
+ position: absolute; top: 3%; width: 10%; height: 28%;
223
+ background: linear-gradient(180deg, #c8e8f8 0%, #a8d4f0 60%, #e8f4ff 100%);
224
+ border: 4px solid #d8ceb8;
225
+ border-radius: 3px;
226
+ box-shadow: inset 0 0 20px rgba(200,230,255,.5);
227
+ overflow: hidden;
228
+ }
229
+ .window::before {
230
+ content: ''; position: absolute;
231
+ top: 0; left: 50%; transform: translateX(-50%);
232
+ width: 3px; height: 100%; background: rgba(216,206,184,.8);
233
+ }
234
+ .window::after {
235
+ content: ''; position: absolute;
236
+ left: 0; top: 50%; transform: translateY(-50%);
237
+ height: 3px; width: 100%; background: rgba(216,206,184,.8);
238
+ }
239
+ .window.left { left: 4%; }
240
+ .window.right { right: 4%; }
241
+
242
+ /* SERVER RACK */
243
+ .server-rack {
244
+ position: absolute;
245
+ top: 2%; right: 1%;
246
+ width: 52px;
247
+ background: var(--server);
248
+ border: 1px solid #2a3550;
249
+ border-radius: 2px;
250
+ padding: 5px 4px;
251
+ display: flex;
252
+ flex-direction: column;
253
+ gap: 2px;
254
+ box-shadow: var(--shadow-sm);
255
+ z-index: 5;
256
+ }
257
+ .rack-unit {
258
+ height: 8px;
259
+ background: var(--server2);
260
+ border: 1px solid #1a2a3a;
261
+ display: flex;
262
+ align-items: center;
263
+ gap: 2px;
264
+ padding: 0 3px;
265
+ }
266
+ .rack-led {
267
+ width: 4px; height: 4px; border-radius: 50%;
268
+ }
269
+ .rack-bar { flex: 1; height: 2px; background: #1a2a3a; }
270
+
271
+ /* DESK */
272
+ .desk {
273
+ position: absolute;
274
+ width: 130px; height: 72px;
275
+ background: var(--desk);
276
+ border-radius: 6px;
277
+ box-shadow: 0 12px 30px rgba(0,0,0,.18),
278
+ inset 0 3px 8px rgba(255,255,255,.22);
279
+ }
280
+ .desk::before {
281
+ content: '';
282
+ position: absolute; top: 0; left: 0; right: 0; height: 3px;
283
+ background: rgba(255,255,255,.28);
284
+ border-radius: 6px 6px 0 0;
285
+ }
286
+ /* Desk legs shadow */
287
+ .desk::after {
288
+ content: '';
289
+ position: absolute; bottom: -14px; left: 8px; right: 8px; height: 14px;
290
+ background: linear-gradient(180deg, rgba(0,0,0,.15), transparent);
291
+ }
292
+ .desk-monitor {
293
+ position: absolute; top: 6px; left: 10px;
294
+ width: 88px; height: 52px;
295
+ background: #2a3545;
296
+ border: 2px solid #1a2535;
297
+ border-radius: 3px;
298
+ overflow: hidden;
299
+ }
300
+ .desk-monitor-screen {
301
+ width: 100%; height: 100%;
302
+ background: #0a1a28;
303
+ display: flex; align-items: center; justify-content: center;
304
+ font-size: 7px; color: #4ade80; letter-spacing: 1px;
305
+ font-family: 'Share Tech Mono', monospace;
306
+ position: relative;
307
+ }
308
+ .desk-mug {
309
+ position: absolute; top: 10px; right: 8px;
310
+ width: 18px; height: 20px;
311
+ border-radius: 3px 3px 5px 5px;
312
+ box-shadow: inset 0 -4px 6px rgba(0,0,0,.2);
313
+ }
314
+ .desk-kbd {
315
+ position: absolute; bottom: 7px; left: 14px;
316
+ width: 70px; height: 8px;
317
+ background: #d4cdc0; border-radius: 2px;
318
+ box-shadow: inset 0 2px 4px rgba(0,0,0,.1);
319
+ }
320
+ .desk-paper {
321
+ position: absolute; bottom: 18px; right: 10px;
322
+ width: 28px; height: 20px;
323
+ background: #fafaf5;
324
+ border: 1px solid #ddd;
325
+ border-radius: 1px;
326
+ transform: rotate(-3deg);
327
+ box-shadow: 1px 1px 3px rgba(0,0,0,.1);
328
+ }
329
+
330
+ /* AGENT PERSON β€” CSS character */
331
+ .agent-person {
332
+ position: absolute;
333
+ width: 36px; height: 80px;
334
+ z-index: 20;
335
+ transition: left .8s cubic-bezier(.4,0,.2,1), bottom .8s cubic-bezier(.4,0,.2,1);
336
+ cursor: pointer;
337
+ }
338
+ .p-head {
339
+ width: 24px; height: 24px;
340
+ background: var(--skin);
341
+ border-radius: 50%;
342
+ position: absolute; top: 0; left: 6px;
343
+ box-shadow: 0 3px 8px rgba(0,0,0,.15);
344
+ }
345
+ .p-hair {
346
+ width: 26px; height: 16px;
347
+ position: absolute; top: -5px; left: 5px;
348
+ border-radius: 50% 50% 20% 20%;
349
+ }
350
+ .p-eyes {
351
+ position: absolute; top: 9px; left: 8px;
352
+ display: flex; gap: 6px;
353
+ }
354
+ .p-eye { width: 4px; height: 4px; background: #2a1a0a; border-radius: 50%; }
355
+ .p-body {
356
+ width: 28px; height: 38px;
357
+ position: absolute; top: 22px; left: 4px;
358
+ border-radius: 8px 8px 6px 6px;
359
+ box-shadow: 0 4px 12px rgba(0,0,0,.15);
360
+ }
361
+ .p-arms {
362
+ position: absolute; top: 26px; left: 0; right: 0; height: 24px;
363
+ }
364
+ .p-arm {
365
+ position: absolute; width: 8px; height: 22px;
366
+ border-radius: 4px;
367
+ top: 0;
368
+ }
369
+ .p-arm.left { left: 0; transform-origin: top center; }
370
+ .p-arm.right { right: 0; transform-origin: top center; }
371
+ .p-legs { position: absolute; top: 58px; left: 4px; right: 4px; height: 20px; display: flex; gap: 2px; }
372
+ .p-leg { flex: 1; background: #2a3545; border-radius: 2px 2px 4px 4px; height: 100%; }
373
+ .p-feet { position: absolute; top: 74px; left: 2px; right: 2px; display: flex; gap: 4px; }
374
+ .p-foot { flex: 1; height: 6px; background: #1a1a2a; border-radius: 0 0 3px 3px; }
375
+
376
+ /* Walking animation */
377
+ @keyframes walk-bob { 0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)} }
378
+ @keyframes arm-swing-l { 0%,100%{transform:rotate(-15deg)}50%{transform:rotate(15deg)} }
379
+ @keyframes arm-swing-r { 0%,100%{transform:rotate(15deg)}50%{transform:rotate(-15deg)} }
380
+ @keyframes work-type { 0%,100%{transform:rotate(-8deg)}50%{transform:rotate(8deg)} }
381
+ @keyframes breathe { 0%,100%{transform:scaleY(1)}50%{transform:scaleY(1.02)} }
382
+
383
+ .agent-person.walking { animation: walk-bob .5s ease-in-out infinite; }
384
+ .agent-person.walking .p-arm.left { animation: arm-swing-l .5s ease-in-out infinite; }
385
+ .agent-person.walking .p-arm.right { animation: arm-swing-r .5s ease-in-out infinite; }
386
+ .agent-person.working .p-arm.left { animation: work-type .3s ease-in-out infinite; }
387
+ .agent-person.working .p-arm.right { animation: work-type .3s ease-in-out infinite reverse; }
388
+ .agent-person.idle .p-body { animation: breathe 3s ease-in-out infinite; }
389
+
390
+ /* Agent name tag */
391
+ .agent-tag {
392
+ position: absolute;
393
+ bottom: -18px; left: 50%; transform: translateX(-50%);
394
+ font-size: 8px; white-space: nowrap;
395
+ font-family: 'VT323', monospace;
396
+ letter-spacing: 1px;
397
+ padding: 1px 5px;
398
+ border-radius: 2px;
399
+ opacity: .9;
400
+ }
401
+
402
+ /* Talk bubble */
403
+ .talk-bubble {
404
+ position: absolute;
405
+ bottom: 88px; left: 50%;
406
+ transform: translateX(-50%) scale(.9);
407
+ background: white; color: var(--text);
408
+ padding: 6px 10px;
409
+ border-radius: 10px;
410
+ font-size: 10px; font-family: 'Share Tech Mono', monospace;
411
+ white-space: nowrap;
412
+ box-shadow: var(--shadow-sm);
413
+ border: 1px solid #ddd;
414
+ opacity: 0;
415
+ transition: all .3s;
416
+ z-index: 30;
417
+ pointer-events: none;
418
+ max-width: 160px;
419
+ white-space: normal;
420
+ text-align: center;
421
+ line-height: 1.3;
422
+ }
423
+ .talk-bubble::after {
424
+ content: '';
425
+ position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
426
+ border: 5px solid transparent;
427
+ border-top-color: white;
428
+ }
429
+ .talk-bubble.show { opacity: 1; transform: translateX(-50%) scale(1); }
430
+
431
+ /* Status glow ring */
432
+ .agent-person.working::before {
433
+ content: '';
434
+ position: absolute; inset: -4px;
435
+ border-radius: 50%;
436
+ border: 2px solid;
437
+ animation: dg 1s ease infinite;
438
+ }
439
+
440
+ /* PLANT */
441
+ .plant {
442
+ position: absolute;
443
+ z-index: 6;
444
+ }
445
+ .plant-pot { width: 20px; height: 16px; background: #a07050; border-radius: 0 0 4px 4px; margin: 0 auto; }
446
+ .plant-soil { width: 24px; height: 5px; background: #5a3a20; border-radius: 2px; margin: 0 auto; }
447
+ .plant-leaves { display: flex; justify-content: center; gap: 3px; margin-bottom: 2px; }
448
+ .plant-leaf { width: 12px; height: 18px; background: #2d7a3a; border-radius: 50% 50% 40% 40%; }
449
+ .plant-leaf:nth-child(1) { transform: rotate(-25deg); background: #267030; }
450
+ .plant-leaf:nth-child(3) { transform: rotate(25deg); background: #34884a; }
451
+
452
+ /* BOOKSHELF */
453
+ .bookshelf {
454
+ position: absolute;
455
+ background: #b8905a;
456
+ border: 2px solid #9a7840;
457
+ border-radius: 2px;
458
+ display: flex;
459
+ flex-direction: column;
460
+ gap: 2px;
461
+ padding: 3px 4px;
462
+ z-index: 5;
463
+ }
464
+ .book-row { display: flex; gap: 1px; }
465
+ .book { width: 6px; height: 18px; border-radius: 1px 2px 2px 1px; }
466
+
467
+ /* RESULT PANEL */
468
+ .result-panel {
469
+ position: absolute; bottom: 0; left: 0; right: 0;
470
+ max-height: 42%;
471
+ background: rgba(248,249,250,.96);
472
+ border-top: 2px solid #ddd;
473
+ padding: 10px 14px;
474
+ overflow-y: auto;
475
+ z-index: 50;
476
+ display: none;
477
+ backdrop-filter: blur(8px);
478
+ box-shadow: 0 -8px 32px rgba(0,0,0,.12);
479
+ }
480
+ .result-panel.show { display: block; }
481
+ .rp-header {
482
+ display: flex; justify-content: space-between; align-items: center;
483
+ margin-bottom: 10px;
484
+ padding-bottom: 8px;
485
+ border-bottom: 1px solid #eee;
486
+ }
487
+ .rp-title { font-family: 'VT323', monospace; font-size: 16px; color: var(--text); letter-spacing: 2px; }
488
+ .rp-close { cursor: pointer; color: var(--text3); font-size: 16px; padding: 2px 8px; border: 1px solid #ddd; border-radius: 3px; }
489
+ .rp-close:hover { background: #f0f0f0; }
490
+ .rp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 8px; }
491
+ .rp-card {
492
+ background: white; border: 1px solid #e8e8e8;
493
+ border-radius: 6px; padding: 10px 12px;
494
+ box-shadow: var(--shadow-sm);
495
+ }
496
+ .rp-name {
497
+ font-family: 'VT323', monospace; font-size: 14px;
498
+ letter-spacing: 1px; margin-bottom: 5px;
499
+ display: flex; align-items: center; gap: 6px;
500
+ }
501
+ .rp-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
502
+ .rp-text { font-size: 10px; color: var(--text2); line-height: 1.5; word-break: break-word; }
503
+ .rp-model { font-size: 9px; color: var(--text3); margin-top: 5px; }
504
+ .dl-btn {
505
+ display: inline-flex; align-items: center; gap: 4px;
506
+ margin-top: 6px; padding: 4px 10px;
507
+ background: #f0f9f0; border: 1px solid #4ade80;
508
+ color: #16a34a; font-size: 9px; cursor: pointer;
509
+ text-decoration: none; border-radius: 3px;
510
+ font-family: 'Share Tech Mono', monospace;
511
+ transition: background .15s;
512
+ }
513
+ .dl-btn:hover { background: #dcfce7; }
514
+ .img-row { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 6px; }
515
+ .img-th {
516
+ width: 64px; height: 46px; object-fit: cover;
517
+ border: 1px solid #ddd; border-radius: 3px;
518
+ cursor: pointer; transition: border-color .15s;
519
+ }
520
+ .img-th:hover { border-color: #4ade80; }
521
+
522
+ /* Orch log in result panel */
523
+ .orch-log { margin-top: 10px; padding-top: 8px; border-top: 1px solid #eee; }
524
+ .orch-title { font-family: 'VT323', monospace; font-size: 12px; color: var(--text3); letter-spacing: 2px; margin-bottom: 4px; }
525
+ .orch-ev { font-size: 9px; color: var(--text3); padding: 1px 0; display: flex; gap: 8px; }
526
+ .orch-t { color: var(--text3); flex-shrink: 0; }
527
+
528
+ /* LIGHTBOX */
529
+ .lbox { position: fixed; inset: 0; background: rgba(0,0,0,.88); z-index: 900; display: none; align-items: center; justify-content: center; flex-direction: column; gap: 12px; }
530
+ .lbox.show { display: flex; }
531
+ .lbox img { max-width: 85vw; max-height: 72vh; border-radius: 4px; box-shadow: var(--shadow); }
532
+ .lbox-btns { display: flex; gap: 10px; }
533
+ .lbox-dl { display:inline-flex;align-items:center;gap:4px;padding:6px 16px;background:rgba(74,222,128,.1);border:1px solid #4ade80;color:#4ade80;font-size:11px;cursor:pointer;text-decoration:none;border-radius:3px; }
534
+ .lbox-cl { font-size: 13px; color: #8a9aaa; cursor: pointer; padding: 6px 16px; border: 1px solid #334; border-radius: 3px; }
535
+ .lbox-cl:hover { color: white; }
536
 
537
  /* WALK OVERLAY */
538
+ .walk-ov { position: fixed; inset: 0; background: rgba(248,249,250,.94); z-index: 400; display: none; align-items: center; justify-content: center; flex-direction: column; gap: 12px; backdrop-filter: blur(4px); }
539
+ .walk-ov.show { display: flex; }
540
+ .walk-wrap { width: min(680px,94vw); height: min(340px,50vh); background: white; border: 1px solid #ddd; border-radius: 8px; overflow: hidden; box-shadow: var(--shadow); position: relative; }
541
+ .walk-lbl { font-family: 'VT323', monospace; font-size: 14px; color: var(--text); letter-spacing: 3px; }
542
+ .walk-log { font-size: 10px; color: var(--text2); height: 14px; font-family: 'Share Tech Mono', monospace; }
543
+
544
+ /* MODAL */
545
+ .modal-bg { position: fixed; inset: 0; background: rgba(0,0,0,.5); z-index: 300; display: none; align-items: center; justify-content: center; }
546
+ .modal-bg.show { display: flex; }
547
+ .modal { background: white; border-radius: 8px; padding: 22px; width: 360px; box-shadow: var(--shadow); }
548
+ .modal-t { font-family: 'VT323', monospace; font-size: 20px; color: var(--text); letter-spacing: 2px; margin-bottom: 14px; }
549
+ .field { margin-bottom: 10px; }
550
+ .field label { display: block; font-size: 10px; color: var(--text2); letter-spacing: 1px; margin-bottom: 3px; }
551
+ .field input, .field select, .field textarea { width: 100%; background: #f8f9fa; border: 1px solid #ddd; color: var(--text); font-family: 'Share Tech Mono', monospace; font-size: 11px; padding: 6px 8px; outline: none; border-radius: 3px; transition: border-color .15s; }
552
+ .field input:focus, .field select:focus, .field textarea:focus { border-color: #22c55e; }
553
+ .field textarea { resize: vertical; min-height: 50px; }
554
+ .modal-acts { display: flex; gap: 7px; justify-content: flex-end; margin-top: 12px; }
555
+ .mbtn { padding: 6px 16px; font-size: 11px; cursor: pointer; border: 1px solid; background: transparent; border-radius: 3px; transition: all .15s; }
556
+ .mbtn-ok { border-color: #22c55e; color: #16a34a; }
557
+ .mbtn-ok:hover { background: #f0fdf4; }
558
+ .mbtn-no { border-color: #ddd; color: var(--text2); }
559
+ .mbtn-no:hover { background: #f8f9fa; }
560
 
561
  /* NOTIFICATION */
562
+ .notif {
563
+ position: fixed; top: 52px; right: 14px;
564
+ background: white; border: 1px solid #ddd; border-left: 3px solid #22c55e;
565
+ padding: 10px 14px; max-width: 260px; z-index: 600;
566
+ transform: translateX(280px); transition: transform .3s ease;
567
+ border-radius: 6px; box-shadow: var(--shadow); font-size: 11px;
568
+ }
569
+ .notif.show { transform: translateX(0); }
570
+ .notif-t { font-family: 'VT323', monospace; font-size: 16px; letter-spacing: 1px; margin-bottom: 2px; }
571
+ .notif-m { color: var(--text2); font-size: 10px; line-height: 1.4; }
572
 
573
  /* SIDEBAR */
574
+ .sidebar { background: white; border-left: 1px solid #e8e8e8; display: flex; flex-direction: column; overflow: hidden; }
575
+ .sb-sec { border-bottom: 1px solid #eee; flex-shrink: 0; }
576
+ .sb-t { font-family: 'VT323', monospace; font-size: 13px; color: var(--text2); padding: 8px 12px 6px; border-bottom: 1px solid #eee; letter-spacing: 2px; }
577
+ .act-scroll { overflow-y: auto; max-height: 240px; }
578
+ .act-item { padding: 6px 12px; border-bottom: 1px solid #f0f0f0; display: flex; gap: 6px; }
579
+ .act-dot { width: 4px; height: 4px; border-radius: 50%; margin-top: 5px; flex-shrink: 0; }
580
+ .act-msg { font-size: 10px; color: var(--text); line-height: 1.4; }
581
+ .act-time { font-size: 9px; color: var(--text3); margin-top: 1px; }
582
+ .hist-scroll { overflow-y: auto; max-height: 200px; }
583
+ .hist-item { padding: 7px 12px; border-bottom: 1px solid #f0f0f0; cursor: pointer; transition: background .1s; }
584
+ .hist-item:hover { background: #f8f9fa; }
585
+ .hist-task { font-size: 10px; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
586
+ .hist-meta { font-size: 9px; color: var(--text3); margin-top: 2px; display: flex; gap: 6px; }
587
+ ::-webkit-scrollbar { width: 4px; }
588
+ ::-webkit-scrollbar-track { background: transparent; }
589
+ ::-webkit-scrollbar-thumb { background: #ddd; border-radius: 2px; }
590
  </style>
591
  </head>
592
  <body>
593
+
594
+ <!-- TOPBAR -->
595
  <header class="topbar">
596
  <div style="display:flex;align-items:center;gap:8px">
597
  <span class="tb-logo">MISSION CONTROL AI</span>
598
+ <span style="font-size:9px;color:#2a3d58">v7</span>
599
  </div>
600
  <div class="tb-right">
601
  <div class="tb-stat"><div class="sdot dg"></div><span id="top-active">0 active</span></div>
 
603
  <div class="clock" id="clock">00:00:00</div>
604
  </div>
605
  </header>
606
+
607
  <div class="prog" id="prog"></div>
608
+
609
+ <!-- MISSION BAR -->
610
  <div class="mbar">
611
+ <input class="minput" id="task-input" placeholder="Assign mission to agents..." autocomplete="off"/>
612
  <button class="launch" id="launch-btn" onclick="launchMission()">&#9658; LAUNCH</button>
613
  <button class="add-btn" onclick="openModal()">+ Agent</button>
614
+ <span class="mc-count" id="mc-count">MISSIONS: 0</span>
615
  </div>
616
+
617
+ <!-- MAIN -->
618
  <div class="main">
619
+ <div class="office" id="office">
620
+
621
+ <!-- WALL -->
622
+ <div class="wall"></div>
623
+
624
+ <!-- WINDOWS -->
625
+ <div class="window left"></div>
626
+ <div class="window right"></div>
627
+
628
+ <!-- FLOOR -->
629
+ <div class="floor-area"></div>
630
+
631
+ <!-- BIG SCREEN -->
632
+ <div class="big-screen" id="big-screen">
633
+ <div class="big-screen-inner">
634
+ <div class="bs-corner tl"></div>
635
+ <div class="bs-corner tr"></div>
636
+ <div class="bs-corner bl"></div>
637
+ <div class="bs-corner br"></div>
638
+ <div class="bs-scan"></div>
639
+ <div class="bs-title" id="bs-title">MISSION CONTROL AI</div>
640
+ <div class="bs-content" id="bs-content">Awaiting mission...</div>
641
+ </div>
642
  </div>
643
+
644
+ <!-- Screen stand -->
645
+ <div class="screen-stand" id="screen-stand" style="top:0;"></div>
646
+ <div class="screen-base" id="screen-base" style="top:0;"></div>
647
+
648
+ <!-- SERVER RACK -->
649
+ <div class="server-rack" id="server-rack-el"></div>
650
+
651
+ <!-- PLANTS -->
652
+ <div class="plant" style="right:16%;bottom:32%">
653
+ <div class="plant-leaves"><div class="plant-leaf"></div><div class="plant-leaf"></div><div class="plant-leaf"></div></div>
654
+ <div class="plant-soil"></div><div class="plant-pot"></div>
655
+ </div>
656
+ <div class="plant" style="left:17%;bottom:30%">
657
+ <div class="plant-leaves"><div class="plant-leaf"></div><div class="plant-leaf"></div><div class="plant-leaf"></div></div>
658
+ <div class="plant-soil"></div><div class="plant-pot"></div>
659
+ </div>
660
+
661
+ <!-- BOOKSHELF -->
662
+ <div class="bookshelf" style="left:1%;top:10%;width:36px;height:70px" id="bookshelf-el"></div>
663
+
664
+ <!-- DESKS rendered by JS -->
665
+ <div id="desks-layer"></div>
666
+
667
+ <!-- AGENTS rendered by JS -->
668
+ <div id="agents-layer"></div>
669
+
670
+ <!-- RESULT PANEL -->
671
+ <div class="result-panel" id="result-panel">
672
+ <div class="rp-header">
673
+ <div class="rp-title">MISSION RESULTS</div>
674
+ <div class="rp-close" onclick="document.getElementById('result-panel').classList.remove('show')">&#10005; close</div>
675
+ </div>
676
+ <div class="rp-grid" id="rp-grid"></div>
677
+ <div id="orch-log-wrap"></div>
678
+ </div>
679
+
680
  </div>
681
+
682
+ <!-- SIDEBAR -->
683
  <aside class="sidebar">
684
  <div class="sb-sec"><div class="sb-t">ACTIVITY</div><div class="act-scroll" id="act-log"></div></div>
685
+ <div class="sb-sec"><div class="sb-t">MISSIONS</div>
686
+ <div class="hist-scroll" id="hist-log">
687
+ <div style="padding:12px;font-size:10px;color:var(--text3);text-align:center">No missions yet</div>
688
+ </div>
689
+ </div>
690
  </aside>
691
  </div>
692
 
693
  <!-- WALK OVERLAY -->
694
+ <div class="walk-ov" id="walk-ov">
695
+ <div class="walk-lbl" id="walk-lbl">MANAGER HEADING TO CONFERENCE ROOM</div>
696
  <div class="walk-wrap"><canvas id="walk-canvas"></canvas></div>
697
+ <div class="walk-log" id="walk-log">Preparing briefing...</div>
698
  </div>
699
 
700
  <!-- LIGHTBOX -->
701
+ <div class="lbox" id="lbox" onclick="if(event.target===this)closeLbox()">
702
+ <img id="lbox-img" src="" alt=""/>
703
+ <div class="lbox-btns">
704
+ <a class="lbox-dl" id="lbox-dl" href="#" download>&#8595; Download</a>
705
+ <div class="lbox-cl" onclick="closeLbox()">&#10005; Close</div>
706
  </div>
707
  </div>
708
 
709
  <!-- ADD AGENT MODAL -->
710
  <div class="modal-bg" id="modal-bg" onclick="if(event.target===this)closeModal()">
711
  <div class="modal">
712
+ <div class="modal-t">+ New Agent</div>
713
+ <div class="field"><label>Key (ID)</label><input id="m-key" placeholder="e.g. researcher"/></div>
714
+ <div class="field"><label>Name</label><input id="m-name" placeholder="e.g. Researcher"/></div>
715
  <div class="field"><label>Role</label><textarea id="m-role" placeholder="Especialista en..."></textarea></div>
716
+ <div class="field"><label>Provider</label>
717
+ <select id="m-prov">
718
+ <option value="openrouter">OpenRouter (free)</option>
719
+ <option value="gemini">Google Gemini</option>
720
+ <option value="groq">Groq</option>
721
+ </select>
722
+ </div>
723
  <div class="modal-acts">
724
  <button class="mbtn mbtn-no" onclick="closeModal()">Cancel</button>
725
  <button class="mbtn mbtn-ok" onclick="submitAgent()">Add Agent</button>
 
729
 
730
  <!-- NOTIFICATION -->
731
  <div class="notif" id="notif">
732
+ <div class="notif-t" id="notif-t">Done</div>
733
  <div class="notif-m" id="notif-m"></div>
734
  </div>
735
 
736
  <script>
737
+ // ── CONFIG ────────────────────────────────────────────────────────────────
738
+ const AGENT_COLORS = {
739
+ manager:'#22c55e', backend_dev:'#3b82f6', frontend_dev:'#a855f7',
740
+ analyst:'#f59e0b', writer:'#06b6d4', image_agent:'#ec4899', developer:'#3b82f6'
741
+ };
742
+ const SHIRT_COLORS = {
743
+ manager:'#166534', backend_dev:'#1e3a6e', frontend_dev:'#581c87',
744
+ analyst:'#78350f', writer:'#164e63', image_agent:'#831843', developer:'#1e3a6e'
745
+ };
746
+ const HAIR_COLORS = { manager:'#4a2e18', backend_dev:'#1a1a2e', frontend_dev:'#2e0a4a', analyst:'#3a2010', writer:'#0a2a3a', image_agent:'#2a0a1a', developer:'#1a1a2e' };
747
+ const SPEECH_IDLE = { manager:['Planning next move...','Reviewing KPIs...','Coffee break?'], backend_dev:['Debugging...','Push to prod?','PR approved!'], frontend_dev:['CSS is fine...','Pixel perfect!','Dark mode ftw'], analyst:['Numbers check out','Risk: medium','ROI looks good'], writer:['Draft complete','Editing now...','Words per minute: ∞'], image_agent:['Searching...','Found it!','Resolution: HD'], developer:['Compiling...','Ship it!','LGTM'] };
748
+ const SPEECH_WORK = { manager:['Delegating now...','Team briefed!','Executing plan'], backend_dev:['Building...','Almost done...','Tests passing!'], frontend_dev:['Styling...','Responsive βœ“','Deployed!'], analyst:['Analyzing data...','Insight found!','Report ready'], writer:['Writing report...','First draft done','Publishing...'], image_agent:['Fetching image...','Generating AI...','Found 3 images!'], developer:['Coding...','Feature done!','Bug fixed!'] };
749
+ const PROV_MODELS = { openrouter:['meta-llama/llama-3.3-70b-instruct:free','qwen/qwen3-4b:free'], gemini:['gemini-2.5-flash-preview-04-17','gemini-2.0-flash'], groq:['llama3-70b-8192','gemma2-9b-it'] };
750
+
751
+ let agentDefs=[], agentStates={}, agentPositions={}, agentTargets={}, agentModes={};
752
+ let activity=[], history=[], mCount=0, bubbleTimers={};
753
+ let officeW=0, officeH=0;
754
+
755
+ // ── INIT ───────────────────────────────────────────────────────────────────
756
+ async function init(){
757
+ positionScreenElements();
758
+ buildServerRack();
759
+ buildBookshelf();
760
+ const r = await fetch('/api/agents'), d = await r.json();
761
+ agentDefs = d.agents;
762
+ agentDefs.forEach(a=>{
763
+ agentStates[a.key] = { status:'idle', message:'', model:'', doc_file:null, img_base:null, img_count:0 };
764
+ agentModes[a.key] = 'idle';
765
+ });
766
+ renderDesks();
767
+ renderAgents();
768
+ startIdleLife();
769
+ addAct('Office open β€” agents ready','#22c55e');
770
  }
771
 
772
+ function positionScreenElements(){
773
+ const office = document.getElementById('office');
774
+ officeW = office.clientWidth; officeH = office.clientHeight;
775
+ const screen = document.getElementById('big-screen');
776
+ const sw = screen.offsetWidth, sh = screen.offsetHeight;
777
+ const screenBottom = officeH * 0.04 + sh;
778
+ document.getElementById('screen-stand').style.cssText = `top:${screenBottom}px;left:${officeW/2-30}px;`;
779
+ document.getElementById('screen-base').style.cssText = `top:${screenBottom+8}px;left:${officeW/2-50}px;`;
780
+ }
781
 
782
+ // ── SERVER RACK ────────────────────────────────────────────────────────────
783
+ function buildServerRack(){
784
+ const el = document.getElementById('server-rack-el');
785
+ el.style.cssText = `position:absolute;top:3%;right:1%;width:52px;background:#1a2035;border:1px solid #2a3550;border-radius:2px;padding:5px 4px;display:flex;flex-direction:column;gap:2px;box-shadow:0 4px 12px rgba(0,0,0,.2);z-index:5`;
786
+ const ledColors = ['#22c55e','#3b82f6','#f59e0b','#22c55e','#ec4899','#22c55e','#3b82f6'];
787
+ let html = '';
788
+ ledColors.forEach((c,i)=>{
789
+ html += `<div style="height:8px;background:#0d1520;border:1px solid #1a2a3a;display:flex;align-items:center;gap:2px;padding:0 3px">
790
+ <div style="width:4px;height:4px;border-radius:50%;background:${c};animation:led-blink ${.8+i*.3}s ease-in-out infinite alternate" class="rack-led"></div>
791
+ <div style="flex:1;height:2px;background:#1a2a3a"></div>
792
+ </div>`;
793
+ });
794
+ el.innerHTML = html + `<div style="font-family:'VT323',monospace;font-size:8px;color:#2a3d58;text-align:center;letter-spacing:1px;margin-top:2px">SRV</div>`;
795
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
796
 
797
+ // ── BOOKSHELF ──────────────────────────────────────────────────────────────
798
+ function buildBookshelf(){
799
+ const el = document.getElementById('bookshelf-el');
800
+ const BOOKS = ['#e74c3c','#3498db','#2ecc71','#f39c12','#9b59b6','#1abc9c','#e67e22','#34495e','#e91e63','#00bcd4'];
801
+ let html = '';
802
+ for(let row=0;row<3;row++){
803
+ html += '<div class="book-row">';
804
+ for(let b=0;b<5;b++) html += `<div class="book" style="background:${BOOKS[(row*5+b)%BOOKS.length]}"></div>`;
805
+ html += '</div>';
806
+ }
807
+ el.innerHTML = html;
808
+ }
809
 
810
+ // ── DESK POSITIONS ─────────────────────────────────────────────────────────
811
+ function getDeskPositions(count){
812
+ const positions = [];
813
+ const cols = Math.ceil(count/2);
814
+ for(let i=0;i<count;i++){
815
+ const col = i % cols;
816
+ const row = Math.floor(i / cols);
817
+ const xPct = 15 + (col / Math.max(cols-1,1)) * 65;
818
+ const yFromBottom = 22 + row * 22; // % from bottom
819
+ positions.push({ xPct, yFromBottom });
820
  }
821
+ return positions;
822
+ }
823
 
824
+ function renderDesks(){
825
+ const layer = document.getElementById('desks-layer');
826
+ const positions = getDeskPositions(agentDefs.length);
827
+ const mugColors = ['#f6eb61','#ff8a65','#4fc3f7','#a5d6a7','#ce93d8','#ffcc02'];
828
+ layer.innerHTML = agentDefs.map((agent,i)=>{
829
+ const pos = positions[i];
830
+ const color = AGENT_COLORS[agent.key] || '#3b82f6';
831
+ const status = agentStates[agent.key]?.status || 'idle';
832
+ const screenTxt = status==='working' ? 'β–Œβ–Œ BUSY β–Œβ–Œ' : status==='active' ? '● DONE' : 'β€” IDLE';
833
+ const screenColor = status==='working' ? '#4ade80' : status==='active' ? '#22c55e' : '#334';
834
+ return `<div id="desk-${agent.key}" class="desk" style="left:${pos.xPct}%;bottom:${pos.yFromBottom}%;transform:translateX(-50%)">
835
+ <div class="desk-monitor">
836
+ <div class="desk-monitor-screen" id="dscreen-${agent.key}" style="color:${screenColor}">${screenTxt}</div>
837
+ </div>
838
+ <div class="desk-mug" style="background:${mugColors[i%mugColors.length]}"></div>
839
+ <div class="desk-kbd"></div>
840
+ <div class="desk-paper"></div>
841
+ </div>`;
842
+ }).join('');
843
+ }
844
+
845
+ // ── AGENT RENDERING ────────────────────────────────────────────────────────
846
+ function seatPosition(agentKey){
847
+ const i = agentDefs.findIndex(a=>a.key===agentKey);
848
+ if(i<0) return {x:50,y:30};
849
+ const positions = getDeskPositions(agentDefs.length);
850
+ const pos = positions[i];
851
+ return { x: pos.xPct, y: pos.yFromBottom + 12 };
852
+ }
853
+
854
+ function renderAgents(){
855
+ const layer = document.getElementById('agents-layer');
856
+ layer.innerHTML = agentDefs.map(agent=>{
857
+ const color = AGENT_COLORS[agent.key] || '#3b82f6';
858
+ const shirt = SHIRT_COLORS[agent.key] || '#1e3a6e';
859
+ const hair = HAIR_COLORS[agent.key] || '#1a1a2e';
860
+ const st = agentStates[agent.key] || {};
861
+ const status = st.status || 'idle';
862
+ const seat = seatPosition(agent.key);
863
+ const mode = agentModes[agent.key] || 'idle';
864
+ const isEmpty = status === 'resting';
865
+ if(!agentPositions[agent.key]) agentPositions[agent.key] = { x:seat.x, y:seat.y };
866
+ const pos = agentPositions[agent.key];
867
+ const modeClass = mode === 'walking' ? 'walking' : mode === 'working' ? 'working' : 'idle';
868
+ const tagColor = status==='working'?color : status==='active'?'#16a34a' : status==='resting'?'#f59e0b' : '#8a9aaa';
869
+ const tagBg = status==='working'?`${color}22` : 'rgba(255,255,255,.8)';
870
+ const tagLabel = {idle:'IDLE',working:'●',active:'βœ“',resting:'ZZZ',error:'!'}[status]||'IDLE';
871
+ return `<div class="agent-person ${modeClass}" id="agent-${agent.key}"
872
+ style="left:${pos.x}%;bottom:${pos.y}%;transform:translateX(-50%);${isEmpty?'opacity:.3':''}"
873
+ onclick="onAgentClick('${agent.key}')">
874
+ <div class="p-hair" style="background:${hair}"></div>
875
+ <div class="p-head"><div class="p-eyes"><div class="p-eye"></div><div class="p-eye"></div></div></div>
876
+ <div class="p-body" style="background:${shirt}">
877
+ <div style="position:absolute;top:4px;left:50%;transform:translateX(-50%);width:10px;height:3px;background:${color};opacity:.6;border-radius:1px"></div>
878
  </div>
879
+ <div class="p-arms">
880
+ <div class="p-arm left" style="background:${shirt}"></div>
881
+ <div class="p-arm right" style="background:${shirt}"></div>
 
 
 
 
 
882
  </div>
883
+ <div class="p-legs"><div class="p-leg"></div><div class="p-leg"></div></div>
884
+ <div class="p-feet"><div class="p-foot"></div><div class="p-foot"></div></div>
885
+ <div class="agent-tag" style="color:${tagColor};background:${tagBg}">${tagLabel} ${agent.name}</div>
886
+ <div class="talk-bubble" id="bubble-${agent.key}"></div>
887
+ ${isEmpty?`<div style="position:absolute;top:-25px;left:50%;transform:translateX(-50%);font-size:16px">πŸ’€</div>`:''}
888
+ </div>`;
889
+ }).join('');
890
+ }
891
+
892
+ function updateAgentEl(agentKey){
893
+ const el = document.getElementById(`agent-${agentKey}`);
894
+ if(!el) return;
895
+ const st = agentStates[agentKey] || {};
896
+ const status = st.status || 'idle';
897
+ const mode = agentModes[agentKey] || 'idle';
898
+ const color = AGENT_COLORS[agentKey] || '#3b82f6';
899
+ const pos = agentPositions[agentKey] || seatPosition(agentKey);
900
+ el.style.left = pos.x + '%';
901
+ el.style.bottom = pos.y + '%';
902
+ el.className = `agent-person ${mode}`;
903
+ el.style.opacity = status === 'resting' ? '.3' : '1';
904
+ el.style.transform = 'translateX(-50%)';
905
+ // Tag
906
+ const tag = el.querySelector('.agent-tag');
907
+ if(tag){
908
+ const tagColor = status==='working'?color : status==='active'?'#16a34a' : status==='resting'?'#f59e0b' : '#8a9aaa';
909
+ const tagBg = status==='working'?`${color}22` : 'rgba(255,255,255,.8)';
910
+ const name = agentDefs.find(a=>a.key===agentKey)?.name || agentKey;
911
+ tag.style.color = tagColor;
912
+ tag.style.background = tagBg;
913
+ tag.textContent = ({idle:'IDLE',working:'●',active:'βœ“',resting:'ZZZ',error:'!'}[status]||'IDLE') + ' ' + name;
914
+ }
915
+ // Desk monitor
916
+ const dscreen = document.getElementById(`dscreen-${agentKey}`);
917
+ if(dscreen){
918
+ dscreen.textContent = status==='working'?'β–Œβ–Œ BUSY β–Œβ–Œ': status==='active'?'● DONE':'β€” IDLE';
919
+ dscreen.style.color = status==='working'?'#4ade80':status==='active'?'#22c55e':'#334';
920
+ }
921
  }
922
 
923
+ function onAgentClick(key){
924
+ const st = agentStates[key];
925
+ if(st && st.message) showSingleResult(key);
926
  }
927
 
928
+ function showSingleResult(key){
929
+ const st = agentStates[key];
930
+ if(!st || !st.message) return;
931
+ const agent = agentDefs.find(a=>a.key===key);
932
+ const color = AGENT_COLORS[key]||'#3b82f6';
933
+ let extra='';
934
+ if(st.doc_file){
935
+ const ext=st.doc_file.split('.').pop().toLowerCase();
936
+ const label={xlsx:'Excel',docx:'Word',html:'HTML',py:'Python',groovy:'Groovy',jpg:'Image'}[ext]||'File';
937
+ extra+=`<a class="dl-btn" href="/api/docs/${st.doc_file}" download>&#8595; Download ${label}</a>`;
938
  }
939
+ if(st.img_base&&st.img_count>0){
940
+ const thumbs=Array.from({length:st.img_count},(_,i)=>`<img class="img-th" src="/api/docs/${st.img_base}_img${i+1}.jpg" onclick="openLbox('/api/docs/${st.img_base}_img${i+1}.jpg','img${i+1}.jpg')"/>`).join('');
941
+ extra+=`<div class="img-row">${thumbs}</div>`;
942
+ }
943
+ document.getElementById('rp-grid').innerHTML=`<div class="rp-card">
944
+ <div class="rp-name"><div class="rp-dot" style="background:${color}"></div><span style="color:${color}">${agent?.name||key}</span></div>
945
+ <div class="rp-text">${(st.message||'').substring(0,500)}</div>
946
+ ${st.model?`<div class="rp-model">via ${st.model}</div>`:''}
947
+ ${extra}
948
+ </div>`;
949
+ document.getElementById('result-panel').classList.add('show');
950
  }
951
 
952
+ // ── IDLE LIFE ──────────────────────────────────────────────────────────────
953
+ function startIdleLife(){
954
+ setInterval(()=>{
955
+ agentDefs.forEach(agent=>{
956
+ const st = agentStates[agent.key]?.status || 'idle';
957
+ if(st === 'working') return;
958
+ const mode = agentModes[agent.key] || 'idle';
959
+ if(Math.random() < .25){
960
+ if(mode === 'idle' || mode === 'talking'){
961
+ // Start walking to random spot
962
+ agentModes[agent.key] = 'walking';
963
+ const targetX = 10 + Math.random() * 75;
964
+ const targetY = 10 + Math.random() * 35;
965
+ agentTargets[agent.key] = { x: targetX, y: targetY };
966
+ }
967
+ }
968
+ if(Math.random() < .06){
969
+ const phrases = SPEECH_IDLE[agent.key] || ['Working...'];
970
+ showBubble(agent.key, phrases[Math.floor(Math.random()*phrases.length)], 2500);
971
+ }
972
+ });
973
+ }, 3000);
974
+
975
+ // Smooth movement loop
976
+ setInterval(()=>{
977
+ agentDefs.forEach(agent=>{
978
+ const st = agentStates[agent.key]?.status || 'idle';
979
+ if(st === 'working'){
980
+ // Stay at desk
981
+ const seat = seatPosition(agent.key);
982
+ agentPositions[agent.key] = { x: seat.x, y: seat.y };
983
+ agentModes[agent.key] = 'working';
984
+ updateAgentEl(agent.key);
985
+ return;
986
+ }
987
+ const target = agentTargets[agent.key];
988
+ if(!target) return;
989
+ const pos = agentPositions[agent.key] || seatPosition(agent.key);
990
+ const dx = target.x - pos.x, dy = target.y - pos.y;
991
+ const dist = Math.sqrt(dx*dx+dy*dy);
992
+ if(dist < 1.2){
993
+ agentPositions[agent.key] = { x: target.x, y: target.y };
994
+ agentTargets[agent.key] = null;
995
+ agentModes[agent.key] = 'idle';
996
+ } else {
997
+ const spd = 0.4;
998
+ agentPositions[agent.key] = { x: pos.x + dx/dist*spd, y: pos.y + dy/dist*spd };
999
+ agentModes[agent.key] = 'walking';
1000
+ }
1001
+ updateAgentEl(agent.key);
1002
+ });
1003
+ }, 80);
1004
  }
 
1005
 
1006
+ // ── CLOCK ──────────────────────────────────────────────────────────────────
1007
+ setInterval(()=>{
1008
+ const n=new Date();
1009
+ const t=[n.getHours(),n.getMinutes(),n.getSeconds()].map(x=>String(x).padStart(2,'0')).join(':');
1010
+ document.getElementById('clock').textContent=t;
1011
+ },1000);
1012
+
1013
+ // ── SPEECH BUBBLES ─────────────────────────────────────────────────────────
1014
+ function showBubble(key, text, ms=2500){
1015
+ const b = document.getElementById(`bubble-${key}`);
1016
+ if(!b) return;
1017
+ if(bubbleTimers[key]) clearTimeout(bubbleTimers[key]);
1018
+ b.textContent = text;
1019
+ b.classList.add('show');
1020
+ bubbleTimers[key] = setTimeout(()=>b.classList.remove('show'), ms);
1021
  }
1022
 
1023
+ // ── SCREEN UPDATE ──────────────────────────────────────────────────────────
1024
+ function updateScreen(title, content){
1025
+ document.getElementById('bs-title').textContent = title;
1026
+ document.getElementById('bs-content').textContent = content;
 
 
1027
  }
1028
 
1029
+ // ── ACTIVITY ───────────────────────────────────────────────────────────────
1030
+ function addAct(msg,color='#3b82f6'){
1031
  const n=new Date();
1032
  const t=[n.getHours(),n.getMinutes(),n.getSeconds()].map(x=>String(x).padStart(2,'0')).join(':');
1033
  activity.unshift({msg,t,color});
 
1036
  ).join('');
1037
  }
1038
 
1039
+ function notify(title,msg,color='#22c55e'){
1040
  document.getElementById('notif-t').textContent=title;
1041
  document.getElementById('notif-t').style.color=color;
1042
  document.getElementById('notif-m').textContent=msg;
1043
  document.getElementById('notif').style.borderLeftColor=color;
1044
+ document.getElementById('notif').classList.add('show');
1045
+ setTimeout(()=>document.getElementById('notif').classList.remove('show'),5500);
 
1046
  }
1047
 
1048
  function updateHistory(){
1049
  const list=document.getElementById('hist-log');
1050
+ if(!history.length){list.innerHTML=`<div style="padding:12px;font-size:10px;color:var(--text3);text-align:center">No missions yet</div>`;return;}
1051
+ list.innerHTML=[...history].reverse().map(m=>`<div class="hist-item" onclick=""><div class="hist-task">${m.task}</div><div class="hist-meta"><span>${m.time}</span><span style="color:${m.ok?'#16a34a':'#f59e0b'}">${m.ok?'Done':'Partial'}</span></div></div>`).join('');
 
 
1052
  }
1053
 
1054
+ // ── WALK OVERLAY ANIMATION ─────────────────────────────────────────────────
1055
  function runWalkAnimation(task, delegates, onDone){
1056
+ const ov=document.getElementById('walk-ov');
1057
+ const wc=document.getElementById('walk-canvas');
1058
  const logEl=document.getElementById('walk-log');
1059
+ const wrap=wc.parentElement;
1060
+ wc.width=wrap.clientWidth||640; wc.height=wrap.clientHeight||320;
1061
+ const W=wc.width,H=wc.height,ctx=wc.getContext('2d');
1062
+ ov.classList.add('show');
1063
+
1064
+ // Light office background
1065
+ const others=agentDefs.filter(a=>a.key!=='manager').slice(0,6);
 
 
 
1066
  const delSet=new Set(delegates);
1067
+ const deskW=100,deskH=52,deskY=H*0.52;
1068
+ const spacing=(W-80)/(Math.max(others.length-1,1)||1);
1069
+ const desks=others.map((a,i)=>({
1070
+ x:40+i*spacing, y:deskY, key:a.key, name:a.name,
1071
+ color:AGENT_COLORS[a.key]||'#3b82f6',
1072
+ shirt:SHIRT_COLORS[a.key]||'#1e3a6e',
1073
+ hair:HAIR_COLORS[a.key]||'#1a1a2e',
1074
+ }));
1075
+
1076
+ const delSet2=new Set(delegates);
1077
+ const toVisit=desks.filter(d=>delSet2.has(d.key));
1078
+ const msgs={writer:`Write report on:\n"${task.substring(0,22)}..."`,analyst:`Analyze results\nfor this task.`,backend_dev:`Build solution:\n"${task.substring(0,22)}..."`,frontend_dev:`Design the HTML\ninterface.`,image_agent:`Find/generate\nimages now.`};
1079
+ const manColor='#22c55e',manShirt='#166534',manHair='#4a2e18';
1080
+ let mx=W/2,my=6,frame=0,stepI=0,phase='walking',talkF=0;
1081
+ const TALK=75,SPD=3.2;
1082
+ const steps=[{key:'__top',x:W/2,y:H*0.14,msg:'Team! New mission.'},{...toVisit.map(d=>({key:d.key,x:d.x,y:deskY-20,msg:msgs[d.key]||'Please handle this.'}))}.length===0?{key:'__back',x:W/2,y:H*0.14,msg:'All briefed!'}:{key:'__dummy'},...toVisit.map(d=>({key:d.key,x:d.x,y:deskY-20,msg:msgs[d.key]||'Please handle this.'})),{key:'__back',x:W/2,y:H*0.14,msg:'All briefed!'}].filter(s=>s.key!=='__dummy');
1083
  const visited=new Set(); let curMsg='';
1084
 
1085
+ function drawPerson(x,y,hair,shirt,color,scale=1){
1086
+ const s=scale;
1087
+ // Hair
1088
+ ctx.fillStyle=hair; ctx.beginPath(); ctx.ellipse(x,y-22*s,10*s,9*s,0,0,Math.PI*2); ctx.fill();
1089
+ // Head
1090
+ ctx.fillStyle='#f5c9a0'; ctx.beginPath(); ctx.arc(x,y-16*s,9*s,0,Math.PI*2); ctx.fill();
1091
+ // Eyes
1092
+ ctx.fillStyle='#2a1a0a';
1093
+ ctx.beginPath(); ctx.arc(x-3*s,y-17*s,1.5*s,0,Math.PI*2); ctx.fill();
1094
+ ctx.beginPath(); ctx.arc(x+3*s,y-17*s,1.5*s,0,Math.PI*2); ctx.fill();
1095
+ // Body
1096
+ ctx.fillStyle=shirt;
1097
+ ctx.beginPath(); ctx.roundRect(x-8*s,y-7*s,16*s,20*s,4*s); ctx.fill();
1098
+ // Color badge
1099
+ ctx.fillStyle=color; ctx.globalAlpha=.6;
1100
+ ctx.fillRect(x-3*s,y-5*s,6*s,3*s); ctx.globalAlpha=1;
1101
+ // Legs
1102
+ ctx.fillStyle='#2a3545';
1103
+ ctx.fillRect(x-6*s,y+13*s,6*s,14*s); ctx.fillRect(x+1*s,y+13*s,6*s,14*s);
1104
+ ctx.fillStyle='#1a1a2a';
1105
+ ctx.fillRect(x-7*s,y+27*s,7*s,4*s); ctx.fillRect(x+1*s,y+27*s,7*s,4*s);
1106
  }
1107
 
1108
+ function drawDesk(d,visited){
1109
+ const dw=deskW*0.8,dh=deskH*0.7;
1110
+ const dx=d.x-dw/2,dy=d.y;
1111
+ // Desk
1112
+ ctx.fillStyle=visited?'#d4a870':'#c8a060';
1113
+ ctx.beginPath(); ctx.roundRect(dx,dy,dw,dh,4); ctx.fill();
1114
+ ctx.fillStyle='rgba(255,255,255,.15)';
1115
+ ctx.fillRect(dx,dy,dw,3);
1116
+ // Monitor
1117
+ ctx.fillStyle='#2a3545';
1118
+ ctx.fillRect(dx+8,dy+6,dw-44,dh-14);
1119
+ ctx.fillStyle=visited?d.color:'#1a2535';
1120
+ ctx.fillRect(dx+10,dy+8,dw-48,dh-18);
1121
+ // Name
1122
+ ctx.fillStyle=visited?d.color:'#8a9aaa';
1123
+ ctx.font=`bold 9px "Share Tech Mono"`;
1124
+ ctx.textAlign='center';
1125
+ ctx.fillText(d.name.toUpperCase(),d.x,dy+dh+14);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1126
  }
1127
 
1128
  function drawBubble(x,y,lines){
1129
+ const mxW=Math.max(...lines.map(l=>l.length))*6.5+18,bh=lines.length*14+14;
1130
+ const bx=Math.min(Math.max(x-mxW/2,4),W-mxW-4),by=Math.max(y-bh-8,4);
1131
+ ctx.fillStyle='white'; ctx.strokeStyle='#ddd'; ctx.lineWidth=1;
1132
+ ctx.beginPath(); ctx.roundRect(bx,by,mxW,bh,6); ctx.fill(); ctx.stroke();
1133
+ // Arrow
1134
+ ctx.beginPath(); ctx.moveTo(x-5,by+bh); ctx.lineTo(x+5,by+bh); ctx.lineTo(x,by+bh+8); ctx.closePath();
1135
+ ctx.fillStyle='white'; ctx.fill(); ctx.stroke();
1136
+ ctx.fillStyle='#1a1a2a'; ctx.font=`10px "Share Tech Mono"`; ctx.textAlign='left';
1137
+ lines.forEach((l,i)=>ctx.fillText(l,bx+9,by+12+i*14+4));
1138
  }
1139
 
1140
  function tick(){
1141
+ // BG
1142
+ ctx.fillStyle='#f2ede0'; ctx.fillRect(0,0,W,H);
1143
+ ctx.fillStyle='linear-gradient';
1144
+ // Wall
1145
+ const wallH=H*0.45;
1146
+ ctx.fillStyle='#ece7d8'; ctx.fillRect(0,0,W,wallH);
1147
+ ctx.fillStyle='#c8bc9a'; ctx.fillRect(0,wallH-6,W,6);
1148
+ // Floor
1149
+ ctx.fillStyle='#d4cdb8'; ctx.fillRect(0,wallH,W,H-wallH);
1150
+ // Floor lines
1151
+ ctx.strokeStyle='rgba(0,0,0,.04)'; ctx.lineWidth=1;
1152
+ for(let fx=0;fx<W;fx+=60){ctx.beginPath();ctx.moveTo(fx,wallH);ctx.lineTo(fx,H);ctx.stroke();}
1153
+ for(let fy=wallH;fy<H;fy+=40){ctx.beginPath();ctx.moveTo(0,fy);ctx.lineTo(W,fy);ctx.stroke();}
1154
+
1155
+ // Big screen on wall
1156
+ const scW=200,scH=120,scX=W/2-scW/2,scY=20;
1157
+ ctx.fillStyle='#2a3545'; ctx.fillRect(scX-6,scY-6,scW+12,scH+12);
1158
+ ctx.fillStyle='#1a4a2a'; ctx.fillRect(scX,scY,scW,scH);
1159
+ ctx.fillStyle='#4ade80'; ctx.font=`bold 9px "Share Tech Mono"`; ctx.textAlign='center';
1160
+ ctx.fillText('MISSION CONTROL', W/2, scY+20);
1161
+ ctx.fillStyle='#a0d4b0'; ctx.font=`8px "Share Tech Mono"`;
1162
+ const taskShort=task.substring(0,30)+(task.length>30?'...':'');
1163
+ ctx.fillText(taskShort, W/2, scY+38);
1164
+
1165
+ // Desks + seated agents
1166
+ desks.forEach(d=>{
1167
+ drawDesk(d,visited.has(d.key));
1168
+ // Seated agent at desk
1169
+ drawPerson(d.x, d.y-18, d.hair, d.shirt, d.color, 0.75);
1170
+ });
1171
+
1172
+ // Walking manager
1173
+ const bob=phase==='walking'?Math.sin(frame*.4)*2:0;
1174
+ drawPerson(Math.round(mx),Math.round(my+bob),manHair,manShirt,manColor,1);
1175
+
1176
+ // Speech bubble
1177
+ if(phase==='talking'&&curMsg) drawBubble(Math.round(mx),Math.round(my-10),curMsg.split('\n'));
1178
+
1179
+ // Step logic
1180
  if(stepI<steps.length){
1181
  const step=steps[stepI];
1182
+ const dx=step.x-mx,dy=step.y-my,dist=Math.sqrt(dx*dx+dy*dy);
1183
+ if(dist>3&&phase==='walking'){mx+=dx/dist*SPD;my+=dy/dist*SPD;}
1184
  else if(phase==='walking'){
1185
+ phase='talking';talkF=0;curMsg=step.msg;
1186
  if(step.key!=='__top'&&step.key!=='__back'){
1187
  visited.add(step.key);
1188
  logEl.textContent=`Briefing ${step.key}...`;
1189
+ addAct(`Manager β†’ ${step.key}`,'#3b82f6');
1190
  } else if(step.key==='__back'){
1191
+ logEl.textContent='Team briefed β€” processing!';
1192
  }
1193
  }
1194
  if(phase==='talking'){talkF++;if(talkF>TALK){phase='walking';stepI++;curMsg='';}}
1195
  } else {
1196
+ setTimeout(()=>{ov.classList.remove('show');onDone();},300);return;
1197
  }
1198
+ frame++;
1199
+ requestAnimationFrame(tick);
 
1200
  }
1201
  tick();
1202
  }
1203
 
1204
+ // ── RESULTS PANEL ──────────────────────────────────────────────────────────
1205
+ function showAllResults(results, events){
1206
+ const panel=document.getElementById('result-panel');
1207
+ const grid=document.getElementById('rp-grid');
1208
+ panel.classList.add('show');
1209
+ grid.innerHTML=agentDefs.map(agent=>{
1210
+ const r=results[agent.key];
1211
+ if(!r||r.status==='idle') return '';
1212
+ const color=AGENT_COLORS[agent.key]||'#3b82f6';
1213
+ const msg=(r.message||'').substring(0,400)+(r.message?.length>400?'...':'');
1214
+ let extra='';
1215
+ if(r.doc_file){
1216
+ const ext=r.doc_file.split('.').pop().toLowerCase();
1217
+ const label={xlsx:'Excel',docx:'Word',html:'HTML',py:'Python',groovy:'Groovy',jpg:'Image'}[ext]||'File';
1218
+ extra+=`<a class="dl-btn" href="/api/docs/${r.doc_file}" download>&#8595; ${label}</a>`;
1219
+ }
1220
+ if(r.img_base&&r.img_count>0){
1221
+ const thumbs=Array.from({length:r.img_count},(_,i)=>`<img class="img-th" src="/api/docs/${r.img_base}_img${i+1}.jpg" onclick="openLbox('/api/docs/${r.img_base}_img${i+1}.jpg','img${i+1}.jpg')"/>`).join('');
1222
+ extra+=`<div class="img-row">${thumbs}</div>`;
1223
+ }
1224
+ return `<div class="rp-card">
1225
+ <div class="rp-name"><div class="rp-dot" style="background:${color}"></div><span style="color:${color}">${agent.name}</span></div>
1226
+ <div class="rp-text">${msg||'Completed.'}</div>
1227
+ ${r.model?`<div class="rp-model">via ${r.model}</div>`:''}
1228
+ ${extra}
1229
+ </div>`;
1230
+ }).filter(Boolean).join('');
1231
+ if(events&&events.length){
1232
+ const evHtml=events.map(e=>`<div class="orch-ev"><span class="orch-t">${e.time}</span><span>${e.msg}</span></div>`).join('');
1233
+ document.getElementById('orch-log-wrap').innerHTML=`<div class="orch-log"><div class="orch-title">LOG</div>${evHtml}</div>`;
1234
+ }
1235
+ }
1236
 
1237
+ // ── LIGHTBOX ───────────────────────────────────────────────────────────────
1238
+ function openLbox(src,fn){document.getElementById('lbox-img').src=src;document.getElementById('lbox-dl').href=src;document.getElementById('lbox-dl').download=fn;document.getElementById('lbox').classList.add('show');}
1239
+ function closeLbox(){document.getElementById('lbox').classList.remove('show');}
1240
+
1241
+ // ── MODAL ──────────────────────────────────────────────────────────────────
1242
+ function openModal(){document.getElementById('modal-bg').classList.add('show');}
1243
+ function closeModal(){document.getElementById('modal-bg').classList.remove('show');}
1244
  async function submitAgent(){
1245
  const key=document.getElementById('m-key').value.trim();
1246
  const name=document.getElementById('m-name').value.trim()||key;
 
1251
  const d=await r.json();
1252
  if(d.success){
1253
  agentDefs.push({key:d.agent.key,name:d.agent.name,role:d.agent.role});
1254
+ agentStates[d.agent.key]={status:'idle',message:'',model:'',doc_file:null,img_base:null,img_count:0};
1255
+ agentModes[d.agent.key]='idle';
1256
+ renderDesks(); renderAgents(); startIdleLife();
1257
+ addAct(`Agent "${name}" added`,'#a855f7'); closeModal();
1258
  ['m-key','m-name','m-role'].forEach(id=>document.getElementById(id).value='');
1259
  }
1260
  }
 
1268
  document.getElementById('prog').classList.add('on');
1269
  document.getElementById('top-status').textContent='Running';
1270
  document.getElementById('top-dot').className='sdot da';
1271
+ document.getElementById('result-panel').classList.remove('show');
1272
+ updateScreen('MISSION ACTIVE', task.substring(0,60));
1273
 
1274
+ // Set all working
1275
+ agentDefs.forEach(a=>{
1276
+ agentStates[a.key]={status:'working',message:'',model:'',doc_file:null,img_base:null,img_count:0};
1277
+ agentModes[a.key]='working';
1278
+ const seat=seatPosition(a.key);
1279
+ agentPositions[a.key]={x:seat.x,y:seat.y};
1280
+ agentTargets[a.key]=null;
1281
+ updateAgentEl(a.key);
1282
  });
1283
+ addAct(`Mission: "${task.substring(0,38)}"`,'#3b82f6');
1284
 
 
1285
  let apiData=null;
1286
+ const fetchP=fetch('/api/mission',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({task})})
1287
+ .then(r=>r.json()).then(d=>{apiData=d;}).catch(e=>{apiData={error:e.message};});
 
1288
 
 
1289
  const lo=task.toLowerCase();
1290
  let qd=[];
1291
+ if(/imagen|image|foto|gato|cat|dog|perro|picture|dibuja|genera/.test(lo)) qd.push('image_agent');
1292
  if(/informe|report|word|documento/.test(lo)){qd.push('writer');qd.push('analyst');}
1293
  if(/excel|planilla|spreadsheet|registro/.test(lo)) qd.push('backend_dev');
1294
  if(/python|script|groovy|jenkins|backend|api/.test(lo)) qd.push('backend_dev');
1295
  if(/html|css|frontend|web|interfaz/.test(lo)) qd.push('frontend_dev');
1296
+ if(!qd.length) qd=['analyst'];
1297
 
1298
  runWalkAnimation(task, qd, async()=>{
1299
+ // Stagger speech bubbles while waiting
1300
+ agentDefs.forEach((a,i)=>{
1301
+ setTimeout(()=>{
1302
+ const phrases=SPEECH_WORK[a.key]||['Working...'];
1303
+ showBubble(a.key,phrases[Math.floor(Math.random()*phrases.length)],3000);
1304
+ },i*600);
1305
+ });
1306
+
1307
  await fetchP;
1308
 
1309
  if(!apiData||apiData.error){
1310
+ agentDefs.forEach(a=>{
1311
+ agentStates[a.key]={status:'resting',message:apiData?.error||'Error',model:'',doc_file:null,img_base:null,img_count:0};
1312
+ agentModes[a.key]='idle'; updateAgentEl(a.key);
1313
+ });
1314
+ updateScreen('ERROR',apiData?.error||'Mission failed');
1315
+ addAct(`Error: ${apiData?.error}`,'#ef4444');
1316
+ notify('Mission Failed',apiData?.error||'Error','#ef4444');
1317
  document.getElementById('top-dot').className='sdot dr';
1318
  document.getElementById('top-status').textContent='Error';
1319
+ btn.disabled=false; document.getElementById('prog').classList.remove('on'); return;
 
1320
  }
1321
 
1322
+ let anyResting=false, hasFile=false, screenText='';
 
1323
  agentDefs.forEach(a=>{
1324
  const r=apiData.results[a.key];
1325
+ if(!r){agentStates[a.key]={status:'idle',message:'',model:'',doc_file:null,img_base:null,img_count:0};return;}
1326
  if(r.status==='resting') anyResting=true;
1327
+ if(r.doc_file) hasFile=true;
1328
+ agentStates[a.key]={
1329
+ status:r.status==='resting'?'resting':r.status==='idle'?'idle':'active',
1330
+ message:r.message||'', model:r.model||'',
1331
+ doc_file:r.doc_file||null, img_base:r.img_base||null, img_count:r.img_count||0,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1332
  };
1333
+ agentModes[a.key]='idle';
1334
+ updateAgentEl(a.key);
1335
+ if(r.message&&r.status!=='resting') screenText+=(agentDefs.find(x=>x.key===a.key)?.name||a.key)+': '+r.message.substring(0,60)+'\n';
1336
+ // Celebration bubble
1337
+ if(r.status!=='resting'&&r.status!=='idle'){
1338
+ setTimeout(()=>showBubble(a.key,'Done! βœ“',2000),Math.random()*1000);
 
 
 
 
 
1339
  }
1340
  });
1341
 
1342
+ updateScreen('MISSION COMPLETE', screenText || 'All agents responded.');
1343
+ showAllResults(apiData.results, apiData.events);
1344
+
1345
  mCount++;
1346
  document.getElementById('mc-count').textContent=`MISSIONS: ${mCount}`;
1347
+ document.getElementById('top-active').textContent='0 active';
1348
  history.push({task,time:new Date().toLocaleTimeString(),ok:!anyResting,doc_file:apiData.doc_file});
1349
  updateHistory();
1350
+ addAct('Mission complete','#22c55e');
1351
 
1352
  setTimeout(()=>notify(
1353
+ anyResting?'Mission Partial':hasFile?'Files Ready βœ“':'Mission Complete βœ“',
1354
+ anyResting?'Some agents rate-limited.':hasFile?'Click agents to see results.':'All agents responded.',
1355
+ anyResting?'#f59e0b':hasFile?'#a855f7':'#22c55e'
1356
+ ),500);
1357
 
1358
  document.getElementById('top-status').textContent=anyResting?'Partial':'Done';
1359
  document.getElementById('top-dot').className=`sdot ${anyResting?'da':'dg'}`;
1360
+ btn.disabled=false; document.getElementById('prog').classList.remove('on');
 
 
1361
  });
1362
  }
1363
 
1364
  document.getElementById('task-input').addEventListener('keydown',e=>{if(e.key==='Enter')launchMission();});
1365
+
1366
+ // Add CSS keyframe for rack-led
1367
+ const style=document.createElement('style');
1368
+ style.textContent=`@keyframes led-blink{0%{opacity:.25}100%{opacity:1;box-shadow:0 0 4px currentColor}}`;
1369
+ document.head.appendChild(style);
1370
+
1371
+ // INIT
1372
+ window.addEventListener('resize',positionScreenElements);
1373
+ init();
1374
  </script>
1375
  </body>
1376
+ </html>