Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d0dd8950a6 | |||
| 4612ed2108 | |||
| 0e988b201c | |||
| 7b4ebb8d76 | |||
| 0499f2c43e | |||
| 35bd55615b | |||
| f38f6850c3 | |||
| 989f511d01 | |||
| b827b77184 | |||
| 4f3d59bfb7 | |||
| c18fd39283 | |||
| f5825a7a24 |
@@ -151,7 +151,6 @@ import {
|
||||
onMessageReceived,
|
||||
onCharacterChanged,
|
||||
onMessageSwiped,
|
||||
onMessageDeleted,
|
||||
updatePersonaAvatar,
|
||||
clearExtensionPrompts,
|
||||
onGenerationEnded,
|
||||
@@ -1255,7 +1254,6 @@ jQuery(async () => {
|
||||
[event_types.GENERATION_ENDED]: onGenerationEnded,
|
||||
[event_types.CHAT_CHANGED]: [onCharacterChanged, updatePersonaAvatar, restoreCheckpointOnLoad, clearSessionAvatarPrompts],
|
||||
[event_types.MESSAGE_SWIPED]: onMessageSwiped,
|
||||
[event_types.MESSAGE_DELETED]: onMessageDeleted,
|
||||
[event_types.USER_MESSAGE_RENDERED]: updatePersonaAvatar,
|
||||
[event_types.SETTINGS_UPDATED]: updatePersonaAvatar
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<select id="rpg-companion-language-select" class="text_pole">
|
||||
<option value="en" data-i18n-key="settings.language.option.en">English</option>
|
||||
<option value="zh-tw" data-i18n-key="settings.language.option.zh-tw">繁體中文</option>
|
||||
<option value="ru" data-i18n-key="settings.language.option.ru">Русский</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -546,12 +546,33 @@ function formatTrackerDataForContext(jsonData, trackerType, userName) {
|
||||
if (trackerType === 'userStats') {
|
||||
formatted += `${userName}'s Stats:\n`;
|
||||
|
||||
// Get display mode and custom stats config for maxValue lookup
|
||||
const userStatsConfig = extensionSettings.trackerConfig?.userStats;
|
||||
const displayMode = userStatsConfig?.statsDisplayMode || 'percentage';
|
||||
const customStats = userStatsConfig?.customStats || [];
|
||||
|
||||
// Helper to get maxValue for a stat by id
|
||||
const getMaxValue = (statId) => {
|
||||
const statConfig = customStats.find(s => s.id === statId);
|
||||
return statConfig?.maxValue || 100;
|
||||
};
|
||||
|
||||
// Helper to format stat value based on display mode
|
||||
const formatStatValue = (value, statId) => {
|
||||
if (displayMode === 'number') {
|
||||
const maxValue = getMaxValue(statId);
|
||||
return `${value}/${maxValue}`;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
// Handle stats array format: [{id, name, value}, ...]
|
||||
if (data.stats && Array.isArray(data.stats)) {
|
||||
for (const stat of data.stats) {
|
||||
if (stat && stat.value !== undefined) {
|
||||
const statName = stat.name || (stat.id ? stat.id.charAt(0).toUpperCase() + stat.id.slice(1) : 'Unknown');
|
||||
formatted += `${statName}: ${stat.value}\n`;
|
||||
const statId = stat.id || statName.toLowerCase();
|
||||
formatted += `${statName}: ${formatStatValue(stat.value, statId)}\n`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -564,7 +585,7 @@ function formatTrackerDataForContext(jsonData, trackerType, userName) {
|
||||
const value = getValue(data[statName]);
|
||||
if (value) {
|
||||
const displayName = statName.charAt(0).toUpperCase() + statName.slice(1);
|
||||
formatted += `${displayName}: ${value}\n`;
|
||||
formatted += `${displayName}: ${formatStatValue(value, statName)}\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -573,7 +594,7 @@ function formatTrackerDataForContext(jsonData, trackerType, userName) {
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
if (!statFieldOrder.includes(key) && !specialFields.includes(key) && typeof value === 'number') {
|
||||
const displayName = key.charAt(0).toUpperCase() + key.slice(1);
|
||||
formatted += `${displayName}: ${getValue(value)}\n`;
|
||||
formatted += `${displayName}: ${formatStatValue(getValue(value), key)}\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,8 +359,7 @@ export function onMessageSwiped(messageIndex) {
|
||||
// console.log('[RPG Companion] 🔵 EVENT: onMessageSwiped at index:', messageIndex);
|
||||
|
||||
// Get the message that was swiped
|
||||
const currentChat = getContext().chat;
|
||||
const message = currentChat[messageIndex];
|
||||
const message = chat[messageIndex];
|
||||
if (!message || message.is_user) {
|
||||
// console.log('[RPG Companion] 🔵 Ignoring swipe - message is user or undefined');
|
||||
return;
|
||||
@@ -380,79 +379,32 @@ export function onMessageSwiped(messageIndex) {
|
||||
setLastActionWasSwipe(true);
|
||||
setIsAwaitingNewMessage(true);
|
||||
// console.log('[RPG Companion] 🔵 NEW swipe detected - Set lastActionWasSwipe = true');
|
||||
|
||||
// CRITICAL: For new swipes, commit data from the PREVIOUS assistant message
|
||||
// This ensures the LLM gets context from BEFORE the message being regenerated,
|
||||
// not the message itself (which would cause time/story to advance incorrectly)
|
||||
for (let i = messageIndex - 1; i >= 0; i--) {
|
||||
const prevMessage = currentChat[i];
|
||||
if (!prevMessage.is_user && prevMessage.extra?.rpg_companion_swipes) {
|
||||
const prevSwipeId = prevMessage.swipe_id || 0;
|
||||
const prevSwipeData = prevMessage.extra.rpg_companion_swipes[prevSwipeId];
|
||||
|
||||
if (prevSwipeData) {
|
||||
// console.log('[RPG Companion] 🔵 Committing tracker data from PREVIOUS message at index', i);
|
||||
committedTrackerData.userStats = prevSwipeData.userStats || null;
|
||||
committedTrackerData.infoBox = prevSwipeData.infoBox || null;
|
||||
committedTrackerData.characterThoughts = prevSwipeData.characterThoughts || null;
|
||||
} else {
|
||||
// Previous message has no swipe data - clear committed data
|
||||
committedTrackerData.userStats = null;
|
||||
committedTrackerData.infoBox = null;
|
||||
committedTrackerData.characterThoughts = null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// If we hit index 0 without finding a previous assistant message, clear committed data
|
||||
if (i === 0) {
|
||||
// console.log('[RPG Companion] 🔵 No previous assistant message found - clearing committed data');
|
||||
committedTrackerData.userStats = null;
|
||||
committedTrackerData.infoBox = null;
|
||||
committedTrackerData.characterThoughts = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Edge case: if messageIndex is 0 (first message being swiped), clear committed data
|
||||
if (messageIndex === 0) {
|
||||
// console.log('[RPG Companion] 🔵 Swiping first message - clearing committed data');
|
||||
committedTrackerData.userStats = null;
|
||||
committedTrackerData.infoBox = null;
|
||||
committedTrackerData.characterThoughts = null;
|
||||
}
|
||||
|
||||
// For new swipes, also update lastGeneratedData to match committed data
|
||||
// This ensures the UI shows the "before" state while waiting for the new response
|
||||
lastGeneratedData.userStats = committedTrackerData.userStats;
|
||||
lastGeneratedData.infoBox = committedTrackerData.infoBox;
|
||||
lastGeneratedData.characterThoughts = committedTrackerData.characterThoughts;
|
||||
|
||||
// Parse user stats for display if available
|
||||
if (committedTrackerData.userStats) {
|
||||
parseUserStats(committedTrackerData.userStats);
|
||||
}
|
||||
} else {
|
||||
// This is navigating to an EXISTING swipe - don't change the flag
|
||||
// console.log('[RPG Companion] 🔵 EXISTING swipe navigation - lastActionWasSwipe unchanged =', lastActionWasSwipe);
|
||||
}
|
||||
|
||||
// Load RPG data for this existing swipe for DISPLAY purposes
|
||||
if (message.extra && message.extra.rpg_companion_swipes && message.extra.rpg_companion_swipes[currentSwipeId]) {
|
||||
const swipeData = message.extra.rpg_companion_swipes[currentSwipeId];
|
||||
// console.log('[RPG Companion] Loading data for swipe', currentSwipeId);
|
||||
|
||||
// Load swipe data into lastGeneratedData for display
|
||||
lastGeneratedData.userStats = swipeData.userStats || null;
|
||||
lastGeneratedData.infoBox = swipeData.infoBox || null;
|
||||
lastGeneratedData.characterThoughts = swipeData.characterThoughts || null;
|
||||
// IMPORTANT: onMessageSwiped is for DISPLAY only!
|
||||
// lastGeneratedData is for DISPLAY, committedTrackerData is for GENERATION
|
||||
// It's safe to load swipe data into lastGeneratedData - it won't be committed due to !lastActionWasSwipe check
|
||||
if (message.extra && message.extra.rpg_companion_swipes && message.extra.rpg_companion_swipes[currentSwipeId]) {
|
||||
const swipeData = message.extra.rpg_companion_swipes[currentSwipeId];
|
||||
|
||||
// Parse user stats if available
|
||||
if (swipeData.userStats) {
|
||||
parseUserStats(swipeData.userStats);
|
||||
}
|
||||
// Load swipe data into lastGeneratedData for display (both modes)
|
||||
lastGeneratedData.userStats = swipeData.userStats || null;
|
||||
lastGeneratedData.infoBox = swipeData.infoBox || null;
|
||||
lastGeneratedData.characterThoughts = swipeData.characterThoughts || null;
|
||||
|
||||
// console.log('[RPG Companion] 🔄 Loaded swipe data into lastGeneratedData for display:', currentSwipeId);
|
||||
} else {
|
||||
// console.log('[RPG Companion] ℹ️ No stored data for swipe:', currentSwipeId);
|
||||
// Parse user stats if available
|
||||
if (swipeData.userStats) {
|
||||
parseUserStats(swipeData.userStats);
|
||||
}
|
||||
|
||||
// console.log('[RPG Companion] 🔄 Loaded swipe data into lastGeneratedData for display:', currentSwipeId);
|
||||
} else {
|
||||
// console.log('[RPG Companion] ℹ️ No stored data for swipe:', currentSwipeId);
|
||||
}
|
||||
|
||||
// Re-render the panels
|
||||
@@ -467,148 +419,6 @@ export function onMessageSwiped(messageIndex) {
|
||||
updateChatThoughts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for when a message is deleted.
|
||||
* Restores RPG state from the last assistant message with RPG data,
|
||||
* or clears state if no messages remain.
|
||||
*/
|
||||
export function onMessageDeleted(messageIndex) {
|
||||
if (!extensionSettings.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log('[RPG Companion] 🗑️ EVENT: onMessageDeleted at index:', messageIndex);
|
||||
|
||||
const context = getContext();
|
||||
const currentChat = context.chat;
|
||||
|
||||
// If chat is empty, clear all RPG state
|
||||
if (!currentChat || currentChat.length === 0) {
|
||||
// console.log('[RPG Companion] 🗑️ Chat is empty - clearing RPG state');
|
||||
lastGeneratedData.userStats = null;
|
||||
lastGeneratedData.infoBox = null;
|
||||
lastGeneratedData.characterThoughts = null;
|
||||
|
||||
committedTrackerData.userStats = null;
|
||||
committedTrackerData.infoBox = null;
|
||||
committedTrackerData.characterThoughts = null;
|
||||
|
||||
// Clear parsed stats from extensionSettings
|
||||
if (extensionSettings.userStats) {
|
||||
extensionSettings.userStats = null;
|
||||
}
|
||||
|
||||
// Re-render empty panels
|
||||
renderUserStats();
|
||||
renderInfoBox();
|
||||
renderThoughts();
|
||||
renderInventory();
|
||||
renderQuests();
|
||||
renderMusicPlayer($musicPlayerContainer[0]);
|
||||
|
||||
// Update FAB widgets and strip widgets
|
||||
updateFabWidgets();
|
||||
updateStripWidgets();
|
||||
|
||||
// Update chat thought overlays (removes any remaining)
|
||||
updateChatThoughts();
|
||||
|
||||
// Save the cleared state
|
||||
saveChatData();
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the last assistant message with RPG data
|
||||
for (let i = currentChat.length - 1; i >= 0; i--) {
|
||||
const message = currentChat[i];
|
||||
if (!message.is_user && message.extra?.rpg_companion_swipes) {
|
||||
const swipeId = message.swipe_id || 0;
|
||||
const swipeData = message.extra.rpg_companion_swipes[swipeId];
|
||||
|
||||
if (swipeData) {
|
||||
// Check if this is the same data we already have displayed
|
||||
const sameUserStats = lastGeneratedData.userStats === swipeData.userStats;
|
||||
const sameInfoBox = lastGeneratedData.infoBox === swipeData.infoBox;
|
||||
const sameThoughts = lastGeneratedData.characterThoughts === swipeData.characterThoughts;
|
||||
|
||||
if (sameUserStats && sameInfoBox && sameThoughts) {
|
||||
// console.log('[RPG Companion] 🗑️ RPG state already matches last message - no restore needed');
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log('[RPG Companion] 🗑️ Restoring RPG state from message index', i, 'swipe', swipeId);
|
||||
|
||||
// Restore state from this message
|
||||
lastGeneratedData.userStats = swipeData.userStats || null;
|
||||
lastGeneratedData.infoBox = swipeData.infoBox || null;
|
||||
lastGeneratedData.characterThoughts = swipeData.characterThoughts || null;
|
||||
|
||||
// Also update committed data so next generation uses correct context
|
||||
committedTrackerData.userStats = swipeData.userStats || null;
|
||||
committedTrackerData.infoBox = swipeData.infoBox || null;
|
||||
committedTrackerData.characterThoughts = swipeData.characterThoughts || null;
|
||||
|
||||
// Parse user stats if available
|
||||
if (swipeData.userStats) {
|
||||
parseUserStats(swipeData.userStats);
|
||||
}
|
||||
|
||||
// Re-render panels with restored data
|
||||
renderUserStats();
|
||||
renderInfoBox();
|
||||
renderThoughts();
|
||||
renderInventory();
|
||||
renderQuests();
|
||||
renderMusicPlayer($musicPlayerContainer[0]);
|
||||
|
||||
// Update FAB widgets and strip widgets
|
||||
updateFabWidgets();
|
||||
updateStripWidgets();
|
||||
|
||||
// Update chat thought overlays
|
||||
updateChatThoughts();
|
||||
|
||||
// Save the restored state
|
||||
saveChatData();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No assistant message with RPG data found - clear state
|
||||
// console.log('[RPG Companion] 🗑️ No assistant message with RPG data found - clearing state');
|
||||
lastGeneratedData.userStats = null;
|
||||
lastGeneratedData.infoBox = null;
|
||||
lastGeneratedData.characterThoughts = null;
|
||||
|
||||
committedTrackerData.userStats = null;
|
||||
committedTrackerData.infoBox = null;
|
||||
committedTrackerData.characterThoughts = null;
|
||||
|
||||
// Clear parsed stats
|
||||
if (extensionSettings.userStats) {
|
||||
extensionSettings.userStats = null;
|
||||
}
|
||||
|
||||
// Re-render empty panels
|
||||
renderUserStats();
|
||||
renderInfoBox();
|
||||
renderThoughts();
|
||||
renderInventory();
|
||||
renderQuests();
|
||||
renderMusicPlayer($musicPlayerContainer[0]);
|
||||
|
||||
// Update FAB widgets and strip widgets
|
||||
updateFabWidgets();
|
||||
updateStripWidgets();
|
||||
|
||||
// Update chat thought overlays
|
||||
updateChatThoughts();
|
||||
|
||||
// Save the cleared state
|
||||
saveChatData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the persona avatar image when user switches personas
|
||||
*/
|
||||
|
||||
@@ -237,9 +237,9 @@ function createMist() {
|
||||
* Returns { left: vw%, top: dvh% }
|
||||
*/
|
||||
function calculateSunPosition(hour) {
|
||||
// Daytime is roughly 6 AM to 8 PM (6-20)
|
||||
// Daytime is roughly 5 AM to 8 PM (5-20)
|
||||
// Map hour to position along an arc
|
||||
// 6 AM = far left, low | 12 PM = center, high | 6 PM = far right, low
|
||||
// 5 AM = far left, low | 12 PM = center, high | 8 PM = far right, low
|
||||
|
||||
if (hour === null) hour = 12; // Default to noon if unknown
|
||||
|
||||
@@ -249,14 +249,14 @@ function calculateSunPosition(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;
|
||||
// Horizontal position: 3% to 92% (left to right, wider range)
|
||||
const left = 3 + progress * 89;
|
||||
|
||||
// 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)
|
||||
// At progress 0 or 1, top should be ~40% (low, near horizon)
|
||||
const normalizedProgress = (progress - 0.5) * 2; // -1 to 1
|
||||
const top = 8 + 27 * (normalizedProgress * normalizedProgress);
|
||||
const top = 8 + 32 * (normalizedProgress * normalizedProgress);
|
||||
|
||||
return { left, top };
|
||||
}
|
||||
@@ -327,6 +327,134 @@ function createSunshine(hour) {
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create sunrise effect (dawn - warm orange/pink sky gradient with low sun)
|
||||
*/
|
||||
function createSunrise(hour) {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'rpg-weather-particles rpg-sunrise-weather';
|
||||
|
||||
// Create sunrise gradient overlay
|
||||
const sunriseOverlay = document.createElement('div');
|
||||
sunriseOverlay.className = 'rpg-weather-particle rpg-sunrise-overlay';
|
||||
container.appendChild(sunriseOverlay);
|
||||
|
||||
// Calculate sun position (rising from left horizon)
|
||||
const sunPos = calculateSunPosition(hour);
|
||||
|
||||
// Create the rising sun
|
||||
const sun = document.createElement('div');
|
||||
sun.className = 'rpg-weather-particle rpg-clear-sun rpg-sunrise-sun';
|
||||
sun.style.left = `${sunPos.left}vw`;
|
||||
sun.style.top = `${sunPos.top}dvh`;
|
||||
container.appendChild(sun);
|
||||
|
||||
// Create sun glow (more orange during sunrise)
|
||||
const sunGlow = document.createElement('div');
|
||||
sunGlow.className = 'rpg-weather-particle rpg-clear-sun-glow rpg-sunrise-glow';
|
||||
sunGlow.style.left = `${sunPos.left}vw`;
|
||||
sunGlow.style.top = `${sunPos.top}dvh`;
|
||||
container.appendChild(sunGlow);
|
||||
|
||||
// Create horizon glow
|
||||
const horizonGlow = document.createElement('div');
|
||||
horizonGlow.className = 'rpg-weather-particle rpg-sunrise-horizon-glow';
|
||||
container.appendChild(horizonGlow);
|
||||
|
||||
// Add some fading stars (still visible at dawn)
|
||||
for (let i = 0; i < 15; i++) {
|
||||
const star = document.createElement('div');
|
||||
star.className = 'rpg-weather-particle rpg-night-star rpg-sunrise-fading-star';
|
||||
star.style.left = `${Math.random() * 100}vw`;
|
||||
star.style.top = `${Math.random() * 40}dvh`;
|
||||
star.style.animationDelay = `${Math.random() * 3}s`;
|
||||
const size = 1 + Math.random() * 1.5;
|
||||
star.style.width = `${size}px`;
|
||||
star.style.height = `${size}px`;
|
||||
container.appendChild(star);
|
||||
}
|
||||
|
||||
// Add some golden dust motes
|
||||
for (let i = 0; i < 12; 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`;
|
||||
const size = 2 + Math.random() * 3;
|
||||
particle.style.width = `${size}px`;
|
||||
particle.style.height = `${size}px`;
|
||||
container.appendChild(particle);
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create sunset effect (dusk - warm red/purple sky gradient with low sun)
|
||||
*/
|
||||
function createSunset(hour) {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'rpg-weather-particles rpg-sunset-weather';
|
||||
|
||||
// Create sunset gradient overlay
|
||||
const sunsetOverlay = document.createElement('div');
|
||||
sunsetOverlay.className = 'rpg-weather-particle rpg-sunset-overlay';
|
||||
container.appendChild(sunsetOverlay);
|
||||
|
||||
// Calculate sun position (setting on right horizon)
|
||||
const sunPos = calculateSunPosition(hour);
|
||||
|
||||
// Create the setting sun
|
||||
const sun = document.createElement('div');
|
||||
sun.className = 'rpg-weather-particle rpg-clear-sun rpg-sunset-sun';
|
||||
sun.style.left = `${sunPos.left}vw`;
|
||||
sun.style.top = `${sunPos.top}dvh`;
|
||||
container.appendChild(sun);
|
||||
|
||||
// Create sun glow (more red during sunset)
|
||||
const sunGlow = document.createElement('div');
|
||||
sunGlow.className = 'rpg-weather-particle rpg-clear-sun-glow rpg-sunset-glow';
|
||||
sunGlow.style.left = `${sunPos.left}vw`;
|
||||
sunGlow.style.top = `${sunPos.top}dvh`;
|
||||
container.appendChild(sunGlow);
|
||||
|
||||
// Create horizon glow
|
||||
const horizonGlow = document.createElement('div');
|
||||
horizonGlow.className = 'rpg-weather-particle rpg-sunset-horizon-glow';
|
||||
container.appendChild(horizonGlow);
|
||||
|
||||
// Add some early stars (appearing at dusk)
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const star = document.createElement('div');
|
||||
star.className = 'rpg-weather-particle rpg-night-star rpg-sunset-emerging-star';
|
||||
star.style.left = `${Math.random() * 100}vw`;
|
||||
star.style.top = `${Math.random() * 50}dvh`;
|
||||
star.style.animationDelay = `${Math.random() * 5}s`;
|
||||
const size = 1 + Math.random() * 1.5;
|
||||
star.style.width = `${size}px`;
|
||||
star.style.height = `${size}px`;
|
||||
container.appendChild(star);
|
||||
}
|
||||
|
||||
// Add some golden/pink dust motes
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const particle = document.createElement('div');
|
||||
particle.className = 'rpg-weather-particle rpg-clear-dust-mote rpg-sunset-dust';
|
||||
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`;
|
||||
const size = 2 + Math.random() * 3;
|
||||
particle.style.width = `${size}px`;
|
||||
particle.style.height = `${size}px`;
|
||||
container.appendChild(particle);
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create clear nighttime weather effect with moon, stars, and fireflies
|
||||
*/
|
||||
@@ -572,9 +700,13 @@ export function updateWeatherEffect() {
|
||||
weatherContainer = createMist();
|
||||
break;
|
||||
case 'sunny':
|
||||
// Use nighttime effect for clear weather at night
|
||||
// Use appropriate effect based on time of day
|
||||
if (timeOfDay === 'night') {
|
||||
weatherContainer = createNighttime(hour);
|
||||
} else if (timeOfDay === 'dawn') {
|
||||
weatherContainer = createSunrise(hour);
|
||||
} else if (timeOfDay === 'dusk') {
|
||||
weatherContainer = createSunset(hour);
|
||||
} else {
|
||||
weatherContainer = createSunshine(hour);
|
||||
}
|
||||
|
||||
@@ -9898,6 +9898,200 @@ body[data-theme="cyberpunk"] .rpg-music-widget-play {
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Sunrise Effects (Dawn) ===== */
|
||||
|
||||
.rpg-sunrise-weather {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Sunrise sky gradient overlay */
|
||||
.rpg-sunrise-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100dvh;
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(40, 30, 80, 0.1) 0%,
|
||||
rgba(120, 60, 120, 0.08) 15%,
|
||||
rgba(200, 100, 100, 0.1) 35%,
|
||||
rgba(255, 140, 100, 0.12) 55%,
|
||||
rgba(255, 180, 120, 0.1) 75%,
|
||||
rgba(255, 200, 150, 0.08) 100%);
|
||||
animation: rpg-sunrise-sky-transition 30s ease-in-out infinite alternate;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes rpg-sunrise-sky-transition {
|
||||
0% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sunrise sun - more orange/red */
|
||||
.rpg-sunrise-sun {
|
||||
background: radial-gradient(circle at 40% 40%,
|
||||
rgba(255, 255, 220, 1) 0%,
|
||||
rgba(255, 220, 150, 1) 30%,
|
||||
rgba(255, 160, 80, 0.9) 60%,
|
||||
rgba(255, 100, 50, 0.6) 80%,
|
||||
rgba(255, 80, 30, 0) 100%) !important;
|
||||
box-shadow:
|
||||
0 0 40px 15px rgba(255, 180, 100, 0.6),
|
||||
0 0 80px 30px rgba(255, 140, 80, 0.4),
|
||||
0 0 120px 50px rgba(255, 100, 50, 0.2) !important;
|
||||
}
|
||||
|
||||
/* Sunrise sun glow - warm orange */
|
||||
.rpg-sunrise-glow {
|
||||
background: radial-gradient(circle at center,
|
||||
rgba(255, 200, 150, 0.35) 0%,
|
||||
rgba(255, 160, 100, 0.2) 30%,
|
||||
rgba(255, 120, 80, 0.1) 50%,
|
||||
transparent 70%) !important;
|
||||
}
|
||||
|
||||
/* Horizon glow for sunrise */
|
||||
.rpg-sunrise-horizon-glow {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 40dvh;
|
||||
background: linear-gradient(to top,
|
||||
rgba(255, 160, 100, 0.15) 0%,
|
||||
rgba(255, 140, 90, 0.1) 20%,
|
||||
rgba(255, 120, 80, 0.05) 50%,
|
||||
rgba(255, 100, 70, 0.02) 75%,
|
||||
transparent 100%);
|
||||
animation: rpg-horizon-glow-pulse 8s ease-in-out infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes rpg-horizon-glow-pulse {
|
||||
0%, 100% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fading stars at sunrise */
|
||||
.rpg-sunrise-fading-star {
|
||||
opacity: 0.3 !important;
|
||||
animation: rpg-star-fade-out 4s ease-in-out infinite !important;
|
||||
}
|
||||
|
||||
@keyframes rpg-star-fade-out {
|
||||
0%, 100% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Sunset Effects (Dusk) ===== */
|
||||
|
||||
.rpg-sunset-weather {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Sunset sky gradient overlay */
|
||||
.rpg-sunset-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100dvh;
|
||||
background: linear-gradient(to bottom,
|
||||
rgba(30, 20, 60, 0.12) 0%,
|
||||
rgba(80, 40, 100, 0.1) 15%,
|
||||
rgba(150, 60, 90, 0.12) 30%,
|
||||
rgba(220, 80, 70, 0.12) 50%,
|
||||
rgba(255, 120, 80, 0.12) 70%,
|
||||
rgba(255, 160, 100, 0.1) 85%,
|
||||
rgba(255, 180, 120, 0.06) 100%);
|
||||
animation: rpg-sunset-sky-transition 30s ease-in-out infinite alternate;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes rpg-sunset-sky-transition {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sunset sun - more red/deep orange */
|
||||
.rpg-sunset-sun {
|
||||
background: radial-gradient(circle at 40% 40%,
|
||||
rgba(255, 240, 200, 1) 0%,
|
||||
rgba(255, 180, 100, 1) 30%,
|
||||
rgba(255, 120, 60, 0.9) 60%,
|
||||
rgba(255, 80, 40, 0.6) 80%,
|
||||
rgba(200, 50, 30, 0) 100%) !important;
|
||||
box-shadow:
|
||||
0 0 40px 15px rgba(255, 140, 80, 0.6),
|
||||
0 0 80px 30px rgba(255, 100, 60, 0.4),
|
||||
0 0 120px 50px rgba(200, 60, 40, 0.2) !important;
|
||||
}
|
||||
|
||||
/* Sunset sun glow - deep orange/red */
|
||||
.rpg-sunset-glow {
|
||||
background: radial-gradient(circle at center,
|
||||
rgba(255, 160, 120, 0.35) 0%,
|
||||
rgba(255, 120, 80, 0.2) 30%,
|
||||
rgba(200, 80, 60, 0.1) 50%,
|
||||
transparent 70%) !important;
|
||||
}
|
||||
|
||||
/* Horizon glow for sunset */
|
||||
.rpg-sunset-horizon-glow {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 45dvh;
|
||||
background: linear-gradient(to top,
|
||||
rgba(255, 120, 60, 0.18) 0%,
|
||||
rgba(255, 100, 50, 0.12) 20%,
|
||||
rgba(220, 70, 50, 0.06) 45%,
|
||||
rgba(150, 50, 60, 0.02) 70%,
|
||||
transparent 100%);
|
||||
animation: rpg-horizon-glow-pulse 8s ease-in-out infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Emerging stars at sunset */
|
||||
.rpg-sunset-emerging-star {
|
||||
animation: rpg-star-emerge 5s ease-in-out infinite !important;
|
||||
}
|
||||
|
||||
@keyframes rpg-star-emerge {
|
||||
0%, 100% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sunset dust motes - pinkish tint */
|
||||
.rpg-sunset-dust {
|
||||
background: radial-gradient(circle at 30% 30%,
|
||||
rgba(255, 200, 180, 0.9) 0%,
|
||||
rgba(255, 180, 160, 0.6) 50%,
|
||||
rgba(255, 160, 140, 0) 100%) !important;
|
||||
box-shadow: 0 0 6px 2px rgba(255, 180, 160, 0.4) !important;
|
||||
}
|
||||
|
||||
/* Lens flare effect */
|
||||
.rpg-clear-lens-flare {
|
||||
position: fixed;
|
||||
@@ -10272,6 +10466,12 @@ body[data-theme="cyberpunk"] .rpg-music-widget-play {
|
||||
.rpg-night-shooting-star {
|
||||
animation-duration: 18s;
|
||||
}
|
||||
|
||||
/* Sunrise/Sunset mobile optimizations */
|
||||
.rpg-sunrise-horizon-glow,
|
||||
.rpg-sunset-horizon-glow {
|
||||
height: 35%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Foreground mode - reduced opacity for celestial bodies to not obstruct content */
|
||||
@@ -10319,6 +10519,32 @@ body[data-theme="cyberpunk"] .rpg-music-widget-play {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Sunrise/Sunset foreground mode */
|
||||
.rpg-weather-foreground .rpg-sunrise-overlay,
|
||||
.rpg-weather-foreground .rpg-sunset-overlay {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.rpg-weather-foreground .rpg-sunrise-horizon-glow,
|
||||
.rpg-weather-foreground .rpg-sunset-horizon-glow {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.rpg-weather-foreground .rpg-sunrise-sun,
|
||||
.rpg-weather-foreground .rpg-sunset-sun {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
.rpg-weather-foreground .rpg-sunrise-glow,
|
||||
.rpg-weather-foreground .rpg-sunset-glow {
|
||||
opacity: 0.3 !important;
|
||||
}
|
||||
|
||||
.rpg-weather-foreground .rpg-sunrise-fading-star,
|
||||
.rpg-weather-foreground .rpg-sunset-emerging-star {
|
||||
opacity: 0.2 !important;
|
||||
}
|
||||
|
||||
/* Lightning flash effect */
|
||||
.rpg-lightning {
|
||||
position: fixed;
|
||||
|
||||
Reference in New Issue
Block a user