kyp-mem 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,40 +10,40 @@
10
10
  <script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
11
11
  <style>
12
12
  :root {
13
- --bg-void: #08080f;
14
- --bg-primary: #0c0c16;
15
- --bg-secondary: #0e0e1a;
16
- --bg-tertiary: #070710;
17
- --bg-hover: #121224;
18
- --bg-active: #181836;
19
- --bg-card: #101020;
20
- --bg-surface: #0a0a15;
21
-
22
- --neon-cyan: #00e8df;
23
- --neon-green: #34d399;
24
- --neon-magenta: #e879f9;
13
+ --bg-void: #09090b;
14
+ --bg-primary: #0d0d10;
15
+ --bg-secondary: #0f0f12;
16
+ --bg-tertiary: #08080a;
17
+ --bg-hover: #15151c;
18
+ --bg-active: #1c1c26;
19
+ --bg-card: #111116;
20
+ --bg-surface: #0b0b0e;
21
+
22
+ --neon-cyan: #D97757;
23
+ --neon-green: #5bb98c;
24
+ --neon-magenta: #c47ad7;
25
25
  --neon-purple: #a78bfa;
26
- --neon-orange: #fb923c;
27
- --neon-yellow: #fbbf24;
28
- --neon-blue: #60a5fa;
29
- --neon-red: #f87171;
26
+ --neon-orange: #e8935a;
27
+ --neon-yellow: #d4a853;
28
+ --neon-blue: #7da8d4;
29
+ --neon-red: #d47171;
30
30
 
31
- --glow-cyan: 0 0 8px #00e8df30;
32
- --glow-green: 0 0 8px #34d39930;
33
- --glow-magenta: 0 0 8px #e879f930;
31
+ --glow-cyan: 0 0 8px #D9775730;
32
+ --glow-green: 0 0 8px #5bb98c30;
33
+ --glow-magenta: 0 0 8px #c47ad730;
34
34
 
35
- --text-primary: #d4d4e8;
36
- --text-secondary: #7a7a9a;
37
- --text-muted: #3e3e5c;
38
- --border: #16162a;
39
- --border-subtle: #121225;
35
+ --text-primary: #d8d5cf;
36
+ --text-secondary: #807b73;
37
+ --text-muted: #55514a;
38
+ --border: rgba(255,255,255,0.06);
39
+ --border-subtle: rgba(255,255,255,0.03);
40
40
 
41
41
  --font: 'Inter', -apple-system, sans-serif;
42
42
  --font-mono: 'JetBrains Mono', monospace;
43
43
 
44
44
  --radius-sm: 4px;
45
- --radius: 6px;
46
- --radius-lg: 10px;
45
+ --radius: 8px;
46
+ --radius-lg: 12px;
47
47
  }
48
48
 
49
49
  * { margin: 0; padding: 0; box-sizing: border-box; }
@@ -51,6 +51,7 @@
51
51
  body {
52
52
  font-family: var(--font);
53
53
  background: var(--bg-void);
54
+ background-image: radial-gradient(circle at 50% 0%, var(--bg-secondary) 0%, var(--bg-void) 70%);
54
55
  color: var(--text-primary);
55
56
  height: 100vh;
56
57
  overflow: hidden;
@@ -62,8 +63,8 @@ body::before {
62
63
  position: fixed;
63
64
  inset: 0;
64
65
  background-image:
65
- linear-gradient(rgba(0,232,223,0.015) 1px, transparent 1px),
66
- linear-gradient(90deg, rgba(0,232,223,0.015) 1px, transparent 1px);
66
+ linear-gradient(rgba(217,119,87,0.012) 1px, transparent 1px),
67
+ linear-gradient(90deg, rgba(217,119,87,0.012) 1px, transparent 1px);
67
68
  background-size: 48px 48px;
68
69
  pointer-events: none;
69
70
  z-index: 0;
@@ -103,7 +104,7 @@ body::before {
103
104
  .resize-handle:hover,
104
105
  .resize-handle.dragging {
105
106
  background: var(--neon-cyan);
106
- box-shadow: 0 0 12px rgba(0,232,223,0.2);
107
+ box-shadow: 0 0 12px rgba(217,119,87,0.2);
107
108
  }
108
109
 
109
110
  body.resizing { cursor: col-resize !important; user-select: none !important; }
@@ -113,7 +114,9 @@ body.resizing .resize-handle { pointer-events: auto !important; }
113
114
  /* ============ HEADER ============ */
114
115
  .header {
115
116
  grid-column: 1 / -1;
116
- background: var(--bg-tertiary);
117
+ background: rgba(8,8,10,0.6);
118
+ backdrop-filter: blur(12px);
119
+ -webkit-backdrop-filter: blur(12px);
117
120
  border-bottom: 1px solid var(--border);
118
121
  display: flex;
119
122
  align-items: center;
@@ -191,9 +194,9 @@ body.resizing .resize-handle { pointer-events: auto !important; }
191
194
  }
192
195
 
193
196
  .header-btn.active {
194
- border-color: rgba(0,232,223,0.3);
197
+ border-color: rgba(217,119,87,0.3);
195
198
  color: var(--neon-cyan);
196
- background: rgba(0,232,223,0.05);
199
+ background: rgba(217,119,87,0.05);
197
200
  }
198
201
 
199
202
  .header-btn .dot {
@@ -229,8 +232,8 @@ body.resizing .resize-handle { pointer-events: auto !important; }
229
232
  .search-box input::placeholder { color: var(--text-muted); }
230
233
 
231
234
  .search-box input:focus {
232
- border-color: rgba(0,232,223,0.3);
233
- box-shadow: 0 0 16px rgba(0,232,223,0.08);
235
+ border-color: rgba(217,119,87,0.3);
236
+ box-shadow: 0 0 16px rgba(217,119,87,0.08);
234
237
  width: 260px;
235
238
  }
236
239
 
@@ -309,24 +312,27 @@ body.resizing .resize-handle { pointer-events: auto !important; }
309
312
  .quick-switcher-overlay {
310
313
  position: fixed;
311
314
  inset: 0;
312
- background: rgba(6,6,12,0.7);
315
+ background: rgba(6,6,12,0.6);
313
316
  z-index: 200;
314
317
  display: none;
315
318
  align-items: flex-start;
316
319
  justify-content: center;
317
320
  padding-top: 15vh;
318
- backdrop-filter: blur(4px);
321
+ backdrop-filter: blur(12px);
322
+ -webkit-backdrop-filter: blur(12px);
319
323
  }
320
324
 
321
325
  .quick-switcher-overlay.active { display: flex; }
322
326
 
323
327
  .quick-switcher {
324
328
  width: 480px;
325
- background: var(--bg-card);
329
+ background: rgba(17,17,22,0.85);
326
330
  border: 1px solid var(--border);
327
331
  border-radius: var(--radius-lg);
328
- box-shadow: 0 20px 60px rgba(0,0,0,0.6), 0 0 1px var(--neon-cyan);
332
+ box-shadow: 0 24px 64px rgba(0,0,0,0.8), 0 0 0 1px rgba(217,119,87,0.1);
329
333
  overflow: hidden;
334
+ backdrop-filter: blur(20px);
335
+ -webkit-backdrop-filter: blur(20px);
330
336
  }
331
337
 
332
338
  .quick-switcher input {
@@ -434,7 +440,7 @@ body.resizing .resize-handle { pointer-events: auto !important; }
434
440
  .tree-item:hover { background: var(--bg-hover); }
435
441
 
436
442
  .tree-item.active {
437
- background: rgba(0,232,223,0.05);
443
+ background: rgba(217,119,87,0.05);
438
444
  }
439
445
 
440
446
  .tree-item.active::before {
@@ -523,9 +529,9 @@ body.resizing .resize-handle { pointer-events: auto !important; }
523
529
  font-size: 9px;
524
530
  padding: 2px 7px;
525
531
  border-radius: var(--radius-sm);
526
- background: rgba(0,232,223,0.08);
532
+ background: rgba(217,119,87,0.08);
527
533
  color: var(--neon-cyan);
528
- border: 1px solid rgba(0,232,223,0.2);
534
+ border: 1px solid rgba(217,119,87,0.2);
529
535
  cursor: pointer;
530
536
  display: flex;
531
537
  align-items: center;
@@ -533,7 +539,7 @@ body.resizing .resize-handle { pointer-events: auto !important; }
533
539
  transition: all 0.15s;
534
540
  }
535
541
 
536
- .active-tag:hover { background: rgba(0,232,223,0.15); }
542
+ .active-tag:hover { background: rgba(217,119,87,0.15); }
537
543
  .active-tag .remove { font-size: 11px; opacity: 0.5; }
538
544
  .active-tag .remove:hover { opacity: 1; }
539
545
 
@@ -566,9 +572,9 @@ body.resizing .resize-handle { pointer-events: auto !important; }
566
572
  }
567
573
 
568
574
  .tag-chip.selected {
569
- background: rgba(0,232,223,0.08);
575
+ background: rgba(217,119,87,0.08);
570
576
  color: var(--neon-cyan);
571
- border-color: rgba(0,232,223,0.2);
577
+ border-color: rgba(217,119,87,0.2);
572
578
  }
573
579
 
574
580
  .tag-chip .tag-count {
@@ -637,9 +643,9 @@ body.resizing .resize-handle { pointer-events: auto !important; }
637
643
  }
638
644
 
639
645
  .tag.clickable-tag:hover {
640
- background: rgba(0,232,223,0.08);
646
+ background: rgba(217,119,87,0.08);
641
647
  color: var(--neon-cyan);
642
- border-color: rgba(0,232,223,0.2);
648
+ border-color: rgba(217,119,87,0.2);
643
649
  cursor: pointer;
644
650
  }
645
651
 
@@ -657,119 +663,132 @@ body.resizing .resize-handle { pointer-events: auto !important; }
657
663
 
658
664
  /* ============ MARKDOWN ============ */
659
665
  .md-body h1 {
660
- font-size: 26px;
666
+ font-size: 28px;
661
667
  font-weight: 700;
662
- margin: 0 0 24px;
668
+ margin: 0 0 28px;
663
669
  color: var(--text-primary);
664
- letter-spacing: -0.3px;
670
+ letter-spacing: -0.5px;
671
+ text-shadow: 0 0 15px rgba(255,255,255,0.05);
665
672
  }
666
673
 
667
674
  .md-body h2 {
668
- font-size: 18px;
675
+ font-size: 20px;
669
676
  font-weight: 600;
670
- margin: 36px 0 12px;
677
+ margin: 40px 0 16px;
671
678
  color: var(--text-primary);
672
- padding-bottom: 8px;
673
- border-bottom: 1px solid var(--border-subtle);
679
+ padding-bottom: 10px;
680
+ border-bottom: 1px solid var(--border);
674
681
  }
675
682
 
676
683
  .md-body h3 {
677
- font-size: 15px;
684
+ font-size: 16px;
678
685
  font-weight: 600;
679
- margin: 28px 0 8px;
686
+ margin: 32px 0 12px;
680
687
  color: var(--neon-purple);
688
+ text-shadow: 0 0 8px rgba(168,139,250,0.2);
681
689
  }
682
690
 
683
691
  .md-body p {
684
- line-height: 1.8;
685
- margin: 8px 0;
692
+ line-height: 1.9;
693
+ margin: 12px 0;
686
694
  color: var(--text-secondary);
687
- font-size: 13.5px;
695
+ font-size: 14px;
688
696
  }
689
697
 
690
- .md-body ul, .md-body ol { padding-left: 24px; margin: 8px 0; }
698
+ .md-body ul, .md-body ol { padding-left: 24px; margin: 12px 0; }
691
699
 
692
700
  .md-body li {
693
- line-height: 1.8;
701
+ line-height: 1.9;
694
702
  color: var(--text-secondary);
695
- margin: 2px 0;
696
- font-size: 13.5px;
703
+ margin: 4px 0;
704
+ font-size: 14px;
697
705
  }
698
706
 
699
707
  .md-body li::marker { color: var(--text-muted); }
700
708
 
701
- .md-body a { color: var(--neon-blue); text-decoration: none; }
702
- .md-body a:hover { text-decoration: underline; }
709
+ .md-body a { color: var(--neon-blue); text-decoration: none; transition: color 0.2s; }
710
+ .md-body a:hover { color: var(--neon-cyan); text-decoration: underline; }
703
711
 
704
712
  .md-body strong { color: var(--text-primary); font-weight: 600; }
705
713
 
706
714
  .md-body code {
707
- background: var(--bg-card);
708
- padding: 1.5px 6px;
709
- border-radius: 3px;
715
+ background: var(--bg-hover);
716
+ padding: 3px 6px;
717
+ border-radius: 4px;
710
718
  font-family: var(--font-mono);
711
- font-size: 12px;
719
+ font-size: 12.5px;
712
720
  color: var(--neon-orange);
713
- border: 1px solid var(--border-subtle);
721
+ border: 1px solid var(--border);
714
722
  }
715
723
 
716
724
  .md-body pre {
717
- background: var(--bg-tertiary);
725
+ background: rgba(8,8,10,0.4);
718
726
  border: 1px solid var(--border);
719
- border-radius: var(--radius);
720
- padding: 16px 18px;
727
+ border-radius: var(--radius-lg);
728
+ padding: 20px;
721
729
  overflow-x: auto;
722
- margin: 16px 0;
730
+ margin: 20px 0;
731
+ box-shadow: inset 0 2px 10px rgba(0,0,0,0.2);
723
732
  }
724
733
 
725
734
  .md-body pre code {
726
735
  background: none;
727
736
  padding: 0;
728
737
  border: none;
729
- color: var(--text-primary);
730
- font-size: 12px;
731
- line-height: 1.7;
738
+ color: #c9c7c2;
739
+ font-size: 12.5px;
740
+ line-height: 1.8;
732
741
  }
733
742
 
734
743
  .md-body table {
735
744
  width: 100%;
736
- border-collapse: collapse;
737
- margin: 16px 0;
738
- font-size: 12.5px;
745
+ border-collapse: separate;
746
+ border-spacing: 0;
747
+ margin: 24px 0;
748
+ font-size: 13px;
749
+ border: 1px solid var(--border);
750
+ border-radius: var(--radius);
751
+ overflow: hidden;
739
752
  }
740
753
 
741
754
  .md-body th {
742
755
  text-align: left;
743
- padding: 8px 12px;
756
+ padding: 12px 16px;
744
757
  border-bottom: 1px solid var(--border);
745
- color: var(--text-secondary);
758
+ background: rgba(255,255,255,0.02);
759
+ color: var(--text-primary);
746
760
  font-family: var(--font-mono);
747
761
  font-weight: 600;
748
- font-size: 10px;
762
+ font-size: 11px;
749
763
  text-transform: uppercase;
750
764
  letter-spacing: 0.5px;
751
765
  }
752
766
 
753
767
  .md-body td {
754
- padding: 8px 12px;
755
- border-bottom: 1px solid var(--border-subtle);
768
+ padding: 12px 16px;
769
+ border-bottom: 1px solid var(--border);
756
770
  color: var(--text-secondary);
771
+ transition: background 0.15s;
757
772
  }
758
773
 
759
- .md-body tr:hover td { background: var(--bg-hover); }
774
+ .md-body tr:last-child td { border-bottom: none; }
775
+ .md-body tr:hover td { background: var(--bg-hover); color: var(--text-primary); }
760
776
 
761
777
  .md-body blockquote {
762
- border-left: 2px solid var(--neon-purple);
763
- padding-left: 16px;
764
- margin: 16px 0;
765
- color: var(--text-muted);
778
+ border-left: 3px solid var(--neon-purple);
779
+ padding: 12px 20px;
780
+ margin: 20px 0;
781
+ color: var(--text-secondary);
782
+ background: linear-gradient(90deg, rgba(168,139,250,0.05), transparent);
783
+ border-radius: 0 var(--radius) var(--radius) 0;
784
+ font-style: italic;
766
785
  }
767
786
 
768
787
  .md-body hr {
769
788
  border: none;
770
789
  height: 1px;
771
- background: var(--border);
772
- margin: 28px 0;
790
+ background: linear-gradient(90deg, transparent, var(--border), transparent);
791
+ margin: 36px 0;
773
792
  }
774
793
 
775
794
  .wikilink {
@@ -783,7 +802,7 @@ body.resizing .resize-handle { pointer-events: auto !important; }
783
802
 
784
803
  .wikilink:hover {
785
804
  color: var(--neon-cyan);
786
- border-bottom-color: rgba(0,232,223,0.4);
805
+ border-bottom-color: rgba(217,119,87,0.4);
787
806
  }
788
807
 
789
808
  /* ============ EMPTY STATE ============ */
@@ -840,15 +859,40 @@ body.resizing .resize-handle { pointer-events: auto !important; }
840
859
 
841
860
  .rp-section-title {
842
861
  font-family: var(--font-mono);
843
- font-size: 9px;
862
+ font-size: 10px;
844
863
  font-weight: 600;
845
864
  text-transform: uppercase;
846
865
  letter-spacing: 1.5px;
847
- color: var(--text-muted);
866
+ color: var(--text-secondary);
848
867
  margin-bottom: 8px;
849
868
  display: flex;
850
869
  align-items: center;
851
870
  gap: 6px;
871
+ cursor: pointer;
872
+ padding: 4px 6px;
873
+ border-radius: var(--radius-sm);
874
+ transition: background 0.15s;
875
+ }
876
+
877
+ .rp-section-title:hover {
878
+ background: var(--bg-hover);
879
+ color: var(--text-primary);
880
+ }
881
+
882
+ .rp-section-title .arrow {
883
+ margin-left: auto;
884
+ font-size: 8px;
885
+ color: var(--text-muted);
886
+ transition: transform 0.2s;
887
+ transform: rotate(90deg);
888
+ }
889
+
890
+ .rp-section.collapsed .rp-section-title .arrow {
891
+ transform: rotate(0deg);
892
+ }
893
+
894
+ .rp-section.collapsed .rp-section-body {
895
+ display: none;
852
896
  }
853
897
 
854
898
  .rp-section-title .rp-count {
@@ -859,122 +903,582 @@ body.resizing .resize-handle { pointer-events: auto !important; }
859
903
  border-radius: 8px;
860
904
  }
861
905
 
862
- .rp-item {
863
- display: flex;
906
+ .rp-item {
907
+ display: flex;
908
+ align-items: center;
909
+ padding: 5px 8px;
910
+ border-radius: var(--radius-sm);
911
+ cursor: pointer;
912
+ font-size: 11px;
913
+ gap: 8px;
914
+ margin-bottom: 1px;
915
+ transition: background 0.1s;
916
+ }
917
+
918
+ .rp-item:hover { background: var(--bg-hover); }
919
+
920
+ .rp-item .rp-score {
921
+ font-family: var(--font-mono);
922
+ font-size: 10px;
923
+ color: var(--neon-green);
924
+ min-width: 30px;
925
+ opacity: 0.8;
926
+ }
927
+
928
+ .rp-item .rp-title {
929
+ overflow: hidden;
930
+ text-overflow: ellipsis;
931
+ white-space: nowrap;
932
+ color: var(--neon-purple);
933
+ font-weight: 500;
934
+ }
935
+
936
+ .rp-item .rp-backlink-title { color: var(--neon-cyan); }
937
+
938
+ /* ============ OUTLINE ============ */
939
+ .outline-item {
940
+ display: block;
941
+ padding: 3px 8px;
942
+ border-radius: var(--radius-sm);
943
+ cursor: pointer;
944
+ font-size: 11px;
945
+ color: var(--text-secondary);
946
+ transition: all 0.1s;
947
+ overflow: hidden;
948
+ text-overflow: ellipsis;
949
+ white-space: nowrap;
950
+ margin-bottom: 1px;
951
+ }
952
+
953
+ .outline-item:hover {
954
+ background: var(--bg-hover);
955
+ color: var(--text-primary);
956
+ }
957
+
958
+ .outline-item.h2 { padding-left: 8px; }
959
+ .outline-item.h3 { padding-left: 20px; font-size: 10px; color: var(--text-muted); }
960
+
961
+ /* ============ GRAPH ============ */
962
+ #graph-section { margin-bottom: 12px; transition: all 0.3s; }
963
+ #graph-section.hidden { display: none; }
964
+
965
+ #graph-section.maximized {
966
+ position: fixed;
967
+ inset: 40px;
968
+ z-index: 250;
969
+ background: rgba(17,17,22,0.95);
970
+ border: 1px solid var(--border);
971
+ border-radius: var(--radius-lg);
972
+ padding: 20px;
973
+ display: flex;
974
+ flex-direction: column;
975
+ box-shadow: 0 24px 64px rgba(0,0,0,0.8), 0 0 0 1px rgba(217,119,87,0.3);
976
+ backdrop-filter: blur(20px);
977
+ -webkit-backdrop-filter: blur(20px);
978
+ }
979
+
980
+ #graph-container {
981
+ width: 100%;
982
+ height: 200px;
983
+ border: 1px solid var(--border);
984
+ border-radius: var(--radius);
985
+ overflow: hidden;
986
+ background: transparent;
987
+ }
988
+
989
+ #graph-section.maximized #graph-container {
990
+ flex: 1;
991
+ height: auto !important;
992
+ border: none;
993
+ }
994
+
995
+ #graph-container svg { width: 100%; height: 100%; }
996
+
997
+ .graph-node { cursor: pointer; }
998
+
999
+ .graph-node circle {
1000
+ fill: var(--neon-purple);
1001
+ filter: drop-shadow(0 0 3px rgba(168,139,250,0.5));
1002
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
1003
+ }
1004
+
1005
+ .graph-node:hover circle {
1006
+ fill: var(--neon-cyan);
1007
+ r: 8;
1008
+ filter: drop-shadow(0 0 8px rgba(217,119,87,0.7));
1009
+ }
1010
+
1011
+ .graph-node.active circle {
1012
+ fill: var(--neon-cyan);
1013
+ filter: drop-shadow(0 0 10px rgba(217,119,87,0.9));
1014
+ }
1015
+
1016
+ .graph-node text {
1017
+ fill: var(--text-muted);
1018
+ font-family: var(--font-mono);
1019
+ font-size: 11px;
1020
+ pointer-events: none;
1021
+ transition: all 0.2s;
1022
+ text-shadow: 0 0 2px var(--bg-primary), 0 0 4px var(--bg-primary);
1023
+ }
1024
+
1025
+ .graph-node:hover text, .graph-node.active text {
1026
+ fill: var(--text-primary);
1027
+ font-size: 13px;
1028
+ }
1029
+
1030
+ .graph-link {
1031
+ stroke: var(--neon-purple);
1032
+ stroke-width: 1.5;
1033
+ transition: opacity 0.3s;
1034
+ }
1035
+
1036
+ .graph-size-controls {
1037
+ margin-left: auto;
1038
+ display: flex;
1039
+ gap: 2px;
1040
+ }
1041
+
1042
+ .graph-size-btn {
1043
+ background: var(--bg-hover);
1044
+ border: 1px solid var(--border);
1045
+ color: var(--text-muted);
1046
+ font-family: var(--font-mono);
1047
+ font-size: 11px;
1048
+ width: 18px;
1049
+ height: 18px;
1050
+ border-radius: 3px;
1051
+ cursor: pointer;
1052
+ display: flex;
1053
+ align-items: center;
1054
+ justify-content: center;
1055
+ transition: all 0.15s;
1056
+ padding: 0;
1057
+ line-height: 1;
1058
+ }
1059
+
1060
+ .graph-size-btn:hover {
1061
+ border-color: var(--neon-cyan);
1062
+ color: var(--neon-cyan);
1063
+ }
1064
+
1065
+ .graph-resize-handle {
1066
+ height: 8px;
1067
+ cursor: ns-resize;
1068
+ position: relative;
1069
+ }
1070
+
1071
+ .graph-resize-handle::after {
1072
+ content: '';
1073
+ position: absolute;
1074
+ bottom: 2px;
1075
+ left: 50%;
1076
+ transform: translateX(-50%);
1077
+ width: 24px;
1078
+ height: 2px;
1079
+ background: var(--border);
1080
+ border-radius: 1px;
1081
+ transition: background 0.2s;
1082
+ }
1083
+
1084
+ .graph-resize-handle:hover::after {
1085
+ background: var(--neon-cyan);
1086
+ }
1087
+
1088
+ /* ============ SESSIONS PILLAR ============ */
1089
+ .pillar-divider {
1090
+ height: 1px;
1091
+ margin: 4px 12px;
1092
+ background: linear-gradient(90deg, transparent, var(--border), transparent);
1093
+ }
1094
+
1095
+ .pillar-label {
1096
+ font-family: var(--font-mono);
1097
+ font-size: 8px;
1098
+ font-weight: 700;
1099
+ text-transform: uppercase;
1100
+ letter-spacing: 2px;
1101
+ color: var(--text-muted);
1102
+ padding: 10px 18px 4px;
1103
+ display: flex;
1104
+ align-items: center;
1105
+ justify-content: space-between;
1106
+ user-select: none;
1107
+ }
1108
+
1109
+ .pillar-label .pillar-accent {
1110
+ width: 6px;
1111
+ height: 6px;
1112
+ border-radius: 50%;
1113
+ display: inline-block;
1114
+ margin-right: 6px;
1115
+ }
1116
+
1117
+ .pillar-label .pillar-accent.sessions { background: var(--neon-green); box-shadow: 0 0 6px var(--neon-green); }
1118
+ .pillar-label .pillar-accent.projects { background: var(--neon-cyan); box-shadow: 0 0 6px var(--neon-cyan); }
1119
+
1120
+ .pillar-label .pillar-action {
1121
+ font-size: 9px;
1122
+ font-weight: 500;
1123
+ color: var(--text-muted);
1124
+ cursor: pointer;
1125
+ padding: 1px 6px;
1126
+ border-radius: 3px;
1127
+ border: 1px solid transparent;
1128
+ transition: all 0.15s;
1129
+ letter-spacing: 0.5px;
1130
+ }
1131
+
1132
+ .pillar-label .pillar-action:hover {
1133
+ color: var(--neon-green);
1134
+ border-color: rgba(91,185,140,0.3);
1135
+ }
1136
+
1137
+ .session-search-box {
1138
+ padding: 6px 12px 4px;
1139
+ }
1140
+
1141
+ .session-search-box input {
1142
+ width: 100%;
1143
+ background: rgba(8,8,10,0.4);
1144
+ border: 1px solid var(--border);
1145
+ color: var(--text-primary);
1146
+ font-family: var(--font-mono);
1147
+ font-size: 10px;
1148
+ padding: 5px 10px 5px 24px;
1149
+ border-radius: var(--radius);
1150
+ outline: none;
1151
+ transition: all 0.2s;
1152
+ }
1153
+
1154
+ .session-search-box input::placeholder { color: var(--text-muted); }
1155
+
1156
+ .session-search-box input:focus {
1157
+ border-color: rgba(91,185,140,0.4);
1158
+ box-shadow: 0 0 8px rgba(91,185,140,0.08);
1159
+ }
1160
+
1161
+ .session-search-box {
1162
+ position: relative;
1163
+ }
1164
+
1165
+ .session-search-box .ss-icon {
1166
+ position: absolute;
1167
+ left: 20px;
1168
+ top: 50%;
1169
+ transform: translateY(-50%);
1170
+ color: var(--text-muted);
1171
+ font-size: 10px;
1172
+ pointer-events: none;
1173
+ }
1174
+
1175
+ .session-search-results {
1176
+ padding: 0 8px;
1177
+ max-height: 180px;
1178
+ overflow-y: auto;
1179
+ }
1180
+
1181
+ .session-search-results:empty { display: none; }
1182
+
1183
+ .ss-result {
1184
+ padding: 5px 10px;
1185
+ border-radius: var(--radius-sm);
1186
+ cursor: pointer;
1187
+ margin: 1px 0;
1188
+ transition: background 0.1s;
1189
+ }
1190
+
1191
+ .ss-result:hover { background: var(--bg-hover); }
1192
+
1193
+ .ss-result .ss-title {
1194
+ font-size: 11px;
1195
+ color: var(--neon-green);
1196
+ font-weight: 500;
1197
+ }
1198
+
1199
+ .ss-result .ss-meta {
1200
+ font-family: var(--font-mono);
1201
+ font-size: 9px;
1202
+ color: var(--text-muted);
1203
+ margin-top: 1px;
1204
+ }
1205
+
1206
+ .ss-result .ss-snippet {
1207
+ font-size: 10px;
1208
+ color: var(--text-secondary);
1209
+ margin-top: 2px;
1210
+ overflow: hidden;
1211
+ text-overflow: ellipsis;
1212
+ white-space: nowrap;
1213
+ }
1214
+
1215
+ .session-project-group {
1216
+ padding: 0 8px;
1217
+ }
1218
+
1219
+ .session-project-header {
1220
+ display: flex;
1221
+ align-items: center;
1222
+ padding: 5px 10px;
1223
+ border-radius: var(--radius-sm);
1224
+ cursor: pointer;
1225
+ gap: 6px;
1226
+ user-select: none;
1227
+ transition: background 0.1s;
1228
+ margin: 1px 0;
1229
+ }
1230
+
1231
+ .session-project-header:hover { background: var(--bg-hover); }
1232
+
1233
+ .session-project-header .sp-arrow {
1234
+ width: 12px;
1235
+ font-size: 8px;
1236
+ color: var(--text-muted);
1237
+ text-align: center;
1238
+ flex-shrink: 0;
1239
+ transition: transform 0.15s;
1240
+ }
1241
+
1242
+ .session-project-header .sp-arrow.open { transform: rotate(90deg); }
1243
+
1244
+ .session-project-header .sp-icon {
1245
+ font-size: 11px;
1246
+ color: var(--neon-green);
1247
+ flex-shrink: 0;
1248
+ opacity: 0.7;
1249
+ }
1250
+
1251
+ .session-project-header .sp-name {
1252
+ font-size: 12px;
1253
+ color: var(--text-primary);
1254
+ font-weight: 500;
1255
+ flex: 1;
1256
+ }
1257
+
1258
+ .session-project-header .sp-count {
1259
+ font-family: var(--font-mono);
1260
+ font-size: 9px;
1261
+ color: var(--text-muted);
1262
+ background: rgba(91,185,140,0.06);
1263
+ padding: 1px 5px;
1264
+ border-radius: 3px;
1265
+ }
1266
+
1267
+ .session-project-header .sp-add {
1268
+ background: transparent;
1269
+ border: 1px solid var(--border);
1270
+ color: var(--text-muted);
1271
+ font-family: var(--font-mono);
1272
+ font-size: 11px;
1273
+ width: 16px;
1274
+ height: 16px;
1275
+ border-radius: 3px;
1276
+ cursor: pointer;
1277
+ display: none;
1278
+ align-items: center;
1279
+ justify-content: center;
1280
+ transition: all 0.15s;
1281
+ padding: 0;
1282
+ line-height: 1;
1283
+ }
1284
+
1285
+ .session-project-header:hover .sp-add { display: flex; }
1286
+ .session-project-header .sp-add:hover { border-color: var(--neon-green); color: var(--neon-green); }
1287
+
1288
+ .session-list {
1289
+ padding-left: 12px;
1290
+ overflow: hidden;
1291
+ }
1292
+
1293
+ .session-list.collapsed { display: none; }
1294
+
1295
+ .session-item {
1296
+ display: flex;
1297
+ align-items: center;
1298
+ padding: 3px 10px;
1299
+ border-radius: var(--radius-sm);
1300
+ cursor: pointer;
1301
+ font-size: 11px;
1302
+ gap: 6px;
1303
+ user-select: none;
1304
+ transition: background 0.1s;
1305
+ position: relative;
1306
+ margin: 1px 0;
1307
+ }
1308
+
1309
+ .session-item:hover { background: var(--bg-hover); }
1310
+
1311
+ .session-item.active {
1312
+ background: rgba(91,185,140,0.05);
1313
+ }
1314
+
1315
+ .session-item.active::before {
1316
+ content: '';
1317
+ position: absolute;
1318
+ left: 0;
1319
+ top: 3px;
1320
+ bottom: 3px;
1321
+ width: 2px;
1322
+ background: var(--neon-green);
1323
+ border-radius: 1px;
1324
+ }
1325
+
1326
+ .session-item .si-icon {
1327
+ font-size: 9px;
1328
+ color: var(--neon-green);
1329
+ opacity: 0.5;
1330
+ flex-shrink: 0;
1331
+ }
1332
+
1333
+ .session-item .si-time {
1334
+ font-family: var(--font-mono);
1335
+ font-size: 10px;
1336
+ color: var(--text-secondary);
1337
+ }
1338
+
1339
+ .session-item.active .si-time { color: var(--neon-green); }
1340
+
1341
+ .session-badge {
1342
+ display: inline-flex;
864
1343
  align-items: center;
865
- padding: 5px 8px;
866
- border-radius: var(--radius-sm);
867
- cursor: pointer;
868
- font-size: 11px;
869
1344
  gap: 8px;
870
- margin-bottom: 1px;
871
- transition: background 0.1s;
1345
+ font-family: var(--font-mono);
1346
+ font-size: 10px;
1347
+ color: var(--neon-green);
1348
+ background: rgba(91,185,140,0.08);
1349
+ border: 1px solid rgba(91,185,140,0.15);
1350
+ padding: 4px 12px;
1351
+ border-radius: var(--radius);
1352
+ margin-bottom: 16px;
1353
+ letter-spacing: 0.5px;
872
1354
  }
873
1355
 
874
- .rp-item:hover { background: var(--bg-hover); }
875
-
876
- .rp-item .rp-score {
1356
+ .session-empty {
1357
+ padding: 12px 18px;
877
1358
  font-family: var(--font-mono);
878
1359
  font-size: 10px;
879
- color: var(--neon-green);
880
- min-width: 30px;
881
- opacity: 0.8;
1360
+ color: var(--text-muted);
1361
+ text-align: center;
882
1362
  }
883
1363
 
884
- .rp-item .rp-title {
885
- overflow: hidden;
886
- text-overflow: ellipsis;
887
- white-space: nowrap;
888
- color: var(--neon-purple);
889
- font-weight: 500;
1364
+ .session-create-overlay {
1365
+ position: fixed;
1366
+ inset: 0;
1367
+ background: rgba(6,6,12,0.6);
1368
+ z-index: 200;
1369
+ display: none;
1370
+ align-items: center;
1371
+ justify-content: center;
1372
+ backdrop-filter: blur(12px);
1373
+ -webkit-backdrop-filter: blur(12px);
890
1374
  }
891
1375
 
892
- .rp-item .rp-backlink-title { color: var(--neon-cyan); }
1376
+ .session-create-overlay.active { display: flex; }
893
1377
 
894
- /* ============ OUTLINE ============ */
895
- .outline-item {
896
- display: block;
897
- padding: 3px 8px;
898
- border-radius: var(--radius-sm);
899
- cursor: pointer;
900
- font-size: 11px;
901
- color: var(--text-secondary);
902
- transition: all 0.1s;
1378
+ .session-create-modal {
1379
+ width: 420px;
1380
+ background: rgba(17,17,22,0.85);
1381
+ border: 1px solid var(--border);
1382
+ border-radius: var(--radius-lg);
1383
+ box-shadow: 0 24px 64px rgba(0,0,0,0.8), 0 0 0 1px rgba(91,185,140,0.1);
903
1384
  overflow: hidden;
904
- text-overflow: ellipsis;
905
- white-space: nowrap;
906
- margin-bottom: 1px;
1385
+ backdrop-filter: blur(20px);
1386
+ -webkit-backdrop-filter: blur(20px);
907
1387
  }
908
1388
 
909
- .outline-item:hover {
910
- background: var(--bg-hover);
911
- color: var(--text-primary);
1389
+ .session-create-header {
1390
+ padding: 16px 20px 12px;
1391
+ border-bottom: 1px solid var(--border);
1392
+ font-family: var(--font-mono);
1393
+ font-size: 11px;
1394
+ font-weight: 600;
1395
+ color: var(--text-secondary);
1396
+ letter-spacing: 0.5px;
912
1397
  }
913
1398
 
914
- .outline-item.h2 { padding-left: 8px; }
915
- .outline-item.h3 { padding-left: 20px; font-size: 10px; color: var(--text-muted); }
1399
+ .session-create-body {
1400
+ padding: 16px 20px;
1401
+ display: flex;
1402
+ flex-direction: column;
1403
+ gap: 12px;
1404
+ }
916
1405
 
917
- /* ============ GRAPH ============ */
918
- #graph-section { margin-bottom: 12px; }
919
- #graph-section.hidden { display: none; }
1406
+ .session-field label {
1407
+ display: block;
1408
+ font-family: var(--font-mono);
1409
+ font-size: 9px;
1410
+ font-weight: 600;
1411
+ text-transform: uppercase;
1412
+ letter-spacing: 1px;
1413
+ color: var(--text-muted);
1414
+ margin-bottom: 4px;
1415
+ }
920
1416
 
921
- #graph-container {
1417
+ .session-field select,
1418
+ .session-field input,
1419
+ .session-field textarea {
922
1420
  width: 100%;
923
- height: 200px;
924
- border: 1px solid var(--border-subtle);
1421
+ background: rgba(8,8,10,0.5);
1422
+ border: 1px solid var(--border);
1423
+ color: var(--text-primary);
1424
+ font-family: var(--font-mono);
1425
+ font-size: 12px;
1426
+ padding: 8px 12px;
925
1427
  border-radius: var(--radius);
926
- overflow: hidden;
927
- background: var(--bg-tertiary);
1428
+ outline: none;
1429
+ transition: all 0.2s;
928
1430
  }
929
1431
 
930
- #graph-container svg { width: 100%; height: 100%; }
931
-
932
- .graph-node { cursor: pointer; }
933
-
934
- .graph-node circle {
935
- fill: var(--neon-purple);
936
- filter: drop-shadow(0 0 2px rgba(168,139,250,0.4));
937
- transition: all 0.2s;
1432
+ .session-field select:focus,
1433
+ .session-field input:focus,
1434
+ .session-field textarea:focus {
1435
+ border-color: rgba(91,185,140,0.4);
1436
+ box-shadow: 0 0 8px rgba(91,185,140,0.1);
1437
+ background: var(--bg-surface);
938
1438
  }
939
1439
 
940
- .graph-node:hover circle {
941
- fill: var(--neon-cyan);
942
- filter: drop-shadow(0 0 4px rgba(0,232,223,0.5));
1440
+ .session-field textarea {
1441
+ min-height: 60px;
1442
+ resize: vertical;
943
1443
  }
944
1444
 
945
- .graph-node.active circle {
946
- fill: var(--neon-cyan);
947
- r: 6;
948
- filter: drop-shadow(0 0 6px rgba(0,232,223,0.5));
1445
+ .session-create-footer {
1446
+ padding: 12px 20px;
1447
+ border-top: 1px solid var(--border);
1448
+ display: flex;
1449
+ justify-content: flex-end;
1450
+ gap: 8px;
949
1451
  }
950
1452
 
951
- .graph-node text {
952
- fill: var(--text-muted);
953
- font-family: var(--font-mono);
954
- font-size: 8px;
1453
+ .session-create-footer .edit-btn.create {
1454
+ background: rgba(91,185,140,0.1);
1455
+ color: var(--neon-green);
1456
+ border-color: rgba(91,185,140,0.3);
955
1457
  }
956
1458
 
957
- .graph-link {
958
- stroke: rgba(168,139,250,0.15);
959
- stroke-width: 1;
1459
+ .session-create-footer .edit-btn.create:hover {
1460
+ background: rgba(91,185,140,0.2);
1461
+ box-shadow: 0 0 10px rgba(91,185,140,0.15);
1462
+ transform: translateY(-1px);
960
1463
  }
961
1464
 
962
1465
  /* ============ SCROLLBAR ============ */
963
- ::-webkit-scrollbar { width: 4px; }
1466
+ ::-webkit-scrollbar { width: 5px; height: 5px; }
964
1467
  ::-webkit-scrollbar-track { background: transparent; }
965
- ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
966
- ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
1468
+ ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: 4px; }
1469
+ ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.15); }
967
1470
 
968
1471
  /* ============ EDIT MODAL ============ */
969
1472
  .edit-overlay {
970
1473
  position: fixed;
971
1474
  inset: 0;
972
- background: rgba(6,6,12,0.75);
1475
+ background: rgba(6,6,12,0.6);
973
1476
  z-index: 200;
974
1477
  display: none;
975
1478
  align-items: center;
976
1479
  justify-content: center;
977
- backdrop-filter: blur(4px);
1480
+ backdrop-filter: blur(12px);
1481
+ -webkit-backdrop-filter: blur(12px);
978
1482
  }
979
1483
 
980
1484
  .edit-overlay.active { display: flex; }
@@ -982,13 +1486,15 @@ body.resizing .resize-handle { pointer-events: auto !important; }
982
1486
  .edit-modal {
983
1487
  width: 640px;
984
1488
  max-height: 80vh;
985
- background: var(--bg-card);
1489
+ background: rgba(17,17,22,0.85);
986
1490
  border: 1px solid var(--border);
987
1491
  border-radius: var(--radius-lg);
988
- box-shadow: 0 20px 60px rgba(0,0,0,0.6);
1492
+ box-shadow: 0 24px 64px rgba(0,0,0,0.8), 0 0 0 1px rgba(217,119,87,0.1);
989
1493
  display: flex;
990
1494
  flex-direction: column;
991
1495
  overflow: hidden;
1496
+ backdrop-filter: blur(20px);
1497
+ -webkit-backdrop-filter: blur(20px);
992
1498
  }
993
1499
 
994
1500
  .edit-header {
@@ -996,7 +1502,7 @@ body.resizing .resize-handle { pointer-events: auto !important; }
996
1502
  align-items: center;
997
1503
  justify-content: space-between;
998
1504
  padding: 12px 16px;
999
- border-bottom: 1px solid var(--border-subtle);
1505
+ border-bottom: 1px solid var(--border);
1000
1506
  font-family: var(--font-mono);
1001
1507
  font-size: 11px;
1002
1508
  color: var(--text-secondary);
@@ -1013,7 +1519,7 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1013
1519
  border-radius: var(--radius);
1014
1520
  border: 1px solid var(--border);
1015
1521
  cursor: pointer;
1016
- transition: all 0.15s;
1522
+ transition: all 0.2s ease-out;
1017
1523
  letter-spacing: 0.5px;
1018
1524
  }
1019
1525
 
@@ -1022,25 +1528,25 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1022
1528
  color: var(--text-muted);
1023
1529
  }
1024
1530
 
1025
- .edit-btn.cancel:hover { color: var(--text-secondary); border-color: var(--text-muted); }
1531
+ .edit-btn.cancel:hover { color: var(--text-secondary); border-color: var(--text-muted); transform: translateY(-1px); }
1026
1532
 
1027
1533
  .edit-btn.save {
1028
- background: rgba(0,232,223,0.1);
1534
+ background: rgba(217,119,87,0.1);
1029
1535
  color: var(--neon-cyan);
1030
- border-color: rgba(0,232,223,0.3);
1536
+ border-color: rgba(217,119,87,0.3);
1031
1537
  }
1032
1538
 
1033
- .edit-btn.save:hover { background: rgba(0,232,223,0.2); }
1539
+ .edit-btn.save:hover { background: rgba(217,119,87,0.2); box-shadow: 0 0 10px rgba(217,119,87,0.15); transform: translateY(-1px); }
1034
1540
 
1035
1541
  .edit-textarea {
1036
1542
  flex: 1;
1037
1543
  min-height: 300px;
1038
- background: var(--bg-primary);
1544
+ background: rgba(8,8,10,0.3);
1039
1545
  border: none;
1040
1546
  color: var(--text-primary);
1041
1547
  font-family: var(--font-mono);
1042
1548
  font-size: 13px;
1043
- line-height: 1.7;
1549
+ line-height: 1.8;
1044
1550
  padding: 16px 20px;
1045
1551
  resize: none;
1046
1552
  outline: none;
@@ -1112,6 +1618,9 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1112
1618
  <span class="dot"></span>
1113
1619
  <span>GRAPH</span>
1114
1620
  </button>
1621
+ <button class="header-btn" id="new-project-btn" title="New project">
1622
+ <span>+ PROJECT</span>
1623
+ </button>
1115
1624
  <div class="search-box">
1116
1625
  <span class="search-icon">&#9906;</span>
1117
1626
  <input type="text" id="search-input" placeholder="Search...">
@@ -1124,8 +1633,26 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1124
1633
  <!-- Left sidebar -->
1125
1634
  <div class="sidebar">
1126
1635
  <div class="sidebar-scroll">
1636
+ <!-- SESSIONS PILLAR (Claude-Mem) -->
1637
+ <div class="pillar-label">
1638
+ <span><span class="pillar-accent sessions"></span>SESSIONS</span>
1639
+ <span class="pillar-action" id="new-session-btn">+ NEW</span>
1640
+ </div>
1641
+ <div class="session-search-box">
1642
+ <span class="ss-icon">&#9906;</span>
1643
+ <input type="text" id="session-search-input" placeholder="Semantic search sessions...">
1644
+ </div>
1645
+ <div class="session-search-results" id="session-search-results"></div>
1646
+ <div id="session-tree"></div>
1647
+
1648
+ <div class="pillar-divider"></div>
1649
+
1650
+ <!-- PROJECTS PILLAR (Obsidian) -->
1651
+ <div class="pillar-label">
1652
+ <span><span class="pillar-accent projects"></span>PROJECTS</span>
1653
+ <span class="pillar-action" id="new-project-sidebar-btn">+ NEW</span>
1654
+ </div>
1127
1655
  <div class="sidebar-section">
1128
- <div class="section-label">Explorer</div>
1129
1656
  <div id="filter-info" class="filter-info" style="display:none;">
1130
1657
  <span id="filter-count"></span>
1131
1658
  <span class="clear-btn" id="clear-filters">clear</span>
@@ -1164,28 +1691,29 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1164
1691
  <!-- Right panel -->
1165
1692
  <div class="right-panel" id="right-panel">
1166
1693
  <div id="graph-section">
1167
- <div class="rp-section-title">Local Graph</div>
1694
+ <div class="rp-section-title">Local Graph <span class="graph-size-controls"><button class="graph-size-btn" id="graph-shrink" title="Shrink graph">&minus;</button><button class="graph-size-btn" id="graph-grow" title="Grow graph">+</button><button class="graph-size-btn" id="graph-max" title="Maximize graph">&#9974;</button></span></div>
1168
1695
  <div id="graph-container"></div>
1696
+ <div id="graph-resize-handle" class="graph-resize-handle"></div>
1169
1697
  </div>
1170
1698
  <div id="rp-outline" class="rp-section" style="display:none;">
1171
- <div class="rp-section-title">Outline <span class="rp-count" id="outline-count"></span></div>
1172
- <div id="rp-outline-list"></div>
1699
+ <div class="rp-section-title" onclick="this.parentElement.classList.toggle('collapsed')">Outline <span class="rp-count" id="outline-count"></span><span class="arrow">&#9654;</span></div>
1700
+ <div id="rp-outline-list" class="rp-section-body"></div>
1173
1701
  </div>
1174
1702
  <div id="rp-backlinks" class="rp-section" style="display:none;">
1175
- <div class="rp-section-title">Backlinks <span class="rp-count" id="bl-count"></span></div>
1176
- <div id="rp-backlinks-list"></div>
1703
+ <div class="rp-section-title" onclick="this.parentElement.classList.toggle('collapsed')">Backlinks <span class="rp-count" id="bl-count"></span><span class="arrow">&#9654;</span></div>
1704
+ <div id="rp-backlinks-list" class="rp-section-body"></div>
1177
1705
  </div>
1178
1706
  <div id="rp-related" class="rp-section" style="display:none;">
1179
- <div class="rp-section-title">Related <span class="rp-count" id="rel-count"></span></div>
1180
- <div id="rp-related-list"></div>
1707
+ <div class="rp-section-title" onclick="this.parentElement.classList.toggle('collapsed')">Related <span class="rp-count" id="rel-count"></span><span class="arrow">&#9654;</span></div>
1708
+ <div id="rp-related-list" class="rp-section-body"></div>
1181
1709
  </div>
1182
1710
  <div id="rp-outlinks" class="rp-section" style="display:none;">
1183
- <div class="rp-section-title">Outgoing Links <span class="rp-count" id="out-count"></span></div>
1184
- <div id="rp-outlinks-list"></div>
1711
+ <div class="rp-section-title" onclick="this.parentElement.classList.toggle('collapsed')">Outgoing Links <span class="rp-count" id="out-count"></span><span class="arrow">&#9654;</span></div>
1712
+ <div id="rp-outlinks-list" class="rp-section-body"></div>
1185
1713
  </div>
1186
1714
  <div id="rp-unlinked" class="rp-section" style="display:none;">
1187
- <div class="rp-section-title">Unlinked Mentions <span class="rp-count" id="unlinked-count"></span></div>
1188
- <div id="rp-unlinked-list"></div>
1715
+ <div class="rp-section-title" onclick="this.parentElement.classList.toggle('collapsed')">Unlinked Mentions <span class="rp-count" id="unlinked-count"></span><span class="arrow">&#9654;</span></div>
1716
+ <div id="rp-unlinked-list" class="rp-section-body"></div>
1189
1717
  </div>
1190
1718
  </div>
1191
1719
  </div>
@@ -1212,11 +1740,55 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1212
1740
  </div>
1213
1741
  </div>
1214
1742
 
1743
+ <!-- Session Create Modal -->
1744
+ <div class="session-create-overlay" id="session-create-overlay">
1745
+ <div class="session-create-modal">
1746
+ <div class="session-create-header">NEW SESSION</div>
1747
+ <div class="session-create-body">
1748
+ <div class="session-field">
1749
+ <label>Project</label>
1750
+ <input type="text" id="session-project" list="project-list" placeholder="Project name...">
1751
+ <datalist id="project-list"></datalist>
1752
+ </div>
1753
+ <div class="session-field">
1754
+ <label>Summary</label>
1755
+ <textarea id="session-summary" placeholder="What are you working on?"></textarea>
1756
+ </div>
1757
+ </div>
1758
+ <div class="session-create-footer">
1759
+ <button class="edit-btn cancel" id="session-cancel">CANCEL</button>
1760
+ <button class="edit-btn create" id="session-create-btn">CREATE</button>
1761
+ </div>
1762
+ </div>
1763
+ </div>
1764
+
1765
+ <!-- Project Create Modal -->
1766
+ <div class="session-create-overlay" id="project-create-overlay">
1767
+ <div class="session-create-modal">
1768
+ <div class="session-create-header">NEW PROJECT</div>
1769
+ <div class="session-create-body">
1770
+ <div class="session-field">
1771
+ <label>Project Name</label>
1772
+ <input type="text" id="project-name-input" placeholder="My Project...">
1773
+ </div>
1774
+ <div class="session-field">
1775
+ <label>Overview (optional)</label>
1776
+ <textarea id="project-overview-input" placeholder="Brief project description, goals, tech stack..."></textarea>
1777
+ </div>
1778
+ </div>
1779
+ <div class="session-create-footer">
1780
+ <button class="edit-btn cancel" id="project-cancel">CANCEL</button>
1781
+ <button class="edit-btn create" id="project-create-btn">CREATE</button>
1782
+ </div>
1783
+ </div>
1784
+ </div>
1785
+
1215
1786
  <script>
1216
1787
  let currentPath = null;
1217
1788
  let treeData = null;
1218
1789
  let allNotes = {};
1219
1790
  let graphVisible = true;
1791
+ let currentNote = null;
1220
1792
  let activeTagFilters = new Set();
1221
1793
  let qsSelectedIndex = 0;
1222
1794
 
@@ -1255,6 +1827,8 @@ document.addEventListener('keydown', (e) => {
1255
1827
  }
1256
1828
  if (e.key === 'Escape') {
1257
1829
  closeQuickSwitcher();
1830
+ closeSessionCreate();
1831
+ closeProjectCreate();
1258
1832
  }
1259
1833
  });
1260
1834
 
@@ -1350,9 +1924,11 @@ function updateQsSelection() {
1350
1924
  });
1351
1925
  }
1352
1926
 
1353
- // --- File Tree ---
1927
+ // --- File Tree (Projects pillar only — no Sessions folders) ---
1354
1928
  function renderTree(node, container) {
1355
1929
  if (node.type === 'folder' && node.name !== 'vault') {
1930
+ if (node.name === 'Sessions') return;
1931
+
1356
1932
  const item = document.createElement('div');
1357
1933
  item.className = 'tree-item';
1358
1934
  item.innerHTML = `<span class="arrow open">&#9654;</span><span class="icon folder-icon">&#9776;</span><span class="name">${node.name}</span>`;
@@ -1374,7 +1950,8 @@ function renderTree(node, container) {
1374
1950
  const item = document.createElement('div');
1375
1951
  item.className = 'tree-item';
1376
1952
  item.dataset.path = node.path;
1377
- item.innerHTML = `<span class="arrow" style="visibility:hidden">&#9654;</span><span class="icon note-icon">&#9671;</span><span class="name">${node.name.replace('.md', '')}</span>`;
1953
+ const displayName = node.name.replace('.md', '');
1954
+ item.innerHTML = `<span class="arrow" style="visibility:hidden">&#9654;</span><span class="icon note-icon">&#9671;</span><span class="name">${displayName}</span>`;
1378
1955
  item.addEventListener('click', () => loadNote(node.path));
1379
1956
  container.appendChild(item);
1380
1957
 
@@ -1383,13 +1960,118 @@ function renderTree(node, container) {
1383
1960
  }
1384
1961
  }
1385
1962
 
1963
+ // --- Session Tree (Sessions pillar — grouped by project with dropdowns) ---
1964
+ function formatSessionTimestamp(filename) {
1965
+ const stem = filename.replace('.md', '');
1966
+ const match = stem.match(/^(\d{4})-(\d{2})-(\d{2})_(\d{2})(\d{2})(\d{2})$/);
1967
+ if (!match) return stem;
1968
+ const [, y, mo, d, h, mi] = match;
1969
+ const hr = parseInt(h);
1970
+ const ampm = hr >= 12 ? 'PM' : 'AM';
1971
+ const hr12 = hr % 12 || 12;
1972
+ const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
1973
+ return `${months[parseInt(mo)-1]} ${parseInt(d)}, ${hr12}:${mi} ${ampm}`;
1974
+ }
1975
+
1976
+ function renderSessionTree(sessionsData) {
1977
+ const container = document.getElementById('session-tree');
1978
+ container.innerHTML = '';
1979
+
1980
+ if (Object.keys(sessionsData).length === 0) {
1981
+ container.innerHTML = '<div class="session-empty">No sessions yet</div>';
1982
+ return;
1983
+ }
1984
+
1985
+ const sortedProjects = Object.keys(sessionsData).sort();
1986
+ sortedProjects.forEach(project => {
1987
+ const sessions = sessionsData[project];
1988
+ const group = document.createElement('div');
1989
+ group.className = 'session-project-group';
1990
+
1991
+ const header = document.createElement('div');
1992
+ header.className = 'session-project-header';
1993
+ header.innerHTML = `
1994
+ <span class="sp-arrow open">&#9654;</span>
1995
+ <span class="sp-icon">&#9716;</span>
1996
+ <span class="sp-name">${project}</span>
1997
+ <span class="sp-count">${sessions.length}</span>
1998
+ <button class="sp-add" title="New session for ${project}">+</button>
1999
+ `;
2000
+
2001
+ const list = document.createElement('div');
2002
+ list.className = 'session-list';
2003
+
2004
+ sessions.sort((a, b) => b.path.localeCompare(a.path));
2005
+ sessions.forEach(session => {
2006
+ const item = document.createElement('div');
2007
+ item.className = 'session-item';
2008
+ item.dataset.path = session.path;
2009
+ const displayTime = formatSessionTimestamp(session.path.split('/').pop());
2010
+ item.innerHTML = `<span class="si-icon">&#9679;</span><span class="si-time">${displayTime}</span>`;
2011
+ item.title = session.title || session.path;
2012
+ item.addEventListener('click', () => loadNote(session.path));
2013
+ list.appendChild(item);
2014
+ });
2015
+
2016
+ header.addEventListener('click', (e) => {
2017
+ if (e.target.classList.contains('sp-add')) {
2018
+ e.stopPropagation();
2019
+ openSessionCreate(project);
2020
+ return;
2021
+ }
2022
+ e.stopPropagation();
2023
+ header.querySelector('.sp-arrow').classList.toggle('open');
2024
+ list.classList.toggle('collapsed');
2025
+ });
2026
+
2027
+ group.appendChild(header);
2028
+ group.appendChild(list);
2029
+ container.appendChild(group);
2030
+ });
2031
+ }
2032
+
2033
+ // --- Session Semantic Search ---
2034
+ let sessionSearchTimeout;
2035
+ const sessionSearchInput = document.getElementById('session-search-input');
2036
+ const sessionSearchResults = document.getElementById('session-search-results');
2037
+
2038
+ sessionSearchInput.addEventListener('input', () => {
2039
+ clearTimeout(sessionSearchTimeout);
2040
+ const q = sessionSearchInput.value.trim();
2041
+ if (!q) { sessionSearchResults.innerHTML = ''; return; }
2042
+ sessionSearchTimeout = setTimeout(async () => {
2043
+ const results = await fetchJSON(`/api/sessions/search?q=${encodeURIComponent(q)}`);
2044
+ sessionSearchResults.innerHTML = '';
2045
+ if (results.length === 0) {
2046
+ sessionSearchResults.innerHTML = '<div class="ss-result"><span class="ss-meta">No matching sessions</span></div>';
2047
+ return;
2048
+ }
2049
+ results.forEach(r => {
2050
+ const div = document.createElement('div');
2051
+ div.className = 'ss-result';
2052
+ const dist = r.distance !== undefined ? ` &middot; dist: ${r.distance.toFixed(2)}` : '';
2053
+ div.innerHTML = `<div class="ss-title">${r.title}</div><div class="ss-meta">${r.path}${dist}</div>${r.snippet ? `<div class="ss-snippet">${r.snippet.substring(0, 120)}</div>` : ''}`;
2054
+ div.addEventListener('click', () => {
2055
+ loadNote(r.path);
2056
+ sessionSearchInput.value = '';
2057
+ sessionSearchResults.innerHTML = '';
2058
+ });
2059
+ sessionSearchResults.appendChild(div);
2060
+ });
2061
+ }, 250);
2062
+ });
2063
+
1386
2064
  // --- Note rendering ---
1387
2065
  async function loadNote(path) {
1388
2066
  currentPath = path;
1389
2067
  const note = await fetchJSON(`/api/note/${path}`);
1390
2068
  if (note.error) return;
2069
+ currentNote = note;
1391
2070
 
1392
- document.querySelectorAll('.tree-item').forEach(el => {
2071
+ document.querySelectorAll('.tree-item[data-path]').forEach(el => {
2072
+ el.classList.toggle('active', el.dataset.path === path);
2073
+ });
2074
+ document.querySelectorAll('.session-item[data-path]').forEach(el => {
1393
2075
  el.classList.toggle('active', el.dataset.path === path);
1394
2076
  });
1395
2077
 
@@ -1404,6 +2086,13 @@ async function loadNote(path) {
1404
2086
  let html = '<div class="fade-in">';
1405
2087
 
1406
2088
  html += `<button class="note-edit-btn" onclick="openEditor('${path}')">EDIT</button>`;
2089
+
2090
+ const isSession = path.includes('/Sessions/') || path.startsWith('Sessions/');
2091
+ if (isSession) {
2092
+ const sessionProject = path.includes('/Sessions/') ? path.split('/Sessions/')[0] : 'General';
2093
+ html += `<div class="session-badge">&#9716; SESSION &middot; ${sessionProject}</div>`;
2094
+ }
2095
+
1407
2096
  html += '<div class="note-properties">';
1408
2097
  (note.tags || []).forEach(t => { html += `<span class="tag clickable-tag" data-tag="${t}">#${t}</span>`; });
1409
2098
  Object.entries(note.properties || {}).forEach(([k, v]) => {
@@ -1619,6 +2308,156 @@ document.addEventListener('keydown', (e) => {
1619
2308
  }
1620
2309
  });
1621
2310
 
2311
+ // --- Session Management ---
2312
+ function openSessionCreate(defaultProject) {
2313
+ const overlay = document.getElementById('session-create-overlay');
2314
+ const projectInput = document.getElementById('session-project');
2315
+ const datalist = document.getElementById('project-list');
2316
+ const summaryInput = document.getElementById('session-summary');
2317
+
2318
+ const projects = new Set();
2319
+ for (const path of Object.keys(allNotes)) {
2320
+ const parts = path.split('/');
2321
+ if (parts.length > 1) projects.add(parts[0]);
2322
+ }
2323
+
2324
+ datalist.innerHTML = '';
2325
+ for (const p of [...projects].sort()) {
2326
+ const opt = document.createElement('option');
2327
+ opt.value = p;
2328
+ datalist.appendChild(opt);
2329
+ }
2330
+
2331
+ projectInput.value = defaultProject || '';
2332
+ summaryInput.value = '';
2333
+ overlay.classList.add('active');
2334
+
2335
+ if (defaultProject) summaryInput.focus();
2336
+ else projectInput.focus();
2337
+ }
2338
+
2339
+ function closeSessionCreate() {
2340
+ document.getElementById('session-create-overlay').classList.remove('active');
2341
+ }
2342
+
2343
+ document.getElementById('session-cancel').addEventListener('click', closeSessionCreate);
2344
+ document.getElementById('session-create-overlay').addEventListener('click', (e) => {
2345
+ if (e.target === e.currentTarget) closeSessionCreate();
2346
+ });
2347
+
2348
+ document.getElementById('session-create-btn').addEventListener('click', async () => {
2349
+ const project = document.getElementById('session-project').value.trim();
2350
+ const summary = document.getElementById('session-summary').value.trim();
2351
+
2352
+ if (!project) {
2353
+ document.getElementById('session-project').focus();
2354
+ return;
2355
+ }
2356
+
2357
+ const resp = await fetch('/api/sessions/create', {
2358
+ method: 'POST',
2359
+ headers: { 'Content-Type': 'application/json' },
2360
+ body: JSON.stringify({ project, summary }),
2361
+ });
2362
+
2363
+ const result = await resp.json();
2364
+ if (result.ok) {
2365
+ closeSessionCreate();
2366
+ await refreshTree();
2367
+ loadNote(result.path);
2368
+ }
2369
+ });
2370
+
2371
+ // --- Sidebar pillar buttons ---
2372
+ document.getElementById('new-session-btn').addEventListener('click', () => openSessionCreate(''));
2373
+
2374
+ document.getElementById('new-project-sidebar-btn').addEventListener('click', () => {
2375
+ document.getElementById('project-create-overlay').classList.add('active');
2376
+ document.getElementById('project-name-input').value = '';
2377
+ document.getElementById('project-overview-input').value = '';
2378
+ document.getElementById('project-name-input').focus();
2379
+ });
2380
+
2381
+ // --- Project Management ---
2382
+ function closeProjectCreate() {
2383
+ document.getElementById('project-create-overlay').classList.remove('active');
2384
+ }
2385
+
2386
+ document.getElementById('new-project-btn').addEventListener('click', () => {
2387
+ document.getElementById('project-create-overlay').classList.add('active');
2388
+ document.getElementById('project-name-input').value = '';
2389
+ document.getElementById('project-overview-input').value = '';
2390
+ document.getElementById('project-name-input').focus();
2391
+ });
2392
+
2393
+ document.getElementById('project-cancel').addEventListener('click', closeProjectCreate);
2394
+ document.getElementById('project-create-overlay').addEventListener('click', (e) => {
2395
+ if (e.target === e.currentTarget) closeProjectCreate();
2396
+ });
2397
+
2398
+ document.getElementById('project-create-btn').addEventListener('click', async () => {
2399
+ const name = document.getElementById('project-name-input').value.trim();
2400
+ const overview = document.getElementById('project-overview-input').value.trim();
2401
+ if (!name) {
2402
+ document.getElementById('project-name-input').focus();
2403
+ return;
2404
+ }
2405
+ const resp = await fetch('/api/projects/create', {
2406
+ method: 'POST',
2407
+ headers: { 'Content-Type': 'application/json' },
2408
+ body: JSON.stringify({ name, overview }),
2409
+ });
2410
+ const result = await resp.json();
2411
+ if (result.ok) {
2412
+ closeProjectCreate();
2413
+ await refreshTree();
2414
+ loadNote(result.path);
2415
+ }
2416
+ });
2417
+
2418
+ async function refreshTree() {
2419
+ const [rawTreeData, sessionsData, stats] = await Promise.all([
2420
+ fetchJSON('/api/tree'),
2421
+ fetchJSON('/api/sessions'),
2422
+ fetchJSON('/api/stats'),
2423
+ ]);
2424
+
2425
+ allNotes = {};
2426
+ function walk(node) {
2427
+ if (node.type === 'note') {
2428
+ allNotes[node.path] = { title: node.name.replace('.md', ''), tags: node.tags || [] };
2429
+ }
2430
+ (node.children || []).forEach(walk);
2431
+ }
2432
+ walk(rawTreeData);
2433
+
2434
+ const treeEl = document.getElementById('file-tree');
2435
+ treeEl.innerHTML = '';
2436
+ renderTree(rawTreeData, treeEl);
2437
+
2438
+ renderSessionTree(sessionsData);
2439
+ renderTagCloud(collectAllTags());
2440
+
2441
+ document.getElementById('stats-bar').innerHTML = `
2442
+ <span><span class="stat-val">${stats.notes}</span> notes</span>
2443
+ <span><span class="stat-val">${stats.folders}</span> folders</span>
2444
+ <span><span class="stat-val">${stats.tags}</span> tags</span>
2445
+ <span><span class="stat-val">${stats.links}</span> links</span>
2446
+ `;
2447
+
2448
+ markActiveItems();
2449
+ }
2450
+
2451
+ function markActiveItems() {
2452
+ if (!currentPath) return;
2453
+ document.querySelectorAll('.tree-item[data-path]').forEach(el => {
2454
+ el.classList.toggle('active', el.dataset.path === currentPath);
2455
+ });
2456
+ document.querySelectorAll('.session-item[data-path]').forEach(el => {
2457
+ el.classList.toggle('active', el.dataset.path === currentPath);
2458
+ });
2459
+ }
2460
+
1622
2461
  // --- Graph ---
1623
2462
  function renderGraph(note) {
1624
2463
  const container = document.getElementById('graph-container');
@@ -1627,71 +2466,202 @@ function renderGraph(note) {
1627
2466
  const nodes = new Map();
1628
2467
  const links = [];
1629
2468
 
1630
- nodes.set(note.path, { id: note.path, title: note.title, active: true });
2469
+ nodes.set(note.path, { id: note.path, title: note.title, active: true, tags: note.tags || [] });
2470
+
2471
+ const projectPrefix = note.path.includes('/') ? note.path.split('/')[0] + '/' : '';
2472
+ const inSameProject = (p) => !projectPrefix ? !p.includes('/') : p.startsWith(projectPrefix);
1631
2473
 
1632
2474
  (note.links || []).forEach(link => {
1633
2475
  const path = findNotePath(link);
1634
- if (path && !nodes.has(path)) {
1635
- const n = allNotes[path];
1636
- nodes.set(path, { id: path, title: n ? n.title : link, active: false });
2476
+ if (path && inSameProject(path)) {
2477
+ if (!nodes.has(path)) {
2478
+ const n = allNotes[path];
2479
+ nodes.set(path, { id: path, title: n ? n.title : link, active: false, tags: n ? n.tags : [] });
2480
+ }
2481
+ links.push({ source: note.path, target: path });
1637
2482
  }
1638
- if (path) links.push({ source: note.path, target: path });
1639
2483
  });
1640
2484
 
1641
- (note.backlinks || []).forEach(path => {
1642
- if (!nodes.has(path)) {
1643
- const n = allNotes[path];
1644
- nodes.set(path, { id: path, title: n ? n.title : path, active: false });
2485
+ (note.backlinks || []).forEach(bl => {
2486
+ const blPath = typeof bl === 'string' ? bl : (bl && bl.path);
2487
+ if (!blPath) return;
2488
+ if (inSameProject(blPath)) {
2489
+ if (!nodes.has(blPath)) {
2490
+ const n = allNotes[blPath];
2491
+ const title = (bl && typeof bl === 'object' && bl.title) ? bl.title : (n ? n.title : blPath);
2492
+ nodes.set(blPath, { id: blPath, title, active: false, tags: n ? n.tags : [] });
2493
+ }
2494
+ links.push({ source: blPath, target: note.path });
1645
2495
  }
1646
- links.push({ source: path, target: note.path });
1647
2496
  });
1648
2497
 
1649
2498
  (note.related || []).forEach(r => {
1650
- if (!nodes.has(r.path)) {
1651
- nodes.set(r.path, { id: r.path, title: r.title, active: false });
2499
+ if (r.path && inSameProject(r.path)) {
2500
+ if (!nodes.has(r.path)) {
2501
+ const n = allNotes[r.path];
2502
+ nodes.set(r.path, { id: r.path, title: r.title, active: false, tags: n ? n.tags : [] });
2503
+ }
2504
+ links.push({ source: note.path, target: r.path, dashed: true });
1652
2505
  }
1653
2506
  });
1654
2507
 
1655
2508
  const nodeArray = Array.from(nodes.values());
2509
+ nodeArray.forEach(n => n.degree = 0);
2510
+ links.forEach(l => {
2511
+ const s = nodes.get(l.source);
2512
+ const t = nodes.get(l.target);
2513
+ if (s) s.degree++;
2514
+ if (t) t.degree++;
2515
+ });
2516
+
1656
2517
  if (nodeArray.length < 2) {
1657
- container.innerHTML = '<svg></svg>';
2518
+ container.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-size:10px;font-family:var(--font-mono)">No connections</div>';
1658
2519
  return;
1659
2520
  }
1660
2521
 
1661
- const width = container.clientWidth;
1662
- const height = container.clientHeight;
2522
+ const isMaximized = document.getElementById('graph-section').classList.contains('maximized');
2523
+ const width = container.clientWidth || 248;
2524
+ const height = container.clientHeight || 200;
2525
+ const margin = isMaximized ? 60 : 24;
1663
2526
 
1664
2527
  const svg = d3.select(container).append('svg')
1665
2528
  .attr('viewBox', `0 0 ${width} ${height}`);
1666
2529
 
2530
+ const g = svg.append('g');
2531
+
2532
+ svg.call(d3.zoom()
2533
+ .scaleExtent([0.2, 4])
2534
+ .on('zoom', (event) => g.attr('transform', event.transform))
2535
+ );
2536
+
1667
2537
  const simulation = d3.forceSimulation(nodeArray)
1668
- .force('link', d3.forceLink(links).id(d => d.id).distance(50))
1669
- .force('charge', d3.forceManyBody().strength(-100))
2538
+ .force('link', d3.forceLink(links).id(d => d.id).distance(isMaximized ? 150 : 80))
2539
+ .force('charge', d3.forceManyBody().strength(d => isMaximized ? -300 - (d.degree * 40) : -100 - (d.degree * 20)))
1670
2540
  .force('center', d3.forceCenter(width / 2, height / 2))
1671
- .force('collision', d3.forceCollide().radius(22));
2541
+ .force('collision', d3.forceCollide().radius(d => {
2542
+ const textWidth = d.title.length * (isMaximized ? 6 : 4.5);
2543
+ return (isMaximized ? textWidth + 10 : 12) + (d.degree * 2);
2544
+ }));
1672
2545
 
1673
- const link = svg.selectAll('.graph-link')
2546
+ const linkEl = g.selectAll('.graph-link')
1674
2547
  .data(links).enter().append('line')
1675
- .attr('class', 'graph-link');
2548
+ .attr('class', 'graph-link')
2549
+ .attr('stroke-dasharray', d => d.dashed ? '3,3' : null)
2550
+ .style('opacity', d => d.dashed ? 0.3 : 0.6);
1676
2551
 
1677
- const node = svg.selectAll('.graph-node')
2552
+ const node = g.selectAll('.graph-node')
1678
2553
  .data(nodeArray).enter().append('g')
1679
2554
  .attr('class', d => 'graph-node' + (d.active ? ' active' : ''))
1680
- .on('click', (e, d) => loadNote(d.id))
2555
+ .on('click', (e, d) => {
2556
+ e.stopPropagation();
2557
+ loadNote(d.id);
2558
+ if (isMaximized) {
2559
+ document.getElementById('graph-section').classList.remove('maximized');
2560
+ isGraphMaximized = false;
2561
+ renderGraph(currentNote);
2562
+ }
2563
+ })
1681
2564
  .call(d3.drag()
1682
2565
  .on('start', (e, d) => { if (!e.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })
1683
2566
  .on('drag', (e, d) => { d.fx = e.x; d.fy = e.y; })
1684
2567
  .on('end', (e, d) => { if (!e.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; })
1685
2568
  );
1686
2569
 
1687
- node.append('circle').attr('r', d => d.active ? 5 : 3.5);
1688
- node.append('text').text(d => d.title).attr('dx', 10).attr('dy', 3);
2570
+ node.append('circle').attr('r', d => d.active ? 8 : Math.min(12, 3 + (d.degree * 1.2)));
2571
+ node.append('text').text(d => d.title).attr('dx', d => (d.active ? 8 : Math.min(12, 3 + (d.degree * 1.2))) + 6).attr('dy', 3);
2572
+
2573
+ if (isMaximized) {
2574
+ node.append('text')
2575
+ .text(d => {
2576
+ let hash = 0;
2577
+ for (let i = 0; i < d.id.length; i++) hash = Math.imul(31, hash) + d.id.charCodeAt(i) | 0;
2578
+ const v1 = ((Math.abs(hash) % 1000) / 1000).toFixed(3);
2579
+ const v2 = ((Math.abs(hash * 13) % 1000) / 1000).toFixed(3);
2580
+ const v3 = ((Math.abs(hash * 17) % 1000) / 1000).toFixed(3);
2581
+ return `[${v1}, ${v2}, ${v3}, ...]`;
2582
+ })
2583
+ .attr('dx', d => (d.active ? 8 : Math.min(12, 3 + (d.degree * 1.2))) + 6)
2584
+ .attr('dy', 16)
2585
+ .style('fill', 'var(--neon-purple)')
2586
+ .style('font-size', '8px')
2587
+ .style('opacity', '0.8')
2588
+ .style('pointer-events', 'none')
2589
+ .style('font-family', 'var(--font-mono)');
2590
+
2591
+ node.append('text')
2592
+ .text(d => d.id ? d.id.split('/').slice(0, -1).join('/') : '')
2593
+ .attr('dx', d => (d.active ? 8 : Math.min(12, 3 + (d.degree * 1.2))) + 6)
2594
+ .attr('dy', 26)
2595
+ .style('fill', 'var(--text-muted)')
2596
+ .style('font-size', '8px')
2597
+ .style('pointer-events', 'none');
2598
+ }
1689
2599
 
1690
2600
  simulation.on('tick', () => {
1691
- link
2601
+ linkEl
1692
2602
  .attr('x1', d => d.source.x).attr('y1', d => d.source.y)
1693
2603
  .attr('x2', d => d.target.x).attr('y2', d => d.target.y);
1694
- node.attr('transform', d => `translate(${d.x},${d.y})`);
2604
+ node.attr('transform', d => {
2605
+ d.x = Math.max(margin, Math.min(width - margin, d.x));
2606
+ d.y = Math.max(margin, Math.min(height - margin, d.y));
2607
+ return `translate(${d.x},${d.y})`;
2608
+ });
2609
+ });
2610
+ }
2611
+
2612
+ // --- Graph Resize ---
2613
+ let graphHeight = parseInt(localStorage.getItem('kyp-graph-h')) || 200;
2614
+ let isGraphMaximized = false;
2615
+
2616
+ function initGraphResize() {
2617
+ const container = document.getElementById('graph-container');
2618
+ const section = document.getElementById('graph-section');
2619
+ container.style.height = graphHeight + 'px';
2620
+
2621
+ document.getElementById('graph-max').addEventListener('click', () => {
2622
+ isGraphMaximized = !isGraphMaximized;
2623
+ section.classList.toggle('maximized', isGraphMaximized);
2624
+ if (graphVisible && currentNote) renderGraph(currentNote);
2625
+ });
2626
+
2627
+ document.getElementById('graph-shrink').addEventListener('click', () => {
2628
+ graphHeight = Math.max(100, graphHeight - 50);
2629
+ container.style.height = graphHeight + 'px';
2630
+ localStorage.setItem('kyp-graph-h', graphHeight);
2631
+ if (graphVisible && currentNote) renderGraph(currentNote);
2632
+ });
2633
+
2634
+ document.getElementById('graph-grow').addEventListener('click', () => {
2635
+ graphHeight = Math.min(600, graphHeight + 50);
2636
+ container.style.height = graphHeight + 'px';
2637
+ localStorage.setItem('kyp-graph-h', graphHeight);
2638
+ if (graphVisible && currentNote) renderGraph(currentNote);
2639
+ });
2640
+
2641
+ const handle = document.getElementById('graph-resize-handle');
2642
+ handle.addEventListener('mousedown', (e) => {
2643
+ e.preventDefault();
2644
+ const startY = e.clientY;
2645
+ const startH = graphHeight;
2646
+ document.body.style.cursor = 'ns-resize';
2647
+ document.body.style.userSelect = 'none';
2648
+
2649
+ function onMove(ev) {
2650
+ graphHeight = Math.max(100, Math.min(600, startH + (ev.clientY - startY)));
2651
+ container.style.height = graphHeight + 'px';
2652
+ }
2653
+
2654
+ function onUp() {
2655
+ document.body.style.cursor = '';
2656
+ document.body.style.userSelect = '';
2657
+ localStorage.setItem('kyp-graph-h', graphHeight);
2658
+ if (graphVisible && currentNote) renderGraph(currentNote);
2659
+ document.removeEventListener('mousemove', onMove);
2660
+ document.removeEventListener('mouseup', onUp);
2661
+ }
2662
+
2663
+ document.addEventListener('mousemove', onMove);
2664
+ document.addEventListener('mouseup', onUp);
1695
2665
  });
1696
2666
  }
1697
2667
 
@@ -1899,8 +2869,13 @@ function initResize() {
1899
2869
 
1900
2870
  // --- Init ---
1901
2871
  async function init() {
1902
- treeData = await fetchJSON('/api/tree');
1903
- const stats = await fetchJSON('/api/stats');
2872
+ const [rawTreeData, sessionsData, stats] = await Promise.all([
2873
+ fetchJSON('/api/tree'),
2874
+ fetchJSON('/api/sessions'),
2875
+ fetchJSON('/api/stats'),
2876
+ ]);
2877
+
2878
+ treeData = rawTreeData;
1904
2879
 
1905
2880
  function walk(node) {
1906
2881
  if (node.type === 'note') {
@@ -1919,6 +2894,7 @@ async function init() {
1919
2894
  await Promise.all(promises);
1920
2895
 
1921
2896
  renderTree(treeData, document.getElementById('file-tree'));
2897
+ renderSessionTree(sessionsData);
1922
2898
  renderTagCloud(collectAllTags());
1923
2899
 
1924
2900
  document.getElementById('stats-bar').innerHTML = `
@@ -1930,6 +2906,7 @@ async function init() {
1930
2906
  }
1931
2907
 
1932
2908
  initResize();
2909
+ initGraphResize();
1933
2910
  init();
1934
2911
  </script>
1935
2912
  </body>