feat: implement draggable mobile FAB with comprehensive event handling

Add full mouse and touch support for mobile toggle button with drag-to-reposition
functionality and persistent position saving.

Changes:
- Add mobile FAB button with draggable positioning (both mouse and touch)
- Implement time-based drag detection (200ms or 10px threshold)
- Add position persistence in extension settings
- Fix click handler to work on both mobile and desktop viewports
- Add comprehensive diagnostic logging for event debugging
- Update mobile panel to slide from right (matching desktop UX)
- Implement dual-button pattern (FAB + internal collapse toggle)
- Add viewport-constrained dragging with 10px padding
- Prevent click events from firing after drag completion

Mobile UX:
- FAB visible when panel closed, hidden when open
- Internal collapse toggle visible only when panel open
- Touch and mouse drag support with real-time positioning
- Click/tap toggles panel, drag repositions button
- Position saved across sessions

Technical:
- Add mousedown/mousemove/mouseup handlers for desktop drag
- Add touchstart/touchmove/touchend handlers for mobile drag
- Remove broken viewport check that prevented mobile clicks
- Add 'just-dragged' flag to prevent click after drag
- Update .gitignore to exclude CLAUDE.md and .env files
This commit is contained in:
Lucas 'Paperboy' Rose-Winters
2025-10-16 09:43:23 +11:00
parent 9ed76a4381
commit 9a653a9c7a
3 changed files with 520 additions and 65 deletions
+6
View File
@@ -18,3 +18,9 @@
# Node modules (if any) # Node modules (if any)
node_modules/ node_modules/
# Environment variables
.env
# Claude
CLAUDE.md
+455 -23
View File
@@ -35,6 +35,10 @@ let extensionSettings = {
statBarColorLow: '#cc3333', // Color for low stat values (red) statBarColorLow: '#cc3333', // Color for low stat values (red)
statBarColorHigh: '#33cc66', // Color for high stat values (green) statBarColorHigh: '#33cc66', // Color for high stat values (green)
enableAnimations: true, // Enable smooth animations for stats and content updates enableAnimations: true, // Enable smooth animations for stats and content updates
mobileFabPosition: {
top: 'calc(var(--topBarBlockSize) + 60px)',
right: '12px'
}, // Saved position for mobile FAB button
userStats: { userStats: {
health: 100, health: 100,
sustenance: 100, sustenance: 100,
@@ -989,21 +993,323 @@ function setupMobileToggle() {
const $panel = $('#rpg-companion-panel'); const $panel = $('#rpg-companion-panel');
const $overlay = $('<div class="rpg-mobile-overlay"></div>'); const $overlay = $('<div class="rpg-mobile-overlay"></div>');
// Toggle panel visibility on mobile // DIAGNOSTIC: Check if elements exist and log setup state
$mobileToggle.on('click', function() { console.log('[RPG Mobile] ========================================');
console.log('[RPG Mobile] setupMobileToggle called');
console.log('[RPG Mobile] Button exists:', $mobileToggle.length > 0, 'jQuery object:', $mobileToggle);
console.log('[RPG Mobile] Panel exists:', $panel.length > 0);
console.log('[RPG Mobile] Window width:', window.innerWidth);
console.log('[RPG Mobile] Is mobile viewport (<=1000):', window.innerWidth <= 1000);
console.log('[RPG Mobile] ========================================');
if ($mobileToggle.length === 0) {
console.error('[RPG Mobile] ERROR: Mobile toggle button not found in DOM!');
console.error('[RPG Mobile] Cannot attach event handlers - button does not exist');
return; // Exit early if button doesn't exist
}
// Load and apply saved FAB position
if (extensionSettings.mobileFabPosition) {
const pos = extensionSettings.mobileFabPosition;
console.log('[RPG Mobile] Loading saved FAB position:', pos);
// Apply saved position
if (pos.top) $mobileToggle.css('top', pos.top);
if (pos.right) $mobileToggle.css('right', pos.right);
if (pos.bottom) $mobileToggle.css('bottom', pos.bottom);
if (pos.left) $mobileToggle.css('left', pos.left);
}
// Touch/drag state
let isDragging = false;
let touchStartTime = 0;
let touchStartX = 0;
let touchStartY = 0;
let buttonStartX = 0;
let buttonStartY = 0;
const LONG_PRESS_DURATION = 200; // ms to hold before enabling drag
const MOVE_THRESHOLD = 10; // px to move before enabling drag
// Touch start - begin tracking
$mobileToggle.on('touchstart', function(e) {
console.log('[RPG Mobile] >>> TOUCHSTART EVENT FIRED <<<');
const touch = e.originalEvent.touches[0];
touchStartTime = Date.now();
touchStartX = touch.clientX;
touchStartY = touch.clientY;
const offset = $mobileToggle.offset();
buttonStartX = offset.left;
buttonStartY = offset.top;
isDragging = false;
console.log('[RPG Mobile] Touch start:', {
time: touchStartTime,
touchX: touchStartX,
touchY: touchStartY,
buttonX: buttonStartX,
buttonY: buttonStartY
});
});
// Touch move - check if should start dragging
$mobileToggle.on('touchmove', function(e) {
const touch = e.originalEvent.touches[0];
const deltaX = touch.clientX - touchStartX;
const deltaY = touch.clientY - touchStartY;
const timeSinceStart = Date.now() - touchStartTime;
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
console.log('[RPG Mobile] >>> TOUCHMOVE EVENT FIRED <<<', {
distance: distance.toFixed(2),
timeSinceStart,
isDragging
});
// Start dragging if held long enough OR moved far enough
if (!isDragging && (timeSinceStart > LONG_PRESS_DURATION || distance > MOVE_THRESHOLD)) {
isDragging = true;
$mobileToggle.css('transition', 'none'); // Disable transitions while dragging
console.log('[RPG Mobile] Started dragging:', { timeSinceStart, distance });
}
if (isDragging) {
e.preventDefault(); // Prevent scrolling while dragging
// Calculate new position
let newX = buttonStartX + deltaX;
let newY = buttonStartY + deltaY;
// Get button dimensions
const buttonWidth = $mobileToggle.outerWidth();
const buttonHeight = $mobileToggle.outerHeight();
// Constrain to viewport with 10px padding
const minX = 10;
const maxX = window.innerWidth - buttonWidth - 10;
const minY = 10;
const maxY = window.innerHeight - buttonHeight - 10;
newX = Math.max(minX, Math.min(maxX, newX));
newY = Math.max(minY, Math.min(maxY, newY));
// Apply position
$mobileToggle.css({
left: newX + 'px',
top: newY + 'px',
right: 'auto',
bottom: 'auto'
});
}
});
// Mouse drag support for desktop
let mouseDown = false;
$mobileToggle.on('mousedown', function(e) {
console.log('[RPG Mobile] >>> MOUSEDOWN EVENT FIRED <<<');
// Prevent default to avoid text selection
e.preventDefault();
touchStartTime = Date.now();
touchStartX = e.clientX;
touchStartY = e.clientY;
const offset = $mobileToggle.offset();
buttonStartX = offset.left;
buttonStartY = offset.top;
isDragging = false;
mouseDown = true;
console.log('[RPG Mobile] Mouse down:', {
time: touchStartTime,
mouseX: touchStartX,
mouseY: touchStartY,
buttonX: buttonStartX,
buttonY: buttonStartY
});
});
// Mouse move - only track if mouse is down
$(document).on('mousemove', function(e) {
if (!mouseDown) return;
const deltaX = e.clientX - touchStartX;
const deltaY = e.clientY - touchStartY;
const timeSinceStart = Date.now() - touchStartTime;
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
console.log('[RPG Mobile] >>> MOUSEMOVE EVENT FIRED <<<', {
distance: distance.toFixed(2),
timeSinceStart,
isDragging,
mouseDown
});
// Start dragging if held long enough OR moved far enough
if (!isDragging && (timeSinceStart > LONG_PRESS_DURATION || distance > MOVE_THRESHOLD)) {
isDragging = true;
$mobileToggle.css('transition', 'none');
console.log('[RPG Mobile] Started mouse dragging:', { timeSinceStart, distance });
}
if (isDragging) {
e.preventDefault();
// Calculate new position
let newX = buttonStartX + deltaX;
let newY = buttonStartY + deltaY;
// Get button dimensions
const buttonWidth = $mobileToggle.outerWidth();
const buttonHeight = $mobileToggle.outerHeight();
// Constrain to viewport with 10px padding
const minX = 10;
const maxX = window.innerWidth - buttonWidth - 10;
const minY = 10;
const maxY = window.innerHeight - buttonHeight - 10;
newX = Math.max(minX, Math.min(maxX, newX));
newY = Math.max(minY, Math.min(maxY, newY));
// Apply position
$mobileToggle.css({
left: newX + 'px',
top: newY + 'px',
right: 'auto',
bottom: 'auto'
});
}
});
// Mouse up - save position or let click handler toggle
$(document).on('mouseup', function(e) {
if (!mouseDown) return;
console.log('[RPG Mobile] >>> MOUSEUP EVENT FIRED <<<', { isDragging });
mouseDown = false;
if (isDragging) {
// Was dragging - save new position
const offset = $mobileToggle.offset();
const newPosition = {
left: offset.left + 'px',
top: offset.top + 'px'
};
extensionSettings.mobileFabPosition = newPosition;
saveSettings();
console.log('[RPG Mobile] Saved new FAB position (mouse):', newPosition);
// Re-enable transitions
setTimeout(() => {
$mobileToggle.css('transition', '');
}, 50);
isDragging = false;
// Prevent click from firing after drag
e.preventDefault();
e.stopPropagation();
// Add flag to prevent click handler from firing
$mobileToggle.data('just-dragged', true);
setTimeout(() => {
$mobileToggle.data('just-dragged', false);
}, 100);
}
// If not dragging, let the click handler toggle the panel
});
// Touch end - save position or toggle panel
$mobileToggle.on('touchend', function(e) {
console.log('[RPG Mobile] >>> TOUCHEND EVENT FIRED <<<', { isDragging });
// TEMPORARILY COMMENTED FOR DIAGNOSIS - might be blocking click fallback
// e.preventDefault();
console.log('[RPG Mobile] Touch end details:', { isDragging });
if (isDragging) {
// Was dragging - save new position
const offset = $mobileToggle.offset();
const newPosition = {
left: offset.left + 'px',
top: offset.top + 'px'
};
extensionSettings.mobileFabPosition = newPosition;
saveSettings();
console.log('[RPG Mobile] Saved new FAB position:', newPosition);
// Re-enable transitions
setTimeout(() => {
$mobileToggle.css('transition', '');
}, 50);
isDragging = false;
} else {
// Was a tap - toggle panel
console.log('[RPG Mobile] Quick tap detected - toggling panel');
if ($panel.hasClass('rpg-mobile-open')) {
// Close panel
$panel.removeClass('rpg-mobile-open');
$overlay.remove();
$mobileToggle.removeClass('active');
} else {
// Open panel
$panel.addClass('rpg-mobile-open');
$('body').append($overlay);
$mobileToggle.addClass('active');
// Close when clicking overlay
$overlay.on('click', function() {
$panel.removeClass('rpg-mobile-open');
$overlay.remove();
$mobileToggle.removeClass('active');
});
}
}
});
// Click handler - works on both mobile and desktop
$mobileToggle.on('click', function(e) {
// Skip if we just finished dragging
if ($mobileToggle.data('just-dragged')) {
console.log('[RPG Mobile] Click blocked - just finished dragging');
return;
}
console.log('[RPG Mobile] >>> CLICK EVENT FIRED <<<', {
windowWidth: window.innerWidth,
isMobileViewport: window.innerWidth <= 1000,
panelOpen: $panel.hasClass('rpg-mobile-open')
});
// Work on both mobile and desktop (removed viewport check)
if ($panel.hasClass('rpg-mobile-open')) { if ($panel.hasClass('rpg-mobile-open')) {
// Close panel console.log('[RPG Mobile] Click: Closing panel');
$panel.removeClass('rpg-mobile-open'); $panel.removeClass('rpg-mobile-open');
$overlay.remove(); $overlay.remove();
$mobileToggle.removeClass('active'); $mobileToggle.removeClass('active');
} else { } else {
// Open panel console.log('[RPG Mobile] Click: Opening panel');
$panel.addClass('rpg-mobile-open'); $panel.addClass('rpg-mobile-open');
$('body').append($overlay); $('body').append($overlay);
$mobileToggle.addClass('active'); $mobileToggle.addClass('active');
// Close when clicking overlay
$overlay.on('click', function() { $overlay.on('click', function() {
console.log('[RPG Mobile] Overlay clicked - closing panel');
$panel.removeClass('rpg-mobile-open'); $panel.removeClass('rpg-mobile-open');
$overlay.remove(); $overlay.remove();
$mobileToggle.removeClass('active'); $mobileToggle.removeClass('active');
@@ -1024,6 +1330,8 @@ function setupMobileToggle() {
// Transitioning from desktop to mobile - handle immediately for smooth transition // Transitioning from desktop to mobile - handle immediately for smooth transition
if (!wasMobile && isMobile) { if (!wasMobile && isMobile) {
console.log('[RPG Mobile] Transitioning desktop -> mobile');
// Remove desktop positioning classes // Remove desktop positioning classes
$panel.removeClass('rpg-position-right rpg-position-left rpg-position-top'); $panel.removeClass('rpg-position-right rpg-position-left rpg-position-top');
@@ -1035,9 +1343,26 @@ function setupMobileToggle() {
$mobileToggle.removeClass('active'); $mobileToggle.removeClass('active');
$('.rpg-mobile-overlay').remove(); $('.rpg-mobile-overlay').remove();
// Clear any inline styles that might be overriding CSS
$panel.attr('style', '');
console.log('[RPG Mobile] After cleanup:', {
panelClasses: $panel.attr('class'),
inlineStyles: $panel.attr('style'),
panelPosition: {
top: $panel.css('top'),
bottom: $panel.css('bottom'),
transform: $panel.css('transform'),
visibility: $panel.css('visibility')
}
});
// Set up mobile tabs IMMEDIATELY (no debounce delay) // Set up mobile tabs IMMEDIATELY (no debounce delay)
setupMobileTabs(); setupMobileTabs();
// Update icon for mobile state
updateCollapseToggleIcon();
wasMobile = isMobile; wasMobile = isMobile;
return; return;
} }
@@ -1078,7 +1403,23 @@ function setupMobileToggle() {
// Initialize mobile tabs if starting on mobile // Initialize mobile tabs if starting on mobile
const isMobile = window.innerWidth <= 1000; const isMobile = window.innerWidth <= 1000;
if (isMobile) { if (isMobile) {
const $panel = $('#rpg-companion-panel');
// Clear any inline styles
$panel.attr('style', '');
console.log('[RPG Mobile] Initial load on mobile viewport:', {
panelClasses: $panel.attr('class'),
inlineStyles: $panel.attr('style'),
panelPosition: {
top: $panel.css('top'),
bottom: $panel.css('top'),
transform: $panel.css('transform'),
visibility: $panel.css('visibility')
}
});
setupMobileTabs(); setupMobileTabs();
// Set initial icon for mobile
updateCollapseToggleIcon();
} }
} }
@@ -1225,14 +1566,78 @@ function setupCollapseToggle() {
const $panel = $('#rpg-companion-panel'); const $panel = $('#rpg-companion-panel');
const $icon = $collapseToggle.find('i'); const $icon = $collapseToggle.find('i');
$collapseToggle.on('click', function() { $collapseToggle.on('click', function(e) {
e.preventDefault();
e.stopPropagation();
const isMobile = window.innerWidth <= 1000; const isMobile = window.innerWidth <= 1000;
// On mobile: button acts as close button for mobile panel // On mobile: button toggles panel open/closed (same as desktop behavior)
if (isMobile) { if (isMobile) {
$panel.removeClass('rpg-mobile-open'); const isOpen = $panel.hasClass('rpg-mobile-open');
$('.rpg-mobile-overlay').remove(); console.log('[RPG Mobile] Collapse toggle clicked. Current state:', {
$('#rpg-mobile-toggle').removeClass('active'); isOpen,
panelClasses: $panel.attr('class'),
inlineStyles: $panel.attr('style'),
panelPosition: {
top: $panel.css('top'),
bottom: $panel.css('bottom'),
transform: $panel.css('transform'),
visibility: $panel.css('visibility')
}
});
if (isOpen) {
// Close panel
console.log('[RPG Mobile] Closing panel');
$panel.removeClass('rpg-mobile-open');
$('.rpg-mobile-overlay').remove();
$('#rpg-mobile-toggle').removeClass('active');
} else {
// Open panel
console.log('[RPG Mobile] Opening panel');
$panel.addClass('rpg-mobile-open');
const $overlay = $('<div class="rpg-mobile-overlay"></div>');
$('body').append($overlay);
// Debug: Check state after animation should complete
setTimeout(() => {
console.log('[RPG Mobile] 500ms after opening:', {
panelClasses: $panel.attr('class'),
hasOpenClass: $panel.hasClass('rpg-mobile-open'),
visibility: $panel.css('visibility'),
transform: $panel.css('transform'),
display: $panel.css('display'),
opacity: $panel.css('opacity')
});
}, 500);
// Close when clicking overlay
$overlay.on('click', function() {
console.log('[RPG Mobile] Overlay clicked - closing panel');
$panel.removeClass('rpg-mobile-open');
$overlay.remove();
updateCollapseToggleIcon();
});
}
// Update icon to reflect new state
updateCollapseToggleIcon();
console.log('[RPG Mobile] After toggle:', {
panelClasses: $panel.attr('class'),
inlineStyles: $panel.attr('style'),
panelPosition: {
top: $panel.css('top'),
bottom: $panel.css('bottom'),
transform: $panel.css('transform'),
visibility: $panel.css('visibility')
},
gameContainer: {
opacity: $('.rpg-game-container').css('opacity'),
visibility: $('.rpg-game-container').css('visibility')
}
});
return; return;
} }
@@ -1273,21 +1678,41 @@ function updateCollapseToggleIcon() {
const $collapseToggle = $('#rpg-collapse-toggle'); const $collapseToggle = $('#rpg-collapse-toggle');
const $panel = $('#rpg-companion-panel'); const $panel = $('#rpg-companion-panel');
const $icon = $collapseToggle.find('i'); const $icon = $collapseToggle.find('i');
const isCollapsed = $panel.hasClass('rpg-collapsed'); const isMobile = window.innerWidth <= 1000;
if (isCollapsed) { if (isMobile) {
// When collapsed, arrow points inward (to expand) // Mobile: slides from right, use same icon logic as desktop right panel
if ($panel.hasClass('rpg-position-right')) { const isOpen = $panel.hasClass('rpg-mobile-open');
$icon.removeClass('fa-chevron-right').addClass('fa-chevron-left'); console.log('[RPG Mobile] updateCollapseToggleIcon:', {
} else if ($panel.hasClass('rpg-position-left')) { isMobile: true,
$icon.removeClass('fa-chevron-left').addClass('fa-chevron-right'); isOpen,
settingIcon: isOpen ? 'chevron-right' : 'chevron-left'
});
if (isOpen) {
// Panel open - chevron points right (to close/slide right)
$icon.removeClass('fa-chevron-down fa-chevron-up fa-chevron-left').addClass('fa-chevron-right');
} else {
// Panel closed - chevron points left (to open/slide left)
$icon.removeClass('fa-chevron-down fa-chevron-up fa-chevron-right').addClass('fa-chevron-left');
} }
} else { } else {
// When expanded, arrow points outward (to collapse) // Desktop: icon direction based on panel position and collapsed state
if ($panel.hasClass('rpg-position-right')) { const isCollapsed = $panel.hasClass('rpg-collapsed');
$icon.removeClass('fa-chevron-left').addClass('fa-chevron-right');
} else if ($panel.hasClass('rpg-position-left')) { if (isCollapsed) {
$icon.removeClass('fa-chevron-right').addClass('fa-chevron-left'); // When collapsed, arrow points inward (to expand)
if ($panel.hasClass('rpg-position-right')) {
$icon.removeClass('fa-chevron-right').addClass('fa-chevron-left');
} else if ($panel.hasClass('rpg-position-left')) {
$icon.removeClass('fa-chevron-left').addClass('fa-chevron-right');
}
} else {
// When expanded, arrow points outward (to collapse)
if ($panel.hasClass('rpg-position-right')) {
$icon.removeClass('fa-chevron-left').addClass('fa-chevron-right');
} else if ($panel.hasClass('rpg-position-left')) {
$icon.removeClass('fa-chevron-right').addClass('fa-chevron-left');
}
} }
} }
} }
@@ -1344,10 +1769,17 @@ function updateSectionVisibility() {
function applyPanelPosition() { function applyPanelPosition() {
if (!$panelContainer) return; if (!$panelContainer) return;
const isMobile = window.innerWidth <= 1000;
// Remove all position classes // Remove all position classes
$panelContainer.removeClass('rpg-position-left rpg-position-right rpg-position-top'); $panelContainer.removeClass('rpg-position-left rpg-position-right rpg-position-top');
// Add the appropriate position class // On mobile, don't apply desktop position classes
if (isMobile) {
return;
}
// Desktop: Add the appropriate position class
$panelContainer.addClass(`rpg-position-${extensionSettings.panelPosition}`); $panelContainer.addClass(`rpg-position-${extensionSettings.panelPosition}`);
// Update collapse toggle icon direction for new position // Update collapse toggle icon direction for new position
+59 -42
View File
@@ -2931,20 +2931,23 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
/* Hide mobile toggle on desktop, show on mobile */ /* Hide mobile toggle on desktop, show on mobile */
.rpg-mobile-toggle { .rpg-mobile-toggle {
display: none; display: none;
align-items: center;
justify-content: center;
position: fixed; position: fixed;
bottom: 5rem; /* Position set by JavaScript based on saved settings */
right: 1.25rem; width: 44px;
width: 3.5rem; height: 44px;
height: 3.5rem;
border-radius: 50%; border-radius: 50%;
background: var(--rpg-accent, #2c3e50); background: var(--SmartThemeBlurTintColor);
border: 3px solid var(--rpg-border, #34495e); border: 2px solid var(--SmartThemeBorderColor);
color: var(--rpg-text, #ecf0f1); color: var(--rpg-text, #ecf0f1);
font-size: 1.5rem; font-size: 1.25rem;
cursor: pointer; cursor: pointer;
z-index: 999; z-index: 10002;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease; transition: opacity 0.3s ease, transform 0.2s ease;
user-select: none; /* Prevent text selection while dragging */
-webkit-user-select: none;
} }
.rpg-mobile-toggle:hover { .rpg-mobile-toggle:hover {
@@ -2975,16 +2978,31 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
} }
/* Mobile-specific panel behavior - matches SillyTavern's 1000px breakpoint */ /* Mobile-specific panel behavior - matches SillyTavern's 1000px breakpoint */
/* CACHE BUST v2025-01-16 */
@media (max-width: 1000px) { @media (max-width: 1000px) {
/* ======================================== /* ========================================
MOBILE PANEL FOUNDATION MOBILE PANEL FOUNDATION
======================================== */ ======================================== */
/* Show the FAB on mobile */ /* Show the mobile FAB toggle button */
.rpg-mobile-toggle { .rpg-mobile-toggle {
display: flex; display: flex;
align-items: center; }
justify-content: center;
/* Hide FAB when panel is open */
body:has(.rpg-panel.rpg-mobile-open) .rpg-mobile-toggle {
opacity: 0;
pointer-events: none;
}
/* Hide internal collapse toggle when panel is closed on mobile */
.rpg-collapse-toggle {
display: none !important;
}
/* Show internal collapse toggle when panel is open on mobile */
.rpg-panel.rpg-mobile-open .rpg-collapse-toggle {
display: flex !important;
} }
/* Show overlay when needed */ /* Show overlay when needed */
@@ -2998,36 +3016,37 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
margin-left: 0 !important; margin-left: 0 !important;
} }
/* CLEAN SLATE: Reset ALL desktop positioning */ /* Mobile panel - slide from right like desktop */
.rpg-panel { .rpg-panel {
position: fixed !important; position: fixed !important;
left: 0 !important; top: var(--topBarBlockSize) !important;
right: 0 !important; right: 0 !important;
top: auto !important; bottom: 0 !important;
width: 100dvw !important; left: auto !important;
max-width: 100dvw !important;
min-width: unset !important; /* Mobile panel sizing */
width: 85dvw !important;
max-width: 400px !important;
height: calc(100dvh - var(--topBarBlockSize)) !important; height: calc(100dvh - var(--topBarBlockSize)) !important;
max-height: calc(100dvh - var(--topBarBlockSize)) !important;
border-radius: 20px 20px 0 0; /* Start off-screen to the right */
overflow-y: auto; transform: translateX(100%) !important;
transition: transform 0.3s ease-in-out !important;
overflow-y: auto !important;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
box-shadow: 0 -5px 20px var(--rpg-shadow);
/* Styling */
border-radius: 20px 0 0 0;
border-left: 1px solid var(--SmartThemeBorderColor); border-left: 1px solid var(--SmartThemeBorderColor);
border-right: 1px solid var(--SmartThemeBorderColor);
border-top: 1px solid var(--SmartThemeBorderColor); border-top: 1px solid var(--SmartThemeBorderColor);
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2)); backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
box-shadow: -5px 0 20px var(--rpg-shadow);
/* DRAWER POSITIONING: Starts below viewport */
bottom: calc(-100dvh + var(--topBarBlockSize));
/* ONLY transition bottom property */
transition: bottom 0.3s ease-in-out;
} }
/* Show panel when opened - slides up */ /* Show panel when opened - slides in from right */
.rpg-panel.rpg-mobile-open { .rpg-panel.rpg-mobile-open {
bottom: 0; transform: translateX(0) !important;
z-index: 50; z-index: 50;
} }
@@ -3043,14 +3062,14 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
pointer-events: auto !important; pointer-events: auto !important;
} }
/* Reposition collapse toggle on mobile as close button inside panel */ /* Collapse toggle on mobile - right side, always visible */
.rpg-collapse-toggle { .rpg-collapse-toggle {
display: flex !important; display: flex !important;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
position: absolute; position: fixed !important;
top: 12px; top: calc(var(--topBarBlockSize) + 60px) !important;
right: 12px; right: 12px !important;
left: auto !important; left: auto !important;
width: 44px; width: 44px;
height: 44px; height: 44px;
@@ -3059,9 +3078,11 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
border-radius: 50%; border-radius: 50%;
background: var(--SmartThemeBlurTintColor); background: var(--SmartThemeBlurTintColor);
border: 2px solid var(--SmartThemeBorderColor); border: 2px solid var(--SmartThemeBorderColor);
z-index: 100; z-index: 9999 !important;
transition: all 0.2s ease; transition: all 0.2s ease;
transform: none !important; transform: none !important;
pointer-events: auto !important;
cursor: pointer !important;
} }
.rpg-collapse-toggle:hover { .rpg-collapse-toggle:hover {
@@ -3073,13 +3094,9 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
transform: scale(0.95) !important; transform: scale(0.95) !important;
} }
/* Change icon to X on mobile */ /* Mobile icon styling - use chevrons for drawer UX */
.rpg-collapse-toggle i { .rpg-collapse-toggle i {
transform: none !important; transform: none !important;
}
.rpg-collapse-toggle i::before {
content: "\f00d" !important; /* fa-times (X icon) */
font-size: 20px; font-size: 20px;
} }
@@ -3380,7 +3397,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
/* Extra small screens - adjust FAB position */ /* Extra small screens - adjust FAB position */
@media (max-width: 480px) { @media (max-width: 480px) {
.rpg-mobile-toggle { .rpg-mobile-toggle {
bottom: 4.375rem; bottom: 6rem;
right: 0.938rem; right: 0.938rem;
width: 3.25rem; width: 3.25rem;
height: 3.25rem; height: 3.25rem;