@mooncompany/uplink-chat 0.5.2 → 0.32.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/LICENSE +14 -1
  2. package/README.md +105 -3
  3. package/bin/uplink.js +3 -0
  4. package/middleware/error-handler.js +1 -0
  5. package/package.json +89 -5
  6. package/public/css/agents.1fd7567a.css +1499 -0
  7. package/public/css/app.bc7e7484.css +2819 -0
  8. package/public/css/artifacts.css +488 -0
  9. package/public/css/commands.css +77 -0
  10. package/public/css/connection.css +131 -0
  11. package/public/css/cron-panel.css +364 -0
  12. package/public/css/dashboard.css +233 -0
  13. package/public/css/developer.css +342 -0
  14. package/public/css/files.css +123 -0
  15. package/public/css/markdown.css +156 -0
  16. package/public/css/message-actions.css +285 -0
  17. package/public/css/mobile.css +614 -0
  18. package/public/css/panels-unified.css +485 -0
  19. package/public/css/premium.css +415 -0
  20. package/public/css/realtime.css +189 -0
  21. package/public/css/satellites.7fa72088.css +632 -0
  22. package/public/css/settings-redesign.css +1322 -0
  23. package/public/css/shortcuts.css +185 -0
  24. package/public/css/split-view.4bc23474.css +858 -0
  25. package/public/css/themes.css +387 -0
  26. package/public/css/timestamps.css +54 -0
  27. package/public/css/variables.css +81 -0
  28. package/public/dist/bundle.493af136.js +1 -0
  29. package/public/favicon-256.png +0 -0
  30. package/public/icon-192.png +0 -0
  31. package/public/icon-512.png +0 -0
  32. package/public/img/icons/icon-alert-triangle.svg +17 -0
  33. package/public/img/icons/icon-brain.svg +20 -0
  34. package/public/img/icons/icon-brand-python.svg +19 -0
  35. package/public/img/icons/icon-camera.svg +16 -0
  36. package/public/img/icons/icon-chart-bar.svg +18 -0
  37. package/public/img/icons/icon-check.svg +15 -0
  38. package/public/img/icons/icon-circle-check.svg +16 -0
  39. package/public/img/icons/icon-clipboard.svg +16 -0
  40. package/public/img/icons/icon-cloud.svg +15 -0
  41. package/public/img/icons/icon-confetti.svg +24 -0
  42. package/public/img/icons/icon-device-mobile.svg +17 -0
  43. package/public/img/icons/icon-diamond.svg +16 -0
  44. package/public/img/icons/icon-file-text.svg +19 -0
  45. package/public/img/icons/icon-file-type-css.svg +19 -0
  46. package/public/img/icons/icon-file-type-csv.svg +19 -0
  47. package/public/img/icons/icon-file-type-doc.svg +19 -0
  48. package/public/img/icons/icon-file-type-html.svg +23 -0
  49. package/public/img/icons/icon-file-type-js.svg +18 -0
  50. package/public/img/icons/icon-file-type-pdf.svg +20 -0
  51. package/public/img/icons/icon-file-zip.svg +22 -0
  52. package/public/img/icons/icon-file.svg +16 -0
  53. package/public/img/icons/icon-folder.svg +15 -0
  54. package/public/img/icons/icon-headphones.svg +17 -0
  55. package/public/img/icons/icon-hourglass.svg +18 -0
  56. package/public/img/icons/icon-keyboard.svg +22 -0
  57. package/public/img/icons/icon-link.svg +17 -0
  58. package/public/img/icons/icon-lock-open.svg +17 -0
  59. package/public/img/icons/icon-lock.svg +17 -0
  60. package/public/img/icons/icon-mail.svg +16 -0
  61. package/public/img/icons/icon-message-dots.svg +18 -0
  62. package/public/img/icons/icon-microphone-2.svg +16 -0
  63. package/public/img/icons/icon-microphone.svg +18 -0
  64. package/public/img/icons/icon-movie.svg +22 -0
  65. package/public/img/icons/icon-music.svg +18 -0
  66. package/public/img/icons/icon-palette.svg +18 -0
  67. package/public/img/icons/icon-paperclip.svg +15 -0
  68. package/public/img/icons/icon-pencil.svg +16 -0
  69. package/public/img/icons/icon-photo.svg +18 -0
  70. package/public/img/icons/icon-presentation.svg +19 -0
  71. package/public/img/icons/icon-robot.svg +23 -0
  72. package/public/img/icons/icon-rocket.svg +17 -0
  73. package/public/img/icons/icon-satellite.svg +20 -0
  74. package/public/img/icons/icon-settings.svg +16 -0
  75. package/public/img/icons/icon-shield-lock.svg +17 -0
  76. package/public/img/icons/icon-sparkles.svg +15 -0
  77. package/public/img/icons/icon-star-filled.svg +11 -0
  78. package/public/img/icons/icon-tool.svg +15 -0
  79. package/public/img/icons/icon-trash.svg +19 -0
  80. package/public/img/icons/icon-volume.svg +17 -0
  81. package/public/img/icons/icon-world.svg +19 -0
  82. package/public/img/icons/icon-x.svg +16 -0
  83. package/public/img/logo.svg +13 -0
  84. package/public/img/wordmark.svg +35 -0
  85. package/public/index.html +1195 -0
  86. package/public/js/agents-data.js +1 -0
  87. package/public/js/agents-ui.js +1 -0
  88. package/public/js/agents.js +1 -0
  89. package/public/js/app.js +1 -0
  90. package/public/js/appearance-settings.js +1 -0
  91. package/public/js/artifacts.js +1 -0
  92. package/public/js/audio-pcm-processor.js +1 -0
  93. package/public/js/audio-queue.js +1 -0
  94. package/public/js/bootstrap.js +1 -0
  95. package/public/js/chat.js +1 -0
  96. package/public/js/commands.js +1 -0
  97. package/public/js/connection-api.js +1 -0
  98. package/public/js/connection.js +1 -0
  99. package/public/js/context-tracker.js +1 -0
  100. package/public/js/core.js +1 -0
  101. package/public/js/cron-panel.js +1 -0
  102. package/public/js/dashboard.js +1 -0
  103. package/public/js/developer.js +1 -0
  104. package/public/js/encryption.js +1 -0
  105. package/public/js/errors.js +1 -0
  106. package/public/js/event-bus.js +1 -0
  107. package/public/js/fetch-utils.js +1 -0
  108. package/public/js/file-handler.js +1 -0
  109. package/public/js/files.js +1 -0
  110. package/public/js/gateway-chat.js +1 -0
  111. package/public/js/logger.js +1 -0
  112. package/public/js/markdown.js +1 -0
  113. package/public/js/message-actions.js +1 -0
  114. package/public/js/message-renderer.js +1 -0
  115. package/public/js/missed-messages.js +1 -0
  116. package/public/js/mobile-debug.js +1 -0
  117. package/public/js/notifications.js +1 -0
  118. package/public/js/offline-queue.js +1 -0
  119. package/public/js/onboarding.js +1 -0
  120. package/public/js/panels.js +1 -0
  121. package/public/js/premium.js +1 -0
  122. package/public/js/primary-header.js +1 -0
  123. package/public/js/realtime-voice.js +1 -0
  124. package/public/js/satellite-sync.js +1 -0
  125. package/public/js/satellite-ui.js +1 -0
  126. package/public/js/satellites.js +1 -0
  127. package/public/js/settings.js +1 -0
  128. package/public/js/shortcuts.js +1 -0
  129. package/public/js/split-chat.js +1 -0
  130. package/public/js/split-resize.js +1 -0
  131. package/public/js/splitview.js +1 -0
  132. package/public/js/storage.js +1 -0
  133. package/public/js/streaming-handler.js +1 -0
  134. package/public/js/stt-settings.js +1 -0
  135. package/public/js/themes.js +1 -0
  136. package/public/js/timestamps.js +1 -0
  137. package/public/js/tts-settings.js +1 -0
  138. package/public/js/ui.js +1 -0
  139. package/public/js/update-notifier.js +1 -0
  140. package/public/js/utils/constants.js +1 -0
  141. package/public/js/utils/icons.js +1 -0
  142. package/public/js/utils/sanitize.js +1 -0
  143. package/public/js/utils/sse-parser.js +1 -0
  144. package/public/js/vad.js +1 -0
  145. package/public/js/vendor/dompurify.min.js +2 -0
  146. package/public/js/voice-settings-v2.js +1 -0
  147. package/public/js/voice.js +1 -0
  148. package/public/manifest.json +66 -0
  149. package/public/moon_texture.jpg +0 -0
  150. package/public/sw.js +1 -0
  151. package/public/three.min.js +6 -0
  152. package/public/u-icon.png +0 -0
  153. package/server/channel.js +1 -0
  154. package/server/chat.js +1 -0
  155. package/server/config-store.js +1 -0
  156. package/server/config.js +1 -0
  157. package/server/context.js +1 -0
  158. package/server/gateway-api-proxy.js +1 -0
  159. package/server/gateway-commands.js +1 -0
  160. package/server/gateway-proxy.js +1 -0
  161. package/server/index.js +1 -0
  162. package/server/logger.js +1 -0
  163. package/server/message-store.js +1 -0
  164. package/server/middleware/auth.js +1 -0
  165. package/server/middleware.js +1 -0
  166. package/server/openclaw-discover.js +1 -0
  167. package/server/premium/index.js +1 -0
  168. package/server/premium/license.js +1 -0
  169. package/server/realtime/bridge.js +1 -0
  170. package/server/realtime/index.js +1 -0
  171. package/server/realtime/tts-stream.js +1 -0
  172. package/server/routes/agents.js +1 -0
  173. package/server/routes/artifacts.js +1 -0
  174. package/server/routes/chat.js +1 -0
  175. package/server/routes/config-settings.js +1 -0
  176. package/server/routes/config.js +1 -0
  177. package/server/routes/cron.js +1 -0
  178. package/server/routes/files.js +1 -0
  179. package/server/routes/index.js +1 -0
  180. package/server/routes/media.js +1 -0
  181. package/server/routes/missed-messages.js +1 -0
  182. package/server/routes/premium.js +1 -0
  183. package/server/routes/push.js +1 -0
  184. package/server/routes/satellite.js +1 -0
  185. package/server/routes/status.js +1 -0
  186. package/server/routes/stt.js +1 -0
  187. package/server/routes/voice.js +1 -0
  188. package/server/routes/webhooks.js +1 -0
  189. package/server/routes.js +1 -0
  190. package/server/runtime-config.js +1 -0
  191. package/server/share.js +1 -0
  192. package/server/stt/faster-whisper.js +1 -0
  193. package/server/stt/groq.js +1 -0
  194. package/server/stt/index.js +1 -0
  195. package/server/stt/openai.js +1 -0
  196. package/server/sync.js +1 -0
  197. package/server/tailscale-https.js +1 -0
  198. package/server/tts.js +1 -0
  199. package/server/update-checker.js +1 -0
  200. package/server/utils/filename.js +1 -0
  201. package/server/utils.js +1 -0
  202. package/server/watchdog.js +3 -0
  203. package/server/websocket/broadcast.js +1 -0
  204. package/server/websocket/connections.js +1 -0
  205. package/server/websocket/index.js +1 -0
  206. package/server/websocket/routing.js +1 -0
  207. package/server/websocket/sync.js +1 -0
  208. package/server.js +1 -0
  209. package/utils/detect-tool-usage.js +1 -0
  210. package/utils/errors.js +1 -0
  211. package/utils/html-escape.js +1 -0
  212. package/utils/id-sanitize.js +1 -0
  213. package/utils/response.js +1 -0
  214. package/utils/with-retry.js +1 -0
  215. package/index.js +0 -2
@@ -0,0 +1,858 @@
1
+ /**
2
+ * Split-View Dual Chat Layout
3
+ * Desktop-only (≥1024px) side-by-side chat sessions
4
+ * Splits the CHAT AREA inside .app, not the whole layout
5
+ */
6
+
7
+ /* ==========================================================================
8
+ Split View Variables
9
+ ========================================================================== */
10
+
11
+ :root {
12
+ --split-ratio: 0.5; /* Default 50/50 split, updated by drag */
13
+ --split-handle-width: 4px;
14
+ --split-min-width: 320px;
15
+ --split-transition: 300ms cubic-bezier(0.4, 0, 0.2, 1);
16
+ }
17
+
18
+ /* ==========================================================================
19
+ Split Chat Container
20
+ ========================================================================== */
21
+
22
+ /* Default: single column, primary takes all space */
23
+ .split-chat-container {
24
+ display: flex;
25
+ flex-direction: column;
26
+ flex: 1;
27
+ min-height: 0;
28
+ overflow: hidden;
29
+ }
30
+
31
+ /* Desktop: when split-view is active, switch to row layout */
32
+ @media (min-width: 1024px) {
33
+ body.split-view-active .split-chat-container {
34
+ flex-direction: row;
35
+ }
36
+ }
37
+
38
+ /* ==========================================================================
39
+ Primary Chat Pane
40
+ ========================================================================== */
41
+
42
+ .split-pane-primary {
43
+ display: flex;
44
+ flex-direction: column;
45
+ flex: 1;
46
+ min-height: 0;
47
+ min-width: 0;
48
+ overflow: hidden;
49
+ }
50
+
51
+ @media (min-width: 1024px) {
52
+ body.split-view-active .split-pane-primary {
53
+ flex: 0 0 calc(var(--split-ratio) * (100% - var(--split-handle-width) - 1px));
54
+ min-width: var(--split-min-width);
55
+ }
56
+
57
+ /* Only animate on open/close, not during drag */
58
+ body.split-view-active:not(.split-dragging) .split-pane-primary {
59
+ transition: flex var(--split-transition);
60
+ }
61
+ }
62
+
63
+ /* ==========================================================================
64
+ Split View Button (Header)
65
+ ========================================================================== */
66
+
67
+ #splitViewBtn {
68
+ position: relative;
69
+ }
70
+
71
+ /* Glow effect when activated */
72
+ body.split-view-active #splitViewBtn {
73
+ color: var(--accent);
74
+ background: var(--accent-10);
75
+ }
76
+
77
+ body.split-view-active #splitViewBtn::after {
78
+ content: '';
79
+ position: absolute;
80
+ inset: -2px;
81
+ border: 2px solid var(--accent);
82
+ border-radius: var(--radius-md);
83
+ opacity: 0;
84
+ animation: split-btn-glow 1s ease-out;
85
+ }
86
+
87
+ @keyframes split-btn-glow {
88
+ 0% {
89
+ opacity: 1;
90
+ transform: scale(1);
91
+ }
92
+ 100% {
93
+ opacity: 0;
94
+ transform: scale(1.2);
95
+ }
96
+ }
97
+
98
+ /* Mobile: split-view cannot activate */
99
+ @media (max-width: 1023px) {
100
+ .split-pane-secondary,
101
+ .split-drag-handle {
102
+ display: none !important;
103
+ }
104
+
105
+ #splitViewBtn {
106
+ display: none !important;
107
+ }
108
+ }
109
+
110
+ /* ==========================================================================
111
+ Secondary Chat Pane
112
+ ========================================================================== */
113
+
114
+ .split-pane-secondary {
115
+ display: none;
116
+ flex-direction: column;
117
+ background: var(--bg-app, var(--bg));
118
+ overflow: hidden;
119
+ min-width: 0;
120
+ border-left: 1px solid var(--border);
121
+ }
122
+
123
+ @media (min-width: 1024px) {
124
+ body.split-view-active .split-pane-secondary {
125
+ display: flex;
126
+ flex: 0 0 calc((1 - var(--split-ratio)) * (100% - var(--split-handle-width) - 1px));
127
+ min-width: var(--split-min-width);
128
+ animation: split-pane-slide-in var(--split-transition);
129
+ }
130
+ }
131
+
132
+ @keyframes split-pane-slide-in {
133
+ from {
134
+ opacity: 0;
135
+ transform: translateX(20px);
136
+ }
137
+ to {
138
+ opacity: 1;
139
+ transform: translateX(0);
140
+ }
141
+ }
142
+
143
+ /* ==========================================================================
144
+ Primary Pane Header
145
+ Mirrors split-secondary-header for visual consistency
146
+ ========================================================================== */
147
+
148
+ .split-primary-header {
149
+ display: flex;
150
+ align-items: center;
151
+ justify-content: space-between;
152
+ padding: var(--space-3) var(--space-4);
153
+ border-bottom: 1px solid var(--border);
154
+ background: var(--bg-header, var(--bg));
155
+ flex-shrink: 0;
156
+ min-height: 48px;
157
+ }
158
+
159
+ .split-primary-info {
160
+ display: flex;
161
+ align-items: center;
162
+ gap: var(--space-3);
163
+ flex: 1;
164
+ min-width: 0;
165
+ border-radius: var(--radius-md);
166
+ padding: var(--space-1) var(--space-2);
167
+ margin: calc(-1 * var(--space-1)) calc(-1 * var(--space-2));
168
+ transition: background 0.15s ease;
169
+ }
170
+
171
+ .split-primary-info:hover {
172
+ background: var(--accent-10);
173
+ }
174
+
175
+ .split-primary-info:active {
176
+ background: var(--accent-20);
177
+ }
178
+
179
+ .split-primary-icon {
180
+ width: 28px;
181
+ height: 28px;
182
+ border-radius: 50%;
183
+ object-fit: cover;
184
+ flex-shrink: 0;
185
+ }
186
+
187
+ .split-primary-name {
188
+ font-size: var(--text-base);
189
+ font-weight: 600;
190
+ color: var(--text);
191
+ white-space: nowrap;
192
+ overflow: hidden;
193
+ text-overflow: ellipsis;
194
+ }
195
+
196
+ .split-primary-chevron {
197
+ flex-shrink: 0;
198
+ color: var(--text-muted);
199
+ transition: transform 0.2s ease, color 0.15s ease;
200
+ }
201
+
202
+ .split-primary-info:hover .split-primary-chevron {
203
+ color: var(--accent);
204
+ }
205
+
206
+ /* Context badge in primary header — push to right */
207
+ .split-primary-info .context-badge {
208
+ margin-left: auto;
209
+ }
210
+
211
+ /* ==========================================================================
212
+ Secondary Pane Header
213
+ ========================================================================== */
214
+
215
+ .split-secondary-header {
216
+ display: flex;
217
+ align-items: center;
218
+ justify-content: space-between;
219
+ padding: var(--space-3) var(--space-4);
220
+ border-bottom: 1px solid var(--border);
221
+ background: var(--bg-header, var(--bg));
222
+ flex-shrink: 0;
223
+ min-height: 48px;
224
+ }
225
+
226
+ .split-secondary-info {
227
+ display: flex;
228
+ align-items: center;
229
+ gap: var(--space-3);
230
+ flex: 1;
231
+ min-width: 0;
232
+ border-radius: var(--radius-md);
233
+ padding: var(--space-1) var(--space-2);
234
+ margin: calc(-1 * var(--space-1)) calc(-1 * var(--space-2));
235
+ transition: background 0.15s ease;
236
+ cursor: pointer;
237
+ }
238
+
239
+ .split-secondary-info:hover {
240
+ background: var(--accent-10);
241
+ }
242
+
243
+ .split-secondary-info:active {
244
+ background: var(--accent-20);
245
+ }
246
+
247
+ .split-secondary-icon {
248
+ font-size: var(--text-xl);
249
+ flex-shrink: 0;
250
+ width: 28px;
251
+ height: 28px;
252
+ border-radius: 50%;
253
+ object-fit: cover;
254
+ }
255
+
256
+ .split-secondary-name {
257
+ font-size: var(--text-base);
258
+ font-weight: 600;
259
+ color: var(--text);
260
+ white-space: nowrap;
261
+ overflow: hidden;
262
+ text-overflow: ellipsis;
263
+ }
264
+
265
+ .split-context-badge {
266
+ margin-left: auto;
267
+ }
268
+
269
+ .split-secondary-status {
270
+ display: flex;
271
+ align-items: center;
272
+ }
273
+
274
+ .split-secondary-status .status-dot {
275
+ width: 8px;
276
+ height: 8px;
277
+ border-radius: 50%;
278
+ background: var(--neutral);
279
+ animation: pulse 2s ease-in-out infinite;
280
+ }
281
+
282
+ .split-secondary-status .status-dot.connected {
283
+ background: var(--success);
284
+ }
285
+
286
+ .split-secondary-chevron {
287
+ flex-shrink: 0;
288
+ color: var(--text-muted);
289
+ transition: transform 0.2s ease, color 0.15s ease;
290
+ }
291
+
292
+ .split-secondary-info:hover .split-secondary-chevron {
293
+ color: var(--accent);
294
+ }
295
+
296
+ .split-secondary-close {
297
+ display: flex;
298
+ align-items: center;
299
+ justify-content: center;
300
+ width: 32px;
301
+ height: 32px;
302
+ border: none;
303
+ background: transparent;
304
+ color: var(--text-muted);
305
+ cursor: pointer;
306
+ border-radius: var(--radius-md);
307
+ transition: all 0.2s ease;
308
+ flex-shrink: 0;
309
+ font-size: var(--text-2xl);
310
+ line-height: 1;
311
+ }
312
+
313
+ .split-secondary-close:hover {
314
+ background: var(--accent-10);
315
+ color: var(--accent);
316
+ }
317
+
318
+ .split-secondary-close:active {
319
+ transform: scale(0.95);
320
+ }
321
+
322
+ /* ==========================================================================
323
+ Secondary Pane Messages Area
324
+ Match primary .messages styling from app.css
325
+ ========================================================================== */
326
+
327
+ .split-secondary-messages {
328
+ flex: 1;
329
+ overflow-y: auto;
330
+ overflow-x: hidden;
331
+ padding: var(--space-4);
332
+ padding-bottom: 80px;
333
+ display: flex;
334
+ flex-direction: column;
335
+ gap: var(--space-2);
336
+ scroll-behavior: smooth;
337
+ -webkit-overflow-scrolling: touch;
338
+ min-width: 0;
339
+ }
340
+
341
+ /* Push messages to bottom when few */
342
+ .split-secondary-messages .messages-spacer {
343
+ flex: 1;
344
+ }
345
+
346
+ /* Scrollbar — match primary */
347
+ .split-secondary-messages::-webkit-scrollbar { width: 8px; }
348
+ .split-secondary-messages::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.1); }
349
+ .split-secondary-messages::-webkit-scrollbar-thumb {
350
+ background: var(--accent-30);
351
+ border-radius: 4px;
352
+ }
353
+ .split-secondary-messages::-webkit-scrollbar-thumb:hover {
354
+ background: var(--accent-50);
355
+ }
356
+
357
+ /* Message bubbles — match primary .message */
358
+ .split-secondary-messages .message {
359
+ max-width: 85%;
360
+ padding: var(--space-2) var(--space-3);
361
+ border-radius: var(--radius-lg);
362
+ font-size: var(--text-base);
363
+ line-height: 1.6;
364
+ color: var(--text);
365
+ word-wrap: break-word;
366
+ overflow-wrap: break-word;
367
+ min-width: 0;
368
+ animation: message-fade-in 0.3s ease-out;
369
+ }
370
+
371
+ /* Prevent code blocks from blowing out the split pane width */
372
+ .split-secondary-messages .message pre {
373
+ overflow-x: auto;
374
+ max-width: 100%;
375
+ }
376
+
377
+ .split-secondary-messages .message code {
378
+ overflow-wrap: break-word;
379
+ word-break: break-all;
380
+ }
381
+
382
+ .split-secondary-messages .message pre code {
383
+ word-break: normal;
384
+ }
385
+
386
+ .split-secondary-messages .message.assistant {
387
+ align-self: flex-start;
388
+ background: var(--assistant-bubble, transparent);
389
+ }
390
+
391
+ .split-secondary-messages .message.user {
392
+ align-self: flex-end;
393
+ background: var(--user-bubble);
394
+ }
395
+
396
+ /* Empty state */
397
+ .split-secondary-messages:empty::after,
398
+ .split-secondary-messages:has(> .messages-spacer:only-child)::after {
399
+ content: 'Select an agent to start a conversation';
400
+ color: var(--text-muted);
401
+ font-size: var(--text-base);
402
+ text-align: center;
403
+ align-self: center;
404
+ pointer-events: none;
405
+ }
406
+
407
+ /* ==========================================================================
408
+ Secondary Pane Input Area
409
+ Match primary .input-area / .text-field / .send-btn from app.css
410
+ ========================================================================== */
411
+
412
+ .split-secondary-input {
413
+ padding: var(--space-3) var(--space-4);
414
+ background: transparent;
415
+ border-top: 1px solid var(--border);
416
+ flex-shrink: 0;
417
+ }
418
+
419
+ .split-input-row {
420
+ display: flex;
421
+ align-items: flex-end;
422
+ gap: var(--space-2);
423
+ }
424
+
425
+ /* Match primary .text-field — pill shape with own bg/border */
426
+ .split-text-field {
427
+ flex: 1;
428
+ background: var(--bg-input);
429
+ border: 1px solid var(--border);
430
+ border-radius: var(--radius-2xl);
431
+ padding: var(--space-2) var(--space-4);
432
+ color: var(--text);
433
+ font-size: var(--text-base);
434
+ font-family: var(--font-sans);
435
+ outline: none;
436
+ transition: border-color 0.15s;
437
+ resize: none;
438
+ min-height: var(--space-10);
439
+ max-height: 150px;
440
+ overflow-y: auto;
441
+ line-height: 1.4;
442
+ }
443
+
444
+ .split-text-field:focus {
445
+ border-color: var(--accent);
446
+ box-shadow: 0 0 15px var(--accent-20);
447
+ }
448
+
449
+ .split-text-field:focus-visible {
450
+ outline: 2px solid var(--accent);
451
+ outline-offset: 2px;
452
+ }
453
+
454
+ .split-text-field::placeholder {
455
+ color: var(--text-dim);
456
+ }
457
+
458
+ /* Match primary .send-btn — circle with glow */
459
+ .split-send-btn {
460
+ width: 44px;
461
+ height: 44px;
462
+ min-width: 44px;
463
+ min-height: 44px;
464
+ border-radius: var(--radius-full);
465
+ border: none;
466
+ background: var(--accent);
467
+ color: var(--text-inverse, #fff);
468
+ cursor: pointer;
469
+ display: flex;
470
+ align-items: center;
471
+ justify-content: center;
472
+ transition: all 0.15s;
473
+ box-shadow: 0 0 20px var(--accent-30);
474
+ flex-shrink: 0;
475
+ }
476
+
477
+ .split-send-btn:hover {
478
+ background: var(--accent-hover);
479
+ box-shadow: 0 0 30px var(--accent-40);
480
+ }
481
+
482
+ .split-send-btn:active {
483
+ transform: scale(0.95);
484
+ }
485
+
486
+ .split-send-btn:disabled {
487
+ opacity: 0.4;
488
+ cursor: not-allowed;
489
+ transform: none;
490
+ }
491
+
492
+ .split-send-btn svg {
493
+ width: 18px;
494
+ height: 18px;
495
+ }
496
+
497
+ /* ==========================================================================
498
+ Session Picker Overlay
499
+ ========================================================================== */
500
+
501
+ .split-chat-picker {
502
+ position: absolute;
503
+ inset: 0;
504
+ background: var(--bg);
505
+ z-index: 10;
506
+ display: flex;
507
+ flex-direction: column;
508
+ overflow: hidden;
509
+ max-height: 100%;
510
+ }
511
+
512
+ /* Ensure parent constrains the absolute picker */
513
+ .split-pane-primary,
514
+ .split-pane-secondary {
515
+ position: relative;
516
+ }
517
+
518
+ .split-chat-picker-header {
519
+ display: flex;
520
+ align-items: center;
521
+ justify-content: space-between;
522
+ padding: var(--space-3) var(--space-4);
523
+ border-bottom: 1px solid var(--border);
524
+ flex-shrink: 0;
525
+ }
526
+
527
+ .split-chat-picker-title {
528
+ font-size: var(--text-base);
529
+ font-weight: 600;
530
+ color: var(--text);
531
+ text-transform: uppercase;
532
+ letter-spacing: 0.05em;
533
+ }
534
+
535
+ .split-chat-picker-close {
536
+ width: 32px;
537
+ height: 32px;
538
+ display: flex;
539
+ align-items: center;
540
+ justify-content: center;
541
+ background: transparent;
542
+ border: none;
543
+ color: var(--text-muted);
544
+ font-size: var(--text-xl);
545
+ cursor: pointer;
546
+ border-radius: var(--radius-md);
547
+ transition: all 0.15s;
548
+ }
549
+
550
+ .split-chat-picker-close:hover {
551
+ background: var(--accent-10);
552
+ color: var(--accent);
553
+ }
554
+
555
+ .split-chat-picker-list {
556
+ flex: 1;
557
+ min-height: 0;
558
+ overflow-y: auto;
559
+ padding: var(--space-3);
560
+ display: flex;
561
+ flex-direction: column;
562
+ gap: var(--space-2);
563
+ }
564
+
565
+ .split-chat-picker-item {
566
+ display: flex;
567
+ align-items: center;
568
+ gap: var(--space-3);
569
+ padding: var(--space-3) var(--space-4);
570
+ background: var(--bg-input, var(--bg-secondary));
571
+ border: 1px solid var(--border);
572
+ border-radius: var(--radius-lg);
573
+ color: var(--text);
574
+ font-size: var(--text-base);
575
+ cursor: pointer;
576
+ transition: all 0.15s;
577
+ text-align: left;
578
+ width: 100%;
579
+ }
580
+
581
+ .split-chat-picker-item:hover {
582
+ border-color: var(--accent);
583
+ background: var(--accent-10);
584
+ }
585
+
586
+ .split-chat-picker-item.active {
587
+ border-color: var(--accent);
588
+ box-shadow: 0 0 15px var(--accent-20);
589
+ }
590
+
591
+ .split-chat-picker-item.primary {
592
+ opacity: 0.5;
593
+ }
594
+
595
+ .split-chat-picker-avatar {
596
+ width: 32px;
597
+ height: 32px;
598
+ border-radius: 50%;
599
+ object-fit: cover;
600
+ flex-shrink: 0;
601
+ }
602
+
603
+ .split-chat-picker-avatar-fallback {
604
+ width: 32px;
605
+ height: 32px;
606
+ flex-shrink: 0;
607
+ display: flex;
608
+ align-items: center;
609
+ justify-content: center;
610
+ font-size: 20px;
611
+ line-height: 1;
612
+ border-radius: 50%;
613
+ background: var(--accent-10);
614
+ color: var(--text);
615
+ }
616
+
617
+ .split-chat-picker-emoji {
618
+ font-size: var(--text-xl);
619
+ flex-shrink: 0;
620
+ }
621
+
622
+ .split-chat-picker-name {
623
+ flex: 1;
624
+ font-weight: 500;
625
+ }
626
+
627
+ .split-chat-picker-badge {
628
+ font-size: var(--text-xs);
629
+ font-weight: 600;
630
+ padding: 2px 8px;
631
+ border-radius: var(--radius-full);
632
+ background: var(--border);
633
+ color: var(--text-muted);
634
+ text-transform: uppercase;
635
+ letter-spacing: 0.05em;
636
+ }
637
+
638
+ .split-chat-picker-badge.active {
639
+ background: var(--accent-20);
640
+ color: var(--accent);
641
+ }
642
+
643
+ .split-chat-picker-empty {
644
+ text-align: center;
645
+ color: var(--text-muted);
646
+ padding: var(--space-6);
647
+ font-size: var(--text-base);
648
+ }
649
+
650
+ /* ==========================================================================
651
+ Drag Handle
652
+ ========================================================================== */
653
+
654
+ .split-drag-handle {
655
+ display: none;
656
+ position: relative;
657
+ width: var(--split-handle-width);
658
+ background: var(--border);
659
+ cursor: col-resize;
660
+ flex-shrink: 0;
661
+ transition: background 0.2s ease;
662
+ user-select: none;
663
+ }
664
+
665
+ @media (min-width: 1024px) {
666
+ body.split-view-active .split-drag-handle {
667
+ display: block;
668
+ }
669
+
670
+ .split-drag-handle:hover {
671
+ background: var(--accent-30);
672
+ }
673
+
674
+ .split-drag-handle:active,
675
+ .split-drag-handle.dragging {
676
+ background: var(--accent);
677
+ }
678
+
679
+ /* Visual indicator on hover */
680
+ .split-drag-handle::before {
681
+ content: '';
682
+ position: absolute;
683
+ top: 50%;
684
+ left: 50%;
685
+ transform: translate(-50%, -50%);
686
+ width: 20px;
687
+ height: 40px;
688
+ background: var(--accent-20);
689
+ border-radius: var(--radius-full);
690
+ opacity: 0;
691
+ transition: opacity 0.2s ease;
692
+ pointer-events: none;
693
+ }
694
+
695
+ .split-drag-handle:hover::before {
696
+ opacity: 1;
697
+ }
698
+ }
699
+
700
+ /* ==========================================================================
701
+ Accessibility
702
+ ========================================================================== */
703
+
704
+ .split-drag-handle:focus-visible {
705
+ outline: 2px solid var(--accent);
706
+ outline-offset: 2px;
707
+ }
708
+
709
+ /* Reduce motion */
710
+ @media (prefers-reduced-motion: reduce) {
711
+ .split-pane-secondary,
712
+ .split-drag-handle,
713
+ #splitViewBtn::after,
714
+ .split-input-row {
715
+ animation: none !important;
716
+ transition: none !important;
717
+ }
718
+ }
719
+
720
+ /* ==========================================================================
721
+ Loading State
722
+ ========================================================================== */
723
+
724
+ .split-secondary-messages.loading::after {
725
+ content: '';
726
+ display: block;
727
+ width: 40px;
728
+ height: 40px;
729
+ margin: var(--space-4) auto;
730
+ border: 3px solid var(--border);
731
+ border-top-color: var(--accent);
732
+ border-radius: 50%;
733
+ animation: spin 1s linear infinite;
734
+ }
735
+
736
+ @keyframes spin {
737
+ to { transform: rotate(360deg); }
738
+ }
739
+
740
+ /* ==========================================================================
741
+ Override: Primary chat max-width in split mode
742
+ When split is active, primary should use all its allocated space
743
+ ========================================================================== */
744
+
745
+ @media (min-width: 1024px) {
746
+ body.split-view-active .app {
747
+ max-width: none;
748
+ min-width: 0;
749
+ }
750
+
751
+ body.split-view-active .split-chat-container {
752
+ min-width: 0;
753
+ }
754
+
755
+ body.split-view-active .split-pane-primary .messages {
756
+ max-width: none;
757
+ }
758
+ }
759
+
760
+ /* ==========================================================================
761
+ Side Panel Integration in Split-View Mode
762
+ When split-view AND a panel are both open, the side panel should
763
+ flow alongside the chat container instead of overlaying.
764
+ ========================================================================== */
765
+
766
+ @media (min-width: 1024px) {
767
+ /* Override: make side panel flow in layout-wrapper instead of fixed overlay */
768
+ body.split-view-active.panel-open .side-panel {
769
+ position: relative;
770
+ top: auto;
771
+ right: auto;
772
+ width: 25%;
773
+ min-width: 280px;
774
+ max-width: 400px;
775
+ height: 100%;
776
+ z-index: auto;
777
+ flex-shrink: 0;
778
+ }
779
+
780
+ /* App shrinks to make room for the panel in the flow */
781
+ body.split-view-active.panel-open .app {
782
+ max-width: none;
783
+ margin-right: 0;
784
+ flex: 1;
785
+ min-width: 0;
786
+ }
787
+ }
788
+
789
+ /* ==========================================================================
790
+ Artifacts Split Pane
791
+ When artifacts opens as a split pane, it replaces the secondary chat
792
+ and uses the same flex/drag mechanics.
793
+ ========================================================================== */
794
+
795
+ /* Artifacts as a split pane — mirrors .split-pane-secondary layout */
796
+ .split-artifacts-pane {
797
+ display: none;
798
+ flex-direction: column;
799
+ background: var(--bg);
800
+ overflow: hidden;
801
+ min-width: 0;
802
+ border-left: 1px solid var(--border);
803
+ }
804
+
805
+ @media (min-width: 1024px) {
806
+ body.split-artifacts-active .split-artifacts-pane {
807
+ display: flex;
808
+ flex: 0 0 calc((1 - var(--split-ratio)) * (100% - var(--split-handle-width) - 1px));
809
+ min-width: var(--split-min-width);
810
+ animation: split-pane-slide-in var(--split-transition);
811
+ }
812
+
813
+ /* Show drag handle when artifacts split is active */
814
+ body.split-artifacts-active .split-drag-handle {
815
+ display: block;
816
+ }
817
+
818
+ /* Primary pane sizing when artifacts split is active */
819
+ body.split-artifacts-active .split-pane-primary {
820
+ flex: 0 0 calc(var(--split-ratio) * (100% - var(--split-handle-width) - 1px));
821
+ min-width: var(--split-min-width);
822
+ }
823
+
824
+ body.split-artifacts-active:not(.split-dragging) .split-pane-primary {
825
+ transition: flex var(--split-transition);
826
+ }
827
+
828
+ /* App max-width override */
829
+ body.split-artifacts-active .app {
830
+ max-width: none;
831
+ }
832
+
833
+ /* Artifacts panel header shows its own close button in split mode */
834
+ body.split-artifacts-active .split-artifacts-pane .artifacts-panel-header {
835
+ display: flex;
836
+ }
837
+
838
+ /* Override the panel-content to fill the pane */
839
+ body.split-artifacts-active .split-artifacts-pane .panel-content {
840
+ flex: 1;
841
+ overflow-y: auto;
842
+ }
843
+ }
844
+
845
+ /* Artifacts button glow when active (matches split-view button) */
846
+ body.split-artifacts-active #artifactsBtn {
847
+ color: var(--accent);
848
+ background: var(--accent-10);
849
+ }
850
+
851
+ /* Streaming animation for split view messages
852
+ Uses @keyframes streamingRainbow defined in app.css.
853
+ Add border so border-color animation is visible. */
854
+ .split-secondary-messages .message.streaming {
855
+ position: relative;
856
+ border: 1px solid transparent;
857
+ animation: streamingRainbow 4s linear infinite;
858
+ }