feat: Add desktop collapsed strip widgets

- Add new desktopStripWidgets settings in state.js with toggles for weather, clock, date, location, stats, and attributes
- Add strip widget container in template.html with animated clock face
- Add CSS styles for strip widgets with wider collapsed panel (5rem), vertical layout, and theme support
- Implement updateStripWidgets() in desktop.js to populate widgets from tracker data
- Wire up settings handlers in index.js for all strip widget toggles
- Call updateStripWidgets() on data updates in sillytavern.js integration
- Trigger widget update when panel is collapsed in layout.js

The strip widgets display compact stats/info in the collapsed panel strip on desktop, similar to mobile FAB widgets, eliminating the need to expand the panel to view basic data.
This commit is contained in:
tomt610
2026-01-13 00:08:00 +00:00
parent b18aaee0c0
commit 4644e0fd93
7 changed files with 763 additions and 11 deletions
+73 -2
View File
@@ -131,7 +131,8 @@ import {
} from './src/systems/ui/mobile.js'; } from './src/systems/ui/mobile.js';
import { import {
setupDesktopTabs, setupDesktopTabs,
removeDesktopTabs removeDesktopTabs,
updateStripWidgets
} from './src/systems/ui/desktop.js'; } from './src/systems/ui/desktop.js';
// Feature modules // Feature modules
@@ -726,6 +727,63 @@ async function initUI() {
updateFabWidgets(); updateFabWidgets();
}); });
// Desktop Strip Widget toggles
$('#rpg-toggle-strip-widgets-enabled').on('change', function() {
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
extensionSettings.desktopStripWidgets.enabled = $(this).prop('checked');
saveSettings();
updateStripWidgets();
$('#rpg-strip-widget-options').toggle(extensionSettings.desktopStripWidgets.enabled);
});
$('#rpg-toggle-strip-weather-icon').on('change', function() {
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
if (!extensionSettings.desktopStripWidgets.weatherIcon) extensionSettings.desktopStripWidgets.weatherIcon = {};
extensionSettings.desktopStripWidgets.weatherIcon.enabled = $(this).prop('checked');
saveSettings();
updateStripWidgets();
});
$('#rpg-toggle-strip-clock').on('change', function() {
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
if (!extensionSettings.desktopStripWidgets.clock) extensionSettings.desktopStripWidgets.clock = {};
extensionSettings.desktopStripWidgets.clock.enabled = $(this).prop('checked');
saveSettings();
updateStripWidgets();
});
$('#rpg-toggle-strip-date').on('change', function() {
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
if (!extensionSettings.desktopStripWidgets.date) extensionSettings.desktopStripWidgets.date = {};
extensionSettings.desktopStripWidgets.date.enabled = $(this).prop('checked');
saveSettings();
updateStripWidgets();
});
$('#rpg-toggle-strip-location').on('change', function() {
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
if (!extensionSettings.desktopStripWidgets.location) extensionSettings.desktopStripWidgets.location = {};
extensionSettings.desktopStripWidgets.location.enabled = $(this).prop('checked');
saveSettings();
updateStripWidgets();
});
$('#rpg-toggle-strip-stats').on('change', function() {
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
if (!extensionSettings.desktopStripWidgets.stats) extensionSettings.desktopStripWidgets.stats = {};
extensionSettings.desktopStripWidgets.stats.enabled = $(this).prop('checked');
saveSettings();
updateStripWidgets();
});
$('#rpg-toggle-strip-attributes').on('change', function() {
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
if (!extensionSettings.desktopStripWidgets.attributes) extensionSettings.desktopStripWidgets.attributes = {};
extensionSettings.desktopStripWidgets.attributes.enabled = $(this).prop('checked');
saveSettings();
updateStripWidgets();
});
$('#rpg-manual-update').on('click', async function() { $('#rpg-manual-update').on('click', async function() {
if (!extensionSettings.enabled) { if (!extensionSettings.enabled) {
// console.log('[RPG Companion] Extension is disabled. Please enable it in the Extensions tab.'); // console.log('[RPG Companion] Extension is disabled. Please enable it in the Extensions tab.');
@@ -963,6 +1021,18 @@ async function initUI() {
// Toggle visibility of widget options based on master toggle // Toggle visibility of widget options based on master toggle
$('#rpg-fab-widget-options').toggle(fabWidgets.enabled || false); $('#rpg-fab-widget-options').toggle(fabWidgets.enabled || false);
// Initialize Desktop Strip Widget checkboxes
const stripWidgets = extensionSettings.desktopStripWidgets || {};
$('#rpg-toggle-strip-widgets-enabled').prop('checked', stripWidgets.enabled || false);
$('#rpg-toggle-strip-weather-icon').prop('checked', stripWidgets.weatherIcon?.enabled ?? true);
$('#rpg-toggle-strip-clock').prop('checked', stripWidgets.clock?.enabled ?? true);
$('#rpg-toggle-strip-date').prop('checked', stripWidgets.date?.enabled ?? true);
$('#rpg-toggle-strip-location').prop('checked', stripWidgets.location?.enabled ?? true);
$('#rpg-toggle-strip-stats').prop('checked', stripWidgets.stats?.enabled ?? true);
$('#rpg-toggle-strip-attributes').prop('checked', stripWidgets.attributes?.enabled ?? true);
// Toggle visibility of strip widget options based on master toggle
$('#rpg-strip-widget-options').toggle(stripWidgets.enabled || false);
$('#rpg-stat-bar-color-low').val(extensionSettings.statBarColorLow); $('#rpg-stat-bar-color-low').val(extensionSettings.statBarColorLow);
$('#rpg-stat-bar-color-high').val(extensionSettings.statBarColorHigh); $('#rpg-stat-bar-color-high').val(extensionSettings.statBarColorHigh);
$('#rpg-theme-select').val(extensionSettings.theme); $('#rpg-theme-select').val(extensionSettings.theme);
@@ -1106,8 +1176,9 @@ jQuery(async () => {
// Load chat-specific data for current chat // Load chat-specific data for current chat
try { try {
loadChatData(); loadChatData();
// Initialize FAB widgets with any loaded data // Initialize FAB widgets and strip widgets with any loaded data
updateFabWidgets(); updateFabWidgets();
updateStripWidgets();
} catch (error) { } catch (error) {
console.error('[RPG Companion] Chat data load failed, using defaults:', error); console.error('[RPG Companion] Chat data load failed, using defaults:', error);
} }
+10
View File
@@ -84,6 +84,16 @@ export let extensionSettings = {
stats: { enabled: true, position: 5 }, // All stats as compact numbers stats: { enabled: true, position: 5 }, // All stats as compact numbers
attributes: { enabled: true, position: 6 } // Compact RPG attributes display attributes: { enabled: true, position: 6 } // Compact RPG attributes display
}, },
// Desktop strip widget display options (shown in collapsed panel strip)
desktopStripWidgets: {
enabled: false, // Master toggle for strip widgets (disabled by default)
weatherIcon: { enabled: true }, // Weather emoji (☀️, 🌧️, etc.)
clock: { enabled: true }, // Current time display
date: { enabled: true }, // Date display
location: { enabled: true }, // Location name
stats: { enabled: true }, // All stats as compact numbers
attributes: { enabled: true } // Compact RPG attributes display
},
userStats: JSON.stringify({ userStats: JSON.stringify({
stats: [ stats: [
{ id: 'health', name: 'Health', value: 100 }, { id: 'health', name: 'Health', value: 100 },
+14 -6
View File
@@ -45,6 +45,7 @@ import { getSafeThumbnailUrl } from '../../utils/avatars.js';
// UI // UI
import { setFabLoadingState, updateFabWidgets } from '../ui/mobile.js'; import { setFabLoadingState, updateFabWidgets } from '../ui/mobile.js';
import { updateStripWidgets } from '../ui/desktop.js';
// Chapter checkpoint // Chapter checkpoint
import { updateAllCheckpointIndicators } from '../ui/checkpointUI.js'; import { updateAllCheckpointIndicators } from '../ui/checkpointUI.js';
@@ -232,8 +233,9 @@ export async function onMessageReceived(data) {
renderQuests(); renderQuests();
renderMusicPlayer($musicPlayerContainer[0]); renderMusicPlayer($musicPlayerContainer[0]);
// Update FAB widgets with newly parsed data // Update FAB widgets and strip widgets with newly parsed data
updateFabWidgets(); updateFabWidgets();
updateStripWidgets();
// Then update the DOM to reflect the cleaned message // Then update the DOM to reflect the cleaned message
// Using updateMessageBlock to perform macro substitutions + regex formatting // Using updateMessageBlock to perform macro substitutions + regex formatting
@@ -266,9 +268,10 @@ export async function onMessageReceived(data) {
if (extensionSettings.autoUpdate && isAwaitingNewMessage) { if (extensionSettings.autoUpdate && isAwaitingNewMessage) {
setTimeout(async () => { setTimeout(async () => {
await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory); await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory);
// Update FAB widgets after separate/external mode update completes // Update FAB widgets and strip widgets after separate/external mode update completes
setFabLoadingState(false); setFabLoadingState(false);
updateFabWidgets(); updateFabWidgets();
updateStripWidgets();
}, 500); }, 500);
} }
} }
@@ -294,6 +297,7 @@ export async function onMessageReceived(data) {
// Stop FAB loading state and update widgets // Stop FAB loading state and update widgets
setFabLoadingState(false); setFabLoadingState(false);
updateFabWidgets(); updateFabWidgets();
updateStripWidgets();
// Re-apply checkpoint in case SillyTavern unhid messages during generation // Re-apply checkpoint in case SillyTavern unhid messages during generation
await restoreCheckpointOnLoad(); await restoreCheckpointOnLoad();
@@ -332,8 +336,9 @@ export function onCharacterChanged() {
renderQuests(); renderQuests();
renderMusicPlayer($musicPlayerContainer[0]); renderMusicPlayer($musicPlayerContainer[0]);
// Update FAB widgets with loaded data // Update FAB widgets and strip widgets with loaded data
updateFabWidgets(); updateFabWidgets();
updateStripWidgets();
// Update chat thought overlays // Update chat thought overlays
updateChatThoughts(); updateChatThoughts();
@@ -501,8 +506,9 @@ export function onMessageDeleted(messageIndex) {
renderQuests(); renderQuests();
renderMusicPlayer($musicPlayerContainer[0]); renderMusicPlayer($musicPlayerContainer[0]);
// Update FAB widgets // Update FAB widgets and strip widgets
updateFabWidgets(); updateFabWidgets();
updateStripWidgets();
// Update chat thought overlays (removes any remaining) // Update chat thought overlays (removes any remaining)
updateChatThoughts(); updateChatThoughts();
@@ -555,8 +561,9 @@ export function onMessageDeleted(messageIndex) {
renderQuests(); renderQuests();
renderMusicPlayer($musicPlayerContainer[0]); renderMusicPlayer($musicPlayerContainer[0]);
// Update FAB widgets // Update FAB widgets and strip widgets
updateFabWidgets(); updateFabWidgets();
updateStripWidgets();
// Update chat thought overlays // Update chat thought overlays
updateChatThoughts(); updateChatThoughts();
@@ -591,8 +598,9 @@ export function onMessageDeleted(messageIndex) {
renderQuests(); renderQuests();
renderMusicPlayer($musicPlayerContainer[0]); renderMusicPlayer($musicPlayerContainer[0]);
// Update FAB widgets // Update FAB widgets and strip widgets
updateFabWidgets(); updateFabWidgets();
updateStripWidgets();
// Update chat thought overlays // Update chat thought overlays
updateChatThoughts(); updateChatThoughts();
+265 -2
View File
@@ -1,10 +1,273 @@
/** /**
* Desktop UI Module * Desktop UI Module
* Handles desktop-specific UI functionality: tab navigation * Handles desktop-specific UI functionality: tab navigation and strip widgets
*/ */
import { i18n } from '../../core/i18n.js'; import { i18n } from '../../core/i18n.js';
import { extensionSettings } from '../../core/state.js'; import { extensionSettings, lastGeneratedData, committedTrackerData } from '../../core/state.js';
/**
* Helper to parse time string and calculate clock hand angles
*/
function parseTimeForClock(timeStr) {
const timeMatch = timeStr.match(/(\d+):(\d+)/);
if (timeMatch) {
const hours = parseInt(timeMatch[1]);
const minutes = parseInt(timeMatch[2]);
const hourAngle = (hours % 12) * 30 + minutes * 0.5; // 30° per hour + 0.5° per minute
const minuteAngle = minutes * 6; // 6° per minute
return { hourAngle, minuteAngle };
}
return { hourAngle: 0, minuteAngle: 0 };
}
/**
* Updates the desktop strip widgets display based on current tracker data and settings.
* Strip widgets are shown vertically in the collapsed panel strip.
*/
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
const widgetSettings = extensionSettings.desktopStripWidgets;
if (!widgetSettings || !widgetSettings.enabled) {
$panel.removeClass('rpg-strip-widgets-enabled');
$container.find('.rpg-strip-widget').removeClass('rpg-strip-widget-visible');
return;
}
// Add enabled class to panel for CSS styling (wider collapsed width)
$panel.addClass('rpg-strip-widgets-enabled');
// Get tracker data - use imported state directly
const infoBox = lastGeneratedData?.infoBox || committedTrackerData?.infoBox;
// Parse infoBox if it's a string
let infoData = null;
if (infoBox) {
try {
infoData = typeof infoBox === 'string' ? JSON.parse(infoBox) : infoBox;
} catch (e) {
console.warn('[RPG Strip Widgets] Failed to parse infoBox:', e);
}
}
// Weather Icon Widget (with description)
const $weatherWidget = $container.find('.rpg-strip-widget-weather');
if (widgetSettings.weatherIcon?.enabled && infoData?.weather?.emoji) {
$weatherWidget.find('.rpg-strip-widget-icon').text(infoData.weather.emoji);
// Show weather description truncated
const forecast = infoData.weather.forecast || '';
const displayForecast = forecast.length > 12 ? forecast.substring(0, 10) + '…' : forecast;
$weatherWidget.find('.rpg-strip-widget-desc').text(displayForecast);
$weatherWidget.attr('title', forecast || 'Weather');
$weatherWidget.addClass('rpg-strip-widget-visible');
} else {
$weatherWidget.removeClass('rpg-strip-widget-visible');
}
// Clock Widget with animated face
const $clockWidget = $container.find('.rpg-strip-widget-clock');
if (widgetSettings.clock?.enabled && infoData?.time) {
const timeStr = infoData.time.end || infoData.time.value || infoData.time.start || '';
if (timeStr) {
// Update clock hands
const { hourAngle, minuteAngle } = parseTimeForClock(timeStr);
$clockWidget.find('.rpg-strip-clock-hour').css('transform', `rotate(${hourAngle}deg)`);
$clockWidget.find('.rpg-strip-clock-minute').css('transform', `rotate(${minuteAngle}deg)`);
$clockWidget.find('.rpg-strip-widget-value').text(timeStr);
$clockWidget.attr('title', `Time: ${timeStr}`);
$clockWidget.addClass('rpg-strip-widget-visible');
} else {
$clockWidget.removeClass('rpg-strip-widget-visible');
}
} else {
$clockWidget.removeClass('rpg-strip-widget-visible');
}
// Date Widget
const $dateWidget = $container.find('.rpg-strip-widget-date');
if (widgetSettings.date?.enabled && infoData?.date?.value) {
const dateVal = infoData.date.value;
// Truncate long dates for display
const displayDate = dateVal.length > 20 ? dateVal.substring(0, 18) + '…' : dateVal;
$dateWidget.find('.rpg-strip-widget-value').text(displayDate);
$dateWidget.attr('title', dateVal);
$dateWidget.addClass('rpg-strip-widget-visible');
} else {
$dateWidget.removeClass('rpg-strip-widget-visible');
}
// Location Widget
const $locationWidget = $container.find('.rpg-strip-widget-location');
if (widgetSettings.location?.enabled && infoData?.location?.value) {
const loc = infoData.location.value;
// Truncate long locations for display
const displayLoc = loc.length > 15 ? loc.substring(0, 13) + '…' : loc;
$locationWidget.find('.rpg-strip-widget-value').text(displayLoc);
$locationWidget.attr('title', loc);
$locationWidget.addClass('rpg-strip-widget-visible');
} else {
$locationWidget.removeClass('rpg-strip-widget-visible');
}
// Stats Widget - get from lastGeneratedData or committedTrackerData first, fallback to extensionSettings
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) {
try {
const parsedStats = typeof userStatsData === 'string' ? JSON.parse(userStatsData) : userStatsData;
if (parsedStats?.stats) {
allStats = parsedStats.stats;
}
} catch (e) {
console.warn('[RPG Strip Widgets] Failed to parse tracker userStats:', e);
}
}
// Fallback to extensionSettings.userStats
if (allStats.length === 0 && extensionSettings.userStats) {
try {
const userStatsJson = extensionSettings.userStats;
const parsedUserStats = typeof userStatsJson === 'string' ? JSON.parse(userStatsJson) : userStatsJson;
if (parsedUserStats?.stats) {
allStats = parsedUserStats.stats;
}
} catch (e) {
console.warn('[RPG Strip Widgets] Failed to parse extensionSettings.userStats:', e);
}
}
if (allStats.length > 0) {
// Get enabled stats from trackerConfig
const configuredStats = extensionSettings.trackerConfig?.userStats?.customStats || [];
const enabledStatMap = new Map();
configuredStats.forEach(s => {
if (s.enabled !== false) {
enabledStatMap.set(s.id?.toLowerCase(), true);
enabledStatMap.set(s.name?.toLowerCase(), true);
}
});
const $statsList = $statsWidget.find('.rpg-strip-stats-list');
$statsList.empty();
allStats.forEach(stat => {
// Filter by config if available - but if no config, show all
if (configuredStats.length > 0) {
const statId = stat.id?.toLowerCase();
const statName = stat.name?.toLowerCase();
if (!enabledStatMap.has(statId) && !enabledStatMap.has(statName)) return;
}
const value = typeof stat.value === 'number' ? stat.value : parseInt(stat.value) || 0;
const color = getStatColor(value);
const abbr = stat.name.substring(0, 3).toUpperCase();
const $item = $(`<div class="rpg-strip-stat-item" title="${stat.name}: ${value}">
<span class="rpg-strip-stat-name">${abbr}</span>
<span class="rpg-strip-stat-value" style="color: ${color};">${value}</span>
</div>`);
$statsList.append($item);
});
if ($statsList.children().length > 0) {
$statsWidget.addClass('rpg-strip-widget-visible');
} else {
$statsWidget.removeClass('rpg-strip-widget-visible');
}
} else {
$statsWidget.removeClass('rpg-strip-widget-visible');
}
} else {
$statsWidget.removeClass('rpg-strip-widget-visible');
}
// Attributes Widget
const $attrsWidget = $container.find('.rpg-strip-widget-attributes');
if (widgetSettings.attributes?.enabled) {
const showRPGAttributes = extensionSettings.trackerConfig?.userStats?.showRPGAttributes !== false;
if (showRPGAttributes && extensionSettings.classicStats) {
// Get enabled attributes from trackerConfig
const configuredAttrs = extensionSettings.trackerConfig?.userStats?.rpgAttributes || [];
const enabledAttrIds = configuredAttrs.filter(a => a.enabled !== false).map(a => a.id);
const attrs = extensionSettings.classicStats;
const $attrsGrid = $attrsWidget.find('.rpg-strip-attributes-grid');
$attrsGrid.empty();
Object.entries(attrs).forEach(([key, value]) => {
// Filter by config if available
if (enabledAttrIds.length > 0 && !enabledAttrIds.includes(key.toLowerCase())) {
return;
}
const $item = $(`<div class="rpg-strip-attr-item" title="${key.toUpperCase()}: ${value}">
<span class="rpg-strip-attr-name">${key.toUpperCase()}</span>
<span class="rpg-strip-attr-value">${value}</span>
</div>`);
$attrsGrid.append($item);
});
if ($attrsGrid.children().length > 0) {
$attrsWidget.addClass('rpg-strip-widget-visible');
} else {
$attrsWidget.removeClass('rpg-strip-widget-visible');
}
} else {
$attrsWidget.removeClass('rpg-strip-widget-visible');
}
} else {
$attrsWidget.removeClass('rpg-strip-widget-visible');
}
}
/**
* Gets a color interpolated between low and high based on stat value (0-100).
* @param {number} value - The stat value (0-100)
* @returns {string} CSS color value
*/
function getStatColor(value) {
const lowColor = extensionSettings.statBarColorLow || '#cc3333';
const highColor = extensionSettings.statBarColorHigh || '#33cc66';
// Simple linear interpolation between low and high colors
const percent = Math.min(100, Math.max(0, value)) / 100;
// Parse colors
const lowRGB = hexToRgb(lowColor);
const highRGB = hexToRgb(highColor);
if (!lowRGB || !highRGB) return value > 50 ? highColor : lowColor;
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);
return `rgb(${r}, ${g}, ${b})`;
}
/**
* Converts a hex color to RGB object.
* @param {string} hex - Hex color string (e.g., "#cc3333")
* @returns {{r: number, g: number, b: number}|null}
*/
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
/** /**
* Sets up desktop tab navigation for organizing content. * Sets up desktop tab navigation for organizing content.
+4 -1
View File
@@ -19,7 +19,7 @@ import {
} from '../../core/state.js'; } from '../../core/state.js';
import { i18n } from '../../core/i18n.js'; import { i18n } from '../../core/i18n.js';
import { setupMobileTabs, removeMobileTabs } from './mobile.js'; import { setupMobileTabs, removeMobileTabs } from './mobile.js';
import { setupDesktopTabs, removeDesktopTabs } from './desktop.js'; import { setupDesktopTabs, removeDesktopTabs, updateStripWidgets } from './desktop.js';
/** /**
* Toggles the visibility of plot buttons based on settings. * Toggles the visibility of plot buttons based on settings.
@@ -243,6 +243,9 @@ export function setupCollapseToggle() {
} else if ($panel.hasClass('rpg-position-left')) { } else if ($panel.hasClass('rpg-position-left')) {
$icon.removeClass('fa-chevron-left').addClass('fa-chevron-right'); $icon.removeClass('fa-chevron-left').addClass('fa-chevron-right');
} }
// Update strip widgets when collapsing (they show in collapsed state)
updateStripWidgets();
} }
}); });
+311
View File
@@ -10961,3 +10961,314 @@ body:has(.rpg-panel[data-theme="light"]) .rpg-fab-widget:hover {
border-color: rgba(52, 152, 219, 0.6); border-color: rgba(52, 152, 219, 0.6);
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.15); box-shadow: 0 3px 12px rgba(0, 0, 0, 0.15);
} }
/* ============================================
DESKTOP STRIP WIDGETS (Collapsed Panel)
Shows compact widgets when panel is collapsed
============================================ */
/* Strip Widget Container - hidden by default, shown when collapsed with strip widgets enabled */
.rpg-strip-widget-container {
display: none;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 8px 4px;
padding-top: 40px;
width: 100%;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: thin;
scrollbar-color: var(--rpg-border) transparent;
}
/* Show strip widgets when panel is collapsed and strip widgets are enabled */
.rpg-panel.rpg-collapsed.rpg-strip-widgets-enabled .rpg-strip-widget-container {
display: flex;
}
/* Wider collapsed state when strip widgets are enabled */
.rpg-panel.rpg-collapsed.rpg-strip-widgets-enabled {
max-width: 5rem !important;
min-width: 5rem !important;
}
/* Adjust collapse button position for wider strip */
.rpg-panel.rpg-collapsed.rpg-strip-widgets-enabled .rpg-collapse-toggle {
left: -2rem;
}
.rpg-panel.rpg-position-left.rpg-collapsed.rpg-strip-widgets-enabled .rpg-collapse-toggle {
left: auto;
right: -2rem;
}
/* Fix positioning for wider strip */
.rpg-panel.rpg-position-right.rpg-collapsed.rpg-strip-widgets-enabled {
left: calc(100vw - 5rem);
}
.rpg-panel.rpg-position-left.rpg-collapsed.rpg-strip-widgets-enabled {
right: calc(100vw - 5rem);
}
/* Base Strip Widget Styles */
.rpg-strip-widget {
display: none; /* Hidden by default, shown via JS when enabled */
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(20, 20, 35, 0.85);
border: 1px solid var(--rpg-border, rgba(100, 150, 255, 0.3));
border-radius: 6px;
padding: 4px;
font-size: 10px;
color: var(--rpg-text, #eaeaea);
backdrop-filter: blur(8px);
width: calc(100% - 8px);
min-width: 0;
transition: all 0.2s ease;
}
.rpg-strip-widget.rpg-strip-widget-visible {
display: flex;
}
.rpg-strip-widget:hover {
border-color: var(--rpg-highlight, #4a90e2);
box-shadow: 0 0 8px rgba(100, 150, 255, 0.3);
}
/* Weather Icon Widget */
.rpg-strip-widget-weather {
font-size: 18px;
padding: 6px 4px;
gap: 2px;
}
.rpg-strip-widget-weather .rpg-strip-widget-icon {
line-height: 1;
}
.rpg-strip-widget-weather .rpg-strip-widget-desc {
font-size: 8px;
opacity: 0.8;
text-align: center;
word-break: break-word;
line-height: 1.2;
}
/* Clock Widget with animated face */
.rpg-strip-widget-clock {
font-family: 'Roboto Mono', 'Consolas', monospace;
font-size: 10px;
gap: 4px;
padding: 6px 4px;
}
/* Mini animated clock face for strip */
.rpg-strip-clock-face {
position: relative;
width: 28px;
height: 28px;
border: 2px solid var(--rpg-border, #4a7ba7);
border-radius: 50%;
background: var(--rpg-accent, rgba(22, 33, 62, 0.9));
}
.rpg-strip-clock-hour {
position: absolute;
width: 2px;
height: 8px;
background: var(--rpg-text, #eaeaea);
left: 50%;
bottom: 50%;
margin-left: -1px;
transform-origin: bottom center;
border-radius: 1px;
}
.rpg-strip-clock-minute {
position: absolute;
width: 1.5px;
height: 10px;
background: var(--rpg-highlight, #4a90e2);
left: 50%;
bottom: 50%;
margin-left: -0.75px;
transform-origin: bottom center;
border-radius: 1px;
}
.rpg-strip-clock-center {
position: absolute;
width: 4px;
height: 4px;
background: var(--rpg-highlight, #4a90e2);
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.rpg-strip-widget-clock .rpg-strip-widget-value {
font-weight: 600;
letter-spacing: 0.5px;
font-size: 10px;
}
/* Date Widget */
.rpg-strip-widget-date {
font-size: 9px;
text-align: center;
gap: 2px;
}
.rpg-strip-widget-date i {
font-size: 11px;
opacity: 0.7;
}
.rpg-strip-widget-date .rpg-strip-widget-value {
word-break: break-word;
line-height: 1.2;
}
/* Location Widget */
.rpg-strip-widget-location {
font-size: 9px;
text-align: center;
gap: 2px;
}
.rpg-strip-widget-location i {
font-size: 11px;
color: var(--rpg-highlight, #4a90e2);
}
.rpg-strip-widget-location .rpg-strip-widget-value {
word-break: break-word;
line-height: 1.2;
max-height: 3em;
overflow: hidden;
text-overflow: ellipsis;
}
/* Stats Widget - Vertical list */
.rpg-strip-widget-stats {
padding: 4px 2px;
}
.rpg-strip-stats-list {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
width: 100%;
}
.rpg-strip-stat-item {
display: flex;
flex-direction: column;
align-items: center;
font-family: 'Roboto Mono', 'Consolas', monospace;
font-size: 9px;
width: 100%;
}
.rpg-strip-stat-name {
font-size: 7px;
opacity: 0.6;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.rpg-strip-stat-value {
font-size: 11px;
font-weight: 700;
}
/* Attributes Widget - 2-column grid */
.rpg-strip-widget-attributes {
padding: 4px 2px;
}
.rpg-strip-attributes-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 2px 4px;
width: 100%;
}
.rpg-strip-attr-item {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.rpg-strip-attr-name {
font-size: 7px;
opacity: 0.6;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.rpg-strip-attr-value {
font-size: 10px;
font-weight: 700;
color: var(--rpg-highlight, #6af);
font-family: 'Roboto Mono', 'Consolas', monospace;
}
/* ============================================
STRIP WIDGET THEME VARIATIONS
============================================ */
/* Sci-Fi Theme */
body:has(.rpg-panel[data-theme="sci-fi"]) .rpg-strip-widget {
background: rgba(10, 14, 39, 0.9);
border-color: rgba(139, 0, 255, 0.5);
color: #00fff9;
}
/* Fantasy Theme */
body:has(.rpg-panel[data-theme="fantasy"]) .rpg-strip-widget {
background: rgba(43, 24, 16, 0.9);
border-color: rgba(139, 105, 20, 0.6);
color: #f4e8d0;
}
/* Cyberpunk Theme */
body:has(.rpg-panel[data-theme="cyberpunk"]) .rpg-strip-widget {
background: rgba(15, 5, 25, 0.9);
border-color: rgba(255, 0, 255, 0.4);
color: #00ffff;
}
/* Dark Theme */
body:has(.rpg-panel[data-theme="dark"]) .rpg-strip-widget {
background: rgba(25, 25, 35, 0.9);
border-color: rgba(70, 70, 90, 0.6);
color: #e0e0e0;
}
/* Light Theme */
body:has(.rpg-panel[data-theme="light"]) .rpg-strip-widget {
background: rgba(255, 255, 255, 0.95);
border-color: rgba(220, 220, 230, 0.8);
color: #2c3e50;
}
/* Hide strip widgets on mobile */
@media (max-width: 768px) {
.rpg-strip-widget-container {
display: none !important;
}
.rpg-panel.rpg-collapsed.rpg-strip-widgets-enabled {
max-width: 2.5rem !important;
min-width: 2.5rem !important;
}
}
+86
View File
@@ -4,6 +4,42 @@
<i class="fa-solid fa-chevron-right"></i> <i class="fa-solid fa-chevron-right"></i>
</button> </button>
<!-- Strip Widget Container (shown when collapsed with strip widgets enabled) -->
<div id="rpg-strip-widget-container" class="rpg-strip-widget-container">
<!-- Weather Icon Widget -->
<div class="rpg-strip-widget rpg-strip-widget-weather" data-widget="weatherIcon">
<span class="rpg-strip-widget-icon"></span>
<span class="rpg-strip-widget-desc"></span>
</div>
<!-- Clock Widget with animated face -->
<div class="rpg-strip-widget rpg-strip-widget-clock" data-widget="clock">
<div class="rpg-strip-clock-face">
<div class="rpg-strip-clock-hour"></div>
<div class="rpg-strip-clock-minute"></div>
<div class="rpg-strip-clock-center"></div>
</div>
<span class="rpg-strip-widget-value"></span>
</div>
<!-- Date Widget -->
<div class="rpg-strip-widget rpg-strip-widget-date" data-widget="date">
<i class="fa-solid fa-calendar"></i>
<span class="rpg-strip-widget-value"></span>
</div>
<!-- Location Widget -->
<div class="rpg-strip-widget rpg-strip-widget-location" data-widget="location">
<i class="fa-solid fa-location-dot"></i>
<span class="rpg-strip-widget-value"></span>
</div>
<!-- Stats Widget -->
<div class="rpg-strip-widget rpg-strip-widget-stats" data-widget="stats">
<div class="rpg-strip-stats-list"></div>
</div>
<!-- Attributes Widget -->
<div class="rpg-strip-widget rpg-strip-widget-attributes" data-widget="attributes">
<div class="rpg-strip-attributes-grid"></div>
</div>
</div>
<!-- Main Game Panel --> <!-- Main Game Panel -->
<div class="rpg-game-container"> <div class="rpg-game-container">
<!-- Header with Controls --> <!-- Header with Controls -->
@@ -505,6 +541,56 @@
</div> </div>
</div> </div>
<!-- Desktop Strip Widgets Section -->
<div class="rpg-settings-group">
<h4 data-i18n-key="template.settingsModal.desktopStripTitle"><i class="fa-solid fa-bars-staggered" aria-hidden="true"></i> Desktop Collapsed Strip Widgets</h4>
<small class="notes" style="display: block; margin-bottom: 10px;"
data-i18n-key="template.settingsModal.desktopStripNote">
Show compact info widgets in the collapsed panel strip on desktop. Displays stats vertically without needing to expand the panel.
</small>
<label class="checkbox_label">
<input type="checkbox" id="rpg-toggle-strip-widgets-enabled" />
<span data-i18n-key="template.settingsModal.desktopStrip.enabled">Enable Strip Widgets</span>
</label>
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;"
data-i18n-key="template.settingsModal.desktopStrip.enabledNote">
Shows widgets in the collapsed panel strip for quick access to stats.
</small>
<div id="rpg-strip-widget-options" style="margin-left: 10px; border-left: 2px solid var(--SmartThemeBorderColor); padding-left: 10px; margin-top: 8px; display: none;">
<label class="checkbox_label">
<input type="checkbox" id="rpg-toggle-strip-weather-icon" />
<span data-i18n-key="template.settingsModal.desktopStrip.weatherIcon">Weather Icon</span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="rpg-toggle-strip-clock" />
<span data-i18n-key="template.settingsModal.desktopStrip.clock">Time/Clock</span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="rpg-toggle-strip-date" />
<span data-i18n-key="template.settingsModal.desktopStrip.date">Date</span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="rpg-toggle-strip-location" />
<span data-i18n-key="template.settingsModal.desktopStrip.location">Location</span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="rpg-toggle-strip-stats" />
<span data-i18n-key="template.settingsModal.desktopStrip.stats">Stats (Health, Energy, etc.)</span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="rpg-toggle-strip-attributes" />
<span data-i18n-key="template.settingsModal.desktopStrip.attributes">RPG Attributes (STR, DEX, etc.)</span>
</label>
</div>
</div>
<div class="rpg-settings-group"> <div class="rpg-settings-group">
<h4 data-i18n-key="template.settingsModal.advancedTitle"><i class="fa-solid fa-sliders" <h4 data-i18n-key="template.settingsModal.advancedTitle"><i class="fa-solid fa-sliders"
aria-hidden="true"></i> Advanced</h4> aria-hidden="true"></i> Advanced</h4>