@zendir/ui 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (391) hide show
  1. package/CHANGELOG.md +419 -386
  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 +313 -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 +26 -3
  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 +533 -155
  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,1270 +1,1391 @@
1
- import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
- import { useRef, useState, useMemo, useEffect, useCallback } from "react";
3
1
  import { useTheme } from "../theme/ThemeProvider.js";
4
2
  import { AstroIcon } from "../core/display/AstroIcon.js";
5
- const STATUS_COLOR_MAP = {
6
- normal: "#56f000",
7
- // Green — Astro UXDS "Normal"
8
- standby: "#2dccff",
9
- // Cyan Astro UXDS "Standby"
10
- caution: "#fce83a",
11
- // Yellow Astro UXDS "Caution"
12
- serious: "#ffb302",
13
- // Orange — Astro UXDS "Serious"
14
- critical: "#ff3838",
15
- // Red Astro UXDS "Critical"
16
- off: "#a4abb6"
17
- // Grey Astro UXDS "Off"
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/react/charts/GroundTrackMap.tsx
6
+ /**
7
+ * @zendir/ui - GroundTrackMap Component (Enhanced)
8
+ *
9
+ * Full-featured 2D world map showing spacecraft ground tracks, current positions,
10
+ * ground station locations, coverage footprints, satellite icons, and day/night terminator.
11
+ *
12
+ * Inspired by Astro UXDS TT&C Monitor / GRM Dashboard ground map patterns:
13
+ * - Satellite icons with status-colored indicators (not just dots)
14
+ * - Ground station antenna icons with coverage circles
15
+ * - Sensor/antenna footprint cone projections
16
+ * - Future orbit tracks (dashed) vs past tracks (solid)
17
+ * - Day/night terminator
18
+ * - Multi-satellite support
19
+ * - Interactive hover tooltips
20
+ * - AOS/LOS markers on track
21
+ * - Legend with satellite/station counts
22
+ *
23
+ * Astro UX Compliance:
24
+ * - Theme-integrated via useTheme()
25
+ * - Uses official Astro status colors from theme tokens
26
+ * - Uses Astro monitoring icon patterns
27
+ * - 14pt minimum for labels (AstroUXDS guideline)
28
+ * - Consistent with Astro data visualization patterns
29
+ */
30
+ var STATUS_COLOR_MAP = {
31
+ normal: "#56f000",
32
+ standby: "#2dccff",
33
+ caution: "#fce83a",
34
+ serious: "#ffb302",
35
+ critical: "#ff3838",
36
+ off: "#a4abb6"
18
37
  };
19
- const DEFAULT_TRACK_COLORS = [
20
- "#2dccff",
21
- "#3E3CFF",
22
- "#9D70FF",
23
- "#56f000",
24
- "#fce83a",
25
- "#ff7849",
26
- "#2dd4bf",
27
- "#ff3838"
38
+ var DEFAULT_TRACK_COLORS = [
39
+ "#2dccff",
40
+ "#3E3CFF",
41
+ "#9D70FF",
42
+ "#56f000",
43
+ "#fce83a",
44
+ "#ff7849",
45
+ "#2dd4bf",
46
+ "#ff3838"
28
47
  ];
29
- const WORLD_MAP_PATHS = [
30
- // North America
31
- "M-168,70 L-168,60 L-145,63 L-140,58 L-130,55 L-125,49 L-122,48 L-117,33 L-115,28 L-105,22 L-97,18 L-88,20 L-82,25 L-82,30 L-80,32 L-67,47 L-60,46 L-55,52 L-56,60 L-65,70 L-85,73 L-100,72 L-130,70 L-168,70",
32
- // South America
33
- "M-82,12 L-77,8 L-73,11 L-65,5 L-60,5 L-50,0 L-45,-5 L-43,-10 L-39,-15 L-38,-22 L-40,-25 L-43,-30 L-48,-33 L-50,-40 L-55,-48 L-57,-52 L-68,-55 L-73,-50 L-78,-42 L-80,-20 L-78,-5 L-82,12",
34
- // Europe + Africa
35
- "M-10,72 L-5,60 L-5,48 L0,44 L3,44 L5,48 L8,44 L3,37 L-5,35 L-17,28 L-17,15 L-8,10 L-4,5 L5,5 L10,2 L15,-3 L22,-12 L28,-22 L33,-30 L30,-35 L22,-35 L18,-27 L12,-20 L8,-10 L3,0 L-5,10 L-12,15 L-15,20 L-13,32 L-10,38 L0,48 L8,55 L15,58 L20,60 L25,65 L30,70 L-10,72",
36
- // Asia
37
- "M30,70 L40,65 L50,60 L60,55 L70,50 L80,45 L90,45 L100,40 L105,35 L110,30 L115,25 L120,30 L125,35 L130,40 L135,45 L140,50 L145,55 L150,60 L155,65 L160,68 L170,70 L180,70 L180,72 L30,72 L30,70",
38
- // Australia
39
- "M112,-12 L118,-14 L125,-15 L132,-13 L138,-15 L143,-17 L148,-20 L152,-23 L153,-28 L150,-33 L147,-37 L140,-38 L134,-36 L130,-33 L126,-30 L120,-30 L116,-28 L114,-24 L113,-18 L112,-12",
40
- // Antarctica
41
- "M-180,-72 L180,-72 L180,-90 L-180,-90 Z"
48
+ var WORLD_MAP_PATHS = [
49
+ "M-168,70 L-168,60 L-145,63 L-140,58 L-130,55 L-125,49 L-122,48 L-117,33 L-115,28 L-105,22 L-97,18 L-88,20 L-82,25 L-82,30 L-80,32 L-67,47 L-60,46 L-55,52 L-56,60 L-65,70 L-85,73 L-100,72 L-130,70 L-168,70",
50
+ "M-82,12 L-77,8 L-73,11 L-65,5 L-60,5 L-50,0 L-45,-5 L-43,-10 L-39,-15 L-38,-22 L-40,-25 L-43,-30 L-48,-33 L-50,-40 L-55,-48 L-57,-52 L-68,-55 L-73,-50 L-78,-42 L-80,-20 L-78,-5 L-82,12",
51
+ "M-10,72 L-5,60 L-5,48 L0,44 L3,44 L5,48 L8,44 L3,37 L-5,35 L-17,28 L-17,15 L-8,10 L-4,5 L5,5 L10,2 L15,-3 L22,-12 L28,-22 L33,-30 L30,-35 L22,-35 L18,-27 L12,-20 L8,-10 L3,0 L-5,10 L-12,15 L-15,20 L-13,32 L-10,38 L0,48 L8,55 L15,58 L20,60 L25,65 L30,70 L-10,72",
52
+ "M30,70 L40,65 L50,60 L60,55 L70,50 L80,45 L90,45 L100,40 L105,35 L110,30 L115,25 L120,30 L125,35 L130,40 L135,45 L140,50 L145,55 L150,60 L155,65 L160,68 L170,70 L180,70 L180,72 L30,72 L30,70",
53
+ "M112,-12 L118,-14 L125,-15 L132,-13 L138,-15 L143,-17 L148,-20 L152,-23 L153,-28 L150,-33 L147,-37 L140,-38 L134,-36 L130,-33 L126,-30 L120,-30 L116,-28 L114,-24 L113,-18 L112,-12",
54
+ "M-180,-72 L180,-72 L180,-90 L-180,-90 Z"
42
55
  ];
56
+ /**
57
+ * Draw a ground-circle footprint using great-circle math.
58
+ * Given a center lat/lon and an angular radius (in degrees measured along
59
+ * Earth's surface), compute N points around the circle on the sphere, then
60
+ * project each to equirectangular pixel coordinates.
61
+ *
62
+ * This produces a circle that is geographically correct AND looks natural
63
+ * on the map — at high latitudes the circle is wider in longitude because
64
+ * that's how equirectangular projection works, but it's capped by reality
65
+ * rather than a simple 1/cos blowup.
66
+ */
43
67
  function drawCoverageEllipse(ctx, centerLat, centerLon, radiusDeg, W, H, lonToX, latToY, numSegments = 72) {
44
- const lat0 = centerLat * Math.PI / 180;
45
- const lon0 = centerLon * Math.PI / 180;
46
- const d = radiusDeg * Math.PI / 180;
47
- const cx = lonToX(centerLon, W);
48
- const cy = latToY(centerLat, H);
49
- const pxPerDegLon = W / 360;
50
- const pxPerDegLat = H / 180;
51
- ctx.beginPath();
52
- for (let i = 0; i <= numSegments; i++) {
53
- const bearing = i / numSegments * 2 * Math.PI;
54
- const sinLat = Math.sin(lat0) * Math.cos(d) + Math.cos(lat0) * Math.sin(d) * Math.cos(bearing);
55
- const lat = Math.asin(Math.max(-1, Math.min(1, sinLat)));
56
- const lon = lon0 + Math.atan2(
57
- Math.sin(bearing) * Math.sin(d) * Math.cos(lat0),
58
- Math.cos(d) - Math.sin(lat0) * sinLat
59
- );
60
- const pLatDeg = lat * 180 / Math.PI;
61
- const pLonDeg = lon * 180 / Math.PI;
62
- const dLon = pLonDeg - centerLon;
63
- const dLat = pLatDeg - centerLat;
64
- const px = cx + dLon * pxPerDegLon;
65
- const py = cy - dLat * pxPerDegLat;
66
- if (i === 0) ctx.moveTo(px, py);
67
- else ctx.lineTo(px, py);
68
- }
69
- ctx.closePath();
68
+ const lat0 = centerLat * Math.PI / 180;
69
+ const lon0 = centerLon * Math.PI / 180;
70
+ const d = radiusDeg * Math.PI / 180;
71
+ const cx = lonToX(centerLon, W);
72
+ const cy = latToY(centerLat, H);
73
+ const pxPerDegLon = W / 360;
74
+ const pxPerDegLat = H / 180;
75
+ ctx.beginPath();
76
+ for (let i = 0; i <= numSegments; i++) {
77
+ const bearing = i / numSegments * 2 * Math.PI;
78
+ const sinLat = Math.sin(lat0) * Math.cos(d) + Math.cos(lat0) * Math.sin(d) * Math.cos(bearing);
79
+ const lat = Math.asin(Math.max(-1, Math.min(1, sinLat)));
80
+ const lon = lon0 + Math.atan2(Math.sin(bearing) * Math.sin(d) * Math.cos(lat0), Math.cos(d) - Math.sin(lat0) * sinLat);
81
+ const pLatDeg = lat * 180 / Math.PI;
82
+ const dLon = lon * 180 / Math.PI - centerLon;
83
+ const dLat = pLatDeg - centerLat;
84
+ const px = cx + dLon * pxPerDegLon;
85
+ const py = cy - dLat * pxPerDegLat;
86
+ if (i === 0) ctx.moveTo(px, py);
87
+ else ctx.lineTo(px, py);
88
+ }
89
+ ctx.closePath();
70
90
  }
91
+ /** Parse a CSS hex color to [r, g, b]. Falls back to warm white. */
71
92
  function hexToRgb(hex) {
72
- const h = hex.replace("#", "");
73
- if (h.length === 3) {
74
- return [parseInt(h[0] + h[0], 16), parseInt(h[1] + h[1], 16), parseInt(h[2] + h[2], 16)];
75
- }
76
- if (h.length >= 6) {
77
- return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
78
- }
79
- return [255, 238, 221];
93
+ const h = hex.replace("#", "");
94
+ if (h.length === 3) return [
95
+ parseInt(h[0] + h[0], 16),
96
+ parseInt(h[1] + h[1], 16),
97
+ parseInt(h[2] + h[2], 16)
98
+ ];
99
+ if (h.length >= 6) return [
100
+ parseInt(h.slice(0, 2), 16),
101
+ parseInt(h.slice(2, 4), 16),
102
+ parseInt(h.slice(4, 6), 16)
103
+ ];
104
+ return [
105
+ 255,
106
+ 238,
107
+ 221
108
+ ];
80
109
  }
81
110
  function calculateTerminatorContinuous(date, depressionDeg = 0, numPoints = 360) {
82
- const dayOfYear = Math.floor((date.getTime() - Date.UTC(date.getUTCFullYear(), 0, 0)) / 864e5);
83
- const declination = -23.44 * Math.cos(2 * Math.PI / 365 * (dayOfYear + 10));
84
- const decRad = declination * Math.PI / 180;
85
- const B = 2 * Math.PI / 365 * (dayOfYear - 81);
86
- const eotMinutes = 9.87 * Math.sin(2 * B) - 7.53 * Math.cos(B) - 1.5 * Math.sin(B);
87
- const utcHours = date.getUTCHours() + date.getUTCMinutes() / 60 + date.getUTCSeconds() / 3600;
88
- const solarHours = utcHours + eotMinutes / 60;
89
- const subSolarLon = -(solarHours - 12) * 15;
90
- const depRad = depressionDeg * Math.PI / 180;
91
- const sunset = [];
92
- const sunrise = [];
93
- for (let i = 0; i <= numPoints; i++) {
94
- const lat = -89.999 + i / numPoints * 179.998;
95
- const latRad = lat * (Math.PI / 180);
96
- const cosH = -(Math.sin(depRad) + Math.sin(latRad) * Math.sin(decRad)) / (Math.cos(latRad) * Math.cos(decRad));
97
- if (cosH < -1) ;
98
- else if (cosH > 1) {
99
- sunset.push([lat, subSolarLon]);
100
- sunrise.push([lat, subSolarLon + 360]);
101
- } else {
102
- const H = Math.acos(cosH) * 180 / Math.PI;
103
- sunset.push([lat, subSolarLon + H]);
104
- sunrise.push([lat, subSolarLon + 360 - H]);
105
- }
106
- }
107
- return { sunset, sunrise };
111
+ const dayOfYear = Math.floor((date.getTime() - Date.UTC(date.getUTCFullYear(), 0, 0)) / 864e5);
112
+ const decRad = -23.44 * Math.cos(2 * Math.PI / 365 * (dayOfYear + 10)) * Math.PI / 180;
113
+ const B = 2 * Math.PI / 365 * (dayOfYear - 81);
114
+ const eotMinutes = 9.87 * Math.sin(2 * B) - 7.53 * Math.cos(B) - 1.5 * Math.sin(B);
115
+ const subSolarLon = -(date.getUTCHours() + date.getUTCMinutes() / 60 + date.getUTCSeconds() / 3600 + eotMinutes / 60 - 12) * 15;
116
+ const depRad = depressionDeg * Math.PI / 180;
117
+ const sunset = [];
118
+ const sunrise = [];
119
+ for (let i = 0; i <= numPoints; i++) {
120
+ const lat = -89.999 + i / numPoints * 179.998;
121
+ const latRad = lat * (Math.PI / 180);
122
+ const cosH = -(Math.sin(depRad) + Math.sin(latRad) * Math.sin(decRad)) / (Math.cos(latRad) * Math.cos(decRad));
123
+ if (cosH < -1) {} else if (cosH > 1) {
124
+ sunset.push([lat, subSolarLon]);
125
+ sunrise.push([lat, subSolarLon + 360]);
126
+ } else {
127
+ const H = Math.acos(cosH) * 180 / Math.PI;
128
+ sunset.push([lat, subSolarLon + H]);
129
+ sunrise.push([lat, subSolarLon + 360 - H]);
130
+ }
131
+ }
132
+ return {
133
+ sunset,
134
+ sunrise
135
+ };
108
136
  }
109
137
  function buildNightPolygon(terminator) {
110
- const { sunset, sunrise } = terminator;
111
- if (!sunset.length || !sunrise.length) return [];
112
- const poly = [];
113
- poly.push(...sunset);
114
- poly.push(...[...sunrise].reverse());
115
- return poly;
138
+ const { sunset, sunrise } = terminator;
139
+ if (!sunset.length || !sunrise.length) return [];
140
+ const poly = [];
141
+ poly.push(...sunset);
142
+ poly.push(...[...sunrise].reverse());
143
+ return poly;
116
144
  }
117
- function GroundTrackMap({
118
- groundTrack,
119
- satellites = [],
120
- groundStations = [],
121
- accessMask,
122
- teamPaths,
123
- showTerminator = true,
124
- terminatorTime,
125
- showGrid = true,
126
- showLegend = true,
127
- showEquator = true,
128
- showRecenterButton = false,
129
- showMapStyleToggle = true,
130
- isLoading = false,
131
- emptyMessage = "No orbital data available",
132
- width = "100%",
133
- height = "100%",
134
- minHeight = "400px",
135
- defaultCenter,
136
- defaultZoom,
137
- className = "",
138
- onSatelliteClick,
139
- onStationClick,
140
- mapProvider = "leaflet",
141
- tileUrl,
142
- nightTileUrl,
143
- lightSources,
144
- pins,
145
- pinsEditable = false,
146
- onPinAdd,
147
- onPinUpdate,
148
- onPinRemove,
149
- customLayers,
150
- onLayerChange,
151
- showCelestialMarkers
152
- }) {
153
- const { tokens } = useTheme();
154
- const canvasRef = useRef(null);
155
- const containerRef = useRef(null);
156
- const [tooltip, setTooltip] = useState(null);
157
- const [_viewOffset, setViewOffset] = useState({ x: 0, y: 0 });
158
- const allSatellites = useMemo(() => {
159
- if (satellites.length > 0) return satellites;
160
- if (teamPaths && teamPaths.length > 0) {
161
- return teamPaths.map((tp, idx) => ({
162
- id: `team-${idx}`,
163
- name: tp.name,
164
- color: tp.color,
165
- status: "normal",
166
- groundTrack: tp.positions.filter((p) => p.lat != null && p.lng != null).map((p) => ({
167
- latitude: p.lat,
168
- longitude: p.lng,
169
- altitude: p.altitude ?? 0,
170
- timestamp: p.time != null ? new Date(p.time * 1e3).toISOString() : (/* @__PURE__ */ new Date()).toISOString()
171
- }))
172
- }));
173
- }
174
- if (groundTrack && groundTrack.length > 0) {
175
- return [{
176
- id: "primary",
177
- name: "Satellite",
178
- groundTrack,
179
- status: "normal",
180
- accessMask
181
- }];
182
- }
183
- return [];
184
- }, [satellites, groundTrack, accessMask, teamPaths]);
185
- const leafletDefaultCenter = useMemo(() => defaultCenter ?? [20, 0], [defaultCenter]);
186
- const leafletDefaultZoom = useMemo(() => defaultZoom ?? 2, [defaultZoom]);
187
- const [LeafletMap, setLeafletMap] = useState(null);
188
- const [leafletFailed, setLeafletFailed] = useState(false);
189
- useEffect(() => {
190
- if (mapProvider !== "leaflet") return;
191
- import("./GroundTrackMapLeaflet.js").then((m) => setLeafletMap(() => m.GroundTrackMapLeaflet)).catch((err) => {
192
- console.warn('[GroundTrackMap] Leaflet backend failed to load. Install "leaflet" and ensure it is resolvable. Falling back to static canvas.', err);
193
- setLeafletFailed(true);
194
- });
195
- }, [mapProvider]);
196
- const handleRecenter = useCallback(() => {
197
- setViewOffset({ x: 0, y: 0 });
198
- }, []);
199
- const COLORS = useMemo(() => ({
200
- background: tokens.colors.background.base,
201
- grid: tokens.colors.border.muted,
202
- text: tokens.colors.text.secondary,
203
- accent: tokens.colors.accent.primary,
204
- land: "rgba(30, 40, 58, 0.92)",
205
- ocean: tokens.colors.background.base,
206
- night: "rgba(0, 10, 40, 0.35)",
207
- equator: "rgba(88, 166, 255, 0.25)"
208
- }), [tokens]);
209
- const lonToX = useCallback((lon, w) => {
210
- let normalizedLon = lon;
211
- while (normalizedLon > 180) normalizedLon -= 360;
212
- while (normalizedLon < -180) normalizedLon += 360;
213
- return (normalizedLon + 180) / 360 * w;
214
- }, []);
215
- const latToY = useCallback((lat, h) => {
216
- return (90 - lat) / 180 * h;
217
- }, []);
218
- const draw = useCallback(() => {
219
- const canvas = canvasRef.current;
220
- const container = containerRef.current;
221
- if (!canvas || !container) return;
222
- const ctx = canvas.getContext("2d");
223
- if (!ctx) return;
224
- const dpr = window.devicePixelRatio || 1;
225
- const rect = container.getBoundingClientRect();
226
- const W = rect.width;
227
- const H = typeof height === "number" ? height : rect.height || 400;
228
- canvas.width = W * dpr;
229
- canvas.height = H * dpr;
230
- canvas.style.width = `${W}px`;
231
- canvas.style.height = `${H}px`;
232
- ctx.scale(dpr, dpr);
233
- ctx.fillStyle = COLORS.background;
234
- ctx.fillRect(0, 0, W, H);
235
- if (showTerminator) {
236
- const now = terminatorTime ?? /* @__PURE__ */ new Date();
237
- const BAND_STEP = 1.5;
238
- const MAX_DEP = 30;
239
- const NIGHT_ALPHA = 0.38;
240
- const bandSteps = [];
241
- for (let d = 0; d <= MAX_DEP; d += BAND_STEP) {
242
- const t = Math.min(d / 18, 1);
243
- const alpha = Math.pow(t, 1.6) * (NIGHT_ALPHA * 0.85) + (d > 18 ? (d - 18) / 12 * (NIGHT_ALPHA * 0.15) : 0);
244
- bandSteps.push({ depression: d, alpha: Math.min(alpha, NIGHT_ALPHA) });
245
- }
246
- bandSteps.push({ depression: 90, alpha: NIGHT_ALPHA });
247
- let prevAlpha = 0;
248
- for (const zone of bandSteps) {
249
- const dep = Math.min(zone.depression, 89);
250
- const bandAlpha = zone.alpha - prevAlpha;
251
- if (bandAlpha < 2e-3) {
252
- prevAlpha = zone.alpha;
253
- continue;
254
- }
255
- const terminator = calculateTerminatorContinuous(now, dep);
256
- if (terminator.sunset.length === 0) {
257
- prevAlpha = zone.alpha;
258
- continue;
259
- }
260
- const poly = buildNightPolygon(terminator);
261
- if (poly.length < 3) {
262
- prevAlpha = zone.alpha;
263
- continue;
264
- }
265
- ctx.fillStyle = `rgba(42, 58, 82, ${bandAlpha.toFixed(4)})`;
266
- for (const offset of [-360, 0, 360]) {
267
- ctx.beginPath();
268
- for (let i = 0; i < poly.length; i++) {
269
- const lat = poly[i][0];
270
- const lon = poly[i][1] + offset;
271
- const x = lonToX(lon, W);
272
- const y = latToY(lat, H);
273
- if (i === 0) {
274
- ctx.moveTo(x, y);
275
- } else {
276
- ctx.lineTo(x, y);
277
- }
278
- }
279
- ctx.closePath();
280
- ctx.fill();
281
- }
282
- prevAlpha = zone.alpha;
283
- }
284
- const terminatorEdge = calculateTerminatorContinuous(now, 0);
285
- if (terminatorEdge.sunset.length > 2) {
286
- ctx.save();
287
- ctx.strokeStyle = "rgba(160, 160, 160, 0.08)";
288
- ctx.lineWidth = 3;
289
- for (const curve of [terminatorEdge.sunset, terminatorEdge.sunrise]) {
290
- for (const offset of [-360, 0, 360]) {
291
- ctx.beginPath();
292
- for (let i = 0; i < curve.length; i++) {
293
- const x = lonToX(curve[i][1] + offset, W);
294
- const y = latToY(curve[i][0], H);
295
- if (i === 0) ctx.moveTo(x, y);
296
- else ctx.lineTo(x, y);
297
- }
298
- ctx.stroke();
299
- }
300
- }
301
- ctx.strokeStyle = "rgba(168, 168, 168, 0.30)";
302
- ctx.lineWidth = 1;
303
- for (const curve of [terminatorEdge.sunset, terminatorEdge.sunrise]) {
304
- for (const offset of [-360, 0, 360]) {
305
- ctx.beginPath();
306
- for (let i = 0; i < curve.length; i++) {
307
- const x = lonToX(curve[i][1] + offset, W);
308
- const y = latToY(curve[i][0], H);
309
- if (i === 0) ctx.moveTo(x, y);
310
- else ctx.lineTo(x, y);
311
- }
312
- ctx.stroke();
313
- }
314
- }
315
- ctx.restore();
316
- }
317
- }
318
- ctx.fillStyle = COLORS.land;
319
- ctx.strokeStyle = "rgba(100, 120, 160, 0.25)";
320
- ctx.lineWidth = 1;
321
- for (const pathStr of WORLD_MAP_PATHS) {
322
- const commands = pathStr.split(" ");
323
- ctx.beginPath();
324
- for (const cmd of commands) {
325
- if (cmd.startsWith("M") || cmd.startsWith("L")) {
326
- const coords = cmd.substring(1).split(",");
327
- const lon = parseFloat(coords[0]);
328
- const lat = parseFloat(coords[1]);
329
- const x = lonToX(lon, W);
330
- const y = latToY(lat, H);
331
- if (cmd.startsWith("M")) ctx.moveTo(x, y);
332
- else ctx.lineTo(x, y);
333
- } else if (cmd === "Z") {
334
- ctx.closePath();
335
- }
336
- }
337
- ctx.fill();
338
- ctx.stroke();
339
- }
340
- if (showGrid) {
341
- ctx.strokeStyle = COLORS.grid;
342
- ctx.lineWidth = 0.4;
343
- for (let lon = -180; lon <= 180; lon += 30) {
344
- const x = lonToX(lon, W);
345
- ctx.beginPath();
346
- ctx.moveTo(x, 0);
347
- ctx.lineTo(x, H);
348
- ctx.stroke();
349
- }
350
- for (let lat = -90; lat <= 90; lat += 30) {
351
- const y = latToY(lat, H);
352
- ctx.beginPath();
353
- ctx.moveTo(0, y);
354
- ctx.lineTo(W, y);
355
- ctx.stroke();
356
- }
357
- }
358
- if (showEquator) {
359
- const eqY = latToY(0, H);
360
- ctx.strokeStyle = COLORS.equator;
361
- ctx.lineWidth = 1;
362
- ctx.setLineDash([6, 4]);
363
- ctx.beginPath();
364
- ctx.moveTo(0, eqY);
365
- ctx.lineTo(W, eqY);
366
- ctx.stroke();
367
- ctx.setLineDash([]);
368
- }
369
- for (const gs of groundStations) {
370
- const enhanced = gs;
371
- if (enhanced.showCoverage && enhanced.coverageRadius) {
372
- const statusColor = STATUS_COLOR_MAP[enhanced.status || "standby"] || STATUS_COLOR_MAP.standby;
373
- drawCoverageEllipse(ctx, gs.latitude, gs.longitude, enhanced.coverageRadius, W, H, lonToX, latToY);
374
- ctx.fillStyle = `${statusColor}15`;
375
- ctx.fill();
376
- ctx.strokeStyle = `${statusColor}40`;
377
- ctx.lineWidth = 1;
378
- ctx.setLineDash([4, 3]);
379
- ctx.stroke();
380
- ctx.setLineDash([]);
381
- }
382
- }
383
- for (const gs of groundStations) {
384
- const enhanced = gs;
385
- const x = lonToX(gs.longitude, W);
386
- const y = latToY(gs.latitude, H);
387
- const status = enhanced.status || "standby";
388
- const statusColor = STATUS_COLOR_MAP[status] || STATUS_COLOR_MAP.standby;
389
- const stationType = enhanced.type || "dish";
390
- const iconSize = 20;
391
- const halfIcon = iconSize / 2;
392
- ctx.save();
393
- ctx.translate(x, y);
394
- ctx.beginPath();
395
- ctx.arc(0, 0, halfIcon + 2, 0, Math.PI * 2);
396
- ctx.fillStyle = `${statusColor}18`;
397
- ctx.fill();
398
- ctx.beginPath();
399
- ctx.arc(0, 0, halfIcon, 0, Math.PI * 2);
400
- ctx.fillStyle = "rgba(15, 23, 42, 0.85)";
401
- ctx.fill();
402
- ctx.strokeStyle = `${statusColor}66`;
403
- ctx.lineWidth = 1.5;
404
- ctx.stroke();
405
- ctx.save();
406
- const iconScale = iconSize / 24;
407
- ctx.translate(-halfIcon, -halfIcon);
408
- ctx.scale(iconScale, iconScale);
409
- if (stationType === "dish" || stationType === "omni") {
410
- ctx.strokeStyle = statusColor;
411
- ctx.fillStyle = statusColor;
412
- ctx.lineWidth = 2 / iconScale;
413
- ctx.beginPath();
414
- ctx.arc(12, 12, 10, Math.PI * 1.25, Math.PI * 1.75);
415
- ctx.stroke();
416
- ctx.beginPath();
417
- ctx.arc(12, 12, 6.5, Math.PI * 1.2, Math.PI * 1.8);
418
- ctx.stroke();
419
- ctx.beginPath();
420
- ctx.arc(12, 12, 3, Math.PI * 1.15, Math.PI * 1.85);
421
- ctx.stroke();
422
- ctx.beginPath();
423
- ctx.arc(12, 12, 1.5, 0, Math.PI * 2);
424
- ctx.fill();
425
- ctx.lineWidth = 1.5 / iconScale;
426
- ctx.beginPath();
427
- ctx.moveTo(12, 14);
428
- ctx.lineTo(12, 20);
429
- ctx.stroke();
430
- ctx.beginPath();
431
- ctx.moveTo(8, 20);
432
- ctx.lineTo(16, 20);
433
- ctx.stroke();
434
- } else if (stationType === "phased-array") {
435
- ctx.strokeStyle = statusColor;
436
- ctx.fillStyle = statusColor;
437
- ctx.lineWidth = 1.5 / iconScale;
438
- ctx.strokeRect(5, 4, 14, 12);
439
- for (let gx = 8; gx <= 16; gx += 4) {
440
- ctx.beginPath();
441
- ctx.moveTo(gx, 4);
442
- ctx.lineTo(gx, 16);
443
- ctx.stroke();
444
- }
445
- for (let gy = 8; gy <= 12; gy += 4) {
446
- ctx.beginPath();
447
- ctx.moveTo(5, gy);
448
- ctx.lineTo(19, gy);
449
- ctx.stroke();
450
- }
451
- ctx.beginPath();
452
- ctx.moveTo(12, 16);
453
- ctx.lineTo(12, 21);
454
- ctx.stroke();
455
- ctx.beginPath();
456
- ctx.moveTo(8, 21);
457
- ctx.lineTo(16, 21);
458
- ctx.stroke();
459
- } else {
460
- ctx.strokeStyle = statusColor;
461
- ctx.fillStyle = statusColor;
462
- ctx.lineWidth = 2 / iconScale;
463
- ctx.beginPath();
464
- ctx.arc(10, 12, 7, Math.PI * 0.8, Math.PI * 1.7);
465
- ctx.stroke();
466
- ctx.lineWidth = 1.5 / iconScale;
467
- ctx.beginPath();
468
- ctx.moveTo(10, 12);
469
- ctx.lineTo(16, 6);
470
- ctx.stroke();
471
- ctx.lineWidth = 1.2 / iconScale;
472
- ctx.beginPath();
473
- ctx.arc(17, 5, 2.5, Math.PI * 1.2, Math.PI * 1.7);
474
- ctx.stroke();
475
- ctx.beginPath();
476
- ctx.arc(17, 5, 4.5, Math.PI * 1.15, Math.PI * 1.75);
477
- ctx.stroke();
478
- ctx.lineWidth = 1.5 / iconScale;
479
- ctx.beginPath();
480
- ctx.moveTo(10, 14);
481
- ctx.lineTo(10, 20);
482
- ctx.stroke();
483
- ctx.beginPath();
484
- ctx.moveTo(6, 20);
485
- ctx.lineTo(14, 20);
486
- ctx.stroke();
487
- }
488
- ctx.restore();
489
- const badgeX = -halfIcon + 1;
490
- const badgeY = -halfIcon + 1;
491
- const badgeSize = 7;
492
- ctx.beginPath();
493
- ctx.arc(badgeX, badgeY, badgeSize / 2 + 2, 0, Math.PI * 2);
494
- ctx.fillStyle = "rgba(15, 23, 42, 0.9)";
495
- ctx.fill();
496
- ctx.fillStyle = statusColor;
497
- ctx.strokeStyle = statusColor;
498
- ctx.lineWidth = 1;
499
- if (status === "normal") {
500
- ctx.beginPath();
501
- ctx.arc(badgeX, badgeY, badgeSize / 2, 0, Math.PI * 2);
502
- ctx.fill();
503
- } else if (status === "standby") {
504
- ctx.beginPath();
505
- ctx.arc(badgeX, badgeY, badgeSize / 2 - 0.5, 0, Math.PI * 2);
506
- ctx.lineWidth = 2;
507
- ctx.stroke();
508
- } else if (status === "caution") {
509
- const hs = badgeSize / 2;
510
- ctx.fillRect(badgeX - hs, badgeY - hs, badgeSize, badgeSize);
511
- } else if (status === "serious") {
512
- const hs = badgeSize / 2;
513
- ctx.beginPath();
514
- ctx.moveTo(badgeX, badgeY - hs);
515
- ctx.lineTo(badgeX + hs, badgeY);
516
- ctx.lineTo(badgeX, badgeY + hs);
517
- ctx.lineTo(badgeX - hs, badgeY);
518
- ctx.closePath();
519
- ctx.fill();
520
- } else if (status === "critical") {
521
- const hs = badgeSize / 2;
522
- ctx.beginPath();
523
- ctx.moveTo(badgeX, badgeY + hs);
524
- ctx.lineTo(badgeX + hs, badgeY - hs);
525
- ctx.lineTo(badgeX - hs, badgeY - hs);
526
- ctx.closePath();
527
- ctx.fill();
528
- } else {
529
- ctx.beginPath();
530
- ctx.arc(badgeX, badgeY, badgeSize / 3, 0, Math.PI * 2);
531
- ctx.fill();
532
- }
533
- ctx.restore();
534
- ctx.fillStyle = COLORS.text;
535
- ctx.font = '9px "Inter", system-ui, sans-serif';
536
- ctx.textAlign = "center";
537
- ctx.fillText(gs.name.length > 14 ? gs.name.slice(0, 14) + "…" : gs.name, x, y + halfIcon + 12);
538
- }
539
- if (lightSources && lightSources.length > 0 && showTerminator) {
540
- const now = terminatorTime ?? /* @__PURE__ */ new Date();
541
- for (const light of lightSources) {
542
- const lx = lonToX(light.longitude, W);
543
- const ly = latToY(light.latitude, H);
544
- const r = light.radius ?? 4;
545
- const intensity = light.intensity ?? 0.8;
546
- const color = light.color ?? "#ffeedd";
547
- const dayOfYear = Math.floor((now.getTime() - Date.UTC(now.getUTCFullYear(), 0, 0)) / 864e5);
548
- const declination = -23.44 * Math.cos(2 * Math.PI / 365 * (dayOfYear + 10));
549
- const decRad = declination * Math.PI / 180;
550
- const lsSubSolarLon = -(now.getUTCHours() + now.getUTCMinutes() / 60 - 12) * 15;
551
- const latRad = light.latitude * Math.PI / 180;
552
- const sinAlt = Math.sin(latRad) * Math.sin(decRad) + Math.cos(latRad) * Math.cos(decRad) * Math.cos((light.longitude - lsSubSolarLon) * Math.PI / 180);
553
- const solarAltDeg = Math.asin(sinAlt) * 180 / Math.PI;
554
- let nightFactor;
555
- if (solarAltDeg >= 0) nightFactor = 0;
556
- else if (solarAltDeg <= -18) nightFactor = 1;
557
- else nightFactor = Math.min(1, -solarAltDeg / 18);
558
- if (nightFactor < 0.01) continue;
559
- const alpha = intensity * nightFactor;
560
- const [cr, cg, cb] = hexToRgb(color);
561
- const grad = ctx.createRadialGradient(lx, ly, 0, lx, ly, r * 2);
562
- grad.addColorStop(0, `rgba(${cr}, ${cg}, ${cb}, ${alpha})`);
563
- grad.addColorStop(0.4, `rgba(${cr}, ${cg}, ${cb}, ${alpha * 0.6})`);
564
- grad.addColorStop(1, `rgba(${cr}, ${cg}, ${cb}, 0)`);
565
- ctx.fillStyle = grad;
566
- ctx.beginPath();
567
- ctx.arc(lx, ly, r * 2, 0, Math.PI * 2);
568
- ctx.fill();
569
- ctx.fillStyle = `rgba(${cr}, ${cg}, ${cb}, ${Math.min(1, alpha * 1.3)})`;
570
- ctx.beginPath();
571
- ctx.arc(lx, ly, Math.max(1, r * 0.4), 0, Math.PI * 2);
572
- ctx.fill();
573
- }
574
- }
575
- allSatellites.forEach((sat, satIdx) => {
576
- const track = sat.groundTrack;
577
- if (!track || track.length === 0) return;
578
- const trackColor = sat.color || DEFAULT_TRACK_COLORS[satIdx % DEFAULT_TRACK_COLORS.length];
579
- const futureIdx = sat.futureTrackIndex ?? track.length;
580
- ctx.strokeStyle = trackColor;
581
- ctx.lineWidth = 2;
582
- ctx.lineCap = "round";
583
- ctx.lineJoin = "round";
584
- ctx.setLineDash([]);
585
- ctx.beginPath();
586
- let prevX = lonToX(track[0].longitude, W);
587
- let prevY = latToY(track[0].latitude, H);
588
- ctx.moveTo(prevX, prevY);
589
- for (let i = 1; i < Math.min(futureIdx, track.length); i++) {
590
- const x = lonToX(track[i].longitude, W);
591
- const y = latToY(track[i].latitude, H);
592
- const crossesDateLine = Math.abs(x - prevX) > W / 2;
593
- if (crossesDateLine) {
594
- ctx.stroke();
595
- ctx.beginPath();
596
- ctx.moveTo(x, y);
597
- } else {
598
- ctx.lineTo(x, y);
599
- }
600
- prevX = x;
601
- prevY = y;
602
- }
603
- ctx.stroke();
604
- if (futureIdx < track.length) {
605
- ctx.strokeStyle = `${trackColor}88`;
606
- ctx.lineWidth = 1.5;
607
- ctx.setLineDash([6, 4]);
608
- ctx.beginPath();
609
- ctx.moveTo(lonToX(track[futureIdx].longitude, W), latToY(track[futureIdx].latitude, H));
610
- let fpx = lonToX(track[futureIdx].longitude, W);
611
- for (let i = futureIdx + 1; i < track.length; i++) {
612
- const x = lonToX(track[i].longitude, W);
613
- const y = latToY(track[i].latitude, H);
614
- if (Math.abs(x - fpx) > W / 2) {
615
- ctx.stroke();
616
- ctx.beginPath();
617
- ctx.moveTo(x, y);
618
- } else {
619
- ctx.lineTo(x, y);
620
- }
621
- fpx = x;
622
- }
623
- ctx.stroke();
624
- ctx.setLineDash([]);
625
- }
626
- if (sat.accessMask && sat.accessMask.some(Boolean)) {
627
- ctx.strokeStyle = STATUS_COLOR_MAP.normal;
628
- ctx.lineWidth = 3;
629
- ctx.setLineDash([]);
630
- ctx.beginPath();
631
- let drawing = false;
632
- let apx = lonToX(track[0].longitude, W);
633
- for (let i = 0; i < track.length; i++) {
634
- const x = lonToX(track[i].longitude, W);
635
- const y = latToY(track[i].latitude, H);
636
- const active = sat.accessMask[i];
637
- const crossesDateLine = i > 0 && Math.abs(x - apx) > W / 2;
638
- if (active && !crossesDateLine) {
639
- if (!drawing) {
640
- ctx.moveTo(x, y);
641
- drawing = true;
642
- } else ctx.lineTo(x, y);
643
- } else {
644
- if (drawing) {
645
- ctx.stroke();
646
- ctx.beginPath();
647
- drawing = false;
648
- }
649
- }
650
- apx = x;
651
- }
652
- if (drawing) ctx.stroke();
653
- }
654
- if (sat.passMarkers) {
655
- for (const marker of sat.passMarkers) {
656
- const mx = lonToX(marker.longitude, W);
657
- const my = latToY(marker.latitude, H);
658
- const markerColor = marker.type === "aos" ? STATUS_COLOR_MAP.normal : STATUS_COLOR_MAP.critical;
659
- ctx.save();
660
- ctx.translate(mx, my);
661
- ctx.beginPath();
662
- if (marker.type === "aos") {
663
- ctx.moveTo(0, -6);
664
- ctx.lineTo(4, 2);
665
- ctx.lineTo(-4, 2);
666
- } else {
667
- ctx.moveTo(0, 6);
668
- ctx.lineTo(4, -2);
669
- ctx.lineTo(-4, -2);
670
- }
671
- ctx.closePath();
672
- ctx.fillStyle = markerColor;
673
- ctx.fill();
674
- ctx.restore();
675
- if (marker.label) {
676
- ctx.fillStyle = markerColor;
677
- ctx.font = '8px "Inter", system-ui, sans-serif';
678
- ctx.textAlign = "center";
679
- ctx.fillText(marker.label, mx, marker.type === "aos" ? my - 9 : my + 13);
680
- }
681
- }
682
- }
683
- if (sat.showFootprint && sat.footprintRadius) {
684
- const lastPt = futureIdx > 0 ? track[Math.min(futureIdx - 1, track.length - 1)] : track[track.length - 1];
685
- drawCoverageEllipse(ctx, lastPt.latitude, lastPt.longitude, sat.footprintRadius, W, H, lonToX, latToY);
686
- ctx.fillStyle = `${trackColor}12`;
687
- ctx.fill();
688
- ctx.strokeStyle = `${trackColor}50`;
689
- ctx.lineWidth = 1;
690
- ctx.setLineDash([3, 3]);
691
- ctx.stroke();
692
- ctx.setLineDash([]);
693
- }
694
- const currentIdx = futureIdx > 0 ? Math.min(futureIdx - 1, track.length - 1) : track.length - 1;
695
- const current = track[currentIdx];
696
- const cx = lonToX(current.longitude, W);
697
- const cy = latToY(current.latitude, H);
698
- const satStatus = sat.status || "normal";
699
- const statusColor = STATUS_COLOR_MAP[satStatus] || STATUS_COLOR_MAP.normal;
700
- const satIconSize = 22;
701
- const satHalf = satIconSize / 2;
702
- ctx.beginPath();
703
- ctx.arc(cx, cy, satHalf + 6, 0, Math.PI * 2);
704
- ctx.fillStyle = `${statusColor}12`;
705
- ctx.fill();
706
- ctx.beginPath();
707
- ctx.arc(cx, cy, satHalf + 3, 0, Math.PI * 2);
708
- ctx.fillStyle = `${statusColor}20`;
709
- ctx.fill();
710
- ctx.save();
711
- ctx.translate(cx, cy);
712
- ctx.beginPath();
713
- ctx.arc(0, 0, satHalf, 0, Math.PI * 2);
714
- ctx.fillStyle = "rgba(15, 23, 42, 0.88)";
715
- ctx.fill();
716
- ctx.strokeStyle = `${statusColor}88`;
717
- ctx.lineWidth = 1.5;
718
- ctx.stroke();
719
- ctx.fillStyle = statusColor;
720
- const bw = 5, bh = 3.5;
721
- ctx.beginPath();
722
- ctx.moveTo(-bw / 2 + 0.5, -bh / 2);
723
- ctx.lineTo(bw / 2 - 0.5, -bh / 2);
724
- ctx.arcTo(bw / 2, -bh / 2, bw / 2, -bh / 2 + 0.5, 0.5);
725
- ctx.lineTo(bw / 2, bh / 2 - 0.5);
726
- ctx.arcTo(bw / 2, bh / 2, bw / 2 - 0.5, bh / 2, 0.5);
727
- ctx.lineTo(-bw / 2 + 0.5, bh / 2);
728
- ctx.arcTo(-bw / 2, bh / 2, -bw / 2, bh / 2 - 0.5, 0.5);
729
- ctx.lineTo(-bw / 2, -bh / 2 + 0.5);
730
- ctx.arcTo(-bw / 2, -bh / 2, -bw / 2 + 0.5, -bh / 2, 0.5);
731
- ctx.closePath();
732
- ctx.fill();
733
- ctx.fillStyle = `${statusColor}BB`;
734
- ctx.fillRect(-satHalf + 2, -2, 5, 4);
735
- ctx.strokeStyle = `${statusColor}55`;
736
- ctx.lineWidth = 0.5;
737
- ctx.beginPath();
738
- ctx.moveTo(-satHalf + 4.5, -2);
739
- ctx.lineTo(-satHalf + 4.5, 2);
740
- ctx.stroke();
741
- ctx.fillStyle = `${statusColor}BB`;
742
- ctx.fillRect(satHalf - 7, -2, 5, 4);
743
- ctx.strokeStyle = `${statusColor}55`;
744
- ctx.beginPath();
745
- ctx.moveTo(satHalf - 4.5, -2);
746
- ctx.lineTo(satHalf - 4.5, 2);
747
- ctx.stroke();
748
- ctx.strokeStyle = statusColor;
749
- ctx.lineWidth = 1;
750
- ctx.beginPath();
751
- ctx.moveTo(-bw / 2, 0);
752
- ctx.lineTo(-satHalf + 7, 0);
753
- ctx.moveTo(bw / 2, 0);
754
- ctx.lineTo(satHalf - 7, 0);
755
- ctx.stroke();
756
- const satBadgeX = -satHalf + 2;
757
- const satBadgeY = -satHalf + 2;
758
- const satBadgeSize = 7;
759
- ctx.beginPath();
760
- ctx.arc(satBadgeX, satBadgeY, satBadgeSize / 2 + 2, 0, Math.PI * 2);
761
- ctx.fillStyle = "rgba(15, 23, 42, 0.9)";
762
- ctx.fill();
763
- ctx.fillStyle = statusColor;
764
- ctx.strokeStyle = statusColor;
765
- ctx.lineWidth = 1;
766
- if (satStatus === "normal") {
767
- ctx.beginPath();
768
- ctx.arc(satBadgeX, satBadgeY, satBadgeSize / 2, 0, Math.PI * 2);
769
- ctx.fill();
770
- } else if (satStatus === "standby") {
771
- ctx.beginPath();
772
- ctx.arc(satBadgeX, satBadgeY, satBadgeSize / 2 - 0.5, 0, Math.PI * 2);
773
- ctx.lineWidth = 2;
774
- ctx.stroke();
775
- } else if (satStatus === "caution") {
776
- const hs = satBadgeSize / 2;
777
- ctx.fillRect(satBadgeX - hs, satBadgeY - hs, satBadgeSize, satBadgeSize);
778
- } else if (satStatus === "serious") {
779
- const hs = satBadgeSize / 2;
780
- ctx.beginPath();
781
- ctx.moveTo(satBadgeX, satBadgeY - hs);
782
- ctx.lineTo(satBadgeX + hs, satBadgeY);
783
- ctx.lineTo(satBadgeX, satBadgeY + hs);
784
- ctx.lineTo(satBadgeX - hs, satBadgeY);
785
- ctx.closePath();
786
- ctx.fill();
787
- } else if (satStatus === "critical") {
788
- const hs = satBadgeSize / 2;
789
- ctx.beginPath();
790
- ctx.moveTo(satBadgeX, satBadgeY + hs);
791
- ctx.lineTo(satBadgeX + hs, satBadgeY - hs);
792
- ctx.lineTo(satBadgeX - hs, satBadgeY - hs);
793
- ctx.closePath();
794
- ctx.fill();
795
- } else {
796
- ctx.beginPath();
797
- ctx.arc(satBadgeX, satBadgeY, satBadgeSize / 3, 0, Math.PI * 2);
798
- ctx.fill();
799
- }
800
- ctx.restore();
801
- ctx.fillStyle = "#fff";
802
- ctx.font = 'bold 10px "Inter", system-ui, sans-serif';
803
- ctx.textAlign = "center";
804
- ctx.fillText(sat.name, cx, cy - satHalf - 6);
805
- });
806
- if (showLegend && (allSatellites.length > 0 || groundStations.length > 0)) {
807
- const legendX = 10;
808
- const legendY = 10;
809
- const lineHeight = 14;
810
- const items = [];
811
- allSatellites.forEach((sat, idx) => {
812
- const c = sat.color || DEFAULT_TRACK_COLORS[idx % DEFAULT_TRACK_COLORS.length];
813
- items.push({ color: c, label: sat.name });
814
- });
815
- if (groundStations.length > 0) {
816
- items.push({ color: STATUS_COLOR_MAP.standby, label: `${groundStations.length} Ground Station${groundStations.length > 1 ? "s" : ""}` });
817
- }
818
- const maxLabelWidth = Math.max(...items.map((item) => ctx.measureText(item.label).width)) + 28;
819
- const legendH = items.length * lineHeight + 10;
820
- ctx.fillStyle = "rgba(0, 0, 0, 0.6)";
821
- ctx.strokeStyle = "rgba(100, 116, 139, 0.3)";
822
- ctx.lineWidth = 1;
823
- const r = 4;
824
- ctx.beginPath();
825
- ctx.moveTo(legendX + r, legendY);
826
- ctx.lineTo(legendX + maxLabelWidth - r, legendY);
827
- ctx.arcTo(legendX + maxLabelWidth, legendY, legendX + maxLabelWidth, legendY + r, r);
828
- ctx.lineTo(legendX + maxLabelWidth, legendY + legendH - r);
829
- ctx.arcTo(legendX + maxLabelWidth, legendY + legendH, legendX + maxLabelWidth - r, legendY + legendH, r);
830
- ctx.lineTo(legendX + r, legendY + legendH);
831
- ctx.arcTo(legendX, legendY + legendH, legendX, legendY + legendH - r, r);
832
- ctx.lineTo(legendX, legendY + r);
833
- ctx.arcTo(legendX, legendY, legendX + r, legendY, r);
834
- ctx.closePath();
835
- ctx.fill();
836
- ctx.stroke();
837
- items.forEach((item, i) => {
838
- const iy = legendY + 8 + i * lineHeight;
839
- ctx.fillStyle = item.color;
840
- ctx.beginPath();
841
- ctx.arc(legendX + 10, iy + 3, 4, 0, Math.PI * 2);
842
- ctx.fill();
843
- ctx.fillStyle = "rgba(255, 255, 255, 0.85)";
844
- ctx.font = '10px "Inter", system-ui, sans-serif';
845
- ctx.textAlign = "left";
846
- ctx.fillText(item.label, legendX + 20, iy + 7);
847
- });
848
- }
849
- if (pins && pins.length > 0) {
850
- pins.forEach((pin) => {
851
- const px = lonToX(pin.longitude, W);
852
- const py = latToY(pin.latitude, H);
853
- const pinColor = pin.color || tokens.colors.accent.primary;
854
- ctx.save();
855
- ctx.shadowColor = "rgba(0,0,0,0.4)";
856
- ctx.shadowBlur = 6;
857
- ctx.shadowOffsetY = 2;
858
- ctx.beginPath();
859
- ctx.arc(px, py - 10, 6, Math.PI, 0);
860
- ctx.lineTo(px, py);
861
- ctx.closePath();
862
- ctx.fillStyle = pinColor;
863
- ctx.fill();
864
- ctx.restore();
865
- ctx.beginPath();
866
- ctx.arc(px, py - 10, 2.5, 0, Math.PI * 2);
867
- ctx.fillStyle = "#0d1323";
868
- ctx.fill();
869
- if (pin.label) {
870
- ctx.fillStyle = "rgba(255,255,255,0.9)";
871
- ctx.font = '10px "Inter", system-ui, sans-serif';
872
- ctx.textAlign = "center";
873
- ctx.fillText(pin.label, px, py - 20);
874
- }
875
- });
876
- }
877
- if (allSatellites.length === 0 && groundStations.length === 0 && (!pins || pins.length === 0)) {
878
- ctx.fillStyle = COLORS.text;
879
- ctx.font = '12px "Inter", system-ui, sans-serif';
880
- ctx.textAlign = "center";
881
- ctx.fillText(emptyMessage, W / 2, H / 2);
882
- }
883
- }, [allSatellites, groundStations, height, showTerminator, terminatorTime, showGrid, showLegend, showEquator, lonToX, latToY, COLORS, emptyMessage, pins, lightSources, tokens.colors.accent.primary]);
884
- const handleMouseMove = useCallback((e) => {
885
- const canvas = canvasRef.current;
886
- if (!canvas) return;
887
- const rect = canvas.getBoundingClientRect();
888
- const mx = e.clientX - rect.left;
889
- const my = e.clientY - rect.top;
890
- const W = rect.width;
891
- const H = typeof height === "number" ? height : rect.height;
892
- for (const sat of allSatellites) {
893
- if (!sat.groundTrack.length) continue;
894
- const futureIdx = sat.futureTrackIndex ?? sat.groundTrack.length;
895
- const currentIdx = futureIdx > 0 ? Math.min(futureIdx - 1, sat.groundTrack.length - 1) : sat.groundTrack.length - 1;
896
- const lastPt = sat.groundTrack[currentIdx];
897
- const sx = lonToX(lastPt.longitude, W);
898
- const sy = latToY(lastPt.latitude, H);
899
- const dist = Math.sqrt((mx - sx) ** 2 + (my - sy) ** 2);
900
- if (dist < 18) {
901
- const satStatus = sat.status || "normal";
902
- setTooltip({
903
- x: e.clientX - rect.left + 15,
904
- y: e.clientY - rect.top - 10,
905
- type: "satellite",
906
- name: sat.name,
907
- status: satStatus,
908
- statusColor: STATUS_COLOR_MAP[satStatus] || STATUS_COLOR_MAP.normal,
909
- lat: lastPt.latitude,
910
- lon: lastPt.longitude,
911
- alt: lastPt.altitude,
912
- trackColor: sat.color || DEFAULT_TRACK_COLORS[allSatellites.indexOf(sat) % DEFAULT_TRACK_COLORS.length],
913
- hasFootprint: sat.showFootprint,
914
- futurePoints: sat.groundTrack.length - futureIdx,
915
- pastPoints: futureIdx
916
- });
917
- return;
918
- }
919
- }
920
- for (const gs of groundStations) {
921
- const gx = lonToX(gs.longitude, W);
922
- const gy = latToY(gs.latitude, H);
923
- const dist = Math.sqrt((mx - gx) ** 2 + (my - gy) ** 2);
924
- if (dist < 16) {
925
- const enhanced = gs;
926
- const gsStatus = enhanced.status || "standby";
927
- setTooltip({
928
- x: e.clientX - rect.left + 15,
929
- y: e.clientY - rect.top - 10,
930
- type: "station",
931
- name: gs.name,
932
- status: gsStatus,
933
- statusColor: STATUS_COLOR_MAP[gsStatus] || STATUS_COLOR_MAP.standby,
934
- lat: gs.latitude,
935
- lon: gs.longitude,
936
- stationType: enhanced.type || "dish",
937
- network: "network" in gs && gs.network ? String(gs.network) : void 0,
938
- coverageRadius: enhanced.coverageRadius
939
- });
940
- return;
941
- }
942
- }
943
- setTooltip(null);
944
- }, [allSatellites, groundStations, height, lonToX, latToY]);
945
- const handleClick = useCallback((e) => {
946
- const canvas = canvasRef.current;
947
- if (!canvas) return;
948
- const rect = canvas.getBoundingClientRect();
949
- const mx = e.clientX - rect.left;
950
- const my = e.clientY - rect.top;
951
- const W = rect.width;
952
- const H = typeof height === "number" ? height : rect.height;
953
- for (const sat of allSatellites) {
954
- if (!sat.groundTrack.length) continue;
955
- const futureIdx = sat.futureTrackIndex ?? sat.groundTrack.length;
956
- const currentIdx = futureIdx > 0 ? Math.min(futureIdx - 1, sat.groundTrack.length - 1) : sat.groundTrack.length - 1;
957
- const currentPt = sat.groundTrack[currentIdx];
958
- const sx = lonToX(currentPt.longitude, W);
959
- const sy = latToY(currentPt.latitude, H);
960
- if (Math.sqrt((mx - sx) ** 2 + (my - sy) ** 2) < 15) {
961
- onSatelliteClick == null ? void 0 : onSatelliteClick(sat.id);
962
- return;
963
- }
964
- }
965
- for (const gs of groundStations) {
966
- const gx = lonToX(gs.longitude, W);
967
- const gy = latToY(gs.latitude, H);
968
- if (Math.sqrt((mx - gx) ** 2 + (my - gy) ** 2) < 12) {
969
- onStationClick == null ? void 0 : onStationClick("id" in gs ? gs.id : gs.name);
970
- return;
971
- }
972
- }
973
- }, [allSatellites, groundStations, height, lonToX, latToY, onSatelliteClick, onStationClick]);
974
- useEffect(() => {
975
- draw();
976
- window.addEventListener("resize", draw);
977
- return () => window.removeEventListener("resize", draw);
978
- }, [draw]);
979
- const resolvedMinHeightForLeaflet = minHeight || (typeof height === "number" ? `${height}px` : "400px");
980
- if (mapProvider === "leaflet") {
981
- if (LeafletMap) {
982
- return /* @__PURE__ */ jsx(
983
- "div",
984
- {
985
- role: "img",
986
- "aria-label": allSatellites.length > 0 || groundStations.length > 0 ? "Ground track map showing satellite tracks and ground stations" : "Ground track map",
987
- style: {
988
- display: "block",
989
- width: width ?? "100%",
990
- height: typeof height === "string" ? height : void 0,
991
- minHeight: resolvedMinHeightForLeaflet
992
- },
993
- children: /* @__PURE__ */ jsx(
994
- LeafletMap,
995
- {
996
- allSatellites,
997
- groundStations,
998
- showTerminator,
999
- terminatorTime,
1000
- showGrid,
1001
- showLegend,
1002
- showEquator,
1003
- showRecenterButton,
1004
- showMapStyleToggle,
1005
- defaultCenter: leafletDefaultCenter,
1006
- defaultZoom: leafletDefaultZoom,
1007
- height,
1008
- width,
1009
- minHeight,
1010
- emptyMessage,
1011
- tileUrl,
1012
- nightTileUrl,
1013
- lightSources,
1014
- className,
1015
- onSatelliteClick,
1016
- onStationClick,
1017
- pins,
1018
- pinsEditable,
1019
- onPinAdd,
1020
- onPinUpdate,
1021
- onPinRemove,
1022
- customLayers,
1023
- onLayerChange,
1024
- showCelestialMarkers
1025
- }
1026
- )
1027
- }
1028
- );
1029
- }
1030
- if (!leafletFailed) {
1031
- return /* @__PURE__ */ jsx(
1032
- "div",
1033
- {
1034
- className: `zendir-ground-track-map ${className}`,
1035
- style: {
1036
- width,
1037
- height: typeof height === "string" ? height : void 0,
1038
- minHeight: resolvedMinHeightForLeaflet,
1039
- backgroundColor: tokens.colors.background.base,
1040
- borderRadius: 8,
1041
- display: "flex",
1042
- alignItems: "center",
1043
- justifyContent: "center"
1044
- },
1045
- children: /* @__PURE__ */ jsx("span", { style: { color: tokens.colors.text.secondary, fontSize: 14 }, children: "Loading map…" })
1046
- }
1047
- );
1048
- }
1049
- }
1050
- const resolvedMinHeight = minHeight || (typeof height === "number" ? `${height}px` : "400px");
1051
- if (isLoading) {
1052
- return /* @__PURE__ */ jsx(
1053
- "div",
1054
- {
1055
- className: `zendir-ground-track-map ${className}`,
1056
- style: {
1057
- width,
1058
- minHeight: resolvedMinHeight,
1059
- backgroundColor: COLORS.background,
1060
- borderRadius: "8px",
1061
- display: "flex",
1062
- alignItems: "center",
1063
- justifyContent: "center"
1064
- },
1065
- children: /* @__PURE__ */ jsx("span", { style: { color: COLORS.text, fontSize: "14px", letterSpacing: "1px" }, children: "Loading…" })
1066
- }
1067
- );
1068
- }
1069
- return /* @__PURE__ */ jsxs(
1070
- "div",
1071
- {
1072
- ref: containerRef,
1073
- role: "img",
1074
- "aria-label": allSatellites.length > 0 || groundStations.length > 0 ? "Ground track map showing satellite tracks and ground stations" : "Ground track map",
1075
- className: `zendir-ground-track-map ${className}`,
1076
- style: { width, height: typeof height === "string" ? height : void 0, minHeight: resolvedMinHeight, backgroundColor: COLORS.background, borderRadius: "8px", overflow: "hidden", position: "relative" },
1077
- "data-map-backend": "canvas",
1078
- children: [
1079
- mapProvider === "leaflet" && leafletFailed && /* @__PURE__ */ jsxs(
1080
- "div",
1081
- {
1082
- style: {
1083
- position: "absolute",
1084
- bottom: 8,
1085
- left: 8,
1086
- zIndex: 100,
1087
- padding: "4px 8px",
1088
- background: "rgba(0,0,0,0.75)",
1089
- color: "#fce83a",
1090
- fontSize: 11,
1091
- borderRadius: 4
1092
- },
1093
- children: [
1094
- "Static view (install ",
1095
- /* @__PURE__ */ jsx("code", { style: { fontFamily: "monospace" }, children: "leaflet" }),
1096
- " for zoom/pan)"
1097
- ]
1098
- }
1099
- ),
1100
- showRecenterButton && /* @__PURE__ */ jsx(
1101
- "button",
1102
- {
1103
- type: "button",
1104
- onClick: handleRecenter,
1105
- title: "Recenter Map",
1106
- "aria-label": "Recenter map",
1107
- style: {
1108
- position: "absolute",
1109
- top: "8px",
1110
- right: "8px",
1111
- zIndex: 20,
1112
- background: "rgba(30, 41, 59, 0.85)",
1113
- border: "1px solid rgba(100, 116, 139, 0.4)",
1114
- borderRadius: "4px",
1115
- color: "#fff",
1116
- cursor: "pointer",
1117
- padding: "4px 8px",
1118
- fontSize: "12px",
1119
- lineHeight: "1",
1120
- display: "flex",
1121
- alignItems: "center",
1122
- gap: "4px"
1123
- },
1124
- children: /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1125
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
1126
- /* @__PURE__ */ jsx("path", { d: "M12 2v4M12 18v4M2 12h4M18 12h4" })
1127
- ] })
1128
- }
1129
- ),
1130
- /* @__PURE__ */ jsx(
1131
- "canvas",
1132
- {
1133
- ref: canvasRef,
1134
- style: { display: "block", cursor: tooltip ? "pointer" : "default" },
1135
- onMouseMove: handleMouseMove,
1136
- onMouseLeave: () => setTooltip(null),
1137
- onClick: handleClick
1138
- }
1139
- ),
1140
- tooltip && /* @__PURE__ */ jsxs(
1141
- "div",
1142
- {
1143
- style: {
1144
- position: "absolute",
1145
- left: tooltip.x,
1146
- top: tooltip.y,
1147
- background: "rgba(15, 23, 42, 0.95)",
1148
- backdropFilter: "blur(8px)",
1149
- border: `1px solid ${tooltip.statusColor}40`,
1150
- borderRadius: "8px",
1151
- padding: 0,
1152
- pointerEvents: "none",
1153
- zIndex: 10,
1154
- color: "rgba(255, 255, 255, 0.9)",
1155
- fontSize: "11px",
1156
- fontFamily: '"Inter", system-ui, sans-serif',
1157
- lineHeight: "1.5",
1158
- boxShadow: `0 4px 16px rgba(0, 0, 0, 0.5), 0 0 12px ${tooltip.statusColor}15`,
1159
- minWidth: 180,
1160
- overflow: "hidden"
1161
- },
1162
- children: [
1163
- /* @__PURE__ */ jsxs("div", { style: {
1164
- padding: "6px 10px",
1165
- borderBottom: `1px solid ${tooltip.statusColor}25`,
1166
- background: `${tooltip.statusColor}10`,
1167
- display: "flex",
1168
- alignItems: "center",
1169
- gap: 6
1170
- }, children: [
1171
- /* @__PURE__ */ jsx("span", { style: {
1172
- width: 8,
1173
- height: 8,
1174
- borderRadius: tooltip.status === "caution" ? 1 : "50%",
1175
- background: tooltip.statusColor,
1176
- display: "inline-block",
1177
- flexShrink: 0,
1178
- boxShadow: `0 0 4px ${tooltip.statusColor}88`
1179
- } }),
1180
- /* @__PURE__ */ jsx("span", { style: { fontWeight: 600, fontSize: 12 }, children: tooltip.name }),
1181
- /* @__PURE__ */ jsx("span", { style: {
1182
- marginLeft: "auto",
1183
- fontSize: 9,
1184
- fontWeight: 600,
1185
- textTransform: "uppercase",
1186
- color: tooltip.statusColor,
1187
- letterSpacing: "0.5px"
1188
- }, children: tooltip.status })
1189
- ] }),
1190
- /* @__PURE__ */ jsxs("div", { style: { padding: "6px 10px", display: "flex", flexDirection: "column", gap: 3 }, children: [
1191
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 2 }, children: [
1192
- /* @__PURE__ */ jsx("span", { style: {
1193
- fontSize: 9,
1194
- fontWeight: 600,
1195
- padding: "1px 5px",
1196
- borderRadius: 3,
1197
- textTransform: "uppercase",
1198
- letterSpacing: "0.3px",
1199
- background: tooltip.type === "satellite" ? "rgba(45, 204, 255, 0.15)" : "rgba(86, 240, 0, 0.15)",
1200
- color: tooltip.type === "satellite" ? "#2dccff" : "#56f000"
1201
- }, children: tooltip.type === "satellite" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1202
- /* @__PURE__ */ jsx(AstroIcon, { name: "satellite", size: "extra-small", label: "Satellite", style: { marginRight: 4, verticalAlign: "middle" } }),
1203
- "Satellite"
1204
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1205
- /* @__PURE__ */ jsx(AstroIcon, { name: "antenna", size: "extra-small", label: tooltip.stationType || "Ground station", style: { marginRight: 4, verticalAlign: "middle" } }),
1206
- tooltip.stationType || "dish"
1207
- ] }) }),
1208
- tooltip.network && /* @__PURE__ */ jsx("span", { style: {
1209
- fontSize: 9,
1210
- padding: "1px 5px",
1211
- borderRadius: 3,
1212
- background: "rgba(157, 112, 255, 0.15)",
1213
- color: "#9D70FF"
1214
- }, children: tooltip.network })
1215
- ] }),
1216
- /* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "52px 1fr", gap: "2px 8px", fontSize: 10 }, children: [
1217
- /* @__PURE__ */ jsx("span", { style: { color: "rgba(255,255,255,0.5)" }, children: "Lat" }),
1218
- /* @__PURE__ */ jsxs("span", { style: { fontFamily: '"Roboto Mono", monospace', fontWeight: 500 }, children: [
1219
- tooltip.lat >= 0 ? "+" : "",
1220
- tooltip.lat.toFixed(4),
1221
- "°"
1222
- ] }),
1223
- /* @__PURE__ */ jsx("span", { style: { color: "rgba(255,255,255,0.5)" }, children: "Lon" }),
1224
- /* @__PURE__ */ jsxs("span", { style: { fontFamily: '"Roboto Mono", monospace', fontWeight: 500 }, children: [
1225
- tooltip.lon >= 0 ? "+" : "",
1226
- tooltip.lon.toFixed(4),
1227
- "°"
1228
- ] }),
1229
- tooltip.alt !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
1230
- /* @__PURE__ */ jsx("span", { style: { color: "rgba(255,255,255,0.5)" }, children: "Altitude" }),
1231
- /* @__PURE__ */ jsxs("span", { style: { fontFamily: '"Roboto Mono", monospace', fontWeight: 500 }, children: [
1232
- tooltip.alt.toFixed(1),
1233
- " km"
1234
- ] })
1235
- ] }),
1236
- tooltip.coverageRadius !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
1237
- /* @__PURE__ */ jsx("span", { style: { color: "rgba(255,255,255,0.5)" }, children: "Coverage" }),
1238
- /* @__PURE__ */ jsxs("span", { style: { fontFamily: '"Roboto Mono", monospace', fontWeight: 500 }, children: [
1239
- tooltip.coverageRadius.toFixed(1),
1240
- "° radius"
1241
- ] })
1242
- ] })
1243
- ] }),
1244
- tooltip.type === "satellite" && (tooltip.pastPoints !== void 0 || tooltip.futurePoints !== void 0) && /* @__PURE__ */ jsxs("div", { style: { marginTop: 3, paddingTop: 3, borderTop: "1px solid rgba(255,255,255,0.08)", display: "flex", gap: 10, fontSize: 9 }, children: [
1245
- tooltip.pastPoints !== void 0 && /* @__PURE__ */ jsxs("span", { children: [
1246
- /* @__PURE__ */ jsx("span", { style: { color: tooltip.trackColor }, children: "" }),
1247
- " ",
1248
- tooltip.pastPoints,
1249
- " past pts"
1250
- ] }),
1251
- tooltip.futurePoints !== void 0 && tooltip.futurePoints > 0 && /* @__PURE__ */ jsxs("span", { children: [
1252
- /* @__PURE__ */ jsx("span", { style: { color: `${tooltip.trackColor}88` }, children: "╌" }),
1253
- " ",
1254
- tooltip.futurePoints,
1255
- " predicted"
1256
- ] }),
1257
- tooltip.hasFootprint && /* @__PURE__ */ jsx("span", { style: { color: "#9D70FF" }, children: "◎ Footprint" })
1258
- ] })
1259
- ] })
1260
- ]
1261
- }
1262
- )
1263
- ]
1264
- }
1265
- );
145
+ function GroundTrackMap({ groundTrack, satellites = [], groundStations = [], accessMask, teamPaths, showTerminator = true, terminatorTime, showGrid = true, showLegend = true, showEquator = true, showRecenterButton = false, showMapStyleToggle = true, isLoading = false, emptyMessage = "No orbital data available", width = "100%", height = "100%", minHeight = "400px", defaultCenter, defaultZoom, className = "", onSatelliteClick, onStationClick, mapProvider = "leaflet", tileUrl, nightTileUrl, lightSources, pins, pinsEditable = false, onPinAdd, onPinUpdate, onPinRemove, customLayers, onLayerChange, showCelestialMarkers }) {
146
+ const { tokens } = useTheme();
147
+ const canvasRef = useRef(null);
148
+ const containerRef = useRef(null);
149
+ const [tooltip, setTooltip] = useState(null);
150
+ const [_viewOffset, setViewOffset] = useState({
151
+ x: 0,
152
+ y: 0
153
+ });
154
+ const allSatellites = useMemo(() => {
155
+ if (satellites.length > 0) return satellites;
156
+ if (teamPaths && teamPaths.length > 0) return teamPaths.map((tp, idx) => ({
157
+ id: `team-${idx}`,
158
+ name: tp.name,
159
+ color: tp.color,
160
+ status: "normal",
161
+ groundTrack: tp.positions.filter((p) => p.lat != null && p.lng != null).map((p) => ({
162
+ latitude: p.lat,
163
+ longitude: p.lng,
164
+ altitude: p.altitude ?? 0,
165
+ timestamp: p.time != null ? (/* @__PURE__ */ new Date(p.time * 1e3)).toISOString() : (/* @__PURE__ */ new Date()).toISOString()
166
+ }))
167
+ }));
168
+ if (groundTrack && groundTrack.length > 0) return [{
169
+ id: "primary",
170
+ name: "Satellite",
171
+ groundTrack,
172
+ status: "normal",
173
+ accessMask
174
+ }];
175
+ return [];
176
+ }, [
177
+ satellites,
178
+ groundTrack,
179
+ accessMask,
180
+ teamPaths
181
+ ]);
182
+ const leafletDefaultCenter = useMemo(() => defaultCenter ?? [20, 0], [defaultCenter]);
183
+ const leafletDefaultZoom = useMemo(() => defaultZoom ?? 2, [defaultZoom]);
184
+ const [LeafletMap, setLeafletMap] = useState(null);
185
+ const [leafletFailed, setLeafletFailed] = useState(false);
186
+ useEffect(() => {
187
+ if (mapProvider !== "leaflet") return;
188
+ import("./GroundTrackMapLeaflet.js").then((m) => setLeafletMap(() => m.GroundTrackMapLeaflet)).catch((err) => {
189
+ console.warn("[GroundTrackMap] Leaflet backend failed to load. Install \"leaflet\" and ensure it is resolvable. Falling back to static canvas.", err);
190
+ setLeafletFailed(true);
191
+ });
192
+ }, [mapProvider]);
193
+ const handleRecenter = useCallback(() => {
194
+ setViewOffset({
195
+ x: 0,
196
+ y: 0
197
+ });
198
+ }, []);
199
+ const COLORS = useMemo(() => ({
200
+ background: tokens.colors.background.base,
201
+ grid: tokens.colors.border.muted,
202
+ text: tokens.colors.text.secondary,
203
+ textPrimary: tokens.colors.text.primary,
204
+ borderMuted: tokens.colors.border.muted,
205
+ accent: tokens.colors.accent.primary,
206
+ land: "rgba(30, 40, 58, 0.92)",
207
+ ocean: tokens.colors.background.base,
208
+ night: "rgba(0, 10, 40, 0.35)",
209
+ equator: "rgba(88, 166, 255, 0.25)"
210
+ }), [tokens]);
211
+ const lonToX = useCallback((lon, w) => {
212
+ let normalizedLon = lon;
213
+ while (normalizedLon > 180) normalizedLon -= 360;
214
+ while (normalizedLon < -180) normalizedLon += 360;
215
+ return (normalizedLon + 180) / 360 * w;
216
+ }, []);
217
+ const latToY = useCallback((lat, h) => {
218
+ return (90 - lat) / 180 * h;
219
+ }, []);
220
+ const draw = useCallback(() => {
221
+ const canvas = canvasRef.current;
222
+ const container = containerRef.current;
223
+ if (!canvas || !container) return;
224
+ const ctx = canvas.getContext("2d");
225
+ if (!ctx) return;
226
+ const dpr = window.devicePixelRatio || 1;
227
+ const rect = container.getBoundingClientRect();
228
+ const W = rect.width;
229
+ const H = typeof height === "number" ? height : rect.height || 400;
230
+ canvas.width = W * dpr;
231
+ canvas.height = H * dpr;
232
+ canvas.style.width = `${W}px`;
233
+ canvas.style.height = `${H}px`;
234
+ ctx.scale(dpr, dpr);
235
+ ctx.fillStyle = COLORS.background;
236
+ ctx.fillRect(0, 0, W, H);
237
+ if (showTerminator) {
238
+ const now = terminatorTime ?? /* @__PURE__ */ new Date();
239
+ const BAND_STEP = 1.5;
240
+ const MAX_DEP = 30;
241
+ const NIGHT_ALPHA = .38;
242
+ const bandSteps = [];
243
+ for (let d = 0; d <= MAX_DEP; d += BAND_STEP) {
244
+ const t = Math.min(d / 18, 1);
245
+ const alpha = Math.pow(t, 1.6) * (NIGHT_ALPHA * .85) + (d > 18 ? (d - 18) / 12 * (NIGHT_ALPHA * .15) : 0);
246
+ bandSteps.push({
247
+ depression: d,
248
+ alpha: Math.min(alpha, NIGHT_ALPHA)
249
+ });
250
+ }
251
+ bandSteps.push({
252
+ depression: 90,
253
+ alpha: NIGHT_ALPHA
254
+ });
255
+ let prevAlpha = 0;
256
+ for (const zone of bandSteps) {
257
+ const dep = Math.min(zone.depression, 89);
258
+ const bandAlpha = zone.alpha - prevAlpha;
259
+ if (bandAlpha < .002) {
260
+ prevAlpha = zone.alpha;
261
+ continue;
262
+ }
263
+ const terminator = calculateTerminatorContinuous(now, dep);
264
+ if (terminator.sunset.length === 0) {
265
+ prevAlpha = zone.alpha;
266
+ continue;
267
+ }
268
+ const poly = buildNightPolygon(terminator);
269
+ if (poly.length < 3) {
270
+ prevAlpha = zone.alpha;
271
+ continue;
272
+ }
273
+ ctx.fillStyle = `rgba(42, 58, 82, ${bandAlpha.toFixed(4)})`;
274
+ for (const offset of [
275
+ -360,
276
+ 0,
277
+ 360
278
+ ]) {
279
+ ctx.beginPath();
280
+ for (let i = 0; i < poly.length; i++) {
281
+ const lat = poly[i][0];
282
+ const lon = poly[i][1] + offset;
283
+ const x = lonToX(lon, W);
284
+ const y = latToY(lat, H);
285
+ if (i === 0) ctx.moveTo(x, y);
286
+ else ctx.lineTo(x, y);
287
+ }
288
+ ctx.closePath();
289
+ ctx.fill();
290
+ }
291
+ prevAlpha = zone.alpha;
292
+ }
293
+ const terminatorEdge = calculateTerminatorContinuous(now, 0);
294
+ if (terminatorEdge.sunset.length > 2) {
295
+ ctx.save();
296
+ ctx.strokeStyle = "rgba(160, 160, 160, 0.08)";
297
+ ctx.lineWidth = 3;
298
+ for (const curve of [terminatorEdge.sunset, terminatorEdge.sunrise]) for (const offset of [
299
+ -360,
300
+ 0,
301
+ 360
302
+ ]) {
303
+ ctx.beginPath();
304
+ for (let i = 0; i < curve.length; i++) {
305
+ const x = lonToX(curve[i][1] + offset, W);
306
+ const y = latToY(curve[i][0], H);
307
+ if (i === 0) ctx.moveTo(x, y);
308
+ else ctx.lineTo(x, y);
309
+ }
310
+ ctx.stroke();
311
+ }
312
+ ctx.strokeStyle = "rgba(168, 168, 168, 0.30)";
313
+ ctx.lineWidth = 1;
314
+ for (const curve of [terminatorEdge.sunset, terminatorEdge.sunrise]) for (const offset of [
315
+ -360,
316
+ 0,
317
+ 360
318
+ ]) {
319
+ ctx.beginPath();
320
+ for (let i = 0; i < curve.length; i++) {
321
+ const x = lonToX(curve[i][1] + offset, W);
322
+ const y = latToY(curve[i][0], H);
323
+ if (i === 0) ctx.moveTo(x, y);
324
+ else ctx.lineTo(x, y);
325
+ }
326
+ ctx.stroke();
327
+ }
328
+ ctx.restore();
329
+ }
330
+ }
331
+ ctx.fillStyle = COLORS.land;
332
+ ctx.strokeStyle = "rgba(100, 120, 160, 0.25)";
333
+ ctx.lineWidth = 1;
334
+ for (const pathStr of WORLD_MAP_PATHS) {
335
+ const commands = pathStr.split(" ");
336
+ ctx.beginPath();
337
+ for (const cmd of commands) if (cmd.startsWith("M") || cmd.startsWith("L")) {
338
+ const coords = cmd.substring(1).split(",");
339
+ const lon = parseFloat(coords[0]);
340
+ const lat = parseFloat(coords[1]);
341
+ const x = lonToX(lon, W);
342
+ const y = latToY(lat, H);
343
+ if (cmd.startsWith("M")) ctx.moveTo(x, y);
344
+ else ctx.lineTo(x, y);
345
+ } else if (cmd === "Z") ctx.closePath();
346
+ ctx.fill();
347
+ ctx.stroke();
348
+ }
349
+ if (showGrid) {
350
+ ctx.strokeStyle = COLORS.grid;
351
+ ctx.lineWidth = .4;
352
+ for (let lon = -180; lon <= 180; lon += 30) {
353
+ const x = lonToX(lon, W);
354
+ ctx.beginPath();
355
+ ctx.moveTo(x, 0);
356
+ ctx.lineTo(x, H);
357
+ ctx.stroke();
358
+ }
359
+ for (let lat = -90; lat <= 90; lat += 30) {
360
+ const y = latToY(lat, H);
361
+ ctx.beginPath();
362
+ ctx.moveTo(0, y);
363
+ ctx.lineTo(W, y);
364
+ ctx.stroke();
365
+ }
366
+ }
367
+ if (showEquator) {
368
+ const eqY = latToY(0, H);
369
+ ctx.strokeStyle = COLORS.equator;
370
+ ctx.lineWidth = 1;
371
+ ctx.setLineDash([6, 4]);
372
+ ctx.beginPath();
373
+ ctx.moveTo(0, eqY);
374
+ ctx.lineTo(W, eqY);
375
+ ctx.stroke();
376
+ ctx.setLineDash([]);
377
+ }
378
+ for (const gs of groundStations) {
379
+ const enhanced = gs;
380
+ if (enhanced.showCoverage && enhanced.coverageRadius) {
381
+ const statusColor = STATUS_COLOR_MAP[enhanced.status || "standby"] || STATUS_COLOR_MAP.standby;
382
+ drawCoverageEllipse(ctx, gs.latitude, gs.longitude, enhanced.coverageRadius, W, H, lonToX, latToY);
383
+ ctx.fillStyle = `${statusColor}15`;
384
+ ctx.fill();
385
+ ctx.strokeStyle = `${statusColor}40`;
386
+ ctx.lineWidth = 1;
387
+ ctx.setLineDash([4, 3]);
388
+ ctx.stroke();
389
+ ctx.setLineDash([]);
390
+ }
391
+ }
392
+ for (const gs of groundStations) {
393
+ const enhanced = gs;
394
+ const x = lonToX(gs.longitude, W);
395
+ const y = latToY(gs.latitude, H);
396
+ const status = enhanced.status || "standby";
397
+ const statusColor = STATUS_COLOR_MAP[status] || STATUS_COLOR_MAP.standby;
398
+ const stationType = enhanced.type || "dish";
399
+ const iconSize = 20;
400
+ const halfIcon = iconSize / 2;
401
+ ctx.save();
402
+ ctx.translate(x, y);
403
+ ctx.beginPath();
404
+ ctx.arc(0, 0, 12, 0, Math.PI * 2);
405
+ ctx.fillStyle = `${statusColor}18`;
406
+ ctx.fill();
407
+ ctx.beginPath();
408
+ ctx.arc(0, 0, halfIcon, 0, Math.PI * 2);
409
+ ctx.fillStyle = "rgba(15, 23, 42, 0.85)";
410
+ ctx.fill();
411
+ ctx.strokeStyle = `${statusColor}66`;
412
+ ctx.lineWidth = 1.5;
413
+ ctx.stroke();
414
+ ctx.save();
415
+ const iconScale = iconSize / 24;
416
+ ctx.translate(-10, -10);
417
+ ctx.scale(iconScale, iconScale);
418
+ if (stationType === "dish" || stationType === "omni") {
419
+ ctx.strokeStyle = statusColor;
420
+ ctx.fillStyle = statusColor;
421
+ ctx.lineWidth = 2 / iconScale;
422
+ ctx.beginPath();
423
+ ctx.arc(12, 12, 10, Math.PI * 1.25, Math.PI * 1.75);
424
+ ctx.stroke();
425
+ ctx.beginPath();
426
+ ctx.arc(12, 12, 6.5, Math.PI * 1.2, Math.PI * 1.8);
427
+ ctx.stroke();
428
+ ctx.beginPath();
429
+ ctx.arc(12, 12, 3, Math.PI * 1.15, Math.PI * 1.85);
430
+ ctx.stroke();
431
+ ctx.beginPath();
432
+ ctx.arc(12, 12, 1.5, 0, Math.PI * 2);
433
+ ctx.fill();
434
+ ctx.lineWidth = 1.5 / iconScale;
435
+ ctx.beginPath();
436
+ ctx.moveTo(12, 14);
437
+ ctx.lineTo(12, 20);
438
+ ctx.stroke();
439
+ ctx.beginPath();
440
+ ctx.moveTo(8, 20);
441
+ ctx.lineTo(16, 20);
442
+ ctx.stroke();
443
+ } else if (stationType === "phased-array") {
444
+ ctx.strokeStyle = statusColor;
445
+ ctx.fillStyle = statusColor;
446
+ ctx.lineWidth = 1.5 / iconScale;
447
+ ctx.strokeRect(5, 4, 14, 12);
448
+ for (let gx = 8; gx <= 16; gx += 4) {
449
+ ctx.beginPath();
450
+ ctx.moveTo(gx, 4);
451
+ ctx.lineTo(gx, 16);
452
+ ctx.stroke();
453
+ }
454
+ for (let gy = 8; gy <= 12; gy += 4) {
455
+ ctx.beginPath();
456
+ ctx.moveTo(5, gy);
457
+ ctx.lineTo(19, gy);
458
+ ctx.stroke();
459
+ }
460
+ ctx.beginPath();
461
+ ctx.moveTo(12, 16);
462
+ ctx.lineTo(12, 21);
463
+ ctx.stroke();
464
+ ctx.beginPath();
465
+ ctx.moveTo(8, 21);
466
+ ctx.lineTo(16, 21);
467
+ ctx.stroke();
468
+ } else {
469
+ ctx.strokeStyle = statusColor;
470
+ ctx.fillStyle = statusColor;
471
+ ctx.lineWidth = 2 / iconScale;
472
+ ctx.beginPath();
473
+ ctx.arc(10, 12, 7, Math.PI * .8, Math.PI * 1.7);
474
+ ctx.stroke();
475
+ ctx.lineWidth = 1.5 / iconScale;
476
+ ctx.beginPath();
477
+ ctx.moveTo(10, 12);
478
+ ctx.lineTo(16, 6);
479
+ ctx.stroke();
480
+ ctx.lineWidth = 1.2 / iconScale;
481
+ ctx.beginPath();
482
+ ctx.arc(17, 5, 2.5, Math.PI * 1.2, Math.PI * 1.7);
483
+ ctx.stroke();
484
+ ctx.beginPath();
485
+ ctx.arc(17, 5, 4.5, Math.PI * 1.15, Math.PI * 1.75);
486
+ ctx.stroke();
487
+ ctx.lineWidth = 1.5 / iconScale;
488
+ ctx.beginPath();
489
+ ctx.moveTo(10, 14);
490
+ ctx.lineTo(10, 20);
491
+ ctx.stroke();
492
+ ctx.beginPath();
493
+ ctx.moveTo(6, 20);
494
+ ctx.lineTo(14, 20);
495
+ ctx.stroke();
496
+ }
497
+ ctx.restore();
498
+ const badgeX = -9;
499
+ const badgeY = -9;
500
+ const badgeSize = 7;
501
+ ctx.beginPath();
502
+ ctx.arc(badgeX, badgeY, 5.5, 0, Math.PI * 2);
503
+ ctx.fillStyle = "rgba(15, 23, 42, 0.9)";
504
+ ctx.fill();
505
+ ctx.fillStyle = statusColor;
506
+ ctx.strokeStyle = statusColor;
507
+ ctx.lineWidth = 1;
508
+ if (status === "normal") {
509
+ ctx.beginPath();
510
+ ctx.arc(badgeX, badgeY, badgeSize / 2, 0, Math.PI * 2);
511
+ ctx.fill();
512
+ } else if (status === "standby") {
513
+ ctx.beginPath();
514
+ ctx.arc(badgeX, badgeY, badgeSize / 2 - .5, 0, Math.PI * 2);
515
+ ctx.lineWidth = 2;
516
+ ctx.stroke();
517
+ } else if (status === "caution") {
518
+ const hs = badgeSize / 2;
519
+ ctx.fillRect(badgeX - hs, badgeY - hs, badgeSize, badgeSize);
520
+ } else if (status === "serious") {
521
+ const hs = badgeSize / 2;
522
+ ctx.beginPath();
523
+ ctx.moveTo(badgeX, badgeY - hs);
524
+ ctx.lineTo(-5.5, badgeY);
525
+ ctx.lineTo(badgeX, -5.5);
526
+ ctx.lineTo(badgeX - hs, badgeY);
527
+ ctx.closePath();
528
+ ctx.fill();
529
+ } else if (status === "critical") {
530
+ const hs = badgeSize / 2;
531
+ ctx.beginPath();
532
+ ctx.moveTo(badgeX, -5.5);
533
+ ctx.lineTo(-5.5, badgeY - hs);
534
+ ctx.lineTo(badgeX - hs, badgeY - hs);
535
+ ctx.closePath();
536
+ ctx.fill();
537
+ } else {
538
+ ctx.beginPath();
539
+ ctx.arc(badgeX, badgeY, badgeSize / 3, 0, Math.PI * 2);
540
+ ctx.fill();
541
+ }
542
+ ctx.restore();
543
+ ctx.fillStyle = COLORS.text;
544
+ ctx.font = "9px \"Inter\", system-ui, sans-serif";
545
+ ctx.textAlign = "center";
546
+ ctx.fillText(gs.name.length > 14 ? gs.name.slice(0, 14) + "…" : gs.name, x, y + halfIcon + 12);
547
+ }
548
+ if (lightSources && lightSources.length > 0 && showTerminator) {
549
+ const now = terminatorTime ?? /* @__PURE__ */ new Date();
550
+ for (const light of lightSources) {
551
+ const lx = lonToX(light.longitude, W);
552
+ const ly = latToY(light.latitude, H);
553
+ const r = light.radius ?? 4;
554
+ const intensity = light.intensity ?? .8;
555
+ const color = light.color ?? "#ffeedd";
556
+ const dayOfYear = Math.floor((now.getTime() - Date.UTC(now.getUTCFullYear(), 0, 0)) / 864e5);
557
+ const decRad = -23.44 * Math.cos(2 * Math.PI / 365 * (dayOfYear + 10)) * Math.PI / 180;
558
+ const lsSubSolarLon = -(now.getUTCHours() + now.getUTCMinutes() / 60 - 12) * 15;
559
+ const latRad = light.latitude * Math.PI / 180;
560
+ const sinAlt = Math.sin(latRad) * Math.sin(decRad) + Math.cos(latRad) * Math.cos(decRad) * Math.cos((light.longitude - lsSubSolarLon) * Math.PI / 180);
561
+ const solarAltDeg = Math.asin(sinAlt) * 180 / Math.PI;
562
+ let nightFactor;
563
+ if (solarAltDeg >= 0) nightFactor = 0;
564
+ else if (solarAltDeg <= -18) nightFactor = 1;
565
+ else nightFactor = Math.min(1, -solarAltDeg / 18);
566
+ if (nightFactor < .01) continue;
567
+ const alpha = intensity * nightFactor;
568
+ const [cr, cg, cb] = hexToRgb(color);
569
+ const grad = ctx.createRadialGradient(lx, ly, 0, lx, ly, r * 2);
570
+ grad.addColorStop(0, `rgba(${cr}, ${cg}, ${cb}, ${alpha})`);
571
+ grad.addColorStop(.4, `rgba(${cr}, ${cg}, ${cb}, ${alpha * .6})`);
572
+ grad.addColorStop(1, `rgba(${cr}, ${cg}, ${cb}, 0)`);
573
+ ctx.fillStyle = grad;
574
+ ctx.beginPath();
575
+ ctx.arc(lx, ly, r * 2, 0, Math.PI * 2);
576
+ ctx.fill();
577
+ ctx.fillStyle = `rgba(${cr}, ${cg}, ${cb}, ${Math.min(1, alpha * 1.3)})`;
578
+ ctx.beginPath();
579
+ ctx.arc(lx, ly, Math.max(1, r * .4), 0, Math.PI * 2);
580
+ ctx.fill();
581
+ }
582
+ }
583
+ allSatellites.forEach((sat, satIdx) => {
584
+ const track = sat.groundTrack;
585
+ if (!track || track.length === 0) return;
586
+ const trackColor = sat.color || DEFAULT_TRACK_COLORS[satIdx % DEFAULT_TRACK_COLORS.length];
587
+ const futureIdx = sat.futureTrackIndex ?? track.length;
588
+ ctx.strokeStyle = trackColor;
589
+ ctx.lineWidth = 2;
590
+ ctx.lineCap = "round";
591
+ ctx.lineJoin = "round";
592
+ ctx.setLineDash([]);
593
+ ctx.beginPath();
594
+ let prevX = lonToX(track[0].longitude, W);
595
+ let prevY = latToY(track[0].latitude, H);
596
+ ctx.moveTo(prevX, prevY);
597
+ for (let i = 1; i < Math.min(futureIdx, track.length); i++) {
598
+ const x = lonToX(track[i].longitude, W);
599
+ const y = latToY(track[i].latitude, H);
600
+ if (Math.abs(x - prevX) > W / 2) {
601
+ ctx.stroke();
602
+ ctx.beginPath();
603
+ ctx.moveTo(x, y);
604
+ } else ctx.lineTo(x, y);
605
+ prevX = x;
606
+ prevY = y;
607
+ }
608
+ ctx.stroke();
609
+ if (futureIdx < track.length) {
610
+ ctx.strokeStyle = `${trackColor}88`;
611
+ ctx.lineWidth = 1.5;
612
+ ctx.setLineDash([6, 4]);
613
+ ctx.beginPath();
614
+ ctx.moveTo(lonToX(track[futureIdx].longitude, W), latToY(track[futureIdx].latitude, H));
615
+ let fpx = lonToX(track[futureIdx].longitude, W);
616
+ for (let i = futureIdx + 1; i < track.length; i++) {
617
+ const x = lonToX(track[i].longitude, W);
618
+ const y = latToY(track[i].latitude, H);
619
+ if (Math.abs(x - fpx) > W / 2) {
620
+ ctx.stroke();
621
+ ctx.beginPath();
622
+ ctx.moveTo(x, y);
623
+ } else ctx.lineTo(x, y);
624
+ fpx = x;
625
+ }
626
+ ctx.stroke();
627
+ ctx.setLineDash([]);
628
+ }
629
+ if (sat.accessMask && sat.accessMask.some(Boolean)) {
630
+ ctx.strokeStyle = STATUS_COLOR_MAP.normal;
631
+ ctx.lineWidth = 3;
632
+ ctx.setLineDash([]);
633
+ ctx.beginPath();
634
+ let drawing = false;
635
+ let apx = lonToX(track[0].longitude, W);
636
+ for (let i = 0; i < track.length; i++) {
637
+ const x = lonToX(track[i].longitude, W);
638
+ const y = latToY(track[i].latitude, H);
639
+ const active = sat.accessMask[i];
640
+ const crossesDateLine = i > 0 && Math.abs(x - apx) > W / 2;
641
+ if (active && !crossesDateLine) if (!drawing) {
642
+ ctx.moveTo(x, y);
643
+ drawing = true;
644
+ } else ctx.lineTo(x, y);
645
+ else if (drawing) {
646
+ ctx.stroke();
647
+ ctx.beginPath();
648
+ drawing = false;
649
+ }
650
+ apx = x;
651
+ }
652
+ if (drawing) ctx.stroke();
653
+ }
654
+ if (sat.passMarkers) for (const marker of sat.passMarkers) {
655
+ const mx = lonToX(marker.longitude, W);
656
+ const my = latToY(marker.latitude, H);
657
+ const markerColor = marker.type === "aos" ? STATUS_COLOR_MAP.normal : STATUS_COLOR_MAP.critical;
658
+ ctx.save();
659
+ ctx.translate(mx, my);
660
+ ctx.beginPath();
661
+ if (marker.type === "aos") {
662
+ ctx.moveTo(0, -6);
663
+ ctx.lineTo(4, 2);
664
+ ctx.lineTo(-4, 2);
665
+ } else {
666
+ ctx.moveTo(0, 6);
667
+ ctx.lineTo(4, -2);
668
+ ctx.lineTo(-4, -2);
669
+ }
670
+ ctx.closePath();
671
+ ctx.fillStyle = markerColor;
672
+ ctx.fill();
673
+ ctx.restore();
674
+ if (marker.label) {
675
+ ctx.fillStyle = markerColor;
676
+ ctx.font = "8px \"Inter\", system-ui, sans-serif";
677
+ ctx.textAlign = "center";
678
+ ctx.fillText(marker.label, mx, marker.type === "aos" ? my - 9 : my + 13);
679
+ }
680
+ }
681
+ if (sat.showFootprint && sat.footprintRadius) {
682
+ const lastPt = futureIdx > 0 ? track[Math.min(futureIdx - 1, track.length - 1)] : track[track.length - 1];
683
+ drawCoverageEllipse(ctx, lastPt.latitude, lastPt.longitude, sat.footprintRadius, W, H, lonToX, latToY);
684
+ ctx.fillStyle = `${trackColor}12`;
685
+ ctx.fill();
686
+ ctx.strokeStyle = `${trackColor}50`;
687
+ ctx.lineWidth = 1;
688
+ ctx.setLineDash([3, 3]);
689
+ ctx.stroke();
690
+ ctx.setLineDash([]);
691
+ }
692
+ const current = track[futureIdx > 0 ? Math.min(futureIdx - 1, track.length - 1) : track.length - 1];
693
+ const cx = lonToX(current.longitude, W);
694
+ const cy = latToY(current.latitude, H);
695
+ const satStatus = sat.status || "normal";
696
+ const statusColor = STATUS_COLOR_MAP[satStatus] || STATUS_COLOR_MAP.normal;
697
+ const satHalf = 22 / 2;
698
+ ctx.beginPath();
699
+ ctx.arc(cx, cy, 17, 0, Math.PI * 2);
700
+ ctx.fillStyle = `${statusColor}12`;
701
+ ctx.fill();
702
+ ctx.beginPath();
703
+ ctx.arc(cx, cy, 14, 0, Math.PI * 2);
704
+ ctx.fillStyle = `${statusColor}20`;
705
+ ctx.fill();
706
+ ctx.save();
707
+ ctx.translate(cx, cy);
708
+ ctx.beginPath();
709
+ ctx.arc(0, 0, satHalf, 0, Math.PI * 2);
710
+ ctx.fillStyle = "rgba(15, 23, 42, 0.88)";
711
+ ctx.fill();
712
+ ctx.strokeStyle = `${statusColor}88`;
713
+ ctx.lineWidth = 1.5;
714
+ ctx.stroke();
715
+ ctx.fillStyle = statusColor;
716
+ const bw = 5, bh = 3.5;
717
+ ctx.beginPath();
718
+ ctx.moveTo(-2, -3.5 / 2);
719
+ ctx.lineTo(bw / 2 - .5, -3.5 / 2);
720
+ ctx.arcTo(bw / 2, -3.5 / 2, bw / 2, -1.25, .5);
721
+ ctx.lineTo(bw / 2, bh / 2 - .5);
722
+ ctx.arcTo(bw / 2, bh / 2, bw / 2 - .5, bh / 2, .5);
723
+ ctx.lineTo(-2, bh / 2);
724
+ ctx.arcTo(-5 / 2, bh / 2, -5 / 2, bh / 2 - .5, .5);
725
+ ctx.lineTo(-5 / 2, -1.25);
726
+ ctx.arcTo(-5 / 2, -3.5 / 2, -2, -3.5 / 2, .5);
727
+ ctx.closePath();
728
+ ctx.fill();
729
+ ctx.fillStyle = `${statusColor}BB`;
730
+ ctx.fillRect(-9, -2, 5, 4);
731
+ ctx.strokeStyle = `${statusColor}55`;
732
+ ctx.lineWidth = .5;
733
+ ctx.beginPath();
734
+ ctx.moveTo(-6.5, -2);
735
+ ctx.lineTo(-6.5, 2);
736
+ ctx.stroke();
737
+ ctx.fillStyle = `${statusColor}BB`;
738
+ ctx.fillRect(satHalf - 7, -2, 5, 4);
739
+ ctx.strokeStyle = `${statusColor}55`;
740
+ ctx.beginPath();
741
+ ctx.moveTo(satHalf - 4.5, -2);
742
+ ctx.lineTo(satHalf - 4.5, 2);
743
+ ctx.stroke();
744
+ ctx.strokeStyle = statusColor;
745
+ ctx.lineWidth = 1;
746
+ ctx.beginPath();
747
+ ctx.moveTo(-5 / 2, 0);
748
+ ctx.lineTo(-4, 0);
749
+ ctx.moveTo(bw / 2, 0);
750
+ ctx.lineTo(satHalf - 7, 0);
751
+ ctx.stroke();
752
+ const satBadgeX = -9;
753
+ const satBadgeY = -9;
754
+ const satBadgeSize = 7;
755
+ ctx.beginPath();
756
+ ctx.arc(satBadgeX, satBadgeY, 5.5, 0, Math.PI * 2);
757
+ ctx.fillStyle = "rgba(15, 23, 42, 0.9)";
758
+ ctx.fill();
759
+ ctx.fillStyle = statusColor;
760
+ ctx.strokeStyle = statusColor;
761
+ ctx.lineWidth = 1;
762
+ if (satStatus === "normal") {
763
+ ctx.beginPath();
764
+ ctx.arc(satBadgeX, satBadgeY, satBadgeSize / 2, 0, Math.PI * 2);
765
+ ctx.fill();
766
+ } else if (satStatus === "standby") {
767
+ ctx.beginPath();
768
+ ctx.arc(satBadgeX, satBadgeY, satBadgeSize / 2 - .5, 0, Math.PI * 2);
769
+ ctx.lineWidth = 2;
770
+ ctx.stroke();
771
+ } else if (satStatus === "caution") {
772
+ const hs = satBadgeSize / 2;
773
+ ctx.fillRect(satBadgeX - hs, satBadgeY - hs, satBadgeSize, satBadgeSize);
774
+ } else if (satStatus === "serious") {
775
+ const hs = satBadgeSize / 2;
776
+ ctx.beginPath();
777
+ ctx.moveTo(satBadgeX, satBadgeY - hs);
778
+ ctx.lineTo(-5.5, satBadgeY);
779
+ ctx.lineTo(satBadgeX, -5.5);
780
+ ctx.lineTo(satBadgeX - hs, satBadgeY);
781
+ ctx.closePath();
782
+ ctx.fill();
783
+ } else if (satStatus === "critical") {
784
+ const hs = satBadgeSize / 2;
785
+ ctx.beginPath();
786
+ ctx.moveTo(satBadgeX, -5.5);
787
+ ctx.lineTo(-5.5, satBadgeY - hs);
788
+ ctx.lineTo(satBadgeX - hs, satBadgeY - hs);
789
+ ctx.closePath();
790
+ ctx.fill();
791
+ } else {
792
+ ctx.beginPath();
793
+ ctx.arc(satBadgeX, satBadgeY, satBadgeSize / 3, 0, Math.PI * 2);
794
+ ctx.fill();
795
+ }
796
+ ctx.restore();
797
+ ctx.fillStyle = COLORS.textPrimary;
798
+ ctx.font = "bold 10px \"Inter\", system-ui, sans-serif";
799
+ ctx.textAlign = "center";
800
+ ctx.fillText(sat.name, cx, cy - satHalf - 6);
801
+ });
802
+ if (showLegend && (allSatellites.length > 0 || groundStations.length > 0)) {
803
+ const legendX = 10;
804
+ const legendY = 10;
805
+ const lineHeight = 14;
806
+ const items = [];
807
+ allSatellites.forEach((sat, idx) => {
808
+ const c = sat.color || DEFAULT_TRACK_COLORS[idx % DEFAULT_TRACK_COLORS.length];
809
+ items.push({
810
+ color: c,
811
+ label: sat.name
812
+ });
813
+ });
814
+ if (groundStations.length > 0) items.push({
815
+ color: STATUS_COLOR_MAP.standby,
816
+ label: `${groundStations.length} Ground Station${groundStations.length > 1 ? "s" : ""}`
817
+ });
818
+ const maxLabelWidth = Math.max(...items.map((item) => ctx.measureText(item.label).width)) + 28;
819
+ const legendH = items.length * lineHeight + 10;
820
+ ctx.fillStyle = "rgba(0, 0, 0, 0.6)";
821
+ ctx.strokeStyle = COLORS.borderMuted;
822
+ ctx.lineWidth = 1;
823
+ const r = 4;
824
+ ctx.beginPath();
825
+ ctx.moveTo(14, legendY);
826
+ ctx.lineTo(legendX + maxLabelWidth - r, legendY);
827
+ ctx.arcTo(legendX + maxLabelWidth, legendY, legendX + maxLabelWidth, 14, r);
828
+ ctx.lineTo(legendX + maxLabelWidth, legendY + legendH - r);
829
+ ctx.arcTo(legendX + maxLabelWidth, legendY + legendH, legendX + maxLabelWidth - r, legendY + legendH, r);
830
+ ctx.lineTo(14, legendY + legendH);
831
+ ctx.arcTo(legendX, legendY + legendH, legendX, legendY + legendH - r, r);
832
+ ctx.lineTo(legendX, 14);
833
+ ctx.arcTo(legendX, legendY, 14, legendY, r);
834
+ ctx.closePath();
835
+ ctx.fill();
836
+ ctx.stroke();
837
+ items.forEach((item, i) => {
838
+ const iy = 18 + i * lineHeight;
839
+ ctx.fillStyle = item.color;
840
+ ctx.beginPath();
841
+ ctx.arc(20, iy + 3, 4, 0, Math.PI * 2);
842
+ ctx.fill();
843
+ ctx.fillStyle = COLORS.textPrimary;
844
+ ctx.font = "10px \"Inter\", system-ui, sans-serif";
845
+ ctx.textAlign = "left";
846
+ ctx.fillText(item.label, 30, iy + 7);
847
+ });
848
+ }
849
+ if (pins && pins.length > 0) pins.forEach((pin) => {
850
+ const px = lonToX(pin.longitude, W);
851
+ const py = latToY(pin.latitude, H);
852
+ const pinColor = pin.color || tokens.colors.accent.primary;
853
+ ctx.save();
854
+ ctx.shadowColor = "rgba(0,0,0,0.4)";
855
+ ctx.shadowBlur = 6;
856
+ ctx.shadowOffsetY = 2;
857
+ ctx.beginPath();
858
+ ctx.arc(px, py - 10, 6, Math.PI, 0);
859
+ ctx.lineTo(px, py);
860
+ ctx.closePath();
861
+ ctx.fillStyle = pinColor;
862
+ ctx.fill();
863
+ ctx.restore();
864
+ ctx.beginPath();
865
+ ctx.arc(px, py - 10, 2.5, 0, Math.PI * 2);
866
+ ctx.fillStyle = "#0d1323";
867
+ ctx.fill();
868
+ if (pin.label) {
869
+ ctx.fillStyle = COLORS.textPrimary;
870
+ ctx.font = "10px \"Inter\", system-ui, sans-serif";
871
+ ctx.textAlign = "center";
872
+ ctx.fillText(pin.label, px, py - 20);
873
+ }
874
+ });
875
+ if (allSatellites.length === 0 && groundStations.length === 0 && (!pins || pins.length === 0)) {
876
+ ctx.fillStyle = COLORS.text;
877
+ ctx.font = "12px \"Inter\", system-ui, sans-serif";
878
+ ctx.textAlign = "center";
879
+ ctx.fillText(emptyMessage, W / 2, H / 2);
880
+ }
881
+ }, [
882
+ allSatellites,
883
+ groundStations,
884
+ height,
885
+ showTerminator,
886
+ terminatorTime,
887
+ showGrid,
888
+ showLegend,
889
+ showEquator,
890
+ lonToX,
891
+ latToY,
892
+ COLORS,
893
+ emptyMessage,
894
+ pins,
895
+ lightSources,
896
+ tokens.colors.accent.primary
897
+ ]);
898
+ const handleMouseMove = useCallback((e) => {
899
+ const canvas = canvasRef.current;
900
+ if (!canvas) return;
901
+ const rect = canvas.getBoundingClientRect();
902
+ const mx = e.clientX - rect.left;
903
+ const my = e.clientY - rect.top;
904
+ const W = rect.width;
905
+ const H = typeof height === "number" ? height : rect.height;
906
+ for (const sat of allSatellites) {
907
+ if (!sat.groundTrack.length) continue;
908
+ const futureIdx = sat.futureTrackIndex ?? sat.groundTrack.length;
909
+ const currentIdx = futureIdx > 0 ? Math.min(futureIdx - 1, sat.groundTrack.length - 1) : sat.groundTrack.length - 1;
910
+ const lastPt = sat.groundTrack[currentIdx];
911
+ const sx = lonToX(lastPt.longitude, W);
912
+ const sy = latToY(lastPt.latitude, H);
913
+ if (Math.sqrt((mx - sx) ** 2 + (my - sy) ** 2) < 18) {
914
+ const satStatus = sat.status || "normal";
915
+ setTooltip({
916
+ x: e.clientX - rect.left + 15,
917
+ y: e.clientY - rect.top - 10,
918
+ type: "satellite",
919
+ name: sat.name,
920
+ status: satStatus,
921
+ statusColor: STATUS_COLOR_MAP[satStatus] || STATUS_COLOR_MAP.normal,
922
+ lat: lastPt.latitude,
923
+ lon: lastPt.longitude,
924
+ alt: lastPt.altitude,
925
+ trackColor: sat.color || DEFAULT_TRACK_COLORS[allSatellites.indexOf(sat) % DEFAULT_TRACK_COLORS.length],
926
+ hasFootprint: sat.showFootprint,
927
+ futurePoints: sat.groundTrack.length - futureIdx,
928
+ pastPoints: futureIdx
929
+ });
930
+ return;
931
+ }
932
+ }
933
+ for (const gs of groundStations) {
934
+ const gx = lonToX(gs.longitude, W);
935
+ const gy = latToY(gs.latitude, H);
936
+ if (Math.sqrt((mx - gx) ** 2 + (my - gy) ** 2) < 16) {
937
+ const enhanced = gs;
938
+ const gsStatus = enhanced.status || "standby";
939
+ setTooltip({
940
+ x: e.clientX - rect.left + 15,
941
+ y: e.clientY - rect.top - 10,
942
+ type: "station",
943
+ name: gs.name,
944
+ status: gsStatus,
945
+ statusColor: STATUS_COLOR_MAP[gsStatus] || STATUS_COLOR_MAP.standby,
946
+ lat: gs.latitude,
947
+ lon: gs.longitude,
948
+ stationType: enhanced.type || "dish",
949
+ network: "network" in gs && gs.network ? String(gs.network) : void 0,
950
+ coverageRadius: enhanced.coverageRadius
951
+ });
952
+ return;
953
+ }
954
+ }
955
+ setTooltip(null);
956
+ }, [
957
+ allSatellites,
958
+ groundStations,
959
+ height,
960
+ lonToX,
961
+ latToY
962
+ ]);
963
+ const handleClick = useCallback((e) => {
964
+ const canvas = canvasRef.current;
965
+ if (!canvas) return;
966
+ const rect = canvas.getBoundingClientRect();
967
+ const mx = e.clientX - rect.left;
968
+ const my = e.clientY - rect.top;
969
+ const W = rect.width;
970
+ const H = typeof height === "number" ? height : rect.height;
971
+ for (const sat of allSatellites) {
972
+ if (!sat.groundTrack.length) continue;
973
+ const futureIdx = sat.futureTrackIndex ?? sat.groundTrack.length;
974
+ const currentIdx = futureIdx > 0 ? Math.min(futureIdx - 1, sat.groundTrack.length - 1) : sat.groundTrack.length - 1;
975
+ const currentPt = sat.groundTrack[currentIdx];
976
+ const sx = lonToX(currentPt.longitude, W);
977
+ const sy = latToY(currentPt.latitude, H);
978
+ if (Math.sqrt((mx - sx) ** 2 + (my - sy) ** 2) < 15) {
979
+ onSatelliteClick?.(sat.id);
980
+ return;
981
+ }
982
+ }
983
+ for (const gs of groundStations) {
984
+ const gx = lonToX(gs.longitude, W);
985
+ const gy = latToY(gs.latitude, H);
986
+ if (Math.sqrt((mx - gx) ** 2 + (my - gy) ** 2) < 12) {
987
+ onStationClick?.("id" in gs ? gs.id : gs.name);
988
+ return;
989
+ }
990
+ }
991
+ }, [
992
+ allSatellites,
993
+ groundStations,
994
+ height,
995
+ lonToX,
996
+ latToY,
997
+ onSatelliteClick,
998
+ onStationClick
999
+ ]);
1000
+ useEffect(() => {
1001
+ draw();
1002
+ window.addEventListener("resize", draw);
1003
+ return () => window.removeEventListener("resize", draw);
1004
+ }, [draw]);
1005
+ const resolvedMinHeightForLeaflet = minHeight || (typeof height === "number" ? `${height}px` : "400px");
1006
+ if (mapProvider === "leaflet") {
1007
+ if (LeafletMap) return /* @__PURE__ */ jsx("div", {
1008
+ role: "img",
1009
+ "aria-label": allSatellites.length > 0 || groundStations.length > 0 ? "Ground track map showing satellite tracks and ground stations" : "Ground track map",
1010
+ style: {
1011
+ display: "block",
1012
+ width: width ?? "100%",
1013
+ height: typeof height === "string" ? height : void 0,
1014
+ minHeight: resolvedMinHeightForLeaflet
1015
+ },
1016
+ children: /* @__PURE__ */ jsx(LeafletMap, {
1017
+ allSatellites,
1018
+ groundStations,
1019
+ showTerminator,
1020
+ terminatorTime,
1021
+ showGrid,
1022
+ showLegend,
1023
+ showEquator,
1024
+ showRecenterButton,
1025
+ showMapStyleToggle,
1026
+ defaultCenter: leafletDefaultCenter,
1027
+ defaultZoom: leafletDefaultZoom,
1028
+ height,
1029
+ width,
1030
+ minHeight,
1031
+ emptyMessage,
1032
+ tileUrl,
1033
+ nightTileUrl,
1034
+ lightSources,
1035
+ className,
1036
+ onSatelliteClick,
1037
+ onStationClick,
1038
+ pins,
1039
+ pinsEditable,
1040
+ onPinAdd,
1041
+ onPinUpdate,
1042
+ onPinRemove,
1043
+ customLayers,
1044
+ onLayerChange,
1045
+ showCelestialMarkers
1046
+ })
1047
+ });
1048
+ if (!leafletFailed) return /* @__PURE__ */ jsx("div", {
1049
+ className: `zendir-ground-track-map ${className}`,
1050
+ style: {
1051
+ width,
1052
+ height: typeof height === "string" ? height : void 0,
1053
+ minHeight: resolvedMinHeightForLeaflet,
1054
+ backgroundColor: tokens.colors.background.base,
1055
+ borderRadius: 8,
1056
+ display: "flex",
1057
+ alignItems: "center",
1058
+ justifyContent: "center"
1059
+ },
1060
+ children: /* @__PURE__ */ jsx("span", {
1061
+ style: {
1062
+ color: tokens.colors.text.secondary,
1063
+ fontSize: 14
1064
+ },
1065
+ children: "Loading map…"
1066
+ })
1067
+ });
1068
+ }
1069
+ const resolvedMinHeight = minHeight || (typeof height === "number" ? `${height}px` : "400px");
1070
+ if (isLoading) return /* @__PURE__ */ jsx("div", {
1071
+ className: `zendir-ground-track-map ${className}`,
1072
+ style: {
1073
+ width,
1074
+ minHeight: resolvedMinHeight,
1075
+ backgroundColor: COLORS.background,
1076
+ borderRadius: "8px",
1077
+ display: "flex",
1078
+ alignItems: "center",
1079
+ justifyContent: "center"
1080
+ },
1081
+ children: /* @__PURE__ */ jsx("span", {
1082
+ style: {
1083
+ color: COLORS.text,
1084
+ fontSize: "14px",
1085
+ letterSpacing: "1px"
1086
+ },
1087
+ children: "Loading…"
1088
+ })
1089
+ });
1090
+ return /* @__PURE__ */ jsxs("div", {
1091
+ ref: containerRef,
1092
+ role: "img",
1093
+ "aria-label": allSatellites.length > 0 || groundStations.length > 0 ? "Ground track map showing satellite tracks and ground stations" : "Ground track map",
1094
+ className: `zendir-ground-track-map ${className}`,
1095
+ style: {
1096
+ width,
1097
+ height: typeof height === "string" ? height : void 0,
1098
+ minHeight: resolvedMinHeight,
1099
+ backgroundColor: COLORS.background,
1100
+ borderRadius: "8px",
1101
+ overflow: "hidden",
1102
+ position: "relative"
1103
+ },
1104
+ "data-map-backend": "canvas",
1105
+ children: [
1106
+ mapProvider === "leaflet" && leafletFailed && /* @__PURE__ */ jsxs("div", {
1107
+ style: {
1108
+ position: "absolute",
1109
+ bottom: 8,
1110
+ left: 8,
1111
+ zIndex: 100,
1112
+ padding: "4px 8px",
1113
+ background: "rgba(0,0,0,0.75)",
1114
+ color: tokens.colors.status.caution,
1115
+ fontSize: 11,
1116
+ borderRadius: 4
1117
+ },
1118
+ children: [
1119
+ "Static view (install",
1120
+ " ",
1121
+ /* @__PURE__ */ jsx("code", {
1122
+ style: { fontFamily: "monospace" },
1123
+ children: "leaflet"
1124
+ }),
1125
+ " for zoom/pan)"
1126
+ ]
1127
+ }),
1128
+ showRecenterButton && /* @__PURE__ */ jsx("button", {
1129
+ type: "button",
1130
+ onClick: handleRecenter,
1131
+ title: "Recenter Map",
1132
+ "aria-label": "Recenter map",
1133
+ style: {
1134
+ position: "absolute",
1135
+ top: "8px",
1136
+ right: "8px",
1137
+ zIndex: 20,
1138
+ background: tokens.colors.background.elevated,
1139
+ border: `1px solid ${tokens.colors.border.muted}`,
1140
+ borderRadius: "4px",
1141
+ color: tokens.colors.text.primary,
1142
+ cursor: "pointer",
1143
+ padding: "4px 8px",
1144
+ fontSize: "12px",
1145
+ lineHeight: "1",
1146
+ display: "flex",
1147
+ alignItems: "center",
1148
+ gap: "4px"
1149
+ },
1150
+ children: /* @__PURE__ */ jsxs("svg", {
1151
+ width: "14",
1152
+ height: "14",
1153
+ viewBox: "0 0 24 24",
1154
+ fill: "none",
1155
+ stroke: "currentColor",
1156
+ strokeWidth: "2",
1157
+ strokeLinecap: "round",
1158
+ strokeLinejoin: "round",
1159
+ children: [/* @__PURE__ */ jsx("circle", {
1160
+ cx: "12",
1161
+ cy: "12",
1162
+ r: "3"
1163
+ }), /* @__PURE__ */ jsx("path", { d: "M12 2v4M12 18v4M2 12h4M18 12h4" })]
1164
+ })
1165
+ }),
1166
+ /* @__PURE__ */ jsx("canvas", {
1167
+ ref: canvasRef,
1168
+ style: {
1169
+ display: "block",
1170
+ cursor: tooltip ? "pointer" : "default"
1171
+ },
1172
+ onMouseMove: handleMouseMove,
1173
+ onMouseLeave: () => setTooltip(null),
1174
+ onClick: handleClick
1175
+ }),
1176
+ tooltip && /* @__PURE__ */ jsxs("div", {
1177
+ style: {
1178
+ position: "absolute",
1179
+ left: tooltip.x,
1180
+ top: tooltip.y,
1181
+ background: tokens.colors.background.surface,
1182
+ backdropFilter: "blur(8px)",
1183
+ border: `1px solid ${tooltip.statusColor}40`,
1184
+ borderRadius: "8px",
1185
+ padding: 0,
1186
+ pointerEvents: "none",
1187
+ zIndex: 10,
1188
+ color: tokens.colors.text.primary,
1189
+ fontSize: "11px",
1190
+ fontFamily: "\"Inter\", system-ui, sans-serif",
1191
+ lineHeight: "1.5",
1192
+ boxShadow: `0 4px 16px rgba(0, 0, 0, 0.5), 0 0 12px ${tooltip.statusColor}15`,
1193
+ minWidth: 180,
1194
+ overflow: "hidden"
1195
+ },
1196
+ children: [/* @__PURE__ */ jsxs("div", {
1197
+ style: {
1198
+ padding: "6px 10px",
1199
+ borderBottom: `1px solid ${tooltip.statusColor}25`,
1200
+ background: `${tooltip.statusColor}10`,
1201
+ display: "flex",
1202
+ alignItems: "center",
1203
+ gap: 6
1204
+ },
1205
+ children: [
1206
+ /* @__PURE__ */ jsx("span", { style: {
1207
+ width: 8,
1208
+ height: 8,
1209
+ borderRadius: tooltip.status === "caution" ? 1 : "50%",
1210
+ background: tooltip.statusColor,
1211
+ display: "inline-block",
1212
+ flexShrink: 0,
1213
+ boxShadow: `0 0 4px ${tooltip.statusColor}88`
1214
+ } }),
1215
+ /* @__PURE__ */ jsx("span", {
1216
+ style: {
1217
+ fontWeight: 600,
1218
+ fontSize: 12
1219
+ },
1220
+ children: tooltip.name
1221
+ }),
1222
+ /* @__PURE__ */ jsx("span", {
1223
+ style: {
1224
+ marginLeft: "auto",
1225
+ fontSize: 9,
1226
+ fontWeight: 600,
1227
+ textTransform: "uppercase",
1228
+ color: tooltip.statusColor,
1229
+ letterSpacing: "0.5px"
1230
+ },
1231
+ children: tooltip.status
1232
+ })
1233
+ ]
1234
+ }), /* @__PURE__ */ jsxs("div", {
1235
+ style: {
1236
+ padding: "6px 10px",
1237
+ display: "flex",
1238
+ flexDirection: "column",
1239
+ gap: 3
1240
+ },
1241
+ children: [
1242
+ /* @__PURE__ */ jsxs("div", {
1243
+ style: {
1244
+ display: "flex",
1245
+ alignItems: "center",
1246
+ gap: 6,
1247
+ marginBottom: 2
1248
+ },
1249
+ children: [/* @__PURE__ */ jsx("span", {
1250
+ style: {
1251
+ fontSize: 9,
1252
+ fontWeight: 600,
1253
+ padding: "1px 5px",
1254
+ borderRadius: 3,
1255
+ textTransform: "uppercase",
1256
+ letterSpacing: "0.3px",
1257
+ background: tooltip.type === "satellite" ? `${tokens.colors.status.standby}26` : `${tokens.colors.status.normal}26`,
1258
+ color: tooltip.type === "satellite" ? tokens.colors.status.standby : tokens.colors.status.normal
1259
+ },
1260
+ children: tooltip.type === "satellite" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(AstroIcon, {
1261
+ name: "satellite",
1262
+ size: "extra-small",
1263
+ label: "Satellite",
1264
+ style: {
1265
+ marginRight: 4,
1266
+ verticalAlign: "middle"
1267
+ }
1268
+ }), "Satellite"] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(AstroIcon, {
1269
+ name: "antenna",
1270
+ size: "extra-small",
1271
+ label: tooltip.stationType || "Ground station",
1272
+ style: {
1273
+ marginRight: 4,
1274
+ verticalAlign: "middle"
1275
+ }
1276
+ }), tooltip.stationType || "dish"] })
1277
+ }), tooltip.network && /* @__PURE__ */ jsx("span", {
1278
+ style: {
1279
+ fontSize: 9,
1280
+ padding: "1px 5px",
1281
+ borderRadius: 3,
1282
+ background: "rgba(157, 112, 255, 0.15)",
1283
+ color: "#9D70FF"
1284
+ },
1285
+ children: tooltip.network
1286
+ })]
1287
+ }),
1288
+ /* @__PURE__ */ jsxs("div", {
1289
+ style: {
1290
+ display: "grid",
1291
+ gridTemplateColumns: "52px 1fr",
1292
+ gap: "2px 8px",
1293
+ fontSize: 10
1294
+ },
1295
+ children: [
1296
+ /* @__PURE__ */ jsx("span", {
1297
+ style: { color: tokens.colors.text.muted },
1298
+ children: "Lat"
1299
+ }),
1300
+ /* @__PURE__ */ jsxs("span", {
1301
+ style: {
1302
+ fontFamily: "\"Roboto Mono\", monospace",
1303
+ fontWeight: 500
1304
+ },
1305
+ children: [
1306
+ tooltip.lat >= 0 ? "+" : "",
1307
+ tooltip.lat.toFixed(4),
1308
+ "°"
1309
+ ]
1310
+ }),
1311
+ /* @__PURE__ */ jsx("span", {
1312
+ style: { color: tokens.colors.text.muted },
1313
+ children: "Lon"
1314
+ }),
1315
+ /* @__PURE__ */ jsxs("span", {
1316
+ style: {
1317
+ fontFamily: "\"Roboto Mono\", monospace",
1318
+ fontWeight: 500
1319
+ },
1320
+ children: [
1321
+ tooltip.lon >= 0 ? "+" : "",
1322
+ tooltip.lon.toFixed(4),
1323
+ "°"
1324
+ ]
1325
+ }),
1326
+ tooltip.alt !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
1327
+ style: { color: tokens.colors.text.muted },
1328
+ children: "Altitude"
1329
+ }), /* @__PURE__ */ jsxs("span", {
1330
+ style: {
1331
+ fontFamily: "\"Roboto Mono\", monospace",
1332
+ fontWeight: 500
1333
+ },
1334
+ children: [tooltip.alt.toFixed(1), " km"]
1335
+ })] }),
1336
+ tooltip.coverageRadius !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
1337
+ style: { color: tokens.colors.text.muted },
1338
+ children: "Coverage"
1339
+ }), /* @__PURE__ */ jsxs("span", {
1340
+ style: {
1341
+ fontFamily: "\"Roboto Mono\", monospace",
1342
+ fontWeight: 500
1343
+ },
1344
+ children: [tooltip.coverageRadius.toFixed(1), "° radius"]
1345
+ })] })
1346
+ ]
1347
+ }),
1348
+ tooltip.type === "satellite" && (tooltip.pastPoints !== void 0 || tooltip.futurePoints !== void 0) && /* @__PURE__ */ jsxs("div", {
1349
+ style: {
1350
+ marginTop: 3,
1351
+ paddingTop: 3,
1352
+ borderTop: `1px solid ${tokens.colors.border.muted}`,
1353
+ display: "flex",
1354
+ gap: 10,
1355
+ fontSize: 9
1356
+ },
1357
+ children: [
1358
+ tooltip.pastPoints !== void 0 && /* @__PURE__ */ jsxs("span", { children: [
1359
+ /* @__PURE__ */ jsx("span", {
1360
+ style: { color: tooltip.trackColor },
1361
+ children: "━"
1362
+ }),
1363
+ " ",
1364
+ tooltip.pastPoints,
1365
+ " past pts"
1366
+ ] }),
1367
+ tooltip.futurePoints !== void 0 && tooltip.futurePoints > 0 && /* @__PURE__ */ jsxs("span", { children: [
1368
+ /* @__PURE__ */ jsx("span", {
1369
+ style: { color: `${tooltip.trackColor}88` },
1370
+ children: "╌"
1371
+ }),
1372
+ " ",
1373
+ tooltip.futurePoints,
1374
+ " predicted"
1375
+ ] }),
1376
+ tooltip.hasFootprint && /* @__PURE__ */ jsx("span", {
1377
+ style: { color: "#9D70FF" },
1378
+ children: "◎ Footprint"
1379
+ })
1380
+ ]
1381
+ })
1382
+ ]
1383
+ })]
1384
+ })
1385
+ ]
1386
+ });
1266
1387
  }
1267
- export {
1268
- GroundTrackMap
1269
- };
1270
- //# sourceMappingURL=GroundTrackMap.js.map
1388
+ //#endregion
1389
+ export { GroundTrackMap, GroundTrackMap as default };
1390
+
1391
+ //# sourceMappingURL=GroundTrackMap.js.map