backpack-viewer 0.2.21 → 0.3.1
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/README.md +31 -8
- package/bin/serve.js +122 -1
- package/dist/api.d.ts +13 -0
- package/dist/api.js +12 -0
- package/dist/app/assets/index-CvkozBSE.css +1 -0
- package/dist/app/assets/index-j3SowZae.js +34 -0
- package/dist/app/assets/layout-worker-BZXiBoiC.js +1 -0
- package/dist/app/index.html +2 -2
- package/dist/canvas.d.ts +2 -0
- package/dist/canvas.js +473 -161
- package/dist/empty-state.js +13 -0
- package/dist/info-panel.js +2 -2
- package/dist/label-cache.d.ts +14 -0
- package/dist/label-cache.js +54 -0
- package/dist/layout-worker.d.ts +17 -0
- package/dist/layout-worker.js +78 -0
- package/dist/layout.js +73 -18
- package/dist/main.js +47 -14
- package/dist/quadtree.d.ts +43 -0
- package/dist/quadtree.js +147 -0
- package/dist/sidebar.d.ts +2 -0
- package/dist/sidebar.js +90 -1
- package/dist/spatial-hash.d.ts +22 -0
- package/dist/spatial-hash.js +67 -0
- package/dist/style.css +193 -0
- package/dist/tools-pane.d.ts +1 -0
- package/dist/tools-pane.js +109 -0
- package/package.json +2 -2
- package/dist/app/assets/index-BBfZ1JvO.js +0 -21
- package/dist/app/assets/index-DNiYjxNx.css +0 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
**See your learning graph.** A web-based visualizer for [Backpack](https://www.npmjs.com/package/backpack-ontology) learning graphs with force-directed layout, interactive navigation, and live reload.
|
|
4
4
|
|
|
5
|
+

|
|
6
|
+
|
|
5
7
|
## Quick start
|
|
6
8
|
|
|
7
9
|
Tell Claude:
|
|
@@ -16,24 +18,45 @@ npx backpack-viewer
|
|
|
16
18
|
|
|
17
19
|
Opens http://localhost:5173. Click any learning graph in the sidebar to visualize it.
|
|
18
20
|
|
|
21
|
+
**Using Claude Code?** The [backpack-ontology-plugin](https://github.com/NoahIrzinger/backpack-ontology-plugin) bundles the MCP server with usage skills (`backpack-guide`, `backpack-mine`) and is the recommended install path. The viewer works against whatever backpack data the plugin or standalone MCP writes, no extra wiring needed.
|
|
22
|
+
|
|
19
23
|
## Features
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
25
|
+
### Exploration
|
|
26
|
+
- **Live reload** — add knowledge via Claude and watch it appear in real time
|
|
27
|
+
- **Pan and zoom** — click-drag to pan, scroll to zoom
|
|
28
|
+
- **Focus mode** — select nodes and isolate their N-hop subgraph
|
|
29
|
+
- **Walk mode** — traverse the graph node-by-node with a highlighted trail
|
|
30
|
+
- **Path finding** — select two nodes, shortest path highlighted
|
|
31
|
+
- **Search** — filter by name in the sidebar, filter by type with chips
|
|
32
|
+
- **Node history** — back/forward navigation through inspected nodes
|
|
33
|
+
- **Graph snippets** — save walk trails as named, reusable subgraphs
|
|
34
|
+
|
|
35
|
+
### Editing
|
|
36
|
+
- **Inspect** — click any item to see properties, connections, and metadata
|
|
37
|
+
- **Inline edit** — rename graphs, change node types and properties, add/remove items
|
|
38
|
+
- **Star nodes** — mark important nodes with a gold star indicator
|
|
39
|
+
|
|
40
|
+
### Collaboration awareness (0.3.0+)
|
|
41
|
+
- **Lock heartbeat badge** — each graph in the sidebar shows `editing: <author>` when another writer is actively editing (within the last 5 minutes). Backed by a batched `/api/locks` endpoint.
|
|
42
|
+
- **Remote graphs section** — subscribe to learning graphs hosted at HTTPS URLs and view them read-only alongside your local graphs.
|
|
43
|
+
|
|
44
|
+
### Versioning
|
|
45
|
+
- **Branches and snapshots** — the underlying storage is event-sourced; the viewer exposes branch switching and snapshot/rollback UI via the MCP tools.
|
|
26
46
|
|
|
27
47
|
## How it works
|
|
28
48
|
|
|
29
|
-
The viewer reads learning graph data from the same local
|
|
49
|
+
The viewer reads learning graph data from the same local event log that the MCP server writes to. Changes appear automatically, no refresh needed.
|
|
30
50
|
|
|
31
51
|
```
|
|
32
|
-
backpack-ontology (MCP) ──writes──> ~/.local/share/backpack/
|
|
33
|
-
│
|
|
52
|
+
backpack-ontology (MCP) ──writes──> ~/.local/share/backpack/graphs/<name>/
|
|
53
|
+
│ branches/<branch>/events.jsonl
|
|
54
|
+
│ branches/<branch>/snapshot.json
|
|
34
55
|
backpack-viewer ──reads──────────────────┘
|
|
35
56
|
```
|
|
36
57
|
|
|
58
|
+
Old-format graphs (pre-0.3.0) are migrated automatically on MCP startup — the viewer reads the new format on first launch after upgrade, no manual step.
|
|
59
|
+
|
|
37
60
|
## Configuration
|
|
38
61
|
|
|
39
62
|
The viewer reads an optional config file for customizing keybindings and other settings. The config file follows the [XDG Base Directory](https://specifications.freedesktop.org/basedir-spec/latest/) convention:
|
package/bin/serve.js
CHANGED
|
@@ -14,11 +14,13 @@ const hasDistBuild = fs.existsSync(path.join(distDir, "index.html"));
|
|
|
14
14
|
|
|
15
15
|
if (hasDistBuild) {
|
|
16
16
|
// --- Production: static file server + API (zero native deps) ---
|
|
17
|
-
const { JsonFileBackend, dataDir } = await import("backpack-ontology");
|
|
17
|
+
const { JsonFileBackend, dataDir, RemoteRegistry } = await import("backpack-ontology");
|
|
18
18
|
const { loadViewerConfig } = await import("../dist/config.js");
|
|
19
19
|
|
|
20
20
|
const storage = new JsonFileBackend();
|
|
21
21
|
await storage.initialize();
|
|
22
|
+
const remoteRegistry = new RemoteRegistry();
|
|
23
|
+
await remoteRegistry.initialize();
|
|
22
24
|
const viewerConfig = loadViewerConfig();
|
|
23
25
|
|
|
24
26
|
const MIME_TYPES = {
|
|
@@ -31,7 +33,25 @@ if (hasDistBuild) {
|
|
|
31
33
|
".ico": "image/x-icon",
|
|
32
34
|
};
|
|
33
35
|
|
|
36
|
+
// Strict CSP — style-src 'self' means no inline styles allowed.
|
|
37
|
+
// Keep it that way; see CLAUDE.md for the rule.
|
|
38
|
+
const CSP = [
|
|
39
|
+
"default-src 'self'",
|
|
40
|
+
"script-src 'self'",
|
|
41
|
+
"style-src 'self'",
|
|
42
|
+
"img-src 'self' data:",
|
|
43
|
+
"connect-src 'self'",
|
|
44
|
+
"object-src 'none'",
|
|
45
|
+
"base-uri 'self'",
|
|
46
|
+
"frame-ancestors 'none'",
|
|
47
|
+
].join("; ");
|
|
48
|
+
|
|
34
49
|
const server = http.createServer(async (req, res) => {
|
|
50
|
+
res.setHeader("Content-Security-Policy", CSP);
|
|
51
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
52
|
+
res.setHeader("X-Frame-Options", "DENY");
|
|
53
|
+
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
54
|
+
|
|
35
55
|
const url = req.url?.replace(/\?.*$/, "") || "/";
|
|
36
56
|
|
|
37
57
|
// --- API routes ---
|
|
@@ -41,6 +61,63 @@ if (hasDistBuild) {
|
|
|
41
61
|
return;
|
|
42
62
|
}
|
|
43
63
|
|
|
64
|
+
// --- Remote graph routes (read-only) ---
|
|
65
|
+
if (url === "/api/remotes" && req.method === "GET") {
|
|
66
|
+
try {
|
|
67
|
+
const remotes = await remoteRegistry.list();
|
|
68
|
+
const summaries = await Promise.all(
|
|
69
|
+
remotes.map(async (r) => {
|
|
70
|
+
try {
|
|
71
|
+
const data = await remoteRegistry.loadCached(r.name);
|
|
72
|
+
return {
|
|
73
|
+
name: r.name,
|
|
74
|
+
url: r.url,
|
|
75
|
+
source: r.source,
|
|
76
|
+
addedAt: r.addedAt,
|
|
77
|
+
lastFetched: r.lastFetched,
|
|
78
|
+
pinned: r.pinned,
|
|
79
|
+
sizeBytes: r.sizeBytes,
|
|
80
|
+
nodeCount: data.nodes.length,
|
|
81
|
+
edgeCount: data.edges.length,
|
|
82
|
+
};
|
|
83
|
+
} catch {
|
|
84
|
+
return {
|
|
85
|
+
name: r.name,
|
|
86
|
+
url: r.url,
|
|
87
|
+
source: r.source,
|
|
88
|
+
addedAt: r.addedAt,
|
|
89
|
+
lastFetched: r.lastFetched,
|
|
90
|
+
pinned: r.pinned,
|
|
91
|
+
sizeBytes: r.sizeBytes,
|
|
92
|
+
nodeCount: 0,
|
|
93
|
+
edgeCount: 0,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
98
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
99
|
+
res.end(JSON.stringify(summaries));
|
|
100
|
+
} catch (err) {
|
|
101
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
102
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const remoteItemMatch = url.match(/^\/api\/remotes\/(.+)$/);
|
|
108
|
+
if (remoteItemMatch && req.method === "GET") {
|
|
109
|
+
const remoteName = decodeURIComponent(remoteItemMatch[1]);
|
|
110
|
+
try {
|
|
111
|
+
const data = await remoteRegistry.loadCached(remoteName);
|
|
112
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
113
|
+
res.end(JSON.stringify(data));
|
|
114
|
+
} catch (err) {
|
|
115
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
116
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
44
121
|
// --- Branch routes ---
|
|
45
122
|
const branchSwitchMatch = url.match(/^\/api\/graphs\/(.+)\/branches\/switch$/);
|
|
46
123
|
if (branchSwitchMatch && req.method === "POST") {
|
|
@@ -250,6 +327,50 @@ if (hasDistBuild) {
|
|
|
250
327
|
return;
|
|
251
328
|
}
|
|
252
329
|
|
|
330
|
+
// --- Lock heartbeat ---
|
|
331
|
+
if (url === "/api/locks" && req.method === "GET") {
|
|
332
|
+
// Batch endpoint: returns { graphName: lockInfo|null } for all graphs.
|
|
333
|
+
// One request instead of N on every sidebar refresh.
|
|
334
|
+
try {
|
|
335
|
+
const summaries = await storage.listOntologies();
|
|
336
|
+
const result = {};
|
|
337
|
+
if (typeof storage.readLock === "function") {
|
|
338
|
+
await Promise.all(
|
|
339
|
+
summaries.map(async (s) => {
|
|
340
|
+
try {
|
|
341
|
+
result[s.name] = await storage.readLock(s.name);
|
|
342
|
+
} catch {
|
|
343
|
+
result[s.name] = null;
|
|
344
|
+
}
|
|
345
|
+
}),
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
349
|
+
res.end(JSON.stringify(result));
|
|
350
|
+
} catch {
|
|
351
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
352
|
+
res.end("{}");
|
|
353
|
+
}
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const lockMatch = url.match(/^\/api\/graphs\/(.+)\/lock$/);
|
|
358
|
+
if (lockMatch && req.method === "GET") {
|
|
359
|
+
const graphName = decodeURIComponent(lockMatch[1]);
|
|
360
|
+
try {
|
|
361
|
+
const lock =
|
|
362
|
+
typeof storage.readLock === "function"
|
|
363
|
+
? await storage.readLock(graphName)
|
|
364
|
+
: null;
|
|
365
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
366
|
+
res.end(JSON.stringify(lock));
|
|
367
|
+
} catch {
|
|
368
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
369
|
+
res.end("null");
|
|
370
|
+
}
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
253
374
|
if (url === "/api/ontologies") {
|
|
254
375
|
try {
|
|
255
376
|
const summaries = await storage.listOntologies();
|
package/dist/api.d.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import type { LearningGraphData, LearningGraphSummary } from "backpack-ontology";
|
|
2
2
|
export declare function listOntologies(): Promise<LearningGraphSummary[]>;
|
|
3
3
|
export declare function loadOntology(name: string): Promise<LearningGraphData>;
|
|
4
|
+
export interface RemoteSummary {
|
|
5
|
+
name: string;
|
|
6
|
+
url: string;
|
|
7
|
+
source?: string;
|
|
8
|
+
addedAt: string;
|
|
9
|
+
lastFetched: string;
|
|
10
|
+
pinned: boolean;
|
|
11
|
+
sizeBytes: number;
|
|
12
|
+
nodeCount: number;
|
|
13
|
+
edgeCount: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function listRemotes(): Promise<RemoteSummary[]>;
|
|
16
|
+
export declare function loadRemote(name: string): Promise<LearningGraphData>;
|
|
4
17
|
export declare function saveOntology(name: string, data: LearningGraphData): Promise<void>;
|
|
5
18
|
export declare function renameOntology(oldName: string, newName: string): Promise<void>;
|
|
6
19
|
export interface BranchInfo {
|
package/dist/api.js
CHANGED
|
@@ -10,6 +10,18 @@ export async function loadOntology(name) {
|
|
|
10
10
|
throw new Error(`Failed to load ontology: ${name}`);
|
|
11
11
|
return res.json();
|
|
12
12
|
}
|
|
13
|
+
export async function listRemotes() {
|
|
14
|
+
const res = await fetch("/api/remotes");
|
|
15
|
+
if (!res.ok)
|
|
16
|
+
return [];
|
|
17
|
+
return res.json();
|
|
18
|
+
}
|
|
19
|
+
export async function loadRemote(name) {
|
|
20
|
+
const res = await fetch(`/api/remotes/${encodeURIComponent(name)}`);
|
|
21
|
+
if (!res.ok)
|
|
22
|
+
throw new Error(`Failed to load remote graph: ${name}`);
|
|
23
|
+
return res.json();
|
|
24
|
+
}
|
|
13
25
|
export async function saveOntology(name, data) {
|
|
14
26
|
const res = await fetch(`/api/ontologies/${encodeURIComponent(name)}`, {
|
|
15
27
|
method: "PUT",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*{margin:0;padding:0;box-sizing:border-box}:root{--bg: #141414;--bg-surface: #1a1a1a;--bg-hover: #222222;--bg-active: #2a2a2a;--bg-elevated: #1e1e1e;--bg-inset: #111111;--border: #2a2a2a;--text: #d4d4d4;--text-strong: #e5e5e5;--text-muted: #737373;--text-dim: #525252;--accent: #d4a27f;--accent-hover: #e8b898;--badge-text: #141414;--glass-bg: rgba(20, 20, 20, .85);--glass-border: rgba(255, 255, 255, .08);--chip-bg: rgba(42, 42, 42, .7);--chip-bg-active: rgba(42, 42, 42, .9);--chip-bg-hover: rgba(50, 50, 50, .9);--chip-border-active: rgba(255, 255, 255, .06);--shadow: rgba(0, 0, 0, .6);--shadow-strong: rgba(0, 0, 0, .5);--canvas-edge: rgba(255, 255, 255, .08);--canvas-edge-highlight: rgba(212, 162, 127, .5);--canvas-edge-dim: rgba(255, 255, 255, .03);--canvas-edge-label: rgba(255, 255, 255, .2);--canvas-edge-label-highlight: rgba(212, 162, 127, .7);--canvas-edge-label-dim: rgba(255, 255, 255, .05);--canvas-arrow: rgba(255, 255, 255, .12);--canvas-arrow-highlight: rgba(212, 162, 127, .5);--canvas-node-label: #a3a3a3;--canvas-node-label-dim: rgba(212, 212, 212, .2);--canvas-type-badge: rgba(115, 115, 115, .5);--canvas-type-badge-dim: rgba(115, 115, 115, .15);--canvas-selection-border: #d4d4d4;--canvas-node-border: rgba(255, 255, 255, .15);--canvas-walk-edge: #e8d5c4}[data-theme=light]{--bg: #f5f5f4;--bg-surface: #fafaf9;--bg-hover: #f0efee;--bg-active: #e7e5e4;--bg-elevated: #f0efee;--bg-inset: #e7e5e4;--border: #d6d3d1;--text: #292524;--text-strong: #1c1917;--text-muted: #78716c;--text-dim: #a8a29e;--accent: #c17856;--accent-hover: #b07a5e;--badge-text: #fafaf9;--glass-bg: rgba(250, 250, 249, .85);--glass-border: rgba(0, 0, 0, .08);--chip-bg: rgba(214, 211, 209, .5);--chip-bg-active: rgba(214, 211, 209, .8);--chip-bg-hover: rgba(200, 197, 195, .8);--chip-border-active: rgba(0, 0, 0, .08);--shadow: rgba(0, 0, 0, .1);--shadow-strong: rgba(0, 0, 0, .15);--canvas-edge: rgba(0, 0, 0, .1);--canvas-edge-highlight: rgba(193, 120, 86, .6);--canvas-edge-dim: rgba(0, 0, 0, .03);--canvas-edge-label: rgba(0, 0, 0, .25);--canvas-edge-label-highlight: rgba(193, 120, 86, .8);--canvas-edge-label-dim: rgba(0, 0, 0, .06);--canvas-arrow: rgba(0, 0, 0, .15);--canvas-arrow-highlight: rgba(193, 120, 86, .6);--canvas-node-label: #57534e;--canvas-node-label-dim: rgba(87, 83, 78, .2);--canvas-type-badge: rgba(87, 83, 78, .5);--canvas-type-badge-dim: rgba(87, 83, 78, .15);--canvas-selection-border: #292524;--canvas-node-border: rgba(0, 0, 0, .1);--canvas-walk-edge: #1a1a1a}body{font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--text);overflow:hidden}#app{display:flex;height:100vh;width:100vw}#sidebar{width:280px;min-width:280px;background:var(--bg-surface);border-right:1px solid var(--border);display:flex;flex-direction:column;padding:16px;overflow-y:auto}#sidebar.sidebar-collapsed{width:0;min-width:0;padding:0;overflow:hidden;border-right:none}.sidebar-heading-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}.sidebar-heading-row h2{margin-bottom:0}.sidebar-collapse-btn{background:none;border:1px solid var(--border);border-radius:6px;color:var(--text-dim);cursor:pointer;padding:4px 6px;line-height:1;transition:color .15s,border-color .15s}.sidebar-collapse-btn:hover{color:var(--text);border-color:var(--text-muted)}#sidebar h2{font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:14px}#sidebar input{width:100%;padding:8px 12px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--text);font-size:13px;outline:none;margin-bottom:12px}#sidebar input:focus{border-color:var(--accent)}#sidebar input::placeholder{color:var(--text-dim)}#ontology-list{list-style:none;display:flex;flex-direction:column;gap:2px}.ontology-item{padding:10px 12px;border-radius:6px;cursor:pointer;transition:background .15s}.ontology-item:hover{background:var(--bg-hover)}.ontology-item.active{background:var(--bg-active)}.ontology-item .name{display:block;font-size:13px;font-weight:500;color:var(--text)}.ontology-item .stats{display:block;font-size:11px;color:var(--text-dim);margin-top:2px}.sidebar-edit-btn{position:absolute;right:8px;top:10px;background:none;border:none;color:var(--text-dim);font-size:11px;cursor:pointer;opacity:0;transition:opacity .1s}.ontology-item{position:relative}.ontology-item:hover .sidebar-edit-btn{opacity:.7}.sidebar-section-heading{font-size:10px;font-weight:600;color:var(--text-dim);letter-spacing:.08em;margin:16px 12px 6px}.remote-list{list-style:none;padding:0;margin:0}.ontology-item-remote .name{display:inline}.remote-name-row{display:flex;align-items:center;gap:8px}.remote-badge{font-size:9px;font-weight:600;color:var(--text-dim);background:var(--bg-hover);padding:1px 6px;border-radius:4px;text-transform:uppercase;letter-spacing:.04em;border:1px solid var(--border)}.remote-source{display:block;font-size:10px;color:var(--text-dim);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sidebar-edit-btn:hover{opacity:1!important;color:var(--text)}.sidebar-rename-input{background:transparent;border:none;border-bottom:1px solid var(--accent);color:var(--text);font:inherit;font-size:13px;font-weight:500;outline:none;width:100%;padding:0}.sidebar-branch{font-size:10px;color:var(--accent);opacity:.7;display:block;margin-top:2px}.sidebar-branch:hover{opacity:1}.sidebar-lock-badge{font-size:10px;color:#c08c00;display:none;margin-top:2px}.sidebar-lock-badge.active{display:block}.sidebar-lock-badge.active:before{content:"● ";color:#c08c00}.branch-picker{background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;padding:4px;margin-top:4px;box-shadow:0 4px 16px var(--shadow);z-index:50}.branch-picker-item{display:flex;align-items:center;justify-content:space-between;padding:6px 8px;font-size:12px;color:var(--text);border-radius:4px;cursor:pointer}.branch-picker-item:hover{background:var(--bg-hover)}.branch-picker-active{color:var(--accent);font-weight:600;cursor:default}.branch-picker-active:hover{background:none}.branch-picker-delete{background:none;border:none;color:var(--text-dim);cursor:pointer;font-size:14px;padding:0 4px}.sidebar-snippets{margin-top:4px;padding-left:8px}.sidebar-snippet{display:flex;align-items:center;justify-content:space-between;padding:2px 4px;border-radius:4px;cursor:pointer}.sidebar-snippet:hover{background:var(--bg-hover)}.sidebar-snippet-label{font-size:10px;color:var(--text-dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sidebar-snippet-delete{background:none;border:none;color:var(--text-dim);font-size:12px;cursor:pointer;padding:0 2px;opacity:0}.sidebar-snippet:hover .sidebar-snippet-delete{opacity:1}.branch-picker-delete:hover{color:var(--danger, #e55)}.branch-picker-create{color:var(--accent);font-size:11px;border-top:1px solid var(--border);margin-top:4px;padding-top:8px}.sidebar-footer{margin-top:auto;padding-top:16px;border-top:1px solid var(--border);text-align:center}.sidebar-footer a{display:block;font-size:12px;font-weight:500;color:var(--accent);text-decoration:none;margin-bottom:4px}.sidebar-footer a:hover{color:var(--accent-hover)}.sidebar-footer span{display:block;font-size:10px;color:var(--text-dim)}.sidebar-version{font-size:9px;color:var(--text-dim);opacity:.5;margin-top:4px}.canvas-top-bar{position:absolute;top:16px;left:16px;right:16px;z-index:30;display:flex;justify-content:space-between;align-items:flex-start;pointer-events:none}.canvas-top-left,.canvas-top-center,.canvas-top-right{pointer-events:auto;display:flex;align-items:center;gap:4px}.canvas-top-left{flex-shrink:0;min-width:var(--tools-width, 264px)}.canvas-top-center{flex:1;justify-content:center}.focus-indicator{display:flex;align-items:center;gap:2px;background:var(--bg-surface);border:1px solid rgba(212,162,127,.4);border-radius:8px;padding:4px 6px 4px 10px;box-shadow:0 2px 8px var(--shadow)}.focus-indicator-label{font-size:11px;color:var(--accent);font-weight:500;white-space:nowrap;margin-right:4px}.focus-indicator-hops{font-size:11px;color:var(--text-muted);font-family:monospace;min-width:12px;text-align:center}.focus-indicator-btn{background:none;border:none;color:var(--text-muted);font-size:14px;cursor:pointer;padding:2px 4px;line-height:1;border-radius:4px;transition:color .15s,background .15s}.focus-indicator-btn:hover:not(:disabled){color:var(--text);background:var(--bg-hover)}.focus-indicator-btn:disabled{color:var(--text-dim);cursor:default;opacity:.3}.focus-indicator-exit{font-size:16px;margin-left:2px}.focus-indicator-exit:hover{color:#ef4444!important}.info-focus-btn{font-size:14px}.theme-toggle{background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;color:var(--text-muted);font-size:18px;cursor:pointer;padding:6px 10px;line-height:1;transition:color .15s,border-color .15s,background .15s;box-shadow:0 2px 8px var(--shadow)}.theme-toggle:hover{color:var(--text);border-color:var(--text-muted);background:var(--bg-hover)}.zoom-controls{display:flex;gap:4px}.zoom-btn{background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;color:var(--text-muted);font-size:18px;cursor:pointer;padding:6px 10px;line-height:1;transition:color .15s,border-color .15s,background .15s;box-shadow:0 2px 8px var(--shadow)}.zoom-btn:hover{color:var(--text);border-color:var(--text-muted);background:var(--bg-hover)}.node-tooltip{position:absolute;pointer-events:none;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:12px;white-space:nowrap;z-index:20;box-shadow:0 2px 8px #00000026;opacity:.95}#canvas-container{flex:1;position:relative;overflow:hidden;touch-action:none}#graph-canvas{position:absolute;top:0;left:0;touch-action:none;width:100%;height:100%;cursor:grab}#graph-canvas:active{cursor:grabbing}.search-overlay{position:relative;display:flex;flex-direction:column;align-items:center;gap:8px;max-height:calc(100vh - 48px);pointer-events:none}.search-overlay>*{pointer-events:auto}.search-overlay.hidden{display:none}.search-input-wrap{position:relative;display:flex;align-items:center;gap:6px;width:380px;max-width:calc(100vw - 340px)}.search-input{flex:1;min-width:0;padding:10px 36px 10px 16px;border:1px solid var(--glass-border);border-radius:10px;background:var(--glass-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);color:var(--text);font-size:14px;outline:none;transition:border-color .15s,box-shadow .15s}.search-input:focus{border-color:#d4a27f66;box-shadow:0 0 0 3px #d4a27f1a}.search-input::placeholder{color:var(--text-dim)}.search-kbd{position:absolute;right:10px;top:50%;transform:translateY(-50%);padding:2px 7px;border:1px solid var(--border);border-radius:4px;background:var(--bg-surface);color:var(--text-dim);font-size:11px;font-family:monospace;pointer-events:none}.search-kbd.hidden{display:none}.search-results{list-style:none;width:380px;max-width:calc(100vw - 340px);background:var(--glass-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid var(--border);border-radius:10px;overflow:hidden;box-shadow:0 8px 32px var(--shadow-strong)}.search-results.hidden{display:none}.search-result-item{display:flex;align-items:center;gap:8px;padding:8px 14px;cursor:pointer;transition:background .1s}.search-result-item:hover,.search-result-active{background:var(--bg-hover)}.search-result-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.search-result-label{font-size:13px;color:var(--text);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.search-result-type{font-size:11px;color:var(--text-dim);flex-shrink:0}.chip-toggle{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:1px solid var(--glass-border);border-radius:10px;background:var(--glass-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);color:var(--text-dim);cursor:pointer;transition:border-color .15s,color .15s;pointer-events:auto}.chip-toggle:hover{border-color:#d4a27f4d;color:var(--text)}.chip-toggle.active{border-color:#d4a27f66;color:var(--accent)}.type-chips{display:flex;flex-wrap:wrap;gap:4px;justify-content:center;max-width:500px;max-height:200px;overflow-y:auto;padding:4px;border-radius:10px}.type-chips.hidden{display:none}.type-chip{display:flex;align-items:center;gap:4px;padding:3px 10px;border:1px solid transparent;border-radius:12px;background:var(--chip-bg);color:var(--text-dim);font-size:11px;cursor:pointer;transition:all .15s;white-space:nowrap}.type-chip.active{background:var(--chip-bg-active);color:var(--text-muted);border-color:var(--chip-border-active)}.type-chip:hover{background:var(--chip-bg-hover)}.type-chip-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}.type-chip:not(.active) .type-chip-dot{opacity:.3}.info-panel{position:absolute;top:56px;right:16px;bottom:16px;width:360px;background:var(--bg-surface);border:1px solid var(--border);border-radius:10px;overflow:hidden;display:flex;flex-direction:column;padding:0;z-index:10;box-shadow:0 8px 32px var(--shadow);transition:top .25s ease,right .25s ease,bottom .25s ease,left .25s ease,width .25s ease,max-height .25s ease,border-radius .25s ease}.info-panel-header{flex-shrink:0;padding:20px 20px 12px;position:relative}.info-panel-body{flex:1;overflow-y:auto;min-height:0;padding:0 20px 20px}.info-panel.hidden{display:none}.info-panel.info-panel-maximized{top:0;right:0;bottom:0;left:0;width:auto;max-height:none;border-radius:0;z-index:40}.info-panel-toolbar{position:absolute;top:12px;right:14px;display:flex;align-items:center;gap:2px;z-index:1}.info-toolbar-btn{background:none;border:none;color:var(--text-muted);font-size:16px;cursor:pointer;padding:4px 6px;line-height:1;border-radius:4px;transition:color .15s,background .15s}.info-toolbar-btn:hover:not(:disabled){color:var(--text);background:var(--bg-hover)}.info-toolbar-btn:disabled{color:var(--text-dim);cursor:default;opacity:.3}.info-close-btn{font-size:20px}.info-connection-link{cursor:pointer;transition:background .15s}.info-connection-link:hover{background:var(--bg-active)}.info-connection-link .info-target{color:var(--accent);text-decoration:underline;text-decoration-color:transparent;transition:text-decoration-color .15s}.info-connection-link:hover .info-target{text-decoration-color:var(--accent)}.info-header{margin-bottom:16px}.info-type-badge{display:inline-block;padding:3px 10px;border-radius:12px;font-size:11px;font-weight:600;color:var(--badge-text);margin-bottom:8px}.info-badge-row{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px}.info-empty-message{font-size:12px;color:var(--text-dim)}.share-list-message{font-size:13px;color:var(--text-dim);text-align:center;padding:12px}.info-label{font-size:18px;font-weight:600;color:var(--text-strong);margin-bottom:4px;word-break:break-word}.info-id{display:block;font-size:11px;color:var(--text-dim);font-family:monospace}.info-section{margin-bottom:16px}.info-section-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:8px;padding-bottom:4px;border-bottom:1px solid var(--border)}.info-props{display:grid;grid-template-columns:auto 1fr;gap:4px 12px}.info-props dt{font-size:12px;color:var(--text-muted);padding-top:2px}.info-props dd{font-size:12px;color:var(--text);word-break:break-word;display:flex;align-items:center;gap:4px}.info-value{white-space:pre-wrap}.info-array{display:flex;flex-wrap:wrap;gap:4px}.info-tag{display:inline-block;padding:2px 8px;background:var(--bg-hover);border-radius:4px;font-size:11px;color:var(--text-muted)}.info-json{font-size:11px;font-family:monospace;color:var(--text-muted);background:var(--bg-inset);padding:6px 8px;border-radius:4px;overflow-x:auto;white-space:pre}.info-connections{list-style:none;display:flex;flex-direction:column;gap:6px}.info-connection{display:flex;align-items:center;gap:6px;padding:6px 8px;background:var(--bg-elevated);border-radius:6px;font-size:12px;flex-wrap:wrap}.info-connection-active{outline:1.5px solid var(--accent);background:var(--bg-hover)}.info-target-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.info-arrow{color:var(--text-dim);font-size:14px;flex-shrink:0}.info-edge-type{color:var(--text-muted);font-size:11px;font-weight:500}.info-target{color:var(--text);font-weight:500}.info-edge-props{width:100%;padding-top:4px;padding-left:20px}.info-edge-prop{display:block;font-size:11px;color:var(--text-dim)}.info-editable{cursor:default;position:relative}.info-inline-edit{background:none;border:none;color:var(--badge-text);opacity:0;font-size:10px;cursor:pointer;margin-left:4px;transition:opacity .15s}.info-editable:hover .info-inline-edit{opacity:.8}.info-inline-edit:hover{opacity:1!important}.info-edit-inline-input{background:transparent;border:none;border-bottom:1px solid var(--accent);color:var(--badge-text);font:inherit;font-size:inherit;outline:none;width:100%;padding:0}.info-edit-input{background:var(--bg-inset);border:1px solid var(--border);border-radius:4px;padding:3px 6px;font-size:12px;font-family:inherit;color:var(--text);flex:1;min-width:0;resize:vertical;overflow:hidden;line-height:1.4;max-height:300px}.info-edit-input:focus{outline:none;border-color:var(--accent)}.info-delete-prop{background:none;border:none;color:var(--text-dim);font-size:14px;cursor:pointer;padding:0 2px;flex-shrink:0;opacity:0;transition:opacity .1s,color .1s}.info-props dd:hover .info-delete-prop{opacity:1}.info-delete-prop:hover{color:#ef4444}.info-add-btn{background:none;border:1px dashed var(--border);border-radius:4px;padding:6px 10px;font-size:12px;color:var(--text-dim);cursor:pointer;width:100%;margin-top:8px;transition:border-color .15s,color .15s}.info-add-btn:hover{border-color:var(--accent);color:var(--text)}.info-add-row{display:flex;gap:4px;margin-top:6px}.info-add-save{background:var(--accent);border:none;border-radius:4px;padding:3px 10px;font-size:12px;color:var(--badge-text);cursor:pointer;flex-shrink:0}.info-add-save:hover{background:var(--accent-hover)}.info-delete-edge{background:none;border:none;color:var(--text-dim);font-size:14px;cursor:pointer;margin-left:auto;padding:0 2px;opacity:0;transition:opacity .1s,color .1s}.info-connection:hover .info-delete-edge{opacity:1}.info-delete-edge:hover{color:#ef4444}.info-danger{margin-top:8px;padding-top:12px;border-top:1px solid var(--border)}.info-delete-node{background:none;border:1px solid rgba(239,68,68,.3);border-radius:6px;padding:6px 12px;font-size:12px;color:#ef4444;cursor:pointer;width:100%;transition:background .15s}.info-delete-node:hover{background:#ef44441a}.tools-pane-toggle{background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;color:var(--text-muted);font-size:18px;cursor:pointer;padding:6px 10px;line-height:1;transition:color .15s,border-color .15s,background .15s;box-shadow:0 2px 8px var(--shadow)}.tools-pane-toggle.hidden{display:none}.tools-pane-toggle:hover{color:var(--text);border-color:var(--text-muted);background:var(--bg-hover)}.tools-pane-toggle.active{color:var(--accent);border-color:#d4a27f66}.tools-pane-content{position:absolute;top:56px;left:16px;bottom:16px;z-index:20;width:var(--tools-width, 264px);box-sizing:border-box;overflow:hidden;display:flex;flex-direction:column;background:var(--bg-surface);border:1px solid var(--border);border-radius:10px;padding:12px;box-shadow:0 8px 32px var(--shadow)}.tools-pane-content.hidden{display:none}.tools-pane-section{margin-bottom:12px}.tools-pane-section:last-child{margin-bottom:0}.tools-pane-heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-dim);margin-bottom:6px}.tools-pane-row{display:flex;align-items:center;gap:6px;padding:3px 0;font-size:12px}.tools-pane-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}.tools-pane-name{flex:1;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tools-pane-count{color:var(--text-dim);font-size:11px;flex-shrink:0}.tools-pane-summary{display:flex;align-items:center;gap:4px;font-size:11px;color:var(--text-muted);padding-bottom:10px;margin-bottom:10px;border-bottom:1px solid var(--border)}.tools-pane-sep{color:var(--text-dim)}.tools-pane-token-card{padding:8px 10px;margin-bottom:10px;border:1px solid var(--border);border-radius:6px;background:var(--bg-hover);font-size:11px}.token-card-label{font-weight:600;color:var(--text);margin-bottom:4px}.token-card-stat{color:var(--text-muted);margin-bottom:4px}.token-card-bar{height:4px;background:var(--border);border-radius:2px;margin:6px 0;overflow:hidden}.token-card-bar-fill{height:100%;background:var(--accent);border-radius:2px;transition:width .3s ease}.tools-pane-clickable{cursor:pointer;border-radius:4px;padding:3px 4px;margin:0 -4px;transition:background .1s}.tools-pane-clickable:hover{background:var(--bg-hover)}.tools-pane-clickable.active{background:var(--bg-hover);outline:1px solid var(--border)}.tools-pane-badge{font-size:9px;color:var(--accent);flex-shrink:0;opacity:.8}.tools-pane-issue .tools-pane-name{color:var(--text-muted)}.tools-pane-more{font-size:10px;color:var(--text-dim);padding:4px 0 0}.tools-pane-edit{background:none;border:none;color:var(--text-dim);font-size:11px;cursor:pointer;padding:0 2px;opacity:0;transition:opacity .1s,color .1s;flex-shrink:0}.tools-pane-row:hover .tools-pane-edit{opacity:1}.tools-pane-edit:hover{color:var(--accent)}.tools-pane-actions{display:flex;gap:6px;padding-top:4px}.tools-pane-action-btn{background:none;border:1px solid var(--border);color:var(--text-muted);font-size:10px;padding:2px 8px;border-radius:3px;cursor:pointer;transition:color .1s,border-color .1s}.tools-pane-action-btn:hover{color:var(--accent);border-color:var(--accent)}.tools-pane-focus-toggle{opacity:.4;font-size:11px}.tools-pane-focus-active{opacity:1!important;color:var(--accent)!important}.tools-pane-focus-clear{margin-top:4px;border-top:1px solid var(--border);padding-top:6px}.tools-pane-editing{background:none!important}.tools-pane-inline-input{width:100%;background:var(--bg);border:1px solid var(--accent);border-radius:4px;color:var(--text);font-size:12px;padding:2px 6px;outline:none}.tools-pane-slider-row{display:flex;align-items:center;gap:6px;padding:4px 0}.tools-pane-slider-label{font-size:11px;color:var(--text-muted);white-space:nowrap;min-width:56px}.tools-pane-slider{flex:1;min-width:0;height:4px;accent-color:var(--accent);cursor:pointer}.tools-pane-slider-value{font-size:10px;color:var(--text-dim);min-width:28px;text-align:right;font-family:monospace}.tools-pane-checkbox{width:14px;height:14px;accent-color:var(--accent);cursor:pointer;flex-shrink:0}.tools-pane-export-row{display:flex;gap:4px;margin-top:6px}.tools-pane-export-btn{flex:1;padding:4px 8px;font-size:11px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text-muted);cursor:pointer;transition:color .15s,border-color .15s}.tools-pane-export-btn:hover{color:var(--text);border-color:var(--text-muted)}.tools-pane-empty{font-size:11px;color:var(--text-dim);text-align:center;padding:8px 0}.tools-pane-tabs{display:flex;gap:2px;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}.tools-pane-tab{flex:1;padding:4px 0;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.03em;background:none;border:1px solid transparent;border-radius:5px;color:var(--text-dim);cursor:pointer;transition:color .15s,background .15s,border-color .15s}.tools-pane-tab:hover{color:var(--text-muted);background:var(--bg-hover)}.tools-pane-tab-active{color:var(--text);background:var(--bg-hover);border-color:var(--border)}.tools-pane-tab-content{flex:1;overflow-y:auto;overflow-x:hidden;min-height:0}.tools-pane-search{width:100%;padding:4px 8px;font-size:11px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);outline:none;margin-bottom:8px;box-sizing:border-box}.tools-pane-search:focus{border-color:var(--accent)}.tools-pane-search::placeholder{color:var(--text-dim)}.tools-pane-empty-msg{font-size:11px;color:var(--text-dim);text-align:center;padding:16px 0}.empty-state{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;z-index:5;pointer-events:none;overflow:hidden}.empty-state.hidden{display:none}.empty-state-bg{position:absolute;top:0;right:0;bottom:0;left:0;overflow:hidden}.empty-state-circle{position:absolute;border-radius:50%;background:var(--accent);opacity:.07}.empty-state-circle.c1{width:80px;height:80px;left:20%;top:15%;animation:float-circle 8s ease-in-out infinite}.empty-state-circle.c2{width:50px;height:50px;right:25%;top:25%;animation:float-circle 6s ease-in-out 1s infinite}.empty-state-circle.c3{width:65px;height:65px;left:55%;bottom:20%;animation:float-circle 7s ease-in-out 2s infinite}.empty-state-circle.c4{width:40px;height:40px;left:15%;bottom:30%;animation:float-circle 9s ease-in-out .5s infinite}.empty-state-circle.c5{width:55px;height:55px;right:15%;bottom:35%;animation:float-circle 7.5s ease-in-out 1.5s infinite}.empty-state-lines{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;color:var(--text-dim);animation:float-circle 10s ease-in-out infinite}@keyframes float-circle{0%,to{transform:translateY(0)}50%{transform:translateY(-12px)}}.empty-state-content{text-align:center;max-width:420px;padding:40px 24px;position:relative;z-index:1}.empty-state-icon{color:var(--text-dim);margin-bottom:16px}.empty-state-title{font-size:18px;font-weight:600;color:var(--text);margin-bottom:8px}.empty-state-desc{font-size:13px;color:var(--text-muted);line-height:1.5;margin-bottom:20px}.empty-state-setup{background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;padding:12px 16px;margin-bottom:16px}.empty-state-label{font-size:11px;color:var(--text-dim);margin-bottom:8px}.empty-state-code{display:block;font-size:12px;color:var(--accent);font-family:monospace;word-break:break-all}.empty-state-hint{font-size:11px;color:var(--text-dim)}.empty-state-hint kbd{padding:1px 5px;border:1px solid var(--border);border-radius:3px;background:var(--bg-surface);font-family:monospace;font-size:11px}.shortcuts-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:100}.shortcuts-overlay.hidden{display:none}.shortcuts-modal{background:var(--bg-surface);border:1px solid var(--border);border-radius:12px;padding:24px;min-width:300px;max-width:400px;box-shadow:0 16px 48px var(--shadow);position:relative}.shortcuts-close{position:absolute;top:12px;right:14px;background:none;border:none;color:var(--text-dim);font-size:20px;cursor:pointer;padding:0 4px;line-height:1}.shortcuts-close:hover{color:var(--text)}.shortcuts-title{font-size:15px;font-weight:600;color:var(--text);margin-bottom:16px}.shortcuts-list{display:flex;flex-direction:column;gap:8px}.shortcuts-row{display:flex;align-items:center;justify-content:space-between;gap:12px}.shortcuts-keys{display:flex;align-items:center;gap:4px}.shortcuts-keys kbd{padding:2px 7px;border:1px solid var(--border);border-radius:4px;background:var(--bg);color:var(--text);font-size:11px;font-family:monospace}.shortcuts-or{font-size:10px;color:var(--text-dim)}.shortcuts-desc{font-size:12px;color:var(--text-muted)}@media(max-width:768px){#app{flex-direction:column}#sidebar{width:100%;min-width:0;max-height:35vh;border-right:none;border-bottom:1px solid var(--border)}.info-panel{top:auto;bottom:72px;right:8px;left:8px;width:auto;max-height:calc(100% - 200px);overflow-y:auto}.info-panel.info-panel-maximized{bottom:0;left:0;right:0}.canvas-top-bar{top:8px;left:8px;right:8px}.tools-pane-content{top:48px;left:8px;bottom:80px;width:160px;max-width:calc(100vw - 24px)}.tools-pane-edit{opacity:.6}}.bp-dialog-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.bp-dialog{background:var(--bg-surface);border:1px solid var(--border);border-radius:12px;padding:20px;min-width:280px;max-width:400px;box-shadow:0 16px 48px #0000004d}.bp-dialog-title{font-size:14px;font-weight:600;color:var(--text);margin-bottom:12px}.bp-dialog-message{font-size:13px;color:var(--text-muted);margin-bottom:16px;line-height:1.5}.bp-dialog-input{width:100%;padding:8px 12px;font-size:13px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);outline:none;margin-bottom:16px}.bp-dialog-input:focus{border-color:var(--accent)}.bp-dialog-buttons{display:flex;justify-content:flex-end;gap:8px}.bp-dialog-btn{padding:6px 16px;font-size:12px;border-radius:6px;border:1px solid var(--border);background:var(--bg);color:var(--text-muted);cursor:pointer;transition:all .15s}.bp-dialog-btn:hover{background:var(--bg-hover);color:var(--text)}.bp-dialog-btn-accent{background:var(--accent);color:#fff;border-color:var(--accent)}.bp-dialog-btn-accent:hover{opacity:.9;color:#fff;background:var(--accent)}.bp-dialog-btn-danger{background:#e55;color:#fff;border-color:#e55}.bp-dialog-btn-danger:hover{opacity:.9;color:#fff;background:#e55}.bp-toast{position:fixed;bottom:24px;left:50%;transform:translate(-50%) translateY(20px);background:var(--bg-surface);border:1px solid var(--border);color:var(--text);padding:8px 20px;border-radius:8px;font-size:12px;z-index:1001;opacity:0;transition:opacity .3s,transform .3s;box-shadow:0 4px 16px var(--shadow)}.bp-toast-visible{opacity:1;transform:translate(-50%) translateY(0)}.context-menu{position:absolute;z-index:100;background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;padding:4px;min-width:180px;box-shadow:0 8px 24px var(--shadow)}.context-menu-item{padding:6px 12px;font-size:12px;color:var(--text);border-radius:4px;cursor:pointer;white-space:nowrap}.context-menu-item:hover{background:var(--bg-hover)}.context-menu-separator{height:1px;background:var(--border);margin:4px 8px}.path-bar{position:absolute;bottom:16px;left:50%;transform:translate(-50%);z-index:25;display:flex;align-items:center;gap:4px;background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;padding:6px 12px;box-shadow:0 4px 16px var(--shadow);max-width:80%;overflow-x:auto}.path-bar.hidden{display:none}.path-bar-node{font-size:11px;color:var(--text);padding:2px 8px;border-radius:4px;cursor:pointer;white-space:nowrap;flex-shrink:0}.path-bar-node:hover{background:var(--bg-hover)}.path-bar-edge{font-size:9px;color:var(--text-dim);white-space:nowrap;flex-shrink:0}.path-bar-close{background:none;border:none;color:var(--text-dim);font-size:14px;cursor:pointer;padding:0 4px;margin-left:8px;flex-shrink:0}.path-bar-close:hover{color:var(--text)}.walk-trail-edge{font-size:9px;color:var(--text-dim);padding:1px 0 1px 24px;opacity:.7}.walk-indicator{font-size:10px;color:var(--accent);padding:2px 8px;border:1px solid rgba(212,162,127,.4);border-radius:4px;cursor:pointer;opacity:.4}.walk-indicator.active{opacity:1;background:#d4a27f26;animation:walk-strobe 2s ease-in-out infinite}@keyframes walk-strobe{0%,to{opacity:.6;border-color:#d4a27f4d}50%{opacity:1;border-color:#d4a27fcc}}
|