backpack-viewer 0.5.1 → 0.7.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/bin/serve.js +159 -396
- package/dist/app/assets/index-D-H7agBH.js +12 -0
- package/dist/app/assets/index-DE73ngo-.css +1 -0
- package/dist/app/assets/index-DFW3OKgJ.js +6 -0
- package/dist/app/assets/layout-worker-4xak23M6.js +1 -0
- package/dist/app/index.html +2 -2
- package/dist/bridge.d.ts +22 -0
- package/dist/bridge.js +41 -0
- package/dist/canvas.d.ts +15 -0
- package/dist/canvas.js +352 -12
- package/dist/config.js +10 -0
- package/dist/copy-prompt.d.ts +17 -0
- package/dist/copy-prompt.js +81 -0
- package/dist/default-config.json +6 -1
- package/dist/dom-utils.d.ts +46 -0
- package/dist/dom-utils.js +57 -0
- package/dist/empty-state.js +63 -31
- package/dist/extensions/api.d.ts +15 -0
- package/dist/extensions/api.js +185 -0
- package/dist/extensions/chat/backpack-extension.json +23 -0
- package/dist/extensions/chat/src/index.js +32 -0
- package/dist/extensions/chat/src/panel.js +306 -0
- package/dist/extensions/chat/src/providers/anthropic.js +158 -0
- package/dist/extensions/chat/src/providers/types.js +15 -0
- package/dist/extensions/chat/src/tools.js +281 -0
- package/dist/extensions/chat/style.css +147 -0
- package/dist/extensions/event-bus.d.ts +12 -0
- package/dist/extensions/event-bus.js +30 -0
- package/dist/extensions/loader.d.ts +32 -0
- package/dist/extensions/loader.js +71 -0
- package/dist/extensions/manifest.d.ts +54 -0
- package/dist/extensions/manifest.js +116 -0
- package/dist/extensions/panel-mount.d.ts +26 -0
- package/dist/extensions/panel-mount.js +377 -0
- package/dist/extensions/taskbar.d.ts +29 -0
- package/dist/extensions/taskbar.js +64 -0
- package/dist/extensions/types.d.ts +182 -0
- package/dist/extensions/types.js +8 -0
- package/dist/info-panel.d.ts +2 -1
- package/dist/info-panel.js +78 -87
- package/dist/keybindings.d.ts +1 -1
- package/dist/keybindings.js +1 -0
- package/dist/layout-worker.d.ts +4 -1
- package/dist/layout-worker.js +51 -1
- package/dist/layout.d.ts +8 -0
- package/dist/layout.js +8 -1
- package/dist/main.js +216 -35
- package/dist/search.js +1 -1
- package/dist/server-api-routes.d.ts +56 -0
- package/dist/server-api-routes.js +442 -0
- package/dist/server-extensions.d.ts +126 -0
- package/dist/server-extensions.js +272 -0
- package/dist/server-viewer-state.d.ts +18 -0
- package/dist/server-viewer-state.js +33 -0
- package/dist/shortcuts.js +6 -2
- package/dist/sidebar.js +19 -7
- package/dist/style.css +356 -74
- package/dist/tools-pane.js +31 -14
- package/package.json +4 -3
- package/dist/app/assets/index-B3z5bBGl.css +0 -1
- package/dist/app/assets/index-CKYlU1zT.js +0 -35
- package/dist/app/assets/layout-worker-BZXiBoiC.js +0 -1
package/dist/style.css
CHANGED
|
@@ -30,6 +30,21 @@
|
|
|
30
30
|
|
|
31
31
|
--shadow: rgba(0, 0, 0, 0.6);
|
|
32
32
|
--shadow-strong: rgba(0, 0, 0, 0.5);
|
|
33
|
+
|
|
34
|
+
/* Z-tier registry — named layers for everything that overlaps.
|
|
35
|
+
Six tiers, each spaced wide enough that click-to-front shuffling
|
|
36
|
+
within a tier (panels) stays inside its band. New code uses these
|
|
37
|
+
instead of magic numbers; existing rules will be migrated as
|
|
38
|
+
touched. */
|
|
39
|
+
--z-canvas-overlay: 20; /* path bar, focus indicator within canvas */
|
|
40
|
+
--z-panel: 30; /* info-panel + extension panels (click-to-front shuffles 30-39) */
|
|
41
|
+
--z-floating: 40; /* zoom, theme, copy-prompt, sidebar-expand, taskbar slots — hidden when a panel is fullscreen */
|
|
42
|
+
--z-panel-fullscreen: 45; /* a maximized panel covers the secondary floating tier */
|
|
43
|
+
--z-floating-primary: 50; /* search bar — always visible, stays above fullscreen panels so users can keep searching */
|
|
44
|
+
--z-popup: 55; /* context menu, search dropdown */
|
|
45
|
+
--z-modal: 60; /* dialog overlay */
|
|
46
|
+
--z-toast: 70; /* transient notifications */
|
|
47
|
+
|
|
33
48
|
/* Canvas colors */
|
|
34
49
|
--canvas-edge: rgba(255, 255, 255, 0.08);
|
|
35
50
|
--canvas-edge-highlight: rgba(212, 162, 127, 0.5);
|
|
@@ -629,16 +644,33 @@ body {
|
|
|
629
644
|
top: 16px;
|
|
630
645
|
left: 16px;
|
|
631
646
|
right: 16px;
|
|
632
|
-
z-index
|
|
647
|
+
/* No z-index here on purpose — that would create a stacking context
|
|
648
|
+
and trap the children's z-indexes. Each child sets its own. */
|
|
633
649
|
display: flex;
|
|
634
650
|
justify-content: space-between;
|
|
635
651
|
align-items: flex-start;
|
|
636
652
|
pointer-events: none;
|
|
637
653
|
}
|
|
638
654
|
|
|
655
|
+
/* Children get their own stacking contexts via position:relative + z.
|
|
656
|
+
Top-left and top-right hold "secondary" controls (zoom, theme,
|
|
657
|
+
copy-prompt, sidebar-expand, taskbar slots) and live at the
|
|
658
|
+
floating tier — these get hidden when a panel is fullscreen.
|
|
659
|
+
Top-center holds the search bar at floating-primary so it stays
|
|
660
|
+
visible even when a panel is fullscreen. */
|
|
639
661
|
.canvas-top-left,
|
|
640
|
-
.canvas-top-center,
|
|
641
662
|
.canvas-top-right {
|
|
663
|
+
position: relative;
|
|
664
|
+
z-index: var(--z-floating);
|
|
665
|
+
pointer-events: auto;
|
|
666
|
+
display: flex;
|
|
667
|
+
align-items: center;
|
|
668
|
+
gap: 4px;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.canvas-top-center {
|
|
672
|
+
position: relative;
|
|
673
|
+
z-index: var(--z-floating-primary);
|
|
642
674
|
pointer-events: auto;
|
|
643
675
|
display: flex;
|
|
644
676
|
align-items: center;
|
|
@@ -1020,30 +1052,20 @@ body {
|
|
|
1020
1052
|
}
|
|
1021
1053
|
|
|
1022
1054
|
/* --- Info Panel --- */
|
|
1055
|
+
/* The outer panel chrome (positioning, border, shadow, drag, fullscreen,
|
|
1056
|
+
close, header buttons) is owned by panel-mount via .extension-panel.
|
|
1057
|
+
Only the body-internal styles live here now. */
|
|
1023
1058
|
|
|
1024
|
-
.info-panel {
|
|
1025
|
-
|
|
1026
|
-
top: 56px;
|
|
1027
|
-
right: 16px;
|
|
1028
|
-
bottom: 16px;
|
|
1029
|
-
width: 360px;
|
|
1030
|
-
background: var(--bg-surface);
|
|
1031
|
-
border: 1px solid var(--border);
|
|
1032
|
-
border-radius: 10px;
|
|
1033
|
-
overflow: hidden;
|
|
1059
|
+
.info-panel-content {
|
|
1060
|
+
flex: 1;
|
|
1034
1061
|
display: flex;
|
|
1035
1062
|
flex-direction: column;
|
|
1036
|
-
|
|
1037
|
-
z-index: 10;
|
|
1038
|
-
box-shadow: 0 8px 32px var(--shadow);
|
|
1039
|
-
transition: top 0.25s ease, right 0.25s ease, bottom 0.25s ease,
|
|
1040
|
-
left 0.25s ease, width 0.25s ease, max-height 0.25s ease,
|
|
1041
|
-
border-radius 0.25s ease;
|
|
1063
|
+
min-height: 0;
|
|
1042
1064
|
}
|
|
1043
1065
|
|
|
1044
1066
|
.info-panel-header {
|
|
1045
1067
|
flex-shrink: 0;
|
|
1046
|
-
padding:
|
|
1068
|
+
padding: 16px 20px 12px;
|
|
1047
1069
|
position: relative;
|
|
1048
1070
|
}
|
|
1049
1071
|
|
|
@@ -1054,60 +1076,6 @@ body {
|
|
|
1054
1076
|
padding: 0 20px 20px;
|
|
1055
1077
|
}
|
|
1056
1078
|
|
|
1057
|
-
.info-panel.hidden {
|
|
1058
|
-
display: none;
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
.info-panel.info-panel-maximized {
|
|
1062
|
-
top: 0;
|
|
1063
|
-
right: 0;
|
|
1064
|
-
bottom: 0;
|
|
1065
|
-
left: 0;
|
|
1066
|
-
width: auto;
|
|
1067
|
-
max-height: none;
|
|
1068
|
-
border-radius: 0;
|
|
1069
|
-
z-index: 40;
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
/* --- Toolbar (back, forward, maximize, close) --- */
|
|
1073
|
-
|
|
1074
|
-
.info-panel-toolbar {
|
|
1075
|
-
position: absolute;
|
|
1076
|
-
top: 12px;
|
|
1077
|
-
right: 14px;
|
|
1078
|
-
display: flex;
|
|
1079
|
-
align-items: center;
|
|
1080
|
-
gap: 2px;
|
|
1081
|
-
z-index: 1;
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
.info-toolbar-btn {
|
|
1085
|
-
background: none;
|
|
1086
|
-
border: none;
|
|
1087
|
-
color: var(--text-muted);
|
|
1088
|
-
font-size: 16px;
|
|
1089
|
-
cursor: pointer;
|
|
1090
|
-
padding: 4px 6px;
|
|
1091
|
-
line-height: 1;
|
|
1092
|
-
border-radius: 4px;
|
|
1093
|
-
transition: color 0.15s, background 0.15s;
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
.info-toolbar-btn:hover:not(:disabled) {
|
|
1097
|
-
color: var(--text);
|
|
1098
|
-
background: var(--bg-hover);
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
.info-toolbar-btn:disabled {
|
|
1102
|
-
color: var(--text-dim);
|
|
1103
|
-
cursor: default;
|
|
1104
|
-
opacity: 0.3;
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
.info-close-btn {
|
|
1108
|
-
font-size: 20px;
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
1079
|
/* --- Clickable connections --- */
|
|
1112
1080
|
|
|
1113
1081
|
.info-connection-link {
|
|
@@ -2338,7 +2306,7 @@ body {
|
|
|
2338
2306
|
bottom: 16px;
|
|
2339
2307
|
left: 50%;
|
|
2340
2308
|
transform: translateX(-50%);
|
|
2341
|
-
z-index:
|
|
2309
|
+
z-index: var(--z-canvas-overlay);
|
|
2342
2310
|
display: flex;
|
|
2343
2311
|
align-items: center;
|
|
2344
2312
|
gap: 4px;
|
|
@@ -2420,3 +2388,317 @@ body {
|
|
|
2420
2388
|
0%, 100% { opacity: 0.6; border-color: rgba(212, 162, 127, 0.3); }
|
|
2421
2389
|
50% { opacity: 1; border-color: rgba(212, 162, 127, 0.8); }
|
|
2422
2390
|
}
|
|
2391
|
+
|
|
2392
|
+
/* --- Copy Prompt Button --- */
|
|
2393
|
+
|
|
2394
|
+
.copy-prompt-btn {
|
|
2395
|
+
background: var(--bg-surface);
|
|
2396
|
+
border: 1px solid var(--border);
|
|
2397
|
+
border-radius: 8px;
|
|
2398
|
+
color: var(--text-muted);
|
|
2399
|
+
font-size: 12px;
|
|
2400
|
+
cursor: pointer;
|
|
2401
|
+
padding: 6px 10px;
|
|
2402
|
+
line-height: 1;
|
|
2403
|
+
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
|
2404
|
+
box-shadow: 0 2px 8px var(--shadow);
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
.copy-prompt-btn:hover {
|
|
2408
|
+
color: var(--text);
|
|
2409
|
+
border-color: var(--text-muted);
|
|
2410
|
+
background: var(--bg-hover);
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
/* --- Extension taskbar slots --- */
|
|
2414
|
+
|
|
2415
|
+
/* Top slots flank the search bar inside .canvas-top-center. They live
|
|
2416
|
+
alongside the search overlay so extensions can flank it on either
|
|
2417
|
+
side without polluting the viewer's own top-left/top-right controls
|
|
2418
|
+
(zoom, theme, copy-prompt, tools toggle). Hidden when empty so they
|
|
2419
|
+
contribute no width and the search bar stays centered. */
|
|
2420
|
+
.ext-slot-top-left,
|
|
2421
|
+
.ext-slot-top-right {
|
|
2422
|
+
display: none;
|
|
2423
|
+
align-items: center;
|
|
2424
|
+
gap: 4px;
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
.ext-slot-top-left.has-icons,
|
|
2428
|
+
.ext-slot-top-right.has-icons {
|
|
2429
|
+
display: inline-flex;
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
/* Bottom slots float in the canvas corners. Hidden until populated so
|
|
2433
|
+
empty corners don't take any space. The bottom-center area is left
|
|
2434
|
+
clear for the path bar — see .path-bar above. */
|
|
2435
|
+
.ext-slot-bottom-left,
|
|
2436
|
+
.ext-slot-bottom-right {
|
|
2437
|
+
position: absolute;
|
|
2438
|
+
bottom: 16px;
|
|
2439
|
+
z-index: var(--z-floating);
|
|
2440
|
+
display: none;
|
|
2441
|
+
flex-direction: row;
|
|
2442
|
+
align-items: center;
|
|
2443
|
+
gap: 6px;
|
|
2444
|
+
padding: 6px;
|
|
2445
|
+
background: var(--bg-surface);
|
|
2446
|
+
border: 1px solid var(--border);
|
|
2447
|
+
border-radius: 12px;
|
|
2448
|
+
box-shadow: 0 4px 16px var(--shadow);
|
|
2449
|
+
pointer-events: auto;
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
.ext-slot-bottom-left {
|
|
2453
|
+
left: 16px;
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
.ext-slot-bottom-right {
|
|
2457
|
+
right: 16px;
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
.ext-slot-bottom-left.has-icons,
|
|
2461
|
+
.ext-slot-bottom-right.has-icons {
|
|
2462
|
+
display: flex;
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
/* Icon button — same look in every slot. In top slots it sits
|
|
2466
|
+
alongside the viewer's own buttons (zoom, theme, copy-prompt) and
|
|
2467
|
+
matches their visual weight. In bottom slots it lives inside the
|
|
2468
|
+
floating pill container styled above. */
|
|
2469
|
+
.extension-taskbar-icon {
|
|
2470
|
+
display: inline-flex;
|
|
2471
|
+
align-items: center;
|
|
2472
|
+
gap: 6px;
|
|
2473
|
+
background: var(--bg-surface);
|
|
2474
|
+
border: 1px solid var(--border);
|
|
2475
|
+
border-radius: 8px;
|
|
2476
|
+
color: var(--text-muted);
|
|
2477
|
+
font-size: 12px;
|
|
2478
|
+
font-weight: 500;
|
|
2479
|
+
font-family: inherit;
|
|
2480
|
+
cursor: pointer;
|
|
2481
|
+
padding: 6px 10px;
|
|
2482
|
+
line-height: 1;
|
|
2483
|
+
box-shadow: 0 2px 8px var(--shadow);
|
|
2484
|
+
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
/* Inside the bottom floating pill, drop the per-icon shadow and
|
|
2488
|
+
border so the pill container's chrome wraps the group cleanly. */
|
|
2489
|
+
.ext-slot-bottom-left .extension-taskbar-icon,
|
|
2490
|
+
.ext-slot-bottom-right .extension-taskbar-icon {
|
|
2491
|
+
background: transparent;
|
|
2492
|
+
border-color: transparent;
|
|
2493
|
+
box-shadow: none;
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
.extension-taskbar-icon:hover {
|
|
2497
|
+
color: var(--text);
|
|
2498
|
+
border-color: var(--text-muted);
|
|
2499
|
+
background: var(--bg-hover);
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
.ext-slot-bottom-left .extension-taskbar-icon:hover,
|
|
2503
|
+
.ext-slot-bottom-right .extension-taskbar-icon:hover {
|
|
2504
|
+
border-color: var(--border);
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
.extension-taskbar-icon-symbol {
|
|
2508
|
+
font-size: 11px;
|
|
2509
|
+
opacity: 0.7;
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
.extension-taskbar-icon-label {
|
|
2513
|
+
font-size: 12px;
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
/* --- Extension Panel Layer + Panels --- */
|
|
2517
|
+
|
|
2518
|
+
/* Extension panel layer is the parent container that holds all
|
|
2519
|
+
panels (info-panel + extension panels). It deliberately does NOT
|
|
2520
|
+
set a z-index — that would create a stacking context and trap
|
|
2521
|
+
the panels' inline z-indexes inside it, preventing fullscreen
|
|
2522
|
+
panels from rising above the top bar. Each panel sets its own
|
|
2523
|
+
z-index via JS (panel tier base + click-to-front offset, or
|
|
2524
|
+
--z-panel-fullscreen when maximized). The layer's only job is
|
|
2525
|
+
to be a positioned container with pointer-events:none so empty
|
|
2526
|
+
regions don't block the canvas. */
|
|
2527
|
+
.extension-panel-layer {
|
|
2528
|
+
position: absolute;
|
|
2529
|
+
inset: 0;
|
|
2530
|
+
pointer-events: none;
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
/* Each panel is positioned via inline left/top from JS so it can be
|
|
2534
|
+
dragged. The class only handles the look, not the position — the
|
|
2535
|
+
default coordinates are computed at mount time. */
|
|
2536
|
+
.extension-panel {
|
|
2537
|
+
position: absolute;
|
|
2538
|
+
display: flex;
|
|
2539
|
+
flex-direction: column;
|
|
2540
|
+
background: var(--bg-surface);
|
|
2541
|
+
border: 1px solid var(--border);
|
|
2542
|
+
border-radius: 12px;
|
|
2543
|
+
box-shadow: 0 8px 32px var(--shadow);
|
|
2544
|
+
overflow: hidden;
|
|
2545
|
+
pointer-events: auto;
|
|
2546
|
+
width: 380px;
|
|
2547
|
+
max-height: calc(100vh - 120px);
|
|
2548
|
+
/* Panels start in the panel tier; click-to-front bumps individual
|
|
2549
|
+
panels by setting style.zIndex inline (always >= --z-panel). */
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
/* Fullscreen takes over the canvas area entirely, suspending the
|
|
2553
|
+
inline left/top until exited. The inline z-index is bumped to
|
|
2554
|
+
--z-panel-fullscreen by JS so the panel covers the top bar. */
|
|
2555
|
+
.extension-panel.is-fullscreen {
|
|
2556
|
+
top: 0 !important;
|
|
2557
|
+
left: 0 !important;
|
|
2558
|
+
right: 0;
|
|
2559
|
+
bottom: 0;
|
|
2560
|
+
width: auto;
|
|
2561
|
+
max-height: none;
|
|
2562
|
+
border-radius: 0;
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
/* setVisible(false) toggles this — display:none keeps the panel
|
|
2566
|
+
instance alive (for hideOnClose / persistent panels). */
|
|
2567
|
+
.extension-panel.is-hidden {
|
|
2568
|
+
display: none;
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
/* When any panel is fullscreen, the panel-mount adds this class to
|
|
2572
|
+
#canvas-container. We hide secondary controls (zoom, theme,
|
|
2573
|
+
copy-prompt, sidebar-expand, taskbar slots) so they don't visually
|
|
2574
|
+
conflict with the panel's own chrome buttons. The search bar in
|
|
2575
|
+
.canvas-top-center is intentionally NOT hidden — users can keep
|
|
2576
|
+
searching and selecting nodes while a panel is fullscreen. */
|
|
2577
|
+
#canvas-container.has-fullscreen-panel .canvas-top-left,
|
|
2578
|
+
#canvas-container.has-fullscreen-panel .canvas-top-right,
|
|
2579
|
+
#canvas-container.has-fullscreen-panel .ext-slot-bottom-left,
|
|
2580
|
+
#canvas-container.has-fullscreen-panel .ext-slot-bottom-right,
|
|
2581
|
+
#canvas-container.has-fullscreen-panel .ext-slot-top-left,
|
|
2582
|
+
#canvas-container.has-fullscreen-panel .ext-slot-top-right {
|
|
2583
|
+
display: none;
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
/* In fullscreen, the panel takes over the WHOLE canvas (top: 0 to
|
|
2587
|
+
bottom: 0). The search bar slides to the top of the canvas and
|
|
2588
|
+
visually meshes into the panel's surface — its parent containers
|
|
2589
|
+
(.canvas-top-bar / .canvas-top-center / .search-overlay) have no
|
|
2590
|
+
background, so the panel's --bg-surface extends up underneath the
|
|
2591
|
+
search input and reads as one continuous slab.
|
|
2592
|
+
|
|
2593
|
+
The panel's chrome header gets padding-top equal to the search bar
|
|
2594
|
+
height so the title + buttons appear below where the search bar
|
|
2595
|
+
sits. In normal (non-fullscreen) mode the top bar keeps its
|
|
2596
|
+
existing top: 16px so widgets align horizontally as before. */
|
|
2597
|
+
#canvas-container.has-fullscreen-panel .canvas-top-bar {
|
|
2598
|
+
top: 0;
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
#canvas-container.has-fullscreen-panel .extension-panel.is-fullscreen .extension-panel-header {
|
|
2602
|
+
padding-top: 56px;
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
/* Drop the panel's chrome (border, rounded corners, shadow) when
|
|
2606
|
+
fullscreen so it visually sits flush against the canvas edges
|
|
2607
|
+
instead of looking like a window with a frame. */
|
|
2608
|
+
.extension-panel.is-fullscreen {
|
|
2609
|
+
border: none;
|
|
2610
|
+
box-shadow: none;
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
/* Header doubles as the drag handle. Cursor hint + grab-state, but
|
|
2614
|
+
buttons inside it stop propagation so clicking close/fullscreen
|
|
2615
|
+
doesn't start a drag. */
|
|
2616
|
+
.extension-panel-header {
|
|
2617
|
+
display: flex;
|
|
2618
|
+
align-items: center;
|
|
2619
|
+
gap: 6px;
|
|
2620
|
+
padding: 8px 10px;
|
|
2621
|
+
border-bottom: 1px solid var(--border);
|
|
2622
|
+
background: var(--bg-surface);
|
|
2623
|
+
flex-shrink: 0;
|
|
2624
|
+
cursor: grab;
|
|
2625
|
+
user-select: none;
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
.extension-panel-header:active {
|
|
2629
|
+
cursor: grabbing;
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
.extension-panel.is-fullscreen .extension-panel-header {
|
|
2633
|
+
cursor: default;
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
.extension-panel-title {
|
|
2637
|
+
flex: 1;
|
|
2638
|
+
font-size: 13px;
|
|
2639
|
+
font-weight: 500;
|
|
2640
|
+
color: var(--text);
|
|
2641
|
+
white-space: nowrap;
|
|
2642
|
+
overflow: hidden;
|
|
2643
|
+
text-overflow: ellipsis;
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
/* Custom-button container — small flex group for extension-supplied
|
|
2647
|
+
header buttons. Sits between the title and the built-in fullscreen +
|
|
2648
|
+
close controls. */
|
|
2649
|
+
.extension-panel-custom-btns {
|
|
2650
|
+
display: flex;
|
|
2651
|
+
gap: 4px;
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
/* Header buttons (custom + fullscreen + close) all share one look so
|
|
2655
|
+
the chrome reads as a single row of controls. Matches the visual
|
|
2656
|
+
weight of info-panel's toolbar buttons. */
|
|
2657
|
+
.extension-panel-btn {
|
|
2658
|
+
display: inline-flex;
|
|
2659
|
+
align-items: center;
|
|
2660
|
+
justify-content: center;
|
|
2661
|
+
background: none;
|
|
2662
|
+
border: 1px solid var(--border);
|
|
2663
|
+
border-radius: 6px;
|
|
2664
|
+
color: var(--text-muted);
|
|
2665
|
+
font-size: 12px;
|
|
2666
|
+
font-family: inherit;
|
|
2667
|
+
cursor: pointer;
|
|
2668
|
+
padding: 3px 8px;
|
|
2669
|
+
line-height: 1;
|
|
2670
|
+
min-height: 22px;
|
|
2671
|
+
transition: color 0.15s, background 0.15s, border-color 0.15s;
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
.extension-panel-btn:hover:not(:disabled) {
|
|
2675
|
+
color: var(--text);
|
|
2676
|
+
background: var(--bg-hover);
|
|
2677
|
+
border-color: var(--text-muted);
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
.extension-panel-btn:disabled {
|
|
2681
|
+
opacity: 0.4;
|
|
2682
|
+
cursor: not-allowed;
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
.extension-panel-btn-fullscreen {
|
|
2686
|
+
padding: 2px 6px;
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
.extension-panel-btn-fullscreen svg {
|
|
2690
|
+
display: block;
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
.extension-panel-btn-close {
|
|
2694
|
+
font-size: 16px;
|
|
2695
|
+
padding: 0 7px;
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
.extension-panel-body {
|
|
2699
|
+
flex: 1;
|
|
2700
|
+
overflow: hidden;
|
|
2701
|
+
display: flex;
|
|
2702
|
+
flex-direction: column;
|
|
2703
|
+
}
|
|
2704
|
+
|
package/dist/tools-pane.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { showPrompt, showConfirm } from "./dialog";
|
|
2
2
|
import { getColor } from "./colors";
|
|
3
|
+
import { makeSvgIcon, snapshotChildren, restoreChildren } from "./dom-utils";
|
|
3
4
|
export function initToolsPane(container, callbacks) {
|
|
4
5
|
let data = null;
|
|
5
6
|
let stats = null;
|
|
@@ -50,8 +51,11 @@ export function initToolsPane(container, callbacks) {
|
|
|
50
51
|
const toggle = document.createElement("button");
|
|
51
52
|
toggle.className = "tools-pane-toggle hidden";
|
|
52
53
|
toggle.title = "Graph Inspector";
|
|
53
|
-
toggle.
|
|
54
|
-
|
|
54
|
+
toggle.appendChild(makeSvgIcon({ size: 16, strokeLinecap: "round", strokeLinejoin: "round" }, [
|
|
55
|
+
{ tag: "path", attrs: { d: "M4 7h16" } },
|
|
56
|
+
{ tag: "path", attrs: { d: "M4 12h16" } },
|
|
57
|
+
{ tag: "path", attrs: { d: "M4 17h10" } },
|
|
58
|
+
]));
|
|
55
59
|
const content = document.createElement("div");
|
|
56
60
|
content.className = "tools-pane-content hidden";
|
|
57
61
|
container.appendChild(toggle);
|
|
@@ -65,16 +69,25 @@ export function initToolsPane(container, callbacks) {
|
|
|
65
69
|
});
|
|
66
70
|
// --- Render ---
|
|
67
71
|
function render() {
|
|
68
|
-
content.
|
|
72
|
+
content.replaceChildren();
|
|
69
73
|
if (!stats)
|
|
70
74
|
return;
|
|
71
75
|
// Graph stats summary (always visible)
|
|
72
76
|
const summary = document.createElement("div");
|
|
73
77
|
summary.className = "tools-pane-summary";
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
const nodesEl = document.createElement("span");
|
|
79
|
+
nodesEl.textContent = `${stats.nodeCount} nodes`;
|
|
80
|
+
const sep1 = document.createElement("span");
|
|
81
|
+
sep1.className = "tools-pane-sep";
|
|
82
|
+
sep1.textContent = "\u00b7";
|
|
83
|
+
const edgesEl = document.createElement("span");
|
|
84
|
+
edgesEl.textContent = `${stats.edgeCount} edges`;
|
|
85
|
+
const sep2 = document.createElement("span");
|
|
86
|
+
sep2.className = "tools-pane-sep";
|
|
87
|
+
sep2.textContent = "\u00b7";
|
|
88
|
+
const typesEl = document.createElement("span");
|
|
89
|
+
typesEl.textContent = `${stats.types.length} types`;
|
|
90
|
+
summary.append(nodesEl, sep1, edgesEl, sep2, typesEl);
|
|
78
91
|
content.appendChild(summary);
|
|
79
92
|
// Token efficiency card
|
|
80
93
|
if (data && stats.nodeCount > 0) {
|
|
@@ -162,7 +175,7 @@ export function initToolsPane(container, callbacks) {
|
|
|
162
175
|
const tabContainer = content.querySelector(".tools-pane-tab-content");
|
|
163
176
|
if (!tabContainer)
|
|
164
177
|
return;
|
|
165
|
-
tabContainer.
|
|
178
|
+
tabContainer.replaceChildren();
|
|
166
179
|
if (activeTab === "types") {
|
|
167
180
|
renderTypesTab(tabContainer);
|
|
168
181
|
}
|
|
@@ -897,13 +910,17 @@ export function initToolsPane(container, callbacks) {
|
|
|
897
910
|
input.className = "tools-pane-inline-input";
|
|
898
911
|
input.value = currentValue;
|
|
899
912
|
input.type = "text";
|
|
900
|
-
//
|
|
901
|
-
|
|
902
|
-
|
|
913
|
+
// Snapshot the row's children so we can restore them on cancel
|
|
914
|
+
// without re-parsing HTML. Cloning is safe — the snapshotted markup
|
|
915
|
+
// is static (text + spans), no event handlers to lose.
|
|
916
|
+
const originalChildren = snapshotChildren(row);
|
|
917
|
+
row.replaceChildren(input);
|
|
903
918
|
row.classList.add("tools-pane-editing");
|
|
904
|
-
row.appendChild(input);
|
|
905
919
|
input.focus();
|
|
906
920
|
input.select();
|
|
921
|
+
function restore() {
|
|
922
|
+
restoreChildren(row, originalChildren);
|
|
923
|
+
}
|
|
907
924
|
function commit() {
|
|
908
925
|
const newValue = input.value.trim();
|
|
909
926
|
row.classList.remove("tools-pane-editing");
|
|
@@ -911,7 +928,7 @@ export function initToolsPane(container, callbacks) {
|
|
|
911
928
|
onCommit(newValue);
|
|
912
929
|
}
|
|
913
930
|
else {
|
|
914
|
-
|
|
931
|
+
restore();
|
|
915
932
|
}
|
|
916
933
|
}
|
|
917
934
|
input.addEventListener("keydown", (e) => {
|
|
@@ -920,7 +937,7 @@ export function initToolsPane(container, callbacks) {
|
|
|
920
937
|
commit();
|
|
921
938
|
}
|
|
922
939
|
if (e.key === "Escape") {
|
|
923
|
-
|
|
940
|
+
restore();
|
|
924
941
|
row.classList.remove("tools-pane-editing");
|
|
925
942
|
}
|
|
926
943
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "backpack-viewer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Web-based graph visualizer for backpack-ontology — Canvas 2D, force-directed layout, live reload",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Noah Irzinger",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
],
|
|
15
15
|
"scripts": {
|
|
16
16
|
"dev": "vite",
|
|
17
|
-
"build": "rm -rf dist && tsc && cp src/style.css dist/ && vite build",
|
|
17
|
+
"build": "rm -rf dist && tsc && tsc -p tsconfig.extensions.json && cp src/style.css dist/ && node scripts/build-extensions.js && vite build",
|
|
18
18
|
"preview": "vite preview",
|
|
19
19
|
"serve": "node bin/serve.js",
|
|
20
20
|
"prepare": "npm run build",
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"release:major": "npm version major && git push && git push --tags"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"
|
|
26
|
+
"age-encryption": "^0.3.0",
|
|
27
|
+
"backpack-ontology": "^0.7.0"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
30
|
"@types/node": "^25.5.0",
|
|
@@ -1 +0,0 @@
|
|
|
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}.sidebar-stale-banner{background:#fff3cd;color:#5c3a00;border:1px solid #e6c263;border-radius:6px;padding:10px 12px;margin-bottom:10px;font-size:11px}.sidebar-stale-banner-title{font-weight:600;margin-bottom:2px}.sidebar-stale-banner-subtitle{opacity:.85;margin-bottom:6px}.sidebar-stale-banner-hint{background:#00000014;border-radius:4px;padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:10px;line-height:1.4;margin:0;white-space:pre-wrap;word-break:break-all}.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}}
|