您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Расширение и перемещение панели эмодзи FFZ
- // ==UserScript==
- // @name FFZ Panel Resize 1.2.29
- // @namespace http://tampermonkey.net/
- // @version 1.2.29
- // @description Расширение и перемещение панели эмодзи FFZ
- // @author gullampis810
- // @match https://www.twitch.tv/*
- // @license MIT
- // @icon https://png.pngtree.com/png-vector/20220703/ourmid/pngtree-send-dark-mode-glyph-icon-png-image_5561369.png
- // @grant GM_addStyle
- // @run-at document-idle
- // @downloadURL
- // @updateURL
- // ==/UserScript==
- (function() {
- 'use strict';
- // кнопка Chat Paused Due to Scroll //
- const observer = new MutationObserver(() => {
- const buttonContainer = document.querySelector('.tw-absolute.tw-border-radius-medium.tw-bottom-0.tw-c-background-overlay.tw-c-text-overlay.tw-mg-b-1');
- if (buttonContainer) {
- buttonContainer.style.height = '34px';
- buttonContainer.style.minHeight = '34px';
- buttonContainer.style.maxHeight = '34px';
- console.log('Высота контейнера кнопки установлена на 34px');
- }
- });
- observer.observe(document.body, { childList: true, subtree: true });
- // Добавляем стили для изменения размеров контейнера эмодзи
- GM_addStyle(`
- .tw-absolute.tw-border-radius-medium.tw-bottom-0.tw-c-background-overlay.tw-c-text-overlay.tw-mg-b-1 {
- background: rgb(22 67 63 / 34%) !important;
- position: relative !important;
- top: 13px !important;
- z-index: 100002 !important;
- }
- button.tw-align-items-center.tw-align-middle.tw-border-bottom-left-radius-medium.tw-border-bottom-right-radius-medium.tw-border-top-left-radius-medium.tw-border-top-right-radius-medium.ffz-core-button.ffz-core-button--overlay.ffz-core-button--text.tw-inline-flex.tw-interactive.tw-justify-content-center.tw-overflow-hidden.tw-relative {
- color: #ffffff5e !important;
- }
- .emote-picker__controls-container.tw-relative {
- bottom: 3px !important;
- }
- .emote-picker {
- width: 107rem !important; /* Увеличенная ширина */
- height: 100rem !important; /* Увеличенная высота */
- left: 24px !important;; /* Сдвиг влево */
- position: relative !important;
- }
- .ffz--emote-picker {
- position: relative !important;
- height: 785px !important;
- width: 1097px !important;
- left: -243px !important;
- }
- .ffz--emote-picker.ffz--emote-picker__tall .emote-picker__nav-content-overflow, .ffz--emote-picker.ffz--emote-picker__tall .emote-picker__tab-content {
- height: unset !important;
- max-height: 73rem !important;
- }
- .tw-absolute.ffz-attached.ffz-attached--right.ffz-attached--up {
- width: 857px !important;
- right: 368px !important;
- bottom: 533px !important;
- }
- /* fix ballon when clicked ffz emote picke in chat input */
- .ffz-balloon.ffz-balloon--auto.tw-inline-block.tw-border-radius-large.tw-c-background-base.tw-c-text-inherit.tw-elevation-2.ffz--emote-picker.ffz--emote-picker__tall {
- top: 290px !important;
- }
- .ffz-attached--up {
- bottom: 510% !important;
- }
- .tw-border-b.tw-border-l.tw-border-r.tw-border-t.tw-border-radius-medium.tw-c-background-base.tw-elevation-1 {
- width: 63px; !important;
- height: 216px; !important;
- }
- .tw-absolute {
- position: absolute !important;
- height: 570px !important;
- }
- .tw-border-b.tw-border-l.tw-border-r.tw-border-t.tw-border-radius-medium.tw-c-background-base.tw-elevation-1 {
- width: 60px !important;
- height: 200px !important;
- bottom: 6px !important;
- position: absolute !important;
- right: 5px !important;
- } /* emoji standard color choice mini panel */
- `);
- console.log("[FFZ Emote Panel] Контейнер .emote-picker изменен: шире, выше, сдвинут влево.");
- })();
- // ============== FFZ Dialog Unconstrained Dragging свободное перемещение Панели ffz Settings main-menu vue js css ============ //
- (function() {
- 'use strict';
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing v1.3] Script started');
- // Функция для проверки, является ли элемент полем ввода
- function isInputElement(target) {
- return target.tagName === 'INPUT' ||
- target.tagName === 'TEXTAREA' ||
- target.tagName === 'SELECT' ||
- target.closest('input, textarea, select');
- }
- // Функция для инициализации перетаскивания и изменения размера
- function initDraggingAndResizing(dialog) {
- if (!dialog) {
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Dialog not found');
- return;
- }
- const header = dialog.querySelector('header');
- if (!header) {
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Header not found');
- return;
- }
- if (dialog.dataset.draggingInitialized) {
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Dragging already initialized, skipping');
- return;
- }
- dialog.dataset.draggingInitialized = 'true';
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Initializing for dialog');
- // Устанавливаем начальные стили для диалога
- dialog.style.position = 'absolute';
- dialog.style.minWidth = '200px';
- dialog.style.minHeight = '200px';
- // Переменные для перетаскивания
- let isDragging = false;
- let startX, startY;
- // Переменные для изменения размера
- let isResizing = false;
- let resizeStartX, resizeStartY;
- // Обработчик начала перетаскивания
- header.addEventListener('mousedown', (e) => {
- if (e.target.closest('button') || isInputElement(e.target)) {
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Ignoring button or input click');
- return;
- }
- isDragging = true;
- startX = e.clientX - (parseFloat(dialog.style.left) || dialog.offsetLeft);
- startY = e.clientY - (parseFloat(dialog.style.top) || dialog.offsetTop);
- dialog.style.zIndex = Math.max(parseInt(dialog.style.zIndex) || 9000, 9000) + 1;
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Drag started at', e.clientX, e.clientY);
- e.preventDefault();
- e.stopPropagation();
- }, { capture: true, passive: false });
- // Обработчик движения мыши для перетаскивания
- document.addEventListener('mousemove', (e) => {
- if (isDragging) {
- requestAnimationFrame(() => {
- const newLeft = e.clientX - startX;
- const newTop = e.clientY - startY;
- dialog.style.left = `${newLeft}px`;
- dialog.style.top = `${newTop}px`;
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Moved to', newLeft, newTop);
- });
- }
- }, { capture: true, passive: true });
- // Обработчик окончания перетаскивания
- document.addEventListener('mouseup', () => {
- if (isDragging) {
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Drag ended');
- isDragging = false;
- }
- }, { capture: true, passive: true });
- // Поддержка сенсорных устройств для перетаскивания
- header.addEventListener('touchstart', (e) => {
- if (e.target.closest('button') || isInputElement(e.target)) {
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Ignoring button or input touch');
- return;
- }
- isDragging = true;
- const touch = e.touches[0];
- startX = touch.clientX - (parseFloat(dialog.style.left) || dialog.offsetLeft);
- startY = touch.clientY - (parseFloat(dialog.style.top) || dialog.offsetTop);
- dialog.style.zIndex = Math.max(parseInt(dialog.style.zIndex) || 9000, 9000) + 1;
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Touch drag started at', touch.clientX, touch.clientY);
- e.preventDefault();
- e.stopPropagation();
- }, { capture: true, passive: false });
- document.addEventListener('touchmove', (e) => {
- if (isDragging) {
- const touch = e.touches[0];
- requestAnimationFrame(() => {
- const newLeft = touch.clientX - startX;
- const newTop = touch.clientY - startY;
- dialog.style.left = `${newLeft}px`;
- dialog.style.top = `${newTop}px`;
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Touch moved to', newLeft, newTop);
- });
- }
- }, { capture: true, passive: true });
- document.addEventListener('touchend', () => {
- if (isDragging) {
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Touch drag ended');
- isDragging = false;
- }
- }, { capture: true, passive: true });
- // Создаем элемент для изменения размера
- const resizeHandle = document.createElement('div');
- resizeHandle.className = 'resize-handle';
- resizeHandle.style.position = 'absolute';
- resizeHandle.style.bottom = '0';
- resizeHandle.style.right = '0';
- resizeHandle.style.width = '15px';
- resizeHandle.style.height = '15px';
- resizeHandle.style.background = '#34767c';
- resizeHandle.style.cursor = 'se-resize';
- resizeHandle.style.zIndex = '10001';
- resizeHandle.style.border = '1px solid #ffffff';
- dialog.appendChild(resizeHandle);
- // Обработчик начала изменения размера
- resizeHandle.addEventListener('mousedown', (e) => {
- if (isInputElement(e.target)) {
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Ignoring input click on resize handle');
- return;
- }
- isResizing = true;
- resizeStartX = e.clientX;
- resizeStartY = e.clientY;
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Resize started at', e.clientX, e.clientY);
- e.preventDefault();
- e.stopPropagation();
- }, { capture: true, passive: false });
- // Обработчик движения мыши для изменения размера
- document.addEventListener('mousemove', (e) => {
- if (isResizing) {
- requestAnimationFrame(() => {
- const newWidth = (parseFloat(dialog.style.width) || dialog.offsetWidth) + (e.clientX - resizeStartX);
- const newHeight = (parseFloat(dialog.style.height) || dialog.offsetHeight) + (e.clientY - resizeStartY);
- // Ограничиваем минимальные размеры
- if (newWidth >= 200) {
- dialog.style.width = `${newWidth}px`;
- resizeStartX = e.clientX;
- }
- if (newHeight >= 200) {
- dialog.style.height = `${newHeight}px`;
- resizeStartY = e.clientY;
- }
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Resized to', newWidth, newHeight);
- });
- }
- }, { capture: true, passive: true });
- // Обработчик окончания изменения размера
- document.addEventListener('mouseup', () => {
- if (isResizing) {
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Resize ended');
- isResizing = false;
- }
- }, { capture: true, passive: true });
- // Поддержка сенсорных устройств для изменения размера
- resizeHandle.addEventListener('touchstart', (e) => {
- if (isInputElement(e.target)) {
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Ignoring input touch on resize handle');
- return;
- }
- isResizing = true;
- const touch = e.touches[0];
- resizeStartX = touch.clientX;
- resizeStartY = touch.clientY;
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Touch resize started at', touch.clientX, touch.clientY);
- e.preventDefault();
- e.stopPropagation();
- }, { capture: true, passive: false });
- document.addEventListener('touchmove', (e) => {
- if (isResizing) {
- const touch = e.touches[0];
- requestAnimationFrame(() => {
- const newWidth = (parseFloat(dialog.style.width) || dialog.offsetWidth) + (touch.clientX - resizeStartX);
- const newHeight = (parseFloat(dialog.style.height) || dialog.offsetHeight) + (touch.clientY - resizeStartY);
- if (newWidth >= 200) {
- dialog.style.width = `${newWidth}px`;
- resizeStartX = touch.clientX;
- }
- if (newHeight >= 200) {
- dialog.style.height = `${newHeight}px`;
- resizeStartY = touch.clientY;
- }
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Touch resized to', newWidth, newHeight);
- });
- }
- }, { capture: true, passive: true });
- document.addEventListener('touchend', () => {
- if (isResizing) {
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Touch resize ended');
- isResizing = false;
- }
- }, { capture: true, passive: true });
- // Добавляем кнопку для сброса позиции
- const resetButton = document.createElement('button');
- resetButton.textContent = 'Reset Position';
- resetButton.style.position = 'absolute';
- resetButton.style.top = '625px';
- resetButton.style.right = '5px';
- resetButton.style.zIndex = '10000';
- resetButton.style.padding = '5px';
- resetButton.style.background = '#34767c';
- resetButton.style.borderRadius = '12px';
- resetButton.style.border = '1px solid #ffffff';
- resetButton.addEventListener('click', () => {
- dialog.style.left = '25%';
- dialog.style.top = '25%';
- dialog.style.width = ''; // Сбрасываем размеры
- dialog.style.height = '';
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Position and size reset to 25%, 25%');
- });
- dialog.appendChild(resetButton);
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Dragging and resizing initialized successfully');
- }
- // Наблюдатель за появлением диалога
- const observer = new MutationObserver((mutations) => {
- for (const mutation of mutations) {
- if (mutation.addedNodes.length) {
- const dialog = document.querySelector('.ffz-dialog.ffz-main-menu:not([data-dragging-initialized])');
- if (dialog) {
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Dialog detected via observer');
- initDraggingAndResizing(dialog);
- }
- }
- }
- });
- observer.observe(document.body, {
- childList: true,
- subtree: true
- });
- const initialDialog = document.querySelector('.ffz-dialog.ffz-main-menu:not([data-dragging-initialized])');
- if (initialDialog) {
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Initial dialog found');
- initDraggingAndResizing(initialDialog);
- }
- console.log('[FFZ Dialog Unconstrained Dragging and Resizing] Setup complete');
- })();
- // ====================== ffz-viewer-card move freedom =========================== //
- (function() {
- 'use strict';
- // Inject CSS for scrollable modified emotes list
- GM_addStyle(`
- .ffz-emote-card__modifiers {
- max-height: 200px !important;
- overflow-y: auto !important;
- overflow-x: hidden !important;
- padding-right: 8px !important;
- }
- .ffz-emote-card__modifiers::-webkit-scrollbar {
- width: 8px;
- }
- .ffz-emote-card__modifiers::-webkit-scrollbar-track {
- background: rgba(0, 0, 0, 0.1);
- }
- .ffz-emote-card__modifiers::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.3);
- border-radius: 4px;
- }
- .ffz-emote-card__modifiers > div {
- width: 100%;
- box-sizing: border-box;
- }
- `);
- // Function to enable unconstrained dragging for ffz-viewer-card
- function enableUnconstrainedDragging() {
- // Find the ffz-viewer-card element
- const observer = new MutationObserver((mutations, obs) => {
- const viewerCard = document.querySelector('.ffz-viewer-card.tw-border');
- if (viewerCard) {
- // Initialize custom drag functionality
- setupCustomDrag(viewerCard);
- // Stop observing once the card is found
- obs.disconnect();
- }
- });
- // Observe the document for the viewer card
- observer.observe(document.body, {
- childList: true,
- subtree: true
- });
- }
- // Custom drag implementation
- function setupCustomDrag(card) {
- const header = card.querySelector('.ffz-viewer-card__header');
- if (!header) {
- console.log('[FFZ Enhancements] Header not found for dragging');
- return;
- }
- let isDragging = false;
- let currentX;
- let currentY;
- let initialX;
- let initialY;
- header.addEventListener('mousedown', (e) => {
- // Ignore if clicking on elements with viewer-card-drag-cancel
- if (e.target.closest('.viewer-card-drag-cancel')) return;
- isDragging = true;
- initialX = e.clientX - currentX;
- initialY = e.clientY - currentY;
- card.style.transition = 'none'; // Disable transitions during drag
- e.preventDefault();
- });
- document.addEventListener('mousemove', (e) => {
- if (!isDragging) return;
- currentX = e.clientX - initialX;
- currentY = e.clientY - initialY;
- card.style.left = `${currentX}px`;
- card.style.top = `${currentY}px`;
- });
- document.addEventListener('mouseup', () => {
- isDragging = false;
- card.style.transition = ''; // Restore transitions
- });
- // Initialize position if not set
- if (!card.style.left || !card.style.top) {
- const rect = card.getBoundingClientRect();
- currentX = rect.left;
- currentY = rect.top;
- card.style.left = `${currentX}px`;
- card.style.top = `${currentY}px`;
- }
- }
- // Re-integrate showEmoteSelectionPopup to follow the draggable card
- function showEmoteSelectionPopup(emotes, callback) {
- console.log("[FFZ Enhancements] Attempting to show emote selection popup with emotes:", emotes);
- // Remove existing popup
- const existingPopup = document.getElementById('emote-selection-popup');
- if (existingPopup) {
- console.log("[FFZ Enhancements] Removing existing popup");
- existingPopup.remove();
- }
- // Create popup
- const popup = document.createElement('div');
- popup.id = 'emote-selection-popup';
- popup.innerHTML = `
- <div class="close-button" style="cursor:pointer;position:absolute;top:6px;right:10px;font-size:20px;">✕</div>
- <div class="emote-options"></div>
- `;
- document.body.appendChild(popup);
- console.log("[FFZ Enhancements] Popup element created and appended to body");
- // Inline styles for popup
- popup.style.position = 'fixed';
- popup.style.background = 'rgb(56, 90, 80)';
- popup.style.color = 'rgb(235, 235, 235)';
- popup.style.fontWeight = 'bold';
- popup.style.fontSize = '16px';
- popup.style.border = '1px solid #12b6a7';
- popup.style.borderRadius = '8px';
- popup.style.padding = '10px 10px 10px 10px';
- popup.style.zIndex = '10001';
- popup.style.maxWidth = '320px';
- popup.style.maxHeight = '500px';
- popup.style.overflowY = 'auto';
- popup.style.transition = 'opacity 0.2s ease, transform 0.2s ease';
- popup.style.opacity = '0';
- popup.style.transform = 'scale(0.9)';
- popup.style.display = 'block';
- popup.style.visibility = 'visible';
- popup.style.paddingTop = '32px';
- // Position popup relative to ffz-viewer-card
- const viewerCard = document.querySelector('.ffz-viewer-card.tw-border');
- if (viewerCard) {
- const rect = viewerCard.getBoundingClientRect();
- const viewportWidth = window.innerWidth;
- const viewportHeight = window.innerHeight;
- const popupWidth = 320;
- const offset = 20;
- const extraOffset = 30;
- let left = rect.right + offset + extraOffset;
- let top = rect.top;
- if (left + popupWidth > viewportWidth) {
- left = rect.left - popupWidth - offset;
- }
- if (top + popup.offsetHeight > viewportHeight) {
- top = viewportHeight - popup.offsetHeight - offset;
- }
- if (top < 0) {
- top = offset;
- }
- if (left < 0) {
- left = offset;
- if (rect.bottom + popup.offsetHeight + offset <= viewportHeight) {
- left = rect.left;
- top = rect.bottom + offset;
- }
- }
- popup.style.left = `${left}px`;
- popup.style.top = `${top}px`;
- console.log("[FFZ Enhancements] Popup positioned at left:", left, "top:", top);
- } else {
- popup.style.right = '310px';
- popup.style.top = '385px';
- console.warn("[FFZ Enhancements] ffz-viewer-card not found, using fallback position");
- }
- const optionsContainer = popup.querySelector('.emote-options');
- // Populate popup
- emotes.forEach((emote, index) => {
- const option = document.createElement('div');
- option.className = 'emote-option';
- option.style.display = 'flex';
- option.style.alignItems = 'center';
- option.style.justifyContent = 'space-between';
- option.style.padding = '8px 0';
- option.style.borderBottom = '1px solid rgba(115, 209, 204, 0.16)';
- option.style.gap = '10px';
- const left = document.createElement('div');
- left.style.display = 'flex';
- left.style.alignItems = 'center';
- left.style.minWidth = '0';
- const img = document.createElement('img');
- img.src = emote.src || '';
- img.alt = emote.alt || 'Emote';
- img.style.width = '24px';
- img.style.height = '24px';
- img.style.marginRight = '10px';
- img.style.flexShrink = '0';
- img.style.userSelect = 'none';
- const info = document.createElement('div');
- info.className = 'emote-info';
- info.style.fontSize = '14px';
- info.style.whiteSpace = 'nowrap';
- info.style.overflow = 'hidden';
- info.style.textOverflow = 'ellipsis';
- info.innerHTML = `<span>${emote.alt || 'Unnamed'} <span style="user-select:none;">(${emote.platform})</span></span>`;
- left.appendChild(img);
- left.appendChild(info);
- const blockButton = document.createElement('button');
- blockButton.className = 'block-button';
- blockButton.type = 'button';
- blockButton.textContent = 'Block';
- blockButton.style.background = '#ff5555';
- blockButton.style.color = '#ffffff';
- blockButton.style.border = 'none';
- blockButton.style.padding = '4px 8px';
- blockButton.style.borderRadius = '4px';
- blockButton.style.cursor = 'pointer';
- blockButton.style.fontSize = '12px';
- blockButton.style.marginLeft = '10px';
- blockButton.style.flexShrink = '0';
- blockButton.style.userSelect = 'none';
- blockButton.addEventListener('click', (e) => {
- e.preventDefault();
- e.stopPropagation();
- console.log("[FFZ Enhancements] Block button clicked for emote:", emote);
- callback(emote);
- if (emote.element) {
- emote.element.style.display = 'none';
- console.log("[FFZ Enhancements] Emote element hidden:", emote.alt);
- const parentContainer = emote.element.closest('.ffz--inline, .chat-line__message, .chat-image');
- if (parentContainer) {
- const allEmotes = parentContainer.querySelectorAll(
- 'img.chat-line__message--emote, .ffz-emote, .seventv-emote, .bttv-emote, .twitch-emote, .chat-image'
- );
- const allBlocked = Array.from(allEmotes).every(e => e.style.display === 'none');
- if (allBlocked) {
- parentContainer.style.display = 'none';
- console.log("[FFZ Enhancements] Parent container hidden as all emotes are blocked");
- }
- }
- }
- popup.remove();
- });
- option.appendChild(left);
- option.appendChild(blockButton);
- optionsContainer.appendChild(option);
- });
- console.log("[FFZ Enhancements] Popup populated with", emotes.length, "emotes");
- // Close button
- const closeButton = popup.querySelector('.close-button');
- closeButton.onclick = () => {
- console.log("[FFZ Enhancements] Emote selection popup closed via close button");
- popup.remove();
- };
- // Ensure visibility
- const computedStyle = window.getComputedStyle(popup);
- if (computedStyle.display === 'none' || computedStyle.visibility === 'hidden') {
- console.warn("[FFZ Enhancements] Popup is not visible, forcing visibility");
- popup.style.display = 'block';
- popup.style.visibility = 'visible';
- }
- // Animation
- setTimeout(() => {
- popup.classList.add('visible');
- popup.style.opacity = '1';
- popup.style.transform = 'scale(1)';
- console.log("[FFZ Enhancements] Popup visibility class and styles applied");
- }, 10);
- // Close on outside click
- document.addEventListener('click', function closePopup(e) {
- if (!popup.contains(e.target) && e.target !== popup && !viewerCard.contains(e.target)) {
- console.log("[FFZ Enhancements] Closing popup due to outside click");
- popup.remove();
- document.removeEventListener('click', closePopup);
- }
- }, { capture: true, once: true });
- // Observe viewerCard for position changes
- const observer = new MutationObserver(() => {
- if (viewerCard) {
- const rect = viewerCard.getBoundingClientRect();
- const viewportWidth = window.innerWidth;
- const viewportHeight = window.innerHeight;
- const popupWidth = 320;
- const offset = 20;
- const extraOffset = 30;
- let left = rect.right + offset + extraOffset;
- let top = rect.top;
- if (left + popupWidth > viewportWidth) {
- left = rect.left - popupWidth - offset;
- }
- if (top + popup.offsetHeight > viewportHeight) {
- top = viewportHeight - popup.offsetHeight - offset;
- }
- if (top < 0) {
- top = offset;
- }
- if (left < 0) {
- left = offset;
- if (rect.bottom + popup.offsetHeight + offset <= viewportHeight) {
- left = rect.left;
- top = rect.bottom + offset;
- }
- }
- popup.style.left = `${left}px`;
- popup.style.top = `${top}px`;
- console.log("[FFZ Enhancements] Popup repositioned to left:", left, "top:", top);
- }
- });
- if (viewerCard) {
- observer.observe(viewerCard, {
- attributes: true,
- attributeFilter: ['style', 'class']
- });
- }
- popup.addEventListener('remove', () => {
- observer.disconnect();
- console.log("[FFZ Enhancements] MutationObserver disconnected");
- });
- }
- // Initialize enhancements
- enableUnconstrainedDragging();
- // Expose showEmoteSelectionPopup globally for external use
- window.showEmoteSelectionPopup = showEmoteSelectionPopup;
- })();