Fix parser to support both text and emoji formats for Info Box and Present Characters trackers
This commit is contained in:
@@ -53,6 +53,7 @@ export let extensionSettings = {
|
||||
assets: "None"
|
||||
}
|
||||
},
|
||||
level: 1, // User's character level
|
||||
classicStats: {
|
||||
str: 10,
|
||||
dex: 10,
|
||||
|
||||
@@ -75,20 +75,26 @@ export function parseUserStats(statsText) {
|
||||
const hygieneMatch = statsText.match(/Hygiene:\s*(\d+)%/);
|
||||
const arousalMatch = statsText.match(/Arousal:\s*(\d+)%/);
|
||||
|
||||
// Match new format: [Emoji]: [Conditions]
|
||||
// Look for a line after Arousal that has format [something]: [text]
|
||||
// Split by lines and find the line after percentages
|
||||
const lines = statsText.split('\n');
|
||||
// Match new format: Status: [Emoji, Conditions]
|
||||
// Also support legacy format: [Emoji]: [Conditions] for backward compatibility
|
||||
let moodMatch = null;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
// Skip lines with percentages or "Inventory:"
|
||||
if (line.includes('%') || line.toLowerCase().startsWith('inventory:')) continue;
|
||||
// Match emoji followed by colon and conditions
|
||||
const match = line.match(/^(.+?):\s*(.+)$/);
|
||||
if (match) {
|
||||
moodMatch = match;
|
||||
break;
|
||||
const statusMatch = statsText.match(/Status:\s*(.+?),\s*(.+)/i);
|
||||
if (statusMatch) {
|
||||
// New format: Status: [Emoji, Conditions]
|
||||
moodMatch = [null, statusMatch[1].trim(), statusMatch[2].trim()];
|
||||
} else {
|
||||
// Legacy format: [Emoji]: [Conditions]
|
||||
const lines = statsText.split('\n');
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
// Skip lines with percentages or "Inventory:" or "Status:"
|
||||
if (line.includes('%') || line.toLowerCase().startsWith('inventory:') || line.toLowerCase().startsWith('status:')) continue;
|
||||
// Match emoji followed by colon and conditions
|
||||
const match = line.match(/^(.+?):\s*(.+)$/);
|
||||
if (match) {
|
||||
moodMatch = match;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon
|
||||
// Only add tracker instructions if at least one tracker is enabled
|
||||
if (hasAnyTrackers) {
|
||||
// Universal instruction header
|
||||
instructions += `\nAt the start of every reply, you must attach update to the trackers in EXACTLY the same format as below, enclosed in separate Markdown code fences. Replace X with proper numbers and [placeholders] with in-world details ${userName} perceives about the current scene and the present characters. Consider the last trackers in the conversation (if they exist). Manage them accordingly and realistically; raise, lower, change, or keep the values unchanged based on the user's actions, the passage of time, and logical consequences:\n`;
|
||||
instructions += `\nAt the start of every reply, you must attach an update to the trackers in EXACTLY the same format as below, enclosed in separate Markdown code fences. Replace X with actual numbers (e.g., 69) and replace all [placeholders] with concrete in-world details that ${userName} perceives about the current scene and the present characters. Do NOT keep the brackets or placeholder text in your response. For example: [Location] becomes Forest Clearing, [Mood Emoji] becomes 😊. Consider the last trackers in the conversation (if they exist). Manage them accordingly and realistically; raise, lower, change, or keep the values unchanged based on the user's actions, the passage of time, and logical consequences:\n`;
|
||||
|
||||
// Add format specifications for each enabled tracker
|
||||
if (extensionSettings.showUserStats) {
|
||||
@@ -112,7 +112,7 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon
|
||||
instructions += '- Energy: X%\n';
|
||||
instructions += '- Hygiene: X%\n';
|
||||
instructions += '- Arousal: X%\n';
|
||||
instructions += '[Mood Emoji]: [Conditions (up to three traits)]\n';
|
||||
instructions += 'Status: [Mood Emoji, Conditions (up to three traits)]\n';
|
||||
|
||||
// Add inventory format based on feature flag
|
||||
if (FEATURE_FLAGS.useNewInventory) {
|
||||
@@ -132,11 +132,11 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon
|
||||
instructions += '```\n';
|
||||
instructions += 'Info Box\n';
|
||||
instructions += '---\n';
|
||||
instructions += '🗓️: [Weekday, Month, Year]\n';
|
||||
instructions += '[Weather Emoji]: [Forecast]\n';
|
||||
instructions += '🌡️: [Temperature in °C]\n';
|
||||
instructions += '🕒: [Time Start → Time End]\n';
|
||||
instructions += '🗺️: [Location]\n';
|
||||
instructions += 'Date: [Weekday, Month, Year]\n';
|
||||
instructions += 'Weather: [Weather Emoji, Forecast]\n';
|
||||
instructions += 'Temperature: [Temperature in °C]\n';
|
||||
instructions += 'Time: [Time Start → Time End]\n';
|
||||
instructions += 'Location: [Location]\n';
|
||||
instructions += '```\n\n';
|
||||
}
|
||||
|
||||
@@ -150,13 +150,13 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon
|
||||
|
||||
// Only add continuation instruction if includeContinuation is true
|
||||
if (includeContinuation) {
|
||||
instructions += `After updating the trackers, continue directly from where the last message in the chat history left off. Ensure the trackers you provide naturally reflect and influence the narrative. Character behavior, dialogue, and story events should acknowledge these conditions when relevant, such as fatigue affecting the protagonist's performance, low hygiene influencing their social interactions, environmental factors shaping the scene, a character's emotional state coloring their responses, and so on. Do not render brackets.\n\n`;
|
||||
instructions += `After updating the trackers, continue directly from where the last message in the chat history left off. Ensure the trackers you provide naturally reflect and influence the narrative. Character behavior, dialogue, and story events should acknowledge these conditions when relevant, such as fatigue affecting the protagonist's performance, low hygiene influencing their social interactions, environmental factors shaping the scene, a character's emotional state coloring their responses, and so on. Remember, all bracketed placeholders (e.g., [Location], [Mood Emoji]) MUST be replaced with actual content without the square brackets.\n\n`;
|
||||
}
|
||||
|
||||
// Include attributes and dice roll only if there was a dice roll
|
||||
if (extensionSettings.lastDiceRoll) {
|
||||
const roll = extensionSettings.lastDiceRoll;
|
||||
instructions += `${userName}'s attributes: STR ${classicStats.str}, DEX ${classicStats.dex}, CON ${classicStats.con}, INT ${classicStats.int}, WIS ${classicStats.wis}, CHA ${classicStats.cha}\n`;
|
||||
instructions += `${userName}'s attributes: STR ${classicStats.str}, DEX ${classicStats.dex}, CON ${classicStats.con}, INT ${classicStats.int}, WIS ${classicStats.wis}, CHA ${classicStats.cha}, LVL ${extensionSettings.level}\n`;
|
||||
instructions += `${userName} rolled ${roll.total} on the last ${roll.formula} roll. Based on their attributes, decide whether they succeeded or failed the action they attempted.\n\n`;
|
||||
}
|
||||
}
|
||||
@@ -215,14 +215,14 @@ export function generateContextualSummary() {
|
||||
if (extensionSettings.lastDiceRoll) {
|
||||
const classicStats = extensionSettings.classicStats;
|
||||
const roll = extensionSettings.lastDiceRoll;
|
||||
summary += `Attributes: STR ${classicStats.str}, DEX ${classicStats.dex}, CON ${classicStats.con}, INT ${classicStats.int}, WIS ${classicStats.wis}, CHA ${classicStats.cha}\n`;
|
||||
summary += `Attributes: STR ${classicStats.str}, DEX ${classicStats.dex}, CON ${classicStats.con}, INT ${classicStats.int}, WIS ${classicStats.wis}, CHA ${classicStats.cha}, LVL ${extensionSettings.level}\n`;
|
||||
summary += `${userName} rolled ${roll.total} on the last ${roll.formula} roll. Based on their attributes, decide whether they succeed or fail the action they attempt.\n`;
|
||||
}
|
||||
summary += `\n`;
|
||||
}
|
||||
|
||||
if (extensionSettings.showInfoBox && committedTrackerData.infoBox) {
|
||||
// Parse info box data
|
||||
// Parse info box data - support both new and legacy formats
|
||||
const lines = committedTrackerData.infoBox.split('\n');
|
||||
let date = '', weather = '', temp = '', time = '', location = '';
|
||||
|
||||
@@ -230,30 +230,35 @@ export function generateContextualSummary() {
|
||||
|
||||
for (const line of lines) {
|
||||
// console.log('[RPG Companion] 🔍 Processing line:', line);
|
||||
// Use separate if statements (not else if) so each line is checked against all conditions
|
||||
if (line.includes('🗓️:')) {
|
||||
|
||||
// New format with text labels
|
||||
if (line.startsWith('Date:')) {
|
||||
date = line.replace('Date:', '').trim();
|
||||
} else if (line.startsWith('Weather:')) {
|
||||
weather = line.replace('Weather:', '').trim();
|
||||
} else if (line.startsWith('Temperature:')) {
|
||||
temp = line.replace('Temperature:', '').trim();
|
||||
} else if (line.startsWith('Time:')) {
|
||||
time = line.replace('Time:', '').trim();
|
||||
} else if (line.startsWith('Location:')) {
|
||||
location = line.replace('Location:', '').trim();
|
||||
}
|
||||
// Legacy format with emojis (for backward compatibility)
|
||||
else if (line.includes('🗓️:')) {
|
||||
date = line.replace('🗓️:', '').trim();
|
||||
// console.log('[RPG Companion] 📅 Found date:', date);
|
||||
}
|
||||
if (line.includes('🌡️:')) {
|
||||
} else if (line.includes('🌡️:')) {
|
||||
temp = line.replace('🌡️:', '').trim();
|
||||
// console.log('[RPG Companion] 🌡️ Found temp:', temp);
|
||||
}
|
||||
if (line.includes('🕒:')) {
|
||||
} else if (line.includes('🕒:')) {
|
||||
time = line.replace('🕒:', '').trim();
|
||||
// console.log('[RPG Companion] 🕒 Found time:', time);
|
||||
}
|
||||
if (line.includes('🗺️:')) {
|
||||
} else if (line.includes('🗺️:')) {
|
||||
location = line.replace('🗺️:', '').trim();
|
||||
// console.log('[RPG Companion] 🗺️ Found location:', location);
|
||||
}
|
||||
// Check for weather emojis - use a simpler approach
|
||||
const weatherEmojis = ['🌤️', '☀️', '⛅', '🌦️', '🌧️', '⛈️', '🌩️', '🌨️', '❄️', '🌫️'];
|
||||
const startsWithWeatherEmoji = weatherEmojis.some(emoji => line.startsWith(emoji + ':'));
|
||||
if (startsWithWeatherEmoji && !line.includes('🌡️') && !line.includes('🗺️')) {
|
||||
// Extract weather description (remove emoji and colon)
|
||||
weather = line.substring(line.indexOf(':') + 1).trim();
|
||||
// console.log('[RPG Companion] 🌧️ Found weather:', weather);
|
||||
} else {
|
||||
// Check for weather emojis in legacy format
|
||||
const weatherEmojis = ['🌤️', '☀️', '⛅', '🌦️', '🌧️', '⛈️', '🌩️', '🌨️', '❄️', '🌫️'];
|
||||
const startsWithWeatherEmoji = weatherEmojis.some(emoji => line.startsWith(emoji + ':'));
|
||||
if (startsWithWeatherEmoji && !line.includes('🌡️') && !line.includes('🗺️')) {
|
||||
weather = line.substring(line.indexOf(':') + 1).trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,7 +378,7 @@ export function generateSeparateUpdatePrompt() {
|
||||
// Build the instruction message
|
||||
let instructionMessage = `</history>\n\n`;
|
||||
instructionMessage += generateRPGPromptText().replace('start your response with', 'respond with');
|
||||
instructionMessage += `Provide ONLY the requested data in the exact formats specified above. Do not include any roleplay response, other text, or commentary. Do not render brackets.`;
|
||||
instructionMessage += `Provide ONLY the requested data in the exact formats specified above. Do not include any roleplay response, other text, or commentary. Remember, all bracketed placeholders (e.g., [Location], [Mood Emoji]) MUST be replaced with actual content without the square brackets.`;
|
||||
|
||||
messages.push({
|
||||
role: 'user',
|
||||
|
||||
@@ -66,35 +66,42 @@ export function renderInfoBox() {
|
||||
for (const line of lines) {
|
||||
// console.log('[RPG Companion] Processing line:', line);
|
||||
|
||||
if (line.includes('🗓️:')) {
|
||||
// Support both new text format (Date:) and legacy emoji format (🗓️:)
|
||||
if (line.startsWith('Date:') || line.includes('🗓️:')) {
|
||||
// console.log('[RPG Companion] → Matched DATE');
|
||||
const dateStr = line.replace('🗓️:', '').trim();
|
||||
const dateStr = line.replace('Date:', '').replace('🗓️:', '').trim();
|
||||
// Parse format: "Weekday, Month Day, Year" or "Weekday, Month, Year"
|
||||
const dateParts = dateStr.split(',').map(p => p.trim());
|
||||
data.weekday = dateParts[0] || '';
|
||||
data.month = dateParts[1] || '';
|
||||
data.year = dateParts[2] || '';
|
||||
data.date = dateStr;
|
||||
} else if (line.includes('🌡️:')) {
|
||||
} else if (line.startsWith('Temperature:') || line.includes('🌡️:')) {
|
||||
// console.log('[RPG Companion] → Matched TEMPERATURE');
|
||||
const tempStr = line.replace('🌡️:', '').trim();
|
||||
const tempStr = line.replace('Temperature:', '').replace('🌡️:', '').trim();
|
||||
data.temperature = tempStr;
|
||||
// Extract numeric value
|
||||
const tempMatch = tempStr.match(/(-?\d+)/);
|
||||
if (tempMatch) {
|
||||
data.tempValue = parseInt(tempMatch[1]);
|
||||
}
|
||||
} else if (line.includes('🕒:')) {
|
||||
} else if (line.startsWith('Time:') || line.includes('🕒:')) {
|
||||
// console.log('[RPG Companion] → Matched TIME');
|
||||
const timeStr = line.replace('🕒:', '').trim();
|
||||
const timeStr = line.replace('Time:', '').replace('🕒:', '').trim();
|
||||
data.time = timeStr;
|
||||
// Parse "HH:MM → HH:MM" format
|
||||
const timeParts = timeStr.split('→').map(t => t.trim());
|
||||
data.timeStart = timeParts[0] || '';
|
||||
data.timeEnd = timeParts[1] || '';
|
||||
} else if (line.includes('🗺️:')) {
|
||||
} else if (line.startsWith('Location:') || line.includes('🗺️:')) {
|
||||
// console.log('[RPG Companion] → Matched LOCATION');
|
||||
data.location = line.replace('🗺️:', '').trim();
|
||||
data.location = line.replace('Location:', '').replace('🗺️:', '').trim();
|
||||
} else if (line.startsWith('Weather:')) {
|
||||
// New text format: Weather: [Emoji], [Forecast]
|
||||
const weatherStr = line.replace('Weather:', '').trim();
|
||||
const weatherParts = weatherStr.split(',').map(p => p.trim());
|
||||
data.weatherEmoji = weatherParts[0] || '';
|
||||
data.weatherForecast = weatherParts[1] || '';
|
||||
} else {
|
||||
// Check if it's a weather line
|
||||
// Since \p{Emoji} doesn't work reliably, use a simpler approach
|
||||
|
||||
@@ -71,6 +71,7 @@ export function renderThoughts() {
|
||||
// console.log('[RPG Companion] Split into lines:', lines);
|
||||
|
||||
// Parse format: [Emoji]: [Name, Status, Demeanor] | [Relationship] | [Thoughts]
|
||||
// Also supports 4-part format: [Emoji]: [Name, Status] | [Demeanor] | [Relationship] | [Thoughts]
|
||||
for (const line of lines) {
|
||||
// Skip empty lines, headers, dividers, and code fences
|
||||
if (line.trim() &&
|
||||
@@ -89,17 +90,40 @@ export function renderThoughts() {
|
||||
if (emojiMatch) {
|
||||
const emoji = emojiMatch[1].trim();
|
||||
const info = emojiMatch[2].trim();
|
||||
const relationship = parts[1].trim(); // Enemy/Neutral/Friend/Lover
|
||||
const thoughts = parts[2] ? parts[2].trim() : '';
|
||||
|
||||
// Handle both 3-part and 4-part formats
|
||||
let relationship, thoughts, traits;
|
||||
|
||||
if (parts.length === 3) {
|
||||
// 3-part format: Emoji:Name,traits | Relationship | Thoughts
|
||||
relationship = parts[1].trim();
|
||||
thoughts = parts[2].trim();
|
||||
const infoParts = info.split(',').map(p => p.trim());
|
||||
traits = infoParts.slice(1).join(', ');
|
||||
} else if (parts.length >= 4) {
|
||||
// 4-part format: Emoji:Name,traits | Demeanor | Relationship | Thoughts
|
||||
// Add the demeanor to traits and use last two parts for relationship/thoughts
|
||||
const demeanor = parts[1].trim();
|
||||
relationship = parts[2].trim();
|
||||
thoughts = parts[3].trim();
|
||||
const infoParts = info.split(',').map(p => p.trim());
|
||||
const baseTraits = infoParts.slice(1).join(', ');
|
||||
traits = baseTraits ? `${baseTraits}, ${demeanor}` : demeanor;
|
||||
} else {
|
||||
// Fallback for 2-part format
|
||||
relationship = parts[1].trim();
|
||||
thoughts = '';
|
||||
const infoParts = info.split(',').map(p => p.trim());
|
||||
traits = infoParts.slice(1).join(', ');
|
||||
}
|
||||
|
||||
// Parse name from info (first part before comma)
|
||||
const infoParts = info.split(',').map(p => p.trim());
|
||||
const name = infoParts[0] || '';
|
||||
const traits = infoParts.slice(1).join(', ');
|
||||
|
||||
if (name && name.toLowerCase() !== 'unavailable') {
|
||||
presentCharacters.push({ emoji, name, traits, relationship, thoughts });
|
||||
// console.log('[RPG Companion] Parsed character:', { name, relationship });
|
||||
// console.log('[RPG Companion] Parsed character:', { name, relationship, thoughts });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -429,6 +453,7 @@ export function updateChatThoughts() {
|
||||
const parts = line.split('|').map(p => p.trim());
|
||||
// console.log('[RPG Companion] Line parts:', parts);
|
||||
|
||||
// Handle both 3-part and 4-part formats
|
||||
if (parts.length >= 3) {
|
||||
const firstPart = parts[0].trim();
|
||||
const emojiMatch = firstPart.match(/^(.+?):\s*(.+)$/);
|
||||
@@ -436,7 +461,15 @@ export function updateChatThoughts() {
|
||||
if (emojiMatch) {
|
||||
const emoji = emojiMatch[1].trim();
|
||||
const info = emojiMatch[2].trim();
|
||||
const thoughts = parts[2] ? parts[2].trim() : '';
|
||||
|
||||
let thoughts;
|
||||
if (parts.length === 3) {
|
||||
// 3-part format: Emoji:Name,traits | Relationship | Thoughts
|
||||
thoughts = parts[2].trim();
|
||||
} else if (parts.length >= 4) {
|
||||
// 4-part format: Emoji:Name,traits | Demeanor | Relationship | Thoughts
|
||||
thoughts = parts[3].trim();
|
||||
}
|
||||
|
||||
const infoParts = info.split(',').map(p => p.trim());
|
||||
const name = infoParts[0] || '';
|
||||
|
||||
@@ -55,8 +55,12 @@ export function renderUserStats() {
|
||||
const html = `
|
||||
<div class="rpg-stats-content">
|
||||
<div class="rpg-stats-left">
|
||||
<div style="display: flex; gap: clamp(4px, 0.8vh, 8px); align-items: center; justify-content: center; flex-shrink: 0;">
|
||||
<div class="rpg-user-info-row">
|
||||
<img src="${userPortrait}" alt="${userName}" class="rpg-user-portrait" onerror="this.style.opacity='0.5';this.onerror=null;" />
|
||||
<span class="rpg-user-name">${userName}</span>
|
||||
<span style="opacity: 0.5;">|</span>
|
||||
<span class="rpg-level-label">LVL</span>
|
||||
<span class="rpg-level-value rpg-editable" contenteditable="true" data-field="level" title="Click to edit level">${extensionSettings.level}</span>
|
||||
</div>
|
||||
<div class="rpg-stats-grid">
|
||||
<div class="rpg-stat-row">
|
||||
@@ -236,4 +240,30 @@ export function renderUserStats() {
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
});
|
||||
|
||||
// Add event listener for level editing
|
||||
$('.rpg-level-value.rpg-editable').on('blur', function() {
|
||||
let value = parseInt($(this).text().trim());
|
||||
if (isNaN(value) || value < 1) {
|
||||
value = 1;
|
||||
}
|
||||
// Set reasonable max level
|
||||
value = Math.min(100, value);
|
||||
|
||||
extensionSettings.level = value;
|
||||
saveSettings();
|
||||
saveChatData();
|
||||
updateMessageSwipeData();
|
||||
|
||||
// Re-render to update the display
|
||||
renderUserStats();
|
||||
});
|
||||
|
||||
// Prevent line breaks in level field
|
||||
$('.rpg-level-value.rpg-editable').on('keydown', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
$(this).blur();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -672,17 +672,97 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
||||
gap: clamp(4px, 0.8vh, 8px);
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Stats Left - Portrait, Inventory, Bars and mood */
|
||||
/* Stats Left - Portrait, Bars and mood */
|
||||
.rpg-stats-left {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: clamp(3px, 0.6vh, 6px);
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.rpg-stats-left::-webkit-scrollbar {
|
||||
width: 0.188rem;
|
||||
}
|
||||
|
||||
.rpg-stats-left::-webkit-scrollbar-track {
|
||||
background: var(--rpg-bg);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.rpg-stats-left::-webkit-scrollbar-thumb {
|
||||
background: var(--rpg-highlight);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.rpg-stats-left::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--rpg-text);
|
||||
}
|
||||
|
||||
/* User info row (portrait, name, separator, level) */
|
||||
.rpg-user-info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.375em;
|
||||
font-size: clamp(0.4vw, 0.5vw, 0.6vw);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* User portrait */
|
||||
.rpg-user-portrait {
|
||||
width: clamp(12px, 1.8vw, 16px);
|
||||
height: clamp(12px, 1.8vw, 16px);
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* User name and level - inline with portrait */
|
||||
.rpg-user-name {
|
||||
font-weight: 600;
|
||||
font-size: 1em;
|
||||
color: var(--rpg-text-color);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.rpg-level-label {
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
color: var(--rpg-text-color);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.rpg-level-value {
|
||||
font-size: 1em;
|
||||
font-weight: 700;
|
||||
color: var(--rpg-highlight-color);
|
||||
padding: clamp(1px, 0.2vh, 2px) 0.375em;
|
||||
background: var(--rpg-accent-color);
|
||||
border-radius: clamp(2px, 0.3vh, 3px);
|
||||
border: 1px solid var(--rpg-highlight-color);
|
||||
min-width: 1.5em;
|
||||
text-align: center;
|
||||
cursor: text;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.rpg-level-value:hover {
|
||||
background: var(--rpg-highlight-color);
|
||||
color: var(--rpg-bg-color);
|
||||
}
|
||||
|
||||
.rpg-level-value:focus {
|
||||
outline: 2px solid var(--rpg-highlight-color);
|
||||
outline-offset: 1px;
|
||||
background: var(--rpg-bg-color);
|
||||
}
|
||||
|
||||
/* Portrait and Inventory row at top of stats-left */
|
||||
@@ -711,11 +791,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: clamp(3px, 0.5vh, 6px);
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
justify-content: space-evenly;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.rpg-stats-grid::-webkit-scrollbar {
|
||||
@@ -736,11 +812,29 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
||||
background: var(--rpg-text);
|
||||
}
|
||||
|
||||
.rpg-classic-stats-grid::-webkit-scrollbar {
|
||||
width: 0.188rem;
|
||||
}
|
||||
|
||||
.rpg-classic-stats-grid::-webkit-scrollbar-track {
|
||||
background: var(--rpg-bg);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.rpg-classic-stats-grid::-webkit-scrollbar-thumb {
|
||||
background: var(--rpg-highlight);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.rpg-classic-stats-grid::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--rpg-text);
|
||||
}
|
||||
|
||||
.rpg-stat-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25em;
|
||||
flex: 0 0 auto;
|
||||
flex: 1 1 0;
|
||||
min-height: clamp(12px, 1.8vh, 16px);
|
||||
}
|
||||
|
||||
@@ -819,8 +913,6 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
||||
}
|
||||
|
||||
.rpg-mood {
|
||||
margin: 0 !important;
|
||||
margin-top: clamp(2px, 0.4vh, 4px) !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375em;
|
||||
@@ -852,6 +944,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.rpg-classic-stats-title {
|
||||
@@ -870,23 +963,23 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: clamp(2px, 0.4vh, 4px);
|
||||
flex: 1;
|
||||
align-content: stretch;
|
||||
grid-auto-rows: 1fr;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.rpg-classic-stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
justify-content: center;
|
||||
gap: clamp(1px, 0.15vh, 2px);
|
||||
padding: clamp(3px, 0.5vh, 5px);
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 0.25em;
|
||||
border: 1px solid var(--rpg-border);
|
||||
box-sizing: border-box;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.rpg-classic-stat-label {
|
||||
@@ -3595,10 +3688,29 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
||||
display: contents !important;
|
||||
}
|
||||
|
||||
/* Center the avatar wrapper on mobile */
|
||||
.rpg-stats-left > div:first-child {
|
||||
/* User info row on mobile */
|
||||
.rpg-user-info-row {
|
||||
grid-column: 1 / 3;
|
||||
grid-row: 1;
|
||||
justify-content: center !important;
|
||||
font-size: clamp(11px, 2.8vw, 14px) !important;
|
||||
gap: clamp(6px, 1.5vw, 10px) !important;
|
||||
}
|
||||
|
||||
/* Make user portrait larger on mobile */
|
||||
.rpg-user-portrait {
|
||||
width: clamp(24px, 6vw, 32px) !important;
|
||||
height: clamp(24px, 6vw, 32px) !important;
|
||||
}
|
||||
|
||||
/* Make user name and level more readable on mobile */
|
||||
.rpg-user-name {
|
||||
font-size: clamp(12px, 3vw, 16px) !important;
|
||||
}
|
||||
|
||||
.rpg-level-label,
|
||||
.rpg-level-value {
|
||||
font-size: clamp(11px, 2.8vw, 14px) !important;
|
||||
}
|
||||
|
||||
.rpg-stats-grid {
|
||||
@@ -3617,14 +3729,6 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
||||
font-size: clamp(12px, 3.1vw, 16px) !important;
|
||||
}
|
||||
|
||||
/* Make the avatar+inventory flex container grow to full width */
|
||||
.rpg-stats-left > div[style*="display: flex"] {
|
||||
width: 100% !important; /* Force full width in grid cell */
|
||||
flex: 1 !important; /* Allow it to grow */
|
||||
grid-column: 1 / 3 !important; /* Take full grid width */
|
||||
grid-row: 1 !important; /* Position in row 1 */
|
||||
}
|
||||
|
||||
/* Inventory - expand to fill horizontal space in flex container */
|
||||
.rpg-inventory-box {
|
||||
flex: 1 !important; /* Grow to fill all available space */
|
||||
@@ -3646,7 +3750,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
||||
/* Mood - row 4, aligned with attributes top */
|
||||
.rpg-mood {
|
||||
grid-column: 1;
|
||||
grid-row: 4 / 6; /* Span 2 rows to match attributes height */
|
||||
grid-row: 3;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
@@ -3662,13 +3766,13 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
||||
/* Attributes - right side, rows 4-6 aligned with mood */
|
||||
.rpg-stats-right {
|
||||
grid-column: 2;
|
||||
grid-row: 4 / 6;
|
||||
grid-row: 3;
|
||||
display: contents !important;
|
||||
}
|
||||
|
||||
.rpg-classic-stats {
|
||||
grid-column: 2;
|
||||
grid-row: 4 / 6; /* Start at row 4, aligned with mood top */
|
||||
grid-row: 3; /* Align with mood */
|
||||
}
|
||||
|
||||
/* Attributes as ultra-compact 2x3 grid for mobile */
|
||||
@@ -3892,6 +3996,50 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
||||
}
|
||||
}
|
||||
|
||||
/* Very narrow screens - single column layout for all stats */
|
||||
@media (max-width: 600px) {
|
||||
/* Change grid to single column */
|
||||
.rpg-stats-section {
|
||||
grid-template-columns: 1fr !important; /* Single column */
|
||||
grid-template-rows: auto !important; /* Auto rows */
|
||||
}
|
||||
|
||||
/* User info row - full width, row 1 */
|
||||
.rpg-user-info-row {
|
||||
grid-column: 1 !important;
|
||||
grid-row: 1 !important;
|
||||
}
|
||||
|
||||
/* Stats grid - full width, row 2 */
|
||||
.rpg-stats-grid {
|
||||
grid-column: 1 !important;
|
||||
grid-row: 2 !important;
|
||||
}
|
||||
|
||||
/* Mood - full width, row 3 */
|
||||
.rpg-mood {
|
||||
grid-column: 1 !important;
|
||||
grid-row: 3 !important;
|
||||
}
|
||||
|
||||
/* Attributes - full width, row 4 */
|
||||
.rpg-stats-right {
|
||||
grid-column: 1 !important;
|
||||
grid-row: 4 !important;
|
||||
}
|
||||
|
||||
.rpg-classic-stats {
|
||||
grid-column: 1 !important;
|
||||
grid-row: 4 !important;
|
||||
}
|
||||
|
||||
/* Make attributes grid single column too for readability */
|
||||
.rpg-classic-stats-grid {
|
||||
grid-template-columns: repeat(3, 1fr) !important; /* 3 columns for attributes */
|
||||
grid-template-rows: repeat(2, 1fr) !important; /* 2 rows */
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra small screens - adjust FAB position */
|
||||
@media (max-width: 480px) {
|
||||
.rpg-mobile-toggle {
|
||||
@@ -4433,7 +4581,6 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
||||
gap: 0;
|
||||
background: var(--SmartThemeBlurTintColor);
|
||||
border-bottom: 2px solid var(--SmartThemeBorderColor);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Desktop tab button */
|
||||
|
||||
Reference in New Issue
Block a user