@zendir/ui 0.5.0 → 0.6.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 (391) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +640 -631
  3. package/dist/_virtual/_rolldown/runtime.js +13 -0
  4. package/dist/index.js +17 -221
  5. package/dist/react/3d/CesiumCaptureSource.d.ts +0 -1
  6. package/dist/react/3d/CesiumCaptureSource.js +364 -283
  7. package/dist/react/3d/CesiumCaptureSource.js.map +1 -1
  8. package/dist/react/3d/ZenSpace3D.d.ts +2 -3
  9. package/dist/react/3d/ZenSpace3D.js +1681 -1237
  10. package/dist/react/3d/ZenSpace3D.js.map +1 -1
  11. package/dist/react/3d/ZenSpace3DCesium.d.ts +0 -1
  12. package/dist/react/3d/ZenSpace3DCesium.js +513 -560
  13. package/dist/react/3d/ZenSpace3DCesium.js.map +1 -1
  14. package/dist/react/3d/ZenSpace3DShaders.js +61 -66
  15. package/dist/react/3d/ZenSpace3DShaders.js.map +1 -1
  16. package/dist/react/3d/ZenSpace3DTypes.d.ts +19 -20
  17. package/dist/react/3d/ZenSpace3DUtils.d.ts +0 -1
  18. package/dist/react/3d/ZenSpace3DUtils.js +37 -24
  19. package/dist/react/3d/ZenSpace3DUtils.js.map +1 -1
  20. package/dist/react/3d/capturePngAnalysis.js +42 -36
  21. package/dist/react/3d/capturePngAnalysis.js.map +1 -1
  22. package/dist/react/3d/index.d.ts +3 -3
  23. package/dist/react/3d/index.js +5 -53
  24. package/dist/react/3d/threeLoader.d.ts +1 -1
  25. package/dist/react/3d/threeLoader.js +23 -18
  26. package/dist/react/3d/threeLoader.js.map +1 -1
  27. package/dist/react/astro/ClassificationBanner.d.ts +2 -3
  28. package/dist/react/astro/ClassificationBanner.js +95 -79
  29. package/dist/react/astro/ClassificationBanner.js.map +1 -1
  30. package/dist/react/astro/GlobalStatusBar.d.ts +0 -1
  31. package/dist/react/astro/GlobalStatusBar.js +157 -163
  32. package/dist/react/astro/GlobalStatusBar.js.map +1 -1
  33. package/dist/react/astro/MissionClock.d.ts +7 -8
  34. package/dist/react/astro/MissionClock.js +377 -400
  35. package/dist/react/astro/MissionClock.js.map +1 -1
  36. package/dist/react/astro/MonitoringIcon.d.ts +1 -2
  37. package/dist/react/astro/MonitoringIcon.js +422 -358
  38. package/dist/react/astro/MonitoringIcon.js.map +1 -1
  39. package/dist/react/astro/Notification.d.ts +1 -2
  40. package/dist/react/astro/Notification.js +229 -126
  41. package/dist/react/astro/Notification.js.map +1 -1
  42. package/dist/react/astro/Progress.d.ts +0 -1
  43. package/dist/react/astro/Progress.js +199 -141
  44. package/dist/react/astro/Progress.js.map +1 -1
  45. package/dist/react/astro/SimulationControls.d.ts +3 -4
  46. package/dist/react/astro/SimulationControls.js +596 -650
  47. package/dist/react/astro/SimulationControls.js.map +1 -1
  48. package/dist/react/astro/StatusIndicator.d.ts +0 -1
  49. package/dist/react/astro/StatusIndicator.js +236 -170
  50. package/dist/react/astro/StatusIndicator.js.map +1 -1
  51. package/dist/react/astro/UnifiedTimeline.d.ts +9 -10
  52. package/dist/react/astro/UnifiedTimeline.js +2345 -2392
  53. package/dist/react/astro/UnifiedTimeline.js.map +1 -1
  54. package/dist/react/astro/index.d.ts +5 -5
  55. package/dist/react/astro/index.js +1 -25
  56. package/dist/react/cards/AccessCard.d.ts +0 -1
  57. package/dist/react/cards/AccessCard.js +471 -405
  58. package/dist/react/cards/AccessCard.js.map +1 -1
  59. package/dist/react/cards/OrbitCard.d.ts +0 -1
  60. package/dist/react/cards/OrbitCard.js +487 -364
  61. package/dist/react/cards/OrbitCard.js.map +1 -1
  62. package/dist/react/cards/SpacecraftCard.d.ts +0 -1
  63. package/dist/react/cards/SpacecraftCard.js +813 -925
  64. package/dist/react/cards/SpacecraftCard.js.map +1 -1
  65. package/dist/react/cards/TelemetryCard.d.ts +0 -1
  66. package/dist/react/cards/TelemetryCard.js +1055 -732
  67. package/dist/react/cards/TelemetryCard.js.map +1 -1
  68. package/dist/react/cards/TelemetryStreamCard.d.ts +0 -1
  69. package/dist/react/cards/TelemetryStreamCard.js +323 -297
  70. package/dist/react/cards/TelemetryStreamCard.js.map +1 -1
  71. package/dist/react/cards/index.d.ts +1 -1
  72. package/dist/react/cards/index.js +1 -8
  73. package/dist/react/charts/GroundTrackMap.d.ts +6 -7
  74. package/dist/react/charts/GroundTrackMap.js +1377 -1256
  75. package/dist/react/charts/GroundTrackMap.js.map +1 -1
  76. package/dist/react/charts/GroundTrackMapLeaflet.d.ts +1 -2
  77. package/dist/react/charts/GroundTrackMapLeaflet.js +1065 -987
  78. package/dist/react/charts/GroundTrackMapLeaflet.js.map +1 -1
  79. package/dist/react/charts/groundTrackMapLeafletTiles.js +34 -25
  80. package/dist/react/charts/groundTrackMapLeafletTiles.js.map +1 -1
  81. package/dist/react/charts/groundTrackMapLeafletUtils.js +70 -61
  82. package/dist/react/charts/groundTrackMapLeafletUtils.js.map +1 -1
  83. package/dist/react/charts/index.d.ts +1 -1
  84. package/dist/react/charts/index.js +7 -100
  85. package/dist/react/charts/unified/AstroChart.d.ts +1 -2
  86. package/dist/react/charts/unified/AstroChart.js +1331 -1411
  87. package/dist/react/charts/unified/AstroChart.js.map +1 -1
  88. package/dist/react/charts/unified/PowerOverviewChart.d.ts +3 -3
  89. package/dist/react/charts/unified/PowerOverviewChart.js +495 -478
  90. package/dist/react/charts/unified/PowerOverviewChart.js.map +1 -1
  91. package/dist/react/charts/unified/domain.d.ts +12 -13
  92. package/dist/react/charts/unified/domain.js +3999 -3145
  93. package/dist/react/charts/unified/domain.js.map +1 -1
  94. package/dist/react/charts/unified/generators.d.ts +3 -4
  95. package/dist/react/charts/unified/generators.js +561 -498
  96. package/dist/react/charts/unified/generators.js.map +1 -1
  97. package/dist/react/charts/unified/index.d.ts +1 -1
  98. package/dist/react/charts/unified/presets.d.ts +14 -15
  99. package/dist/react/charts/unified/presets.js +857 -994
  100. package/dist/react/charts/unified/presets.js.map +1 -1
  101. package/dist/react/charts/unified/sync.d.ts +2 -3
  102. package/dist/react/charts/unified/sync.js +229 -195
  103. package/dist/react/charts/unified/sync.js.map +1 -1
  104. package/dist/react/charts/unified/theme.d.ts +11 -12
  105. package/dist/react/charts/unified/theme.js +606 -642
  106. package/dist/react/charts/unified/theme.js.map +1 -1
  107. package/dist/react/charts/unified/types.d.ts +32 -33
  108. package/dist/react/charts/unified/useChartStream.d.ts +1 -2
  109. package/dist/react/charts/unified/useChartStream.js +203 -216
  110. package/dist/react/charts/unified/useChartStream.js.map +1 -1
  111. package/dist/react/chatgpt/AppCard.d.ts +1 -2
  112. package/dist/react/chatgpt/index.d.ts +7 -7
  113. package/dist/react/chatgpt/index.js +160 -139
  114. package/dist/react/chatgpt/index.js.map +1 -1
  115. package/dist/react/context/CategoryContext.d.ts +0 -1
  116. package/dist/react/context/CategoryContext.js +78 -29
  117. package/dist/react/context/CategoryContext.js.map +1 -1
  118. package/dist/react/context/DisplaySettingsContext.d.ts +1 -2
  119. package/dist/react/context/DisplaySettingsContext.js +205 -156
  120. package/dist/react/context/DisplaySettingsContext.js.map +1 -1
  121. package/dist/react/context/SpatialSelectionContext.d.ts +0 -1
  122. package/dist/react/context/SpatialSelectionContext.js +36 -7
  123. package/dist/react/context/SpatialSelectionContext.js.map +1 -1
  124. package/dist/react/core/data/DataTable.d.ts +3 -4
  125. package/dist/react/core/data/DataTable.js +473 -440
  126. package/dist/react/core/data/DataTable.js.map +1 -1
  127. package/dist/react/core/data/DataValue.d.ts +4 -5
  128. package/dist/react/core/data/DataValue.js +605 -571
  129. package/dist/react/core/data/DataValue.js.map +1 -1
  130. package/dist/react/core/data/propertyConfig.d.ts +1 -2
  131. package/dist/react/core/data/propertyConfig.js +446 -382
  132. package/dist/react/core/data/propertyConfig.js.map +1 -1
  133. package/dist/react/core/display/AstroIcon.d.ts +4 -5
  134. package/dist/react/core/display/AstroIcon.js +339 -429
  135. package/dist/react/core/display/AstroIcon.js.map +1 -1
  136. package/dist/react/core/display/Badge.d.ts +2 -3
  137. package/dist/react/core/display/Badge.js +131 -131
  138. package/dist/react/core/display/Badge.js.map +1 -1
  139. package/dist/react/core/display/CardHeader.d.ts +0 -1
  140. package/dist/react/core/display/CardHeader.js +398 -311
  141. package/dist/react/core/display/CardHeader.js.map +1 -1
  142. package/dist/react/core/display/Container.d.ts +2 -3
  143. package/dist/react/core/display/Container.js +640 -668
  144. package/dist/react/core/display/Container.js.map +1 -1
  145. package/dist/react/core/display/CopyButton.d.ts +1 -2
  146. package/dist/react/core/display/CopyButton.js +158 -101
  147. package/dist/react/core/display/CopyButton.js.map +1 -1
  148. package/dist/react/core/display/GlassCard.d.ts +3 -4
  149. package/dist/react/core/display/GlassCard.js +416 -369
  150. package/dist/react/core/display/GlassCard.js.map +1 -1
  151. package/dist/react/core/display/HeaderIconWithStatus.d.ts +0 -1
  152. package/dist/react/core/display/HeaderIconWithStatus.js +152 -152
  153. package/dist/react/core/display/HeaderIconWithStatus.js.map +1 -1
  154. package/dist/react/core/display/Icon.d.ts +2 -3
  155. package/dist/react/core/display/Icon.js +172 -143
  156. package/dist/react/core/display/Icon.js.map +1 -1
  157. package/dist/react/core/display/Typography.d.ts +24 -25
  158. package/dist/react/core/display/Typography.js +360 -178
  159. package/dist/react/core/display/Typography.js.map +1 -1
  160. package/dist/react/core/feedback/ConfirmDialog.d.ts +1 -2
  161. package/dist/react/core/feedback/ConfirmDialog.js +343 -304
  162. package/dist/react/core/feedback/ConfirmDialog.js.map +1 -1
  163. package/dist/react/core/feedback/Dialog.d.ts +2 -3
  164. package/dist/react/core/feedback/Dialog.js +189 -197
  165. package/dist/react/core/feedback/Dialog.js.map +1 -1
  166. package/dist/react/core/feedback/Toast.d.ts +2 -3
  167. package/dist/react/core/feedback/Toast.js +346 -207
  168. package/dist/react/core/feedback/Toast.js.map +1 -1
  169. package/dist/react/core/index.d.ts +26 -26
  170. package/dist/react/core/index.js +7 -119
  171. package/dist/react/core/inputs/Button.d.ts +2 -3
  172. package/dist/react/core/inputs/Button.js +281 -304
  173. package/dist/react/core/inputs/Button.js.map +1 -1
  174. package/dist/react/core/inputs/Checkbox.d.ts +2 -3
  175. package/dist/react/core/inputs/Checkbox.js +139 -127
  176. package/dist/react/core/inputs/Checkbox.js.map +1 -1
  177. package/dist/react/core/inputs/Input.d.ts +3 -4
  178. package/dist/react/core/inputs/Input.js +277 -285
  179. package/dist/react/core/inputs/Input.js.map +1 -1
  180. package/dist/react/core/inputs/LimitsBar.d.ts +1 -2
  181. package/dist/react/core/inputs/LimitsBar.js +271 -196
  182. package/dist/react/core/inputs/LimitsBar.js.map +1 -1
  183. package/dist/react/core/inputs/NumberInput.d.ts +2 -3
  184. package/dist/react/core/inputs/NumberInput.js +531 -503
  185. package/dist/react/core/inputs/NumberInput.js.map +1 -1
  186. package/dist/react/core/inputs/PinInput.d.ts +2 -3
  187. package/dist/react/core/inputs/PinInput.js +224 -207
  188. package/dist/react/core/inputs/PinInput.js.map +1 -1
  189. package/dist/react/core/inputs/Select.d.ts +1 -2
  190. package/dist/react/core/inputs/Select.js +298 -299
  191. package/dist/react/core/inputs/Select.js.map +1 -1
  192. package/dist/react/core/inputs/Toggle.d.ts +2 -3
  193. package/dist/react/core/inputs/Toggle.js +142 -148
  194. package/dist/react/core/inputs/Toggle.js.map +1 -1
  195. package/dist/react/core/layout/Box.d.ts +3 -4
  196. package/dist/react/core/layout/Box.js +118 -122
  197. package/dist/react/core/layout/Box.js.map +1 -1
  198. package/dist/react/core/layout/Center.d.ts +0 -1
  199. package/dist/react/core/layout/Center.js +45 -32
  200. package/dist/react/core/layout/Center.js.map +1 -1
  201. package/dist/react/core/layout/Divider.d.ts +1 -2
  202. package/dist/react/core/layout/Divider.js +112 -106
  203. package/dist/react/core/layout/Divider.js.map +1 -1
  204. package/dist/react/core/layout/Flex.d.ts +4 -5
  205. package/dist/react/core/layout/Flex.js +120 -124
  206. package/dist/react/core/layout/Flex.js.map +1 -1
  207. package/dist/react/core/layout/Grid.d.ts +5 -6
  208. package/dist/react/core/layout/Grid.js +131 -138
  209. package/dist/react/core/layout/Grid.js.map +1 -1
  210. package/dist/react/core/layout/Spacer.d.ts +0 -1
  211. package/dist/react/core/layout/Spacer.js +36 -29
  212. package/dist/react/core/layout/Spacer.js.map +1 -1
  213. package/dist/react/core/layout/Stack.d.ts +4 -5
  214. package/dist/react/core/layout/Stack.js +127 -70
  215. package/dist/react/core/layout/Stack.js.map +1 -1
  216. package/dist/react/core/layout/responsive.d.ts +1 -2
  217. package/dist/react/core/layout/responsive.js +25 -19
  218. package/dist/react/core/layout/responsive.js.map +1 -1
  219. package/dist/react/core/layout/useBreakpoint.d.ts +1 -1
  220. package/dist/react/core/layout/useBreakpoint.js +119 -66
  221. package/dist/react/core/layout/useBreakpoint.js.map +1 -1
  222. package/dist/react/core/navigation/AppBar.d.ts +1 -2
  223. package/dist/react/core/navigation/AppBar.js +827 -805
  224. package/dist/react/core/navigation/AppBar.js.map +1 -1
  225. package/dist/react/core/navigation/Pagination.d.ts +2 -3
  226. package/dist/react/core/navigation/Pagination.js +207 -187
  227. package/dist/react/core/navigation/Pagination.js.map +1 -1
  228. package/dist/react/core/navigation/SideNav.d.ts +5 -6
  229. package/dist/react/core/navigation/SideNav.js +855 -629
  230. package/dist/react/core/navigation/SideNav.js.map +1 -1
  231. package/dist/react/core/navigation/Tabs.d.ts +0 -1
  232. package/dist/react/core/navigation/Tabs.js +113 -119
  233. package/dist/react/core/navigation/Tabs.js.map +1 -1
  234. package/dist/react/core/overlays/Popover.d.ts +2 -3
  235. package/dist/react/core/overlays/Popover.js +303 -282
  236. package/dist/react/core/overlays/Popover.js.map +1 -1
  237. package/dist/react/core/overlays/SidePanel.d.ts +1 -2
  238. package/dist/react/core/overlays/SidePanel.js +217 -212
  239. package/dist/react/core/overlays/SidePanel.js.map +1 -1
  240. package/dist/react/core/overlays/Tooltip.d.ts +1 -2
  241. package/dist/react/core/overlays/Tooltip.js +170 -176
  242. package/dist/react/core/overlays/Tooltip.js.map +1 -1
  243. package/dist/react/core/widgets/ActivityPlanner.d.ts +0 -1
  244. package/dist/react/core/widgets/ActivityPlanner.js +855 -517
  245. package/dist/react/core/widgets/ActivityPlanner.js.map +1 -1
  246. package/dist/react/core/widgets/Capture.d.ts +2 -3
  247. package/dist/react/core/widgets/Capture.js +965 -779
  248. package/dist/react/core/widgets/Capture.js.map +1 -1
  249. package/dist/react/core/widgets/ChatPanel.d.ts +16 -17
  250. package/dist/react/core/widgets/ChatPanel.js +1734 -1034
  251. package/dist/react/core/widgets/ChatPanel.js.map +1 -1
  252. package/dist/react/core/widgets/ColorPickerPanel.d.ts +0 -1
  253. package/dist/react/core/widgets/ColorPickerPanel.js +298 -287
  254. package/dist/react/core/widgets/ColorPickerPanel.js.map +1 -1
  255. package/dist/react/core/widgets/CommandBuilder.d.ts +2 -3
  256. package/dist/react/core/widgets/CommandBuilder.js +624 -516
  257. package/dist/react/core/widgets/CommandBuilder.js.map +1 -1
  258. package/dist/react/core/widgets/ConnectionForm.d.ts +2 -3
  259. package/dist/react/core/widgets/ConnectionForm.js +528 -492
  260. package/dist/react/core/widgets/ConnectionForm.js.map +1 -1
  261. package/dist/react/core/widgets/FileExplorer.d.ts +3 -4
  262. package/dist/react/core/widgets/FileExplorer.js +617 -505
  263. package/dist/react/core/widgets/FileExplorer.js.map +1 -1
  264. package/dist/react/core/widgets/HexViewer.d.ts +2 -3
  265. package/dist/react/core/widgets/HexViewer.js +1353 -1091
  266. package/dist/react/core/widgets/HexViewer.js.map +1 -1
  267. package/dist/react/core/widgets/ImageFrame.d.ts +1 -2
  268. package/dist/react/core/widgets/ImageFrame.js +230 -193
  269. package/dist/react/core/widgets/ImageFrame.js.map +1 -1
  270. package/dist/react/core/widgets/ImageGallery.d.ts +2 -1
  271. package/dist/react/core/widgets/ImageGallery.js +380 -314
  272. package/dist/react/core/widgets/ImageGallery.js.map +1 -1
  273. package/dist/react/core/widgets/ImagePanel.d.ts +0 -1
  274. package/dist/react/core/widgets/ImagePanel.js +110 -101
  275. package/dist/react/core/widgets/ImagePanel.js.map +1 -1
  276. package/dist/react/core/widgets/LogViewer.d.ts +2 -3
  277. package/dist/react/core/widgets/LogViewer.js +711 -607
  278. package/dist/react/core/widgets/LogViewer.js.map +1 -1
  279. package/dist/react/core/widgets/MessageStream.d.ts +3 -4
  280. package/dist/react/core/widgets/MessageStream.js +514 -444
  281. package/dist/react/core/widgets/MessageStream.js.map +1 -1
  282. package/dist/react/core/widgets/MissionCalendar.d.ts +3 -4
  283. package/dist/react/core/widgets/MissionCalendar.js +1488 -1020
  284. package/dist/react/core/widgets/MissionCalendar.js.map +1 -1
  285. package/dist/react/core/widgets/PacketViewer.d.ts +2 -3
  286. package/dist/react/core/widgets/PacketViewer.js +550 -421
  287. package/dist/react/core/widgets/PacketViewer.js.map +1 -1
  288. package/dist/react/core/widgets/capture-placeholder.png.js +6 -5
  289. package/dist/react/core/widgets/capture-placeholder.png.js.map +1 -1
  290. package/dist/react/hooks/index.d.ts +5 -5
  291. package/dist/react/hooks/useAccessWindows.d.ts +0 -1
  292. package/dist/react/hooks/useCompactMode.d.ts +1 -1
  293. package/dist/react/hooks/useCompactMode.js +104 -58
  294. package/dist/react/hooks/useCompactMode.js.map +1 -1
  295. package/dist/react/hooks/useGroundTrackHistory.d.ts +0 -1
  296. package/dist/react/hooks/useSimulationScene.d.ts +1 -2
  297. package/dist/react/hooks/useSimulationScene.js +481 -374
  298. package/dist/react/hooks/useSimulationScene.js.map +1 -1
  299. package/dist/react/index.d.ts +3 -3
  300. package/dist/react/panels/LayerControlPanel.d.ts +1 -2
  301. package/dist/react/panels/LayerControlPanel.js +223 -180
  302. package/dist/react/panels/LayerControlPanel.js.map +1 -1
  303. package/dist/react/panels/ObjectInventoryPanel.d.ts +2 -3
  304. package/dist/react/panels/ObjectInventoryPanel.js +315 -250
  305. package/dist/react/panels/ObjectInventoryPanel.js.map +1 -1
  306. package/dist/react/panels/index.d.ts +1 -1
  307. package/dist/react/panels/index.js +1 -6
  308. package/dist/react/shared/ErrorBoundary.d.ts +1 -2
  309. package/dist/react/shared/ErrorBoundary.js +170 -139
  310. package/dist/react/shared/ErrorBoundary.js.map +1 -1
  311. package/dist/react/shared/Skeleton.d.ts +1 -2
  312. package/dist/react/shared/Skeleton.js +300 -301
  313. package/dist/react/shared/Skeleton.js.map +1 -1
  314. package/dist/react/theme/ThemeProvider.d.ts +2 -321
  315. package/dist/react/theme/ThemeProvider.js +312 -939
  316. package/dist/react/theme/ThemeProvider.js.map +1 -1
  317. package/dist/react/theme/cardAccent.d.ts +1 -2
  318. package/dist/react/theme/cardAccent.js +196 -126
  319. package/dist/react/theme/cardAccent.js.map +1 -1
  320. package/dist/react/theme/config.d.ts +0 -1
  321. package/dist/react/theme/config.js +47 -25
  322. package/dist/react/theme/config.js.map +1 -1
  323. package/dist/react/theme/index.d.ts +2 -2
  324. package/dist/react/theme/index.js +1 -16
  325. package/dist/react/theme/types.d.ts +332 -0
  326. package/dist/react/theme/types.js +39 -0
  327. package/dist/react/theme/types.js.map +1 -0
  328. package/dist/react/types.d.ts +8 -8
  329. package/dist/react/types.js +147 -47
  330. package/dist/react/types.js.map +1 -1
  331. package/dist/react/utils/categoryPalette.d.ts +0 -1
  332. package/dist/react/utils/categoryPalette.js +91 -102
  333. package/dist/react/utils/categoryPalette.js.map +1 -1
  334. package/dist/react/utils/index.d.ts +5 -5
  335. package/dist/react/utils/index.js +457 -371
  336. package/dist/react/utils/index.js.map +1 -1
  337. package/dist/react/visualizations/EclipseTimerCard.d.ts +0 -1
  338. package/dist/react/visualizations/LinkBudgetCard.d.ts +0 -1
  339. package/dist/react/visualizations/NavBallCard.d.ts +0 -1
  340. package/dist/react/visualizations/PropulsionCard.d.ts +1 -2
  341. package/dist/react/visualizations/SensorFootprintCard.d.ts +1 -2
  342. package/dist/react/visualizations/ThermalHeatmapCard.d.ts +0 -1
  343. package/dist/react/visualizations/index.d.ts +3 -3
  344. package/dist/react.d.ts +2 -1
  345. package/dist/react.js +17 -221
  346. package/dist/shaders/atmosphere.frag.js +6 -5
  347. package/dist/shaders/atmosphere.frag.js.map +1 -1
  348. package/dist/shaders/atmosphere.vert.js +6 -5
  349. package/dist/shaders/atmosphere.vert.js.map +1 -1
  350. package/dist/shaders/stars.frag.js +6 -5
  351. package/dist/shaders/stars.frag.js.map +1 -1
  352. package/dist/shaders/stars.vert.js +6 -5
  353. package/dist/shaders/stars.vert.js.map +1 -1
  354. package/dist/style.css +217 -194
  355. package/dist/tokens/core.d.ts +159 -0
  356. package/dist/tokens/core.js +144 -0
  357. package/dist/tokens/core.js.map +1 -0
  358. package/dist/tokens/css-vars.d.ts +213 -2
  359. package/dist/tokens/css-vars.js +590 -227
  360. package/dist/tokens/css-vars.js.map +1 -1
  361. package/dist/tokens/index.d.ts +31 -228
  362. package/dist/tokens/index.js +281 -366
  363. package/dist/tokens/index.js.map +1 -1
  364. package/dist/tokens/palettes/astro.d.ts +129 -0
  365. package/dist/tokens/palettes/astro.js +112 -0
  366. package/dist/tokens/palettes/astro.js.map +1 -0
  367. package/dist/tokens/palettes/index.d.ts +288 -0
  368. package/dist/tokens/palettes/index.js +35 -0
  369. package/dist/tokens/palettes/index.js.map +1 -0
  370. package/dist/tokens/palettes/tailwind.d.ts +324 -0
  371. package/dist/tokens/palettes/tailwind.js +201 -0
  372. package/dist/tokens/palettes/tailwind.js.map +1 -0
  373. package/dist/tokens/palettes/zendir.d.ts +126 -0
  374. package/dist/tokens/palettes/zendir.js +92 -0
  375. package/dist/tokens/palettes/zendir.js.map +1 -0
  376. package/dist/tokens/themes.d.ts +30 -0
  377. package/dist/tokens/themes.js +704 -0
  378. package/dist/tokens/themes.js.map +1 -0
  379. package/dist/tokens/tokens.css +419 -41
  380. package/dist/types/index.d.ts +2 -2
  381. package/package.json +310 -313
  382. package/dist/index.js.map +0 -1
  383. package/dist/react/3d/index.js.map +0 -1
  384. package/dist/react/astro/index.js.map +0 -1
  385. package/dist/react/cards/index.js.map +0 -1
  386. package/dist/react/charts/index.js.map +0 -1
  387. package/dist/react/core/index.js.map +0 -1
  388. package/dist/react/panels/index.js.map +0 -1
  389. package/dist/react/theme/astro-tokens.d.ts +0 -153
  390. package/dist/react/theme/index.js.map +0 -1
  391. package/dist/react.js.map +0 -1
@@ -1,32 +1,40 @@
1
- import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
- import { useRef, useState, useEffect, useCallback } from "react";
3
1
  import { useTheme } from "../theme/ThemeProvider.js";
4
- import L from "leaflet";
5
- import "leaflet/dist/leaflet.css";
2
+ import { FALLBACK_TILE, OSM_ATTRIBUTION, TILE_PRESETS, getBasemapAttribution } from "./groundTrackMapLeafletTiles.js";
6
3
  /* empty css */
7
4
  import { geodesicCirclePointsContinuous, segmentsWithWorldCopies, splitPolylineAtAntimeridian } from "./groundTrackMapLeafletUtils.js";
8
- import { TILE_PRESETS, getBasemapAttribution, FALLBACK_TILE, OSM_ATTRIBUTION } from "./groundTrackMapLeafletTiles.js";
9
- const STATUS_COLOR_MAP = {
10
- normal: "#56f000",
11
- standby: "#2dccff",
12
- caution: "#fce83a",
13
- serious: "#ffb302",
14
- critical: "#ff3838",
15
- off: "#a4abb6"
5
+ import { useCallback, useEffect, useRef, useState } from "react";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ import L from "leaflet";
8
+ import "leaflet/dist/leaflet.css";
9
+ //#region src/react/charts/GroundTrackMapLeaflet.tsx
10
+ /**
11
+ * @zendir/ui - GroundTrackMap Leaflet Provider
12
+ *
13
+ * Leaflet-based 2D world map for ground tracks, stations, and terminator.
14
+ * Uses Zendir hybrid theme; all interactions enabled (zoom, scroll, drag, etc.).
15
+ * Import leaflet-zendir.css when using this component.
16
+ */
17
+ var STATUS_COLOR_MAP = {
18
+ normal: "#56f000",
19
+ standby: "#2dccff",
20
+ caution: "#fce83a",
21
+ serious: "#ffb302",
22
+ critical: "#ff3838",
23
+ off: "#a4abb6"
16
24
  };
17
- const DEFAULT_TRACK_COLORS = [
18
- "#2dccff",
19
- "#3E3CFF",
20
- "#9D70FF",
21
- "#56f000",
22
- "#fce83a",
23
- "#ff7849",
24
- "#2dd4bf",
25
- "#ff3838"
25
+ var DEFAULT_TRACK_COLORS = [
26
+ "#2dccff",
27
+ "#3E3CFF",
28
+ "#9D70FF",
29
+ "#56f000",
30
+ "#fce83a",
31
+ "#ff7849",
32
+ "#2dd4bf",
33
+ "#ff3838"
26
34
  ];
35
+ /** Satellite spacecraft icon with solar panels, matching Astro UXDS style. */
27
36
  function createSatDivIcon(statusColor, trackColor) {
28
- const badge = statusColor === "#56f000" ? `<circle cx="7" cy="7" r="3" fill="${statusColor}"/>` : statusColor === "#2dccff" ? `<circle cx="7" cy="7" r="2.5" fill="none" stroke="${statusColor}" stroke-width="1.5"/>` : statusColor === "#fce83a" ? `<rect x="4.5" y="4.5" width="5" height="5" fill="${statusColor}"/>` : statusColor === "#ffb302" ? `<polygon points="7,3.5 10.5,7 7,10.5 3.5,7" fill="${statusColor}"/>` : statusColor === "#ff3838" ? `<polygon points="7,10 4,4 10,4" fill="${statusColor}"/>` : `<circle cx="7" cy="7" r="2" fill="${statusColor}"/>`;
29
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34">
37
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="34" height="34" viewBox="0 0 34 34">
30
38
  <!-- Glow ring -->
31
39
  <circle cx="17" cy="17" r="15" fill="${statusColor}" fill-opacity="0.12"/>
32
40
  <!-- Main body circle -->
@@ -45,21 +53,21 @@ function createSatDivIcon(statusColor, trackColor) {
45
53
  <line x1="26.5" y1="15" x2="26.5" y2="19" stroke="${trackColor}" stroke-width="0.5" stroke-opacity="0.45"/>
46
54
  <!-- Status badge (top-left) -->
47
55
  <circle cx="7" cy="7" r="5.5" fill="#0d1323"/>
48
- ${badge}
56
+ ${statusColor === "#56f000" ? `<circle cx="7" cy="7" r="3" fill="${statusColor}"/>` : statusColor === "#2dccff" ? `<circle cx="7" cy="7" r="2.5" fill="none" stroke="${statusColor}" stroke-width="1.5"/>` : statusColor === "#fce83a" ? `<rect x="4.5" y="4.5" width="5" height="5" fill="${statusColor}"/>` : statusColor === "#ffb302" ? `<polygon points="7,3.5 10.5,7 7,10.5 3.5,7" fill="${statusColor}"/>` : statusColor === "#ff3838" ? `<polygon points="7,10 4,4 10,4" fill="${statusColor}"/>` : `<circle cx="7" cy="7" r="2" fill="${statusColor}"/>`}
49
57
  </svg>`;
50
- return L.divIcon({
51
- html: svg,
52
- className: "zendir-sat-icon",
53
- iconSize: [34, 34],
54
- iconAnchor: [17, 17],
55
- tooltipAnchor: [0, -18]
56
- });
58
+ return L.divIcon({
59
+ html: svg,
60
+ className: "zendir-sat-icon",
61
+ iconSize: [34, 34],
62
+ iconAnchor: [17, 17],
63
+ tooltipAnchor: [0, -18]
64
+ });
57
65
  }
66
+ /** Ground station icon — dish/antenna shape, matching Astro UXDS style. */
58
67
  function createStationDivIcon(statusColor, type, status) {
59
- const badge = status === "normal" ? `<circle cx="5" cy="5" r="2.5" fill="${statusColor}"/>` : status === "standby" ? `<circle cx="5" cy="5" r="2" fill="none" stroke="${statusColor}" stroke-width="1.2"/>` : status === "caution" ? `<rect x="3" y="3" width="4" height="4" fill="${statusColor}"/>` : status === "serious" ? `<polygon points="5,2.5 7.5,5 5,7.5 2.5,5" fill="${statusColor}"/>` : status === "critical" ? `<polygon points="5,7.5 2.5,2.5 7.5,2.5" fill="${statusColor}"/>` : `<circle cx="5" cy="5" r="1.5" fill="${statusColor}"/>`;
60
- let iconInner = "";
61
- if (type === "phased-array") {
62
- iconInner = `
68
+ const badge = status === "normal" ? `<circle cx="5" cy="5" r="2.5" fill="${statusColor}"/>` : status === "standby" ? `<circle cx="5" cy="5" r="2" fill="none" stroke="${statusColor}" stroke-width="1.2"/>` : status === "caution" ? `<rect x="3" y="3" width="4" height="4" fill="${statusColor}"/>` : status === "serious" ? `<polygon points="5,2.5 7.5,5 5,7.5 2.5,5" fill="${statusColor}"/>` : status === "critical" ? `<polygon points="5,7.5 2.5,2.5 7.5,2.5" fill="${statusColor}"/>` : `<circle cx="5" cy="5" r="1.5" fill="${statusColor}"/>`;
69
+ let iconInner = "";
70
+ if (type === "phased-array") iconInner = `
63
71
  <rect x="9" y="7" width="10" height="8" rx="0.5" fill="none" stroke="${statusColor}" stroke-width="1.2"/>
64
72
  <line x1="12" y1="7" x2="12" y2="15" stroke="${statusColor}" stroke-width="0.7"/>
65
73
  <line x1="15" y1="7" x2="15" y2="15" stroke="${statusColor}" stroke-width="0.7"/>
@@ -67,24 +75,21 @@ function createStationDivIcon(statusColor, type, status) {
67
75
  <line x1="9" y1="13" x2="19" y2="13" stroke="${statusColor}" stroke-width="0.7"/>
68
76
  <line x1="14" y1="15" x2="14" y2="20" stroke="${statusColor}" stroke-width="1.2"/>
69
77
  <line x1="11" y1="20" x2="17" y2="20" stroke="${statusColor}" stroke-width="1.2"/>`;
70
- } else if (type === "relay") {
71
- iconInner = `
78
+ else if (type === "relay") iconInner = `
72
79
  <path d="M 8 16 a 7 7 0 0 1 7 -7" fill="none" stroke="${statusColor}" stroke-width="1.5" stroke-linecap="round"/>
73
80
  <path d="M 8 16 m 2 -2 a 5 5 0 0 1 5 -5" fill="none" stroke="${statusColor}" stroke-width="1.2" stroke-linecap="round"/>
74
81
  <line x1="14" y1="16" x2="14" y2="21" stroke="${statusColor}" stroke-width="1.2"/>
75
82
  <line x1="11" y1="21" x2="17" y2="21" stroke="${statusColor}" stroke-width="1.2"/>
76
83
  <path d="M 16 8 a 3 3 0 0 1 0 4" fill="none" stroke="${statusColor}" stroke-width="1.2" stroke-linecap="round"/>
77
84
  <path d="M 17.5 6.5 a 5 5 0 0 1 0 7" fill="none" stroke="${statusColor}" stroke-width="1" stroke-linecap="round"/>`;
78
- } else {
79
- iconInner = `
85
+ else iconInner = `
80
86
  <path d="M 14 14 m -7 0 a 7 7 0 0 1 7 -7" fill="none" stroke="${statusColor}" stroke-width="1.5" stroke-linecap="round"/>
81
87
  <path d="M 14 14 m -5 0 a 5 5 0 0 1 5 -5" fill="none" stroke="${statusColor}" stroke-width="1.5" stroke-linecap="round"/>
82
88
  <path d="M 14 14 m -3 0 a 3 3 0 0 1 3 -3" fill="none" stroke="${statusColor}" stroke-width="1.5" stroke-linecap="round"/>
83
89
  <circle cx="14" cy="14" r="1.5" fill="${statusColor}"/>
84
90
  <line x1="14" y1="15" x2="14" y2="21" stroke="${statusColor}" stroke-width="1.2"/>
85
91
  <line x1="11" y1="21" x2="17" y2="21" stroke="${statusColor}" stroke-width="1.2"/>`;
86
- }
87
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 28 28">
92
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 28 28">
88
93
  <!-- Glow ring -->
89
94
  <circle cx="14" cy="14" r="13" fill="${statusColor}" fill-opacity="0.1"/>
90
95
  <!-- Background -->
@@ -94,580 +99,591 @@ function createStationDivIcon(statusColor, type, status) {
94
99
  <circle cx="5" cy="5" r="5" fill="#0d1323"/>
95
100
  ${badge}
96
101
  </svg>`;
97
- return L.divIcon({
98
- html: svg,
99
- className: "zendir-station-icon",
100
- iconSize: [28, 28],
101
- iconAnchor: [14, 14],
102
- tooltipAnchor: [0, -14]
103
- });
102
+ return L.divIcon({
103
+ html: svg,
104
+ className: "zendir-station-icon",
105
+ iconSize: [28, 28],
106
+ iconAnchor: [14, 14],
107
+ tooltipAnchor: [0, -14]
108
+ });
104
109
  }
110
+ /**
111
+ * Compute a terminator line with CONTINUOUS (unwrapped) longitudes.
112
+ *
113
+ * `depressionDeg` offsets the solar zenith angle to produce twilight
114
+ * boundaries instead of the geometric sunset line:
115
+ * - 0° = geometric sunset/sunrise (day/night boundary)
116
+ * - 6° = civil twilight (horizon still visible, outdoor activities possible)
117
+ * - 12° = nautical twilight (horizon barely visible, stars appearing)
118
+ * - 18° = astronomical twilight (sky fully dark for observation)
119
+ *
120
+ * These thresholds follow IAU/USNO standard definitions used in celestial
121
+ * navigation, Astro UX space ops dashboards, and STK/GMAT mission tools.
122
+ */
105
123
  function calculateTerminatorContinuous(date, depressionDeg = 0, numPoints = 360) {
106
- const dayOfYear = Math.floor((date.getTime() - Date.UTC(date.getUTCFullYear(), 0, 0)) / 864e5);
107
- const declination = -23.44 * Math.cos(2 * Math.PI / 365 * (dayOfYear + 10));
108
- const decRad = declination * Math.PI / 180;
109
- const B = 2 * Math.PI / 365 * (dayOfYear - 81);
110
- const eotMinutes = 9.87 * Math.sin(2 * B) - 7.53 * Math.cos(B) - 1.5 * Math.sin(B);
111
- const utcHours = date.getUTCHours() + date.getUTCMinutes() / 60 + date.getUTCSeconds() / 3600;
112
- const solarHours = utcHours + eotMinutes / 60;
113
- const subSolarLon = -(solarHours - 12) * 15;
114
- const depRad = depressionDeg * Math.PI / 180;
115
- const sunset = [];
116
- const sunrise = [];
117
- for (let i = 0; i <= numPoints; i++) {
118
- const lat = -89.999 + i / numPoints * 179.998;
119
- const latRad = lat * (Math.PI / 180);
120
- const cosH = -(Math.sin(depRad) + Math.sin(latRad) * Math.sin(decRad)) / (Math.cos(latRad) * Math.cos(decRad));
121
- if (cosH < -1) ;
122
- else if (cosH > 1) {
123
- sunset.push([lat, subSolarLon]);
124
- sunrise.push([lat, subSolarLon + 360]);
125
- } else {
126
- const H = Math.acos(cosH) * 180 / Math.PI;
127
- sunset.push([lat, subSolarLon + H]);
128
- sunrise.push([lat, subSolarLon + 360 - H]);
129
- }
130
- }
131
- return { sunset, sunrise };
124
+ const dayOfYear = Math.floor((date.getTime() - Date.UTC(date.getUTCFullYear(), 0, 0)) / 864e5);
125
+ const decRad = -23.44 * Math.cos(2 * Math.PI / 365 * (dayOfYear + 10)) * Math.PI / 180;
126
+ const B = 2 * Math.PI / 365 * (dayOfYear - 81);
127
+ const eotMinutes = 9.87 * Math.sin(2 * B) - 7.53 * Math.cos(B) - 1.5 * Math.sin(B);
128
+ const subSolarLon = -(date.getUTCHours() + date.getUTCMinutes() / 60 + date.getUTCSeconds() / 3600 + eotMinutes / 60 - 12) * 15;
129
+ const depRad = depressionDeg * Math.PI / 180;
130
+ const sunset = [];
131
+ const sunrise = [];
132
+ for (let i = 0; i <= numPoints; i++) {
133
+ const lat = -89.999 + i / numPoints * 179.998;
134
+ const latRad = lat * (Math.PI / 180);
135
+ const cosH = -(Math.sin(depRad) + Math.sin(latRad) * Math.sin(decRad)) / (Math.cos(latRad) * Math.cos(decRad));
136
+ if (cosH < -1) {} else if (cosH > 1) {
137
+ sunset.push([lat, subSolarLon]);
138
+ sunrise.push([lat, subSolarLon + 360]);
139
+ } else {
140
+ const H = Math.acos(cosH) * 180 / Math.PI;
141
+ sunset.push([lat, subSolarLon + H]);
142
+ sunrise.push([lat, subSolarLon + 360 - H]);
143
+ }
144
+ }
145
+ return {
146
+ sunset,
147
+ sunrise
148
+ };
132
149
  }
150
+ /**
151
+ * Build the full night polygon, properly closing the loops around the poles.
152
+ * Ensures longitudes are strictly continuous so the map projection doesn't
153
+ * draw antimeridian lines across the day side.
154
+ */
133
155
  function buildNightPolygon(terminator) {
134
- const { sunset, sunrise } = terminator;
135
- if (!sunset.length || !sunrise.length) return [];
136
- const poly = [];
137
- poly.push(...sunset);
138
- poly.push(...[...sunrise].reverse());
139
- return poly;
156
+ const { sunset, sunrise } = terminator;
157
+ if (!sunset.length || !sunrise.length) return [];
158
+ const poly = [];
159
+ poly.push(...sunset);
160
+ poly.push(...[...sunrise].reverse());
161
+ return poly;
140
162
  }
141
- function GroundTrackMapLeaflet({
142
- allSatellites,
143
- groundStations,
144
- showTerminator = true,
145
- terminatorTime,
146
- showGrid = false,
147
- showLegend = true,
148
- showEquator = false,
149
- showRecenterButton = true,
150
- showMapStyleToggle = true,
151
- defaultCenter = [20, 0],
152
- defaultZoom = 2,
153
- height = "100%",
154
- width = "100%",
155
- minHeight = "400px",
156
- emptyMessage = "No orbital data available",
157
- tileUrl,
158
- nightTileUrl,
159
- lightSources,
160
- className = "",
161
- onSatelliteClick,
162
- onStationClick,
163
- pins,
164
- pinsEditable = false,
165
- onPinAdd,
166
- onPinUpdate,
167
- onPinRemove,
168
- customLayers,
169
- onLayerChange,
170
- showCelestialMarkers
171
- }) {
172
- const { tokens } = useTheme();
173
- const containerRef = useRef(null);
174
- const mapRef = useRef(null);
175
- const tileLayerRef = useRef(null);
176
- const overlayGroupRef = useRef(null);
177
- const controlsRef = useRef([]);
178
- const pinsGroupRef = useRef(null);
179
- const [ready, setReady] = useState(false);
180
- const [layersPanelOpen, setLayersPanelOpen] = useState(false);
181
- const layersPanelRef = useRef(null);
182
- useEffect(() => {
183
- if (!layersPanelOpen) return;
184
- const handleClickOutside = (e) => {
185
- if (layersPanelRef.current && !layersPanelRef.current.contains(e.target)) {
186
- setLayersPanelOpen(false);
187
- }
188
- };
189
- document.addEventListener("mousedown", handleClickOutside);
190
- return () => document.removeEventListener("mousedown", handleClickOutside);
191
- }, [layersPanelOpen]);
192
- const TILE_STORAGE_KEY = "zendir-map-tile-style";
193
- const isExplicitTileUrl = tileUrl !== void 0;
194
- const [tileStyle, setTileStyle] = useState(() => {
195
- if (isExplicitTileUrl) return "dark";
196
- try {
197
- const saved = localStorage.getItem(TILE_STORAGE_KEY);
198
- if (saved === "dark" || saved === "satellite") return saved;
199
- } catch {
200
- }
201
- return "dark";
202
- });
203
- const handleTileStyleChange = useCallback((style) => {
204
- setTileStyle(style);
205
- try {
206
- localStorage.setItem(TILE_STORAGE_KEY, style);
207
- } catch {
208
- }
209
- }, []);
210
- const effectiveTileUrl = isExplicitTileUrl ? tileUrl : TILE_PRESETS[tileStyle];
211
- const [internalTerminator, setInternalTerminator] = useState(showTerminator);
212
- const [internalGrid, setInternalGrid] = useState(showGrid);
213
- useEffect(() => {
214
- setInternalTerminator(showTerminator);
215
- }, [showTerminator]);
216
- useEffect(() => {
217
- setInternalGrid(showGrid);
218
- }, [showGrid]);
219
- const [customLayerState, setCustomLayerState] = useState(() => {
220
- const state = {};
221
- for (const l of customLayers ?? []) {
222
- state[l.id] = l.defaultEnabled ?? true;
223
- }
224
- return state;
225
- });
226
- useEffect(() => {
227
- setCustomLayerState((prev) => {
228
- const next = { ...prev };
229
- for (const l of customLayers ?? []) {
230
- if (!(l.id in next)) next[l.id] = l.defaultEnabled ?? true;
231
- }
232
- return next;
233
- });
234
- }, [customLayers]);
235
- const handleLayerToggle = useCallback((layerId, enabled) => {
236
- if (layerId === "terminator") {
237
- setInternalTerminator(enabled);
238
- } else if (layerId === "grid") {
239
- setInternalGrid(enabled);
240
- } else {
241
- setCustomLayerState((prev) => ({ ...prev, [layerId]: enabled }));
242
- }
243
- onLayerChange == null ? void 0 : onLayerChange(layerId, enabled);
244
- }, [onLayerChange]);
245
- const clearLayers = useCallback(() => {
246
- var _a;
247
- const map = mapRef.current;
248
- if (!map) return;
249
- (_a = overlayGroupRef.current) == null ? void 0 : _a.clearLayers();
250
- controlsRef.current.forEach((ctrl) => map.removeControl(ctrl));
251
- controlsRef.current = [];
252
- }, []);
253
- const addLayer = useCallback((layer) => {
254
- var _a;
255
- (_a = overlayGroupRef.current) == null ? void 0 : _a.addLayer(layer);
256
- }, []);
257
- useEffect(() => {
258
- const el = containerRef.current;
259
- if (!el) return;
260
- const center = defaultCenter ?? [20, 0];
261
- const zoom = defaultZoom ?? 2;
262
- const map = L.map(el, {
263
- center,
264
- zoom,
265
- zoomControl: false,
266
- scrollWheelZoom: true,
267
- doubleClickZoom: true,
268
- touchZoom: true,
269
- boxZoom: true,
270
- keyboard: true,
271
- dragging: true,
272
- attributionControl: true,
273
- maxBounds: [[-90, -540], [90, 540]],
274
- maxBoundsViscosity: 1
275
- });
276
- map.attributionControl.setPrefix("");
277
- const overlayGroup = L.layerGroup();
278
- overlayGroup.addTo(map);
279
- overlayGroupRef.current = overlayGroup;
280
- const pinsGroup = L.layerGroup();
281
- pinsGroup.addTo(map);
282
- pinsGroupRef.current = pinsGroup;
283
- mapRef.current = map;
284
- setReady(true);
285
- const onSize = () => {
286
- map.invalidateSize({ animate: false });
287
- };
288
- const raf = requestAnimationFrame(onSize);
289
- const t1 = setTimeout(onSize, 150);
290
- const t2 = setTimeout(onSize, 500);
291
- return () => {
292
- var _a;
293
- clearTimeout(t1);
294
- clearTimeout(t2);
295
- cancelAnimationFrame(raf);
296
- clearLayers();
297
- (_a = pinsGroupRef.current) == null ? void 0 : _a.clearLayers();
298
- pinsGroupRef.current = null;
299
- tileLayerRef.current = null;
300
- map.remove();
301
- mapRef.current = null;
302
- overlayGroupRef.current = null;
303
- };
304
- }, [clearLayers]);
305
- useEffect(() => {
306
- const map = mapRef.current;
307
- if (!map || !ready) return;
308
- if (tileLayerRef.current) {
309
- tileLayerRef.current.remove();
310
- tileLayerRef.current = null;
311
- }
312
- const isCartoTiles = effectiveTileUrl.includes("cartocdn");
313
- const basemapAttribution = getBasemapAttribution(effectiveTileUrl);
314
- const tile = L.tileLayer(effectiveTileUrl, {
315
- maxZoom: 19,
316
- subdomains: isCartoTiles ? "abcd" : "abc",
317
- crossOrigin: true,
318
- attribution: basemapAttribution || void 0
319
- });
320
- tile.addTo(map);
321
- tile.bringToBack();
322
- tileLayerRef.current = tile;
323
- let hasSwitchedToFallback = false;
324
- const onTileError = () => {
325
- if (hasSwitchedToFallback) return;
326
- hasSwitchedToFallback = true;
327
- tile.off("tileerror", onTileError);
328
- tile.remove();
329
- const fallback = L.tileLayer(FALLBACK_TILE, {
330
- maxZoom: 19,
331
- subdomains: "abc",
332
- crossOrigin: true,
333
- attribution: OSM_ATTRIBUTION
334
- });
335
- fallback.addTo(map);
336
- fallback.bringToBack();
337
- tileLayerRef.current = fallback;
338
- };
339
- tile.on("tileerror", onTileError);
340
- }, [effectiveTileUrl, ready]);
341
- useEffect(() => {
342
- if (!ready || !mapRef.current) return;
343
- const map = mapRef.current;
344
- clearLayers();
345
- if (nightTileUrl && internalTerminator) {
346
- const nightTile = L.tileLayer(nightTileUrl, {
347
- maxZoom: 19,
348
- crossOrigin: true,
349
- opacity: 0.7,
350
- className: "zendir-night-tiles"
351
- });
352
- addLayer(nightTile);
353
- }
354
- if (internalTerminator) {
355
- const now = terminatorTime ?? /* @__PURE__ */ new Date();
356
- const isSatelliteTile = tileStyle === "satellite";
357
- const BAND_STEP = 1.5;
358
- const MAX_DEP = 30;
359
- const NIGHT_OPACITY = isSatelliteTile ? 0.75 : 0.38;
360
- const NIGHT_COLOR = isSatelliteTile ? "#030810" : "#2a3a52";
361
- const bandSteps = [];
362
- for (let d = 0; d <= MAX_DEP; d += BAND_STEP) {
363
- const t = Math.min(d / 18, 1);
364
- const opacity = Math.pow(t, 1.6) * (NIGHT_OPACITY * 0.85) + (d > 18 ? (d - 18) / 12 * (NIGHT_OPACITY * 0.15) : 0);
365
- bandSteps.push({ depression: d, opacity: Math.min(opacity, NIGHT_OPACITY), color: NIGHT_COLOR });
366
- }
367
- bandSteps.push({ depression: 90, opacity: NIGHT_OPACITY, color: NIGHT_COLOR });
368
- let prevOpacity = 0;
369
- for (const b of bandSteps) {
370
- const terminator = calculateTerminatorContinuous(now, Math.min(b.depression, 89));
371
- const bandOpacity = b.opacity - prevOpacity;
372
- if (bandOpacity < 2e-3 || terminator.sunset.length === 0) {
373
- prevOpacity = b.opacity;
374
- continue;
375
- }
376
- const poly = buildNightPolygon(terminator);
377
- if (poly.length < 3) continue;
378
- [0, 360, -360].forEach((offset) => {
379
- const shifted = poly.map(([lat, lon]) => [lat, lon + offset]);
380
- addLayer(L.polygon(shifted, {
381
- color: "transparent",
382
- fillColor: b.color,
383
- fillOpacity: bandOpacity,
384
- interactive: false
385
- }));
386
- });
387
- prevOpacity = b.opacity;
388
- }
389
- const terminatorEdge = calculateTerminatorContinuous(now, 0);
390
- const glowColor = isSatelliteTile ? "#a0a0a0" : "#909090";
391
- const glowOpacity = isSatelliteTile ? 0.15 : 0.08;
392
- const coreColor = isSatelliteTile ? "#c0c0c0" : "#a8a8a8";
393
- const coreOpacity = isSatelliteTile ? 0.45 : 0.3;
394
- if (terminatorEdge.sunset.length > 2) {
395
- const sunsetLine = terminatorEdge.sunset.map(([lat, lon]) => [lat, lon]);
396
- const sunriseLine = terminatorEdge.sunrise.map(([lat, lon]) => [lat, lon]);
397
- [0, 360, -360].forEach((offset) => {
398
- addLayer(L.polyline(
399
- sunsetLine.map(([lat, lon]) => [lat, lon + offset]),
400
- { color: glowColor, weight: 3, opacity: glowOpacity, interactive: false, smoothFactor: 1.5 }
401
- ));
402
- addLayer(L.polyline(
403
- sunriseLine.map(([lat, lon]) => [lat, lon + offset]),
404
- { color: glowColor, weight: 3, opacity: glowOpacity, interactive: false, smoothFactor: 1.5 }
405
- ));
406
- addLayer(L.polyline(
407
- sunsetLine.map(([lat, lon]) => [lat, lon + offset]),
408
- { color: coreColor, weight: 1, opacity: coreOpacity, interactive: false, smoothFactor: 1.5 }
409
- ));
410
- addLayer(L.polyline(
411
- sunriseLine.map(([lat, lon]) => [lat, lon + offset]),
412
- { color: coreColor, weight: 1, opacity: coreOpacity, interactive: false, smoothFactor: 1.5 }
413
- ));
414
- });
415
- }
416
- }
417
- if (lightSources && lightSources.length > 0 && internalTerminator) {
418
- const now = terminatorTime ?? /* @__PURE__ */ new Date();
419
- const dayOfYear = Math.floor((now.getTime() - Date.UTC(now.getUTCFullYear(), 0, 0)) / 864e5);
420
- const declination = -23.44 * Math.cos(2 * Math.PI / 365 * (dayOfYear + 10));
421
- const decRad = declination * Math.PI / 180;
422
- const lsSubSolarLon = -(now.getUTCHours() + now.getUTCMinutes() / 60 - 12) * 15;
423
- for (const light of lightSources) {
424
- const latRad = light.latitude * Math.PI / 180;
425
- const sinAlt = Math.sin(latRad) * Math.sin(decRad) + Math.cos(latRad) * Math.cos(decRad) * Math.cos((light.longitude - lsSubSolarLon) * Math.PI / 180);
426
- const solarAltDeg = Math.asin(sinAlt) * 180 / Math.PI;
427
- let nightFactor;
428
- if (solarAltDeg >= 0) nightFactor = 0;
429
- else if (solarAltDeg <= -18) nightFactor = 1;
430
- else nightFactor = Math.min(1, -solarAltDeg / 18);
431
- if (nightFactor < 0.01) continue;
432
- const r = light.radius ?? 4;
433
- const intensity = light.intensity ?? 0.8;
434
- const color = light.color ?? "#ffeedd";
435
- const alpha = intensity * nightFactor;
436
- const glowMarker = L.circleMarker([light.latitude, light.longitude], {
437
- radius: r * 2,
438
- fillColor: color,
439
- fillOpacity: alpha * 0.4,
440
- color,
441
- weight: 0,
442
- interactive: !!light.label
443
- });
444
- addLayer(glowMarker);
445
- const coreMarker = L.circleMarker([light.latitude, light.longitude], {
446
- radius: Math.max(1, r * 0.5),
447
- fillColor: color,
448
- fillOpacity: Math.min(1, alpha * 1.2),
449
- color: "transparent",
450
- weight: 0,
451
- interactive: false
452
- });
453
- addLayer(coreMarker);
454
- if (light.label) {
455
- glowMarker.bindTooltip(light.label, {
456
- permanent: false,
457
- direction: "top",
458
- className: "zendir-leaflet-tooltip"
459
- });
460
- }
461
- }
462
- }
463
- if (internalGrid) {
464
- const gridStyle = { color: "rgba(157, 112, 255, 0.12)", weight: 0.5, interactive: false };
465
- [-360, 0, 360].forEach((offset) => {
466
- for (let lon = -180; lon <= 180; lon += 30) {
467
- const l = lon + offset;
468
- addLayer(L.polyline([[90, l], [-90, l]], gridStyle));
469
- }
470
- for (let lat = -90; lat <= 90; lat += 30) {
471
- addLayer(L.polyline([[lat, -180 + offset], [lat, 180 + offset]], gridStyle));
472
- }
473
- });
474
- }
475
- if (showEquator) {
476
- [-360, 0, 360].forEach((offset) => {
477
- addLayer(L.polyline([[0, -180 + offset], [0, 180 + offset]], {
478
- color: "rgba(88, 166, 255, 0.25)",
479
- weight: 1,
480
- dashArray: "6, 4",
481
- interactive: false
482
- }));
483
- });
484
- }
485
- groundStations.forEach((gs) => {
486
- const status = ("status" in gs ? gs.status : void 0) ?? "standby";
487
- const statusColor = STATUS_COLOR_MAP[status] || STATUS_COLOR_MAP.standby;
488
- const stationType = ("type" in gs ? gs.type : void 0) ?? "dish";
489
- const radius = "coverageRadius" in gs ? gs.coverageRadius : void 0;
490
- const showCoverage = "showCoverage" in gs ? gs.showCoverage : false;
491
- if (radius != null && radius > 0 && showCoverage) {
492
- const ring = geodesicCirclePointsContinuous(gs.latitude, gs.longitude, radius, 64);
493
- [0, 360, -360].forEach((offset) => {
494
- addLayer(L.polygon(ring.map(([lat, lon]) => [lat, lon + offset]), {
495
- color: `${statusColor}55`,
496
- fillColor: statusColor,
497
- fillOpacity: 0.1,
498
- weight: 1,
499
- dashArray: "4, 3",
500
- interactive: false
501
- }));
502
- });
503
- }
504
- const gsIcon = createStationDivIcon(statusColor, stationType, status);
505
- const marker = L.marker([gs.latitude, gs.longitude], { icon: gsIcon });
506
- addLayer(marker);
507
- const statusLabel = status !== "standby" ? ` · ${status}` : "";
508
- marker.bindTooltip(
509
- `<strong>${gs.name}</strong>${statusLabel}${"network" in gs && gs.network ? `<br/><span style="opacity:0.7">${gs.network}</span>` : ""}`,
510
- { permanent: false, direction: "auto", className: "zendir-leaflet-tooltip" }
511
- );
512
- marker.on("click", () => {
513
- onStationClick == null ? void 0 : onStationClick("id" in gs ? String(gs.id) : gs.name);
514
- });
515
- });
516
- allSatellites.forEach((sat, satIdx) => {
517
- const track = sat.groundTrack;
518
- if (!track || track.length === 0) return;
519
- const color = sat.color || DEFAULT_TRACK_COLORS[satIdx % DEFAULT_TRACK_COLORS.length];
520
- const futureIdx = sat.futureTrackIndex ?? track.length;
521
- const pastLatLngs = track.slice(0, futureIdx).map((p) => [p.latitude, p.longitude]);
522
- segmentsWithWorldCopies(splitPolylineAtAntimeridian(pastLatLngs)).forEach((seg) => {
523
- if (seg.length >= 2) {
524
- addLayer(L.polyline(seg, { color, weight: 2.5, opacity: 1 }));
525
- }
526
- });
527
- if (futureIdx < track.length) {
528
- const futureLatLngs = track.slice(futureIdx).map((p) => [p.latitude, p.longitude]);
529
- segmentsWithWorldCopies(splitPolylineAtAntimeridian(futureLatLngs)).forEach((seg) => {
530
- if (seg.length >= 2) {
531
- addLayer(L.polyline(seg, { color, weight: 1.5, opacity: 0.5, dashArray: "6, 4" }));
532
- }
533
- });
534
- }
535
- if (sat.accessMask && sat.accessMask.some(Boolean)) {
536
- let segment = [];
537
- for (let i = 0; i < track.length; i++) {
538
- if (sat.accessMask[i]) {
539
- segment.push([track[i].latitude, track[i].longitude]);
540
- } else if (segment.length >= 2) {
541
- segmentsWithWorldCopies(splitPolylineAtAntimeridian(segment)).forEach((seg) => {
542
- if (seg.length >= 2) {
543
- addLayer(L.polyline(seg, { color: STATUS_COLOR_MAP.normal, weight: 4, opacity: 0.9 }));
544
- }
545
- });
546
- segment = [];
547
- } else {
548
- segment = [];
549
- }
550
- }
551
- if (segment.length >= 2) {
552
- segmentsWithWorldCopies(splitPolylineAtAntimeridian(segment)).forEach((seg) => {
553
- if (seg.length >= 2) {
554
- addLayer(L.polyline(seg, { color: STATUS_COLOR_MAP.normal, weight: 4, opacity: 0.9 }));
555
- }
556
- });
557
- }
558
- }
559
- if (sat.passMarkers && sat.passMarkers.length > 0) {
560
- sat.passMarkers.forEach((pm) => {
561
- const isAos = pm.type === "aos";
562
- const pmColor = isAos ? "#56f000" : "#ff3838";
563
- const pmMarker = L.circleMarker([pm.latitude, pm.longitude], {
564
- radius: 5,
565
- fillColor: pmColor,
566
- color: `${pmColor}cc`,
567
- weight: 1.5,
568
- fillOpacity: 0.9
569
- });
570
- addLayer(pmMarker);
571
- pmMarker.bindTooltip(`${pm.type.toUpperCase()}${pm.label ? ` ${pm.label}` : ""}`, {
572
- permanent: false,
573
- direction: "auto",
574
- className: "zendir-leaflet-tooltip"
575
- });
576
- });
577
- }
578
- if (sat.showFootprint && sat.footprintRadius) {
579
- const lastIdx = futureIdx > 0 ? Math.min(futureIdx - 1, track.length - 1) : track.length - 1;
580
- const last = track[lastIdx];
581
- const ring = geodesicCirclePointsContinuous(last.latitude, last.longitude, sat.footprintRadius, 64);
582
- [0, 360, -360].forEach((offset) => {
583
- addLayer(L.polygon(ring.map(([lat, lon]) => [lat, lon + offset]), {
584
- color: `${color}50`,
585
- fillColor: color,
586
- fillOpacity: 0.03,
587
- weight: 1,
588
- dashArray: "4, 4",
589
- interactive: false
590
- }));
591
- });
592
- }
593
- const currentIdx = futureIdx > 0 ? Math.min(futureIdx - 1, track.length - 1) : track.length - 1;
594
- const current = track[currentIdx];
595
- const statusColor = STATUS_COLOR_MAP[sat.status || "normal"] || STATUS_COLOR_MAP.normal;
596
- const satIcon = createSatDivIcon(statusColor, color);
597
- const satMarker = L.marker([current.latitude, current.longitude], { icon: satIcon });
598
- addLayer(satMarker);
599
- satMarker.bindTooltip(
600
- `<strong style="color:${color}">${sat.name}</strong><br/>Lat ${current.latitude.toFixed(3)}° &nbsp; Lon ${current.longitude.toFixed(3)}°` + (current.altitude != null ? `<br/>Alt ${current.altitude.toFixed(1)} km` : "") + (sat.status ? `<br/><span style="color:${statusColor}">${sat.status.toUpperCase()}</span>` : ""),
601
- { permanent: false, direction: "auto", className: "zendir-leaflet-tooltip" }
602
- );
603
- satMarker.on("click", () => onSatelliteClick == null ? void 0 : onSatelliteClick(sat.id));
604
- });
605
- if (showLegend && (allSatellites.length > 0 || groundStations.length > 0)) {
606
- const Legend = L.Control.extend({
607
- onAdd() {
608
- const div = L.DomUtil.create("div", "leaflet-legend-zendir");
609
- div.style.cssText = `
610
- padding: 8px 12px; background: rgba(15,21,32,0.9); border: 1px solid rgba(157,112,255,0.2);
611
- border-radius: 8px; color: #e4e0f0; font-size: 11px; font-family: Roboto, sans-serif;
163
+ function GroundTrackMapLeaflet({ allSatellites, groundStations, showTerminator = true, terminatorTime, showGrid = false, showLegend = true, showEquator = false, showRecenterButton = true, showMapStyleToggle = true, defaultCenter = [20, 0], defaultZoom = 2, height = "100%", width = "100%", minHeight = "400px", emptyMessage = "No orbital data available", tileUrl, nightTileUrl, lightSources, className = "", onSatelliteClick, onStationClick, pins, pinsEditable = false, onPinAdd, onPinUpdate, onPinRemove, customLayers, onLayerChange, showCelestialMarkers }) {
164
+ const { tokens } = useTheme();
165
+ const containerRef = useRef(null);
166
+ const mapRef = useRef(null);
167
+ const tileLayerRef = useRef(null);
168
+ const overlayGroupRef = useRef(null);
169
+ const controlsRef = useRef([]);
170
+ /** Separate layer group so pin updates don't clear satellite/station layers */
171
+ const pinsGroupRef = useRef(null);
172
+ const [ready, setReady] = useState(false);
173
+ const [layersPanelOpen, setLayersPanelOpen] = useState(false);
174
+ const layersPanelRef = useRef(null);
175
+ useEffect(() => {
176
+ if (!layersPanelOpen) return;
177
+ const handleClickOutside = (e) => {
178
+ if (layersPanelRef.current && !layersPanelRef.current.contains(e.target)) setLayersPanelOpen(false);
179
+ };
180
+ document.addEventListener("mousedown", handleClickOutside);
181
+ return () => document.removeEventListener("mousedown", handleClickOutside);
182
+ }, [layersPanelOpen]);
183
+ const TILE_STORAGE_KEY = "zendir-map-tile-style";
184
+ const isExplicitTileUrl = tileUrl !== void 0;
185
+ const [tileStyle, setTileStyle] = useState(() => {
186
+ if (isExplicitTileUrl) return "dark";
187
+ try {
188
+ const saved = localStorage.getItem(TILE_STORAGE_KEY);
189
+ if (saved === "dark" || saved === "satellite") return saved;
190
+ } catch {}
191
+ return "dark";
192
+ });
193
+ const handleTileStyleChange = useCallback((style) => {
194
+ setTileStyle(style);
195
+ try {
196
+ localStorage.setItem(TILE_STORAGE_KEY, style);
197
+ } catch {}
198
+ }, []);
199
+ const effectiveTileUrl = isExplicitTileUrl ? tileUrl : TILE_PRESETS[tileStyle];
200
+ const [internalTerminator, setInternalTerminator] = useState(showTerminator);
201
+ const [internalGrid, setInternalGrid] = useState(showGrid);
202
+ useEffect(() => {
203
+ setInternalTerminator(showTerminator);
204
+ }, [showTerminator]);
205
+ useEffect(() => {
206
+ setInternalGrid(showGrid);
207
+ }, [showGrid]);
208
+ const [customLayerState, setCustomLayerState] = useState(() => {
209
+ const state = {};
210
+ for (const l of customLayers ?? []) state[l.id] = l.defaultEnabled ?? true;
211
+ return state;
212
+ });
213
+ useEffect(() => {
214
+ setCustomLayerState((prev) => {
215
+ const next = { ...prev };
216
+ for (const l of customLayers ?? []) if (!(l.id in next)) next[l.id] = l.defaultEnabled ?? true;
217
+ return next;
218
+ });
219
+ }, [customLayers]);
220
+ const handleLayerToggle = useCallback((layerId, enabled) => {
221
+ if (layerId === "terminator") setInternalTerminator(enabled);
222
+ else if (layerId === "grid") setInternalGrid(enabled);
223
+ else setCustomLayerState((prev) => ({
224
+ ...prev,
225
+ [layerId]: enabled
226
+ }));
227
+ onLayerChange?.(layerId, enabled);
228
+ }, [onLayerChange]);
229
+ const clearLayers = useCallback(() => {
230
+ const map = mapRef.current;
231
+ if (!map) return;
232
+ overlayGroupRef.current?.clearLayers();
233
+ controlsRef.current.forEach((ctrl) => map.removeControl(ctrl));
234
+ controlsRef.current = [];
235
+ }, []);
236
+ const addLayer = useCallback((layer) => {
237
+ overlayGroupRef.current?.addLayer(layer);
238
+ }, []);
239
+ useEffect(() => {
240
+ const el = containerRef.current;
241
+ if (!el) return;
242
+ const center = defaultCenter ?? [20, 0];
243
+ const zoom = defaultZoom ?? 2;
244
+ const map = L.map(el, {
245
+ center,
246
+ zoom,
247
+ zoomControl: false,
248
+ scrollWheelZoom: true,
249
+ doubleClickZoom: true,
250
+ touchZoom: true,
251
+ boxZoom: true,
252
+ keyboard: true,
253
+ dragging: true,
254
+ attributionControl: true,
255
+ maxBounds: [[-90, -540], [90, 540]],
256
+ maxBoundsViscosity: 1
257
+ });
258
+ map.attributionControl.setPrefix("");
259
+ const overlayGroup = L.layerGroup();
260
+ overlayGroup.addTo(map);
261
+ overlayGroupRef.current = overlayGroup;
262
+ const pinsGroup = L.layerGroup();
263
+ pinsGroup.addTo(map);
264
+ pinsGroupRef.current = pinsGroup;
265
+ mapRef.current = map;
266
+ setReady(true);
267
+ const onSize = () => {
268
+ map.invalidateSize({ animate: false });
269
+ };
270
+ const raf = requestAnimationFrame(onSize);
271
+ const t1 = setTimeout(onSize, 150);
272
+ const t2 = setTimeout(onSize, 500);
273
+ return () => {
274
+ clearTimeout(t1);
275
+ clearTimeout(t2);
276
+ cancelAnimationFrame(raf);
277
+ clearLayers();
278
+ pinsGroupRef.current?.clearLayers();
279
+ pinsGroupRef.current = null;
280
+ tileLayerRef.current = null;
281
+ map.remove();
282
+ mapRef.current = null;
283
+ overlayGroupRef.current = null;
284
+ };
285
+ }, [clearLayers]);
286
+ useEffect(() => {
287
+ const map = mapRef.current;
288
+ if (!map || !ready) return;
289
+ if (tileLayerRef.current) {
290
+ tileLayerRef.current.remove();
291
+ tileLayerRef.current = null;
292
+ }
293
+ const isCartoTiles = effectiveTileUrl.includes("cartocdn");
294
+ const basemapAttribution = getBasemapAttribution(effectiveTileUrl);
295
+ const tile = L.tileLayer(effectiveTileUrl, {
296
+ maxZoom: 19,
297
+ subdomains: isCartoTiles ? "abcd" : "abc",
298
+ crossOrigin: true,
299
+ attribution: basemapAttribution || void 0
300
+ });
301
+ tile.addTo(map);
302
+ tile.bringToBack();
303
+ tileLayerRef.current = tile;
304
+ let hasSwitchedToFallback = false;
305
+ const onTileError = () => {
306
+ if (hasSwitchedToFallback) return;
307
+ hasSwitchedToFallback = true;
308
+ tile.off("tileerror", onTileError);
309
+ tile.remove();
310
+ const fallback = L.tileLayer(FALLBACK_TILE, {
311
+ maxZoom: 19,
312
+ subdomains: "abc",
313
+ crossOrigin: true,
314
+ attribution: OSM_ATTRIBUTION
315
+ });
316
+ fallback.addTo(map);
317
+ fallback.bringToBack();
318
+ tileLayerRef.current = fallback;
319
+ };
320
+ tile.on("tileerror", onTileError);
321
+ }, [effectiveTileUrl, ready]);
322
+ useEffect(() => {
323
+ if (!ready || !mapRef.current) return;
324
+ const map = mapRef.current;
325
+ clearLayers();
326
+ if (nightTileUrl && internalTerminator) {
327
+ const nightTile = L.tileLayer(nightTileUrl, {
328
+ maxZoom: 19,
329
+ crossOrigin: true,
330
+ opacity: .7,
331
+ className: "zendir-night-tiles"
332
+ });
333
+ addLayer(nightTile);
334
+ }
335
+ if (internalTerminator) {
336
+ const now = terminatorTime ?? /* @__PURE__ */ new Date();
337
+ const isSatelliteTile = tileStyle === "satellite";
338
+ const BAND_STEP = 1.5;
339
+ const MAX_DEP = 30;
340
+ const NIGHT_OPACITY = isSatelliteTile ? .75 : .38;
341
+ const NIGHT_COLOR = isSatelliteTile ? "#030810" : "#2a3a52";
342
+ const bandSteps = [];
343
+ for (let d = 0; d <= MAX_DEP; d += BAND_STEP) {
344
+ const t = Math.min(d / 18, 1);
345
+ const opacity = Math.pow(t, 1.6) * (NIGHT_OPACITY * .85) + (d > 18 ? (d - 18) / 12 * (NIGHT_OPACITY * .15) : 0);
346
+ bandSteps.push({
347
+ depression: d,
348
+ opacity: Math.min(opacity, NIGHT_OPACITY),
349
+ color: NIGHT_COLOR
350
+ });
351
+ }
352
+ bandSteps.push({
353
+ depression: 90,
354
+ opacity: NIGHT_OPACITY,
355
+ color: NIGHT_COLOR
356
+ });
357
+ let prevOpacity = 0;
358
+ for (const b of bandSteps) {
359
+ const terminator = calculateTerminatorContinuous(now, Math.min(b.depression, 89));
360
+ const bandOpacity = b.opacity - prevOpacity;
361
+ if (bandOpacity < .002 || terminator.sunset.length === 0) {
362
+ prevOpacity = b.opacity;
363
+ continue;
364
+ }
365
+ const poly = buildNightPolygon(terminator);
366
+ if (poly.length < 3) continue;
367
+ [
368
+ 0,
369
+ 360,
370
+ -360
371
+ ].forEach((offset) => {
372
+ const shifted = poly.map(([lat, lon]) => [lat, lon + offset]);
373
+ addLayer(L.polygon(shifted, {
374
+ color: "transparent",
375
+ fillColor: b.color,
376
+ fillOpacity: bandOpacity,
377
+ interactive: false
378
+ }));
379
+ });
380
+ prevOpacity = b.opacity;
381
+ }
382
+ const terminatorEdge = calculateTerminatorContinuous(now, 0);
383
+ const glowColor = isSatelliteTile ? "#a0a0a0" : "#909090";
384
+ const glowOpacity = isSatelliteTile ? .15 : .08;
385
+ const coreColor = isSatelliteTile ? "#c0c0c0" : "#a8a8a8";
386
+ const coreOpacity = isSatelliteTile ? .45 : .3;
387
+ if (terminatorEdge.sunset.length > 2) {
388
+ const sunsetLine = terminatorEdge.sunset.map(([lat, lon]) => [lat, lon]);
389
+ const sunriseLine = terminatorEdge.sunrise.map(([lat, lon]) => [lat, lon]);
390
+ [
391
+ 0,
392
+ 360,
393
+ -360
394
+ ].forEach((offset) => {
395
+ addLayer(L.polyline(sunsetLine.map(([lat, lon]) => [lat, lon + offset]), {
396
+ color: glowColor,
397
+ weight: 3,
398
+ opacity: glowOpacity,
399
+ interactive: false,
400
+ smoothFactor: 1.5
401
+ }));
402
+ addLayer(L.polyline(sunriseLine.map(([lat, lon]) => [lat, lon + offset]), {
403
+ color: glowColor,
404
+ weight: 3,
405
+ opacity: glowOpacity,
406
+ interactive: false,
407
+ smoothFactor: 1.5
408
+ }));
409
+ addLayer(L.polyline(sunsetLine.map(([lat, lon]) => [lat, lon + offset]), {
410
+ color: coreColor,
411
+ weight: 1,
412
+ opacity: coreOpacity,
413
+ interactive: false,
414
+ smoothFactor: 1.5
415
+ }));
416
+ addLayer(L.polyline(sunriseLine.map(([lat, lon]) => [lat, lon + offset]), {
417
+ color: coreColor,
418
+ weight: 1,
419
+ opacity: coreOpacity,
420
+ interactive: false,
421
+ smoothFactor: 1.5
422
+ }));
423
+ });
424
+ }
425
+ }
426
+ if (lightSources && lightSources.length > 0 && internalTerminator) {
427
+ const now = terminatorTime ?? /* @__PURE__ */ new Date();
428
+ const dayOfYear = Math.floor((now.getTime() - Date.UTC(now.getUTCFullYear(), 0, 0)) / 864e5);
429
+ const decRad = -23.44 * Math.cos(2 * Math.PI / 365 * (dayOfYear + 10)) * Math.PI / 180;
430
+ const lsSubSolarLon = -(now.getUTCHours() + now.getUTCMinutes() / 60 - 12) * 15;
431
+ for (const light of lightSources) {
432
+ const latRad = light.latitude * Math.PI / 180;
433
+ const sinAlt = Math.sin(latRad) * Math.sin(decRad) + Math.cos(latRad) * Math.cos(decRad) * Math.cos((light.longitude - lsSubSolarLon) * Math.PI / 180);
434
+ const solarAltDeg = Math.asin(sinAlt) * 180 / Math.PI;
435
+ let nightFactor;
436
+ if (solarAltDeg >= 0) nightFactor = 0;
437
+ else if (solarAltDeg <= -18) nightFactor = 1;
438
+ else nightFactor = Math.min(1, -solarAltDeg / 18);
439
+ if (nightFactor < .01) continue;
440
+ const r = light.radius ?? 4;
441
+ const intensity = light.intensity ?? .8;
442
+ const color = light.color ?? "#ffeedd";
443
+ const alpha = intensity * nightFactor;
444
+ const glowMarker = L.circleMarker([light.latitude, light.longitude], {
445
+ radius: r * 2,
446
+ fillColor: color,
447
+ fillOpacity: alpha * .4,
448
+ color,
449
+ weight: 0,
450
+ interactive: !!light.label
451
+ });
452
+ addLayer(glowMarker);
453
+ const coreMarker = L.circleMarker([light.latitude, light.longitude], {
454
+ radius: Math.max(1, r * .5),
455
+ fillColor: color,
456
+ fillOpacity: Math.min(1, alpha * 1.2),
457
+ color: "transparent",
458
+ weight: 0,
459
+ interactive: false
460
+ });
461
+ addLayer(coreMarker);
462
+ if (light.label) glowMarker.bindTooltip(light.label, {
463
+ permanent: false,
464
+ direction: "top",
465
+ className: "zendir-leaflet-tooltip"
466
+ });
467
+ }
468
+ }
469
+ if (internalGrid) {
470
+ const gridStyle = {
471
+ color: "rgba(157, 112, 255, 0.12)",
472
+ weight: .5,
473
+ interactive: false
474
+ };
475
+ [
476
+ -360,
477
+ 0,
478
+ 360
479
+ ].forEach((offset) => {
480
+ for (let lon = -180; lon <= 180; lon += 30) {
481
+ const l = lon + offset;
482
+ addLayer(L.polyline([[90, l], [-90, l]], gridStyle));
483
+ }
484
+ for (let lat = -90; lat <= 90; lat += 30) addLayer(L.polyline([[lat, -180 + offset], [lat, 180 + offset]], gridStyle));
485
+ });
486
+ }
487
+ if (showEquator) [
488
+ -360,
489
+ 0,
490
+ 360
491
+ ].forEach((offset) => {
492
+ addLayer(L.polyline([[0, -180 + offset], [0, 180 + offset]], {
493
+ color: "rgba(88, 166, 255, 0.25)",
494
+ weight: 1,
495
+ dashArray: "6, 4",
496
+ interactive: false
497
+ }));
498
+ });
499
+ groundStations.forEach((gs) => {
500
+ const status = ("status" in gs ? gs.status : void 0) ?? "standby";
501
+ const statusColor = STATUS_COLOR_MAP[status] || STATUS_COLOR_MAP.standby;
502
+ const stationType = ("type" in gs ? gs.type : void 0) ?? "dish";
503
+ const radius = "coverageRadius" in gs ? gs.coverageRadius : void 0;
504
+ const showCoverage = "showCoverage" in gs ? gs.showCoverage : false;
505
+ if (radius != null && radius > 0 && showCoverage) {
506
+ const ring = geodesicCirclePointsContinuous(gs.latitude, gs.longitude, radius, 64);
507
+ [
508
+ 0,
509
+ 360,
510
+ -360
511
+ ].forEach((offset) => {
512
+ addLayer(L.polygon(ring.map(([lat, lon]) => [lat, lon + offset]), {
513
+ color: `${statusColor}55`,
514
+ fillColor: statusColor,
515
+ fillOpacity: .1,
516
+ weight: 1,
517
+ dashArray: "4, 3",
518
+ interactive: false
519
+ }));
520
+ });
521
+ }
522
+ const gsIcon = createStationDivIcon(statusColor, stationType, status);
523
+ const marker = L.marker([gs.latitude, gs.longitude], { icon: gsIcon });
524
+ addLayer(marker);
525
+ const statusLabel = status !== "standby" ? ` · ${status}` : "";
526
+ marker.bindTooltip(`<strong>${gs.name}</strong>${statusLabel}${"network" in gs && gs.network ? `<br/><span style="opacity:0.7">${gs.network}</span>` : ""}`, {
527
+ permanent: false,
528
+ direction: "auto",
529
+ className: "zendir-leaflet-tooltip"
530
+ });
531
+ marker.on("click", () => {
532
+ onStationClick?.("id" in gs ? String(gs.id) : gs.name);
533
+ });
534
+ });
535
+ allSatellites.forEach((sat, satIdx) => {
536
+ const track = sat.groundTrack;
537
+ if (!track || track.length === 0) return;
538
+ const color = sat.color || DEFAULT_TRACK_COLORS[satIdx % DEFAULT_TRACK_COLORS.length];
539
+ const futureIdx = sat.futureTrackIndex ?? track.length;
540
+ segmentsWithWorldCopies(splitPolylineAtAntimeridian(track.slice(0, futureIdx).map((p) => [p.latitude, p.longitude]))).forEach((seg) => {
541
+ if (seg.length >= 2) addLayer(L.polyline(seg, {
542
+ color,
543
+ weight: 2.5,
544
+ opacity: 1
545
+ }));
546
+ });
547
+ if (futureIdx < track.length) segmentsWithWorldCopies(splitPolylineAtAntimeridian(track.slice(futureIdx).map((p) => [p.latitude, p.longitude]))).forEach((seg) => {
548
+ if (seg.length >= 2) addLayer(L.polyline(seg, {
549
+ color,
550
+ weight: 1.5,
551
+ opacity: .5,
552
+ dashArray: "6, 4"
553
+ }));
554
+ });
555
+ if (sat.accessMask && sat.accessMask.some(Boolean)) {
556
+ let segment = [];
557
+ for (let i = 0; i < track.length; i++) if (sat.accessMask[i]) segment.push([track[i].latitude, track[i].longitude]);
558
+ else if (segment.length >= 2) {
559
+ segmentsWithWorldCopies(splitPolylineAtAntimeridian(segment)).forEach((seg) => {
560
+ if (seg.length >= 2) addLayer(L.polyline(seg, {
561
+ color: STATUS_COLOR_MAP.normal,
562
+ weight: 4,
563
+ opacity: .9
564
+ }));
565
+ });
566
+ segment = [];
567
+ } else segment = [];
568
+ if (segment.length >= 2) segmentsWithWorldCopies(splitPolylineAtAntimeridian(segment)).forEach((seg) => {
569
+ if (seg.length >= 2) addLayer(L.polyline(seg, {
570
+ color: STATUS_COLOR_MAP.normal,
571
+ weight: 4,
572
+ opacity: .9
573
+ }));
574
+ });
575
+ }
576
+ if (sat.passMarkers && sat.passMarkers.length > 0) sat.passMarkers.forEach((pm) => {
577
+ const pmColor = pm.type === "aos" ? "#56f000" : "#ff3838";
578
+ const pmMarker = L.circleMarker([pm.latitude, pm.longitude], {
579
+ radius: 5,
580
+ fillColor: pmColor,
581
+ color: `${pmColor}cc`,
582
+ weight: 1.5,
583
+ fillOpacity: .9
584
+ });
585
+ addLayer(pmMarker);
586
+ pmMarker.bindTooltip(`${pm.type.toUpperCase()}${pm.label ? ` – ${pm.label}` : ""}`, {
587
+ permanent: false,
588
+ direction: "auto",
589
+ className: "zendir-leaflet-tooltip"
590
+ });
591
+ });
592
+ if (sat.showFootprint && sat.footprintRadius) {
593
+ const last = track[futureIdx > 0 ? Math.min(futureIdx - 1, track.length - 1) : track.length - 1];
594
+ const ring = geodesicCirclePointsContinuous(last.latitude, last.longitude, sat.footprintRadius, 64);
595
+ [
596
+ 0,
597
+ 360,
598
+ -360
599
+ ].forEach((offset) => {
600
+ addLayer(L.polygon(ring.map(([lat, lon]) => [lat, lon + offset]), {
601
+ color: `${color}50`,
602
+ fillColor: color,
603
+ fillOpacity: .03,
604
+ weight: 1,
605
+ dashArray: "4, 4",
606
+ interactive: false
607
+ }));
608
+ });
609
+ }
610
+ const current = track[futureIdx > 0 ? Math.min(futureIdx - 1, track.length - 1) : track.length - 1];
611
+ const statusColor = STATUS_COLOR_MAP[sat.status || "normal"] || STATUS_COLOR_MAP.normal;
612
+ const satIcon = createSatDivIcon(statusColor, color);
613
+ const satMarker = L.marker([current.latitude, current.longitude], { icon: satIcon });
614
+ addLayer(satMarker);
615
+ satMarker.bindTooltip(`<strong style="color:${color}">${sat.name}</strong><br/>Lat ${current.latitude.toFixed(3)}° &nbsp; Lon ${current.longitude.toFixed(3)}°` + (current.altitude != null ? `<br/>Alt ${current.altitude.toFixed(1)} km` : "") + (sat.status ? `<br/><span style="color:${statusColor}">${sat.status.toUpperCase()}</span>` : ""), {
616
+ permanent: false,
617
+ direction: "auto",
618
+ className: "zendir-leaflet-tooltip"
619
+ });
620
+ satMarker.on("click", () => onSatelliteClick?.(sat.id));
621
+ });
622
+ if (showLegend && (allSatellites.length > 0 || groundStations.length > 0)) {
623
+ const legend = new (L.Control.extend({ onAdd() {
624
+ const div = L.DomUtil.create("div", "leaflet-legend-zendir");
625
+ div.style.cssText = `
626
+ padding: 8px 12px; background: ${tokens.colors.background.surface}; border: 1px solid ${tokens.colors.border.default};
627
+ border-radius: 8px; color: ${tokens.colors.text.primary}; font-size: 11px; font-family: Roboto, sans-serif;
612
628
  `;
613
- allSatellites.forEach((s, i) => {
614
- const c = s.color || DEFAULT_TRACK_COLORS[i % DEFAULT_TRACK_COLORS.length];
615
- const row = document.createElement("div");
616
- row.style.cssText = "display: flex; align-items: center; gap: 6px; margin: 2px 0;";
617
- const swatch = document.createElement("span");
618
- swatch.style.cssText = `width:8px;height:8px;border-radius:50%;background:${c};`;
619
- const label = document.createElement("span");
620
- label.textContent = s.name;
621
- row.appendChild(swatch);
622
- row.appendChild(label);
623
- div.appendChild(row);
624
- });
625
- if (groundStations.length > 0) {
626
- const row = document.createElement("div");
627
- row.style.cssText = "display: flex; align-items: center; gap: 6px; margin: 2px 0; margin-top: 6px;";
628
- const swatch = document.createElement("span");
629
- swatch.style.cssText = "width:8px;height:8px;border-radius:50%;background:#2dccff;";
630
- const label = document.createElement("span");
631
- label.textContent = `${groundStations.length} Ground Station${groundStations.length > 1 ? "s" : ""}`;
632
- row.appendChild(swatch);
633
- row.appendChild(label);
634
- div.appendChild(row);
635
- }
636
- return div;
637
- }
638
- });
639
- const legend = new Legend({ position: "topleft" });
640
- legend.addTo(map);
641
- controlsRef.current.push(legend);
642
- }
643
- }, [
644
- ready,
645
- allSatellites,
646
- groundStations,
647
- internalTerminator,
648
- terminatorTime,
649
- nightTileUrl,
650
- lightSources,
651
- internalGrid,
652
- showEquator,
653
- showLegend,
654
- tileStyle,
655
- tokens.colors.text.secondary,
656
- addLayer,
657
- clearLayers,
658
- onSatelliteClick,
659
- onStationClick
660
- ]);
661
- useEffect(() => {
662
- if (!ready || !mapRef.current) return;
663
- const group = pinsGroupRef.current;
664
- if (!group) return;
665
- group.clearLayers();
666
- if (!pins || pins.length === 0) return;
667
- const accentColor = tokens.colors.accent.primary;
668
- pins.forEach((pin) => {
669
- const pinColor = pin.color || accentColor;
670
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="36" viewBox="0 0 24 36">
629
+ allSatellites.forEach((s, i) => {
630
+ const c = s.color || DEFAULT_TRACK_COLORS[i % DEFAULT_TRACK_COLORS.length];
631
+ const row = document.createElement("div");
632
+ row.style.cssText = "display: flex; align-items: center; gap: 6px; margin: 2px 0;";
633
+ const swatch = document.createElement("span");
634
+ swatch.style.cssText = `width:8px;height:8px;border-radius:50%;background:${c};`;
635
+ const label = document.createElement("span");
636
+ label.textContent = s.name;
637
+ row.appendChild(swatch);
638
+ row.appendChild(label);
639
+ div.appendChild(row);
640
+ });
641
+ if (groundStations.length > 0) {
642
+ const row = document.createElement("div");
643
+ row.style.cssText = "display: flex; align-items: center; gap: 6px; margin: 2px 0; margin-top: 6px;";
644
+ const swatch = document.createElement("span");
645
+ swatch.style.cssText = `width:8px;height:8px;border-radius:50%;background:${STATUS_COLOR_MAP.standby};`;
646
+ const label = document.createElement("span");
647
+ label.textContent = `${groundStations.length} Ground Station${groundStations.length > 1 ? "s" : ""}`;
648
+ row.appendChild(swatch);
649
+ row.appendChild(label);
650
+ div.appendChild(row);
651
+ }
652
+ return div;
653
+ } }))({ position: "topleft" });
654
+ legend.addTo(map);
655
+ controlsRef.current.push(legend);
656
+ }
657
+ }, [
658
+ ready,
659
+ allSatellites,
660
+ groundStations,
661
+ internalTerminator,
662
+ terminatorTime,
663
+ nightTileUrl,
664
+ lightSources,
665
+ internalGrid,
666
+ showEquator,
667
+ showLegend,
668
+ tileStyle,
669
+ tokens.colors.text.primary,
670
+ tokens.colors.background.surface,
671
+ tokens.colors.border.default,
672
+ addLayer,
673
+ clearLayers,
674
+ onSatelliteClick,
675
+ onStationClick
676
+ ]);
677
+ useEffect(() => {
678
+ if (!ready || !mapRef.current) return;
679
+ const group = pinsGroupRef.current;
680
+ if (!group) return;
681
+ group.clearLayers();
682
+ if (!pins || pins.length === 0) return;
683
+ const accentColor = tokens.colors.accent.primary;
684
+ pins.forEach((pin) => {
685
+ const pinColor = pin.color || accentColor;
686
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="36" viewBox="0 0 24 36">
671
687
  <defs>
672
688
  <filter id="pin-shadow-${pin.id}" x="-30%" y="-10%" width="160%" height="140%">
673
689
  <feDropShadow dx="0" dy="2" stdDeviation="2" flood-color="#000" flood-opacity="0.35"/>
@@ -679,382 +695,444 @@ function GroundTrackMapLeaflet({
679
695
  <circle cx="12" cy="12" r="4" fill="#0d1323" stroke="rgba(255,255,255,0.7)" stroke-width="0.8"/>
680
696
  </g>
681
697
  </svg>`;
682
- const icon = L.divIcon({
683
- html: svg,
684
- className: "zendir-pin-icon",
685
- iconSize: [24, 36],
686
- iconAnchor: [12, 36],
687
- tooltipAnchor: [0, -36]
688
- });
689
- const marker = L.marker([pin.latitude, pin.longitude], {
690
- icon,
691
- draggable: pinsEditable,
692
- title: pin.label ?? ""
693
- });
694
- const tooltipLines = [
695
- pin.label ? `<strong>${pin.label}</strong>` : "",
696
- pin.description ? `<div style="opacity:0.7;font-size:11px">${pin.description}</div>` : "",
697
- `<div style="font-size:10px;opacity:0.5;font-family:monospace">${pin.latitude.toFixed(4)}°, ${pin.longitude.toFixed(4)}°</div>`,
698
- pin.createdBy ? `<div style="font-size:10px;opacity:0.4;margin-top:2px">by ${pin.createdBy}</div>` : ""
699
- ].filter(Boolean).join("");
700
- marker.bindTooltip(tooltipLines, {
701
- className: "zendir-pin-tooltip",
702
- direction: "top",
703
- offset: [0, -4]
704
- });
705
- if (pinsEditable) {
706
- marker.on("dragend", () => {
707
- const latlng = marker.getLatLng();
708
- onPinUpdate == null ? void 0 : onPinUpdate({ ...pin, latitude: latlng.lat, longitude: latlng.lng });
709
- });
710
- marker.on("contextmenu", (e) => {
711
- L.DomEvent.stopPropagation(e);
712
- onPinRemove == null ? void 0 : onPinRemove(pin.id);
713
- });
714
- }
715
- group.addLayer(marker);
716
- });
717
- }, [ready, pins, pinsEditable, onPinUpdate, onPinRemove, tokens.colors.accent.primary]);
718
- useEffect(() => {
719
- if (!ready || !mapRef.current || !pinsEditable || !onPinAdd) return;
720
- const map = mapRef.current;
721
- const handleClick = (e) => {
722
- var _a, _b, _c;
723
- if ((_c = (_b = (_a = e.originalEvent) == null ? void 0 : _a.target) == null ? void 0 : _b.closest) == null ? void 0 : _c.call(_b, ".leaflet-marker-icon")) return;
724
- onPinAdd({
725
- latitude: e.latlng.lat,
726
- longitude: e.latlng.lng,
727
- label: "",
728
- color: tokens.colors.accent.primary
729
- });
730
- };
731
- map.on("click", handleClick);
732
- return () => {
733
- map.off("click", handleClick);
734
- };
735
- }, [ready, pinsEditable, onPinAdd, tokens.colors.accent.primary]);
736
- const handleRecenter = useCallback(() => {
737
- const map = mapRef.current;
738
- if (!map) return;
739
- const points = [];
740
- allSatellites.forEach((s) => {
741
- s.groundTrack.forEach((p) => points.push([p.latitude, p.longitude]));
742
- });
743
- groundStations.forEach((gs) => points.push([gs.latitude, gs.longitude]));
744
- if (points.length > 0) {
745
- const bounds = L.latLngBounds(points);
746
- map.fitBounds(bounds, { padding: [40, 40], maxZoom: 8 });
747
- } else {
748
- map.setView(defaultCenter, defaultZoom);
749
- }
750
- }, [allSatellites, groundStations, defaultCenter, defaultZoom]);
751
- const handleZoomIn = useCallback(() => {
752
- var _a;
753
- (_a = mapRef.current) == null ? void 0 : _a.zoomIn();
754
- }, []);
755
- const handleZoomOut = useCallback(() => {
756
- var _a;
757
- (_a = mapRef.current) == null ? void 0 : _a.zoomOut();
758
- }, []);
759
- const resolvedMinHeight = minHeight || (typeof height === "number" ? `${height}px` : "400px");
760
- const isEmpty = allSatellites.length === 0 && groundStations.length === 0 && (!pins || pins.length === 0);
761
- const ctrlBtnBase = {
762
- background: "rgba(20, 24, 38, 0.92)",
763
- backdropFilter: "blur(8px)",
764
- WebkitBackdropFilter: "blur(8px)",
765
- border: "1px solid rgba(120, 100, 180, 0.18)",
766
- color: "#c8c0d8",
767
- cursor: "pointer",
768
- display: "flex",
769
- alignItems: "center",
770
- justifyContent: "center",
771
- transition: "background 0.15s, border-color 0.15s"
772
- };
773
- const ctrlHover = (e) => {
774
- e.currentTarget.style.background = "rgba(30, 34, 52, 0.95)";
775
- e.currentTarget.style.borderColor = "rgba(157, 112, 255, 0.4)";
776
- };
777
- const ctrlLeave = (e) => {
778
- e.currentTarget.style.background = "rgba(20, 24, 38, 0.92)";
779
- e.currentTarget.style.borderColor = "rgba(120, 100, 180, 0.18)";
780
- };
781
- const overlayItems = [];
782
- overlayItems.push({ id: "terminator", label: "Day / Night", enabled: internalTerminator });
783
- overlayItems.push({ id: "grid", label: "Grid Lines", enabled: internalGrid });
784
- for (const l of customLayers ?? []) {
785
- overlayItems.push({ id: l.id, label: l.label, enabled: customLayerState[l.id] ?? (l.defaultEnabled ?? true) });
786
- }
787
- return /* @__PURE__ */ jsxs(
788
- "div",
789
- {
790
- className: `zendir-ground-track-map zendir-ground-track-map-leaflet${pinsEditable ? " zendir-pins-editable" : ""} ${className}`,
791
- "data-map-backend": "leaflet",
792
- style: {
793
- width,
794
- height,
795
- minHeight: resolvedMinHeight,
796
- backgroundColor: tokens.colors.background.base,
797
- borderRadius: 8,
798
- overflow: "hidden",
799
- position: "relative"
800
- },
801
- children: [
802
- /* @__PURE__ */ jsx(
803
- "div",
804
- {
805
- ref: containerRef,
806
- style: { width: "100%", height: "100%", minHeight: resolvedMinHeight }
807
- }
808
- ),
809
- isEmpty && /* @__PURE__ */ jsx(
810
- "div",
811
- {
812
- style: {
813
- position: "absolute",
814
- left: "50%",
815
- top: "50%",
816
- transform: "translate(-50%, -50%)",
817
- color: tokens.colors.text.secondary,
818
- fontSize: 14,
819
- pointerEvents: "none"
820
- },
821
- children: emptyMessage
822
- }
823
- ),
824
- /* @__PURE__ */ jsxs(
825
- "div",
826
- {
827
- style: {
828
- position: "absolute",
829
- top: 10,
830
- right: 10,
831
- zIndex: 1e3,
832
- display: "flex",
833
- flexDirection: "row",
834
- gap: 4,
835
- alignItems: "flex-start",
836
- pointerEvents: "none"
837
- },
838
- children: [
839
- showRecenterButton && /* @__PURE__ */ jsx(
840
- "button",
841
- {
842
- type: "button",
843
- onClick: handleRecenter,
844
- title: "Recenter map to fit all assets and ground stations",
845
- "aria-label": "Recenter map",
846
- style: {
847
- ...ctrlBtnBase,
848
- borderRadius: 4,
849
- padding: "6px 8px",
850
- gap: 4,
851
- fontSize: 10,
852
- fontWeight: 500,
853
- letterSpacing: "0.03em",
854
- pointerEvents: "auto"
855
- },
856
- onMouseEnter: ctrlHover,
857
- onMouseLeave: ctrlLeave,
858
- children: /* @__PURE__ */ jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", children: [
859
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
860
- /* @__PURE__ */ jsx("path", { d: "M12 2v4M12 18v4M2 12h4M18 12h4" })
861
- ] })
862
- }
863
- ),
864
- /* @__PURE__ */ jsxs("div", { ref: layersPanelRef, style: { position: "relative", pointerEvents: "auto" }, children: [
865
- /* @__PURE__ */ jsx(
866
- "button",
867
- {
868
- type: "button",
869
- onClick: () => setLayersPanelOpen((o) => !o),
870
- title: "Map layers",
871
- "aria-label": "Toggle map layers panel",
872
- "aria-expanded": layersPanelOpen,
873
- style: {
874
- ...ctrlBtnBase,
875
- borderRadius: 4,
876
- padding: "6px 8px",
877
- pointerEvents: "auto",
878
- borderColor: layersPanelOpen ? "rgba(157, 112, 255, 0.4)" : void 0,
879
- background: layersPanelOpen ? "rgba(30, 34, 52, 0.95)" : ctrlBtnBase.background
880
- },
881
- onMouseEnter: ctrlHover,
882
- onMouseLeave: ctrlLeave,
883
- children: /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinejoin: "round", children: [
884
- /* @__PURE__ */ jsx("path", { d: "M12 2L2 7l10 5 10-5-10-5z" }),
885
- /* @__PURE__ */ jsx("path", { d: "M2 17l10 5 10-5" }),
886
- /* @__PURE__ */ jsx("path", { d: "M2 12l10 5 10-5" })
887
- ] })
888
- }
889
- ),
890
- layersPanelOpen && /* @__PURE__ */ jsxs(
891
- "div",
892
- {
893
- style: {
894
- position: "absolute",
895
- top: "calc(100% + 6px)",
896
- right: 0,
897
- minWidth: 180,
898
- background: "rgba(16, 18, 30, 0.96)",
899
- backdropFilter: "blur(16px)",
900
- WebkitBackdropFilter: "blur(16px)",
901
- border: "1px solid rgba(120, 100, 180, 0.18)",
902
- borderRadius: 6,
903
- padding: "8px 0",
904
- boxShadow: "0 8px 32px rgba(0,0,0,0.5)"
905
- },
906
- children: [
907
- !isExplicitTileUrl && /* @__PURE__ */ jsxs(Fragment, { children: [
908
- /* @__PURE__ */ jsx("div", { style: {
909
- padding: "4px 12px 6px",
910
- fontSize: 9,
911
- fontWeight: 600,
912
- letterSpacing: "0.08em",
913
- textTransform: "uppercase",
914
- color: "#7a748e"
915
- }, children: "Base Map" }),
916
- /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 2, padding: "0 8px 8px" }, children: ["dark", "satellite"].map((style) => /* @__PURE__ */ jsxs(
917
- "button",
918
- {
919
- type: "button",
920
- onClick: () => handleTileStyleChange(style),
921
- "aria-pressed": tileStyle === style,
922
- style: {
923
- flex: 1,
924
- background: tileStyle === style ? "rgba(157, 112, 255, 0.18)" : "rgba(255,255,255,0.03)",
925
- border: `1px solid ${tileStyle === style ? "rgba(157, 112, 255, 0.35)" : "rgba(120, 100, 180, 0.1)"}`,
926
- borderRadius: 4,
927
- color: tileStyle === style ? "#d0c4ee" : "#7a748e",
928
- cursor: "pointer",
929
- padding: "5px 6px",
930
- fontSize: 10,
931
- fontWeight: tileStyle === style ? 600 : 400,
932
- display: "flex",
933
- alignItems: "center",
934
- justifyContent: "center",
935
- gap: 4,
936
- transition: "all 0.15s ease"
937
- },
938
- children: [
939
- style === "dark" ? /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "currentColor", stroke: "none", children: /* @__PURE__ */ jsx("path", { d: "M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" }) }) : /* @__PURE__ */ jsxs("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
940
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
941
- /* @__PURE__ */ jsx("path", { d: "M2 12h20M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z" })
942
- ] }),
943
- style === "dark" ? "Dark" : "Satellite"
944
- ]
945
- },
946
- style
947
- )) }),
948
- /* @__PURE__ */ jsx("div", { style: { height: 1, background: "rgba(120, 100, 180, 0.1)", margin: "0 8px" } })
949
- ] }),
950
- /* @__PURE__ */ jsx("div", { style: {
951
- padding: `${isExplicitTileUrl ? "4px" : "8px"} 12px 4px`,
952
- fontSize: 9,
953
- fontWeight: 600,
954
- letterSpacing: "0.08em",
955
- textTransform: "uppercase",
956
- color: "#7a748e"
957
- }, children: "Overlays" }),
958
- overlayItems.map((item) => /* @__PURE__ */ jsxs(
959
- "button",
960
- {
961
- type: "button",
962
- onClick: () => handleLayerToggle(item.id, !item.enabled),
963
- style: {
964
- display: "flex",
965
- alignItems: "center",
966
- gap: 8,
967
- width: "100%",
968
- padding: "5px 12px",
969
- background: "transparent",
970
- border: "none",
971
- color: item.enabled ? "#c8c0d8" : "#5a5470",
972
- cursor: "pointer",
973
- fontSize: 11,
974
- textAlign: "left",
975
- transition: "background 0.1s"
976
- },
977
- onMouseEnter: (e) => {
978
- e.currentTarget.style.background = "rgba(157, 112, 255, 0.08)";
979
- },
980
- onMouseLeave: (e) => {
981
- e.currentTarget.style.background = "transparent";
982
- },
983
- children: [
984
- /* @__PURE__ */ jsx("span", { style: {
985
- width: 14,
986
- height: 14,
987
- borderRadius: 3,
988
- border: `1.5px solid ${item.enabled ? "rgba(157, 112, 255, 0.6)" : "rgba(120, 100, 180, 0.25)"}`,
989
- background: item.enabled ? "rgba(157, 112, 255, 0.22)" : "transparent",
990
- display: "flex",
991
- alignItems: "center",
992
- justifyContent: "center",
993
- flexShrink: 0,
994
- transition: "all 0.15s ease"
995
- }, children: item.enabled && /* @__PURE__ */ jsx("svg", { width: "9", height: "9", viewBox: "0 0 12 12", fill: "none", stroke: "#d0c4ee", strokeWidth: "2", children: /* @__PURE__ */ jsx("path", { d: "M2 6l3 3 5-5" }) }) }),
996
- item.label
997
- ]
998
- },
999
- item.id
1000
- ))
1001
- ]
1002
- }
1003
- )
1004
- ] }),
1005
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", pointerEvents: "auto" }, children: [
1006
- /* @__PURE__ */ jsx(
1007
- "button",
1008
- {
1009
- type: "button",
1010
- onClick: handleZoomIn,
1011
- title: "Zoom in",
1012
- "aria-label": "Zoom in",
1013
- style: {
1014
- ...ctrlBtnBase,
1015
- borderRadius: "4px 4px 0 0",
1016
- borderBottom: "none",
1017
- width: 30,
1018
- height: 28,
1019
- fontSize: 16,
1020
- fontWeight: 300,
1021
- pointerEvents: "auto"
1022
- },
1023
- onMouseEnter: ctrlHover,
1024
- onMouseLeave: ctrlLeave,
1025
- children: "+"
1026
- }
1027
- ),
1028
- /* @__PURE__ */ jsx(
1029
- "button",
1030
- {
1031
- type: "button",
1032
- onClick: handleZoomOut,
1033
- title: "Zoom out",
1034
- "aria-label": "Zoom out",
1035
- style: {
1036
- ...ctrlBtnBase,
1037
- borderRadius: "0 0 4px 4px",
1038
- width: 30,
1039
- height: 28,
1040
- fontSize: 16,
1041
- fontWeight: 300,
1042
- pointerEvents: "auto"
1043
- },
1044
- onMouseEnter: ctrlHover,
1045
- onMouseLeave: ctrlLeave,
1046
- children: "−"
1047
- }
1048
- )
1049
- ] })
1050
- ]
1051
- }
1052
- )
1053
- ]
1054
- }
1055
- );
698
+ const icon = L.divIcon({
699
+ html: svg,
700
+ className: "zendir-pin-icon",
701
+ iconSize: [24, 36],
702
+ iconAnchor: [12, 36],
703
+ tooltipAnchor: [0, -36]
704
+ });
705
+ const marker = L.marker([pin.latitude, pin.longitude], {
706
+ icon,
707
+ draggable: pinsEditable,
708
+ title: pin.label ?? ""
709
+ });
710
+ const tooltipLines = [
711
+ pin.label ? `<strong>${pin.label}</strong>` : "",
712
+ pin.description ? `<div style="opacity:0.7;font-size:11px">${pin.description}</div>` : "",
713
+ `<div style="font-size:10px;opacity:0.5;font-family:monospace">${pin.latitude.toFixed(4)}°, ${pin.longitude.toFixed(4)}°</div>`,
714
+ pin.createdBy ? `<div style="font-size:10px;opacity:0.4;margin-top:2px">by ${pin.createdBy}</div>` : ""
715
+ ].filter(Boolean).join("");
716
+ marker.bindTooltip(tooltipLines, {
717
+ className: "zendir-pin-tooltip",
718
+ direction: "top",
719
+ offset: [0, -4]
720
+ });
721
+ if (pinsEditable) {
722
+ marker.on("dragend", () => {
723
+ const latlng = marker.getLatLng();
724
+ onPinUpdate?.({
725
+ ...pin,
726
+ latitude: latlng.lat,
727
+ longitude: latlng.lng
728
+ });
729
+ });
730
+ marker.on("contextmenu", (e) => {
731
+ L.DomEvent.stopPropagation(e);
732
+ onPinRemove?.(pin.id);
733
+ });
734
+ }
735
+ group.addLayer(marker);
736
+ });
737
+ }, [
738
+ ready,
739
+ pins,
740
+ pinsEditable,
741
+ onPinUpdate,
742
+ onPinRemove,
743
+ tokens.colors.accent.primary
744
+ ]);
745
+ useEffect(() => {
746
+ if (!ready || !mapRef.current || !pinsEditable || !onPinAdd) return;
747
+ const map = mapRef.current;
748
+ const handleClick = (e) => {
749
+ if ((e.originalEvent?.target)?.closest?.(".leaflet-marker-icon")) return;
750
+ onPinAdd({
751
+ latitude: e.latlng.lat,
752
+ longitude: e.latlng.lng,
753
+ label: "",
754
+ color: tokens.colors.accent.primary
755
+ });
756
+ };
757
+ map.on("click", handleClick);
758
+ return () => {
759
+ map.off("click", handleClick);
760
+ };
761
+ }, [
762
+ ready,
763
+ pinsEditable,
764
+ onPinAdd,
765
+ tokens.colors.accent.primary
766
+ ]);
767
+ const handleRecenter = useCallback(() => {
768
+ const map = mapRef.current;
769
+ if (!map) return;
770
+ const points = [];
771
+ allSatellites.forEach((s) => {
772
+ s.groundTrack.forEach((p) => points.push([p.latitude, p.longitude]));
773
+ });
774
+ groundStations.forEach((gs) => points.push([gs.latitude, gs.longitude]));
775
+ if (points.length > 0) {
776
+ const bounds = L.latLngBounds(points);
777
+ map.fitBounds(bounds, {
778
+ padding: [40, 40],
779
+ maxZoom: 8
780
+ });
781
+ } else map.setView(defaultCenter, defaultZoom);
782
+ }, [
783
+ allSatellites,
784
+ groundStations,
785
+ defaultCenter,
786
+ defaultZoom
787
+ ]);
788
+ const handleZoomIn = useCallback(() => {
789
+ mapRef.current?.zoomIn();
790
+ }, []);
791
+ const handleZoomOut = useCallback(() => {
792
+ mapRef.current?.zoomOut();
793
+ }, []);
794
+ const resolvedMinHeight = minHeight || (typeof height === "number" ? `${height}px` : "400px");
795
+ const isEmpty = allSatellites.length === 0 && groundStations.length === 0 && (!pins || pins.length === 0);
796
+ const ctrlBtnBase = {
797
+ background: "rgba(20, 24, 38, 0.92)",
798
+ backdropFilter: "blur(8px)",
799
+ WebkitBackdropFilter: "blur(8px)",
800
+ border: "1px solid rgba(120, 100, 180, 0.18)",
801
+ color: "#c8c0d8",
802
+ cursor: "pointer",
803
+ display: "flex",
804
+ alignItems: "center",
805
+ justifyContent: "center",
806
+ transition: "background 0.15s, border-color 0.15s"
807
+ };
808
+ const ctrlHover = (e) => {
809
+ e.currentTarget.style.background = "rgba(30, 34, 52, 0.95)";
810
+ e.currentTarget.style.borderColor = "rgba(157, 112, 255, 0.4)";
811
+ };
812
+ const ctrlLeave = (e) => {
813
+ e.currentTarget.style.background = "rgba(20, 24, 38, 0.92)";
814
+ e.currentTarget.style.borderColor = "rgba(120, 100, 180, 0.18)";
815
+ };
816
+ const overlayItems = [];
817
+ overlayItems.push({
818
+ id: "terminator",
819
+ label: "Day / Night",
820
+ enabled: internalTerminator
821
+ });
822
+ overlayItems.push({
823
+ id: "grid",
824
+ label: "Grid Lines",
825
+ enabled: internalGrid
826
+ });
827
+ for (const l of customLayers ?? []) overlayItems.push({
828
+ id: l.id,
829
+ label: l.label,
830
+ enabled: customLayerState[l.id] ?? l.defaultEnabled ?? true
831
+ });
832
+ return /* @__PURE__ */ jsxs("div", {
833
+ className: `zendir-ground-track-map zendir-ground-track-map-leaflet${pinsEditable ? " zendir-pins-editable" : ""} ${className}`,
834
+ "data-map-backend": "leaflet",
835
+ style: {
836
+ width,
837
+ height,
838
+ minHeight: resolvedMinHeight,
839
+ backgroundColor: tokens.colors.background.base,
840
+ borderRadius: 8,
841
+ overflow: "hidden",
842
+ position: "relative"
843
+ },
844
+ children: [
845
+ /* @__PURE__ */ jsx("div", {
846
+ ref: containerRef,
847
+ style: {
848
+ width: "100%",
849
+ height: "100%",
850
+ minHeight: resolvedMinHeight
851
+ }
852
+ }),
853
+ isEmpty && /* @__PURE__ */ jsx("div", {
854
+ style: {
855
+ position: "absolute",
856
+ left: "50%",
857
+ top: "50%",
858
+ transform: "translate(-50%, -50%)",
859
+ color: tokens.colors.text.secondary,
860
+ fontSize: 14,
861
+ pointerEvents: "none"
862
+ },
863
+ children: emptyMessage
864
+ }),
865
+ /* @__PURE__ */ jsxs("div", {
866
+ style: {
867
+ position: "absolute",
868
+ top: 10,
869
+ right: 10,
870
+ zIndex: 1e3,
871
+ display: "flex",
872
+ flexDirection: "row",
873
+ gap: 4,
874
+ alignItems: "flex-start",
875
+ pointerEvents: "none"
876
+ },
877
+ children: [
878
+ showRecenterButton && /* @__PURE__ */ jsx("button", {
879
+ type: "button",
880
+ onClick: handleRecenter,
881
+ title: "Recenter map to fit all assets and ground stations",
882
+ "aria-label": "Recenter map",
883
+ style: {
884
+ ...ctrlBtnBase,
885
+ borderRadius: 4,
886
+ padding: "6px 8px",
887
+ gap: 4,
888
+ fontSize: 10,
889
+ fontWeight: 500,
890
+ letterSpacing: "0.03em",
891
+ pointerEvents: "auto"
892
+ },
893
+ onMouseEnter: ctrlHover,
894
+ onMouseLeave: ctrlLeave,
895
+ children: /* @__PURE__ */ jsxs("svg", {
896
+ width: "13",
897
+ height: "13",
898
+ viewBox: "0 0 24 24",
899
+ fill: "none",
900
+ stroke: "currentColor",
901
+ strokeWidth: "2.2",
902
+ children: [/* @__PURE__ */ jsx("circle", {
903
+ cx: "12",
904
+ cy: "12",
905
+ r: "3"
906
+ }), /* @__PURE__ */ jsx("path", { d: "M12 2v4M12 18v4M2 12h4M18 12h4" })]
907
+ })
908
+ }),
909
+ /* @__PURE__ */ jsxs("div", {
910
+ ref: layersPanelRef,
911
+ style: {
912
+ position: "relative",
913
+ pointerEvents: "auto"
914
+ },
915
+ children: [/* @__PURE__ */ jsx("button", {
916
+ type: "button",
917
+ onClick: () => setLayersPanelOpen((o) => !o),
918
+ title: "Map layers",
919
+ "aria-label": "Toggle map layers panel",
920
+ "aria-expanded": layersPanelOpen,
921
+ style: {
922
+ ...ctrlBtnBase,
923
+ borderRadius: 4,
924
+ padding: "6px 8px",
925
+ pointerEvents: "auto",
926
+ borderColor: layersPanelOpen ? "rgba(157, 112, 255, 0.4)" : ctrlBtnBase.border ? void 0 : "rgba(120, 100, 180, 0.18)",
927
+ background: layersPanelOpen ? "rgba(30, 34, 52, 0.95)" : ctrlBtnBase.background
928
+ },
929
+ onMouseEnter: ctrlHover,
930
+ onMouseLeave: ctrlLeave,
931
+ children: /* @__PURE__ */ jsxs("svg", {
932
+ width: "14",
933
+ height: "14",
934
+ viewBox: "0 0 24 24",
935
+ fill: "none",
936
+ stroke: "currentColor",
937
+ strokeWidth: "1.8",
938
+ strokeLinejoin: "round",
939
+ children: [
940
+ /* @__PURE__ */ jsx("path", { d: "M12 2L2 7l10 5 10-5-10-5z" }),
941
+ /* @__PURE__ */ jsx("path", { d: "M2 17l10 5 10-5" }),
942
+ /* @__PURE__ */ jsx("path", { d: "M2 12l10 5 10-5" })
943
+ ]
944
+ })
945
+ }), layersPanelOpen && /* @__PURE__ */ jsxs("div", {
946
+ style: {
947
+ position: "absolute",
948
+ top: "calc(100% + 6px)",
949
+ right: 0,
950
+ minWidth: 180,
951
+ background: "rgba(16, 18, 30, 0.96)",
952
+ backdropFilter: "blur(16px)",
953
+ WebkitBackdropFilter: "blur(16px)",
954
+ border: "1px solid rgba(120, 100, 180, 0.18)",
955
+ borderRadius: 6,
956
+ padding: "8px 0",
957
+ boxShadow: "0 8px 32px rgba(0,0,0,0.5)"
958
+ },
959
+ children: [
960
+ !isExplicitTileUrl && /* @__PURE__ */ jsxs(Fragment, { children: [
961
+ /* @__PURE__ */ jsx("div", {
962
+ style: {
963
+ padding: "4px 12px 6px",
964
+ fontSize: 9,
965
+ fontWeight: 600,
966
+ letterSpacing: "0.08em",
967
+ textTransform: "uppercase",
968
+ color: "#7a748e"
969
+ },
970
+ children: "Base Map"
971
+ }),
972
+ /* @__PURE__ */ jsx("div", {
973
+ style: {
974
+ display: "flex",
975
+ gap: 2,
976
+ padding: "0 8px 8px"
977
+ },
978
+ children: ["dark", "satellite"].map((style) => /* @__PURE__ */ jsxs("button", {
979
+ type: "button",
980
+ onClick: () => handleTileStyleChange(style),
981
+ "aria-pressed": tileStyle === style,
982
+ style: {
983
+ flex: 1,
984
+ background: tileStyle === style ? "rgba(157, 112, 255, 0.18)" : "rgba(255,255,255,0.03)",
985
+ border: `1px solid ${tileStyle === style ? "rgba(157, 112, 255, 0.35)" : "rgba(120, 100, 180, 0.1)"}`,
986
+ borderRadius: 4,
987
+ color: tileStyle === style ? "#d0c4ee" : "#7a748e",
988
+ cursor: "pointer",
989
+ padding: "5px 6px",
990
+ fontSize: 10,
991
+ fontWeight: tileStyle === style ? 600 : 400,
992
+ display: "flex",
993
+ alignItems: "center",
994
+ justifyContent: "center",
995
+ gap: 4,
996
+ transition: "all 0.15s ease"
997
+ },
998
+ children: [style === "dark" ? /* @__PURE__ */ jsx("svg", {
999
+ width: "10",
1000
+ height: "10",
1001
+ viewBox: "0 0 24 24",
1002
+ fill: "currentColor",
1003
+ stroke: "none",
1004
+ children: /* @__PURE__ */ jsx("path", { d: "M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" })
1005
+ }) : /* @__PURE__ */ jsxs("svg", {
1006
+ width: "10",
1007
+ height: "10",
1008
+ viewBox: "0 0 24 24",
1009
+ fill: "none",
1010
+ stroke: "currentColor",
1011
+ strokeWidth: "2",
1012
+ children: [/* @__PURE__ */ jsx("circle", {
1013
+ cx: "12",
1014
+ cy: "12",
1015
+ r: "10"
1016
+ }), /* @__PURE__ */ jsx("path", { d: "M2 12h20M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z" })]
1017
+ }), style === "dark" ? "Dark" : "Satellite"]
1018
+ }, style))
1019
+ }),
1020
+ /* @__PURE__ */ jsx("div", { style: {
1021
+ height: 1,
1022
+ background: "rgba(120, 100, 180, 0.1)",
1023
+ margin: "0 8px"
1024
+ } })
1025
+ ] }),
1026
+ /* @__PURE__ */ jsx("div", {
1027
+ style: {
1028
+ padding: `${isExplicitTileUrl ? "4px" : "8px"} 12px 4px`,
1029
+ fontSize: 9,
1030
+ fontWeight: 600,
1031
+ letterSpacing: "0.08em",
1032
+ textTransform: "uppercase",
1033
+ color: "#7a748e"
1034
+ },
1035
+ children: "Overlays"
1036
+ }),
1037
+ overlayItems.map((item) => /* @__PURE__ */ jsxs("button", {
1038
+ type: "button",
1039
+ onClick: () => handleLayerToggle(item.id, !item.enabled),
1040
+ style: {
1041
+ display: "flex",
1042
+ alignItems: "center",
1043
+ gap: 8,
1044
+ width: "100%",
1045
+ padding: "5px 12px",
1046
+ background: "transparent",
1047
+ border: "none",
1048
+ color: item.enabled ? "#c8c0d8" : "#5a5470",
1049
+ cursor: "pointer",
1050
+ fontSize: 11,
1051
+ textAlign: "left",
1052
+ transition: "background 0.1s"
1053
+ },
1054
+ onMouseEnter: (e) => {
1055
+ e.currentTarget.style.background = "rgba(157, 112, 255, 0.08)";
1056
+ },
1057
+ onMouseLeave: (e) => {
1058
+ e.currentTarget.style.background = "transparent";
1059
+ },
1060
+ children: [/* @__PURE__ */ jsx("span", {
1061
+ style: {
1062
+ width: 14,
1063
+ height: 14,
1064
+ borderRadius: 3,
1065
+ border: `1.5px solid ${item.enabled ? "rgba(157, 112, 255, 0.6)" : "rgba(120, 100, 180, 0.25)"}`,
1066
+ background: item.enabled ? "rgba(157, 112, 255, 0.22)" : "transparent",
1067
+ display: "flex",
1068
+ alignItems: "center",
1069
+ justifyContent: "center",
1070
+ flexShrink: 0,
1071
+ transition: "all 0.15s ease"
1072
+ },
1073
+ children: item.enabled && /* @__PURE__ */ jsx("svg", {
1074
+ width: "9",
1075
+ height: "9",
1076
+ viewBox: "0 0 12 12",
1077
+ fill: "none",
1078
+ stroke: "#d0c4ee",
1079
+ strokeWidth: "2",
1080
+ children: /* @__PURE__ */ jsx("path", { d: "M2 6l3 3 5-5" })
1081
+ })
1082
+ }), item.label]
1083
+ }, item.id))
1084
+ ]
1085
+ })]
1086
+ }),
1087
+ /* @__PURE__ */ jsxs("div", {
1088
+ style: {
1089
+ display: "flex",
1090
+ flexDirection: "column",
1091
+ pointerEvents: "auto"
1092
+ },
1093
+ children: [/* @__PURE__ */ jsx("button", {
1094
+ type: "button",
1095
+ onClick: handleZoomIn,
1096
+ title: "Zoom in",
1097
+ "aria-label": "Zoom in",
1098
+ style: {
1099
+ ...ctrlBtnBase,
1100
+ borderRadius: "4px 4px 0 0",
1101
+ borderBottom: "none",
1102
+ width: 30,
1103
+ height: 28,
1104
+ fontSize: 16,
1105
+ fontWeight: 300,
1106
+ pointerEvents: "auto"
1107
+ },
1108
+ onMouseEnter: ctrlHover,
1109
+ onMouseLeave: ctrlLeave,
1110
+ children: "+"
1111
+ }), /* @__PURE__ */ jsx("button", {
1112
+ type: "button",
1113
+ onClick: handleZoomOut,
1114
+ title: "Zoom out",
1115
+ "aria-label": "Zoom out",
1116
+ style: {
1117
+ ...ctrlBtnBase,
1118
+ borderRadius: "0 0 4px 4px",
1119
+ width: 30,
1120
+ height: 28,
1121
+ fontSize: 16,
1122
+ fontWeight: 300,
1123
+ pointerEvents: "auto"
1124
+ },
1125
+ onMouseEnter: ctrlHover,
1126
+ onMouseLeave: ctrlLeave,
1127
+ children: "−"
1128
+ })]
1129
+ })
1130
+ ]
1131
+ })
1132
+ ]
1133
+ });
1056
1134
  }
1057
- export {
1058
- GroundTrackMapLeaflet
1059
- };
1060
- //# sourceMappingURL=GroundTrackMapLeaflet.js.map
1135
+ //#endregion
1136
+ export { GroundTrackMapLeaflet, GroundTrackMapLeaflet as default };
1137
+
1138
+ //# sourceMappingURL=GroundTrackMapLeaflet.js.map