backpack-viewer 0.3.1 → 0.5.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/README.md +21 -1
- package/bin/serve.js +145 -6
- package/dist/app/assets/index-D7P8kwxI.js +34 -0
- package/dist/app/assets/index-dtYoeajN.css +1 -0
- package/dist/app/index.html +2 -2
- package/dist/config.js +1 -0
- package/dist/default-config.json +4 -0
- package/dist/dialog.d.ts +13 -0
- package/dist/dialog.js +116 -0
- package/dist/main.js +57 -0
- package/dist/sidebar.d.ts +11 -0
- package/dist/sidebar.js +133 -1
- package/dist/style.css +188 -0
- package/package.json +2 -2
- package/dist/app/assets/index-CvkozBSE.css +0 -1
- package/dist/app/assets/index-j3SowZae.js +0 -34
package/README.md
CHANGED
|
@@ -128,11 +128,31 @@ Bindings are strings with optional modifier prefixes separated by `+`:
|
|
|
128
128
|
- With modifiers: `"ctrl+z"`, `"ctrl+shift+z"`, `"alt+s"`
|
|
129
129
|
- `ctrl` and `cmd`/`meta` are treated as equivalent (works on both Mac and Linux)
|
|
130
130
|
|
|
131
|
+
## Network binding
|
|
132
|
+
|
|
133
|
+
The viewer binds to **loopback only (`127.0.0.1`) by default** — its API exposes read/write access to your learning graphs, so it must never be reachable from other machines on your network without explicit opt-in. To change the bind host or port, edit `~/.config/backpack/viewer.json`:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"server": {
|
|
138
|
+
"host": "127.0.0.1",
|
|
139
|
+
"port": 5173
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Setting `host` to anything other than `127.0.0.1`, `localhost`, or `::1` prints a startup warning because the API becomes reachable from other machines on the network. Only do this for devcontainer / VM scenarios where you understand the trust boundary, and consider putting a reverse proxy with auth in front of it.
|
|
145
|
+
|
|
146
|
+
Environment variables override the config file:
|
|
147
|
+
- `BACKPACK_VIEWER_HOST` — bind host (use `0.0.0.0` for all interfaces)
|
|
148
|
+
- `PORT` — bind port
|
|
149
|
+
|
|
131
150
|
## Reference
|
|
132
151
|
|
|
133
152
|
| Variable | Effect |
|
|
134
153
|
|---|---|
|
|
135
|
-
| `PORT` | Override the default port (default: `5173`) |
|
|
154
|
+
| `PORT` | Override the default port (default: `5173` or `server.port` in config) |
|
|
155
|
+
| `BACKPACK_VIEWER_HOST` | Override the bind host (default: `127.0.0.1` or `server.host` in config) |
|
|
136
156
|
| `XDG_CONFIG_HOME` | Override config location (default: `~/.config`) |
|
|
137
157
|
| `XDG_DATA_HOME` | Override data location (default: `~/.local/share`) |
|
|
138
158
|
| `BACKPACK_DIR` | Override both config and data directories |
|
package/bin/serve.js
CHANGED
|
@@ -8,17 +8,45 @@ import http from "node:http";
|
|
|
8
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
const root = path.resolve(__dirname, "..");
|
|
10
10
|
const distDir = path.resolve(root, "dist/app");
|
|
11
|
-
const port = parseInt(process.env.PORT || "5173", 10);
|
|
12
11
|
|
|
13
12
|
const hasDistBuild = fs.existsSync(path.join(distDir, "index.html"));
|
|
14
13
|
|
|
15
14
|
if (hasDistBuild) {
|
|
16
15
|
// --- Production: static file server + API (zero native deps) ---
|
|
17
|
-
const {
|
|
16
|
+
const {
|
|
17
|
+
JsonFileBackend,
|
|
18
|
+
dataDir,
|
|
19
|
+
RemoteRegistry,
|
|
20
|
+
listBackpacks,
|
|
21
|
+
getActiveBackpack,
|
|
22
|
+
setActiveBackpack,
|
|
23
|
+
registerBackpack,
|
|
24
|
+
unregisterBackpack,
|
|
25
|
+
} = await import("backpack-ontology");
|
|
18
26
|
const { loadViewerConfig } = await import("../dist/config.js");
|
|
19
27
|
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
// Resolve host + port from the viewer config, with env vars taking
|
|
29
|
+
// precedence for quick overrides. Default is 127.0.0.1 loopback —
|
|
30
|
+
// the viewer must never bind to all interfaces by default because
|
|
31
|
+
// its API exposes read/write access to the user's learning graphs.
|
|
32
|
+
const viewerConfigForServer = loadViewerConfig();
|
|
33
|
+
const configuredHost = viewerConfigForServer?.server?.host ?? "127.0.0.1";
|
|
34
|
+
const configuredPort = viewerConfigForServer?.server?.port ?? 5173;
|
|
35
|
+
const bindHost = process.env.BACKPACK_VIEWER_HOST ?? configuredHost;
|
|
36
|
+
const port = parseInt(process.env.PORT || String(configuredPort), 10);
|
|
37
|
+
|
|
38
|
+
// Storage points at the active backpack. Wrapped in a mutable
|
|
39
|
+
// holder so a `/api/backpacks/switch` POST can swap it out in place
|
|
40
|
+
// without restarting the whole server.
|
|
41
|
+
async function makeBackend() {
|
|
42
|
+
const entry = await getActiveBackpack();
|
|
43
|
+
const backend = new JsonFileBackend(undefined, {
|
|
44
|
+
graphsDirOverride: entry.path,
|
|
45
|
+
});
|
|
46
|
+
await backend.initialize();
|
|
47
|
+
return { backend, entry };
|
|
48
|
+
}
|
|
49
|
+
let { backend: storage, entry: activeEntry } = await makeBackend();
|
|
22
50
|
const remoteRegistry = new RemoteRegistry();
|
|
23
51
|
await remoteRegistry.initialize();
|
|
24
52
|
const viewerConfig = loadViewerConfig();
|
|
@@ -327,6 +355,100 @@ if (hasDistBuild) {
|
|
|
327
355
|
return;
|
|
328
356
|
}
|
|
329
357
|
|
|
358
|
+
// --- Backpacks (meta: list, active, switch) ---
|
|
359
|
+
if (url === "/api/backpacks" && req.method === "GET") {
|
|
360
|
+
try {
|
|
361
|
+
const list = await listBackpacks();
|
|
362
|
+
const active = await getActiveBackpack();
|
|
363
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
364
|
+
res.end(
|
|
365
|
+
JSON.stringify(
|
|
366
|
+
list.map((b) => ({ ...b, active: b.name === active.name })),
|
|
367
|
+
),
|
|
368
|
+
);
|
|
369
|
+
} catch (err) {
|
|
370
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
371
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
372
|
+
}
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (url === "/api/backpacks/active" && req.method === "GET") {
|
|
377
|
+
try {
|
|
378
|
+
const active = await getActiveBackpack();
|
|
379
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
380
|
+
res.end(JSON.stringify(active));
|
|
381
|
+
} catch (err) {
|
|
382
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
383
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
384
|
+
}
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (url === "/api/backpacks/switch" && req.method === "POST") {
|
|
389
|
+
let body = "";
|
|
390
|
+
req.on("data", (chunk) => { body += chunk.toString(); });
|
|
391
|
+
req.on("end", async () => {
|
|
392
|
+
try {
|
|
393
|
+
const { name } = JSON.parse(body);
|
|
394
|
+
await setActiveBackpack(name);
|
|
395
|
+
const swapped = await makeBackend();
|
|
396
|
+
storage = swapped.backend;
|
|
397
|
+
activeEntry = swapped.entry;
|
|
398
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
399
|
+
res.end(JSON.stringify({ ok: true, active: activeEntry }));
|
|
400
|
+
} catch (err) {
|
|
401
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
402
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (url === "/api/backpacks" && req.method === "POST") {
|
|
409
|
+
let body = "";
|
|
410
|
+
req.on("data", (chunk) => { body += chunk.toString(); });
|
|
411
|
+
req.on("end", async () => {
|
|
412
|
+
try {
|
|
413
|
+
const { name, path: p, activate } = JSON.parse(body);
|
|
414
|
+
const entry = await registerBackpack(name, p);
|
|
415
|
+
if (activate) {
|
|
416
|
+
await setActiveBackpack(name);
|
|
417
|
+
const swapped = await makeBackend();
|
|
418
|
+
storage = swapped.backend;
|
|
419
|
+
activeEntry = swapped.entry;
|
|
420
|
+
}
|
|
421
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
422
|
+
res.end(JSON.stringify({ ok: true, entry }));
|
|
423
|
+
} catch (err) {
|
|
424
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
425
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const backpackDeleteMatch = url.match(/^\/api\/backpacks\/(.+)$/);
|
|
432
|
+
if (backpackDeleteMatch && req.method === "DELETE") {
|
|
433
|
+
const name = decodeURIComponent(backpackDeleteMatch[1]);
|
|
434
|
+
try {
|
|
435
|
+
await unregisterBackpack(name);
|
|
436
|
+
// If we just removed the active one, the registry switched us;
|
|
437
|
+
// rebuild the backend to match.
|
|
438
|
+
if (activeEntry && activeEntry.name === name) {
|
|
439
|
+
const swapped = await makeBackend();
|
|
440
|
+
storage = swapped.backend;
|
|
441
|
+
activeEntry = swapped.entry;
|
|
442
|
+
}
|
|
443
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
444
|
+
res.end(JSON.stringify({ ok: true }));
|
|
445
|
+
} catch (err) {
|
|
446
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
447
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
448
|
+
}
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
330
452
|
// --- Lock heartbeat ---
|
|
331
453
|
if (url === "/api/locks" && req.method === "GET") {
|
|
332
454
|
// Batch endpoint: returns { graphName: lockInfo|null } for all graphs.
|
|
@@ -416,8 +538,25 @@ if (hasDistBuild) {
|
|
|
416
538
|
}
|
|
417
539
|
});
|
|
418
540
|
|
|
419
|
-
|
|
420
|
-
|
|
541
|
+
// Bind to whatever the config + env var resolved to. The default is
|
|
542
|
+
// 127.0.0.1 because the viewer API exposes read/write access to the
|
|
543
|
+
// user's learning graphs and must never be reachable from other
|
|
544
|
+
// machines on the same network by default. Users who really need to
|
|
545
|
+
// bind to another interface (e.g. for a devcontainer scenario) can
|
|
546
|
+
// set server.host in ~/.config/backpack/viewer.json or the
|
|
547
|
+
// BACKPACK_VIEWER_HOST env var, and will see a loud warning.
|
|
548
|
+
server.listen(port, bindHost, () => {
|
|
549
|
+
const displayHost = bindHost === "0.0.0.0" || bindHost === "::" ? "localhost" : bindHost;
|
|
550
|
+
console.log(` Backpack Viewer running at http://${displayHost}:${port}/`);
|
|
551
|
+
const isLoopback =
|
|
552
|
+
bindHost === "127.0.0.1" ||
|
|
553
|
+
bindHost === "localhost" ||
|
|
554
|
+
bindHost === "::1";
|
|
555
|
+
if (!isLoopback) {
|
|
556
|
+
console.warn(
|
|
557
|
+
` WARNING: viewer is bound to ${bindHost}, not loopback. The API exposes read/write access to your learning graphs — anyone on your network can reach it. Set server.host to "127.0.0.1" in ~/.config/backpack/viewer.json to restrict to localhost.`,
|
|
558
|
+
);
|
|
559
|
+
}
|
|
421
560
|
});
|
|
422
561
|
} else {
|
|
423
562
|
// --- Development: use Vite for HMR + TypeScript compilation ---
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
var En=Object.defineProperty;var wn=(t,e,s)=>e in t?En(t,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[e]=s;var mt=(t,e,s)=>wn(t,typeof e!="symbol"?e+"":e,s);(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))r(l);new MutationObserver(l=>{for(const y of l)if(y.type==="childList")for(const x of y.addedNodes)x.tagName==="LINK"&&x.rel==="modulepreload"&&r(x)}).observe(document,{childList:!0,subtree:!0});function s(l){const y={};return l.integrity&&(y.integrity=l.integrity),l.referrerPolicy&&(y.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?y.credentials="include":l.crossOrigin==="anonymous"?y.credentials="omit":y.credentials="same-origin",y}function r(l){if(l.ep)return;l.ep=!0;const y=s(l);fetch(l.href,y)}})();async function at(){const t=await fetch("/api/ontologies");return t.ok?t.json():[]}async function ht(t){const e=await fetch(`/api/ontologies/${encodeURIComponent(t)}`);if(!e.ok)throw new Error(`Failed to load ontology: ${t}`);return e.json()}async function kn(){const t=await fetch("/api/remotes");return t.ok?t.json():[]}async function Nn(t){const e=await fetch(`/api/remotes/${encodeURIComponent(t)}`);if(!e.ok)throw new Error(`Failed to load remote graph: ${t}`);return e.json()}async function wt(t,e){if(!(await fetch(`/api/ontologies/${encodeURIComponent(t)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).ok)throw new Error(`Failed to save ontology: ${t}`)}async function Sn(t,e){if(!(await fetch(`/api/ontologies/${encodeURIComponent(t)}/rename`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e})})).ok)throw new Error(`Failed to rename ontology: ${t}`)}async function Ln(t){const e=await fetch(`/api/graphs/${encodeURIComponent(t)}/branches`);return e.ok?e.json():[]}async function _t(t,e,s){const r=await fetch(`/api/graphs/${encodeURIComponent(t)}/branches`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,from:s})});if(!r.ok){const l=await r.json().catch(()=>({}));throw new Error(l.error||"Failed to create branch")}}async function Gt(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/branches/switch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e})});if(!s.ok){const r=await s.json().catch(()=>({}));throw new Error(r.error||"Failed to switch branch")}}async function In(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/branches/${encodeURIComponent(e)}`,{method:"DELETE"});if(!s.ok){const r=await s.json().catch(()=>({}));throw new Error(r.error||"Failed to delete branch")}}async function Mn(t){const e=await fetch(`/api/graphs/${encodeURIComponent(t)}/snapshots`);return e.ok?e.json():[]}async function Tn(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/snapshots`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({label:e})});if(!s.ok){const r=await s.json().catch(()=>({}));throw new Error(r.error||"Failed to create snapshot")}}async function An(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/rollback`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({version:e})});if(!s.ok){const r=await s.json().catch(()=>({}));throw new Error(r.error||"Failed to rollback")}}async function Bn(t){const e=await fetch(`/api/graphs/${encodeURIComponent(t)}/snippets`);return e.ok?e.json():[]}async function Vt(t,e,s,r,l){const y=await fetch(`/api/graphs/${encodeURIComponent(t)}/snippets`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({label:e,description:l,nodeIds:s,edgeIds:r})});if(!y.ok)throw new Error("Failed to save snippet");return(await y.json()).id}async function Pn(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/snippets/${encodeURIComponent(e)}`);if(!s.ok)throw new Error("Snippet not found");return s.json()}async function Rn(t,e){if(!(await fetch(`/api/graphs/${encodeURIComponent(t)}/snippets/${encodeURIComponent(e)}`,{method:"DELETE"})).ok)throw new Error("Failed to delete snippet")}const Jt="bp-dialog-overlay";function Bt(){var e;(e=document.querySelector(`.${Jt}`))==null||e.remove();const t=document.createElement("div");return t.className=Jt,document.body.appendChild(t),t}function Pt(t,e){const s=document.createElement("div");s.className="bp-dialog";const r=document.createElement("h4");return r.className="bp-dialog-title",r.textContent=e,s.appendChild(r),t.appendChild(s),t.addEventListener("click",l=>{l.target===t&&t.remove()}),s}function Rt(t,e){const s=document.createElement("div");s.className="bp-dialog-buttons";for(const r of e){const l=document.createElement("button");l.className="bp-dialog-btn",r.accent&&l.classList.add("bp-dialog-btn-accent"),r.danger&&l.classList.add("bp-dialog-btn-danger"),l.textContent=r.label,l.addEventListener("click",r.onClick),s.appendChild(l)}t.appendChild(s)}function hn(t,e){return new Promise(s=>{var x;const r=Bt(),l=Pt(r,t),y=document.createElement("p");y.className="bp-dialog-message",y.textContent=e,l.appendChild(y),Rt(l,[{label:"Cancel",onClick:()=>{r.remove(),s(!1)}},{label:"Confirm",accent:!0,onClick:()=>{r.remove(),s(!0)}}]),(x=l.querySelector(".bp-dialog-btn-accent"))==null||x.focus()})}function gt(t,e,s){return new Promise(r=>{const l=Bt(),y=Pt(l,t),x=document.createElement("input");x.type="text",x.className="bp-dialog-input",x.placeholder=e??"",x.value="",y.appendChild(x);const c=()=>{const o=x.value.trim();l.remove(),r(o||null)};x.addEventListener("keydown",o=>{o.key==="Enter"&&c(),o.key==="Escape"&&(l.remove(),r(null))}),Rt(y,[{label:"Cancel",onClick:()=>{l.remove(),r(null)}},{label:"OK",accent:!0,onClick:c}]),x.focus(),x.select()})}function Fn(){return new Promise(t=>{const e=Bt(),s=Pt(e,"Add Backpack"),r=document.createElement("p");r.className="bp-dialog-message",r.textContent="Enter the absolute path to a directory that should become a backpack. It will be shown in the sidebar using the last segment of the path as its display name.",s.appendChild(r);const l=document.createElement("label");l.className="bp-dialog-label",l.textContent="Path",s.appendChild(l);const y=document.createElement("div");y.className="bp-dialog-path-row",s.appendChild(y);const x=document.createElement("input");x.type="text",x.className="bp-dialog-input bp-dialog-path-input",x.placeholder="/Users/you/OneDrive/work",y.appendChild(x);const c=document.createElement("button");c.type="button",c.className="bp-dialog-btn bp-dialog-browse-btn",c.textContent="Browse...",y.appendChild(c),typeof window.showDirectoryPicker=="function"||(c.disabled=!0,c.title="Browser doesn't support native folder picker — paste the path manually"),c.addEventListener("click",async C=>{C.preventDefault();try{const j=await window.showDirectoryPicker({mode:"read"});P.textContent=`Picked "${j.name}" — paste the absolute path to it below.`,x.focus()}catch{}});const P=document.createElement("div");P.className="bp-dialog-picker-hint",s.appendChild(P);const i=document.createElement("div");i.className="bp-dialog-activate-row";const a=document.createElement("input");a.type="checkbox",a.id="bp-dialog-activate",a.checked=!0;const I=document.createElement("label");I.htmlFor="bp-dialog-activate",I.textContent="Switch to this backpack after registering",i.appendChild(a),i.appendChild(I),s.appendChild(i),x.addEventListener("dragover",C=>{C.preventDefault(),x.classList.add("bp-dialog-drag-over")}),x.addEventListener("dragleave",()=>{x.classList.remove("bp-dialog-drag-over")}),x.addEventListener("drop",C=>{var Q,W,ne;C.preventDefault(),x.classList.remove("bp-dialog-drag-over");const j=(Q=C.dataTransfer)==null?void 0:Q.items;if(!j||j.length===0)return;const U=(ne=(W=j[0]).webkitGetAsEntry)==null?void 0:ne.call(W);U!=null&&U.isDirectory&&(P.textContent=`Dropped "${U.name}" — paste the absolute path to it below.`)});const F=()=>{const C=x.value.trim();C&&(e.remove(),t({path:C,activate:a.checked}))};x.addEventListener("keydown",C=>{C.key==="Enter"&&F(),C.key==="Escape"&&(e.remove(),t(null))}),Rt(s,[{label:"Cancel",onClick:()=>{e.remove(),t(null)}},{label:"Register",accent:!0,onClick:F}]),x.focus()})}function Kt(t){return t>=1e3?`${(t/1e3).toFixed(1)}k tokens`:`${t} tokens`}function Zt(t,e){return t*50+e*25+50}function $n(t,e){const s=typeof e=="function"?{onSelect:e}:e,r=document.createElement("h2");r.textContent="Backpack Viewer";const l=document.createElement("input");l.type="text",l.placeholder="Filter...",l.id="filter";const y=document.createElement("ul");y.id="ontology-list";const x=document.createElement("h3");x.className="sidebar-section-heading",x.textContent="REMOTE GRAPHS",x.hidden=!0;const c=document.createElement("ul");c.id="remote-list",c.className="remote-list",c.hidden=!0;const o=document.createElement("div");o.className="sidebar-footer",o.innerHTML='<a href="mailto:support@backpackontology.com">support@backpackontology.com</a><span>Feedback & support</span><span class="sidebar-version">v0.5.0</span>';const P=document.createElement("button");P.className="sidebar-collapse-btn",P.title="Toggle sidebar (Tab)",P.innerHTML='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="11 17 6 12 11 7"/><polyline points="18 17 13 12 18 7"/></svg>';let i=!1;function a(){i=!i,t.classList.toggle("sidebar-collapsed",i),J.classList.toggle("hidden",!i)}P.addEventListener("click",a);const I=document.createElement("div");I.className="sidebar-heading-row",I.appendChild(r),I.appendChild(P),t.appendChild(I);const F=document.createElement("button");F.className="backpack-picker-pill",F.type="button",F.setAttribute("aria-haspopup","listbox"),F.setAttribute("aria-expanded","false");const C=document.createElement("span");C.className="backpack-picker-dot";const j=document.createElement("span");j.className="backpack-picker-name",j.textContent="...";const U=document.createElement("span");U.className="backpack-picker-caret",U.textContent="▾",F.appendChild(C),F.appendChild(j),F.appendChild(U);const Q=document.createElement("div");Q.className="backpack-picker-dropdown",Q.hidden=!0,Q.setAttribute("role","listbox");const W=document.createElement("div");W.className="backpack-picker-container",W.appendChild(F),W.appendChild(Q),t.appendChild(W);let ne=!1;function V(){ne=!1,Q.hidden=!0,F.setAttribute("aria-expanded","false")}function B(){ne=!0,Q.hidden=!1,F.setAttribute("aria-expanded","true")}F.addEventListener("click",_=>{_.stopPropagation(),ne?V():B()}),document.addEventListener("click",_=>{W.contains(_.target)||V()});let ee=[],K=null;function ce(){Q.replaceChildren();for(const D of ee){const w=document.createElement("button");w.className="backpack-picker-item",w.type="button",w.setAttribute("role","option"),D.active&&w.classList.add("active");const p=document.createElement("span");p.className="backpack-picker-item-dot",p.style.setProperty("--backpack-color",D.color);const m=document.createElement("span");m.className="backpack-picker-item-name",m.textContent=D.name;const g=document.createElement("span");g.className="backpack-picker-item-path",g.textContent=D.path,w.appendChild(p),w.appendChild(m),w.appendChild(g),w.addEventListener("click",v=>{v.stopPropagation(),V(),!D.active&&s.onBackpackSwitch&&s.onBackpackSwitch(D.name)}),Q.appendChild(w)}const _=document.createElement("div");_.className="backpack-picker-divider",Q.appendChild(_);const H=document.createElement("button");H.className="backpack-picker-item backpack-picker-add",H.type="button",H.textContent="+ Add new backpack…",H.addEventListener("click",async D=>{if(D.stopPropagation(),V(),!s.onBackpackRegister)return;const w=await Fn();w&&s.onBackpackRegister(w.path,w.activate)}),Q.appendChild(H)}const J=document.createElement("button");J.className="tools-pane-toggle hidden",J.title="Show sidebar (Tab)",J.innerHTML='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="13 7 18 12 13 17"/><polyline points="6 7 11 12 6 17"/></svg>',J.addEventListener("click",a),t.appendChild(l),t.appendChild(y),t.appendChild(x),t.appendChild(c),t.appendChild(o);let ie=[],te=[],ye="";return l.addEventListener("input",()=>{const _=l.value.toLowerCase();for(const H of ie){const D=H.dataset.name??"";H.style.display=D.includes(_)?"":"none"}for(const H of te){const D=H.dataset.name??"";H.style.display=D.includes(_)?"":"none"}}),{setBackpacks(_){ee=_.slice();const H=_.find(D=>D.active)??null;K=H,H&&(j.textContent=H.name,C.style.setProperty("--backpack-color",H.color),t.style.setProperty("--backpack-color",H.color)),ce()},setActiveBackpack(_){K=_,ee=ee.map(H=>({...H,active:H.name===_.name})),ee.some(H=>H.name===_.name)||ee.push({..._,active:!0}),j.textContent=_.name,C.style.setProperty("--backpack-color",_.color),t.style.setProperty("--backpack-color",_.color),ce()},getActiveBackpack(){return K},setSummaries(_){y.innerHTML="";const H=fetch("/api/locks").then(D=>D.json()).catch(()=>({}));ie=_.map(D=>{const w=document.createElement("li");w.className="ontology-item",w.dataset.name=D.name;const p=document.createElement("span");p.className="name",p.textContent=D.name;const m=document.createElement("span");m.className="stats";const g=Zt(D.nodeCount,D.edgeCount);m.textContent=`${D.nodeCount} nodes, ${D.edgeCount} edges · ~${Kt(g)}`;const v=document.createElement("span");v.className="sidebar-branch",v.dataset.graph=D.name;const N=document.createElement("span");if(N.className="sidebar-lock-badge",N.dataset.graph=D.name,H.then(u=>{if(!N.isConnected)return;const E=u[D.name];E&&typeof E=="object"&&E.author&&(N.textContent=`editing: ${E.author}`,N.title=`Last activity: ${E.lastActivity??""}`,N.classList.add("active"))}),w.appendChild(p),w.appendChild(m),w.appendChild(N),w.appendChild(v),s.onRename){const u=document.createElement("button");u.className="sidebar-edit-btn",u.textContent="✎",u.title="Rename";const E=s.onRename;u.addEventListener("click",R=>{R.stopPropagation();const M=document.createElement("input");M.type="text",M.className="sidebar-rename-input",M.value=D.name,p.textContent="",p.appendChild(M),u.style.display="none",M.focus(),M.select();const O=()=>{const k=M.value.trim();k&&k!==D.name?E(D.name,k):(p.textContent=D.name,u.style.display="")};M.addEventListener("blur",O),M.addEventListener("keydown",k=>{k.key==="Enter"&&M.blur(),k.key==="Escape"&&(M.value=D.name,M.blur())})}),w.appendChild(u)}return w.addEventListener("click",()=>s.onSelect(D.name)),y.appendChild(w),w}),ye&&this.setActive(ye)},setActive(_){ye=_;for(const H of ie)H.classList.toggle("active",H.dataset.name===_);for(const H of te)H.classList.toggle("active",H.dataset.name===_)},setRemotes(_){c.replaceChildren(),te=_.map(D=>{const w=document.createElement("li");w.className="ontology-item ontology-item-remote",w.dataset.name=D.name;const p=document.createElement("div");p.className="remote-name-row";const m=document.createElement("span");m.className="name",m.textContent=D.name;const g=document.createElement("span");g.className="remote-badge",g.textContent=D.pinned?"remote · pinned":"remote",g.title=`Source: ${D.source??D.url}`,p.appendChild(m),p.appendChild(g);const v=document.createElement("span");v.className="stats";const N=Zt(D.nodeCount,D.edgeCount);v.textContent=`${D.nodeCount} nodes, ${D.edgeCount} edges · ~${Kt(N)}`;const u=document.createElement("span");return u.className="remote-source",u.textContent=D.source??new URL(D.url).hostname,u.title=D.url,w.appendChild(p),w.appendChild(v),w.appendChild(u),w.addEventListener("click",()=>s.onSelect(D.name)),c.appendChild(w),w});const H=_.length>0;x.hidden=!H,c.hidden=!H,ye&&this.setActive(ye)},setActiveBranch(_,H,D){const w=y.querySelectorAll(`.sidebar-branch[data-graph="${_}"]`);for(const p of w){p.textContent=`/ ${H}`,p.title="Click to switch branch",p.style.cursor="pointer";const m=p.cloneNode(!0);p.replaceWith(m),m.addEventListener("click",g=>{g.stopPropagation(),ke(_,m,D??[])})}},setSnippets(_,H){var p;const D=ie.find(m=>m.dataset.name===_);if(!D||((p=D.querySelector(".sidebar-snippets"))==null||p.remove(),H.length===0))return;const w=document.createElement("div");w.className="sidebar-snippets";for(const m of H){const g=document.createElement("div");g.className="sidebar-snippet";const v=document.createElement("span");v.className="sidebar-snippet-label",v.textContent=`◆ ${m.label}`,v.title=`${m.nodeCount} nodes — click to load`;const N=document.createElement("button");N.className="sidebar-snippet-delete",N.textContent="×",N.title="Delete snippet",N.addEventListener("click",u=>{var E;u.stopPropagation(),(E=s.onSnippetDelete)==null||E.call(s,_,m.id)}),g.appendChild(v),g.appendChild(N),g.addEventListener("click",u=>{var E;u.stopPropagation(),(E=s.onSnippetLoad)==null||E.call(s,_,m.id)}),w.appendChild(g)}D.appendChild(w)},toggle:a,expandBtn:J};function ke(_,H,D){const w=t.querySelector(".branch-picker");w&&w.remove();const p=document.createElement("div");p.className="branch-picker";for(const g of D){const v=document.createElement("div");v.className="branch-picker-item",g.active&&v.classList.add("branch-picker-active");const N=document.createElement("span");if(N.textContent=g.name,v.appendChild(N),!g.active&&s.onBranchDelete){const u=document.createElement("button");u.className="branch-picker-delete",u.textContent="×",u.title=`Delete ${g.name}`,u.addEventListener("click",E=>{E.stopPropagation(),hn("Delete branch",`Delete branch "${g.name}"?`).then(R=>{R&&(s.onBranchDelete(_,g.name),p.remove())})}),v.appendChild(u)}g.active||v.addEventListener("click",()=>{var u;(u=s.onBranchSwitch)==null||u.call(s,_,g.name),p.remove()}),p.appendChild(v)}if(s.onBranchCreate){const g=document.createElement("div");g.className="branch-picker-item branch-picker-create",g.textContent="+ New branch",g.addEventListener("click",()=>{gt("New branch","Branch name").then(v=>{v&&(s.onBranchCreate(_,v),p.remove())})}),p.appendChild(g)}H.after(p);const m=g=>{p.contains(g.target)||(p.remove(),document.removeEventListener("click",m))};setTimeout(()=>document.addEventListener("click",m),0)}}function Tt(t,e,s,r){return{x0:t,y0:e,x1:s,y1:r,cx:0,cy:0,mass:0,children:[null,null,null,null],body:null}}function Qt(t,e,s){const r=(t.x0+t.x1)/2,l=(t.y0+t.y1)/2;return(e<r?0:1)+(s<l?0:2)}function en(t,e){const s=(t.x0+t.x1)/2,r=(t.y0+t.y1)/2;switch(e){case 0:return[t.x0,t.y0,s,r];case 1:return[s,t.y0,t.x1,r];case 2:return[t.x0,r,s,t.y1];default:return[s,r,t.x1,t.y1]}}function At(t,e){if(t.mass===0&&t.body===null){t.body=e,t.cx=e.x,t.cy=e.y,t.mass=1;return}if(t.body!==null){const l=t.body;t.body=null,l.x===e.x&&l.y===e.y&&(e.x+=(Math.random()-.5)*.1,e.y+=(Math.random()-.5)*.1);const y=Qt(t,l.x,l.y);if(t.children[y]===null){const[x,c,o,P]=en(t,y);t.children[y]=Tt(x,c,o,P)}At(t.children[y],l)}const s=Qt(t,e.x,e.y);if(t.children[s]===null){const[l,y,x,c]=en(t,s);t.children[s]=Tt(l,y,x,c)}At(t.children[s],e);const r=t.mass+1;t.cx=(t.cx*t.mass+e.x)/r,t.cy=(t.cy*t.mass+e.y)/r,t.mass=r}function Dn(t){if(t.length===0)return null;let e=1/0,s=1/0,r=-1/0,l=-1/0;for(const i of t)i.x<e&&(e=i.x),i.y<s&&(s=i.y),i.x>r&&(r=i.x),i.y>l&&(l=i.y);const y=Math.max(r-e,l-s)*.1+50,x=(e+r)/2,c=(s+l)/2,o=Math.max(r-e,l-s)/2+y,P=Tt(x-o,c-o,x+o,c+o);for(const i of t)At(P,i);return P}function On(t,e,s,r,l,y){fn(t,e,s,r,l,y)}function fn(t,e,s,r,l,y){if(t.mass===0)return;const x=t.cx-e.x,c=t.cy-e.y,o=x*x+c*c;if(t.body!==null){if(t.body!==e){let i=Math.sqrt(o);i<y&&(i=y);const a=r*l/(i*i),I=x/i*a,F=c/i*a;e.vx-=I,e.vy-=F}return}const P=t.x1-t.x0;if(P*P/o<s*s){let i=Math.sqrt(o);i<y&&(i=y);const a=r*t.mass*l/(i*i),I=x/i*a,F=c/i*a;e.vx-=I,e.vy-=F;return}for(let i=0;i<4;i++)t.children[i]!==null&&fn(t.children[i],e,s,r,l,y)}const gn={clusterStrength:.08,spacing:1.5},tn=6e3,Wn=12e3,Hn=.004,yn=140,Cn=350,nn=.9,on=.01,ct=30,kt=50;let Xe={...gn};function Qe(t){t.clusterStrength!==void 0&&(Xe.clusterStrength=t.clusterStrength),t.spacing!==void 0&&(Xe.spacing=t.spacing)}function rt(){return{...Xe}}function zn(t){if(t<=30)return{...gn};const e=Math.log2(t/30);return{clusterStrength:Math.min(.5,.08+.06*e),spacing:Math.min(15,1.5+1.2*e)}}function jn(t,e){for(const s of Object.values(t))if(typeof s=="string")return s;return e}function sn(t,e,s){const r=new Set(e);let l=new Set(e);for(let y=0;y<s;y++){const x=new Set;for(const c of t.edges)l.has(c.sourceId)&&!r.has(c.targetId)&&x.add(c.targetId),l.has(c.targetId)&&!r.has(c.sourceId)&&x.add(c.sourceId);for(const c of x)r.add(c);if(l=x,x.size===0)break}return{nodes:t.nodes.filter(y=>r.has(y.id)),edges:t.edges.filter(y=>r.has(y.sourceId)&&r.has(y.targetId)),metadata:t.metadata}}function Nt(t){const e=new Map,s=[...new Set(t.nodes.map(o=>o.type))],r=Math.sqrt(s.length)*Cn*.6*Math.max(1,Xe.spacing),l=new Map,y=new Map;for(const o of t.nodes)y.set(o.type,(y.get(o.type)??0)+1);const x=t.nodes.map(o=>{const P=s.indexOf(o.type),i=2*Math.PI*P/Math.max(s.length,1),a=Math.cos(i)*r,I=Math.sin(i)*r,F=l.get(o.type)??0;l.set(o.type,F+1);const C=y.get(o.type)??1,j=2*Math.PI*F/C,U=yn*.6,Q={id:o.id,x:a+Math.cos(j)*U,y:I+Math.sin(j)*U,vx:0,vy:0,label:jn(o.properties,o.id),type:o.type};return e.set(o.id,Q),Q}),c=t.edges.map(o=>({sourceId:o.sourceId,targetId:o.targetId,type:o.type}));return{nodes:x,edges:c,nodeMap:e}}const Un=.7,qn=80;function Yn(t,e){const{nodes:s,edges:r,nodeMap:l}=t,y=Wn*Xe.spacing;if(s.length>=qn){const c=Dn(s);if(c)for(const P of s)On(c,P,Un,y,e,ct);const o=y-tn;if(o>0){const P=new Map;for(const i of s){let a=P.get(i.type);a||(a=[],P.set(i.type,a)),a.push(i)}for(const i of P.values())for(let a=0;a<i.length;a++)for(let I=a+1;I<i.length;I++){const F=i[a],C=i[I];let j=C.x-F.x,U=C.y-F.y,Q=Math.sqrt(j*j+U*U);Q<ct&&(Q=ct);const W=o*e/(Q*Q),ne=j/Q*W,V=U/Q*W;F.vx+=ne,F.vy+=V,C.vx-=ne,C.vy-=V}}}else for(let c=0;c<s.length;c++)for(let o=c+1;o<s.length;o++){const P=s[c],i=s[o];let a=i.x-P.x,I=i.y-P.y,F=Math.sqrt(a*a+I*I);F<ct&&(F=ct);const j=(P.type===i.type?tn:y)*e/(F*F),U=a/F*j,Q=I/F*j;P.vx-=U,P.vy-=Q,i.vx+=U,i.vy+=Q}for(const c of r){const o=l.get(c.sourceId),P=l.get(c.targetId);if(!o||!P)continue;const i=P.x-o.x,a=P.y-o.y,I=Math.sqrt(i*i+a*a);if(I===0)continue;const F=o.type===P.type?yn*Xe.spacing:Cn*Xe.spacing,C=Hn*(I-F)*e,j=i/I*C,U=a/I*C;o.vx+=j,o.vy+=U,P.vx-=j,P.vy-=U}for(const c of s)c.vx-=c.x*on*e,c.vy-=c.y*on*e;const x=new Map;for(const c of s){const o=x.get(c.type)??{x:0,y:0,count:0};o.x+=c.x,o.y+=c.y,o.count++,x.set(c.type,o)}for(const c of x.values())c.x/=c.count,c.y/=c.count;for(const c of s){const o=x.get(c.type);c.vx+=(o.x-c.x)*Xe.clusterStrength*e,c.vy+=(o.y-c.y)*Xe.clusterStrength*e}for(const c of s){c.vx*=nn,c.vy*=nn;const o=Math.sqrt(c.vx*c.vx+c.vy*c.vy);o>kt&&(c.vx=c.vx/o*kt,c.vy=c.vy/o*kt),c.x+=c.vx,c.y+=c.vy}return e*.995}const an=["#d4a27f","#c17856","#b07a5e","#d4956b","#a67c5a","#cc9e7c","#c4866a","#cb8e6c","#b8956e","#a88a70","#d9b08c","#c4a882","#e8b898","#b5927a","#a8886e","#d1a990"],cn=new Map;function Ne(t){const e=cn.get(t);if(e)return e;let s=0;for(let l=0;l<t.length;l++)s=(s<<5)-s+t.charCodeAt(l)|0;const r=an[Math.abs(s)%an.length];return cn.set(t,r),r}class Xn{constructor(e){mt(this,"cells",new Map);mt(this,"cellSize");mt(this,"invCell");this.cellSize=e,this.invCell=1/e}key(e,s){const r=e+32768|0,l=s+32768|0;return r*73856093^l*19349663}clear(){this.cells.clear()}insert(e){const s=Math.floor(e.x*this.invCell),r=Math.floor(e.y*this.invCell),l=this.key(s,r),y=this.cells.get(l);y?y.push(e):this.cells.set(l,[e])}rebuild(e){this.cells.clear();for(const s of e)this.insert(s)}query(e,s,r){const l=r*r,y=Math.floor((e-r)*this.invCell),x=Math.floor((e+r)*this.invCell),c=Math.floor((s-r)*this.invCell),o=Math.floor((s+r)*this.invCell);let P=null,i=l;for(let a=y;a<=x;a++)for(let I=c;I<=o;I++){const F=this.cells.get(this.key(a,I));if(F)for(const C of F){const j=C.x-e,U=C.y-s,Q=j*j+U*U;Q<=i&&(i=Q,P=C)}}return P}}const et=new Map,_n=2e3;function Gn(t,e,s){return`${t}|${e}|${s}`}const Vn=new OffscreenCanvas(1,1),ln=Vn.getContext("2d");function Jn(t,e,s){ln.font=e;const r=ln.measureText(t),l=Math.ceil(r.width)+2,y=Math.ceil(r.actualBoundingBoxAscent+r.actualBoundingBoxDescent)+4,x=new OffscreenCanvas(l,y),c=x.getContext("2d");return c.font=e,c.fillStyle=s,c.textAlign="left",c.textBaseline="top",c.fillText(t,1,1),{canvas:x,width:l,height:y}}function rn(t,e,s,r,l,y,x){const c=Gn(e,l,y);let o=et.get(c);if(!o){if(et.size>=_n){const a=et.keys().next().value;a!==void 0&&et.delete(a)}o=Jn(e,l,y),et.set(c,o)}const P=s-o.width/2,i=x==="top"?r:r-o.height;t.drawImage(o.canvas,P,i)}function Kn(){et.clear()}function Ce(t){return getComputedStyle(document.documentElement).getPropertyValue(t).trim()}const Fe=20,St=.001,Zn={hideBadges:.4,hideLabels:.25,hideEdgeLabels:.35,smallNodes:.2,hideArrows:.15,dotNodes:.1,hullsOnly:.05},Qn={zoomFactor:1.3,zoomMin:.05,zoomMax:10,panAnimationMs:300};function ft(t,e,s,r,l,y=100){const x=(t-s.x)*s.scale,c=(e-s.y)*s.scale;return x>=-y&&x<=r+y&&c>=-y&&c<=l+y}function eo(t,e,s,r){const l={...Zn,...(r==null?void 0:r.lod)??{}},y={...Qn,...(r==null?void 0:r.navigation)??{}},x={pulseSpeed:.02,...(r==null?void 0:r.walk)??{}},c=t.querySelector("canvas"),o=c.getContext("2d"),P=window.devicePixelRatio||1;let i={x:0,y:0,scale:1},a=null,I=1,F=0,C=new Set,j=null,U=!0,Q=!0,W=!0,ne=!0,V=null,B=null,ee=1,K=null,ce=null,J=null,ie=!1,te=[],ye=0,ke=0;const _=400,H=new Xn(Fe*2);let D=0;function w(){M(),D||(D=requestAnimationFrame(()=>{D=0,d()}))}const p=150;let m=null,g=!1;function v(){if(!m)try{m=new Worker(new URL("/assets/layout-worker-BZXiBoiC.js",import.meta.url),{type:"module"}),m.onmessage=N,m.onerror=()=>{g=!1,m=null,xe()}}catch{return g=!1,null}return m}function N(h){const b=h.data;if(b.type==="tick"&&a){const A=b.positions,$=a.nodes;for(let z=0;z<$.length;z++)$[z].x=A[z*4],$[z].y=A[z*4+1],$[z].vx=A[z*4+2],$[z].vy=A[z*4+3];I=b.alpha,H.rebuild($),d()}b.type==="settled"&&(I=0,ie&&te.length>0&&!le&&(le=requestAnimationFrame(ge)))}let u=null,E=null,R=!0;function M(){R=!0}let O=null,k=null;const f=y.panAnimationMs;function S(){c.width=c.clientWidth*P,c.height=c.clientHeight*P,M(),w()}const q=new ResizeObserver(S);q.observe(t),S();function se(h,b){return[h/i.scale+i.x,b/i.scale+i.y]}function pe(h,b){if(!a)return null;const[A,$]=se(h,b);return H.query(A,$,Fe)}function n(){if(!a)return;ye+=x.pulseSpeed;const h=new Set(te);o.save(),o.setTransform(P,0,0,P,0,0),u&&(o.clearRect(0,0,c.clientWidth,c.clientHeight),o.drawImage(u,0,0,c.clientWidth,c.clientHeight)),o.save(),o.translate(-i.x*i.scale,-i.y*i.scale),o.scale(i.scale,i.scale);const b=Ce("--canvas-walk-edge")||"#1a1a1a",A=[];for(const X of a.edges){if(!h.has(X.sourceId)||!h.has(X.targetId)||X.sourceId===X.targetId)continue;const Y=a.nodeMap.get(X.sourceId),Z=a.nodeMap.get(X.targetId);!Y||!Z||A.push(Y.x,Y.y,Z.x,Z.y)}if(A.length>0){o.beginPath();for(let X=0;X<A.length;X+=4)o.moveTo(A[X],A[X+1]),o.lineTo(A[X+2],A[X+3]);o.strokeStyle=b,o.lineWidth=3,o.globalAlpha=.5+.5*Math.sin(ye),o.stroke(),o.globalAlpha=1}const $=i.scale<l.smallNodes?Fe*.5:Fe,z=Ce("--accent")||"#d4a27f";for(const X of te){const Y=a.nodeMap.get(X);if(!Y||!ft(Y.x,Y.y,i,c.clientWidth,c.clientHeight))continue;const Z=X===te[te.length-1],ue=.5+.5*Math.sin(ye);o.strokeStyle=z,o.lineWidth=Z?3:2,o.globalAlpha=Z?.5+.5*ue:.3+.4*ue,o.beginPath(),o.arc(Y.x,Y.y,$+(Z?6:4),0,Math.PI*2),o.stroke()}o.globalAlpha=1,o.restore(),ne&&a.nodes.length>1&&T(),o.restore()}function d(){var zt;if(!a){o.clearRect(0,0,c.width,c.height);return}if(!R&&u&&ie&&te.length>0&&I<St){n();return}const h=I<St&&ie&&te.length>0;ie&&te.length>0&&(ye+=x.pulseSpeed);const b=ie&&!h?new Set(te):null,A=Ce("--canvas-edge"),$=Ce("--canvas-edge-highlight"),z=Ce("--canvas-edge-dim"),X=Ce("--canvas-edge-label"),Y=Ce("--canvas-edge-label-highlight"),Z=Ce("--canvas-edge-label-dim"),ue=Ce("--canvas-arrow"),he=Ce("--canvas-arrow-highlight"),fe=Ce("--canvas-node-label"),be=Ce("--canvas-node-label-dim"),Oe=Ce("--canvas-type-badge"),Me=Ce("--canvas-type-badge-dim"),Ie=Ce("--canvas-selection-border"),je=Ce("--canvas-node-border");if(o.save(),o.setTransform(P,0,0,P,0,0),o.clearRect(0,0,c.clientWidth,c.clientHeight),o.save(),o.translate(-i.x*i.scale,-i.y*i.scale),o.scale(i.scale,i.scale),W&&i.scale>=l.smallNodes){const oe=new Map;for(const Se of a.nodes){if(j!==null&&!j.has(Se.id))continue;const Be=oe.get(Se.type)??[];Be.push(Se),oe.set(Se.type,Be)}for(const[Se,Be]of oe){if(Be.length<2)continue;const Ve=Ne(Se),_e=Fe*2.5;let We=1/0,ze=1/0,Pe=-1/0,Ye=-1/0;for(const Re of Be)Re.x<We&&(We=Re.x),Re.y<ze&&(ze=Re.y),Re.x>Pe&&(Pe=Re.x),Re.y>Ye&&(Ye=Re.y);o.beginPath();const ae=(Pe-We)/2+_e,we=(Ye-ze)/2+_e,ve=(We+Pe)/2,$e=(ze+Ye)/2;o.ellipse(ve,$e,ae,we,0,0,Math.PI*2),o.fillStyle=Ve,o.globalAlpha=.05,o.fill(),o.strokeStyle=Ve,o.globalAlpha=.12,o.lineWidth=1,o.setLineDash([4,4]),o.stroke(),o.setLineDash([]),o.globalAlpha=1}}let Te=null;if(C.size>0){Te=new Set;for(const oe of a.edges)C.has(oe.sourceId)&&Te.add(oe.targetId),C.has(oe.targetId)&&Te.add(oe.sourceId)}const vt=Ce("--accent")||"#d4a27f",xt=Ce("--canvas-walk-edge")||"#1a1a1a",Ze=i.scale>=l.hideArrows,pt=Q&&i.scale>=l.hideEdgeLabels;if(U){const oe=[],Se=[],Be=[],Ve=[],_e=[],We=[];for(const ae of a.edges){const we=a.nodeMap.get(ae.sourceId),ve=a.nodeMap.get(ae.targetId);if(!we||!ve||!ft(we.x,we.y,i,c.clientWidth,c.clientHeight,200)&&!ft(ve.x,ve.y,i,c.clientWidth,c.clientHeight,200))continue;const $e=j===null||j.has(ae.sourceId),Re=j===null||j.has(ae.targetId),jt=$e&ℜif(j!==null&&!$e&&!Re)continue;const Et=C.size>0&&(C.has(ae.sourceId)||C.has(ae.targetId))||j!==null&&jt,Ut=j!==null&&!jt,qt=b!==null&&b.has(ae.sourceId)&&b.has(ae.targetId),Yt=J?V==null?void 0:V.edges.find(ut=>ut.sourceId===ae.sourceId&&ut.targetId===ae.targetId||ut.targetId===ae.sourceId&&ut.sourceId===ae.targetId):null,Xt=!!(J&&Yt&&J.edgeIds.has(Yt.id));if(ae.sourceId===ae.targetId){re(we,ae.type,Et,A,$,X,Y);continue}(Xt?_e:qt?Ve:Et?Se:Ut?Be:oe).push(we.x,we.y,ve.x,ve.y),(Ze||pt)&&We.push({sx:we.x,sy:we.y,tx:ve.x,ty:ve.y,type:ae.type,highlighted:Et,edgeDimmed:Ut,isPathEdge:Xt,isWalkEdge:qt})}const ze=Ze?1.5:1,Ye=[{lines:oe,color:A,width:ze,alpha:1},{lines:Be,color:z,width:ze,alpha:1},{lines:Se,color:$,width:Ze?2.5:1,alpha:1},{lines:_e,color:vt,width:3,alpha:1},{lines:Ve,color:xt,width:3,alpha:.5+.5*Math.sin(ye)}];for(const ae of Ye)if(ae.lines.length!==0){o.beginPath();for(let we=0;we<ae.lines.length;we+=4)o.moveTo(ae.lines[we],ae.lines[we+1]),o.lineTo(ae.lines[we+2],ae.lines[we+3]);o.strokeStyle=ae.color,o.lineWidth=ae.width,o.globalAlpha=ae.alpha,o.stroke()}o.globalAlpha=1;for(const ae of We)if(Ze&&G(ae.sx,ae.sy,ae.tx,ae.ty,ae.highlighted||ae.isPathEdge,ue,he),pt){const we=(ae.sx+ae.tx)/2,ve=(ae.sy+ae.ty)/2;o.fillStyle=ae.highlighted?Y:ae.edgeDimmed?Z:X,o.font="9px system-ui, sans-serif",o.textAlign="center",o.textBaseline="bottom",o.fillText(ae.type,we,ve-4)}}const bt=performance.now()-ke,Ee=Math.min(1,bt/_),Ue=1-(1-Ee)*(1-Ee),qe=Ee<1,Ht=i.scale<l.hullsOnly,bn=!Ht&&i.scale<l.dotNodes;if(!Ht)for(const oe of a.nodes){if(!ft(oe.x,oe.y,i,c.clientWidth,c.clientHeight))continue;const Se=Ne(oe.type);if(bn){const ve=j!==null&&!j.has(oe.id);o.fillStyle=Se;const $e=ve?.1:.8;o.globalAlpha=qe?$e*Ue:$e,o.fillRect(oe.x-2,oe.y-2,4,4);continue}const Be=C.has(oe.id),Ve=Te!==null&&Te.has(oe.id),_e=j!==null&&!j.has(oe.id),We=_e||C.size>0&&!Be&&!Ve,ze=i.scale<l.smallNodes?Fe*.5:Fe,Pe=qe?ze*Ue:ze;if(b!=null&&b.has(oe.id)){const ve=te[te.length-1]===oe.id,$e=.5+.5*Math.sin(ye),Re=Ce("--accent")||"#d4a27f";o.save(),o.strokeStyle=Re,o.lineWidth=ve?3:2,o.globalAlpha=ve?.5+.5*$e:.3+.4*$e,o.beginPath(),o.arc(oe.x,oe.y,Pe+(ve?6:4),0,Math.PI*2),o.stroke(),o.restore()}Be&&(o.save(),o.shadowColor=Se,o.shadowBlur=20,o.beginPath(),o.arc(oe.x,oe.y,Pe+3,0,Math.PI*2),o.fillStyle=Se,o.globalAlpha=.3,o.fill(),o.restore()),o.beginPath(),o.arc(oe.x,oe.y,Pe,0,Math.PI*2),o.fillStyle=Se;const Ye=_e?.1:We?.3:1;o.globalAlpha=qe?Ye*Ue:Ye,o.fill(),o.strokeStyle=Be?Ie:je,o.lineWidth=Be?3:1.5,o.stroke(),o.globalAlpha=1,J&&J.nodeIds.has(oe.id)&&!Be&&(o.save(),o.shadowColor=Ce("--accent")||"#d4a27f",o.shadowBlur=15,o.beginPath(),o.arc(oe.x,oe.y,Pe+2,0,Math.PI*2),o.strokeStyle=Ce("--accent")||"#d4a27f",o.globalAlpha=.5,o.lineWidth=2,o.stroke(),o.restore());const ae=V==null?void 0:V.nodes.find(ve=>ve.id===oe.id);if(((zt=ae==null?void 0:ae.properties)==null?void 0:zt._starred)===!0&&(o.fillStyle="#ffd700",o.font="10px system-ui, sans-serif",o.textAlign="left",o.textBaseline="bottom",o.fillText("★",oe.x+Pe-2,oe.y-Pe+2)),i.scale>=l.hideLabels){const ve=oe.label.length>24?oe.label.slice(0,22)+"...":oe.label,$e=We?be:fe;rn(o,ve,oe.x,oe.y+Pe+4,"11px system-ui, sans-serif",$e,"top")}if(i.scale>=l.hideBadges){const ve=We?Me:Oe;rn(o,oe.type,oe.x,oe.y-Pe-3,"9px system-ui, sans-serif",ve,"bottom")}o.globalAlpha=1}if(o.restore(),o.restore(),h){const oe=c.width,Se=c.height;(!u||u.width!==oe||u.height!==Se)&&(u=new OffscreenCanvas(oe,Se),E=u.getContext("2d")),E&&(E.clearRect(0,0,oe,Se),E.drawImage(c,0,0),R=!1),n();return}ne&&a.nodes.length>1&&T()}function T(){if(!a)return;const h=140,b=100,A=8,$=c.clientWidth-h-16,z=c.clientHeight-b-16;let X=1/0,Y=1/0,Z=-1/0,ue=-1/0;for(const Ee of a.nodes)Ee.x<X&&(X=Ee.x),Ee.y<Y&&(Y=Ee.y),Ee.x>Z&&(Z=Ee.x),Ee.y>ue&&(ue=Ee.y);const he=Z-X||1,fe=ue-Y||1,be=Math.min((h-A*2)/he,(b-A*2)/fe),Oe=$+A+(h-A*2-he*be)/2,Me=z+A+(b-A*2-fe*be)/2;o.save(),o.setTransform(P,0,0,P,0,0),o.fillStyle=Ce("--bg-surface")||"#1a1a1a",o.globalAlpha=.85,o.beginPath(),o.roundRect($,z,h,b,8),o.fill(),o.strokeStyle=Ce("--border")||"#2a2a2a",o.globalAlpha=1,o.lineWidth=1,o.stroke(),o.globalAlpha=.15,o.strokeStyle=Ce("--canvas-edge")||"#555",o.lineWidth=.5;for(const Ee of a.edges){const Ue=a.nodeMap.get(Ee.sourceId),qe=a.nodeMap.get(Ee.targetId);!Ue||!qe||Ee.sourceId===Ee.targetId||(o.beginPath(),o.moveTo(Oe+(Ue.x-X)*be,Me+(Ue.y-Y)*be),o.lineTo(Oe+(qe.x-X)*be,Me+(qe.y-Y)*be),o.stroke())}o.globalAlpha=.8;for(const Ee of a.nodes){const Ue=Oe+(Ee.x-X)*be,qe=Me+(Ee.y-Y)*be;o.beginPath(),o.arc(Ue,qe,2,0,Math.PI*2),o.fillStyle=Ne(Ee.type),o.fill()}const Ie=i.x,je=i.y,Te=i.x+c.clientWidth/i.scale,vt=i.y+c.clientHeight/i.scale,xt=Oe+(Ie-X)*be,Ze=Me+(je-Y)*be,pt=(Te-Ie)*be,bt=(vt-je)*be;o.globalAlpha=.3,o.strokeStyle=Ce("--accent")||"#d4a27f",o.lineWidth=1.5,o.strokeRect(Math.max($,Math.min(xt,$+h)),Math.max(z,Math.min(Ze,z+b)),Math.min(pt,h),Math.min(bt,b)),o.globalAlpha=1,o.restore()}function G(h,b,A,$,z,X,Y){const Z=Math.atan2($-b,A-h),ue=A-Math.cos(Z)*Fe,he=$-Math.sin(Z)*Fe,fe=8;o.beginPath(),o.moveTo(ue,he),o.lineTo(ue-fe*Math.cos(Z-.4),he-fe*Math.sin(Z-.4)),o.lineTo(ue-fe*Math.cos(Z+.4),he-fe*Math.sin(Z+.4)),o.closePath(),o.fillStyle=z?Y:X,o.fill()}function re(h,b,A,$,z,X,Y){const Z=h.x+Fe+15,ue=h.y-Fe-15;o.beginPath(),o.arc(Z,ue,15,0,Math.PI*2),o.strokeStyle=A?z:$,o.lineWidth=A?2.5:1.5,o.stroke(),Q&&(o.fillStyle=A?Y:X,o.font="9px system-ui, sans-serif",o.textAlign="center",o.fillText(b,Z,ue-18))}function me(){if(!O||!k)return;const h=performance.now()-k.time,b=Math.min(h/f,1),A=1-Math.pow(1-b,3);i.x=k.x+(O.x-k.x)*A,i.y=k.y+(O.y-k.y)*A,M(),d(),b<1?requestAnimationFrame(me):(O=null,k=null)}let le=0;function ge(){if(!ie||te.length===0){le=0;return}d(),le=requestAnimationFrame(ge)}function Le(){if(!a||a.nodes.length===0)return;let h=1/0,b=1/0,A=-1/0,$=-1/0;for(const he of a.nodes)he.x<h&&(h=he.x),he.y<b&&(b=he.y),he.x>A&&(A=he.x),he.y>$&&($=he.y);const z=Fe*4,X=A-h+z*2,Y=$-b+z*2,Z=c.clientWidth/Math.max(X,1),ue=c.clientHeight/Math.max(Y,1);i.scale=Math.min(Z,ue,2),i.x=(h+A)/2-c.clientWidth/(2*i.scale),i.y=(b+$)/2-c.clientHeight/(2*i.scale),w()}function xe(){if(!a||I<St){ie&&te.length>0&&!le&&(le=requestAnimationFrame(ge));return}I=Yn(a,I),H.rebuild(a.nodes),d(),F=requestAnimationFrame(xe)}let He=!1,yt=!1,Je=0,Ke=0;c.addEventListener("mousedown",h=>{He=!0,yt=!1,Je=h.clientX,Ke=h.clientY}),c.addEventListener("mousemove",h=>{if(!He)return;const b=h.clientX-Je,A=h.clientY-Ke;(Math.abs(b)>5||Math.abs(A)>5)&&(yt=!0),i.x-=b/i.scale,i.y-=A/i.scale,Je=h.clientX,Ke=h.clientY,w()}),c.addEventListener("mouseup",h=>{if(He=!1,yt)return;const b=c.getBoundingClientRect(),A=h.clientX-b.left,$=h.clientY-b.top,z=pe(A,$),X=h.ctrlKey||h.metaKey;if(ie&&B&&z&&a){const Y=te.length>0?te[te.length-1]:B[0],Z=new Set([Y]),ue=[{id:Y,path:[Y]}];let he=null;for(;ue.length>0;){const{id:Me,path:Ie}=ue.shift();if(Me===z.id){he=Ie;break}for(const je of a.edges){let Te=null;je.sourceId===Me?Te=je.targetId:je.targetId===Me&&(Te=je.sourceId),Te&&!Z.has(Te)&&(Z.add(Te),ue.push({id:Te,path:[...Ie,Te]}))}}if(!he)return;for(const Me of he.slice(1))te.includes(Me)||te.push(Me);B=[z.id];const fe=Math.max(1,ee);ee=fe;const be=sn(V,[z.id],fe);cancelAnimationFrame(F),m&&m.postMessage({type:"stop"}),a=Nt(be),H.rebuild(a.nodes),I=1,C=new Set([z.id]),j=null,i={x:0,y:0,scale:1},g=be.nodes.length>=p;const Oe=g?v():null;Oe?Oe.postMessage({type:"start",data:be}):(g=!1,xe()),setTimeout(()=>{a&&Le()},300),s==null||s({seedNodeIds:[z.id],hops:fe,totalNodes:be.nodes.length}),e==null||e([z.id]);return}if(z){X?C.has(z.id)?C.delete(z.id):C.add(z.id):C.size===1&&C.has(z.id)?C.clear():(C.clear(),C.add(z.id));const Y=[...C];e==null||e(Y.length>0?Y:null)}else C.clear(),e==null||e(null);w()}),c.addEventListener("mouseleave",()=>{He=!1}),c.addEventListener("wheel",h=>{h.preventDefault();const b=c.getBoundingClientRect(),A=h.clientX-b.left,$=h.clientY-b.top,[z,X]=se(A,$),Y=h.ctrlKey?1-h.deltaY*.01:h.deltaY>0?.9:1.1;i.scale=Math.max(y.zoomMin,Math.min(y.zoomMax,i.scale*Y)),i.x=z-A/i.scale,i.y=X-$/i.scale,w()},{passive:!1});let De=[],Ft=0,$t=1,Dt=0,Ot=0,Ct=!1;c.addEventListener("touchstart",h=>{h.preventDefault(),De=Array.from(h.touches),De.length===2?(Ft=Wt(De[0],De[1]),$t=i.scale):De.length===1&&(Je=De[0].clientX,Ke=De[0].clientY,Dt=De[0].clientX,Ot=De[0].clientY,Ct=!1)},{passive:!1}),c.addEventListener("touchmove",h=>{h.preventDefault();const b=Array.from(h.touches);if(b.length===2&&De.length===2){const $=Wt(b[0],b[1])/Ft;i.scale=Math.max(y.zoomMin,Math.min(y.zoomMax,$t*$)),w()}else if(b.length===1){const A=b[0].clientX-Je,$=b[0].clientY-Ke;(Math.abs(b[0].clientX-Dt)>10||Math.abs(b[0].clientY-Ot)>10)&&(Ct=!0),i.x-=A/i.scale,i.y-=$/i.scale,Je=b[0].clientX,Ke=b[0].clientY,w()}De=b},{passive:!1}),c.addEventListener("touchend",h=>{if(h.preventDefault(),Ct||h.changedTouches.length!==1)return;const b=h.changedTouches[0],A=c.getBoundingClientRect(),$=b.clientX-A.left,z=b.clientY-A.top,X=pe($,z);if(X){C.size===1&&C.has(X.id)?C.clear():(C.clear(),C.add(X.id));const Y=[...C];e==null||e(Y.length>0?Y:null)}else C.clear(),e==null||e(null);w()},{passive:!1}),c.addEventListener("gesturestart",h=>h.preventDefault()),c.addEventListener("gesturechange",h=>h.preventDefault());function Wt(h,b){const A=h.clientX-b.clientX,$=h.clientY-b.clientY;return Math.sqrt(A*A+$*$)}const tt=document.createElement("div");tt.className="zoom-controls";const nt=document.createElement("button");nt.className="zoom-btn",nt.textContent="+",nt.title="Zoom in",nt.addEventListener("click",()=>{const h=c.clientWidth/2,b=c.clientHeight/2,[A,$]=se(h,b);i.scale=Math.min(y.zoomMax,i.scale*y.zoomFactor),i.x=A-h/i.scale,i.y=$-b/i.scale,w()});const ot=document.createElement("button");ot.className="zoom-btn",ot.textContent="−",ot.title="Zoom out",ot.addEventListener("click",()=>{const h=c.clientWidth/2,b=c.clientHeight/2,[A,$]=se(h,b);i.scale=Math.max(y.zoomMin,i.scale/y.zoomFactor),i.x=A-h/i.scale,i.y=$-b/i.scale,w()});const st=document.createElement("button");st.className="zoom-btn",st.textContent="○",st.title="Reset zoom",st.addEventListener("click",()=>{if(a){if(i={x:0,y:0,scale:1},a.nodes.length>0){let h=1/0,b=1/0,A=-1/0,$=-1/0;for(const Y of a.nodes)Y.x<h&&(h=Y.x),Y.y<b&&(b=Y.y),Y.x>A&&(A=Y.x),Y.y>$&&($=Y.y);const z=(h+A)/2,X=(b+$)/2;i.x=z-c.clientWidth/2,i.y=X-c.clientHeight/2}w()}}),tt.appendChild(nt),tt.appendChild(st),tt.appendChild(ot),t.appendChild(tt);const Ae=document.createElement("div");Ae.className="node-tooltip",Ae.style.display="none",t.appendChild(Ae);let dt=null,Ge=null;return c.addEventListener("mousemove",h=>{if(He){Ae.style.display!=="none"&&(Ae.style.display="none",dt=null);return}const b=c.getBoundingClientRect(),A=h.clientX-b.left,$=h.clientY-b.top,z=pe(A,$),X=(z==null?void 0:z.id)??null;X!==dt?(dt=X,Ae.style.display="none",Ge&&clearTimeout(Ge),Ge=null,X&&z&&(Ge=setTimeout(()=>{if(!a||!V)return;const Y=a.edges.filter(Z=>Z.sourceId===X||Z.targetId===X).length;Ae.textContent=`${z.label} · ${z.type} · ${Y} edge${Y!==1?"s":""}`,Ae.style.left=`${h.clientX-b.left+12}px`,Ae.style.top=`${h.clientY-b.top-8}px`,Ae.style.display="block"},200))):X&&Ae.style.display==="block"&&(Ae.style.left=`${h.clientX-b.left+12}px`,Ae.style.top=`${h.clientY-b.top-8}px`)}),c.addEventListener("mouseleave",()=>{Ae.style.display="none",dt=null,Ge&&clearTimeout(Ge),Ge=null}),{loadGraph(h){if(cancelAnimationFrame(F),m&&m.postMessage({type:"stop"}),Kn(),V=h,B=null,K=null,ce=null,ke=performance.now(),a=Nt(h),H.rebuild(a.nodes),I=1,C=new Set,j=null,i={x:0,y:0,scale:1},a.nodes.length>0){let A=1/0,$=1/0,z=-1/0,X=-1/0;for(const fe of a.nodes)fe.x<A&&(A=fe.x),fe.y<$&&($=fe.y),fe.x>z&&(z=fe.x),fe.y>X&&(X=fe.y);const Y=(A+z)/2,Z=($+X)/2,ue=c.clientWidth,he=c.clientHeight;i.x=Y-ue/2,i.y=Z-he/2}g=h.nodes.length>=p;const b=g?v():null;b?b.postMessage({type:"start",data:h}):(g=!1,xe())},setFilteredNodeIds(h){j=h,w()},panToNode(h){this.panToNodes([h])},panToNodes(h){if(!a||h.length===0)return;const b=h.map(z=>a.nodeMap.get(z)).filter(Boolean);if(b.length===0)return;C=new Set(h),e==null||e(h);const A=c.clientWidth,$=c.clientHeight;if(b.length===1)k={x:i.x,y:i.y,time:performance.now()},O={x:b[0].x-A/(2*i.scale),y:b[0].y-$/(2*i.scale)};else{let z=1/0,X=1/0,Y=-1/0,Z=-1/0;for(const Ie of b)Ie.x<z&&(z=Ie.x),Ie.y<X&&(X=Ie.y),Ie.x>Y&&(Y=Ie.x),Ie.y>Z&&(Z=Ie.y);const ue=Fe*4,he=Y-z+ue*2,fe=Z-X+ue*2,be=Math.min(A/he,$/fe,i.scale);i.scale=be;const Oe=(z+Y)/2,Me=(X+Z)/2;k={x:i.x,y:i.y,time:performance.now()},O={x:Oe-A/(2*i.scale),y:Me-$/(2*i.scale)}}me()},setEdges(h){U=h,w()},setEdgeLabels(h){Q=h,w()},setTypeHulls(h){W=h,w()},setMinimap(h){ne=h,w()},centerView(){Le()},panBy(h,b){i.x+=h/i.scale,i.y+=b/i.scale,w()},zoomBy(h){const b=c.clientWidth/2,A=c.clientHeight/2,[$,z]=se(b,A);i.scale=Math.max(y.zoomMin,Math.min(y.zoomMax,i.scale*h)),i.x=$-b/i.scale,i.y=z-A/i.scale,w()},reheat(){g&&m?m.postMessage({type:"params",params:rt()}):(I=.5,cancelAnimationFrame(F),xe())},exportImage(h){if(!a)return"";const b=c.width,A=c.height;if(h==="png"){const Y=document.createElement("canvas");Y.width=b,Y.height=A;const Z=Y.getContext("2d");return Z.fillStyle=Ce("--bg")||"#141414",Z.fillRect(0,0,b,A),Z.drawImage(c,0,0),xn(Z,b,A),Y.toDataURL("image/png")}const $=c.toDataURL("image/png"),z=Math.max(16,Math.round(b/80)),X=`<svg xmlns="http://www.w3.org/2000/svg" width="${b}" height="${A}">
|
|
2
|
+
<image href="${$}" width="${b}" height="${A}"/>
|
|
3
|
+
<text x="${b-20}" y="${A-16}" text-anchor="end" font-family="system-ui, sans-serif" font-size="${z}" fill="#ffffff" opacity="0.4">backpackontology.com</text>
|
|
4
|
+
</svg>`;return"data:image/svg+xml;charset=utf-8,"+encodeURIComponent(X)},enterFocus(h,b){if(!V||!a)return;B||(K=a,ce={...i}),B=h,ee=b;const A=sn(V,h,b);cancelAnimationFrame(F),m&&m.postMessage({type:"stop"}),a=Nt(A),H.rebuild(a.nodes),I=1,C=new Set(h),j=null,i={x:0,y:0,scale:1},g=A.nodes.length>=p;const $=g?v():null;$?$.postMessage({type:"start",data:A}):(g=!1,xe()),setTimeout(()=>{!a||!B||Le()},300),s==null||s({seedNodeIds:h,hops:b,totalNodes:A.nodes.length})},exitFocus(){!B||!K||(cancelAnimationFrame(F),m&&m.postMessage({type:"stop"}),a=K,H.rebuild(a.nodes),i=ce??{x:0,y:0,scale:1},B=null,K=null,ce=null,C=new Set,j=null,w(),s==null||s(null))},isFocused(){return B!==null},getFocusInfo(){return!B||!a?null:{seedNodeIds:B,hops:ee,totalNodes:a.nodes.length}},findPath(h,b){if(!a)return null;const A=new Set([h]),$=[{nodeId:h,path:[h],edges:[]}];for(;$.length>0;){const{nodeId:z,path:X,edges:Y}=$.shift();if(z===b)return{nodeIds:X,edgeIds:Y};for(const Z of a.edges){let ue=null;if(Z.sourceId===z?ue=Z.targetId:Z.targetId===z&&(ue=Z.sourceId),ue&&!A.has(ue)){A.add(ue);const he=V==null?void 0:V.edges.find(fe=>fe.sourceId===Z.sourceId&&fe.targetId===Z.targetId||fe.targetId===Z.sourceId&&fe.sourceId===Z.targetId);$.push({nodeId:ue,path:[...X,ue],edges:[...Y,(he==null?void 0:he.id)??""]})}}}return null},setHighlightedPath(h,b){h&&b?J={nodeIds:new Set(h),edgeIds:new Set(b)}:J=null,w()},clearHighlightedPath(){J=null,w()},setWalkMode(h){ie=h,h?(te=B?[...B]:[...C],le||(le=requestAnimationFrame(ge))):(te=[],le&&(cancelAnimationFrame(le),le=0)),w()},getWalkMode(){return ie},getWalkTrail(){return[...te]},getFilteredNodeIds(){return j},removeFromWalkTrail(h){te=te.filter(b=>b!==h),w()},nodeAtScreen(h,b){return pe(h,b)},getNodeIds(){if(!a)return[];if(B){const h=new Set(B),b=a.nodes.filter($=>h.has($.id)).map($=>$.id),A=a.nodes.filter($=>!h.has($.id)).map($=>$.id);return[...b,...A]}return a.nodes.map(h=>h.id)},destroy(){cancelAnimationFrame(F),D&&(cancelAnimationFrame(D),D=0),le&&(cancelAnimationFrame(le),le=0),m&&(m.terminate(),m=null),u=null,E=null,q.disconnect()}};function xn(h,b,A){const $=Math.max(16,Math.round(b/80));h.save(),h.font=`${$}px system-ui, sans-serif`,h.fillStyle="rgba(255, 255, 255, 0.4)",h.textAlign="right",h.textBaseline="bottom",h.fillText("backpackontology.com",b-20,A-16),h.restore()}}function it(t){for(const e of Object.values(t.properties))if(typeof e=="string")return e;return t.id}const to="✎";function no(t,e,s,r){const l=document.createElement("div");l.id="info-panel",l.className="info-panel hidden",t.appendChild(l);let y=!1,x=[],c=-1,o=!1,P=null,i=[],a=!1,I=[],F=-1;function C(){l.classList.add("hidden"),l.classList.remove("info-panel-maximized"),l.innerHTML="",y=!1,x=[],c=-1}function j(B){!P||!s||(c<x.length-1&&(x=x.slice(0,c+1)),x.push(B),c=x.length-1,o=!0,s(B),o=!1)}function U(){if(c<=0||!P)return;c--,o=!0;const B=x[c];s==null||s(B),ne(B,P),o=!1}function Q(){if(c>=x.length-1||!P)return;c++,o=!0;const B=x[c];s==null||s(B),ne(B,P),o=!1}function W(){const B=document.createElement("div");B.className="info-panel-toolbar";const ee=document.createElement("button");ee.className="info-toolbar-btn",ee.textContent="←",ee.title="Back",ee.disabled=c<=0,ee.addEventListener("click",U),B.appendChild(ee);const K=document.createElement("button");if(K.className="info-toolbar-btn",K.textContent="→",K.title="Forward",K.disabled=c>=x.length-1,K.addEventListener("click",Q),B.appendChild(K),r&&i.length>0){const ie=document.createElement("button");ie.className="info-toolbar-btn info-focus-btn",ie.textContent="◎",ie.title="Focus on neighborhood (F)",ie.disabled=a,a&&(ie.style.opacity="0.3"),ie.addEventListener("click",()=>{a||r(i)}),B.appendChild(ie)}const ce=document.createElement("button");ce.className="info-toolbar-btn",ce.textContent=y?"⎘":"⛶",ce.title=y?"Restore":"Maximize",ce.addEventListener("click",()=>{y=!y,l.classList.toggle("info-panel-maximized",y),ce.textContent=y?"⎘":"⛶",ce.title=y?"Restore":"Maximize"}),B.appendChild(ce);const J=document.createElement("button");return J.className="info-toolbar-btn info-close-btn",J.textContent="×",J.title="Close",J.addEventListener("click",C),B.appendChild(J),B}function ne(B,ee){const K=ee.nodes.find(u=>u.id===B);if(!K)return;const ce=ee.edges.filter(u=>u.sourceId===B||u.targetId===B);I=ce.map(u=>u.sourceId===B?u.targetId:u.sourceId),F=-1,l.innerHTML="",l.classList.remove("hidden"),y&&l.classList.add("info-panel-maximized");const J=document.createElement("div");J.className="info-panel-header",J.appendChild(W());const ie=document.createElement("div");ie.className="info-header";const te=document.createElement("span");if(te.className="info-type-badge",te.textContent=K.type,te.style.backgroundColor=Ne(K.type),e){te.classList.add("info-editable");const u=document.createElement("button");u.className="info-inline-edit",u.textContent=to,u.addEventListener("click",E=>{E.stopPropagation();const R=document.createElement("input");R.type="text",R.className="info-edit-inline-input",R.value=K.type,te.textContent="",te.appendChild(R),R.focus(),R.select();const M=()=>{const O=R.value.trim();O&&O!==K.type?e.onChangeNodeType(B,O):(te.textContent=K.type,te.appendChild(u))};R.addEventListener("blur",M),R.addEventListener("keydown",O=>{O.key==="Enter"&&R.blur(),O.key==="Escape"&&(R.value=K.type,R.blur())})}),te.appendChild(u)}const ye=document.createElement("h3");ye.className="info-label",ye.textContent=it(K);const ke=document.createElement("span");ke.className="info-id",ke.textContent=K.id,ie.appendChild(te),ie.appendChild(ye),ie.appendChild(ke),J.appendChild(ie),l.appendChild(J);const _=document.createElement("div");_.className="info-panel-body";const H=Object.keys(K.properties),D=lt("Properties");if(H.length>0){const u=document.createElement("dl");u.className="info-props";for(const E of H){const R=document.createElement("dt");R.textContent=E;const M=document.createElement("dd");if(e){const O=Lt(K.properties[E]),k=document.createElement("textarea");k.className="info-edit-input",k.value=O,k.rows=1,k.addEventListener("input",()=>dn(k)),k.addEventListener("keydown",S=>{S.key==="Enter"&&!S.shiftKey&&(S.preventDefault(),k.blur())}),k.addEventListener("blur",()=>{const S=k.value;S!==O&&e.onUpdateNode(B,{[E]:so(S)})}),M.appendChild(k),requestAnimationFrame(()=>dn(k));const f=document.createElement("button");f.className="info-delete-prop",f.textContent="×",f.title=`Remove ${E}`,f.addEventListener("click",()=>{const S={...K.properties};delete S[E],e.onUpdateNode(B,S)}),M.appendChild(f)}else M.appendChild(oo(K.properties[E]));u.appendChild(R),u.appendChild(M)}D.appendChild(u)}if(e){const u=document.createElement("button");u.className="info-add-btn",u.textContent="+ Add property",u.addEventListener("click",()=>{const E=document.createElement("div");E.className="info-add-row";const R=document.createElement("input");R.type="text",R.className="info-edit-input",R.placeholder="key";const M=document.createElement("input");M.type="text",M.className="info-edit-input",M.placeholder="value";const O=document.createElement("button");O.className="info-add-save",O.textContent="Add",O.addEventListener("click",()=>{R.value&&e.onAddProperty(B,R.value,M.value)}),E.appendChild(R),E.appendChild(M),E.appendChild(O),D.appendChild(E),R.focus()}),D.appendChild(u)}if(_.appendChild(D),ce.length>0){const u=lt(`Connections (${ce.length})`),E=document.createElement("ul");E.className="info-connections";for(const R of ce){const M=R.sourceId===B,O=M?R.targetId:R.sourceId,k=ee.nodes.find(d=>d.id===O),f=k?it(k):O,S=document.createElement("li");if(S.className="info-connection",s&&k&&(S.classList.add("info-connection-link"),S.addEventListener("click",d=>{d.target.closest(".info-delete-edge")||j(O)})),k){const d=document.createElement("span");d.className="info-target-dot",d.style.backgroundColor=Ne(k.type),S.appendChild(d)}const q=document.createElement("span");q.className="info-arrow",q.textContent=M?"→":"←";const se=document.createElement("span");se.className="info-edge-type",se.textContent=R.type;const pe=document.createElement("span");pe.className="info-target",pe.textContent=f,S.appendChild(q),S.appendChild(se),S.appendChild(pe);const n=Object.keys(R.properties);if(n.length>0){const d=document.createElement("div");d.className="info-edge-props";for(const T of n){const G=document.createElement("span");G.className="info-edge-prop",G.textContent=`${T}: ${Lt(R.properties[T])}`,d.appendChild(G)}S.appendChild(d)}if(e){const d=document.createElement("button");d.className="info-delete-edge",d.textContent="×",d.title="Remove connection",d.addEventListener("click",T=>{T.stopPropagation(),e.onDeleteEdge(R.id)}),S.appendChild(d)}E.appendChild(S)}u.appendChild(E),_.appendChild(u)}const w=lt("Timestamps"),p=document.createElement("dl");p.className="info-props";const m=document.createElement("dt");m.textContent="created";const g=document.createElement("dd");g.textContent=pn(K.createdAt);const v=document.createElement("dt");v.textContent="updated";const N=document.createElement("dd");if(N.textContent=pn(K.updatedAt),p.appendChild(m),p.appendChild(g),p.appendChild(v),p.appendChild(N),w.appendChild(p),_.appendChild(w),e){const u=document.createElement("div");u.className="info-section info-danger";const E=document.createElement("button");E.className="info-delete-node",E.textContent="Delete node",E.addEventListener("click",()=>{e.onDeleteNode(B),C()}),u.appendChild(E),_.appendChild(u)}l.appendChild(_)}function V(B,ee){const K=new Set(B),ce=ee.nodes.filter(w=>K.has(w.id));if(ce.length===0)return;const J=ee.edges.filter(w=>K.has(w.sourceId)&&K.has(w.targetId));l.innerHTML="",l.classList.remove("hidden"),y&&l.classList.add("info-panel-maximized"),l.appendChild(W());const ie=document.createElement("div");ie.className="info-header";const te=document.createElement("h3");te.className="info-label",te.textContent=`${ce.length} nodes selected`,ie.appendChild(te);const ye=document.createElement("div");ye.className="info-badge-row";const ke=new Map;for(const w of ce)ke.set(w.type,(ke.get(w.type)??0)+1);for(const[w,p]of ke){const m=document.createElement("span");m.className="info-type-badge",m.style.backgroundColor=Ne(w),m.textContent=p>1?`${w} (${p})`:w,ye.appendChild(m)}ie.appendChild(ye),l.appendChild(ie);const _=lt("Selected Nodes"),H=document.createElement("ul");H.className="info-connections";for(const w of ce){const p=document.createElement("li");p.className="info-connection",s&&(p.classList.add("info-connection-link"),p.addEventListener("click",()=>{j(w.id)}));const m=document.createElement("span");m.className="info-target-dot",m.style.backgroundColor=Ne(w.type);const g=document.createElement("span");g.className="info-target",g.textContent=it(w);const v=document.createElement("span");v.className="info-edge-type",v.textContent=w.type,p.appendChild(m),p.appendChild(g),p.appendChild(v),H.appendChild(p)}_.appendChild(H),l.appendChild(_);const D=lt(J.length>0?`Connections Between Selected (${J.length})`:"Connections Between Selected");if(J.length===0){const w=document.createElement("p");w.className="info-empty-message",w.textContent="No direct connections between selected nodes",D.appendChild(w)}else{const w=document.createElement("ul");w.className="info-connections";for(const p of J){const m=ee.nodes.find(S=>S.id===p.sourceId),g=ee.nodes.find(S=>S.id===p.targetId),v=m?it(m):p.sourceId,N=g?it(g):p.targetId,u=document.createElement("li");if(u.className="info-connection",m){const S=document.createElement("span");S.className="info-target-dot",S.style.backgroundColor=Ne(m.type),u.appendChild(S)}const E=document.createElement("span");E.className="info-target",E.textContent=v;const R=document.createElement("span");R.className="info-arrow",R.textContent="→";const M=document.createElement("span");M.className="info-edge-type",M.textContent=p.type;const O=document.createElement("span");if(O.className="info-arrow",O.textContent="→",u.appendChild(E),u.appendChild(R),u.appendChild(M),u.appendChild(O),g){const S=document.createElement("span");S.className="info-target-dot",S.style.backgroundColor=Ne(g.type),u.appendChild(S)}const k=document.createElement("span");k.className="info-target",k.textContent=N,u.appendChild(k);const f=Object.keys(p.properties);if(f.length>0){const S=document.createElement("div");S.className="info-edge-props";for(const q of f){const se=document.createElement("span");se.className="info-edge-prop",se.textContent=`${q}: ${Lt(p.properties[q])}`,S.appendChild(se)}u.appendChild(S)}w.appendChild(u)}D.appendChild(w)}l.appendChild(D)}return{show(B,ee){if(P=ee,i=B,B.length===1&&!o){const K=B[0];x[c]!==K&&(c<x.length-1&&(x=x.slice(0,c+1)),x.push(K),c=x.length-1)}B.length===1?ne(B[0],ee):B.length>1&&V(B,ee)},hide:C,goBack:U,goForward:Q,cycleConnection(B){if(I.length===0)return null;F===-1?F=B===1?0:I.length-1:(F+=B,F>=I.length&&(F=0),F<0&&(F=I.length-1));const ee=l.querySelectorAll(".info-connection");return ee.forEach((K,ce)=>{K.classList.toggle("info-connection-active",ce===F)}),F>=0&&ee[F]&&ee[F].scrollIntoView({block:"nearest"}),I[F]??null},setFocusDisabled(B){a=B;const ee=l.querySelector(".info-focus-btn");ee&&(ee.disabled=B,ee.style.opacity=B?"0.3":"")},get visible(){return!l.classList.contains("hidden")}}}function lt(t){const e=document.createElement("div");e.className="info-section";const s=document.createElement("h4");return s.className="info-section-title",s.textContent=t,e.appendChild(s),e}function oo(t){if(Array.isArray(t)){const s=document.createElement("div");s.className="info-array";for(const r of t){const l=document.createElement("span");l.className="info-tag",l.textContent=String(r),s.appendChild(l)}return s}if(t!==null&&typeof t=="object"){const s=document.createElement("pre");return s.className="info-json",s.textContent=JSON.stringify(t,null,2),s}const e=document.createElement("span");return e.className="info-value",e.textContent=String(t??""),e}function Lt(t){return Array.isArray(t)?t.map(String).join(", "):t!==null&&typeof t=="object"?JSON.stringify(t):String(t??"")}function so(t){const e=t.trim();if(e==="true")return!0;if(e==="false")return!1;if(e!==""&&!isNaN(Number(e)))return Number(e);if(e.startsWith("[")&&e.endsWith("]")||e.startsWith("{")&&e.endsWith("}"))try{return JSON.parse(e)}catch{return t}return t}function dn(t){t.style.height="auto",t.style.height=t.scrollHeight+"px"}function pn(t){try{return new Date(t).toLocaleString()}catch{return t}}function vn(t){for(const e of Object.values(t.properties))if(typeof e=="string")return e;return t.id}function un(t,e){const s=e.toLowerCase();if(vn(t).toLowerCase().includes(s)||t.type.toLowerCase().includes(s))return!0;for(const r of Object.values(t.properties))if(typeof r=="string"&&r.toLowerCase().includes(s))return!0;return!1}function ao(t,e){const s=(e==null?void 0:e.maxResults)??8,r=(e==null?void 0:e.debounceMs)??150;let l=null,y=null,x=null,c=null;const o=document.createElement("div");o.className="search-overlay hidden";const P=document.createElement("div");P.className="search-input-wrap";const i=document.createElement("input");i.className="search-input",i.type="text",i.placeholder="Search nodes...",i.setAttribute("autocomplete","off"),i.setAttribute("spellcheck","false");const a=document.createElement("kbd");a.className="search-kbd",a.textContent="/",P.appendChild(i),P.appendChild(a);const I=document.createElement("ul");I.className="search-results hidden",o.appendChild(P),o.appendChild(I),t.appendChild(o);function F(){if(!l)return null;const W=i.value.trim();if(W.length===0)return null;const ne=new Set;for(const V of l.nodes)un(V,W)&&ne.add(V.id);return ne}function C(){const W=F();y==null||y(W),j()}function j(){I.innerHTML="",U=-1;const W=i.value.trim();if(!l||W.length===0){I.classList.add("hidden");return}const ne=[];for(const V of l.nodes)if(un(V,W)&&(ne.push(V),ne.length>=s))break;if(ne.length===0){I.classList.add("hidden");return}for(const V of ne){const B=document.createElement("li");B.className="search-result-item";const ee=document.createElement("span");ee.className="search-result-dot",ee.style.backgroundColor=Ne(V.type);const K=document.createElement("span");K.className="search-result-label";const ce=vn(V);K.textContent=ce.length>36?ce.slice(0,34)+"...":ce;const J=document.createElement("span");J.className="search-result-type",J.textContent=V.type,B.appendChild(ee),B.appendChild(K),B.appendChild(J),B.addEventListener("click",()=>{x==null||x(V.id),i.value="",I.classList.add("hidden"),C()}),I.appendChild(B)}I.classList.remove("hidden")}i.addEventListener("input",()=>{c&&clearTimeout(c),c=setTimeout(C,r)});let U=-1;function Q(){const W=I.querySelectorAll(".search-result-item");W.forEach((ne,V)=>{ne.classList.toggle("search-result-active",V===U)}),U>=0&&W[U]&&W[U].scrollIntoView({block:"nearest"})}return i.addEventListener("keydown",W=>{const ne=I.querySelectorAll(".search-result-item");W.key==="ArrowDown"?(W.preventDefault(),ne.length>0&&(U=Math.min(U+1,ne.length-1),Q())):W.key==="ArrowUp"?(W.preventDefault(),ne.length>0&&(U=Math.max(U-1,0),Q())):W.key==="Enter"?(W.preventDefault(),U>=0&&ne[U]?ne[U].click():ne.length>0&&ne[0].click(),i.blur()):W.key==="Escape"&&(i.value="",i.blur(),I.classList.add("hidden"),U=-1,C())}),document.addEventListener("click",W=>{o.contains(W.target)||I.classList.add("hidden")}),i.addEventListener("focus",()=>a.classList.add("hidden")),i.addEventListener("blur",()=>{i.value.length===0&&a.classList.remove("hidden")}),{setLearningGraphData(W){l=W,i.value="",I.classList.add("hidden"),l&&l.nodes.length>0?o.classList.remove("hidden"):o.classList.add("hidden")},onFilterChange(W){y=W},onNodeSelect(W){x=W},clear(){i.value="",I.classList.add("hidden"),y==null||y(null)},focus(){i.focus()}}}function co(t,e){let s=null,r=null,l=!0,y=null,x=!0,c=!0,o=!0,P="types",i="",a="",I=[],F=[];const C={types:new Set,nodeIds:new Set};function j(){if(!s)return[];const p=new Set;for(const m of s.nodes)C.types.has(m.type)&&p.add(m.id);for(const m of C.nodeIds)p.add(m);return[...p]}function U(p){if(C.nodeIds.has(p))return!0;const m=s==null?void 0:s.nodes.find(g=>g.id===p);return m?C.types.has(m.type):!1}function Q(){return C.types.size===0&&C.nodeIds.size===0}function W(){const p=j();e.onFocusChange(p.length>0?p:null)}const ne=document.createElement("button");ne.className="tools-pane-toggle hidden",ne.title="Graph Inspector",ne.innerHTML='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16"/><path d="M4 12h16"/><path d="M4 17h10"/></svg>';const V=document.createElement("div");V.className="tools-pane-content hidden",t.appendChild(ne),t.appendChild(V),ne.addEventListener("click",()=>{var p;l=!l,V.classList.toggle("hidden",l),ne.classList.toggle("active",!l),l||(p=e.onOpen)==null||p.call(e)});function B(){if(V.innerHTML="",!r)return;const p=document.createElement("div");if(p.className="tools-pane-summary",p.innerHTML=`<span>${r.nodeCount} nodes</span><span class="tools-pane-sep">·</span><span>${r.edgeCount} edges</span><span class="tools-pane-sep">·</span><span>${r.types.length} types</span>`,V.appendChild(p),s&&r.nodeCount>0){const N=Math.ceil(JSON.stringify(s).length/4),u=Math.round(N/r.nodeCount),E=Math.max(10,Math.round(u*.3)*Math.min(5,r.nodeCount)),R=N>E?Math.round((1-E/N)*100):0,M=document.createElement("div");M.className="tools-pane-token-card";const O=Math.min(100,R),k=document.createElement("div");k.className="token-card-label",k.textContent="Token Efficiency",M.appendChild(k);const f=document.createElement("div");f.className="token-card-stat",f.textContent=`~${N.toLocaleString()} tokens stored`,M.appendChild(f);const S=document.createElement("div");S.className="token-card-bar";const q=document.createElement("div");q.className="token-card-bar-fill",q.style.width=`${O}%`,S.appendChild(q),M.appendChild(S);const se=document.createElement("div");se.className="token-card-stat",se.textContent=`A search returns ~${E} tokens instead of ~${N.toLocaleString()} (${R}% reduction)`,M.appendChild(se),V.appendChild(M)}const m=document.createElement("div");m.className="tools-pane-tabs";const g=[{id:"types",label:"Types"},{id:"insights",label:"Insights"},{id:"controls",label:"Controls"}];for(const N of g){const u=document.createElement("button");u.className="tools-pane-tab",P===N.id&&u.classList.add("tools-pane-tab-active"),u.textContent=N.label,u.addEventListener("click",()=>{P=N.id,B()}),m.appendChild(u)}V.appendChild(m),Q()||ce(),F.length>0&&K(),P==="types"&&r.types.length>5?V.appendChild(ie("Filter types...",i,N=>{i=N,ee()})):P==="insights"&&r.orphans.length+r.singletons.length+r.emptyNodes.length>5&&V.appendChild(ie("Filter issues...",a,u=>{a=u,ee()}));const v=document.createElement("div");v.className="tools-pane-tab-content",V.appendChild(v),ee()}function ee(){const p=V.querySelector(".tools-pane-tab-content");p&&(p.innerHTML="",P==="types"?te(p):P==="insights"?ye(p):P==="controls"&&ke(p))}function K(){V.appendChild(H(`Walk Trail (${F.length})`,p=>{for(let g=0;g<F.length;g++){const v=F[g],N=g===F.length-1;if(v.edgeType){const f=document.createElement("div");f.className="walk-trail-edge",f.textContent=`↓ ${v.edgeType}`,p.appendChild(f)}const u=document.createElement("div");u.className="tools-pane-row tools-pane-clickable",N&&(u.style.fontWeight="600");const E=document.createElement("span");E.className="tools-pane-count",E.style.minWidth="18px",E.textContent=`${g+1}`;const R=document.createElement("span");R.className="tools-pane-dot",R.style.backgroundColor=Ne(v.type);const M=document.createElement("span");M.className="tools-pane-name",M.textContent=v.label;const O=document.createElement("span");O.className="tools-pane-count",O.textContent=v.type;const k=document.createElement("button");k.className="tools-pane-edit",k.style.opacity="1",k.textContent="×",k.title="Remove from trail",k.addEventListener("click",f=>{var S;f.stopPropagation(),(S=e.onWalkTrailRemove)==null||S.call(e,v.id)}),u.appendChild(E),u.appendChild(R),u.appendChild(M),u.appendChild(O),u.appendChild(k),u.addEventListener("click",()=>{e.onNavigateToNode(v.id)}),p.appendChild(u)}const m=document.createElement("div");if(m.className="tools-pane-export-row",e.onWalkIsolate){const g=document.createElement("button");g.className="tools-pane-export-btn",g.textContent="Isolate (I)",g.addEventListener("click",()=>e.onWalkIsolate()),m.appendChild(g)}if(e.onWalkSaveSnippet&&F.length>=2){const g=document.createElement("button");g.className="tools-pane-export-btn",g.textContent="Save snippet",g.addEventListener("click",()=>{gt("Save snippet","Name for this snippet").then(v=>{v&&e.onWalkSaveSnippet(v)})}),m.appendChild(g)}p.appendChild(m)}))}function ce(){if(!r||!s)return;const p=j();V.appendChild(H("Focused",m=>{for(const u of C.types){const E=r.types.find(S=>S.name===u);if(!E)continue;const R=document.createElement("div");R.className="tools-pane-row tools-pane-clickable";const M=document.createElement("span");M.className="tools-pane-dot",M.style.backgroundColor=Ne(E.name);const O=document.createElement("span");O.className="tools-pane-name",O.textContent=E.name;const k=document.createElement("span");k.className="tools-pane-count",k.textContent=`${E.count} nodes`;const f=document.createElement("button");f.className="tools-pane-edit tools-pane-focus-active",f.style.opacity="1",f.textContent="×",f.title=`Remove ${E.name} from focus`,R.appendChild(M),R.appendChild(O),R.appendChild(k),R.appendChild(f),f.addEventListener("click",S=>{S.stopPropagation(),C.types.delete(E.name),W(),B()}),m.appendChild(R)}for(const u of C.nodeIds){const E=s.nodes.find(q=>q.id===u);if(!E)continue;const R=mn(E.properties)??E.id,M=document.createElement("div");M.className="tools-pane-row tools-pane-clickable";const O=document.createElement("span");O.className="tools-pane-dot",O.style.backgroundColor=Ne(E.type);const k=document.createElement("span");k.className="tools-pane-name",k.textContent=R;const f=document.createElement("span");f.className="tools-pane-count",f.textContent=E.type;const S=document.createElement("button");S.className="tools-pane-edit tools-pane-focus-active",S.style.opacity="1",S.textContent="×",S.title=`Remove ${R} from focus`,M.appendChild(O),M.appendChild(k),M.appendChild(f),M.appendChild(S),M.addEventListener("click",q=>{q.target.closest(".tools-pane-edit")||e.onNavigateToNode(u)}),S.addEventListener("click",q=>{q.stopPropagation(),C.nodeIds.delete(u),W(),B()}),m.appendChild(M)}const g=document.createElement("div");g.className="tools-pane-row tools-pane-clickable tools-pane-focus-clear";const v=document.createElement("span");v.className="tools-pane-name",v.style.color="var(--accent)",v.textContent=`${p.length} total`;const N=document.createElement("span");N.className="tools-pane-badge",N.textContent="clear all",g.appendChild(v),g.appendChild(N),g.addEventListener("click",()=>{C.types.clear(),C.nodeIds.clear(),W(),B()}),m.appendChild(g)}))}function J(p){const m=document.createElement("div");m.className="tools-pane-row tools-pane-clickable",y===p.name&&m.classList.add("active");const g=document.createElement("span");g.className="tools-pane-dot",g.style.backgroundColor=Ne(p.name);const v=document.createElement("span");v.className="tools-pane-name",v.textContent=p.name;const N=document.createElement("span");N.className="tools-pane-count",N.textContent=String(p.count);const u=document.createElement("button");u.className="tools-pane-edit tools-pane-focus-toggle",C.types.has(p.name)&&u.classList.add("tools-pane-focus-active"),u.textContent="◎",u.title=C.types.has(p.name)?`Remove ${p.name} from focus`:`Add ${p.name} to focus`;const E=document.createElement("button");return E.className="tools-pane-edit",E.textContent="✎",E.title=`Rename all ${p.name} nodes`,m.appendChild(g),m.appendChild(v),m.appendChild(N),m.appendChild(u),m.appendChild(E),m.addEventListener("click",R=>{R.target.closest(".tools-pane-edit")||(y===p.name?(y=null,e.onFilterByType(null)):(y=p.name,e.onFilterByType(p.name)),B())}),u.addEventListener("click",R=>{R.stopPropagation(),C.types.has(p.name)?C.types.delete(p.name):C.types.add(p.name),W(),B()}),E.addEventListener("click",R=>{R.stopPropagation(),D(m,p.name,M=>{M&&M!==p.name&&e.onRenameNodeType(p.name,M)})}),m}function ie(p,m,g){const v=document.createElement("input");return v.type="text",v.className="tools-pane-search",v.placeholder=p,v.value=m,v.addEventListener("input",()=>g(v.value)),v}function te(p){if(!r)return;const m=i.toLowerCase();if(r.types.length){const v=r.types.filter(N=>!C.types.has(N.name)).filter(N=>!m||N.name.toLowerCase().includes(m));v.length>0&&p.appendChild(H("Node Types",N=>{for(const u of v)N.appendChild(J(u))}))}const g=r.edgeTypes.filter(v=>!m||v.name.toLowerCase().includes(m));g.length&&p.appendChild(H("Edge Types",v=>{for(const N of g){const u=document.createElement("div");u.className="tools-pane-row tools-pane-clickable";const E=document.createElement("span");E.className="tools-pane-name",E.textContent=N.name;const R=document.createElement("span");R.className="tools-pane-count",R.textContent=String(N.count);const M=document.createElement("button");M.className="tools-pane-edit",M.textContent="✎",M.title=`Rename all ${N.name} edges`,u.appendChild(E),u.appendChild(R),u.appendChild(M),M.addEventListener("click",O=>{O.stopPropagation(),D(u,N.name,k=>{k&&k!==N.name&&e.onRenameEdgeType(N.name,k)})}),v.appendChild(u)}}))}function ye(p){if(!r)return;const m=a.toLowerCase(),g=r.starred.filter(k=>!m||k.label.toLowerCase().includes(m)||k.type.toLowerCase().includes(m));g.length&&p.appendChild(H("★ Starred",k=>{for(const q of g){const se=document.createElement("div");se.className="tools-pane-row tools-pane-clickable";const pe=document.createElement("span");pe.className="tools-pane-dot",pe.style.backgroundColor=Ne(q.type);const n=document.createElement("span");n.className="tools-pane-name",n.textContent=q.label;const d=document.createElement("button");d.className="tools-pane-edit tools-pane-focus-toggle",U(q.id)&&d.classList.add("tools-pane-focus-active"),d.textContent="◎",d.title=U(q.id)?`Remove ${q.label} from focus`:`Add ${q.label} to focus`,se.appendChild(pe),se.appendChild(n),se.appendChild(d),se.addEventListener("click",T=>{T.target.closest(".tools-pane-edit")||e.onNavigateToNode(q.id)}),d.addEventListener("click",T=>{T.stopPropagation(),C.nodeIds.has(q.id)?C.nodeIds.delete(q.id):C.nodeIds.add(q.id),W(),B()}),k.appendChild(se)}const f=document.createElement("div");f.className="tools-pane-row tools-pane-actions";const S=document.createElement("button");if(S.className="tools-pane-action-btn",S.textContent="Focus all",S.title="Enter focus mode with all starred nodes",S.addEventListener("click",()=>{C.nodeIds.clear(),C.types.clear();for(const q of r.starred)C.nodeIds.add(q.id);W(),B()}),f.appendChild(S),e.onStarredSaveSnippet){const q=document.createElement("button");q.className="tools-pane-action-btn",q.textContent="Save as snippet",q.title="Save starred nodes as a reusable snippet",q.addEventListener("click",async()=>{const se=await gt("Snippet name","starred");se&&e.onStarredSaveSnippet(se,r.starred.map(pe=>pe.id))}),f.appendChild(q)}k.appendChild(f)}));const v=r.mostConnected.filter(k=>!m||k.label.toLowerCase().includes(m)||k.type.toLowerCase().includes(m));v.length&&p.appendChild(H("Most Connected",k=>{for(const f of v){const S=document.createElement("div");S.className="tools-pane-row tools-pane-clickable";const q=document.createElement("span");q.className="tools-pane-dot",q.style.backgroundColor=Ne(f.type);const se=document.createElement("span");se.className="tools-pane-name",se.textContent=f.label;const pe=document.createElement("span");pe.className="tools-pane-count",pe.textContent=`${f.connections}`;const n=document.createElement("button");n.className="tools-pane-edit tools-pane-focus-toggle",U(f.id)&&n.classList.add("tools-pane-focus-active"),n.textContent="◎",n.title=U(f.id)?`Remove ${f.label} from focus`:`Add ${f.label} to focus`,S.appendChild(q),S.appendChild(se),S.appendChild(pe),S.appendChild(n),S.addEventListener("click",d=>{d.target.closest(".tools-pane-edit")||e.onNavigateToNode(f.id)}),n.addEventListener("click",d=>{d.stopPropagation(),C.nodeIds.has(f.id)?C.nodeIds.delete(f.id):C.nodeIds.add(f.id),W(),B()}),k.appendChild(S)}}));const N=r.orphans.filter(k=>!m||k.label.toLowerCase().includes(m)||k.type.toLowerCase().includes(m)),u=r.singletons.filter(k=>!m||k.name.toLowerCase().includes(m)),E=r.emptyNodes.filter(k=>!m||k.label.toLowerCase().includes(m)||k.type.toLowerCase().includes(m)),R=N.length>0,M=u.length>0,O=E.length>0;if(!R&&!M&&!O){const k=document.createElement("div");k.className="tools-pane-empty-msg",k.textContent="No issues found",p.appendChild(k);return}R&&p.appendChild(H("Orphans",k=>{for(const f of N.slice(0,5)){const S=document.createElement("div");S.className="tools-pane-row tools-pane-clickable tools-pane-issue";const q=document.createElement("span");q.className="tools-pane-dot",q.style.backgroundColor=Ne(f.type);const se=document.createElement("span");se.className="tools-pane-name",se.textContent=f.label;const pe=document.createElement("span");pe.className="tools-pane-badge",pe.textContent="orphan";const n=document.createElement("button");n.className="tools-pane-edit tools-pane-focus-toggle",U(f.id)&&n.classList.add("tools-pane-focus-active"),n.textContent="◎",n.title=U(f.id)?`Remove ${f.label} from focus`:`Add ${f.label} to focus`,S.appendChild(q),S.appendChild(se),S.appendChild(pe),S.appendChild(n),S.addEventListener("click",d=>{d.target.closest(".tools-pane-edit")||e.onNavigateToNode(f.id)}),n.addEventListener("click",d=>{d.stopPropagation(),C.nodeIds.has(f.id)?C.nodeIds.delete(f.id):C.nodeIds.add(f.id),W(),B()}),k.appendChild(S)}if(N.length>5){const f=document.createElement("div");f.className="tools-pane-more",f.textContent=`+ ${N.length-5} more orphans`,k.appendChild(f)}})),M&&p.appendChild(H("Singletons",k=>{for(const f of u.slice(0,5)){const S=document.createElement("div");S.className="tools-pane-row tools-pane-issue";const q=document.createElement("span");q.className="tools-pane-dot",q.style.backgroundColor=Ne(f.name);const se=document.createElement("span");se.className="tools-pane-name",se.textContent=f.name;const pe=document.createElement("span");pe.className="tools-pane-badge",pe.textContent="1 node",S.appendChild(q),S.appendChild(se),S.appendChild(pe),k.appendChild(S)}})),O&&p.appendChild(H("Empty Nodes",k=>{for(const f of E.slice(0,5)){const S=document.createElement("div");S.className="tools-pane-row tools-pane-issue";const q=document.createElement("span");q.className="tools-pane-dot",q.style.backgroundColor=Ne(f.type);const se=document.createElement("span");se.className="tools-pane-name",se.textContent=f.label;const pe=document.createElement("span");pe.className="tools-pane-badge",pe.textContent="empty",S.appendChild(q),S.appendChild(se),S.appendChild(pe),k.appendChild(S)}if(r.emptyNodes.length>5){const f=document.createElement("div");f.className="tools-pane-more",f.textContent=`+ ${r.emptyNodes.length-5} more empty nodes`,k.appendChild(f)}}))}function ke(p){p.appendChild(H("Display",m=>{const g=document.createElement("div");g.className="tools-pane-row tools-pane-clickable";const v=document.createElement("input");v.type="checkbox",v.checked=x,v.className="tools-pane-checkbox";const N=document.createElement("span");N.className="tools-pane-name",N.textContent="Edge labels",g.appendChild(v),g.appendChild(N),g.addEventListener("click",f=>{f.target!==v&&(v.checked=!v.checked),x=v.checked,e.onToggleEdgeLabels(x)}),m.appendChild(g);const u=document.createElement("div");u.className="tools-pane-row tools-pane-clickable";const E=document.createElement("input");E.type="checkbox",E.checked=c,E.className="tools-pane-checkbox";const R=document.createElement("span");R.className="tools-pane-name",R.textContent="Type regions",u.appendChild(E),u.appendChild(R),u.addEventListener("click",f=>{f.target!==E&&(E.checked=!E.checked),c=E.checked,e.onToggleTypeHulls(c)}),m.appendChild(u);const M=document.createElement("div");M.className="tools-pane-row tools-pane-clickable";const O=document.createElement("input");O.type="checkbox",O.checked=o,O.className="tools-pane-checkbox";const k=document.createElement("span");k.className="tools-pane-name",k.textContent="Minimap",M.appendChild(O),M.appendChild(k),M.addEventListener("click",f=>{f.target!==O&&(O.checked=!O.checked),o=O.checked,e.onToggleMinimap(o)}),m.appendChild(M)})),p.appendChild(H("Layout",m=>{m.appendChild(_("Clustering",0,1,.02,.08,g=>{e.onLayoutChange("clusterStrength",g)})),m.appendChild(_("Spacing",.5,20,.5,1.5,g=>{e.onLayoutChange("spacing",g)})),m.appendChild(_("Pan speed",20,200,10,60,g=>{e.onPanSpeedChange(g)}))})),p.appendChild(H("Export",m=>{const g=document.createElement("div");g.className="tools-pane-export-row";const v=document.createElement("button");v.className="tools-pane-export-btn",v.textContent="Export PNG",v.addEventListener("click",()=>e.onExport("png"));const N=document.createElement("button");N.className="tools-pane-export-btn",N.textContent="Export SVG",N.addEventListener("click",()=>e.onExport("svg")),g.appendChild(v),g.appendChild(N),m.appendChild(g)})),(e.onSnapshot||e.onRollback)&&p.appendChild(H("Versions",m=>{const g=document.createElement("div");g.className="tools-pane-export-row";const v=document.createElement("button");if(v.className="tools-pane-export-btn",v.textContent="Save snapshot",v.addEventListener("click",()=>{gt("Save snapshot","Label (optional)").then(N=>{var u;(u=e.onSnapshot)==null||u.call(e,N||void 0)})}),g.appendChild(v),m.appendChild(g),I.length>0)for(const N of I){const u=document.createElement("div");u.className="tools-pane-row";const E=document.createElement("span");E.className="tools-pane-name";const R=io(N.timestamp);E.textContent=N.label?`#${N.version} ${N.label}`:`#${N.version}`,E.title=`${R} — ${N.nodeCount} nodes, ${N.edgeCount} edges`;const M=document.createElement("span");M.className="tools-pane-count",M.textContent=R;const O=document.createElement("button");O.className="tools-pane-edit",O.style.opacity="1",O.textContent="↩",O.title="Restore this snapshot",O.addEventListener("click",()=>{hn("Restore snapshot",`Restore snapshot #${N.version}? Current state will be lost unless you save a snapshot first.`).then(k=>{var f;k&&((f=e.onRollback)==null||f.call(e,N.version))})}),u.appendChild(E),u.appendChild(M),u.appendChild(O),m.appendChild(u)}else{const N=document.createElement("div");N.className="tools-pane-empty-msg",N.textContent="No snapshots yet",m.appendChild(N)}}))}function _(p,m,g,v,N,u){const E=document.createElement("div");E.className="tools-pane-slider-row";const R=document.createElement("span");R.className="tools-pane-slider-label",R.textContent=p;const M=document.createElement("input");M.type="range",M.className="tools-pane-slider",M.min=String(m),M.max=String(g),M.step=String(v),M.value=String(N);const O=document.createElement("span");return O.className="tools-pane-slider-value",O.textContent=String(N),M.addEventListener("input",()=>{const k=parseFloat(M.value);O.textContent=k%1===0?String(k):k.toFixed(2),u(k)}),E.appendChild(R),E.appendChild(M),E.appendChild(O),E}function H(p,m){const g=document.createElement("div");g.className="tools-pane-section";const v=document.createElement("div");return v.className="tools-pane-heading",v.textContent=p,g.appendChild(v),m(g),g}function D(p,m,g){const v=document.createElement("input");v.className="tools-pane-inline-input",v.value=m,v.type="text";const N=p.innerHTML;p.innerHTML="",p.classList.add("tools-pane-editing"),p.appendChild(v),v.focus(),v.select();function u(){const E=v.value.trim();p.classList.remove("tools-pane-editing"),E&&E!==m?g(E):p.innerHTML=N}v.addEventListener("keydown",E=>{E.key==="Enter"&&(E.preventDefault(),u()),E.key==="Escape"&&(p.innerHTML=N,p.classList.remove("tools-pane-editing"))}),v.addEventListener("blur",u)}function w(p){const m=new Map,g=new Map,v=new Map,N=new Set;for(const f of p.nodes)m.set(f.type,(m.get(f.type)??0)+1);for(const f of p.edges)g.set(f.type,(g.get(f.type)??0)+1),v.set(f.sourceId,(v.get(f.sourceId)??0)+1),v.set(f.targetId,(v.get(f.targetId)??0)+1),N.add(f.sourceId),N.add(f.targetId);const u=f=>mn(f.properties)??f.id,E=p.nodes.filter(f=>f.properties._starred===!0).map(f=>({id:f.id,label:u(f),type:f.type})),R=p.nodes.filter(f=>!N.has(f.id)).map(f=>({id:f.id,label:u(f),type:f.type})),M=[...m.entries()].filter(([,f])=>f===1).map(([f])=>({name:f})),O=p.nodes.filter(f=>Object.keys(f.properties).length===0).map(f=>({id:f.id,label:f.id,type:f.type})),k=p.nodes.map(f=>({id:f.id,label:u(f),type:f.type,connections:v.get(f.id)??0})).filter(f=>f.connections>0).sort((f,S)=>S.connections-f.connections).slice(0,5);return{nodeCount:p.nodes.length,edgeCount:p.edges.length,types:[...m.entries()].sort((f,S)=>S[1]-f[1]).map(([f,S])=>({name:f,count:S})),edgeTypes:[...g.entries()].sort((f,S)=>S[1]-f[1]).map(([f,S])=>({name:f,count:S})),starred:E,orphans:R,singletons:M,emptyNodes:O,mostConnected:k}}return{collapse(){l=!0,V.classList.add("hidden"),ne.classList.remove("active")},addToFocusSet(p){for(const m of p)C.nodeIds.add(m);W(),B()},clearFocusSet(){C.types.clear(),C.nodeIds.clear(),W(),B()},setData(p){s=p,y=null,C.types.clear(),C.nodeIds.clear(),s&&s.nodes.length>0?(r=w(s),ne.classList.remove("hidden"),B()):(r=null,ne.classList.add("hidden"),V.classList.add("hidden"))},setSnapshots(p){I=p,P==="controls"&&ee()},setWalkTrail(p){F=p,B()}}}function io(t){const e=Date.now()-new Date(t).getTime(),s=Math.floor(e/6e4);if(s<1)return"just now";if(s<60)return`${s}m ago`;const r=Math.floor(s/60);return r<24?`${r}h ago`:`${Math.floor(r/24)}d ago`}function mn(t){for(const e of Object.values(t))if(typeof e=="string")return e;return null}function lo(t,e){const s=e.split("+"),r=s.pop(),l=s.map(o=>o.toLowerCase()),y=l.includes("ctrl")||l.includes("cmd")||l.includes("meta"),x=l.includes("shift"),c=l.includes("alt");return y!==(t.ctrlKey||t.metaKey)||!y&&(t.ctrlKey||t.metaKey)||x&&!t.shiftKey||c!==t.altKey?!1:r.toLowerCase()==="escape"?t.key==="Escape":r.toLowerCase()==="tab"?t.key==="Tab":l.length>0?t.key.toLowerCase()===r.toLowerCase():t.key===r}function ro(){return{search:"Focus search",searchAlt:"Focus search (alt)",undo:"Undo",redo:"Redo",help:"Toggle help",escape:"Exit focus / close panel",focus:"Focus on selected / exit focus",toggleEdges:"Toggle edges on/off",center:"Center view on graph",nextNode:"Next node in view",prevNode:"Previous node in view",nextConnection:"Next connection",prevConnection:"Previous connection",historyBack:"Node history back",historyForward:"Node history forward",hopsIncrease:"Increase hops",hopsDecrease:"Decrease hops",panLeft:"Pan left",panDown:"Pan down",panUp:"Pan up",panRight:"Pan right",panFastLeft:"Pan fast left",zoomOut:"Zoom out",zoomIn:"Zoom in",panFastRight:"Pan fast right",spacingDecrease:"Decrease spacing",spacingIncrease:"Increase spacing",clusteringDecrease:"Decrease clustering",clusteringIncrease:"Increase clustering",toggleSidebar:"Toggle sidebar",walkMode:"Toggle walk mode (in focus)",walkIsolate:"Isolate walk trail nodes"}}const po=[{key:"Click",description:"Select node"},{key:"Ctrl+Click",description:"Multi-select nodes"},{key:"Drag",description:"Pan canvas"},{key:"Scroll",description:"Zoom in/out"}],uo=["search","searchAlt","undo","redo","help","focus","toggleEdges","center","nextNode","prevNode","nextConnection","prevConnection","historyBack","historyForward","hopsIncrease","hopsDecrease","panLeft","panDown","panUp","panRight","panFastLeft","panFastRight","zoomIn","zoomOut","spacingDecrease","spacingIncrease","clusteringDecrease","clusteringIncrease","toggleSidebar","walkMode","walkIsolate","escape"];function mo(t){return t.split("+").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join("+")}function ho(t,e){const s=ro(),r=document.createElement("div");r.className="shortcuts-overlay hidden";const l=document.createElement("div");l.className="shortcuts-modal";const y=document.createElement("h3");y.className="shortcuts-title",y.textContent="Keyboard Shortcuts";const x=document.createElement("div");x.className="shortcuts-list";for(const a of uo){const I=e[a];if(!I)continue;const F=document.createElement("div");F.className="shortcuts-row";const C=document.createElement("div");C.className="shortcuts-keys";const j=document.createElement("kbd");j.textContent=mo(I),C.appendChild(j);const U=document.createElement("span");U.className="shortcuts-desc",U.textContent=s[a],F.appendChild(C),F.appendChild(U),x.appendChild(F)}for(const a of po){const I=document.createElement("div");I.className="shortcuts-row";const F=document.createElement("div");F.className="shortcuts-keys";const C=document.createElement("kbd");C.textContent=a.key,F.appendChild(C);const j=document.createElement("span");j.className="shortcuts-desc",j.textContent=a.description,I.appendChild(F),I.appendChild(j),x.appendChild(I)}const c=document.createElement("button");c.className="shortcuts-close",c.textContent="×",l.appendChild(c),l.appendChild(y),l.appendChild(x),r.appendChild(l),t.appendChild(r);function o(){r.classList.remove("hidden")}function P(){r.classList.add("hidden")}function i(){r.classList.toggle("hidden")}return c.addEventListener("click",P),r.addEventListener("click",a=>{a.target===r&&P()}),{show:o,hide:P,toggle:i}}function fo(t){const e=document.createElement("div");return e.className="empty-state",e.innerHTML=`
|
|
5
|
+
<div class="empty-state-bg">
|
|
6
|
+
<div class="empty-state-circle c1"></div>
|
|
7
|
+
<div class="empty-state-circle c2"></div>
|
|
8
|
+
<div class="empty-state-circle c3"></div>
|
|
9
|
+
<div class="empty-state-circle c4"></div>
|
|
10
|
+
<div class="empty-state-circle c5"></div>
|
|
11
|
+
<svg class="empty-state-lines" viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice">
|
|
12
|
+
<line x1="80" y1="60" x2="220" y2="140" stroke="currentColor" stroke-width="0.5" opacity="0.15"/>
|
|
13
|
+
<line x1="220" y1="140" x2="320" y2="80" stroke="currentColor" stroke-width="0.5" opacity="0.15"/>
|
|
14
|
+
<line x1="220" y1="140" x2="160" y2="240" stroke="currentColor" stroke-width="0.5" opacity="0.15"/>
|
|
15
|
+
<line x1="160" y1="240" x2="300" y2="220" stroke="currentColor" stroke-width="0.5" opacity="0.15"/>
|
|
16
|
+
</svg>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="empty-state-content">
|
|
19
|
+
<div class="empty-state-icon">
|
|
20
|
+
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
21
|
+
<path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 002 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0022 16z"/>
|
|
22
|
+
<polyline points="3.27 6.96 12 12.01 20.73 6.96"/>
|
|
23
|
+
<line x1="12" y1="22.08" x2="12" y2="12"/>
|
|
24
|
+
</svg>
|
|
25
|
+
</div>
|
|
26
|
+
<h2 class="empty-state-title">No learning graphs yet</h2>
|
|
27
|
+
<p class="empty-state-desc">Connect Backpack to Claude, then start a conversation. Claude will build your first learning graph automatically.</p>
|
|
28
|
+
<div class="empty-state-setup">
|
|
29
|
+
<div class="empty-state-label">Add Backpack to Claude Code:</div>
|
|
30
|
+
<code class="empty-state-code">claude mcp add backpack-local -s user -- npx backpack-ontology@latest</code>
|
|
31
|
+
</div>
|
|
32
|
+
<p class="empty-state-hint">Press <kbd>?</kbd> for keyboard shortcuts</p>
|
|
33
|
+
</div>
|
|
34
|
+
`,t.appendChild(e),{show(){e.classList.remove("hidden")},hide(){e.classList.add("hidden")}}}const go=30;function yo(){let t=[],e=[];return{push(s){t.push(JSON.stringify(s)),t.length>go&&t.shift(),e=[]},undo(s){return t.length===0?null:(e.push(JSON.stringify(s)),JSON.parse(t.pop()))},redo(s){return e.length===0?null:(t.push(JSON.stringify(s)),JSON.parse(e.pop()))},canUndo(){return t.length>0},canRedo(){return e.length>0},clear(){t=[],e=[]}}}function Co(t,e){let s=null;function r(x,c,o,P,i){l(),s=document.createElement("div"),s.className="context-menu",s.style.left=`${P}px`,s.style.top=`${i}px`;const a=[{label:o?"★ Unstar":"☆ Star",action:()=>e.onStar(x),premium:!1},{label:"◎ Focus on node",action:()=>e.onFocusNode(x),premium:!1},{label:"⑂ Explore in branch",action:()=>e.onExploreInBranch(x),premium:!1},{label:"⎘ Copy ID",action:()=>e.onCopyId(x),premium:!1}];e.onExpand&&a.push({label:"⊕ Expand node",action:()=>e.onExpand(x),premium:!0}),e.onExplainPath&&a.push({label:"↔ Explain path to…",action:()=>e.onExplainPath(x),premium:!0}),e.onEnrich&&a.push({label:"≡ Enrich from web",action:()=>e.onEnrich(x),premium:!0});let I=!1;for(const C of a){if(!I&&C.premium){const U=document.createElement("div");U.className="context-menu-separator",s.appendChild(U),I=!0}const j=document.createElement("div");j.className="context-menu-item",j.textContent=C.label,j.addEventListener("click",()=>{C.action(),l()}),s.appendChild(j)}t.appendChild(s);const F=s.getBoundingClientRect();F.right>window.innerWidth&&(s.style.left=`${P-F.width}px`),F.bottom>window.innerHeight&&(s.style.top=`${i-F.height}px`),setTimeout(()=>document.addEventListener("click",l),0),document.addEventListener("keydown",y)}function l(){s&&(s.remove(),s=null),document.removeEventListener("click",l),document.removeEventListener("keydown",y)}function y(x){x.key==="Escape"&&l()}return{show:r,hide:l}}const vo={search:"/",searchAlt:"ctrl+k",undo:"ctrl+z",redo:"ctrl+shift+z",help:"?",escape:"Escape",focus:"f",toggleEdges:"e",center:"c",nextNode:".",prevNode:",",nextConnection:">",prevConnection:"<",historyBack:"(",historyForward:")",hopsIncrease:"=",hopsDecrease:"-",panLeft:"h",panDown:"j",panUp:"k",panRight:"l",panFastLeft:"H",zoomOut:"J",zoomIn:"K",panFastRight:"L",spacingDecrease:"[",spacingIncrease:"]",clusteringDecrease:"{",clusteringIncrease:"}",toggleSidebar:"Tab",walkMode:"w",walkIsolate:"i"},xo={host:"127.0.0.1",port:5173},bo={edges:!0,edgeLabels:!0,typeHulls:!0,minimap:!0,theme:"system"},Eo={spacing:1.5,clustering:.08},wo={panSpeed:60,panFastMultiplier:3,zoomFactor:1.3,zoomMin:.05,zoomMax:10,panAnimationMs:300},ko={hideBadges:.4,hideLabels:.25,hideEdgeLabels:.35,smallNodes:.2,hideArrows:.15},No={pulseSpeed:.02},So={maxSearchResults:8,maxQualityItems:5,maxMostConnected:5,searchDebounceMs:150},Lo={keybindings:vo,server:xo,display:bo,layout:Eo,navigation:wo,lod:ko,walk:No,limits:So};let de="",L=null,It=new Set,Mt=!1;async function Io(){const t=document.getElementById("canvas-container"),e={...Lo};try{const n=await fetch("/api/config");if(n.ok){const d=await n.json();Object.assign(e.keybindings,d.keybindings??{}),Object.assign(e.display,d.display??{}),Object.assign(e.layout,d.layout??{}),Object.assign(e.navigation,d.navigation??{}),Object.assign(e.lod,d.lod??{}),Object.assign(e.limits,d.limits??{})}}catch{}const s=e.keybindings,r=window.matchMedia("(prefers-color-scheme: dark)"),l=e.display.theme==="system"?r.matches?"dark":"light":e.display.theme,x=localStorage.getItem("backpack-theme")??l;document.documentElement.setAttribute("data-theme",x);const c=document.createElement("button");c.className="theme-toggle",c.textContent=x==="light"?"☾":"☼",c.title="Toggle light/dark mode",c.addEventListener("click",()=>{const d=document.documentElement.getAttribute("data-theme")==="light"?"dark":"light";document.documentElement.setAttribute("data-theme",d),localStorage.setItem("backpack-theme",d),c.textContent=d==="light"?"☾":"☼"}),t.appendChild(c);const o=yo();async function P(){if(!de||!L)return;L.metadata.updatedAt=new Date().toISOString(),await wt(de,L),a.loadGraph(L),ce.setLearningGraphData(L),J.setData(L);const n=await at();w.setSummaries(n)}async function i(n){L=n,await wt(de,L),a.loadGraph(L),ce.setLearningGraphData(L),J.setData(L);const d=await at();w.setSummaries(d)}let a;const I=no(t,{onUpdateNode(n,d){if(!L)return;o.push(L);const T=L.nodes.find(G=>G.id===n);T&&(T.properties={...T.properties,...d},T.updatedAt=new Date().toISOString(),P().then(()=>I.show([n],L)))},onChangeNodeType(n,d){if(!L)return;o.push(L);const T=L.nodes.find(G=>G.id===n);T&&(T.type=d,T.updatedAt=new Date().toISOString(),P().then(()=>I.show([n],L)))},onDeleteNode(n){L&&(o.push(L),L.nodes=L.nodes.filter(d=>d.id!==n),L.edges=L.edges.filter(d=>d.sourceId!==n&&d.targetId!==n),P())},onDeleteEdge(n){var T;if(!L)return;o.push(L);const d=(T=L.edges.find(G=>G.id===n))==null?void 0:T.sourceId;L.edges=L.edges.filter(G=>G.id!==n),P().then(()=>{d&&L&&I.show([d],L)})},onAddProperty(n,d,T){if(!L)return;o.push(L);const G=L.nodes.find(re=>re.id===n);G&&(G.properties[d]=T,G.updatedAt=new Date().toISOString(),P().then(()=>I.show([n],L)))}},n=>{a.panToNode(n)},n=>{J.addToFocusSet(n)}),F=window.matchMedia("(max-width: 768px)");let C=[],j=e.display.edges,U=e.navigation.panSpeed,Q=-1,W=null;function ne(n){W&&W.remove(),W=document.createElement("div"),W.className="focus-indicator";const d=document.createElement("span");d.className="focus-indicator-label",d.textContent=`Focused: ${n.totalNodes} nodes`;const T=document.createElement("span");T.className="focus-indicator-hops",T.textContent=`${n.hops}`;const G=document.createElement("button");G.className="focus-indicator-btn",G.textContent="−",G.title="Fewer hops",G.disabled=n.hops===0,G.addEventListener("click",()=>{a.enterFocus(n.seedNodeIds,Math.max(0,n.hops-1))});const re=document.createElement("button");re.className="focus-indicator-btn",re.textContent="+",re.title="More hops",re.disabled=!1,re.addEventListener("click",()=>{a.enterFocus(n.seedNodeIds,n.hops+1)});const me=document.createElement("button");me.className="focus-indicator-btn focus-indicator-exit",me.textContent="×",me.title="Exit focus (Esc)",me.addEventListener("click",()=>J.clearFocusSet());const le=document.createElement("button");le.className="walk-indicator",a.getWalkMode()&&le.classList.add("active"),le.textContent="Walk",le.title="Toggle walk mode (W) — click nodes to traverse",le.addEventListener("click",()=>{a.setWalkMode(!a.getWalkMode()),le.classList.toggle("active",a.getWalkMode())}),W.appendChild(d),W.appendChild(G),W.appendChild(T),W.appendChild(re),W.appendChild(le),W.appendChild(me)}function V(){W&&(W.remove(),W=null)}const B=document.createElement("div");B.className="path-bar hidden",t.appendChild(B);function ee(n){if(B.innerHTML="",!L)return;for(let T=0;T<n.nodeIds.length;T++){const G=n.nodeIds[T],re=L.nodes.find(ge=>ge.id===G);if(!re)continue;const me=Object.values(re.properties).find(ge=>typeof ge=="string")??re.id;if(T>0){const ge=n.edgeIds[T-1],Le=L.edges.find(He=>He.id===ge),xe=document.createElement("span");xe.className="path-bar-edge",xe.textContent=Le?`→ ${Le.type} →`:"→",B.appendChild(xe)}const le=document.createElement("span");le.className="path-bar-node",le.textContent=me,le.addEventListener("click",()=>a.panToNode(G)),B.appendChild(le)}const d=document.createElement("button");d.className="path-bar-close",d.textContent="×",d.addEventListener("click",K),B.appendChild(d),B.classList.remove("hidden")}function K(){B.classList.add("hidden"),B.innerHTML="",a.clearHighlightedPath()}a=eo(t,n=>{if(C=n??[],!a.getWalkMode())if(n&&n.length===2){const d=a.findPath(n[0],n[1]);d&&d.nodeIds.length>0?(a.setHighlightedPath(d.nodeIds,d.edgeIds),ee(d)):K()}else K();n&&n.length>0&&L?(I.show(n,L),F.matches&&J.collapse(),M(de,n)):(I.hide(),de&&M(de))},n=>{if(n){ne(n);const d=t.querySelector(".canvas-top-left");d&&W&&d.appendChild(W),M(de,n.seedNodeIds),I.setFocusDisabled(n.hops===0),m()}else V(),I.setFocusDisabled(!1),de&&M(de),m()},{lod:e.lod,navigation:e.navigation,walk:e.walk});const ce=ao(t,{maxResults:e.limits.maxSearchResults,debounceMs:e.limits.searchDebounceMs}),J=co(t,{onFilterByType(n){if(L)if(n===null)a.setFilteredNodeIds(null);else{const d=new Set(((L==null?void 0:L.nodes)??[]).filter(T=>T.type===n).map(T=>T.id));a.setFilteredNodeIds(d)}},onNavigateToNode(n){a.panToNode(n),L&&I.show([n],L)},onWalkTrailRemove(n){a.removeFromWalkTrail(n),m()},onWalkIsolate(){if(!L)return;const n=a.getWalkTrail();n.length!==0&&a.enterFocus(n,0)},async onWalkSaveSnippet(n){if(!de||!L)return;const d=a.getWalkTrail();if(d.length<2)return;const T=new Set(d),G=L.edges.filter(re=>T.has(re.sourceId)&&T.has(re.targetId)).map(re=>re.id);await Vt(de,n,d,G),await N(de)},async onStarredSaveSnippet(n,d){if(!de||!L)return;const T=new Set(d),G=L.edges.filter(re=>T.has(re.sourceId)&&T.has(re.targetId)).map(re=>re.id);await Vt(de,n,d,G),await N(de)},onFocusChange(n){n&&n.length>0?a.enterFocus(n,0):a.isFocused()&&a.exitFocus()},onRenameNodeType(n,d){if(L){o.push(L);for(const T of L.nodes)T.type===n&&(T.type=d,T.updatedAt=new Date().toISOString());P()}},onRenameEdgeType(n,d){if(L){o.push(L);for(const T of L.edges)T.type===n&&(T.type=d);P()}},onToggleEdgeLabels(n){a.setEdgeLabels(n)},onToggleTypeHulls(n){a.setTypeHulls(n)},onToggleMinimap(n){a.setMinimap(n)},onLayoutChange(n,d){Qe({[n]:d}),a.reheat()},onPanSpeedChange(n){U=n},onExport(n){const d=a.exportImage(n);if(!d)return;const T=document.createElement("a");T.download=`${de||"graph"}.${n}`,T.href=d,T.click()},onSnapshot:async n=>{de&&(await Tn(de,n),await v(de))},onRollback:async n=>{de&&(await An(de,n),L=await ht(de),a.loadGraph(L),ce.setLearningGraphData(L),J.setData(L),await v(de))},onOpen(){F.matches&&I.hide()}}),ie=document.createElement("div");ie.className="canvas-top-bar";const te=document.createElement("div");te.className="canvas-top-left";const ye=document.createElement("div");ye.className="canvas-top-center";const ke=document.createElement("div");ke.className="canvas-top-right";const _=t.querySelector(".tools-pane-toggle");_&&te.appendChild(_);const H=t.querySelector(".search-overlay");H&&ye.appendChild(H);const D=t.querySelector(".zoom-controls");D&&ke.appendChild(D),ke.appendChild(c),ie.appendChild(te),ie.appendChild(ye),ie.appendChild(ke),t.appendChild(ie),ce.onFilterChange(n=>{a.setFilteredNodeIds(n)}),ce.onNodeSelect(n=>{a.isFocused()&&J.clearFocusSet(),a.panToNode(n),L&&I.show([n],L)});const w=$n(document.getElementById("sidebar"),{onSelect:n=>k(n),onRename:async(n,d)=>{await Sn(n,d),de===n&&(de=d);const T=await at();w.setSummaries(T),w.setActive(de),de===d&&(L=await ht(d),a.loadGraph(L),ce.setLearningGraphData(L),J.setData(L))},onBranchSwitch:async(n,d)=>{await Gt(n,d),await g(n),L=await ht(n),a.loadGraph(L),ce.setLearningGraphData(L),J.setData(L),await v(n)},onBranchCreate:async(n,d)=>{await _t(n,d),await g(n)},onBranchDelete:async(n,d)=>{await In(n,d),await g(n)},onSnippetLoad:async(n,d)=>{var G;const T=await Pn(n,d);((G=T==null?void 0:T.nodeIds)==null?void 0:G.length)>0&&a.enterFocus(T.nodeIds,0)},onSnippetDelete:async(n,d)=>{await Rn(n,d),await N(n)},onBackpackSwitch:async n=>{await fetch("/api/backpacks/switch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:n})}),await p()},onBackpackRegister:async(n,d)=>{await fetch("/api/backpacks",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:n,activate:d})}),await p()}});async function p(){try{const d=await(await fetch("/api/backpacks")).json();w.setBackpacks(d)}catch{}try{const n=await at();w.setSummaries(n),de&&!n.some(d=>d.name===de)&&(de="",L=null,a.loadGraph({metadata:{name:"",description:"",createdAt:"",updatedAt:""},nodes:[],edges:[]}))}catch{}}function m(){const n=a.getWalkTrail();if(!L||n.length===0){J.setWalkTrail([]),K();return}const d=[],T=n.map((G,re)=>{const me=L.nodes.find(ge=>ge.id===G);let le;if(re>0){const ge=n[re-1],Le=L.edges.find(xe=>xe.sourceId===ge&&xe.targetId===G||xe.targetId===ge&&xe.sourceId===G);le=Le==null?void 0:Le.type,Le&&d.push(Le.id)}return{id:G,label:me?Object.values(me.properties).find(ge=>typeof ge=="string")??me.id:G,type:(me==null?void 0:me.type)??"?",edgeType:le}});J.setWalkTrail(T),n.length>=2?(a.setHighlightedPath(n,d),ee({nodeIds:n,edgeIds:d})):K()}async function g(n){const d=await Ln(n),T=d.find(G=>G.active);T&&w.setActiveBranch(n,T.name,d)}async function v(n){const d=await Mn(n);J.setSnapshots(d)}async function N(n){const d=await Bn(n);w.setSnippets(n,d)}te.insertBefore(w.expandBtn,te.firstChild);const u=ho(t,s),E=fo(t),R=Co(t,{onStar(n){if(!L)return;const d=L.nodes.find(G=>G.id===n);if(!d)return;const T=d.properties._starred===!0;d.properties._starred=!T,wt(de,L),a.loadGraph(L)},onFocusNode(n){J.addToFocusSet([n])},onExploreInBranch(n){if(de){const d=`explore-${n.slice(0,8)}`;_t(de,d).then(()=>{Gt(de,d).then(()=>{a.enterFocus([n],1)})})}},onCopyId(n){navigator.clipboard.writeText(n)}});t.addEventListener("contextmenu",n=>{n.preventDefault();const d=t.querySelector("canvas");if(!d||!L)return;const T=d.getBoundingClientRect(),G=n.clientX-T.left,re=n.clientY-T.top,me=a.nodeAtScreen(G,re);if(!me)return;const le=L.nodes.find(xe=>xe.id===me.id);if(!le)return;const ge=Object.values(le.properties).find(xe=>typeof xe=="string")??le.id,Le=le.properties._starred===!0;R.show(le.id,ge,Le,n.clientX-T.left,n.clientY-T.top)}),e.display.edges||a.setEdges(!1),e.display.edgeLabels||a.setEdgeLabels(!1),e.display.typeHulls||a.setTypeHulls(!1),e.display.minimap||a.setMinimap(!1);function M(n,d){const T=[];d!=null&&d.length&&T.push("node="+d.map(encodeURIComponent).join(","));const G=a.getFocusInfo();G&&(T.push("focus="+G.seedNodeIds.map(encodeURIComponent).join(",")),T.push("hops="+G.hops));const re="#"+encodeURIComponent(n)+(T.length?"?"+T.join("&"):"");history.replaceState(null,"",re)}function O(){const n=window.location.hash.slice(1);if(!n)return{graph:null,nodes:[],focus:[],hops:1};const[d,T]=n.split("?"),G=d?decodeURIComponent(d):null;let re=[],me=[],le=1;if(T){const ge=new URLSearchParams(T),Le=ge.get("node");Le&&(re=Le.split(",").map(decodeURIComponent));const xe=ge.get("focus");xe&&(me=xe.split(",").map(decodeURIComponent));const He=ge.get("hops");He&&(le=Math.max(0,parseInt(He,10)||1))}return{graph:G,nodes:re,focus:me,hops:le}}async function k(n,d,T,G){de=n,Mt=It.has(n),w.setActive(n),I.hide(),V(),ce.clear(),o.clear(),L=Mt?await Nn(n):await ht(n);const re=zn(L.nodes.length);if(Qe({spacing:Math.max(e.layout.spacing,re.spacing),clusterStrength:Math.max(e.layout.clustering,re.clusterStrength)}),a.loadGraph(L),ce.setLearningGraphData(L),J.setData(L),E.hide(),M(n),Mt||(await g(n),await v(n),await N(n)),T!=null&&T.length&&L){const me=T.filter(le=>L.nodes.some(ge=>ge.id===le));if(me.length){setTimeout(()=>{a.enterFocus(me,G??1)},500);return}}if(d!=null&&d.length&&L){const me=d.filter(le=>L.nodes.some(ge=>ge.id===le));me.length&&setTimeout(()=>{a.panToNodes(me),L&&I.show(me,L),M(n,me)},500)}}try{const d=await(await fetch("/api/backpacks")).json();w.setBackpacks(d)}catch{}const[f,S]=await Promise.all([at(),kn().catch(()=>[])]);w.setSummaries(f),w.setRemotes(S),It=new Set(S.map(n=>n.name));const q=O(),se=q.graph&&f.some(n=>n.name===q.graph)||q.graph&&It.has(q.graph)?q.graph:f.length>0?f[0].name:S.length>0?S[0].name:null;se?await k(se,q.nodes.length?q.nodes:void 0,q.focus.length?q.focus:void 0,q.hops):E.show();const pe={search(){ce.focus()},searchAlt(){ce.focus()},undo(){if(L){const n=o.undo(L);n&&i(n)}},redo(){if(L){const n=o.redo(L);n&&i(n)}},focus(){a.isFocused()?J.clearFocusSet():C.length>0&&J.addToFocusSet(C)},hopsDecrease(){const n=a.getFocusInfo();n&&n.hops>0&&a.enterFocus(n.seedNodeIds,n.hops-1)},hopsIncrease(){const n=a.getFocusInfo();n&&a.enterFocus(n.seedNodeIds,n.hops+1)},nextNode(){const n=a.getNodeIds();n.length>0&&(Q=(Q+1)%n.length,a.panToNode(n[Q]),L&&I.show([n[Q]],L))},prevNode(){const n=a.getNodeIds();n.length>0&&(Q=Q<=0?n.length-1:Q-1,a.panToNode(n[Q]),L&&I.show([n[Q]],L))},nextConnection(){const n=I.cycleConnection(1);n&&a.panToNode(n)},prevConnection(){const n=I.cycleConnection(-1);n&&a.panToNode(n)},historyBack(){I.goBack()},historyForward(){I.goForward()},center(){a.centerView()},toggleEdges(){j=!j,a.setEdges(j)},panLeft(){a.panBy(-U,0)},panDown(){a.panBy(0,U)},panUp(){a.panBy(0,-U)},panRight(){a.panBy(U,0)},panFastLeft(){a.panBy(-U*e.navigation.panFastMultiplier,0)},zoomOut(){a.zoomBy(1/e.navigation.zoomFactor)},zoomIn(){a.zoomBy(e.navigation.zoomFactor)},panFastRight(){a.panBy(U*e.navigation.panFastMultiplier,0)},spacingDecrease(){const n=rt();Qe({spacing:Math.max(.5,n.spacing-.5)}),a.reheat()},spacingIncrease(){const n=rt();Qe({spacing:Math.min(20,n.spacing+.5)}),a.reheat()},clusteringDecrease(){const n=rt();Qe({clusterStrength:Math.max(0,n.clusterStrength-.03)}),a.reheat()},clusteringIncrease(){const n=rt();Qe({clusterStrength:Math.min(1,n.clusterStrength+.03)}),a.reheat()},help(){u.toggle()},toggleSidebar(){w.toggle()},walkIsolate(){if(!L)return;const n=a.getWalkTrail();n.length!==0&&a.enterFocus(n,0)},walkMode(){!a.isFocused()&&C.length>0&&J.addToFocusSet(C),a.setWalkMode(!a.getWalkMode());const n=t.querySelector(".walk-indicator");n&&n.classList.toggle("active",a.getWalkMode()),m()},escape(){a.isFocused()?J.clearFocusSet():u.hide()}};document.addEventListener("keydown",n=>{var d;if(!(n.target instanceof HTMLInputElement||n.target instanceof HTMLTextAreaElement)){for(const[T,G]of Object.entries(s))if(lo(n,G)){(T==="search"||T==="searchAlt"||T==="undo"||T==="redo"||T==="toggleSidebar")&&n.preventDefault(),(d=pe[T])==null||d.call(pe);return}}}),window.addEventListener("hashchange",()=>{const n=O();if(n.graph&&n.graph!==de)k(n.graph,n.nodes.length?n.nodes:void 0,n.focus.length?n.focus:void 0,n.hops);else if(n.graph&&n.focus.length&&L)a.enterFocus(n.focus,n.hops);else if(n.graph&&n.nodes.length&&L){a.isFocused()&&a.exitFocus();const d=n.nodes.filter(T=>L.nodes.some(G=>G.id===T));d.length&&(a.panToNodes(d),I.show(d,L))}})}Io();
|
|
@@ -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);border-left:3px solid var(--backpack-color, transparent);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}.backpack-picker-container{position:relative;margin-bottom:10px}.backpack-picker-pill{display:flex;align-items:center;gap:6px;width:100%;padding:6px 10px;background:var(--bg-base);border:1px solid var(--border);border-radius:6px;color:var(--fg);font-size:11px;font-family:inherit;cursor:pointer;text-align:left}.backpack-picker-pill:hover{border-color:var(--backpack-color, var(--accent))}.backpack-picker-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--backpack-color, var(--accent));flex-shrink:0}.backpack-picker-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}.backpack-picker-caret{opacity:.6;font-size:10px}.backpack-picker-dropdown{position:absolute;top:calc(100% + 4px);left:0;right:0;background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;box-shadow:0 4px 16px var(--shadow);z-index:50;max-height:300px;overflow-y:auto;padding:4px}.backpack-picker-item{display:flex;align-items:center;gap:6px;width:100%;padding:6px 8px;background:transparent;border:none;border-radius:4px;color:var(--fg);font-family:inherit;font-size:11px;cursor:pointer;text-align:left}.backpack-picker-item:hover{background:var(--bg-hover)}.backpack-picker-item.active{background:var(--bg-hover);font-weight:600}.backpack-picker-item-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--backpack-color, var(--accent));flex-shrink:0}.backpack-picker-item-name{flex-shrink:0}.backpack-picker-item-path{flex:1;opacity:.55;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:9px;text-align:right}.backpack-picker-divider{height:1px;background:var(--border);margin:4px 0}.backpack-picker-add{opacity:.75;font-style:italic}.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-label{display:block;font-size:11px;color:var(--text-muted);margin-bottom:4px;margin-top:8px;text-transform:uppercase;letter-spacing:.04em}.bp-dialog-path-row{display:flex;gap:8px;margin-bottom:4px}.bp-dialog-path-input{flex:1;margin-bottom:0}.bp-dialog-browse-btn{flex-shrink:0;padding:8px 14px;font-size:12px}.bp-dialog-browse-btn:disabled{opacity:.4;cursor:not-allowed}.bp-dialog-path-input.bp-dialog-drag-over{border-color:var(--accent);background:var(--bg-hover)}.bp-dialog-picker-hint{font-size:11px;color:var(--text-muted);min-height:1em;margin-bottom:8px}.bp-dialog-activate-row{display:flex;align-items:center;gap:8px;margin-top:8px;margin-bottom:12px;font-size:12px;color:var(--text-muted)}.bp-dialog-activate-row input[type=checkbox]{margin:0}.bp-dialog-activate-row label{cursor:pointer}.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}}
|
package/dist/app/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Backpack Viewer</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-D7P8kwxI.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-dtYoeajN.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="app">
|