lvwerra HF Staff commited on
Commit
a699cda
·
verified ·
1 Parent(s): 2d4a54b

Restyle: stroke icon set, mono voice, agent brand-color tints, live brand dot, group rails, motion, terminal empty states, tighter radii

Browse files
web/src/App.tsx CHANGED
@@ -133,6 +133,21 @@ export default function App() {
133
  else setActiveRef(null);
134
  };
135
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  const allowDrop = (e: React.DragEvent) => { e.preventDefault(); setDropMain(true); };
137
  const onDropMain = (e: React.DragEvent) => {
138
  e.preventDefault();
@@ -225,10 +240,9 @@ export default function App() {
225
  onDrop={onDropMain}
226
  >
227
  <div className="empty-card">
228
- <h2>{activeGroup.name}</h2>
229
- <p>Create an agent here, or drag one in from the sidebar.</p>
230
  <NewSession clis={clis} sessions={tree.sessions} defaultPath={lastPath} onCreate={newSessionHere} />
231
- <div className="dropline">drop an agent to add it to this group</div>
232
  </div>
233
  </div>
234
  ) : renderTiles(groupSessions)
@@ -236,10 +250,10 @@ export default function App() {
236
  renderTiles([activeSingle])
237
  ) : (
238
  <div className="empty-group">
239
- <div className="empty-card">
240
- <h2>Welcome to Agent Manager</h2>
241
- <p>Add an agent or a group from the sidebar. Drag an agent onto another to group them.</p>
242
- </div>
243
  </div>
244
  )}
245
  </div>
 
133
  else setActiveRef(null);
134
  };
135
 
136
+ const promptUser = info?.spaceId?.split('/')[0] || 'you';
137
+ // Empty states speak the app's native language: a prompt, waiting.
138
+ const EmptyPrompt = ({ path, hints }: { path: string; hints: string[] }) => (
139
+ <div className="empty-term">
140
+ <div className="et-prompt mono">
141
+ <span className="et-user">{promptUser}</span>
142
+ <span className="et-dim">/</span>
143
+ <span className="et-path">{path}</span>
144
+ <span className="et-dim"> $</span>
145
+ <span className="et-cursor" />
146
+ </div>
147
+ {hints.map((h) => <p key={h} className="et-hint">{h}</p>)}
148
+ </div>
149
+ );
150
+
151
  const allowDrop = (e: React.DragEvent) => { e.preventDefault(); setDropMain(true); };
152
  const onDropMain = (e: React.DragEvent) => {
153
  e.preventDefault();
 
240
  onDrop={onDropMain}
241
  >
242
  <div className="empty-card">
243
+ <EmptyPrompt path={activeGroup.name} hints={['create an agent here, or drag one in from the sidebar']} />
 
244
  <NewSession clis={clis} sessions={tree.sessions} defaultPath={lastPath} onCreate={newSessionHere} />
245
+ <div className="dropline">drop an agent here to add it to this group</div>
246
  </div>
247
  </div>
248
  ) : renderTiles(groupSessions)
 
250
  renderTiles([activeSingle])
251
  ) : (
252
  <div className="empty-group">
253
+ <EmptyPrompt
254
+ path="workspaces"
255
+ hints={['create an agent or a group from the sidebar', 'drag an agent onto another to group them']}
256
+ />
257
  </div>
258
  )}
259
  </div>
web/src/components/FilesPane.tsx CHANGED
@@ -3,7 +3,7 @@ import type { Session } from '../types';
3
  import * as api from '../api';
4
  import type { FileEntry } from '../api';
5
  import Logo from './Logo';
6
- import { FolderGlyph, FileGlyph } from './icons';
7
 
8
  const fmtSize = (n: number) => {
9
  if (n < 1024) return `${n} B`;
@@ -139,15 +139,15 @@ export default function FilesPane({
139
  <div className={`slot${focused ? ' focused' : ''}`} onMouseDown={onFocus}>
140
  <div className="pane-head">
141
  <div className="ph-left">
142
- <Logo cli="files" size={20} />
143
  <span className="status idle" />
144
  </div>
145
  <span className="ph-title">{session.name}</span>
146
- <button className="mini-btn ph-close" title="Close" onClick={(e) => { e.stopPropagation(); onClose(); }}></button>
147
  </div>
148
 
149
  <div className="files-bar">
150
- <button className="mini-btn" title="Up" disabled={!root} onClick={up}></button>
151
  <div className="crumbs">
152
  {crumbs.map((c, i) => (
153
  <span key={c || 'root'}>
@@ -158,7 +158,7 @@ export default function FilesPane({
158
  </div>
159
  <span className="spacer" />
160
  <label className="mini-btn upload-btn" title="Upload files">
161
- Upload
162
  <input type="file" multiple hidden onChange={(e) => { if (e.target.files) upload(e.target.files); e.target.value = ''; }} />
163
  </label>
164
  </div>
 
3
  import * as api from '../api';
4
  import type { FileEntry } from '../api';
5
  import Logo from './Logo';
6
+ import { FolderGlyph, FileGlyph, CloseGlyph, UpGlyph, UploadGlyph } from './icons';
7
 
8
  const fmtSize = (n: number) => {
9
  if (n < 1024) return `${n} B`;
 
139
  <div className={`slot${focused ? ' focused' : ''}`} onMouseDown={onFocus}>
140
  <div className="pane-head">
141
  <div className="ph-left">
142
+ <Logo cli="files" size={16} tint="#d99a2b" />
143
  <span className="status idle" />
144
  </div>
145
  <span className="ph-title">{session.name}</span>
146
+ <button className="mini-btn ph-close" title="Close" onClick={(e) => { e.stopPropagation(); onClose(); }}><CloseGlyph /></button>
147
  </div>
148
 
149
  <div className="files-bar">
150
+ <button className="mini-btn" title="Up" disabled={!root} onClick={up}><UpGlyph /></button>
151
  <div className="crumbs">
152
  {crumbs.map((c, i) => (
153
  <span key={c || 'root'}>
 
158
  </div>
159
  <span className="spacer" />
160
  <label className="mini-btn upload-btn" title="Upload files">
161
+ <UploadGlyph /> Upload
162
  <input type="file" multiple hidden onChange={(e) => { if (e.target.files) upload(e.target.files); e.target.value = ''; }} />
163
  </label>
164
  </div>
web/src/components/Locked.tsx CHANGED
@@ -1,4 +1,5 @@
1
  import { useState } from 'react';
 
2
 
3
  // Shown when the server reports the Space is public. The terminal backend is
4
  // disabled server-side; this explains how to run a private copy or relock.
@@ -31,7 +32,7 @@ api.duplicate_repo(
31
  return (
32
  <div className="locked">
33
  <div className="locked-card">
34
- <div className="locked-emoji">🔒</div>
35
  <h1>Agent Manager</h1>
36
  <p className="locked-lead">
37
  This Space is <b>public</b>, so the terminal manager is disabled. It has
 
1
  import { useState } from 'react';
2
+ import { LockGlyph } from './icons';
3
 
4
  // Shown when the server reports the Space is public. The terminal backend is
5
  // disabled server-side; this explains how to run a private copy or relock.
 
32
  return (
33
  <div className="locked">
34
  <div className="locked-card">
35
+ <div className="locked-emoji"><LockGlyph /></div>
36
  <h1>Agent Manager</h1>
37
  <p className="locked-lead">
38
  This Space is <b>public</b>, so the terminal manager is disabled. It has
web/src/components/Logo.tsx CHANGED
@@ -4,18 +4,25 @@ import { FolderGlyph } from './icons';
4
  const INVERT_DARK = new Set(['shell', 'opencode']);
5
  const INVERT_LIGHT = new Set(['hermes']);
6
 
7
- export default function Logo({ cli, size = 18 }: { cli: string; size?: number }) {
 
 
 
 
 
 
 
8
  // Files uses the same minimal folder glyph as the file tree.
9
  if (cli === 'files') {
10
  return (
11
- <span className="cli-logo files-glyph" style={{ width: size, height: size }}>
12
  <FolderGlyph />
13
  </span>
14
  );
15
  }
16
  const cls = INVERT_DARK.has(cli) ? ' inv-dark' : INVERT_LIGHT.has(cli) ? ' inv-light' : '';
17
  return (
18
- <span className={`cli-logo${cls}`} style={{ width: size, height: size }}>
19
  <img
20
  src={`/logos/${cli}.png`}
21
  alt={cli}
 
4
  const INVERT_DARK = new Set(['shell', 'opencode']);
5
  const INVERT_LIGHT = new Set(['hermes']);
6
 
7
+ export default function Logo({ cli, size = 18, tint }: { cli: string; size?: number; tint?: string }) {
8
+ // `tint` (the CLI's brand color) wraps the glyph in a softly tinted tile so
9
+ // rows/panes read as "Claude / Codex / Gemini" at a glance. `size` stays the
10
+ // glyph size; the tile adds its own padding around it.
11
+ const tile = tint
12
+ ? { padding: Math.max(3, Math.round(size * 0.18)), background: `color-mix(in srgb, ${tint} 13%, transparent)`, borderRadius: Math.max(4, Math.round(size * 0.28)) }
13
+ : undefined;
14
+
15
  // Files uses the same minimal folder glyph as the file tree.
16
  if (cli === 'files') {
17
  return (
18
+ <span className="cli-logo files-glyph" style={{ width: size, height: size, boxSizing: 'content-box', ...tile }}>
19
  <FolderGlyph />
20
  </span>
21
  );
22
  }
23
  const cls = INVERT_DARK.has(cli) ? ' inv-dark' : INVERT_LIGHT.has(cli) ? ' inv-light' : '';
24
  return (
25
+ <span className={`cli-logo${cls}`} style={{ width: size, height: size, boxSizing: 'content-box', ...tile }}>
26
  <img
27
  src={`/logos/${cli}.png`}
28
  alt={cli}
web/src/components/SettingsView.tsx CHANGED
@@ -3,6 +3,7 @@ import type { Cli } from '../types';
3
  import * as api from '../api';
4
  import SkillsEditor from './SkillsEditor';
5
  import UsagePanel from './UsagePanel';
 
6
 
7
  type Page = 'general' | 'usage' | 'skills';
8
  const PAGES: { id: Page; label: string }[] = [
@@ -68,7 +69,7 @@ export default function SettingsView({
68
 
69
  <div className="setting-row">
70
  <div><div className="s-label">Theme</div><div className="s-help">Defaults to your system setting.</div></div>
71
- <button className="btn-ghost" onClick={onToggleTheme}>{theme === 'dark' ? '☾ Dark' : '☀ Light'}</button>
72
  </div>
73
 
74
  <h3>Agents</h3>
@@ -101,7 +102,7 @@ export default function SettingsView({
101
  </div>
102
  </div>
103
  {!info?.canRelaunch ? (
104
- <button className="btn-ghost" disabled title="Add a write-scoped HF_TOKEN secret to the Space to enable"> Relaunch &amp; update</button>
105
  ) : relaunch.confirm ? (
106
  <span className="confirm-del">
107
  <span className="s-muted">Rebuild now?</span>
@@ -109,7 +110,7 @@ export default function SettingsView({
109
  <button className="btn-ghost" onClick={() => setRelaunch({})}>Cancel</button>
110
  </span>
111
  ) : (
112
- <button className="btn-ghost" onClick={() => setRelaunch({ confirm: true })}> Relaunch &amp; update</button>
113
  )}
114
  </div>
115
  {relaunch.msg && <div className="s-help" style={{ marginTop: 6 }}>{relaunch.msg}</div>}
 
3
  import * as api from '../api';
4
  import SkillsEditor from './SkillsEditor';
5
  import UsagePanel from './UsagePanel';
6
+ import { SunGlyph, MoonGlyph, RefreshGlyph } from './icons';
7
 
8
  type Page = 'general' | 'usage' | 'skills';
9
  const PAGES: { id: Page; label: string }[] = [
 
69
 
70
  <div className="setting-row">
71
  <div><div className="s-label">Theme</div><div className="s-help">Defaults to your system setting.</div></div>
72
+ <button className="btn-ghost" onClick={onToggleTheme}>{theme === 'dark' ? <><MoonGlyph /> Dark</> : <><SunGlyph /> Light</>}</button>
73
  </div>
74
 
75
  <h3>Agents</h3>
 
102
  </div>
103
  </div>
104
  {!info?.canRelaunch ? (
105
+ <button className="btn-ghost" disabled title="Add a write-scoped HF_TOKEN secret to the Space to enable"><RefreshGlyph /> Relaunch &amp; update</button>
106
  ) : relaunch.confirm ? (
107
  <span className="confirm-del">
108
  <span className="s-muted">Rebuild now?</span>
 
110
  <button className="btn-ghost" onClick={() => setRelaunch({})}>Cancel</button>
111
  </span>
112
  ) : (
113
+ <button className="btn-ghost" onClick={() => setRelaunch({ confirm: true })}><RefreshGlyph /> Relaunch &amp; update</button>
114
  )}
115
  </div>
116
  {relaunch.msg && <div className="s-help" style={{ marginTop: 6 }}>{relaunch.msg}</div>}
web/src/components/Sidebar.tsx CHANGED
@@ -4,6 +4,7 @@ import { STATE_LABEL } from '../types';
4
  import Logo from './Logo';
5
  import NewSession from './NewSession';
6
  import FolderPicker from './FolderPicker';
 
7
 
8
  type Zone = 'before' | 'after' | 'on';
9
 
@@ -43,6 +44,10 @@ export default function Sidebar({
43
 
44
  const sessById = useMemo(() => Object.fromEntries(tree.sessions.map((s) => [s.id, s])), [tree.sessions]);
45
  const groupById = useMemo(() => Object.fromEntries(tree.groups.map((g) => [g.id, g])), [tree.groups]);
 
 
 
 
46
 
47
  const clearDrag = () => { setDragRef(null); setDrop(null); };
48
  const bump = (id: string, d: number) => setCart((c) => ({ ...c, [id]: Math.max(0, (c[id] || 0) + d) }));
@@ -123,10 +128,10 @@ export default function Sidebar({
123
  ) : (
124
  <span className="name">{s.name}</span>
125
  )}
126
- <Logo cli={s.cli} size={13} />
127
  <span className="row-actions">
128
- {s.running && <button className="mini-btn" title="Stop" onClick={(e) => { e.stopPropagation(); onStopSession(s.id); }}></button>}
129
- <button className="mini-btn" title="Delete" onClick={(e) => { e.stopPropagation(); onDeleteSession(s.id); }}>🗑</button>
130
  </span>
131
  </div>
132
  );
@@ -160,8 +165,8 @@ export default function Sidebar({
160
  <span className="name">{g.name}</span>
161
  <span className="count">{g.sessionIds.length}</span>
162
  <span className="row-actions">
163
- <button className="mini-btn" title="Rename" onClick={(e) => { e.stopPropagation(); startEdit(ref, g.name); }}></button>
164
- <button className="mini-btn" title="Delete group" onClick={(e) => { e.stopPropagation(); onDeleteGroup(g.id); }}>🗑</button>
165
  </span>
166
  </>
167
  )}
@@ -175,10 +180,13 @@ export default function Sidebar({
175
  return (
176
  <aside className="sidebar">
177
  <div className="brand">
178
- <div className="logo"><span className="dot" /><h1>Agent Manager</h1></div>
 
 
 
179
  <div className="brand-actions">
180
- <button className="icon-btn" onClick={onOpenSettings} title="Settings"></button>
181
- <button className="icon-btn" onClick={onToggleTheme} title="Toggle light / dark">{theme === 'dark' ? '☾' : '☀'}</button>
182
  </div>
183
  </div>
184
 
 
4
  import Logo from './Logo';
5
  import NewSession from './NewSession';
6
  import FolderPicker from './FolderPicker';
7
+ import { SlidersGlyph, SunGlyph, MoonGlyph, TrashGlyph, PencilGlyph, StopGlyph } from './icons';
8
 
9
  type Zone = 'before' | 'after' | 'on';
10
 
 
44
 
45
  const sessById = useMemo(() => Object.fromEntries(tree.sessions.map((s) => [s.id, s])), [tree.sessions]);
46
  const groupById = useMemo(() => Object.fromEntries(tree.groups.map((g) => [g.id, g])), [tree.groups]);
47
+ const colorOf = useMemo(() => Object.fromEntries(clis.map((c) => [c.id, c.color])), [clis]);
48
+ // Brand dot doubles as aggregate status: any agent working > any waiting > rest.
49
+ const agg = tree.sessions.some((s) => s.state === 'working') ? 'working'
50
+ : tree.sessions.some((s) => s.state === 'waiting') ? 'waiting' : 'idle';
51
 
52
  const clearDrag = () => { setDragRef(null); setDrop(null); };
53
  const bump = (id: string, d: number) => setCart((c) => ({ ...c, [id]: Math.max(0, (c[id] || 0) + d) }));
 
128
  ) : (
129
  <span className="name">{s.name}</span>
130
  )}
131
+ <Logo cli={s.cli} size={11} tint={colorOf[s.cli]} />
132
  <span className="row-actions">
133
+ {s.running && <button className="mini-btn" title="Stop" onClick={(e) => { e.stopPropagation(); onStopSession(s.id); }}><StopGlyph /></button>}
134
+ <button className="mini-btn" title="Delete" onClick={(e) => { e.stopPropagation(); onDeleteSession(s.id); }}><TrashGlyph /></button>
135
  </span>
136
  </div>
137
  );
 
165
  <span className="name">{g.name}</span>
166
  <span className="count">{g.sessionIds.length}</span>
167
  <span className="row-actions">
168
+ <button className="mini-btn" title="Rename" onClick={(e) => { e.stopPropagation(); startEdit(ref, g.name); }}><PencilGlyph /></button>
169
+ <button className="mini-btn" title="Delete group" onClick={(e) => { e.stopPropagation(); onDeleteGroup(g.id); }}><TrashGlyph /></button>
170
  </span>
171
  </>
172
  )}
 
180
  return (
181
  <aside className="sidebar">
182
  <div className="brand">
183
+ <div className="logo">
184
+ <span className={`dot agg-${agg}`} title={agg === 'working' ? 'agents working' : agg === 'waiting' ? 'an agent needs you' : 'all quiet'} />
185
+ <h1>Agent Manager</h1>
186
+ </div>
187
  <div className="brand-actions">
188
+ <button className="icon-btn" onClick={onOpenSettings} title="Settings"><SlidersGlyph /></button>
189
+ <button className="icon-btn" onClick={onToggleTheme} title="Toggle light / dark">{theme === 'dark' ? <MoonGlyph /> : <SunGlyph />}</button>
190
  </div>
191
  </div>
192
 
web/src/components/SkillsEditor.tsx CHANGED
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
2
  import { marked } from 'marked';
3
  import * as api from '../api';
4
  import type { SkillFile } from '../api';
 
5
 
6
  // Split YAML-ish frontmatter (name/description) from the markdown body.
7
  function parseFront(md: string): { meta: Record<string, string> | null; body: string } {
@@ -112,7 +113,7 @@ export default function SkillsEditor() {
112
  <button className="btn-ghost" onClick={() => setConfirmDel(false)}>Cancel</button>
113
  </span>
114
  ) : (
115
- <button className="btn-ghost danger" title="Delete skill" onClick={() => setConfirmDel(true)}>🗑 Delete</button>
116
  )}
117
  </div>
118
  {mode === 'view' ? (() => {
 
2
  import { marked } from 'marked';
3
  import * as api from '../api';
4
  import type { SkillFile } from '../api';
5
+ import { TrashGlyph } from './icons';
6
 
7
  // Split YAML-ish frontmatter (name/description) from the markdown body.
8
  function parseFront(md: string): { meta: Record<string, string> | null; body: string } {
 
113
  <button className="btn-ghost" onClick={() => setConfirmDel(false)}>Cancel</button>
114
  </span>
115
  ) : (
116
+ <button className="btn-ghost danger" title="Delete skill" onClick={() => setConfirmDel(true)}><TrashGlyph /> Delete</button>
117
  )}
118
  </div>
119
  {mode === 'view' ? (() => {
web/src/components/TerminalPane.tsx CHANGED
@@ -6,6 +6,7 @@ import '@xterm/xterm/css/xterm.css';
6
  import type { Cli, Session } from '../types';
7
  import { STATE_LABEL } from '../types';
8
  import Logo from './Logo';
 
9
 
10
  const THEMES: Record<'light' | 'dark', ITheme> = {
11
  dark: {
@@ -90,7 +91,7 @@ function writeClipboard(text: string) {
90
  }
91
 
92
  export default function TerminalPane({
93
- session, theme, focused, active, zoom = 100, onFocus, onRename, onClose,
94
  }: {
95
  session: Session;
96
  cli?: Cli;
@@ -250,13 +251,23 @@ export default function TerminalPane({
250
  return () => clearTimeout(t);
251
  }, [active]);
252
 
 
 
253
  return (
254
- <div className={`slot${focused ? ' focused' : ''}`} onMouseDown={() => onFocus?.()}>
 
 
 
 
255
  {/* preventDefault so clicking the (non-focusable) header keeps keyboard
256
  focus in the terminal instead of the browser blurring it. */}
257
- <div className="pane-head" onMouseDown={(e) => { e.preventDefault(); onFocus?.(); termRef.current?.focus(); }}>
 
 
 
 
258
  <div className="ph-left">
259
- <Logo cli={session.cli} size={20} />
260
  <span className={`status ${session.state}`} title={`${STATE_LABEL[session.state]} · ${conn}`} />
261
  </div>
262
  {editing ? (
@@ -270,7 +281,7 @@ export default function TerminalPane({
270
  ) : (
271
  <span className="ph-title" title="Double-click to rename" onDoubleClick={() => { setDraft(session.name); setEditing(true); }}>{session.name}</span>
272
  )}
273
- <button className="mini-btn ph-close" title="Close" onClick={(e) => { e.stopPropagation(); onClose(); }}></button>
274
  </div>
275
  <div className="term-host" ref={hostRef} />
276
  {conn === 'exited' && (
@@ -284,7 +295,7 @@ export default function TerminalPane({
284
  className="btn-ghost"
285
  onMouseDown={(e) => e.stopPropagation()}
286
  onClick={(e) => { e.stopPropagation(); reconnectRef.current(); }}
287
- > Restart</button>
288
  </div>
289
  </div>
290
  )}
 
6
  import type { Cli, Session } from '../types';
7
  import { STATE_LABEL } from '../types';
8
  import Logo from './Logo';
9
+ import { CloseGlyph, RefreshGlyph } from './icons';
10
 
11
  const THEMES: Record<'light' | 'dark', ITheme> = {
12
  dark: {
 
91
  }
92
 
93
  export default function TerminalPane({
94
+ session, cli, theme, focused, active, zoom = 100, onFocus, onRename, onClose,
95
  }: {
96
  session: Session;
97
  cli?: Cli;
 
251
  return () => clearTimeout(t);
252
  }, [active]);
253
 
254
+ // Focused panes tint toward THEIR agent's brand color, not the app accent.
255
+ const tint = cli?.color;
256
  return (
257
+ <div
258
+ className={`slot${focused ? ' focused' : ''}`}
259
+ style={focused && tint ? { borderColor: `color-mix(in srgb, ${tint} 45%, var(--border))` } : undefined}
260
+ onMouseDown={() => onFocus?.()}
261
+ >
262
  {/* preventDefault so clicking the (non-focusable) header keeps keyboard
263
  focus in the terminal instead of the browser blurring it. */}
264
+ <div
265
+ className="pane-head"
266
+ style={focused && tint ? { background: `color-mix(in srgb, ${tint} 8%, var(--panel))` } : undefined}
267
+ onMouseDown={(e) => { e.preventDefault(); onFocus?.(); termRef.current?.focus(); }}
268
+ >
269
  <div className="ph-left">
270
+ <Logo cli={session.cli} size={16} tint={tint} />
271
  <span className={`status ${session.state}`} title={`${STATE_LABEL[session.state]} · ${conn}`} />
272
  </div>
273
  {editing ? (
 
281
  ) : (
282
  <span className="ph-title" title="Double-click to rename" onDoubleClick={() => { setDraft(session.name); setEditing(true); }}>{session.name}</span>
283
  )}
284
+ <button className="mini-btn ph-close" title="Close" onClick={(e) => { e.stopPropagation(); onClose(); }}><CloseGlyph /></button>
285
  </div>
286
  <div className="term-host" ref={hostRef} />
287
  {conn === 'exited' && (
 
295
  className="btn-ghost"
296
  onMouseDown={(e) => e.stopPropagation()}
297
  onClick={(e) => { e.stopPropagation(); reconnectRef.current(); }}
298
+ ><RefreshGlyph /> Restart</button>
299
  </div>
300
  </div>
301
  )}
web/src/components/icons.tsx CHANGED
@@ -1,18 +1,100 @@
 
 
1
  // Shared minimal stroke glyphs (inherit currentColor, scale to their box).
2
- export const FolderGlyph = ({ className, open = false }: { className?: string; open?: boolean }) => (open ? (
 
3
  <svg className={className} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinejoin="round" strokeLinecap="round">
 
 
 
 
 
 
4
  <path d="M2 12.25V4.25a1 1 0 0 1 1-1h2.85a1 1 0 0 1 .7.3l.9.9h4.9a1 1 0 0 1 1 1v1.1" />
5
  <path d="M2 12.4l1.7-4.05a1 1 0 0 1 .92-.6h9.36a1 1 0 0 1 .94 1.35l-1.3 3.5a1 1 0 0 1-.94.65H3a1 1 0 0 1-1-1z" />
6
- </svg>
7
  ) : (
8
- <svg className={className} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinejoin="round">
9
  <path d="M1.75 4.25a1 1 0 0 1 1-1h3.1a1 1 0 0 1 .7.3l.9.9h5.1a1 1 0 0 1 1 1v6.1a1 1 0 0 1-1 1H2.75a1 1 0 0 1-1-1z" />
10
- </svg>
11
  ));
12
 
13
  export const FileGlyph = ({ className }: { className?: string }) => (
14
- <svg className={className} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinejoin="round">
15
  <path d="M4 1.75h5L12.25 5v8.25a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2.75a1 1 0 0 1 1-1z" />
16
  <path d="M8.75 1.9V5.25h3.35" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  </svg>
18
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { ReactNode } from 'react';
2
+
3
  // Shared minimal stroke glyphs (inherit currentColor, scale to their box).
4
+ // One pen for the whole UI: 16px grid, 1.2 stroke, round joins.
5
+ const G = ({ className, children }: { className?: string; children: ReactNode }) => (
6
  <svg className={className} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinejoin="round" strokeLinecap="round">
7
+ {children}
8
+ </svg>
9
+ );
10
+
11
+ export const FolderGlyph = ({ className, open = false }: { className?: string; open?: boolean }) => (open ? (
12
+ <G className={className}>
13
  <path d="M2 12.25V4.25a1 1 0 0 1 1-1h2.85a1 1 0 0 1 .7.3l.9.9h4.9a1 1 0 0 1 1 1v1.1" />
14
  <path d="M2 12.4l1.7-4.05a1 1 0 0 1 .92-.6h9.36a1 1 0 0 1 .94 1.35l-1.3 3.5a1 1 0 0 1-.94.65H3a1 1 0 0 1-1-1z" />
15
+ </G>
16
  ) : (
17
+ <G className={className}>
18
  <path d="M1.75 4.25a1 1 0 0 1 1-1h3.1a1 1 0 0 1 .7.3l.9.9h5.1a1 1 0 0 1 1 1v6.1a1 1 0 0 1-1 1H2.75a1 1 0 0 1-1-1z" />
19
+ </G>
20
  ));
21
 
22
  export const FileGlyph = ({ className }: { className?: string }) => (
23
+ <G className={className}>
24
  <path d="M4 1.75h5L12.25 5v8.25a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2.75a1 1 0 0 1 1-1z" />
25
  <path d="M8.75 1.9V5.25h3.35" />
26
+ </G>
27
+ );
28
+
29
+ export const SlidersGlyph = ({ className }: { className?: string }) => (
30
+ <G className={className}>
31
+ <path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h11" />
32
+ <circle cx="10" cy="4.5" r="1.5" fill="var(--panel, #fff)" />
33
+ <circle cx="6" cy="8" r="1.5" fill="var(--panel, #fff)" />
34
+ <circle cx="11" cy="11.5" r="1.5" fill="var(--panel, #fff)" />
35
+ </G>
36
+ );
37
+
38
+ export const SunGlyph = ({ className }: { className?: string }) => (
39
+ <G className={className}>
40
+ <circle cx="8" cy="8" r="3" />
41
+ <path d="M8 1.5v1.6M8 12.9v1.6M1.5 8h1.6M12.9 8h1.6M3.4 3.4l1.14 1.14M11.46 11.46l1.14 1.14M12.6 3.4l-1.14 1.14M4.54 11.46L3.4 12.6" />
42
+ </G>
43
+ );
44
+
45
+ export const MoonGlyph = ({ className }: { className?: string }) => (
46
+ <G className={className}>
47
+ <path d="M13.2 9.6A5.6 5.6 0 1 1 6.4 2.8a4.4 4.4 0 0 0 6.8 6.8z" />
48
+ </G>
49
+ );
50
+
51
+ export const TrashGlyph = ({ className }: { className?: string }) => (
52
+ <G className={className}>
53
+ <path d="M3 4.5h10M6.4 4.4v-1a1 1 0 0 1 1-1h1.2a1 1 0 0 1 1 1v1" />
54
+ <path d="M4.4 4.6l.55 8a1 1 0 0 0 1 .93h4.1a1 1 0 0 0 1-.93l.55-8M6.7 7.2v3.8M9.3 7.2v3.8" />
55
+ </G>
56
+ );
57
+
58
+ export const PencilGlyph = ({ className }: { className?: string }) => (
59
+ <G className={className}>
60
+ <path d="M11.2 2.6l2.2 2.2-7.6 7.6-2.9.7.7-2.9zM9.7 4.1l2.2 2.2" />
61
+ </G>
62
+ );
63
+
64
+ export const CloseGlyph = ({ className }: { className?: string }) => (
65
+ <G className={className}>
66
+ <path d="M4.2 4.2l7.6 7.6M11.8 4.2l-7.6 7.6" />
67
+ </G>
68
+ );
69
+
70
+ export const StopGlyph = ({ className }: { className?: string }) => (
71
+ <svg className={className} viewBox="0 0 16 16" fill="currentColor">
72
+ <rect x="4.4" y="4.4" width="7.2" height="7.2" rx="1.4" />
73
  </svg>
74
  );
75
+
76
+ export const UploadGlyph = ({ className }: { className?: string }) => (
77
+ <G className={className}>
78
+ <path d="M8 10V3.3M5.1 6L8 3.2 10.9 6M3 10.7v1.8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1.8" />
79
+ </G>
80
+ );
81
+
82
+ export const UpGlyph = ({ className }: { className?: string }) => (
83
+ <G className={className}>
84
+ <path d="M8 12.8V3.4M4.4 7L8 3.4 11.6 7" />
85
+ </G>
86
+ );
87
+
88
+ export const RefreshGlyph = ({ className }: { className?: string }) => (
89
+ <G className={className}>
90
+ <path d="M13.2 8A5.2 5.2 0 1 1 11 3.8" />
91
+ <path d="M11.2 1.6l.3 2.5 2.5-.3" />
92
+ </G>
93
+ );
94
+
95
+ export const LockGlyph = ({ className }: { className?: string }) => (
96
+ <G className={className}>
97
+ <path d="M5.4 7V5.1a2.6 2.6 0 0 1 5.2 0V7" />
98
+ <rect x="3.4" y="7" width="9.2" height="6.4" rx="1.2" />
99
+ </G>
100
+ );
web/src/styles.css CHANGED
@@ -4,6 +4,12 @@
4
  :root {
5
  --font-sans: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
6
  --font-mono: 'Geist Mono', ui-monospace, 'SF Mono', SFMono-Regular, Menlo, 'Cascadia Code', monospace;
 
 
 
 
 
 
7
  --accent: #0e7c86;
8
  --accent-fg: #ffffff;
9
  --go: #1c8c57;
@@ -39,8 +45,20 @@
39
  --tile: #f2f4f6;
40
  }
41
 
42
- * { box-sizing: border-box; }
 
 
 
43
  html, body, #root { height: 100%; margin: 0; }
 
 
 
 
 
 
 
 
 
44
  body {
45
  font-family: var(--font-sans);
46
  background: var(--bg);
@@ -53,7 +71,7 @@ body {
53
  .sidebar { width: 282px; flex: none; background: var(--panel); border-right: 1px solid var(--border); display: flex; flex-direction: column; overflow: hidden; }
54
  .main { flex: 1; min-width: 0; padding: 12px; overflow: hidden; display: flex; flex-direction: column; }
55
  .stage { flex: 1; min-height: 0; }
56
- .zoombar { display: flex; align-items: center; gap: 4px; padding: 4px 8px; margin-top: 8px; background: var(--panel); border: 1px solid var(--border); border-radius: 10px; flex: none; }
57
  .zoombar .spacer { flex: 1; }
58
 
59
  /* transparent CLI logos; invert the mostly-black ones in dark mode */
@@ -68,16 +86,28 @@ body {
68
  .brand { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 14px 16px; border-bottom: 1px solid var(--border); }
69
  .brand .logo { display: flex; align-items: center; gap: 9px; }
70
  .brand h1 { font-size: 15px; margin: 0; letter-spacing: -0.01em; }
71
- .brand .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 10px var(--accent); }
72
- .icon-btn { background: transparent; border: 1px solid var(--border); color: var(--muted); border-radius: 8px; width: 32px; height: 30px; cursor: pointer; font-size: 15px; display: inline-flex; align-items: center; justify-content: center; }
 
 
 
 
73
  .icon-btn:hover { color: var(--text); border-color: var(--border-strong); }
74
 
 
 
 
 
 
 
 
 
75
  /* controls */
76
  .controls { padding: 12px 12px; display: flex; flex-direction: column; gap: 8px; border-bottom: 1px solid var(--border); }
77
  .add-row { display: flex; gap: 8px; }
78
  .add-row .btn-ghost { flex: 1; }
79
  /* one shared button box so every button lines up (same height/padding/font) */
80
- .btn-ghost, .btn-primary { display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 7px 12px; font-size: 13px; font-weight: 600; line-height: 1.25; border: 1px solid transparent; border-radius: 8px; cursor: pointer; }
81
  .btn-ghost { background: var(--panel-2); color: var(--text); border-color: var(--border); }
82
  .btn-ghost:hover { border-color: var(--border-strong); }
83
  .btn-ghost:disabled { opacity: 0.45; cursor: not-allowed; }
@@ -85,9 +115,13 @@ body {
85
  .btn-ghost.on { border-color: var(--accent); color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); }
86
  .btn-primary { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
87
  .btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
 
 
 
 
88
 
89
- .widget { display: flex; flex-direction: column; gap: 8px; background: var(--panel-2); border: 1px solid var(--border); border-radius: 10px; padding: 10px; }
90
- .widget input, .widget select { width: 100%; padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--panel); color: var(--text); font-size: 13px; font-family: inherit; }
91
  .widget input:focus, .widget select:focus { outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent); border-color: var(--accent); }
92
  .widget select { appearance: none; -webkit-appearance: none; cursor: pointer; padding-right: 28px;
93
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'><path d='M2.5 4.5l3.5 3.5 3.5-3.5' fill='none' stroke='%238a93a0' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");
@@ -97,15 +131,15 @@ body {
97
 
98
  /* new-agent: logo buttons */
99
  .agent-picks { display: flex; flex-direction: column; gap: 4px; }
100
- .agent-pick { display: flex; align-items: center; gap: 8px; padding: 6px 8px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; color: var(--text); font: inherit; font-size: 13px; cursor: pointer; text-align: left; }
101
  .agent-pick:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--panel)); }
102
  .agent-pick.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, var(--panel)); font-weight: 600; }
103
 
104
  /* detected secrets/variables */
105
  .secret-list { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
106
  .secret-row { display: flex; align-items: center; gap: 8px; }
107
- .secret-chip { padding: 4px 8px; font-size: 12px; background: var(--panel-2); border: 1px solid var(--border); border-radius: 6px; white-space: nowrap; flex: none; min-width: 140px; }
108
- .secret-desc { flex: 1; padding: 6px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--panel); color: var(--text); font: inherit; font-size: 13px; }
109
  .secret-desc:focus { outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent); border-color: var(--accent); }
110
 
111
  /* new-group: per-agent quantity cart */
@@ -113,13 +147,13 @@ body {
113
 
114
  /* folder location picker (in the create widgets) */
115
  .fp { display: flex; flex-direction: column; gap: 4px; }
116
- .fp-current { display: flex; align-items: center; gap: 7px; width: 100%; padding: 7px 10px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; color: var(--text); font: inherit; font-size: 12.5px; cursor: pointer; text-align: left; }
117
  .fp-current:hover { border-color: var(--border-strong); }
118
  .fp-ico { flex: none; width: 14px; height: 14px; color: var(--muted); }
119
  .fp-row .fp-ico { width: 13px; height: 13px; margin-right: 4px; }
120
  .fp-path { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: 'Geist Mono', ui-monospace, monospace; font-size: 11.5px; }
121
  .fp-toggle { flex: none; color: var(--muted); font-size: 10px; }
122
- .fp-tree { max-height: 180px; overflow-y: auto; border: 1px solid var(--border); border-radius: 8px; background: var(--panel); padding: 3px 0; display: flex; flex-direction: column; }
123
  .fp-row { display: flex; align-items: center; gap: 2px; padding: 2px 8px; font-size: 12.5px; color: var(--text); background: transparent; border: none; cursor: pointer; text-align: left; width: 100%; font-family: inherit; }
124
  .fp-row.picked { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); }
125
  .fp-row:hover:not(.picked) { background: var(--panel-2); }
@@ -133,10 +167,10 @@ body {
133
  .fp-input { flex: 1; padding: 3px 8px !important; font-size: 12px !important; }
134
 
135
  .cart { display: flex; flex-direction: column; gap: 4px; }
136
- .cart-row { display: flex; align-items: center; gap: 8px; padding: 3px 4px; border-radius: 8px; font-size: 13px; }
137
  .cart-row.has { background: color-mix(in srgb, var(--accent) 8%, transparent); }
138
  .cart-name { color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
139
- .stepper { display: inline-flex; align-items: center; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; flex: none; background: var(--panel); }
140
  .stepper button { width: 24px; height: 24px; border: none; background: transparent; color: var(--muted); font-size: 15px; line-height: 1; cursor: pointer; }
141
  .stepper button:hover:not(:disabled) { color: var(--text); background: var(--panel-2); }
142
  .stepper button:disabled { opacity: 0.35; cursor: not-allowed; }
@@ -151,7 +185,7 @@ body {
151
  .gap.over { position: relative; }
152
  .gap.over::after { content: ''; position: absolute; left: 8px; right: 8px; top: 3px; height: 2px; border-radius: 2px; background: var(--accent); }
153
 
154
- .row { position: relative; display: flex; align-items: center; gap: 8px; padding: 4px 8px; border-radius: 7px; cursor: pointer; border: 1px solid transparent; }
155
  .row.session { cursor: grab; }
156
  .row.nested { margin-left: 16px; }
157
  .row:hover { background: var(--panel-2); }
@@ -161,24 +195,29 @@ body {
161
  .row.drop-before::after { top: -1px; }
162
  .row.drop-after::after { bottom: -1px; }
163
  .row.drop-on { background: var(--drop); outline: 1px solid var(--accent); outline-offset: -1px; }
164
- .row .name { flex: 1; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
165
- .row .rename { flex: 1; font: inherit; font-size: 13px; padding: 1px 6px; border: 1px solid var(--accent); border-radius: 6px; background: var(--panel-2); color: var(--text); min-width: 0; }
166
- .row.group-head .name { font-weight: 600; }
167
- .row .count { font-size: 11px; color: var(--muted); }
 
168
  .caret { background: transparent; border: none; color: var(--muted); cursor: pointer; font-size: 13px; line-height: 1; width: 16px; padding: 0; flex: none; }
169
- .row .row-actions { display: none; gap: 2px; }
170
- .row:hover .row-actions { display: flex; }
171
- .mini-btn { display: inline-flex; align-items: center; justify-content: center; gap: 4px; border: none; background: transparent; color: var(--muted); cursor: pointer; font-size: 12px; padding: 3px 6px; border-radius: 5px; }
172
  .mini-btn:hover { color: var(--text); background: var(--border); }
173
 
174
- .group { border: 1px solid var(--border); border-radius: 9px; margin: 5px 2px; padding: 2px; }
175
- .group.active { background: color-mix(in srgb, var(--accent) 9%, transparent); border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }
176
- .group.drop-into { background: var(--drop); outline: 1px dashed var(--accent); }
 
 
 
 
177
 
178
  /* status dots */
179
- .status { width: 8px; height: 8px; border-radius: 50%; flex: none; display: inline-block; }
180
- .status.working { background: var(--accent); box-shadow: 0 0 6px color-mix(in srgb, var(--accent) 55%, transparent); }
181
- .status.waiting { background: #e0a948; box-shadow: 0 0 6px color-mix(in srgb, #e0a948 55%, transparent); }
182
  .status.idle { background: var(--muted); opacity: 0.5; }
183
  .status.stopped { background: transparent; border: 1.5px solid var(--muted); opacity: 0.5; }
184
 
@@ -191,27 +230,27 @@ body {
191
 
192
  /* main: tiles */
193
  .tiles { height: 100%; display: grid; gap: 12px; grid-auto-rows: minmax(0, 1fr); }
194
- .tiles.drop-over { outline: 2px dashed var(--accent); outline-offset: -6px; border-radius: 12px; }
195
- .slot { position: relative; min-width: 0; min-height: 0; border-radius: 12px; overflow: hidden; border: 1px solid var(--border); background: var(--term-bg); display: flex; flex-direction: column; }
196
 
197
  /* public-space locked screen */
198
  .locked { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px; background: var(--bg); }
199
- .locked-card { max-width: 920px; width: 100%; background: var(--panel); border: 1px solid var(--border); border-radius: 16px; padding: 32px 34px; box-shadow: 0 12px 40px rgba(0,0,0,0.22); }
200
- .locked-emoji { font-size: 34px; }
201
  .locked-card h1 { margin: 8px 0 4px; font-size: 22px; }
202
  .locked-card h3 { margin: 22px 0 6px; font-size: 14px; }
203
  .locked-lead { color: var(--text); font-size: 14px; line-height: 1.55; }
204
  .locked-sub { color: var(--muted); font-size: 13px; line-height: 1.5; }
205
  .locked-cmd { position: relative; margin-top: 8px; }
206
- .locked-cmd pre { margin: 0; overflow-x: hidden; white-space: pre-wrap; overflow-wrap: anywhere; background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px; padding: 42px 14px 14px; font-size: 12px; line-height: 1.5; }
207
- .locked-copy { position: absolute; top: 10px; right: 10px; width: 30px; height: 30px; border: 1px solid var(--border); border-radius: 8px; background: var(--panel); color: var(--muted); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
208
  .locked-copy:hover { color: var(--text); border-color: var(--border-strong); }
209
  .locked-copy svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linejoin: round; }
210
 
211
  /* terminal "process exited" overlay */
212
  /* Non-blocking banner so the agent's last output (e.g. a login error) stays readable. */
213
  .term-overlay { position: absolute; left: 0; right: 0; bottom: 0; display: flex; justify-content: center; padding: 12px; z-index: 5; pointer-events: none; }
214
- .term-overlay-card { pointer-events: auto; display: flex; align-items: center; gap: 12px; padding: 9px 9px 9px 14px; background: var(--panel); border: 1px solid var(--border); border-radius: 10px; box-shadow: 0 8px 28px rgba(0,0,0,0.28); }
215
  .term-overlay-card .to-text { display: flex; flex-direction: column; gap: 1px; }
216
  .term-overlay-card .to-title { font-size: 13px; font-weight: 600; }
217
  .term-overlay-card .to-help { font-size: 11px; color: var(--muted); }
@@ -219,25 +258,36 @@ body {
219
 
220
  .pane-head { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 9px; padding: 7px 11px; background: var(--panel); border-bottom: 1px solid var(--border); font-size: 12px; flex: none; }
221
  .pane-head .ph-left { display: inline-flex; align-items: center; gap: 7px; }
222
- .pane-head .ph-title { text-align: center; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: text; }
223
- .pane-head .ph-title-input { width: 100%; text-align: center; font: inherit; font-weight: 600; padding: 1px 6px; border: 1px solid var(--accent); border-radius: 6px; background: var(--panel-2); color: var(--text); min-width: 0; }
224
  .pane-head .ph-close { justify-self: end; }
225
- .slot.focused { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }
226
- .slot.focused .pane-head { background: color-mix(in srgb, var(--accent) 13%, var(--panel)); }
 
 
227
 
228
- .term-host { flex: 1; min-height: 0; padding: 6px 4px 4px 8px; background: var(--term-bg); }
229
  .term-host .xterm { height: 100%; }
230
  .xterm .xterm-viewport { scrollbar-width: none; }
231
  .xterm .xterm-viewport::-webkit-scrollbar { width: 0; height: 0; display: none; }
232
 
233
  /* main: empty states */
234
  .empty-group { height: 100%; display: flex; align-items: center; justify-content: center; }
235
- .empty-group.drop-over { outline: 2px dashed var(--accent); outline-offset: -10px; border-radius: 14px; }
236
- .empty-card { width: min(420px, 90%); background: var(--panel); border: 1px solid var(--border); border-radius: 14px; padding: 26px; text-align: center; display: flex; flex-direction: column; gap: 12px; }
237
- .empty-card h2 { margin: 0; font-size: 18px; }
238
- .empty-card p { margin: 0; color: var(--muted); font-size: 13.5px; line-height: 1.5; }
239
  .empty-card .widget { text-align: left; }
240
- .dropline { margin-top: 2px; padding: 14px; border: 1px dashed var(--border-strong); border-radius: 10px; color: var(--muted); font-size: 12.5px; }
 
 
 
 
 
 
 
 
 
 
 
241
 
242
  .brand-actions { display: flex; gap: 6px; }
243
 
@@ -245,7 +295,7 @@ body {
245
  .files-bar { display: flex; align-items: center; gap: 8px; padding: 6px 10px; background: var(--panel); border-bottom: 1px solid var(--border); font-size: 12px; flex: none; }
246
  .files-bar .spacer { flex: 1; }
247
  .crumbs { display: flex; align-items: center; gap: 1px; overflow: hidden; white-space: nowrap; }
248
- .crumb { background: none; border: none; color: var(--accent); cursor: pointer; font: inherit; font-size: 12px; padding: 1px 4px; border-radius: 5px; }
249
  .crumb:hover { background: var(--panel-2); }
250
  .crumbs .sep { color: var(--muted); }
251
  /* upload is a <label> styled as a button — inherit size from its paired class
@@ -253,7 +303,7 @@ body {
253
  .upload-btn { display: inline-flex; align-items: center; justify-content: center; gap: 5px; cursor: pointer; }
254
  .files-body { flex: 1; min-height: 0; overflow-y: auto; padding: 6px; background: var(--term-bg); }
255
  .files-body.drag { outline: 2px dashed var(--accent); outline-offset: -6px; }
256
- .file-row { display: flex; align-items: center; gap: 10px; padding: 6px 10px; border-radius: 7px; cursor: pointer; text-decoration: none; color: var(--text); font-size: 13px; }
257
  .file-row:hover { background: var(--panel-2); }
258
  .file-row .fi { width: 14px; color: var(--muted); text-align: center; flex: none; }
259
  .file-row .fi.dir { color: var(--accent); }
@@ -263,7 +313,7 @@ body {
263
 
264
  /* tree file viewer — ASCII-style rails (font-size set inline from shared zoom) */
265
  .files-body.tree { padding: 6px 8px; font-size: 13px; }
266
- .tree-row { position: relative; display: flex; align-items: center; gap: 0.4em; padding: 0.15em 0.5em 0.15em 0; border-radius: 6px; cursor: pointer; text-decoration: none; color: var(--text); white-space: nowrap; user-select: none; font-size: 1em; }
267
  .tree-row:hover { background: var(--panel-2); }
268
 
269
  /* one fixed-width cell per depth; lines drawn with pseudo-elements */
@@ -282,34 +332,34 @@ body {
282
 
283
  /* settings */
284
  .settings-nav { padding: 10px; display: flex; flex-direction: column; gap: 4px; }
285
- .settings-navitem { text-align: left; background: none; border: 1px solid transparent; border-radius: 8px; padding: 9px 12px; font: inherit; font-size: 13px; color: var(--text); cursor: pointer; }
286
  .settings-navitem:hover { background: var(--panel-2); }
287
  .settings-navitem.active { background: color-mix(in srgb, var(--accent) 12%, transparent); border-color: color-mix(in srgb, var(--accent) 30%, transparent); color: var(--accent); font-weight: 600; }
288
  .settings-main { overflow-y: auto; }
289
  .settings-page { max-width: 640px; margin: 0 auto; padding: 6px 4px 30px; }
290
  .settings-page h2 { margin: 6px 0 18px; font-size: 22px; }
291
  .settings-page h3 { margin: 24px 0 8px; font-size: 14px; }
292
- .setting-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 14px 16px; background: var(--panel); border: 1px solid var(--border); border-radius: 12px; }
293
  .s-label { font-weight: 600; font-size: 14px; }
294
  .s-help { color: var(--muted); font-size: 12.5px; line-height: 1.5; margin: 2px 0 0; }
295
  .s-muted { color: var(--muted); }
296
  .agent-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 8px; margin-top: 8px; }
297
- .agent-chip { display: flex; align-items: center; gap: 8px; padding: 10px 12px; background: var(--panel); border: 1px solid var(--border); border-radius: 10px; font-size: 13px; }
298
  .agent-chip .s-muted { font-size: 11px; }
299
  .agent-chip .chip-ver { margin-left: 6px; opacity: 0.8; }
300
  .agent-chip .chip-state { margin-left: auto; }
301
- .kv { display: flex; flex-direction: column; background: var(--panel); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; margin-top: 8px; }
302
  .kv > div { display: flex; justify-content: space-between; gap: 16px; padding: 11px 16px; border-bottom: 1px solid var(--border); font-size: 13px; }
303
  .kv > div:last-child { border-bottom: none; }
304
  .kv span { color: var(--muted); }
305
  .kv b { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 65%; }
306
- .placeholder { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 20px; }
307
  .placeholder p { margin: 0 0 8px; }
308
 
309
  /* terminal zoom bar */
310
  .pane-foot { display: flex; align-items: center; gap: 4px; padding: 3px 8px; background: var(--panel); border-top: 1px solid var(--border); flex: none; }
311
  .pane-foot .spacer { flex: 1; }
312
- .zbtn { width: 22px; height: 20px; border: 1px solid var(--border); background: var(--panel-2); color: var(--muted); border-radius: 6px; cursor: pointer; font-size: 13px; line-height: 1; display: inline-flex; align-items: center; justify-content: center; }
313
  .zbtn:hover { color: var(--text); border-color: var(--border-strong); }
314
  .zlvl { min-width: 44px; background: none; border: none; color: var(--muted); font-size: 11px; cursor: pointer; font-variant-numeric: tabular-nums; }
315
  .zlvl:hover { color: var(--text); }
@@ -320,7 +370,7 @@ body {
320
  .skills-list { width: 220px; flex: none; display: flex; flex-direction: column; gap: 4px; overflow-y: auto; border-right: 1px solid var(--border); padding-right: 10px; }
321
  .skills-actions { display: flex; gap: 6px; margin-bottom: 6px; }
322
  .skills-actions .btn-ghost { flex: 1; text-align: center; }
323
- .skill-item { text-align: left; background: none; border: 1px solid transparent; border-radius: 7px; padding: 7px 10px; font: inherit; font-size: 13px; color: var(--text); cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
324
  .skill-item:hover { background: var(--panel-2); }
325
  .skill-item.active { background: color-mix(in srgb, var(--accent) 12%, transparent); border-color: color-mix(in srgb, var(--accent) 30%, transparent); color: var(--accent); }
326
  .skills-editor { flex: 1; min-width: 0; display: flex; flex-direction: column; }
@@ -328,30 +378,30 @@ body {
328
  .skills-toolbar .spacer { flex: 1; }
329
  .btn-ghost.danger { color: var(--danger); }
330
  .btn-ghost.danger:hover { border-color: var(--danger); }
331
- .btn-danger { background: var(--danger); color: #fff; border: 1px solid var(--danger); border-radius: 8px; padding: 8px 12px; font-size: 13px; font-weight: 600; cursor: pointer; }
332
  .confirm-del { display: inline-flex; align-items: center; gap: 8px; }
333
  .confirm-del .s-muted { font-size: 12px; }
334
  .skill-title { font-size: 13px; font-weight: 600; }
335
- .seg { display: inline-flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
336
  .seg button { background: var(--panel); border: none; padding: 5px 12px; font: inherit; font-size: 12px; color: var(--muted); cursor: pointer; }
337
  .seg button.on { background: var(--accent); color: var(--accent-fg); }
338
- .skill-text { flex: 1; width: 100%; resize: none; border: 1px solid var(--border); border-radius: 10px; background: var(--term-bg); color: var(--text); font-family: var(--font-mono); font-size: 13px; padding: 12px; line-height: 1.5; }
339
  .skill-text:focus { outline: 2px solid color-mix(in srgb, var(--accent) 40%, transparent); border-color: var(--accent); }
340
 
341
  /* rendered markdown */
342
  .skill-view { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 10px; }
343
- .skill-meta { flex: none; display: flex; flex-direction: column; gap: 3px; border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: 10px; padding: 10px 14px; background: var(--panel-2); }
344
  .skill-meta-name { font-size: 12px; color: var(--muted); }
345
  .skill-meta-desc { font-size: 14px; color: var(--text); line-height: 1.45; }
346
- .markdown { flex: 1; overflow-y: auto; border: 1px solid var(--border); border-radius: 10px; padding: 16px 20px; background: var(--panel); line-height: 1.6; font-size: 14px; }
347
  .markdown > :first-child { margin-top: 0; }
348
  .markdown h1, .markdown h2, .markdown h3, .markdown h4 { margin: 18px 0 8px; line-height: 1.25; }
349
  .markdown h1 { font-size: 22px; } .markdown h2 { font-size: 18px; } .markdown h3 { font-size: 15px; }
350
  .markdown p { margin: 0 0 12px; }
351
  .markdown ul, .markdown ol { margin: 0 0 12px; padding-left: 22px; }
352
  .markdown li { margin: 3px 0; }
353
- .markdown code { font-family: var(--font-mono); font-size: 0.88em; background: color-mix(in srgb, var(--muted) 18%, transparent); padding: 1px 5px; border-radius: 5px; }
354
- .markdown pre { background: var(--term-bg); border: 1px solid var(--border); border-radius: 8px; padding: 12px; overflow-x: auto; }
355
  .markdown pre code { background: none; padding: 0; }
356
  .markdown blockquote { margin: 0 0 12px; padding: 2px 14px; border-left: 3px solid var(--border-strong); color: var(--muted); }
357
  .markdown a { color: var(--accent); }
@@ -366,7 +416,7 @@ body {
366
  .usage-top .spacer { flex: 1; }
367
  .usage-top .s-muted { font-size: 12px; }
368
  .usage-top .btn-ghost { padding: 5px 10px; font-size: 12px; }
369
- .usage-card { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px; }
370
  .usage-head { display: flex; align-items: center; gap: 9px; font-size: 14px; }
371
  .usage-head .spacer { flex: 1; }
372
  .usage-head .s-muted { font-size: 12px; font-variant-numeric: tabular-nums; }
 
4
  :root {
5
  --font-sans: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
6
  --font-mono: 'Geist Mono', ui-monospace, 'SF Mono', SFMono-Regular, Menlo, 'Cascadia Code', monospace;
7
+ /* radius scale: soft, not bubbly */
8
+ --r-sm: 4px; /* mini buttons, chips, inline inputs */
9
+ --r-md: 6px; /* buttons, inputs, rows */
10
+ --r-lg: 8px; /* widgets, cards */
11
+ --r-xl: 10px; /* panes, tiles */
12
+ --amber: #e0a948;
13
  --accent: #0e7c86;
14
  --accent-fg: #ffffff;
15
  --go: #1c8c57;
 
45
  --tile: #f2f4f6;
46
  }
47
 
48
+ * { box-sizing: border-box; scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
49
+ *::-webkit-scrollbar { width: 8px; height: 8px; }
50
+ *::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 999px; }
51
+ *::-webkit-scrollbar-track { background: transparent; }
52
  html, body, #root { height: 100%; margin: 0; }
53
+
54
+ /* keyboard focus, one style everywhere */
55
+ button:focus-visible, a:focus-visible, label:focus-visible, [tabindex]:focus-visible {
56
+ outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent); outline-offset: 1px;
57
+ }
58
+
59
+ @media (prefers-reduced-motion: reduce) {
60
+ *, *::before, *::after { animation: none !important; transition: none !important; }
61
+ }
62
  body {
63
  font-family: var(--font-sans);
64
  background: var(--bg);
 
71
  .sidebar { width: 282px; flex: none; background: var(--panel); border-right: 1px solid var(--border); display: flex; flex-direction: column; overflow: hidden; }
72
  .main { flex: 1; min-width: 0; padding: 12px; overflow: hidden; display: flex; flex-direction: column; }
73
  .stage { flex: 1; min-height: 0; }
74
+ .zoombar { display: flex; align-items: center; gap: 4px; padding: 4px 8px; margin-top: 8px; background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg); flex: none; }
75
  .zoombar .spacer { flex: 1; }
76
 
77
  /* transparent CLI logos; invert the mostly-black ones in dark mode */
 
86
  .brand { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 14px 16px; border-bottom: 1px solid var(--border); }
87
  .brand .logo { display: flex; align-items: center; gap: 9px; }
88
  .brand h1 { font-size: 15px; margin: 0; letter-spacing: -0.01em; }
89
+ /* The brand dot is live: it aggregates every agent's state. */
90
+ .brand .dot { width: 9px; height: 9px; border-radius: 50%; transition: background 0.25s, box-shadow 0.25s, opacity 0.25s; }
91
+ .brand .dot.agg-working { background: var(--accent); box-shadow: 0 0 10px var(--accent); animation: breathe 2.4s ease-in-out infinite; }
92
+ .brand .dot.agg-waiting { background: var(--amber); box-shadow: 0 0 10px var(--amber); }
93
+ .brand .dot.agg-idle { background: var(--muted); opacity: 0.5; }
94
+ .icon-btn { background: transparent; border: 1px solid var(--border); color: var(--muted); border-radius: var(--r-md); width: 32px; height: 30px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
95
  .icon-btn:hover { color: var(--text); border-color: var(--border-strong); }
96
 
97
+ /* stroke glyph sizing inside the shared button boxes */
98
+ .icon-btn svg { width: 15px; height: 15px; }
99
+ .mini-btn svg { width: 13px; height: 13px; }
100
+ .btn-ghost svg, .btn-primary svg, .btn-danger svg { width: 14px; height: 14px; }
101
+
102
+ @keyframes breathe { 50% { opacity: 0.55; } }
103
+ @keyframes rise-in { from { opacity: 0; transform: translateY(-3px); } }
104
+
105
  /* controls */
106
  .controls { padding: 12px 12px; display: flex; flex-direction: column; gap: 8px; border-bottom: 1px solid var(--border); }
107
  .add-row { display: flex; gap: 8px; }
108
  .add-row .btn-ghost { flex: 1; }
109
  /* one shared button box so every button lines up (same height/padding/font) */
110
+ .btn-ghost, .btn-primary { display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 7px 12px; font-size: 13px; font-weight: 600; line-height: 1.25; border: 1px solid transparent; border-radius: var(--r-md); cursor: pointer; }
111
  .btn-ghost { background: var(--panel-2); color: var(--text); border-color: var(--border); }
112
  .btn-ghost:hover { border-color: var(--border-strong); }
113
  .btn-ghost:disabled { opacity: 0.45; cursor: not-allowed; }
 
115
  .btn-ghost.on { border-color: var(--accent); color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); }
116
  .btn-primary { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
117
  .btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
118
+ /* hovers ease instead of snapping */
119
+ .btn-ghost, .btn-primary, .icon-btn, .agent-pick, .mini-btn, .fp-current, .settings-navitem, .skill-item, .row {
120
+ transition: border-color 0.12s ease-out, background 0.12s ease-out, color 0.12s ease-out;
121
+ }
122
 
123
+ .widget { display: flex; flex-direction: column; gap: 8px; background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--r-lg); padding: 10px; animation: rise-in 0.14s ease-out; }
124
+ .widget input, .widget select { width: 100%; padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--panel); color: var(--text); font-size: 13px; font-family: inherit; }
125
  .widget input:focus, .widget select:focus { outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent); border-color: var(--accent); }
126
  .widget select { appearance: none; -webkit-appearance: none; cursor: pointer; padding-right: 28px;
127
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'><path d='M2.5 4.5l3.5 3.5 3.5-3.5' fill='none' stroke='%238a93a0' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");
 
131
 
132
  /* new-agent: logo buttons */
133
  .agent-picks { display: flex; flex-direction: column; gap: 4px; }
134
+ .agent-pick { display: flex; align-items: center; gap: 8px; padding: 6px 8px; background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-md); color: var(--text); font: inherit; font-size: 13px; cursor: pointer; text-align: left; }
135
  .agent-pick:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--panel)); }
136
  .agent-pick.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, var(--panel)); font-weight: 600; }
137
 
138
  /* detected secrets/variables */
139
  .secret-list { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
140
  .secret-row { display: flex; align-items: center; gap: 8px; }
141
+ .secret-chip { padding: 4px 8px; font-size: 12px; background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--r-sm); white-space: nowrap; flex: none; min-width: 140px; }
142
+ .secret-desc { flex: 1; padding: 6px 10px; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--panel); color: var(--text); font: inherit; font-size: 13px; }
143
  .secret-desc:focus { outline: 2px solid color-mix(in srgb, var(--accent) 45%, transparent); border-color: var(--accent); }
144
 
145
  /* new-group: per-agent quantity cart */
 
147
 
148
  /* folder location picker (in the create widgets) */
149
  .fp { display: flex; flex-direction: column; gap: 4px; }
150
+ .fp-current { display: flex; align-items: center; gap: 7px; width: 100%; padding: 7px 10px; background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-md); color: var(--text); font: inherit; font-size: 12.5px; cursor: pointer; text-align: left; }
151
  .fp-current:hover { border-color: var(--border-strong); }
152
  .fp-ico { flex: none; width: 14px; height: 14px; color: var(--muted); }
153
  .fp-row .fp-ico { width: 13px; height: 13px; margin-right: 4px; }
154
  .fp-path { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: 'Geist Mono', ui-monospace, monospace; font-size: 11.5px; }
155
  .fp-toggle { flex: none; color: var(--muted); font-size: 10px; }
156
+ .fp-tree { max-height: 180px; overflow-y: auto; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--panel); padding: 3px 0; display: flex; flex-direction: column; animation: rise-in 0.14s ease-out; }
157
  .fp-row { display: flex; align-items: center; gap: 2px; padding: 2px 8px; font-size: 12.5px; color: var(--text); background: transparent; border: none; cursor: pointer; text-align: left; width: 100%; font-family: inherit; }
158
  .fp-row.picked { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); }
159
  .fp-row:hover:not(.picked) { background: var(--panel-2); }
 
167
  .fp-input { flex: 1; padding: 3px 8px !important; font-size: 12px !important; }
168
 
169
  .cart { display: flex; flex-direction: column; gap: 4px; }
170
+ .cart-row { display: flex; align-items: center; gap: 8px; padding: 3px 4px; border-radius: var(--r-md); font-size: 13px; }
171
  .cart-row.has { background: color-mix(in srgb, var(--accent) 8%, transparent); }
172
  .cart-name { color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
173
+ .stepper { display: inline-flex; align-items: center; border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden; flex: none; background: var(--panel); }
174
  .stepper button { width: 24px; height: 24px; border: none; background: transparent; color: var(--muted); font-size: 15px; line-height: 1; cursor: pointer; }
175
  .stepper button:hover:not(:disabled) { color: var(--text); background: var(--panel-2); }
176
  .stepper button:disabled { opacity: 0.35; cursor: not-allowed; }
 
185
  .gap.over { position: relative; }
186
  .gap.over::after { content: ''; position: absolute; left: 8px; right: 8px; top: 3px; height: 2px; border-radius: 2px; background: var(--accent); }
187
 
188
+ .row { position: relative; display: flex; align-items: center; gap: 8px; padding: 4px 8px; border-radius: var(--r-md); cursor: pointer; border: 1px solid transparent; }
189
  .row.session { cursor: grab; }
190
  .row.nested { margin-left: 16px; }
191
  .row:hover { background: var(--panel-2); }
 
195
  .row.drop-before::after { top: -1px; }
196
  .row.drop-after::after { bottom: -1px; }
197
  .row.drop-on { background: var(--drop); outline: 1px solid var(--accent); outline-offset: -1px; }
198
+ /* mono for THINGS (session names, counts), sans for commentary */
199
+ .row .name { flex: 1; font-family: var(--font-mono); font-size: 12.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
200
+ .row .rename { flex: 1; font: inherit; font-family: var(--font-mono); font-size: 12.5px; padding: 1px 6px; border: 1px solid var(--accent); border-radius: var(--r-sm); background: var(--panel-2); color: var(--text); min-width: 0; }
201
+ .row.group-head .name { font-family: var(--font-sans); font-size: 13px; font-weight: 600; }
202
+ .row .count { font-family: var(--font-mono); font-size: 11px; color: var(--muted); }
203
  .caret { background: transparent; border: none; color: var(--muted); cursor: pointer; font-size: 13px; line-height: 1; width: 16px; padding: 0; flex: none; }
204
+ .row .row-actions { display: flex; gap: 2px; opacity: 0; pointer-events: none; transition: opacity 0.12s ease-out; }
205
+ .row:hover .row-actions, .row:focus-within .row-actions { opacity: 1; pointer-events: auto; }
206
+ .mini-btn { display: inline-flex; align-items: center; justify-content: center; gap: 4px; border: none; background: transparent; color: var(--muted); cursor: pointer; font-size: 12px; padding: 3px 6px; border-radius: var(--r-sm); }
207
  .mini-btn:hover { color: var(--text); background: var(--border); }
208
 
209
+ /* groups: no box nested rows hang off an indent rail, like the file tree */
210
+ .group { margin: 3px 0; }
211
+ .group.active .group-head { background: color-mix(in srgb, var(--accent) 10%, transparent); border-color: color-mix(in srgb, var(--accent) 30%, transparent); }
212
+ .group.drop-into { background: var(--drop); outline: 1px dashed var(--accent); border-radius: var(--r-md); }
213
+ .row.nested { margin-left: 21px; }
214
+ .row.nested::before { content: ''; position: absolute; left: -11px; top: -2px; bottom: -2px; border-left: 1px solid var(--border); }
215
+ .group.active .row.nested::before { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
216
 
217
  /* status dots */
218
+ .status { width: 8px; height: 8px; border-radius: 50%; flex: none; display: inline-block; transition: background 0.2s, box-shadow 0.2s, opacity 0.2s; }
219
+ .status.working { background: var(--accent); box-shadow: 0 0 6px color-mix(in srgb, var(--accent) 55%, transparent); animation: breathe 2.2s ease-in-out infinite; }
220
+ .status.waiting { background: var(--amber); box-shadow: 0 0 6px color-mix(in srgb, var(--amber) 55%, transparent); }
221
  .status.idle { background: var(--muted); opacity: 0.5; }
222
  .status.stopped { background: transparent; border: 1.5px solid var(--muted); opacity: 0.5; }
223
 
 
230
 
231
  /* main: tiles */
232
  .tiles { height: 100%; display: grid; gap: 12px; grid-auto-rows: minmax(0, 1fr); }
233
+ .tiles.drop-over { outline: 2px dashed var(--accent); outline-offset: -6px; border-radius: var(--r-xl); }
234
+ .slot { position: relative; min-width: 0; min-height: 0; border-radius: var(--r-xl); overflow: hidden; border: 1px solid var(--border); background: var(--term-bg); display: flex; flex-direction: column; }
235
 
236
  /* public-space locked screen */
237
  .locked { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px; background: var(--bg); }
238
+ .locked-card { max-width: 920px; width: 100%; background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 32px 34px; box-shadow: 0 12px 40px rgba(0,0,0,0.22); }
239
+ .locked-emoji svg { width: 36px; height: 36px; color: var(--accent); }
240
  .locked-card h1 { margin: 8px 0 4px; font-size: 22px; }
241
  .locked-card h3 { margin: 22px 0 6px; font-size: 14px; }
242
  .locked-lead { color: var(--text); font-size: 14px; line-height: 1.55; }
243
  .locked-sub { color: var(--muted); font-size: 13px; line-height: 1.5; }
244
  .locked-cmd { position: relative; margin-top: 8px; }
245
+ .locked-cmd pre { margin: 0; overflow-x: hidden; white-space: pre-wrap; overflow-wrap: anywhere; background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--r-md); padding: 42px 14px 14px; font-size: 12px; line-height: 1.5; }
246
+ .locked-copy { position: absolute; top: 10px; right: 10px; width: 30px; height: 30px; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--panel); color: var(--muted); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
247
  .locked-copy:hover { color: var(--text); border-color: var(--border-strong); }
248
  .locked-copy svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linejoin: round; }
249
 
250
  /* terminal "process exited" overlay */
251
  /* Non-blocking banner so the agent's last output (e.g. a login error) stays readable. */
252
  .term-overlay { position: absolute; left: 0; right: 0; bottom: 0; display: flex; justify-content: center; padding: 12px; z-index: 5; pointer-events: none; }
253
+ .term-overlay-card { pointer-events: auto; display: flex; align-items: center; gap: 12px; padding: 9px 9px 9px 14px; background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: 0 8px 28px rgba(0,0,0,0.28); }
254
  .term-overlay-card .to-text { display: flex; flex-direction: column; gap: 1px; }
255
  .term-overlay-card .to-title { font-size: 13px; font-weight: 600; }
256
  .term-overlay-card .to-help { font-size: 11px; color: var(--muted); }
 
258
 
259
  .pane-head { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 9px; padding: 7px 11px; background: var(--panel); border-bottom: 1px solid var(--border); font-size: 12px; flex: none; }
260
  .pane-head .ph-left { display: inline-flex; align-items: center; gap: 7px; }
261
+ .pane-head .ph-title { text-align: center; font-family: var(--font-mono); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: text; }
262
+ .pane-head .ph-title-input { width: 100%; text-align: center; font: inherit; font-family: var(--font-mono); font-weight: 600; padding: 1px 6px; border: 1px solid var(--accent); border-radius: var(--r-sm); background: var(--panel-2); color: var(--text); min-width: 0; }
263
  .pane-head .ph-close { justify-self: end; }
264
+ .slot { transition: border-color 0.15s ease-out, box-shadow 0.15s ease-out; }
265
+ /* fallback focus tint (terminal panes override with their agent's color inline) */
266
+ .slot.focused { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); box-shadow: 0 4px 18px -10px rgba(0, 0, 0, 0.35); }
267
+ .slot.focused .pane-head { background: color-mix(in srgb, var(--accent) 8%, var(--panel)); }
268
 
269
+ .term-host { flex: 1; min-height: 0; padding: 10px 8px 8px 12px; background: var(--term-bg); }
270
  .term-host .xterm { height: 100%; }
271
  .xterm .xterm-viewport { scrollbar-width: none; }
272
  .xterm .xterm-viewport::-webkit-scrollbar { width: 0; height: 0; display: none; }
273
 
274
  /* main: empty states */
275
  .empty-group { height: 100%; display: flex; align-items: center; justify-content: center; }
276
+ .empty-group.drop-over { outline: 2px dashed var(--accent); outline-offset: -10px; border-radius: var(--r-xl); }
277
+ .empty-card { width: min(420px, 90%); background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-xl); padding: 26px; text-align: center; display: flex; flex-direction: column; gap: 14px; }
 
 
278
  .empty-card .widget { text-align: left; }
279
+ .empty-card .empty-term { align-items: center; }
280
+ .dropline { margin-top: 2px; padding: 14px; border: 1px dashed var(--border-strong); border-radius: var(--r-lg); color: var(--muted); font-size: 12.5px; }
281
+
282
+ /* empty states speak the native language: a prompt, waiting */
283
+ .empty-term { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
284
+ .et-prompt { font-size: 15px; letter-spacing: 0.01em; }
285
+ .et-user { color: var(--accent); font-weight: 600; }
286
+ .et-path { color: var(--go); font-weight: 600; }
287
+ .et-dim { color: var(--muted); }
288
+ .et-cursor { display: inline-block; width: 8px; height: 15px; margin-left: 8px; vertical-align: -2px; background: var(--accent); animation: blink 1.15s steps(1) infinite; }
289
+ @keyframes blink { 50% { opacity: 0; } }
290
+ .et-hint { margin: 0; color: var(--muted); font-size: 12.5px; line-height: 1.5; }
291
 
292
  .brand-actions { display: flex; gap: 6px; }
293
 
 
295
  .files-bar { display: flex; align-items: center; gap: 8px; padding: 6px 10px; background: var(--panel); border-bottom: 1px solid var(--border); font-size: 12px; flex: none; }
296
  .files-bar .spacer { flex: 1; }
297
  .crumbs { display: flex; align-items: center; gap: 1px; overflow: hidden; white-space: nowrap; }
298
+ .crumb { background: none; border: none; color: var(--accent); cursor: pointer; font: inherit; font-size: 12px; padding: 1px 4px; border-radius: var(--r-sm); }
299
  .crumb:hover { background: var(--panel-2); }
300
  .crumbs .sep { color: var(--muted); }
301
  /* upload is a <label> styled as a button — inherit size from its paired class
 
303
  .upload-btn { display: inline-flex; align-items: center; justify-content: center; gap: 5px; cursor: pointer; }
304
  .files-body { flex: 1; min-height: 0; overflow-y: auto; padding: 6px; background: var(--term-bg); }
305
  .files-body.drag { outline: 2px dashed var(--accent); outline-offset: -6px; }
306
+ .file-row { display: flex; align-items: center; gap: 10px; padding: 6px 10px; border-radius: var(--r-md); cursor: pointer; text-decoration: none; color: var(--text); font-size: 13px; }
307
  .file-row:hover { background: var(--panel-2); }
308
  .file-row .fi { width: 14px; color: var(--muted); text-align: center; flex: none; }
309
  .file-row .fi.dir { color: var(--accent); }
 
313
 
314
  /* tree file viewer — ASCII-style rails (font-size set inline from shared zoom) */
315
  .files-body.tree { padding: 6px 8px; font-size: 13px; }
316
+ .tree-row { position: relative; display: flex; align-items: center; gap: 0.4em; padding: 0.15em 0.5em 0.15em 0; border-radius: var(--r-sm); cursor: pointer; text-decoration: none; color: var(--text); white-space: nowrap; user-select: none; font-size: 1em; }
317
  .tree-row:hover { background: var(--panel-2); }
318
 
319
  /* one fixed-width cell per depth; lines drawn with pseudo-elements */
 
332
 
333
  /* settings */
334
  .settings-nav { padding: 10px; display: flex; flex-direction: column; gap: 4px; }
335
+ .settings-navitem { text-align: left; background: none; border: 1px solid transparent; border-radius: var(--r-md); padding: 9px 12px; font: inherit; font-size: 13px; color: var(--text); cursor: pointer; }
336
  .settings-navitem:hover { background: var(--panel-2); }
337
  .settings-navitem.active { background: color-mix(in srgb, var(--accent) 12%, transparent); border-color: color-mix(in srgb, var(--accent) 30%, transparent); color: var(--accent); font-weight: 600; }
338
  .settings-main { overflow-y: auto; }
339
  .settings-page { max-width: 640px; margin: 0 auto; padding: 6px 4px 30px; }
340
  .settings-page h2 { margin: 6px 0 18px; font-size: 22px; }
341
  .settings-page h3 { margin: 24px 0 8px; font-size: 14px; }
342
+ .setting-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 14px 16px; background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-xl); }
343
  .s-label { font-weight: 600; font-size: 14px; }
344
  .s-help { color: var(--muted); font-size: 12.5px; line-height: 1.5; margin: 2px 0 0; }
345
  .s-muted { color: var(--muted); }
346
  .agent-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 8px; margin-top: 8px; }
347
+ .agent-chip { display: flex; align-items: center; gap: 8px; padding: 10px 12px; background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg); font-size: 13px; }
348
  .agent-chip .s-muted { font-size: 11px; }
349
  .agent-chip .chip-ver { margin-left: 6px; opacity: 0.8; }
350
  .agent-chip .chip-state { margin-left: auto; }
351
+ .kv { display: flex; flex-direction: column; background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-xl); overflow: hidden; margin-top: 8px; }
352
  .kv > div { display: flex; justify-content: space-between; gap: 16px; padding: 11px 16px; border-bottom: 1px solid var(--border); font-size: 13px; }
353
  .kv > div:last-child { border-bottom: none; }
354
  .kv span { color: var(--muted); }
355
  .kv b { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 65%; }
356
+ .placeholder { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-xl); padding: 20px; }
357
  .placeholder p { margin: 0 0 8px; }
358
 
359
  /* terminal zoom bar */
360
  .pane-foot { display: flex; align-items: center; gap: 4px; padding: 3px 8px; background: var(--panel); border-top: 1px solid var(--border); flex: none; }
361
  .pane-foot .spacer { flex: 1; }
362
+ .zbtn { width: 22px; height: 20px; border: 1px solid var(--border); background: var(--panel-2); color: var(--muted); border-radius: var(--r-sm); cursor: pointer; font-size: 13px; line-height: 1; display: inline-flex; align-items: center; justify-content: center; }
363
  .zbtn:hover { color: var(--text); border-color: var(--border-strong); }
364
  .zlvl { min-width: 44px; background: none; border: none; color: var(--muted); font-size: 11px; cursor: pointer; font-variant-numeric: tabular-nums; }
365
  .zlvl:hover { color: var(--text); }
 
370
  .skills-list { width: 220px; flex: none; display: flex; flex-direction: column; gap: 4px; overflow-y: auto; border-right: 1px solid var(--border); padding-right: 10px; }
371
  .skills-actions { display: flex; gap: 6px; margin-bottom: 6px; }
372
  .skills-actions .btn-ghost { flex: 1; text-align: center; }
373
+ .skill-item { text-align: left; background: none; border: 1px solid transparent; border-radius: var(--r-md); padding: 7px 10px; font: inherit; font-family: var(--font-mono); font-size: 12.5px; color: var(--text); cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
374
  .skill-item:hover { background: var(--panel-2); }
375
  .skill-item.active { background: color-mix(in srgb, var(--accent) 12%, transparent); border-color: color-mix(in srgb, var(--accent) 30%, transparent); color: var(--accent); }
376
  .skills-editor { flex: 1; min-width: 0; display: flex; flex-direction: column; }
 
378
  .skills-toolbar .spacer { flex: 1; }
379
  .btn-ghost.danger { color: var(--danger); }
380
  .btn-ghost.danger:hover { border-color: var(--danger); }
381
+ .btn-danger { background: var(--danger); color: #fff; border: 1px solid var(--danger); border-radius: var(--r-md); padding: 8px 12px; font-size: 13px; font-weight: 600; cursor: pointer; }
382
  .confirm-del { display: inline-flex; align-items: center; gap: 8px; }
383
  .confirm-del .s-muted { font-size: 12px; }
384
  .skill-title { font-size: 13px; font-weight: 600; }
385
+ .seg { display: inline-flex; border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden; }
386
  .seg button { background: var(--panel); border: none; padding: 5px 12px; font: inherit; font-size: 12px; color: var(--muted); cursor: pointer; }
387
  .seg button.on { background: var(--accent); color: var(--accent-fg); }
388
+ .skill-text { flex: 1; width: 100%; resize: none; border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--term-bg); color: var(--text); font-family: var(--font-mono); font-size: 13px; padding: 12px; line-height: 1.5; }
389
  .skill-text:focus { outline: 2px solid color-mix(in srgb, var(--accent) 40%, transparent); border-color: var(--accent); }
390
 
391
  /* rendered markdown */
392
  .skill-view { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 10px; }
393
+ .skill-meta { flex: none; display: flex; flex-direction: column; gap: 3px; border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: var(--r-lg); padding: 10px 14px; background: var(--panel-2); }
394
  .skill-meta-name { font-size: 12px; color: var(--muted); }
395
  .skill-meta-desc { font-size: 14px; color: var(--text); line-height: 1.45; }
396
+ .markdown { flex: 1; overflow-y: auto; border: 1px solid var(--border); border-radius: var(--r-lg); padding: 16px 20px; background: var(--panel); line-height: 1.6; font-size: 14px; }
397
  .markdown > :first-child { margin-top: 0; }
398
  .markdown h1, .markdown h2, .markdown h3, .markdown h4 { margin: 18px 0 8px; line-height: 1.25; }
399
  .markdown h1 { font-size: 22px; } .markdown h2 { font-size: 18px; } .markdown h3 { font-size: 15px; }
400
  .markdown p { margin: 0 0 12px; }
401
  .markdown ul, .markdown ol { margin: 0 0 12px; padding-left: 22px; }
402
  .markdown li { margin: 3px 0; }
403
+ .markdown code { font-family: var(--font-mono); font-size: 0.88em; background: color-mix(in srgb, var(--muted) 18%, transparent); padding: 1px 5px; border-radius: var(--r-sm); }
404
+ .markdown pre { background: var(--term-bg); border: 1px solid var(--border); border-radius: var(--r-md); padding: 12px; overflow-x: auto; }
405
  .markdown pre code { background: none; padding: 0; }
406
  .markdown blockquote { margin: 0 0 12px; padding: 2px 14px; border-left: 3px solid var(--border-strong); color: var(--muted); }
407
  .markdown a { color: var(--accent); }
 
416
  .usage-top .spacer { flex: 1; }
417
  .usage-top .s-muted { font-size: 12px; }
418
  .usage-top .btn-ghost { padding: 5px 10px; font-size: 12px; }
419
+ .usage-card { background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-xl); padding: 14px 16px; }
420
  .usage-head { display: flex; align-items: center; gap: 9px; font-size: 14px; }
421
  .usage-head .spacer { flex: 1; }
422
  .usage-head .s-muted { font-size: 12px; font-variant-numeric: tabular-nums; }