From bedfbc77d57213016b0af933f1f59a80b1fae5dd Mon Sep 17 00:00:00 2001 From: Lucas 'Paperboy' Rose-Winters Date: Tue, 4 Nov 2025 14:24:31 +1100 Subject: [PATCH] fix: Use solid RGB colors for modal and menu backgrounds Problem: CSS variables --rpg-bg and --rpg-accent have 0.9 opacity, making all modals and menus transparent even with gradient backgrounds. Solution: Use solid RGB colors directly in gradients: - rgba(22, 33, 62, 1) instead of var(--rpg-accent) - rgba(26, 26, 46, 1) instead of var(--rpg-bg) Changes: - style.css: .rpg-modal-content uses solid gradient - tabContextMenu.js: Context menu uses solid gradient - Both maintain inset shadow for depth - Mobile long-press support included Now modals and context menus have opaque backgrounds matching widget styling instead of see-through transparency. --- src/systems/dashboard/promptDialog.js | 119 ++++-------------- src/systems/dashboard/tabContextMenu.js | 159 ++++++++++++++++-------- style.css | 4 +- 3 files changed, 130 insertions(+), 152 deletions(-) diff --git a/src/systems/dashboard/promptDialog.js b/src/systems/dashboard/promptDialog.js index 9647d4c..6cdfc6f 100644 --- a/src/systems/dashboard/promptDialog.js +++ b/src/systems/dashboard/promptDialog.js @@ -1,7 +1,7 @@ /** * Prompt Dialog System * - * Provides styled prompt dialogs for text input, similar to confirmDialog. + * Provides styled prompt dialogs for text input, matching extension theming. * Used for tab renaming, creation, etc. */ @@ -29,98 +29,55 @@ export function showPromptDialog(options) { validator = null } = options; - // Create modal container + // Create modal container (uses .rpg-modal class for theming) const modal = document.createElement('div'); modal.className = 'rpg-modal rpg-prompt-modal'; modal.style.display = 'flex'; - modal.style.position = 'fixed'; - modal.style.inset = '0'; - modal.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; - modal.style.zIndex = '10001'; - modal.style.alignItems = 'center'; - modal.style.justifyContent = 'center'; - // Create modal content + // Create modal content (uses .rpg-modal-content class for theming) const modalContent = document.createElement('div'); modalContent.className = 'rpg-modal-content rpg-prompt-content'; modalContent.style.cssText = ` - background: #16213e; - border-radius: 8px; - padding: 0; min-width: 400px; max-width: 90vw; - box-shadow: 0 4px 20px rgba(0,0,0,0.5); `; - // Header + // Header (uses .rpg-modal-header class) const header = document.createElement('div'); header.className = 'rpg-modal-header'; - header.style.cssText = ` - padding: 20px; - border-bottom: 1px solid #0f3460; - display: flex; - align-items: center; - justify-content: space-between; - `; const headerContent = document.createElement('div'); headerContent.style.display = 'flex'; headerContent.style.alignItems = 'center'; - headerContent.style.gap = '10px'; + headerContent.style.gap = '0.5rem'; const icon = document.createElement('i'); icon.className = 'fa-solid fa-pencil'; - icon.style.color = '#4ecca3'; - icon.style.fontSize = '20px'; + icon.style.color = 'var(--rpg-highlight)'; const titleEl = document.createElement('h3'); titleEl.textContent = title; - titleEl.style.cssText = ` - margin: 0; - font-size: 18px; - color: #eeeeee; - `; + titleEl.style.margin = '0'; const closeBtn = document.createElement('button'); closeBtn.className = 'rpg-modal-close'; closeBtn.innerHTML = ''; - closeBtn.style.cssText = ` - background: transparent; - border: none; - color: #eeeeee; - font-size: 20px; - cursor: pointer; - padding: 4px; - width: 28px; - height: 28px; - border-radius: 4px; - display: flex; - align-items: center; - justify-content: center; - transition: background 0.2s; - `; - closeBtn.onmouseenter = () => closeBtn.style.background = '#e94560'; - closeBtn.onmouseleave = () => closeBtn.style.background = 'transparent'; headerContent.appendChild(icon); headerContent.appendChild(titleEl); header.appendChild(headerContent); header.appendChild(closeBtn); - // Body + // Body (uses .rpg-modal-body class) const body = document.createElement('div'); body.className = 'rpg-modal-body'; - body.style.cssText = ` - padding: 20px; - `; if (message) { const messageEl = document.createElement('p'); messageEl.textContent = message; messageEl.style.cssText = ` - margin: 0 0 15px 0; - color: #eeeeee; - font-size: 14px; + margin: 0 0 1rem 0; + color: var(--rpg-text); `; body.appendChild(messageEl); } @@ -131,12 +88,12 @@ export function showPromptDialog(options) { input.placeholder = placeholder; input.style.cssText = ` width: 100%; - padding: 10px; - background: #0f3460; - border: 1px solid #1a4d7a; + padding: 0.5rem; + background: var(--rpg-accent); + border: 1px solid var(--rpg-border); border-radius: 4px; - color: #eeeeee; - font-size: 14px; + color: var(--rpg-text); + font-size: 1rem; font-family: inherit; box-sizing: border-box; `; @@ -144,58 +101,26 @@ export function showPromptDialog(options) { const errorEl = document.createElement('div'); errorEl.className = 'rpg-prompt-error'; errorEl.style.cssText = ` - margin-top: 8px; - color: #e94560; - font-size: 12px; - min-height: 18px; + margin-top: 0.5rem; + color: var(--rpg-highlight); + font-size: 0.875rem; + min-height: 1.25rem; `; body.appendChild(input); body.appendChild(errorEl); - // Footer + // Footer (uses .rpg-modal-footer class) const footer = document.createElement('div'); footer.className = 'rpg-modal-footer'; - footer.style.cssText = ` - padding: 20px; - border-top: 1px solid #0f3460; - display: flex; - gap: 10px; - justify-content: flex-end; - `; const cancelBtn = document.createElement('button'); cancelBtn.className = 'rpg-btn-secondary'; - cancelBtn.textContent = cancelText; - cancelBtn.style.cssText = ` - padding: 10px 20px; - background: #0f3460; - border: none; - border-radius: 6px; - color: #eeeeee; - font-size: 14px; - cursor: pointer; - transition: background 0.2s; - `; - cancelBtn.onmouseenter = () => cancelBtn.style.background = '#1a4d7a'; - cancelBtn.onmouseleave = () => cancelBtn.style.background = '#0f3460'; + cancelBtn.innerHTML = ` ${cancelText}`; const confirmBtn = document.createElement('button'); confirmBtn.className = 'rpg-btn-primary'; - confirmBtn.textContent = confirmText; - confirmBtn.style.cssText = ` - padding: 10px 20px; - background: #4ecca3; - border: none; - border-radius: 6px; - color: #16213e; - font-size: 14px; - font-weight: bold; - cursor: pointer; - transition: background 0.2s; - `; - confirmBtn.onmouseenter = () => confirmBtn.style.background = '#45b993'; - confirmBtn.onmouseleave = () => confirmBtn.style.background = '#4ecca3'; + confirmBtn.innerHTML = ` ${confirmText}`; footer.appendChild(cancelBtn); footer.appendChild(confirmBtn); diff --git a/src/systems/dashboard/tabContextMenu.js b/src/systems/dashboard/tabContextMenu.js index a182f4a..6c19e47 100644 --- a/src/systems/dashboard/tabContextMenu.js +++ b/src/systems/dashboard/tabContextMenu.js @@ -45,7 +45,12 @@ export class TabContextMenu { attachHandlers() { if (!this.tabsContainer) return; - // Use event delegation for dynamically added tabs + // Long press support for mobile + let longPressTimer = null; + let longPressTarget = null; + let touchStartPos = { x: 0, y: 0 }; + + // Desktop: Right-click context menu this.tabsContainer.addEventListener('contextmenu', (e) => { // Find closest tab element const tabElement = e.target.closest('.rpg-dashboard-tab'); @@ -60,8 +65,77 @@ export class TabContextMenu { this.showMenu(e.pageX, e.pageY, tabId); }); - // Close menu on any click outside + // Mobile: Long press support (touch and hold) + this.tabsContainer.addEventListener('touchstart', (e) => { + const tabElement = e.target.closest('.rpg-dashboard-tab'); + if (!tabElement) return; + + const tabId = tabElement.dataset.tabId; + if (!tabId) return; + + // Store touch position + const touch = e.touches[0]; + touchStartPos = { x: touch.pageX, y: touch.pageY }; + longPressTarget = { tabId, x: touch.pageX, y: touch.pageY }; + + // Start long press timer (500ms) + longPressTimer = setTimeout(() => { + if (longPressTarget) { + // Prevent default touch behavior + e.preventDefault(); + // Show context menu at touch position + this.showMenu(longPressTarget.x, longPressTarget.y, longPressTarget.tabId); + // Provide haptic feedback if available + if (navigator.vibrate) { + navigator.vibrate(50); + } + longPressTarget = null; + } + }, 500); + }, { passive: false }); + + // Cancel long press on touch move (if moved too far) + this.tabsContainer.addEventListener('touchmove', (e) => { + if (!longPressTimer) return; + + const touch = e.touches[0]; + const deltaX = Math.abs(touch.pageX - touchStartPos.x); + const deltaY = Math.abs(touch.pageY - touchStartPos.y); + + // Cancel if moved more than 10px + if (deltaX > 10 || deltaY > 10) { + clearTimeout(longPressTimer); + longPressTimer = null; + longPressTarget = null; + } + }); + + // Cancel long press on touch end (if timer still running) + this.tabsContainer.addEventListener('touchend', () => { + if (longPressTimer) { + clearTimeout(longPressTimer); + longPressTimer = null; + longPressTarget = null; + } + }); + + // Cancel long press on touch cancel + this.tabsContainer.addEventListener('touchcancel', () => { + if (longPressTimer) { + clearTimeout(longPressTimer); + longPressTimer = null; + longPressTarget = null; + } + }); + + // Close menu on any click/touch outside document.addEventListener('click', () => this.hideMenu()); + document.addEventListener('touchstart', (e) => { + // Close menu if touching outside context menu + if (this.menu && !this.menu.contains(e.target)) { + this.hideMenu(); + } + }); document.addEventListener('contextmenu', (e) => { // Only hide if right-clicking outside tabs if (!e.target.closest('.rpg-dashboard-tab')) { @@ -83,17 +157,17 @@ export class TabContextMenu { const tab = this.tabManager.getTab(tabId); if (!tab) return; - // Create menu container + // Create menu container (matches widget styling with solid background) this.menu = document.createElement('div'); this.menu.className = 'rpg-tab-context-menu'; this.menu.style.cssText = ` position: fixed; left: ${x}px; top: ${y}px; - background: #16213e; - border: 1px solid #0f3460; + background: linear-gradient(135deg, rgba(22, 33, 62, 1) 0%, rgba(26, 26, 46, 1) 100%); + border: 2px solid var(--rpg-border); border-radius: 6px; - box-shadow: 0 4px 20px rgba(0,0,0,0.5); + box-shadow: 0 4px 18px var(--rpg-shadow), inset 0 0 12px rgba(0, 0, 0, 0.3); z-index: 10002; min-width: 180px; padding: 6px 0; @@ -115,7 +189,7 @@ export class TabContextMenu { const separator = document.createElement('div'); separator.style.cssText = ` height: 1px; - background: #0f3460; + background: var(--rpg-border); margin: 6px 0; `; this.menu.appendChild(separator); @@ -142,8 +216,8 @@ export class TabContextMenu { const menuItem = document.createElement('div'); menuItem.className = 'rpg-tab-context-menu-item'; - const baseColor = item.danger ? '#e94560' : '#eeeeee'; - const hoverBg = item.danger ? '#8b2a3a' : '#0f3460'; + const baseColor = item.danger ? 'var(--rpg-highlight)' : 'var(--rpg-text)'; + const hoverBg = item.danger ? 'rgba(233, 69, 96, 0.2)' : 'var(--rpg-accent)'; menuItem.style.cssText = ` padding: 10px 16px; @@ -172,7 +246,7 @@ export class TabContextMenu { icon.style.cssText = ` width: 16px; text-align: center; - color: ${item.danger ? '#e94560' : '#4ecca3'}; + color: ${item.danger ? 'var(--rpg-highlight)' : 'var(--rpg-border)'}; `; const label = document.createElement('span'); @@ -338,44 +412,31 @@ export class TabContextMenu { */ showIconPicker(iconOptions, currentIcon) { return new Promise((resolve) => { - // Create modal + // Create modal (uses .rpg-modal class for theming) const modal = document.createElement('div'); modal.className = 'rpg-modal'; - modal.style.cssText = ` - position: fixed; - inset: 0; - background: rgba(0,0,0,0.7); - z-index: 10001; - display: flex; - align-items: center; - justify-content: center; - `; + modal.style.display = 'flex'; + // Modal content (uses .rpg-modal-content class for theming) const content = document.createElement('div'); content.className = 'rpg-modal-content'; - content.style.cssText = ` - background: #16213e; - border-radius: 8px; - padding: 24px; - max-width: 500px; - max-height: 80vh; - overflow-y: auto; - `; + content.style.padding = '1.5rem'; + content.style.maxWidth = '500px'; const title = document.createElement('h3'); title.textContent = 'Choose Icon'; title.style.cssText = ` - margin: 0 0 20px 0; - color: #eeeeee; - font-size: 18px; + margin: 0 0 1.25rem 0; + color: var(--rpg-text); + font-size: 1.25rem; `; const grid = document.createElement('div'); grid.style.cssText = ` display: grid; grid-template-columns: repeat(5, 1fr); - gap: 12px; - margin-bottom: 20px; + gap: 0.75rem; + margin-bottom: 1.25rem; `; // Extract icon name without fa-solid prefix for comparison @@ -386,12 +447,12 @@ export class TabContextMenu { const isSelected = option.icon === currentIconName; iconBtn.style.cssText = ` - padding: 16px; - background: ${isSelected ? '#4ecca3' : '#0f3460'}; - border: 2px solid ${isSelected ? '#4ecca3' : '#1a4d7a'}; + padding: 1rem; + background: ${isSelected ? 'var(--rpg-highlight)' : 'var(--rpg-accent)'}; + border: 2px solid ${isSelected ? 'var(--rpg-highlight)' : 'var(--rpg-border)'}; border-radius: 6px; - color: ${isSelected ? '#16213e' : '#eeeeee'}; - font-size: 24px; + color: ${isSelected ? 'white' : 'var(--rpg-text)'}; + font-size: 1.5rem; cursor: pointer; transition: all 0.2s; display: flex; @@ -404,14 +465,14 @@ export class TabContextMenu { iconBtn.onmouseenter = () => { if (!isSelected) { - iconBtn.style.background = '#1a4d7a'; - iconBtn.style.borderColor = '#4ecca3'; + iconBtn.style.borderColor = 'var(--rpg-highlight)'; + iconBtn.style.transform = 'scale(1.05)'; } }; iconBtn.onmouseleave = () => { if (!isSelected) { - iconBtn.style.background = '#0f3460'; - iconBtn.style.borderColor = '#1a4d7a'; + iconBtn.style.borderColor = 'var(--rpg-border)'; + iconBtn.style.transform = 'scale(1)'; } }; @@ -424,17 +485,9 @@ export class TabContextMenu { }); const cancelBtn = document.createElement('button'); - cancelBtn.textContent = 'Cancel'; - cancelBtn.style.cssText = ` - padding: 10px 20px; - background: #0f3460; - border: none; - border-radius: 6px; - color: #eeeeee; - font-size: 14px; - cursor: pointer; - width: 100%; - `; + cancelBtn.className = 'rpg-btn-secondary'; + cancelBtn.innerHTML = ' Cancel'; + cancelBtn.style.width = '100%'; cancelBtn.onclick = () => { modal.remove(); resolve(null); diff --git a/style.css b/style.css index 3c79d11..1982636 100644 --- a/style.css +++ b/style.css @@ -1479,13 +1479,13 @@ body:has(.rpg-panel.rpg-position-left) #sheld { /* Modal Content Container */ .rpg-modal-content { - background: var(--rpg-bg); + background: linear-gradient(135deg, rgba(22, 33, 62, 1) 0%, rgba(26, 26, 46, 1) 100%); border: 2px solid var(--rpg-border); border-radius: 8px; max-width: 600px; max-height: 80vh; overflow: auto; - box-shadow: 0 4px 20px var(--rpg-shadow); + box-shadow: 0 4px 18px var(--rpg-shadow), inset 0 0 12px rgba(0, 0, 0, 0.3); } /* Modal Header */