-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.js
More file actions
2201 lines (1994 loc) · 103 KB
/
Copy pathapp.js
File metadata and controls
2201 lines (1994 loc) · 103 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// ===== Inline Shaders (loaded from script tags, no fetch needed) =====
const SHADERS = {};
// ===== Object Catalog =====
const OBJECTS = {
sgr_a: {
name: "Sagittarius A*",
class: "SUPERMASSIVE BLACK HOLE",
sector: "SECTOR 00-CORE",
coords: "X: 0.00 / Y: 0.00 / Z: 0.00",
mass: "4.15e6 M☉",
rad: "EVENT HORIZON STABLE",
desc: "Сверхмассивная чёрная дыра в центре Млечного Пути. Аккреционный диск, мягкое гравитационное линзирование и спокойное вращение плазмы вокруг горизонта.",
shader: "shaders/black-hole.frag",
accentParticles: true,
position: new THREE.Vector3(0, 0, 0),
presets: {
"tranquil-core": { rs: 1.1, distortion: 0.9, outer: 9.0, speed: 0.4, doppler: 0.35, stars: 1.0, theme: 0 },
"deep-horizon": { rs: 1.6, distortion: 1.1, outer: 12.0, speed: 0.3, doppler: 0.25, stars: 1.1, theme: 1 }
},
themes: [
{ c1: '#e85a1a', c2: '#ffb86b', name: 'Warm Amber', temp: '8.4e6' },
{ c1: '#7ec8ff', c2: '#2a4cff', name: 'Cool Cobalt', temp: '1.1e7' }
],
labels: { rs: "Horizon Mass (Rs)", distortion: "Warp Lensing", outer: "Disk Outer Bound", speed: "Disk Rotation", doppler: "Doppler Beaming" }
},
orion_nebula: {
name: "Orion Nebula M42",
class: "EMISSION NEBULA",
sector: "SECTOR 03-O",
coords: "X: -4.20 / Y: 0.80 / Z: 3.40",
mass: "~2000 M☉",
rad: "Hα EMISSION",
desc: "Один из самых ярких звёздных питомников в небе. Огромное облако водорода и пыли, подсвеченное молодыми голубыми звёздами в центре.",
shader: "shaders/nebula.frag",
renderMode: "particles",
particleType: "orion",
orbitRadius: 16.4,
orbitSwing: 0.24,
position: new THREE.Vector3(-4.2, 0.8, 3.4),
presets: {
"ha-glow": { rs: 1.4, distortion: 0.6, outer: 11.0, speed: 0.25, doppler: 1.0, stars: 1.0, theme: 0 },
"deep-cloud":{ rs: 2.0, distortion: 0.4, outer: 13.5, speed: 0.15, doppler: 0.8, stars: 1.2, theme: 1 }
},
themes: [
{ c1: '#ff5f8b', c2: '#5d3acc', name: 'Hydrogen Pink', temp: '1.0e4' },
{ c1: '#ff9a6b', c2: '#2e6fff', name: 'Trapezium Blue', temp: '1.5e4' }
],
labels: { rs: "Core Density", distortion: "Cloud Shape", outer: "Cloud Extent", speed: "Drift Speed", doppler: "Emission Strength" }
},
aurelia: {
name: "Aurelia Rogue Planet",
class: "ROGUE EXOPLANET ANOMALY",
sector: "SECTOR 03-H",
coords: "X: -3.10 / Y: -0.40 / Z: 4.80",
mass: "~2.3 Mj",
rad: "AURORA PLASMA WAKE",
desc: "Одинокая планета-гигант без звезды: холодный мир с сильным магнитным полем, полярными сияниями, прозрачными кольцами и хвостом ионизированной плазмы.",
shader: "shaders/nebula.frag",
renderMode: "particles",
particleType: "aurelia",
orbitRadius: 18.0,
orbitSwing: 0.35,
position: new THREE.Vector3(-3.1, -0.4, 4.8),
presets: {
"aurora-storm": { rs: 1.35, distortion: 0.9, outer: 8.4, speed: 0.34, doppler: 1.1, stars: 1.0, theme: 0 },
"eclipse-rings":{ rs: 1.10, distortion: 1.2, outer: 10.2, speed: 0.22, doppler: 0.85, stars: 1.2, theme: 1 }
},
themes: [
{ c1: '#25f0ff', c2: '#7a4dff', name: 'Aurora Cyan', temp: '87 K' },
{ c1: '#ff7a3d', c2: '#3de0ff', name: 'Ion Ember', temp: '112 K' }
],
labels: { rs: "Planet Radius", distortion: "Magnetic Shear", outer: "Ring Span", speed: "Storm Drift", doppler: "Plasma Glow" }
},
crab_nebula: {
name: "Crab Nebula M1",
class: "SUPERNOVA REMNANT",
sector: "SECTOR 12-T",
coords: "X: 5.40 / Y: 0.50 / Z: 2.20",
mass: "~5 M☉",
rad: "SYNCHROTRON EMISSION",
desc: "Остаток сверхновой 1054 года. Расширяющееся облако филаментов, в центре - пульсар, который подпитывает свечение синхротронным излучением.",
shader: "shaders/nebula.frag",
renderMode: "particles",
particleType: "crab",
orbitRadius: 21,
orbitSwing: 0.32,
position: new THREE.Vector3(5.4, 0.5, 2.2),
presets: {
"filaments": { rs: 1.2, distortion: 0.5, outer: 10.0, speed: 0.3, doppler: 1.0, stars: 1.0, theme: 0 },
"expanding": { rs: 1.6, distortion: 0.3, outer: 12.5, speed: 0.18, doppler: 0.85, stars: 1.1, theme: 1 }
},
themes: [
{ c1: '#b14eff', c2: '#00e0ff', name: 'Synchrotron Violet', temp: '1.5e4' },
{ c1: '#ff5b8a', c2: '#3affe5', name: 'Crab Filaments', temp: '1.2e4' }
],
labels: { rs: "Core Density", distortion: "Filament Shape", outer: "Remnant Radius", speed: "Expansion Drift", doppler: "Synchrotron Glow" }
},
pleiades: {
name: "Pleiades M45",
class: "OPEN STAR CLUSTER",
sector: "SECTOR 06-S",
coords: "X: 4.10 / Y: 1.20 / Z: -3.60",
mass: "~800 M☉",
rad: "REFLECTION NEBULA",
desc: "Молодое звёздное скопление в созвездии Тельца. Семь ярких голубых звёзд погружены в нежную отражательную туманность.",
shader: "shaders/nebula.frag",
renderMode: "particles",
particleType: "pleiades",
orbitRadius: 18.0,
orbitSwing: 0.34,
position: new THREE.Vector3(4.1, 1.2, -3.6),
presets: {
"seven-sisters":{ rs: 1.6, distortion: 0.3, outer: 10.5, speed: 0.2, doppler: 0.7, stars: 1.2, theme: 0 },
"soft-veil": { rs: 2.0, distortion: 0.2, outer: 12.0, speed: 0.12, doppler: 0.55, stars: 1.4, theme: 1 }
},
themes: [
{ c1: '#a8c8ff', c2: '#ffffff', name: 'Reflection Blue', temp: '1.0e4' },
{ c1: '#bcd6ff', c2: '#fff2c8', name: 'Starlight Cream', temp: '9.5e3' }
],
labels: { rs: "Cluster Core", distortion: "Halo Shape", outer: "Halo Radius", speed: "Drift Speed", doppler: "Nebula Glow" }
},
cygnus: {
name: "Cygnus Wormhole",
class: "MORRIS-THORNE BRIDGE",
sector: "SECTOR 07-F",
coords: "X: 5.40 / Y: -0.80 / Z: -2.50",
mass: "N/A (Exotic)",
rad: "STABLE GATEWAY",
desc: "Топологический мост через искривлённое пространство. Через горло видна звёздная панорама другой вселенной.",
shader: "shaders/wormhole.frag",
accentParticles: true,
position: new THREE.Vector3(5.4, -0.8, -2.5),
presets: {
"stable-gate": { rs: 1.1, distortion: 0.8, outer: 10.0, speed: 0.5, doppler: 0.9, stars: 1.0, theme: 0 },
"einstein-rosen":{ rs: 0.8, distortion: 1.4, outer: 11.5, speed: 0.9, doppler: 1.1, stars: 1.2, theme: 1 }
},
themes: [
{ c1: '#b56cff', c2: '#5ce0ff', name: 'Aurora Portal', temp: '0' },
{ c1: '#ffb86b', c2: '#5cffaa', name: 'Gold-Emerald', temp: '12' }
],
labels: { rs: "Throat Radius (Rs)", distortion: "Throat Bending", outer: "Lensing Zone", speed: "Chromatic Drift", doppler: "Alternate Lumens" }
},
kepler: {
name: "Kepler Dyson Sphere",
class: "STELLAR MEGASTRUCTURE",
sector: "SECTOR 19-B",
coords: "X: 7.00 / Y: 1.00 / Z: 0.50",
mass: "1.08 M☉ (Host Star)",
rad: "THERMAL EMISSION",
desc: "Рой геометрических солнечных коллекторов, окружающих звезду. Свет пробивается через зазоры между панелями, обрисовывая силуэт мегаструктуры.",
shader: "shaders/dyson-sphere.frag",
renderMode: "particles",
particleType: "dyson",
orbitRadius: 20,
orbitSwing: 0.55,
position: new THREE.Vector3(7.0, 1.0, 0.5),
presets: {
"dyson-orbit": { rs: 0.9, distortion: 0.0, outer: 2.8, speed: 0.7, doppler: 1.1, stars: 1.0, theme: 0 },
"closed-swarm":{ rs: 1.15, distortion: 0.0, outer: 2.8, speed: 0.4, doppler: 0.7, stars: 1.2, theme: 1 }
},
themes: [
{ c1: '#ff9d00', c2: '#ffcc00', name: 'Solar Gold', temp: '5780' },
{ c1: '#7ed8ff', c2: '#ffffff', name: 'Sirius White-Blue', temp: '9940' }
],
labels: { rs: "Star Diameter", distortion: "Gravity Flex", outer: "Shell Size", speed: "Orbital Speed", doppler: "Circuit Radiance" }
}
};
// ===== Dossier Data (English) =====
const OBJECT_DOSSIERS_EN = {
sgr_a: {
overview: `Sagittarius A* is the supermassive black hole at the heart of the Milky Way, about 26,000 light-years from Earth. With a mass of 4.15 × 10⁶ M☉ it anchors the gravitational structure of the entire galactic core.
In this scene the disk rotates slowly and the lensing of background stars is gentle — the goal is to show the quiet majesty of a horizon, not a violent flare.`,
params: [
['Mass', '4.15 × 10⁶ M☉'],
['Schwarzschild Radius','Rs ≈ 12.3 × 10⁹ km'],
['Distance from Earth', '~26,000 ly'],
['Disk Temperature', '~10⁷ K (inner edge)'],
['Photon Sphere', '1.5 Rs (unstable)'],
['ISCO', '3 Rs'],
['Hawking Temperature', '~10⁻¹⁴ K'],
],
features: [
'Schwarzschild geodesic raymarching with smooth gravitational lensing',
'Volumetric accretion disk — Keplerian rotation, soft Doppler beaming',
'Calmed presets (slow rotation, low beaming amplitude) for relaxed viewing',
'Two color themes: warm amber, cool cobalt',
],
facts: [
'Imaged by the Event Horizon Telescope collaboration in 2022',
'Despite its mass, its angular size on the sky is smaller than 50 microarcseconds',
'The S2 star orbits it at up to 7,650 km/s — confirming general relativity in strong fields',
'A photon at the photon sphere can orbit the black hole multiple times before escaping',
],
},
orion_nebula: {
overview: `The Orion Nebula (M42) is one of the brightest and most studied stellar nurseries visible from Earth, lying about 1,344 light-years away in the constellation Orion. Inside this glowing cloud of hydrogen and dust, thousands of young stars are being born right now.
Its pink and violet glow comes from hot young stars in the Trapezium cluster ionising the surrounding hydrogen — emission and reflection blending into one of the most photogenic objects in the sky.`,
params: [
['Distance', '1,344 ly'],
['Diameter', '~24 ly'],
['Total Mass', '~2,000 M☉'],
['Age', '~3 million years'],
['Visible Stars', '~700 (forming)'],
['Apparent Mag.', '+4.0 (naked-eye)'],
],
features: [
'Volumetric raymarched cloud with soft fbm noise, slow drift',
'Two-tone color gradient: Hα pink core to violet outer wisps',
'No pulsing or flashing — meditative, calm look',
'Background starfield blends naturally with cloud edges',
],
facts: [
'Discovered by Nicolas-Claude Fabri de Peiresc in 1610',
'You can see it with the naked eye as the "sword" of Orion',
'Hubble has imaged hundreds of protoplanetary disks (proplyds) inside it',
'Light leaving M42 in 681 AD is arriving at Earth right now',
],
},
horsehead: {
overview: `Barnard 33 — the Horsehead Nebula — is a dense cloud of cold gas and dust silhouetted against the bright red emission nebula IC 434 in Orion. The iconic horse-head shape is pure shadow, the cloud blocking the glow behind it.
It is one of the most recognisable shapes in the night sky and a classic example of a dark nebula seen in absorption rather than emission.`,
params: [
['Distance', '~1,375 ly'],
['Diameter', '~3.5 ly'],
['Mass', '~300 M☉'],
['Constellation', 'Orion'],
['Type', 'Dark molecular cloud'],
['Backlight', 'IC 434 (Hα emission)'],
],
features: [
'High distortion uniform shapes the dark silhouette',
'Warm red-orange backlight bleeds through the cloud edges',
'Almost static — extremely slow drift, no flicker',
'Two themes: bright backlit ember, deep coal & rust',
],
facts: [
'First photographed by Williamina Fleming in 1888',
'The dust cloud will eventually dissipate — in cosmic terms, it is short-lived',
'New stars are still forming inside the densest knots',
'Visible only with long exposure or large telescopes',
],
},
crab_nebula: {
overview: `Messier 1, the Crab Nebula, is the expanding remnant of a supernova that Chinese astronomers recorded in 1054 AD. It contains a fast-spinning pulsar at its heart that lights up the surrounding gas through synchrotron radiation.
The result is a wispy, filament-rich cloud of purple and cyan, expanding outwards at roughly 1,500 km/s.`,
params: [
['Distance', '~6,500 ly'],
['Diameter', '~11 ly'],
['Mass', '~4.6 M☉'],
['Expansion Speed', '~1,500 km/s'],
['Age', '~970 years'],
['Central Pulsar', '33 ms spin period'],
],
features: [
'Volumetric cloud with wisp-detail layer for filaments',
'Synchrotron color palette — violet core, cyan filaments',
'Slow outward drift simulates expansion',
'Soft halo around centre suggests embedded pulsar',
],
facts: [
'The 1054 supernova was bright enough to be visible in daylight for 23 days',
'Recorded by Chinese, Japanese, and Arab astronomers',
'The central pulsar (PSR B0531+21) rotates 30 times a second',
'Charles Messier catalogued it first — it became Messier 1',
],
},
pleiades: {
overview: `Messier 45 — the Pleiades, or Seven Sisters — is the most famous open cluster in the sky. About 444 light-years away in Taurus, it consists of hot, young, blue stars wrapped in a delicate reflection nebula.
Many cultures across history have included the Pleiades in myth — they are visible to the naked eye and unmistakable on a clear winter night.`,
params: [
['Distance', '~444 ly'],
['Total Mass', '~800 M☉'],
['Age', '~100 million years'],
['Bright Stars', '7 visible to naked eye'],
['Total Stars', '~1,000 (gravitationally bound)'],
['Diameter', '~17 ly'],
],
features: [
'Cluster mode active — 7 embedded bright stars rendered inside the cloud',
'Soft reflection-nebula colour palette: blue cloud, white-cream stars',
'Minimal motion — the cluster feels peaceful and still',
'Two themes: classic reflection blue, warmer starlight cream',
],
facts: [
'Named after the Seven Sisters of Greek mythology',
'Featured on the Subaru car logo (Subaru = "Pleiades" in Japanese)',
'The reflection nebula is unrelated dust the cluster is currently passing through',
'In ~250 million years the cluster will disperse and stop being a cluster',
],
},
cygnus: {
overview: `Cygnus Wormhole is a topological shortcut through curved spacetime — a Morris-Thorne bridge. Looking into the throat you see the starfield of an entirely different universe, blended with gravitational lensing of our own.
In this scene the throat is stable and the colour drift is slow, allowing you to study the geometry rather than be overwhelmed by motion.`,
params: [
['Throat Radius', 'Rs ≈ 1.1 (configurable)'],
['Mass-Energy Type', 'Exotic / Negative'],
['Stability', 'Stabilised'],
['Bridge Length', 'Theoretically negligible'],
['Tidal Forces', 'Walkable (large throat)'],
['Observable Colour', 'Mixed alternate spectrum'],
],
features: [
'Coordinate inversion at r < Rs — the ray enters the alternate universe',
'Independent starfield rendered through the throat',
'Einstein-ring lensing around the boundary',
'Slow chromatic drift — no aggressive colour cycling',
],
facts: [
'Theoretically proposed by Morris and Thorne in 1988',
'Requires exotic matter with negative energy density to remain open',
'Quantum field theory permits negative energy in small amounts (Casimir effect)',
'No wormholes have been observed — they remain mathematical solutions to GR',
],
},
kepler: {
overview: `Kepler Dyson Sphere is a Type-II Kardashev megastructure — a swarm of geometric solar collectors enclosing a Sun-like star. Light escapes through the gaps in the panel grid, sketching the silhouette of an artificial world.
It represents what an advanced civilisation could build to harvest the total energy output of its star.`,
params: [
['Host Star', '1.08 M☉ (G-type)'],
['Shell Radius', '~1 AU'],
['Surface Area', '~2.8 × 10²³ m²'],
['Energy Captured', '~3.8 × 10²⁶ W'],
['Civilisation', 'Kardashev Type II'],
['Construction', 'Hypothetical'],
],
features: [
'Analytic ray-sphere intersection — pure geometry, very fast',
'Panel grid drawn from latitude/longitude lines',
'Gentle thermal emission from the panels',
'Two themes: Solar gold, Sirius white-blue',
],
facts: [
'Proposed by Freeman Dyson in 1960',
'A full solid sphere is gravitationally unstable — a real one would be a swarm',
'Tabby\'s Star (KIC 8462852) briefly raised Dyson-swarm speculation in 2015',
'Detecting one would show up as an infrared excess in star surveys',
],
},
};
// ===== Dossier Data (Russian) =====
const OBJECT_DOSSIERS_RU = {
sgr_a: {
overview: `Стрелец A* — сверхмассивная чёрная дыра в центре Млечного Пути, примерно в 26 000 световых лет от Земли. С массой 4.15 × 10⁶ M☉ она удерживает гравитационную структуру всего галактического ядра.
В этой сцене диск вращается медленно, а линзирование фоновых звёзд мягкое — задача показать тихое величие горизонта, а не яростную вспышку.`,
params: [
['Масса', '4.15 × 10⁶ M☉'],
['Радиус Шварцшильда', 'Rs ≈ 12.3 × 10⁹ км'],
['Расстояние от Земли', '~26 000 св. лет'],
['Температура диска', '~10⁷ K (внутр. край)'],
['Фотонная сфера', '1.5 Rs (нестабильная)'],
['ISCO', '3 Rs'],
['Температура Хокинга', '~10⁻¹⁴ K'],
],
features: [
'Реймарчинг по геодезикам Шварцшильда, мягкое линзирование',
'Объёмный аккреционный диск, кеплеровское вращение, тихий Доплер',
'Спокойные пресеты (медленное вращение, низкая амплитуда биминга)',
'Две темы: тёплый янтарь, холодный кобальт',
],
facts: [
'Снят коллаборацией Event Horizon Telescope в 2022 году',
'Несмотря на массу, угловой размер на небе меньше 50 микросекунд',
'Звезда S2 проходит мимо со скоростью до 7 650 км/с, подтверждая ОТО',
'Фотон на фотонной сфере может обогнуть дыру несколько раз перед побегом',
],
},
orion_nebula: {
overview: `Туманность Ориона (M42) — одна из самых ярких и изученных звёздных колыбелей, видимых с Земли. Лежит примерно в 1 344 световых годах от нас в созвездии Ориона. Внутри этого светящегося облака водорода и пыли прямо сейчас рождаются тысячи молодых звёзд.
Её розово-фиолетовое свечение возникает из-за того, что горячие молодые звёзды в скоплении Трапеция ионизируют окружающий водород.`,
params: [
['Расстояние', '1 344 св. лет'],
['Диаметр', '~24 св. лет'],
['Полная масса', '~2 000 M☉'],
['Возраст', '~3 млн лет'],
['Видимые звёзды', '~700 (формируются)'],
['Видимая величина','+4.0 (видна глазом)'],
],
features: [
'Объёмное реймарченное облако с мягким fbm-шумом и медленным дрейфом',
'Двухтоновый градиент: розовое ядро Hα → фиолетовые внешние нити',
'Без пульсации и вспышек — медитативный, спокойный вид',
'Звёздный фон естественно сливается с краями облака',
],
facts: [
'Открыта Николя-Клодом Фабри де Пейреском в 1610 году',
'Видна невооружённым глазом как «меч» Ориона',
'Hubble сфотографировал сотни протопланетных дисков внутри неё',
'Свет, ушедший из M42 в 681 году, доходит до Земли прямо сейчас',
],
},
horsehead: {
overview: `Барнард 33 — туманность Конская Голова — плотное облако холодного газа и пыли, видимое силуэтом на фоне яркой красной эмиссионной туманности IC 434 в Орионе. Знаменитая форма головы лошади — это чистая тень.
Один из самых узнаваемых силуэтов в ночном небе и классический пример тёмной туманности, видимой по поглощению.`,
params: [
['Расстояние', '~1 375 св. лет'],
['Диаметр', '~3.5 св. лет'],
['Масса', '~300 M☉'],
['Созвездие', 'Орион'],
['Тип', 'Тёмное молекулярное облако'],
['Подсветка', 'IC 434 (эмиссия Hα)'],
],
features: [
'Высокий uDistortion формирует характерный тёмный силуэт',
'Тёплая красно-оранжевая подсветка пробивается сквозь края облака',
'Почти статика — крайне медленный дрейф, без мерцаний',
'Две темы: яркий «уголь и пламя» и глубокая «уголь и ржавчина»',
],
facts: [
'Впервые сфотографирована Уильяминой Флеминг в 1888 году',
'Пылевое облако со временем рассеется — по космическим меркам недолговечно',
'Внутри плотнейших узлов всё ещё формируются новые звёзды',
'Видна только при длинной выдержке или в крупный телескоп',
],
},
crab_nebula: {
overview: `Мессье 1, Крабовидная туманность — расширяющийся остаток сверхновой, которую китайские астрономы зафиксировали в 1054 году. В центре сидит быстро вращающийся пульсар, подсвечивающий газ синхротронным излучением.
Результат — туманное облако фиолетовых и бирюзовых нитей, расширяющееся со скоростью около 1 500 км/с.`,
params: [
['Расстояние', '~6 500 св. лет'],
['Диаметр', '~11 св. лет'],
['Масса', '~4.6 M☉'],
['Скорость расширения','~1 500 км/с'],
['Возраст', '~970 лет'],
['Центральный пульсар','33 мс период'],
],
features: [
'Объёмное облако с дополнительным wisp-слоем для нитей',
'Синхротронная палитра — фиолетовое ядро, бирюзовые нити',
'Медленный направленный дрейф имитирует расширение',
'Мягкий ореол в центре намекает на встроенный пульсар',
],
facts: [
'Сверхновая 1054 года была видна днём 23 дня подряд',
'Зафиксирована китайскими, японскими и арабскими астрономами',
'Центральный пульсар PSR B0531+21 делает 30 оборотов в секунду',
'Шарль Мессье каталогизировал её первой — она стала Messier 1',
],
},
pleiades: {
overview: `Мессье 45 — Плеяды, или Семь Сестёр — самое известное рассеянное скопление на небе. Около 444 световых лет от нас в созвездии Тельца. Состоит из горячих молодых голубых звёзд, окутанных нежной отражательной туманностью.
Многие культуры в истории включали Плеяды в свои мифы — они видны невооружённым глазом и безошибочно узнаваемы ясной зимней ночью.`,
params: [
['Расстояние', '~444 св. лет'],
['Полная масса', '~800 M☉'],
['Возраст', '~100 млн лет'],
['Ярких звёзд', '7 видны глазом'],
['Всего звёзд', '~1 000 (связанных)'],
['Диаметр', '~17 св. лет'],
],
features: [
'Активирован кластер-режим — 7 встроенных ярких звёзд внутри облака',
'Палитра отражательной туманности: голубое облако, бело-кремовые звёзды',
'Минимум движения — скопление выглядит спокойным и неподвижным',
'Две темы: классическая «отражательная синева» и тёплая «кремовая»',
],
facts: [
'Названы в честь семи сестёр из греческой мифологии',
'Изображены на логотипе Subaru (subaru = «плеяды» по-японски)',
'Отражательная туманность — это просто пыль, через которую сейчас проходит скопление',
'Через ~250 млн лет скопление рассеется и перестанет быть скоплением',
],
},
cygnus: {
overview: `Червоточина Лебедя — топологический мост через искривлённое пространство-время, мост Морриса-Торна. Через горло видно звёздное поле другой вселенной, смешанное с гравитационным линзированием нашей.
В этой сцене горло стабильно, а цветовой дрейф медленный — это позволяет рассматривать геометрию, а не быть оглушённым движением.`,
params: [
['Радиус горла', 'Rs ≈ 1.1 (настраивается)'],
['Тип массы-энергии', 'Экзотическая / отрицательная'],
['Стабильность', 'Стабилизирована'],
['Длина моста', 'Теоретически пренебрежимо мала'],
['Приливные силы', 'Проходимы (большое горло)'],
['Видимый спектр', 'Смешанный, альтернативный'],
],
features: [
'Инверсия координат при r < Rs — луч уходит в альтернативную вселенную',
'Независимое звёздное поле, видимое сквозь горло',
'Линзирование в форме эйнштейновского кольца на границе',
'Медленный хроматический дрейф — без агрессивной смены цвета',
],
facts: [
'Теоретически предложена Моррисом и Торном в 1988 году',
'Требует экзотической материи с отрицательной плотностью энергии',
'Квантовая теория поля допускает отрицательную энергию (эффект Казимира)',
'Червоточины ни разу не наблюдались — пока только решения ОТО',
],
},
kepler: {
overview: `Сфера Дайсона Кеплера — мегаструктура II типа по шкале Кардашёва. Рой геометрических солнечных коллекторов, окружающих звезду солнечного класса. Свет пробивается сквозь зазоры между панелями, обрисовывая силуэт искусственного мира.
Это то, что могла бы построить продвинутая цивилизация, чтобы захватить всю энергию своей звезды.`,
params: [
['Центральная звезда','1.08 M☉ (G-класс)'],
['Радиус оболочки', '~1 а.е.'],
['Площадь поверхности','~2.8 × 10²³ м²'],
['Захваченная энергия','~3.8 × 10²⁶ Вт'],
['Цивилизация', 'II тип Кардашёва'],
['Конструкция', 'Гипотетическая'],
],
features: [
'Аналитическое пересечение луча со сферой — чистая геометрия, очень быстро',
'Сетка панелей нарисована линиями широты и долготы',
'Мягкое тепловое излучение от панелей',
'Две темы: «солнечное золото», «бело-голубой Сириус»',
],
facts: [
'Предложена Фрименом Дайсоном в 1960 году',
'Сплошная сфера гравитационно неустойчива — реальная была бы роем',
'Звезда Табби (KIC 8462852) в 2015 ненадолго оживила гипотезу о Дайсоне',
'Обнаружение проявилось бы как избыток ИК-излучения в обзоре звёзд',
],
},
};
OBJECT_DOSSIERS_EN.aurelia = {
overview: `Aurelia is a fictional rogue exoplanet anomaly: a cold gas giant drifting without a parent star, still visible through charged auroras, translucent rings, and a long ionized plasma wake.`,
params: [
['Mass', '~2.3 Mj'],
['Type', 'Rogue gas giant'],
['Thermal State', 'Cryogenic upper clouds'],
['Field', 'Extreme magnetosphere'],
['Visible Feature', 'Aurora and plasma tail'],
['Ring Material', 'Ice and metallic dust'],
],
features: [
'Procedural 3D planet shader with animated storm bands',
'Transparent atmosphere, cloud layer, rings, moons, and magnetic arcs',
'Aurora curtains above both poles',
'A plasma wake makes the object read clearly from orbit',
],
facts: [
'Rogue planets are expected to exist between star systems',
'Strong magnetospheres can produce auroras without direct starlight',
'Ring systems can survive around giant planets far from a star',
'The visual design references Jupiter bands, Saturn rings, and polar aurora imagery',
],
};
OBJECT_DOSSIERS_RU.aurelia = {
overview: `Aurelia - вымышленная аномалия: холодная планета-гигант без родительской звезды, видимая за счет заряженных полярных сияний, прозрачных колец и длинного хвоста ионизированной плазмы.`,
params: [
['Масса', '~2.3 Mj'],
['Тип', 'одинокий газовый гигант'],
['Состояние', 'криогенные верхние облака'],
['Поле', 'экстремальная магнитосфера'],
['Главный признак', 'сияния и плазменный хвост'],
['Кольца', 'лед и металлическая пыль'],
],
features: [
'Процедурный 3D-шейдер планеты с живыми штормовыми поясами',
'Прозрачная атмосфера, облачный слой, кольца, луны и магнитные дуги',
'Полярные aurora-завесы над обоими полюсами',
'Плазменный хвост делает объект читаемым в orbit mode',
],
facts: [
'Одинокие планеты должны встречаться между звездными системами',
'Сильная магнитосфера может создавать сияния даже без прямого света звезды',
'Кольцевые системы могут сохраняться вокруг планет-гигантов далеко от звезды',
'Визуальная база: пояса Юпитера, кольца Сатурна и полярные сияния',
],
};
// Active language (default: Russian, swap via toggle)
let dossierLang = 'ru';
const DOSSIER_LABELS = {
en: { overview: 'OVERVIEW', params: 'PHYSICS PARAMETERS', features: 'RENDER FEATURES', facts: 'INTERESTING FACTS', title: 'ANOMALY DOSSIER', esc: 'ESC to close' },
ru: { overview: 'ОБЗОР', params: 'ФИЗИЧЕСКИЕ ПАРАМЕТРЫ', features: 'ОСОБЕННОСТИ РЕНДЕРА', facts: 'ИНТЕРЕСНЫЕ ФАКТЫ', title: 'ДОСЬЕ АНОМАЛИИ', esc: 'ESC чтобы закрыть' }
};
// ===== State =====
let appState = 'BOOT';
let nodeLabels = {};
let activeObjectId = 'sgr_a';
let activeThemeIdx = 0;
let autoRotate = true;
// ===== Boot Terminal =====
// entry types: plain (default), 'scan' (animated bar fill), 'typewriter' (char-by-char)
const BOOT_LINES = [
{ text: "> BIOS POST sequence initiated ...", cls: "line-info", delay: 90 },
{ text: " [MEM ] 256 TB unified ................... OK", cls: "line-ok", delay: 50 },
{ text: " [GPU ] 8x RTX-ASTRO cluster ............. OK", cls: "line-ok", delay: 50 },
{ text: " [PHT ] 4096-lane photon buffer .......... OK", cls: "line-ok", delay: 50 },
{ text: " [QENT] Quantum entangler ................ OK", cls: "line-ok", delay: 50 },
{ text: "", cls: "line-dim", delay: 20 },
{ text: "> Loading ASTRO-NAV/x86_64 kernel ...", cls: "line-info", delay: 100 },
{ text: " gravitational_lens.ko ........... [ OK ]", cls: "line-dim", delay: 50 },
{ text: " schwarzschild_integrator.ko ..... [ OK ]", cls: "line-dim", delay: 50 },
{ text: " doppler_beaming.ko .............. [ OK ]", cls: "line-dim", delay: 40 },
{ text: " accretion_disk_volumetric.ko .... [ OK ]", cls: "line-dim", delay: 50 },
{ text: " magnetosphere_dipole.ko ......... [ OK ]", cls: "line-dim", delay: 50 },
{ text: " wormhole_throat.ko .............. [ OK ]", cls: "line-dim", delay: 40 },
{ text: "", cls: "line-dim", delay: 20 },
{ text: "> Hardware diagnostics:", cls: "line-info", delay: 80 },
{ text: " CPU_LOAD [██████████░░░░░░] 67% NOMINAL", cls: "line-dim", delay: 35 },
{ text: " MEM_UTIL [████████████░░░░] 76% NOMINAL", cls: "line-dim", delay: 35 },
{ text: " GPU_VRAM [██████████████░░] 89% NOMINAL", cls: "line-dim", delay: 35 },
{ text: " THRML_SY [█████████░░░░░░░] 58°C NOMINAL", cls: "line-dim", delay: 35 },
{ text: " NET_SYNC [███████████░░░░░] 71% NOMINAL", cls: "line-dim", delay: 35 },
{ text: "", cls: "line-dim", delay: 20 },
{ text: "> Initializing WebGL2 renderer ...", cls: "line-info", delay: 100 },
{ text: " Canvas: 1920x1080 @ 2x DPI ............. OK", cls: "line-dim", delay: 40 },
{ text: " GLSL 3.00 ES compiler .............. READY", cls: "line-ok", delay: 50 },
{ text: " Fragment pipeline: 4 shaders queued ... OK", cls: "line-dim", delay: 40 },
{ text: "", cls: "line-dim", delay: 20 },
{ text: "> Decoding raw telemetry stream ...", cls: "line-info", delay: 100 },
{ text: " 0x4A2F FF91 04B7 338E 0x11CC 75D0 52AE F90B", cls: "line-warn", delay: 25 },
{ text: " 0x671A 4488 BB36 E25D 0xC3F0 1B82 9D4E A7C1", cls: "line-warn", delay: 25 },
{ text: " 0x08F5 3A61 77BE D294 0x5E19 AB4C 2D83 F6E0", cls: "line-warn", delay: 25 },
{ text: "> Carrier signal analysis:", cls: "line-info", delay: 80 },
{ text: " ◁▁▂▃▅▇██▇▅▃▂▁▁▂▃▅▇██▇▅▃▂▁▂▄▆▇██▇▆▄▂▷", cls: "line-ok", delay: 30 },
{ text: " FREQ: 1.420405 GHz SNR: 34.7 dB LOCK: ACQ", cls: "line-dim", delay: 40 },
{ text: "", cls: "line-dim", delay: 20 },
{ type: "scan", label: "> Sector authentication", cls: "line-ok", result: "GRANTED", delay: 0 },
{ text: "", cls: "line-dim", delay: 20 },
{ text: "> Scanning deep-space anomaly registry ...", cls: "line-info", delay: 100 },
{ text: " [ANOMALY] Sagittarius A* — SEC_00-C — 4.15e6 M☉", cls: "line-warn", delay: 32 },
{ text: " [ANOMALY] Gargantua Singularity — SEC_04-A — 4.3e6 M☉", cls: "line-warn", delay: 32 },
{ text: " [ANOMALY] Vela Pulsar — SEC_12-C — 1.44 M☉", cls: "line-warn", delay: 32 },
{ text: " [ANOMALY] SGR 1806-20 Magnetar — SEC_18-F — extreme", cls: "line-warn", delay: 32 },
{ text: " [ANOMALY] Cygnus Wormhole — SEC_07-F — exotic", cls: "line-warn", delay: 32 },
{ text: " [ANOMALY] Andromeda Gateway — SEC_99-Z — bridge", cls: "line-warn", delay: 32 },
{ text: " [ANOMALY] Kepler Dyson Sphere — SEC_19-B — megastruct",cls: "line-warn", delay: 32 },
{ text: " CATALOG: 7 / 7 anomalies — COMPLETE", cls: "line-dim", delay: 40 },
{ text: "", cls: "line-dim", delay: 20 },
{ text: "> Building galaxy particle simulation ...", cls: "line-info", delay: 80 },
{ text: " Background: 10,000 stars — spherical shell", cls: "line-dim", delay: 40 },
{ text: " Disk: 40,000 stars — 2-arm log-spiral", cls: "line-dim", delay: 40 },
{ text: " Nebula: 15,000 pts — volumetric dust", cls: "line-dim", delay: 40 },
{ text: "", cls: "line-dim", delay: 20 },
{ text: "> Running diagnostics ...", cls: "line-info", delay: 80 },
{ text: " Framebuffer integrity ............. PASS", cls: "line-ok", delay: 50 },
{ text: " Depth buffer precision ............ PASS", cls: "line-ok", delay: 40 },
{ text: " Raymarching pipeline .............. PASS", cls: "line-ok", delay: 50 },
{ text: " Tone mapping (Reinhard) ........... PASS", cls: "line-ok", delay: 40 },
{ text: "", cls: "line-dim", delay: 30 },
{ type: "typewriter", text: "> ALL SYSTEMS NOMINAL — STAR CHART READY", cls: "line-ok", charDelay: 32, delay: 180 },
];
function bootClock() {
const el = document.getElementById('boot-clock');
if (!el) return;
const now = new Date();
el.textContent = now.toISOString().slice(11, 19) + ' UTC';
}
async function runBootSequence() {
const terminal = document.getElementById('boot-terminal');
const progressWrap = document.getElementById('boot-progress-wrap');
const progressBar = document.getElementById('boot-progress-bar');
const progressPct = document.getElementById('boot-progress-pct');
const enterWrap = document.getElementById('boot-enter-wrap');
bootClock();
const clockInterval = setInterval(bootClock, 1000);
const total = BOOT_LINES.length;
// phase 1: print lines
for (let i = 0; i < total; i++) {
const entry = BOOT_LINES[i];
// update progress
if (i === 3) progressWrap.classList.remove('boot-hidden');
const pct = Math.min(100, Math.round(((i + 1) / total) * 100));
progressBar.style.setProperty('--pct', pct + '%');
progressPct.textContent = pct + '%';
if (entry.type === 'scan') {
// Animated fill bar
const div = document.createElement('div');
div.className = `line ${entry.cls}`;
terminal.appendChild(div);
terminal.scrollTop = terminal.scrollHeight;
const barLen = 18;
for (let b = 0; b <= barLen; b++) {
const bar = '█'.repeat(b) + '░'.repeat(barLen - b);
div.textContent = `${entry.label} [${bar}]`;
await sleep(28);
}
div.textContent = `${entry.label} [${'█'.repeat(barLen)}] ${entry.result}`;
await sleep(entry.delay || 200);
} else if (entry.type === 'typewriter') {
// Character-by-character typing
const div = document.createElement('div');
div.className = `line ${entry.cls}`;
div.textContent = '';
terminal.appendChild(div);
terminal.scrollTop = terminal.scrollHeight;
for (const ch of entry.text) {
div.textContent += ch;
terminal.scrollTop = terminal.scrollHeight;
await sleep(entry.charDelay || 50);
}
await sleep(entry.delay || 100);
} else {
const div = document.createElement('div');
div.className = `line ${entry.cls}`;
div.textContent = entry.text;
terminal.appendChild(div);
terminal.scrollTop = terminal.scrollHeight;
await sleep(entry.delay);
}
}
await sleep(300);
// show enter button
enterWrap.classList.remove('boot-hidden');
enterWrap.style.animation = 'fadeIn 0.4s forwards';
// wait for click or Enter key
await new Promise(resolve => {
const btn = document.getElementById('boot-enter-btn');
const handler = () => {
btn.removeEventListener('click', handler);
document.removeEventListener('keydown', keyHandler);
resolve();
};
const keyHandler = (e) => { if (e.key === 'Enter') handler(); };
btn.addEventListener('click', handler);
document.addEventListener('keydown', keyHandler);
});
clearInterval(clockInterval);
// fade out boot screen
const bootScreen = document.getElementById('boot-screen');
bootScreen.classList.add('boot-exit');
await sleep(800);
bootScreen.style.display = 'none';
// reveal main app
const appContainer = document.getElementById('app-container');
appContainer.classList.remove('app-hidden');
appContainer.style.animation = 'fadeIn 0.5s forwards';
appState = 'GALAXY';
}
function sleep(ms) {
return new Promise(r => setTimeout(r, ms));
}
// ===== DOM refs =====
let sidebar, infoCard, btnBack, btnOrbit, btnAutopilot, btnHelp;
// ===== Sliders =====
const sliderIds = ['rs', 'distortion', 'outer', 'speed', 'doppler', 'stars'];
let sliders = {};
let displays = {};
let hudFps, hudTemp;
// ===== Three.js =====
let renderer, scene, camera, controls, clock;
let orthoCamera, orthoScene, shaderMaterial;
let objectScene, composer, bloomPass, currentNebulaGroup = null;
let uniforms = {};
let galaxyParticles, galaxyDust, coreSprite, systemNodes = [];
let raycaster, mouse;
let targetCameraPos = new THREE.Vector3();
let targetLookAt = new THREE.Vector3();
let currentLookAt = new THREE.Vector3();
let transitionProgress = 1.0;
function createStarTexture() {
const c = document.createElement('canvas');
c.width = 16; c.height = 16;
const ctx = c.getContext('2d');
const g = ctx.createRadialGradient(8, 8, 0, 8, 8, 8);
g.addColorStop(0, 'rgba(255,255,255,1)');
g.addColorStop(0.25, 'rgba(255,255,255,0.85)');
g.addColorStop(1, 'rgba(255,255,255,0)');
ctx.fillStyle = g;
ctx.fillRect(0, 0, 16, 16);
return new THREE.CanvasTexture(c);
}
function createNodeTexture(hex, type) {
const S = 192;
const c = document.createElement('canvas');
c.width = S; c.height = S;
const ctx = c.getContext('2d');
ctx.clearRect(0, 0, S, S);
const cx = S / 2, cy = S / 2, R = S / 2;
if (type === 'blackhole') {
// ── Mini black hole: tilted accretion disk + dark event horizon ──
const dR = R * 0.50; // disk radius
const dY = R * 0.10; // disk vertical compression (3D tilt)
// Diffuse outer halo
const halo = ctx.createRadialGradient(cx, cy, dR * 0.6, cx, cy, R * 0.96);
halo.addColorStop(0, hex + '22'); halo.addColorStop(0.5, hex + '18'); halo.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = halo; ctx.beginPath(); ctx.arc(cx, cy, R * 0.96, 0, Math.PI * 2); ctx.fill();
// Back half of disk (dimmer, behind horizon)
ctx.shadowColor = hex; ctx.shadowBlur = 14;
const back = ctx.createLinearGradient(cx - dR, cy, cx + dR, cy);
back.addColorStop(0, 'rgba(255,100,0,0.55)');
back.addColorStop(0.45, hex + 'bb');
back.addColorStop(1, 'rgba(255,200,50,0.2)');
ctx.strokeStyle = back; ctx.lineWidth = 11;
ctx.beginPath(); ctx.ellipse(cx, cy, dR, dY, 0, Math.PI, 0); ctx.stroke();
// Event horizon (solid black circle)
ctx.shadowBlur = 0;
const hor = ctx.createRadialGradient(cx, cy, 0, cx, cy, R * 0.30);
hor.addColorStop(0, '#000000'); hor.addColorStop(0.88, '#000000'); hor.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = hor; ctx.beginPath(); ctx.arc(cx, cy, R * 0.31, 0, Math.PI * 2); ctx.fill();
// Front half of disk (bright, Doppler-shifted)
ctx.shadowColor = hex; ctx.shadowBlur = 28;
const front = ctx.createLinearGradient(cx - dR, cy, cx + dR, cy);
front.addColorStop(0, 'rgba(255,230,80,1.0)'); // approaching = blue-shifted = bright
front.addColorStop(0.35, hex + 'ff');
front.addColorStop(0.7, hex + 'cc');
front.addColorStop(1, 'rgba(200,40,0,0.5)'); // receding = red-shifted = dim
ctx.strokeStyle = front; ctx.lineWidth = 14;
ctx.beginPath(); ctx.ellipse(cx, cy, dR, dY, 0, 0, Math.PI); ctx.stroke();
// Photon sphere glow (gravitational lensing ring)
ctx.shadowBlur = 10; ctx.strokeStyle = 'rgba(255,210,120,0.5)'; ctx.lineWidth = 1.5;
ctx.beginPath(); ctx.arc(cx, cy, R * 0.36, 0, Math.PI * 2); ctx.stroke();
} else if (type === 'pulsar') {
// ── Mini pulsar: neutron star + relativistic jets + magnetosphere rings ──
const jLen = R * 0.86;
// Faint magnetosphere rings
ctx.shadowColor = hex; ctx.shadowBlur = 6; ctx.strokeStyle = hex + '28'; ctx.lineWidth = 1;
[R*0.38, R*0.58, R*0.78].forEach(r => { ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.stroke(); });
// Jet beams (top + bottom)
ctx.shadowBlur = 22;
[[cy - 7, cy - jLen], [cy + 7, cy + jLen]].forEach(([y0, y1]) => {
const g = ctx.createLinearGradient(cx, y0, cx, y1);
g.addColorStop(0, hex + 'ff'); g.addColorStop(0.4, hex + 'cc'); g.addColorStop(1, 'rgba(0,0,0,0)');
ctx.strokeStyle = g; ctx.lineWidth = 9;
ctx.beginPath(); ctx.moveTo(cx, y0); ctx.lineTo(cx, y1); ctx.stroke();
// bright inner core of jet
ctx.lineWidth = 2.5; ctx.strokeStyle = '#ffffffbb';
ctx.beginPath(); ctx.moveTo(cx, y0); ctx.lineTo(cx, y0 + (y1 - y0) * 0.45); ctx.stroke();
});
// Cross-ticks along jets
ctx.shadowBlur = 4; ctx.strokeStyle = hex + 'aa'; ctx.lineWidth = 1.5;
for (let t = 1; t <= 5; t++) {
const w = Math.max(2, 10 - t * 1.8);
ctx.beginPath(); ctx.moveTo(cx - w, cy - t * 14); ctx.lineTo(cx + w, cy - t * 14); ctx.stroke();
ctx.beginPath(); ctx.moveTo(cx - w, cy + t * 14); ctx.lineTo(cx + w, cy + t * 14); ctx.stroke();
}
// Neutron star core
ctx.shadowBlur = 30;
const core = ctx.createRadialGradient(cx, cy, 0, cx, cy, R * 0.21);
core.addColorStop(0, '#ffffff'); core.addColorStop(0.2, '#ddf4ff');
core.addColorStop(0.55, hex); core.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = core; ctx.beginPath(); ctx.arc(cx, cy, R * 0.21, 0, Math.PI * 2); ctx.fill();
} else if (type === 'wormhole') {
// ── Mini wormhole: portal with alternate-universe interior ──
// Dark space background around portal
const bg = ctx.createRadialGradient(cx, cy, R * 0.28, cx, cy, R * 0.90);
bg.addColorStop(0, 'rgba(0,0,0,0)'); bg.addColorStop(1, 'rgba(0,5,20,0.5)');
ctx.fillStyle = bg; ctx.beginPath(); ctx.arc(cx, cy, R * 0.90, 0, Math.PI * 2); ctx.fill();
// Alternate universe glow inside portal
const portal = ctx.createRadialGradient(cx, cy, 0, cx, cy, R * 0.40);
portal.addColorStop(0, hex + 'ff'); portal.addColorStop(0.45, hex + 'cc');
portal.addColorStop(0.8, hex + '55'); portal.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = portal; ctx.beginPath(); ctx.arc(cx, cy, R * 0.40, 0, Math.PI * 2); ctx.fill();
// Stars visible through the portal (other universe)
ctx.shadowBlur = 0; ctx.fillStyle = '#ffffff';
const rng = s => { let x = Math.sin(s * 9301 + 49297) * 43758; return x - Math.floor(x); };
for (let i = 0; i < 14; i++) {
const ang = rng(i * 3.14) * Math.PI * 2;
const dist = rng(i * 1.61) * R * 0.30;
const sr = 0.7 + rng(i * 2.71) * 1.8;
ctx.globalAlpha = 0.6 + rng(i * 0.5) * 0.4;
ctx.beginPath(); ctx.arc(cx + Math.cos(ang) * dist, cy + Math.sin(ang) * dist, sr, 0, Math.PI * 2); ctx.fill();
}
ctx.globalAlpha = 1;
// Einstein ring (dashed bright)
ctx.shadowColor = hex; ctx.shadowBlur = 20; ctx.strokeStyle = hex; ctx.lineWidth = 5;
const segs = 10;
for (let a = 0; a < segs; a++) {
const s = (a / segs) * Math.PI * 2, e = s + (Math.PI * 2 / segs) - 0.26;
ctx.beginPath(); ctx.arc(cx, cy, R * 0.50, s, e); ctx.stroke();
}
// Fine lensing halo
ctx.shadowBlur = 6; ctx.strokeStyle = '#ffffff55'; ctx.lineWidth = 1.2;
ctx.beginPath(); ctx.arc(cx, cy, R * 0.52, 0, Math.PI * 2); ctx.stroke();
} else if (type === 'planet') {
// Mini rogue planet: crescent disk, rings, and aurora rim.
const planetR = R * 0.38;
const ringR = R * 0.64;
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(-0.28);
ctx.shadowColor = hex;
ctx.shadowBlur = 18;
ctx.strokeStyle = hex + 'aa';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.ellipse(0, 0, ringR, ringR * 0.26, 0, 0, Math.PI * 2);
ctx.stroke();
ctx.shadowBlur = 4;
ctx.strokeStyle = '#ffffff55';
ctx.lineWidth = 1.2;
ctx.beginPath();
ctx.ellipse(0, 0, ringR * 0.78, ringR * 0.18, 0, 0, Math.PI * 2);
ctx.stroke();
ctx.restore();
const body = ctx.createRadialGradient(cx - planetR * 0.35, cy - planetR * 0.32, 0, cx, cy, planetR);
body.addColorStop(0, '#ffffff');
body.addColorStop(0.18, hex);
body.addColorStop(0.58, '#18235a');
body.addColorStop(1, '#020411');
ctx.shadowColor = hex;
ctx.shadowBlur = 20;
ctx.fillStyle = body;
ctx.beginPath();
ctx.arc(cx, cy, planetR, 0, Math.PI * 2);
ctx.fill();
ctx.globalCompositeOperation = 'lighter';
ctx.strokeStyle = '#6fffffdd';
ctx.lineWidth = 2;
ctx.shadowBlur = 14;
for (let i = -1; i <= 1; i++) {
ctx.beginPath();
ctx.arc(cx, cy - planetR * 0.42 + i * 5, planetR * 0.58, Math.PI * 0.15, Math.PI * 0.82);
ctx.stroke();
}
ctx.globalCompositeOperation = 'source-over';
} else if (type === 'dyson') {
// ── Mini Dyson sphere: star peeking through metallic grid cage ──
const RG = R * 0.60;
// Star background glow
const sg = ctx.createRadialGradient(cx, cy, 0, cx, cy, RG * 0.9);
sg.addColorStop(0, hex + 'ee'); sg.addColorStop(0.35, hex + 'aa');
sg.addColorStop(0.65, hex + '44'); sg.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = sg; ctx.beginPath(); ctx.arc(cx, cy, RG * 0.9, 0, Math.PI * 2); ctx.fill();
// Dark panel coverage (sphere blocks most of the star)
ctx.shadowBlur = 0;
const panels = ctx.createRadialGradient(cx, cy, RG * 0.18, cx, cy, RG);
panels.addColorStop(0, 'rgba(2,4,10,0)'); panels.addColorStop(0.5, 'rgba(2,4,10,0.55)');
panels.addColorStop(0.88, 'rgba(2,4,10,0.88)'); panels.addColorStop(1, 'rgba(2,4,10,0)');
ctx.fillStyle = panels; ctx.beginPath(); ctx.arc(cx, cy, RG, 0, Math.PI * 2); ctx.fill();
// Latitude ellipses (structural rings)
ctx.shadowColor = hex; ctx.shadowBlur = 10; ctx.strokeStyle = hex + 'bb'; ctx.lineWidth = 1.5;
[-R*0.28, -R*0.15, 0, R*0.15, R*0.28].forEach(yo => {
const rx = Math.sqrt(Math.max(0, RG * RG - yo * yo));
ctx.beginPath(); ctx.ellipse(cx, cy + yo, rx, rx * 0.18, 0, 0, Math.PI * 2); ctx.stroke();
});
// Longitude arcs (6 great circles)
for (let i = 0; i < 6; i++) {
ctx.save(); ctx.translate(cx, cy); ctx.rotate(i * Math.PI / 6);
ctx.beginPath(); ctx.ellipse(0, 0, RG * 0.22, RG, 0, 0, Math.PI * 2);
ctx.stroke(); ctx.restore();
}
// Outer shell circle
ctx.lineWidth = 2.5; ctx.strokeStyle = hex; ctx.shadowBlur = 16;
ctx.beginPath(); ctx.arc(cx, cy, RG, 0, Math.PI * 2); ctx.stroke();
// Central star (shows through gaps)
ctx.shadowBlur = 24;
const sc = ctx.createRadialGradient(cx, cy, 0, cx, cy, R * 0.18);
sc.addColorStop(0, '#ffffff'); sc.addColorStop(0.3, hex);
sc.addColorStop(0.8, hex + '88'); sc.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = sc; ctx.beginPath(); ctx.arc(cx, cy, R * 0.18, 0, Math.PI * 2); ctx.fill();
} else if (type === 'nebula') {