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