From 0066b6174684f85d1248f9449f43181cdee41e28 Mon Sep 17 00:00:00 2001 From: tomt610 Date: Mon, 12 Jan 2026 23:21:19 +0000 Subject: [PATCH] Add sun/moon traveling across sky based on hour - Sun position calculated from hour (5 AM - 8 PM arc trajectory) - Moon position calculated from hour (8 PM - 5 AM arc trajectory) - Celestial bodies move smoothly without resetting particles - Reduced opacity for sun/moon in foreground mode for readability - Fixed mobile viewport units (dvh/vw) for proper positioning --- src/systems/ui/weatherEffects.js | 146 +++++++++++++++++++++++++++++-- style.css | 126 +++++++++++++++++++++++++- 2 files changed, 262 insertions(+), 10 deletions(-) diff --git a/src/systems/ui/weatherEffects.js b/src/systems/ui/weatherEffects.js index 0d24ca8..727f8cb 100644 --- a/src/systems/ui/weatherEffects.js +++ b/src/systems/ui/weatherEffects.js @@ -9,6 +9,7 @@ import { repairJSON } from '../../utils/jsonRepair.js'; let weatherContainer = null; let currentWeatherType = null; let currentTimeOfDay = null; +let currentHour = null; /** * Parse time string to extract hour (24-hour format) @@ -231,13 +232,58 @@ function createMist() { return container; } +/** + * Calculate sun position based on hour (arc across sky) + * Returns { left: vw%, top: dvh% } + */ +function calculateSunPosition(hour) { + // Daytime is roughly 6 AM to 8 PM (6-20) + // Map hour to position along an arc + // 6 AM = far left, low | 12 PM = center, high | 6 PM = far right, low + + if (hour === null) hour = 12; // Default to noon if unknown + + // Clamp to daytime hours + const clampedHour = Math.max(5, Math.min(20, hour)); + + // Normalize to 0-1 range (5 AM = 0, 20 PM = 1) + const progress = (clampedHour - 5) / 15; + + // Horizontal position: 5% to 85% (left to right) + const left = 5 + progress * 80; + + // Vertical position: parabolic arc (high at noon, low at dawn/dusk) + // At progress 0.5 (noon), top should be ~8% (high) + // At progress 0 or 1, top should be ~35% (low, near horizon) + const normalizedProgress = (progress - 0.5) * 2; // -1 to 1 + const top = 8 + 27 * (normalizedProgress * normalizedProgress); + + return { left, top }; +} + /** * Create clear/sunny weather effect with floating particles and warm glow */ -function createSunshine() { +function createSunshine(hour) { const container = document.createElement('div'); container.className = 'rpg-weather-particles rpg-clear-weather'; + // Create the sun based on current hour + const sunPos = calculateSunPosition(hour); + + const sun = document.createElement('div'); + sun.className = 'rpg-weather-particle rpg-clear-sun'; + sun.style.left = `${sunPos.left}vw`; + sun.style.top = `${sunPos.top}dvh`; + container.appendChild(sun); + + // Create sun glow + const sunGlow = document.createElement('div'); + sunGlow.className = 'rpg-weather-particle rpg-clear-sun-glow'; + sunGlow.style.left = `${sunPos.left}vw`; + sunGlow.style.top = `${sunPos.top}dvh`; + container.appendChild(sunGlow); + // Create warm ambient glow overlay const ambientGlow = document.createElement('div'); ambientGlow.className = 'rpg-weather-particle rpg-clear-ambient-glow'; @@ -284,7 +330,7 @@ function createSunshine() { /** * Create clear nighttime weather effect with moon, stars, and fireflies */ -function createNighttime() { +function createNighttime(hour) { const container = document.createElement('div'); container.className = 'rpg-weather-particles rpg-night-weather'; @@ -293,14 +339,21 @@ function createNighttime() { nightOverlay.className = 'rpg-weather-particle rpg-night-overlay'; container.appendChild(nightOverlay); + // Calculate moon position based on hour + const moonPos = calculateMoonPosition(hour); + // Create the moon const moon = document.createElement('div'); moon.className = 'rpg-weather-particle rpg-night-moon'; + moon.style.left = `${moonPos.left}vw`; + moon.style.top = `${moonPos.top}dvh`; container.appendChild(moon); // Create moon glow const moonGlow = document.createElement('div'); moonGlow.className = 'rpg-weather-particle rpg-night-moon-glow'; + moonGlow.style.left = `${moonPos.left - 3}vw`; + moonGlow.style.top = `${moonPos.top - 3}dvh`; container.appendChild(moonGlow); // Create twinkling stars @@ -383,6 +436,75 @@ function createWind() { return container; } +/** + * Calculate moon position based on hour (arc across sky at night) + * Returns { left: vw%, top: dvh% } + */ +function calculateMoonPosition(hour) { + // Nighttime is roughly 8 PM to 5 AM (20-5) + // Map hour to position along an arc + // 8 PM = far left, low | midnight = center-left, high | 5 AM = far right, low + + if (hour === null) hour = 0; // Default to midnight if unknown + + // Normalize night hours to 0-1 range + // 20 (8 PM) = 0, 0 (midnight) = ~0.44, 5 (5 AM) = 1 + let progress; + if (hour >= 20) { + // 8 PM to midnight: 20-24 maps to 0-0.44 + progress = (hour - 20) / 9; + } else { + // Midnight to 5 AM: 0-5 maps to 0.44-1 + progress = (hour + 4) / 9; + } + + // Horizontal position: 10% to 80% (left to right) + const left = 10 + progress * 70; + + // Vertical position: parabolic arc (high at ~2 AM, low at dusk/dawn) + // Peak should be around progress 0.67 (~2 AM) + const peakProgress = 0.5; + const normalizedProgress = (progress - peakProgress) * 2; // -1 to 1 + const top = 8 + 25 * (normalizedProgress * normalizedProgress); + + return { left, top }; +} + +/** + * Update sun/moon position without recreating the whole effect + */ +function updateCelestialPosition(hour) { + if (!weatherContainer) return false; + + // Update sun position if it exists + const sun = weatherContainer.querySelector('.rpg-clear-sun'); + const sunGlow = weatherContainer.querySelector('.rpg-clear-sun-glow'); + + if (sun && sunGlow) { + const sunPos = calculateSunPosition(hour); + sun.style.left = `${sunPos.left}vw`; + sun.style.top = `${sunPos.top}dvh`; + sunGlow.style.left = `${sunPos.left}vw`; + sunGlow.style.top = `${sunPos.top}dvh`; + return true; + } + + // Update moon position if it exists + const moon = weatherContainer.querySelector('.rpg-night-moon'); + const moonGlow = weatherContainer.querySelector('.rpg-night-moon-glow'); + + if (moon && moonGlow) { + const moonPos = calculateMoonPosition(hour); + moon.style.left = `${moonPos.left}vw`; + moon.style.top = `${moonPos.top}dvh`; + moonGlow.style.left = `${moonPos.left - 3}vw`; + moonGlow.style.top = `${moonPos.top - 3}dvh`; + return true; + } + + return false; +} + /** * Remove current weather effect */ @@ -392,6 +514,7 @@ function removeWeatherEffect() { weatherContainer = null; currentWeatherType = null; currentTimeOfDay = null; + currentHour = null; } } @@ -413,8 +536,16 @@ export function updateWeatherEffect() { const hour = parseHourFromTime(timeStr); const timeOfDay = getTimeOfDay(hour); - // Don't recreate if weather and time haven't changed - if (weatherType === currentWeatherType && timeOfDay === currentTimeOfDay) { + // If only the hour changed (same weather and time of day), just update celestial position + if (weatherType === currentWeatherType && timeOfDay === currentTimeOfDay && hour !== currentHour) { + if (updateCelestialPosition(hour)) { + currentHour = hour; + return; // Successfully updated position without recreating + } + } + + // Don't recreate if nothing has changed + if (weatherType === currentWeatherType && timeOfDay === currentTimeOfDay && hour === currentHour) { return; } @@ -428,6 +559,7 @@ export function updateWeatherEffect() { currentWeatherType = weatherType; currentTimeOfDay = timeOfDay; + currentHour = hour; switch (weatherType) { case 'snow': @@ -442,9 +574,9 @@ export function updateWeatherEffect() { case 'sunny': // Use nighttime effect for clear weather at night if (timeOfDay === 'night') { - weatherContainer = createNighttime(); + weatherContainer = createNighttime(hour); } else { - weatherContainer = createSunshine(); + weatherContainer = createSunshine(hour); } break; case 'wind': @@ -478,8 +610,10 @@ export function updateWeatherEffect() { // Apply z-index based on background/foreground settings if (extensionSettings.weatherForeground) { weatherContainer.style.zIndex = '9998'; // In front of chat + weatherContainer.classList.add('rpg-weather-foreground'); } else if (extensionSettings.weatherBackground) { weatherContainer.style.zIndex = '1'; // Behind chat (default) + weatherContainer.classList.remove('rpg-weather-foreground'); } else { // Both disabled - don't show weather return; diff --git a/style.css b/style.css index 4b6bc55..596438b 100644 --- a/style.css +++ b/style.css @@ -9740,6 +9740,72 @@ body[data-theme="cyberpunk"] .rpg-music-widget-play { } } +/* Sun - positioned dynamically based on hour */ +.rpg-clear-sun { + position: fixed; + width: 50px; + height: 50px; + background: radial-gradient(circle at 40% 40%, + rgba(255, 255, 240, 1) 0%, + rgba(255, 250, 200, 1) 30%, + rgba(255, 220, 120, 0.9) 60%, + rgba(255, 180, 80, 0.6) 80%, + rgba(255, 150, 50, 0) 100%); + border-radius: 50%; + box-shadow: + 0 0 30px 10px rgba(255, 220, 100, 0.5), + 0 0 60px 20px rgba(255, 200, 80, 0.3), + 0 0 100px 40px rgba(255, 180, 60, 0.15); + transform: translate(-50%, -50%); + animation: rpg-clear-sun-pulse 8s ease-in-out infinite; + pointer-events: none; +} + +@keyframes rpg-clear-sun-pulse { + 0%, 100% { + transform: translate(-50%, -50%) scale(1); + box-shadow: + 0 0 30px 10px rgba(255, 220, 100, 0.5), + 0 0 60px 20px rgba(255, 200, 80, 0.3), + 0 0 100px 40px rgba(255, 180, 60, 0.15); + } + 50% { + transform: translate(-50%, -50%) scale(1.05); + box-shadow: + 0 0 35px 12px rgba(255, 220, 100, 0.6), + 0 0 70px 25px rgba(255, 200, 80, 0.35), + 0 0 110px 45px rgba(255, 180, 60, 0.2); + } +} + +/* Sun glow aura */ +.rpg-clear-sun-glow { + position: fixed; + width: 150px; + height: 150px; + background: radial-gradient(circle at center, + rgba(255, 240, 180, 0.25) 0%, + rgba(255, 220, 150, 0.15) 30%, + rgba(255, 200, 120, 0.08) 50%, + transparent 70%); + border-radius: 50%; + transform: translate(-50%, -50%); + filter: blur(8px); + animation: rpg-clear-sun-glow-pulse 6s ease-in-out infinite; + pointer-events: none; +} + +@keyframes rpg-clear-sun-glow-pulse { + 0%, 100% { + opacity: 0.7; + transform: translate(-50%, -50%) scale(1); + } + 50% { + opacity: 1; + transform: translate(-50%, -50%) scale(1.15); + } +} + /* Lens flare effect */ .rpg-clear-lens-flare { position: fixed; @@ -9849,8 +9915,6 @@ body[data-theme="cyberpunk"] .rpg-music-widget-play { /* Moon */ .rpg-night-moon { position: fixed; - top: 8dvh; - left: 12vw; width: 60px; height: 60px; background: radial-gradient(circle at 35% 35%, @@ -9880,8 +9944,6 @@ body[data-theme="cyberpunk"] .rpg-music-widget-play { /* Moon glow aura */ .rpg-night-moon-glow { position: fixed; - top: 5dvh; - left: 9vw; width: 120px; height: 120px; background: radial-gradient(circle at center, @@ -10089,6 +10151,17 @@ body[data-theme="cyberpunk"] .rpg-music-widget-play { height: 100px; } + /* Sun mobile optimizations */ + .rpg-clear-sun { + width: 40px; + height: 40px; + } + + .rpg-clear-sun-glow { + width: 100px; + height: 100px; + } + /* Nighttime mobile optimizations */ .rpg-night-moon { width: 45px; @@ -10109,6 +10182,51 @@ body[data-theme="cyberpunk"] .rpg-music-widget-play { } } +/* Foreground mode - reduced opacity for celestial bodies to not obstruct content */ +.rpg-weather-foreground .rpg-clear-sun { + opacity: 0.5; + box-shadow: + 0 0 20px 8px rgba(255, 220, 100, 0.3), + 0 0 40px 15px rgba(255, 200, 80, 0.15), + 0 0 60px 25px rgba(255, 180, 60, 0.08); +} + +.rpg-weather-foreground .rpg-clear-sun-glow { + opacity: 0.3; +} + +.rpg-weather-foreground .rpg-clear-lens-flare { + opacity: 0.3; +} + +.rpg-weather-foreground .rpg-clear-ambient-glow { + opacity: 0.4; +} + +.rpg-weather-foreground .rpg-night-moon { + opacity: 0.7; + box-shadow: + 0 0 15px 4px rgba(255, 255, 240, 0.25), + 0 0 30px 8px rgba(200, 210, 255, 0.12); +} + +.rpg-weather-foreground .rpg-night-moon-glow { + opacity: 0.3; +} + +.rpg-weather-foreground .rpg-night-overlay { + opacity: 0.4; +} + +.rpg-weather-foreground .rpg-night-star, +.rpg-weather-foreground .rpg-night-star-bright { + opacity: 0.5; +} + +.rpg-weather-foreground .rpg-night-firefly { + opacity: 0.6; +} + /* Lightning flash effect */ .rpg-lightning { position: fixed;