FFZ Panel Resize 1.2.27

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

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