initial commit

This commit is contained in:
2026-06-25 21:36:46 +00:00
commit f3a9e905bc
14 changed files with 176379 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
# Basic hardening + caching
Options -Indexes
<IfModule mod_headers.c>
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "SAMEORIGIN"
Header set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>
# Cache static assets
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 7 days"
ExpiresByType application/javascript "access plus 7 days"
ExpiresByType image/svg+xml "access plus 30 days"
ExpiresByType image/png "access plus 30 days"
</IfModule>
+103574
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -0,0 +1,15 @@
{
"meta": {
"source": "CelesTrak",
"group": "noaa",
"groupName": "NOAA",
"upstream": "https://celestrak.org/NORAD/elements/gp.php?GROUP=noaa&FORMAT=tle",
"cached": false,
"fetchedAtUtc": "2026-06-19 21:20:44",
"cacheTtlSeconds": 7200,
"count": 0,
"upstreamHttpCode": 200,
"serverMs": 319
},
"satellites": []
}
+70849
View File
File diff suppressed because it is too large Load Diff
+233
View File
@@ -0,0 +1,233 @@
{
"meta": {
"source": "CelesTrak",
"group": "stations",
"groupName": "Space Stations",
"upstream": "https://celestrak.org/NORAD/elements/gp.php?GROUP=stations&FORMAT=tle",
"cached": false,
"fetchedAtUtc": "2026-03-24 22:51:44",
"cacheTtlSeconds": 7200,
"count": 31,
"upstreamHttpCode": 200,
"serverMs": 313
},
"satellites": [
{
"name": "ISS (ZARYA)",
"norad": "25544",
"line1": "1 25544U 98067A 26083.48628161 .00013391 00000+0 25488-3 0 9991",
"line2": "2 25544 51.6345 359.2131 0006234 227.6352 132.4108 15.48523431558623",
"epoch": "2026-03-24 11:40:15"
},
{
"name": "POISK",
"norad": "36086",
"line1": "1 36086U 09060A 26083.48628161 .00013391 00000+0 25488-3 0 9999",
"line2": "2 36086 51.6345 359.2131 0006234 227.6352 132.4108 15.48523431558390",
"epoch": "2026-03-24 11:40:15"
},
{
"name": "CSS (TIANHE)",
"norad": "48274",
"line1": "1 48274U 21035A 26083.49395739 .00019397 00000+0 22400-3 0 9993",
"line2": "2 48274 41.4663 118.0307 0004758 10.7895 349.3045 15.61427580279921",
"epoch": "2026-03-24 11:51:18"
},
{
"name": "ISS (NAUKA)",
"norad": "49044",
"line1": "1 49044U 21066A 26083.48628161 .00013391 00000+0 25488-3 0 9997",
"line2": "2 49044 51.6345 359.2131 0006234 227.6352 132.4108 15.48523431558433",
"epoch": "2026-03-24 11:40:15"
},
{
"name": "FREGAT DEB",
"norad": "49271",
"line1": "1 49271U 11037PF 26083.51009843 .00011351 00000+0 16086-1 0 9996",
"line2": "2 49271 51.6508 256.2575 0962852 93.4027 277.7281 12.40658271214534",
"epoch": "2026-03-24 12:14:33"
},
{
"name": "CSS (WENTIAN)",
"norad": "53239",
"line1": "1 53239U 22085A 26083.49395739 .00019397 00000+0 22400-3 0 9996",
"line2": "2 53239 41.4663 118.0307 0004758 10.7895 349.3045 15.61427580279995",
"epoch": "2026-03-24 11:51:18"
},
{
"name": "CSS (MENGTIAN)",
"norad": "54216",
"line1": "1 54216U 22143A 26083.49395739 .00019397 00000+0 22400-3 0 9997",
"line2": "2 54216 41.4663 118.0307 0004758 10.7895 349.3045 15.61427580279991",
"epoch": "2026-03-24 11:51:18"
},
{
"name": "TIANZHOU-9",
"norad": "64786",
"line1": "1 64786U 25149A 26083.49395739 .00019397 00000+0 22400-3 0 9999",
"line2": "2 64786 41.4663 118.0307 0004758 10.7895 349.3045 15.61427580279994",
"epoch": "2026-03-24 11:51:18"
},
{
"name": "PROGRESS-MS 32",
"norad": "65586",
"line1": "1 65586U 25204A 26083.16360592 .00014170 00000+0 26927-3 0 9995",
"line2": "2 65586 51.6345 0.8084 0006267 226.2093 133.8378 15.48514008554509",
"epoch": "2026-03-24 03:55:36"
},
{
"name": "YOTSUBA-KULOVER",
"norad": "65941",
"line1": "1 65941U 98067XN 26082.96327371 .00332191 00000+0 11506-2 0 9994",
"line2": "2 65941 51.6197 342.8745 0005099 174.2801 185.8263 15.88031375 25769",
"epoch": "2026-03-23 23:07:07"
},
{
"name": "BOTAN",
"norad": "65943",
"line1": "1 65943U 98067XQ 26082.97212654 .00308939 00000+0 11098-2 0 9993",
"line2": "2 65943 51.6228 342.9564 0005691 173.8079 186.2997 15.87345825 25762",
"epoch": "2026-03-23 23:19:52"
},
{
"name": "HRC MONOBLOCK CAMERA",
"norad": "66052",
"line1": "1 66052U 98067XR 26083.46970303 .00045505 00000+0 47981-3 0 9990",
"line2": "2 66052 51.6287 350.8514 0003822 160.3996 199.7146 15.63768342 24700",
"epoch": "2026-03-24 11:16:22"
},
{
"name": "HTV-X1",
"norad": "66174",
"line1": "1 66174U 25241A 26083.32282862 .00005545 00000+0 18606-3 0 9992",
"line2": "2 66174 51.6376 2.5775 0007466 227.6504 132.3845 15.32402906 23181",
"epoch": "2026-03-24 07:44:52"
},
{
"name": "SZ-21 MODULE",
"norad": "66515",
"line1": "1 66515U 25246C 26083.45739579 .00033703 00000+0 27749-3 0 9997",
"line2": "2 66515 41.4738 112.3746 0000853 213.1562 146.9227 15.69668398 20398",
"epoch": "2026-03-24 10:58:39"
},
{
"name": "SHENZHOU-22",
"norad": "66645",
"line1": "1 66645U 25272A 26083.49395739 .00019397 00000+0 22400-3 0 9992",
"line2": "2 66645 41.4663 118.0307 0004758 10.7895 349.3045 15.61427580279990",
"epoch": "2026-03-24 11:51:18"
},
{
"name": "SOYUZ-MS 28",
"norad": "66664",
"line1": "1 66664U 25275A 26083.48628161 .00013391 00000+0 25488-3 0 9990",
"line2": "2 66664 51.6345 359.2131 0006234 227.6352 132.4108 15.48523431558676",
"epoch": "2026-03-24 11:40:15"
},
{
"name": "DUPLEX",
"norad": "66906",
"line1": "1 66906U 98067XS 26083.47964882 .00027087 00000+0 37156-3 0 9992",
"line2": "2 66906 51.6302 355.4219 0003163 176.3649 183.7366 15.57139447 17439",
"epoch": "2026-03-24 11:30:42"
},
{
"name": "ISS OBJECT XT",
"norad": "66907",
"line1": "1 66907U 98067XT 26083.46050153 .00185654 00000+0 12240-2 0 9990",
"line2": "2 66907 51.6252 350.0134 0005402 175.0560 185.0493 15.74515234 17502",
"epoch": "2026-03-24 11:03:07"
},
{
"name": "ISS OBJECT XU",
"norad": "66908",
"line1": "1 66908U 98067XU 26083.45733407 .00188153 00000+0 12296-2 0 9998",
"line2": "2 66908 51.6249 349.9877 0005644 175.3702 184.7351 15.74708720 17502",
"epoch": "2026-03-24 10:58:34"
},
{
"name": "SILVERSAT",
"norad": "66909",
"line1": "1 66909U 98067XV 26082.98164823 .00288378 00000+0 15389-2 0 9992",
"line2": "2 66909 51.6221 351.3039 0006229 177.1516 182.9521 15.78994089 17444",
"epoch": "2026-03-23 23:33:34"
},
{
"name": "ISS OBJECT XW",
"norad": "66910",
"line1": "1 66910U 98067XW 26083.46394234 .00165160 00000+0 11502-2 0 9995",
"line2": "2 66910 51.6240 350.0286 0005904 174.9033 185.2027 15.73305424 17503",
"epoch": "2026-03-24 11:08:05"
},
{
"name": "ISS OBJECT XX",
"norad": "66911",
"line1": "1 66911U 98067XX 26083.44596692 .00391459 00000+0 15820-2 0 9995",
"line2": "2 66911 51.6199 347.6223 0007866 183.5979 176.4969 15.84695682 17523",
"epoch": "2026-03-24 10:42:12"
},
{
"name": "ISS OBJECT XY",
"norad": "66912",
"line1": "1 66912U 98067XY 26083.45850020 .00094960 00000+0 88169-3 0 9999",
"line2": "2 66912 51.6275 352.2703 0004026 174.2260 185.8783 15.66654645 17464",
"epoch": "2026-03-24 11:00:14"
},
{
"name": "KNACKSAT-2",
"norad": "67683",
"line1": "1 67683U 98067XZ 26083.01837906 .00038683 00000+0 61191-3 0 9991",
"line2": "2 67683 51.6326 0.5913 0009822 237.4563 122.5479 15.53013062 7018",
"epoch": "2026-03-24 00:26:28"
},
{
"name": "CORAL",
"norad": "67684",
"line1": "1 67684U 98067YA 26083.47622175 .00078164 00000+0 10629-2 0 9999",
"line2": "2 67684 51.6305 357.6492 0012273 228.4697 131.5242 15.56803499 7096",
"epoch": "2026-03-24 11:25:46"
},
{
"name": "GXIBA-1",
"norad": "67685",
"line1": "1 67685U 98067YB 26082.94566353 .00047226 00000+0 73089-3 0 9991",
"line2": "2 67685 51.6338 0.8611 0011822 234.4096 125.5793 15.53503062 7007",
"epoch": "2026-03-23 22:41:45"
},
{
"name": "UITMSAT-2",
"norad": "67686",
"line1": "1 67686U 98067YC 26083.06244469 .00055892 00000+0 83755-3 0 9996",
"line2": "2 67686 51.6326 0.1380 0008812 205.0636 154.9928 15.54352229 7026",
"epoch": "2026-03-24 01:29:55"
},
{
"name": "HMU-SAT2",
"norad": "67687",
"line1": "1 67687U 98067YD 26083.01414055 .00041357 00000+0 64805-3 0 9998",
"line2": "2 67687 51.6328 0.5652 0008396 207.9190 152.1351 15.53258522 6937",
"epoch": "2026-03-24 00:20:22"
},
{
"name": "LEOPARD",
"norad": "67688",
"line1": "1 67688U 98067YE 26083.06786206 .00050002 00000+0 76118-3 0 9991",
"line2": "2 67688 51.6323 0.1737 0008646 205.7190 154.3372 15.53969684 6943",
"epoch": "2026-03-24 01:37:43"
},
{
"name": "CREW DRAGON 12",
"norad": "67796",
"line1": "1 67796U 26031A 26083.48628161 .00013391 00000+0 25488-3 0 9998",
"line2": "2 67796 51.6345 359.2131 0006234 227.6352 132.4108 15.48523431558550",
"epoch": "2026-03-24 11:40:15"
},
{
"name": "PROGRESS-MS 33",
"norad": "68319",
"line1": "1 68319U 26058A 26082.05109224 .00200487 28256-4 35870-3 0 9993",
"line2": "2 68319 51.6358 6.8873 0011621 152.2903 207.8729 16.01170532 69",
"epoch": "2026-03-23 01:13:34"
}
]
}
+534
View File
@@ -0,0 +1,534 @@
{
"meta": {
"source": "CelesTrak",
"group": "weather",
"groupName": "Weather",
"upstream": "https://celestrak.org/NORAD/elements/gp.php?GROUP=weather&FORMAT=tle",
"cached": false,
"fetchedAtUtc": "2026-06-22 15:29:21",
"cacheTtlSeconds": 7200,
"count": 74,
"upstreamHttpCode": 200,
"serverMs": 355
},
"satellites": [
{
"name": "DMSP 5D-3 F16 (USA 172)",
"norad": "28054",
"line1": "1 28054U 03048A 26171.42689955 -.00000038 00000+0 37443-5 0 9996",
"line2": "2 28054 98.9889 195.2768 0007887 132.0538 331.9039 14.14483136169944",
"epoch": "2026-06-20 10:14:44"
},
{
"name": "METEOSAT-9 (MSG-2)",
"norad": "28912",
"line1": "1 28912U 05049B 26170.92883707 .00000115 00000+0 00000+0 0 9995",
"line2": "2 28912 9.3683 54.1432 0001774 346.4633 247.5699 1.00273646 7020",
"epoch": "2026-06-19 22:17:32"
},
{
"name": "DMSP 5D-3 F17 (USA 191)",
"norad": "29522",
"line1": "1 29522U 06050A 26171.42241591 -.00000013 00000+0 15609-4 0 9995",
"line2": "2 29522 98.7378 177.9264 0011060 51.0273 309.1884 14.14993196 12885",
"epoch": "2026-06-20 10:08:17"
},
{
"name": "FENGYUN 3A",
"norad": "32958",
"line1": "1 32958U 08026A 26171.43054098 .00000055 00000+0 46755-4 0 9993",
"line2": "2 32958 98.6883 112.2031 0009703 115.2254 244.9929 14.19571712935868",
"epoch": "2026-06-20 10:19:59"
},
{
"name": "GOES 14",
"norad": "35491",
"line1": "1 35491U 09033A 26171.34840896 -.00000308 00000+0 00000+0 0 9996",
"line2": "2 35491 1.6993 83.0080 0001921 13.0479 77.9267 1.00110067 6626",
"epoch": "2026-06-20 08:21:43"
},
{
"name": "DMSP 5D-3 F18 (USA 210)",
"norad": "35951",
"line1": "1 35951U 09057A 26171.39400846 .00000210 00000+0 13184-3 0 9997",
"line2": "2 35951 98.9038 152.1561 0009964 239.2588 120.7604 14.14898526860044",
"epoch": "2026-06-20 09:27:22"
},
{
"name": "EWS-G2 (GOES 15)",
"norad": "36411",
"line1": "1 36411U 10008A 26171.11943681 .00000015 00000+0 00000+0 0 9998",
"line2": "2 36411 1.2866 82.8036 0004924 42.0631 250.1257 1.00266729 59663",
"epoch": "2026-06-20 02:51:59"
},
{
"name": "COMS 1",
"norad": "36744",
"line1": "1 36744U 10032A 26171.12316773 .00000087 00000+0 00000+0 0 9990",
"line2": "2 36744 4.8136 75.3217 0011650 12.3209 286.7612 0.99163259 51117",
"epoch": "2026-06-20 02:57:22"
},
{
"name": "FENGYUN 3B",
"norad": "37214",
"line1": "1 37214U 10059A 26172.56789564 .00000611 00000+0 33905-3 0 9990",
"line2": "2 37214 98.9613 218.6016 0022188 171.6777 356.3576 14.14844553808040",
"epoch": "2026-06-21 13:37:46"
},
{
"name": "SUOMI NPP",
"norad": "37849",
"line1": "1 37849U 11061A 26171.44192975 .00000061 00000+0 50150-4 0 9999",
"line2": "2 37849 98.7953 112.2958 0001348 359.0198 1.0975 14.19517985758904",
"epoch": "2026-06-20 10:36:23"
},
{
"name": "METEOSAT-10 (MSG-3)",
"norad": "38552",
"line1": "1 38552U 12035B 26171.03561044 -.00000013 00000+0 00000+0 0 9997",
"line2": "2 38552 4.6793 61.0121 0001484 2.4454 217.8971 1.00266543 50884",
"epoch": "2026-06-20 00:51:17"
},
{
"name": "METOP-B",
"norad": "38771",
"line1": "1 38771U 12049A 26171.42762080 .00000060 00000+0 47478-4 0 9997",
"line2": "2 38771 98.6503 222.8621 0002032 10.6441 349.4779 14.21440128713725",
"epoch": "2026-06-20 10:15:46"
},
{
"name": "INSAT-3D",
"norad": "39216",
"line1": "1 39216U 13038B 26171.27504292 -.00000345 00000+0 00000+0 0 9995",
"line2": "2 39216 1.9351 82.5055 0000638 326.5441 87.9271 1.00267986 47115",
"epoch": "2026-06-20 06:36:04"
},
{
"name": "FENGYUN 3C",
"norad": "39260",
"line1": "1 39260U 13052A 26172.49624285 .00000036 00000+0 39296-4 0 9990",
"line2": "2 39260 98.4988 146.1733 0015000 15.7162 344.4476 14.15755814659172",
"epoch": "2026-06-21 11:54:35"
},
{
"name": "METEOR-M 2",
"norad": "40069",
"line1": "1 40069U 14037A 26172.50978403 -.00000166 00000+0 -56554-4 0 9991",
"line2": "2 40069 98.5120 148.0953 0006595 33.6763 326.4832 14.21461368619931",
"epoch": "2026-06-21 12:14:05"
},
{
"name": "HIMAWARI-8",
"norad": "40267",
"line1": "1 40267U 14060A 26171.46088416 -.00000280 00000+0 00000+0 0 9993",
"line2": "2 40267 0.0187 51.4118 0001168 42.2924 121.5465 1.00270806 42796",
"epoch": "2026-06-20 11:03:40"
},
{
"name": "FENGYUN 2G",
"norad": "40367",
"line1": "1 40367U 14090A 26172.16642169 -.00000306 00000+0 00000+0 0 9995",
"line2": "2 40367 5.6502 72.7719 0003712 157.9383 198.2472 1.00270539 42031",
"epoch": "2026-06-21 03:59:39"
},
{
"name": "METEOSAT-11 (MSG-4)",
"norad": "40732",
"line1": "1 40732U 15034A 26171.32244802 .00000057 00000+0 00000+0 0 9991",
"line2": "2 40732 3.2087 71.0883 0002400 346.3671 336.7454 1.00264042 7245",
"epoch": "2026-06-20 07:44:20"
},
{
"name": "ELEKTRO-L 2",
"norad": "41105",
"line1": "1 41105U 15074A 26169.03651196 -.00000126 00000+0 00000+0 0 9991",
"line2": "2 41105 6.6975 70.5132 0000902 256.1704 298.2263 1.00270556 38515",
"epoch": "2026-06-18 00:52:35"
},
{
"name": "SENTINEL-3A",
"norad": "41335",
"line1": "1 41335U 16011A 26171.44253080 .00000048 00000+0 37722-4 0 9994",
"line2": "2 41335 98.6248 238.7795 0001418 99.3701 260.7640 14.26733412538520",
"epoch": "2026-06-20 10:37:15"
},
{
"name": "INSAT-3DR",
"norad": "41752",
"line1": "1 41752U 16054A 26171.40667204 -.00000081 00000+0 00000+0 0 9999",
"line2": "2 41752 0.1148 86.2468 0010425 187.4441 215.2920 1.00271300 35856",
"epoch": "2026-06-20 09:45:36"
},
{
"name": "HIMAWARI-9",
"norad": "41836",
"line1": "1 41836U 16064A 26171.46088412 .00000000 00000+0 00000+0 0 9997",
"line2": "2 41836 0.0140 351.6731 0000932 111.3358 112.3453 1.00270283 35247",
"epoch": "2026-06-20 11:03:40"
},
{
"name": "GOES 16",
"norad": "41866",
"line1": "1 41866U 16071A 26171.35461541 -.00000095 00000+0 00000+0 0 9999",
"line2": "2 41866 0.3504 85.5238 0000306 339.5740 226.4328 1.00271436 35133",
"epoch": "2026-06-20 08:30:39"
},
{
"name": "FENGYUN 4A",
"norad": "41882",
"line1": "1 41882U 16077A 26172.55715684 -.00000359 00000+0 00000+0 0 9999",
"line2": "2 41882 2.3350 80.2401 0008569 153.4835 0.2982 1.00266450 34978",
"epoch": "2026-06-21 13:22:18"
},
{
"name": "CYGFM05",
"norad": "41884",
"line1": "1 41884U 16078A 26171.32171544 .00019452 00000+0 26251-3 0 9990",
"line2": "2 41884 34.9559 239.8312 0007362 181.9302 178.1395 15.56789762529244",
"epoch": "2026-06-20 07:43:16"
},
{
"name": "CYGFM04",
"norad": "41885",
"line1": "1 41885U 16078B 26171.41040240 .00025123 00000+0 28119-3 0 9996",
"line2": "2 41885 34.9311 206.7708 0007079 223.5840 136.4326 15.61665288529500",
"epoch": "2026-06-20 09:50:59"
},
{
"name": "CYGFM02",
"norad": "41886",
"line1": "1 41886U 16078C 26171.40489205 .00028987 00000+0 28782-3 0 9991",
"line2": "2 41886 34.9392 201.2489 0008091 224.9077 135.0995 15.64678561529674",
"epoch": "2026-06-20 09:43:03"
},
{
"name": "CYGFM01",
"norad": "41887",
"line1": "1 41887U 16078D 26171.30905894 .00020338 00000+0 26751-3 0 9994",
"line2": "2 41887 34.9389 240.9472 0007062 175.8238 184.2546 15.57471223529196",
"epoch": "2026-06-20 07:25:03"
},
{
"name": "CYGFM08",
"norad": "41888",
"line1": "1 41888U 16078E 26171.45513382 .00022398 00000+0 26567-3 0 9991",
"line2": "2 41888 34.9419 219.4708 0008738 207.6434 152.3827 15.60162461529482",
"epoch": "2026-06-20 10:55:24"
},
{
"name": "CYGFM07",
"norad": "41890",
"line1": "1 41890U 16078G 26171.41407463 .00022736 00000+0 26880-3 0 9996",
"line2": "2 41890 34.9433 208.0826 0007489 222.6573 137.3571 15.60258013529500",
"epoch": "2026-06-20 09:56:16"
},
{
"name": "CYGFM03",
"norad": "41891",
"line1": "1 41891U 16078H 26171.44907869 .00021797 00000+0 25786-3 0 9995",
"line2": "2 41891 34.9390 209.7861 0006918 211.7965 148.2343 15.60252937529484",
"epoch": "2026-06-20 10:46:40"
},
{
"name": "FENGYUN 3D",
"norad": "43010",
"line1": "1 43010U 17072A 26172.51679571 -.00000158 00000+0 -52975-4 0 9991",
"line2": "2 43010 99.0133 147.0175 0002490 55.5625 304.5785 14.19739395445605",
"epoch": "2026-06-21 12:24:11"
},
{
"name": "NOAA 20 (JPSS-1)",
"norad": "43013",
"line1": "1 43013U 17073A 26171.38467992 .00000046 00000+0 42637-4 0 9999",
"line2": "2 43013 98.7771 110.8189 0000383 117.6624 242.4591 14.19512290444925",
"epoch": "2026-06-20 09:13:56"
},
{
"name": "GOES 17",
"norad": "43226",
"line1": "1 43226U 18022A 26171.38933449 -.00000187 00000+0 00000+0 0 9995",
"line2": "2 43226 0.9463 85.6559 0000598 28.1563 205.4138 1.00269480 30451",
"epoch": "2026-06-20 09:20:38"
},
{
"name": "SENTINEL-3B",
"norad": "43437",
"line1": "1 43437U 18039A 26171.41568603 .00000074 00000+0 48499-4 0 9990",
"line2": "2 43437 98.6248 238.9017 0000727 94.6800 265.4463 14.26735540424583",
"epoch": "2026-06-20 09:58:35"
},
{
"name": "FENGYUN 2H",
"norad": "43491",
"line1": "1 43491U 18050A 26172.54842275 -.00000131 00000+0 00000+0 0 9994",
"line2": "2 43491 3.0437 79.0286 0001805 136.1614 331.3565 1.00269411 29481",
"epoch": "2026-06-21 13:09:44"
},
{
"name": "METOP-C",
"norad": "43689",
"line1": "1 43689U 18087A 26171.44664144 .00000064 00000+0 49212-4 0 9995",
"line2": "2 43689 98.6643 230.9907 0001880 125.1991 234.9363 14.21512882395295",
"epoch": "2026-06-20 10:43:10"
},
{
"name": "GEO-KOMPSAT-2A",
"norad": "43823",
"line1": "1 43823U 18100A 26171.41741235 -.00000348 00000+0 00000+0 0 9998",
"line2": "2 43823 0.0473 151.2642 0002170 295.2838 100.6088 1.00272261 27669",
"epoch": "2026-06-20 10:01:04"
},
{
"name": "METEOR-M2 2",
"norad": "44387",
"line1": "1 44387U 19038A 26172.52968670 .00000005 00000+0 22237-4 0 9992",
"line2": "2 44387 98.9151 159.8796 0001904 41.8346 318.2978 14.24366528361915",
"epoch": "2026-06-21 12:42:45"
},
{
"name": "ARKTIKA-M 1",
"norad": "47719",
"line1": "1 47719U 21016A 26168.49069990 -.00000210 00000+0 00000+0 0 9999",
"line2": "2 47719 63.2922 57.5948 7288528 269.9420 14.6012 2.00622504 38796",
"epoch": "2026-06-17 11:46:36"
},
{
"name": "FENGYUN 3E",
"norad": "49008",
"line1": "1 49008U 21062A 26172.50017913 .00000183 00000+0 10675-3 0 9999",
"line2": "2 49008 98.7495 175.8605 0002823 78.9678 281.1816 14.19904829257195",
"epoch": "2026-06-21 12:00:15"
},
{
"name": "GOES 18",
"norad": "51850",
"line1": "1 51850U 22021A 26170.93705689 .00000091 00000+0 00000+0 0 9997",
"line2": "2 51850 0.0372 171.7340 0000425 325.4670 331.2805 1.00271950 6625",
"epoch": "2026-06-19 22:29:22"
},
{
"name": "NOAA 21 (JPSS-2)",
"norad": "54234",
"line1": "1 54234U 22150A 26171.41746469 .00000044 00000+0 41590-4 0 9996",
"line2": "2 54234 98.7059 110.1525 0001836 147.2077 212.9214 14.19565986187000",
"epoch": "2026-06-20 10:01:09"
},
{
"name": "METEOSAT-12 (MTG-I1)",
"norad": "54743",
"line1": "1 54743U 22170C 26171.24937015 -.00000013 00000+0 00000+0 0 9996",
"line2": "2 54743 0.7421 23.5307 0003320 70.9892 263.3203 1.00273524 13009",
"epoch": "2026-06-20 05:59:06"
},
{
"name": "TIANMU-1 03",
"norad": "55973",
"line1": "1 55973U 23039A 26172.51236689 .00012760 00000+0 25107-3 0 9997",
"line2": "2 55973 97.5243 346.6508 0004082 88.7124 271.4593 15.47392330181500",
"epoch": "2026-06-21 12:17:48"
},
{
"name": "TIANMU-1 04",
"norad": "55974",
"line1": "1 55974U 23039B 26172.50017186 .00013293 00000+0 25608-3 0 9994",
"line2": "2 55974 97.5177 345.7923 0004718 83.9821 276.1966 15.47992980181519",
"epoch": "2026-06-21 12:00:15"
},
{
"name": "TIANMU-1 05",
"norad": "55975",
"line1": "1 55975U 23039C 26172.49333985 .00012617 00000+0 24893-3 0 9995",
"line2": "2 55975 97.5221 346.2447 0004106 104.9703 255.2001 15.47316256181481",
"epoch": "2026-06-21 11:50:25"
},
{
"name": "TIANMU-1 06",
"norad": "55976",
"line1": "1 55976U 23039D 26172.50381902 .00013184 00000+0 25487-3 0 9990",
"line2": "2 55976 97.5229 346.8955 0004324 99.6322 260.5416 15.47896063181511",
"epoch": "2026-06-21 12:05:30"
},
{
"name": "FENGYUN 3G",
"norad": "56232",
"line1": "1 56232U 23055A 26172.48258118 .00019904 00000+0 32143-3 0 9996",
"line2": "2 56232 49.9935 323.8862 0007121 314.2701 45.7681 15.52798254180671",
"epoch": "2026-06-21 11:34:55"
},
{
"name": "METEOR-M2 3",
"norad": "57166",
"line1": "1 57166U 23091A 26172.52499583 -.00000006 00000+0 16221-4 0 9995",
"line2": "2 57166 98.6080 227.4146 0004589 144.4775 215.6710 14.24047159155136",
"epoch": "2026-06-21 12:36:00"
},
{
"name": "TIANMU-1 07",
"norad": "57399",
"line1": "1 57399U 23101A 26172.48254884 .00004853 00000+0 17043-3 0 9998",
"line2": "2 57399 97.2589 223.0524 0004274 112.8221 247.3472 15.29848940162308",
"epoch": "2026-06-21 11:34:52"
},
{
"name": "TIANMU-1 08",
"norad": "57400",
"line1": "1 57400U 23101B 26172.51458675 .00005094 00000+0 17870-3 0 9995",
"line2": "2 57400 97.2578 222.8538 0005687 104.7184 255.4688 15.29854432162300",
"epoch": "2026-06-21 12:21:00"
},
{
"name": "TIANMU-1 09",
"norad": "57401",
"line1": "1 57401U 23101C 26172.49791285 .00005141 00000+0 18018-3 0 9998",
"line2": "2 57401 97.2536 222.2510 0004785 117.1207 243.0522 15.29886210162300",
"epoch": "2026-06-21 11:57:00"
},
{
"name": "TIANMU-1 10",
"norad": "57402",
"line1": "1 57402U 23101D 26172.46563515 .00005026 00000+0 17632-3 0 9992",
"line2": "2 57402 97.2615 223.2743 0006346 115.1029 245.0871 15.29857439162291",
"epoch": "2026-06-21 11:10:31"
},
{
"name": "FENGYUN 3F",
"norad": "57490",
"line1": "1 57490U 23111A 26172.48399582 .00000011 00000+0 25698-4 0 9994",
"line2": "2 57490 98.6835 242.4127 0001582 90.3874 269.7484 14.19939702149475",
"epoch": "2026-06-21 11:36:57"
},
{
"name": "ARKTIKA-M 2",
"norad": "58584",
"line1": "1 58584U 23198A 26168.75920439 .00000145 00000+0 00000+0 0 9993",
"line2": "2 58584 63.2243 154.2920 6902344 267.4286 18.7609 2.00612717 18330",
"epoch": "2026-06-17 18:13:15"
},
{
"name": "TIANMU-1 11",
"norad": "58645",
"line1": "1 58645U 23205A 26172.50950514 .00004206 00000+0 16121-3 0 9992",
"line2": "2 58645 97.3448 193.9985 0008423 2.1524 357.9750 15.27032570138189",
"epoch": "2026-06-21 12:13:41"
},
{
"name": "TIANMU-1 12",
"norad": "58646",
"line1": "1 58646U 23205B 26172.54550061 .00004499 00000+0 17246-3 0 9993",
"line2": "2 58646 97.3438 193.7888 0006195 347.6663 12.4424 15.26998880138178",
"epoch": "2026-06-21 13:05:31"
},
{
"name": "TIANMU-1 13",
"norad": "58647",
"line1": "1 58647U 23205C 26172.51256397 .00004285 00000+0 16517-3 0 9999",
"line2": "2 58647 97.3464 194.0296 0005433 323.2905 36.7961 15.26851187138160",
"epoch": "2026-06-21 12:18:06"
},
{
"name": "TIANMU-1 14",
"norad": "58648",
"line1": "1 58648U 23205D 26172.49764391 .00004278 00000+0 16465-3 0 9999",
"line2": "2 58648 97.3401 193.1933 0006382 328.1981 31.8872 15.26895930138163",
"epoch": "2026-06-21 11:56:36"
},
{
"name": "TIANMU-1 19",
"norad": "58660",
"line1": "1 58660U 23208A 26172.40296416 .00006532 00000+0 21646-3 0 9997",
"line2": "2 58660 97.4505 288.7245 0003546 308.1636 51.9285 15.31627133137977",
"epoch": "2026-06-21 09:40:16"
},
{
"name": "TIANMU-1 20",
"norad": "58661",
"line1": "1 58661U 23208B 26172.56804883 .00006296 00000+0 20107-3 0 9996",
"line2": "2 58661 97.4447 289.5303 0005732 315.3365 44.7415 15.32819357138096",
"epoch": "2026-06-21 13:37:59"
},
{
"name": "TIANMU-1 21",
"norad": "58662",
"line1": "1 58662U 23208C 26172.48460245 .00007912 00000+0 23428-3 0 9993",
"line2": "2 58662 97.4507 292.1457 0005737 305.0266 55.0438 15.35141113138221",
"epoch": "2026-06-21 11:37:50"
},
{
"name": "TIANMU-1 22",
"norad": "58663",
"line1": "1 58663U 23208D 26172.51689759 .00005940 00000+0 20026-3 0 9991",
"line2": "2 58663 97.4501 288.7713 0002791 346.8495 13.2672 15.31108395137996",
"epoch": "2026-06-21 12:24:20"
},
{
"name": "TIANMU-1 15",
"norad": "58700",
"line1": "1 58700U 24004A 26172.55454425 .00004449 00000+0 17035-3 0 9991",
"line2": "2 58700 97.4674 358.7457 0006262 346.3791 13.7278 15.27060875136444",
"epoch": "2026-06-21 13:18:33"
},
{
"name": "TIANMU-1 16",
"norad": "58701",
"line1": "1 58701U 24004B 26172.54456803 .00004530 00000+0 17484-3 0 9992",
"line2": "2 58701 97.4622 357.9677 0005379 352.3663 7.7493 15.26785843136446",
"epoch": "2026-06-21 13:04:11"
},
{
"name": "TIANMU-1 17",
"norad": "58702",
"line1": "1 58702U 24004C 26172.53002447 .00004662 00000+0 17987-3 0 9997",
"line2": "2 58702 97.4648 358.4398 0005070 359.4602 0.6630 15.26784809136435",
"epoch": "2026-06-21 12:43:14"
},
{
"name": "TIANMU-1 18",
"norad": "58703",
"line1": "1 58703U 24004D 26172.50568463 .00004179 00000+0 16032-3 0 9993",
"line2": "2 58703 97.4666 358.6735 0003609 0.4275 359.6966 15.27041126136436",
"epoch": "2026-06-21 12:08:11"
},
{
"name": "INSAT-3DS",
"norad": "58990",
"line1": "1 58990U 24033A 26171.40823205 -.00000160 00000+0 00000+0 0 9990",
"line2": "2 58990 0.0479 106.3313 0001171 331.3573 59.9873 1.00271082 47109",
"epoch": "2026-06-20 09:47:51"
},
{
"name": "METEOR-M2 4",
"norad": "59051",
"line1": "1 59051U 24039A 26172.54463940 .00000004 00000+0 21415-4 0 9991",
"line2": "2 59051 98.7012 131.7880 0007535 149.0442 211.1182 14.22429890119885",
"epoch": "2026-06-21 13:04:17"
},
{
"name": "GOES 19",
"norad": "60133",
"line1": "1 60133U 24119A 26171.37127980 -.00000253 00000+0 00000+0 0 9993",
"line2": "2 60133 0.0015 112.2371 0000407 49.5773 165.2311 1.00270517 6990",
"epoch": "2026-06-20 08:54:39"
},
{
"name": "MTG-S1",
"norad": "64723",
"line1": "1 64723U 25143A 26171.39155049 -.00000005 00000+0 00000+0 0 9994",
"line2": "2 64723 0.6001 339.7327 0000829 294.1194 133.2187 1.00399694 3452",
"epoch": "2026-06-20 09:23:50"
},
{
"name": "METOP-SGA1",
"norad": "65159",
"line1": "1 65159U 25172A 26167.15332899 .00000049 00000+0 42000-4 0 9997",
"line2": "2 65159 98.6644 226.8998 0000902 93.8213 266.3067 14.21509720 43644",
"epoch": "2026-06-16 03:40:48"
},
{
"name": "FENGYUN 3H",
"norad": "65815",
"line1": "1 65815U 25219A 26171.42343481 .00000033 00000+0 36111-4 0 9991",
"line2": "2 65815 98.6768 112.1297 0001572 74.5092 285.6258 14.19920896 37843",
"epoch": "2026-06-20 10:09:45"
}
]
}
+28
View File
@@ -0,0 +1,28 @@
<?php
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store');
echo json_encode([
'groups' => [
[
'id' => 'starlink',
'name' => 'Starlink (CelesTrak)'
],
[
'id' => 'noaa',
'name' => 'NOAA (CelesTrak)'
],
[
'id' => 'weather',
'name' => 'Weather (CelesTrak)'
],
[
'id' => 'active',
'name' => 'Active (CelesTrak)'
],
[
'id' => 'stations',
'name' => 'Space Stations (CelesTrak)'
]
]
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
+209
View File
@@ -0,0 +1,209 @@
<?php
// sat.thedarkelite.com - TLE proxy + cache (fast + stale fallback)
//
// CelesTrak provides 3-line sets (name + line1 + line2).[6](https://celestrak.org/norad/documentation/tle-fmt.php)
// CelesTrak recommends updated access patterns for GP data and supports multiple formats.[2](https://www.celestrak.org/NORAD/documentation/gp-data-formats.php)[1](https://celestrak.org/NORAD/elements/)
header('Content-Type: application/json; charset=utf-8');
$start = microtime(true);
$group = isset($_GET['group']) ? strtolower(trim($_GET['group'])) : 'starlink';
function gpUrl(string $group, string $format = 'tle'): string {
// Modern group endpoint (preferred for reliability)
// See CelesTrak GP data format guidance + Current GP element sets index.[2](https://www.celestrak.org/NORAD/documentation/gp-data-formats.php)[1](https://celestrak.org/NORAD/elements/)
return 'https://celestrak.org/NORAD/elements/gp.php?GROUP=' . rawurlencode($group) . '&FORMAT=' . rawurlencode($format);
}
// Use gp.php for groups that have shown 404s on static .txt,
// keep starlink static since it commonly exists and is huge.
$allowed = [
'starlink' => ['name' => 'Starlink', 'url' => 'https://celestrak.org/NORAD/elements/gp.php?GROUP=starlink&FORMAT=tle'],
'noaa' => ['name' => 'NOAA', 'url' => 'https://celestrak.org/NORAD/elements/gp.php?GROUP=noaa&FORMAT=tle'],
'weather' => ['name' => 'Weather', 'url' => gpUrl('weather', 'tle')],
'active' => ['name' => 'Active', 'url' => gpUrl('active', 'tle')],
'stations' => ['name' => 'Space Stations', 'url' => gpUrl('stations', 'tle')],
];
if (!isset($allowed[$group])) {
http_response_code(400);
echo json_encode(['error' => 'Invalid group'], JSON_PRETTY_PRINT);
exit;
}
$cacheDir = __DIR__ . '/cache';
if (!is_dir($cacheDir)) @mkdir($cacheDir, 0775, true);
$cacheKey = preg_replace('/[^a-z0-9_\-]/', '_', $group);
$cacheFile = $cacheDir . "/tle_{$cacheKey}.json";
// TTL: 2 hours default (CelesTrak updates on a multi-hour cadence; frequent pulls can cause blocks).[7](https://usradioguy.com/wxtoimg-kepler-fix/)[1](https://celestrak.org/NORAD/elements/)
$ttlSeconds = 2 * 60 * 60;
$now = time();
$emitCache = function(array $cached, bool $isFresh) use ($ttlSeconds, $cacheFile, $now, $start) {
$age = is_file($cacheFile) ? ($now - filemtime($cacheFile)) : null;
$cached['meta']['cached'] = true;
$cached['meta']['cacheAgeSeconds'] = $age;
$cached['meta']['cacheTtlSeconds'] = $ttlSeconds;
$cached['meta']['servedFrom'] = $isFresh ? 'cache_fresh' : 'cache_stale';
$cached['meta']['serverMs'] = (int)round((microtime(true) - $start) * 1000);
header('Cache-Control: public, max-age=60');
echo json_encode($cached, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
exit;
};
// Serve fresh cache
if (is_file($cacheFile)) {
$age = $now - filemtime($cacheFile);
if ($age < $ttlSeconds) {
$cached = json_decode(@file_get_contents($cacheFile), true);
if (is_array($cached)) $emitCache($cached, true);
}
}
// Fetch upstream (fail fast)
$url = $allowed[$group]['url'];
$ua = 'sat.thedarkelite.com (TLE proxy; contact: admin@thedarkelite.com)';
$text = null;
$httpCode = 0;
$curlErr = null;
$connectTimeout = 4;
$totalTimeout = 12;
if (function_exists('curl_init')) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_CONNECTTIMEOUT => $connectTimeout,
CURLOPT_TIMEOUT => $totalTimeout,
CURLOPT_USERAGENT => $ua,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
CURLOPT_ENCODING => '',
]);
$text = curl_exec($ch);
$httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlErr = curl_error($ch);
curl_close($ch);
if ($text === false || $httpCode >= 400) {
$text = null;
}
}
if ($text === null) {
// Serve stale cache if possible
if (is_file($cacheFile)) {
$cached = json_decode(@file_get_contents($cacheFile), true);
if (is_array($cached)) {
$cached['meta']['upstreamError'] = $curlErr ?: "Upstream fetch failed (HTTP $httpCode)";
$cached['meta']['upstream'] = $url;
$emitCache($cached, false);
}
}
http_response_code(502);
echo json_encode([
'error' => 'Failed to fetch TLEs from upstream',
'meta' => [
'group' => $group,
'upstream' => $url,
'httpCode' => $httpCode,
'curlError' => $curlErr,
'serverMs' => (int)round((microtime(true) - $start) * 1000),
]
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
exit;
}
$parsed = parseTleText($text);
$fetchedAtUtc = gmdate('Y-m-d H:i:s');
$out = [
'meta' => [
'source' => 'CelesTrak',
'group' => $group,
'groupName' => $allowed[$group]['name'],
'upstream' => $url,
'cached' => false,
'fetchedAtUtc' => $fetchedAtUtc,
'cacheTtlSeconds' => $ttlSeconds,
'count' => count($parsed),
'upstreamHttpCode' => $httpCode,
'serverMs' => (int)round((microtime(true) - $start) * 1000),
],
'satellites' => $parsed
];
@file_put_contents($cacheFile, json_encode($out, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
header('Cache-Control: public, max-age=60');
echo json_encode($out, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
// ---- Helpers ----
function parseTleText(string $text): array {
// TLE sets are typically name line + line1 + line2.[6](https://celestrak.org/norad/documentation/tle-fmt.php)
$lines = preg_split("/\r\n|\n|\r/", trim($text));
$lines = array_values(array_filter($lines, fn($l) => trim($l) !== ''));
$out = [];
$i = 0;
while ($i < count($lines)) {
$name = trim($lines[$i] ?? '');
$l1 = trim($lines[$i+1] ?? '');
$l2 = trim($lines[$i+2] ?? '');
if ($name !== '' && $l1 !== '' && $l2 !== '' && $l1[0] === '1' && $l2[0] === '2') {
// NORAD number is columns 03-07 in line 1.[6](https://celestrak.org/norad/documentation/tle-fmt.php)
$norad = trim(substr($l1, 2, 5));
if ($norad === '') $norad = '0';
// Epoch year/day are in line1 columns 19-32.[6](https://celestrak.org/norad/documentation/tle-fmt.php)
$epochRaw = trim(substr($l1, 18, 14));
$epochStr = epochToIso($epochRaw);
$out[] = [
'name' => $name,
'norad' => $norad,
'line1' => $l1,
'line2' => $l2,
'epoch' => $epochStr
];
$i += 3;
continue;
}
$i += 1;
}
return $out;
}
function epochToIso(string $epochRaw): ?string {
if ($epochRaw === '' || !preg_match('/^(\d{2})(\d{3}\.\d+)$/', $epochRaw, $m)) return null;
$yy = intval($m[1], 10);
$day = floatval($m[2]);
$year = ($yy >= 57) ? (1900 + $yy) : (2000 + $yy);
$dayInt = (int)floor($day);
$frac = $day - $dayInt;
$dt = new DateTime("$year-01-01 00:00:00", new DateTimeZone("UTC"));
$dt->modify('+' . ($dayInt - 1) . ' days');
$seconds = (int)round($frac * 86400);
$dt->modify('+' . $seconds . ' seconds');
return $dt->format('Y-m-d H:i:s');
}
+174
View File
@@ -0,0 +1,174 @@
:root{
--bg:#0b0f19;
--panel:#0f1626;
--panel2:#0d1322;
--text:#e7eaf0;
--muted:#9aa6bd;
--border:#1f2a44;
--accent:#6d5efc;
--good:#22c55e;
--warn:#f59e0b;
--shadow: 0 10px 30px rgba(0,0,0,.35);
--radius:18px;
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono","Courier New", monospace;
--sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji";
}
*{box-sizing:border-box}
html,body{height:100%;margin:0;background:var(--bg);color:var(--text);font-family:var(--sans)}
a{color:inherit}
.app{display:flex;height:100vh;width:100vw;overflow:hidden}
.sidebar{
width:360px; min-width:320px; max-width:420px;
background:linear-gradient(180deg, var(--panel) 0%, var(--panel2) 100%);
border-right:1px solid var(--border);
padding:16px;
display:flex; flex-direction:column; gap:14px;
}
.brand{display:flex; align-items:center; gap:12px; padding:8px 8px 2px}
.logo{
width:44px;height:44px;border-radius:14px;
background: radial-gradient(circle at 30% 20%, #8b7cff 0%, #6d5efc 40%, #3b33b8 100%);
box-shadow: var(--shadow);
display:flex; align-items:center; justify-content:center;
font-weight:800; letter-spacing:.6px;
}
.brandText .title{font-weight:700}
.brandText .subtitle{color:var(--muted);font-size:12px;margin-top:2px}
.panel{
background: rgba(255,255,255,.02);
border:1px solid var(--border);
border-radius:var(--radius);
padding:14px;
box-shadow: var(--shadow);
overflow:auto;
}
.label{display:block;margin:10px 2px 6px;color:var(--muted);font-size:12px}
.select,.input{
width:100%;
background:#0a1020;
border:1px solid var(--border);
color:var(--text);
border-radius:12px;
padding:10px 12px;
outline:none;
}
.select:focus,.input:focus{border-color:#2a3b68; box-shadow:0 0 0 3px rgba(109,94,252,.18)}
.hint{color:var(--muted);font-size:12px;margin-top:6px;line-height:1.3}
.row{display:flex; gap:12px; margin-top:8px}
.col{flex:1}
.toggles{margin-top:10px; display:flex; flex-direction:column; gap:8px}
.toggle{
display:flex; align-items:center; gap:10px;
padding:10px 10px;
border:1px solid rgba(31,42,68,.8);
background: rgba(0,0,0,.12);
border-radius:14px;
color:var(--text);
user-select:none;
}
.toggle input{accent-color:var(--accent); width:16px; height:16px}
.buttons{margin-top:12px}
.btn{
border:1px solid var(--border);
background: rgba(255,255,255,.04);
color:var(--text);
border-radius:14px;
padding:10px 12px;
cursor:pointer;
transition: transform .05s ease, background .15s ease, border-color .15s ease;
}
.btn:hover{background: rgba(255,255,255,.06); border-color:#2a3b68}
.btn:active{transform: translateY(1px)}
.btn.primary{
background: rgba(109,94,252,.18);
border-color: rgba(109,94,252,.45);
}
.btn.primary:hover{background: rgba(109,94,252,.26)}
.btn.icon{padding:6px 10px;border-radius:12px}
.stats{
margin-top:14px;
border-top:1px solid rgba(31,42,68,.7);
padding-top:12px;
display:flex; flex-direction:column; gap:6px;
}
.k{color:var(--muted);font-size:12px}
.v{font-weight:650}
.small{font-size:12px;color:var(--muted);line-height:1.35}
.mono{font-family:var(--mono)}
.main{flex:1; position:relative}
.globe{height:100%; width:100%}
/* Cesium container must fill */
#cesiumContainer, .cesium-viewer, .cesium-viewer-cesiumWidget {
width: 100%;
height: 100%;
}
/* Hide default Cesium credits (we provide our own attribution elsewhere if you want) */
.cesium-widget-credits { display:none !important; }
/* Top chips */
.topbar{
position:absolute; top:14px; left:14px; right:14px;
display:flex; gap:10px; justify-content:flex-end;
pointer-events:none;
}
.chip{
pointer-events:none;
padding:8px 10px;
border-radius:999px;
background: rgba(15,22,38,.75);
border:1px solid rgba(31,42,68,.75);
backdrop-filter: blur(8px);
box-shadow: var(--shadow);
font-size:12px;
color: var(--muted);
}
.chip.warn{color:#fff; background: rgba(245,158,11,.20); border-color: rgba(245,158,11,.45)}
/* Detail card */
.detail{
position:absolute; right:14px; bottom:14px;
width:min(520px, calc(100% - 28px));
background: rgba(15,22,38,.92);
border:1px solid rgba(31,42,68,.9);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding:12px;
backdrop-filter: blur(10px);
}
.detailHeader{display:flex; align-items:center; justify-content:space-between; gap:12px; padding:4px 2px 10px}
.detailTitle{font-weight:750}
.detailGrid{
display:grid;
grid-template-columns: repeat(2, minmax(0,1fr));
gap:8px 16px;
padding:8px 2px 10px;
border-top:1px solid rgba(31,42,68,.65);
border-bottom:1px solid rgba(31,42,68,.65);
}
.detailTle{
white-space:pre;
margin-top:10px;
background: rgba(0,0,0,.18);
border:1px solid rgba(31,42,68,.65);
border-radius:14px;
padding:10px;
overflow:auto;
max-height:140px;
}
/* Static crisp background image behind Cesium */
#cesiumContainer{
background: url("/assets/bg/space.jpg") center center / cover no-repeat;
background-color: #05060a; /* fallback */
}
+618
View File
@@ -0,0 +1,618 @@
/* sat.thedarkelite.com - Cesium globe client
- Static crisp background image behind Cesium (/assets/bg/space.jpg) with subtle drift
- Cesium canvas made transparent (alpha) so CSS background shows through
- Radar overlay (RainViewer) as an imagery layer (tiles)
- Satellites via TLE -> SGP4 (satellite.js)
*/
const API = {
groups: "/api/groups.php",
tle: (group) => `/api/tle.php?group=${encodeURIComponent(group)}`
};
// RainViewer Weather Maps API endpoint (public)
const RADAR = {
api: "https://api.rainviewer.com/public/weather-maps.json", // provides host + frames[1](https://www.rainviewer.com/api/weather-maps-api.html)
size: 256,
color: 2, // universal blue (good default)
options: "1_0", // smooth=1, snow=0 (see docs)[1](https://www.rainviewer.com/api/weather-maps-api.html)
opacity: 0.55,
refreshMs: 5 * 60 * 1000
};
const state = {
groups: [],
group: "noaa",
sats: [],
satsFiltered: [],
satrecs: new Map(), // norad -> satrec
entities: new Map(), // norad -> Cesium.Entity
selected: null, // norad
trackEntity: null,
lastMeta: null,
timer: null,
// background drift
drift: { enabled: true, raf: null },
// radar
radar: {
layer: null, // Cesium.ImageryLayer
provider: null, // Cesium.UrlTemplateImageryProvider
framePath: null, // current /v2/radar/##########
lastUpdate: 0,
timer: null
}
};
const el = (id) => document.getElementById(id);
function fmt(n, d = 3) {
if (n === null || n === undefined || Number.isNaN(n)) return "—";
return Number(n).toFixed(d);
}
function setWarn(msg) {
const chip = el("chipWarn");
if (!chip) return;
if (!msg) {
chip.style.display = "none";
chip.textContent = "";
return;
}
chip.style.display = "inline-flex";
chip.textContent = msg;
}
function metaString(meta) {
if (!meta) return "—";
const parts = [];
if (meta.groupName) parts.push(meta.groupName);
if (meta.source) parts.push(meta.source);
if (meta.cached !== undefined) parts.push(meta.cached ? "cached" : "fresh");
if (meta.fetchedAtUtc) parts.push(`fetched ${meta.fetchedAtUtc} UTC`);
if (meta.cacheTtlSeconds) parts.push(`TTL ${Math.round(meta.cacheTtlSeconds / 60)}m`);
if (meta.count !== undefined) parts.push(`${meta.count} sats`);
return parts.join(" • ");
}
/* ---------- Cesium init ---------- */
const viewer = new Cesium.Viewer("cesiumContainer", {
animation: false,
timeline: false,
baseLayerPicker: false,
homeButton: false,
geocoder: false,
sceneModePicker: false,
navigationHelpButton: false,
fullscreenButton: false,
infoBox: false,
selectionIndicator: false,
shouldAnimate: false,
// KEY: make the canvas support transparency so CSS background shows through
contextOptions: {
webgl: { alpha: true }
}
});
// --- BASEMAP (labeled, dark) ---
// Using CARTO dark_all with proper "@2x" support via customTags.
// CARTO documents optional @2x tiles as "{scale}" in URL pattern.[5](https://cdnjs.com/libraries/leaflet)
viewer.imageryLayers.removeAll();
const basemap = viewer.imageryLayers.addImageryProvider(
new Cesium.UrlTemplateImageryProvider({
url: "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{scale}.png",
subdomains: ["a", "b", "c", "d"],
maximumLevel: 20,
credit: "© OpenStreetMap contributors © CARTO",
customTags: {
scale: () => (window.devicePixelRatio > 1 ? "@2x" : "")
}
})
); // UrlTemplateImageryProvider supports URL templates + customTags[4](https://usradioguy.com/wxtoimg-kepler-fix/)
// Minor “ops” tuning
basemap.brightness = 0.90;
basemap.contrast = 1.15;
basemap.saturation = 0.95;
basemap.gamma = 1.05;
// ---- CLEAN CRISP BACKGROUND IMAGE MODE ----
// Disable Cesium skybox entirely (prevents blurry/noisy stars). SkyBox can be assigned/removed.[6](https://github.com/CesiumGS/cesium/releases)
viewer.scene.skyBox = null;
viewer.scene.skyAtmosphere.show = false;
viewer.scene.fog.enabled = false;
// Make Cesium background transparent so the CSS image is visible
viewer.scene.backgroundColor = Cesium.Color.TRANSPARENT;
viewer.scene.globe.depthTestAgainstTerrain = false;
// Start camera over North America
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(-98.5, 38.5, 12_500_000)
});
function applyLightingToggle() {
const t = el("toggleLighting");
viewer.scene.globe.enableLighting = !!(t && t.checked);
}
/* ---------- Background drift (subtle parallax) ---------- */
function startBackgroundDrift() {
if (!state.drift.enabled) return;
const container = document.getElementById("cesiumContainer");
if (!container) return;
const maxShiftPx = 28;
const smooth = 0.10;
let curX = 50;
let curY = 50;
function clamp(v, min, max) { return Math.min(max, Math.max(min, v)); }
function tick() {
const heading = viewer.camera.heading || 0;
const pitch = viewer.camera.pitch || 0;
let hn = ((heading + Math.PI) % (Math.PI * 2)) - Math.PI;
const targetXpx = (hn / Math.PI) * maxShiftPx;
const targetYpx = (pitch / (Math.PI / 2)) * (maxShiftPx * 0.6);
const w = container.clientWidth || 1;
const h = container.clientHeight || 1;
const targetX = 50 + (targetXpx / w) * 100;
const targetY = 50 + (targetYpx / h) * 100;
curX = curX + (targetX - curX) * smooth;
curY = curY + (targetY - curY) * smooth;
curX = clamp(curX, 45, 55);
curY = clamp(curY, 46, 54);
container.style.backgroundPosition = `${curX.toFixed(2)}% ${curY.toFixed(2)}%`;
state.drift.raf = requestAnimationFrame(tick);
}
if (state.drift.raf) cancelAnimationFrame(state.drift.raf);
state.drift.raf = requestAnimationFrame(tick);
}
/* ---------- Radar overlay (RainViewer tiles) ---------- */
/*
RainViewer provides:
- JSON listing frames, plus "host" and "path" fields
- Tile URL format: {host}{path}/{size}/{z}/{x}/{y}/{color}/{options}.png[1](https://www.rainviewer.com/api/weather-maps-api.html)
- Public API limitations include max zoom 7 in examples/docs[3](https://github.com/rainviewer/rainviewer-api-example/blob/master/README.md)[1](https://www.rainviewer.com/api/weather-maps-api.html)
*/
async function updateRadarLayer(force = false) {
const now = Date.now();
if (!force && now - state.radar.lastUpdate < RADAR.refreshMs) return;
state.radar.lastUpdate = now;
let data;
try {
const res = await fetch(RADAR.api, { cache: "no-store" });
if (!res.ok) throw new Error(`RainViewer HTTP ${res.status}`);
data = await res.json();
} catch (e) {
// dont kill app if radar fails
console.warn("Radar fetch failed:", e);
return;
}
const host = data.host;
const past = data?.radar?.past;
if (!host || !Array.isArray(past) || past.length === 0) return;
// Use latest past frame (stable)
const latest = past[past.length - 1];
const path = latest.path; // like "/v2/radar/##########"[1](https://www.rainviewer.com/api/weather-maps-api.html)
if (!path) return;
// If frame didnt change, nothing to do
if (state.radar.framePath === path && state.radar.layer) return;
state.radar.framePath = path;
// Build tile template
const url =
`${host}${path}/${RADAR.size}/{z}/{x}/{y}/${RADAR.color}/${RADAR.options}.png`;
const provider = new Cesium.UrlTemplateImageryProvider({
url,
maximumLevel: 7, // public max zoom per RainViewer docs/examples[1](https://www.rainviewer.com/api/weather-maps-api.html)[3](https://github.com/rainviewer/rainviewer-api-example/blob/master/README.md)
credit: "Radar: RainViewer"
}); // UrlTemplateImageryProvider supports tile templates[4](https://usradioguy.com/wxtoimg-kepler-fix/)
// Remove old layer if exists
if (state.radar.layer) {
viewer.imageryLayers.remove(state.radar.layer, true);
state.radar.layer = null;
}
state.radar.provider = provider;
state.radar.layer = viewer.imageryLayers.addImageryProvider(provider);
// Put radar above basemap
// (it is added last so its already on top; keep explicit ordering safe)
state.radar.layer.alpha = RADAR.opacity;
}
function startRadarAutoRefresh() {
// Load immediately, then every 5 min
updateRadarLayer(true);
if (state.radar.timer) clearInterval(state.radar.timer);
state.radar.timer = setInterval(() => updateRadarLayer(false), RADAR.refreshMs);
}
/* ---------- Click picking ---------- */
const clickHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
clickHandler.setInputAction((movement) => {
const picked = viewer.scene.pick(movement.position);
if (Cesium.defined(picked) && picked.id && picked.id.id) {
selectSatellite(String(picked.id.id));
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
/* ---------- Data + propagation ---------- */
async function loadGroups() {
const res = await fetch(API.groups, { cache: "no-store" });
if (!res.ok) throw new Error(`groups.php HTTP ${res.status}`);
const data = await res.json();
state.groups = data.groups || [];
return state.groups;
}
function fillGroupSelect() {
const sel = el("groupSelect");
sel.innerHTML = "";
for (const g of state.groups) {
const opt = document.createElement("option");
opt.value = g.id;
opt.textContent = g.name;
if (g.id === state.group) opt.selected = true;
sel.appendChild(opt);
}
}
async function loadTLEs(group) {
setWarn("");
el("statMeta").textContent = "Loading…";
const res = await fetch(API.tle(group), { cache: "no-store" });
const txt = await res.text();
if (!res.ok) throw new Error(`tle.php HTTP ${res.status} ${txt}`);
const data = JSON.parse(txt);
state.lastMeta = data.meta || null;
state.sats = (data.satellites || []).map((s) => ({
name: s.name,
norad: String(s.norad),
line1: s.line1,
line2: s.line2,
epoch: s.epoch || null
}));
state.satrecs.clear();
for (const s of state.sats) {
try {
const satrec = satellite.twoline2satrec(s.line1, s.line2);
state.satrecs.set(s.norad, satrec);
} catch (_) {}
}
el("statLoaded").textContent = String(state.sats.length);
el("statMeta").textContent = metaString(state.lastMeta);
clearSelection();
applyFilterAndRender();
}
function filterSats() {
const q = (el("searchBox").value || "").trim().toLowerCase();
const max = Math.max(50, Math.min(5000, parseInt(el("maxSats").value || "800", 10)));
let arr = state.sats;
if (q) {
arr = arr.filter(
(s) => s.name.toLowerCase().includes(q) || s.norad.toLowerCase() === q
);
}
arr = arr.slice(0, max);
state.satsFiltered = arr;
el("statShown").textContent = String(arr.length);
}
function computeLatLonAltKm(norad, date) {
const satrec = state.satrecs.get(norad);
if (!satrec) return null;
const pv = satellite.propagate(satrec, date);
if (!pv.position) return null;
const gmst = satellite.gstime(date);
const gd = satellite.eciToGeodetic(pv.position, gmst);
const lat = satellite.degreesLat(gd.latitude);
const lon = satellite.degreesLong(gd.longitude);
const altKm = gd.height;
let speed = null;
if (pv.velocity) {
const v = pv.velocity;
speed = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
}
return { lat, lon, altKm, speed };
}
/* ---------- Entities ---------- */
function clearEntities() {
for (const ent of state.entities.values()) viewer.entities.remove(ent);
state.entities.clear();
if (state.trackEntity) {
viewer.entities.remove(state.trackEntity);
state.trackEntity = null;
}
}
function makeSatEntity(sat) {
return viewer.entities.add({
id: sat.norad,
name: sat.name,
position: Cesium.Cartesian3.fromDegrees(0, 0, 500000),
point: {
pixelSize: 7,
color: Cesium.Color.fromCssColorString("#9b8cff"),
outlineColor: Cesium.Color.fromCssColorString("#000000"),
outlineWidth: 2,
disableDepthTestDistance: Number.POSITIVE_INFINITY
}
});
}
function applyFilterAndRender() {
filterSats();
clearEntities();
for (const sat of state.satsFiltered) {
const ent = makeSatEntity(sat);
state.entities.set(sat.norad, ent);
}
tick();
}
function selectSatellite(norad) {
state.selected = norad;
const sat = state.sats.find((x) => x.norad === norad) || null;
el("statSelected").textContent = sat ? `${sat.name} (${sat.norad})` : "—";
for (const [id, ent] of state.entities.entries()) {
const active = id === norad;
if (ent.point) {
ent.point.pixelSize = active ? 11 : 7;
ent.point.color = active
? Cesium.Color.fromCssColorString("#22c55e")
: Cesium.Color.fromCssColorString("#9b8cff");
ent.point.outlineColor = active
? Cesium.Color.fromCssColorString("#ffffff")
: Cesium.Color.fromCssColorString("#000000");
ent.point.outlineWidth = 2;
}
if (el("toggleLabels").checked && active && sat) {
ent.label = new Cesium.LabelGraphics({
text: `${sat.name} (${sat.norad})`,
font: "14px ui-sans-serif",
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 3,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
pixelOffset: new Cesium.Cartesian2(12, -12),
disableDepthTestDistance: Number.POSITIVE_INFINITY
});
} else {
ent.label = undefined;
}
}
if (!sat) {
el("detail").style.display = "none";
if (state.trackEntity) {
viewer.entities.remove(state.trackEntity);
state.trackEntity = null;
}
return;
}
el("detailTitle").textContent = sat.name;
el("detailNorad").textContent = sat.norad;
el("detailTle").textContent = `${sat.line1}\n${sat.line2}`;
el("detailEpoch").textContent = sat.epoch || "—";
el("detail").style.display = "block";
const pos = computeLatLonAltKm(norad, new Date());
if (pos) {
const height = Math.max(1_500_000, pos.altKm * 1000 * 6);
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat, height),
duration: 0.6
});
}
drawTrackIfEnabled();
}
function clearSelection() {
state.selected = null;
el("statSelected").textContent = "—";
el("detail").style.display = "none";
for (const ent of state.entities.values()) {
if (ent.point) {
ent.point.pixelSize = 7;
ent.point.color = Cesium.Color.fromCssColorString("#9b8cff");
ent.point.outlineColor = Cesium.Color.fromCssColorString("#000000");
ent.point.outlineWidth = 2;
}
ent.label = undefined;
}
if (state.trackEntity) {
viewer.entities.remove(state.trackEntity);
state.trackEntity = null;
}
}
function drawTrackIfEnabled() {
if (!el("toggleTracks").checked) {
if (state.trackEntity) {
viewer.entities.remove(state.trackEntity);
state.trackEntity = null;
}
return;
}
if (!state.selected) return;
const norad = state.selected;
const now = new Date();
const positions = [];
for (let t = -10 * 60; t <= 80 * 60; t += 30) {
const d = new Date(now.getTime() + t * 1000);
const p = computeLatLonAltKm(norad, d);
if (!p) continue;
positions.push(Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.altKm * 1000));
}
if (state.trackEntity) {
viewer.entities.remove(state.trackEntity);
state.trackEntity = null;
}
state.trackEntity = viewer.entities.add({
id: "__track__",
polyline: {
positions,
width: 2,
material: new Cesium.PolylineGlowMaterialProperty({
glowPower: 0.12,
color: Cesium.Color.fromCssColorString("#22c55e")
})
}
});
}
/* ---------- Tick loop ---------- */
function tick() {
const now = new Date();
el("chipTime").textContent = `UTC ${now.toISOString().slice(11, 19)}`;
viewer.clock.currentTime = Cesium.JulianDate.fromDate(now);
let selectedPos = null;
for (const sat of state.satsFiltered) {
const p = computeLatLonAltKm(sat.norad, now);
if (!p) continue;
const ent = state.entities.get(sat.norad);
if (ent) ent.position = Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.altKm * 1000);
if (state.selected === sat.norad) selectedPos = p;
}
if (selectedPos) {
el("detailLat").textContent = fmt(selectedPos.lat, 4);
el("detailLon").textContent = fmt(selectedPos.lon, 4);
el("detailAlt").textContent = fmt(selectedPos.altKm, 2);
el("detailSpeed").textContent = fmt(selectedPos.speed, 3);
}
if (state.selected && el("toggleTracks").checked) {
const sec = now.getUTCSeconds();
if (sec % 5 === 0) drawTrackIfEnabled();
}
}
/* ---------- UI wiring ---------- */
function wireUI() {
el("groupSelect").addEventListener("change", async (e) => {
state.group = e.target.value;
clearSelection();
await loadTLEs(state.group);
});
el("btnReload").addEventListener("click", async () => {
clearSelection();
await loadTLEs(state.group);
});
el("btnClear").addEventListener("click", () => clearSelection());
el("detailClose").addEventListener("click", () => clearSelection());
el("searchBox").addEventListener("input", () => {
clearSelection();
applyFilterAndRender();
});
el("maxSats").addEventListener("change", () => {
clearSelection();
applyFilterAndRender();
});
el("toggleTracks").addEventListener("change", () => drawTrackIfEnabled());
el("toggleLabels").addEventListener("change", () => {
if (state.selected) selectSatellite(state.selected);
});
el("toggleLighting").addEventListener("change", () => applyLightingToggle());
}
async function boot() {
try {
wireUI();
applyLightingToggle();
await loadGroups();
fillGroupSelect();
await loadTLEs(state.group);
startBackgroundDrift();
// Start radar overlay updater
startRadarAutoRefresh();
if (state.timer) clearInterval(state.timer);
state.timer = setInterval(tick, 1000);
el("chipRate").textContent = "Update: 1s";
} catch (err) {
console.error(err);
setWarn("Load failed (see console)");
el("statMeta").textContent = String(err.message || err);
}
}
boot();
Binary file not shown.

After

Width:  |  Height:  |  Size: 988 KiB

+115
View File
@@ -0,0 +1,115 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>sat.thedarkelite.com — Satellite Tracker</title>
<meta name="description" content="Live satellite tracking on a 3D globe using TLE → SGP4." />
<meta name="theme-color" content="#0b0f19" />
<!-- CesiumJS (CDNJS) -->
<script>
// Required so Cesium can find its Workers/Assets/Widgets when loaded from CDN.
window.CESIUM_BASE_URL = "https://cdnjs.cloudflare.com/ajax/libs/cesium/1.133.1/";
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cesium/1.133.1/Widgets/widgets.min.css"> <!-- [1](https://cdnjs.com/libraries/cesium) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/cesium/1.133.1/Cesium.min.js"></script> <!-- [1](https://cdnjs.com/libraries/cesium) -->
<!-- satellite.js (SGP4) -->
<script src="https://unpkg.com/satellite.js@6.0.2/dist/satellite.min.js"></script> <!-- [2](https://github.com/shashwatak/satellite-js) -->
<!-- App styles -->
<link rel="stylesheet" href="/assets/app.css" />
</head>
<body>
<div class="app">
<aside class="sidebar">
<div class="brand">
<div class="logo">SAT</div>
<div class="brandText">
<div class="title">sat.thedarkelite.com</div>
<div class="subtitle">TLE → SGP4 → Globe</div>
</div>
</div>
<div class="panel">
<label class="label" for="groupSelect">Satellite group</label>
<select id="groupSelect" class="select"></select>
<div class="row">
<div class="col">
<label class="label" for="maxSats">Max displayed</label>
<input id="maxSats" class="input" type="number" min="50" max="5000" step="50" value="800" />
<div class="hint">Starlink is huge—cap it for performance.</div>
</div>
</div>
<label class="label" for="searchBox">Search (name / NORAD)</label>
<input id="searchBox" class="input" type="text" placeholder="e.g. STARLINK-3000 or 25544" />
<div class="toggles">
<label class="toggle">
<input id="toggleLabels" type="checkbox" />
<span>Show labels (selected)</span>
</label>
<label class="toggle">
<input id="toggleTracks" type="checkbox" checked />
<span>Show orbit arc (selected)</span>
</label>
<label class="toggle">
<input id="toggleLighting" type="checkbox" checked />
<span>Day/Night lighting</span>
</label>
</div>
<div class="row buttons">
<button id="btnReload" class="btn primary">Reload TLEs</button>
<button id="btnClear" class="btn">Clear selection</button>
</div>
<div class="stats">
<div><span class="k">Loaded</span> <span id="statLoaded" class="v">0</span></div>
<div><span class="k">Displayed</span> <span id="statShown" class="v">0</span></div>
<div><span class="k">Selected</span> <span id="statSelected" class="v"></span></div>
<div class="small" id="statMeta"></div>
</div>
<div class="footer">
<div class="small">
Positions are computed client-side from TLEs using SGP4, then plotted as Entities on a 3D globe. <!-- [4](https://cesium.com/learn/cesiumjs-learn/cesiumjs-creating-entities/)[2](https://github.com/shashwatak/satellite-js) -->
</div>
</div>
</div>
</aside>
<main class="main">
<div id="cesiumContainer" class="globe"></div>
<div class="topbar">
<div class="chip" id="chipTime">UTC —</div>
<div class="chip" id="chipRate">Update: 1s</div>
<div class="chip warn" id="chipWarn" style="display:none"></div>
</div>
<div class="detail" id="detail" style="display:none">
<div class="detailHeader">
<div class="detailTitle" id="detailTitle"></div>
<button class="btn icon" id="detailClose" title="Close"></button>
</div>
<div class="detailGrid">
<div><span class="k">NORAD</span> <span class="v" id="detailNorad"></span></div>
<div><span class="k">Lat</span> <span class="v" id="detailLat"></span></div>
<div><span class="k">Lon</span> <span class="v" id="detailLon"></span></div>
<div><span class="k">Alt (km)</span> <span class="v" id="detailAlt"></span></div>
<div><span class="k">Speed (km/s)</span> <span class="v" id="detailSpeed"></span></div>
<div><span class="k">Epoch</span> <span class="v mono" id="detailEpoch"></span></div>
</div>
<div class="detailTle mono" id="detailTle"></div>
</div>
</main>
</div>
<script src="/assets/app.js"></script>
</body>
</html>
+4
View File
@@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://sat.thedarkelite.com/sitemap.xml
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://sat.thedarkelite.com/</loc>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
</urlset>