From ed0cff20106d44ed63bde1de2fd6fe6f0c318ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 19 May 2026 08:50:19 +0200 Subject: [PATCH] Gui Fixes --- README.md | 8 +++ sternwarte-homeassistant-card/README.md | 22 +++++++ .../dist/sternwarte-card.js | 64 ++++++++++++------- 3 files changed, 72 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 97774cb..ad0166f 100644 --- a/README.md +++ b/README.md @@ -48,3 +48,11 @@ Arbeitsweise: - Nach dem Kopieren muss das Home-Assistant-Frontend hart neu geladen werden, damit kein alter Browser-Cache im Weg ist. - Für das Kopieren gibt es im Repo-Root den Helfer `deploy-test.ps1`. - Aufruf aus dem Repo-Root: `.\deploy-test.ps1` + +Wichtige Besonderheiten der Sternwarten-Card: + +- Der Dämmerungsbalken arbeitet auf einer lokalen `12:00 → 12:00`-Achse. +- Die Dämmerungsphasen werden direkt aus der Sonnenhöhe bestimmt, nicht nur aus einzelnen Dawn-/Dusk-Zeitpunkten. +- Das ist wichtig für Sommerfälle ohne astronomische Nacht. +- Die Marker `Öffnen geplant` und `Schließen geplant` werden für diese Achse nach lokaler Uhrzeit eingeordnet. +- Wenn diese Marker fehlen, ist die erste Prüffrage: Wurde versehentlich wieder gegen ein echtes Datumsfenster statt gegen die `12 → 12`-Achse gerechnet? diff --git a/sternwarte-homeassistant-card/README.md b/sternwarte-homeassistant-card/README.md index 5da2e3e..cf18f98 100644 --- a/sternwarte-homeassistant-card/README.md +++ b/sternwarte-homeassistant-card/README.md @@ -12,6 +12,28 @@ dist/ `sternwarte-card.js` lädt `astronomy.browser.min.js` automatisch nach, um Dämmerungszeiten und Mondposition präziser zu berechnen. +## Dämmerungsbalken + +Der Dämmerungsbalken in `dist/sternwarte-card.js` arbeitet bewusst nicht nur mit einzelnen Dawn-/Dusk-Zeitpunkten, sondern mit einem lokalen `12:00 → 12:00`-Fenster. + +Wichtige Punkte: + +- Die Phasen `Tag`, `bürgerliche Dämmerung`, `nautische Dämmerung`, `astronomische Dämmerung` und `Nacht` werden direkt aus der Sonnenhöhe bestimmt. +- Dafür wird das komplette lokale 24-Stunden-Fenster in kleinen Schritten durchgerechnet. +- Diese Logik ist robuster für Sommerfälle, in denen es keine astronomische Nacht mehr gibt. + +Marker im Balken: + +- `Öffnen geplant` wird grün dargestellt. +- `Schließen geplant` wird rot dargestellt. +- Der Marker `Jetzt` ist bewusst hell/weiß, damit er nicht mit `Schließen` verwechselt wird. + +Wichtig für spätere Änderungen: + +- Die Balkenachse ist eine Uhrzeitachse `12 → 12`, keine reine Datumsachse. +- Die Marker für `Öffnen geplant` und `Schließen geplant` müssen daher anhand der lokalen Uhrzeit auf diese Achse gelegt werden, nicht anhand eines festen Datumsfensters. +- Wenn die Marker verschwinden, zuerst prüfen, ob versehentlich wieder mit einem echten Zeitfenster statt mit der `12 → 12`-Achse gerechnet wird. + ## Für HACS Dieser Ordner ist als Root eines späteren HACS-Dashboard-Repositorys vorbereitet. diff --git a/sternwarte-homeassistant-card/dist/sternwarte-card.js b/sternwarte-homeassistant-card/dist/sternwarte-card.js index cfb4e91..8a696ee 100644 --- a/sternwarte-homeassistant-card/dist/sternwarte-card.js +++ b/sternwarte-homeassistant-card/dist/sternwarte-card.js @@ -330,12 +330,29 @@ class SternwarteCard extends HTMLElement { } } + _parseEntityDate(rawValue) { + if (!rawValue || rawValue === "unknown" || rawValue === "unavailable") return null; + if (rawValue instanceof Date) { + return Number.isNaN(rawValue.getTime()) ? null : rawValue; + } + if (typeof rawValue !== "string") { + const value = new Date(rawValue); + return Number.isNaN(value.getTime()) ? null : value; + } + + const normalized = rawValue.trim().replace(" ", "T"); + const hasTimezone = /(?:Z|[+-]\d{2}:\d{2})$/.test(normalized); + const localCandidate = hasTimezone ? normalized : normalized; + const value = new Date(localCandidate); + return Number.isNaN(value.getTime()) ? null : value; + } + _isoForInput(state) { if (!state || !state.state || state.state === "unknown" || state.state === "unavailable") { return ""; } - const value = new Date(state.state); - if (Number.isNaN(value.getTime())) return ""; + const value = this._parseEntityDate(state.state); + if (!value) return ""; const pad = (n) => String(n).padStart(2, "0"); return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}T${pad(value.getHours())}:${pad(value.getMinutes())}`; } @@ -344,8 +361,8 @@ class SternwarteCard extends HTMLElement { if (!state || !state.state || state.state === "unknown" || state.state === "unavailable") { return "\u2014"; } - const value = new Date(state.state); - if (Number.isNaN(value.getTime())) return "\u2014"; + const value = this._parseEntityDate(state.state); + if (!value) return "\u2014"; const pad = (n) => String(n).padStart(2, "0"); return `${pad(value.getDate())}.${pad(value.getMonth() + 1)}. ${pad(value.getHours())}:${pad(value.getMinutes())}`; } @@ -783,13 +800,12 @@ class SternwarteCard extends HTMLElement { segments.sort((a, b) => a.start - b.start); - const toWindowHour = (state) => { + const toChartHour = (state) => { if (!state?.state || state.state === "unknown" || state.state === "unavailable") return null; - const value = new Date(state.state); - if (Number.isNaN(value.getTime())) return null; - const hour = (value.getTime() - windowStart.getTime()) / 3600000; - if (hour < 0 || hour > 24) return null; - return hour; + const value = this._parseEntityDate(state.state); + if (!value) return null; + const localHour = value.getHours() + value.getMinutes() / 60 + value.getSeconds() / 3600; + return (localHour - 12 + 24) % 24; }; return { @@ -800,8 +816,8 @@ class SternwarteCard extends HTMLElement { color: segment.color, })), markerLeft: `${(nowLocal / 24) * 100}%`, - openMarkerLeft: toWindowHour(this._state(this._scheduleOpenEntityId())), - closeMarkerLeft: toWindowHour(this._state(this._scheduleCloseEntityId())), + openMarkerLeft: toChartHour(this._state(this._scheduleOpenEntityId())), + closeMarkerLeft: toChartHour(this._state(this._scheduleCloseEntityId())), }; } @@ -1084,24 +1100,28 @@ class SternwarteCard extends HTMLElement { position: absolute; top: -4px; bottom: -4px; - width: 4px; + width: 3px; margin-left: -1px; - background: #d32f2f; - box-shadow: 0 0 8px rgba(211, 47, 47, 0.55); + background: rgba(255, 255, 255, 0.95); + box-shadow: 0 0 8px rgba(255, 255, 255, 0.45); + z-index: 5; } .astro-marker { position: absolute; - top: -3px; - bottom: -3px; - width: 4px; - margin-left: -1px; - box-shadow: 0 0 6px rgba(255, 255, 255, 0.25); + top: -4px; + bottom: -4px; + width: 6px; + margin-left: -2px; + border-radius: 999px; + z-index: 4; } .astro-marker.open { - background: #4caf50; + background: #20c45a; + box-shadow: 0 0 10px rgba(32, 196, 90, 0.7); } .astro-marker.close { - background: #ffb300; + background: #e53935; + box-shadow: 0 0 10px rgba(229, 57, 53, 0.72); } .astro-moon-track { position: absolute;