FFZ Panel Resize 1.2.29

Расширение и перемещение панели эмодзи FFZ

2025-09-03 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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;
})();