Mondjahr verbessert
This commit is contained in:
+117
-63
@@ -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 · Erstes Viertel · Vollmond · Letztes Viertel
|
||||
— Zeitzone: <?= htmlspecialchars($chartTimezone, ENT_QUOTES, 'UTF-8') ?>
|
||||
NM = Neumond · EV = Erstes Viertel · VM = Vollmond · LV = Letztes Viertel
|
||||
· 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;
|
||||
|
||||
Reference in New Issue
Block a user