1102 lines
60 KiB
PHP
1102 lines
60 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
ini_set('display_errors', '1');
|
|
error_reporting(E_ALL);
|
|
session_start();
|
|
|
|
$loggedIn = isset($_SESSION['user_id']);
|
|
$pageTitle = 'Bedeckungsphaenomene - AstroTools';
|
|
$bodyClass = 'occ-page';
|
|
?>
|
|
<?php require __DIR__ . '/header.php'; ?>
|
|
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;1,300;1,400&family=Space+Mono:wght@400;700&family=Orbitron:wght@400;600;700;900&display=swap" rel="stylesheet">
|
|
|
|
<div class="app-layout">
|
|
<aside class="controls-panel">
|
|
<!-- Mode -->
|
|
<div class="ctrl-block">
|
|
<div class="ctrl-label">Beobachtungsmodus</div>
|
|
<div class="sel-wrap">
|
|
<select class="sel" id="modeSelect">
|
|
<option value="single" selected>Einzelplanet</option>
|
|
<option value="multi">Sternsystem</option>
|
|
<option value="binary">Doppelstern</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SINGLE -->
|
|
<div id="ctrl-single">
|
|
<div class="ctrl-block">
|
|
<div class="ctrl-label">Exoplaneten-Bibliothek</div>
|
|
<div class="sel-wrap">
|
|
<select class="sel" id="exoSelect">
|
|
<option value="custom">Custom</option>
|
|
<optgroup label="Heisse Jupiter">
|
|
<option value="hd209458b" selected>HD 209458 b - Osiris</option>
|
|
<option value="wasp12b">WASP-12 b</option>
|
|
<option value="hatp32b">HAT-P-32 b</option>
|
|
<option value="kepler7b">Kepler-7 b</option>
|
|
</optgroup>
|
|
<optgroup label="Gasriesen">
|
|
<option value="wasp96b">WASP-96 b (JWST)</option>
|
|
</optgroup>
|
|
<optgroup label="Super-Erden & Felsige">
|
|
<option value="55cnce">55 Cancri e</option>
|
|
<option value="trappist1b">TRAPPIST-1 b</option>
|
|
</optgroup>
|
|
</select>
|
|
</div>
|
|
<div class="info-card">
|
|
<div class="ic-name" id="exoName">HD 209458 b</div>
|
|
<div class="ic-tag" id="exoTag">HEISSER JUPITER · 1999</div>
|
|
<div class="ic-desc" id="exoDesc">Erster Exoplanet mit direkt nachgewiesenem Transit.</div>
|
|
</div>
|
|
</div>
|
|
<div class="ctrl-block">
|
|
<div class="ctrl-label">Orbitparameter</div>
|
|
<div class="param-row">
|
|
<div class="param-head"><span class="param-name">INKLINATION</span><span class="param-val" id="vIncl">86.6°</span></div>
|
|
<input type="range" class="sl" id="sIncl" data-tip="Inklination" data-tip-text="Neigung der Planetenbahn zur Sichtlinie. Bei 90° liegt die Bahn exakt vor dem Stern (maximaler Transit). Je kleiner, desto kürzer oder gar kein Transit." min="45" max="90" step="0.1" value="86.6">
|
|
</div>
|
|
<div class="param-row">
|
|
<div class="param-head"><span class="param-name">ABSTAND (a/R*)</span><span class="param-val" id="vDist">8.8</span></div>
|
|
<input type="range" class="sl" id="sDist" data-tip="Orbitabstand" data-tip-text="Abstand des Planeten vom Stern in Sternradien (a/R*). Groesserer Abstand -> kuerzerer Transit, kuerzere Periode." min="2.5" max="25" step="0.1" value="8.76">
|
|
</div>
|
|
</div>
|
|
<div class="ctrl-block">
|
|
<div class="ctrl-label">Planetenparameter</div>
|
|
<div class="param-row">
|
|
<div class="param-head"><span class="param-name">RADIUS (Rp/R*)</span><span class="param-val" id="vRad">0.121</span></div>
|
|
<input type="range" class="sl" id="sRad" data-tip="Planetenradius" data-tip-text="Radius des Planeten relativ zum Stern (Rp/R*). Bestimmt direkt die Transit-Tiefe: Tiefe ~= (Rp/R*)^2." min="0.01" max="1.0" step="0.001" value="0.1209">
|
|
</div>
|
|
<div class="param-row">
|
|
<div class="param-head"><span class="param-name">ALBEDO</span><span class="param-val" id="vAlb">0.04</span></div>
|
|
<input type="range" class="sl" id="sAlb" data-tip="Albedo" data-tip-text="Rückstrahlvermögen des Planeten (0 = schwarz, 1 = perfekter Spiegel). Hohe Albedo erzeugt einen kleinen Helligkeitsbuckel vor dem Transit (Phasenkurve)." min="0" max="1" step="0.01" value="0.04">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- MULTI -->
|
|
<div id="ctrl-multi" class="hidden">
|
|
<div class="ctrl-block">
|
|
<div class="ctrl-label">Planetensystem</div>
|
|
<div class="sel-wrap">
|
|
<select class="sel" id="multiSelect">
|
|
<option value="trappist1">TRAPPIST-1 - 7 Planeten</option>
|
|
<option value="kepler11">Kepler-11 - 6 Planeten</option>
|
|
<option value="solar">Sonnensystem (innere Planeten)</option>
|
|
</select>
|
|
</div>
|
|
<div class="info-card">
|
|
<div class="ic-name" id="multiName">TRAPPIST-1</div>
|
|
<div class="ic-tag" id="multiTag">ULTRAKUEHLER M-ZWERG · 7 PLANETEN</div>
|
|
<div class="ic-desc" id="multiDesc">Das bekannteste Mehrplanetensystem.</div>
|
|
</div>
|
|
<div class="planet-legend" id="planetLegend"></div>
|
|
</div>
|
|
<div class="ctrl-block">
|
|
<div class="ctrl-label">Zeitfenster</div>
|
|
<div class="param-row">
|
|
<div class="param-head"><span class="param-name">ZEITFENSTER (TAGE)</span><span class="param-val" id="vTimeWin">25</span></div>
|
|
<input type="range" class="sl" id="sTimeWin" data-tip="Zeitfenster" data-tip-text="Dargestellter Zeitraum der Lichtkurve in Tagen. Längere Fenster zeigen mehr Transitevents mehrerer Planeten." min="5" max="200" step="1" value="25">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- BINARY -->
|
|
<div id="ctrl-binary" class="hidden">
|
|
<div class="ctrl-block">
|
|
<div class="ctrl-label">Doppelsternsystem</div>
|
|
<div class="sel-wrap">
|
|
<select class="sel" id="binSelect">
|
|
<option value="algol">Algol (β Persei)</option>
|
|
<option value="wuma">W Ursae Majoris</option>
|
|
<option value="betalyr">Beta Lyrae (β Lyr)</option>
|
|
<option value="custom_bin">Benutzerdefiniert</option>
|
|
</select>
|
|
</div>
|
|
<div class="info-card">
|
|
<div class="ic-name" id="binName">Algol</div>
|
|
<div class="ic-tag" id="binTag">β PERSEI · P=2.87 TAGE</div>
|
|
<div class="ic-desc" id="binDesc">Der "Daemonenstern" - bekanntester Bedeckungsveraenderlicher.</div>
|
|
</div>
|
|
</div>
|
|
<div class="ctrl-block">
|
|
<div class="ctrl-label">Systemparameter</div>
|
|
<div class="param-row">
|
|
<div class="param-head"><span class="param-name">INKLINATION</span><span class="param-val" id="vBinIncl">81.0°</span></div>
|
|
<input type="range" class="sl" id="sBinIncl" data-tip="Inklination" data-tip-text="Neigung der Doppelsternbahn. Bei 90° bedeckt jeder Stern den anderen vollständig (flaches Minimum). Je kleiner, desto flacher die Bedeckung." min="45" max="90" step="0.1" value="81.0">
|
|
</div>
|
|
<div class="param-row">
|
|
<div class="param-head"><span class="param-name">ABSTAND (a/R1)</span><span class="param-val" id="vBinDist">5.3</span></div>
|
|
<input type="range" class="sl" id="sBinDist" data-tip="Sternabstand" data-tip-text="Abstand zwischen den Sternen in Einheiten des Primaersternradius. Kleine Werte -> Kontaktsystem, grosse Werte -> getrenntes System." min="2" max="20" step="0.1" value="5.27">
|
|
</div>
|
|
<div class="param-row">
|
|
<div class="param-head"><span class="param-name">RADIUS R2/R1</span><span class="param-val" id="vBinR2">1.27</span></div>
|
|
<input type="range" class="sl" id="sBinR2" data-tip="Radiusverhaeltnis" data-tip-text="Radius des Sekundaersterns relativ zum Primaerstern (R2/R1). Beeinflusst die Dauer und Form beider Minima." min="0.1" max="3.0" step="0.01" value="1.27">
|
|
</div>
|
|
<div class="param-row">
|
|
<div class="param-head"><span class="param-name">HELLIGKEIT L2/L1</span><span class="param-val" id="vBinSB">0.017</span></div>
|
|
<input type="range" class="sl" id="sBinSB" data-tip="Helligkeitsverhaeltnis" data-tip-text="Flaechenhelligkeit des Sekundaersterns relativ zum Primaerstern (L2/L1). Bei 1.0 gleich hell -> gleich tiefe Minima. Bei kleinen Werten (wie Algol 0.017) ist das sekundaere Minimum sehr flach." min="0.005" max="1.0" step="0.001" value="0.017">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- STATS -->
|
|
<div class="ctrl-block">
|
|
<div class="ctrl-label">Kenngroessen</div>
|
|
<div class="stats-grid">
|
|
<div class="stat-item" data-tip="Primaeres Minimum" data-tip-text="Tiefster Helligkeitsabfall in Prozent. Beim Planeten: (Rp/R*)^2. Beim Doppelstern: Bedeckung des helleren Sterns."><div class="stat-lbl">Prim. Min.</div><div class="stat-val" id="sDepth">--</div></div>
|
|
<div class="stat-item" data-tip="Aktueller Flux" data-tip-text="Normierte Helligkeit des Systems zum aktuellen Zeitpunkt (1.0 = volle Helligkeit). Leuchtet grün, wenn gerade ein Transit oder eine Bedeckung stattfindet."><div class="stat-lbl">Flux jetzt</div><div class="stat-val" id="sFlux">1.000</div></div>
|
|
<div class="stat-item" id="stat-item-3" data-tip="Impact-Parameter b" data-tip-text="Einzelplanet: Minimaler Abstand Planetenmitte-Sternmitte in Sternradien. b=0 -> zentraler Transit, b<1 -> Transit moeglich, b>1 -> kein Transit. Doppelstern: Sekundaeres Minimum. Mehrplaneten: Anzahl der Planeten."><div class="stat-lbl" id="statLbl3">Impact b</div><div class="stat-val" id="sStat3">--</div></div>
|
|
<div class="stat-item" id="stat-item-4" data-tip="Weitere Kenngroesse" data-tip-text="Einzelplanet: Ob ein Transit geometrisch moeglich ist (b < 1+Rp/R*). Doppelstern: Umlaufzeit in Tagen. Mehrplaneten: Kuerzeste Periode im System."><div class="stat-lbl" id="statLbl4">Transit</div><div class="stat-val" id="sStat4">--</div></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ANIMATION -->
|
|
<div class="ctrl-block">
|
|
<div class="ctrl-label">Animation</div>
|
|
<div class="speed-row">
|
|
<div class="spd" data-v="0.3">0.5x</div>
|
|
<div class="spd on" data-v="1">1x</div>
|
|
<div class="spd" data-v="2">2x</div>
|
|
<div class="spd" data-v="5">5x</div>
|
|
<div class="spd" data-v="10">10x</div>
|
|
</div>
|
|
<div class="param-row">
|
|
<div class="param-head">
|
|
<span class="param-name">GESCHWINDIGKEIT</span>
|
|
<span class="param-val" id="vSpeed">1.0x</span>
|
|
</div>
|
|
<input type="range" class="sl" id="sSpeed" min="0.1" max="10" step="0.1" value="1.0"
|
|
data-tip="Geschwindigkeit" data-tip-text="Animationsgeschwindigkeit stufenlos von 0.1x bis 10x. Die Schnellwahlbuttons oben setzen den Schieberegler mit.">
|
|
</div>
|
|
<button class="play-btn" id="playBtn">PLAY</button>
|
|
</div>
|
|
</aside>
|
|
|
|
<section class="viz-panel">
|
|
<div class="occ-header">
|
|
<div class="occ-header-actions">
|
|
<button class="help-btn" id="helpBtn">? HILFE</button>
|
|
</div>
|
|
</div>
|
|
<div class="viz-wrap">
|
|
<canvas id="vizCanvas"></canvas>
|
|
<div class="eclipse-tag off" id="eclipseTag">KEIN TRANSIT</div>
|
|
<div class="viz-overlay">
|
|
<div id="vizHint" class="viz-hint">DRAG = INKLINATION | SCROLL = ZOOM</div>
|
|
<button id="zoomResetBtn" class="zoom-reset-btn">RESET</button>
|
|
</div>
|
|
</div>
|
|
<div class="lc-wrap"><canvas id="lcCanvas"></canvas></div>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- TOOLTIP -->
|
|
<div class="tip" id="tooltip"><div class="tip-title" id="tipTitle"></div><div id="tipText"></div></div>
|
|
|
|
<!-- HELP MODAL -->
|
|
<div class="modal-overlay" id="helpOverlay">
|
|
<div class="modal">
|
|
<div class="modal-head">
|
|
<div>
|
|
<div class="modal-head-title">HILFE & PARAMETER</div>
|
|
<div class="modal-head-sub">Bedeckungsphänomene · Astronomiemuseum der Sternwarte Sonneberg</div>
|
|
</div>
|
|
<button class="modal-close" id="helpClose">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="help-intro">Die Transit-Methode ist das erfolgreichste Verfahren zur Entdeckung von Exoplaneten. Wenn ein Planet vor seinem Stern vorbeizieht, schwaecht er dessen Licht um einen messbaren Betrag ab - die sogenannte Lichtkurve zeigt diesen Einbruch.</div>
|
|
<div class="help-tabs">
|
|
<div class="help-tab on" data-htab="single">Einzelplanet</div>
|
|
<div class="help-tab" data-htab="multi">Sternsystem</div>
|
|
<div class="help-tab" data-htab="binary">Doppelstern</div>
|
|
<div class="help-tab" data-htab="general">Allgemein</div>
|
|
</div>
|
|
|
|
<!-- SINGLE -->
|
|
<div class="help-tab-content on" id="htab-single">
|
|
<div class="help-section">
|
|
<div class="help-section-title">Orbitparameter</div>
|
|
<div class="help-row"><div class="help-key">Inklination (i)</div><div class="help-val">Neigung der Planetenbahn zur Sichtlinie in Grad. Bei i = 90 Grad liegt der Planet exakt auf der Verbindungslinie Beobachter-Stern. Je kleiner i, desto kuerzer der Transit - unter einer Mindestinklination findet gar kein Transit statt (Impact-Parameter b > 1).</div></div>
|
|
<div class="help-row"><div class="help-key">Abstand a/R*</div><div class="help-val">Orbitradius in Einheiten des Sternradius. Beeinflusst die Transitdauer (weiter weg = kuerzerer Transit relativ zur Periode) und die Staerke der Phasenkurve.</div></div>
|
|
</div>
|
|
<div class="help-section">
|
|
<div class="help-section-title">Planetenparameter</div>
|
|
<div class="help-row"><div class="help-key">Radius Rp/R*</div><div class="help-val">Planetenradius relativ zum Sternradius. Die Transit-Tiefe ist direkt proportional zum Flaechenverhaeltnis: Delta F ~= (Rp/R*)^2. Ein Planet mit 10% Sternradius erzeugt 1% Helligkeitsabfall.</div></div>
|
|
<div class="help-row"><div class="help-key">Albedo</div><div class="help-val">Rückstrahlvermögen (0 = absolut schwarz, 1 = perfekter Spiegel). Hohe Albedo erzeugt vor dem Transit einen schwachen Helligkeitsbuckel (reflektierte Phasenkurve) und während der Sekundärbedeckung ein Mini-Minimum.</div></div>
|
|
</div>
|
|
<div class="help-section">
|
|
<div class="help-section-title">Kenngroessen</div>
|
|
<div class="help-row"><div class="help-key">Transit-Tiefe</div><div class="help-val">Maximaler Fluxabfall in Prozent. Entspricht (Rp/R*)^2.</div></div>
|
|
<div class="help-row"><div class="help-key">Impact-Param. b</div><div class="help-val">Minimaler Abstand des Planetenmittelpunkts vom Sternmittelpunkt in Sternradien. b = 0: zentraler Transit. b < 1: Transit findet statt. b > 1: kein Transit.</div></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- MULTI -->
|
|
<div class="help-tab-content" id="htab-multi">
|
|
<div class="help-section">
|
|
<div class="help-section-title">Mehrplanetensysteme</div>
|
|
<div class="help-row"><div class="help-key">TRAPPIST-1</div><div class="help-val">Sieben felsige Planeten um einen ultrakühlen M-Zwergstern, 39 Lichtjahre entfernt. Alle wurden durch Transits entdeckt. Planeten e, f und g liegen in der habitablen Zone.</div></div>
|
|
<div class="help-row"><div class="help-key">Kepler-11</div><div class="help-val">Sechs Planeten in sehr engen Orbits - alle dichter am Stern als die Venus. 2010 als erstes bekanntes Sechs-Planeten-System entdeckt.</div></div>
|
|
<div class="help-row"><div class="help-key">Sonnensystem</div><div class="help-val">Wie wuerde unser System von aussen aussehen? Alle inneren Planeten auf maximale Transitinklination (90 Grad). Man sieht: Merkur transitet haeufig, Mars sehr selten.</div></div>
|
|
</div>
|
|
<div class="help-section">
|
|
<div class="help-section-title">Zeitfenster</div>
|
|
<div class="help-row"><div class="help-key">Zeitfenster</div><div class="help-val">Dargestellter Zeitraum der Lichtkurve in Tagen. Durch Vergroessern des Fensters werden Transits von weiter aussen liegenden Planeten mit laengerer Periode sichtbar.</div></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- BINARY -->
|
|
<div class="help-tab-content" id="htab-binary">
|
|
<div class="help-section">
|
|
<div class="help-section-title">Typen</div>
|
|
<div class="help-row"><div class="help-key">Algol (EA)</div><div class="help-val">Bedeckungsveraenderlicher Typ Algol - getrenntes System. Flache Phasen zwischen den Minima. Primaeres Minimum tiefer (heller Stern wird bedeckt).</div></div>
|
|
<div class="help-row"><div class="help-key">W UMa (EW)</div><div class="help-val">Kontaktsystem - beide Sterne beruehren sich und teilen eine gemeinsame Huelle. Kontinuierliche Helligkeitsvariation, beide Minima fast gleich tief.</div></div>
|
|
<div class="help-row"><div class="help-key">Beta Lyrae (EB)</div><div class="help-val">Halbgetrenntes System mit Massentransfer. Unregelmaessige Lichtkurve, kein echtes Maximum.</div></div>
|
|
</div>
|
|
<div class="help-section">
|
|
<div class="help-section-title">Parameter</div>
|
|
<div class="help-row"><div class="help-key">R2/R1</div><div class="help-val">Radius des Sekundaersterns relativ zum Primaerstern. Beeinflusst die Tiefe beider Minima und die Transitdauer.</div></div>
|
|
<div class="help-row"><div class="help-key">L2/L1</div><div class="help-val">Flaechenhelligkeit des Sekundaersterns. Bei 1.0 gleich hell -> gleich tiefe Minima. Bei kleinen Werten (z.B. Algol: 0.017) ist das sekundaere Minimum kaum sichtbar.</div></div>
|
|
<div class="help-row"><div class="help-key">Prim. / Sek. Min.</div><div class="help-val">Tiefe des primären (Sekundärstern bedeckt Primärstern) und sekundären Minimums (umgekehrt) in Prozent des Gesamtlichts.</div></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- GENERAL -->
|
|
<div class="help-tab-content" id="htab-general">
|
|
<div class="help-section">
|
|
<div class="help-section-title">Lichtkurve</div>
|
|
<div class="help-row"><div class="help-key">X-Achse</div><div class="help-val">Orbitphase (0-360 Grad) beim Einzel- und Doppelstern-Modus. Zeitachse in Tagen im Mehrplaneten-Modus.</div></div>
|
|
<div class="help-row"><div class="help-key">Y-Achse</div><div class="help-val">Normierter Flux (1.0 = volle Helligkeit). Werte unter 1.0 zeigen Bedeckungen, Werte über 1.0 reflektiertes Planetenlicht.</div></div>
|
|
<div class="help-row"><div class="help-key">Weisser Punkt</div><div class="help-val">Aktuelle Position in der Lichtkurve. Durch Klicken/Ziehen in der Lichtkurve kann die Phase direkt eingestellt werden.</div></div>
|
|
<div class="help-row"><div class="help-key">Gestrichelte Linie</div><div class="help-val">Referenzlinie bei Flux = 1.0 (unbedeckter Stern).</div></div>
|
|
</div>
|
|
<div class="help-section">
|
|
<div class="help-section-title">Visualisierung</div>
|
|
<div class="help-row"><div class="help-key">3D-Ansicht</div><div class="help-val">Zeigt das System aus Beobachterperspektive. Die gestrichelte Ellipse ist die projizierte Bahn. Der grüne Badge leuchtet auf, wenn ein Transit/eine Bedeckung aktiv ist.</div></div>
|
|
<div class="help-row"><div class="help-key">Geschwindigkeit</div><div class="help-val">0.5x bis 5x beschleunigt oder verlangsamt die Animation. Nuetzlich um schnelle Transitevents zu beobachten oder ruhig zu studieren.</div></div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
const PC=['#70e0a0','#6090e0','#e09040','#e06070','#a070e0','#40c0e0','#e0c040'];
|
|
|
|
const DB_S={
|
|
custom: {name:'Benutzerdefiniert',tag:'FREIE PARAMETER',desc:'Stelle alle Parameter manuell ein.',incl:85,dist:8,rr:.12,alb:.1},
|
|
hd209458b: {name:'HD 209458 b',tag:'HEISSER JUPITER · 1999 · "OSIRIS"',desc:'Erster Exoplanet mit direkt nachgewiesenem Transit. Wegen seiner expandierenden Atmosphäre auch "Osiris" genannt.',incl:86.6,dist:8.76,rr:.1209,alb:.04},
|
|
wasp12b: {name:'WASP-12 b',tag:'HEISSER JUPITER · 2008',desc:'Umkreist seinen Stern in nur 1,09 Tagen - und wird dabei langsam durch Gezeitenkraefte zerrissen.',incl:83.4,dist:3.09,rr:.1175,alb:.06},
|
|
hatp32b: {name:'HAT-P-32 b',tag:'HEISSER JUPITER · 2011',desc:'Stark aufgeblähter Riesenplanet mit einer der tiefsten bekannten Transit-Lichtkurven.',incl:88.9,dist:4.12,rr:.150,alb:.08},
|
|
kepler7b: {name:'Kepler-7 b',tag:'HEISSER JUPITER · KEPLER · 2010',desc:'Rekord-Albedo von 0.35 - hoechste bekannter Exoplaneten. Helle Wolkenbaender reflektieren das Sternlicht.',incl:85.7,dist:7.32,rr:.0880,alb:.35},
|
|
wasp96b: {name:'WASP-96 b',tag:'GASRIESE · JWST-ERSTES LICHT 2022',desc:'Erster Exoplanet dessen Atmosphäre vom James Webb Space Telescope vollständig spektroskopiert wurde.',incl:85.1,dist:9.33,rr:.1148,alb:.10},
|
|
'55cnce': {name:'55 Cancri e',tag:'SUPER-ERDE · 2004 · LAVA-WELT',desc:'Eine Welt mit möglicherweise geschmolzener Oberfläche und Lava-Ozeanen. Umkreist seinen Stern in nur 17.7 Stunden.',incl:83.0,dist:3.52,rr:.0182,alb:.30},
|
|
trappist1b:{name:'TRAPPIST-1 b',tag:'FELSIGER PLANET · 2016',desc:'Innerster der sieben Planeten des TRAPPIST-1-Systems. Fuer Leben zu heiss, aber wichtiger Vergleichsplanet.',incl:89.6,dist:20.9,rr:.0834,alb:.10},
|
|
};
|
|
|
|
const DB_M={
|
|
trappist1:{name:'TRAPPIST-1',tag:'ULTRAKUEHLER M-ZWERG · 7 PLANETEN · 2017',desc:'Das bekannteste Mehrplanetensystem mit sieben felsigen Welten, darunter drei in der habitablen Zone. Alle Planeten wurden durch Transits entdeckt.',timeWindow:25,
|
|
planets:[{name:'b',rr:.0834,dist:20.9,incl:89.56,period:1.511},{name:'c',rr:.0839,dist:28.5,incl:89.70,period:2.422},{name:'d',rr:.0617,dist:39.6,incl:89.80,period:4.050},{name:'e',rr:.0730,dist:51.5,incl:89.86,period:6.101},{name:'f',rr:.0780,dist:68.4,incl:89.68,period:9.207},{name:'g',rr:.0880,dist:83.2,incl:89.50,period:12.354},{name:'h',rr:.0550,dist:109,incl:89.76,period:18.767}]},
|
|
kepler11:{name:'Kepler-11',tag:'G-ZWERG · 6 PLANETEN · KEPLER · 2010',desc:'Ein kompaktes Sechs-Planeten-System. Alle sechs Planeten wurden gleichzeitig von Kepler entdeckt - damals eine Sensation.',timeWindow:50,
|
|
planets:[{name:'b',rr:.0191,dist:13.3,incl:88.5,period:10.304},{name:'c',rr:.0262,dist:16.8,incl:88.6,period:13.024},{name:'d',rr:.0280,dist:22.6,incl:89.3,period:22.684},{name:'e',rr:.0360,dist:26.4,incl:88.8,period:31.996},{name:'f',rr:.0264,dist:33.1,incl:89.4,period:46.689},{name:'g',rr:.0358,dist:53.8,incl:89.0,period:118.38}]},
|
|
solar:{name:'Sonnensystem',tag:'G2V-STERN · INNERE PLANETEN · TRANSIT-PERSPEKTIVE',desc:'Wie wuerde unser Sonnensystem von aussen als Transitsystem erscheinen? Alle inneren Planeten auf maximale Transit-Inklination.',timeWindow:120,
|
|
planets:[{name:'Merkur',rr:.00351,dist:83.2,incl:90.0,period:87.97},{name:'Venus',rr:.00870,dist:155.5,incl:90.0,period:224.7},{name:'Erde',rr:.00916,dist:215.1,incl:90.0,period:365.25},{name:'Mars',rr:.00487,dist:327.7,incl:90.0,period:686.97}]},
|
|
};
|
|
|
|
const DB_B={
|
|
algol: {name:'Algol (beta Persei)',tag:'B8V + K2III - P=2.87 TAGE - HALBGETRENNT',desc:'Der "Daemonenstern" - seit der Antike beobachtet. Primaeres Minimum tiefer, da der helle B-Stern bedeckt wird. Klassisches EA-System.',incl:81.0,dist:5.27,r2:1.274,sb2:.0167,q:.221,period:2.8673},
|
|
wuma: {name:'W Ursae Majoris',tag:'K/G + K/G - P=0.33 TAGE - KONTAKTSYSTEM',desc:'Prototyp der W-UMa-Kontaktsysteme. Beide Sterne teilen eine Huelle. Lichtkurve ohne flache Teile - kontinuierliche Variation.',incl:75.8,dist:4.40,r2:.82,sb2:.80,q:.42,period:0.3336},
|
|
betalyr: {name:'Beta Lyrae (beta Lyr)',tag:'B UEBERRIESE + B - P=12.94 TAGE - SEMI-DETACHIERT',desc:'EB-Typ mit Massentransfer. Die Akkretionsscheibe um den sekundaeren Stern veraendert die Lichtkurve stark. Kein flaches Maximum.',incl:86.3,dist:7.30,r2:1.85,sb2:.08,q:.27,period:12.9414},
|
|
custom_bin:{name:'Benutzerdefiniert',tag:'FREIE PARAMETER',desc:'Stelle Inklination, Abstand, Radien und Helligkeit frei ein.',incl:80.0,dist:6.0,r2:1.0,sb2:.5,q:.5,period:1.0},
|
|
};
|
|
|
|
// STATE
|
|
const S={
|
|
mode:'single',
|
|
M:1.4, t:0,
|
|
playing:false,speed:1.0,
|
|
incl:86.6,dist:8.76,rr:.1209,alb:.04,
|
|
multiPreset:'trappist1',timeWindow:25,
|
|
binIncl:81.0,binDist:5.27,binR2:1.274,binSB:.0167,binQ:.221,binPeriod:2.8673,
|
|
flux:[],fmin:.99,fmax:1.01,draggingLC:false,
|
|
vizZoom:1.0, vizDragging:false, vizDragStartX:0, vizDragStartY:0, vizDragStartIncl:0,
|
|
};
|
|
|
|
// PHYSICS
|
|
function circOv(r1,r2,d){
|
|
if(d>=r1+r2)return 0;
|
|
if(d+r2<=r1)return Math.PI*r2*r2;
|
|
if(d+r1<=r2)return Math.PI*r1*r1;
|
|
const a1=Math.acos(Math.max(-1,Math.min(1,(d*d+r1*r1-r2*r2)/(2*d*r1))));
|
|
const a2=Math.acos(Math.max(-1,Math.min(1,(d*d+r2*r2-r1*r1)/(2*d*r2))));
|
|
return r1*r1*a1+r2*r2*a2-.5*Math.sqrt(Math.max(0,(-d+r1+r2)*(d+r1-r2)*(d-r1+r2)*(d+r1+r2)));
|
|
}
|
|
|
|
function fSingle(M,incl,dist,rr,alb){
|
|
const ic=incl*Math.PI/180;
|
|
const xp=dist*Math.cos(M),yp=dist*Math.sin(M)*Math.cos(ic),zp=dist*Math.sin(M)*Math.sin(ic);
|
|
const sep=Math.sqrt(xp*xp+yp*yp);
|
|
const ph=(1-zp/dist)/2;
|
|
const refl=alb*Math.pow(rr/dist,2)*ph;
|
|
let f=1.0+refl;
|
|
if(zp>0)f-=circOv(1.0,rr,sep)/Math.PI;
|
|
if(zp<0){const ov=circOv(1.0,rr,sep);if(ov>0)f-=refl*Math.min(1,ov/(Math.PI*rr*rr));}
|
|
return f;
|
|
}
|
|
|
|
function fBinary(M){
|
|
const ic=S.binIncl*Math.PI/180;
|
|
const a2=S.binDist/(1+S.binQ),a1=S.binDist*S.binQ/(1+S.binQ);
|
|
const z2=a2*Math.sin(M)*Math.sin(ic), z1=-a1*Math.sin(M)*Math.sin(ic);
|
|
const x2=a2*Math.cos(M),y2=a2*Math.sin(M)*Math.cos(ic);
|
|
const x1=-a1*Math.cos(M),y1=-a1*Math.sin(M)*Math.cos(ic);
|
|
const sep=Math.sqrt((x1-x2)**2+(y1-y2)**2);
|
|
const sb1=1.0,sb2=S.binSB;
|
|
const f0=Math.PI*(sb1+S.binR2*S.binR2*sb2);
|
|
const ov=circOv(1.0,S.binR2,sep);
|
|
const blocked=ov>0?ov*(z2>z1?sb1:sb2):0;
|
|
return(f0-blocked)/f0;
|
|
}
|
|
|
|
function fMulti(t){
|
|
const sys=DB_M[S.multiPreset];
|
|
let f=1.0;
|
|
for(const p of sys.planets){
|
|
const M=(2*Math.PI*t/p.period)%(2*Math.PI);
|
|
f+=fSingle(M,p.incl,p.dist,p.rr,0)-1.0;
|
|
}
|
|
return Math.max(0.9,f);
|
|
}
|
|
|
|
function recompute(){
|
|
const N=720;S.flux=[];
|
|
let fmin=1e9,fmax=-1e9;
|
|
for(let i=0;i<N;i++){
|
|
let f;
|
|
if(S.mode==='single')f=fSingle((i/N)*Math.PI*2,S.incl,S.dist,S.rr,S.alb);
|
|
else if(S.mode==='binary')f=fBinary((i/N)*Math.PI*2);
|
|
else f=fMulti((i/N)*S.timeWindow);
|
|
S.flux.push(f);fmin=Math.min(fmin,f);fmax=Math.max(fmax,f);
|
|
}
|
|
const df=Math.max(fmax-fmin,0.0001);
|
|
S.fmin=Math.min(0.9975,fmin-df*.3);
|
|
S.fmax=Math.max(1.003,fmax+df*.3);
|
|
// stats
|
|
const depth=Math.max(0,1-fmin);
|
|
document.getElementById('sDepth').textContent=(depth*100).toFixed(4)+'%';
|
|
if(S.mode==='single'){
|
|
const b=S.dist*Math.abs(Math.cos(S.incl*Math.PI/180));
|
|
document.getElementById('statLbl3').textContent='Impact b';
|
|
document.getElementById('sStat3').textContent=b.toFixed(3);
|
|
document.getElementById('statLbl4').textContent='Transit';
|
|
document.getElementById('stat-item-3').dataset.tip='Impact-Parameter b';
|
|
document.getElementById('stat-item-3').dataset.tipText='Minimaler Abstand Planetenmitte-Sternmitte in Sternradien. b=0 -> zentraler Transit, b<1 -> Transit moeglich, b>1 -> kein Transit. Minimum 45 Grad.';
|
|
document.getElementById('stat-item-4').dataset.tip='Transit möglich?';
|
|
document.getElementById('stat-item-4').dataset.tipText='Ob ein Transit geometrisch moeglich ist. Abhaengig vom Impact-Parameter b und dem Planetenradius: Transit wenn b < 1 + Rp/R*.';
|
|
const el=document.getElementById('sStat4');
|
|
const ok=b<1+S.rr;el.textContent=ok?'JA':'NEIN';el.style.color=ok?'#70e0a0':'#e07070';
|
|
}else if(S.mode==='binary'){
|
|
document.getElementById('statLbl3').textContent='Sek. Min.';
|
|
document.getElementById('stat-item-3').dataset.tip='Sekundäres Minimum';
|
|
document.getElementById('stat-item-3').dataset.tipText='Tiefe des zweiten Helligkeitseinbruchs, wenn der schwächere Stern hinter dem helleren verschwindet. Bei Algol kaum sichtbar (0.017), bei W UMa fast gleich tief wie das primäre Minimum.';
|
|
document.getElementById('stat-item-4').dataset.tip='Umlaufzeit';
|
|
document.getElementById('stat-item-4').dataset.tipText='Periode des Doppelsternsystems in Tagen. W UMa: 0.33 d (8 Stunden!), Algol: 2.87 d, Beta Lyrae: 12.94 d.';
|
|
// find secondary minimum: deepest point at least N/4 away from primary minimum
|
|
const N2=S.flux.length;
|
|
let primIdx=0;
|
|
for(let i=1;i<N2;i++){if(S.flux[i]<S.flux[primIdx])primIdx=i;}
|
|
let secIdx=-1,secVal=1;
|
|
for(let i=0;i<N2;i++){
|
|
const dist=Math.min(Math.abs(i-primIdx),N2-Math.abs(i-primIdx));
|
|
if(dist>N2/4&&S.flux[i]<secVal){secVal=S.flux[i];secIdx=i;}
|
|
}
|
|
const secDepth=secIdx>=0?Math.max(0,1-secVal):0;
|
|
document.getElementById('sStat3').textContent=(secDepth*100).toFixed(4)+'%';
|
|
document.getElementById('statLbl4').textContent='Periode';
|
|
document.getElementById('sStat4').textContent=S.binPeriod.toFixed(4)+' d';
|
|
document.getElementById('sStat4').style.color='var(--gold2)';
|
|
}else{
|
|
const sys=DB_M[S.multiPreset];
|
|
document.getElementById('statLbl3').textContent='Planeten';
|
|
document.getElementById('stat-item-3').dataset.tip='Anzahl Planeten';
|
|
document.getElementById('stat-item-3').dataset.tipText='Anzahl der Planeten im ausgewählten System. TRAPPIST-1: 7 Planeten, Kepler-11: 6 Planeten, Sonnensystem: 4 innere Planeten.';
|
|
document.getElementById('stat-item-4').dataset.tip='Kürzeste Periode';
|
|
document.getElementById('stat-item-4').dataset.tipText='Umlaufzeit des innersten Planeten in Tagen. Planeten mit kurzer Periode transiten häufiger und sind daher leichter zu entdecken.';
|
|
document.getElementById('sStat3').textContent=sys.planets.length;
|
|
document.getElementById('statLbl4').textContent='P-min (d)';
|
|
document.getElementById('sStat4').textContent=sys.planets[0].period.toFixed(2);
|
|
document.getElementById('sStat4').style.color='var(--gold2)';
|
|
}
|
|
}
|
|
|
|
// WEBGL 3D SPHERE RENDERER
|
|
const glCv = document.createElement('canvas');
|
|
let gl = null, glProg = null, glLoc = {};
|
|
|
|
const GL_VS = `attribute vec2 aPos; void main(){ gl_Position=vec4(aPos,0.,1.); }`;
|
|
|
|
const GL_FS = `
|
|
precision highp float;
|
|
uniform vec2 uRes;
|
|
uniform vec2 uPos0; uniform float uRad0; uniform vec3 uCol0; uniform float uZ0; uniform float uType0; uniform float uAct0;
|
|
uniform vec2 uPos1; uniform float uRad1; uniform vec3 uCol1; uniform float uZ1; uniform float uType1; uniform float uAct1;
|
|
uniform vec2 uPos2; uniform float uRad2; uniform vec3 uCol2; uniform float uZ2; uniform float uType2; uniform float uAct2;
|
|
uniform vec2 uLight;
|
|
|
|
vec3 jupiterBand(float ny){
|
|
float b=0.5+0.32*sin(ny*15.0)+0.14*sin(ny*7.3+0.8)+0.06*sin(ny*32.0)+0.04*sin(ny*51.0);
|
|
b=clamp(b,0.0,1.0);
|
|
vec3 dark=vec3(0.34,0.18,0.05); vec3 lite=vec3(0.86,0.54,0.22);
|
|
return mix(dark,lite,b);
|
|
}
|
|
|
|
vec4 sphere(vec2 fc, vec2 cen, float r, vec3 col, float tp){
|
|
vec2 d=fc-cen; float dst2=dot(d,d);
|
|
float edge=r-sqrt(dst2);
|
|
if(edge<0.0) return vec4(0.0);
|
|
float dz=sqrt(max(0.0,r*r-dst2));
|
|
vec3 N=normalize(vec3(d.x,-d.y,dz));
|
|
float alpha=smoothstep(0.0,1.8,edge);
|
|
vec3 c;
|
|
if(tp<0.5){
|
|
// Star: limb darkening + specular peak
|
|
float mu=N.z;
|
|
float ld=1.0-0.65*(1.0-mu);
|
|
c=col*ld;
|
|
c=mix(c,vec3(1.0,0.97,0.93),pow(max(0.0,mu),4.5)*0.55);
|
|
} else {
|
|
// Planet: Jupiter bands + Lambertian from star
|
|
vec3 bc=jupiterBand(N.y);
|
|
vec2 lv=uLight-cen; float ll=length(lv)+0.001;
|
|
vec3 L=normalize(vec3(lv.x/ll,-lv.y/ll,2.5));
|
|
float diff=max(0.0,dot(N,L));
|
|
c=bc*(0.09+0.91*diff);
|
|
c*=(0.22+0.78*N.z);
|
|
}
|
|
return vec4(c,alpha);
|
|
}
|
|
|
|
void main(){
|
|
vec2 fc=vec2(gl_FragCoord.x, uRes.y-gl_FragCoord.y);
|
|
float bestZ=-1e9; vec4 best=vec4(0.0);
|
|
if(uAct0>0.5){ vec2 dv=fc-uPos0; if(dot(dv,dv)<=uRad0*uRad0&&uZ0>bestZ){ vec4 c=sphere(fc,uPos0,uRad0,uCol0,uType0); if(c.a>0.01){bestZ=uZ0;best=c;} } }
|
|
if(uAct1>0.5){ vec2 dv=fc-uPos1; if(dot(dv,dv)<=uRad1*uRad1&&uZ1>bestZ){ vec4 c=sphere(fc,uPos1,uRad1,uCol1,uType1); if(c.a>0.01){bestZ=uZ1;best=c;} } }
|
|
if(uAct2>0.5){ vec2 dv=fc-uPos2; if(dot(dv,dv)<=uRad2*uRad2&&uZ2>bestZ){ vec4 c=sphere(fc,uPos2,uRad2,uCol2,uType2); if(c.a>0.01){bestZ=uZ2;best=c;} } }
|
|
if(best.a<0.01) discard;
|
|
gl_FragColor=best;
|
|
}`;
|
|
|
|
function initGL(){
|
|
gl=glCv.getContext('webgl',{alpha:true,premultipliedAlpha:false,antialias:false});
|
|
if(!gl){console.warn('WebGL not available');return;}
|
|
function makeShader(type,src){const s=gl.createShader(type);gl.shaderSource(s,src);gl.compileShader(s);return s;}
|
|
glProg=gl.createProgram();
|
|
gl.attachShader(glProg,makeShader(gl.VERTEX_SHADER,GL_VS));
|
|
gl.attachShader(glProg,makeShader(gl.FRAGMENT_SHADER,GL_FS));
|
|
gl.linkProgram(glProg); gl.useProgram(glProg);
|
|
const buf=gl.createBuffer();
|
|
gl.bindBuffer(gl.ARRAY_BUFFER,buf);
|
|
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,1,1]),gl.STATIC_DRAW);
|
|
const ap=gl.getAttribLocation(glProg,'aPos');
|
|
gl.enableVertexAttribArray(ap); gl.vertexAttribPointer(ap,2,gl.FLOAT,false,0,0);
|
|
['uRes','uPos0','uRad0','uCol0','uZ0','uType0','uAct0',
|
|
'uPos1','uRad1','uCol1','uZ1','uType1','uAct1',
|
|
'uPos2','uRad2','uCol2','uZ2','uType2','uAct2','uLight']
|
|
.forEach(n=>{glLoc[n]=gl.getUniformLocation(glProg,n);});
|
|
gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);
|
|
}
|
|
|
|
function resizeGL(pw,ph){glCv.width=pw;glCv.height=ph;if(gl)gl.viewport(0,0,pw,ph);}
|
|
|
|
function hexRGB(hex){const v=parseInt(hex.replace('#',''),16);return[(v>>16&255)/255,(v>>8&255)/255,(v&255)/255];}
|
|
|
|
function renderGL(spheres,W,H){
|
|
if(!gl) return;
|
|
const dpr=window.devicePixelRatio||1;
|
|
gl.clearColor(0,0,0,0); gl.clear(gl.COLOR_BUFFER_BIT);
|
|
gl.uniform2f(glLoc.uRes,glCv.width,glCv.height);
|
|
// default all inactive
|
|
gl.uniform1f(glLoc.uAct0,0); gl.uniform1f(glLoc.uAct1,0); gl.uniform1f(glLoc.uAct2,0);
|
|
// light = first star position
|
|
const star=spheres.find(s=>s.type==='star');
|
|
if(star) gl.uniform2f(glLoc.uLight,star.x*dpr,glCv.height-star.y*dpr);
|
|
spheres.slice(0,3).forEach((s,i)=>{
|
|
const px=s.x*dpr, py=glCv.height-s.y*dpr, r=s.r*dpr;
|
|
gl.uniform2f(glLoc['uPos'+i],px,py);
|
|
gl.uniform1f(glLoc['uRad'+i],r);
|
|
gl.uniform3fv(glLoc['uCol'+i],s.col);
|
|
gl.uniform1f(glLoc['uZ'+i],s.z);
|
|
gl.uniform1f(glLoc['uType'+i],s.type==='planet'?1.0:0.0);
|
|
gl.uniform1f(glLoc['uAct'+i],1.0);
|
|
});
|
|
gl.drawArrays(gl.TRIANGLE_STRIP,0,4);
|
|
}
|
|
|
|
initGL();
|
|
|
|
|
|
// VIZ
|
|
const vCv=document.getElementById('vizCanvas'),vCx=vCv.getContext('2d');
|
|
function resizeViz(){const p=vCv.parentElement,w=p.clientWidth,h=p.clientHeight,dpr=window.devicePixelRatio||1;vCv.width=w*dpr;vCv.height=h*dpr;vCv.style.width=w+'px';vCv.style.height=h+'px';resizeGL(w*dpr,h*dpr);}
|
|
|
|
function drawViz(){
|
|
const dpr=window.devicePixelRatio||1,W=vCv.width/dpr,H=vCv.height/dpr;
|
|
vCx.setTransform(dpr,0,0,dpr,0,0);vCx.clearRect(0,0,W,H);
|
|
const bg=vCx.createRadialGradient(W/2,H/2,0,W/2,H/2,Math.max(W,H)*.7);
|
|
bg.addColorStop(0,'#0a0d22');bg.addColorStop(1,'#020208');
|
|
vCx.fillStyle=bg;vCx.fillRect(0,0,W,H);
|
|
if(S.mode==='single')drawVizSingle(W,H);
|
|
else if(S.mode==='multi')drawVizMulti(W,H);
|
|
else drawVizBinary(W,H);
|
|
}
|
|
|
|
function drawVizSingle(W,H){
|
|
const cx=W/2,cy=H/2,mg=48;
|
|
const sc=(Math.min(W,H)/2-mg)/(S.dist+1.8)*S.vizZoom;
|
|
const sR=Math.max(sc*1.0,10),ic=S.incl*Math.PI/180;
|
|
// Orbit ellipse (2D, behind everything)
|
|
vCx.save();vCx.setLineDash([3,7]);vCx.strokeStyle='rgba(200,168,75,0.18)';vCx.lineWidth=1;
|
|
vCx.beginPath();vCx.ellipse(cx,cy,S.dist*sc,S.dist*sc*Math.abs(Math.cos(ic)),0,0,Math.PI*2);vCx.stroke();vCx.restore();
|
|
const M=S.M;
|
|
const xp=S.dist*Math.cos(M),yp=S.dist*Math.sin(M)*Math.cos(ic),zp=S.dist*Math.sin(M)*Math.sin(ic);
|
|
const px=cx+xp*sc,py=cy-yp*sc,pr=Math.max(S.rr*sR,3);
|
|
// 2D corona glow (outside star disk, keep this in 2D)
|
|
const cor=vCx.createRadialGradient(cx,cy,sR*.5,cx,cy,sR*4.5);
|
|
cor.addColorStop(0,'rgba(255,180,60,0.18)');cor.addColorStop(.4,'rgba(255,120,20,0.07)');cor.addColorStop(1,'rgba(0,0,0,0)');
|
|
vCx.fillStyle=cor;vCx.beginPath();vCx.arc(cx,cy,sR*4.5,0,Math.PI*2);vCx.fill();
|
|
// WebGL 3D spheres (star at z=0, planet at actual z depth)
|
|
renderGL([
|
|
{x:cx,y:cy,r:sR,z:0,type:'star',col:[1.0,0.88,0.68]},
|
|
{x:px,y:py,r:pr,z:zp,type:'planet',col:[0.7,0.4,0.14]}
|
|
],W,H);
|
|
vCx.drawImage(glCv,0,0,W,H);
|
|
// Labels on top (2D)
|
|
vCx.font='600 10px Orbitron,monospace';vCx.fillStyle='rgba(200,168,75,0.7)';
|
|
vCx.fillText('i = '+S.incl.toFixed(1)+' deg',14,22);vCx.fillText('a/R* = '+S.dist.toFixed(1),14,36);
|
|
const sep=Math.sqrt((cx-px)**2+(cy-py)**2)/sc;
|
|
setTag(zp>0&&sep<1+S.rr?'* TRANSIT AKTIV':'KEIN TRANSIT',zp>0&&sep<1+S.rr);
|
|
}
|
|
|
|
function drawVizMulti(W,H){
|
|
const sys=DB_M[S.multiPreset];
|
|
const cx=W/2,cy=H/2,mg=40;
|
|
const maxD=sys.planets[sys.planets.length-1].dist;
|
|
const sc=(Math.min(W,H)/2-mg)/(maxD*1.05)*S.vizZoom;
|
|
const sR=Math.max(sc*1.0,5);
|
|
// back planets + orbits
|
|
sys.planets.forEach((p,i)=>{
|
|
const ic=p.incl*Math.PI/180;
|
|
vCx.save();vCx.setLineDash([2,6]);vCx.strokeStyle=PC[i%PC.length]+'22';vCx.lineWidth=1;
|
|
vCx.beginPath();vCx.ellipse(cx,cy,p.dist*sc,p.dist*sc*Math.abs(Math.cos(ic)),0,0,Math.PI*2);vCx.stroke();vCx.restore();
|
|
});
|
|
// back planets (zp<=0)
|
|
sys.planets.forEach((p,i)=>{
|
|
const M=(2*Math.PI*S.t/p.period)%(2*Math.PI),ic=p.incl*Math.PI/180;
|
|
const xp=p.dist*Math.cos(M),yp=p.dist*Math.sin(M)*Math.cos(ic),zp=p.dist*Math.sin(M)*Math.sin(ic);
|
|
if(zp<=0)drawColoredPlanet(cx+xp*sc,cy-yp*sc,Math.max(p.rr*sR,2.5),PC[i%PC.length],cx,cy);
|
|
});
|
|
drawHotStar(cx,cy,sR);
|
|
// front planets (zp>0)
|
|
let anyT=false;
|
|
sys.planets.forEach((p,i)=>{
|
|
const M=(2*Math.PI*S.t/p.period)%(2*Math.PI),ic=p.incl*Math.PI/180;
|
|
const xp=p.dist*Math.cos(M),yp=p.dist*Math.sin(M)*Math.cos(ic),zp=p.dist*Math.sin(M)*Math.sin(ic);
|
|
if(zp>0){
|
|
const pxc=cx+xp*sc,pyc=cy-yp*sc,pr=Math.max(p.rr*sR,2.5);
|
|
drawColoredPlanet(pxc,pyc,pr,PC[i%PC.length],cx,cy);
|
|
if(Math.sqrt((cx-pxc)**2+(cy-pyc)**2)/sc<1+p.rr)anyT=true;
|
|
}
|
|
});
|
|
vCx.font='600 7px Orbitron,monospace';vCx.fillStyle='rgba(200,168,75,0.7)';
|
|
vCx.fillText('t = '+S.t.toFixed(2)+' Tage',14,20);
|
|
setTag(anyT?'* TRANSIT AKTIV':'KEIN TRANSIT',anyT);
|
|
}
|
|
|
|
function drawVizBinary(W,H){
|
|
const cx=W/2,cy=H/2,mg=44;
|
|
const ic=S.binIncl*Math.PI/180;
|
|
const a2=S.binDist/(1+S.binQ),a1=S.binDist*S.binQ/(1+S.binQ);
|
|
const sc=(Math.min(W,H)/2-mg)/((a2+S.binR2)*1.1)*S.vizZoom;
|
|
const sR=Math.max(sc*1.0,8),s2R=Math.max(sc*S.binR2,5);
|
|
const M=S.M;
|
|
// orbit ellipses - primary (gold) and secondary (col-tinted)
|
|
const sb2=S.binSB;
|
|
const s2col=sb2<.1?'#e06030':sb2<.35?'#e09040':sb2<.65?'#f0c060':'#fff8c0';
|
|
vCx.save();vCx.setLineDash([4,6]);
|
|
// primary orbit
|
|
vCx.strokeStyle='rgba(200,168,75,0.35)';vCx.lineWidth=1.2;
|
|
vCx.beginPath();vCx.ellipse(cx,cy,a1*sc,a1*sc*Math.abs(Math.cos(ic)),0,0,Math.PI*2);vCx.stroke();
|
|
// secondary orbit
|
|
vCx.strokeStyle='rgba(220,140,80,0.28)';vCx.lineWidth=1.2;
|
|
vCx.beginPath();vCx.ellipse(cx,cy,a2*sc,a2*sc*Math.abs(Math.cos(ic)),0,0,Math.PI*2);vCx.stroke();
|
|
vCx.restore();
|
|
// CoM crosshair
|
|
vCx.save();vCx.setLineDash([1,6]);vCx.strokeStyle='rgba(200,168,75,0.22)';vCx.lineWidth=1;
|
|
vCx.beginPath();vCx.moveTo(cx-14,cy);vCx.lineTo(cx+14,cy);vCx.stroke();
|
|
vCx.beginPath();vCx.moveTo(cx,cy-14);vCx.lineTo(cx,cy+14);vCx.stroke();
|
|
vCx.restore();
|
|
// CoM dot
|
|
vCx.fillStyle='rgba(200,168,75,0.4)';vCx.beginPath();vCx.arc(cx,cy,2.5,0,Math.PI*2);vCx.fill();
|
|
// positions
|
|
const x1p=cx-a1*sc*Math.cos(M),y1p=cy+a1*sc*Math.sin(M)*Math.cos(ic),z1=-a1*Math.sin(M)*Math.sin(ic);
|
|
const x2p=cx+a2*sc*Math.cos(M), y2p=cy-a2*sc*Math.sin(M)*Math.cos(ic),z2=a2*Math.sin(M)*Math.sin(ic);
|
|
// WebGL 3D stars (depth sorted by z in shader)
|
|
const s2rgb=hexRGB(s2col);
|
|
renderGL([
|
|
{x:x1p,y:y1p,r:sR, z:z1,type:'star',col:[1.0,0.88,0.68]},
|
|
{x:x2p,y:y2p,r:s2R,z:z2,type:'star',col:s2rgb}
|
|
],W,H);
|
|
// 2D coronas (outside disks)
|
|
const cor1=vCx.createRadialGradient(x1p,y1p,sR*.5,x1p,y1p,sR*4);
|
|
cor1.addColorStop(0,'rgba(255,180,60,0.18)');cor1.addColorStop(1,'rgba(0,0,0,0)');
|
|
vCx.fillStyle=cor1;vCx.beginPath();vCx.arc(x1p,y1p,sR*4,0,Math.PI*2);vCx.fill();
|
|
const cor2=vCx.createRadialGradient(x2p,y2p,s2R*.4,x2p,y2p,s2R*3.5);
|
|
const [rr2,gg2,bb2]=s2rgb;
|
|
cor2.addColorStop(0,`rgba(${Math.round(rr2*255)},${Math.round(gg2*255)},${Math.round(bb2*255)},0.14)`);cor2.addColorStop(1,'rgba(0,0,0,0)');
|
|
vCx.fillStyle=cor2;vCx.beginPath();vCx.arc(x2p,y2p,s2R*3.5,0,Math.PI*2);vCx.fill();
|
|
vCx.drawImage(glCv,0,0,W,H);
|
|
// connecting line between stars (behind labels)
|
|
vCx.save();vCx.setLineDash([2,4]);vCx.strokeStyle='rgba(200,168,75,0.12)';vCx.lineWidth=1;
|
|
vCx.beginPath();vCx.moveTo(x1p,y1p);vCx.lineTo(x2p,y2p);vCx.stroke();vCx.restore();
|
|
// corner labels
|
|
vCx.font='600 7.5px Orbitron,monospace';vCx.fillStyle='rgba(200,168,75,0.7)';
|
|
vCx.fillText('i = '+S.binIncl.toFixed(1)+' deg',14,20);vCx.fillText('R2/R1 = '+S.binR2.toFixed(2),14,32);
|
|
const sep=Math.sqrt((x1p-x2p)**2+(y1p-y2p)**2)/sc;
|
|
const inE=sep<1.0+S.binR2;
|
|
setTag(inE?'* BEDECKUNG AKTIV':'KEINE BEDECKUNG',inE);
|
|
}
|
|
|
|
// Drawing helpers
|
|
function drawHotStar(px,py,r){
|
|
const cor=vCx.createRadialGradient(px,py,r*.5,px,py,r*4);
|
|
cor.addColorStop(0,'rgba(255,170,40,0.13)');cor.addColorStop(.5,'rgba(255,110,15,0.04)');cor.addColorStop(1,'rgba(0,0,0,0)');
|
|
vCx.fillStyle=cor;vCx.beginPath();vCx.arc(px,py,r*4,0,Math.PI*2);vCx.fill();
|
|
vCx.save();
|
|
vCx.beginPath();vCx.arc(px,py,r,0,Math.PI*2);vCx.clip();
|
|
const ld=vCx.createRadialGradient(px-r*.18,py-r*.18,r*.04,px,py,r);
|
|
ld.addColorStop(0.00,'rgba(255,252,224,1.0)');
|
|
ld.addColorStop(0.18,'rgba(255,232,136,1.0)');
|
|
ld.addColorStop(0.45,'rgba(255,181,48,1.0)');
|
|
ld.addColorStop(0.70,'rgba(220,104,8,1.0)');
|
|
ld.addColorStop(0.88,'rgba(175,52,0,0.75)');
|
|
ld.addColorStop(1.00,'rgba(100,20,0,0.0)');
|
|
vCx.fillStyle=ld;vCx.fillRect(px-r,py-r,r*2,r*2);
|
|
vCx.restore();
|
|
}
|
|
function drawBinStar(px,py,r,col){
|
|
const bigint=parseInt(col.replace('#',''),16);
|
|
const rr=(bigint>>16)&255,gg=(bigint>>8)&255,bb=bigint&255;
|
|
const cor=vCx.createRadialGradient(px,py,r*.4,px,py,r*3.5);
|
|
cor.addColorStop(0,`rgba(${rr},${gg},${bb},0.18)`);cor.addColorStop(1,'rgba(0,0,0,0)');
|
|
vCx.fillStyle=cor;vCx.beginPath();vCx.arc(px,py,r*3.5,0,Math.PI*2);vCx.fill();
|
|
vCx.save();
|
|
vCx.beginPath();vCx.arc(px,py,r,0,Math.PI*2);vCx.clip();
|
|
const ld=vCx.createRadialGradient(px-r*.18,py-r*.18,r*.04,px,py,r);
|
|
ld.addColorStop(0.00,'rgba(255,252,248,1.0)');
|
|
ld.addColorStop(0.25,`rgba(${rr},${gg},${bb},1.0)`);
|
|
ld.addColorStop(0.65,`rgba(${Math.round(rr*.8)},${Math.round(gg*.7)},${Math.round(bb*.6)},1.0)`);
|
|
ld.addColorStop(0.88,`rgba(${Math.round(rr*.5)},${Math.round(gg*.4)},${Math.round(bb*.3)},0.65)`);
|
|
ld.addColorStop(1.00,`rgba(${Math.round(rr*.2)},${Math.round(gg*.15)},${Math.round(bb*.1)},0.0)`);
|
|
vCx.fillStyle=ld;vCx.fillRect(px-r,py-r,r*2,r*2);
|
|
vCx.restore();
|
|
}
|
|
function drawPlanet(px,py,r,scx,scy){
|
|
vCx.save();
|
|
vCx.beginPath(); vCx.arc(px,py,r,0,Math.PI*2); vCx.clip();
|
|
|
|
// Base brown
|
|
vCx.fillStyle='#7a4a1e';
|
|
vCx.fillRect(px-r,py-r,r*2,r*2);
|
|
|
|
// Jupiter-style bands
|
|
const bands=[
|
|
{y:-r, h:r*0.18, c:'#c8843a'},
|
|
{y:-r*0.82, h:r*0.12, c:'#8b5220'},
|
|
{y:-r*0.70, h:r*0.20, c:'#d4904a'},
|
|
{y:-r*0.50, h:r*0.10, c:'#6a3a12'},
|
|
{y:-r*0.40, h:r*0.25, c:'#e8a060'},
|
|
{y:-r*0.15, h:r*0.10, c:'#7a4820'},
|
|
{y:-r*0.05, h:r*0.22, c:'#c87838'},
|
|
{y: r*0.17, h:r*0.10, c:'#5a3010'},
|
|
{y: r*0.27, h:r*0.22, c:'#d49050'},
|
|
{y: r*0.49, h:r*0.12, c:'#8a5228'},
|
|
{y: r*0.61, h:r*0.20, c:'#bf7c38'},
|
|
{y: r*0.81, h:r*0.19, c:'#7a4a1e'},
|
|
];
|
|
bands.forEach(b=>{ vCx.fillStyle=b.c; vCx.fillRect(px-r,py+b.y,r*2,b.h); });
|
|
|
|
// Limb darkening
|
|
const limb=vCx.createRadialGradient(px,py,r*0.55,px,py,r);
|
|
limb.addColorStop(0,'rgba(0,0,0,0)');
|
|
limb.addColorStop(0.75,'rgba(0,0,0,0.2)');
|
|
limb.addColorStop(1,'rgba(0,0,0,0.88)');
|
|
vCx.fillStyle=limb; vCx.fillRect(px-r,py-r,r*2,r*2);
|
|
|
|
vCx.restore();
|
|
}
|
|
|
|
function drawColoredPlanet(px,py,r,col,scx,scy){
|
|
const dx=px-scx,dy=py-scy,d=Math.sqrt(dx*dx+dy*dy)||1,nx=dx/d,ny=dy/d;
|
|
const base=vCx.createRadialGradient(px-nx*r*.4,py-ny*r*.4,0,px,py,r);
|
|
base.addColorStop(0,col+'ff');base.addColorStop(.55,col+'99');base.addColorStop(1,'#08080e');
|
|
vCx.fillStyle=base;vCx.beginPath();vCx.arc(px,py,r,0,Math.PI*2);vCx.fill();
|
|
const shd=vCx.createRadialGradient(px+nx*r*.2,py+ny*r*.2,0,px,py,r);
|
|
shd.addColorStop(0,'rgba(0,0,0,0)');shd.addColorStop(.6,'rgba(0,0,0,0.5)');shd.addColorStop(1,'rgba(0,0,0,0.9)');
|
|
vCx.fillStyle=shd;vCx.beginPath();vCx.arc(px,py,r,0,Math.PI*2);vCx.fill();
|
|
}
|
|
function setTag(txt,on){const t=document.getElementById('eclipseTag');t.textContent=txt;t.className='eclipse-tag '+(on?'on':'off');}
|
|
|
|
// LIGHT CURVE
|
|
const lCv=document.getElementById('lcCanvas'),lCx=lCv.getContext('2d');
|
|
function resizeLC(){const p=lCv.parentElement,w=p.clientWidth,h=p.clientHeight,dpr=window.devicePixelRatio||1;lCv.width=w*dpr;lCv.height=h*dpr;lCv.style.width=w+'px';lCv.style.height=h+'px';}
|
|
|
|
function drawLC(){
|
|
if(!S.flux.length)return;
|
|
const dpr=window.devicePixelRatio||1,W=lCv.width/dpr,H=lCv.height/dpr;
|
|
const ml=60,mr=14,mt=13,mb=32,pw=W-ml-mr,ph=H-mt-mb;
|
|
lCx.setTransform(dpr,0,0,dpr,0,0);lCx.clearRect(0,0,W,H);
|
|
lCx.fillStyle='rgba(2,2,12,0.95)';lCx.fillRect(0,0,W,H);
|
|
lCx.strokeStyle='rgba(200,168,75,0.04)';lCx.lineWidth=1;
|
|
for(let i=0;i<=6;i++){const x=ml+(i/6)*pw;lCx.beginPath();lCx.moveTo(x,mt);lCx.lineTo(x,mt+ph);lCx.stroke();}
|
|
for(let i=0;i<=4;i++){const y=mt+(i/4)*ph;lCx.beginPath();lCx.moveTo(ml,y);lCx.lineTo(ml+pw,y);lCx.stroke();}
|
|
lCx.strokeStyle='rgba(200,168,75,0.18)';lCx.lineWidth=1;
|
|
lCx.beginPath();lCx.moveTo(ml,mt);lCx.lineTo(ml,mt+ph);lCx.lineTo(ml+pw,mt+ph);lCx.stroke();
|
|
// unity line
|
|
const y1=mt+ph-(1.0-S.fmin)/(S.fmax-S.fmin)*ph;
|
|
lCx.save();lCx.setLineDash([4,5]);lCx.strokeStyle='rgba(200,168,75,0.12)';lCx.lineWidth=1;
|
|
lCx.beginPath();lCx.moveTo(ml,y1);lCx.lineTo(ml+pw,y1);lCx.stroke();lCx.restore();
|
|
// flux curve
|
|
lCx.save();lCx.shadowColor='rgba(200,168,75,0.5)';lCx.shadowBlur=6;lCx.strokeStyle='#c8a84b';lCx.lineWidth=1.7;lCx.beginPath();
|
|
S.flux.forEach((f,i)=>{const x=ml+(i/(S.flux.length-1))*pw,y=mt+ph-(f-S.fmin)/(S.fmax-S.fmin)*ph;i===0?lCx.moveTo(x,y):lCx.lineTo(x,y);});
|
|
lCx.stroke();lCx.restore();
|
|
// indicator
|
|
const frac=S.mode==='multi'?(S.t%S.timeWindow)/S.timeWindow:(((S.M%(Math.PI*2))+Math.PI*2)%(Math.PI*2))/(Math.PI*2);
|
|
const ix=ml+frac*pw;
|
|
const cf=S.mode==='single'?fSingle(S.M,S.incl,S.dist,S.rr,S.alb):S.mode==='binary'?fBinary(S.M):fMulti(S.t);
|
|
const iy=mt+ph-(cf-S.fmin)/(S.fmax-S.fmin)*ph;
|
|
lCx.save();lCx.setLineDash([2,5]);lCx.strokeStyle='rgba(220,240,255,0.35)';lCx.lineWidth=1;
|
|
lCx.beginPath();lCx.moveTo(ix,mt);lCx.lineTo(ix,mt+ph);lCx.stroke();lCx.restore();
|
|
lCx.save();lCx.shadowColor='rgba(200,240,255,0.9)';lCx.shadowBlur=10;lCx.fillStyle='#e0f0ff';
|
|
lCx.beginPath();lCx.arc(ix,iy,3.5,0,Math.PI*2);lCx.fill();lCx.restore();
|
|
// x labels
|
|
lCx.fillStyle='rgba(200,168,75,0.28)';lCx.font='7.5px "Space Mono",monospace';lCx.textAlign='center';
|
|
if(S.mode==='multi'){
|
|
const tw=S.timeWindow;[0,.17,.33,.5,.67,.83,1].forEach(f=>{lCx.fillText((f*tw).toFixed(0)+'d',ml+f*pw,mt+ph+18);});
|
|
}else{
|
|
[0,60,120,180,240,300,360].forEach(deg=>{lCx.fillText(deg+'°',ml+(deg/360)*pw,mt+ph+18);});
|
|
}
|
|
// y labels
|
|
lCx.textAlign='right';
|
|
for(let i=0;i<=4;i++){const fv=S.fmin+(i/4)*(S.fmax-S.fmin),y=mt+ph-(i/4)*ph;lCx.fillText(fv.toFixed(4),ml-5,y+3);}
|
|
// axis titles
|
|
lCx.fillStyle='rgba(200,168,75,0.18)';lCx.font='7px Orbitron,monospace';lCx.textAlign='center';
|
|
lCx.fillText(S.mode==='multi'?'ZEIT (TAGE)':'ORBITPHASE',ml+pw/2,H-4);
|
|
lCx.save();lCx.translate(9,mt+ph/2);lCx.rotate(-Math.PI/2);lCx.fillText('FLUX',0,0);lCx.restore();
|
|
}
|
|
|
|
// UI
|
|
function setMode(m){
|
|
S.mode=m;
|
|
document.querySelectorAll('.mode-btn').forEach(b=>b.classList.toggle('on',b.dataset.mode===m));
|
|
document.getElementById('ctrl-single').classList.toggle('hidden',m!=='single');
|
|
document.getElementById('ctrl-multi').classList.toggle('hidden',m!=='multi');
|
|
document.getElementById('ctrl-binary').classList.toggle('hidden',m!=='binary');
|
|
const hint=document.getElementById('vizHint');
|
|
if(hint){hint.textContent=m==='multi'?'SCROLL = ZOOM':'DRAG = INKLINATION | SCROLL = ZOOM';}
|
|
vCv.style.cursor=m==='multi'?'default':'grab';
|
|
recompute();
|
|
}
|
|
document.getElementById('modeSelect').addEventListener('change',e=>setMode(e.target.value));
|
|
|
|
// Single
|
|
function applySingle(key){
|
|
const p=DB_S[key];if(!p)return;
|
|
S.incl=p.incl;S.dist=p.dist;S.rr=p.rr;S.alb=p.alb;
|
|
document.getElementById('sIncl').value=S.incl;document.getElementById('sDist').value=S.dist;
|
|
document.getElementById('sRad').value=S.rr;document.getElementById('sAlb').value=S.alb;
|
|
document.getElementById('exoName').textContent=p.name;
|
|
document.getElementById('exoTag').textContent=p.tag;
|
|
document.getElementById('exoDesc').textContent=p.desc;
|
|
updSL();recompute();
|
|
}
|
|
function updSL(){
|
|
document.getElementById('vIncl').textContent=S.incl.toFixed(1)+'°';
|
|
document.getElementById('vDist').textContent=S.dist.toFixed(1);
|
|
document.getElementById('vRad').textContent=S.rr.toFixed(3);
|
|
document.getElementById('vAlb').textContent=S.alb.toFixed(2);
|
|
}
|
|
function markCustS(){const p=DB_S.custom;document.getElementById('exoSelect').value='custom';document.getElementById('exoName').textContent=p.name;document.getElementById('exoTag').textContent=p.tag;document.getElementById('exoDesc').textContent=p.desc;}
|
|
document.getElementById('exoSelect').addEventListener('change',e=>applySingle(e.target.value));
|
|
[['sIncl','incl','vIncl',v=>v.toFixed(1)+'°'],['sDist','dist','vDist',v=>v.toFixed(1)],['sRad','rr','vRad',v=>v.toFixed(3)],['sAlb','alb','vAlb',v=>v.toFixed(2)]].forEach(([id,key,lbl,fmt])=>{
|
|
document.getElementById(id).addEventListener('input',function(){S[key]=parseFloat(this.value);document.getElementById(lbl).textContent=fmt(S[key]);markCustS();recompute();});
|
|
});
|
|
|
|
// Multi
|
|
function applyMulti(key){
|
|
const sys=DB_M[key];if(!sys)return;
|
|
S.multiPreset=key;S.timeWindow=sys.timeWindow;S.t=0;
|
|
document.getElementById('sTimeWin').value=S.timeWindow;document.getElementById('vTimeWin').textContent=S.timeWindow;
|
|
document.getElementById('multiName').textContent=sys.name;document.getElementById('multiTag').textContent=sys.tag;document.getElementById('multiDesc').textContent=sys.desc;
|
|
const leg=document.getElementById('planetLegend');leg.innerHTML='';
|
|
sys.planets.forEach((p,i)=>{leg.innerHTML+=`<div class="pl-item"><div class="pl-dot pl-dot-${i%PC.length}"></div>${p.name}</div>`;});
|
|
recompute();
|
|
}
|
|
document.getElementById('multiSelect').addEventListener('change',e=>applyMulti(e.target.value));
|
|
document.getElementById('sTimeWin').addEventListener('input',function(){S.timeWindow=parseFloat(this.value);document.getElementById('vTimeWin').textContent=S.timeWindow;recompute();});
|
|
|
|
// Binary
|
|
function applyBinary(key){
|
|
const b=DB_B[key];if(!b)return;
|
|
S.binIncl=b.incl;S.binDist=b.dist;S.binR2=b.r2;S.binSB=b.sb2;S.binQ=b.q;S.binPeriod=b.period||1.0;
|
|
document.getElementById('sBinIncl').value=S.binIncl;document.getElementById('sBinDist').value=S.binDist;
|
|
document.getElementById('sBinR2').value=S.binR2;document.getElementById('sBinSB').value=S.binSB;
|
|
document.getElementById('binName').textContent=b.name;document.getElementById('binTag').textContent=b.tag;document.getElementById('binDesc').textContent=b.desc;
|
|
updBL();recompute();
|
|
}
|
|
function updBL(){
|
|
document.getElementById('vBinIncl').textContent=S.binIncl.toFixed(1)+'°';
|
|
document.getElementById('vBinDist').textContent=S.binDist.toFixed(1);
|
|
document.getElementById('vBinR2').textContent=S.binR2.toFixed(2);
|
|
document.getElementById('vBinSB').textContent=S.binSB.toFixed(3);
|
|
}
|
|
function markCustB(){const b=DB_B.custom_bin;document.getElementById('binSelect').value='custom_bin';document.getElementById('binName').textContent=b.name;document.getElementById('binTag').textContent=b.tag;document.getElementById('binDesc').textContent=b.desc;}
|
|
document.getElementById('binSelect').addEventListener('change',e=>applyBinary(e.target.value));
|
|
[['sBinIncl','binIncl','vBinIncl',v=>v.toFixed(1)+'°'],['sBinDist','binDist','vBinDist',v=>v.toFixed(1)],['sBinR2','binR2','vBinR2',v=>v.toFixed(2)],['sBinSB','binSB','vBinSB',v=>v.toFixed(3)]].forEach(([id,key,lbl,fmt])=>{
|
|
document.getElementById(id).addEventListener('input',function(){S[key]=parseFloat(this.value);document.getElementById(lbl).textContent=fmt(S[key]);markCustB();recompute();});
|
|
});
|
|
|
|
// Speed + play
|
|
const sSpeed=document.getElementById('sSpeed');
|
|
const vSpeed=document.getElementById('vSpeed');
|
|
function setSpeed(v){
|
|
S.speed=v;
|
|
sSpeed.value=v;
|
|
vSpeed.textContent=v.toFixed(1)+'x';
|
|
document.querySelectorAll('.spd').forEach(b=>b.classList.toggle('on',parseFloat(b.dataset.v)===v));
|
|
}
|
|
document.querySelectorAll('.spd').forEach(b=>{b.addEventListener('click',()=>setSpeed(parseFloat(b.dataset.v)));});
|
|
sSpeed.addEventListener('input',()=>{
|
|
const v=parseFloat(sSpeed.value);
|
|
S.speed=v;
|
|
vSpeed.textContent=v.toFixed(1)+'x';
|
|
document.querySelectorAll('.spd').forEach(b=>b.classList.remove('on'));
|
|
document.querySelectorAll('.spd').forEach(b=>{if(parseFloat(b.dataset.v)===v)b.classList.add('on');});
|
|
});
|
|
const playBtn=document.getElementById('playBtn');
|
|
playBtn.addEventListener('click',()=>{S.playing=!S.playing;playBtn.textContent=S.playing?'PAUSE':'PLAY';});
|
|
|
|
// LC drag
|
|
function lcSet(cx){
|
|
const rect=lCv.getBoundingClientRect(),ml=60,mr=14,pw=rect.width-ml-mr;
|
|
const fr=Math.max(0,Math.min(1,(cx-rect.left-ml)/pw));
|
|
if(S.mode==='multi')S.t=fr*S.timeWindow;else S.M=fr*Math.PI*2;
|
|
}
|
|
lCv.addEventListener('mousedown',e=>{S.draggingLC=true;S.playing=false;playBtn.textContent='PLAY';lcSet(e.clientX);});
|
|
lCv.addEventListener('mousemove',e=>{if(S.draggingLC)lcSet(e.clientX);});
|
|
window.addEventListener('mouseup',()=>{S.draggingLC=false;});
|
|
lCv.addEventListener('touchstart',e=>{e.preventDefault();S.playing=false;playBtn.textContent='PLAY';lcSet(e.touches[0].clientX);},{passive:false});
|
|
lCv.addEventListener('touchmove',e=>{e.preventDefault();lcSet(e.touches[0].clientX);},{passive:false});
|
|
|
|
// LOOP
|
|
function updStats(){
|
|
const cf=Math.max(0,S.mode==='single'?fSingle(S.M,S.incl,S.dist,S.rr,S.alb):S.mode==='binary'?fBinary(S.M):fMulti(S.t));
|
|
document.getElementById('sFlux').textContent=cf.toFixed(5);
|
|
}
|
|
let last=0;
|
|
function loop(t){
|
|
requestAnimationFrame(loop);
|
|
if(S.playing){
|
|
const dt=Math.min(t-last,50)/1000;
|
|
if(S.mode==='multi')S.t=(S.t+dt*S.speed*S.timeWindow/60)%S.timeWindow;
|
|
else S.M=(S.M+dt*S.speed*.55)%(Math.PI*2);
|
|
}
|
|
last=t;
|
|
drawViz();drawLC();updStats();
|
|
}
|
|
|
|
// INIT
|
|
function onResize(){resizeViz();resizeLC();}
|
|
window.addEventListener('resize',onResize);
|
|
applySingle('hd209458b');
|
|
applyMulti('trappist1');
|
|
applyBinary('algol');
|
|
setMode('single');
|
|
onResize();
|
|
requestAnimationFrame(loop);
|
|
// VIZ DRAG (inclination) + SCROLL (zoom)
|
|
vCv.addEventListener('mousedown', e => {
|
|
if (S.mode === 'multi') return;
|
|
S.vizDragging = true;
|
|
S.vizDragStartX = e.clientX;
|
|
S.vizDragStartY = e.clientY;
|
|
S.vizDragStartIncl = S.mode === 'binary' ? S.binIncl : S.incl;
|
|
vCv.style.cursor = 'grabbing';
|
|
});
|
|
window.addEventListener('mouseup', () => {
|
|
if (S.vizDragging) { S.vizDragging = false; vCv.style.cursor = 'grab'; }
|
|
});
|
|
window.addEventListener('mousemove', e => {
|
|
if (!S.vizDragging || S.mode === 'multi') return;
|
|
// horizontal drag -> inclination, 1px = 0.15 deg
|
|
const dx = e.clientX - S.vizDragStartX;
|
|
const dy = e.clientY - S.vizDragStartY;
|
|
// use Y drag for inclination (drag up = higher incl), X drag for phase only
|
|
const delta = -dy * 0.15;
|
|
if (S.mode === 'binary') {
|
|
S.binIncl = Math.max(45, Math.min(90, S.vizDragStartIncl + delta));
|
|
document.getElementById('sBinIncl').value = S.binIncl;
|
|
document.getElementById('vBinIncl').textContent = S.binIncl.toFixed(1) + '°';
|
|
markCustB(); recompute();
|
|
} else {
|
|
const minIncl = S.mode === 'single' ? 60 : 60;
|
|
S.incl = Math.max(minIncl, Math.min(90, S.vizDragStartIncl + delta));
|
|
document.getElementById('sIncl').value = S.incl;
|
|
document.getElementById('vIncl').textContent = S.incl.toFixed(1) + '°';
|
|
if (S.mode === 'single') { markCustS(); }
|
|
recompute();
|
|
}
|
|
});
|
|
// touch drag
|
|
vCv.addEventListener('touchstart', e => {
|
|
e.preventDefault();
|
|
if (S.mode === 'multi') return;
|
|
S.vizDragging = true;
|
|
S.vizDragStartX = e.touches[0].clientX;
|
|
S.vizDragStartY = e.touches[0].clientY;
|
|
S.vizDragStartIncl = S.mode === 'binary' ? S.binIncl : S.incl;
|
|
}, {passive:false});
|
|
vCv.addEventListener('touchend', () => { S.vizDragging = false; }, {passive:true});
|
|
vCv.addEventListener('touchmove', e => {
|
|
e.preventDefault();
|
|
if (!S.vizDragging || S.mode === 'multi') return;
|
|
const dy = e.touches[0].clientY - S.vizDragStartY;
|
|
const delta = -dy * 0.15;
|
|
if (S.mode === 'binary') {
|
|
S.binIncl = Math.max(45, Math.min(90, S.vizDragStartIncl + delta));
|
|
document.getElementById('sBinIncl').value = S.binIncl;
|
|
document.getElementById('vBinIncl').textContent = S.binIncl.toFixed(1) + '°';
|
|
markCustB(); recompute();
|
|
} else {
|
|
S.incl = Math.max(45, Math.min(90, S.vizDragStartIncl + delta));
|
|
document.getElementById('sIncl').value = S.incl;
|
|
document.getElementById('vIncl').textContent = S.incl.toFixed(1) + '°';
|
|
if (S.mode === 'single') markCustS();
|
|
recompute();
|
|
}
|
|
}, {passive:false});
|
|
|
|
document.getElementById('zoomResetBtn').addEventListener('click', () => { S.vizZoom = 1.0; });
|
|
|
|
// scroll wheel -> zoom
|
|
vCv.addEventListener('wheel', e => {
|
|
e.preventDefault();
|
|
const factor = e.deltaY < 0 ? 1.08 : 0.926;
|
|
S.vizZoom = Math.max(0.6, Math.min(3.0, S.vizZoom * factor));
|
|
}, {passive:false});
|
|
|
|
|
|
// SLIDER SCROLL
|
|
document.querySelectorAll('.sl').forEach(sl => {
|
|
sl.addEventListener('wheel', e => {
|
|
e.preventDefault();
|
|
const step = parseFloat(sl.step) || 1;
|
|
const min = parseFloat(sl.min);
|
|
const max = parseFloat(sl.max);
|
|
// scroll up = increase, scroll down = decrease
|
|
const dir = e.deltaY < 0 ? 1 : -1;
|
|
const val = Math.min(max, Math.max(min, parseFloat(sl.value) + dir * step));
|
|
sl.value = val;
|
|
sl.dispatchEvent(new Event('input'));
|
|
}, {passive: false});
|
|
});
|
|
|
|
// TOOLTIPS
|
|
const tip = document.getElementById('tooltip');
|
|
const tipTitle = document.getElementById('tipTitle');
|
|
const tipText = document.getElementById('tipText');
|
|
let tipTimer = null;
|
|
|
|
function showTip(el, x, y) {
|
|
const t = el.dataset.tip, tx = el.dataset.tipText;
|
|
if (!t && !tx) return;
|
|
tipTitle.textContent = t || '';
|
|
tipText.textContent = tx || '';
|
|
// position: keep inside viewport
|
|
const tw = 230, th = 80;
|
|
let lx = x + 14, ly = y + 14;
|
|
if (lx + tw > window.innerWidth - 10) lx = x - tw - 8;
|
|
if (ly + th > window.innerHeight - 10) ly = y - th - 8;
|
|
tip.style.left = lx + 'px';
|
|
tip.style.top = ly + 'px';
|
|
tip.classList.add('vis');
|
|
}
|
|
function hideTip() {
|
|
clearTimeout(tipTimer);
|
|
tip.classList.remove('vis');
|
|
}
|
|
|
|
document.addEventListener('mouseover', e => {
|
|
const el = e.target.closest('[data-tip]');
|
|
if (!el) { hideTip(); return; }
|
|
clearTimeout(tipTimer);
|
|
tipTimer = setTimeout(() => showTip(el, e.clientX, e.clientY), 600);
|
|
});
|
|
document.addEventListener('mousemove', e => {
|
|
if (!tip.classList.contains('vis')) return;
|
|
const tw = 230, th = 80;
|
|
let lx = e.clientX + 14, ly = e.clientY + 14;
|
|
if (lx + tw > window.innerWidth - 10) lx = e.clientX - tw - 8;
|
|
if (ly + th > window.innerHeight - 10) ly = e.clientY - th - 8;
|
|
tip.style.left = lx + 'px';
|
|
tip.style.top = ly + 'px';
|
|
});
|
|
document.addEventListener('mouseout', e => {
|
|
const el = e.target.closest('[data-tip]');
|
|
if (el) hideTip();
|
|
});
|
|
// touch: long press
|
|
let touchTipEl = null, touchTipTimer = null;
|
|
document.addEventListener('touchstart', e => {
|
|
const el = e.target.closest('[data-tip]');
|
|
if (!el) return;
|
|
touchTipEl = el;
|
|
touchTipTimer = setTimeout(() => {
|
|
const t = e.touches[0];
|
|
showTip(el, t.clientX, t.clientY);
|
|
}, 600);
|
|
}, {passive: true});
|
|
document.addEventListener('touchend', () => { clearTimeout(touchTipTimer); hideTip(); }, {passive: true});
|
|
|
|
// HELP MODAL
|
|
const overlay = document.getElementById('helpOverlay');
|
|
document.getElementById('helpBtn').addEventListener('click', () => overlay.classList.add('vis'));
|
|
document.getElementById('helpClose').addEventListener('click', () => overlay.classList.remove('vis'));
|
|
overlay.addEventListener('click', e => { if (e.target === overlay) overlay.classList.remove('vis'); });
|
|
document.addEventListener('keydown', e => { if (e.key === 'Escape') overlay.classList.remove('vis'); });
|
|
|
|
document.querySelectorAll('.help-tab').forEach(tab => {
|
|
tab.addEventListener('click', () => {
|
|
document.querySelectorAll('.help-tab').forEach(t => t.classList.remove('on'));
|
|
document.querySelectorAll('.help-tab-content').forEach(t => t.classList.remove('on'));
|
|
tab.classList.add('on');
|
|
document.getElementById('htab-' + tab.dataset.htab).classList.add('on');
|
|
});
|
|
});
|
|
|
|
</script>
|
|
|
|
<?php require __DIR__ . '/footer.php'; ?>
|
|
|