diff --git a/src/core/persistence.js b/src/core/persistence.js
index 58535a4..fcd1131 100644
--- a/src/core/persistence.js
+++ b/src/core/persistence.js
@@ -381,6 +381,14 @@ function migrateToTrackerConfig() {
userStats: {
customStats: [],
showRPGAttributes: true,
+ rpgAttributes: [
+ { id: 'str', name: 'STR', enabled: true },
+ { id: 'dex', name: 'DEX', enabled: true },
+ { id: 'con', name: 'CON', enabled: true },
+ { id: 'int', name: 'INT', enabled: true },
+ { id: 'wis', name: 'WIS', enabled: true },
+ { id: 'cha', name: 'CHA', enabled: true }
+ ],
statusSection: {
enabled: true,
showMoodEmoji: true,
@@ -438,6 +446,47 @@ function migrateToTrackerConfig() {
}
}
+ // Migrate old showRPGAttributes boolean to rpgAttributes array
+ if (extensionSettings.trackerConfig.userStats.showRPGAttributes !== undefined) {
+ const shouldShow = extensionSettings.trackerConfig.userStats.showRPGAttributes;
+ extensionSettings.trackerConfig.userStats.rpgAttributes = [
+ { id: 'str', name: 'STR', enabled: shouldShow },
+ { id: 'dex', name: 'DEX', enabled: shouldShow },
+ { id: 'con', name: 'CON', enabled: shouldShow },
+ { id: 'int', name: 'INT', enabled: shouldShow },
+ { id: 'wis', name: 'WIS', enabled: shouldShow },
+ { id: 'cha', name: 'CHA', enabled: shouldShow }
+ ];
+ delete extensionSettings.trackerConfig.userStats.showRPGAttributes;
+ console.log('[RPG Companion] Migrated showRPGAttributes to rpgAttributes array');
+ }
+
+ // Ensure rpgAttributes exists even if no migration was needed
+ if (!extensionSettings.trackerConfig.userStats.rpgAttributes) {
+ extensionSettings.trackerConfig.userStats.rpgAttributes = [
+ { id: 'str', name: 'STR', enabled: true },
+ { id: 'dex', name: 'DEX', enabled: true },
+ { id: 'con', name: 'CON', enabled: true },
+ { id: 'int', name: 'INT', enabled: true },
+ { id: 'wis', name: 'WIS', enabled: true },
+ { id: 'cha', name: 'CHA', enabled: true }
+ ];
+ }
+
+ // Ensure showRPGAttributes exists (defaults to true)
+ if (extensionSettings.trackerConfig.userStats.showRPGAttributes === undefined) {
+ extensionSettings.trackerConfig.userStats.showRPGAttributes = true;
+ }
+
+ // Ensure all rpgAttributes have corresponding values in classicStats
+ if (extensionSettings.classicStats) {
+ for (const attr of extensionSettings.trackerConfig.userStats.rpgAttributes) {
+ if (extensionSettings.classicStats[attr.id] === undefined) {
+ extensionSettings.classicStats[attr.id] = 10;
+ }
+ }
+ }
+
// Migrate old presentCharacters structure to new format
if (extensionSettings.trackerConfig.presentCharacters) {
const pc = extensionSettings.trackerConfig.presentCharacters;
diff --git a/src/core/state.js b/src/core/state.js
index d0069c0..d3afd51 100644
--- a/src/core/state.js
+++ b/src/core/state.js
@@ -71,8 +71,16 @@ export let extensionSettings = {
{ id: 'hygiene', name: 'Hygiene', enabled: true },
{ id: 'arousal', name: 'Arousal', enabled: true }
],
- // RPG Attributes toggle
+ // RPG Attributes (customizable D&D-style attributes)
showRPGAttributes: true,
+ rpgAttributes: [
+ { id: 'str', name: 'STR', enabled: true },
+ { id: 'dex', name: 'DEX', enabled: true },
+ { id: 'con', name: 'CON', enabled: true },
+ { id: 'int', name: 'INT', enabled: true },
+ { id: 'wis', name: 'WIS', enabled: true },
+ { id: 'cha', name: 'CHA', enabled: true }
+ ],
// Status section config
statusSection: {
enabled: true,
diff --git a/src/systems/generation/promptBuilder.js b/src/systems/generation/promptBuilder.js
index 4049536..62be66c 100644
--- a/src/systems/generation/promptBuilder.js
+++ b/src/systems/generation/promptBuilder.js
@@ -56,6 +56,41 @@ export function buildInventorySummary(inventory) {
return 'None';
}
+/**
+ * Builds a dynamic attributes string based on configured RPG attributes.
+ * Uses custom attribute names and values from classicStats.
+ *
+ * @returns {string} Formatted attributes string (e.g., "STR 10, DEX 12, INT 15, LVL 5")
+ */
+function buildAttributesString() {
+ const trackerConfig = extensionSettings.trackerConfig;
+ const classicStats = extensionSettings.classicStats;
+ const userStatsConfig = trackerConfig?.userStats;
+
+ // Get enabled attributes from config
+ const rpgAttributes = userStatsConfig?.rpgAttributes || [
+ { id: 'str', name: 'STR', enabled: true },
+ { id: 'dex', name: 'DEX', enabled: true },
+ { id: 'con', name: 'CON', enabled: true },
+ { id: 'int', name: 'INT', enabled: true },
+ { id: 'wis', name: 'WIS', enabled: true },
+ { id: 'cha', name: 'CHA', enabled: true }
+ ];
+
+ const enabledAttributes = rpgAttributes.filter(attr => attr && attr.enabled && attr.name && attr.id);
+
+ // Build attributes string dynamically
+ const attributeParts = enabledAttributes.map(attr => {
+ const value = classicStats[attr.id] !== undefined ? classicStats[attr.id] : 10;
+ return `${attr.name} ${value}`;
+ });
+
+ // Add level at the end
+ attributeParts.push(`LVL ${extensionSettings.level}`);
+
+ return attributeParts.join(', ');
+}
+
/**
* Generates an example block showing current tracker states in markdown code blocks.
* Uses COMMITTED data (not displayed data) for generation context.
@@ -170,7 +205,7 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon
instructions += 'Weather: [Weather Emoji, Forecast]\n';
}
if (widgets.temperature?.enabled) {
- const unit = widgets.temperature.unit === 'fahrenheit' ? '°F' : '°C';
+ const unit = widgets.temperature.unit === 'F' ? '°F' : '°C';
instructions += `Temperature: [Temperature in ${unit}]\n`;
}
if (widgets.time?.enabled) {
@@ -250,7 +285,8 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon
// 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}, LVL ${extensionSettings.level}\n`;
+ const attributesString = buildAttributesString();
+ instructions += `${userName}'s attributes: ${attributesString}\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`;
}
}
@@ -327,9 +363,9 @@ export function generateContextualSummary() {
// Include attributes and dice roll only if there was a dice roll
if (extensionSettings.lastDiceRoll) {
- const classicStats = extensionSettings.classicStats;
const roll = extensionSettings.lastDiceRoll;
- summary += `${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`;
+ const attributesString = buildAttributesString();
+ summary += `${userName}'s attributes: ${attributesString}\n`;
summary += `${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`;
}
diff --git a/src/systems/integration/sillytavern.js b/src/systems/integration/sillytavern.js
index 53ac984..d429938 100644
--- a/src/systems/integration/sillytavern.js
+++ b/src/systems/integration/sillytavern.js
@@ -103,11 +103,11 @@ export async function onMessageReceived(data) {
// console.log('[RPG Companion] Parsing together mode response:', responseText);
const parsedData = parseResponse(responseText);
- console.log('[RPG Companion] Parsed data results:', {
- hasUserStats: !!parsedData.userStats,
- hasInfoBox: !!parsedData.infoBox,
- hasCharacterThoughts: !!parsedData.characterThoughts
- });
+ // console.log('[RPG Companion] Parsed data results:', {
+ // hasUserStats: !!parsedData.userStats,
+ // hasInfoBox: !!parsedData.infoBox,
+ // hasCharacterThoughts: !!parsedData.characterThoughts
+ // });
// Update stored data (both lastGeneratedData for old UI and extensionSettings for dashboard widgets)
if (parsedData.userStats) {
@@ -171,9 +171,7 @@ export async function onMessageReceived(data) {
lastMessage.swipes[currentSwipeId] = cleanedMessage.trim();
}
- // console.log('[RPG Companion] Cleaned message, removed tracker code blocks');
-
- // Render the updated data (old panel UI)
+ // Render the updated data FIRST (before cleaning DOM)
renderUserStats();
renderInfoBox();
renderThoughts();
@@ -183,6 +181,17 @@ export async function onMessageReceived(data) {
// Refresh dashboard widgets (v2 dashboard)
refreshDashboard();
+ // Then update the DOM to reflect the cleaned message
+ const lastMessageElement = $('#chat').children('.mes').last();
+ if (lastMessageElement.length) {
+ const messageText = lastMessageElement.find('.mes_text');
+ if (messageText.length) {
+ messageText.html(substituteParams(cleanedMessage.trim()));
+ }
+ }
+
+ // console.log('[RPG Companion] Cleaned message, removed tracker code blocks from DOM');
+
// Save to chat metadata
saveChatData();
}
diff --git a/src/systems/rendering/infoBox.js b/src/systems/rendering/infoBox.js
index 4a4a5e6..d223e84 100644
--- a/src/systems/rendering/infoBox.js
+++ b/src/systems/rendering/infoBox.js
@@ -329,18 +329,18 @@ export function renderInfoBox() {
let tempValue = data.tempValue || 20;
// Apply temperature unit conversion
- const preferredUnit = config.widgets.temperature.unit || 'celsius';
+ const preferredUnit = config.widgets.temperature.unit || 'C';
if (data.temperature) {
// Detect current unit in the data
const isCelsius = tempDisplay.includes('°C');
const isFahrenheit = tempDisplay.includes('°F');
- if (preferredUnit === 'fahrenheit' && isCelsius) {
+ if (preferredUnit === 'F' && isCelsius) {
// Convert C to F
const fahrenheit = Math.round((tempValue * 9/5) + 32);
tempDisplay = `${fahrenheit}°F`;
tempValue = fahrenheit;
- } else if (preferredUnit === 'celsius' && isFahrenheit) {
+ } else if (preferredUnit === 'C' && isFahrenheit) {
// Convert F to C
const celsius = Math.round((tempValue - 32) * 5/9);
tempDisplay = `${celsius}°C`;
@@ -348,12 +348,14 @@ export function renderInfoBox() {
}
} else {
// No data yet, use default for preferred unit
- tempDisplay = preferredUnit === 'fahrenheit' ? '68°F' : '20°C';
- tempValue = preferredUnit === 'fahrenheit' ? 68 : 20;
+ tempDisplay = preferredUnit === 'F' ? '68°F' : '20°C';
+ tempValue = preferredUnit === 'F' ? 68 : 20;
}
- const tempPercent = Math.min(100, Math.max(0, ((tempValue + 20) / 60) * 100));
- const tempColor = tempValue < 10 ? '#4a90e2' : tempValue < 25 ? '#67c23a' : '#e94560';
+ // Calculate thermometer display (convert to Celsius for consistent thresholds)
+ const tempInCelsius = preferredUnit === 'F' ? Math.round((tempValue - 32) * 5/9) : tempValue;
+ const tempPercent = Math.min(100, Math.max(0, ((tempInCelsius + 20) / 60) * 100));
+ const tempColor = tempInCelsius < 10 ? '#4a90e2' : tempInCelsius < 25 ? '#67c23a' : '#e94560';
row1Widgets.push(`
diff --git a/src/systems/rendering/thoughts.js b/src/systems/rendering/thoughts.js
index fa1fbc0..d263ed1 100644
--- a/src/systems/rendering/thoughts.js
+++ b/src/systems/rendering/thoughts.js
@@ -396,7 +396,7 @@ export function renderThoughts() {
const statColor = getStatColor(statValue, extensionSettings.statBarColorLow, extensionSettings.statBarColorHigh);
html += `
- ${stat.name}: ${statValue}%
+ ${stat.name}: ${statValue}%
`;
}
@@ -431,6 +431,7 @@ export function renderThoughts() {
const character = $(this).data('character');
const field = $(this).data('field');
const value = $(this).text().trim();
+ console.log('[RPG Companion] Character stat edit:', { character, field, value });
updateCharacterField(character, field, value);
});
@@ -492,10 +493,20 @@ export function updateCharacterField(characterName, field, value) {
}
if (characterFound) {
- // Update the specific field within the character block
+ // Check if we're updating a character stat
+ const isStatField = enabledCharStats.findIndex(s => s.name === field) !== -1;
+ let statsLineExists = false;
+ let statsLineIndex = -1;
+
+ // First pass: check if Stats line exists and update other fields
for (let i = characterStartIndex; i < characterEndIndex; i++) {
const line = lines[i].trim();
+ if (line.startsWith('Stats:')) {
+ statsLineExists = true;
+ statsLineIndex = i;
+ }
+
if (field === 'name' && line.startsWith('- ')) {
lines[i] = `- ${value}`;
}
@@ -519,20 +530,68 @@ export function updateCharacterField(characterName, field, value) {
const relationshipValue = emojiToRelationship[value] || value;
lines[i] = `Relationship: ${relationshipValue}`;
}
- else if (line.startsWith('Stats:')) {
- const statIndex = enabledCharStats.findIndex(s => s.name === field);
- if (statIndex !== -1) {
- const statsContent = line.substring(line.indexOf(':') + 1).trim();
- const statParts = statsContent.split('|').map(p => p.trim());
+ }
- for (let j = 0; j < statParts.length; j++) {
- if (statParts[j].startsWith(field + ':')) {
- statParts[j] = `${field}: ${value}%`;
- break;
- }
+ // Handle stat updates
+ if (isStatField) {
+ // Clean the value: remove % if present, parse as integer, clamp 0-100
+ let cleanValue = value.replace('%', '').trim();
+ let numValue = parseInt(cleanValue);
+ if (isNaN(numValue)) {
+ numValue = 0;
+ }
+ numValue = Math.max(0, Math.min(100, numValue));
+
+ console.log('[RPG Companion] Updating stat:', { field, rawValue: value, cleanValue, numValue });
+
+ if (statsLineExists) {
+ // Update existing Stats line
+ const line = lines[statsLineIndex];
+ const statsContent = line.substring(line.indexOf(':') + 1).trim();
+ const statParts = statsContent.split('|').map(p => p.trim());
+
+ let statFound = false;
+ for (let j = 0; j < statParts.length; j++) {
+ if (statParts[j].startsWith(field + ':')) {
+ statParts[j] = `${field}: ${numValue}%`;
+ statFound = true;
+ console.log('[RPG Companion] Updated stat part:', statParts[j]);
+ break;
}
- lines[i] = `Stats: ${statParts.join(' | ')}`;
}
+
+ // If stat wasn't found in existing parts, add it
+ if (!statFound) {
+ statParts.push(`${field}: ${numValue}%`);
+ console.log('[RPG Companion] Added new stat to existing line:', `${field}: ${numValue}%`);
+ }
+
+ lines[statsLineIndex] = `Stats: ${statParts.join(' | ')}`;
+ console.log('[RPG Companion] Updated stats line:', lines[statsLineIndex]);
+ } else {
+ // Create new Stats line with all enabled stats (defaulting to 0% except the one being edited)
+ const statsParts = enabledCharStats.map(s => {
+ if (s.name === field) {
+ return `${s.name}: ${numValue}%`;
+ }
+ return `${s.name}: 0%`;
+ });
+ const newStatsLine = `Stats: ${statsParts.join(' | ')}`;
+
+ // Insert before Thoughts line or at end of character block
+ let insertIndex = characterEndIndex;
+ for (let i = characterStartIndex; i < characterEndIndex; i++) {
+ const line = lines[i].trim();
+ const thoughtsFieldName = presentCharsConfig?.thoughts?.name || 'Thoughts';
+ if (line.startsWith(thoughtsFieldName + ':')) {
+ insertIndex = i;
+ break;
+ }
+ }
+
+ lines.splice(insertIndex, 0, newStatsLine);
+ console.log('[RPG Companion] Created new stats line:', newStatsLine);
+ characterEndIndex++; // Adjust end index since we inserted a line
}
}
} else {
@@ -554,7 +613,19 @@ export function updateCharacterField(characterName, field, value) {
}
if (enabledCharStats.length > 0) {
- const statsParts = enabledCharStats.map(s => `${s.name}: ${field === s.name ? value : '0'}%`);
+ const statsParts = enabledCharStats.map(s => {
+ if (field === s.name) {
+ // Clean the value: remove % if present, parse as integer, clamp 0-100
+ let cleanValue = value.replace('%', '').trim();
+ let numValue = parseInt(cleanValue);
+ if (isNaN(numValue)) {
+ numValue = 0;
+ }
+ numValue = Math.max(0, Math.min(100, numValue));
+ return `${s.name}: ${numValue}%`;
+ }
+ return `${s.name}: 0%`;
+ });
newCharacterLines.push(`Stats: ${statsParts.join(' | ')}`);
}
@@ -565,6 +636,8 @@ export function updateCharacterField(characterName, field, value) {
lastGeneratedData.characterThoughts = lines.join('\n');
committedTrackerData.characterThoughts = lines.join('\n');
+ console.log('[RPG Companion] Updated characterThoughts data:', lastGeneratedData.characterThoughts);
+
const chat = getContext().chat;
if (chat && chat.length > 0) {
for (let i = chat.length - 1; i >= 0; i--) {
diff --git a/src/systems/rendering/userStats.js b/src/systems/rendering/userStats.js
index 12de415..97fad33 100644
--- a/src/systems/rendering/userStats.js
+++ b/src/systems/rendering/userStats.js
@@ -86,7 +86,14 @@ export function renderUserStats() {
{ id: 'hygiene', name: 'Hygiene', enabled: true },
{ id: 'arousal', name: 'Arousal', enabled: true }
],
- showRPGAttributes: true,
+ rpgAttributes: [
+ { id: 'str', name: 'STR', enabled: true },
+ { id: 'dex', name: 'DEX', enabled: true },
+ { id: 'con', name: 'CON', enabled: true },
+ { id: 'int', name: 'INT', enabled: true },
+ { id: 'wis', name: 'WIS', enabled: true },
+ { id: 'cha', name: 'CHA', enabled: true }
+ ],
statusSection: { enabled: true, showMoodEmoji: true, customFields: ['Conditions'] },
skillsSection: { enabled: false, label: 'Skills' }
};
@@ -171,64 +178,49 @@ export function renderUserStats() {
html += '
'; // Close rpg-stats-left
- // RPG Attributes section (conditionally rendered)
- if (config.showRPGAttributes) {
+ // RPG Attributes section (dynamically generated from config)
+ // Check if RPG Attributes section is enabled
+ const showRPGAttributes = config.showRPGAttributes !== undefined ? config.showRPGAttributes : true;
+
+ if (showRPGAttributes) {
+ // Use attributes from config, with fallback to defaults if not configured
+ const rpgAttributes = (config.rpgAttributes && config.rpgAttributes.length > 0) ? config.rpgAttributes : [
+ { id: 'str', name: 'STR', enabled: true },
+ { id: 'dex', name: 'DEX', enabled: true },
+ { id: 'con', name: 'CON', enabled: true },
+ { id: 'int', name: 'INT', enabled: true },
+ { id: 'wis', name: 'WIS', enabled: true },
+ { id: 'cha', name: 'CHA', enabled: true }
+ ];
+ const enabledAttributes = rpgAttributes.filter(attr => attr && attr.enabled && attr.name && attr.id);
+
+ if (enabledAttributes.length > 0) {
html += `
-
-
STR
+ `;
+
+ enabledAttributes.forEach(attr => {
+ const value = extensionSettings.classicStats[attr.id] !== undefined ? extensionSettings.classicStats[attr.id] : 10;
+ html += `
+
+
${attr.name}
-
- ${extensionSettings.classicStats.str}
-
-
-
-
-
DEX
-
-
- ${extensionSettings.classicStats.dex}
-
-
-
-
-
CON
-
-
- ${extensionSettings.classicStats.con}
-
-
-
-
-
INT
-
-
- ${extensionSettings.classicStats.int}
-
-
-
-
-
WIS
-
-
- ${extensionSettings.classicStats.wis}
-
-
-
-
-
CHA
-
-
- ${extensionSettings.classicStats.cha}
-
+
+ ${value}
+
+ `;
+ });
+
+ html += `
`;
+ }
}
html += '
'; // Close rpg-stats-content
diff --git a/src/systems/ui/trackerEditor.js b/src/systems/ui/trackerEditor.js
index 4b96ef8..2d66f67 100644
--- a/src/systems/ui/trackerEditor.js
+++ b/src/systems/ui/trackerEditor.js
@@ -137,6 +137,14 @@ function resetToDefaults() {
{ id: 'arousal', name: 'Arousal', enabled: true }
],
showRPGAttributes: true,
+ rpgAttributes: [
+ { id: 'str', name: 'STR', enabled: true },
+ { id: 'dex', name: 'DEX', enabled: true },
+ { id: 'con', name: 'CON', enabled: true },
+ { id: 'int', name: 'INT', enabled: true },
+ { id: 'wis', name: 'WIS', enabled: true },
+ { id: 'cha', name: 'CHA', enabled: true }
+ ],
statusSection: {
enabled: true,
showMoodEmoji: true,
@@ -221,12 +229,47 @@ function renderUserStatsTab() {
html += '
';
html += '';
- // RPG Attributes toggle
+ // RPG Attributes section
+ html += ' RPG Attributes
';
+
+ // Enable/disable toggle for entire RPG Attributes section
+ const showRPGAttributes = config.showRPGAttributes !== undefined ? config.showRPGAttributes : true;
html += '';
- html += ``;
- html += '';
+ html += ``;
+ html += '';
html += '
';
+ html += '';
+
+ // Ensure rpgAttributes exists in the actual config (not just local fallback)
+ if (!config.rpgAttributes || config.rpgAttributes.length === 0) {
+ config.rpgAttributes = [
+ { id: 'str', name: 'STR', enabled: true },
+ { id: 'dex', name: 'DEX', enabled: true },
+ { id: 'con', name: 'CON', enabled: true },
+ { id: 'int', name: 'INT', enabled: true },
+ { id: 'wis', name: 'WIS', enabled: true },
+ { id: 'cha', name: 'CHA', enabled: true }
+ ];
+ // Save the defaults back to the actual config
+ extensionSettings.trackerConfig.userStats.rpgAttributes = config.rpgAttributes;
+ }
+
+ const rpgAttributes = config.rpgAttributes;
+
+ rpgAttributes.forEach((attr, index) => {
+ html += `
+
+
+
+
+
+ `;
+ });
+
+ html += '
';
+ html += '';
+
// Status Section
html += ' Status Section
';
html += '';
@@ -296,7 +339,52 @@ function setupUserStatsListeners() {
extensionSettings.trackerConfig.userStats.customStats[index].name = $(this).val();
});
- // RPG attributes toggle
+ // Add attribute
+ $('#rpg-add-attr').off('click').on('click', function() {
+ // Ensure rpgAttributes array exists with defaults if needed
+ if (!extensionSettings.trackerConfig.userStats.rpgAttributes || extensionSettings.trackerConfig.userStats.rpgAttributes.length === 0) {
+ extensionSettings.trackerConfig.userStats.rpgAttributes = [
+ { id: 'str', name: 'STR', enabled: true },
+ { id: 'dex', name: 'DEX', enabled: true },
+ { id: 'con', name: 'CON', enabled: true },
+ { id: 'int', name: 'INT', enabled: true },
+ { id: 'wis', name: 'WIS', enabled: true },
+ { id: 'cha', name: 'CHA', enabled: true }
+ ];
+ }
+ const newId = 'attr_' + Date.now();
+ extensionSettings.trackerConfig.userStats.rpgAttributes.push({
+ id: newId,
+ name: 'NEW',
+ enabled: true
+ });
+ // Initialize value in classicStats if doesn't exist
+ if (extensionSettings.classicStats[newId] === undefined) {
+ extensionSettings.classicStats[newId] = 10;
+ }
+ renderUserStatsTab();
+ });
+
+ // Remove attribute
+ $('.rpg-attr-remove').off('click').on('click', function() {
+ const index = $(this).data('index');
+ extensionSettings.trackerConfig.userStats.rpgAttributes.splice(index, 1);
+ renderUserStatsTab();
+ });
+
+ // Toggle attribute
+ $('.rpg-attr-toggle').off('change').on('change', function() {
+ const index = $(this).data('index');
+ extensionSettings.trackerConfig.userStats.rpgAttributes[index].enabled = $(this).is(':checked');
+ });
+
+ // Rename attribute
+ $('.rpg-attr-name').off('blur').on('blur', function() {
+ const index = $(this).data('index');
+ extensionSettings.trackerConfig.userStats.rpgAttributes[index].name = $(this).val();
+ });
+
+ // Enable/disable RPG Attributes section toggle
$('#rpg-show-rpg-attrs').off('change').on('change', function() {
extensionSettings.trackerConfig.userStats.showRPGAttributes = $(this).is(':checked');
});
@@ -452,7 +540,7 @@ function renderPresentCharactersTab() {
→
-
+
`;
}
diff --git a/style.css b/style.css
index 13d82f5..2c45c86 100644
--- a/style.css
+++ b/style.css
@@ -7981,3 +7981,67 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
.rpg-widget-empty-state a:hover {
opacity: 0.8;
}
+
+/* ========================================
+ MOBILE FONT SIZE OVERRIDES (ALL SCREENS)
+ Apply to screens up to 1000px
+ ======================================== */
+@media (max-width: 1000px) {
+ /* Collapse toggle button */
+ .rpg-collapse-toggle {
+ font-size: clamp(16px, 3vw, 20px) !important;
+ }
+
+ /* Top position panel titles */
+ .rpg-panel.rpg-position-top .rpg-stats-title {
+ font-size: clamp(12px, 2.6vw, 16px) !important;
+ }
+
+ .rpg-panel.rpg-position-top .rpg-mood {
+ font-size: clamp(10px, 2vw, 13px) !important;
+ }
+
+ .rpg-panel.rpg-position-top .rpg-classic-stats-title {
+ font-size: clamp(10px, 2vw, 13px) !important;
+ }
+
+ .rpg-panel.rpg-position-top .rpg-classic-stat-label {
+ font-size: clamp(8px, 1.7vw, 11px) !important;
+ }
+
+ .rpg-panel.rpg-position-top .rpg-classic-stat-value {
+ font-size: clamp(12px, 2.6vw, 16px) !important;
+ }
+
+ .rpg-panel.rpg-position-top .rpg-classic-stat-btn {
+ font-size: clamp(10px, 2.2vw, 14px) !important;
+ }
+
+ .rpg-panel.rpg-position-top .rpg-info-content,
+ .rpg-panel.rpg-position-top .rpg-thoughts-content {
+ font-size: clamp(10px, 2.2vw, 14px) !important;
+ }
+
+ /* Panel header */
+ .rpg-panel-header h3 {
+ font-size: clamp(14px, 3.4vw, 18px) !important;
+ }
+
+ /* Loading indicator */
+ .rpg-loading {
+ font-size: clamp(12px, 2.6vw, 16px) !important;
+ }
+
+ /* Dice display */
+ .rpg-dice-display {
+ font-size: clamp(10px, 2vw, 13px) !important;
+ }
+
+ .rpg-dice-display i {
+ font-size: clamp(12px, 2.6vw, 16px) !important;
+ }
+
+ .rpg-clear-dice-btn {
+ font-size: clamp(14px, 3vw, 18px) !important;
+ }
+}