808 lines
30 KiB
PHP
808 lines
30 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
ini_set('display_errors', '1');
|
||
error_reporting(E_ALL);
|
||
session_start();
|
||
|
||
$loggedIn = isset($_SESSION['user_id']);
|
||
|
||
?>
|
||
<?php require __DIR__ . '/header.php'; ?>
|
||
|
||
<style>
|
||
.at-layout {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 1.5rem;
|
||
align-items: start;
|
||
}
|
||
@media (max-width: 720px) {
|
||
.at-layout { grid-template-columns: 1fr; }
|
||
}
|
||
|
||
/* Parameter-Grid */
|
||
.at-param-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 0.75rem 1rem;
|
||
}
|
||
@media (max-width: 480px) {
|
||
.at-param-grid { grid-template-columns: 1fr; }
|
||
}
|
||
.at-param {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.3rem;
|
||
}
|
||
.at-param label {
|
||
font-size: 0.78rem;
|
||
color: var(--text-dim);
|
||
letter-spacing: 0.04em;
|
||
}
|
||
.at-param input[type=range] {
|
||
width: 100%;
|
||
accent-color: var(--gold);
|
||
cursor: pointer;
|
||
}
|
||
.at-param .at-num {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
.at-param .at-num input[type=number] {
|
||
flex: 1;
|
||
background: var(--bg);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
color: var(--text);
|
||
padding: 0.35rem 0.6rem;
|
||
font-size: 0.92rem;
|
||
min-width: 0;
|
||
}
|
||
.at-param .at-num input[type=number]:focus {
|
||
outline: none;
|
||
border-color: var(--gold-dim);
|
||
}
|
||
.at-param .at-unit {
|
||
font-size: 0.78rem;
|
||
color: var(--text-dim);
|
||
white-space: nowrap;
|
||
}
|
||
.at-hint {
|
||
font-size: 0.75rem;
|
||
color: var(--text-dim);
|
||
line-height: 1.45;
|
||
}
|
||
|
||
/* Albedo-Modus Toggle */
|
||
.at-mode-toggle {
|
||
display: flex;
|
||
gap: 0;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
overflow: hidden;
|
||
width: fit-content;
|
||
}
|
||
.at-mode-toggle input[type=radio] { display: none; }
|
||
.at-mode-toggle label {
|
||
padding: 0.3rem 0.85rem;
|
||
font-size: 0.78rem;
|
||
color: var(--text-dim);
|
||
cursor: pointer;
|
||
transition: background 0.15s, color 0.15s;
|
||
letter-spacing: 0.03em;
|
||
border: none;
|
||
}
|
||
.at-mode-toggle input[type=radio]:checked + label {
|
||
background: var(--gold-dim);
|
||
color: var(--bg);
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* Abgeleitete Bond-Albedo Anzeige */
|
||
.at-derived {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
background: rgba(201,168,76,0.07);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
padding: 0.35rem 0.7rem;
|
||
font-size: 0.85rem;
|
||
}
|
||
.at-derived-label { color: var(--text-dim); }
|
||
.at-derived-val { color: var(--gold); font-weight: 600; }
|
||
|
||
/* Albedo-Vorschau */
|
||
.at-albedo-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
}
|
||
.at-albedo-swatch {
|
||
width: 2rem;
|
||
height: 2rem;
|
||
border-radius: 50%;
|
||
border: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
transition: background 0.2s;
|
||
}
|
||
.at-albedo-caption { font-size: 0.75rem; color: var(--text-dim); }
|
||
|
||
/* Preset-Buttons */
|
||
.at-presets {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.4rem;
|
||
}
|
||
|
||
/* Ergebnisse */
|
||
.at-results { display: flex; flex-direction: column; gap: 1rem; }
|
||
.at-result-row {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 0.6rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
.at-result-label { font-size: 0.78rem; color: var(--text-dim); min-width: 5rem; }
|
||
.at-result-sym {
|
||
font-family: 'Georgia', serif;
|
||
font-style: italic;
|
||
color: var(--gold-pale);
|
||
font-size: 1rem;
|
||
min-width: 3rem;
|
||
}
|
||
.at-result-val {
|
||
font-size: 1.45rem;
|
||
font-weight: 600;
|
||
color: var(--gold);
|
||
letter-spacing: 0.01em;
|
||
}
|
||
.at-result-c { font-size: 0.9rem; color: var(--text-dim); }
|
||
|
||
/* Durchmesser */
|
||
.at-diam-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
.at-diam-val {
|
||
font-size: 1.6rem;
|
||
font-weight: 700;
|
||
color: var(--cyan);
|
||
}
|
||
.at-diam-unit { font-size: 0.9rem; color: var(--text-dim); }
|
||
.at-diam-note { font-size: 0.75rem; color: var(--text-dim); }
|
||
|
||
/* Temperaturprofil Canvas */
|
||
.at-theta-wrap {
|
||
position: relative;
|
||
width: 100%;
|
||
}
|
||
#at-theta-canvas {
|
||
display: block;
|
||
width: 100%;
|
||
height: 170px;
|
||
border-radius: var(--radius);
|
||
}
|
||
|
||
/* Größenschätzung hidden state */
|
||
.at-size-section { display: none; }
|
||
.at-size-section.is-visible { display: block; }
|
||
|
||
/* Trennlinie */
|
||
.at-divider {
|
||
border: none;
|
||
border-top: 1px solid var(--border);
|
||
margin: 0.8rem 0;
|
||
}
|
||
|
||
/* Tooltip */
|
||
.at-tip {
|
||
position: relative;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 1.1em;
|
||
height: 1.1em;
|
||
border-radius: 50%;
|
||
border: 1px solid var(--gold-dim);
|
||
color: var(--gold-dim);
|
||
font-size: 0.68rem;
|
||
font-style: normal;
|
||
cursor: help;
|
||
vertical-align: middle;
|
||
margin-left: 0.3em;
|
||
flex-shrink: 0;
|
||
user-select: none;
|
||
line-height: 1;
|
||
}
|
||
.at-tip::after {
|
||
content: attr(data-tip);
|
||
position: absolute;
|
||
bottom: calc(100% + 8px);
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
background: #111827;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
color: var(--text);
|
||
font-size: 0.78rem;
|
||
line-height: 1.5;
|
||
padding: 0.5rem 0.75rem;
|
||
width: 220px;
|
||
white-space: normal;
|
||
pointer-events: none;
|
||
opacity: 0;
|
||
transition: opacity 0.15s;
|
||
z-index: 100;
|
||
box-shadow: 0 4px 16px rgba(0,0,0,0.5);
|
||
}
|
||
.at-tip:hover::after,
|
||
.at-tip:focus::after {
|
||
opacity: 1;
|
||
}
|
||
</style>
|
||
|
||
<div class="at-layout">
|
||
|
||
<!-- Linke Spalte: Eingaben -->
|
||
<div class="card">
|
||
<h2>Parameter</h2>
|
||
|
||
<div class="at-param-grid">
|
||
|
||
<div class="at-param">
|
||
<label for="at-r">Sonnenabstand <em>r</em> <span class="at-tip" tabindex="0" data-tip="Wie weit ist der Asteroid von der Sonne entfernt? Je weiter weg, desto weniger Wärme kommt an. 1 AE = Abstand Erde–Sonne (≈ 150 Mio. km).">ⓘ</span></label>
|
||
<div class="at-num">
|
||
<input type="number" id="at-r" min="0.05" max="50" step="0.01" value="1.00">
|
||
<span class="at-unit">AE</span>
|
||
</div>
|
||
<input type="range" id="at-r-slider" min="0.05" max="10" step="0.01" value="1.00">
|
||
<p class="at-hint">Abstand zur Sonne. Einstrahlung skaliert mit 1/r².</p>
|
||
</div>
|
||
|
||
<div class="at-param">
|
||
<label for="at-eps">Emissivität <em>ε</em> <span class="at-tip" tabindex="0" data-tip="Wie gut gibt die Oberfläche Wärme als Strahlung ab. Gestein strahlt fast perfekt (≈ 0,90). Der Maximalwert 1 wäre ein idealer Wärmestrahler.">ⓘ</span></label>
|
||
<div class="at-num">
|
||
<input type="number" id="at-eps" min="0.01" max="1" step="0.01" value="0.90">
|
||
<span class="at-unit">0–1</span>
|
||
</div>
|
||
<input type="range" id="at-eps-slider" min="0.01" max="1" step="0.01" value="0.90">
|
||
<p class="at-hint">Wärmeabstrahlung. Typisch 0,90–0,95.</p>
|
||
</div>
|
||
|
||
<div class="at-param" style="grid-column:1/-1">
|
||
<label for="at-eta">Beaming-Faktor <em>η</em> <span class="at-tip" tabindex="0" data-tip="Korrektur für Oberflächenrauigkeit und Wärmespeicherung. Eine glatte, schnell rotierende Kugel hat η = 1. Rauere oder langsamere Asteroiden weichen davon ab (typisch 0,7–1,4).">ⓘ</span></label>
|
||
<div class="at-num">
|
||
<input type="number" id="at-eta" min="0.1" max="3" step="0.01" value="1.00">
|
||
<span class="at-unit">> 0</span>
|
||
</div>
|
||
<input type="range" id="at-eta-slider" min="0.1" max="3" step="0.01" value="1.00">
|
||
<p class="at-hint">Rauigkeits-/Anisotropie-Korrektur (NEATM). Typisch 0,7–1,4.</p>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<hr class="at-divider">
|
||
|
||
<!-- Albedo-Modus -->
|
||
<div style="display:flex;align-items:center;gap:0.75rem;margin-bottom:0.65rem;flex-wrap:wrap;">
|
||
<span class="at-hint" style="min-height:0;">Albedo-Modus:</span>
|
||
<div class="at-mode-toggle">
|
||
<input type="radio" name="at-mode" id="at-mode-bond" value="bond" checked>
|
||
<label for="at-mode-bond">Bond-Albedo</label>
|
||
<input type="radio" name="at-mode" id="at-mode-geo" value="geo">
|
||
<label for="at-mode-geo">Geometrische Albedo</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Bond-Modus -->
|
||
<div id="at-bond-section">
|
||
<div class="at-param">
|
||
<label for="at-a">Bond-Albedo <em>A</em> <span class="at-tip" tabindex="0" data-tip="Wie viel des einfallenden Sonnenlichts wird insgesamt zurückgeworfen? 0 = pechschwarz, 1 = perfekter Spiegel. Kohle-artige Asteroiden: ≈ 0,03; heller Gesteinsasteroid: ≈ 0,25.">ⓘ</span></label>
|
||
<div class="at-num">
|
||
<input type="number" id="at-a" min="0" max="0.999" step="0.001" value="0.050">
|
||
<span class="at-unit">0–1</span>
|
||
</div>
|
||
<input type="range" id="at-a-slider" min="0" max="0.999" step="0.001" value="0.050">
|
||
<p class="at-hint">Reflektierter Anteil der gesamten Strahlungsenergie. Dunkel ≈ 0,02–0,10.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Geo-Modus -->
|
||
<div id="at-geo-section" style="display:none;">
|
||
<div class="at-param-grid">
|
||
<div class="at-param">
|
||
<label for="at-p">Geometrische Albedo <em>p</em> <span class="at-tip" tabindex="0" data-tip="Wie hell wirkt die Oberfläche, wenn die Sonne genau hinter dem Beobachter steht? Dieser Wert steht in Asteroidenkatalogen (MPC/JPL). Dunkel ≈ 0,04–0,10, hell ≈ 0,20–0,45.">ⓘ</span></label>
|
||
<div class="at-num">
|
||
<input type="number" id="at-p" min="0.001" max="0.999" step="0.001" value="0.100">
|
||
<span class="at-unit">0–1</span>
|
||
</div>
|
||
<input type="range" id="at-p-slider" min="0.001" max="0.999" step="0.001" value="0.100">
|
||
<p class="at-hint">Katalogwert (MPC/JPL). C-Typ ≈ 0,04–0,10.</p>
|
||
</div>
|
||
<div class="at-param">
|
||
<label for="at-G">Phasenkoeffizient <em>G</em> <span class="at-tip" tabindex="0" data-tip="Beschreibt, wie stark der Asteroid aufleuchtet, wenn die Sonne fast direkt hinter dem Beobachter steht (Opposition). Standardwert 0,15 gilt für die meisten Asteroiden.">ⓘ</span></label>
|
||
<div class="at-num">
|
||
<input type="number" id="at-G" min="0" max="0.9" step="0.01" value="0.15">
|
||
<span class="at-unit">IAU</span>
|
||
</div>
|
||
<input type="range" id="at-G-slider" min="0" max="0.9" step="0.01" value="0.15">
|
||
<p class="at-hint">IAU H-G Standard. Typisch 0,15; dunkel ≈ 0,02.</p>
|
||
</div>
|
||
</div>
|
||
<div style="margin-top:0.6rem;">
|
||
<div class="at-derived">
|
||
<span class="at-derived-label">→ Bond-Albedo <em>A</em> =</span>
|
||
<span class="at-derived-val" id="at-derived-A">–</span>
|
||
<span class="at-derived-label" style="margin-left:0.25rem;font-size:0.7rem;">(= p · q, q = 0,290 + 0,684·G)</span>
|
||
</div>
|
||
</div>
|
||
<hr class="at-divider">
|
||
<div class="at-param">
|
||
<label for="at-H">Absolute Helligkeit <em>H</em> <span style="color:var(--text-dim);font-style:normal;">(optional, für Größe)</span> <span class="at-tip" tabindex="0" data-tip="Die Helligkeit des Asteroiden aus einer genormten Entfernung (1 AE zur Sonne und zum Beobachter). Steht in Katalogen. Je kleiner die Zahl, desto heller — und desto größer der Asteroid.">ⓘ</span></label>
|
||
<div class="at-num">
|
||
<input type="number" id="at-H" min="-5" max="35" step="0.1" value="" placeholder="z. B. 20,5">
|
||
<span class="at-unit">mag</span>
|
||
</div>
|
||
<p class="at-hint">H-Magnitude aus MPC/JPL → berechnet Durchmesser D.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<hr class="at-divider">
|
||
|
||
<div class="at-albedo-wrap" style="margin-bottom:0.6rem;">
|
||
<div class="at-albedo-swatch" id="at-albedo-swatch"></div>
|
||
<span class="at-albedo-caption">Albedo-Vorschau (Helligkeit der Oberfläche)</span>
|
||
</div>
|
||
|
||
<hr class="at-divider">
|
||
|
||
<p class="at-hint" style="margin-bottom:0.4rem;">Bekannte Asteroiden</p>
|
||
<div class="at-presets">
|
||
<button class="btn btn-sm btn-secondary" data-preset="c">C-Typ</button>
|
||
<button class="btn btn-sm btn-secondary" data-preset="s">S-Typ</button>
|
||
<button class="btn btn-sm btn-secondary" data-preset="vesta">Vesta</button>
|
||
<button class="btn btn-sm btn-secondary" data-preset="eros">Eros</button>
|
||
<button class="btn btn-sm btn-secondary" data-preset="bennu">Bennu</button>
|
||
<button class="btn btn-sm btn-secondary" data-preset="ryugu">Ryugu</button>
|
||
<button class="btn btn-sm btn-secondary" data-preset="neufang">(2183) Neufang</button>
|
||
<button class="btn btn-sm btn-danger" id="at-reset">Zurücksetzen</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Rechte Spalte -->
|
||
<div style="display:flex;flex-direction:column;gap:1.25rem;">
|
||
|
||
<!-- Temperaturen -->
|
||
<div class="card">
|
||
<h2>Temperaturen</h2>
|
||
<div class="at-results">
|
||
<div>
|
||
<div class="at-result-row">
|
||
<span class="at-result-label">Gleichgewicht</span>
|
||
<span class="at-result-sym">T<sub>eq</sub></span>
|
||
<span class="at-result-val" id="at-teq-k">–</span>
|
||
<span class="at-result-c" id="at-teq-c"></span>
|
||
</div>
|
||
<p class="at-hint" style="margin-top:0.2rem;">Globaler Mittelwert (gesamte Oberfläche, schnelle Rotation).</p>
|
||
</div>
|
||
<hr class="at-divider">
|
||
<div>
|
||
<div class="at-result-row">
|
||
<span class="at-result-label">Subsolar</span>
|
||
<span class="at-result-sym">T<sub>ss</sub></span>
|
||
<span class="at-result-val" id="at-tss-k">–</span>
|
||
<span class="at-result-c" id="at-tss-c"></span>
|
||
</div>
|
||
<p class="at-hint" style="margin-top:0.2rem;">Heißester Punkt direkt unter der Sonne (Zenitwinkel θ = 0°).</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Temperaturprofil -->
|
||
<div class="card">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;gap:0.5rem;margin-bottom:0.5rem;">
|
||
<h2 style="margin:0;">Temperaturprofil Tagseite</h2>
|
||
<div class="at-mode-toggle">
|
||
<input type="radio" name="at-unit" id="at-unit-k" value="K" checked>
|
||
<label for="at-unit-k">K</label>
|
||
<input type="radio" name="at-unit" id="at-unit-c" value="C">
|
||
<label for="at-unit-c">°C</label>
|
||
</div>
|
||
</div>
|
||
<div class="at-theta-wrap">
|
||
<canvas id="at-theta-canvas"></canvas>
|
||
</div>
|
||
<p class="at-hint" style="margin-top:0.5rem;">
|
||
T(θ) = T<sub>ss</sub> · cos(θ)<sup>¼</sup> — Temperatur als Funktion des Sonnenzenitwinkels.
|
||
Gestrichelt: T<sub>eq</sub>.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Größenschätzung (nur geo-Modus + H-Eingabe) -->
|
||
<div class="card at-size-section" id="at-size-card">
|
||
<h2>Größenabschätzung</h2>
|
||
<div class="at-diam-row">
|
||
<span class="at-diam-val" id="at-diam-val">–</span>
|
||
<span class="at-diam-unit">km</span>
|
||
</div>
|
||
<p class="at-hint" style="margin-top:0.4rem;" id="at-diam-note"></p>
|
||
<p class="at-diam-note" style="margin-top:0.3rem;">
|
||
D = (1329 km / √p) · 10<sup>−H/5</sup>
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Formeln -->
|
||
<div class="card">
|
||
<h2>Formeln</h2>
|
||
<table class="data-table">
|
||
<tr><td>T<sub>eq</sub></td><td>⁴√[ (1−A)·S / (4·ε·σ·η) ]</td></tr>
|
||
<tr><td>T<sub>ss</sub></td><td>⁴√[ (1−A)·S / (ε·σ·η) ]</td></tr>
|
||
<tr><td>T(θ)</td><td>T<sub>ss</sub> · cos(θ)<sup>¼</sup></td></tr>
|
||
<tr><td>S</td><td>S₀ / r² (S₀ = 1361 W/m²)</td></tr>
|
||
<tr><td>A</td><td>p · q (q = 0,290 + 0,684·G)</td></tr>
|
||
<tr><td>D</td><td>(1329 km / √p) · 10<sup>−H/5</sup></td></tr>
|
||
<tr><td>σ</td><td>5,670 × 10⁻⁸ W m⁻² K⁻⁴</td></tr>
|
||
</table>
|
||
<p class="hint" style="margin-top:0.75rem;">
|
||
Modell: NEATM · Albedo: IAU H-G-System (Bowell et al. 1989)
|
||
</p>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(function () {
|
||
const SIGMA = 5.670374419e-8;
|
||
const S0 = 1361.0;
|
||
|
||
// Phasenintegral (Bowell et al. 1989, IAU H-G)
|
||
function phaseIntegral(G) { return 0.290 + 0.684 * G; }
|
||
function bondAlbedo(p, G) { return p * phaseIntegral(G); }
|
||
function asteroidDiam(H, p) { return (1329.0 / Math.sqrt(p)) * Math.pow(10, -H / 5.0); }
|
||
|
||
// Presets: geometrische Albedo p + G (Bond A wird daraus abgeleitet)
|
||
// Quellen: Dawn, NEAR-Shoemaker, OSIRIS-REx, Hayabusa2, JPL/MPC
|
||
const PRESETS = {
|
||
// r p G eps eta
|
||
default: { r:1.00, p:0.100, G:0.15, eps:0.90, eta:1.00 },
|
||
c: { r:2.50, p:0.057, G:0.15, eps:0.90, eta:1.00 }, // C-Typ typisch
|
||
s: { r:2.00, p:0.202, G:0.24, eps:0.90, eta:1.00 }, // S-Typ typisch
|
||
vesta: { r:2.36, p:0.423, G:0.32, eps:0.90, eta:1.00 }, // Dawn-Mission
|
||
eros: { r:1.46, p:0.250, G:0.22, eps:0.90, eta:1.00 }, // NEAR-Shoemaker
|
||
bennu: { r:1.13, p:0.044, G:0.02, eps:0.90, eta:1.00 }, // OSIRIS-REx
|
||
ryugu: { r:1.19, p:0.045, G:0.09, eps:0.90, eta:1.00 }, // Hayabusa2
|
||
neufang: { r:3.01, p:0.115, G:0.15, eps:0.90, eta:1.00 }, // Schätzwert
|
||
};
|
||
|
||
// Eingaben
|
||
const numR = document.getElementById('at-r');
|
||
const numA = document.getElementById('at-a');
|
||
const numP = document.getElementById('at-p');
|
||
const numG = document.getElementById('at-G');
|
||
const numH = document.getElementById('at-H');
|
||
const numEps = document.getElementById('at-eps');
|
||
const numEta = document.getElementById('at-eta');
|
||
const sldR = document.getElementById('at-r-slider');
|
||
const sldA = document.getElementById('at-a-slider');
|
||
const sldP = document.getElementById('at-p-slider');
|
||
const sldG = document.getElementById('at-G-slider');
|
||
const sldEps = document.getElementById('at-eps-slider');
|
||
const sldEta = document.getElementById('at-eta-slider');
|
||
|
||
const modeRadios = document.querySelectorAll('input[name="at-mode"]');
|
||
const bondSection = document.getElementById('at-bond-section');
|
||
const geoSection = document.getElementById('at-geo-section');
|
||
const derivedA = document.getElementById('at-derived-A');
|
||
|
||
// Ausgaben
|
||
const outTeqK = document.getElementById('at-teq-k');
|
||
const outTeqC = document.getElementById('at-teq-c');
|
||
const outTssK = document.getElementById('at-tss-k');
|
||
const outTssC = document.getElementById('at-tss-c');
|
||
const swatch = document.getElementById('at-albedo-swatch');
|
||
const sizeCard = document.getElementById('at-size-card');
|
||
const diamVal = document.getElementById('at-diam-val');
|
||
const diamNote = document.getElementById('at-diam-note');
|
||
|
||
function fmt(val, d) { return val.toFixed(d).replace('.', ','); }
|
||
function fmtSI(val) {
|
||
if (val >= 1e6) return fmt(val / 1e6, 2) + ' ×10⁶';
|
||
if (val >= 1000) return fmt(val, 0);
|
||
return fmt(val, 1);
|
||
}
|
||
|
||
function getMode() {
|
||
for (const r of modeRadios) { if (r.checked) return r.value; }
|
||
return 'bond';
|
||
}
|
||
|
||
function getBondAlbedo() {
|
||
if (getMode() === 'geo') {
|
||
const p = parseFloat(numP.value);
|
||
const G = parseFloat(numG.value);
|
||
if (!isFinite(p) || !isFinite(G)) return NaN;
|
||
return bondAlbedo(p, G);
|
||
}
|
||
return parseFloat(numA.value);
|
||
}
|
||
|
||
function calculate() {
|
||
const r = parseFloat(numR.value);
|
||
const A = getBondAlbedo();
|
||
const eps = parseFloat(numEps.value);
|
||
const eta = parseFloat(numEta.value);
|
||
const mode = getMode();
|
||
|
||
// Bond-Albedo Anzeige im Geo-Modus
|
||
if (mode === 'geo') {
|
||
derivedA.textContent = isFinite(A) ? fmt(A, 4) : '–';
|
||
}
|
||
|
||
// Albedo-Swatch (immer mit A)
|
||
if (isFinite(A)) {
|
||
const v = Math.round(Math.min(1, Math.max(0, A)) * 255);
|
||
swatch.style.background = `rgb(${v},${v},${v})`;
|
||
}
|
||
|
||
if (!isFinite(r) || r <= 0 || !isFinite(A) || A < 0 || A >= 1 ||
|
||
!isFinite(eps) || eps <= 0 || !isFinite(eta) || eta <= 0) {
|
||
outTeqK.textContent = '–'; outTeqC.textContent = '';
|
||
outTssK.textContent = '–'; outTssC.textContent = '';
|
||
drawThetaProfile(null, null);
|
||
updateSize(mode, NaN, NaN);
|
||
return;
|
||
}
|
||
|
||
const S = S0 / (r * r);
|
||
const Teq = Math.pow((1 - A) * S / (4.0 * eps * SIGMA * eta), 0.25);
|
||
const Tss = Math.pow((1 - A) * S / ( eps * SIGMA * eta), 0.25);
|
||
|
||
outTeqK.textContent = fmt(Teq, 2) + ' K';
|
||
outTeqC.textContent = '(' + fmt(Teq - 273.15, 2) + ' °C)';
|
||
outTssK.textContent = fmt(Tss, 2) + ' K';
|
||
outTssC.textContent = '(' + fmt(Tss - 273.15, 2) + ' °C)';
|
||
|
||
drawThetaProfile(Tss, Teq);
|
||
|
||
const p = mode === 'geo' ? parseFloat(numP.value) : NaN;
|
||
updateSize(mode, p, NaN);
|
||
}
|
||
|
||
function updateSize(mode, p, _unused) {
|
||
if (mode !== 'geo') { sizeCard.classList.remove('is-visible'); return; }
|
||
const H = parseFloat(numH.value);
|
||
if (!isFinite(H) || !isFinite(p) || p <= 0) {
|
||
sizeCard.classList.remove('is-visible');
|
||
return;
|
||
}
|
||
const D = asteroidDiam(H, p);
|
||
sizeCard.classList.add('is-visible');
|
||
diamVal.textContent = fmtSI(D) + ' km';
|
||
diamNote.textContent = 'H = ' + fmt(H, 1) + ' mag, p = ' + fmt(p, 3);
|
||
}
|
||
|
||
function getTempUnit() {
|
||
return document.getElementById('at-unit-c').checked ? 'C' : 'K';
|
||
}
|
||
function toUnit(K) { return getTempUnit() === 'C' ? K - 273.15 : K; }
|
||
function unitLabel() { return getTempUnit() === 'C' ? '°C' : 'K'; }
|
||
|
||
document.querySelectorAll('input[name="at-unit"]').forEach(r =>
|
||
r.addEventListener('change', () => {
|
||
const Tss = parseFloat(outTssK.textContent);
|
||
const Teq = parseFloat(outTeqK.textContent);
|
||
if (isFinite(Tss) && isFinite(Teq)) drawThetaProfile(Tss, Teq);
|
||
})
|
||
);
|
||
|
||
// T(θ) Profil Canvas
|
||
function drawThetaProfile(Tss, Teq) {
|
||
const canvas = document.getElementById('at-theta-canvas');
|
||
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
||
const cssW = canvas.offsetWidth || 400;
|
||
const cssH = 170;
|
||
canvas.style.height = cssH + 'px';
|
||
canvas.width = Math.round(cssW * dpr);
|
||
canvas.height = Math.round(cssH * dpr);
|
||
const ctx = canvas.getContext('2d');
|
||
ctx.scale(dpr, dpr);
|
||
ctx.clearRect(0, 0, cssW, cssH);
|
||
|
||
const PAD = { top: 20, right: 14, bottom: 34, left: 52 };
|
||
const cW = cssW - PAD.left - PAD.right;
|
||
const cH = cssH - PAD.top - PAD.bottom;
|
||
|
||
// Hintergrund
|
||
ctx.fillStyle = '#06080f';
|
||
ctx.fillRect(0, 0, cssW, cssH);
|
||
|
||
if (!Tss || !Teq) {
|
||
ctx.fillStyle = '#8899bb';
|
||
ctx.font = '12px sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'middle';
|
||
ctx.fillText('Bitte Parameter eingeben', cssW / 2, cssH / 2);
|
||
return;
|
||
}
|
||
|
||
const TssU = toUnit(Tss);
|
||
const TeqU = toUnit(Teq);
|
||
const unit = unitLabel();
|
||
// Y-Achse: bei Celsius kann Minimum negativ sein → Bereich anpassen
|
||
const Tmin_u = getTempUnit() === 'C' ? Math.min(0, toUnit(0)) : 0;
|
||
const Tmax_u = TssU * (getTempUnit() === 'C' ? 1.08 : 1.08);
|
||
const Trange = Tmax_u - Tmin_u || 1;
|
||
const xOf = t => PAD.left + (t / 90) * cW;
|
||
const yOf = T => PAD.top + cH - ((T - Tmin_u) / Trange) * cH;
|
||
|
||
// Grid
|
||
ctx.strokeStyle = 'rgba(136,153,187,0.12)';
|
||
ctx.lineWidth = 1;
|
||
for (const theta of [0, 30, 60, 90]) {
|
||
ctx.beginPath(); ctx.moveTo(xOf(theta), PAD.top); ctx.lineTo(xOf(theta), PAD.top + cH); ctx.stroke();
|
||
}
|
||
const nY = 4;
|
||
for (let i = 0; i <= nY; i++) {
|
||
const T = Tmin_u + (i / nY) * Trange;
|
||
ctx.beginPath(); ctx.moveTo(PAD.left, yOf(T)); ctx.lineTo(PAD.left + cW, yOf(T)); ctx.stroke();
|
||
}
|
||
|
||
// T_eq gestrichelt (cyan)
|
||
ctx.strokeStyle = '#4fc3d8';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.setLineDash([5, 4]);
|
||
ctx.beginPath(); ctx.moveTo(PAD.left, yOf(TeqU)); ctx.lineTo(PAD.left + cW, yOf(TeqU)); ctx.stroke();
|
||
ctx.setLineDash([]);
|
||
|
||
// T(θ) Kurve (gold, Füllung darunter)
|
||
const tCurve = px => toUnit(Tss * Math.pow(Math.cos((px / cW) * 90 * Math.PI / 180), 0.25));
|
||
ctx.beginPath();
|
||
for (let px = 0; px <= cW; px++) {
|
||
px === 0 ? ctx.moveTo(xOf(px / cW * 90), yOf(tCurve(px))) : ctx.lineTo(xOf(px / cW * 90), yOf(tCurve(px)));
|
||
}
|
||
ctx.lineTo(PAD.left + cW, PAD.top + cH);
|
||
ctx.lineTo(PAD.left, PAD.top + cH);
|
||
ctx.closePath();
|
||
ctx.fillStyle = 'rgba(201,168,76,0.08)';
|
||
ctx.fill();
|
||
|
||
ctx.strokeStyle = '#c9a84c';
|
||
ctx.lineWidth = 2;
|
||
ctx.beginPath();
|
||
for (let px = 0; px <= cW; px++) {
|
||
px === 0 ? ctx.moveTo(xOf(px / cW * 90), yOf(tCurve(px))) : ctx.lineTo(xOf(px / cW * 90), yOf(tCurve(px)));
|
||
}
|
||
ctx.stroke();
|
||
|
||
// Achsen
|
||
ctx.strokeStyle = 'rgba(136,153,187,0.35)';
|
||
ctx.lineWidth = 1;
|
||
ctx.beginPath();
|
||
ctx.moveTo(PAD.left, PAD.top);
|
||
ctx.lineTo(PAD.left, PAD.top + cH);
|
||
ctx.lineTo(PAD.left + cW, PAD.top + cH);
|
||
ctx.stroke();
|
||
|
||
// X-Beschriftung
|
||
ctx.fillStyle = '#8899bb';
|
||
ctx.font = '10px sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'top';
|
||
for (const theta of [0, 30, 60, 90]) {
|
||
ctx.fillText(theta + '°', xOf(theta), PAD.top + cH + 5);
|
||
}
|
||
ctx.fillText('Zenitwinkel θ', PAD.left + cW / 2, PAD.top + cH + 19);
|
||
|
||
// Y-Beschriftung
|
||
ctx.textAlign = 'right';
|
||
ctx.textBaseline = 'middle';
|
||
for (let i = 0; i <= nY; i++) {
|
||
const T = Tmin_u + (i / nY) * Trange;
|
||
ctx.fillText(Math.round(T) + ' ' + unit, PAD.left - 5, yOf(T));
|
||
}
|
||
|
||
// Labels an den Kurven
|
||
ctx.font = 'bold 10px sans-serif';
|
||
ctx.textAlign = 'left';
|
||
ctx.textBaseline = 'bottom';
|
||
ctx.fillStyle = '#c9a84c';
|
||
ctx.fillText('T_ss = ' + TssU.toFixed(0) + ' ' + unit, xOf(0) + 4, yOf(TssU) - 2);
|
||
|
||
ctx.fillStyle = '#4fc3d8';
|
||
ctx.font = '10px sans-serif';
|
||
const labelX = Math.min(xOf(45), PAD.left + cW - 90);
|
||
ctx.fillText('T_eq = ' + TeqU.toFixed(0) + ' ' + unit, labelX, yOf(TeqU) - 3);
|
||
}
|
||
|
||
// Slider ↔ Zahl synchronisieren
|
||
function sync(num, sld) {
|
||
num.addEventListener('input', () => { sld.value = num.value; calculate(); });
|
||
sld.addEventListener('input', () => { num.value = sld.value; calculate(); });
|
||
}
|
||
sync(numR, sldR);
|
||
sync(numA, sldA);
|
||
sync(numP, sldP);
|
||
sync(numG, sldG);
|
||
sync(numEps, sldEps);
|
||
sync(numEta, sldEta);
|
||
numH.addEventListener('input', calculate);
|
||
|
||
// Modus-Toggle
|
||
modeRadios.forEach(r => r.addEventListener('change', () => {
|
||
const geo = getMode() === 'geo';
|
||
bondSection.style.display = geo ? 'none' : '';
|
||
geoSection.style.display = geo ? '' : 'none';
|
||
if (geo) {
|
||
// Bond→Geo: A in p umrechnen (näherungsweise mit aktuellem G)
|
||
const A = parseFloat(numA.value);
|
||
const G = parseFloat(numG.value) || 0.15;
|
||
const q = phaseIntegral(G);
|
||
if (isFinite(A) && q > 0) {
|
||
const p = Math.min(0.999, Math.max(0.001, A / q));
|
||
numP.value = sldP.value = p.toFixed(3);
|
||
}
|
||
} else {
|
||
// Geo→Bond: berechnetes A übernehmen
|
||
const A = getBondAlbedo();
|
||
if (isFinite(A)) numA.value = sldA.value = A.toFixed(3);
|
||
}
|
||
calculate();
|
||
}));
|
||
|
||
// Presets
|
||
function applyPreset(pr) {
|
||
numR.value = sldR.value = pr.r;
|
||
numEps.value = sldEps.value = pr.eps;
|
||
numEta.value = sldEta.value = pr.eta;
|
||
|
||
// Preset immer im Geo-Modus anwenden
|
||
document.getElementById('at-mode-geo').checked = true;
|
||
bondSection.style.display = 'none';
|
||
geoSection.style.display = '';
|
||
|
||
numP.value = sldP.value = pr.p;
|
||
numG.value = sldG.value = pr.G;
|
||
const A = bondAlbedo(pr.p, pr.G);
|
||
numA.value = sldA.value = A.toFixed(3);
|
||
|
||
calculate();
|
||
}
|
||
|
||
document.querySelectorAll('[data-preset]').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
const k = btn.dataset.preset;
|
||
if (PRESETS[k]) applyPreset(PRESETS[k]);
|
||
});
|
||
});
|
||
document.getElementById('at-reset').addEventListener('click', () => {
|
||
document.getElementById('at-mode-bond').checked = true;
|
||
bondSection.style.display = '';
|
||
geoSection.style.display = 'none';
|
||
numR.value = sldR.value = 1.00;
|
||
numA.value = sldA.value = 0.050;
|
||
numP.value = sldP.value = 0.100;
|
||
numG.value = sldG.value = 0.15;
|
||
numH.value = '';
|
||
numEps.value = sldEps.value = 0.90;
|
||
numEta.value = sldEta.value = 1.00;
|
||
calculate();
|
||
});
|
||
|
||
window.addEventListener('resize', () => {
|
||
const Tss = parseFloat(outTssK.textContent);
|
||
const Teq = parseFloat(outTeqK.textContent);
|
||
if (isFinite(Tss) && isFinite(Teq)) drawThetaProfile(Tss, Teq);
|
||
});
|
||
|
||
calculate();
|
||
})();
|
||
</script>
|
||
|
||
<?php require __DIR__ . '/footer.php'; ?>
|