@omiron33/omi-neuron-web 0.1.4 → 0.1.6

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.
@@ -36,7 +36,7 @@ var DEFAULT_THEME = {
36
36
  edgeActive: "#c6d4ff",
37
37
  edgeSelected: "#ffffff",
38
38
  labelText: "#ffffff",
39
- labelBackground: "rgba(0, 0, 0, 0.8)"
39
+ labelBackground: "rgba(5, 6, 31, 0.8)"
40
40
  },
41
41
  typography: {
42
42
  labelFontFamily: "system-ui, sans-serif",
@@ -55,8 +55,8 @@ var DEFAULT_THEME = {
55
55
  edgeFlowSpeed: 1.2,
56
56
  fogEnabled: true,
57
57
  fogColor: "#020314",
58
- fogNear: 32,
59
- fogFar: 180
58
+ fogNear: 24,
59
+ fogFar: 160
60
60
  },
61
61
  animation: {
62
62
  focusDuration: 800,
@@ -74,8 +74,8 @@ var SceneManager = class {
74
74
  this.container = container;
75
75
  this.config = config;
76
76
  this.scene = new THREE__namespace.Scene();
77
- this.camera = new THREE__namespace.PerspectiveCamera(60, 1, 0.1, 2e3);
78
- this.renderer = new THREE__namespace.WebGLRenderer({ antialias: true });
77
+ this.camera = new THREE__namespace.PerspectiveCamera(config.cameraFov ?? 52, 1, 0.1, 220);
78
+ this.renderer = new THREE__namespace.WebGLRenderer({ antialias: true, alpha: true });
79
79
  this.labelRenderer = new CSS2DRenderer_js.CSS2DRenderer();
80
80
  this.controls = new OrbitControls_js.OrbitControls(this.camera, this.renderer.domElement);
81
81
  }
@@ -92,6 +92,7 @@ var SceneManager = class {
92
92
  ambientLight = null;
93
93
  keyLight = null;
94
94
  fillLight = null;
95
+ resizeObserver = null;
95
96
  initialize() {
96
97
  const { cameraPosition, cameraTarget, backgroundColor } = this.config;
97
98
  this.scene.background = new THREE__namespace.Color(backgroundColor);
@@ -106,9 +107,14 @@ var SceneManager = class {
106
107
  this.controls.update();
107
108
  this.renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, this.config.pixelRatioCap));
108
109
  this.renderer.outputColorSpace = THREE__namespace.SRGBColorSpace;
109
- this.renderer.toneMapping = THREE__namespace.ACESFilmicToneMapping;
110
- this.renderer.toneMappingExposure = 1.05;
110
+ this.renderer.toneMapping = THREE__namespace.NoToneMapping;
111
+ this.renderer.toneMappingExposure = 1;
111
112
  this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
113
+ this.renderer.domElement.style.position = "absolute";
114
+ this.renderer.domElement.style.top = "0";
115
+ this.renderer.domElement.style.left = "0";
116
+ this.renderer.domElement.style.width = "100%";
117
+ this.renderer.domElement.style.height = "100%";
112
118
  this.labelRenderer.setSize(this.container.clientWidth, this.container.clientHeight);
113
119
  this.labelRenderer.domElement.style.position = "absolute";
114
120
  this.labelRenderer.domElement.style.top = "0";
@@ -123,11 +129,17 @@ var SceneManager = class {
123
129
  this.initStarfield();
124
130
  }
125
131
  window.addEventListener("resize", this.resize);
132
+ if (typeof ResizeObserver !== "undefined") {
133
+ this.resizeObserver = new ResizeObserver(() => this.resize());
134
+ this.resizeObserver.observe(this.container);
135
+ }
126
136
  this.startAnimationLoop();
127
137
  }
128
138
  dispose() {
129
139
  this.stopAnimationLoop();
130
140
  window.removeEventListener("resize", this.resize);
141
+ this.resizeObserver?.disconnect();
142
+ this.resizeObserver = null;
131
143
  this.renderer.dispose();
132
144
  this.scene.clear();
133
145
  this.container.innerHTML = "";
@@ -165,6 +177,7 @@ var SceneManager = class {
165
177
  resize = () => {
166
178
  const width = this.container.clientWidth;
167
179
  const height = this.container.clientHeight;
180
+ if (!width || !height) return;
168
181
  this.camera.aspect = width / height;
169
182
  this.camera.updateProjectionMatrix();
170
183
  this.renderer.setSize(width, height);
@@ -219,20 +232,17 @@ var SceneManager = class {
219
232
  const geometry = new THREE__namespace.BufferGeometry();
220
233
  const positions = new Float32Array(count * 3);
221
234
  for (let i = 0; i < count; i += 1) {
222
- const radius = 180 + Math.random() * 120;
223
- const theta = Math.random() * Math.PI * 2;
224
- const phi = Math.acos(2 * Math.random() - 1);
225
- positions[i * 3] = radius * Math.sin(phi) * Math.cos(theta);
226
- positions[i * 3 + 1] = radius * Math.sin(phi) * Math.sin(theta);
227
- positions[i * 3 + 2] = radius * Math.cos(phi);
235
+ positions[i * 3] = THREE__namespace.MathUtils.randFloatSpread(70);
236
+ positions[i * 3 + 1] = THREE__namespace.MathUtils.randFloatSpread(70);
237
+ positions[i * 3 + 2] = THREE__namespace.MathUtils.randFloatSpread(70);
228
238
  }
229
239
  geometry.setAttribute("position", new THREE__namespace.BufferAttribute(positions, 3));
230
240
  const material = new THREE__namespace.PointsMaterial({
231
241
  color: this.config.starfieldColor,
232
- size: 0.6,
242
+ size: 0.22,
233
243
  sizeAttenuation: true,
234
244
  transparent: true,
235
- opacity: 0.45,
245
+ opacity: 0.5,
236
246
  depthWrite: false
237
247
  });
238
248
  this.starfield = new THREE__namespace.Points(geometry, material);
@@ -273,38 +283,53 @@ var NodeRenderer = class {
273
283
  this.scene = scene;
274
284
  this.config = config;
275
285
  this.scene.add(this.group);
286
+ this.glowTexture = this.createGlowTexture();
287
+ if (config.labelOffset) {
288
+ this.labelOffset.set(...config.labelOffset);
289
+ }
276
290
  }
277
291
  group = new THREE__namespace.Group();
278
292
  nodeStates = /* @__PURE__ */ new Map();
279
293
  hoveredNodeId = null;
280
294
  selectedNodeId = null;
295
+ glowTexture = null;
296
+ labelOffset = new THREE__namespace.Vector3(0, 0.65, 0);
281
297
  renderNodes(nodes) {
282
298
  this.clear();
299
+ const shouldRenderLabels = this.config.maxVisibleLabels > 0 && this.config.labelDistance > 0;
283
300
  nodes.forEach((node) => {
284
301
  const color = new THREE__namespace.Color(
285
302
  this.config.domainColors[node.domain] ?? this.config.defaultColor
286
303
  );
287
- const geometry = new THREE__namespace.SphereGeometry(this.config.baseScale, 18, 18);
288
- const material = new THREE__namespace.MeshStandardMaterial({
304
+ const material = new THREE__namespace.SpriteMaterial({
305
+ map: this.glowTexture ?? void 0,
289
306
  color,
290
- roughness: 0.45,
291
- metalness: 0.1,
292
- emissive: color.clone().multiplyScalar(this.config.glowIntensity * 0.4),
293
- emissiveIntensity: 1
307
+ transparent: true,
308
+ opacity: 0.78,
309
+ depthWrite: false
294
310
  });
295
- const mesh = new THREE__namespace.Mesh(geometry, material);
311
+ const sprite = new THREE__namespace.Sprite(material);
296
312
  const position = new THREE__namespace.Vector3();
297
313
  if (node.position) {
298
314
  position.set(...node.position);
299
315
  }
300
- mesh.position.copy(position);
316
+ sprite.position.copy(position);
301
317
  const tierScale = node.tier ? this.config.tierScales[node.tier] ?? 1 : 1;
302
318
  const baseScale = this.config.baseScale * tierScale;
303
- mesh.scale.setScalar(baseScale);
304
- mesh.userData = { nodeId: node.id, nodeSlug: node.slug };
305
- this.group.add(mesh);
319
+ sprite.scale.setScalar(baseScale);
320
+ sprite.userData = { nodeId: node.id, nodeSlug: node.slug };
321
+ this.group.add(sprite);
322
+ let labelObject = null;
323
+ if (shouldRenderLabels) {
324
+ const labelElement = this.createLabelElement(node, color);
325
+ labelObject = new CSS2DRenderer_js.CSS2DObject(labelElement);
326
+ labelObject.position.copy(sprite.position).add(this.labelOffset);
327
+ this.scene.add(labelObject);
328
+ }
306
329
  this.nodeStates.set(node.id, {
307
- mesh,
330
+ sprite,
331
+ material,
332
+ label: labelObject,
308
333
  basePosition: position.clone(),
309
334
  baseScale,
310
335
  phase: Math.random() * Math.PI * 2,
@@ -320,7 +345,7 @@ var NodeRenderer = class {
320
345
  if (!state) return;
321
346
  if (updates.position) {
322
347
  state.basePosition.set(...updates.position);
323
- state.mesh.position.set(...updates.position);
348
+ state.sprite.position.set(...updates.position);
324
349
  }
325
350
  if (updates.tier) {
326
351
  const tierScale = this.config.tierScales[updates.tier] ?? 1;
@@ -331,18 +356,24 @@ var NodeRenderer = class {
331
356
  this.config.domainColors[updates.domain] ?? this.config.defaultColor
332
357
  );
333
358
  state.baseColor = color;
334
- const material = state.mesh.material;
335
- material.color = color;
336
- material.emissive = color.clone().multiplyScalar(this.config.glowIntensity * 0.4);
359
+ state.material.color = color;
337
360
  }
338
361
  }
339
362
  removeNode(nodeId) {
340
363
  const state = this.nodeStates.get(nodeId);
341
364
  if (!state) return;
342
- this.group.remove(state.mesh);
365
+ if (state.label) {
366
+ this.scene.remove(state.label);
367
+ }
368
+ this.group.remove(state.sprite);
343
369
  this.nodeStates.delete(nodeId);
344
370
  }
345
371
  clear() {
372
+ this.nodeStates.forEach((state) => {
373
+ if (state.label) {
374
+ this.scene.remove(state.label);
375
+ }
376
+ });
346
377
  this.group.clear();
347
378
  this.nodeStates.clear();
348
379
  this.hoveredNodeId = null;
@@ -350,29 +381,130 @@ var NodeRenderer = class {
350
381
  }
351
382
  showNodes(nodeIds) {
352
383
  nodeIds.forEach((id) => {
353
- const obj = this.nodeObjects.get(id);
354
- if (obj) obj.visible = true;
384
+ const state = this.nodeStates.get(id);
385
+ if (state) state.sprite.visible = true;
355
386
  });
356
387
  }
357
388
  hideNodes(nodeIds) {
358
389
  nodeIds.forEach((id) => {
359
- const obj = this.nodeObjects.get(id);
360
- if (obj) obj.visible = false;
390
+ const state = this.nodeStates.get(id);
391
+ if (state) state.sprite.visible = false;
361
392
  });
362
393
  }
363
394
  setVisibleNodes(nodeIds) {
364
395
  if (!nodeIds) {
365
396
  this.nodeStates.forEach((state) => {
366
- state.mesh.visible = true;
397
+ state.sprite.visible = true;
367
398
  });
368
399
  return;
369
400
  }
370
401
  const visibleSet = new Set(nodeIds);
371
402
  this.nodeStates.forEach((state, id) => {
372
- state.mesh.visible = visibleSet.has(id);
403
+ state.sprite.visible = visibleSet.has(id);
373
404
  });
374
405
  }
375
- updateLabelVisibility() {
406
+ updateLabelVisibility(camera) {
407
+ if (this.config.maxVisibleLabels <= 0 || this.config.labelDistance <= 0) {
408
+ this.nodeStates.forEach((state) => {
409
+ if (state.label) state.label.visible = false;
410
+ });
411
+ return;
412
+ }
413
+ const entries = [];
414
+ this.nodeStates.forEach((state) => {
415
+ if (!state.label) return;
416
+ if (!state.sprite.visible) {
417
+ state.label.visible = false;
418
+ return;
419
+ }
420
+ const distance = camera.position.distanceTo(state.sprite.position);
421
+ entries.push({ state, distance });
422
+ });
423
+ entries.sort((a, b) => a.distance - b.distance);
424
+ entries.forEach((entry, index) => {
425
+ const visible = entry.distance <= this.config.labelDistance && index < this.config.maxVisibleLabels;
426
+ entry.state.label.visible = visible;
427
+ });
428
+ }
429
+ createLabelElement(node, accent) {
430
+ const wrapper = document.createElement("div");
431
+ wrapper.style.borderRadius = "10px";
432
+ wrapper.style.border = "1px solid rgba(255, 255, 255, 0.12)";
433
+ wrapper.style.background = this.config.labelBackground;
434
+ wrapper.style.color = this.config.labelTextColor;
435
+ wrapper.style.fontFamily = this.config.labelFontFamily;
436
+ wrapper.style.fontSize = `${this.config.labelFontSize}px`;
437
+ wrapper.style.fontWeight = this.config.labelFontWeight;
438
+ wrapper.style.padding = "6px 8px";
439
+ wrapper.style.boxShadow = "0 10px 30px rgba(5, 10, 20, 0.35)";
440
+ wrapper.style.backdropFilter = "blur(10px)";
441
+ wrapper.style.pointerEvents = "none";
442
+ wrapper.style.maxWidth = "220px";
443
+ const isInsight = node.tier === "insight" || node.domain === "insight";
444
+ if (isInsight) {
445
+ const accentBright = accent.clone().lerp(new THREE__namespace.Color("#ffffff"), 0.35);
446
+ wrapper.style.border = `1px solid ${this.toRgba(accentBright, 0.6)}`;
447
+ wrapper.style.background = this.toRgba(accent, 0.2);
448
+ wrapper.style.color = this.toRgba(accentBright, 0.95);
449
+ }
450
+ const badgeRow = document.createElement("div");
451
+ badgeRow.style.display = "flex";
452
+ badgeRow.style.flexWrap = "wrap";
453
+ badgeRow.style.gap = "4px";
454
+ badgeRow.style.marginBottom = "4px";
455
+ const makeBadge = (text, tone) => {
456
+ const badge = document.createElement("span");
457
+ badge.textContent = text;
458
+ badge.style.display = "inline-flex";
459
+ badge.style.alignItems = "center";
460
+ badge.style.gap = "4px";
461
+ badge.style.borderRadius = "999px";
462
+ badge.style.padding = "2px 6px";
463
+ badge.style.fontSize = "0.6rem";
464
+ badge.style.fontWeight = "600";
465
+ badge.style.textTransform = "uppercase";
466
+ badge.style.letterSpacing = "0.2em";
467
+ if (tone === "accent") {
468
+ badge.style.border = `1px solid ${this.toRgba(accent, 0.6)}`;
469
+ badge.style.background = this.toRgba(accent, 0.4);
470
+ badge.style.color = "#f5f7ff";
471
+ } else {
472
+ badge.style.border = "1px solid rgba(255, 255, 255, 0.2)";
473
+ badge.style.background = "rgba(255, 255, 255, 0.08)";
474
+ badge.style.color = "rgba(255, 255, 255, 0.8)";
475
+ }
476
+ return badge;
477
+ };
478
+ if (isInsight) {
479
+ badgeRow.appendChild(makeBadge("Insight", "accent"));
480
+ const statusRaw = node.metadata?.status ?? node.metadata?.draftNodeStatus ?? node.metadata?.studyPathStatus;
481
+ if (statusRaw) {
482
+ const formatted = statusRaw.replace(/[_-]/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
483
+ badgeRow.appendChild(makeBadge(formatted, "muted"));
484
+ }
485
+ }
486
+ if (badgeRow.childElementCount > 0) {
487
+ wrapper.appendChild(badgeRow);
488
+ }
489
+ const title = document.createElement("div");
490
+ title.textContent = node.label;
491
+ title.style.fontSize = "0.8rem";
492
+ title.style.fontWeight = "600";
493
+ wrapper.appendChild(title);
494
+ if (node.ref) {
495
+ const reference = document.createElement("div");
496
+ reference.textContent = node.ref;
497
+ reference.style.fontSize = "0.65rem";
498
+ reference.style.opacity = "0.7";
499
+ wrapper.appendChild(reference);
500
+ }
501
+ return wrapper;
502
+ }
503
+ toRgba(color, alpha) {
504
+ const r = Math.round(color.r * 255);
505
+ const g = Math.round(color.g * 255);
506
+ const b = Math.round(color.b * 255);
507
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
376
508
  }
377
509
  highlightNode(nodeId) {
378
510
  this.setHoveredNode(nodeId);
@@ -387,14 +519,14 @@ var NodeRenderer = class {
387
519
  }
388
520
  getNodePosition(nodeId) {
389
521
  const state = this.nodeStates.get(nodeId);
390
- return state ? state.mesh.position.clone() : null;
522
+ return state ? state.sprite.position.clone() : null;
391
523
  }
392
524
  getNodeObject(nodeId) {
393
525
  const state = this.nodeStates.get(nodeId);
394
- return state?.mesh ?? null;
526
+ return state?.sprite ?? null;
395
527
  }
396
528
  getNodeObjects() {
397
- return Array.from(this.nodeStates.values()).map((state) => state.mesh);
529
+ return Array.from(this.nodeStates.values()).map((state) => state.sprite);
398
530
  }
399
531
  setHoveredNode(nodeId) {
400
532
  if (this.hoveredNodeId === nodeId) return;
@@ -432,13 +564,16 @@ var NodeRenderer = class {
432
564
  const drift = Math.sin(elapsed * this.config.ambientMotionSpeed + state.phase) * this.config.ambientMotionAmplitude;
433
565
  const driftX = Math.cos(elapsed * this.config.ambientMotionSpeed * 0.6 + state.phase) * this.config.ambientMotionAmplitude * 0.45;
434
566
  const driftZ = Math.sin(elapsed * this.config.ambientMotionSpeed * 0.4 + state.phase) * this.config.ambientMotionAmplitude * 0.35;
435
- state.mesh.position.set(
567
+ state.sprite.position.set(
436
568
  state.basePosition.x + driftX,
437
569
  state.basePosition.y + drift,
438
570
  state.basePosition.z + driftZ
439
571
  );
440
572
  } else {
441
- state.mesh.position.copy(state.basePosition);
573
+ state.sprite.position.copy(state.basePosition);
574
+ }
575
+ if (state.label) {
576
+ state.label.position.copy(state.sprite.position).add(this.labelOffset);
442
577
  }
443
578
  const hoverScale = state.hovered ? this.config.hoverScale : 1;
444
579
  const selectedScale = state.selected ? this.config.selectedScale : 1;
@@ -452,17 +587,42 @@ var NodeRenderer = class {
452
587
  }
453
588
  }
454
589
  const targetScale = state.baseScale * hoverScale * selectedScale * (1 + pulseScale);
455
- const currentScale = state.mesh.scale.x;
590
+ const currentScale = state.sprite.scale.x;
456
591
  const nextScale = currentScale + (targetScale - currentScale) * 0.18;
457
- state.mesh.scale.setScalar(nextScale);
458
- const material = state.mesh.material;
459
- const emissiveBoost = state.selected ? 0.65 : state.hovered ? 0.45 : 0.25;
460
- material.emissive = state.baseColor.clone().multiplyScalar(this.config.glowIntensity * emissiveBoost);
592
+ state.sprite.scale.setScalar(nextScale);
593
+ const material = state.material;
594
+ const baseOpacity = 0.78;
595
+ const hoverOpacity = Math.min(0.95, baseOpacity + 0.12);
596
+ const selectedOpacity = 1;
597
+ material.opacity = state.selected ? selectedOpacity : state.hovered ? hoverOpacity : baseOpacity;
598
+ material.color.copy(state.baseColor);
599
+ if (state.selected) {
600
+ material.color.lerp(new THREE__namespace.Color("#ffffff"), 0.25);
601
+ }
461
602
  });
462
603
  }
463
604
  dispose() {
464
605
  this.clear();
465
606
  this.scene.remove(this.group);
607
+ this.glowTexture?.dispose();
608
+ this.glowTexture = null;
609
+ }
610
+ createGlowTexture() {
611
+ const size = 256;
612
+ const canvas = document.createElement("canvas");
613
+ canvas.width = size;
614
+ canvas.height = size;
615
+ const ctx = canvas.getContext("2d");
616
+ if (!ctx) return null;
617
+ const gradient = ctx.createRadialGradient(size / 2, size / 2, 0, size / 2, size / 2, size / 2);
618
+ gradient.addColorStop(0, "rgba(255,255,255,0.95)");
619
+ gradient.addColorStop(0.4, "rgba(255,255,255,0.45)");
620
+ gradient.addColorStop(1, "rgba(255,255,255,0)");
621
+ ctx.fillStyle = gradient;
622
+ ctx.fillRect(0, 0, size, size);
623
+ const texture = new THREE__namespace.CanvasTexture(canvas);
624
+ texture.colorSpace = THREE__namespace.SRGBColorSpace;
625
+ return texture;
466
626
  }
467
627
  };
468
628
  var EdgeRenderer = class {
@@ -586,6 +746,35 @@ var EdgeRenderer = class {
586
746
 
587
747
  // src/visualization/layouts/fuzzy-layout.ts
588
748
  var GOLDEN_ANGLE = Math.PI * (3 - Math.sqrt(5));
749
+ var ATLAS_POSITION_OVERRIDES = {
750
+ uap: [6, 2, 2],
751
+ ez1: [2, 4, 0],
752
+ neph: [-3, 3, 2],
753
+ jude6: [-1, 1.5, 2.5],
754
+ "2p24": [1.2, 0.3, 2.6],
755
+ aiimg: [4.6, -1.6, 1.8],
756
+ rev13: [2.7, -2.8, 0.6],
757
+ xhuman: [0.4, -3.4, -1.8],
758
+ dan243: [-1.6, -2.1, -2.9],
759
+ llm: [3.4, 0.3, -2.4],
760
+ babel: [0.9, 1.4, -3.4],
761
+ warfare: [-4.6, 0.4, -1.5],
762
+ eph612: [-5.5, -1.7, 0.5],
763
+ berea: [-0.2, 4.6, -1.3],
764
+ pharm: [-3.1, -2.4, 1.2],
765
+ testsp: [5.4, 3.2, 1.6]
766
+ };
767
+ function generateSpherePosition(index, total, radius) {
768
+ if (total <= 1) {
769
+ return [0, 0, 0];
770
+ }
771
+ const offset = 2 / total;
772
+ const increment = Math.PI * (3 - Math.sqrt(5));
773
+ const y = 1 - index * offset;
774
+ const r = Math.sqrt(Math.max(0, 1 - y * y));
775
+ const phi = index * increment;
776
+ return [Math.cos(phi) * r * radius, y * radius, Math.sin(phi) * r * radius];
777
+ }
589
778
  function hashString(input) {
590
779
  let hash = 2166136261;
591
780
  for (let i = 0; i < input.length; i += 1) {
@@ -607,17 +796,53 @@ function buildSeed(baseSeed, nodeKey) {
607
796
  return mulberry32(hashString(`${baseSeed}:${nodeKey}`));
608
797
  }
609
798
  function applyFuzzyLayout(nodes, options = {}) {
610
- const mode = options.mode ?? "auto";
799
+ const mode = options.mode ?? "atlas";
611
800
  if (mode === "positioned") {
612
801
  return nodes;
613
802
  }
614
- const needsLayout = mode === "fuzzy" || nodes.some((node) => !node.position);
615
- if (!needsLayout) {
803
+ const needsLayout = nodes.some((node) => !node.position);
804
+ if (mode === "auto" && !needsLayout) {
616
805
  return nodes;
617
806
  }
807
+ const spread = options.spread ?? 1;
808
+ const overrides = { ...ATLAS_POSITION_OVERRIDES, ...options.overrides ?? {} };
809
+ if (mode === "atlas" || mode === "auto") {
810
+ const baseRadius2 = (options.radius ?? 12) * spread;
811
+ const insightRadius = (options.insightRadius ?? Math.max(5, baseRadius2 * 0.4)) * spread;
812
+ const canonicalNodes = nodes.filter(
813
+ (node) => node.tier !== "insight" && node.domain !== "insight"
814
+ );
815
+ const insightNodes = nodes.filter(
816
+ (node) => node.tier === "insight" || node.domain === "insight"
817
+ );
818
+ const canonicalPositions = /* @__PURE__ */ new Map();
819
+ const insightPositions = /* @__PURE__ */ new Map();
820
+ canonicalNodes.forEach((node, index) => {
821
+ canonicalPositions.set(
822
+ node.id,
823
+ generateSpherePosition(index, canonicalNodes.length, baseRadius2)
824
+ );
825
+ });
826
+ insightNodes.forEach((node, index) => {
827
+ insightPositions.set(
828
+ node.id,
829
+ generateSpherePosition(index, insightNodes.length || 1, insightRadius)
830
+ );
831
+ });
832
+ return nodes.map((node) => {
833
+ const override = overrides[node.id] ?? overrides[node.slug];
834
+ if (node.position && !override) {
835
+ return node;
836
+ }
837
+ if (override) {
838
+ return { ...node, position: [...override] };
839
+ }
840
+ const fallback = (node.tier === "insight" || node.domain === "insight" ? insightPositions.get(node.id) : canonicalPositions.get(node.id)) ?? [0, 0, 0];
841
+ return { ...node, position: fallback };
842
+ });
843
+ }
618
844
  const baseSeed = options.seed ?? "omi-neuron-web";
619
845
  const count = Math.max(nodes.length, 1);
620
- const spread = options.spread ?? 1.2;
621
846
  const baseRadius = (options.radius ?? Math.max(4, Math.sqrt(count) * 2.4)) * spread;
622
847
  const jitter = (options.jitter ?? baseRadius * 0.12) * spread;
623
848
  const zSpread = (options.zSpread ?? baseRadius * 0.6) * spread;
@@ -820,6 +1045,8 @@ function NeuronWeb({
820
1045
  graphData,
821
1046
  className,
822
1047
  style,
1048
+ fullHeight,
1049
+ isFullScreen,
823
1050
  isLoading,
824
1051
  error,
825
1052
  renderEmptyState,
@@ -859,17 +1086,18 @@ function NeuronWeb({
859
1086
  }, [performanceMode, graphData.nodes.length]);
860
1087
  const sceneManager = useSceneManager(containerRef, {
861
1088
  backgroundColor: resolvedTheme.colors.background,
1089
+ cameraFov: 52,
862
1090
  cameraPosition: [4, 8, 20],
863
1091
  cameraTarget: [0, 0, 0],
864
1092
  minZoom: 4,
865
1093
  maxZoom: 42,
866
1094
  enableStarfield: resolvedTheme.effects.starfieldEnabled,
867
- starfieldCount: 1200,
1095
+ starfieldCount: resolvedPerformanceMode === "normal" ? 1200 : 700,
868
1096
  starfieldColor: resolvedTheme.effects.starfieldColor,
869
1097
  pixelRatioCap: 2,
870
- ambientLightIntensity: 0.7,
871
- keyLightIntensity: 1.1,
872
- fillLightIntensity: 0.6,
1098
+ ambientLightIntensity: 0.9,
1099
+ keyLightIntensity: 0.6,
1100
+ fillLightIntensity: 0.4,
873
1101
  fogEnabled: resolvedTheme.effects.fogEnabled,
874
1102
  fogColor: resolvedTheme.effects.fogColor,
875
1103
  fogNear: resolvedTheme.effects.fogNear,
@@ -880,16 +1108,22 @@ function NeuronWeb({
880
1108
  return new NodeRenderer(sceneManager.scene, {
881
1109
  domainColors: resolvedTheme.colors.domainColors,
882
1110
  defaultColor: resolvedTheme.colors.defaultDomainColor,
883
- baseScale: 0.4,
1111
+ baseScale: 1.15,
884
1112
  tierScales: {
885
- primary: 1.6,
886
- secondary: 1.2,
887
- tertiary: 1,
888
- insight: 1
1113
+ primary: 1.25,
1114
+ secondary: 1.1,
1115
+ tertiary: 0.95,
1116
+ insight: 1.05
889
1117
  },
890
1118
  glowIntensity: resolvedTheme.effects.glowEnabled ? resolvedTheme.effects.glowIntensity : 0,
891
- labelDistance: 20,
892
- maxVisibleLabels: 50,
1119
+ labelDistance: resolvedPerformanceMode === "normal" ? 26 : 0,
1120
+ maxVisibleLabels: resolvedPerformanceMode === "normal" ? 80 : 0,
1121
+ labelOffset: [0, 0.65, 0],
1122
+ labelFontFamily: resolvedTheme.typography.labelFontFamily,
1123
+ labelFontSize: resolvedTheme.typography.labelFontSize,
1124
+ labelFontWeight: resolvedTheme.typography.labelFontWeight,
1125
+ labelTextColor: resolvedTheme.colors.labelText,
1126
+ labelBackground: resolvedTheme.colors.labelBackground,
893
1127
  ambientMotionEnabled: resolvedTheme.effects.ambientMotionEnabled && resolvedPerformanceMode === "normal",
894
1128
  ambientMotionAmplitude: resolvedTheme.effects.ambientMotionAmplitude,
895
1129
  ambientMotionSpeed: resolvedTheme.effects.ambientMotionSpeed,
@@ -905,7 +1139,7 @@ function NeuronWeb({
905
1139
  defaultColor: resolvedTheme.colors.edgeDefault,
906
1140
  activeColor: resolvedTheme.colors.edgeActive,
907
1141
  selectedColor: resolvedTheme.colors.edgeSelected,
908
- baseOpacity: 0.5,
1142
+ baseOpacity: 0.45,
909
1143
  strengthOpacityScale: true,
910
1144
  edgeFlowEnabled: resolvedTheme.effects.edgeFlowEnabled && resolvedPerformanceMode === "normal",
911
1145
  edgeFlowSpeed: resolvedTheme.effects.edgeFlowSpeed
@@ -986,6 +1220,7 @@ function NeuronWeb({
986
1220
  if (!sceneManager || !nodeRenderer || !edgeRenderer) return;
987
1221
  return sceneManager.addFrameListener((delta, elapsed) => {
988
1222
  nodeRenderer.update(delta, elapsed);
1223
+ nodeRenderer.updateLabelVisibility(sceneManager.camera);
989
1224
  edgeRenderer.update(delta, elapsed);
990
1225
  animationController?.update();
991
1226
  if (hoverCardRef.current && hoveredNodeId) {
@@ -1106,14 +1341,30 @@ function NeuronWeb({
1106
1341
  if (!resolvedNodes.length) {
1107
1342
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style, "aria-label": ariaLabel, children: renderEmptyState ? renderEmptyState() : /* @__PURE__ */ jsxRuntime.jsx("div", { children: "No data" }) });
1108
1343
  }
1344
+ const resolvedStyle = {
1345
+ position: isFullScreen ? "fixed" : "relative",
1346
+ inset: isFullScreen ? 0 : void 0,
1347
+ width: isFullScreen ? "100vw" : "100%",
1348
+ height: isFullScreen ? "100vh" : "100%",
1349
+ minHeight: !isFullScreen && fullHeight ? "100vh" : void 0,
1350
+ overflow: "hidden",
1351
+ background: resolvedTheme.colors.background,
1352
+ ...style
1353
+ };
1109
1354
  return /* @__PURE__ */ jsxRuntime.jsxs(
1110
1355
  "div",
1111
1356
  {
1112
1357
  className,
1113
- style: { position: "relative", width: "100%", height: "100%", overflow: "hidden", ...style },
1358
+ style: resolvedStyle,
1114
1359
  "aria-label": ariaLabel,
1115
1360
  children: [
1116
- /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, style: { width: "100%", height: "100%" } }),
1361
+ /* @__PURE__ */ jsxRuntime.jsx(
1362
+ "div",
1363
+ {
1364
+ ref: containerRef,
1365
+ style: { position: "absolute", inset: 0, width: "100%", height: "100%" }
1366
+ }
1367
+ ),
1117
1368
  hoverCardEnabled && hoveredNode && /* @__PURE__ */ jsxRuntime.jsx(
1118
1369
  "div",
1119
1370
  {
@@ -1216,5 +1467,5 @@ exports.NeuronWeb = NeuronWeb;
1216
1467
  exports.SceneManager = SceneManager;
1217
1468
  exports.ThemeEngine = ThemeEngine;
1218
1469
  exports.applyFuzzyLayout = applyFuzzyLayout;
1219
- //# sourceMappingURL=chunk-KC7V76I3.cjs.map
1220
- //# sourceMappingURL=chunk-KC7V76I3.cjs.map
1470
+ //# sourceMappingURL=chunk-U7K7KQZG.cjs.map
1471
+ //# sourceMappingURL=chunk-U7K7KQZG.cjs.map