122131231212

🧩 Syntax:
// ==UserScript==
// @name         Lolz Transparent Chat — v2.9.10 (glass restore + mobile header H-scroll + buttons safe)
// @namespace    http://tampermonkey.net/
// @version      2.9.10
// @description  Прозрачный чат (окно и /chatbox/) + корректные рамки упоминаний + фиксы кнопок + крестики + мобильный H-скролл шапки. Прозрачность восстановлена во всех контейнерах.
// @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 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)';

  function injectCSS(){
    const old = document.getElementById('kyan-chat-style');
    if (old) old.remove();

    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 {
        background: transparent !important;
        border: 1px solid rgba(128,128,128,.45) !important;
        border-radius: 6px !important;
      }
      .${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 {
        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;
      }
      .${SCOPE_CLASS} .chat2-footer .chat2-cancel-reply: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;
        overflow: visible !important;
        background: initial !important;
        backdrop-filter: none !important; -webkit-backdrop-filter: none !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;
      }

      /* Крестик «Редактирование сообщения» */
      .${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-message-editing .chat2-cancel-editing:empty::before { content:'×'; font-size:16px; line-height:1; }

      /* Узел действий/кнопок в шапке — не обрезается */
      .${SCOPE_CLASS} .chat2-header [class*="actions"],
      .${SCOPE_CLASS} .chat2-header .chat2-title-actions {
        position: relative !important;
        z-index: 10020 !important;
        pointer-events: auto !important;
        overflow: visible !important;
      }

      /* ===== МОБАЙЛ: шапка скроллится + горизонтальная прокрутка ===== */
      @media (max-width: 768px) {
        .${SCOPE_CLASS} .chat2-header,
        .${SCOPE_CLASS} [class*="chat2-header"] {
          position: static !important;
          top: auto !important; bottom: auto !important; transform: none !important; z-index: auto !important;
        }

        .${SCOPE_CLASS} .chat2-header,
        .${SCOPE_CLASS} .chat2-header > * { overflow: visible !important; }

        .${SCOPE_CLASS} .chat2-header [class*="actions"],
        .${SCOPE_CLASS} .chat2-header .chat2-title-actions,
        .${SCOPE_CLASS} .chat2-header .actions,
        .${SCOPE_CLASS} .chat2-header .title-actions {
          display: flex !important;
          flex-wrap: nowrap !important;
          align-items: center !important;
          gap: .5rem !important;
          width: 100% !important; max-width: 100% !important;
          overflow-x: auto !important; overflow-y: hidden !important;
          white-space: nowrap !important;
          -webkit-overflow-scrolling: touch !important;
          touch-action: pan-x !important;
          overscroll-behavior-x: contain !important;
          scroll-behavior: smooth !important;
          scrollbar-width: thin;
        }
        .${SCOPE_CLASS} .chat2-header [class*="actions"] > *,
        .${SCOPE_CLASS} .chat2-header .chat2-title-actions > * {
          flex: 0 0 auto !important;
          min-width: max-content !important;
        }
      }
    `;
    const tag = document.createElement('style');
    tag.id = 'kyan-chat-style';
    tag.textContent = css;
    document.documentElement.appendChild(tag);
  }

  const qs  = (r,s)=>r.querySelector(s);
  const qsa = (r,s)=>Array.from(r.querySelectorAll(s));

  function inlineGlass(el, alpha, blur, keepBorder=false){
    if(!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');
    } else {
      el.style.removeProperty('backdrop-filter');
      el.style.removeProperty('-webkit-backdrop-filter');
    }
    if(!keepBorder) el.style.setProperty('border', 'none', 'important');
  }

  // На некоторых разметках нужные зоны отличаются — даём широкий охват
  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){
    // Всегда — сам root
    inlineGlass(root, ALPHA_GLASS, BLUR_PX, true);

    // Все целевые селекторы
    for (const sel of GLASS_SELECTORS){
      qsa(root, sel).forEach(el => inlineGlass(el, ALPHA_GLASS, BLUR_PX, false));
    }

    // Поднимаемся вверх от ленты, чтобы пробить фон родителей
    const scroll = findScroll(root);
    if (scroll){
      let p = scroll.parentElement, i=0;
      while (p && p!==root && i<6){
        inlineGlass(p, ALPHA_GLASS, BLUR_PX, true);
        p = p.parentElement; i++;
      }
    }
  }

  function findChatRoot(){
    return qs(document,'[class^="chat2-floating"]') ||
           qs(document,'#chat2-full') ||
           qs(document,'.chat2') ||
           qs(document,'[class*="chat2-container"]') ||
           qs(document,'[class*="chatbox"]');
  }
  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 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=>{
      const tagged = msg.classList.contains('chat2-message-tagged');
      setMentionBorderOnMessage(msg, tagged);
    });
    qsa(root, '.reply-message.reply-message-your').forEach(msg=>{
      setMentionBorderOnMessage(msg, true);
    });
  }

  // Наблюдение за DOM
  let mo;
  function watchAll(root, mode){
    mo?.disconnect();
    mo = new MutationObserver(()=>{
      cancelAnimationFrame(watchAll._raf||0);
      watchAll._raf = requestAnimationFrame(()=>{
        applyGlassSet(root);   // держим прозрачность после любых перерисовок
        refreshMentions(root); // и зелёные рамки
      });
    });
    mo.observe(root, { childList:true, subtree:true, attributes:true, attributeFilter:['class'] });

    // первичное применение
    applyGlassSet(root);
    refreshMentions(root);
  }

  let moLayout;
  function watchLayout(root, mode){
    moLayout?.disconnect();
    moLayout = new MutationObserver(()=>{
      cancelAnimationFrame(watchLayout._raf||0);
      watchLayout._raf = requestAnimationFrame(()=>{
        applyGlassSet(root);
      });
    });
    moLayout.observe(root, { childList:true, subtree:true });
  }

  function init(){
    const root = findChatRoot();
    if (!root){ setTimeout(init, 250); return; }
    root.classList.add(SCOPE_CLASS);
    injectCSS();

    // НЕ стеклим шапку — только рамка через CSS (прозрачность даём остальным)
    applyGlassSet(root);
    watchAll(root);
    const ON_CHATBOX = /\/chatbox\/?$/.test(location.pathname);
    watchLayout(root, ON_CHATBOX ? 'chatbox' : 'floating');
  }

  if (document.readyState==='loading'){
    document.addEventListener('DOMContentLoaded', init, { once:true });
  } else {
    init();
  }
})();