디시인사이드 화면을 넓고 일관된 레이아웃으로 정리하고 목록·댓글·디시콘 가독성을 개선합니다.
Versión del día
// ==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("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
/******************************************************************
* 글 보기 / 댓글 보정
******************************************************************/
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();
}
})();