kyp-mem 0.2.2 → 0.4.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.
@@ -3,42 +3,47 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>KYP-MEM — Know Your Project</title>
6
+ <title>KYP-MEM</title>
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
8
  <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
9
9
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
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: #06060c;
14
- --bg-primary: #0a0a14;
15
- --bg-secondary: #0d0d1a;
16
- --bg-tertiary: #050510;
17
- --bg-hover: #141428;
18
- --bg-active: #1a1a35;
19
- --bg-card: #0f0f1e;
20
-
21
- --neon-cyan: #00fff5;
22
- --neon-green: #39ff14;
23
- --neon-magenta: #ff00e0;
24
- --neon-purple: #bf5af2;
25
- --neon-orange: #ff6b35;
26
- --neon-yellow: #ffd700;
27
- --neon-blue: #4d9fff;
28
- --neon-red: #ff3366;
29
-
30
- --glow-cyan: 0 0 10px #00fff540, 0 0 20px #00fff520;
31
- --glow-green: 0 0 10px #39ff1440, 0 0 20px #39ff1420;
32
- --glow-magenta: 0 0 10px #ff00e040, 0 0 20px #ff00e020;
33
-
34
- --text-primary: #e0e0f0;
35
- --text-secondary: #8888a8;
36
- --text-muted: #4a4a68;
37
- --border: #1a1a30;
38
- --border-glow: #1a1a4080;
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
+ --neon-purple: #a78bfa;
26
+ --neon-orange: #e8935a;
27
+ --neon-yellow: #d4a853;
28
+ --neon-blue: #7da8d4;
29
+ --neon-red: #d47171;
30
+
31
+ --glow-cyan: 0 0 8px #D9775730;
32
+ --glow-green: 0 0 8px #5bb98c30;
33
+ --glow-magenta: 0 0 8px #c47ad730;
34
+
35
+ --text-primary: #d8d5cf;
36
+ --text-secondary: #807b73;
37
+ --text-muted: #44413a;
38
+ --border: #1c1a17;
39
+ --border-subtle: #16150f;
39
40
 
40
41
  --font: 'Inter', -apple-system, sans-serif;
41
42
  --font-mono: 'JetBrains Mono', monospace;
43
+
44
+ --radius-sm: 4px;
45
+ --radius: 6px;
46
+ --radius-lg: 10px;
42
47
  }
43
48
 
44
49
  * { margin: 0; padding: 0; box-sizing: border-box; }
@@ -49,91 +54,61 @@ body {
49
54
  color: var(--text-primary);
50
55
  height: 100vh;
51
56
  overflow: hidden;
57
+ -webkit-font-smoothing: antialiased;
52
58
  }
53
59
 
54
- /* Subtle grid background */
55
60
  body::before {
56
61
  content: '';
57
62
  position: fixed;
58
63
  inset: 0;
59
64
  background-image:
60
- linear-gradient(rgba(0,255,245,0.03) 1px, transparent 1px),
61
- linear-gradient(90deg, rgba(0,255,245,0.03) 1px, transparent 1px);
62
- background-size: 40px 40px;
65
+ linear-gradient(rgba(217,119,87,0.015) 1px, transparent 1px),
66
+ linear-gradient(90deg, rgba(217,119,87,0.015) 1px, transparent 1px);
67
+ background-size: 48px 48px;
63
68
  pointer-events: none;
64
69
  z-index: 0;
65
70
  }
66
71
 
67
72
  .layout {
68
73
  display: grid;
69
- grid-template-columns: var(--sidebar-w, 260px) 5px 1fr 5px var(--right-w, 280px);
70
- grid-template-rows: 52px 1fr;
74
+ grid-template-columns: var(--sidebar-w, 256px) 1px 1fr 1px var(--right-w, 272px);
75
+ grid-template-rows: 48px 1fr;
71
76
  height: 100vh;
72
77
  position: relative;
73
78
  z-index: 1;
74
79
  }
75
80
 
76
81
  .layout.no-right-panel {
77
- grid-template-columns: var(--sidebar-w, 260px) 5px 1fr;
82
+ grid-template-columns: var(--sidebar-w, 256px) 1px 1fr;
78
83
  }
79
84
  .layout.no-right-panel .right-panel,
80
85
  .layout.no-right-panel #resize-right { display: none; }
81
86
 
82
87
  /* ============ RESIZE HANDLES ============ */
83
88
  .resize-handle {
84
- background: transparent;
89
+ background: var(--border-subtle);
85
90
  cursor: col-resize;
86
91
  position: relative;
87
92
  z-index: 10;
88
- transition: background 0.2s;
93
+ transition: background 0.3s;
89
94
  }
90
95
 
91
96
  .resize-handle::before {
92
97
  content: '';
93
98
  position: absolute;
94
- inset: 0 -3px;
99
+ inset: 0 -4px;
95
100
  z-index: 1;
96
101
  }
97
102
 
98
- .resize-handle::after {
99
- content: '';
100
- position: absolute;
101
- top: 50%;
102
- left: 50%;
103
- transform: translate(-50%, -50%);
104
- width: 3px;
105
- height: 32px;
106
- border-radius: 2px;
107
- background: var(--text-muted);
108
- opacity: 0;
109
- transition: opacity 0.2s, background 0.2s, box-shadow 0.2s;
110
- }
111
-
112
- .resize-handle:hover::after,
113
- .resize-handle.dragging::after {
114
- opacity: 1;
115
- background: var(--neon-cyan);
116
- box-shadow: 0 0 8px rgba(0, 255, 245, 0.4);
117
- }
118
-
119
103
  .resize-handle:hover,
120
104
  .resize-handle.dragging {
121
- background: rgba(0, 255, 245, 0.08);
122
- }
123
-
124
- body.resizing {
125
- cursor: col-resize !important;
126
- user-select: none !important;
127
- }
128
-
129
- body.resizing * {
130
- cursor: col-resize !important;
131
- pointer-events: none !important;
105
+ background: var(--neon-cyan);
106
+ box-shadow: 0 0 12px rgba(217,119,87,0.2);
132
107
  }
133
108
 
134
- body.resizing .resize-handle {
135
- pointer-events: auto !important;
136
- }
109
+ body.resizing { cursor: col-resize !important; user-select: none !important; }
110
+ body.resizing * { cursor: col-resize !important; pointer-events: none !important; }
111
+ body.resizing .resize-handle { pointer-events: auto !important; }
137
112
 
138
113
  /* ============ HEADER ============ */
139
114
  .header {
@@ -150,21 +125,20 @@ body.resizing .resize-handle {
150
125
  .header::after {
151
126
  content: '';
152
127
  position: absolute;
153
- bottom: -1px;
128
+ bottom: 0;
154
129
  left: 0;
155
130
  right: 0;
156
131
  height: 1px;
157
- background: linear-gradient(90deg, transparent, var(--neon-cyan), var(--neon-magenta), var(--neon-purple), transparent);
158
- opacity: 0.4;
132
+ background: linear-gradient(90deg, transparent, var(--neon-cyan), var(--neon-magenta), transparent);
133
+ opacity: 0.2;
159
134
  }
160
135
 
161
136
  .logo {
162
137
  font-family: var(--font-mono);
163
138
  font-weight: 700;
164
- font-size: 15px;
139
+ font-size: 13px;
165
140
  color: var(--neon-cyan);
166
- text-shadow: var(--glow-cyan);
167
- letter-spacing: 1px;
141
+ letter-spacing: 2px;
168
142
  flex-shrink: 0;
169
143
  }
170
144
 
@@ -172,66 +146,65 @@ body.resizing .resize-handle {
172
146
  font-size: 10px;
173
147
  font-weight: 400;
174
148
  color: var(--text-muted);
175
- text-shadow: none;
176
149
  letter-spacing: 0.5px;
177
- margin-left: 8px;
150
+ margin-left: 10px;
178
151
  }
179
152
 
180
153
  .breadcrumb {
181
154
  font-family: var(--font-mono);
182
- font-size: 12px;
155
+ font-size: 11px;
183
156
  color: var(--text-secondary);
184
157
  overflow: hidden;
185
158
  text-overflow: ellipsis;
186
159
  white-space: nowrap;
187
160
  }
188
161
 
189
- .breadcrumb .sep { color: var(--text-muted); margin: 0 6px; }
190
- .breadcrumb .current { color: var(--neon-cyan); font-weight: 600; }
162
+ .breadcrumb .sep { color: var(--text-muted); margin: 0 5px; }
163
+ .breadcrumb .current { color: var(--neon-cyan); font-weight: 500; }
191
164
 
192
165
  .header-actions {
193
166
  margin-left: auto;
194
167
  display: flex;
195
168
  align-items: center;
196
- gap: 10px;
169
+ gap: 8px;
197
170
  }
198
171
 
199
- .toggle-btn {
200
- background: var(--bg-hover);
172
+ .header-btn {
173
+ background: transparent;
201
174
  border: 1px solid var(--border);
202
- color: var(--text-secondary);
175
+ color: var(--text-muted);
203
176
  font-family: var(--font-mono);
204
- font-size: 11px;
177
+ font-size: 10px;
205
178
  padding: 5px 10px;
206
- border-radius: 6px;
179
+ border-radius: var(--radius);
207
180
  cursor: pointer;
208
181
  transition: all 0.2s;
209
182
  display: flex;
210
183
  align-items: center;
211
184
  gap: 5px;
185
+ letter-spacing: 0.5px;
212
186
  }
213
187
 
214
- .toggle-btn:hover {
215
- border-color: var(--neon-cyan);
216
- color: var(--neon-cyan);
188
+ .header-btn:hover {
189
+ border-color: var(--text-muted);
190
+ color: var(--text-secondary);
217
191
  }
218
192
 
219
- .toggle-btn.active {
220
- border-color: var(--neon-cyan);
193
+ .header-btn.active {
194
+ border-color: rgba(217,119,87,0.3);
221
195
  color: var(--neon-cyan);
222
- background: rgba(0, 255, 245, 0.08);
223
- box-shadow: 0 0 8px rgba(0, 255, 245, 0.15);
196
+ background: rgba(217,119,87,0.05);
224
197
  }
225
198
 
226
- .toggle-btn .dot {
227
- width: 6px;
228
- height: 6px;
199
+ .header-btn .dot {
200
+ width: 5px;
201
+ height: 5px;
229
202
  border-radius: 50%;
230
203
  background: var(--text-muted);
231
204
  transition: all 0.2s;
232
205
  }
233
206
 
234
- .toggle-btn.active .dot {
207
+ .header-btn.active .dot {
235
208
  background: var(--neon-green);
236
209
  box-shadow: 0 0 6px var(--neon-green);
237
210
  }
@@ -241,33 +214,33 @@ body.resizing .resize-handle {
241
214
  }
242
215
 
243
216
  .search-box input {
244
- background: var(--bg-secondary);
217
+ background: var(--bg-surface);
245
218
  border: 1px solid var(--border);
246
219
  color: var(--text-primary);
247
220
  font-family: var(--font-mono);
248
221
  padding: 6px 12px 6px 28px;
249
- border-radius: 6px;
250
- font-size: 12px;
251
- width: 220px;
222
+ border-radius: var(--radius);
223
+ font-size: 11px;
224
+ width: 200px;
252
225
  outline: none;
253
- transition: all 0.2s;
226
+ transition: all 0.25s;
254
227
  }
255
228
 
256
229
  .search-box input::placeholder { color: var(--text-muted); }
257
230
 
258
231
  .search-box input:focus {
259
- border-color: var(--neon-cyan);
260
- box-shadow: 0 0 12px rgba(0, 255, 245, 0.15);
261
- width: 280px;
232
+ border-color: rgba(217,119,87,0.3);
233
+ box-shadow: 0 0 16px rgba(217,119,87,0.08);
234
+ width: 260px;
262
235
  }
263
236
 
264
237
  .search-box .search-icon {
265
238
  position: absolute;
266
- left: 9px;
239
+ left: 10px;
267
240
  top: 50%;
268
241
  transform: translateY(-50%);
269
242
  color: var(--text-muted);
270
- font-size: 12px;
243
+ font-size: 11px;
271
244
  }
272
245
 
273
246
  .search-box .search-hint {
@@ -276,36 +249,36 @@ body.resizing .resize-handle {
276
249
  top: 50%;
277
250
  transform: translateY(-50%);
278
251
  font-family: var(--font-mono);
279
- font-size: 10px;
252
+ font-size: 9px;
280
253
  color: var(--text-muted);
281
254
  background: var(--bg-hover);
282
255
  padding: 1px 5px;
283
256
  border-radius: 3px;
284
257
  border: 1px solid var(--border);
258
+ letter-spacing: 0.5px;
285
259
  }
286
260
 
287
261
  .search-results {
288
262
  position: absolute;
289
- top: calc(100% + 6px);
263
+ top: calc(100% + 8px);
290
264
  right: 0;
291
- width: 420px;
292
- max-height: 420px;
265
+ width: 400px;
266
+ max-height: 380px;
293
267
  overflow-y: auto;
294
268
  background: var(--bg-card);
295
269
  border: 1px solid var(--border);
296
- border-radius: 10px;
270
+ border-radius: var(--radius-lg);
297
271
  z-index: 100;
298
272
  display: none;
299
- backdrop-filter: blur(12px);
300
- box-shadow: 0 8px 32px rgba(0,0,0,0.6), 0 0 1px var(--neon-cyan);
273
+ box-shadow: 0 12px 40px rgba(0,0,0,0.5);
301
274
  }
302
275
 
303
276
  .search-results.active { display: block; }
304
277
 
305
278
  .search-result {
306
- padding: 12px 16px;
279
+ padding: 10px 14px;
307
280
  cursor: pointer;
308
- border-bottom: 1px solid var(--border);
281
+ border-bottom: 1px solid var(--border-subtle);
309
282
  transition: background 0.15s;
310
283
  }
311
284
 
@@ -313,8 +286,8 @@ body.resizing .resize-handle {
313
286
  .search-result:hover { background: var(--bg-hover); }
314
287
 
315
288
  .search-result .sr-title {
316
- font-size: 13px;
317
- font-weight: 600;
289
+ font-size: 12px;
290
+ font-weight: 500;
318
291
  color: var(--neon-cyan);
319
292
  }
320
293
 
@@ -322,20 +295,102 @@ body.resizing .resize-handle {
322
295
  font-family: var(--font-mono);
323
296
  font-size: 10px;
324
297
  color: var(--text-muted);
325
- margin-top: 3px;
298
+ margin-top: 2px;
326
299
  }
327
300
 
328
301
  .search-result .sr-snippet {
329
- font-size: 12px;
302
+ font-size: 11px;
330
303
  color: var(--text-secondary);
331
304
  margin-top: 4px;
332
- line-height: 1.4;
305
+ line-height: 1.5;
306
+ }
307
+
308
+ /* ============ QUICK SWITCHER ============ */
309
+ .quick-switcher-overlay {
310
+ position: fixed;
311
+ inset: 0;
312
+ background: rgba(6,6,12,0.7);
313
+ z-index: 200;
314
+ display: none;
315
+ align-items: flex-start;
316
+ justify-content: center;
317
+ padding-top: 15vh;
318
+ backdrop-filter: blur(4px);
319
+ }
320
+
321
+ .quick-switcher-overlay.active { display: flex; }
322
+
323
+ .quick-switcher {
324
+ width: 480px;
325
+ background: var(--bg-card);
326
+ border: 1px solid var(--border);
327
+ border-radius: var(--radius-lg);
328
+ box-shadow: 0 20px 60px rgba(0,0,0,0.6), 0 0 1px var(--neon-cyan);
329
+ overflow: hidden;
330
+ }
331
+
332
+ .quick-switcher input {
333
+ width: 100%;
334
+ background: transparent;
335
+ border: none;
336
+ border-bottom: 1px solid var(--border);
337
+ color: var(--text-primary);
338
+ font-family: var(--font-mono);
339
+ font-size: 14px;
340
+ padding: 16px 20px;
341
+ outline: none;
342
+ }
343
+
344
+ .quick-switcher input::placeholder { color: var(--text-muted); }
345
+
346
+ .quick-switcher-results {
347
+ max-height: 320px;
348
+ overflow-y: auto;
349
+ }
350
+
351
+ .qs-item {
352
+ padding: 10px 20px;
353
+ cursor: pointer;
354
+ display: flex;
355
+ align-items: center;
356
+ gap: 10px;
357
+ transition: background 0.1s;
358
+ }
359
+
360
+ .qs-item:hover, .qs-item.selected { background: var(--bg-hover); }
361
+
362
+ .qs-item .qs-icon {
363
+ color: var(--neon-purple);
364
+ font-size: 12px;
365
+ flex-shrink: 0;
366
+ opacity: 0.7;
367
+ }
368
+
369
+ .qs-item .qs-name {
370
+ font-size: 13px;
371
+ color: var(--text-primary);
372
+ font-weight: 500;
373
+ }
374
+
375
+ .qs-item .qs-path {
376
+ font-family: var(--font-mono);
377
+ font-size: 10px;
378
+ color: var(--text-muted);
379
+ margin-left: auto;
380
+ }
381
+
382
+ .qs-item.selected .qs-name { color: var(--neon-cyan); }
383
+
384
+ .qs-empty {
385
+ padding: 20px;
386
+ text-align: center;
387
+ color: var(--text-muted);
388
+ font-size: 12px;
333
389
  }
334
390
 
335
391
  /* ============ SIDEBAR ============ */
336
392
  .sidebar {
337
393
  background: var(--bg-secondary);
338
- border-right: 1px solid var(--border);
339
394
  display: flex;
340
395
  flex-direction: column;
341
396
  overflow: hidden;
@@ -344,38 +399,42 @@ body.resizing .resize-handle {
344
399
  .sidebar-scroll {
345
400
  flex: 1;
346
401
  overflow-y: auto;
347
- padding: 8px 0;
402
+ padding: 6px 0;
348
403
  }
349
404
 
350
- .sidebar-section { padding: 0 10px; }
405
+ .sidebar-section { padding: 0 8px; }
351
406
 
352
- .sidebar-section-title {
407
+ .section-label {
353
408
  font-family: var(--font-mono);
354
- font-size: 10px;
409
+ font-size: 9px;
355
410
  font-weight: 600;
356
411
  text-transform: uppercase;
357
412
  letter-spacing: 1.5px;
358
413
  color: var(--text-muted);
359
- padding: 10px 8px 8px;
414
+ padding: 12px 10px 6px;
415
+ display: flex;
416
+ align-items: center;
417
+ justify-content: space-between;
360
418
  }
361
419
 
362
420
  .tree-item {
363
421
  display: flex;
364
422
  align-items: center;
365
- padding: 5px 8px;
366
- border-radius: 5px;
423
+ padding: 4px 10px;
424
+ border-radius: var(--radius-sm);
367
425
  cursor: pointer;
368
- font-size: 13px;
426
+ font-size: 12px;
369
427
  gap: 6px;
370
428
  user-select: none;
371
- transition: all 0.12s;
429
+ transition: background 0.1s;
372
430
  position: relative;
431
+ margin: 1px 0;
373
432
  }
374
433
 
375
434
  .tree-item:hover { background: var(--bg-hover); }
376
435
 
377
436
  .tree-item.active {
378
- background: rgba(0, 255, 245, 0.06);
437
+ background: rgba(217,119,87,0.05);
379
438
  }
380
439
 
381
440
  .tree-item.active::before {
@@ -387,12 +446,11 @@ body.resizing .resize-handle {
387
446
  width: 2px;
388
447
  background: var(--neon-cyan);
389
448
  border-radius: 1px;
390
- box-shadow: 0 0 6px var(--neon-cyan);
391
449
  }
392
450
 
393
451
  .tree-item .arrow {
394
- width: 14px;
395
- font-size: 9px;
452
+ width: 12px;
453
+ font-size: 8px;
396
454
  color: var(--text-muted);
397
455
  text-align: center;
398
456
  flex-shrink: 0;
@@ -403,7 +461,8 @@ body.resizing .resize-handle {
403
461
 
404
462
  .tree-item .icon {
405
463
  flex-shrink: 0;
406
- font-size: 13px;
464
+ font-size: 11px;
465
+ opacity: 0.6;
407
466
  }
408
467
 
409
468
  .tree-item .folder-icon { color: var(--neon-yellow); }
@@ -413,146 +472,254 @@ body.resizing .resize-handle {
413
472
  overflow: hidden;
414
473
  text-overflow: ellipsis;
415
474
  white-space: nowrap;
416
- font-size: 13px;
475
+ font-size: 12px;
417
476
  color: var(--text-primary);
418
477
  }
419
478
 
420
479
  .tree-item.active .name { color: var(--neon-cyan); }
421
480
 
422
- .tree-children {
423
- padding-left: 14px;
424
- overflow: hidden;
481
+ .tree-children { padding-left: 12px; overflow: hidden; }
482
+ .tree-children.collapsed { display: none; }
483
+
484
+ /* ============ TAG FILTER ============ */
485
+ .tag-filter-section { padding: 0 8px 8px; }
486
+
487
+ .tag-filter-header {
488
+ font-family: var(--font-mono);
489
+ font-size: 9px;
490
+ font-weight: 600;
491
+ text-transform: uppercase;
492
+ letter-spacing: 1.5px;
493
+ color: var(--text-muted);
494
+ padding: 12px 10px 6px;
495
+ display: flex;
496
+ align-items: center;
497
+ justify-content: space-between;
498
+ cursor: pointer;
499
+ user-select: none;
425
500
  }
426
501
 
427
- .tree-children.collapsed { display: none; }
502
+ .tag-filter-header .arrow {
503
+ font-size: 8px;
504
+ transition: transform 0.15s;
505
+ }
506
+
507
+ .tag-filter-header .arrow.open { transform: rotate(90deg); }
508
+
509
+ .tag-filter-body { padding: 0 4px; }
510
+ .tag-filter-body.collapsed { display: none; }
511
+
512
+ .tag-filter-active {
513
+ display: flex;
514
+ flex-wrap: wrap;
515
+ gap: 4px;
516
+ margin-bottom: 6px;
517
+ }
518
+
519
+ .tag-filter-active:empty { display: none; }
520
+
521
+ .active-tag {
522
+ font-family: var(--font-mono);
523
+ font-size: 9px;
524
+ padding: 2px 7px;
525
+ border-radius: var(--radius-sm);
526
+ background: rgba(217,119,87,0.08);
527
+ color: var(--neon-cyan);
528
+ border: 1px solid rgba(217,119,87,0.2);
529
+ cursor: pointer;
530
+ display: flex;
531
+ align-items: center;
532
+ gap: 3px;
533
+ transition: all 0.15s;
534
+ }
535
+
536
+ .active-tag:hover { background: rgba(217,119,87,0.15); }
537
+ .active-tag .remove { font-size: 11px; opacity: 0.5; }
538
+ .active-tag .remove:hover { opacity: 1; }
539
+
540
+ .tag-cloud {
541
+ display: flex;
542
+ flex-wrap: wrap;
543
+ gap: 3px;
544
+ max-height: 140px;
545
+ overflow-y: auto;
546
+ padding: 2px 0;
547
+ }
548
+
549
+ .tag-chip {
550
+ font-family: var(--font-mono);
551
+ font-size: 9px;
552
+ font-weight: 500;
553
+ padding: 2px 7px;
554
+ border-radius: var(--radius-sm);
555
+ background: rgba(168,139,250,0.06);
556
+ color: var(--neon-purple);
557
+ border: 1px solid rgba(168,139,250,0.1);
558
+ cursor: pointer;
559
+ transition: all 0.15s;
560
+ user-select: none;
561
+ }
562
+
563
+ .tag-chip:hover {
564
+ background: rgba(168,139,250,0.12);
565
+ border-color: rgba(168,139,250,0.25);
566
+ }
567
+
568
+ .tag-chip.selected {
569
+ background: rgba(217,119,87,0.08);
570
+ color: var(--neon-cyan);
571
+ border-color: rgba(217,119,87,0.2);
572
+ }
573
+
574
+ .tag-chip .tag-count {
575
+ font-size: 8px;
576
+ opacity: 0.4;
577
+ margin-left: 2px;
578
+ }
579
+
580
+ .filter-info {
581
+ font-family: var(--font-mono);
582
+ font-size: 9px;
583
+ color: var(--text-muted);
584
+ padding: 2px 10px 4px;
585
+ display: flex;
586
+ align-items: center;
587
+ justify-content: space-between;
588
+ }
589
+
590
+ .filter-info .clear-btn {
591
+ color: var(--neon-cyan);
592
+ cursor: pointer;
593
+ font-size: 9px;
594
+ }
595
+
596
+ .filter-info .clear-btn:hover { text-decoration: underline; }
428
597
 
429
598
  .stats-bar {
430
- padding: 10px 14px;
431
- border-top: 1px solid var(--border);
599
+ padding: 8px 14px;
600
+ border-top: 1px solid var(--border-subtle);
432
601
  font-family: var(--font-mono);
433
- font-size: 10px;
602
+ font-size: 9px;
434
603
  color: var(--text-muted);
435
604
  display: flex;
436
- gap: 14px;
605
+ gap: 12px;
437
606
  flex-wrap: wrap;
438
607
  }
439
608
 
440
- .stat-val { color: var(--neon-green); font-weight: 600; }
609
+ .stat-val { color: var(--neon-green); font-weight: 500; }
441
610
 
442
611
  /* ============ CONTENT ============ */
443
612
  .content {
444
613
  overflow-y: auto;
445
- padding: 36px 52px;
614
+ padding: 40px 56px;
446
615
  background: var(--bg-primary);
447
616
  }
448
617
 
449
618
  .note-properties {
450
619
  display: flex;
451
620
  flex-wrap: wrap;
452
- gap: 6px;
453
- margin-bottom: 24px;
454
- padding-bottom: 18px;
455
- border-bottom: 1px solid var(--border);
621
+ gap: 5px;
622
+ margin-bottom: 28px;
623
+ padding-bottom: 20px;
624
+ border-bottom: 1px solid var(--border-subtle);
456
625
  }
457
626
 
458
627
  .tag {
459
628
  font-family: var(--font-mono);
460
- font-size: 11px;
629
+ font-size: 10px;
461
630
  font-weight: 500;
462
- padding: 3px 10px;
463
- border-radius: 4px;
464
- background: rgba(255, 0, 224, 0.1);
465
- color: var(--neon-magenta);
466
- border: 1px solid rgba(255, 0, 224, 0.2);
631
+ padding: 2px 9px;
632
+ border-radius: var(--radius-sm);
633
+ background: rgba(168,139,250,0.08);
634
+ color: var(--neon-purple);
635
+ border: 1px solid rgba(168,139,250,0.12);
636
+ transition: all 0.15s;
637
+ }
638
+
639
+ .tag.clickable-tag:hover {
640
+ background: rgba(217,119,87,0.08);
641
+ color: var(--neon-cyan);
642
+ border-color: rgba(217,119,87,0.2);
643
+ cursor: pointer;
467
644
  }
468
645
 
469
646
  .prop-item {
470
647
  font-family: var(--font-mono);
471
- font-size: 11px;
472
- padding: 3px 10px;
473
- border-radius: 4px;
648
+ font-size: 10px;
649
+ padding: 2px 9px;
650
+ border-radius: var(--radius-sm);
474
651
  background: var(--bg-card);
475
652
  color: var(--text-secondary);
476
- border: 1px solid var(--border);
653
+ border: 1px solid var(--border-subtle);
477
654
  }
478
655
 
479
656
  .prop-item .prop-key { color: var(--text-muted); }
480
657
 
481
658
  /* ============ MARKDOWN ============ */
482
659
  .md-body h1 {
483
- font-size: 28px;
660
+ font-size: 26px;
484
661
  font-weight: 700;
485
- margin: 0 0 20px;
486
- color: var(--neon-cyan);
487
- text-shadow: 0 0 20px rgba(0,255,245,0.15);
662
+ margin: 0 0 24px;
663
+ color: var(--text-primary);
664
+ letter-spacing: -0.3px;
488
665
  }
489
666
 
490
667
  .md-body h2 {
491
- font-size: 20px;
668
+ font-size: 18px;
492
669
  font-weight: 600;
493
- margin: 32px 0 12px;
494
- color: var(--neon-yellow);
670
+ margin: 36px 0 12px;
671
+ color: var(--text-primary);
495
672
  padding-bottom: 8px;
496
- border-bottom: 1px solid var(--border);
673
+ border-bottom: 1px solid var(--border-subtle);
497
674
  }
498
675
 
499
676
  .md-body h3 {
500
- font-size: 16px;
677
+ font-size: 15px;
501
678
  font-weight: 600;
502
- margin: 24px 0 8px;
679
+ margin: 28px 0 8px;
503
680
  color: var(--neon-purple);
504
681
  }
505
682
 
506
683
  .md-body p {
507
- line-height: 1.75;
508
- margin: 10px 0;
684
+ line-height: 1.8;
685
+ margin: 8px 0;
509
686
  color: var(--text-secondary);
687
+ font-size: 13.5px;
510
688
  }
511
689
 
512
690
  .md-body ul, .md-body ol { padding-left: 24px; margin: 8px 0; }
513
691
 
514
692
  .md-body li {
515
- line-height: 1.75;
693
+ line-height: 1.8;
516
694
  color: var(--text-secondary);
517
- margin: 3px 0;
695
+ margin: 2px 0;
696
+ font-size: 13.5px;
518
697
  }
519
698
 
520
- .md-body li::marker { color: var(--neon-cyan); }
699
+ .md-body li::marker { color: var(--text-muted); }
521
700
 
522
701
  .md-body a { color: var(--neon-blue); text-decoration: none; }
523
- .md-body a:hover { text-decoration: underline; text-shadow: 0 0 8px rgba(77,159,255,0.3); }
702
+ .md-body a:hover { text-decoration: underline; }
524
703
 
525
704
  .md-body strong { color: var(--text-primary); font-weight: 600; }
526
705
 
527
706
  .md-body code {
528
707
  background: var(--bg-card);
529
- padding: 2px 7px;
530
- border-radius: 4px;
708
+ padding: 1.5px 6px;
709
+ border-radius: 3px;
531
710
  font-family: var(--font-mono);
532
711
  font-size: 12px;
533
712
  color: var(--neon-orange);
534
- border: 1px solid var(--border);
713
+ border: 1px solid var(--border-subtle);
535
714
  }
536
715
 
537
716
  .md-body pre {
538
717
  background: var(--bg-tertiary);
539
718
  border: 1px solid var(--border);
540
- border-radius: 8px;
541
- padding: 18px;
719
+ border-radius: var(--radius);
720
+ padding: 16px 18px;
542
721
  overflow-x: auto;
543
722
  margin: 16px 0;
544
- position: relative;
545
- }
546
-
547
- .md-body pre::before {
548
- content: '';
549
- position: absolute;
550
- top: 0;
551
- left: 0;
552
- right: 0;
553
- height: 1px;
554
- background: linear-gradient(90deg, var(--neon-cyan), var(--neon-magenta), transparent);
555
- opacity: 0.3;
556
723
  }
557
724
 
558
725
  .md-body pre code {
@@ -560,65 +727,63 @@ body.resizing .resize-handle {
560
727
  padding: 0;
561
728
  border: none;
562
729
  color: var(--text-primary);
563
- font-size: 13px;
564
- line-height: 1.6;
730
+ font-size: 12px;
731
+ line-height: 1.7;
565
732
  }
566
733
 
567
734
  .md-body table {
568
735
  width: 100%;
569
736
  border-collapse: collapse;
570
737
  margin: 16px 0;
571
- font-size: 13px;
738
+ font-size: 12.5px;
572
739
  }
573
740
 
574
741
  .md-body th {
575
742
  text-align: left;
576
- padding: 10px 14px;
577
- border-bottom: 2px solid var(--neon-cyan);
578
- color: var(--neon-cyan);
743
+ padding: 8px 12px;
744
+ border-bottom: 1px solid var(--border);
745
+ color: var(--text-secondary);
579
746
  font-family: var(--font-mono);
580
747
  font-weight: 600;
581
- font-size: 12px;
748
+ font-size: 10px;
582
749
  text-transform: uppercase;
583
750
  letter-spacing: 0.5px;
584
751
  }
585
752
 
586
753
  .md-body td {
587
- padding: 10px 14px;
588
- border-bottom: 1px solid var(--border);
754
+ padding: 8px 12px;
755
+ border-bottom: 1px solid var(--border-subtle);
589
756
  color: var(--text-secondary);
590
757
  }
591
758
 
592
759
  .md-body tr:hover td { background: var(--bg-hover); }
593
760
 
594
761
  .md-body blockquote {
595
- border-left: 3px solid var(--neon-magenta);
762
+ border-left: 2px solid var(--neon-purple);
596
763
  padding-left: 16px;
597
764
  margin: 16px 0;
598
765
  color: var(--text-muted);
599
- box-shadow: -3px 0 8px rgba(255,0,224,0.1);
600
766
  }
601
767
 
602
768
  .md-body hr {
603
769
  border: none;
604
770
  height: 1px;
605
- background: linear-gradient(90deg, transparent, var(--border), transparent);
606
- margin: 24px 0;
771
+ background: var(--border);
772
+ margin: 28px 0;
607
773
  }
608
774
 
609
775
  .wikilink {
610
776
  color: var(--neon-purple);
611
777
  cursor: pointer;
612
778
  text-decoration: none;
613
- border-bottom: 1px dashed rgba(191, 90, 242, 0.4);
779
+ border-bottom: 1px dashed rgba(168,139,250,0.3);
614
780
  transition: all 0.15s;
615
781
  font-weight: 500;
616
782
  }
617
783
 
618
784
  .wikilink:hover {
619
- color: var(--neon-magenta);
620
- text-shadow: 0 0 8px rgba(255,0,224,0.3);
621
- border-bottom-color: var(--neon-magenta);
785
+ color: var(--neon-cyan);
786
+ border-bottom-color: rgba(217,119,87,0.4);
622
787
  }
623
788
 
624
789
  /* ============ EMPTY STATE ============ */
@@ -628,35 +793,35 @@ body.resizing .resize-handle {
628
793
  align-items: center;
629
794
  justify-content: center;
630
795
  height: 100%;
631
- gap: 16px;
796
+ gap: 12px;
632
797
  }
633
798
 
634
799
  .empty-state .es-logo {
635
800
  font-family: var(--font-mono);
636
- font-size: 32px;
801
+ font-size: 28px;
637
802
  font-weight: 700;
638
803
  color: var(--neon-cyan);
639
- text-shadow: var(--glow-cyan), 0 0 40px rgba(0,255,245,0.1);
640
- letter-spacing: 3px;
804
+ letter-spacing: 4px;
805
+ opacity: 0.6;
641
806
  }
642
807
 
643
808
  .empty-state .es-tagline {
644
809
  font-family: var(--font-mono);
645
- font-size: 12px;
810
+ font-size: 11px;
646
811
  color: var(--text-muted);
647
- letter-spacing: 2px;
812
+ letter-spacing: 3px;
648
813
  text-transform: uppercase;
649
814
  }
650
815
 
651
816
  .empty-state .es-hint {
652
- font-size: 13px;
817
+ font-size: 12px;
653
818
  color: var(--text-muted);
654
- margin-top: 20px;
819
+ margin-top: 24px;
655
820
  }
656
821
 
657
822
  .empty-state .es-hint kbd {
658
823
  font-family: var(--font-mono);
659
- font-size: 11px;
824
+ font-size: 10px;
660
825
  background: var(--bg-card);
661
826
  border: 1px solid var(--border);
662
827
  padding: 2px 6px;
@@ -667,21 +832,20 @@ body.resizing .resize-handle {
667
832
  /* ============ RIGHT PANEL ============ */
668
833
  .right-panel {
669
834
  background: var(--bg-secondary);
670
- border-left: 1px solid var(--border);
671
835
  overflow-y: auto;
672
- padding: 16px;
836
+ padding: 12px;
673
837
  }
674
838
 
675
- .rp-section { margin-bottom: 24px; }
839
+ .rp-section { margin-bottom: 20px; }
676
840
 
677
841
  .rp-section-title {
678
842
  font-family: var(--font-mono);
679
- font-size: 10px;
843
+ font-size: 9px;
680
844
  font-weight: 600;
681
845
  text-transform: uppercase;
682
846
  letter-spacing: 1.5px;
683
847
  color: var(--text-muted);
684
- margin-bottom: 10px;
848
+ margin-bottom: 8px;
685
849
  display: flex;
686
850
  align-items: center;
687
851
  gap: 6px;
@@ -691,32 +855,30 @@ body.resizing .resize-handle {
691
855
  background: var(--bg-hover);
692
856
  color: var(--text-muted);
693
857
  font-size: 9px;
694
- padding: 1px 5px;
858
+ padding: 0px 5px;
695
859
  border-radius: 8px;
696
860
  }
697
861
 
698
862
  .rp-item {
699
863
  display: flex;
700
864
  align-items: center;
701
- padding: 6px 8px;
702
- border-radius: 5px;
865
+ padding: 5px 8px;
866
+ border-radius: var(--radius-sm);
703
867
  cursor: pointer;
704
- font-size: 12px;
868
+ font-size: 11px;
705
869
  gap: 8px;
706
- margin-bottom: 2px;
707
- transition: all 0.12s;
870
+ margin-bottom: 1px;
871
+ transition: background 0.1s;
708
872
  }
709
873
 
710
- .rp-item:hover {
711
- background: var(--bg-hover);
712
- }
874
+ .rp-item:hover { background: var(--bg-hover); }
713
875
 
714
876
  .rp-item .rp-score {
715
877
  font-family: var(--font-mono);
716
- font-size: 11px;
878
+ font-size: 10px;
717
879
  color: var(--neon-green);
718
- min-width: 34px;
719
- text-shadow: 0 0 6px rgba(57,255,20,0.2);
880
+ min-width: 30px;
881
+ opacity: 0.8;
720
882
  }
721
883
 
722
884
  .rp-item .rp-title {
@@ -727,89 +889,404 @@ body.resizing .resize-handle {
727
889
  font-weight: 500;
728
890
  }
729
891
 
730
- .rp-item .rp-backlink-title {
731
- color: var(--neon-cyan);
892
+ .rp-item .rp-backlink-title { color: var(--neon-cyan); }
893
+
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;
903
+ overflow: hidden;
904
+ text-overflow: ellipsis;
905
+ white-space: nowrap;
906
+ margin-bottom: 1px;
732
907
  }
733
908
 
734
- /* ============ GRAPH ============ */
735
- #graph-section {
736
- margin-bottom: 16px;
909
+ .outline-item:hover {
910
+ background: var(--bg-hover);
911
+ color: var(--text-primary);
737
912
  }
738
913
 
914
+ .outline-item.h2 { padding-left: 8px; }
915
+ .outline-item.h3 { padding-left: 20px; font-size: 10px; color: var(--text-muted); }
916
+
917
+ /* ============ GRAPH ============ */
918
+ #graph-section { margin-bottom: 12px; }
739
919
  #graph-section.hidden { display: none; }
740
920
 
741
921
  #graph-container {
742
922
  width: 100%;
743
- height: 220px;
744
- border: 1px solid var(--border);
745
- border-radius: 8px;
923
+ height: 200px;
924
+ border: 1px solid var(--border-subtle);
925
+ border-radius: var(--radius);
746
926
  overflow: hidden;
747
927
  background: var(--bg-tertiary);
748
- position: relative;
749
- }
750
-
751
- #graph-container::before {
752
- content: '';
753
- position: absolute;
754
- inset: 0;
755
- background-image:
756
- radial-gradient(rgba(0,255,245,0.03) 1px, transparent 1px);
757
- background-size: 16px 16px;
758
- pointer-events: none;
759
928
  }
760
929
 
761
- #graph-container svg { width: 100%; height: 100%; position: relative; z-index: 1; }
930
+ #graph-container svg { width: 100%; height: 100%; }
762
931
 
763
932
  .graph-node { cursor: pointer; }
764
933
 
765
934
  .graph-node circle {
766
935
  fill: var(--neon-purple);
767
- filter: drop-shadow(0 0 3px rgba(191,90,242,0.5));
936
+ filter: drop-shadow(0 0 2px rgba(168,139,250,0.4));
768
937
  transition: all 0.2s;
769
938
  }
770
939
 
771
940
  .graph-node:hover circle {
772
941
  fill: var(--neon-cyan);
773
- filter: drop-shadow(0 0 6px rgba(0,255,245,0.6));
942
+ filter: drop-shadow(0 0 4px rgba(217,119,87,0.5));
774
943
  }
775
944
 
776
945
  .graph-node.active circle {
777
946
  fill: var(--neon-cyan);
778
- r: 7;
779
- filter: drop-shadow(0 0 8px rgba(0,255,245,0.7));
947
+ r: 6;
948
+ filter: drop-shadow(0 0 6px rgba(217,119,87,0.5));
780
949
  }
781
950
 
782
951
  .graph-node text {
783
- fill: var(--text-secondary);
952
+ fill: var(--text-muted);
784
953
  font-family: var(--font-mono);
785
- font-size: 9px;
954
+ font-size: 8px;
786
955
  }
787
956
 
788
957
  .graph-link {
789
- stroke: rgba(191, 90, 242, 0.3);
958
+ stroke: rgba(168,139,250,0.15);
790
959
  stroke-width: 1;
791
960
  }
792
961
 
962
+ .graph-size-controls {
963
+ margin-left: auto;
964
+ display: flex;
965
+ gap: 2px;
966
+ }
967
+
968
+ .graph-size-btn {
969
+ background: var(--bg-hover);
970
+ border: 1px solid var(--border);
971
+ color: var(--text-muted);
972
+ font-family: var(--font-mono);
973
+ font-size: 11px;
974
+ width: 18px;
975
+ height: 18px;
976
+ border-radius: 3px;
977
+ cursor: pointer;
978
+ display: flex;
979
+ align-items: center;
980
+ justify-content: center;
981
+ transition: all 0.15s;
982
+ padding: 0;
983
+ line-height: 1;
984
+ }
985
+
986
+ .graph-size-btn:hover {
987
+ border-color: var(--neon-cyan);
988
+ color: var(--neon-cyan);
989
+ }
990
+
991
+ .graph-resize-handle {
992
+ height: 8px;
993
+ cursor: ns-resize;
994
+ position: relative;
995
+ }
996
+
997
+ .graph-resize-handle::after {
998
+ content: '';
999
+ position: absolute;
1000
+ bottom: 2px;
1001
+ left: 50%;
1002
+ transform: translateX(-50%);
1003
+ width: 24px;
1004
+ height: 2px;
1005
+ background: var(--border);
1006
+ border-radius: 1px;
1007
+ transition: background 0.2s;
1008
+ }
1009
+
1010
+ .graph-resize-handle:hover::after {
1011
+ background: var(--neon-cyan);
1012
+ }
1013
+
1014
+ /* ============ SESSIONS ============ */
1015
+ .session-folder-icon { color: var(--neon-green) !important; }
1016
+
1017
+ .session-add-btn {
1018
+ background: transparent;
1019
+ border: 1px solid var(--border);
1020
+ color: var(--text-muted);
1021
+ font-family: var(--font-mono);
1022
+ font-size: 11px;
1023
+ width: 16px;
1024
+ height: 16px;
1025
+ border-radius: 3px;
1026
+ cursor: pointer;
1027
+ margin-left: auto;
1028
+ display: none;
1029
+ align-items: center;
1030
+ justify-content: center;
1031
+ transition: all 0.15s;
1032
+ padding: 0;
1033
+ line-height: 1;
1034
+ }
1035
+
1036
+ .tree-item:hover .session-add-btn { display: flex; }
1037
+
1038
+ .session-add-btn:hover {
1039
+ border-color: var(--neon-green);
1040
+ color: var(--neon-green);
1041
+ }
1042
+
1043
+ .session-badge {
1044
+ display: inline-flex;
1045
+ align-items: center;
1046
+ gap: 8px;
1047
+ font-family: var(--font-mono);
1048
+ font-size: 10px;
1049
+ color: var(--neon-green);
1050
+ background: rgba(91,185,140,0.08);
1051
+ border: 1px solid rgba(91,185,140,0.15);
1052
+ padding: 4px 12px;
1053
+ border-radius: var(--radius);
1054
+ margin-bottom: 16px;
1055
+ letter-spacing: 0.5px;
1056
+ }
1057
+
1058
+ .session-create-overlay {
1059
+ position: fixed;
1060
+ inset: 0;
1061
+ background: rgba(6,6,12,0.75);
1062
+ z-index: 200;
1063
+ display: none;
1064
+ align-items: center;
1065
+ justify-content: center;
1066
+ backdrop-filter: blur(4px);
1067
+ }
1068
+
1069
+ .session-create-overlay.active { display: flex; }
1070
+
1071
+ .session-create-modal {
1072
+ width: 420px;
1073
+ background: var(--bg-card);
1074
+ border: 1px solid var(--border);
1075
+ border-radius: var(--radius-lg);
1076
+ box-shadow: 0 20px 60px rgba(0,0,0,0.6);
1077
+ overflow: hidden;
1078
+ }
1079
+
1080
+ .session-create-header {
1081
+ padding: 16px 20px 12px;
1082
+ border-bottom: 1px solid var(--border-subtle);
1083
+ font-family: var(--font-mono);
1084
+ font-size: 11px;
1085
+ font-weight: 600;
1086
+ color: var(--text-secondary);
1087
+ letter-spacing: 0.5px;
1088
+ }
1089
+
1090
+ .session-create-body {
1091
+ padding: 16px 20px;
1092
+ display: flex;
1093
+ flex-direction: column;
1094
+ gap: 12px;
1095
+ }
1096
+
1097
+ .session-field label {
1098
+ display: block;
1099
+ font-family: var(--font-mono);
1100
+ font-size: 9px;
1101
+ font-weight: 600;
1102
+ text-transform: uppercase;
1103
+ letter-spacing: 1px;
1104
+ color: var(--text-muted);
1105
+ margin-bottom: 4px;
1106
+ }
1107
+
1108
+ .session-field select,
1109
+ .session-field input,
1110
+ .session-field textarea {
1111
+ width: 100%;
1112
+ background: var(--bg-surface);
1113
+ border: 1px solid var(--border);
1114
+ color: var(--text-primary);
1115
+ font-family: var(--font-mono);
1116
+ font-size: 12px;
1117
+ padding: 8px 12px;
1118
+ border-radius: var(--radius);
1119
+ outline: none;
1120
+ transition: border-color 0.2s;
1121
+ }
1122
+
1123
+ .session-field select:focus,
1124
+ .session-field input:focus,
1125
+ .session-field textarea:focus {
1126
+ border-color: rgba(217,119,87,0.3);
1127
+ }
1128
+
1129
+ .session-field textarea {
1130
+ min-height: 60px;
1131
+ resize: vertical;
1132
+ }
1133
+
1134
+ .session-create-footer {
1135
+ padding: 12px 20px;
1136
+ border-top: 1px solid var(--border-subtle);
1137
+ display: flex;
1138
+ justify-content: flex-end;
1139
+ gap: 8px;
1140
+ }
1141
+
1142
+ .session-create-footer .edit-btn.create {
1143
+ background: rgba(91,185,140,0.1);
1144
+ color: var(--neon-green);
1145
+ border-color: rgba(91,185,140,0.3);
1146
+ }
1147
+
1148
+ .session-create-footer .edit-btn.create:hover {
1149
+ background: rgba(91,185,140,0.2);
1150
+ }
1151
+
793
1152
  /* ============ SCROLLBAR ============ */
794
- ::-webkit-scrollbar { width: 5px; }
1153
+ ::-webkit-scrollbar { width: 4px; }
795
1154
  ::-webkit-scrollbar-track { background: transparent; }
796
- ::-webkit-scrollbar-thumb { background: var(--bg-active); border-radius: 3px; }
1155
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
797
1156
  ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
798
1157
 
799
- /* ============ TRANSITIONS ============ */
800
- .fade-in {
801
- animation: fadeIn 0.2s ease;
1158
+ /* ============ EDIT MODAL ============ */
1159
+ .edit-overlay {
1160
+ position: fixed;
1161
+ inset: 0;
1162
+ background: rgba(6,6,12,0.75);
1163
+ z-index: 200;
1164
+ display: none;
1165
+ align-items: center;
1166
+ justify-content: center;
1167
+ backdrop-filter: blur(4px);
802
1168
  }
803
1169
 
804
- @keyframes fadeIn {
805
- from { opacity: 0; transform: translateY(4px); }
806
- to { opacity: 1; transform: translateY(0); }
1170
+ .edit-overlay.active { display: flex; }
1171
+
1172
+ .edit-modal {
1173
+ width: 640px;
1174
+ max-height: 80vh;
1175
+ background: var(--bg-card);
1176
+ border: 1px solid var(--border);
1177
+ border-radius: var(--radius-lg);
1178
+ box-shadow: 0 20px 60px rgba(0,0,0,0.6);
1179
+ display: flex;
1180
+ flex-direction: column;
1181
+ overflow: hidden;
1182
+ }
1183
+
1184
+ .edit-header {
1185
+ display: flex;
1186
+ align-items: center;
1187
+ justify-content: space-between;
1188
+ padding: 12px 16px;
1189
+ border-bottom: 1px solid var(--border-subtle);
1190
+ font-family: var(--font-mono);
1191
+ font-size: 11px;
1192
+ color: var(--text-secondary);
1193
+ }
1194
+
1195
+ .edit-header .edit-path { opacity: 0.6; }
1196
+
1197
+ .edit-actions { display: flex; gap: 8px; }
1198
+
1199
+ .edit-btn {
1200
+ font-family: var(--font-mono);
1201
+ font-size: 10px;
1202
+ padding: 5px 14px;
1203
+ border-radius: var(--radius);
1204
+ border: 1px solid var(--border);
1205
+ cursor: pointer;
1206
+ transition: all 0.15s;
1207
+ letter-spacing: 0.5px;
1208
+ }
1209
+
1210
+ .edit-btn.cancel {
1211
+ background: transparent;
1212
+ color: var(--text-muted);
1213
+ }
1214
+
1215
+ .edit-btn.cancel:hover { color: var(--text-secondary); border-color: var(--text-muted); }
1216
+
1217
+ .edit-btn.save {
1218
+ background: rgba(217,119,87,0.1);
1219
+ color: var(--neon-cyan);
1220
+ border-color: rgba(217,119,87,0.3);
1221
+ }
1222
+
1223
+ .edit-btn.save:hover { background: rgba(217,119,87,0.2); }
1224
+
1225
+ .edit-textarea {
1226
+ flex: 1;
1227
+ min-height: 300px;
1228
+ background: var(--bg-primary);
1229
+ border: none;
1230
+ color: var(--text-primary);
1231
+ font-family: var(--font-mono);
1232
+ font-size: 13px;
1233
+ line-height: 1.7;
1234
+ padding: 16px 20px;
1235
+ resize: none;
1236
+ outline: none;
1237
+ }
1238
+
1239
+ .edit-textarea::placeholder { color: var(--text-muted); }
1240
+
1241
+ .note-edit-btn {
1242
+ background: transparent;
1243
+ border: 1px solid var(--border);
1244
+ color: var(--text-muted);
1245
+ font-family: var(--font-mono);
1246
+ font-size: 10px;
1247
+ padding: 3px 10px;
1248
+ border-radius: var(--radius-sm);
1249
+ cursor: pointer;
1250
+ transition: all 0.15s;
1251
+ float: right;
1252
+ margin-bottom: 12px;
1253
+ }
1254
+
1255
+ .note-edit-btn:hover {
1256
+ border-color: var(--text-muted);
1257
+ color: var(--text-secondary);
1258
+ }
1259
+
1260
+ /* ============ BACKLINK CONTEXT ============ */
1261
+ .rp-item .rp-context {
1262
+ font-size: 10px;
1263
+ color: var(--text-muted);
1264
+ margin-top: 2px;
1265
+ line-height: 1.4;
1266
+ overflow: hidden;
1267
+ text-overflow: ellipsis;
1268
+ white-space: nowrap;
1269
+ }
1270
+
1271
+ .rp-item-col {
1272
+ display: flex;
1273
+ flex-direction: column;
1274
+ padding: 5px 8px;
1275
+ border-radius: var(--radius-sm);
1276
+ cursor: pointer;
1277
+ margin-bottom: 1px;
1278
+ transition: background 0.1s;
1279
+ overflow: hidden;
807
1280
  }
808
1281
 
809
- /* Pulse animation for active graph node */
810
- @keyframes pulse {
811
- 0%, 100% { opacity: 1; }
812
- 50% { opacity: 0.6; }
1282
+ .rp-item-col:hover { background: var(--bg-hover); }
1283
+
1284
+ /* ============ TRANSITIONS ============ */
1285
+ .fade-in { animation: fadeIn 0.15s ease; }
1286
+
1287
+ @keyframes fadeIn {
1288
+ from { opacity: 0; transform: translateY(3px); }
1289
+ to { opacity: 1; transform: translateY(0); }
813
1290
  }
814
1291
  </style>
815
1292
  </head>
@@ -821,10 +1298,13 @@ body.resizing .resize-handle {
821
1298
  <div class="logo">KYP-MEM <span class="logo-sub">know your project</span></div>
822
1299
  <div class="breadcrumb" id="breadcrumb"></div>
823
1300
  <div class="header-actions">
824
- <button class="toggle-btn active" id="graph-toggle" title="Toggle graph view">
1301
+ <button class="header-btn active" id="graph-toggle" title="Toggle graph">
825
1302
  <span class="dot"></span>
826
1303
  <span>GRAPH</span>
827
1304
  </button>
1305
+ <button class="header-btn" id="new-project-btn" title="New project">
1306
+ <span>+ PROJECT</span>
1307
+ </button>
828
1308
  <div class="search-box">
829
1309
  <span class="search-icon">&#9906;</span>
830
1310
  <input type="text" id="search-input" placeholder="Search...">
@@ -838,9 +1318,23 @@ body.resizing .resize-handle {
838
1318
  <div class="sidebar">
839
1319
  <div class="sidebar-scroll">
840
1320
  <div class="sidebar-section">
841
- <div class="sidebar-section-title">Explorer</div>
1321
+ <div class="section-label">Explorer</div>
1322
+ <div id="filter-info" class="filter-info" style="display:none;">
1323
+ <span id="filter-count"></span>
1324
+ <span class="clear-btn" id="clear-filters">clear</span>
1325
+ </div>
842
1326
  <div id="file-tree"></div>
843
1327
  </div>
1328
+ <div class="tag-filter-section">
1329
+ <div class="tag-filter-header" id="tag-filter-toggle">
1330
+ <span>Tags</span>
1331
+ <span class="arrow open" id="tag-arrow">&#9654;</span>
1332
+ </div>
1333
+ <div class="tag-filter-body" id="tag-filter-body">
1334
+ <div class="tag-filter-active" id="active-tags"></div>
1335
+ <div class="tag-cloud" id="tag-cloud"></div>
1336
+ </div>
1337
+ </div>
844
1338
  </div>
845
1339
  <div class="stats-bar" id="stats-bar"></div>
846
1340
  </div>
@@ -853,7 +1347,7 @@ body.resizing .resize-handle {
853
1347
  <div class="empty-state">
854
1348
  <div class="es-logo">KYP-MEM</div>
855
1349
  <div class="es-tagline">Know Your Project Memory</div>
856
- <div class="es-hint">Select a note from the sidebar or press <kbd>&#8984;K</kbd> to search</div>
1350
+ <div class="es-hint">Select a note or press <kbd>&#8984;O</kbd> to quick-switch &middot; <kbd>&#8984;K</kbd> to search</div>
857
1351
  </div>
858
1352
  </div>
859
1353
 
@@ -863,8 +1357,13 @@ body.resizing .resize-handle {
863
1357
  <!-- Right panel -->
864
1358
  <div class="right-panel" id="right-panel">
865
1359
  <div id="graph-section">
866
- <div class="rp-section-title">Graph View</div>
1360
+ <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></span></div>
867
1361
  <div id="graph-container"></div>
1362
+ <div id="graph-resize-handle" class="graph-resize-handle"></div>
1363
+ </div>
1364
+ <div id="rp-outline" class="rp-section" style="display:none;">
1365
+ <div class="rp-section-title">Outline <span class="rp-count" id="outline-count"></span></div>
1366
+ <div id="rp-outline-list"></div>
868
1367
  </div>
869
1368
  <div id="rp-backlinks" class="rp-section" style="display:none;">
870
1369
  <div class="rp-section-title">Backlinks <span class="rp-count" id="bl-count"></span></div>
@@ -878,6 +1377,75 @@ body.resizing .resize-handle {
878
1377
  <div class="rp-section-title">Outgoing Links <span class="rp-count" id="out-count"></span></div>
879
1378
  <div id="rp-outlinks-list"></div>
880
1379
  </div>
1380
+ <div id="rp-unlinked" class="rp-section" style="display:none;">
1381
+ <div class="rp-section-title">Unlinked Mentions <span class="rp-count" id="unlinked-count"></span></div>
1382
+ <div id="rp-unlinked-list"></div>
1383
+ </div>
1384
+ </div>
1385
+ </div>
1386
+
1387
+ <!-- Edit Modal -->
1388
+ <div class="edit-overlay" id="edit-overlay">
1389
+ <div class="edit-modal">
1390
+ <div class="edit-header">
1391
+ <span class="edit-path" id="edit-path"></span>
1392
+ <div class="edit-actions">
1393
+ <button class="edit-btn cancel" id="edit-cancel">ESC</button>
1394
+ <button class="edit-btn save" id="edit-save">SAVE</button>
1395
+ </div>
1396
+ </div>
1397
+ <textarea class="edit-textarea" id="edit-textarea" placeholder="Write markdown..."></textarea>
1398
+ </div>
1399
+ </div>
1400
+
1401
+ <!-- Quick Switcher Overlay -->
1402
+ <div class="quick-switcher-overlay" id="qs-overlay">
1403
+ <div class="quick-switcher">
1404
+ <input type="text" id="qs-input" placeholder="Jump to note...">
1405
+ <div class="quick-switcher-results" id="qs-results"></div>
1406
+ </div>
1407
+ </div>
1408
+
1409
+ <!-- Session Create Modal -->
1410
+ <div class="session-create-overlay" id="session-create-overlay">
1411
+ <div class="session-create-modal">
1412
+ <div class="session-create-header">NEW SESSION</div>
1413
+ <div class="session-create-body">
1414
+ <div class="session-field">
1415
+ <label>Project</label>
1416
+ <input type="text" id="session-project" list="project-list" placeholder="Project name...">
1417
+ <datalist id="project-list"></datalist>
1418
+ </div>
1419
+ <div class="session-field">
1420
+ <label>Summary</label>
1421
+ <textarea id="session-summary" placeholder="What are you working on?"></textarea>
1422
+ </div>
1423
+ </div>
1424
+ <div class="session-create-footer">
1425
+ <button class="edit-btn cancel" id="session-cancel">CANCEL</button>
1426
+ <button class="edit-btn create" id="session-create-btn">CREATE</button>
1427
+ </div>
1428
+ </div>
1429
+ </div>
1430
+
1431
+ <!-- Project Create Modal -->
1432
+ <div class="session-create-overlay" id="project-create-overlay">
1433
+ <div class="session-create-modal">
1434
+ <div class="session-create-header">NEW PROJECT</div>
1435
+ <div class="session-create-body">
1436
+ <div class="session-field">
1437
+ <label>Project Name</label>
1438
+ <input type="text" id="project-name-input" placeholder="My Project...">
1439
+ </div>
1440
+ <div class="session-field">
1441
+ <label>Overview (optional)</label>
1442
+ <textarea id="project-overview-input" placeholder="Brief project description, goals, tech stack..."></textarea>
1443
+ </div>
1444
+ </div>
1445
+ <div class="session-create-footer">
1446
+ <button class="edit-btn cancel" id="project-cancel">CANCEL</button>
1447
+ <button class="edit-btn create" id="project-create-btn">CREATE</button>
1448
+ </div>
881
1449
  </div>
882
1450
  </div>
883
1451
 
@@ -886,6 +1454,9 @@ let currentPath = null;
886
1454
  let treeData = null;
887
1455
  let allNotes = {};
888
1456
  let graphVisible = true;
1457
+ let currentNote = null;
1458
+ let activeTagFilters = new Set();
1459
+ let qsSelectedIndex = 0;
889
1460
 
890
1461
  async function fetchJSON(url) {
891
1462
  const r = await fetch(url);
@@ -910,25 +1481,137 @@ document.getElementById('graph-toggle').addEventListener('click', () => {
910
1481
  }
911
1482
  });
912
1483
 
913
- // --- Keyboard shortcut ---
1484
+ // --- Keyboard shortcuts ---
914
1485
  document.addEventListener('keydown', (e) => {
915
1486
  if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
916
1487
  e.preventDefault();
917
1488
  document.getElementById('search-input').focus();
918
1489
  }
1490
+ if ((e.metaKey || e.ctrlKey) && e.key === 'o') {
1491
+ e.preventDefault();
1492
+ openQuickSwitcher();
1493
+ }
1494
+ if (e.key === 'Escape') {
1495
+ closeQuickSwitcher();
1496
+ closeSessionCreate();
1497
+ closeProjectCreate();
1498
+ }
919
1499
  });
920
1500
 
1501
+ // --- Quick Switcher ---
1502
+ function openQuickSwitcher() {
1503
+ const overlay = document.getElementById('qs-overlay');
1504
+ const input = document.getElementById('qs-input');
1505
+ overlay.classList.add('active');
1506
+ input.value = '';
1507
+ input.focus();
1508
+ qsSelectedIndex = 0;
1509
+ renderQsResults('');
1510
+ }
1511
+
1512
+ function closeQuickSwitcher() {
1513
+ document.getElementById('qs-overlay').classList.remove('active');
1514
+ }
1515
+
1516
+ document.getElementById('qs-overlay').addEventListener('click', (e) => {
1517
+ if (e.target === e.currentTarget) closeQuickSwitcher();
1518
+ });
1519
+
1520
+ document.getElementById('qs-input').addEventListener('input', (e) => {
1521
+ qsSelectedIndex = 0;
1522
+ renderQsResults(e.target.value.trim().toLowerCase());
1523
+ });
1524
+
1525
+ document.getElementById('qs-input').addEventListener('keydown', (e) => {
1526
+ const items = document.querySelectorAll('.qs-item');
1527
+ if (e.key === 'ArrowDown') {
1528
+ e.preventDefault();
1529
+ qsSelectedIndex = Math.min(qsSelectedIndex + 1, items.length - 1);
1530
+ updateQsSelection();
1531
+ } else if (e.key === 'ArrowUp') {
1532
+ e.preventDefault();
1533
+ qsSelectedIndex = Math.max(qsSelectedIndex - 1, 0);
1534
+ updateQsSelection();
1535
+ } else if (e.key === 'Enter') {
1536
+ e.preventDefault();
1537
+ const selected = items[qsSelectedIndex];
1538
+ if (selected) {
1539
+ loadNote(selected.dataset.path);
1540
+ closeQuickSwitcher();
1541
+ }
1542
+ }
1543
+ });
1544
+
1545
+ function renderQsResults(query) {
1546
+ const container = document.getElementById('qs-results');
1547
+ const entries = Object.entries(allNotes);
1548
+
1549
+ let filtered = entries;
1550
+ if (query) {
1551
+ filtered = entries.filter(([path, note]) => {
1552
+ const name = note.title.toLowerCase();
1553
+ const p = path.toLowerCase();
1554
+ return name.includes(query) || p.includes(query);
1555
+ });
1556
+ }
1557
+
1558
+ filtered.sort((a, b) => a[1].title.localeCompare(b[1].title));
1559
+ filtered = filtered.slice(0, 15);
1560
+
1561
+ if (filtered.length === 0) {
1562
+ container.innerHTML = '<div class="qs-empty">No notes found</div>';
1563
+ return;
1564
+ }
1565
+
1566
+ container.innerHTML = filtered.map(([path, note], i) => {
1567
+ const folder = path.includes('/') ? path.split('/').slice(0, -1).join('/') : '';
1568
+ return `<div class="qs-item${i === qsSelectedIndex ? ' selected' : ''}" data-path="${path}">
1569
+ <span class="qs-icon">&#9671;</span>
1570
+ <span class="qs-name">${note.title}</span>
1571
+ ${folder ? `<span class="qs-path">${folder}</span>` : ''}
1572
+ </div>`;
1573
+ }).join('');
1574
+
1575
+ container.querySelectorAll('.qs-item').forEach((el, i) => {
1576
+ el.addEventListener('click', () => {
1577
+ loadNote(el.dataset.path);
1578
+ closeQuickSwitcher();
1579
+ });
1580
+ el.addEventListener('mouseenter', () => {
1581
+ qsSelectedIndex = i;
1582
+ updateQsSelection();
1583
+ });
1584
+ });
1585
+ }
1586
+
1587
+ function updateQsSelection() {
1588
+ document.querySelectorAll('.qs-item').forEach((el, i) => {
1589
+ el.classList.toggle('selected', i === qsSelectedIndex);
1590
+ });
1591
+ }
1592
+
921
1593
  // --- File Tree ---
922
- function renderTree(node, container) {
1594
+ function renderTree(node, container, parentFolder) {
923
1595
  if (node.type === 'folder' && node.name !== 'vault') {
1596
+ const isSessionsFolder = node.name === 'Sessions';
924
1597
  const item = document.createElement('div');
925
1598
  item.className = 'tree-item';
926
- item.innerHTML = `<span class="arrow open">&#9654;</span><span class="icon folder-icon">&#9776;</span><span class="name">${node.name}</span>`;
1599
+
1600
+ if (isSessionsFolder) {
1601
+ item.innerHTML = `<span class="arrow open">&#9654;</span><span class="icon session-folder-icon">&#9716;</span><span class="name">Sessions</span><button class="session-add-btn" data-project="${parentFolder || ''}" title="New session">+</button>`;
1602
+ } else {
1603
+ item.innerHTML = `<span class="arrow open">&#9654;</span><span class="icon folder-icon">&#9776;</span><span class="name">${node.name}</span>`;
1604
+ }
927
1605
 
928
1606
  const children = document.createElement('div');
929
1607
  children.className = 'tree-children';
930
1608
 
931
1609
  item.addEventListener('click', (e) => {
1610
+ if (e.target.classList.contains('session-add-btn')) {
1611
+ e.stopPropagation();
1612
+ openSessionCreate(e.target.dataset.project);
1613
+ return;
1614
+ }
932
1615
  e.stopPropagation();
933
1616
  item.querySelector('.arrow').classList.toggle('open');
934
1617
  children.classList.toggle('collapsed');
@@ -936,13 +1619,30 @@ function renderTree(node, container) {
936
1619
 
937
1620
  container.appendChild(item);
938
1621
  container.appendChild(children);
939
- (node.children || []).forEach(c => renderTree(c, children));
1622
+ (node.children || []).forEach(c => renderTree(c, children, node.name));
940
1623
 
941
1624
  } else if (node.type === 'note') {
942
1625
  const item = document.createElement('div');
943
1626
  item.className = 'tree-item';
944
1627
  item.dataset.path = node.path;
945
- 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>`;
1628
+
1629
+ const isSessionNote = node.path.includes('/Sessions/') || node.path.startsWith('Sessions/');
1630
+ let displayName = node.name.replace('.md', '');
1631
+
1632
+ if (isSessionNote) {
1633
+ const match = displayName.match(/^(\d{4})-(\d{2})-(\d{2})_(\d{2})(\d{2})(\d{2})$/);
1634
+ if (match) {
1635
+ const [, y, mo, d, h, mi] = match;
1636
+ const hr = parseInt(h);
1637
+ const ampm = hr >= 12 ? 'PM' : 'AM';
1638
+ const hr12 = hr % 12 || 12;
1639
+ const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
1640
+ displayName = `${months[parseInt(mo)-1]} ${parseInt(d)}, ${hr12}:${mi} ${ampm}`;
1641
+ item.title = node.name.replace('.md', '');
1642
+ }
1643
+ }
1644
+
1645
+ item.innerHTML = `<span class="arrow" style="visibility:hidden">&#9654;</span><span class="icon note-icon">&#9671;</span><span class="name">${displayName}</span>`;
946
1646
  item.addEventListener('click', () => loadNote(node.path));
947
1647
  container.appendChild(item);
948
1648
 
@@ -956,12 +1656,12 @@ async function loadNote(path) {
956
1656
  currentPath = path;
957
1657
  const note = await fetchJSON(`/api/note/${path}`);
958
1658
  if (note.error) return;
1659
+ currentNote = note;
959
1660
 
960
1661
  document.querySelectorAll('.tree-item').forEach(el => {
961
1662
  el.classList.toggle('active', el.dataset.path === path);
962
1663
  });
963
1664
 
964
- // Breadcrumb
965
1665
  const parts = path.replace('.md', '').split('/');
966
1666
  document.getElementById('breadcrumb').innerHTML = parts.map((p, i) =>
967
1667
  i < parts.length - 1
@@ -969,12 +1669,19 @@ async function loadNote(path) {
969
1669
  : `<span class="current">${p}</span>`
970
1670
  ).join('');
971
1671
 
972
- // Content
973
1672
  const contentEl = document.getElementById('content');
974
1673
  let html = '<div class="fade-in">';
975
1674
 
1675
+ html += `<button class="note-edit-btn" onclick="openEditor('${path}')">EDIT</button>`;
1676
+
1677
+ const isSession = path.includes('/Sessions/') || path.startsWith('Sessions/');
1678
+ if (isSession) {
1679
+ const sessionProject = path.includes('/Sessions/') ? path.split('/Sessions/')[0] : 'General';
1680
+ html += `<div class="session-badge">&#9716; SESSION &middot; ${sessionProject}</div>`;
1681
+ }
1682
+
976
1683
  html += '<div class="note-properties">';
977
- (note.tags || []).forEach(t => { html += `<span class="tag">#${t}</span>`; });
1684
+ (note.tags || []).forEach(t => { html += `<span class="tag clickable-tag" data-tag="${t}">#${t}</span>`; });
978
1685
  Object.entries(note.properties || {}).forEach(([k, v]) => {
979
1686
  html += `<span class="prop-item"><span class="prop-key">${k}:</span> ${v}</span>`;
980
1687
  });
@@ -1000,7 +1707,15 @@ async function loadNote(path) {
1000
1707
  });
1001
1708
  });
1002
1709
 
1710
+ contentEl.querySelectorAll('.clickable-tag').forEach(el => {
1711
+ el.addEventListener('click', () => {
1712
+ activeTagFilters.add(el.dataset.tag);
1713
+ applyTagFilter();
1714
+ });
1715
+ });
1716
+
1003
1717
  renderRightPanel(note);
1718
+ renderOutline(note);
1004
1719
  if (graphVisible) renderGraph(note);
1005
1720
  }
1006
1721
 
@@ -1013,20 +1728,58 @@ function findNotePath(name) {
1013
1728
  return null;
1014
1729
  }
1015
1730
 
1731
+ // --- Outline ---
1732
+ function renderOutline(note) {
1733
+ const section = document.getElementById('rp-outline');
1734
+ const list = document.getElementById('rp-outline-list');
1735
+ list.innerHTML = '';
1736
+
1737
+ const content = note.content || '';
1738
+ const headings = [];
1739
+ content.split('\n').forEach(line => {
1740
+ const m = line.match(/^(#{1,3})\s+(.+)/);
1741
+ if (m) headings.push({ level: m[1].length, text: m[2].trim() });
1742
+ });
1743
+
1744
+ if (headings.length < 2) {
1745
+ section.style.display = 'none';
1746
+ return;
1747
+ }
1748
+
1749
+ section.style.display = '';
1750
+ document.getElementById('outline-count').textContent = headings.length;
1751
+
1752
+ headings.forEach(h => {
1753
+ const el = document.createElement('div');
1754
+ el.className = `outline-item h${h.level}`;
1755
+ el.textContent = h.text;
1756
+ el.addEventListener('click', () => {
1757
+ const contentEl = document.getElementById('content');
1758
+ const target = Array.from(contentEl.querySelectorAll('h1,h2,h3')).find(
1759
+ el => el.textContent.trim() === h.text
1760
+ );
1761
+ if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
1762
+ });
1763
+ list.appendChild(el);
1764
+ });
1765
+ }
1766
+
1016
1767
  // --- Right Panel ---
1017
1768
  function renderRightPanel(note) {
1018
1769
  const blSection = document.getElementById('rp-backlinks');
1019
1770
  const blList = document.getElementById('rp-backlinks-list');
1020
1771
  blList.innerHTML = '';
1021
- if (note.backlinks && note.backlinks.length) {
1772
+ const backlinks = note.backlinks || [];
1773
+ if (backlinks.length) {
1022
1774
  blSection.style.display = '';
1023
- document.getElementById('bl-count').textContent = note.backlinks.length;
1024
- note.backlinks.forEach(path => {
1025
- const n = allNotes[path];
1775
+ document.getElementById('bl-count').textContent = backlinks.length;
1776
+ backlinks.forEach(bl => {
1026
1777
  const item = document.createElement('div');
1027
- item.className = 'rp-item';
1028
- item.innerHTML = `<span class="rp-title rp-backlink-title">${n ? n.title : path}</span>`;
1029
- item.addEventListener('click', () => loadNote(path));
1778
+ item.className = 'rp-item-col';
1779
+ let html = `<span class="rp-title rp-backlink-title" style="font-size:11px">${bl.title || bl.path || bl}</span>`;
1780
+ if (bl.context) html += `<span class="rp-context">${bl.context}</span>`;
1781
+ item.innerHTML = html;
1782
+ item.addEventListener('click', () => loadNote(bl.path || bl));
1030
1783
  blList.appendChild(item);
1031
1784
  });
1032
1785
  } else {
@@ -1062,12 +1815,212 @@ function renderRightPanel(note) {
1062
1815
  item.className = 'rp-item';
1063
1816
  item.innerHTML = `<span class="rp-title">${link}</span>`;
1064
1817
  if (path) item.addEventListener('click', () => loadNote(path));
1065
- else item.style.opacity = '0.4';
1818
+ else item.style.opacity = '0.3';
1066
1819
  outList.appendChild(item);
1067
1820
  });
1068
1821
  } else {
1069
1822
  outSection.style.display = 'none';
1070
1823
  }
1824
+
1825
+ const ulSection = document.getElementById('rp-unlinked');
1826
+ const ulList = document.getElementById('rp-unlinked-list');
1827
+ ulList.innerHTML = '';
1828
+ const unlinked = note.unlinked || [];
1829
+ if (unlinked.length) {
1830
+ ulSection.style.display = '';
1831
+ document.getElementById('unlinked-count').textContent = unlinked.length;
1832
+ unlinked.forEach(u => {
1833
+ const item = document.createElement('div');
1834
+ item.className = 'rp-item-col';
1835
+ let html = `<span class="rp-title" style="font-size:11px;color:var(--text-secondary)">${u.title}</span>`;
1836
+ if (u.context) html += `<span class="rp-context">${u.context}</span>`;
1837
+ item.innerHTML = html;
1838
+ item.addEventListener('click', () => loadNote(u.path));
1839
+ ulList.appendChild(item);
1840
+ });
1841
+ } else {
1842
+ ulSection.style.display = 'none';
1843
+ }
1844
+ }
1845
+
1846
+ // --- Editor ---
1847
+ let editingPath = null;
1848
+ let editingNote = null;
1849
+
1850
+ async function openEditor(path) {
1851
+ const note = await fetchJSON(`/api/note/${path}`);
1852
+ if (note.error) return;
1853
+ editingPath = path;
1854
+ editingNote = note;
1855
+ document.getElementById('edit-path').textContent = path;
1856
+ document.getElementById('edit-textarea').value = note.content || '';
1857
+ document.getElementById('edit-overlay').classList.add('active');
1858
+ document.getElementById('edit-textarea').focus();
1859
+ }
1860
+
1861
+ document.getElementById('edit-cancel').addEventListener('click', closeEditor);
1862
+ document.getElementById('edit-overlay').addEventListener('click', (e) => {
1863
+ if (e.target === e.currentTarget) closeEditor();
1864
+ });
1865
+
1866
+ document.getElementById('edit-save').addEventListener('click', async () => {
1867
+ if (!editingPath) return;
1868
+ const content = document.getElementById('edit-textarea').value;
1869
+ await fetch(`/api/note/${editingPath}`, {
1870
+ method: 'POST',
1871
+ headers: { 'Content-Type': 'application/json' },
1872
+ body: JSON.stringify({
1873
+ content,
1874
+ tags: editingNote.tags || [],
1875
+ properties: editingNote.properties || {},
1876
+ }),
1877
+ });
1878
+ closeEditor();
1879
+ await loadNote(editingPath);
1880
+ });
1881
+
1882
+ function closeEditor() {
1883
+ document.getElementById('edit-overlay').classList.remove('active');
1884
+ editingPath = null;
1885
+ editingNote = null;
1886
+ }
1887
+
1888
+ document.addEventListener('keydown', (e) => {
1889
+ if (e.key === 'Escape' && document.getElementById('edit-overlay').classList.contains('active')) {
1890
+ closeEditor();
1891
+ }
1892
+ if ((e.metaKey || e.ctrlKey) && e.key === 's' && editingPath) {
1893
+ e.preventDefault();
1894
+ document.getElementById('edit-save').click();
1895
+ }
1896
+ });
1897
+
1898
+ // --- Session Management ---
1899
+ function openSessionCreate(defaultProject) {
1900
+ const overlay = document.getElementById('session-create-overlay');
1901
+ const projectInput = document.getElementById('session-project');
1902
+ const datalist = document.getElementById('project-list');
1903
+ const summaryInput = document.getElementById('session-summary');
1904
+
1905
+ const projects = new Set();
1906
+ for (const path of Object.keys(allNotes)) {
1907
+ const parts = path.split('/');
1908
+ if (parts.length > 1) projects.add(parts[0]);
1909
+ }
1910
+
1911
+ datalist.innerHTML = '';
1912
+ for (const p of [...projects].sort()) {
1913
+ const opt = document.createElement('option');
1914
+ opt.value = p;
1915
+ datalist.appendChild(opt);
1916
+ }
1917
+
1918
+ projectInput.value = defaultProject || '';
1919
+ summaryInput.value = '';
1920
+ overlay.classList.add('active');
1921
+
1922
+ if (defaultProject) summaryInput.focus();
1923
+ else projectInput.focus();
1924
+ }
1925
+
1926
+ function closeSessionCreate() {
1927
+ document.getElementById('session-create-overlay').classList.remove('active');
1928
+ }
1929
+
1930
+ document.getElementById('session-cancel').addEventListener('click', closeSessionCreate);
1931
+ document.getElementById('session-create-overlay').addEventListener('click', (e) => {
1932
+ if (e.target === e.currentTarget) closeSessionCreate();
1933
+ });
1934
+
1935
+ document.getElementById('session-create-btn').addEventListener('click', async () => {
1936
+ const project = document.getElementById('session-project').value.trim();
1937
+ const summary = document.getElementById('session-summary').value.trim();
1938
+
1939
+ if (!project) {
1940
+ document.getElementById('session-project').focus();
1941
+ return;
1942
+ }
1943
+
1944
+ const resp = await fetch('/api/sessions/create', {
1945
+ method: 'POST',
1946
+ headers: { 'Content-Type': 'application/json' },
1947
+ body: JSON.stringify({ project, summary }),
1948
+ });
1949
+
1950
+ const result = await resp.json();
1951
+ if (result.ok) {
1952
+ closeSessionCreate();
1953
+ await refreshTree();
1954
+ loadNote(result.path);
1955
+ }
1956
+ });
1957
+
1958
+ // --- Project Management ---
1959
+ function closeProjectCreate() {
1960
+ document.getElementById('project-create-overlay').classList.remove('active');
1961
+ }
1962
+
1963
+ document.getElementById('new-project-btn').addEventListener('click', () => {
1964
+ document.getElementById('project-create-overlay').classList.add('active');
1965
+ document.getElementById('project-name-input').value = '';
1966
+ document.getElementById('project-overview-input').value = '';
1967
+ document.getElementById('project-name-input').focus();
1968
+ });
1969
+
1970
+ document.getElementById('project-cancel').addEventListener('click', closeProjectCreate);
1971
+ document.getElementById('project-create-overlay').addEventListener('click', (e) => {
1972
+ if (e.target === e.currentTarget) closeProjectCreate();
1973
+ });
1974
+
1975
+ document.getElementById('project-create-btn').addEventListener('click', async () => {
1976
+ const name = document.getElementById('project-name-input').value.trim();
1977
+ const overview = document.getElementById('project-overview-input').value.trim();
1978
+ if (!name) {
1979
+ document.getElementById('project-name-input').focus();
1980
+ return;
1981
+ }
1982
+ const resp = await fetch('/api/projects/create', {
1983
+ method: 'POST',
1984
+ headers: { 'Content-Type': 'application/json' },
1985
+ body: JSON.stringify({ name, overview }),
1986
+ });
1987
+ const result = await resp.json();
1988
+ if (result.ok) {
1989
+ closeProjectCreate();
1990
+ await refreshTree();
1991
+ loadNote(result.path);
1992
+ }
1993
+ });
1994
+
1995
+ async function refreshTree() {
1996
+ treeData = await fetchJSON('/api/tree');
1997
+ allNotes = {};
1998
+ function walk(node) {
1999
+ if (node.type === 'note') {
2000
+ allNotes[node.path] = { title: node.name.replace('.md', ''), tags: node.tags || [] };
2001
+ }
2002
+ (node.children || []).forEach(walk);
2003
+ }
2004
+ walk(treeData);
2005
+
2006
+ const treeEl = document.getElementById('file-tree');
2007
+ treeEl.innerHTML = '';
2008
+ renderTree(treeData, treeEl);
2009
+ renderTagCloud(collectAllTags());
2010
+
2011
+ const stats = await fetchJSON('/api/stats');
2012
+ document.getElementById('stats-bar').innerHTML = `
2013
+ <span><span class="stat-val">${stats.notes}</span> notes</span>
2014
+ <span><span class="stat-val">${stats.folders}</span> folders</span>
2015
+ <span><span class="stat-val">${stats.tags}</span> tags</span>
2016
+ <span><span class="stat-val">${stats.links}</span> links</span>
2017
+ `;
2018
+
2019
+ if (currentPath) {
2020
+ document.querySelectorAll('.tree-item').forEach(el => {
2021
+ el.classList.toggle('active', el.dataset.path === currentPath);
2022
+ });
2023
+ }
1071
2024
  }
1072
2025
 
1073
2026
  // --- Graph ---
@@ -1089,66 +2042,126 @@ function renderGraph(note) {
1089
2042
  if (path) links.push({ source: note.path, target: path });
1090
2043
  });
1091
2044
 
1092
- (note.backlinks || []).forEach(path => {
1093
- if (!nodes.has(path)) {
1094
- const n = allNotes[path];
1095
- nodes.set(path, { id: path, title: n ? n.title : path, active: false });
2045
+ (note.backlinks || []).forEach(bl => {
2046
+ const blPath = typeof bl === 'string' ? bl : (bl && bl.path);
2047
+ if (!blPath) return;
2048
+ if (!nodes.has(blPath)) {
2049
+ const n = allNotes[blPath];
2050
+ const title = (bl && typeof bl === 'object' && bl.title) ? bl.title : (n ? n.title : blPath);
2051
+ nodes.set(blPath, { id: blPath, title, active: false });
1096
2052
  }
1097
- links.push({ source: path, target: note.path });
2053
+ links.push({ source: blPath, target: note.path });
1098
2054
  });
1099
2055
 
1100
2056
  (note.related || []).forEach(r => {
1101
2057
  if (!nodes.has(r.path)) {
1102
2058
  nodes.set(r.path, { id: r.path, title: r.title, active: false });
1103
2059
  }
2060
+ links.push({ source: note.path, target: r.path, dashed: true });
1104
2061
  });
1105
2062
 
1106
2063
  const nodeArray = Array.from(nodes.values());
1107
2064
  if (nodeArray.length < 2) {
1108
- container.innerHTML = '<svg></svg>';
2065
+ 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>';
1109
2066
  return;
1110
2067
  }
1111
2068
 
1112
- const width = container.clientWidth;
1113
- const height = container.clientHeight;
2069
+ const width = container.clientWidth || 248;
2070
+ const height = container.clientHeight || 200;
2071
+ const margin = 24;
1114
2072
 
1115
2073
  const svg = d3.select(container).append('svg')
1116
2074
  .attr('viewBox', `0 0 ${width} ${height}`);
1117
2075
 
1118
- const defs = svg.append('defs');
1119
- const grad = defs.append('radialGradient').attr('id', 'node-glow');
1120
- grad.append('stop').attr('offset', '0%').attr('stop-color', 'var(--neon-cyan)').attr('stop-opacity', 0.3);
1121
- grad.append('stop').attr('offset', '100%').attr('stop-color', 'var(--neon-cyan)').attr('stop-opacity', 0);
2076
+ const g = svg.append('g');
2077
+
2078
+ svg.call(d3.zoom()
2079
+ .scaleExtent([0.3, 3])
2080
+ .on('zoom', (event) => g.attr('transform', event.transform))
2081
+ );
1122
2082
 
1123
2083
  const simulation = d3.forceSimulation(nodeArray)
1124
- .force('link', d3.forceLink(links).id(d => d.id).distance(55))
2084
+ .force('link', d3.forceLink(links).id(d => d.id).distance(60))
1125
2085
  .force('charge', d3.forceManyBody().strength(-120))
1126
2086
  .force('center', d3.forceCenter(width / 2, height / 2))
1127
2087
  .force('collision', d3.forceCollide().radius(25));
1128
2088
 
1129
- const link = svg.selectAll('.graph-link')
2089
+ const linkEl = g.selectAll('.graph-link')
1130
2090
  .data(links).enter().append('line')
1131
- .attr('class', 'graph-link');
2091
+ .attr('class', 'graph-link')
2092
+ .attr('stroke-dasharray', d => d.dashed ? '3,3' : null)
2093
+ .style('opacity', d => d.dashed ? 0.5 : 1);
1132
2094
 
1133
- const node = svg.selectAll('.graph-node')
2095
+ const node = g.selectAll('.graph-node')
1134
2096
  .data(nodeArray).enter().append('g')
1135
2097
  .attr('class', d => 'graph-node' + (d.active ? ' active' : ''))
1136
- .on('click', (e, d) => loadNote(d.id))
2098
+ .on('click', (e, d) => { e.stopPropagation(); loadNote(d.id); })
1137
2099
  .call(d3.drag()
1138
2100
  .on('start', (e, d) => { if (!e.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })
1139
2101
  .on('drag', (e, d) => { d.fx = e.x; d.fy = e.y; })
1140
2102
  .on('end', (e, d) => { if (!e.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; })
1141
2103
  );
1142
2104
 
1143
- node.filter(d => d.active).append('circle').attr('r', 14).style('fill', 'url(#node-glow)').style('stroke', 'none');
1144
2105
  node.append('circle').attr('r', d => d.active ? 6 : 4);
1145
- node.append('text').text(d => d.title).attr('dx', 12).attr('dy', 3);
2106
+ node.append('text').text(d => d.title).attr('dx', 10).attr('dy', 3);
1146
2107
 
1147
2108
  simulation.on('tick', () => {
1148
- link
2109
+ linkEl
1149
2110
  .attr('x1', d => d.source.x).attr('y1', d => d.source.y)
1150
2111
  .attr('x2', d => d.target.x).attr('y2', d => d.target.y);
1151
- node.attr('transform', d => `translate(${d.x},${d.y})`);
2112
+ node.attr('transform', d => {
2113
+ d.x = Math.max(margin, Math.min(width - margin, d.x));
2114
+ d.y = Math.max(margin, Math.min(height - margin, d.y));
2115
+ return `translate(${d.x},${d.y})`;
2116
+ });
2117
+ });
2118
+ }
2119
+
2120
+ // --- Graph Resize ---
2121
+ let graphHeight = parseInt(localStorage.getItem('kyp-graph-h')) || 200;
2122
+
2123
+ function initGraphResize() {
2124
+ const container = document.getElementById('graph-container');
2125
+ container.style.height = graphHeight + 'px';
2126
+
2127
+ document.getElementById('graph-shrink').addEventListener('click', () => {
2128
+ graphHeight = Math.max(100, graphHeight - 50);
2129
+ container.style.height = graphHeight + 'px';
2130
+ localStorage.setItem('kyp-graph-h', graphHeight);
2131
+ if (graphVisible && currentNote) renderGraph(currentNote);
2132
+ });
2133
+
2134
+ document.getElementById('graph-grow').addEventListener('click', () => {
2135
+ graphHeight = Math.min(600, graphHeight + 50);
2136
+ container.style.height = graphHeight + 'px';
2137
+ localStorage.setItem('kyp-graph-h', graphHeight);
2138
+ if (graphVisible && currentNote) renderGraph(currentNote);
2139
+ });
2140
+
2141
+ const handle = document.getElementById('graph-resize-handle');
2142
+ handle.addEventListener('mousedown', (e) => {
2143
+ e.preventDefault();
2144
+ const startY = e.clientY;
2145
+ const startH = graphHeight;
2146
+ document.body.style.cursor = 'ns-resize';
2147
+ document.body.style.userSelect = 'none';
2148
+
2149
+ function onMove(ev) {
2150
+ graphHeight = Math.max(100, Math.min(600, startH + (ev.clientY - startY)));
2151
+ container.style.height = graphHeight + 'px';
2152
+ }
2153
+
2154
+ function onUp() {
2155
+ document.body.style.cursor = '';
2156
+ document.body.style.userSelect = '';
2157
+ localStorage.setItem('kyp-graph-h', graphHeight);
2158
+ if (graphVisible && currentNote) renderGraph(currentNote);
2159
+ document.removeEventListener('mousemove', onMove);
2160
+ document.removeEventListener('mouseup', onUp);
2161
+ }
2162
+
2163
+ document.addEventListener('mousemove', onMove);
2164
+ document.addEventListener('mouseup', onUp);
1152
2165
  });
1153
2166
  }
1154
2167
 
@@ -1195,14 +2208,113 @@ document.addEventListener('click', (e) => {
1195
2208
  if (!e.target.closest('.search-box')) searchResults.classList.remove('active');
1196
2209
  });
1197
2210
 
2211
+ // --- Tag Filter ---
2212
+ document.getElementById('tag-filter-toggle').addEventListener('click', () => {
2213
+ const body = document.getElementById('tag-filter-body');
2214
+ const arrow = document.getElementById('tag-arrow');
2215
+ body.classList.toggle('collapsed');
2216
+ arrow.classList.toggle('open');
2217
+ });
2218
+
2219
+ document.getElementById('clear-filters').addEventListener('click', () => {
2220
+ activeTagFilters.clear();
2221
+ applyTagFilter();
2222
+ });
2223
+
2224
+ function renderTagCloud(tags) {
2225
+ const cloud = document.getElementById('tag-cloud');
2226
+ cloud.innerHTML = '';
2227
+ const sorted = Object.entries(tags).sort((a, b) => b[1] - a[1]);
2228
+ sorted.forEach(([tag, count]) => {
2229
+ const chip = document.createElement('span');
2230
+ chip.className = 'tag-chip' + (activeTagFilters.has(tag) ? ' selected' : '');
2231
+ chip.innerHTML = `#${tag}<span class="tag-count">${count}</span>`;
2232
+ chip.addEventListener('click', () => {
2233
+ if (activeTagFilters.has(tag)) {
2234
+ activeTagFilters.delete(tag);
2235
+ } else {
2236
+ activeTagFilters.add(tag);
2237
+ }
2238
+ applyTagFilter();
2239
+ });
2240
+ cloud.appendChild(chip);
2241
+ });
2242
+ }
2243
+
2244
+ function applyTagFilter() {
2245
+ const activeEl = document.getElementById('active-tags');
2246
+ const filterInfo = document.getElementById('filter-info');
2247
+ const filterCount = document.getElementById('filter-count');
2248
+
2249
+ activeEl.innerHTML = '';
2250
+ activeTagFilters.forEach(tag => {
2251
+ const el = document.createElement('span');
2252
+ el.className = 'active-tag';
2253
+ el.innerHTML = `#${tag}<span class="remove">&times;</span>`;
2254
+ el.addEventListener('click', () => {
2255
+ activeTagFilters.delete(tag);
2256
+ applyTagFilter();
2257
+ });
2258
+ activeEl.appendChild(el);
2259
+ });
2260
+
2261
+ document.querySelectorAll('.tag-chip').forEach(chip => {
2262
+ const tag = chip.textContent.replace('#', '').replace(/\d+$/, '');
2263
+ chip.classList.toggle('selected', activeTagFilters.has(tag));
2264
+ });
2265
+
2266
+ const treeItems = document.querySelectorAll('.tree-item[data-path]');
2267
+ let visibleCount = 0;
2268
+
2269
+ if (activeTagFilters.size === 0) {
2270
+ treeItems.forEach(el => { el.style.display = ''; });
2271
+ document.querySelectorAll('.tree-children').forEach(el => el.classList.remove('collapsed'));
2272
+ filterInfo.style.display = 'none';
2273
+ renderTagCloud(collectAllTags());
2274
+ return;
2275
+ }
2276
+
2277
+ const matchingPaths = new Set();
2278
+
2279
+ for (const [path, note] of Object.entries(allNotes)) {
2280
+ const noteTags = (note.tags || []).map(t => t.toLowerCase());
2281
+ const matches = [...activeTagFilters].every(f => noteTags.includes(f.toLowerCase()));
2282
+ if (matches) matchingPaths.add(path);
2283
+ }
2284
+
2285
+ treeItems.forEach(el => {
2286
+ const path = el.dataset.path;
2287
+ if (matchingPaths.has(path)) {
2288
+ el.style.display = '';
2289
+ visibleCount++;
2290
+ } else {
2291
+ el.style.display = 'none';
2292
+ }
2293
+ });
2294
+
2295
+ document.querySelectorAll('.tree-children').forEach(el => el.classList.remove('collapsed'));
2296
+
2297
+ filterInfo.style.display = '';
2298
+ filterCount.textContent = `${visibleCount} note${visibleCount !== 1 ? 's' : ''}`;
2299
+ renderTagCloud(collectAllTags());
2300
+ }
2301
+
2302
+ function collectAllTags() {
2303
+ const tags = {};
2304
+ for (const note of Object.values(allNotes)) {
2305
+ (note.tags || []).forEach(t => { tags[t] = (tags[t] || 0) + 1; });
2306
+ }
2307
+ return tags;
2308
+ }
2309
+
1198
2310
  // --- Resizable Panels ---
1199
2311
  function initResize() {
1200
2312
  const layout = document.getElementById('layout');
1201
2313
  const leftHandle = document.getElementById('resize-left');
1202
2314
  const rightHandle = document.getElementById('resize-right');
1203
2315
 
1204
- let sidebarW = parseInt(localStorage.getItem('kyp-sidebar-w')) || 260;
1205
- let rightW = parseInt(localStorage.getItem('kyp-right-w')) || 280;
2316
+ let sidebarW = parseInt(localStorage.getItem('kyp-sidebar-w')) || 256;
2317
+ let rightW = parseInt(localStorage.getItem('kyp-right-w')) || 272;
1206
2318
 
1207
2319
  layout.style.setProperty('--sidebar-w', sidebarW + 'px');
1208
2320
  layout.style.setProperty('--right-w', rightW + 'px');
@@ -1242,7 +2354,7 @@ function initResize() {
1242
2354
  save: () => localStorage.setItem('kyp-sidebar-w', sidebarW),
1243
2355
  invert: false,
1244
2356
  min: 180,
1245
- max: 450,
2357
+ max: 400,
1246
2358
  });
1247
2359
 
1248
2360
  makeDraggable(rightHandle, {
@@ -1251,7 +2363,7 @@ function initResize() {
1251
2363
  save: () => localStorage.setItem('kyp-right-w', rightW),
1252
2364
  invert: true,
1253
2365
  min: 200,
1254
- max: 500,
2366
+ max: 450,
1255
2367
  });
1256
2368
  }
1257
2369
 
@@ -1277,6 +2389,7 @@ async function init() {
1277
2389
  await Promise.all(promises);
1278
2390
 
1279
2391
  renderTree(treeData, document.getElementById('file-tree'));
2392
+ renderTagCloud(collectAllTags());
1280
2393
 
1281
2394
  document.getElementById('stats-bar').innerHTML = `
1282
2395
  <span><span class="stat-val">${stats.notes}</span> notes</span>
@@ -1287,6 +2400,7 @@ async function init() {
1287
2400
  }
1288
2401
 
1289
2402
  initResize();
2403
+ initGraphResize();
1290
2404
  init();
1291
2405
  </script>
1292
2406
  </body>