synthos 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -1
- package/default-pages/app_builder.html +40 -0
- package/default-pages/app_builder.json +1 -0
- package/default-pages/json_tools.html +89 -159
- package/default-pages/json_tools.json +1 -0
- package/default-pages/my_notes.html +33 -0
- package/default-pages/my_notes.json +12 -0
- package/default-pages/neon_asteroids.html +77 -0
- package/default-pages/neon_asteroids.json +12 -0
- package/default-pages/sidebar_builder.html +49 -0
- package/default-pages/sidebar_builder.json +1 -0
- package/default-pages/solar_explorer.html +1956 -0
- package/default-pages/solar_explorer.json +12 -0
- package/default-pages/solar_tutorial.html +476 -0
- package/default-pages/solar_tutorial.json +1 -0
- package/default-pages/two-panel_builder.html +66 -0
- package/default-pages/two-panel_builder.json +1 -0
- package/dist/connectors/index.d.ts +3 -0
- package/dist/connectors/index.d.ts.map +1 -0
- package/dist/connectors/index.js +6 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors/registry.d.ts +3 -0
- package/dist/connectors/registry.d.ts.map +1 -0
- package/dist/connectors/registry.js +100 -0
- package/dist/connectors/registry.js.map +1 -0
- package/dist/connectors/types.d.ts +61 -0
- package/dist/connectors/types.d.ts.map +1 -0
- package/dist/connectors/types.js +3 -0
- package/dist/connectors/types.js.map +1 -0
- package/dist/files.d.ts +2 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +12 -1
- package/dist/files.js.map +1 -1
- package/dist/init.d.ts +8 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +155 -3
- package/dist/init.js.map +1 -1
- package/dist/migrations.d.ts +11 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +281 -0
- package/dist/migrations.js.map +1 -0
- package/dist/models/index.d.ts +3 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +10 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models/providers.d.ts +7 -0
- package/dist/models/providers.d.ts.map +1 -0
- package/dist/models/providers.js +33 -0
- package/dist/models/providers.js.map +1 -0
- package/dist/models/types.d.ts +21 -0
- package/dist/models/types.d.ts.map +1 -0
- package/dist/models/types.js +3 -0
- package/dist/models/types.js.map +1 -0
- package/dist/pages.d.ts +21 -2
- package/dist/pages.d.ts.map +1 -1
- package/dist/pages.js +202 -23
- package/dist/pages.js.map +1 -1
- package/dist/scripts.js +2 -2
- package/dist/scripts.js.map +1 -1
- package/dist/service/createCompletePrompt.d.ts +3 -2
- package/dist/service/createCompletePrompt.d.ts.map +1 -1
- package/dist/service/createCompletePrompt.js +11 -16
- package/dist/service/createCompletePrompt.js.map +1 -1
- package/dist/service/debugLog.d.ts +11 -0
- package/dist/service/debugLog.d.ts.map +1 -0
- package/dist/service/debugLog.js +26 -0
- package/dist/service/debugLog.js.map +1 -0
- package/dist/service/modelInstructions.d.ts +7 -0
- package/dist/service/modelInstructions.d.ts.map +1 -0
- package/dist/service/modelInstructions.js +16 -0
- package/dist/service/modelInstructions.js.map +1 -0
- package/dist/service/requiresSettings.d.ts +2 -2
- package/dist/service/requiresSettings.d.ts.map +1 -1
- package/dist/service/requiresSettings.js.map +1 -1
- package/dist/service/server.d.ts.map +1 -1
- package/dist/service/server.js +15 -0
- package/dist/service/server.js.map +1 -1
- package/dist/service/transformPage.d.ts +81 -2
- package/dist/service/transformPage.d.ts.map +1 -1
- package/dist/service/transformPage.js +672 -82
- package/dist/service/transformPage.js.map +1 -1
- package/dist/service/useApiRoutes.d.ts.map +1 -1
- package/dist/service/useApiRoutes.js +579 -13
- package/dist/service/useApiRoutes.js.map +1 -1
- package/dist/service/useConnectorRoutes.d.ts +4 -0
- package/dist/service/useConnectorRoutes.d.ts.map +1 -0
- package/dist/service/useConnectorRoutes.js +389 -0
- package/dist/service/useConnectorRoutes.js.map +1 -0
- package/dist/service/useDataRoutes.d.ts.map +1 -1
- package/dist/service/useDataRoutes.js +83 -70
- package/dist/service/useDataRoutes.js.map +1 -1
- package/dist/service/usePageRoutes.d.ts.map +1 -1
- package/dist/service/usePageRoutes.js +243 -38
- package/dist/service/usePageRoutes.js.map +1 -1
- package/dist/settings.d.ts +33 -4
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +108 -15
- package/dist/settings.js.map +1 -1
- package/dist/synthos-cli.d.ts.map +1 -1
- package/dist/synthos-cli.js +11 -1
- package/dist/synthos-cli.js.map +1 -1
- package/dist/themes.d.ts +9 -0
- package/dist/themes.d.ts.map +1 -0
- package/dist/themes.js +64 -0
- package/dist/themes.js.map +1 -0
- package/package.json +5 -3
- package/required-pages/builder.html +74 -0
- package/required-pages/builder.json +1 -0
- package/required-pages/pages.html +169 -126
- package/required-pages/pages.json +1 -0
- package/required-pages/settings.html +812 -156
- package/required-pages/settings.json +1 -0
- package/required-pages/synthos_apis.html +272 -0
- package/required-pages/synthos_apis.json +1 -0
- package/required-pages/synthos_scripts.html +87 -0
- package/required-pages/synthos_scripts.json +1 -0
- package/src/connectors/index.ts +12 -0
- package/src/connectors/registry.ts +98 -0
- package/src/connectors/types.ts +68 -0
- package/src/files.ts +11 -0
- package/src/init.ts +151 -5
- package/src/migrations.ts +266 -0
- package/src/models/index.ts +2 -0
- package/src/models/providers.ts +33 -0
- package/src/models/types.ts +23 -0
- package/src/pages.ts +234 -26
- package/src/scripts.ts +2 -2
- package/src/service/createCompletePrompt.ts +14 -18
- package/src/service/debugLog.ts +17 -0
- package/src/service/modelInstructions.ts +14 -0
- package/src/service/requiresSettings.ts +3 -3
- package/src/service/server.ts +19 -2
- package/src/service/transformPage.ts +709 -88
- package/src/service/useApiRoutes.ts +632 -16
- package/src/service/useConnectorRoutes.ts +427 -0
- package/src/service/useDataRoutes.ts +87 -71
- package/src/service/usePageRoutes.ts +237 -44
- package/src/settings.ts +143 -20
- package/src/synthos-cli.ts +11 -1
- package/src/themes.ts +71 -0
- package/default-pages/[application].html +0 -95
- package/default-pages/[markdown].html +0 -271
- package/default-pages/[sidebar].html +0 -114
- package/default-pages/[split-application].html +0 -118
- package/default-pages/solar_system.html +0 -432
- package/default-pages/space_invaders.html +0 -617
- package/required-pages/apis.html +0 -362
- package/required-pages/home.html +0 -126
- package/required-pages/scripts.html +0 -350
|
@@ -0,0 +1,1956 @@
|
|
|
1
|
+
<!DOCTYPE html><html lang="en"><head>
|
|
2
|
+
<meta charset="UTF-8">
|
|
3
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
4
|
+
<title>SynthOS</title>
|
|
5
|
+
<script id="theme-info" src="/api/theme-info.js" data-locked="true"></script>
|
|
6
|
+
<link id="theme-css" rel="stylesheet" href="/api/theme.css" data-locked="true">
|
|
7
|
+
<style id="solar-styles">
|
|
8
|
+
#solarSystem {
|
|
9
|
+
width: 100%;
|
|
10
|
+
height: 100%;
|
|
11
|
+
position: relative;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.controls {
|
|
15
|
+
position: absolute;
|
|
16
|
+
bottom: 8px;
|
|
17
|
+
left: 0;
|
|
18
|
+
right: 0;
|
|
19
|
+
display: flex;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
gap: 10px;
|
|
22
|
+
z-index: 100;
|
|
23
|
+
background: rgba(15, 15, 35, 0.6);
|
|
24
|
+
padding: 10px 20px;
|
|
25
|
+
border-top: 1px solid rgba(138, 43, 226, 0.3);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.controls button {
|
|
29
|
+
padding: 8px 24px;
|
|
30
|
+
border: none;
|
|
31
|
+
border-radius: 12px;
|
|
32
|
+
background: linear-gradient(135deg, #667eea 0, #764ba2 100%);
|
|
33
|
+
color: #fff;
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
font-size: 12px;
|
|
36
|
+
white-space: nowrap;
|
|
37
|
+
transition: 0.3s;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.controls button:hover {
|
|
41
|
+
transform: scale(1.05);
|
|
42
|
+
box-shadow: 0 0 15px rgba(102, 126, 234, 0.5);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.controls button.active {
|
|
46
|
+
background: linear-gradient(135deg, #f093fb 0, #764ba2 100%);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.info-panel {
|
|
50
|
+
position: absolute;
|
|
51
|
+
top: 20px;
|
|
52
|
+
right: 20px;
|
|
53
|
+
background: rgba(15, 15, 35, 0.6);
|
|
54
|
+
padding: 20px;
|
|
55
|
+
border-radius: 15px;
|
|
56
|
+
border: 1px solid rgba(138, 43, 226, 0.3);
|
|
57
|
+
max-width: 280px;
|
|
58
|
+
z-index: 100;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.info-panel h3 {
|
|
62
|
+
color: #f093fb;
|
|
63
|
+
margin-bottom: 10px;
|
|
64
|
+
font-size: 18px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.info-panel p {
|
|
68
|
+
font-size: 13px;
|
|
69
|
+
line-height: 1.6;
|
|
70
|
+
color: #b794f6;
|
|
71
|
+
margin-bottom: 8px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.speed-control {
|
|
75
|
+
position: absolute;
|
|
76
|
+
top: 20px;
|
|
77
|
+
left: 20px;
|
|
78
|
+
background: rgba(15, 15, 35, 0.6);
|
|
79
|
+
padding: 15px 20px;
|
|
80
|
+
border-radius: 15px;
|
|
81
|
+
border: 1px solid rgba(138, 43, 226, 0.3);
|
|
82
|
+
z-index: 100;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.speed-control label {
|
|
86
|
+
color: #b794f6;
|
|
87
|
+
font-size: 13px;
|
|
88
|
+
display: block;
|
|
89
|
+
margin-bottom: 8px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.speed-control input[type=range] {
|
|
93
|
+
width: 150px;
|
|
94
|
+
accent-color: #f093fb;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.planet-label {
|
|
98
|
+
font-size: 11px;
|
|
99
|
+
fill: #b794f6;
|
|
100
|
+
pointer-events: none;
|
|
101
|
+
text-anchor: middle;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#viewerPanel.full-viewer {
|
|
105
|
+
background: #0a0a19 !important;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.light-mode .controls,
|
|
109
|
+
.light-mode .info-panel,
|
|
110
|
+
.light-mode .speed-control {
|
|
111
|
+
background: rgba(15, 15, 35, 0.6) !important;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.light-mode .info-panel h3 {
|
|
115
|
+
color: #f093fb !important;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.light-mode .info-panel p {
|
|
119
|
+
color: #b794f6 !important;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.light-mode .speed-control label {
|
|
123
|
+
color: #b794f6 !important;
|
|
124
|
+
}
|
|
125
|
+
</style>
|
|
126
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/14.1.1/marked.min.js"></script>
|
|
127
|
+
<script id="page-info" src="/api/page-info.js?page=solar_system"></script>
|
|
128
|
+
<style id="tutorial-link-styles">.tutorial-link{color:#667eea;text-decoration:underline;cursor:pointer;transition:color 0.2s}.tutorial-link:hover{color:#f093fb}.light-mode .tutorial-link{color:#667eea}.light-mode .tutorial-link:hover{color:#764ba2}</style><style id="detail-card-options">.card-showcase{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);display:none;gap:30px;z-index:200;padding:20px}.card-showcase.active{display:flex}.planet-detail-card{width:240px;background:rgba(15,15,35,.6);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.18);border-radius:12px;padding:16px;color:#fff;box-shadow:0 8px 32px rgba(0,0,0,.3);position:absolute;pointer-events:none;opacity:0;transition:opacity .2s;z-index:150}.planet-detail-card.visible{opacity:1;pointer-events:auto}.planet-detail-card .planet-header{display:flex;align-items:center;gap:10px;margin-bottom:12px;padding-bottom:10px;border-bottom:1px solid rgba(255,255,255,.15)}.planet-detail-card .planet-icon{width:32px;height:32px;border-radius:50%;box-shadow:0 0 12px rgba(255,200,100,.3);flex-shrink:0}.planet-detail-card .planet-name{font-size:18px;font-weight:600;background:linear-gradient(135deg,#f093fb,#667eea);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.planet-detail-card .stat-row{display:flex;justify-content:space-between;padding:6px 0;font-size:12px;border-bottom:1px solid rgba(255,255,255,.08)}.planet-detail-card .stat-row:last-of-type{border-bottom:none}.planet-detail-card .stat-label{color:rgba(255,255,255,.5)}.planet-detail-card .stat-value{color:#fff;font-weight:500}.planet-detail-card .fun-fact{margin-top:12px;padding:10px;background:rgba(102,126,234,.15);border-radius:8px;font-size:11px;color:rgba(255,255,255,.9);line-height:1.4;display:none}.planet-detail-card .fun-fact-label{color:#f093fb;font-weight:600;margin-bottom:4px;display:block}.planet-detail-card .question-section{margin-top:12px;display:none}.planet-detail-card .question-input{width:100%;padding:10px;background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.2);border-radius:8px;color:#fff;font-size:12px;outline:none;box-sizing:border-box}.planet-detail-card .question-input::placeholder{color:rgba(255,255,255,.4)}.planet-detail-card .question-input:focus{border-color:#667eea;box-shadow:0 0 10px rgba(102,126,234,.3)}.planet-detail-card .ai-answer{margin-top:10px;padding:10px;background:rgba(240,147,251,.1);border-radius:8px;font-size:11px;color:rgba(255,255,255,.9);line-height:1.4;border-left:2px solid #f093fb;max-height:120px;overflow-y:auto;display:none}.planet-detail-card .ai-answer.visible{display:block}.planet-detail-card .ai-answer::-webkit-scrollbar{width:4px}.planet-detail-card .ai-answer::-webkit-scrollbar-track{background:rgba(255,255,255,.05);border-radius:2px}.planet-detail-card .ai-answer::-webkit-scrollbar-thumb{background:rgba(240,147,251,.3);border-radius:2px}.planet-detail-card .ai-answer::-webkit-scrollbar-thumb:hover{background:rgba(240,147,251,.5)}.planet-detail-card.mode-funfact .fun-fact{display:block}.planet-detail-card.mode-funfact .question-section{display:none}.planet-detail-card.mode-question .fun-fact{display:none}.planet-detail-card.mode-question .question-section{display:block}.light-mode .planet-detail-card{background:rgba(15,15,35,.6);border-color:rgba(118,75,162,.3)}.light-mode .planet-detail-card .stat-label{color:rgba(255,255,255,.6)}.light-mode .planet-detail-card .question-input{background:rgba(255,255,255,.15);border-color:rgba(255,255,255,.25)}.moon-detail-card{position:absolute;width:200px;background:rgba(15,15,35,.6);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.18);border-radius:10px;padding:12px;color:#fff;box-shadow:0 8px 32px rgba(0,0,0,.3);pointer-events:none;opacity:0;transition:opacity .2s;z-index:200}.moon-detail-card.visible{opacity:1}.moon-detail-card .moon-name{font-size:14px;font-weight:600;background:linear-gradient(135deg,#f093fb,#667eea);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin-bottom:8px}.moon-detail-card .moon-stat{display:flex;justify-content:space-between;padding:4px 0;font-size:11px;border-bottom:1px solid rgba(255,255,255,.08)}.moon-detail-card .moon-stat:last-of-type{border-bottom:none}.moon-detail-card .moon-stat-label{color:rgba(255,255,255,.5)}.moon-detail-card .moon-stat-value{color:#fff}.moon-detail-card .moon-fun-fact{margin-top:8px;padding:8px;background:rgba(102,126,234,.15);border-radius:6px;font-size:10px;color:rgba(255,255,255,.9);line-height:1.4}.light-mode .moon-detail-card{background:rgba(15,15,35,.6);border-color:rgba(118,75,162,.3)}</style><style id="detail-view-styles">.detail-view-container{position:absolute;top:0;left:0;width:100%;height:100%;display:none;background:#0a0a19}.detail-view-container.active{display:flex}.detail-planet-area{flex:1;position:relative;display:flex;align-items:center;justify-content:center;overflow:hidden}.detail-planet-canvas{position:absolute;top:0;left:0}.detail-sidebar{width:300px;background:rgba(15,15,35,.95);border-left:1px solid rgba(138,43,226,.3);padding:20px;overflow-y:auto;display:flex;flex-direction:column;gap:15px}.detail-sidebar .planet-detail-card{position:relative;opacity:1;pointer-events:auto;width:100%;box-sizing:border-box}.back-button{padding:10px 20px;border:none;border-radius:15px;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:#fff;cursor:pointer;font-size:13px;transition:.3s;margin-bottom:10px}.back-button:hover{transform:scale(1.05);box-shadow:0 0 15px rgba(102,126,234,.5)}.moon-speed-control{background:rgba(255,255,255,.1);padding:12px;border-radius:10px;margin-top:10px}.moon-speed-control label{color:#b794f6;font-size:12px;display:block;margin-bottom:8px}.moon-speed-control input[type="range"]{width:100%;accent-color:#f093fb}.moon-detail-card{position:absolute;width:200px;background:rgba(15,15,35,.6);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.18);border-radius:10px;padding:12px;color:#fff;box-shadow:0 8px 32px rgba(0,0,0,.3);pointer-events:none;opacity:0;transition:opacity .2s;z-index:200}.moon-detail-card.visible{opacity:1}.moon-detail-card .moon-name{font-size:14px;font-weight:600;background:linear-gradient(135deg,#f093fb,#667eea);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin-bottom:8px}.moon-detail-card .moon-stat{display:flex;justify-content:space-between;padding:4px 0;font-size:11px;border-bottom:1px solid rgba(255,255,255,.08)}.moon-detail-card .moon-stat:last-of-type{border-bottom:none}.moon-detail-card .moon-stat-label{color:rgba(255,255,255,.5)}.moon-detail-card .moon-stat-value{color:#fff}.moon-detail-card .moon-fun-fact{margin-top:8px;padding:8px;background:rgba(102,126,234,.15);border-radius:6px;font-size:10px;color:rgba(255,255,255,.9);line-height:1.4}.light-mode .detail-view-container{background:#0a0a19}.light-mode .detail-sidebar{background:rgba(15,15,35,.95)}</style>
|
|
129
|
+
<script id="planet-details" type="application/json" data-locked="true">{"sun":{"name":"The Sun","type":"G-type main-sequence star","diameter":"1,392,700 km","surfaceTemp":"5,500°C","funFact":"Contains 99.86% of the Solar System's mass and could fit 1.3 million Earths inside it."},"planets":{"mercury":{"name":"Mercury","color":"#b5b5b5","type":"Terrestrial","diameter":"4,879 km","distance":"57.9 million km","orbitalPeriod":"88 Earth days","dayLength":"59 Earth days","surfaceTemp":"-180°C to 430°C","moons":0,"funFact":"Despite being closest to the Sun, Mercury isn't the hottest planet. Its lack of atmosphere means heat escapes quickly at night."},"venus":{"name":"Venus","color":"#e6c87a","type":"Terrestrial","diameter":"12,104 km","distance":"108.2 million km","orbitalPeriod":"225 Earth days","dayLength":"243 Earth days","surfaceTemp":"465°C","moons":0,"funFact":"Venus rotates backwards compared to other planets, so the Sun rises in the west and sets in the east."},"earth":{"name":"Earth","color":"#6b93d6","type":"Terrestrial","diameter":"12,742 km","distance":"149.6 million km","orbitalPeriod":"365.25 days","dayLength":"24 hours","surfaceTemp":"-89°C to 57°C","moons":1,"funFact":"Earth is the only planet not named after a Greek or Roman god. Its name comes from Old English meaning 'ground'.","moonData":{"luna":{"name":"Luna (The Moon)","diameter":"3,474 km","distance":"384,400 km","distanceKm":384400,"orbitalPeriod":"27.3 days","funFact":"The Moon is slowly drifting away from Earth at about 3.8 cm per year."}}},"mars":{"name":"Mars","color":"#c1440e","type":"Terrestrial","diameter":"6,779 km","distance":"227.9 million km","orbitalPeriod":"687 Earth days","dayLength":"24.6 hours","surfaceTemp":"-125°C to 20°C","moons":2,"funFact":"Mars has the largest volcano in the solar system, Olympus Mons, which is nearly 3 times the height of Mount Everest.","moonData":{"phobos":{"name":"Phobos","diameter":"22.4 km","distance":"9,376 km","distanceKm":9376,"orbitalPeriod":"7.7 hours","funFact":"Phobos orbits Mars faster than Mars rotates, so it rises in the west and sets in the east."},"deimos":{"name":"Deimos","diameter":"12.4 km","distance":"23,463 km","distanceKm":23463,"orbitalPeriod":"30.3 hours","funFact":"Deimos is one of the smallest moons in the solar system and may be a captured asteroid."}}},"jupiter":{"name":"Jupiter","color":"#d4a574","type":"Gas Giant","diameter":"139,820 km","distance":"778.5 million km","orbitalPeriod":"11.86 Earth years","dayLength":"9.9 hours","surfaceTemp":"-110°C","moons":95,"funFact":"Jupiter's Great Red Spot is a storm that has been raging for at least 400 years and is larger than Earth.","moonData":{"io":{"name":"Io","diameter":"3,643 km","distance":"421,700 km","distanceKm":421700,"orbitalPeriod":"1.77 days","funFact":"Io is the most volcanically active body in the solar system with over 400 active volcanoes."},"europa":{"name":"Europa","diameter":"3,122 km","distance":"671,034 km","distanceKm":671034,"orbitalPeriod":"3.55 days","funFact":"Europa likely has a subsurface ocean containing more water than all of Earth's oceans combined."},"ganymede":{"name":"Ganymede","diameter":"5,268 km","distance":"1,070,400 km","distanceKm":1070400,"orbitalPeriod":"7.15 days","funFact":"Ganymede is the largest moon in the solar system, even bigger than the planet Mercury."},"callisto":{"name":"Callisto","diameter":"4,821 km","distance":"1,882,700 km","distanceKm":1882700,"orbitalPeriod":"16.69 days","funFact":"Callisto has the oldest and most heavily cratered surface in the solar system."}}},"saturn":{"name":"Saturn","color":"#f4d59e","type":"Gas Giant","diameter":"116,460 km","distance":"1.43 billion km","orbitalPeriod":"29.46 Earth years","dayLength":"10.7 hours","surfaceTemp":"-140°C","moons":146,"funFact":"Saturn's density is so low that it would float if you could find a bathtub big enough to hold it.","moonData":{"mimas":{"name":"Mimas","diameter":"396 km","distance":"185,520 km","distanceKm":185520,"orbitalPeriod":"0.94 days","funFact":"Mimas has a giant crater that makes it look like the Death Star from Star Wars."},"enceladus":{"name":"Enceladus","diameter":"504 km","distance":"238,020 km","distanceKm":238020,"orbitalPeriod":"1.37 days","funFact":"Enceladus shoots geysers of water ice into space, suggesting a subsurface ocean."},"tethys":{"name":"Tethys","diameter":"1,062 km","distance":"294,619 km","distanceKm":294619,"orbitalPeriod":"1.89 days","funFact":"Tethys has a massive canyon called Ithaca Chasma that stretches 2,000 km across its surface."},"dione":{"name":"Dione","diameter":"1,123 km","distance":"377,396 km","distanceKm":377396,"orbitalPeriod":"2.74 days","funFact":"Dione has wispy ice cliffs and may have a subsurface ocean beneath its icy crust."},"rhea":{"name":"Rhea","diameter":"1,527 km","distance":"527,040 km","distanceKm":527040,"orbitalPeriod":"4.52 days","funFact":"Rhea may have its own faint ring system, which would make it the only moon known to have rings."},"titan":{"name":"Titan","diameter":"5,150 km","distance":"1,221,870 km","distanceKm":1221870,"orbitalPeriod":"15.95 days","funFact":"Titan is the only moon with a dense atmosphere and has lakes of liquid methane on its surface."},"iapetus":{"name":"Iapetus","diameter":"1,469 km","distance":"3,560,820 km","distanceKm":3560820,"orbitalPeriod":"79.32 days","funFact":"Iapetus has one hemisphere as dark as coal and the other as bright as snow, giving it a striking two-toned appearance."}}},"uranus":{"name":"Uranus","color":"#d1e7e7","type":"Ice Giant","diameter":"50,724 km","distance":"2.87 billion km","orbitalPeriod":"84.01 Earth years","dayLength":"17.2 hours","surfaceTemp":"-195°C","moons":28,"funFact":"Uranus rotates on its side with an axial tilt of 98°, possibly due to a collision with an Earth-sized object.","moonData":{"miranda":{"name":"Miranda","diameter":"472 km","distance":"129,390 km","distanceKm":129390,"orbitalPeriod":"1.41 days","funFact":"Miranda has the tallest known cliff in the solar system, Verona Rupes, at 20 km high."},"ariel":{"name":"Ariel","diameter":"1,158 km","distance":"191,020 km","distanceKm":191020,"orbitalPeriod":"2.52 days","funFact":"Ariel is the brightest of Uranus's moons and has extensive canyon systems."},"umbriel":{"name":"Umbriel","diameter":"1,169 km","distance":"266,000 km","distanceKm":266000,"orbitalPeriod":"4.14 days","funFact":"Umbriel is the darkest of Uranus's major moons, with a mysterious bright ring on its surface called Wunda crater."},"titania":{"name":"Titania","diameter":"1,578 km","distance":"435,910 km","distanceKm":435910,"orbitalPeriod":"8.71 days","funFact":"Titania is the largest moon of Uranus and may have a subsurface ocean."},"oberon":{"name":"Oberon","diameter":"1,523 km","distance":"583,520 km","distanceKm":583520,"orbitalPeriod":"13.46 days","funFact":"Oberon has a mountain that rises about 6 km above the surrounding terrain."}}},"neptune":{"name":"Neptune","color":"#5b5ddf","type":"Ice Giant","diameter":"49,244 km","distance":"4.5 billion km","orbitalPeriod":"164.8 Earth years","dayLength":"16.1 hours","surfaceTemp":"-200°C","moons":16,"funFact":"Neptune has the strongest winds in the solar system, reaching speeds of 2,100 km/h.","moonData":{"triton":{"name":"Triton","diameter":"2,707 km","distance":"354,760 km","distanceKm":354760,"orbitalPeriod":"5.88 days (retrograde)","funFact":"Triton orbits Neptune backwards and is slowly spiraling inward, destined to be torn apart in about 3.6 billion years."},"proteus":{"name":"Proteus","diameter":"420 km","distance":"117,647 km","distanceKm":117647,"orbitalPeriod":"1.12 days","funFact":"Proteus is Neptune's second largest moon and is irregularly shaped — it's about as large as an object can be without being pulled into a sphere by its own gravity."},"nereid":{"name":"Nereid","diameter":"340 km","distance":"5,513,400 km","distanceKm":5513400,"orbitalPeriod":"360.14 days","funFact":"Nereid has one of the most eccentric orbits of any known moon in the solar system."}}},"pluto":{"name":"Pluto","color":"#a89f91","type":"Dwarf Planet","diameter":"2,377 km","distance":"5.9 billion km","orbitalPeriod":"248 Earth years","dayLength":"6.4 Earth days","surfaceTemp":"-230°C","moons":5,"funFact":"Pluto has a heart-shaped glacier called Tombaugh Regio that's larger than Texas.","moonData":{"charon":{"name":"Charon","diameter":"1,212 km","distance":"19,571 km","distanceKm":19571,"orbitalPeriod":"6.4 days","funFact":"Charon is so large relative to Pluto that they orbit a point in space between them, making them a 'double dwarf planet'."},"styx":{"name":"Styx","diameter":"16 km","distance":"42,656 km","distanceKm":42656,"orbitalPeriod":"20.16 days","funFact":"Styx is the smallest and last-discovered moon of Pluto, found in 2012 by the Hubble Space Telescope."},"nix":{"name":"Nix","diameter":"50 km","distance":"48,694 km","distanceKm":48694,"orbitalPeriod":"24.85 days","funFact":"Nix rotates chaotically due to the gravitational influence of Pluto and Charon."},"kerberos":{"name":"Kerberos","diameter":"19 km","distance":"57,783 km","distanceKm":57783,"orbitalPeriod":"32.17 days","funFact":"Kerberos is surprisingly small and dark, defying predictions that it would be large and bright."},"hydra":{"name":"Hydra","diameter":"51 km","distance":"64,738 km","distanceKm":64738,"orbitalPeriod":"38.2 days","funFact":"Hydra is shaped like a potato and tumbles unpredictably as it orbits."}}}}}</script>
|
|
130
|
+
<style id="markdown-answer-styles">
|
|
131
|
+
#solarAnswer p,
|
|
132
|
+
#solarAnswer ul,
|
|
133
|
+
#solarAnswer ol,
|
|
134
|
+
#solarAnswer li,
|
|
135
|
+
#planetAnswerBox p,
|
|
136
|
+
#planetAnswerBox ul,
|
|
137
|
+
#planetAnswerBox ol,
|
|
138
|
+
#planetAnswerBox li {
|
|
139
|
+
margin: 0 0 8px 0;
|
|
140
|
+
font-size: inherit;
|
|
141
|
+
line-height: inherit;
|
|
142
|
+
color: inherit;
|
|
143
|
+
}
|
|
144
|
+
#solarAnswer p:last-child,
|
|
145
|
+
#planetAnswerBox p:last-child {
|
|
146
|
+
margin-bottom: 0;
|
|
147
|
+
}
|
|
148
|
+
#solarAnswer ul,
|
|
149
|
+
#solarAnswer ol,
|
|
150
|
+
#planetAnswerBox ul,
|
|
151
|
+
#planetAnswerBox ol {
|
|
152
|
+
padding-left: 18px;
|
|
153
|
+
}
|
|
154
|
+
#solarAnswer code,
|
|
155
|
+
#planetAnswerBox code {
|
|
156
|
+
background: rgba(102, 126, 234, 0.2);
|
|
157
|
+
padding: 1px 4px;
|
|
158
|
+
border-radius: 3px;
|
|
159
|
+
font-size: 0.9em;
|
|
160
|
+
}
|
|
161
|
+
#solarAnswer strong,
|
|
162
|
+
#planetAnswerBox strong {
|
|
163
|
+
color: #f093fb;
|
|
164
|
+
}
|
|
165
|
+
</style><style id="settings-modal-styles">
|
|
166
|
+
.settings-overlay {
|
|
167
|
+
position: fixed;
|
|
168
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
169
|
+
background: rgba(0,0,0,0.6);
|
|
170
|
+
z-index: 9999;
|
|
171
|
+
display: none;
|
|
172
|
+
align-items: center;
|
|
173
|
+
justify-content: center;
|
|
174
|
+
}
|
|
175
|
+
.settings-overlay.active {
|
|
176
|
+
display: flex;
|
|
177
|
+
}
|
|
178
|
+
.settings-modal {
|
|
179
|
+
background: rgba(15, 15, 35, 0.95);
|
|
180
|
+
backdrop-filter: blur(16px);
|
|
181
|
+
-webkit-backdrop-filter: blur(16px);
|
|
182
|
+
border: 1px solid rgba(138, 43, 226, 0.4);
|
|
183
|
+
border-radius: 16px;
|
|
184
|
+
padding: 28px 32px;
|
|
185
|
+
width: 420px;
|
|
186
|
+
max-width: 90vw;
|
|
187
|
+
max-height: 85vh;
|
|
188
|
+
overflow-y: auto;
|
|
189
|
+
color: #fff;
|
|
190
|
+
box-shadow: 0 12px 48px rgba(0,0,0,0.5);
|
|
191
|
+
}
|
|
192
|
+
.settings-modal h2 {
|
|
193
|
+
margin: 0 0 20px 0;
|
|
194
|
+
font-size: 20px;
|
|
195
|
+
background: linear-gradient(135deg, #f093fb, #667eea);
|
|
196
|
+
-webkit-background-clip: text;
|
|
197
|
+
-webkit-text-fill-color: transparent;
|
|
198
|
+
background-clip: text;
|
|
199
|
+
}
|
|
200
|
+
.settings-section {
|
|
201
|
+
margin-bottom: 20px;
|
|
202
|
+
}
|
|
203
|
+
.settings-section label {
|
|
204
|
+
display: block;
|
|
205
|
+
color: #b794f6;
|
|
206
|
+
font-size: 13px;
|
|
207
|
+
font-weight: 600;
|
|
208
|
+
margin-bottom: 8px;
|
|
209
|
+
}
|
|
210
|
+
.settings-section select,
|
|
211
|
+
.settings-section input[type="radio"] {
|
|
212
|
+
accent-color: #f093fb;
|
|
213
|
+
}
|
|
214
|
+
.settings-section select {
|
|
215
|
+
width: 100%;
|
|
216
|
+
padding: 10px 12px;
|
|
217
|
+
border-radius: 8px;
|
|
218
|
+
border: 1px solid rgba(138, 43, 226, 0.3);
|
|
219
|
+
background: rgba(255,255,255,0.1);
|
|
220
|
+
color: #e0d0f0;
|
|
221
|
+
font-size: 13px;
|
|
222
|
+
outline: none;
|
|
223
|
+
cursor: pointer;
|
|
224
|
+
appearance: auto;
|
|
225
|
+
}
|
|
226
|
+
.settings-section select option {
|
|
227
|
+
background: #1a1a3a;
|
|
228
|
+
color: #e0d0f0;
|
|
229
|
+
}
|
|
230
|
+
.settings-section select:focus {
|
|
231
|
+
border-color: #667eea;
|
|
232
|
+
box-shadow: 0 0 10px rgba(102,126,234,0.3);
|
|
233
|
+
}
|
|
234
|
+
.settings-radio-group {
|
|
235
|
+
display: flex;
|
|
236
|
+
flex-direction: column;
|
|
237
|
+
gap: 8px;
|
|
238
|
+
}
|
|
239
|
+
.settings-radio-item {
|
|
240
|
+
display: flex;
|
|
241
|
+
align-items: center;
|
|
242
|
+
gap: 8px;
|
|
243
|
+
padding: 8px 12px;
|
|
244
|
+
border-radius: 8px;
|
|
245
|
+
background: rgba(255,255,255,0.05);
|
|
246
|
+
cursor: pointer;
|
|
247
|
+
transition: background 0.2s;
|
|
248
|
+
}
|
|
249
|
+
.settings-radio-item:hover {
|
|
250
|
+
background: rgba(255,255,255,0.1);
|
|
251
|
+
}
|
|
252
|
+
.settings-radio-item label {
|
|
253
|
+
margin: 0;
|
|
254
|
+
font-weight: 400;
|
|
255
|
+
cursor: pointer;
|
|
256
|
+
color: #d0c0e8;
|
|
257
|
+
}
|
|
258
|
+
.settings-radio-item input[type="radio"] {
|
|
259
|
+
margin: 0;
|
|
260
|
+
}
|
|
261
|
+
.settings-btn-row {
|
|
262
|
+
display: flex;
|
|
263
|
+
justify-content: flex-end;
|
|
264
|
+
gap: 10px;
|
|
265
|
+
margin-top: 24px;
|
|
266
|
+
}
|
|
267
|
+
.settings-btn {
|
|
268
|
+
padding: 10px 28px;
|
|
269
|
+
border: none;
|
|
270
|
+
border-radius: 10px;
|
|
271
|
+
font-size: 13px;
|
|
272
|
+
cursor: pointer;
|
|
273
|
+
transition: 0.3s;
|
|
274
|
+
}
|
|
275
|
+
.settings-btn-save {
|
|
276
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
277
|
+
color: #fff;
|
|
278
|
+
}
|
|
279
|
+
.settings-btn-save:hover {
|
|
280
|
+
transform: scale(1.05);
|
|
281
|
+
box-shadow: 0 0 15px rgba(102,126,234,0.5);
|
|
282
|
+
}
|
|
283
|
+
.settings-btn-cancel {
|
|
284
|
+
background: rgba(255,255,255,0.1);
|
|
285
|
+
color: #b794f6;
|
|
286
|
+
border: 1px solid rgba(138,43,226,0.3);
|
|
287
|
+
}
|
|
288
|
+
.settings-btn-cancel:hover {
|
|
289
|
+
background: rgba(255,255,255,0.15);
|
|
290
|
+
}
|
|
291
|
+
.settings-description {
|
|
292
|
+
font-size: 11px;
|
|
293
|
+
color: rgba(183,148,246,0.7);
|
|
294
|
+
margin-top: 4px;
|
|
295
|
+
line-height: 1.4;
|
|
296
|
+
}
|
|
297
|
+
.light-mode .settings-modal {
|
|
298
|
+
background: rgba(15, 15, 35, 0.95) !important;
|
|
299
|
+
}
|
|
300
|
+
</style><style id="settings-optgroup-styles">
|
|
301
|
+
#settingsAudience optgroup {
|
|
302
|
+
background: #3a3a4a;
|
|
303
|
+
color: #e0d0f0;
|
|
304
|
+
font-weight: 600;
|
|
305
|
+
}
|
|
306
|
+
#settingsAudience option {
|
|
307
|
+
background: #1a1a3a;
|
|
308
|
+
color: #e0d0f0;
|
|
309
|
+
}
|
|
310
|
+
.light-mode #settingsAudience optgroup {
|
|
311
|
+
background: #3a3a4a;
|
|
312
|
+
color: #e0d0f0;
|
|
313
|
+
}
|
|
314
|
+
.light-mode #settingsAudience option {
|
|
315
|
+
background: #1a1a3a;
|
|
316
|
+
color: #e0d0f0;
|
|
317
|
+
}
|
|
318
|
+
</style><style id="time-readout-styles">
|
|
319
|
+
.time-readout {
|
|
320
|
+
color: #b794f6;
|
|
321
|
+
font-size: 11px;
|
|
322
|
+
margin-top: 6px;
|
|
323
|
+
opacity: 0.85;
|
|
324
|
+
font-variant-numeric: tabular-nums;
|
|
325
|
+
display: flex;
|
|
326
|
+
align-items: center;
|
|
327
|
+
gap: 8px;
|
|
328
|
+
}
|
|
329
|
+
.time-readout-paused {
|
|
330
|
+
display: none;
|
|
331
|
+
color: #ff6b6b;
|
|
332
|
+
font-weight: 600;
|
|
333
|
+
font-size: 11px;
|
|
334
|
+
}
|
|
335
|
+
.time-readout.paused .time-readout-paused {
|
|
336
|
+
display: inline;
|
|
337
|
+
}
|
|
338
|
+
.ff-row {
|
|
339
|
+
display: flex;
|
|
340
|
+
align-items: center;
|
|
341
|
+
gap: 6px;
|
|
342
|
+
margin-top: 6px;
|
|
343
|
+
}
|
|
344
|
+
.ff-input {
|
|
345
|
+
width: 80px;
|
|
346
|
+
padding: 4px 8px;
|
|
347
|
+
border-radius: 8px;
|
|
348
|
+
border: 1px solid rgba(138, 43, 226, 0.3);
|
|
349
|
+
background: rgba(255, 255, 255, 0.1);
|
|
350
|
+
color: #b794f6;
|
|
351
|
+
font-size: 11px;
|
|
352
|
+
box-sizing: border-box;
|
|
353
|
+
}
|
|
354
|
+
.ff-input::placeholder { color: rgba(183, 148, 246, 0.5); }
|
|
355
|
+
.ff-btn {
|
|
356
|
+
padding: 4px 10px;
|
|
357
|
+
border: 1px solid rgba(138, 43, 226, 0.3);
|
|
358
|
+
border-radius: 8px;
|
|
359
|
+
background: linear-gradient(135deg, #667eea 0, #764ba2 100%);
|
|
360
|
+
color: #fff;
|
|
361
|
+
cursor: pointer;
|
|
362
|
+
font-size: 11px;
|
|
363
|
+
white-space: nowrap;
|
|
364
|
+
transition: 0.2s;
|
|
365
|
+
}
|
|
366
|
+
.ff-btn:hover:not(:disabled) {
|
|
367
|
+
box-shadow: 0 0 10px rgba(102, 126, 234, 0.4);
|
|
368
|
+
}
|
|
369
|
+
.ff-btn:disabled {
|
|
370
|
+
opacity: 0.4;
|
|
371
|
+
cursor: not-allowed;
|
|
372
|
+
}
|
|
373
|
+
.pause-btn {
|
|
374
|
+
display: inline-flex;
|
|
375
|
+
align-items: center;
|
|
376
|
+
justify-content: center;
|
|
377
|
+
width: 32px;
|
|
378
|
+
height: 32px;
|
|
379
|
+
border: 1px solid rgba(138, 43, 226, 0.3);
|
|
380
|
+
border-radius: 8px;
|
|
381
|
+
background: rgba(255,255,255,0.1);
|
|
382
|
+
color: #f093fb;
|
|
383
|
+
cursor: pointer;
|
|
384
|
+
font-size: 16px;
|
|
385
|
+
transition: 0.2s;
|
|
386
|
+
vertical-align: middle;
|
|
387
|
+
margin-left: 8px;
|
|
388
|
+
padding: 0;
|
|
389
|
+
line-height: 1;
|
|
390
|
+
}
|
|
391
|
+
.pause-btn:hover {
|
|
392
|
+
background: rgba(255,255,255,0.2);
|
|
393
|
+
box-shadow: 0 0 10px rgba(240, 147, 251, 0.3);
|
|
394
|
+
}
|
|
395
|
+
.pause-btn.paused {
|
|
396
|
+
color: #667eea;
|
|
397
|
+
border-color: rgba(102, 126, 234, 0.5);
|
|
398
|
+
}
|
|
399
|
+
.pause-btn:disabled {
|
|
400
|
+
opacity: 0.3;
|
|
401
|
+
cursor: not-allowed;
|
|
402
|
+
}
|
|
403
|
+
.speed-row {
|
|
404
|
+
display: flex;
|
|
405
|
+
align-items: center;
|
|
406
|
+
}
|
|
407
|
+
.speed-row input[type="range"] {
|
|
408
|
+
flex: 1;
|
|
409
|
+
}
|
|
410
|
+
.light-mode .time-readout {
|
|
411
|
+
color: #b794f6;
|
|
412
|
+
}
|
|
413
|
+
.light-mode .pause-btn {
|
|
414
|
+
border-color: rgba(138, 43, 226, 0.3);
|
|
415
|
+
background: rgba(255,255,255,0.1);
|
|
416
|
+
color: #f093fb;
|
|
417
|
+
}
|
|
418
|
+
</style></head>
|
|
419
|
+
<body>
|
|
420
|
+
<div class="chat-panel" data-locked="true">
|
|
421
|
+
<div class="chat-header" data-locked="true">SynthOS</div>
|
|
422
|
+
<div class="chat-messages" id="chatMessages" data-locked="true">
|
|
423
|
+
<div class="chat-message"><p><strong>SynthOS:</strong> Welcome to the <strong>Solar Explorer</strong></p>
|
|
424
|
+
<p><strong>Main View Controls:</strong></p>
|
|
425
|
+
<ul>
|
|
426
|
+
<li><strong>Hover</strong> over any planet to see quick facts</li>
|
|
427
|
+
<li><strong>Click</strong> a planet to enter its detail view with moons</li>
|
|
428
|
+
<li>Use the <strong>speed slider</strong> (top-left) to control time</li>
|
|
429
|
+
<li>Toggle <strong>orbits, labels, and asteroids</strong> with the bottom toolbar</li>
|
|
430
|
+
</ul>
|
|
431
|
+
<p><strong>Detail View:</strong></p>
|
|
432
|
+
<ul>
|
|
433
|
+
<li>Watch moons orbit realistically around the planet</li>
|
|
434
|
+
<li><strong>Hover</strong> over moons for info, <strong>click</strong> to ask AI about them</li>
|
|
435
|
+
<li>Use the <strong>moon speed slider</strong> to control orbital animation</li>
|
|
436
|
+
<li>Ask questions in the sidebar input box</li>
|
|
437
|
+
</ul>
|
|
438
|
+
<p><strong>Solar Explorer Panel</strong> (top-right): Ask any question about the solar system!</p>
|
|
439
|
+
<p>Try clicking on Jupiter or Saturn to see their impressive moon systems! 🪐</p></div>
|
|
440
|
+
</div>
|
|
441
|
+
<div class="link-group" data-locked="true">
|
|
442
|
+
<a href="#" id="saveLink" data-locked="true">Save</a>
|
|
443
|
+
<a href="/pages" id="pagesLink" data-locked="true">Pages</a>
|
|
444
|
+
<a href="#" id="resetLink" data-locked="true">Reset</a>
|
|
445
|
+
</div>
|
|
446
|
+
<form action="/" method="POST" id="chatForm" data-locked="true">
|
|
447
|
+
<input type="text" class="chat-input" id="chatInput" name="message" placeholder="Type a message..." data-locked="true">
|
|
448
|
+
<button type="submit" class="chat-submit" data-locked="true">Send</button>
|
|
449
|
+
</form>
|
|
450
|
+
</div>
|
|
451
|
+
<div class="viewer-panel full-viewer" id="viewerPanel" style="background: #0a0a19;">
|
|
452
|
+
<div id="solarSystem"><div id="planetDetailCard" class="planet-detail-card"><div class="planet-header">
|
|
453
|
+
<div class="planet-icon"></div>
|
|
454
|
+
<div class="planet-name"></div>
|
|
455
|
+
</div>
|
|
456
|
+
<div class="stat-row"><span class="stat-label">Type</span><span class="stat-value" data-field="type"></span></div>
|
|
457
|
+
<div class="stat-row"><span class="stat-label">Diameter</span><span class="stat-value" data-field="diameter"></span></div>
|
|
458
|
+
<div class="stat-row"><span class="stat-label">Moons</span><span class="stat-value" data-field="moons"></span></div>
|
|
459
|
+
<div class="fun-fact">
|
|
460
|
+
<span class="fun-fact-label">Fun Fact</span>
|
|
461
|
+
<span data-field="funFact"></span>
|
|
462
|
+
</div>
|
|
463
|
+
<div class="question-section">
|
|
464
|
+
<input type="text" class="question-input" placeholder="Ask a question...">
|
|
465
|
+
<div class="ai-answer"></div>
|
|
466
|
+
</div></div><div id="detailViewContainer" class="detail-view-container">
|
|
467
|
+
<div class="detail-planet-area">
|
|
468
|
+
<canvas id="detailCanvas" class="detail-planet-canvas"></canvas>
|
|
469
|
+
<div id="moonDetailCard" class="moon-detail-card">
|
|
470
|
+
<div class="moon-name"></div>
|
|
471
|
+
<div class="moon-stat"><span class="moon-stat-label">Diameter</span><span class="moon-stat-value" data-field="diameter"></span></div>
|
|
472
|
+
<div class="moon-stat"><span class="moon-stat-label">Distance</span><span class="moon-stat-value" data-field="distance"></span></div>
|
|
473
|
+
<div class="moon-stat"><span class="moon-stat-label">Orbital Period</span><span class="moon-stat-value" data-field="period"></span></div>
|
|
474
|
+
<div class="moon-fun-fact"></div>
|
|
475
|
+
<div class="moon-click-hint" style="margin-top: 8px; font-size: 10px; color: #f093fb; font-style: italic;"></div>
|
|
476
|
+
</div>
|
|
477
|
+
<div id="moonSpeedControl" class="moon-speed-control" style="display: none; position: absolute; top: 20px; left: 20px; z-index: 100; background: rgba(15, 15, 35, 0.6); padding: 15px 20px; border-radius: 15px; border: 1px solid rgba(138, 43, 226, 0.3);">
|
|
478
|
+
<label style="color: #b794f6; font-size: 13px; display: block; margin-bottom: 8px;">Moon Orbit Speed: <span id="moonSpeedValue">1x</span></label>
|
|
479
|
+
<div class="speed-row">
|
|
480
|
+
<input type="range" id="moonSpeedSlider" min="0" max="20" step="0.5" value="1" style="width: 150px; accent-color: #f093fb;">
|
|
481
|
+
<button class="pause-btn" id="moonPauseBtn" title="Pause/Play">⏸</button>
|
|
482
|
+
</div>
|
|
483
|
+
<div class="time-readout" id="moonTimeReadout">
|
|
484
|
+
<span class="time-readout-speed" id="moonTimeSpeed">~1 days / sec</span>
|
|
485
|
+
<span class="time-readout-paused" id="moonTimePaused">Paused</span>
|
|
486
|
+
</div>
|
|
487
|
+
<div class="ff-row" id="moonFfRow" style="display: none;">
|
|
488
|
+
<input type="number" id="moonFfDays" class="ff-input" placeholder="Days..." min="1" step="1">
|
|
489
|
+
<button class="ff-btn" id="moonFfBtn" disabled>Fast Forward ⏩</button>
|
|
490
|
+
</div>
|
|
491
|
+
</div></div>
|
|
492
|
+
<div class="detail-sidebar" id="detailSidebar" style="width: 340px; min-width: 340px; background: rgba(15, 15, 35, 0.95); border-left: 1px solid rgba(138, 43, 226, 0.3); padding: 20px; display: flex; flex-direction: column; gap: 12px; overflow: hidden;">
|
|
493
|
+
<button class="back-button" id="backToSystem">← Back to System</button>
|
|
494
|
+
<div class="detail-planet-header" style="display: flex; align-items: center; gap: 12px;">
|
|
495
|
+
<div id="detailPlanetIcon" style="width: 40px; height: 40px; border-radius: 50%; box-shadow: 0 0 12px rgba(255, 200, 100, 0.3); flex-shrink: 0;"></div>
|
|
496
|
+
<div id="detailPlanetName" style="font-size: 20px; font-weight: 600; background: linear-gradient(135deg, #f093fb, #667eea); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;"></div>
|
|
497
|
+
</div>
|
|
498
|
+
<div class="detail-stats" style="display: flex; flex-direction: column; gap: 6px; font-size: 12px;">
|
|
499
|
+
<div style="display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid rgba(255,255,255,0.08);"><span style="color: rgba(255,255,255,0.5);">Type</span><span id="detailType" style="color: #fff; font-weight: 500;"></span></div>
|
|
500
|
+
<div style="display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid rgba(255,255,255,0.08);"><span style="color: rgba(255,255,255,0.5);">Diameter</span><span id="detailDiameter" style="color: #fff; font-weight: 500;"></span></div>
|
|
501
|
+
<div style="display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid rgba(255,255,255,0.08);"><span style="color: rgba(255,255,255,0.5);">Distance from Sun</span><span id="detailDistance" style="color: #fff; font-weight: 500;"></span></div>
|
|
502
|
+
<div style="display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid rgba(255,255,255,0.08);"><span style="color: rgba(255,255,255,0.5);">Orbital Period</span><span id="detailOrbitalPeriod" style="color: #fff; font-weight: 500;"></span></div>
|
|
503
|
+
<div style="display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid rgba(255,255,255,0.08);"><span style="color: rgba(255,255,255,0.5);">Day Length</span><span id="detailDayLength" style="color: #fff; font-weight: 500;"></span></div>
|
|
504
|
+
<div style="display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid rgba(255,255,255,0.08);"><span style="color: rgba(255,255,255,0.5);">Surface Temp</span><span id="detailSurfaceTemp" style="color: #fff; font-weight: 500;"></span></div>
|
|
505
|
+
<div style="display: flex; justify-content: space-between; padding: 6px 0;"><span style="color: rgba(255,255,255,0.5);">Moons</span><span id="detailMoons" style="color: #fff; font-weight: 500;"></span></div>
|
|
506
|
+
</div>
|
|
507
|
+
<input type="text" id="planetQuestionInput" placeholder="Ask about this planet..." style="width: 100%; padding: 10px; border-radius: 8px; border: 1px solid rgba(138, 43, 226, 0.3); background: rgba(255, 255, 255, 0.1); color: #b794f6; font-size: 13px; box-sizing: border-box; flex-shrink: 0;">
|
|
508
|
+
<div id="planetAnswerBox" style="flex: 1; min-height: 100px; overflow-y: auto; padding: 10px; background: rgba(0, 0, 0, 0.2); border-radius: 8px; font-size: 12px; line-height: 1.5; color: #b794f6;"></div>
|
|
509
|
+
</div>
|
|
510
|
+
</div></div>
|
|
511
|
+
<div class="speed-control">
|
|
512
|
+
<label>Simulation Speed: <span id="speedValue">1x</span></label>
|
|
513
|
+
<div class="speed-row">
|
|
514
|
+
<input type="range" id="speedSlider" min="0.0" max="10" step="0.1" value="1" style="width: 150px; accent-color: #f093fb;">
|
|
515
|
+
<button class="pause-btn" id="pauseBtn" title="Pause/Play">⏸</button>
|
|
516
|
+
</div>
|
|
517
|
+
<div class="time-readout" id="mainTimeReadout">
|
|
518
|
+
<span class="time-readout-speed" id="mainTimeSpeed">~60 days / sec</span>
|
|
519
|
+
<span class="time-readout-paused" id="mainTimePaused">Paused</span>
|
|
520
|
+
</div>
|
|
521
|
+
<div class="ff-row" id="mainFfRow" style="display: none;">
|
|
522
|
+
<input type="number" id="mainFfDays" class="ff-input" placeholder="Days..." min="1" step="1">
|
|
523
|
+
<button class="ff-btn" id="mainFfBtn" disabled>Fast Forward ⏩</button>
|
|
524
|
+
</div>
|
|
525
|
+
</div>
|
|
526
|
+
<div class="info-panel" id="infoPanel" style="width: 336px; height: 384px; background: rgba(15, 15, 35, 0.6); display: flex; flex-direction: column; padding: 20px; padding-bottom: 10px; box-sizing: border-box;"><h3 style="margin: 0 0 10px 0; flex-shrink: 0;">Solar Explorer</h3>
|
|
527
|
+
<input type="text" id="solarQuestion" placeholder="Ask about the solar system..." style="width: 100%; padding: 10px; border-radius: 8px; border: 1px solid rgba(138, 43, 226, 0.3); background: rgba(255, 255, 255, 0.1); color: #b794f6; font-size: 13px; box-sizing: border-box; margin-bottom: 10px; flex-shrink: 0;">
|
|
528
|
+
|
|
529
|
+
<div id="solarAnswer" style="flex: 1 1 0; min-height: 0; overflow-y: auto; padding: 10px; background: rgba(0, 0, 0, 0.2); border-radius: 8px; font-size: 12px; line-height: 1.5; color: #b794f6;"></div></div>
|
|
530
|
+
<div class="controls">
|
|
531
|
+
<button id="toggleOrbits" class="active">Show Orbits</button>
|
|
532
|
+
<button id="toggleLabels" class="active">Show Labels</button>
|
|
533
|
+
<button id="toggleAsteroids">Show Asteroids</button>
|
|
534
|
+
<button id="resetView">Reset View</button>
|
|
535
|
+
<button id="openSettings">⚙️ Settings</button>
|
|
536
|
+
</div>
|
|
537
|
+
<div id="loadingOverlay" class="loading-overlay"><div class="spinner"></div></div>
|
|
538
|
+
<div id="settingsOverlay" class="settings-overlay">
|
|
539
|
+
<div class="settings-modal">
|
|
540
|
+
<h2>⚙️ Explorer Settings</h2>
|
|
541
|
+
|
|
542
|
+
<div class="settings-section">
|
|
543
|
+
<label>Target Audience</label>
|
|
544
|
+
<select id="settingsAudience">
|
|
545
|
+
<optgroup label="By Age Group">
|
|
546
|
+
<option value="ages-5-7">Ages 5–7 (Early Elementary)</option>
|
|
547
|
+
<option value="ages-8-10">Ages 8–10 (Upper Elementary)</option>
|
|
548
|
+
<option value="ages-11-13">Ages 11–13 (Middle School)</option>
|
|
549
|
+
<option value="ages-14-17">Ages 14–17 (High School)</option>
|
|
550
|
+
<option value="ages-18-plus">Ages 18+ (Adult General)</option>
|
|
551
|
+
</optgroup>
|
|
552
|
+
<optgroup label="By Expertise">
|
|
553
|
+
<option value="casual">Casual Learner</option>
|
|
554
|
+
<option value="hobbyist" selected="">Hobbyist / Enthusiast</option>
|
|
555
|
+
<option value="student-undergrad">Undergraduate Student</option>
|
|
556
|
+
<option value="student-grad">Graduate Student</option>
|
|
557
|
+
<option value="educator">Educator / Teacher</option>
|
|
558
|
+
<option value="academic">Academic / Researcher</option>
|
|
559
|
+
<option value="professional">Professional Astronomer</option>
|
|
560
|
+
</optgroup>
|
|
561
|
+
</select>
|
|
562
|
+
<div class="settings-description">Controls the complexity, vocabulary, and depth of AI responses.</div>
|
|
563
|
+
</div>
|
|
564
|
+
|
|
565
|
+
<div class="settings-section">
|
|
566
|
+
<label>Temperature Units</label>
|
|
567
|
+
<div class="settings-radio-group">
|
|
568
|
+
<div class="settings-radio-item">
|
|
569
|
+
<input type="radio" name="tempUnit" id="tempC" value="C" checked="">
|
|
570
|
+
<label for="tempC">Celsius (°C)</label>
|
|
571
|
+
</div>
|
|
572
|
+
<div class="settings-radio-item">
|
|
573
|
+
<input type="radio" name="tempUnit" id="tempF" value="F">
|
|
574
|
+
<label for="tempF">Fahrenheit (°F)</label>
|
|
575
|
+
</div>
|
|
576
|
+
</div>
|
|
577
|
+
</div>
|
|
578
|
+
|
|
579
|
+
<div class="settings-section">
|
|
580
|
+
<label>Distance Units</label>
|
|
581
|
+
<div class="settings-radio-group">
|
|
582
|
+
<div class="settings-radio-item">
|
|
583
|
+
<input type="radio" name="distUnit" id="distKm" value="km" checked="">
|
|
584
|
+
<label for="distKm">Kilometers (km)</label>
|
|
585
|
+
</div>
|
|
586
|
+
<div class="settings-radio-item">
|
|
587
|
+
<input type="radio" name="distUnit" id="distMi" value="miles">
|
|
588
|
+
<label for="distMi">Miles (mi)</label>
|
|
589
|
+
</div>
|
|
590
|
+
</div>
|
|
591
|
+
</div>
|
|
592
|
+
|
|
593
|
+
<div class="settings-btn-row">
|
|
594
|
+
<button class="settings-btn settings-btn-cancel" id="settingsCancel">Cancel</button>
|
|
595
|
+
<button class="settings-btn settings-btn-save" id="settingsSave">Save Settings</button>
|
|
596
|
+
</div>
|
|
597
|
+
</div>
|
|
598
|
+
</div></div>
|
|
599
|
+
<div id="instructions" style="display: none;" data-locked="true"></div>
|
|
600
|
+
<div id="thoughts" style="display: none;" data-locked="true">U</div>
|
|
601
|
+
<script id="solar-sim">
|
|
602
|
+
const container = document.getElementById("solarSystem");
|
|
603
|
+
const canvas = document.createElement("canvas");
|
|
604
|
+
const ctx = canvas.getContext("2d");
|
|
605
|
+
let width, height, centerX, centerY;
|
|
606
|
+
container.appendChild(canvas);
|
|
607
|
+
|
|
608
|
+
let simulationSpeed = 1;
|
|
609
|
+
let timeDirection = 1;
|
|
610
|
+
let showOrbits = true;
|
|
611
|
+
let showLabels = true;
|
|
612
|
+
let showAsteroids = false;
|
|
613
|
+
let time = 0;
|
|
614
|
+
let mainPaused = false;
|
|
615
|
+
let moonPausedState = false;
|
|
616
|
+
let mainFfRemaining = 0;
|
|
617
|
+
let moonFfRemaining = 0;
|
|
618
|
+
|
|
619
|
+
const planetDetailsData = JSON.parse(document.getElementById('planet-details').textContent);
|
|
620
|
+
|
|
621
|
+
const planets = [
|
|
622
|
+
{ name: "Mercury", key: "mercury", color: "#b5b5b5", size: 4, distance: 50, period: 0.24, eccentricity: 0.206 },
|
|
623
|
+
{ name: "Venus", key: "venus", color: "#e6c87a", size: 6, distance: 75, period: 0.62, eccentricity: 0.007 },
|
|
624
|
+
{ name: "Earth", key: "earth", color: "#6b93d6", size: 6, distance: 100, period: 1, eccentricity: 0.017 },
|
|
625
|
+
{ name: "Mars", key: "mars", color: "#c1440e", size: 5, distance: 130, period: 1.88, eccentricity: 0.093 },
|
|
626
|
+
{ name: "Jupiter", key: "jupiter", color: "#d4a574", size: 18, distance: 200, period: 11.86, eccentricity: 0.049 },
|
|
627
|
+
{ name: "Saturn", key: "saturn", color: "#f4d59e", size: 15, distance: 270, period: 29.46, eccentricity: 0.057 },
|
|
628
|
+
{ name: "Uranus", key: "uranus", color: "#d1e7e7", size: 10, distance: 330, period: 84.01, eccentricity: 0.046 },
|
|
629
|
+
{ name: "Neptune", key: "neptune", color: "#5b5ddf", size: 10, distance: 380, period: 164.8, eccentricity: 0.01 },
|
|
630
|
+
{ name: "Pluto", key: "pluto", color: "#a89f91", size: 3, distance: 420, period: 248, eccentricity: 0.249 }
|
|
631
|
+
];
|
|
632
|
+
|
|
633
|
+
let asteroids = [];
|
|
634
|
+
let planetPositions = [];
|
|
635
|
+
let hoveredPlanet = null;
|
|
636
|
+
let inDetailView = false;
|
|
637
|
+
window.detailPlanet = null;
|
|
638
|
+
let detailMoons = [];
|
|
639
|
+
let detailMoonPositions = [];
|
|
640
|
+
let detailTime = 0;
|
|
641
|
+
let moonSpeed = 1;
|
|
642
|
+
let hoveredMoon = null;
|
|
643
|
+
let detailAnimId = null;
|
|
644
|
+
|
|
645
|
+
// FPS tracking for accurate time readout
|
|
646
|
+
let lastFrameTime = performance.now();
|
|
647
|
+
let currentFps = 60;
|
|
648
|
+
let fpsSmoothing = 0.95;
|
|
649
|
+
|
|
650
|
+
function updateFps() {
|
|
651
|
+
const now = performance.now();
|
|
652
|
+
const delta = now - lastFrameTime;
|
|
653
|
+
lastFrameTime = now;
|
|
654
|
+
if (delta > 0) {
|
|
655
|
+
const instantFps = 1000 / delta;
|
|
656
|
+
currentFps = fpsSmoothing * currentFps + (1 - fpsSmoothing) * instantFps;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function formatSimTime(daysPerSec) {
|
|
661
|
+
if (daysPerSec < 0.01) return 'Paused';
|
|
662
|
+
if (daysPerSec < 1) {
|
|
663
|
+
return '~' + Math.round(daysPerSec * 24) + ' hours / sec';
|
|
664
|
+
}
|
|
665
|
+
if (daysPerSec < 365) {
|
|
666
|
+
return '~' + Math.round(daysPerSec) + ' days / sec';
|
|
667
|
+
}
|
|
668
|
+
const years = daysPerSec / 365;
|
|
669
|
+
return '~' + years.toFixed(1) + ' years / sec';
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Throttle readout updates to ~1 per second
|
|
673
|
+
let lastReadoutUpdate = 0;
|
|
674
|
+
const READOUT_INTERVAL = 1000;
|
|
675
|
+
|
|
676
|
+
function updateMainTimeReadout(force) {
|
|
677
|
+
const el = document.getElementById('mainTimeReadout');
|
|
678
|
+
const speedEl = document.getElementById('mainTimeSpeed');
|
|
679
|
+
const ffRow = document.getElementById('mainFfRow');
|
|
680
|
+
if (!el || !speedEl) return;
|
|
681
|
+
el.classList.toggle('paused', mainPaused);
|
|
682
|
+
if (ffRow) ffRow.style.display = mainPaused ? 'flex' : 'none';
|
|
683
|
+
if (mainPaused && !force) return;
|
|
684
|
+
const now = performance.now();
|
|
685
|
+
if (!force && now - lastReadoutUpdate < READOUT_INTERVAL) return;
|
|
686
|
+
lastReadoutUpdate = now;
|
|
687
|
+
const daysPerSec = currentFps * simulationSpeed;
|
|
688
|
+
speedEl.textContent = formatSimTime(daysPerSec);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
let lastMoonReadoutUpdate = 0;
|
|
692
|
+
|
|
693
|
+
function updateMoonTimeReadout(force) {
|
|
694
|
+
const el = document.getElementById('moonTimeReadout');
|
|
695
|
+
const speedEl = document.getElementById('moonTimeSpeed');
|
|
696
|
+
const ffRow = document.getElementById('moonFfRow');
|
|
697
|
+
if (!el || !speedEl) return;
|
|
698
|
+
el.classList.toggle('paused', moonPausedState);
|
|
699
|
+
if (ffRow) ffRow.style.display = moonPausedState ? 'flex' : 'none';
|
|
700
|
+
if (moonPausedState && !force) return;
|
|
701
|
+
const now = performance.now();
|
|
702
|
+
if (!force && now - lastMoonReadoutUpdate < READOUT_INTERVAL) return;
|
|
703
|
+
lastMoonReadoutUpdate = now;
|
|
704
|
+
const daysPerSec = currentFps * moonSpeed / 60;
|
|
705
|
+
speedEl.textContent = formatSimTime(daysPerSec);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/* === Orbital State Helpers for AI Context === */
|
|
709
|
+
|
|
710
|
+
window.getSystemOrbitalState = function() {
|
|
711
|
+
const state = {
|
|
712
|
+
simulationSpeed: simulationSpeed,
|
|
713
|
+
simulationTimeDirection: timeDirection > 0 ? 'forward' : 'reverse',
|
|
714
|
+
simulationPaused: mainPaused,
|
|
715
|
+
simulationTicks: time,
|
|
716
|
+
planets: []
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
planets.forEach(function(planet) {
|
|
720
|
+
const meanAnomaly = (2 * Math.PI) / (365 * planet.period) * time * simulationSpeed;
|
|
721
|
+
const e = planet.eccentricity;
|
|
722
|
+
const trueAnomaly = meanAnomaly + 2 * e * Math.sin(meanAnomaly);
|
|
723
|
+
let angleDeg = ((trueAnomaly * 180 / Math.PI) % 360 + 360) % 360;
|
|
724
|
+
const r = planet.distance * (1 - e * e) / (1 + e * Math.cos(trueAnomaly));
|
|
725
|
+
|
|
726
|
+
state.planets.push({
|
|
727
|
+
name: planet.name,
|
|
728
|
+
angleDegrees: Math.round(angleDeg * 10) / 10,
|
|
729
|
+
relativeDistanceFromSun: Math.round(r * 10) / 10,
|
|
730
|
+
orbitalPeriodYears: planet.period
|
|
731
|
+
});
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
const closest = { pair: '', distance: Infinity };
|
|
735
|
+
for (let i = 0; i < state.planets.length; i++) {
|
|
736
|
+
for (let j = i + 1; j < state.planets.length; j++) {
|
|
737
|
+
const p1 = state.planets[i];
|
|
738
|
+
const p2 = state.planets[j];
|
|
739
|
+
const a1 = p1.angleDegrees * Math.PI / 180;
|
|
740
|
+
const a2 = p2.angleDegrees * Math.PI / 180;
|
|
741
|
+
const r1 = p1.relativeDistanceFromSun;
|
|
742
|
+
const r2 = p2.relativeDistanceFromSun;
|
|
743
|
+
const dx = r1 * Math.cos(a1) - r2 * Math.cos(a2);
|
|
744
|
+
const dy = r1 * Math.sin(a1) - r2 * Math.sin(a2);
|
|
745
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
746
|
+
if (dist < closest.distance) {
|
|
747
|
+
closest.distance = Math.round(dist * 10) / 10;
|
|
748
|
+
closest.pair = p1.name + ' and ' + p2.name;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
state.closestPair = closest;
|
|
753
|
+
|
|
754
|
+
return state;
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
window.getDetailOrbitalState = function() {
|
|
758
|
+
if (!inDetailView || !window.detailPlanet) return null;
|
|
759
|
+
|
|
760
|
+
const state = {
|
|
761
|
+
planet: window.detailPlanet.name,
|
|
762
|
+
moonAnimationSpeed: moonSpeed,
|
|
763
|
+
moonPaused: moonPausedState,
|
|
764
|
+
moons: []
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
detailMoons.forEach(function(moon) {
|
|
768
|
+
let angleDeg = ((moon.angle * 180 / Math.PI) % 360 + 360) % 360;
|
|
769
|
+
state.moons.push({
|
|
770
|
+
name: moon.name,
|
|
771
|
+
angleDegrees: Math.round(angleDeg * 10) / 10,
|
|
772
|
+
orbitalPeriodDays: moon.periodDays,
|
|
773
|
+
distanceKm: moon.distanceKm
|
|
774
|
+
});
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
return state;
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
function generateAsteroids() {
|
|
781
|
+
asteroids = [];
|
|
782
|
+
for (let i = 0; i < 500; i++) {
|
|
783
|
+
asteroids.push({
|
|
784
|
+
distance: 155 + 35 * Math.random(),
|
|
785
|
+
angle: Math.random() * Math.PI * 2,
|
|
786
|
+
speed: 5e-4 + 0.001 * Math.random(),
|
|
787
|
+
size: 0.5 + 1.5 * Math.random()
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function resize() {
|
|
793
|
+
width = container.clientWidth;
|
|
794
|
+
height = container.clientHeight;
|
|
795
|
+
canvas.width = width;
|
|
796
|
+
canvas.height = height;
|
|
797
|
+
centerX = width / 2;
|
|
798
|
+
centerY = height / 2;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function getOrbitalPosition(planet, t) {
|
|
802
|
+
const meanAnomaly = (2 * Math.PI) / (365 * planet.period) * t * simulationSpeed;
|
|
803
|
+
const e = planet.eccentricity;
|
|
804
|
+
const trueAnomaly = meanAnomaly + 2 * e * Math.sin(meanAnomaly);
|
|
805
|
+
const r = planet.distance * (1 - e * e) / (1 + e * Math.cos(trueAnomaly));
|
|
806
|
+
return {
|
|
807
|
+
x: centerX + r * Math.cos(trueAnomaly),
|
|
808
|
+
y: centerY + r * Math.sin(trueAnomaly),
|
|
809
|
+
angle: trueAnomaly
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function drawOrbit(planet) {
|
|
814
|
+
ctx.beginPath();
|
|
815
|
+
ctx.strokeStyle = "rgba(138, 43, 226, 0.35)";
|
|
816
|
+
ctx.lineWidth = 1;
|
|
817
|
+
const a = planet.distance;
|
|
818
|
+
const e = planet.eccentricity;
|
|
819
|
+
const b = a * Math.sqrt(1 - e * e);
|
|
820
|
+
const c = a * e;
|
|
821
|
+
ctx.ellipse(centerX + c, centerY, a, b, 0, 0, 2 * Math.PI);
|
|
822
|
+
ctx.stroke();
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function drawSun() {
|
|
826
|
+
const gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, 60);
|
|
827
|
+
gradient.addColorStop(0, "rgba(255, 200, 50, 1)");
|
|
828
|
+
gradient.addColorStop(0.3, "rgba(255, 150, 0, 0.8)");
|
|
829
|
+
gradient.addColorStop(0.6, "rgba(255, 100, 0, 0.3)");
|
|
830
|
+
gradient.addColorStop(1, "rgba(255, 50, 0, 0)");
|
|
831
|
+
ctx.beginPath();
|
|
832
|
+
ctx.arc(centerX, centerY, 60, 0, 2 * Math.PI);
|
|
833
|
+
ctx.fillStyle = gradient;
|
|
834
|
+
ctx.fill();
|
|
835
|
+
ctx.beginPath();
|
|
836
|
+
ctx.arc(centerX, centerY, 25, 0, 2 * Math.PI);
|
|
837
|
+
ctx.fillStyle = "#fff5c0";
|
|
838
|
+
ctx.fill();
|
|
839
|
+
ctx.beginPath();
|
|
840
|
+
ctx.arc(centerX, centerY, 30, 0, 2 * Math.PI);
|
|
841
|
+
ctx.fillStyle = "rgba(255, 220, 100, 0.5)";
|
|
842
|
+
ctx.fill();
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function drawSaturnRings(x, y, size, angle) {
|
|
846
|
+
ctx.save();
|
|
847
|
+
ctx.translate(x, y);
|
|
848
|
+
ctx.rotate(0.4);
|
|
849
|
+
ctx.beginPath();
|
|
850
|
+
ctx.ellipse(0, 0, 2.2 * size, 0.5 * size, 0, 0, 2 * Math.PI);
|
|
851
|
+
ctx.strokeStyle = "rgba(210, 180, 140, 0.6)";
|
|
852
|
+
ctx.lineWidth = 3;
|
|
853
|
+
ctx.stroke();
|
|
854
|
+
ctx.beginPath();
|
|
855
|
+
ctx.ellipse(0, 0, 1.8 * size, 0.4 * size, 0, 0, 2 * Math.PI);
|
|
856
|
+
ctx.strokeStyle = "rgba(180, 150, 120, 0.4)";
|
|
857
|
+
ctx.lineWidth = 2;
|
|
858
|
+
ctx.stroke();
|
|
859
|
+
ctx.restore();
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function drawPlanet(planet, pos) {
|
|
863
|
+
ctx.beginPath();
|
|
864
|
+
ctx.arc(pos.x + 2, pos.y + 2, planet.size, 0, 2 * Math.PI);
|
|
865
|
+
ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
|
|
866
|
+
ctx.fill();
|
|
867
|
+
const gradient = ctx.createRadialGradient(
|
|
868
|
+
pos.x - 0.3 * planet.size, pos.y - 0.3 * planet.size, 0,
|
|
869
|
+
pos.x, pos.y, planet.size
|
|
870
|
+
);
|
|
871
|
+
gradient.addColorStop(0, lightenColor(planet.color, 30));
|
|
872
|
+
gradient.addColorStop(1, planet.color);
|
|
873
|
+
ctx.beginPath();
|
|
874
|
+
ctx.arc(pos.x, pos.y, planet.size, 0, 2 * Math.PI);
|
|
875
|
+
ctx.fillStyle = gradient;
|
|
876
|
+
ctx.fill();
|
|
877
|
+
if (planet.name === "Saturn") {
|
|
878
|
+
drawSaturnRings(pos.x, pos.y, planet.size, pos.angle);
|
|
879
|
+
}
|
|
880
|
+
if (showLabels) {
|
|
881
|
+
ctx.fillStyle = "#e0d0f0";
|
|
882
|
+
ctx.font = "bold 11px Segoe UI, sans-serif";
|
|
883
|
+
ctx.textAlign = "center";
|
|
884
|
+
ctx.shadowColor = "rgba(240, 147, 251, 0.6)";
|
|
885
|
+
ctx.shadowBlur = 4;
|
|
886
|
+
ctx.fillText(planet.name, pos.x, pos.y - planet.size - 8);
|
|
887
|
+
ctx.shadowColor = "transparent";
|
|
888
|
+
ctx.shadowBlur = 0;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
function drawAsteroidBelt() {
|
|
893
|
+
ctx.fillStyle = "rgba(150, 150, 150, 0.6)";
|
|
894
|
+
asteroids.forEach(function(asteroid) {
|
|
895
|
+
if (!mainPaused) {
|
|
896
|
+
asteroid.angle += asteroid.speed * simulationSpeed * timeDirection;
|
|
897
|
+
}
|
|
898
|
+
const x = centerX + asteroid.distance * Math.cos(asteroid.angle);
|
|
899
|
+
const y = centerY + asteroid.distance * Math.sin(asteroid.angle);
|
|
900
|
+
ctx.beginPath();
|
|
901
|
+
ctx.arc(x, y, asteroid.size, 0, 2 * Math.PI);
|
|
902
|
+
ctx.fill();
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
function lightenColor(color, percent) {
|
|
907
|
+
const num = parseInt(color.replace("#", ""), 16);
|
|
908
|
+
const amt = Math.round(2.55 * percent);
|
|
909
|
+
const R = (num >> 16) + amt;
|
|
910
|
+
const G = (num >> 8 & 255) + amt;
|
|
911
|
+
const B = (num & 255) + amt;
|
|
912
|
+
return "#" + (
|
|
913
|
+
16777216 +
|
|
914
|
+
65536 * (R < 255 ? (R < 1 ? 0 : R) : 255) +
|
|
915
|
+
256 * (G < 255 ? (G < 1 ? 0 : G) : 255) +
|
|
916
|
+
(B < 255 ? (B < 1 ? 0 : B) : 255)
|
|
917
|
+
).toString(16).slice(1);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
generateAsteroids();
|
|
921
|
+
resize();
|
|
922
|
+
window.addEventListener("resize", function() {
|
|
923
|
+
resize();
|
|
924
|
+
if (inDetailView) {
|
|
925
|
+
resizeDetailCanvas();
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
let stars = [];
|
|
930
|
+
|
|
931
|
+
function generateStars() {
|
|
932
|
+
stars = [];
|
|
933
|
+
for (let i = 0; i < 200; i++) {
|
|
934
|
+
stars.push({
|
|
935
|
+
x: Math.random() * width,
|
|
936
|
+
y: Math.random() * height,
|
|
937
|
+
size: 1.5 * Math.random(),
|
|
938
|
+
brightness: 0.3 + 0.7 * Math.random()
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
function drawStars() {
|
|
944
|
+
stars.forEach(function(star) {
|
|
945
|
+
const twinkle = 0.8 + 0.2 * Math.sin(0.01 * time + star.x);
|
|
946
|
+
ctx.beginPath();
|
|
947
|
+
ctx.arc(star.x, star.y, star.size, 0, 2 * Math.PI);
|
|
948
|
+
ctx.fillStyle = "rgba(255, 255, 255, " + (star.brightness * twinkle) + ")";
|
|
949
|
+
ctx.fill();
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function animate() {
|
|
954
|
+
updateFps();
|
|
955
|
+
ctx.fillStyle = "rgba(10, 10, 25, 1)";
|
|
956
|
+
ctx.fillRect(0, 0, width, height);
|
|
957
|
+
drawStars();
|
|
958
|
+
if (showOrbits) {
|
|
959
|
+
planets.forEach(function(planet) { drawOrbit(planet); });
|
|
960
|
+
}
|
|
961
|
+
if (showAsteroids) {
|
|
962
|
+
drawAsteroidBelt();
|
|
963
|
+
}
|
|
964
|
+
drawSun();
|
|
965
|
+
planetPositions = [];
|
|
966
|
+
planets.forEach(function(planet) {
|
|
967
|
+
const pos = getOrbitalPosition(planet, time);
|
|
968
|
+
planetPositions.push({ planet: planet, x: pos.x, y: pos.y, size: planet.size });
|
|
969
|
+
drawPlanet(planet, pos);
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
if (hoveredPlanet) {
|
|
973
|
+
const hp = planetPositions.find(p => p.planet.name === hoveredPlanet.name);
|
|
974
|
+
if (hp) {
|
|
975
|
+
ctx.beginPath();
|
|
976
|
+
ctx.arc(hp.x, hp.y, hp.size + 4, 0, 2 * Math.PI);
|
|
977
|
+
ctx.strokeStyle = "rgba(240, 147, 251, 0.6)";
|
|
978
|
+
ctx.lineWidth = 2;
|
|
979
|
+
ctx.stroke();
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
if (!mainPaused) {
|
|
984
|
+
time += timeDirection;
|
|
985
|
+
if (mainFfRemaining > 0) {
|
|
986
|
+
mainFfRemaining--;
|
|
987
|
+
if (mainFfRemaining <= 0) {
|
|
988
|
+
mainFfRemaining = 0;
|
|
989
|
+
mainPaused = true;
|
|
990
|
+
var pb = document.getElementById('pauseBtn');
|
|
991
|
+
pb.textContent = '▶'; pb.classList.add('paused'); pb.title = 'Play';
|
|
992
|
+
updateMainTimeReadout(true);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
updateMainTimeReadout();
|
|
997
|
+
if (!inDetailView) {
|
|
998
|
+
requestAnimationFrame(animate);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
generateStars();
|
|
1003
|
+
|
|
1004
|
+
document.getElementById("speedSlider").addEventListener("input", function(e) {
|
|
1005
|
+
simulationSpeed = parseFloat(e.target.value);
|
|
1006
|
+
document.getElementById("speedValue").textContent = simulationSpeed.toFixed(1) + "x";
|
|
1007
|
+
document.getElementById("pauseBtn").disabled = simulationSpeed === 0;
|
|
1008
|
+
updateMainTimeReadout(true);
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
// Pause button for main view
|
|
1012
|
+
document.getElementById("pauseBtn").addEventListener("click", function() {
|
|
1013
|
+
mainPaused = !mainPaused;
|
|
1014
|
+
this.textContent = mainPaused ? '▶' : '⏸';
|
|
1015
|
+
this.classList.toggle('paused', mainPaused);
|
|
1016
|
+
this.title = mainPaused ? 'Play' : 'Pause';
|
|
1017
|
+
updateMainTimeReadout(true);
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
// Fast-forward input/button for main view
|
|
1021
|
+
document.getElementById('mainFfDays').addEventListener('input', function() {
|
|
1022
|
+
document.getElementById('mainFfBtn').disabled = !(parseFloat(this.value) > 0);
|
|
1023
|
+
});
|
|
1024
|
+
document.getElementById('mainFfBtn').addEventListener('click', function() {
|
|
1025
|
+
var days = parseFloat(document.getElementById('mainFfDays').value);
|
|
1026
|
+
if (!(days > 0) || simulationSpeed === 0) return;
|
|
1027
|
+
mainFfRemaining = Math.ceil(days / simulationSpeed);
|
|
1028
|
+
mainPaused = false;
|
|
1029
|
+
var pb = document.getElementById('pauseBtn');
|
|
1030
|
+
pb.textContent = '⏸'; pb.classList.remove('paused'); pb.title = 'Pause';
|
|
1031
|
+
updateMainTimeReadout(true);
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
document.getElementById("toggleOrbits").addEventListener("click", function(e) {
|
|
1035
|
+
showOrbits = !showOrbits;
|
|
1036
|
+
e.target.classList.toggle("active");
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
document.getElementById("toggleLabels").addEventListener("click", function(e) {
|
|
1040
|
+
showLabels = !showLabels;
|
|
1041
|
+
e.target.classList.toggle("active");
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
document.getElementById("toggleAsteroids").addEventListener("click", function(e) {
|
|
1045
|
+
showAsteroids = !showAsteroids;
|
|
1046
|
+
e.target.classList.toggle("active");
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
document.getElementById("resetView").addEventListener("click", function() {
|
|
1050
|
+
time = 0;
|
|
1051
|
+
simulationSpeed = 1;
|
|
1052
|
+
timeDirection = 1;
|
|
1053
|
+
mainPaused = false;
|
|
1054
|
+
mainFfRemaining = 0;
|
|
1055
|
+
document.getElementById("speedSlider").value = 1;
|
|
1056
|
+
document.getElementById("speedValue").textContent = "1x";
|
|
1057
|
+
document.getElementById("pauseBtn").disabled = false;
|
|
1058
|
+
const pauseBtn = document.getElementById("pauseBtn");
|
|
1059
|
+
pauseBtn.textContent = '⏸';
|
|
1060
|
+
pauseBtn.classList.remove('paused');
|
|
1061
|
+
pauseBtn.title = 'Pause';
|
|
1062
|
+
document.getElementById('mainFfDays').value = '';
|
|
1063
|
+
document.getElementById('mainFfBtn').disabled = true;
|
|
1064
|
+
updateMainTimeReadout(true);
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
/* Detail card hover logic */
|
|
1068
|
+
const detailCard = document.getElementById('planetDetailCard');
|
|
1069
|
+
|
|
1070
|
+
function getPlanetAtPosition(mx, my) {
|
|
1071
|
+
for (let i = planetPositions.length - 1; i >= 0; i--) {
|
|
1072
|
+
const pp = planetPositions[i];
|
|
1073
|
+
const dx = mx - pp.x;
|
|
1074
|
+
const dy = my - pp.y;
|
|
1075
|
+
const hitRadius = Math.max(pp.size + 6, 12);
|
|
1076
|
+
if (dx * dx + dy * dy <= hitRadius * hitRadius) {
|
|
1077
|
+
return pp;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
return null;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
function showDetailCard(pp, mx, my) {
|
|
1084
|
+
const planet = pp.planet;
|
|
1085
|
+
const data = planetDetailsData.planets[planet.key];
|
|
1086
|
+
if (!data) return;
|
|
1087
|
+
|
|
1088
|
+
detailCard.querySelector('.planet-icon').style.background = planet.color;
|
|
1089
|
+
detailCard.querySelector('.planet-name').textContent = data.name;
|
|
1090
|
+
detailCard.querySelector('[data-field="type"]').textContent = data.type;
|
|
1091
|
+
detailCard.querySelector('[data-field="diameter"]').textContent = typeof window.convertDist === 'function' ? window.convertDist(data.diameter) : data.diameter;
|
|
1092
|
+
detailCard.querySelector('[data-field="moons"]').textContent = data.moons;
|
|
1093
|
+
detailCard.querySelector('[data-field="funFact"]').textContent = data.funFact;
|
|
1094
|
+
|
|
1095
|
+
detailCard.classList.add('mode-funfact');
|
|
1096
|
+
detailCard.classList.remove('mode-question');
|
|
1097
|
+
|
|
1098
|
+
let cardX = pp.x + pp.size + 15;
|
|
1099
|
+
let cardY = pp.y - 60;
|
|
1100
|
+
|
|
1101
|
+
const cardWidth = 260;
|
|
1102
|
+
const cardHeight = 280;
|
|
1103
|
+
const containerRect = container.getBoundingClientRect();
|
|
1104
|
+
|
|
1105
|
+
if (cardX + cardWidth > containerRect.width - 20) {
|
|
1106
|
+
cardX = pp.x - pp.size - cardWidth - 15;
|
|
1107
|
+
}
|
|
1108
|
+
if (cardY < 10) cardY = 10;
|
|
1109
|
+
if (cardY + cardHeight > containerRect.height - 10) {
|
|
1110
|
+
cardY = containerRect.height - cardHeight - 10;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
detailCard.style.left = cardX + 'px';
|
|
1114
|
+
detailCard.style.top = cardY + 'px';
|
|
1115
|
+
detailCard.classList.add('visible');
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
function hideDetailCard() {
|
|
1119
|
+
detailCard.classList.remove('visible');
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
canvas.addEventListener("mousemove", function(e) {
|
|
1123
|
+
if (inDetailView) return;
|
|
1124
|
+
const rect = canvas.getBoundingClientRect();
|
|
1125
|
+
const mx = e.clientX - rect.left;
|
|
1126
|
+
const my = e.clientY - rect.top;
|
|
1127
|
+
|
|
1128
|
+
const pp = getPlanetAtPosition(mx, my);
|
|
1129
|
+
if (pp) {
|
|
1130
|
+
hoveredPlanet = pp.planet;
|
|
1131
|
+
canvas.style.cursor = 'pointer';
|
|
1132
|
+
showDetailCard(pp, mx, my);
|
|
1133
|
+
} else {
|
|
1134
|
+
hoveredPlanet = null;
|
|
1135
|
+
canvas.style.cursor = 'default';
|
|
1136
|
+
hideDetailCard();
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
canvas.addEventListener("mouseleave", function() {
|
|
1141
|
+
if (inDetailView) return;
|
|
1142
|
+
hoveredPlanet = null;
|
|
1143
|
+
canvas.style.cursor = 'default';
|
|
1144
|
+
hideDetailCard();
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
/* ===== DETAIL VIEW ===== */
|
|
1148
|
+
|
|
1149
|
+
const detailViewContainer = document.getElementById('detailViewContainer');
|
|
1150
|
+
const detailCanvas = document.getElementById('detailCanvas');
|
|
1151
|
+
const detailCtx = detailCanvas.getContext('2d');
|
|
1152
|
+
const moonDetailCard = document.getElementById('moonDetailCard');
|
|
1153
|
+
const moonSpeedControl = document.getElementById('moonSpeedControl');
|
|
1154
|
+
const backToSystem = document.getElementById('backToSystem');
|
|
1155
|
+
|
|
1156
|
+
let detailStars = [];
|
|
1157
|
+
let detailWidth = 0;
|
|
1158
|
+
let detailHeight = 0;
|
|
1159
|
+
|
|
1160
|
+
function generateDetailStars(w, h) {
|
|
1161
|
+
detailStars = [];
|
|
1162
|
+
for (let i = 0; i < 150; i++) {
|
|
1163
|
+
detailStars.push({
|
|
1164
|
+
x: Math.random() * w,
|
|
1165
|
+
y: Math.random() * h,
|
|
1166
|
+
size: 1.5 * Math.random(),
|
|
1167
|
+
brightness: 0.3 + 0.7 * Math.random()
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
function resizeDetailCanvas() {
|
|
1173
|
+
const area = detailViewContainer.querySelector('.detail-planet-area');
|
|
1174
|
+
if (!area) return;
|
|
1175
|
+
detailWidth = area.clientWidth;
|
|
1176
|
+
detailHeight = area.clientHeight;
|
|
1177
|
+
detailCanvas.width = detailWidth;
|
|
1178
|
+
detailCanvas.height = detailHeight;
|
|
1179
|
+
generateDetailStars(detailWidth, detailHeight);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
const moonColors = [
|
|
1183
|
+
'#c8c8c8', '#a0a0b0', '#d4c4a8', '#b8a898',
|
|
1184
|
+
'#e0d8c8', '#9898a8', '#c0b8a0', '#a8b0b8',
|
|
1185
|
+
'#d0c0b0', '#b0a8c0', '#c8c0b8', '#a8a0a0'
|
|
1186
|
+
];
|
|
1187
|
+
|
|
1188
|
+
function openDetailView(planetObj) {
|
|
1189
|
+
inDetailView = true;
|
|
1190
|
+
window.detailPlanet = planetObj;
|
|
1191
|
+
hideDetailCard();
|
|
1192
|
+
hoveredPlanet = null;
|
|
1193
|
+
|
|
1194
|
+
const data = planetDetailsData.planets[planetObj.key];
|
|
1195
|
+
if (!data) return;
|
|
1196
|
+
|
|
1197
|
+
document.getElementById('detailPlanetIcon').style.background = planetObj.color;
|
|
1198
|
+
document.getElementById('detailPlanetName').textContent = data.name;
|
|
1199
|
+
document.getElementById('detailType').textContent = data.type;
|
|
1200
|
+
document.getElementById('detailDiameter').textContent = typeof window.convertDist === 'function' ? window.convertDist(data.diameter) : data.diameter;
|
|
1201
|
+
document.getElementById('detailDistance').textContent = (typeof window.convertDist === 'function' && data.distance) ? window.convertDist(data.distance) : (data.distance || '\u2014');
|
|
1202
|
+
document.getElementById('detailOrbitalPeriod').textContent = data.orbitalPeriod || '\u2014';
|
|
1203
|
+
document.getElementById('detailDayLength').textContent = data.dayLength || '\u2014';
|
|
1204
|
+
document.getElementById('detailSurfaceTemp').textContent = (typeof window.convertTemp === 'function' && data.surfaceTemp) ? window.convertTemp(data.surfaceTemp) : (data.surfaceTemp || '\u2014');
|
|
1205
|
+
document.getElementById('detailMoons').textContent = data.moons;
|
|
1206
|
+
|
|
1207
|
+
const questionInput = document.getElementById('planetQuestionInput');
|
|
1208
|
+
const answerBox = document.getElementById('planetAnswerBox');
|
|
1209
|
+
if (questionInput) questionInput.value = '';
|
|
1210
|
+
if (answerBox) answerBox.innerHTML = '';
|
|
1211
|
+
|
|
1212
|
+
detailMoons = [];
|
|
1213
|
+
if (data.moonData) {
|
|
1214
|
+
const moonKeys = Object.keys(data.moonData);
|
|
1215
|
+
moonKeys.forEach(function(key, idx) {
|
|
1216
|
+
const md = data.moonData[key];
|
|
1217
|
+
let periodDays = parseFloat(md.orbitalPeriod);
|
|
1218
|
+
if (md.orbitalPeriod.toLowerCase().includes('hour')) {
|
|
1219
|
+
periodDays = parseFloat(md.orbitalPeriod) / 24;
|
|
1220
|
+
}
|
|
1221
|
+
if (isNaN(periodDays)) periodDays = 5;
|
|
1222
|
+
|
|
1223
|
+
let distanceKm = md.distanceKm || 500000;
|
|
1224
|
+
|
|
1225
|
+
detailMoons.push({
|
|
1226
|
+
key: key,
|
|
1227
|
+
name: md.name,
|
|
1228
|
+
diameter: md.diameter,
|
|
1229
|
+
distanceStr: md.distance,
|
|
1230
|
+
distanceKm: distanceKm,
|
|
1231
|
+
orbitalPeriod: md.orbitalPeriod,
|
|
1232
|
+
funFact: md.funFact,
|
|
1233
|
+
periodDays: periodDays,
|
|
1234
|
+
color: moonColors[idx % moonColors.length],
|
|
1235
|
+
angle: Math.random() * Math.PI * 2,
|
|
1236
|
+
orbitIndex: idx
|
|
1237
|
+
});
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
detailMoons.sort((a, b) => a.distanceKm - b.distanceKm);
|
|
1241
|
+
|
|
1242
|
+
moonSpeedControl.style.display = 'block';
|
|
1243
|
+
} else {
|
|
1244
|
+
moonSpeedControl.style.display = 'none';
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
detailViewContainer.classList.add('active');
|
|
1248
|
+
|
|
1249
|
+
document.querySelector('.controls').style.display = 'none';
|
|
1250
|
+
document.querySelector('.speed-control').style.display = 'none';
|
|
1251
|
+
document.getElementById('infoPanel').style.display = 'none';
|
|
1252
|
+
|
|
1253
|
+
// Reset moon pause state
|
|
1254
|
+
moonPausedState = false;
|
|
1255
|
+
moonFfRemaining = 0;
|
|
1256
|
+
const moonPauseBtn = document.getElementById('moonPauseBtn');
|
|
1257
|
+
if (moonPauseBtn) {
|
|
1258
|
+
moonPauseBtn.textContent = '⏸';
|
|
1259
|
+
moonPauseBtn.classList.remove('paused');
|
|
1260
|
+
moonPauseBtn.title = 'Pause';
|
|
1261
|
+
moonPauseBtn.disabled = false;
|
|
1262
|
+
}
|
|
1263
|
+
var moonFfDaysEl = document.getElementById('moonFfDays');
|
|
1264
|
+
if (moonFfDaysEl) { moonFfDaysEl.value = ''; }
|
|
1265
|
+
var moonFfBtnEl = document.getElementById('moonFfBtn');
|
|
1266
|
+
if (moonFfBtnEl) { moonFfBtnEl.disabled = true; }
|
|
1267
|
+
|
|
1268
|
+
setTimeout(function() {
|
|
1269
|
+
resizeDetailCanvas();
|
|
1270
|
+
detailTime = 0;
|
|
1271
|
+
moonSpeed = 1;
|
|
1272
|
+
const moonSpeedSlider = document.getElementById('moonSpeedSlider');
|
|
1273
|
+
const moonSpeedValue = document.getElementById('moonSpeedValue');
|
|
1274
|
+
if (moonSpeedSlider) moonSpeedSlider.value = 1;
|
|
1275
|
+
if (moonSpeedValue) moonSpeedValue.textContent = '1x';
|
|
1276
|
+
updateMoonTimeReadout(true);
|
|
1277
|
+
animateDetail();
|
|
1278
|
+
}, 50);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
function closeDetailView() {
|
|
1282
|
+
inDetailView = false;
|
|
1283
|
+
window.detailPlanet = null;
|
|
1284
|
+
detailMoons = [];
|
|
1285
|
+
detailMoonPositions = [];
|
|
1286
|
+
hoveredMoon = null;
|
|
1287
|
+
moonPausedState = false;
|
|
1288
|
+
moonDetailCard.classList.remove('visible');
|
|
1289
|
+
|
|
1290
|
+
detailViewContainer.classList.remove('active');
|
|
1291
|
+
|
|
1292
|
+
document.querySelector('.controls').style.display = 'flex';
|
|
1293
|
+
document.querySelector('.speed-control').style.display = 'block';
|
|
1294
|
+
document.getElementById('infoPanel').style.display = 'block';
|
|
1295
|
+
|
|
1296
|
+
if (detailAnimId) {
|
|
1297
|
+
cancelAnimationFrame(detailAnimId);
|
|
1298
|
+
detailAnimId = null;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
animate();
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
function animateDetail() {
|
|
1305
|
+
if (!inDetailView) return;
|
|
1306
|
+
updateFps();
|
|
1307
|
+
|
|
1308
|
+
const w = detailCanvas.width;
|
|
1309
|
+
const h = detailCanvas.height;
|
|
1310
|
+
const cx = w / 2;
|
|
1311
|
+
const cy = h / 2;
|
|
1312
|
+
|
|
1313
|
+
detailCtx.fillStyle = '#0a0a19';
|
|
1314
|
+
detailCtx.fillRect(0, 0, w, h);
|
|
1315
|
+
|
|
1316
|
+
detailStars.forEach(function(star) {
|
|
1317
|
+
const twinkle = 0.8 + 0.2 * Math.sin(0.01 * detailTime + star.x);
|
|
1318
|
+
detailCtx.beginPath();
|
|
1319
|
+
detailCtx.arc(star.x, star.y, star.size, 0, 2 * Math.PI);
|
|
1320
|
+
detailCtx.fillStyle = 'rgba(255,255,255,' + (star.brightness * twinkle) + ')';
|
|
1321
|
+
detailCtx.fill();
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
const planetRadius = Math.min(w, h) * 0.08;
|
|
1325
|
+
|
|
1326
|
+
const moonCount = detailMoons.length;
|
|
1327
|
+
const minOrbitRadius = planetRadius + 30;
|
|
1328
|
+
const maxOrbitRadius = Math.min(w, h) * 0.46;
|
|
1329
|
+
|
|
1330
|
+
let minDist = Infinity, maxDist = 0;
|
|
1331
|
+
detailMoons.forEach(function(moon) {
|
|
1332
|
+
if (moon.distanceKm < minDist) minDist = moon.distanceKm;
|
|
1333
|
+
if (moon.distanceKm > maxDist) maxDist = moon.distanceKm;
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
const logMin = Math.log(minDist || 1);
|
|
1337
|
+
const logMax = Math.log(maxDist || 1);
|
|
1338
|
+
const logRange = logMax - logMin || 1;
|
|
1339
|
+
|
|
1340
|
+
const glowGrad = detailCtx.createRadialGradient(cx, cy, planetRadius * 0.8, cx, cy, planetRadius * 2.5);
|
|
1341
|
+
glowGrad.addColorStop(0, window.detailPlanet.color + '40');
|
|
1342
|
+
glowGrad.addColorStop(1, 'transparent');
|
|
1343
|
+
detailCtx.beginPath();
|
|
1344
|
+
detailCtx.arc(cx, cy, planetRadius * 2.5, 0, 2 * Math.PI);
|
|
1345
|
+
detailCtx.fillStyle = glowGrad;
|
|
1346
|
+
detailCtx.fill();
|
|
1347
|
+
|
|
1348
|
+
const pGrad = detailCtx.createRadialGradient(
|
|
1349
|
+
cx - planetRadius * 0.3, cy - planetRadius * 0.3, 0,
|
|
1350
|
+
cx, cy, planetRadius
|
|
1351
|
+
);
|
|
1352
|
+
pGrad.addColorStop(0, lightenColor(window.detailPlanet.color, 40));
|
|
1353
|
+
pGrad.addColorStop(0.7, window.detailPlanet.color);
|
|
1354
|
+
pGrad.addColorStop(1, lightenColor(window.detailPlanet.color, -20));
|
|
1355
|
+
detailCtx.beginPath();
|
|
1356
|
+
detailCtx.arc(cx, cy, planetRadius, 0, 2 * Math.PI);
|
|
1357
|
+
detailCtx.fillStyle = pGrad;
|
|
1358
|
+
detailCtx.fill();
|
|
1359
|
+
|
|
1360
|
+
if (window.detailPlanet.name === 'Saturn') {
|
|
1361
|
+
detailCtx.save();
|
|
1362
|
+
detailCtx.translate(cx, cy);
|
|
1363
|
+
detailCtx.rotate(0.4);
|
|
1364
|
+
detailCtx.beginPath();
|
|
1365
|
+
detailCtx.ellipse(0, 0, planetRadius * 2.0, planetRadius * 0.45, 0, 0, 2 * Math.PI);
|
|
1366
|
+
detailCtx.strokeStyle = 'rgba(210, 180, 140, 0.6)';
|
|
1367
|
+
detailCtx.lineWidth = 6;
|
|
1368
|
+
detailCtx.stroke();
|
|
1369
|
+
detailCtx.beginPath();
|
|
1370
|
+
detailCtx.ellipse(0, 0, planetRadius * 1.7, planetRadius * 0.38, 0, 0, 2 * Math.PI);
|
|
1371
|
+
detailCtx.strokeStyle = 'rgba(180, 150, 120, 0.4)';
|
|
1372
|
+
detailCtx.lineWidth = 4;
|
|
1373
|
+
detailCtx.stroke();
|
|
1374
|
+
detailCtx.beginPath();
|
|
1375
|
+
detailCtx.ellipse(0, 0, planetRadius * 2.2, planetRadius * 0.5, 0, 0, 2 * Math.PI);
|
|
1376
|
+
detailCtx.strokeStyle = 'rgba(190, 160, 130, 0.3)';
|
|
1377
|
+
detailCtx.lineWidth = 3;
|
|
1378
|
+
detailCtx.stroke();
|
|
1379
|
+
detailCtx.restore();
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
detailMoonPositions = [];
|
|
1383
|
+
|
|
1384
|
+
detailMoons.forEach(function(moon, idx) {
|
|
1385
|
+
let orbitRadius;
|
|
1386
|
+
if (moonCount === 1) {
|
|
1387
|
+
orbitRadius = (minOrbitRadius + maxOrbitRadius) / 2;
|
|
1388
|
+
} else {
|
|
1389
|
+
const logDist = Math.log(moon.distanceKm);
|
|
1390
|
+
const normalizedDist = (logDist - logMin) / logRange;
|
|
1391
|
+
orbitRadius = minOrbitRadius + normalizedDist * (maxOrbitRadius - minOrbitRadius);
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
detailCtx.beginPath();
|
|
1395
|
+
detailCtx.arc(cx, cy, orbitRadius, 0, 2 * Math.PI);
|
|
1396
|
+
detailCtx.strokeStyle = 'rgba(138, 43, 226, 0.25)';
|
|
1397
|
+
detailCtx.lineWidth = 1;
|
|
1398
|
+
detailCtx.stroke();
|
|
1399
|
+
|
|
1400
|
+
if (!moonPausedState) {
|
|
1401
|
+
const angularSpeed = (2 * Math.PI) / (moon.periodDays * 60) * moonSpeed;
|
|
1402
|
+
moon.angle += angularSpeed;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
const mx = cx + orbitRadius * Math.cos(moon.angle);
|
|
1406
|
+
const my = cy + orbitRadius * Math.sin(moon.angle);
|
|
1407
|
+
|
|
1408
|
+
let moonRadius = 4 + (moonCount > 4 ? 2 : 4) * (1 - idx * 0.05);
|
|
1409
|
+
moonRadius = Math.max(3, Math.min(moonRadius, 8));
|
|
1410
|
+
|
|
1411
|
+
detailCtx.beginPath();
|
|
1412
|
+
detailCtx.arc(mx + 1, my + 1, moonRadius, 0, 2 * Math.PI);
|
|
1413
|
+
detailCtx.fillStyle = 'rgba(0,0,0,0.3)';
|
|
1414
|
+
detailCtx.fill();
|
|
1415
|
+
|
|
1416
|
+
const mGrad = detailCtx.createRadialGradient(
|
|
1417
|
+
mx - moonRadius * 0.3, my - moonRadius * 0.3, 0,
|
|
1418
|
+
mx, my, moonRadius
|
|
1419
|
+
);
|
|
1420
|
+
mGrad.addColorStop(0, lightenColor(moon.color, 30));
|
|
1421
|
+
mGrad.addColorStop(1, moon.color);
|
|
1422
|
+
detailCtx.beginPath();
|
|
1423
|
+
detailCtx.arc(mx, my, moonRadius, 0, 2 * Math.PI);
|
|
1424
|
+
detailCtx.fillStyle = mGrad;
|
|
1425
|
+
detailCtx.fill();
|
|
1426
|
+
|
|
1427
|
+
detailCtx.fillStyle = '#d0c0e8';
|
|
1428
|
+
detailCtx.font = '10px Segoe UI, sans-serif';
|
|
1429
|
+
detailCtx.textAlign = 'center';
|
|
1430
|
+
detailCtx.fillText(moon.name.split('(')[0].trim(), mx, my - moonRadius - 6);
|
|
1431
|
+
|
|
1432
|
+
if (hoveredMoon && hoveredMoon.key === moon.key) {
|
|
1433
|
+
detailCtx.beginPath();
|
|
1434
|
+
detailCtx.arc(mx, my, moonRadius + 3, 0, 2 * Math.PI);
|
|
1435
|
+
detailCtx.strokeStyle = 'rgba(240, 147, 251, 0.7)';
|
|
1436
|
+
detailCtx.lineWidth = 2;
|
|
1437
|
+
detailCtx.stroke();
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
detailMoonPositions.push({
|
|
1441
|
+
moon: moon,
|
|
1442
|
+
x: mx,
|
|
1443
|
+
y: my,
|
|
1444
|
+
radius: moonRadius
|
|
1445
|
+
});
|
|
1446
|
+
});
|
|
1447
|
+
|
|
1448
|
+
detailCtx.fillStyle = '#f0e0ff';
|
|
1449
|
+
detailCtx.font = 'bold 16px Segoe UI, sans-serif';
|
|
1450
|
+
detailCtx.textAlign = 'center';
|
|
1451
|
+
detailCtx.shadowColor = 'rgba(240, 147, 251, 0.8)';
|
|
1452
|
+
detailCtx.shadowBlur = 6;
|
|
1453
|
+
detailCtx.fillText(window.detailPlanet.name, cx, cy + planetRadius + 25);
|
|
1454
|
+
detailCtx.shadowColor = 'transparent';
|
|
1455
|
+
detailCtx.shadowBlur = 0;
|
|
1456
|
+
|
|
1457
|
+
if (moonCount === 0) {
|
|
1458
|
+
detailCtx.fillStyle = 'rgba(183, 148, 246, 0.6)';
|
|
1459
|
+
detailCtx.font = '14px Segoe UI, sans-serif';
|
|
1460
|
+
detailCtx.textAlign = 'center';
|
|
1461
|
+
detailCtx.fillText('No known moons', cx, cy + planetRadius + 50);
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
detailTime++;
|
|
1465
|
+
if (!moonPausedState && moonFfRemaining > 0) {
|
|
1466
|
+
moonFfRemaining--;
|
|
1467
|
+
if (moonFfRemaining <= 0) {
|
|
1468
|
+
moonFfRemaining = 0;
|
|
1469
|
+
moonPausedState = true;
|
|
1470
|
+
var mpb = document.getElementById('moonPauseBtn');
|
|
1471
|
+
if (mpb) { mpb.textContent = '▶'; mpb.classList.add('paused'); mpb.title = 'Play'; }
|
|
1472
|
+
updateMoonTimeReadout(true);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
updateMoonTimeReadout();
|
|
1476
|
+
detailAnimId = requestAnimationFrame(animateDetail);
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
function getMoonAtPosition(mx, my) {
|
|
1480
|
+
for (let i = detailMoonPositions.length - 1; i >= 0; i--) {
|
|
1481
|
+
const mp = detailMoonPositions[i];
|
|
1482
|
+
const dx = mx - mp.x;
|
|
1483
|
+
const dy = my - mp.y;
|
|
1484
|
+
const hitRadius = Math.max(mp.radius + 5, 10);
|
|
1485
|
+
if (dx * dx + dy * dy <= hitRadius * hitRadius) {
|
|
1486
|
+
return mp;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
return null;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
function showMoonDetailCard(mp, clientX, clientY) {
|
|
1493
|
+
const moon = mp.moon;
|
|
1494
|
+
const moonShortName = moon.name.split('(')[0].trim();
|
|
1495
|
+
moonDetailCard.querySelector('.moon-name').textContent = moon.name;
|
|
1496
|
+
moonDetailCard.querySelector('[data-field="diameter"]').textContent = typeof window.convertDist === 'function' ? window.convertDist(moon.diameter) : moon.diameter;
|
|
1497
|
+
moonDetailCard.querySelector('[data-field="distance"]').textContent = typeof window.convertDist === 'function' ? window.convertDist(moon.distanceStr) : moon.distanceStr;
|
|
1498
|
+
moonDetailCard.querySelector('[data-field="period"]').textContent = moon.orbitalPeriod;
|
|
1499
|
+
moonDetailCard.querySelector('.moon-fun-fact').textContent = moon.funFact;
|
|
1500
|
+
moonDetailCard.querySelector('.moon-click-hint').textContent = 'Click ' + moonShortName + ' to learn more';
|
|
1501
|
+
|
|
1502
|
+
const areaRect = detailViewContainer.querySelector('.detail-planet-area').getBoundingClientRect();
|
|
1503
|
+
let cardX = clientX - areaRect.left + 15;
|
|
1504
|
+
let cardY = clientY - areaRect.top - 30;
|
|
1505
|
+
|
|
1506
|
+
const cardW = 220;
|
|
1507
|
+
const cardH = 220;
|
|
1508
|
+
if (cardX + cardW > areaRect.width - 10) cardX = clientX - areaRect.left - cardW - 15;
|
|
1509
|
+
if (cardY < 10) cardY = 10;
|
|
1510
|
+
if (cardY + cardH > areaRect.height - 10) cardY = areaRect.height - cardH - 10;
|
|
1511
|
+
|
|
1512
|
+
moonDetailCard.style.left = cardX + 'px';
|
|
1513
|
+
moonDetailCard.style.top = cardY + 'px';
|
|
1514
|
+
moonDetailCard.classList.add('visible');
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
detailCanvas.addEventListener('mousemove', function(e) {
|
|
1518
|
+
const rect = detailCanvas.getBoundingClientRect();
|
|
1519
|
+
const mx = e.clientX - rect.left;
|
|
1520
|
+
const my = e.clientY - rect.top;
|
|
1521
|
+
|
|
1522
|
+
const mp = getMoonAtPosition(mx, my);
|
|
1523
|
+
if (mp) {
|
|
1524
|
+
hoveredMoon = mp.moon;
|
|
1525
|
+
detailCanvas.style.cursor = 'pointer';
|
|
1526
|
+
showMoonDetailCard(mp, e.clientX, e.clientY);
|
|
1527
|
+
} else {
|
|
1528
|
+
hoveredMoon = null;
|
|
1529
|
+
detailCanvas.style.cursor = 'default';
|
|
1530
|
+
moonDetailCard.classList.remove('visible');
|
|
1531
|
+
}
|
|
1532
|
+
});
|
|
1533
|
+
|
|
1534
|
+
detailCanvas.addEventListener('mouseleave', function() {
|
|
1535
|
+
hoveredMoon = null;
|
|
1536
|
+
detailCanvas.style.cursor = 'default';
|
|
1537
|
+
moonDetailCard.classList.remove('visible');
|
|
1538
|
+
});
|
|
1539
|
+
|
|
1540
|
+
detailCanvas.addEventListener('click', function(e) {
|
|
1541
|
+
const rect = detailCanvas.getBoundingClientRect();
|
|
1542
|
+
const mx = e.clientX - rect.left;
|
|
1543
|
+
const my = e.clientY - rect.top;
|
|
1544
|
+
|
|
1545
|
+
const mp = getMoonAtPosition(mx, my);
|
|
1546
|
+
if (mp) {
|
|
1547
|
+
const moonName = mp.moon.name.split('(')[0].trim();
|
|
1548
|
+
const questionInput = document.getElementById('planetQuestionInput');
|
|
1549
|
+
if (questionInput) {
|
|
1550
|
+
questionInput.value = 'tell me about ' + moonName;
|
|
1551
|
+
questionInput.focus();
|
|
1552
|
+
if (typeof window.triggerPlanetQuestion === 'function') {
|
|
1553
|
+
window.triggerPlanetQuestion();
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
// Moon speed slider
|
|
1560
|
+
document.getElementById('moonSpeedSlider').addEventListener('input', function(e) {
|
|
1561
|
+
moonSpeed = parseFloat(e.target.value);
|
|
1562
|
+
document.getElementById('moonSpeedValue').textContent = moonSpeed.toFixed(1) + 'x';
|
|
1563
|
+
document.getElementById('moonPauseBtn').disabled = moonSpeed === 0;
|
|
1564
|
+
updateMoonTimeReadout(true);
|
|
1565
|
+
});
|
|
1566
|
+
|
|
1567
|
+
// Moon pause button
|
|
1568
|
+
document.getElementById('moonPauseBtn').addEventListener('click', function() {
|
|
1569
|
+
moonPausedState = !moonPausedState;
|
|
1570
|
+
this.textContent = moonPausedState ? '▶' : '⏸';
|
|
1571
|
+
this.classList.toggle('paused', moonPausedState);
|
|
1572
|
+
this.title = moonPausedState ? 'Play' : 'Pause';
|
|
1573
|
+
updateMoonTimeReadout(true);
|
|
1574
|
+
});
|
|
1575
|
+
|
|
1576
|
+
// Fast-forward input/button for moon view
|
|
1577
|
+
document.getElementById('moonFfDays').addEventListener('input', function() {
|
|
1578
|
+
document.getElementById('moonFfBtn').disabled = !(parseFloat(this.value) > 0);
|
|
1579
|
+
});
|
|
1580
|
+
document.getElementById('moonFfBtn').addEventListener('click', function() {
|
|
1581
|
+
var days = parseFloat(document.getElementById('moonFfDays').value);
|
|
1582
|
+
if (!(days > 0) || moonSpeed === 0) return;
|
|
1583
|
+
moonFfRemaining = Math.ceil(days * 60 / moonSpeed);
|
|
1584
|
+
moonPausedState = false;
|
|
1585
|
+
var mpb = document.getElementById('moonPauseBtn');
|
|
1586
|
+
if (mpb) { mpb.textContent = '⏸'; mpb.classList.remove('paused'); mpb.title = 'Pause'; }
|
|
1587
|
+
updateMoonTimeReadout(true);
|
|
1588
|
+
});
|
|
1589
|
+
|
|
1590
|
+
backToSystem.addEventListener('click', function() {
|
|
1591
|
+
closeDetailView();
|
|
1592
|
+
});
|
|
1593
|
+
|
|
1594
|
+
canvas.addEventListener("click", function(e) {
|
|
1595
|
+
if (inDetailView) return;
|
|
1596
|
+
const rect = canvas.getBoundingClientRect();
|
|
1597
|
+
const mx = e.clientX - rect.left;
|
|
1598
|
+
const my = e.clientY - rect.top;
|
|
1599
|
+
const pp = getPlanetAtPosition(mx, my);
|
|
1600
|
+
if (pp) {
|
|
1601
|
+
openDetailView(pp.planet);
|
|
1602
|
+
}
|
|
1603
|
+
});
|
|
1604
|
+
|
|
1605
|
+
const viewerPanel = document.getElementById('viewerPanel');
|
|
1606
|
+
if (viewerPanel) {
|
|
1607
|
+
const resizeObserver = new ResizeObserver(function() {
|
|
1608
|
+
if (inDetailView) {
|
|
1609
|
+
resizeDetailCanvas();
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
1612
|
+
resizeObserver.observe(viewerPanel);
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
animate();
|
|
1616
|
+
</script>
|
|
1617
|
+
|
|
1618
|
+
|
|
1619
|
+
<script id="tutorial-links">document.addEventListener('click',function(e){if(e.target.classList.contains('tutorial-link')){e.preventDefault();const message=e.target.getAttribute('data-message');const chatInput=document.getElementById('chatInput');if(chatInput&&message){chatInput.value=message;chatInput.focus()}}});</script>
|
|
1620
|
+
<script id="solar-ai-question">
|
|
1621
|
+
(function() {
|
|
1622
|
+
const questionInput = document.getElementById('solarQuestion');
|
|
1623
|
+
const answerBox = document.getElementById('solarAnswer');
|
|
1624
|
+
|
|
1625
|
+
// Conversation history (max 10 pairs)
|
|
1626
|
+
let conversationHistory = [];
|
|
1627
|
+
const MAX_HISTORY = 10;
|
|
1628
|
+
|
|
1629
|
+
function formatHistory() {
|
|
1630
|
+
if (conversationHistory.length === 0) return '';
|
|
1631
|
+
let historyStr = '\n<CONVERSATION_HISTORY>\n';
|
|
1632
|
+
conversationHistory.forEach((pair, i) => {
|
|
1633
|
+
historyStr += `User: ${pair.question}\nAssistant: ${pair.answer}\n\n`;
|
|
1634
|
+
});
|
|
1635
|
+
historyStr += '</CONVERSATION_HISTORY>\n';
|
|
1636
|
+
return historyStr;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
function formatOrbitalContext() {
|
|
1640
|
+
if (typeof window.getSystemOrbitalState !== 'function') return '';
|
|
1641
|
+
const state = window.getSystemOrbitalState();
|
|
1642
|
+
if (!state) return '';
|
|
1643
|
+
|
|
1644
|
+
let ctx = '\n<SIMULATION_STATE>\n';
|
|
1645
|
+
ctx += 'Simulation speed: ' + state.simulationSpeed.toFixed(1) + 'x (time direction: ' + state.simulationTimeDirection + ')\n';
|
|
1646
|
+
ctx += 'Current orbital positions (0° = right/3 o\'clock, angles increase counter-clockwise):\n';
|
|
1647
|
+
state.planets.forEach(function(p) {
|
|
1648
|
+
ctx += ' ' + p.name + ': ' + p.angleDegrees + '° from reference, relative distance from Sun: ' + p.relativeDistanceFromSun + ' units (orbital period: ' + p.orbitalPeriodYears + ' Earth years)\n';
|
|
1649
|
+
});
|
|
1650
|
+
if (state.closestPair) {
|
|
1651
|
+
ctx += 'Currently closest pair: ' + state.closestPair.pair + ' (distance: ' + state.closestPair.distance + ' units apart)\n';
|
|
1652
|
+
}
|
|
1653
|
+
ctx += 'Note: These are scaled simulation positions. Distances are relative/proportional, not actual astronomical distances. Angles represent current orbital positions in the simulation.\n';
|
|
1654
|
+
ctx += '</SIMULATION_STATE>\n';
|
|
1655
|
+
return ctx;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
async function askQuestion() {
|
|
1659
|
+
const question = questionInput.value.trim();
|
|
1660
|
+
if (!question) return;
|
|
1661
|
+
|
|
1662
|
+
questionInput.disabled = true;
|
|
1663
|
+
answerBox.innerHTML = '<em>Thinking...</em>';
|
|
1664
|
+
|
|
1665
|
+
try {
|
|
1666
|
+
const historyContext = formatHistory();
|
|
1667
|
+
const orbitalContext = formatOrbitalContext();
|
|
1668
|
+
const audienceContext = (typeof window.getAudiencePrompt === 'function') ? '\n' + window.getAudiencePrompt() : '';
|
|
1669
|
+
const prompt = `You're an expert on the solar system. The user is viewing an interactive solar system simulation.\n\n<INSTRUCTION>\nYou can return markdown but avoid using # headings.${audienceContext}\nThe user can see the simulation and may ask questions about current positions of planets. Use the simulation state data to answer positional/relational questions. Make it clear when you're referring to the simulation vs. real life.\n</INSTRUCTION>${orbitalContext}${historyContext}\n<QUESTION>\n${question}`;
|
|
1670
|
+
const result = await synthos.generate.completion({ prompt });
|
|
1671
|
+
answerBox.innerHTML = marked.parse(result.answer);
|
|
1672
|
+
|
|
1673
|
+
// Add to history
|
|
1674
|
+
conversationHistory.push({ question: question, answer: result.answer });
|
|
1675
|
+
if (conversationHistory.length > MAX_HISTORY) {
|
|
1676
|
+
conversationHistory.shift();
|
|
1677
|
+
}
|
|
1678
|
+
} catch (err) {
|
|
1679
|
+
answerBox.innerHTML = '<span style="color: #ff6b6b;">Error: Could not get answer.</span>';
|
|
1680
|
+
} finally {
|
|
1681
|
+
questionInput.disabled = false;
|
|
1682
|
+
questionInput.value = '';
|
|
1683
|
+
questionInput.focus();
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
questionInput.addEventListener('keypress', function(e) {
|
|
1688
|
+
if (e.key === 'Enter') askQuestion();
|
|
1689
|
+
});
|
|
1690
|
+
})();
|
|
1691
|
+
</script><script id="planet-detail-ai-question">
|
|
1692
|
+
(function() {
|
|
1693
|
+
const questionInput = document.getElementById('planetQuestionInput');
|
|
1694
|
+
const answerBox = document.getElementById('planetAnswerBox');
|
|
1695
|
+
|
|
1696
|
+
if (!questionInput || !answerBox) return;
|
|
1697
|
+
|
|
1698
|
+
// Conversation history per planet (max 10 pairs)
|
|
1699
|
+
let conversationHistory = [];
|
|
1700
|
+
let currentPlanetKey = null;
|
|
1701
|
+
const MAX_HISTORY = 10;
|
|
1702
|
+
|
|
1703
|
+
function formatHistory() {
|
|
1704
|
+
if (conversationHistory.length === 0) return '';
|
|
1705
|
+
let historyStr = '\n<CONVERSATION_HISTORY>\n';
|
|
1706
|
+
conversationHistory.forEach((pair, i) => {
|
|
1707
|
+
historyStr += `User: ${pair.question}\nAssistant: ${pair.answer}\n\n`;
|
|
1708
|
+
});
|
|
1709
|
+
historyStr += '</CONVERSATION_HISTORY>\n';
|
|
1710
|
+
return historyStr;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
function formatDetailOrbitalContext() {
|
|
1714
|
+
if (typeof window.getDetailOrbitalState !== 'function') return '';
|
|
1715
|
+
const state = window.getDetailOrbitalState();
|
|
1716
|
+
if (!state) return '';
|
|
1717
|
+
|
|
1718
|
+
let ctx = '\n<SIMULATION_STATE>\n';
|
|
1719
|
+
ctx += 'Viewing: ' + state.planet + ' detail view\n';
|
|
1720
|
+
ctx += 'Moon animation speed: ' + state.moonAnimationSpeed.toFixed(1) + 'x\n';
|
|
1721
|
+
if (state.moons.length > 0) {
|
|
1722
|
+
ctx += 'Current moon orbital positions (0° = right/3 o\'clock, angles increase counter-clockwise):\n';
|
|
1723
|
+
state.moons.forEach(function(m) {
|
|
1724
|
+
ctx += ' ' + m.name + ': ' + m.angleDegrees + '° in orbit, distance: ' + m.distanceKm.toLocaleString() + ' km from ' + state.planet + ', orbital period: ' + m.orbitalPeriodDays.toFixed(2) + ' days\n';
|
|
1725
|
+
});
|
|
1726
|
+
} else {
|
|
1727
|
+
ctx += 'This planet has no known moons displayed.\n';
|
|
1728
|
+
}
|
|
1729
|
+
ctx += 'Note: Moon positions are from the animated simulation. Angles represent current orbital positions.\n';
|
|
1730
|
+
ctx += '</SIMULATION_STATE>\n';
|
|
1731
|
+
return ctx;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
async function askPlanetQuestion() {
|
|
1735
|
+
const question = questionInput.value.trim();
|
|
1736
|
+
if (!question) return;
|
|
1737
|
+
if (!window.detailPlanet) {
|
|
1738
|
+
answerBox.innerHTML = '<span style="color: #ff6b6b;">No planet selected.</span>';
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
// Reset history if planet changed
|
|
1743
|
+
if (currentPlanetKey !== window.detailPlanet.key) {
|
|
1744
|
+
conversationHistory = [];
|
|
1745
|
+
currentPlanetKey = window.detailPlanet.key;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
questionInput.disabled = true;
|
|
1749
|
+
answerBox.innerHTML = '<em>Thinking...</em>';
|
|
1750
|
+
|
|
1751
|
+
try {
|
|
1752
|
+
const planetName = window.detailPlanet.name;
|
|
1753
|
+
const historyContext = formatHistory();
|
|
1754
|
+
const orbitalContext = formatDetailOrbitalContext();
|
|
1755
|
+
const audienceContext = (typeof window.getAudiencePrompt === 'function') ? '\n' + window.getAudiencePrompt() : '';
|
|
1756
|
+
const prompt = `You're an expert on the solar system. The user is viewing ${planetName} in a detail view showing its moons orbiting around it.\n\n<INSTRUCTION>\nYou can return markdown but avoid using # headings.${audienceContext}\nThe user can see the moon simulation and may ask questions about current positions of moons. Use the simulation state data to answer positional/relational questions. Make it clear when you're referring to the simulation vs. real life.\n</INSTRUCTION>${orbitalContext}${historyContext}\n<QUESTION>\n${question}`;
|
|
1757
|
+
const result = await synthos.generate.completion({ prompt });
|
|
1758
|
+
answerBox.innerHTML = marked.parse(result.answer);
|
|
1759
|
+
|
|
1760
|
+
// Add to history
|
|
1761
|
+
conversationHistory.push({ question: question, answer: result.answer });
|
|
1762
|
+
if (conversationHistory.length > MAX_HISTORY) {
|
|
1763
|
+
conversationHistory.shift();
|
|
1764
|
+
}
|
|
1765
|
+
} catch (err) {
|
|
1766
|
+
answerBox.innerHTML = '<span style="color: #ff6b6b;">Error: Could not get answer.</span>';
|
|
1767
|
+
} finally {
|
|
1768
|
+
questionInput.disabled = false;
|
|
1769
|
+
questionInput.value = '';
|
|
1770
|
+
questionInput.focus();
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
// Expose function globally so moon click can trigger it
|
|
1775
|
+
window.triggerPlanetQuestion = askPlanetQuestion;
|
|
1776
|
+
|
|
1777
|
+
questionInput.addEventListener('keypress', function(e) {
|
|
1778
|
+
if (e.key === 'Enter') askPlanetQuestion();
|
|
1779
|
+
});
|
|
1780
|
+
})();
|
|
1781
|
+
</script><script id="settings-manager">
|
|
1782
|
+
(function() {
|
|
1783
|
+
const SETTINGS_TABLE = 'settings';
|
|
1784
|
+
const SETTINGS_ID = 'user-prefs';
|
|
1785
|
+
|
|
1786
|
+
// Default settings
|
|
1787
|
+
window.explorerSettings = {
|
|
1788
|
+
audience: 'hobbyist',
|
|
1789
|
+
tempUnit: 'C',
|
|
1790
|
+
distUnit: 'km'
|
|
1791
|
+
};
|
|
1792
|
+
|
|
1793
|
+
// Load settings from server
|
|
1794
|
+
async function loadSettings() {
|
|
1795
|
+
try {
|
|
1796
|
+
const saved = await synthos.data.get(SETTINGS_TABLE, SETTINGS_ID);
|
|
1797
|
+
if (saved) {
|
|
1798
|
+
if (saved.audience) window.explorerSettings.audience = saved.audience;
|
|
1799
|
+
if (saved.tempUnit) window.explorerSettings.tempUnit = saved.tempUnit;
|
|
1800
|
+
if (saved.distUnit) window.explorerSettings.distUnit = saved.distUnit;
|
|
1801
|
+
}
|
|
1802
|
+
} catch (e) {
|
|
1803
|
+
// No saved settings yet, use defaults
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
function applySettingsToUI() {
|
|
1808
|
+
const s = window.explorerSettings;
|
|
1809
|
+
const audienceSelect = document.getElementById('settingsAudience');
|
|
1810
|
+
if (audienceSelect) audienceSelect.value = s.audience;
|
|
1811
|
+
|
|
1812
|
+
const tempRadio = document.querySelector('input[name="tempUnit"][value="' + s.tempUnit + '"]');
|
|
1813
|
+
if (tempRadio) tempRadio.checked = true;
|
|
1814
|
+
|
|
1815
|
+
const distRadio = document.querySelector('input[name="distUnit"][value="' + s.distUnit + '"]');
|
|
1816
|
+
if (distRadio) distRadio.checked = true;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
async function saveSettings() {
|
|
1820
|
+
const audience = document.getElementById('settingsAudience').value;
|
|
1821
|
+
const tempUnit = document.querySelector('input[name="tempUnit"]:checked').value;
|
|
1822
|
+
const distUnit = document.querySelector('input[name="distUnit"]:checked').value;
|
|
1823
|
+
|
|
1824
|
+
window.explorerSettings = { audience, tempUnit, distUnit };
|
|
1825
|
+
|
|
1826
|
+
try {
|
|
1827
|
+
await synthos.data.save(SETTINGS_TABLE, {
|
|
1828
|
+
id: SETTINGS_ID,
|
|
1829
|
+
audience: audience,
|
|
1830
|
+
tempUnit: tempUnit,
|
|
1831
|
+
distUnit: distUnit
|
|
1832
|
+
});
|
|
1833
|
+
} catch (e) {
|
|
1834
|
+
console.error('Failed to save settings:', e);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
// Modal open/close
|
|
1839
|
+
const overlay = document.getElementById('settingsOverlay');
|
|
1840
|
+
const openBtn = document.getElementById('openSettings');
|
|
1841
|
+
const cancelBtn = document.getElementById('settingsCancel');
|
|
1842
|
+
const saveBtn = document.getElementById('settingsSave');
|
|
1843
|
+
|
|
1844
|
+
openBtn.addEventListener('click', async function() {
|
|
1845
|
+
// Always reload from server when opening to ensure fresh data
|
|
1846
|
+
await loadSettings();
|
|
1847
|
+
applySettingsToUI();
|
|
1848
|
+
overlay.classList.add('active');
|
|
1849
|
+
});
|
|
1850
|
+
|
|
1851
|
+
cancelBtn.addEventListener('click', function() {
|
|
1852
|
+
overlay.classList.remove('active');
|
|
1853
|
+
applySettingsToUI(); // revert UI to saved
|
|
1854
|
+
});
|
|
1855
|
+
|
|
1856
|
+
overlay.addEventListener('click', function(e) {
|
|
1857
|
+
if (e.target === overlay) {
|
|
1858
|
+
overlay.classList.remove('active');
|
|
1859
|
+
applySettingsToUI();
|
|
1860
|
+
}
|
|
1861
|
+
});
|
|
1862
|
+
|
|
1863
|
+
saveBtn.addEventListener('click', async function() {
|
|
1864
|
+
await saveSettings();
|
|
1865
|
+
overlay.classList.remove('active');
|
|
1866
|
+
});
|
|
1867
|
+
|
|
1868
|
+
// Helper to build audience instruction for prompts
|
|
1869
|
+
window.getAudiencePrompt = function() {
|
|
1870
|
+
const s = window.explorerSettings;
|
|
1871
|
+
const audienceMap = {
|
|
1872
|
+
'ages-5-7': 'a young child aged 5-7. Use very simple words, short sentences, fun comparisons, and excitement. Avoid technical jargon entirely.',
|
|
1873
|
+
'ages-8-10': 'a child aged 8-10. Use simple but informative language, fun facts, and relatable comparisons. Keep explanations clear and engaging.',
|
|
1874
|
+
'ages-11-13': 'a middle school student aged 11-13. Use age-appropriate scientific vocabulary, explain concepts clearly, and include interesting details.',
|
|
1875
|
+
'ages-14-17': 'a high school student aged 14-17. Use proper scientific terminology with brief explanations, include quantitative details, and connect to broader concepts.',
|
|
1876
|
+
'ages-18-plus': 'a general adult audience. Use clear scientific language, provide good detail, and assume basic science literacy.',
|
|
1877
|
+
'casual': 'a casual learner who is curious but not deeply knowledgeable. Keep things accessible, interesting, and avoid overwhelming detail.',
|
|
1878
|
+
'hobbyist': 'a space enthusiast or hobbyist. Include interesting technical details, comparisons, and deeper insights while remaining engaging.',
|
|
1879
|
+
'student-undergrad': 'an undergraduate science student. Use proper scientific terminology, include quantitative data, and reference relevant physical processes.',
|
|
1880
|
+
'student-grad': 'a graduate-level science student. Provide detailed technical information, reference current research where relevant, and discuss underlying physics.',
|
|
1881
|
+
'educator': 'an educator or teacher. Provide accurate, well-structured information that could be used for teaching. Include key concepts and common misconceptions.',
|
|
1882
|
+
'academic': 'an academic researcher. Provide detailed, precise scientific information with quantitative data. Reference current understanding and open questions in the field.',
|
|
1883
|
+
'professional': 'a professional astronomer. Use full technical precision, include relevant observational data, current research findings, and discuss uncertainties.'
|
|
1884
|
+
};
|
|
1885
|
+
const audienceDesc = audienceMap[s.audience] || audienceMap['hobbyist'];
|
|
1886
|
+
|
|
1887
|
+
let unitInstructions = '';
|
|
1888
|
+
if (s.tempUnit === 'F') {
|
|
1889
|
+
unitInstructions += ' Use Fahrenheit (°F) for all temperatures.';
|
|
1890
|
+
} else {
|
|
1891
|
+
unitInstructions += ' Use Celsius (°C) for all temperatures.';
|
|
1892
|
+
}
|
|
1893
|
+
if (s.distUnit === 'miles') {
|
|
1894
|
+
unitInstructions += ' Use miles for distances instead of kilometers.';
|
|
1895
|
+
} else {
|
|
1896
|
+
unitInstructions += ' Use kilometers for distances.';
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
return 'Your audience is ' + audienceDesc + unitInstructions;
|
|
1900
|
+
};
|
|
1901
|
+
|
|
1902
|
+
// Load on init (fire and forget, but also apply to UI when done)
|
|
1903
|
+
loadSettings().then(function() {
|
|
1904
|
+
applySettingsToUI();
|
|
1905
|
+
});
|
|
1906
|
+
})();
|
|
1907
|
+
</script><script id="unit-converters">
|
|
1908
|
+
(function() {
|
|
1909
|
+
// Convert a temperature string from °C to °F
|
|
1910
|
+
// Handles: "465°C", "-180°C to 430°C", "-89°C to 57°C"
|
|
1911
|
+
function convertTempString(str, toUnit) {
|
|
1912
|
+
if (!str || toUnit === 'C') return str;
|
|
1913
|
+
// Replace all °C values with °F
|
|
1914
|
+
return str.replace(/(-?[\d,.]+)\s*°C/g, function(match, num) {
|
|
1915
|
+
const celsius = parseFloat(num.replace(/,/g, ''));
|
|
1916
|
+
const fahrenheit = Math.round(celsius * 9 / 5 + 32);
|
|
1917
|
+
return fahrenheit.toLocaleString() + '°F';
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
// Convert a distance/diameter string from km to miles
|
|
1922
|
+
// Handles: "4,879 km", "57.9 million km", "1.43 billion km", "384,400 km"
|
|
1923
|
+
function convertDistString(str, toUnit) {
|
|
1924
|
+
if (!str || toUnit === 'km') return str;
|
|
1925
|
+
const KM_TO_MI = 0.621371;
|
|
1926
|
+
// Handle "X million km" or "X billion km"
|
|
1927
|
+
const bigMatch = str.match(/^([\d,.]+)\s*(million|billion)\s*km$/i);
|
|
1928
|
+
if (bigMatch) {
|
|
1929
|
+
const val = parseFloat(bigMatch[1].replace(/,/g, ''));
|
|
1930
|
+
const converted = (val * KM_TO_MI);
|
|
1931
|
+
// Round to 1-2 decimal places
|
|
1932
|
+
const rounded = converted < 10 ? converted.toFixed(2) : converted < 100 ? converted.toFixed(1) : Math.round(converted).toLocaleString();
|
|
1933
|
+
return rounded + ' ' + bigMatch[2].toLowerCase() + ' mi';
|
|
1934
|
+
}
|
|
1935
|
+
// Handle plain "X km" or "X,XXX km"
|
|
1936
|
+
const plainMatch = str.match(/^([\d,.]+)\s*km$/i);
|
|
1937
|
+
if (plainMatch) {
|
|
1938
|
+
const val = parseFloat(plainMatch[1].replace(/,/g, ''));
|
|
1939
|
+
const converted = Math.round(val * KM_TO_MI);
|
|
1940
|
+
return converted.toLocaleString() + ' mi';
|
|
1941
|
+
}
|
|
1942
|
+
return str;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
// Apply conversions based on current settings
|
|
1946
|
+
window.convertTemp = function(str) {
|
|
1947
|
+
if (!window.explorerSettings) return str;
|
|
1948
|
+
return convertTempString(str, window.explorerSettings.tempUnit);
|
|
1949
|
+
};
|
|
1950
|
+
|
|
1951
|
+
window.convertDist = function(str) {
|
|
1952
|
+
if (!window.explorerSettings) return str;
|
|
1953
|
+
return convertDistString(str, window.explorerSettings.distUnit);
|
|
1954
|
+
};
|
|
1955
|
+
})();
|
|
1956
|
+
</script><script id="page-helpers" src="/api/page-helpers.js?v=2" data-locked="true"></script><script id="page-script" src="/api/page-script.js?v=2" data-locked="true"></script></body></html>
|