Asteroid temp verbessert

This commit is contained in:
2026-04-23 10:36:31 +02:00
parent 79fc30c212
commit 47eb2c492a
+495 -107
View File
@@ -20,10 +20,11 @@ $headerIntroSub = 'Gleichgewichts- und Subsolartemperatur eines Asteroiden berec
gap: 1.5rem;
align-items: start;
}
@media (max-width: 700px) {
@media (max-width: 720px) {
.at-layout { grid-template-columns: 1fr; }
}
/* Parameter-Grid */
.at-param-grid {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -32,7 +33,6 @@ $headerIntroSub = 'Gleichgewichts- und Subsolartemperatur eines Asteroiden berec
@media (max-width: 480px) {
.at-param-grid { grid-template-columns: 1fr; }
}
.at-param {
display: flex;
flex-direction: column;
@@ -76,26 +76,79 @@ $headerIntroSub = 'Gleichgewichts- und Subsolartemperatur eines Asteroiden berec
font-size: 0.75rem;
color: var(--text-dim);
line-height: 1.45;
min-height: 2.2em;
}
/* 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-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: 4.5rem;
}
.at-result-label { font-size: 0.78rem; color: var(--text-dim); min-width: 5rem; }
.at-result-sym {
font-family: 'Georgia', serif;
font-style: italic;
@@ -109,44 +162,44 @@ $headerIntroSub = 'Gleichgewichts- und Subsolartemperatur eines Asteroiden berec
color: var(--gold);
letter-spacing: 0.01em;
}
.at-result-c {
font-size: 0.9rem;
color: var(--text-dim);
}
.at-result-c { font-size: 0.9rem; color: var(--text-dim); }
/* Albedo-Vorschau */
.at-albedo-wrap {
/* Durchmesser */
.at-diam-row {
display: flex;
align-items: center;
gap: 0.75rem;
margin-top: 0.5rem;
flex-wrap: wrap;
}
.at-albedo-swatch {
width: 2.2rem;
height: 2.2rem;
border-radius: 50%;
border: 1px solid var(--border);
flex-shrink: 0;
transition: background 0.2s;
.at-diam-val {
font-size: 1.6rem;
font-weight: 700;
color: var(--cyan);
}
.at-albedo-label {
font-size: 0.78rem;
color: var(--text-dim);
.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);
}
/* Preset-Buttons */
.at-presets {
display: flex;
flex-wrap: wrap;
gap: 0.45rem;
margin-top: 0.25rem;
}
/* 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.75rem 0;
margin: 0.8rem 0;
}
</style>
@@ -165,17 +218,7 @@ $headerIntroSub = 'Gleichgewichts- und Subsolartemperatur eines Asteroiden berec
<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 in Astronomischen Einheiten. Einstrahlung skaliert mit 1/r².</p>
</div>
<div class="at-param">
<label for="at-a">Bond-Albedo <em>A</em></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">Anteil der reflektierten Gesamtenergie. Dunkel ≈ 0,02–0,10.</p>
<p class="at-hint">Abstand zur Sonne. Einstrahlung skaliert mit 1/r².</p>
</div>
<div class="at-param">
@@ -185,31 +228,97 @@ $headerIntroSub = 'Gleichgewichts- und Subsolartemperatur eines Asteroiden berec
<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">Wie gut die Oberfläche Wärme abstrahlt. Typisch 0,90–0,95.</p>
<p class="at-hint">Wärmeabstrahlung. Typisch 0,90–0,95.</p>
</div>
<div class="at-param">
<div class="at-param" style="grid-column:1/-1">
<label for="at-eta">Beaming-Faktor <em>η</em></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">&gt; 0</span>
<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. Typisch 0,7–1,4; Standard 1,0.</p>
<p class="at-hint">Rauigkeits-/Anisotropie-Korrektur (NEATM). Typisch 0,7–1,4.</p>
</div>
</div>
<hr class="at-divider">
<div class="at-albedo-wrap">
<div class="at-albedo-swatch" id="at-albedo-swatch"></div>
<span class="at-albedo-label">Albedo-Vorschau (Helligkeit der Oberfläche)</span>
<!-- 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></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></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></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, &nbsp;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></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">
<p class="at-albedo-label" style="margin-bottom:0.4rem;">Bekannte Asteroiden</p>
<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>
@@ -222,13 +331,13 @@ $headerIntroSub = 'Gleichgewichts- und Subsolartemperatur eines Asteroiden berec
</div>
</div>
<!-- Rechte Spalte: Ergebnisse + Erklärung -->
<!-- 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>
@@ -236,11 +345,9 @@ $headerIntroSub = 'Gleichgewichts- und Subsolartemperatur eines Asteroiden berec
<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.25rem;">Isotherme Gleichgewichtstemperatur — globaler Mittelwert über die gesamte Oberfläche.</p>
<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>
@@ -248,23 +355,58 @@ $headerIntroSub = 'Gleichgewichts- und Subsolartemperatur eines Asteroiden berec
<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.25rem;">Subsolartemperatur — heißester Punkt direkt unter der Sonne auf der Tagseite.</p>
<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>S</td><td>S₀ / r² &nbsp;mit S₀ = 1361 W/m²</td></tr>
<tr><td>T(θ)</td><td>T<sub>ss</sub> · cos(θ)<sup>¼</sup></td></tr>
<tr><td>S</td><td>S₀ / r² &nbsp;&nbsp;(S₀ = 1361 W/m²)</td></tr>
<tr><td>A</td><td>p · q &nbsp;&nbsp;(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 (Near-Earth Asteroid Thermal Model).
T<sub>ss</sub> = 4<sup>¼</sup> · T<sub>eq</sub> ≈ 1,414 · T<sub>eq</sub>.
Modell: NEATM · Albedo: IAU H-G-System (Bowell et al. 1989)
</p>
</div>
@@ -276,48 +418,101 @@ $headerIntroSub = 'Gleichgewichts- und Subsolartemperatur eines Asteroiden berec
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 = {
default: { r: 1.00, A: 0.050, eps: 0.90, eta: 1.00 },
c: { r: 2.50, A: 0.040, eps: 0.90, eta: 1.00 },
s: { r: 2.00, A: 0.150, eps: 0.90, eta: 1.00 },
vesta: { r: 2.36, A: 0.200, eps: 0.90, eta: 1.00 },
eros: { r: 1.46, A: 0.120, eps: 0.90, eta: 1.00 },
bennu: { r: 1.13, A: 0.020, eps: 0.90, eta: 1.00 },
ryugu: { r: 1.19, A: 0.020, eps: 0.90, eta: 1.00 },
neufang: { r: 3.01, A: 0.045, eps: 0.90, eta: 1.00 },
// 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 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 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');
function fmt(val, decimals = 2) {
return val.toFixed(decimals).replace('.', ',');
// 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 = parseFloat(numA.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 = '';
outTeqK.textContent = '–'; outTeqC.textContent = '';
outTssK.textContent = '–'; outTssC.textContent = '';
drawThetaProfile(null, null);
updateSize(mode, NaN, NaN);
return;
}
@@ -330,43 +525,236 @@ $headerIntroSub = 'Gleichgewichts- und Subsolartemperatur eines Asteroiden berec
outTssK.textContent = fmt(Tss, 2) + ' K';
outTssC.textContent = '(' + fmt(Tss - 273.15, 2) + ' °C)';
const v = Math.round(Math.min(1, Math.max(0, A)) * 255);
swatch.style.background = `rgb(${v},${v},${v})`;
drawThetaProfile(Tss, Teq);
const p = mode === 'geo' ? parseFloat(numP.value) : NaN;
updateSize(mode, p, NaN);
}
function sync(numInput, slider) {
numInput.addEventListener('input', () => {
slider.value = numInput.value;
calculate();
});
slider.addEventListener('input', () => {
numInput.value = slider.value;
calculate();
});
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);
function applyPreset(p) {
numR.value = sldR.value = p.r;
numA.value = sldA.value = p.A;
numEps.value = sldEps.value = p.eps;
numEta.value = sldEta.value = p.eta;
calculate();
}
document.querySelectorAll('[data-preset]').forEach(btn => {
btn.addEventListener('click', () => {
const key = btn.dataset.preset;
if (PRESETS[key]) applyPreset(PRESETS[key]);
const k = btn.dataset.preset;
if (PRESETS[k]) applyPreset(PRESETS[k]);
});
});
document.getElementById('at-reset').addEventListener('click', () => {
applyPreset(PRESETS.default);
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();