`;
for (const stat of enabledCharStats) {
const statValue = char[stat.name] || 0;
- const statColor = getStatColor(statValue, extensionSettings.statBarColorLow, extensionSettings.statBarColorHigh);
+ const statColor = getStatColor(
+ statValue,
+ extensionSettings.statBarColorLow,
+ extensionSettings.statBarColorHigh,
+ extensionSettings.statBarColorLowOpacity ?? 100,
+ extensionSettings.statBarColorHighOpacity ?? 100
+ );
html += `
${stat.name}: ${statValue}%
diff --git a/src/systems/rendering/userStats.js b/src/systems/rendering/userStats.js
index 31cbe16..2f11822 100644
--- a/src/systems/rendering/userStats.js
+++ b/src/systems/rendering/userStats.js
@@ -21,6 +21,7 @@ import { getSafeThumbnailUrl } from '../../utils/avatars.js';
import { buildInventorySummary } from '../generation/promptBuilder.js';
import { isItemLocked, setItemLock } from '../generation/lockManager.js';
import { updateFabWidgets } from '../ui/mobile.js';
+import { getStatBarColors } from '../ui/theme.js';
/**
* Builds the user stats text string using custom stat names
@@ -251,8 +252,9 @@ export function renderUserStats() {
}
}
- // Create gradient from low to high color
- const gradient = `linear-gradient(to right, ${extensionSettings.statBarColorLow}, ${extensionSettings.statBarColorHigh})`;
+ // Create gradient from low to high color with opacity
+ const colors = getStatBarColors();
+ const gradient = `linear-gradient(to right, ${colors.low}, ${colors.high})`;
// Check if stats bars section is locked
const isStatsLocked = isItemLocked('userStats', 'stats');
diff --git a/src/systems/ui/desktop.js b/src/systems/ui/desktop.js
index 3324c29..07e2916 100644
--- a/src/systems/ui/desktop.js
+++ b/src/systems/ui/desktop.js
@@ -5,6 +5,7 @@
import { i18n } from '../../core/i18n.js';
import { extensionSettings, lastGeneratedData, committedTrackerData } from '../../core/state.js';
+import { hexToRgba } from './theme.js';
/**
* Helper to parse time string and calculate clock hand angles
@@ -28,7 +29,7 @@ function parseTimeForClock(timeStr) {
export function updateStripWidgets() {
const $panel = $('#rpg-companion-panel');
const $container = $('#rpg-strip-widget-container');
-
+
if ($panel.length === 0 || $container.length === 0) return;
// Check if strip widgets are enabled
@@ -118,7 +119,7 @@ export function updateStripWidgets() {
const $statsWidget = $container.find('.rpg-strip-widget-stats');
if (widgetSettings.stats?.enabled) {
let allStats = [];
-
+
// Try to get stats from tracker data first (most current)
const userStatsData = lastGeneratedData?.userStats || committedTrackerData?.userStats;
if (userStatsData) {
@@ -131,7 +132,7 @@ export function updateStripWidgets() {
console.warn('[RPG Strip Widgets] Failed to parse tracker userStats:', e);
}
}
-
+
// Fallback to extensionSettings.userStats
if (allStats.length === 0 && extensionSettings.userStats) {
try {
@@ -237,7 +238,9 @@ export function updateStripWidgets() {
*/
function getStatColor(value) {
const lowColor = extensionSettings.statBarColorLow || '#cc3333';
+ const lowOpacity = extensionSettings.statBarColorLowOpacity ?? 100;
const highColor = extensionSettings.statBarColorHigh || '#33cc66';
+ const highOpacity = extensionSettings.statBarColorHighOpacity ?? 100;
// Simple linear interpolation between low and high colors
const percent = Math.min(100, Math.max(0, value)) / 100;
@@ -246,13 +249,14 @@ function getStatColor(value) {
const lowRGB = hexToRgb(lowColor);
const highRGB = hexToRgb(highColor);
- if (!lowRGB || !highRGB) return value > 50 ? highColor : lowColor;
+ if (!lowRGB || !highRGB) return value > 50 ? hexToRgba(highColor, highOpacity) : hexToRgba(lowColor, lowOpacity);
const r = Math.round(lowRGB.r + (highRGB.r - lowRGB.r) * percent);
const g = Math.round(lowRGB.g + (highRGB.g - lowRGB.g) * percent);
const b = Math.round(lowRGB.b + (highRGB.b - lowRGB.b) * percent);
+ const a = (lowOpacity + (highOpacity - lowOpacity) * percent) / 100;
- return `rgb(${r}, ${g}, ${b})`;
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
}
/**
diff --git a/src/systems/ui/mobile.js b/src/systems/ui/mobile.js
index b3f9a42..9a537d3 100644
--- a/src/systems/ui/mobile.js
+++ b/src/systems/ui/mobile.js
@@ -8,6 +8,7 @@ import { saveSettings } from '../../core/persistence.js';
import { closeMobilePanelWithAnimation, updateCollapseToggleIcon } from './layout.js';
import { setupDesktopTabs, removeDesktopTabs } from './desktop.js';
import { i18n } from '../../core/i18n.js';
+import { hexToRgba } from './theme.js';
/**
* Updates the text labels of the mobile navigation tabs based on the current language.
@@ -1451,7 +1452,7 @@ export function updateFabWidgets() {
if (widgetSettings.attributes?.enabled) {
// Check if RPG attributes are enabled in trackerConfig
const showRPGAttributes = extensionSettings.trackerConfig?.userStats?.showRPGAttributes !== false;
-
+
if (showRPGAttributes && extensionSettings.classicStats) {
// Get enabled attributes from trackerConfig
const configuredAttrs = extensionSettings.trackerConfig?.userStats?.rpgAttributes || [];
@@ -1541,10 +1542,10 @@ export function updateFabWidgets() {
e.stopPropagation();
const $this = $(this);
const wasExpanded = $this.hasClass('expanded');
-
+
// Collapse all other expanded widgets
$container.find('.rpg-fab-widget.expanded').removeClass('expanded');
-
+
// Toggle this one
if (!wasExpanded) {
$this.addClass('expanded');
@@ -1567,7 +1568,9 @@ export function updateFabWidgets() {
*/
function getStatColor(value) {
const lowColor = extensionSettings.statBarColorLow || '#cc3333';
+ const lowOpacity = extensionSettings.statBarColorLowOpacity ?? 100;
const highColor = extensionSettings.statBarColorHigh || '#33cc66';
+ const highOpacity = extensionSettings.statBarColorHighOpacity ?? 100;
// Simple linear interpolation between low and high colors
const percent = Math.min(100, Math.max(0, value)) / 100;
@@ -1576,13 +1579,14 @@ function getStatColor(value) {
const lowRGB = hexToRgb(lowColor);
const highRGB = hexToRgb(highColor);
- if (!lowRGB || !highRGB) return value > 50 ? highColor : lowColor;
+ if (!lowRGB || !highRGB) return value > 50 ? hexToRgba(highColor, highOpacity) : hexToRgba(lowColor, lowOpacity);
const r = Math.round(lowRGB.r + (highRGB.r - lowRGB.r) * percent);
const g = Math.round(lowRGB.g + (highRGB.g - lowRGB.g) * percent);
const b = Math.round(lowRGB.b + (highRGB.b - lowRGB.b) * percent);
+ const a = (lowOpacity + (highOpacity - lowOpacity) * percent) / 100;
- return `rgb(${r}, ${g}, ${b})`;
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
}
/**
diff --git a/src/systems/ui/promptsEditor.js b/src/systems/ui/promptsEditor.js
index 86b02fa..4e47661 100644
--- a/src/systems/ui/promptsEditor.js
+++ b/src/systems/ui/promptsEditor.js
@@ -4,7 +4,7 @@
*/
import { extensionSettings } from '../../core/state.js';
import { saveSettings } from '../../core/persistence.js';
-import { DEFAULT_HTML_PROMPT, DEFAULT_DIALOGUE_COLORING_PROMPT, DEFAULT_DECEPTION_PROMPT, DEFAULT_OMNISCIENCE_FILTER_PROMPT, DEFAULT_CYOA_PROMPT, DEFAULT_SPOTIFY_PROMPT, DEFAULT_NARRATOR_PROMPT } from '../generation/promptBuilder.js';
+import { DEFAULT_HTML_PROMPT, DEFAULT_DIALOGUE_COLORING_PROMPT, DEFAULT_DECEPTION_PROMPT, DEFAULT_OMNISCIENCE_FILTER_PROMPT, DEFAULT_CYOA_PROMPT, DEFAULT_SPOTIFY_PROMPT, DEFAULT_NARRATOR_PROMPT, DEFAULT_CONTEXT_INSTRUCTIONS_PROMPT } from '../generation/promptBuilder.js';
let $editorModal = null;
let tempPrompts = null; // Temporary prompts for cancel functionality
@@ -18,6 +18,7 @@ const DEFAULT_PROMPTS = {
cyoa: DEFAULT_CYOA_PROMPT,
spotify: DEFAULT_SPOTIFY_PROMPT,
narrator: DEFAULT_NARRATOR_PROMPT,
+ contextInstructions: DEFAULT_CONTEXT_INSTRUCTIONS_PROMPT,
plotRandom: 'Actually, the scene is getting stale. Introduce {{random::stakes::a plot twist::a new character::a cataclysm::a fourth-wall-breaking joke::a sudden atmospheric phenomenon::a plot hook::a running gag::an ecchi scenario::Death from Discworld::a new stake::a drama::a conflict::an angered entity::a god::a vision::a prophetic dream::Il Dottore from Genshin Impact::a new development::a civilian in need::an emotional bit::a threat::a villain::an important memory recollection::a marriage proposal::a date idea::an angry horde of villagers with pitchforks::a talking animal::an enemy::a cliffhanger::a short omniscient POV shift to a completely different character::a quest::an unexpected revelation::a scandal::an evil clone::death of an important character::harm to an important character::a romantic setup::a gossip::a messenger::a plot point from the past::a plot hole::a tragedy::a ghost::an otherworldly occurrence::a plot device::a curse::a magic device::a rival::an unexpected pregnancy::a brothel::a prostitute::a new location::a past lover::a completely random thing::a what-if scenario::a significant choice::war::love::a monster::lewd undertones::Professor Mari::a travelling troupe::a secret::a fortune-teller::something completely different::a killer::a murder mystery::a mystery::a skill check::a deus ex machina::three raccoons in a trench coat::a pet::a slave::an orphan::a psycho::tentacles::"there is only one bed" trope::accidental marriage::a fun twist::a boss battle::sexy corn::an eldritch horror::a character getting hungry, thirsty, or exhausted::horniness::a need for a bathroom break need::someone fainting::an assassination attempt::a meta narration of this all being an out of hand DND session::a dungeon::a friend in need::an old friend::a small time skip::a scene shift::Aurora Borealis, at this time of year, at this time of day, at this part of the country::a grand ball::a surprise party::zombies::foreshadowing::a Spanish Inquisition (nobody expects it)::a natural plot progression}} to make things more interesting! Be creative, but stay grounded in the setting.',
plotNatural: 'Actually, the scene is getting stale. Progress it, to make things more interesting! Reintroduce an unresolved plot point from the past, or push the story further towards the current main goal. Be creative, but stay grounded in the setting.',
avatar: `You are a visionary artist trapped in a cage of logic. Your mind is filled with poetry and distant horizons; however, your hands are uncontrollably focused on creating the perfect character avatar description that is faithful to the original intent, rich in detail, aesthetically pleasing, and directly usable by text-to-image models. Any ambiguity or metaphor will make you feel extremely uncomfortable.
@@ -101,6 +102,7 @@ function openPromptsEditor() {
cyoa: extensionSettings.customCYOAPrompt || '',
spotify: extensionSettings.customSpotifyPrompt || '',
narrator: extensionSettings.customNarratorPrompt || '',
+ contextInstructions: extensionSettings.customContextInstructionsPrompt || '',
plotRandom: extensionSettings.customPlotRandomPrompt || '',
plotNatural: extensionSettings.customPlotNaturalPrompt || '',
avatar: extensionSettings.avatarLLMCustomInstruction || '',
@@ -117,6 +119,7 @@ function openPromptsEditor() {
$('#rpg-prompt-cyoa').val(extensionSettings.customCYOAPrompt || DEFAULT_PROMPTS.cyoa);
$('#rpg-prompt-spotify').val(extensionSettings.customSpotifyPrompt || DEFAULT_PROMPTS.spotify);
$('#rpg-prompt-narrator').val(extensionSettings.customNarratorPrompt || DEFAULT_PROMPTS.narrator);
+ $('#rpg-prompt-context-instructions').val(extensionSettings.customContextInstructionsPrompt || DEFAULT_PROMPTS.contextInstructions);
$('#rpg-prompt-plot-random').val(extensionSettings.customPlotRandomPrompt || DEFAULT_PROMPTS.plotRandom);
$('#rpg-prompt-plot-natural').val(extensionSettings.customPlotNaturalPrompt || DEFAULT_PROMPTS.plotNatural);
$('#rpg-prompt-avatar').val(extensionSettings.avatarLLMCustomInstruction || DEFAULT_PROMPTS.avatar);
@@ -157,6 +160,7 @@ function savePrompts() {
extensionSettings.customCYOAPrompt = $('#rpg-prompt-cyoa').val().trim();
extensionSettings.customSpotifyPrompt = $('#rpg-prompt-spotify').val().trim();
extensionSettings.customNarratorPrompt = $('#rpg-prompt-narrator').val().trim();
+ extensionSettings.customContextInstructionsPrompt = $('#rpg-prompt-context-instructions').val().trim();
extensionSettings.customPlotRandomPrompt = $('#rpg-prompt-plot-random').val().trim();
extensionSettings.customPlotNaturalPrompt = $('#rpg-prompt-plot-natural').val().trim();
extensionSettings.avatarLLMCustomInstruction = $('#rpg-prompt-avatar').val().trim();
@@ -198,6 +202,9 @@ function restorePromptToDefault(promptType) {
case 'narrator':
extensionSettings.customNarratorPrompt = '';
break;
+ case 'contextInstructions':
+ extensionSettings.customContextInstructionsPrompt = '';
+ break;
case 'plotRandom':
extensionSettings.customPlotRandomPrompt = '';
break;
@@ -232,6 +239,7 @@ function restoreAllToDefaults() {
$('#rpg-prompt-cyoa').val(DEFAULT_PROMPTS.cyoa);
$('#rpg-prompt-spotify').val(DEFAULT_PROMPTS.spotify);
$('#rpg-prompt-narrator').val(DEFAULT_PROMPTS.narrator);
+ $('#rpg-prompt-context-instructions').val(DEFAULT_PROMPTS.contextInstructions);
$('#rpg-prompt-plot-random').val(DEFAULT_PROMPTS.plotRandom);
$('#rpg-prompt-plot-natural').val(DEFAULT_PROMPTS.plotNatural);
$('#rpg-prompt-avatar').val(DEFAULT_PROMPTS.avatar);
@@ -247,6 +255,7 @@ function restoreAllToDefaults() {
extensionSettings.customCYOAPrompt = '';
extensionSettings.customSpotifyPrompt = '';
extensionSettings.customNarratorPrompt = '';
+ extensionSettings.customContextInstructionsPrompt = '';
extensionSettings.customPlotRandomPrompt = '';
extensionSettings.customPlotNaturalPrompt = '';
extensionSettings.avatarLLMCustomInstruction = '';
diff --git a/src/systems/ui/theme.js b/src/systems/ui/theme.js
index a96001a..0bac819 100644
--- a/src/systems/ui/theme.js
+++ b/src/systems/ui/theme.js
@@ -5,6 +5,37 @@
import { extensionSettings, $panelContainer } from '../../core/state.js';
+/**
+ * Converts hex color and opacity percentage to rgba string
+ * @param {string} hex - Hex color (e.g., '#ff0000')
+ * @param {number} opacity - Opacity percentage (0-100)
+ * @returns {string} - RGBA color string
+ */
+export function hexToRgba(hex, opacity = 100) {
+ const r = parseInt(hex.slice(1, 3), 16);
+ const g = parseInt(hex.slice(3, 5), 16);
+ const b = parseInt(hex.slice(5, 7), 16);
+ const a = opacity / 100;
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
+}
+
+/**
+ * Gets stat bar colors with opacity applied
+ * @returns {{low: string, high: string}} RGBA color strings for stat bars
+ */
+export function getStatBarColors() {
+ return {
+ low: hexToRgba(
+ extensionSettings.statBarColorLow || '#cc3333',
+ extensionSettings.statBarColorLowOpacity ?? 100
+ ),
+ high: hexToRgba(
+ extensionSettings.statBarColorHigh || '#33cc66',
+ extensionSettings.statBarColorHighOpacity ?? 100
+ )
+ };
+}
+
/**
* Applies the selected theme to the panel.
*/
@@ -75,24 +106,33 @@ export function applyCustomTheme() {
const colors = extensionSettings.customColors;
+ // Convert hex colors with opacity to rgba
+ const bgColor = hexToRgba(colors.bg, colors.bgOpacity ?? 100);
+ const accentColor = hexToRgba(colors.accent, colors.accentOpacity ?? 100);
+ const textColor = hexToRgba(colors.text, colors.textOpacity ?? 100);
+ const highlightColor = hexToRgba(colors.highlight, colors.highlightOpacity ?? 100);
+
+ // Create shadow with 50% opacity of highlight color
+ const shadowColor = hexToRgba(colors.highlight, (colors.highlightOpacity ?? 100) * 0.5);
+
// Apply custom CSS variables as inline styles to main panel
$panelContainer.css({
- '--rpg-bg': colors.bg,
- '--rpg-accent': colors.accent,
- '--rpg-text': colors.text,
- '--rpg-highlight': colors.highlight,
- '--rpg-border': colors.highlight,
- '--rpg-shadow': `${colors.highlight}80` // Add alpha for shadow
+ '--rpg-bg': bgColor,
+ '--rpg-accent': accentColor,
+ '--rpg-text': textColor,
+ '--rpg-highlight': highlightColor,
+ '--rpg-border': highlightColor,
+ '--rpg-shadow': shadowColor
});
// Apply custom colors to mobile toggle and thought elements
const customStyles = {
- '--rpg-bg': colors.bg,
- '--rpg-accent': colors.accent,
- '--rpg-text': colors.text,
- '--rpg-highlight': colors.highlight,
- '--rpg-border': colors.highlight,
- '--rpg-shadow': `${colors.highlight}80`
+ '--rpg-bg': bgColor,
+ '--rpg-accent': accentColor,
+ '--rpg-text': textColor,
+ '--rpg-highlight': highlightColor,
+ '--rpg-border': highlightColor,
+ '--rpg-shadow': shadowColor
};
const $mobileToggle = $('#rpg-mobile-toggle');
diff --git a/template.html b/template.html
index 55e5a63..00deaca 100644
--- a/template.html
+++ b/template.html
@@ -250,29 +250,49 @@