Sonnensystem-Review: Finsternis-Geometrie-Fix + Vergleichsansichten + Anleitung + Photon-Skala

Aufgaben (observations.json):
- Finsternis-Bug behoben: Mond wurde siderisch statt synodisch positioniert -> Konstellation schief. Jetzt exakt Neumond (solar)/Vollmond (lunar) via moonPhaseTimeDays(). Sim-Detektor bestaetigt (Badge) SONNEN-/MONDFINSTERNIS.
- Pol-Aufgaben (Polartag/Polarnacht): Kamera -> neue erdzentrierte Draufsicht earth-top (Sonne geht am Pol nie unter/auf sichtbar).
- Mondphasen: Start bei Neumond (moonPhaseStart) passend zur Reihenfolge Neumond->zunehmend->Vollmond->abnehmend.
- Vergleichsansichten (Strahlen-Steilheit, Tag-Nacht-Gleiche, ohne Achsneigung): neue nahe Seitenansicht earth-side + Sommer/Winter- bzw. Maerz/Sept-Phasen + klare watchHints.
- "Was bewegt sich worum": Antwort praezisiert (Mond um die Erde).
- Apfel-Vergleich: 109 x (Leerzeichen), Erde = Mohnkorn (~1 mm), lange Striche raus, Distraktor Murmel angepasst.
- Photon: schwankende 8:20 erwaehnt, watchHint "mind. 30 s zaehlen + hochrechnen", Speed watchbarer.

game.html:
- Graue/unsichtbare Fact-/Onboarding-Texte: dunkle Lesefarbe gesetzt.
- Kameras earth-top + earth-side; obsApplyState-Fallback fuer button-lose Kameras.
- moonPhaseTimeDays(new|full) (synodisch, Anker Vollmond 3.1.).
- Anleitung-Button (Intro+Aufloesung) + Overlay: erklaert das Bedienfeld Schritt fuer Schritt.
- Photon-Skala unten: 8 Segmente (1/min), fuellt mit Fortschritt; Photon pulsiert die ganze Strecke.

Getestet mit Playwright: 0 JS-Fehler, Finsternis-Badges korrekt, Photon-Skala fuellt, alle DOM-Elemente da.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 12:04:53 +02:00
parent c950f954d1
commit 5b0505b140
2 changed files with 148 additions and 22 deletions
+117 -5
View File
@@ -516,6 +516,35 @@
}
#obs-close:active { background: #e2e8f0; }
/* Anleitung-Overlay */
.sn-anl-card {
background: #fff; color: #2a2440; border-radius: 16px;
max-width: 560px; width: 100%; max-height: 88vh; overflow-y: auto;
box-shadow: 0 16px 48px rgba(20,12,60,0.4); padding: 22px 24px;
text-align: left; animation: snPop 0.35s cubic-bezier(.18,1.4,.6,1);
}
.sn-anl-card h3 { margin: 0 0 4px; color: #3d2c8d; font-size: 1.2rem; text-align: center; font-weight: 800; }
.sn-anl-lead { text-align: center; color: #6b6785; font-size: 0.85rem; margin: 0 0 14px; }
.sn-anl-sec { border-top: 1px solid #ece9f5; padding: 10px 0; }
.sn-anl-sec:first-of-type { border-top: none; padding-top: 2px; }
.sn-anl-sec h4 { margin: 0 0 3px; font-size: 0.97rem; color: #2a2440; font-weight: 700; }
.sn-anl-sec p { margin: 0; font-size: 0.85rem; line-height: 1.5; color: #4a4660; }
/* Photon-Lichtlaufzeit-Skala (8 Segmente = 8 Minuten) */
#photon-scale {
position: fixed; left: 50%; transform: translateX(-50%); bottom: 14px;
z-index: 30; width: min(440px, 90vw);
background: rgba(15,10,40,0.82); border-radius: 12px; padding: 8px 12px 6px;
color: #fde68a; font-size: 12px; text-align: center;
box-shadow: 0 6px 20px rgba(0,0,0,0.4);
}
#photon-scale .ph-label { font-weight: 700; margin-bottom: 5px; color: #fff; }
#photon-scale .ph-track { position: relative; height: 14px; border-radius: 7px; background: rgba(255,255,255,0.12); overflow: hidden; }
#photon-scale .ph-fill { position: absolute; left: 0; top: 0; bottom: 0; width: 0%; background: linear-gradient(90deg, #f59e0b, #fde68a); }
#photon-scale .ph-ticks { position: absolute; inset: 0; pointer-events: none;
background: repeating-linear-gradient(90deg, transparent 0, transparent calc(12.5% - 1.5px), rgba(255,255,255,0.5) calc(12.5% - 1.5px), rgba(255,255,255,0.5) 12.5%); }
#photon-scale .ph-mins { display: flex; justify-content: space-between; font-size: 10px; color: #cbd5e1; margin-top: 3px; }
#obs-progress {
font-size: 12px; color: #6b7280; font-weight: 600;
margin-bottom: 4px;
@@ -694,6 +723,7 @@
}
.sn-onboard-visual svg { width: 100%; max-width: 460px; height: auto; }
.sn-onboard-text {
color: #2a2440;
font-size: 0.95rem; line-height: 1.55;
color: #1f1a3a; margin-bottom: 18px;
}
@@ -724,7 +754,7 @@
font-size: 0.72rem; letter-spacing: 0.18em; text-transform: uppercase;
font-weight: 700; color: #5a8a5e; margin-bottom: 4px;
}
.sn-ff-text { font-size: 0.96rem; line-height: 1.55; margin-bottom: 18px; }
.sn-ff-text { font-size: 0.96rem; line-height: 1.55; margin-bottom: 18px; color: #2a2440; }
.sn-ff-text b { color: #3d2c8d; }
/* Endscreen Stars + Achievements */
@@ -760,6 +790,11 @@
<div id="label-earth" class="body-label long-arrow">🌍 Erde</div>
<div id="label-moon" class="body-label">🌙 Mond</div>
<div id="label-photon" class="body-label" style="display:none; background:rgba(253,184,19,0.92); color:#1e293b">💡 Photon</div>
<div id="photon-scale" style="display:none">
<div class="ph-label">☀️ Sonne → 🌍 Erde · Lichtlaufzeit ≈ 8 Minuten</div>
<div class="ph-track"><div class="ph-fill" id="ph-fill"></div><div class="ph-ticks"></div></div>
<div class="ph-mins"><span>0</span><span>2</span><span>4</span><span>6</span><span>8 Min</span></div>
</div>
<div id="month-labels" style="position:absolute; inset:0; pointer-events:none"></div>
<div id="date-banner">📆 1. Jan</div>
@@ -979,6 +1014,21 @@
</div>
</div>
<div id="sn-anleitung" class="sn-overlay" hidden role="dialog" aria-label="Anleitung">
<div class="sn-anl-card">
<h3>📖 Anleitung — das Bedienfeld</h3>
<p class="sn-anl-lead">Was die einzelnen Bereiche der Steuerung machen.</p>
<div class="sn-anl-sec"><h4>🔍 Maßstab</h4><p><b>Symbolisch</b> = alles groß und gut sichtbar (nicht maßstabsgetreu). <b>Reale Größen</b> = Sonne, Erde und Mond in echtem Größenverhältnis. <b>Reale Abstände</b> = zusätzlich der echte, riesige Abstand — die Erde wird zu einem winzigen Punkt.</p></div>
<div class="sn-anl-sec"><h4>🎬 Szenario</h4><p><b>Normal</b> = der übliche Lauf. <b>🌞 Sonnenfinsternis</b> und <b>🌑 Mondfinsternis</b> stellen die Sonne, Erde und Mond exakt in eine Reihe, damit du die Konstellation genau siehst.</p></div>
<div class="sn-anl-sec"><h4>📷 Kamera &amp; 🎯 Ziel</h4><p>Oben wählst du den Blickwinkel: <b>Frei</b>, <b>Übersicht</b>, <b>Oben</b>, <b>Seite</b>. Die <b>🎯-Zeile</b> zentriert auf einen Körper: ☀️ Sonne, 🌍🌙 Erde+Mond, 🌎 Erde nah, 🌙 Mond. In Aufgaben wird die passende Ansicht automatisch gesetzt.</p></div>
<div class="sn-anl-sec"><h4>⏱️ Geschwindigkeit &amp; Pause</h4><p>Der Regler geht von 🐢 langsam bis 🚀 schnell. Mit <b>⏸ / ▶</b> hältst du an oder läufst weiter — gut, um eine Konstellation in Ruhe anzuschauen.</p></div>
<div class="sn-anl-sec"><h4>📅 Datum-Sprünge</h4><p><b>☀️ Sommer</b>, <b>❄️ Winter</b>, <b>🌷 Frühling</b>, <b>🍂 Herbst</b> springen direkt zu den Sonnenwenden und Tag-und-Nacht-Gleichen — ideal zum Vergleichen der Jahreszeiten.</p></div>
<div class="sn-anl-sec"><h4>✨ Visualisierungen</h4><p><b>Mondspur</b> zeichnet die Mondbahn als Linie. <b>☀️ Sonnenstrahlen</b> zeigt, wie steil oder flach das Licht auftrifft. <b>💡 Photon</b> lässt ein Lichtteilchen von der Sonne zur Erde laufen. <b>Monatsmarken</b> zeigen den Jahresfortschritt am Bahnrand.</p></div>
<div class="sn-anl-sec"><h4>🔎 Detail-Fenster (Lupe)</h4><p>Zeigt das gewählte Objekt vergrößert oben rechts — z. B. den Mondschatten bei einer Finsternis oder die <b>🇦🇹 Sicht aus Österreich</b> (Sonne und Mond am Himmel über Wien).</p></div>
<div class="sn-anl-sec"><h4>📚 Aufgaben</h4><p><b>▶ Sequenz starten</b> spielt eine kurze Animation ab, dann kommt die Frage und die Auflösung. Mit <b>↻ Nochmal</b> kannst du jede Sequenz wiederholen, so oft du willst.</p></div>
<button id="snAnlClose" class="sn-btn sn-btn-primary" type="button" style="margin-top:16px;width:100%">Verstanden →</button>
</div>
</div>
<div id="obs-overlay" class="obs-hidden">
<div id="obs-card">
<button id="obs-close" type="button"></button>
@@ -988,6 +1038,7 @@
<h2 id="obs-title">Titel</h2>
<p id="obs-intro">Hinführung</p>
<div class="obs-actions">
<button id="obs-anleitung" class="obs-secondary" style="font-size:12px">📖 Anleitung</button>
<button id="obs-skip-restart" class="obs-secondary" style="font-size:12px">↻ Aufgaben abbrechen und neu starten</button>
<button id="obs-prev" class="obs-secondary" style="font-size:12px">← Zurück zur letzten Übung</button>
<button id="obs-start" class="obs-primary">▶ Sequenz starten</button>
@@ -1018,6 +1069,7 @@
<p id="obs-sim-hint" class="obs-small"></p>
</details>
<div class="obs-actions">
<button id="obs-anleitung-2" class="obs-secondary" style="font-size:12px">📖 Anleitung</button>
<button id="obs-replay-after" class="obs-secondary">↻ Sequenz nochmal</button>
<button id="obs-next" class="obs-primary">Nächste Aufgabe →</button>
</div>
@@ -2110,7 +2162,7 @@ document.querySelectorAll('[data-scene]').forEach(btn => {
moonTiltDeg = 0;
tiltLabel.textContent = '0.0';
moonInclination.rotation.x = 0;
timeDays = MOON_MONTH_DAYS / 2; // Neumond: Mond zwischen Sonne und Erde
timeDays = moonPhaseTimeDays('new'); // exakt Neumond: Mond genau zwischen Sonne und Erde
paused = true;
pauseBtn.textContent = '▶';
clearTrail();
@@ -2119,7 +2171,7 @@ document.querySelectorAll('[data-scene]').forEach(btn => {
moonTiltDeg = 0;
tiltLabel.textContent = '0.0';
moonInclination.rotation.x = 0;
timeDays = 0; // Vollmond: Erde zwischen Sonne und Mond
timeDays = moonPhaseTimeDays('full'); // exakt Vollmond: Erde genau zwischen Sonne und Mond
paused = true;
pauseBtn.textContent = '▶';
clearTrail();
@@ -2140,6 +2192,22 @@ function applyCameraMode(mode) {
if (mode === 'top') {
camera.position.set(...SCALE.camTop);
controls.target.set(0, 0, 0);
} else if (mode === 'earth-top') {
// Draufsicht auf die Erde (über dem Nordpol) — für Polartag/Polarnacht:
// man sieht, dass der Polbereich bei einer ganzen Erddrehung nie in den
// Schatten (Polartag) bzw. nie ins Licht (Polarnacht) gerät.
earthPivot.getWorldPosition(_earthPos);
const dTop = EARTH_RADIUS * 4.2;
camera.position.set(_earthPos.x, _earthPos.y + dTop, _earthPos.z + 0.001);
controls.target.copy(_earthPos);
} else if (mode === 'earth-side') {
// Nahe Seitenansicht der Erde, quer zur Sonnenrichtung — zeigt, wie STEIL
// oder FLACH die Sonnenstrahlen auf die Halbkugeln treffen und wo die
// Tag-Nacht-Grenze verläuft (Äquinoktium: senkrecht durch beide Pole).
earthPivot.getWorldPosition(_earthPos);
const dES = EARTH_RADIUS * 4;
camera.position.set(_earthPos.x, EARTH_RADIUS * 0.35, _earthPos.z + dES);
controls.target.copy(_earthPos);
} else if (mode === 'side') {
camera.position.set(...SCALE.camSide);
controls.target.set(0, 0, 0);
@@ -2583,6 +2651,19 @@ const EPOCH = new Date(CURRENT_YEAR, 0, 1);
const MOON_REF_FULLMOON = new Date(2026, 0, 3);
const MOON_EPOCH_OFFSET_DAYS = (EPOCH - MOON_REF_FULLMOON) / 86400000;
// Liefert den timeDays-Wert für exakten Neumond ('new') bzw. Vollmond ('full')
// nahe der Referenz `near` (Default: aktueller timeDays). Nutzt die SYNODISCHE
// Phasen-Formel (moonOrbit.rotation.y: 0=Vollmond, π=Neumond), damit Finsternis-
// Szenen und der Mondphasen-Start die ECHTE Konstellation treffen. (Bugfix: die
// Finsternis-Buttons setzten den Mond vorher über den siderischen Monat → schief.)
function moonPhaseTimeDays(phase, near) {
const base = (typeof near === 'number') ? near : timeDays;
const targetFrac = (phase === 'new') ? 0.5 : 0.0; // rotation.y-Anteil
const raw = targetFrac * MOON_SYNODIC_DAYS - MOON_EPOCH_OFFSET_DAYS;
const k = Math.round((base - raw) / MOON_SYNODIC_DAYS);
return raw + k * MOON_SYNODIC_DAYS;
}
function dayToDate(day) {
const d = new Date(EPOCH.getTime() + day * 86400000);
return d;
@@ -3198,6 +3279,8 @@ document.getElementById('photon-toggle').addEventListener('change', (e) => {
photonLineEarth.visible = false;
photonLineMoon.visible = false;
photonLabel.style.display = 'none';
var _psOff = document.getElementById('photon-scale');
if (_psOff) _psOff.style.display = 'none';
}
});
@@ -3231,6 +3314,8 @@ function updatePhoton() {
photonToEarth.visible = false;
photonToMoon.visible = false;
photonLabel.style.display = 'none';
const _psFast = document.getElementById('photon-scale');
if (_psFast) _psFast.style.display = 'none';
photonLineEarth.visible = true;
photonLineMoon.visible = true;
photonLineEarth.geometry.setFromPoints([
@@ -3253,10 +3338,18 @@ function updatePhoton() {
const totalToEarth = PHOTON_TRAVEL_DAYS_AT_1X;
const progress = Math.min(elapsed / totalToEarth, 1);
// Photon zur Erde — pulsiert im ersten Viertel des Fluges (progress < 0.25)
// 8-Segment-Skala unten aktualisieren (1 Segment = 1 Minute Lichtlaufzeit).
const _ps = document.getElementById('photon-scale');
if (_ps) {
_ps.style.display = 'block';
const _pf = document.getElementById('ph-fill');
if (_pf) _pf.style.width = (progress * 100).toFixed(1) + '%';
}
// Photon zur Erde — pulsiert die GANZE Strecke, damit es sichtbar bleibt.
photonToEarth.visible = true;
photonToEarth.position.lerpVectors(new THREE.Vector3(0,0,0), _photonEarthPos, progress);
if (progress < 0.25) {
if (progress < 1) {
const t = (Date.now() % 600) / 600;
const pulse = 1.0 + 0.7 * Math.sin(t * Math.PI * 2);
photonToEarth.scale.setScalar(pulse);
@@ -3541,6 +3634,7 @@ function obsApplyState(s) {
if (s.camera) {
const btn = document.querySelector('[data-cam="' + s.camera + '"]');
if (btn) btn.click();
else if (typeof applyCameraMode === 'function') applyCameraMode(s.camera);
}
// Date NUR setzen wenn keine Eclipse-Scene aktiv ist (sonst würde
// das Eclipse-Datum überschrieben → keine echte Konstellation mehr).
@@ -3563,6 +3657,12 @@ function obsApplyState(s) {
timeDays = (target - EPOCH) / 86400000;
if (typeof clearTrail === 'function') clearTrail();
}
// Mondphase am Start erzwingen (Mondphasen-Aufgabe beginnt bei Neumond,
// passend zur Reihenfolge Neumond→zunehmend→Vollmond→abnehmend).
if (s.moonPhaseStart === 'new' || s.moonPhaseStart === 'full') {
timeDays = moonPhaseTimeDays(s.moonPhaseStart, timeDays);
if (typeof clearTrail === 'function') clearTrail();
}
if (typeof s.speed === 'number') {
speedSlider.value = s.speed;
applySpeedFromSlider();
@@ -3935,6 +4035,18 @@ function obsOpen() {
document.getElementById('obs-open-btn').addEventListener('click', obsOpen);
document.getElementById('obs-close').addEventListener('click', obsClose);
document.getElementById('obs-close-end').addEventListener('click', obsClose);
// 📖 Anleitung-Overlay (erklärt das Bedienfeld Schritt für Schritt)
(function(){
var anl = document.getElementById('sn-anleitung');
function openAnl(){ if (anl) anl.hidden = false; }
function closeAnl(){ if (anl) anl.hidden = true; }
['obs-anleitung','obs-anleitung-2'].forEach(function(id){
var b = document.getElementById(id); if (b) b.addEventListener('click', openAnl);
});
var c = document.getElementById('snAnlClose'); if (c) c.addEventListener('click', closeAnl);
if (anl) anl.addEventListener('click', function(e){ if (e.target === anl) closeAnl(); });
})();
document.getElementById('obs-start').addEventListener('click', obsStartSequence);
document.getElementById('obs-replay').addEventListener('click', () => {
const t = obsState.tasks[obsState.index];