diff --git a/public/monatsvorhersage_old.php b/public/monatsvorhersage_old.php index 6117904..22e8a8b 100644 --- a/public/monatsvorhersage_old.php +++ b/public/monatsvorhersage_old.php @@ -1062,6 +1062,8 @@ $chartConfig = [ ], 'localTimeLabel' => $chartLocalTime->format('d.m.Y H:i') . ' ' . $chartTimezone, 'utcTimeIso' => $chartUtcTime->format(DateTimeInterface::ATOM), + 'monthSkyHour' => $monthTime['hour'], + 'monthSkyMinute' => $monthTime['minute'], 'starLimit' => $starDisplayMagnitudeLimit, 'starLabelLimit' => $starLabelMagnitudeLimit, 'messierLimit' => $messierDisplayMagnitudeLimit, @@ -1074,6 +1076,7 @@ $constellationLinesJson = json_encode($constellationLines, JSON_UNESCAPED_UNICOD $messierDataJson = json_encode($messierData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $planetDataJson = json_encode($planetData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $moonDataJson = json_encode($moonData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); +$moonPhaseEventsJson = json_encode($moonPhaseEvents, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $starDataErrorJson = json_encode($starDataError, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?> @@ -1152,6 +1155,25 @@ $starDataErrorJson = json_encode($starDataError, JSON_UNESCAPED_UNICODE | JSON_U +
+
+
+

Mondphasen Im

+

Eine Monatszeile vom ersten bis zum letzten Tag mit der Mondtextur aus moon4geocron.png.

+
+
+ +
+
+ +
+
+ + +
+
@@ -1297,6 +1319,29 @@ $starDataErrorJson = json_encode($starDataError, JSON_UNESCAPED_UNICODE | JSON_U aspect-ratio: 5 / 3; } +.monatsphase-stage { + padding-top: 8px; +} + +.monatsphase-strip-wrap { + width: 100%; + overflow-x: auto; + padding-bottom: 4px; +} + +#monatsphasenCanvas { + display: block; + width: min(100%, 1200px); + min-width: 920px; + height: auto; + margin: 0 auto; + border-radius: 20px; + background: + radial-gradient(circle at 50% 0%, rgba(92, 122, 186, 0.16), transparent 42%), + linear-gradient(180deg, rgba(3, 6, 12, 0.98) 0%, rgba(5, 9, 16, 0.99) 100%); + box-shadow: inset 0 0 0 1px rgba(140, 165, 220, 0.12); +} + .monatskarte-footer { padding-top: 0; } @@ -1321,8 +1366,198 @@ const MONTH_CHART_CONSTELLATIONS = ; const MONTH_CHART_MESSIER = ; const MONTH_CHART_PLANETS = ; const MONTH_CHART_MOON = ; +const MONTH_PHASE_EVENTS = ; const MONTH_CHART_ERROR = ; +(function () { + const canvas = document.getElementById('monatsphasenCanvas'); + const statusEl = document.getElementById('monatsphasenStatus'); + + if (!canvas || !statusEl || !window.Astronomy || !MONTH_CHART_CONFIG) { + return; + } + + const ctx = canvas.getContext('2d'); + const phaseImage = new Image(); + phaseImage.src = ; + + function clamp01(value) { + return Math.max(0, Math.min(1, value)); + } + + function daysInMonth(year, month) { + return new Date(year, month, 0).getDate(); + } + + function phaseInfoForDay(year, month, day) { + const monthHour = Number(MONTH_CHART_CONFIG?.monthSkyHour ?? 12); + const monthMinute = Number(MONTH_CHART_CONFIG?.monthSkyMinute ?? 0); + const localPhaseTime = new Date(year, month - 1, day, monthHour, monthMinute, 0); + const phaseAngle = Number(Astronomy.MoonPhase(localPhaseTime)); + const normalizedPhase = ((phaseAngle % 360) + 360) % 360; + + return { + phaseAngleDeg: normalizedPhase, + illumination: clamp01((1 - Math.cos(normalizedPhase * Math.PI / 180)) / 2), + waxing: normalizedPhase <= 180, + }; + } + + function resizeCanvas() { + const cssWidth = Math.max(920, Math.floor(canvas.clientWidth || 1200)); + const cssHeight = 220; + const dpr = Math.max(1, window.devicePixelRatio || 1); + + canvas.width = Math.floor(cssWidth * dpr); + canvas.height = Math.floor(cssHeight * dpr); + canvas.style.height = cssHeight + 'px'; + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + draw(); + } + + function drawMoonDisc(x, y, radius, phaseInfo) { + const phaseRad = phaseInfo.phaseAngleDeg * Math.PI / 180; + const cosPhase = Math.cos(phaseRad); + + ctx.save(); + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.clip(); + + ctx.fillStyle = 'rgba(8, 10, 16, 0.96)'; + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.fill(); + + ctx.globalAlpha = 0.96; + ctx.drawImage(phaseImage, x - radius, y - radius, radius * 2, radius * 2); + ctx.globalAlpha = 1; + + ctx.fillStyle = 'rgba(8, 10, 16, 0.96)'; + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.fill(); + + ctx.save(); + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.clip(); + + if (phaseInfo.illumination >= 0.999) { + ctx.drawImage(phaseImage, x - radius, y - radius, radius * 2, radius * 2); + } else if (phaseInfo.illumination > 0.001) { + ctx.beginPath(); + for (let i = 0; i <= 72; i++) { + const t = -radius + (2 * radius * i / 72); + const xr = Math.sqrt(Math.max(0, radius * radius - t * t)); + const xt = (phaseInfo.waxing ? cosPhase : -cosPhase) * xr; + const px = x + xt; + const py = y + t; + if (i === 0) ctx.moveTo(px, py); + else ctx.lineTo(px, py); + } + for (let i = 72; i >= 0; i--) { + const t = -radius + (2 * radius * i / 72); + const xr = Math.sqrt(Math.max(0, radius * radius - t * t)); + const px = x + (phaseInfo.waxing ? xr : -xr); + const py = y + t; + ctx.lineTo(px, py); + } + ctx.closePath(); + ctx.clip(); + ctx.drawImage(phaseImage, x - radius, y - radius, radius * 2, radius * 2); + } + + ctx.restore(); + ctx.restore(); + + ctx.strokeStyle = 'rgba(255, 255, 255, 0.16)'; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.stroke(); + } + + function draw() { + if (!phaseImage.complete) { + return; + } + + const width = canvas.width / Math.max(1, window.devicePixelRatio || 1); + const height = canvas.height / Math.max(1, window.devicePixelRatio || 1); + const year = Number(MONTH_CHART_CONFIG.year); + const month = Number(MONTH_CHART_CONFIG.month); + const monthName = String(MONTH_CHART_CONFIG.monthName || ''); + const totalDays = daysInMonth(year, month); + + ctx.clearRect(0, 0, width, height); + + const bg = ctx.createLinearGradient(0, 0, 0, height); + bg.addColorStop(0, '#02050a'); + bg.addColorStop(1, '#04080f'); + ctx.fillStyle = bg; + ctx.fillRect(0, 0, width, height); + + ctx.fillStyle = 'rgba(240, 215, 132, 0.96)'; + ctx.font = '600 16px Rajdhani, sans-serif'; + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + ctx.fillText('Mondphasen im ' + monthName + ' ' + year, 28, 18); + + const leftPad = 22; + const rightPad = 22; + const usableWidth = width - leftPad - rightPad; + const spacing = usableWidth / totalDays; + const radius = Math.max(11, Math.min(18, spacing * 0.42)); + const centerY = 82; + + ctx.fillStyle = 'rgba(255, 255, 255, 0.72)'; + ctx.font = '500 10px \"Share Tech Mono\", monospace'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'top'; + + for (let day = 1; day <= totalDays; day++) { + const x = leftPad + spacing * (day - 0.5); + drawMoonDisc(x, centerY, radius, phaseInfoForDay(year, month, day)); + ctx.fillText(String(day), x, centerY + radius + 8); + } + + ctx.font = '600 12px Rajdhani, sans-serif'; + ctx.fillStyle = 'rgba(225, 232, 246, 0.94)'; + + for (const event of Array.isArray(MONTH_PHASE_EVENTS) ? MONTH_PHASE_EVENTS : []) { + const parts = String(event.local_date || '').split('.'); + if (parts.length < 3) { + continue; + } + + const day = Number(parts[0]); + if (!Number.isFinite(day) || day < 1 || day > totalDays) { + continue; + } + + const x = leftPad + spacing * (day - 0.5); + ctx.fillText(String(event.label || ''), x, 158); + ctx.fillStyle = 'rgba(182, 196, 224, 0.94)'; + ctx.fillText(day + '.' + month + '.', x, 175); + ctx.fillStyle = 'rgba(225, 232, 246, 0.94)'; + } + + statusEl.textContent = 'Mondphasenleiste fuer ' + monthName + ' ' + year; + } + + phaseImage.onload = resizeCanvas; + phaseImage.onerror = function () { + statusEl.textContent = 'Mondgrafik konnte nicht geladen werden.'; + }; + + window.addEventListener('resize', resizeCanvas); + + if (phaseImage.complete) { + resizeCanvas(); + } +})(); + (function () { const canvas = document.getElementById('monatskarteCanvas'); const statusEl = document.getElementById('monatskarteStatus'); diff --git a/todo.md b/todo.md index 9b566f2..dbee541 100644 --- a/todo.md +++ b/todo.md @@ -1,6 +1,5 @@ # Monatsvorschau TODO -- Meteorstroeme aufnehmen: aktiver Zeitraum, Maximum, geschaetzte Stundenrate - Besonders enge Mond-Stern-Begegnungen heller Sterne ergaenzen, auch ohne Bedeckung - Helle Stern-/Planeten-Begegnungen aufnehmen, z. B. Venus nahe Regulus oder Mars nahe Spica - Mond nahe Plejaden und Mond nahe Praesepe als eigene Ereignisse pruefen