@danieltmn/openbridge 0.6.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1374 +1,1468 @@
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, viewport-fit=cover">
6
- <meta name="theme-color" content="#0a0d12" id="themeColor">
7
- <title>OpenBridge</title>
8
- <link rel="manifest" href="manifest.webmanifest">
9
- <link rel="icon" href="icons/icon-192x192.png">
10
- <link rel="apple-touch-icon" href="icons/icon-192x192.png">
11
- <link rel="apple-touch-icon" sizes="152x152" href="icons/icon-152x152.png">
12
- <link rel="stylesheet" href="themes/themes.css" id="themeStylesheet">
13
- <style>
14
- :root {
15
- --font: ui-monospace, "Cascadia Code", "JetBrains Mono", "SF Mono", "Fira Code", Consolas, "Roboto Mono", monospace;
16
- /* Resaltado de código (paleta VSCode dark) */
17
- --hl-cm: #6a9955;
18
- --hl-kw: #569cd6;
19
- --hl-k2: #c586c9;
20
- --hl-st: #ce9178;
21
- --hl-nu: #b5cea8;
22
- --hl-fn: #dcdcaa;
23
- --hl-ty: #4ec9b0;
24
- --hl-pr: #9cdcfe;
25
- }
26
- body[data-theme="light"], body[data-theme="solarized"],
27
- body[data-theme="light-plus"], body[data-theme="github-light"] {
28
- --hl-cm: #008000;
29
- --hl-kw: #0000ff;
30
- --hl-k2: #af00db;
31
- --hl-st: #a31515;
32
- --hl-nu: #098658;
33
- --hl-fn: #795e26;
34
- --hl-ty: #267f79;
35
- --hl-pr: #001080;
36
- }
37
- * { box-sizing: border-box; }
38
- html, body { height: 100%; }
39
- body {
40
- margin: 0;
41
- font-family: var(--font);
42
- font-size: 15px;
43
- line-height: 1.55;
44
- background: var(--bg);
45
- color: var(--text);
46
- display: flex;
47
- flex-direction: row;
48
- min-height: 100vh;
49
- min-height: 100dvh;
50
- }
51
- a { color: var(--accent); }
52
- b { font-weight: 600; }
53
- ::selection { background: var(--accent-soft); }
54
- ::-webkit-scrollbar { width: 8px; height: 8px; }
55
- ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
56
- ::-webkit-scrollbar-track { background: transparent; }
57
- .code { font-family: var(--font); }
58
-
59
- /* ----- Sidebar: navegador de proyectos y sesiones ----- */
60
- #sidebar {
61
- width: var(--sb-w, 264px);
62
- position: relative;
63
- background: var(--rail, var(--panel));
64
- border-right: 1px solid var(--border);
65
- display: flex;
66
- flex-direction: column;
67
- flex-shrink: 0;
68
- transition: transform .22s ease;
69
- z-index: 30;
70
- }
71
- #sidebar .brand {
72
- min-height: 54px;
73
- padding: 12px 14px 10px;
74
- display: flex;
75
- align-items: center;
76
- gap: 10px;
77
- border-bottom: 1px solid var(--border);
78
- }
79
- #sidebar .brand .logo {
80
- font-family: var(--font);
81
- color: var(--mine);
82
- font-weight: 700;
83
- font-size: 15px;
84
- letter-spacing: -.05em;
85
- }
86
- #sidebar .brand .name { font-weight: 600; font-size: 14px; letter-spacing: .02em; }
87
- #sidebar .brand .ver { margin-left: auto; font-size: 11px; color: var(--muted); }
88
- #sidebar .brand { cursor: pointer; }
89
- #sidebar .brand:hover { background: var(--accent-soft); }
90
- #sidebar .sbhide {
91
- display: inline-block;
92
- background: transparent; border: 0; color: var(--muted); cursor: pointer;
93
- font: inherit; font-size: 13px; line-height: 1; padding: 5px 7px;
94
- border-radius: 6px; flex-shrink: 0;
95
- }
96
- #sidebar .sbhide:hover { color: var(--text); background: var(--accent-soft); }
97
- #sidebar .resizer {
98
- display: none;
99
- position: absolute; top: 0; bottom: 0; right: -3px; width: 6px;
100
- cursor: col-resize; z-index: 31; touch-action: none;
101
- }
102
- #sidebar .resizer:hover, #sidebar .resizer.dragging { background: var(--accent-soft); }
103
- .sb-reveal {
104
- display: none;
105
- position: fixed; top: 50%; left: 0; transform: translateY(-50%);
106
- background: var(--panel); border: 1px solid var(--border); border-left: 0;
107
- color: var(--muted); border-radius: 0 8px 8px 0;
108
- padding: 16px 5px; font: inherit; font-size: 12px; line-height: 1;
109
- cursor: pointer; z-index: 31;
110
- }
111
- .sb-reveal:hover { color: var(--accent); background: var(--accent-soft); }
112
- .newsession {
113
- margin: 10px 10px 8px;
114
- padding: 9px 12px;
115
- background: transparent;
116
- border: 1px dashed var(--mine);
117
- color: var(--mine);
118
- border-radius: 8px;
119
- font: inherit;
120
- font-weight: 600;
121
- text-align: left;
122
- cursor: pointer;
123
- }
124
- .newsession:hover { background: var(--mine-soft, var(--accent-soft)); }
125
- .sidesearch { padding: 0 10px 8px; }
126
- .sidesearch input {
127
- width: 100%;
128
- padding: 7px 10px;
129
- background: var(--bg);
130
- border: 1px solid var(--border);
131
- border-radius: 8px;
132
- color: var(--text);
133
- font: inherit;
134
- font-size: 13px;
135
- outline: none;
136
- }
137
- .sidesearch input:focus { border-color: var(--accent); }
138
- .sidesearch input::placeholder { color: var(--muted); }
139
- /* ----- Selector de puente (computadora) ----- */
140
- #bridgeBar { display: none; padding: 0 10px 8px; gap: 6px; }
141
- #bridgeBar.show { display: flex; flex-wrap: wrap; }
142
- #bridgeBar .bb {
143
- flex: 1 1 auto; min-width: 0;
144
- display: flex; align-items: center; gap: 6px;
145
- background: transparent; border: 1px solid var(--border); color: var(--muted);
146
- border-radius: 8px; padding: 5px 9px; font: inherit; font-size: 11.5px; cursor: pointer;
147
- white-space: nowrap;
148
- }
149
- #bridgeBar .bb:hover { color: var(--text); }
150
- #bridgeBar .bb.on { color: var(--text); border-color: var(--accent); background: var(--accent-soft); }
151
- #bridgeBar .bb .bdot { width: 6px; height: 6px; border-radius: 50%; background: var(--muted); flex-shrink: 0; }
152
- #bridgeBar .bb .bdot.on { background: var(--ok, #22c55e); box-shadow: 0 0 4px var(--ok, #22c55e); }
153
- #bridgeBar .bb .bname { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
154
- #bridgeBar .bb .bbusy { font-size: 10px; color: var(--accent); flex-shrink: 0; }
155
- #sessionTree { flex: 1; overflow-y: auto; padding: 2px 6px 10px; min-height: 0; }
156
- .utilities { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 10px 10px; }
157
- .utilities button {
158
- flex: 1 1 64px;
159
- min-width: 64px;
160
- background: transparent;
161
- border: 1px solid var(--border);
162
- color: var(--muted);
163
- border-radius: 8px;
164
- padding: 6px 4px;
165
- font: inherit;
166
- font-size: 11.5px;
167
- cursor: pointer;
168
- white-space: nowrap;
169
- }
170
- .utilities button:hover { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
171
- .proj { margin-top: 2px; }
172
- .prow { display: flex; align-items: center; }
173
- .prow .proj-head { flex: 1; min-width: 0; width: auto; padding: 6px 4px 6px 8px; }
174
- .prow .proj-head .chev { width: 12px; }
175
- .prow.active .proj-head .pname { color: var(--accent); }
176
- .prow .padd {
177
- background: transparent; border: 1px solid var(--border); color: var(--muted);
178
- border-radius: 6px; width: 22px; height: 22px; line-height: 1;
179
- font: inherit; font-size: 14px; cursor: pointer; flex-shrink: 0; padding: 0;
180
- margin-right: 8px;
181
- }
182
- .prow .padd:hover { color: var(--mine); border-color: var(--mine); background: var(--mine-soft, var(--accent-soft)); }
183
- .proj-head {
184
- display: flex;
185
- align-items: center;
186
- gap: 7px;
187
- width: 100%;
188
- background: transparent;
189
- border: 0;
190
- color: var(--muted);
191
- font: inherit;
192
- font-size: 12.5px;
193
- letter-spacing: .04em;
194
- padding: 8px 8px 6px;
195
- cursor: pointer;
196
- text-align: left;
197
- }
198
- .proj-head:hover { color: var(--text); }
199
- .proj-head .chev { width: 10px; display: inline-block; transition: transform .12s ease; }
200
- .proj-head.open .chev { transform: rotate(90deg); }
201
- .proj-head .pname { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); font-weight: 600; }
202
- .proj-head .pcount { font-size: 11.5px; color: var(--muted); }
203
- .proj-sessions { display: none; }
204
- .proj.open .proj-sessions { display: block; }
205
- .proj-head .padd {
206
- background: transparent; border: 1px solid var(--border); color: var(--muted);
207
- border-radius: 6px; width: 22px; height: 22px; line-height: 1;
208
- font: inherit; font-size: 14px; cursor: pointer; flex-shrink: 0; padding: 0;
209
- }
210
- .proj-head .padd:hover { color: var(--mine); border-color: var(--mine); background: var(--mine-soft, var(--accent-soft)); }
211
- .sitem {
212
- display: flex;
213
- align-items: center;
214
- gap: 8px;
215
- width: 100%;
216
- background: transparent;
217
- border: 0;
218
- border-left: 2px solid transparent;
219
- color: var(--text);
220
- font: inherit;
221
- font-size: 12.5px;
222
- padding: 8px 8px 8px 12px;
223
- cursor: pointer;
224
- text-align: left;
225
- border-radius: 0 8px 8px 0;
226
- }
227
- .sitem:hover { background: var(--accent-soft); }
228
- .sitem.active {
229
- background: var(--accent-soft);
230
- border-left-color: var(--accent);
231
- color: var(--accent);
232
- font-weight: 600;
233
- }
234
- .sitem .sname { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; opacity: .78; }
235
- .sitem.active .sname { opacity: 1; }
236
- .sitem .stok { font-size: 10.5px; color: var(--muted); flex-shrink: 0; }
237
- .sitem .scost { font-size: 10.5px; color: var(--warn-text, #d29922); flex-shrink: 0; }
238
- .sitem .stime { font-size: 11px; color: var(--muted); flex-shrink: 0; }
239
- .sitem .spen { width: 6px; height: 6px; border-radius: 50%; background: var(--mine); flex-shrink: 0; }
240
- .sdot {
241
- display: inline-block; width: 6px; height: 6px; border-radius: 50%;
242
- background: var(--mine); flex-shrink: 0; vertical-align: middle;
243
- }
244
- .sbusy {
245
- display: inline-block; width: 7px; height: 7px; border-radius: 50%;
246
- background: var(--accent);
247
- box-shadow: 0 0 5px var(--accent);
248
- vertical-align: middle;
249
- animation: sbusyPulse 1.1s ease-in-out infinite;
250
- }
251
- @keyframes sbusyPulse {
252
- 0%, 100% { opacity: 1; }
253
- 50% { opacity: .3; }
254
- }
255
- .sitem .sfolder {
256
- font-size: 9.5px; color: var(--muted);
257
- border: 1px solid var(--border); border-radius: 3px;
258
- padding: 1px 4px; flex-shrink: 0; max-width: 92px;
259
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
260
- }
261
- .sitem.active .sfolder { color: var(--accent); border-color: var(--accent); }
262
- .proj.todas .pname { text-transform: uppercase; font-size: 10.5px; letter-spacing: .06em; }
263
- .tree-empty { color: var(--muted); font-size: 12.5px; padding: 14px 10px; text-align: center; }
264
- #sidebar .footer {
265
- padding: 10px 12px;
266
- border-top: 1px solid var(--border);
267
- display: flex; flex-direction: column; gap: 6px;
268
- font-size: 11.5px;
269
- color: var(--muted);
270
- }
271
- #sidebar .footer .row { display: flex; align-items: center; gap: 8px; }
272
- #sidebar .footer .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); flex-shrink: 0; }
273
- #sidebar .footer .dot.on { background: var(--ok, #22c55e); }
274
- #sidebar .footer .dot.off { background: var(--danger); }
275
- #sidebar .footer a.logout {
276
- color: var(--danger);
277
- text-decoration: none;
278
- padding: 6px 10px;
279
- border-radius: 8px;
280
- text-align: center;
281
- border: 1px solid var(--border);
282
- }
283
- #sidebar .footer .userbadge {
284
- flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
285
- color: var(--muted); font-size: 12px;
286
- }
287
- #sidebar .footer .userbadge.admin { color: var(--accent); }
288
- #scrim {
289
- display: none;
290
- position: fixed; inset: 0;
291
- background: rgba(0, 0, 0, .6);
292
- z-index: 25;
293
- }
294
-
295
- /* ----- Main ----- */
296
- main {
297
- flex: 1; min-width: 0;
298
- display: flex; flex-direction: column;
299
- background: var(--bg);
300
- }
301
- header {
302
- display: flex; align-items: center; gap: 10px;
303
- padding: 8px 14px;
304
- min-height: 46px;
305
- background: var(--panel);
306
- border-bottom: 1px solid var(--border);
307
- position: sticky; top: 0; z-index: 10;
308
- }
309
- header .burger {
310
- background: transparent;
311
- color: var(--text);
312
- border: 1px solid var(--border);
313
- border-radius: 8px;
314
- padding: 5px 10px;
315
- font-size: 16px; line-height: 1.2;
316
- cursor: pointer;
317
- display: none;
318
- }
319
- header .back {
320
- background: transparent; color: var(--text); border: 1px solid var(--border);
321
- border-radius: 8px; padding: 4px 10px; font-size: 14px; cursor: pointer; font-family: var(--font);
322
- }
323
- header .back:hover { background: var(--accent-soft); }
324
- header .left { display: flex; align-items: center; gap: 10px; min-width: 0; flex: 1; }
325
- header .titles { min-width: 0; }
326
- header .title { font-weight: 600; font-size: 15px; line-height: 1.25; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
327
- header .sub { font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden;
328
- text-overflow: ellipsis; max-width: 58vw; }
329
- header .actions { display: flex; align-items: center; gap: 8px; }
330
- header .icbtn {
331
- background: transparent; color: var(--text); border: 1px solid var(--border);
332
- border-radius: 8px; padding: 5px 10px; font-size: 13px;
333
- cursor: pointer; line-height: 1.2; font-family: var(--font);
334
- min-width: 34px; min-height: 32px;
335
- }
336
- header .icbtn:hover { background: var(--accent-soft); color: var(--accent); }
337
- #btnPush { display: none; align-items: center; justify-content: center; }
338
- #btnPush svg { display: block; width: 16px; height: 16px; }
339
- #btnPush.on { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
340
- #btnPush.denied { color: var(--danger); border-color: var(--danger); }
341
- header a.logout {
342
- display: none;
343
- color: var(--danger); text-decoration: none; font-size: 12px;
344
- padding: 5px 10px; border: 1px solid var(--border); border-radius: 8px;
345
- }
346
-
347
- #cmdsPanel {
348
- display: none; position: fixed; top: 54px; right: 12px; z-index: 40;
349
- background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
350
- min-width: 280px; max-width: 92vw; max-height: 72vh; overflow-y: auto; padding: 8px;
351
- box-shadow: var(--shadow);
352
- }
353
- #cmdsPanel.open { display: block; }
354
- #cmdsPanel h4 { margin: 4px 6px 8px; font-size: 11px; color: var(--muted); letter-spacing: .06em; font-weight: 600; }
355
- #cmdsPanel .item { padding: 7px 8px; border-radius: 8px; cursor: pointer; }
356
- #cmdsPanel .item:hover { background: var(--accent-soft); }
357
- #cmdsPanel .item b { color: var(--mine); font-size: 13px; font-weight: 600; }
358
- #cmdsPanel .item span { display: block; color: var(--muted); font-size: 11px; margin-top: 1px; }
359
-
360
- #skinPanel {
361
- display: none; position: fixed; top: 54px; right: 12px; z-index: 40;
362
- background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
363
- min-width: 220px; padding: 8px;
364
- box-shadow: var(--shadow);
365
- }
366
- #skinPanel.open { display: block; }
367
- #skinPanel h4 { margin: 4px 6px 8px; font-size: 11px; color: var(--muted); letter-spacing: .06em; font-weight: 600; }
368
- #skinPanel .item {
369
- padding: 8px 10px; border-radius: 8px; cursor: pointer;
370
- display: flex; align-items: center; gap: 10px;
371
- }
372
- #skinPanel .item:hover { background: var(--accent-soft); }
373
- #skinPanel .item.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
374
- #skinPanel .sw {
375
- width: 20px; height: 20px; border-radius: 5px;
376
- border: 1px solid var(--border);
377
- background: linear-gradient(135deg, var(--swatch-a, var(--bg)) 0 50%, var(--swatch-b, var(--mine)) 50% 100%);
378
- }
379
-
380
- /* ----- Tab de procesos (dev servers de la sesión activa) ----- */
381
- #viewDevices .dev-add { display: flex; gap: 8px; margin: 10px 0; }
382
- #viewDevices .dev-add input { flex: 1; min-width: 0; padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--panel); color: var(--text); font: inherit; }
383
- #viewDevices .dev-add input:focus { border-color: var(--accent); outline: none; }
384
- #viewDevices .device { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px; margin-bottom: 8px; background: var(--panel); }
385
- #viewDevices .device .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok, #22c55e); flex-shrink: 0; }
386
- #viewDevices .device .dnote { color: var(--muted); font-size: 11.5px; }
387
- #viewDevices .device .linkbtn { margin-left: auto; }
388
- #viewDevices code { background: var(--panel); border: 1px solid var(--border); border-radius: 6px; padding: 1px 6px; font-size: 11.5px; }
389
- #viewProcs .pp-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
390
- #viewProcs .pp-head b { font-size: 16px; font-weight: 600; }
391
- #viewProcs .pp-head b::before { content: "❯ "; color: var(--mine); }
392
- #viewProcs .pp-sub { font-size: 11.5px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
393
- #viewProcs .pp-back { margin-left: auto; flex-shrink: 0; }
394
- #procStatus { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 12.5px; color: var(--muted); margin-bottom: 10px; }
395
- #procStatus .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); flex-shrink: 0; }
396
- #procStatus .dot.on { background: var(--ok, #22c55e); }
397
- #procStatus .dot.off { background: var(--danger); }
398
- #procStatus .pp-cmd { font-family: var(--font); color: var(--text); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
399
- #procStatus .linkbtn { flex-shrink: 0; }
400
- .pp-warn {
401
- display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
402
- padding: 8px 12px; margin-bottom: 10px; font-size: 12.5px;
403
- color: var(--warn-text, #fbbf24); background: var(--warn-bg);
404
- border: 1px solid var(--warn-border); border-radius: 10px;
405
- }
406
- .pp-warn b { color: var(--warn-text, #fbbf24); font-family: var(--font); }
407
- .pp-warn .linkbtn { margin-left: auto; }
408
- #procLog {
409
- margin: 0; padding: 12px 14px; height: 46vh; overflow: auto;
410
- background: var(--code-bg, var(--bg));
411
- border: 1px solid var(--border); border-radius: 10px;
412
- font-family: var(--font); font-size: 12px; line-height: 1.55; color: var(--text);
413
- white-space: pre-wrap; word-break: break-word; user-select: text;
414
- }
415
- #procLog .pl-empty { color: var(--muted); font-style: italic; }
416
- .pp-form { display: flex; gap: 6px; margin-top: 10px; }
417
- .pp-form input[type="text"] {
418
- flex: 1 1 160px; padding: 9px 10px; background: var(--bg); border: 1px solid var(--border);
419
- border-radius: 8px; color: var(--text); font: inherit; font-size: 13px; outline: none; min-width: 0;
420
- }
421
- .pp-form input[type="text"]:focus { border-color: var(--accent); }
422
- .pp-form input:disabled, .pp-form button:disabled { opacity: .5; cursor: default; }
423
- .pp-chips { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
424
- .pp-chips .chip {
425
- background: transparent; border: 1px solid var(--border); color: var(--muted);
426
- border-radius: 999px; padding: 3px 10px; font: inherit; font-size: 11.5px; cursor: pointer;
427
- }
428
- .pp-chips .chip:hover { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
429
- .pp-chips .chip:disabled { opacity: .5; cursor: default; }
430
- .pp-msg { font-size: 12.5px; color: var(--muted); line-height: 1.5; }
431
- #viewProcs .pp-tunnel { border-top: 1px solid var(--border); margin-top: 14px; padding-top: 10px; }
432
- #viewProcs .pp-tunhead { font-size: 11px; color: var(--muted); letter-spacing: .05em; margin-bottom: 6px; }
433
- .pp-tunform { display: flex; gap: 6px; margin-bottom: 6px; }
434
- .pp-tunform input[type="text"] {
435
- flex: 1 1 120px; min-width: 0; padding: 7px 10px; background: var(--bg);
436
- border: 1px solid var(--border); border-radius: 8px; color: var(--text);
437
- font: inherit; font-size: 13px; outline: none;
438
- }
439
- .pp-tunform input[type="text"]:focus { border-color: var(--accent); }
440
- #ppTunList .tunnel { margin-bottom: 6px; padding: 8px 10px; }
441
- #ppTunList .tunnel:last-child { margin-bottom: 0; }
442
-
443
- #offline {
444
- text-align: center; font-size: 12.5px; color: var(--warn-text, #fbbf24); line-height: 1.4;
445
- background: var(--warn-bg); border-bottom: 1px solid var(--warn-border);
446
- padding: 7px 12px;
447
- }
448
-
449
- /* ----- Statusbar (estilo TUI) ----- */
450
- #statusbar {
451
- display: flex; align-items: center; gap: 10px;
452
- padding: 4px 14px calc(4px + env(safe-area-inset-bottom));
453
- background: var(--panel);
454
- border-top: 1px solid var(--border);
455
- font-size: 12.5px;
456
- color: var(--muted);
457
- white-space: nowrap; overflow: hidden;
458
- flex-shrink: 0;
459
- }
460
- #statusbar .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--muted); flex-shrink: 0; }
461
- #statusbar .dot.on { background: var(--ok, #22c55e); }
462
- #statusbar .dot.off { background: var(--danger); }
463
- #statusbar .sep { color: var(--border); }
464
- #statusbar .seg { overflow: hidden; text-overflow: ellipsis; }
465
- #statusbar .seg.mode { color: var(--mine); font-weight: 600; }
466
- #statusbar .seg.mode.remoto { color: var(--accent); }
467
- #sbModel {
468
- background: transparent; border: 0; padding: 2px 0;
469
- font: inherit; color: var(--muted); cursor: pointer;
470
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
471
- border-bottom: 1px dotted var(--border);
472
- }
473
- #sbModel:hover { color: var(--accent); border-bottom-color: var(--accent); }
474
- #sbModel.flash { animation: sbflash 1.4s ease; }
475
- @keyframes sbflash {
476
- 0% { color: var(--mine); border-bottom-color: var(--mine); }
477
- 100% { color: var(--muted); border-bottom-color: var(--border); }
478
- }
479
-
480
- /* ----- Home ----- */
481
- #home { flex: 1; overflow-y: auto; padding: 18px 16px; max-width: 980px; margin: 0 auto; width: 100%; min-height: 0; }
482
- #home .hero { display: flex; flex-direction: column; gap: 4px; margin-bottom: 14px; }
483
- #home .hero h1 { margin: 0; font-size: 18px; font-weight: 600; }
484
- #home .hero h1::before { content: "❯ "; color: var(--mine); }
485
- #home .hero p { margin: 0; color: var(--muted); font-size: 12.5px; }
486
- #home .bridge-status {
487
- display: inline-flex; align-items: center; gap: 6px;
488
- padding: 3px 9px; border-radius: 999px;
489
- background: var(--accent-soft);
490
- color: var(--accent); font-size: 11.5px;
491
- }
492
- #home .bridge-status .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); }
493
- #home .bridge-status.off { background: var(--warn-bg); color: var(--danger); }
494
- #home .bridge-status.off .dot { background: var(--danger); }
495
- .newbtn {
496
- width: 100%; padding: 12px; border: 1px dashed var(--mine); border-radius: 10px;
497
- background: transparent; color: var(--mine); font: inherit; font-size: 14px; font-weight: 600;
498
- cursor: pointer; margin-bottom: 16px;
499
- }
500
- .newbtn:hover { background: var(--mine-soft, var(--accent-soft)); }
501
- .projsec { margin-bottom: 18px; }
502
- .projsec .pshead {
503
- display: flex; align-items: center; gap: 7px;
504
- width: 100%; margin: 0 0 8px; padding: 7px 2px;
505
- background: transparent; border: 0; border-bottom: 1px solid var(--border);
506
- color: var(--text); font: inherit; cursor: pointer; text-align: left;
507
- }
508
- .projsec .pshead .chev { width: 13px; font-size: 10px; color: var(--muted); flex-shrink: 0; }
509
- .projsec .pshead .psname { font-size: 12.5px; font-weight: 600; color: var(--text); letter-spacing: .03em; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
510
- .projsec .pshead .pscount { font-size: 11px; color: var(--muted); flex-shrink: 0; }
511
- .projsec .pbody { display: none; }
512
- .projsec.open .pbody { display: block; }
513
- .histrow { display: flex; gap: 6px; margin-top: 6px; }
514
- .histbtn {
515
- flex: 1; min-width: 0; padding: 8px; border: 1px dashed var(--border); border-radius: 8px;
516
- background: transparent; color: var(--muted); font: inherit; font-size: 12px; cursor: pointer;
517
- }
518
- .histbtn:hover { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
519
- @keyframes secflash {
520
- 0% { background: var(--accent-soft); }
521
- 100% { background: transparent; }
522
- }
523
- .projsec.flash .pshead { animation: secflash 1.5s ease-out; }
524
- .cards { display: grid; gap: 8px; grid-template-columns: minmax(0, 1fr); }
525
- .card {
526
- background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
527
- padding: 10px 12px; cursor: pointer; display: flex; gap: 12px; align-items: center;
528
- transition: border-color .15s ease;
529
- position: relative;
530
- }
531
- .card:hover { border-color: var(--accent); }
532
- .card:active { transform: scale(.995); }
533
- .card .grow { flex: 1; min-width: 0; padding-right: 30px; }
534
- .card .cname { font-weight: 600; font-size: 15px; }
535
- .card .cmeta { font-size: 12.5px; color: var(--muted); margin-top: 2px; white-space: nowrap;
536
- overflow: hidden; text-overflow: ellipsis; }
537
- .card .cpreview { font-size: 13.5px; color: var(--text); margin-top: 4px; opacity: .75;
538
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
539
- /* Menú de opciones de la tarjeta (⋮): acciones fuera del golpe principal */
540
- .card .kebab {
541
- position: absolute; top: 4px; right: 6px;
542
- background: transparent; border: 0; color: var(--muted);
543
- font: inherit; font-size: 16px; line-height: 1; cursor: pointer;
544
- padding: 7px 8px; border-radius: 6px;
545
- }
546
- .card .kebab:hover { color: var(--text); background: var(--accent-soft); }
547
- #cardMenu {
548
- display: none; position: fixed; z-index: 55;
549
- background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
550
- min-width: 170px; padding: 6px; box-shadow: var(--shadow);
551
- }
552
- #cardMenu.open { display: block; }
553
- #cardMenu .item {
554
- padding: 9px 10px; border-radius: 8px; cursor: pointer; font-size: 13px;
555
- display: flex; align-items: center; gap: 8px;
556
- }
557
- #cardMenu .item:hover { background: var(--accent-soft); }
558
- #cardMenu .item.danger { color: var(--danger); }
559
- #cardMenu .item.danger:hover { background: rgba(239, 68, 68, .12); }
560
- .empty { text-align: center; color: var(--muted); margin-top: 40px; font-size: 13px; }
561
-
562
- .fab {
563
- display: none;
564
- position: fixed;
565
- right: calc(16px + env(safe-area-inset-right));
566
- bottom: calc(56px + env(safe-area-inset-bottom));
567
- width: 52px; height: 52px;
568
- border-radius: 50%;
569
- border: 1px solid var(--mine);
570
- background: var(--panel);
571
- color: var(--mine);
572
- font-size: 24px; line-height: 1;
573
- cursor: pointer;
574
- z-index: 20;
575
- box-shadow: var(--shadow);
576
- }
577
- .fab:active { transform: scale(.96); }
578
-
579
- /* ----- Chat: bloques de terminal ----- */
580
- #chat { display: none; flex-direction: column; flex: 1; min-height: 0; position: relative; }
581
- #messages { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 10px; align-items: stretch; min-height: 0; }
582
- .msg {
583
- max-width: 100%;
584
- background: var(--panel);
585
- border: 1px solid var(--border);
586
- border-left: 2px solid var(--border);
587
- border-radius: 8px;
588
- padding: 9px 13px 10px;
589
- font-size: 15px;
590
- word-wrap: break-word; overflow-wrap: break-word;
591
- }
592
- .msg .role {
593
- display: flex; align-items: baseline; gap: 8px;
594
- font-size: 11px; letter-spacing: .05em;
595
- margin-bottom: 5px;
596
- }
597
- .msg .role .who { font-weight: 700; }
598
- .msg .role .rmeta { color: var(--muted); margin-left: auto; font-size: 10.5px; flex-shrink: 0; }
599
- .msg .role .genbadge { color: var(--warn-text, #fbbf24); }
600
- .msg .abadge {
601
- font-size: 9.5px;
602
- padding: 1px 7px;
603
- border-radius: 4px;
604
- border: 1px solid var(--border);
605
- color: var(--muted);
606
- letter-spacing: .06em;
607
- line-height: 1.5;
608
- flex-shrink: 0;
609
- }
610
- .msg .abadge.plan { color: var(--warn-text, #d29922); border-color: var(--warn-text, #d29922); background: var(--warn-bg, transparent); }
611
- .msg .abadge.build { color: #f472b6; border-color: #f472b6; background: rgba(244, 114, 182, .10); }
612
- .msg .body { white-space: pre-wrap; word-break: break-word; }
613
- .msg.mine { border-left-color: var(--mine); background: var(--mine-soft, var(--panel)); }
614
- .msg.mine .who { color: var(--mine); }
615
- .msg.theirs { border-left-color: var(--accent); }
616
- .msg.theirs .who { color: var(--accent); }
617
- .msg.theirs.plan { border-left-color: var(--warn-text, #d29922); }
618
- .msg.theirs.plan .who { color: var(--warn-text, #d29922); }
619
- .msg.error { border-left-color: var(--danger); }
620
- /* Aire extra en el cambio de turno: lo tuyo ↔ lo que responde el agente */
621
- .msg.mine + .msg.theirs,
622
- .msg.theirs + .msg.mine { margin-top: 10px; }
623
- pre.codeblock {
624
- background: var(--code-bg, var(--bg));
625
- border: 1px solid var(--border);
626
- border-radius: 6px;
627
- padding: 10px 12px;
628
- margin: 8px 0 2px;
629
- overflow-x: auto;
630
- font-size: 13.5px;
631
- line-height: 1.55;
632
- white-space: pre;
633
- }
634
- .msg code.inl {
635
- background: var(--code-bg, var(--bg));
636
- border: 1px solid var(--border);
637
- border-radius: 4px;
638
- padding: 0 4px;
639
- font-size: .92em;
640
- }
641
- /* ----- Markdown ligero ----- */
642
- .msg .body strong { color: var(--text); font-weight: 700; }
643
- .msg .body em { font-style: italic; }
644
- .msg .body del { opacity: .65; }
645
- .msg .body a { color: var(--accent); text-decoration: underline; word-break: break-all; }
646
- .msg .body .md-p { margin: 2px 0; }
647
- .msg .body .md-h { font-weight: 700; color: var(--text); margin: 10px 0 3px; }
648
- .msg .body .md-h1 { font-size: 16px; }
649
- .msg .body .md-h2 { font-size: 15px; }
650
- .msg .body .md-h3, .msg .body .md-h4 { font-size: 14px; }
651
- .msg .body ul.md-ul, .msg .body ol.md-ol { margin: 4px 0 4px; padding-left: 22px; }
652
- .msg .body .md-ul li, .msg .body .md-ol li { margin: 2px 0; }
653
- .msg .body .md-quote {
654
- border-left: 3px solid var(--border);
655
- padding: 2px 0 2px 10px;
656
- margin: 4px 0;
657
- color: var(--muted);
658
- }
659
- .msg .body .md-hr { border: 0; border-top: 1px solid var(--border); margin: 10px 0; }
660
- /* ----- Franja "opencode trabajando…" (KITT), fija sobre la barra de envío ----- */
661
- .typing {
662
- display: flex; align-items: center; gap: 10px;
663
- font-size: 13px; color: var(--muted);
664
- min-width: 0; max-width: 100%;
665
- }
666
- .typing.warn { color: var(--warn-text, #fbbf24); }
667
- .procrow {
668
- display: none; align-items: center; gap: 10px;
669
- padding: 7px 14px; min-width: 0;
670
- background: var(--panel); border-top: 1px solid var(--border);
671
- flex-shrink: 0;
672
- }
673
- .procrow.open { display: flex; animation: procIn .18s ease-out; }
674
- @keyframes procIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
675
- .procrow .typing { flex: 1 1 auto; }
676
- .proc-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
677
- .proc-time {
678
- flex-shrink: 0; min-width: 30px; text-align: right;
679
- font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums;
680
- }
681
- .procrow.warn .proc-time { color: var(--warn-text, #fbbf24); }
682
- .kitt {
683
- --kitt-w: 22%;
684
- --kitt-color: var(--accent);
685
- position: relative; flex: 0 0 auto;
686
- width: clamp(60px, 16vw, 120px); height: 16px;
687
- border: 1px solid var(--border);
688
- border-radius: 4px;
689
- background: rgba(127, 127, 127, .07);
690
- overflow: hidden;
691
- }
692
- @supports (color: color-mix(in srgb, red 50%, blue)) {
693
- .kitt { background: color-mix(in srgb, var(--muted) 7%, transparent); }
694
- }
695
- /* La luz es una estela simetrica (gradiente) con una cabeza luminosa. */
696
- .kitt-scan {
697
- position: absolute; top: 0; bottom: 0; left: 0;
698
- width: var(--kitt-w);
699
- background: linear-gradient(90deg, transparent, var(--kitt-color) 50%, transparent);
700
- animation: kittScan 1.5s ease-in-out infinite alternate;
701
- }
702
- .kitt-scan::after {
703
- content: ''; position: absolute; top: 50%; left: 50%;
704
- width: 2px; height: 8px; margin: -4px 0 0 -1px;
705
- border-radius: 1px; background: var(--kitt-color);
706
- box-shadow: 0 0 8px 2px var(--kitt-color);
707
- }
708
- @keyframes kittScan {
709
- from { left: 0; }
710
- to { left: calc(100% - var(--kitt-w)); }
711
- }
712
- @keyframes kittPulse { 0%, 100% { opacity: 1; } 50% { opacity: .4; } }
713
- /* Sin puente: gris y lenta. Alerta (>4 min): ámbar y palpitando. */
714
- .typing.pause .kitt { --kitt-color: var(--muted); }
715
- .typing.pause .kitt-scan { animation-duration: 3.5s; }
716
- .typing.pause .kitt-scan::after { box-shadow: none; }
717
- .typing.warn .kitt { --kitt-color: var(--warn-text, #fbbf24); }
718
- .typing.warn .kitt-scan {
719
- animation: kittScan 1s ease-in-out infinite alternate, kittPulse 1.4s ease-in-out infinite;
720
- }
721
- @media (prefers-reduced-motion: reduce) {
722
- .kitt-scan { animation: none; left: 50%; transform: translateX(-50%); }
723
- .typing.warn .kitt-scan { animation: kittPulse 1.4s ease-in-out infinite; }
724
- .sbusy { animation: none; opacity: 1; }
725
- .procrow.open { animation: none; }
726
- }
727
- .stopbtn {
728
- background: transparent; color: var(--danger); border: 1px solid var(--danger);
729
- border-radius: 8px; padding: 7px 11px; font: inherit; font-size: 12.5px; font-weight: 600;
730
- cursor: pointer; flex-shrink: 0; min-height: 38px; min-width: 38px;
731
- display: inline-flex; align-items: center; justify-content: center; gap: 5px;
732
- }
733
- .stopbtn:hover { background: rgba(239, 68, 68, .12); }
734
- .stopbtn:disabled { opacity: .55; cursor: default; }
735
- .stopbadge {
736
- color: var(--danger); border: 1px solid var(--danger); border-radius: 5px;
737
- font-size: 10px; padding: 1px 6px; letter-spacing: .04em;
738
- }
739
- .msg.canceled { opacity: .55; }
740
- details.reasoning {
741
- margin: 0 0 8px; border: 1px dashed var(--border); border-radius: 8px;
742
- background: rgba(128, 128, 128, .08); overflow: hidden;
743
- }
744
- details.reasoning summary {
745
- cursor: pointer; padding: 6px 10px; font-size: 12px; color: var(--muted);
746
- user-select: none; list-style-position: inside;
747
- }
748
- details.reasoning summary .rsize { font-size: 10.5px; opacity: .75; }
749
- details.reasoning .rbody {
750
- padding: 4px 12px 10px; font-size: 13px; color: var(--muted);
751
- border-top: 1px dashed var(--border);
752
- }
753
- details.reasoning .rbody p { margin: 6px 0; }
754
-
755
- form.sendbar {
756
- display: flex; align-items: flex-end; gap: 8px;
757
- padding: 8px 14px calc(8px + env(safe-area-inset-bottom));
758
- background: var(--panel); border-top: 1px solid var(--border);
759
- position: relative;
760
- }
761
- form.sendbar .prompt {
762
- color: var(--mine); font-weight: 700; font-size: 15px; user-select: none; flex-shrink: 0;
763
- }
764
- form.sendbar textarea {
765
- flex: 1; min-width: 0; resize: none;
766
- padding: 10px 2px; border: 0; border-bottom: 1px solid var(--border);
767
- background: transparent; color: var(--text); font: inherit; font-size: 16px; line-height: 1.45;
768
- outline: none; max-height: 148px; overflow-y: hidden;
769
- }
770
- form.sendbar textarea:focus { border-bottom-color: var(--mine); }
771
- form.sendbar textarea::placeholder { color: var(--muted); }
772
- form.sendbar .prompt { padding-bottom: 11px; }
773
- form.sendbar .imgbtn { margin-bottom: 3px; }
774
- .slash-panel {
775
- display: none; position: absolute; bottom: 100%; left: 10px; right: 10px;
776
- margin-bottom: 8px; background: var(--panel); border: 1px solid var(--border);
777
- border-radius: 10px; box-shadow: var(--shadow); max-height: 44vh; overflow-y: auto;
778
- padding: 6px; z-index: 35;
779
- }
780
- .slash-panel .item { padding: 8px 10px; border-radius: 8px; cursor: pointer; font-size: 13.5px; }
781
- .slash-panel .item.sel, .slash-panel .item:hover { background: var(--accent-soft); }
782
- .slash-panel .item b { color: var(--mine); font-weight: 600; }
783
- .slash-panel .item span { display: block; color: var(--muted); font-size: 11px; margin-top: 1px; }
784
- .tpl-panel .tpl-row { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: 8px; cursor: pointer; font-size: 13.5px; }
785
- .tpl-panel .tpl-row:hover { background: var(--accent-soft); }
786
- .tpl-panel .tpl-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
787
- .tpl-panel .tpl-del { background: transparent; border: 0; color: var(--muted); cursor: pointer; font: inherit; padding: 2px 6px; border-radius: 6px; flex-shrink: 0; }
788
- .tpl-panel .tpl-del:hover { color: var(--danger); }
789
- .tpl-panel .tpl-empty { color: var(--muted); font-size: 12px; padding: 8px 10px; line-height: 1.4; }
790
- .tpl-panel .tpl-save {
791
- display: block; width: 100%; margin-top: 6px; padding: 8px; border: 1px dashed var(--border);
792
- border-radius: 8px; background: transparent; color: var(--muted); font: inherit; font-size: 12.5px; cursor: pointer;
793
- }
794
- .tpl-panel .tpl-save:hover { color: var(--accent); border-color: var(--accent); }
795
- form.sendbar button {
796
- padding: 9px 16px; border: 1px solid var(--mine); border-radius: 8px; background: transparent; color: var(--mine);
797
- font: inherit; font-size: 13.5px; font-weight: 600; cursor: pointer;
798
- min-height: 40px;
799
- }
800
- form.sendbar button:hover { background: var(--mine-soft, var(--accent-soft)); }
801
- form.sendbar button:disabled { opacity: .4; cursor: not-allowed; }
802
- form.sendbar .imgbtn {
803
- background: transparent; border: 1px solid var(--border); color: var(--muted);
804
- border-radius: 8px; width: 36px; height: 34px; font-size: 14px; line-height: 1;
805
- cursor: pointer; flex-shrink: 0; font-family: var(--font);
806
- display: inline-flex; align-items: center; justify-content: center;
807
- }
808
- form.sendbar .imgbtn:hover { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); }
809
- form.sendbar .imgbtn svg { flex-shrink: 0; display: block; }
810
- form.sendbar .imgbtn.listening {
811
- color: var(--danger); border-color: var(--danger); background: transparent;
812
- animation: micpulse 1.1s ease-in-out infinite;
813
- }
814
- @keyframes micpulse {
815
- 0%, 100% { box-shadow: 0 0 0 0 transparent; }
816
- 50% { box-shadow: 0 0 0 4px var(--accent-soft); }
817
- }
818
- /* ----- Botón que abre la hoja de escritura (móvil) ----- */
819
- .composeopen {
820
- display: none; align-items: center; gap: 10px; width: 100%;
821
- padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
822
- background: var(--panel); border-top: 1px solid var(--border);
823
- color: var(--muted); font: inherit; font-size: 15px;
824
- cursor: pointer; text-align: left; flex-shrink: 0;
825
- }
826
- .composeopen .prompt { color: var(--mine); font-weight: 700; font-size: 15px; user-select: none; flex-shrink: 0; }
827
- .composeopen.show { display: flex; }
828
- @media (min-width: 1024px) { .composeopen.show { display: none; } }
829
- form.sendbar .shead { display: none; }
830
- /* ----- Móvil: la barra de envío es una hoja que emerge desde abajo ----- */
831
- @media (max-width: 1023px) {
832
- form.sendbar {
833
- position: fixed; left: 0; right: 0; bottom: 0;
834
- z-index: 46; flex-wrap: wrap;
835
- border-radius: 14px 14px 0 0;
836
- padding: 0 14px calc(12px + env(safe-area-inset-bottom));
837
- transform: translateY(105%); visibility: hidden;
838
- transition: transform .2s ease, visibility .2s;
839
- box-shadow: 0 -8px 24px rgba(0, 0, 0, .35);
840
- }
841
- form.sendbar.open { transform: none; visibility: visible; }
842
- form.sendbar .shead {
843
- display: flex; align-items: center; justify-content: center;
844
- position: relative; flex: 1 1 100%; order: -3;
845
- padding: 8px 0 6px; cursor: pointer;
846
- }
847
- form.sendbar .shead .handle { width: 44px; height: 4px; border-radius: 2px; background: var(--border); }
848
- form.sendbar .shead .cclose {
849
- position: absolute; right: -8px; top: 2px;
850
- background: transparent; border: 0; color: var(--muted);
851
- font: inherit; font-size: 15px; padding: 6px 8px; cursor: pointer;
852
- }
853
- form.sendbar .shead .cclose:hover { color: var(--text); }
854
- #imgPreview { order: -2; flex: 1 1 100%; padding: 6px 0 0; }
855
- form.sendbar textarea { order: -1; flex: 1 1 100%; min-height: 58px; max-height: 38vh; padding-top: 4px; }
856
- form.sendbar .imgbtn { margin-bottom: 0; }
857
- form.sendbar button[type="submit"] { margin-left: auto; }
858
- body.compose-open #scrim { display: block; z-index: 45; }
859
- }
860
- #imgPreview {
861
- display: flex; align-items: center; gap: 10px;
862
- padding: 8px 14px 0; font-size: 11.5px; color: var(--muted);
863
- }
864
- #imgPreview img {
865
- max-height: 64px; max-width: 120px; object-fit: cover;
866
- border-radius: 8px; border: 1px solid var(--border);
867
- }
868
- #imgPreview button {
869
- background: transparent; border: 1px solid var(--border); color: var(--muted);
870
- border-radius: 6px; width: 22px; height: 22px; line-height: 1; cursor: pointer; font: inherit;
871
- }
872
- #imgPreview button:hover { color: var(--danger); border-color: var(--danger); }
873
- .msg .body .msg-img {
874
- display: block; max-width: 100%; max-height: 420px;
875
- border-radius: 8px; border: 1px solid var(--border);
876
- margin: 2px 0 8px;
877
- }
878
-
879
- /* Copiar mensajes y bloques de código */
880
- .copybtn {
881
- background: transparent; border: 0; color: var(--muted); cursor: pointer;
882
- font: inherit; font-size: 11px; padding: 0 2px; flex-shrink: 0; line-height: 1.4;
883
- }
884
- .copybtn:hover { color: var(--accent); }
885
- .msg .role .copybtn { margin-left: 6px; }
886
- .codewrap {
887
- margin: 8px 0 2px; border: 1px solid var(--border); border-radius: 6px;
888
- background: var(--code-bg, var(--bg)); overflow: hidden;
889
- }
890
- .codehead {
891
- display: flex; align-items: center; justify-content: space-between; gap: 8px;
892
- padding: 4px 6px 4px 10px; border-bottom: 1px solid var(--border);
893
- background: var(--rail, rgba(255, 255, 255, .03));
894
- font-size: 11px; color: var(--muted);
895
- }
896
- .codehead .lang { letter-spacing: .04em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
897
- .codewrap pre.codeblock { border: 0; border-radius: 0; margin: 0; background: transparent; }
898
- .hl-cm { color: var(--hl-cm); font-style: italic; }
899
- .hl-kw { color: var(--hl-kw); }
900
- .hl-k2 { color: var(--hl-k2); }
901
- .hl-st { color: var(--hl-st); }
902
- .hl-nu { color: var(--hl-nu); }
903
- .hl-fn { color: var(--hl-fn); }
904
- .hl-ty { color: var(--hl-ty); }
905
- .hl-pr { color: var(--hl-pr); }
906
-
907
- /* Separadores: días en el chat, buckets de fecha en la lista */
908
- .daysep, .dsep {
909
- display: flex; align-items: center; gap: 10px;
910
- color: var(--muted); font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase;
911
- }
912
- .daysep::before, .daysep::after, .dsep::before, .dsep::after {
913
- content: ""; flex: 1; border-top: 1px solid var(--border);
914
- }
915
- .daysep { margin: 6px 0 0; }
916
- .dsep { margin: 8px 4px 4px; }
917
-
918
- /* Cursor de streaming */
919
- .stream-cursor { color: var(--accent); animation: curblink 1.05s steps(1) infinite; }
920
- @keyframes curblink { 50% { opacity: 0; } }
921
-
922
- /* Botón flotante: ir al último mensaje */
923
- .jumpbtn {
924
- display: none; position: absolute; right: 16px; bottom: 74px; z-index: 15;
925
- background: var(--panel); border: 1px solid var(--accent); color: var(--accent);
926
- border-radius: 999px; padding: 7px 13px; font: inherit; font-size: 12px; font-weight: 600;
927
- cursor: pointer; box-shadow: var(--shadow);
928
- }
929
- .jumpbtn.show { display: inline-flex; }
930
-
931
- /* Toast estilo TUI */
932
- #toast {
933
- position: fixed; left: 50%; transform: translateX(-50%) translateY(8px);
934
- bottom: calc(84px + env(safe-area-inset-bottom));
935
- background: var(--panel); border: 1px solid var(--border); color: var(--text);
936
- padding: 8px 14px; border-radius: 8px; font-size: 12.5px;
937
- opacity: 0; pointer-events: none; transition: opacity .18s ease, transform .18s ease;
938
- z-index: 60; max-width: 88vw; box-shadow: var(--shadow);
939
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
940
- }
941
- #toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
942
- #toast.ok { border-color: var(--mine); color: var(--mine); }
943
- #toast.error { border-color: var(--danger); color: var(--danger); }
944
-
945
- /* ----- Modal ----- */
946
- #overlay, #modelOverlay {
947
- display: none; position: fixed; inset: 0; background: rgba(0,0,0,.7); z-index: 50;
948
- align-items: center; justify-content: center; padding: 16px;
949
- }
950
- #overlay.show, #modelOverlay.show { display: flex; }
951
- .modal {
952
- background: var(--panel); border: 1px solid var(--border); border-radius: 12px;
953
- padding: 18px 16px; width: 100%; max-width: 400px; max-height: 90vh; overflow-y: auto;
954
- box-shadow: var(--shadow);
955
- }
956
- .modal h2 { margin: 0 0 12px; font-size: 15px; font-weight: 600; }
957
- .modal h2::before { content: "❯ "; color: var(--mine); }
958
- .modal label { display: block; font-size: 12px; color: var(--muted); margin: 11px 0 5px; }
959
- .modal input[type="text"], .modal select, .modal textarea {
960
- width: 100%; padding: 11px; border-radius: 8px; border: 1px solid var(--border);
961
- background: var(--bg); color: var(--text); font: inherit; font-size: 15px; outline: none;
962
- }
963
- .modal textarea { resize: vertical; min-height: 74px; }
964
- .modal input:focus, .modal select:focus, .modal textarea:focus { border-color: var(--accent); }
965
- .modal select optgroup { color: var(--muted); font-style: normal; }
966
- .modal select optgroup option { color: var(--text); }
967
- .modal .row { display: flex; gap: 10px; margin-top: 12px; }
968
- .modal button {
969
- flex: 1; padding: 11px; border-radius: 8px; font: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
970
- min-height: 44px;
971
- }
972
- .modal .save { background: transparent; color: var(--mine); border: 1px solid var(--mine); }
973
- .modal .save:hover { background: var(--mine-soft, var(--accent-soft)); }
974
- .modal .cancel { background: transparent; color: var(--muted); border: 1px solid var(--border); }
975
- .warn { font-size: 12.5px; color: var(--warn-text, #fbbf24); margin-top: 8px; }
976
-
977
- /* ----- Vistas ----- */
978
- .view { flex: 1; overflow-y: auto; padding: 18px 16px; max-width: 980px; margin: 0 auto; width: 100%; min-height: 0; }
979
- .view h2 { margin: 0 0 12px; font-size: 16px; font-weight: 600; }
980
- .view h2::before { content: "❯ "; color: var(--mine); }
981
- .view .placeholder {
982
- color: var(--muted); font-size: 13px; padding: 24px 0; text-align: center;
983
- }
984
-
985
- /* ----- Cambios (git) ----- */
986
- .chg-files { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; margin: 0 0 14px; }
987
- .chg-row { display: grid; grid-template-columns: 34px 1fr auto; gap: 8px; padding: 8px 12px; border-bottom: 1px solid var(--border); font-size: 13px; align-items: center; }
988
- .chg-row:last-child { border-bottom: 0; }
989
- .chg-rev {
990
- background: transparent; border: 1px solid var(--border); color: var(--muted);
991
- border-radius: 6px; width: 26px; height: 24px; line-height: 1; cursor: pointer; font: inherit; font-size: 12px;
992
- }
993
- .chg-rev:hover { color: var(--danger); border-color: var(--danger); }
994
- .chg-rev:disabled { opacity: .4; cursor: not-allowed; }
995
- .chg-st { font-weight: 700; text-align: center; font-size: 11px; color: var(--muted); }
996
- .chg-st.mod { color: var(--warn-text, #d29922); }
997
- .chg-st.add { color: var(--accent); }
998
- .chg-st.del { color: var(--danger); }
999
- .chg-path { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1000
- .diff {
1001
- background: var(--code-bg, var(--bg)); border: 1px solid var(--border);
1002
- border-radius: 8px; overflow: auto; padding: 10px 0; margin: 0 0 20px;
1003
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
1004
- font-size: 12px; line-height: 1.5;
1005
- }
1006
- .diff-line { white-space: pre; padding: 0 12px; }
1007
- .diff-line.add { background: rgba(63, 185, 80, .12); color: #3fb950; }
1008
- .diff-line.del { background: rgba(248, 81, 73, .12); color: #f85149; }
1009
- .diff-line.hunk { color: var(--accent); }
1010
- .diff-line.head { color: var(--muted); }
1011
-
1012
- .view-head { margin-bottom: 12px; }
1013
- .view-head h2 { margin: 0 0 4px; font-size: 16px; }
1014
- .view-sub { font-size: 11.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1015
- .files-toolbar { display: flex; align-items: center; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--border); margin-bottom: 8px; }
1016
- .files-path { font-size: 11.5px; color: var(--muted); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1017
- .linkbtn { background: transparent; border: 1px solid var(--border); color: var(--accent); padding: 5px 10px; border-radius: 8px; cursor: pointer; font: inherit; font-size: 12.5px; }
1018
- .linkbtn:hover { background: var(--accent-soft); }
1019
- .files-list { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }
1020
- .files-row { display: grid; grid-template-columns: 28px 1fr auto; gap: 8px; padding: 9px 12px; cursor: pointer; align-items: center; border-bottom: 1px solid var(--border); }
1021
- .files-row:last-child { border-bottom: 0; }
1022
- .files-row:hover { background: var(--accent-soft); }
1023
- .files-row .ic { text-align: center; }
1024
- .files-row .name { font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1025
- .files-row .size { font-size: 11px; color: var(--muted); }
1026
- .filemeta { font-size: 11.5px; color: var(--muted); padding: 4px 0 8px; }
1027
- .fileview {
1028
- background: var(--code-bg, var(--bg));
1029
- border: 1px solid var(--border);
1030
- border-radius: 8px;
1031
- padding: 12px;
1032
- margin: 0 0 20px;
1033
- overflow: auto;
1034
- max-height: 72vh;
1035
- font-size: 12.5px;
1036
- line-height: 1.5;
1037
- white-space: pre;
1038
- word-break: normal;
1039
- overflow-wrap: normal;
1040
- }
1041
- /* ----- Lector de archivos (código estilo editor, markdown, imágenes) ----- */
1042
- .fviewer {
1043
- background: var(--panel);
1044
- border: 1px solid var(--border);
1045
- border-radius: 10px;
1046
- overflow: hidden;
1047
- margin: 0 0 20px;
1048
- }
1049
- .fbar {
1050
- display: flex; align-items: center; gap: 10px;
1051
- padding: 7px 12px;
1052
- border-bottom: 1px solid var(--border);
1053
- background: var(--code-bg, var(--bg));
1054
- }
1055
- .fbar .fname { font-weight: 600; font-size: 13px; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1056
- .fbar .fsize { font-size: 11px; color: var(--muted); flex-shrink: 0; }
1057
- .fbar .fro { margin-left: auto; font-size: 10px; color: var(--muted); letter-spacing: .06em; text-transform: uppercase; flex-shrink: 0; }
1058
- .fbar .copybtn {
1059
- border: 0; background: transparent; color: var(--muted); cursor: pointer;
1060
- font: inherit; font-size: 12.5px; padding: 2px 6px; border-radius: 6px; flex-shrink: 0;
1061
- }
1062
- .fbar .copybtn:hover { color: var(--accent); background: var(--accent-soft); }
1063
- .fscroll { overflow: auto; max-height: 74vh; }
1064
- .fcodewrap {
1065
- min-width: max-content; width: max-content;
1066
- background: var(--code-bg, var(--bg));
1067
- padding: 8px 0;
1068
- font-family: var(--font);
1069
- font-size: 13px;
1070
- line-height: 1.55;
1071
- }
1072
- .frow { display: flex; white-space: pre; }
1073
- .frow:hover { background: rgba(128, 128, 128, .06); }
1074
- .frow .fg {
1075
- flex: 0 0 auto; min-width: 3.4em;
1076
- padding: 0 12px 0 10px; text-align: right;
1077
- color: var(--muted); opacity: .72; user-select: none;
1078
- }
1079
- .frow .fc { flex: 1; min-width: 0; padding-right: 16px; white-space: pre; color: var(--text); }
1080
- .fimgwrap {
1081
- padding: 18px; display: flex; justify-content: center;
1082
- background: var(--code-bg, var(--bg));
1083
- }
1084
- .fimgwrap img {
1085
- max-width: 100%; max-height: 74vh; object-fit: contain;
1086
- border-radius: 6px; box-shadow: var(--shadow);
1087
- }
1088
- /* Markdown dentro del lector (mismas reglas que el chat, sin `.msg .body`) */
1089
- .mdview { padding: 14px 16px; font-size: 14px; line-height: 1.6; color: var(--text); background: var(--panel); }
1090
- .mdview strong { color: var(--text); font-weight: 700; }
1091
- .mdview em { font-style: italic; }
1092
- .mdview del { opacity: .65; }
1093
- .mdview a { color: var(--accent); text-decoration: underline; word-break: break-all; }
1094
- .mdview code.inl {
1095
- background: var(--code-bg, var(--bg)); border: 1px solid var(--border); border-radius: 4px;
1096
- padding: 0 5px; font-size: .9em;
1097
- }
1098
- .mdview .md-p { margin: 4px 0; }
1099
- .mdview .md-h { font-weight: 700; color: var(--text); margin: 14px 0 6px; }
1100
- .mdview .md-h1 { font-size: 19px; border-bottom: 1px solid var(--border); padding-bottom: 4px; }
1101
- .mdview .md-h2 { font-size: 17px; }
1102
- .mdview .md-h3, .mdview .md-h4 { font-size: 15px; }
1103
- .mdview ul.md-ul, .mdview ol.md-ol { margin: 4px 0 6px; padding-left: 24px; }
1104
- .mdview .md-ul li, .mdview .md-ol li { margin: 2px 0; }
1105
- .mdview .md-quote {
1106
- border-left: 3px solid var(--border); padding: 2px 0 2px 12px; margin: 6px 0;
1107
- color: var(--muted);
1108
- }
1109
- .mdview .md-hr { border: 0; border-top: 1px solid var(--border); margin: 12px 0; }
1110
- .mdview .codewrap { margin: 10px 0; }
1111
- .search-bar { display: flex; align-items: center; gap: 8px; padding: 10px 12px; background: var(--panel); border: 1px solid var(--border); border-radius: 10px; margin-bottom: 12px; }
1112
- .search-bar input { flex: 1; border: 0; background: transparent; color: var(--text); font: inherit; font-size: 16px; outline: none; }
1113
- .search-bar span { font-size: 13px; color: var(--muted); }
1114
- .search-bar button { background: transparent; border: 0; color: var(--muted); cursor: pointer; font-size: 17px; padding: 0 6px; }
1115
- .search-summary { font-size: 11.5px; color: var(--muted); margin: 8px 0 12px; }
1116
- .search-group { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 12px; margin-bottom: 10px; }
1117
- .search-group-head { font-size: 12.5px; margin-bottom: 6px; }
1118
- .search-group-head .link { color: var(--accent); text-decoration: none; }
1119
- .search-match { font-size: 12.5px; padding: 6px 0; border-top: 1px solid var(--border); }
1120
- .search-match:first-of-type { border-top: 0; }
1121
- .search-match .role { display: inline-block; font-size: 10px; padding: 1px 6px; border-radius: 4px; background: var(--accent-soft); color: var(--accent); margin-right: 6px; }
1122
- .sessions-list .card .icbtn { padding: 8px 12px; }
1123
-
1124
- /* Vista: vista previa del proyecto vía túnel (TunnelMole en la PC) */
1125
- .tunnel-form { display: flex; gap: 8px; margin-bottom: 14px; align-items: center; }
1126
- .tunnel-form input[type="text"] {
1127
- width: 170px; flex-shrink: 0; padding: 10px 12px;
1128
- background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
1129
- color: var(--text); font: inherit; font-size: 15px; outline: none;
1130
- }
1131
- .tunnel-form input[type="text"]:focus { border-color: var(--accent); }
1132
- .tunnel {
1133
- background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
1134
- padding: 12px; margin-bottom: 10px;
1135
- }
1136
- .tunnel .thead { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
1137
- .tunnel .thead b { font-size: 13.5px; }
1138
- .tunnel .thead .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok, #22c55e); flex-shrink: 0; }
1139
- .tunnel .thead .tnote { font-size: 11px; color: var(--muted); }
1140
- .tunnel .thead .linkbtn { margin-left: auto; }
1141
- .tunnel .turl {
1142
- display: flex; align-items: center; gap: 8px; padding: 7px 9px;
1143
- background: var(--code-bg, var(--bg)); border: 1px solid var(--border); border-radius: 8px;
1144
- margin-bottom: 6px; font-size: 12.5px; overflow: hidden;
1145
- }
1146
- .tunnel .turl a { color: var(--accent); text-decoration: none; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1147
- .tunnel .tnote-sec { font-size: 11px; color: var(--muted); margin-top: 4px; }
1148
- mark { background: var(--accent); color: var(--bg); padding: 0 2px; border-radius: 3px; }
1149
-
1150
- /* ----- Responsive ----- */
1151
- @media (max-width: 1023px) {
1152
- #sidebar {
1153
- position: fixed; top: 0; bottom: 0; left: 0;
1154
- width: min(var(--sb-w, 264px), 90vw);
1155
- transform: translateX(-100%);
1156
- box-shadow: var(--shadow);
1157
- }
1158
- body.sidebar-open #sidebar { transform: translateX(0); }
1159
- body.sidebar-open #scrim { display: block; }
1160
- header .burger { display: inline-flex; }
1161
- .fab.show { display: block; }
1162
- .sb-reveal { display: none !important; }
1163
- }
1164
- @media (min-width: 1024px) {
1165
- header .burger { display: none; }
1166
- .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1167
- .fab { display: none !important; }
1168
- #sidebar .resizer { display: block; }
1169
- body.sb-hide #sidebar { display: none; }
1170
- body.sb-hide .sb-reveal { display: block; }
1171
- }
1172
- @media (min-width: 720px) {
1173
- header a.logout { display: inline-flex; }
1174
- }
1175
- @media (max-width: 479px) {
1176
- body { font-size: 14.5px; }
1177
- header .sub { max-width: 44vw; }
1178
- #messages { padding: 10px; gap: 8px; }
1179
- .msg { font-size: 14.5px; }
1180
- form.sendbar textarea { font-size: 16px; } /* evita zoom iOS */
1181
- .sitem { padding: 11px 8px 11px 12px; font-size: 13.5px; }
1182
- .proj-head { font-size: 13px; }
1183
- .utilities button { padding: 9px 4px; font-size: 12px; }
1184
- .procrow { padding: 7px 10px; gap: 8px; }
1185
- .stopbtn .stoptxt { display: none; } /* en pantallas chicas, solo el ícono */
1186
- }
1187
- </style>
1188
- </head>
1189
- <body data-theme="{{THEME}}">
1190
- <div id="scrim"></div>
1191
- <button type="button" class="sb-reveal" id="sbReveal" title="Mostrar panel" aria-label="Mostrar panel">»</button>
1192
-
1193
- <aside id="sidebar" aria-label="Proyectos y sesiones">
1194
- <div class="brand" id="brandHome" title="Ir al inicio">
1195
- <div class="logo">❯_</div>
1196
- <div class="name">OpenBridge</div>
1197
- <div class="ver">v3</div>
1198
- <button type="button" class="sbhide" id="btnSbHide" title="Ocultar panel" aria-label="Ocultar panel">«</button>
1199
- </div>
1200
- <button class="newsession" id="btnNewSession" type="button">+ nueva sesión</button>
1201
- <div class="sidesearch">
1202
- <input type="text" id="sideSearch" placeholder="filtrar proyectos…" autocomplete="off">
1203
- </div>
1204
- <div id="bridgeBar" title="Computadora conectada: sus carpetas, modelos y chats"></div>
1205
- <div id="sessionTree"></div>
1206
- <div class="utilities">
1207
- <button data-view="home" type="button" title="Chats">chats</button>
1208
- <button data-view="files" type="button" title="Archivos">archivos</button>
1209
- <button data-view="sessions" type="button" title="Sesiones opencode">sesiones</button>
1210
- <button data-view="search" type="button" title="Buscar">buscar</button>
1211
- <button data-view="preview" type="button" title="Vista previa del proyecto (túnel)">preview</button>
1212
- <button data-view="procs" type="button" title="Procesos de la sesión">procs</button>
1213
- <button data-view="changes" type="button" title="Cambios del proyecto (git)">cambios</button>
1214
- <button data-view="mcp" type="button" title="Servidores MCP de opencode">mcp</button>
1215
- <button data-view="devices" type="button" title="PCs emparejadas a tu cuenta" style="display:none">dispositivos</button>
1216
- </div>
1217
- <div class="footer">
1218
- <div class="row" id="bridgeStatusRow">
1219
- <span class="dot" id="bridgeDot"></span>
1220
- <span id="bridgeStatusText">puente: desconocido</span>
1221
- </div>
1222
- <div class="row">
1223
- <span class="userbadge" id="userBadge" title="sesión actual">{{USER_NAME}}</span>
1224
- <a class="logout" href="logout.php">salir</a>
1225
- </div>
1226
- </div>
1227
- <div class="resizer" id="sbResizer" title="Arrastrá para ajustar · doble clic para restablecer"></div>
1228
- </aside>
1229
-
1230
- <main>
1231
- <header>
1232
- <button class="burger" id="btnBurger" aria-label="Menú">☰</button>
1233
- <button class="back" id="btnBack" style="display:none" aria-label="Volver">←</button>
1234
- <div class="left">
1235
- <div class="titles">
1236
- <div class="title" id="hTitle">OpenBridge</div>
1237
- <div class="sub" id="hSub">conectando…</div>
1238
- </div>
1239
- </div>
1240
- <div class="actions">
1241
- <button class="icbtn" id="btnPush" type="button" title="Activar avisos en este dispositivo" aria-label="Activar avisos" aria-pressed="false"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0"/></svg></button>
1242
- <button class="icbtn" id="btnSkin" title="Cambiar tema" aria-label="Cambiar tema">◍</button>
1243
- <button class="icbtn" id="btnCmds" title="Comandos" aria-label="Comandos disponibles">/</button>
1244
- </div>
1245
- </header>
1246
-
1247
- <div id="cmdsPanel" class="cmds"></div>
1248
- <div id="skinPanel"></div>
1249
- <div id="cardMenu"></div>
1250
-
1251
- <div id="home"></div>
1252
-
1253
- <div id="chat">
1254
- <div id="offline" style="display:none">⚠ el puente de la computadora seleccionada está apagado · iniciá “OpenBridge” en esa PC</div>
1255
- <div id="messages" aria-live="polite"></div>
1256
- <button type="button" id="jumpBtn" class="jumpbtn" aria-label="Ir al último mensaje">↓ final</button>
1257
- <div id="imgPreview" style="display:none">
1258
- <img id="imgThumb" alt="imagen adjunta">
1259
- <button type="button" id="imgRemove" title="Quitar imagen" aria-label="Quitar imagen">✕</button>
1260
- <span id="imgMeta"></span>
1261
- </div>
1262
- <div id="procrow" class="procrow" role="status">
1263
- <div class="typing proc" id="proc-ind">
1264
- <span class="kitt" aria-hidden="true"><span class="kitt-scan"></span></span>
1265
- <span class="proc-label" id="proc-label"></span>
1266
- </div>
1267
- <span class="proc-time" id="proc-time"></span>
1268
- <button type="button" class="stopbtn" id="btnStop" title="Corta la ejecución en la PC" aria-label="Detener"><span aria-hidden="true">⏹</span><span class="stoptxt">detener</span></button>
1269
- </div>
1270
- <button type="button" id="composeOpen" class="composeopen" aria-label="Escribir mensaje">
1271
- <span class="prompt" aria-hidden="true">❯</span>
1272
- <span class="cph">escribí un mensaje…</span>
1273
- </button>
1274
- <form class="sendbar" id="sendForm" style="display:none">
1275
- <div class="shead" id="composeClose" title="Cerrar">
1276
- <span class="handle" aria-hidden="true"></span>
1277
- <button type="button" class="cclose" aria-label="Cerrar">✕</button>
1278
- </div>
1279
- <span class="prompt">❯</span>
1280
- <textarea id="input" rows="1" placeholder="escribí un mensaje…" autocomplete="off"></textarea>
1281
- <button type="button" class="imgbtn" id="btnTpl" title="Plantillas de prompts" aria-label="Plantillas de prompts"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M17.593 3.322c1.1.128 1.907 1.077 1.907 2.185V21L12 17.25 4.5 21V5.507c0-1.108.806-2.057 1.907-2.185a48.507 48.507 0 0 1 11.186 0Z"/></svg></button>
1282
- <button type="button" class="imgbtn" id="btnImg" title="Adjuntar imagen (modelos con visión)" aria-label="Adjuntar imagen"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"/></svg></button>
1283
- <button type="button" class="imgbtn" id="btnMic" title="Dictar por voz" aria-label="Dictar por voz"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18.75a6 6 0 0 0 6-6v-1.5m-6 7.5a6 6 0 0 1-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 0 1-3-3V4.5a3 3 0 1 1 6 0v8.25a3 3 0 0 1-3 3Z"/></svg></button>
1284
- <input type="file" id="imgInput" accept="image/png,image/jpeg,image/webp,image/gif" style="display:none">
1285
- <button type="submit" id="sendBtn">enviar</button>
1286
- <div id="slashPanel" class="slash-panel"></div>
1287
- <div id="tplPanel" class="slash-panel tpl-panel"></div>
1288
- </form>
1289
- </div>
1290
-
1291
- <div id="viewFiles" class="view" style="display:none"></div>
1292
- <div id="viewSessions" class="view" style="display:none"></div>
1293
- <div id="viewSearch" class="view" style="display:none"></div>
1294
- <div id="viewHistory" class="view" style="display:none"></div>
1295
- <div id="viewPreview" class="view" style="display:none"></div>
1296
- <div id="viewProcs" class="view" style="display:none"></div>
1297
- <div id="viewChanges" class="view" style="display:none"></div>
1298
- <div id="viewMcp" class="view" style="display:none"></div>
1299
- <div id="viewDevices" class="view" style="display:none"></div>
1300
-
1301
- <footer id="statusbar">
1302
- <span class="dot" id="sbDot"></span>
1303
- <span class="seg mode" id="sbMode">…</span>
1304
- <span class="sep">│</span>
1305
- <button type="button" id="sbModel" title="Cambiar modelo / agente de este chat">sin modelo</button>
1306
- <span class="sep" id="sbSep2">│</span>
1307
- <span class="seg" id="sbExtra"></span>
1308
- </footer>
1309
- </main>
1310
-
1311
- <button class="fab" id="fabNew" title="Nueva sesión" aria-label="Nueva sesión">+</button>
1312
-
1313
- <div id="overlay">
1314
- <div class="modal">
1315
- <h2>nueva sesión</h2>
1316
- <label for="fFolder">Carpeta de trabajo</label>
1317
- <select id="fFolder"></select>
1318
- <div id="fFolderCreate" style="display:none; margin-top:6px">
1319
- <div style="font-size:12px; color:var(--muted)">¿no está tu carpeta?</div>
1320
- <div style="display:flex; gap:6px; margin-top:4px">
1321
- <input type="text" id="fNewFolder" placeholder="nombre de la carpeta" style="flex:1">
1322
- <button type="button" id="btnNewFolder" style="flex:0 0 auto; padding:11px 14px; border:1px solid var(--mine); border-radius:8px; background:transparent; color:var(--mine); font-family:var(--font); font-weight:600; cursor:pointer">crear</button>
1323
- </div>
1324
- <div id="fNewFolderMsg" style="font-size:11.5px; color:var(--muted); margin-top:4px"></div>
1325
- </div>
1326
- <label for="fModel">Modelo de IA (todos los proveedores)</label>
1327
- <input type="text" id="fModelSearch" placeholder="buscar modelo… (ej: glm, deepseek)" autocomplete="off">
1328
- <select id="fModel"></select>
1329
- <label for="fAgent">Cómo debe actuar</label>
1330
- <select id="fAgent"></select>
1331
- <label for="fMsg">Mensaje (opcional vacío = solo crear el chat)</label>
1332
- <textarea id="fMsg" rows="3" placeholder="escribí tu pedido… (enter = crear · shift+enter = salto de línea)"></textarea>
1333
- <div class="warn" id="fWarn" style="display:none">el puente aún no sincronizó carpetas/modelos.</div>
1334
- <div class="row">
1335
- <button type="button" class="cancel" id="btnCancel">cancelar</button>
1336
- <button type="button" class="save" id="btnSave">crear</button>
1337
- </div>
1338
- </div>
1339
- </div>
1340
-
1341
- <div id="modelOverlay">
1342
- <div class="modal">
1343
- <h2>cambiar modelo</h2>
1344
- <div class="msub" id="mTitle" style="font-size:11.5px;color:var(--muted);margin:-6px 0 4px"></div>
1345
- <label for="fModel2">Modelo de IA (todos los proveedores)</label>
1346
- <input type="text" id="mSearch" placeholder="buscar modelo… (ej: glm, deepseek)" autocomplete="off">
1347
- <select id="fModel2"></select>
1348
- <label for="fAgent2">Cómo debe actuar</label>
1349
- <select id="fAgent2"></select>
1350
- <div class="warn" id="mWarn" style="display:none">el puente aún no sincronizó modelos.</div>
1351
- <div class="hint" style="font-size:11px;color:var(--muted);margin-top:8px">aplica desde el próximo mensaje · la conversación continúa igual</div>
1352
- <div class="row">
1353
- <button type="button" class="cancel" id="btnModelCancel">cancelar</button>
1354
- <button type="button" class="save" id="btnModelSave">aplicar</button>
1355
- </div>
1356
- </div>
1357
- </div>
1358
-
1359
- <script>
1360
- window.APP_CONFIG = {
1361
- csrf: "{{CSRF}}",
1362
- initialSession: {{INITIAL_SESSION}},
1363
- theme: "{{THEME}}",
1364
- themes: {{THEMES_JSON}},
1365
- sseDisabled: {{SSE_DISABLED}},
1366
- pushEnabled: {{PUSH_ENABLED}},
1367
- pushKey: "{{PUSH_KEY}}",
1368
- userName: "{{USER_NAME}}",
1369
- userRole: "{{USER_ROLE}}"
1370
- };
1371
- </script>
1372
- <script src="app.js?v={{APPJS_VER}}" defer></script>
1373
- </body>
1374
- </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, viewport-fit=cover">
6
+ <meta name="theme-color" content="#0a0d12" id="themeColor">
7
+ <title>OpenBridge</title>
8
+ <link rel="manifest" href="manifest.webmanifest">
9
+ <link rel="icon" href="icons/icon-192x192.png">
10
+ <link rel="apple-touch-icon" href="icons/icon-192x192.png">
11
+ <link rel="apple-touch-icon" sizes="152x152" href="icons/icon-152x152.png">
12
+ <link rel="stylesheet" href="themes/themes.css" id="themeStylesheet">
13
+ <style>
14
+ :root {
15
+ --font: ui-monospace, "Cascadia Code", "JetBrains Mono", "SF Mono", "Fira Code", Consolas, "Roboto Mono", monospace;
16
+ /* Resaltado de código (paleta VSCode dark) */
17
+ --hl-cm: #6a9955;
18
+ --hl-kw: #569cd6;
19
+ --hl-k2: #c586c9;
20
+ --hl-st: #ce9178;
21
+ --hl-nu: #b5cea8;
22
+ --hl-fn: #dcdcaa;
23
+ --hl-ty: #4ec9b0;
24
+ --hl-pr: #9cdcfe;
25
+ }
26
+ body[data-theme="light"], body[data-theme="solarized"],
27
+ body[data-theme="light-plus"], body[data-theme="github-light"] {
28
+ --hl-cm: #008000;
29
+ --hl-kw: #0000ff;
30
+ --hl-k2: #af00db;
31
+ --hl-st: #a31515;
32
+ --hl-nu: #098658;
33
+ --hl-fn: #795e26;
34
+ --hl-ty: #267f79;
35
+ --hl-pr: #001080;
36
+ }
37
+ * { box-sizing: border-box; }
38
+ html, body { height: 100%; }
39
+ body {
40
+ margin: 0;
41
+ font-family: var(--font);
42
+ font-size: 15px;
43
+ line-height: 1.55;
44
+ background: var(--bg);
45
+ color: var(--text);
46
+ display: flex;
47
+ flex-direction: row;
48
+ min-height: 100vh;
49
+ min-height: 100dvh;
50
+ }
51
+ a { color: var(--accent); }
52
+ b { font-weight: 600; }
53
+ ::selection { background: var(--accent-soft); }
54
+ ::-webkit-scrollbar { width: 8px; height: 8px; }
55
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
56
+ ::-webkit-scrollbar-track { background: transparent; }
57
+ .code { font-family: var(--font); }
58
+
59
+ /* ----- Sidebar: navegador de proyectos y sesiones ----- */
60
+ #sidebar {
61
+ width: var(--sb-w, 264px);
62
+ position: relative;
63
+ background: var(--rail, var(--panel));
64
+ border-right: 1px solid var(--border);
65
+ display: flex;
66
+ flex-direction: column;
67
+ flex-shrink: 0;
68
+ transition: transform .22s ease;
69
+ z-index: 30;
70
+ }
71
+ #sidebar .brand {
72
+ min-height: 54px;
73
+ padding: 12px 14px 10px;
74
+ display: flex;
75
+ align-items: center;
76
+ gap: 10px;
77
+ border-bottom: 1px solid var(--border);
78
+ }
79
+ #sidebar .brand .logo {
80
+ font-family: var(--font);
81
+ color: var(--mine);
82
+ font-weight: 700;
83
+ font-size: 15px;
84
+ letter-spacing: -.05em;
85
+ }
86
+ #sidebar .brand .name { font-weight: 600; font-size: 14px; letter-spacing: .02em; }
87
+ #sidebar .brand .ver { margin-left: auto; font-size: 11px; color: var(--muted); }
88
+ #sidebar .brand { cursor: pointer; }
89
+ #sidebar .brand:hover { background: var(--accent-soft); }
90
+ #sidebar .sbhide {
91
+ display: inline-block;
92
+ background: transparent; border: 0; color: var(--muted); cursor: pointer;
93
+ font: inherit; font-size: 13px; line-height: 1; padding: 5px 7px;
94
+ border-radius: 6px; flex-shrink: 0;
95
+ }
96
+ #sidebar .sbhide:hover { color: var(--text); background: var(--accent-soft); }
97
+ #sidebar .resizer {
98
+ display: none;
99
+ position: absolute; top: 0; bottom: 0; right: -3px; width: 6px;
100
+ cursor: col-resize; z-index: 31; touch-action: none;
101
+ }
102
+ #sidebar .resizer:hover, #sidebar .resizer.dragging { background: var(--accent-soft); }
103
+ .sb-reveal {
104
+ display: none;
105
+ position: fixed; top: 50%; left: 0; transform: translateY(-50%);
106
+ background: var(--panel); border: 1px solid var(--border); border-left: 0;
107
+ color: var(--muted); border-radius: 0 8px 8px 0;
108
+ padding: 16px 5px; font: inherit; font-size: 12px; line-height: 1;
109
+ cursor: pointer; z-index: 31;
110
+ }
111
+ .sb-reveal:hover { color: var(--accent); background: var(--accent-soft); }
112
+ .newsession {
113
+ margin: 10px 10px 8px;
114
+ padding: 9px 12px;
115
+ background: transparent;
116
+ border: 1px dashed var(--mine);
117
+ color: var(--mine);
118
+ border-radius: 8px;
119
+ font: inherit;
120
+ font-weight: 600;
121
+ text-align: left;
122
+ cursor: pointer;
123
+ }
124
+ .newsession:hover { background: var(--mine-soft, var(--accent-soft)); }
125
+ .sidesearch { padding: 0 10px 8px; }
126
+ .sidesearch input {
127
+ width: 100%;
128
+ padding: 7px 10px;
129
+ background: var(--bg);
130
+ border: 1px solid var(--border);
131
+ border-radius: 8px;
132
+ color: var(--text);
133
+ font: inherit;
134
+ font-size: 13px;
135
+ outline: none;
136
+ }
137
+ .sidesearch input:focus { border-color: var(--accent); }
138
+ .sidesearch input::placeholder { color: var(--muted); }
139
+ /* ----- Selector de puente (computadora) ----- */
140
+ #bridgeBar { display: none; padding: 0 10px 8px; gap: 6px; }
141
+ #bridgeBar.show { display: flex; flex-wrap: wrap; }
142
+ #bridgeBar .bb {
143
+ flex: 1 1 auto; min-width: 0;
144
+ display: flex; align-items: center; gap: 6px;
145
+ background: transparent; border: 1px solid var(--border); color: var(--muted);
146
+ border-radius: 8px; padding: 5px 9px; font: inherit; font-size: 11.5px; cursor: pointer;
147
+ white-space: nowrap;
148
+ }
149
+ #bridgeBar .bb:hover { color: var(--text); }
150
+ #bridgeBar .bb.on { color: var(--text); border-color: var(--accent); background: var(--accent-soft); }
151
+ #bridgeBar .bb .bdot { width: 6px; height: 6px; border-radius: 50%; background: var(--muted); flex-shrink: 0; }
152
+ #bridgeBar .bb .bdot.on { background: var(--ok, #22c55e); box-shadow: 0 0 4px var(--ok, #22c55e); }
153
+ #bridgeBar .bb .bname { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
154
+ #bridgeBar .bb .bbusy { font-size: 10px; color: var(--accent); flex-shrink: 0; }
155
+ #sessionTree { flex: 1; overflow-y: auto; padding: 2px 6px 10px; min-height: 0; }
156
+ .utilities { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 10px 10px; }
157
+ .utilities button {
158
+ flex: 1 1 64px;
159
+ min-width: 64px;
160
+ background: transparent;
161
+ border: 1px solid var(--border);
162
+ color: var(--muted);
163
+ border-radius: 8px;
164
+ padding: 6px 4px;
165
+ font: inherit;
166
+ font-size: 11.5px;
167
+ cursor: pointer;
168
+ white-space: nowrap;
169
+ }
170
+ .utilities button:hover { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
171
+ .proj { margin-top: 2px; }
172
+ .prow { display: flex; align-items: center; }
173
+ .prow .proj-head { flex: 1; min-width: 0; width: auto; padding: 6px 4px 6px 8px; }
174
+ .prow .proj-head .chev { width: 12px; }
175
+ .prow.active .proj-head .pname { color: var(--accent); }
176
+ .prow .padd {
177
+ background: transparent; border: 1px solid var(--border); color: var(--muted);
178
+ border-radius: 6px; width: 22px; height: 22px; line-height: 1;
179
+ font: inherit; font-size: 14px; cursor: pointer; flex-shrink: 0; padding: 0;
180
+ margin-right: 8px;
181
+ }
182
+ .prow .padd:hover { color: var(--mine); border-color: var(--mine); background: var(--mine-soft, var(--accent-soft)); }
183
+ .proj-head {
184
+ display: flex;
185
+ align-items: center;
186
+ gap: 7px;
187
+ width: 100%;
188
+ background: transparent;
189
+ border: 0;
190
+ color: var(--muted);
191
+ font: inherit;
192
+ font-size: 12.5px;
193
+ letter-spacing: .04em;
194
+ padding: 8px 8px 6px;
195
+ cursor: pointer;
196
+ text-align: left;
197
+ }
198
+ .proj-head:hover { color: var(--text); }
199
+ .proj-head .chev { width: 10px; display: inline-block; transition: transform .12s ease; }
200
+ .proj-head.open .chev { transform: rotate(90deg); }
201
+ .proj-head .pname { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); font-weight: 600; font-size: 13.5px; }
202
+ .proj-head .pcount { font-size: 11.5px; color: var(--muted); }
203
+ .proj-sessions { display: none; }
204
+ .proj.open .proj-sessions { display: block; }
205
+ .proj-head .padd {
206
+ background: transparent; border: 1px solid var(--border); color: var(--muted);
207
+ border-radius: 6px; width: 22px; height: 22px; line-height: 1;
208
+ font: inherit; font-size: 14px; cursor: pointer; flex-shrink: 0; padding: 0;
209
+ }
210
+ .proj-head .padd:hover { color: var(--mine); border-color: var(--mine); background: var(--mine-soft, var(--accent-soft)); }
211
+ .sitem {
212
+ display: flex;
213
+ align-items: center;
214
+ gap: 8px;
215
+ width: 100%;
216
+ background: transparent;
217
+ border: 0;
218
+ border-left: 2px solid transparent;
219
+ color: var(--text);
220
+ font: inherit;
221
+ font-size: 12.5px;
222
+ padding: 8px 8px 8px 12px;
223
+ cursor: pointer;
224
+ text-align: left;
225
+ border-radius: 0 8px 8px 0;
226
+ }
227
+ .sitem:hover { background: var(--accent-soft); }
228
+ .sitem.active {
229
+ background: var(--accent-soft);
230
+ border-left-color: var(--accent);
231
+ color: var(--accent);
232
+ font-weight: 600;
233
+ }
234
+ .sitem .sname { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; opacity: .78; }
235
+ .sitem.active .sname { opacity: 1; }
236
+ .sitem .stok { font-size: 10.5px; color: var(--muted); flex-shrink: 0; }
237
+ .sitem .scost { font-size: 10.5px; color: var(--warn-text, #d29922); flex-shrink: 0; }
238
+ .sitem .stime { font-size: 11px; color: var(--muted); flex-shrink: 0; }
239
+ .sitem .spen { width: 6px; height: 6px; border-radius: 50%; background: var(--mine); flex-shrink: 0; }
240
+ .sdot {
241
+ display: inline-block; width: 6px; height: 6px; border-radius: 50%;
242
+ background: var(--mine); flex-shrink: 0; vertical-align: middle;
243
+ }
244
+ .sbusy {
245
+ display: inline-block; width: 7px; height: 7px; border-radius: 50%;
246
+ background: var(--accent);
247
+ box-shadow: 0 0 5px var(--accent);
248
+ vertical-align: middle;
249
+ animation: sbusyPulse 1.1s ease-in-out infinite;
250
+ }
251
+ @keyframes sbusyPulse {
252
+ 0%, 100% { opacity: 1; }
253
+ 50% { opacity: .3; }
254
+ }
255
+ .sitem .sfolder {
256
+ font-size: 9.5px; color: var(--muted);
257
+ border: 1px solid var(--border); border-radius: 3px;
258
+ padding: 1px 4px; flex-shrink: 0; max-width: 92px;
259
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
260
+ }
261
+ .sitem.active .sfolder { color: var(--accent); border-color: var(--accent); }
262
+ .proj.todas .pname { text-transform: uppercase; font-size: 10.5px; letter-spacing: .06em; }
263
+ .tree-empty { color: var(--muted); font-size: 12.5px; padding: 14px 10px; text-align: center; }
264
+ #sidebar .footer {
265
+ padding: 10px 12px;
266
+ border-top: 1px solid var(--border);
267
+ display: flex; flex-direction: column; gap: 6px;
268
+ font-size: 11.5px;
269
+ color: var(--muted);
270
+ }
271
+ #sidebar .footer .row { display: flex; align-items: center; gap: 8px; }
272
+ #sidebar .footer .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); flex-shrink: 0; }
273
+ #sidebar .footer .dot.on { background: var(--ok, #22c55e); }
274
+ #sidebar .footer .dot.off { background: var(--danger); }
275
+ #sidebar .footer a.logout {
276
+ color: var(--danger);
277
+ text-decoration: none;
278
+ padding: 6px 10px;
279
+ border-radius: 8px;
280
+ text-align: center;
281
+ border: 1px solid var(--border);
282
+ }
283
+ #sidebar .footer .userbadge {
284
+ flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
285
+ color: var(--muted); font-size: 12px;
286
+ }
287
+ #sidebar .footer .userbadge.admin { color: var(--accent); }
288
+ #scrim {
289
+ display: none;
290
+ position: fixed; inset: 0;
291
+ background: rgba(0, 0, 0, .6);
292
+ z-index: 25;
293
+ }
294
+
295
+ /* ----- Main ----- */
296
+ main {
297
+ flex: 1; min-width: 0;
298
+ display: flex; flex-direction: column;
299
+ background: var(--bg);
300
+ }
301
+ header {
302
+ display: flex; align-items: center; gap: 10px;
303
+ padding: 8px 14px;
304
+ min-height: 46px;
305
+ background: var(--panel);
306
+ border-bottom: 1px solid var(--border);
307
+ position: sticky; top: 0; z-index: 10;
308
+ }
309
+ header .burger {
310
+ background: transparent;
311
+ color: var(--text);
312
+ border: 1px solid var(--border);
313
+ border-radius: 8px;
314
+ padding: 5px 10px;
315
+ font-size: 16px; line-height: 1.2;
316
+ cursor: pointer;
317
+ display: none;
318
+ }
319
+ header .back {
320
+ background: transparent; color: var(--text); border: 1px solid var(--border);
321
+ border-radius: 8px; padding: 4px 10px; font-size: 14px; cursor: pointer; font-family: var(--font);
322
+ }
323
+ header .back:hover { background: var(--accent-soft); }
324
+ header .left { display: flex; align-items: center; gap: 10px; min-width: 0; flex: 1; }
325
+ header .titles { min-width: 0; }
326
+ header .title { font-weight: 600; font-size: 15px; line-height: 1.25; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
327
+ header .sub { font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden;
328
+ text-overflow: ellipsis; max-width: 58vw; }
329
+ header .actions { display: flex; align-items: center; gap: 8px; }
330
+ header .icbtn {
331
+ background: transparent; color: var(--text); border: 1px solid var(--border);
332
+ border-radius: 8px; padding: 5px 10px; font-size: 13px;
333
+ cursor: pointer; line-height: 1.2; font-family: var(--font);
334
+ min-width: 34px; min-height: 32px;
335
+ }
336
+ header .icbtn:hover { background: var(--accent-soft); color: var(--accent); }
337
+ #btnPush { display: none; align-items: center; justify-content: center; }
338
+ #btnPush svg { display: block; width: 16px; height: 16px; }
339
+ #btnPush.on { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
340
+ #btnPush.denied { color: var(--danger); border-color: var(--danger); }
341
+ header a.logout {
342
+ display: none;
343
+ color: var(--danger); text-decoration: none; font-size: 12px;
344
+ padding: 5px 10px; border: 1px solid var(--border); border-radius: 8px;
345
+ }
346
+
347
+ #cmdsPanel {
348
+ display: none; position: fixed; top: 54px; right: 12px; z-index: 40;
349
+ background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
350
+ min-width: 280px; max-width: 92vw; max-height: 72vh; overflow-y: auto; padding: 8px;
351
+ box-shadow: var(--shadow);
352
+ }
353
+ #cmdsPanel.open { display: block; }
354
+ #cmdsPanel h4 { margin: 4px 6px 8px; font-size: 11px; color: var(--muted); letter-spacing: .06em; font-weight: 600; }
355
+ #cmdsPanel .item { padding: 7px 8px; border-radius: 8px; cursor: pointer; }
356
+ #cmdsPanel .item:hover { background: var(--accent-soft); }
357
+ #cmdsPanel .item b { color: var(--mine); font-size: 13px; font-weight: 600; }
358
+ #cmdsPanel .item span { display: block; color: var(--muted); font-size: 11px; margin-top: 1px; }
359
+
360
+ #skinPanel {
361
+ display: none; position: fixed; top: 54px; right: 12px; z-index: 40;
362
+ background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
363
+ min-width: 220px; padding: 8px;
364
+ box-shadow: var(--shadow);
365
+ }
366
+ #skinPanel.open { display: block; }
367
+ #skinPanel h4 { margin: 4px 6px 8px; font-size: 11px; color: var(--muted); letter-spacing: .06em; font-weight: 600; }
368
+ #skinPanel .item {
369
+ padding: 8px 10px; border-radius: 8px; cursor: pointer;
370
+ display: flex; align-items: center; gap: 10px;
371
+ }
372
+ #skinPanel .item:hover { background: var(--accent-soft); }
373
+ #skinPanel .item.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
374
+ #skinPanel .sw {
375
+ width: 20px; height: 20px; border-radius: 5px;
376
+ border: 1px solid var(--border);
377
+ background: linear-gradient(135deg, var(--swatch-a, var(--bg)) 0 50%, var(--swatch-b, var(--mine)) 50% 100%);
378
+ }
379
+
380
+ /* ----- Tab de procesos (dev servers de la sesión activa) ----- */
381
+ #viewDevices .dev-add { display: flex; gap: 8px; margin: 10px 0; }
382
+ #viewDevices .dev-add input { flex: 1; min-width: 0; padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--panel); color: var(--text); font: inherit; }
383
+ #viewDevices .dev-add input:focus { border-color: var(--accent); outline: none; }
384
+ #viewDevices .device { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px; margin-bottom: 8px; background: var(--panel); }
385
+ #viewDevices .device .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok, #22c55e); flex-shrink: 0; }
386
+ #viewDevices .device .dnote { color: var(--muted); font-size: 11.5px; }
387
+ #viewDevices .device .linkbtn { margin-left: auto; }
388
+ #viewDevices code { background: var(--panel); border: 1px solid var(--border); border-radius: 6px; padding: 1px 6px; font-size: 11.5px; }
389
+ #viewProcs .pp-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
390
+ #viewProcs .pp-head b { font-size: 16px; font-weight: 600; }
391
+ #viewProcs .pp-head b::before { content: "❯ "; color: var(--mine); }
392
+ #viewProcs .pp-sub { font-size: 11.5px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
393
+ #viewProcs .pp-back { margin-left: auto; flex-shrink: 0; }
394
+ #procStatus { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 12.5px; color: var(--muted); margin-bottom: 10px; }
395
+ #procStatus .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); flex-shrink: 0; }
396
+ #procStatus .dot.on { background: var(--ok, #22c55e); }
397
+ #procStatus .dot.off { background: var(--danger); }
398
+ #procStatus .pp-cmd { font-family: var(--font); color: var(--text); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
399
+ #procStatus .linkbtn { flex-shrink: 0; }
400
+ .pp-warn {
401
+ display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
402
+ padding: 8px 12px; margin-bottom: 10px; font-size: 12.5px;
403
+ color: var(--warn-text, #fbbf24); background: var(--warn-bg);
404
+ border: 1px solid var(--warn-border); border-radius: 10px;
405
+ }
406
+ .pp-warn b { color: var(--warn-text, #fbbf24); font-family: var(--font); }
407
+ .pp-warn .linkbtn { margin-left: auto; }
408
+ #procLog {
409
+ margin: 0; padding: 12px 14px; height: 46vh; overflow: auto;
410
+ background: var(--code-bg, var(--bg));
411
+ border: 1px solid var(--border); border-radius: 10px;
412
+ font-family: var(--font); font-size: 12px; line-height: 1.55; color: var(--text);
413
+ white-space: pre-wrap; word-break: break-word; user-select: text;
414
+ }
415
+ #procLog .pl-empty { color: var(--muted); font-style: italic; }
416
+ .pp-form { display: flex; gap: 6px; margin-top: 10px; }
417
+ .pp-form input[type="text"] {
418
+ flex: 1 1 160px; padding: 9px 10px; background: var(--bg); border: 1px solid var(--border);
419
+ border-radius: 8px; color: var(--text); font: inherit; font-size: 13px; outline: none; min-width: 0;
420
+ }
421
+ .pp-form input[type="text"]:focus { border-color: var(--accent); }
422
+ .pp-form input:disabled, .pp-form button:disabled { opacity: .5; cursor: default; }
423
+ .pp-chips { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
424
+ .pp-chips .chip {
425
+ background: transparent; border: 1px solid var(--border); color: var(--muted);
426
+ border-radius: 999px; padding: 3px 10px; font: inherit; font-size: 11.5px; cursor: pointer;
427
+ }
428
+ .pp-chips .chip:hover { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
429
+ .pp-chips .chip:disabled { opacity: .5; cursor: default; }
430
+ .pp-msg { font-size: 12.5px; color: var(--muted); line-height: 1.5; }
431
+ #viewProcs .pp-tunnel { border-top: 1px solid var(--border); margin-top: 14px; padding-top: 10px; }
432
+ #viewProcs .pp-tunhead { font-size: 11px; color: var(--muted); letter-spacing: .05em; margin-bottom: 6px; }
433
+ .pp-tunform { display: flex; gap: 6px; margin-bottom: 6px; }
434
+ .pp-tunform input[type="text"] {
435
+ flex: 1 1 120px; min-width: 0; padding: 7px 10px; background: var(--bg);
436
+ border: 1px solid var(--border); border-radius: 8px; color: var(--text);
437
+ font: inherit; font-size: 13px; outline: none;
438
+ }
439
+ .pp-tunform input[type="text"]:focus { border-color: var(--accent); }
440
+ #ppTunList .tunnel { margin-bottom: 6px; padding: 8px 10px; }
441
+ #ppTunList .tunnel:last-child { margin-bottom: 0; }
442
+
443
+ #offline {
444
+ text-align: center; font-size: 12.5px; color: var(--warn-text, #fbbf24); line-height: 1.4;
445
+ background: var(--warn-bg); border-bottom: 1px solid var(--warn-border);
446
+ padding: 7px 12px;
447
+ }
448
+
449
+ /* ----- Statusbar (estilo TUI) ----- */
450
+ #statusbar {
451
+ display: flex; align-items: center; gap: 10px;
452
+ padding: 4px 14px calc(4px + env(safe-area-inset-bottom));
453
+ background: var(--panel);
454
+ border-top: 1px solid var(--border);
455
+ font-size: 12.5px;
456
+ color: var(--muted);
457
+ white-space: nowrap; overflow: hidden;
458
+ flex-shrink: 0;
459
+ }
460
+ #statusbar .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--muted); flex-shrink: 0; }
461
+ #statusbar .dot.on { background: var(--ok, #22c55e); }
462
+ #statusbar .dot.off { background: var(--danger); }
463
+ #statusbar .sep { color: var(--border); }
464
+ #statusbar .seg { overflow: hidden; text-overflow: ellipsis; }
465
+ #statusbar .seg.mode { color: var(--mine); font-weight: 600; }
466
+ #statusbar .seg.mode.remoto { color: var(--accent); }
467
+ #sbModel {
468
+ background: transparent; border: 0; padding: 2px 0;
469
+ font: inherit; color: var(--muted); cursor: pointer;
470
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
471
+ border-bottom: 1px dotted var(--border);
472
+ }
473
+ #sbModel:hover { color: var(--accent); border-bottom-color: var(--accent); }
474
+ #sbModel.flash { animation: sbflash 1.4s ease; }
475
+ @keyframes sbflash {
476
+ 0% { color: var(--mine); border-bottom-color: var(--mine); }
477
+ 100% { color: var(--muted); border-bottom-color: var(--border); }
478
+ }
479
+
480
+ /* ----- Home ----- */
481
+ #home { flex: 1; overflow-y: auto; padding: 18px 16px; max-width: 980px; margin: 0 auto; width: 100%; min-height: 0; }
482
+ #home .hero { display: flex; flex-direction: column; gap: 4px; margin-bottom: 14px; }
483
+ #home .hero h1 { margin: 0; font-size: 18px; font-weight: 600; }
484
+ #home .hero h1::before { content: "❯ "; color: var(--mine); }
485
+ #home .hero p { margin: 0; color: var(--muted); font-size: 12.5px; }
486
+ #home .bridge-status {
487
+ display: inline-flex; align-items: center; gap: 6px;
488
+ padding: 3px 9px; border-radius: 999px;
489
+ background: var(--accent-soft);
490
+ color: var(--accent); font-size: 11.5px;
491
+ }
492
+ #home .bridge-status .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); }
493
+ #home .bridge-status.off { background: var(--warn-bg); color: var(--danger); }
494
+ #home .bridge-status.off .dot { background: var(--danger); }
495
+ .newbtn {
496
+ width: 100%; padding: 12px; border: 1px dashed var(--mine); border-radius: 10px;
497
+ background: transparent; color: var(--mine); font: inherit; font-size: 14px; font-weight: 600;
498
+ cursor: pointer; margin-bottom: 16px;
499
+ }
500
+ .newbtn:hover { background: var(--mine-soft, var(--accent-soft)); }
501
+ .projsec { margin-bottom: 18px; }
502
+ .projsec .pshead {
503
+ display: flex; align-items: center; gap: 7px;
504
+ width: 100%; margin: 0 0 8px; padding: 7px 2px;
505
+ background: transparent; border: 0; border-bottom: 1px solid var(--border);
506
+ color: var(--text); font: inherit; cursor: pointer; text-align: left;
507
+ }
508
+ .projsec .pshead .chev { width: 13px; font-size: 10px; color: var(--muted); flex-shrink: 0; }
509
+ .projsec .pshead .psname { font-size: 14px; font-weight: 700; color: var(--accent); letter-spacing: .03em; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
510
+ .projsec .pshead .pscount { font-size: 11px; color: var(--muted); flex-shrink: 0; }
511
+ .projsec .pbody { display: none; }
512
+ .projsec.open .pbody { display: block; }
513
+ .histrow { display: flex; gap: 6px; margin-top: 6px; }
514
+ .histbtn {
515
+ flex: 1; min-width: 0; padding: 8px; border: 1px dashed var(--border); border-radius: 8px;
516
+ background: transparent; color: var(--muted); font: inherit; font-size: 12px; cursor: pointer;
517
+ }
518
+ .histbtn:hover { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
519
+ @keyframes secflash {
520
+ 0% { background: var(--accent-soft); }
521
+ 100% { background: transparent; }
522
+ }
523
+ .projsec.flash .pshead { animation: secflash 1.5s ease-out; }
524
+ .cards { display: grid; gap: 8px; grid-template-columns: minmax(0, 1fr); }
525
+ .card {
526
+ background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
527
+ padding: 10px 12px; cursor: pointer; display: flex; gap: 12px; align-items: center;
528
+ transition: border-color .15s ease;
529
+ position: relative;
530
+ }
531
+ .card:hover { border-color: var(--accent); }
532
+ .card:active { transform: scale(.995); }
533
+ .card .grow { flex: 1; min-width: 0; padding-right: 30px; }
534
+ .card .cname { font-weight: 600; font-size: 15px; }
535
+ .card .cmeta { font-size: 12.5px; color: var(--muted); margin-top: 2px; white-space: nowrap;
536
+ overflow: hidden; text-overflow: ellipsis; }
537
+ .card .cpreview { font-size: 13.5px; color: var(--text); margin-top: 4px; opacity: .75;
538
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
539
+ /* Menú de opciones de la tarjeta (⋮): acciones fuera del golpe principal */
540
+ .card .kebab {
541
+ position: absolute; top: 4px; right: 6px;
542
+ background: transparent; border: 0; color: var(--muted);
543
+ font: inherit; font-size: 16px; line-height: 1; cursor: pointer;
544
+ padding: 7px 8px; border-radius: 6px;
545
+ }
546
+ .card .kebab:hover { color: var(--text); background: var(--accent-soft); }
547
+ #cardMenu {
548
+ display: none; position: fixed; z-index: 55;
549
+ background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
550
+ min-width: 170px; padding: 6px; box-shadow: var(--shadow);
551
+ }
552
+ #cardMenu.open { display: block; }
553
+ #cardMenu .item {
554
+ padding: 9px 10px; border-radius: 8px; cursor: pointer; font-size: 13px;
555
+ display: flex; align-items: center; gap: 8px;
556
+ }
557
+ #cardMenu .item:hover { background: var(--accent-soft); }
558
+ #cardMenu .item.danger { color: var(--danger); }
559
+ #cardMenu .item.danger:hover { background: rgba(239, 68, 68, .12); }
560
+ .empty { text-align: center; color: var(--muted); margin-top: 40px; font-size: 13px; }
561
+
562
+ .fab {
563
+ display: none;
564
+ position: fixed;
565
+ right: calc(16px + env(safe-area-inset-right));
566
+ bottom: calc(56px + env(safe-area-inset-bottom));
567
+ width: 52px; height: 52px;
568
+ border-radius: 50%;
569
+ border: 1px solid var(--mine);
570
+ background: var(--panel);
571
+ color: var(--mine);
572
+ font-size: 24px; line-height: 1;
573
+ cursor: pointer;
574
+ z-index: 20;
575
+ box-shadow: var(--shadow);
576
+ }
577
+ .fab:active { transform: scale(.96); }
578
+
579
+ /* ----- Chat: bloques de terminal ----- */
580
+ #chat { display: none; flex-direction: column; flex: 1; min-height: 0; position: relative; }
581
+ #messages { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 10px; align-items: stretch; min-height: 0; }
582
+ .msg {
583
+ max-width: 100%;
584
+ background: var(--panel);
585
+ border: 1px solid var(--border);
586
+ border-left: 2px solid var(--border);
587
+ border-radius: 8px;
588
+ padding: 9px 13px 10px;
589
+ font-size: 15px;
590
+ word-wrap: break-word; overflow-wrap: break-word;
591
+ }
592
+ .msg .role {
593
+ display: flex; align-items: baseline; gap: 8px;
594
+ font-size: 11px; letter-spacing: .05em;
595
+ margin-bottom: 5px;
596
+ }
597
+ .msg .role .who { font-weight: 700; }
598
+ .msg .role .rmeta { color: var(--muted); margin-left: auto; font-size: 10.5px; flex-shrink: 0; }
599
+ .msg .role .genbadge { color: var(--warn-text, #fbbf24); }
600
+ .msg .abadge {
601
+ font-size: 9.5px;
602
+ padding: 1px 7px;
603
+ border-radius: 4px;
604
+ border: 1px solid var(--border);
605
+ color: var(--muted);
606
+ letter-spacing: .06em;
607
+ line-height: 1.5;
608
+ flex-shrink: 0;
609
+ }
610
+ .msg .abadge.plan { color: var(--warn-text, #d29922); border-color: var(--warn-text, #d29922); background: var(--warn-bg, transparent); }
611
+ .msg .abadge.build { color: #f472b6; border-color: #f472b6; background: rgba(244, 114, 182, .10); }
612
+ .msg .body { white-space: pre-wrap; word-break: break-word; }
613
+ .msg.mine { border-left-color: var(--mine); background: var(--mine-soft, var(--panel)); }
614
+ .msg.mine .who { color: var(--mine); }
615
+ .msg.theirs { border-left-color: var(--accent); }
616
+ .msg.theirs .who { color: var(--accent); }
617
+ .msg.theirs.plan { border-left-color: var(--warn-text, #d29922); }
618
+ .msg.theirs.plan .who { color: var(--warn-text, #d29922); }
619
+ .msg.error { border-left-color: var(--danger); }
620
+ /* Aire extra en el cambio de turno: lo tuyo ↔ lo que responde el agente */
621
+ .msg.mine + .msg.theirs,
622
+ .msg.theirs + .msg.mine { margin-top: 10px; }
623
+ pre.codeblock {
624
+ background: var(--code-bg, var(--bg));
625
+ border: 1px solid var(--border);
626
+ border-radius: 6px;
627
+ padding: 10px 12px;
628
+ margin: 8px 0 2px;
629
+ overflow-x: auto;
630
+ font-size: 13.5px;
631
+ line-height: 1.55;
632
+ white-space: pre;
633
+ }
634
+ .msg code.inl {
635
+ background: var(--code-bg, var(--bg));
636
+ border: 1px solid var(--border);
637
+ border-radius: 4px;
638
+ padding: 0 4px;
639
+ font-size: .92em;
640
+ }
641
+ /* ----- Markdown ligero ----- */
642
+ .msg .body strong { color: var(--text); font-weight: 700; }
643
+ .msg .body em { font-style: italic; }
644
+ .msg .body del { opacity: .65; }
645
+ .msg .body a { color: var(--accent); text-decoration: underline; word-break: break-all; }
646
+ .msg .body .md-p { margin: 2px 0; }
647
+ .msg .body .md-h { font-weight: 700; color: var(--text); margin: 10px 0 3px; }
648
+ .msg .body .md-h1 { font-size: 16px; }
649
+ .msg .body .md-h2 { font-size: 15px; }
650
+ .msg .body .md-h3, .msg .body .md-h4 { font-size: 14px; }
651
+ .msg .body ul.md-ul, .msg .body ol.md-ol { margin: 4px 0 4px; padding-left: 22px; }
652
+ .msg .body .md-ul li, .msg .body .md-ol li { margin: 2px 0; }
653
+ .msg .body .md-quote {
654
+ border-left: 3px solid var(--border);
655
+ padding: 2px 0 2px 10px;
656
+ margin: 4px 0;
657
+ color: var(--muted);
658
+ }
659
+ .msg .body .md-hr { border: 0; border-top: 1px solid var(--border); margin: 10px 0; }
660
+ /* ----- Franja "opencode trabajando…" (KITT), fija sobre la barra de envío ----- */
661
+ .typing {
662
+ display: flex; align-items: center; gap: 10px;
663
+ font-size: 13px; color: var(--muted);
664
+ min-width: 0; max-width: 100%;
665
+ }
666
+ .typing.warn { color: var(--warn-text, #fbbf24); }
667
+ .procrow {
668
+ display: none; align-items: center; gap: 10px;
669
+ padding: 7px 14px; min-width: 0;
670
+ background: var(--panel); border-top: 1px solid var(--border);
671
+ flex-shrink: 0;
672
+ }
673
+ .procrow.open { display: flex; animation: procIn .18s ease-out; }
674
+ @keyframes procIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
675
+ .procrow .typing { flex: 1 1 auto; }
676
+ .proc-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
677
+ .proc-time {
678
+ flex-shrink: 0; min-width: 30px; text-align: right;
679
+ font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums;
680
+ }
681
+ .procrow.warn .proc-time { color: var(--warn-text, #fbbf24); }
682
+ .kitt {
683
+ --kitt-w: 22%;
684
+ --kitt-color: var(--accent);
685
+ position: relative; flex: 0 0 auto;
686
+ width: clamp(60px, 16vw, 120px); height: 16px;
687
+ border: 1px solid var(--border);
688
+ border-radius: 4px;
689
+ background: rgba(127, 127, 127, .07);
690
+ overflow: hidden;
691
+ }
692
+ @supports (color: color-mix(in srgb, red 50%, blue)) {
693
+ .kitt { background: color-mix(in srgb, var(--muted) 7%, transparent); }
694
+ }
695
+ /* La luz es una estela simetrica (gradiente) con una cabeza luminosa. */
696
+ .kitt-scan {
697
+ position: absolute; top: 0; bottom: 0; left: 0;
698
+ width: var(--kitt-w);
699
+ background: linear-gradient(90deg, transparent, var(--kitt-color) 50%, transparent);
700
+ animation: kittScan 1.5s ease-in-out infinite alternate;
701
+ }
702
+ .kitt-scan::after {
703
+ content: ''; position: absolute; top: 50%; left: 50%;
704
+ width: 2px; height: 8px; margin: -4px 0 0 -1px;
705
+ border-radius: 1px; background: var(--kitt-color);
706
+ box-shadow: 0 0 8px 2px var(--kitt-color);
707
+ }
708
+ @keyframes kittScan {
709
+ from { left: 0; }
710
+ to { left: calc(100% - var(--kitt-w)); }
711
+ }
712
+ @keyframes kittPulse { 0%, 100% { opacity: 1; } 50% { opacity: .4; } }
713
+ /* Sin puente: gris y lenta. Alerta (>4 min): ámbar y palpitando. */
714
+ .typing.pause .kitt { --kitt-color: var(--muted); }
715
+ .typing.pause .kitt-scan { animation-duration: 3.5s; }
716
+ .typing.pause .kitt-scan::after { box-shadow: none; }
717
+ .typing.warn .kitt { --kitt-color: var(--warn-text, #fbbf24); }
718
+ .typing.warn .kitt-scan {
719
+ animation: kittScan 1s ease-in-out infinite alternate, kittPulse 1.4s ease-in-out infinite;
720
+ }
721
+ @media (prefers-reduced-motion: reduce) {
722
+ .kitt-scan { animation: none; left: 50%; transform: translateX(-50%); }
723
+ .typing.warn .kitt-scan { animation: kittPulse 1.4s ease-in-out infinite; }
724
+ .sbusy { animation: none; opacity: 1; }
725
+ .procrow.open { animation: none; }
726
+ }
727
+ .stopbtn {
728
+ background: transparent; color: var(--danger); border: 1px solid var(--danger);
729
+ border-radius: 8px; padding: 7px 11px; font: inherit; font-size: 12.5px; font-weight: 600;
730
+ cursor: pointer; flex-shrink: 0; min-height: 38px; min-width: 38px;
731
+ display: inline-flex; align-items: center; justify-content: center; gap: 5px;
732
+ }
733
+ .stopbtn:hover { background: rgba(239, 68, 68, .12); }
734
+ .stopbtn:disabled { opacity: .55; cursor: default; }
735
+ .stopbadge {
736
+ color: var(--danger); border: 1px solid var(--danger); border-radius: 5px;
737
+ font-size: 10px; padding: 1px 6px; letter-spacing: .04em;
738
+ }
739
+ .msg.canceled { opacity: .55; }
740
+ details.reasoning {
741
+ margin: 0 0 8px; border: 1px dashed var(--border); border-radius: 8px;
742
+ background: rgba(128, 128, 128, .08); overflow: hidden;
743
+ }
744
+ details.reasoning summary {
745
+ cursor: pointer; padding: 6px 10px; font-size: 12px; color: var(--muted);
746
+ user-select: none; list-style-position: inside;
747
+ }
748
+ details.reasoning summary .rsize { font-size: 10.5px; opacity: .75; }
749
+ details.reasoning .rbody {
750
+ padding: 4px 12px 10px; font-size: 13px; color: var(--muted);
751
+ border-top: 1px dashed var(--border);
752
+ }
753
+ details.reasoning .rbody p { margin: 6px 0; }
754
+
755
+ form.sendbar {
756
+ display: flex; align-items: flex-end; gap: 8px;
757
+ padding: 8px 14px calc(8px + env(safe-area-inset-bottom));
758
+ background: var(--panel); border-top: 1px solid var(--border);
759
+ position: relative;
760
+ }
761
+ form.sendbar .prompt {
762
+ color: var(--mine); font-weight: 700; font-size: 15px; user-select: none; flex-shrink: 0;
763
+ }
764
+ form.sendbar textarea {
765
+ flex: 1; min-width: 0; resize: none;
766
+ padding: 10px 2px; border: 0; border-bottom: 1px solid var(--border);
767
+ background: transparent; color: var(--text); font: inherit; font-size: 16px; line-height: 1.45;
768
+ outline: none; max-height: 148px; overflow-y: hidden;
769
+ }
770
+ form.sendbar textarea:focus { border-bottom-color: var(--mine); }
771
+ form.sendbar textarea::placeholder { color: var(--muted); }
772
+ form.sendbar .prompt { padding-bottom: 11px; }
773
+ form.sendbar .imgbtn { margin-bottom: 3px; }
774
+ .slash-panel {
775
+ display: none; position: absolute; bottom: 100%; left: 10px; right: 10px;
776
+ margin-bottom: 8px; background: var(--panel); border: 1px solid var(--border);
777
+ border-radius: 10px; box-shadow: var(--shadow); max-height: 44vh; overflow-y: auto;
778
+ padding: 6px; z-index: 35;
779
+ }
780
+ .slash-panel .item { padding: 8px 10px; border-radius: 8px; cursor: pointer; font-size: 13.5px; }
781
+ .slash-panel .item.sel, .slash-panel .item:hover { background: var(--accent-soft); }
782
+ .slash-panel .item b { color: var(--mine); font-weight: 600; }
783
+ .slash-panel .item span { display: block; color: var(--muted); font-size: 11px; margin-top: 1px; }
784
+ .tpl-panel .tpl-row { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: 8px; cursor: pointer; font-size: 13.5px; }
785
+ .tpl-panel .tpl-row:hover { background: var(--accent-soft); }
786
+ .tpl-panel .tpl-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
787
+ .tpl-panel .tpl-del { background: transparent; border: 0; color: var(--muted); cursor: pointer; font: inherit; padding: 2px 6px; border-radius: 6px; flex-shrink: 0; }
788
+ .tpl-panel .tpl-del:hover { color: var(--danger); }
789
+ .tpl-panel .tpl-empty { color: var(--muted); font-size: 12px; padding: 8px 10px; line-height: 1.4; }
790
+ .tpl-panel .tpl-save {
791
+ display: block; width: 100%; margin-top: 6px; padding: 8px; border: 1px dashed var(--border);
792
+ border-radius: 8px; background: transparent; color: var(--muted); font: inherit; font-size: 12.5px; cursor: pointer;
793
+ }
794
+ .tpl-panel .tpl-save:hover { color: var(--accent); border-color: var(--accent); }
795
+ form.sendbar button {
796
+ padding: 9px 16px; border: 1px solid var(--mine); border-radius: 8px; background: transparent; color: var(--mine);
797
+ font: inherit; font-size: 13.5px; font-weight: 600; cursor: pointer;
798
+ min-height: 40px;
799
+ }
800
+ form.sendbar button:hover { background: var(--mine-soft, var(--accent-soft)); }
801
+ form.sendbar button:disabled { opacity: .4; cursor: not-allowed; }
802
+ form.sendbar .imgbtn {
803
+ background: transparent; border: 1px solid var(--border); color: var(--muted);
804
+ border-radius: 8px; width: 36px; height: 34px; font-size: 14px; line-height: 1;
805
+ cursor: pointer; flex-shrink: 0; font-family: var(--font);
806
+ display: inline-flex; align-items: center; justify-content: center;
807
+ }
808
+ form.sendbar .imgbtn:hover { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); }
809
+ form.sendbar .imgbtn svg { flex-shrink: 0; display: block; }
810
+ form.sendbar .imgbtn.listening {
811
+ color: var(--danger); border-color: var(--danger); background: transparent;
812
+ animation: micpulse 1.1s ease-in-out infinite;
813
+ }
814
+ @keyframes micpulse {
815
+ 0%, 100% { box-shadow: 0 0 0 0 transparent; }
816
+ 50% { box-shadow: 0 0 0 4px var(--accent-soft); }
817
+ }
818
+ /* ----- Botón que abre la hoja de escritura (móvil) ----- */
819
+ .composeopen {
820
+ display: none; align-items: center; gap: 10px; width: 100%;
821
+ padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
822
+ background: var(--panel); border-top: 1px solid var(--border);
823
+ color: var(--muted); font: inherit; font-size: 15px;
824
+ cursor: pointer; text-align: left; flex-shrink: 0;
825
+ }
826
+ .composeopen .prompt { color: var(--mine); font-weight: 700; font-size: 15px; user-select: none; flex-shrink: 0; }
827
+ .composeopen.show { display: flex; }
828
+ @media (min-width: 1024px) { .composeopen.show { display: none; } }
829
+ form.sendbar .shead { display: none; }
830
+ /* ----- Móvil: la barra de envío es una hoja que emerge desde abajo ----- */
831
+ @media (max-width: 1023px) {
832
+ form.sendbar {
833
+ position: fixed; left: 0; right: 0; bottom: 0;
834
+ z-index: 46; flex-wrap: wrap;
835
+ border-radius: 14px 14px 0 0;
836
+ padding: 0 14px calc(12px + env(safe-area-inset-bottom));
837
+ transform: translateY(105%); visibility: hidden;
838
+ transition: transform .2s ease, visibility .2s;
839
+ box-shadow: 0 -8px 24px rgba(0, 0, 0, .35);
840
+ }
841
+ form.sendbar.open { transform: none; visibility: visible; }
842
+ form.sendbar .shead {
843
+ display: flex; align-items: center; justify-content: center;
844
+ position: relative; flex: 1 1 100%; order: -3;
845
+ padding: 8px 0 6px; cursor: pointer;
846
+ }
847
+ form.sendbar .shead .handle { width: 44px; height: 4px; border-radius: 2px; background: var(--border); }
848
+ form.sendbar .shead .cclose {
849
+ position: absolute; right: -8px; top: 2px;
850
+ background: transparent; border: 0; color: var(--muted);
851
+ font: inherit; font-size: 15px; padding: 6px 8px; cursor: pointer;
852
+ }
853
+ form.sendbar .shead .cclose:hover { color: var(--text); }
854
+ #imgPreview { order: -2; flex: 1 1 100%; padding: 6px 0 0; }
855
+ form.sendbar textarea { order: -1; flex: 1 1 100%; min-height: 58px; max-height: 38vh; padding-top: 4px; }
856
+ form.sendbar .imgbtn { margin-bottom: 0; }
857
+ form.sendbar button[type="submit"] { margin-left: auto; }
858
+ body.compose-open #scrim { display: block; z-index: 45; }
859
+ }
860
+ #imgPreview {
861
+ display: flex; align-items: center; gap: 10px;
862
+ padding: 8px 14px 0; font-size: 11.5px; color: var(--muted);
863
+ }
864
+ #imgPreview img {
865
+ max-height: 64px; max-width: 120px; object-fit: cover;
866
+ border-radius: 8px; border: 1px solid var(--border);
867
+ }
868
+ #imgPreview button {
869
+ background: transparent; border: 1px solid var(--border); color: var(--muted);
870
+ border-radius: 6px; width: 22px; height: 22px; line-height: 1; cursor: pointer; font: inherit;
871
+ }
872
+ #imgPreview button:hover { color: var(--danger); border-color: var(--danger); }
873
+ .msg .body .msg-img {
874
+ display: block; max-width: 100%; max-height: 420px;
875
+ border-radius: 8px; border: 1px solid var(--border);
876
+ margin: 2px 0 8px;
877
+ }
878
+
879
+ /* Copiar mensajes y bloques de código */
880
+ .copybtn {
881
+ background: transparent; border: 0; color: var(--muted); cursor: pointer;
882
+ font: inherit; font-size: 11px; padding: 0 2px; flex-shrink: 0; line-height: 1.4;
883
+ }
884
+ .copybtn:hover { color: var(--accent); }
885
+ .msg .role .copybtn { margin-left: 6px; }
886
+ .codewrap {
887
+ margin: 8px 0 2px; border: 1px solid var(--border); border-radius: 6px;
888
+ background: var(--code-bg, var(--bg)); overflow: hidden;
889
+ }
890
+ .codehead {
891
+ display: flex; align-items: center; justify-content: space-between; gap: 8px;
892
+ padding: 4px 6px 4px 10px; border-bottom: 1px solid var(--border);
893
+ background: var(--rail, rgba(255, 255, 255, .03));
894
+ font-size: 11px; color: var(--muted);
895
+ }
896
+ .codehead .lang { letter-spacing: .04em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
897
+ .codewrap pre.codeblock { border: 0; border-radius: 0; margin: 0; background: transparent; }
898
+ .hl-cm { color: var(--hl-cm); font-style: italic; }
899
+ .hl-kw { color: var(--hl-kw); }
900
+ .hl-k2 { color: var(--hl-k2); }
901
+ .hl-st { color: var(--hl-st); }
902
+ .hl-nu { color: var(--hl-nu); }
903
+ .hl-fn { color: var(--hl-fn); }
904
+ .hl-ty { color: var(--hl-ty); }
905
+ .hl-pr { color: var(--hl-pr); }
906
+
907
+ /* Separadores: días en el chat, buckets de fecha en la lista */
908
+ .daysep, .dsep {
909
+ display: flex; align-items: center; gap: 10px;
910
+ color: var(--muted); font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase;
911
+ }
912
+ .daysep::before, .daysep::after, .dsep::before, .dsep::after {
913
+ content: ""; flex: 1; border-top: 1px solid var(--border);
914
+ }
915
+ .daysep { margin: 6px 0 0; }
916
+ .dsep { margin: 8px 4px 4px; }
917
+
918
+ /* Cursor de streaming */
919
+ .stream-cursor { color: var(--accent); animation: curblink 1.05s steps(1) infinite; }
920
+ @keyframes curblink { 50% { opacity: 0; } }
921
+
922
+ /* Botón flotante: ir al último mensaje */
923
+ .jumpbtn {
924
+ display: none; position: absolute; right: 16px; bottom: 74px; z-index: 15;
925
+ background: var(--panel); border: 1px solid var(--accent); color: var(--accent);
926
+ border-radius: 999px; padding: 7px 13px; font: inherit; font-size: 12px; font-weight: 600;
927
+ cursor: pointer; box-shadow: var(--shadow);
928
+ }
929
+ .jumpbtn.show { display: inline-flex; }
930
+
931
+ /* Toast estilo TUI */
932
+ #toast {
933
+ position: fixed; left: 50%; transform: translateX(-50%) translateY(8px);
934
+ bottom: calc(84px + env(safe-area-inset-bottom));
935
+ background: var(--panel); border: 1px solid var(--border); color: var(--text);
936
+ padding: 8px 14px; border-radius: 8px; font-size: 12.5px;
937
+ opacity: 0; pointer-events: none; transition: opacity .18s ease, transform .18s ease;
938
+ z-index: 60; max-width: 88vw; box-shadow: var(--shadow);
939
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
940
+ }
941
+ #toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
942
+ #toast.ok { border-color: var(--mine); color: var(--mine); }
943
+ #toast.error { border-color: var(--danger); color: var(--danger); }
944
+
945
+ /* ----- Modal ----- */
946
+ #overlay, #modelOverlay {
947
+ display: none; position: fixed; inset: 0; background: rgba(0,0,0,.7); z-index: 50;
948
+ align-items: center; justify-content: center; padding: 16px;
949
+ }
950
+ #overlay.show, #modelOverlay.show { display: flex; }
951
+ .modal {
952
+ background: var(--panel); border: 1px solid var(--border); border-radius: 12px;
953
+ padding: 18px 16px; width: 100%; max-width: 400px; max-height: 90vh; overflow-y: auto;
954
+ box-shadow: var(--shadow);
955
+ }
956
+ .modal h2 { margin: 0 0 12px; font-size: 15px; font-weight: 600; }
957
+ .modal h2::before { content: "❯ "; color: var(--mine); }
958
+ .modal label { display: block; font-size: 12px; color: var(--muted); margin: 11px 0 5px; }
959
+ .modal input[type="text"], .modal select, .modal textarea {
960
+ width: 100%; padding: 11px; border-radius: 8px; border: 1px solid var(--border);
961
+ background: var(--bg); color: var(--text); font: inherit; font-size: 15px; outline: none;
962
+ }
963
+ .modal textarea { resize: vertical; min-height: 74px; }
964
+ .modal input:focus, .modal select:focus, .modal textarea:focus { border-color: var(--accent); }
965
+ .modal select optgroup { color: var(--muted); font-style: normal; }
966
+ .modal select optgroup option { color: var(--text); }
967
+ .modal .row { display: flex; gap: 10px; margin-top: 12px; }
968
+ .modal button {
969
+ flex: 1; padding: 11px; border-radius: 8px; font: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
970
+ min-height: 44px;
971
+ }
972
+ .modal .save { background: transparent; color: var(--mine); border: 1px solid var(--mine); }
973
+ .modal .save:hover { background: var(--mine-soft, var(--accent-soft)); }
974
+ .modal .cancel { background: transparent; color: var(--muted); border: 1px solid var(--border); }
975
+ .warn { font-size: 12.5px; color: var(--warn-text, #fbbf24); margin-top: 8px; }
976
+
977
+ /* ----- Vistas ----- */
978
+ .view { flex: 1; overflow-y: auto; padding: 18px 16px; max-width: 980px; margin: 0 auto; width: 100%; min-height: 0; }
979
+ .view h2 { margin: 0 0 12px; font-size: 16px; font-weight: 600; }
980
+ .view h2::before { content: "❯ "; color: var(--mine); }
981
+ .view .placeholder {
982
+ color: var(--muted); font-size: 13px; padding: 24px 0; text-align: center;
983
+ }
984
+
985
+ /* ----- Cambios (git) ----- */
986
+ .chg-files { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; margin: 0 0 14px; }
987
+ .chg-row { display: grid; grid-template-columns: 34px 1fr auto; gap: 8px; padding: 8px 12px; border-bottom: 1px solid var(--border); font-size: 13px; align-items: center; }
988
+ .chg-row:last-child { border-bottom: 0; }
989
+ .chg-rev {
990
+ background: transparent; border: 1px solid var(--border); color: var(--muted);
991
+ border-radius: 6px; width: 26px; height: 24px; line-height: 1; cursor: pointer; font: inherit; font-size: 12px;
992
+ }
993
+ .chg-rev:hover { color: var(--danger); border-color: var(--danger); }
994
+ .chg-rev:disabled { opacity: .4; cursor: not-allowed; }
995
+ .chg-st { font-weight: 700; text-align: center; font-size: 11px; color: var(--muted); }
996
+ .chg-st.mod { color: var(--warn-text, #d29922); }
997
+ .chg-st.add { color: var(--accent); }
998
+ .chg-st.del { color: var(--danger); }
999
+ .chg-path { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1000
+ .diff {
1001
+ background: var(--code-bg, var(--bg)); border: 1px solid var(--border);
1002
+ border-radius: 8px; overflow: auto; padding: 10px 0; margin: 0 0 20px;
1003
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
1004
+ font-size: 12px; line-height: 1.5;
1005
+ }
1006
+ .diff-line { white-space: pre; padding: 0 12px; }
1007
+ .diff-line.add { background: rgba(63, 185, 80, .12); color: #3fb950; }
1008
+ .diff-line.del { background: rgba(248, 81, 73, .12); color: #f85149; }
1009
+ .diff-line.hunk { color: var(--accent); }
1010
+ .diff-line.head { color: var(--muted); }
1011
+
1012
+ .view-head { margin-bottom: 12px; }
1013
+ .view-head h2 { margin: 0 0 4px; font-size: 16px; }
1014
+ .view-sub { font-size: 11.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1015
+ .files-toolbar { display: flex; align-items: center; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--border); margin-bottom: 8px; }
1016
+ .files-path { font-size: 11.5px; color: var(--muted); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1017
+ .linkbtn { background: transparent; border: 1px solid var(--border); color: var(--accent); padding: 5px 10px; border-radius: 8px; cursor: pointer; font: inherit; font-size: 12.5px; }
1018
+ .linkbtn:hover { background: var(--accent-soft); }
1019
+ .files-list { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }
1020
+ .files-row { display: grid; grid-template-columns: 28px 1fr auto; gap: 8px; padding: 9px 12px; cursor: pointer; align-items: center; border-bottom: 1px solid var(--border); }
1021
+ .files-row:last-child { border-bottom: 0; }
1022
+ .files-row:hover { background: var(--accent-soft); }
1023
+ .files-row .ic { text-align: center; }
1024
+ .files-row .name { font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1025
+ .files-row .size { font-size: 11px; color: var(--muted); }
1026
+ .filemeta { font-size: 11.5px; color: var(--muted); padding: 4px 0 8px; }
1027
+ .fileview {
1028
+ background: var(--code-bg, var(--bg));
1029
+ border: 1px solid var(--border);
1030
+ border-radius: 8px;
1031
+ padding: 12px;
1032
+ margin: 0 0 20px;
1033
+ overflow: auto;
1034
+ max-height: 72vh;
1035
+ font-size: 12.5px;
1036
+ line-height: 1.5;
1037
+ white-space: pre;
1038
+ word-break: normal;
1039
+ overflow-wrap: normal;
1040
+ }
1041
+ /* ----- Lector de archivos (código estilo editor, markdown, imágenes) ----- */
1042
+ .fviewer {
1043
+ background: var(--panel);
1044
+ border: 1px solid var(--border);
1045
+ border-radius: 10px;
1046
+ overflow: hidden;
1047
+ margin: 0 0 20px;
1048
+ }
1049
+ .fbar {
1050
+ display: flex; align-items: center; gap: 10px;
1051
+ padding: 7px 12px;
1052
+ border-bottom: 1px solid var(--border);
1053
+ background: var(--code-bg, var(--bg));
1054
+ }
1055
+ .fbar .fname { font-weight: 600; font-size: 13px; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1056
+ .fbar .fsize { font-size: 11px; color: var(--muted); flex-shrink: 0; }
1057
+ .fbar .fro { margin-left: auto; font-size: 10px; color: var(--muted); letter-spacing: .06em; text-transform: uppercase; flex-shrink: 0; }
1058
+ .fbar .copybtn {
1059
+ border: 0; background: transparent; color: var(--muted); cursor: pointer;
1060
+ font: inherit; font-size: 12.5px; padding: 2px 6px; border-radius: 6px; flex-shrink: 0;
1061
+ }
1062
+ .fbar .copybtn:hover { color: var(--accent); background: var(--accent-soft); }
1063
+ .fscroll { overflow: auto; max-height: 74vh; }
1064
+ .fcodewrap {
1065
+ min-width: max-content; width: max-content;
1066
+ background: var(--code-bg, var(--bg));
1067
+ padding: 8px 0;
1068
+ font-family: var(--font);
1069
+ font-size: 13px;
1070
+ line-height: 1.55;
1071
+ }
1072
+ .frow { display: flex; white-space: pre; }
1073
+ .frow:hover { background: rgba(128, 128, 128, .06); }
1074
+ .frow .fg {
1075
+ flex: 0 0 auto; min-width: 3.4em;
1076
+ padding: 0 12px 0 10px; text-align: right;
1077
+ color: var(--muted); opacity: .72; user-select: none;
1078
+ }
1079
+ .frow .fc { flex: 1; min-width: 0; padding-right: 16px; white-space: pre; color: var(--text); }
1080
+ .fimgwrap {
1081
+ padding: 18px; display: flex; justify-content: center;
1082
+ background: var(--code-bg, var(--bg));
1083
+ }
1084
+ .fimgwrap img {
1085
+ max-width: 100%; max-height: 74vh; object-fit: contain;
1086
+ border-radius: 6px; box-shadow: var(--shadow);
1087
+ }
1088
+ /* Markdown dentro del lector (mismas reglas que el chat, sin `.msg .body`) */
1089
+ .mdview { padding: 14px 16px; font-size: 14px; line-height: 1.6; color: var(--text); background: var(--panel); }
1090
+ .mdview strong { color: var(--text); font-weight: 700; }
1091
+ .mdview em { font-style: italic; }
1092
+ .mdview del { opacity: .65; }
1093
+ .mdview a { color: var(--accent); text-decoration: underline; word-break: break-all; }
1094
+ .mdview code.inl {
1095
+ background: var(--code-bg, var(--bg)); border: 1px solid var(--border); border-radius: 4px;
1096
+ padding: 0 5px; font-size: .9em;
1097
+ }
1098
+ .mdview .md-p { margin: 4px 0; }
1099
+ .mdview .md-h { font-weight: 700; color: var(--text); margin: 14px 0 6px; }
1100
+ .mdview .md-h1 { font-size: 19px; border-bottom: 1px solid var(--border); padding-bottom: 4px; }
1101
+ .mdview .md-h2 { font-size: 17px; }
1102
+ .mdview .md-h3, .mdview .md-h4 { font-size: 15px; }
1103
+ .mdview ul.md-ul, .mdview ol.md-ol { margin: 4px 0 6px; padding-left: 24px; }
1104
+ .mdview .md-ul li, .mdview .md-ol li { margin: 2px 0; }
1105
+ .mdview .md-quote {
1106
+ border-left: 3px solid var(--border); padding: 2px 0 2px 12px; margin: 6px 0;
1107
+ color: var(--muted);
1108
+ }
1109
+ .mdview .md-hr { border: 0; border-top: 1px solid var(--border); margin: 12px 0; }
1110
+ .mdview .codewrap { margin: 10px 0; }
1111
+ .search-bar { display: flex; align-items: center; gap: 8px; padding: 10px 12px; background: var(--panel); border: 1px solid var(--border); border-radius: 10px; margin-bottom: 12px; }
1112
+ .search-bar input { flex: 1; border: 0; background: transparent; color: var(--text); font: inherit; font-size: 16px; outline: none; }
1113
+ .search-bar span { font-size: 13px; color: var(--muted); }
1114
+ .search-bar button { background: transparent; border: 0; color: var(--muted); cursor: pointer; font-size: 17px; padding: 0 6px; }
1115
+ .search-summary { font-size: 11.5px; color: var(--muted); margin: 8px 0 12px; }
1116
+ .search-group { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 12px; margin-bottom: 10px; }
1117
+ .search-group-head { font-size: 12.5px; margin-bottom: 6px; }
1118
+ .search-group-head .link { color: var(--accent); text-decoration: none; }
1119
+ .search-match { font-size: 12.5px; padding: 6px 0; border-top: 1px solid var(--border); }
1120
+ .search-match:first-of-type { border-top: 0; }
1121
+ .search-match .role { display: inline-block; font-size: 10px; padding: 1px 6px; border-radius: 4px; background: var(--accent-soft); color: var(--accent); margin-right: 6px; }
1122
+ .sessions-list .card .icbtn { padding: 8px 12px; }
1123
+
1124
+ /* Vista: vista previa del proyecto vía túnel (TunnelMole en la PC) */
1125
+ .tunnel-form { display: flex; gap: 8px; margin-bottom: 14px; align-items: center; }
1126
+ .tunnel-form input[type="text"] {
1127
+ width: 170px; flex-shrink: 0; padding: 10px 12px;
1128
+ background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
1129
+ color: var(--text); font: inherit; font-size: 15px; outline: none;
1130
+ }
1131
+ .tunnel-form input[type="text"]:focus { border-color: var(--accent); }
1132
+ .tunnel {
1133
+ background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
1134
+ padding: 12px; margin-bottom: 10px;
1135
+ }
1136
+ .tunnel .thead { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
1137
+ .tunnel .thead b { font-size: 13.5px; }
1138
+ .tunnel .thead .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok, #22c55e); flex-shrink: 0; }
1139
+ .tunnel .thead .tnote { font-size: 11px; color: var(--muted); }
1140
+ .tunnel .thead .linkbtn { margin-left: auto; }
1141
+ .tunnel .turl {
1142
+ display: flex; align-items: center; gap: 8px; padding: 7px 9px;
1143
+ background: var(--code-bg, var(--bg)); border: 1px solid var(--border); border-radius: 8px;
1144
+ margin-bottom: 6px; font-size: 12.5px; overflow: hidden;
1145
+ }
1146
+ .tunnel .turl a { color: var(--accent); text-decoration: none; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1147
+ .tunnel .tnote-sec { font-size: 11px; color: var(--muted); margin-top: 4px; }
1148
+ mark { background: var(--accent); color: var(--bg); padding: 0 2px; border-radius: 3px; }
1149
+
1150
+ /* ----- Panel derecho: estructura del proyecto + vista previa (escritorio) ----- */
1151
+ #rpanel {
1152
+ width: var(--rp-w, 380px);
1153
+ position: relative;
1154
+ background: var(--rail, var(--panel));
1155
+ border-left: 1px solid var(--border);
1156
+ display: flex; flex-direction: column;
1157
+ flex-shrink: 0; z-index: 30; min-width: 0;
1158
+ }
1159
+ #rpanel .rp-tabs {
1160
+ display: flex; align-items: center; gap: 6px;
1161
+ padding: 8px 10px; border-bottom: 1px solid var(--border); min-height: 54px;
1162
+ }
1163
+ #rpanel .rp-tab {
1164
+ background: transparent; border: 1px solid transparent; color: var(--muted);
1165
+ border-radius: 8px; padding: 5px 10px; font: inherit; font-size: 12px; cursor: pointer;
1166
+ }
1167
+ #rpanel .rp-tab:hover { color: var(--text); }
1168
+ #rpanel .rp-tab.active { color: var(--text); border-color: var(--border); background: var(--accent-soft); }
1169
+ #rpanel .rp-hide {
1170
+ margin-left: auto; background: transparent; border: 0; color: var(--muted);
1171
+ cursor: pointer; font: inherit; font-size: 13px; line-height: 1; padding: 5px 7px; border-radius: 6px;
1172
+ }
1173
+ #rpanel .rp-hide:hover { color: var(--text); background: var(--accent-soft); }
1174
+ #rpanel .rp-body { flex: 1; min-height: 0; display: flex; flex-direction: column; }
1175
+ #rpanel .rp-pane { flex: 1; min-height: 0; overflow: auto; padding: 4px 4px 12px; }
1176
+ #rpanel .rp-pane[hidden] { display: none; }
1177
+ #rpanel .rp-root { padding: 6px 9px; font-size: 11px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border-bottom: 1px solid var(--border); margin-bottom: 4px; }
1178
+ #rpanel .rp-empty { padding: 14px 12px; color: var(--muted); font-size: 12.5px; }
1179
+ .rp-row {
1180
+ display: flex; align-items: center; gap: 5px; width: 100%;
1181
+ background: transparent; border: 0; color: var(--text);
1182
+ font: inherit; font-size: 12.5px; text-align: left; cursor: pointer;
1183
+ padding: 3px 8px; border-radius: 6px; white-space: nowrap;
1184
+ }
1185
+ .rp-row:hover { background: var(--accent-soft); }
1186
+ .rp-row .rp-chev { width: 11px; flex-shrink: 0; color: var(--muted); font-size: 9px; }
1187
+ .rp-row .rp-ico { flex-shrink: 0; font-size: 11px; opacity: .85; }
1188
+ .rp-row .rp-name { overflow: hidden; text-overflow: ellipsis; }
1189
+ .rp-row.file .rp-name { color: var(--muted); }
1190
+ .rp-row.file:hover .rp-name { color: var(--text); }
1191
+ #rpanel .rp-pane.pv { display: flex; flex-direction: column; overflow: hidden; padding: 0; }
1192
+ #rpanel .rp-pane.pv[hidden] { display: none; }
1193
+ #rpanel .rp-pv-bar { display: flex; gap: 6px; padding: 8px 10px; align-items: center; }
1194
+ #rpanel .rp-pv-bar input {
1195
+ flex: 1; min-width: 0; padding: 6px 9px; background: var(--bg);
1196
+ border: 1px solid var(--border); border-radius: 8px; color: var(--text);
1197
+ font: inherit; font-size: 12.5px; outline: none;
1198
+ }
1199
+ #rpanel .rp-pv-bar input:focus { border-color: var(--accent); }
1200
+ #rpanel .rp-pv-url { padding: 0 10px 8px; font-size: 11px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1201
+ #rpanel .rp-pv-url a { color: var(--accent); }
1202
+ #rpanel .rp-frame { flex: 1; min-height: 0; width: 100%; border: 0; background: #fff; }
1203
+ #rpanel .rp-resizer {
1204
+ position: absolute; top: 0; bottom: 0; left: -3px; width: 6px;
1205
+ cursor: col-resize; z-index: 31; touch-action: none;
1206
+ }
1207
+ #rpanel .rp-resizer:hover, #rpanel .rp-resizer.dragging { background: var(--accent-soft); }
1208
+ .rp-reveal {
1209
+ display: none; position: fixed; top: 50%; right: 0; transform: translateY(-50%);
1210
+ background: var(--panel); border: 1px solid var(--border); border-right: 0;
1211
+ color: var(--muted); border-radius: 8px 0 0 8px;
1212
+ padding: 16px 5px; font: inherit; font-size: 12px; line-height: 1; cursor: pointer; z-index: 31;
1213
+ }
1214
+ .rp-reveal:hover { color: var(--accent); background: var(--accent-soft); }
1215
+
1216
+ /* ----- Responsive ----- */
1217
+ @media (max-width: 1023px) {
1218
+ #sidebar {
1219
+ position: fixed; top: 0; bottom: 0; left: 0;
1220
+ width: min(var(--sb-w, 264px), 90vw);
1221
+ transform: translateX(-100%);
1222
+ box-shadow: var(--shadow);
1223
+ }
1224
+ body.sidebar-open #sidebar { transform: translateX(0); }
1225
+ body.sidebar-open #scrim { display: block; }
1226
+ header .burger { display: inline-flex; }
1227
+ .fab.show { display: block; }
1228
+ .sb-reveal { display: none !important; }
1229
+ #rpanel { display: none !important; }
1230
+ .rp-reveal { display: none !important; }
1231
+ header #btnPanel { display: none; }
1232
+ }
1233
+ @media (min-width: 1024px) {
1234
+ header .burger { display: none; }
1235
+ .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1236
+ .fab { display: none !important; }
1237
+ #sidebar .resizer { display: block; }
1238
+ body.sb-hide #sidebar { display: none; }
1239
+ body.sb-hide .sb-reveal { display: block; }
1240
+ body.rp-hide #rpanel { display: none; }
1241
+ body.rp-hide .rp-reveal { display: block; }
1242
+ }
1243
+ @media (min-width: 720px) {
1244
+ header a.logout { display: inline-flex; }
1245
+ }
1246
+ @media (max-width: 479px) {
1247
+ body { font-size: 14.5px; }
1248
+ header .sub { max-width: 44vw; }
1249
+ #messages { padding: 10px; gap: 8px; }
1250
+ .msg { font-size: 14.5px; }
1251
+ form.sendbar textarea { font-size: 16px; } /* evita zoom iOS */
1252
+ .sitem { padding: 11px 8px 11px 12px; font-size: 13.5px; }
1253
+ .proj-head { font-size: 13px; }
1254
+ .utilities button { padding: 9px 4px; font-size: 12px; }
1255
+ .procrow { padding: 7px 10px; gap: 8px; }
1256
+ .stopbtn .stoptxt { display: none; } /* en pantallas chicas, solo el ícono */
1257
+ }
1258
+ </style>
1259
+ </head>
1260
+ <body data-theme="{{THEME}}">
1261
+ <div id="scrim"></div>
1262
+ <button type="button" class="sb-reveal" id="sbReveal" title="Mostrar panel" aria-label="Mostrar panel">»</button>
1263
+
1264
+ <aside id="sidebar" aria-label="Proyectos y sesiones">
1265
+ <div class="brand" id="brandHome" title="Ir al inicio">
1266
+ <div class="logo">❯_</div>
1267
+ <div class="name">OpenBridge</div>
1268
+ <div class="ver">v3</div>
1269
+ <button type="button" class="sbhide" id="btnSbHide" title="Ocultar panel" aria-label="Ocultar panel">«</button>
1270
+ </div>
1271
+ <button class="newsession" id="btnNewSession" type="button">+ nueva sesión</button>
1272
+ <div class="sidesearch">
1273
+ <input type="text" id="sideSearch" placeholder="filtrar proyectos…" autocomplete="off">
1274
+ </div>
1275
+ <div id="bridgeBar" title="Computadora conectada: sus carpetas, modelos y chats"></div>
1276
+ <div id="sessionTree"></div>
1277
+ <div class="utilities">
1278
+ <button data-view="home" type="button" title="Chats">chats</button>
1279
+ <button data-view="files" type="button" title="Archivos">archivos</button>
1280
+ <button data-view="sessions" type="button" title="Sesiones opencode">sesiones</button>
1281
+ <button data-view="search" type="button" title="Buscar">buscar</button>
1282
+ <button data-view="preview" type="button" title="Vista previa del proyecto (túnel)">preview</button>
1283
+ <button data-view="procs" type="button" title="Procesos de la sesión">procs</button>
1284
+ <button data-view="changes" type="button" title="Cambios del proyecto (git)">cambios</button>
1285
+ <button data-view="mcp" type="button" title="Servidores MCP de opencode">mcp</button>
1286
+ <button data-view="devices" type="button" title="PCs emparejadas a tu cuenta" style="display:none">dispositivos</button>
1287
+ </div>
1288
+ <div class="footer">
1289
+ <div class="row" id="bridgeStatusRow">
1290
+ <span class="dot" id="bridgeDot"></span>
1291
+ <span id="bridgeStatusText">puente: desconocido</span>
1292
+ </div>
1293
+ <div class="row">
1294
+ <span class="userbadge" id="userBadge" title="sesión actual">{{USER_NAME}}</span>
1295
+ <a class="logout" href="logout.php">salir</a>
1296
+ </div>
1297
+ </div>
1298
+ <div class="resizer" id="sbResizer" title="Arrastrá para ajustar · doble clic para restablecer"></div>
1299
+ </aside>
1300
+
1301
+ <main>
1302
+ <header>
1303
+ <button class="burger" id="btnBurger" aria-label="Menú">☰</button>
1304
+ <button class="back" id="btnBack" style="display:none" aria-label="Volver">←</button>
1305
+ <div class="left">
1306
+ <div class="titles">
1307
+ <div class="title" id="hTitle">OpenBridge</div>
1308
+ <div class="sub" id="hSub">conectando…</div>
1309
+ </div>
1310
+ </div>
1311
+ <div class="actions">
1312
+ <button class="icbtn" id="btnPanel" type="button" title="Panel del proyecto (estructura / vista previa)" aria-label="Panel del proyecto" aria-pressed="false">▥</button>
1313
+ <button class="icbtn" id="btnPush" type="button" title="Activar avisos en este dispositivo" aria-label="Activar avisos" aria-pressed="false"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0"/></svg></button>
1314
+ <button class="icbtn" id="btnSkin" title="Cambiar tema" aria-label="Cambiar tema">◍</button>
1315
+ <button class="icbtn" id="btnCmds" title="Comandos" aria-label="Comandos disponibles">/</button>
1316
+ </div>
1317
+ </header>
1318
+
1319
+ <div id="cmdsPanel" class="cmds"></div>
1320
+ <div id="skinPanel"></div>
1321
+ <div id="cardMenu"></div>
1322
+
1323
+ <div id="home"></div>
1324
+
1325
+ <div id="chat">
1326
+ <div id="offline" style="display:none">⚠ el puente de la computadora seleccionada está apagado · iniciá “OpenBridge” en esa PC</div>
1327
+ <div id="messages" aria-live="polite"></div>
1328
+ <button type="button" id="jumpBtn" class="jumpbtn" aria-label="Ir al último mensaje">↓ final</button>
1329
+ <div id="imgPreview" style="display:none">
1330
+ <img id="imgThumb" alt="imagen adjunta">
1331
+ <button type="button" id="imgRemove" title="Quitar imagen" aria-label="Quitar imagen">✕</button>
1332
+ <span id="imgMeta"></span>
1333
+ </div>
1334
+ <div id="procrow" class="procrow" role="status">
1335
+ <div class="typing proc" id="proc-ind">
1336
+ <span class="kitt" aria-hidden="true"><span class="kitt-scan"></span></span>
1337
+ <span class="proc-label" id="proc-label"></span>
1338
+ </div>
1339
+ <span class="proc-time" id="proc-time"></span>
1340
+ <button type="button" class="stopbtn" id="btnStop" title="Corta la ejecución en la PC" aria-label="Detener"><span aria-hidden="true">⏹</span><span class="stoptxt">detener</span></button>
1341
+ </div>
1342
+ <button type="button" id="composeOpen" class="composeopen" aria-label="Escribir mensaje">
1343
+ <span class="prompt" aria-hidden="true">❯</span>
1344
+ <span class="cph">escribí un mensaje…</span>
1345
+ </button>
1346
+ <form class="sendbar" id="sendForm" style="display:none">
1347
+ <div class="shead" id="composeClose" title="Cerrar">
1348
+ <span class="handle" aria-hidden="true"></span>
1349
+ <button type="button" class="cclose" aria-label="Cerrar">✕</button>
1350
+ </div>
1351
+ <span class="prompt">❯</span>
1352
+ <textarea id="input" rows="1" placeholder="escribí un mensaje…" autocomplete="off"></textarea>
1353
+ <button type="button" class="imgbtn" id="btnTpl" title="Plantillas de prompts" aria-label="Plantillas de prompts"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M17.593 3.322c1.1.128 1.907 1.077 1.907 2.185V21L12 17.25 4.5 21V5.507c0-1.108.806-2.057 1.907-2.185a48.507 48.507 0 0 1 11.186 0Z"/></svg></button>
1354
+ <button type="button" class="imgbtn" id="btnImg" title="Adjuntar imagen (modelos con visión)" aria-label="Adjuntar imagen"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"/></svg></button>
1355
+ <button type="button" class="imgbtn" id="btnMic" title="Dictar por voz" aria-label="Dictar por voz"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18.75a6 6 0 0 0 6-6v-1.5m-6 7.5a6 6 0 0 1-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 0 1-3-3V4.5a3 3 0 1 1 6 0v8.25a3 3 0 0 1-3 3Z"/></svg></button>
1356
+ <input type="file" id="imgInput" accept="image/png,image/jpeg,image/webp,image/gif" style="display:none">
1357
+ <button type="submit" id="sendBtn">enviar</button>
1358
+ <div id="slashPanel" class="slash-panel"></div>
1359
+ <div id="tplPanel" class="slash-panel tpl-panel"></div>
1360
+ </form>
1361
+ </div>
1362
+
1363
+ <div id="viewFiles" class="view" style="display:none"></div>
1364
+ <div id="viewSessions" class="view" style="display:none"></div>
1365
+ <div id="viewSearch" class="view" style="display:none"></div>
1366
+ <div id="viewHistory" class="view" style="display:none"></div>
1367
+ <div id="viewPreview" class="view" style="display:none"></div>
1368
+ <div id="viewProcs" class="view" style="display:none"></div>
1369
+ <div id="viewChanges" class="view" style="display:none"></div>
1370
+ <div id="viewMcp" class="view" style="display:none"></div>
1371
+ <div id="viewDevices" class="view" style="display:none"></div>
1372
+
1373
+ <footer id="statusbar">
1374
+ <span class="dot" id="sbDot"></span>
1375
+ <span class="seg mode" id="sbMode">…</span>
1376
+ <span class="sep">│</span>
1377
+ <button type="button" id="sbModel" title="Cambiar modelo / agente de este chat">sin modelo</button>
1378
+ <span class="sep" id="sbSep2">│</span>
1379
+ <span class="seg" id="sbExtra"></span>
1380
+ </footer>
1381
+ </main>
1382
+
1383
+ <aside id="rpanel" aria-label="Panel del proyecto">
1384
+ <div class="rp-tabs">
1385
+ <button type="button" class="rp-tab active" data-rptab="tree">estructura</button>
1386
+ <button type="button" class="rp-tab" data-rptab="preview">preview</button>
1387
+ <button type="button" class="rp-hide" id="btnRpHide" title="Ocultar panel" aria-label="Ocultar panel">✕</button>
1388
+ </div>
1389
+ <div class="rp-body">
1390
+ <div class="rp-pane" id="rpTreePane"></div>
1391
+ <div class="rp-pane pv" id="rpPreviewPane" hidden>
1392
+ <div class="rp-pv-bar">
1393
+ <input type="text" id="rpPvPort" inputmode="numeric" placeholder="puerto (ej: 3001)" autocomplete="off">
1394
+ <button type="button" class="linkbtn" id="rpPvGo">ver</button>
1395
+ <button type="button" class="linkbtn" id="rpPvTun">túnel</button>
1396
+ </div>
1397
+ <div class="rp-pv-url" id="rpPvUrl"></div>
1398
+ <iframe id="rpPvFrame" class="rp-frame" title="vista previa" referrerpolicy="no-referrer"></iframe>
1399
+ </div>
1400
+ </div>
1401
+ <div class="rp-resizer" id="rpResizer" title="Arrastrá para ajustar · doble clic para restablecer"></div>
1402
+ </aside>
1403
+ <button type="button" class="rp-reveal" id="rpReveal" title="Mostrar panel" aria-label="Mostrar panel">«</button>
1404
+
1405
+ <button class="fab" id="fabNew" title="Nueva sesión" aria-label="Nueva sesión">+</button>
1406
+
1407
+ <div id="overlay">
1408
+ <div class="modal">
1409
+ <h2>nueva sesión</h2>
1410
+ <label for="fFolder">Carpeta de trabajo</label>
1411
+ <select id="fFolder"></select>
1412
+ <div id="fFolderCreate" style="display:none; margin-top:6px">
1413
+ <div style="font-size:12px; color:var(--muted)">¿no está tu carpeta?</div>
1414
+ <div style="display:flex; gap:6px; margin-top:4px">
1415
+ <input type="text" id="fNewFolder" placeholder="nombre de la carpeta" style="flex:1">
1416
+ <button type="button" id="btnNewFolder" style="flex:0 0 auto; padding:11px 14px; border:1px solid var(--mine); border-radius:8px; background:transparent; color:var(--mine); font-family:var(--font); font-weight:600; cursor:pointer">crear</button>
1417
+ </div>
1418
+ <div id="fNewFolderMsg" style="font-size:11.5px; color:var(--muted); margin-top:4px"></div>
1419
+ </div>
1420
+ <label for="fModel">Modelo de IA (todos los proveedores)</label>
1421
+ <input type="text" id="fModelSearch" placeholder="buscar modelo… (ej: glm, deepseek)" autocomplete="off">
1422
+ <select id="fModel"></select>
1423
+ <label for="fAgent">Cómo debe actuar</label>
1424
+ <select id="fAgent"></select>
1425
+ <label for="fMsg">Mensaje (opcional — vacío = solo crear el chat)</label>
1426
+ <textarea id="fMsg" rows="3" placeholder="escribí tu pedido… (enter = crear · shift+enter = salto de línea)"></textarea>
1427
+ <div class="warn" id="fWarn" style="display:none">el puente aún no sincronizó carpetas/modelos.</div>
1428
+ <div class="row">
1429
+ <button type="button" class="cancel" id="btnCancel">cancelar</button>
1430
+ <button type="button" class="save" id="btnSave">crear</button>
1431
+ </div>
1432
+ </div>
1433
+ </div>
1434
+
1435
+ <div id="modelOverlay">
1436
+ <div class="modal">
1437
+ <h2>cambiar modelo</h2>
1438
+ <div class="msub" id="mTitle" style="font-size:11.5px;color:var(--muted);margin:-6px 0 4px"></div>
1439
+ <label for="fModel2">Modelo de IA (todos los proveedores)</label>
1440
+ <input type="text" id="mSearch" placeholder="buscar modelo… (ej: glm, deepseek)" autocomplete="off">
1441
+ <select id="fModel2"></select>
1442
+ <label for="fAgent2">Cómo debe actuar</label>
1443
+ <select id="fAgent2"></select>
1444
+ <div class="warn" id="mWarn" style="display:none">el puente aún no sincronizó modelos.</div>
1445
+ <div class="hint" style="font-size:11px;color:var(--muted);margin-top:8px">aplica desde el próximo mensaje · la conversación continúa igual</div>
1446
+ <div class="row">
1447
+ <button type="button" class="cancel" id="btnModelCancel">cancelar</button>
1448
+ <button type="button" class="save" id="btnModelSave">aplicar</button>
1449
+ </div>
1450
+ </div>
1451
+ </div>
1452
+
1453
+ <script>
1454
+ window.APP_CONFIG = {
1455
+ csrf: "{{CSRF}}",
1456
+ initialSession: {{INITIAL_SESSION}},
1457
+ theme: "{{THEME}}",
1458
+ themes: {{THEMES_JSON}},
1459
+ sseDisabled: {{SSE_DISABLED}},
1460
+ pushEnabled: {{PUSH_ENABLED}},
1461
+ pushKey: "{{PUSH_KEY}}",
1462
+ userName: "{{USER_NAME}}",
1463
+ userRole: "{{USER_ROLE}}"
1464
+ };
1465
+ </script>
1466
+ <script src="app.js?v={{APPJS_VER}}" defer></script>
1467
+ </body>
1468
+ </html>