v2.1.3 - Improved thought bubble positioning and responsiveness
- Align thought bubbles with avatar top instead of center for better visibility - Fix issue where bubbles extend above avatar when scrolling is limited - Change thought circles to horizontal layout for cleaner visual flow - Add responsive positioning that adapts to screen width changes - Implement smart viewport detection to prevent cutoff at narrow widths
This commit is contained in:
@@ -7,6 +7,13 @@ An immersive RPG extension for browsers that tracks character stats, scene infor
|
|||||||
|
|
||||||
## 🆕 What's New
|
## 🆕 What's New
|
||||||
|
|
||||||
|
### v2.1.3
|
||||||
|
- **Improved Thought Bubble Positioning**: Thought bubbles now align with the top of the character's avatar/name
|
||||||
|
- **Better Visibility**: Fixed issue where thought bubbles would extend above the avatar when scrolling is limited
|
||||||
|
- **Horizontal Thought Circles**: Thought circles now display horizontally for a cleaner visual flow
|
||||||
|
- **Responsive Positioning**: Thought bubbles dynamically adjust to screen width changes and stay visible at all resolutions
|
||||||
|
- **Smart Viewport Detection**: Bubbles automatically reposition to avoid being cut off at narrow window widths
|
||||||
|
|
||||||
### v2.1.2
|
### v2.1.2
|
||||||
- Added optional toggle for Relationship Status Fields in Edit Trackers
|
- Added optional toggle for Relationship Status Fields in Edit Trackers
|
||||||
- Relationship fields and emoji badges can now be disabled/enabled like other trackers
|
- Relationship fields and emoji badges can now be disabled/enabled like other trackers
|
||||||
|
|||||||
+1
-1
@@ -6,6 +6,6 @@
|
|||||||
"js": "index.js",
|
"js": "index.js",
|
||||||
"css": "style.css",
|
"css": "style.css",
|
||||||
"author": "Marysia",
|
"author": "Marysia",
|
||||||
"version": "2.1.2",
|
"version": "2.1.3",
|
||||||
"homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern"
|
"homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1142,7 +1142,7 @@ export function createThoughtPanel($message, thoughtsArray) {
|
|||||||
const panelWidth = 350;
|
const panelWidth = 350;
|
||||||
const panelMargin = 20;
|
const panelMargin = 20;
|
||||||
|
|
||||||
let top = avatarRect.top + (avatarRect.height / 2);
|
let top = avatarRect.top; // Align top of panel with top of avatar
|
||||||
let left;
|
let left;
|
||||||
let right;
|
let right;
|
||||||
let useRightPosition = false;
|
let useRightPosition = false;
|
||||||
@@ -1176,50 +1176,47 @@ export function createThoughtPanel($message, thoughtsArray) {
|
|||||||
});
|
});
|
||||||
} else if (panelPosition === 'left') {
|
} else if (panelPosition === 'left') {
|
||||||
// Main panel is on left, so thought bubble goes to RIGHT side
|
// Main panel is on left, so thought bubble goes to RIGHT side
|
||||||
// Mirror the left side positioning: bubble should be same distance from avatar
|
|
||||||
// but on the opposite side, extending to the right
|
|
||||||
const chatContainer = $('#chat')[0];
|
const chatContainer = $('#chat')[0];
|
||||||
const chatRect = chatContainer ? chatContainer.getBoundingClientRect() : { right: window.innerWidth };
|
const chatRect = chatContainer ? chatContainer.getBoundingClientRect() : { right: window.innerWidth };
|
||||||
|
|
||||||
// Position bubble starting from chat edge, extending right
|
// Calculate how much space is available to the right of the chat
|
||||||
left = chatRect.right + panelMargin; // Start at chat's right edge + margin
|
const spaceRight = window.innerWidth - chatRect.right;
|
||||||
useRightPosition = false; // Use left positioning so it extends right
|
|
||||||
iconLeft = chatRect.right + 10; // Icon just at the chat edge
|
// If there's enough space, position normally; otherwise, adjust
|
||||||
|
if (spaceRight >= panelWidth + panelMargin * 2) {
|
||||||
|
left = chatRect.right + panelMargin;
|
||||||
|
} else {
|
||||||
|
// Not enough space on the right, position closer to chat or slightly overlapping
|
||||||
|
left = Math.max(chatRect.right - panelWidth - panelMargin, window.innerWidth - panelWidth - 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
useRightPosition = false;
|
||||||
|
iconLeft = chatRect.right + 10;
|
||||||
$thoughtPanel.addClass('rpg-thought-panel-right');
|
$thoughtPanel.addClass('rpg-thought-panel-right');
|
||||||
$thoughtIcon.addClass('rpg-thought-icon-right');
|
$thoughtIcon.addClass('rpg-thought-icon-right');
|
||||||
|
|
||||||
// Position circles to flow from left (toward chat/avatar) to right (toward panel)
|
// Circles use default CSS positioning
|
||||||
$thoughtPanel.find('.rpg-thought-circles').css({
|
|
||||||
top: 'calc(50% - 50px)',
|
|
||||||
left: '-25px',
|
|
||||||
bottom: 'auto',
|
|
||||||
right: 'auto'
|
|
||||||
});
|
|
||||||
// Mirror the circle flow for right side (left-to-right)
|
|
||||||
$thoughtPanel.find('.rpg-thought-circles').css('align-items', 'flex-start');
|
|
||||||
$thoughtPanel.find('.rpg-circle-1').css({ 'align-self': 'flex-start', 'margin-right': '0', 'margin-left': '0' });
|
|
||||||
$thoughtPanel.find('.rpg-circle-2').css({ 'align-self': 'flex-start', 'margin-right': '0', 'margin-left': '4px' });
|
|
||||||
$thoughtPanel.find('.rpg-circle-3').css({ 'align-self': 'flex-start', 'margin-right': '0', 'margin-left': '8px' });
|
|
||||||
} else {
|
} else {
|
||||||
// Main panel is on right, so thought bubble goes on left (near avatar)
|
// Main panel is on right, so thought bubble goes on left (near avatar)
|
||||||
left = avatarRect.left - panelWidth - panelMargin;
|
const chatContainer = $('#chat')[0];
|
||||||
iconLeft = avatarRect.left - 40;
|
const chatRect = chatContainer ? chatContainer.getBoundingClientRect() : { left: 0 };
|
||||||
|
|
||||||
|
// Calculate how much space is available to the left of the chat
|
||||||
|
const spaceLeft = chatRect.left;
|
||||||
|
|
||||||
|
// If there's enough space, position normally; otherwise, adjust
|
||||||
|
if (spaceLeft >= panelWidth + panelMargin * 2) {
|
||||||
|
left = avatarRect.left - panelWidth - panelMargin;
|
||||||
|
} else {
|
||||||
|
// Not enough space on the left, position it within visible area
|
||||||
|
left = Math.max(10, avatarRect.left - panelWidth - panelMargin);
|
||||||
|
}
|
||||||
|
|
||||||
|
iconLeft = Math.max(10, avatarRect.left - 40);
|
||||||
$thoughtPanel.addClass('rpg-thought-panel-left');
|
$thoughtPanel.addClass('rpg-thought-panel-left');
|
||||||
$thoughtIcon.addClass('rpg-thought-icon-left');
|
$thoughtIcon.addClass('rpg-thought-icon-left');
|
||||||
|
|
||||||
// Position circles to flow from avatar (left) to bubble (more left)
|
// Circles use default CSS positioning
|
||||||
// Circles should flow right-to-left when bubble is on left
|
|
||||||
$thoughtPanel.find('.rpg-thought-circles').css({
|
|
||||||
top: 'calc(50% - 50px)',
|
|
||||||
right: '-25px',
|
|
||||||
bottom: 'auto',
|
|
||||||
left: 'auto'
|
|
||||||
});
|
|
||||||
// Keep the circle flow for left side (right-to-left) - default from CSS
|
|
||||||
$thoughtPanel.find('.rpg-thought-circles').css('align-items', 'flex-end');
|
|
||||||
$thoughtPanel.find('.rpg-circle-1').css({ 'align-self': 'flex-end', 'margin-left': '0', 'margin-right': '0' });
|
|
||||||
$thoughtPanel.find('.rpg-circle-2').css({ 'align-self': 'flex-end', 'margin-left': '0', 'margin-right': '4px' });
|
|
||||||
$thoughtPanel.find('.rpg-circle-3').css({ 'align-self': 'flex-end', 'margin-left': '0', 'margin-right': '8px' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useRightPosition) {
|
if (useRightPosition) {
|
||||||
@@ -1298,34 +1295,47 @@ export function createThoughtPanel($message, thoughtsArray) {
|
|||||||
// Schedule update on next frame
|
// Schedule update on next frame
|
||||||
positionUpdateRaf = requestAnimationFrame(() => {
|
positionUpdateRaf = requestAnimationFrame(() => {
|
||||||
const newAvatarRect = $avatar[0].getBoundingClientRect();
|
const newAvatarRect = $avatar[0].getBoundingClientRect();
|
||||||
const newTop = newAvatarRect.top + (newAvatarRect.height / 2);
|
const newTop = newAvatarRect.top; // Align with avatar top
|
||||||
const newIconTop = newAvatarRect.top;
|
const newIconTop = newAvatarRect.top;
|
||||||
let newLeft, newIconLeft;
|
let newLeft, newIconLeft;
|
||||||
|
|
||||||
if (panelPosition === 'left') {
|
const isMobileNow = window.innerWidth <= 1000;
|
||||||
|
|
||||||
|
if (isMobileNow) {
|
||||||
|
newLeft = window.innerWidth / 2 - panelWidth / 2;
|
||||||
|
newIconLeft = newAvatarRect.left + (newAvatarRect.width / 2) - 18;
|
||||||
|
} else if (panelPosition === 'left') {
|
||||||
// Position at chat's right edge, extending right
|
// Position at chat's right edge, extending right
|
||||||
const chatContainer = $('#chat')[0];
|
const chatContainer = $('#chat')[0];
|
||||||
const chatRect = chatContainer ? chatContainer.getBoundingClientRect() : { right: window.innerWidth };
|
const chatRect = chatContainer ? chatContainer.getBoundingClientRect() : { right: window.innerWidth };
|
||||||
newLeft = chatRect.right + panelMargin;
|
const spaceRight = window.innerWidth - chatRect.right;
|
||||||
newIconLeft = chatRect.right + 10;
|
|
||||||
|
|
||||||
$thoughtPanel.css({
|
if (spaceRight >= panelWidth + panelMargin * 2) {
|
||||||
top: `${newTop}px`,
|
newLeft = chatRect.right + panelMargin;
|
||||||
left: `${newLeft}px`,
|
} else {
|
||||||
right: 'auto'
|
newLeft = Math.max(chatRect.right - panelWidth - panelMargin, window.innerWidth - panelWidth - 10);
|
||||||
});
|
}
|
||||||
|
newIconLeft = chatRect.right + 10;
|
||||||
} else {
|
} else {
|
||||||
// Left position relative to avatar
|
// Left position relative to avatar
|
||||||
newLeft = newAvatarRect.left - panelWidth - panelMargin;
|
const chatContainer = $('#chat')[0];
|
||||||
newIconLeft = newAvatarRect.left - 40;
|
const chatRect = chatContainer ? chatContainer.getBoundingClientRect() : { left: 0 };
|
||||||
|
const spaceLeft = chatRect.left;
|
||||||
|
|
||||||
$thoughtPanel.css({
|
if (spaceLeft >= panelWidth + panelMargin * 2) {
|
||||||
top: `${newTop}px`,
|
newLeft = newAvatarRect.left - panelWidth - panelMargin;
|
||||||
left: `${newLeft}px`,
|
} else {
|
||||||
right: 'auto'
|
newLeft = Math.max(10, newAvatarRect.left - panelWidth - panelMargin);
|
||||||
});
|
}
|
||||||
|
newIconLeft = Math.max(10, newAvatarRect.left - 40);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$thoughtPanel.css({
|
||||||
|
top: `${newTop}px`,
|
||||||
|
left: `${newLeft}px`,
|
||||||
|
right: 'auto'
|
||||||
|
});
|
||||||
|
|
||||||
$thoughtIcon.css({
|
$thoughtIcon.css({
|
||||||
top: `${newIconTop}px`,
|
top: `${newIconTop}px`,
|
||||||
left: `${newIconLeft}px`,
|
left: `${newIconLeft}px`,
|
||||||
|
|||||||
@@ -4458,7 +4458,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
z-index: 1000; /* Lower z-index to stay below dropdown menus */
|
z-index: 1000; /* Lower z-index to stay below dropdown menus */
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
max-width: 15.3rem; /* 30% smaller than 21.875rem (350px → 245px) */
|
max-width: 15.3rem; /* 30% smaller than 21.875rem (350px → 245px) */
|
||||||
transform: translateY(-50%);
|
transform: translateY(0); /* No vertical centering - align with avatar top */
|
||||||
animation: thoughtPanelFadeIn 0.4s ease-out;
|
animation: thoughtPanelFadeIn 0.4s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4528,9 +4528,10 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
.rpg-thought-circles {
|
.rpg-thought-circles {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column-reverse; /* Reverse so circles go upward */
|
flex-direction: row-reverse; /* Horizontal layout, flipped */
|
||||||
align-items: flex-end; /* Align to the right side */
|
align-items: center; /* Center vertically */
|
||||||
gap: 0.75em;
|
gap: 0.5em;
|
||||||
|
inset: 0px -50px auto;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4545,23 +4546,18 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
width: 0.5rem;
|
width: 0.5rem;
|
||||||
height: 0.5rem;
|
height: 0.5rem;
|
||||||
animation-delay: 0s;
|
animation-delay: 0s;
|
||||||
align-self: flex-end; /* Circle 1 on the far right (at avatar) */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpg-circle-2 {
|
.rpg-circle-2 {
|
||||||
width: 0.75rem;
|
width: 0.75rem;
|
||||||
height: 0.75rem;
|
height: 0.75rem;
|
||||||
animation-delay: 0.2s;
|
animation-delay: 0.2s;
|
||||||
align-self: flex-end;
|
|
||||||
margin-right: 4px; /* Move slightly left from circle 1 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpg-circle-3 {
|
.rpg-circle-3 {
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
animation-delay: 0.4s;
|
animation-delay: 0.4s;
|
||||||
align-self: flex-end;
|
|
||||||
margin-right: 8px; /* Move more left from circle 1 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Thought bubble main container */
|
/* Thought bubble main container */
|
||||||
|
|||||||
Reference in New Issue
Block a user