ArcaLive PostSidebar 배포용

이 스크립트는 Arcalive 웹 페이지에서 우측에 인접 게시글 패널을 생성합니다. 사용자 익명화, 키보드 단축키 추가 등의 기능을 포함하며, 특정 요소를 숨깁니다.

目前為 2025-03-11 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         ArcaLive PostSidebar 배포용
// @namespace    ArcaLive PostSidebar
// @version      1.1
// @description  이 스크립트는 Arcalive 웹 페이지에서 우측에 인접 게시글 패널을 생성합니다. 사용자 익명화, 키보드 단축키 추가 등의 기능을 포함하며, 특정 요소를 숨깁니다.
// @author       Hess
// @match        https://arca.live/*
// @run-at       document-idle
// @icon         https://i.namu.wiki/i/uDNhs7D-YhK4rVCOjzk6NLNzbC58cvwSpMHw-b0mG8XGgPA1uxFI1JqUFBE1gLHvSWhq1LNrXuwchq6TPh1WIg.svg
// @grant        GM_getValue
// @grant        GM_setValue
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';
    const maxGauge = 7; // 추천 버튼(F키)를 눌러야하는 횟수, 1 이상의 값으로 변경 가능

    if (window.self === window.top) {
        window.currentPrevPage = (function() {
            const urlParams = new URLSearchParams(window.location.search);
            const pageFromUrl = parseInt(urlParams.get('p'));
            const pageFromDOM = getCurrentPageNumber();
            return pageFromUrl || pageFromDOM || 1;
        })();
        window.prevLoadCount = 0;
        window.MAX_PREV_LOAD_COUNT = 3;
    }
    // 현재 페이지의 방문 기록을 저장
    storeCurrentPage();

    // 우측에 인접 게시글 n개 생성
    const n = 15;
    createAdjacentPostsSection(n);
    const makeBorder = true; // 이전, 현재, 다음 게시물들 사이에 경계 넣기

    // 기본 익명화 최초 설정 값 (이후 스크립트에 저장함, h키로 토글하여 변경 가능)
    let DEFAULT_ANONYMIZE_SETTING = false;
    // 로컬 스토리지에서 익명화 설정 값을 불러오거나, 없으면 기본값 사용
    let anonymizeSetting = GM_getValue("anonymizeSetting", DEFAULT_ANONYMIZE_SETTING);

    // 닉네임 익명화
    let anony = false; // 글 작성자, 댓글 작성자, 사이드바 게시물 익명화
    let anony2 = false; // 기존 게시글 목록 익명화

    anony = anonymizeSetting; // 글 작성자, 댓글 작성자, 사이드바 게시물 익명화
    anony2 = anonymizeSetting; // 기존 게시글 목록 익명화

    // 댓글 입력창에 색 넣기
    const replyColoring = true;

    // 세로 모드이면 일부 요소 제거
    hideElementsInPortrait(detectScreenMode());
    // 세로로 인식시키고 강제로 제거
    // hideElementsInPortrait("Portrait");

    // 새로운 키 기능 추가
    const keyActionsEnabled = true;
    const myLink = 'https://arca.live/b/holopro'; // Shift + Q 단축키로 이동할 링크


    let recommendButton = document.querySelector('button#rateUp.item');
    let pressedRecommendButton = document.querySelector('button#rateUp.item.already');
    recommendButton = pressedRecommendButton ? pressedRecommendButton : recommendButton;
    recommendButton.style.backgroundColor = pressedRecommendButton ? 'Azure' : '#F5F5F5';
    let recommendCount = pressedRecommendButton ? 2 * maxGauge : 0;
    // 버튼 텍스트가 위에 표시되도록 설정
    recommendButton.style.zIndex = '1';

    // const background = recommendButton.querySelector('.blue-background');

    function fillRecommendGauge(recommendCount) {
        if (maxGauge === 1) return;
        if (maxGauge > 7) {fillRecommendGauge2(recommendCount); return;}
        if (recommendCount < 2 * maxGauge - 1) {
        // 배경 부분 생성
            const newBackground = document.createElement('div');

            if (recommendCount === 1/*|| recommendCount === 2 * maxGauge*/) {
                Object.assign(newBackground.style, {
                    position: 'absolute',
                    id: 'firstNewBackground',
                    width: recommendButton.offsetWidth + 0.2 -2 + 'px',
                    height: recommendButton.offsetHeight * recommendCount / (maxGauge) - 1 + 'px', // 비율에 따라 계산
                    backgroundColor: 'Azure',
                    bottom: '-0px',
                    left: '-0px',
                    zIndex: '-1000',
                    borderRadius: '12px',
                    // border: '1px solid rgb(186, 186, 186)',
                });
            } else {
                // 파란색 배경 스타일 추가
                Object.assign(newBackground.style, {
                    position: 'absolute',
                    width: recommendButton.offsetWidth - 1.8 + 'px',
                    height: recommendButton.offsetHeight * recommendCount / (2 * maxGauge) - 5 + 'px', // 비율에 따라 계산
                    backgroundColor: 'Azure',
                    bottom: '4px',
                    left: '0',
                    zIndex: '-1000',
                    borderRadius: '0',
                });
            }
            recommendButton.style.zIndex = '2';
            recommendButton.appendChild(newBackground);
        }
    }

    function fillRecommendGauge2(recommendCount) {
        if (recommendCount < 2 * maxGauge - 1) {
            const newBackground = document.createElement('div');
            if (recommendCount <= maxGauge / 20) {
                Object.assign(newBackground.style, {
                    position: 'absolute',
                    id: 'firstNewBackground',
                    width: recommendButton.offsetWidth + 0.2 - 2 - 8 + 'px',
                    height: recommendButton.offsetHeight * recommendCount / (3 * maxGauge) + 'px', // 비율에 따라 계산
                    backgroundColor: 'Azure',
                    bottom: '0px',
                    left: '4px',
                    zIndex: '-1000',
                    borderRadius: '2px',
                    // border: '1px solid rgb(186, 186, 186)',
                });
            } else if (recommendCount <= maxGauge / 18) {
                Object.assign(newBackground.style, {
                    position: 'absolute',
                    id: 'firstNewBackground',
                    width: recommendButton.offsetWidth + 0.2 - 2 - 6 + 'px',
                    height: recommendButton.offsetHeight * recommendCount / (2 * maxGauge) + 'px', // 비율에 따라 계산
                    backgroundColor: 'Azure',
                    bottom: '0px',
                    left: '3px',
                    zIndex: '-1000',
                    borderRadius: '3px',
                    // border: '1px solid rgb(186, 186, 186)',
                });
            } else if (recommendCount <= maxGauge / 11) {
                Object.assign(newBackground.style, {
                    position: 'absolute',
                    id: 'firstNewBackground',
                    width: recommendButton.offsetWidth + 0.2 -2 - 4 + 'px',
                    height: recommendButton.offsetHeight * recommendCount / (2 * maxGauge) + 'px', // 비율에 따라 계산
                    backgroundColor: 'Azure',
                    bottom: '0px',
                    left: '2px',
                    zIndex: '-1000',
                    borderRadius: '4px',
                    // border: '1px solid rgb(186, 186, 186)',
                });
            } else if (recommendCount <= maxGauge / 7) {
                Object.assign(newBackground.style, {
                    position: 'absolute',
                    id: 'firstNewBackground',
                    width: recommendButton.offsetWidth + 0.2 -2 - 3 + 'px',
                    height: recommendButton.offsetHeight * recommendCount / (2 * maxGauge) + 'px', // 비율에 따라 계산
                    backgroundColor: 'Azure',
                    bottom: '-0px',
                    left: '1.5px',
                    zIndex: '-1000',
                    borderRadius: '10px',
                    // border: '1px solid rgb(186, 186, 186)',
                });
            } else if (recommendCount <= maxGauge * 2 / 7) {
                Object.assign(newBackground.style, {
                    position: 'absolute',
                    id: 'firstNewBackground',
                    width: recommendButton.offsetWidth + 0.2 -2 - 1 + 'px',
                    height: (recommendButton.offsetHeight * recommendCount / (2 * maxGauge)) * (recommendCount - maxGauge / 7) / (maxGauge / 7)
                            + (recommendButton.offsetHeight * (13 / 14) * recommendCount / (2 * maxGauge) - 4) * (2 * maxGauge / 7 - recommendCount) / (maxGauge / 7)
                            + 'px', // 비율에 따라 계산
                    backgroundColor: 'Azure',
                    bottom: (recommendButton.offsetHeight / 14 - 2) * (2 * maxGauge / 7 - recommendCount) / (maxGauge / 7) + 'px',
                    left: '0.5px',
                    zIndex: '-1000',
                    borderRadius: '12px',
                    // border: '1px solid rgb(186, 186, 186)',
                });
            } else {
                // 파란색 배경 스타일 추가
                Object.assign(newBackground.style, {
                    position: 'absolute',
                    width: recommendButton.offsetWidth - 1.8 + 'px',
                    height: recommendButton.offsetHeight * (13 / 14) * recommendCount / (2 * maxGauge) - 4 + 'px', // 비율에 따라 계산
                    backgroundColor: 'Azure',
                    bottom: recommendButton.offsetHeight / 14 - 2 + 'px',
                    left: '0',
                    zIndex: '-1000',
                    borderRadius: '2px',
                });
            }
            recommendButton.style.zIndex = '2';
            recommendButton.appendChild(newBackground);
        }
    }


    function pressRecommendButton() {
        recommendButton.style.backgroundColor = 'Azure';
        recommendButton.click();
    }
    // 키 동작 기능
    const keyHandlers = {
        keydown: {
            "f": () => {
                recommendCount++;
                console.log(recommendCount);
                fillRecommendGauge(recommendCount);
                recommendCount++
                fillRecommendGauge(recommendCount);
                if (recommendCount >= 2 * maxGauge) {
                    console.log('recommendCount reached to the max!');
                    pressRecommendButton();
                    recommendButton.getElementById('firstNewBackground').remove();
                    //recommendCount--;
                }
            },
            "j": () => {
                if (/^https:\/\/arca\.live\/b\/holopro\/\d+(?:\?p=\d+)?$/.test(window.location.href)) {
                    console.log("글 번호가 포함된 URL입니다.");
                } else {
                    console.log("글 번호가 포함되지 않은 URL입니다.");
                }
            },
            "d": () => {scrollHandler('down');}, // 아래로 빠르게, 아래로 느리게, 멈춤
            "n": () => {hideElementsInPortrait("Portrait");}, // 일부 요소 직접 제거
            "g": () => {
                if (/^https:\/\/arca\.live\/b\/[^\/?]+(?:\?p=[1-9]\d*)?$/.test(window.location.href)) { // 이 조건은 글 페이지, 목록 페이지 구분법 가져와도 되긴 함
                    const firstPost = document.querySelector('a.vrow.column:not(.notice)');
                    window.location.href = firstPost.href;
                } else if (0) {
                } else { // 새로운 댓글 버튼 클릭 기능
                    const newCommentButton = document.querySelector('a.newcomment-alert.w-100.fetch-comment.d-block');
                    if (newCommentButton) {
                        const precount = getCommentCount(); // 댓글 갱신 이전의 개수 ?????

                        newCommentButton.click();
                        applyBackgroundColors1();
                        console.log("댓글 갱신이 진행됩니다");
                        const newCommentAlert = 'a.newcomment-alert.w-100.fetch-comment.d-block';
                        hideAll(newCommentAlert);

                        setTimeout(() => {
                            const count = getCommentCount();
                            console.log("새로운 댓글 개수:", count);
                            storeCurrentPage(); // 바뀐 댓글 개수 저장
                            setTimeout(() => {
                                applyBackgroundColors2(); // 댓글 아랫쪽 색상 변경
                            }, 2000); // 1+2초 후 호출
                        }, 1000); // 1초 후 호출
                        cloneAndOverlayLastComment();
                        function runCloneAndOverlayFor3Seconds() {
                            const interval = setInterval(() => {
                                cloneAndOverlayLastComment();
                            }, 10);
                            setTimeout(() => {
                                clearInterval(interval);
                            }, 500);
                        }
                        runCloneAndOverlayFor3Seconds();

                        storeCurrentPage(); // 최근 방문 시간, 댓글 수 새로 저장
                    } else {
                        console.log("댓글 추가 없음");
                    }
                }
            },
            "h": () => {
                const toggle = toggleAnonymizeSetting();
                let anony = toggle; // 글 작성자, 댓글 작성자, 사이드바 게시물 익명화
                let anony2 = toggle; // 기존 게시글 목록 익명화
                location.reload(); // 새로고침
            },
            "p": () => {

            },

        },
        keydownShift: {
            "Q": () => {window.location.href = myLink;},
            "D": () => {scrollHandler('up');}, // 위로 빠르게, 위로 느리게, 멈춤
            // "P": () => {cleanOldVisitedPages(1);},
        },
    }

    function updateReplyColors() {
        if (replyColoring) {
            applyBackgroundColors1(); // 댓글 윗쪽 색상 변경
            applyBackgroundColors2(); // 댓글 아랫쪽 색상 변경
        }
    }
    //////////////////////////////////////

    // 전역 변수 선언
    let scrollDirection = null; // 'up' 또는 'down'
    let scrollSpeed = 0; // 0: 정지, 1: 빠른 스크롤, 2: 느린 스크롤
    let scrollInterval = null;
    let stopTimeout = null;
    let loadFinished = false;
    var visitedPages = {};

    //////////////////////////////////////

    // 익명화 설정 값을 변경하고 로컬 스토리지에 저장하는 함수
    function setAnonymizeSetting(newSetting) {
        if (typeof newSetting === "boolean") {
            anonymizeSetting = newSetting;
            GM_setValue("anonymizeSetting", anonymizeSetting);
            console.log("익명화 설정이 업데이트되었습니다:", anonymizeSetting);
        } else {
            console.error("익명화 설정은 boolean 값이어야 합니다.");
        }
    }

    function toggleAnonymizeSetting() {
        // 기존 설정 값을 로컬 스토리지에서 불러옴
        let currentSetting = GM_getValue("anonymizeSetting", DEFAULT_ANONYMIZE_SETTING);
        // 값을 반대로 토글
        let newSetting = !currentSetting;
        // 로컬 스토리지에 저장 및 전역 변수 업데이트
        GM_setValue("anonymizeSetting", newSetting);
        anonymizeSetting = newSetting;
        console.log("익명화 설정이 토글되었습니다:", newSetting);
        return newSetting;
    }

    //////////////////////////////////////

    // 사이드바 아이템 제거
    document.querySelectorAll('.sidebar-item').forEach(element => element.remove());
    // *ㅎㅎ 공지 제거
    const notices = document.querySelectorAll('a.vrow.column.notice');
    const filteredElements = Array.from(notices).filter(element => element.textContent.includes('*ㅎㅎ'));
    filteredElements.forEach(element => {element.remove();});

    function isIframe() {return window.self !== window.top;}
    if (isIframe()) return;

    // 🔄 스크립트 실행 시 스타일 추가
    const style = document.createElement('style');
    style.textContent = `
        .my-script-hidden-post {
            display: none;  /* 처음엔 보이지 않음 */
        }
    `;
    document.head.appendChild(style);

    // 댓글 입력창 색칠 (색칠 여부는 자동 판단)
    updateReplyColors();
    // 이후 코드에서 클릭의 경우도 감지, g키도 확인

    // 댓글이 0개인 경우 + 댓글 입력창 상단부
    function applyBackgroundColors1() {
        // 댓글 입력창 상단부 색칠
        const elements = [
            { selector: '.reply-form .reply-form__container .reply-form__user-info', color: 'lightgreen' },
            { selector: '.reply-form-button-container', color: 'lightgreen' },
            { selector: '.reply-form-arcacon-button.btn-namlacon', color: '#32CD32' }
        ];
        elements.forEach(({ selector, color }) => {
            const element = document.querySelector(selector);
            if (element) element.style.backgroundColor = color;
        });

        // 댓글 0개일 때 댓글 개수 칸을 하늘색으로 칠함
        // 댓글 개수 칸 선택
        const commentCounterBar = document.querySelector('.article-comment.position-relative .title');
        const writeButton = document.querySelector('#comment .title .btn-arca-article-write');
        if (!commentCounterBar) { // 없으면 목록 페이지이고, 색칠할 필요 없음
            return;
        }
        let previousColor = window.getComputedStyle(commentCounterBar).backgroundColor;
        let previousColor2 = window.getComputedStyle(writeButton).backgroundColor;

        const startTime = Date.now();
        const interval = setInterval(() => {
            const currentTime = Date.now();
            if (currentTime - startTime >= 3000) {
                clearInterval(interval); // 길어야 3초 후 종료
                return;
            }

            // 댓글 개수 칸 색 변경
            const newColor = getCommentCount() === 0 ? 'rgb(130, 206, 235)' : 'rgba(0, 0, 0, 0)';
            const newColor2 = getCommentCount() === 0 ? 'rgb(50, 148, 235)' : 'rgba(0, 0, 0, 0)';
            // DodgerBlue : rgb(30, 144, 255)
            // CornflowerBlue : rgb(100, 149, 237)
            // DeepSkyBlue : rgb(0, 191, 255)

            if (newColor !== previousColor) {
                commentCounterBar.style.backgroundColor = newColor;
                writeButton.style.backgroundColor = newColor2;
                writeButton.style.borderColor = newColor2;
                clearInterval(interval); // 색을 변경했으니 종료
                return;
            }
        }, 50);
    }

    // 댓글이 하나 이상 있는 경우
    // 댓글을 새로 불러오면 기존의 댓글들이 싹 새로 불러와져서 색을 되돌리는 과정은 필요없음
    function applyBackgroundColors2() {
        const comments = document.querySelectorAll('.comment-wrapper');
        if (comments.length > 0) {
            const lastComment = comments[comments.length - 1]; // 마지막 댓글 선택
            const infoRow = lastComment.querySelector('.content .info-row.clearfix');
            const message = lastComment.querySelector('.content .message');

            // 색 변경
            if (infoRow) infoRow.style.backgroundColor = 'skyblue';
            infoRow.style.setProperty("transition", "none", "important");
            if (message) message.style.backgroundColor = 'Azure'; // AliceBlue, Azure 추천
            message.style.setProperty("transition", "none", "important");
        }
    }

    // g키를 누르면 댓글 갱신
    function cloneAndOverlayLastComment() {
        const comments = document.querySelectorAll('.comment-wrapper');
        const lastComment = comments[comments.length - 1];
        const clone = lastComment.cloneNode(true);
        if (!clone) return;
        clone.id = 'clonedComment-userScript';
        lastComment.style.display = "none";

        // 원본 스타일 복사
        const computedStyle = window.getComputedStyle(lastComment);
        clone.style.textAlign = computedStyle.textAlign;
        clone.style.fontSize = computedStyle.fontSize;
        clone.style.animation = "none";
        clone.querySelectorAll('img').forEach(imgClone => { // 이미지 크기 유지
            const originalImg = lastComment.querySelector(`img[src="${imgClone.src}"]`);
            if (originalImg) {
                const originalImgStyle = window.getComputedStyle(originalImg);
                imgClone.style.width = originalImgStyle.width;
                imgClone.style.height = originalImgStyle.height;
            }
        });

        // 위치와 크기 복사
        const rect = lastComment.getBoundingClientRect();
        // 원본 스타일 유지
        clone.querySelectorAll('[id]').forEach(el => el.removeAttribute('id'));
        clone.querySelectorAll('img').forEach(imgClone => {
            const originalImg = lastComment.querySelector(`img[src="${imgClone.src}"]`);
            if (originalImg) {
                const originalImgStyle = window.getComputedStyle(originalImg);
                imgClone.style.width = originalImgStyle.width;
                imgClone.style.height = originalImgStyle.height;
            }
        });
        // 신고 버튼 구현
        const cloneAlert = clone.querySelector('.icon.ion-alert');
        cloneAlert.parentNode.onclick = function () {
        };

        // 삭제 버튼 구현
        const cloneDelete = clone.querySelector('.icon.ion-trash-b');
        if (cloneDelete) {
            cloneDelete.parentNode.addEventListener('click', function(event) {
            });
        };

        // 수정 버튼 구현
        const cloneCompose = clone.querySelector('.icon.ion-compose');
        if (cloneCompose) {
            cloneCompose.parentNode.addEventListener('click', function(event) {
                event.preventDefault();
                const list = document.querySelectorAll('#clonedComment-userScript');
                list.forEach((element, index) => {
                    if (index > 0 && list[index - 1].style.display === 'none') element.remove();
                });
                list[list.length - 1].style.display = '';
                const hiddenComment = Array.from(document.querySelectorAll('.comment-wrapper'))
                .find(element => getComputedStyle(element).display === 'none');
                hiddenComment.style.display = '';
                clone.style.display = 'none';
                hiddenComment.querySelector('.icon.ion-compose').parentNode.click();
            });
        };

        // 답글 버튼 구현
        const cloneReply = clone.querySelector('.icon.ion-reply');
        if (cloneReply) {
            cloneReply.parentNode.addEventListener('click', function(event) {
                event.preventDefault();
                const list = document.querySelectorAll('#clonedComment-userScript');
                list.forEach((element, index) => {
                    if (index > 0 && list[index - 1].style.display === 'none') element.remove();
                });
                list[list.length - 1].style.display = '';
                const hiddenComment = Array.from(document.querySelectorAll('.comment-wrapper'))
                .find(element => getComputedStyle(element).display === 'none');
                hiddenComment.style.display = '';
                clone.style.display = 'none';
                hiddenComment.querySelector('.icon.ion-reply').parentNode.click();
            });
        };

        const fadein = lastComment.querySelector('.content-item fadein');
        const infoRow = clone.querySelector('.content .info-row.clearfix');
        infoRow.style.animation = "none";
        const message = clone.querySelector('.content .message');
        // 색 변경
        if (infoRow) infoRow.style.backgroundColor = 'skyblue';
        infoRow.style.setProperty("transition", "none", "important");

        if (message) message.style.backgroundColor = 'Azure'; // AliceBlue, Azure 추천
        message.style.setProperty("transition", "none", "important");

        lastComment.parentNode.style.setProperty("transition", "none", "important");

        if (message) message.style.backgroundColor = 'Azure'; // AliceBlue, Azure 추천 // orange
        lastComment.parentNode.appendChild(clone);
        comments.forEach(comment => {
        });
    }
    let commentNumberChanged = false;

    const newCommentAlert = 'a.newcomment-alert.w-100.fetch-comment.d-block';
    function hideFirst(selector) {
        const elements = document.querySelectorAll(selector);
        if (elements.length >= 2) {
            elements[0].style.backgroundColor = 'pink'; // 작동함!!
            elements[0].style.display = 'none'; // 요소를 완전히 숨김
            elements[0].style.setProperty("display", "none", "important");
        }
    }

    function hideAll(selector) {
        document.querySelectorAll(selector).forEach(element => {
            element.style.setProperty("display", "none", "important");
        });
    }

    // MutationObserver를 설정하는 함수
    function observeDOMChanges(targetNode) {
        if (!(targetNode instanceof Node)) {
            console.error("오류: MutationObserver를 실행할 대상이 유효한 Node가 아닙니다.", targetNode);
            return;
        }
        const observer = new MutationObserver(() => {
            hideFirst(newCommentAlert);
        });

        observer.observe(targetNode, { childList: true, subtree: true });
    }
    document.addEventListener("DOMContentLoaded", () => {
        observeDOMChanges(document.body);
    });

    function observeAndCloneNewCommentButton() {
        const callback = (mutationsList, observer) => {
            const newCommentButton = document.querySelector('a.newcomment-alert.w-100.fetch-comment.d-block'); // "새로운 댓글이 달렸습니다" 버튼
            if (newCommentButton) {
                const clone = newCommentButton.cloneNode(true);
                const comments = document.querySelectorAll('.comment-wrapper');
                const lastComment = comments[comments.length - 1];

                if (lastComment && !commentNumberChanged) {
                    lastComment.parentNode.appendChild(clone);
                    commentNumberChanged = true;
                }
            } else commentNumberChanged = false;
        };

        const observer = new MutationObserver(callback);
        observer.observe(document.body, { childList: true, subtree: true });
    }

    // 감시 시작
    observeAndCloneNewCommentButton();

    // 광고 제거
    ['.sticky-container .ad', '.banner'].forEach(selector => document.querySelector(selector)?.remove());
    document.querySelector('.ad#svQazR5NHC3xCQr3')?.remove();

    function scrollHandler(inputDirection) {
        if (scrollSpeed === 0 || scrollDirection !== inputDirection) {
            scrollDirection = inputDirection;
            scrollSpeed = 1;
        } else {
            scrollSpeed = scrollSpeed === 1 ? 2 : 0;
            if (scrollSpeed === 0) scrollDirection = null;
        }

        clearInterval(scrollInterval);
        clearTimeout(stopTimeout);
        if (scrollSpeed === 0) return;

        // 스크롤 이동량 및 간격 설정
        // 빠른 스크롤: 이동량 2, 인터벌 4ms / 느린 스크롤: 이동량 1, 인터벌 5ms
        let moveAmount = (scrollSpeed === 1) ? 2 : 1;
        let intervalDelay = (scrollSpeed === 1) ? 4 : 5;
        // 방향에 따라 양수(아래) 또는 음수(위) 적용
        let scrollAmount = (scrollDirection === 'down') ? moveAmount : -moveAmount;

        scrollInterval = setInterval(() => {
            window.scrollBy({ top: scrollAmount, left: 0 });
        }, intervalDelay);

        // 일정 시간(예: 8000ms) 후에 자동 정지 처리
        stopTimeout = setTimeout(() => {
            clearInterval(scrollInterval);
            scrollSpeed = 0;
            scrollDirection = null;
        }, 8000);
    }

    // 가로세로 판별 함수
    function detectScreenMode() {
        return window.innerWidth >= 992 ? "Landscape" : "Portrait"; // 가로 : 세로
    }

    // 세로일 때 몇몇 요소 지우기
    function hideElementsInPortrait(isPortrait = "Portrait") {
        if (isPortrait === "Portrait") {
            const elementsToHide = ["nav.navbar", "div.board-title", /*"div#vote.vote-area",*/ "div.article-menu.mt-2",
                                    "div.edit-menu", "div.alert.alert-info", "div.article-link", "a.vrow.column.notice notice-unfilter"]; // 숨길 요소의 선택자 목록
            elementsToHide.forEach(selector => {
                const element = document.querySelector(selector);
                if (element) element.style.display = "none";
            });
            document.querySelectorAll("a.vrow.column.notice").forEach(element => {
                element.style.display = "none";
            });
            const vrowInner = document.querySelector('div.vrow-inner');
            if (vrowInner) {
                const parent = vrowInner.parentElement;
                if (parent) parent.style.display = 'none';
            }
            const allAds = document.querySelectorAll('div.ad');
            // 모든 요소를 순회하면서 작업 수행
            allAds.forEach(ad => {
                ad.remove(); // 예시: 요소 없애기
            });

            // MutationObserver를 사용하여 DOM 변경 시 자동으로 notice 요소 제거
            const targetSelectors = ["a.vrow.column.notice", "a.vrow.column.notice.notice-unfilter"];
            const observer = new MutationObserver(mutations => {
                mutations.forEach(m => {
                    m.addedNodes.forEach(node => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            targetSelectors.forEach(sel => { if (node.matches(sel)) node.remove(); });
                            targetSelectors.forEach(sel => { node.querySelectorAll(sel).forEach(el => el.remove()); });
                        }
                    });
                });
            });
            observer.observe(document.body, { childList: true, subtree: true });
        }
    }

    const handleKeyEvent = (event) => {
        // 반복 이벤트 무시, 직접 입력이 아니면 무시
        if (event.repeat || !event.isTrusted) return;

        // 텍스트 입력창에 포커스가 있으면 키 입력 무시
        const activeElement = document.activeElement;
        const tagName = activeElement.tagName.toLowerCase();
        const isTextInput = (
            tagName === "textarea" ||
            (tagName === "input" && ["text", "password", "email", "search", "tel", "url", "number", "date", "time"].includes(activeElement.type)) ||
            activeElement.isContentEditable
        );
        if (isTextInput) return;

        // 키 입력
        if (event.shiftKey) {
            if (event.type === "keydown") keyHandlers.keydownShift?.[event.key]?.(event);
            else if (event.type === "keyup") keyHandlers.keyupShift?.[event.key]?.(event);
        } else {
            if (event.type === "keydown") keyHandlers.keydown?.[event.key]?.(event);
            else if (event.type === "keyup") keyHandlers.keyup?.[event.key]?.(event);
        }
    };
    if (keyActionsEnabled) {
        window.addEventListener("keydown", handleKeyEvent);
        window.addEventListener("keyup", handleKeyEvent);
    }

    const waitForElementState = (selector, desiredState = "present", timeout = 10000) => {
        return new Promise((resolve, reject) => {
            // 조건 체크 함수: "present"이면 요소가 존재하는지, "removed"이면 요소가 없는지 판단
            const checkCondition = () => {
                const element = document.querySelector(selector);
                if (desiredState === "present") return element || null;
                if (desiredState === "removed") return element ? null : true;
            };

            // 초기 상태 검사
            const initialResult = checkCondition();
            if ((desiredState === "present" && initialResult) || (desiredState === "removed" && initialResult === true)) {
                return resolve(initialResult);
            }

            const observer = new MutationObserver(() => {
                const result = checkCondition();
                if ((desiredState === "present" && result) || (desiredState === "removed" && result === true)) {
                    observer.disconnect();
                    resolve(result);
                }
            });

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

            setTimeout(() => {
                observer.disconnect();
                reject(new Error(`Desired state "${desiredState}" not achieved within the maximum wait time.`));
            }, timeout);
        });
    };

    // 특정 요소가 화면에 보이는지 확인하는 함수
    function isElementVisible(element) {
        const rect = element.getBoundingClientRect();
        return rect.bottom > 0 && rect.top < window.innerHeight;
    }

    // 댓글 개수를 세는 함수
    function getCommentCount() {
        // 첫 번째 요소: <span class="title-comment-count">[0]</span>
        let element = document.querySelector('span.title-comment-count');
        if (element) {
            // 대괄호([])를 제거하고 숫자만 추출합니다.
            const text = element.textContent.replace(/[\[\]]/g, '').trim();
            const count = parseInt(text, 10);
            if (!isNaN(count)) {
                return count;
            }
        }
        // 두 번째 요소: <span class="body comment-count">0</span>
        element = document.querySelector('span.body.comment-count');
        if (element) {
            const text = element.textContent.trim();
            const count = parseInt(text, 10);
            if (!isNaN(count)) {
                return count;
            }
        }
        return 0;
    }

    async function createGeneralPostSectionFromAdjacentPage(direction = "curr", postCount, hidden = false) {
        const currentPage = window.currentPrevPage;
        let targetPage;

        if (direction === "prev") {
            if (window.prevLoadCount >= window.MAX_PREV_LOAD_COUNT) {
                console.warn("최대 이전 페이지 로드 횟수에 도달하여 로드를 중단합니다.");
                return;
            }
            if (currentPage > 1) {
                targetPage = currentPage - 1;
                window.currentPrevPage = targetPage; // 부모 변수 업데이트
                window.prevLoadCount++;
            } else {
                return;
            }
        } else if (direction === "curr") {
            targetPage = currentPage;
        } else if (direction === "next") {
            targetPage = currentPage + 1;
        } else {
            console.error("유효한 방향('prev', 'curr' 또는 'next')을 입력하세요.");
            return;
        }
        // 사이드바(또는 body)에 붙이기
        const sidebarContainer = document.querySelector('div.sidebar-item')?.parentElement;

        // 컨테이너 생성
        let adjacentClonedItem = document.getElementById("adjacent-posts-container");
        if (!adjacentClonedItem) {
            // console.log("새 인접 게시글 컨테이너 생성");
            adjacentClonedItem = document.createElement('div');
            adjacentClonedItem.id = "adjacent-posts-container";
            adjacentClonedItem.style.backgroundColor = 'white';
            adjacentClonedItem.style.maxHeight = '600px';
            adjacentClonedItem.style.overflowY = 'auto';
            adjacentClonedItem.style.marginTop = '10px';
            adjacentClonedItem.style.width = '310px';
            adjacentClonedItem.classList.add('my-script-hidden-post');

            // 상단 구분선 추가
            const topSeparator = document.createElement('div');
            topSeparator.style.height = '1px';
            topSeparator.style.backgroundColor = 'gray';
            topSeparator.style.margin = '0';
            adjacentClonedItem.appendChild(topSeparator);

            if (sidebarContainer) {
                sidebarContainer.appendChild(adjacentClonedItem);
            } else {
                document.body.appendChild(adjacentClonedItem);
            }
        }

        // curr의 경우 iframe 없이 바로 처리
        if (direction === "curr") {
            // 현재 페이지에서 active(현재 보고 있는 글)의 위치를 찾음
            let posts = Array.from(document.querySelectorAll('a.vrow.column:not(.notice)'));
            const activeIndex = posts.findIndex(post => {
                try {
                    const currentUrl = new URL(window.location.href);
                    const postUrl = new URL(post.href, window.location.origin);
                    return postUrl.pathname === currentUrl.pathname;
                } catch (e) {
                    return false;
                }
            });

            if (activeIndex !== -1) {
                let start = Math.max(0, activeIndex - Math.floor(postCount / 2));
                let end = Math.min(posts.length, start + postCount);
                posts = posts.slice(Math.max(0, end - postCount), end);
            } else {
                posts = posts.slice(0, postCount);
            }

            extractAndAppendPosts(document, adjacentClonedItem, direction, postCount, posts);
            finalizeAdjacentSection(adjacentClonedItem);
            return;
        }

        // prev 또는 next의 경우 hidden iframe을 생성하여 targetPage 로드
        const iframe = document.createElement('iframe');
        iframe.style.display = 'none';
        const currentUrl = new URL(location.href);
        const originUrl = window.location.origin;
        const pathName = window.location.pathname.split('/').slice(0, 3).join('/');
        const baseUrl = `${originUrl}${pathName}`;

        // 현재 페이지의 URL 객체 생성
        currentUrl.searchParams.delete("p");
        let otherParams = currentUrl.searchParams.toString();
        if (otherParams) {
            iframe.src = `${baseUrl}?p=${targetPage}&${otherParams}`; // 다른 파라미터가 있으면 추가
        } else {
            iframe.src = `${baseUrl}?p=${targetPage}`; // 없으면 그냥 p만 붙임
        }
        console.log("iframe src:", iframe.src);
        document.body.appendChild(iframe);

        iframe.onload = function() {
            const thickSeparator = document.createElement('div');
            thickSeparator.style.height = '2px';
            thickSeparator.style.backgroundColor = 'gray';
            thickSeparator.style.margin = '0';
            if (makeBorder && direction === "next") {
                adjacentClonedItem.appendChild(thickSeparator);
            }
            console.log("iframe 로드 완료, targetPage =", targetPage);
            try {
                const doc = iframe.contentDocument || iframe.contentWindow.document;
                extractAndAppendPosts(doc, adjacentClonedItem, direction, postCount);
            } catch (e) {
                console.error(`페이지 ${targetPage} 처리 중 오류 발생:`, e);
            }
            iframe.remove();
            finalizeAdjacentSection(adjacentClonedItem);
            if (makeBorder && direction === "prev") {
                adjacentClonedItem.appendChild(thickSeparator);
            }
        };

        iframe.onerror = function() {
            console.error(`페이지 ${targetPage}의 iframe 로드 중 오류 발생.`);
            iframe.remove();
            finalizeAdjacentSection(adjacentClonedItem);
        };

        function finalizeAdjacentSection(container) {
            // 위치 설정 및 고정
            container.style.position = 'sticky';
            container.style.top = '10px';
        }

        // 내부 함수: 게시글 추출 및 컨테이너에 추가
        function extractAndAppendPosts(doc, container, direction, postCount, posts = null) {
            // console.log("extractAndAppendPosts 호출됨:", { direction, postCount });
            const containerElement = doc.querySelector('div.article-list');
            if (!containerElement) {
                console.error("게시글 컨테이너를 찾을 수 없습니다.");
                return;
            }
            if (!posts) {
                posts = Array.from(containerElement.querySelectorAll('a.vrow.column:not(.notice)'));
                posts = direction === "prev" ? posts.slice(-postCount) : direction === "next" ? posts.slice(0, postCount) : posts;
            }

            posts.forEach(post => {
                const clonedPost = post.cloneNode(true);
                clonedPost.querySelectorAll('.vrow-preview').forEach(preview => preview.remove());

                let url = clonedPost.href;
                const baseUrl = url.split('?')[0]; // iframe의 기존 게시판에 있는 글일테니 ?만 해결하면 됨
                if (isPageVisited(baseUrl)) {
                    clonedPost.style.color = 'lightgray';
                }

                const idElement = clonedPost.querySelector('.vcol.col-id');
                if (idElement) idElement.remove();

                const viewElement = clonedPost.querySelector('.vcol.col-view');
                if (viewElement) {
                    const viewSpan = document.createElement('span');
                    viewSpan.innerText = '조회수 ';
                    viewElement.parentNode.insertBefore(viewSpan, viewElement);
                }

                const rateElement = clonedPost.querySelector('.vcol.col-rate');
                if (rateElement && viewElement) {
                    const rateSpan = document.createElement('span');
                    rateSpan.innerText = '추천 ';
                    viewElement.parentNode.insertBefore(rateSpan, rateElement);
                }

                clonedPost.style.fontSize = '11px';
                if (clonedPost.classList.contains('active')) {
                    clonedPost.style.backgroundColor = '#d0d0d0';
                    clonedPost.style.zIndex = '2';
                    const activeBackgroundDiv = document.createElement('div');
                    activeBackgroundDiv.style.position = 'absolute';
                    activeBackgroundDiv.style.top = '0';
                    activeBackgroundDiv.style.left = '0';
                    activeBackgroundDiv.style.width = '100%';
                    activeBackgroundDiv.style.height = '100%';
                    activeBackgroundDiv.style.backgroundColor = '#EEEEEE';
                    activeBackgroundDiv.style.zIndex = '-1';
                    clonedPost.style.position = 'relative';
                    clonedPost.appendChild(activeBackgroundDiv);
                }

                container.appendChild(clonedPost);
                const separator = document.createElement('div');
                separator.style.height = '1px';
                separator.style.backgroundColor = 'gray';
                separator.style.margin = '0';
                container.appendChild(separator);
            });

            container.classList.add(`${direction}-loaded`);
        }
    }

    // 게시글 개수 분배
    function calculateAboveBelowNext(pageNumber, activeIndex, length = 45, count = 15) {
        const half = Math.floor(count/2); // = 7
        // 1페이지인 경우 이전 페이지에서 가져올 게시글은 없으므로 prev는 항상 0
        if (pageNumber === 1) {
            // active가 0-indexed 기준으로 후반(8번째 이상)이 아니면
            if (activeIndex < length - half) {
                return [0, count, 0]; // 전부 현재 페이지에서 사용
            } else {
                const curr = count + (length - half -1) - activeIndex;
                return [0, curr, count - curr];
            }
        } else {
            // 2페이지 이상인 경우
            if (activeIndex < half) {
                return [half - activeIndex, count - (half - activeIndex), 0]; // 페이지 상단일 때
            } else if (activeIndex < length - half) {
                return [0, 15, 0]; // 중간 부분이면 현재 페이지 전체 15개 사용
            } else {
                const curr = count + (length - half -1) - activeIndex;
                return [0, curr, count - curr];
            }
        }
    }

    // 최종 목표: 우측 컨테이너를 인접 게시글로 채우기
    async function createAdjacentPostsSection(postCount) {
        const posts = Array.from(document.querySelectorAll('a.vrow.column:not(.notice)'));

        let activeIndex = posts.findIndex(post => {
            try {
                const currentUrl = new URL(window.location.href);
                const postUrl = new URL(post.href, window.location.origin);
                return postUrl.pathname === currentUrl.pathname;
            } catch (e) {
                console.error("findIndex 오류:", e);
                return false;
            }
        });

        const urlParams = new URLSearchParams(window.location.search);
        const currentPage = getCurrentPageNumber() || parseInt(urlParams.get('p')) || 1; // yyy
        const postDistribution = calculateAboveBelowNext(currentPage, Math.max(activeIndex, 0), posts.length, postCount);
        // 이전, 현재, 다음 페이지 섹션 로드 및 조건 기반 대기
        try {
            (async () => {
                if (postDistribution[0] > 0) {
                    // console.log("이전 페이지에서", postDistribution[0], "개 게시글 로드");
                    await createGeneralPostSectionFromAdjacentPage("prev", postDistribution[0], true);
                    await waitForCondition(() => document.querySelector('.prev-loaded') !== null, 10000); // 타임아웃 오류가 뜨면 이 숫자 등을 늘릴 것
                }

                if (postDistribution[1] > 0) {
                    // console.log("현재 페이지에서", postDistribution[1], "개 게시글 로드");
                    await createGeneralPostSectionFromAdjacentPage("curr", postDistribution[1], true);
                    await waitForCondition(() => document.querySelector('.curr-loaded') !== null, 2000);
                }

                if (postDistribution[2] > 0) {
                    // console.log("다음 페이지에서", postDistribution[2], "개 게시글 로드");
                    await createGeneralPostSectionFromAdjacentPage("next", postDistribution[2], true);
                    await waitForCondition(() => document.querySelector('.next-loaded') !== null, 2000);
                }
                // console.log("모든 게시글 로드 완료");
                let isHidden = true;
                function revealPosts() {
                    // 🔵 모두 완료된 후 한꺼번에 보이기
                    document.querySelectorAll('.my-script-hidden-post').forEach(post => {
                        post.classList.remove('my-script-hidden-post'); // 숨김 속성 클래스 제거
                        isHidden = false;
                    });
                    if (!isHidden) {
                        clearInterval(intervalId); // 반복 멈춤
                        loadFinished = true;
                    }
                }
                const intervalId = setInterval(revealPosts, 200); // 0.2초마다 반복
            })();
        } catch (error) {
            console.warn("조건 기반 대기 중 오류 발생:", error);
        }
    }

    function waitForCondition(predicate, timeout = 2000, interval = 50) {
        return new Promise((resolve, reject) => {
            const startTime = Date.now();
            const timer = setInterval(() => {
                if (predicate()) {
                    clearInterval(timer);
                    resolve();
                } else if (Date.now() - startTime >= timeout) {
                    clearInterval(timer);
                    reject(new Error("조건이 충족되지 않음 (타임아웃)")); // 타임아웃 오류는 여기
                }
            }, interval);
        });
    }

    let i = 1;
    // 작성자, 댓글 작성자, 사이드바 게시물 작성자 익명화
    if (anony) {
        // loadFinished 상태를 주기적으로 확인하는 함수
        let checkLoadFinished = setInterval(function() {
            if (loadFinished) {
                clearInterval(checkLoadFinished); // 주기적인 상태 확인 중지
                const sidePosts = document.querySelectorAll('.user-info');

                // 여러 필터링 조건을 한 번에 적용
                const filteredSidePosts = Array.from(sidePosts).reduce((acc, post) => {
                    if (
                        (!post.closest('.article-view') || !post.closest('.board-title')) &&
                        !post.closest('.board-article-list') &&
                        !post.closest('.included-article-list') &&
                        !post.closest('.nav')
                    ) {
                        acc.push(post);
                    }
                    return acc;
                }, []);

                filteredSidePosts.forEach(
                    name => {
                        name.style.whiteSpace = "pre"; // 전후의 공백 유지
                        name.textContent = '홀붕이 ' + i + '  ';
                        i++;
                    }
                );
            }
        }, 100);
    }

    let j = i;
    if (anony2) { // 메인 페이지 게시글 익명화
        function anonymizePosts() {
            // loadFinished 상태를 주기적으로 확인하는 함수
            let checkLoadFinished = setInterval(function() {
                if (loadFinished) {
                    clearInterval(checkLoadFinished); // 주기적인 상태 확인 중지
                    const sidePosts = document.querySelectorAll('.user-info');

                    // 여러 필터링 조건을 한 번에 적용
                    const filteredSidePosts = Array.from(sidePosts).reduce((acc, post) => {
                        if (
                            (post.closest('.article-list') && !post.closest('.board-title'))||
                            (post.closest('.board-article-list') && !post.closest('.board-title')) ||
                            post.closest('.included-article-list') // &&
                        ) {
                            acc.push(post);
                        }
                        return acc;
                    }, []);

                    filteredSidePosts.forEach(
                        (element, index) => {
                            if (element.textContent.trim() !== "*ㅎㅎ") {
                                element.textContent = `홀붕이 ${j}`;
                                j++;
                            }
                        }
                    );
                    j = i;
                }
            }, 100);
        }
        // MutationObserver로 DOM 변화를 감지하여 anonymizePosts 함수 실행
        const observer = new MutationObserver((mutationsList, observer) => {
            let loadFinished = false;
            mutationsList.forEach((mutation) => {
                if (mutation.type === 'childList' || mutation.type === 'attributes') {
                    loadFinished = true;
                }
            });
            if (loadFinished) {
                anonymizePosts();
            }
        });

        // 감시할 대상 노드와 옵션 설정
        const config = { childList: true, subtree: true, attributes: true };

        // 대상 노드 설정 (body 요소를 감시)
        observer.observe(document.body, config);

        // 초기 호출
        anonymizePosts();
    }

    // 방문 페이지 정보를 저장할 객체 (정렬 없이, URL을 키로 사용)
    visitedPages = {};

    // 보관 기간 (예: 30일). 이후 방문 기록은 정리합니다.
    var retentionPeriod = 10 * 365 * 24 * 60 * 60 * 1000; // 10년을 밀리초로 변환

    // 저장소에서 방문 기록을 불러옵니다.
    // visitedPages 전역변수를 불러옴.
    function getVisitedPages() {
        let stored = GM_getValue("visitedPages");
        if (stored === undefined) {
            visitedPages = {};
        } else {
            try {
                visitedPages = JSON.parse(stored);
            } catch (e) {
                visitedPages = {};
            }
        }
        return visitedPages;
    }
    getVisitedPages();

    // 방문 기록에 페이지 정보를 추가하거나 업데이트합니다.
    function addVisitedPage(pageUrl = window.location.origin + window.location.pathname, time = Date.now(), noSave = false) {
        getVisitedPages();
        const comment = getCommentCount();
        if (!visitedPages[pageUrl]) {
            // 새로 방문한 페이지이면, 최초 방문 및 최근 방문 시각, 댓글 수를 모두 기록
            visitedPages[pageUrl] = {
                firstVisit: time,
                lastVisit: time,
                comment: comment
            };
        } else {
            // 이미 존재하면 최신 방문 시각, 댓글만 업데이트
            visitedPages[pageUrl].lastVisit = time;
            visitedPages[pageUrl].comment = comment;
        }
        if (!noSave) {
            GM_setValue("visitedPages", JSON.stringify(visitedPages));
            // console.log("저장했습니다", pageUrl, visitedPages[pageUrl]);
        }
    }

    function delVisitedPage(pageUrl, noSave = false) {
        getVisitedPages(); // 저장소에서 기존 방문 기록 불러오기
        if (visitedPages[pageUrl]) {
            delete visitedPages[pageUrl];
            if (!noSave) {
                GM_setValue("visitedPages", JSON.stringify(visitedPages));
            }
        }
    }


    // 보관 기간(retentionPeriod)을 넘긴 방문 기록을 정리합니다.
    function cleanOldVisitedPages(retentionPeriod = retentionPeriod, noSave = false) {
        const now = Date.now();
        let changed = false;
        for (const pageUrl in visitedPages) {
            if (visitedPages.hasOwnProperty(pageUrl)) {
                // 마지막 방문 시각이 현재보다 retentionPeriod보다 오래 전이면 삭제
                if (now - visitedPages[pageUrl].lastVisit > retentionPeriod) {
                    delete visitedPages[pageUrl];
                    changed = true;
                }
            }
        }
        if (changed && !noSave) {
            GM_setValue("visitedPages", JSON.stringify(visitedPages));
        }
    }

    function storeCurrentPage(noSave = false) {
        let baseUrl = window.location.origin + window.location.pathname;
        if (window.location.pathname.split('/').length > 4) {
            baseUrl = window.location.origin + window.location.pathname.split('/').slice(0, 4).join('/');
        }
        addVisitedPage(baseUrl, Date.now(), noSave);
    }

    function isPageVisited(pageUrl) {
        getVisitedPages();
        return visitedPages.hasOwnProperty(pageUrl);
    }

    document.addEventListener("contextmenu", function (event) {
        if (event.ctrlKey) { // 컨트롤 + 우클릭을 누른 상태에서만 실행
            const target = event.target.closest("a"); // 클릭한 위치에서 가장 가까운 <a> 태그 탐색
            if (target) {
                const href = target.href.split('?')[0];
                console.log("쿼리 제거 후 URL:", href);
                event.preventDefault(); // 기본 우클릭 메뉴 방지
                delVisitedPage(href); // 저장소에서 URL 제거
            }
        }
    });

    function getCurrentPageNumber() {
        const element = document.querySelector('.page-item.active');
        return Number(element.textContent.trim());
    }

    // 마지막으로 메인 게시글들 읽음 여부 다시 설정
    let postPage = document.querySelector('.article-view'); // 있으면 글 페이지
    const element44 = document.querySelector('.article-comment.position-relative .title'); // 있으면 글 페이지
    let boerdPage = document.querySelector('.board-article-list'); // 있으면 목록 페이지
    const mainPage = postPage || boerdPage;
    let mainPosts = Array.from(mainPage.querySelectorAll(' a.vrow.column:not(.notice)'));
    getVisitedPages();
    mainPosts.forEach((post, index) => {
        post.querySelectorAll('.vrow-preview').forEach(preview => preview.remove());
        let url = post.href;
        const baseUrl = url.split('?')[0];
        if (isPageVisited(baseUrl)) {
            post.style.color = 'lightgray';
        } else {
            post.style.color = 'black';
            const bottom = post.querySelector('.vrow-inner .vrow-bottom');
            bottom.style.color = 'black';
        }
    });
    // 별의 색상 다시 지정
    const starElement = document.querySelector('.ion-android-star');
    if (starElement) {
        const style = document.createElement('style');
        style.textContent = `
        .ion-android-star::before {
            color: orange;
        }
    `;
        document.head.appendChild(style);
    }

    // 안읽은 답글 숫자 지정
    const allPosts = Array.from(document.querySelectorAll(' a.vrow.column:not(.notice)'));
    allPosts.forEach((post) => {
        const count = post.querySelector('.comment-count');

        let comments;
        if (count === null) comments = 0;
        else comments = count.textContent.match(/\d+/)[0];
        const url = post.href.split('?')[0];
        getVisitedPages();
        const recordedComments = visitedPages[url];

        if (!recordedComments) return;
        if (comments > recordedComments.comment) {
            count.style.color = 'red'; // 댓글 숫자가 이 색으로 표시됨 'pink', 'HotPink', 'red'
        }
    });
})();