diff --git a/public/monatsvorhersage.php b/public/monatsvorhersage.php
index 3d4422b..f26f87d 100644
--- a/public/monatsvorhersage.php
+++ b/public/monatsvorhersage.php
@@ -11,6 +11,8 @@ $publicBasePath = './';
$currentYear = (int) date('Y');
$selectedYear = isset($_GET['year']) ? (int) $_GET['year'] : $currentYear;
$selectedMonth = isset($_GET['month']) ? max(1, min(12, (int) $_GET['month'])) : (int) date('n');
+$selectedWeek = isset($_GET['week']) ? max(1, min(53, (int) $_GET['week'])) : (int) date('W');
+$selectedWeekYear = isset($_GET['week_year']) ? (int) $_GET['week_year'] : $currentYear;
$shouldRunForecast = isset($_GET['run']) && $_GET['run'] === '1';
// -------------------------------------------------------------------------
@@ -178,7 +180,16 @@ $favoriteCometVisibilityMaxMagnitude = 10.0;
// Einzelne Zufallstreffer sollen nicht als eigenes Monatsereignis erscheinen.
$favoriteCometVisibilityMinConsecutiveDays = 2;
+if (!function_exists('getIsoWeekCountForYear')) {
+ function getIsoWeekCountForYear(int $year): int
+ {
+ return (int) (new DateTimeImmutable(sprintf('%04d-12-28', $year)))->format('W');
+ }
+}
+
$years = range($currentYear - 2, $currentYear + 5);
+$selectedWeek = max(1, min($selectedWeek, getIsoWeekCountForYear($selectedWeekYear)));
+$weekNumbers = range(1, getIsoWeekCountForYear($selectedWeekYear));
$months = [
1 => 'Januar',
2 => 'Februar',
@@ -2399,7 +2410,13 @@ $observationTipCalendarFilenameJson = json_encode(sprintf('astrotools-monatserei
Die Sternkarte wird für den 15. des gewählten Monats mit der festen Monatsuhrzeit erzeugt.
-
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2590,6 +2642,37 @@ $observationTipCalendarFilenameJson = json_encode(sprintf('astrotools-monatserei
padding: 22px 20px;
}
+.monatsvorhersage-mode {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 16px;
+ align-content: start;
+ padding: 18px;
+ border: 1px solid rgba(108, 145, 220, 0.2);
+ border-radius: 16px;
+ background: rgba(6, 12, 24, 0.38);
+}
+
+.monatsvorhersage-mode-disabled {
+ border-color: rgba(201, 168, 76, 0.18);
+ background: rgba(18, 18, 22, 0.34);
+}
+
+.monatsvorhersage-mode-header {
+ grid-column: 1 / -1;
+}
+
+.monatsvorhersage-mode-header h4 {
+ margin: 0 0 6px;
+ font-size: 1.05rem;
+}
+
+.monatsvorhersage-mode-header p {
+ margin: 0;
+ color: rgba(222, 232, 255, 0.78);
+ line-height: 1.6;
+}
+
.monatsvorhersage-form-header,
.monatskarte-header,
.monatskarte-footer {
@@ -2650,6 +2733,13 @@ $observationTipCalendarFilenameJson = json_encode(sprintf('astrotools-monatserei
cursor: pointer;
}
+.monatsvorhersage-mode-disabled .monatsvorhersage-actions button {
+ border-color: rgba(180, 186, 205, 0.38);
+ background: rgba(48, 54, 70, 0.7);
+ color: rgba(226, 232, 244, 0.76);
+ cursor: not-allowed;
+}
+
.monatskarte-header {
display: flex;
justify-content: space-between;
@@ -2761,6 +2851,10 @@ $observationTipCalendarFilenameJson = json_encode(sprintf('astrotools-monatserei
grid-template-columns: 1fr;
}
+ .monatsvorhersage-mode {
+ grid-template-columns: 1fr;
+ }
+
.monatskarte-header {
flex-direction: column;
}
@@ -2785,6 +2879,69 @@ const OBSERVATION_TIP_ICAL_FILENAME = = $observationTipCalendarFilenameJson ?:
const MONTH_CHART_COMET_K = 0.01720209895;
const MONTH_CHART_COMET_OBLIQ = 23.439279444444445 * Math.PI / 180;
+function isoWeekCount(year) {
+ const dec28 = new Date(Date.UTC(year, 11, 28));
+ const day = dec28.getUTCDay() || 7;
+ dec28.setUTCDate(dec28.getUTCDate() + 4 - day);
+ const yearStart = new Date(Date.UTC(dec28.getUTCFullYear(), 0, 1));
+ return Math.ceil((((dec28 - yearStart) / 86400000) + 1) / 7);
+}
+
+function isoWeekMonday(year, week) {
+ const jan4 = new Date(Date.UTC(year, 0, 4));
+ const jan4Day = jan4.getUTCDay() || 7;
+ const monday = new Date(jan4);
+ monday.setUTCDate(jan4.getUTCDate() - jan4Day + 1 + ((week - 1) * 7));
+ return monday;
+}
+
+function formatGermanDate(date) {
+ const day = String(date.getUTCDate()).padStart(2, '0');
+ const month = String(date.getUTCMonth() + 1).padStart(2, '0');
+ const year = String(date.getUTCFullYear());
+ return day + '.' + month + '.' + year;
+}
+
+function rebuildWeekOptions() {
+ const weekYearSelect = document.getElementById('weekYearSelect');
+ const weekSelect = document.getElementById('weekSelect');
+
+ if (!weekYearSelect || !weekSelect) {
+ return;
+ }
+
+ const selectedYear = Number(weekYearSelect.value);
+ if (!Number.isInteger(selectedYear) || selectedYear < 1) {
+ return;
+ }
+
+ const previousWeek = Number(weekSelect.value) || 1;
+ const totalWeeks = isoWeekCount(selectedYear);
+ const nextWeek = Math.min(Math.max(previousWeek, 1), totalWeeks);
+
+ weekSelect.innerHTML = '';
+
+ for (let week = 1; week <= totalWeeks; week++) {
+ const option = document.createElement('option');
+ option.value = String(week);
+ option.textContent = 'KW ' + String(week).padStart(2, '0') + ' (' + formatGermanDate(isoWeekMonday(selectedYear, week)) + ')';
+ if (week === nextWeek) {
+ option.selected = true;
+ }
+ weekSelect.appendChild(option);
+ }
+}
+
+document.addEventListener('DOMContentLoaded', function () {
+ const weekYearSelect = document.getElementById('weekYearSelect');
+ if (!weekYearSelect) {
+ return;
+ }
+
+ rebuildWeekOptions();
+ weekYearSelect.addEventListener('change', rebuildWeekOptions);
+});
+
function monthChartCometSolveElliptic(M, e) {
let E = e < 0.8 ? M : (M >= 0 ? Math.PI : -Math.PI);
for (let i = 0; i < 30; i++) {