Files
animated_wallpaper/index.html
T

1448 lines
66 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vanta Clouds</title>
<link rel="stylesheet" href="ash.css">
<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%; font-family: monospace; }
#vanta { width: 100vw; height: 100vh; position: fixed; top: 0; left: 0; }
#debug-panel {
position: fixed;
bottom: 2vh;
left: 2vh;
width: 20vw;
min-width: 200px;
max-width: 320px;
background: rgba(28, 27, 31, 0.85);
border: 1px solid rgba(230, 223, 209, 0.3);
border-radius: 0.6vh;
padding: 1vh 1vw;
color: var(--bone);
font-family: monospace;
font-size: clamp(9px, 1.2vh, 14px);
line-height: 1.4;
z-index: 9999;
max-height: 90vh;
overflow-y: auto;
}
.debug-title {
font-size: 1.2em;
font-weight: bold;
color: var(--orange);
border-bottom: 1px solid rgba(230, 223, 209, 0.3);
padding-bottom: 4px;
margin-bottom: 6px;
cursor: pointer;
user-select: none;
display: flex;
justify-content: space-between;
align-items: center;
}
.debug-title-toggle {
font-size: 0.8em;
color: var(--icon);
margin-left: 8px;
}
.debug-content {
overflow: hidden;
transition: max-height 0.3s ease;
}
.debug-content.collapsed {
max-height: 0;
padding-top: 0;
padding-bottom: 0;
margin: 0;
}
.debug-section {
font-size: 1em;
color: var(--icon);
margin-top: 6px;
margin-bottom: 2px;
}
.debug-row {
display: flex;
align-items: center;
gap: 6px;
padding: 1px 0;
}
.debug-label {
color: var(--icon);
min-width: 80px;
}
.libel {
color: var(--icon);
min-width: 50px;
}
.hex {
color: var(--bone);
font-size: 0.9em;
font-family: monospace;
}
.swatch {
width: 1.4em;
height: 1.4em;
border: 1px solid var(--stone);
border-radius: 2px;
}
.theme-table {
font-size: 0.9em;
margin-top: 4px;
}
.theme-table-row {
display: grid;
grid-template-columns: 5em 2.4em 2.4em 2.4em 2.4em 2.8em;
gap: 5px;
align-items: center;
padding: 1px 0;
}
.palette-table-row {
display: grid;
grid-template-columns: 7em 1.6em auto;
gap: 5px;
align-items: center;
padding: 1px 0;
}
.palette-table-row .theme-table-name {
text-align: right;
padding-right: 4px;
}
.palette-table-row .theme-table-speed {
text-align: left;
}
.theme-table-header {
color: var(--icon);
font-weight: bold;
border-bottom: 1px solid var(--border);
padding-bottom: 2px;
}
.theme-table-name {
color: var(--bone);
}
.theme-table-mini-swatch {
width: 1.3em;
height: 1.3em;
border: 1px solid var(--border);
border-radius: 1px;
}
.theme-table-speed {
color: var(--icon);
font-size: 0.85em;
}
@media (orientation: portrait) and (max-width: 700px) {
#debug-panel {
display: none;
}
}
#info-panel {
position: fixed;
top: 2vh;
right: 2vh;
bottom: 2vh;
color: var(--bone);
background: rgba(28, 27, 31, 0.7);
padding: 1vh 1vw;
border-radius: 0.6vh;
z-index: 9999;
font-size: clamp(10px, 1.2vh, 14px);
line-height: 1.4;
width: clamp(260px, min(33vh, 28vw), 40vw);
display: flex;
flex-direction: column;
overflow-y: auto;
align-items: stretch;
}
@media (orientation: portrait) and (max-width: 700px) {
#info-panel {
width: 90vw;
}
}
#analog-clock {
width: 100%;
height: auto;
margin: 0 auto 0.4vh auto;
display: block;
flex-shrink: 0;
}
#digital-clock {
text-align: center;
font-size: 1.3em;
letter-spacing: 2px;
margin-bottom: 0.6vh;
padding-bottom: 0.5vh;
border-bottom: 1px solid rgba(230, 223, 209, 0.2);
}
.info-row {
display: flex;
align-items: center;
gap: 0.6vw;
padding: 0.05vh 0;
}
.info-icon {
width: 1.4em;
height: 1.4em;
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: 0.6vw;
}
.info-label {
color: var(--icon);
}
.info-value {
color: var(--bone);
font-weight: bold;
}
.info-sub {
color: var(--stone);
font-size: 0.85em;
margin-left: 4px;
}
.weather-divider {
height: 1px;
background: rgba(230, 223, 209, 0.2);
margin: 0.5vh 0;
}
.weather-current {
display: flex;
align-items: center;
gap: 0.8vw;
padding: 0.8vh 0 1vh 0;
border-bottom: 1px solid rgba(230, 223, 209, 0.2);
margin-bottom: 1vh;
}
.weather-current-icon {
width: 3.5em;
height: 3.5em;
flex-shrink: 0;
}
.weather-current-icon svg { width: 100%; height: 100%; }
.weather-current-temp {
font-size: 2.4em;
font-weight: bold;
}
.weather-current-detail {
font-size: 1em;
color: var(--icon);
line-height: 1.3;
}
.weather-day {
margin-bottom: 0.6vh;
}
.weather-day-header {
display: flex;
justify-content: space-between;
align-items: baseline;
padding: 0.2vh 0;
font-size: 1em;
}
.weather-day-name {
color: var(--bone);
font-weight: bold;
}
.weather-day-minmax {
color: var(--bone);
font-size: 0.85em;
}
.weather-day-slots {
display: flex;
justify-content: space-between;
gap: 0.3vw;
font-size: 0.9em;
margin-top: 0.3vh;
}
.weather-slot {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
gap: 0.3vh;
}
.weather-slot-icon {
width: 2em;
height: 2em;
}
.weather-slot-icon svg { width: 100%; height: 100%; }
.weather-slot-label {
color: var(--stone);
font-size: 0.8em;
}
.weather-slot-temp {
color: var(--bone);
}
</style>
</head>
<body>
<div id="vanta"></div>
<div id="debug-panel">
<div class="debug-title" onclick="toggleDebugPanel()"><span>DEBUG - Palette: Ash</span><span class="debug-title-toggle" id="debug-toggle-icon">▼</span></div>
<div id="debug-content" class="debug-content">
<div class="debug-row"><span class="debug-label">Time:</span> <span id="dbg-time">--</span></div>
<div class="debug-row"><span class="debug-label">Day%:</span> <span id="dbg-day">--</span></div>
<div class="debug-row"><span class="debug-label">Weather:</span> <span id="dbg-weather">--</span></div>
<div class="debug-row"><span class="debug-label">Theme:</span> <span id="dbg-theme">--</span></div>
<div class="debug-row"><span class="debug-label">Speed:</span> <span id="dbg-speed">--</span></div>
<div class="debug-row"><span class="debug-label">Transition:</span> <span id="dbg-trans">--</span></div>
<div class="debug-section">Effective theme</div>
<div class="debug-row"><span class="libel">skyColor</span> <div class="swatch" id="dbg-t-sky"></div> <span class="hex" id="dbg-h-sky">--</span></div>
<div class="debug-row"><span class="libel">cloudColor</span> <div class="swatch" id="dbg-t-cloud"></div> <span class="hex" id="dbg-h-cloud">--</span></div>
<div class="debug-row"><span class="libel">lightColor</span> <div class="swatch" id="dbg-t-sunLight"></div> <span class="hex" id="dbg-h-sunLight">--</span></div>
<div class="debug-row"><span class="libel">bgColor</span> <div class="swatch" id="dbg-t-bg"></div> <span class="hex" id="dbg-h-bg">--</span></div>
<div class="debug-section">Vanta colors</div>
<div class="debug-row"><span class="libel">skyColor</span> <div class="swatch" id="dbg-v-sky"></div> <span class="hex" id="dbg-vh-sky">--</span></div>
<div class="debug-row"><span class="libel">cloudColor</span> <div class="swatch" id="dbg-v-cloud"></div> <span class="hex" id="dbg-vh-cloud">--</span></div>
<div class="debug-row"><span class="libel">lightColor</span> <div class="swatch" id="dbg-v-sunLight"></div> <span class="hex" id="dbg-vh-sunLight">--</span></div>
<div class="debug-row"><span class="libel">bgColor</span> <div class="swatch" id="dbg-v-bg"></div> <span class="hex" id="dbg-vh-bg">--</span></div>
<div class="debug-section">Theme table</div>
<div id="dbg-theme-table" class="theme-table"></div>
<div id="dbg-color-table" class="theme-table"></div>
</div>
</div>
<div id="info-panel">
<svg id="analog-clock" viewBox="0 0 280 280">
<!-- Clock face -->
<circle cx="140" cy="140" r="135" fill="rgba(28,27,31,0.3)" stroke="var(--bone)" stroke-width="2"/>
<!-- Hour marks -->
<g id="hour-marks"></g>
<!-- Sun orbit (radius 65) -->
<circle cx="140" cy="140" r="65" stroke="var(--gray-dark)" stroke-width="1" fill="none" stroke-dasharray="2,3" opacity="0.6"/>
<!-- Moon orbit (radius 88) -->
<circle cx="140" cy="140" r="88" stroke="var(--gray-dark)" stroke-width="1" fill="none" stroke-dasharray="2,3" opacity="0.6"/>
<!-- Sun arc (from sunrise to sunset) -->
<path id="sun-arc" d="" stroke="var(--orange)" stroke-width="2.5" fill="none"/>
<!-- Moon arc (from moonrise to moonset) -->
<path id="moon-arc" d="" stroke="var(--icon)" stroke-width="2.5" fill="none"/>
<!-- Sun rise arrow (pointing in / up at sunrise) -->
<g id="sun-rise-arrow" fill="var(--orange)">
<polygon points="0,-9 8,0 -9,0"/>
</g>
<!-- Sun set arrow (pointing out / down at sunset) -->
<g id="sun-set-arrow" fill="var(--orange)">
<polygon points="0,9 8,0 -9,0"/>
</g>
<!-- Moon rise arrow -->
<g id="moon-rise-arrow" fill="var(--icon)">
<polygon points="0,-9 8,0 -9,0"/>
</g>
<!-- Moon set arrow -->
<g id="moon-set-arrow" fill="var(--icon)">
<polygon points="0,9 8,0 -9,0"/>
</g>
<!-- Sun zenith marker (diamond shape, small) -->
<g id="sun-zenith-marker" fill="var(--orange)">
<polygon points="0,-5 5,0 0,5 -5,0"/>
</g>
<!-- Moon zenith marker removed -->
<!-- Sun position (rendered after arcs/arrows so it's visible) -->
<circle id="rpg-sun-glow" cx="140" cy="140" r="14" fill="var(--orange)" opacity="0.4"/>
<circle id="rpg-sun" cx="140" cy="140" r="8" fill="var(--orange)"/>
<circle id="rpg-sun-core" cx="140" cy="140" r="3.5" fill="var(--chalk)"/>
<!-- Moon position (with phase) -->
<g id="rpg-moon">
<circle id="rpg-moon-bg" cx="0" cy="0" r="10" fill="var(--dark)" stroke="var(--stone)" stroke-width="1"/>
<path id="rpg-moon-lit" d="" fill="var(--bone)"/>
<path id="rpg-moon-dark" d="" fill="var(--gray)"/>
</g>
<!-- Clock hands (on top of everything) -->
<line id="hour-hand" x1="140" y1="140" x2="140" y2="49" stroke="var(--bone)" stroke-width="7" stroke-linecap="round" opacity="0.7"/>
<line id="minute-hand" x1="140" y1="140" x2="140" y2="28" stroke="var(--bone)" stroke-width="5" stroke-linecap="round" opacity="0.7"/>
<line id="second-hand" x1="140" y1="140" x2="140" y2="20" stroke="var(--red)" stroke-width="2.5" stroke-linecap="round"/>
<circle cx="140" cy="140" r="5" fill="var(--red)"/>
</svg>
<div id="digital-clock">00:00:00</div>
<div class="info-row">
<div class="info-icon">
<svg viewBox="0 0 24 24" fill="var(--orange)"><circle cx="12" cy="12" r="4"/><g stroke="var(--orange)" 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="var(--orange)"><circle cx="12" cy="12" r="4"/><line x1="12" y1="12" x2="12" y2="3" stroke="var(--orange)" stroke-width="2"/></svg>
</div>
<div class="info-content"><span class="info-label">Zenit Sonne</span><span><span class="info-value" id="sun-zenith-value">--:--</span><span class="info-sub" id="sun-zenith-when"></span></span></div>
</div>
<div class="info-row">
<div class="info-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="var(--orange)" 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="var(--dark)"/></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="var(--icon)"><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="var(--icon)" stroke-width="2"><path d="M3 12 A9 9 0 0 1 21 22 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 id="weather-divider" class="weather-divider"></div>
<div id="weather-current" class="weather-current"></div>
<div id="weather-forecast" class="weather-forecast"></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@latest/dist/vanta.clouds2.min.js"></script>
<script>
var effect = (VANTA.CLOUDS2 || VANTA.CLOUDS)({
el: "#vanta",
mouseControls: false,
touchControls: false,
gyroControls: false,
minHeight: 200.00,
minWidth: 200.00,
scale: 1.00,
scaleMobile: 4,
speed: 1.0,
backgroundColor: 0x1c1b1f,
backgroundAlpha: 1,
skyColor: 0x3a4458,
cloudColor: 0x6d6a63,
lightColor: 0xe6dfd1,
texturePath: "./noise.png"
}) || { setOptions: function() {} };
function hexToRgb(h) {
return [(h >> 16) & 255, (h >> 8) & 255, h & 255];
}
function rgbToHex(r, g, b) {
return (Math.round(r) << 16) | (Math.round(g) << 8) | Math.round(b);
}
function lerp(a, b, t) {
return a + (b - a) * t;
}
function lerpColor(c1, c2, t) {
var a = hexToRgb(c1), b = hexToRgb(c2);
return rgbToHex(lerp(a[0], b[0], t), lerp(a[1], b[1], t), lerp(a[2], b[2], t));
}
function smooth(t) {
return t * t * (3 - 2 * t);
}
var CYCLE = 20000;
var PASSAU_LAT = 48.5667;
var PASSAU_LON = 13.4319;
var realDayProgress = 0;
// Read color palette from CSS variables
function getColors() {
var root = getComputedStyle(document.documentElement);
var keys = [
'void', 'panel', 'raised', 'border', 'mid', 'ash',
'icon', 'stone', 'bone', 'chalk',
'ember', 'gold', 'blood', 'rust', 'rot', 'moss', 'steel', 'steelLight', 'violet'
];
var map = {
void: 'black',
panel: 'dark',
raised: 'gray-darker',
border: 'gray-dark',
mid: 'gray',
ash: 'gray-light',
ember: 'orange',
gold: 'yellow',
blood: 'red',
rust: 'brown',
rot: 'pink',
moss: 'green',
steel: 'blue',
steelLight: 'blue-light',
violet: 'purple'
};
var result = {};
for (var i = 0; i < keys.length; i++) {
var ashKey = keys[i];
var cssKey = map[ashKey] || ashKey;
result[ashKey] = root.getPropertyValue('--' + cssKey).trim();
}
return result;
}
function hexToInt(hex) {
return parseInt(hex.replace('#', ''), 16);
}
var ASH = getColors();
// Sun position palette (altitude-based) - all colors from Ash palette
// altitude in radians: -pi/2 (below) to pi/2 (zenith)
var sunPalettes = [
{ alt: -Math.PI / 2, sky: hexToInt(ASH.void), cloud: hexToInt(ASH.void), light: hexToInt(ASH.stone), bg: hexToInt(ASH.void), cloudShadow: hexToInt(ASH.void), sun: hexToInt(ASH.stone), sunGlare: hexToInt(ASH.ash), sunLight: hexToInt(ASH.stone), sunVis: 0 },
{ alt: -0.1, sky: hexToInt(ASH.panel), cloud: hexToInt(ASH.void), light: hexToInt(ASH.stone), bg: hexToInt(ASH.void), cloudShadow: hexToInt(ASH.panel), sun: hexToInt(ASH.stone), sunGlare: hexToInt(ASH.ash), sunLight: hexToInt(ASH.stone), sunVis: 0 },
{ alt: 0.0, sky: hexToInt(ASH.rust), cloud: hexToInt(ASH.border), light: hexToInt(ASH.gold), bg: hexToInt(ASH.rust), cloudShadow: hexToInt(ASH.raised), sun: hexToInt(ASH.ember), sunGlare: hexToInt(ASH.gold), sunLight: hexToInt(ASH.ember), sunVis: 0.8 },
{ alt: 0.15, sky: hexToInt(ASH.blood), cloud: hexToInt(ASH.border), light: hexToInt(ASH.ember), bg: hexToInt(ASH.blood), cloudShadow: hexToInt(ASH.raised), sun: hexToInt(ASH.ember), sunGlare: hexToInt(ASH.ember), sunLight: hexToInt(ASH.ember), sunVis: 0.7 },
{ alt: 0.4, sky: hexToInt(ASH.steel), cloud: hexToInt(ASH.border), light: hexToInt(ASH.bone), bg: hexToInt(ASH.steel), cloudShadow: hexToInt(ASH.steel), sun: hexToInt(ASH.chalk), sunGlare: hexToInt(ASH.bone), sunLight: hexToInt(ASH.chalk), sunVis: 1 },
{ alt: Math.PI / 2, sky: hexToInt(ASH.steelLight), cloud: hexToInt(ASH.border), light: hexToInt(ASH.chalk), bg: hexToInt(ASH.steelLight), cloudShadow: hexToInt(ASH.steelLight), sun: hexToInt(ASH.chalk), sunGlare: hexToInt(ASH.bone), sunLight: hexToInt(ASH.chalk), sunVis: 1 }
];
// Night palette based on moon phase (0 = new, 0.5 = full, 1 = new) - Ash colors
var nightPaletteNew = { sky: hexToInt(ASH.void), cloud: hexToInt(ASH.void), light: hexToInt(ASH.stone), bg: hexToInt(ASH.void), cloudShadow: hexToInt(ASH.void), sun: hexToInt(ASH.stone), sunGlare: hexToInt(ASH.ash), sunLight: hexToInt(ASH.stone) };
var nightPaletteFull = { sky: hexToInt(ASH.panel), cloud: hexToInt(ASH.border), light: hexToInt(ASH.icon), bg: hexToInt(ASH.panel), cloudShadow: hexToInt(ASH.panel), sun: hexToInt(ASH.icon), sunGlare: hexToInt(ASH.mid), sunLight: hexToInt(ASH.icon) };
var MIN_NIGHT_BRIGHTNESS = 0.25; // minimum night brightness (even at new moon)
function getDayColor(sunAlt) {
// Find palette segment based on sun altitude
var palette;
if (sunAlt <= sunPalettes[0].alt) {
palette = sunPalettes[0];
} else if (sunAlt >= sunPalettes[sunPalettes.length - 1].alt) {
palette = sunPalettes[sunPalettes.length - 1];
} else {
for (var i = 0; i < sunPalettes.length - 1; i++) {
if (sunAlt >= sunPalettes[i].alt && sunAlt <= sunPalettes[i + 1].alt) {
var span = sunPalettes[i + 1].alt - sunPalettes[i].alt;
var localT = span === 0 ? 0 : (sunAlt - sunPalettes[i].alt) / span;
var s = smooth(localT);
palette = {
sky: lerpColor(sunPalettes[i].sky, sunPalettes[i + 1].sky, s),
cloud: lerpColor(sunPalettes[i].cloud, sunPalettes[i + 1].cloud, s),
light: lerpColor(sunPalettes[i].light, sunPalettes[i + 1].light, s),
bg: lerpColor(sunPalettes[i].bg, sunPalettes[i + 1].bg, s),
cloudShadow: lerpColor(sunPalettes[i].cloudShadow, sunPalettes[i + 1].cloudShadow, s),
sun: lerpColor(sunPalettes[i].sun, sunPalettes[i + 1].sun, s),
sunGlare: lerpColor(sunPalettes[i].sunGlare, sunPalettes[i + 1].sunGlare, s),
sunLight: lerpColor(sunPalettes[i].sunLight, sunPalettes[i + 1].sunLight, s),
sunVis: sunPalettes[i].sunVis + (sunPalettes[i + 1].sunVis - sunPalettes[i].sunVis) * s
};
break;
}
}
}
return palette;
}
function getNightColor(moonIllum) {
// moonIllum.fraction: 0 (new moon) to 1 (full moon)
// Apply minimum brightness
var brightness = MIN_NIGHT_BRIGHTNESS + (1 - MIN_NIGHT_BRIGHTNESS) * moonIllum.fraction;
// Interpolate between new and full moon palette based on fraction
var s = moonIllum.fraction;
var c = {
sky: lerpColor(nightPaletteNew.sky, nightPaletteFull.sky, s),
cloud: lerpColor(nightPaletteNew.cloud, nightPaletteFull.cloud, s),
light: lerpColor(nightPaletteNew.light, nightPaletteFull.light, s),
bg: lerpColor(nightPaletteNew.bg, nightPaletteFull.bg, s),
cloudShadow: lerpColor(nightPaletteNew.cloudShadow, nightPaletteFull.cloudShadow, s),
sun: lerpColor(nightPaletteNew.sun, nightPaletteFull.sun, s),
sunGlare: lerpColor(nightPaletteNew.sunGlare, nightPaletteFull.sunGlare, s),
sunLight: lerpColor(nightPaletteNew.sunLight, nightPaletteFull.sunLight, s)
};
// Apply brightness factor (relative to mid)
return c;
}
function getColor(t) {
// t is no longer used - now uses real sun/moon
var now = new Date();
var sunPos = SunCalc.getPosition(now, PASSAU_LAT, PASSAU_LON);
var moonIllum = SunCalc.getMoonIllumination(now);
if (sunPos.altitude > 0) {
return getDayColor(sunPos.altitude);
} else {
return getNightColor(moonIllum);
}
}
function adjustColor(color, factor) {
var r = Math.min(255, Math.floor(((color >> 16) & 255) * factor));
var g = Math.min(255, Math.floor(((color >> 8) & 255) * factor));
var b = Math.min(255, Math.floor((color & 255) * factor));
return (r << 16) | (g << 8) | b;
}
function shiftColor(color, delta) {
var r = Math.max(0, Math.min(255, ((color >> 16) & 255) + delta));
var g = Math.max(0, Math.min(255, ((color >> 8) & 255) + delta));
var b = Math.max(0, Math.min(255, (color & 255) + delta));
return (r << 16) | (g << 8) | b;
}
function getWeatherModifiers(code) {
// Returns {cloudDark: factor, cloudLight: factor, skyShift: shift, bgShift: shift}
// code is WMO weather code from open-meteo
if (code === 0) {
// Clear sky - lighter clouds, brighter sky
return { cloudDark: 0.7, cloudLight: 1.3, skyShift: 15, bgShift: 15 };
} else if (code >= 1 && code <= 3) {
// Mainly clear to overcast - neutral
return { cloudDark: 0.95, cloudLight: 1.0, skyShift: 0, bgShift: -5 };
} else if (code === 45 || code === 48) {
// Fog - cool, washed out
return { cloudDark: 1.1, cloudLight: 0.85, skyShift: 10, bgShift: 10 };
} else if ((code >= 51 && code <= 57) || (code >= 61 && code <= 67)) {
// Drizzle / Rain - darker, slightly cooler
return { cloudDark: 1.3, cloudLight: 0.7, skyShift: -15, bgShift: -20 };
} else if ((code >= 71 && code <= 77) || (code >= 85 && code <= 86)) {
// Snow - brighter (light reflection)
return { cloudDark: 0.7, cloudLight: 1.5, skyShift: 25, bgShift: 25 };
} else if (code >= 80 && code <= 82) {
// Rain showers - darker
return { cloudDark: 1.4, cloudLight: 0.6, skyShift: -20, bgShift: -25 };
} else if (code >= 95) {
// Thunderstorm - very dark, dramatic
return { cloudDark: 1.6, cloudLight: 0.4, skyShift: -30, bgShift: -35 };
}
return { cloudDark: 1.0, cloudLight: 1.0, skyShift: 0, bgShift: 0 };
}
var currentWeatherCode = -1;
function applyWeatherModifiers(c, code) {
var m = getWeatherModifiers(code);
return {
sky: c.sky,
cloud: adjustColor(c.cloud, m.cloudLight),
cloudShadow: adjustColor(c.cloudShadow, m.cloudDark),
sun: adjustColor(c.sun, m.cloudLight),
sunGlare: adjustColor(c.sunGlare, m.cloudLight),
sunLight: adjustColor(c.sunLight, m.cloudLight),
bg: shiftColor(c.bg, m.bgShift),
sunVis: c.sunVis
};
}
// ============ Clock & Sun/Moon Calculations ============
var hourHand = document.getElementById('hour-hand');
var minuteHand = document.getElementById('minute-hand');
var secondHand = document.getElementById('second-hand');
var hourMarks = document.getElementById('hour-marks');
var rpgSunArc = document.getElementById('sun-arc');
var rpgMoonArc = document.getElementById('moon-arc');
var rpgSun = document.getElementById('rpg-sun');
var rpgSunGlow = document.getElementById('rpg-sun-glow');
var rpgSunCore = document.getElementById('rpg-sun-core');
var rpgMoon = document.getElementById('rpg-moon');
var rpgMoonLit = document.getElementById('rpg-moon-lit');
var rpgMoonDark = document.getElementById('rpg-moon-dark');
var sunRiseArrow = document.getElementById('sun-rise-arrow');
var sunSetArrow = document.getElementById('sun-set-arrow');
var moonRiseArrow = document.getElementById('moon-rise-arrow');
var moonsetArrow = document.getElementById('moon-set-arrow');
var sunZenithMarker = document.getElementById('sun-zenith-marker');
var SUN_ORBIT_RADIUS = 65;
var MOON_ORBIT_RADIUS = 88;
// Generate hour marks
for (var i = 0; i < 12; i++) {
var angle = (i * 30) * Math.PI / 180;
var isMain = i % 3 === 0;
var innerR = isMain ? 118 : 122;
var outerR = isMain ? 130 : 128;
var x1 = 140 + Math.sin(angle) * innerR;
var y1 = 140 - Math.cos(angle) * innerR;
var x2 = 140 + Math.sin(angle) * outerR;
var y2 = 140 - Math.cos(angle) * outerR;
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', ASH.bone);
line.setAttribute('stroke-width', isMain ? 5 : 3);
line.setAttribute('stroke-linecap', 'round');
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';
}
var CENTER_X = 140;
var CENTER_Y = 140;
function angleForTime(date) {
// 0:00 = bottom, 6:00 = left, 12:00 = top, 18:00 = right
var hours = date.getHours() + date.getMinutes() / 60;
return ((hours / 24) * 360 + 90) * Math.PI / 180;
}
function pointOnCircle(date, radius) {
var angle = angleForTime(date);
return {
x: CENTER_X + Math.cos(angle) * radius,
y: CENTER_Y + Math.sin(angle) * radius
};
}
function arcBetween(startDate, endDate, radius) {
var sp = pointOnCircle(startDate, radius);
var ep = pointOnCircle(endDate, radius);
var startA = angleForTime(startDate) * 180 / Math.PI;
var endA = angleForTime(endDate) * 180 / Math.PI;
var diff = endA - startA;
if (diff < 0) diff += 360;
var largeArc = diff > 180 ? 1 : 0;
return 'M ' + sp.x + ' ' + sp.y + ' A ' + radius + ' ' + radius + ' 0 ' + largeArc + ' 1 ' + ep.x + ' ' + ep.y;
}
function getMoonPosition(now, moonTimes) {
try {
var rise = moonTimes.rise;
var set = moonTimes.set;
if (!rise || !set) {
return { point: pointOnCircle(now, MOON_ORBIT_RADIUS), isVisible: false };
}
var isVisible;
if (rise <= set) {
isVisible = (now >= rise && now <= set);
} else {
isVisible = (now >= rise || now <= set);
}
if (!isVisible) {
return { point: pointOnCircle(now, MOON_ORBIT_RADIUS), isVisible: false };
}
var riseAngle = angleForTime(rise);
var setAngle = angleForTime(set);
var diff = setAngle - riseAngle;
if (diff > Math.PI) diff -= 2 * Math.PI;
if (diff < -Math.PI) diff += 2 * Math.PI;
var progress;
if (rise <= set) {
progress = (now.getTime() - rise.getTime()) / (set.getTime() - rise.getTime());
} else {
if (now >= rise) {
var endMs = set.getTime() + 24 * 3600 * 1000;
progress = (now.getTime() - rise.getTime()) / (endMs - rise.getTime());
} else {
progress = (now.getTime() + 24 * 3600 * 1000 - rise.getTime()) /
(set.getTime() + 24 * 3600 * 1000 - rise.getTime());
}
}
var moonAngle = riseAngle + diff * progress;
return {
point: {
x: CENTER_X + Math.cos(moonAngle) * MOON_ORBIT_RADIUS,
y: CENTER_Y + Math.sin(moonAngle) * MOON_ORBIT_RADIUS
},
isVisible: true
};
} catch (e) {
return { point: pointOnCircle(now, MOON_ORBIT_RADIUS), isVisible: false };
}
}
function renderMoonPhaseIcon(phase, fraction) {
var icon = document.getElementById('moon-phase-icon');
icon.innerHTML = '';
var r = 9;
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('fill', ASH.panel);
bg.setAttribute('stroke', ASH.stone);
bg.setAttribute('stroke-width', '0.5');
icon.appendChild(bg);
var rx = r * (1 - 2 * fraction);
var path;
if (phase < 0.5) {
if (rx > 0) {
path = 'M 12 3 A 9 9 0 0 0 12 21 A ' + rx + ' 9 0 0 1 12 3 Z';
} else {
path = 'M 12 3 A 9 9 0 0 0 12 21 A ' + Math.abs(rx) + ' 9 0 0 0 12 3 Z';
}
} else {
if (rx > 0) {
path = 'M 12 3 A 9 9 0 0 1 12 21 A ' + rx + ' 9 0 0 0 12 3 Z';
} else {
path = 'M 12 3 A 9 9 0 0 1 12 21 A ' + Math.abs(rx) + ' 9 0 0 1 12 3 Z';
}
}
var lit = document.createElementNS('http://www.w3.org/2000/svg', 'path');
lit.setAttribute('d', path);
lit.setAttribute('fill', ASH.bone);
icon.appendChild(lit);
}
function renderRpgMoon(phase, fraction) {
var r = 10;
rpgMoonDark.setAttribute('d', '');
var rx = r * (1 - 2 * fraction);
var path;
if (phase < 0.5) {
if (rx > 0) {
path = 'M 0 -10 A 10 10 0 0 0 0 10 A ' + rx + ' 10 0 0 1 0 -10 Z';
} else {
path = 'M 0 -10 A 10 10 0 0 0 0 10 A ' + Math.abs(rx) + ' 10 0 0 0 0 -10 Z';
}
} else {
if (rx > 0) {
path = 'M 0 -10 A 10 10 0 0 1 0 10 A ' + rx + ' 10 0 0 0 0 -10 Z';
} else {
path = 'M 0 -10 A 10 10 0 0 1 0 10 A ' + Math.abs(rx) + ' 10 0 0 1 0 -10 Z';
}
}
rpgMoonLit.setAttribute('d', path);
}
function updateClockAndEvents() {
var now = new Date();
// Calculate real day progress based on sun position
// 0% = midnight, 50% = noon, 100% = next midnight
var sunTimes = SunCalc.getTimes(now, PASSAU_LAT, PASSAU_LON);
var sunrise = sunTimes.sunrise;
var sunset = sunTimes.sunset;
if (now < sunrise) {
var prevSunset = new Date(sunset.getTime() - 24 * 3600 * 1000);
realDayProgress = (now.getTime() - prevSunset.getTime()) / (sunrise.getTime() - prevSunset.getTime());
} else if (now < sunset) {
realDayProgress = 0.5 + (now.getTime() - sunrise.getTime()) / (sunset.getTime() - sunrise.getTime()) * 0.5;
} else {
realDayProgress = 0.5 + (now.getTime() - sunset.getTime()) / (sunrise.getTime() + 24 * 3600 * 1000 - sunset.getTime()) * 0.5;
}
realDayProgress = Math.max(0, Math.min(1, realDayProgress));
// 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 + ' 140 140)');
minuteHand.setAttribute('transform', 'rotate(' + minuteAngle + ' 140 140)');
secondHand.setAttribute('transform', 'rotate(' + secondAngle + ' 140 140)');
// 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 : '';
// Sun zenith (solar noon) - find next occurrence
var sunZenith = sunTimes.solarNoon;
var sunZenithWhen = sunZenith > now ? 'heute' : 'morgen';
if (sunZenith <= now) {
var tomorrow = new Date(now);
tomorrow.setDate(tomorrow.getDate() + 1);
sunZenith = SunCalc.getTimes(tomorrow, PASSAU_LAT, PASSAU_LON).solarNoon;
sunZenithWhen = formatDateLong(tomorrow);
}
document.getElementById('sun-zenith-value').textContent = formatTimeHM(sunZenith);
document.getElementById('sun-zenith-when').textContent = '· ' + sunZenithWhen;
// 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 : '';
// Sun arc
if (sunTimes.sunrise && sunTimes.sunset) {
rpgSunArc.setAttribute('d', arcBetween(sunTimes.sunrise, sunTimes.sunset, SUN_ORBIT_RADIUS));
}
// Moon arc
if (moonTimes.rise && moonTimes.set) {
rpgMoonArc.setAttribute('d', arcBetween(moonTimes.rise, moonTimes.set, MOON_ORBIT_RADIUS));
}
// Sun position
var sunP = pointOnCircle(now, SUN_ORBIT_RADIUS);
rpgSun.setAttribute('cx', sunP.x);
rpgSun.setAttribute('cy', sunP.y);
rpgSunGlow.setAttribute('cx', sunP.x);
rpgSunGlow.setAttribute('cy', sunP.y);
rpgSunCore.setAttribute('cx', sunP.x);
rpgSunCore.setAttribute('cy', sunP.y);
var isDaytime = now >= sunTimes.sunrise && now <= sunTimes.sunset;
rpgSun.setAttribute('opacity', isDaytime ? '1' : '0.35');
rpgSunGlow.setAttribute('opacity', isDaytime ? '0.4' : '0.15');
rpgSunCore.setAttribute('opacity', isDaytime ? '1' : '0.35');
// Moon position - based on current time on the orbit
var moonP = pointOnCircle(now, MOON_ORBIT_RADIUS);
rpgMoon.setAttribute('transform', 'translate(' + moonP.x + ', ' + moonP.y + ')');
var moonVisible = false;
if (moonTimes.rise && moonTimes.set) {
if (moonTimes.rise < moonTimes.set) {
moonVisible = (now >= moonTimes.rise && now <= moonTimes.set);
} else {
moonVisible = (now >= moonTimes.rise || now <= moonTimes.set);
}
}
rpgMoon.setAttribute('opacity', moonVisible ? '1' : '0.6');
// Sun rise/set arrows
var srP = pointOnCircle(sunTimes.sunrise, SUN_ORBIT_RADIUS);
sunRiseArrow.setAttribute('transform', 'translate(' + srP.x + ', ' + srP.y + ')');
var ssP = pointOnCircle(sunTimes.sunset, SUN_ORBIT_RADIUS);
sunSetArrow.setAttribute('transform', 'translate(' + ssP.x + ', ' + ssP.y + ')');
// Sun zenith marker (next occurrence)
var sunZenithDate = sunTimes.solarNoon > now ? sunTimes.solarNoon : SunCalc.getTimes(new Date(now.getTime() + 86400000), PASSAU_LAT, PASSAU_LON).solarNoon;
var szP = pointOnCircle(sunZenithDate, SUN_ORBIT_RADIUS);
sunZenithMarker.setAttribute('transform', 'translate(' + szP.x + ', ' + szP.y + ')');
// Moon rise/set arrows
if (moonTimes.rise) {
var mrP = pointOnCircle(moonTimes.rise, MOON_ORBIT_RADIUS);
moonRiseArrow.setAttribute('transform', 'translate(' + mrP.x + ', ' + mrP.y + ')');
moonRiseArrow.setAttribute('opacity', '1');
} else {
moonRiseArrow.setAttribute('opacity', '0');
}
if (moonTimes.set) {
var msP = pointOnCircle(moonTimes.set, MOON_ORBIT_RADIUS);
moonsetArrow.setAttribute('transform', 'translate(' + msP.x + ', ' + msP.y + ')');
moonsetArrow.setAttribute('opacity', '1');
} else {
moonsetArrow.setAttribute('opacity', '0');
}
// Moon phase
var moonIllum = SunCalc.getMoonIllumination(now);
document.getElementById('moon-phase-text').textContent =
getMoonPhaseText(moonIllum.phase) + ' ' + Math.round(moonIllum.fraction * 100) + '%';
renderMoonPhaseIcon(moonIllum.phase, moonIllum.fraction);
renderRpgMoon(moonIllum.phase, moonIllum.fraction);
}
var startTime = Date.now();
var weatherThemes = {
sunny: {
skyColor: hexToInt(ASH.steelLight),
cloudColor: hexToInt(ASH.stone),
lightColor: hexToInt(ASH.chalk),
backgroundColor: hexToInt(ASH.mid),
speed: 1.0
},
cloudy: {
skyColor: hexToInt(ASH.steel),
cloudColor: hexToInt(ASH.mid),
lightColor: hexToInt(ASH.bone),
backgroundColor: hexToInt(ASH.border),
speed: 1.2
},
stormy: {
skyColor: hexToInt(ASH.steel),
cloudColor: hexToInt(ASH.panel),
lightColor: hexToInt(ASH.stone),
backgroundColor: hexToInt(ASH.void),
speed: 2.5
},
night: {
skyColor: hexToInt(ASH.void),
cloudColor: hexToInt(ASH.raised),
lightColor: hexToInt(ASH.icon),
backgroundColor: hexToInt(ASH.void),
speed: 0.4
}
};
function getWeatherThemeName(code) {
if (code === 0) return 'sunny';
if (code >= 1 && code <= 3) return 'cloudy';
if (code >= 45 && code <= 48) return 'cloudy';
if ((code >= 51 && code <= 67) || (code >= 80 && code <= 82) || (code >= 71 && code <= 77) || (code >= 85 && code <= 86)) return 'stormy';
if (code >= 95) return 'stormy';
return 'cloudy';
}
function mixColor(color1, color2, factor) {
if (color1 === undefined || color2 === undefined) return color1 || color2;
var r1 = (color1 >> 16) & 255;
var g1 = (color1 >> 8) & 255;
var b1 = color1 & 255;
var r2 = (color2 >> 16) & 255;
var g2 = (color2 >> 8) & 255;
var b2 = color2 & 255;
var r = Math.round(r1 + (r2 - r1) * factor);
var g = Math.round(g1 + (g2 - g1) * factor);
var b = Math.round(b1 + (b2 - b1) * factor);
return (r << 16) | (g << 8) | b;
}
var prevThemeColors = null;
var targetThemeColors = null;
var transitionStartTime = 0;
var TRANSITION_DURATION = 5 * 60 * 1000;
var currentWeatherThemeName = null;
var currentThemeSpeed = 0.4;
function setWeatherTheme(themeName) {
if (currentWeatherThemeName !== themeName) {
prevThemeColors = getEffectiveThemeColors();
targetThemeColors = weatherThemes[themeName] || weatherThemes.cloudy;
transitionStartTime = Date.now();
currentWeatherThemeName = themeName;
}
}
function getEffectiveThemeColors() {
if (!targetThemeColors) return null;
var elapsed = Date.now() - transitionStartTime;
var smoothT;
if (!prevThemeColors || elapsed >= TRANSITION_DURATION) {
smoothT = 1;
} else {
var t = elapsed / TRANSITION_DURATION;
smoothT = t * t * (3 - 2 * t);
}
var result = {};
var keys = ['skyColor', 'cloudColor', 'lightColor', 'backgroundColor'];
for (var i = 0; i < keys.length; i++) {
var k = keys[i];
if (prevThemeColors && smoothT < 1) {
result[k] = mixColor(prevThemeColors[k], targetThemeColors[k], smoothT);
} else {
result[k] = targetThemeColors[k];
}
}
if (smoothT >= 1 && prevThemeColors !== targetThemeColors) {
prevThemeColors = targetThemeColors;
currentThemeSpeed = targetThemeColors.speed;
}
return result;
}
function applyWeatherTheme(c) {
var themeColors = getEffectiveThemeColors();
if (!themeColors) return c;
var mix = 0.6;
return {
sky: mixColor(c.sky, themeColors.skyColor, mix),
cloud: mixColor(c.cloud, themeColors.cloudColor, mix),
light: mixColor(c.sun, themeColors.lightColor, mix * 0.5),
bg: mixColor(c.bg, themeColors.backgroundColor, mix)
};
}
function getCurrentDayProgress() {
var now = new Date();
var startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
return (now.getTime() - startOfDay.getTime()) / 86400000;
}
function toggleDebugPanel() {
var content = document.getElementById('debug-content');
var icon = document.getElementById('debug-toggle-icon');
if (content.style.display === 'none') {
content.style.display = '';
icon.textContent = '▼';
} else {
content.style.display = 'none';
icon.textContent = '▶';
}
}
function updateDebug(t, palette, vantaColors, themeName, realDayProgress) {
var now = new Date();
var hh = now.getHours(), mm = now.getMinutes(), ss = now.getSeconds();
document.getElementById('dbg-time').textContent =
(hh < 10 ? '0' : '') + hh + ':' + (mm < 10 ? '0' : '') + mm + ':' + (ss < 10 ? '0' : '') + ss;
document.getElementById('dbg-day').textContent = realDayProgress != null ? (realDayProgress * 100).toFixed(1) + '%' : (t * 100).toFixed(1) + '%';
document.getElementById('dbg-weather').textContent =
currentWeatherCode + ' (' + (weatherThemes[themeName] ? themeName : '?') + ')';
document.getElementById('dbg-theme').textContent = themeName || '-';
document.getElementById('dbg-speed').textContent = currentThemeSpeed.toFixed(2);
var elapsed = Date.now() - transitionStartTime;
var transPct = targetThemeColors ? Math.min(100, (elapsed / TRANSITION_DURATION) * 100) : 100;
document.getElementById('dbg-trans').textContent = transPct.toFixed(0) + '%';
var themeColors = getEffectiveThemeColors();
if (themeColors) {
var keys = ['skyColor', 'cloudColor', 'lightColor', 'backgroundColor'];
var keyMap = {skyColor: 'sky', cloudColor: 'cloud', lightColor: 'sunLight', backgroundColor: 'bg'};
for (var i = 0; i < keys.length; i++) {
var k = keys[i];
var kk = keyMap[k];
var c = themeColors[k];
document.getElementById('dbg-t-' + kk).style.backgroundColor = '#' + ('00000' + c.toString(16)).slice(-6);
document.getElementById('dbg-h-' + kk).textContent = '#' + ('00000' + c.toString(16)).slice(-6);
}
}
if (vantaColors) {
var vKeys = ['sky', 'cloud', 'light', 'bg'];
var vKeyMap = {sky: 'sky', cloud: 'cloud', light: 'sunLight', bg: 'bg'};
for (var j = 0; j < vKeys.length; j++) {
var vk = vKeys[j];
var vc = vantaColors[vk];
var kk = vKeyMap[vk];
document.getElementById('dbg-v-' + kk).style.backgroundColor = '#' + ('00000' + vc.toString(16)).slice(-6);
document.getElementById('dbg-vh-' + kk).textContent = '#' + ('00000' + vc.toString(16)).slice(-6);
}
}
// Theme table
var themeNames = ['sunny', 'cloudy', 'stormy', 'night'];
var themeFields = [
{ key: 'skyColor', label: 'sky' },
{ key: 'cloudColor', label: 'cloud' },
{ key: 'lightColor', label: 'light' },
{ key: 'backgroundColor', label: 'bg' }
];
var tableHtml = '<div class="theme-table-row theme-table-header">';
tableHtml += '<span>Theme (Ash)</span>';
for (var t = 0; t < themeFields.length; t++) {
tableHtml += '<span title="' + themeFields[t].key + '">' + themeFields[t].label + '</span>';
}
tableHtml += '<span>speed</span>';
tableHtml += '</div>';
for (var n = 0; n < themeNames.length; n++) {
var tn = themeNames[n];
var th = weatherThemes[tn];
tableHtml += '<div class="theme-table-row">';
tableHtml += '<span class="theme-table-name">' + tn + '</span>';
for (var f = 0; f < themeFields.length; f++) {
var fc = th[themeFields[f].key];
tableHtml += '<div class="theme-table-mini-swatch" style="background:#' + ('00000' + fc.toString(16)).slice(-6) + '"></div>';
}
tableHtml += '<span class="theme-table-speed">' + th.speed + '</span>';
tableHtml += '</div>';
}
document.getElementById('dbg-theme-table').innerHTML = tableHtml;
// Color palette table - all colors from the palette
var colorKeys = [
'black', 'dark', 'gray-darker', 'gray-dark', 'gray', 'gray-light',
'icon', 'stone', 'bone', 'chalk',
'orange', 'yellow', 'red', 'brown', 'pink', 'green', 'blue', 'blue-light', 'purple'
];
var dbgRoot = getComputedStyle(document.documentElement);
var colorHtml = '<div class="theme-table-header">Palette</div>';
for (var k = 0; k < colorKeys.length; k++) {
var ck = colorKeys[k];
var cv = ASH[ck] || dbgRoot.getPropertyValue('--' + ck).trim();
colorHtml += '<div class="palette-table-row">' +
'<span class="theme-table-name">' + ck + '</span>' +
'<div class="theme-table-mini-swatch" style="background:' + cv + '"></div>' +
'<span class="theme-table-speed">' + cv + '</span>' +
'</div>';
}
document.getElementById('dbg-color-table').innerHTML = colorHtml;
}
function animate() {
var t = (Date.now() - startTime) / CYCLE % 1;
var c = getColor(t);
c = applyWeatherTheme(c);
effect.setOptions({
skyColor: c.sky,
cloudColor: c.cloud,
lightColor: c.light,
backgroundColor: c.bg,
backgroundAlpha: 0,
speed: currentThemeSpeed
});
document.body.style.backgroundColor = '#' + ('00000' + c.sky.toString(16)).slice(-6);
updateDebug(t, null, c, currentWeatherThemeName, realDayProgress);
requestAnimationFrame(animate);
}
function tick() {
updateClockAndEvents();
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 = 'var(--bone)';
switch (code) {
case 0:
return '<svg viewBox="0 0 24 24" fill="' + c + '"><circle cx="12" cy="12" r="5"/><g stroke="' + c + '" stroke-width="2" stroke-linecap="round"><line x1="12" y1="2" x2="12" y2="4"/><line x1="12" y1="20" x2="12" y2="22"/><line x1="2" y1="12" x2="4" y2="12"/><line x1="20" y1="12" x2="22" y2="12"/><line x1="4.9" y1="4.9" x2="6.3" y2="6.3"/><line x1="17.7" y1="17.7" x2="19.1" y2="19.1"/><line x1="4.9" y1="19.1" x2="6.3" y2="17.7"/><line x1="17.7" y1="6.3" x2="19.1" y2="4.9"/></g></svg>';
case 1:
case 2:
return '<svg viewBox="0 0 24 24" fill="none" stroke="' + c + '" stroke-width="2"><circle cx="8" cy="8" r="3" fill="' + c + '"/><line x1="8" y1="2" x2="8" y2="3"/><line x1="2" y1="8" x2="3" y2="8"/><line x1="3.5" y1="3.5" x2="4.2" y2="4.2"/><line x1="12.5" y1="3.5" x2="11.8" y2="4.2"/><path d="M10 14 a4 4 0 0 1 4 -4 a4 4 0 0 1 4 4 a3 3 0 0 1 0 6 h-8 a3 3 0 0 1 0 -6 z" fill="var(--stone)" stroke="none"/></svg>';
case 3:
return '<svg viewBox="0 0 24 24" fill="var(--stone)"><path d="M6 18 a4 4 0 0 1 -2 -7 a5 5 0 0 1 9 -3 a4 4 0 0 1 7 1 a3 3 0 0 1 0 6 h-14 z"/></svg>';
case 45:
case 48:
return '<svg viewBox="0 0 24 24" fill="none" stroke="' + c + '" stroke-width="2"><path d="M6 18 a4 4 0 0 1 -2 -7 a5 5 0 0 1 9 -3 a4 4 0 0 1 7 1 a3 3 0 0 1 0 6 h-14 z" fill="var(--stone)" stroke="' + c + '"/><line x1="8" y1="20" x2="8" y2="22"/><line x1="12" y1="20" x2="12" y2="22"/><line x1="16" y1="20" x2="16" y2="22"/></svg>';
case 51:
case 55:
return '<svg viewBox="0 0 24 24" fill="var(--stone)"><path d="M6 14 a4 4 0 0 1 -2 -7 a5 5 0 0 1 9 -3 a4 4 0 0 1 7 1 a3 3 0 0 1 0 6 h-14 z"/><g stroke="var(--blue)" stroke-width="1.5" stroke-linecap="round"><line x1="8" y1="17" x2="7" y2="20"/><line x1="13" y1="17" x2="12" y2="20"/><line x1="17" y1="17" x2="16" y2="20"/></g></svg>';
case 53:
return '<svg viewBox="0 0 24 24" fill="var(--stone)"><path d="M6 14 a4 4 0 0 1 -2 -7 a5 5 0 0 1 9 -3 a4 4 0 0 1 7 1 a3 3 0 0 1 0 6 h-14 z"/><g stroke="var(--blue)" stroke-width="1.5" stroke-linecap="round"><line x1="9" y1="16" x2="8" y2="20"/><line x1="13" y1="16" x2="12" y2="20"/></g></svg>';
case 61:
case 80:
return '<svg viewBox="0 0 24 24" fill="var(--stone)"><path d="M6 13 a4 4 0 0 1 -2 -7 a5 5 0 0 1 9 -3 a4 4 0 0 1 7 1 a3 3 0 0 1 0 6 h-14 z"/><g stroke="var(--blue)" stroke-width="1.5" stroke-linecap="round"><line x1="8" y1="16" x2="7" y2="21"/><line x1="13" y1="16" x2="12" y2="21"/><line x1="17" y1="16" x2="16" y2="21"/></g></svg>';
case 63:
case 81:
return '<svg viewBox="0 0 24 24" fill="var(--stone)"><path d="M6 13 a4 4 0 0 1 -2 -7 a5 5 0 0 1 9 -3 a4 4 0 0 1 7 1 a3 3 0 0 1 0 6 h-14 z"/><g stroke="var(--blue)" stroke-width="1.5" stroke-linecap="round"><line x1="9" y1="15" x2="7" y2="21"/><line x1="13" y1="15" x2="11" y2="21"/><line x1="17" y1="15" x2="15" y2="21"/></g></svg>';
case 65:
case 82:
return '<svg viewBox="0 0 24 24" fill="var(--stone)"><path d="M6 13 a4 4 0 0 1 -2 -7 a5 5 0 0 1 9 -3 a4 4 0 0 1 7 1 a3 3 0 0 1 0 6 h-14 z"/><g stroke="var(--blue)" stroke-width="1.5" stroke-linecap="round"><line x1="7" y1="15" x2="4" y2="22"/><line x1="11" y1="15" x2="8" y2="22"/><line x1="15" y1="15" x2="12" y2="22"/><line x1="19" y1="15" x2="16" y2="22"/></g></svg>';
case 71:
case 73:
case 75:
case 77:
case 85:
case 86:
return '<svg viewBox="0 0 24 24" fill="var(--stone)"><path d="M6 13 a4 4 0 0 1 -2 -7 a5 5 0 0 1 9 -3 a4 4 0 0 1 7 1 a3 3 0 0 1 0 6 h-14 z"/><g fill="' + c + '"><circle cx="8" cy="18" r="1.5"/><circle cx="13" cy="19" r="1.5"/><circle cx="17" cy="18" r="1.5"/></g></svg>';
case 95:
case 96:
case 99:
return '<svg viewBox="0 0 24 24" fill="var(--stone)"><path d="M6 13 a4 4 0 0 1 -2 -7 a5 5 0 0 1 9 -3 a4 4 0 0 1 7 1 a3 3 0 0 1 0 6 h-14 z"/><path d="M12 2 L8 11 L12 11 L10 18 L16 9 L12 9 Z" fill="var(--yellow)"/></svg>';
default:
return '<svg viewBox="0 0 24 24" fill="var(--stone)"><circle cx="12" cy="12" r="6"/></svg>';
}
}
function fetchWeather() {
var url = 'https://api.open-meteo.com/v1/forecast?latitude=' + WEATHER_LAT +
'&longitude=' + WEATHER_LON +
'&current=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;
currentWeatherCode = data.current.weather_code;
setWeatherTheme(getWeatherThemeName(currentWeatherCode));
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 = '<div class="weather-current-icon">' + weatherSVG(current.weather_code) + '</div>' +
'<div><div class="weather-current-temp">' + Math.round(current.temperature_2m) + '°C</div>' +
'<div class="weather-current-detail">' +
'Feuchte ' + Math.round(current.relative_humidity_2m) + '% · Wind ' + Math.round(current.wind_speed_10m) + ' km/h</div></div>';
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), Night (2)
var slots = [8, 14, 20, 2];
var slotsHtml = '';
for (var i = 0; i < slots.length; i++) {
var slotDate = new Date(dayDate);
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 slotCode = hourly.weather_code[slotIdx];
var slotTemp = hourly.temperature_2m[slotIdx];
var slotLabels = ['M', 'Mi', 'A', 'N'];
slotsHtml += '<div class="weather-slot">' +
'<div class="weather-slot-icon">' + weatherSVG(slotCode) + '</div>' +
'<div class="weather-slot-label">' + slotLabels[i] + '</div>' +
'<div class="weather-slot-temp">' + Math.round(slotTemp) + '°</div>' +
'</div>';
}
forecastHtml += '<div class="weather-day">' +
'<div class="weather-day-header">' +
'<span class="weather-day-name">' + dayLabel + '</span>' +
'<span class="weather-day-minmax">' + Math.round(daily.temperature_2m_min[d]) + '° / ' + Math.round(daily.temperature_2m_max[d]) + '°</span>' +
'</div>' +
'<div class="weather-day-slots">' + slotsHtml + '</div>' +
'</div>';
}
document.getElementById('weather-forecast').innerHTML = forecastHtml;
}
// Update weather every 15 minutes
animate();
tick();
fetchWeather();
setInterval(fetchWeather, 15 * 60 * 1000);
tick();
</script>
</body>
</html>