DCInside Wide

디시인사이드 화면을 넓고 일관된 레이아웃으로 정리하고 목록·댓글·디시콘 가독성을 개선합니다.

2026/09/15のページです。最新版はこちら

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         DCInside Wide
// @namespace    dcinside-wide-layout-controller
// @version      1.0.2
// @description  디시인사이드 화면을 넓고 일관된 레이아웃으로 정리하고 목록·댓글·디시콘 가독성을 개선합니다.
// @author       Yun. S. Lee
// @icon         https://is1-ssl.mzstatic.com/image/thumb/PurpleSource221/v4/ad/98/26/ad982679-2aeb-7209-8357-9d5da0f0953d/Placeholder.mill/400x400bb-75.webp
// @match        https://gall.dcinside.com/board/lists*
// @match        https://gall.dcinside.com/board/view*
// @match        https://*.dcinside.com/board/lists*
// @match        https://*.dcinside.com/board/view*
// @match        https://gall.dcinside.com/mgallery/board/lists*
// @match        https://gall.dcinside.com/mgallery/board/view*
// @match        https://gall.dcinside.com/mini/board/lists*
// @match        https://gall.dcinside.com/mini/board/view*
// @match        https://gall.dcinside.com/person/board/lists*
// @match        https://gall.dcinside.com/person/board/view*
// @run-at       document-start
// @grant        GM_getValue
// @grant        GM_setValue
// @license MIT
// ==/UserScript==

(() => {
    "use strict";

    // v1.1의 설정을 그대로 이어받는다.
    const STORAGE_KEY = "dcwide-layout-v1.1";
    const MIN_TITLE_WIDTH = 260;
    const AUTO_SUBJECT_MIN_WIDTH = 150;

    const DEFAULTS = {
        enabled: true,

        // 전체 레이아웃
        // 기본값은 우측 사이드바를 없애고 그 공간을 본문에 돌려준다.
        hideSidebar: true,
        mainWidth: 1600,
        sidebarWidth: 280,
        columnGap: 24,

        // 상단/갤러리 정보
        syncTop: true,
        fixGalleryInfo: true,
        showAllHeads: true,

        // 목록 말머리/가독성
        autoSubjectWidth: true,
        collapseNoticesByDefault: true,
        wrapLongTitles: true,
        readabilityEnabled: true,
        titleFontSize: 16,
        metaFontSize: 13,
        headerFontSize: 13,
        titleWeight: 500,
        metaWeight: 400,
        headerWeight: 600,
        lineHeightPercent: 145,
        rowPadding: 6,
        headFontSize: 14,
        headWeight: 500,

        // 글 보기 / 댓글 입력창
        fixCommentBox: true,

        // 글 보기 / 디시콘
        // 추가 네트워크 요청 없이 DC가 이미 DOM에 올린 콘만 재배치한다.
        enhanceDccon: true,
        dcconColumns: 12,
        dcconScalePercent: 135,

        // 게시글 목록 열: 기본 합계 1600px
        numberWidth: 80,
        subjectWidth: 110,
        titleWidth: 890,
        writerWidth: 280,
        dateWidth: 100,
        countWidth: 70,
        recommendWidth: 70,
    };

    const COLUMN_DEFS = [
        { key: "numberWidth", cls: "gall_num", labels: ["번호"], name: "번호" },
        { key: "subjectWidth", cls: "gall_subject", labels: ["말머리"], name: "말머리" },
        { key: "titleWidth", cls: "gall_tit", labels: ["제목"], name: "제목" },
        { key: "writerWidth", cls: "gall_writer", labels: ["글쓴이"], name: "글쓴이" },
        { key: "dateWidth", cls: "gall_date", labels: ["작성일", "날짜"], name: "작성일" },
        { key: "countWidth", cls: "gall_count", labels: ["조회"], name: "조회" },
        { key: "recommendWidth", cls: "gall_recommend", labels: ["추천"], name: "추천" },
    ];

    const COLUMN_KEYS = new Set(COLUMN_DEFS.map((item) => item.key));
    const NON_TITLE_COLUMN_KEYS = new Set(
        COLUMN_DEFS.filter((item) => item.key !== "titleWidth").map((item) => item.key),
    );

    let settings = loadSettings();
    let noticeCollapsed = Boolean(settings.collapseNoticesByDefault);
    let panel = null;
    let summaryEl = null;
    let applyQueued = false;
    const panelControls = new Map();

    function loadSettings() {
        try {
            const saved = GM_getValue(STORAGE_KEY, {});
            const validSaved = saved && typeof saved === "object" ? saved : {};
            const merged = {
                ...DEFAULTS,
                ...validSaved,
            };

            /*
             * v2.0 최초 마이그레이션:
             * 예전에는 "본문 + 사이드바 + 간격"으로 쓰던 전체 폭을
             * 사이드바를 숨긴 뒤 그대로 본문 폭으로 회수한다.
             *
             * 예: 1195 + 280 + 24 = 1499px
             *     -> v2.0에서는 본문 1499px, 사이드바 0px
             *
             * 기존 번호/말머리/글쓴이 폭은 유지하고,
             * 회수한 공간은 제목 열에 추가한다.
             */
            if (!Object.prototype.hasOwnProperty.call(validSaved, "hideSidebar")) {
                const reclaimed =
                    Number(merged.sidebarWidth || 0) +
                    Number(merged.columnGap || 0);

                const oldMainWidth = Number(merged.mainWidth || DEFAULTS.mainWidth);

                merged.hideSidebar = true;
                merged.mainWidth = clamp(
                    oldMainWidth + reclaimed,
                    1000,
                    2200,
                );

                const actuallyReclaimed = merged.mainWidth - oldMainWidth;
                merged.titleWidth = Math.max(
                    MIN_TITLE_WIDTH,
                    Number(merged.titleWidth || DEFAULTS.titleWidth) + actuallyReclaimed,
                );

                try {
                    GM_setValue(STORAGE_KEY, merged);
                } catch {
                    // 저장 실패 시에도 현재 세션에서는 병합된 값을 사용한다.
                }
            }

            /*
             * v2.1 최초 1회:
             * 이전 저장값에 hideSidebar=false가 남아 있더라도
             * 사용자가 요청한 "사이드바 없는 통일 UI"를 다시 기본으로 확정한다.
             * 본문 폭 자체는 여기서 변경하지 않는다.
             */
            if (!validSaved._dcwideV21SidebarMigrated) {
                merged.hideSidebar = true;
                merged._dcwideV21SidebarMigrated = true;

                try {
                    GM_setValue(STORAGE_KEY, merged);
                } catch {
                    // 저장 실패 시에도 현재 세션에서는 merged 값을 사용한다.
                }
            }

            if (!validSaved._dcwideV22DcconMigrated) {
                merged.enhanceDccon = true;
                merged.dcconColumns = 12;
                delete merged.dcconRows;
                merged._dcwideV22DcconMigrated = true;

                try {
                    GM_setValue(STORAGE_KEY, merged);
                } catch {
                    // 현재 세션에서는 merged 값을 그대로 사용한다.
                }
            }

            return merged;
        } catch {
            return { ...DEFAULTS };
        }
    }

    function saveSettings() {
        try {
            GM_setValue(STORAGE_KEY, settings);
        } catch (error) {
            console.warn("[DC Wide] 설정 저장 실패", error);
        }
    }

    function clamp(value, min, max) {
        return Math.max(min, Math.min(max, Number(value) || min));
    }

    function cleanText(value) {
        return String(value || "").replace(/\s+/g, " ").trim();
    }

    function columnSum() {
        return COLUMN_DEFS.reduce((sum, item) => sum + Number(settings[item.key] || 0), 0);
    }

    function pageWidth() {
        if (settings.hideSidebar) {
            return Number(settings.mainWidth);
        }

        return Number(settings.mainWidth) + Number(settings.sidebarWidth) + Number(settings.columnGap);
    }

    function setCssVar(name, value) {
        document.documentElement.style.setProperty(name, value);
    }

    function applyVariables() {
        const root = document.documentElement;
        root.classList.toggle("dcwide-enabled", Boolean(settings.enabled));

        // 글 보기 페이지에서는 right_content가 하단 목록을 침범하지 않도록 항상 숨긴다.
        const hideSidebarNow = Boolean(settings.hideSidebar || isViewPage());
        root.classList.toggle("dcwide-hide-sidebar", hideSidebarNow);

        root.classList.toggle("dcwide-sync-top", Boolean(settings.syncTop));
        root.classList.toggle("dcwide-fix-intro", Boolean(settings.fixGalleryInfo));
        root.classList.toggle("dcwide-show-all-heads", Boolean(settings.showAllHeads));
        root.classList.toggle("dcwide-readability", Boolean(settings.readabilityEnabled));
        root.classList.toggle("dcwide-wrap-titles", Boolean(settings.wrapLongTitles));
        root.classList.toggle("dcwide-fix-comments", Boolean(settings.fixCommentBox));
        root.classList.toggle(
            "dcwide-enhance-dccon",
            Boolean(settings.enhanceDccon && isViewPage()),
        );

        setCssVar("--dcwide-main", `${settings.mainWidth}px`);
        setCssVar("--dcwide-sidebar", `${settings.sidebarWidth}px`);
        setCssVar("--dcwide-gap", `${settings.columnGap}px`);
        setCssVar("--dcwide-page", `${pageWidth()}px`);
        setCssVar("--dcwide-title-size", `${settings.titleFontSize}px`);
        setCssVar("--dcwide-meta-size", `${settings.metaFontSize}px`);
        setCssVar("--dcwide-header-size", `${settings.headerFontSize}px`);
        setCssVar("--dcwide-title-weight", String(settings.titleWeight));
        setCssVar("--dcwide-meta-weight", String(settings.metaWeight));
        setCssVar("--dcwide-header-weight", String(settings.headerWeight));
        setCssVar("--dcwide-line-height", String(settings.lineHeightPercent / 100));
        setCssVar("--dcwide-row-padding", `${settings.rowPadding}px`);
        setCssVar("--dcwide-head-size", `${settings.headFontSize}px`);
        setCssVar("--dcwide-head-weight", String(settings.headWeight));

        const dcconScale = clamp(Number(settings.dcconScalePercent || 135), 100, 160);
        const dcconCell = Math.round(78 * (dcconScale / 100));

        const dcconColumns = clamp(Number(settings.dcconColumns || 12), 6, 12);
        const dcconGap = 6;
        const dcconGridWidth =
            dcconColumns * dcconCell
            + Math.max(0, dcconColumns - 1) * dcconGap
            + 16;

        setCssVar("--dcwide-dccon-cols", String(dcconColumns));
        setCssVar("--dcwide-dccon-cell", `${dcconCell}px`);
        setCssVar("--dcwide-dccon-gap", `${dcconGap}px`);
        setCssVar("--dcwide-dccon-grid-width", `${dcconGridWidth}px`);
    }

    function injectStyle() {
        if (document.getElementById("dcwide-style-v19")) return;

        const style = document.createElement("style");
        style.id = "dcwide-style-v19";
        style.textContent = `
            html.dcwide-enabled body {
                min-width: var(--dcwide-page) !important;
            }

            /*
             * DCRefresher가 동시에 켜져 있어도 레이아웃 폭만큼은
             * 이 스크립트의 값을 단일 기준으로 사용한다.
             * (Refresher의 다른 기능은 건드리지 않음)
             */
            html.dcwide-enabled {
                --dcr-layoutWidth: var(--dcwide-page) !important;
                --dcr-sidebarWidth: var(--dcwide-sidebar) !important;
                --dcr-columnGap: var(--dcwide-gap) !important;
            }

            html.dcwide-enabled.dcwide-hide-sidebar {
                --dcr-layoutWidth: var(--dcwide-main) !important;
                --dcr-sidebarWidth: 0px !important;
                --dcr-columnGap: 0px !important;
            }

            /*
             * DC 원본의 .wrap_inner 고정폭을 해제한다.
             * 갤러리 제목 / 정보 카드 / 목록이 서로 다른 폭으로 보이던
             * 가장 큰 원인 중 하나다.
             */
            html.dcwide-enabled .wrap_inner {
                width: var(--dcwide-page) !important;
                min-width: var(--dcwide-page) !important;
                max-width: var(--dcwide-page) !important;
                margin-left: auto !important;
                margin-right: auto !important;
                box-sizing: border-box !important;
            }

            html.dcwide-enabled #container {
                display: grid !important;
                grid-template-columns: minmax(0, var(--dcwide-main)) var(--dcwide-sidebar) !important;
                column-gap: var(--dcwide-gap) !important;
                width: var(--dcwide-page) !important;
                max-width: none !important;
                margin-left: auto !important;
                margin-right: auto !important;
                align-items: start !important;
                box-sizing: border-box !important;
            }

            html.dcwide-enabled #container > .left_content,
            html.dcwide-enabled #container > section:first-of-type {
                float: none !important;
                width: var(--dcwide-main) !important;
                min-width: 0 !important;
                max-width: var(--dcwide-main) !important;
                margin-left: 0 !important;
                margin-right: 0 !important;
                box-sizing: border-box !important;
            }

            html.dcwide-enabled #container > .right_content {
                float: none !important;
                width: var(--dcwide-sidebar) !important;
                min-width: var(--dcwide-sidebar) !important;
                max-width: var(--dcwide-sidebar) !important;
                margin-left: 0 !important;
                margin-right: 0 !important;
                box-sizing: border-box !important;
            }

            /*
             * 기본 모드: 오른쪽 로그인/실시간베스트/디시미디어 사이드바 제거.
             * 로그인은 DC 상단의 원래 로그인 링크를 그대로 사용한다.
             */
            html.dcwide-enabled.dcwide-hide-sidebar #container {
                grid-template-columns: minmax(0, var(--dcwide-main)) !important;
                column-gap: 0 !important;
                width: var(--dcwide-main) !important;
                max-width: var(--dcwide-main) !important;
            }

            html.dcwide-enabled.dcwide-hide-sidebar #container > .right_content {
                display: none !important;
            }

            /*
             * 좌측의 모든 핵심 블록을 정확히 동일한 가로 폭으로 맞춘다.
             */
            html.dcwide-enabled #container > .left_content > header,
            html.dcwide-enabled #container > .left_content > article,
            html.dcwide-enabled #container > .left_content > .page_head,
            html.dcwide-enabled #container > .left_content > .issue_wrap,
            html.dcwide-enabled #container > .left_content .issue_contentbox,
            html.dcwide-enabled #container > .left_content .minor_intro_box,
            html.dcwide-enabled #container > .left_content .mini_intro_box,
            html.dcwide-enabled #container > .left_content .person_intro_box,
            html.dcwide-enabled #container > .left_content .dcwide-gallery-card,
            html.dcwide-enabled #container > .left_content .gall_listwrap,
            html.dcwide-enabled #container > .left_content .list_array_option,
            html.dcwide-enabled #container > .left_content .bottom_paging_box,
            html.dcwide-enabled #container > .left_content .view_content_wrap,
            html.dcwide-enabled #container > .left_content .view_comment {
                width: 100% !important;
                max-width: none !important;
                box-sizing: border-box !important;
            }

            html.dcwide-enabled.dcwide-sync-top #top.list_wrap,
            html.dcwide-enabled.dcwide-sync-top #top.view_wrap {
                min-width: var(--dcwide-page) !important;
            }

            html.dcwide-enabled.dcwide-sync-top #top > .dcheader,
            html.dcwide-enabled.dcwide-sync-top #top > .gnb_bar > .gnb,
            html.dcwide-enabled.dcwide-sync-top #top > .gnb_bar .gnb,
            html.dcwide-enabled.dcwide-sync-top #visit_history,
            html.dcwide-enabled.dcwide-sync-top .visit_history {
                width: var(--dcwide-page) !important;
                max-width: none !important;
                margin-left: auto !important;
                margin-right: auto !important;
                box-sizing: border-box !important;
            }

            /**********************************************************
             * 게시글 표
             **********************************************************/

            html.dcwide-enabled table.gall_list {
                width: 100% !important;
                max-width: 100% !important;
                table-layout: fixed !important;
                box-sizing: border-box !important;
            }

            html.dcwide-enabled table.gall_list th,
            html.dcwide-enabled table.gall_list td {
                box-sizing: border-box !important;
            }

            /*
             * 제목 셀은 폭 안에서 정리하되,
             * 글쓴이 셀은 DC/Refresher의 .user_data 팝업이 셀 밖으로
             * 나와야 하므로 절대로 overflow:hidden 처리하지 않는다.
             */
            html.dcwide-enabled table.gall_list .gall_tit {
                overflow: hidden !important;
            }

            html.dcwide-enabled table.gall_list .gall_writer {
                position: relative !important;
                overflow: visible !important;
                white-space: nowrap !important;
                text-overflow: clip !important;
            }

            html.dcwide-enabled table.gall_list .gall_writer .addbox {
                position: relative !important;
                overflow: visible !important;
            }

            html.dcwide-enabled table.gall_list .gall_writer .user_data {
                position: absolute;
                overflow: visible !important;
                z-index: 2147483000 !important;
            }

            html.dcwide-enabled table.gall_list .gall_writer:has(.user_data[style*="display:inline-block"]),
            html.dcwide-enabled table.gall_list .gall_writer:has(.user_data[style*="display: inline-block"]) {
                z-index: 2147482000 !important;
            }

            html.dcwide-enabled .gall_listwrap,
            html.dcwide-enabled table.gall_list,
            html.dcwide-enabled table.gall_list tbody,
            html.dcwide-enabled table.gall_list tr {
                overflow: visible !important;
            }

            html.dcwide-enabled table.gall_list td.gall_tit.dc-thumb-title-cell {
                box-sizing: border-box !important;
            }

            html.dcwide-enabled table.gall_list tr.dc-thumb-has-image > td {
                vertical-align: middle !important;
            }

            /**********************************************************
             * 긴 제목 줄바꿈
             **********************************************************/

            /*
             * 중요: 썸네일 스크립트가 만든 .dc-list-thumbnail-link는 제목 링크가 아니다.
             * 제목 줄바꿈 규칙에서 반드시 제외해야 100×74 썸네일 박스가 무너지지 않는다.
             */
            html.dcwide-enabled.dcwide-wrap-titles table.gall_list .gall_tit,
            html.dcwide-enabled.dcwide-wrap-titles table.gall_list td.gall_tit,
            html.dcwide-enabled.dcwide-wrap-titles table.gall_list td.gall_tit > a:not(.dc-list-thumbnail-link),
            html.dcwide-enabled.dcwide-wrap-titles table.gall_list td.gall_tit > span,
            html.dcwide-enabled.dcwide-wrap-titles table.gall_list td.gall_tit > span > a:not(.dc-list-thumbnail-link),
            html.dcwide-enabled.dcwide-wrap-titles table.gall_list td.gall_tit a:not(.reply_numbox):not(.dc-list-thumbnail-link) {
                max-width: none !important;
                max-height: none !important;
                overflow: visible !important;
                text-overflow: clip !important;
                white-space: normal !important;
                word-break: keep-all !important;
                overflow-wrap: anywhere !important;
            }

            html.dcwide-enabled.dcwide-wrap-titles table.gall_list td.gall_tit > a:not(.dc-list-thumbnail-link),
            html.dcwide-enabled.dcwide-wrap-titles table.gall_list td.gall_tit > span > a:not(.dc-list-thumbnail-link),
            html.dcwide-enabled.dcwide-wrap-titles table.gall_list td.gall_tit a:not(.reply_numbox):not(.dc-list-thumbnail-link) {
                display: inline !important;
                height: auto !important;
            }

            /*
             * 썸네일 행의 height를 auto로 덮어쓰지 않는다.
             * 썸네일 스크립트의 86px 행 높이를 최소 높이처럼 유지하면서,
             * 제목이 여러 줄이면 표 자체가 필요한 만큼 더 커지게 둔다.
             */
            html.dcwide-enabled.dcwide-wrap-titles table.gall_list tr.dc-thumb-has-image > td {
                max-height: none !important;
                padding-top: max(var(--dcwide-row-padding, 6px), 6px) !important;
                padding-bottom: max(var(--dcwide-row-padding, 6px), 6px) !important;
            }

            /* 썸네일 링크/이미지는 외부 썸네일 스크립트가 지정한 크기를 그대로 보존 */
            html.dcwide-enabled table.gall_list td.gall_tit .dc-list-thumbnail-link {
                white-space: normal !important;
                text-overflow: clip !important;
                overflow: visible !important;
            }

            html.dcwide-enabled table.gall_list td.gall_tit .dc-list-thumbnail {
                object-fit: cover !important;
                flex: none !important;
            }

            /**********************************************************
             * 글 보기 댓글 입력창 와이드 보정
             **********************************************************/

            html.dcwide-enabled.dcwide-fix-comments .view_comment,
            html.dcwide-enabled.dcwide-fix-comments .cmt_wrap,
            html.dcwide-enabled.dcwide-fix-comments .comment_box,
            html.dcwide-enabled.dcwide-fix-comments .comment_box > .inner {
                box-sizing: border-box !important;
                width: 100% !important;
                max-width: none !important;
            }

            html.dcwide-enabled.dcwide-fix-comments .cmt_write_box {
                display: grid !important;
                grid-template-columns: minmax(0, 1fr) !important;
                row-gap: 7px !important;
                box-sizing: border-box !important;
                width: 100% !important;
                max-width: none !important;
                min-height: 0 !important;
                margin: 10px 0 0 !important;
                padding: 12px 14px !important;
            }

            html.dcwide-enabled.dcwide-fix-comments .cmt_write_box > .fl {
                display: flex !important;
                float: none !important;
                width: auto !important;
                min-width: 0 !important;
                align-items: center !important;
                flex-wrap: wrap !important;
                gap: 6px !important;
                margin: 0 !important;
            }

            html.dcwide-enabled.dcwide-fix-comments .cmt_write_box .user_info_input {
                float: none !important;
                box-sizing: border-box !important;
                width: auto !important;
                min-width: 100px !important;
                margin: 0 !important;
            }

            html.dcwide-enabled.dcwide-fix-comments .cmt_write_box .user_info_input input {
                box-sizing: border-box !important;
                width: 120px !important;
                max-width: 100% !important;
            }

            html.dcwide-enabled.dcwide-fix-comments .cmt_write_box > .cmt_txt_cont,
            html.dcwide-enabled.dcwide-fix-comments .cmt_write_box .cmt_write {
                float: none !important;
                box-sizing: border-box !important;
                width: 100% !important;
                max-width: none !important;
                min-width: 0 !important;
                margin: 0 !important;
            }

            html.dcwide-enabled.dcwide-fix-comments .cmt_write_box .cmt_write textarea {
                display: block !important;
                box-sizing: border-box !important;
                width: 100% !important;
                min-width: 0 !important;
                max-width: none !important;
                min-height: 120px !important;
                height: 120px;
                margin: 0 !important;
                padding: 10px 12px !important;
                resize: vertical !important;
            }

            html.dcwide-enabled.dcwide-fix-comments .cmt_write_box .cmt_textarea_label {
                box-sizing: border-box !important;
                width: 100% !important;
                max-width: none !important;
                padding: 10px 12px !important;
                line-height: 1.55 !important;
            }

            html.dcwide-enabled.dcwide-fix-comments .cmt_write_box .cmt_cont_bottm {
                display: flex !important;
                box-sizing: border-box !important;
                width: 100% !important;
                max-width: none !important;
                min-height: 34px !important;
                height: auto !important;
                align-items: center !important;
                justify-content: space-between !important;
                flex-wrap: wrap !important;
                gap: 10px !important;
                margin: 0 !important;
                overflow: visible !important;
            }

            /* 댓글 등록 / 등록+추천 버튼 묶음을 항상 오른쪽 끝으로 보낸다. */
            html.dcwide-enabled.dcwide-fix-comments .cmt_cont_bottm .dcwide-comment-submit-group {
                margin-left: auto !important;
                margin-right: 0 !important;
                display: flex !important;
                align-items: center !important;
                justify-content: flex-end !important;
                gap: 8px !important;
                float: none !important;
            }

            html.dcwide-enabled.dcwide-fix-comments .cmt_cont_bottm > .fr {
                margin-left: auto !important;
                margin-right: 0 !important;
                float: none !important;
            }

            /**********************************************************
             * 글 보기 디시콘 확장 v2.2
             *
             * - 가로 최대 12열
             * - DC 기본 24개 단위 페이지 구분 제거
             * - 선택한 팩의 모든 콘을 한 번에 표시
             * - 세로는 패널 내부 스크롤
             **********************************************************/

            html.dcwide-enabled.dcwide-enhance-dccon #div_con {
                z-index: 2147482500 !important;
                width: min(var(--dcwide-dccon-grid-width), calc(100vw - 40px)) !important;
                min-width: 0 !important;
                max-width: calc(100vw - 40px) !important;
                overflow: visible !important;
                box-sizing: border-box !important;
            }

            html.dcwide-enabled.dcwide-enhance-dccon #div_con .pop_content.dcconlayer.edit,
            html.dcwide-enabled.dcwide-enhance-dccon #div_con .dccon_list_wrap,
            html.dcwide-enabled.dcwide-enhance-dccon #div_con > .inner,
            html.dcwide-enabled.dcwide-enhance-dccon #div_con .dccon_list_box.dcconlist {
                width: 100% !important;
                min-width: 0 !important;
                max-width: 100% !important;
                box-sizing: border-box !important;
            }

            /* 상단 팩 선택줄도 아래 콘 그리드 폭과 일치. */
            html.dcwide-enabled.dcwide-enhance-dccon #div_con .dccon_tab_btnbox {
                float: none !important;
                display: flex !important;
                align-items: center !important;
                width: 100% !important;
                min-width: 0 !important;
                max-width: 100% !important;
                box-sizing: border-box !important;
            }

            html.dcwide-enabled.dcwide-enhance-dccon #div_con .dccon_tab_btnbox .tab_btnlist {
                flex: 1 1 auto !important;
                min-width: 0 !important;
                max-width: none !important;
                overflow: hidden !important;
            }

            /* 모든 콘을 담는 단일 세로 스크롤 영역. */
            html.dcwide-enabled.dcwide-enhance-dccon #div_con .dccon_list_box.dcconlist {
                max-height: min(72vh, 900px) !important;
                overflow-x: hidden !important;
                overflow-y: auto !important;
                overscroll-behavior: contain !important;
                scrollbar-gutter: stable !important;
            }

            /*
             * JS가 DC native inline display 상태를 읽어 현재 선택 팩 하나에만
             * dcwide-dccon-active를 붙인다.
             */
            html.dcwide-enabled.dcwide-enhance-dccon
            #div_con ul.dccon_list.dcwide-dccon-active {
                display: grid !important;
                grid-template-columns:
                    repeat(var(--dcwide-dccon-cols), var(--dcwide-dccon-cell)) !important;
                grid-auto-rows: var(--dcwide-dccon-cell) !important;
                gap: var(--dcwide-dccon-gap) !important;
                align-items: stretch !important;
                justify-content: start !important;
                width: 100% !important;
                min-width: 0 !important;
                max-width: 100% !important;
                height: auto !important;
                min-height: 0 !important;
                margin: 0 !important;
                padding: 8px !important;
                box-sizing: border-box !important;
                overflow: visible !important;
            }

            /*
             * 2페이지 이후 콘에 붙은 inline display:none을 선택된 팩에서만 무시.
             * 따라서 1/6 페이지 구분 없이 전부 한 목록에 나타난다.
             */
            html.dcwide-enabled.dcwide-enhance-dccon
            #div_con ul.dccon_list.dcwide-dccon-active > li.list_li {
                display: block !important;
                float: none !important;
                position: relative !important;
                width: var(--dcwide-dccon-cell) !important;
                min-width: var(--dcwide-dccon-cell) !important;
                max-width: var(--dcwide-dccon-cell) !important;
                height: var(--dcwide-dccon-cell) !important;
                min-height: var(--dcwide-dccon-cell) !important;
                max-height: var(--dcwide-dccon-cell) !important;
                margin: 0 !important;
                padding: 0 !important;
                box-sizing: border-box !important;
                overflow: hidden !important;
            }

            html.dcwide-enabled.dcwide-enhance-dccon
            #div_con ul.dccon_list.dcwide-dccon-active > li.list_li > .img_dccon {
                display: block !important;
                width: 100% !important;
                height: 100% !important;
                min-width: 0 !important;
                min-height: 0 !important;
                max-width: none !important;
                max-height: none !important;
                padding: 0 !important;
                box-sizing: border-box !important;
            }

            html.dcwide-enabled.dcwide-enhance-dccon
            #div_con ul.dccon_list.dcwide-dccon-active > li.list_li > .img_dccon img,
            html.dcwide-enabled.dcwide-enhance-dccon
            #div_con ul.dccon_list.dcwide-dccon-active > li.list_li > .img_dccon video {
                display: block !important;
                width: 100% !important;
                height: 100% !important;
                max-width: 100% !important;
                max-height: 100% !important;
                object-fit: contain !important;
            }

            html.dcwide-enabled.dcwide-enhance-dccon
            #div_con ul.dccon_list.dcwide-dccon-active > li.list_li > .btn_starmark {
                position: absolute !important;
                right: 2px !important;
                bottom: 2px !important;
                z-index: 3 !important;
            }

            /* 연속 스크롤 모드에서는 기존 1/6 페이지 UI 자체를 숨긴다. */
            html.dcwide-enabled.dcwide-enhance-dccon
            #div_con ul.dccon_list.dcwide-dccon-active > .dccon_list_btm {
                display: none !important;
            }

            /**********************************************************
             * 갤러리 정보 카드
             * - 정식/마이너/미니/인물 갤러리 공통
             * - 원본 기능 DOM은 보존하고, 보기만 와이드 카드로 재구성
             **********************************************************/

            html.dcwide-enabled.dcwide-fix-intro .dcwide-native-intro-hidden {
                display: none !important;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-card {
                position: relative;
                display: grid;
                grid-template-columns: 186px minmax(0, 1fr) 188px;
                grid-template-areas:
                    "cover main rank"
                    "cover managers rank";
                gap: 12px 20px;
                width: 100%;
                min-height: 166px;
                margin: 12px 0 18px;
                padding: 16px 18px;
                border: 1px solid #dfe3ef;
                border-radius: 9px;
                background: linear-gradient(180deg, #ffffff 0%, #fbfcff 100%);
                box-shadow: 0 1px 2px rgb(20 34 83 / 5%);
                box-sizing: border-box;
                color: #2d3240;
                overflow: hidden;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-card::before {
                content: "";
                position: absolute;
                top: 0;
                left: 0;
                width: 4px;
                height: 100%;
                background: #3b4890;
                opacity: .9;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-cover {
                grid-area: cover;
                display: flex;
                align-items: center;
                justify-content: center;
                min-width: 0;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-cover img,
            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-cover .dcwide-cover-bg {
                display: block;
                width: 172px;
                height: 129px;
                max-width: 100%;
                border: 1px solid #d8ddea;
                border-radius: 7px;
                background-color: #f4f5f9;
                background-position: center;
                background-repeat: no-repeat;
                background-size: cover;
                object-fit: cover;
                box-sizing: border-box;
                box-shadow: 0 2px 8px rgb(25 37 82 / 8%);
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-main {
                grid-area: main;
                min-width: 0;
                padding: 2px 2px 0;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-kicker {
                display: flex;
                flex-wrap: wrap;
                align-items: center;
                gap: 6px;
                margin-bottom: 9px;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-badge,
            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-chip {
                display: inline-flex;
                align-items: center;
                min-height: 23px;
                padding: 0 8px;
                border-radius: 12px;
                box-sizing: border-box;
                white-space: nowrap;
                font-size: 11px;
                line-height: 21px;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-badge {
                border: 1px solid #c9d1ea;
                background: #eef1fb;
                color: #29367c;
                font-weight: 700;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-chip {
                border: 1px solid #e1e4ec;
                background: #f7f8fb;
                color: #676d7d;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-description {
                max-width: 1040px;
                color: #343947;
                font-size: 13px;
                line-height: 1.65;
                word-break: keep-all;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-description:empty {
                display: none;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-meta-line {
                display: flex;
                flex-wrap: wrap;
                gap: 7px;
                margin-top: 11px;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-meta-item {
                display: inline-flex;
                align-items: center;
                gap: 5px;
                min-height: 25px;
                padding: 0 9px;
                border: 1px solid #e2e5ed;
                border-radius: 5px;
                background: #fff;
                color: #606676;
                font-size: 11px;
                line-height: 23px;
                box-sizing: border-box;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-meta-item strong {
                color: #353b4a;
                font-weight: 700;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-managers {
                grid-area: managers;
                min-width: 0;
                align-self: end;
                display: grid;
                grid-template-columns: minmax(0, 1fr);
                gap: 5px;
                padding: 10px 12px;
                border: 1px solid #e6e8ef;
                border-radius: 6px;
                background: rgb(247 248 251 / 78%);
                color: #555d6e;
                font-size: 11px;
                line-height: 1.55;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-manager-row {
                display: grid;
                grid-template-columns: 64px minmax(0, 1fr);
                gap: 8px;
                min-width: 0;
                align-items: start;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-manager-row strong {
                color: #323847;
                white-space: nowrap;
                font-weight: 700;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-manager-value {
                min-width: 0;
                overflow-wrap: anywhere;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-rank {
                grid-area: rank;
                align-self: stretch;
                display: flex;
                flex-direction: column;
                align-items: center;
                justify-content: center;
                min-width: 0;
                padding: 14px 12px;
                border: 1px solid #dde2f0;
                border-radius: 8px;
                background: #f7f9ff;
                text-align: center;
                box-sizing: border-box;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-rank-label {
                color: #4f5870;
                font-size: 12px;
                font-weight: 800;
                letter-spacing: -.1px;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-rank-number {
                margin-top: 5px;
                color: #18224b;
                font-size: 31px;
                font-weight: 800;
                line-height: 1.05;
                letter-spacing: -1px;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-rank.dcwide-rank-major {
                background: linear-gradient(180deg, #fff9ed 0%, #fffdf8 100%);
                border-color: #ecd9ad;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-rank.dcwide-rank-major .dcwide-rank-label,
            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-rank.dcwide-rank-major .dcwide-rank-number {
                color: #8b5d12;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-rank.dcwide-rank-cold {
                background: #f7f7f8;
                border-color: #e1e2e5;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-rank.dcwide-rank-cold .dcwide-rank-number {
                color: #777d88;
                font-size: 18px;
                letter-spacing: 0;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-rank-button {
                margin-top: 11px;
                min-width: 122px;
                height: 29px;
                padding: 0 12px;
                border: 1px solid #3b4890;
                border-radius: 15px;
                background: #fff;
                color: #3b4890;
                font-size: 10.5px;
                font-weight: 700;
                cursor: pointer;
                transition: background 120ms ease, color 120ms ease, box-shadow 120ms ease;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-rank-button:hover {
                background: #3b4890;
                color: #fff;
                box-shadow: 0 2px 6px rgb(41 54 124 / 18%);
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-card.dcwide-no-cover {
                grid-template-columns: minmax(0, 1fr) 188px;
                grid-template-areas:
                    "main rank"
                    "managers rank";
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-card.dcwide-no-rank {
                grid-template-columns: 186px minmax(0, 1fr);
                grid-template-areas:
                    "cover main"
                    "cover managers";
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-card.dcwide-no-cover.dcwide-no-rank {
                grid-template-columns: minmax(0, 1fr);
                grid-template-areas:
                    "main"
                    "managers";
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-card.dcwide-no-managers {
                grid-template-areas: "cover main rank";
                align-items: center;
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-card.dcwide-no-cover.dcwide-no-managers {
                grid-template-columns: minmax(0, 1fr) 188px;
                grid-template-areas: "main rank";
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-card.dcwide-no-rank.dcwide-no-managers {
                grid-template-columns: 186px minmax(0, 1fr);
                grid-template-areas: "cover main";
            }

            html.dcwide-enabled.dcwide-fix-intro .dcwide-gallery-card.dcwide-no-cover.dcwide-no-rank.dcwide-no-managers {
                grid-template-columns: minmax(0, 1fr);
                grid-template-areas: "main";
            }

            /**********************************************************
             * 말머리: 더보기 없이 모두 표시 + 우측 UI와 겹치지 않게 별도 행
             **********************************************************/

            html.dcwide-enabled.dcwide-show-all-heads .list_array_option {
                display: flex !important;
                flex-wrap: wrap !important;
                align-items: center !important;
                min-height: 42px !important;
                height: auto !important;
                gap: 6px 10px !important;
                overflow: visible !important;
            }

            html.dcwide-enabled.dcwide-show-all-heads .list_array_option .array_tab {
                order: 1 !important;
                flex: 0 0 auto !important;
                width: auto !important;
                white-space: nowrap !important;
            }

            html.dcwide-enabled.dcwide-show-all-heads .list_array_option .center_box {
                display: none !important;
            }

            html.dcwide-enabled.dcwide-show-all-heads .list_array_option .right_box {
                order: 2 !important;
                flex: 0 0 auto !important;
                width: auto !important;
                margin-left: auto !important;
            }

            html.dcwide-enabled.dcwide-show-all-heads .dcwide-headbar {
                order: 3 !important;
                display: flex !important;
                flex: 0 0 100% !important;
                width: 100% !important;
                min-width: 0 !important;
                align-items: center !important;
                flex-wrap: wrap !important;
                gap: 5px 7px !important;
                margin: 2px 0 0 !important;
                padding: 7px 0 3px !important;
                border-top: 1px solid #e2e6f2 !important;
                white-space: normal !important;
                overflow: visible !important;
                box-sizing: border-box !important;
            }

            html.dcwide-enabled.dcwide-show-all-heads .dcwide-headbar a {
                display: inline-flex !important;
                flex: 0 0 auto !important;
                min-width: max-content !important;
                width: auto !important;
                max-width: none !important;
                align-items: center !important;
                justify-content: center !important;
                min-height: 30px !important;
                padding: 0 9px !important;
                border: 1px solid transparent !important;
                border-radius: 6px !important;
                color: #4d5260 !important;
                background: transparent !important;
                font-size: var(--dcwide-head-size) !important;
                font-weight: var(--dcwide-head-weight) !important;
                line-height: 30px !important;
                white-space: nowrap !important;
                overflow: visible !important;
                text-overflow: clip !important;
                text-decoration: none !important;
                transition: background 100ms ease, border-color 100ms ease, color 100ms ease !important;
            }

            html.dcwide-enabled.dcwide-show-all-heads .dcwide-headbar a:hover {
                border-color: #d8deef !important;
                background: #f5f7fc !important;
                color: #29367c !important;
            }

            html.dcwide-enabled.dcwide-show-all-heads .dcwide-headbar a.dcwide-head-active {
                border-color: #3b4890 !important;
                background: #3b4890 !important;
                color: #fff !important;
                font-weight: 700 !important;
                box-shadow: 0 1px 2px rgb(41 54 124 / 14%) !important;
            }

            /**********************************************************
             * 목록 말머리 잘림 방지
             **********************************************************/

            html.dcwide-enabled table.gall_list th.gall_subject,
            html.dcwide-enabled table.gall_list td.gall_subject {
                white-space: nowrap !important;
                text-overflow: clip !important;
            }

            /*
             * DC 말머리 셀 안에는 경우에 따라 짧은 표시명과 전체 표시명이 함께 들어 있다.
             * 후손 전체의 display/width를 강제로 풀면 DOR + DORAI처럼 두 이름이 동시에 노출된다.
             * 따라서 원본 DOM은 건드리지 않고, 최종 표시명만 오버레이로 한 번 그린다.
             */
            html.dcwide-enabled table.gall_list td.gall_subject.dcwide-subject-normalized {
                position: relative !important;
                overflow: visible !important;
                color: transparent !important;
            }

            html.dcwide-enabled table.gall_list td.gall_subject.dcwide-subject-normalized > *:not(.dcwide-subject-display) {
                opacity: 0 !important;
            }

            html.dcwide-enabled table.gall_list td.gall_subject .dcwide-subject-display {
                position: absolute !important;
                inset: 0 !important;
                display: flex !important;
                align-items: center !important;
                justify-content: center !important;
                box-sizing: border-box !important;
                width: 100% !important;
                min-width: 100% !important;
                max-width: none !important;
                padding: 0 4px !important;
                overflow: visible !important;
                color: var(--dcwide-subject-color, #333) !important;
                opacity: 1 !important;
                white-space: nowrap !important;
                text-overflow: clip !important;
                pointer-events: none !important;
            }

            /*
             * 공지 행은 DC 원본에서 gall_subject의 계산 색상이 transparent인
             * 경우가 있어 그 값을 복사하면 "공지"가 사라진다.
             * 공지는 명시적인 색으로 표시해 항상 보이게 한다.
             */
            html.dcwide-enabled table.gall_list
            tr[data-type="icon_notice"]
            td.gall_subject .dcwide-subject-display {
                color: #4b4f5a !important;
                font-weight: 700 !important;
            }

            /**********************************************************
             * 고정 공지 접기 / 펼치기
             **********************************************************/

            html.dcwide-enabled .dcwide-notice-toolbar {
                display: flex;
                align-items: center;
                justify-content: flex-end;
                width: 100%;
                min-height: 38px;
                margin: 0;
                padding: 5px 2px;
                border-bottom: 1px solid #e4e7ef;
                box-sizing: border-box;
            }

            html.dcwide-enabled .dcwide-notice-toggle {
                display: inline-flex;
                align-items: center;
                gap: 6px;
                min-height: 28px;
                padding: 0 10px;
                border: 1px solid #d7dbe7;
                border-radius: 7px;
                background: #fff;
                color: #555c6d;
                font: 600 11.5px/26px -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;
                cursor: pointer;
                box-shadow: 0 1px 2px rgb(30 40 80 / 4%);
                transition: background 100ms ease, border-color 100ms ease, color 100ms ease;
            }

            html.dcwide-enabled .dcwide-notice-toggle:hover {
                border-color: #b7bfd8;
                background: #f7f8fc;
                color: #29367c;
            }

            html.dcwide-enabled .dcwide-notice-toggle .dcwide-notice-chevron {
                display: inline-block;
                color: #7a8194;
                font-size: 10px;
                transition: transform 120ms ease;
            }

            html.dcwide-enabled .dcwide-notice-toolbar:not(.is-collapsed)
            .dcwide-notice-chevron {
                transform: rotate(180deg);
            }

            html.dcwide-enabled table.gall_list
            tr.dcwide-pinned-notice.dcwide-notice-hidden {
                display: none !important;
            }

            /**********************************************************
             * 목록 글자 크기/굵기/간격
             **********************************************************/

            html.dcwide-enabled.dcwide-readability table.gall_list thead th {
                font-size: var(--dcwide-header-size) !important;
                font-weight: var(--dcwide-header-weight) !important;
            }

            html.dcwide-enabled.dcwide-readability table.gall_list tbody td {
                padding-top: var(--dcwide-row-padding) !important;
                padding-bottom: var(--dcwide-row-padding) !important;
                line-height: var(--dcwide-line-height) !important;
            }

            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_num,
            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_num *,
            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_subject,
            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_subject *,
            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_writer,
            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_writer *,
            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_date,
            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_date *,
            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_count,
            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_count *,
            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_recommend,
            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_recommend * {
                font-size: var(--dcwide-meta-size) !important;
                font-weight: var(--dcwide-meta-weight) !important;
                line-height: var(--dcwide-line-height) !important;
            }

            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_tit,
            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_tit > a,
            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_tit > span,
            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_tit > span > a,
            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_tit a:not(.reply_numbox),
            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_tit em:not(.icon_img),
            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_tit strong {
                font-size: var(--dcwide-title-size) !important;
                font-weight: var(--dcwide-title-weight) !important;
                line-height: var(--dcwide-line-height) !important;
            }

            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_tit .reply_num,
            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_tit .reply_numbox,
            html.dcwide-enabled.dcwide-readability table.gall_list tbody td.gall_tit .reply_numbox * {
                font-size: max(11px, calc(var(--dcwide-title-size) - 2px)) !important;
                font-weight: 400 !important;
            }

            /**********************************************************
             * 설정 UI
             **********************************************************/

            #dcwide-toggle-v15 {
                position: fixed;
                right: 16px;
                bottom: 16px;
                z-index: 2147483645;
                height: 38px;
                padding: 0 15px;
                border: 1px solid #29367c;
                border-radius: 9px;
                background: #3b4890;
                color: #fff;
                font: 700 13px/36px -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;
                letter-spacing: -.1px;
                cursor: pointer;
                box-shadow: 0 5px 16px rgb(35 48 106 / 22%);
                transition: transform 120ms ease, background 120ms ease, box-shadow 120ms ease;
            }

            #dcwide-toggle-v15:hover {
                background: #314080;
                box-shadow: 0 7px 20px rgb(35 48 106 / 28%);
                transform: translateY(-1px);
            }

            #dcwide-panel-v15 {
                position: fixed;
                right: 16px;
                bottom: 64px;
                z-index: 2147483646;
                width: 460px;
                max-height: calc(100vh - 92px);
                overflow-y: auto;
                overflow-x: hidden;
                box-sizing: border-box;
                padding: 18px;
                border: 1px solid #d8deee;
                border-radius: 16px;
                background: rgb(255 255 255 / 98%);
                color: #252936;
                box-shadow:
                    0 20px 55px rgb(29 40 88 / 18%),
                    0 2px 8px rgb(29 40 88 / 8%);
                font: 13px/1.45 -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;
                scrollbar-width: thin;
                scrollbar-color: #c9cee0 transparent;
            }

            #dcwide-panel-v15::-webkit-scrollbar { width: 8px; }
            #dcwide-panel-v15::-webkit-scrollbar-thumb {
                border: 2px solid transparent;
                border-radius: 8px;
                background: #c9cee0;
                background-clip: padding-box;
            }

            #dcwide-panel-v15[hidden] { display: none !important; }
            #dcwide-panel-v15 * { box-sizing: border-box; }

            #dcwide-panel-v15 .dcw-brand {
                display: flex;
                align-items: flex-start;
                justify-content: space-between;
                gap: 12px;
                margin-bottom: 12px;
            }

            #dcwide-panel-v15 .dcw-brand-main { min-width: 0; }

            #dcwide-panel-v15 h2 {
                margin: 0;
                color: #263678;
                font-size: 20px;
                line-height: 1.2;
                letter-spacing: -.35px;
            }

            #dcwide-panel-v15 .dcw-byline {
                margin-top: 4px;
                color: #858b9c;
                font-size: 11px;
            }

            #dcwide-panel-v15 .dcw-live-badge {
                display: inline-flex;
                align-items: center;
                justify-content: center;
                flex: 0 0 auto;
                min-height: 25px;
                padding: 0 9px;
                border: 1px solid #d6dcef;
                border-radius: 999px;
                background: #f5f7fd;
                color: #3b4890;
                font-size: 10.5px;
                font-weight: 700;
                white-space: nowrap;
            }

            #dcwide-panel-v15 .dcw-guide {
                margin: 0 0 15px;
                padding: 12px 13px;
                border: 1px solid #dfe4f2;
                border-radius: 10px;
                background: linear-gradient(180deg, #f8faff 0%, #f5f7fc 100%);
                color: #565d70;
            }

            #dcwide-panel-v15 .dcw-guide strong {
                display: block;
                margin-bottom: 4px;
                color: #30384e;
                font-size: 12px;
            }

            #dcwide-panel-v15 .dcw-guide p {
                margin: 0;
                font-size: 11.5px;
                line-height: 1.55;
            }

            #dcwide-panel-v15 .dcw-guide-tips {
                display: flex;
                flex-wrap: wrap;
                gap: 5px;
                margin-top: 8px;
            }

            #dcwide-panel-v15 .dcw-guide-tip {
                display: inline-flex;
                align-items: center;
                min-height: 22px;
                padding: 0 7px;
                border: 1px solid #e1e5f0;
                border-radius: 6px;
                background: #fff;
                color: #6c7282;
                font-size: 10.5px;
            }

            #dcwide-panel-v15 .dcw-section {
                margin-top: 10px;
                padding: 13px;
                border: 1px solid #e5e8f0;
                border-radius: 11px;
                background: #fff;
            }

            #dcwide-panel-v15 .dcw-section:first-of-type { margin-top: 0; }

            #dcwide-panel-v15 .dcw-section-title {
                margin-bottom: 10px;
                color: #30384e;
                font-weight: 800;
                font-size: 13.5px;
                letter-spacing: -.15px;
            }

            #dcwide-panel-v15 .dcw-row {
                display: grid;
                grid-template-columns: 122px 1fr 76px;
                gap: 10px;
                align-items: center;
                min-height: 34px;
                margin: 5px 0;
            }

            #dcwide-panel-v15 .dcw-row > label {
                color: #4c5262;
                font-size: 12px;
            }

            #dcwide-panel-v15 input[type="range"] {
                width: 100%;
                accent-color: #3b4890;
                cursor: pointer;
            }

            #dcwide-panel-v15 input[type="number"] {
                width: 76px;
                height: 32px;
                padding: 0 8px;
                border: 1px solid #d2d6e0;
                border-radius: 7px;
                outline: none;
                background: #fff;
                color: #333947;
                text-align: right;
                transition: border-color 100ms ease, box-shadow 100ms ease;
            }

            #dcwide-panel-v15 input[type="number"]:focus {
                border-color: #6572b2;
                box-shadow: 0 0 0 3px rgb(59 72 144 / 9%);
            }

            #dcwide-panel-v15 .dcw-check {
                display: flex;
                gap: 8px;
                align-items: flex-start;
                min-height: 30px;
                margin: 2px -3px;
                padding: 6px 7px;
                border-radius: 7px;
                color: #424858;
                cursor: pointer;
                transition: background 100ms ease;
            }

            #dcwide-panel-v15 .dcw-check:hover { background: #f6f7fb; }

            #dcwide-panel-v15 .dcw-check input {
                flex: 0 0 auto;
                margin-top: 1px;
                accent-color: #3b4890;
            }

            #dcwide-panel-v15 .dcw-summary {
                margin-top: 10px;
                padding: 9px 10px;
                border: 1px solid #e5e8f0;
                border-radius: 8px;
                background: #f7f8fb;
                color: #686e7c;
                font-size: 10.8px;
                line-height: 1.5;
            }

            #dcwide-panel-v15 .dcw-buttons {
                position: sticky;
                bottom: -18px;
                display: flex;
                gap: 8px;
                margin: 14px -18px -18px;
                padding: 12px 18px 16px;
                border-top: 1px solid #e3e6ee;
                background: rgb(255 255 255 / 97%);
                backdrop-filter: blur(8px);
            }

            #dcwide-panel-v15 .dcw-buttons button {
                flex: 1;
                height: 36px;
                border: 1px solid #cfd4e1;
                border-radius: 8px;
                background: #f7f8fb;
                color: #454b5b;
                font-weight: 700;
                cursor: pointer;
                transition: background 100ms ease, border-color 100ms ease;
            }

            #dcwide-panel-v15 .dcw-buttons button:hover {
                border-color: #aeb6d1;
                background: #eef1f8;
            }

            #dcwide-panel-v15 .dcw-buttons button:last-child {
                border-color: #3b4890;
                background: #3b4890;
                color: #fff;
            }

            #dcwide-panel-v15 .dcw-buttons button:last-child:hover { background: #314080; }
        `;

        document.documentElement.appendChild(style);
    }

    /******************************************************************
     * 게시글 열
     ******************************************************************/

    function findHeaderIndex(table, def) {
        const headers = Array.from(table.querySelectorAll("thead th"));
        let index = headers.findIndex((header) => header.classList.contains(def.cls));
        if (index >= 0) return index;
        return headers.findIndex((header) => def.labels.includes(cleanText(header.textContent)));
    }

    function ensureColgroup(table, count) {
        let colgroup = table.querySelector(":scope > colgroup");
        if (!colgroup) {
            colgroup = document.createElement("colgroup");
            const caption = table.querySelector(":scope > caption");
            if (caption?.nextSibling) table.insertBefore(colgroup, caption.nextSibling);
            else table.insertBefore(colgroup, table.firstChild);
        }

        while (colgroup.children.length < count) {
            colgroup.appendChild(document.createElement("col"));
        }
        return Array.from(colgroup.children);
    }

    function applyColumnWidthsToTable(table) {
        const headers = Array.from(table.querySelectorAll("thead th"));
        if (!headers.length) return;

        table.style.setProperty("table-layout", "fixed", "important");
        table.style.setProperty("width", `${settings.mainWidth}px`, "important");
        table.style.setProperty("max-width", `${settings.mainWidth}px`, "important");

        const cols = ensureColgroup(table, headers.length);

        for (const def of COLUMN_DEFS) {
            const index = findHeaderIndex(table, def);
            if (index < 0) continue;
            const width = `${settings[def.key]}px`;

            const col = cols[index];
            if (col) {
                col.style.setProperty("width", width, "important");
                col.style.setProperty("min-width", width, "important");
                col.style.setProperty("max-width", width, "important");
            }

            const header = headers[index];
            if (header) {
                header.style.setProperty("width", width, "important");
                header.style.setProperty("min-width", width, "important");
                header.style.setProperty("max-width", width, "important");
            }

            table.querySelectorAll(`tbody .${def.cls}`).forEach((cell) => {
                cell.style.setProperty("width", width, "important");
                cell.style.setProperty("min-width", width, "important");
                cell.style.setProperty("max-width", width, "important");
            });
        }
    }

    function applyColumnWidths(root = document) {
        if (root instanceof HTMLTableElement && root.matches("table.gall_list")) {
            applyColumnWidthsToTable(root);
        }
        root.querySelectorAll?.("table.gall_list").forEach(applyColumnWidthsToTable);
    }

    function renderedColumnWidth(table, index) {
        const headers = Array.from(table.querySelectorAll("thead th"));
        const cols = ensureColgroup(table, headers.length);
        const values = [
            cols[index]?.style.width,
            headers[index]?.style.width,
            cols[index] ? getComputedStyle(cols[index]).width : "",
            headers[index] ? getComputedStyle(headers[index]).width : "",
        ];
        for (const value of values) {
            const n = Number.parseFloat(value || "");
            if (Number.isFinite(n) && n > 0) return n;
        }
        return 0;
    }

    function setRenderedColumnWidth(table, index, px) {
        const headers = Array.from(table.querySelectorAll("thead th"));
        if (index < 0 || index >= headers.length) return;
        const cols = ensureColgroup(table, headers.length);
        const width = `${Math.round(px)}px`;

        for (const node of [cols[index], headers[index]]) {
            if (!node) continue;
            node.style.setProperty("width", width, "important");
            node.style.setProperty("min-width", width, "important");
            node.style.setProperty("max-width", width, "important");
        }

        for (const row of table.querySelectorAll("tbody tr")) {
            const cell = row.children[index];
            if (!cell) continue;
            cell.style.setProperty("width", width, "important");
            cell.style.setProperty("min-width", width, "important");
            cell.style.setProperty("max-width", width, "important");
        }
    }

    function plainSubjectText(value) {
        return cleanText(value)
            .replace(/[\uFE0E\uFE0F\u200D]/g, "")
            .replace(/[❔❓❗⭐🎨🚀🗡⚔✅🏆📚🚌🔃]/g, "")
            .replace(/\s+/g, "")
            .toLowerCase();
    }

    function knownSubjectLabels() {
        const labels = new Set([
            "일반", "공지", "설문", "AD",
        ]);

        document.querySelectorAll(
            ".dcwide-headbar a, .list_array_option a[href*='search_head'], .list_array_option a[onclick*='listSearchHead']"
        ).forEach((node) => {
            const text = cleanText(node.textContent);
            if (text && text.length <= 30) labels.add(text);
        });

        return Array.from(labels)
            .filter(Boolean)
            .sort((a, b) => plainSubjectText(b).length - plainSubjectText(a).length);
    }

    function normalizeSubjectCells(root = document) {
        const labels = knownSubjectLabels();
        const cells = [];

        if (root instanceof Element && root.matches?.("td.gall_subject")) cells.push(root);
        root.querySelectorAll?.("table.gall_list tbody td.gall_subject").forEach((cell) => cells.push(cell));

        for (const cell of new Set(cells)) {
            let raw = cell.dataset.dcwideSubjectRaw;
            if (!raw) {
                const existingOverlay = cell.querySelector(":scope > .dcwide-subject-display");
                existingOverlay?.remove();
                raw = cleanText(cell.textContent);
                cell.dataset.dcwideSubjectRaw = raw;
            }

            if (!raw) continue;
            const rawPlain = plainSubjectText(raw);
            let label = labels.find((candidate) => {
                const candidatePlain = plainSubjectText(candidate);
                return candidatePlain && rawPlain.includes(candidatePlain);
            });

            // 알려진 말머리와 매치되지 않는 경우에는 원문을 그대로 두어 사이트 기본 표시를 보존한다.
            if (!label) {
                cell.classList.remove("dcwide-subject-normalized");
                cell.querySelector(":scope > .dcwide-subject-display")?.remove();
                continue;
            }

            const row = cell.closest("tr");
            const isNotice = row?.dataset?.type === "icon_notice";

            // 공지 행은 원본 CSS에서 투명색을 반환하는 경우가 있으므로 직접 고정한다.
            if (isNotice) {
                label = "공지";
                cell.style.setProperty("--dcwide-subject-color", "#4b4f5a");
            } else {
                let sourceColor = "";
                const visibleSource = Array.from(cell.querySelectorAll(":scope > *"))
                    .find((node) => {
                        const style = getComputedStyle(node);
                        const rect = node.getBoundingClientRect();
                        return style.display !== "none"
                            && style.visibility !== "hidden"
                            && rect.width > 0
                            && rect.height > 0;
                    });

                try {
                    sourceColor = getComputedStyle(visibleSource || cell).color || "";
                } catch (_error) {
                    sourceColor = "";
                }

                /*
                 * survey / AD 등도 원본에서 rgba(..., 0)으로 계산되는 경우가 있다.
                 * 투명색을 오버레이 색상으로 복사하지 않는다.
                 */
                const transparent =
                    !sourceColor
                    || sourceColor === "transparent"
                    || /^rgba\([^)]*,\s*0(?:\.0+)?\s*\)$/i.test(sourceColor);

                cell.style.setProperty(
                    "--dcwide-subject-color",
                    transparent ? "#4b4f5a" : sourceColor,
                );
            }

            let overlay = cell.querySelector(":scope > .dcwide-subject-display");
            if (!overlay) {
                overlay = document.createElement("span");
                overlay.className = "dcwide-subject-display";
                overlay.setAttribute("aria-hidden", "true");
                cell.appendChild(overlay);
            }
            overlay.textContent = label;
            cell.dataset.dcwideSubjectLabel = label;
            cell.classList.add("dcwide-subject-normalized");
        }
    }

    function neededSubjectWidth(table) {
        const cells = Array.from(table.querySelectorAll("tbody td.gall_subject"));
        let needed = Math.max(AUTO_SUBJECT_MIN_WIDTH, Number(settings.subjectWidth) || 0);

        for (const cell of cells) {
            const text = cell.dataset.dcwideSubjectLabel || cleanText(cell.textContent);
            if (!text) continue;

            const probe = document.createElement("span");
            const cs = getComputedStyle(cell);
            probe.textContent = text;
            probe.style.cssText = "position:fixed;left:-10000px;top:-10000px;visibility:hidden;white-space:nowrap;";
            probe.style.fontFamily = cs.fontFamily;
            probe.style.fontSize = settings.readabilityEnabled ? `${settings.metaFontSize}px` : cs.fontSize;
            probe.style.fontWeight = settings.readabilityEnabled ? String(settings.metaWeight) : cs.fontWeight;
            probe.style.letterSpacing = cs.letterSpacing;
            document.body.appendChild(probe);
            needed = Math.max(needed, probe.getBoundingClientRect().width + 30);
            probe.remove();
        }

        return clamp(Math.ceil(needed), AUTO_SUBJECT_MIN_WIDTH, 280);
    }

    function applyAutoSubjectWidth(root = document) {
        if (!settings.autoSubjectWidth) return;

        normalizeSubjectCells(root);

        const tables = [];
        if (root instanceof HTMLTableElement && root.matches("table.gall_list")) tables.push(root);
        root.querySelectorAll?.("table.gall_list").forEach((table) => tables.push(table));

        for (const table of new Set(tables)) {
            const subjectDef = COLUMN_DEFS.find((def) => def.key === "subjectWidth");
            const titleDef = COLUMN_DEFS.find((def) => def.key === "titleWidth");
            const subjectIndex = findHeaderIndex(table, subjectDef);
            const titleIndex = findHeaderIndex(table, titleDef);
            if (subjectIndex < 0) continue;

            const currentSubject = renderedColumnWidth(table, subjectIndex);
            const desiredSubject = Math.max(currentSubject, neededSubjectWidth(table));
            if (desiredSubject <= currentSubject + 0.5) continue;

            const delta = desiredSubject - currentSubject;
            setRenderedColumnWidth(table, subjectIndex, desiredSubject);

            if (titleIndex >= 0) {
                const currentTitle = renderedColumnWidth(table, titleIndex);
                if (currentTitle - delta >= MIN_TITLE_WIDTH) {
                    setRenderedColumnWidth(table, titleIndex, currentTitle - delta);
                }
            }
        }
    }

    /******************************************************************
     * 말머리 한 줄 전체 표시
     ******************************************************************/

    function buildHeadHref(source) {
        const onclick = source.getAttribute("onclick") || "";
        const headValue = onclick.match(/listSearchHead\((\d+)\)/)?.[1];

        if (headValue !== undefined) {
            const url = new URL(location.href);
            url.searchParams.delete("page");
            url.searchParams.delete("exception_mode");
            url.searchParams.set("search_head", headValue);
            return { href: url.href, headValue };
        }

        const rawHref = source.getAttribute("href") || "";
        if (!rawHref || /^javascript:/i.test(rawHref) || rawHref === "#") return null;

        try {
            const url = new URL(rawHref, location.href);
            return {
                href: url.href,
                headValue: url.searchParams.get("search_head"),
            };
        } catch {
            return null;
        }
    }

    function mountAllHeads() {
        const listOptions = document.querySelector(".list_array_option");
        if (!listOptions) return;

        const nativeCenter = listOptions.querySelector(".center_box");
        if (!nativeCenter) return;

        if (!settings.showAllHeads) {
            listOptions.querySelector(".dcwide-headbar")?.remove();
            return;
        }

        const sources = Array.from(nativeCenter.querySelectorAll("a"));
        const entries = [];
        const seen = new Set();

        for (const source of sources) {
            const label = cleanText(source.textContent);
            if (!label) continue;

            const target = buildHeadHref(source);
            if (!target?.href) continue;

            const dedupeKey = `${label}\u0000${target.headValue ?? target.href}`;
            if (seen.has(dedupeKey)) continue;
            seen.add(dedupeKey);

            entries.push({ label, ...target });
        }

        if (!entries.length) return;

        let bar = listOptions.querySelector(".dcwide-headbar");
        if (!bar) {
            bar = document.createElement("nav");
            bar.className = "dcwide-headbar";
            bar.setAttribute("aria-label", "말머리 전체 목록");

            const rightBox = listOptions.querySelector(".right_box");
            if (rightBox) listOptions.insertBefore(bar, rightBox);
            else listOptions.appendChild(bar);
        }

        const currentHead = new URL(location.href).searchParams.get("search_head");
        bar.replaceChildren();

        for (const entry of entries) {
            const link = document.createElement("a");
            link.href = entry.href;
            link.textContent = entry.label;
            if (entry.headValue !== null && entry.headValue === currentHead) {
                link.classList.add("dcwide-head-active");
                link.setAttribute("aria-current", "page");
            }
            bar.appendChild(link);
        }
    }

    /******************************************************************
     * 갤러리 정보 와이드 카드
     ******************************************************************/

    function galleryTypeLabel() {
        const path = location.pathname;
        if (path.includes("/mgallery/")) return "마이너 갤러리";
        if (path.includes("/mini/")) return "미니 갤러리";
        if (path.includes("/person/")) return "인물 갤러리";
        return "정식 갤러리";
    }

    function findManagedIntroRoot() {
        return document.querySelector(
            ".issue_contentbox, .minor_intro_box, .mini_intro_box, .person_intro_box",
        );
    }

    function findRegularInfoRoot() {
        if (location.pathname.includes("/mgallery/")
            || location.pathname.includes("/mini/")
            || location.pathname.includes("/person/")) return null;

        // 정식 갤러리는 갤러리 정보가 화면 본문이 아니라 숨겨진 레이어에 들어 있다.
        // 클래스명이 바뀌어도 버티도록 "개설일/카테고리/순위" 텍스트 조합으로 찾는다.
        const candidateMap = new Map();
        const addCandidate = (node) => {
            if (!(node instanceof Element) || node.closest(".dcwide-gallery-card")) return;
            const text = cleanText(node.textContent);
            if (!text.includes("개설일") || !text.includes("카테고리")) return;
            if (!text.includes("전체 순위") && !text.includes("순위권 밖")
                && !text.includes("흥한갤") && !text.includes("정전갤")) return;

            let score = Math.min(text.length, 10000);
            if (text.includes("갤러리 정보")) score -= 180;
            if (text.includes("소개 이미지")) score -= 80;
            if (text.includes("레이어 닫기")) score -= 30;
            const previous = candidateMap.get(node);
            if (previous === undefined || score < previous) candidateMap.set(node, score);
        };

        // 1차: "개설일" 텍스트 노드에서 위로 올라가며 정보 레이어를 찾는다.
        // body 전체 Element의 textContent를 반복 계산하지 않아 페이지가 커도 부담이 작다.
        const walker = document.createTreeWalker(
            document.body,
            NodeFilter.SHOW_TEXT,
            {
                acceptNode(textNode) {
                    const text = cleanText(textNode.nodeValue);
                    return /^개설일(?:\s|$)/.test(text) && text.length <= 70
                        ? NodeFilter.FILTER_ACCEPT
                        : NodeFilter.FILTER_REJECT;
                },
            },
        );

        let textNode = walker.nextNode();
        while (textNode) {
            let node = textNode.parentElement;
            for (let depth = 0; depth < 9 && node && node !== document.body; depth += 1) {
                addCandidate(node);
                node = node.parentElement;
            }
            textNode = walker.nextNode();
        }

        // 2차 fallback: 레이어의 직접 구조를 모르더라도 전체 후보에서 잡는다.
        if (!candidateMap.size) {
            for (const node of document.querySelectorAll("div, section, article")) {
                const text = cleanText(node.textContent);
                if (text.length < 40 || text.length > 5000) continue;
                addCandidate(node);
            }
        }

        return Array.from(candidateMap.entries())
            .sort((left, right) => left[1] - right[1])[0]?.[0] || null;
    }

    function managerRows(source) {
        if (!source) return new Map();
        const result = new Map();
        const valid = new Set(["매니저", "부매니저", "개설일"]);

        source.querySelectorAll(".info_cont").forEach((row) => {
            const label = cleanText(row.querySelector(".tit, dt, strong")?.textContent).replace(/:$/, "");
            if (!valid.has(label) || result.has(label)) return;

            const copy = (row.querySelector(".cont, dd, p") || row).cloneNode(true);
            copy.querySelectorAll("button, script, style, .btn_user_data, .pop_wrap").forEach((node) => node.remove());
            copy.querySelectorAll("[title]").forEach((node) => {
                const title = cleanText(node.getAttribute("title"));
                if (title && cleanText(node.textContent).length <= 12) node.textContent = title;
            });
            result.set(label, cleanText(copy.textContent));
        });

        return result;
    }

    function normalizeImageUrl(raw) {
        const value = String(raw || "").trim();
        if (!value || value.startsWith("data:") || value.startsWith("blob:")) return "";
        try {
            return new URL(value, location.href).href;
        } catch {
            return "";
        }
    }

    function extractCover(source) {
        if (!source) return null;

        const cover = source.querySelector(".mintro_imgbox .cover, .bgcover .cover");
        if (cover) {
            const link = cover.closest("a") || source.querySelector(".mintro_imgbox");
            const popupSource = link?.getAttribute("href") || link?.getAttribute("onclick") || "";
            const popupUrl = popupSource.match(/imgPop\(\s*['\"]([^'\"]+)/)?.[1] || "";
            const originalUrl = popupUrl.replace(/\/viewimagePop\.php(?=\?)/, "/viewimage.php");
            const background = cover.style.backgroundImage || getComputedStyle(cover).backgroundImage || "";
            const thumbUrl = background.match(/^url\((['\"]?)(.*?)\1\)$/)?.[2] || "";
            return {
                url: normalizeImageUrl(originalUrl || thumbUrl),
                fallback: normalizeImageUrl(thumbUrl),
                background,
            };
        }

        // 정식 갤러리의 숨겨진 "갤러리 정보" 레이어는 일반 img를 쓴다.
        const images = Array.from(source.querySelectorAll("img"));
        const preferred = images.find((img) => {
            const src = img.getAttribute("data-src") || img.getAttribute("data-original") || img.getAttribute("src") || "";
            const alt = cleanText(img.getAttribute("alt"));
            return /zzbang\.dcinside\.com/i.test(src) || /소개\s*이미지/.test(alt);
        });
        const fallbackImage = images.find((img) => {
            const src = img.getAttribute("data-src") || img.getAttribute("data-original") || img.getAttribute("src") || "";
            return src && !/(icon|logo|btn_|sp_img|loading|blank|nstatic\.dcinside)/i.test(src);
        });
        const image = preferred || fallbackImage;

        if (image) {
            const src = image.getAttribute("data-src") || image.getAttribute("data-original") || image.getAttribute("src") || "";
            const url = normalizeImageUrl(src);
            if (url) return { url, fallback: "", background: "" };
        }

        const styled = Array.from(source.querySelectorAll("[style*='background']")).find((node) => {
            const bg = node.style.backgroundImage || "";
            return /url\(/i.test(bg) && !/(icon|logo|sp_img)/i.test(bg);
        });
        if (styled) {
            const background = styled.style.backgroundImage || "";
            const url = normalizeImageUrl(background.match(/^url\((['\"]?)(.*?)\1\)$/)?.[2] || "");
            if (url) return { url, fallback: "", background };
        }

        return null;
    }

    function findNativeRankTrigger(source) {
        if (!source) return null;
        const direct = Array.from(source.querySelectorAll("button, a")).find((node) => {
            const text = cleanText(node.textContent);
            return text === "전체 순위" || text.includes("흥한갤 전체 순위");
        });
        if (direct) return direct;

        const rankNode = source.querySelector(".rankingcon, .mini_ranktxt, .rank_txt, .ranktxt");
        if (!rankNode) return null;
        return rankNode.closest("a, button") || rankNode.querySelector?.("a, button") || rankNode;
    }

    function extractManagedGalleryInfo(introRoot) {
        if (!introRoot) return null;
        const source = introRoot.querySelector(".img_contbox") || introRoot;
        const managers = managerRows(introRoot);

        return {
            sourceKind: "managed",
            introRoot,
            source,
            typeLabel: galleryTypeLabel(),
            description: cleanText(source.querySelector(".mintro_txt")?.textContent),
            category: "",
            opened: managers.get("개설일") || "",
            memberLabel: cleanText(source.querySelector(".mini_set.membernum .txt, .membernum .txt")?.textContent),
            memberNumber: cleanText(source.querySelector(".mini_set.membernum .members_num, .membernum .members_num")?.textContent),
            managers,
            coverData: extractCover(source),
            rankLabel: cleanText(source.querySelector(".mini_ranktxt, .rank_txt, .ranktxt")?.textContent),
            rankNumber: cleanText(source.querySelector(".mini_ranknum, .rank_num, .ranknum")?.textContent),
            rankTrigger: findNativeRankTrigger(source),
        };
    }

    function extractRegularGalleryInfo(infoRoot) {
        if (!infoRoot) return null;
        const text = cleanText(infoRoot.textContent);

        const rankMatch = text.match(/(대흥갤|안흥한갤|정전갤|흥한갤)\s*((?:\d\s*){1,4}위|순위권\s*밖)/);
        const opened = text.match(/개설일\s*([12]\d{3}[-./]\d{2}[-./]\d{2})/)?.[1] || "";
        const category = text.match(/카테고리\s*([가-힣A-Za-z0-9+&/·_-]{1,30})/)?.[1] || "";

        return {
            sourceKind: "regular",
            introRoot: null,
            source: infoRoot,
            typeLabel: "정식 갤러리",
            description: "",
            category,
            opened,
            memberLabel: "",
            memberNumber: "",
            managers: new Map(),
            coverData: extractCover(infoRoot),
            rankLabel: rankMatch?.[1] || "",
            rankNumber: (rankMatch?.[2] || "").replace(/\s+/g, ""),
            rankTrigger: findNativeRankTrigger(infoRoot),
        };
    }

    function collectGalleryInfo() {
        const managed = findManagedIntroRoot();
        if (managed) {
            const info = extractManagedGalleryInfo(managed);
            const hasData = info && (
                info.description || info.opened || info.memberNumber || info.coverData
                || info.rankLabel || info.rankNumber || info.managers.size
            );
            if (hasData) return info;
        }

        const regularRoot = findRegularInfoRoot();
        if (regularRoot) return extractRegularGalleryInfo(regularRoot);
        return null;
    }

    function appendMetaItem(parent, label, value) {
        if (!value) return;
        const item = document.createElement("span");
        item.className = "dcwide-meta-item";

        const strong = document.createElement("strong");
        strong.textContent = label;

        const text = document.createElement("span");
        text.textContent = value;

        item.append(strong, text);
        parent.appendChild(item);
    }

    function mountWideGalleryCard() {
        const oldCard = document.querySelector(".dcwide-gallery-card");

        if (!settings.fixGalleryInfo) {
            oldCard?.remove();
            document.querySelectorAll(".dcwide-native-intro-hidden").forEach((node) => {
                node.classList.remove("dcwide-native-intro-hidden");
            });
            return;
        }

        const pageHead = document.querySelector(".page_head");
        const info = collectGalleryInfo();

        if (!pageHead || !info) {
            oldCard?.remove();
            document.querySelectorAll(".dcwide-native-intro-hidden").forEach((node) => {
                node.classList.remove("dcwide-native-intro-hidden");
            });
            return;
        }

        // SPA/동적 갱신으로 종류가 바뀌었을 때 이전 숨김 상태가 남지 않게 한다.
        document.querySelectorAll(".dcwide-native-intro-hidden").forEach((node) => {
            if (node !== info.introRoot) node.classList.remove("dcwide-native-intro-hidden");
        });

        let card = oldCard;
        if (!card) {
            card = document.createElement("section");
            card.className = "dcwide-gallery-card";
            card.setAttribute("aria-label", "갤러리 정보");
            pageHead.insertAdjacentElement("afterend", card);
        } else if (card.previousElementSibling !== pageHead) {
            pageHead.insertAdjacentElement("afterend", card);
        }

        const hasManagers = Boolean(info.managers.get("매니저") || info.managers.get("부매니저"));
        const hasRank = Boolean(info.rankLabel || info.rankNumber);

        card.classList.toggle("dcwide-no-cover", !info.coverData);
        card.classList.toggle("dcwide-no-rank", !hasRank);
        card.classList.toggle("dcwide-no-managers", !hasManagers);
        card.dataset.galleryKind = info.sourceKind;
        card.replaceChildren();

        if (info.coverData) {
            const coverWrap = document.createElement("div");
            coverWrap.className = "dcwide-gallery-cover";

            if (info.coverData.url) {
                const img = document.createElement("img");
                img.src = info.coverData.url;
                img.alt = "갤러리 소개 이미지";
                img.decoding = "async";
                if (info.coverData.fallback && info.coverData.fallback !== info.coverData.url) {
                    img.addEventListener("error", () => {
                        if (img.src !== info.coverData.fallback) img.src = info.coverData.fallback;
                    }, { once: true });
                }
                coverWrap.appendChild(img);
            } else if (info.coverData.background) {
                const bg = document.createElement("span");
                bg.className = "dcwide-cover-bg";
                bg.style.backgroundImage = info.coverData.background;
                coverWrap.appendChild(bg);
            }

            if (coverWrap.childElementCount) card.appendChild(coverWrap);
            else card.classList.add("dcwide-no-cover");
        }

        const main = document.createElement("div");
        main.className = "dcwide-gallery-main";

        const kicker = document.createElement("div");
        kicker.className = "dcwide-gallery-kicker";

        const typeBadge = document.createElement("span");
        typeBadge.className = "dcwide-gallery-badge";
        typeBadge.textContent = info.typeLabel;
        kicker.appendChild(typeBadge);

        if (info.category) {
            const categoryChip = document.createElement("span");
            categoryChip.className = "dcwide-gallery-chip";
            categoryChip.textContent = info.category;
            kicker.appendChild(categoryChip);
        }

        main.appendChild(kicker);

        if (info.description) {
            const desc = document.createElement("div");
            desc.className = "dcwide-gallery-description";
            desc.textContent = info.description;
            main.appendChild(desc);
        }

        const meta = document.createElement("div");
        meta.className = "dcwide-gallery-meta-line";
        appendMetaItem(meta, "개설일", info.opened);
        if (info.memberLabel || info.memberNumber) {
            appendMetaItem(meta, info.memberLabel || "멤버", info.memberNumber);
        }
        if (meta.childElementCount) main.appendChild(meta);

        card.appendChild(main);

        if (hasManagers) {
            const managerWrap = document.createElement("div");
            managerWrap.className = "dcwide-gallery-managers";

            for (const label of ["매니저", "부매니저"]) {
                const value = info.managers.get(label);
                if (!value) continue;

                const row = document.createElement("div");
                row.className = "dcwide-manager-row";

                const strong = document.createElement("strong");
                strong.textContent = label;

                const text = document.createElement("span");
                text.className = "dcwide-manager-value";
                text.textContent = value;

                row.append(strong, text);
                managerWrap.appendChild(row);
            }

            if (managerWrap.childElementCount) card.appendChild(managerWrap);
        }

        if (hasRank) {
            const rank = document.createElement("div");
            rank.className = "dcwide-gallery-rank";

            const labelText = info.rankLabel || "흥한갤";
            if (labelText.includes("대흥갤")) rank.classList.add("dcwide-rank-major");
            if (labelText.includes("안흥") || labelText.includes("정전") || info.rankNumber.includes("순위권밖")) {
                rank.classList.add("dcwide-rank-cold");
            }

            const label = document.createElement("div");
            label.className = "dcwide-rank-label";
            label.textContent = labelText;

            const number = document.createElement("div");
            number.className = "dcwide-rank-number";
            const normalized = String(info.rankNumber || "").replace(/\s+/g, "");
            if (/^\d+$/.test(normalized)) number.textContent = `${normalized}위`;
            else if (/^\d+위$/.test(normalized) || normalized === "순위권밖") {
                number.textContent = normalized === "순위권밖" ? "순위권 밖" : normalized;
            } else number.textContent = normalized;

            rank.append(label, number);

            if (info.rankTrigger) {
                const button = document.createElement("button");
                button.type = "button";
                button.className = "dcwide-rank-button";
                button.textContent = "흥한갤 전체 순위";
                button.addEventListener("click", (event) => {
                    event.preventDefault();
                    event.stopPropagation();
                    info.rankTrigger.click?.();
                });
                rank.appendChild(button);
            }

            card.appendChild(rank);
        }

        // 마이너/미니/인물의 기존 소개 박스는 실제 화면에 노출되므로 숨긴다.
        // 정식 갤러리의 정보 레이어는 사용자가 '갤러리 정보' 버튼으로 다시 열 수 있어야 하므로 숨기지 않는다.
        if (info.sourceKind === "managed" && info.introRoot) {
            info.introRoot.classList.add("dcwide-native-intro-hidden");
        }
    }

    function escapeHtml(value) {
        return String(value || "")
            .replaceAll("&", "&amp;")
            .replaceAll("<", "&lt;")
            .replaceAll(">", "&gt;")
            .replaceAll('"', "&quot;")
            .replaceAll("'", "&#39;");
    }

    /******************************************************************
     * 글 보기 / 댓글 보정
     ******************************************************************/

    function isViewPage() {
        return /\/board\/view\/?$/.test(location.pathname);
    }

    function fixCommentSubmitAlignment() {
        if (!isViewPage()) return;

        document.querySelectorAll(".cmt_cont_bottm").forEach((bottom) => {
            bottom.querySelectorAll(".dcwide-comment-submit-group")
                .forEach((node) => node.classList.remove("dcwide-comment-submit-group"));

            const submitControls = Array.from(bottom.querySelectorAll("button, a, input[type='button'], input[type='submit']"))
                .filter((node) => {
                    const label = cleanText(node.value || node.textContent);
                    return label === "등록" || label === "등록+추천" || label === "등록 + 추천";
                });

            if (!submitControls.length) return;

            // 두 버튼이 같은 래퍼(.fr 등)에 들어있으면 그 래퍼를 오른쪽 정렬 대상으로 사용.
            const first = submitControls[0];
            let group = first;
            let parent = first.parentElement;
            while (parent && parent !== bottom) {
                if (submitControls.every((control) => parent.contains(control))) {
                    group = parent;
                    parent = parent.parentElement;
                    continue;
                }
                break;
            }
            if (group === bottom) group = first.parentElement || first;
            group.classList.add("dcwide-comment-submit-group");
        });
    }

    function dcconListIsNativeVisible(list) {
        /*
         * getComputedStyle() 금지:
         * active list에 display:grid!important를 걸기 때문에 computed style은
         * 이전 팩도 계속 보이는 것으로 오판할 수 있다.
         * DC가 직접 바꾸는 inline style(display:none)만 기준으로 삼는다.
         */
        return list instanceof HTMLElement && list.style.display !== "none";
    }

    function getActiveDcconList() {
        const lists = Array.from(
            document.querySelectorAll("#div_con .dccon_list_box.dcconlist > ul.dccon_list"),
        );

        return lists.find(dcconListIsNativeVisible) || null;
    }

    function clearDcconEnhancementClasses() {
        document.querySelectorAll("#div_con ul.dccon_list").forEach((list) => {
            list.classList.remove("dcwide-dccon-active");
            delete list.dataset.dcwidePage;

            list.querySelectorAll(":scope > li.list_li").forEach((item) => {
                item.classList.remove("dcwide-dccon-show", "dcwide-dccon-hide");
            });
        });
    }

    function refreshDcconGrid() {
        if (!isViewPage()) return;

        if (!settings.enhanceDccon) {
            clearDcconEnhancementClasses();
            return;
        }

        const panel = document.querySelector("#div_con");
        if (!panel) return;

        const lists = Array.from(
            panel.querySelectorAll(".dccon_list_box.dcconlist > ul.dccon_list"),
        );
        if (!lists.length) return;

        /*
         * 가장 먼저 우리 active 클래스를 전부 제거한다.
         * 그래야 이전 팩의 display:grid!important가 DC의 display:none을
         * 덮어쓰지 않는다.
         */
        for (const list of lists) {
            list.classList.remove("dcwide-dccon-active");
            delete list.dataset.dcwidePage;

            list.querySelectorAll(":scope > li.list_li").forEach((item) => {
                item.classList.remove("dcwide-dccon-show", "dcwide-dccon-hide");
            });
        }

        const active = lists.find(dcconListIsNativeVisible);
        if (!active) return;

        active.classList.add("dcwide-dccon-active");

        const scrollBox = panel.querySelector(".dccon_list_box.dcconlist");
        if (scrollBox) {
            const activeKey =
                active.className
                + "::"
                + (active.querySelector(".img_dccon")?.getAttribute("package_idx") || "");

            if (scrollBox.dataset.dcwideActiveList !== activeKey) {
                scrollBox.scrollTop = 0;
                scrollBox.dataset.dcwideActiveList = activeKey;
            }
        }
    }

    function scheduleDcconRefresh() {
        /*
         * DC native 클릭이 UL의 inline display를 바꾼 뒤
         * 새 선택 팩을 세 번 확인한다.
         */
        window.setTimeout(refreshDcconGrid, 0);
        window.setTimeout(refreshDcconGrid, 35);
        window.setTimeout(refreshDcconGrid, 120);
    }

    function bindDcconEnhancerEvents() {
        if (window.__dcwideDcconEventsBoundV22) return;

        document.addEventListener(
            "click",
            (event) => {
                if (!settings.enabled || !settings.enhanceDccon || !isViewPage()) return;
                if (!(event.target instanceof Element)) return;

                const packTrigger = event.target.closest(
                    "#div_con .dccon_btn, #div_con .btn_dccon_prev, #div_con .btn_dccon_next",
                );

                if (packTrigger) {
                    /*
                     * capture 단계에서 이전 팩의 강제 display를 즉시 해제한다.
                     * preventDefault/stopPropagation은 하지 않으므로
                     * DC 원래 팩 전환 클릭은 그대로 실행된다.
                     */
                    clearDcconEnhancementClasses();
                    scheduleDcconRefresh();
                    return;
                }

                const openTrigger = event.target.closest(".tx_dccon");
                if (openTrigger) {
                    scheduleDcconRefresh();
                }
            },
            true,
        );

        window.__dcwideDcconEventsBoundV22 = true;
    }

    function applyViewFixes() {
        if (!isViewPage()) return;

        fixCommentSubmitAlignment();
        bindDcconEnhancerEvents();
        refreshDcconGrid();
    }

    /******************************************************************
     * 고정 공지 접기 / 펼치기
     ******************************************************************/

    function isListPage() {
        return /\/board\/lists\/?$/.test(location.pathname);
    }

    function getPinnedNoticeRows() {
        if (!isListPage()) return [];

        const result = [];

        document.querySelectorAll("table.gall_list").forEach((table) => {
            const rows = Array.from(
                table.querySelectorAll("tbody tr.ub-content[data-type='icon_notice']"),
            );

            if (!rows.length) return;

            /*
             * "공지" 전용 목록에서 모든 행을 접어버리는 상황을 피한다.
             * 일반 게시글이 함께 있는 목록에서만 상단 고정 공지로 취급한다.
             */
            const hasOrdinaryPosts = Array.from(
                table.querySelectorAll("tbody tr.ub-content.us-post"),
            ).some((row) => row.dataset.type !== "icon_notice");

            if (!hasOrdinaryPosts) return;

            result.push(...rows);
        });

        return result;
    }

    function clearNoticeCollapse() {
        document.querySelectorAll("tr.dcwide-pinned-notice").forEach((row) => {
            row.classList.remove("dcwide-pinned-notice", "dcwide-notice-hidden");
        });

        document.querySelectorAll(".dcwide-notice-toolbar").forEach((node) => node.remove());
    }

    function updateNoticeToolbar(toolbar, count) {
        if (!toolbar) return;

        toolbar.classList.toggle("is-collapsed", noticeCollapsed);

        const button = toolbar.querySelector(".dcwide-notice-toggle");
        if (!button) return;

        button.setAttribute("aria-expanded", String(!noticeCollapsed));

        const label = button.querySelector(".dcwide-notice-label");
        if (label) {
            label.textContent =
                noticeCollapsed
                    ? `공지 펼치기 (${count})`
                    : `공지 접기 (${count})`;
        }
    }

    function mountNoticeToggle() {
        if (!isListPage()) {
            clearNoticeCollapse();
            return;
        }

        const rows = getPinnedNoticeRows();

        // 먼저 예전 표시 상태를 정리한 뒤 현재 행만 다시 지정한다.
        document.querySelectorAll("tr.dcwide-pinned-notice").forEach((row) => {
            if (!rows.includes(row)) {
                row.classList.remove("dcwide-pinned-notice", "dcwide-notice-hidden");
            }
        });

        if (!rows.length) {
            document.querySelectorAll(".dcwide-notice-toolbar").forEach((node) => node.remove());
            return;
        }

        rows.forEach((row) => {
            row.classList.add("dcwide-pinned-notice");
            row.classList.toggle("dcwide-notice-hidden", noticeCollapsed);
        });

        /*
         * 공지 행이 들어 있는 gall_listwrap에 한 개의 토글만 둔다.
         * survey/AD 행은 공지가 아니므로 접지 않는다.
         */
        const table = rows[0].closest("table.gall_list");
        const wrap = table?.closest(".gall_listwrap") || table?.parentElement;
        if (!table || !wrap) return;

        let toolbar = wrap.querySelector(":scope > .dcwide-notice-toolbar");

        if (!toolbar) {
            toolbar = document.createElement("div");
            toolbar.className = "dcwide-notice-toolbar";

            const button = document.createElement("button");
            button.type = "button";
            button.className = "dcwide-notice-toggle";
            button.innerHTML =
                '<span class="dcwide-notice-chevron">▲</span>'
                + '<span class="dcwide-notice-label"></span>';

            button.addEventListener("click", () => {
                noticeCollapsed = !noticeCollapsed;

                getPinnedNoticeRows().forEach((row) => {
                    row.classList.add("dcwide-pinned-notice");
                    row.classList.toggle("dcwide-notice-hidden", noticeCollapsed);
                });

                updateNoticeToolbar(toolbar, getPinnedNoticeRows().length);
            });

            toolbar.append(button);
            wrap.insertBefore(toolbar, table);
        }

        updateNoticeToolbar(toolbar, rows.length);
    }

    /******************************************************************
     * 전체 적용
     ******************************************************************/

    function applyAll(root = document) {
        applyVariables();

        if (!settings.enabled) {
            clearNoticeCollapse();
            return;
        }

        applyColumnWidths(root);
        mountAllHeads();
        normalizeSubjectCells(root);
        applyAutoSubjectWidth(root);
        mountNoticeToggle();
        mountWideGalleryCard();
        applyViewFixes();
        updatePanelSummary();
    }

    function queueApply(root = document) {
        if (applyQueued) return;
        applyQueued = true;
        requestAnimationFrame(() => {
            applyQueued = false;
            applyAll(root);
        });
    }

    /******************************************************************
     * 설정 변경
     ******************************************************************/

    function setNumericSetting(key, nextValue, min, max) {
        const next = clamp(nextValue, min, max);
        const previous = Number(settings[key]);
        if (next === previous) return;

        if (key === "mainWidth") {
            const delta = next - previous;
            settings.mainWidth = next;
            settings.titleWidth = Math.max(MIN_TITLE_WIDTH, Number(settings.titleWidth) + delta);
        } else if (key === "titleWidth") {
            const delta = next - previous;
            settings.titleWidth = next;
            settings.mainWidth = clamp(Number(settings.mainWidth) + delta, 900, 2300);
        } else if (NON_TITLE_COLUMN_KEYS.has(key)) {
            const delta = next - previous;
            settings[key] = next;
            const wantedTitle = Number(settings.titleWidth) - delta;

            if (wantedTitle >= MIN_TITLE_WIDTH) {
                settings.titleWidth = wantedTitle;
            } else {
                const shortage = MIN_TITLE_WIDTH - wantedTitle;
                settings.titleWidth = MIN_TITLE_WIDTH;
                settings.mainWidth = clamp(Number(settings.mainWidth) + shortage, 900, 2300);
            }
        } else {
            settings[key] = next;
        }

        if (COLUMN_KEYS.has(key) || key === "mainWidth") {
            const diff = Number(settings.mainWidth) - columnSum();
            settings.titleWidth = Math.max(MIN_TITLE_WIDTH, Number(settings.titleWidth) + diff);
        }

        saveSettings();
        syncPanelControls();
        applyAll(document);
    }

    const RANGE_CONFIG = [
        { key: "mainWidth", label: "본문 폭", min: 1000, max: 2200, step: 10 },
        { key: "sidebarWidth", label: "우측 사이드바", min: 180, max: 420, step: 10 },
        { key: "columnGap", label: "본문↔사이드", min: 0, max: 80, step: 2 },
        { key: "numberWidth", label: "번호", min: 45, max: 180, step: 5 },
        { key: "subjectWidth", label: "말머리", min: 55, max: 260, step: 5 },
        { key: "titleWidth", label: "제목", min: MIN_TITLE_WIDTH, max: 1500, step: 10 },
        { key: "writerWidth", label: "글쓴이", min: 100, max: 500, step: 10 },
        { key: "dateWidth", label: "작성일", min: 55, max: 180, step: 5 },
        { key: "countWidth", label: "조회", min: 45, max: 150, step: 5 },
        { key: "recommendWidth", label: "추천", min: 45, max: 150, step: 5 },
    ];

    const TEXT_RANGE_CONFIG = [
        { key: "titleFontSize", label: "제목 글자", min: 11, max: 30, step: 1 },
        { key: "metaFontSize", label: "기타 글자", min: 10, max: 24, step: 1 },
        { key: "headerFontSize", label: "열 제목", min: 10, max: 22, step: 1 },
        { key: "titleWeight", label: "제목 굵기", min: 400, max: 900, step: 100 },
        { key: "metaWeight", label: "기타 굵기", min: 300, max: 800, step: 100 },
        { key: "headerWeight", label: "열 제목 굵기", min: 400, max: 900, step: 100 },
        { key: "lineHeightPercent", label: "줄간격 (%)", min: 100, max: 220, step: 5 },
        { key: "rowPadding", label: "행 위아래", min: 0, max: 20, step: 1 },
        { key: "headFontSize", label: "상단 말머리", min: 11, max: 22, step: 1 },
        { key: "headWeight", label: "말머리 굵기", min: 400, max: 900, step: 100 },
    ];

    const DCCON_RANGE_CONFIG = [
        { key: "dcconColumns", label: "디시콘 가로", min: 6, max: 12, step: 1 },
        { key: "dcconScalePercent", label: "디시콘 크기 (%)", min: 100, max: 160, step: 5 },
    ];

    function makeRange(config) {
        const row = document.createElement("div");
        row.className = "dcw-row";

        const label = document.createElement("label");
        label.textContent = config.label;

        const range = document.createElement("input");
        range.type = "range";
        range.min = String(config.min);
        range.max = String(config.max);
        range.step = String(config.step);

        const number = document.createElement("input");
        number.type = "number";
        number.min = String(config.min);
        number.max = String(config.max);
        number.step = String(config.step);

        const update = (value) => setNumericSetting(config.key, value, config.min, config.max);
        range.addEventListener("input", () => update(range.value));
        number.addEventListener("change", () => update(number.value));

        row.append(label, range, number);
        panelControls.set(config.key, { range, number });
        return row;
    }

    function makeCheck(key, text) {
        const label = document.createElement("label");
        label.className = "dcw-check";

        const input = document.createElement("input");
        input.type = "checkbox";
        input.checked = Boolean(settings[key]);

        input.addEventListener("change", () => {
            settings[key] = input.checked;

            if (key === "collapseNoticesByDefault") {
                noticeCollapsed = input.checked;
            }

            saveSettings();
            applyAll(document);
        });

        label.append(input, document.createTextNode(text));
        panelControls.set(key, { checkbox: input });
        return label;
    }

    function syncPanelControls() {
        for (const [key, control] of panelControls) {
            if (control.range) control.range.value = String(settings[key]);
            if (control.number) control.number.value = String(settings[key]);
            if (control.checkbox) control.checkbox.checked = Boolean(settings[key]);
        }
        updatePanelSummary();
    }

    function updatePanelSummary() {
        if (!summaryEl) return;
        summaryEl.textContent =
            `현재 설정 · 본문 ${settings.mainWidth}px` +
            ` · 사이드바 ${settings.hideSidebar || isViewPage() ? "숨김" : "표시"}` +
            ` · 제목 ${settings.titleFontSize}px` +
            ` · 디시콘 ${settings.dcconColumns}열 / ${settings.dcconScalePercent}%`;
    }

    function mountPanel() {
        if (!document.body || document.getElementById("dcwide-toggle-v15")) return;

        const button = document.createElement("button");
        button.id = "dcwide-toggle-v15";
        button.type = "button";
        button.textContent = "WIDE 설정";

        panel = document.createElement("div");
        panel.id = "dcwide-panel-v15";
        panel.hidden = true;

        const brand = document.createElement("div");
        brand.className = "dcw-brand";

        const brandMain = document.createElement("div");
        brandMain.className = "dcw-brand-main";

        const heading = document.createElement("h2");
        heading.textContent = "DCInside Wide";

        const byline = document.createElement("div");
        byline.className = "dcw-byline";
        byline.textContent = "v1.0.2 · Yun. S. Lee";

        brandMain.append(heading, byline);

        const liveBadge = document.createElement("span");
        liveBadge.className = "dcw-live-badge";
        liveBadge.textContent = "자동 저장";

        brand.append(brandMain, liveBadge);

        const guide = document.createElement("div");
        guide.className = "dcw-guide";

        const guideTitle = document.createElement("strong");
        guideTitle.textContent = "화면에 맞게 간편하게 조절하세요";

        const guideText = document.createElement("p");
        guideText.textContent =
            "슬라이더와 체크 항목의 변경 사항은 즉시 적용되고 자동으로 저장됩니다. "
            + "설정이 어색해졌다면 아래의 기본 설정 버튼으로 언제든 되돌릴 수 있습니다.";

        const guideTips = document.createElement("div");
        guideTips.className = "dcw-guide-tips";

        for (const tipText of ["1440p 권장 폭 1600px", "사이드바 숨김 권장", "설정 즉시 반영"]) {
            const tip = document.createElement("span");
            tip.className = "dcw-guide-tip";
            tip.textContent = tipText;
            guideTips.append(tip);
        }

        guide.append(guideTitle, guideText, guideTips);
        panel.append(brand, guide);

        const layout = document.createElement("div");
        const layoutTitle = document.createElement("div");
        layoutTitle.className = "dcw-section-title";
        layoutTitle.textContent = "레이아웃";
        layout.append(layoutTitle);

        for (const config of RANGE_CONFIG.slice(0, 3)) layout.append(makeRange(config));
        layout.append(
            makeCheck("enabled", "와이드 레이아웃 켜기"),
            makeCheck("hideSidebar", "우측 사이드바 숨기기"),
            makeCheck("syncTop", "상단 영역 폭 맞추기"),
            makeCheck("fixGalleryInfo", "갤러리 정보 카드 정리"),
            makeCheck("showAllHeads", "말머리 전체 펼쳐보기"),
            makeCheck("autoSubjectWidth", "말머리 너비 자동 보정"),
            makeCheck("collapseNoticesByDefault", "고정 공지 기본 접기"),
            makeCheck("wrapLongTitles", "긴 제목 줄바꿈"),
            makeCheck("readabilityEnabled", "목록 가독성 설정 사용"),
            makeCheck("fixCommentBox", "댓글 입력창 폭 맞추기"),
            makeCheck("enhanceDccon", "디시콘 연속 스크롤 보기"),
        );
        panel.append(layout);

        const cols = document.createElement("div");
        cols.className = "dcw-section";
        const colsTitle = document.createElement("div");
        colsTitle.className = "dcw-section-title";
        colsTitle.textContent = "목록 열 너비";
        cols.append(colsTitle);

        for (const config of RANGE_CONFIG.slice(3)) cols.append(makeRange(config));

        summaryEl = document.createElement("div");
        summaryEl.className = "dcw-summary";
        cols.append(summaryEl);
        panel.append(cols);

        const typography = document.createElement("div");
        typography.className = "dcw-section";
        const typographyTitle = document.createElement("div");
        typographyTitle.className = "dcw-section-title";
        typographyTitle.textContent = "목록 가독성";
        typography.append(typographyTitle);
        for (const config of TEXT_RANGE_CONFIG) typography.append(makeRange(config));
        panel.append(typography);

        const dcconSection = document.createElement("div");
        dcconSection.className = "dcw-section";

        const dcconTitle = document.createElement("div");
        dcconTitle.className = "dcw-section-title";
        dcconTitle.textContent = "글 보기 · 디시콘";
        dcconSection.append(dcconTitle);

        for (const config of DCCON_RANGE_CONFIG) {
            dcconSection.append(makeRange(config));
        }

        const dcconInfo = document.createElement("div");
        dcconInfo.className = "dcw-summary";
        dcconInfo.textContent =
            "선택한 디시콘 팩을 최대 12열의 연속 그리드로 펼쳐 보여줍니다. 팩 전환은 디시인사이드의 기본 선택 방식을 그대로 사용합니다.";
        dcconSection.append(dcconInfo);
        panel.append(dcconSection);

        const buttons = document.createElement("div");
        buttons.className = "dcw-buttons";

        const reset = document.createElement("button");
        reset.type = "button";
        reset.textContent = "기본 설정으로";
        reset.addEventListener("click", () => {
            settings = { ...DEFAULTS };
            saveSettings();
            syncPanelControls();
            applyAll(document);
        });

        const close = document.createElement("button");
        close.type = "button";
        close.textContent = "완료";
        close.addEventListener("click", () => { panel.hidden = true; });

        buttons.append(reset, close);
        panel.append(buttons);

        button.addEventListener("click", () => { panel.hidden = !panel.hidden; });
        document.body.append(panel, button);
        syncPanelControls();
    }

    /******************************************************************
     * 동적 갱신 대응
     ******************************************************************/

    function observeDom() {
        if (!document.body || window.__dcwideV21Observer) return;

        const observer = new MutationObserver((records) => {
            let relevant = false;

            for (const record of records) {
                for (const node of record.addedNodes) {
                    if (!(node instanceof Element)) continue;
                    const structuralSelector = "table.gall_list, tr.ub-content, .list_array_option, .issue_contentbox, .minor_intro_box, .mini_intro_box, .person_intro_box, .page_head, .view_comment, .cmt_wrap, .cmt_write_box, #div_con, .dccon_list_box, ul.dccon_list";
                    const nodeText = cleanText(node.textContent);
                    if (
                        node.matches?.(structuralSelector)
                        || node.querySelector?.(structuralSelector)
                        || (nodeText.length < 2400 && (
                            (nodeText.includes("개설일") && nodeText.includes("카테고리"))
                            || nodeText.includes("갤러리 정보")
                        ))
                    ) {
                        relevant = true;
                        break;
                    }
                }
                if (relevant) break;
            }

            if (relevant) queueApply(document);
        });

        observer.observe(document.body, { childList: true, subtree: true });
        window.__dcwideV21Observer = observer;
    }

    function start() {
        injectStyle();
        applyVariables();

        if (!document.body) {
            requestAnimationFrame(start);
            return;
        }

        mountPanel();
        applyAll(document);
        observeDom();

        // 정식 갤러리의 정보 레이어가 약간 늦게 DOM에 붙는 경우까지 대응.
        for (const delay of [180, 650, 1500, 3000]) {
            window.setTimeout(() => queueApply(document), delay);
        }
    }

    injectStyle();
    applyVariables();

    if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", start, { once: true });
    } else {
        start();
    }
})();