Mondjahr verbessert

This commit is contained in:
Eskimue
2026-04-22 11:37:49 +02:00
parent 656bd954fa
commit 5141751d0b
+117 -63
View File
@@ -116,6 +116,7 @@ for ($m = 1; $m <= 12; $m++) {
<?php if ($selectedYear !== $currentYear): ?>
<a href="moonyear.php?year=<?= $currentYear ?>" class="btn btn-secondary btn-sm">Aktuelles Jahr</a>
<?php endif; ?>
<a id="moonyearDownload" href="#" download="Mondphasen-<?= htmlspecialchars((string) $selectedYear, ENT_QUOTES, 'UTF-8') ?>.png" class="btn btn-secondary btn-sm" style="display:none">Bild herunterladen (A3)</a>
</form>
<div class="moonyear-wrap">
@@ -124,8 +125,8 @@ for ($m = 1; $m <= 12; $m++) {
<p id="moonyearStatus">Zeichne Mondphasen-Jahresübersicht…</p>
<div class="moonyear-legend">
Neumond &nbsp;·&nbsp; Erstes Viertel &nbsp;·&nbsp; Vollmond &nbsp;·&nbsp; Letztes Viertel
— Zeitzone: <?= htmlspecialchars($chartTimezone, ENT_QUOTES, 'UTF-8') ?>
NM = Neumond &nbsp;·&nbsp; EV = Erstes Viertel &nbsp;·&nbsp; VM = Vollmond &nbsp;·&nbsp; LV = Letztes Viertel
&nbsp;·&nbsp; Zeitzone: <?= htmlspecialchars($chartTimezone, ENT_QUOTES, 'UTF-8') ?>
</div>
</div>
@@ -184,6 +185,8 @@ for ($m = 1; $m <= 12; $m++) {
eventMap[key] = { short: meta.short, color: meta.color, time: e.time };
}
const downloadEl = document.getElementById('moonyearDownload');
const phaseImage = new Image();
phaseImage.src = <?= json_encode($publicBasePath . 'images/moons/moon4geocron.png', JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?>;
@@ -249,120 +252,171 @@ for ($m = 1; $m <= 12; $m++) {
function draw() {
if (!phaseImage.complete || !phaseImage.naturalWidth) return;
const dpr = Math.max(1, window.devicePixelRatio || 1);
// Layout (CSS pixels)
const labelW = 92;
const dayW = 30;
const rowH = 46;
const headerH = 26;
const padTop = 10;
const padBot = 20;
const padRight = 10;
const radius = 11;
const cssW = labelW + 31 * dayW + padRight;
const cssH = padTop + headerH + 12 * rowH + padBot;
// DIN A3 Querformat bei 150 DPI: 420mm × 297mm → 2480 × 1754 px
const W = 2480;
const H = 1754;
const canvas = document.createElement('canvas');
canvas.width = Math.round(cssW * dpr);
canvas.height = Math.round(cssH * dpr);
canvas.width = W;
canvas.height = H;
const ctx = canvas.getContext('2d');
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
// Background
const bg = ctx.createLinearGradient(0, 0, 0, cssH);
// --- Hintergrund ---
const bg = ctx.createLinearGradient(0, 0, 0, H);
bg.addColorStop(0, '#02050a');
bg.addColorStop(1, '#04080f');
ctx.fillStyle = bg;
ctx.fillRect(0, 0, cssW, cssH);
ctx.fillRect(0, 0, W, H);
// Day-number header
ctx.fillStyle = 'rgba(201, 168, 76, 0.65)';
ctx.font = '500 9px "Share Tech Mono", monospace';
// --- Layout ---
const padH = 48; // links/rechts
const padTop = 48;
const titleH = 72; // Titelzeile oben
const headerH = 44; // Zeile mit Tageszahlen
const footerH = 52; // untere Beschriftungszeile
const labelW = 180; // Spalte für Monatsnamen
const gridLeft = padH + labelW;
const gridRight = W - padH;
const gridW = gridRight - gridLeft;
const dayW = gridW / 31;
const gridTop = padTop + titleH + headerH;
const gridBot = H - footerH - 16;
const gridH = gridBot - gridTop;
const rowH = gridH / 12;
const discRadius = Math.min(dayW * 0.38, rowH * 0.33);
// --- Titel ---
ctx.fillStyle = 'rgba(240, 215, 132, 0.95)';
ctx.font = '700 52px Rajdhani, sans-serif';
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.fillText('Mondphasen ' + YEAR, padH + labelW, padTop + titleH / 2);
// Zeitzone rechts im Titel
ctx.fillStyle = 'rgba(201, 168, 76, 0.55)';
ctx.font = '400 22px "Share Tech Mono", monospace';
ctx.textAlign = 'right';
ctx.fillText('Zeitzone: <?= htmlspecialchars($chartTimezone, ENT_QUOTES, 'UTF-8') ?>', W - padH, padTop + titleH / 2);
// --- Tageszahlen-Header ---
const headerY = padTop + titleH + headerH / 2;
ctx.fillStyle = 'rgba(201, 168, 76, 0.70)';
ctx.font = '500 20px "Share Tech Mono", monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const headerY = padTop + headerH / 2;
for (let d = 1; d <= 31; d++) {
ctx.fillText(String(d), labelW + (d - 1) * dayW + dayW / 2, headerY);
ctx.fillText(String(d), gridLeft + (d - 0.5) * dayW, headerY);
}
// Month rows
// Trennlinie unter Header
ctx.strokeStyle = 'rgba(201,168,76,0.25)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(padH, padTop + titleH + headerH);
ctx.lineTo(W - padH, padTop + titleH + headerH);
ctx.stroke();
// --- Monatszeilen ---
for (let m = 1; m <= 12; m++) {
const rowTop = padTop + headerH + (m - 1) * rowH;
const centerY = rowTop + rowH / 2;
const rowTop = gridTop + (m - 1) * rowH;
const centerY = rowTop + rowH * 0.44;
const days = daysInMonth(YEAR, m);
// Alternating row tint
// Alternierende Zeile
if (m % 2 === 0) {
ctx.fillStyle = 'rgba(255,255,255,0.018)';
ctx.fillRect(0, rowTop, cssW, rowH);
ctx.fillRect(padH, rowTop, W - 2 * padH, rowH);
}
// Month name
ctx.fillStyle = 'rgba(240, 217, 132, 0.85)';
ctx.font = '500 11px Rajdhani, sans-serif';
// Monatsname
ctx.fillStyle = 'rgba(240, 217, 132, 0.90)';
ctx.font = '600 28px Rajdhani, sans-serif';
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
ctx.fillText(MONTH_NAMES[m - 1], labelW - 8, centerY - 5);
ctx.fillText(MONTH_NAMES[m - 1], padH + labelW - 14, centerY);
// Moon discs & phase labels
// Mondscheiben & Phasenbeschriftungen
for (let d = 1; d <= 31; d++) {
const x = labelW + (d - 1) * dayW + dayW / 2;
const x = gridLeft + (d - 0.5) * dayW;
if (d > days) {
ctx.fillStyle = 'rgba(255,255,255,0.05)';
ctx.fillRect(x - 7, centerY - 0.5, 14, 1);
ctx.fillStyle = 'rgba(255,255,255,0.04)';
ctx.fillRect(x - 12, centerY - 0.5, 24, 1);
continue;
}
const info = phaseInfoForDay(YEAR, m, d);
drawMoonDisc(ctx, x, centerY - 5, radius, info);
drawMoonDisc(ctx, x, centerY, discRadius, info);
// Phase event label from Python data
const key = m + '-' + d;
if (eventMap[key]) {
const ev = eventMap[key];
const labelY = centerY + discRadius + 6;
ctx.fillStyle = ev.color;
ctx.font = '600 7.5px "Share Tech Mono", monospace';
ctx.font = '700 15px "Share Tech Mono", monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText(ev.short, x, centerY - 5 + radius + 2);
// Time below label
ctx.fillText(ev.short, x, labelY);
if (ev.time) {
ctx.fillStyle = 'rgba(180,180,180,0.55)';
ctx.font = '400 6.5px "Share Tech Mono", monospace';
ctx.fillText(ev.time, x, centerY - 5 + radius + 11);
ctx.fillStyle = 'rgba(180,180,180,0.60)';
ctx.font = '400 13px "Share Tech Mono", monospace';
ctx.fillText(ev.time, x, labelY + 17);
}
}
}
// Row separator
// Zeilentrenner
ctx.strokeStyle = 'rgba(201,168,76,0.07)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, rowTop + rowH);
ctx.lineTo(cssW, rowTop + rowH);
ctx.moveTo(padH, rowTop + rowH);
ctx.lineTo(W - padH, rowTop + rowH);
ctx.stroke();
}
// Vertical separator after label column
ctx.strokeStyle = 'rgba(201,168,76,0.15)';
// Vertikaler Trenner nach Monatsnamen-Spalte
ctx.strokeStyle = 'rgba(201,168,76,0.18)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(labelW, padTop);
ctx.lineTo(labelW, cssH - padBot);
ctx.moveTo(padH + labelW, padTop + titleH);
ctx.lineTo(padH + labelW, gridBot);
ctx.stroke();
// Footer
ctx.fillStyle = 'rgba(240, 215, 132, 0.40)';
ctx.font = '400 8px "Share Tech Mono", monospace';
ctx.textAlign = 'right';
ctx.textBaseline = 'bottom';
ctx.fillText('NM=Neumond EV=Erstes Viertel VM=Vollmond LV=Letztes Viertel', cssW - padRight, cssH - 4);
// --- Fußzeile ---
const fy = H - footerH / 2 - 8;
imageEl.src = canvas.toDataURL('image/png');
statusEl.textContent = 'Mondphasen ' + YEAR + ' — Zeitzone: <?= htmlspecialchars($chartTimezone, ENT_QUOTES, 'UTF-8') ?>';
// Links: Legende
ctx.fillStyle = 'rgba(201, 168, 76, 0.55)';
ctx.font = '400 18px "Share Tech Mono", monospace';
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.fillText('NM = Neumond · EV = Erstes Viertel · VM = Vollmond · LV = Letztes Viertel', padH, fy);
// Rechts: Museumssignatur
ctx.fillStyle = 'rgba(240, 215, 132, 0.70)';
ctx.font = '500 20px Rajdhani, sans-serif';
ctx.textAlign = 'right';
ctx.fillText('Astronomiemuseum der Sternwarte Sonneberg', W - padH, fy);
// Trennlinie über Fußzeile
ctx.strokeStyle = 'rgba(201,168,76,0.20)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(padH, H - footerH - 16);
ctx.lineTo(W - padH, H - footerH - 16);
ctx.stroke();
// --- Bild ausgeben ---
const dataUrl = canvas.toDataURL('image/png');
imageEl.src = dataUrl;
if (downloadEl) {
downloadEl.href = dataUrl;
downloadEl.style.display = '';
}
statusEl.textContent = 'Mondphasen ' + YEAR + ' (DIN A3, 150 DPI) — Zeitzone: <?= htmlspecialchars($chartTimezone, ENT_QUOTES, 'UTF-8') ?>';
}
phaseImage.onload = draw;