pilotswarm-web 0.1.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.
Files changed (57) hide show
  1. package/README.md +144 -0
  2. package/auth/authz/engine.js +139 -0
  3. package/auth/config.js +110 -0
  4. package/auth/index.js +153 -0
  5. package/auth/normalize/entra.js +22 -0
  6. package/auth/providers/entra.js +76 -0
  7. package/auth/providers/none.js +24 -0
  8. package/auth.js +10 -0
  9. package/bin/serve.js +53 -0
  10. package/config.js +20 -0
  11. package/dist/app.js +469 -0
  12. package/dist/assets/index-BSVg-lGb.css +1 -0
  13. package/dist/assets/index-BXD5YP7A.js +24 -0
  14. package/dist/assets/msal-CytV9RFv.js +7 -0
  15. package/dist/assets/pilotswarm-WX3NED6m.js +40 -0
  16. package/dist/assets/react-jg0oazEi.js +1 -0
  17. package/dist/index.html +16 -0
  18. package/node_modules/pilotswarm-ui-core/README.md +6 -0
  19. package/node_modules/pilotswarm-ui-core/package.json +32 -0
  20. package/node_modules/pilotswarm-ui-core/src/commands.js +72 -0
  21. package/node_modules/pilotswarm-ui-core/src/context-usage.js +212 -0
  22. package/node_modules/pilotswarm-ui-core/src/controller.js +3613 -0
  23. package/node_modules/pilotswarm-ui-core/src/formatting.js +872 -0
  24. package/node_modules/pilotswarm-ui-core/src/history.js +571 -0
  25. package/node_modules/pilotswarm-ui-core/src/index.js +13 -0
  26. package/node_modules/pilotswarm-ui-core/src/layout.js +196 -0
  27. package/node_modules/pilotswarm-ui-core/src/reducer.js +1027 -0
  28. package/node_modules/pilotswarm-ui-core/src/selectors.js +2786 -0
  29. package/node_modules/pilotswarm-ui-core/src/session-tree.js +109 -0
  30. package/node_modules/pilotswarm-ui-core/src/state.js +80 -0
  31. package/node_modules/pilotswarm-ui-core/src/store.js +23 -0
  32. package/node_modules/pilotswarm-ui-core/src/system-titles.js +24 -0
  33. package/node_modules/pilotswarm-ui-core/src/themes/catppuccin-mocha.js +56 -0
  34. package/node_modules/pilotswarm-ui-core/src/themes/cobalt2.js +56 -0
  35. package/node_modules/pilotswarm-ui-core/src/themes/dark-high-contrast.js +56 -0
  36. package/node_modules/pilotswarm-ui-core/src/themes/dracula.js +56 -0
  37. package/node_modules/pilotswarm-ui-core/src/themes/github-dark.js +56 -0
  38. package/node_modules/pilotswarm-ui-core/src/themes/gruvbox-dark.js +56 -0
  39. package/node_modules/pilotswarm-ui-core/src/themes/hacker-x-matrix.js +56 -0
  40. package/node_modules/pilotswarm-ui-core/src/themes/hacker-x-orion-prime.js +56 -0
  41. package/node_modules/pilotswarm-ui-core/src/themes/helpers.js +77 -0
  42. package/node_modules/pilotswarm-ui-core/src/themes/index.js +42 -0
  43. package/node_modules/pilotswarm-ui-core/src/themes/noctis-viola.js +56 -0
  44. package/node_modules/pilotswarm-ui-core/src/themes/noctis.js +56 -0
  45. package/node_modules/pilotswarm-ui-core/src/themes/nord.js +56 -0
  46. package/node_modules/pilotswarm-ui-core/src/themes/solarized-dark.js +56 -0
  47. package/node_modules/pilotswarm-ui-core/src/themes/tokyo-night.js +56 -0
  48. package/node_modules/pilotswarm-ui-react/README.md +5 -0
  49. package/node_modules/pilotswarm-ui-react/package.json +36 -0
  50. package/node_modules/pilotswarm-ui-react/src/components.js +1316 -0
  51. package/node_modules/pilotswarm-ui-react/src/index.js +4 -0
  52. package/node_modules/pilotswarm-ui-react/src/platform.js +15 -0
  53. package/node_modules/pilotswarm-ui-react/src/use-controller-state.js +38 -0
  54. package/node_modules/pilotswarm-ui-react/src/web-app.js +2661 -0
  55. package/package.json +64 -0
  56. package/runtime.js +146 -0
  57. package/server.js +311 -0
package/bin/serve.js ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * pilotswarm-web — Starts the Express + WebSocket server and serves the
5
+ * built React portal. In development, use `npm run dev` (Vite) with the
6
+ * server running separately via `node server.js`.
7
+ *
8
+ * Usage:
9
+ * npx pilotswarm-web --env .env.remote
10
+ * npx pilotswarm-web --port 3001
11
+ * npx pilotswarm-web --plugin ./plugin
12
+ * npx pilotswarm-web --workers 4 # embedded workers
13
+ * npx pilotswarm-web --workers 0 # remote workers (AKS)
14
+ */
15
+
16
+ import { fileURLToPath } from "node:url";
17
+ import path from "node:path";
18
+
19
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
+
21
+ // Load env file if --env flag provided
22
+ const envIdx = process.argv.indexOf("--env");
23
+ if (envIdx !== -1 && process.argv[envIdx + 1]) {
24
+ const envPath = path.resolve(process.argv[envIdx + 1]);
25
+ // Node 24+ supports --env-file natively; for programmatic loading we
26
+ // read the file and set process.env entries manually.
27
+ const { readFileSync } = await import("node:fs");
28
+ for (const line of readFileSync(envPath, "utf-8").split("\n")) {
29
+ const trimmed = line.trim();
30
+ if (!trimmed || trimmed.startsWith("#")) continue;
31
+ const eq = trimmed.indexOf("=");
32
+ if (eq === -1) continue;
33
+ const key = trimmed.slice(0, eq).trim();
34
+ const val = trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
35
+ if (!process.env[key]) process.env[key] = val;
36
+ }
37
+ }
38
+
39
+ // Dynamically import the server (after env is loaded)
40
+ const { startServer } = await import("../server.js");
41
+
42
+ const portFlag = process.argv.indexOf("--port");
43
+ const port = portFlag !== -1 ? parseInt(process.argv[portFlag + 1], 10) : 3001;
44
+
45
+ const pluginFlag = process.argv.indexOf("--plugin");
46
+ if (pluginFlag !== -1 && process.argv[pluginFlag + 1]) {
47
+ process.env.PLUGIN_DIRS = path.resolve(process.argv[pluginFlag + 1]);
48
+ }
49
+
50
+ const workersFlag = process.argv.indexOf("--workers");
51
+ const workers = workersFlag !== -1 ? parseInt(process.argv[workersFlag + 1], 10) : 4;
52
+
53
+ startServer({ port, workers });
package/config.js ADDED
@@ -0,0 +1,20 @@
1
+ import { getPluginDirsFromEnv, resolvePortalConfigBundleFromPluginDirs } from "pilotswarm-cli/portal";
2
+
3
+ let cachedPortalBundle = null;
4
+
5
+ function getPortalBundle() {
6
+ if (!cachedPortalBundle) {
7
+ cachedPortalBundle = resolvePortalConfigBundleFromPluginDirs(getPluginDirsFromEnv());
8
+ }
9
+ return cachedPortalBundle;
10
+ }
11
+
12
+ export function getPortalConfig() {
13
+ return getPortalBundle().portalConfig;
14
+ }
15
+
16
+ export function getPortalAssetFile(assetName) {
17
+ const key = String(assetName || "").trim();
18
+ if (!key) return null;
19
+ return getPortalBundle().assetFiles?.[key] || null;
20
+ }
package/dist/app.js ADDED
@@ -0,0 +1,469 @@
1
+ import { Terminal } from "/xterm/lib/xterm.mjs";
2
+ import { FitAddon } from "/xterm-addon-fit/lib/addon-fit.mjs";
3
+ import { WebLinksAddon } from "/xterm-addon-web-links/lib/addon-web-links.mjs";
4
+ import { DEFAULT_THEME_ID, getTheme, listThemes } from "/ui-core/themes/index.js";
5
+
6
+ // ── Auth state ──────────────────────────────────────────────────
7
+ let authEnabled = false;
8
+ let msalInstance = null;
9
+ let currentAccount = null;
10
+ let accessToken = null;
11
+
12
+ const topbarUser = document.getElementById("topbar-user");
13
+ const topbarSignInBtn = document.getElementById("topbar-signin-btn");
14
+ const topbarSignOutBtn = document.getElementById("topbar-signout-btn");
15
+ const signedOutScreen = document.getElementById("signed-out-screen");
16
+
17
+ async function initAuth() {
18
+ const resp = await fetch("/api/auth-config");
19
+ const config = await resp.json();
20
+ if (!config.enabled) {
21
+ // No auth configured — proceed directly
22
+ authEnabled = false;
23
+ updateAuthUI();
24
+ return true;
25
+ }
26
+
27
+ authEnabled = true;
28
+ msalInstance = new msal.PublicClientApplication({
29
+ auth: {
30
+ clientId: config.clientId,
31
+ authority: config.authority,
32
+ redirectUri: config.redirectUri,
33
+ },
34
+ cache: {
35
+ cacheLocation: "sessionStorage",
36
+ storeAuthStateInCookie: true,
37
+ },
38
+ });
39
+
40
+ await msalInstance.initialize();
41
+
42
+ // Handle redirect callback (if returning from redirect flow)
43
+ const redirectResp = await msalInstance.handleRedirectPromise();
44
+ if (redirectResp) {
45
+ currentAccount = redirectResp.account;
46
+ } else {
47
+ const accounts = msalInstance.getAllAccounts();
48
+ if (accounts.length > 0) currentAccount = accounts[0];
49
+ }
50
+
51
+ if (currentAccount) {
52
+ await acquireToken();
53
+ }
54
+
55
+ updateAuthUI();
56
+ return !!currentAccount;
57
+ }
58
+
59
+ async function acquireToken() {
60
+ if (!msalInstance || !currentAccount) return null;
61
+ try {
62
+ const resp = await msalInstance.acquireTokenSilent({
63
+ scopes: [`${msalInstance.getConfiguration().auth.clientId}/.default`],
64
+ account: currentAccount,
65
+ });
66
+ accessToken = resp.accessToken || resp.idToken;
67
+ return accessToken;
68
+ } catch {
69
+ try {
70
+ const resp = await msalInstance.acquireTokenPopup({
71
+ scopes: [`${msalInstance.getConfiguration().auth.clientId}/.default`],
72
+ account: currentAccount,
73
+ });
74
+ accessToken = resp.accessToken || resp.idToken;
75
+ return accessToken;
76
+ } catch (err) {
77
+ console.error("[auth] Token acquisition failed:", err);
78
+ return null;
79
+ }
80
+ }
81
+ }
82
+
83
+ async function signIn() {
84
+ if (!msalInstance) return;
85
+ try {
86
+ // Mobile browsers block popups — use redirect flow instead
87
+ if (/Mobi|Android/i.test(navigator.userAgent)) {
88
+ await msalInstance.loginRedirect({ scopes: ["User.Read"] });
89
+ return; // page will redirect
90
+ }
91
+ const resp = await msalInstance.loginPopup({
92
+ scopes: ["User.Read"],
93
+ });
94
+ currentAccount = resp.account;
95
+ await acquireToken();
96
+ updateAuthUI();
97
+ connectWebSocket();
98
+ } catch (err) {
99
+ console.error("[auth] Sign-in failed:", err);
100
+ }
101
+ }
102
+
103
+ function signOut() {
104
+ if (!msalInstance) return;
105
+ msalInstance.logoutPopup({ account: currentAccount });
106
+ currentAccount = null;
107
+ accessToken = null;
108
+ if (ws && ws.readyState === WebSocket.OPEN) ws.close();
109
+ ws = null;
110
+ updateAuthUI();
111
+ }
112
+
113
+ function updateAuthUI() {
114
+ if (!authEnabled) {
115
+ // Auth not configured — hide all auth UI, show terminal
116
+ topbarSignInBtn.classList.add("hidden");
117
+ topbarSignOutBtn.classList.add("hidden");
118
+ topbarUser.textContent = "";
119
+ signedOutScreen.classList.add("hidden");
120
+ return;
121
+ }
122
+
123
+ const signedIn = !!currentAccount;
124
+ topbarSignInBtn.classList.toggle("hidden", signedIn);
125
+ topbarSignOutBtn.classList.toggle("hidden", !signedIn);
126
+ if (signedIn) {
127
+ const name = currentAccount.name || currentAccount.username || "";
128
+ const email = currentAccount.username || currentAccount.idTokenClaims?.preferred_username || "";
129
+ topbarUser.textContent = email && email !== name ? `${name} (${email})` : name;
130
+ } else {
131
+ topbarUser.textContent = "";
132
+ }
133
+ signedOutScreen.classList.toggle("hidden", signedIn);
134
+
135
+ // Hide overlay + terminal when signed out, show signed-out screen
136
+ if (!signedIn) {
137
+ overlayEl.classList.add("hidden");
138
+ container.style.visibility = "hidden";
139
+ } else {
140
+ container.style.visibility = "visible";
141
+ }
142
+ }
143
+
144
+ // Expose for onclick handlers in HTML
145
+ window.__portalSignIn = signIn;
146
+ window.__portalSignOut = signOut;
147
+
148
+ const THEME_STORAGE_KEY = "pilotswarm.portal.theme";
149
+ const FONT_FAMILY = "ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace";
150
+ const THEMES = listThemes();
151
+
152
+ function readStoredThemeId() {
153
+ try {
154
+ return window.localStorage.getItem(THEME_STORAGE_KEY);
155
+ } catch {
156
+ return null;
157
+ }
158
+ }
159
+
160
+ function writeStoredThemeId(themeId) {
161
+ try {
162
+ window.localStorage.setItem(THEME_STORAGE_KEY, themeId);
163
+ } catch {}
164
+ }
165
+
166
+ function resolveInitialTheme() {
167
+ const storedThemeId = readStoredThemeId();
168
+ return getTheme(storedThemeId) || getTheme(DEFAULT_THEME_ID) || THEMES[0];
169
+ }
170
+
171
+ function setThemeCssVariable(name, value) {
172
+ document.documentElement.style.setProperty(name, value);
173
+ }
174
+
175
+ function applyDocumentTheme(theme) {
176
+ const page = theme.page;
177
+ setThemeCssVariable("--app-background", page.background);
178
+ setThemeCssVariable("--app-foreground", page.foreground);
179
+ setThemeCssVariable("--overlay-background", page.overlayBackground);
180
+ setThemeCssVariable("--overlay-foreground", page.overlayForeground);
181
+ setThemeCssVariable("--hint-color", page.hintColor);
182
+ setThemeCssVariable("--modal-backdrop", page.modalBackdrop);
183
+ setThemeCssVariable("--modal-background", page.modalBackground);
184
+ setThemeCssVariable("--modal-border", page.modalBorder);
185
+ setThemeCssVariable("--modal-foreground", page.modalForeground);
186
+ setThemeCssVariable("--modal-muted", page.modalMuted);
187
+ setThemeCssVariable("--modal-selected-background", page.modalSelectedBackground);
188
+ setThemeCssVariable("--modal-selected-border", page.modalSelectedBorder);
189
+ setThemeCssVariable("--modal-selected-foreground", page.modalSelectedForeground);
190
+ }
191
+
192
+ const overlayEl = document.getElementById("overlay");
193
+ const dotsEl = document.getElementById("dots");
194
+ const modalBackdropEl = document.getElementById("theme-modal-backdrop");
195
+ const modalOptionsEl = document.getElementById("theme-options");
196
+ const modalTitleEl = document.getElementById("theme-modal-title");
197
+
198
+ let currentTheme = resolveInitialTheme();
199
+ let themeModalOpen = false;
200
+ let modalSelectedThemeId = currentTheme.id;
201
+
202
+ applyDocumentTheme(currentTheme);
203
+
204
+ const term = new Terminal({
205
+ cursorBlink: true,
206
+ cursorStyle: "block",
207
+ fontFamily: FONT_FAMILY,
208
+ fontSize: 14,
209
+ lineHeight: 1.1,
210
+ macOptionIsMeta: true,
211
+ allowProposedApi: true,
212
+ theme: { ...currentTheme.terminal },
213
+ });
214
+
215
+ const fitAddon = new FitAddon();
216
+ term.loadAddon(fitAddon);
217
+ term.loadAddon(new WebLinksAddon());
218
+
219
+ const container = document.getElementById("terminal");
220
+ term.open(container);
221
+
222
+ // ── WebSocket (created after auth) ──────────────────────────────
223
+ let ws = null;
224
+
225
+ function connectWebSocket() {
226
+ if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return;
227
+
228
+ const proto = location.protocol === "https:" ? "wss:" : "ws:";
229
+ const url = `${proto}//${location.host}/ws`;
230
+
231
+ // Pass token via sub-protocol if auth is enabled
232
+ if (authEnabled && accessToken) {
233
+ ws = new WebSocket(url, ["access_token", accessToken]);
234
+ } else {
235
+ ws = new WebSocket(url);
236
+ }
237
+
238
+ ws.onopen = () => {
239
+ overlayEl.classList.add("hidden");
240
+ // Re-fit now that layout is fully resolved, then send accurate dimensions
241
+ fitAddon.fit();
242
+ ws.send(JSON.stringify({ type: "resize", cols: term.cols, rows: term.rows }));
243
+ };
244
+
245
+ ws.onmessage = (event) => {
246
+ try {
247
+ const message = JSON.parse(event.data);
248
+ if (message.type === "output") {
249
+ term.write(message.data);
250
+ } else if (message.type === "exit") {
251
+ term.write("\r\n\x1b[90m[Session ended]\x1b[0m\r\n");
252
+ }
253
+ } catch {}
254
+ };
255
+
256
+ ws.onclose = (ev) => {
257
+ if (ev.code === 4401) {
258
+ // Auth rejected — show signed-out state
259
+ currentAccount = null;
260
+ accessToken = null;
261
+ updateAuthUI();
262
+ return;
263
+ }
264
+ term.write("\r\n\x1b[90m[Disconnected - reload to reconnect]\x1b[0m\r\n");
265
+ };
266
+ }
267
+
268
+ term.onData((data) => {
269
+ if (themeModalOpen) return;
270
+ if (ws && ws.readyState === WebSocket.OPEN) {
271
+ ws.send(JSON.stringify({ type: "input", data }));
272
+ }
273
+ });
274
+
275
+ term.onBinary((data) => {
276
+ if (themeModalOpen) return;
277
+ if (ws && ws.readyState === WebSocket.OPEN) {
278
+ ws.send(JSON.stringify({ type: "input", data }));
279
+ }
280
+ });
281
+
282
+ window.addEventListener("resize", () => {
283
+ fitAddon.fit();
284
+ });
285
+
286
+ term.onResize(({ cols, rows }) => {
287
+ if (ws && ws.readyState === WebSocket.OPEN) {
288
+ ws.send(JSON.stringify({ type: "resize", cols, rows }));
289
+ }
290
+ });
291
+
292
+ let dotCount = 0;
293
+ const dotInterval = window.setInterval(() => {
294
+ dotCount = (dotCount + 1) % 4;
295
+ dotsEl.textContent = ".".repeat(dotCount || 1);
296
+ if (ws && ws.readyState === WebSocket.OPEN) {
297
+ window.clearInterval(dotInterval);
298
+ }
299
+ }, 400);
300
+
301
+ // ── Boot: auth then connect ─────────────────────────────────────
302
+ (async () => {
303
+ const signedIn = await initAuth();
304
+ if (!authEnabled || signedIn) {
305
+ // Wait for layout to settle before fitting + connecting
306
+ requestAnimationFrame(() => {
307
+ fitAddon.fit();
308
+ term.focus();
309
+ connectWebSocket();
310
+ });
311
+ }
312
+ })();
313
+
314
+ function renderThemeOptions() {
315
+ modalOptionsEl.replaceChildren();
316
+
317
+ for (const theme of THEMES) {
318
+ const optionEl = document.createElement("button");
319
+ optionEl.type = "button";
320
+ optionEl.className = "theme-option";
321
+ if (theme.id === modalSelectedThemeId) optionEl.classList.add("is-selected");
322
+
323
+ const titleRowEl = document.createElement("div");
324
+ titleRowEl.className = "theme-option-title-row";
325
+
326
+ const titleEl = document.createElement("span");
327
+ titleEl.className = "theme-option-title";
328
+ titleEl.textContent = theme.label;
329
+ titleRowEl.appendChild(titleEl);
330
+
331
+ if (theme.id === currentTheme.id) {
332
+ const currentEl = document.createElement("span");
333
+ currentEl.className = "theme-option-current";
334
+ currentEl.textContent = "Current";
335
+ titleRowEl.appendChild(currentEl);
336
+ }
337
+
338
+ const descriptionEl = document.createElement("div");
339
+ descriptionEl.className = "theme-option-description";
340
+ descriptionEl.textContent = theme.description;
341
+
342
+ const swatchesEl = document.createElement("div");
343
+ swatchesEl.className = "theme-option-swatches";
344
+ for (const color of [
345
+ theme.terminal.background,
346
+ theme.terminal.blue,
347
+ theme.terminal.green,
348
+ theme.terminal.magenta,
349
+ theme.terminal.yellow,
350
+ ]) {
351
+ const swatchEl = document.createElement("span");
352
+ swatchEl.className = "theme-swatch";
353
+ swatchEl.style.backgroundColor = color;
354
+ swatchesEl.appendChild(swatchEl);
355
+ }
356
+
357
+ optionEl.append(titleRowEl, descriptionEl, swatchesEl);
358
+ optionEl.addEventListener("click", () => {
359
+ modalSelectedThemeId = theme.id;
360
+ renderThemeOptions();
361
+ applySelectedTheme();
362
+ });
363
+ modalOptionsEl.appendChild(optionEl);
364
+ }
365
+ }
366
+
367
+ function moveThemeSelection(delta) {
368
+ const currentIndex = THEMES.findIndex((theme) => theme.id === modalSelectedThemeId);
369
+ const safeIndex = currentIndex >= 0 ? currentIndex : 0;
370
+ const nextIndex = (safeIndex + delta + THEMES.length) % THEMES.length;
371
+ modalSelectedThemeId = THEMES[nextIndex].id;
372
+ renderThemeOptions();
373
+ // Scroll selected option into view
374
+ const selected = modalOptionsEl.querySelector(".is-selected");
375
+ if (selected) selected.scrollIntoView({ block: "nearest", behavior: "smooth" });
376
+ }
377
+
378
+ function applyTheme(themeId, { persist = true } = {}) {
379
+ const nextTheme = getTheme(themeId);
380
+ if (!nextTheme) return;
381
+ currentTheme = nextTheme;
382
+ modalSelectedThemeId = nextTheme.id;
383
+ applyDocumentTheme(nextTheme);
384
+ term.options.theme = { ...nextTheme.terminal };
385
+ if (typeof term.clearTextureAtlas === "function") term.clearTextureAtlas();
386
+ term.refresh(0, term.rows - 1);
387
+ if (persist) writeStoredThemeId(nextTheme.id);
388
+ renderThemeOptions();
389
+ if (ws && ws.readyState === WebSocket.OPEN) {
390
+ ws.send(JSON.stringify({ type: "theme", themeId: nextTheme.id }));
391
+ }
392
+ }
393
+
394
+ function openThemeModal() {
395
+ themeModalOpen = true;
396
+ modalSelectedThemeId = currentTheme.id;
397
+ modalTitleEl.textContent = "Theme Picker";
398
+ renderThemeOptions();
399
+ modalBackdropEl.classList.remove("hidden");
400
+ modalBackdropEl.setAttribute("aria-hidden", "false");
401
+ }
402
+
403
+ function closeThemeModal() {
404
+ themeModalOpen = false;
405
+ modalBackdropEl.classList.add("hidden");
406
+ modalBackdropEl.setAttribute("aria-hidden", "true");
407
+ term.focus();
408
+ }
409
+
410
+ function applySelectedTheme() {
411
+ applyTheme(modalSelectedThemeId);
412
+ closeThemeModal();
413
+ }
414
+
415
+ modalBackdropEl.addEventListener("click", (event) => {
416
+ if (event.target === modalBackdropEl) {
417
+ closeThemeModal();
418
+ }
419
+ });
420
+
421
+ window.addEventListener("keydown", (event) => {
422
+ const toggleThemeModal = event.shiftKey
423
+ && !event.ctrlKey
424
+ && !event.metaKey
425
+ && !event.altKey
426
+ && event.code === "KeyT";
427
+
428
+ if (toggleThemeModal) {
429
+ event.preventDefault();
430
+ event.stopPropagation();
431
+ if (themeModalOpen) {
432
+ closeThemeModal();
433
+ } else {
434
+ openThemeModal();
435
+ }
436
+ return;
437
+ }
438
+
439
+ if (!themeModalOpen) return;
440
+
441
+ event.preventDefault();
442
+ event.stopPropagation();
443
+
444
+ if (event.key === "Escape") {
445
+ closeThemeModal();
446
+ return;
447
+ }
448
+ if (event.key === "Enter") {
449
+ applySelectedTheme();
450
+ return;
451
+ }
452
+ if (event.key === "ArrowUp" || event.key === "k" || event.key === "K") {
453
+ moveThemeSelection(-1);
454
+ return;
455
+ }
456
+ if (event.key === "ArrowDown" || event.key === "j" || event.key === "J") {
457
+ moveThemeSelection(1);
458
+ return;
459
+ }
460
+ if (event.key === "Home" || event.key === "g") {
461
+ modalSelectedThemeId = THEMES[0].id;
462
+ renderThemeOptions();
463
+ return;
464
+ }
465
+ if (event.key === "End" || event.key === "G") {
466
+ modalSelectedThemeId = THEMES[THEMES.length - 1].id;
467
+ renderThemeOptions();
468
+ }
469
+ }, true);
@@ -0,0 +1 @@
1
+ :root{--ps-page-background: #0d1117;--ps-page-foreground: #f0f6fc;--ps-surface: #0d1117;--ps-background: #0d1117;--ps-foreground: #f0f6fc;--ps-muted: #8b949e;--ps-border: #6e7681;--ps-selection-background: #58a6ff;--ps-selection-foreground: #0d1117;--ps-highlight-background: #1f6feb;--ps-highlight-foreground: #ffffff;--ps-modal-backdrop: rgba(13, 17, 23, .72);--ps-modal-background: #161b22;--ps-modal-border: #30363d;--ps-modal-foreground: #f0f6fc;--ps-modal-muted: #8b949e;--ps-modal-selected-background: rgba(31, 111, 235, .16);--ps-modal-selected-border: #58a6ff;--ps-modal-selected-foreground: #ffffff;--ps-font-size-base: 13px;--ps-font-size-dense: 12px;--ps-line-height-base: 1.28;--ps-line-height-dense: 1.18;--ps-radius-sm: 6px;--ps-radius-md: 8px;--ps-scrollbar-size: 10px;--ps-scrollbar-track: rgba(110, 118, 129, .14);--ps-scrollbar-thumb: color-mix(in srgb, var(--ps-border) 56%, var(--ps-surface));--ps-scrollbar-thumb-hover: color-mix(in srgb, var(--ps-modal-selected-border) 42%, var(--ps-border))}*{box-sizing:border-box;scrollbar-width:thin;scrollbar-color:var(--ps-scrollbar-thumb) var(--ps-scrollbar-track)}html,body,#root{margin:0;min-height:100%;height:100%;background:var(--ps-page-background);color:var(--ps-page-foreground);font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;font-size:var(--ps-font-size-base);line-height:var(--ps-line-height-base)}body{overflow:hidden;-webkit-text-size-adjust:100%}button,input,textarea{font:inherit}button{cursor:pointer}.portal-app-shell,.portal-gate{min-height:var(--ps-app-height, 100%);height:var(--ps-app-height, 100%);display:flex;flex-direction:column;background:radial-gradient(circle at top left,color-mix(in srgb,var(--ps-highlight-background) 18%,transparent),transparent 42%),radial-gradient(circle at top right,color-mix(in srgb,var(--ps-modal-selected-border) 16%,transparent),transparent 38%),var(--ps-page-background)}.portal-gate{align-items:center;justify-content:center;padding:24px}.portal-gate-card{width:min(480px,100%);border:1px solid var(--ps-modal-border);border-radius:var(--ps-radius-md);background:color-mix(in srgb,var(--ps-modal-background) 92%,transparent);box-shadow:0 28px 80px #00000052;padding:28px;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.portal-gate-brand{display:flex;flex-direction:column;align-items:flex-start;gap:14px;margin-bottom:18px}.portal-gate-kicker,.portal-header-kicker{color:var(--ps-muted);font-size:12px;letter-spacing:.08em;text-transform:uppercase}.portal-gate-title{margin:10px 0 14px;font-size:clamp(28px,5vw,42px);line-height:1.05}.portal-gate-copy{margin:0;color:var(--ps-muted);line-height:1.6}.portal-primary-button,.portal-secondary-button,.ps-toolbar-button,.ps-mini-button,.ps-send-button,.ps-modal-button,.ps-mobile-nav-button,.ps-tab,.ps-filter-option,.ps-list-button,.ps-modal-close{border:1px solid var(--ps-modal-border);border-radius:var(--ps-radius-sm);background:color-mix(in srgb,var(--ps-surface) 94%,transparent);color:var(--ps-foreground);transition:background .12s ease,border-color .12s ease,transform .12s ease}.portal-primary-button,.ps-send-button,.ps-modal-button.is-primary{background:var(--ps-highlight-background);border-color:var(--ps-highlight-background);color:var(--ps-highlight-foreground)}.portal-primary-button,.portal-secondary-button{padding:10px 14px;margin-top:20px}.portal-secondary-button{margin-top:0;flex:0 0 auto}.portal-header{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:12px;padding:10px 14px;border-bottom:1px solid color-mix(in srgb,var(--ps-border) 50%,transparent);background:color-mix(in srgb,var(--ps-page-background) 86%,transparent);-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px)}.portal-header-brand{display:flex;align-items:center;gap:12px;min-width:0}.portal-header-brand-copy{display:flex;flex-direction:column;gap:3px;min-width:0}.portal-logo-frame{width:38px;height:38px;border-radius:var(--ps-radius-sm);border:1px solid color-mix(in srgb,var(--ps-modal-selected-border) 52%,var(--ps-modal-border));background:radial-gradient(circle at 28% 24%,color-mix(in srgb,var(--ps-modal-selected-border) 20%,transparent),transparent 58%),linear-gradient(135deg,color-mix(in srgb,var(--ps-highlight-background) 20%,var(--ps-surface)),color-mix(in srgb,var(--ps-background) 92%,transparent));box-shadow:inset 0 1px color-mix(in srgb,white 10%,transparent),0 10px 24px #0000002e;display:grid;place-items:center;flex:0 0 auto;overflow:hidden}.portal-logo-frame.is-large{width:76px;height:76px;border-radius:16px}.portal-logo-frame.has-image{background:radial-gradient(circle at 28% 24%,color-mix(in srgb,white 10%,transparent),transparent 58%),linear-gradient(135deg,color-mix(in srgb,var(--ps-surface) 94%,white 6%),color-mix(in srgb,var(--ps-background) 90%,transparent))}.portal-logo{width:24px;height:24px}.portal-logo-frame.is-large .portal-logo{width:44px;height:44px}.portal-logo-image{width:74%;height:74%;object-fit:contain;display:block}.portal-logo-frame.is-large .portal-logo-image{width:82%;height:82%}.portal-logo-ring,.portal-logo-link{stroke:color-mix(in srgb,var(--ps-modal-selected-border) 82%,var(--ps-page-foreground));stroke-linecap:round;stroke-linejoin:round}.portal-logo-ring{stroke-width:3;opacity:.46}.portal-logo-link{stroke-width:2.5;opacity:.9}.portal-logo-core{fill:var(--ps-highlight-background)}.portal-logo-node{stroke:color-mix(in srgb,var(--ps-page-background) 70%,transparent);stroke-width:1.5}.portal-logo-node-a,.portal-logo-node-d{fill:#7ee787}.portal-logo-node-b,.portal-logo-node-e{fill:#58a6ff}.portal-logo-node-c,.portal-logo-node-f{fill:#d2a8ff}.portal-header-user{align-self:start}.portal-header-identity,.portal-header-identity-stack{min-width:0}.portal-header-identity-stack{display:flex;flex-direction:column;align-items:flex-start;gap:1px;text-align:left}.portal-header-name,.portal-header-email,.portal-header-identity{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.portal-header-name{color:var(--ps-foreground);font-size:.9rem;font-weight:700;line-height:1.05}.portal-header-email{color:var(--ps-muted);font-size:.78rem;line-height:1.05}.portal-header-identity{color:var(--ps-foreground);font-size:.84rem}.portal-header-identity.is-muted{color:var(--ps-muted)}.portal-main{flex:1;min-height:0;padding:10px;overflow:hidden}.ps-web-shell{min-height:0;height:100%;display:flex;flex-direction:column;gap:8px;overflow:hidden}.ps-toolbar{display:flex;align-items:center;justify-content:space-between;gap:6px}.ps-toolbar-actions{display:flex;flex-wrap:wrap;gap:6px;min-width:0}.ps-toolbar-status{flex:0 1 auto;color:var(--ps-muted);font-size:10px;line-height:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ps-toolbar-button,.ps-mini-button,.ps-modal-button,.ps-mobile-nav-button,.ps-tab,.ps-filter-option,.ps-modal-close{padding:6px 10px;font-size:.92rem}.ps-workspace{flex:1;min-height:0;overflow:hidden}.ps-workspace>*{min-height:0}.ps-workspace-full{min-height:0;height:100%;display:flex}.ps-workspace-full>.ps-panel{flex:1;min-height:0}.ps-workspace-grid{display:grid;gap:8px;min-height:0;height:100%;overflow:hidden}.ps-workspace-column{display:grid;gap:8px;min-height:0;overflow:hidden}.ps-chat-focus-shell{min-height:0;height:100%;display:grid;grid-template-rows:auto minmax(0,1fr);gap:8px}.ps-chat-focus-rail{display:flex;align-items:center;gap:6px;padding:4px 6px;border:1px solid color-mix(in srgb,var(--ps-border) 42%,transparent);border-radius:var(--ps-radius-sm);background:color-mix(in srgb,var(--ps-surface) 94%,transparent)}.ps-chat-focus-button{flex:0 0 auto}.ps-chat-focus-status{margin-left:auto;color:var(--ps-muted);font-size:.72rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ps-chat-focus-body{position:relative;min-height:0;display:flex}.ps-chat-focus-body>.ps-panel{flex:1;min-height:0;min-width:0}.ps-chat-focus-body .ps-panel-body,.ps-chat-focus-body .ps-scroll-panel{min-width:0}.ps-chat-focus-body .ps-line,.ps-chat-focus-body .ps-chat-card-line{overflow-wrap:anywhere}.ps-chat-focus-overlay{position:absolute;top:10px;bottom:10px;width:min(34rem,calc(100% - 20px));max-width:min(34rem,42%);z-index:6;pointer-events:none}.ps-chat-focus-overlay.is-left{left:10px}.ps-chat-focus-overlay.is-right{right:10px}.ps-chat-focus-overlay>.ps-panel{height:100%;pointer-events:auto;box-shadow:0 20px 48px #00000057,inset 0 0 0 1px color-mix(in srgb,var(--ps-page-background) 35%,transparent)}.ps-mobile-workspace{min-height:0;height:100%;display:grid;grid-template-rows:auto minmax(0,1fr);gap:8px}.ps-mobile-session-pane{max-height:min(18vh,124px)}.ps-mobile-session-pane .ps-action-list{min-height:0}.ps-mobile-session-collapsed .ps-panel-body{flex:0 0 auto}.ps-mobile-session-summary{padding:0 10px 10px;color:var(--ps-muted);font-size:var(--ps-font-size-dense);line-height:var(--ps-line-height-dense)}.ps-mobile-chat-pane{min-height:0;display:flex;flex-direction:column;overflow:hidden}.ps-mobile-chat-pane>.ps-panel{flex:1;min-height:0}.ps-mobile-pane-fill{min-height:0;height:100%;display:flex;flex-direction:column;overflow:hidden}.ps-mobile-pane-fill>.ps-panel{flex:1;min-height:0}.ps-column-resizer{align-self:stretch;width:16px;min-height:0;padding:0;border:0;border-radius:999px;background:transparent;display:grid;place-items:center;cursor:col-resize;position:relative}.ps-row-resizer{justify-self:stretch;height:16px;min-width:0;padding:0;border:0;border-radius:999px;background:transparent;display:grid;place-items:center;cursor:row-resize;position:relative}.ps-column-resizer:before{content:"";position:absolute;top:10px;bottom:10px;left:50%;width:1px;transform:translate(-50%);background:color-mix(in srgb,var(--ps-border) 52%,transparent)}.ps-row-resizer:before{content:"";position:absolute;left:10px;right:10px;top:50%;height:1px;transform:translateY(-50%);background:color-mix(in srgb,var(--ps-border) 52%,transparent)}.ps-column-resizer-handle{position:relative;z-index:1;display:grid;gap:4px;padding:10px 4px;border-radius:999px;border:1px solid color-mix(in srgb,var(--ps-modal-border) 78%,transparent);background:color-mix(in srgb,var(--ps-surface) 94%,transparent);box-shadow:0 6px 20px #0000002e}.ps-row-resizer-handle{position:relative;z-index:1;display:flex;gap:4px;padding:4px 10px;border-radius:999px;border:1px solid color-mix(in srgb,var(--ps-modal-border) 78%,transparent);background:color-mix(in srgb,var(--ps-surface) 94%,transparent);box-shadow:0 6px 20px #0000002e}.ps-column-resizer-dot,.ps-row-resizer-dot{width:3px;height:3px;border-radius:999px;background:color-mix(in srgb,var(--ps-modal-selected-border) 84%,var(--ps-page-foreground))}.ps-column-resizer:hover .ps-column-resizer-handle,.ps-column-resizer.is-dragging .ps-column-resizer-handle,.ps-column-resizer:focus-visible .ps-column-resizer-handle{border-color:color-mix(in srgb,var(--ps-modal-selected-border) 72%,var(--ps-modal-border));background:color-mix(in srgb,var(--ps-modal-selected-background) 72%,var(--ps-surface))}.ps-row-resizer:hover .ps-row-resizer-handle,.ps-row-resizer.is-dragging .ps-row-resizer-handle,.ps-row-resizer:focus-visible .ps-row-resizer-handle{border-color:color-mix(in srgb,var(--ps-modal-selected-border) 72%,var(--ps-modal-border));background:color-mix(in srgb,var(--ps-modal-selected-background) 72%,var(--ps-surface))}.ps-column-resizer:focus-visible{outline:none}.ps-row-resizer:focus-visible{outline:none}body.is-resizing-pane-x,body.is-resizing-pane-x *{cursor:col-resize!important;-webkit-user-select:none!important;user-select:none!important}body.is-resizing-pane-y,body.is-resizing-pane-y *{cursor:row-resize!important;-webkit-user-select:none!important;user-select:none!important}.ps-panel{position:relative;min-height:0;display:flex;flex-direction:column;overflow:hidden;border:2px solid var(--ps-panel-accent, var(--ps-border));border-radius:var(--ps-radius-sm);background:color-mix(in srgb,var(--ps-surface) 96%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--ps-page-background) 35%,transparent)}.ps-panel.is-focused{box-shadow:0 0 0 1px color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 40%,transparent),inset 0 0 0 1px color-mix(in srgb,var(--ps-page-background) 35%,transparent)}.ps-panel-header{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:6px 10px 0;margin-bottom:0}.ps-panel-title{display:inline-flex;align-items:center;gap:4px;padding:0;background:transparent;font-size:.94rem;font-weight:700;line-height:1.08}.ps-panel-actions{display:inline-flex;gap:6px;padding-left:0;background:transparent}.ps-panel-body,.ps-scroll-panel{flex:1;min-height:0}.ps-panel-body{display:flex;flex-direction:column;min-height:0}.ps-scroll-panel{overflow:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;touch-action:pan-x pan-y;scrollbar-gutter:stable both-edges;padding:6px 10px 10px;font-size:var(--ps-font-size-dense);line-height:var(--ps-line-height-dense)}.ps-panel-sticky{flex:0 0 auto;padding:6px 10px 0;border-bottom:1px solid color-mix(in srgb,var(--ps-border) 35%,transparent);font-size:var(--ps-font-size-dense);line-height:var(--ps-line-height-dense)}.ps-panel-sticky.is-scroll-sync{overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;-webkit-overflow-scrolling:touch;touch-action:pan-x;scrollbar-gutter:stable both-edges}.ps-line{min-height:1.16rem;white-space:pre-wrap;line-height:1.16rem;word-break:break-word}.ps-panel.has-preserved-sticky .ps-panel-sticky .ps-line,.ps-scroll-panel.is-preserve .ps-line{white-space:pre;word-break:normal;overflow-wrap:normal}.ps-scroll-panel.is-preserve .ps-line{min-width:max-content}.ps-scroll-panel.is-wrapped .ps-line,.ps-modal-details .ps-line,.ps-filter-column .ps-line{white-space:pre-wrap;word-break:break-word}.ps-chat-card{margin:8px auto 8px 0;border:1px solid color-mix(in srgb,var(--ps-chat-card-accent, var(--ps-border)) 78%,transparent);border-radius:var(--ps-radius-sm);background:color-mix(in srgb,var(--ps-surface) 98%,transparent);overflow:hidden;width:fit-content;max-width:min(100%,82ch)}.ps-chat-card-header{padding:8px 12px 6px;border-bottom:1px solid color-mix(in srgb,var(--ps-chat-card-accent, var(--ps-border)) 28%,transparent);background:color-mix(in srgb,var(--ps-chat-card-accent, var(--ps-surface)) 10%,transparent)}.ps-chat-card-body{padding:8px 12px 10px}.ps-chat-card-line{white-space:pre-wrap;word-break:break-word}.ps-chat-card-line+.ps-chat-card-line{margin-top:6px}.ps-chat-panel .ps-line,.ps-chat-panel .ps-chat-card-line{min-height:1.24rem;line-height:1.24rem}.ps-system-notice{margin:10px 0 12px;color:var(--ps-muted)}.ps-system-notice[open]{margin-bottom:14px}.ps-system-notice-summary{cursor:pointer;list-style:none;-webkit-user-select:none;user-select:none;opacity:.86;transition:opacity .12s ease}.ps-system-notice-summary:hover,.ps-system-notice[open] .ps-system-notice-summary{opacity:1}.ps-system-notice-summary::-webkit-details-marker{display:none}.ps-system-notice-summary-text{display:inline-block;white-space:pre-wrap}.ps-system-notice-summary:before{content:"▸ ";color:color-mix(in srgb,var(--ps-muted) 84%,transparent)}.ps-system-notice[open] .ps-system-notice-summary:before{content:"▾ "}.ps-system-notice-body{margin-top:8px;padding-left:12px;border-left:1px solid color-mix(in srgb,var(--ps-border) 42%,transparent);color:var(--ps-muted)}.ps-system-notice-body .ps-markdown-preview{gap:10px}.ps-system-notice-body .ps-md-paragraph,.ps-system-notice-body .ps-md-list,.ps-system-notice-body .ps-md-quote{color:var(--ps-muted)}.ps-chat-table-wrap{margin:8px auto 8px 0;border:1px solid color-mix(in srgb,var(--ps-border) 55%,transparent);border-radius:var(--ps-radius-sm);overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;-webkit-overflow-scrolling:touch;touch-action:pan-x;width:fit-content;max-width:100%}.ps-chat-table{width:max-content;border-collapse:collapse;table-layout:auto}.ps-chat-table th,.ps-chat-table td{padding:6px 8px;border-right:1px solid color-mix(in srgb,var(--ps-border) 34%,transparent);border-bottom:1px solid color-mix(in srgb,var(--ps-border) 34%,transparent);white-space:pre-wrap;word-break:break-word;vertical-align:top}.ps-chat-table th:last-child,.ps-chat-table td:last-child{border-right:0}.ps-chat-table tbody tr:last-child td{border-bottom:0}.ps-chat-table th{text-align:left;font-weight:700;color:var(--ps-foreground);background:color-mix(in srgb,var(--ps-highlight-background) 10%,var(--ps-surface))}.ps-chat-code-block{margin:8px auto 8px 0;width:min(100%,120ch);max-width:min(100%,120ch)}.ps-action-list{display:flex;flex-direction:column;gap:4px;padding:6px 10px 10px;overflow:auto;-webkit-overflow-scrolling:touch;touch-action:pan-y;scrollbar-gutter:stable both-edges;font-size:var(--ps-font-size-dense);line-height:var(--ps-line-height-dense)}.ps-list-button{width:100%;text-align:left;padding:7px 9px}.ps-action-list .ps-line{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ps-session-pane .ps-panel-body{padding:0 8px 8px}.ps-session-pane .ps-session-list{gap:1px;padding:2px 0 6px}.ps-session-pane .ps-session-list-button{border:0;border-radius:2px;background:transparent;box-shadow:none;color:inherit;padding:3px 8px;min-height:1.55rem}.ps-session-pane .ps-session-list-button:hover{transform:none;border-color:transparent;background:color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 7%,transparent)}.ps-session-pane .ps-session-list-button.is-selected{border-color:transparent;background:color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 15%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 80%,transparent),inset 3px 0 color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 90%,transparent);color:inherit}.ps-session-pane .ps-session-list-button:focus-visible{outline:none;box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 80%,transparent),inset 3px 0 color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 90%,transparent),0 0 0 1px color-mix(in srgb,var(--ps-panel-accent, var(--ps-border)) 35%,transparent)}.ps-session-row-content{display:block}.ps-list-button.is-selected,.ps-toolbar-button.is-active,.ps-mini-button.is-active,.ps-tab.is-active,.ps-mobile-nav-button.is-active,.ps-filter-option.is-selected{border-color:var(--ps-modal-selected-border);background:var(--ps-modal-selected-background);color:var(--ps-modal-selected-foreground)}.ps-empty-state{color:var(--ps-muted);padding:6px 0}.ps-tab-row{display:flex;flex-wrap:wrap;gap:5px;padding:4px 10px 6px;align-items:center}.ps-files-grid{flex:1;min-height:0;display:grid;grid-template-rows:minmax(180px,.9fr) minmax(220px,1.3fr);gap:8px;padding:0 10px 10px}.ps-scroll-panel.is-preview,.ps-markdown-scroll{padding-top:10px}.ps-markdown-preview{display:flex;flex-direction:column;gap:14px;padding-right:4px;line-height:1.55}.ps-md-heading{margin:0;font-weight:800;line-height:1.2;color:var(--ps-foreground)}.ps-md-heading.is-h1{font-size:1.45rem}.ps-md-heading.is-h2{font-size:1.28rem}.ps-md-heading.is-h3{font-size:1.12rem}.ps-md-heading.is-h4,.ps-md-heading.is-h5,.ps-md-heading.is-h6{font-size:1rem}.ps-md-paragraph,.ps-md-quote,.ps-md-list{margin:0}.ps-md-list{padding-left:22px;display:flex;flex-direction:column;gap:6px}.ps-md-list-item{line-height:1.5}.ps-md-quote{margin:0;padding:10px 14px;border-left:3px solid color-mix(in srgb,var(--ps-modal-selected-border) 78%,transparent);background:color-mix(in srgb,var(--ps-highlight-background) 8%,transparent);color:color-mix(in srgb,var(--ps-foreground) 88%,var(--ps-muted))}.ps-md-inline-code{padding:1px 6px;border-radius:999px;background:color-mix(in srgb,var(--ps-highlight-background) 12%,var(--ps-surface));border:1px solid color-mix(in srgb,var(--ps-border) 35%,transparent);font-size:.94em}.ps-md-code-block{border:1px solid color-mix(in srgb,var(--ps-border) 45%,transparent);border-radius:var(--ps-radius-sm);overflow:hidden;background:color-mix(in srgb,var(--ps-background) 96%,black 4%)}.ps-md-code-header{padding:7px 12px;font-size:.82rem;text-transform:uppercase;letter-spacing:.08em;color:var(--ps-muted);border-bottom:1px solid color-mix(in srgb,var(--ps-border) 32%,transparent);background:color-mix(in srgb,var(--ps-surface) 92%,transparent)}.ps-md-code-pre{margin:0;padding:14px 16px;overflow:auto;overscroll-behavior-x:contain;-webkit-overflow-scrolling:touch;touch-action:pan-x;line-height:1.55;font-size:.95rem;white-space:pre}.ps-md-code-pre code{font:inherit;color:#9fe3ff}.ps-md-table-wrap{overflow-x:auto;overscroll-behavior-x:contain;-webkit-overflow-scrolling:touch;touch-action:pan-x;border:1px solid color-mix(in srgb,var(--ps-border) 40%,transparent);border-radius:var(--ps-radius-sm)}.ps-md-table{width:100%;border-collapse:collapse}.ps-md-table th,.ps-md-table td{padding:9px 12px;border-bottom:1px solid color-mix(in srgb,var(--ps-border) 25%,transparent);text-align:left;vertical-align:top}.ps-md-table th{background:color-mix(in srgb,var(--ps-highlight-background) 10%,var(--ps-surface));font-weight:700}.ps-md-table tbody tr:last-child td{border-bottom:0}.ps-md-link{text-decoration:underline;text-underline-offset:2px}.ps-chat-panel .ps-panel-header{padding-bottom:8px}.ps-chat-panel .ps-panel-body{padding-top:2px}.ps-chat-panel .ps-scroll-panel{padding-top:6px}.ps-status-strip{display:flex;justify-content:space-between;gap:10px;padding:0 2px;color:var(--ps-muted);font-size:9px}.ps-status-left,.ps-status-right{min-width:0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ps-footer-shell{display:flex;flex-direction:column;gap:4px;padding:6px 8px 8px;border:1px solid color-mix(in srgb,var(--ps-border) 45%,transparent);border-radius:var(--ps-radius-sm);background:color-mix(in srgb,var(--ps-surface) 94%,transparent)}.ps-prompt-shell{display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:8px;align-items:center;padding:0}.ps-prompt-shell.is-mobile{grid-template-columns:auto minmax(0,1fr) auto;gap:8px}.ps-hidden-file-input{display:none}.ps-prompt-label{padding:0 4px 0 2px;color:var(--ps-muted);text-transform:uppercase;font-size:10px;letter-spacing:.08em;align-self:center}.ps-prompt-shell.is-mobile .ps-prompt-label{padding:0 2px 0 0}.ps-prompt-input,.ps-modal-input{width:100%;min-height:40px;resize:vertical;border:1px solid color-mix(in srgb,var(--ps-border) 60%,transparent);border-radius:var(--ps-radius-sm);background:color-mix(in srgb,var(--ps-background) 94%,transparent);color:var(--ps-foreground);padding:8px 10px;outline:none;font-size:var(--ps-font-size-dense);line-height:var(--ps-line-height-dense)}.ps-prompt-shell.is-mobile .ps-prompt-input{font-size:16px;line-height:1.25}.ps-prompt-input:focus,.ps-modal-input:focus{border-color:var(--ps-modal-selected-border);box-shadow:0 0 0 3px color-mix(in srgb,var(--ps-modal-selected-border) 20%,transparent)}.ps-prompt-shell.is-drag-over .ps-prompt-input{border-color:var(--ps-modal-selected-border);background:color-mix(in srgb,var(--ps-modal-selected-background) 26%,var(--ps-background));box-shadow:0 0 0 3px color-mix(in srgb,var(--ps-modal-selected-border) 18%,transparent)}.ps-send-button{min-height:40px;padding:0 12px}.ps-send-button.is-inline{min-height:40px;min-width:40px;padding:0;border-radius:var(--ps-radius-sm);font-size:18px;line-height:1;display:inline-flex;align-items:center;justify-content:center}.ps-mobile-nav{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}.ps-modal-backdrop{position:fixed;inset:0;background:var(--ps-modal-backdrop);display:flex;align-items:center;justify-content:center;padding:16px;z-index:50}.ps-compose-backdrop{align-items:flex-start;padding-top:max(18px,env(safe-area-inset-top))}.ps-compose-card{width:min(980px,100%);border:1px solid var(--ps-modal-border);border-radius:var(--ps-radius-md);background:var(--ps-modal-background);color:var(--ps-modal-foreground);padding:14px;box-shadow:0 30px 80px #0006;display:flex;flex-direction:column;gap:10px}.ps-compose-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.ps-modal{width:min(900px,100%);max-height:min(86vh,900px);overflow:auto;-webkit-overflow-scrolling:touch;touch-action:pan-y;scrollbar-gutter:stable both-edges;border:1px solid var(--ps-modal-border);border-radius:var(--ps-radius-md);background:var(--ps-modal-background);color:var(--ps-modal-foreground);padding:18px;box-shadow:0 30px 80px #0006}.ps-scroll-panel::-webkit-scrollbar,.ps-action-list::-webkit-scrollbar,.ps-panel-sticky.is-scroll-sync::-webkit-scrollbar,.ps-modal::-webkit-scrollbar{width:var(--ps-scrollbar-size);height:var(--ps-scrollbar-size)}.ps-scroll-panel::-webkit-scrollbar-track,.ps-action-list::-webkit-scrollbar-track,.ps-panel-sticky.is-scroll-sync::-webkit-scrollbar-track,.ps-modal::-webkit-scrollbar-track{background:transparent}.ps-scroll-panel::-webkit-scrollbar-thumb,.ps-action-list::-webkit-scrollbar-thumb,.ps-panel-sticky.is-scroll-sync::-webkit-scrollbar-thumb,.ps-modal::-webkit-scrollbar-thumb{background:var(--ps-scrollbar-thumb);border:2px solid transparent;border-radius:999px;background-clip:padding-box;min-height:28px}.ps-scroll-panel::-webkit-scrollbar-thumb:hover,.ps-action-list::-webkit-scrollbar-thumb:hover,.ps-panel-sticky.is-scroll-sync::-webkit-scrollbar-thumb:hover,.ps-modal::-webkit-scrollbar-thumb:hover{background:var(--ps-scrollbar-thumb-hover);border:2px solid transparent;background-clip:padding-box}.ps-scroll-panel::-webkit-scrollbar-corner,.ps-action-list::-webkit-scrollbar-corner,.ps-panel-sticky.is-scroll-sync::-webkit-scrollbar-corner,.ps-modal::-webkit-scrollbar-corner{background:transparent}.ps-modal.is-narrow{width:min(560px,100%)}.ps-modal.is-wide{width:min(980px,100%)}.ps-modal-header,.ps-modal-footer{display:flex;align-items:center;justify-content:space-between;gap:12px}.ps-modal-title,.ps-modal-details-title,.ps-filter-title{font-size:16px;font-weight:700}.ps-modal-grid{display:grid;grid-template-columns:minmax(280px,1fr) minmax(260px,.9fr);gap:16px;margin:16px 0}.ps-modal-list,.ps-modal-details{display:flex;flex-direction:column;gap:10px}.ps-keybinding-modal{width:min(860px,100%)}.ps-keybinding-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:18px;margin:18px 0}.ps-keybinding-section{display:flex;flex-direction:column;gap:10px}.ps-keybinding-title{margin:0;font-size:14px;font-weight:700;color:var(--ps-page-foreground)}.ps-keybinding-list{display:flex;flex-direction:column;gap:10px}.ps-keybinding-row{display:grid;grid-template-columns:minmax(110px,auto) minmax(0,1fr);align-items:start;gap:12px}.ps-keybinding-kbd{display:inline-flex;align-items:center;justify-content:center;min-height:28px;padding:4px 10px;border-radius:8px;border:1px solid color-mix(in srgb,var(--ps-modal-selected-border) 45%,var(--ps-modal-border));background:color-mix(in srgb,var(--ps-modal-selected-background) 30%,var(--ps-surface));color:var(--ps-page-foreground);font:inherit;font-weight:700}.ps-keybinding-description{color:var(--ps-muted);line-height:1.45}.ps-filter-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;margin:18px 0}.ps-filter-column{display:flex;flex-direction:column;gap:10px}.ps-modal-close{color:var(--ps-muted)}.portal-primary-button:hover,.portal-secondary-button:hover,.ps-toolbar-button:hover,.ps-mini-button:hover,.ps-send-button:hover,.ps-modal-button:hover,.ps-mobile-nav-button:hover,.ps-tab:hover,.ps-filter-option:hover,.ps-list-button:hover,.ps-modal-close:hover,.ps-column-resizer:hover{transform:translateY(-1px);border-color:color-mix(in srgb,var(--ps-modal-selected-border) 70%,var(--ps-modal-border))}@media(max-width:920px){:root{--ps-font-size-base: 13px;--ps-font-size-dense: 12px}.portal-main{padding:8px}.portal-header{grid-template-columns:minmax(0,1fr) auto;align-items:start;padding:8px 10px}.portal-header-user{align-self:start}.ps-toolbar{align-items:center}.ps-toolbar-status{max-width:34%;font-size:9px}.ps-chat-focus-rail{flex-wrap:wrap}.ps-chat-focus-status{width:100%;margin-left:0}.ps-chat-focus-overlay{left:8px;right:8px;width:auto;max-width:none}.ps-chat-focus-shell .ps-chat-table th,.ps-chat-focus-shell .ps-chat-table td{white-space:nowrap;word-break:normal;overflow-wrap:normal}.ps-footer-shell{padding:4px 6px 6px}.ps-status-right{display:none}.ps-status-strip{justify-content:flex-start}.ps-modal-grid{grid-template-columns:1fr}.ps-files-grid{grid-template-rows:minmax(160px,1fr) minmax(260px,1.4fr)}.ps-column-resizer{display:none}.ps-mobile-session-pane{max-height:min(16vh,108px)}.ps-mobile-pane-fill>.ps-panel .ps-scroll-panel,.ps-mobile-pane-fill>.ps-panel .ps-panel-body{flex:1}}