Mondphase eingebaut

This commit is contained in:
2026-04-29 08:56:26 +02:00
parent 35086a3441
commit 4abe5f0b97
2 changed files with 166 additions and 10 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

+166 -10
View File
@@ -22,6 +22,14 @@ const ASTRONOMY_LIB_URL = (() => {
}
})();
const MOON_IMAGE_URL = (() => {
try {
return new URL("./moon_small.png", import.meta.url).toString();
} catch (error) {
return null;
}
})();
class SternwarteCard extends HTMLElement {
constructor() {
super();
@@ -31,10 +39,13 @@ class SternwarteCard extends HTMLElement {
};
this._pendingRender = false;
this._astronomyLoadPromise = null;
this._moonTexture = null;
this._moonTexturePromise = null;
}
connectedCallback() {
this._ensureAstronomy();
this._ensureMoonTexture();
this._ensureClockTimer();
}
@@ -121,6 +132,29 @@ class SternwarteCard extends HTMLElement {
return this._astronomyLoadPromise;
}
_ensureMoonTexture() {
if (this._moonTexture) return Promise.resolve(this._moonTexture);
if (this._moonTexturePromise) return this._moonTexturePromise;
if (!MOON_IMAGE_URL) return Promise.resolve(null);
this._moonTexturePromise = new Promise((resolve) => {
const image = new Image();
image.addEventListener("load", () => {
this._moonTexture = image;
this._moonTexturePromise = null;
if (this._hass) this._requestRender();
resolve(image);
}, { once: true });
image.addEventListener("error", () => {
this._moonTexturePromise = null;
resolve(null);
}, { once: true });
image.src = MOON_IMAGE_URL;
});
return this._moonTexturePromise;
}
_requestRender() {
if (this._isScheduleEditing()) {
this._pendingRender = true;
@@ -500,7 +534,7 @@ class SternwarteCard extends HTMLElement {
const astronomy = this._astronomy();
const observer = this._astroObserver();
if (!astronomy || !observer) {
return { altitude: Number.NEGATIVE_INFINITY, illumination: 0 };
return { altitude: Number.NEGATIVE_INFINITY, illumination: 0, phaseAngle: 0 };
}
try {
@@ -508,16 +542,108 @@ class SternwarteCard extends HTMLElement {
const equator = astronomy.Equator(bodyMoon, date, observer, true, true);
const horizon = astronomy.Horizon(date, observer, equator.ra, equator.dec, "normal");
const illumination = astronomy.Illumination(bodyMoon, date);
const phaseAngle = Number(astronomy.MoonPhase(date)) || 0;
return {
altitude: (Number(horizon?.altitude) || 0) * Math.PI / 180,
illumination: Math.max(0, Math.min(1, Number(illumination?.phase_fraction) || 0)),
phaseAngle: ((phaseAngle % 360) + 360) % 360,
};
} catch (error) {
return { altitude: Number.NEGATIVE_INFINITY, illumination: 0 };
return { altitude: Number.NEGATIVE_INFINITY, illumination: 0, phaseAngle: 0 };
}
}
_moonVisualState() {
const moon = this._moonStateAt(new Date());
const phaseAngle = moon.phaseAngle ?? 0;
const waxing = phaseAngle <= 180;
const radians = phaseAngle * Math.PI / 180;
return {
illumination: Math.max(0, Math.min(1, moon.illumination || 0)),
illuminationPercent: Math.round((moon.illumination || 0) * 100),
waxing,
phaseAngle,
cosPhase: Math.cos(radians),
};
}
_renderMoonPhaseDataUrl(moonVisual, size = 64) {
const texture = this._moonTexture;
if (!texture) return "";
const canvas = document.createElement("canvas");
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext("2d");
if (!ctx) return "";
const radius = size / 2 - 1;
const x = size / 2;
const y = size / 2;
const cosPhase = Number(moonVisual.cosPhase) || 0;
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.98;
ctx.drawImage(texture, 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 (moonVisual.illumination >= 0.999) {
ctx.drawImage(texture, x - radius, y - radius, radius * 2, radius * 2);
} else if (moonVisual.illumination > 0.001) {
ctx.beginPath();
for (let i = 0; i <= 72; i += 1) {
const t = -radius + (2 * radius * i) / 72;
const xr = Math.sqrt(Math.max(0, radius * radius - t * t));
const xt = (moonVisual.waxing ? cosPhase : -cosPhase) * xr;
if (i === 0) {
ctx.moveTo(x + xt, y + t);
} else {
ctx.lineTo(x + xt, y + t);
}
}
for (let i = 72; i >= 0; i -= 1) {
const t = -radius + (2 * radius * i) / 72;
const xr = Math.sqrt(Math.max(0, radius * radius - t * t));
ctx.lineTo(x + (moonVisual.waxing ? xr : -xr), y + t);
}
ctx.closePath();
ctx.clip();
ctx.drawImage(texture, x - radius, y - radius, radius * 2, radius * 2);
}
ctx.restore();
ctx.restore();
ctx.strokeStyle = "rgba(255,255,255,0.18)";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.stroke();
return canvas.toDataURL("image/png");
}
_moonTrackState() {
const now = new Date();
const cycleStart = new Date(now);
@@ -709,6 +835,8 @@ class SternwarteCard extends HTMLElement {
}
const astro = this._astroState();
const moonTrack = this._moonTrackState();
const moonVisual = this._moonVisualState();
const moonImageDataUrl = this._renderMoonPhaseDataUrl(moonVisual, 56);
this.shadowRoot.innerHTML = `
<style>
@@ -790,10 +918,6 @@ class SternwarteCard extends HTMLElement {
outline: 2px solid var(--primary-color);
outline-offset: 2px;
}
.stat-spacer {
visibility: hidden;
pointer-events: none;
}
.stat.wind-good {
background: rgba(76, 175, 80, 0.14);
border-color: rgba(76, 175, 80, 0.3);
@@ -817,6 +941,28 @@ class SternwarteCard extends HTMLElement {
font-size: 16px;
font-weight: 700;
}
.moon-stat {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.moon-icon {
width: 28px;
height: 28px;
flex: 0 0 28px;
border-radius: 50%;
display: block;
object-fit: cover;
box-shadow: 0 0 10px rgba(246, 240, 196, 0.12);
}
.moon-percent {
text-align: right;
line-height: 1.05;
}
.moon-percent .stat-value {
margin-top: 2px;
}
.position {
margin-top: 10px;
}
@@ -1096,9 +1242,6 @@ class SternwarteCard extends HTMLElement {
.stats {
grid-template-columns: 1fr;
}
.stat-spacer {
display: none;
}
.schedule-overview {
grid-template-columns: 1fr;
}
@@ -1129,7 +1272,20 @@ class SternwarteCard extends HTMLElement {
<span class="stat-label">Wind</span>
<span class="stat-value">${windIndicator.display}</span>
</div>
<div class="stat stat-spacer" aria-hidden="true"></div>
<div class="stat">
<span class="stat-label">Mond</span>
<div class="moon-stat">
<img
class="moon-icon"
src="${moonImageDataUrl}"
alt="Mondphase"
aria-hidden="true"
>
<div class="moon-percent">
<span class="stat-value">${moonVisual.illuminationPercent} %</span>
</div>
</div>
</div>
</div>
<div class="badges">
${this._boolBadge("Dach zu", this.config.roofClosed, "Ja", "Nein")}