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:
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user