@tldiagram/core-ui 1.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/dist/App.d.ts +1 -0
  2. package/dist/api/client.d.ts +143 -0
  3. package/dist/api/transport-vscode.d.ts +8 -0
  4. package/dist/api/transport.d.ts +1 -0
  5. package/dist/components/CodePreviewPanel-vscode.d.ts +7 -0
  6. package/dist/components/CodePreviewPanel.d.ts +9 -0
  7. package/dist/components/ConfirmDialog.d.ts +12 -0
  8. package/dist/components/ConnectorPanel.d.ts +21 -0
  9. package/dist/components/ContextBoundaryElement.d.ts +11 -0
  10. package/dist/components/ContextNeighborElement.d.ts +29 -0
  11. package/dist/components/ContextStraightConnector.d.ts +4 -0
  12. package/dist/components/CrossBranchControls.d.ts +9 -0
  13. package/dist/components/DependenciesOnboarding.d.ts +5 -0
  14. package/dist/components/DrawingCanvas.d.ts +39 -0
  15. package/dist/components/ElementLibrary-vscode.d.ts +7 -0
  16. package/dist/components/ElementLibrary.d.ts +22 -0
  17. package/dist/components/ElementNode.d.ts +36 -0
  18. package/dist/components/ElementPanel.d.ts +25 -0
  19. package/dist/components/ExploreOnboarding.d.ts +5 -0
  20. package/dist/components/ExplorePageOnboarding.d.ts +5 -0
  21. package/dist/components/ExportModal.d.ts +16 -0
  22. package/dist/components/FloatingEdge.d.ts +9 -0
  23. package/dist/components/GitSourceLinker.d.ts +8 -0
  24. package/dist/components/HeaderContext.d.ts +16 -0
  25. package/dist/components/Icons.d.ts +95 -0
  26. package/dist/components/ImportModal.d.ts +10 -0
  27. package/dist/components/InlineElementAdder.d.ts +17 -0
  28. package/dist/components/LayoutSection.d.ts +7 -0
  29. package/dist/components/LocalSourceLinker.d.ts +8 -0
  30. package/dist/components/MiniZoomOnboarding.d.ts +5 -0
  31. package/dist/components/NavBreadcrumb.d.ts +6 -0
  32. package/dist/components/NodeBody.d.ts +12 -0
  33. package/dist/components/NodeContainer.d.ts +8 -0
  34. package/dist/components/NodeHoverCard.d.ts +10 -0
  35. package/dist/components/PanelHeader.d.ts +8 -0
  36. package/dist/components/PanelUI.d.ts +3 -0
  37. package/dist/components/ProxyConnectorEdge.d.ts +4 -0
  38. package/dist/components/ProxyConnectorPanel.d.ts +9 -0
  39. package/dist/components/SafeBackground.d.ts +13 -0
  40. package/dist/components/ScrollIndicatorWrapper.d.ts +8 -0
  41. package/dist/components/SetChildModal.d.ts +10 -0
  42. package/dist/components/SetParentModal.d.ts +10 -0
  43. package/dist/components/SlidingPanel.d.ts +16 -0
  44. package/dist/components/TagUpsert.d.ts +8 -0
  45. package/dist/components/TopMenuBar.d.ts +8 -0
  46. package/dist/components/ViewBezierConnector.d.ts +4 -0
  47. package/dist/components/ViewDrawMenu.d.ts +22 -0
  48. package/dist/components/ViewEditorEdgeLabelLayout.d.ts +16 -0
  49. package/dist/components/ViewEditorOnboarding.d.ts +5 -0
  50. package/dist/components/ViewExplorer/TagManager/ColorPicker.d.ts +7 -0
  51. package/dist/components/ViewExplorer/TagManager/GroupNamingPopover.d.ts +10 -0
  52. package/dist/components/ViewExplorer/TagManager/LayerItem.d.ts +27 -0
  53. package/dist/components/ViewExplorer/TagManager/TagItem.d.ts +25 -0
  54. package/dist/components/ViewExplorer/TagManager/index.d.ts +21 -0
  55. package/dist/components/ViewExplorer/ViewNavigator.d.ts +11 -0
  56. package/dist/components/ViewExplorer/ViewSearch.d.ts +8 -0
  57. package/dist/components/ViewExplorer/ViewTree.d.ts +18 -0
  58. package/dist/components/ViewExplorer/index.d.ts +31 -0
  59. package/dist/components/ViewExplorer/types.d.ts +11 -0
  60. package/dist/components/ViewExplorer/utils.d.ts +6 -0
  61. package/dist/components/ViewExplorer-vscode.d.ts +6 -0
  62. package/dist/components/ViewFloatingMenu-vscode.d.ts +27 -0
  63. package/dist/components/ViewFloatingMenu.d.ts +39 -0
  64. package/dist/components/ViewGridNode.d.ts +29 -0
  65. package/dist/components/ViewHeaderButton.d.ts +11 -0
  66. package/dist/components/ViewPanel.d.ts +18 -0
  67. package/dist/components/ViewsGridOnboarding.d.ts +5 -0
  68. package/dist/components/ZUI/ZUICanvas.d.ts +18 -0
  69. package/dist/components/ZUI/index.d.ts +2 -0
  70. package/dist/components/ZUI/layout.d.ts +18 -0
  71. package/dist/components/ZUI/proxy.d.ts +25 -0
  72. package/dist/components/ZUI/renderer.d.ts +30 -0
  73. package/dist/components/ZUI/types.d.ts +140 -0
  74. package/dist/components/ZUI/useZUIInteraction.d.ts +21 -0
  75. package/dist/config/runtime-vscode.d.ts +22 -0
  76. package/dist/config/runtime.d.ts +5 -0
  77. package/dist/constants/colors.d.ts +27 -0
  78. package/dist/constants/diagramColors.d.ts +1 -0
  79. package/dist/context/ThemeContext.d.ts +27 -0
  80. package/dist/crossBranch/graph.d.ts +13 -0
  81. package/dist/crossBranch/resolve.d.ts +22 -0
  82. package/dist/crossBranch/settings.d.ts +6 -0
  83. package/dist/crossBranch/store.d.ts +11 -0
  84. package/dist/crossBranch/types.d.ts +96 -0
  85. package/dist/demo/DemoPage.d.ts +9 -0
  86. package/dist/demo/seed.d.ts +9 -0
  87. package/dist/demo/store.d.ts +137 -0
  88. package/dist/demo/viewEditor.d.ts +26 -0
  89. package/dist/favicon.svg +35 -0
  90. package/dist/hooks/useSafeFitView.d.ts +16 -0
  91. package/dist/index.css +1 -0
  92. package/dist/index.d.ts +115 -0
  93. package/dist/index.js +19966 -0
  94. package/dist/lib/vscodeBridge-vscode.d.ts +13 -0
  95. package/dist/lib/vscodeBridge.d.ts +5 -0
  96. package/dist/logo-120.png +0 -0
  97. package/dist/logo-bw.png +0 -0
  98. package/dist/logo-bw.svg +15 -0
  99. package/dist/logo-text.svg +51 -0
  100. package/dist/logo.svg +35 -0
  101. package/dist/pages/AppearanceSettings.d.ts +3 -0
  102. package/dist/pages/Dependencies.d.ts +1 -0
  103. package/dist/pages/InfiniteZoom.d.ts +7 -0
  104. package/dist/pages/Settings.d.ts +7 -0
  105. package/dist/pages/ViewEditor/components/EditorMenus.d.ts +24 -0
  106. package/dist/pages/ViewEditor/components/EditorOverlays.d.ts +30 -0
  107. package/dist/pages/ViewEditor/components/EmptyCanvasState.d.ts +7 -0
  108. package/dist/pages/ViewEditor/context.d.ts +13 -0
  109. package/dist/pages/ViewEditor/hooks/useCanvasInteractions.d.ts +201 -0
  110. package/dist/pages/ViewEditor/hooks/useDrawingEngine.d.ts +40 -0
  111. package/dist/pages/ViewEditor/hooks/useViewContextNeighbours.d.ts +20 -0
  112. package/dist/pages/ViewEditor/hooks/useViewData.d.ts +74 -0
  113. package/dist/pages/ViewEditor/index.d.ts +8 -0
  114. package/dist/pages/ViewEditor/utils.d.ts +14 -0
  115. package/dist/pages/Views.d.ts +6 -0
  116. package/dist/pages/ViewsGrid.d.ts +6 -0
  117. package/dist/pkg/importer/mermaid.d.ts +7 -0
  118. package/dist/pkg/importer/mermaid.test.d.ts +1 -0
  119. package/dist/platform/PlatformContext.d.ts +6 -0
  120. package/dist/platform/context.d.ts +3 -0
  121. package/dist/platform/local.d.ts +2 -0
  122. package/dist/platform/types.d.ts +17 -0
  123. package/dist/slots.d.ts +67 -0
  124. package/dist/theme.d.ts +2 -0
  125. package/dist/types/index.d.ts +193 -0
  126. package/dist/types/vscode-messages.d.ts +60 -0
  127. package/dist/utils/edgeDistribution.d.ts +34 -0
  128. package/dist/utils/githubApi.d.ts +4 -0
  129. package/dist/utils/githubCache.d.ts +17 -0
  130. package/dist/utils/ids.d.ts +2 -0
  131. package/dist/utils/technologyCatalog.d.ts +15 -0
  132. package/dist/utils/toast.d.ts +15 -0
  133. package/dist/utils/treesitter.d.ts +13 -0
  134. package/dist/utils/url.d.ts +12 -0
  135. package/package.json +159 -0
  136. package/src/App.tsx +141 -0
  137. package/src/api/client.ts +618 -0
  138. package/src/api/transport-vscode.ts +28 -0
  139. package/src/api/transport.ts +7 -0
  140. package/src/assets/logo-mark.svg +31 -0
  141. package/src/assets/logo-wordmark.svg +22 -0
  142. package/src/assets/logo.svg +35 -0
  143. package/src/components/CodePreviewPanel-vscode.tsx +85 -0
  144. package/src/components/CodePreviewPanel.tsx +384 -0
  145. package/src/components/ConfirmDialog.tsx +66 -0
  146. package/src/components/ConnectorPanel.tsx +403 -0
  147. package/src/components/ContextBoundaryElement.tsx +35 -0
  148. package/src/components/ContextNeighborElement.tsx +282 -0
  149. package/src/components/ContextStraightConnector.tsx +144 -0
  150. package/src/components/CrossBranchControls.tsx +105 -0
  151. package/src/components/DependenciesOnboarding.tsx +427 -0
  152. package/src/components/DrawingCanvas.tsx +391 -0
  153. package/src/components/ElementLibrary-vscode.tsx +9 -0
  154. package/src/components/ElementLibrary.tsx +512 -0
  155. package/src/components/ElementNode.tsx +1033 -0
  156. package/src/components/ElementPanel.tsx +928 -0
  157. package/src/components/ExploreOnboarding.tsx +347 -0
  158. package/src/components/ExplorePageOnboarding.tsx +383 -0
  159. package/src/components/ExportModal.tsx +132 -0
  160. package/src/components/FloatingEdge.tsx +115 -0
  161. package/src/components/GitSourceLinker.tsx +1053 -0
  162. package/src/components/HeaderContext.tsx +30 -0
  163. package/src/components/Icons.tsx +245 -0
  164. package/src/components/ImportModal.tsx +219 -0
  165. package/src/components/InlineElementAdder.tsx +216 -0
  166. package/src/components/LayoutSection.tsx +624 -0
  167. package/src/components/LocalSourceLinker.tsx +330 -0
  168. package/src/components/MiniZoomOnboarding.tsx +78 -0
  169. package/src/components/NavBreadcrumb.tsx +24 -0
  170. package/src/components/NodeBody.tsx +89 -0
  171. package/src/components/NodeContainer.tsx +58 -0
  172. package/src/components/NodeHoverCard.tsx +135 -0
  173. package/src/components/PanelHeader.tsx +36 -0
  174. package/src/components/PanelUI.tsx +24 -0
  175. package/src/components/ProxyConnectorEdge.tsx +169 -0
  176. package/src/components/ProxyConnectorPanel.tsx +130 -0
  177. package/src/components/SafeBackground.tsx +19 -0
  178. package/src/components/ScrollIndicatorWrapper.tsx +117 -0
  179. package/src/components/SetChildModal.tsx +191 -0
  180. package/src/components/SetParentModal.tsx +187 -0
  181. package/src/components/SlidingPanel.tsx +114 -0
  182. package/src/components/TagUpsert.tsx +142 -0
  183. package/src/components/TopMenuBar.tsx +380 -0
  184. package/src/components/ViewBezierConnector.tsx +143 -0
  185. package/src/components/ViewDrawMenu.tsx +270 -0
  186. package/src/components/ViewEditorEdgeLabelLayout.ts +189 -0
  187. package/src/components/ViewEditorOnboarding.tsx +445 -0
  188. package/src/components/ViewExplorer/TagManager/ColorPicker.tsx +49 -0
  189. package/src/components/ViewExplorer/TagManager/GroupNamingPopover.tsx +96 -0
  190. package/src/components/ViewExplorer/TagManager/LayerItem.tsx +228 -0
  191. package/src/components/ViewExplorer/TagManager/TagItem.tsx +242 -0
  192. package/src/components/ViewExplorer/TagManager/index.tsx +418 -0
  193. package/src/components/ViewExplorer/ViewNavigator.tsx +121 -0
  194. package/src/components/ViewExplorer/ViewSearch.tsx +33 -0
  195. package/src/components/ViewExplorer/ViewTree.tsx +98 -0
  196. package/src/components/ViewExplorer/index.tsx +384 -0
  197. package/src/components/ViewExplorer/types.ts +13 -0
  198. package/src/components/ViewExplorer/utils.ts +56 -0
  199. package/src/components/ViewExplorer-vscode.tsx +8 -0
  200. package/src/components/ViewFloatingMenu-vscode.tsx +248 -0
  201. package/src/components/ViewFloatingMenu.tsx +379 -0
  202. package/src/components/ViewGridNode.tsx +451 -0
  203. package/src/components/ViewHeaderButton.tsx +60 -0
  204. package/src/components/ViewPanel.tsx +162 -0
  205. package/src/components/ViewsGridOnboarding.tsx +400 -0
  206. package/src/components/ZUI/ZUICanvas.tsx +853 -0
  207. package/src/components/ZUI/index.ts +3 -0
  208. package/src/components/ZUI/layout.ts +323 -0
  209. package/src/components/ZUI/proxy.ts +278 -0
  210. package/src/components/ZUI/renderer.ts +1189 -0
  211. package/src/components/ZUI/types.ts +150 -0
  212. package/src/components/ZUI/useZUIInteraction.ts +720 -0
  213. package/src/config/runtime-vscode.ts +46 -0
  214. package/src/config/runtime.ts +30 -0
  215. package/src/constants/colors.ts +80 -0
  216. package/src/constants/diagramColors.ts +9 -0
  217. package/src/context/ThemeContext.tsx +158 -0
  218. package/src/crossBranch/graph.ts +207 -0
  219. package/src/crossBranch/resolve.ts +643 -0
  220. package/src/crossBranch/settings.ts +59 -0
  221. package/src/crossBranch/store.ts +71 -0
  222. package/src/crossBranch/types.ts +102 -0
  223. package/src/demo/DemoPage.tsx +184 -0
  224. package/src/demo/seed.ts +67 -0
  225. package/src/demo/store.ts +536 -0
  226. package/src/demo/viewEditor.ts +110 -0
  227. package/src/hooks/useSafeFitView.ts +60 -0
  228. package/src/index.css +309 -0
  229. package/src/index.ts +184 -0
  230. package/src/kafka-ss.png +0 -0
  231. package/src/lib/vscodeBridge-vscode.ts +27 -0
  232. package/src/lib/vscodeBridge.ts +7 -0
  233. package/src/main.tsx +46 -0
  234. package/src/pages/AppearanceSettings.tsx +135 -0
  235. package/src/pages/Dependencies.tsx +926 -0
  236. package/src/pages/InfiniteZoom.tsx +404 -0
  237. package/src/pages/Settings.tsx +91 -0
  238. package/src/pages/ViewEditor/EDGE_DISTRIBUTION.md +64 -0
  239. package/src/pages/ViewEditor/components/EditorMenus.tsx +112 -0
  240. package/src/pages/ViewEditor/components/EditorOverlays.tsx +172 -0
  241. package/src/pages/ViewEditor/components/EmptyCanvasState.tsx +42 -0
  242. package/src/pages/ViewEditor/context.tsx +21 -0
  243. package/src/pages/ViewEditor/hooks/useCanvasInteractions.ts +1349 -0
  244. package/src/pages/ViewEditor/hooks/useDrawingEngine.ts +127 -0
  245. package/src/pages/ViewEditor/hooks/useViewContextNeighbours.ts +501 -0
  246. package/src/pages/ViewEditor/hooks/useViewData.ts +491 -0
  247. package/src/pages/ViewEditor/index.tsx +1366 -0
  248. package/src/pages/ViewEditor/utils.ts +88 -0
  249. package/src/pages/Views.tsx +171 -0
  250. package/src/pages/ViewsGrid.tsx +1310 -0
  251. package/src/pkg/importer/mermaid.test.ts +141 -0
  252. package/src/pkg/importer/mermaid.ts +76 -0
  253. package/src/platform/PlatformContext.tsx +17 -0
  254. package/src/platform/context.ts +9 -0
  255. package/src/platform/local.tsx +15 -0
  256. package/src/platform/types.ts +19 -0
  257. package/src/slots.ts +92 -0
  258. package/src/styles/editor-panels.css +66 -0
  259. package/src/styles/theme.css +56 -0
  260. package/src/theme.ts +336 -0
  261. package/src/types/index.ts +234 -0
  262. package/src/types/offline-ambient.d.ts +14 -0
  263. package/src/types/vscode-messages.ts +32 -0
  264. package/src/utils/edgeDistribution.ts +103 -0
  265. package/src/utils/githubApi.ts +121 -0
  266. package/src/utils/githubCache.ts +108 -0
  267. package/src/utils/ids.ts +9 -0
  268. package/src/utils/technologyCatalog.ts +143 -0
  269. package/src/utils/toast.ts +100 -0
  270. package/src/utils/treesitter.ts +147 -0
  271. package/src/utils/url.ts +72 -0
  272. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,383 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { Box, Button, HStack, Text, VStack } from '@chakra-ui/react'
3
+
4
+ const STORAGE_KEY = `explore_page_tutorial_v1_core`
5
+
6
+ interface Props {
7
+
8
+ hasDiagrams: boolean
9
+ }
10
+
11
+ function GridDots({ prefix }: { prefix: string }) {
12
+ return (
13
+ <>
14
+ {Array.from({ length: 7 }, (_, r) =>
15
+ Array.from({ length: 14 }, (_, c) => (
16
+ <circle
17
+ key={`${prefix}-${r}-${c}`}
18
+ cx={c * 20 + 10}
19
+ cy={r * 20 + 10}
20
+ r={0.8}
21
+ fill="#1E2D40"
22
+ />
23
+ )),
24
+ ).flat()}
25
+ </>
26
+ )
27
+ }
28
+
29
+ // Step 0 - overview: all diagrams on one canvas
30
+ function OverviewIllustration() {
31
+ return (
32
+ <svg
33
+ viewBox="0 0 280 150"
34
+ xmlns="http://www.w3.org/2000/svg"
35
+ style={{ width: '100%', height: 'auto', borderRadius: 10, display: 'block', overflow: 'hidden' }}
36
+ >
37
+ <defs>
38
+ <style>{`
39
+ .ep-group-a { animation: epFadeIn 0.5s 0.0s ease both; }
40
+ .ep-group-b { animation: epFadeIn 0.5s 0.2s ease both; }
41
+ .ep-group-c { animation: epFadeIn 0.5s 0.4s ease both; }
42
+ .ep-edges { animation: epFadeIn 0.5s 0.6s ease both; }
43
+ @keyframes epFadeIn {
44
+ from { opacity: 0; }
45
+ to { opacity: 1; }
46
+ }
47
+ `}</style>
48
+ <marker id="ep-arr" markerWidth="7" markerHeight="7" refX="5" refY="3" orient="auto">
49
+ <path d="M0,0 L0,6 L7,3 z" fill="#4A5568" />
50
+ </marker>
51
+ </defs>
52
+
53
+ <rect width="280" height="150" fill="#0D1117" rx="8" />
54
+ <GridDots prefix="ov" />
55
+
56
+ {/* Diagram group A */}
57
+ <g className="ep-group-a">
58
+ <rect x="10" y="18" width="110" height="60" rx="6" fill="none" stroke="#2D3748" strokeWidth="1.2" strokeDasharray="5,3" />
59
+ <text x="14" y="29" fill="#4A5568" fontSize="7" fontFamily="system-ui,sans-serif">System Context</text>
60
+ <rect x="18" y="34" width="40" height="30" rx="5" fill="#1C2535" stroke="#374151" strokeWidth="1" />
61
+ <text x="38" y="53" textAnchor="middle" fill="#94A3B8" fontSize="7.5" fontFamily="system-ui,sans-serif">User</text>
62
+ <rect x="70" y="34" width="40" height="30" rx="5" fill="#1C2535" stroke="#374151" strokeWidth="1" />
63
+ <text x="90" y="53" textAnchor="middle" fill="#94A3B8" fontSize="7.5" fontFamily="system-ui,sans-serif">API</text>
64
+ {/* blue dot on API - has child */}
65
+ <circle cx="107" cy="37" r="3.5" fill="#3B82F6" />
66
+ <circle cx="107" cy="37" r="3.5" fill="none" stroke="#3B82F6" strokeWidth="1">
67
+ <animate attributeName="r" values="3.5;9" dur="2s" repeatCount="indefinite" />
68
+ <animate attributeName="opacity" values="0.7;0" dur="2s" repeatCount="indefinite" />
69
+ </circle>
70
+ </g>
71
+
72
+ {/* Diagram group B */}
73
+ <g className="ep-group-b">
74
+ <rect x="130" y="18" width="140" height="60" rx="6" fill="none" stroke="#2D3748" strokeWidth="1.2" strokeDasharray="5,3" />
75
+ <text x="134" y="29" fill="#4A5568" fontSize="7" fontFamily="system-ui,sans-serif">Container View</text>
76
+ <rect x="138" y="34" width="36" height="30" rx="5" fill="#1C2535" stroke="#374151" strokeWidth="1" />
77
+ <text x="156" y="53" textAnchor="middle" fill="#94A3B8" fontSize="7.5" fontFamily="system-ui,sans-serif">Router</text>
78
+ <rect x="186" y="34" width="36" height="30" rx="5" fill="#1C2535" stroke="#374151" strokeWidth="1" />
79
+ <text x="204" y="53" textAnchor="middle" fill="#94A3B8" fontSize="7.5" fontFamily="system-ui,sans-serif">Auth</text>
80
+ <rect x="234" y="34" width="30" height="30" rx="5" fill="#1C2535" stroke="#374151" strokeWidth="1" />
81
+ <text x="249" y="53" textAnchor="middle" fill="#94A3B8" fontSize="7.5" fontFamily="system-ui,sans-serif">DB</text>
82
+ </g>
83
+
84
+ {/* Diagram group C */}
85
+ <g className="ep-group-c">
86
+ <rect x="10" y="88" width="110" height="50" rx="6" fill="none" stroke="#2D3748" strokeWidth="1.2" strokeDasharray="5,3" />
87
+ <text x="14" y="99" fill="#4A5568" fontSize="7" fontFamily="system-ui,sans-serif">Data Platform</text>
88
+ <rect x="18" y="104" width="36" height="26" rx="5" fill="#1C2535" stroke="#374151" strokeWidth="1" />
89
+ <text x="36" y="121" textAnchor="middle" fill="#94A3B8" fontSize="7.5" fontFamily="system-ui,sans-serif">Kafka</text>
90
+ <rect x="70" y="104" width="40" height="26" rx="5" fill="#1C2535" stroke="#374151" strokeWidth="1" />
91
+ <text x="90" y="121" textAnchor="middle" fill="#94A3B8" fontSize="7.5" fontFamily="system-ui,sans-serif">Spark</text>
92
+ </g>
93
+
94
+ {/* Cross-diagram edges */}
95
+ <g className="ep-edges">
96
+ <line x1="110" y1="49" x2="138" y2="49" stroke="#374151" strokeWidth="1.2" markerEnd="url(#ep-arr)" />
97
+ <line x1="90" y1="64" x2="90" y2="104" stroke="#374151" strokeWidth="1.2" markerEnd="url(#ep-arr)" />
98
+ </g>
99
+
100
+ <text x="140" y="145" textAnchor="middle" fill="#2D3748" fontSize="8" fontFamily="system-ui,sans-serif">all diagrams on one infinite canvas</text>
101
+ </svg>
102
+ )
103
+ }
104
+
105
+ // Step 1 - zoom in on a blue-dot node → transitions inside
106
+ function ZoomInIllustration() {
107
+ return (
108
+ <svg
109
+ viewBox="0 0 280 150"
110
+ xmlns="http://www.w3.org/2000/svg"
111
+ style={{ width: '100%', height: 'auto', borderRadius: 10, display: 'block', overflow: 'hidden' }}
112
+ >
113
+ <defs>
114
+ <style>{`
115
+ .ep-outer {
116
+ animation: epOuter 5s ease-in-out infinite;
117
+ }
118
+ @keyframes epOuter {
119
+ 0%, 15% { transform: scale(1); opacity: 1; }
120
+ 40%, 60% { transform: scale(2.6); opacity: 0; }
121
+ 82%, 100%{ transform: scale(1); opacity: 1; }
122
+ }
123
+ .ep-inner {
124
+ animation: epInner 5s ease-in-out infinite;
125
+ }
126
+ @keyframes epInner {
127
+ 0%, 30% { opacity: 0; }
128
+ 50%, 62% { opacity: 1; }
129
+ 78%, 100%{ opacity: 0; }
130
+ }
131
+ `}</style>
132
+ <marker id="ep-arr2" markerWidth="7" markerHeight="7" refX="5" refY="3" orient="auto">
133
+ <path d="M0,0 L0,6 L7,3 z" fill="#374151" />
134
+ </marker>
135
+ </defs>
136
+
137
+ <rect width="280" height="150" fill="#0D1117" rx="8" />
138
+ <GridDots prefix="zi" />
139
+
140
+ {/* Outer scene */}
141
+ <g className="ep-outer" style={{ transformOrigin: '207px 49px' }}>
142
+ <rect x="22" y="42" width="86" height="52" rx="7" fill="#1C2535" stroke="#2D3748" strokeWidth="1" />
143
+ <text x="65" y="65" textAnchor="middle" fill="#94A3B8" fontSize="9" fontFamily="system-ui,sans-serif" fontWeight="600">Auth Service</text>
144
+ <text x="65" y="78" textAnchor="middle" fill="#475569" fontSize="7.5" fontFamily="system-ui,sans-serif">container</text>
145
+ <line x1="108" y1="68" x2="166" y2="68" stroke="#2D3748" strokeWidth="1.5" markerEnd="url(#ep-arr2)" />
146
+ <rect x="170" y="42" width="86" height="52" rx="7" fill="#1C2535" stroke="#2D3748" strokeWidth="1" />
147
+ <text x="213" y="65" textAnchor="middle" fill="#94A3B8" fontSize="9" fontFamily="system-ui,sans-serif" fontWeight="600">API Gateway</text>
148
+ <text x="213" y="78" textAnchor="middle" fill="#475569" fontSize="7.5" fontFamily="system-ui,sans-serif">container</text>
149
+ {/* Blue dot on API Gateway */}
150
+ <circle cx="249" cy="48" r="4" fill="#3B82F6" />
151
+ <circle cx="249" cy="48" r="4" fill="none" stroke="#3B82F6" strokeWidth="1.5">
152
+ <animate attributeName="r" values="4;14" dur="2s" repeatCount="indefinite" />
153
+ <animate attributeName="opacity" values="0.8;0" dur="2s" repeatCount="indefinite" />
154
+ </circle>
155
+ <text x="213" y="31" textAnchor="middle" fill="#3B82F6" fontSize="7" fontFamily="system-ui,sans-serif">zoom in ↑</text>
156
+ </g>
157
+
158
+ {/* Inner scene */}
159
+ <g className="ep-inner" opacity="0">
160
+ <rect width="280" height="150" fill="#0D1117" />
161
+ <GridDots prefix="zi2" />
162
+ <text x="140" y="16" textAnchor="middle" fill="#3B82F6" fontSize="8" fontFamily="system-ui,sans-serif" fontWeight="700" letterSpacing="0.05em">API GATEWAY - INTERNALS</text>
163
+ <rect x="14" y="28" width="66" height="44" rx="6" fill="#1C2535" stroke="#2D3748" strokeWidth="1" />
164
+ <text x="47" y="54" textAnchor="middle" fill="#94A3B8" fontSize="8.5" fontFamily="system-ui,sans-serif">Router</text>
165
+ <rect x="107" y="52" width="66" height="44" rx="6" fill="#1C2535" stroke="#2D3748" strokeWidth="1" />
166
+ <text x="140" y="78" textAnchor="middle" fill="#94A3B8" fontSize="8.5" fontFamily="system-ui,sans-serif">Auth MW</text>
167
+ <rect x="200" y="28" width="66" height="44" rx="6" fill="#1C2535" stroke="#2D3748" strokeWidth="1" />
168
+ <text x="233" y="54" textAnchor="middle" fill="#94A3B8" fontSize="8.5" fontFamily="system-ui,sans-serif">Cache</text>
169
+ <line x1="80" y1="56" x2="107" y2="70" stroke="#374151" strokeWidth="1.5" strokeDasharray="4,2" markerEnd="url(#ep-arr2)" />
170
+ <line x1="173" y1="70" x2="200" y2="56" stroke="#374151" strokeWidth="1.5" strokeDasharray="4,2" markerEnd="url(#ep-arr2)" />
171
+ </g>
172
+
173
+ <text x="140" y="144" textAnchor="middle" fill="#2D3748" fontSize="8" fontFamily="system-ui,sans-serif">scroll to zoom · nodes with a blue dot have sub-diagrams</text>
174
+ </svg>
175
+ )
176
+ }
177
+
178
+ // Step 2 - bottom bar + breadcrumbs
179
+ function NavigateIllustration() {
180
+ return (
181
+ <svg
182
+ viewBox="0 0 280 150"
183
+ xmlns="http://www.w3.org/2000/svg"
184
+ style={{ width: '100%', height: 'auto', borderRadius: 10, display: 'block', overflow: 'hidden' }}
185
+ >
186
+ <defs>
187
+ <style>{`
188
+ .ep-crumb-glow {
189
+ animation: epCrumbGlow 3.5s ease-in-out infinite;
190
+ }
191
+ @keyframes epCrumbGlow {
192
+ 0%, 20% { fill: #374151; }
193
+ 45%, 65% { fill: #3B82F6; }
194
+ 85%, 100%{ fill: #374151; }
195
+ }
196
+ .ep-bar-btn {
197
+ animation: epBarPulse 3.5s ease-in-out infinite;
198
+ }
199
+ @keyframes epBarPulse {
200
+ 0%, 30% { opacity: 0.5; }
201
+ 50%, 70% { opacity: 1; }
202
+ 90%, 100%{ opacity: 0.5; }
203
+ }
204
+ `}</style>
205
+ </defs>
206
+
207
+ <rect width="280" height="150" fill="#0D1117" rx="8" />
208
+ <GridDots prefix="nav" />
209
+
210
+ {/* Canvas content */}
211
+ <rect x="20" y="20" width="110" height="72" rx="6" fill="none" stroke="#1E2D3E" strokeWidth="1.2" strokeDasharray="5,3" />
212
+ <text x="24" y="31" fill="#2D3748" fontSize="7" fontFamily="system-ui,sans-serif">System</text>
213
+ <rect x="28" y="37" width="40" height="26" rx="4" fill="#1C2535" stroke="#2D3748" strokeWidth="1" />
214
+ <text x="48" y="54" textAnchor="middle" fill="#94A3B8" fontSize="7.5" fontFamily="system-ui,sans-serif">Auth</text>
215
+ <rect x="82" y="37" width="40" height="26" rx="4" fill="#1C2535" stroke="#2D3748" strokeWidth="1" />
216
+ <text x="102" y="54" textAnchor="middle" fill="#94A3B8" fontSize="7.5" fontFamily="system-ui,sans-serif">API</text>
217
+
218
+ {/* Focused inner group */}
219
+ <rect x="148" y="20" width="120" height="72" rx="6" fill="none" stroke="#3B82F6" strokeWidth="1.2" />
220
+ <text x="153" y="31" fill="#3B82F6" fontSize="7" fontFamily="system-ui,sans-serif">API Gateway (current)</text>
221
+ <rect x="153" y="37" width="32" height="26" rx="4" fill="#1C2535" stroke="#2D3748" strokeWidth="1" />
222
+ <text x="169" y="54" textAnchor="middle" fill="#94A3B8" fontSize="7" fontFamily="system-ui,sans-serif">Router</text>
223
+ <rect x="197" y="37" width="32" height="26" rx="4" fill="#1C2535" stroke="#2D3748" strokeWidth="1" />
224
+ <text x="213" y="54" textAnchor="middle" fill="#94A3B8" fontSize="7" fontFamily="system-ui,sans-serif">Auth MW</text>
225
+ <rect x="232" y="37" width="30" height="26" rx="4" fill="#1C2535" stroke="#2D3748" strokeWidth="1" />
226
+ <text x="247" y="54" textAnchor="middle" fill="#94A3B8" fontSize="7" fontFamily="system-ui,sans-serif">Cache</text>
227
+
228
+ {/* Breadcrumb bar */}
229
+ <rect x="0" y="97" width="280" height="20" fill="rgba(26,32,44,0.9)" />
230
+ <text x="10" y="111" fill="#4A5568" fontSize="8" fontFamily="system-ui,sans-serif">All Diagrams</text>
231
+ <text x="85" y="111" fill="#4A5568" fontSize="8" fontFamily="system-ui,sans-serif">/</text>
232
+ <text x="95" y="111" fill="#4A5568" fontSize="8" fontFamily="system-ui,sans-serif">System Context</text>
233
+ <text x="185" y="111" fill="#4A5568" fontSize="8" fontFamily="system-ui,sans-serif">/</text>
234
+ <rect x="192" y="101" width="73" height="14" rx="3" className="ep-crumb-glow" fill="#374151" />
235
+ <text x="228" y="111" textAnchor="middle" fill="white" fontSize="8" fontFamily="system-ui,sans-serif" fontWeight="600">API Gateway</text>
236
+
237
+ {/* Bottom bar */}
238
+ <rect x="60" y="124" width="160" height="20" rx="8" fill="rgba(22,30,43,0.95)" stroke="rgba(255,255,255,0.07)" strokeWidth="1" />
239
+ <text x="85" y="137" textAnchor="middle" fill="#9CA3AF" fontSize="7.5" fontFamily="system-ui,sans-serif" className="ep-bar-btn">Zoom Out</text>
240
+ <line x1="115" y1="127" x2="115" y2="141" stroke="rgba(255,255,255,0.07)" strokeWidth="1" />
241
+ <text x="140" y="137" textAnchor="middle" fill="#9CA3AF" fontSize="7.5" fontFamily="system-ui,sans-serif" className="ep-bar-btn">Fit View</text>
242
+ <line x1="165" y1="127" x2="165" y2="141" stroke="rgba(255,255,255,0.07)" strokeWidth="1" />
243
+ <text x="195" y="137" textAnchor="middle" fill="#9CA3AF" fontSize="7.5" fontFamily="system-ui,sans-serif" className="ep-bar-btn">Share</text>
244
+ </svg>
245
+ )
246
+ }
247
+
248
+ const STEPS = [
249
+ {
250
+ title: 'Your Full Architecture',
251
+ body: 'Explore renders all your diagrams together on one infinite canvas. Every node and connection is visible - see how your systems relate at a glance.',
252
+ visual: 'overview' as const,
253
+ },
254
+ {
255
+ title: 'Zoom In to Drill Down',
256
+ body: 'Nodes with a pulsing blue dot are linked to a sub-diagram. Scroll to zoom in on one and the view transitions inside. Zoom out past the boundary to go back up.',
257
+ visual: 'zoomin' as const,
258
+ },
259
+ {
260
+ title: 'Navigate Levels',
261
+ body: 'The breadcrumb trail at the top shows your current depth - click any crumb to jump there instantly. Use the bottom bar to fit all diagrams back into view or share the canvas.',
262
+ visual: 'navigate' as const,
263
+ },
264
+ ]
265
+
266
+ export default function ExplorePageOnboarding({ hasDiagrams }: Props) {
267
+ const [visible, setVisible] = useState(false)
268
+ const [step, setStep] = useState(0)
269
+
270
+ useEffect(() => {
271
+ if (!hasDiagrams) return
272
+ if (!localStorage.getItem(STORAGE_KEY)) {
273
+ setVisible(true)
274
+ }
275
+ }, [ hasDiagrams])
276
+
277
+ const dismiss = () => {
278
+ localStorage.setItem(STORAGE_KEY, '1')
279
+ setVisible(false)
280
+ }
281
+
282
+ if (!visible) return null
283
+
284
+ const current = STEPS[step]
285
+ const isLast = step === STEPS.length - 1
286
+
287
+ return (
288
+ <Box
289
+ position="fixed"
290
+ inset={0}
291
+ zIndex={2000}
292
+ display="flex"
293
+ alignItems="center"
294
+ justifyContent="center"
295
+ pointerEvents="none"
296
+ >
297
+ <Box
298
+ position="absolute"
299
+ inset={0}
300
+ bg="blackAlpha.700"
301
+ pointerEvents="auto"
302
+ onClick={dismiss}
303
+ />
304
+ <Box
305
+ position="relative"
306
+ w="380px"
307
+ bg="var(--bg-panel)"
308
+ border="1px solid"
309
+ borderColor="var(--border-main)"
310
+ borderRadius="16px"
311
+ p={6}
312
+ boxShadow="0 24px 60px rgba(0,0,0,0.6), 0 4px 16px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.04)"
313
+ pointerEvents="auto"
314
+ >
315
+ <Button
316
+ position="absolute"
317
+ top={3}
318
+ right={3}
319
+ size="xs"
320
+ variant="ghost"
321
+ color="gray.500"
322
+ _hover={{ color: 'gray.200', bg: 'whiteAlpha.100' }}
323
+ onClick={dismiss}
324
+ fontWeight="normal"
325
+ >
326
+ Skip Tutorial
327
+ </Button>
328
+
329
+ <HStack justify="center" spacing={2} mb={5}>
330
+ {STEPS.map((_, i) => (
331
+ <Box
332
+ key={i}
333
+ w={i === step ? '18px' : '6px'}
334
+ h="6px"
335
+ rounded="full"
336
+ bg={i === step ? 'blue.400' : 'gray.600'}
337
+ transition="all 0.25s ease"
338
+ cursor="pointer"
339
+ onClick={() => setStep(i)}
340
+ _hover={{ bg: i === step ? 'blue.400' : 'gray.500' }}
341
+ />
342
+ ))}
343
+ </HStack>
344
+
345
+ <VStack spacing={4} textAlign="center">
346
+ {current.visual === 'overview' && <OverviewIllustration />}
347
+ {current.visual === 'zoomin' && <ZoomInIllustration />}
348
+ {current.visual === 'navigate' && <NavigateIllustration />}
349
+
350
+ <VStack spacing={2}>
351
+ <Text fontWeight="bold" fontSize="lg" color="gray.100" lineHeight="short">
352
+ {current.title}
353
+ </Text>
354
+ <Text fontSize="sm" color="gray.400" lineHeight="tall" maxW="300px">
355
+ {current.body}
356
+ </Text>
357
+ </VStack>
358
+ </VStack>
359
+
360
+ <HStack mt={6} justify="space-between" align="center">
361
+ <Button
362
+ size="sm"
363
+ variant="ghost"
364
+ color="gray.500"
365
+ _hover={{ color: 'gray.300' }}
366
+ onClick={() => setStep(step - 1)}
367
+ visibility={step > 0 ? 'visible' : 'hidden'}
368
+ >
369
+ ← Back
370
+ </Button>
371
+ <Button
372
+ size="sm"
373
+ colorScheme="blue"
374
+ px={5}
375
+ onClick={isLast ? dismiss : () => setStep(step + 1)}
376
+ >
377
+ {isLast ? 'Got it' : 'Next →'}
378
+ </Button>
379
+ </HStack>
380
+ </Box>
381
+ </Box>
382
+ )
383
+ }
@@ -0,0 +1,132 @@
1
+ import { memo, useEffect, useMemo, useState } from 'react'
2
+ import {
3
+ Button,
4
+ FormControl,
5
+ FormLabel,
6
+ HStack,
7
+ Input,
8
+ Modal,
9
+ ModalBody,
10
+ ModalCloseButton,
11
+ ModalContent,
12
+ ModalFooter,
13
+ ModalHeader,
14
+ ModalOverlay,
15
+ Radio,
16
+ RadioGroup,
17
+ Text,
18
+ VStack,
19
+ } from '@chakra-ui/react'
20
+
21
+ export type ExportFormat = 'svg' | 'png' | 'mermaid'
22
+
23
+ export interface ExportOptions {
24
+ format: ExportFormat
25
+ scale: 1 | 2 | 3
26
+ filename: string
27
+ }
28
+
29
+ interface Props {
30
+ isOpen: boolean
31
+ onClose: () => void
32
+ defaultFilename: string
33
+ isExporting?: boolean
34
+ onExport: (options: ExportOptions) => Promise<void> | void
35
+ }
36
+
37
+ function sanitizeFilename(value: string) {
38
+ const trimmed = value.trim()
39
+ if (!trimmed) return 'diagram-export'
40
+ return trimmed.replace(/[\\/:*?"<>|]+/g, '-').replace(/\s+/g, ' ')
41
+ }
42
+
43
+ function ExportModal({
44
+ isOpen,
45
+ onClose,
46
+ defaultFilename,
47
+ isExporting,
48
+ onExport,
49
+ }: Props) {
50
+ const [format, setFormat] = useState<ExportFormat>('svg')
51
+ const [scale, setScale] = useState<1 | 2 | 3>(2)
52
+ const [filename, setFilename] = useState(defaultFilename)
53
+
54
+ useEffect(() => {
55
+ if (!isOpen) return
56
+ setFilename(defaultFilename)
57
+ setFormat('svg')
58
+ setScale(2)
59
+ }, [isOpen, defaultFilename])
60
+
61
+ const extension = useMemo(() => (format === 'svg' ? '.svg' : format === 'png' ? '.png' : '.mermaid'), [format])
62
+
63
+ const handleSubmit = async () => {
64
+ await onExport({
65
+ format,
66
+ scale,
67
+ filename: sanitizeFilename(filename),
68
+ })
69
+ }
70
+
71
+ return (
72
+ <Modal isOpen={isOpen} onClose={onClose} isCentered>
73
+ <ModalOverlay bg="blackAlpha.700" backdropFilter="blur(4px)" />
74
+ <ModalContent mx={4}>
75
+ <ModalHeader>Export Diagram</ModalHeader>
76
+ <ModalCloseButton />
77
+ <ModalBody>
78
+ <VStack spacing={4} align="stretch">
79
+ <FormControl id="export-format">
80
+ <FormLabel fontSize="sm">Format</FormLabel>
81
+ <RadioGroup name="format" value={format} onChange={(value) => setFormat(value as ExportFormat)}>
82
+ <HStack spacing={4}>
83
+ <Radio value="svg">SVG</Radio>
84
+ <Radio value="png">PNG</Radio>
85
+ <Radio value="mermaid">Mermaid</Radio>
86
+ </HStack>
87
+ </RadioGroup>
88
+ </FormControl>
89
+
90
+ {format === 'png' && (
91
+ <FormControl id="export-resolution">
92
+ <FormLabel fontSize="sm">Resolution</FormLabel>
93
+ <RadioGroup name="scale" value={String(scale)} onChange={(value) => setScale(Number(value) as 1 | 2 | 3)}>
94
+ <HStack spacing={4}>
95
+ <Radio value="1">1x</Radio>
96
+ <Radio value="2">2x</Radio>
97
+ <Radio value="3">3x</Radio>
98
+ </HStack>
99
+ </RadioGroup>
100
+ </FormControl>
101
+ )}
102
+
103
+ <FormControl id="export-filename">
104
+ <FormLabel fontSize="sm">Filename</FormLabel>
105
+ <Input
106
+ name="filename"
107
+ value={filename}
108
+ onChange={(e) => setFilename(e.target.value)}
109
+ placeholder="diagram-export"
110
+ size="sm"
111
+ />
112
+ <Text mt={1.5} fontSize="xs" color="gray.400">
113
+ File extension will be added automatically ({extension})
114
+ </Text>
115
+ </FormControl>
116
+ </VStack>
117
+ </ModalBody>
118
+
119
+ <ModalFooter gap={2}>
120
+ <Button variant="ghost" size="sm" onClick={onClose} isDisabled={isExporting}>
121
+ Cancel
122
+ </Button>
123
+ <Button size="sm" colorScheme="blue" onClick={handleSubmit} isLoading={isExporting}>
124
+ Export
125
+ </Button>
126
+ </ModalFooter>
127
+ </ModalContent>
128
+ </Modal>
129
+ )
130
+ }
131
+
132
+ export default memo(ExportModal)
@@ -0,0 +1,115 @@
1
+ import { memo, useState } from 'react'
2
+ import { useStore, getStraightPath, type EdgeProps } from 'reactflow'
3
+
4
+ function getNodeBorderPoint(
5
+ node: { positionAbsolute?: { x: number; y: number }; width?: number | null; height?: number | null },
6
+ targetCenter: { x: number; y: number }
7
+ ) {
8
+ const w = (node.width ?? 0) / 2
9
+ const h = (node.height ?? 0) / 2
10
+ const cx = (node.positionAbsolute?.x ?? 0) + w
11
+ const cy = (node.positionAbsolute?.y ?? 0) + h
12
+ const dx = targetCenter.x - cx
13
+ const dy = targetCenter.y - cy
14
+ if (dx === 0 && dy === 0) return { x: cx, y: cy }
15
+ const scaleX = dx !== 0 ? Math.abs(w / dx) : Infinity
16
+ const scaleY = dy !== 0 ? Math.abs(h / dy) : Infinity
17
+ const scale = Math.min(scaleX, scaleY)
18
+ return { x: cx + dx * scale, y: cy + dy * scale }
19
+ }
20
+
21
+ export interface FloatingConnectorData {
22
+ color: string
23
+ /** true = portal/navigational link (dotted), false = hierarchy (solid) */
24
+ dashed?: boolean
25
+ }
26
+
27
+ function FloatingConnector({
28
+ source,
29
+ target,
30
+ data,
31
+ selected,
32
+ }: EdgeProps<FloatingConnectorData>) {
33
+ const [hovered, setHovered] = useState(false)
34
+ const sourceNode = useStore((s) => s.nodeInternals.get(source))
35
+ const targetNode = useStore((s) => s.nodeInternals.get(target))
36
+
37
+ if (
38
+ !sourceNode?.positionAbsolute || !targetNode?.positionAbsolute ||
39
+ !isFinite(sourceNode.positionAbsolute.x) || !isFinite(sourceNode.positionAbsolute.y) ||
40
+ !isFinite(targetNode.positionAbsolute.x) || !isFinite(targetNode.positionAbsolute.y)
41
+ ) return null
42
+
43
+ const sourceCx = (sourceNode.positionAbsolute.x ?? 0) + (sourceNode.width ?? 0) / 2
44
+ const sourceCy = (sourceNode.positionAbsolute.y ?? 0) + (sourceNode.height ?? 0) / 2
45
+ const targetCx = (targetNode.positionAbsolute.x ?? 0) + (targetNode.width ?? 0) / 2
46
+ const targetCy = (targetNode.positionAbsolute.y ?? 0) + (targetNode.height ?? 0) / 2
47
+
48
+ const sourcePoint = getNodeBorderPoint(sourceNode, { x: targetCx, y: targetCy })
49
+ const targetPoint = getNodeBorderPoint(targetNode, { x: sourceCx, y: sourceCy })
50
+
51
+ const [connectorPath] = getStraightPath({
52
+ sourceX: sourcePoint.x,
53
+ sourceY: sourcePoint.y,
54
+ targetX: targetPoint.x,
55
+ targetY: targetPoint.y,
56
+ })
57
+
58
+ const color = data?.color ?? '#718096'
59
+ const isPortal = data?.dashed ?? false
60
+ const active = hovered || !!selected
61
+
62
+ return (
63
+ <g>
64
+ {/* Main stroke */}
65
+ {isPortal ? (
66
+ /* Portal: fine rounded dots - distinct from hierarchy */
67
+ <path
68
+ d={connectorPath}
69
+ fill="none"
70
+ stroke={color}
71
+ strokeWidth={active ? 1.5 : 1}
72
+ strokeDasharray="1.5 7"
73
+ strokeLinecap="round"
74
+ opacity={active ? 0.9 : 0.6}
75
+ style={{ transition: 'opacity 0.15s ease, stroke-width 0.15s ease' }}
76
+ />
77
+ ) : (
78
+ /* Hierarchy: solid line */
79
+ <path
80
+ d={connectorPath}
81
+ fill="none"
82
+ stroke={color}
83
+ strokeWidth={active ? 1.5 : 1}
84
+ opacity={active ? 0.85 : 0.6}
85
+ style={{ transition: 'opacity 0.15s ease, stroke-width 0.15s ease' }}
86
+ />
87
+ )}
88
+
89
+ {/* Source terminus dot - hierarchy only, signals the origin node */}
90
+ {!isPortal && (
91
+ <circle
92
+ cx={sourcePoint.x}
93
+ cy={sourcePoint.y}
94
+ r={active ? 2.5 : 2}
95
+ fill={color}
96
+ opacity={active ? 0.85 : 0.55}
97
+ style={{ transition: 'r 0.15s ease, opacity 0.15s ease', pointerEvents: 'none' }}
98
+ />
99
+ )}
100
+
101
+ {/* Wide transparent hit area for hover detection */}
102
+ <path
103
+ d={connectorPath}
104
+ fill="none"
105
+ stroke="transparent"
106
+ strokeWidth={16}
107
+ onMouseEnter={() => setHovered(true)}
108
+ onMouseLeave={() => setHovered(false)}
109
+ style={{ cursor: 'default' }}
110
+ />
111
+ </g>
112
+ )
113
+ }
114
+
115
+ export default memo(FloatingConnector)