diff --git a/index.js b/index.js index 4a6c2ff..a1d70cf 100644 --- a/index.js +++ b/index.js @@ -131,7 +131,8 @@ import { } from './src/systems/ui/mobile.js'; import { setupDesktopTabs, - removeDesktopTabs + removeDesktopTabs, + updateStripWidgets } from './src/systems/ui/desktop.js'; // Feature modules @@ -726,6 +727,63 @@ async function initUI() { 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() { if (!extensionSettings.enabled) { // 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 $('#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-high').val(extensionSettings.statBarColorHigh); $('#rpg-theme-select').val(extensionSettings.theme); @@ -1106,8 +1176,9 @@ jQuery(async () => { // Load chat-specific data for current chat try { loadChatData(); - // Initialize FAB widgets with any loaded data + // Initialize FAB widgets and strip widgets with any loaded data updateFabWidgets(); + updateStripWidgets(); } catch (error) { console.error('[RPG Companion] Chat data load failed, using defaults:', error); } diff --git a/src/core/state.js b/src/core/state.js index b3244ae..178d213 100644 --- a/src/core/state.js +++ b/src/core/state.js @@ -84,6 +84,16 @@ export let extensionSettings = { stats: { enabled: true, position: 5 }, // All stats as compact numbers 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({ stats: [ { id: 'health', name: 'Health', value: 100 }, diff --git a/src/systems/integration/sillytavern.js b/src/systems/integration/sillytavern.js index f585d42..4756145 100644 --- a/src/systems/integration/sillytavern.js +++ b/src/systems/integration/sillytavern.js @@ -45,6 +45,7 @@ import { getSafeThumbnailUrl } from '../../utils/avatars.js'; // UI import { setFabLoadingState, updateFabWidgets } from '../ui/mobile.js'; +import { updateStripWidgets } from '../ui/desktop.js'; // Chapter checkpoint import { updateAllCheckpointIndicators } from '../ui/checkpointUI.js'; @@ -232,8 +233,9 @@ export async function onMessageReceived(data) { renderQuests(); renderMusicPlayer($musicPlayerContainer[0]); - // Update FAB widgets with newly parsed data + // Update FAB widgets and strip widgets with newly parsed data updateFabWidgets(); + updateStripWidgets(); // Then update the DOM to reflect the cleaned message // Using updateMessageBlock to perform macro substitutions + regex formatting @@ -266,9 +268,10 @@ export async function onMessageReceived(data) { if (extensionSettings.autoUpdate && isAwaitingNewMessage) { setTimeout(async () => { 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); updateFabWidgets(); + updateStripWidgets(); }, 500); } } @@ -294,6 +297,7 @@ export async function onMessageReceived(data) { // Stop FAB loading state and update widgets setFabLoadingState(false); updateFabWidgets(); + updateStripWidgets(); // Re-apply checkpoint in case SillyTavern unhid messages during generation await restoreCheckpointOnLoad(); @@ -332,8 +336,9 @@ export function onCharacterChanged() { renderQuests(); renderMusicPlayer($musicPlayerContainer[0]); - // Update FAB widgets with loaded data + // Update FAB widgets and strip widgets with loaded data updateFabWidgets(); + updateStripWidgets(); // Update chat thought overlays updateChatThoughts(); @@ -501,8 +506,9 @@ export function onMessageDeleted(messageIndex) { renderQuests(); renderMusicPlayer($musicPlayerContainer[0]); - // Update FAB widgets + // Update FAB widgets and strip widgets updateFabWidgets(); + updateStripWidgets(); // Update chat thought overlays (removes any remaining) updateChatThoughts(); @@ -555,8 +561,9 @@ export function onMessageDeleted(messageIndex) { renderQuests(); renderMusicPlayer($musicPlayerContainer[0]); - // Update FAB widgets + // Update FAB widgets and strip widgets updateFabWidgets(); + updateStripWidgets(); // Update chat thought overlays updateChatThoughts(); @@ -591,8 +598,9 @@ export function onMessageDeleted(messageIndex) { renderQuests(); renderMusicPlayer($musicPlayerContainer[0]); - // Update FAB widgets + // Update FAB widgets and strip widgets updateFabWidgets(); + updateStripWidgets(); // Update chat thought overlays updateChatThoughts(); diff --git a/src/systems/ui/desktop.js b/src/systems/ui/desktop.js index 40b3c20..3324c29 100644 --- a/src/systems/ui/desktop.js +++ b/src/systems/ui/desktop.js @@ -1,10 +1,273 @@ /** * 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 { 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 = $(`
+ ${abbr} + ${value} +
`); + $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 = $(`
+ ${key.toUpperCase()} + ${value} +
`); + $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. diff --git a/src/systems/ui/layout.js b/src/systems/ui/layout.js index 2b3e70c..8b92cf4 100644 --- a/src/systems/ui/layout.js +++ b/src/systems/ui/layout.js @@ -19,7 +19,7 @@ import { } from '../../core/state.js'; import { i18n } from '../../core/i18n.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. @@ -243,6 +243,9 @@ export function setupCollapseToggle() { } else if ($panel.hasClass('rpg-position-left')) { $icon.removeClass('fa-chevron-left').addClass('fa-chevron-right'); } + + // Update strip widgets when collapsing (they show in collapsed state) + updateStripWidgets(); } }); diff --git a/style.css b/style.css index 596438b..f846b05 100644 --- a/style.css +++ b/style.css @@ -10961,3 +10961,314 @@ body:has(.rpg-panel[data-theme="light"]) .rpg-fab-widget:hover { border-color: rgba(52, 152, 219, 0.6); 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; + } +} diff --git a/template.html b/template.html index 18a0710..d651658 100644 --- a/template.html +++ b/template.html @@ -4,6 +4,42 @@ + +
+ +
+ + +
+ +
+
+
+
+
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+
+
+ +
+
+
+
+
@@ -505,6 +541,56 @@
+ +
+

Desktop Collapsed Strip Widgets

+ + Show compact info widgets in the collapsed panel strip on desktop. Displays stats vertically without needing to expand the panel. + + + + + Shows widgets in the collapsed panel strip for quick access to stats. + + + +
+

Advanced