Kick Left Chat

Move Kick chat left without the visible slide, while avoiding blank chat

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Kick Left Chat
// @namespace    https://kick.com/
// @version      0.3.1
// @description  Move Kick chat left without the visible slide, while avoiding blank chat
// @match        https://kick.com/*
// @grant        GM_addStyle
// @run-at       document-start
// ==/UserScript==

(function () {
  'use strict';

  const CHAT_WIDTH = 350;
  const READY_CLASS = 'kick-left-chat-ready';

  GM_addStyle(`
    /* Hide chat until it has been moved and is ready */
    #channel-chatroom {
      opacity: 0 !important;
      pointer-events: none !important;
    }

    #channel-chatroom.${READY_CLASS} {
      opacity: 1 !important;
      pointer-events: auto !important;
    }
  `);

  function chatLooksReady(chat) {
    if (!chat) return false;

    // Need more than just the empty shell existing
    const hasChildren = chat.querySelector('*') !== null;
    const hasText = (chat.textContent || '').trim().length > 20;
    const rect = chat.getBoundingClientRect();

    return hasChildren && (hasText || rect.height > 200);
  }

  function moveChatLeft() {
    const chat = document.getElementById('channel-chatroom');
    const main = document.querySelector('main');

    if (!chat || !main) return false;

    // Wait until chat is actually populated, not just present
    if (!chatLooksReady(chat)) return false;

    // If chat is nested inside main on some layouts, do nothing
    if (main.contains(chat)) return false;

    const parent = main.parentElement;
    if (!parent) return false;

    // This is the part from the version that actually worked for you
    if (chat.parentElement !== parent || chat !== main.previousElementSibling) {
      parent.insertBefore(chat, main);
    }

    chat.style.width = `${CHAT_WIDTH}px`;
    chat.style.minWidth = `${CHAT_WIDTH}px`;
    chat.style.flex = `0 0 ${CHAT_WIDTH}px`;

    main.style.flex = '1 1 auto';
    main.style.minWidth = '0';

    chat.classList.add(READY_CLASS);
    return true;
  }

  const observer = new MutationObserver(() => {
    moveChatLeft();
  });

  observer.observe(document.documentElement, {
    childList: true,
    subtree: true
  });

  // Try repeatedly during initial load/hydration
  let tries = 0;
  const timer = setInterval(() => {
    tries += 1;
    const done = moveChatLeft();

    // Stop after success or after ~10 seconds
    if (done || tries >= 40) {
      clearInterval(timer);

      // Failsafe: never leave chat hidden forever
      const chat = document.getElementById('channel-chatroom');
      if (chat && !chat.classList.contains(READY_CLASS)) {
        chat.classList.add(READY_CLASS);
      }
    }
  }, 250);

  window.addEventListener('load', moveChatLeft);
})();