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
This commit is contained in:
tomt610
2026-01-12 22:45:48 +00:00
parent 7bac0d48f9
commit 3797e21912
2 changed files with 658 additions and 38 deletions
+218 -13
View File
@@ -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':
// Use nighttime effect for clear weather at night
if (timeOfDay === 'night') {
weatherContainer = createNighttime();
} else {
weatherContainer = createSunshine();
}
break;
case 'wind':
weatherContainer = createWind();
+439 -24
View File
@@ -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 */