Прозрачный чат
🧩 Syntax:
// ==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();
}
})();