@visualli/core 0.1.7 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/README.md +37 -105
  2. package/dist/chunk-25AJTPMS.js +68 -0
  3. package/dist/chunk-25AJTPMS.js.map +1 -0
  4. package/dist/chunk-64PUANNH.js +151 -0
  5. package/dist/chunk-64PUANNH.js.map +1 -0
  6. package/dist/chunk-ITDZ72JE.js +96 -0
  7. package/dist/chunk-ITDZ72JE.js.map +1 -0
  8. package/dist/{chunk-PDP5CVUL.js → chunk-LOQLNYB3.js} +2 -2
  9. package/dist/chunk-N7HCYULH.js +12 -0
  10. package/dist/chunk-N7HCYULH.js.map +1 -0
  11. package/dist/{chunk-I75TMOE4.js → chunk-P6PKWVYF.js} +2 -2
  12. package/dist/chunk-RFMUATWB.js +753 -0
  13. package/dist/chunk-RFMUATWB.js.map +1 -0
  14. package/dist/chunk-UAMEJG2C.js +267 -0
  15. package/dist/chunk-UAMEJG2C.js.map +1 -0
  16. package/dist/{chunk-LJYSKSCE.js → chunk-W2CAOOJJ.js} +6 -6
  17. package/dist/chunk-W2CAOOJJ.js.map +1 -0
  18. package/dist/{chunk-6NXADJK2.js → chunk-WO4JAAKP.js} +84 -117
  19. package/dist/chunk-WO4JAAKP.js.map +1 -0
  20. package/dist/config/index.cjs +250 -349
  21. package/dist/config/index.cjs.map +1 -1
  22. package/dist/config/index.d.cts +107 -66
  23. package/dist/config/index.d.ts +107 -66
  24. package/dist/config/index.js +52 -34
  25. package/dist/constants/index.cjs +14 -333
  26. package/dist/constants/index.cjs.map +1 -1
  27. package/dist/constants/index.d.cts +5 -243
  28. package/dist/constants/index.d.ts +5 -243
  29. package/dist/constants/index.js +5 -75
  30. package/dist/designSystem-D49xVfr8.d.cts +150 -0
  31. package/dist/designSystem-D49xVfr8.d.ts +150 -0
  32. package/dist/document-BFBwn93X.d.ts +26 -0
  33. package/dist/document-MEJEfxl3.d.cts +26 -0
  34. package/dist/ideaSize-BTJu6thN.d.cts +11 -0
  35. package/dist/ideaSize-RKbms8K6.d.ts +11 -0
  36. package/dist/index-CcF0Jvmb.d.ts +99 -0
  37. package/dist/index-Dt--T2l8.d.cts +99 -0
  38. package/dist/index.cjs +1636 -961
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.cts +350 -27
  41. package/dist/index.d.ts +350 -27
  42. package/dist/index.js +434 -179
  43. package/dist/index.js.map +1 -1
  44. package/dist/{document-B3hnNr-F.d.cts → layer-B2A3Ki48.d.cts} +2 -16
  45. package/dist/{document-B3hnNr-F.d.ts → layer-B2A3Ki48.d.ts} +2 -16
  46. package/dist/layout/index.d.cts +2 -1
  47. package/dist/layout/index.d.ts +2 -1
  48. package/dist/mindmap-BO3YD5qu.d.cts +182 -0
  49. package/dist/mindmap-D0Of0jaZ.d.ts +182 -0
  50. package/dist/parser/index.cjs +745 -179
  51. package/dist/parser/index.cjs.map +1 -1
  52. package/dist/parser/index.d.cts +23 -8
  53. package/dist/parser/index.d.ts +23 -8
  54. package/dist/parser/index.js +7 -2
  55. package/dist/performance/index.cjs +13 -0
  56. package/dist/performance/index.cjs.map +1 -1
  57. package/dist/performance/index.js +2 -2
  58. package/dist/theme/index.cjs +884 -0
  59. package/dist/theme/index.cjs.map +1 -0
  60. package/dist/theme/index.d.cts +2 -0
  61. package/dist/theme/index.d.ts +2 -0
  62. package/dist/theme/index.js +53 -0
  63. package/dist/types/index.d.cts +4 -2
  64. package/dist/types/index.d.ts +4 -2
  65. package/dist/viewport/index.cjs +17 -6
  66. package/dist/viewport/index.cjs.map +1 -1
  67. package/dist/viewport/index.d.cts +6 -4
  68. package/dist/viewport/index.d.ts +6 -4
  69. package/dist/viewport/index.js +2 -2
  70. package/licenses/visualli-design-system/LICENSE +9 -0
  71. package/licenses/visualli-design-system/NOTICE.md +6 -0
  72. package/package.json +12 -4
  73. package/src/config/README.md +2 -4
  74. package/src/config/USAGE_EXAMPLES.md +0 -4
  75. package/src/config/index.ts +3 -4
  76. package/src/constants/index.ts +1 -1
  77. package/src/constants/layout.ts +11 -0
  78. package/src/constants/performanceConstants.ts +8 -27
  79. package/src/generated/README.md +12 -0
  80. package/src/generated/designSystem.ts +758 -0
  81. package/src/generated/geometry/blob.ts +99 -0
  82. package/src/generated/geometry/blobShapes.ts +4 -0
  83. package/src/generated/geometry/color.ts +37 -0
  84. package/src/generated/geometry/container.ts +84 -0
  85. package/src/generated/geometry/detail.ts +22 -0
  86. package/src/generated/geometry/idea.ts +56 -0
  87. package/src/generated/geometry/interaction.ts +122 -0
  88. package/src/generated/geometry/motion.ts +140 -0
  89. package/src/index.ts +18 -1
  90. package/src/parser/configUtils.ts +12 -19
  91. package/src/parser/visualliConverter.ts +71 -112
  92. package/src/parser/visualliParser.ts +16 -1
  93. package/src/rendering/culling.ts +16 -12
  94. package/src/rendering/ideaSize.ts +21 -0
  95. package/src/rendering/nodeGeometry.ts +97 -0
  96. package/src/theme/index.ts +136 -0
  97. package/src/types/document.ts +9 -0
  98. package/src/types/layer.ts +1 -1
  99. package/src/types/mindmap.ts +13 -0
  100. package/src/utils/navigation.ts +33 -63
  101. package/src/viewport/viewportUtils.ts +6 -4
  102. package/dist/animations/index.cjs +0 -146
  103. package/dist/animations/index.cjs.map +0 -1
  104. package/dist/animations/index.d.cts +0 -81
  105. package/dist/animations/index.d.ts +0 -81
  106. package/dist/animations/index.js +0 -35
  107. package/dist/chunk-6NXADJK2.js.map +0 -1
  108. package/dist/chunk-F36SMC3K.js +0 -106
  109. package/dist/chunk-F36SMC3K.js.map +0 -1
  110. package/dist/chunk-G5H3P4YZ.js +0 -273
  111. package/dist/chunk-G5H3P4YZ.js.map +0 -1
  112. package/dist/chunk-LDJRGT2Q.js +0 -163
  113. package/dist/chunk-LDJRGT2Q.js.map +0 -1
  114. package/dist/chunk-LJYSKSCE.js.map +0 -1
  115. package/dist/chunk-LNTJTDB2.js +0 -349
  116. package/dist/chunk-LNTJTDB2.js.map +0 -1
  117. package/dist/mindmap-DWS0WZT_.d.cts +0 -118
  118. package/dist/mindmap-DWS0WZT_.d.ts +0 -118
  119. package/src/animations/constants.ts +0 -72
  120. package/src/animations/easing.ts +0 -70
  121. package/src/animations/index.ts +0 -2
  122. package/src/config/blobShapes.ts +0 -146
  123. package/src/config/textScaling.ts +0 -54
  124. package/src/constants/design.ts +0 -281
  125. /package/dist/{chunk-PDP5CVUL.js.map → chunk-LOQLNYB3.js.map} +0 -0
  126. /package/dist/{chunk-I75TMOE4.js.map → chunk-P6PKWVYF.js.map} +0 -0
  127. /package/dist/{animations → theme}/index.js.map +0 -0
package/dist/index.cjs CHANGED
@@ -30,40 +30,21 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- ACTIVE_BLOB_TYPES: () => ACTIVE_BLOB_TYPES,
34
- ALL_BLOB_SHAPES: () => ALL_BLOB_SHAPES,
35
- ANIMATION_DURATION: () => ANIMATION_DURATION,
36
- ANIMATION_PHASES: () => ANIMATION_PHASES,
37
- BLOB_LAYER_CONFIG: () => BLOB_LAYER_CONFIG,
38
- BLOB_TEXT_OFFSETS: () => BLOB_TEXT_OFFSETS,
39
- BORDER_WIDTH_NORMAL: () => BORDER_WIDTH_NORMAL,
40
- BORDER_WIDTH_SELECTED: () => BORDER_WIDTH_SELECTED,
41
- BRAND_COLORS: () => BRAND_COLORS,
42
- CANVAS_COLORS: () => CANVAS_COLORS,
43
- CHAR_WIDTH: () => CHAR_WIDTH,
33
+ BLOB_SHAPES: () => BLOB_SHAPES,
34
+ CANVAS_STYLE: () => CANVAS_STYLE,
44
35
  CIRCULAR_REFERENCE_MAX_DEPTH: () => CIRCULAR_REFERENCE_MAX_DEPTH,
45
36
  CLICK_THRESHOLD: () => CLICK_THRESHOLD,
46
- COLOR_BORDER_DARK: () => COLOR_BORDER_DARK,
47
- COLOR_BORDER_LIGHT: () => COLOR_BORDER_LIGHT,
48
- COLOR_DEFAULT_NODE: () => COLOR_DEFAULT_NODE,
49
- COLOR_SELECTED: () => COLOR_SELECTED,
50
- CONTAINER_LABEL_BASE_FONT_PX: () => CONTAINER_LABEL_BASE_FONT_PX,
51
- COOLDOWN: () => COOLDOWN,
52
- CORNER_RADIUS: () => CORNER_RADIUS,
53
37
  CULLING_QUERY_TIME_BUDGET: () => CULLING_QUERY_TIME_BUDGET,
38
+ CUSTOM: () => CUSTOM,
54
39
  DEFAULT_CANVAS_HEIGHT: () => DEFAULT_CANVAS_HEIGHT,
55
40
  DEFAULT_CANVAS_WIDTH: () => DEFAULT_CANVAS_WIDTH,
56
41
  DEFAULT_RENDER_CONFIG: () => DEFAULT_RENDER_CONFIG,
57
- DESCRIPTION_TEXT_BASE_FONT_PX: () => DESCRIPTION_TEXT_BASE_FONT_PX,
42
+ DESIGN_SYSTEM_VERSION: () => DESIGN_SYSTEM_VERSION,
43
+ DETAIL: () => DETAIL,
58
44
  DOUBLE_CLICK_DELAY: () => DOUBLE_CLICK_DELAY,
59
45
  DRAG_START_DISTANCE: () => DRAG_START_DISTANCE,
60
- DS_COLORS: () => DS_COLORS,
61
- DS_RADII: () => DS_RADII,
62
- DS_TYPOGRAPHY: () => DS_TYPOGRAPHY,
63
- EDGE_LABEL_BASE_FONT_PX: () => EDGE_LABEL_BASE_FONT_PX,
46
+ EASE: () => EASE,
64
47
  FEATURE_FLAGS: () => FEATURE_FLAGS,
65
- FONTS: () => FONTS,
66
- FONT_SIZES: () => FONT_SIZES,
67
48
  FPSMonitor: () => FPSMonitor,
68
49
  FPS_CRITICAL_THRESHOLD: () => FPS_CRITICAL_THRESHOLD,
69
50
  FPS_DOWNGRADE_THRESHOLD: () => FPS_DOWNGRADE_THRESHOLD,
@@ -74,27 +55,29 @@ __export(index_exports, {
74
55
  FPS_UPDATE_INTERVAL: () => FPS_UPDATE_INTERVAL,
75
56
  FPS_UPGRADE_THRESHOLD: () => FPS_UPGRADE_THRESHOLD,
76
57
  FPS_WARNING_THRESHOLD: () => FPS_WARNING_THRESHOLD,
58
+ FRAME: () => FRAME,
59
+ GESTURE: () => GESTURE,
60
+ HULL: () => HULL,
61
+ HULL_CLEAR: () => HULL_CLEAR,
62
+ IDEA: () => IDEA,
63
+ LABEL_GROWTH: () => LABEL_GROWTH,
77
64
  LAYOUT_SPACING: () => LAYOUT_SPACING,
78
- LEVEL_COLORS: () => LEVEL_COLORS,
79
- LEVEL_COLOR_ARRAY: () => LEVEL_COLOR_ARRAY,
80
65
  MAX_FRAME_TIME: () => MAX_FRAME_TIME,
81
66
  MAX_NODES: () => MAX_NODES,
67
+ MAX_RINGS: () => MAX_RINGS,
82
68
  MAX_VISIBLE_NODES: () => MAX_VISIBLE_NODES,
83
69
  MEMORY_CHECK_INTERVAL: () => MEMORY_CHECK_INTERVAL,
84
70
  MEMORY_CRITICAL_MB: () => MEMORY_CRITICAL_MB,
85
71
  MEMORY_LIMITS: () => MEMORY_LIMITS,
86
72
  MEMORY_TARGET_MB: () => MEMORY_TARGET_MB,
87
73
  MEMORY_WARNING_MB: () => MEMORY_WARNING_MB,
74
+ METRICS: () => METRICS,
75
+ METRICS_REDUCED_MOTION: () => METRICS_REDUCED_MOTION,
76
+ MOTION: () => MOTION,
88
77
  MemoryMonitor: () => MemoryMonitor,
89
78
  NODE_COUNT_THRESHOLDS: () => NODE_COUNT_THRESHOLDS,
90
- NODE_HEIGHT: () => NODE_HEIGHT,
91
- NODE_LAYER_CONFIG: () => NODE_LAYER_CONFIG,
92
- NODE_MIN_WIDTH: () => NODE_MIN_WIDTH,
93
- NODE_PADDING: () => NODE_PADDING,
94
- NODE_TEXT_BASE_FONT_PX: () => NODE_TEXT_BASE_FONT_PX,
95
- OPACITY: () => OPACITY,
96
- OVERLAY_FONT_UPPER_BOOST_PX: () => OVERLAY_FONT_UPPER_BOOST_PX,
97
- PARENT_FALLBACK_COLORS: () => PARENT_FALLBACK_COLORS,
79
+ PEEK: () => PEEK,
80
+ PEEK_PLACEMENT: () => PEEK_PLACEMENT,
98
81
  PERFORMANCE_TOAST_COOLDOWN: () => PERFORMANCE_TOAST_COOLDOWN,
99
82
  PerformanceProfiler: () => PerformanceProfiler,
100
83
  QUALITY_HIGH: () => QUALITY_HIGH,
@@ -106,33 +89,37 @@ __export(index_exports, {
106
89
  RBushSpatialIndex: () => RBushSpatialIndex,
107
90
  RENDER_MODES: () => RENDER_MODES,
108
91
  RESIZE_DEBOUNCE: () => RESIZE_DEBOUNCE,
92
+ RINGS: () => RINGS,
109
93
  SCROLL_THROTTLE: () => SCROLL_THROTTLE,
110
94
  SEARCH_DEBOUNCE: () => SEARCH_DEBOUNCE,
111
- SEMANTIC_COLORS: () => SEMANTIC_COLORS,
95
+ SHAPE: () => SHAPE,
96
+ SHEET: () => SHEET,
112
97
  SIMPLIFIED_RENDER_CONFIG: () => SIMPLIFIED_RENDER_CONFIG,
113
- SPACING: () => SPACING,
114
98
  SPATIAL_INDEX_BUILD_BUDGET: () => SPATIAL_INDEX_BUILD_BUDGET,
115
99
  TARGET_FRAME_TIME: () => TARGET_FRAME_TIME,
116
- TARGET_ZOOM: () => TARGET_ZOOM,
117
- TEXT_LABEL_HIDE_BELOW_ZOOM: () => TEXT_LABEL_HIDE_BELOW_ZOOM,
118
- THEME_COLORS: () => THEME_COLORS,
100
+ TERM: () => TERM,
101
+ THEME_NAMES: () => THEME_NAMES,
119
102
  TIMING_THRESHOLDS: () => TIMING_THRESHOLDS,
120
- TITLE_MAX_LENGTH: () => TITLE_MAX_LENGTH,
103
+ TOKENS: () => TOKENS,
104
+ TOPICS: () => TOPICS,
105
+ TOPIC_ORDER: () => TOPIC_ORDER,
106
+ TRAIL: () => TRAIL,
107
+ TYPE_STYLES: () => TYPE_STYLES,
108
+ VIEW: () => VIEW,
121
109
  VIEWPORT_CONFIG: () => VIEWPORT_CONFIG,
122
- VIEWPORT_FRAMING: () => VIEWPORT_FRAMING,
123
110
  VIEWPORT_MARGIN: () => VIEWPORT_MARGIN,
124
111
  VisualliParseError: () => VisualliParseError,
125
112
  ZOOM_DEFAULT: () => ZOOM_DEFAULT,
126
113
  ZOOM_MAX: () => ZOOM_MAX,
127
114
  ZOOM_MIN: () => ZOOM_MIN,
128
- ZOOM_NAV_IN_THRESHOLD: () => ZOOM_NAV_IN_THRESHOLD,
129
- ZOOM_NAV_OUT_THRESHOLD: () => ZOOM_NAV_OUT_THRESHOLD,
130
- ZOOM_STEP: () => ZOOM_STEP,
131
- ZOOM_THRESHOLDS: () => ZOOM_THRESHOLDS,
132
- ZOOM_WHEEL_SENSITIVITY: () => ZOOM_WHEEL_SENSITIVITY,
133
115
  applyCircularLayout: () => applyCircularLayout,
134
116
  applyLinearHorizontalLayout: () => applyLinearHorizontalLayout,
135
117
  applyLinearVerticalLayout: () => applyLinearVerticalLayout,
118
+ approximateMeasure: () => approximateMeasure,
119
+ arrowPath: () => arrowPath,
120
+ blobPath: () => blobPath,
121
+ blobRadius: () => blobRadius,
122
+ bloomOffset: () => bloomOffset,
136
123
  calculateDistance: () => calculateDistance,
137
124
  calculateFitCenter: () => calculateFitCenter,
138
125
  calculateFitView: () => calculateFitView,
@@ -142,32 +129,26 @@ __export(index_exports, {
142
129
  calculateViewportBounds: () => calculateViewportBounds,
143
130
  checkCollision: () => checkCollision,
144
131
  clampZoom: () => clampZoom,
145
- computeEdgeLabelScale: () => computeEdgeLabelScale,
146
- computeNodeTextScreenScale: () => computeNodeTextScreenScale,
147
- computeNodeTextWorldScale: () => computeNodeTextWorldScale,
148
- computeOverlayScale: () => computeOverlayScale,
132
+ connectorDelay: () => connectorDelay,
133
+ connectorGeometry: () => connectorGeometry,
134
+ connectorSamples: () => connectorSamples,
135
+ containerHull: () => containerHull,
149
136
  convertLayerToFlatNodes: () => convertLayerToFlatNodes,
150
137
  convertVisualliToFlatNodes: () => convertVisualliToFlatNodes,
151
138
  countLayersBeneath: () => countLayersBeneath,
152
- createCubicBezier: () => createCubicBezier,
153
139
  createMindMapConfig: () => createMindMapConfig,
154
- darkenHexColor: () => darkenHexColor,
155
- drawBlobPath: () => drawBlobPath,
156
- easeInCubic: () => easeInCubic,
157
- easeInOutCubic: () => easeInOutCubic,
158
- easeInOutQuart: () => easeInOutQuart,
159
- easeInOutSine: () => easeInOutSine,
160
- easeOutCubic: () => easeOutCubic,
161
- easeOutQuad: () => easeOutQuad,
162
- easeZoomTransition: () => easeZoomTransition,
140
+ cssEase: () => cssEase,
141
+ customColor: () => customColor,
142
+ drawBlob: () => drawBlob,
143
+ easeBezier: () => ease,
144
+ edgePath: () => edgePath,
163
145
  fetchAndParseVisualli: () => fetchAndParseVisualli,
146
+ fitView: () => fitView,
164
147
  flattenNodes: () => flattenNodes,
165
148
  focusOnNode: () => focusOnNode,
166
149
  generateSampleConfig: () => generateSampleConfig,
167
- getBlobTypeForLayer: () => getBlobTypeForLayer,
168
150
  getChildLayerForNode: () => getChildLayerForNode,
169
151
  getChildLayers: () => getChildLayers,
170
- getColorForLevel: () => getColorForLevel,
171
152
  getConnectionsForLayer: () => getConnectionsForLayer,
172
153
  getContainersForLayer: () => getContainersForLayer,
173
154
  getLayerForNavigation: () => getLayerForNavigation,
@@ -177,27 +158,70 @@ __export(index_exports, {
177
158
  getOptimalQualityLevel: () => getOptimalQualityLevel,
178
159
  getRecommendedRenderConfig: () => getRecommendedRenderConfig,
179
160
  getRenderConfigForQuality: () => getRenderConfigForQuality,
180
- getThemeBackground: () => getThemeBackground,
181
- getThemeBorder: () => getThemeBorder,
182
- getThemeText: () => getThemeText,
161
+ getSemanticAnchors: () => getSemanticAnchors,
162
+ getTokens: () => getTokens,
183
163
  getTopLevelConnections: () => getTopLevelConnections,
184
164
  getViewportVisibleNodes: () => getViewportVisibleNodes,
185
165
  getVisibleNodes: () => getVisibleNodes,
166
+ hoverOpensTerm: () => hoverOpensTerm,
167
+ ideaColor: () => ideaColor,
168
+ ideaDetail: () => ideaDetail,
169
+ ideaKindOf: () => ideaKindOf,
170
+ ideaSize: () => ideaSize,
171
+ ideaStyle: () => ideaStyle,
172
+ intersectsViewport: () => intersectsViewport,
173
+ isContrastTheme: () => isContrastTheme,
174
+ isDarkTheme: () => isDarkTheme,
186
175
  isFeatureEnabled: () => isFeatureEnabled,
176
+ isFocusTheme: () => isFocusTheme,
187
177
  isNodeInViewport: () => isNodeInViewport,
178
+ isThemeName: () => isThemeName,
179
+ labelCandidates: () => labelCandidates,
180
+ labelGrowth: () => labelGrowth,
181
+ labelScale: () => labelScale,
182
+ layerBlooms: () => layerBlooms,
183
+ layerBounds: () => layerBounds,
184
+ layerFrame: () => layerFrame,
185
+ layerReveal: () => layerReveal,
188
186
  loadVisualliFileFromFile: () => loadVisualliFileFromFile,
187
+ metricsFor: () => metricsFor,
188
+ motionVars: () => motionVars,
189
+ nodeBounds: () => nodeBounds,
190
+ nodeRadii: () => nodeRadii,
189
191
  optimizeLayout: () => optimizeLayout,
192
+ outlineRadius: () => outlineRadius,
190
193
  panViewport: () => panViewport,
191
194
  parseVisualliFile: () => parseVisualliFile,
192
195
  parseVisualliFromMongoDB: () => parseVisualliFromMongoDB,
196
+ peekPosition: () => peekPosition,
197
+ pinchZoom: () => pinchZoom,
198
+ placeContainerLabel: () => placeContainerLabel,
199
+ pointerModeFor: () => pointerModeFor,
193
200
  recalculateViewportBounds: () => recalculateViewportBounds,
194
201
  renderConfigStore: () => renderConfigStore,
195
202
  resolveCollisions: () => resolveCollisions,
203
+ resolveComfort: () => resolveComfort,
196
204
  resolveNodeOverlaps: () => resolveNodeOverlaps,
205
+ resolveTheme: () => resolveTheme,
206
+ revealDelay: () => revealDelay,
207
+ revealSlot: () => revealSlot,
208
+ revealTotal: () => revealTotal,
209
+ ringCount: () => ringCount,
210
+ ringsFor: () => ringsFor,
197
211
  screenToWorld: () => screenToWorld,
198
212
  selectionStore: () => selectionStore,
199
213
  setViewportCenter: () => setViewportCenter,
214
+ shadowsShown: () => shadowsShown,
215
+ shapeForLevel: () => shapeForLevel,
216
+ shapeOfLevel: () => shapeOfLevel,
217
+ swapDelay: () => swapDelay,
218
+ token: () => token,
219
+ topicFor: () => topicFor,
220
+ topicFromName: () => topicFromName,
221
+ topicStyle: () => topicStyle,
200
222
  worldToScreen: () => worldToScreen,
223
+ wrapLines: () => wrapLines,
224
+ zoomAround: () => zoomAround,
201
225
  zoomViewport: () => zoomViewport
202
226
  });
203
227
  module.exports = __toCommonJS(index_exports);
@@ -315,9 +339,9 @@ function resolveCollisions(nodes, maxIterations = 10) {
315
339
  const b1 = getNodeBounds(n1);
316
340
  const b2 = getNodeBounds(n2);
317
341
  const minSep = (b1.width + b2.width) / 2;
318
- const overlap = minSep - dist + 20;
319
- if (overlap > 0) {
320
- const move = overlap / 2;
342
+ const overlap2 = minSep - dist + 20;
343
+ if (overlap2 > 0) {
344
+ const move = overlap2 / 2;
321
345
  n1.x -= dx / dist * move;
322
346
  n1.y -= dy / dist * move;
323
347
  n2.x += dx / dist * move;
@@ -431,6 +455,13 @@ function parseVisualliFile(content) {
431
455
  }
432
456
  break;
433
457
  }
458
+ case "extension": {
459
+ const id = obj["id"];
460
+ if (typeof id === "string") {
461
+ (doc.extensions ?? (doc.extensions = {}))[id] = Array.isArray(obj["data"]) ? obj["data"] : [];
462
+ }
463
+ break;
464
+ }
434
465
  default:
435
466
  break;
436
467
  }
@@ -468,6 +499,11 @@ function loadVisualliFileFromFile(file) {
468
499
  reader.readAsText(file);
469
500
  });
470
501
  }
502
+ function getSemanticAnchors(doc) {
503
+ const raw = doc.extensions?.["semantic-anchors"];
504
+ if (!Array.isArray(raw)) return [];
505
+ return raw.filter((a) => !!a && typeof a.word === "string" && typeof a.description === "string");
506
+ }
471
507
  function getChildLayers(doc, nodeId) {
472
508
  const result = [];
473
509
  for (const layer of doc.layers.values()) {
@@ -491,6 +527,874 @@ function countLayersBeneath(doc, nodeId, visited = /* @__PURE__ */ new Set()) {
491
527
  return maxDepth;
492
528
  }
493
529
 
530
+ // src/generated/designSystem.ts
531
+ var DESIGN_SYSTEM_VERSION = "0.2.1";
532
+ var THEME_NAMES = [
533
+ "light",
534
+ "dark",
535
+ "focus-light",
536
+ "focus-dark",
537
+ "colorsafe-light",
538
+ "colorsafe-dark",
539
+ "contrast-light",
540
+ "contrast-dark"
541
+ ];
542
+ var TOKENS = {
543
+ "light": {
544
+ "font-display": '"Atkinson Hyperlegible Next", system-ui, sans-serif',
545
+ "font-ui": '"Atkinson Hyperlegible Next", system-ui, -apple-system, "Segoe UI", sans-serif',
546
+ "font-hand": 'Kalam, "Atkinson Hyperlegible Next", cursive',
547
+ "font-note": "Caveat, Kalam, cursive",
548
+ "font-mono": '"Atkinson Hyperlegible Mono", ui-monospace, "SF Mono", Menlo, monospace',
549
+ "canvas": "#f6f3ee",
550
+ "surface": "#ffffff",
551
+ "surface-sunken": "#f0ece5",
552
+ "surface-overlay": "#ffffff",
553
+ "line": "#e4dfd6",
554
+ "line-strong": "#7a7f94",
555
+ "ink": "#182140",
556
+ "ink-muted": "#4f5671",
557
+ "ink-subtle": "#5f647c",
558
+ "action": "#182140",
559
+ "on-action": "#ffffff",
560
+ "focus": "#325e8c",
561
+ "link": "#2f5a87",
562
+ "selection": "rgba(18, 199, 211, 0.28)",
563
+ "node-ink": "#182140",
564
+ "edge": "#2a3048",
565
+ "edge-label": "#4f5671",
566
+ "topic-teal": "#87f2f8",
567
+ "topic-teal-ring": "#00cdd5",
568
+ "topic-harbor": "#bcdbff",
569
+ "topic-harbor-ring": "#76b2f5",
570
+ "topic-iris": "#e0c8fe",
571
+ "topic-iris-ring": "#bd96ea",
572
+ "topic-berry": "#ffbdc4",
573
+ "topic-berry-ring": "#f47e8f",
574
+ "topic-coral": "#ffc7b2",
575
+ "topic-coral-ring": "#f38e65",
576
+ "topic-amber": "#ffcf9d",
577
+ "topic-amber-ring": "#eb9c38",
578
+ "topic-sun": "#ffea94",
579
+ "topic-sun-ring": "#dfbd04",
580
+ "topic-stone": "#e0dacf",
581
+ "topic-stone-ring": "#bab09f",
582
+ "space-1": "4px",
583
+ "space-2": "8px",
584
+ "space-3": "12px",
585
+ "space-4": "16px",
586
+ "space-7": "48px",
587
+ "radius-sm": "10px",
588
+ "radius-md": "16px",
589
+ "radius-lg": "24px",
590
+ "radius-pill": "999px",
591
+ "shadow-float": "0 1px 2px rgba(24, 33, 64, 0.06), 0 8px 24px rgba(24, 33, 64, 0.08)",
592
+ "shadow-pop": "0 2px 4px rgba(24, 33, 64, 0.08), 0 16px 40px rgba(24, 33, 64, 0.14)",
593
+ "node-width": "200px",
594
+ "node-height": "148px",
595
+ "node-stroke": "3px",
596
+ "edge-width": "2.5px",
597
+ "edge-gap": "15px",
598
+ "edge-arc-max": "45px",
599
+ "immersion-alpha": "0.15",
600
+ "zoom-min": "0.3",
601
+ "zoom-max": "5",
602
+ "duration-instant": "100ms",
603
+ "duration-quick": "160ms",
604
+ "duration-base": "240ms",
605
+ "duration-zoom": "500ms",
606
+ "duration-reveal": "420ms",
607
+ "stagger-reveal": "70ms",
608
+ "ease-zoom": "cubic-bezier(0.2, 0.8, 0.2, 1)",
609
+ "ease-standard": "cubic-bezier(0.4, 0, 0.2, 1)",
610
+ "ease-bloom": "cubic-bezier(0.34, 1.4, 0.64, 1)",
611
+ "z-chrome": "10",
612
+ "z-card": "20",
613
+ "z-menu": "40"
614
+ },
615
+ "dark": {
616
+ "font-display": '"Atkinson Hyperlegible Next", system-ui, sans-serif',
617
+ "font-ui": '"Atkinson Hyperlegible Next", system-ui, -apple-system, "Segoe UI", sans-serif',
618
+ "font-hand": 'Kalam, "Atkinson Hyperlegible Next", cursive',
619
+ "font-note": "Caveat, Kalam, cursive",
620
+ "font-mono": '"Atkinson Hyperlegible Mono", ui-monospace, "SF Mono", Menlo, monospace',
621
+ "canvas": "#0f1220",
622
+ "surface": "#171b2b",
623
+ "surface-sunken": "#0f1220",
624
+ "surface-overlay": "#212639",
625
+ "line": "#2b3148",
626
+ "line-strong": "#6c7392",
627
+ "ink": "#eef0f7",
628
+ "ink-muted": "#b3b8cc",
629
+ "ink-subtle": "#8f95ae",
630
+ "action": "#f6f3ee",
631
+ "on-action": "#182140",
632
+ "focus": "#12c7d3",
633
+ "link": "#5fd8e1",
634
+ "selection": "rgba(18, 199, 211, 0.32)",
635
+ "node-ink": "#182140",
636
+ "edge": "#c9cddc",
637
+ "edge-label": "#b3b8cc",
638
+ "topic-teal": "#87f2f8",
639
+ "topic-teal-ring": "#0fc5cd",
640
+ "topic-harbor": "#bcdbff",
641
+ "topic-harbor-ring": "#73aceb",
642
+ "topic-iris": "#e0c8fe",
643
+ "topic-iris-ring": "#b691e1",
644
+ "topic-berry": "#ffbdc4",
645
+ "topic-berry-ring": "#eb798a",
646
+ "topic-coral": "#ffc7b2",
647
+ "topic-coral-ring": "#ea8962",
648
+ "topic-amber": "#ffcf9d",
649
+ "topic-amber-ring": "#e29737",
650
+ "topic-sun": "#ffea94",
651
+ "topic-sun-ring": "#d8b70a",
652
+ "topic-stone": "#e0dacf",
653
+ "topic-stone-ring": "#b4a999",
654
+ "space-1": "4px",
655
+ "space-2": "8px",
656
+ "space-3": "12px",
657
+ "space-4": "16px",
658
+ "space-7": "48px",
659
+ "radius-sm": "10px",
660
+ "radius-md": "16px",
661
+ "radius-lg": "24px",
662
+ "radius-pill": "999px",
663
+ "shadow-float": "0 1px 2px rgba(0, 0, 0, 0.40), 0 8px 24px rgba(0, 0, 0, 0.35)",
664
+ "shadow-pop": "0 2px 4px rgba(0, 0, 0, 0.50), 0 16px 40px rgba(0, 0, 0, 0.50)",
665
+ "node-width": "200px",
666
+ "node-height": "148px",
667
+ "node-stroke": "3px",
668
+ "edge-width": "2.5px",
669
+ "edge-gap": "15px",
670
+ "edge-arc-max": "45px",
671
+ "immersion-alpha": "0.15",
672
+ "zoom-min": "0.3",
673
+ "zoom-max": "5",
674
+ "duration-instant": "100ms",
675
+ "duration-quick": "160ms",
676
+ "duration-base": "240ms",
677
+ "duration-zoom": "500ms",
678
+ "duration-reveal": "420ms",
679
+ "stagger-reveal": "70ms",
680
+ "ease-zoom": "cubic-bezier(0.2, 0.8, 0.2, 1)",
681
+ "ease-standard": "cubic-bezier(0.4, 0, 0.2, 1)",
682
+ "ease-bloom": "cubic-bezier(0.34, 1.4, 0.64, 1)",
683
+ "z-chrome": "10",
684
+ "z-card": "20",
685
+ "z-menu": "40"
686
+ },
687
+ "focus-light": {
688
+ "font-display": '"Atkinson Hyperlegible Next", system-ui, sans-serif',
689
+ "font-ui": '"Atkinson Hyperlegible Next", system-ui, -apple-system, "Segoe UI", sans-serif',
690
+ "font-hand": 'Kalam, "Atkinson Hyperlegible Next", cursive',
691
+ "font-note": "Caveat, Kalam, cursive",
692
+ "font-mono": '"Atkinson Hyperlegible Mono", ui-monospace, "SF Mono", Menlo, monospace',
693
+ "canvas": "#f4f2ee",
694
+ "surface": "#ffffff",
695
+ "surface-sunken": "#f0ece5",
696
+ "surface-overlay": "#ffffff",
697
+ "line": "#e4dfd6",
698
+ "line-strong": "#7a7f94",
699
+ "ink": "#182140",
700
+ "ink-muted": "#4f5671",
701
+ "ink-subtle": "#5f647c",
702
+ "action": "#182140",
703
+ "on-action": "#ffffff",
704
+ "focus": "#325e8c",
705
+ "link": "#2f5a87",
706
+ "selection": "rgba(18, 199, 211, 0.28)",
707
+ "node-ink": "#182140",
708
+ "edge": "#5a6079",
709
+ "edge-label": "#5a6079",
710
+ "topic-teal": "#bce8ea",
711
+ "topic-teal-ring": "#67c6cb",
712
+ "topic-harbor": "#c9d9ec",
713
+ "topic-harbor-ring": "#8bb2de",
714
+ "topic-iris": "#d9cfe8",
715
+ "topic-iris-ring": "#b79ed4",
716
+ "topic-berry": "#edc6c9",
717
+ "topic-berry-ring": "#da9099",
718
+ "topic-coral": "#eecec1",
719
+ "topic-coral-ring": "#db9b82",
720
+ "topic-amber": "#eed4ba",
721
+ "topic-amber-ring": "#d6a570",
722
+ "topic-sun": "#f4eac1",
723
+ "topic-sun-ring": "#d2bf73",
724
+ "topic-stone": "#dddad5",
725
+ "topic-stone-ring": "#bab09f",
726
+ "space-1": "4px",
727
+ "space-2": "8px",
728
+ "space-3": "12px",
729
+ "space-4": "16px",
730
+ "space-7": "48px",
731
+ "radius-sm": "10px",
732
+ "radius-md": "16px",
733
+ "radius-lg": "24px",
734
+ "radius-pill": "999px",
735
+ "shadow-float": "0 1px 2px rgba(24, 33, 64, 0.06), 0 8px 24px rgba(24, 33, 64, 0.08)",
736
+ "shadow-pop": "0 2px 4px rgba(24, 33, 64, 0.08), 0 16px 40px rgba(24, 33, 64, 0.14)",
737
+ "node-width": "200px",
738
+ "node-height": "148px",
739
+ "node-stroke": "3px",
740
+ "edge-width": "2.5px",
741
+ "edge-gap": "15px",
742
+ "edge-arc-max": "45px",
743
+ "immersion-alpha": "0.15",
744
+ "zoom-min": "0.3",
745
+ "zoom-max": "5",
746
+ "duration-instant": "100ms",
747
+ "duration-quick": "160ms",
748
+ "duration-base": "240ms",
749
+ "duration-zoom": "500ms",
750
+ "duration-reveal": "420ms",
751
+ "stagger-reveal": "70ms",
752
+ "ease-zoom": "cubic-bezier(0.2, 0.8, 0.2, 1)",
753
+ "ease-standard": "cubic-bezier(0.4, 0, 0.2, 1)",
754
+ "ease-bloom": "cubic-bezier(0.34, 1.4, 0.64, 1)",
755
+ "z-chrome": "10",
756
+ "z-card": "20",
757
+ "z-menu": "40"
758
+ },
759
+ "focus-dark": {
760
+ "font-display": '"Atkinson Hyperlegible Next", system-ui, sans-serif',
761
+ "font-ui": '"Atkinson Hyperlegible Next", system-ui, -apple-system, "Segoe UI", sans-serif',
762
+ "font-hand": 'Kalam, "Atkinson Hyperlegible Next", cursive',
763
+ "font-note": "Caveat, Kalam, cursive",
764
+ "font-mono": '"Atkinson Hyperlegible Mono", ui-monospace, "SF Mono", Menlo, monospace',
765
+ "canvas": "#12141c",
766
+ "surface": "#171b2b",
767
+ "surface-sunken": "#0f1220",
768
+ "surface-overlay": "#212639",
769
+ "line": "#2b3148",
770
+ "line-strong": "#6c7392",
771
+ "ink": "#eef0f7",
772
+ "ink-muted": "#b3b8cc",
773
+ "ink-subtle": "#8f95ae",
774
+ "action": "#f6f3ee",
775
+ "on-action": "#182140",
776
+ "focus": "#12c7d3",
777
+ "link": "#5fd8e1",
778
+ "selection": "rgba(18, 199, 211, 0.32)",
779
+ "node-ink": "#182140",
780
+ "edge": "#a9aec0",
781
+ "edge-label": "#b3b8cc",
782
+ "topic-teal": "#bce8ea",
783
+ "topic-teal-ring": "#71bdc1",
784
+ "topic-harbor": "#c9d9ec",
785
+ "topic-harbor-ring": "#8cabce",
786
+ "topic-iris": "#d9cfe8",
787
+ "topic-iris-ring": "#ae9bc6",
788
+ "topic-berry": "#edc6c9",
789
+ "topic-berry-ring": "#cc8e95",
790
+ "topic-coral": "#eecec1",
791
+ "topic-coral-ring": "#cd9884",
792
+ "topic-amber": "#eed4ba",
793
+ "topic-amber-ring": "#c9a176",
794
+ "topic-sun": "#f4eac1",
795
+ "topic-sun-ring": "#c9b97a",
796
+ "topic-stone": "#dddad5",
797
+ "topic-stone-ring": "#b4a999",
798
+ "space-1": "4px",
799
+ "space-2": "8px",
800
+ "space-3": "12px",
801
+ "space-4": "16px",
802
+ "space-7": "48px",
803
+ "radius-sm": "10px",
804
+ "radius-md": "16px",
805
+ "radius-lg": "24px",
806
+ "radius-pill": "999px",
807
+ "shadow-float": "0 1px 2px rgba(24, 33, 64, 0.06), 0 8px 24px rgba(24, 33, 64, 0.08)",
808
+ "shadow-pop": "0 2px 4px rgba(24, 33, 64, 0.08), 0 16px 40px rgba(24, 33, 64, 0.14)",
809
+ "node-width": "200px",
810
+ "node-height": "148px",
811
+ "node-stroke": "3px",
812
+ "edge-width": "2.5px",
813
+ "edge-gap": "15px",
814
+ "edge-arc-max": "45px",
815
+ "immersion-alpha": "0.15",
816
+ "zoom-min": "0.3",
817
+ "zoom-max": "5",
818
+ "duration-instant": "100ms",
819
+ "duration-quick": "160ms",
820
+ "duration-base": "240ms",
821
+ "duration-zoom": "500ms",
822
+ "duration-reveal": "420ms",
823
+ "stagger-reveal": "70ms",
824
+ "ease-zoom": "cubic-bezier(0.2, 0.8, 0.2, 1)",
825
+ "ease-standard": "cubic-bezier(0.4, 0, 0.2, 1)",
826
+ "ease-bloom": "cubic-bezier(0.34, 1.4, 0.64, 1)",
827
+ "z-chrome": "10",
828
+ "z-card": "20",
829
+ "z-menu": "40"
830
+ },
831
+ "colorsafe-light": {
832
+ "font-display": '"Atkinson Hyperlegible Next", system-ui, sans-serif',
833
+ "font-ui": '"Atkinson Hyperlegible Next", system-ui, -apple-system, "Segoe UI", sans-serif',
834
+ "font-hand": 'Kalam, "Atkinson Hyperlegible Next", cursive',
835
+ "font-note": "Caveat, Kalam, cursive",
836
+ "font-mono": '"Atkinson Hyperlegible Mono", ui-monospace, "SF Mono", Menlo, monospace',
837
+ "canvas": "#f6f3ee",
838
+ "surface": "#ffffff",
839
+ "surface-sunken": "#f0ece5",
840
+ "surface-overlay": "#ffffff",
841
+ "line": "#e4dfd6",
842
+ "line-strong": "#7a7f94",
843
+ "ink": "#182140",
844
+ "ink-muted": "#4f5671",
845
+ "ink-subtle": "#5f647c",
846
+ "action": "#182140",
847
+ "on-action": "#ffffff",
848
+ "focus": "#325e8c",
849
+ "link": "#2f5a87",
850
+ "selection": "rgba(18, 199, 211, 0.28)",
851
+ "node-ink": "#182140",
852
+ "edge": "#2a3048",
853
+ "edge-label": "#4f5671",
854
+ "topic-teal": "#b0ecbc",
855
+ "topic-teal-ring": "#6cc982",
856
+ "topic-harbor": "#abdbfe",
857
+ "topic-harbor-ring": "#60b4ec",
858
+ "topic-iris": "#f6bde2",
859
+ "topic-iris-ring": "#dc87c0",
860
+ "topic-berry": "#feb79e",
861
+ "topic-berry-ring": "#ec7f58",
862
+ "topic-coral": "#fece96",
863
+ "topic-coral-ring": "#e49d41",
864
+ "topic-amber": "#b2e8fa",
865
+ "topic-amber-ring": "#5cc4e2",
866
+ "topic-sun": "#f6f097",
867
+ "topic-sun-ring": "#d0c637",
868
+ "topic-stone": "#dbdbdb",
869
+ "topic-stone-ring": "#b7afb1",
870
+ "space-1": "4px",
871
+ "space-2": "8px",
872
+ "space-3": "12px",
873
+ "space-4": "16px",
874
+ "space-7": "48px",
875
+ "radius-sm": "10px",
876
+ "radius-md": "16px",
877
+ "radius-lg": "24px",
878
+ "radius-pill": "999px",
879
+ "shadow-float": "0 1px 2px rgba(24, 33, 64, 0.06), 0 8px 24px rgba(24, 33, 64, 0.08)",
880
+ "shadow-pop": "0 2px 4px rgba(24, 33, 64, 0.08), 0 16px 40px rgba(24, 33, 64, 0.14)",
881
+ "node-width": "200px",
882
+ "node-height": "148px",
883
+ "node-stroke": "3px",
884
+ "edge-width": "2.5px",
885
+ "edge-gap": "15px",
886
+ "edge-arc-max": "45px",
887
+ "immersion-alpha": "0.15",
888
+ "zoom-min": "0.3",
889
+ "zoom-max": "5",
890
+ "duration-instant": "100ms",
891
+ "duration-quick": "160ms",
892
+ "duration-base": "240ms",
893
+ "duration-zoom": "500ms",
894
+ "duration-reveal": "420ms",
895
+ "stagger-reveal": "70ms",
896
+ "ease-zoom": "cubic-bezier(0.2, 0.8, 0.2, 1)",
897
+ "ease-standard": "cubic-bezier(0.4, 0, 0.2, 1)",
898
+ "ease-bloom": "cubic-bezier(0.34, 1.4, 0.64, 1)",
899
+ "z-chrome": "10",
900
+ "z-card": "20",
901
+ "z-menu": "40"
902
+ },
903
+ "colorsafe-dark": {
904
+ "font-display": '"Atkinson Hyperlegible Next", system-ui, sans-serif',
905
+ "font-ui": '"Atkinson Hyperlegible Next", system-ui, -apple-system, "Segoe UI", sans-serif',
906
+ "font-hand": 'Kalam, "Atkinson Hyperlegible Next", cursive',
907
+ "font-note": "Caveat, Kalam, cursive",
908
+ "font-mono": '"Atkinson Hyperlegible Mono", ui-monospace, "SF Mono", Menlo, monospace',
909
+ "canvas": "#0f1220",
910
+ "surface": "#171b2b",
911
+ "surface-sunken": "#0f1220",
912
+ "surface-overlay": "#212639",
913
+ "line": "#2b3148",
914
+ "line-strong": "#6c7392",
915
+ "ink": "#eef0f7",
916
+ "ink-muted": "#b3b8cc",
917
+ "ink-subtle": "#8f95ae",
918
+ "action": "#f6f3ee",
919
+ "on-action": "#182140",
920
+ "focus": "#12c7d3",
921
+ "link": "#5fd8e1",
922
+ "selection": "rgba(18, 199, 211, 0.32)",
923
+ "node-ink": "#182140",
924
+ "edge": "#c9cddc",
925
+ "edge-label": "#b3b8cc",
926
+ "topic-teal": "#b0ecbc",
927
+ "topic-teal-ring": "#69c27e",
928
+ "topic-harbor": "#abdbfe",
929
+ "topic-harbor-ring": "#5eade2",
930
+ "topic-iris": "#f6bde2",
931
+ "topic-iris-ring": "#d383b9",
932
+ "topic-berry": "#feb79e",
933
+ "topic-berry-ring": "#e37a55",
934
+ "topic-coral": "#fece96",
935
+ "topic-coral-ring": "#db9840",
936
+ "topic-amber": "#b2e8fa",
937
+ "topic-amber-ring": "#5bbdda",
938
+ "topic-sun": "#f6f097",
939
+ "topic-sun-ring": "#c9bf37",
940
+ "topic-stone": "#dbdbdb",
941
+ "topic-stone-ring": "#b1a8aa",
942
+ "space-1": "4px",
943
+ "space-2": "8px",
944
+ "space-3": "12px",
945
+ "space-4": "16px",
946
+ "space-7": "48px",
947
+ "radius-sm": "10px",
948
+ "radius-md": "16px",
949
+ "radius-lg": "24px",
950
+ "radius-pill": "999px",
951
+ "shadow-float": "0 1px 2px rgba(24, 33, 64, 0.06), 0 8px 24px rgba(24, 33, 64, 0.08)",
952
+ "shadow-pop": "0 2px 4px rgba(24, 33, 64, 0.08), 0 16px 40px rgba(24, 33, 64, 0.14)",
953
+ "node-width": "200px",
954
+ "node-height": "148px",
955
+ "node-stroke": "3px",
956
+ "edge-width": "2.5px",
957
+ "edge-gap": "15px",
958
+ "edge-arc-max": "45px",
959
+ "immersion-alpha": "0.15",
960
+ "zoom-min": "0.3",
961
+ "zoom-max": "5",
962
+ "duration-instant": "100ms",
963
+ "duration-quick": "160ms",
964
+ "duration-base": "240ms",
965
+ "duration-zoom": "500ms",
966
+ "duration-reveal": "420ms",
967
+ "stagger-reveal": "70ms",
968
+ "ease-zoom": "cubic-bezier(0.2, 0.8, 0.2, 1)",
969
+ "ease-standard": "cubic-bezier(0.4, 0, 0.2, 1)",
970
+ "ease-bloom": "cubic-bezier(0.34, 1.4, 0.64, 1)",
971
+ "z-chrome": "10",
972
+ "z-card": "20",
973
+ "z-menu": "40"
974
+ },
975
+ "contrast-light": {
976
+ "font-display": '"Atkinson Hyperlegible Next", system-ui, sans-serif',
977
+ "font-ui": '"Atkinson Hyperlegible Next", system-ui, -apple-system, "Segoe UI", sans-serif',
978
+ "font-hand": 'Kalam, "Atkinson Hyperlegible Next", cursive',
979
+ "font-note": "Caveat, Kalam, cursive",
980
+ "font-mono": '"Atkinson Hyperlegible Mono", ui-monospace, "SF Mono", Menlo, monospace',
981
+ "canvas": "#ffffff",
982
+ "surface": "#ffffff",
983
+ "surface-sunken": "#f2f2f2",
984
+ "surface-overlay": "#ffffff",
985
+ "line": "#5c6178",
986
+ "line-strong": "#000000",
987
+ "ink": "#000000",
988
+ "ink-muted": "#1f2433",
989
+ "ink-subtle": "#3a3f52",
990
+ "action": "#000000",
991
+ "on-action": "#ffffff",
992
+ "focus": "#0b4a8f",
993
+ "link": "#0b4a8f",
994
+ "selection": "rgba(18, 199, 211, 0.28)",
995
+ "node-ink": "#000000",
996
+ "edge": "#000000",
997
+ "edge-label": "#1f2433",
998
+ "topic-teal": "#87f2f8",
999
+ "topic-teal-ring": "#006b70",
1000
+ "topic-harbor": "#bcdbff",
1001
+ "topic-harbor-ring": "#0e599d",
1002
+ "topic-iris": "#e0c8fe",
1003
+ "topic-iris-ring": "#683b92",
1004
+ "topic-berry": "#ffbdc4",
1005
+ "topic-berry-ring": "#96173a",
1006
+ "topic-coral": "#ffc7b2",
1007
+ "topic-coral-ring": "#923400",
1008
+ "topic-amber": "#ffcf9d",
1009
+ "topic-amber-ring": "#7e4d00",
1010
+ "topic-sun": "#ffea94",
1011
+ "topic-sun-ring": "#7a6600",
1012
+ "topic-stone": "#e0dacf",
1013
+ "topic-stone-ring": "#63594b",
1014
+ "space-1": "4px",
1015
+ "space-2": "8px",
1016
+ "space-3": "12px",
1017
+ "space-4": "16px",
1018
+ "space-7": "48px",
1019
+ "radius-sm": "10px",
1020
+ "radius-md": "16px",
1021
+ "radius-lg": "24px",
1022
+ "radius-pill": "999px",
1023
+ "shadow-float": "none",
1024
+ "shadow-pop": "none",
1025
+ "node-width": "200px",
1026
+ "node-height": "148px",
1027
+ "node-stroke": "3px",
1028
+ "edge-width": "2.5px",
1029
+ "edge-gap": "15px",
1030
+ "edge-arc-max": "45px",
1031
+ "immersion-alpha": "0.15",
1032
+ "zoom-min": "0.3",
1033
+ "zoom-max": "5",
1034
+ "duration-instant": "100ms",
1035
+ "duration-quick": "160ms",
1036
+ "duration-base": "240ms",
1037
+ "duration-zoom": "500ms",
1038
+ "duration-reveal": "420ms",
1039
+ "stagger-reveal": "70ms",
1040
+ "ease-zoom": "cubic-bezier(0.2, 0.8, 0.2, 1)",
1041
+ "ease-standard": "cubic-bezier(0.4, 0, 0.2, 1)",
1042
+ "ease-bloom": "cubic-bezier(0.34, 1.4, 0.64, 1)",
1043
+ "z-chrome": "10",
1044
+ "z-card": "20",
1045
+ "z-menu": "40"
1046
+ },
1047
+ "contrast-dark": {
1048
+ "font-display": '"Atkinson Hyperlegible Next", system-ui, sans-serif',
1049
+ "font-ui": '"Atkinson Hyperlegible Next", system-ui, -apple-system, "Segoe UI", sans-serif',
1050
+ "font-hand": 'Kalam, "Atkinson Hyperlegible Next", cursive',
1051
+ "font-note": "Caveat, Kalam, cursive",
1052
+ "font-mono": '"Atkinson Hyperlegible Mono", ui-monospace, "SF Mono", Menlo, monospace',
1053
+ "canvas": "#000000",
1054
+ "surface": "#000000",
1055
+ "surface-sunken": "#0d0d0d",
1056
+ "surface-overlay": "#0d0d0d",
1057
+ "line": "#9aa0b8",
1058
+ "line-strong": "#ffffff",
1059
+ "ink": "#ffffff",
1060
+ "ink-muted": "#e6e8f0",
1061
+ "ink-subtle": "#c8ccda",
1062
+ "action": "#ffffff",
1063
+ "on-action": "#000000",
1064
+ "focus": "#5fe8f0",
1065
+ "link": "#7ff0f5",
1066
+ "selection": "rgba(18, 199, 211, 0.32)",
1067
+ "node-ink": "#000000",
1068
+ "edge": "#ffffff",
1069
+ "edge-label": "#e6e8f0",
1070
+ "topic-teal": "#87f2f8",
1071
+ "topic-teal-ring": "#64eff7",
1072
+ "topic-harbor": "#bcdbff",
1073
+ "topic-harbor-ring": "#b0d5fe",
1074
+ "topic-iris": "#e0c8fe",
1075
+ "topic-iris-ring": "#dcbfff",
1076
+ "topic-berry": "#ffbdc4",
1077
+ "topic-berry-ring": "#feb3bb",
1078
+ "topic-coral": "#ffc7b2",
1079
+ "topic-coral-ring": "#ffbea5",
1080
+ "topic-amber": "#ffcf9d",
1081
+ "topic-amber-ring": "#ffc78a",
1082
+ "topic-sun": "#ffea94",
1083
+ "topic-sun-ring": "#ffe26d",
1084
+ "topic-stone": "#e0dacf",
1085
+ "topic-stone-ring": "#ddd3c2",
1086
+ "space-1": "4px",
1087
+ "space-2": "8px",
1088
+ "space-3": "12px",
1089
+ "space-4": "16px",
1090
+ "space-7": "48px",
1091
+ "radius-sm": "10px",
1092
+ "radius-md": "16px",
1093
+ "radius-lg": "24px",
1094
+ "radius-pill": "999px",
1095
+ "shadow-float": "none",
1096
+ "shadow-pop": "none",
1097
+ "node-width": "200px",
1098
+ "node-height": "148px",
1099
+ "node-stroke": "3px",
1100
+ "edge-width": "2.5px",
1101
+ "edge-gap": "15px",
1102
+ "edge-arc-max": "45px",
1103
+ "immersion-alpha": "0.15",
1104
+ "zoom-min": "0.3",
1105
+ "zoom-max": "5",
1106
+ "duration-instant": "100ms",
1107
+ "duration-quick": "160ms",
1108
+ "duration-base": "240ms",
1109
+ "duration-zoom": "500ms",
1110
+ "duration-reveal": "420ms",
1111
+ "stagger-reveal": "70ms",
1112
+ "ease-zoom": "cubic-bezier(0.2, 0.8, 0.2, 1)",
1113
+ "ease-standard": "cubic-bezier(0.4, 0, 0.2, 1)",
1114
+ "ease-bloom": "cubic-bezier(0.34, 1.4, 0.64, 1)",
1115
+ "z-chrome": "10",
1116
+ "z-card": "20",
1117
+ "z-menu": "40"
1118
+ }
1119
+ };
1120
+ var TOPICS = [
1121
+ "teal",
1122
+ "harbor",
1123
+ "iris",
1124
+ "berry",
1125
+ "coral",
1126
+ "amber",
1127
+ "sun",
1128
+ "stone"
1129
+ ];
1130
+ var METRICS = {
1131
+ "space1": 4,
1132
+ "space2": 8,
1133
+ "space3": 12,
1134
+ "space4": 16,
1135
+ "space7": 48,
1136
+ "radiusSm": 10,
1137
+ "radiusMd": 16,
1138
+ "radiusLg": 24,
1139
+ "radiusPill": 999,
1140
+ "nodeWidth": 200,
1141
+ "nodeHeight": 148,
1142
+ "nodeStroke": 3,
1143
+ "edgeWidth": 2.5,
1144
+ "edgeGap": 15,
1145
+ "edgeArcMax": 45,
1146
+ "immersionAlpha": 0.15,
1147
+ "zoomMin": 0.3,
1148
+ "zoomMax": 5,
1149
+ "durationInstant": 100,
1150
+ "durationQuick": 160,
1151
+ "durationBase": 240,
1152
+ "durationZoom": 500,
1153
+ "durationReveal": 420,
1154
+ "staggerReveal": 70,
1155
+ "zChrome": 10,
1156
+ "zCard": 20,
1157
+ "zMenu": 40
1158
+ };
1159
+ var METRICS_REDUCED_MOTION = {
1160
+ "durationInstant": 120,
1161
+ "durationQuick": 0,
1162
+ "durationBase": 0,
1163
+ "durationZoom": 0,
1164
+ "durationReveal": 0,
1165
+ "staggerReveal": 0
1166
+ };
1167
+ var TYPE_STYLES = {
1168
+ "heading": {
1169
+ "family": "font-ui",
1170
+ "size": 17,
1171
+ "lineHeight": 24,
1172
+ "weight": 700
1173
+ },
1174
+ "body": {
1175
+ "family": "font-ui",
1176
+ "size": 15,
1177
+ "lineHeight": 22,
1178
+ "weight": 400
1179
+ },
1180
+ "body-sm": {
1181
+ "family": "font-ui",
1182
+ "size": 13,
1183
+ "lineHeight": 18,
1184
+ "weight": 400
1185
+ },
1186
+ "label": {
1187
+ "family": "font-ui",
1188
+ "size": 13,
1189
+ "lineHeight": 16,
1190
+ "weight": 600,
1191
+ "letterSpacing": 0.01
1192
+ },
1193
+ "caption": {
1194
+ "family": "font-ui",
1195
+ "size": 12,
1196
+ "lineHeight": 16,
1197
+ "weight": 400
1198
+ },
1199
+ "node-root": {
1200
+ "family": "font-hand",
1201
+ "size": 30,
1202
+ "lineHeight": 34,
1203
+ "weight": 700
1204
+ },
1205
+ "node-label": {
1206
+ "family": "font-hand",
1207
+ "size": 22,
1208
+ "lineHeight": 26,
1209
+ "weight": 700
1210
+ },
1211
+ "edge-label": {
1212
+ "family": "font-note",
1213
+ "size": 18,
1214
+ "lineHeight": 20,
1215
+ "weight": 500
1216
+ },
1217
+ "annotation": {
1218
+ "family": "font-note",
1219
+ "size": 24,
1220
+ "lineHeight": 26,
1221
+ "weight": 600
1222
+ }
1223
+ };
1224
+ var CANVAS_STYLE = {
1225
+ "node": {
1226
+ "selectedStrokeWidth": 4,
1227
+ "hoverRingsRotate": -2,
1228
+ "hoverRingsScale": 1.015,
1229
+ "hoverLift": -2,
1230
+ "dimmedOpacity": 1,
1231
+ "dimmedOpacityFocus": 0.4,
1232
+ "dimmedOpacityTouch": 0.6,
1233
+ "dimmedOpacityFocusTouch": 0.4,
1234
+ "shadowLight": "0 6px 14px rgba(24, 33, 64, .10)",
1235
+ "shadowDark": "0 0 22px rgba(255, 255, 255, .10)"
1236
+ },
1237
+ "edge": {
1238
+ "dash": [
1239
+ 7,
1240
+ 7
1241
+ ],
1242
+ "labelWeight": 500,
1243
+ "labelSize": 18,
1244
+ "readableLabelSize": 14,
1245
+ "readableLabelWeight": 600,
1246
+ "labelHaloWidth": 5
1247
+ },
1248
+ "group": {
1249
+ "strokeWidth": 1.5,
1250
+ "dash": [
1251
+ 10,
1252
+ 6
1253
+ ],
1254
+ "labelWeight": 700,
1255
+ "labelLineHeight": 1.1,
1256
+ "labelSize": 26,
1257
+ "labelPadding": {
1258
+ "top": 6,
1259
+ "x": 22,
1260
+ "bottom": 8
1261
+ },
1262
+ "labelMaxWidth": 420,
1263
+ "labelBorderWidth": 1.5,
1264
+ "labelFill": "topic-stone",
1265
+ "labelBorder": "topic-stone-ring",
1266
+ "labelInk": "node-ink",
1267
+ "contrastLabelBorderWidth": 2,
1268
+ "contrastLabelBorder": "line-strong"
1269
+ }
1270
+ };
1271
+
1272
+ // src/generated/geometry/color.ts
1273
+ var TOPIC_ORDER = ["teal", "harbor", "iris", "berry", "coral", "amber", "sun", "stone"];
1274
+ function topicFromName(color) {
1275
+ if (!color) return null;
1276
+ const c = color.trim().toLowerCase();
1277
+ return TOPIC_ORDER.includes(c) ? c : null;
1278
+ }
1279
+ function topicFor(color, siblingIndex) {
1280
+ if (!color || !color.trim()) return TOPIC_ORDER[(siblingIndex % TOPIC_ORDER.length + TOPIC_ORDER.length) % TOPIC_ORDER.length];
1281
+ return topicFromName(color);
1282
+ }
1283
+ var CUSTOM = {
1284
+ /** The ring is the fill with each RGB channel multiplied by this. */
1285
+ ringShade: 0.78
1286
+ };
1287
+ function customColor(color) {
1288
+ const m = /^#?([0-9a-f]{6})$/i.exec((color || "").trim());
1289
+ if (!m) return { fill: color, ring: null };
1290
+ const n = parseInt(m[1], 16), f = CUSTOM.ringShade;
1291
+ const r = Math.round((n >> 16 & 255) * f), g = Math.round((n >> 8 & 255) * f), b = Math.round((n & 255) * f);
1292
+ return { fill: color.trim(), ring: "#" + (1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1) };
1293
+ }
1294
+
1295
+ // src/theme/index.ts
1296
+ var FAMILIES = /* @__PURE__ */ new Set(["focus", "colorsafe", "contrast"]);
1297
+ function isThemeName(v) {
1298
+ return typeof v === "string" && THEME_NAMES.includes(v);
1299
+ }
1300
+ function resolveTheme(input, env = {}) {
1301
+ const scheme = env.prefersDark ? "dark" : "light";
1302
+ if (input === void 0 || input === "auto") return env.forcedColors ? `contrast-${scheme}` : scheme;
1303
+ if (FAMILIES.has(input)) return `${input}-${scheme}`;
1304
+ return isThemeName(input) ? input : "light";
1305
+ }
1306
+ var isDarkTheme = (name) => name.endsWith("dark");
1307
+ var isContrastTheme = (name) => name.startsWith("contrast");
1308
+ var isFocusTheme = (name) => name.startsWith("focus");
1309
+ var getTokens = (name) => TOKENS[name];
1310
+ function token(name, key) {
1311
+ const v = TOKENS[name][key];
1312
+ if (v === void 0) throw new Error(`Unknown design-system token "${key}"`);
1313
+ return v;
1314
+ }
1315
+ function resolveComfort(c, env = {}) {
1316
+ const rm = c?.reducedMotion;
1317
+ return {
1318
+ readableType: !!c?.readableType,
1319
+ largerText: !!c?.largerText,
1320
+ reducedMotion: rm === void 0 || rm === "system" ? !!env.prefersReducedMotion : rm
1321
+ };
1322
+ }
1323
+ function metricsFor(comfort) {
1324
+ return comfort.reducedMotion ? { ...METRICS, ...METRICS_REDUCED_MOTION } : METRICS;
1325
+ }
1326
+ var labelScale = (c) => c.largerText ? 1.15 : 1;
1327
+ function topicStyle(theme, topic) {
1328
+ const t = TOKENS[theme];
1329
+ return { topic, fill: t[`topic-${topic}`], ring: t[`topic-${topic}-ring`] };
1330
+ }
1331
+ function ideaColor(color, siblingIndex) {
1332
+ const topic = topicFor(color, siblingIndex);
1333
+ return topic ? { topic, custom: null } : { topic: null, custom: customColor(color) };
1334
+ }
1335
+ function ideaStyle(theme, c) {
1336
+ if (c.custom) return { fill: c.custom.fill, ring: c.custom.ring ?? TOKENS[theme]["edge"] };
1337
+ return topicStyle(theme, c.topic ?? TOPIC_ORDER[0]);
1338
+ }
1339
+
1340
+ // src/generated/geometry/idea.ts
1341
+ var LABEL_GROWTH = { idea: 1.3, connector: 1.6 };
1342
+ var IDEA = {
1343
+ /** Width an idea starts at… */
1344
+ baseWidth: { root: 240, node: 200, mini: 120 },
1345
+ /** …and the widest it gets before it grows taller instead. */
1346
+ maxWidth: { root: 400, node: 360, mini: 200 },
1347
+ /** Width grows in steps of this. */
1348
+ step: 20,
1349
+ /** Height = width × aspect (at least `minRy` × 2), more if the label needs it. */
1350
+ aspect: 0.74,
1351
+ minRy: 64,
1352
+ /** The label's box is the idea's width less this (22 each side). */
1353
+ labelInset: 44,
1354
+ /** The idea widens until its label fits in this many lines. */
1355
+ labelLines: 3,
1356
+ /** Label size (before the label scale and zoom growth), weight from the canvas type styles. */
1357
+ labelSize: { root: 30, node: 22, mini: 15 },
1358
+ labelLineHeight: 1.15,
1359
+ /** The label may fill at most this share of the idea's height. */
1360
+ labelFillHeight: 0.7
1361
+ };
1362
+ function wrapLines(text, maxWidth, measure) {
1363
+ const words = text.split(/\s+/).filter(Boolean);
1364
+ const lines = [];
1365
+ let cur = "";
1366
+ for (const w of words) {
1367
+ const next = cur ? `${cur} ${w}` : w;
1368
+ if (cur && measure(next) > maxWidth) {
1369
+ lines.push(cur);
1370
+ cur = w;
1371
+ } else cur = next;
1372
+ }
1373
+ if (cur) lines.push(cur);
1374
+ return lines.length ? lines : [""];
1375
+ }
1376
+ function ideaSize(kind, label, measure, labelScale2 = 1) {
1377
+ const px = IDEA.labelSize[kind] * labelScale2 * LABEL_GROWTH.idea;
1378
+ const fit = (w) => wrapLines(label, w - IDEA.labelInset, (t) => measure(t, px));
1379
+ const base = IDEA.baseWidth[kind], max = IDEA.maxWidth[kind];
1380
+ let width = base, lines = fit(width);
1381
+ while (lines.length > IDEA.labelLines && width < max) {
1382
+ width = Math.min(max, width + IDEA.step);
1383
+ lines = fit(width);
1384
+ }
1385
+ const rx = width / 2;
1386
+ const ry = Math.max(IDEA.minRy, rx * IDEA.aspect, lines.length * px * IDEA.labelLineHeight / 2 / IDEA.labelFillHeight);
1387
+ return { width, height: ry * 2, rx, ry, lines };
1388
+ }
1389
+ function labelGrowth(scale) {
1390
+ const inv = 1 / Math.max(scale, 1e-4);
1391
+ return { idea: Math.max(1, Math.min(inv, LABEL_GROWTH.idea)), connector: Math.max(1, Math.min(inv, LABEL_GROWTH.connector)) };
1392
+ }
1393
+
1394
+ // src/rendering/ideaSize.ts
1395
+ var approximateMeasure = (text, px) => text.length * px * 0.5;
1396
+ var ideaKindOf = (layer, index) => layer.level === 0 && (layer.layout ?? "radial") === "radial" && index === 0 ? "root" : "node";
1397
+
494
1398
  // src/parser/visualliConverter.ts
495
1399
  function doNodesOverlap(n1, n2, padding = 20) {
496
1400
  return !(n1.x + n1.width / 2 + padding < n2.x - n2.width / 2 - padding || n1.x - n1.width / 2 - padding > n2.x + n2.width / 2 + padding || n1.y + n1.height / 2 + padding < n2.y - n2.height / 2 - padding || n1.y - n1.height / 2 - padding > n2.y + n2.height / 2 + padding);
@@ -525,26 +1429,10 @@ function resolveNodeOverlaps(nodes, maxIterations = 10) {
525
1429
  if (!any) break;
526
1430
  }
527
1431
  }
528
- var COLOR_PALETTE = [
529
- "#12C7D3",
530
- "#325E8C",
531
- "#8A70A6",
532
- "#F54A57",
533
- "#FF6C4D",
534
- "#F28C16",
535
- "#FFD347",
536
- "#12C7D3",
537
- "#7F7F7F",
538
- "#8D8D8D",
539
- "#12C7D3"
540
- ];
541
- function getRandomColorForNode(nodeId) {
542
- let hash = 0;
543
- for (let i = 0; i < nodeId.length; i++) {
544
- hash = nodeId.charCodeAt(i) + ((hash << 5) - hash);
545
- }
546
- const index = Math.abs(hash) % COLOR_PALETTE.length;
547
- return COLOR_PALETTE[index];
1432
+ function hasFilePositions(layer) {
1433
+ const ps = layer.nodes.map((n) => n.position);
1434
+ if (!ps.length || !ps.every((p) => p && Number.isFinite(p.x) && Number.isFinite(p.y))) return false;
1435
+ return ps.length === 1 || ps.some((p) => p.x !== ps[0].x || p.y !== ps[0].y);
548
1436
  }
549
1437
  function applyFormation(nodes, formation) {
550
1438
  if (nodes.length === 0) return;
@@ -608,44 +1496,34 @@ function proxyRadial(proxies, radius = 500) {
608
1496
  proxies[i].y = radius * Math.sin(angle);
609
1497
  }
610
1498
  }
611
- function calculateNodeWidth(title) {
612
- return Math.max(200, title.length * 8 + 40);
613
- }
614
- function makeFlatNode(nodeId, label, summary, layer, color, branchCount, x = 0, y = 0) {
1499
+ function makeFlatNode(node, layer, doc, siblingIndex, opts) {
1500
+ const label = Array.isArray(node.data.label) ? node.data.label.join(" ") : node.data.label || "Untitled";
1501
+ const branchCount = countLayersBeneath(doc, node.id);
1502
+ const kind = ideaKindOf(layer, siblingIndex);
1503
+ const size = ideaSize(kind, label, opts.measure ?? approximateMeasure, opts.labelScale ?? 1);
1504
+ const { topic, custom } = ideaColor(node.data.color, siblingIndex);
615
1505
  return {
616
- id: nodeId,
1506
+ id: node.id,
617
1507
  parentId: layer.parentNodeId ?? null,
618
- x,
619
- y,
1508
+ x: node.position?.x ?? 0,
1509
+ y: node.position?.y ?? 0,
620
1510
  level: layer.level,
621
1511
  title: label,
622
- description: summary,
623
- color,
624
- width: calculateNodeWidth(label),
625
- height: 80,
1512
+ description: node.data.summary || "",
1513
+ color: node.data.color ?? "",
1514
+ ...topic ? { topic } : {},
1515
+ ...custom ? { custom } : {},
1516
+ kind,
1517
+ width: size.width,
1518
+ height: size.height,
626
1519
  isExpanded: branchCount > 0,
627
1520
  branchCount,
628
1521
  createdAt: /* @__PURE__ */ new Date(),
629
1522
  updatedAt: /* @__PURE__ */ new Date()
630
1523
  };
631
1524
  }
632
- function convertLayerWithContainers(layer, doc) {
1525
+ function layoutLayerWithContainers(layer, flatNodes) {
633
1526
  const PADDING = 80;
634
- const flatNodes = [];
635
- for (const node of layer.nodes) {
636
- const label = Array.isArray(node.data.label) ? node.data.label.join(" ") : node.data.label || "Untitled";
637
- const branchCount = countLayersBeneath(doc, node.id);
638
- flatNodes.push(
639
- makeFlatNode(
640
- node.id,
641
- label,
642
- node.data.summary || "",
643
- layer,
644
- node.data.color || getRandomColorForNode(node.id),
645
- branchCount
646
- )
647
- );
648
- }
649
1527
  const byId = new Map(flatNodes.map((n) => [n.id, n]));
650
1528
  const containerNodeIds = /* @__PURE__ */ new Set();
651
1529
  const containerGroups = /* @__PURE__ */ new Map();
@@ -658,11 +1536,8 @@ function convertLayerWithContainers(layer, doc) {
658
1536
  containerGroups.set(container.id, group);
659
1537
  }
660
1538
  const proxies = [];
661
- for (const node of layer.nodes) {
662
- if (!containerNodeIds.has(node.id)) {
663
- const nodeWidth = calculateNodeWidth(node.data.label);
664
- proxies.push({ id: node.id, x: 0, y: 0, width: nodeWidth, height: 80 });
665
- }
1539
+ for (const n of flatNodes) {
1540
+ if (!containerNodeIds.has(n.id)) proxies.push({ id: n.id, x: 0, y: 0, width: n.width, height: n.height });
666
1541
  }
667
1542
  for (const container of layer.containers) {
668
1543
  const group = containerGroups.get(container.id);
@@ -689,54 +1564,40 @@ function convertLayerWithContainers(layer, doc) {
689
1564
  }
690
1565
  }
691
1566
  }
692
- resolveNodeOverlaps(flatNodes);
693
- return flatNodes;
694
1567
  }
695
- function convertLayerToFlatNodes(layer, doc) {
696
- if ((layer.containers ?? []).length > 0) {
697
- return convertLayerWithContainers(layer, doc);
698
- }
699
- const flatNodes = [];
700
- for (const node of layer.nodes) {
701
- const label = Array.isArray(node.data.label) ? node.data.label.join(" ") : node.data.label || "Untitled";
702
- const branchCount = countLayersBeneath(doc, node.id);
703
- flatNodes.push(
704
- makeFlatNode(
705
- node.id,
706
- label,
707
- node.data.summary || "",
708
- layer,
709
- node.data.color || getRandomColorForNode(node.id),
710
- branchCount,
711
- 0,
712
- 0
713
- )
714
- );
715
- }
716
- if (flatNodes.length > 0) {
717
- const layout = layer.layout || "radial";
718
- if (layout === "linear-horizontal") {
719
- applyLinearHorizontalLayout(flatNodes, { centerX: 0, centerY: 0 });
720
- } else if (layout === "linear-vertical") {
721
- applyLinearVerticalLayout(flatNodes, { centerX: 0, centerY: 0 });
722
- } else {
723
- const pct = calculateOptimalRadiusPercentage(flatNodes.length, 2e3, 2e3, 200, 30);
724
- applyCircularLayout(flatNodes, {
725
- radiusPercentage: pct,
726
- containerWidth: 2e3,
727
- containerHeight: 2e3,
728
- centerX: 0,
729
- centerY: 0
730
- });
731
- }
1568
+ function layoutLayer(layer, flatNodes) {
1569
+ const layout = layer.layout || "radial";
1570
+ if (layout === "linear-horizontal") {
1571
+ applyLinearHorizontalLayout(flatNodes, { centerX: 0, centerY: 0 });
1572
+ } else if (layout === "linear-vertical") {
1573
+ applyLinearVerticalLayout(flatNodes, { centerX: 0, centerY: 0 });
1574
+ } else {
1575
+ const pct = calculateOptimalRadiusPercentage(flatNodes.length, 2e3, 2e3, 200, 30);
1576
+ applyCircularLayout(flatNodes, {
1577
+ radiusPercentage: pct,
1578
+ containerWidth: 2e3,
1579
+ containerHeight: 2e3,
1580
+ centerX: 0,
1581
+ centerY: 0
1582
+ });
1583
+ }
1584
+ }
1585
+ function convertLayerToFlatNodes(layer, doc, opts = {}) {
1586
+ const flatNodes = layer.nodes.map((node, i) => makeFlatNode(node, layer, doc, i, opts));
1587
+ if (flatNodes.length === 0 || hasFilePositions(layer)) return flatNodes;
1588
+ for (const n of flatNodes) {
1589
+ n.x = 0;
1590
+ n.y = 0;
732
1591
  }
1592
+ if ((layer.containers ?? []).length > 0) layoutLayerWithContainers(layer, flatNodes);
1593
+ else layoutLayer(layer, flatNodes);
733
1594
  resolveNodeOverlaps(flatNodes);
734
1595
  return flatNodes;
735
1596
  }
736
- function convertVisualliToFlatNodes(doc) {
1597
+ function convertVisualliToFlatNodes(doc, opts = {}) {
737
1598
  const map = /* @__PURE__ */ new Map();
738
1599
  for (const layer of doc.layers.values()) {
739
- for (const node of convertLayerToFlatNodes(layer, doc)) {
1600
+ for (const node of convertLayerToFlatNodes(layer, doc, opts)) {
740
1601
  map.set(node.id, node);
741
1602
  }
742
1603
  }
@@ -748,9 +1609,9 @@ function convertVisualliToFlatNodes(doc) {
748
1609
  }
749
1610
  return map;
750
1611
  }
751
- function getNodesForLayer(doc, layerId) {
1612
+ function getNodesForLayer(doc, layerId, opts = {}) {
752
1613
  const layer = doc.layers.get(layerId);
753
- return layer ? convertLayerToFlatNodes(layer, doc) : [];
1614
+ return layer ? convertLayerToFlatNodes(layer, doc, opts) : [];
754
1615
  }
755
1616
 
756
1617
  // src/parser/mindmapUtils.ts
@@ -801,258 +1662,17 @@ function calculateDistance(x1, y1, x2, y2) {
801
1662
  return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
802
1663
  }
803
1664
 
804
- // src/constants/design.ts
805
- var DS_COLORS = {
806
- light: {
807
- bgApp: "#F0EDE6",
808
- bgSurface: "#FAF8F4",
809
- bgInput: "#FFFFFF",
810
- border: "#DDD9D0",
811
- borderDivider: "#E8E4DC",
812
- primary: "#1B2D4F",
813
- primaryHover: "#243B68",
814
- textPrimary: "#1A1A18",
815
- textSecondary: "#6B6860",
816
- textMuted: "#747270",
817
- textOnPrimary: "#FAF8F4",
818
- hoverItem: "rgba(27,45,79,0.07)",
819
- focusRing: "rgba(27,45,79,0.40)",
820
- btnMuted: "#8E8C88"
821
- },
822
- dark: {
823
- bgApp: "#141412",
824
- bgSurface: "#1C1C1A",
825
- bgInput: "#262622",
826
- border: "#333330",
827
- borderDivider: "#2A2A28",
828
- primary: "#BB532D",
829
- primaryHover: "#CC6440",
830
- textPrimary: "#F0EDE6",
831
- textSecondary: "#B0ABA3",
832
- textMuted: "#8E8C88",
833
- textOnPrimary: "#FAF8F4",
834
- hoverItem: "rgba(198,94,56,0.12)",
835
- focusRing: "rgba(198,94,56,0.50)",
836
- btnMuted: "#4A4845"
837
- }
838
- };
839
- var DS_TYPOGRAPHY = {
840
- fontDisplay: "'Nunito', sans-serif",
841
- fontBody: "'Nunito Sans', sans-serif",
842
- logo: { size: "20px", weight: 800, letterSpacing: "0.04em" },
843
- heading: { size: "16px", weight: 700, letterSpacing: "-0.2px" },
844
- cta: { size: "11px", weight: 800, letterSpacing: "0.12em", transform: "uppercase" },
845
- body: { size: "13px", weight: 400 },
846
- input: { size: "13px", weight: 300, style: "italic" },
847
- search: { size: "12px", weight: 400 },
848
- username: { size: "13px", weight: 600 },
849
- avatar: { size: "11px", weight: 700 },
850
- footer: { size: "12px", weight: 400 },
851
- empty: { size: "11px", weight: 300, style: "italic" }
852
- };
853
- var DS_RADII = {
854
- sidebar: 16,
855
- input: 8,
856
- cta: 7,
857
- item: 6,
858
- swatch: 10
859
- };
860
- var BRAND_COLORS = {
861
- primary: {
862
- 50: "#EFF6FF",
863
- 100: "#DBEAFE",
864
- 200: "#BFDBFE",
865
- 300: "#93C5FD",
866
- 400: "#60A5FA",
867
- 500: "#3B82F6",
868
- 600: "#2563EB",
869
- 700: "#1D4ED8",
870
- 800: "#1E40AF",
871
- 900: "#1E3A8A"
872
- },
873
- secondary: {
874
- 50: "#F0FDF4",
875
- 100: "#DCFCE7",
876
- 200: "#BBF7D0",
877
- 300: "#86EFAC",
878
- 400: "#4ADE80",
879
- 500: "#10B981",
880
- 600: "#059669",
881
- 700: "#047857",
882
- 800: "#065F46",
883
- 900: "#064E3B"
884
- },
885
- accent: {
886
- purple: "#8B5CF6",
887
- cyan: "#06B6D4",
888
- sky: "#0EA5E9",
889
- lime: "#84CC16",
890
- amber: "#F59E0B",
891
- red: "#EF4444"
892
- }
893
- };
894
- var LEVEL_COLORS = {
895
- level0: BRAND_COLORS.primary[500],
896
- level1: BRAND_COLORS.primary[400],
897
- level2: BRAND_COLORS.primary[300],
898
- level3: BRAND_COLORS.primary[200],
899
- level4: BRAND_COLORS.primary[300],
900
- level5: BRAND_COLORS.primary[400],
901
- level6: BRAND_COLORS.primary[500],
902
- level7: BRAND_COLORS.primary[600],
903
- level8: BRAND_COLORS.primary[700]
904
- };
905
- var LEVEL_COLOR_ARRAY = [
906
- LEVEL_COLORS.level0,
907
- LEVEL_COLORS.level1,
908
- LEVEL_COLORS.level2,
909
- LEVEL_COLORS.level3,
910
- LEVEL_COLORS.level4,
911
- LEVEL_COLORS.level5,
912
- LEVEL_COLORS.level6,
913
- LEVEL_COLORS.level7,
914
- LEVEL_COLORS.level8
915
- ];
916
- var PARENT_FALLBACK_COLORS = [
917
- "#12C7D3",
918
- "#325E8C",
919
- "#8A70A6",
920
- "#F54A57",
921
- "#FF6C4D",
922
- "#F28C16",
923
- "#FFD347",
924
- "#7F7F7F",
925
- "#8D8D8D"
926
- ];
927
- var SEMANTIC_COLORS = {
928
- success: BRAND_COLORS.secondary[500],
929
- error: BRAND_COLORS.accent.red,
930
- warning: BRAND_COLORS.accent.amber,
931
- info: BRAND_COLORS.primary[500]
932
- };
933
- var THEME_COLORS = {
934
- light: {
935
- background: { primary: "#FAF8F4", secondary: "#F0EDE6", tertiary: "#FFFFFF" },
936
- text: { primary: "#1A1A18", secondary: "#6B6860", tertiary: "#747270" },
937
- border: { primary: "#DDD9D0", secondary: "#E8E4DC" }
938
- },
939
- dark: {
940
- background: { primary: "#1C1C1A", secondary: "#141412", tertiary: "#262622" },
941
- text: { primary: "#F0EDE6", secondary: "#B0ABA3", tertiary: "#8E8C88" },
942
- border: { primary: "#333330", secondary: "#2A2A28" }
943
- }
944
- };
945
- var CANVAS_COLORS = {
946
- node: {
947
- border: "#FFFFFF",
948
- titleColor: "#1e1b4b",
949
- badgeBackground: "#FFC107",
950
- badgeStroke: "#fcd34d"
951
- }
952
- };
953
- var SPACING = {
954
- xs: 4,
955
- sm: 8,
956
- md: 16,
957
- lg: 24,
958
- xl: 32,
959
- "2xl": 48,
960
- "3xl": 64,
961
- "4xl": 96
962
- };
1665
+ // src/constants/layout.ts
963
1666
  var LAYOUT_SPACING = {
964
1667
  nodeSpacing: 200,
965
1668
  levelSpacing: 150,
966
1669
  containerPadding: 20,
967
1670
  gridSize: 50
968
1671
  };
969
- var FONTS = {
970
- primary: "'Inter', sans-serif",
971
- secondary: "'Poppins', sans-serif",
972
- mono: "'Roboto Mono', monospace"
973
- };
974
- var FONT_SIZES = {
975
- xs: "0.75rem",
976
- sm: "0.875rem",
977
- base: "1rem",
978
- lg: "1.125rem",
979
- xl: "1.25rem",
980
- "2xl": "1.5rem",
981
- "3xl": "1.875rem",
982
- "4xl": "2.25rem"
983
- };
984
- function getColorForLevel(level) {
985
- return LEVEL_COLOR_ARRAY[level % LEVEL_COLOR_ARRAY.length];
986
- }
987
- function darkenHexColor(hex, amount = 0.22) {
988
- if (!hex || !hex.startsWith("#")) return hex;
989
- let r, g, b;
990
- if (hex.length === 4) {
991
- r = parseInt(hex[1] + hex[1], 16);
992
- g = parseInt(hex[2] + hex[2], 16);
993
- b = parseInt(hex[3] + hex[3], 16);
994
- } else if (hex.length === 7) {
995
- r = parseInt(hex.slice(1, 3), 16);
996
- g = parseInt(hex.slice(3, 5), 16);
997
- b = parseInt(hex.slice(5, 7), 16);
998
- } else {
999
- return hex;
1000
- }
1001
- if (isNaN(r) || isNaN(g) || isNaN(b)) return hex;
1002
- const rn = r / 255, gn = g / 255, bn = b / 255;
1003
- const cmax = Math.max(rn, gn, bn), cmin = Math.min(rn, gn, bn);
1004
- const d = cmax - cmin;
1005
- const l = (cmax + cmin) / 2;
1006
- const s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
1007
- let h = 0;
1008
- if (d !== 0) {
1009
- const seg = cmax === rn ? (gn - bn) / d : cmax === gn ? (bn - rn) / d + 2 : (rn - gn) / d + 4;
1010
- h = (seg * 60 + 360) % 360;
1011
- }
1012
- const newL = Math.max(0, l - amount);
1013
- const c2 = (1 - Math.abs(2 * newL - 1)) * s;
1014
- const x2 = c2 * (1 - Math.abs(h / 60 % 2 - 1));
1015
- const m2 = newL - c2 / 2;
1016
- let r1 = 0, g1 = 0, b1 = 0;
1017
- if (h < 60) {
1018
- r1 = c2;
1019
- g1 = x2;
1020
- b1 = 0;
1021
- } else if (h < 120) {
1022
- r1 = x2;
1023
- g1 = c2;
1024
- b1 = 0;
1025
- } else if (h < 180) {
1026
- r1 = 0;
1027
- g1 = c2;
1028
- b1 = x2;
1029
- } else if (h < 240) {
1030
- r1 = 0;
1031
- g1 = x2;
1032
- b1 = c2;
1033
- } else if (h < 300) {
1034
- r1 = x2;
1035
- g1 = 0;
1036
- b1 = c2;
1037
- } else {
1038
- r1 = c2;
1039
- g1 = 0;
1040
- b1 = x2;
1041
- }
1042
- const toHex = (v) => Math.round((v + m2) * 255).toString(16).padStart(2, "0");
1043
- return `#${toHex(r1)}${toHex(g1)}${toHex(b1)}`;
1044
- }
1045
- function getThemeBackground(isDark, variant = "primary") {
1046
- return isDark ? THEME_COLORS.dark.background[variant] : THEME_COLORS.light.background[variant];
1047
- }
1048
- function getThemeText(isDark, variant = "primary") {
1049
- return isDark ? THEME_COLORS.dark.text[variant] : THEME_COLORS.light.text[variant];
1050
- }
1051
- function getThemeBorder(isDark, variant = "primary") {
1052
- return isDark ? THEME_COLORS.dark.border[variant] : THEME_COLORS.light.border[variant];
1053
- }
1054
1672
 
1055
1673
  // src/parser/configUtils.ts
1674
+ var levelColor = (level) => TOKENS.light[`topic-${TOPICS[level % TOPICS.length]}`];
1675
+ var levelColors = (count) => Object.fromEntries(Array.from({ length: count }, (_, i) => [`level${i}`, levelColor(i)]));
1056
1676
  function createMindMapConfig(nodes, topLevelConnections, title = "Untitled Mindmap") {
1057
1677
  const now = (/* @__PURE__ */ new Date()).toISOString();
1058
1678
  return {
@@ -1066,12 +1686,7 @@ function createMindMapConfig(nodes, topLevelConnections, title = "Untitled Mindm
1066
1686
  nodes,
1067
1687
  topLevelConnections,
1068
1688
  settings: {
1069
- defaultColors: {
1070
- level0: LEVEL_COLORS.level0,
1071
- level1: LEVEL_COLORS.level1,
1072
- level2: LEVEL_COLORS.level2,
1073
- level3: LEVEL_COLORS.level3
1074
- },
1689
+ defaultColors: levelColors(4),
1075
1690
  layout: {
1076
1691
  nodeSpacing: LAYOUT_SPACING.nodeSpacing,
1077
1692
  levelSpacing: LAYOUT_SPACING.levelSpacing
@@ -1098,7 +1713,7 @@ function generateLevelNodes(parentId, level, maxLevels, xOffset = 0, yOffset = 0
1098
1713
  level,
1099
1714
  x,
1100
1715
  y,
1101
- color: getColorForLevel(level),
1716
+ color: levelColor(level),
1102
1717
  relationshipLabel: level > 0 ? "child" : void 0,
1103
1718
  parent: parentId ?? void 0
1104
1719
  };
@@ -1127,17 +1742,7 @@ function generateSampleConfig(maxLevels = 9) {
1127
1742
  nodes,
1128
1743
  topLevelConnections,
1129
1744
  settings: {
1130
- defaultColors: {
1131
- level0: LEVEL_COLORS.level0,
1132
- level1: LEVEL_COLORS.level1,
1133
- level2: LEVEL_COLORS.level2,
1134
- level3: LEVEL_COLORS.level3,
1135
- level4: LEVEL_COLORS.level4,
1136
- level5: LEVEL_COLORS.level5,
1137
- level6: LEVEL_COLORS.level6,
1138
- level7: LEVEL_COLORS.level7,
1139
- level8: LEVEL_COLORS.level8
1140
- },
1745
+ defaultColors: levelColors(9),
1141
1746
  layout: {
1142
1747
  nodeSpacing: LAYOUT_SPACING.nodeSpacing,
1143
1748
  levelSpacing: LAYOUT_SPACING.levelSpacing
@@ -1146,6 +1751,129 @@ function generateSampleConfig(maxLevels = 9) {
1146
1751
  };
1147
1752
  }
1148
1753
 
1754
+ // src/generated/geometry/interaction.ts
1755
+ var TERM = {
1756
+ /** Hovering the term this long opens its definition card… */
1757
+ hoverOpen: 250,
1758
+ /** …which closes this long after the pointer leaves both the term and the card (moving between them keeps it open). */
1759
+ hoverClose: 300,
1760
+ /** Only a mouse opens on hover; touch and pen open on tap. */
1761
+ hoverPointers: ["mouse"],
1762
+ /** A click, tap, Enter or Space pins the card open (hovering away doesn't close it); pressing again unpins and closes. */
1763
+ clickPins: true,
1764
+ /** Escape closes the card and returns focus to the term; a press anywhere outside the term and its card closes it. */
1765
+ escapeCloses: true,
1766
+ pressOutsideCloses: true,
1767
+ /** Opening by click or keyboard moves focus to the card's first link or button. */
1768
+ focusCardOnOpen: true,
1769
+ /** The card is kept this far inside the viewport's left and right edges. */
1770
+ viewportMargin: 8,
1771
+ /** The card's link to a source about the term. */
1772
+ learnMoreLabel: "Learn more"
1773
+ };
1774
+ var PEEK = {
1775
+ /** Hovering an idea opens its peek at once; it stays this long after the pointer leaves the idea (and while the
1776
+ * pointer is over the peek itself), so the pointer can travel from the idea onto the card. */
1777
+ hoverClose: 180
1778
+ };
1779
+ var SHEET = {
1780
+ /** Dragging the grip down this far closes the sheet (or collapses it when expanded); up this far expands it. */
1781
+ dismissDrag: 80,
1782
+ expandDrag: 40,
1783
+ /** Its height: this share of the map, at most `maxHeight` px; the map pans the chosen idea into the space above it. */
1784
+ heightRatio: 0.42,
1785
+ maxHeight: 300
1786
+ };
1787
+ var VIEW = {
1788
+ /** Each zoom step multiplies or divides the zoom by this. */
1789
+ zoomStep: 1.2,
1790
+ /** Zoom limits (the zoom-min / zoom-max tokens; the build checks they agree). */
1791
+ zoomMin: 0.3,
1792
+ zoomMax: 5,
1793
+ /** Fit to view never enlarges a layer beyond this. */
1794
+ fitMax: 1.1
1795
+ };
1796
+ var GESTURE = {
1797
+ /** Two fingers pinch to zoom the map's contents — ideas, connectors, containers and their labels — around the
1798
+ * point between the fingers, within VIEW's limits. Controls, the depth trail, the peek, chat and the page never scale. */
1799
+ pinchZoom: true,
1800
+ /** Moving both fingers pans the contents. */
1801
+ twoFingerPan: true,
1802
+ /** The pointers that pinch (PointerEvent.pointerType). */
1803
+ pointers: ["touch"],
1804
+ /** Where the map is the page (an app: AppShell, the mobile app), it takes every touch gesture (touch-action: none),
1805
+ * so the browser never zooms the page. Embedded on a scrolling page (website, docs), one finger still scrolls the
1806
+ * page (touch-action: pan-x pan-y); two fingers that land together pinch the map. */
1807
+ appTouchAction: "none",
1808
+ embeddedTouchAction: "pan-x pan-y",
1809
+ /** Zoom never navigates: stepping inside and backing out stay explicit (tap an idea / Step inside, the depth trail,
1810
+ * Escape or Backspace). */
1811
+ zoomNavigates: false
1812
+ };
1813
+ function pinchZoom(startZoom, startDistance, distance) {
1814
+ const z = startZoom * (distance / Math.max(startDistance, 1));
1815
+ return Math.min(VIEW.zoomMax, Math.max(VIEW.zoomMin, z));
1816
+ }
1817
+ function zoomAround(t0, s0, s1, from, to) {
1818
+ const wx = (from.x - t0.x) / s0, wy = (from.y - t0.y) / s0;
1819
+ return { x: to.x - wx * s1, y: to.y - wy * s1 };
1820
+ }
1821
+ var FRAME = {
1822
+ /** Room beyond each idea's outline (at least `minPadX` / `minPadY` from its centre). */
1823
+ clearX: 50,
1824
+ clearY: 46,
1825
+ minPadX: 150,
1826
+ minPadY: 120,
1827
+ /** Room beyond each container hull for its dashed line and the name pill straddling it. */
1828
+ hullX: 24,
1829
+ hullY: 40
1830
+ };
1831
+ function layerBounds(ideas, hulls = []) {
1832
+ let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
1833
+ for (const n of ideas) {
1834
+ const px = Math.max(FRAME.minPadX, n.rx + FRAME.clearX), py = Math.max(FRAME.minPadY, n.ry + FRAME.clearY);
1835
+ x0 = Math.min(x0, n.x - px);
1836
+ x1 = Math.max(x1, n.x + px);
1837
+ y0 = Math.min(y0, n.y - py);
1838
+ y1 = Math.max(y1, n.y + py);
1839
+ }
1840
+ for (const h of hulls) {
1841
+ x0 = Math.min(x0, h.x0 - FRAME.hullX);
1842
+ x1 = Math.max(x1, h.x1 + FRAME.hullX);
1843
+ y0 = Math.min(y0, h.y0 - FRAME.hullY);
1844
+ y1 = Math.max(y1, h.y1 + FRAME.hullY);
1845
+ }
1846
+ if (!Number.isFinite(x0)) return { x0: -FRAME.minPadX, y0: -FRAME.minPadY, x1: FRAME.minPadX, y1: FRAME.minPadY };
1847
+ return { x0, y0, x1, y1 };
1848
+ }
1849
+ function fitView(bounds, width, height) {
1850
+ const scale = Math.min(width / Math.max(1, bounds.x1 - bounds.x0), height / Math.max(1, bounds.y1 - bounds.y0), VIEW.fitMax);
1851
+ return { scale, center: { x: (bounds.x0 + bounds.x1) / 2, y: (bounds.y0 + bounds.y1) / 2 } };
1852
+ }
1853
+ var PEEK_PLACEMENT = {
1854
+ /** Gap between the idea's outermost ring and the card. */
1855
+ gap: 16,
1856
+ /** The card's width and the height kept clear below it. */
1857
+ cardWidth: 300,
1858
+ cardHeight: 220,
1859
+ /** The card is raised this much above the idea's centre. */
1860
+ raise: 70,
1861
+ /** Kept this far inside the map's edges. */
1862
+ margin: 12
1863
+ };
1864
+ function peekPosition(idea, box, card = { w: PEEK_PLACEMENT.cardWidth, h: PEEK_PLACEMENT.cardHeight }) {
1865
+ const P = PEEK_PLACEMENT, half = idea.half + P.gap;
1866
+ const right = idea.x + half + card.w < box.w - P.margin;
1867
+ return { left: right ? idea.x + half : Math.max(P.margin, idea.x - half - card.w), top: Math.min(Math.max(P.margin, idea.y - P.raise), box.h - card.h), side: right ? "right" : "left" };
1868
+ }
1869
+ var TRAIL = { rootTopic: "stone", rootLabel: "icon", homeIconSize: 12, dotBox: 16, dotRadius: 6.4, stroke: 1.25, currentStroke: 1.75 };
1870
+ function pointerModeFor(pointerType) {
1871
+ return pointerType === "mouse" ? "hover" : pointerType === "touch" || pointerType === "pen" ? "touch" : null;
1872
+ }
1873
+ function hoverOpensTerm(pointerType) {
1874
+ return TERM.hoverPointers.includes(pointerType);
1875
+ }
1876
+
1149
1877
  // src/constants/performanceConstants.ts
1150
1878
  var FPS_TARGET = 60;
1151
1879
  var FPS_WARNING_THRESHOLD = 45;
@@ -1179,26 +1907,9 @@ var MAX_VISIBLE_NODES = 800;
1179
1907
  var CULLING_QUERY_TIME_BUDGET = 5;
1180
1908
  var RBUSH_MAX_ENTRIES = 16;
1181
1909
  var SPATIAL_INDEX_BUILD_BUDGET = 50;
1182
- var NODE_MIN_WIDTH = 200;
1183
- var NODE_HEIGHT = 80;
1184
- var NODE_PADDING = 40;
1185
- var CHAR_WIDTH = 8;
1186
- var TITLE_MAX_LENGTH = 150;
1187
- var CORNER_RADIUS = 8;
1188
- var BORDER_WIDTH_NORMAL = 2;
1189
- var BORDER_WIDTH_SELECTED = 3;
1190
- var COLOR_SELECTED = "#1B2D4F";
1191
- var COLOR_BORDER_LIGHT = "#4A3728";
1192
- var COLOR_BORDER_DARK = "#FAF6F1";
1193
- var COLOR_DEFAULT_NODE = "#FAF6F1";
1194
- var ZOOM_MIN = 0.05;
1195
- var ZOOM_MAX = 5;
1910
+ var ZOOM_MIN = VIEW.zoomMin;
1911
+ var ZOOM_MAX = VIEW.zoomMax;
1196
1912
  var ZOOM_DEFAULT = 1;
1197
- var ZOOM_STEP = 0.1;
1198
- var ZOOM_WHEEL_SENSITIVITY = 1e-3;
1199
- var ZOOM_NAV_IN_THRESHOLD = 2.7;
1200
- var ZOOM_NAV_OUT_THRESHOLD = 0.4;
1201
- var TEXT_LABEL_HIDE_BELOW_ZOOM = 0.3;
1202
1913
  var QUALITY_HIGH = {
1203
1914
  shadows: true,
1204
1915
  bezierCurves: true,
@@ -1366,8 +2077,8 @@ function screenToWorld(screenX, screenY, viewport, canvasWidth = DEFAULT_CANVAS_
1366
2077
  y: (screenY - canvasHeight / 2) / viewport.zoomLevel + viewport.centerY
1367
2078
  };
1368
2079
  }
1369
- function clampZoom(zoom) {
1370
- return Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, zoom));
2080
+ function clampZoom(zoom, fit = 1) {
2081
+ return Math.min(ZOOM_MAX * fit, Math.max(ZOOM_MIN * fit, zoom));
1371
2082
  }
1372
2083
  function panViewport(deltaX, deltaY, viewport) {
1373
2084
  return {
@@ -1379,8 +2090,8 @@ function panViewport(deltaX, deltaY, viewport) {
1379
2090
  function setViewportCenter(x, y, viewport) {
1380
2091
  return { ...viewport, centerX: x, centerY: y };
1381
2092
  }
1382
- function zoomViewport(delta, viewport, pivotX, pivotY, canvasWidth = DEFAULT_CANVAS_WIDTH, canvasHeight = DEFAULT_CANVAS_HEIGHT) {
1383
- const newZoom = clampZoom(viewport.zoomLevel * (1 + delta));
2093
+ function zoomViewport(delta, viewport, pivotX, pivotY, canvasWidth = DEFAULT_CANVAS_WIDTH, canvasHeight = DEFAULT_CANVAS_HEIGHT, fit = 1) {
2094
+ const newZoom = clampZoom(viewport.zoomLevel * (1 + delta), fit);
1384
2095
  let cx = viewport.centerX;
1385
2096
  let cy = viewport.centerY;
1386
2097
  if (pivotX !== void 0 && pivotY !== void 0) {
@@ -1536,94 +2247,6 @@ var PerformanceProfiler = class {
1536
2247
  }
1537
2248
  };
1538
2249
 
1539
- // src/animations/easing.ts
1540
- var easeInOutCubic = (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
1541
- var easeInOutQuart = (t) => t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2;
1542
- var easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
1543
- var easeInCubic = (t) => t * t * t;
1544
- var easeOutQuad = (t) => 1 - (1 - t) * (1 - t);
1545
- var easeInOutSine = (t) => -(Math.cos(Math.PI * t) - 1) / 2;
1546
- function createCubicBezier(x1, y1, x2, y2) {
1547
- const N = 256;
1548
- const lut = new Float64Array(N + 1);
1549
- for (let i = 0; i <= N; i++) {
1550
- const target = i / N;
1551
- let lo = 0, hi = 1;
1552
- for (let j = 0; j < 20; j++) {
1553
- const mid = (lo + hi) / 2;
1554
- const om = 1 - mid;
1555
- const x = 3 * om * om * mid * x1 + 3 * om * mid * mid * x2 + mid * mid * mid;
1556
- if (x < target) lo = mid;
1557
- else hi = mid;
1558
- }
1559
- const u = (lo + hi) / 2;
1560
- const ou = 1 - u;
1561
- lut[i] = 3 * ou * ou * u * y1 + 3 * ou * u * u * y2 + u * u * u;
1562
- }
1563
- return (t) => {
1564
- if (t <= 0) return 0;
1565
- if (t >= 1) return 1;
1566
- const s = t * N;
1567
- const i = s | 0;
1568
- return lut[i] + (lut[i + 1] - lut[i]) * (s - i);
1569
- };
1570
- }
1571
- var easeZoomTransition = createCubicBezier(0.2, 0.8, 0.2, 1);
1572
-
1573
- // src/animations/constants.ts
1574
- var ANIMATION_DURATION = {
1575
- ZOOM_IN_LAYER: 500,
1576
- ZOOM_OUT_LAYER: 500,
1577
- ZOOM_GESTURE: 500,
1578
- FADE_OUT: 200,
1579
- FADE_IN: 250,
1580
- COLOR_TRANSITION: 500,
1581
- CURSOR_CHANGE: 150,
1582
- LOADING_FADE: 200
1583
- };
1584
- var ANIMATION_PHASES = {
1585
- ZOOM_IN: {
1586
- ZOOM_TO_TARGET: 500,
1587
- FADE_OUT_START: 120,
1588
- FADE_OUT: 280,
1589
- CONTENT_SWAP: 310,
1590
- FADE_IN: 200,
1591
- TOTAL: 510
1592
- },
1593
- ZOOM_OUT: {
1594
- FADE_OUT: 200,
1595
- ZOOM_BACK: 50,
1596
- FADE_IN: 250,
1597
- TOTAL: 500
1598
- }
1599
- };
1600
- var ZOOM_THRESHOLDS = {
1601
- ENTER_LAYER: 5,
1602
- EXIT_LAYER: 0.5,
1603
- GESTURE_TRIGGER: 0.8
1604
- };
1605
- var COOLDOWN = {
1606
- NAVIGATION: 1e3,
1607
- ZOOM_GESTURE: 800,
1608
- DOUBLE_CLICK: 200
1609
- };
1610
- var OPACITY = {
1611
- VISIBLE: 1,
1612
- HIDDEN: 0,
1613
- TRANSITIONING: 0.3
1614
- };
1615
- var TARGET_ZOOM = {
1616
- NORMAL: 1,
1617
- FOCUSED: 0.8,
1618
- FILL_NODE: 3,
1619
- DETAIL: 2
1620
- };
1621
- var VIEWPORT_FRAMING = {
1622
- NODE_FILL_RATIO: 0.7,
1623
- EXIT_NODE_SIZE: 0.4,
1624
- PADDING_RATIO: 0.1
1625
- };
1626
-
1627
2250
  // src/spatial/spatialIndex.ts
1628
2251
  var import_rbush = __toESM(require("rbush"), 1);
1629
2252
  var RBushSpatialIndex = class {
@@ -1722,6 +2345,73 @@ var renderConfigStore = createStore((set, get) => ({
1722
2345
  isFeatureEnabled: (feature) => isFeatureEnabled(feature, get())
1723
2346
  }));
1724
2347
 
2348
+ // src/generated/geometry/container.ts
2349
+ var HULL = { padX: 150, padY: 125, radius: 48 };
2350
+ var WEIGHT = { idea: 1e3, label: 1e3, connector: 60, preference: 1 };
2351
+ var HULL_CLEAR = { x: 50, y: 51 };
2352
+ function containerHull(members) {
2353
+ if (!members.length) return null;
2354
+ let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
2355
+ for (const m of members) {
2356
+ const px = Math.max(HULL.padX, (m.rx ?? 0) + HULL_CLEAR.x), py = Math.max(HULL.padY, (m.ry ?? 0) + HULL_CLEAR.y);
2357
+ x0 = Math.min(x0, m.x - px);
2358
+ x1 = Math.max(x1, m.x + px);
2359
+ y0 = Math.min(y0, m.y - py);
2360
+ y1 = Math.max(y1, m.y + py);
2361
+ }
2362
+ return { x0, x1, y0, y1, radius: HULL.radius };
2363
+ }
2364
+ function labelCandidates(hull, w, h) {
2365
+ const { x0, x1, y0, y1, radius } = hull;
2366
+ const cx = (x0 + x1) / 2, cy = (y0 + y1) / 2;
2367
+ const minX = x0 + radius + w / 2, maxX = x1 - radius - w / 2;
2368
+ const minY = y0 + radius + h / 2, maxY = y1 - radius - h / 2;
2369
+ const along = (lo, hi, f) => lo > hi ? (lo + hi) / 2 : lo + (hi - lo) * f;
2370
+ const out = [
2371
+ { x: cx, y: y1, side: "bottom" },
2372
+ { x: cx, y: y0, side: "top" },
2373
+ { x: along(minX, maxX, 0.2), y: y1, side: "bottom" },
2374
+ { x: along(minX, maxX, 0.8), y: y1, side: "bottom" },
2375
+ { x: along(minX, maxX, 0.2), y: y0, side: "top" },
2376
+ { x: along(minX, maxX, 0.8), y: y0, side: "top" },
2377
+ { x: x1, y: cy, side: "right" },
2378
+ { x: x0, y: cy, side: "left" },
2379
+ { x: x1, y: along(minY, maxY, 0.25), side: "right" },
2380
+ { x: x1, y: along(minY, maxY, 0.75), side: "right" },
2381
+ { x: x0, y: along(minY, maxY, 0.25), side: "left" },
2382
+ { x: x0, y: along(minY, maxY, 0.75), side: "left" }
2383
+ ];
2384
+ out.push(
2385
+ { x: along(minX, maxX, 0), y: y1, side: "bottom" },
2386
+ { x: along(minX, maxX, 1), y: y1, side: "bottom" },
2387
+ { x: along(minX, maxX, 0), y: y0, side: "top" },
2388
+ { x: along(minX, maxX, 1), y: y0, side: "top" }
2389
+ );
2390
+ return out;
2391
+ }
2392
+ var overlap = (a, b) => Math.max(0, Math.min(a.x1, b.x1) - Math.max(a.x0, b.x0)) * Math.max(0, Math.min(a.y1, b.y1) - Math.max(a.y0, b.y0));
2393
+ function placeContainerLabel(hull, size, obstacles, gap = 10) {
2394
+ const { w, h } = size;
2395
+ let best = null;
2396
+ labelCandidates(hull, w, h).forEach((c, i) => {
2397
+ const box = { x0: c.x - w / 2 - gap, x1: c.x + w / 2 + gap, y0: c.y - h / 2 - gap, y1: c.y + h / 2 + gap };
2398
+ const area = w * h;
2399
+ let score = i * WEIGHT.preference;
2400
+ for (const b of obstacles.ideas) score += overlap(box, b) / area * WEIGHT.idea;
2401
+ for (const b of obstacles.labels || []) score += overlap(box, b) / area * WEIGHT.label;
2402
+ for (const line of obstacles.connectors || []) if (line.some((p) => p.x > box.x0 && p.x < box.x1 && p.y > box.y0 && p.y < box.y1)) score += WEIGHT.connector;
2403
+ if (!best || score < best.score) best = { x: c.x, y: c.y, side: c.side, score };
2404
+ });
2405
+ return best;
2406
+ }
2407
+ function connectorSamples(a, mid, b, n = 16) {
2408
+ const c1 = { x: a.x + (mid.x - a.x) * 0.66, y: a.y + (mid.y - a.y) * 0.66 }, c2 = { x: b.x + (mid.x - b.x) * 0.66, y: b.y + (mid.y - b.y) * 0.66 };
2409
+ return Array.from({ length: n + 1 }, (_, k) => {
2410
+ const t = k / n, u = 1 - t;
2411
+ return { x: u * u * u * a.x + 3 * u * u * t * c1.x + 3 * u * t * t * c2.x + t * t * t * b.x, y: u * u * u * a.y + 3 * u * u * t * c1.y + 3 * u * t * t * c2.y + t * t * t * b.y };
2412
+ });
2413
+ }
2414
+
1725
2415
  // src/utils/navigation.ts
1726
2416
  function getConnectionsForLayer(doc, layerId) {
1727
2417
  const layer = doc.layers.get(layerId);
@@ -1730,7 +2420,8 @@ function getConnectionsForLayer(doc, layerId) {
1730
2420
  from: c.from,
1731
2421
  to: c.to,
1732
2422
  level: layer.level,
1733
- label: c.data?.label
2423
+ label: c.data?.label,
2424
+ style: c.data?.style === "dashed" ? "dashed" : "solid"
1734
2425
  }));
1735
2426
  }
1736
2427
  function getChildLayerForNode(doc, nodeId, currentLayerId) {
@@ -1744,53 +2435,176 @@ function getChildLayerForNode(doc, nodeId, currentLayerId) {
1744
2435
  function getLayerForNavigation(doc, nodeId, currentLayerId) {
1745
2436
  return getChildLayerForNode(doc, nodeId, currentLayerId);
1746
2437
  }
1747
- function calculateFitZoom(nodes, canvasWidth, canvasHeight, padding = 0.15) {
1748
- if (nodes.length === 0) return 1;
1749
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1750
- for (const n of nodes) {
1751
- minX = Math.min(minX, n.x - n.width / 2);
1752
- minY = Math.min(minY, n.y - n.height / 2);
1753
- maxX = Math.max(maxX, n.x + n.width / 2);
1754
- maxY = Math.max(maxY, n.y + n.height / 2);
2438
+ function layerFrame(nodes, containers = []) {
2439
+ const byId = new Map(nodes.map((n) => [n.id, n]));
2440
+ const hulls = [];
2441
+ for (const c of containers) {
2442
+ const hull = containerHull(c.nodeIds.flatMap((id) => {
2443
+ const n = byId.get(id);
2444
+ return n ? [{ x: n.x, y: n.y, rx: n.width / 2, ry: n.height / 2 }] : [];
2445
+ }));
2446
+ if (hull) hulls.push(hull);
1755
2447
  }
1756
- const rawW = maxX - minX;
1757
- const rawH = maxY - minY;
1758
- const spreadW = nodes.length === 1 ? Math.max(rawW, 600) : rawW;
1759
- const spreadH = nodes.length === 1 ? Math.max(rawH, 400) : rawH;
1760
- const zoomX = canvasWidth * (1 - padding) / spreadW;
1761
- const zoomY = canvasHeight * (1 - padding) / spreadH;
1762
- const z = Math.min(zoomX, zoomY);
1763
- return Math.max(z * 0.8, ZOOM_NAV_OUT_THRESHOLD);
2448
+ return layerBounds(nodes.map((n) => ({ x: n.x, y: n.y, rx: n.width / 2, ry: n.height / 2 })), hulls);
1764
2449
  }
1765
- function calculateFitCenter(nodes) {
2450
+ function calculateFitZoom(nodes, canvasWidth, canvasHeight, containers = []) {
2451
+ if (nodes.length === 0) return 1;
2452
+ return fitView(layerFrame(nodes, containers), canvasWidth, canvasHeight).scale;
2453
+ }
2454
+ function calculateFitCenter(nodes, containers = []) {
1766
2455
  if (nodes.length === 0) return { x: 0, y: 0 };
1767
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1768
- for (const n of nodes) {
1769
- minX = Math.min(minX, n.x - n.width / 2);
1770
- minY = Math.min(minY, n.y - n.height / 2);
1771
- maxX = Math.max(maxX, n.x + n.width / 2);
1772
- maxY = Math.max(maxY, n.y + n.height / 2);
1773
- }
1774
- return { x: (minX + maxX) / 2, y: (minY + maxY) / 2 };
2456
+ const f = layerFrame(nodes, containers);
2457
+ return { x: (f.x0 + f.x1) / 2, y: (f.y0 + f.y1) / 2 };
1775
2458
  }
1776
2459
  function getContainersForLayer(doc, layerId) {
1777
2460
  const layer = doc.layers.get(layerId);
1778
2461
  if (!layer) return [];
1779
2462
  const containers = layer.containers ?? [];
1780
2463
  if (!Array.isArray(containers)) return [];
1781
- return containers.map((c) => {
2464
+ return containers.filter((c) => c.data?.style !== "none").map((c) => {
1782
2465
  const label = c.data?.label ?? c.id;
1783
2466
  return { id: c.id, label, nodeIds: c.nodes, level: layer.level };
1784
2467
  });
1785
2468
  }
1786
- function calculateFitView(nodes, canvasWidth, canvasHeight) {
1787
- const center = calculateFitCenter(nodes);
1788
- const zoom = calculateFitZoom(nodes, canvasWidth, canvasHeight);
1789
- return { centerX: center.x, centerY: center.y, zoomLevel: zoom };
2469
+ function calculateFitView(nodes, canvasWidth, canvasHeight, containers = []) {
2470
+ if (nodes.length === 0) return { centerX: 0, centerY: 0, zoomLevel: 1 };
2471
+ const { scale, center } = fitView(layerFrame(nodes, containers), canvasWidth, canvasHeight);
2472
+ return { centerX: center.x, centerY: center.y, zoomLevel: scale };
2473
+ }
2474
+
2475
+ // src/generated/geometry/blobShapes.ts
2476
+ var BLOB_SHAPES = [[[1.12, 0], [0.86, 0.56], [0.46, 0.94], [-0.08, 1.06], [-0.68, 0.82], [-0.96, 0.08], [-0.55, -0.82], [0.04, -1.02], [0.72, -0.8], [1.08, -0.38]], [[-0.489, -0.625], [-0.384, -0.716], [-0.285, -0.792], [-0.189, -0.855], [-0.09, -0.903], [0.014, -0.932], [0.129, -0.94], [0.255, -0.924], [0.412, -0.876], [0.516, -0.832], [0.612, -0.78], [0.695, -0.725], [0.767, -0.666], [0.83, -0.606], [0.885, -0.546], [0.933, -0.486], [0.979, -0.425], [1.006, -0.385], [1.034, -0.334], [1.058, -0.276], [1.078, -0.209], [1.092, -0.133], [1.101, -0.046], [1.102, 0.052], [1.094, 0.173], [1.083, 0.244], [1.063, 0.324], [1.034, 0.401], [0.996, 0.477], [0.948, 0.549], [0.889, 0.617], [0.82, 0.681], [0.735, 0.741], [0.658, 0.783], [0.569, 0.822], [0.474, 0.855], [0.372, 0.882], [0.265, 0.904], [0.156, 0.92], [0.046, 0.931], [-0.069, 0.938], [-0.155, 0.935], [-0.25, 0.919], [-0.346, 0.89], [-0.443, 0.851], [-0.539, 0.8], [-0.633, 0.738], [-0.723, 0.667], [-0.808, 0.585], [-0.882, 0.501], [-0.944, 0.421], [-0.994, 0.343], [-1.034, 0.267], [-1.064, 0.192], [-1.085, 0.12], [-1.097, 0.042], [-1.102, 1e-3], [-1.09, -0.243], [-1.05, -0.365], [-0.99, -0.441], [-0.911, -0.479], [-0.819, -0.499], [-0.715, -0.517], [-0.605, -0.551]], [[3e-3, -0.97], [0.163, -0.984], [0.286, -1.028], [0.389, -1.096], [0.488, -1.174], [0.597, -1.21], [0.728, -1.165], [0.867, -1.036], [0.996, -0.839], [1.098, -0.59], [1.158, -0.304], [1.153, 0.012], [1.08, 0.336], [0.944, 0.639], [0.756, 0.889], [0.484, 1.072], [0.339, 1.079], [0.219, 1.051], [0.131, 0.994], [0.059, 0.939], [8e-3, 0.915], [-0.105, 0.931], [-0.176, 0.965], [-0.249, 1.005], [-0.345, 1.039], [-0.497, 1.055], [-0.672, 1.032], [-0.817, 0.952], [-0.945, 0.81], [-1.054, 0.6], [-1.16, 0.302], [-1.187, -0.011], [-1.165, -0.326], [-1.097, -0.599], [-0.991, -0.838], [-0.852, -0.912], [-0.695, -0.958], [-0.53, -0.988], [-0.357, -0.992], [-0.18, -0.983]], [[-0.734, -1.127], [-0.656, -1.146], [-0.567, -1.142], [-0.468, -1.115], [-0.358, -1.079], [-0.238, -1.034], [-0.107, -0.989], [0.035, -0.953], [0.188, -0.929], [0.325, -0.916], [0.484, -0.896], [0.65, -0.854], [0.811, -0.77], [0.955, -0.631], [1.068, -0.422], [1.138, -0.126], [1.152, 0.272], [1.121, 0.451], [1.046, 0.602], [0.928, 0.727], [0.767, 0.833], [0.565, 0.925], [0.323, 1.001], [0.042, 1.075], [-0.277, 1.142], [-0.368, 1.147], [-0.475, 1.136], [-0.589, 1.104], [-0.704, 1.05], [-0.815, 0.973], [-0.913, 0.871], [-0.993, 0.746], [-1.048, 0.593], [-1.109, 0.298], [-1.145, 0.018], [-1.154, -0.243], [-1.135, -0.482], [-1.085, -0.695], [-1.003, -0.874], [-0.886, -1.02]], [[-0.734, 1.127], [-0.656, 1.146], [-0.567, 1.142], [-0.468, 1.115], [-0.358, 1.079], [-0.238, 1.034], [-0.107, 0.989], [0.035, 0.953], [0.188, 0.929], [0.325, 0.916], [0.484, 0.896], [0.65, 0.854], [0.811, 0.77], [0.955, 0.631], [1.068, 0.422], [1.138, 0.126], [1.152, -0.272], [1.121, -0.451], [1.046, -0.602], [0.928, -0.727], [0.767, -0.833], [0.565, -0.925], [0.323, -1.001], [0.042, -1.075], [-0.277, -1.142], [-0.368, -1.147], [-0.475, -1.136], [-0.589, -1.104], [-0.704, -1.05], [-0.815, -0.973], [-0.913, -0.871], [-0.993, -0.746], [-1.048, -0.593], [-1.109, -0.298], [-1.145, -0.018], [-1.154, 0.243], [-1.135, 0.482], [-1.085, 0.695], [-1.003, 0.874], [-0.886, 1.02]], [[1e-3, -1.006], [0.134, -1.001], [0.262, -1.024], [0.382, -1.073], [0.498, -1.107], [0.614, -1.105], [0.733, -1.049], [0.85, -0.932], [0.953, -0.755], [1.03, -0.529], [1.067, -0.265], [1.054, 0.018], [0.986, 0.3], [0.868, 0.557], [0.715, 0.766], [0.55, 0.909], [0.396, 0.984], [0.262, 1.005], [0.148, 1.003], [0.037, 1.008], [-0.083, 1.04], [-0.22, 1.118], [-0.36, 1.192], [-0.5, 1.234], [-0.65, 1.175], [-0.81, 1.045], [-0.99, 0.855], [-1.14, 0.61], [-1.24, 0.33], [-1.27, 0.04], [-1.275, -0.25], [-1.225, -0.5], [-1.08, -0.72], [-0.89, -0.92], [-0.734, -1.053], [-0.614, -1.112], [-0.498, -1.127], [-0.381, -1.11], [-0.261, -1.095], [-0.133, -1.058], [1e-3, -1.01], [0.134, -1.001]]];
2477
+
2478
+ // src/generated/geometry/blob.ts
2479
+ var SHAPE = { idea: 0, varyByLevel: false };
2480
+ function shapeForLevel(level) {
2481
+ if (!SHAPE.varyByLevel) return SHAPE.idea;
2482
+ const n = BLOB_SHAPES.length;
2483
+ let cur = SHAPE.idea;
2484
+ for (let i = 1; i <= level; i++) {
2485
+ const r = Math.floor(Math.abs(Math.sin((i + 1) * 12.9898) * 43758.5453));
2486
+ cur = (cur + r % (n - 1) + 1) % n;
2487
+ }
2488
+ return cur;
2489
+ }
2490
+ function blobPath(shape, rx, ry, cx = 0, cy = 0) {
2491
+ const pts = BLOB_SHAPES[(shape % BLOB_SHAPES.length + BLOB_SHAPES.length) % BLOB_SHAPES.length].map(([x, y]) => [cx + x * rx, cy + y * ry]);
2492
+ const mid = (a, b) => [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
2493
+ const f = (n) => n.toFixed(2);
2494
+ const start = mid(pts[pts.length - 1], pts[0]);
2495
+ let d = `M${f(start[0])} ${f(start[1])}`;
2496
+ for (let i = 0; i < pts.length; i++) {
2497
+ const p = pts[i], m = mid(p, pts[(i + 1) % pts.length]);
2498
+ d += `Q${f(p[0])} ${f(p[1])} ${f(m[0])} ${f(m[1])}`;
2499
+ }
2500
+ return d + "Z";
2501
+ }
2502
+ function drawBlob(ctx, shape, rx, ry, cx = 0, cy = 0) {
2503
+ const pts = BLOB_SHAPES[(shape % BLOB_SHAPES.length + BLOB_SHAPES.length) % BLOB_SHAPES.length].map(([x, y]) => [cx + x * rx, cy + y * ry]);
2504
+ const mid = (a, b) => [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
2505
+ const s = mid(pts[pts.length - 1], pts[0]);
2506
+ ctx.beginPath();
2507
+ ctx.moveTo(s[0], s[1]);
2508
+ pts.forEach((p, i) => {
2509
+ const m = mid(p, pts[(i + 1) % pts.length]);
2510
+ ctx.quadraticCurveTo(p[0], p[1], m[0], m[1]);
2511
+ });
2512
+ ctx.closePath();
2513
+ }
2514
+ function blobRadius(shape, rx, ry, angle) {
2515
+ const pts = BLOB_SHAPES[(shape % BLOB_SHAPES.length + BLOB_SHAPES.length) % BLOB_SHAPES.length].map(([x, y]) => [x * rx, y * ry]);
2516
+ const mid = (a, b) => [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
2517
+ const poly = [];
2518
+ for (let i = 0; i < pts.length; i++) {
2519
+ const p0 = mid(pts[(i - 1 + pts.length) % pts.length], pts[i]), c = pts[i], p1 = mid(pts[i], pts[(i + 1) % pts.length]);
2520
+ for (let k = 0; k < 8; k++) {
2521
+ const t = k / 8, u = 1 - t;
2522
+ poly.push([u * u * p0[0] + 2 * u * t * c[0] + t * t * p1[0], u * u * p0[1] + 2 * u * t * c[1] + t * t * p1[1]]);
2523
+ }
2524
+ }
2525
+ const dx = Math.cos(angle), dy = Math.sin(angle);
2526
+ let best = 0;
2527
+ for (let i = 0; i < poly.length; i++) {
2528
+ const [ax, ay] = poly[i], [bx, by] = poly[(i + 1) % poly.length], ex = bx - ax, ey = by - ay;
2529
+ const den = dx * ey - dy * ex;
2530
+ if (Math.abs(den) < 1e-9) continue;
2531
+ const r = (ax * ey - ay * ex) / den, q = (ax * dy - ay * dx) / den;
2532
+ if (r > 0 && q >= 0 && q <= 1) best = Math.max(best, r);
2533
+ }
2534
+ return best || Math.max(rx, ry);
2535
+ }
2536
+ var RINGS = [
2537
+ { scale: 1.1, opacity: 0.5, rotate: -5, width: 2, dash: "" },
2538
+ { scale: 1.2, opacity: 0.4, rotate: -10, width: 1, dash: "" },
2539
+ { scale: 1.3, opacity: 0.3, rotate: -15, width: 3, dash: "6 4" }
2540
+ ];
2541
+ function edgePath(a, b, bow = 1) {
2542
+ const dx = b.x - a.x, dy = b.y - a.y, d = Math.hypot(dx, dy) || 1;
2543
+ const arc = Math.min(45, 45 / (1 + Math.max(0, (d - 200) / 400))) * bow;
2544
+ const nx = -dy / d, ny = dx / d;
2545
+ const mx = (a.x + b.x) / 2 + nx * arc, my = (a.y + b.y) / 2 + ny * arc;
2546
+ const c1 = { x: a.x + (mx - a.x) * 0.66 + 0, y: a.y + (my - a.y) * 0.66 };
2547
+ const c2 = { x: b.x + (mx - b.x) * 0.66, y: b.y + (my - b.y) * 0.66 };
2548
+ const ang = Math.atan2(b.y - c2.y, b.x - c2.x);
2549
+ return { d: `M${a.x.toFixed(1)} ${a.y.toFixed(1)}C${c1.x.toFixed(1)} ${c1.y.toFixed(1)} ${c2.x.toFixed(1)} ${c2.y.toFixed(1)} ${b.x.toFixed(1)} ${b.y.toFixed(1)}`, angle: ang, mid: { x: mx, y: my } };
2550
+ }
2551
+ function arrowPath(p, angle, len = 11) {
2552
+ const s = Math.PI / 5.5;
2553
+ const a1 = { x: p.x - len * Math.cos(angle - s), y: p.y - len * Math.sin(angle - s) };
2554
+ const a2 = { x: p.x - len * Math.cos(angle + s), y: p.y - len * Math.sin(angle + s) };
2555
+ return `M${a1.x.toFixed(1)} ${a1.y.toFixed(1)}L${p.x.toFixed(1)} ${p.y.toFixed(1)}L${a2.x.toFixed(1)} ${a2.y.toFixed(1)}`;
2556
+ }
2557
+
2558
+ // src/rendering/nodeGeometry.ts
2559
+ var MAX_RINGS = RINGS.length;
2560
+ var DEG = Math.PI / 180;
2561
+ function nodeRadii(n) {
2562
+ return { rx: n.width / 2, ry: n.height / 2 };
2563
+ }
2564
+ var ringCount = (branchCount) => Math.max(0, Math.min(branchCount ?? 0, MAX_RINGS));
2565
+ var ringsFor = (branchCount) => RINGS.slice(0, ringCount(branchCount));
2566
+ var shapeCache = /* @__PURE__ */ new Map();
2567
+ function shapeOfLevel(level) {
2568
+ let s = shapeCache.get(level);
2569
+ if (s === void 0) {
2570
+ s = shapeForLevel(level);
2571
+ shapeCache.set(level, s);
2572
+ }
2573
+ return s;
2574
+ }
2575
+ var BLOB_MAX_NORM = Math.max(...BLOB_SHAPES.flatMap((s) => s.map(([x, y]) => Math.hypot(x, y))));
2576
+ var VISUAL_MARGIN = METRICS.nodeStroke * 2 + 24;
2577
+ function nodeBounds(n) {
2578
+ const { rx, ry } = nodeRadii(n);
2579
+ const rings = ringCount(n.branchCount);
2580
+ const scale = rings > 0 ? RINGS[rings - 1].scale : 1;
2581
+ const r = BLOB_MAX_NORM * Math.max(rx, ry) * scale + VISUAL_MARGIN;
2582
+ return { minX: n.x - r, minY: n.y - r, maxX: n.x + r, maxY: n.y + r };
2583
+ }
2584
+ function outlineRadius(n, angle) {
2585
+ const { rx, ry } = nodeRadii(n);
2586
+ const shape = shapeOfLevel(n.level);
2587
+ const rings = ringCount(n.branchCount);
2588
+ if (rings === 0) return blobRadius(shape, rx, ry, angle);
2589
+ const ring = RINGS[rings - 1];
2590
+ return blobRadius(shape, rx * ring.scale, ry * ring.scale, angle - ring.rotate * DEG);
2591
+ }
2592
+ function connectorGeometry(from, to, gap = METRICS.edgeGap) {
2593
+ const theta = Math.atan2(to.y - from.y, to.x - from.x);
2594
+ const ra = outlineRadius(from, theta) + gap;
2595
+ const rb = outlineRadius(to, theta + Math.PI) + gap;
2596
+ const a = { x: from.x + Math.cos(theta) * ra, y: from.y + Math.sin(theta) * ra };
2597
+ const b = { x: to.x - Math.cos(theta) * rb, y: to.y - Math.sin(theta) * rb };
2598
+ const e = edgePath(a, b);
2599
+ return { a, b, d: e.d, angle: e.angle, mid: e.mid, arrow: arrowPath(b, e.angle) };
1790
2600
  }
1791
2601
 
1792
2602
  // src/rendering/culling.ts
1793
2603
  var DEFAULT_SPATIAL_INDEX_THRESHOLD = 200;
2604
+ function intersectsViewport(n, bounds) {
2605
+ const b = nodeBounds(n);
2606
+ return b.maxX >= bounds.minX && b.minX <= bounds.maxX && b.maxY >= bounds.minY && b.minY <= bounds.maxY;
2607
+ }
1794
2608
  function getViewportVisibleNodes(options) {
1795
2609
  const {
1796
2610
  nodes,
@@ -1808,9 +2622,7 @@ function getViewportVisibleNodes(options) {
1808
2622
  if (skipCulling || isDragging) return filtered;
1809
2623
  const bounds = calculateViewportBounds(viewport, canvasWidth, canvasHeight);
1810
2624
  if (filtered.length < spatialIndexThreshold) {
1811
- return filtered.filter(
1812
- (n) => n.x + n.width >= bounds.minX && n.x <= bounds.maxX && n.y + n.height >= bounds.minY && n.y <= bounds.maxY
1813
- );
2625
+ return filtered.filter((n) => intersectsViewport(n, bounds));
1814
2626
  }
1815
2627
  if (spatialIndex) {
1816
2628
  const nodeIds = spatialIndex.query({
@@ -1822,372 +2634,192 @@ function getViewportVisibleNodes(options) {
1822
2634
  const idSet = new Set(nodeIds);
1823
2635
  return filtered.filter((n) => idSet.has(n.id));
1824
2636
  }
1825
- return filtered.filter(
1826
- (n) => n.x + n.width >= bounds.minX && n.x <= bounds.maxX && n.y + n.height >= bounds.minY && n.y <= bounds.maxY
1827
- );
2637
+ return filtered.filter((n) => intersectsViewport(n, bounds));
1828
2638
  }
1829
2639
 
1830
- // src/config/blobShapes.ts
1831
- var BLOB_LAYER_CONFIG = [
1832
- { opacity: 0.3, rotation: -15, scaleUp: 1.3, strokeWidth: 3, dash: [6, 4] },
1833
- // Layer #3 (outermost)
1834
- { opacity: 0.4, rotation: -10, scaleUp: 1.2, strokeWidth: 1, dash: void 0 },
1835
- // Layer #2
1836
- { opacity: 0.5, rotation: -5, scaleUp: 1.1, strokeWidth: 2, dash: void 0 }
1837
- // Layer #1
1838
- ];
1839
- var NODE_LAYER_CONFIG = { opacity: 1, rotation: 0, strokeWidth: 3 };
1840
- var ALL_BLOB_SHAPES = [
1841
- // Shape 0 · Blob 1
1842
- [
1843
- { x: 1.12, y: 0 },
1844
- { x: 0.86, y: 0.56 },
1845
- { x: 0.46, y: 0.94 },
1846
- { x: -0.08, y: 1.06 },
1847
- { x: -0.68, y: 0.82 },
1848
- { x: -0.96, y: 0.08 },
1849
- { x: -0.55, y: -0.82 },
1850
- { x: 0.04, y: -1.02 },
1851
- { x: 0.72, y: -0.8 },
1852
- { x: 1.08, y: -0.38 }
1853
- ],
1854
- // Shape 1 · Blob 2
1855
- [
1856
- { x: -0.489, y: -0.625 },
1857
- { x: -0.384, y: -0.716 },
1858
- { x: -0.285, y: -0.792 },
1859
- { x: -0.189, y: -0.855 },
1860
- { x: -0.09, y: -0.903 },
1861
- { x: 0.014, y: -0.932 },
1862
- { x: 0.129, y: -0.94 },
1863
- { x: 0.255, y: -0.924 },
1864
- { x: 0.412, y: -0.876 },
1865
- { x: 0.516, y: -0.832 },
1866
- { x: 0.612, y: -0.78 },
1867
- { x: 0.695, y: -0.725 },
1868
- { x: 0.767, y: -0.666 },
1869
- { x: 0.83, y: -0.606 },
1870
- { x: 0.885, y: -0.546 },
1871
- { x: 0.933, y: -0.486 },
1872
- { x: 0.979, y: -0.425 },
1873
- { x: 1.006, y: -0.385 },
1874
- { x: 1.034, y: -0.334 },
1875
- { x: 1.058, y: -0.276 },
1876
- { x: 1.078, y: -0.209 },
1877
- { x: 1.092, y: -0.133 },
1878
- { x: 1.101, y: -0.046 },
1879
- { x: 1.102, y: 0.052 },
1880
- { x: 1.094, y: 0.173 },
1881
- { x: 1.083, y: 0.244 },
1882
- { x: 1.063, y: 0.324 },
1883
- { x: 1.034, y: 0.401 },
1884
- { x: 0.996, y: 0.477 },
1885
- { x: 0.948, y: 0.549 },
1886
- { x: 0.889, y: 0.617 },
1887
- { x: 0.82, y: 0.681 },
1888
- { x: 0.735, y: 0.741 },
1889
- { x: 0.658, y: 0.783 },
1890
- { x: 0.569, y: 0.822 },
1891
- { x: 0.474, y: 0.855 },
1892
- { x: 0.372, y: 0.882 },
1893
- { x: 0.265, y: 0.904 },
1894
- { x: 0.156, y: 0.92 },
1895
- { x: 0.046, y: 0.931 },
1896
- { x: -0.069, y: 0.938 },
1897
- { x: -0.155, y: 0.935 },
1898
- { x: -0.25, y: 0.919 },
1899
- { x: -0.346, y: 0.89 },
1900
- { x: -0.443, y: 0.851 },
1901
- { x: -0.539, y: 0.8 },
1902
- { x: -0.633, y: 0.738 },
1903
- { x: -0.723, y: 0.667 },
1904
- { x: -0.808, y: 0.585 },
1905
- { x: -0.882, y: 0.501 },
1906
- { x: -0.944, y: 0.421 },
1907
- { x: -0.994, y: 0.343 },
1908
- { x: -1.034, y: 0.267 },
1909
- { x: -1.064, y: 0.192 },
1910
- { x: -1.085, y: 0.12 },
1911
- { x: -1.097, y: 0.042 },
1912
- { x: -1.102, y: 1e-3 },
1913
- { x: -1.09, y: -0.243 },
1914
- { x: -1.05, y: -0.365 },
1915
- { x: -0.99, y: -0.441 },
1916
- { x: -0.911, y: -0.479 },
1917
- { x: -0.819, y: -0.499 },
1918
- { x: -0.715, y: -0.517 },
1919
- { x: -0.605, y: -0.551 }
1920
- ],
1921
- // Shape 2 · Blob 3
1922
- [
1923
- { x: 3e-3, y: -0.97 },
1924
- { x: 0.163, y: -0.984 },
1925
- { x: 0.286, y: -1.028 },
1926
- { x: 0.389, y: -1.096 },
1927
- { x: 0.488, y: -1.174 },
1928
- { x: 0.597, y: -1.21 },
1929
- { x: 0.728, y: -1.165 },
1930
- { x: 0.867, y: -1.036 },
1931
- { x: 0.996, y: -0.839 },
1932
- { x: 1.098, y: -0.59 },
1933
- { x: 1.158, y: -0.304 },
1934
- { x: 1.153, y: 0.012 },
1935
- { x: 1.08, y: 0.336 },
1936
- { x: 0.944, y: 0.639 },
1937
- { x: 0.756, y: 0.889 },
1938
- { x: 0.484, y: 1.072 },
1939
- { x: 0.339, y: 1.079 },
1940
- { x: 0.219, y: 1.051 },
1941
- { x: 0.131, y: 0.994 },
1942
- { x: 0.059, y: 0.939 },
1943
- { x: 8e-3, y: 0.915 },
1944
- { x: -0.105, y: 0.931 },
1945
- { x: -0.176, y: 0.965 },
1946
- { x: -0.249, y: 1.005 },
1947
- { x: -0.345, y: 1.039 },
1948
- { x: -0.497, y: 1.055 },
1949
- { x: -0.672, y: 1.032 },
1950
- { x: -0.817, y: 0.952 },
1951
- { x: -0.945, y: 0.81 },
1952
- { x: -1.054, y: 0.6 },
1953
- { x: -1.16, y: 0.302 },
1954
- { x: -1.187, y: -0.011 },
1955
- { x: -1.165, y: -0.326 },
1956
- { x: -1.097, y: -0.599 },
1957
- { x: -0.991, y: -0.838 },
1958
- { x: -0.852, y: -0.912 },
1959
- { x: -0.695, y: -0.958 },
1960
- { x: -0.53, y: -0.988 },
1961
- { x: -0.357, y: -0.992 },
1962
- { x: -0.18, y: -0.983 }
1963
- ],
1964
- // Shape 3 · Blob 4
1965
- [
1966
- { x: -0.734, y: -1.127 },
1967
- { x: -0.656, y: -1.146 },
1968
- { x: -0.567, y: -1.142 },
1969
- { x: -0.468, y: -1.115 },
1970
- { x: -0.358, y: -1.079 },
1971
- { x: -0.238, y: -1.034 },
1972
- { x: -0.107, y: -0.989 },
1973
- { x: 0.035, y: -0.953 },
1974
- { x: 0.188, y: -0.929 },
1975
- { x: 0.325, y: -0.916 },
1976
- { x: 0.484, y: -0.896 },
1977
- { x: 0.65, y: -0.854 },
1978
- { x: 0.811, y: -0.77 },
1979
- { x: 0.955, y: -0.631 },
1980
- { x: 1.068, y: -0.422 },
1981
- { x: 1.138, y: -0.126 },
1982
- { x: 1.152, y: 0.272 },
1983
- { x: 1.121, y: 0.451 },
1984
- { x: 1.046, y: 0.602 },
1985
- { x: 0.928, y: 0.727 },
1986
- { x: 0.767, y: 0.833 },
1987
- { x: 0.565, y: 0.925 },
1988
- { x: 0.323, y: 1.001 },
1989
- { x: 0.042, y: 1.075 },
1990
- { x: -0.277, y: 1.142 },
1991
- { x: -0.368, y: 1.147 },
1992
- { x: -0.475, y: 1.136 },
1993
- { x: -0.589, y: 1.104 },
1994
- { x: -0.704, y: 1.05 },
1995
- { x: -0.815, y: 0.973 },
1996
- { x: -0.913, y: 0.871 },
1997
- { x: -0.993, y: 0.746 },
1998
- { x: -1.048, y: 0.593 },
1999
- { x: -1.109, y: 0.298 },
2000
- { x: -1.145, y: 0.018 },
2001
- { x: -1.154, y: -0.243 },
2002
- { x: -1.135, y: -0.482 },
2003
- { x: -1.085, y: -0.695 },
2004
- { x: -1.003, y: -0.874 },
2005
- { x: -0.886, y: -1.02 }
2006
- ],
2007
- // Shape 4 · Blob 5
2008
- [
2009
- { x: -0.734, y: 1.127 },
2010
- { x: -0.656, y: 1.146 },
2011
- { x: -0.567, y: 1.142 },
2012
- { x: -0.468, y: 1.115 },
2013
- { x: -0.358, y: 1.079 },
2014
- { x: -0.238, y: 1.034 },
2015
- { x: -0.107, y: 0.989 },
2016
- { x: 0.035, y: 0.953 },
2017
- { x: 0.188, y: 0.929 },
2018
- { x: 0.325, y: 0.916 },
2019
- { x: 0.484, y: 0.896 },
2020
- { x: 0.65, y: 0.854 },
2021
- { x: 0.811, y: 0.77 },
2022
- { x: 0.955, y: 0.631 },
2023
- { x: 1.068, y: 0.422 },
2024
- { x: 1.138, y: 0.126 },
2025
- { x: 1.152, y: -0.272 },
2026
- { x: 1.121, y: -0.451 },
2027
- { x: 1.046, y: -0.602 },
2028
- { x: 0.928, y: -0.727 },
2029
- { x: 0.767, y: -0.833 },
2030
- { x: 0.565, y: -0.925 },
2031
- { x: 0.323, y: -1.001 },
2032
- { x: 0.042, y: -1.075 },
2033
- { x: -0.277, y: -1.142 },
2034
- { x: -0.368, y: -1.147 },
2035
- { x: -0.475, y: -1.136 },
2036
- { x: -0.589, y: -1.104 },
2037
- { x: -0.704, y: -1.05 },
2038
- { x: -0.815, y: -0.973 },
2039
- { x: -0.913, y: -0.871 },
2040
- { x: -0.993, y: -0.746 },
2041
- { x: -1.048, y: -0.593 },
2042
- { x: -1.109, y: -0.298 },
2043
- { x: -1.145, y: -0.018 },
2044
- { x: -1.154, y: 0.243 },
2045
- { x: -1.135, y: 0.482 },
2046
- { x: -1.085, y: 0.695 },
2047
- { x: -1.003, y: 0.874 },
2048
- { x: -0.886, y: 1.02 }
2049
- ],
2050
- // Shape 5 · Blob 6
2051
- [
2052
- { x: 1e-3, y: -1.006 },
2053
- { x: 0.134, y: -1.001 },
2054
- { x: 0.262, y: -1.024 },
2055
- { x: 0.382, y: -1.073 },
2056
- { x: 0.498, y: -1.107 },
2057
- { x: 0.614, y: -1.105 },
2058
- { x: 0.733, y: -1.049 },
2059
- { x: 0.85, y: -0.932 },
2060
- { x: 0.953, y: -0.755 },
2061
- { x: 1.03, y: -0.529 },
2062
- { x: 1.067, y: -0.265 },
2063
- { x: 1.054, y: 0.018 },
2064
- { x: 0.986, y: 0.3 },
2065
- { x: 0.868, y: 0.557 },
2066
- { x: 0.715, y: 0.766 },
2067
- { x: 0.55, y: 0.909 },
2068
- { x: 0.396, y: 0.984 },
2069
- { x: 0.262, y: 1.005 },
2070
- { x: 0.148, y: 1.003 },
2071
- { x: 0.037, y: 1.008 },
2072
- { x: -0.083, y: 1.04 },
2073
- { x: -0.22, y: 1.118 },
2074
- { x: -0.36, y: 1.192 },
2075
- { x: -0.5, y: 1.234 },
2076
- { x: -0.65, y: 1.175 },
2077
- { x: -0.81, y: 1.045 },
2078
- { x: -0.99, y: 0.855 },
2079
- { x: -1.14, y: 0.61 },
2080
- { x: -1.24, y: 0.33 },
2081
- { x: -1.27, y: 0.04 },
2082
- { x: -1.275, y: -0.25 },
2083
- { x: -1.225, y: -0.5 },
2084
- { x: -1.08, y: -0.72 },
2085
- { x: -0.89, y: -0.92 },
2086
- { x: -0.734, y: -1.053 },
2087
- { x: -0.614, y: -1.112 },
2088
- { x: -0.498, y: -1.127 },
2089
- { x: -0.381, y: -1.11 },
2090
- { x: -0.261, y: -1.095 },
2091
- { x: -0.133, y: -1.058 },
2092
- { x: 1e-3, y: -1.01 },
2093
- { x: 0.134, y: -1.001 }
2094
- ]
2095
- ];
2096
- var ACTIVE_BLOB_TYPES = [0, 1, 2, 3, 4, 5];
2097
- var BLOB_TEXT_OFFSETS = [0, 0, 0, 0, 0, -0.1];
2098
- function getBlobTypeForLayer(level) {
2099
- const numTypes = ACTIVE_BLOB_TYPES.length;
2100
- if (numTypes <= 1) return ACTIVE_BLOB_TYPES[0] ?? 0;
2101
- let currentType = 0;
2102
- for (let i = 1; i <= level; i++) {
2103
- const pseudoRandom = Math.floor(Math.abs(Math.sin((i + 1) * 12.9898) * 43758.5453));
2104
- const stepOffset = pseudoRandom % (numTypes - 1) + 1;
2105
- currentType = (currentType + stepOffset) % numTypes;
2106
- }
2107
- return ACTIVE_BLOB_TYPES[currentType];
2640
+ // src/generated/geometry/motion.ts
2641
+ var EASE = {
2642
+ /** ease-standard — UI and connector drawing. */
2643
+ standard: [0.4, 0, 0.2, 1],
2644
+ /** ease-zoom — diving into and out of layers. */
2645
+ zoom: [0.2, 0.8, 0.2, 1],
2646
+ /** ease-bloom — ideas arriving, with a little overshoot. */
2647
+ bloom: [0.34, 1.4, 0.64, 1]
2648
+ };
2649
+ var MOTION = {
2650
+ /** A layer arrives: ideas bloom one by one in sibling order, center first, then connectors draw. */
2651
+ reveal: {
2652
+ /** duration-reveal: each idea's bloom. */
2653
+ duration: 420,
2654
+ /** stagger-reveal: delay between consecutive ideas. */
2655
+ stagger: 70,
2656
+ /** An idea starts at this scale… */
2657
+ fromScale: 0.55,
2658
+ /** …and this fraction of the way from its place toward the layer's center. */
2659
+ fromCenter: 0.5,
2660
+ ease: EASE.bloom,
2661
+ /** Settle time after the last bloom before the map is idle (interactive again). */
2662
+ tail: 200
2663
+ },
2664
+ /** Connectors of an arriving layer: they start once the ideas are mostly in. */
2665
+ connector: {
2666
+ /** Lead time before the first connector, on top of the ideas' stagger. */
2667
+ lead: 240,
2668
+ /** Delay between consecutive connectors. */
2669
+ stagger: 60,
2670
+ /** A solid connector draws from source to target; a dashed one fades in instead. */
2671
+ draw: 500,
2672
+ /** The connector group's fade (duration-base). */
2673
+ fade: 240,
2674
+ ease: EASE.standard
2675
+ },
2676
+ /** Step inside: the current layer zooms toward the idea and fades; the inner layer then arrives (reveal). */
2677
+ dive: {
2678
+ /** The old layer's zoom, toward the idea's position. */
2679
+ scale: 3.2,
2680
+ /** duration-zoom. */
2681
+ duration: 500,
2682
+ /** It fades out after this delay, over `fade`. */
2683
+ fadeDelay: 150,
2684
+ fade: 160,
2685
+ /** The layers swap (and the reveal starts) this long after the dive starts. */
2686
+ swapAfter: 320,
2687
+ ease: EASE.zoom
2688
+ },
2689
+ /** Back out (depth trail): the current layer shrinks toward the center and fades; the outer layer arrives. */
2690
+ surface: {
2691
+ scale: 0.6,
2692
+ duration: 220,
2693
+ fade: 200,
2694
+ swapAfter: 220,
2695
+ ease: EASE.zoom
2696
+ },
2697
+ /** The arriving layer as a whole settles from slightly small. */
2698
+ settle: { fromScale: 0.94, duration: 260, ease: EASE.standard },
2699
+ /** Hover (pointer) or select: the idea lifts; its rings turn and grow a touch — a nudge, not a wobble. */
2700
+ hover: {
2701
+ lift: 2,
2702
+ ringRotate: -2,
2703
+ ringScale: 1.015,
2704
+ /** duration-base, for both the lift (ease-bloom) and the rings (ease-standard). */
2705
+ duration: 240,
2706
+ liftEase: EASE.bloom,
2707
+ ringEase: EASE.standard
2708
+ },
2709
+ /** Reduced motion, or Gradual reveal off: everything appears together with a short fade; dives become cross-fades. */
2710
+ reduced: { fade: 120 },
2711
+ /** Large layers. The stagger is written for a dozen ideas; a layer of hundreds would take minutes to arrive and
2712
+ * every frame of a bloom repaints the whole layer. Above `staggerSlots` ideas (and connectors), they share that many
2713
+ * slots, so a layer never takes longer to arrive than `staggerSlots` ideas would. Above `bloomMax` ideas the layer
2714
+ * fades in together (the reduced fade) and hover lifts snap instead of easing. */
2715
+ largeLayer: { staggerSlots: 30, bloomMax: 150 }
2716
+ };
2717
+ function revealDelay(i) {
2718
+ return i * MOTION.reveal.stagger;
2108
2719
  }
2109
- function drawBlobPath(ctx, radiusX, radiusY, blobType) {
2110
- const srcPts = ALL_BLOB_SHAPES[blobType] ?? ALL_BLOB_SHAPES[0];
2111
- const pts = srcPts.map((p) => ({ x: p.x * radiusX, y: p.y * radiusY }));
2112
- ctx.beginPath();
2113
- const last = pts[pts.length - 1];
2114
- const first = pts[0];
2115
- ctx.moveTo((last.x + first.x) / 2, (last.y + first.y) / 2);
2116
- for (let i = 0; i < pts.length; i++) {
2117
- const curr = pts[i];
2118
- const next = pts[(i + 1) % pts.length];
2119
- ctx.quadraticCurveTo(curr.x, curr.y, (curr.x + next.x) / 2, (curr.y + next.y) / 2);
2120
- }
2121
- ctx.closePath();
2720
+ function connectorDelay(i, nodeCount) {
2721
+ return MOTION.connector.lead + nodeCount * MOTION.reveal.stagger + i * MOTION.connector.stagger;
2722
+ }
2723
+ function revealTotal(nodeCount, reduced = false) {
2724
+ return reduced ? 0 : MOTION.reveal.duration + nodeCount * MOTION.reveal.stagger + MOTION.reveal.tail;
2725
+ }
2726
+ function bloomOffset(node, center) {
2727
+ const k = MOTION.reveal.fromCenter;
2728
+ return { x: (center.x - node.x) * k, y: (center.y - node.y) * k };
2729
+ }
2730
+ function swapDelay(dir, reduced = false) {
2731
+ return reduced ? 0 : dir === "in" ? MOTION.dive.swapAfter : MOTION.surface.swapAfter;
2732
+ }
2733
+ function revealSlot(i, count) {
2734
+ const slots = Math.min(count, MOTION.largeLayer.staggerSlots);
2735
+ return count > slots ? Math.floor(i * slots / count) : i;
2736
+ }
2737
+ function layerBlooms(nodeCount) {
2738
+ return nodeCount <= MOTION.largeLayer.bloomMax;
2739
+ }
2740
+ function layerReveal(nodeCount, connectorCount, reduced = false) {
2741
+ const instant = reduced || !layerBlooms(nodeCount);
2742
+ const slots = Math.min(nodeCount, MOTION.largeLayer.staggerSlots);
2743
+ const edgeSlots = Math.min(connectorCount, MOTION.largeLayer.staggerSlots);
2744
+ return {
2745
+ /** Fades in together (reduced motion, Gradual reveal off, or a layer past `bloomMax`). */
2746
+ instant,
2747
+ ideaDelay: (i) => instant ? 0 : revealDelay(revealSlot(i, nodeCount)),
2748
+ connectorDelay: (i) => instant ? 0 : connectorDelay(revealSlot(i, connectorCount), slots),
2749
+ /** Until the map is idle again. */
2750
+ total: instant ? reduced ? 0 : MOTION.reduced.fade : Math.max(revealTotal(slots), edgeSlots ? connectorDelay(edgeSlots - 1, slots) + MOTION.connector.draw : 0)
2751
+ };
2752
+ }
2753
+ function cssEase(e) {
2754
+ return `cubic-bezier(${e.join(", ")})`;
2755
+ }
2756
+ function ease(e, t) {
2757
+ const [x1, y1, x2, y2] = e;
2758
+ if (t <= 0) return 0;
2759
+ if (t >= 1) return 1;
2760
+ const bx = (u2) => 3 * x1 * u2 * (1 - u2) ** 2 + 3 * x2 * u2 * u2 * (1 - u2) + u2 ** 3;
2761
+ const by = (u2) => 3 * y1 * u2 * (1 - u2) ** 2 + 3 * y2 * u2 * u2 * (1 - u2) + u2 ** 3;
2762
+ let lo = 0, hi = 1, u = t;
2763
+ for (let k = 0; k < 24; k++) {
2764
+ u = (lo + hi) / 2;
2765
+ if (bx(u) < t) lo = u;
2766
+ else hi = u;
2767
+ }
2768
+ return by(u);
2769
+ }
2770
+ function motionVars() {
2771
+ const m = MOTION;
2772
+ return {
2773
+ "--vi-bloom-from": String(m.reveal.fromScale),
2774
+ "--vi-line-draw": `${m.connector.draw}ms`,
2775
+ "--vi-dive-scale": String(m.dive.scale),
2776
+ "--vi-dive-fade": `${m.dive.fade}ms`,
2777
+ "--vi-dive-fade-delay": `${m.dive.fadeDelay}ms`,
2778
+ "--vi-surface-scale": String(m.surface.scale),
2779
+ "--vi-surface-dur": `${m.surface.duration}ms`,
2780
+ "--vi-surface-fade": `${m.surface.fade}ms`,
2781
+ "--vi-settle-from": String(m.settle.fromScale),
2782
+ "--vi-settle-dur": `${m.settle.duration}ms`,
2783
+ "--vi-reduced-fade": `${m.reduced.fade}ms`,
2784
+ "--vi-lift": `${m.hover.lift}px`,
2785
+ "--vi-ring-turn": `${m.hover.ringRotate}deg`,
2786
+ "--vi-ring-grow": String(m.hover.ringScale)
2787
+ };
2122
2788
  }
2123
2789
 
2124
- // src/config/textScaling.ts
2125
- var NODE_TEXT_BASE_FONT_PX = 28;
2126
- var DESCRIPTION_TEXT_BASE_FONT_PX = 12;
2127
- var CONTAINER_LABEL_BASE_FONT_PX = 24;
2128
- var EDGE_LABEL_BASE_FONT_PX = 18;
2129
- var OVERLAY_FONT_UPPER_BOOST_PX = 8;
2130
- var NODE_TEXT_SAFE_BLOB_FILL_RATIO = 0.88;
2131
- function computeNodeTextWorldScale(nodeWidth, zoomLevel) {
2132
- const safeZoom = Math.max(zoomLevel, 1e-4);
2133
- const baseRadiusX = nodeWidth / 2;
2134
- const baseRadiusY = Math.max(NODE_HEIGHT * 1.6 / 2, baseRadiusX * 0.74);
2135
- const textNaturalWidth = nodeWidth - 40;
2136
- const textNaturalHeight = NODE_HEIGHT - 20;
2137
- const maxScaleX = baseRadiusX * 2 * NODE_TEXT_SAFE_BLOB_FILL_RATIO / Math.max(textNaturalWidth, 1);
2138
- const maxScaleY = baseRadiusY * 2 * NODE_TEXT_SAFE_BLOB_FILL_RATIO / Math.max(textNaturalHeight, 1);
2139
- const fitScale = Math.max(0.01, Math.min(maxScaleX, maxScaleY));
2140
- return Math.min(1 / safeZoom, fitScale);
2141
- }
2142
- function computeNodeTextScreenScale(nodeWidth, zoomLevel) {
2143
- return computeNodeTextWorldScale(nodeWidth, zoomLevel) * zoomLevel;
2144
- }
2145
- function computeOverlayScale(zoomLevel) {
2146
- const safeZoom = Math.max(zoomLevel, 1e-4);
2147
- const inverseScale = 1 / safeZoom;
2148
- const maxScale = (NODE_TEXT_BASE_FONT_PX + OVERLAY_FONT_UPPER_BOOST_PX) / NODE_TEXT_BASE_FONT_PX;
2149
- return Math.min(Math.max(inverseScale, 1), maxScale);
2150
- }
2151
- function computeEdgeLabelScale(zoomLevel) {
2152
- const safeZoom = Math.max(zoomLevel, 1e-4);
2153
- return Math.min(Math.max(EDGE_LABEL_BASE_FONT_PX / safeZoom, 10), 32);
2790
+ // src/generated/geometry/detail.ts
2791
+ var DETAIL = {
2792
+ /** Below this on-screen width an idea draws only its body: no rings, label or shadow. */
2793
+ bodyOnlyBelow: 24,
2794
+ /** Below this it may be drawn as a plain filled speck in its topic fill (no outline) — indistinguishable at this size. */
2795
+ speckBelow: 12,
2796
+ /** Above this many ideas in view, idea shadows are skipped (the costliest effect to draw). */
2797
+ shadowsMaxVisible: 150
2798
+ };
2799
+ function ideaDetail(screenWidth, emphasised = false) {
2800
+ if (emphasised || screenWidth >= DETAIL.bodyOnlyBelow) return "full";
2801
+ return screenWidth < DETAIL.speckBelow ? "speck" : "body";
2802
+ }
2803
+ function shadowsShown(visibleIdeas) {
2804
+ return visibleIdeas <= DETAIL.shadowsMaxVisible;
2154
2805
  }
2155
2806
  // Annotate the CommonJS export names for ESM import in node:
2156
2807
  0 && (module.exports = {
2157
- ACTIVE_BLOB_TYPES,
2158
- ALL_BLOB_SHAPES,
2159
- ANIMATION_DURATION,
2160
- ANIMATION_PHASES,
2161
- BLOB_LAYER_CONFIG,
2162
- BLOB_TEXT_OFFSETS,
2163
- BORDER_WIDTH_NORMAL,
2164
- BORDER_WIDTH_SELECTED,
2165
- BRAND_COLORS,
2166
- CANVAS_COLORS,
2167
- CHAR_WIDTH,
2808
+ BLOB_SHAPES,
2809
+ CANVAS_STYLE,
2168
2810
  CIRCULAR_REFERENCE_MAX_DEPTH,
2169
2811
  CLICK_THRESHOLD,
2170
- COLOR_BORDER_DARK,
2171
- COLOR_BORDER_LIGHT,
2172
- COLOR_DEFAULT_NODE,
2173
- COLOR_SELECTED,
2174
- CONTAINER_LABEL_BASE_FONT_PX,
2175
- COOLDOWN,
2176
- CORNER_RADIUS,
2177
2812
  CULLING_QUERY_TIME_BUDGET,
2813
+ CUSTOM,
2178
2814
  DEFAULT_CANVAS_HEIGHT,
2179
2815
  DEFAULT_CANVAS_WIDTH,
2180
2816
  DEFAULT_RENDER_CONFIG,
2181
- DESCRIPTION_TEXT_BASE_FONT_PX,
2817
+ DESIGN_SYSTEM_VERSION,
2818
+ DETAIL,
2182
2819
  DOUBLE_CLICK_DELAY,
2183
2820
  DRAG_START_DISTANCE,
2184
- DS_COLORS,
2185
- DS_RADII,
2186
- DS_TYPOGRAPHY,
2187
- EDGE_LABEL_BASE_FONT_PX,
2821
+ EASE,
2188
2822
  FEATURE_FLAGS,
2189
- FONTS,
2190
- FONT_SIZES,
2191
2823
  FPSMonitor,
2192
2824
  FPS_CRITICAL_THRESHOLD,
2193
2825
  FPS_DOWNGRADE_THRESHOLD,
@@ -2198,27 +2830,29 @@ function computeEdgeLabelScale(zoomLevel) {
2198
2830
  FPS_UPDATE_INTERVAL,
2199
2831
  FPS_UPGRADE_THRESHOLD,
2200
2832
  FPS_WARNING_THRESHOLD,
2833
+ FRAME,
2834
+ GESTURE,
2835
+ HULL,
2836
+ HULL_CLEAR,
2837
+ IDEA,
2838
+ LABEL_GROWTH,
2201
2839
  LAYOUT_SPACING,
2202
- LEVEL_COLORS,
2203
- LEVEL_COLOR_ARRAY,
2204
2840
  MAX_FRAME_TIME,
2205
2841
  MAX_NODES,
2842
+ MAX_RINGS,
2206
2843
  MAX_VISIBLE_NODES,
2207
2844
  MEMORY_CHECK_INTERVAL,
2208
2845
  MEMORY_CRITICAL_MB,
2209
2846
  MEMORY_LIMITS,
2210
2847
  MEMORY_TARGET_MB,
2211
2848
  MEMORY_WARNING_MB,
2849
+ METRICS,
2850
+ METRICS_REDUCED_MOTION,
2851
+ MOTION,
2212
2852
  MemoryMonitor,
2213
2853
  NODE_COUNT_THRESHOLDS,
2214
- NODE_HEIGHT,
2215
- NODE_LAYER_CONFIG,
2216
- NODE_MIN_WIDTH,
2217
- NODE_PADDING,
2218
- NODE_TEXT_BASE_FONT_PX,
2219
- OPACITY,
2220
- OVERLAY_FONT_UPPER_BOOST_PX,
2221
- PARENT_FALLBACK_COLORS,
2854
+ PEEK,
2855
+ PEEK_PLACEMENT,
2222
2856
  PERFORMANCE_TOAST_COOLDOWN,
2223
2857
  PerformanceProfiler,
2224
2858
  QUALITY_HIGH,
@@ -2230,33 +2864,37 @@ function computeEdgeLabelScale(zoomLevel) {
2230
2864
  RBushSpatialIndex,
2231
2865
  RENDER_MODES,
2232
2866
  RESIZE_DEBOUNCE,
2867
+ RINGS,
2233
2868
  SCROLL_THROTTLE,
2234
2869
  SEARCH_DEBOUNCE,
2235
- SEMANTIC_COLORS,
2870
+ SHAPE,
2871
+ SHEET,
2236
2872
  SIMPLIFIED_RENDER_CONFIG,
2237
- SPACING,
2238
2873
  SPATIAL_INDEX_BUILD_BUDGET,
2239
2874
  TARGET_FRAME_TIME,
2240
- TARGET_ZOOM,
2241
- TEXT_LABEL_HIDE_BELOW_ZOOM,
2242
- THEME_COLORS,
2875
+ TERM,
2876
+ THEME_NAMES,
2243
2877
  TIMING_THRESHOLDS,
2244
- TITLE_MAX_LENGTH,
2878
+ TOKENS,
2879
+ TOPICS,
2880
+ TOPIC_ORDER,
2881
+ TRAIL,
2882
+ TYPE_STYLES,
2883
+ VIEW,
2245
2884
  VIEWPORT_CONFIG,
2246
- VIEWPORT_FRAMING,
2247
2885
  VIEWPORT_MARGIN,
2248
2886
  VisualliParseError,
2249
2887
  ZOOM_DEFAULT,
2250
2888
  ZOOM_MAX,
2251
2889
  ZOOM_MIN,
2252
- ZOOM_NAV_IN_THRESHOLD,
2253
- ZOOM_NAV_OUT_THRESHOLD,
2254
- ZOOM_STEP,
2255
- ZOOM_THRESHOLDS,
2256
- ZOOM_WHEEL_SENSITIVITY,
2257
2890
  applyCircularLayout,
2258
2891
  applyLinearHorizontalLayout,
2259
2892
  applyLinearVerticalLayout,
2893
+ approximateMeasure,
2894
+ arrowPath,
2895
+ blobPath,
2896
+ blobRadius,
2897
+ bloomOffset,
2260
2898
  calculateDistance,
2261
2899
  calculateFitCenter,
2262
2900
  calculateFitView,
@@ -2266,32 +2904,26 @@ function computeEdgeLabelScale(zoomLevel) {
2266
2904
  calculateViewportBounds,
2267
2905
  checkCollision,
2268
2906
  clampZoom,
2269
- computeEdgeLabelScale,
2270
- computeNodeTextScreenScale,
2271
- computeNodeTextWorldScale,
2272
- computeOverlayScale,
2907
+ connectorDelay,
2908
+ connectorGeometry,
2909
+ connectorSamples,
2910
+ containerHull,
2273
2911
  convertLayerToFlatNodes,
2274
2912
  convertVisualliToFlatNodes,
2275
2913
  countLayersBeneath,
2276
- createCubicBezier,
2277
2914
  createMindMapConfig,
2278
- darkenHexColor,
2279
- drawBlobPath,
2280
- easeInCubic,
2281
- easeInOutCubic,
2282
- easeInOutQuart,
2283
- easeInOutSine,
2284
- easeOutCubic,
2285
- easeOutQuad,
2286
- easeZoomTransition,
2915
+ cssEase,
2916
+ customColor,
2917
+ drawBlob,
2918
+ easeBezier,
2919
+ edgePath,
2287
2920
  fetchAndParseVisualli,
2921
+ fitView,
2288
2922
  flattenNodes,
2289
2923
  focusOnNode,
2290
2924
  generateSampleConfig,
2291
- getBlobTypeForLayer,
2292
2925
  getChildLayerForNode,
2293
2926
  getChildLayers,
2294
- getColorForLevel,
2295
2927
  getConnectionsForLayer,
2296
2928
  getContainersForLayer,
2297
2929
  getLayerForNavigation,
@@ -2301,27 +2933,70 @@ function computeEdgeLabelScale(zoomLevel) {
2301
2933
  getOptimalQualityLevel,
2302
2934
  getRecommendedRenderConfig,
2303
2935
  getRenderConfigForQuality,
2304
- getThemeBackground,
2305
- getThemeBorder,
2306
- getThemeText,
2936
+ getSemanticAnchors,
2937
+ getTokens,
2307
2938
  getTopLevelConnections,
2308
2939
  getViewportVisibleNodes,
2309
2940
  getVisibleNodes,
2941
+ hoverOpensTerm,
2942
+ ideaColor,
2943
+ ideaDetail,
2944
+ ideaKindOf,
2945
+ ideaSize,
2946
+ ideaStyle,
2947
+ intersectsViewport,
2948
+ isContrastTheme,
2949
+ isDarkTheme,
2310
2950
  isFeatureEnabled,
2951
+ isFocusTheme,
2311
2952
  isNodeInViewport,
2953
+ isThemeName,
2954
+ labelCandidates,
2955
+ labelGrowth,
2956
+ labelScale,
2957
+ layerBlooms,
2958
+ layerBounds,
2959
+ layerFrame,
2960
+ layerReveal,
2312
2961
  loadVisualliFileFromFile,
2962
+ metricsFor,
2963
+ motionVars,
2964
+ nodeBounds,
2965
+ nodeRadii,
2313
2966
  optimizeLayout,
2967
+ outlineRadius,
2314
2968
  panViewport,
2315
2969
  parseVisualliFile,
2316
2970
  parseVisualliFromMongoDB,
2971
+ peekPosition,
2972
+ pinchZoom,
2973
+ placeContainerLabel,
2974
+ pointerModeFor,
2317
2975
  recalculateViewportBounds,
2318
2976
  renderConfigStore,
2319
2977
  resolveCollisions,
2978
+ resolveComfort,
2320
2979
  resolveNodeOverlaps,
2980
+ resolveTheme,
2981
+ revealDelay,
2982
+ revealSlot,
2983
+ revealTotal,
2984
+ ringCount,
2985
+ ringsFor,
2321
2986
  screenToWorld,
2322
2987
  selectionStore,
2323
2988
  setViewportCenter,
2989
+ shadowsShown,
2990
+ shapeForLevel,
2991
+ shapeOfLevel,
2992
+ swapDelay,
2993
+ token,
2994
+ topicFor,
2995
+ topicFromName,
2996
+ topicStyle,
2324
2997
  worldToScreen,
2998
+ wrapLines,
2999
+ zoomAround,
2325
3000
  zoomViewport
2326
3001
  });
2327
3002
  //# sourceMappingURL=index.cjs.map