diff --git a/index.html b/index.html
index c0abf9a..48c4284 100644
--- a/index.html
+++ b/index.html
@@ -71,6 +71,77 @@
font-size: 11px;
margin-left: 4px;
}
+ .weather-divider {
+ height: 1px;
+ background: rgba(230, 223, 209, 0.2);
+ margin: 8px 0;
+ }
+ .weather-current {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 4px 0 8px 0;
+ border-bottom: 1px solid rgba(230, 223, 209, 0.2);
+ margin-bottom: 8px;
+ }
+ .weather-current-icon {
+ width: 36px;
+ height: 36px;
+ flex-shrink: 0;
+ }
+ .weather-current-icon svg { width: 100%; height: 100%; }
+ .weather-current-temp {
+ font-size: 22px;
+ font-weight: bold;
+ }
+ .weather-current-detail {
+ font-size: 11px;
+ color: #b0a89a;
+ line-height: 1.4;
+ }
+ .weather-day {
+ margin-bottom: 6px;
+ }
+ .weather-day-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ padding: 2px 0;
+ font-size: 12px;
+ }
+ .weather-day-name {
+ color: #e6dfd1;
+ font-weight: bold;
+ }
+ .weather-day-minmax {
+ color: #999187;
+ font-size: 11px;
+ }
+ .weather-day-slots {
+ display: flex;
+ justify-content: space-between;
+ gap: 4px;
+ font-size: 11px;
+ }
+ .weather-slot {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ flex: 1;
+ gap: 2px;
+ }
+ .weather-slot-icon {
+ width: 22px;
+ height: 22px;
+ }
+ .weather-slot-icon svg { width: 100%; height: 100%; }
+ .weather-slot-label {
+ color: #999187;
+ font-size: 9px;
+ }
+ .weather-slot-temp {
+ color: #e6dfd1;
+ }
@@ -160,10 +231,13 @@
+
+
+
@@ -602,6 +676,177 @@
setTimeout(tick, 1000);
}
+ // ============ Weather ============
+ var WEATHER_LAT = 48.5667;
+ var WEATHER_LON = 13.4319;
+ var weatherData = null;
+
+ var WMO_ICONS = {
+ 0: 'sun',
+ 1: 'sun-cloud',
+ 2: 'cloud-sun',
+ 3: 'cloud',
+ 45: 'fog',
+ 48: 'fog',
+ 51: 'drizzle',
+ 53: 'drizzle',
+ 55: 'drizzle',
+ 56: 'drizzle',
+ 57: 'drizzle',
+ 61: 'rain',
+ 63: 'rain',
+ 65: 'rain',
+ 66: 'rain',
+ 67: 'rain',
+ 71: 'snow',
+ 73: 'snow',
+ 75: 'snow',
+ 77: 'snow',
+ 80: 'rain',
+ 81: 'rain',
+ 82: 'rain',
+ 85: 'snow',
+ 86: 'snow',
+ 95: 'thunder',
+ 96: 'thunder',
+ 99: 'thunder'
+ };
+
+ function weatherSVG(code) {
+ var c = '#e6dfd1';
+ switch (code) {
+ case 0:
+ return '';
+ case 1:
+ case 2:
+ return '';
+ case 3:
+ return '';
+ case 45:
+ case 48:
+ return '';
+ case 51:
+ case 55:
+ return '';
+ case 53:
+ return '';
+ case 61:
+ case 80:
+ return '';
+ case 63:
+ case 81:
+ return '';
+ case 65:
+ case 82:
+ return '';
+ case 71:
+ case 73:
+ case 75:
+ case 77:
+ case 85:
+ case 86:
+ return '';
+ case 95:
+ case 96:
+ case 99:
+ return '';
+ default:
+ return '';
+ }
+ }
+
+ function fetchWeather() {
+ var url = 'https://api.open-meteo.com/v1/forecast?latitude=' + WEATHER_LAT +
+ '&longitude=' + WEATHER_LON +
+ '¤t=temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m' +
+ '&hourly=temperature_2m,weather_code' +
+ '&daily=weather_code,temperature_2m_max,temperature_2m_min' +
+ '&timezone=Europe/Berlin&forecast_days=5';
+
+ fetch(url)
+ .then(function(response) { return response.json(); })
+ .then(function(data) {
+ weatherData = data;
+ updateWeatherDisplay();
+ })
+ .catch(function(err) {
+ console.log('Weather fetch failed:', err);
+ });
+ }
+
+ function findHourlyIndex(targetDate, hourlyTimeArray) {
+ var bestIdx = 0;
+ var bestDiff = Infinity;
+ for (var i = 0; i < hourlyTimeArray.length; i++) {
+ var t = new Date(hourlyTimeArray[i]);
+ var diff = Math.abs(t.getTime() - targetDate.getTime());
+ if (diff < bestDiff) {
+ bestDiff = diff;
+ bestIdx = i;
+ }
+ }
+ return bestIdx;
+ }
+
+ function updateWeatherDisplay() {
+ if (!weatherData) return;
+
+ var now = new Date();
+ var current = weatherData.current;
+ var hourly = weatherData.hourly;
+ var daily = weatherData.daily;
+
+ // Current
+ var currentHtml = '' + weatherSVG(current.weather_code) + '
' +
+ '' + Math.round(current.temperature_2m) + '°C
' +
+ '
' +
+ 'Feuchte ' + Math.round(current.relative_humidity_2m) + '% · Wind ' + Math.round(current.wind_speed_10m) + ' km/h
';
+ document.getElementById('weather-current').innerHTML = currentHtml;
+
+ // Forecast
+ var forecastHtml = '';
+ var dayNames = ['Heute', 'Morgen'];
+ for (var d = 0; d < 4; d++) {
+ var dayDate = new Date(daily.time[d]);
+ var dayLabel = d === 0 ? 'Heute' : dayNames[1] || dayDate.toLocaleDateString('de-DE', { weekday: 'short' });
+ if (d > 1) {
+ dayLabel = dayDate.toLocaleDateString('de-DE', { weekday: 'short' }) + ' ' +
+ (dayDate.getDate() < 10 ? '0' : '') + dayDate.getDate() + '.' +
+ (dayDate.getMonth() + 1 < 10 ? '0' : '') + (dayDate.getMonth() + 1) + '.';
+ }
+
+ // Morning (8), Noon (14), Evening (20)
+ var slots = [8, 14, 20];
+ var slotsHtml = '';
+ for (var i = 0; i < slots.length; i++) {
+ var slotDate = new Date(dayDate);
+ slotDate.setHours(slots[i], 0, 0, 0);
+ var slotIdx = findHourlyIndex(slotDate, hourly.time);
+ var slotCode = hourly.weather_code[slotIdx];
+ var slotTemp = hourly.temperature_2m[slotIdx];
+ var slotLabels = ['M', 'Mi', 'A'];
+ slotsHtml += '' +
+ '
' + weatherSVG(slotCode) + '
' +
+ '
' + slotLabels[i] + '
' +
+ '
' + Math.round(slotTemp) + '°
' +
+ '
';
+ }
+
+ forecastHtml += '' +
+ '' +
+ '
' + slotsHtml + '
' +
+ '
';
+ }
+ document.getElementById('weather-forecast').innerHTML = forecastHtml;
+ }
+
+ // Update weather every 15 minutes
+ fetchWeather();
+ setInterval(fetchWeather, 15 * 60 * 1000);
+
animate();
tick();