Add night slot to weather, widen info-panel, scale analog clock to 280px

This commit is contained in:
Richard Zink committed 2026-10-07 08:47:54 +02:00
1 parent 7117dce49a
commit 70c4d72a03
1 file changed
+56 -61
+56 -61
View File
@@ -19,14 +19,19 @@
z-index: 9999; z-index: 9999;
font-size: 13px; font-size: 13px;
line-height: 1.6; line-height: 1.6;
min-width: 220px; min-width: 340px;
} }
#analog-clock { #analog-clock {
width: 200px; width: 280px;
height: 200px; height: 280px;
margin: 0 auto 10px auto; margin: 0 auto 10px auto;
display: block; display: block;
} }
#info-panel:not(:has(.weather-forecast)) #analog-clock,
#analog-clock {
width: 280px;
}
#digital-clock { #digital-clock {
text-align: center; text-align: center;
font-size: 16px; font-size: 16px;
@@ -147,18 +152,18 @@
<body> <body>
<div id="vanta"></div> <div id="vanta"></div>
<div id="info-panel"> <div id="info-panel">
<svg id="analog-clock" viewBox="0 0 200 200"> <svg id="analog-clock" viewBox="0 0 280 280">
<!-- Clock face --> <!-- Clock face -->
<circle cx="100" cy="100" r="95" fill="rgba(28,27,31,0.3)" stroke="#e6dfd1" stroke-width="2"/> <circle cx="140" cy="140" r="135" fill="rgba(28,27,31,0.3)" stroke="#e6dfd1" stroke-width="2"/>
<!-- Hour marks --> <!-- Hour marks -->
<g id="hour-marks"></g> <g id="hour-marks"></g>
<!-- Sun orbit (radius 55) --> <!-- Sun orbit (radius 65) -->
<circle cx="100" cy="100" r="55" stroke="#3a383d" stroke-width="1" fill="none" stroke-dasharray="2,3" opacity="0.6"/> <circle cx="140" cy="140" r="65" stroke="#3a383d" stroke-width="1" fill="none" stroke-dasharray="2,3" opacity="0.6"/>
<!-- Moon orbit (radius 75) --> <!-- Moon orbit (radius 88) -->
<circle cx="100" cy="100" r="75" stroke="#3a383d" stroke-width="1" fill="none" stroke-dasharray="2,3" opacity="0.6"/> <circle cx="140" cy="140" r="88" stroke="#3a383d" stroke-width="1" fill="none" stroke-dasharray="2,3" opacity="0.6"/>
<!-- Sun arc (from sunrise to sunset) --> <!-- Sun arc (from sunrise to sunset) -->
<path id="sun-arc" d="" stroke="#e0914f" stroke-width="2.5" fill="none"/> <path id="sun-arc" d="" stroke="#e0914f" stroke-width="2.5" fill="none"/>
@@ -168,41 +173,41 @@
<!-- Sun rise arrow (pointing in / up at sunrise) --> <!-- Sun rise arrow (pointing in / up at sunrise) -->
<g id="sun-rise-arrow" fill="#e0914f"> <g id="sun-rise-arrow" fill="#e0914f">
<polygon points="0,-7 6,3 -6,3"/> <polygon points="0,-9 8,0 -9,0"/>
</g> </g>
<!-- Sun set arrow (pointing out / down at sunset) --> <!-- Sun set arrow (pointing out / down at sunset) -->
<g id="sun-set-arrow" fill="#e0914f"> <g id="sun-set-arrow" fill="#e0914f">
<polygon points="0,7 6,-3 -6,-3"/> <polygon points="0,9 8,0 -9,0"/>
</g> </g>
<!-- Moon rise arrow --> <!-- Moon rise arrow -->
<g id="moon-rise-arrow" fill="#b0a89a"> <g id="moon-rise-arrow" fill="#b0a89a">
<polygon points="0,-7 6,3 -6,3"/> <polygon points="0,-9 8,0 -9,0"/>
</g> </g>
<!-- Moon set arrow --> <!-- Moon set arrow -->
<g id="moon-set-arrow" fill="#b0a89a"> <g id="moon-set-arrow" fill="#b0a89a">
<polygon points="0,7 6,-3 -6,-3"/> <polygon points="0,9 8,0 -9,0"/>
</g> </g>
<!-- Sun position (rendered after arcs/arrows so it's visible) --> <!-- Sun position (rendered after arcs/arrows so it's visible) -->
<circle id="rpg-sun-glow" cx="100" cy="100" r="12" fill="#e0914f" opacity="0.4"/> <circle id="rpg-sun-glow" cx="140" cy="140" r="14" fill="#e0914f" opacity="0.4"/>
<circle id="rpg-sun" cx="100" cy="100" r="7" fill="#e0914f"/> <circle id="rpg-sun" cx="140" cy="140" r="8" fill="#e0914f"/>
<circle id="rpg-sun-core" cx="100" cy="100" r="3" fill="#f3ede1"/> <circle id="rpg-sun-core" cx="140" cy="140" r="3.5" fill="#f3ede1"/>
<!-- Moon position (with phase) --> <!-- Moon position (with phase) -->
<g id="rpg-moon"> <g id="rpg-moon">
<circle id="rpg-moon-bg" cx="0" cy="0" r="9" fill="#1c1b1f" stroke="#999187" stroke-width="1"/> <circle id="rpg-moon-bg" cx="0" cy="0" r="10" fill="#1c1b1f" stroke="#999187" stroke-width="1"/>
<path id="rpg-moon-lit" d="" fill="#e6dfd1"/> <path id="rpg-moon-lit" d="" fill="#e6dfd1"/>
<path id="rpg-moon-dark" d="" fill="#54514f"/> <path id="rpg-moon-dark" d="" fill="#54514f"/>
</g> </g>
<!-- Clock hands (on top of everything) --> <!-- Clock hands (on top of everything) -->
<line id="hour-hand" x1="100" y1="100" x2="100" y2="35" stroke="#e6dfd1" stroke-width="4" stroke-linecap="round" opacity="0.7"/> <line id="hour-hand" x1="140" y1="140" x2="140" y2="49" stroke="#e6dfd1" stroke-width="5" stroke-linecap="round" opacity="0.7"/>
<line id="minute-hand" x1="100" y1="100" x2="100" y2="20" stroke="#e6dfd1" stroke-width="3" stroke-linecap="round" opacity="0.7"/> <line id="minute-hand" x1="140" y1="140" x2="140" y2="28" stroke="#e6dfd1" stroke-width="4" stroke-linecap="round" opacity="0.7"/>
<line id="second-hand" x1="100" y1="100" x2="100" y2="14" stroke="#e0914f" stroke-width="1.5" stroke-linecap="round"/> <line id="second-hand" x1="140" y1="140" x2="140" y2="20" stroke="#e0914f" stroke-width="2" stroke-linecap="round"/>
<circle cx="100" cy="100" r="4" fill="#e0914f"/> <circle cx="140" cy="140" r="5" fill="#e0914f"/>
</svg> </svg>
<div id="digital-clock">00:00:00</div> <div id="digital-clock">00:00:00</div>
<div class="info-row"> <div class="info-row">
@@ -399,23 +404,23 @@
var moonRiseArrow = document.getElementById('moon-rise-arrow'); var moonRiseArrow = document.getElementById('moon-rise-arrow');
var moonSetArrow = document.getElementById('moon-set-arrow'); var moonSetArrow = document.getElementById('moon-set-arrow');
var SUN_ORBIT_RADIUS = 55; var SUN_ORBIT_RADIUS = 65;
var MOON_ORBIT_RADIUS = 75; var MOON_ORBIT_RADIUS = 88;
// Generate hour marks // Generate hour marks
for (var i = 0; i < 12; i++) { for (var i = 0; i < 12; i++) {
var angle = (i * 30) * Math.PI / 180; var angle = (i * 30) * Math.PI / 180;
var x1 = 100 + Math.sin(angle) * 85; var x1 = 140 + Math.sin(angle) * 122;
var y1 = 100 - Math.cos(angle) * 85; var y1 = 140 - Math.cos(angle) * 122;
var x2 = 100 + Math.sin(angle) * 90; var x2 = 140 + Math.sin(angle) * 128;
var y2 = 100 - Math.cos(angle) * 90; var y2 = 140 - Math.cos(angle) * 128;
var line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); var line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', x1); line.setAttribute('x1', x1);
line.setAttribute('y1', y1); line.setAttribute('y1', y1);
line.setAttribute('x2', x2); line.setAttribute('x2', x2);
line.setAttribute('y2', y2); line.setAttribute('y2', y2);
line.setAttribute('stroke', '#e6dfd1'); line.setAttribute('stroke', '#e6dfd1');
line.setAttribute('stroke-width', i % 3 === 0 ? 3 : 1); line.setAttribute('stroke-width', i % 3 === 0 ? 4 : 3);
hourMarks.appendChild(line); hourMarks.appendChild(line);
} }
@@ -475,6 +480,9 @@
return 'Abnehm. Sichel'; return 'Abnehm. Sichel';
} }
var CENTER_X = 140;
var CENTER_Y = 140;
function angleForTime(date) { function angleForTime(date) {
// 0:00 = bottom, 6:00 = left, 12:00 = top, 18:00 = right // 0:00 = bottom, 6:00 = left, 12:00 = top, 18:00 = right
var hours = date.getHours() + date.getMinutes() / 60; var hours = date.getHours() + date.getMinutes() / 60;
@@ -484,8 +492,8 @@
function pointOnCircle(date, radius) { function pointOnCircle(date, radius) {
var angle = angleForTime(date); var angle = angleForTime(date);
return { return {
x: 100 + Math.cos(angle) * radius, x: CENTER_X + Math.cos(angle) * radius,
y: 100 + Math.sin(angle) * radius y: CENTER_Y + Math.sin(angle) * radius
}; };
} }
@@ -496,7 +504,6 @@
var endA = angleForTime(endDate) * 180 / Math.PI; var endA = angleForTime(endDate) * 180 / Math.PI;
var diff = endA - startA; var diff = endA - startA;
if (diff < 0) diff += 360; if (diff < 0) diff += 360;
// Use shorter arc (largeArc=0) unless the difference exceeds 180
var largeArc = diff > 180 ? 1 : 0; var largeArc = diff > 180 ? 1 : 0;
return 'M ' + sp.x + ' ' + sp.y + ' A ' + radius + ' ' + radius + ' 0 ' + largeArc + ' 1 ' + ep.x + ' ' + ep.y; return 'M ' + sp.x + ' ' + sp.y + ' A ' + radius + ' ' + radius + ' 0 ' + largeArc + ' 1 ' + ep.x + ' ' + ep.y;
} }
@@ -510,7 +517,6 @@
return { point: pointOnCircle(now, MOON_ORBIT_RADIUS), isVisible: false }; return { point: pointOnCircle(now, MOON_ORBIT_RADIUS), isVisible: false };
} }
// Determine if moon is visible (above horizon)
var isVisible; var isVisible;
if (rise <= set) { if (rise <= set) {
isVisible = (now >= rise && now <= set); isVisible = (now >= rise && now <= set);
@@ -522,16 +528,13 @@
return { point: pointOnCircle(now, MOON_ORBIT_RADIUS), isVisible: false }; return { point: pointOnCircle(now, MOON_ORBIT_RADIUS), isVisible: false };
} }
// Above horizon - interpolate between moonrise and moonset
var riseAngle = angleForTime(rise); var riseAngle = angleForTime(rise);
var setAngle = angleForTime(set); var setAngle = angleForTime(set);
// Calculate the angular difference (could be negative if crossing midnight)
var diff = setAngle - riseAngle; var diff = setAngle - riseAngle;
if (diff > Math.PI) diff -= 2 * Math.PI; if (diff > Math.PI) diff -= 2 * Math.PI;
if (diff < -Math.PI) diff += 2 * Math.PI; if (diff < -Math.PI) diff += 2 * Math.PI;
// Calculate progress (0 at rise, 1 at set)
var progress; var progress;
if (rise <= set) { if (rise <= set) {
progress = (now.getTime() - rise.getTime()) / (set.getTime() - rise.getTime()); progress = (now.getTime() - rise.getTime()) / (set.getTime() - rise.getTime());
@@ -548,8 +551,8 @@
var moonAngle = riseAngle + diff * progress; var moonAngle = riseAngle + diff * progress;
return { return {
point: { point: {
x: 100 + Math.cos(moonAngle) * MOON_ORBIT_RADIUS, x: CENTER_X + Math.cos(moonAngle) * MOON_ORBIT_RADIUS,
y: 100 + Math.sin(moonAngle) * MOON_ORBIT_RADIUS y: CENTER_Y + Math.sin(moonAngle) * MOON_ORBIT_RADIUS
}, },
isVisible: true isVisible: true
}; };
@@ -563,7 +566,6 @@
icon.innerHTML = ''; icon.innerHTML = '';
var r = 9; var r = 9;
// Background: dark full circle
var bg = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); var bg = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
bg.setAttribute('cx', '12'); bg.setAttribute('cy', '12'); bg.setAttribute('r', r); bg.setAttribute('cx', '12'); bg.setAttribute('cy', '12'); bg.setAttribute('r', r);
bg.setAttribute('fill', '#1c1b1f'); bg.setAttribute('fill', '#1c1b1f');
@@ -571,28 +573,19 @@
bg.setAttribute('stroke-width', '0.5'); bg.setAttribute('stroke-width', '0.5');
icon.appendChild(bg); icon.appendChild(bg);
// Calculate terminator ellipse radius from fraction
// rx > 0 (crescent): terminator curves into the lit area
// rx < 0 (gibbous): terminator curves into the dark area
var rx = r * (1 - 2 * fraction); var rx = r * (1 - 2 * fraction);
var path; var path;
if (phase < 0.5) { if (phase < 0.5) {
// Waxing: right side lit, left side dark
if (rx > 0) { if (rx > 0) {
// Crescent
path = 'M 12 3 A 9 9 0 0 0 12 21 A ' + rx + ' 9 0 0 1 12 3 Z'; path = 'M 12 3 A 9 9 0 0 0 12 21 A ' + rx + ' 9 0 0 1 12 3 Z';
} else { } else {
// Gibbous
path = 'M 12 3 A 9 9 0 0 0 12 21 A ' + Math.abs(rx) + ' 9 0 0 0 12 3 Z'; path = 'M 12 3 A 9 9 0 0 0 12 21 A ' + Math.abs(rx) + ' 9 0 0 0 12 3 Z';
} }
} else { } else {
// Waning: left side lit, right side dark
if (rx > 0) { if (rx > 0) {
// Crescent
path = 'M 12 3 A 9 9 0 0 1 12 21 A ' + rx + ' 9 0 0 0 12 3 Z'; path = 'M 12 3 A 9 9 0 0 1 12 21 A ' + rx + ' 9 0 0 0 12 3 Z';
} else { } else {
// Gibbous
path = 'M 12 3 A 9 9 0 0 1 12 21 A ' + Math.abs(rx) + ' 9 0 0 1 12 3 Z'; path = 'M 12 3 A 9 9 0 0 1 12 21 A ' + Math.abs(rx) + ' 9 0 0 1 12 3 Z';
} }
} }
@@ -604,7 +597,7 @@
} }
function renderRpgMoon(phase, fraction) { function renderRpgMoon(phase, fraction) {
var r = 9; var r = 10;
rpgMoonDark.setAttribute('d', ''); rpgMoonDark.setAttribute('d', '');
var rx = r * (1 - 2 * fraction); var rx = r * (1 - 2 * fraction);
@@ -612,15 +605,15 @@
var path; var path;
if (phase < 0.5) { if (phase < 0.5) {
if (rx > 0) { if (rx > 0) {
path = 'M 0 -9 A 9 9 0 0 0 0 9 A ' + rx + ' 9 0 0 1 0 -9 Z'; path = 'M 0 -10 A 10 10 0 0 0 0 10 A ' + rx + ' 10 0 0 1 0 -10 Z';
} else { } else {
path = 'M 0 -9 A 9 9 0 0 0 0 9 A ' + Math.abs(rx) + ' 9 0 0 0 0 -9 Z'; path = 'M 0 -10 A 10 10 0 0 0 0 10 A ' + Math.abs(rx) + ' 10 0 0 0 0 -10 Z';
} }
} else { } else {
if (rx > 0) { if (rx > 0) {
path = 'M 0 -9 A 9 9 0 0 1 0 9 A ' + rx + ' 9 0 0 0 0 -9 Z'; path = 'M 0 -10 A 10 10 0 0 1 0 10 A ' + rx + ' 10 0 0 0 0 -10 Z';
} else { } else {
path = 'M 0 -9 A 9 9 0 0 1 0 9 A ' + Math.abs(rx) + ' 9 0 0 1 0 -9 Z'; path = 'M 0 -10 A 10 10 0 0 1 0 10 A ' + Math.abs(rx) + ' 10 0 0 1 0 -10 Z';
} }
} }
@@ -641,9 +634,9 @@
var minuteAngle = (m + s / 60) * 6; var minuteAngle = (m + s / 60) * 6;
var secondAngle = s * 6; var secondAngle = s * 6;
hourHand.setAttribute('transform', 'rotate(' + hourAngle + ' 100 100)'); hourHand.setAttribute('transform', 'rotate(' + hourAngle + ' 140 140)');
minuteHand.setAttribute('transform', 'rotate(' + minuteAngle + ' 100 100)'); minuteHand.setAttribute('transform', 'rotate(' + minuteAngle + ' 140 140)');
secondHand.setAttribute('transform', 'rotate(' + secondAngle + ' 100 100)'); secondHand.setAttribute('transform', 'rotate(' + secondAngle + ' 140 140)');
// Sun times // Sun times
var sunTimes = SunCalc.getTimes(now, PASSAU_LAT, PASSAU_LON); var sunTimes = SunCalc.getTimes(now, PASSAU_LAT, PASSAU_LON);
@@ -739,9 +732,6 @@
var elapsed = (Date.now() - startTime) % CYCLE; var elapsed = (Date.now() - startTime) % CYCLE;
var t = elapsed / CYCLE; var t = elapsed / CYCLE;
var c = getColor(t); var c = getColor(t);
if (currentWeatherCode >= 0) {
c = applyWeatherModifiers(c, currentWeatherCode);
}
effect.setOptions({ effect.setOptions({
skyColor: c.sky, skyColor: c.sky,
cloudColor: c.cloud, cloudColor: c.cloud,
@@ -900,16 +890,21 @@
(dayDate.getMonth() + 1 < 10 ? '0' : '') + (dayDate.getMonth() + 1) + '.'; (dayDate.getMonth() + 1 < 10 ? '0' : '') + (dayDate.getMonth() + 1) + '.';
} }
// Morning (8), Noon (14), Evening (20) // Morning (8), Noon (14), Evening (20), Night (2)
var slots = [8, 14, 20]; var slots = [8, 14, 20, 2];
var slotsHtml = ''; var slotsHtml = '';
for (var i = 0; i < slots.length; i++) { for (var i = 0; i < slots.length; i++) {
var slotDate = new Date(dayDate); var slotDate = new Date(dayDate);
slotDate.setHours(slots[i], 0, 0, 0); slotDate.setHours(slots[i], 0, 0, 0);
if (i === 3 && d > 0) {
slotDate.setDate(slotDate.getDate() + 1);
} else if (i === 3 && d === 0) {
slotDate.setDate(slotDate.getDate() + 1);
}
var slotIdx = findHourlyIndex(slotDate, hourly.time); var slotIdx = findHourlyIndex(slotDate, hourly.time);
var slotCode = hourly.weather_code[slotIdx]; var slotCode = hourly.weather_code[slotIdx];
var slotTemp = hourly.temperature_2m[slotIdx]; var slotTemp = hourly.temperature_2m[slotIdx];
var slotLabels = ['M', 'Mi', 'A']; var slotLabels = ['M', 'Mi', 'A', 'N'];
slotsHtml += '<div class="weather-slot">' + slotsHtml += '<div class="weather-slot">' +
'<div class="weather-slot-icon">' + weatherSVG(slotCode) + '</div>' + '<div class="weather-slot-icon">' + weatherSVG(slotCode) + '</div>' +
'<div class="weather-slot-label">' + slotLabels[i] + '</div>' + '<div class="weather-slot-label">' + slotLabels[i] + '</div>' +