Spaces:
Running
Running
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 +21 -7
- web/src/components/FilesPane.tsx +5 -5
- web/src/components/Locked.tsx +2 -1
- web/src/components/Logo.tsx +10 -3
- web/src/components/SettingsView.tsx +4 -3
- web/src/components/Sidebar.tsx +16 -8
- web/src/components/SkillsEditor.tsx +2 -1
- web/src/components/TerminalPane.tsx +17 -6
- web/src/components/icons.tsx +87 -5
- web/src/styles.css +113 -63
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 |
-
<
|
| 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">
|
| 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 |
-
<
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 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={
|
| 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(); }}>
|
| 147 |
</div>
|
| 148 |
|
| 149 |
<div className="files-bar">
|
| 150 |
-
<button className="mini-btn" title="Up" disabled={!root} onClick={up}>
|
| 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 |
-
|
| 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">
|
| 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' ?
|
| 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">
|
| 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 })}>
|
| 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 & 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 & 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={
|
| 127 |
<span className="row-actions">
|
| 128 |
-
{s.running && <button className="mini-btn" title="Stop" onClick={(e) => { e.stopPropagation(); onStopSession(s.id); }}>
|
| 129 |
-
<button className="mini-btn" title="Delete" onClick={(e) => { e.stopPropagation(); onDeleteSession(s.id); }}>
|
| 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); }}>
|
| 164 |
-
<button className="mini-btn" title="Delete group" onClick={(e) => { e.stopPropagation(); onDeleteGroup(g.id); }}>
|
| 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">
|
|
|
|
|
|
|
|
|
|
| 179 |
<div className="brand-actions">
|
| 180 |
-
<button className="icon-btn" onClick={onOpenSettings} title="Settings">
|
| 181 |
-
<button className="icon-btn" onClick={onToggleTheme} title="Toggle light / dark">{theme === 'dark' ?
|
| 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)}>
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
{/* preventDefault so clicking the (non-focusable) header keeps keyboard
|
| 256 |
focus in the terminal instead of the browser blurring it. */}
|
| 257 |
-
<div
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
<div className="ph-left">
|
| 259 |
-
<Logo cli={session.cli} size={
|
| 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(); }}>
|
| 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 |
-
>
|
| 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 |
-
|
|
|
|
| 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 |
-
</
|
| 7 |
) : (
|
| 8 |
-
<
|
| 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 |
-
</
|
| 11 |
));
|
| 12 |
|
| 13 |
export const FileGlyph = ({ className }: { className?: string }) => (
|
| 14 |
-
<
|
| 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:
|
| 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 |
-
|
| 72 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 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:
|
| 90 |
-
.widget input, .widget select { width: 100%; padding: 8px 10px; border: 1px solid var(--border); border-radius:
|
| 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:
|
| 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:
|
| 108 |
-
.secret-desc { flex: 1; padding: 6px 10px; border: 1px solid var(--border); border-radius:
|
| 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:
|
| 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:
|
| 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:
|
| 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:
|
| 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:
|
| 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 |
-
|
| 165 |
-
.row .
|
| 166 |
-
.row
|
| 167 |
-
.row .
|
|
|
|
| 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:
|
| 170 |
-
.row:hover .row-actions {
|
| 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:
|
| 172 |
.mini-btn:hover { color: var(--text); background: var(--border); }
|
| 173 |
|
| 174 |
-
|
| 175 |
-
.group
|
| 176 |
-
.group.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 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:
|
| 195 |
-
.slot { position: relative; min-width: 0; min-height: 0; border-radius:
|
| 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:
|
| 200 |
-
.locked-emoji {
|
| 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:
|
| 207 |
-
.locked-copy { position: absolute; top: 10px; right: 10px; width: 30px; height: 30px; border: 1px solid var(--border); border-radius:
|
| 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:
|
| 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:
|
| 224 |
.pane-head .ph-close { justify-self: end; }
|
| 225 |
-
.slot
|
| 226 |
-
|
|
|
|
|
|
|
| 227 |
|
| 228 |
-
.term-host { flex: 1; min-height: 0; padding:
|
| 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:
|
| 236 |
-
.empty-card { width: min(420px, 90%); background: var(--panel); border: 1px solid var(--border); border-radius:
|
| 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 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 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:
|
| 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:
|
| 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:
|
| 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:
|
| 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:
|
| 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:
|
| 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:
|
| 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:
|
| 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:
|
| 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:
|
| 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:
|
| 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:
|
| 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:
|
| 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:
|
| 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:
|
| 354 |
-
.markdown pre { background: var(--term-bg); border: 1px solid var(--border); border-radius:
|
| 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:
|
| 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; }
|