diff --git a/src/systems/ui/weatherEffects.js b/src/systems/ui/weatherEffects.js index 5328019..727f8cb 100644 --- a/src/systems/ui/weatherEffects.js +++ b/src/systems/ui/weatherEffects.js @@ -4,9 +4,106 @@ */ import { extensionSettings, lastGeneratedData, committedTrackerData } from '../../core/state.js'; +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) + * Supports formats like "3:00 PM", "15:00", "3 PM", "Evening", etc. + */ +function parseHourFromTime(timeStr) { + if (!timeStr) return null; + + const text = timeStr.toLowerCase().trim(); + + // Check for descriptive time words first + if (text.includes('dawn') || text.includes('sunrise')) return 6; + if (text.includes('early morning')) return 7; + if (text.includes('morning')) return 9; + if (text.includes('midday') || text.includes('noon') || text.includes('mid-day')) return 12; + if (text.includes('afternoon')) return 14; + if (text.includes('late afternoon')) return 16; + if (text.includes('evening') || text.includes('dusk') || text.includes('sunset')) return 19; + if (text.includes('twilight')) return 20; + if (text.includes('night') || text.includes('nighttime')) return 22; + if (text.includes('midnight')) return 0; + if (text.includes('late night')) return 2; + + // Try to parse numeric time formats + // Format: "3:00 PM" or "3:00PM" or "3 PM" + const ampmMatch = text.match(/(\d{1,2})(?::(\d{2}))?\s*(am|pm)/i); + if (ampmMatch) { + let hour = parseInt(ampmMatch[1], 10); + const isPM = ampmMatch[3].toLowerCase() === 'pm'; + if (isPM && hour !== 12) hour += 12; + if (!isPM && hour === 12) hour = 0; + return hour; + } + + // Format: "15:00" (24-hour) + const militaryMatch = text.match(/(\d{1,2}):(\d{2})/); + if (militaryMatch) { + return parseInt(militaryMatch[1], 10); + } + + return null; +} + +/** + * Determine time of day based on hour + */ +function getTimeOfDay(hour) { + if (hour === null) return 'unknown'; + + // Night: 8 PM (20:00) to 5 AM (05:00) + if (hour >= 20 || hour < 5) return 'night'; + + // Dawn/Dusk: 5 AM - 7 AM and 6 PM - 8 PM + if (hour >= 5 && hour < 7) return 'dawn'; + if (hour >= 18 && hour < 20) return 'dusk'; + + // Day: 7 AM to 6 PM + return 'day'; +} + +/** + * Extract time from Info Box data + */ +function getCurrentTime() { + const infoBoxData = lastGeneratedData.infoBox || committedTrackerData.infoBox || ''; + + // Try to parse as JSON first (new format) + try { + const parsed = typeof infoBoxData === 'string' ? repairJSON(infoBoxData) : infoBoxData; + if (parsed && parsed.time) { + // Use the end time if available (current time), otherwise start time + return parsed.time.end || parsed.time.start || null; + } + } catch (e) { + // Not JSON, try old text format + } + + // Fallback: Parse the old text format to find Time field + const lines = infoBoxData.split('\n'); + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed.startsWith('Time:')) { + const timeStr = trimmed.substring('Time:'.length).trim(); + // If it contains →, take the end time (after arrow) + if (timeStr.includes('→')) { + const parts = timeStr.split('→'); + return parts[1]?.trim() || parts[0]?.trim(); + } + return timeStr; + } + } + + return null; +} /** * Parse weather text to determine effect type @@ -136,22 +233,171 @@ function createMist() { } /** - * Create sunshine rays effect + * Calculate sun position based on hour (arc across sky) + * Returns { left: vw%, top: dvh% } */ -function createSunshine() { - const container = document.createElement('div'); - container.className = 'rpg-weather-particles'; +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 8 sun rays - for (let i = 0; i < 8; i++) { - const ray = document.createElement('div'); - ray.className = 'rpg-weather-particle rpg-sunray'; - ray.style.left = `${10 + i * 12}%`; - ray.style.animationDelay = `${i * 0.5}s`; - ray.style.animationDuration = `${8 + Math.random() * 4}s`; - container.appendChild(ray); +/** + * Create clear/sunny weather effect with floating particles and warm glow + */ +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'; + container.appendChild(ambientGlow); + + // Create floating dust motes / pollen particles (golden sparkles) + for (let i = 0; i < 25; i++) { + const particle = document.createElement('div'); + particle.className = 'rpg-weather-particle rpg-clear-dust-mote'; + particle.style.left = `${Math.random() * 100}vw`; + particle.style.top = `${Math.random() * 100}dvh`; + particle.style.animationDelay = `${Math.random() * 15}s`; + particle.style.animationDuration = `${12 + Math.random() * 8}s`; + // Vary the size slightly + const size = 2 + Math.random() * 4; + particle.style.width = `${size}px`; + particle.style.height = `${size}px`; + container.appendChild(particle); } + // Create soft light orbs that drift gently + for (let i = 0; i < 6; i++) { + const orb = document.createElement('div'); + orb.className = 'rpg-weather-particle rpg-clear-light-orb'; + orb.style.left = `${10 + Math.random() * 80}vw`; + orb.style.top = `${10 + Math.random() * 80}dvh`; + orb.style.animationDelay = `${i * 2}s`; + orb.style.animationDuration = `${20 + Math.random() * 10}s`; + // Vary the size + const size = 80 + Math.random() * 120; + orb.style.width = `${size}px`; + orb.style.height = `${size}px`; + container.appendChild(orb); + } + + // Create lens flare effect in corner + const lensFlare = document.createElement('div'); + lensFlare.className = 'rpg-weather-particle rpg-clear-lens-flare'; + container.appendChild(lensFlare); + + return container; +} + +/** + * Create clear nighttime weather effect with moon, stars, and fireflies + */ +function createNighttime(hour) { + const container = document.createElement('div'); + container.className = 'rpg-weather-particles rpg-night-weather'; + + // Create dark blue ambient overlay + const nightOverlay = document.createElement('div'); + 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 + for (let i = 0; i < 60; i++) { + const star = document.createElement('div'); + star.className = 'rpg-weather-particle rpg-night-star'; + star.style.left = `${Math.random() * 100}vw`; + star.style.top = `${Math.random() * 60}dvh`; // Stars mostly in upper portion + star.style.animationDelay = `${Math.random() * 5}s`; + star.style.animationDuration = `${2 + Math.random() * 3}s`; + // Vary the size + const size = 1 + Math.random() * 2; + star.style.width = `${size}px`; + star.style.height = `${size}px`; + container.appendChild(star); + } + + // Create a few brighter stars + for (let i = 0; i < 8; i++) { + const brightStar = document.createElement('div'); + brightStar.className = 'rpg-weather-particle rpg-night-star rpg-night-star-bright'; + brightStar.style.left = `${Math.random() * 100}vw`; + brightStar.style.top = `${Math.random() * 50}dvh`; + brightStar.style.animationDelay = `${Math.random() * 4}s`; + brightStar.style.animationDuration = `${3 + Math.random() * 2}s`; + container.appendChild(brightStar); + } + + // Create fireflies / floating light particles + for (let i = 0; i < 15; i++) { + const firefly = document.createElement('div'); + firefly.className = 'rpg-weather-particle rpg-night-firefly'; + firefly.style.left = `${Math.random() * 100}vw`; + firefly.style.top = `${40 + Math.random() * 55}dvh`; // Fireflies in lower portion + firefly.style.animationDelay = `${Math.random() * 10}s`; + firefly.style.animationDuration = `${8 + Math.random() * 7}s`; + container.appendChild(firefly); + } + + // Create subtle shooting star occasionally + const shootingStar = document.createElement('div'); + shootingStar.className = 'rpg-weather-particle rpg-night-shooting-star'; + container.appendChild(shootingStar); + return container; } @@ -190,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 */ @@ -198,11 +513,13 @@ function removeWeatherEffect() { weatherContainer.remove(); weatherContainer = null; currentWeatherType = null; + currentTimeOfDay = null; + currentHour = null; } } /** - * Update weather effect based on current weather + * Update weather effect based on current weather and time */ export function updateWeatherEffect() { // Check if dynamic weather is enabled @@ -214,8 +531,21 @@ export function updateWeatherEffect() { const weather = getCurrentWeather(); const weatherType = parseWeatherType(weather); - // Don't recreate if weather hasn't changed - if (weatherType === currentWeatherType) { + // Get current time of day + const timeStr = getCurrentTime(); + const hour = parseHourFromTime(timeStr); + const timeOfDay = getTimeOfDay(hour); + + // 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; } @@ -228,6 +558,8 @@ export function updateWeatherEffect() { } currentWeatherType = weatherType; + currentTimeOfDay = timeOfDay; + currentHour = hour; switch (weatherType) { case 'snow': @@ -240,7 +572,12 @@ export function updateWeatherEffect() { weatherContainer = createMist(); break; case 'sunny': - weatherContainer = createSunshine(); + // Use nighttime effect for clear weather at night + if (timeOfDay === 'night') { + weatherContainer = createNighttime(hour); + } else { + weatherContainer = createSunshine(hour); + } break; case 'wind': weatherContainer = createWind(); @@ -273,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 0d0b9c3..596438b 100644 --- a/style.css +++ b/style.css @@ -9524,11 +9524,11 @@ body[data-theme="cyberpunk"] .rpg-music-widget-play { /* Snowfall animation */ @keyframes rpg-snowfall { 0% { - transform: translateY(0vh) rotate(0deg); + transform: translateY(0) rotate(0deg); opacity: 0.8; } 100% { - transform: translateY(100vh) rotate(360deg); + transform: translateY(100dvh) rotate(360deg); opacity: 0.2; } } @@ -9598,7 +9598,7 @@ body[data-theme="cyberpunk"] .rpg-music-widget-play { opacity: 0.8; } 100% { - transform: translateY(100vh); + transform: translateY(100dvh); opacity: 0.3; } } @@ -9643,40 +9643,487 @@ body[data-theme="cyberpunk"] .rpg-music-widget-play { background: radial-gradient(ellipse at center, rgba(220, 220, 240, 0.25) 0%, transparent 70%); } -/* Sunshine rays */ -.rpg-sunray { - width: 3px; - height: 100dvh; - background: linear-gradient(to bottom, - rgba(255, 250, 200, 0) 0%, - rgba(255, 250, 200, 0.3) 20%, - rgba(255, 250, 200, 0.15) 50%, - rgba(255, 250, 200, 0) 100%); - transform-origin: top; - animation: rpg-sunrayShine ease-in-out infinite; - top: -20%; - filter: blur(2px); +/* Clear/Sunny Weather Effects */ + +/* Container modifier for clear weather */ +.rpg-clear-weather { + overflow: hidden; } -@keyframes rpg-sunrayShine { +/* Warm ambient glow overlay */ +.rpg-clear-ambient-glow { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: radial-gradient(ellipse at 15% 10%, + rgba(255, 245, 200, 0.15) 0%, + rgba(255, 240, 180, 0.08) 30%, + transparent 70%); + animation: rpg-clear-ambient-pulse 8s ease-in-out infinite; + pointer-events: none; +} + +@keyframes rpg-clear-ambient-pulse { 0%, 100% { - opacity: 0.2; - transform: translateY(0) scaleY(1); + opacity: 0.6; } 50% { - opacity: 0.4; - transform: translateY(5%) scaleY(1.05); + opacity: 1; } } -.rpg-sunray:nth-child(2n) { - width: 4px; - animation-duration: 10s; +/* Floating dust motes / pollen particles */ +.rpg-clear-dust-mote { + position: fixed; + border-radius: 50%; + background: radial-gradient(circle at 30% 30%, + rgba(255, 250, 220, 0.9) 0%, + rgba(255, 235, 180, 0.6) 50%, + rgba(255, 220, 150, 0) 100%); + box-shadow: 0 0 6px 2px rgba(255, 245, 200, 0.4); + animation: rpg-clear-dust-float linear infinite; + pointer-events: none; } -.rpg-sunray:nth-child(3n) { +@keyframes rpg-clear-dust-float { + 0% { + transform: translate(0, 0) scale(1); + opacity: 0; + } + 10% { + opacity: 0.8; + } + 50% { + transform: translate(30px, -50px) scale(1.2); + opacity: 0.6; + } + 90% { + opacity: 0.8; + } + 100% { + transform: translate(60px, -100px) scale(1); + opacity: 0; + } +} + +/* Soft drifting light orbs */ +.rpg-clear-light-orb { + position: fixed; + border-radius: 50%; + background: radial-gradient(circle at center, + rgba(255, 250, 230, 0.12) 0%, + rgba(255, 245, 200, 0.06) 40%, + transparent 70%); + filter: blur(20px); + animation: rpg-clear-orb-drift ease-in-out infinite; + pointer-events: none; +} + +@keyframes rpg-clear-orb-drift { + 0%, 100% { + transform: translate(0, 0) scale(1); + opacity: 0.4; + } + 25% { + transform: translate(20px, 15px) scale(1.1); + opacity: 0.6; + } + 50% { + transform: translate(-10px, 30px) scale(0.95); + opacity: 0.5; + } + 75% { + transform: translate(-25px, 10px) scale(1.05); + opacity: 0.7; + } +} + +/* 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; + top: 5%; + left: 10%; + width: 150px; + height: 150px; + background: radial-gradient(circle at center, + rgba(255, 255, 255, 0.3) 0%, + rgba(255, 250, 220, 0.15) 20%, + rgba(255, 240, 180, 0.08) 40%, + transparent 60%); + border-radius: 50%; + animation: rpg-clear-lens-flare-pulse 6s ease-in-out infinite; + pointer-events: none; +} + +.rpg-clear-lens-flare::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 60px; + height: 60px; + transform: translate(-50%, -50%); + background: radial-gradient(circle at center, + rgba(255, 255, 255, 0.5) 0%, + rgba(255, 250, 230, 0.2) 50%, + transparent 70%); + border-radius: 50%; + animation: rpg-clear-lens-core-pulse 4s ease-in-out infinite; +} + +.rpg-clear-lens-flare::after { + content: ''; + position: absolute; + top: 120%; + left: 150%; + width: 40px; + height: 40px; + background: radial-gradient(circle at center, + rgba(255, 200, 150, 0.4) 0%, + rgba(255, 180, 100, 0.1) 50%, + transparent 70%); + border-radius: 50%; + animation: rpg-clear-secondary-flare 8s ease-in-out infinite; +} + +@keyframes rpg-clear-lens-flare-pulse { + 0%, 100% { + opacity: 0.7; + transform: scale(1); + } + 50% { + opacity: 1; + transform: scale(1.1); + } +} + +@keyframes rpg-clear-lens-core-pulse { + 0%, 100% { + opacity: 0.8; + transform: translate(-50%, -50%) scale(1); + } + 50% { + opacity: 1; + transform: translate(-50%, -50%) scale(1.15); + } +} + +@keyframes rpg-clear-secondary-flare { + 0%, 100% { + opacity: 0.3; + transform: scale(1); + } + 30% { + opacity: 0.6; + transform: scale(1.2); + } + 70% { + opacity: 0.4; + transform: scale(0.9); + } +} + +/* ===== Nighttime Clear Weather Effects ===== */ + +/* Container modifier for night weather */ +.rpg-night-weather { + overflow: hidden; +} + +/* Dark blue night overlay */ +.rpg-night-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(to bottom, + rgba(10, 15, 40, 0.3) 0%, + rgba(15, 25, 55, 0.2) 40%, + rgba(20, 30, 60, 0.15) 100%); + pointer-events: none; +} + +/* Moon */ +.rpg-night-moon { + position: fixed; + width: 60px; + height: 60px; + background: radial-gradient(circle at 35% 35%, + rgba(255, 255, 245, 1) 0%, + rgba(240, 240, 230, 0.95) 40%, + rgba(220, 220, 210, 0.9) 60%, + rgba(200, 200, 195, 0.8) 80%, + rgba(180, 180, 175, 0) 100%); + border-radius: 50%; + box-shadow: + 0 0 20px 5px rgba(255, 255, 240, 0.4), + 0 0 40px 10px rgba(200, 210, 255, 0.2), + inset -8px -5px 15px rgba(150, 150, 140, 0.3); + animation: rpg-night-moon-float 30s ease-in-out infinite; + pointer-events: none; +} + +@keyframes rpg-night-moon-float { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(10px); + } +} + +/* Moon glow aura */ +.rpg-night-moon-glow { + position: fixed; + width: 120px; + height: 120px; + background: radial-gradient(circle at center, + rgba(200, 220, 255, 0.15) 0%, + rgba(180, 200, 240, 0.08) 40%, + transparent 70%); + border-radius: 50%; + filter: blur(10px); + animation: rpg-night-moon-glow-pulse 8s ease-in-out infinite; + pointer-events: none; +} + +@keyframes rpg-night-moon-glow-pulse { + 0%, 100% { + opacity: 0.6; + transform: scale(1); + } + 50% { + opacity: 1; + transform: scale(1.1); + } +} + +/* Twinkling stars */ +.rpg-night-star { + position: fixed; + background: radial-gradient(circle at center, + rgba(255, 255, 255, 1) 0%, + rgba(200, 220, 255, 0.8) 40%, + transparent 70%); + border-radius: 50%; + animation: rpg-night-star-twinkle ease-in-out infinite; + pointer-events: none; +} + +@keyframes rpg-night-star-twinkle { + 0%, 100% { + opacity: 0.3; + transform: scale(1); + } + 50% { + opacity: 1; + transform: scale(1.3); + } +} + +/* Brighter stars with cross-flare effect */ +.rpg-night-star-bright { + width: 4px !important; + height: 4px !important; + background: rgba(255, 255, 255, 1); + box-shadow: + 0 0 4px 2px rgba(255, 255, 255, 0.8), + 0 0 8px 4px rgba(200, 220, 255, 0.4); +} + +.rpg-night-star-bright::before, +.rpg-night-star-bright::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + background: linear-gradient(90deg, + transparent 0%, + rgba(255, 255, 255, 0.8) 45%, + rgba(255, 255, 255, 1) 50%, + rgba(255, 255, 255, 0.8) 55%, + transparent 100%); +} + +.rpg-night-star-bright::before { + width: 16px; + height: 2px; + transform: translate(-50%, -50%); +} + +.rpg-night-star-bright::after { width: 2px; - animation-duration: 12s; + height: 16px; + transform: translate(-50%, -50%); +} + +/* Fireflies */ +.rpg-night-firefly { + position: fixed; + width: 4px; + height: 4px; + background: radial-gradient(circle at center, + rgba(200, 255, 150, 1) 0%, + rgba(180, 255, 120, 0.8) 30%, + rgba(150, 230, 100, 0.4) 60%, + transparent 100%); + border-radius: 50%; + box-shadow: + 0 0 6px 3px rgba(180, 255, 120, 0.6), + 0 0 12px 6px rgba(150, 230, 100, 0.3); + animation: rpg-night-firefly-float ease-in-out infinite; + pointer-events: none; +} + +@keyframes rpg-night-firefly-float { + 0% { + opacity: 0; + transform: translate(0, 0) scale(0.5); + } + 10% { + opacity: 1; + transform: translate(5px, -10px) scale(1); + } + 30% { + opacity: 0.3; + transform: translate(-15px, -25px) scale(0.8); + } + 50% { + opacity: 1; + transform: translate(10px, -40px) scale(1.1); + } + 70% { + opacity: 0.4; + transform: translate(-10px, -55px) scale(0.9); + } + 90% { + opacity: 0.8; + transform: translate(5px, -70px) scale(1); + } + 100% { + opacity: 0; + transform: translate(0, -80px) scale(0.5); + } +} + +/* Shooting star */ +.rpg-night-shooting-star { + position: fixed; + top: 15%; + left: -10%; + width: 100px; + height: 2px; + background: linear-gradient(90deg, + transparent 0%, + rgba(255, 255, 255, 0.2) 20%, + rgba(255, 255, 255, 0.8) 60%, + rgba(255, 255, 255, 1) 100%); + border-radius: 50%; + transform: rotate(35deg); + animation: rpg-night-shooting-star-fly 12s ease-in-out infinite; + pointer-events: none; + opacity: 0; +} + +.rpg-night-shooting-star::after { + content: ''; + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); + width: 6px; + height: 6px; + background: rgba(255, 255, 255, 1); + border-radius: 50%; + box-shadow: 0 0 10px 4px rgba(255, 255, 255, 0.8); +} + +@keyframes rpg-night-shooting-star-fly { + 0%, 85%, 100% { + opacity: 0; + left: -10%; + top: 15%; + } + 88% { + opacity: 1; + } + 95% { + opacity: 1; + left: 110%; + top: 45%; + } + 96% { + opacity: 0; + } } /* Mobile optimizations */ @@ -9689,9 +10136,95 @@ body[data-theme="cyberpunk"] .rpg-music-widget-play { animation-duration: 20s; } - .rpg-sunray { + /* Clear weather mobile optimizations */ + .rpg-clear-dust-mote { animation-duration: 15s; } + + .rpg-clear-light-orb { + animation-duration: 25s; + filter: blur(15px); + } + + .rpg-clear-lens-flare { + width: 100px; + 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; + height: 45px; + } + + .rpg-night-moon-glow { + width: 90px; + height: 90px; + } + + .rpg-night-firefly { + animation-duration: 12s; + } + + .rpg-night-shooting-star { + animation-duration: 18s; + } +} + +/* 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 */