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
This commit is contained in:
tomt610
2026-01-12 23:21:19 +00:00
parent 6e9ff9812d
commit 0066b61746
2 changed files with 262 additions and 10 deletions
+140 -6
View File
@@ -9,6 +9,7 @@ import { repairJSON } from '../../utils/jsonRepair.js';
let weatherContainer = null; let weatherContainer = null;
let currentWeatherType = null; let currentWeatherType = null;
let currentTimeOfDay = null; let currentTimeOfDay = null;
let currentHour = null;
/** /**
* Parse time string to extract hour (24-hour format) * Parse time string to extract hour (24-hour format)
@@ -231,13 +232,58 @@ function createMist() {
return container; 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 * Create clear/sunny weather effect with floating particles and warm glow
*/ */
function createSunshine() { function createSunshine(hour) {
const container = document.createElement('div'); const container = document.createElement('div');
container.className = 'rpg-weather-particles rpg-clear-weather'; 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 // Create warm ambient glow overlay
const ambientGlow = document.createElement('div'); const ambientGlow = document.createElement('div');
ambientGlow.className = 'rpg-weather-particle rpg-clear-ambient-glow'; 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 * Create clear nighttime weather effect with moon, stars, and fireflies
*/ */
function createNighttime() { function createNighttime(hour) {
const container = document.createElement('div'); const container = document.createElement('div');
container.className = 'rpg-weather-particles rpg-night-weather'; container.className = 'rpg-weather-particles rpg-night-weather';
@@ -293,14 +339,21 @@ function createNighttime() {
nightOverlay.className = 'rpg-weather-particle rpg-night-overlay'; nightOverlay.className = 'rpg-weather-particle rpg-night-overlay';
container.appendChild(nightOverlay); container.appendChild(nightOverlay);
// Calculate moon position based on hour
const moonPos = calculateMoonPosition(hour);
// Create the moon // Create the moon
const moon = document.createElement('div'); const moon = document.createElement('div');
moon.className = 'rpg-weather-particle rpg-night-moon'; moon.className = 'rpg-weather-particle rpg-night-moon';
moon.style.left = `${moonPos.left}vw`;
moon.style.top = `${moonPos.top}dvh`;
container.appendChild(moon); container.appendChild(moon);
// Create moon glow // Create moon glow
const moonGlow = document.createElement('div'); const moonGlow = document.createElement('div');
moonGlow.className = 'rpg-weather-particle rpg-night-moon-glow'; 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); container.appendChild(moonGlow);
// Create twinkling stars // Create twinkling stars
@@ -383,6 +436,75 @@ function createWind() {
return container; 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 * Remove current weather effect
*/ */
@@ -392,6 +514,7 @@ function removeWeatherEffect() {
weatherContainer = null; weatherContainer = null;
currentWeatherType = null; currentWeatherType = null;
currentTimeOfDay = null; currentTimeOfDay = null;
currentHour = null;
} }
} }
@@ -413,8 +536,16 @@ export function updateWeatherEffect() {
const hour = parseHourFromTime(timeStr); const hour = parseHourFromTime(timeStr);
const timeOfDay = getTimeOfDay(hour); const timeOfDay = getTimeOfDay(hour);
// Don't recreate if weather and time haven't changed // If only the hour changed (same weather and time of day), just update celestial position
if (weatherType === currentWeatherType && timeOfDay === currentTimeOfDay) { 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; return;
} }
@@ -428,6 +559,7 @@ export function updateWeatherEffect() {
currentWeatherType = weatherType; currentWeatherType = weatherType;
currentTimeOfDay = timeOfDay; currentTimeOfDay = timeOfDay;
currentHour = hour;
switch (weatherType) { switch (weatherType) {
case 'snow': case 'snow':
@@ -442,9 +574,9 @@ export function updateWeatherEffect() {
case 'sunny': case 'sunny':
// Use nighttime effect for clear weather at night // Use nighttime effect for clear weather at night
if (timeOfDay === 'night') { if (timeOfDay === 'night') {
weatherContainer = createNighttime(); weatherContainer = createNighttime(hour);
} else { } else {
weatherContainer = createSunshine(); weatherContainer = createSunshine(hour);
} }
break; break;
case 'wind': case 'wind':
@@ -478,8 +610,10 @@ export function updateWeatherEffect() {
// Apply z-index based on background/foreground settings // Apply z-index based on background/foreground settings
if (extensionSettings.weatherForeground) { if (extensionSettings.weatherForeground) {
weatherContainer.style.zIndex = '9998'; // In front of chat weatherContainer.style.zIndex = '9998'; // In front of chat
weatherContainer.classList.add('rpg-weather-foreground');
} else if (extensionSettings.weatherBackground) { } else if (extensionSettings.weatherBackground) {
weatherContainer.style.zIndex = '1'; // Behind chat (default) weatherContainer.style.zIndex = '1'; // Behind chat (default)
weatherContainer.classList.remove('rpg-weather-foreground');
} else { } else {
// Both disabled - don't show weather // Both disabled - don't show weather
return; return;
+122 -4
View File
@@ -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 */ /* Lens flare effect */
.rpg-clear-lens-flare { .rpg-clear-lens-flare {
position: fixed; position: fixed;
@@ -9849,8 +9915,6 @@ body[data-theme="cyberpunk"] .rpg-music-widget-play {
/* Moon */ /* Moon */
.rpg-night-moon { .rpg-night-moon {
position: fixed; position: fixed;
top: 8dvh;
left: 12vw;
width: 60px; width: 60px;
height: 60px; height: 60px;
background: radial-gradient(circle at 35% 35%, background: radial-gradient(circle at 35% 35%,
@@ -9880,8 +9944,6 @@ body[data-theme="cyberpunk"] .rpg-music-widget-play {
/* Moon glow aura */ /* Moon glow aura */
.rpg-night-moon-glow { .rpg-night-moon-glow {
position: fixed; position: fixed;
top: 5dvh;
left: 9vw;
width: 120px; width: 120px;
height: 120px; height: 120px;
background: radial-gradient(circle at center, background: radial-gradient(circle at center,
@@ -10089,6 +10151,17 @@ body[data-theme="cyberpunk"] .rpg-music-widget-play {
height: 100px; height: 100px;
} }
/* Sun mobile optimizations */
.rpg-clear-sun {
width: 40px;
height: 40px;
}
.rpg-clear-sun-glow {
width: 100px;
height: 100px;
}
/* Nighttime mobile optimizations */ /* Nighttime mobile optimizations */
.rpg-night-moon { .rpg-night-moon {
width: 45px; 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 */ /* Lightning flash effect */
.rpg-lightning { .rpg-lightning {
position: fixed; position: fixed;