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:
@@ -18,3 +18,9 @@
|
|||||||
|
|
||||||
# Node modules (if any)
|
# Node modules (if any)
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Claude
|
||||||
|
CLAUDE.md
|
||||||
@@ -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,8 +993,274 @@ 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')) {
|
if ($panel.hasClass('rpg-mobile-open')) {
|
||||||
// Close panel
|
// Close panel
|
||||||
$panel.removeClass('rpg-mobile-open');
|
$panel.removeClass('rpg-mobile-open');
|
||||||
@@ -1009,6 +1279,42 @@ function setupMobileToggle() {
|
|||||||
$mobileToggle.removeClass('active');
|
$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')) {
|
||||||
|
console.log('[RPG Mobile] Click: Closing panel');
|
||||||
|
$panel.removeClass('rpg-mobile-open');
|
||||||
|
$overlay.remove();
|
||||||
|
$mobileToggle.removeClass('active');
|
||||||
|
} else {
|
||||||
|
console.log('[RPG Mobile] Click: Opening panel');
|
||||||
|
$panel.addClass('rpg-mobile-open');
|
||||||
|
$('body').append($overlay);
|
||||||
|
$mobileToggle.addClass('active');
|
||||||
|
|
||||||
|
$overlay.on('click', function() {
|
||||||
|
console.log('[RPG Mobile] Overlay clicked - closing panel');
|
||||||
|
$panel.removeClass('rpg-mobile-open');
|
||||||
|
$overlay.remove();
|
||||||
|
$mobileToggle.removeClass('active');
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle viewport resize to manage desktop/mobile transitions
|
// Handle viewport resize to manage desktop/mobile transitions
|
||||||
@@ -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) {
|
||||||
|
const isOpen = $panel.hasClass('rpg-mobile-open');
|
||||||
|
console.log('[RPG Mobile] Collapse toggle clicked. Current state:', {
|
||||||
|
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');
|
$panel.removeClass('rpg-mobile-open');
|
||||||
$('.rpg-mobile-overlay').remove();
|
$('.rpg-mobile-overlay').remove();
|
||||||
$('#rpg-mobile-toggle').removeClass('active');
|
$('#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,6 +1678,25 @@ 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 isMobile = window.innerWidth <= 1000;
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
// Mobile: slides from right, use same icon logic as desktop right panel
|
||||||
|
const isOpen = $panel.hasClass('rpg-mobile-open');
|
||||||
|
console.log('[RPG Mobile] updateCollapseToggleIcon:', {
|
||||||
|
isMobile: true,
|
||||||
|
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 {
|
||||||
|
// Desktop: icon direction based on panel position and collapsed state
|
||||||
const isCollapsed = $panel.hasClass('rpg-collapsed');
|
const isCollapsed = $panel.hasClass('rpg-collapsed');
|
||||||
|
|
||||||
if (isCollapsed) {
|
if (isCollapsed) {
|
||||||
@@ -1291,6 +1715,7 @@ function updateCollapseToggleIcon() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the visibility of the entire panel.
|
* Updates the visibility of the entire panel.
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user