Replace clock with analog+digital clock, add sun/moon events for Passau, moon phase

This commit is contained in:
Richard Zink committed 2026-10-07 07:33:41 +02:00
1 parent 4214d6cfe0
commit 7b89afe36b
1 file changed
+289 -53
+289 -53
View File
@@ -3,24 +3,11 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vanta Clouds2</title>
<title>Vanta Clouds</title>
<script src="https://cdn.jsdelivr.net/npm/suncalc@1.9.0/suncalc.js"></script>
<style>
html, body { margin: 0; padding: 0; overflow: hidden; width: 100%; height: 100%; }
html, body { margin: 0; padding: 0; overflow: hidden; width: 100%; height: 100%; font-family: monospace; }
#vanta { width: 100vw; height: 100vh; position: fixed; top: 0; left: 0; }
#moon {
position: fixed;
top: 80px;
right: 80px;
width: 60px;
height: 60px;
border-radius: 50%;
background: radial-gradient(circle at 35% 35%, #e6dfd1, #999187);
box-shadow: 0 0 30px rgba(229, 224, 209, 0.4);
opacity: 0;
transition: opacity 0.5s;
z-index: 5;
pointer-events: none;
}
#sun {
position: fixed;
top: 80px;
@@ -35,26 +22,120 @@
z-index: 5;
pointer-events: none;
}
#clock {
#info-panel {
position: fixed;
top: 20px;
bottom: 20px;
right: 20px;
color: #e6dfd1;
font-family: monospace;
font-size: 18px;
background: rgba(28, 27, 31, 0.7);
padding: 8px 14px;
border-radius: 6px;
padding: 14px 18px;
border-radius: 8px;
z-index: 9999;
letter-spacing: 1px;
font-size: 13px;
line-height: 1.6;
min-width: 220px;
}
#analog-clock {
width: 140px;
height: 140px;
margin: 0 auto 10px auto;
display: block;
}
#digital-clock {
text-align: center;
font-size: 16px;
letter-spacing: 2px;
margin-bottom: 12px;
padding-bottom: 10px;
border-bottom: 1px solid rgba(230, 223, 209, 0.2);
}
.info-row {
display: flex;
align-items: center;
gap: 10px;
padding: 3px 0;
}
.info-icon {
width: 18px;
height: 18px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}
.info-icon svg {
width: 100%;
height: 100%;
}
.info-content {
flex-grow: 1;
display: flex;
justify-content: space-between;
gap: 10px;
}
.info-label {
color: #b0a89a;
}
.info-value {
color: #e6dfd1;
font-weight: bold;
}
.info-sub {
color: #999187;
font-size: 11px;
margin-left: 4px;
}
.moon-icon-container {
width: 18px;
height: 18px;
position: relative;
}
</style>
</head>
<body>
<div id="vanta"></div>
<div id="moon"></div>
<div id="sun"></div>
<div id="clock">00:00</div>
<div id="info-panel">
<svg id="analog-clock" viewBox="0 0 140 140">
<circle cx="70" cy="70" r="65" fill="rgba(28,27,31,0.3)" stroke="#e6dfd1" stroke-width="2"/>
<g id="hour-marks"></g>
<line id="hour-hand" x1="70" y1="70" x2="70" y2="25" stroke="#e6dfd1" stroke-width="4" stroke-linecap="round"/>
<line id="minute-hand" x1="70" y1="70" x2="70" y2="15" stroke="#e6dfd1" stroke-width="3" stroke-linecap="round"/>
<line id="second-hand" x1="70" y1="70" x2="70" y2="10" stroke="#e0914f" stroke-width="1.5" stroke-linecap="round"/>
<circle cx="70" cy="70" r="4" fill="#e0914f"/>
</svg>
<div id="digital-clock">00:00:00</div>
<div class="info-row">
<div class="info-icon">
<svg viewBox="0 0 24 24" fill="#e0914f"><circle cx="12" cy="12" r="4"/><g stroke="#e0914f" stroke-width="2" stroke-linecap="round"><line x1="12" y1="2" x2="12" y2="5"/><line x1="12" y1="19" x2="12" y2="22"/><line x1="2" y1="12" x2="5" y2="12"/><line x1="19" y1="12" x2="22" y2="12"/><line x1="4.9" y1="4.9" x2="6.9" y2="6.9"/><line x1="17.1" y1="17.1" x2="19.1" y2="19.1"/><line x1="4.9" y1="19.1" x2="6.9" y2="17.1"/><line x1="17.1" y1="6.9" x2="19.1" y2="4.9"/></g></svg>
</div>
<div class="info-content"><span class="info-label">Aufgang</span><span><span class="info-value" id="sunrise-value">--:--</span><span class="info-sub" id="sunrise-when"></span></span></div>
</div>
<div class="info-row">
<div class="info-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="#e0914f" stroke-width="2"><path d="M3 12 A9 9 0 0 1 21 12 Z"/></svg>
</div>
<div class="info-content"><span class="info-label">Untergang</span><span><span class="info-value" id="sunset-value">--:--</span><span class="info-sub" id="sunset-when"></span></span></div>
</div>
<div class="info-row">
<div class="info-icon">
<svg id="moon-phase-icon" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" fill="#e6dfd1"/></svg>
</div>
<div class="info-content"><span class="info-label">Mondphase</span><span class="info-value" id="moon-phase-text">--</span></div>
</div>
<div class="info-row">
<div class="info-icon">
<svg viewBox="0 0 24 24" fill="#b0a89a"><path d="M12 2 a10 10 0 1 0 0 20 a8 8 0 1 1 0 -16 a6 6 0 1 1 0 8"/></svg>
</div>
<div class="info-content"><span class="info-label">Mond auf</span><span><span class="info-value" id="moonrise-value">--:--</span><span class="info-sub" id="moonrise-when"></span></span></div>
</div>
<div class="info-row">
<div class="info-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="#b0a89a" stroke-width="2"><path d="M3 12 A9 9 0 0 1 21 12 Z"/></svg>
</div>
<div class="info-content"><span class="info-label">Mond unter</span><span><span class="info-value" id="moonset-value">--:--</span><span class="info-sub" id="moonset-when"></span></span></div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vanta@0.5.21/dist/vanta.clouds.min.js"></script>
<script>
@@ -72,8 +153,10 @@
backgroundAlpha: 0,
skyColor: 0x3a4458,
cloudColor: 0x6d6a63,
lightColor: 0xb0a89a,
texturePath: "./noise.png"
cloudShadowColor: 0x2a3450,
sunColor: 0xf3ede1,
sunGlareColor: 0xe6dfd1,
sunlightColor: 0xf3ede1
}) || { setOptions: function() {} };
function hexToRgb(h) {
@@ -96,19 +179,19 @@
var CYCLE = 20000;
var palette = [
{ t: 0.00, sky: 0x0a0a14, cloud: 0x2a2822, light: 0x999187, bg: 0x0a0a14, cloudShadow: 0x100f12, sun: 0x999187, sunGlare: 0x6d6a63, sunLight: 0x999187, moon: 1, sunVis: 0 },
{ t: 0.08, sky: 0x141428, cloud: 0x2a2822, light: 0x999187, bg: 0x1a1428, cloudShadow: 0x141316, sun: 0x999187, sunGlare: 0x6d6a63, sunLight: 0x999187, moon: 1, sunVis: 0 },
{ t: 0.18, sky: 0x3a2a28, cloud: 0x4a2e1c, light: 0xa85f1d, bg: 0x4a2820, cloudShadow: 0x1f1814, sun: 0xc56a2b, sunGlare: 0xa85f1d, sunLight: 0xc56a2b, moon: 0.3, sunVis: 0.3 },
{ t: 0.28, sky: 0x6a4030, cloud: 0x5a3a28, light: 0xd6824a, bg: 0x7a4530, cloudShadow: 0x2a1f17, sun: 0xe0914f, sunGlare: 0xd6824a, sunLight: 0xe0914f, moon: 0, sunVis: 0.8 },
{ t: 0.42, sky: 0x4a5060, cloud: 0x6d6a63, light: 0xe0d4be, bg: 0x506080, cloudShadow: 0x3a4458, sun: 0xf3ede1, sunGlare: 0xe6dfd1, sunLight: 0xf3ede1, moon: 0, sunVis: 1 },
{ t: 0.62, sky: 0x3a4458, cloud: 0x6d6a63, light: 0xe6dfd1, bg: 0x445878, cloudShadow: 0x2a3450, sun: 0xf3ede1, sunGlare: 0xe6dfd1, sunLight: 0xf3ede1, moon: 0, sunVis: 1 },
{ t: 0.66, sky: 0x4a4a55, cloud: 0x6d5a52, light: 0xe6c89a, bg: 0x5a554e, cloudShadow: 0x3a3428, sun: 0xe6c89a, sunGlare: 0xd6b46a, sunLight: 0xe6c89a, moon: 0, sunVis: 0.95 },
{ t: 0.69, sky: 0x6a5040, cloud: 0x6d4a3a, light: 0xe6a872, bg: 0x7a5540, cloudShadow: 0x4a3025, sun: 0xe6a872, sunGlare: 0xd6824a, sunLight: 0xe6a872, moon: 0, sunVis: 0.85 },
{ t: 0.72, sky: 0x7a4530, cloud: 0x6d3a28, light: 0xe0914f, bg: 0x7a3825, cloudShadow: 0x4a2818, sun: 0xe0914f, sunGlare: 0xa86030, sunLight: 0xe0914f, moon: 0, sunVis: 0.7 },
{ t: 0.78, sky: 0x5a3030, cloud: 0x4a2e22, light: 0xa86030, bg: 0x5a2820, cloudShadow: 0x2a1818, sun: 0xa86030, sunGlare: 0x6b4423, sunLight: 0xa86030, moon: 0, sunVis: 0.4 },
{ t: 0.85, sky: 0x3a2030, cloud: 0x3a2a2a, light: 0x6a4030, bg: 0x4a2030, cloudShadow: 0x1f1814, sun: 0x6a4030, sunGlare: 0x54514f, sunLight: 0x6a4030, moon: 0.3, sunVis: 0.1 },
{ t: 0.92, sky: 0x1a1428, cloud: 0x2a2822, light: 0x999187, bg: 0x1a1428, cloudShadow: 0x141316, sun: 0x999187, sunGlare: 0x6d6a63, sunLight: 0x999187, moon: 0.9, sunVis: 0 },
{ t: 1.00, sky: 0x0a0a14, cloud: 0x2a2822, light: 0x999187, bg: 0x0a0a14, cloudShadow: 0x100f12, sun: 0x999187, sunGlare: 0x6d6a63, sunLight: 0x999187, moon: 1, sunVis: 0 }
{ t: 0.00, sky: 0x0a0a14, cloud: 0x2a2822, light: 0x999187, bg: 0x0a0a14, cloudShadow: 0x100f12, sun: 0x999187, sunGlare: 0x6d6a63, sunLight: 0x999187, sunVis: 0 },
{ t: 0.08, sky: 0x141428, cloud: 0x2a2822, light: 0x999187, bg: 0x1a1428, cloudShadow: 0x141316, sun: 0x999187, sunGlare: 0x6d6a63, sunLight: 0x999187, sunVis: 0 },
{ t: 0.18, sky: 0x3a2a28, cloud: 0x4a2e1c, light: 0xa85f1d, bg: 0x4a2820, cloudShadow: 0x1f1814, sun: 0xc56a2b, sunGlare: 0xa85f1d, sunLight: 0xc56a2b, sunVis: 0.3 },
{ t: 0.28, sky: 0x6a4030, cloud: 0x5a3a28, light: 0xd6824a, bg: 0x7a4530, cloudShadow: 0x2a1f17, sun: 0xe0914f, sunGlare: 0xd6824a, sunLight: 0xe0914f, sunVis: 0.8 },
{ t: 0.42, sky: 0x4a5060, cloud: 0x6d6a63, light: 0xe0d4be, bg: 0x506080, cloudShadow: 0x3a4458, sun: 0xf3ede1, sunGlare: 0xe6dfd1, sunLight: 0xf3ede1, sunVis: 1 },
{ t: 0.62, sky: 0x3a4458, cloud: 0x6d6a63, light: 0xe6dfd1, bg: 0x445878, cloudShadow: 0x2a3450, sun: 0xf3ede1, sunGlare: 0xe6dfd1, sunLight: 0xf3ede1, sunVis: 1 },
{ t: 0.66, sky: 0x4a4a55, cloud: 0x6d5a52, light: 0xe6c89a, bg: 0x5a554e, cloudShadow: 0x3a3428, sun: 0xe6c89a, sunGlare: 0xd6b46a, sunLight: 0xe6c89a, sunVis: 0.95 },
{ t: 0.69, sky: 0x6a5040, cloud: 0x6d4a3a, light: 0xe6a872, bg: 0x7a5540, cloudShadow: 0x4a3025, sun: 0xe6a872, sunGlare: 0xd6824a, sunLight: 0xe6a872, sunVis: 0.85 },
{ t: 0.72, sky: 0x7a4530, cloud: 0x6d3a28, light: 0xe0914f, bg: 0x7a3825, cloudShadow: 0x4a2818, sun: 0xe0914f, sunGlare: 0xa86030, sunLight: 0xe0914f, sunVis: 0.7 },
{ t: 0.78, sky: 0x5a3030, cloud: 0x4a2e22, light: 0xa86030, bg: 0x5a2820, cloudShadow: 0x2a1818, sun: 0xa86030, sunGlare: 0x6b4423, sunLight: 0xa86030, sunVis: 0.4 },
{ t: 0.85, sky: 0x3a2030, cloud: 0x3a2a2a, light: 0x6a4030, bg: 0x4a2030, cloudShadow: 0x1f1814, sun: 0x6a4030, sunGlare: 0x54514f, sunLight: 0x6a4030, sunVis: 0.1 },
{ t: 0.92, sky: 0x1a1428, cloud: 0x2a2822, light: 0x999187, bg: 0x1a1428, cloudShadow: 0x141316, sun: 0x999187, sunGlare: 0x6d6a63, sunLight: 0x999187, sunVis: 0 },
{ t: 1.00, sky: 0x0a0a14, cloud: 0x2a2822, light: 0x999187, bg: 0x0a0a14, cloudShadow: 0x100f12, sun: 0x999187, sunGlare: 0x6d6a63, sunLight: 0x999187, sunVis: 0 }
];
function getColor(t) {
@@ -127,26 +210,175 @@
sun: lerpColor(palette[i].sun, palette[i + 1].sun, s),
sunGlare: lerpColor(palette[i].sunGlare, palette[i + 1].sunGlare, s),
sunLight: lerpColor(palette[i].sunLight, palette[i + 1].sunLight, s),
moon: palette[i].moon + (palette[i + 1].moon - palette[i].moon) * s,
sunVis: palette[i].sunVis + (palette[i + 1].sunVis - palette[i].sunVis) * s
};
}
}
return { sky: palette[0].sky, cloud: palette[0].cloud, light: palette[0].light, bg: palette[0].bg, cloudShadow: palette[0].cloudShadow, sun: palette[0].sun, sunGlare: palette[0].sunGlare, sunLight: palette[0].sunLight, moon: 1, sunVis: 0 };
return { sky: palette[0].sky, cloud: palette[0].cloud, light: palette[0].light, bg: palette[0].bg, cloudShadow: palette[0].cloudShadow, sun: palette[0].sun, sunGlare: palette[0].sunGlare, sunLight: palette[0].sunLight, sunVis: 0 };
}
// ============ Clock & Sun/Moon Calculations ============
var PASSAU_LAT = 48.5667;
var PASSAU_LON = 13.4319;
var hourHand = document.getElementById('hour-hand');
var minuteHand = document.getElementById('minute-hand');
var secondHand = document.getElementById('second-hand');
var hourMarks = document.getElementById('hour-marks');
// Generate hour marks
for (var i = 0; i < 12; i++) {
var angle = (i * 30) * Math.PI / 180;
var x1 = 70 + Math.sin(angle) * 58;
var y1 = 70 - Math.cos(angle) * 58;
var x2 = 70 + Math.sin(angle) * 62;
var y2 = 70 - Math.cos(angle) * 62;
var line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', x1);
line.setAttribute('y1', y1);
line.setAttribute('x2', x2);
line.setAttribute('y2', y2);
line.setAttribute('stroke', '#e6dfd1');
line.setAttribute('stroke-width', i % 3 === 0 ? 3 : 1);
hourMarks.appendChild(line);
}
function formatTimeHM(date) {
if (!date || isNaN(date.getTime())) return '--:--';
var h = date.getHours();
var m = date.getMinutes();
return (h < 10 ? '0' : '') + h + ':' + (m < 10 ? '0' : '') + m;
}
function formatTimeHMS(h, m, s) {
return (h < 10 ? '0' : '') + h + ':' + (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s;
}
function formatDateShort(date) {
var d = date.getDate();
var mo = date.getMonth() + 1;
return (d < 10 ? '0' : '') + d + '.' + (mo < 10 ? '0' : '') + mo + '.';
}
function formatDateLong(date) {
var days = ['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.'];
var d = date.getDate();
var mo = date.getMonth() + 1;
return days[date.getDay()] + ' ' + (d < 10 ? '0' : '') + d + '.' + (mo < 10 ? '0' : '') + mo + '.';
}
function getNextEvent(todayEvent, now, lat, lon, type) {
if (!todayEvent || isNaN(todayEvent.getTime())) {
return { date: null, label: '' };
}
if (todayEvent > now) {
return { date: todayEvent, label: 'heute' };
}
for (var d = 1; d <= 7; d++) {
var nextDate = new Date(now);
nextDate.setDate(nextDate.getDate() + d);
var times = type.indexOf('moon') === 0 ? SunCalc.getMoonTimes(nextDate, lat, lon) : SunCalc.getTimes(nextDate, lat, lon);
var ev = type === 'moonrise' ? times.rise : type === 'moonset' ? times.set : times[type];
if (ev && !isNaN(ev.getTime())) {
var dayLabel = d === 1 ? 'morgen' : formatDateLong(nextDate);
return { date: ev, label: dayLabel };
}
}
return { date: null, label: '' };
}
function getMoonPhaseText(phase) {
var p = phase;
if (p < 0.0625 || p >= 0.9375) return 'Neumond';
if (p < 0.1875) return 'Zunehm. Sichel';
if (p < 0.3125) return 'Erstes Viertel';
if (p < 0.4375) return 'Zunehm. Gibbous';
if (p < 0.5625) return 'Vollmond';
if (p < 0.6875) return 'Abnehm. Gibbous';
if (p < 0.8125) return 'Letztes Viertel';
return 'Abnehm. Sichel';
}
function renderMoonPhaseIcon(phase) {
var icon = document.getElementById('moon-phase-icon');
icon.innerHTML = '';
// Background dark circle
var bg = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
bg.setAttribute('cx', '12'); bg.setAttribute('cy', '12'); bg.setAttribute('r', '9');
bg.setAttribute('fill', '#1c1b1f');
icon.appendChild(bg);
// The illuminated part
if (phase < 0.5) {
// Waxing - light on right
var lit = document.createElementNS('http://www.w3.org/2000/svg', 'path');
var r = 9;
var phaseAngle = phase * 360;
var rx = r * Math.abs(Math.cos(phaseAngle * Math.PI / 180));
var sweepFlag = phase < 0.25 ? 0 : 1;
lit.setAttribute('d', 'M 12 3 A 9 9 0 0 1 12 21 A ' + rx + ' 9 0 0 ' + sweepFlag + ' 12 3 Z');
lit.setAttribute('fill', '#e6dfd1');
icon.appendChild(lit);
} else {
// Waning - light on left
var lit = document.createElementNS('http://www.w3.org/2000/svg', 'path');
var r = 9;
var phaseAngle = (1 - phase) * 360;
var rx = r * Math.abs(Math.cos(phaseAngle * Math.PI / 180));
var sweepFlag = phase > 0.75 ? 0 : 1;
lit.setAttribute('d', 'M 12 3 A 9 9 0 0 0 12 21 A ' + rx + ' 9 0 0 ' + sweepFlag + ' 12 3 Z');
lit.setAttribute('fill', '#e6dfd1');
icon.appendChild(lit);
}
}
function updateClockAndEvents() {
var now = new Date();
// Real-time clock hands and digital
var h = now.getHours();
var m = now.getMinutes();
var s = now.getSeconds();
document.getElementById('digital-clock').textContent = formatTimeHMS(h, m, s);
var hourAngle = ((h % 12) + m / 60) * 30;
var minuteAngle = (m + s / 60) * 6;
var secondAngle = s * 6;
hourHand.setAttribute('transform', 'rotate(' + hourAngle + ' 70 70)');
minuteHand.setAttribute('transform', 'rotate(' + minuteAngle + ' 70 70)');
secondHand.setAttribute('transform', 'rotate(' + secondAngle + ' 70 70)');
// Sun times
var sunTimes = SunCalc.getTimes(now, PASSAU_LAT, PASSAU_LON);
var sunriseNext = getNextEvent(sunTimes.sunrise, now, PASSAU_LAT, PASSAU_LON, 'sunrise');
var sunsetNext = getNextEvent(sunTimes.sunset, now, PASSAU_LAT, PASSAU_LON, 'sunset');
document.getElementById('sunrise-value').textContent = formatTimeHM(sunriseNext.date);
document.getElementById('sunrise-when').textContent = sunriseNext.label ? '· ' + sunriseNext.label : '';
document.getElementById('sunset-value').textContent = formatTimeHM(sunsetNext.date);
document.getElementById('sunset-when').textContent = sunsetNext.label ? '· ' + sunsetNext.label : '';
// Moon times
var moonTimes = SunCalc.getMoonTimes(now, PASSAU_LAT, PASSAU_LON);
var moonriseNext = getNextEvent(moonTimes.rise, now, PASSAU_LAT, PASSAU_LON, 'moonrise');
var moonsetNext = getNextEvent(moonTimes.set, now, PASSAU_LAT, PASSAU_LON, 'moonset');
document.getElementById('moonrise-value').textContent = formatTimeHM(moonriseNext.date);
document.getElementById('moonrise-when').textContent = moonriseNext.label ? '· ' + moonriseNext.label : '';
document.getElementById('moonset-value').textContent = formatTimeHM(moonsetNext.date);
document.getElementById('moonset-when').textContent = moonsetNext.label ? '· ' + moonsetNext.label : '';
// Moon phase
var moonIllum = SunCalc.getMoonIllumination(now);
document.getElementById('moon-phase-text').textContent =
getMoonPhaseText(moonIllum.phase) + ' ' + Math.round(moonIllum.fraction * 100) + '%';
renderMoonPhaseIcon(moonIllum.phase);
}
var startTime = Date.now();
var clockEl = document.getElementById('clock');
var moonEl = document.getElementById('moon');
var sunEl = document.getElementById('sun');
function formatTime(totalMinutes) {
var hours = Math.floor(totalMinutes / 60) % 24;
var minutes = Math.floor(totalMinutes % 60);
var pad = function(n) { return n < 10 ? '0' + n : '' + n; };
return pad(hours) + ':' + pad(minutes);
}
function animate() {
var elapsed = (Date.now() - startTime) % CYCLE;
var t = elapsed / CYCLE;
@@ -161,13 +393,17 @@
backgroundColor: c.bg
});
document.body.style.backgroundColor = '#' + ('00000' + c.bg.toString(16)).slice(-6);
moonEl.style.opacity = c.moon;
sunEl.style.opacity = c.sunVis;
clockEl.textContent = formatTime(t * 1440);
requestAnimationFrame(animate);
}
function tick() {
updateClockAndEvents();
setTimeout(tick, 1000);
}
animate();
tick();
</script>
</body>
</html>