Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fd8afba7f2 |
@@ -7,10 +7,11 @@ An immersive RPG extension for browsers that tracks character stats, scene infor
|
|||||||
|
|
||||||
## 🆕 What's New
|
## 🆕 What's New
|
||||||
|
|
||||||
### v3.6.2
|
### v3.6.1
|
||||||
|
|
||||||
- Various bug fixes.
|
- Fixed the bugs in the encounter system where you couldn't use the buttons after performing any custom action.
|
||||||
- Added the ability to add present characters manually.
|
- Improved combat actions and made them dynamic, depending on the current situation.
|
||||||
|
- Added Russian as a supported language.
|
||||||
|
|
||||||
**Special thanks to all the other contributors for this project:**
|
**Special thanks to all the other contributors for this project:**
|
||||||
Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDeathByte, Chungchandev, Joenunezb, Amauragis, and Tomt610.
|
Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDeathByte, Chungchandev, Joenunezb, Amauragis, and Tomt610.
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ import {
|
|||||||
onMessageReceived,
|
onMessageReceived,
|
||||||
onCharacterChanged,
|
onCharacterChanged,
|
||||||
onMessageSwiped,
|
onMessageSwiped,
|
||||||
|
onMessageDeleted,
|
||||||
updatePersonaAvatar,
|
updatePersonaAvatar,
|
||||||
clearExtensionPrompts,
|
clearExtensionPrompts,
|
||||||
onGenerationEnded,
|
onGenerationEnded,
|
||||||
@@ -1254,6 +1255,7 @@ jQuery(async () => {
|
|||||||
[event_types.GENERATION_ENDED]: onGenerationEnded,
|
[event_types.GENERATION_ENDED]: onGenerationEnded,
|
||||||
[event_types.CHAT_CHANGED]: [onCharacterChanged, updatePersonaAvatar, restoreCheckpointOnLoad, clearSessionAvatarPrompts],
|
[event_types.CHAT_CHANGED]: [onCharacterChanged, updatePersonaAvatar, restoreCheckpointOnLoad, clearSessionAvatarPrompts],
|
||||||
[event_types.MESSAGE_SWIPED]: onMessageSwiped,
|
[event_types.MESSAGE_SWIPED]: onMessageSwiped,
|
||||||
|
[event_types.MESSAGE_DELETED]: onMessageDeleted,
|
||||||
[event_types.USER_MESSAGE_RENDERED]: updatePersonaAvatar,
|
[event_types.USER_MESSAGE_RENDERED]: updatePersonaAvatar,
|
||||||
[event_types.SETTINGS_UPDATED]: updatePersonaAvatar
|
[event_types.SETTINGS_UPDATED]: updatePersonaAvatar
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -6,6 +6,6 @@
|
|||||||
"js": "index.js",
|
"js": "index.js",
|
||||||
"css": "style.css",
|
"css": "style.css",
|
||||||
"author": "Marinara",
|
"author": "Marinara",
|
||||||
"version": "3.6.3",
|
"version": "3.6.1",
|
||||||
"homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern"
|
"homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -15,7 +15,6 @@
|
|||||||
<select id="rpg-companion-language-select" class="text_pole">
|
<select id="rpg-companion-language-select" class="text_pole">
|
||||||
<option value="en" data-i18n-key="settings.language.option.en">English</option>
|
<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="zh-tw" data-i18n-key="settings.language.option.zh-tw">繁體中文</option>
|
||||||
<option value="ru" data-i18n-key="settings.language.option.ru">Русский</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -49,7 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-top: 10px; text-align: center; opacity: 0.6; font-size: 0.85em;">
|
<div style="margin-top: 10px; text-align: center; opacity: 0.6; font-size: 0.85em;">
|
||||||
v3.6.2
|
v3.6.1
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -546,33 +546,12 @@ function formatTrackerDataForContext(jsonData, trackerType, userName) {
|
|||||||
if (trackerType === 'userStats') {
|
if (trackerType === 'userStats') {
|
||||||
formatted += `${userName}'s Stats:\n`;
|
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}, ...]
|
// Handle stats array format: [{id, name, value}, ...]
|
||||||
if (data.stats && Array.isArray(data.stats)) {
|
if (data.stats && Array.isArray(data.stats)) {
|
||||||
for (const stat of data.stats) {
|
for (const stat of data.stats) {
|
||||||
if (stat && stat.value !== undefined) {
|
if (stat && stat.value !== undefined) {
|
||||||
const statName = stat.name || (stat.id ? stat.id.charAt(0).toUpperCase() + stat.id.slice(1) : 'Unknown');
|
const statName = stat.name || (stat.id ? stat.id.charAt(0).toUpperCase() + stat.id.slice(1) : 'Unknown');
|
||||||
const statId = stat.id || statName.toLowerCase();
|
formatted += `${statName}: ${stat.value}\n`;
|
||||||
formatted += `${statName}: ${formatStatValue(stat.value, statId)}\n`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -585,7 +564,7 @@ function formatTrackerDataForContext(jsonData, trackerType, userName) {
|
|||||||
const value = getValue(data[statName]);
|
const value = getValue(data[statName]);
|
||||||
if (value) {
|
if (value) {
|
||||||
const displayName = statName.charAt(0).toUpperCase() + statName.slice(1);
|
const displayName = statName.charAt(0).toUpperCase() + statName.slice(1);
|
||||||
formatted += `${displayName}: ${formatStatValue(value, statName)}\n`;
|
formatted += `${displayName}: ${value}\n`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -594,7 +573,7 @@ function formatTrackerDataForContext(jsonData, trackerType, userName) {
|
|||||||
for (const [key, value] of Object.entries(data)) {
|
for (const [key, value] of Object.entries(data)) {
|
||||||
if (!statFieldOrder.includes(key) && !specialFields.includes(key) && typeof value === 'number') {
|
if (!statFieldOrder.includes(key) && !specialFields.includes(key) && typeof value === 'number') {
|
||||||
const displayName = key.charAt(0).toUpperCase() + key.slice(1);
|
const displayName = key.charAt(0).toUpperCase() + key.slice(1);
|
||||||
formatted += `${displayName}: ${formatStatValue(getValue(value), key)}\n`;
|
formatted += `${displayName}: ${getValue(value)}\n`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -725,14 +704,13 @@ function formatTrackerDataForContext(jsonData, trackerType, userName) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Relationship - check both Relationship (new format) and relationship (old format)
|
// Relationship
|
||||||
const relationshipValue = char.Relationship || char.relationship;
|
if (char.relationship) {
|
||||||
if (relationshipValue) {
|
|
||||||
let relValue;
|
let relValue;
|
||||||
if (typeof relationshipValue === 'object' && !Array.isArray(relationshipValue) && 'status' in relationshipValue) {
|
if (typeof char.relationship === 'object' && !Array.isArray(char.relationship) && 'status' in char.relationship) {
|
||||||
relValue = getValue(relationshipValue.status);
|
relValue = getValue(char.relationship.status);
|
||||||
} else {
|
} else {
|
||||||
relValue = getValue(relationshipValue);
|
relValue = getValue(char.relationship);
|
||||||
}
|
}
|
||||||
if (relValue) formatted += ` Relationship: ${relValue}\n`;
|
if (relValue) formatted += ` Relationship: ${relValue}\n`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -359,7 +359,8 @@ export function onMessageSwiped(messageIndex) {
|
|||||||
// console.log('[RPG Companion] 🔵 EVENT: onMessageSwiped at index:', messageIndex);
|
// console.log('[RPG Companion] 🔵 EVENT: onMessageSwiped at index:', messageIndex);
|
||||||
|
|
||||||
// Get the message that was swiped
|
// Get the message that was swiped
|
||||||
const message = chat[messageIndex];
|
const currentChat = getContext().chat;
|
||||||
|
const message = currentChat[messageIndex];
|
||||||
if (!message || message.is_user) {
|
if (!message || message.is_user) {
|
||||||
// console.log('[RPG Companion] 🔵 Ignoring swipe - message is user or undefined');
|
// console.log('[RPG Companion] 🔵 Ignoring swipe - message is user or undefined');
|
||||||
return;
|
return;
|
||||||
@@ -379,40 +380,80 @@ export function onMessageSwiped(messageIndex) {
|
|||||||
setLastActionWasSwipe(true);
|
setLastActionWasSwipe(true);
|
||||||
setIsAwaitingNewMessage(true);
|
setIsAwaitingNewMessage(true);
|
||||||
// console.log('[RPG Companion] 🔵 NEW swipe detected - Set lastActionWasSwipe = 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 {
|
} else {
|
||||||
// This is navigating to an EXISTING swipe - don't change the flag
|
// This is navigating to an EXISTING swipe - don't change the flag
|
||||||
// console.log('[RPG Companion] 🔵 EXISTING swipe navigation - lastActionWasSwipe unchanged =', lastActionWasSwipe);
|
// console.log('[RPG Companion] 🔵 EXISTING swipe navigation - lastActionWasSwipe unchanged =', lastActionWasSwipe);
|
||||||
}
|
|
||||||
|
|
||||||
// console.log('[RPG Companion] Loading data for swipe', currentSwipeId);
|
// Load RPG data for this existing swipe for DISPLAY purposes
|
||||||
|
|
||||||
// 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]) {
|
if (message.extra && message.extra.rpg_companion_swipes && message.extra.rpg_companion_swipes[currentSwipeId]) {
|
||||||
const swipeData = message.extra.rpg_companion_swipes[currentSwipeId];
|
const swipeData = message.extra.rpg_companion_swipes[currentSwipeId];
|
||||||
|
|
||||||
// Load swipe data into lastGeneratedData for display (both modes)
|
// Load swipe data into lastGeneratedData for display
|
||||||
lastGeneratedData.userStats = swipeData.userStats || null;
|
lastGeneratedData.userStats = swipeData.userStats || null;
|
||||||
lastGeneratedData.infoBox = swipeData.infoBox || null;
|
lastGeneratedData.infoBox = swipeData.infoBox || null;
|
||||||
|
|
||||||
// Normalize characterThoughts to string format (for backward compatibility with old object format)
|
|
||||||
if (swipeData.characterThoughts && typeof swipeData.characterThoughts === 'object') {
|
|
||||||
lastGeneratedData.characterThoughts = JSON.stringify(swipeData.characterThoughts, null, 2);
|
|
||||||
} else {
|
|
||||||
lastGeneratedData.characterThoughts = swipeData.characterThoughts || null;
|
lastGeneratedData.characterThoughts = swipeData.characterThoughts || null;
|
||||||
}
|
|
||||||
|
|
||||||
// DON'T parse user stats when loading swipe data
|
// Parse user stats if available
|
||||||
// This would overwrite manually edited fields (like Conditions) with old swipe data
|
if (swipeData.userStats) {
|
||||||
// The lastGeneratedData is loaded for display purposes only
|
parseUserStats(swipeData.userStats);
|
||||||
// parseUserStats() updates extensionSettings.userStats which should only be modified
|
}
|
||||||
// by new generations or manual edits, not by swipe navigation
|
|
||||||
|
|
||||||
// console.log('[RPG Companion] 🔄 Loaded swipe data into lastGeneratedData for display:', currentSwipeId);
|
// console.log('[RPG Companion] 🔄 Loaded swipe data into lastGeneratedData for display:', currentSwipeId);
|
||||||
} else {
|
} else {
|
||||||
// console.log('[RPG Companion] ℹ️ No stored data for swipe:', currentSwipeId);
|
// console.log('[RPG Companion] ℹ️ No stored data for swipe:', currentSwipeId);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Re-render the panels
|
// Re-render the panels
|
||||||
renderUserStats();
|
renderUserStats();
|
||||||
@@ -426,6 +467,148 @@ export function onMessageSwiped(messageIndex) {
|
|||||||
updateChatThoughts();
|
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
|
* Update the persona avatar image when user switches personas
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -512,20 +512,17 @@ export function renderThoughts() {
|
|||||||
const fieldNameLower = field.name.toLowerCase();
|
const fieldNameLower = field.name.toLowerCase();
|
||||||
// Skip lock icons for thoughts field
|
// Skip lock icons for thoughts field
|
||||||
const showLock = !fieldNameLower.includes('thought');
|
const showLock = !fieldNameLower.includes('thought');
|
||||||
// Add placeholder for empty fields
|
|
||||||
const placeholder = fieldValue ? '' : `data-placeholder="${field.name}"`;
|
|
||||||
const emptyClass = fieldValue ? '' : ' rpg-empty-field';
|
|
||||||
if (showLock) {
|
if (showLock) {
|
||||||
const lockIconHtml = getLockIconHtml('characters', `${char.name}.${field.name}`);
|
const lockIconHtml = getLockIconHtml('characters', `${char.name}.${field.name}`);
|
||||||
html += `
|
html += `
|
||||||
<div class="rpg-character-field rpg-character-${fieldId}" style="position: relative;">
|
<div class="rpg-character-field rpg-character-${fieldId}" style="position: relative;">
|
||||||
${lockIconHtml}
|
${lockIconHtml}
|
||||||
<span class="rpg-editable${emptyClass}" contenteditable="true" data-character="${char.name}" data-field="${field.name}" title="Click to edit ${field.name}" ${placeholder}>${fieldValue}</span>
|
<span class="rpg-editable" contenteditable="true" data-character="${char.name}" data-field="${field.name}" title="Click to edit ${field.name}">${fieldValue}</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
html += `
|
html += `
|
||||||
<div class="rpg-character-field rpg-character-${fieldId} rpg-editable${emptyClass}" contenteditable="true" data-character="${char.name}" data-field="${field.name}" title="Click to edit ${field.name}" ${placeholder}>${fieldValue}</div>
|
<div class="rpg-character-field rpg-character-${fieldId} rpg-editable" contenteditable="true" data-character="${char.name}" data-field="${field.name}" title="Click to edit ${field.name}">${fieldValue}</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -567,16 +564,6 @@ export function renderThoughts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
debugLog('[RPG Thoughts] Finished building all character cards');
|
debugLog('[RPG Thoughts] Finished building all character cards');
|
||||||
|
|
||||||
// Add "Add Character" button if data exists (inside rpg-thoughts-content)
|
|
||||||
if (presentCharacters.length > 0) {
|
|
||||||
html += `
|
|
||||||
<button class="rpg-add-character-btn" title="Add a new character">
|
|
||||||
<i class="fa-solid fa-plus"></i> Add Character
|
|
||||||
</button>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -675,31 +662,6 @@ export function renderThoughts() {
|
|||||||
fileInput.trigger('click');
|
fileInput.trigger('click');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add event listener for "Add Character" button (support both click and touch for mobile)
|
|
||||||
$thoughtsContainer.find('.rpg-add-character-btn').on('click touchend', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
addNewCharacter();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle empty field focus - remove placeholder styling on focus
|
|
||||||
$thoughtsContainer.find('.rpg-editable.rpg-empty-field').on('focus', function() {
|
|
||||||
$(this).removeClass('rpg-empty-field');
|
|
||||||
$(this).removeAttr('data-placeholder');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Restore placeholder if field becomes empty on blur (after the main blur handler)
|
|
||||||
$thoughtsContainer.find('.rpg-editable').on('blur', function() {
|
|
||||||
const $this = $(this);
|
|
||||||
if (!$this.text().trim()) {
|
|
||||||
const field = $this.data('field');
|
|
||||||
if (field) {
|
|
||||||
$this.addClass('rpg-empty-field');
|
|
||||||
$this.attr('data-placeholder', field);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove updating class after animation
|
// Remove updating class after animation
|
||||||
if (extensionSettings.enableAnimations) {
|
if (extensionSettings.enableAnimations) {
|
||||||
setTimeout(() => $thoughtsContainer.removeClass('rpg-content-updating'), 600);
|
setTimeout(() => $thoughtsContainer.removeClass('rpg-content-updating'), 600);
|
||||||
@@ -826,136 +788,6 @@ export function removeCharacter(characterName) {
|
|||||||
renderThoughts();
|
renderThoughts();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new blank character to Present Characters data.
|
|
||||||
* Creates a character with empty fields based on the tracker template.
|
|
||||||
*/
|
|
||||||
export function addNewCharacter() {
|
|
||||||
const presentCharsConfig = extensionSettings.trackerConfig?.presentCharacters;
|
|
||||||
const enabledFields = presentCharsConfig?.customFields?.filter(f => f && f.enabled && f.name) || [];
|
|
||||||
const characterStats = presentCharsConfig?.characterStats;
|
|
||||||
const enabledCharStats = characterStats?.enabled && characterStats?.customStats?.filter(s => s && s.enabled && s.name) || [];
|
|
||||||
const hasRelationship = presentCharsConfig?.relationshipFields?.length > 0;
|
|
||||||
|
|
||||||
// Check if data is in JSON format
|
|
||||||
let isJSON = false;
|
|
||||||
let parsedData = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
parsedData = typeof lastGeneratedData.characterThoughts === 'string'
|
|
||||||
? JSON.parse(lastGeneratedData.characterThoughts)
|
|
||||||
: lastGeneratedData.characterThoughts;
|
|
||||||
|
|
||||||
if (Array.isArray(parsedData) || (parsedData && parsedData.characters)) {
|
|
||||||
isJSON = true;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Not JSON, treat as text format
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isJSON) {
|
|
||||||
// JSON format - add new character object
|
|
||||||
const charactersArray = Array.isArray(parsedData) ? parsedData : (parsedData.characters || []);
|
|
||||||
|
|
||||||
const newCharacter = {
|
|
||||||
name: 'New Character',
|
|
||||||
emoji: '👤',
|
|
||||||
details: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add all enabled custom fields as empty
|
|
||||||
for (const field of enabledFields) {
|
|
||||||
newCharacter.details[field.name] = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add relationship if enabled
|
|
||||||
if (hasRelationship) {
|
|
||||||
newCharacter.relationship = 'Neutral';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add stats if enabled
|
|
||||||
if (enabledCharStats.length > 0) {
|
|
||||||
newCharacter.stats = {};
|
|
||||||
for (const stat of enabledCharStats) {
|
|
||||||
newCharacter.stats[stat.name] = 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
charactersArray.push(newCharacter);
|
|
||||||
|
|
||||||
// Save back as JSON string
|
|
||||||
lastGeneratedData.characterThoughts = JSON.stringify(
|
|
||||||
Array.isArray(parsedData) ? charactersArray : { ...parsedData, characters: charactersArray },
|
|
||||||
null,
|
|
||||||
2
|
|
||||||
);
|
|
||||||
committedTrackerData.characterThoughts = lastGeneratedData.characterThoughts;
|
|
||||||
} else {
|
|
||||||
// Text format - add new character block
|
|
||||||
const lines = lastGeneratedData.characterThoughts.split('\n');
|
|
||||||
const dividerIndex = lines.findIndex(line => line.includes('---'));
|
|
||||||
|
|
||||||
if (dividerIndex >= 0) {
|
|
||||||
const newCharacterLines = ['- New Character'];
|
|
||||||
|
|
||||||
// Add custom detail fields as standalone lines
|
|
||||||
for (const customField of enabledFields) {
|
|
||||||
newCharacterLines.push(` ${customField.name}: `);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add Relationship field if enabled
|
|
||||||
if (hasRelationship) {
|
|
||||||
newCharacterLines.push(` Relationship: Neutral`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add Stats if enabled
|
|
||||||
if (enabledCharStats.length > 0) {
|
|
||||||
const statsParts = enabledCharStats.map(s => `${s.name}: 100%`);
|
|
||||||
newCharacterLines.push(` Stats: ${statsParts.join(' | ')}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the last character and add after it, or after divider if no characters
|
|
||||||
let insertIndex = dividerIndex + 1;
|
|
||||||
for (let i = lines.length - 1; i > dividerIndex; i--) {
|
|
||||||
if (lines[i].trim().startsWith('- ')) {
|
|
||||||
// Find the end of this character block
|
|
||||||
insertIndex = i + 1;
|
|
||||||
while (insertIndex < lines.length && lines[insertIndex].trim() && !lines[insertIndex].trim().startsWith('- ')) {
|
|
||||||
insertIndex++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lines.splice(insertIndex, 0, ...newCharacterLines);
|
|
||||||
lastGeneratedData.characterThoughts = lines.join('\n');
|
|
||||||
committedTrackerData.characterThoughts = lines.join('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update message swipe data
|
|
||||||
const chat = getContext().chat;
|
|
||||||
if (chat && chat.length > 0) {
|
|
||||||
for (let i = chat.length - 1; i >= 0; i--) {
|
|
||||||
const message = chat[i];
|
|
||||||
if (!message.is_user) {
|
|
||||||
if (message.extra && message.extra.rpg_companion_swipes) {
|
|
||||||
const swipeId = message.swipe_id || 0;
|
|
||||||
if (message.extra.rpg_companion_swipes[swipeId]) {
|
|
||||||
message.extra.rpg_companion_swipes[swipeId].characterThoughts = lastGeneratedData.characterThoughts;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
saveChatData();
|
|
||||||
|
|
||||||
// Re-render to show new character
|
|
||||||
renderThoughts();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a specific character field in Present Characters data and re-renders.
|
* Updates a specific character field in Present Characters data and re-renders.
|
||||||
* Works with the new multi-line format.
|
* Works with the new multi-line format.
|
||||||
@@ -1023,27 +855,18 @@ export function updateCharacterField(characterName, field, value) {
|
|||||||
} else if (field === 'emoji') {
|
} else if (field === 'emoji') {
|
||||||
char.emoji = value;
|
char.emoji = value;
|
||||||
} else if (field === 'Relationship') {
|
} else if (field === 'Relationship') {
|
||||||
// Store relationship in the correct nested format
|
// Store relationship as text, converting emoji if needed
|
||||||
// Remove old flat format if it exists
|
|
||||||
if (char.Relationship) {
|
|
||||||
delete char.Relationship;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First check if it's an emoji → convert to text
|
// First check if it's an emoji → convert to text
|
||||||
let relationshipValue;
|
|
||||||
if (emojiToRelationship[value]) {
|
if (emojiToRelationship[value]) {
|
||||||
relationshipValue = emojiToRelationship[value];
|
char.Relationship = emojiToRelationship[value];
|
||||||
} else {
|
} else {
|
||||||
// It's text - find matching relationship name (case-insensitive)
|
// It's text - find matching relationship name (case-insensitive)
|
||||||
const matchingRelationship = Object.keys(relationshipEmojis).find(
|
const matchingRelationship = Object.keys(relationshipEmojis).find(
|
||||||
name => name.toLowerCase() === value.toLowerCase()
|
name => name.toLowerCase() === value.toLowerCase()
|
||||||
);
|
);
|
||||||
relationshipValue = matchingRelationship || value;
|
char.Relationship = matchingRelationship || value;
|
||||||
}
|
}
|
||||||
|
// console.log('[RPG Companion] After update - char.Relationship:', char.Relationship);
|
||||||
// Store in the correct nested format
|
|
||||||
char.relationship = { status: relationshipValue };
|
|
||||||
// console.log('[RPG Companion] After update - char.relationship:', char.relationship);
|
|
||||||
// console.log('[RPG Companion] relationshipEmojis:', relationshipEmojis);
|
// console.log('[RPG Companion] relationshipEmojis:', relationshipEmojis);
|
||||||
// console.log('[RPG Companion] emojiToRelationship:', emojiToRelationship);
|
// console.log('[RPG Companion] emojiToRelationship:', emojiToRelationship);
|
||||||
} else if (field.toLowerCase() === 'thoughts' || field === (presentCharsConfig?.thoughts?.name || 'Thoughts')) {
|
} else if (field.toLowerCase() === 'thoughts' || field === (presentCharsConfig?.thoughts?.name || 'Thoughts')) {
|
||||||
@@ -1059,44 +882,15 @@ export function updateCharacterField(characterName, field, value) {
|
|||||||
numValue = Math.max(0, Math.min(100, numValue));
|
numValue = Math.max(0, Math.min(100, numValue));
|
||||||
char.stats[field] = numValue;
|
char.stats[field] = numValue;
|
||||||
} else {
|
} else {
|
||||||
// It's a custom detail field - store in details object
|
// It's a custom detail field
|
||||||
if (!char.details) char.details = {};
|
if (!char.details) char.details = {};
|
||||||
char.details[field] = value;
|
char.details[field] = value;
|
||||||
|
|
||||||
// Clean up snake_case version if it exists (from AI generation)
|
|
||||||
const fieldKey = toSnakeCase(field);
|
|
||||||
if (fieldKey !== field && char.details[fieldKey] !== undefined) {
|
|
||||||
delete char.details[fieldKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up old root-level field if it exists (from v2 format)
|
|
||||||
if (char[field] !== undefined && field !== 'name' && field !== 'emoji') {
|
|
||||||
delete char[field];
|
|
||||||
}
|
|
||||||
if (char[fieldKey] !== undefined && fieldKey !== 'name' && fieldKey !== 'emoji') {
|
|
||||||
delete char[fieldKey];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up ALL duplicate snake_case fields in details (not just the edited field)
|
// Save back to lastGeneratedData
|
||||||
// This prevents duplicates from AI-generated data
|
lastGeneratedData.characterThoughts = Array.isArray(parsedData) ? charactersArray : { ...parsedData, characters: charactersArray };
|
||||||
if (char.details) {
|
|
||||||
for (const customField of enabledFields) {
|
|
||||||
const fieldName = customField.name;
|
|
||||||
const snakeCaseKey = toSnakeCase(fieldName);
|
|
||||||
// If both versions exist, keep the properly-cased one and remove snake_case
|
|
||||||
if (snakeCaseKey !== fieldName &&
|
|
||||||
char.details[fieldName] !== undefined &&
|
|
||||||
char.details[snakeCaseKey] !== undefined) {
|
|
||||||
delete char.details[snakeCaseKey];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save back to lastGeneratedData as JSON string (consistent with infoBox and userStats)
|
|
||||||
lastGeneratedData.characterThoughts = JSON.stringify(Array.isArray(parsedData) ? charactersArray : { ...parsedData, characters: charactersArray }, null, 2);
|
|
||||||
committedTrackerData.characterThoughts = lastGeneratedData.characterThoughts;
|
committedTrackerData.characterThoughts = lastGeneratedData.characterThoughts;
|
||||||
|
|
||||||
// console.log('[RPG Companion] Saved to lastGeneratedData.characterThoughts:', JSON.stringify(lastGeneratedData.characterThoughts));
|
// console.log('[RPG Companion] Saved to lastGeneratedData.characterThoughts:', JSON.stringify(lastGeneratedData.characterThoughts));
|
||||||
@@ -1177,9 +971,6 @@ export function updateCharacterField(characterName, field, value) {
|
|||||||
const thoughtsFieldName = presentCharsConfig?.thoughts?.name || 'Thoughts';
|
const thoughtsFieldName = presentCharsConfig?.thoughts?.name || 'Thoughts';
|
||||||
const isThoughtsField = field.toLowerCase() === 'thoughts' || field === thoughtsFieldName;
|
const isThoughtsField = field.toLowerCase() === 'thoughts' || field === thoughtsFieldName;
|
||||||
|
|
||||||
// Track if field was found and updated
|
|
||||||
let fieldUpdated = false;
|
|
||||||
|
|
||||||
// First pass: check if Stats line exists and update other fields
|
// First pass: check if Stats line exists and update other fields
|
||||||
for (let i = characterStartIndex; i < characterEndIndex; i++) {
|
for (let i = characterStartIndex; i < characterEndIndex; i++) {
|
||||||
const line = lines[i].trim();
|
const line = lines[i].trim();
|
||||||
@@ -1187,37 +978,35 @@ export function updateCharacterField(characterName, field, value) {
|
|||||||
if (line.startsWith('Stats:')) {
|
if (line.startsWith('Stats:')) {
|
||||||
statsLineExists = true;
|
statsLineExists = true;
|
||||||
statsLineIndex = i;
|
statsLineIndex = i;
|
||||||
continue; // Skip to next line
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for name update
|
|
||||||
if (field === 'name' && line.startsWith('- ')) {
|
if (field === 'name' && line.startsWith('- ')) {
|
||||||
lines[i] = `- ${value}`;
|
lines[i] = `- ${value}`;
|
||||||
fieldUpdated = true;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
else if (field === 'emoji' && line.startsWith('Details:')) {
|
||||||
// Check for Relationship field
|
const parts = line.substring(line.indexOf(':') + 1).split('|').map(p => p.trim());
|
||||||
if (field === 'Relationship' && line.startsWith('Relationship:')) {
|
parts[0] = value;
|
||||||
|
lines[i] = `Details: ${parts.join(' | ')}`;
|
||||||
|
}
|
||||||
|
else if (line.startsWith('Details:')) {
|
||||||
|
const fieldIndex = enabledFields.findIndex(f => f.name === field);
|
||||||
|
if (fieldIndex !== -1) {
|
||||||
|
const parts = line.substring(line.indexOf(':') + 1).split('|').map(p => p.trim());
|
||||||
|
if (parts.length > fieldIndex + 1) {
|
||||||
|
parts[fieldIndex + 1] = value;
|
||||||
|
lines[i] = `Details: ${parts.join(' | ')}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (field === 'Relationship' && line.startsWith('Relationship:')) {
|
||||||
const emojiToRelationship = { '⚔️': 'Enemy', '⚖️': 'Neutral', '⭐': 'Friend', '❤️': 'Lover' };
|
const emojiToRelationship = { '⚔️': 'Enemy', '⚖️': 'Neutral', '⭐': 'Friend', '❤️': 'Lover' };
|
||||||
const relationshipValue = emojiToRelationship[value] || value;
|
const relationshipValue = emojiToRelationship[value] || value;
|
||||||
lines[i] = `Relationship: ${relationshipValue}`;
|
lines[i] = `Relationship: ${relationshipValue}`;
|
||||||
fieldUpdated = true;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
else if (isThoughtsField && line.startsWith(thoughtsFieldName + ':')) {
|
||||||
// Check for Thoughts field
|
// Update thoughts field
|
||||||
if (isThoughtsField && line.startsWith(thoughtsFieldName + ':')) {
|
lines[i] = `${thoughtsFieldName}: ${value}`;
|
||||||
lines[i] = ` ${thoughtsFieldName}: ${value}`;
|
// console.log('[RPG Companion] Updated thoughts:', lines[i]);
|
||||||
fieldUpdated = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for v3 text format standalone field lines (e.g., "Appearance: ...", "Demeanor: ...")
|
|
||||||
if (line.startsWith(field + ':')) {
|
|
||||||
lines[i] = ` ${field}: ${value}`;
|
|
||||||
fieldUpdated = true;
|
|
||||||
// Don't break - update ALL instances of this field (in case of duplicates from previous bugs)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1284,28 +1073,23 @@ export function updateCharacterField(characterName, field, value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Create new character block (v3 text format only)
|
// Create new character block
|
||||||
const dividerIndex = lines.findIndex(line => line.includes('---'));
|
const dividerIndex = lines.findIndex(line => line.includes('---'));
|
||||||
if (dividerIndex >= 0) {
|
if (dividerIndex >= 0) {
|
||||||
const newCharacterLines = [`- ${characterName}`];
|
const newCharacterLines = [`- ${characterName}`];
|
||||||
|
|
||||||
// Add custom detail fields as standalone lines
|
let detailsParts = [field === 'emoji' ? value : '😊'];
|
||||||
for (const customField of enabledFields) {
|
for (let i = 0; i < enabledFields.length; i++) {
|
||||||
if (field === customField.name) {
|
detailsParts.push(field === enabledFields[i].name ? value : '');
|
||||||
newCharacterLines.push(` ${customField.name}: ${value}`);
|
|
||||||
} else {
|
|
||||||
newCharacterLines.push(` ${customField.name}: `);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
newCharacterLines.push(`Details: ${detailsParts.join(' | ')}`);
|
||||||
|
|
||||||
// Add Relationship field if enabled
|
|
||||||
if (presentCharsConfig?.relationshipFields?.length > 0) {
|
if (presentCharsConfig?.relationshipFields?.length > 0) {
|
||||||
const emojiToRelationship = { '⚔️': 'Enemy', '⚖️': 'Neutral', '⭐': 'Friend', '❤️': 'Lover' };
|
const emojiToRelationship = { '⚔️': 'Enemy', '⚖️': 'Neutral', '⭐': 'Friend', '❤️': 'Lover' };
|
||||||
const relationshipValue = field === 'Relationship' ? (emojiToRelationship[value] || value) : 'Neutral';
|
const relationshipValue = field === 'Relationship' ? (emojiToRelationship[value] || value) : 'Neutral';
|
||||||
newCharacterLines.push(` Relationship: ${relationshipValue}`);
|
newCharacterLines.push(`Relationship: ${relationshipValue}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Stats if enabled
|
|
||||||
if (enabledCharStats.length > 0) {
|
if (enabledCharStats.length > 0) {
|
||||||
const statsParts = enabledCharStats.map(s => {
|
const statsParts = enabledCharStats.map(s => {
|
||||||
if (field === s.name) {
|
if (field === s.name) {
|
||||||
@@ -1320,7 +1104,7 @@ export function updateCharacterField(characterName, field, value) {
|
|||||||
}
|
}
|
||||||
return `${s.name}: 0%`;
|
return `${s.name}: 0%`;
|
||||||
});
|
});
|
||||||
newCharacterLines.push(` Stats: ${statsParts.join(' | ')}`);
|
newCharacterLines.push(`Stats: ${statsParts.join(' | ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.splice(dividerIndex + 1, 0, ...newCharacterLines);
|
lines.splice(dividerIndex + 1, 0, ...newCharacterLines);
|
||||||
|
|||||||
@@ -105,48 +105,41 @@ function getCurrentTime() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patterns for specific weather conditions (order matters - combined effects first)
|
|
||||||
// Grouped by languages for easy editing
|
|
||||||
const WEATHER_PATTERNS_BY_LANGUAGE = {
|
|
||||||
en: [
|
|
||||||
{ id: "blizzard", patterns: [ "blizzard" ] }, // Snow + Wind
|
|
||||||
{ id: "storm", patterns: [ "storm", "thunder", "lightning" ] }, // Rain + Lightning
|
|
||||||
{ id: "wind", patterns: [ "wind", "breeze", "gust", "gale" ] },
|
|
||||||
{ id: "snow", patterns: [ "snow", "flurries" ] },
|
|
||||||
{ id: "rain", patterns: [ "rain", "drizzle", "shower" ] },
|
|
||||||
{ id: "mist", patterns: [ "mist", "fog", "haze" ] },
|
|
||||||
{ id: "sunny", patterns: [ "sunny", "clear", "bright" ] },
|
|
||||||
{ id: "none", patterns: [ "cloud", "overcast", "indoor", "inside" ] },
|
|
||||||
],
|
|
||||||
ru: [
|
|
||||||
{ id: "blizzard", patterns: [ "метель" ] },
|
|
||||||
{ id: "storm", patterns: [ "гроза", "буря", "шторм" ] },
|
|
||||||
{ id: "wind", patterns: [ "ветер", "ветрено", "ветерок", "бриз", "легкий бриз", "слегка ветрено", "легкий ветер", "шквал,буря" ] },
|
|
||||||
{ id: "snow", patterns: [ "снег", "снегопад" ] },
|
|
||||||
{ id: "rain", patterns: [ "дождь", "морось", "ливень" ] },
|
|
||||||
{ id: "mist", patterns: [ "мгла", "туман", "туманно" ] },
|
|
||||||
{ id: "sunny", patterns: [ "солнечно", "ясно", "ярко", "ясное утро", "ясный день" ] },
|
|
||||||
{ id: "none", patterns: [ "облачно", "пасмурно", "в помещении", "внутри" ] },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse weather text to determine effect type
|
* Parse weather text to determine effect type
|
||||||
*/
|
*/
|
||||||
function parseWeatherType(weatherText) {
|
function parseWeatherType(weatherText) {
|
||||||
if (!weatherText) return "none";
|
if (!weatherText) return 'none';
|
||||||
|
|
||||||
const text = weatherText.toLowerCase();
|
const text = weatherText.toLowerCase();
|
||||||
|
|
||||||
for (const language of Object.values(WEATHER_PATTERNS_BY_LANGUAGE)) {
|
// Check for specific weather conditions (order matters - check combined effects first)
|
||||||
for (const { id, patterns } of language) {
|
if (text.includes('blizzard')) {
|
||||||
if (patterns.some(p => text.includes(p))) {
|
return 'blizzard'; // Snow + Wind
|
||||||
return id;
|
|
||||||
}
|
}
|
||||||
|
if (text.includes('storm') || text.includes('thunder') || text.includes('lightning')) {
|
||||||
|
return 'storm'; // Rain + Lightning
|
||||||
}
|
}
|
||||||
|
if (text.includes('wind') || text.includes('breeze') || text.includes('gust') || text.includes('gale')) {
|
||||||
|
return 'wind';
|
||||||
|
}
|
||||||
|
if (text.includes('snow') || text.includes('flurries')) {
|
||||||
|
return 'snow';
|
||||||
|
}
|
||||||
|
if (text.includes('rain') || text.includes('drizzle') || text.includes('shower')) {
|
||||||
|
return 'rain';
|
||||||
|
}
|
||||||
|
if (text.includes('mist') || text.includes('fog') || text.includes('haze')) {
|
||||||
|
return 'mist';
|
||||||
|
}
|
||||||
|
if (text.includes('sunny') || text.includes('clear') || text.includes('bright')) {
|
||||||
|
return 'sunny';
|
||||||
|
}
|
||||||
|
if (text.includes('cloud') || text.includes('overcast') || text.includes('indoor') || text.includes('inside')) {
|
||||||
|
return 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
return "none";
|
return 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -244,9 +237,9 @@ function createMist() {
|
|||||||
* Returns { left: vw%, top: dvh% }
|
* Returns { left: vw%, top: dvh% }
|
||||||
*/
|
*/
|
||||||
function calculateSunPosition(hour) {
|
function calculateSunPosition(hour) {
|
||||||
// Daytime is roughly 5 AM to 8 PM (5-20)
|
// Daytime is roughly 6 AM to 8 PM (6-20)
|
||||||
// Map hour to position along an arc
|
// Map hour to position along an arc
|
||||||
// 5 AM = far left, low | 12 PM = center, high | 8 PM = far right, low
|
// 6 AM = far left, low | 12 PM = center, high | 6 PM = far right, low
|
||||||
|
|
||||||
if (hour === null) hour = 12; // Default to noon if unknown
|
if (hour === null) hour = 12; // Default to noon if unknown
|
||||||
|
|
||||||
@@ -256,14 +249,14 @@ function calculateSunPosition(hour) {
|
|||||||
// Normalize to 0-1 range (5 AM = 0, 20 PM = 1)
|
// Normalize to 0-1 range (5 AM = 0, 20 PM = 1)
|
||||||
const progress = (clampedHour - 5) / 15;
|
const progress = (clampedHour - 5) / 15;
|
||||||
|
|
||||||
// Horizontal position: 3% to 92% (left to right, wider range)
|
// Horizontal position: 5% to 85% (left to right)
|
||||||
const left = 3 + progress * 89;
|
const left = 5 + progress * 80;
|
||||||
|
|
||||||
// Vertical position: parabolic arc (high at noon, low at dawn/dusk)
|
// Vertical position: parabolic arc (high at noon, low at dawn/dusk)
|
||||||
// At progress 0.5 (noon), top should be ~8% (high)
|
// At progress 0.5 (noon), top should be ~8% (high)
|
||||||
// At progress 0 or 1, top should be ~40% (low, near horizon)
|
// At progress 0 or 1, top should be ~35% (low, near horizon)
|
||||||
const normalizedProgress = (progress - 0.5) * 2; // -1 to 1
|
const normalizedProgress = (progress - 0.5) * 2; // -1 to 1
|
||||||
const top = 8 + 32 * (normalizedProgress * normalizedProgress);
|
const top = 8 + 27 * (normalizedProgress * normalizedProgress);
|
||||||
|
|
||||||
return { left, top };
|
return { left, top };
|
||||||
}
|
}
|
||||||
@@ -334,134 +327,6 @@ function createSunshine(hour) {
|
|||||||
return container;
|
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
|
* Create clear nighttime weather effect with moon, stars, and fireflies
|
||||||
*/
|
*/
|
||||||
@@ -707,13 +572,9 @@ export function updateWeatherEffect() {
|
|||||||
weatherContainer = createMist();
|
weatherContainer = createMist();
|
||||||
break;
|
break;
|
||||||
case 'sunny':
|
case 'sunny':
|
||||||
// Use appropriate effect based on time of day
|
// Use nighttime effect for clear weather at night
|
||||||
if (timeOfDay === 'night') {
|
if (timeOfDay === 'night') {
|
||||||
weatherContainer = createNighttime(hour);
|
weatherContainer = createNighttime(hour);
|
||||||
} else if (timeOfDay === 'dawn') {
|
|
||||||
weatherContainer = createSunrise(hour);
|
|
||||||
} else if (timeOfDay === 'dusk') {
|
|
||||||
weatherContainer = createSunset(hour);
|
|
||||||
} else {
|
} else {
|
||||||
weatherContainer = createSunshine(hour);
|
weatherContainer = createSunshine(hour);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2329,40 +2329,6 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add Character Button (inside rpg-thoughts-content, after last character) */
|
|
||||||
.rpg-add-character-btn {
|
|
||||||
background: var(--rpg-accent);
|
|
||||||
border: 1px solid var(--rpg-border);
|
|
||||||
color: var(--rpg-text);
|
|
||||||
padding: 0;
|
|
||||||
margin: 0.5rem auto 0;
|
|
||||||
font-size: clamp(0.625rem, 0.6vw, 0.75rem);
|
|
||||||
font-weight: 500;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 3px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
opacity: 0.6;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-add-character-btn:hover {
|
|
||||||
background: var(--rpg-highlight);
|
|
||||||
border-color: var(--rpg-highlight);
|
|
||||||
color: var(--rpg-bg);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-add-character-btn:active {
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-add-character-btn i {
|
|
||||||
font-size: 0.875em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Character traits/status line and custom fields */
|
/* Character traits/status line and custom fields */
|
||||||
.rpg-character-traits,
|
.rpg-character-traits,
|
||||||
.rpg-character-field {
|
.rpg-character-field {
|
||||||
@@ -2374,20 +2340,12 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Empty field placeholder using data-placeholder attribute */
|
/* Placeholder for empty editable character fields */
|
||||||
.rpg-editable.rpg-empty-field:empty::before {
|
.rpg-character-field.rpg-editable:empty::before {
|
||||||
content: attr(data-placeholder);
|
content: 'Click to edit...';
|
||||||
color: var(--rpg-highlight);
|
color: var(--rpg-highlight);
|
||||||
opacity: 0.4;
|
opacity: 0.5;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure empty fields have minimum height for clickability */
|
|
||||||
.rpg-editable.rpg-empty-field {
|
|
||||||
min-height: 1.2em;
|
|
||||||
display: inline-block;
|
|
||||||
min-width: 3em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Character stats display */
|
/* Character stats display */
|
||||||
@@ -9940,200 +9898,6 @@ 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 */
|
/* Lens flare effect */
|
||||||
.rpg-clear-lens-flare {
|
.rpg-clear-lens-flare {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -10508,12 +10272,6 @@ body[data-theme="cyberpunk"] .rpg-music-widget-play {
|
|||||||
.rpg-night-shooting-star {
|
.rpg-night-shooting-star {
|
||||||
animation-duration: 18s;
|
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 */
|
/* Foreground mode - reduced opacity for celestial bodies to not obstruct content */
|
||||||
@@ -10561,32 +10319,6 @@ body[data-theme="cyberpunk"] .rpg-music-widget-play {
|
|||||||
opacity: 0.6;
|
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 */
|
/* Lightning flash effect */
|
||||||
.rpg-lightning {
|
.rpg-lightning {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
Reference in New Issue
Block a user