diff --git a/index.js b/index.js
index 488a744..95458ed 100644
--- a/index.js
+++ b/index.js
@@ -1010,6 +1010,211 @@ function setupMobileToggle() {
});
}
});
+
+ // Handle viewport resize to manage desktop/mobile transitions
+ let wasMobile = window.innerWidth <= 1000;
+ let resizeTimer;
+
+ $(window).on('resize', function() {
+ clearTimeout(resizeTimer);
+
+ const isMobile = window.innerWidth <= 1000;
+ const $panel = $('#rpg-companion-panel');
+ const $mobileToggle = $('#rpg-mobile-toggle');
+
+ // Transitioning from desktop to mobile - handle immediately for smooth transition
+ if (!wasMobile && isMobile) {
+ // Remove desktop positioning classes
+ $panel.removeClass('rpg-position-right rpg-position-left rpg-position-top');
+
+ // Clear collapsed state - mobile doesn't use collapse
+ $panel.removeClass('rpg-collapsed');
+
+ // Close panel on mobile - CSS handles smooth transition
+ $panel.removeClass('rpg-mobile-open');
+ $mobileToggle.removeClass('active');
+ $('.rpg-mobile-overlay').remove();
+
+ // Set up mobile tabs IMMEDIATELY (no debounce delay)
+ setupMobileTabs();
+
+ wasMobile = isMobile;
+ return;
+ }
+
+ // For mobile to desktop transition, use debounce
+ resizeTimer = setTimeout(function() {
+ const isMobile = window.innerWidth <= 1000;
+
+ // Transitioning from mobile to desktop
+ if (wasMobile && !isMobile) {
+ // Disable transitions to prevent left→right slide animation
+ $panel.css('transition', 'none');
+
+ $panel.removeClass('rpg-mobile-open');
+ $mobileToggle.removeClass('active');
+ $('.rpg-mobile-overlay').remove();
+
+ // Restore desktop positioning class
+ const position = extensionSettings.panelPosition || 'right';
+ $panel.addClass('rpg-position-' + position);
+
+ // Remove mobile tabs structure
+ removeMobileTabs();
+
+ // Force reflow to apply position instantly
+ $panel[0].offsetHeight;
+
+ // Re-enable transitions after positioned
+ setTimeout(function() {
+ $panel.css('transition', '');
+ }, 50);
+ }
+
+ wasMobile = isMobile;
+ }, 150); // Debounce only for mobile→desktop
+ });
+
+ // Initialize mobile tabs if starting on mobile
+ const isMobile = window.innerWidth <= 1000;
+ if (isMobile) {
+ setupMobileTabs();
+ }
+}
+
+/**
+ * Sets up mobile tab navigation for organizing content.
+ * Only runs on mobile viewports (<=1000px).
+ */
+function setupMobileTabs() {
+ const isMobile = window.innerWidth <= 1000;
+ if (!isMobile) return;
+
+ // Check if tabs already exist
+ if ($('.rpg-mobile-tabs').length > 0) return;
+
+ const $panel = $('#rpg-companion-panel');
+ const $contentBox = $panel.find('.rpg-content-box');
+
+ // Get existing sections
+ const $userStats = $('#rpg-user-stats');
+ const $infoBox = $('#rpg-info-box');
+ const $thoughts = $('#rpg-thoughts');
+
+ // If no sections exist, nothing to organize
+ if ($userStats.length === 0 && $infoBox.length === 0 && $thoughts.length === 0) {
+ return;
+ }
+
+ // Create tab navigation (only show tabs for sections that exist)
+ const tabs = [];
+ const hasInfoOrCharacters = $infoBox.length > 0 || $thoughts.length > 0;
+
+ if ($userStats.length > 0) {
+ tabs.push('');
+ }
+ // Combine Info and Characters into one tab
+ if (hasInfoOrCharacters) {
+ tabs.push('');
+ }
+
+ const $tabNav = $('
' + tabs.join('') + '
');
+
+ // Determine which tab should be active
+ let firstTab = '';
+ if ($userStats.length > 0) firstTab = 'stats';
+ else if (hasInfoOrCharacters) firstTab = 'info-characters';
+
+ // Create tab content wrappers
+ const $statsTab = $('');
+ const $infoCharactersTab = $('');
+
+ // Create combined content wrapper for Info and Characters
+ const $combinedWrapper = $('');
+
+ // Move sections into their respective tabs (detach to preserve event handlers)
+ if ($userStats.length > 0) {
+ $statsTab.append($userStats.detach());
+ $userStats.show();
+ }
+ if ($infoBox.length > 0) {
+ $combinedWrapper.append($infoBox.detach());
+ $infoBox.show();
+ }
+ if ($thoughts.length > 0) {
+ $combinedWrapper.append($thoughts.detach());
+ $thoughts.show();
+ }
+
+ // Add combined wrapper to the info-characters tab
+ if (hasInfoOrCharacters) {
+ $infoCharactersTab.append($combinedWrapper);
+ }
+
+ // Hide dividers on mobile
+ $('.rpg-divider').hide();
+
+ // Build mobile tab structure
+ const $mobileContainer = $('');
+ $mobileContainer.append($tabNav);
+
+ // Only append tab content wrappers that have content
+ if ($userStats.length > 0) $mobileContainer.append($statsTab);
+ if (hasInfoOrCharacters) $mobileContainer.append($infoCharactersTab);
+
+ // Insert mobile tab structure at the beginning of content box
+ $contentBox.prepend($mobileContainer);
+
+ // Handle tab switching
+ $tabNav.find('.rpg-mobile-tab').on('click', function() {
+ const tabName = $(this).data('tab');
+
+ // Update active tab button
+ $tabNav.find('.rpg-mobile-tab').removeClass('active');
+ $(this).addClass('active');
+
+ // Update active tab content
+ $mobileContainer.find('.rpg-mobile-tab-content').removeClass('active');
+ $mobileContainer.find('[data-tab-content="' + tabName + '"]').addClass('active');
+ });
+}
+
+/**
+ * Removes mobile tab navigation and restores desktop layout.
+ */
+function removeMobileTabs() {
+ // Get sections from tabs before removing
+ const $userStats = $('#rpg-user-stats').detach();
+ const $infoBox = $('#rpg-info-box').detach();
+ const $thoughts = $('#rpg-thoughts').detach();
+
+ // Remove mobile tab container
+ $('.rpg-mobile-container').remove();
+
+ // Get dividers
+ const $dividerStats = $('#rpg-divider-stats');
+ const $dividerInfo = $('#rpg-divider-info');
+
+ // Restore original sections to content box in correct order
+ const $contentBox = $('.rpg-content-box');
+
+ // Re-insert sections in original order
+ if ($dividerStats.length) {
+ $dividerStats.before($userStats);
+ $dividerInfo.before($infoBox);
+ $contentBox.append($thoughts);
+ } else {
+ // Fallback if dividers don't exist
+ $contentBox.prepend($thoughts);
+ $contentBox.prepend($infoBox);
+ $contentBox.prepend($userStats);
+ }
+
+ // Show sections and dividers
+ $userStats.show();
+ $infoBox.show();
+ $thoughts.show();
+ $('.rpg-divider').show();
}
/**
@@ -1021,6 +1226,17 @@ function setupCollapseToggle() {
const $icon = $collapseToggle.find('i');
$collapseToggle.on('click', function() {
+ const isMobile = window.innerWidth <= 1000;
+
+ // On mobile: button acts as close button for mobile panel
+ if (isMobile) {
+ $panel.removeClass('rpg-mobile-open');
+ $('.rpg-mobile-overlay').remove();
+ $('#rpg-mobile-toggle').removeClass('active');
+ return;
+ }
+
+ // Desktop behavior: collapse/expand side panel
const isCollapsed = $panel.hasClass('rpg-collapsed');
if (isCollapsed) {
diff --git a/style.css b/style.css
index 08e70a3..6029671 100644
--- a/style.css
+++ b/style.css
@@ -2974,8 +2974,12 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
backdrop-filter: blur(2px);
}
-/* Mobile-specific panel behavior */
-@media (max-width: 768px) {
+/* Mobile-specific panel behavior - matches SillyTavern's 1000px breakpoint */
+@media (max-width: 1000px) {
+ /* ========================================
+ MOBILE PANEL FOUNDATION
+ ======================================== */
+
/* Show the FAB on mobile */
.rpg-mobile-toggle {
display: flex;
@@ -2988,32 +2992,388 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
display: block;
}
- /* Hide panel by default on mobile */
+ /* Remove margin adjustments on mobile - content takes full width */
+ body:has(.rpg-panel) #sheld {
+ margin-right: 0 !important;
+ margin-left: 0 !important;
+ }
+
+ /* CLEAN SLATE: Reset ALL desktop positioning */
.rpg-panel {
- transform: translateY(100%);
- transition: transform 0.3s ease-in-out;
- }
-
- /* Show panel when opened */
- .rpg-panel.rpg-mobile-open {
- transform: translateY(0);
- z-index: 999;
- }
-
- /* On mobile, panel is always at bottom */
- .rpg-panel.rpg-position-right,
- .rpg-panel.rpg-position-left,
- .rpg-panel.rpg-position-top {
- position: fixed;
- top: var(--topBarBlockSize);
- bottom: 0;
- left: 0;
- right: 0;
- width: 100%;
- max-width: 100%;
- border-radius: 1.25em 1.25em 0 0;
- overflow-y: scroll;
+ position: fixed !important;
+ left: 0 !important;
+ right: 0 !important;
+ top: auto !important;
+ width: 100dvw !important;
+ max-width: 100dvw !important;
+ min-width: unset !important;
+ height: calc(100dvh - var(--topBarBlockSize)) !important;
+ max-height: calc(100dvh - var(--topBarBlockSize)) !important;
+ border-radius: 20px 20px 0 0;
+ overflow-y: auto;
-webkit-overflow-scrolling: touch;
+ box-shadow: 0 -5px 20px var(--rpg-shadow);
+ border-left: 1px solid var(--SmartThemeBorderColor);
+ border-right: 1px solid var(--SmartThemeBorderColor);
+ border-top: 1px solid var(--SmartThemeBorderColor);
+ backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
+
+ /* 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 */
+ .rpg-panel.rpg-mobile-open {
+ bottom: 0;
+ z-index: 50;
+ }
+
+ /* Disable collapsed state on mobile */
+ .rpg-panel.rpg-collapsed {
+ max-width: 100dvw !important;
+ min-width: unset !important;
+ width: 100dvw !important;
+ }
+
+ .rpg-panel.rpg-collapsed .rpg-game-container {
+ opacity: 1 !important;
+ pointer-events: auto !important;
+ }
+
+ /* Reposition collapse toggle on mobile as close button inside panel */
+ .rpg-collapse-toggle {
+ display: flex !important;
+ align-items: center;
+ justify-content: center;
+ position: absolute;
+ top: 12px;
+ right: 12px;
+ left: auto !important;
+ width: 44px;
+ height: 44px;
+ min-width: 44px;
+ min-height: 44px;
+ border-radius: 50%;
+ background: var(--SmartThemeBlurTintColor);
+ border: 2px solid var(--SmartThemeBorderColor);
+ z-index: 100;
+ transition: all 0.2s ease;
+ transform: none !important;
+ }
+
+ .rpg-collapse-toggle:hover {
+ background: rgba(255, 255, 255, 0.1);
+ transform: scale(1.05) !important;
+ }
+
+ .rpg-collapse-toggle:active {
+ transform: scale(0.95) !important;
+ }
+
+ /* Change icon to X on mobile */
+ .rpg-collapse-toggle i {
+ transform: none !important;
+ }
+
+ .rpg-collapse-toggle i::before {
+ content: "\f00d" !important; /* fa-times (X icon) */
+ font-size: 20px;
+ }
+
+ /* ========================================
+ MOBILE TAB NAVIGATION
+ ======================================== */
+
+ /* Mobile tab container wrapper */
+ .rpg-mobile-container {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ min-height: 0;
+ margin: -12px -12px 16px -12px;
+ }
+
+ /* Tab container at top of panel */
+ .rpg-mobile-tabs {
+ display: flex;
+ position: sticky;
+ top: 0;
+ z-index: 10;
+ background: var(--SmartThemeBlurTintColor);
+ backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 1.5));
+ border-bottom: 2px solid var(--SmartThemeBorderColor);
+ margin: 0;
+ padding: 0;
+ }
+
+ .rpg-mobile-tab {
+ flex: 1;
+ height: 44px;
+ min-height: 44px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ font-size: 14px;
+ font-weight: 500;
+ color: var(--SmartThemeBodyColor);
+ background: transparent;
+ border: none;
+ border-bottom: 3px solid transparent;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ padding: 0 8px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ }
+
+ .rpg-mobile-tab:active {
+ transform: scale(0.97);
+ }
+
+ .rpg-mobile-tab.active {
+ color: var(--SmartThemeQuoteColor);
+ border-bottom-color: var(--SmartThemeQuoteColor);
+ background: rgba(255, 255, 255, 0.05);
+ }
+
+ .rpg-mobile-tab i {
+ font-size: 16px;
+ }
+
+ /* Tab content sections */
+ .rpg-mobile-tab-content {
+ display: none;
+ animation: fadeIn 0.2s ease;
+ }
+
+ .rpg-mobile-tab-content.active {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ min-height: 0;
+ padding: 12px;
+ }
+
+ @keyframes fadeIn {
+ from { opacity: 0; transform: translateY(10px); }
+ to { opacity: 1; transform: translateY(0); }
+ }
+
+ /* Combined Info & Characters wrapper */
+ .rpg-mobile-combined-content {
+ display: flex;
+ flex-direction: column;
+ gap: 0;
+ height: 100%;
+ min-height: 0;
+ }
+
+ /* Info Box takes fixed 50% of vertical space */
+ .rpg-mobile-combined-content > #rpg-info-box {
+ flex: 0 0 50%;
+ min-height: 0;
+ overflow-y: auto;
+ padding-bottom: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5em;
+ }
+
+ /* Characters section takes remaining 50% */
+ .rpg-mobile-combined-content > #rpg-thoughts {
+ flex: 1;
+ min-height: 0;
+ overflow-y: auto;
+ padding-bottom: 16px;
+ }
+
+ /* Add divider between Info and Characters */
+ .rpg-mobile-combined-content > .rpg-section:not(:last-child) {
+ border-bottom: 1px solid var(--SmartThemeBorderColor);
+ margin-bottom: 16px;
+ }
+
+ /* Hide dividers on mobile (tabs handle separation) */
+ .rpg-divider {
+ display: none;
+ }
+
+ /* ========================================
+ MOBILE INFO BOX IMPROVEMENTS
+ ======================================== */
+
+ /* Rows scale proportionally to fill Info Box */
+ .rpg-dashboard-row-1 {
+ flex: 1.2 !important; /* Slightly more space for 4 widgets */
+ display: flex !important;
+ gap: 0.25em;
+ }
+
+ .rpg-dashboard-row-2 {
+ flex: 0.8 !important; /* Less space for 1 widget */
+ display: flex !important;
+ }
+
+ /* Widgets fill their row height */
+ .rpg-dashboard-row-1 .rpg-dashboard-widget,
+ .rpg-dashboard-row-2 .rpg-dashboard-widget {
+ height: 100% !important;
+ flex: 1 !important;
+ display: flex !important;
+ flex-direction: column !important;
+ justify-content: center;
+ }
+
+ /* ========================================
+ MOBILE STATS TAB LAYOUT IMPROVEMENTS
+ ======================================== */
+
+ /* Make the entire stats section a grid */
+ .rpg-stats-section {
+ display: grid !important;
+ grid-template-columns: 40% 60%; /* Left for inventory/mood, right for attributes */
+ grid-template-rows: auto auto auto auto; /* Portrait, stat bars, inventory, mood */
+ gap: 12px;
+ padding: 16px 12px;
+ }
+
+ /* Use display: contents so children participate in grid */
+ .rpg-stats-header,
+ .rpg-stats-content {
+ display: contents !important;
+ }
+
+ /* Portrait row - centered at top, full width */
+ .rpg-stats-header-left {
+ grid-column: 1 / 3;
+ grid-row: 1;
+ display: flex !important;
+ justify-content: center;
+ align-items: center;
+ gap: 4px;
+ }
+
+ .rpg-user-portrait {
+ width: 64px;
+ height: 64px;
+ }
+
+ /* Hide stats title on mobile */
+ .rpg-stats-title {
+ display: none;
+ }
+
+ /* Stat bars row - full width */
+ .rpg-stats-left {
+ grid-column: 1 / 3;
+ grid-row: 2;
+ display: contents !important;
+ }
+
+ .rpg-stats-grid {
+ grid-column: 1 / 3;
+ grid-row: 2;
+ }
+
+ /* Inventory - bottom left */
+ .rpg-inventory-box {
+ grid-column: 1;
+ grid-row: 3;
+ margin: 0;
+ min-height: auto;
+ max-height: none;
+ max-width: 100%; /* Override 12.5rem restriction */
+ }
+
+ /* Mood - below inventory on left */
+ .rpg-mood {
+ grid-column: 1;
+ grid-row: 4;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ min-width: 0;
+ }
+
+ /* Attributes - right side, spanning rows 3-4 */
+ .rpg-stats-right {
+ grid-column: 2;
+ grid-row: 3 / 5;
+ display: contents !important;
+ }
+
+ .rpg-classic-stats {
+ grid-column: 2;
+ grid-row: 3 / 5;
+ }
+
+ /* Attributes as vertical list instead of grid */
+ .rpg-classic-stats-grid {
+ display: flex !important;
+ flex-direction: column !important;
+ gap: 10px;
+ margin: 12px 0;
+ grid-template-columns: none !important;
+ grid-template-rows: none !important;
+ }
+
+ /* Each attribute as horizontal row */
+ .rpg-classic-stat {
+ display: flex !important;
+ flex-direction: row !important;
+ align-items: center;
+ justify-content: space-between;
+ padding: 12px 16px;
+ gap: 12px;
+ min-height: auto;
+ height: auto;
+ background: var(--SmartThemeBlurTintColor);
+ border: 1px solid var(--SmartThemeBorderColor);
+ border-radius: 10px;
+ }
+
+ /* Label on left */
+ .rpg-classic-stat-label {
+ font-size: 14px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ min-width: 50px;
+ flex-shrink: 0;
+ }
+
+ /* Value in middle */
+ .rpg-classic-stat-value {
+ font-size: 24px;
+ font-weight: 700;
+ color: var(--SmartThemeQuoteColor);
+ flex: 1;
+ text-align: center;
+ }
+
+ /* Buttons on right */
+ .rpg-classic-stat-controls {
+ display: flex;
+ gap: 8px;
+ flex-shrink: 0;
+ }
+
+ .rpg-classic-stat-btn {
+ min-width: 40px !important;
+ min-height: 40px !important;
+ width: 40px;
+ height: 40px;
+ font-size: 16px;
+ font-weight: 700;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
}
}