FFZ Panel Resize 1.2.27

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

Versión del día 20/5/2025. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         FFZ   Panel Resize 1.2.27
// @namespace    http://tampermonkey.net/
// @version      1.2.27
// @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(`

     .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 v1.2] Script started');

    // Функция для инициализации перетаскивания
    function initDragging(dialog) {
        if (!dialog) {
            console.log('[FFZ Dialog Unconstrained Dragging] Dialog not found');
            return;
        }

        const header = dialog.querySelector('header');
        if (!header) {
            console.log('[FFZ Dialog Unconstrained Dragging] Header not found');
            return;
        }

        // Проверяем, не инициализировано ли уже
        if (dialog.dataset.draggingInitialized) {
            console.log('[FFZ Dialog Unconstrained Dragging] Dragging already initialized, skipping');
            return;
        }
        dialog.dataset.draggingInitialized = 'true';

        console.log('[FFZ Dialog Unconstrained Dragging] Initializing dragging for dialog');

        // Переменные для перетаскивания
        let isDragging = false;
        let startX, startY;

        // Обработчик начала перетаскивания
        header.addEventListener('mousedown', (e) => {
            // Игнорируем клики по кнопкам
            if (e.target.closest('button')) {
                console.log('[FFZ Dialog Unconstrained Dragging] Ignoring button click');
                return;
            }

            isDragging = true;
            startX = e.clientX - (parseFloat(dialog.style.left) || dialog.offsetLeft);
            startY = e.clientY - (parseFloat(dialog.style.top) || dialog.offsetTop);

            // Повышаем z-index
            dialog.style.zIndex = Math.max(parseInt(dialog.style.zIndex) || 9000, 9000) + 1;

            console.log('[FFZ Dialog Unconstrained Dragging] Drag started at', e.clientX, e.clientY);
            e.preventDefault();
            e.stopPropagation(); // Останавливаем оригинальные обработчики
        }, { capture: true, passive: false });

        // Обработчик движения мыши
        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;

            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] Moved to', newLeft, newTop);
            });
        }, { capture: true, passive: true });

        // Обработчик окончания перетаскивания
        document.addEventListener('mouseup', () => {
            if (isDragging) {
                console.log('[FFZ Dialog Unconstrained Dragging] Drag ended');
                isDragging = false;
            }
        }, { capture: true, passive: true });

        // Поддержка сенсорных устройств
        header.addEventListener('touchstart', (e) => {
            if (e.target.closest('button')) {
                console.log('[FFZ Dialog Unconstrained Dragging] Ignoring button 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] Touch drag started at', touch.clientX, touch.clientY);
            e.preventDefault();
            e.stopPropagation();
        }, { capture: true, passive: false });

        document.addEventListener('touchmove', (e) => {
            if (!isDragging) return;

            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] Touch moved to', newLeft, newTop);
            });
        }, { capture: true, passive: true });

        document.addEventListener('touchend', () => {
            if (isDragging) {
                console.log('[FFZ Dialog Unconstrained Dragging] Touch drag ended');
                isDragging = 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%';
            console.log('[FFZ Dialog Unconstrained Dragging] Position reset to 25%, 25%');
        });
        dialog.appendChild(resetButton);

        console.log('[FFZ Dialog Unconstrained Dragging] Dragging 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] Dialog detected via observer');
                    initDragging(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] Initial dialog found');
        initDragging(initialDialog);
    }

    console.log('[FFZ Dialog Unconstrained Dragging] 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;
})();