feat(dashboard): replace emojis with Font Awesome, add theme support, and styled dialogs

This commit implements three major improvements to the dashboard system:

1. **Font Awesome Icons for Tabs**
   - Replace emoji tab icons (📊, 🌍, 🎒) with Font Awesome classes
   - Update defaultLayout.js with fa-solid icon classes
   - Add automatic migration for existing saved dashboards with emoji icons
   - Implement migrateEmojiIcons() to convert old emoji icons on load
   - Update fallback icons throughout the system

2. **Custom Theme Support for Dashboard**
   - Replace all --SmartTheme* variables with --rpg-* variables
   - Ensure custom themes (sci-fi, fantasy, cyberpunk) apply to dashboard
   - Update CSS for tabs, buttons, dropdowns, modals, and widget cards
   - Dashboard now respects extension themes instead of main SillyTavern theme

3. **Styled Confirmation Dialogs**
   - Create confirmDialog.js with showConfirmDialog() and showAlertDialog()
   - Support three variants: danger (red), warning (yellow), info (blue)
   - Add keyboard navigation (Enter/Escape) and accessibility features
   - Replace all native confirm() and alert() calls with styled dialogs
   - Add confirmation dialog modal to dashboardTemplate.html

Files Modified:
- src/systems/dashboard/confirmDialog.js (NEW)
- src/systems/dashboard/dashboardManager.js
- src/systems/dashboard/defaultLayout.js
- src/systems/dashboard/tabManager.js
- src/systems/dashboard/dashboardIntegration.js
- src/systems/dashboard/editModeManager.js
- src/systems/dashboard/widgets/inventoryWidget.js
- src/systems/dashboard/dashboardTemplate.html
- style.css
This commit is contained in:
Lucas 'Paperboy' Rose-Winters
2025-10-27 20:41:36 +11:00
parent c39d348a81
commit 7628bb84c1
9 changed files with 466 additions and 54 deletions
+217
View File
@@ -0,0 +1,217 @@
/**
* Confirmation Dialog System
*
* Provides styled confirmation and alert dialogs to replace native browser popups.
* Supports three variants: danger (red), warning (yellow), and info (blue).
*/
/**
* Show a confirmation dialog
* @param {Object} options - Dialog options
* @param {string} options.title - Dialog title
* @param {string} options.message - Dialog message
* @param {string} [options.variant='danger'] - Dialog variant: 'danger', 'warning', or 'info'
* @param {string} [options.confirmText='Confirm'] - Confirm button text
* @param {string} [options.cancelText='Cancel'] - Cancel button text
* @param {Function} [options.onConfirm] - Callback when confirmed
* @param {Function} [options.onCancel] - Callback when cancelled
* @returns {Promise<boolean>} Resolves to true if confirmed, false if cancelled
*/
export function showConfirmDialog(options) {
return new Promise((resolve) => {
const {
title = 'Confirm Action',
message = 'Are you sure?',
variant = 'danger',
confirmText = 'Confirm',
cancelText = 'Cancel',
onConfirm = null,
onCancel = null
} = options;
// Get modal elements
const modal = document.getElementById('rpg-confirm-dialog');
const modalContent = modal.querySelector('.rpg-confirm-content');
const icon = document.getElementById('rpg-confirm-icon');
const titleEl = document.getElementById('rpg-confirm-title');
const messageEl = document.getElementById('rpg-confirm-message');
const confirmBtn = document.getElementById('rpg-confirm-confirm');
const cancelBtn = document.getElementById('rpg-confirm-cancel');
const closeBtn = modal.querySelector('.rpg-confirm-close');
if (!modal) {
console.error('[ConfirmDialog] Modal not found');
return resolve(false);
}
// Set icon based on variant
const iconMap = {
danger: 'fa-solid fa-triangle-exclamation',
warning: 'fa-solid fa-circle-exclamation',
info: 'fa-solid fa-circle-info'
};
icon.className = `rpg-confirm-icon ${iconMap[variant] || iconMap.danger}`;
// Set variant class on modal content
modalContent.className = `rpg-modal-content rpg-confirm-content rpg-confirm-${variant}`;
// Set content
titleEl.textContent = title;
messageEl.textContent = message;
confirmBtn.textContent = confirmText;
cancelBtn.textContent = cancelText;
// Show modal
modal.style.display = 'flex';
// Handle confirm
const handleConfirm = () => {
modal.style.display = 'none';
cleanup();
if (onConfirm) onConfirm();
resolve(true);
};
// Handle cancel
const handleCancel = () => {
modal.style.display = 'none';
cleanup();
if (onCancel) onCancel();
resolve(false);
};
// Handle keyboard
const handleKeyDown = (e) => {
if (e.key === 'Escape') {
handleCancel();
} else if (e.key === 'Enter') {
handleConfirm();
}
};
// Handle backdrop click
const handleBackdropClick = (e) => {
if (e.target === modal) {
handleCancel();
}
};
// Clean up event listeners
const cleanup = () => {
confirmBtn.removeEventListener('click', handleConfirm);
cancelBtn.removeEventListener('click', handleCancel);
closeBtn.removeEventListener('click', handleCancel);
document.removeEventListener('keydown', handleKeyDown);
modal.removeEventListener('click', handleBackdropClick);
};
// Attach event listeners
confirmBtn.addEventListener('click', handleConfirm);
cancelBtn.addEventListener('click', handleCancel);
closeBtn.addEventListener('click', handleCancel);
document.addEventListener('keydown', handleKeyDown);
modal.addEventListener('click', handleBackdropClick);
// Focus confirm button
setTimeout(() => confirmBtn.focus(), 100);
});
}
/**
* Show an alert dialog (info only, single OK button)
* @param {Object} options - Dialog options
* @param {string} options.title - Dialog title
* @param {string} options.message - Dialog message
* @param {string} [options.variant='info'] - Dialog variant: 'danger', 'warning', or 'info'
* @param {string} [options.okText='OK'] - OK button text
* @param {Function} [options.onOk] - Callback when OK clicked
* @returns {Promise<void>} Resolves when OK clicked
*/
export function showAlertDialog(options) {
return new Promise((resolve) => {
const {
title = 'Alert',
message = '',
variant = 'info',
okText = 'OK',
onOk = null
} = options;
// Get modal elements
const modal = document.getElementById('rpg-confirm-dialog');
const modalContent = modal.querySelector('.rpg-confirm-content');
const icon = document.getElementById('rpg-confirm-icon');
const titleEl = document.getElementById('rpg-confirm-title');
const messageEl = document.getElementById('rpg-confirm-message');
const confirmBtn = document.getElementById('rpg-confirm-confirm');
const cancelBtn = document.getElementById('rpg-confirm-cancel');
const closeBtn = modal.querySelector('.rpg-confirm-close');
if (!modal) {
console.error('[ConfirmDialog] Modal not found');
return resolve();
}
// Set icon based on variant
const iconMap = {
danger: 'fa-solid fa-triangle-exclamation',
warning: 'fa-solid fa-circle-exclamation',
info: 'fa-solid fa-circle-info'
};
icon.className = `rpg-confirm-icon ${iconMap[variant] || iconMap.info}`;
// Set variant class on modal content
modalContent.className = `rpg-modal-content rpg-confirm-content rpg-confirm-${variant}`;
// Set content
titleEl.textContent = title;
messageEl.textContent = message;
confirmBtn.textContent = okText;
// Hide cancel button for alerts
cancelBtn.style.display = 'none';
// Show modal
modal.style.display = 'flex';
// Handle OK
const handleOk = () => {
modal.style.display = 'none';
cancelBtn.style.display = ''; // Restore for future confirms
cleanup();
if (onOk) onOk();
resolve();
};
// Handle keyboard
const handleKeyDown = (e) => {
if (e.key === 'Escape' || e.key === 'Enter') {
handleOk();
}
};
// Handle backdrop click
const handleBackdropClick = (e) => {
if (e.target === modal) {
handleOk();
}
};
// Clean up event listeners
const cleanup = () => {
confirmBtn.removeEventListener('click', handleOk);
closeBtn.removeEventListener('click', handleOk);
document.removeEventListener('keydown', handleKeyDown);
modal.removeEventListener('click', handleBackdropClick);
};
// Attach event listeners
confirmBtn.addEventListener('click', handleOk);
closeBtn.addEventListener('click', handleOk);
document.addEventListener('keydown', handleKeyDown);
modal.addEventListener('click', handleBackdropClick);
// Focus OK button
setTimeout(() => confirmBtn.focus(), 100);
});
}
+11 -2
View File
@@ -14,6 +14,7 @@ import { WidgetRegistry } from './widgetRegistry.js';
import { generateDefaultDashboard } from './defaultLayout.js';
import { TabScrollManager } from './tabScrollManager.js';
import { HeaderOverflowManager } from './headerOverflowManager.js';
import { showConfirmDialog } from './confirmDialog.js';
// Widget imports
import { registerUserInfoWidget } from './widgets/userInfoWidget.js';
@@ -224,9 +225,17 @@ function setupDashboardEventListeners(dependencies) {
// Reset layout button
const resetLayoutBtn = document.querySelector('#rpg-dashboard-reset-layout');
if (resetLayoutBtn) {
resetLayoutBtn.addEventListener('click', () => {
resetLayoutBtn.addEventListener('click', async () => {
if (dashboardManager) {
if (confirm('Reset dashboard to default layout? This will remove all widgets and reload the defaults.')) {
const confirmed = await showConfirmDialog({
title: 'Reset Layout?',
message: 'This will remove all widgets and reload the default layout. This action cannot be undone.',
variant: 'danger',
confirmText: 'Reset',
cancelText: 'Cancel'
});
if (confirmed) {
console.log('[RPG Companion] Reset layout button clicked');
dashboardManager.resetLayout();
}
+46 -2
View File
@@ -137,7 +137,7 @@ export class DashboardManager {
this.dashboard.tabs.push({
id: 'main',
name: 'Main',
icon: '🏠',
icon: 'fa-solid fa-house',
order: 0,
widgets: []
});
@@ -361,7 +361,7 @@ export class DashboardManager {
button.className = 'rpg-dashboard-tab';
button.dataset.tabId = tab.id;
button.innerHTML = `
<span class="rpg-tab-icon">${tab.icon}</span>
<span class="rpg-tab-icon"><i class="${tab.icon}"></i></span>
<span class="rpg-tab-name">${tab.name}</span>
`;
@@ -1189,6 +1189,47 @@ export class DashboardManager {
return config;
}
/**
* Migrate emoji icons to Font Awesome
* @param {Object} config - Dashboard configuration
* @returns {Object} Migrated configuration
*/
migrateEmojiIcons(config) {
// Map of common emojis to Font Awesome classes
const emojiToFontAwesome = {
'📊': 'fa-solid fa-chart-line',
'🌍': 'fa-solid fa-map',
'🎒': 'fa-solid fa-bag-shopping',
'🏠': 'fa-solid fa-house',
'📄': 'fa-solid fa-file',
'⚙️': 'fa-solid fa-gear',
'👤': 'fa-solid fa-user',
'📝': 'fa-solid fa-note-sticky',
'🗂️': 'fa-solid fa-folder',
'📁': 'fa-solid fa-folder-open'
};
if (config && config.tabs) {
config.tabs.forEach(tab => {
// Check if icon is an emoji (contains emoji characters)
if (tab.icon && /[\u{1F300}-\u{1F9FF}]/u.test(tab.icon)) {
// Convert to Font Awesome if we have a mapping
const faIcon = emojiToFontAwesome[tab.icon];
if (faIcon) {
console.log(`[DashboardManager] Migrating emoji icon "${tab.icon}" → "${faIcon}" for tab "${tab.name}"`);
tab.icon = faIcon;
} else {
// Fallback to generic file icon
console.warn(`[DashboardManager] Unknown emoji icon "${tab.icon}", using fa-solid fa-file for tab "${tab.name}"`);
tab.icon = 'fa-solid fa-file';
}
}
});
}
return config;
}
/**
* Apply dashboard configuration
* @param {Object} config - Dashboard configuration
@@ -1196,6 +1237,9 @@ export class DashboardManager {
applyDashboardConfig(config) {
console.log('[DashboardManager] Applying dashboard config');
// Migrate emoji icons to Font Awesome
config = this.migrateEmojiIcons(config);
// Update grid config from dashboard config
if (config.gridConfig) {
this.config.rowHeight = config.gridConfig.rowHeight || this.config.rowHeight;
@@ -132,4 +132,28 @@
</div>
</div>
</div>
<!-- Confirmation Dialog Modal -->
<div id="rpg-confirm-dialog" class="rpg-modal rpg-confirm-modal" style="display: none;">
<div class="rpg-modal-content rpg-confirm-content">
<div class="rpg-modal-header rpg-confirm-header">
<div class="rpg-confirm-header-content">
<i id="rpg-confirm-icon" class="rpg-confirm-icon"></i>
<h3 id="rpg-confirm-title"></h3>
</div>
<button class="rpg-modal-close rpg-confirm-close">
<i class="fa-solid fa-times"></i>
</button>
</div>
<div class="rpg-modal-body rpg-confirm-body">
<p id="rpg-confirm-message"></p>
</div>
<div class="rpg-modal-footer rpg-confirm-footer">
<button class="rpg-btn-secondary" id="rpg-confirm-cancel"></button>
<button class="rpg-btn-primary" id="rpg-confirm-confirm"></button>
</div>
</div>
</div>
</div>
+3 -3
View File
@@ -36,7 +36,7 @@ export function generateDefaultDashboard() {
{
id: 'tab-status',
name: 'Status',
icon: '📊',
icon: 'fa-solid fa-user',
order: 0,
widgets: [
// Row 0: User Info (left) + User Mood (top right in 3-col)
@@ -86,7 +86,7 @@ export function generateDefaultDashboard() {
{
id: 'tab-scene',
name: 'Scene',
icon: '🌍',
icon: 'fa-solid fa-map',
order: 1,
widgets: [
// Row 0: Calendar (left) + Weather (right)
@@ -162,7 +162,7 @@ export function generateDefaultDashboard() {
{
id: 'tab-inventory',
name: 'Inventory',
icon: '🎒',
icon: 'fa-solid fa-bag-shopping',
order: 2,
widgets: [
{
+32 -9
View File
@@ -5,6 +5,8 @@
* Handles edit controls, widget library, and layout modifications.
*/
import { showConfirmDialog } from './confirmDialog.js';
/**
* @typedef {Object} EditModeConfig
* @property {HTMLElement} container - Dashboard container element
@@ -453,9 +455,16 @@ export class EditModeManager {
* Show confirmation dialog before canceling
* @param {Function} onConfirm - Callback if confirmed
*/
confirmCancel(onConfirm) {
const message = 'You have unsaved changes. Are you sure you want to cancel?';
if (confirm(message)) {
async confirmCancel(onConfirm) {
const confirmed = await showConfirmDialog({
title: 'Discard Changes?',
message: 'You have unsaved changes. Are you sure you want to discard them?',
variant: 'warning',
confirmText: 'Discard',
cancelText: 'Keep Editing'
});
if (confirmed) {
onConfirm();
}
}
@@ -464,9 +473,16 @@ export class EditModeManager {
* Show confirmation dialog before deleting widget
* @param {string} widgetId - Widget ID to delete
*/
confirmDeleteWidget(widgetId) {
const message = 'Are you sure you want to delete this widget?';
if (confirm(message)) {
async confirmDeleteWidget(widgetId) {
const confirmed = await showConfirmDialog({
title: 'Delete Widget?',
message: 'Are you sure you want to delete this widget? This action cannot be undone.',
variant: 'danger',
confirmText: 'Delete',
cancelText: 'Cancel'
});
if (confirmed) {
if (this.onWidgetDelete) {
this.onWidgetDelete(widgetId);
}
@@ -477,9 +493,16 @@ export class EditModeManager {
* Show confirmation dialog before resetting layout
* @param {Function} onConfirm - Callback if confirmed
*/
confirmReset(onConfirm) {
const message = 'This will reset the layout to default. Are you sure?';
if (confirm(message)) {
async confirmReset(onConfirm) {
const confirmed = await showConfirmDialog({
title: 'Reset Layout?',
message: 'This will reset the layout to default. All widgets will be removed and the default layout will be restored.',
variant: 'danger',
confirmText: 'Reset',
cancelText: 'Cancel'
});
if (confirmed) {
onConfirm();
}
}
+1 -1
View File
@@ -97,7 +97,7 @@ export class TabManager {
const tab = {
id,
name: config.name,
icon: config.icon || '📄',
icon: config.icon || 'fa-solid fa-file',
order,
widgets: []
};
@@ -16,6 +16,7 @@
import { parseItems, serializeItems } from '../../../utils/itemParser.js';
import { sanitizeItemName, sanitizeLocationName } from '../../../utils/security.js';
import { showAlertDialog } from '../confirmDialog.js';
/**
* Convert location name to safe HTML ID
@@ -663,7 +664,11 @@ export function registerInventoryWidget(registry, dependencies) {
const itemName = sanitizeItemName(rawItemName);
if (!itemName) {
alert('Invalid item name.');
showAlertDialog({
title: 'Invalid Item',
message: 'Please enter a valid item name.',
variant: 'warning'
});
hideAddItemForm(widget, field, location);
return;
}
@@ -819,7 +824,11 @@ export function registerInventoryWidget(registry, dependencies) {
const locationName = sanitizeLocationName(rawLocationName);
if (!locationName) {
alert('Invalid location name.');
showAlertDialog({
title: 'Invalid Location',
message: 'Please enter a valid location name.',
variant: 'warning'
});
hideAddLocationForm(widget);
return;
}
@@ -829,7 +838,11 @@ export function registerInventoryWidget(registry, dependencies) {
// Check if location already exists
if (inventory.stored[locationName]) {
alert('A location with this name already exists.');
showAlertDialog({
title: 'Duplicate Location',
message: 'A location with this name already exists.',
variant: 'warning'
});
hideAddLocationForm(widget);
return;
}
+116 -34
View File
@@ -1133,7 +1133,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
font-size: 0.75rem;
border: 1px solid transparent;
background: transparent;
color: var(--SmartThemeBodyColor);
color: var(--rpg-text);
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
@@ -1143,13 +1143,13 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
}
.rpg-dashboard-tab:hover {
background: var(--SmartThemeBlurTintColor);
background: var(--rpg-accent);
opacity: 0.9;
}
.rpg-dashboard-tab.active {
background: var(--SmartThemeBlurTintColor);
border-color: var(--SmartThemeBorderColor);
background: var(--rpg-accent);
border-color: var(--rpg-border);
opacity: 1;
font-weight: 600;
}
@@ -1182,9 +1182,9 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
width: 2rem;
height: 2rem;
padding: 0;
border: 1px solid var(--SmartThemeBorderColor);
background: var(--SmartThemeBlurTintColor);
color: var(--SmartThemeBodyColor);
border: 1px solid var(--rpg-border);
background: var(--rpg-bg);
color: var(--rpg-text);
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
@@ -1200,7 +1200,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
.rpg-tab-nav-arrow:hover {
opacity: 1;
background: var(--SmartThemeQuoteColor);
background: var(--rpg-highlight);
transform: translateY(-50%) scale(1.05);
}
@@ -1232,7 +1232,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
left: 0;
background: linear-gradient(
to right,
var(--SmartThemeBlurTintColor) 0%,
var(--rpg-bg) 0%,
transparent 100%
);
}
@@ -1241,7 +1241,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
right: 0;
background: linear-gradient(
to left,
var(--SmartThemeBlurTintColor) 0%,
var(--rpg-bg) 0%,
transparent 100%
);
}
@@ -1281,16 +1281,16 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
width: 2rem;
height: 2rem;
font-size: 0.9rem;
border: 1px solid var(--SmartThemeBorderColor);
background: var(--SmartThemeBlurTintColor);
color: var(--SmartThemeBodyColor);
border: 1px solid var(--rpg-border);
background: var(--rpg-accent);
color: var(--rpg-text);
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
}
.rpg-dashboard-btn:hover {
background: var(--SmartThemeQuoteColor);
background: var(--rpg-highlight);
transform: translateY(-1px);
}
@@ -1309,10 +1309,10 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
right: 0;
min-width: 200px;
max-width: 300px;
background: var(--SmartThemeBlurTintColor);
border: 1px solid var(--SmartThemeBorderColor);
background: var(--rpg-bg);
border: 1px solid var(--rpg-border);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
box-shadow: 0 4px 12px var(--rpg-shadow);
z-index: 10003; /* Above modals (10000) and mobile toggle (10002) */
overflow: hidden;
animation: slideDown 0.2s ease-out;
@@ -1338,7 +1338,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
padding: 0.75rem 1rem;
border: none;
background: transparent;
color: var(--SmartThemeBodyColor);
color: var(--rpg-text);
text-align: left;
cursor: pointer;
transition: background 0.15s;
@@ -1347,7 +1347,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
.rpg-dropdown-item:hover,
.rpg-dropdown-item:focus {
background: var(--SmartThemeQuoteColor);
background: var(--rpg-highlight);
outline: none;
}
@@ -1444,13 +1444,13 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
/* Modal Content Container */
.rpg-modal-content {
background: var(--SmartThemeBlurTintColor);
border: 2px solid var(--SmartThemeBorderColor);
background: var(--rpg-bg);
border: 2px solid var(--rpg-border);
border-radius: 8px;
max-width: 600px;
max-height: 80vh;
overflow: auto;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
box-shadow: 0 4px 20px var(--rpg-shadow);
}
/* Modal Header */
@@ -1459,12 +1459,12 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
justify-content: space-between;
align-items: center;
padding: 1rem;
border-bottom: 1px solid var(--SmartThemeBorderColor);
border-bottom: 1px solid var(--rpg-border);
}
.rpg-modal-header h3 {
margin: 0;
color: var(--SmartThemeBodyColor);
color: var(--rpg-text);
display: flex;
align-items: center;
gap: 0.5rem;
@@ -1473,7 +1473,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
.rpg-modal-close {
background: transparent;
border: none;
color: var(--SmartThemeBodyColor);
color: var(--rpg-text);
font-size: 1.5rem;
cursor: pointer;
padding: 0.25rem 0.5rem;
@@ -1496,7 +1496,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
justify-content: flex-end;
gap: 0.5rem;
padding: 1rem;
border-top: 1px solid var(--SmartThemeBorderColor);
border-top: 1px solid var(--rpg-border);
}
/* Widget Grid in Modal */
@@ -1509,9 +1509,9 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
/* Widget Card */
.rpg-widget-card {
padding: 1rem;
border: 2px solid var(--SmartThemeBorderColor);
border: 2px solid var(--rpg-border);
border-radius: 8px;
background: var(--SmartThemeQuoteColor);
background: var(--rpg-accent);
text-align: center;
transition: all 0.2s;
display: flex;
@@ -1522,7 +1522,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
.rpg-widget-card:hover {
border-color: var(--rpg-highlight);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
box-shadow: 0 4px 12px var(--rpg-shadow);
}
.rpg-widget-card-icon {
@@ -1533,12 +1533,13 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
.rpg-widget-card-name {
font-size: 0.95rem;
font-weight: 600;
color: var(--SmartThemeBodyColor);
color: var(--rpg-text);
}
.rpg-widget-card-description {
font-size: 0.75rem;
color: var(--SmartThemeFastUISliderColColor);
color: var(--rpg-text);
opacity: 0.7;
line-height: 1.3;
}
@@ -1587,12 +1588,12 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
.rpg-btn-secondary {
background: transparent;
border: 1px solid var(--SmartThemeBorderColor);
color: var(--SmartThemeBodyColor);
border: 1px solid var(--rpg-border);
color: var(--rpg-text);
}
.rpg-btn-secondary:hover {
background: var(--SmartThemeQuoteColor);
background: var(--rpg-accent);
}
/* Tab Drop Zone Highlight */
@@ -1603,6 +1604,87 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
box-shadow: 0 0 12px var(--rpg-highlight);
}
/* ========================================
Confirmation Dialog Styles
======================================== */
/* Confirmation Dialog Content */
.rpg-confirm-content {
max-width: 450px;
width: 90%;
}
/* Confirmation Dialog Header */
.rpg-confirm-header-content {
display: flex;
align-items: center;
gap: 0.75rem;
}
.rpg-confirm-icon {
font-size: 1.75rem;
flex-shrink: 0;
}
/* Confirmation Dialog Body */
.rpg-confirm-body {
padding: 1.5rem 1rem;
}
.rpg-confirm-body p {
margin: 0;
line-height: 1.5;
color: var(--rpg-text);
}
/* Confirmation Dialog Footer */
.rpg-confirm-footer {
padding: 1rem;
display: flex;
gap: 0.75rem;
justify-content: flex-end;
}
/* Variant Styles - Danger (Red) */
.rpg-confirm-danger .rpg-confirm-icon {
color: #ff4444;
}
.rpg-confirm-danger .rpg-btn-primary {
background: #ff4444;
}
.rpg-confirm-danger .rpg-btn-primary:hover {
background: #cc0000;
}
/* Variant Styles - Warning (Yellow/Orange) */
.rpg-confirm-warning .rpg-confirm-icon {
color: #ffaa00;
}
.rpg-confirm-warning .rpg-btn-primary {
background: #ffaa00;
color: #1a1a1a;
}
.rpg-confirm-warning .rpg-btn-primary:hover {
background: #cc8800;
}
/* Variant Styles - Info (Blue) */
.rpg-confirm-info .rpg-confirm-icon {
color: #00aaff;
}
.rpg-confirm-info .rpg-btn-primary {
background: #00aaff;
}
.rpg-confirm-info .rpg-btn-primary:hover {
background: #0088cc;
}
.rpg-dashboard-grid {
position: relative;
width: 100%;