From 3797e21912a3e298f7e5b8fb4940d64e3222770d Mon Sep 17 00:00:00 2001 From: tomt610 Date: Mon, 12 Jan 2026 22:45:48 +0000 Subject: [PATCH] Improve clear weather effects with day/night cycle - Replace blinking sunray lines with pleasant daytime effects: - Warm ambient glow overlay - Floating golden dust motes/pollen particles - Soft drifting light orbs - Subtle lens flare in corner - Add automatic nighttime detection from Info Box time data: - Parses various time formats (12h, 24h, descriptive) - Night mode activates 8 PM - 5 AM - Add nighttime clear weather effects: - Moon with realistic shading and glow (positioned left) - Twinkling stars with bright star cross-flares - Floating fireflies with gentle glow - Occasional shooting star animation - Add mobile optimizations for all new effects --- src/systems/ui/weatherEffects.js | 233 +++++++++++++++- style.css | 463 +++++++++++++++++++++++++++++-- 2 files changed, 658 insertions(+), 38 deletions(-) diff --git a/src/systems/ui/weatherEffects.js b/src/systems/ui/weatherEffects.js index 5328019..04228b1 100644 --- a/src/systems/ui/weatherEffects.js +++ b/src/systems/ui/weatherEffects.js @@ -4,9 +4,105 @@ */ import { extensionSettings, lastGeneratedData, committedTrackerData } from '../../core/state.js'; +import { repairJSON } from '../../utils/jsonRepair.js'; let weatherContainer = null; let currentWeatherType = null; +let currentTimeOfDay = 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 +232,119 @@ function createMist() { } /** - * Create sunshine rays effect + * Create clear/sunny weather effect with floating particles and warm glow */ function createSunshine() { const container = document.createElement('div'); - container.className = 'rpg-weather-particles'; + container.className = 'rpg-weather-particles rpg-clear-weather'; - // 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 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}%`; + particle.style.top = `${Math.random() * 100}%`; + 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}%`; + orb.style.top = `${10 + Math.random() * 80}%`; + 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() { + 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); + + // Create the moon + const moon = document.createElement('div'); + moon.className = 'rpg-weather-particle rpg-night-moon'; + container.appendChild(moon); + + // Create moon glow + const moonGlow = document.createElement('div'); + moonGlow.className = 'rpg-weather-particle rpg-night-moon-glow'; + 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}%`; + star.style.top = `${Math.random() * 60}%`; // 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}%`; + brightStar.style.top = `${Math.random() * 50}%`; + 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}%`; + firefly.style.top = `${40 + Math.random() * 55}%`; // 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; } @@ -198,11 +391,12 @@ function removeWeatherEffect() { weatherContainer.remove(); weatherContainer = null; currentWeatherType = null; + currentTimeOfDay = 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 +408,13 @@ 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); + + // Don't recreate if weather and time haven't changed + if (weatherType === currentWeatherType && timeOfDay === currentTimeOfDay) { return; } @@ -228,6 +427,7 @@ export function updateWeatherEffect() { } currentWeatherType = weatherType; + currentTimeOfDay = timeOfDay; switch (weatherType) { case 'snow': @@ -240,7 +440,12 @@ export function updateWeatherEffect() { weatherContainer = createMist(); break; case 'sunny': - weatherContainer = createSunshine(); + // Use nighttime effect for clear weather at night + if (timeOfDay === 'night') { + weatherContainer = createNighttime(); + } else { + weatherContainer = createSunshine(); + } break; case 'wind': weatherContainer = createWind(); diff --git a/style.css b/style.css index 0d0b9c3..313c75e 100644 --- a/style.css +++ b/style.css @@ -9643,40 +9643,425 @@ 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; + } +} + +/* 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; + top: 8%; + left: 12%; + 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; + top: 5%; + left: 9%; + 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 +10074,39 @@ 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; + } + + /* 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; + } } /* Lightning flash effect */