ArcaLive PostSidebar

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         ArcaLive PostSidebar
// @namespace    ArcaLive PostSidebar
// @version      1.2
// @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==
// https://greasyfork.org/ko/scripts/529392-arcalive-postsidebar-%EB%B0%B0%ED%8F%AC%EC%9A%A9

// 최우선 목표: 함수화하기
// iframe 2개만 불러오게 만들기 iframe 로드 완료 감지
// 로딩 시간 최소화

// 더 압축할 거리 찾기
// 더 넣을 기능 찾기
// 단축키 설명서?
// 저장소 내보내기 / 가져오기

// 저번에 안본 (저장소에 카운트 안된) 댓글은 (최근 방문일을 기준으로) 따로 표시 (완, 맨 밑은 파란색이라 덮어짐)
// 검색창 위에도 만들기 (완, 목록 페이지만)

(function() {
    'use strict';
    // 로드 되면 새 댓글 색깔 바꾸기
    window.onload = function() {colorNewComment();};

    const hideMore = true // 세로일 때 제거 요소가 더 많아짐
    // 세로 모드이면 일부 요소 제거
    hideElementsInPortrait(detectScreenMode(), hideMore);
    // 세로로 인식시키고 강제로 제거
    // hideElementsInPortrait("Portrait", hideMore);

    // 검색창을 위에 복사
    const targetElement = document.querySelector("body > div.root-container > div.content-wrapper.clearfix > article > div");
    const elementToCopy = document.querySelector("body > div.root-container > div.content-wrapper.clearfix > article > div > form.form-inline.search-form.justify-content-end");

    if (targetElement && elementToCopy) {
        const clonedElement = elementToCopy.cloneNode(true); // true는 자식 노드까지 복사
        clonedElement.style.paddingBottom = "7px"; // 원하는 패딩 값 적용
        clonedElement.style.paddingRight = "15px"; // 오른쪽 패딩 추가
        targetElement.parentNode.insertBefore(clonedElement, targetElement);
    }

    const mouseRecommendHardMode = true;
    const keyRecommendHardMode = true;
    const maxGauge = 5; // 추천 버튼을 눌러야하는 횟수, 1 이상의 값으로 변경 가능

    const mouseNotRecommendHardMode = true;
    const maxGauge2 = 10; // 비추 버튼을 클릭해야하는 횟수, 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();
    // 페이지를 떠날 떄 현재 방문 시간을 업데이트 (예: 페이지 unload 시 업데이트)
    window.addEventListener("beforeunload", () => {
        storeCurrentPage();
    });

    // 가로 모드면 우측에 인접 게시글 n개 생성
    const n = 15;
    if (detectScreenMode() === "Landscape") createAdjacentPostsSection(n);
    // 세로 모드면 기존 게시판 위에 인접 게시글 m개 삽입
    // const m = 11;
    // insertDistributedAdjacentPostsAboveBoard(m);
    // 이전, 현재, 다음 게시물들 사이에 경계 넣기
    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 keyActionsEnabled = true;
    const myLink = 'https://arca.live/b/holopro'; // Shift + Q 단축키로 이동할 링크

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

    const els = {
        recommendButton: document.querySelector('button#rateUp.item'), // 추천 버튼
        notRecommendButton: document.querySelector('button#rateDown.item'), // 비추천 버튼
        pressedRecommendButton: document.querySelector('button#rateUp.item.already'),
        pressedNotRecommendButton: document.querySelector('button#rateDown.item.already'),
        commentCounter: document.querySelector('.article-comment.position-relative .title'), // 댓글 수 표시
        writeBtn: document.querySelector('#comment .title .btn-arca-article-write'), // 댓글 작성 버튼
        mainBoard: document.querySelector('.article-list') || document.querySelector('.board-article-list'), // 게시판 목록
    };
    let recommendCount = 0, notRecommendCount = 0;
    let recommendButton = els.recommendButton;
    let notRecommendButton = els.notRecommendButton;
    let pressedRecommendButton = els.pressedRecommendButton;
    let pressedNotRecommendButton = els.pressedNotRecommendButton;

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////
    // 버튼 색 설정 (비추 기본색은 흰색이라 생략)
    if (pressedRecommendButton) {
        recommendButton = pressedRecommendButton;
        recommendButton.style.backgroundColor = 'Azure';
        recommendCount = maxGauge;
    } else if (recommendButton) {
        recommendButton.style.backgroundColor = '#F5F5F5';
    }
    if (pressedNotRecommendButton) {
        notRecommendButton = pressedNotRecommendButton;
        notRecommendButton.style.backgroundColor = 'pink';
        notRecommendCount = maxGauge2;
    }

    // recommendButton이 존재할 때만 스타일 변경
    if (recommendButton) {
        recommendButton.style.zIndex = '1';
    }

    function checkButtonVisibility(selector) {
        const button = document.querySelector(selector);
        // 1. 존재하지 않으면 false 반환
        if (!button) {
            console.log(false);
            return false;
        }
        // 2. 크기 확인 (width와 height가 0이면 보이지 않는 것으로 판단)
        const rect = button.getBoundingClientRect();
        if (rect.width === 0 || rect.height === 0) {
            console.log(false);
            return false;
        }
        // 3. 모든 부모 요소의 display와 visibility를 체크
        let currentElement = button;
        while (currentElement) {
            const style = getComputedStyle(currentElement);
            if (style.display === 'none' || style.visibility === 'hidden') {
                console.log(false);
                return false;
            }
            currentElement = currentElement.parentElement;
        }
        // 위 모든 체크를 통과하면, 보이는 상태로 판단
        console.log("요소가 있습니다.");
        return true;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////
    // fillGauge의 사용
    function fillRecommendGauge(recommendCount) {
        fillGauge(recommendButton, recommendCount, maxGauge, "Azure");
    }
    function fillNotRecommendGauge(notRecommendCount) {
        fillGauge(notRecommendButton, notRecommendCount, maxGauge2, "pink");
    }
    /////////////////////////////////////////////////////////////////////////////////////////////////////////
    function fillGauge(button, count, maxCount, color) {
        if (maxCount === 1) return;

        const borderRadius = parseInt(getComputedStyle(button).borderRadius) - 0.5;
        const height = button.offsetHeight - 2;
        const width = button.offsetWidth;

        const newHeight = height * count / maxCount;
        const newBorderRadius = Math.min(borderRadius, ((newHeight - 0) / 2));
        const newLeft = Math.max(0, borderRadius - newBorderRadius);

        let newWidth;
        let newBottom = 0;

        const widthMap = {
            10: 85.2, 8: 85.2, 6: 85.2, 5: 85.2, 4: 85.1,
            3.5: 85.6, 3: 85.2, 2.5: 85.78, 2.2: 85.38, 2: 85.48,
            1.8: 84.68, 1.6: 84.18, 1.5: 86.18, 1.33: 85.18, 1: 85.18,
            0.67: 86.08, 0.5: 88.08
        };
        function getWidth(ratio) {
            return widthMap[ratio] ?? 85.22;
        }
        const deviceRatio = parseFloat(window.devicePixelRatio.toFixed(2));
        let baseWidth = getWidth(deviceRatio);

        let newWidth2 = width - 2 * (borderRadius - newBorderRadius) - 1.8 - 0.4 - 88.08 + 4 + 85.44;
        const nonRecommendButton = checkButtonVisibility('button#rateDown.item');
        if (color === "pink" || (color === "Azure" && nonRecommendButton)) { // 비추 버튼, 옆의 추천 버튼 보정
            if (deviceRatio === 5.00) {

            } else if (deviceRatio === 2.20) {
                newWidth2 -= 2.3;
            } else if (deviceRatio === 2.00) {
                newWidth2 -= 2.1;
                baseWidth += 0.2;
            } else if (deviceRatio === 1.33) {
                newWidth2 -= 0.7;
            } else if (deviceRatio === 1.00) {
                newWidth2 -= 1;
            } else if (deviceRatio === 0.67) {
                newWidth2 -= 2;
                newBottom += 0.5;
            } else if (deviceRatio === 0.50) {
                newWidth2 -= 4;
                newBottom += 0.9;
            }
            newWidth = newHeight >= 10 ? baseWidth : newWidth2;
        } else { // 비추 숨김일 때 추천 버튼 보정
            if (deviceRatio === 10.00) {
                newWidth2 -= 0.3;
            } else if (deviceRatio === 8.00) {
                newWidth2 -= 0.3;
            } else if (deviceRatio === 5.00) {
                newWidth2 -= 0.3;
            } else if (deviceRatio === 4.00) {
                newWidth2 -= 1.2;
            } else if (deviceRatio === 3.00) {
                baseWidth += 0.2;
            } else if (deviceRatio === 2.00) {
                newWidth2 -= 2.2;
            } else if (deviceRatio === 1.80) {
                baseWidth += 0.5;
            } else if (deviceRatio === 1.60) {
                newWidth2 -= 0.3;
                baseWidth += 0.5;
            } else if (deviceRatio === 1.50) {
                newWidth2 -= 0.7;
            } else if (deviceRatio === 1.33) {
                newWidth2 -= 0.1;
                baseWidth += 0.4;
            } else if (deviceRatio === 1.00) {
                newWidth2 -= 0.1;
            } else if (deviceRatio === 0.67) {
                newWidth2 -= 2;
                baseWidth -= 0.5;
            } else if (deviceRatio === 0.50) {
                newWidth2 -= 2;
            }
            newWidth = newHeight >= 10 ? baseWidth : newWidth2;
        }

        if (count < maxCount) {
            const newBackground = document.createElement('div');
            Object.assign(newBackground.style, {
                position: 'absolute',
                width: newWidth + 'px',
                height: newHeight + 'px',
                backgroundColor: color,
                bottom: newBottom + 'px',
                left: newLeft + 'px',
                zIndex: '-2',
                borderRadius: newBorderRadius + (deviceRatio === 5.00 ? -0.3 : 0) + 'px'
            });

            button.appendChild(newBackground);
            button.style.zIndex = '2';
        }
    }

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

    // 아카 리프레셔의 비추천 안누름 버튼이 보이자마자 클릭해버리기
    const targetSelector = 'button.MuiButtonBase-root.MuiButton-root.MuiButton-contained.MuiButton-containedPrimary'; // 추천 또는 비추 버튼

    // 게이지가 다 차기 직전이 아니면 비추 확인 창이 뜨자마자 꺼버림
    const observer = new MutationObserver((mutationsList) => {
        mutationsList.forEach(mutation => {
            mutation.addedNodes.forEach(node => {
                // ELEMENT_NODE인지 확인 (텍스트 노드 등은 제외)
                if (node.nodeType === Node.ELEMENT_NODE) {
                    // 해당 노드 내에 조건에 맞는 요소가 있는지 확인
                    const button = node.querySelector(targetSelector);
                    if (button && notRecommendCount < maxGauge2) {
                        button.click();
                        console.log("현재의 notRecommendCount", notRecommendCount + 1); // 비추 누른 횟수만 카운트
                    }
                }
            });
        });
    });

    // body 전체를 감시해 자식 요소 변화와 모든 하위 노드의 변화를 체크함
    observer.observe(document.body, { childList: true, subtree: true });

    let allowClick = false; // 복제된 버튼에서의 클릭은 허용하기 위한 플래그

    function beechuClick(e) {
        if (event.target !== notRecommendButton) return;
        console.log(notRecommendCount + 1, "비추 클릭");
        if (notRecommendCount < maxGauge2 - 1) return;
        const yea = document.querySelectorAll('button.MuiButtonBase-root.MuiButton-root.MuiButton-outlined.MuiButton-outlinedPrimary')[1];
        if (notRecommendCount === maxGauge2 - 1 && yea && e.target === yea) {
            interceptClick(e, fillNotRecommendGauge, maxGauge2);
        }
    }

    function interceptClick(e, func, variable) {
        // 복제된 버튼에서의 클릭이면 그냥 플래그를 리셋하고 정상 실행하도록 함.
        if (allowClick) {
            allowClick = false;
            return;
        }

        // 원래 버튼에서 발생한 클릭 이벤트는 잠시 차단
        e.stopImmediatePropagation();
        e.preventDefault();

        func(variable);
        const targetSelector3 = document.querySelectorAll('button.MuiButtonBase-root.MuiButton-root.MuiButton-outlined.MuiButton-outlinedPrimary')[1];

        targetSelector3.click();
        // 이후 복제된 버튼에서는 기존 이벤트를 실행할 수 있도록 플래그를 켜줌.
        allowClick = true;
        // 약간의 지연 후(0ms라도 좋음) 복제된 버튼에 클릭 이벤트를 강제로 발생시킵니다.
        //setTimeout(() => {
        // button.style.backgroundColor = 'pink';
        //}, 0);
    }

    // 캡처링 단계에서 클릭 이벤트를 가로채도록 true 옵션 사용
    document.addEventListener("click", beechuClick, true);


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

    function pressRecommendButton() {
        recommendButton.style.backgroundColor = 'Azure';
        recommendButton.click();
    }
    function pressNotRecommendButton() {
        recommendButton.style.backgroundColor = 'pink';
        notRecommendButton.click();
    }

    if (mouseRecommendHardMode) {
        document.addEventListener('click', function(event) {
            if (!recommendButton) return;
            if (recommendCount >= maxGauge) return;
            if (recommendButton.contains(event.target)) {
                event.preventDefault();
                event.stopPropagation();
                recommendCount++
                fillRecommendGauge(recommendCount);
                if (recommendCount >= maxGauge) {
                    pressRecommendButton();
                    console.log("추천에 성공했습니다");
                }
            }
        });
    }

    if (mouseNotRecommendHardMode) {
        document.addEventListener('click', function(event) {
            if (!notRecommendButton) return;
            if (notRecommendCount >= maxGauge2 - 1) return;
            if (notRecommendButton.contains(event.target)) {
                event.preventDefault();
                event.stopPropagation();
                notRecommendCount++;
                fillNotRecommendGauge(notRecommendCount);
            }
        });
    }

    function triggerRecommend() {
        if (keyRecommendHardMode) {
            recommendCount++
            fillRecommendGauge(recommendCount);
            if (recommendCount >= maxGauge) {
                pressRecommendButton();
                console.log("추천에 성공했습니다");
            }
        }
    }

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

    // 키 동작 기능
    const keyHandlers = {
        keydown: {
            "f": () => triggerRecommend(),
            "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 { // 새로운 댓글 버튼 클릭 기능
                    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("댓글 추가 없음");
                        // 마지막 댓글을 찾아 복사 후 파란색으로 강조하여 표시하고, 수정/답글 이벤트를 재등록하며, 버튼을 반투명하게 설정
                        const comments = document.querySelectorAll('.comment-wrapper');
                        if (comments.length > 0) {
                            const lastComment = comments[comments.length - 1]; // 마지막 댓글 선택
                            const clonedComment = lastComment.cloneNode(true); // 깊은 복사
                            clonedComment.id = 'clonedComment-userScript'; // ID 부여

                            // 원본 댓글 숨기기
                            lastComment.style.display = "none";

                            // 배경색 변경 (파란색 강조)
                            const infoRow = clonedComment.querySelector('.content .info-row.clearfix');
                            const message = clonedComment.querySelector('.content .message');
                            if (infoRow) {
                                infoRow.style.backgroundColor = 'skyblue';
                                infoRow.style.setProperty("transition", "none", "important");
                            }
                            if (message) {
                                message.style.backgroundColor = 'Azure';
                                message.style.setProperty("transition", "none", "important");
                            }

                            // 수정 버튼 이벤트 재등록 및 반투명 스타일 적용
                            const cloneCompose = clonedComment.querySelector('.icon.ion-compose');
                            if (cloneCompose) {
                                // 버튼 반투명 적용
                                cloneCompose.parentNode.style.opacity = "0.2";
                                cloneCompose.parentNode.addEventListener('click', function(event) {
                                    event.preventDefault();
                                    // 원한다면 클릭 시 반투명 스타일을 원래대로 복원할 수 있습니다.
                                    // cloneCompose.parentNode.style.opacity = "1";
                                    const hiddenComment = document.querySelector('.comment-wrapper[style*="display: none"]');
                                    if (hiddenComment) {
                                        hiddenComment.style.display = '';
                                        clonedComment.style.display = 'none';
                                        hiddenComment.querySelector('.icon.ion-compose').parentNode.click();
                                    }
                                });
                            }

                            // 답글 버튼 이벤트 재등록 및 반투명 스타일 적용
                            const cloneReply = clonedComment.querySelector('.icon.ion-reply');
                            if (cloneReply) {
                                // 버튼 반투명 적용
                                cloneReply.parentNode.style.opacity = "0.2";
                                cloneReply.parentNode.addEventListener('click', function(event) {
                                    event.preventDefault();
                                    // cloneReply.parentNode.style.opacity = "1"; // 필요 시 원래대로 복원
                                    const hiddenComment = document.querySelector('.comment-wrapper[style*="display: none"]');
                                    if (hiddenComment) {
                                        hiddenComment.style.display = '';
                                        clonedComment.style.display = 'none';
                                        hiddenComment.querySelector('.icon.ion-reply').parentNode.click();
                                    }
                                });
                            }
                            // 클론된 댓글을 원본 댓글이 있던 부모에 추가
                            lastComment.parentNode.appendChild(clonedComment);
                        }

                    }
                }
            },
            "h": () => {
                const toggle = toggleAnonymizeSetting();
                let anony = toggle; // 글 작성자, 댓글 작성자, 사이드바 게시물 익명화
                let anony2 = toggle; // 기존 게시글 목록 익명화
                location.reload(); // 새로고침
            },
        },
        keydownShift: {
            "Q": () => {window.location.href = myLink;},
            "D": () => scrollHandler('up'), // 위로 빠르게, 위로 느리게, 멈춤
            // "P": () => {cleanOldVisitedPages(1);},
            "A": () => {
                event.preventDefault(); // 기본 동작 막기
                event.stopPropagation(); // 버블링 막기
                goToClosestUnreadAbove(); // 위쪽 안 읽은 글로 이동
            },
            "S": () => {
                event.preventDefault(); // 기본 동작 막기
                event.stopPropagation(); // 버블링 막기
                goToClosestUnreadBelow(); // 아래쪽 안 읽은 글로 이동
            },
        },
    };

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

    // 전역 변수 선언
    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();});
    // 광고 제거
    ['.sticky-container .ad', '.banner'].forEach(selector => document.querySelector(selector)?.remove());
    document.querySelector('.ad#svQazR5NHC3xCQr3')?.remove();
    // iframe이면 여기서 종료
    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);

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

    function updateReplyColors() {
        if (replyColoring) {
            applyBackgroundColors1(); // 댓글 윗쪽 색상 변경
            applyBackgroundColors2(); // 댓글 아랫쪽 색상 변경
        }
    }
    // 댓글 입력창 색칠 (색칠 여부는 자동 판단)
    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 = els.commentCounter;
        const writeButton = els.writeBtn;
        if (!commentCounterBar) return;
        // 없으면 목록 페이지이고, 색칠할 필요 없음

        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)';
            let previousColor = window.getComputedStyle(commentCounterBar).backgroundColor;

            if (newColor !== previousColor) {
                commentCounterBar.style.backgroundColor = newColor;
                writeButton.style.backgroundColor = newColor2;
                writeButton.style.borderColor = newColor2;
                clearInterval(interval); // 색을 변경했으니 종료
            }
        }, 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");
        }
    }

    // 댓글 갱신
    function cloneAndOverlayLastComment() {
        const comments = document.querySelectorAll('.comment-wrapper');
        if (comments.length === 0) return;

        const lastComment = comments[comments.length - 1];
        const clone = lastComment.cloneNode(true);
        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();

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

    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", hideMore) {
        // Set default value for 'hideMore' inside the function body.
        if (hideMore === undefined) {
            hideMore = false;
        }
        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";
            });
            elementsToHide.forEach(selector => {
                const element = document.querySelector(selector);
                if (element) element.style.display = "none";
            });

            if (hideMore) { ////////////////////////////////////////////
                const container = document.querySelector("div#comment.article-comment.position-relative");
                //console.log("컨테이너 선택");
                if (container) {
                    //console.log("컨테이너는 있음");
                    const title = container.querySelector(".title");
                    title.remove();
                    const title1 = container.querySelector(".reply-form.write");
                    title1.remove();
                }
                const container2 = document.querySelector("div.btns-board");
                if (container) {
                    //console.log("컨테이너는 있음2");
                    const title = container2.querySelector(".float-right");
                    title.remove();
                    const title1 = container2.querySelector(".float-left");
                    title1.remove();
                }
                document.querySelector("div.board-category-wrapper").remove();
                const history = document.querySelector("div.channel-visit-history");
                if (history) history.remove();
                // document.querySelector("div.btns-board").remove();
                // document.querySelector("div.board-Btns").remove();

            }

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

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

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

    // 방문 페이지 정보를 저장할 객체 (정렬 없이, 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 getBaseUrl(url) {
        try {
            const urlObj = new URL(url);
            const pathSegments = urlObj.pathname.split('/');
            // 필요한 경우 첫 4개 세그먼트만 사용
            if (pathSegments.length > 4) {
                urlObj.pathname = pathSegments.slice(0, 4).join('/');
            }
            return urlObj.origin + urlObj.pathname;
        } catch (e) {
            return url;
        }
    }

    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());
    }

    // 위에도 검색창 만들기
    const searchBar = document.querySelector("body > div.root-container > div.content-wrapper.clearfix > article > div > form.form-inline.search-form.justify-content-end");

    getVisitedPages();

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

    // 댓글 개수를 세는 함수
    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, adjacentClonedItem = document.getElementById("adjacent-posts-container")) {
        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;

        // 컨테이너 생성
        adjacentClonedItem = adjacentClonedItem ? 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);
            }

            await 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);

            const doc = iframe.contentDocument || iframe.contentWindow.document;
            extractAndAppendPosts(doc, adjacentClonedItem, direction, postCount);

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

    // 게시글 추출 및 컨테이너에 추가
    async function extractAndAppendPosts(doc, container, direction, postCount, posts = null) {
        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());
            // 배경색 확인
            const computedStyle = window.getComputedStyle(post);
            const backgroundColor = computedStyle.backgroundColor;
            let url = clonedPost.href;
            const baseUrl = getBaseUrl(url); // iframe의 기존 게시판에 있는 글일테니 ?만 해결하면 됨 (아님, getBaseUrl 만들어서 사용)
            if (backgroundColor === 'rgb(208, 208, 208)' || backgroundColor === 'rgb(238, 238, 238)' || 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';
                clonedPost.style.color = 'lightgray'; // 텍스트 색상을 회색으로 설정 /////////

                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';
                // active 클래스를 제거하여 이후 키 이벤트 등에 영향이 없도록 합니다.
                clonedPost.classList.remove('active');
                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) {
        // if (detectScreenMode() === "Portrait") return;
        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]);
                    await waitForCondition(() => document.querySelector('.prev-loaded') !== null, 10000); // 타임아웃 오류가 뜨면 이 숫자 등을 늘릴 것
                }

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

                if (postDistribution[2] > 0) {
                    // console.log("다음 페이지에서", postDistribution[2], "개 게시글 로드");
                    await createGeneralPostSectionFromAdjacentPage("next", postDistribution[2]);
                    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);
        });
    }

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

    async function insertDistributedAdjacentPostsAboveBoard(postCount = 9) {
        // 세로 모드(Portrait)에서만 실행
        if (detectScreenMode() !== "Portrait") return;

        // 메인 게시판 컨테이너 찾기
        const mainBoard = els.mainBoard;
        if (!mainBoard) {
            console.warn("메인 게시판 컨테이너를 찾을 수 없습니다.");
            return;
        }

        // 게시글 목록(공지 제외) 가져오기
        const posts = Array.from(mainBoard.querySelectorAll('a.vrow.column:not(.notice)'));
        if (posts.length === 0) return;
        // 현재 페이지 번호 가져오기
        const urlParams = new URLSearchParams(window.location.search);
        const currentPage = getCurrentPageNumber() || parseInt(urlParams.get('p')) || 1;

        // 현재 페이지의 active 게시글 인덱스 찾기
        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) {
                return false;
            }
        });
        if (activeIndex < 0) activeIndex = 0;

        // 게시글 분배 계산
        const [numPrev, numCurr, numNext] = calculateAboveBelowNext(currentPage, activeIndex, posts.length, postCount);
        //console.log([numPrev, numCurr, numNext]);

        // 게시판 위에 추가할 컨테이너 생성
        const container = document.createElement('div');
        container.id = "distributed-adjacent-posts";
        container.style.backgroundColor = '#fff';
        container.style.padding = '5px';
        container.style.marginBottom = '5px';
        container.style.border = '1px solid #ccc';
        container.style.width = '100%';
        container.style.maxHeight = 'none';

        try {
            //console.log(numPrev);
            if (numPrev > 0) {
                await createGeneralPostSectionFromAdjacentPage("prev", numPrev, container);
                //console.log("✅ prev 로딩 완료");
                await delay(1000); // 🔹 prev 완료 후 1000ms 대기
            }

            //console.log(numCurr);
            if (numCurr > 0) {
                await createGeneralPostSectionFromAdjacentPage("curr", numCurr, container);
                //  console.log("✅ curr 로딩 완료");
                await delay(1000); // 🔹 curr 완료 후 1000ms 대기
            }

            //console.log(numNext);
            if (numNext > 0) {
                await createGeneralPostSectionFromAdjacentPage("next", numNext, container);
                //  console.log("✅ next 로딩 완료");
            }
        } catch (error) {
            console.warn("게시글 로딩 중 오류 발생:", error);
        }

        // 메인 게시판 위에 컨테이너 삽입
        mainBoard.parentNode.insertBefore(container, mainBoard);
        loadFinished = true; // 세로 모드에서도 로딩 완료 상태로 표시
    }

    // 🔹 지정한 시간(ms) 동안 대기하는 함수
    function delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    function clonePostsWithOriginalStyle(doc, container, postsToClone) {
        postsToClone.forEach(post => {
            // 기존 게시글 요소를 수정 없이 그대로 클론
            const clonedPost = post.cloneNode(true);
            container.appendChild(clonedPost);
            // 구분선이 필요하다면 추가 (원본과 유사한 방식)
            const separator = document.createElement('div');
            separator.style.height = '1px';
            separator.style.backgroundColor = 'gray';
            separator.style.margin = '0';
            container.appendChild(separator);
        });
    }
    function clonePostsPreservingStyle(doc, container, postCount, posts = null) {
        // doc: 원본 문서, container: 삽입할 컨테이너
        // posts가 없으면 원본 문서에서 게시글을 선택 (공지 제외)
        if (!posts) {
            posts = Array.from(doc.querySelectorAll('a.vrow.column:not(.notice)'));
            posts = posts.slice(0, postCount);
        }
        posts.forEach(post => {
            // 원본 요소를 그대로 복제 (스타일, 클래스, 인라인 스타일 모두 유지)
            const clonedPost = post.cloneNode(true);
            container.appendChild(clonedPost);

            // 필요에 따라 구분선을 추가 (원본과 동일하게)
            const separator = document.createElement('div');
            separator.style.height = '1px';
            separator.style.backgroundColor = 'gray';
            separator.style.margin = '0';
            container.appendChild(separator);
        });
    }

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

    // (A) 인접 페이지의 게시글을 가져오는 함수
    async function createGeneralPostSectionFromAdjacentPage2(direction, postCount, container) {
        const currentPage = getCurrentPageNumber();
        let targetPage;

        if (direction === "prev") {
            if (currentPage <= 1) return;
            targetPage = currentPage - 1;
        } else if (direction === "next") {
            targetPage = currentPage + 1;
        } else {
            targetPage = currentPage;
        }

        const currentUrl = new URL(location.href);
        currentUrl.searchParams.delete("p");
        const baseUrl = currentUrl.origin + currentUrl.pathname;
        const otherParams = currentUrl.searchParams.toString();
        const targetUrl = otherParams ? `${baseUrl}?p=${targetPage}&${otherParams}` : `${baseUrl}?p=${targetPage}`;

        const iframe = document.createElement('iframe');
        iframe.style.display = 'none';
        iframe.src = targetUrl;
        document.body.appendChild(iframe);

        return new Promise((resolve, reject) => {
            iframe.onload = function() {
                const doc = iframe.contentDocument || iframe.contentWindow.document;
                const boardContainer = doc.querySelector('.article-list') || doc.querySelector('.board-article-list');
                if (!boardContainer) {
                    iframe.remove();
                    return reject(new Error("게시글 컨테이너를 찾을 수 없습니다."));
                }
                let posts = Array.from(boardContainer.querySelectorAll('a.vrow.column:not(.notice)'));
                if (direction === "prev") {
                    posts = posts.slice(-postCount);
                } else if (direction === "next") {
                    posts = posts.slice(0, postCount);
                }
                posts.forEach(post => {
                    container.appendChild(post.cloneNode(true));
                });
                iframe.remove();
                resolve();
            };

            iframe.onerror = function() {
                iframe.remove();
                reject(new Error("iframe 로드 오류"));
            };
        });
    }

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

    // (B) 메인 게시판에 인접 게시글을 추가하는 함수
    async function addAdjacentPostsToMainBoard() {
        const mainBoard = els.mainBoard;
        if (!mainBoard) {
            console.warn("메인 게시판을 찾을 수 없습니다.");
            return;
        }

        const currentPage = getCurrentPageNumber();

        // 이전 페이지의 마지막 2개 게시글 추가
        if (currentPage > 1) {
            const prevContainer = document.createElement('div');
            await createGeneralPostSectionFromAdjacentPage2("prev", 2, prevContainer);
            const prevPosts = prevContainer.querySelectorAll('a.vrow.column:not(.notice)');
            if (prevPosts.length > 0) {
                const fragment = document.createDocumentFragment();
                prevPosts.forEach(prevPost => {
                    const clonedPost = prevPost.cloneNode(true);
                    fixDateFormat(clonedPost); // 각 클론된 게시글의 날짜 변환
                    clonedPost.style.backgroundColor = "Azure"; // : "#e6f7ff"; // 색상 적용
                    const url = clonedPost.href;
                    const baseUrl = getBaseUrl(url);
                    if (isPageVisited(baseUrl)) clonedPost.style.color = 'lightgray';

                    fragment.appendChild(clonedPost);
                });
                const firstPost = mainBoard.querySelector('a.vrow.column:not(.notice)');

                const thickSeparator = document.createElement('div');
                thickSeparator.style.height = '2px';
                thickSeparator.style.backgroundColor = 'gray';
                thickSeparator.style.margin = '0';

                if (firstPost && firstPost.parentNode) {
                    firstPost.parentNode.insertBefore(fragment, firstPost);
                    // firstPost.parentNode.insertBefore(thickSeparator, firstPost);
                } else {
                    mainBoard.insertBefore(fragment, mainBoard.firstChild);
                    // mainBoard.insertBefore(thickSeparator, mainBoard.firstChild);
                }
            }
            // console.log("34434");
        }

        // 다음 페이지의 처음 2개 게시글 추가
        const nextContainer = document.createElement('div');
        await createGeneralPostSectionFromAdjacentPage2("next", 2, nextContainer);
        const nextPosts = nextContainer.querySelectorAll('a.vrow.column:not(.notice)');
        if (nextPosts.length > 0) {
            const lastPost = mainBoard.querySelectorAll('a.vrow.column:not(.notice)')[mainBoard.querySelectorAll('a.vrow.column:not(.notice)').length - 1];

            const thickSeparator = document.createElement('div');
            thickSeparator.style.height = '2px';
            thickSeparator.style.backgroundColor = 'gray';
            thickSeparator.style.margin = '0';

            if (lastPost && lastPost.parentNode) {
                // lastPost.parentNode.appendChild(thickSeparator);

                nextPosts.forEach(nextPost => {
                    const clonedPost = nextPost.cloneNode(true);
                    fixDateFormat(clonedPost); // 각 클론된 게시글의 날짜 변환
                    clonedPost.style.backgroundColor = 'rgb(255, 230, 235)'; // 색상 적용
                    const url = clonedPost.href;
                    const baseUrl = getBaseUrl(url);
                    if (isPageVisited(baseUrl)) clonedPost.style.color = 'lightgray';

                    lastPost.parentNode.appendChild(clonedPost);
                });
            } else {
                // mainBoard.appendChild(thickSeparator);

                nextPosts.forEach(nextPost => {
                    const clonedPost = nextPost.cloneNode(true);
                    fixDateFormat(clonedPost); // 날짜 형식 수정
                    clonedPost.style.backgroundColor = "pink"; // 색상 적용

                    mainBoard.appendChild(clonedPost);
                });
            }

        }
    }
    ///////////////////////////////////////////////////////////////////////////////////////

    // 사이드바 관리 모듈
    const SidebarManager = {
        container: null,
        init: () => {
            SidebarManager.container = document.getElementById('adjacent-posts-container') || SidebarManager.createContainer();
        },
        createContainer: () => {
            const div = document.createElement('div');
            div.id = 'adjacent-posts-container';
            div.style.position = 'fixed';
            div.style.top = '10px';
            div.style.right = '10px';
            document.body.appendChild(div);
            return div;
        },
        getPosts: () => SidebarManager.container.querySelectorAll('a.vrow.column:not(.notice)'),
        addPost: (post) => {
            const link = document.createElement('a');
            link.href = post.href;
            link.textContent = post.textContent || '게시글';
            link.className = 'vrow column';
            SidebarManager.container.appendChild(link);
        },
        highlightPost: (post) => {
            post.style.transition = 'background-color 0.3s';
            post.style.backgroundColor = '#ffeb3b';
            setTimeout(() => {post.style.backgroundColor = ''}, 300);
        },
    };

    // 탐색 모듈 (fetch와 연동)
    const Navigation = {
        currentPage: 1, // 현재 페이지 번호 (실제로는 URL에서 가져와야 함)
        init: () => {
            Navigation.currentPage = new URL(window.location.href).searchParams.get('p') || 1;
        },
        goToClosestUnreadBelow: async () => {
            const posts = SidebarManager.getPosts();
            let currentIndex = Navigation.getActiveIndex(posts);
            if (currentIndex === -1) {
                // 현재 페이지에 해당 게시글이 없으면 인접 페이지 로드
                const nextPosts = await fetchAdjacentPage(Navigation.currentPage + 1);
                nextPosts.forEach(post => SidebarManager.addPost(post));
            }
            currentIndex = Navigation.getActiveIndex(SidebarManager.getPosts());
            const unreadPost = Navigation.findClosestUnreadBelow(SidebarManager.getPosts(), currentIndex);
            if (unreadPost) {
                SidebarManager.highlightPost(unreadPost);
                setTimeout(() => {window.location.href = unreadPost.href}, 300);
            }
        },
        getActiveIndex: (posts) => Array.from(posts).findIndex(post => post.href === window.location.href),
        findClosestUnreadBelow: (posts, startIndex) => {
            for (let i = startIndex + 1; i < posts.length; i++) {
                if (!Navigation.isPageVisited(posts[i].href.split('?')[0])) return posts[i];
            }
            return null;
        },
        isPageVisited: (url) => localStorage.getItem(url) === 'visited', // 방문 여부 확인 (예시)
    };

    // 설정 관리 모듈
    const Settings = {
        maxGauge: GM_getValue('maxGauge', 5), // Tampermonkey 값 가져오기 예시
        anonymizeSetting: GM_getValue('anonymizeSetting', false),
        save: () => {
            GM_setValue('maxGauge', Settings.maxGauge);
            GM_setValue('anonymizeSetting', Settings.anonymizeSetting);
        },
    };

    // 초기화 및 단축키 설정
    SidebarManager.init();
    /*
    Navigation.init();
    document.addEventListener('keydown', (e) => {
        if (e.shiftKey && e.key === 'S') {
            Navigation.goToClosestUnreadBelow();
        }
    });

    // 이후에는 Navigation.currentPage 사용
    console.log(Navigation.currentPage);
    */

    ///////////////////////////////////////////////////////////////////////////////////////
    // 페이지의 모든 <t> 요소의 날짜 형식을 동적으로 업데이트하는 함수
    function updateDynamicDateFormat() {
        // datetime 속성을 가진 모든 <t> 요소를 찾음
        const timeElements = document.querySelectorAll('time[datetime]');

        timeElements.forEach(element => {
            // 요소가 댓글 컨테이너(.comment-wrapper) 내에 있는지 확인
            if (!element.closest('.comment-wrapper')) {
                // 댓글 컨테이너 밖에 있는 경우에만 날짜 업데이트 수행
                const datetime = element.getAttribute('datetime');
                const postDate = new Date(datetime); // ISO 8601 형식 파싱 (예: 2025-03-16T18:03:46.000Z)
                const now = new Date(); // 현재 시간
                const diffInHours = (now - postDate) / (1000 * 60 * 60); // 시간 차이 계산 (단위: 시간)
                // console.log(diffInHours);
                let formattedDate;
                if (diffInHours < 24) {
                    // 24시간 이내: "HH:mm" 형식으로 표시
                    formattedDate = postDate.toLocaleTimeString('ko-KR', {
                        hour: '2-digit',
                        minute: '2-digit',
                        hourCycle: 'h23' // 이게 중요
                    }); // 예: "18:03"
                    // console.log(formattedDate);
                } else {
                    // 24시간 초과: "YYYY. MM. DD" 형식으로 표시
                    const year = postDate.getFullYear();
                    const month = String(postDate.getMonth() + 1).padStart(2, '0'); // 월은 0부터 시작하므로 +1
                    const day = String(postDate.getDate()).padStart(2, '0');
                    formattedDate = `${year}. ${month}. ${day}`; // 예: "2025. 03. 16"
                }

                // 변환된 날짜로 텍스트 업데이트
                element.textContent = formattedDate;
            }
        });
    }

    // 페이지가 로드될 때 함수 실행
    updateDynamicDateFormat();

    let countT = 0;
    const intervalId = setInterval(() => {
        if (countT < 10) {
            // console.log(`작동: ${countT + 1}`); // 원하는 작업 실행
            updateDynamicDateFormat();
            countT++;
        } else {
            clearInterval(intervalId); // 10초 작동 후 멈춤
        }
    }, 100); // 0.1초마다 실행


    // 모든 게시글의 날짜를 변환하는 함수
    function fixAllPostDates() {
        const allPosts = document.querySelectorAll('.article-list .vrow.column');
        allPosts.forEach(post => {
            fixDateFormat(post);
            // console.log(post);
        });
    }

    //////////////////////////////////////////////////////////////////////////////////////////
    // 날짜 파싱 함수
    function parseBoardDate(dateString) {
        // 예: "2025-03-17 02:54:01" 형식 처리
        const [datePart, timePart] = dateString.split(' ');
        const [year, month, day] = datePart.split('-').map(Number);
        const [hour, minute, second] = timePart.split(':').map(Number);
        return new Date(year, month - 1, day, hour, minute, second);
    }

    // 날짜 형식 변환 함수
    function formatPostDate(dateString) {
        const postDate = parseBoardDate(dateString); // 날짜를 Date 객체로 변환
        const now = new Date(); // 현재 시간
        const diffInHours = (now - postDate) / (1000 * 60 * 60); // 시간 차이 계산

        if (diffInHours < 24) {
            // 24시간 이내: "HH:mm" 형식
            return postDate.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit' });
        } else {
            // 24시간 이상: "YYYY.MM.DD" 형식
            const year = postDate.getFullYear();
            const month = String(postDate.getMonth() + 1).padStart(2, '0');
            const day = String(postDate.getDate()).padStart(2, '0');
            return `${year}.${month}.${day}`;
        }
    }

    // 메인 게시판의 날짜 업데이트
    function fixDateFormat(postElement) {
        const dateElem = postElement.querySelector('.col-date'); // 날짜가 있는 요소
        if (dateElem) {
            const originalDate = dateElem.textContent; // 원래 날짜 문자열
            const formattedDate = formatPostDate(originalDate); // 변환된 날짜
            dateElem.textContent = formattedDate; // 화면에 반영
        }
    }

    // 모든 게시글에 적용
    document.querySelectorAll('.article-list .vrow.column').forEach(post => {
        fixDateFormat(post);
    });

    function parseDateString(dateString) {
        const [datePart, timePart] = dateString.split(' ');
        const [year, month, day] = datePart.split('-').map(Number);
        const [hour, minute, second] = timePart.split(':').map(Number);
        return new Date(year, month - 1, day, hour, minute, second);
    }

    function toLocalDate(dateString) {
        const date = parseDateString(dateString);
        const offset = date.getTimezoneOffset() * 60000; // 로컬 시간대 오프셋 (밀리초)
        return new Date(date.getTime() - offset); // 로컬 시간으로 변환
    }
    // (C) DOMContentLoaded 이벤트에서 실행하도록 추가
    document.addEventListener("DOMContentLoaded", () => {
        addAdjacentPostsToMainBoard().catch(err => console.error(err));
    });


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

    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();
    }




    ///////////////////////////////////////////////////////////////////////////////////////////////////
    getVisitedPages();

    // 마지막으로 메인 게시글들 읽음 여부 다시 설정
    let postPage = document.querySelector('.article-view'); // 있으면 글 페이지
    const element44 = els.commentCounter; // 있으면 글 페이지
    let boerdPage = document.querySelector('.board-article-list'); // 있으면 목록 페이지
    const mainPage = postPage || boerdPage;
    let mainPosts = Array.from(mainPage.querySelectorAll(' a.vrow.column:not(.notice)'));
    //console.log(mainPosts);

    mainPosts.forEach((post) => {
        post.querySelectorAll('.vrow-preview').forEach(preview => preview.remove());
        let url = post.href;
        const baseUrl = url.split('?')[0];

        // 현재 페이지의 번호 추출
        const currentPath = window.location.pathname;
        const currentParts = currentPath.split('/');
        const currentNumber = currentParts[currentParts.length - 1];

        // 게시글의 번호 추출
        const postPath = new URL(baseUrl).pathname;
        const postParts = postPath.split('/');
        const postNumber = postParts[postParts.length - 1];

        // 읽음 여부 확인 + 번호 일치 여부 확인
        if (isPageVisited(baseUrl) || currentNumber === postNumber) {
            post.style.color = 'lightgray';
        } else {
            post.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) => {
        if (getBaseUrl(post.href) !== getBaseUrl(location.href)) {
            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) {
                let colorDetermine = comments - recordedComments.comment;
                count.style.color = colorDetermine === 1 ? 'rgb(255,155,77)' : 'red'; // 댓글 숫자가 이 색(주황, 빨강)으로 표시됨
                if (colorDetermine > 2) count.style.fontWeight = "bold"; // 3개 이상 쌓이면 굵은 글씨로 표시됨
            }
        }
    });

    // 새 댓글 색깔 바꾸기
    function colorNewComment () {
        // 저장된 visitedPages를 객체로 불러오기 (기본값은 빈 객체)
        let stored = GM_getValue("visitedPages", "{}");
        try {
            stored = JSON.parse(stored);
        } catch (e) {
            stored = {};
        }

        const pageUrl = window.location.origin + window.location.pathname;
        // 저장된 데이터에 현재 페이지 방문 기록이 없으면 종료
        if (!(stored[pageUrl] && stored[pageUrl].lastVisit)) {
            console.log("Your First Visit!");
            return;
        }

        let tempLastVisitTime = stored[pageUrl].lastVisit;

        // 댓글 색칠 작업 (tempLastVisitTime을 기준으로)
        // 댓글 컨테이너 선택자를 '.comment-wrapper'로 변경
        const comments = document.querySelectorAll('.comment-wrapper');
        console.log("찾은 댓글 수:", comments.length);

        comments.forEach((comment, index) => {
            console.log(`댓글 ${index + 1} 처리 시작`);

            // 댓글 내의 시간 요소는 보통 <time> 태그에 datetime 속성이 있음
            const timeElement = comment.querySelector('time[datetime]');
            console.log(`댓글 ${index + 1}: 시간 요소`, timeElement);

            if (timeElement) {
                const datetimeAttr = timeElement.getAttribute('datetime');
                console.log(`댓글 ${index + 1}: datetime 속성 값: ${datetimeAttr}`);

                const commentTime = new Date(datetimeAttr);
                console.log(`댓글 ${index + 1}: 파싱된 시간: ${commentTime}`);

                const tempVisitTime = new Date(tempLastVisitTime);
                console.log(`나의 마지막 방문 시간: ${tempVisitTime}`);

                if (commentTime > tempVisitTime) {
                    console.log(`댓글 ${index + 1}: 새로운 댓글로 판단되어 배경색 변경`);
                    const message = comment.querySelector('.content .message');
                    if (message) {
                        message.style.backgroundColor = '#FFFFE0';
                    }
                } else {
                    console.log(`댓글 ${index + 1}: 새 댓글이 아님`);
                }
            } else {
                console.log(`댓글 ${index + 1}: 시간 요소를 찾을 수 없음`);
            }
        });
    }


    function getSidebarPosts() {
        const sidebarContainer = document.getElementById('adjacent-posts-container');
        if (!sidebarContainer) return [];
        return Array.from(sidebarContainer.querySelectorAll('a.vrow.column:not(.notice)'));
    }

    function getActiveIndexInSidebar(posts) {
        return 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;
            }
        });
    }

    function findClosestUnreadAbove(posts, activeIndex) {
        for (let i = activeIndex - 1; i >= 0; i--) {
            const url = posts[i].href.split('?')[0];
            if (!isPageVisited(url)) {
                return posts[i];
            }
        }
        return null;
    }

    function findClosestUnreadBelow(posts, activeIndex) {
        for (let i = activeIndex + 1; i < posts.length; i++) {
            const url = posts[i].href.split('?')[0];
            if (!isPageVisited(url)) {
                return posts[i];
            }
        }
        return null;
    }

    function isArticlePage() {
        return document.querySelector('.article-view') !== null;
    }

    function goToClosestUnreadAbove() {
        if (!isArticlePage()) return;
        const posts = getSidebarPosts();
        let activeIndex = getActiveIndexInSidebar(posts);
        if (activeIndex === -1) return;
        let unreadPost = findClosestUnreadAbove(posts, activeIndex);
        // 현재 글과 같은 URL은 건너뛰기
        while (unreadPost && unreadPost.href === window.location.href) {
            activeIndex = posts.indexOf(unreadPost);
            unreadPost = findClosestUnreadAbove(posts, activeIndex);
        }
        if (unreadPost) {
            window.location.href = unreadPost.href;
        }
    }

    function goToClosestUnreadBelow() {
        if (!isArticlePage()) return; // 게시글 페이지가 아닌 경우 종료
        const posts = getSidebarPosts(); // 사이드바 게시글 목록 가져오기
        let activeIndex = getActiveIndexInSidebar(posts); // 현재 활성 게시글 인덱스 찾기
        if (activeIndex === -1) {
            console.log("활성 게시글을 찾을 수 없습니다.");
            return;
        }
        let unreadPost = findClosestUnreadBelow(posts, activeIndex); // 아래쪽 안 읽은 게시글 찾기
        if (unreadPost) {
            window.location.href = unreadPost.href; // 안 읽은 게시글이 있으면 이동
        } else {
            console.log("아래에 안 읽은 게시글이 없습니다."); // 없으면 이동하지 않음
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////
    async function fetchAdjacentPage(page) {
        const url = new URL(window.location.href); // 현재 URL 기반으로
        url.searchParams.set('p', page); // 페이지 번호 변경
        const response = await fetch(url); // HTML 요청
        const html = await response.text(); // 텍스트로 받기
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html'); // DOM으로 변환
        return doc.querySelectorAll('a.vrow.column:not(.notice)'); // 게시글만 추출
    }

    function addShortcutGuide() {
        const guide = document.createElement('div');
        guide.id = 'shortcut-guide';
        guide.style.position = 'fixed';
        guide.style.bottom = '10px';
        guide.style.right = '10px';
        guide.style.background = '#fff';
        guide.style.padding = '10px';
        guide.style.border = '1px solid #ccc';
        guide.innerHTML = `
        <style>
            kbd {
                background-color: #FFC0CB; /* 분홍색 배경 */
                color: #000000; /* 검은색 글씨 */
                border: 1px solid #ccc;
                border-radius: 3px;
                padding: 2px 4px;
                font-family: monospace;
            }
        </style>
        <h3>단축키 안내 (<kbd>Shift + H</kbd>로 토글)</h3>
        <h4>단축키</h4>
        <ul>
            <li><kbd>f</kbd>: 추천 (하드모드)</li>
            <li><kbd>d</kbd>: 아래로 스크롤(빠름/느림)</li>
            <li><kbd>n</kbd>: 세로 모드 요소 숨기기</li>
            <li><kbd>g</kbd>: 게시판 첫 글/새 댓글 새로고침</li>
            <li><kbd>h</kbd>: 익명화 토글</li>
        </ul>
        <h4>Shift + 단축키</h4>
        <ul>
            <li><kbd>Shift + Q</kbd>: 홀로라이브 채널로 이동</li>
            <li><kbd>Shift + D</kbd>: 위로 스크롤(빠름/느림)</li>
            <li><kbd>Shift + A</kbd>: 위쪽 안 읽은 글</li>
            <li><kbd>Shift + S</kbd>: 아래쪽 안 읽은 글</li>
        </ul>
    `;
        document.body.appendChild(guide);
        guide.style.display = 'none';

        document.addEventListener('keydown', (e) => {
            if (e.shiftKey && e.key === 'H') {
                guide.style.display = guide.style.display === 'none' ? 'block' : 'none';
            }
        });
    }
    addShortcutGuide();


})();