@polderlabs/bizar 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/bin.mjs +73 -0
- package/cli/dashboard/api.mjs +473 -0
- package/cli/dashboard/browser.mjs +40 -0
- package/cli/dashboard/server.mjs +366 -0
- package/cli/dashboard/state.mjs +438 -0
- package/cli/dashboard/tasks-store.mjs +203 -0
- package/cli/dashboard/watcher.mjs +81 -0
- package/cli/dashboard.mjs +97 -0
- package/config/commands/bizar.md +13 -39
- package/dist/assets/index-BVvY22Gt.css +1 -0
- package/dist/assets/index-CO3c8O32.js +285 -0
- package/dist/assets/index-CO3c8O32.js.map +1 -0
- package/dist/index.html +18 -0
- package/package.json +26 -2
- package/src/App.tsx +233 -0
- package/src/components/Button.tsx +55 -0
- package/src/components/Card.tsx +40 -0
- package/src/components/EmptyState.tsx +30 -0
- package/src/components/Modal.tsx +137 -0
- package/src/components/Spinner.tsx +19 -0
- package/src/components/StatusBadge.tsx +25 -0
- package/src/components/Tag.tsx +28 -0
- package/src/components/Toast.tsx +142 -0
- package/src/components/Topbar.tsx +88 -0
- package/src/index.html +17 -0
- package/src/lib/api.ts +71 -0
- package/src/lib/markdown.tsx +59 -0
- package/src/lib/types.ts +200 -0
- package/src/lib/utils.ts +79 -0
- package/src/lib/ws.ts +132 -0
- package/src/main.tsx +12 -0
- package/src/styles/main.css +2324 -0
- package/src/views/Agents.tsx +199 -0
- package/src/views/Chat.tsx +255 -0
- package/src/views/Config.tsx +250 -0
- package/src/views/Overview.tsx +267 -0
- package/src/views/Plans.tsx +667 -0
- package/src/views/Projects.tsx +155 -0
- package/src/views/Settings.tsx +253 -0
- package/src/views/Tasks.tsx +567 -0
- package/tsconfig.json +23 -0
- package/vite.config.ts +24 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/dashboard/watcher.mjs
|
|
3
|
+
*
|
|
4
|
+
* chokidar-backed file watcher. Translates low-level fs events into the
|
|
5
|
+
* shape the dashboard's WebSocket layer expects.
|
|
6
|
+
*
|
|
7
|
+
* The watcher ignores initial add events (the UI doesn't need a "you just
|
|
8
|
+
* started, here are 200 files that already existed" flood on connect).
|
|
9
|
+
*/
|
|
10
|
+
import chokidar from 'chokidar';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {object} opts
|
|
14
|
+
* @param {string[]} opts.paths - files or directories to watch
|
|
15
|
+
* @param {(event: 'add'|'change'|'unlink', path: string) => void} opts.onChange
|
|
16
|
+
* @param {object} [opts.options] - extra chokidar options
|
|
17
|
+
*/
|
|
18
|
+
export function createWatcher({ paths, onChange, options = {} }) {
|
|
19
|
+
if (!Array.isArray(paths) || paths.length === 0) {
|
|
20
|
+
throw new Error('createWatcher requires a non-empty paths array');
|
|
21
|
+
}
|
|
22
|
+
if (typeof onChange !== 'function') {
|
|
23
|
+
throw new Error('createWatcher requires an onChange callback');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const watcher = chokidar.watch(paths, {
|
|
27
|
+
ignoreInitial: true,
|
|
28
|
+
persistent: true,
|
|
29
|
+
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 },
|
|
30
|
+
// Ignore: any path segment named `node_modules` (anywhere), and any
|
|
31
|
+
// file/dir whose IMMEDIATE basename starts with `.` (covers .DS_Store,
|
|
32
|
+
// .swp, .bak, etc.). We must NOT walk the whole path — the watched
|
|
33
|
+
// root `~/.config/opencode/agents` contains the segment `.config`
|
|
34
|
+
// which would otherwise match and silently disable the watch.
|
|
35
|
+
ignored: (p) => {
|
|
36
|
+
const s = String(p);
|
|
37
|
+
const parts = s.split(/[\\/]/);
|
|
38
|
+
// Walk all segments — `node_modules` is fine to match anywhere.
|
|
39
|
+
if (parts.includes('node_modules')) return true;
|
|
40
|
+
// Last segment is the basename — check that ONLY, so `.config`
|
|
41
|
+
// in parent dirs doesn't disqualify the watched root.
|
|
42
|
+
const basename = parts[parts.length - 1] || '';
|
|
43
|
+
return basename.startsWith('.');
|
|
44
|
+
},
|
|
45
|
+
...options,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const safe = (event, p) => {
|
|
49
|
+
try {
|
|
50
|
+
onChange(event, p);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
// A faulty onChange must not crash the watcher
|
|
53
|
+
console.error('[dashboard watcher] onChange error:', err);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
watcher.on('add', (p) => safe('add', p));
|
|
58
|
+
watcher.on('change', (p) => safe('change', p));
|
|
59
|
+
watcher.on('unlink', (p) => safe('unlink', p));
|
|
60
|
+
watcher.on('error', (err) => {
|
|
61
|
+
console.error('[dashboard watcher] chokidar error:', err);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
/** @returns {chokidar.FSWatcher} */
|
|
66
|
+
start() {
|
|
67
|
+
return watcher;
|
|
68
|
+
},
|
|
69
|
+
async stop() {
|
|
70
|
+
try {
|
|
71
|
+
await watcher.close();
|
|
72
|
+
} catch {
|
|
73
|
+
/* ignore */
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
/** Force a synthetic broadcast — useful after a self-mutation. */
|
|
77
|
+
poke(event = 'change', path = '<synthetic>') {
|
|
78
|
+
safe(event, path);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/dashboard.mjs
|
|
3
|
+
*
|
|
4
|
+
* v2.5.0 — Dashboard launcher.
|
|
5
|
+
*
|
|
6
|
+
* Picks a free port (preferred: 4321), starts an Express + WebSocket server,
|
|
7
|
+
* writes port + PID files under ~/.config/bizar/, and opens the user's
|
|
8
|
+
* default browser. The server is created and returned; the caller decides
|
|
9
|
+
* whether to keep the process alive.
|
|
10
|
+
*
|
|
11
|
+
* Returns: { url, port, close }
|
|
12
|
+
*/
|
|
13
|
+
import { createServer } from './dashboard/server.mjs';
|
|
14
|
+
import { launchBrowser } from './dashboard/browser.mjs';
|
|
15
|
+
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { homedir } from 'node:os';
|
|
18
|
+
import net from 'node:net';
|
|
19
|
+
|
|
20
|
+
const DEFAULT_PORT = 4321;
|
|
21
|
+
const BIZAR_HOME = join(homedir(), '.config', 'bizar');
|
|
22
|
+
const PORT_FILE = join(BIZAR_HOME, 'dashboard.port');
|
|
23
|
+
const PID_FILE = join(BIZAR_HOME, 'dashboard.pid');
|
|
24
|
+
|
|
25
|
+
export async function launchDashboard(options = {}) {
|
|
26
|
+
// 1. Find a free port
|
|
27
|
+
const port = await findFreePort(options.port || DEFAULT_PORT);
|
|
28
|
+
|
|
29
|
+
// 2. Create the server (does not listen yet)
|
|
30
|
+
const { server, wss, close } = createServer({
|
|
31
|
+
port,
|
|
32
|
+
projectRoot: options.projectRoot || process.cwd(),
|
|
33
|
+
opencodeConfigDir:
|
|
34
|
+
options.opencodeConfigDir || join(homedir(), '.config', 'opencode'),
|
|
35
|
+
bizarRoot: options.bizarRoot || new URL('../..', import.meta.url).pathname,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// 3. Start listening on loopback only
|
|
39
|
+
await new Promise((resolve, reject) => {
|
|
40
|
+
server.once('error', reject);
|
|
41
|
+
server.listen(port, '127.0.0.1', () => {
|
|
42
|
+
server.off('error', reject);
|
|
43
|
+
resolve();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// 4. Persist port + PID so the CLI subcommand can find/stop us
|
|
48
|
+
mkdirSync(BIZAR_HOME, { recursive: true });
|
|
49
|
+
writeFileSync(PORT_FILE, String(port), 'utf8');
|
|
50
|
+
writeFileSync(PID_FILE, String(process.pid), 'utf8');
|
|
51
|
+
|
|
52
|
+
// 5. Open the browser (best effort — non-fatal)
|
|
53
|
+
const url = `http://localhost:${port}/`;
|
|
54
|
+
await launchBrowser(url);
|
|
55
|
+
|
|
56
|
+
// 6. Friendly log
|
|
57
|
+
console.log(`Bizar dashboard: ${url}`);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
url,
|
|
61
|
+
port,
|
|
62
|
+
close,
|
|
63
|
+
wss,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Try the preferred port first, then walk upward up to 100 slots. If still
|
|
69
|
+
* no luck, surface an error so the caller can decide what to do.
|
|
70
|
+
*/
|
|
71
|
+
async function findFreePort(preferred) {
|
|
72
|
+
for (let p = preferred; p < preferred + 100; p++) {
|
|
73
|
+
if (await isPortFree(p)) return p;
|
|
74
|
+
}
|
|
75
|
+
throw new Error(
|
|
76
|
+
`No free port found in range ${preferred}..${preferred + 99}`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isPortFree(port) {
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
const server = net.createServer();
|
|
83
|
+
let settled = false;
|
|
84
|
+
const finish = (ok) => {
|
|
85
|
+
if (settled) return;
|
|
86
|
+
settled = true;
|
|
87
|
+
resolve(ok);
|
|
88
|
+
};
|
|
89
|
+
server.once('error', () => finish(false));
|
|
90
|
+
server.once('listening', () => server.close(() => finish(true)));
|
|
91
|
+
// Defensive timeout — don't hang on a wedged port
|
|
92
|
+
const timer = setTimeout(() => finish(false), 1000);
|
|
93
|
+
server.listen(port, '127.0.0.1', () => clearTimeout(timer));
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export { PORT_FILE, PID_FILE, DEFAULT_PORT };
|
package/config/commands/bizar.md
CHANGED
|
@@ -1,44 +1,18 @@
|
|
|
1
|
-
# Bizar
|
|
1
|
+
# Bizar Dashboard
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The `/bizar` command launches the Bizar dashboard in your browser — a fully integrated workspace with Overview, Chat, Agents, Plans, Projects, Config, and Settings panels. The dashboard binds to `127.0.0.1` only and runs as a local Express + WebSocket server on a free port (preferred: 4321).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
If the user invoked `/bizar` with arguments, treat them as a request and route appropriately:
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- "explain X" → invoke `/explain X`
|
|
8
|
+
- "plan Y" → invoke `/visual-plan on` and then `/plan new <slug>` with the user's intent as the slug
|
|
9
|
+
- "review PR" → invoke `/pr-review`
|
|
10
|
+
- "audit" → invoke `/audit`
|
|
11
|
+
- "learn" → invoke `/learn`
|
|
12
|
+
- "init" → invoke `/init`
|
|
13
|
+
- "dashboard" or "open dashboard" → `/bizar` (no args, will launch the dashboard)
|
|
14
|
+
- Otherwise: ask one clarifying question
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
- `/plan new <slug> [template]` — Create a new visual plan
|
|
11
|
-
- `/plan list` — List existing plans
|
|
12
|
-
- `/plan open <slug>` — Open a plan
|
|
13
|
-
- `/plan get <slug>` — Get plan content
|
|
14
|
-
- `/plan add <slug> --title "..."` — Add element to plan
|
|
15
|
-
- `/plan update <slug> <id> ...` — Update element
|
|
16
|
-
- `/plan delete <slug> <id>` — Delete element
|
|
17
|
-
- `/plan comment <slug> [id] "..."` — Add comment
|
|
18
|
-
- `/plan comments <slug> [id]` — List comments
|
|
19
|
-
- `/plan status <slug> <status>` — Set plan status
|
|
20
|
-
- `/plan wait <slug>` — Wait for plan feedback
|
|
21
|
-
- `/audit` — Run a security audit
|
|
22
|
-
- `/explain <question>` — Read-only Q&A (routes to @frigg)
|
|
23
|
-
- `/init` — Initialize Bizar in the current project
|
|
24
|
-
- `/learn` — Extract patterns from the session (routes to @heimdall)
|
|
25
|
-
- `/pr-review` — Run a PR review with @mimir + @forseti (routes to @hermod)
|
|
26
|
-
- `/tailscale-serve` — Set up Tailscale Serve
|
|
16
|
+
If the user invoked `/bizar` with no arguments, the dashboard is launching in the background. Visit `http://localhost:<port>/` to access it. The plugin's `chat.message` hook spawns `bizar dashboard start` as a detached child process and surfaces the live URL in its response.
|
|
27
17
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
1. If the user wants to read code or understand something without changes → invoke `/explain` or answer as @frigg
|
|
31
|
-
2. If the user wants to plan work visually → `/visual-plan on` then `/plan new <slug>`
|
|
32
|
-
3. If the user wants to do a security audit → `/audit`
|
|
33
|
-
4. If the user wants to review a PR → `/pr-review`
|
|
34
|
-
5. If the user wants to learn patterns from a session → `/learn`
|
|
35
|
-
6. If the user wants to initialize Bizar → `/init`
|
|
36
|
-
7. Otherwise, ask a clarifying question and route to the appropriate sub-agent
|
|
37
|
-
|
|
38
|
-
## Response format
|
|
39
|
-
|
|
40
|
-
Briefly explain what you'll do, then either:
|
|
41
|
-
- Run the appropriate slash command, OR
|
|
42
|
-
- Ask one clarifying question if the request is ambiguous
|
|
43
|
-
|
|
44
|
-
Never make changes to the codebase. You are a router, not an executor.
|
|
18
|
+
Common ports: 4321 is preferred; if it's taken, the launcher walks upward and picks the next free port. The PID and port are recorded under `~/.config/bizar/dashboard.{pid,port}` so `bizar dashboard stop` and `bizar dashboard status` can find the running instance.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,*:before,*:after{box-sizing:border-box}html,body,#root{margin:0;padding:0;height:100%}body{font-family:Inter var,Inter,system-ui,-apple-system,Segoe UI,Roboto,Helvetica Neue,sans-serif;font-feature-settings:"cv02","cv03","cv04","cv11";font-size:14px;line-height:1.5;background:var(--bg);color:var(--text);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}button,input,textarea,select{font-family:inherit;font-size:inherit;color:inherit}a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}code,pre,.mono{font-family:var(--mono)}::selection{background:var(--accent-bg);color:var(--accent-2)}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:6px;border:2px solid var(--bg)}::-webkit-scrollbar-thumb:hover{background:var(--border-strong)}:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:4px}:root{--bg: #0b0e14;--bg-elev: #12161f;--bg-elev-2: #1a1f2b;--bg-elev-3: #232a39;--border: #232a39;--border-strong: #2d3648;--text: #c9d1d9;--text-dim: #8b95a8;--text-strong: #f0f6fc;--accent: #8b5cf6;--accent-2: #a78bfa;--accent-3: #c4b5fd;--accent-bg: rgba(139, 92, 246, .12);--accent-border: rgba(139, 92, 246, .4);--success: #34d399;--warning: #fbbf24;--error: #f87171;--info: #60a5fa;--syntax-key: #79c0ff;--syntax-string: #a5d6ff;--syntax-number: #ffa657;--syntax-boolean: #ff7b72;--syntax-null: #ff7b72;--font-mono: "JetBrains Mono", "Fira Code", "SF Mono", "Cascadia Code", Consolas, monospace;--font-sans: "Inter var", "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 20px;--space-6: 24px;--space-8: 32px;--space-10: 40px;--space-12: 48px;--radius-sm: 4px;--radius: 8px;--radius-md: 10px;--radius-lg: 12px;--radius-xl: 16px;--shadow-1: 0 1px 2px rgba(0, 0, 0, .4);--shadow-2: 0 4px 12px rgba(0, 0, 0, .45);--shadow-3: 0 12px 32px rgba(0, 0, 0, .55);--shadow-glow: 0 0 0 1px var(--accent-border), 0 8px 32px rgba(139, 92, 246, .15);--motion-fast: .12s;--motion-base: .18s;--motion-slow: .28s;--ease: cubic-bezier(.4, 0, .2, 1);color-scheme:dark}[data-theme=light]{--bg: #f7f8fa;--bg-elev: #ffffff;--bg-elev-2: #f0f3f8;--bg-elev-3: #e6ebf2;--border: #e2e8f0;--border-strong: #cbd5e1;--text: #1f2937;--text-dim: #64748b;--text-strong: #0f172a;--accent: #7c3aed;--accent-2: #6d28d9;--accent-3: #5b21b6;--accent-bg: rgba(124, 58, 237, .08);--accent-border: rgba(124, 58, 237, .3);--success: #059669;--warning: #d97706;--error: #dc2626;--info: #2563eb;--syntax-key: #0969da;--syntax-string: #0a3069;--syntax-number: #953800;--syntax-boolean: #cf222e;--syntax-null: #cf222e;--shadow-1: 0 1px 2px rgba(15, 23, 42, .06);--shadow-2: 0 4px 12px rgba(15, 23, 42, .08);--shadow-3: 0 12px 32px rgba(15, 23, 42, .12);color-scheme:light}.app{display:flex;flex-direction:column;height:100vh;background:radial-gradient(circle at 0% 0%,var(--accent-bg),transparent 50%),var(--bg);background-attachment:fixed}.content{flex:1;overflow:auto;padding:var(--space-6);position:relative}.view{max-width:1400px;margin:0 auto;display:flex;flex-direction:column;gap:var(--space-5);animation:view-in var(--motion-slow) var(--ease)}@keyframes view-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}.view-loading{display:flex;flex-direction:column;gap:var(--space-3);align-items:center;justify-content:center;min-height:60vh;color:var(--text-dim)}.loading{display:flex;flex-direction:column;gap:var(--space-3);align-items:center;justify-content:center;min-height:100vh;color:var(--text-dim)}.boot-error{max-width:560px;margin:var(--space-12) auto;padding:var(--space-6);background:var(--bg-elev);border:1px solid var(--error);border-radius:var(--radius-lg);box-shadow:var(--shadow-2)}.boot-error h2{margin:0 0 var(--space-3);color:var(--error)}.boot-error code{background:var(--bg);padding:2px 6px;border-radius:var(--radius-sm);font-size:12px}.topbar{display:flex;align-items:center;height:56px;flex-shrink:0;background:color-mix(in srgb,var(--bg-elev) 92%,transparent);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border-bottom:1px solid var(--border);padding:0 var(--space-5);gap:var(--space-5);position:sticky;top:0;z-index:10}.brand{display:flex;align-items:center;gap:var(--space-2);-webkit-user-select:none;user-select:none}.brand-logo{font-size:22px;line-height:1;filter:drop-shadow(0 0 12px rgba(139,92,246,.4))}.brand-title{font-weight:600;font-size:15px;letter-spacing:-.01em;color:var(--text-strong)}.brand-version{font-family:var(--font-mono);font-size:10px;color:var(--text-dim);padding:2px 7px;background:var(--bg-elev-2);border:1px solid var(--border);border-radius:999px;font-weight:500}.tabs{display:flex;gap:2px;flex:1;overflow-x:auto;scrollbar-width:none}.tabs::-webkit-scrollbar{display:none}.tab{display:inline-flex;align-items:center;gap:6px;background:transparent;border:1px solid transparent;color:var(--text-dim);padding:6px 12px;border-radius:999px;cursor:pointer;font-size:13px;font-weight:500;white-space:nowrap;transition:background var(--motion-base) var(--ease),color var(--motion-base) var(--ease),border-color var(--motion-base) var(--ease),transform var(--motion-fast) var(--ease)}.tab:hover{color:var(--text);background:var(--bg-elev-2)}.tab:active{transform:scale(.98)}.tab-active{color:var(--accent-3);background:var(--accent-bg);border-color:var(--accent-border)}.tab-icon{flex-shrink:0}.topbar-right{display:flex;align-items:center;gap:var(--space-3)}.ws-status{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-family:var(--font-mono);color:var(--text-dim);padding:4px 10px;border-radius:999px;background:var(--bg-elev-2);border:1px solid var(--border);text-transform:capitalize}.ws-dot{width:8px;height:8px;border-radius:50%;background:var(--text-dim);position:relative}.ws-connected .ws-dot{background:var(--success);box-shadow:0 0 0 3px #34d39926}.ws-connecting .ws-dot{background:var(--warning);animation:pulse 1.4s ease-in-out infinite}.ws-disconnected .ws-dot{background:var(--error)}@keyframes pulse{0%,to{opacity:1}50%{opacity:.4}}.view-header{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--space-4);flex-wrap:wrap}.view-header-text{display:flex;flex-direction:column;gap:4px;min-width:0}.view-title{display:inline-flex;align-items:center;gap:var(--space-2);font-size:22px;font-weight:700;letter-spacing:-.01em;color:var(--text-strong);margin:0}.view-subtitle{font-size:13px;color:var(--text-dim);margin:0}.view-actions{display:inline-flex;align-items:center;gap:var(--space-2);flex-wrap:wrap}.card{background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius-lg);padding:var(--space-4) var(--space-5);display:flex;flex-direction:column;gap:var(--space-3);transition:border-color var(--motion-base) var(--ease),transform var(--motion-base) var(--ease),box-shadow var(--motion-base) var(--ease)}.card-elevated{background:var(--bg-elev)}.card-outlined{background:transparent}.card-filled{background:var(--bg-elev-2);border-color:var(--border-strong)}.card-interactive{cursor:pointer}.card-interactive:hover{border-color:var(--border-strong);transform:translateY(-1px);box-shadow:var(--shadow-2)}.card-title{display:inline-flex;align-items:center;gap:6px;font-size:13px;font-weight:600;color:var(--text-strong);margin:0;letter-spacing:-.005em}.card-meta{font-size:12px;color:var(--text-dim)}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border-radius:var(--radius);cursor:pointer;font-weight:500;white-space:nowrap;transition:background var(--motion-fast) var(--ease),border-color var(--motion-fast) var(--ease),color var(--motion-fast) var(--ease),transform var(--motion-fast) var(--ease),box-shadow var(--motion-fast) var(--ease)}.btn:active:not(:disabled){transform:scale(.97)}.btn:disabled{opacity:.55;cursor:not-allowed}.btn-size-sm{padding:4px 10px;font-size:12px}.btn-size-md{padding:7px 14px;font-size:13px}.btn-size-lg{padding:10px 18px;font-size:14px}.btn-icon{padding:6px;width:30px;height:30px}.btn-primary{background:var(--accent);color:#fff;border:1px solid var(--accent)}.btn-primary:hover:not(:disabled){background:var(--accent-2);border-color:var(--accent-2)}.btn-accent{background:var(--accent-bg);color:var(--accent-2);border:1px solid var(--accent-border)}.btn-secondary{background:var(--bg-elev-2);color:var(--text);border:1px solid var(--border)}.btn-secondary:hover:not(:disabled){border-color:var(--border-strong);background:var(--bg-elev-3)}.btn-ghost{background:transparent;color:var(--text-dim);border:1px solid transparent}.btn-ghost:hover:not(:disabled){background:var(--bg-elev-2);color:var(--text)}.btn-danger{background:transparent;color:var(--error);border:1px solid var(--error)}.btn-danger:hover:not(:disabled){background:#f871711f}.btn-loading{pointer-events:none}.btn-spinner{width:12px;height:12px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:spin .7s linear infinite}.hint-key{opacity:.7;margin-left:2px}.icon-btn{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:var(--radius-sm);background:transparent;color:var(--text-dim);border:1px solid transparent;cursor:pointer;transition:all var(--motion-fast) var(--ease)}.icon-btn:hover{background:var(--bg-elev-2);color:var(--text);border-color:var(--border)}.icon-btn-danger:hover{background:#f871711f;color:var(--error);border-color:var(--error)}.w-full{width:100%}.input,.textarea,.select{background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);padding:7px 12px;width:100%;font-size:13px;transition:border-color var(--motion-fast) var(--ease),box-shadow var(--motion-fast) var(--ease)}.textarea{resize:vertical;min-height:80px;font-family:var(--font-mono);font-size:12px;line-height:1.55}.select{-webkit-appearance:none;-moz-appearance:none;appearance:none;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 fill='%238b95a8' d='M2 4l4 4 4-4z'/></svg>");background-repeat:no-repeat;background-position:right 10px center;padding-right:30px}[data-theme=light] .select{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 fill='%2364748b' d='M2 4l4 4 4-4z'/></svg>")}.select-sm{padding:4px 28px 4px 10px;font-size:12px;height:30px}.input:focus,.textarea:focus,.select:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-bg)}.textarea.invalid{border-color:var(--error)}.field{display:flex;flex-direction:column;gap:var(--space-2);margin-bottom:var(--space-4)}.field-label{font-size:12px;font-weight:500;color:var(--text-dim);letter-spacing:.01em}.field-help{font-size:11px;color:var(--text-dim)}.checkbox-row{display:flex;align-items:center;gap:var(--space-2);padding:6px 0;font-size:13px;cursor:pointer;color:var(--text)}.checkbox-row input{width:auto;cursor:pointer;accent-color:var(--accent)}.radio-row{display:flex;gap:var(--space-3)}.radio-label{display:inline-flex;align-items:center;gap:4px;font-size:13px;cursor:pointer}.radio-label input{width:auto;accent-color:var(--accent)}.capitalize{text-transform:capitalize}.tabular-nums{font-variant-numeric:tabular-nums}.text-error{color:var(--error)}.muted{color:var(--text-dim)}.text-sm{font-size:12px}.ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:999px;font-size:10px;font-family:var(--font-mono);font-weight:500;background:var(--bg-elev-2);color:var(--text-dim);border:1px solid var(--border);text-transform:uppercase;letter-spacing:.04em}.badge-dot:before{content:"";display:inline-block;width:6px;height:6px;border-radius:50%;background:currentColor}.badge-success{color:var(--success);border-color:#34d39966}.badge-warning{color:var(--warning);border-color:#fbbf2466}.badge-error{color:var(--error);border-color:#f8717166}.badge-info{color:var(--info);border-color:#60a5fa66}.badge-accent{color:var(--accent-2);border-color:var(--accent-border)}.badge-neutral{color:var(--text-dim)}.tag{display:inline-flex;align-items:center;gap:4px;padding:1px 8px;border-radius:999px;font-size:10px;font-family:var(--font-mono);background:var(--accent-bg);color:var(--accent-2);border:1px solid var(--accent-border)}.tag-remove{background:transparent;border:none;color:inherit;cursor:pointer;padding:0 0 0 2px;font-size:12px;line-height:1;opacity:.7}.tag-remove:hover{opacity:1}.spinner{display:inline-block;border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .7s linear infinite}.spinner-sm{width:12px;height:12px}.spinner-md{width:18px;height:18px}.spinner-lg{width:32px;height:32px;border-width:3px}@keyframes spin{to{transform:rotate(360deg)}}.stat-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--space-4)}@media (max-width: 1100px){.stat-grid{grid-template-columns:repeat(2,1fr)}}@media (max-width: 600px){.stat-grid{grid-template-columns:1fr}}.stat-card{flex-direction:row;align-items:center;gap:var(--space-4);padding:var(--space-4) var(--space-5)}.stat-card-icon{width:40px;height:40px;border-radius:var(--radius);background:var(--accent-bg);color:var(--accent-2);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.stat-card-body{flex:1;display:flex;flex-direction:column;gap:2px;min-width:0}.stat-card-value{font-size:26px;font-weight:700;color:var(--text-strong);letter-spacing:-.02em;line-height:1.05}.stat-card-label{font-size:11px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.06em}.stat-card-arrow{color:var(--text-dim);font-size:18px;transition:transform var(--motion-base) var(--ease)}.stat-card:hover .stat-card-arrow{transform:translate(2px);color:var(--accent-2)}.quick-actions{gap:var(--space-3)}.quick-actions-row{display:flex;flex-wrap:wrap;gap:var(--space-2)}.overview-cols{display:grid;grid-template-columns:1.4fr 1fr;gap:var(--space-4)}@media (max-width: 1000px){.overview-cols{grid-template-columns:1fr}}.activity-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px;max-height:360px;overflow-y:auto}.activity-item{display:grid;grid-template-columns:60px 130px 1fr;gap:var(--space-3);padding:8px var(--space-2);font-size:12px;border-radius:var(--radius-sm);font-family:var(--font-mono);border-left:2px solid var(--border);align-items:baseline}.activity-item:hover{background:var(--bg-elev-2)}.activity-ts{color:var(--text-dim);white-space:nowrap}.activity-kind{color:var(--accent-2)}.activity-msg{color:var(--text);word-break:break-word;overflow:hidden;text-overflow:ellipsis}.env-table,.about-table{display:grid;grid-template-columns:max-content 1fr;gap:6px var(--space-4);margin:0;font-size:13px}.env-table dt,.about-table dt{color:var(--text-dim);font-size:12px}.env-table dd,.about-table dd{margin:0;color:var(--text)}.overview-footer{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--text-dim);padding-top:var(--space-3);border-top:1px solid var(--border)}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--space-2);padding:var(--space-8) var(--space-4);text-align:center;color:var(--text-dim)}.empty-icon{color:var(--text-dim);opacity:.7}.empty-title{font-weight:600;color:var(--text);font-size:15px;margin-top:var(--space-2)}.empty-message{font-size:13px;max-width:420px}.empty-action{margin-top:var(--space-3)}.chat-container{display:flex;flex-direction:column;gap:var(--space-3);flex:1;min-height:0}.chat-list{flex:1;min-height:400px;max-height:calc(100vh - 280px);overflow-y:auto;padding:var(--space-3);background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius-lg);display:flex;flex-direction:column;gap:var(--space-3)}.chat-msg{background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-md);padding:var(--space-3) var(--space-4);max-width:85%;animation:msg-in var(--motion-base) var(--ease)}@keyframes msg-in{0%{opacity:0;transform:translateY(2px)}to{opacity:1;transform:none}}.chat-msg-user{align-self:flex-end;background:var(--accent-bg);border-color:var(--accent-border)}.chat-msg-assistant{align-self:flex-start}.chat-msg-system{align-self:center;max-width:100%;font-size:12px;color:var(--text-dim);border-style:dashed}.chat-msg-meta{display:flex;align-items:baseline;gap:var(--space-2);margin-bottom:6px;font-size:11px;font-family:var(--font-mono);color:var(--text-dim)}.chat-msg-role{text-transform:uppercase;font-weight:600;color:var(--accent-2);letter-spacing:.04em}.chat-msg-agent{color:var(--info)}.chat-msg-ts{margin-left:auto}.chat-msg-body{font-size:13px;line-height:1.6;word-break:break-word}.chat-msg-body p{margin:0 0 var(--space-2)}.chat-msg-body p:last-child{margin-bottom:0}.chat-msg-body code{font-family:var(--font-mono);background:var(--bg);padding:1px 6px;border-radius:var(--radius-sm);font-size:12px;border:1px solid var(--border)}.chat-msg-body pre{background:var(--bg);padding:var(--space-3);border-radius:var(--radius);overflow-x:auto;border:1px solid var(--border);font-size:12px}.chat-msg-body pre code{background:transparent;border:none;padding:0}.chat-msg-body a{color:var(--accent-2)}.chat-msg-body ul,.chat-msg-body ol{margin:0 0 var(--space-2);padding-left:var(--space-5)}.chat-msg-body blockquote{border-left:3px solid var(--accent-border);padding-left:var(--space-3);color:var(--text-dim);margin:0 0 var(--space-2)}.chat-composer{display:flex;flex-direction:column;gap:var(--space-2)}.chat-composer-row{display:flex;gap:var(--space-2);align-items:flex-end}.chat-composer-row .chat-input{flex:1;min-height:60px;max-height:200px}.chat-suggestions{display:flex;flex-direction:column;gap:2px;padding:var(--space-2);background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);max-height:200px;overflow-y:auto}.chat-suggestion{display:flex;align-items:baseline;gap:var(--space-2);background:transparent;border:1px solid transparent;color:var(--text);padding:6px var(--space-2);border-radius:var(--radius-sm);text-align:left;cursor:pointer;font-size:12px;transition:background var(--motion-fast) var(--ease)}.chat-suggestion:hover{background:var(--bg-elev-2)}.chat-suggestion-desc{color:var(--text-dim);font-size:11px}.agent-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--space-4)}@media (max-width: 1100px){.agent-grid{grid-template-columns:repeat(2,1fr)}}@media (max-width: 700px){.agent-grid{grid-template-columns:1fr}}.agent-card{gap:var(--space-2)}.agent-card-head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2)}.agent-card-name{font-weight:600;font-family:var(--font-mono);color:var(--accent-2);font-size:15px}.agent-card-desc{font-size:12px;color:var(--text-dim);line-height:1.5;min-height:36px}.agent-card-meta{display:flex;justify-content:space-between;font-size:11px;font-family:var(--font-mono);color:var(--text-dim)}.agent-card-actions{display:flex;gap:var(--space-2);margin-top:var(--space-2)}.invoke-form{display:flex;flex-direction:column;gap:var(--space-3)}.invoke-form-meta{font-size:11px}.invoke-form-desc{margin:0;font-size:13px;color:var(--text-dim)}.new-plan-form{display:flex;gap:var(--space-2);align-items:center;flex-wrap:wrap}.new-plan-form .input{flex:1;min-width:200px}.plans-layout{display:grid;grid-template-columns:340px 1fr;gap:var(--space-4);min-height:500px}@media (max-width: 1000px){.plans-layout{grid-template-columns:1fr}}.plans-list-col{display:flex;flex-direction:column;gap:var(--space-3);max-height:calc(100vh - 280px);overflow-y:auto;padding-right:4px}.plans-canvas-col{display:flex;flex-direction:column;background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius-lg);overflow:hidden;min-height:500px}.plan-card{gap:var(--space-2)}.plan-card-selected{border-color:var(--accent);box-shadow:var(--shadow-glow)}.plan-card-head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2)}.plan-card-title{font-weight:600;color:var(--text-strong);font-size:14px}.plan-card-slug,.plan-card-meta{font-size:11px;color:var(--text-dim)}.plan-card-actions{display:flex;gap:var(--space-2)}.plan-canvas{display:flex;flex-direction:column;flex:1;min-height:0}.plan-canvas-header{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--border);background:var(--bg-elev-2)}.plan-canvas-title{font-weight:600;font-size:14px;color:var(--text-strong)}.plan-canvas-meta{font-size:11px;color:var(--text-dim);margin-top:2px;font-family:var(--font-mono)}.plan-canvas-actions{display:flex;gap:var(--space-2)}.plan-canvas-area{flex:1;position:relative;overflow:hidden;background:var(--bg);min-height:360px}.canvas-root{position:absolute;top:0;right:0;bottom:0;left:0;cursor:grab;overflow:hidden}.canvas-grid-bg{position:absolute;top:-2000px;right:-2000px;bottom:-2000px;left:-2000px;background-image:radial-gradient(circle,var(--border) 1px,transparent 1px);background-size:24px 24px;background-position:2000px 2000px;opacity:.5;pointer-events:none}.canvas-inner{position:absolute;top:0;left:0;transform-origin:0 0}.canvas-element{position:absolute;background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-3);cursor:pointer;transition:border-color var(--motion-base) var(--ease),box-shadow var(--motion-base) var(--ease),transform var(--motion-fast) var(--ease);min-width:140px}.canvas-element:hover{border-color:var(--accent-border);transform:translateY(-1px);box-shadow:var(--shadow-2)}.canvas-element-selected{border-color:var(--accent);box-shadow:var(--shadow-glow)}.canvas-element-type{font-family:var(--font-mono);font-size:10px;color:var(--accent-2);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}.canvas-element-title{font-weight:600;font-size:13px;color:var(--text-strong);margin-bottom:4px}.canvas-element-content{font-size:11px;color:var(--text-dim);line-height:1.5;white-space:pre-wrap;word-break:break-word}.canvas-connections{position:absolute;pointer-events:none;overflow:visible}.canvas-hint{position:absolute;bottom:var(--space-3);right:var(--space-3);font-size:11px;color:var(--text-dim);background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);padding:4px 8px;pointer-events:none}.plan-canvas-empty{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;font-size:13px;color:var(--text-dim);text-align:center;padding:var(--space-6)}.plan-canvas-comments{border-top:1px solid var(--border);padding:var(--space-3) var(--space-4);background:var(--bg-elev-2);max-height:220px;overflow-y:auto}.plan-canvas-comments-title{display:flex;align-items:center;gap:6px;font-size:12px;font-weight:600;color:var(--text);margin:0 0 var(--space-2)}.comment-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--space-3)}.comment-item{padding:var(--space-2) var(--space-3);background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius)}.comment-head{display:flex;align-items:baseline;gap:var(--space-2);margin-bottom:4px}.comment-author{font-size:11px;font-weight:600;color:var(--accent-2)}.comment-time{font-size:11px}.comment-text{font-size:12px;color:var(--text);line-height:1.5}.comment-thread{margin-top:var(--space-2);padding-left:var(--space-3);border-left:2px solid var(--border);display:flex;flex-direction:column;gap:4px}.comment-reply{display:flex;gap:var(--space-2);font-size:11px;align-items:baseline}.comment-reply-text{color:var(--text-dim)}.project-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--space-4)}@media (max-width: 900px){.project-grid{grid-template-columns:1fr}}.project-card{gap:var(--space-2)}.project-card-active{border-color:var(--accent);box-shadow:var(--shadow-glow)}.project-card-head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2)}.project-card-path{font-size:11px;color:var(--text-dim);word-break:break-all}.project-card-actions{display:flex;gap:var(--space-2);margin-top:var(--space-2)}.search-input{position:relative;display:inline-flex;align-items:center}.search-input>svg{position:absolute;left:10px;color:var(--text-dim)}.search-input .input{padding-left:32px;width:220px}.search-input>.icon-btn{position:absolute;right:4px;width:20px;height:20px}.kanban{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--space-4);align-items:start}@media (max-width: 900px){.kanban{grid-template-columns:1fr}}.kanban-column{background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius-lg);display:flex;flex-direction:column;max-height:calc(100vh - 240px);transition:border-color var(--motion-base) var(--ease),background var(--motion-base) var(--ease)}.kanban-column-drop{border-color:var(--accent);background:var(--accent-bg)}.kanban-col-header{display:flex;align-items:center;justify-content:space-between;padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--border);flex-shrink:0}.kanban-col-count{font-size:11px;color:var(--text-dim);background:var(--bg);padding:2px 8px;border-radius:999px;border:1px solid var(--border);font-family:var(--font-mono)}.kanban-col-body{flex:1;overflow-y:auto;padding:var(--space-3);display:flex;flex-direction:column;gap:var(--space-2);min-height:120px}.kanban-empty{text-align:center;color:var(--text-dim);font-size:12px;padding:var(--space-5) 0}.kanban-col-footer{padding:var(--space-2) var(--space-3);border-top:1px solid var(--border);flex-shrink:0}.task-card{background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:var(--space-3);cursor:pointer;border-left:3px solid transparent;transition:border-color var(--motion-base) var(--ease),box-shadow var(--motion-base) var(--ease),transform var(--motion-fast) var(--ease),background var(--motion-base) var(--ease)}.task-card:hover{border-color:var(--border-strong);transform:translateY(-1px);box-shadow:var(--shadow-2)}.task-card-focused{outline:2px solid var(--accent);outline-offset:-2px}.task-card.priority-high{border-left-color:var(--error)}.task-card.priority-low{border-left-color:var(--text-dim)}.task-card.priority-normal{border-left-color:var(--info)}.task-card-head{display:flex;align-items:flex-start;gap:var(--space-2);margin-bottom:6px}.priority-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;margin-top:6px}.task-card-title{font-weight:600;font-size:13px;color:var(--text-strong);flex:1;word-break:break-word;line-height:1.4}.task-card-desc{font-size:12px;color:var(--text-dim);line-height:1.5;margin-bottom:var(--space-2);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.task-card-tags{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:var(--space-2)}.task-card-footer{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2)}.task-card-time{font-size:10px;font-family:var(--font-mono)}.task-card-actions{display:inline-flex;gap:2px}.task-form{display:flex;flex-direction:column;gap:var(--space-2)}.task-form-row{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)}.task-form-field{display:flex;flex-direction:column;gap:var(--space-2)}@keyframes task-move{0%{background:var(--accent-bg)}to{background:transparent}}.config-grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-4)}@media (max-width: 1000px){.config-grid{grid-template-columns:1fr}}.config-textarea{min-height:360px;font-family:var(--font-mono);font-size:12px;line-height:1.55;width:100%;white-space:pre}.config-textarea.invalid{border-color:var(--error)}.json-tree{font-family:var(--font-mono);font-size:12px;line-height:1.6;white-space:pre;overflow-x:auto;max-height:380px;overflow-y:auto;padding:var(--space-3);background:var(--bg);border:1px solid var(--border);border-radius:var(--radius)}.diff-card{gap:var(--space-2)}.diff-view{font-size:11px;line-height:1.5;max-height:300px;overflow-y:auto;padding:var(--space-3);background:var(--bg);border:1px solid var(--border);border-radius:var(--radius)}.diff-line{padding:0 var(--space-2);white-space:pre}.diff-line-removed{color:var(--error);background:#f8717114}.diff-line-added{color:var(--success);background:#34d39914}.settings-grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-4)}@media (max-width: 900px){.settings-grid{grid-template-columns:1fr}}.theme-row{display:flex;gap:var(--space-2);flex-wrap:wrap}.theme-card{flex:1;min-width:100px;display:flex;flex-direction:column;align-items:center;gap:var(--space-2);padding:var(--space-3);background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);cursor:pointer;color:var(--text-dim);transition:border-color var(--motion-base) var(--ease),background var(--motion-base) var(--ease),color var(--motion-base) var(--ease)}.theme-card:hover{border-color:var(--border-strong);color:var(--text)}.theme-card-active{border-color:var(--accent);background:var(--accent-bg);color:var(--accent-2)}.theme-card-label{font-size:12px;font-weight:500}.theme-card-swatch{width:100%;height:24px;border-radius:var(--radius-sm);border:1px solid var(--border)}.theme-card-swatch-dark{background:linear-gradient(135deg,#0b0e14,#1a1f2b)}.theme-card-swatch-light{background:linear-gradient(135deg,#f7f8fa,#e6ebf2)}.theme-card-swatch-system{background:linear-gradient(135deg,#0b0e14,#0b0e14 50%,#f7f8fa 50%,#f7f8fa)}.toast-stack{position:fixed;bottom:var(--space-5);right:var(--space-5);display:flex;flex-direction:column;gap:var(--space-2);z-index:1000;pointer-events:none;max-width:420px}.toast{display:inline-flex;align-items:center;gap:var(--space-2);background:var(--bg-elev);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:var(--radius);padding:10px 14px;box-shadow:var(--shadow-3);font-size:13px;pointer-events:auto;animation:toast-in var(--motion-slow) var(--ease);min-width:240px;color:var(--text)}.toast-icon{flex-shrink:0}.toast-message{flex:1;word-break:break-word}.toast-close{background:transparent;border:none;color:var(--text-dim);cursor:pointer;padding:2px;display:inline-flex}.toast-close:hover{color:var(--text)}.toast-success{border-left-color:var(--success)}.toast-success .toast-icon{color:var(--success)}.toast-error{border-left-color:var(--error)}.toast-error .toast-icon{color:var(--error)}.toast-warning{border-left-color:var(--warning)}.toast-warning .toast-icon{color:var(--warning)}.toast-info .toast-icon{color:var(--info)}@keyframes toast-in{0%{transform:translate(110%);opacity:0}to{transform:translate(0);opacity:1}}.modal-stack{position:fixed;top:0;right:0;bottom:0;left:0;z-index:999;pointer-events:none}.modal-backdrop{position:absolute;top:0;right:0;bottom:0;left:0;background:#0009;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;padding:var(--space-4);pointer-events:auto;animation:fade-in var(--motion-base) var(--ease)}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.modal{background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius-lg);width:min(560px,100%);max-height:80vh;display:flex;flex-direction:column;box-shadow:var(--shadow-3);animation:modal-in var(--motion-slow) var(--ease);overflow:hidden}@keyframes modal-in{0%{opacity:0;transform:translateY(8px) scale(.98)}to{opacity:1;transform:none}}.modal-header{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-4) var(--space-5);border-bottom:1px solid var(--border)}.modal-title{font-size:16px;font-weight:600;margin:0;color:var(--text-strong)}.modal-body{flex:1;overflow-y:auto;padding:var(--space-5);font-size:13px;line-height:1.55}.modal-footer{display:flex;align-items:center;justify-content:flex-end;gap:var(--space-2);padding:var(--space-3) var(--space-5);border-top:1px solid var(--border);background:var(--bg-elev-2)}.modal-footer-actions{display:flex;gap:var(--space-2);justify-content:flex-end;width:100%}.code-block{background:var(--bg);padding:var(--space-3);border-radius:var(--radius);border:1px solid var(--border);font-size:12px;margin:var(--space-2) 0 0;overflow-x:auto}kbd{display:inline-flex;align-items:center;padding:1px 6px;border-radius:var(--radius-sm);background:var(--bg-elev-2);border:1px solid var(--border);border-bottom-width:2px;font-family:var(--font-mono);font-size:10px;color:var(--text-dim)}@media (max-width: 800px){.topbar{padding:0 var(--space-3);gap:var(--space-3)}.brand-version,.tab-label{display:none}.content{padding:var(--space-4)}.ws-status .ws-label{display:none}}
|