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

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

// ==UserScript==
// @name         SOOP (숲) - 사이드바 UI 변경
// @name:ko         SOOP (숲) - 사이드바 UI 변경
// @namespace    https://greasyfork.org/ko/scripts/484713
// @version      20241274
// @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 MIT
// ==/UserScript==

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

    let STATION_FEED_DATA;

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

    let delayCheckEnabled = true;
    let sharpModeCheckEnabled = true;

    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 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 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 isSharpeningEnabled = GM_getValue("isSharpeningEnabled", 0);
    let isAutoScreenModeEnabled = GM_getValue("isAutoScreenModeEnabled", 0);
    let isAdjustDelayNoGridEnabled = GM_getValue("isAdjustDelayNoGridEnabled", 0);
    let ishideButtonsAboveChatInputEnabled = GM_getValue("ishideButtonsAboveChatInputEnabled", 0);
    let isExpandVODChatEnabled = GM_getValue("isExpandVODChatEnabled", 0);
    let isAutoExpandVODChatEnabled = GM_getValue("isAutoExpandVODChatEnabled", 0);
    let isExpandLiveChatEnabled = GM_getValue("isExpandLiveChatEnabled", 0);
    let isAutoExpandLiveChatEnabled = GM_getValue("isAutoExpandLiveChatEnabled", 0);
    let isOpenExternalPlayerEnabled = GM_getValue("isOpenExternalPlayerEnabled", 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 isHideCursorOnScreenModeAndFullScreenModeEnabled = GM_getValue("isHideCursorOnScreenModeAndFullScreenModeEnabled", 1);
    let isShowDeletedMessagesEnabled = GM_getValue("isShowDeletedMessagesEnabled", 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 = () => {
        return new Promise((resolve) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: "https://live.sooplive.co.kr/api/hiddenbj/hiddenbjController.php",
                onload: response => {
                    try {
                        const data = JSON.parse(response.responseText);
                        response.status === 200 && data.RESULT === 1 ? resolve(data.DATA) : resolve([]); // 실패 시 빈 배열 반환
                    } catch (error) {
                        resolve([]); // 파싱 오류 시 빈 배열 반환
                    }
                },
                onerror: () => resolve([]) // 요청 오류 시 빈 배열 반환
            });
        });
    };

    const getStationFeed = () => {
        return new Promise((resolve) => {
            if (!isChannelFeedEnabled) {
                resolve([]); // 채널 피드가 비활성화된 경우 빈 배열 반환
                return;
            }

            GM_xmlhttpRequest({
                method: "GET",
                url: "https://myapi.sooplive.co.kr/api/feed?index_reg_date=0&user_id=&is_bj_write=1&feed_type=&page=1",
                onload: response => {
                    try {
                        const responseData = JSON.parse(response.responseText);
                        resolve(responseData.data || []);
                    } catch (error) {
                        console.error("Error parsing response data:", error);
                        resolve([]);
                    }
                },
                onerror: error => {
                    console.error("Error while loading data:", error);
                    resolve([]);
                }
            });
        });
    };

    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);
                }
            });
        } else {
            //console.log("30 minutes not elapsed since last execution. Skipping data load.");
        }
    }

    function observeDarkAttributeChange(callback) {
        // MutationObserver 설정
        const observer = new MutationObserver((mutationsList) => {
            for (let mutation of mutationsList) {
                if (mutation.type === 'attributes' && mutation.attributeName === 'dark') {
                    const darkValue = document.documentElement.getAttribute('dark') === "true";
                    callback(darkValue); // 콜백 함수 호출
                }
            }
        });

        // 감시할 대상과 옵션 설정
        observer.observe(document.documentElement, {
            attributes: true, // 속성 변화를 감지
            attributeFilter: ['dark'], // 'dark' 속성만 감시
        });

        // observer 반환 (원할 경우 중지할 수 있도록)
        return observer;
    }


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

    const CommonStyles = `

.screen_mode #player,
.fullScreen_mode #player,
.screen_mode #videoLayerCover,
.fullScreen_mode #videoLayerCover {
    cursor: default !important;
}
.screen_mode #player.hide-cursor,
.fullScreen_mode #player.hide-cursor,
.screen_mode #videoLayerCover.hide-cursor,
.fullScreen_mode #videoLayerCover.hide-cursor {
    cursor: 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: 136px;
    z-index: 9999;
}

.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) {
    .left_navbar {
        left: 130px !important;
    }
    .left_nav_button {
        width: 58px !important;
        font-size: 1.2em;
    }
}

@media (max-width: 1100px) {
    .left_navbar {
        left: 124px !important;
    }
    .left_nav_button {
        width: 46px !important;
        font-size: 1.15em;
    }
}

#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 {
    background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22'%3e%3cpath d='M11 2.5c1.07 0 1.938.867 1.938 1.938l-.001.245.12.036c.318.104.628.232.927.382l.112.06.174-.173a1.937 1.937 0 0 1 2.594-.126l.128.117a1.923 1.923 0 0 1 .015 2.748l-.17.171.062.117c.151.299.279.608.382.927l.036.12h.245c1.02 0 1.855.787 1.932 1.787L19.5 11c0 1.07-.867 1.938-1.938 1.938l-.246-.001-.035.12a6.578 6.578 0 0 1-.382.926l-.062.116.155.157c.333.322.537.752.578 1.21l.008.172c0 .521-.212 1.02-.576 1.372a1.938 1.938 0 0 1-2.733 0l-.173-.174-.112.06a6.58 6.58 0 0 1-.927.383l-.12.035v.247a1.936 1.936 0 0 1-1.786 1.931l-.151.006a1.938 1.938 0 0 1-1.938-1.937v-.245l-.119-.035a6.58 6.58 0 0 1-.927-.382l-.114-.062-.168.171a1.94 1.94 0 0 1-2.62.119l-.123-.113a1.94 1.94 0 0 1-.003-2.746l.172-.171-.06-.112a6.578 6.578 0 0 1-.381-.927l-.036-.119h-.245a1.938 1.938 0 0 1-1.932-1.786l-.006-.151c0-1.07.867-1.938 1.938-1.938h.245l.036-.119a6.33 6.33 0 0 1 .382-.926l.059-.113-.175-.174a1.94 1.94 0 0 1-.108-2.619l.114-.123a1.94 1.94 0 0 1 2.745.008l.166.168.114-.06c.3-.152.609-.28.927-.383l.119-.036v-.25c0-1.019.787-1.854 1.787-1.931zm0 1a.937.937 0 0 0-.938.938v.937a.322.322 0 0 0 .02.098 5.578 5.578 0 0 0-2.345.966.347.347 0 0 0-.056-.075l-.656-.663a.94.94 0 1 0-1.331 1.326l.665.663c.023.02.048.036.075.05a5.576 5.576 0 0 0-.965 2.343l-.094-.019h-.938a.937.937 0 1 0 0 1.875h.938l.094-.018c.137.845.468 1.647.965 2.343a.375.375 0 0 0-.075.05l-.665.663a.94.94 0 1 0 1.331 1.325l.656-.662a.347.347 0 0 0 .056-.075 5.58 5.58 0 0 0 2.344.966.322.322 0 0 0-.018.094v.936a.937.937 0 1 0 1.874 0v-.938l-.018-.094a5.58 5.58 0 0 0 2.343-.966l.047.075.666.663a.937.937 0 0 0 1.322 0 .922.922 0 0 0 0-1.326l-.656-.663-.075-.05a5.578 5.578 0 0 0 .965-2.343.57.57 0 0 0 .094.018h.938a.937.937 0 1 0 0-1.874h-.938a.57.57 0 0 0-.094.016 5.576 5.576 0 0 0-.965-2.343l.075-.05.656-.663a.922.922 0 0 0 0-1.325.938.938 0 0 0-1.322 0l-.666.662-.046.075a5.578 5.578 0 0 0-2.344-.966l.018-.094v-.938A.937.937 0 0 0 11 3.5zm0 4.188a3.313 3.313 0 1 1 0 6.625 3.313 3.313 0 0 1 0-6.626zm0 1a2.313 2.313 0 1 0 0 4.625 2.313 2.313 0 0 0 0-4.626z' fill='%23707173'/%3e%3c/svg%3e") 50% 50% no-repeat !important;
    background-size: 18px 22px; !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:11px;
    padding: 1px;
}
#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;
}
.users-section.myplus > .user.show-more,
.users-section.follow > .user.show-more,
.users-section.top > .user.show-more,
.users-section.myplusvod > .user.show-more {
    display: none;
}
#toggleButton, #toggleButton2, #toggleButton3, #toggleButton4 {
    padding: 6px 0px;
    width: 100%;
    text-align: center;
}
#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 {
    display: grid;
    grid-template-areas: "profile-picture username watchers" "profile-picture description blank";
    grid-template-columns: 40px auto auto;
    padding: 5px 10px;
}
.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: 7px;
    margin-right: 5px;
}
.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);
}

.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 {
    grid-template-areas: "profile-picture username description watchers" !important;
    grid-template-columns: 24px auto 1fr auto !important;
    padding: 4px 10px !important;
    gap: 8px !important;
}
#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: 6px !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 .users-section .user.user-offline span {
    opacity: 0.7; /* 밝은 모드: 투명하게 */
}
html[dark="true"] .sharpening #sidebar span:not(.dot) {
    text-shadow: 0px 1px 0px rgb(0 0 0), 0px -1px 0px rgb(0 0 0), 1px 0px 0px rgb(0 0 0), -1px 0px 0px rgb(0 0 0);
}
html .sharpening #sidebar span:not(.dot) {
    text-shadow: 0px 1px 0px rgb(255 255 255), 0px -1px 0px rgb(255 255 255), 1px 0px 0px rgb(255 255 255), -1px 0px 0px rgb(255 255 255);
}


    `;

    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 #ccc;
    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;
}

.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;
}
button.block-icon-svg-white {
  width: 40px;
  height: 50px;
}
button.block-icon-svg-white span {
    background-size: 100% 100%;
    width: 20px;
    height: 20px;
}
button.block-icon-svg {
  width: 40px;
  height: 50px;
}
button.block-icon-svg span {
    background-size: 100% 100%;
    width: 20px;
    height: 20px;
}

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

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

    `;

    const mainPageDarkmodeStyles = `
#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");
}
#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");
}
button.block-icon-svg-white span {
    background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 64 64" style="fill:%23B2B2B2;"%3E%3Cpath d="M32 6C17.641 6 6 17.641 6 32C6 46.359 17.641 58 32 58C46.359 58 58 46.359 58 32C58 17.641 46.359 6 32 6zM32 10C37.331151 10 42.225311 11.905908 46.037109 15.072266L14.505859 45.318359C11.682276 41.618415 10 37.00303 10 32C10 19.869 19.869 10 32 10zM48.927734 17.962891C52.094092 21.774689 54 26.668849 54 32C54 44.131 44.131 54 32 54C26.99697 54 22.381585 52.317724 18.681641 49.494141L48.927734 17.962891z"%3E%3C/path%3E%3C/svg%3E');
}
button.block-icon-svg-white:hover span {
    background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 64 64" style="fill:%235285FF;"%3E%3Cpath d="M32 6C17.641 6 6 17.641 6 32C6 46.359 17.641 58 32 58C46.359 58 58 46.359 58 32C58 17.641 46.359 6 32 6zM32 10C37.331151 10 42.225311 11.905908 46.037109 15.072266L14.505859 45.318359C11.682276 41.618415 10 37.00303 10 32C10 19.869 19.869 10 32 10zM48.927734 17.962891C52.094092 21.774689 54 26.668849 54 32C54 44.131 44.131 54 32 54C26.99697 54 22.381585 52.317724 18.681641 49.494141L48.927734 17.962891z"%3E%3C/path%3E%3C/svg%3E');
}
#toggleButton, #toggleButton2, #toggleButton3, #toggleButton4 {
    color: #A1A1A1;
}
.left_nav_button {
    color: #e5e5e5;
}
.left_nav_button.active {
    color: #019BFE;
}
#sidebar {
    color: #fff;
    background-color: #1F1F23;
}
#sidebar .top-section > span {
    color: #DEDEE3;
}
#sidebar .top-section > span > a {
    color: #DEDEE3;
}
.users-section .user:hover {
    background-color: #26262c;
}
.users-section .user .username {
    color: #DEDEE3;
}
.users-section .user .description {
    color: #a1a1a1;
}
.users-section .user .watchers {
    color: #c0c0c0;
}
.tooltip-container {
    background-color: #26262C;
}
.tooltiptext {
    color: #fff;
    background-color: #26262C;
}
    `;

    const mainPageWhitemodeStyles = `
#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");
}
#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");
}
button.block-icon-svg span {
    background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 64 64" style="fill:%237C7D7D;"%3E%3Cpath d="M32 6C17.641 6 6 17.641 6 32C6 46.359 17.641 58 32 58C46.359 58 58 46.359 58 32C58 17.641 46.359 6 32 6zM32 10C37.331151 10 42.225311 11.905908 46.037109 15.072266L14.505859 45.318359C11.682276 41.618415 10 37.00303 10 32C10 19.869 19.869 10 32 10zM48.927734 17.962891C52.094092 21.774689 54 26.668849 54 32C54 44.131 44.131 54 32 54C26.99697 54 22.381585 52.317724 18.681641 49.494141L48.927734 17.962891z"%3E%3C/path%3E%3C/svg%3E');
}
button.block-icon-svg:hover span {
    background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 64 64" style="fill:%235285FF;"%3E%3Cpath d="M32 6C17.641 6 6 17.641 6 32C6 46.359 17.641 58 32 58C46.359 58 58 46.359 58 32C58 17.641 46.359 6 32 6zM32 10C37.331151 10 42.225311 11.905908 46.037109 15.072266L14.505859 45.318359C11.682276 41.618415 10 37.00303 10 32C10 19.869 19.869 10 32 10zM48.927734 17.962891C52.094092 21.774689 54 26.668849 54 32C54 44.131 44.131 54 32 54C26.99697 54 22.381585 52.317724 18.681641 49.494141L48.927734 17.962891z"%3E%3C/path%3E%3C/svg%3E');
}
#toggleButton, #toggleButton2, #toggleButton3, #toggleButton4 {
    color: #53535F;
}
.left_nav_button {
    color: #1F1F23;
}
.left_nav_button.active {
    color: #0545B1;
}
#sidebar {
    color: black;
    background-color: #EFEFF1;
}
#sidebar .top-section>span {
    color: #0E0E10;
}
#sidebar .top-section>span>a {
    color: #0E0E10;
}
.users-section .user:hover {
    background-color: #E6E6EA;
}
.users-section .user .username {
    color: #1F1F23;
}
.users-section .user .description {
    color: #53535F;
}
.users-section .user .watchers {
    color: black;
}
.tooltip-container {
    background-color: #E6E6EA;
}
.tooltiptext {
    color: black;
    background-color: #E6E6EA;
}
    `;

    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 darkModePlayerStyles = `

#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");
}
#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");
}
#sidebar {
    color: white;
    background-color: #1F1F23;
}

#sidebar .top-section > span {
    color:#DEDEE3;
}

#sidebar .top-section > span > a {
    color:#DEDEE3;
}
.users-section .user:hover {
    background-color: #26262c;
}

.users-section .user .username {
    color:#DEDEE3;
}

.users-section .user .description {
    color: #a1a1a1;
}

.users-section .user .watchers {
    color: #c0c0c0;
}

.left_nav_button {
    color: #e5e5e5;
}

.tooltip-container {
    background-color: #26262C;
}
.tooltiptext {
    color: #fff;
    background-color: #26262C;
}
#toggleButton, #toggleButton2, #toggleButton3, #toggleButton4 {
    color:#A1A1A1;
}

    `;

    const whiteModePlayerStyles = `

#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");
}
#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");
}
#sidebar {
    color: white;
    background-color: #EFEFF1;
}
#sidebar .top-section > span {
    color:#0E0E10;
}
#sidebar .top-section > span > a {
    color:#0E0E10;
}
.users-section .user:hover {
    background-color: #E6E6EA;
}
.users-section .user .username {
    color:#1F1F23;
}
.users-section .user .description {
    color: #53535F;
}
.users-section .user .watchers {
    color: black;
}
.tooltip-container {
    background-color: #E6E6EA;
}
.tooltiptext {
    color: black;
    background-color: #E6E6EA;
}
.left_nav_button {
    color: #1F1F23;
}

#toggleButton, #toggleButton2, #toggleButton3, #toggleButton4 {
    color: #53535F;
}
    `;

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

    const hideCursor = (element) => {
        let hideCursorTimeout;

        element.classList.add('hide-cursor');
        element.addEventListener('mousemove', () => {
            element.classList.remove('hide-cursor'); // 마우스가 움직이면 다시 보여줌
            clearTimeout(hideCursorTimeout); // 기존 타이머 초기화

            hideCursorTimeout = setTimeout(() => {
                element.classList.add('hide-cursor'); // 1초 후 커서 숨김
            }, 1000);
        });
    }

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

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

    const timeSince = (timestamp) => {
        const currentTime = new Date();
        const pastTime = new Date(timestamp.replace(/-/g, '/')); // 형식 변환

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

        if (days > 365) {
            const years = Math.floor(days / 365);
            return `${years}년 전`;
        }
        if (days > 30) {
            const months = Math.floor(days / 30);
            return `${months}개월 전`;
        }
        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})를 차단했습니다.`);
            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) => {
        // Get the container element
        const container = document.body.querySelector(selector);

        // Get all user elements
        const userElements = container.children; // Directly get the children for performance

        // Create arrays for each category
        const categories = [[], [], [], [], []];

        // Categorize users
        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');

            if (isPin && hasBroadThumbnail) {
                categories[0].push(user); // category1
            } else if (isPin) {
                categories[1].push(user); // category2
            } else if (isMobilePush && !isOffline) {
                categories[2].push(user); // category3
            } else if (!isMobilePush && !isOffline) {
                categories[3].push(user); // category4
            } else {
                categories[4].push(user); // category5
            }
        }

        // Sort each category by watchers
        categories.forEach(category => category.sort(compareWatchers));

        // Clear container and append sorted elements
        container.innerHTML = ''; // Clear container

        // Use DocumentFragment for improved performance when appending
        const fragment = document.createDocumentFragment();
        categories.forEach(category => {
            category.forEach(user => fragment.appendChild(user));
        });
        container.appendChild(fragment); // Append all sorted users at once
    }

    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 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/jpeg;base64,/9j/4AAQSkZJRgABAgEASABIAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAEOAeADAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD+7OuwkKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKAIppobaKSe4ljgghQySzTOscUaKMs7yOQqKByWYgCgDx/xF8YdLsS9voFudWnUlTdzb4LBCP7gwLi5wQQdogjIwyTOK0jTb30/MzdRLbX8jzv/AIST4neLT/xL21T7OxKj+yLY2Fqh7qb5BG34TXjY9snN8sI72+ev4f8AAJvOW1/lp+P/AASVfhx8Rb0+ZdOyO3JN5rSzPnGcsYprnnkjqTkntzRzw/pByT/piH4c/EWy/eWrO7jPNnrSQvxxwZZ7bqCcfNngjuMnPD+kHJL+mR/8JL8TfCbD+0TqhgQqCNXtmvrSTBA2i+cOxzjB8i8VsHOckGjlhLa3y0/D/gBecd7/AD1/H/gnoXh74xaZfPHba9anSpnwovIS09gWPB8xTm4tlJxtJFwigkySoq7jDptba/mUqi66fkexQzQ3EUc9vLHPBKoeKaF1likRhlXjkQsjqw5DKSCOhrM0JaACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAzNY1ew0LT59S1KcQWsC5J6ySuc7IYUyDJNKflRB7sxVFZlaTbshNpK7PmTVdd8T/ABM1YaZp0MiWKu0kGnxvstoIVIH2vUp+Ed1+X55PkR2EVrH5kmJdlGMFd79/8jJtzdl936s9c8L/AAr0PRViuNURNa1IYYtOn+gQPgHbBaNlZtpyBLchy2A6RQH5REqje2i/EuMEt9X+B6giJGqpGqoiKFREUKqqBgKqgAKoHAAAAHArMsdQAUANZVdWR1V0YFWVgGVlIwVZTkEEcEEYIoA8u8T/AAq0LWVkuNLRNF1DBI+zoBp8zdhNaKNsXTHmWvlYyzPFMcAaRqNb6r8fv/zIlBPbR/geSaTrvif4Z6s2majDI9iXV7jTpH3280LE/wCl6bPykbtknfH8kjr5V1H5keIrajNXW/f/ADITcHZ7dv8AI+m9I1ew13T4NS02cT2s65B6SROPvwzJkmOaI/K6H2ZSyMrNi007M1Turo06QwoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgBrukaNJIyoiKzu7sFREUFmZmYgKqgEsxIAAJJxQB8reJdZ1L4j+KLfStJDGwjma302I71iEYP8ApGqXYAYqGQGQsU3RWypEqGUv5m8UoRu/n/kYtubsvl/mfRHhjwzp/hbTIrCyQNIQr3l2VAmvLnaA8rnkqgORDCCVhjwo3MXd8pScnf7kaxior82dHUjCgAoAKACgAoA5zxP4Z0/xTpkmn3qBZAGezuwoaayuCMLLHypZDgCaEsEmT5Thgjo4ycXdf8OKUVJWfyfY+d/DOs6l8OPFFxpWq7hYPOtvqUQ3tFsOPs+qWoIUttRlkDBN01szxMglCeXtJKcbr5f5GUW4Ss9uv+Z9Uo6SIkkbK6OqujqQyujAMrKw4KsCCCOCDkVgbDqACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoA8p+LevtpXh5NNt5Cl1rcj25Kkh1sYAj3mCOnm74bds53RzSADPK3TV3fov6RE3ZW7/AJdSp8IfDS6fpD6/cR/6Zq+Vtty4aHTonKrtyAQbuVWlY5KvClsy4y2XUld26L8/+AEFZX7/AJf8H/I9hrMsp3+oWOl20l5qF1BZ2sX35riRY0BwSFBY5eRsEJGgZ3PyorHihJvYG0tzgbH4q+Fb/VYNKgOoKbmdLa3vZbVI7KSWVgkQyZ/tUayOyorS2qBS2ZPLQFqt05JX09OpHPFu2vr0PSqgsw9c8SaN4ct1uNXvY7YSbhDEA0lxcFRyIYIw0jgZAZ9ojQsvmOgYGmk3sJtLc4JfjL4TaURm31pELY89rO1MQH94hL9p9v0hLf7NX7OXl/XyJ9pHz/r5no2k6zpmu2a32lXcd5bMxQum5WjkABMcsUipLDIAykpIitgq2NrAmGmnZ6FJp6o06Qzx74veGk1DSE1+3jH23SdqXJH3ptNkfDA+ptZ5BMvTbFJcE5+UDSm7O3R/mRNXV+q/Iu/CbxC2reHzptw+670RktgSwLPYyBjZsRnP7oJJbcDASGLJLMaKis79/wAwg7q3b8j1SsywoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKAPl34nzy6346h0iBsm3TTtKhA5X7Ress7HHGW33iRtg/8swvBBrenpG/q/uMZ6yt6I+mrS2isrW2s4F2QWsENtCo/higjWOMceiqBWDd3c22LFAHyZrOmeOvFviS7sru0vp54LqaNI5Fkh0ywgEjBGjkYLbRwGPBSYFpbpdrBppGGehOMY3v/mzBqUm1r+iOR063e08RWNrKVMltrVrbyFCSheG+SNyhIUlSynaSqkjGQDxVPb5CW/zPuGuU6D401S8v/HPi07JN0mpX62WnrIWEVrZeaUt125OyOKImefYMvIZpApdyD0JKMfld+pg25S9XZHsNx8FtH/s547XUtQ/tRYiY7mdoPskk4U7VktkgMkcDtgHbO8sY+bdJgq0e1d9lYv2atu7nmfw51m68PeLbaxld4rbUbn+yb+3PK+e7tDbOVztWSC8KL5nVYnmXO12q5pON+yuiIO0rd9H/AF6n1tXOble7tob21ubO4UPBdQTW0yHo0U8bRyL+KsRQB8zfDC4m0Tx1Po8xwbldS0qdT90XFiz3Cvj+8Gs5Ilz2lYdTW89YX9GYw0lb1X9fcfUNYGwUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFAHy2gN38XyJPm2eJ3IzzxZMxi65+6LdMemMLjjG//AC7/AO3fzMf+XnzPqSsDYKACgD4q/wCZy/7mb/3K10/Z+X6HP9r5/qfabAMpU9GBB+hGDXMdB8V2E1x4Q8VQS3ULNLomqbbmHo0kcUhjmEZJAzLAWaBydh3o/wAyHnp+KPqjn+GXoz6WuPib4Pi05r+PU1uH8vdHYRxTLeyS7ciExPGPLO75WlciEYLCRhjOPJK9rfM2542vf5HyyL6S51wamVCTXGrC+KqchJJbz7QVUkdFZsAkdByO1b2sreVvwML6387n3NXKdIUAfLbr9l+L4CcbvEyMcH/n8ZXk6+vntke5AwOa3/5d/wDbpj9v/t78z6krA2CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoA+WronTPi8Gk+QN4ltmJJIATUjEwYnnjZdhj2xnoOm61p/wDbv5GL+P5o+pawNgoAKAPir/mcv+5m/wDcrXT9n5foc/2vn+p9q1zHQcD4t+HujeK3F3I0mn6mFVPt1sqv5yINqJdQOQk/lrwjq8UwUKhlaNFjFxm4+a7EyipeT7nC2fwTtopxLqWvvPaRnc8VtZLaPJGoyd1xLdXCwjj5sRP8ucOpwRTq9l+JPs+7/A8hsrW31Pxdb2umxBLK819I7OMZIisXv/3QO7LMIrXBZnJdgpZiWJJ0+zr21+4z3lps3+p9rVzHQFAHy1aH+0/i6Wj+YL4lumBGcFNNMzFhjdkFLUtnoRz8o6b7U/8At38zHefz/L/hj6lrA2CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoA+aPjDp0lh4ksNagDIL+1iPmgHi+05wmQwGARbm0KgnPysenTam7prt+T/pmNRa37/mj6D0TVIta0jTtVhxsvrWKcqP8AlnIy4miPvDMJIm7ZQ44rJqza7Gqd0n3NSkMKAPi0xuPGvllHEn/CUbPLKnfv/tbG3bjduzxtxnPGM10/Z+X6HP8Aa+f6n2lXMdB8teNZ/GPhvxfeaubu+hjmuZZNLu0dpLJrF3YwWZRt9tiGPEc1rMnzOpm8thIkrbx5ZRt9/e/f/gmMuZSbu/J9PT/gGZq3xP8AFWs6fJps0tpbQ3CNDcvZWzRT3MTja8Tu8soRHBIYQLCWUlSSpKlqEU7/AJic5NWO8+FXge7trlfE2rQPbFI5E0q1mXZMxlQxyXssTrujTymeO2DbXfe820R+S8k1JfZXzKhHq/l/me+ViamVrmqRaLpGo6rMVCWNrLMAxwJJQu2CIf7U07RxKO7OBTSu0u4m7Js+ffg7p0t/4jv9Zm3OthayZlbq17qLlASe5MCXZbA4LLnGRnao7Rt3f4L+kZU1dt9vzf8ATPpesDYKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgDiPiD4c/4STw5dQQpuv7LN/p+BlmmhVt9uO5+0wmSJVyF84wu33BVQfK/LqTJXXnujzP4P8AipYHl8LX0mwSySXOlNI2AJjg3NiM9DJg3EK5AMguF5eRFOlSP2l8yKcvsv5f5H0HWJqFAGedJ0o3v9pHTbA6j/z/AJs7f7bwoQf6V5fn8IAg+fhflHHFO7ta7t2FZXvZX79TQpDI5Io5o2imjSWJxh45EWSNx1wyMCrDIzggigDOh0LQ7aVZ7fRtKt51OVmh060ilU5ByskcKuDkA5BzkA9ad33f3sVl2X3GrSGFAHz38YPFKzvD4WsZQ6wyLc6qYzuzOADa2eVPJj3GeZCD+8NuPleNhWtOP2n8v8zKpLp956Z8PvDjeG/Dltb3CbL+8Y31+CMNHLMqiO3PcG3gWONwSf3wlIO0gCZy5n5LRFwVl57ncVBQUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQB85fEzwXc6TfN4s0NZI7Z5lub5bclJNOvvMDC9i8sBkgmkw7OCTBcktuCSoI9oSuuV9rLzXYynG3vLvr5Pud/4B+INr4lt4tO1CRINfiTayNhI9RWNctc22AqCXaC09sMFcNJCph3CKJwtqtvyKjK+j3/ADPTqgsKACgAoAKACgDzHx98QLXw1bzadp8iXGvTR7VRcPHpyyKdtzc9V84Ah4LZuWyksqiEqJbhDm1e35kSlbRb/kcB8M/Bdzq18vizXFkkt0na5sVuCXk1G+3ljey+YCzwQybnRyQZ7kBslInElzkkuVf8MTCN/efy833Po2sTUKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKAGOiSo8ciLJHIrJJG6h0dHBV0dGBVlZSVZWBDAkEEGgD568Z/Cy7s5pNY8JrJJEHM76XEzC7tHBDB9OYHfMgOWWAEXERCrD5+Qse0anSX3/wCZlKHWP3f5EHhv4u6jpuLDxNay38cR8s3kSiPU4ipKstzFK0cVyy4AJLW8wIYyPMx4HTT1i/8AIFNrRr/M9o0rxn4Y1pV+w6xZtK+MW08gtLrPdRb3PlSvgnBMaumcYYggnNxkt0zRSi+qOmqRhQBzOreMvDOio5vtYsxImf8ARreUXV2WHRfs9v5kiknjdIEQH7zKOaai3smJyS3Z4v4l+Lmoalu0/wAMW01jHK3lC9kCyalNv+ULbQp5kdqz5wrK08+drRvC4rVU0tZa/kZubei/4JP4M+Fl1ezR6x4sWSOFnE6aZKzG6u3Yl9+osTuhjJwzQEm4lJZZvIwVkJTS0j9/b0CMOsvu/wAz6FREiRI40WOONVSONFCIiIAqIiKAqqqgKqqAFAAAAFYmo+gAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgDldf8ABfh3xJufUrBPtRXaL62P2e8HGBumQYm2/wAC3CTIvIC4JBpSktmS4p7r5nkup/BK4DM2ja1FIhyUg1KF4mUc4DXNqJVc4wCwtIxnnHOBoqvdfcQ6fZ/ec8Phn8QbE7LN12g8Gz1gQJxznEkls3Un+HOc9jkvng/+Chckv6Yp+GnxBvTsvHXaTgteawJ0AxjJEclwxGCRwpOARjpk54Lb8EHJL+mdBpnwSuGIbWdahjA6waZC8zN9Lm6EIQj/AK9JAfbHKdXsvvGqfd/ceuaB4N8PeGhnTLBBc4Ia+uD9ovWB4IE7j90pHDR26wxtgFkJ5rNyb3ZoopbL59TqKkYUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFAA/9k=";
        const profileImg = channelInfo?.channelImageUrl;
        const channelPage = 'https://chzzk.naver.com/'+userId;
        const channelName = channelInfo?.channelName;

        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;

        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');
        watchers.innerHTML = `<span class="dot" role="img">🟢</span>${addNumberSeparator(concurrentUserCount ?? liveInfo.concurrentUserCount)}`;

        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,
        } = channel;

        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});
            } 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 (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('onerror', `this.onerror=null; this.src='${pp_jpg}'`);
        profilePicture.setAttribute('alt', `${userId}'`);

        const profileClickHandler = isSendLoadBroadEnabled ?
              // 프로필 클릭 & 현재 탭 & 빠른 전환
              `
        event.preventDefault();
        event.stopPropagation();
        if (document.getElementById('sidebar').offsetWidth === 52) {
            if(event.ctrlKey) return;
            if (document.body.querySelector('div.loading') && getComputedStyle(document.body.querySelector('div.loading')).display === 'none') {
                liveView.playerController.sendLoadBroad('${userId}', ${broadNo});
            } 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');
        watchers.innerHTML = `<span class="dot" role="img">🔴</span>${addNumberSeparator(totalViewCnt)}`;

        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('onerror', `this.onerror=null; this.src='${pp_jpg}'`);
        profilePicture.setAttribute('alt', `${userId}'`);

        const profileClickHandler = `
        event.preventDefault();
        event.stopPropagation();
        const sidebarWidth = document.getElementById('sidebar').offsetWidth;
        if (sidebarWidth === 52) {
            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.innerHTML = 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,
        } = 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('onerror', `this.onerror=null; this.src='${pp_jpg}'`);
        profilePicture.setAttribute('alt', userId);

        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');
        watchers.innerHTML = isFeeditem ? timeSince(isFeeditem.reg_date) : '<span class="dot profile-grayscale" role="img">🔴</span>오프라인';

        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) => {
        return new Promise((resolve, reject) => {
            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: (response) => {
                    if (timeoutId) clearTimeout(timeoutId); // 타임아웃 클리어

                    try {
                        //console.log(response.status);
                        if (response.status >= 200 && response.status < 300) {
                            const jsonResponse = JSON.parse(response.responseText);
                            if (jsonResponse?.code === 401) {
                                console.error(url, "Unauthorized: 401 error - possibly invalid credentials");
                                resolve([]); // 빈 배열로 반환
                            }
                            //console.log(jsonResponse);
                            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);
        }
    };

    let allFollowUserIds = [];

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

    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) {
                        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');
            const { DATA } = response;

            if (response.RESULT === -1) {
                return;
            }

            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 topBarHeight = document.getElementById('serviceHeader')?.offsetHeight ?? 0;

            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', async () => {
                        const uniqueId = `tooltip-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
                        tooltipContainer.setAttribute('data-tooltip-id', uniqueId); // 고유 식별자 설정

                        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 userId = element.getAttribute('user_id');
                        const randomTimeCode = Date.now();

                        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;

                                        // 고유 ID 확인 후 덮어쓰기
                                        if (tooltipContainer.getAttribute('data-tooltip-id') === uniqueId) {
                                            element.setAttribute('broad_thumbnail', newThumbnail);
                                            element.setAttribute('broad_start', newBroadStart);
                                            imgSrc = newThumbnail;
                                            broadStart = newBroadStart;
                                        }
                                    } else {
                                        console.error("Error: liveImageUrl is not available in the response.");
                                    }
                                } else {
                                    console.error(`Error fetching thumbnail: ${getThumbnailJson?.message || 'Unknown error'}`);
                                }
                            } catch (error) {
                                console.error("Error in fetching thumbnail:", error);
                            }
                        }

                        if (broadStart) {
                            if (imgSrc.startsWith("http")) {
                                imgSrc += `?${Math.floor(randomTimeCode / 10000)}`;
                            }
                        }

                        let tooltipContent = `<img src="${imgSrc}">`;

                        if (broadStart) {
                            const videoDuration = getElapsedTime(broadStart, "HH:MM");
                            tooltipContent += `<div class="duration-overlay">${videoDuration}</div>`;
                        } else if (vodDuration) {
                            tooltipContent += `<div class="duration-overlay">${vodDuration}</div>`;
                        }

                        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 ?? '';

                            if (watchers.includes('🔴')) {
                                watchers = `<span class="dot" role="img">🔴</span>${watchers.split('🔴')[1]}`;
                            }

                            if (watchers.includes('🟢')) {
                                watchers = `<span class="dot" role="img">🟢</span>${watchers.split('🟢')[1]}`;
                            }

                            tooltipContent += `<div class="tooltiptext">${username} · ${description} · ${watchers}<br>${broadTitle}</div>`;
                        } else {
                            tooltipContent += `<div class="tooltiptext">${broadTitle}</div>`;
                        }

                        if (tooltipContainer.getAttribute('data-tooltip-id') === uniqueId) {
                            Object.assign(tooltipContainer.style, {
                                left: `${offsetX}px`,
                                top: `${offsetY}px`,
                                display: 'block'
                            });
                            tooltipContainer.innerHTML = tooltipContent;
                        }
                    });

                    element.addEventListener('mouseleave', () => {
                        tooltipContainer.style.display = 'none';
                    });

                    element.setAttribute('data-tooltip-listener', 'true');
                }
            });
        } 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="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>

        <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="switchRemoveCarousel">
                <span class="slider round"></span>
            </label>
            <label for="switchRemoveCarousel">자동 재생되는 추천 채널 숨기기</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="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">즐겨찾기 채널 표시 수</label>
            <input type="range" id="favoriteChannelsDisplay" min="0" max="30">
            <span id="favoriteChannelsDisplayValue">${displayFollow}</span>
        </div>

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

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

        <div class="option">
            <label for="popularChannelsDisplay">인기 채널 표시 수</label>
            <input type="range" id="popularChannelsDisplay" min="0" max="30">
            <span id="popularChannelsDisplayValue">${displayTop}</span>
        </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="switchSmallUserLayout">
                <span class="slider round"></span>
            </label>
            <label for="switchSmallUserLayout">한 줄 방송 목록 사용</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="fixNotificationChannel">
                <span class="slider round"></span>
            </label>
            <label for="fixNotificationChannel">알림 설정된 채널을 상단 고정</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="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="switchSharpening">
                <span class="slider round"></span>
            </label>
            <label for="switchSharpening">폰트에 샤프닝 효과를 적용</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="switchDocumentTitleUpdate">
                <span class="slider round"></span>
            </label>
            <label for="switchDocumentTitleUpdate">탭 제목에 시청자 수 표시</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="switchAdaptiveSpeedControl">
                <span class="slider round"></span>
            </label>
            <label for="switchAdaptiveSpeedControl">화면을 길게 눌러서 2배속 빨리감기</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="showBufferTime">
                <span class="slider round"></span>
            </label>
            <label for="showBufferTime">방송 딜레이 (남은 버퍼 시간) 표시</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)로 앞당기기 (비 그리드 전용, 위 옵션 활성화시)</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="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="option">
            <label class="switch">
                <input type="checkbox" id="switchExpandLiveChat">
                <span class="slider round"></span>
            </label>
            <label for="switchExpandLiveChat">LIVE 채팅창 클릭시 확장하기</label>
        </div>
        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchAutoExpandLiveChat">
                <span class="slider round"></span>
            </label>
            <label for="switchAutoExpandLiveChat">자동으로 LIVE 채팅창 확장하기(위 옵션 활성화시)</label>
        </div>
        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchHideCursorOnScreenModeAndFullScreenMode">
                <span class="slider round"></span>
            </label>
            <label for="switchHideCursorOnScreenModeAndFullScreenMode">스크린모드와 전체화면에서 마우스 자동 숨김</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="switchExpandVODChat">
                <span class="slider round"></span>
            </label>
            <label for="switchExpandVODChat">VOD 채팅창 클릭시 확장하기</label>
        </div>
        <div class="option">
            <label class="switch">
                <input type="checkbox" id="switchAutoExpandVODChat">
                <span class="slider round"></span>
            </label>
            <label for="switchAutoExpandVODChat">자동으로 VOD 채팅창 확장하기(위 옵션 활성화시)</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="selectBlockWords">
                <span class="slider round"></span>
            </label>
            <label for="selectBlockWords">단어 차단<sup>2)</sup></label>
            <textarea id="blockWordsInput" placeholder="콤마(,)로 구분하여 단어 입력" style="width: 340px; height: 34px; border: 1px solid #ccc;">${registeredWords}</textarea>
        </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="switchUnlockCopyPaste">
                <span class="slider round"></span>
            </label>
            <label for="switchUnlockCopyPaste">채팅 입력란 복사/붙여넣기 기능 해제</label>
        </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>
        </div>
        <div class="option">
            <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>
        </div>
        <div class="option">
            <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>
        </div>
        <div class="option">
            <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="switchShowDeletedMessages">
                <span class="slider round"></span>
            </label>
            <label for="switchShowDeletedMessages">강제퇴장된 유저의 메시지 보기</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>3)</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="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;">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;">버그 신고는 <a href="https://greasyfork.org/ko/scripts/484713" target="_blank">https://greasyfork.org/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("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("switchSharpening", isSharpeningEnabled, "isSharpeningEnabled");
        setCheckboxAndSaveValue("switchAutoScreenMode", isAutoScreenModeEnabled, "isAutoScreenModeEnabled");
        setCheckboxAndSaveValue("switchAdjustDelayNoGrid", isAdjustDelayNoGridEnabled, "isAdjustDelayNoGridEnabled");
        setCheckboxAndSaveValue("switchHideButtonsAboveChatInput", ishideButtonsAboveChatInputEnabled, "ishideButtonsAboveChatInputEnabled");
        setCheckboxAndSaveValue("switchExpandVODChat", isExpandVODChatEnabled, "isExpandVODChatEnabled");
        setCheckboxAndSaveValue("switchAutoExpandVODChat", isAutoExpandVODChatEnabled, "isAutoExpandVODChatEnabled");
        setCheckboxAndSaveValue("switchExpandLiveChat", isExpandLiveChatEnabled, "isExpandLiveChatEnabled");
        setCheckboxAndSaveValue("switchAutoExpandLiveChat", isAutoExpandLiveChatEnabled, "isAutoExpandLiveChatEnabled");
        setCheckboxAndSaveValue("switchOpenExternalPlayer", isOpenExternalPlayerEnabled, "isOpenExternalPlayerEnabled");
        setCheckboxAndSaveValue("switchRemoveShadowsFromCatch", isRemoveShadowsFromCatchEnabled, "isRemoveShadowsFromCatchEnabled");
        setCheckboxAndSaveValue("switchChzzkFollowChannels", isChzzkFollowChannelsEnabled, "isChzzkFollowChannelsEnabled");
        setCheckboxAndSaveValue("switchChzzkTopChannels", isChzzkTopChannelsEnabled, "isChzzkTopChannelsEnabled");
        setCheckboxAndSaveValue("switchAdaptiveSpeedControl", isAdaptiveSpeedControlEnabled, "isAdaptiveSpeedControlEnabled");
        setCheckboxAndSaveValue("switchHideCursorOnScreenModeAndFullScreenMode", isHideCursorOnScreenModeAndFullScreenModeEnabled, "isHideCursorOnScreenModeAndFullScreenModeEnabled");
        setCheckboxAndSaveValue("switchShowDeletedMessages", isShowDeletedMessagesEnabled, "isShowDeletedMessagesEnabled");


        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 inputBox = document.getElementById('blockWordsInput');

        // 입력 상자의 내용이 변경될 때마다 설정 저장
        inputBox.addEventListener('input', () => {
            const inputValue = inputBox.value.trim();
            if (inputValue !== '') {
                registeredWords = inputValue;
                GM_setValue("registeredWords", 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();
        }
        GM_addStyle(CommonStyles);

        // 본문 방송 목록의 새 탭 열기 방지
        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");
    };

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

        const requestOptions = {
            method: 'POST',
            body: new URLSearchParams(data),
            credentials: 'include'
        };

        try {
            const response = await fetch('https://live.sooplive.co.kr/afreeca/player_live_api.php', requestOptions);
            const result = await response.json();
            return result.CHANNEL.AID || null;
        } catch (error) {
            console.log('오류 발생:', error);
            return null;
        }
    };

    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);
                        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 createHideButton = (listItem, optionsLayer) => {
            const hideButton = document.createElement('button'); // "숨기기" 버튼 생성
            hideButton.type = 'button';
            hideButton.innerHTML = '<span>이 브라우저에서 스트리머 숨기기</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';
            hideButton.innerHTML = `<span>이 브라우저에서 해당 카테고리 숨기기</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) {
                            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 enableAdaptiveSpeedControl = (videoSelector, delay = 1000, speed = 2.0, bufferThreshold = 2.5, textSelector = '.volume_text') => {
        const videoElement = document.querySelector(videoSelector);
        const textElement = document.querySelector(textSelector);
        let holdTimer = null;

        if (videoElement && textElement) {
            const checkBufferAndAdjustSpeed = () => {
                const buffered = videoElement.buffered;
                const currentTime = videoElement.currentTime;
                let bufferRemaining = 0;

                for (let i = 0; i < buffered.length; i++) {
                    if (currentTime >= buffered.start(i) && currentTime <= buffered.end(i)) {
                        bufferRemaining = buffered.end(i) - currentTime;
                        break;
                    }
                }

                if (bufferRemaining > bufferThreshold) {
                    videoElement.playbackRate = speed;
                    textElement.textContent = '2배속 >>';
                    textElement.classList.remove('hide_text');
                } else {
                    videoElement.playbackRate = 1.0;
                    textElement.classList.add('hide_text');
                }
            };

            const startTimer = () => {
                holdTimer = setTimeout(() => {
                    checkBufferAndAdjustSpeed();
                    videoElement.addEventListener('timeupdate', checkBufferAndAdjustSpeed);
                }, delay);
            };

            const stopTimer = () => {
                if (holdTimer) {
                    clearTimeout(holdTimer);
                    holdTimer = null;
                }
                videoElement.playbackRate = 1.0;
                textElement.classList.add('hide_text'); // 1배속일 때 다시 숨김
                videoElement.removeEventListener('timeupdate', checkBufferAndAdjustSpeed);
            };

            videoElement.addEventListener('mousedown', startTimer);
            videoElement.addEventListener('mouseup', stopTimer);
            videoElement.addEventListener('touchstart', startTimer);
            videoElement.addEventListener('touchend', stopTimer);
        } else {
            console.error('비디오 요소나 텍스트 요소를 찾을 수 없습니다.');
        }
    };
    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 = '0px';
                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 mouseLeaveHandler = () => {
            handleSidebarMouseOut();
        };

        // 이벤트 리스너 등록
        document.addEventListener('mousemove', mouseMoveHandler);
        document.addEventListener('mouseleave', mouseLeaveHandler);
    };


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

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

    const setupInputFocusHandlers = () => {
        document.body.querySelectorAll('input').forEach(input => {
            input.addEventListener('focus', () => {
                sharpModeCheckEnabled = false;
                delayCheckEnabled = false;
            });

            input.addEventListener('blur', () => {
                sharpModeCheckEnabled = true;
                delayCheckEnabled = true;
            });
        });
    };

    const setupWriteAreaInputHandler = () => {
        const writeArea = document.getElementById('write_area');
        writeArea.addEventListener('input', () => {
            sharpModeCheckEnabled = false;
            delayCheckEnabled = false;
        });
    };

    const setupKeydownHandler = (keyCode, toggleFunction) => {
        document.addEventListener('keydown', (event) => {
            if (event.keyCode === keyCode && document.activeElement.nodeName !== 'INPUT' && document.activeElement.id !== 'write_area') {
                toggleFunction();
            }
        });
    };

    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.');
        }
    };

    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 지연됨`;
            }
        };

    };

    let timerId_m;

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

        if (document.hidden) {
            // 탭이 비활성화됨
            timerId_m = setTimeout(() => {
                if (!button.classList.contains("mute")) {
                    button.click();
                    // console.log("탭이 비활성화됨, 음소거");
                }
            }, 1000);
        } else {
            // 탭이 활성화됨
            if (timerId_m) {
                clearTimeout(timerId_m);
            }
            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")) {
                        livePlayer.closeStreamConnector();
                        button.classList.remove("on", "pause");
                        button.classList.add("off", "play");
                        spanElement.textContent = "재생";
                    } else {
                        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();

        // 페이지 변경 시 메시지 삭제 함수 실행
        const observer = new MutationObserver(() => {
            if (isBlockWordsEnabled) deleteMessages();
        });

        const config = {
            childList: true,
            subtree: true
        };

        observer.observe(elem, config);
    };

    const deleteMessages = () => {
        const messages = document.body.querySelectorAll('div.message-text > p.msg:not(.done)');

        // 등록된 단어가 없으면 처리 완료 표시만 추가
        if (rw.length === 0) {
            messages.forEach(message => message.classList.add('done'));
            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.classList.add('filtered-message');
                    listItem.remove();
                }
            } else {
                message.classList.add('done');
            }
        }
    };

    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 = () => {
        // 해상도에 따라 bottomChat 클래스를 추가하거나 제거하는 함수
        const toggleBottomChat = () => {
            const isPortrait = window.innerHeight * 1.1 > window.innerWidth;
            document.body.classList.toggle('bottomChat', window.innerWidth < 1280 && isPortrait);
        };

        // 윈도우 리사이즈 이벤트를 감지하여 toggleBottomChat 함수를 호출합니다.
        window.addEventListener('resize', debounce(toggleBottomChat, 100));

        // 페이지 로드 시 한 번 실행하여 초기 설정을 수행합니다.
        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;
        }
        `);
    }

    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:not(.screen_mode,.fullScreen_mode) #chatting_area .chatting-item-wrap {
            cursor: pointer;
        }

        body:not(.screen_mode,.fullScreen_mode) #chatting_area .chatting-item-wrap.hide-cursor {
            cursor: none;
        }

        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 expandLiveChat = () => {
        waitForElement('#chatting_area .chatting-item-wrap', function (elementSelector, element) {
            element.addEventListener("click", function(event) {
                if (event.target.closest(".chat_scroll_down") || event.target.closest("#contextChatMenu")) {
                    return;
                }
                document.body.classList.toggle("expandLiveChat");
            });

            if (isAutoExpandLiveChatEnabled) document.body.classList.add("expandLiveChat");

            hideCursor(element);
        });
    }

    const showDeletedMessages = (element) => {

        const MAX_MESSAGES = 400;
        const OriginalWebSocket = window.WebSocket;
        const targetUrlPattern = /^wss:\/\/chat-[\w\d]+\.sooplive\.co\.kr/;
        const messageHistory = [];
        const bannedMessages = [];
        const streamerId = window.location.href.split('/')[3];
        let bannedWindow = null;
        let banIcon = null;

        GM_addStyle(`
            .trash-icon {
                position: absolute;
                bottom: 10px;
                right: 10px;
                width: 24px;
                height: 24px;
                cursor: pointer;
                z-index: 1000;
                background-size: contain;
                background-repeat: no-repeat;
            }

            html:not([dark="true"]) .trash-icon {
                background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0 0 50 50'%3E%3Cpath d='M 21.857422 7 C 20.282422 7 19 8.2824219 19 9.8574219 L 19 13 L 10.5 13 C 10.224 13 10 13.224 10 13.5 C 10 13.776 10.224 14 10.5 14 L 12.925781 14 L 14.292969 38.607422 C 14.399969 40.509422 15.974906 42 17.878906 42 L 32.121094 42 C 34.025094 42 35.601031 40.510375 35.707031 38.609375 L 37.074219 14 L 39.5 14 C 39.776 14 40 13.776 40 13.5 C 40 13.224 39.776 13 39.5 13 L 31 13 L 31 9.8574219 C 31 8.2824219 29.717578 7 28.142578 7 L 21.857422 7 z M 21.857422 8 L 28.142578 8 C 29.166578 8 30 8.8334219 30 9.8574219 L 30 13 L 20 13 L 20 9.8574219 C 20 8.8334219 20.832422 8 21.857422 8 z M 13.927734 14 L 36.072266 14 L 34.708984 38.552734 C 34.631984 39.924734 33.495094 41 32.121094 41 L 17.878906 41 C 16.504906 41 15.368016 39.925734 15.291016 38.552734 L 13.927734 14 z M 19.169922 19 C 18.894922 19.009 18.6775 19.241578 18.6875 19.517578 L 19.242188 35.517578 C 19.252187 35.787578 19.473188 35.998047 19.742188 35.998047 L 19.761719 35.998047 C 20.036719 35.989047 20.252188 35.758422 20.242188 35.482422 L 19.6875 19.482422 C 19.6785 19.206422 19.436922 18.962 19.169922 19 z M 25 19 C 24.724 19 24.5 19.224 24.5 19.5 L 24.5 35.431641 C 24.5 35.707641 24.724 35.931641 25 35.931641 C 25.276 35.931641 25.5 35.707641 25.5 35.431641 L 25.5 19.5 C 25.5 19.224 25.276 19 25 19 z M 30.830078 19 C 30.545078 18.98 30.3225 19.207422 30.3125 19.482422 L 29.755859 35.482422 C 29.745859 35.758422 29.963281 35.989047 30.238281 35.998047 L 30.255859 35.998047 C 30.524859 35.998047 30.745859 35.787578 30.755859 35.517578 L 31.3125 19.517578 C 31.3225 19.241578 31.105078 19.009 30.830078 19 z'%3E%3C/path%3E%3C/svg%3E");
            }

            html[dark="true"] .trash-icon {
                background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0,0,255.99431,255.99431'%3E%3Cg fill-opacity='1' fill='%23FFFFFF' fill-rule='nonzero' stroke='none' stroke-width='1' stroke-linecap='butt' stroke-linejoin='miter' stroke-miterlimit='10' stroke-dasharray='' stroke-dashoffset='0' font-family='none' font-weight='none' font-size='none' text-anchor='none' style='mix-blend-mode: normal'%3E%3Cg transform='scale(5.12,5.12)'%3E%3Cpath d='M21.85742,7c-1.575,0 -2.85742,1.28242 -2.85742,2.85742v3.14258h-8.5c-0.276,0 -0.5,0.224 -0.5,0.5c0,0.276 0.224,0.5 0.5,0.5h2.42578l1.36719,24.60742c0.107,1.902 1.68194,3.39258 3.58594,3.39258h14.24219c1.904,0 3.47994,-1.48962 3.58594,-3.39062l1.36719,-24.60937h2.42578c0.276,0 0.5,-0.224 0.5,-0.5c0,-0.276 -0.224,-0.5 -0.5,-0.5h-8.5v-3.14258c0,-1.575 -1.28242,-2.85742 -2.85742,-2.85742zM21.85742,8h6.28516c1.024,0 1.85742,0.83342 1.85742,1.85742v3.14258h-10v-3.14258c0,-1.024 0.83242,-1.85742 1.85742,-1.85742zM13.92773,14h22.14453l-1.36328,24.55273c-0.077,1.372 -1.21389,2.44727 -2.58789,2.44727h-14.24219c-1.374,0 -2.51089,-1.07427 -2.58789,-2.44727zM19.16992,19c-0.275,0.009 -0.49242,0.24158 -0.48242,0.51758l0.55469,16c0.01,0.27 0.231,0.48047 0.5,0.48047h0.01953c0.275,-0.009 0.49047,-0.23962 0.48047,-0.51562l-0.55469,-16c-0.009,-0.276 -0.25058,-0.52042 -0.51758,-0.48242zM25,19c-0.276,0 -0.5,0.224 -0.5,0.5v15.93164c0,0.276 0.224,0.5 0.5,0.5c0.276,0 0.5,-0.224 0.5,-0.5v-15.93164c0,-0.276 -0.224,-0.5 -0.5,-0.5zM30.83008,19c-0.285,-0.02 -0.50758,0.20742 -0.51758,0.48242l-0.55664,16c-0.01,0.276 0.20742,0.50662 0.48242,0.51563h0.01758c0.269,0 0.49,-0.21047 0.5,-0.48047l0.55664,-16c0.01,-0.276 -0.20742,-0.50858 -0.48242,-0.51758z'%3E%3C/path%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
            }
        `);

        unsafeWindow.WebSocket = function(url, protocols) {
            const ws = new OriginalWebSocket(url, protocols);

            if (targetUrlPattern.test(url)) {
                ws.addEventListener("open", (event) => {
                    console.log("WebSocket opened:", event);
                });

                ws.addEventListener("message", (event) => {
                    decodeMessage(event.data);
                });

                ws.addEventListener("close", (event) => {
                    console.log("WebSocket closed:", event);
                });

                ws.addEventListener("error", (event) => {
                    console.error("WebSocket error:", event);
                });
            }

            return ws;
        };

        unsafeWindow.WebSocket.prototype = OriginalWebSocket.prototype;

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

            // 24시간 형식 HH:MM:SS로 시간 표시
            const date = new Date();
            const hours = String(date.getHours()).padStart(2, '0');
            const minutes = String(date.getMinutes()).padStart(2, '0');
            const seconds = String(date.getSeconds()).padStart(2, '0');
            const timestamp = `${hours}:${minutes}:${seconds}`; // HH:MM:SS 형식

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

                recordMessage(userId, userName, message, timestamp);
            } 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;

                // 강제 퇴장 메시지 추가
                const userMessages = messageHistory.filter(msg => msg.userId === userId);
                bannedMessages.push(...userMessages);

                // 강제 퇴장 메시지를 마지막에 추가
                bannedMessages.push({
                    userId: userId,
                    userName: userName,
                    message: ` 님이 강제 퇴장 되었습니다.`,
                    timestamp: timestamp
                });

                updateBannedMessages();
            }
        }

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

        const createBanIcon = (container) => {
            banIcon = document.createElement("div"); // Initialize the global banIcon variable
            banIcon.classList.add("trash-icon");
            container.appendChild(banIcon);

            // 클릭 이벤트 리스너 추가
            banIcon.addEventListener("click", (event) => {
                event.preventDefault();
                event.stopPropagation();
                showBannedMessages();
            });
        };

        const addRedDot = () => {
            if (!banIcon?.querySelector(".red-dot")) {
                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"
                });
                banIcon.appendChild(redDot);
            }
        };

        const removeRedDot = () => {
            const redDot = banIcon?.querySelector(".red-dot");
            if (redDot) redDot.remove();
        };

        const showBannedMessages = () => {
            if (bannedWindow && !bannedWindow.closed) {
                bannedWindow.focus();
            } else {
                bannedWindow = window.open("", "_blank", "width=800,height=600");
                if (bannedWindow) {
                    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();
                    removeRedDot();
                }
            }
        };

        const updateBannedMessages = () => {
            if (bannedWindow && !bannedWindow.closed) {
                const messageList = bannedWindow.document.getElementById("bannedMessages");
                messageList.innerHTML = "";

                if (bannedMessages.length === 0) {
                    messageList.innerHTML = "<li>메시지가 없습니다.</li>";
                } else {
                    bannedMessages.forEach(msg => {
                        const listItem = document.createElement("li");
                        listItem.innerHTML = `<span class="message-timestamp">[${msg.timestamp}]</span> <strong>${msg.userName} (${msg.userId})</strong>: ${msg.message}`;
                        messageList.appendChild(listItem);
                    });
                }
            } else {
                addRedDot();
            }
        };

        createBanIcon(element);
    }

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


    //============================ 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;
            }
            .expandVODChat:not(.screen_mode,.fullScreen_mode) #chatting_area .chatting-item-wrap {
                cursor: pointer;
            }
            .expandVODChat:not(.screen_mode,.fullScreen_mode) #chatting_area .chatting-item-wrap.hide-cursor {
                cursor: none;
            }
        `;
        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")) {
        if (isPreviewModalEnabled || isReplaceEmptyThumbnailEnabled) {
            loadHlsScript();
        }

        if (isCustomSidebarEnabled) document.body.classList.add('customSidebar');
        if (isSharpeningEnabled) document.body.classList.add('sharpening');

        GM_addStyle(mainPageCommonStyles);

        observeDarkAttributeChange((darkValue) => {
            if(darkValue){
                GM_addStyle(mainPageDarkmodeStyles);
            } else {
                GM_addStyle(mainPageWhitemodeStyles);
            }
        });

        waitForElement('#serviceLnb', function (elementSelector, element) {
            if (isCustomSidebarEnabled) makeTopNavbarAndSidebar("main");
            runCommonFunctions();
        });

        removeUnwantedTags();

        processStreamers();

        return;
    }


    //============================ 플레이어 페이지 실행 ============================//

    if (CURRENT_URL.startsWith("https://play.sooplive.co.kr")) {
        // Embed 페이지에서는 실행하지 않음
        const pattern = /^https:\/\/play.sooplive.co.kr\/.*\/.*\/embed(\?.*)?$/;
        if (pattern.test(CURRENT_URL) || CURRENT_URL.includes("vtype=chat")) {
            return;
        }

        if (isCustomSidebarEnabled) document.body.classList.add('customSidebar');
        if (isSharpeningEnabled) document.body.classList.add('sharpening');

        GM_addStyle(playerCommonStyles);

        observeDarkAttributeChange((darkValue) => {
            if(darkValue){
                GM_addStyle(darkModePlayerStyles);
            } else {
                GM_addStyle(whiteModePlayerStyles);
            }
        });

        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(isAdaptiveSpeedControlEnabled){
            waitForElement('#livePlayer', function (elementSelector, element) {
                enableAdaptiveSpeedControl(elementSelector);
            });
        }
        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 (isExpandLiveChatEnabled) {
            // MutationObserver 생성
            const observer = new MutationObserver((mutationsList) => {
                for (const mutation of mutationsList) {
                    if (mutation.type === 'attributes' && document.body.classList.contains('ratio169_mode')) {
                        // 클래스가 추가된 것을 감지했을 때 함수 실행
                        addStyleExpandLiveChat();
                        expandLiveChat();
                        observer.disconnect(); // 더 이상 감시하지 않도록 종료
                        break;
                    }
                }
            });

            // body의 클래스 속성 변화를 감시
            observer.observe(document.body, { attributes: true, attributeFilter: ['class'] });
        }

        if (isHideCursorOnScreenModeAndFullScreenModeEnabled) {
            waitForElement('#player', function (elementSelector, element) {
                hideCursor(element);
            });
        }

        if (isShowDeletedMessagesEnabled) {
            waitForElement(".chatting-item-wrap", function (elementSelector, element) {
                showDeletedMessages(element);
            });
        }

        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)) {

            // vodCore 변수가 선언될 때까지 대기하는 함수
            const waitForVodCore = () => {
                const checkVodCore = setInterval(() => {
                    if (vodCore?.playerController?._currentMediaInfo?.name) {
                        clearInterval(checkVodCore); // setInterval 정지
                        checkMediaInfo(vodCore.playerController._currentMediaInfo.name); // vodCore 변수가 정의되면 미디어 정보 확인 함수 호출
                    }
                }, 500); // 500ms 주기로 확인
            };

            if(isSelectBestQualityEnabled){
                waitForVodCore();
            }

            GM_addStyle(CommonStyles);

            // VOD 채팅창
            waitForElement('#webplayer_contents', function (elementSelector, element) {
                observeChat(elementSelector,element);
            });

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

            if (isAlignNicknameRightEnabled) {
                alignNicknameRight();
            }

            if (isExpandVODChatEnabled) {
                addStyleExpandVODChat();
                waitForElement('#chatting_area .chatting-item-wrap', function (elementSelector, element) {
                    if (isAutoExpandVODChatEnabled) document.body.classList.add("expandVODChat");
                    element.addEventListener("click", function(event) {
                        if (event.target.closest(".chat_scroll_down") || event.target.closest("#contextChatMenu")) {
                            return;
                        }
                        document.body.classList.toggle("expandVODChat");
                    });
                    hideCursor(element);
                });
            }

            if (isHideCursorOnScreenModeAndFullScreenModeEnabled) {
                waitForElement('#videoLayerCover', function (elementSelector, element) {
                    hideCursor(element);
                });
            }

            // 캐치 페이지
        } else if (isCatchUrl(CURRENT_URL)) {

            if (isCustomSidebarEnabled) document.body.classList.add('customSidebar');
            if (isSharpeningEnabled) document.body.classList.add('sharpening');

            GM_addStyle(mainPageCommonStyles);

            observeDarkAttributeChange((darkValue) => {
                if(darkValue){
                    GM_addStyle(mainPageDarkmodeStyles);
                } else {
                    GM_addStyle(mainPageWhitemodeStyles);
                }
            });

            waitForElement('#serviceLnb', function (elementSelector, element) {
                if (isCustomSidebarEnabled) makeTopNavbarAndSidebar("main");
                runCommonFunctions();
            });

            if (isRemoveShadowsFromCatchEnabled) addStyleRemoveShadowsFromCatch();

        }

    }

})();