// ==UserScript== // @name Lolz Transparent Chat — v2.9.18-opt (same look, fewer CPU/DOM hits) // @namespace http://tampermonkey.net/ // @version 2.9.18-opt // @description Тот же вид: прозрачность, серые грани, зелёные упоминания, крестики и полный H-скролл всей шапки. Оптимизация: кэш узлов, единый дебаунс мутаций, WeakSet для "стекла", аккуратные наблюдатели. // @match https://lzt.market/* // @match https://lolz.live/* // @match https://zelenka.guru/* // @grant none // @license MIT // ==/UserScript== (() => { 'use strict'; // ---- Константы/флаги (как раньше) ---- const BLUR_PX = 12; const ALPHA_GLASS = 0.01; // лента const ALPHA_INPUT = 0.01; // поле ввода const SCOPE_CLASS = 'kyan-chat-scope'; const STYLE_ID = 'kyan-chat-style'; const IS_MOBILE = /Android|iPhone|iPad|Mobile/i.test(navigator.userAgent); const SUPPORTS_BLUR = (typeof CSS!=='undefined') && (CSS.supports('backdrop-filter','blur(1px)') || CSS.supports('-webkit-backdrop-filter','blur(1px)')); const EDGE_MOBILE = /\bEdgA\/|EdgiOS\//i.test(navigator.userAgent); const USE_BLUR = SUPPORTS_BLUR && !EDGE_MOBILE; const MENTION_BORDER = '3px solid rgba(34,142,93,0.85)'; // ---- Helpers ---- const qs = (r,s)=>r.querySelector(s); const qsa = (r,s)=>Array.from(r.querySelectorAll(s)); // Запоминаем какие узлы уже «застеклены», чтобы не трогать повторно const glassed = new WeakSet(); function inlineGlass(el, alpha, blur){ if (!el || glassed.has(el)) return; el.style.setProperty('background', `rgba(0,0,0,${alpha})`, 'important'); el.style.setProperty('background-color', `rgba(0,0,0,${alpha})`, 'important'); el.style.setProperty('background-image', 'none', 'important'); if (USE_BLUR) { el.style.setProperty('backdrop-filter', `blur(${blur}px)`, 'important'); el.style.setProperty('-webkit-backdrop-filter', `blur(${blur}px)`, 'important'); } glassed.add(el); } const GLASS_SELECTORS = [ '#chat2-full','[class^="chat2-floating"]','.chat2','[class*="chat2-container"]','[class*="chatbox"]', '.scrollable-content','[class*="chat2-body"]','[class*="chat2-content"]','[class*="messages"]','[class*="message-list"]', '.chat2-widget-inner','[class*="primary-darker"]:not(.chat2-header):not([class*="chat2-header"])', '[class*="primary-dark"]:not(.chat2-header):not([class*="chat2-header"])','[class*="pane"]','[class*="layout"]','[class*="wrapper"]' ]; function applyGlassSet(root){ inlineGlass(root, ALPHA_GLASS, BLUR_PX); // Пробегаемся по селекторам, но не чаще чем нужно: WeakSet защитит от повторных операций for (const sel of GLASS_SELECTORS) { qsa(root, sel).forEach(el => inlineGlass(el, ALPHA_GLASS, BLUR_PX)); } const scroll = findScroll(root); if (scroll){ let p = scroll.parentElement, i=0; while (p && p!==root && i<6){ inlineGlass(p, ALPHA_GLASS, BLUR_PX); p = p.parentElement; i++; } } } // ---- CSS (не меняем внешний вид) ---- function injectCSS() { if (document.getElementById(STYLE_ID)) return; const css = ` .${SCOPE_CLASS} {} /* Прозрачность внутренних контейнеров (ШАПКУ НЕ СТЕКЛИМ) */ .${SCOPE_CLASS} .chat2-widget-inner, .${SCOPE_CLASS} .scrollable-content, .${SCOPE_CLASS} [class*="chat2-body"], .${SCOPE_CLASS} [class*="chat2-content"], .${SCOPE_CLASS} [class*="chat2-container"], .${SCOPE_CLASS} [class*="chat2-wrapper"], .${SCOPE_CLASS} [class*="messages"], .${SCOPE_CLASS} [class*="message-list"], .${SCOPE_CLASS} [class*="list"], .${SCOPE_CLASS} [class*="chat2-layout"], .${SCOPE_CLASS} [class*="pane"], .${SCOPE_CLASS} [class*="primary-darker"]:not(.chat2-header):not([class*="chat2-header"]), .${SCOPE_CLASS} [class*="primary-dark"]:not(.chat2-header):not([class*="chat2-header"]) { background: transparent !important; background-color: transparent !important; background-image: none !important; } /* Псевдоэлементы — не трогаем в шапке */ .${SCOPE_CLASS} [class*="chat2-"]::before, .${SCOPE_CLASS} [class*="chat2-"]::after { background: transparent !important; } .${SCOPE_CLASS} .chat2-header *::before, .${SCOPE_CLASS} .chat2-header *::after { background: initial !important; } /* Сообщения: стабильные серые грани */ .${SCOPE_CLASS} .chat2-message-block, .${SCOPE_CLASS} .reply-message { background: transparent !important; border: 1px solid rgba(128,128,128,.45) !important; border-radius: 6px !important; } /* Панель «Ответ…» */ .${SCOPE_CLASS} .chat2-replying, .${SCOPE_CLASS} [class*="replying"] { background: rgba(0,0,0,.04) !important; border: 1px solid rgba(128,128,128,.35) !important; border-radius: 8px !important; box-shadow: none !important; } .${SCOPE_CLASS} .chat2-replying-author { background: initial !important; } .${SCOPE_CLASS} .chat2-replying * { border: none !important; box-shadow: none !important; } /* Заголовки/ники внутри сообщения — без блюров/фильтров */ .${SCOPE_CLASS} .chat2-message-header, .${SCOPE_CLASS} .chat2-message-header * { backdrop-filter: none !important; -webkit-backdrop-filter: none !important; filter: none !important; box-shadow: revert !important; } /* Поле ввода — почти прозрачное */ .${SCOPE_CLASS} .chat2-footer textarea, .${SCOPE_CLASS} .chat2-footer [contenteditable="true"], .${SCOPE_CLASS} .chat2-footer input[type="text"], .${SCOPE_CLASS} .chat2-footer input[type="search"], .${SCOPE_CLASS} .chat2-footer [class*="editor"] { background: rgba(0,0,0,${ALPHA_INPUT}) !important; border: none !important; border-radius: 8px !important; outline: none !important; box-shadow: none !important; } .${SCOPE_CLASS} .chat2-footer button, .${SCOPE_CLASS} .chat2-footer [role="button"], .${SCOPE_CLASS} .chat2-footer svg { opacity: revert !important; filter: none !important; } .${SCOPE_CLASS} .submit-btn { background: initial !important; } /* Крестики */ .${SCOPE_CLASS} .chat2-footer .chat2-cancel-reply, .${SCOPE_CLASS} .chat2-message-editing .chat2-cancel-editing { display:inline-flex !important; align-items:center; justify-content:center; width:22px; height:22px; border-radius:6px; background: transparent !important; color:#fff !important; opacity:1 !important; filter: drop-shadow(0 0 1px rgba(0,0,0,.7)) !important; cursor: pointer !important; } .${SCOPE_CLASS} .chat2-footer .chat2-cancel-reply:empty::before, .${SCOPE_CLASS} .chat2-message-editing .chat2-cancel-editing:empty::before { content:'×'; font-size:16px; line-height:1; } /* Шапка — рамка без стекла */ .${SCOPE_CLASS} .chat2-header, .${SCOPE_CLASS} .chat2-header.lztng-primary-dark, .${SCOPE_CLASS} [class*="chat2-header"].lztng-primary-dark { border: 1px solid rgba(128,128,128,.45) !important; border-radius: 8px !important; box-shadow: none !important; background: initial !important; backdrop-filter: none !important; -webkit-backdrop-filter: none !important; overflow: visible !important; } /* Упоминания — только рамка */ .${SCOPE_CLASS} .chat2-message.chat2-message-tagged, .${SCOPE_CLASS} .chat2-message.chat2-message-tagged .chat2-message-block, .${SCOPE_CLASS} .chat2-message.chat2-message-tagged .chat2-message-text, .${SCOPE_CLASS} .chat2-message.chat2-message-tagged .chat2-message-header { background: transparent !important; background-color: transparent !important; box-shadow: none !important; } .${SCOPE_CLASS} .reply-message.reply-message-your { border: ${MENTION_BORDER} !important; background: transparent !important; } /* --- Полный H-скролл всей шапки (мобайл) --- */ .kyan-head-outer { width: 100% !important; max-width: 100% !important; overflow: hidden !important; } .kyan-head-scroll { width: 100% !important; max-width: 100% !important; overflow-x: auto !important; overflow-y: hidden !important; -webkit-overflow-scrolling: touch !important; touch-action: pan-x !important; overscroll-behavior-x: contain !important; scrollbar-width: thin; } .kyan-head-strip { display: inline-flex !important; flex-wrap: nowrap !important; align-items: center !important; gap: .75rem !important; white-space: nowrap !important; min-width: max-content !important; } @media (max-width: 768px) { .${SCOPE_CLASS} .chat2-header, .${SCOPE_CLASS} .chat2-header > * { max-width: 100% !important; overflow: visible !important; } } `; const tag = document.createElement('style'); tag.id = STYLE_ID; tag.textContent = css; document.documentElement.appendChild(tag); } // ---- Mentions/Replies ---- function getMsgBlock(msg){ return msg.querySelector('.chat2-message-block') || msg.querySelector('.chat2-message-text') || msg.querySelector('div'); } function setMentionBorderOnMessage(msg, on){ if (msg.classList.contains('reply-message') && msg.classList.contains('reply-message-your')){ if (on){ msg.style.setProperty('border', MENTION_BORDER, 'important'); msg.style.setProperty('background', 'transparent', 'important'); msg.style.setProperty('background-color', 'transparent', 'important'); } else { msg.style.removeProperty('border'); msg.style.removeProperty('background'); msg.style.removeProperty('background-color'); } return; } const block = getMsgBlock(msg); if (!block) return; if (on){ block.style.setProperty('border', MENTION_BORDER, 'important'); block.style.setProperty('background', 'transparent', 'important'); block.style.setProperty('background-color', 'transparent', 'important'); } else { block.style.removeProperty('border'); block.style.removeProperty('background'); block.style.removeProperty('background-color'); } } function refreshMentions(root){ // Только простая проверка классов — дёшево qsa(root, '.chat2-message, [class*="chat2-message"]').forEach(msg=>{ setMentionBorderOnMessage(msg, msg.classList.contains('chat2-message-tagged')); }); qsa(root, '.reply-message.reply-message-your').forEach(msg=>{ setMentionBorderOnMessage(msg, true); }); } // ---- Шапка: полный H-скролл ---- let headerCached = null; let headWrapped = false; function setupFullHeaderScroll(header){ if (!IS_MOBILE || !header || headWrapped) return; // Собираем всех детей шапки const kids = Array.from(header.childNodes); if (!kids.length) return; const outer = document.createElement('div'); outer.className = 'kyan-head-outer'; const scroll = document.createElement('div'); scroll.className = 'kyan-head-scroll'; const strip = document.createElement('div'); strip.className = 'kyan-head-strip'; kids.forEach(n => strip.appendChild(n)); scroll.appendChild(strip); outer.appendChild(scroll); header.appendChild(outer); headWrapped = true; } // ---- Root helpers ---- function findChatRoot(){ return qs(document,'[class^="chat2-floating"]') || qs(document,'#chat2-full') || qs(document,'.chat2') || qs(document,'[class*="chat2-container"]') || qs(document,'[class*="chatbox"]') || document.body; } function findScroll(root){ return qs(root,'.scrollable-content') || qs(root,'[class*="chat2-body"]') || qs(root,'[class*="chat2-content"]') || qs(root,'[class*="messages"]') || qs(root,'[class*="message-list"]'); } function findHeader(root){ return qs(root, '.chat2-header, [class*="chat2-header"]') || document.querySelector('.chat2-header, [class*="chat2-header"]'); } // ---- Orchestration with debounce ---- let rootCached = null; let mo, moLayout; let debounced = false; const DEBOUNCE_MS = 120; function tick(){ debounced = false; const root = rootCached || findChatRoot(); if (!root) return; rootCached = root; applyGlassSet(root); refreshMentions(root); if (!headerCached || !document.contains(headerCached)) { headerCached = findHeader(root); headWrapped = false; // если шапка пере-создана, разрешим обёртку снова } if (headerCached) setupFullHeaderScroll(headerCached); } function scheduleTick(){ if (debounced) return; debounced = true; setTimeout(tick, DEBOUNCE_MS); } function watchAll(root){ mo?.disconnect(); mo = new MutationObserver(scheduleTick); // Наблюдаем весь документ, но дешёвым debounced-тригером mo.observe(document.documentElement, { childList:true, subtree:true, attributes:true, attributeFilter:['class','style'] }); moLayout?.disconnect(); moLayout = new MutationObserver(scheduleTick); moLayout.observe(root, { childList:true, subtree:true }); // сразу один проход tick(); } // ---- Init ---- function init(){ injectCSS(); rootCached = findChatRoot(); if (!rootCached) { setTimeout(init, 150); return; } rootCached.classList.add(SCOPE_CLASS); watchAll(rootCached); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init, { once:true }); } else { init(); } })();