SOOP (숲) - 사이드바 UI 변경

SOOP (숲)의 사이드바 UI를 변경합니다.

// ==UserScript==
// @name         SOOP (숲) - 사이드바 UI 변경
// @name:ko         SOOP (숲) - 사이드바 UI 변경
// @namespace    https://gf.qytechs.cn/ko/scripts/484713
// @version      20250619
// @description  SOOP (숲)의 사이드바 UI를 변경합니다.
// @description:ko  SOOP (숲)의 사이드바 UI를 변경합니다.
// @author       You
// @match        https://www.sooplive.co.kr/*
// @match        https://play.sooplive.co.kr/*
// @match        https://vod.sooplive.co.kr/player/*
// @icon         https://res.sooplive.co.kr/afreeca.ico
// @grant        unsafeWindow
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @connect      sooplive.co.kr
// @connect      naver.com
// @run-at       document-end
// @license
// ==/UserScript==

(function() {
    'use strict';
    const NEW_UPDATE_DATE = 20250609;
    const CURRENT_URL = window.location.href;
    const IS_DARK_MODE = document.documentElement.getAttribute('dark') === 'true';
    const HIDDEN_BJ_LIST = [];

    let allFollowUserIds = GM_getValue('allFollowUserIds', []);
    let STATION_FEED_DATA;

    let menuIds = {};
    let categoryMenuIds = {};
    let wordMenuIds = {};

    let displayFollow = GM_getValue("displayFollow", 6);
    let displayMyplus = GM_getValue("displayMyplus", 6);
    let displayMyplusvod = GM_getValue("displayMyplusvod", 4);
    let displayTop = GM_getValue("displayTop", 6);

    let myplusPosition = GM_getValue("myplusPosition", 1);
    let myplusOrder = GM_getValue("myplusOrder", 1);

    let blockedUsers = GM_getValue('blockedUsers', []);
    let blockedCategories = GM_getValue('blockedCategories', []);
    let blockedWords = GM_getValue('blockedWords', []); // 방송 목록 차단 단어

    let registeredWords = GM_getValue("registeredWords",""); // 채팅창 차단 단어
    let selectedUsers = GM_getValue("selectedUsers",""); // 유저 채팅 모아보기 아이디
    let nicknameWidth = GM_getValue("nicknameWidth",126);

    let isOpenNewtabEnabled = GM_getValue("isOpenNewtabEnabled", 0);
    let isSidebarMinimized = GM_getValue("isSidebarMinimized", 0);
    let showSidebarOnScreenMode = GM_getValue("showSidebarOnScreenMode", 1);
    let showSidebarOnScreenModeAlways = GM_getValue("showSidebarOnScreenModeAlways", 0);
    let savedCategory = GM_getValue("szBroadCategory",0);
    let isAutoChangeMuteEnabled = GM_getValue("isAutoChangeMuteEnabled", 0);
    let isDuplicateRemovalEnabled = GM_getValue("isDuplicateRemovalEnabled", 1);
    let isRemainingBufferTimeEnabled = GM_getValue("isRemainingBufferTimeEnabled", 1);
    let isPinnedStreamWithNotificationEnabled = GM_getValue("isPinnedStreamWithNotificationEnabled", 0);
    let isPinnedStreamWithPinEnabled = GM_getValue("isPinnedStreamWithPinEnabled", 0);
    let isBottomChatEnabled = GM_getValue("isBottomChatEnabled", 0);
    let isMakePauseButtonEnabled = GM_getValue("isMakePauseButtonEnabled", 1);
    let isCaptureButtonEnabled = GM_getValue("isCaptureButtonEnabled", 1);
    let isStreamDownloadEnabled = GM_getValue("isStreamDownloadEnabled", 0);
    let isMakeSharpModeShortcutEnabled = GM_getValue("isMakeSharpModeShortcutEnabled", 1);
    let isMakeLowLatencyShortcutEnabled = GM_getValue("isMakeLowLatencyShortcutEnabled", 1);
    let isSendLoadBroadEnabled = GM_getValue("isSendLoadBroadEnabled", 1);
    let isSelectBestQualityEnabled = GM_getValue("isSelectBestQualityEnabled", 1);
    let isHideSupporterBadgeEnabled = GM_getValue("isHideSupporterBadgeEnabled",0);
    let isHideFanBadgeEnabled = GM_getValue("isHideFanBadgeEnabled",0);
    let isHideSubBadgeEnabled = GM_getValue("isHideSubBadgeEnabled",0);
    let isHideVIPBadgeEnabled = GM_getValue("isHideVIPBadgeEnabled",0);
    let isHideManagerBadgeEnabled = GM_getValue("isHideManagerBadgeEnabled",0);
    let isHideStreamerBadgeEnabled = GM_getValue("isHideStreamerBadgeEnabled",0);
    let isBlockWordsEnabled = GM_getValue("isBlockWordsEnabled",0);
    let isAutoClaimGemEnabled = GM_getValue("isAutoClaimGemEnabled",0);
    let isVideoSkipHandlerEnabled = GM_getValue("isVideoSkipHandlerEnabled",0);
    let isSmallUserLayoutEnabled = GM_getValue("isSmallUserLayoutEnabled",0);
    let isChannelFeedEnabled = GM_getValue("isChannelFeedEnabled",1);
    let isChangeFontEnabled = GM_getValue("isChangeFontEnabled", 0);
    let isCustomSidebarEnabled = GM_getValue("isCustomSidebarEnabled", 1);
    let isRemoveCarouselEnabled = GM_getValue("isRemoveCarouselEnabled", 0);
    let isDocumentTitleUpdateEnabled = GM_getValue("isDocumentTitleUpdateEnabled", 1);
    let isRemoveRedistributionTagEnabled = GM_getValue("isRemoveRedistributionTagEnabled", 1);
    let isRemoveWatchLaterButtonEnabled = GM_getValue("isRemoveWatchLaterButtonEnabled", 1);
    let isRemoveBroadStartTimeTagEnabled = GM_getValue("isRemoveBroadStartTimeTagEnabled", 0);
    let isBroadTitleTextEllipsisEnabled = GM_getValue("isBroadTitleTextEllipsisEnabled", 0);
    let isUnlockCopyPasteEnabled = GM_getValue("isUnlockCopyPasteEnabled", 0);
    let isAlignNicknameRightEnabled = GM_getValue("isAlignNicknameRightEnabled", 0);
    let isPreviewModalEnabled = GM_getValue("isPreviewModalEnabled", 1);
    let isReplaceEmptyThumbnailEnabled = GM_getValue("isReplaceEmptyThumbnailEnabled", 1);
    let isAutoScreenModeEnabled = GM_getValue("isAutoScreenModeEnabled", 0);
    let isAdjustDelayNoGridEnabled = GM_getValue("isAdjustDelayNoGridEnabled", 0);
    let ishideButtonsAboveChatInputEnabled = GM_getValue("ishideButtonsAboveChatInputEnabled", 0);
    let isExpandVODChatAreaEnabled = GM_getValue("isExpandVODChatAreaEnabled", 1);
    let isExpandLiveChatAreaEnabled = GM_getValue("isExpandLiveChatAreaEnabled", 1);
    let isOpenExternalPlayerEnabled = GM_getValue("isOpenExternalPlayerEnabled", 0);
    let isOpenExternalPlayerFromSidebarEnabled = GM_getValue("isOpenExternalPlayerFromSidebarEnabled", 0);
    let isRemoveShadowsFromCatchEnabled = GM_getValue("isRemoveShadowsFromCatchEnabled", 0);
    let isChzzkTopChannelsEnabled = GM_getValue("isChzzkTopChannelsEnabled", 0);
    let isChzzkFollowChannelsEnabled = GM_getValue("isChzzkFollowChannelsEnabled", 0);
    let isAdaptiveSpeedControlEnabled = GM_getValue("isAdaptiveSpeedControlEnabled", 0);
    let isShowSelectedMessagesEnabled = GM_getValue("isShowSelectedMessagesEnabled", 0);
    let isShowDeletedMessagesEnabled = GM_getValue("isShowDeletedMessagesEnabled", 0);
    let isNoAutoVODEnabled = GM_getValue("isNoAutoVODEnabled", 1);
    let isHideEsportsInfoEnabled = GM_getValue("isHideEsportsInfoEnabled",0);
    let isBlockedCategorySortingEnabled = GM_getValue("isBlockedCategorySortingEnabled",0);
    let isChatCounterEnabled = GM_getValue("isChatCounterEnabled",1);
    let isRandomSortEnabled = GM_getValue("isRandomSortEnabled",0);
    let isPinnedOnlineOnlyEnabled = GM_getValue("isPinnedOnlineOnlyEnabled",0);

    const WEB_PLAYER_SCROLL_LEFT = isSidebarMinimized ? 52 : 240;

    function loadHlsScript() {
        // hls.js 동적 로드
        const hlsScript = document.createElement('script');
        hlsScript.src = 'https://cdn.jsdelivr.net/npm/hls.js@latest';
        hlsScript.onload = function() {
            //console.log('hls.js가 성공적으로 로드되었습니다.');
        };
        hlsScript.onerror = function() {
            //console.error('hls.js 로드 중 오류가 발생했습니다.');
        };
        document.head.appendChild(hlsScript);
    }

    function applyFontStyles() {
        const style = document.createElement('style');
        style.textContent = `
            @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
            * {
                font-family: 'Inter' !important;
            }
        `;
        document.head.appendChild(style);
    }

    const getHiddenbjList = async () => {
        const url = "https://live.sooplive.co.kr/api/hiddenbj/hiddenbjController.php";

        const response = await fetchBroadList(url, 3000);

        if (response?.RESULT === 1) {
            return response.DATA || [];
        } else {
            return [];
        }
    };

    const getStationFeed = async () => {
        // 채널 피드가 비활성화된 경우 빈 배열을 반환합니다.
        if (!isChannelFeedEnabled) {
            return [];
        }

        const feedUrl = "https://myapi.sooplive.co.kr/api/feed?index_reg_date=0&user_id=&is_bj_write=1&feed_type=&page=1";
        const response = await fetchBroadList(feedUrl, 5000);

        return response?.data || [];
    };

    function loadCategoryData() {
        // 현재 시간 기록
        const currentTime = new Date().getTime();

        // 이전 실행 시간 불러오기
        const lastExecutionTime = GM_getValue("lastExecutionTime", 0);

        // 마지막 실행 시간으로부터 15분 이상 경과했는지 확인
        if (currentTime - lastExecutionTime >= 900000) {
            // URL에 현재 시간을 쿼리 스트링으로 추가해서 캐시 방지
            const url = "https://live.sooplive.co.kr/script/locale/ko_KR/broad_category.js?" + currentTime;

            GM_xmlhttpRequest({
                method: "GET",
                url: url,
                headers: {
                    "Content-Type": "text/plain; charset=utf-8"
                },
                onload: function(response) {
                    if (response.status === 200) {
                        // 성공적으로 데이터를 받았을 때 처리할 코드 작성
                        let szBroadCategory = response.responseText;
                        //console.log(szBroadCategory);
                        // 이후 처리할 작업 추가
                        szBroadCategory = JSON.parse(szBroadCategory.split('var szBroadCategory = ')[1].slice(0, -1));
                        if (szBroadCategory.CHANNEL.RESULT === "1") {
                            // 데이터 저장
                            GM_setValue("szBroadCategory", szBroadCategory);
                            // 현재 시간을 마지막 실행 시간으로 업데이트
                            GM_setValue("lastExecutionTime", currentTime);
                        }
                    } else {
                        console.error("Failed to load data:", response.statusText);
                    }
                },
                onerror: function(error) {
                    console.error("Error occurred while loading data:", error);
                }
            });
        }
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            if (isChangeFontEnabled) applyFontStyles();
            loadCategoryData();
        });
    } else {
        if (isChangeFontEnabled) applyFontStyles();
        loadCategoryData();
    }

    const CommonStyles = `
#blockWordsInput::placeholder, #selectedUsersInput::placeholder {
  font-size: 14px;
}
/* Expand 토글용 li 스타일 */
.expand-toggle-li {
    width: 32px;
    height: 32px;
    cursor: pointer;
    background-color: transparent;
    background-repeat: no-repeat;
    background-position: center;
    list-style: none;
    background-size: 20px;

    /* 채팅 확장 아이콘 */
    background-image: url('data:image/svg+xml,%3Csvg%20fill%3D%22%23757B8A%22%20height%3D%2264%22%20width%3D%2264%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20512%20512%22%20xml%3Aspace%3D%22preserve%22%20stroke%3D%22%23757B8A%22%3E%3Cg%20stroke-width%3D%220%22%2F%3E%3Cg%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3Cpath%20d%3D%22M335.085%20207.085%20469.333%2072.837V128c0%2011.782%209.551%2021.333%2021.333%2021.333S512%20139.782%20512%20128V21.335q-.001-1.055-.106-2.107c-.031-.316-.09-.622-.135-.933-.054-.377-.098-.755-.172-1.13-.071-.358-.169-.705-.258-1.056-.081-.323-.152-.648-.249-.968-.104-.345-.234-.678-.355-1.015-.115-.319-.22-.641-.35-.956s-.284-.616-.428-.923c-.153-.324-.297-.651-.467-.969-.158-.294-.337-.574-.508-.86-.186-.311-.362-.626-.565-.93-.211-.316-.447-.613-.674-.917-.19-.253-.366-.513-.568-.76a22%2022%200%200%200-1.402-1.551l-.011-.012-.011-.01a22%2022%200%200%200-1.552-1.403c-.247-.203-.507-.379-.761-.569-.303-.227-.6-.462-.916-.673-.304-.203-.619-.379-.931-.565-.286-.171-.565-.35-.859-.508-.318-.17-.644-.314-.969-.467-.307-.145-.609-.298-.923-.429-.315-.13-.637-.236-.957-.35-.337-.121-.669-.25-1.013-.354-.32-.097-.646-.168-.969-.249-.351-.089-.698-.187-1.055-.258-.375-.074-.753-.119-1.13-.173-.311-.044-.617-.104-.933-.135A22%2022%200%200%200%20490.667%200H384c-11.782%200-21.333%209.551-21.333%2021.333S372.218%2042.666%20384%2042.666h55.163L304.915%20176.915c-8.331%208.331-8.331%2021.839%200%2030.17s21.839%208.331%2030.17%200zm-158.17%2097.83L42.667%20439.163V384c0-11.782-9.551-21.333-21.333-21.333C9.551%20362.667%200%20372.218%200%20384v106.667q.001%201.055.106%202.105c.031.315.09.621.135.933.054.377.098.756.173%201.13.071.358.169.704.258%201.055.081.324.152.649.249.969.104.344.233.677.354%201.013.115.32.22.642.35.957s.284.616.429.923c.153.324.297.651.467.969.158.294.337.573.508.859.186.311.362.627.565.931.211.316.446.612.673.916.19.254.366.514.569.761q.664.811%201.403%201.552l.01.011.012.011q.741.738%201.551%201.402c.247.203.507.379.76.568.304.227.601.463.917.674.303.203.618.379.93.565.286.171.565.35.86.508.318.17.645.314.969.467.307.145.609.298.923.428s.636.235.956.35c.337.121.67.25%201.015.355.32.097.645.168.968.249.351.089.698.187%201.056.258.375.074.753.118%201.13.172.311.044.618.104.933.135q1.05.105%202.104.106H128c11.782%200%2021.333-9.551%2021.333-21.333s-9.551-21.333-21.333-21.333H72.837l134.248-134.248c8.331-8.331%208.331-21.839%200-30.17s-21.839-8.331-30.17%200zm330.821%20198.51c.226-.302.461-.598.671-.913.204-.304.38-.62.566-.932.17-.285.349-.564.506-.857.17-.318.315-.646.468-.971.145-.306.297-.607.428-.921.13-.315.236-.637.35-.957.121-.337.25-.669.354-1.013.097-.32.168-.646.249-.969.089-.351.187-.698.258-1.055.074-.375.118-.753.173-1.13.044-.311.104-.617.135-.933a22%2022%200%200%200%20.106-2.107V384c0-11.782-9.551-21.333-21.333-21.333s-21.333%209.551-21.333%2021.333v55.163L335.085%20304.915c-8.331-8.331-21.839-8.331-30.17%200s-8.331%2021.839%200%2030.17l134.248%20134.248H384c-11.782%200-21.333%209.551-21.333%2021.333S372.218%20512%20384%20512h106.667q1.055-.001%202.105-.106c.315-.031.621-.09.933-.135.377-.054.756-.098%201.13-.173.358-.071.704-.169%201.055-.258.324-.081.649-.152.969-.249.344-.104.677-.233%201.013-.354.32-.115.642-.22.957-.35s.615-.283.921-.428c.325-.153.653-.297.971-.468.293-.157.572-.336.857-.506.312-.186.628-.363.932-.566.315-.211.611-.445.913-.671.255-.191.516-.368.764-.571q.804-.659%201.54-1.392l.023-.021.021-.023q.732-.736%201.392-1.54c.205-.248.382-.509.573-.764zM72.837%2042.667H128c11.782%200%2021.333-9.551%2021.333-21.333C149.333%209.551%20139.782%200%20128%200H21.332q-1.054.001-2.104.106c-.316.031-.622.09-.933.135-.377.054-.755.098-1.13.172-.358.071-.705.169-1.056.258-.323.081-.648.152-.968.249-.345.104-.678.234-1.015.355-.319.115-.641.22-.956.35-.315.131-.618.284-.925.43-.323.152-.65.296-.967.466-.295.158-.575.338-.862.509-.31.185-.625.36-.928.563-.317.212-.615.448-.92.676-.252.189-.511.364-.756.566a21.5%2021.5%200%200%200-2.977%202.977c-.202.245-.377.504-.566.757-.228.305-.464.603-.676.92-.203.303-.378.617-.564.928-.171.286-.351.567-.509.862-.17.317-.313.643-.466.967-.145.307-.299.61-.43.925-.13.315-.235.636-.35.956-.121.337-.25.67-.355%201.015-.097.32-.168.645-.249.968-.089.351-.187.698-.258%201.056-.074.375-.118.753-.172%201.13-.044.311-.104.618-.135.933A22%2022%200%200%200%200%2021.333V128c0%2011.782%209.551%2021.333%2021.333%2021.333S42.666%20139.782%2042.666%20128V72.837l134.248%20134.248c8.331%208.331%2021.839%208.331%2030.17%200s8.331-21.839%200-30.17z%22%2F%3E%3C%2Fsvg%3E');
}
.expandVODChat .expand-toggle-li,
.expandLiveChat .expand-toggle-li {
    background-image: url('data:image/svg+xml,%3Csvg%20fill%3D%22%23757B8A%22%20height%3D%2264%22%20width%3D%2264%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20512%20512%22%20xml%3Aspace%3D%22preserve%22%20stroke%3D%22%23757B8A%22%3E%3Cg%20stroke-width%3D%220%22%2F%3E%3Cg%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3Cpath%20d%3D%22M320.106%20172.772c.031.316.09.622.135.933.054.377.098.755.172%201.13.071.358.169.705.258%201.056.081.323.152.648.249.968.104.345.234.678.355%201.015.115.319.22.641.35.956.131.315.284.618.43.925.152.323.296.65.466.967.158.294.337.574.508.86.186.311.362.626.565.93.211.316.447.613.674.917.19.253.365.513.568.759a21.4%2021.4%200%200%200%202.977%202.977c.246.202.506.378.759.567.304.228.601.463.918.675.303.203.618.379.929.565.286.171.566.351.861.509.317.17.644.314.968.466.307.145.609.298.924.429.315.13.637.236.957.35.337.121.669.25%201.013.354.32.097.646.168.969.249.351.089.698.187%201.055.258.375.074.753.119%201.13.173.311.044.617.104.932.135q1.051.105%202.105.106H448c11.782%200%2021.333-9.551%2021.333-21.333s-9.551-21.333-21.333-21.333h-55.163L505.752%2036.418c8.331-8.331%208.331-21.839%200-30.17s-21.839-8.331-30.17%200L362.667%20119.163V64c0-11.782-9.551-21.333-21.333-21.333C329.551%2042.667%20320%2052.218%20320%2064v106.668q.001%201.053.106%202.104zM170.667%2042.667c-11.782%200-21.333%209.551-21.333%2021.333v55.163L36.418%206.248c-8.331-8.331-21.839-8.331-30.17%200s-8.331%2021.839%200%2030.17l112.915%20112.915H64c-11.782%200-21.333%209.551-21.333%2021.333C42.667%20182.449%2052.218%20192%2064%20192h106.667q1.055-.001%202.105-.106c.316-.031.622-.09.933-.135.377-.054.755-.098%201.13-.172.358-.071.705-.169%201.056-.258.323-.081.648-.152.968-.249.345-.104.678-.234%201.015-.355.319-.115.641-.22.956-.35.315-.131.618-.284.925-.43.323-.152.65-.296.967-.466.295-.158.575-.338.862-.509.311-.185.625-.361.928-.564.317-.212.615-.448.92-.676.252-.189.511-.364.757-.566a21.5%2021.5%200%200%200%202.977-2.977c.202-.246.377-.505.566-.757.228-.305.464-.603.676-.92.203-.303.378-.617.564-.928.171-.286.351-.567.509-.862.17-.317.313-.643.466-.967.145-.307.299-.61.43-.925.13-.315.235-.636.35-.956.121-.337.25-.67.355-1.015.097-.32.168-.645.249-.968.089-.351.187-.698.258-1.056.074-.375.118-.753.172-1.13.044-.311.104-.618.135-.933q.105-1.05.106-2.104V64c-.002-11.782-9.553-21.333-21.335-21.333zm21.227%20296.561c-.031-.316-.09-.622-.135-.933-.054-.377-.098-.755-.172-1.13-.071-.358-.169-.705-.258-1.056-.081-.323-.152-.648-.249-.968-.104-.345-.234-.678-.355-1.015-.115-.319-.22-.641-.35-.956-.131-.315-.284-.618-.43-.925-.152-.323-.296-.65-.466-.967-.158-.295-.338-.575-.509-.862-.185-.311-.361-.625-.564-.928-.212-.317-.448-.615-.676-.92-.189-.252-.364-.511-.566-.757a21.5%2021.5%200%200%200-2.977-2.977c-.246-.202-.505-.377-.757-.566-.305-.228-.603-.464-.92-.676-.303-.203-.617-.378-.928-.564-.286-.171-.567-.351-.862-.509-.317-.17-.643-.313-.967-.466-.307-.145-.61-.299-.925-.43-.315-.13-.636-.235-.956-.35-.337-.121-.67-.25-1.015-.355-.32-.097-.645-.168-.968-.249-.351-.089-.698-.187-1.056-.258-.375-.074-.753-.118-1.13-.172-.311-.044-.618-.104-.933-.135q-1.051-.105-2.105-.106H64c-11.782%200-21.333%209.551-21.333%2021.333S52.218%20362.664%2064%20362.664h55.163L6.248%20475.582c-8.331%208.331-8.331%2021.839%200%2030.17s21.839%208.331%2030.17%200l112.915-112.915V448c0%2011.782%209.551%2021.333%2021.333%2021.333s21.333-9.551%2021.333-21.333V341.332a21%2021%200%200%200-.105-2.104zm200.943%2023.439H448c11.782%200%2021.333-9.551%2021.333-21.333s-9.551-21.333-21.333-21.333H341.333q-1.055.001-2.105.106c-.315.031-.621.09-.932.135-.378.054-.756.098-1.13.173-.358.071-.704.169-1.055.258-.324.081-.649.152-.969.249-.344.104-.677.233-1.013.354-.32.115-.642.22-.957.35-.315.131-.617.284-.924.429-.324.153-.65.296-.968.466-.295.158-.575.338-.861.509-.311.186-.626.362-.929.565-.316.212-.614.447-.918.675-.253.19-.512.365-.759.567a21.4%2021.4%200%200%200-2.977%202.977c-.202.246-.378.506-.568.759-.227.304-.463.601-.674.917-.203.304-.379.619-.565.93-.171.286-.351.566-.508.86-.17.317-.313.643-.466.967-.145.307-.299.61-.43.925-.13.315-.235.636-.35.956-.121.337-.25.67-.355%201.015-.097.32-.168.645-.249.968-.089.351-.187.698-.258%201.056-.074.374-.118.753-.172%201.13-.044.311-.104.618-.135.933q-.105%201.05-.106%202.104V448c0%2011.782%209.551%2021.333%2021.333%2021.333s21.333-9.551%2021.333-21.333v-55.163l112.915%20112.915c8.331%208.331%2021.839%208.331%2030.17%200s8.331-21.839%200-30.17z%22%2F%3E%3C%2Fsvg%3E');
}

html[dark="true"] .expand-toggle-li {
    background-image: url('data:image/svg+xml,%3Csvg%20fill%3D%22%23ACB0B9%22%20height%3D%2264%22%20width%3D%2264%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20512%20512%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%20stroke-width%3D%220%22%2F%3E%3Cg%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3Cpath%20d%3D%22M335.085%20207.085%20469.333%2072.837V128c0%2011.782%209.551%2021.333%2021.333%2021.333S512%20139.782%20512%20128V21.335q-.001-1.055-.106-2.107c-.031-.316-.09-.622-.135-.933-.054-.377-.098-.755-.172-1.13-.071-.358-.169-.705-.258-1.056-.081-.323-.152-.648-.249-.968-.104-.345-.234-.678-.355-1.015-.115-.319-.22-.641-.35-.956s-.284-.616-.428-.923c-.153-.324-.297-.651-.467-.969-.158-.294-.337-.574-.508-.86-.186-.311-.362-.626-.565-.93-.211-.316-.447-.613-.674-.917-.19-.253-.366-.513-.568-.76a22%2022%200%200%200-1.402-1.551l-.011-.012-.011-.01a22%2022%200%200%200-1.552-1.403c-.247-.203-.507-.379-.761-.569-.303-.227-.6-.462-.916-.673-.304-.203-.619-.379-.931-.565-.286-.171-.565-.35-.859-.508-.318-.17-.644-.314-.969-.467-.307-.145-.609-.298-.923-.429-.315-.13-.637-.236-.957-.35-.337-.121-.669-.25-1.013-.354-.32-.097-.646-.168-.969-.249-.351-.089-.698-.187-1.055-.258-.375-.074-.753-.119-1.13-.173-.311-.044-.617-.104-.933-.135A22%2022%200%200%200%20490.667%200H384c-11.782%200-21.333%209.551-21.333%2021.333S372.218%2042.666%20384%2042.666h55.163L304.915%20176.915c-8.331%208.331-8.331%2021.839%200%2030.17s21.839%208.331%2030.17%200m-158.17%2097.83L42.667%20439.163V384c0-11.782-9.551-21.333-21.333-21.333C9.551%20362.667%200%20372.218%200%20384v106.667q.001%201.055.106%202.105c.031.315.09.621.135.933.054.377.098.756.173%201.13.071.358.169.704.258%201.055.081.324.152.649.249.969.104.344.233.677.354%201.013.115.32.22.642.35.957s.284.616.429.923c.153.324.297.651.467.969.158.294.337.573.508.859.186.311.362.627.565.931.211.316.446.612.673.916.19.254.366.514.569.761q.664.811%201.403%201.552l.01.011.012.011q.741.738%201.551%201.402c.247.203.507.379.76.568.304.227.601.463.917.674.303.203.618.379.93.565.286.171.565.35.86.508.318.17.645.314.969.467.307.145.609.298.923.428s.636.235.956.35c.337.121.67.25%201.015.355.32.097.645.168.968.249.351.089.698.187%201.056.258.375.074.753.118%201.13.172.311.044.618.104.933.135q1.05.105%202.104.106H128c11.782%200%2021.333-9.551%2021.333-21.333s-9.551-21.333-21.333-21.333H72.837l134.248-134.248c8.331-8.331%208.331-21.839%200-30.17s-21.839-8.331-30.17%200m330.821%20198.51c.226-.302.461-.598.671-.913.204-.304.38-.62.566-.932.17-.285.349-.564.506-.857.17-.318.315-.646.468-.971.145-.306.297-.607.428-.921.13-.315.236-.637.35-.957.121-.337.25-.669.354-1.013.097-.32.168-.646.249-.969.089-.351.187-.698.258-1.055.074-.375.118-.753.173-1.13.044-.311.104-.617.135-.933a22%2022%200%200%200%20.106-2.107V384c0-11.782-9.551-21.333-21.333-21.333s-21.333%209.551-21.333%2021.333v55.163L335.085%20304.915c-8.331-8.331-21.839-8.331-30.17%200s-8.331%2021.839%200%2030.17l134.248%20134.248H384c-11.782%200-21.333%209.551-21.333%2021.333S372.218%20512%20384%20512h106.667q1.055-.001%202.105-.106c.315-.031.621-.09.933-.135.377-.054.756-.098%201.13-.173.358-.071.704-.169%201.055-.258.324-.081.649-.152.969-.249.344-.104.677-.233%201.013-.354.32-.115.642-.22.957-.35s.615-.283.921-.428c.325-.153.653-.297.971-.468.293-.157.572-.336.857-.506.312-.186.628-.363.932-.566.315-.211.611-.445.913-.671.255-.191.516-.368.764-.571q.804-.659%201.54-1.392l.023-.021.021-.023q.732-.736%201.392-1.54c.205-.248.382-.509.573-.764M72.837%2042.667H128c11.782%200%2021.333-9.551%2021.333-21.333C149.333%209.551%20139.782%200%20128%200H21.332q-1.054.001-2.104.106c-.316.031-.622.09-.933.135-.377.054-.755.098-1.13.172-.358.071-.705.169-1.056.258-.323.081-.648.152-.968.249-.345.104-.678.234-1.015.355-.319.115-.641.22-.956.35-.315.131-.618.284-.925.43-.323.152-.65.296-.967.466-.295.158-.575.338-.862.509-.31.185-.625.36-.928.563-.317.212-.615.448-.92.676-.252.189-.511.364-.756.566a21.5%2021.5%200%200%200-2.977%202.977c-.202.245-.377.504-.566.757-.228.305-.464.603-.676.92-.203.303-.378.617-.564.928-.171.286-.351.567-.509.862-.17.317-.313.643-.466.967-.145.307-.299.61-.43.925-.13.315-.235.636-.35.956-.121.337-.25.67-.355%201.015-.097.32-.168.645-.249.968-.089.351-.187.698-.258%201.056-.074.375-.118.753-.172%201.13-.044.311-.104.618-.135.933A22%2022%200%200%200%200%2021.333V128c0%2011.782%209.551%2021.333%2021.333%2021.333S42.666%20139.782%2042.666%20128V72.837l134.248%20134.248c8.331%208.331%2021.839%208.331%2030.17%200s8.331-21.839%200-30.17z%22%2F%3E%3C%2Fsvg%3E') !important;
}
html[dark="true"] .expandVODChat .expand-toggle-li,
html[dark="true"] .expandLiveChat .expand-toggle-li {
    background-image: url('data:image/svg+xml,%3Csvg%20fill%3D%22%23ACB0B9%22%20height%3D%2264%22%20width%3D%2264%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20512%20512%22%20xml%3Aspace%3D%22preserve%22%20stroke%3D%22%23ACB0B9%22%3E%3Cg%20stroke-width%3D%220%22%2F%3E%3Cg%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke%3D%22%23CCC%22%20stroke-width%3D%222.048%22%2F%3E%3Cpath%20d%3D%22M320.106%20172.772c.031.316.09.622.135.933.054.377.098.755.172%201.13.071.358.169.705.258%201.056.081.323.152.648.249.968.104.345.234.678.355%201.015.115.319.22.641.35.956.131.315.284.618.43.925.152.323.296.65.466.967.158.294.337.574.508.86.186.311.362.626.565.93.211.316.447.613.674.917.19.253.365.513.568.759a21.4%2021.4%200%200%200%202.977%202.977c.246.202.506.378.759.567.304.228.601.463.918.675.303.203.618.379.929.565.286.171.566.351.861.509.317.17.644.314.968.466.307.145.609.298.924.429.315.13.637.236.957.35.337.121.669.25%201.013.354.32.097.646.168.969.249.351.089.698.187%201.055.258.375.074.753.119%201.13.173.311.044.617.104.932.135q1.051.105%202.105.106H448c11.782%200%2021.333-9.551%2021.333-21.333s-9.551-21.333-21.333-21.333h-55.163L505.752%2036.418c8.331-8.331%208.331-21.839%200-30.17s-21.839-8.331-30.17%200L362.667%20119.163V64c0-11.782-9.551-21.333-21.333-21.333C329.551%2042.667%20320%2052.218%20320%2064v106.668q.001%201.053.106%202.104zM170.667%2042.667c-11.782%200-21.333%209.551-21.333%2021.333v55.163L36.418%206.248c-8.331-8.331-21.839-8.331-30.17%200s-8.331%2021.839%200%2030.17l112.915%20112.915H64c-11.782%200-21.333%209.551-21.333%2021.333C42.667%20182.449%2052.218%20192%2064%20192h106.667q1.055-.001%202.105-.106c.316-.031.622-.09.933-.135.377-.054.755-.098%201.13-.172.358-.071.705-.169%201.056-.258.323-.081.648-.152.968-.249.345-.104.678-.234%201.015-.355.319-.115.641-.22.956-.35.315-.131.618-.284.925-.43.323-.152.65-.296.967-.466.295-.158.575-.338.862-.509.311-.185.625-.361.928-.564.317-.212.615-.448.92-.676.252-.189.511-.364.757-.566a21.5%2021.5%200%200%200%202.977-2.977c.202-.246.377-.505.566-.757.228-.305.464-.603.676-.92.203-.303.378-.617.564-.928.171-.286.351-.567.509-.862.17-.317.313-.643.466-.967.145-.307.299-.61.43-.925.13-.315.235-.636.35-.956.121-.337.25-.67.355-1.015.097-.32.168-.645.249-.968.089-.351.187-.698.258-1.056.074-.375.118-.753.172-1.13.044-.311.104-.618.135-.933q.105-1.05.106-2.104V64c-.002-11.782-9.553-21.333-21.335-21.333zm21.227%20296.561c-.031-.316-.09-.622-.135-.933-.054-.377-.098-.755-.172-1.13-.071-.358-.169-.705-.258-1.056-.081-.323-.152-.648-.249-.968-.104-.345-.234-.678-.355-1.015-.115-.319-.22-.641-.35-.956-.131-.315-.284-.618-.43-.925-.152-.323-.296-.65-.466-.967-.158-.295-.338-.575-.509-.862-.185-.311-.361-.625-.564-.928-.212-.317-.448-.615-.676-.92-.189-.252-.364-.511-.566-.757a21.5%2021.5%200%200%200-2.977-2.977c-.246-.202-.505-.377-.757-.566-.305-.228-.603-.464-.92-.676-.303-.203-.617-.378-.928-.564-.286-.171-.567-.351-.862-.509-.317-.17-.643-.313-.967-.466-.307-.145-.61-.299-.925-.43-.315-.13-.636-.235-.956-.35-.337-.121-.67-.25-1.015-.355-.32-.097-.645-.168-.968-.249-.351-.089-.698-.187-1.056-.258-.375-.074-.753-.118-1.13-.172-.311-.044-.618-.104-.933-.135q-1.051-.105-2.105-.106H64c-11.782%200-21.333%209.551-21.333%2021.333S52.218%20362.664%2064%20362.664h55.163L6.248%20475.582c-8.331%208.331-8.331%2021.839%200%2030.17s21.839%208.331%2030.17%200l112.915-112.915V448c0%2011.782%209.551%2021.333%2021.333%2021.333s21.333-9.551%2021.333-21.333V341.332a21%2021%200%200%200-.105-2.104zm200.943%2023.439H448c11.782%200%2021.333-9.551%2021.333-21.333s-9.551-21.333-21.333-21.333H341.333q-1.055.001-2.105.106c-.315.031-.621.09-.932.135-.378.054-.756.098-1.13.173-.358.071-.704.169-1.055.258-.324.081-.649.152-.969.249-.344.104-.677.233-1.013.354-.32.115-.642.22-.957.35-.315.131-.617.284-.924.429-.324.153-.65.296-.968.466-.295.158-.575.338-.861.509-.311.186-.626.362-.929.565-.316.212-.614.447-.918.675-.253.19-.512.365-.759.567a21.4%2021.4%200%200%200-2.977%202.977c-.202.246-.378.506-.568.759-.227.304-.463.601-.674.917-.203.304-.379.619-.565.93-.171.286-.351.566-.508.86-.17.317-.313.643-.466.967-.145.307-.299.61-.43.925-.13.315-.235.636-.35.956-.121.337-.25.67-.355%201.015-.097.32-.168.645-.249.968-.089.351-.187.698-.258%201.056-.074.374-.118.753-.172%201.13-.044.311-.104.618-.135.933q-.105%201.05-.106%202.104V448c0%2011.782%209.551%2021.333%2021.333%2021.333s21.333-9.551%2021.333-21.333v-55.163l112.915%20112.915c8.331%208.331%2021.839%208.331%2030.17%200s8.331-21.839%200-30.17z%22%2F%3E%3C%2Fsvg%3E') !important;
}

.screen_mode .expand-toggle-li,
.fullScreen_mode .expand-toggle-li {
    display: none !important;
}

.customSidebar #serviceLnb {
    display: none !important;
}

.left_navbar {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    position: fixed;
    flex-direction: row-reverse;
    top: 0px;
    left: 128px;
    z-index: 9999;
    background-color: white;
}
html[dark="true"] .left_navbar {
    background-color: #0c0d0e;
}

html[dark="true"] .left_nav_button {
    color: #e5e5e5;
}
html:not([dark="true"]) .left_nav_button {
    color: #1F1F23;
}
html[dark="true"] .left_nav_button {
    color: #e5e5e5;
}
html:not([dark="true"]) .left_nav_button {
    color: #1F1F23;
}

.left_navbar button.left_nav_button {
    position: relative;
    width: 68px;
    height: 64px;
    padding: 0;
    border: 0;
    cursor: pointer;
    z-index: 3001;
    font-size: 1.25em !important;
    font-weight: 600;
}

@media (max-width: 1280px) {
    #serviceHeader .left_navbar {
        left: 124px !important;
    }
    #serviceHeader .left_nav_button {
        width: 58px !important;
        font-size: 1.2em !important;
    }
}

@media (max-width: 1100px) {
    #serviceHeader .left_navbar {
        left: 120px !important;
    }
    #serviceHeader .left_nav_button {
        width: 46px !important;
        font-size: 1.1em !important;
    }
}

#sidebar {
    top: 64px;
    display: flex !important;
    flex-direction: column !important;
}

#sidebar .top-section.follow {
    order: 1;
}

#sidebar .users-section.follow {
    order: 2;
}

#sidebar .top-section.myplus {
    order: 3;
}

#sidebar .users-section.myplus {
    order: 4;
}

#sidebar .top-section.myplusvod {
    order: 5;
}

#sidebar .users-section.myplusvod {
    order: 6;
}

#sidebar .top-section.top {
    order: 7;
}

#sidebar .users-section.top {
    order: 8;
}

.starting-line .chatting-list-item .message-container .username {
    width: ${nicknameWidth}px !important;
}

.duration-overlay {
    position: absolute;
    top: 235px;
    right: 4px;
    background-color: rgba(0, 0, 0, 0.7);
    color: white;
    padding: 2px 5px;
    font-size: 15px;
    border-radius: 3px;
    z-index:9999;
    line-height: 17px;
}

#studioPlayKorPlayer,
#studioPlayKor,
#studioPlay,
.btn-broadcast {
    display: none;
}

#myModal.modal {
  display: none;
  position: fixed;
  z-index: 9999;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  overflow: auto;
  background-color: rgba(0,0,0,0.4);
  color: black;
}

#myModal .modal-content {
  background-color: #fefefe;
  margin: 15% auto;
  padding: 20px;
  border: 1px solid #888;
  border-radius: 10px;
  width: clamp(400px, 80%, 550px);
}

#myModal .myModalClose {
  color: #aaa;
  float: right;
  font-size: 36px;
  font-weight: bold;
  margin-top: -12px;
}

#myModal .myModalClose:hover,
#myModal .myModalClose:focus {
  color: black;
  text-decoration: none;
  cursor: pointer;
}

#myModal .option {
  margin-bottom: 10px;
  display: flex;
  align-items: center;
}

#myModal .option label {
  margin-right: 10px;
  font-size: 15px;
}

#myModal .switch {
  position: relative;
  display: inline-block;
  width: 60px;
  height: 34px;
  transform: scale(0.9); /* 축소 */
}

#myModal .switch input {
  display: none;
}

#myModal .slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  transition: .4s;
  border-radius: 34px;
}

#myModal .slider:before {
  position: absolute;
  content: "";
  height: 26px;
  width: 26px;
  left: 4px;
  bottom: 4px;
  background-color: white;
  transition: .4s;
  border-radius: 50%;
}

#myModal .slider.round {
  border-radius: 34px;
  min-width: 60px;
}

#myModal .slider.round:before {
  border-radius: 50%;
}
#myModal input:checked + .slider {
  background-color: #2196F3;
}

#myModal input:focus + .slider {
  box-shadow: 0 0 1px #2196F3;
}

#myModal input:checked + .slider:before {
  transform: translateX(26px);
}

#myModal #range {
  width: 100%;
}

#myModal #rangeValue {
  display: inline-block;
  margin-left: 10px;
}

#myModal .divider {
    width: 100%; /* 가로 폭 설정 */
    height: 1px; /* 세로 높이 설정 */
    background-color: #000; /* 배경색 설정 */
    margin: 20px 0; /* 위아래 여백 설정 */
}

#openModalBtn {
    box-sizing: border-box;
    font-size: 12px;
    line-height: 1.2 !important;
    font-family: "NG";
    list-style: none;
    position: relative;
    margin-left: 12px;
    width: 40px;
    height: 40px;
}

#topInnerHeader #openModalBtn {
    margin-right: 12px;
}
#openModalBtn > button.btn-settings-ui {
    background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' fill='none'%3e%3cpath stroke='%23757B8A' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.4' d='M8.269 2.061c.44-1.815 3.022-1.815 3.462 0a1.782 1.782 0 0 0 2.658 1.101c1.595-.971 3.42.854 2.449 2.449a1.781 1.781 0 0 0 1.1 2.658c1.816.44 1.816 3.022 0 3.462a1.781 1.781 0 0 0-1.1 2.659c.971 1.595-.854 3.42-2.449 2.448a1.781 1.781 0 0 0-2.658 1.101c-.44 1.815-3.022 1.815-3.462 0a1.781 1.781 0 0 0-2.658-1.101c-1.595.972-3.42-.854-2.449-2.448a1.782 1.782 0 0 0-1.1-2.659c-1.816-.44-1.816-3.021 0-3.462a1.782 1.782 0 0 0 1.1-2.658c-.972-1.595.854-3.42 2.449-2.449a1.781 1.781 0 0 0 2.658-1.1Z'/%3e%3cpath stroke='%23757B8A' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.4' d='M13.1 10a3.1 3.1 0 1 1-6.2 0 3.1 3.1 0 0 1 6.2 0Z'/%3e%3c/svg%3e") 50% 50% no-repeat !important;
    background-size: 18px !important;
}
html[dark="true"] #openModalBtn > button.btn-settings-ui {
    background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' fill='none'%3e%3cpath stroke='%23ACB0B9' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.4' d='M8.269 2.061c.44-1.815 3.022-1.815 3.462 0a1.782 1.782 0 0 0 2.658 1.101c1.595-.971 3.42.854 2.449 2.449a1.781 1.781 0 0 0 1.1 2.658c1.816.44 1.816 3.022 0 3.462a1.781 1.781 0 0 0-1.1 2.659c.971 1.595-.854 3.42-2.449 2.448a1.781 1.781 0 0 0-2.658 1.101c-.44 1.815-3.022 1.815-3.462 0a1.781 1.781 0 0 0-2.658-1.101c-1.595.972-3.42-.854-2.449-2.448a1.782 1.782 0 0 0-1.1-2.659c-1.816-.44-1.816-3.021 0-3.462a1.782 1.782 0 0 0 1.1-2.658c-.972-1.595.854-3.42 2.449-2.449a1.781 1.781 0 0 0 2.658-1.1Z'/%3e%3cpath stroke='%23ACB0B9' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.4' d='M13.1 10a3.1 3.1 0 1 1-6.2 0 3.1 3.1 0 0 1 6.2 0Z'/%3e%3c/svg%3e") 50% 50% no-repeat !important;
    background-size: 18px !important;
}
@keyframes rotate {
    0% {
        transform: rotate(0deg);
    }
    100% {
        transform: rotate(360deg);
    }
}
/* .red-dot이 있을 때만 회전 */
#openModalBtn:has(.red-dot) .btn-settings-ui {
    animation: rotate 4s linear infinite;
    animation-duration: 4s; /* 4초에 한 번 회전 */
    animation-iteration-count: 10; /* 10번 반복 */
}
#sidebar.max {
    width: 240px;
}
#sidebar.min {
    width: 52px;
}
#sidebar.min .users-section a.user span {
    display: none;
}
#sidebar.min .users-section button {
    font-size:12px;
    padding: 4px;
}
#sidebar.max .button-fold-sidebar {
    background-size: 7px 11px;
    background-repeat: no-repeat;
    width: 26px;
    height: 26px;
    background-position: center;
    position: absolute;
    top: 13px;
    left: 200px;
}
#sidebar.max .button-unfold-sidebar {
    display:none;
}
#sidebar.min .button-fold-sidebar {
    display:none;
}
#sidebar.min .button-unfold-sidebar {
    background-size: 7px 11px;
    background-repeat: no-repeat;
    width: 26px;
    height: 26px;
    background-position: center;
    position: relative;
    top: 8px;
    left: 12px;
    padding-top:16px;
    padding-bottom:12px;
}
#sidebar.min .top-section span.max{
    display:none;
}
#sidebar.max .top-section span.min{
    display:none;
}
#toggleButton, #toggleButton2, #toggleButton3, #toggleButton4 {
    padding: 7px 0px;
    width: 100%;
    text-align: center;
    font-size: 14px;
}

html[dark="true"] #toggleButton,
html[dark="true"] #toggleButton2,
html[dark="true"] #toggleButton3,
html[dark="true"] #toggleButton4 {
    color:#A1A1A1;
}

html:not([dark="true"]) #toggleButton,
html:not([dark="true"]) #toggleButton2,
html:not([dark="true"]) #toggleButton3,
html:not([dark="true"]) #toggleButton4 {
    color: #53535F;
}

#sidebar {
    grid-area: sidebar;
    padding-bottom: 360px;
    height: 100vh;
    overflow-y: auto;
    position: fixed;
    scrollbar-width: none; /* 파이어폭스 */
    transition: all 0.1s ease-in-out; /* 부드러운 전환 효과 */
}
#sidebar::-webkit-scrollbar {
    display: none;  /* Chrome, Safari, Edge */
}
#sidebar .top-section {
    display: flex;
    align-items: center;
    justify-content: space-around;
    margin: 12px 0px 6px 0px;
    line-height: 17px;
}
#sidebar .top-section > span {
    text-transform: uppercase;
    font-weight: 550;
    font-size: 14px;
    margin-top: 6px;
    margin-bottom: 2px;
}
.users-section .user.show-more {
    max-height: 0;
    opacity: 0;
    padding-top: 0;
    padding-bottom: 0;
    pointer-events: none;
}
.users-section .user {
    display: grid;
    grid-template-areas: "profile-picture username watchers" "profile-picture description blank";
    grid-template-columns: 40px auto auto;
    padding: 5px 10px;
    max-height: 50px;
    opacity: 1;
    overflow: hidden;
    transition: opacity 0.7s ease;
}
.users-section .user:hover {
    cursor: pointer;
}
.users-section .user .profile-picture {
    grid-area: profile-picture;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    line-height: 20px;
}
.users-section .user .username {
    grid-area: username;
    font-size: 14px;
    font-weight: 600;
    letter-spacing: 0.6px;
    margin-left:1px;
    line-height: 17px;
}
.users-section .user .description {
    grid-area: description;
    font-size: 13px;
    font-weight: 400;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    margin-left:1px;
    line-height: 16px;
}
.users-section .user .watchers {
    grid-area: watchers;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    font-weight: 400;
    font-size: 14px;
    margin-right: 2px;
    line-height: 17px;
}
.users-section .user .watchers .dot {
    font-size: 10px;
    margin-right: 5px;
    color: #ff2424;
}
.users-section .user .watchers .dot.greendot {
    color: #34c76b !important;
}
.tooltip-container {
    z-index: 999;
    width: 460px;
    height: auto;
    position: fixed;
    display: flex;
    flex-direction: column;
    align-items: center;
    border-radius: 10px;
    box-shadow: 5px 5px 10px 0px rgba(0, 0, 0, 0.5);
    opacity: 0;
    transition: opacity 0.1s ease-in-out;
    pointer-events: none;
}

.tooltip-container.visible {
    opacity: 1;
    pointer-events: auto;
}

.tooltip-container img {
    z-index: 999;
    width: 100%; /* 컨테이너의 너비에 맞게 확장 */
    height: 260px; /* 고정 높이 */
    object-fit: cover; /* 비율 유지하며 공간에 맞게 잘리기 */
    border-top-left-radius: 10px;
    border-top-right-radius: 10px;
    border-bottom-left-radius: 0px;
    border-bottom-right-radius: 0px;
}

.tooltiptext {
    position: relative;
    z-index: 999;
    width: 100%;
    max-width: 460px;
    height: auto;
    text-align: center;
    box-sizing: border-box;
    padding: 14px 20px;
    font-size: 17px;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
    border-bottom-left-radius: 10px;
    border-bottom-right-radius: 10px;
    line-height: 22px;
    overflow-wrap: break-word;
}

.tooltiptext .dot {
    font-size: 11px;
    margin-right: 2px;
    vertical-align: middle;
    line-height: 22px;
    display: inline-block;
}

.profile-grayscale {
    filter: grayscale(100%) contrast(85%);
    opacity: .8;
}

#sidebar.max .small-user-layout.show-more {
    max-height: 0;
    opacity: 0;
    padding: 0 !important;
    pointer-events: none;
}
#sidebar.max .small-user-layout {
    grid-template-areas: "profile-picture username description watchers" !important;
    grid-template-columns: 24px auto 1fr auto !important;
    padding: 4px 10px !important;
    gap: 8px !important;
    max-height: 32px;
    opacity: 1;
    overflow: hidden;
    transition: opacity 0.4s ease;
}
#sidebar.max .small-user-layout .profile-picture {
    width: 24px !important;
    height: 24px !important;
    border-radius: 20% !important;
}
#sidebar.max .small-user-layout .username {
    max-width: 80px !important;
    font-size: 14px !important;
    line-height: 24px !important;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}
#sidebar.max .small-user-layout .description {
    font-size: 12px !important;
    line-height: 24px !important;
}
#sidebar.max .small-user-layout .watchers {
    font-size: 14px !important;
    line-height: 24px !important;
}
#sidebar.max .small-user-layout .watchers .dot {
    font-size: 8px !important;
    margin-right: 4px !important;
}

.customSidebar #serviceHeader .a_d_banner {
    display: none !important;
}
.customSidebar #serviceHeader .btn_flexible+.logo_wrap {
    left: 24px !important;
}
.customSidebar #serviceHeader .logo_wrap {
    left: 24px !important;
}


html[dark="true"] .users-section .user.user-offline span {
    filter: grayscale(1) brightness(0.8); /* 다크모드: 완전 흑백과 약간 어둡게 */
}

html:not([dark="true"]) .users-section .user.user-offline span {
    opacity: 0.7; /* 밝은 모드: 투명하게 */
}


/* darkMode Sidebar Styles */

html[dark="true"] #sidebar.max .button-fold-sidebar {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none slice' viewBox='0 0 7 11'%3e%3cpath fill='%23f9f9f9' d='M5.87 11.01L.01 5.51 5.87.01l1.08 1.01-4.74 4.45L7 9.96 5.87 11z'/%3e%3c/svg%3e");
}
html[dark="true"] #sidebar.min .button-unfold-sidebar {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none slice' viewBox='0 0 7 11'%3e%3cpath fill='%23f9f9f9' d='M1.13 11.01l5.86-5.5L1.13.01.05 1.02l4.74 4.45L0 9.96 1.13 11z'/%3e%3c/svg%3e");
}
html[dark="true"] #sidebar {
    color: white;
    background-color: #1F1F23;
}
html[dark="true"] #sidebar .top-section > span {
    color:#DEDEE3;
}
html[dark="true"] #sidebar .top-section > span > a {
    color:#DEDEE3;
}
html[dark="true"] .users-section .user:hover {
    background-color: #26262c;
}
html[dark="true"] .users-section .user .username {
    color:#DEDEE3;
}
html[dark="true"] .users-section .user .description {
    color: #a1a1a1;
}
html[dark="true"] .users-section .user .watchers {
    color: #c0c0c0;
}
html[dark="true"] .tooltip-container {
    background-color: #26262C;
}
html[dark="true"] .tooltiptext {
    color: #fff;
    background-color: #26262C;
}

/* whiteMode Sidebar Styles */

html:not([dark="true"]) #sidebar.max .button-fold-sidebar {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none slice' viewBox='0 0 7 11'%3e%3cpath fill='%23888' d='M5.87 11.01L.01 5.51 5.87.01l1.08 1.01-4.74 4.45L7 9.96 5.87 11z'/%3e%3c/svg%3e");
}
html:not([dark="true"]) #sidebar.min .button-unfold-sidebar {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none slice' viewBox='0 0 7 11'%3e%3cpath fill='%23888' d='M1.13 11.01l5.86-5.5L1.13.01.05 1.02l4.74 4.45L0 9.96 1.13 11z'/%3e%3c/svg%3e");
}
html:not([dark="true"]) #sidebar {
    color: white;
    background-color: #EFEFF1;
}
html:not([dark="true"]) #sidebar .top-section > span {
    color:#0E0E10;
}
html:not([dark="true"]) #sidebar .top-section > span > a {
    color:#0E0E10;
}
html:not([dark="true"]) .users-section .user:hover {
    background-color: #E6E6EA;
}
html:not([dark="true"]) .users-section .user .username {
    color:#1F1F23;
}
html:not([dark="true"]) .users-section .user .description {
    color: #53535F;
}
html:not([dark="true"]) .users-section .user .watchers {
    color: black;
}
html:not([dark="true"]) .tooltip-container {
    background-color: #E6E6EA;
}
html:not([dark="true"]) .tooltiptext {
    color: black;
    background-color: #E6E6EA;
}

    `;

    const mainPageCommonStyles = `

._moreDot_layer button {
    text-align: left;
}

/*----- preview-modal 시작 -----*/

.preview-modal {
    display: none;
    position: fixed;
    z-index: 10000;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
    background-color: rgba(0, 0, 0, 0.9);
    backdrop-filter: blur(5px);
}

.preview-modal-content {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    padding: 0;
    width: 80%;
    max-width: 800px;
    max-height: 800px;
    border-radius: 10px;
    border: 1px solid #cccccc52;
    overflow: hidden;
    box-shadow: 0 4px 30px rgba(0, 0, 0, 0.7);
    pointer-events: auto;
}

.preview-modal .preview-close {
    position: absolute;
    top: 10px;
    right: 15px;
    color: #fff;
    font-size: 30px;
    font-weight: bold;
    cursor: pointer;
    transition: color 0.3s ease;
    z-index: 10;
}

.preview-modal .preview-close:hover,
.preview-modal .preview-close:focus {
    color: #e50914;
}

.preview-modal .thumbnail-container {
    position: relative;
    width: 100%;
    height: 450px;
    background-color: black;
    display: flex;
    justify-content: center;
    align-items: center;
}

.preview-modal .thumbnail-container img {
    max-width: 100%;
    max-height: 100%;
    object-fit: cover;
}

.preview-modal .preview-modal-content video {
    width: clamp(100%, 50vw, 800px);
    height: 449px;
    display: none;
}

.preview-modal .info {
    color: white;
    text-align: left;
    padding: 28px;
    background-color: rgba(0, 0, 0, 0.65);
}

.preview-modal .streamer-name {
    font-size: 50px;
    font-weight: bold;
    letter-spacing: -2px;
}

.preview-modal .video-title {
    font-size: 20px;
    margin: 20px 0 30px 0;
}

.preview-modal .tags {
    display: flex;
    justify-content: left;
    flex-wrap: wrap;
    flex-direction: row;
    margin-left: -3px;
}

.preview-modal .tags a {
    margin: 5px;
    color: white;
    text-decoration: none;
    border: 1px solid #fff;
    padding: 5px 10px;
    border-radius: 5px;
    transition: background-color 0.3s;
}

.preview-modal .tags a:hover {
    background-color: rgba(255, 255, 255, 0.2);
}

.preview-modal .start-button {
    background-color: #2d6bffba;
    color: white;
    padding: 12px 20px;
    border: none;
    border-radius: 5px;
    font-size: 22px;
    cursor: pointer;
    display: inline-block; /* inline-block으로 변경 */
    width: auto; /* 너비는 자동으로 */
    text-align: center;
    text-decoration: none;
    transition: background-color 0.3s;
}

.preview-modal .start-button:hover {
    background-color: #2d6bff8f;
}

/*----- preview-modal 끝 -----*/

.customSidebar .btn_flexible {
    display: none;
}
#sidebar {
    z-index: 1401;
}

body.customSidebar main {
    padding-left: 238px !important;
}

body.customSidebar .catch_webplayer_wrap {
    margin-left: 24px !important;
}

    `;

    const playerCommonStyles = `

.screen_mode .left_navbar,
.fullScreen_mode .left_navbar {
    display: none;
}

.customSidebar .btn_flexible {
    display: none;
}

/* 스크롤바 스타일링 */
html {
    overflow: auto; /* 스크롤 기능 유지 */
}

/* Firefox 전용 스크롤바 감추기 */
html::-webkit-scrollbar {
    display: none; /* 크롬 및 사파리에서 */
}

/* Firefox에서는 아래와 같이 처리 */
html {
    scrollbar-width: none; /* Firefox에서 스크롤바 감추기 */
    -ms-overflow-style: none; /* Internet Explorer 및 Edge */
}

.customSidebar #player,
.customSidebar #webplayer #webplayer_contents #player_area .float_box,
.customSidebar #webplayer #webplayer_contents #player_area
{
    min-width: 180px !important;
}

.customSidebar.screen_mode #webplayer,
.customSidebar.screen_mode #sidebar
{
    transition: all 0.25s ease-in-out !important;
}

@media screen and (max-width: 892px) {
    .screen_mode.bottomChat #webplayer #player .view_ctrl,
    .screen_mode.bottomChat #webplayer .wrapping.side {
        display: block !important;
    }
}

.customSidebar #webplayer_contents {
    width: calc(100vw - ${WEB_PLAYER_SCROLL_LEFT}px) !important;
    gap:0 !important;
    padding: 0 !important;
    margin: 64px 0 0 !important;
    left: ${WEB_PLAYER_SCROLL_LEFT}px !important;
}

.customSidebar.top_hide #webplayer_contents,
.customSidebar.top_hide #sidebar {
    top: 0 !important;
    margin-top: 0 !important;
    min-height: 100vh !important;
}

/* sidebar가 .max 클래스를 가질 때, body에 .screen_mode가 없을 경우 */
body:not(.screen_mode):not(.fullScreen_mode):has(#sidebar.max) #webplayer_contents {
    width: calc(100vw - 240px) !important;
    left: 240px !important;
}

/* sidebar가 .min 클래스를 가질 때, body에 .screen_mode가 없을 경우 */
body:not(.screen_mode):not(.fullScreen_mode):has(#sidebar.min) #webplayer_contents {
    width: calc(100vw - 52px) !important;
    left: 52px !important;
}

.customSidebar.screen_mode #webplayer #webplayer_contents,
.customSidebar.fullScreen_mode #webplayer #webplayer_contents {
    top: 0 !important;
    left: 0 !important;
    width: 100vw;
    height: 100vh !important;
    margin: 0 !important;
}

.customSidebar.screen_mode #sidebar{
    display: none !important;
    top: 0 !important;
}

.customSidebar.screen_mode #sidebar .button-fold-sidebar,
.customSidebar.screen_mode #sidebar .button-unfold-sidebar
{
    display: none !important;
}

.customSidebar.screen_mode.showSidebar #sidebar{
    display: flex !important;
}

.customSidebar.screen_mode #webplayer_contents,
.customSidebar.fullScreen_mode #webplayer_contents{
    width: 100vw !important
}

.customSidebar.screen_mode.showSidebar:has(#sidebar.min) #webplayer_contents {
    width: calc(100vw - 52px) !important
}
.customSidebar.screen_mode.showSidebar:has(#sidebar.max) #webplayer_contents {
    width: calc(100vw - 240px) !important
}

.screen_mode.bottomChat #webplayer #webplayer_contents {
    top: 0 !important;
    margin: 0 !important;
}

.screen_mode.bottomChat #player {
    min-height: auto !important;
}

.screen_mode.bottomChat #webplayer #webplayer_contents {
    position: relative;
    box-sizing: border-box;
    flex: auto;
    display: flex;
    flex-direction: column !important;
    justify-content:flex-start !important;
}

.screen_mode.bottomChat #webplayer #webplayer_contents .wrapping.side {
    width: 100% !important;
    max-height: calc(100vh - (100vw * 9 / 16)) !important;
}

.screen_mode.bottomChat.showSidebar:has(#sidebar.min) #webplayer #webplayer_contents .wrapping.side {
    width: 100% !important;
    max-height: calc(100vh - ((100vw - 52px) * 9 / 16)) !important;
}
.screen_mode.bottomChat.showSidebar:has(#sidebar.max) #webplayer #webplayer_contents .wrapping.side {
    width: 100% !important;
    max-height: calc(100vh - ((100vw - 240px) * 9 / 16)) !important;
}

.screen_mode.bottomChat #webplayer #webplayer_contents .wrapping.side section.box.chatting_box {
    height: 100% !important;
}

.screen_mode.bottomChat #webplayer #webplayer_contents .wrapping.side section.box.chatting_box #chatting_area {
    height: 100% !important;
    min-height: 10vh !important;
}

.screen_mode.bottomChat #webplayer #webplayer_contents #player_area .htmlplayer_wrap,
.screen_mode.bottomChat #webplayer #webplayer_contents #player_area .htmlplayer_content,
.screen_mode.bottomChat #webplayer #webplayer_contents #player_area .float_box,
.screen_mode.bottomChat #webplayer #webplayer_contents #player_area #player {
    height: auto !important;
    max-height: max-content;
}

.customSidebar #player {
    max-height: 100vh !important;
}

`;

    //======================================공용 함수======================================//

    const checkIfTimeover = (timestamp) => {
        const now = Date.now();
        const inputTime = timestamp * 1000; // 초 단위 타임스탬프를 밀리초로 변환

        // 24시간(1일) = 86400000 밀리초
        return (now - inputTime) > 86400000;
    };

    const timeSince = (serverTimeStr) => {
        // 입력 문자열 → ISO 8601 + KST 오프셋으로 변환
        const toKSTDate = (str) => {
            const iso = str.replace(' ', 'T') + '+09:00';
            return new Date(iso);
        };

        const postTime = toKSTDate(serverTimeStr).getTime(); // 게시물 작성 시각 (KST)
        const now = Date.now(); // 현재 시각 (밀리초 기준, UTC)

        const seconds = Math.floor((now - postTime) / 1000);
        const minutes = Math.floor(seconds / 60);
        const hours = Math.floor(minutes / 60);
        const days = Math.floor(hours / 24);

        if (days > 365) return `${Math.floor(days / 365)}년 전`;
        if (days > 30) return `${Math.floor(days / 30)}개월 전`;
        if (days > 0) return `${days}일 전`;
        if (hours > 0) return `${hours}시간 전`;
        if (minutes > 0) return `${minutes}분 전`;

        return `${seconds}초 전`;
    };

    const waitForElement = (elementSelector, callBack, maxAttempts = 200, interval = 200) => {
        let attempts = 0;

        const checkElement = () => {
            const element = document.body.querySelector(elementSelector);

            if (element) {
                callBack(elementSelector, element);
            } else if (attempts < maxAttempts) {
                attempts++;
                setTimeout(checkElement, interval); // 반복 검사
            } else {
                console.warn(`Reached maximum attempts. ${elementSelector} not found.`);
            }
        };

        checkElement(); // 첫 번째 검사 호출
    };

    const waitForElementAsync = (elementSelector, maxAttempts = 200, attemptInterval = 200) => {
        return new Promise((resolve, reject) => {
            let attempts = 0;

            const checkElement = () => {
                const element = document.body.querySelector(elementSelector);

                if (element) {
                    resolve(element); // 요소를 찾으면 resolve
                } else if (attempts < maxAttempts) {
                    attempts += 1; // attempts 증가
                    setTimeout(checkElement, attemptInterval); // 반복 검사
                } else {
                    reject(`Reached maximum attempts. ${elementSelector} not found.`); // 최대 시도 횟수 초과
                }
            };

            checkElement(); // 첫 번째 검사 호출
        });
    };

    const updateElementWithContent = (targetElement, newContent) => {
        // DocumentFragment 생성
        const createFragment = (content) => {
            const fragment = document.createDocumentFragment();
            const tempDiv = document.createElement('div');
            tempDiv.innerHTML = content;

            // tempDiv의 자식 요소를 fragment에 추가
            while (tempDiv.firstChild) {
                fragment.appendChild(tempDiv.firstChild);
            }

            return fragment;
        };

        // 기존 내용을 지우고 DocumentFragment를 적용
        const applyFragment = (fragment) => {
            targetElement.innerHTML = ''; // 기존 내용을 모두 지움
            targetElement.appendChild(fragment); // 새로운 내용 추가
        };

        // DocumentFragment 생성 후 적용
        applyFragment(createFragment(newContent));
    };

    const manageRedDot = () => {
        const RED_DOT_CLASS = 'red-dot';
        const style = document.createElement('style');
        style.textContent = `
        .${RED_DOT_CLASS} {
            position: absolute;
            top: 8px;
            right: 8px;
            width: 4px;
            height: 4px;
            background-color: red;
            border-radius: 50%;
        }
        `;
        document.head.appendChild(style);

        const lastUpdateDate = GM_getValue('lastUpdateDate', 0);
        const btn = document.querySelector('#openModalBtn > button');

        // 빨간 점 추가 함수
        const showRedDot = () => {
            if (!btn || document.querySelector(`#openModalBtn .${RED_DOT_CLASS}`)) return;
            const redDot = document.createElement('div');
            redDot.classList.add(RED_DOT_CLASS);
            btn.parentElement.appendChild(redDot);
        };

        // 빨간 점 제거 함수
        const hideRedDot = () => {
            const redDot = document.querySelector(`#openModalBtn .${RED_DOT_CLASS}`);
            if (redDot) redDot.remove();
        };

        // 날짜를 비교하여 빨간 점 표시
        if (NEW_UPDATE_DATE > lastUpdateDate) {
            showRedDot();
        } else {
            hideRedDot();
        }

        // 버튼 클릭 시 이벤트 핸들러 추가
        btn?.addEventListener('click', () => {
            GM_setValue('lastUpdateDate', NEW_UPDATE_DATE);
            hideRedDot();
        });
    };

    const addNumberSeparator = (number) => {
        number = Number(number);

        // 숫자가 10,000 이상일 때
        if (number >= 10000) {
            const displayNumber = (number / 10000).toFixed(1);
            return displayNumber.endsWith('.0') ?
                displayNumber.slice(0, -2) + '만' : displayNumber + '만';
        }

        return number.toLocaleString();
    };

    const addNumberSeparatorAll = (number) => {
        number = Number(number);

        // 숫자가 10,000 이상일 때
        if (number >= 10000) {
            const displayNumber = (number / 10000).toFixed(1);
            return displayNumber.endsWith('.0') ?
                displayNumber.slice(0, -2) + '만' : displayNumber + '만';
        }
        // 숫자가 1,000 이상일 때
        else if (number >= 1000) {
            const displayNumber = (number / 1000).toFixed(1);
            return displayNumber.endsWith('.0') ?
                displayNumber.slice(0, -2) + '천' : displayNumber + '천';
        }

        // 기본적으로 쉼표 추가
        return number.toLocaleString();
    };

    const getCategoryName = (targetCateNo) => {
        const searchCategory = (categories) => {
            for (const category of categories) {
                if (category.cate_no === targetCateNo) {
                    return category.cate_name;
                }

                if (category.child?.length) {
                    const result = searchCategory(category.child);
                    if (result) return result;
                }
            }
            return targetCateNo === "ADULT_BROAD_CATE" ? "연령제한" : null;
        };

        return searchCategory(savedCategory.CHANNEL.BROAD_CATEGORY);
    };

    const getCategoryNo = (targetCateName) => {
        const searchCategory = (categories) => {
            for (const category of categories) {
                if (category.cate_name === targetCateName) {
                    return category.cate_no;
                }

                if (category.child?.length) {
                    const result = searchCategory(category.child);
                    if (result) return result;
                }
            }
            return targetCateName === "연령제한" ? "ADULT_BROAD_CATE" : null;
        };

        return searchCategory(savedCategory.CHANNEL.BROAD_CATEGORY);
    };

    // 차단 목록을 저장합니다.
    function saveBlockedUsers() {
        GM_setValue('blockedUsers', blockedUsers);
    }

    // 사용자를 차단 목록에 추가합니다.
    function blockUser(userName, userId) {
        // 이미 차단된 사용자인지 확인
        if (!isUserBlocked(userId)) {
            blockedUsers.push({ userName, userId });
            saveBlockedUsers();
            alert(`사용자 ${userName}(${userId})를 차단했습니다.\n차단 해제 메뉴는 템퍼몽키 아이콘을 누르면 있습니다.`);
            registerUnblockMenu({ userName, userId });
        } else {
            alert(`사용자 ${userName}(${userId})는 이미 차단되어 있습니다.`);
        }
    }

    // 함수: 사용자 차단 해제
    function unblockUser(userId) {
        // 차단된 사용자 목록에서 해당 사용자 찾기
        let unblockedUser = blockedUsers.find(user => user.userId === userId);

        // 사용자를 찾았을 때만 차단 해제 및 메뉴 삭제 수행
        if (unblockedUser) {
            // 차단된 사용자 목록에서 해당 사용자 제거
            blockedUsers = blockedUsers.filter(user => user.userId !== userId);

            // 변경된 목록을 저장
            GM_setValue('blockedUsers', blockedUsers);

            alert(`사용자 ${userId}의 차단이 해제되었습니다.`);

            unregisterUnblockMenu(unblockedUser.userName);
        }
    }

    // 사용자가 이미 차단되어 있는지 확인합니다.
    function isUserBlocked(userId) {
        return blockedUsers.some(user => user.userId === userId);
    }

    // 함수: 동적으로 메뉴 등록
    function registerUnblockMenu(user) {
        // GM_registerMenuCommand로 메뉴를 등록하고 메뉴 ID를 기록
        let menuId = GM_registerMenuCommand(`💔 차단 해제 - ${user.userName}`, function() {
            unblockUser(user.userId);
        });

        // 메뉴 ID를 기록
        menuIds[user.userName] = menuId;
    }

    // 함수: 동적으로 메뉴 삭제
    function unregisterUnblockMenu(userName) {
        // userName을 기반으로 저장된 메뉴 ID를 가져와서 삭제
        let menuId = menuIds[userName];
        if (menuId) {
            GM_unregisterMenuCommand(menuId);
            delete menuIds[userName]; // 삭제된 메뉴 ID를 객체에서도 제거
        }
    }

    // 카테고리 목록을 저장합니다.
    function saveBlockedCategories() {
        GM_setValue('blockedCategories', blockedCategories);
    }

    // 카테고리를 차단 목록에 추가합니다.
    function blockCategory(categoryName, categoryId) {
        // 이미 차단된 카테고리인지 확인
        if (!isCategoryBlocked(categoryId)) {
            blockedCategories.push({ categoryName, categoryId });
            saveBlockedCategories();
            alert(`카테고리 ${categoryName}(${categoryId})를 차단했습니다.\n차단 해제 메뉴는 템퍼몽키 아이콘을 누르면 있습니다.`);
            registerCategoryUnblockMenu({ categoryName, categoryId });
        } else {
            alert(`카테고리 ${categoryName}(${categoryId})는 이미 차단되어 있습니다.`);
        }
    }

    // 함수: 카테고리 차단 해제
    function unblockCategory(categoryId) {
        // 차단된 카테고리 목록에서 해당 카테고리 찾기
        let unblockedCategory = blockedCategories.find(category => category.categoryId === categoryId);

        // 카테고리를 찾았을 때만 차단 해제 및 메뉴 삭제 수행
        if (unblockedCategory) {
            // 차단된 카테고리 목록에서 해당 카테고리 제거
            blockedCategories = blockedCategories.filter(category => category.categoryId !== categoryId);

            // 변경된 목록을 저장
            GM_setValue('blockedCategories', blockedCategories);

            alert(`카테고리 ${categoryId}의 차단이 해제되었습니다.`);

            unregisterCategoryUnblockMenu(unblockedCategory.categoryName);
        }
    }

    // 카테고리가 이미 차단되어 있는지 확인합니다.
    function isCategoryBlocked(categoryId) {
        return blockedCategories.some(category => category.categoryId === categoryId);
    }

    // 함수: 동적으로 카테고리 메뉴 등록
    function registerCategoryUnblockMenu(category) {
        // GM_registerMenuCommand로 카테고리 메뉴를 등록하고 메뉴 ID를 기록
        let menuId = GM_registerMenuCommand(`💔 카테고리 차단 해제 - ${category.categoryName}`, function() {
            unblockCategory(category.categoryId);
        });

        // 메뉴 ID를 기록
        categoryMenuIds[category.categoryName] = menuId;
    }

    // 함수: 동적으로 카테고리 메뉴 삭제
    function unregisterCategoryUnblockMenu(categoryName) {
        // categoryName을 기반으로 저장된 메뉴 ID를 가져와서 삭제
        let menuId = categoryMenuIds[categoryName];
        if (menuId) {
            GM_unregisterMenuCommand(menuId);
            delete categoryMenuIds[categoryName]; // 삭제된 메뉴 ID를 객체에서도 제거
        }
    }

    // 단어 목록을 저장합니다.
    function saveBlockedWords() {
        GM_setValue('blockedWords', blockedWords);
    }

    // 단어를 차단 목록에 추가합니다.
    function blockWord(word) {
        // 단어의 양쪽 공백 제거
        word = word.trim();

        // 단어가 두 글자 이상인지 확인
        if (word.length < 2) {
            alert("단어는 두 글자 이상이어야 합니다.");
            return;
        }

        // 이미 차단된 단어인지 확인
        if (!isWordBlocked(word)) {
            blockedWords.push(word);
            saveBlockedWords();
            alert(`단어 "${word}"를 차단했습니다.`);
            registerWordUnblockMenu(word);
        } else {
            alert(`단어 "${word}"는 이미 차단되어 있습니다.`);
        }
    }

    // 함수: 단어 차단 해제
    function unblockWord(word) {
        // 차단된 단어 목록에서 해당 단어 찾기
        let unblockedWord = blockedWords.find(blockedWord => blockedWord === word);

        // 단어를 찾았을 때만 차단 해제 및 메뉴 삭제 수행
        if (unblockedWord) {
            // 차단된 단어 목록에서 해당 단어 제거
            blockedWords = blockedWords.filter(blockedWord => blockedWord !== word);

            // 변경된 목록을 저장
            saveBlockedWords();

            alert(`단어 "${word}"의 차단이 해제되었습니다.`);
            unregisterWordUnblockMenu(word);
        }
    }

    // 단어가 이미 차단되어 있는지 확인합니다.
    function isWordBlocked(word) {
        const lowerCaseWord = word.toLowerCase();
        return blockedWords.map(word => word.toLowerCase()).includes(lowerCaseWord);
    }

    // 함수: 동적으로 단어 차단 해제 메뉴 등록
    function registerWordUnblockMenu(word) {
        // GM_registerMenuCommand로 단어 차단 해제 메뉴를 등록하고 메뉴 ID를 기록
        let menuId = GM_registerMenuCommand(`💔 단어 차단 해제 - ${word}`, function() {
            unblockWord(word);
        });

        // 메뉴 ID를 기록
        wordMenuIds[word] = menuId;
    }

    // 함수: 동적으로 단어 차단 해제 메뉴 삭제
    function unregisterWordUnblockMenu(word) {
        // word를 기반으로 저장된 메뉴 ID를 가져와서 삭제
        let menuId = wordMenuIds[word];
        if (menuId) {
            GM_unregisterMenuCommand(menuId);
            delete wordMenuIds[word]; // 삭제된 메뉴 ID를 객체에서도 제거
        }
    }

    function registerMenuBlockingWord() {
        // GM 메뉴에 단어 차단 등록 메뉴를 추가합니다.
        GM_registerMenuCommand('단어 등록 | 방제에 포함시 차단', function() {
            // 사용자에게 차단할 단어 입력을 요청
            let word = prompt('차단할 단어 (2자 이상): ');

            // 입력한 단어가 있을 때만 처리
            if (word) {
                blockWord(word);
            }
        });
    }

    const desc_order = (selector) => {
        const container = document.body.querySelector(selector);
        const userElements = container.children;

        const categories = [[], [], [], [], [], []];

        for (let i = 0; i < userElements.length; i++) {
            const user = userElements[i];
            const isPin = user.getAttribute('is_pin') === 'Y';
            const hasBroadThumbnail = user.hasAttribute('broad_thumbnail');
            const isMobilePush = user.getAttribute('is_mobile_push') === 'Y';
            const isOffline = user.hasAttribute('is_offline');
            const broad_cate_no = user.getAttribute('broad_cate_no');

            const isBlocked = blockedCategories.some(b => b.categoryId === broad_cate_no);

            if (isPin && hasBroadThumbnail) {
                categories[0].push(user); // 1. 고정 + 생방
            } else if (isPin) {
                categories[1].push(user); // 2. 고정 + 오프라인
            } else if (isMobilePush && !isOffline) {
                categories[2].push(user); // 3. 알림 켜짐 + 생방
            } else if (isBlocked && isBlockedCategorySortingEnabled) {
                categories[4].push(user); // 5. 차단된 카테고리 (고정 제외, 알림 켜짐 제외)
            } else if (!isMobilePush && !isOffline) {
                categories[3].push(user); // 4. 알림 꺼짐 + 생방
            } else {
                categories[5].push(user); // 6. 그 외
            }
        }

        categories.forEach((category, index) => {
            if (index === 5 || selector !== '.users-section.follow') { // 방송국 글이거나 즐찾 채널이 아닌 경우 시청자 많은 순
                category.sort(compareWatchers);
            } else {
                category.sort(isRandomSortEnabled ? stableRandomOrder : compareWatchers)
            }
        });

        container.innerHTML = '';
        const fragment = document.createDocumentFragment();
        categories.forEach(category => {
            category.forEach(user => fragment.appendChild(user));
        });
        container.appendChild(fragment);
    };

    const compareWatchers = (a, b) => {
        // Get watchers data only once for each element
        const watchersA = a.dataset.watchers ? +a.dataset.watchers : 0; // Use dataset for better performance
        const watchersB = b.dataset.watchers ? +b.dataset.watchers : 0; // Use dataset for better performance
        return watchersB - watchersA; // Sort by watchers
    }

    const stableRandomOrder = (() => {
        // 한 번에 여러 개를 정렬할 때 일관된 랜덤성을 유지하려면, 미리 섞어주는 방식이 좋습니다.
        // 이 함수는 내부적으로 shuffle된 index 맵을 사용해서 안정적인 무작위 정렬을 구현합니다.

        let randomMap = new WeakMap();

        return (a, b) => {
            if (!randomMap.has(a)) randomMap.set(a, Math.random());
            if (!randomMap.has(b)) randomMap.set(b, Math.random());
            return randomMap.get(a) - randomMap.get(b);
        };
    })();

    const makeTopNavbarAndSidebar = (page) => {
        // .left_navbar를 찾거나 생성
        let leftNavbar = document.body.querySelector('.left_navbar');
        if (!leftNavbar) {
            leftNavbar = document.createElement('div');
            leftNavbar.className = 'left_navbar';

            // 페이지의 적절한 위치에 추가
            waitForElement('#serviceHeader', function (elementSelector, element) {
                element.prepend(leftNavbar);
            });
        }

        const buttonData = [
            { href: 'https://www.sooplive.co.kr/live/all', text: 'LIVE', onClickTarget: '#live > a' },
            { href: 'https://www.sooplive.co.kr/my/favorite', text: 'MY', onClickTarget: '#my > a' },
            { href: 'https://www.sooplive.co.kr/directory/category', text: '탐색', onClickTarget: '#cate > a' },
            { href: 'https://vod.sooplive.co.kr/player/catch', text: '캐치', onClickTarget: '#catch > a' }
        ];

        // 버튼을 미리 만들어 DocumentFragment에 추가
        const buttonFragment = document.createDocumentFragment();

        buttonData.reverse().forEach(data => {
            const newButton = document.createElement('a');
            newButton.innerHTML = `<button type="button" class="left_nav_button">${data.text}</button>`;

            const isTargetUrl = CURRENT_URL.startsWith("https://www.sooplive.co.kr");

            // 이벤트 리스너 함수 정의
            const triggerClick = (event) => {
                event.preventDefault();
                const targetElement = isTargetUrl && data.onClickTarget ? document.querySelector(data.onClickTarget) : null;
                if (targetElement) {
                    targetElement.click(); // 타겟 요소 클릭
                } else {
                    console.warn("타겟 요소를 찾을 수 없음:", data.onClickTarget);
                }
            };

            // MutationObserver 설정: 타겟 요소가 로드될 때까지 기다림
            if (isTargetUrl && data.onClickTarget) {
                const observer = new MutationObserver((mutations, observer) => {
                    const targetElement = document.querySelector(data.onClickTarget);
                    if (targetElement) {
                        observer.disconnect(); // 요소가 확인되면 Observer 중지
                        newButton.addEventListener('click', triggerClick);
                    }
                });
                observer.observe(document.body, { childList: true, subtree: true });
            } else {
                // 기본 링크 설정
                newButton.href = data.href;
                newButton.target = isOpenNewtabEnabled ? "_blank" : "_self";
            }

            buttonFragment.appendChild(newButton);
        });

        leftNavbar.appendChild(buttonFragment); // 한 번에 추가

        const tooltipContainer = document.createElement('div');
        tooltipContainer.classList.add('tooltip-container');

        const sidebarClass = isSidebarMinimized ? "min" : "max";

        if (page === "main") {
            const newHtml = `
            <div id="sidebar" class="max"></div>
            `;
            const serviceLnbElement = document.getElementById('soop-gnb');
            if (serviceLnbElement) {
                serviceLnbElement.insertAdjacentHTML('afterend', newHtml);
            }
            document.body.appendChild(tooltipContainer);
        }

        if (page === "player") {
            const sidebarHtml = `
            <div id="sidebar" class="${sidebarClass}"></div>
            `;
            document.body.insertAdjacentHTML('beforeend', sidebarHtml);
            document.body.appendChild(tooltipContainer);
        }
    }

    const createUserElementChzzk = (channel, is_mobile_push) => {
        const {
            liveTitle: liveTitle,
            liveImageUrl: liveImageUrl,
            concurrentUserCount: concurrentUserCount,
            openDate: openDate,
            liveCategoryValue: liveCategoryValue,
            liveCategory: liveCategory,
            channel: channelInfo,
            liveInfo: liveInfo
        } = channel;

        const userId = channelInfo.channelId;
        const playerLink = `https://chzzk.naver.com/live/${channelInfo.channelId}`;
        const broadThumbnail = liveImageUrl ? liveImageUrl.split('{type}').join('360')
        : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQIHWNgAAAAAgABz8g15QAAAABJRU5ErkJggg==";
        const profileImg = channelInfo?.channelImageUrl;
        const channelPage = 'https://chzzk.naver.com/'+userId;
        const channelName = channelInfo?.channelName;
        const emptyimage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQIHWNgAAAAAgABz8g15QAAAABJRU5ErkJggg==';

        const userElement = document.createElement('a');
        userElement.classList.add('user');
        if (isSmallUserLayoutEnabled) userElement.classList.add('small-user-layout');

        userElement.setAttribute('href', playerLink);
        if (isOpenNewtabEnabled) {
            userElement.setAttribute('target', '_blank');
        } else {
            userElement.setAttribute('target', '_self');
        }

        userElement.setAttribute('data-watchers', concurrentUserCount ?? liveInfo.concurrentUserCount);
        userElement.setAttribute('broad_thumbnail', broadThumbnail);
        userElement.setAttribute('tooltip', liveTitle ?? liveInfo.liveTitle);
        userElement.setAttribute('user_id', userId);
        userElement.setAttribute('broad_start', openDate ?? 'NotAvailable');

        userElement.setAttribute('is_mobile_push', is_mobile_push === "Y" ? 'Y' : 'N');
        userElement.setAttribute('is_pin', 'N');

        const profilePicture = document.createElement('img');
        profilePicture.src = profileImg || emptyimage;
        profilePicture.setAttribute('loading', 'lazy');

        const profileClickHandler =
              `
        event.preventDefault();
        event.stopPropagation();
        if (document.getElementById('sidebar').offsetWidth === 52) {
            if(event.ctrlKey) {
                window.open('${playerLink}', '_blank');
                return;
            }
            location.href = '${playerLink}';
        } else {
            window.open('${channelPage}', '_blank');
        }
        `;
        // 프로필 클릭 & 새 탭 열기: 최소화 시 생방송, 최대화 시 방송국
        const profileClickHandlerForNewtab = `
        event.preventDefault();
        event.stopPropagation();
        if (document.getElementById('sidebar').offsetWidth === 52) {
            window.open('${playerLink}', '_blank');
        } else {
            window.open('${channelPage}', '_blank');
        }
        `

        profilePicture.setAttribute('onclick', isOpenNewtabEnabled === 1 ?
                                    profileClickHandlerForNewtab :
                                    profileClickHandler
                                   );

        profilePicture.setAttribute('onmousedown', `
        if (event.button === 1) {
            event.preventDefault();
            event.stopPropagation();
            if (document.getElementById('sidebar').offsetWidth !== 52) {
                window.open('${channelPage}', '_blank');
            }
        }
        `);

        profilePicture.classList.add('profile-picture');

        const username = document.createElement('span');
        username.classList.add('username');
        username.textContent = (is_mobile_push === "Y") ? `🖈${channelName}` : channelName;
        username.setAttribute('title', is_mobile_push === "Y" ? '고정됨(알림 받기 켜짐)' : '');
        username.title = username.textContent;

        const description = document.createElement('span');
        description.classList.add('description');
        description.textContent = liveCategoryValue ?? liveInfo.liveCategoryValue;
        description.title = description.textContent;

        userElement.setAttribute('broad_cate_no', liveCategory ?? '');

        const watchers = document.createElement('span');
        watchers.classList.add('watchers');

        const dot = document.createElement('span');
        dot.classList.add('dot', 'greendot');
        dot.setAttribute('role', 'img');
        dot.textContent = '●';

        const userCount = addNumberSeparator(concurrentUserCount ?? liveInfo.concurrentUserCount);
        const countText = document.createTextNode(userCount);

        watchers.append(dot, countText);

        userElement.append(profilePicture, username, description, watchers);

        return userElement;
    }

    const createUserElement = (channel, is_mobile_push, is_pin) => {
        const {
            user_id: userId,
            broad_no: broadNo,
            total_view_cnt: totalViewCnt,
            broad_title: broadTitle,
            user_nick: userNick,
            broad_start: broadStart,
            broad_cate_no: catNo,
            category_name: categoryName,
            subscription_only: subscriptionOnly
        } = channel;

        const isSubOnly = Number(subscriptionOnly || 0) > 0;
        const playerLink = `https://play.sooplive.co.kr/${userId}/${broadNo}`;
        const broadThumbnail = `https://liveimg.sooplive.co.kr/m/${broadNo}`;

        const userElement = document.createElement('a');
        userElement.classList.add('user');
        if (isSmallUserLayoutEnabled) userElement.classList.add('small-user-layout');

        userElement.setAttribute('href', playerLink);
        if (isOpenNewtabEnabled) {
            userElement.setAttribute('target', '_blank');
        } else if (isSendLoadBroadEnabled) {
            userElement.setAttribute('onclick', `
            if(event.ctrlKey || (window.location.href.indexOf('play.sooplive.co.kr') === -1) ) return;
            event.preventDefault();
            event.stopPropagation();
            if (document.body.querySelector('div.loading') && getComputedStyle(document.body.querySelector('div.loading')).display === 'none') {
                liveView.playerController.sendLoadBroad('${userId}', ${broadNo});
                typeof resetChatData === 'function' && resetChatData();
            } else {
                location.href = '${playerLink}';
            }
            `);
        } else {
            userElement.setAttribute('target', '_self');
        }

        userElement.setAttribute('data-watchers', totalViewCnt);
        userElement.setAttribute('broad_thumbnail', broadThumbnail);
        userElement.setAttribute('tooltip', broadTitle);
        userElement.setAttribute('user_id', userId);
        userElement.setAttribute('broad_start', broadStart);

        if (isOpenExternalPlayerFromSidebarEnabled) {
            userElement.setAttribute('oncontextmenu', `
            event.preventDefault();
            event.stopPropagation();
            (async () => {
                const aid = await getBroadAid2('${userId}', ${broadNo});
                if (aid) openHlsStream('${userNick}', 'https://live-global-cdn-v02.sooplive.co.kr/live-stm-12/auth_playlist.m3u8?aid=' + aid);
            })();
        `);
        }

        if (is_mobile_push) {
            userElement.setAttribute('is_mobile_push', is_mobile_push);
            userElement.setAttribute('is_pin', is_pin ? 'Y' : 'N');
        }

        const profilePicture = document.createElement('img');
        const pp_webp = `https://stimg.sooplive.co.kr/LOGO/${userId.slice(0, 2)}/${userId}/m/${userId}.webp`;
        const pp_jpg = `https://profile.img.sooplive.co.kr/LOGO/${userId.slice(0, 2)}/${userId}/m/${userId}.jpg`;
        profilePicture.src = pp_webp;
        profilePicture.setAttribute('loading', 'lazy');
        profilePicture.setAttribute('onerror', `this.onerror=null; this.src='${pp_jpg}'`);

        const profileClickHandler = isSendLoadBroadEnabled ?
              // 프로필 클릭 & 현재 탭 & 빠른 전환
              `
        event.preventDefault();
        event.stopPropagation();
        if (document.getElementById('sidebar').offsetWidth === 52) {
            if (event.ctrlKey) {
                window.open('${playerLink}', '_blank');
                return;
            }
            if (document.body.querySelector('div.loading') && getComputedStyle(document.body.querySelector('div.loading')).display === 'none') {
                liveView.playerController.sendLoadBroad('${userId}', ${broadNo});
                typeof resetChatData === 'function' && resetChatData();
            } else {
                location.href = '${playerLink}';
            }
        } else {
            window.open('https://ch.sooplive.co.kr/${userId}', '_blank');
        }
        `

        :
        // 프로필 클릭 & 현재 탭 & 새로고침
        `
        event.preventDefault();
        event.stopPropagation();
        if (document.getElementById('sidebar').offsetWidth === 52) {
            if(event.ctrlKey) {
                window.open('${playerLink}', '_blank');
                return;
            }
            location.href = '${playerLink}';
        } else {
            window.open('https://ch.sooplive.co.kr/${userId}', '_blank');
        }
        `;
        // 프로필 클릭 & 새 탭 열기: 최소화 시 생방송, 최대화 시 방송국
        const profileClickHandlerForNewtab = `
        event.preventDefault();
        event.stopPropagation();
        if (document.getElementById('sidebar').offsetWidth === 52) {
            window.open('${playerLink}', '_blank');
        } else {
            window.open('https://ch.sooplive.co.kr/${userId}', '_blank');
        }
        `

        profilePicture.setAttribute('onclick', isOpenNewtabEnabled === 1 ?
                                    profileClickHandlerForNewtab :
                                    profileClickHandler
                                   );

        profilePicture.setAttribute('onmousedown', `
        if (event.button === 1) {
            event.preventDefault();
            event.stopPropagation();
            if (document.getElementById('sidebar').offsetWidth !== 52) {
                window.open('https://ch.sooplive.co.kr/${userId}', '_blank');
            }
        }
        `);

        profilePicture.classList.add('profile-picture');

        const username = document.createElement('span');
        username.classList.add('username');
        username.textContent = (is_pin || is_mobile_push === "Y") ? `🖈${userNick}` : userNick;
        username.setAttribute('title', is_pin ? '고정됨(상단 고정 켜짐)' : is_mobile_push === "Y" ? '고정됨(알림 받기 켜짐)' : '');
        username.title = username.textContent;

        const description = document.createElement('span');
        description.classList.add('description');
        description.textContent = categoryName || getCategoryName(catNo);
        description.title = description.textContent;

        userElement.setAttribute('broad_cate_no', catNo);

        const watchers = document.createElement('span');
        watchers.classList.add('watchers');

        // <span class="dot" role="img">●</span>
        const dot = document.createElement('span');
        dot.classList.add('dot');
        dot.setAttribute('role', 'img');
        dot.textContent = isSubOnly ? '★' : '●';
        dot.title = isSubOnly ? '구독자 전용' : '';

        const viewCountText = document.createTextNode(addNumberSeparator(totalViewCnt));

        watchers.append(dot, viewCountText);

        userElement.append(profilePicture, username, description, watchers);

        return userElement;
    };

    const createUserElement_vod = (channel) => {
        const {
            user_id: userId,
            title_no: broadNo,
            view_cnt: totalViewCnt,
            title: broadTitle,
            user_nick: userNick,
            vod_duration: vodDuration,
            reg_date: regDate,
            thumbnail,
        } = channel;

        const playerLink = `https://vod.sooplive.co.kr/player/${broadNo}`;
        const broadThumbnail = thumbnail.replace("http://", "https://");

        const userElement = document.createElement('a');
        userElement.classList.add('user');
        if (isSmallUserLayoutEnabled) userElement.classList.add('small-user-layout');

        userElement.setAttribute('href', playerLink);
        if (isOpenNewtabEnabled) {
            userElement.setAttribute('target', '_blank');
        }

        userElement.setAttribute('data-watchers', totalViewCnt);
        userElement.setAttribute('broad_thumbnail', broadThumbnail);
        userElement.setAttribute('tooltip', broadTitle);
        userElement.setAttribute('user_id', userId);
        userElement.setAttribute('vod_duration', vodDuration);

        const profilePicture = document.createElement('img');
        const pp_webp = `https://stimg.sooplive.co.kr/LOGO/${userId.slice(0, 2)}/${userId}/m/${userId}.webp`;
        const pp_jpg = `https://profile.img.sooplive.co.kr/LOGO/${userId.slice(0, 2)}/${userId}/m/${userId}.jpg`;

        profilePicture.src = pp_webp;
        profilePicture.setAttribute('loading', 'lazy');
        profilePicture.setAttribute('onerror', `this.onerror=null; this.src='${pp_jpg}'`);

        const profileClickHandler = `
        event.preventDefault();
        event.stopPropagation();
        const sidebarWidth = document.getElementById('sidebar').offsetWidth;
        if (sidebarWidth === 52) {
            if (event.ctrlKey) {
                window.open('${playerLink}', '_blank');
                return;
            }
            location.href = '${playerLink}';
        } else {
            window.open('https://ch.sooplive.co.kr/${userId}', '_blank');
        }
        `;
        profilePicture.setAttribute('onclick', isOpenNewtabEnabled ?
                                    `event.preventDefault(); event.stopPropagation(); window.open('${playerLink}', '_blank');` :
                                    profileClickHandler
                                   );

        profilePicture.setAttribute('onmousedown', `
        if (event.button === 1) {
            if (document.getElementById('sidebar').offsetWidth !== 52) {
                event.preventDefault();
                event.stopPropagation();
                window.open('https://ch.sooplive.co.kr/${userId}', '_blank');
            }
        }
        `);

        profilePicture.classList.add('profile-picture', 'profile-grayscale');

        const username = document.createElement('span');
        username.classList.add('username');
        username.textContent = userNick;
        username.title = username.textContent;

        const description = document.createElement('span');
        description.classList.add('description');
        description.textContent = vodDuration;
        description.title = vodDuration;

        const watchers = document.createElement('span');
        watchers.classList.add('watchers');
        watchers.textContent = timeSince(regDate);

        userElement.append(profilePicture, username, description, watchers);

        return userElement;
    };

    const createUserElement_offline = (channel, isFeeditem) => {
        const {
            user_id: userId,
            total_view_cnt: totalViewCnt,
            user_nick: userNick,
            is_mobile_push: isMobilePush,
            is_pin: isPin,
            reg_date_human: reg_date_human,
        } = channel;

        const playerLink = isFeeditem ? isFeeditem.url : `https://ch.sooplive.co.kr/${userId}`;
        const isOffline = "Y";
        const feedTimestamp = isFeeditem ? isFeeditem.reg_timestamp : false;
        const feedRegDate = isFeeditem ? isFeeditem.reg_date : false;

        const userElement = document.createElement('a');
        userElement.classList.add('user');
        userElement.classList.add('user-offline');

        if (isSmallUserLayoutEnabled) userElement.classList.add('small-user-layout');

        userElement.setAttribute('href', playerLink);
        userElement.setAttribute('target', '_blank');
        userElement.setAttribute('broad_start', feedRegDate || '');
        userElement.setAttribute('data-watchers', isFeeditem ? feedTimestamp : totalViewCnt);
        userElement.setAttribute('user_id', userId);

        if (isFeeditem) {
            if (isFeeditem.photo_cnt) {
                userElement.setAttribute('broad_thumbnail', `https:${isFeeditem.photos[0].url}`);
            } else {
                userElement.setAttribute('data-tooltip-listener', 'false');
            }
            userElement.setAttribute('tooltip', isFeeditem.title_name);
        } else {
            userElement.setAttribute('data-tooltip-listener', 'false');
        }

        if (isMobilePush) {
            userElement.setAttribute('is_mobile_push', isMobilePush);
            userElement.setAttribute('is_pin', isPin ? 'Y' : 'N');
        }

        userElement.setAttribute('is_offline', isOffline);

        const profilePicture = document.createElement('img');
        const pp_webp = `https://stimg.sooplive.co.kr/LOGO/${userId.slice(0, 2)}/${userId}/m/${userId}.webp`;
        const pp_jpg = `https://profile.img.sooplive.co.kr/LOGO/${userId.slice(0, 2)}/${userId}/m/${userId}.jpg`;

        profilePicture.src = pp_webp;
        profilePicture.setAttribute('loading', 'lazy');
        profilePicture.setAttribute('onerror', `this.onerror=null; this.src='${pp_jpg}'`);

        const profileClickHandler = `
        event.preventDefault();
        event.stopPropagation();
        const sidebarWidth = document.getElementById('sidebar').offsetWidth;
        if (sidebarWidth === 52) {
            window.open('${playerLink}', '_blank');
        } else {
            window.open('https://ch.sooplive.co.kr/${userId}', '_blank');
        }
        `;
        profilePicture.setAttribute('onclick', profileClickHandler);
        profilePicture.classList.add('profile-picture', 'profile-grayscale');

        const username = document.createElement('span');
        username.classList.add('username');
        username.textContent = isPin ? `🖈${userNick}` : userNick;
        username.title = username.textContent;
        if (isPin) username.setAttribute('title', '고정됨(상단 고정 켜짐)');

        const description = document.createElement('span');
        description.classList.add('description');
        description.textContent = isFeeditem ? isFeeditem.title_name : '';
        description.title = isFeeditem ? isFeeditem.title_name : '';

        const watchers = document.createElement('span');
        watchers.classList.add('watchers');

        if (isFeeditem) {
            // 피드 아이템이 있으면 시간 표시
            watchers.textContent = isFeeditem.reg_date_human;
        } else {
            // 오프라인 상태일 경우 ● + "오프라인"
            const dot = document.createElement('span');
            dot.classList.add('dot', 'profile-grayscale');
            dot.setAttribute('role', 'img');
            dot.textContent = '●';

            const offlineText = document.createTextNode('오프라인');

            watchers.append(dot, offlineText);
        }

        userElement.append(profilePicture, username, description, watchers);

        return userElement;
    };

    const isUserInFollowSection = (userid) => {
        const followUsers = document.body.querySelectorAll('.users-section.follow .user');

        // 유저가 포함되어 있는지 확인
        return Array.from(followUsers).some(user => user.getAttribute('user_id') === userid);
    }

    const insertFoldButton = () => {
        const foldButton = `
        <div class="button-fold-sidebar" role="button"></div>
        <div class="button-unfold-sidebar" role="button"></div>
        `;

        const webplayer_scroll = document.getElementById('webplayer_scroll') || document.getElementById('list-container');
        const serviceLnbElement = document.getElementById('sidebar');

        if (serviceLnbElement) {
            serviceLnbElement.insertAdjacentHTML('beforeend', foldButton);

            // 클릭 이벤트 리스너를 정의
            const toggleSidebar = () => {
                isSidebarMinimized = !isSidebarMinimized;

                // max 클래스가 있으면 제거하고 min 클래스 추가
                if (serviceLnbElement.classList.toggle('max')) {
                    serviceLnbElement.classList.remove('min');
                    webplayer_scroll.style.left = '240px';
                } else {
                    serviceLnbElement.classList.remove('max');
                    serviceLnbElement.classList.add('min');
                    webplayer_scroll.style.left = '52px';
                }

                // isSidebarMinimized 값을 저장
                GM_setValue("isSidebarMinimized", isSidebarMinimized ? 1 : 0);
            };

            // 버튼에 클릭 이벤트 리스너 추가
            const buttons = serviceLnbElement.querySelectorAll('.button-fold-sidebar, .button-unfold-sidebar');
            for (const button of buttons) {
                button.addEventListener('click', toggleSidebar);
            }
        }
    };

    const fetchBroadList = async (url, timeout) => {
        const CACHE_EXPIRY_MS = 45 * 1000; // 45초

        const cacheKey = `fetchCache_${encodeURIComponent(url)}`;

        // 1. LocalStorage 확인
        const cached = localStorage.getItem(cacheKey);
        if (cached) {
            try {
                const { timestamp, data } = JSON.parse(cached);
                if (Date.now() - timestamp < CACHE_EXPIRY_MS) {
                    return data;
                }
            } catch (e) {
                console.warn(url, 'Cache parse error in LocalStorage, ignoring.');
            }
        }

        // 2. GM 저장소 확인
        const gmCached = await GM_getValue(cacheKey, null);
        if (gmCached) {
            try {
                const { timestamp, data } = JSON.parse(gmCached);
                if (Date.now() - timestamp < CACHE_EXPIRY_MS) {
                    // LocalStorage에도 저장해두기 (빠른 재사용을 위해)
                    localStorage.setItem(cacheKey, gmCached);
                    return data;
                }
            } catch (e) {
                console.warn(url, 'Cache parse error in GM storage, ignoring.');
            }
        }

        // 3. 요청 수행
        return new Promise((resolve) => {
            let timeoutId;

            if (timeout) {
                timeoutId = setTimeout(() => {
                    console.error(url, `Request timed out after ${timeout} ms`);
                    resolve([]);
                }, timeout);
            }

            GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                headers: {
                    'Content-Type': 'application/json'
                },
                onload: async (response) => {
                    if (timeoutId) clearTimeout(timeoutId);

                    try {
                        if (response.status >= 200 && response.status < 300) {
                            const jsonResponse = JSON.parse(response.responseText);

                            // 에러 응답 처리
                            if (jsonResponse?.RESULT === -1 || (jsonResponse?.code && jsonResponse.code < 0)) {
                                console.error(url, `API Error (Login Required or other): ${jsonResponse.MSG || jsonResponse.message}`);
                                localStorage.removeItem(cacheKey);
                                await GM_setValue(cacheKey, ""); // GM 저장소도 삭제 (빈 문자열)
                                resolve([]);
                            } else {
                                const cacheData = JSON.stringify({
                                    timestamp: Date.now(),
                                    data: jsonResponse
                                });

                                // LocalStorage + GM 저장소에 저장
                                localStorage.setItem(cacheKey, cacheData);
                                await GM_setValue(cacheKey, cacheData);

                                resolve(jsonResponse);
                            }
                        } else if (response.status === 401) {
                            console.error(url, "Unauthorized: 401 error - possibly invalid credentials");
                            resolve([]);
                        } else {
                            console.error(url, `Error: ${response.status}`);
                            resolve([]);
                        }
                    } catch (error) {
                        console.error(url, "Parsing error: ", error);
                        resolve([]);
                    }
                },
                onerror: (error) => {
                    if (timeoutId) clearTimeout(timeoutId);
                    console.error(url, "Request error: " + error.message);
                    resolve([]);
                }
            });
        });
    };

    const insertTopChannels = async (update) => {
        const topIcon = IS_DARK_MODE ?
              `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAExUlEQVR4nO1ZaYgcVRB+HlkVjyheibckxgNRQZSomB8RFC8MElGiP0T9LSgiASELgkpERRGPxeBqghhfV3XPrpN+1bsx8yMRVOKJENR4H9FVo+sRjbKulDPtfN07Mz1s98xOYD94sDtd7+v63lGv6rUxs5hFb8L35Xxm97k2/dvsSSCKlhDJOLNMVpv7zfPCS8yeAM+LLmWWXXXnq41Iftdnppfhee5idbTutPtaG4ogii4yvQhmOZtIdsKo77A2PINo5BQi+RJEjAdBdK7pJRCVTyRy38HIf2GtLIyf699JEe5ba0dPML0AaysHMcu74NwPRHLaVDtZyCxjYPf22rXRgWYm0d/fvzeR+LjGmcPFzew9Ty7EDU4krBxmpkAkq2BE/2EOb2ijz4pUdFplZgLM4XnM7i+I9Q+031dWg/C/fd9dYLqJUql0MJFsB+c3WWv3abe/2jJLBWZhu+4l0y0QuWfB+Z+nE1GYw+NSYXeN6QZ8X65KreEV0+VilhuTXOEVppMYHKzsz+w+hvX7Yl5OZrceZvOjMAz3M50CkdwDI7/T2vDIvJzM0VG6DIF3pekEgqByaHLNutuL4maO7gDeH60dnWuKBpH0w0s+GRjYOqcobmttH7N81rGzQddlMtcJb812yp2qa5rZbfP9DYuy7JndbclcyfYVJsDzwpuSWWZr8kqlsi+RvAF93sqaMWttnzpe75N9qrcNIjcC03tvtr2snFrQuLvb6Hcf2IeFOK8bill2x8S+Hy7IOqCY3a9pAfqbtS8f26qv729YBAP1p574uQUwR8uB9L1se3kJ7D/QBiLWt9H//fpguWsLEFBPvJjlwda20VKwndAkTUvIaqYa/x4tbc3hHppOgthKwEZ4+fJmdpOTk3vhxtV8CZx6Dje02jbjIZLrQUCUWwBmnRoam9npdOM1CrObDwLmJ4t9WdacZ+R0TC1yC9CTsb4mRw9vZkfk3oFRXj2VRx4GAW824xkeHj4Cy9PcArTgiAk1vje2iZaA87sa5UjWlucly8nG1ysDA1vnYLGTW0B1OcRLqHGOghklkTzTggv2gnuhkU25XD4MuMZzC0hWXtGZ6edaSWnMjm1a3flowQ8C/mh0K+F54Vlg82FuAcxuGKb05vRz33fXYNzP4sNzgSi6eqoAuQX4SgUIkLuA0G/g0NOw/u/PFuAegRF+sgHfEAzYnbkF6PEeH0S6VNLpALPbAg5dmc0XXQ4DshmfBYEcH6ct+k683csFvEFIjxrm8Rj7mwsIF4CAT1vMZqUQ56tOhoshHZjAEIgbuJ0ip1pX1xM2kwzFE/Gzwr8naAEPL/5Gs87a7z/Fvw8NbTw6i6d2HvxfV5v60tkBMxmYolEqjRzDLF/hMR8E7iRm9ypkj9dl8aRynS3WRidjqK7ebJfnmU4gCKJzMNcnku91I4JDr7W6rNUbuWTCJ5uVA/7/Rc8B00kQucv0RbDZ0u3RRtlmLVt9rFk/+s/5Ln2Cqn55wUJlSvn4ijqjFZW26nczt6m5aLdNs9CuOF8XMTqXSJ7AcnMabTeze3zduvCQrjqfFKKb0D2P4TSr1WqDQQ0CplegN3e165c1tY06VpsdbWNE7nUieUpPa2vtATPt7yxmYar4F3KMj24yKDCnAAAAAElFTkSuQmCC" style="width:22px">`
        : `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEzklEQVR4nO1ZaYgcVRB+URMVb7zibTDGA1FBFA90QSHGY3emq/qhO9Nd1TuR+aegiAhCFgSViIoiHmCI+kOMP4zHD0Ex8UciqOCJENR4H4nX6iYao5JVKjOdru6Znp5s98xOYD940DOvXr2q96req6pnzCxmMZiwli4A4K+lybfZkwBAlwHwJCL/Jw2A/0DkK8yeAERaDMDbQuGjRn9KnxlkIAaXNgTdJfT3jRYpAcCXmEGE44ydA8ATkdnQJmvHzgQITkXkb5U5TVrL55lBAkDtJET+Ua30N9Z6C8N++dZKINJma/0TzSDAWnsgAH+o7P0XAD69lc5bCEA/qR163/O8A8xMYnx8fC9EXq1t3HH4wjR6xOBi7eAA9LzwMDMFAF6mVn7Kdfn67DFBRZ9OALzMzAQcJzgfgP5Rq39Pt2MRablS4l9r/YtMPzEyUjsIkTcqIdZaa/fudrzQAtAbavxG8SXTLwDwSmXHv0/nRHGc6vH62EWkFaYfQPSvjdtwUJkuL9flaoLX1aaXYOb9EOlztfrP5uWJyKuUQ3+2ZMmN+5peAYDvUJNNWDt2ZF6ejuMdJWao+N5ueoFSiQ+N2yzfVBRvRLpZ7eqv1tYPMUUDgMbVKn1Rr9fnFsXbWjsPkb7q2d0gdqljHQBemi1U7TSxaUTaUC77i7LoXZduiMdKdl5hCiCSF48yOzMfGhraB5HeUQq/l7VjtrELm8Mx3dzqu6PAa0qBO7PoxRGTCQ0A39bFuLsU/SuFCC8OBcB/h4zLZTqliwtqaxsFtlpLx3UaWy77i5QZbZcbP7cCjkOuWv2PsugB+Dkl+CfNFv5e1cX4jyP6AHIrEA+86N5OtI4TXK6U3SFBmqSQEqmG/wtNxnz3TSdATAUAvR5NTm4H0jkJx10Z8eCntEMLbfp8/nWK9tXcCuioU47GdLoAdBnFcarHhH3yrZN9AC6n8SmX/TN0aJFbAbkZIwf2D09XgD5QW7+8tZ/vV/3vpvEZHR09QqenuRWQhCNkKOd7WiFLrdq2djGStTw/nk62L6/U6/W5OtnJrUCzqtY0ofYxSiKifKIDr12+gEjPtKOpVCqHKV6ThfqA43hnJfslk5IzO1IyveYjCb86pf5qV5VApLOVkp8WoAC9rFYkaO33S/rcz+YX3QsAwXBrf1BT871YhAK3KgFXJ/sB+HHVf3cWPwB+QNE/2jofvxQp4N+SW4Hm9d68iGh7MhxApPWRifE1WfwQ6Sq1wut0X6kUnKDClild3cuFRAUhtmo6jtdnfxokllI2/mXabsqcpig0nW8qDBH0EagduJskp5FXRwGbib0p0A61UMW+J0gCr5j/IFFnU4Hfwv+Hh5cencVH7gNlQhOR6dAmpdgLpmiMjNSOBeDv9DVfKvHJAPRm+J/rBjaLj451EGm9td6CeJFMKts83/QC1vK5OtYH4J/FEdXvtzoVa6Uilwj41gkPJfwWuQdMLwHgX9mYKJ6wqPZgSrQ5B4AeSh9HW/r2BCUvL4lEJSnMGhFGMqpGHZUWS/20A/0GiUL7Inwi1XxEp5u726Ax9uFqtXpwX4WPK+ItAOCn9XGa3Xa+WD4ph4AZFEjlrlF+oRXiqPKcJCvcaDu/3wagx+S2ttbuP9PyzmIWpoH/AeaUFWmyMuZqAAAAAElFTkSuQmCC" style="width:22px">`;

        const newHtml = `
        <div class="top-section top">
            <span class="max"><a href="https://www.sooplive.co.kr/live/all">인기 채널</a></span>
            <span class="min"><a href="https://www.sooplive.co.kr/live/all">${topIcon}</a></span>
        </div>
        <div class="users-section top"></div>
    `;

        const serviceLnbElement = document.getElementById('sidebar');
        if (serviceLnbElement && !update) {
            serviceLnbElement.insertAdjacentHTML('beforeend', newHtml);
        }

        const openList = document.body.querySelectorAll('.users-section.top .user:not(.show-more)').length;

        try {
            const [hiddenBjList, broadListResponse] = await Promise.all([getHiddenbjList(), fetchBroadList('https://live.sooplive.co.kr/api/main_broad_list_api.php?selectType=action&orderType=view_cnt&pageNo=1&lang=ko_KR')]);
            HIDDEN_BJ_LIST.length = 0;
            HIDDEN_BJ_LIST.push(...hiddenBjList);

            const channels = broadListResponse.broad;
            const usersSection = document.querySelector('.users-section.top');
            let temp_html = '';

            channels.forEach(channel => {
                const isBlocked = blockedWords.some(word => channel.broad_title.toLowerCase().includes(word.toLowerCase())) ||
                      HIDDEN_BJ_LIST.includes(channel.user_id) || isCategoryBlocked(channel.broad_cate_no) || isUserBlocked(channel.user_id);

                if (!isBlocked) {
                    const userElement = createUserElement(channel, 0, 0);
                    temp_html += userElement.outerHTML;
                }
            });

            if (isChzzkTopChannelsEnabled) {
                const chzzkTopChannelsData = await fetchBroadList('https://api.chzzk.naver.com/service/v1/lives?size=50&sortType=POPULAR');
                const chzzkChannels = chzzkTopChannelsData.content.data;

                chzzkChannels.forEach(channel => {
                    const userElement = createUserElementChzzk(channel, 0);
                    temp_html += userElement.outerHTML;
                });
            }

            if (update) {
                updateElementWithContent(usersSection, temp_html);
            } else {
                usersSection.insertAdjacentHTML('beforeend', temp_html);
            }

            desc_order('.users-section.top');
            showMore('.users-section.top', 'toggleButton3', update ? openList : displayTop, displayTop);
            makeThumbnailTooltip();

        } catch (error) {
            console.error("Error:", error);
        }
    };

    const extractFollowUserIds = (response) => {
        allFollowUserIds = response.data.map(item => item.user_id); // 모든 user_id를 추출하여 전역 배열에 저장
        GM_setValue("allFollowUserIds", allFollowUserIds);
    };

    const insertFavoriteChannels = async (update) => {

        let followingListSoop;
        let followingListChzzk;

        if (isChzzkFollowChannelsEnabled) {
            [followingListSoop, followingListChzzk] = await Promise.all([fetchBroadList('https://myapi.sooplive.co.kr/api/favorite'), fetchBroadList('https://api.chzzk.naver.com/service/v1/channels/followings/live',3000)]);
        } else {
            followingListSoop = await fetchBroadList('https://myapi.sooplive.co.kr/api/favorite');
        }
        const isSooploggedIn = followingListSoop?.data;
        const isChzzkloggedIn = followingListChzzk?.code === 200;

        if (!isSooploggedIn && !isChzzkloggedIn) {
            return;
        }

        if (isSooploggedIn){
            extractFollowUserIds(followingListSoop);
        }

        const followIcon = IS_DARK_MODE ?
              `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAD1ElEQVR4nO2YXYhVVRTHd6NZ1tAH9CA9CAYJZb4oCdVDUhS9GFQPvSgWPViQiklmRJzCh3orIgijGFAo2Gf9z5mud85aZ6Y69SSSEBrlFGTli0+hqGkf0sSac+/Mvnvy3nvu5wjnBxcunLP+e6191tp77W1MSUlJSckgqFartwLyLJGMAzIN8AWALwF8EpBPgOQJa+2SVjr6ThTxk0T8aW47q3GhphkT8TNxnN3SM8ettcuJ+FWAzwIy0/zHJ6KI77uSVhjK/fpOKx0iPkMke8fGsuu7cn58fPJ2gL9p7XjD4P9owDMzM9fUdYIgGCGS1/RZQa0j6kPHzhPxKU/0GMA7rE3uPnAgvVFnSP8TyW5AfvUGf6+uRSTvNz6TX4B0VxRN3qUaqhWGvIaIdxLJcU/nVOEgNG3cmSeSPwHepjPZzEad9lLqDYDf9Cbh3WapEQTBSBTx8/mY81+iUDrlOT/vfBQlDxawfblJSrzUrk4Y8kYviD0FVhu3YHmbKQggH/2P8x8W1SGSF9zCbmt10qXSzflmaXMlKpXKDUT8nfMVj2uKFdWx1i5xayIM061tBMCxM/s7TIdE0cTq2tp+gmjyzk51iHinMxFow0B+rBvoCmOGTBjyGicjplsaAHx+PoBs1AwZa7NRJyPOtzSobeuzBgcPJjeZIWPt1M0FA9C8nSuaRwfiZRMAecxZiX4wrdDlrlDR9BnKm8e6Px+0NAjD5GEn4n+jSDaYIRHH6Xr1YT4jeGNLI23C8p5nLogvOtkLuiUIghGAv3T8+LZtYyB9yGu+dpsBQ15LUrgeARlzqv/vQaYSwOsA+csZ/+PCItriatW7LbC11RWmz1hbXZG323Oz/722Jh2JxfHEWkAuOnl4tJ+bm7XZqI7hOH8RSO/pSpRIngLksiM6kWXZUtNjsixbqtrOOJf17NwTcW2pvdb4UJIk1/VE3Bizf//Ra3XP8Vrw7aaXALLPW5nGrbXLutW11i5zN6vab5/pBwuDYO7m5sDmzn/mab5j+kl+zm0496adHFY0BYn40ECdrwPw694nnyqy1Om7gHw+FOfrAPKKVxNftdN+6zsAf+19xbfMMNBDt9ts6RpeqVRua3ZhQCSHvZl/2wwTInmxMQg9wC/csfMd1j3oz9psN4uBMEw2N14b8kmiqTvqz62dWgnwT+4mRZQ8ZxYTRPK0Nn1OEL/pzYS16SqAf/buTreYxQjAj7u3aYCcrv3qQV0iSjeZxUyYXwmeW3gzJ38A8oi5GgCSewH+3Zn5s0TpA+ZqIo7T9Vq4+UUZrxu2PyUlJSWmL/wH5eEJ5sFzGH4AAAAASUVORK5CYII=" style="width:20px">`
        : `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAADyUlEQVR4nO1YTWhdVRA+/TH+Ba3gorgQFBS0ulEsqItXFCWLmuZ8k0GSvDtzo/C0YFu0+IfIU7rQnSKCIIo7XYmRIiL+oCsRBbFimwr+ddOVWKy2aovK5OYl552Q9+59P7kp3A8O5OWe+c7MnJk5c45zFSpUqFBhLTA9PX3Z5KTMAjoHyDygfwByGtAfAX3Te/HMvKkbj80hSgHIW5nsAodxzRPJO4Cmu3bploEpzswXAvIkICeI9L/OQ44wJ7euxkWU3mZzcvD8BugTqnpBX8qPj993BaBfdl+wbZwxg51zG1o8zWZzI6BP2bciXIB+YTr0rDyRHIsIvyHSvcyz19fr9YvNQ/Y3oPuJ5OfIiy+1uAB5Ofr2E5E8PDGRXGccxkUk2wDZB8ihaO6xwkZkYRN6Xv6anJQHzJOdZEzpyIvPEMmz0f9e7BQazWZzI5E+aGuGO1EonCwEQuWZk1p+WX109ZBIHsnLQ6Q7IiMey11twoQ1z+dddHlxeW2l8vJqUR5AdoeJnas6WakMY75T2KyGnTsbFxHpt4HyhyzEivIw86b2nEi0q5DV4sBze12PmJhIrl2s7UeA9JpeeQDZFzji7a4CRHq0JWAVxpUMItkWGDDfVQDQk8sG8KgrGcw8GoT0ya4Cdqy3BGZmZi5xJYO5cWlBAyxulzL/blcyvNexICcPdxWwclcoaYYMQOcCfV7pKkCkdwYW/8ss211JYNabTYdAnx155DYs9jytMPq4l7OgXzSzluKTwPtf5xb2Pr0jauL2uzUGVrQkBfORSN4IrP9nLUPJ+/QmQP8O1n+9MEnW4urhsAVm1q1uyGDWrVm7vaT8d9aa9EiW3gjoqSCUvhrm4cbMo7ZGsN4p7+s39EUKKAF6NtiJ92q12mY3YNRqtc3GHSh/1u7OAyG3ljpK6oNjY3vOHwi5c67RaJxnZ064hve6xw0SRHogMmKOmUf65WXmkfCwWhwH3DAQG0Ek7/fzcsDMI0T6bsT5ghsmsntu20580MtlxUIQ0INrqnwLRPp0tOUfFil1NheQj0pRvgUieTzaiU/ztN82h0g/i5R/zpWBxUv3UrNlNXxqauryTg8GRPJ5ZPjzrkwA8lC7EXaBX3liZyfs8kXfZPygS2WvIJJ6+GyYPdrOXt36zpxcSSTfh4cUoPe79QQgudeavkDJX+xlgrl+FZH8EHj+DKCJW4/wXsbbX9PkuI3g92kgvcetZ1D2JPh7/DJHJH96n97lzgV4n94CyK+B508Aers7l8AL99mFxD1qF5Sy9alQoUIFNxT8D3rbJQHjF4hlAAAAAElFTkSuQmCC" style="width:20px">`;

        if (!update) {
            const newHtml = `
            <div class="top-section follow">
                <span class="max"><a href="https://www.sooplive.co.kr/my/favorite">즐겨찾기 채널</a></span>
                <span class="min"><a href="https://www.sooplive.co.kr/my/favorite">${followIcon}</a></span>
            </div>
            <div class="users-section follow"></div>
        `;

            const serviceLnbElement = document.getElementById('sidebar');
            serviceLnbElement?.insertAdjacentHTML('beforeend', newHtml);
        }

        const openList = document.body.querySelectorAll('.users-section.follow .user:not(.show-more)').length;

        try {
            let tempHtmlArray = [];
            const usersSection = document.querySelector('.users-section.follow');

            if (isSooploggedIn){
                const feedData = await getStationFeed(); // 피드 데이터를 비동기적으로 가져옴
                const feedUserIdSet = new Set(feedData.map(feedItem => feedItem.station_user_id));

                tempHtmlArray = followingListSoop.data.reduce((acc, item) => {
                    const { is_live, user_id, broad_info } = item;
                    const is_mobile_push = isPinnedStreamWithNotificationEnabled === 1 ? item.is_mobile_push : "N";
                    const is_pin = isPinnedStreamWithPinEnabled === 1 ? item.is_pin : false;

                    if (is_live) { // 생방송 중
                        broad_info.forEach(channel => {
                            const userElement = createUserElement(channel, is_mobile_push, is_pin);
                            acc.push(userElement.outerHTML);
                        });
                    } else if (feedUserIdSet.has(user_id)) { // 비방 + 방송국 새 글
                        const feedItems = feedData.filter(feedItem => feedItem.station_user_id === user_id);

                        feedItems.forEach(feedItem => {
                            if (feedItem?.reg_timestamp && checkIfTimeover(feedItem.reg_timestamp)) {
                                return; // 타임오버된 경우 넘어감
                            }
                            const userElement = createUserElement_offline(item, feedItem);
                            acc.push(userElement.outerHTML);
                        });
                    } else if (is_pin && !isPinnedOnlineOnlyEnabled ) { // 비방 + 상단 고정 + 설정값
                        const userElement = createUserElement_offline(item, null);
                        acc.push(userElement.outerHTML);
                    }

                    return acc;
                }, []);
            }

            if (isChzzkloggedIn){
                // 기존의 tempHtmlArray에 추가하도록 변경
                followingListChzzk?.content?.followingList.forEach(item => {
                    const is_mobile_push = isPinnedStreamWithNotificationEnabled === 1 ? (item?.channel?.personalData?.following?.notification === true ? "Y" : "N") : "N";

                    const userElement = createUserElementChzzk(item, is_mobile_push);
                    tempHtmlArray.push(userElement.outerHTML); // 기존 배열에 추가
                });
            }

            if (update) {
                updateElementWithContent(usersSection, tempHtmlArray.join(''));
            } else {
                usersSection.insertAdjacentHTML('beforeend', tempHtmlArray.join(''));
            }

            desc_order('.users-section.follow');
            showMore('.users-section.follow', 'toggleButton2', update ? openList : displayFollow, displayFollow);
            makeThumbnailTooltip();

        } catch (error) {
            console.error("Error in insertFavoriteChannels:", error);
        }
    }

    const waitForNonEmptyArray = async () => {
        const timeout = new Promise((resolve) =>
                                    setTimeout(() => resolve([]), 3000) // 3초 후 빈 배열 반환
                                   );

        const checkArray = (async () => {
            while (allFollowUserIds.length === 0) {
                await new Promise((resolve) => setTimeout(resolve, 100)); // 100ms 대기
            }
            return allFollowUserIds;
        })();

        return Promise.race([timeout, checkArray]);
    };

    const insertMyplusChannels = async (update) => {
        try {
            const response = await fetchBroadList('https://live.sooplive.co.kr/api/myplus/preferbjLiveVodController.php?nInitCnt=6&szRelationType=C');

            if (!response || typeof response !== 'object' || response.RESULT === -1 || !response.DATA) {
                return;
            }

            const { DATA } = response;

            const myplusIcon = IS_DARK_MODE ?
                  `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAABkUlEQVR4nO2Wu0oDQRSGExQsBO/BC2Kl4CNY+ABW4hMIFpZi4RuI4gtYCRYm3eE/m4th5uiKKQWDoiAiIqKlqIjaCBYjQyaYFIYsJruI+eHAzuwM/zdnDjMTi7X0l0XkdwNyBOgnQG+lUqorNPN0utADyDGzmIpIhmKez+d7mXWxZKpvmWXGfgPy2nRzIukD5MQZ3hD5Y0QyXmrrh6aae57fzyynbuXXzGrUGBNn1tsOYKdp5rlcbgDQZ26vrzKZ/RHbzyxLDugd2J9oijmRSjDLuVvlJVF+qPwP0Lu23/NksWGGXF3ZlXGRzR4MlscVCoV2QN5qjLeZeQZknYjafg1ApBLV49RUbfPvAGQ1MEDsh3YQeZ6adgCPkQAw62FXOy9RAcy6WjiMCmDNAWxEBCC+q4G50AGMMXG793Zu+eAKFQCQSZf++0ATuUEAzDLv0k+RAACy6QBWIgFg92awh1HoAEqpDkA+AP2ZTO51hgpgjInb29EVYDGQuVW9F0ydsRALKm6M8R2glwObt9TSv9MXwO1y9weCI98AAAAASUVORK5CYII=" style="width:24px">`
            : `<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAABmElEQVR4nO2Wu0oDQRSGNyhYCN6DF8RKwUewENKmyWXO2VMkuznHGLQLFr6BKL6AlWChpa2lYCsYFAUREREtRUXURrBQVk+ICpEsJruI+WFgZ3aG/5t/Z2fGspr6yyKa7UTkXUS5BeBVx3E6AjNPpaQLkfcQ5bVSeCMQ82w22w0gJc8UQC6Mkbg+PzTcnKjQg8j7OutzotwIkTuq9euGmqfTuV4AOdC4z4xxhi3LigDwmiaw3jDzTCbTh8iHH0Z8mkxOD3ntxkhRzZ8ApsYaYk6UjwLIkcZ8QiQD5XcAsqXtM3UzxC8ru1IA5DiRKPSX+8VisVZEfqzWX9O6A5AlImr5NQBRPvq5nzEy8ZP5N5AF3wBWlbofEeUmNb2bUACMcQb1r7kPCYCTOn4nFABEXtQElkMCkG1dA+kwACLet/fGljeuQAEAZFxnf+VrINYJwLaZdQ/YDAUAgFc0gfmQAOT9zuBtRoEDxOPFNkR+RpQX13XbgwaIeKejxl/yZe6p1gOmlmLbnLf8CutizpcAPOfbvKmm/p3eAMYsiMeMK5ANAAAAAElFTkSuQmCC" style="width:24px">`;

            if (!update) {
                const newHtml = `
                <div class="top-section myplus">
                    <span class="max">추천 채널</span>
                    <span class="min">${myplusIcon}</span>
                </div>
                <div class="users-section myplus"></div>
                <div class="top-section myplusvod">
                    <span class="max">추천 VOD</span>
                    <span class="min">${myplusIcon}</span>
                </div>
                <div class="users-section myplusvod"></div>
            `;

                document.getElementById('sidebar')?.insertAdjacentHTML('beforeend', newHtml);
            }

            const openList = document.querySelectorAll('.users-section.myplus .user:not(.show-more)').length;
            const openListvod = document.querySelectorAll('.users-section.myplusvod .user:not(.show-more)').length;

            const { live_list: channels, vod_list: vods } = DATA;

            const usersSection = document.querySelector('.users-section.myplus');
            const usersSection_vod = document.querySelector('.users-section.myplusvod');

            const tempHtmlArray = [];
            const tempHtmlVodArray = [];

            const addChannelElements = (channelList, isVod = false) => {
                for (const channel of channelList) {
                    const isWordBlocked = channel.broad_title &&
                          blockedWords.some(word => channel.broad_title.toLowerCase().includes(word.toLowerCase()));

                    // 조건 추가: allFollowUserIds와 channel.user_id 비교
                    if (
                        allFollowUserIds.includes(channel.user_id) && // allFollowUserIds에 user_id가 포함된 경우
                        !isVod && // isVod가 false일 때
                        isDuplicateRemovalEnabled // 중복 제거 기능이 활성화되어 있을 때
                    ) {
                        continue; // 조건이 충족되면 다음 루프 반복
                    }

                    if (
                        isCategoryBlocked(isVod ? channel.category : channel.broad_cate_no) ||
                        isUserBlocked(channel.user_id) ||
                        isWordBlocked ||
                        (update && isDuplicateRemovalEnabled && isUserInFollowSection(channel.user_id))
                    ) {
                        continue; // 다른 조건에 따라 건너뛰기
                    }

                    const userElement = isVod ? createUserElement_vod(channel) : createUserElement(channel, 0, 0);
                    (isVod ? tempHtmlVodArray : tempHtmlArray).push(userElement.outerHTML);
                }
            };

            if (isDuplicateRemovalEnabled && displayFollow) await waitForNonEmptyArray();
            addChannelElements(channels);
            addChannelElements(vods, true);

            if (update) {
                updateElementWithContent(usersSection, tempHtmlArray.join(''));
                updateElementWithContent(usersSection_vod, tempHtmlVodArray.join(''));
            } else {
                usersSection.insertAdjacentHTML('beforeend', tempHtmlArray.join(''));
                usersSection_vod.insertAdjacentHTML('beforeend', tempHtmlVodArray.join(''));
            }

            makeThumbnailTooltip();

            if (!myplusOrder) {
                desc_order('.users-section.myplus');
            }

            const showMoreHandler = () => {
                showMore('.users-section.myplus', 'toggleButton', update ? openList : displayMyplus, displayMyplus);
                showMore('.users-section.myplusvod', 'toggleButton4', update ? openListvod : displayMyplusvod, displayMyplusvod);
            };

            showMoreHandler();

        } catch (error) {
            console.error("Error fetching or processing data:", error);
        }

    };

    const makeThumbnailTooltip = () => {
        try {
            const elements = document.querySelectorAll('#sidebar a.user');
            const tooltipContainer = document.querySelector('.tooltip-container');
            const sidebar = document.getElementById('sidebar');

            const hoverTimeouts = new Map();

            elements.forEach(element => {
                const isOffline = element.getAttribute('data-tooltip-listener') === 'false';
                if (isOffline) return;

                const hasEventListener = element.getAttribute('data-tooltip-listener') === 'true';
                if (!hasEventListener) {
                    element.addEventListener('mouseenter', (e) => {
                        const uniqueId = `tooltip-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
                        element.setAttribute('data-hover-tooltip-id', uniqueId);

                        const timeoutId = setTimeout(() => {
                            if (element.matches(':hover') && element.getAttribute('data-hover-tooltip-id') === uniqueId) {
                                showTooltip(element, uniqueId);
                            }
                        }, 20);
                        hoverTimeouts.set(element, timeoutId);
                    });

                    element.addEventListener('mouseleave', (e) => {
                        element.removeAttribute('data-hover-tooltip-id');

                        const timeoutId = hoverTimeouts.get(element);
                        if (timeoutId) {
                            clearTimeout(timeoutId);
                            hoverTimeouts.delete(element);
                        }

                        const to = e.relatedTarget;
                        const isGoingToAnotherElement = Array.from(elements).some(el => {
                            const isOffline = el.getAttribute('data-tooltip-listener') === 'false';
                            return el !== element && el.contains(to) && !isOffline;
                        });
                        if (!isGoingToAnotherElement) {
                            tooltipContainer.classList.remove('visible');
                            tooltipContainer.removeAttribute('data-tooltip-id');
                            tooltipContainer.innerHTML = ''; // 초기화
                        }
                    });

                    window.addEventListener('mouseout', (e) => {
                        if (!e.relatedTarget && !e.toElement) {
                            tooltipContainer.classList.remove('visible');
                            tooltipContainer.innerHTML = '';
                        }
                    });

                    element.setAttribute('data-tooltip-listener', 'true');
                }
            });

            async function showTooltip(element, uniqueId) {
                // hover 중인지 다시 검사
                if (element.getAttribute('data-hover-tooltip-id') !== uniqueId) return;

                tooltipContainer.setAttribute('data-tooltip-id', uniqueId);

                const topBarHeight = document.getElementById('serviceHeader')?.offsetHeight ?? 0;
                const isScreenMode = document.body.classList.contains('screen_mode');
                const { left: elementX, top: elementY } = element.getBoundingClientRect();
                const offsetX = elementX + sidebar.offsetWidth;
                const offsetY = Math.max(elementY - 260, isScreenMode ? 0 : topBarHeight);

                let imgSrc = element.getAttribute('broad_thumbnail');
                const broadTitle = element.getAttribute('tooltip');
                let broadStart = element.getAttribute('broad_start');
                const vodDuration = element.getAttribute('vod_duration');
                const randomTimeCode = Date.now();
                const userId = element.getAttribute('user_id');

                if (broadStart === "NotAvailable") {
                    try {
                        const getThumbnailJson = await fetchBroadList(`https://api.chzzk.naver.com/service/v1/channels/${userId}/data?fields=topExposedVideos`);
                        if (getThumbnailJson?.code === 200) {
                            const topExposedVideos = getThumbnailJson.content?.topExposedVideos;
                            if (topExposedVideos?.openLive?.liveImageUrl) {
                                const newThumbnail = topExposedVideos.openLive.liveImageUrl.split('{type}').join('360');
                                const newBroadStart = topExposedVideos.openLive.openDate;

                                if (
                                    tooltipContainer.getAttribute('data-tooltip-id') === uniqueId &&
                                    element.getAttribute('data-hover-tooltip-id') === uniqueId
                                ) {
                                    element.setAttribute('broad_thumbnail', newThumbnail);
                                    element.setAttribute('broad_start', newBroadStart);
                                    imgSrc = newThumbnail;
                                    broadStart = newBroadStart;
                                }
                            }
                        }
                    } catch (error) {
                        console.error("Error in fetching thumbnail:", error);
                    }
                }

                if (element.getAttribute('data-hover-tooltip-id') !== uniqueId) return;

                // 방송 시간 && 이미지 && !게시판이미지
                if (broadStart && imgSrc?.startsWith("http") && !imgSrc?.startsWith('https://stimg.')) {
                    imgSrc += `?${Math.floor(randomTimeCode / 10000)}`;
                }

                let durationText = broadStart
                ? getElapsedTime(broadStart, "HH:MM")
                : vodDuration;

                let tooltipText = '';
                if (sidebar.offsetWidth === 52) {
                    const username = element.querySelector('span.username')?.textContent ?? '';
                    const description = element.querySelector('span.description')?.textContent ?? '';
                    let watchers = element.querySelector('span.watchers')?.textContent ?? '';
                    watchers = watchers.replace('●', '').trim();
                    tooltipText = `${username} · ${description} · ${watchers}<br>${broadTitle}`;
                } else {
                    tooltipText = broadTitle;
                }

                const isTooltipVisible = tooltipContainer.classList.contains('visible');
                const isSameTooltip = tooltipContainer.getAttribute('data-tooltip-id') === uniqueId;

                if (isTooltipVisible && isSameTooltip) {
                    const imgEl = tooltipContainer.querySelector('img');
                    if (imgEl) imgEl.src = imgSrc;
                    else {
                        const newImg = document.createElement('img');
                        newImg.src = imgSrc;
                        tooltipContainer.prepend(newImg);
                    }

                    const durationOverlay = tooltipContainer.querySelector('.duration-overlay');
                    if (durationOverlay) {
                        durationOverlay.textContent = durationText;
                    } else if (durationText) {
                        const newOverlay = document.createElement('div');
                        newOverlay.className = 'duration-overlay';
                        newOverlay.textContent = durationText;
                        tooltipContainer.appendChild(newOverlay);
                    }

                    const textEl = tooltipContainer.querySelector('.tooltiptext');
                    if (textEl) {
                        textEl.innerHTML = tooltipText;
                    } else {
                        const newText = document.createElement('div');
                        newText.className = 'tooltiptext';
                        newText.innerHTML = tooltipText;
                        tooltipContainer.appendChild(newText);
                    }
                } else {
                    let tooltipContent = `<img src="${imgSrc}">`;

                    if (durationText) {
                        tooltipContent += `<div class="duration-overlay">${durationText}</div>`;
                    }

                    tooltipContent += `<div class="tooltiptext">${tooltipText}</div>`;
                    tooltipContainer.innerHTML = tooltipContent;
                }

                Object.assign(tooltipContainer.style, {
                    left: `${offsetX}px`,
                    top: `${offsetY}px`
                });

                tooltipContainer.classList.add('visible');
            }
        } catch (error) {
            console.error('makeThumbnailTooltip 함수에서 오류가 발생했습니다:', error);
        }
    };


    const showMore = (containerSelector, buttonId, n, fixed_n) => {
        const userContainer = document.body.querySelector(containerSelector);
        const users = Array.from(userContainer?.querySelectorAll('.user') || []);
        const displayPerClick = 10;

        // n보다 목록이 적으면 함수를 끝낸다
        if (users.length <= fixed_n) return false;

        // n개를 넘는 모든 요소를 숨긴다
        users.slice(n).forEach(user => user.classList.add('show-more'));

        const toggleButton = document.createElement('button');
        toggleButton.textContent = users.length > n ? `더 보기 (${users.length - n})` : '접기';
        toggleButton.id = buttonId;
        toggleButton.title = "우클릭시 접기(초기화)";
        userContainer.appendChild(toggleButton);

        toggleButton.addEventListener('click', () => {
            const hiddenUsers = users.filter(user => user.classList.contains('show-more'));
            const hiddenCount = hiddenUsers.length;

            if (hiddenCount > 0) {
                hiddenUsers.slice(0, displayPerClick).forEach(user => user.classList.remove('show-more'));
                const remainingHidden = hiddenUsers.length - displayPerClick;
                toggleButton.textContent = remainingHidden > 0 ? `더 보기 (${remainingHidden})` : '접기';
            } else {
                users.slice(fixed_n).forEach(user => user.classList.add('show-more'));
                toggleButton.textContent = `더 보기 (${users.length - fixed_n})`;
            }
        });

        toggleButton.addEventListener('contextmenu', event => {
            event.preventDefault();
            users.slice(fixed_n).forEach(user => user.classList.add('show-more'));
            toggleButton.textContent = `더 보기 (${users.length - fixed_n})`;
        });
    }

    const generateBroadcastElements = async (update) => {
        //console.log(`방송 목록 갱신: ${new Date().toLocaleString()}`);

        try {
            if (displayFollow) insertFavoriteChannels(update);
            if (displayTop) insertTopChannels(update);
            if (displayMyplus || displayMyplusvod) insertMyplusChannels(update);
        } catch (error) {
            console.error('Error:', error);
        }
    }

    const addModalSettings = () => {
        const openModalBtn = document.createElement("div");
        openModalBtn.setAttribute("id", "openModalBtn");
        const link = document.createElement("button");
        link.setAttribute("class", "btn-settings-ui");
        openModalBtn.appendChild(link);

        const serviceUtilDiv = document.body.querySelector("div.serviceUtil");
        serviceUtilDiv.prepend(openModalBtn);

        // 모달 컨텐츠를 담고 있는 HTML 문자열
        const modalContentHTML = `
<div id="myModal" class="modal">
    <div class="modal-content">
        <span class="myModalClose">&times;</span>
        <h2 style="font-size: 24px;">확장 프로그램 설정</h2>

        <div class="divider"></div>
        <h3 style="margin-bottom: 15px; font-size: 16px;">방송 목록 옵션</h3>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchPreviewModal">
                <span class="slider round"></span>
            </label>
            <label for="switchPreviewModal">[썸네일] 클릭시 프리뷰 열기</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchOpenExternalPlayer">
                <span class="slider round"></span>
            </label>
            <label for="switchOpenExternalPlayer">[썸네일] 오른쪽 클릭시 외부 재생기 열기</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchReplaceEmptyThumbnail">
                <span class="slider round"></span>
            </label>
            <label for="switchReplaceEmptyThumbnail">[썸네일] 마우스 오버시 연령 제한 썸네일 보기</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchRemoveRedistributionTag">
                <span class="slider round"></span>
            </label>
            <label for="switchRemoveRedistributionTag">[썸네일] 탐방 허용 태그 숨기기</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchRemoveWatchLaterButton">
                <span class="slider round"></span>
            </label>
            <label for="switchRemoveWatchLaterButton">[썸네일] 나중에 보기 버튼 숨기기 👍</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchRemoveBroadStartTimeTag">
                <span class="slider round"></span>
            </label>
            <label for="switchRemoveBroadStartTimeTag">[썸네일] 방송 시작 시간 숨기기 👍</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchRemoveCarousel">
                <span class="slider round"></span>
            </label>
            <label for="switchRemoveCarousel">[메인 페이지] 자동 재생되는 채널 전광판 숨기기</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchBroadTitleTextEllipsis">
                <span class="slider round"></span>
            </label>
            <label for="switchBroadTitleTextEllipsis">방송 제목이 긴 경우 ...으로 생략하기 👍</label>
        </div>

        <div class="divider"></div>
        <h3 style="margin-bottom: 15px; font-size: 16px;">사이드바 옵션</h3>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchCustomSidebar">
                <span class="slider round"></span>
            </label>
            <label for="switchCustomSidebar">사이드바 사용 (해제시 기본 사이드바) 👍</label>
        </div>

        <div class="option">
            <label for="favoriteChannelsDisplay" title="0 = 숨김">[즐겨찾기 채널] 표시 수</label>
            <input type="range" id="favoriteChannelsDisplay" min="0" max="30" title="0 = 숨김">
            <span id="favoriteChannelsDisplayValue">${displayFollow}</span>
        </div>

        <div class="option">
            <label for="myPlusChannelsDisplay" title="0 = 숨김">[추천 채널] 표시 수</label>
            <input type="range" id="myPlusChannelsDisplay" min="0" max="30" title="0 = 숨김">
            <span id="myPlusChannelsDisplayValue">${displayMyplus}</span>
        </div>

        <div class="option">
            <label for="myPlusVODDisplay" title="0 = 숨김">[추천 VOD] 표시 수</label>
            <input type="range" id="myPlusVODDisplay" min="0" max="30" title="0 = 숨김">
            <span id="myPlusVODDisplayValue">${displayMyplusvod}</span>
        </div>

        <div class="option">
            <label for="popularChannelsDisplay" title="0 = 숨김">[인기 채널] 표시 수</label>
            <input type="range" id="popularChannelsDisplay" min="0" max="30" title="0 = 숨김">
            <span id="popularChannelsDisplayValue">${displayTop}</span>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchSmallUserLayout">
                <span class="slider round"></span>
            </label>
            <label for="switchSmallUserLayout">미니 방송 목록</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchOpenExternalPlayerFromSidebar">
                <span class="slider round"></span>
            </label>
            <label for="switchOpenExternalPlayerFromSidebar">오른쪽 클릭시 외부 재생기 열기</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="sendLoadBroadCheck">
                <span class="slider round"></span>
            </label>
            <label for="sendLoadBroadCheck">새로고침 없는 방송 전환 사용 👍</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchRandomSort">
                <span class="slider round"></span>
            </label>
            <label for="switchRandomSort">[즐겨찾기 채널] 랜덤 정렬 (해제시 시청자 많은 순)</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchChannelFeed">
                <span class="slider round"></span>
            </label>
            <label for="switchChannelFeed">[즐겨찾기 채널] 오프라인 채널의 최신 글 보기 👍</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchBlockedCategorySorting">
                <span class="slider round"></span>
            </label>
            <label for="switchBlockedCategorySorting">[즐겨찾기 채널] 차단된 카테고리를 하단으로 이동</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="fixNotificationChannel">
                <span class="slider round"></span>
            </label>
            <label for="fixNotificationChannel">[즐겨찾기 채널] 알림 설정된 채널을 상단 고정</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="fixFixedChannel">
                <span class="slider round"></span>
            </label>
            <label for="fixFixedChannel" title="MY 페이지에서 스트리머 고정 버튼(핀 모양)을 누르면 사이드바에 고정이 됩니다.">[즐겨찾기 채널] 스트리머 관리에서 고정된 채널을 상단 고정<sup>1)</sup></label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchPinnedOnlineOnly">
                <span class="slider round"></span>
            </label>
            <label for="switchPinnedOnlineOnly">[즐겨찾기 채널] 온라인일 때만 상단 고정하기</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="popularChannelsFirst">
                <span class="slider round"></span>
            </label>
            <label for="popularChannelsFirst">[추천 채널]을 [인기 채널]보다 위에 표시</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="mpSortByViewers">
                <span class="slider round"></span>
            </label>
            <label for="mpSortByViewers">[추천 채널] 정렬을 추천순으로 변경 (해제시 시청자순)</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="removeDuplicates">
                <span class="slider round"></span>
            </label>
            <label for="removeDuplicates">[추천 채널] 즐겨찾기 중복 제거</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="openInNewTab">
                <span class="slider round"></span>
            </label>
            <label for="openInNewTab">방송 목록 클릭 시 새 탭으로 열기 👎🏼</label>
        </div>

        <div class="divider"></div>
        <h3 style="margin-bottom: 15px; font-size: 16px;">LIVE 플레이어 옵션</h3>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchNoAutoVOD">
                <span class="slider round"></span>
            </label>
            <label for="switchNoAutoVOD">방송 종료 후 자동으로 VOD 재생하는 기능 차단 👍</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchHideEsportsInfo">
                <span class="slider round"></span>
            </label>
            <label for="switchHideEsportsInfo">E-Sports 정보 숨기기</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="autoClaimGem">
                <span class="slider round"></span>
            </label>
            <label for="autoClaimGem">젬 자동 획득 👍</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="showPauseButton">
                <span class="slider round"></span>
            </label>
            <label for="showPauseButton">[플레이어] 일시정지 버튼 표시 👍</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchCaptureButton">
                <span class="slider round"></span>
            </label>
            <label for="switchCaptureButton">[플레이어] LIVE / VOD 스크린샷 버튼 표시 👍</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchStreamDownload">
                <span class="slider round"></span>
            </label>
            <label for="switchStreamDownload">[플레이어] LIVE 스트림 다운로드 버튼 표시<sup>5)</sup></label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="showBufferTime">
                <span class="slider round"></span>
            </label>
            <label for="showBufferTime">[채팅창] 방송 딜레이 (남은 버퍼 시간) 표시 👍</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchVideoSkipHandler">
                <span class="slider round"></span>
            </label>
            <label for="switchVideoSkipHandler">[단축키] 좌/우 방향키를 눌러 1초 전/후로 이동 👍</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchSharpmodeShortcut">
                <span class="slider round"></span>
            </label>
            <label for="switchSharpmodeShortcut">[단축키] '선명한 모드'(e) 활성화 👍</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchLLShortcut">
                <span class="slider round"></span>
            </label>
            <label for="switchLLShortcut">[단축키] '시차 단축'(d) 활성화** 👍</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchAdjustDelayNoGrid">
                <span class="slider round"></span>
            </label>
            <label for="switchAdjustDelayNoGrid">[단축키] (d)를 '앞당기기'로 변경 👎🏼<br>(위 옵션** 활성화 필수, 비 그리드 사용자만)</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="mutedInactiveTabs">
                <span class="slider round"></span>
            </label>
            <label for="mutedInactiveTabs">[브라우저 탭] 전환 시 방송 음소거</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchDocumentTitleUpdate">
                <span class="slider round"></span>
            </label>
            <label for="switchDocumentTitleUpdate">[브라우저 탭] 제목에 시청자 수 표시</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchShowSidebarOnScreenModeAlways">
                <span class="slider round"></span>
            </label>
            <label for="switchShowSidebarOnScreenModeAlways">[스크린 모드] 항상 사이드바 보기</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="mouseOverSideBar">
                <span class="slider round"></span>
            </label>
            <label for="mouseOverSideBar">[스크린 모드] 좌측 마우스 오버시 사이드바 사용 👍</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="chatPosition">
                <span class="slider round"></span>
            </label>
            <label for="chatPosition">[스크린 모드] 세로로 긴 화면에서 채팅창을 아래에 위치 👍</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchAutoScreenMode">
                <span class="slider round"></span>
            </label>
            <label for="switchAutoScreenMode">[스크린 모드] 자동 스크린 모드</label>
        </div>

        <div class="divider"></div>
        <h3 style="margin-bottom: 15px; font-size: 16px;">VOD 플레이어 옵션</h3>
        <div class="option">
            <label class="switch">
                <input type="checkbox" id="selectBestQuality">
                <span class="slider round"></span>
            </label>
            <label for="selectBestQuality">최고화질 자동 선택 👍</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchRemoveShadowsFromCatch">
                <span class="slider round"></span>
            </label>
            <label for="switchRemoveShadowsFromCatch">[플레이어] CATCH 플레이어 하단의 그림자 효과 없애기</label>
        </div>

        <div class="divider"></div>
        <h3 style="margin-bottom: 15px; font-size: 16px;">채팅창 옵션</h3>

        <div class="option">
            <label for="nicknameWidthDisplay">[닉네임] 가로 크기 (채팅 메시지 정렬시)</label>
            <input type="range" id="nicknameWidthDisplay" min="86" max="186">
            <span id="nicknameWidthDisplayValue">${nicknameWidth}</span>
        </div>
        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchAlignNicknameRight">
                <span class="slider round"></span>
            </label>
            <label for="switchAlignNicknameRight">[닉네임] 우측 정렬하기 (채팅 메시지 정렬시)</label>
        </div>
        <div class="option">
            <label class="switch">
                <input type="checkbox" id="selectHideSupporterBadge">
                <span class="slider round"></span>
            </label>
            <label for="selectHideSupporterBadge">[닉네임] 서포터 배지 숨기기</label>
            <label class="switch">
                <input type="checkbox" id="selectHideFanBadge">
                <span class="slider round"></span>
            </label>
            <label for="selectHideFanBadge">[닉네임] 팬 배지 숨기기</label>
        </div>
        <div class="option">
            <label class="switch">
                <input type="checkbox" id="selectHideSubBadge">
                <span class="slider round"></span>
            </label>
            <label for="selectHideSubBadge">[닉네임] 구독팬 배지 숨기기</label>
            <label class="switch">
                <input type="checkbox" id="selectHideVIPBadge">
                <span class="slider round"></span>
            </label>
            <label for="selectHideVIPBadge">[닉네임] 열혈팬 배지 숨기기</label>
        </div>
        <div class="option">
            <label class="switch">
                <input type="checkbox" id="selectHideMngrBadge">
                <span class="slider round"></span>
            </label>
            <label for="selectHideMngrBadge">[닉네임] 매니저 배지 숨기기</label>
            <label class="switch">
                <input type="checkbox" id="selectHideStreamerBadge">
                <span class="slider round"></span>
            </label>
            <label for="selectHideStreamerBadge">[닉네임] 스트리머 배지 숨기기</label>
        </div>
        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchUnlockCopyPaste">
                <span class="slider round"></span>
            </label>
            <label for="switchUnlockCopyPaste">[채팅 입력란] 복사/붙여넣기 기능 복원</label>
        </div>
        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchHideButtonsAboveChatInput">
                <span class="slider round"></span>
            </label>
            <label for="switchHideButtonsAboveChatInput">[채팅 입력란] 상단 버튼 탭 숨기기</label>
        </div>
        <div class="option">
            <label class="switch">
                <input type="checkbox" id="selectBlockWords">
                <span class="slider round"></span>
            </label>
            <label for="selectBlockWords">[메시지] 단어로 차단<sup>2)</sup></label>
        </div>
        <div class="option">
            <textarea id="blockWordsInput" placeholder="콤마(,)로 구분하여 단어 입력" style="width: 100%; height: 34px; border: 1px solid #ccc;">${registeredWords}</textarea>
        </div>
        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchShowSelectedMessages">
                <span class="slider round"></span>
            </label>
            <label for="switchShowSelectedMessages">[메시지] 유저 채팅 모아보기</label>
        </div>
        <div class="option">
            <textarea id="selectedUsersInput" placeholder="대상 유저 추가: 콤마(,)로 구분하여 유저 아이디 입력하세요 \n즐겨찾기는 자동 등록이므로 따로 입력할 필요 없음" style="width: 100%; height: 34px; border: 1px solid #ccc;">${selectedUsers}</textarea>
        </div>
        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchShowDeletedMessages">
                <span class="slider round"></span>
            </label>
            <label for="switchShowDeletedMessages">[메시지] 강제퇴장 된 유저의 채팅 모아보기</label>
        </div>
        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchChatCounter">
                <span class="slider round"></span>
            </label>
            <label for="switchChatCounter">[메시지] 초당 채팅 수 카운터 표시<sup>3)</sup></label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchExpandLiveChatArea">
                <span class="slider round"></span>
            </label>
            <label for="switchExpandLiveChatArea">세로로 긴 화면에서 LIVE 채팅창 확장 버튼 추가 👍</label>
        </div>
        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchExpandVODChatArea">
                <span class="slider round"></span>
            </label>
            <label for="switchExpandVODChatArea">세로로 긴 화면에서 VOD 채팅창 확장 버튼 추가 👍</label>
        </div>
        <div class="divider"></div>
        <h3 style="margin-bottom: 15px; font-size: 16px;">기타 옵션</h3>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchChzzkFollowChannels">
                <span class="slider round"></span>
            </label>
            <label for="switchChzzkFollowChannels">치지직 팔로우 채널 통합<sup>4)</sup></label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchChzzkTopChannels">
                <span class="slider round"></span>
            </label>
            <label for="switchChzzkTopChannels">치지직 인기 채널 통합</label>
        </div>

        <div class="option">
            <label class="switch">
                <input type="checkbox" id="useInterFont">
                <span class="slider round"></span>
            </label>
            <label for="useInterFont">트위치 폰트 (Inter) 사용</label>
        </div>

        <div class="divider"></div>
        <h3 style="margin-bottom: 15px; font-size: 16px;">차단 관리</h3>
        <span style="margin-bottom: 15px; font-size: 12px; display: block;">채널 차단: 본문 방송 목록 -> 점 세개 버튼 -> [이 브라우저에서 ... 숨기기]</span>
        <span style="margin-bottom: 15px; font-size: 12px; display: block;">차단 해제: Tampermonkey 아이콘을 눌러서 가능합니다.</span>
        <span style="margin-bottom: 15px; font-size: 12px; display: block;">단어 등록: Tampermonkey 아이콘을 눌러서 가능합니다.</span>

        <div class="divider"></div>
        <h3 style="margin-bottom: 15px; font-size: 16px;">부가 설명</h3>
        <span style="margin-bottom: 15px; font-size: 12px; display: block;">* 👍 (대부분 추천), 👎🏼 (대부분 비추천)</span>
        <span style="margin-bottom: 15px; font-size: 12px; display: block;">1) MY 페이지에서 스트리머 고정 버튼(핀 모양)을 누르면 사이드바에 고정이 됩니다.</span>
        <span style="margin-bottom: 15px; font-size: 12px; display: block;">2) 해당 단어를 포함하는 메시지 숨김. 완전 일치할 때만 숨김은 단어 앞에 e:를 붙이기. <br>예시) ㄱㅇㅇ,ㅔㅔ,e:ㅇㅇ,e:ㅇㅎ,e:극,e:나,e:락</span>
        <span style="margin-bottom: 15px; font-size: 12px; display: block;">3) [메시지] 유저 채팅 모아보기, [메시지] 강제퇴장 된 유저의 채팅 모아보기 둘 중 하나를 먼저 켜야 작동 함</span>
        <span style="margin-bottom: 15px; font-size: 12px; display: block;">4) 치지직 로그인이 되어있지 않으면 응답지연이 생겨서 느려집니다</span>
        <span style="margin-bottom: 15px; font-size: 12px; display: block;">5) 본문 방송 목록 or 플레이어 페이지 -> 점 세개 버튼 -> [스트림 다운로드]</span>

        <span style="margin-bottom: 15px; font-size: 12px; display: block;">버그 신고는 <a href="https://gf.qytechs.cn/ko/scripts/484713" target="_blank">https://gf.qytechs.cn/ko/scripts/484713</a>에서 가능합니다.</span>
    </div>
</div>
`;

        // 모달 컨텐츠를 body에 삽입
        document.body.insertAdjacentHTML("beforeend", modalContentHTML);

        // 모달 열기 버튼에 이벤트 리스너 추가
        let isFirstClick = true; // 첫 클릭 여부를 저장하는 변수

        openModalBtn.addEventListener("click", () => {
            // 모달을 표시
            document.getElementById("myModal").style.display = "block";

            // 첫 클릭인 경우에만 updateSettingsData 호출
            if (isFirstClick) {
                updateSettingsData();
                isFirstClick = false; // 첫 클릭 후에는 false로 변경
            }
        });

        // 모달 닫기 버튼에 이벤트 리스너 추가
        const closeModalBtn = document.body.querySelector(".myModalClose");
        closeModalBtn.addEventListener("click", () => {
            // 모달을 숨김
            const modal = document.getElementById("myModal");
            if (modal) {
                modal.style.display = "none";
            }
        });

        // 모달 외부를 클릭했을 때 닫기
        document.getElementById("myModal").addEventListener("click", (event) => {
            const modalContent = document.querySelector('div.modal-content');
            const modal = document.getElementById("myModal");

            // 모달 콘텐츠가 아닌 곳을 클릭한 경우에만 모달 닫기
            if (modal && !modalContent.contains(event.target)) {
                modal.style.display = "none";
            }
        });
    }

    const updateSettingsData = () => {

        const setCheckboxAndSaveValue = (elementId, storageVariable, storageKey) => {
            const checkbox = document.getElementById(elementId);

            // elementId가 유효한 경우에만 체크박스를 설정
            if (checkbox) {
                checkbox.checked = (storageVariable === 1);

                checkbox.addEventListener("change", (event) => {
                    GM_setValue(storageKey, event.target.checked ? 1 : 0);
                    storageVariable = event.target.checked ? 1 : 0;
                });

            } else {
                console.warn(`Checkbox with id "${elementId}" not found.`);
            }
        }

        // 함수를 사용하여 각 체크박스를 설정하고 값을 저장합니다.
        setCheckboxAndSaveValue("fixFixedChannel", isPinnedStreamWithPinEnabled, "isPinnedStreamWithPinEnabled");
        setCheckboxAndSaveValue("fixNotificationChannel", isPinnedStreamWithNotificationEnabled, "isPinnedStreamWithNotificationEnabled");
        setCheckboxAndSaveValue("showBufferTime", isRemainingBufferTimeEnabled, "isRemainingBufferTimeEnabled");
        setCheckboxAndSaveValue("mutedInactiveTabs", isAutoChangeMuteEnabled, "isAutoChangeMuteEnabled");
        setCheckboxAndSaveValue("popularChannelsFirst", myplusPosition, "myplusPosition");
        setCheckboxAndSaveValue("mpSortByViewers", myplusOrder, "myplusOrder");
        setCheckboxAndSaveValue("removeDuplicates", isDuplicateRemovalEnabled, "isDuplicateRemovalEnabled");
        setCheckboxAndSaveValue("openInNewTab", isOpenNewtabEnabled, "isOpenNewtabEnabled");
        setCheckboxAndSaveValue("mouseOverSideBar", showSidebarOnScreenMode, "showSidebarOnScreenMode");
        setCheckboxAndSaveValue("switchShowSidebarOnScreenModeAlways", showSidebarOnScreenModeAlways, "showSidebarOnScreenModeAlways");
        setCheckboxAndSaveValue("chatPosition", isBottomChatEnabled, "isBottomChatEnabled");
        setCheckboxAndSaveValue("showPauseButton", isMakePauseButtonEnabled, "isMakePauseButtonEnabled");
        setCheckboxAndSaveValue("switchCaptureButton", isCaptureButtonEnabled, "isCaptureButtonEnabled");
        setCheckboxAndSaveValue("switchStreamDownload", isStreamDownloadEnabled, "isStreamDownloadEnabled");
        setCheckboxAndSaveValue("switchSharpmodeShortcut", isMakeSharpModeShortcutEnabled, "isMakeSharpModeShortcutEnabled");
        setCheckboxAndSaveValue("switchLLShortcut", isMakeLowLatencyShortcutEnabled, "isMakeLowLatencyShortcutEnabled");
        setCheckboxAndSaveValue("sendLoadBroadCheck", isSendLoadBroadEnabled, "isSendLoadBroadEnabled");
        setCheckboxAndSaveValue("selectBestQuality", isSelectBestQualityEnabled, "isSelectBestQualityEnabled");
        setCheckboxAndSaveValue("selectHideSupporterBadge", isHideSupporterBadgeEnabled, "isHideSupporterBadgeEnabled");
        setCheckboxAndSaveValue("selectHideFanBadge", isHideFanBadgeEnabled, "isHideFanBadgeEnabled");
        setCheckboxAndSaveValue("selectHideSubBadge", isHideSubBadgeEnabled, "isHideSubBadgeEnabled");
        setCheckboxAndSaveValue("selectHideVIPBadge", isHideVIPBadgeEnabled, "isHideVIPBadgeEnabled");
        setCheckboxAndSaveValue("selectHideMngrBadge", isHideManagerBadgeEnabled, "isHideManagerBadgeEnabled");
        setCheckboxAndSaveValue("selectHideStreamerBadge", isHideStreamerBadgeEnabled, "isHideStreamerBadgeEnabled");
        setCheckboxAndSaveValue("selectBlockWords", isBlockWordsEnabled, "isBlockWordsEnabled");
        setCheckboxAndSaveValue("useInterFont", isChangeFontEnabled, "isChangeFontEnabled");
        setCheckboxAndSaveValue("autoClaimGem", isAutoClaimGemEnabled, "isAutoClaimGemEnabled");
        setCheckboxAndSaveValue("switchVideoSkipHandler", isVideoSkipHandlerEnabled, "isVideoSkipHandlerEnabled");
        setCheckboxAndSaveValue("switchSmallUserLayout", isSmallUserLayoutEnabled, "isSmallUserLayoutEnabled");
        setCheckboxAndSaveValue("switchChannelFeed", isChannelFeedEnabled, "isChannelFeedEnabled");
        setCheckboxAndSaveValue("switchCustomSidebar", isCustomSidebarEnabled, "isCustomSidebarEnabled");
        setCheckboxAndSaveValue("switchRemoveCarousel", isRemoveCarouselEnabled, "isRemoveCarouselEnabled");
        setCheckboxAndSaveValue("switchDocumentTitleUpdate", isDocumentTitleUpdateEnabled, "isDocumentTitleUpdateEnabled");
        setCheckboxAndSaveValue("switchRemoveRedistributionTag", isRemoveRedistributionTagEnabled, "isRemoveRedistributionTagEnabled");
        setCheckboxAndSaveValue("switchRemoveWatchLaterButton", isRemoveWatchLaterButtonEnabled, "isRemoveWatchLaterButtonEnabled");
        setCheckboxAndSaveValue("switchBroadTitleTextEllipsis", isBroadTitleTextEllipsisEnabled, "isBroadTitleTextEllipsisEnabled");
        setCheckboxAndSaveValue("switchRemoveBroadStartTimeTag", isRemoveBroadStartTimeTagEnabled, "isRemoveBroadStartTimeTagEnabled");
        setCheckboxAndSaveValue("switchUnlockCopyPaste", isUnlockCopyPasteEnabled, "isUnlockCopyPasteEnabled");
        setCheckboxAndSaveValue("switchAlignNicknameRight", isAlignNicknameRightEnabled, "isAlignNicknameRightEnabled");
        setCheckboxAndSaveValue("switchPreviewModal", isPreviewModalEnabled, "isPreviewModalEnabled");
        setCheckboxAndSaveValue("switchReplaceEmptyThumbnail", isReplaceEmptyThumbnailEnabled, "isReplaceEmptyThumbnailEnabled");
        setCheckboxAndSaveValue("switchAutoScreenMode", isAutoScreenModeEnabled, "isAutoScreenModeEnabled");
        setCheckboxAndSaveValue("switchAdjustDelayNoGrid", isAdjustDelayNoGridEnabled, "isAdjustDelayNoGridEnabled");
        setCheckboxAndSaveValue("switchHideButtonsAboveChatInput", ishideButtonsAboveChatInputEnabled, "ishideButtonsAboveChatInputEnabled");
        setCheckboxAndSaveValue("switchExpandVODChatArea", isExpandVODChatAreaEnabled, "isExpandVODChatAreaEnabled");
        setCheckboxAndSaveValue("switchExpandLiveChatArea", isExpandLiveChatAreaEnabled, "isExpandLiveChatAreaEnabled");
        setCheckboxAndSaveValue("switchOpenExternalPlayer", isOpenExternalPlayerEnabled, "isOpenExternalPlayerEnabled");
        setCheckboxAndSaveValue("switchOpenExternalPlayerFromSidebar", isOpenExternalPlayerFromSidebarEnabled, "isOpenExternalPlayerFromSidebarEnabled");
        setCheckboxAndSaveValue("switchRemoveShadowsFromCatch", isRemoveShadowsFromCatchEnabled, "isRemoveShadowsFromCatchEnabled");
        setCheckboxAndSaveValue("switchChzzkFollowChannels", isChzzkFollowChannelsEnabled, "isChzzkFollowChannelsEnabled");
        setCheckboxAndSaveValue("switchChzzkTopChannels", isChzzkTopChannelsEnabled, "isChzzkTopChannelsEnabled");
        setCheckboxAndSaveValue("switchShowSelectedMessages", isShowSelectedMessagesEnabled, "isShowSelectedMessagesEnabled");
        setCheckboxAndSaveValue("switchShowDeletedMessages", isShowDeletedMessagesEnabled, "isShowDeletedMessagesEnabled");
        setCheckboxAndSaveValue("switchNoAutoVOD", isNoAutoVODEnabled, "isNoAutoVODEnabled");
        setCheckboxAndSaveValue("switchHideEsportsInfo", isHideEsportsInfoEnabled, "isHideEsportsInfoEnabled");
        setCheckboxAndSaveValue("switchBlockedCategorySorting", isBlockedCategorySortingEnabled, "isBlockedCategorySortingEnabled");
        setCheckboxAndSaveValue("switchChatCounter", isChatCounterEnabled, "isChatCounterEnabled");
        setCheckboxAndSaveValue("switchRandomSort", isRandomSortEnabled, "isRandomSortEnabled");
        setCheckboxAndSaveValue("switchPinnedOnlineOnly", isPinnedOnlineOnlyEnabled, "isPinnedOnlineOnlyEnabled");

        const handleRangeInput = (inputId, displayId, currentValue, storageKey) => {
            const input = document.getElementById(inputId);
            input.value = currentValue;

            input.addEventListener("input", (event) => {
                const newValue = parseInt(event.target.value); // event.target.value로 변경
                if (newValue !== currentValue) {
                    GM_setValue(storageKey, newValue);
                    currentValue = newValue;
                    document.getElementById(displayId).textContent = newValue;
                    if (inputId === "nicknameWidthDisplay") setWidthNickname(newValue);
                }
            });
        }

        handleRangeInput("favoriteChannelsDisplay", "favoriteChannelsDisplayValue", displayFollow, "displayFollow");
        handleRangeInput("myPlusChannelsDisplay", "myPlusChannelsDisplayValue", displayMyplus, "displayMyplus");
        handleRangeInput("myPlusVODDisplay", "myPlusVODDisplayValue", displayMyplusvod, "displayMyplusvod");
        handleRangeInput("popularChannelsDisplay", "popularChannelsDisplayValue", displayTop, "displayTop");
        handleRangeInput("nicknameWidthDisplay", "nicknameWidthDisplayValue", nicknameWidth, "nicknameWidth");

        // 채팅 단어 차단 입력 상자 설정
        const blockWordsInputBox = document.getElementById('blockWordsInput');

        blockWordsInputBox.addEventListener('input', () => {
            const inputValue = blockWordsInputBox.value.trim();
            registeredWords = inputValue;
            GM_setValue("registeredWords", inputValue);
        });

        // 유저 채팅 모아보기 입력 상자 설정
        const selectedUsersinputBox = document.getElementById('selectedUsersInput');

        selectedUsersinputBox.addEventListener('input', () => {
            const inputValue = selectedUsersinputBox.value.trim();
            selectedUsers = inputValue;
            GM_setValue("selectedUsers", inputValue);
        });

    }

    const checkSidebarVisibility = () => {
        let intervalId = null;
        let lastExecutionTime = Date.now(); // 마지막 실행 시점 기록

        const handleVisibilityChange = () => {
            const body = document.body;
            const isScreenmode = body.classList.contains('screen_mode');
            const isShowSidebar = body.classList.contains('showSidebar');
            const isFullScreenmode = body.classList.contains('fullScreen_mode');
            const isSidebarHidden = (isScreenmode ? !isShowSidebar : false) || isFullScreenmode;
            const webplayer = document.getElementById('webplayer');
            const webplayerStyle = webplayer?.style;
            const sidebar = document.getElementById('sidebar');

            // 스크린 모드에서 사이드바 항상 보이는 옵션
            if (webplayer && isScreenmode && showSidebarOnScreenModeAlways && !isShowSidebar) {
                body.classList.add('showSidebar');
                webplayer.style.left = '0px';
                webplayer.style.left = sidebar.offsetWidth + 'px';
                webplayer.style.width = `calc(100vw - ${sidebar.offsetWidth}px)`;
            }

            // 사이드바가 보이는 상태에서 스크린 모드 종료할 때
            if (webplayer && !isScreenmode && isShowSidebar) {
                body.classList.remove('showSidebar');
                webplayerStyle.removeProperty('width');
                webplayerStyle.removeProperty('left');
            }

            if (document.visibilityState === 'visible' && isSidebarHidden) {
                //console.log('#sidebar는 숨겨져 있음');
                return;
            }

            const currentTime = Date.now();
            const timeSinceLastExecution = (currentTime - lastExecutionTime) / 1000; // 초 단위로 변환

            if (document.visibilityState === 'visible' && timeSinceLastExecution >= 60) {
                //console.log('탭 활성화됨');
                generateBroadcastElements(1);
                lastExecutionTime = currentTime; // 갱신 시점 기록
                restartInterval(); // 인터벌 재시작
            } else if (document.visibilityState === 'visible') {
                //console.log('60초 미만 경과: 방송 목록 갱신하지 않음');
            } else {
                //console.log(`탭 비활성화됨: 마지막 갱신 = ${parseInt(timeSinceLastExecution)}초 전`);
            }
        };

        const restartInterval = () => {
            if (intervalId) clearInterval(intervalId); // 기존 인터벌 중단

            intervalId = setInterval(() => {
                handleVisibilityChange();
            }, 60 * 1000); // 60초마다 실행
        };

        const observeBodyClassChanges = () => {
            const body = document.querySelector('body');

            const observer = new MutationObserver((mutations) => {
                mutations.forEach(({ attributeName }) => {
                    if (attributeName === 'class') {
                        handleVisibilityChange();
                    }
                });
            });

            observer.observe(body, {
                attributes: true,
                attributeFilter: ['class']
            });
        };

        waitForElement('#sidebar', function (elementSelector, element) {
            //console.log('#sidebar가 로드됨!');
            observeBodyClassChanges(); // body 클래스 감시 시작
            restartInterval(); // 인터벌 시작
            document.addEventListener('visibilitychange', handleVisibilityChange);
        });
    };


    const hideUsersSection = () => {
        const styles = [
            !displayMyplus && '#sidebar .myplus { display: none !important; }',
            !displayMyplusvod && '#sidebar .myplusvod { display: none !important; }',
            !displayTop && '#sidebar .top { display: none !important; }'
        ].filter(Boolean).join(' '); // 빈 값 제거 및 합침

        if (styles) {
            GM_addStyle(styles);
        }
    }

    const removeTargetFromLinks = () => {
        try {
            const links = document.querySelectorAll('#container a[target], .side_list a[target]');
            links.forEach(link => {
                link.removeAttribute('target');
            });
        } catch (error) {
            console.error('target 속성 제거 중 오류 발생:', error);
        }
    }

    const runCommonFunctions = () => {

        if (isCustomSidebarEnabled) {
            orderSidebarSection();
            hideUsersSection();
            generateBroadcastElements(0);
            checkSidebarVisibility();
        }

        // 본문 방송 목록의 새 탭 열기 방지
        if(!isOpenNewtabEnabled){
            setInterval(removeTargetFromLinks, 1000);
        }

        waitForElement('div.serviceUtil', function (elementSelector, element) {
            addModalSettings();
            manageRedDot();
        });

        registerMenuBlockingWord();

        blockedUsers.forEach(function(user) {
            registerUnblockMenu(user);
        });

        blockedCategories.forEach(function(category) {
            registerCategoryUnblockMenu(category);
        });

        blockedWords.forEach(function(word) {
            registerWordUnblockMenu(word);
        });

    }

    const orderSidebarSection = () => {

        const style =
              `
                #sidebar .top-section.top {
                    order: 3 !important;
                }
                #sidebar .users-section.top {
                    order: 4 !important;
                }
                #sidebar .top-section.myplus {
                    order: 5 !important;
                }
                #sidebar .users-section.myplus {
                    order: 6 !important;
                }
                #sidebar .top-section.myplusvod {
                    order: 7 !important;
                }
                #sidebar .users-section.myplusvod {
                    order: 8 !important;
                }
            `;
        if (!myplusPosition) {
            GM_addStyle(style);
        }
    }
    //=================================공용 함수 끝=================================//

    //=================================메인 페이지 함수=================================//
    const openHlsStream = (nickname, m3u8Url) => {
        // HTML과 JavaScript 코드 생성
        const htmlContent = `
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>${nickname}</title>
  <style>
    body {
        background-color: black;
        margin: 0;
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100vh;
        overflow: hidden;
        position: relative;  /* 자식 요소 위치 조정을 위해 추가 */
    }
    #video {
        position: absolute;
        width: 100%;
        height: 100%;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        margin: auto;
        max-height: 100%;
        max-width: 100%;
    }
    #overlay {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.5);  /* 반투명 배경 */
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 5;  /* 비디오보다 위에 보이도록 설정 */
    }
    #muteButton {
        background-color: rgba(255, 255, 255, 0.8);
        border: none;
        border-radius: 50%;
        padding: 30px;  /* 버튼 크기 증가 */
        cursor: pointer;
        display: flex;
        justify-content: center;
        align-items: center;
        font-size: 36px;  /* 아이콘 크기 증가 */
        z-index: 10;  /* 버튼이 다른 요소 위에 보이도록 설정 */
    }
  </style>
  <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
</head>
<body>
  <video id="video" controls autoplay muted></video>
  <div id="overlay">
    <button id="muteButton"><i class="fas fa-volume-mute"></i></button>
  </div>
  <script>
    const video = document.getElementById("video");
    const muteButton = document.getElementById("muteButton");
    const overlay = document.getElementById("overlay");

    if (Hls.isSupported()) {
      const hls = new Hls();
      hls.loadSource("${m3u8Url}");
      hls.attachMedia(video);
      hls.on(Hls.Events.MANIFEST_PARSED, function () {
        video.play();
      });
    } else if (video.canPlayType("application/vnd.apple.mpegurl")) {
      video.src = "${m3u8Url}";
      video.addEventListener("loadedmetadata", function () {
        video.play();
      });
    }

    const toggleMute = () => {
      video.muted = !video.muted;
      muteButton.innerHTML = video.muted ? '<i class="fas fa-volume-mute"></i>' : '<i class="fas fa-volume-up"></i>';
      overlay.style.display = 'none';  // 버튼 클릭 후 레이어 사라지도록 설정
    };

    // 버튼 클릭 시 음소거 해제
    muteButton.addEventListener("click", (event) => {
      event.stopPropagation(); // 클릭 이벤트 전파 방지
      toggleMute();
    });

    // 문서의 아무 곳을 클릭해도 음소거 해제
    overlay.addEventListener("click", toggleMute);
  </script>
</body>
</html>
    `;

        // Blob 생성
        const blob = new Blob([htmlContent], { type: 'text/html' });
        const blobUrl = URL.createObjectURL(blob);

        // 새로운 창으로 Blob URL 열기
        window.open(blobUrl, "_blank");
    };
    unsafeWindow.openHlsStream = openHlsStream;


    const getBroadAid2 = async (id, broadNumber) => {
        const basePayload = {
            bid: id,
            bno: broadNumber,
            from_api: '0',
            mode: 'landing',
            player_type: 'html5',
            stream_type: 'common',
            quality: 'original'
        };

        // AID 요청 함수
        const requestAid = async (password = '') => {
            const payload = {
                ...basePayload,
                type: 'aid',
                pwd: password
            };
            const options = {
                method: 'POST',
                body: new URLSearchParams(payload),
                credentials: 'include',
                cache: 'no-store'
            };
            const res = await fetch('https://live.sooplive.co.kr/afreeca/player_live_api.php', options);
            return await res.json();
        };

        // LIVE 요청 함수
        const requestLive = async () => {
            const payload = {
                ...basePayload,
                type: 'live',
                pwd: ''
            };
            const options = {
                method: 'POST',
                body: new URLSearchParams(payload),
                credentials: 'include',
                cache: 'no-store'
            };
            const res = await fetch('https://live.sooplive.co.kr/afreeca/player_live_api.php', options);
            return await res.json();
        };

        try {
            // 1차: 비밀번호 없이 AID 요청
            const result1 = await requestAid('');
            if (result1?.CHANNEL?.AID) {
                return result1.CHANNEL.AID;
            }

            // 2차: LIVE 요청으로 BPWD 확인
            const result2 = await requestLive();
            if (result2?.CHANNEL?.BPWD === 'Y') {
                const password = prompt('비밀번호를 입력하세요:');
                if (password === null) return null;

                // 3차: 입력된 비밀번호로 다시 AID 요청
                const retryResult = await requestAid(password);
                if (retryResult?.CHANNEL?.AID) {
                    return retryResult.CHANNEL.AID;
                } else {
                    alert('비밀번호가 틀렸거나 종료된 방송입니다.');
                }
            }

            return null;
        } catch (error) {
            console.log('오류 발생:', error);
            return null;
        }
    };
    unsafeWindow.getBroadAid2 = getBroadAid2;

    const openStreamDownloader = async (id, broadNumber) => {
        const aid = await getBroadAid2(id, broadNumber);
        if (!aid) return;

        const m3u8Url = `https://live-global-cdn-v02.sooplive.co.kr/live-stm-12/auth_playlist.m3u8?aid=${aid}`;
        const baseUrl = m3u8Url.split('/').slice(0, -1).join('/') + '/';

        const html = `
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>다운로드 준비 중... ${id}</title>
  <style>
    body { font-family: sans-serif; padding: 20px; }
    #info { margin-top: 10px; padding: 10px; border: 1px solid #ccc; border-radius: 8px; }
    #info p { margin: 4px 0; }
    label { display: block; margin-top: 10px; }
    input[type="number"] { width: 60px; }
  </style>
</head>
<body>
  <h2>📡 실시간 저장 중...</h2>
  <button id="saveBtn">💾 저장</button>
  <label>
    <input type="checkbox" id="autoSaveCheckbox">
    자동 저장 (저장을 너무 오래 하지 않을 시 작업을 잃을 수 있습니다)
  </label>
  <label>
    간격 (분):
    <input type="number" id="autoSaveInterval" min="1" value="30">
  </label>

  <div id="info">
    <p>⏱️ <span id="elapsedTime">00:00:00</span></p>
    <p>📦 <span id="downloadedSegments">0</span> / <span id="totalSegments">0</span></p>
    <p>🍰 최신 조각: <span id="currentSegment">0</span></p>
    <p>🍰 마지막 저장: <span id="lastSavedSegment">0</span></p>
  </div>

  <script>
    const m3u8Url = ${JSON.stringify(m3u8Url)};
    const baseUrl = ${JSON.stringify(baseUrl)};
    const id = ${JSON.stringify(id)};
    const broadNumber = ${JSON.stringify(broadNumber)};
    let fetchedSet = new Set();
    let downloaded = new Set();
    let stopped = false;
    let lastSavedSegment = 0;
    let segmentQueue = [];
    let autoSave = false;
    let autoSaveInterval = 30 * 60 * 1000; // 30분
    let startTime = Date.now();
    let lastAutoSaveTime = Date.now();
    let maxSegNum = 0;

    const checkbox = document.getElementById("autoSaveCheckbox");
    const intervalInput = document.getElementById("autoSaveInterval");

    checkbox.addEventListener("change", e => {
      autoSave = e.target.checked;
      lastAutoSaveTime = Date.now();
    });

    intervalInput.addEventListener("input", () => {
      const minutes = parseInt(intervalInput.value);
      if (!isNaN(minutes) && minutes > 0) {
        autoSaveInterval = minutes * 60 * 1000;
      }
    });

    document.getElementById("saveBtn").addEventListener("click", () => {
      saveToFile();
    });

    function getTimeString(d = new Date()) {
      d = new Date(d.getTime() + 9 * 60 * 60 * 1000); // KST 보정
      return d.toISOString().slice(0, 19).replace(/[-T:]/g, '');
    }

    function updateInfo() {
      const elapsed = Math.floor((Date.now() - startTime) / 1000);
      const h = String(Math.floor(elapsed / 3600)).padStart(2, '0');
      const m = String(Math.floor((elapsed % 3600) / 60)).padStart(2, '0');
      const s = String(elapsed % 60).padStart(2, '0');

      const downloadedCount = downloaded.size;
      const totalCount = fetchedSet.size;

      // 탭 제목 업데이트
      document.title = \`[\${downloadedCount}/\${totalCount}] \${id}\`;

      // 정보 패널 업데이트
      document.getElementById("elapsedTime").textContent = \`\${h}:\${m}:\${s}\`;
      document.getElementById("totalSegments").textContent = totalCount;
      document.getElementById("downloadedSegments").textContent = downloadedCount;
      document.getElementById("lastSavedSegment").textContent = lastSavedSegment;
      document.getElementById("currentSegment").textContent = maxSegNum;
    }

    async function fetchSegments() {
      try {
        const res = await fetch(m3u8Url + '&_=' + Date.now());
        const text = await res.text();
        const lines = text.split('\\n').map(l => l.trim()).filter(l => l && !l.startsWith('#') && l.includes('.TS'));

        for (const line of lines) {
          const tsUrl = new URL(line, baseUrl).href;
          if (!fetchedSet.has(tsUrl)) {
            fetchedSet.add(tsUrl);
            const match = line.match(/hls_(\\d+)_/i);
            const segNum = match ? parseInt(match[1], 10) : null;
            if (segNum !== null && segNum > lastSavedSegment) {
              maxSegNum = Math.max(maxSegNum, segNum);
              downloadSegment(tsUrl, segNum);
            }
          }
        }
      } catch (err) {
        console.error('m3u8 fetch error', err);
      }
    }

    async function downloadSegment(url, segNum) {
      try {
        const res = await fetch(url);
        if (!res.ok) return;
        const buf = await res.arrayBuffer();
        segmentQueue.push({ segNum, buf });
        downloaded.add(segNum);
        // 다운로드 후 즉시 정보 업데이트를 호출하여 더 빠르게 반영
        updateInfo();
      } catch (err) {
        console.error('segment download error', err);
      }
    }

    function saveToFile() {
      if (segmentQueue.length === 0) {
        alert("저장할 세그먼트가 없습니다.");
        return;
      }

      segmentQueue.sort((a, b) => a.segNum - b.segNum);
      const start = segmentQueue[0].segNum;
      const end = segmentQueue[segmentQueue.length - 1].segNum;
      const timeString = getTimeString();
      const blob = new Blob(segmentQueue.map(s => s.buf), { type: 'video/MP2T' });
      const a = document.createElement('a');
      a.href = URL.createObjectURL(blob);
      a.download = \`\${id}_\${broadNumber}_\${timeString}_\${start}_to_\${end}.ts\`;
      a.click();
      lastSavedSegment = end;
      segmentQueue = [];
      updateInfo();
    }

    setInterval(fetchSegments, 2000);
    setInterval(updateInfo, 1000);
    setInterval(() => {
      if (autoSave && Date.now() - lastAutoSaveTime >= autoSaveInterval) {
        saveToFile();
        lastAutoSaveTime = Date.now();
      }
    }, 5000);
  </script>
</body>
</html>
`;

        const blob = new Blob([html], { type: 'text/html' });
        const url = URL.createObjectURL(blob);
        window.open(url, '_blank');
    };
    unsafeWindow.openStreamDownloader = openStreamDownloader;

    const makeExternalLinks = (thumbsBoxLinks) => {
        for (const thumbsBoxLink of thumbsBoxLinks) {
            if (!thumbsBoxLink.classList.contains("externalPlayer-checked")) {
                thumbsBoxLink.classList.add("externalPlayer-checked");
                const hrefValue = thumbsBoxLink.getAttribute('href');

                if (hrefValue?.includes("play.sooplive.co.kr")) {
                    const [ , , , id, broadNumber] = hrefValue.split('/');

                    thumbsBoxLink.addEventListener('contextmenu', async (event) => {
                        event.preventDefault();
                        event.stopPropagation();
                        const nickname = thumbsBoxLink.parentNode.parentNode.querySelector('.nick').innerText;
                        const aid = await getBroadAid2(id, broadNumber);
                        if (aid){
                            openHlsStream(nickname, `https://live-global-cdn-v02.sooplive.co.kr/live-stm-12/auth_playlist.m3u8?aid=${aid}`);
                        }
                    });
                }
            }
        }
    };

    const getBroadAid = async (id, broadNumber) => {
        const requestOptions = {
            method: 'GET',
            credentials: 'include'
        };

        try {
            const response = await fetch(`https://live.sooplive.co.kr/api/live_status.php?user_id=${id}&broad_no=${broadNumber}&type=play`, requestOptions);
            const result = await response.json();
            return result.data.aid || null;
        } catch (error) {
            console.log('오류 발생:', error);
            return null;
        }
    };

    const getBroadDomain = async (id, broadNumber) => {
        const requestOptions = {
            method: 'GET'
        };

        try {
            const response = await fetch(`https://livestream-manager.sooplive.co.kr/broad_stream_assign.html?return_type=gs_cdn_preview&use_cors=true&cors_origin_url=www.sooplive.co.kr&broad_key=${broadNumber}-common-hd-hls`, requestOptions);
            const result = await response.json();
            return result.view_url || null;
        } catch (error) {
            console.log('오류 발생:', error);
            return null;
        }
    };

    // 최신 프레임 캡처 함수
    const captureLatestFrame = (videoElement) => {
        return new Promise((resolve) => {
            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');

            // 캔버스 크기 설정 (480x270)
            const canvasWidth = 480;
            const canvasHeight = 270;
            canvas.width = canvasWidth;
            canvas.height = canvasHeight;

            // 원본 비디오의 비율을 유지하면서 크기 계산
            const videoRatio = videoElement.videoWidth / videoElement.videoHeight;
            const canvasRatio = canvasWidth / canvasHeight;

            let drawWidth, drawHeight;
            let offsetX = 0, offsetY = 0;

            if (videoRatio > canvasRatio) {
                drawWidth = canvasWidth;
                drawHeight = canvasWidth / videoRatio;
                offsetY = (canvasHeight - drawHeight) / 2;
            } else {
                drawHeight = canvasHeight;
                drawWidth = canvasHeight * videoRatio;
                offsetX = (canvasWidth - drawWidth) / 2;
            }

            // 배경을 검은색으로 채우기
            ctx.fillStyle = 'black';
            ctx.fillRect(0, 0, canvasWidth, canvasHeight);

            // 비디오의 현재 프레임을 캔버스에 그림
            ctx.drawImage(videoElement, offsetX, offsetY, drawWidth, drawHeight);

            // webp 형식으로 변환 후 반환
            const dataURL = canvas.toDataURL('image/webp');
            resolve(dataURL); // 데이터 URL 반환
        });
    };

    // id와 broadNumber로 이미지 데이터 캡처
    const getLatestFrameData = async (id, broadNumber) => {
        const videoElement = document.createElement('video');
        videoElement.playbackRate = 16; // 빠른 재생 속도 설정

        // 병렬로 broadAid와 broadDomain 가져오기
        const [broadAid, broadDomain] = await Promise.all([
            getBroadAid(id, broadNumber),
            getBroadDomain(id, broadNumber)
        ]);

        const m3u8url = `${broadDomain}?aid=${broadAid}`;

        if (Hls.isSupported()) {
            const hls = new Hls();
            hls.loadSource(m3u8url);
            hls.attachMedia(videoElement);

            return new Promise((resolve) => {
                videoElement.addEventListener('canplay', async () => {
                    const frameData = await captureLatestFrame(videoElement);
                    resolve(frameData);
                    videoElement.pause();
                    videoElement.src = '';
                });
            });
        } else {
            console.error('HLS.js를 지원하지 않는 브라우저입니다.');
            return null;
        }
    };

    const replaceThumbnails = (thumbsBoxLinks) => {
        for (const thumbsBoxLink of thumbsBoxLinks) {
            if (!thumbsBoxLink.classList.contains("thumbnail-checked")) {
                thumbsBoxLink.classList.add("thumbnail-checked");
                const hrefValue = thumbsBoxLink.getAttribute('href');

                if (hrefValue && hrefValue.includes("sooplive.co.kr")) {
                    const [ , , , id, broadNumber] = hrefValue.split('/');

                    thumbsBoxLink.dataset.lastMouseEnterTime = 0;

                    thumbsBoxLink.addEventListener('mouseenter', async function(event) {
                        event.preventDefault();
                        event.stopPropagation();

                        const currentTime = Date.now();
                        const lastMouseEnterTime = Number(thumbsBoxLink.dataset.lastMouseEnterTime);

                        if (currentTime - lastMouseEnterTime >= 30000) {
                            thumbsBoxLink.dataset.lastMouseEnterTime = currentTime;

                            const frameData = await getLatestFrameData(id, broadNumber);
                            let imgElement = thumbsBoxLink.querySelector('img');

                            if (!imgElement) {
                                imgElement = document.createElement('img');
                                thumbsBoxLink.appendChild(imgElement);
                            }
                            imgElement.src = frameData;
                        }
                    });
                }
            }
        }
    };

    // 모달 생성 함수 먼저 정의
    const createModal = () => {

        if (!CURRENT_URL.startsWith("https://www.sooplive.co.kr")){
            return false;
        }

        window.onclick = function(event) {
            if (event.target === modal) {
                closeModal(modal, modalElements.videoPlayer);
            }
        };

        const modal = document.createElement('div');
        modal.className = 'preview-modal';

        const modalContent = document.createElement('div');
        modalContent.className = 'preview-modal-content';

        const closeButton = document.createElement('span');
        closeButton.className = 'preview-close';
        closeButton.innerHTML = '&times;';
        closeButton.onclick = () => closeModal(modal, modalElements.videoPlayer); // closeButton에 클릭 이벤트 설정

        const videoPlayer = document.createElement('video');
        videoPlayer.controls = true;

        const infoContainer = document.createElement('div');
        infoContainer.className = 'info';

        // 방송 정보 및 태그 추가
        const streamerName = document.createElement('div');
        streamerName.className = 'streamer-name';

        const videoTitle = document.createElement('div');
        videoTitle.className = 'video-title';

        const tagsContainer = document.createElement('div');
        tagsContainer.className = 'tags';

        const startButton = document.createElement('a');
        startButton.className = 'start-button';
        startButton.textContent = '참여하기 >'; // 버튼 텍스트 설정

        infoContainer.append(streamerName, tagsContainer, videoTitle, startButton);
        modalContent.append(closeButton, videoPlayer, infoContainer);
        modal.appendChild(modalContent);
        document.body.appendChild(modal);

        return { modal, videoPlayer, streamerName, videoTitle, tagsContainer, startButton };
    };


    // 전역 변수에 modalElements 저장
    const modalElements = createModal(); // 모달을 한 번 생성하고 변수에 저장

    const makePreviewModalContents = (thumbsBoxLinks) => {
        for (const thumbsBoxLink of thumbsBoxLinks) {
            if (!thumbsBoxLink.classList.contains("preview-checked")) {
                thumbsBoxLink.classList.add("preview-checked");
                const hrefValue = thumbsBoxLink.getAttribute('href');

                if (hrefValue?.includes("play.sooplive.co.kr")) {
                    const [ , , , id, broadNumber] = hrefValue.split('/');

                    thumbsBoxLink.addEventListener('click', async (event) => {
                        event.preventDefault();
                        event.stopPropagation();

                        // 모달이 이미 표시된 경우 클릭 처리하지 않음
                        if (modalElements.modal.style.display === 'block') return;

                        await handleLinkClick(id, broadNumber, thumbsBoxLink);
                        modalElements.modal.style.display = 'block';
                    });
                }
            }
        }
    };

    const closeModal = (modal, videoPlayer) => {
        modal.style.display = 'none';
        videoPlayer.pause(); // 비디오 정지
        videoPlayer.src = ''; // 소스 초기화
    };

    const handleLinkClick = async (id, broadNumber, thumbsBoxLink) => {
        const playerLink = `https://play.sooplive.co.kr/${id}/${broadNumber}`;

        try {
            // 병렬로 broadAid와 broadDomain 가져오기
            const [broadAid, broadDomain] = await Promise.all([
                getBroadAid(id, broadNumber),
                getBroadDomain(id, broadNumber)
            ]);

            if (broadAid && broadDomain) {
                const m3u8url = `${broadDomain}?aid=${broadAid}`;
                // 내용 업데이트
                updateModalContent(m3u8url, playerLink, thumbsBoxLink);
            } else {
                console.error('Invalid broadAid or broadDomain');
            }
        } catch (error) {
            console.error('Error fetching broadcast information:', error);
        }
    };

    const updateModalContent = (m3u8url, playerLink, thumbsBoxLink) => {
        const { videoPlayer, streamerName, videoTitle, tagsContainer, startButton } = modalElements;
        const hrefTarget = isOpenNewtabEnabled ? "_blank" : "_self";

        // 방송 정보 업데이트
        const parent = thumbsBoxLink.parentNode.parentNode;
        streamerName.textContent = parent.querySelector('.nick').innerText; // 스트리머 이름
        videoTitle.textContent = parent.querySelector('.title a').innerText; // 방송 제목

        // 태그 추가
        updateTags(tagsContainer, thumbsBoxLink);

        // startButton의 href 업데이트
        startButton.setAttribute('href', playerLink);
        startButton.setAttribute('target', hrefTarget);

        // 비디오 표시 및 재생
        const playVideo = () => {
            videoPlayer.style.display = 'block'; // 비디오 표시
            videoPlayer.play(); // 비디오 재생 시작
        };

        // HLS.js를 사용하여 m3u8 재생 설정
        const initializeHLS = () => {
            const hls = new Hls();
            hls.loadSource(m3u8url);
            hls.attachMedia(videoPlayer);
            hls.on(Hls.Events.MANIFEST_PARSED, playVideo);
            hls.on(Hls.Events.ERROR, (event, data) => {
                console.error('HLS error: ', data);
            });
        };

        const handleSafariSupport = () => {
            videoPlayer.src = m3u8url;
            videoPlayer.addEventListener('loadedmetadata', playVideo);
            videoPlayer.addEventListener('error', () => {
                console.error('Video playback error');
                alert('비디오를 로드하는 데 오류가 발생했습니다.');
            });
        };

        // HLS 지원 여부에 따라 초기화
        if (Hls.isSupported()) {
            initializeHLS();
        } else if (videoPlayer.canPlayType('application/vnd.apple.mpegurl')) {
            handleSafariSupport();
        } else {
            console.error('이 브라우저는 HLS를 지원하지 않습니다.');
            alert('이 브라우저는 HLS 비디오를 지원하지 않습니다.');
        }
    };

    // 기존 updateTags 함수 유지
    const updateTags = (tagsContainer, thumbsBoxLink) => {
        const tags = thumbsBoxLink.parentNode.parentNode.querySelectorAll('.tag_wrap a');
        tagsContainer.innerHTML = ''; // 이전 태그 제거
        tags.forEach(tag => {
            const tagElement = document.createElement('a');
            tagElement.textContent = tag.innerText;
            tagElement.href = tag.getAttribute("class") === "category"
                ? `https://www.sooplive.co.kr/directory/category/${encodeURIComponent(tag.innerText)}/live`
            : `https://www.sooplive.co.kr/search?hash=hashtag&tagname=${encodeURIComponent(tag.innerText)}&hashtype=live&stype=hash&acttype=live&location=live_main&inflow_tab=`;

            tagsContainer.appendChild(tagElement);
        });
    };

    const removeUnwantedTags = () =>{
        if (isRemoveCarouselEnabled) {
            GM_addStyle(`
                div[class^="player_player_wrap"] {
                    display: none !important;
                }
            `);
        }

        if (isRemoveRedistributionTagEnabled) {
            GM_addStyle(`
                [data-type=cBox] .thumbs-box .allow {
                    display: none !important;
                }
            `);
        }

        if (isRemoveWatchLaterButtonEnabled) {
            GM_addStyle(`
                [data-type=cBox] .thumbs-box .later {
                    display: none !important;
                }
            `);
        }

        if (isRemoveBroadStartTimeTagEnabled) {
            GM_addStyle(`
                [data-type=cBox] .thumbs-box .time {
                    display: none !important;
                }
            `);
        }

        if (isBroadTitleTextEllipsisEnabled) {
            GM_addStyle(`
                [data-type=cBox] .cBox-info .title a {
                    white-space: nowrap;
                    text-overflow: ellipsis;
                    display: inline-block;
                }
            `);
        }
    }

    const processStreamers = () => {
        const processedLayers = new Set(); // 처리된 레이어를 추적

        const createStreamDownloadButton_MainPage = (listItem, optionsLayer) => {
            const downloadButton = document.createElement('button'); // "스트림 다운로드" 버튼 생성
            downloadButton.type = 'button';

            const span = document.createElement('span');
            span.textContent = '스트림 다운로드';

            downloadButton.appendChild(span);

            // 클릭 이벤트 추가
            downloadButton.addEventListener('click', () => {
                const userIdElement = listItem.querySelector('.cBox-info .title a');

                if (userIdElement) {
                    const urlParts = userIdElement.href.split('/');
                    const id = urlParts[3];
                    const broadNumber = urlParts[4];

                    if (id) {
                        if (id === "player") alert('VOD는 아직 지원하지 않습니다.');
                        openStreamDownloader(id, broadNumber); // 스트리밍 다운로드 함수 호출
                    } else {
                        //console.log("ID를 찾을 수 없습니다.");
                    }
                } else {
                    //console.log("userIdElement 또는 broadNumber를 찾을 수 없습니다.");
                }
            });

            // 리스트 맨 앞에 삽입
            optionsLayer.insertBefore(downloadButton, optionsLayer.firstChild);
        };

        // 버튼 생성 및 클릭 이벤트 처리
        const createHideButton = (listItem, optionsLayer) => {
            const hideButton = document.createElement('button'); // "숨기기" 버튼 생성
            hideButton.type = 'button';

            const span = document.createElement('span');
            span.textContent = '이 브라우저에서 스트리머 숨기기';

            hideButton.appendChild(span);

            // 클릭 이벤트 추가
            hideButton.addEventListener('click', () => {
                const userNameElement = listItem.querySelector('a.nick > span'); // 사용자 이름 요소
                const userIdElement = listItem.querySelector('.cBox-info > a'); // 사용자 ID 요소

                if (userNameElement && userIdElement) {
                    const userId = userIdElement.href.split('/')[3]; // 사용자 ID 추출
                    const userName = userNameElement.innerText; // 사용자 이름 추출

                    //console.log(`Blocking user: ${userName}, ID: ${userId}`); // 로그 추가

                    if (userId && userName) {
                        blockUser(userName, userId); // 사용자 차단 함수 호출
                        listItem.style.display = 'none';
                    }
                } else {
                    //console.log("User elements not found."); // 요소가 없을 경우 로그 추가
                }
            });

            optionsLayer.appendChild(hideButton); // 옵션 레이어에 버튼 추가
        };

        const createCategoryHideButton = (listItem, optionsLayer) => {
            const hideButton = document.createElement('button'); // "숨기기" 버튼 생성
            hideButton.type = 'button';

            const span = document.createElement('span');
            span.textContent = '이 브라우저에서 해당 카테고리 숨기기';

            hideButton.appendChild(span);

            // 클릭 이벤트 추가 [data-type=cBox] .cBox-info .tag_wrap a.category
            hideButton.addEventListener('click', () => {
                const categoryElement = listItem.querySelector('.cBox-info .tag_wrap a.category');

                if (categoryElement) {
                    const categoryName = categoryElement.textContent;
                    const categoryNo = getCategoryNo(categoryName);
                    if (categoryName && categoryNo) {
                        blockCategory(categoryName, categoryNo);
                    }
                } else {
                    //console.log("User elements not found."); // 요소가 없을 경우 로그 추가
                }
            });

            optionsLayer.appendChild(hideButton); // 옵션 레이어에 버튼 추가
        }

        // DOM 변경 감지 및 처리
        const handleDOMChange = (mutationsList) => {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    const moreOptionsContainer = document.querySelector('div._moreDot_wrapper'); // 추가 옵션 컨테이너
                    const optionsLayer = moreOptionsContainer ? moreOptionsContainer.querySelector('div._moreDot_layer') : null; // 옵션 레이어

                    if (optionsLayer && optionsLayer.style.display !== 'none' && !processedLayers.has(optionsLayer)) {
                        const activeButton = document.querySelector('button.more_dot.on'); // 활성화된 버튼
                        const listItem = activeButton.closest('li[data-type="cBox"]'); // 가장 가까운 리스트 아이템 찾기

                        if (listItem) {
                            if (isStreamDownloadEnabled) createStreamDownloadButton_MainPage(listItem, optionsLayer);
                            createHideButton(listItem, optionsLayer); // 숨기기 버튼 생성
                            createCategoryHideButton(listItem, optionsLayer);
                            processedLayers.add(optionsLayer); // 이미 처리된 레이어로 추가
                        }
                    } else if (!optionsLayer) {
                        processedLayers.clear(); // 요소가 없을 때 처리된 레이어 초기화
                    }

                    // cBox-list의 리스트 아이템 처리
                    const cBoxListItems = document.querySelectorAll('div.cBox-list li[data-type="cBox"]:not(.hide-checked)');

                    // cBoxListItems를 for...of 루프로 반복
                    for (const listItem of cBoxListItems) {
                        listItem.classList.add('hide-checked');
                        const userIdElement = listItem.querySelector('.cBox-info > a'); // 사용자 ID 요소
                        const categoryElement = listItem.querySelector('.cBox-info .tag_wrap a.category');
                        const titleElement = listItem.querySelector('.cBox-info .title a');

                        if (userIdElement) {
                            const userId = userIdElement.href.split('/')[3]; // 사용자 ID 추출

                            // 차단된 사용자일 경우 li 삭제
                            if (isUserBlocked(userId)) {
                                listItem.style.display = 'none';
                                //console.log(`Removed blocked user with ID: ${userId}`); // 로그 추가
                            }
                        }

                        if (categoryElement) {
                            const categoryName = categoryElement.textContent;
                            if (isCategoryBlocked(getCategoryNo(categoryName))) {
                                listItem.style.display = 'none';
                                //console.log(`Removed blocked category with Name: ${categoryName}`); // 로그 추가
                            }
                        }

                        if (titleElement) {
                            const broadTitle = titleElement.textContent;

                            // blockedWords에 포함된 단어가 broadTitle에 있는지 체크
                            for (const word of blockedWords) {
                                if (broadTitle.toLowerCase().includes(word.toLowerCase())) {
                                    listItem.style.display = 'none';
                                    //console.log(`Removed item with blocked word in title: ${broadTitle}`); // 로그 추가
                                    break; // 하나의 차단 단어가 발견되면 더 이상 확인할 필요 없음
                                }
                            }
                        }
                    }

                    // 프리뷰 모달 사용
                    if (isPreviewModalEnabled) {
                        const allThumbsBoxLinks = document.querySelectorAll('[data-type=cBox] .thumbs-box > a[href]:not([href^="https://vod.sooplive.co.kr"])');
                        if (allThumbsBoxLinks.length) makePreviewModalContents(allThumbsBoxLinks);
                    }

                    // 외부 재생기 사용
                    if (isOpenExternalPlayerEnabled) {
                        const allThumbsBoxLinks = document.querySelectorAll('[data-type=cBox] .thumbs-box > a[href]:not([href^="https://vod.sooplive.co.kr"])');
                        if (allThumbsBoxLinks.length) makeExternalLinks(allThumbsBoxLinks);
                    }

                    // 빈 썸네일 대체
                    if (isReplaceEmptyThumbnailEnabled){
                        const noThumbsBoxLinks = document.querySelectorAll('[data-type=cBox] .thumbs-box > a[href].thumb-adult:not([href^="https://vod.sooplive.co.kr"])');
                        if (noThumbsBoxLinks.length) replaceThumbnails(noThumbsBoxLinks);
                    }

                }
            }
        };

        const observer = new MutationObserver(handleDOMChange); // DOM 변경 감지기

        // 감지할 옵션 설정
        const config = { childList: true, subtree: true };

        // 관찰 시작
        observer.observe(document.body, config);
    };


    //=================================메인 페이지 함수 끝=================================//


    //=================================플레이어 페이지 함수=================================//

    const createStreamDownloadButton_LivePage = () => {
        const moreDotLayers = document.querySelectorAll('._moreDot_layer');

        moreDotLayers.forEach(moreDotLayer => {
            // 이미 버튼이 있으면 중복 삽입 방지
            if (moreDotLayer.querySelector('.stream-download')) return;

            const downloadButton = document.createElement('button');
            downloadButton.type = 'button';
            downloadButton.textContent = '스트림 다운로드';
            downloadButton.className = 'stream-download';

            downloadButton.addEventListener('click', () => {
                const match = location.href.match(/\/([^/]+)\/(\d+)/);
                if (!match) {
                    console.warn("URL에서 id 또는 broadNumber를 추출할 수 없습니다.");
                    return;
                }

                const id = match[1];
                const broadNumber = match[2];

                openStreamDownloader(id, broadNumber);
            });

            moreDotLayer.insertBefore(downloadButton, moreDotLayer.firstChild);
        });
    };

    const showSidebarOnMouseOver = () => {
        const sidebar = document.getElementById('sidebar');
        const videoLayer = document.getElementById('player');
        const webplayerContents = document.getElementById('webplayer');
        const body = document.body;

        const handleSidebarMouseOver = () => {
            if (body.classList.contains('screen_mode') && !body.classList.contains('showSidebar')) {
                body.classList.add('showSidebar');
                webplayerContents.style.left = sidebar.offsetWidth + 'px';
                webplayerContents.style.width = `calc(100vw - ${sidebar.offsetWidth}px)`;
            }
        };

        const handleSidebarMouseOut = () => {
            if (body.classList.contains('screen_mode') && body.classList.contains('showSidebar')) {
                body.classList.remove('showSidebar');
                webplayerContents.style.left = '0px';
                webplayerContents.style.width = '100vw';
            }
        };

        const mouseMoveHandler = (event) => {
            const mouseX = event.clientX;
            const mouseY = event.clientY;

            if (!body.classList.contains('showSidebar')) {
                if (mouseX < 52 && mouseY < videoLayer.clientHeight - 150) {
                    handleSidebarMouseOver();
                }
            } else {
                if (mouseX < sidebar.clientWidth && mouseY < sidebar.clientHeight) {
                    handleSidebarMouseOver();
                } else {
                    handleSidebarMouseOut();
                }
            }
        };

        const windowMouseOutHandler = (event) => {
            if (!event.relatedTarget && !event.toElement) {
                handleSidebarMouseOut();
            }
        };

        document.addEventListener('mousemove', mouseMoveHandler);
        window.addEventListener('mouseout', windowMouseOutHandler); // 창 벗어남 감지
    };

    const toggleExpandChatShortcut = () => {
        setupKeydownHandler("KeyX", toggleExpandChat); // X 키
    };

    const toggleSharpModeShortcut = () => {
        setupKeydownHandler("KeyE", togglesharpModeCheck); // E 키
        updateLabel('clear_screen', '선명한 모드', '선명한 모드(e)');
    };

    const toggleLowLatencyShortcut = () => {
        setupKeydownHandler("KeyD", toggleDelayCheck); // D 키
        updateLabel('delay_check', '시차 단축', '시차 단축(d)');
    };

    const setupKeydownHandler = (targetCode, toggleFunction) => {
        document.addEventListener('keydown', (event) => {
            const active = document.activeElement;
            const tag = active?.tagName?.toUpperCase();

            const isEditable = (
                tag === 'INPUT' ||
                tag === 'TEXTAREA' ||
                active?.isContentEditable ||
                active?.id === 'write_area'
            );

            if (event.code === targetCode && !isEditable) {
                toggleFunction();
            }
        }, true); // 캡처링 단계에서 이벤트 수신
    };

    const updateLabel = (forId, oldText, newText) => {
        const labelElement = document.body.querySelector(`#player label[for="${forId}"]`);
        if (labelElement) {
            labelElement.innerHTML = labelElement.innerHTML.replace(oldText, newText);
        } else {
            console.error('Label element not found.');
        }
    };

    function toggleExpandChat() {
        if (!isElementVisible('.expand-toggle-li')) {
            return;
        }
        waitForElement('.expand-toggle-li a', function (elementSelector, element) {
            element.click();
        })
    }

    const togglesharpModeCheck = () => {
        const sharpModeCheckElement = document.getElementById('clear_screen');
        sharpModeCheckElement.click();
        showPlayerBar(69); // E 키
    };

    const toggleDelayCheck = () => {
        if (isAdjustDelayNoGridEnabled) {
            moveToLatestBufferedPoint();
        } else {
            const delayCheckElement = document.getElementById('delay_check');
            delayCheckElement.click();
            showPlayerBar(68); // D 키
        }
    };

    const showPlayerBar = (keyCode) => {
        const player = document.getElementById('player');
        player.classList.add('mouseover');

        let settingButton, settingBoxOn;
        if (keyCode === 69) { // E 키
            settingButton = document.body.querySelector('#player button.btn_quality_mode');
            settingBoxOn = document.body.querySelector('.quality_box.on');
        } else if (keyCode === 68) { // D 키
            settingButton = document.body.querySelector('#player button.btn_setting');
            settingBoxOn = document.body.querySelector('.setting_box.on');
        }

        if (settingButton) {
            if (!settingBoxOn) {
                settingButton.click();
            }
            setTimeout(() => {
                if (settingBoxOn) {
                    settingButton.click();
                }
                player.classList.remove('mouseover');
            }, 1000); // 1초 후에 mouseover 클래스 제거
        } else {
            console.error('Setting button not found or not visible.');
        }
    };

    // 비디오의 가장 최신 버퍼링 지점에서 2초 전으로 이동 (현재 지점보다 오래된 지점으로는 이동하지 않음)
    const moveToLatestBufferedPoint = () => {
        const video = document.querySelector('video');
        const buffered = video.buffered;

        if (buffered.length > 0) {
            // 버퍼링된 구간의 마지막 시간
            const bufferedEnd = buffered.end(buffered.length - 1);
            const targetTime = bufferedEnd - 2; // 2초 전으로 설정

            // targetTime이 현재 시간보다 뒤에 있을 경우에만 이동
            if (targetTime > video.currentTime) {
                video.currentTime = targetTime;
            }
        }
    }

    const checkPlayerPageHeaderAd = () => {
        waitForElement('#header_ad', function (elementSelector, element) {
            element.remove();
        })
    }

    const extractDateTime = (text) => {
        const [dateStr, timeStr] = text.split(' '); // split 한 번으로 날짜와 시간을 동시에 얻기
        const dateTimeStr = `${dateStr}T${timeStr}Z`; // 문자열 템플릿 사용
        return new Date(dateTimeStr);
    }

    const getElapsedTime = (broadcastStartTimeText, type) => {
        const broadcastStartTime = extractDateTime(broadcastStartTimeText);
        broadcastStartTime.setHours(broadcastStartTime.getHours() - 9);
        const currentTime = new Date();
        const timeDiff = currentTime - broadcastStartTime;

        const secondsElapsed = Math.floor(timeDiff / 1000);
        const hoursElapsed = Math.floor(secondsElapsed / 3600);
        const minutesElapsed = Math.floor((secondsElapsed % 3600) / 60);
        const remainingSeconds = secondsElapsed % 60;
        let formattedTime = '';

        if (type === "HH:MM:SS") {
            formattedTime = `${String(hoursElapsed).padStart(2, '0')}:${String(minutesElapsed).padStart(2, '0')}:${String(remainingSeconds).padStart(2, '0')}`;
        } else if (type === "HH:MM") {
            if (hoursElapsed > 0) {
                formattedTime = `${String(hoursElapsed)}시간 `;
            }
            formattedTime += `${String(minutesElapsed)}분`;
        }
        return formattedTime;
    }

    // remainingBufferTime을 계산하여 리턴하는 함수
    const getRemainingBufferTime = (video) => {
        const buffered = video.buffered;
        if (buffered.length > 0) {
            // 마지막 버퍼의 끝과 현재 시간의 차이를 계산
            const remainingBufferTime = buffered.end(buffered.length - 1) - video.currentTime;

            // 0초 또는 정수일 경우 소수점 한 자리로 반환
            return remainingBufferTime >= 0
                ? remainingBufferTime.toFixed(remainingBufferTime % 1 === 0 ? 0 : 1)
            : '';
        }
        return ''; // 버퍼가 없으면 빈 문자열 반환
    };

    // remainingBufferTime 값을 삽입하는 함수
    const insertRemainingBuffer = (element) => {
        const video = element;
        const emptyChat = document.body.querySelector('#empty_chat');

        // video의 onprogress 이벤트 핸들러
        video.onprogress = () => {
            const remainingBufferTime = getRemainingBufferTime(video); // remainingBufferTime 계산
            if (emptyChat && remainingBufferTime !== '') {
                emptyChat.innerText = `${remainingBufferTime}s 지연됨`;
            }
        };

    };

    const handleMuteByVisibility = () => {
        const button = document.body.querySelector("#btn_sound");

        if (document.hidden) {
            // 탭이 비활성화됨
            if (!button.classList.contains("mute")) {
                button.click();
                // console.log("탭이 비활성화됨, 음소거");
            }
        } else {
            // 탭이 활성화됨
            if (button.classList.contains("mute")) {
                button.click();
                // console.log("탭이 활성화됨, 음소거 해제");
            }
        }
    }

    const isVideoInPiPMode = () => {
        const videoElement = document.body.querySelector('video');
        return videoElement && document.pictureInPictureElement === videoElement;
    }

    const registerVisibilityChangeHandler = () => {
        document.addEventListener('visibilitychange', () => {
            if (!isVideoInPiPMode() && isAutoChangeMuteEnabled) {
                handleMuteByVisibility();
            }
        }, true);
    }

    const appendPauseButton = () => {
        try {
            const checkInterval = 250;
            let elapsedTime = 0;
            let intervalId;
            let buttonCreated = false; // 버튼 생성 여부 체크 변수 추가

            const checkLiveViewStatus = () => {
                const closeStreamButton = document.body.querySelector("#closeStream");
                const playerDiv = document.body.querySelector("#player");
                const isPlayerPresent = !!playerDiv; // null 체크를 간단히
                const isMouseoverClass = isPlayerPresent && playerDiv.classList.contains("mouseover");
                const isTimeover = isPlayerPresent && (elapsedTime > 30);

                if (closeStreamButton) {
                    // 버튼이 이미 존재하면 체크
                    if (!isMouseoverClass && !isTimeover) {
                        // 마우스 오버 상태가 아니고 시간 초과가 아닐 때 버튼 제거
                        closeStreamButton.remove();
                        buttonCreated = false; // 버튼이 제거됨
                    }
                } else if ((!closeStreamButton && isMouseoverClass) || isTimeover) {
                    // 버튼이 없고 마우스 오버 상태이거나 시간 초과일 때만 생성
                    if (!buttonCreated) {
                        createCloseStreamButton();
                        buttonCreated = true; // 버튼이 생성됨
                    }
                }

                elapsedTime += checkInterval / 1000; // 초 단위로 변환하여 증가
            };

            const createCloseStreamButton = () => {
                waitForElement('button#time_shift_play', (elementSelector, element) => {
                    if (window.getComputedStyle(element).display === 'none') { // Time Shift 기능이 비활성화된 경우
                        const ctrlDiv = document.body.querySelector('div.ctrl');
                        const newCloseStreamButton = document.createElement("button");
                        newCloseStreamButton.type = "button";
                        newCloseStreamButton.id = "closeStream";
                        newCloseStreamButton.className = "pause on";

                        const tooltipDiv = document.createElement("div");
                        tooltipDiv.className = "tooltip";
                        const spanElement = document.createElement("span");
                        spanElement.textContent = "일시정지";

                        tooltipDiv.appendChild(spanElement);
                        newCloseStreamButton.appendChild(tooltipDiv);
                        ctrlDiv.insertBefore(newCloseStreamButton, ctrlDiv.firstChild);

                        newCloseStreamButton.addEventListener("click", (e) => {
                            e.preventDefault();
                            toggleStream(newCloseStreamButton, spanElement);
                        });
                    }
                });
            };

            const toggleStream = (button, spanElement) => {
                try {
                    if (button.classList.contains("on")) {
                        unsafeWindow.livePlayer.closeStreamConnector();
                        button.classList.remove("on", "pause");
                        button.classList.add("off", "play");
                        spanElement.textContent = "재생";
                    } else {
                        unsafeWindow.livePlayer._startBroad();
                        button.classList.remove("off", "play");
                        button.classList.add("on", "pause");
                        spanElement.textContent = "일시정지";
                    }
                } catch (error) {
                    console.log(error);
                }
            };

            // setInterval을 사용해 일정 간격으로 체크
            intervalId = setInterval(checkLiveViewStatus, checkInterval);
        } catch (error) {
            console.error(error);
        }
    };

    const detectPlayerChangeAndAppendPauseButton = () => {

        const updateBjIdIfMismatch = () => {
            const currentUrl = window.location.href;
            const urlBjId = currentUrl.split('/')[3];
            const infoNickName = document.querySelector('#infoNickName');
            const dataBjId = infoNickName.getAttribute('data-bj_id');
            const streamerNick = document.querySelector('#streamerNick');

            if (dataBjId !== urlBjId) {
                infoNickName.setAttribute('data-bj_id', urlBjId);
                streamerNick.setAttribute('data-bj_id', urlBjId);
            }
        };

        const handleMutations = (mutationsList) => {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    appendPauseButton();
                    emptyViewStreamer();
                    updateBjIdIfMismatch();
                }
            }
        };

        appendPauseButton();

        const targetNode = document.body.querySelector('#infoNickName');
        const observer = new MutationObserver(handleMutations);

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

    const emptyViewStreamer = () => {
        const viewStreamer = document.getElementById('view_streamer');
        if (viewStreamer) {
            viewStreamer.innerHTML = '';
        }
    }

    const setWidthNickname = (wpx) => {
        if (typeof wpx === 'number' && wpx > 0) { // wpx가 유효한 값인지 확인
            GM_addStyle(`
            .starting-line .chatting-list-item .message-container .username {
                width: ${wpx}px !important;
            }
        `);
        } else {
            console.warn('Invalid width value provided for setWidthNickname.'); // 유효하지 않은 값 경고
        }
    }

    const hideBadges = () => {
        const badgeSettings = [
            { key: 'isHideSupporterBadgeEnabled', className: 'support' },
            { key: 'isHideFanBadgeEnabled', className: 'fan' },
            { key: 'isHideSubBadgeEnabled', className: 'sub' },
            { key: 'isHideVIPBadgeEnabled', className: 'vip' },
            { key: 'isHideManagerBadgeEnabled', className: 'manager' },
            { key: 'isHideStreamerBadgeEnabled', className: 'streamer' }
        ];

        // 각 배지 숨김 설정 값 가져오기
        const settings = badgeSettings.map(setting => ({
            key: setting.key,
            enabled: GM_getValue(setting.key),
            className: setting.className
        }));

        // 모든 배지 숨김 설정이 비활성화된 경우 종료
        if (!settings.some(setting => setting.enabled)) {
            return;
        }

        // 활성화된 설정에 대한 CSS 규칙 생성
        let cssRules = settings
        .filter(setting => setting.enabled)
        .map(setting => `[class^="grade-badge-${setting.className}"] { display: none !important; }`)
        .join('\n');

        // 서브 배지용 CSS 규칙 추가
        if (settings.find(s => s.className === 'sub' && s.enabled)) {
            const thumbSpanSelector = CURRENT_URL.startsWith("https://play.sooplive.co.kr/")
            ? '#chat_area div.username > button > span.thumb'
            : '#chatMemo div.username > button > span.thumb';
            cssRules += `\n${thumbSpanSelector} { display: none !important; }`;
        }

        // CSS 규칙 한 번만 적용
        GM_addStyle(cssRules);
    };

    // 디바운스 함수 구현
    const debounce = (func, wait) => {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    };

    // registeredWords 배열 초기화 - 여러 번 실행되지 않도록 전역에서 한 번만 설정
    const rw = registeredWords ? registeredWords.split(',').map(word => word.trim()).filter(Boolean) : [];

    const observeChat = (elementSelector, elem) => {
        hideBadges();
        if (!isBlockWordsEnabled) return;
        if (rw.length === 0) return;

        const observer = new MutationObserver((mutations) => {
            mutations.forEach(({ addedNodes }) => {
                addedNodes.forEach(node => {
                    if (node.nodeType !== Node.ELEMENT_NODE) return;

                    // 메시지 요소를 직접 감지하거나 하위에서 찾음
                    const message = node.matches?.('div.message-text > p.msg')
                    ? node
                    : node.querySelector?.('div.message-text > p.msg');

                    if (message) {
                        deleteMessages([message]);
                    }
                });
            });
        });

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

    const deleteMessages = (messages) => {
        if (!Array.isArray(messages) || messages.length === 0) return;

        // 필터 단어가 없으면 아무것도 하지 않음
        if (rw.length === 0) return;

        for (const message of messages) {
            const messageText = message.textContent.trim();
            let shouldRemove = false;

            for (const word of rw) {
                const isExactCheck = word.startsWith("e:");
                const wordToCheck = isExactCheck ? word.slice(2) : word;

                if ((isExactCheck && messageText === wordToCheck) ||
                    (!isExactCheck && messageText.includes(wordToCheck))) {
                    shouldRemove = true;
                    break;
                }
            }

            if (shouldRemove) {
                const listItem = message.closest('.chatting-list-item');
                if (listItem) {
                    listItem.remove();
                }
            }
        }
    };

    const autoClaimGem = () => {
        const element = document.querySelector('#actionbox > div.ic_gem');

        // 요소가 존재하고, display 속성이 'none'이 아닌 경우 클릭
        if (element && getComputedStyle(element).display !== 'none') {
            element.click();
        }
    }

    // 비디오 재생 건너뛰기 및 입력란 확인 함수
    const videoSkipHandler = (e) => {
        const activeElement = document.activeElement;
        const tagName = activeElement.tagName.toLowerCase();

        // 입력란 활성화 여부 체크
        const isInputActive = (tagName === 'input') ||
              (tagName === 'textarea') ||
              (activeElement.id === 'write_area') ||
              (activeElement.contentEditable === 'true');

        // 입력란이 활성화되어 있지 않은 경우 비디오 제어
        if (!isInputActive) {
            const video = document.querySelector('video');
            if (video) {
                switch (e.code) {
                    case 'ArrowRight':
                        // 오른쪽 방향키: 동영상을 1초 앞으로 이동
                        video.currentTime += 1;
                        break;
                    case 'ArrowLeft':
                        // 왼쪽 방향키: 동영상을 1초 뒤로 이동
                        video.currentTime -= 1;
                        break;
                }
            }
        }
    }

    const homePageCurrentTab = () => {
        waitForElement('#logo > a', function (elementSelector, element) {
            element.removeAttribute("target");
        });
    }

    const useBottomChat = () => {
        const toggleBottomChat = () => {
            const playerArea = document.querySelector('#player_area');
            if (!playerArea) {
                console.warn('#player_area 요소를 찾을 수 없습니다.');
                return;
            }

            const playerHeight = playerArea.getBoundingClientRect().height;
            const browserHeight = window.innerHeight;

            const isPortrait = window.innerHeight * 1.1 > window.innerWidth;

            document.body.classList.toggle('bottomChat', isPortrait);
        };

        window.addEventListener('resize', debounce(toggleBottomChat, 500));
        toggleBottomChat();
    };


    // #nAllViewer의 숫자를 변환하여 리턴하는 함수
    const getViewersNumber = (raw = false) => {
        const element = document.querySelector('#nAllViewer');

        if (!element) return '0';

        // 요소의 텍스트에서 쉼표 제거 후 숫자 처리
        const rawNumber = element.innerText.replace(/,/g, '').trim();

        // raw가 truthy한 값이면 (1, true 등) 숫자를 변환하지 않고 원본 그대로 반환
        if (Boolean(raw)) {
            return rawNumber;
        }

        return addNumberSeparator(rawNumber);
    };

    let previousViewers = 0; // 이전 시청자 수를 저장할 변수
    let previousTitle = ''; // 이전 제목을 저장할 변수

    // 제목 표시줄을 업데이트하는 함수
    const updateTitleWithViewers = () => {
        const originalTitle = document.title.split(' ')[0]; // 기존 제목의 첫 번째 단어
        const viewers = getViewersNumber(true); // 현재 시청자 수 갱신
        const formattedViewers = addNumberSeparatorAll(viewers); // 형식화된 시청자 수
        let title = originalTitle;

        if (originalTitle !== previousTitle) {
            previousViewers = 0; // 제목이 변경되면 이전 시청자 수 초기화
        }

        if (viewers && previousViewers) {
            if (viewers > previousViewers) {
                title += ` 🔺${formattedViewers}`;
            } else if (viewers < previousViewers) {
                title += ` 🔻${formattedViewers}`;
            } else {
                title += ` • ${formattedViewers}`; // 시청자 수가 변동 없을 때
            }
        } else {
            title += ` • ${formattedViewers}`; // 시청자 수가 변동 없을 때
        }

        document.title = title; // 제목을 업데이트
        previousViewers = viewers; // 이전 시청자 수 업데이트
        previousTitle = originalTitle; // 현재 제목을 이전 제목으로 업데이트
    };

    const unlockCopyPaste = () => {
        const writeArea = document.getElementById('write_area');

        // 복사 기능
        const handleCopy = (event) => {
            event.preventDefault(); // 기본 복사 동작 막기
            const selectedText = window.getSelection().toString(); // 선택된 텍스트 가져오기
            if (selectedText) {
                event.clipboardData.setData('text/plain', selectedText); // 클립보드에 텍스트 쓰기
            }
        };

        // 잘라내기 기능
        const handleCut = (event) => {
            event.preventDefault(); // 기본 잘라내기 동작 막기
            const selectedText = window.getSelection().toString(); // 선택된 텍스트 가져오기
            if (selectedText) {
                event.clipboardData.setData('text/plain', selectedText); // 클립보드에 텍스트 쓰기
                document.execCommand("delete"); // 선택된 텍스트 삭제
            }
        };

        // 붙여넣기 기능
        const handlePaste = (event) => {
            event.preventDefault(); // 기본 붙여넣기 동작 막기
            const text = (event.clipboardData || window.clipboardData).getData('text'); // 클립보드에서 텍스트 가져오기
            document.execCommand("insertText", false, text); // 텍스트를 수동으로 삽입
        };

        // 이벤트 리스너 등록
        writeArea.addEventListener('copy', handleCopy);
        writeArea.addEventListener('cut', handleCut);
        writeArea.addEventListener('paste', handlePaste);
    }

    const alignNicknameRight = () => {
        GM_addStyle(`
        .starting-line .chatting-list-item .message-container .username > button {
            float: right !important;
            white-space: nowrap;
        }
        `);
    }

    const hideButtonsAboveChatInput = () => {
        const style = `
        .chatbox .actionbox .chat_item_list {
            display: none !important;
        }
        .chatbox .actionbox {
            height: auto !important;
        }
        `;
        GM_addStyle(style);
    }

    const addStyleExpandLiveChat = () => {
        const style = `
        body.expandLiveChat:not(.screen_mode,.fullScreen_mode) #serviceHeader,
        body.expandLiveChat:not(.screen_mode,.fullScreen_mode) .broadcast_information,
        body.expandLiveChat:not(.screen_mode,.fullScreen_mode) .section_selectTab,
        body.expandLiveChat:not(.screen_mode,.fullScreen_mode) .wrapping.player_bottom{
            display: none !important;
        }

        body.expandLiveChat:not(.screen_mode,.fullScreen_mode) #webplayer_contents,
        body.expandLiveChat:not(.screen_mode,.fullScreen_mode) #sidebar {
            top: 0 !important;
            margin-top: 0 !important;
            min-height: 100vh !important;
        }

        body.expandLiveChat:not(.screen_mode,.fullScreen_mode) #webplayer #webplayer_contents .wrapping.side {
            padding: 0 !important;
        }
        `;
        GM_addStyle(style);
    }

    const makeExpandChatButton = (el, css_class) => {
        if (!el) return;

        // li 요소 생성
        const li = document.createElement('li');
        li.className = 'expand-toggle-li';

        // a 요소 생성
        const a = document.createElement('a');
        a.href = 'javascript:;';
        a.setAttribute('tip', '확장/축소(x)');
        a.textContent = '확장/축소(x)';

        // 클릭 이벤트 등록 (a에 등록해도 되고 li에 등록해도 됨)
        a.addEventListener('click', () => {
            document.body.classList.toggle(css_class);
        });

        // li에 a 추가, 그리고 el에 li 추가
        li.appendChild(a);
        el.appendChild(li);
    };

    function setupChatMessageTrackers(element) {
        const OriginalWebSocket = window.WebSocket;
        const targetUrlPattern = /^wss:\/\/chat-[\w\d]+\.sooplive\.co\.kr/;
        const MAX_MESSAGES = 500;

        const messageHistory = [];
        const bannedMessages = [];
        const targetUserMessages = [];

        let bannedWindow = null;
        let targetWindow = null;
        let banIcon = null;
        let highlightIcon = null;

        const selectedUsersArray = selectedUsers ? selectedUsers.split(',').map(user_id => user_id.trim()).filter(Boolean) : [];
        const targetUserIdSet = new Set([...allFollowUserIds, ...selectedUsersArray]);
        const highlightPosition = isShowDeletedMessagesEnabled ? "40px" : "10px" ;

        let totalChatCount = 0;
        let lastChatCount = 0;
        let last10Intervals = [];

        if (isChatCounterEnabled) {
            // 채팅 속도 디스플레이
            // 1. CPS 표시용 div 생성 및 삽입
            const container = document.querySelector('.chatting-item-wrap');
            const cpsDisplay = document.createElement('div');
            cpsDisplay.id = 'cps_display';
            container.appendChild(cpsDisplay);

            // 2. 스타일 적용
            Object.assign(cpsDisplay.style, {
                position: 'absolute',
                top: '8px',
                left: '8px',
                background: 'rgba(0, 0, 0, 0.3)',
                color: '#fff',
                fontSize: '14px',
                padding: '4px 8px',
                borderRadius: '4px',
                zIndex: '10',
                pointerEvents: 'none'
            });

            // 3. CPS 계산 및 표시

            setInterval(() => {
                const delta = totalChatCount - lastChatCount;
                lastChatCount = totalChatCount;

                last10Intervals.push(delta);
                if (last10Intervals.length > 10) {
                    last10Intervals.shift();
                }

                const sum = last10Intervals.reduce((a, b) => a + b, 0);
                const avg = sum / 5;

                cpsDisplay.textContent = `${Math.round(avg)}개/s`;
            }, 500);
            // 채팅 속도 디스플레이 끝
        }
        // 스타일 추가
        GM_addStyle(`

  #cps_display {
    position: absolute;
    top: 8px;
    left: 8px;
    background: rgba(0, 0, 0, 0.5);
    color: #fff;
    font-size: 14px;
    padding: 4px 8px;
    border-radius: 4px;
    z-index: 10;
    pointer-events: none;
  }

        .chat-icon {
            position: absolute;
            bottom: 10px;
            right: 6px;
            width: 24px;
            height: 24px;
            cursor: pointer;
            z-index: 1000;
            background-size: contain;
            background-repeat: no-repeat;
        }
        .chat-icon.highlight {
            right: 7px;
            width: 22px;
            height: 22px;
            bottom: ${highlightPosition};
        }
        html:not([dark="true"]) .trash-icon {
            background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2264%22%20height%3D%2264%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20stroke%3D%22%23000%22%20stroke-width%3D%220%22%3E%3Cg%2F%3E%3Cg%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke%3D%22%23CCC%22%20stroke-width%3D%22.192%22%2F%3E%3Cg%20fill%3D%22%236A6A75%22%20stroke%3D%22none%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M10.31%202.25h3.38c.217%200%20.406%200%20.584.028a2.25%202.25%200%200%201%201.64%201.183c.084.16.143.339.212.544l.111.335.03.085a1.25%201.25%200%200%200%201.233.825h3a.75.75%200%200%201%200%201.5h-17a.75.75%200%200%201%200-1.5h3.09a1.25%201.25%200%200%200%201.173-.91l.112-.335c.068-.205.127-.384.21-.544a2.25%202.25%200%200%201%201.641-1.183c.178-.028.367-.028.583-.028Zm-1.302%203a3%203%200%200%200%20.175-.428l.1-.3c.091-.273.112-.328.133-.368a.75.75%200%200%201%20.547-.395%203%203%200%200%201%20.392-.009h3.29c.288%200%20.348.002.392.01a.75.75%200%200%201%20.547.394c.021.04.042.095.133.369l.1.3.039.112q.059.164.136.315z%22%2F%3E%3Cpath%20d%3D%22M5.915%208.45a.75.75%200%201%200-1.497.1l.464%206.952c.085%201.282.154%202.318.316%203.132.169.845.455%201.551%201.047%202.104s1.315.793%202.17.904c.822.108%201.86.108%203.146.108h.879c1.285%200%202.324%200%203.146-.108.854-.111%201.578-.35%202.17-.904.591-.553.877-1.26%201.046-2.104.162-.813.23-1.85.316-3.132l.464-6.952a.75.75%200%200%200-1.497-.1l-.46%206.9c-.09%201.347-.154%202.285-.294%202.99-.137.685-.327%201.047-.6%201.303-.274.256-.648.422-1.34.512-.713.093-1.653.095-3.004.095h-.774c-1.35%200-2.29-.002-3.004-.095-.692-.09-1.066-.256-1.34-.512-.273-.256-.463-.618-.6-1.302-.14-.706-.204-1.644-.294-2.992z%22%2F%3E%3Cpath%20d%3D%22M9.425%2010.254a.75.75%200%200%201%20.821.671l.5%205a.75.75%200%200%201-1.492.15l-.5-5a.75.75%200%200%201%20.671-.821m5.15%200a.75.75%200%200%201%20.671.82l-.5%205a.75.75%200%200%201-1.492-.149l.5-5a.75.75%200%200%201%20.82-.671Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
        }
        html[dark="true"] .trash-icon {
            background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2264%22%20height%3D%2264%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20stroke%3D%22%23000%22%20stroke-width%3D%220%22%3E%3Cg%2F%3E%3Cg%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke%3D%22%23CCC%22%20stroke-width%3D%22.192%22%2F%3E%3Cg%20fill%3D%22%2394949C%22%20stroke%3D%22none%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M10.31%202.25h3.38c.217%200%20.406%200%20.584.028a2.25%202.25%200%200%201%201.64%201.183c.084.16.143.339.212.544l.111.335.03.085a1.25%201.25%200%200%200%201.233.825h3a.75.75%200%200%201%200%201.5h-17a.75.75%200%200%201%200-1.5h3.09a1.25%201.25%200%200%200%201.173-.91l.112-.335c.068-.205.127-.384.21-.544a2.25%202.25%200%200%201%201.641-1.183c.178-.028.367-.028.583-.028Zm-1.302%203a3%203%200%200%200%20.175-.428l.1-.3c.091-.273.112-.328.133-.368a.75.75%200%200%201%20.547-.395%203%203%200%200%201%20.392-.009h3.29c.288%200%20.348.002.392.01a.75.75%200%200%201%20.547.394c.021.04.042.095.133.369l.1.3.039.112q.059.164.136.315z%22%2F%3E%3Cpath%20d%3D%22M5.915%208.45a.75.75%200%201%200-1.497.1l.464%206.952c.085%201.282.154%202.318.316%203.132.169.845.455%201.551%201.047%202.104s1.315.793%202.17.904c.822.108%201.86.108%203.146.108h.879c1.285%200%202.324%200%203.146-.108.854-.111%201.578-.35%202.17-.904.591-.553.877-1.26%201.046-2.104.162-.813.23-1.85.316-3.132l.464-6.952a.75.75%200%200%200-1.497-.1l-.46%206.9c-.09%201.347-.154%202.285-.294%202.99-.137.685-.327%201.047-.6%201.303-.274.256-.648.422-1.34.512-.713.093-1.653.095-3.004.095h-.774c-1.35%200-2.29-.002-3.004-.095-.692-.09-1.066-.256-1.34-.512-.273-.256-.463-.618-.6-1.302-.14-.706-.204-1.644-.294-2.992z%22%2F%3E%3Cpath%20d%3D%22M9.425%2010.254a.75.75%200%200%201%20.821.671l.5%205a.75.75%200%200%201-1.492.15l-.5-5a.75.75%200%200%201%20.671-.821m5.15%200a.75.75%200%200%201%20.671.82l-.5%205a.75.75%200%200%201-1.492-.149l.5-5a.75.75%200%200%201%20.82-.671Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
        }
        html:not([dark="true"]) .highlight-icon {
            background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAADyUlEQVR4nO1YTWhdVRA+/TH+Ba3gorgQFBS0ulEsqItXFCWLmuZ8k0GSvDtzo/C0YFu0+IfIU7rQnSKCIIo7XYmRIiL+oCsRBbFimwr+ddOVWKy2aovK5OYl552Q9+59P7kp3A8O5OWe+c7MnJk5c45zFSpUqFBhLTA9PX3Z5KTMAjoHyDygfwByGtAfAX3Te/HMvKkbj80hSgHIW5nsAodxzRPJO4Cmu3bploEpzswXAvIkICeI9L/OQ44wJ7euxkWU3mZzcvD8BugTqnpBX8qPj993BaBfdl+wbZwxg51zG1o8zWZzI6BP2bciXIB+YTr0rDyRHIsIvyHSvcyz19fr9YvNQ/Y3oPuJ5OfIiy+1uAB5Ofr2E5E8PDGRXGccxkUk2wDZB8ihaO6xwkZkYRN6Xv6anJQHzJOdZEzpyIvPEMmz0f9e7BQazWZzI5E+aGuGO1EonCwEQuWZk1p+WX109ZBIHsnLQ6Q7IiMey11twoQ1z+dddHlxeW2l8vJqUR5AdoeJnas6WakMY75T2KyGnTsbFxHpt4HyhyzEivIw86b2nEi0q5DV4sBze12PmJhIrl2s7UeA9JpeeQDZFzji7a4CRHq0JWAVxpUMItkWGDDfVQDQk8sG8KgrGcw8GoT0ya4Cdqy3BGZmZi5xJYO5cWlBAyxulzL/blcyvNexICcPdxWwclcoaYYMQOcCfV7pKkCkdwYW/8ss211JYNabTYdAnx155DYs9jytMPq4l7OgXzSzluKTwPtf5xb2Pr0jauL2uzUGVrQkBfORSN4IrP9nLUPJ+/QmQP8O1n+9MEnW4urhsAVm1q1uyGDWrVm7vaT8d9aa9EiW3gjoqSCUvhrm4cbMo7ZGsN4p7+s39EUKKAF6NtiJ92q12mY3YNRqtc3GHSh/1u7OAyG3ljpK6oNjY3vOHwi5c67RaJxnZ064hve6xw0SRHogMmKOmUf65WXmkfCwWhwH3DAQG0Ek7/fzcsDMI0T6bsT5ghsmsntu20580MtlxUIQ0INrqnwLRPp0tOUfFil1NheQj0pRvgUieTzaiU/ztN82h0g/i5R/zpWBxUv3UrNlNXxqauryTg8GRPJ5ZPjzrkwA8lC7EXaBX3liZyfs8kXfZPygS2WvIJJ6+GyYPdrOXt36zpxcSSTfh4cUoPe79QQgudeavkDJX+xlgrl+FZH8EHj+DKCJW4/wXsbbX9PkuI3g92kgvcetZ1D2JPh7/DJHJH96n97lzgV4n94CyK+B508Aers7l8AL99mFxD1qF5Sy9alQoUIFNxT8D3rbJQHjF4hlAAAAAElFTkSuQmCC");
        }
        html[dark="true"] .highlight-icon {
            background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAD1ElEQVR4nO2YXYhVVRTHd6NZ1tAH9CA9CAYJZb4oCdVDUhS9GFQPvSgWPViQiklmRJzCh3orIgijGFAo2Gf9z5mud85aZ6Y69SSSEBrlFGTli0+hqGkf0sSac+/Mvnvy3nvu5wjnBxcunLP+e6191tp77W1MSUlJSckgqFartwLyLJGMAzIN8AWALwF8EpBPgOQJa+2SVjr6ThTxk0T8aW47q3GhphkT8TNxnN3SM8ettcuJ+FWAzwIy0/zHJ6KI77uSVhjK/fpOKx0iPkMke8fGsuu7cn58fPJ2gL9p7XjD4P9owDMzM9fUdYIgGCGS1/RZQa0j6kPHzhPxKU/0GMA7rE3uPnAgvVFnSP8TyW5AfvUGf6+uRSTvNz6TX4B0VxRN3qUaqhWGvIaIdxLJcU/nVOEgNG3cmSeSPwHepjPZzEad9lLqDYDf9Cbh3WapEQTBSBTx8/mY81+iUDrlOT/vfBQlDxawfblJSrzUrk4Y8kYviD0FVhu3YHmbKQggH/2P8x8W1SGSF9zCbmt10qXSzflmaXMlKpXKDUT8nfMVj2uKFdWx1i5xayIM061tBMCxM/s7TIdE0cTq2tp+gmjyzk51iHinMxFow0B+rBvoCmOGTBjyGicjplsaAHx+PoBs1AwZa7NRJyPOtzSobeuzBgcPJjeZIWPt1M0FA9C8nSuaRwfiZRMAecxZiX4wrdDlrlDR9BnKm8e6Px+0NAjD5GEn4n+jSDaYIRHH6Xr1YT4jeGNLI23C8p5nLogvOtkLuiUIghGAv3T8+LZtYyB9yGu+dpsBQ15LUrgeARlzqv/vQaYSwOsA+csZ/+PCItriatW7LbC11RWmz1hbXZG323Oz/722Jh2JxfHEWkAuOnl4tJ+bm7XZqI7hOH8RSO/pSpRIngLksiM6kWXZUtNjsixbqtrOOJf17NwTcW2pvdb4UJIk1/VE3Bizf//Ra3XP8Vrw7aaXALLPW5nGrbXLutW11i5zN6vab5/pBwuDYO7m5sDmzn/mab5j+kl+zm0496adHFY0BYn40ECdrwPw694nnyqy1Om7gHw+FOfrAPKKVxNftdN+6zsAf+19xbfMMNBDt9ts6RpeqVRua3ZhQCSHvZl/2wwTInmxMQg9wC/csfMd1j3oz9psN4uBMEw2N14b8kmiqTvqz62dWgnwT+4mRZQ8ZxYTRPK0Nn1OEL/pzYS16SqAf/buTreYxQjAj7u3aYCcrv3qQV0iSjeZxUyYXwmeW3gzJ38A8oi5GgCSewH+3Zn5s0TpA+ZqIo7T9Vq4+UUZrxu2PyUlJSWmL/wH5eEJ5sFzGH4AAAAASUVORK5CYII=");
        }
    `);

        // 빨간 점 토글 함수
        const toggleRedDot = (icon, shouldShow) => {
            if (!icon) return;
            const existingDot = icon.querySelector(".red-dot");
            if (shouldShow && !existingDot) {
                const redDot = document.createElement("div");
                redDot.classList.add("red-dot");
                Object.assign(redDot.style, {
                    position: "absolute",
                    top: "0px",
                    right: "0px",
                    width: "4px",
                    height: "4px",
                    borderRadius: "50%",
                    backgroundColor: "red",
                    zIndex: 1001
                });
                icon.appendChild(redDot);
            } else if (!shouldShow && existingDot) {
                existingDot.remove();
            }
        };

        const recordMessage = (userId, userName, message, timestamp) => {
            messageHistory.push({ userId, userName, message, timestamp });
            if (messageHistory.length > MAX_MESSAGES) {
                messageHistory.shift();
            }

            if (isShowSelectedMessagesEnabled && targetUserIdSet.has(userId)) {
                targetUserMessages.push({ userId, userName, message, timestamp });
                updateTargetMessages();
            }
        };

        const decodeMessage = (data) => {
            const decoder = new TextDecoder("utf-8");
            const decodedText = decoder.decode(data);
            const parts = decodedText.split("\x0c");

            const now = new Date();
            const timestamp = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;

            if (parts.length === 13 || parts.length === 14) {
                const message = parts[1];
                const userId = parts[2].split('(')[0];
                const userName = parts[6];

                if (userId.includes('|') || userName.includes('|') || !userId || !userName || userName === '0') return;

                recordMessage(userId, userName, message, timestamp);
                if(isChatCounterEnabled) totalChatCount++; // ✅ 여기서 1씩 증가

            } else if (parts[1] === '-1' && parts[4] === '2') {
                const userId = parts[2].split('(')[0];
                const userName = parts[3];
                if (userId.includes('|') || userName.includes('|') || !userId || !userName) return;

                if (isShowDeletedMessagesEnabled) {
                    const userMessages = messageHistory.filter(msg => msg.userId === userId);
                    bannedMessages.push(...userMessages);
                    bannedMessages.push({
                        userId,
                        userName,
                        message: "[강제퇴장 됨]",
                        timestamp
                    });
                    updateBannedMessages();
                }
            }
        };

        // WebSocket Hook
        unsafeWindow.WebSocket = function(url, protocols) {
            const ws = new OriginalWebSocket(url, protocols);
            if (targetUrlPattern.test(url)) {
                ws.addEventListener("message", (event) => decodeMessage(event.data));
            }
            return ws;
        };
        unsafeWindow.WebSocket.prototype = OriginalWebSocket.prototype;

        // 아이콘 생성
        const createIcon = (type, onClick) => {
            const icon = document.createElement("div");
            icon.classList.add("chat-icon", type === "highlight" ? "highlight-icon" : "trash-icon", type);
            icon.addEventListener("click", (e) => {
                e.preventDefault();
                e.stopPropagation();
                onClick();
            });
            element.appendChild(icon);
            return icon;
        };

        const showBannedMessages = () => {
            if (bannedWindow && !bannedWindow.closed) {
                bannedWindow.focus();
                return;
            }

            const width = 600;
            const height = 600;

            const left = (screen.width - width) / 2;
            const top = (screen.height - height) / 2;

            const features = `width=${width},height=${height},left=${left},top=${top}`;

            bannedWindow = window.open("", "_blank", features);
            bannedWindow.document.write(`
                    <html>
                        <head>
                            <title>강제퇴장된 유저의 채팅</title>
                            <style>
                                body {
                                    font-family: Arial, sans-serif;
                                    background-color: #f4f4f9;
                                    color: #333;
                                    margin: 20px;
                                }
                                #bannedMessages {
                                    list-style: none;
                                    padding: 0;
                                }
                                #bannedMessages li {
                                    background-color: #fff;
                                    border-radius: 8px;
                                    padding: 10px;
                                    margin-bottom: 10px;
                                    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
                                }
                                .message-timestamp {
                                    font-size: 0.9em;
                                    color: #888;
                                }
                            </style>
                        </head>
                        <body>
                            <ul id='bannedMessages'></ul>
                        </body>
                    </html>

        `);
            bannedWindow.document.close();
            updateBannedMessages();
        };

        const updateBannedMessages = () => {
            if (!bannedWindow || bannedWindow.closed) {
                toggleRedDot(banIcon, true);
                return;
            }
            toggleRedDot(banIcon, false);
            const messageList = bannedWindow.document.getElementById("bannedMessages");
            messageList.replaceChildren();
            if (!Array.isArray(bannedMessages) || bannedMessages.length === 0) {
                const noMessageItem = document.createElement("li");
                noMessageItem.textContent = "메시지가 없습니다.";
                messageList.appendChild(noMessageItem);
            } else {
                bannedMessages.forEach(msg => {
                    const systemMessage = msg.message === `[강제퇴장 됨]`;
                    const listItem = document.createElement("li");

                    const timestampSpan = document.createElement("span");
                    timestampSpan.className = "message-timestamp";
                    timestampSpan.textContent = `[${msg.timestamp}]`;

                    const nameTag = systemMessage
                    ? document.createElement("i")
                    : document.createElement("strong");

                    nameTag.textContent = systemMessage
                        ? `${msg.userName} (${msg.userId}) 님이 강제 퇴장 되었습니다.`
                    : `${msg.userName} (${msg.userId})`;

                    const text = systemMessage ? '' : `: ${msg.message}`;
                    const messageText = document.createTextNode(text);

                    listItem.appendChild(timestampSpan);
                    listItem.appendChild(document.createTextNode(" "));
                    listItem.appendChild(nameTag);
                    listItem.appendChild(messageText);

                    messageList.insertBefore(listItem, messageList.firstChild);
                });
            }
        };

        const showTargetMessages = () => {
            if (targetWindow && !targetWindow.closed) {
                targetWindow.focus();
                return;
            }
            const width = 600;
            const height = 600;

            const left = (screen.width - width) / 2;
            const top = (screen.height - height) / 2;

            const features = `width=${width},height=${height},left=${left},top=${top}`;

            targetWindow = window.open("", "_blank", features);
            targetWindow.document.write(`
                    <html>
                        <head>
                            <title>지정 유저 채팅 | 즐겨찾기 ${allFollowUserIds.length}명 | 수동 입력 ${selectedUsersArray.length}명</title>
                            <style>
                                body {
                                    font-family: Arial, sans-serif;
                                    background-color: #f4f4f9;
                                    color: #333;
                                    margin: 20px;
                                }
                                #targetUserMessages {
                                    list-style: none;
                                    padding: 0;
                                }
                                #targetUserMessages li {
                                    background-color: #fff;
                                    border-radius: 8px;
                                    padding: 10px;
                                    margin-bottom: 10px;
                                    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
                                }
                                .message-timestamp {
                                    font-size: 0.9em;
                                    color: #888;
                                }
                            </style>
                        </head>
                        <body>
                            <ul id='targetUserMessages'></ul>
                        </body>
                    </html>
        `);
            targetWindow.document.close();
            updateTargetMessages();
        };

        const updateTargetMessages = () => {
            if (!targetWindow || targetWindow.closed) {
                toggleRedDot(highlightIcon, true);
                return;
            }
            toggleRedDot(highlightIcon, false);
            const messageList = targetWindow.document.getElementById("targetUserMessages");
            messageList.replaceChildren();

            if (!Array.isArray(targetUserMessages) || targetUserMessages.length === 0) {
                const noMessageItem = document.createElement("li");
                noMessageItem.textContent = "메시지가 없습니다.";
                messageList.appendChild(noMessageItem);
            } else {
                targetUserMessages.forEach(msg => {
                    const li = document.createElement("li");
                    li.textContent = `[${msg.timestamp}] ${msg.userName} (${msg.userId}): ${msg.message}`;
                    messageList.insertBefore(li, messageList.firstChild);
                });
            }
        };

        const resetChatData = () => {
            messageHistory.length = 0;
            bannedMessages.length = 0;
            targetUserMessages.length = 0;

            updateBannedMessages();
            updateTargetMessages();

            toggleRedDot(banIcon, false);
            toggleRedDot(highlightIcon, false);
        };

        unsafeWindow.resetChatData = resetChatData;

        // 조건부 실행
        if (isShowDeletedMessagesEnabled) {
            banIcon = createIcon("trash", showBannedMessages);
        }

        if (isShowSelectedMessagesEnabled) {
            highlightIcon = createIcon("highlight", showTargetMessages);
        }
    }

    const disableAutoVOD = () => {
        const container = unsafeWindow.liveView?.aContainer?.[1];
        if (container) {
            if (container.autoPlayVodBanner) {
                container.autoPlayVodBanner.show = () => {};
            }
        } else {
            setTimeout(disableAutoVOD, 1000); // container가 없으면 재시도
        }
    };

    function isElementVisible(selector) {
        const el = document.querySelector(selector);
        if (!el) return false; // 요소가 없음

        const style = window.getComputedStyle(el);
        if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
            return false; // CSS로 숨겨진 경우
        }

        const rect = el.getBoundingClientRect();
        if (rect.width === 0 || rect.height === 0) {
            return false; // 크기가 0인 경우
        }

        // 화면 안에 일부라도 보이는 경우
        return (
            rect.bottom > 0 &&
            rect.right > 0 &&
            rect.top < (window.innerHeight || document.documentElement.clientHeight) &&
            rect.left < (window.innerWidth || document.documentElement.clientWidth)
        );
    }

    function updateBodyClass(targetClass) {
        if (!window.matchMedia("(orientation: portrait)").matches) {
            document.body.classList.remove(targetClass);
            document.querySelector('.expand-toggle-li').style.display = 'none';
        } else {
            document.querySelector('.expand-toggle-li').style.display = 'block';
        }
    }

    function makeCaptureButton() {
        const svgDataUrl = 'data:image/svg+xml,%3Csvg%20width%3D%2264%22%20height%3D%2264%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20stroke%3D%22%23fff%22%3E%3Cg%20stroke-width%3D%220%22%2F%3E%3Cg%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke%3D%22%23CCC%22%20stroke-width%3D%22.048%22%2F%3E%3Cg%20stroke-width%3D%221.488%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M21%2013c0-2.667-.5-5-1-5.333-.32-.214-1.873-.428-4-.553C14.808%207.043%2017%205%2012%205S9.192%207.043%208%207.114c-2.127.125-3.68.339-4%20.553C3.5%208%203%2010.333%203%2013s.5%205%201%205.333S8%2019%2012%2019s7.5-.333%208-.667c.5-.333%201-2.666%201-5.333%22%2F%3E%3Cpath%20d%3D%22M12%2016a3%203%200%201%200%200-6%203%203%200%200%200%200%206%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E';

        // 1. CSS 삽입
        const style = document.createElement('style');
        style.textContent = `
    #player .imageCapture {
      overflow: visible;
      color: rgba(0, 0, 0, 0);
      width: 32px;
      height: 32px;
      margin: 0;
      font-size: 0;
      opacity: 0.9;
      background: url("${svgDataUrl}") 50% 50% no-repeat;
      background-size: 82%;
      border: none;
      padding: 0;
      cursor: pointer;
      position: relative;
    }
    #player .imageCapture:hover {
      opacity: 1;
    }
  `;
        document.head.appendChild(style);

        const captureAndOpenBlob = () => {
            const video = document.getElementById('livePlayer') || document.getElementById('video');
            if (!video) return;

            const canvas = document.createElement('canvas');
            canvas.width = video.videoWidth;
            canvas.height = video.videoHeight;
            const ctx = canvas.getContext('2d');
            ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

            const now = new Date();
            const pad = n => String(n).padStart(2, '0');
            const timestamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}`;

            canvas.toBlob(blob => {
                if (!blob) return;

                const imgURL = URL.createObjectURL(blob);

                const html = `
      <!DOCTYPE html>
      <html>
      <head>
        <meta charset="UTF-8">
        <title>ScreenShot (${video.videoWidth}x${video.videoHeight})</title>
        <style>
          body {
            margin: 0;
            background: #000;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            position: relative;
          }
          img {
            max-width: 100%;
            max-height: 100%;
          }
          #downloadBtn {
            position: absolute;
            top: 16px;
            right: 16px;
            padding: 8px 12px;
            background-color: #ffffffcc;
            border: none;
            border-radius: 4px;
            font-size: 14px;
            cursor: pointer;
            font-weight: bold;
          }
        </style>
      </head>
      <body>
        <img id="capturedImg" src="${imgURL}" alt="영상 캡쳐 이미지">
        <button id="downloadBtn">다운로드 capture_${timestamp}.jpg</button>
        <script>
          const timestamp = "${timestamp}";
          document.getElementById('downloadBtn').addEventListener('click', () => {
            const a = document.createElement('a');
            a.href = document.getElementById('capturedImg').src;
            a.download = \`capture_\${timestamp}.jpg\`;
            a.click();
          });
        </script>
      </body>
      </html>
    `;

                const blobURL = URL.createObjectURL(new Blob(
                    [html],
                    { type: 'text/html;charset=UTF-8' }
                ));
                window.open(blobURL, '_blank');
            }, 'image/jpeg', 0.92);
        };

        // 2. 버튼 생성
        const createButton = () => {
            const btn = document.createElement('button');
            btn.className = 'imageCapture';
            btn.type = 'button';
            btn.title = '비디오 스크린샷';

            btn.addEventListener('click', () => {
                try {
                    captureAndOpenBlob();
                } catch (err) {
                    console.error('캡처 실패:', err);
                }
            });

            return btn;
        };

        // 3. 버튼 삽입
        const insertButton = () => {
            const container = document.querySelector('#player .player_ctrlBox .ctrlBox .right_ctrl');
            if (container && !container.querySelector('.imageCapture')) {
                const btn = createButton();
                container.insertBefore(btn, container.firstChild);
            }
        };

        // 4. DOM 변화 감지 + 초기 실행
        const observer = new MutationObserver(() => {
            insertButton();
            const container = document.querySelector('#player .player_ctrlBox .ctrlBox .right_ctrl');
            if (container && container.querySelector('.imageCapture')) {
                observer.disconnect();
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }


    //=================================플레이어 페이지 함수 끝=================================//


    //============================ VOD 페이지 함수 시작 ============================//


    // 미디어 정보 확인 함수
    const checkMediaInfo = async (mediaName) => {
        if (mediaName !== 'original') { // 원본 화질로 설정되지 않은 경우
            const player = await waitForElementAsync('#player');
            player.className = 'video mouseover ctrl_output';

            // 설정 버튼 클릭
            const settingButton = await waitForElementAsync('#player > div.player_ctrlBox > div.ctrlBox > div.right_ctrl .setting_box > button.btn_setting');
            settingButton.click();

            // 화질 변경 리스트 대기
            const settingList = await waitForElementAsync('#player > div.player_ctrlBox > div.ctrlBox > div.right_ctrl .setting_box.on .setting_list');
            const spanElement = Array.from(settingList.querySelectorAll('span')).find(el => el.textContent.includes("화질 변경"));
            const buttonElement = spanElement.closest('button');
            buttonElement.click();

            // 두 번째 설정 대기
            const resolutionButton = await waitForElementAsync('#player > div.player_ctrlBox > div.ctrlBox > div.right_ctrl .setting_box .setting_list_subLayer ul > li:nth-child(2) > button');
            resolutionButton.click();
            resolutionButton.className = 'video';
        }
    };

    const addStyleExpandVODChat = () => {
        const style = `
            .expandVODChat:not(.screen_mode,.fullScreen_mode) #serviceHeader,
            .expandVODChat:not(.screen_mode,.fullScreen_mode) .broadcast_information,
            .expandVODChat:not(.screen_mode,.fullScreen_mode) .section_selectTab,
            .expandVODChat:not(.screen_mode,.fullScreen_mode) .wrapping.player_bottom{
                display: none !important;
            }
            .expandVODChat:not(.screen_mode,.fullScreen_mode) #webplayer_contents {
                margin: 0 auto !important;
                min-height: 100vh !important;
            }
        `;
        GM_addStyle(style);
    }

    const addStyleRemoveShadowsFromCatch = () => {
        const style = `
            .catch_webplayer_wrap .vod_player:after {
                background-image: none !important;
            }
        `;
        GM_addStyle(style);
    }

    //============================ VOD 페이지 함수 끝 ============================//


    //============================ 메인 페이지 실행 ============================//

    if (CURRENT_URL.startsWith("https://www.sooplive.co.kr")) {

        GM_addStyle(CommonStyles);

        GM_addStyle(mainPageCommonStyles);

        if (isPreviewModalEnabled || isReplaceEmptyThumbnailEnabled) {
            loadHlsScript();
        }

        if (isCustomSidebarEnabled) document.body.classList.add('customSidebar');

        waitForElement('#serviceLnb', function (elementSelector, element) {
            if (isCustomSidebarEnabled) makeTopNavbarAndSidebar("main");
            runCommonFunctions();
        });

        removeUnwantedTags();

        processStreamers();

        return;
    }


    //============================ 플레이어 페이지 실행 ============================//

    if (CURRENT_URL.startsWith("https://play.sooplive.co.kr")) {
        const blankA = !!document.getElementById("bannedMessages");
        const blankB = !!document.getElementById("targetUserMessages");
        // Embed 페이지에서는 실행하지 않음
        const pattern = /^https:\/\/play.sooplive.co.kr\/.*\/.*\/embed(\?.*)?$/;
        if (pattern.test(CURRENT_URL) || CURRENT_URL.includes("vtype=chat") || blankA || blankB) {
            return;
        }
        GM_addStyle(CommonStyles);

        GM_addStyle(playerCommonStyles);

        if (isCustomSidebarEnabled) document.body.classList.add('customSidebar');

        if (isCustomSidebarEnabled) {
            makeTopNavbarAndSidebar("player");
            insertFoldButton();
            if(showSidebarOnScreenMode && !showSidebarOnScreenModeAlways) {
                showSidebarOnMouseOver();
            }
        }
        if(isBottomChatEnabled) useBottomChat();
        if(isMakePauseButtonEnabled) detectPlayerChangeAndAppendPauseButton();
        if(isMakeSharpModeShortcutEnabled) toggleSharpModeShortcut();
        if(isMakeLowLatencyShortcutEnabled) toggleLowLatencyShortcut();
        if(isRemainingBufferTimeEnabled){
            waitForElement('#livePlayer', function (elementSelector, element) {
                insertRemainingBuffer(element);
            });
        }
        if(isCaptureButtonEnabled){
            makeCaptureButton();
        }
        if(isStreamDownloadEnabled){
            createStreamDownloadButton_LivePage();
        }
        if(isAutoClaimGemEnabled){
            setInterval(autoClaimGem, 30000);
        }
        if(isVideoSkipHandlerEnabled){
            waitForElement('#livePlayer', function (elementSelector, element) {
                window.addEventListener('keydown', videoSkipHandler);
            });
        }
        registerVisibilityChangeHandler();
        checkPlayerPageHeaderAd();
        if(!isOpenNewtabEnabled){
            homePageCurrentTab();
        }
        if(isDocumentTitleUpdateEnabled){
            setTimeout(updateTitleWithViewers, 10000);
            setInterval(updateTitleWithViewers, 60000);
        }
        runCommonFunctions();

        // LIVE 채팅창
        waitForElement('#chat_area', function (elementSelector, element) {
            observeChat(elementSelector,element);
        });

        if (isUnlockCopyPasteEnabled) {
            waitForElement('#write_area', function (elementSelector, element) {
                unlockCopyPaste();
            });
        }

        if (isAlignNicknameRightEnabled) {
            alignNicknameRight();
        }

        if (isAutoScreenModeEnabled) {
            waitForElement('#livePlayer', function (elementSelector, element) {
                if (!document.body.classList.contains('screen_mode')) {
                    document.body.querySelector('#player .btn_screen_mode').click();
                }
            });
        }

        if (ishideButtonsAboveChatInputEnabled) {
            hideButtonsAboveChatInput();
        }

        if (isExpandLiveChatAreaEnabled) {
            waitForElement('#chatting_area div.area_header > div.chat_title > ul', function (elementSelector, element) {
                // MutationObserver 생성
                const observer = new MutationObserver((mutationsList) => {
                    for (const mutation of mutationsList) {
                        if (mutation.type === 'attributes' && document.body.classList.contains('ratio169_mode')) {
                            // 클래스가 추가된 것을 감지했을 때 함수 실행
                            addStyleExpandLiveChat();
                            makeExpandChatButton(element, 'expandLiveChat');
                            toggleExpandChatShortcut();
                            updateBodyClass('expandLiveChat');
                            window.addEventListener('resize', debounce(() => updateBodyClass('expandLiveChat'), 500));
                            observer.disconnect(); // 더 이상 감시하지 않도록 종료
                            break;
                        }
                    }
                });

                // body의 클래스 속성 변화를 감시
                observer.observe(document.body, { attributes: true, attributeFilter: ['class'] });

            });
        }

        if (isShowDeletedMessagesEnabled || isShowSelectedMessagesEnabled) {
            waitForElement(".chatting-item-wrap", function (elementSelector, element) {
                setupChatMessageTrackers(element);
            });
        }

        if (isNoAutoVODEnabled) {
            disableAutoVOD();
        }

        if (isHideEsportsInfoEnabled) {
            GM_addStyle(`
              body:not(.screen_mode,.fullScreen_mode,.embeded_mode)
              #webplayer #webplayer_contents #player_area
              .broadcast_information.detail_open .esports_info {
                    display: none !important;
              }
              .broadcast_information .esports_info {
                    display: none !important;
              }
              `
                       );
        }

        return;
    }

    //============================ VOD 페이지 실행 ============================//

    if (CURRENT_URL.startsWith("https://vod.sooplive.co.kr/player/")) {
        const isBaseUrl = (url) => /https:\/\/vod\.sooplive\.co\.kr\/player\/\d+/.test(url) && !isCatchUrl(url);
        const isCatchUrl = (url) => /https:\/\/vod\.sooplive\.co\.kr\/player\/\d+\/catch/.test(url) || /https:\/\/vod\.sooplive\.co\.kr\/player\/catch/.test(url);

        // 다시보기 페이지
        if (isBaseUrl(CURRENT_URL)) {

            GM_addStyle(CommonStyles);

            // vodCore 변수가 선언될 때까지 대기하는 함수
            const waitForVodCore = () => {
                const checkVodCore = setInterval(() => {
                    if (unsafeWindow.vodCore?.playerController?._currentMediaInfo?.name) {
                        clearInterval(checkVodCore); // setInterval 정지
                        checkMediaInfo(unsafeWindow.vodCore.playerController._currentMediaInfo.name); // vodCore 변수가 정의되면 미디어 정보 확인 함수 호출
                    }
                }, 500); // 500ms 주기로 확인
            };

            if(isSelectBestQualityEnabled){
                waitForVodCore();
            }

            if(isCaptureButtonEnabled){
                makeCaptureButton();
            }


            // VOD 채팅창
            waitForElement('#webplayer_contents', function (elementSelector, element) {
                observeChat(elementSelector,element);
            });

            waitForElement('div.serviceUtil', function (elementSelector, element) {
                addModalSettings();
                manageRedDot();
            });

            if (isAlignNicknameRightEnabled) {
                alignNicknameRight();
            }

            if (isExpandVODChatAreaEnabled) {
                waitForElement('#chatting_area div.area_header > div.chat_title > ul', function (elementSelector, element) {
                    addStyleExpandVODChat();
                    makeExpandChatButton(element, 'expandVODChat');
                    toggleExpandChatShortcut();
                    updateBodyClass('expandVODChat');
                    window.addEventListener('resize', debounce(() => updateBodyClass('expandVODChat'), 500));
                });
            }

            // 캐치 페이지
        } else if (isCatchUrl(CURRENT_URL)) {

            GM_addStyle(CommonStyles);

            GM_addStyle(mainPageCommonStyles);

            if (isCustomSidebarEnabled) document.body.classList.add('customSidebar');

            waitForElement('#serviceLnb', function (elementSelector, element) {
                if (isCustomSidebarEnabled) makeTopNavbarAndSidebar("main");
                runCommonFunctions();
            });

            if (isRemoveShadowsFromCatchEnabled) addStyleRemoveShadowsFromCatch();

        }

    }

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址