@charset "UTF-8";
/* ==========================================================================
   Devin.KR Design System
   - AdminLTE / Bootstrap / jQuery 의존 없음. 이 파일 하나로 동작한다.
   - 다크모드: prefers-color-scheme 자동 + <html data-theme="dark|light"> 수동 우선
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. 토큰
   -------------------------------------------------------------------------- */
:root {
	/*
	   색 — 무채색 우선, 색은 아껴서

	   이전에는 accent 가 #2563eb(부트스트랩/테일윈드 기본 파랑)였고 버튼·페이지네이션이
	   전부 그 색이었다. 어느 사이트에서나 본 색이라 "기본값 그대로"로 읽힌다.
	   기준을 바꿨다 — **UI 의 강조는 검정이 하고, 파랑은 링크와 포커스에만 쓴다.**
	   Linear·Vercel·Stripe 계열이 이 방식이고, 색을 아낄수록 위계가 선명해진다.
	*/
	--bg:            #ffffff;
	--bg-subtle:     #fafafa;
	--surface:       #ffffff;
	--surface-alt:   #f4f4f5;
	--border:        #e7e7ea;   /* 헤어라인. 이전 #e2e5ea 보다 한 단계 옅다 */
	--border-strong: #d0d0d5;
	--text:          #131417;   /* 순검정을 피한 잉크색 */
	--text-muted:    #61636b;
	--text-faint:    #8e9098;

	/* UI 강조 = 잉크. 버튼·활성 상태가 이 색이다 */
	--ink:           #131417;
	--ink-hover:     #000000;
	--ink-fg:        #ffffff;

	/* 색 강조 = 링크·포커스 전용. 채도를 낮춰 기본 파랑 느낌을 지웠다 */
	--accent:        #2a4bd7;
	--accent-hover:  #1e3ab0;
	--accent-fg:     #ffffff;
	--accent-soft:   #eef1fd;

	--success:       #14713a;
	--success-soft:  #e8f4ec;
	--warn:          #8f5000;
	--warn-soft:     #fbf1e2;
	--danger:        #b02121;
	--danger-soft:   #fceded;

	--code-bg:       #f7f7f8;
	--code-text:     #131417;
	--mark-bg:       #fff2b0;

	/* 간격 (4px 배수) */
	--sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
	--sp-5: 24px; --sp-6: 32px; --sp-7: 40px; --sp-8: 48px;
	--sp-9: 64px; --sp-10: 88px;

	/*
	   타이포 — 크기 대비를 키웠다

	   이전 스케일은 12/13/15/18/22/28 로 위아래 차이가 작아서 제목과 본문이
	   비슷해 보였다. 그래서 굵기로만 위계를 주다 보니 "전부 굵은" 화면이 됐다.
	   이제 크기가 위계를 지고 굵기는 덜 쓴다.
	*/
	--fs-xs:   11px;
	--fs-sm:   13px;
	--fs-base: 15px;
	--fs-md:   16px;
	--fs-lg:   19px;
	--fs-xl:   24px;
	--fs-2xl:  32px;
	--fs-3xl:  42px;

	--lh-tight: 1.25;
	--lh-snug:  1.45;
	--lh-base:  1.75;   /* 한글 본문은 1.7 보다 조금 더 열어야 편하다 */

	/* 제목은 자간을 좁혀야 덩어리로 읽힌다. 한글도 마찬가지다 */
	--tr-tight:  -.022em;
	--tr-tighter: -.032em;
	/* 라벨·메타는 반대로 벌린다 */
	--tr-wide:   .06em;

	--font-sans: "Pretendard", "Pretendard Variable", -apple-system, BlinkMacSystemFont,
	             "Apple SD Gothic Neo", "Malgun Gothic", "맑은 고딕", system-ui, sans-serif;
	--font-mono: ui-monospace, SFMono-Regular, "SF Mono", "JetBrains Mono",
	             "D2Coding", Consolas, "Liberation Mono", monospace;

	/* 반경 — 크면 장난감처럼 보인다. 작게 */
	--r-sm: 3px; --r-md: 6px; --r-lg: 10px; --r-full: 999px;

	/* 그림자 — 거의 쓰지 않는다. 면 구분은 헤어라인이 한다 */
	--sh-1: 0 1px 2px rgba(19,20,23,.05);
	--sh-2: 0 8px 24px rgba(19,20,23,.08);

	/*
	   레이아웃

	   폭을 한 단계씩 넓혔다 (2026-09-05).
	   1440~1600 모니터에서 목록 폭이 1000px 이면 좌우가 통째로 비어
	   "글이 몇 개 없는 사이트" 로 보였다. okky 는 같은 화면에서 목록에
	   1200 안팎을 쓰고 우측 레일까지 채운다. 그쪽이 한 화면에 들어오는
	   글 수가 두 배 가까이 많다 — 그게 "해상도가 높아 보이는" 실체다.

	   본문(--w-content)만 그대로 둔다. 읽는 글의 한 줄 길이는 넓힌다고
	   좋아지지 않는다(오히려 눈이 줄을 놓친다).
	*/
	--w-content: 720px;   /* 글 본문 — 한 줄 길이를 읽기 좋게. 넓히지 말 것 */
	--w-list:   1180px;   /* 목록 — 본문보다 넓어야 허전하지 않다 */
	--w-wide:   1400px;
	--h-header:   60px;
	--w-subnav:  180px;
	--w-aside:   300px;   /* 우측 레일. 300x250 광고 단위 폭에 맞춘 값이다 */

	/* 좌우 여백 광고 레일 (2026-09-06). 계산 근거는 아래 .ad-rail 주석 참조 */
	--w-rail:     160px;  /* 레일 광고 폭. 160x600 = 여백 세로 배너의 사실상 표준 */
	--rail-gap:    24px;  /* 본문 컨테이너 가장자리 ↔ 레일 */
	--rail-edge:   24px;  /* 레일 ↔ 화면 가장자리. 스크롤바 폭(약 15px)도 여기서 흡수한다 */
	/* 레일이 "가리면 안 되는" 가운데 본문 폭.
	   기본값은 가장 넓은 컨테이너(--w-wide). 좁은 화면용 페이지는 아래에서 낮춘다.
	   낮출수록 레일이 더 일찍(좁은 화면부터) 나타난다. */
	--w-page: var(--w-wide);

	--dur: 140ms;
}


@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) {
		--bg:            #0e0f12;
		--bg-subtle:     #08090b;
		--surface:       #16181d;
		--surface-alt:   #1e2127;
		--border:        #26292f;
		--border-strong: #383c44;
		--text:          #e9eaec;
		--text-muted:    #9a9da5;
		--text-faint:    #71757d;

		/* 다크에서는 잉크 강조를 뒤집는다 — 흰 버튼이 검은 배경에서 가장 또렷하다 */
		--ink:           #f2f3f5;
		--ink-hover:     #ffffff;
		--ink-fg:        #0e0f12;

		--accent:        #8aa4ff;
		--accent-hover:  #a5b9ff;
		--accent-fg:     #0e0f12;
		--accent-soft:   #1a2033;

		--success:       #5cc47f;
		--success-soft:  #12291b;
		--warn:          #e2a548;
		--warn-soft:     #2b2113;
		--danger:        #f07a72;
		--danger-soft:   #2d1a19;

		--code-bg:       #0b0c0f;
		--code-text:     #e9eaec;
		--mark-bg:       #4a4021;

		--sh-1: 0 1px 2px rgba(0,0,0,.5);
		--sh-2: 0 8px 28px rgba(0,0,0,.6);
	}
}

:root[data-theme="dark"] {
	--bg:            #0e0f12;
	--bg-subtle:     #08090b;
	--surface:       #16181d;
	--surface-alt:   #1e2127;
	--border:        #26292f;
	--border-strong: #383c44;
	--text:          #e9eaec;
	--text-muted:    #9a9da5;
	--text-faint:    #71757d;

	/* 다크에서는 잉크 강조를 뒤집는다 — 흰 버튼이 검은 배경에서 가장 또렷하다 */
	--ink:           #f2f3f5;
	--ink-hover:     #ffffff;
	--ink-fg:        #0e0f12;

	--accent:        #8aa4ff;
	--accent-hover:  #a5b9ff;
	--accent-fg:     #0e0f12;
	--accent-soft:   #1a2033;

	--success:       #5cc47f;
	--success-soft:  #12291b;
	--warn:          #e2a548;
	--warn-soft:     #2b2113;
	--danger:        #f07a72;
	--danger-soft:   #2d1a19;

	--code-bg:       #0b0c0f;
	--code-text:     #e9eaec;
	--mark-bg:       #4a4021;

	--sh-1: 0 1px 2px rgba(0,0,0,.5);
	--sh-2: 0 8px 28px rgba(0,0,0,.6);
}


/* --------------------------------------------------------------------------
   2. 리셋 · 기본
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
	margin: 0;
	background: var(--bg);
	color: var(--text);
	font-family: var(--font-sans);
	font-size: var(--fs-base);
	line-height: var(--lh-base);
	word-break: keep-all;
	overflow-wrap: anywhere;
	-webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5, h6 { margin: 0 0 var(--sp-3); line-height: var(--lh-tight); font-weight: 700; }
h1 { font-size: var(--fs-2xl); letter-spacing: -.02em; }
h2 { font-size: var(--fs-xl); letter-spacing: -.01em; }
h3 { font-size: var(--fs-lg); }
h4, h5, h6 { font-size: var(--fs-base); }

p  { margin: 0 0 var(--sp-4); }
ul, ol { margin: 0 0 var(--sp-4); padding-left: var(--sp-5); }
li { margin-bottom: var(--sp-1); }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

hr { border: 0; border-top: 1px solid var(--border); margin: var(--sp-5) 0; }

img, svg, video { max-width: 100%; height: auto; }

small { font-size: var(--fs-sm); }
mark  { background: var(--mark-bg); color: inherit; padding: 0 2px; border-radius: 2px; }

code, kbd, samp, pre { font-family: var(--font-mono); font-size: .92em; }
code {
	background: var(--code-bg); color: var(--code-text);
	padding: .12em .38em; border-radius: var(--r-sm);
	border: 1px solid var(--border);
}
pre {
	background: var(--code-bg); color: var(--code-text);
	border: 1px solid var(--border); border-radius: var(--r-md);
	padding: var(--sp-4); overflow-x: auto; line-height: 1.6; margin: 0 0 var(--sp-4);
}
pre code { background: none; border: 0; padding: 0; }

kbd {
	background: var(--surface-alt); border: 1px solid var(--border-strong);
	border-bottom-width: 2px; border-radius: var(--r-sm);
	padding: .1em .4em; font-size: var(--fs-xs); color: var(--text);
}

blockquote {
	margin: 0 0 var(--sp-4); padding: var(--sp-2) var(--sp-4);
	border-left: 3px solid var(--border-strong); color: var(--text-muted);
}

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-sm); }

::selection { background: var(--accent-soft); color: var(--text); }

@media (prefers-reduced-motion: reduce) {
	*, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; scroll-behavior: auto !important; }
}

/* 스크린리더 전용 / 건너뛰기 링크 */
.sr-only {
	position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
	overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.skip-link {
	position: absolute; left: var(--sp-3); top: -100px; z-index: 200;
	background: var(--accent); color: var(--accent-fg);
	padding: var(--sp-2) var(--sp-4); border-radius: var(--r-md);
}
.skip-link:focus { top: var(--sp-3); text-decoration: none; }

/* --------------------------------------------------------------------------
   3. 레이아웃
   -------------------------------------------------------------------------- */
.container { width: 100%; max-width: var(--w-wide); margin: 0 auto; padding: 0 var(--sp-5); }
.container--narrow { max-width: var(--w-content); }
/* 목록용. 글 본문보다 넓게 잡는다 — 좁은 목록이 넓은 화면에서 가장 허전해 보인다 */
.container--list { max-width: var(--w-list); }
.page { min-height: 100vh; display: flex; flex-direction: column; }
.page__main { flex: 1 0 auto; padding: var(--sp-5) 0 var(--sp-8); }

/* doc.html: 좌 서브내비 / 중 콘텐츠 / 우 사이드 */
.doc-grid {
	display: grid;
	grid-template-columns: var(--w-subnav) minmax(0, 1fr) var(--w-aside);
	gap: var(--sp-6);
	align-items: start;
}
.doc-grid > .doc-main { min-width: 0; }
/* 목록용 2단 — 우측 열 없이 서브내비 + 본문 */
.doc-grid--list { grid-template-columns: var(--w-subnav) minmax(0, 1fr); gap: var(--sp-6); }
@media (max-width: 760px) { .doc-grid--list { grid-template-columns: minmax(0, 1fr); } }

/*
   서브내비가 없는 화면은 본문이 전체 폭을 쓴다. (2026-09-06)

   공지사항 · 방명록 · 연애&가족 · 일&직장 네 화면에는 서브내비가 렌더되지 않는다
   (menu.json 에 하위 항목이 없다). 그런데 위 규칙이 첫 칸을 항상 --w-subnav 로 잡으므로
   subnav 요소가 없으면 **doc-main 이 그 좁은 칸에 들어가** 제목이 두 줄로 접히고
   넓은 칸은 통째로 비어 버렸다. 화면이 고장난 것처럼 보이던 원인이다.

   :first-child 로 "앞에 서브내비가 없는 경우" 를 정확히 집어 전체 폭을 주도록 한다.
   서브내비가 있는 화면은 doc-main 이 두 번째 자식이라 이 규칙에 걸리지 않는다.
*/
.doc-grid--list > .doc-main:first-child { grid-column: 1 / -1; }

/*
   우측 레일이 있는 목록 (2026-09-05)

   레일에는 "많이 본 글" 같은 실제 내용이 먼저 오고 광고는 그 아래에 붙는다.
   레일을 광고 전용으로 만들면 광고 단위 ID 가 비었을 때(현재 상태) 열이
   통째로 비어 오히려 화면이 더 허전해진다.

   1200px 미만에서는 레일을 접는다. 접히는 시점이 --w-list(1180) + 여백 근처라
   레일이 살아 있는 동안에는 본문 폭이 700 아래로 내려가지 않는다.
*/
.doc-grid--rail { grid-template-columns: var(--w-subnav) minmax(0, 1fr) var(--w-aside); }
@media (max-width: 1199px) {
	.doc-grid--rail { grid-template-columns: var(--w-subnav) minmax(0, 1fr); }
	.doc-grid--rail > .doc-aside { display: none; }
}
@media (max-width: 760px) { .doc-grid--rail { grid-template-columns: minmax(0, 1fr); } }

.doc-aside { position: sticky; top: calc(var(--h-header) + var(--sp-4)); }

/*
   홈 2단 — 본문 + 우측 레일 (2026-09-05)

   홈에는 서브내비가 없어 doc-grid 를 그대로 쓸 수 없다.
   레일에 "많이 본 글" 을 옮기면 세로로 쌓이던 섹션 하나가 사라져
   첫 화면에 최신 글이 그만큼 더 들어온다. 광고 슬롯도 이 레일 안에만 둔다.
*/
.home-grid {
	display: grid;
	grid-template-columns: minmax(0, 1fr) var(--w-aside);
	gap: var(--sp-6);
	align-items: start;
}
.home-grid > .doc-main { min-width: 0; }
@media (max-width: 1023px) {
	/* 레일을 접을 때 숨기지 않고 본문 아래로 흘린다.
	   "많이 본 글" 은 모바일에서도 볼 값어치가 있는 내용이다 */
	.home-grid { grid-template-columns: minmax(0, 1fr); }
	.home-grid > .doc-aside { position: static; }
}

@media (max-width: 1100px) {
	.doc-grid { grid-template-columns: var(--w-subnav) minmax(0, 1fr); }
	.doc-aside { display: none; }
}
@media (max-width: 760px) {
	.doc-grid { grid-template-columns: minmax(0, 1fr); gap: var(--sp-4); }
	.doc-subnav { display: none; }
	.doc-subnav.is-open { display: block; }
}

/* Rails occupy the same grid row as content; normal-flow height bounds sticky at
   main's bottom. No fixed positions, clipping ancestors or scroll-time hiding.
   Width thresholds = page + 2 * (160px rail + 24px gap + 24px edge).
   Keep the widest direct content container if a page has mixed widths. */
.page:has(.page__content > .container--narrow) { --w-page: var(--w-content); --rail-breakpoint: 1136; }
.page:has(.page__content > .container--list) { --w-page: var(--w-list); --rail-breakpoint: 1596; }
.page:has(.page__content > .container:not(.container--narrow):not(.container--list)) { --w-page: var(--w-wide); --rail-breakpoint: 1816; }
.page__main {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, min(100%, var(--w-page))) minmax(0, 1fr);
}
.page__content { grid-area: 1 / 1 / 2 / -1; min-width: 0; }
.ad-rail { display: none; width: var(--w-rail); z-index: 5; }
.ad-rail--left { grid-area: 1 / 1; justify-self: end; margin-right: var(--rail-gap); }
.ad-rail--right { grid-area: 1 / 3; justify-self: start; margin-left: var(--rail-gap); }
.ad-rail .ad-slot { margin: 0; }
/* 좌·우 레일 모두 스크롤을 따라온다 (2026-09-12 운영자 요청).

   footer 를 가리지 않는 장치는 좌표 계산이 아니라 위 구조다 — .ad-rail 은 main 의
   그리드 칸이고 sticky 는 자기 칸 바닥(= main 바닥)에서 멈춘다. 그래서 스크롤을 끝까지
   내려도 광고가 footer 위로 넘어오지 않는다. position:fixed 로 바꾸면 이 보장이 사라진다.

   우측은 160x300 두 장(라벨 포함 320 * 2 + 간격 32 = 672)이 한 덩어리로 붙어 다닌다.
   헤더(60) + 위 여백(24) + 672 = 756 이라 그만큼 안 되는 화면도 있는데, 거기서는
   **고정을 포기하지 않고 장수를 줄인다** — ad.js 의 fitRightRail 이 요청 전에
   들어오지 않는 아래 칸을 빼므로(요청 자체를 안 한다) 어떤 높이에서도 남은 광고가
   화면 안에 온전히 들어온 채 고정된다. 그래서 여기서는 높이 분기를 두지 않는다.
   아래 top 값과 칸 사이 간격은 ad.js 가 계산 근거로 그대로 읽어 간다 — 숫자를 바꾸면
   그쪽 판정도 따라 바뀐다(박아 둔 값이 없다). */
.ad-rail__sticky { position: sticky; top: calc(var(--h-header) + var(--sp-5)); }
.ad-rail--right .ad-rail__sticky > .ad-slot + .ad-slot { margin-top: var(--sp-6); }
/* 과도기 규칙 (2026-09-12) — static/fro/** 는 WAR 와 따로 배포된다. 이 CSS 가 먼저
   올라가고 새 ad.html 이 아직 안 올라간 동안에는 우측 위 칸이 .ad-rail--right 의
   직계 자식이라 위 규칙이 안 먹고 두 광고가 맞붙는다. 그 창을 메우는 한 줄이다.
   새 마크업에서는 슬롯이 직계 자식이 아니므로 아무 영향이 없다.
   WAR 배포가 끝나면 지워도 된다. */
.ad-rail--right > .ad-slot { margin-bottom: var(--sp-6); }
/* JS sets the density choice before any request, preserving it across resizes.
   A served banner is never hidden to substitute a new rail. */
.ad-slot[data-ad-suppressed="1"] { display: none !important; }
@media (min-width: 1280px) and (min-height: 720px) {
	.page[data-ad-rails="1"] .ad-rail { display: block; }
}
.ad-rail:not(:has(.ad-slot:not(.ad-slot--empty))) { display: none !important; }

/* tool.html: 입력/출력 2분할 */
.tool-io {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	gap: var(--sp-4);
}
@media (max-width: 860px) { .tool-io { grid-template-columns: minmax(0, 1fr); } }

.stack   > * + * { margin-top: var(--sp-4); }
.stack-sm > * + * { margin-top: var(--sp-2); }

.row { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center; }
.row--between { justify-content: space-between; }
.row--end { justify-content: flex-end; }
.spacer { flex: 1 1 auto; }

/* --------------------------------------------------------------------------
   4. 헤더 · 내비
   -------------------------------------------------------------------------- */
.header {
	position: sticky; top: 0; z-index: 100;
	height: var(--h-header);
	background: color-mix(in srgb, var(--bg) 88%, transparent);
	backdrop-filter: saturate(180%) blur(8px);
	border-bottom: 1px solid var(--border);
}
.header__inner {
	height: 100%; display: flex; align-items: center; gap: var(--sp-4);
	max-width: var(--w-wide); margin: 0 auto; padding: 0 var(--sp-4);
}
.brand {
	display: inline-flex; align-items: center; gap: var(--sp-2);
	font-weight: 800; font-size: var(--fs-lg); color: var(--text);
	letter-spacing: -.02em; flex: 0 0 auto;
}
.brand:hover { text-decoration: none; }
.brand__logo { width: 26px; height: 26px; border-radius: var(--r-sm); }

.gnb { display: flex; gap: var(--sp-1); flex: 1 1 auto; }
.gnb__link {
	position: relative;
	display: inline-flex; align-items: center;
	height: var(--h-header); padding: 0 var(--sp-3);
	color: var(--text-muted); font-weight: 500; font-size: var(--fs-sm);
	letter-spacing: -.01em; white-space: nowrap;
}
.gnb__link:hover { color: var(--text); text-decoration: none; }
/* 활성 표시를 알약 배경에서 밑줄로 바꿨다.
   배경 알약은 메뉴가 버튼처럼 보여 헤더가 소란스러워진다. */
.gnb__link.is-active { color: var(--text); font-weight: 600; }
.gnb__link.is-active::after {
	content: ""; position: absolute; left: var(--sp-3); right: var(--sp-3); bottom: -1px;
	height: 2px; background: var(--ink);
}

.header__actions { display: flex; align-items: center; gap: var(--sp-2); flex: 0 0 auto; }

/* ⌘K 검색 트리거 */
.search-trigger {
	display: inline-flex; align-items: center; gap: var(--sp-2);
	min-width: 180px; height: 32px; padding: 0 var(--sp-3);
	background: transparent; border: 1px solid var(--border);
	border-radius: var(--r-sm); color: var(--text-faint);
	font-size: var(--fs-sm); cursor: pointer; font-family: inherit;
	transition: border-color var(--dur);
}
.search-trigger:hover { border-color: var(--border-strong); }
.search-trigger kbd { margin-left: auto; }

@media (max-width: 1050px) {
	.gnb { display: none; }
	.gnb.is-open {
		display: flex; flex-direction: column; position: absolute;
		top: var(--h-header); left: 0; right: 0; padding: var(--sp-3);
		background: var(--bg); border-bottom: 1px solid var(--border); box-shadow: var(--sh-2);
	}
	.search-trigger { min-width: 0; }
	.search-trigger span { display: none; }
	.search-trigger kbd { display: none; }
}
/* 햄버거는 좁은 화면에서만 나온다.
   .nav-toggle 단독 선택자는 뒤에 오는 .btn 의 display:inline-flex 에 밀려(같은 특정도, 나중 규칙 승)
   데스크톱에서도 버튼이 보였다. .header 를 앞에 붙여 특정도를 올린다. */
.header .nav-toggle { display: none; }
@media (max-width: 1050px) { .header .nav-toggle { display: inline-flex; } }

@media (max-width: 1050px) {
    .header__inner { gap: 8px; }
    .header__actions { margin-left: auto; }
    /* 메뉴가 열린 동안: 뒤 화면 스크롤 잠금 + 헤더 아래 어두운 막(z 99, 헤더 100 바로 아래).
       막을 누르면 devin.js 의 바깥 pointerdown 으로 닫힌다. */
    html.nav-open { overflow: hidden; }
    html.nav-open body::after {
        content: ""; position: fixed; left: 0; right: 0; bottom: 0; top: var(--h-header);
        z-index: 99; background: rgba(10, 13, 17, .35);
    }
    .gnb.is-open { max-height: calc(100vh - var(--h-header) - 8px); max-height: calc(100dvh - var(--h-header) - 8px); overflow-y: auto; overscroll-behavior: contain; }
    .gnb__link { min-height: 44px; height: auto; padding-top: 10px; padding-bottom: 10px; }
    .header .btn--icon, .header__profile { width: 40px; height: 40px; min-height: 40px; padding: 0; }
    .header .search-trigger { width: 40px; height: 40px; padding: 0; justify-content: center; }
}
@media (max-width: 400px) {
    .header__inner { gap: 4px; padding: 0 8px; }
    .header__actions { gap: 4px; }
    .header .brand { font-size: 17px; }
    .header .brand__logo { display: none; }
    .header__actions > .btn--sm { padding-left: 8px; padding-right: 8px; }
}

/* Shared disclosure state is controlled by JS so aria-expanded and visibility agree. */
.nav-section__heading { display: flex; align-items: center; }
.nav-section .gnb__link { padding-right: 3px; }
.header .nav-disclosure { display: none; }
.nav-enhanced .header .nav-disclosure { display: inline-flex; align-items: center; justify-content: center; }
.nav-section .nav-disclosure { border: 0; background: transparent; color: var(--text-muted); padding: 0; min-width: 24px; min-height: 44px; cursor: pointer; }
.header [hidden] { display: none !important; }
.mega-menu { position: absolute; top: 100%; left: max(16px, calc((100% - var(--w-wide)) / 2)); right: max(16px, calc((100% - var(--w-wide)) / 2)); padding: var(--sp-5); background: var(--bg); border: 1px solid var(--border); border-radius: 0 0 var(--r-lg) var(--r-lg); box-shadow: var(--sh-2); max-height: calc(100dvh - var(--h-header) - 16px); overflow: auto; }
.mega-menu__tagline { margin: 0 0 var(--sp-4); color: var(--text-muted); }
.mega-menu__columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(145px, 1fr)); gap: var(--sp-5); }
.mega-menu__group { min-width: 0; overflow-wrap: anywhere; }
.mega-menu__group h2 { font-size: var(--fs-sm); margin: 0 0 var(--sp-2); }
.mega-menu__group ul { list-style: none; padding: 0; margin: 0; }
.mega-menu a, .personal-menu a { display: block; padding: 8px 0; color: var(--text); font-size: var(--fs-sm); }
.mega-menu__more { font-weight: 600; }
.personal-nav { position: relative; }
.header .personal-nav__toggle { gap: 6px; white-space: nowrap; }
.personal-menu { position: absolute; top: 100%; right: 0; width: 220px; max-width: calc(100vw - 24px); background: var(--bg); border: 1px solid var(--border); border-radius: var(--r-md); padding: var(--sp-4); box-shadow: var(--sh-2); }
.personal-menu form { margin-top: var(--sp-2); }
/* 개인 메뉴: 드롭다운 없이 바로 보이는 링크(2026-09-29). 로그인 버튼은 항상 보인다. */
.personal-links { display: flex; align-items: center; gap: 4px; white-space: nowrap; }
.personal-links .btn { gap: 6px; }
.personal-links__logout { margin: 0; }
@media (max-width: 1050px) {
    .personal-links__extra, .personal-links__logout { display: none; }
}
@media (max-width: 560px) { .personal-links .btn span { display: none; } }
@media (max-width: 1050px) {
    .nav-section__heading { justify-content: space-between; }
    .nav-section .nav-disclosure { min-width: 44px; }
    .mega-menu { position: static; max-height: none; padding: var(--sp-3); box-shadow: none; border-radius: var(--r-sm); }
    .mega-menu__columns { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .header .personal-nav__toggle { width: 40px; padding: 0; }
    .personal-nav__toggle span { display: none; }
    html:not(.nav-enhanced) .header { height: auto; position: relative; }
    html:not(.nav-enhanced) .header__inner { flex-wrap: wrap; }
    html:not(.nav-enhanced) .gnb { display: flex; flex-wrap: wrap; order: 3; flex-basis: 100%; }
    html:not(.nav-enhanced) .header .nav-toggle { display: none; }
}

@media (min-width: 1051px) and (max-width: 1200px) {
    .search-trigger { min-width: 34px; }
    .search-trigger span, .search-trigger kbd { display: none; }
}

/* 섹션 서브내비 */
.subnav { position: sticky; top: calc(var(--h-header) + var(--sp-4)); font-size: var(--fs-sm); }
/* 한 단(760px 이하)으로 쌓이면 서브내비는 본문 위에 놓인다. 데스크톱용 sticky 가 그대로 남아
   배경 없는 서브내비가 스크롤되는 글 목록 위에 겹쳐 보였다(2026-09-24, 이야기>잡담).
   위 .subnav 규칙보다 뒤에 있어야 이긴다. .ad-slot--sidebar 는 뒤(6장)에 따로 둔다. */
@media (max-width: 760px) { .subnav, .doc-aside { position: static; } }
.subnav__group { margin-bottom: var(--sp-5); }
.subnav__title {
	font-family: var(--font-mono);
	font-size: var(--fs-xs); font-weight: 500; text-transform: uppercase;
	letter-spacing: var(--tr-wide); color: var(--text-faint);
	margin: 0 0 var(--sp-3); padding: 0 var(--sp-2);
}
.subnav__link {
	display: flex; align-items: center; gap: var(--sp-2);
	padding: var(--sp-2) var(--sp-2); border-radius: var(--r-sm);
	color: var(--text-muted);
}
.subnav__link:hover { background: var(--surface-alt); color: var(--text); text-decoration: none; }
.subnav__link.is-active { color: var(--text); background: var(--surface-alt); font-weight: 600; box-shadow: inset 2px 0 0 var(--ink); }

/* 푸터 */
.footer {
	flex: 0 0 auto; margin-top: var(--sp-10);
	border-top: 1px solid var(--border);
	background: var(--bg);
	padding: var(--sp-8) 0 var(--sp-6);
	font-size: var(--fs-sm); color: var(--text-muted);
}
.footer a { color: var(--text-muted); text-decoration: none; }
.footer a:hover { color: var(--text); text-decoration: underline; text-underline-offset: 3px; }
/* JSON 기반 사이트 지도: 넓은 화면 6열, 좁은 화면 3·2·1열. */
.footer__grid {
	display: grid;
	grid-template-columns: repeat(6, minmax(0, 1fr));
	gap: var(--sp-8) var(--sp-6);
}
.footer__brand { font-weight: 700; font-size: var(--fs-md); color: var(--text); letter-spacing: -.02em; }
.footer__head {
	font-family: var(--font-mono); font-size: var(--fs-xs);
	letter-spacing: var(--tr-wide); text-transform: uppercase; color: var(--text-faint);
	margin-bottom: var(--sp-3);
}
.footer__links { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-2); }
@media (max-width: 700px) {
	.footer__grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

.footer__identity { margin-bottom: var(--sp-6); }
.footer__brand { display: inline-flex; align-items: center; }
.footer__column { min-width: 0; overflow-wrap: anywhere; }
.footer__legal { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
@media (min-width: 701px) and (max-width: 1050px) { .footer__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 420px) { .footer__grid { grid-template-columns: minmax(0, 1fr); } }

/* 버튼 */
.btn {
	display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
	height: 34px; padding: 0 var(--sp-4);
	border: 1px solid var(--border-strong); border-radius: var(--r-sm);
	background: var(--surface); color: var(--text);
	font: inherit; font-size: var(--fs-sm); font-weight: 500; line-height: 1;
	letter-spacing: -.01em;
	cursor: pointer; text-decoration: none; white-space: nowrap;
	transition: background var(--dur), border-color var(--dur), color var(--dur);
}
.btn:hover { background: var(--surface-alt); border-color: var(--text-faint); text-decoration: none; }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .45; cursor: default; }

/* 주 버튼은 잉크(검정)다. 파랑을 쓰면 어느 사이트에나 있는 화면이 된다 */
.btn--primary { background: var(--ink); border-color: var(--ink); color: var(--ink-fg); font-weight: 600; }
.btn--primary:hover { background: var(--ink-hover); border-color: var(--ink-hover); color: var(--ink-fg); }

.btn--ghost { background: transparent; border-color: transparent; color: var(--text-muted); }
.btn--ghost:hover { background: var(--surface-alt); border-color: transparent; color: var(--text); }
.btn--danger { border-color: var(--danger); color: var(--danger); background: transparent; }
.btn--danger:hover { background: var(--danger-soft); border-color: var(--danger); }
.btn--sm { height: 28px; padding: 0 var(--sp-3); font-size: var(--fs-xs); }
.btn--lg { height: 42px; padding: 0 var(--sp-5); font-size: var(--fs-md); }
.btn--icon { width: 34px; padding: 0; }
.btn--icon.btn--sm { width: 28px; }
.btn--block { width: 100%; }

.card {
	background: var(--surface); border: 1px solid var(--border);
	border-radius: var(--r-lg); box-shadow: var(--sh-1);
}
.card__header {
	padding: var(--sp-4); border-bottom: 1px solid var(--border);
	display: flex; align-items: center; gap: var(--sp-3);
}
.card__title { margin: 0; font-size: var(--fs-base); font-weight: 700; }
.card__body { padding: var(--sp-4); }
.card__footer { padding: var(--sp-3) var(--sp-4); border-top: 1px solid var(--border); background: var(--bg-subtle); border-radius: 0 0 var(--r-lg) var(--r-lg); }

/* 카드 그리드 (도구 허브) */
.grid { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
.grid--wide { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }

/* 도구 카드 */
.toolcard {
	display: block; padding: var(--sp-4);
	background: var(--surface); border: 1px solid var(--border);
	border-radius: var(--r-lg); color: var(--text);
	transition: border-color var(--dur), box-shadow var(--dur), transform var(--dur);
}
.toolcard:hover { border-color: var(--accent); box-shadow: var(--sh-2); text-decoration: none; transform: translateY(-1px); }
.toolcard__name { font-weight: 700; margin-bottom: var(--sp-1); display: flex; align-items: center; gap: var(--sp-2); }
.toolcard__desc { font-size: var(--fs-sm); color: var(--text-muted); margin: 0; line-height: 1.55; }

/* 표 */
.table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.table th, .table td { padding: var(--sp-3); text-align: left; border-bottom: 1px solid var(--border); }
.table th { font-weight: 700; color: var(--text-muted); font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .04em; }
.table tbody tr:hover { background: var(--bg-subtle); }
.table--fixed { table-layout: fixed; }
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* --------------------------------------------------------------------------
   게시글 목록 (2026-09-05 재작업 — okky 목록을 기준으로 밀도를 올렸다)

   무엇을 바꿨나
     · 제목 19px -> 16px, 행 여백 16px -> 12px, 한 줄 말줄임.
       이전에는 제목이 본문 h2 만큼 커서 10건이 화면을 꽉 채웠다.
       okky 는 같은 높이에 20건 넘게 들어간다.
     · 메타를 모노 대문자 자간에서 일반 서체로 바꿨다. 모노는 "코드" 신호라
       작성자·날짜에 쓰면 읽는 속도가 느려진다. 숫자만 tabular-nums 로 남긴다.
     · 조회수 같은 수치를 오른쪽 끝으로 몰았다(.postlist__stats).
       왼쪽은 "무엇/누가", 오른쪽은 "얼마나" — 눈이 두 번만 움직인다.
     · 행 전체가 hover 대상이다. 제목 글자만 노리지 않아도 된다.
   -------------------------------------------------------------------------- */
.postlist { list-style: none; margin: 0; padding: 0; }
.postlist__item {
	position: relative;
	/* 세로 8px. 한 줄 제목 + 메타 한 줄이면 행 높이가 56px 언저리가 된다.
	   (터치 목표 44px 은 넘으므로 모바일에서도 누르기 어렵지 않다) */
	padding: var(--sp-2);
	margin: 0 calc(var(--sp-2) * -1);
	border-radius: var(--r-md);
	border-bottom: 1px solid var(--border);
}
.postlist__item:last-child { border-bottom: 0; }
.postlist__item:hover { background: var(--bg-subtle); }
.postlist__title {
	display: block;
	font-size: var(--fs-md); font-weight: 600; line-height: 1.35;
	letter-spacing: var(--tr-tight);
	color: var(--text); text-decoration: none;
	/* 한 줄 말줄임. 목록에서 제목이 두 줄로 접히면 행 높이가 들쭉날쭉해진다 */
	overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
	/* 밑줄은 hover 에서만, 글자에서 살짝 떨어뜨린다 */
	text-underline-offset: 3px;
}
.postlist__title:hover { color: var(--accent); text-decoration: underline; }
/* 제목 뒤에 붙는 아이콘(외부 링크 표시)만 왼쪽 여백을 준다.
   :last-child 로 한정하지 않으면 error/common.html 처럼 아이콘이 앞에 오는
   목록에서 첫 글자가 4px 밀려 들여쓰기처럼 보인다. */
.postlist__title .icon:last-child { color: var(--text-faint); margin-left: 4px; }

.postlist__meta {
	display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3);
	margin-top: 2px; line-height: 1.5;
	font-size: var(--fs-xs);
	color: var(--text-faint);
	/* 숫자 폭을 고정해 조회수가 세로로 정렬되게 한다 */
	font-variant-numeric: tabular-nums;
}
.postlist__meta a { color: inherit; }
/* 수치는 오른쪽 끝으로. 첫 요소에 margin-left:auto 를 걸면 뒤 요소가 따라붙는다 */
.postlist__stats { margin-left: auto; display: inline-flex; align-items: center; gap: var(--sp-3); }
.postlist__stats > span { display: inline-flex; align-items: center; gap: 4px; }

/* 작성자 — 아바타는 이미지가 없으므로 닉네임 첫 글자를 원 안에 둔다 */
.postlist__author { display: inline-flex; align-items: center; gap: 6px; color: var(--text-muted); }
.postlist__avatar {
	width: 18px; height: 18px; border-radius: var(--r-full);
	background: var(--surface-alt); color: var(--text-muted);
	font-size: 10px; font-weight: 700; line-height: 18px; text-align: center;
	flex: 0 0 auto; text-transform: uppercase;
}

.postlist__excerpt {
	margin: var(--sp-2) 0 0;
	font-size: var(--fs-sm); line-height: var(--lh-snug); color: var(--text-muted);
	display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* 뉴스처럼 요약이 붙는 목록은 말줄임을 풀고 두 줄까지 허용한다 */
.postlist--rich .postlist__title { white-space: normal; overflow: visible; font-size: var(--fs-base); }
.postlist--rich .postlist__item { padding-top: var(--sp-3); padding-bottom: var(--sp-3); }

.field { display: block; margin-bottom: var(--sp-4); }
.label { display: block; font-size: var(--fs-sm); font-weight: 600; margin-bottom: var(--sp-2); }
.hint  { font-size: var(--fs-xs); color: var(--text-faint); margin-top: var(--sp-1); }
.error { font-size: var(--fs-xs); color: var(--danger); margin-top: var(--sp-1); }

.input, .textarea, .select {
	width: 100%; padding: var(--sp-2) var(--sp-3); min-height: 36px;
	background: var(--surface); color: var(--text);
	border: 1px solid var(--border-strong); border-radius: var(--r-md);
	font-family: inherit; font-size: var(--fs-sm); line-height: 1.5;
}
.input:focus, .textarea:focus, .select:focus {
	border-color: var(--accent); outline: 2px solid var(--accent-soft); outline-offset: 0;
}
.textarea { min-height: 180px; resize: vertical; font-family: var(--font-mono); line-height: 1.6; }
.textarea--code { min-height: 320px; white-space: pre; overflow-wrap: normal; overflow-x: auto; }
.select { appearance: none; padding-right: var(--sp-6); background-image: linear-gradient(45deg, transparent 50%, var(--text-muted) 50%), linear-gradient(135deg, var(--text-muted) 50%, transparent 50%); background-position: calc(100% - 16px) 15px, calc(100% - 11px) 15px; background-size: 5px 5px; background-repeat: no-repeat; }

.checkline { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-sm); cursor: pointer; }

/* 뱃지 */
.badge {
	display: inline-flex; align-items: center; gap: 4px;
	padding: 2px var(--sp-2); border-radius: var(--r-full);
	font-size: var(--fs-xs); font-weight: 600; line-height: 1.6;
	background: var(--surface-alt); color: var(--text-muted); border: 1px solid var(--border);
}
.badge--accent  { background: var(--accent-soft);  color: var(--accent);  border-color: transparent; }
.badge--success { background: var(--success-soft); color: var(--success); border-color: transparent; }
.badge--warn    { background: var(--warn-soft);    color: var(--warn);    border-color: transparent; }
.badge--danger  { background: var(--danger-soft);  color: var(--danger);  border-color: transparent; }

/* 태그 */
.tag {
	display: inline-block; padding: 1px var(--sp-2); border-radius: var(--r-sm);
	background: var(--surface-alt); color: var(--text-muted);
	font-size: var(--fs-xs); font-weight: 500;
}
.tag:hover { background: var(--accent-soft); color: var(--accent); text-decoration: none; }

/* 알림 박스 */
.notice {
	padding: var(--sp-3) var(--sp-4); border-radius: var(--r-md);
	border: 1px solid var(--border); background: var(--surface-alt);
	font-size: var(--fs-sm);
}
.notice--info    { background: var(--accent-soft);  border-color: transparent; }
.notice--success { background: var(--success-soft); border-color: transparent; }
.notice--warn    { background: var(--warn-soft);    border-color: transparent; }
.notice--danger  { background: var(--danger-soft);  border-color: transparent; }

/* 토스트 */
.toast-host {
	position: fixed; right: var(--sp-4); bottom: var(--sp-4); z-index: 300;
	display: flex; flex-direction: column; gap: var(--sp-2); pointer-events: none;
}
.toast {
	background: var(--text); color: var(--bg);
	padding: var(--sp-3) var(--sp-4); border-radius: var(--r-md);
	box-shadow: var(--sh-2); font-size: var(--fs-sm); font-weight: 600;
	animation: toast-in var(--dur) ease-out;
}
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* 페이지네이션 */
.pagination {
	flex-wrap: wrap;
	display: flex; align-items: center; justify-content: center; gap: var(--sp-1);
	margin: var(--sp-8) 0 var(--sp-6);
}
.pagination a, .pagination span {
	display: inline-flex; align-items: center; justify-content: center;
	min-width: 32px; height: 32px; padding: 0 var(--sp-2);
	border: 0; border-radius: var(--r-sm);
	font-size: var(--fs-sm); font-variant-numeric: tabular-nums;
	color: var(--text-muted); text-decoration: none;
	transition: background var(--dur), color var(--dur);
}
.pagination a:hover { background: var(--surface-alt); color: var(--text); text-decoration: none; }
/* 현재 페이지도 잉크. 파란 알약보다 조용하고 또렷하다 */
.pagination .is-current { background: var(--ink); color: var(--ink-fg); font-weight: 600; }
.pagination .is-disabled { opacity: .3; cursor: default; }

/* 제목 덩어리. 위 여백을 48px -> 24px 로 줄였다 —
   첫 화면 상단 절반이 제목과 여백만으로 차 있었다(2026-09-05) */
.pagehead {
	display: flex; align-items: flex-end; justify-content: space-between; gap: var(--sp-5);
	flex-wrap: wrap;
	margin: var(--sp-5) 0 var(--sp-4);
	padding-bottom: var(--sp-3);
	border-bottom: 1px solid var(--border);
}
.pagehead__title {
	margin: 0;
	font-size: var(--fs-xl); font-weight: 700; line-height: var(--lh-tight);
	letter-spacing: var(--tr-tighter);
}
.pagehead__desc { margin: var(--sp-2) 0 0; color: var(--text-muted); font-size: var(--fs-sm); }
/* 제목 위에 얹는 분류 라벨. 모노+자간으로 "손으로 짠 화면" 느낌을 준다 */
.pagehead__eyebrow {
	display: block; margin-bottom: var(--sp-2);
	font-family: var(--font-mono); font-size: var(--fs-xs);
	letter-spacing: var(--tr-wide); text-transform: uppercase;
	color: var(--text-faint);
}
.pagehead__actions { display: flex; align-items: center; gap: var(--sp-2); }

.toolhead {
	display: flex; align-items: flex-start; gap: var(--sp-4);
	padding-bottom: var(--sp-4); margin-bottom: var(--sp-5);
	border-bottom: 1px solid var(--border); flex-wrap: wrap;
}
.toolhead__meta { flex: 1 1 260px; min-width: 0; }
.toolhead__actions { display: flex; gap: var(--sp-2); flex: 0 0 auto; }

/* 로컬 처리 배지 — 도구 신뢰 표시 */
.localbadge {
	display: inline-flex; align-items: center; gap: 6px;
	padding: 2px var(--sp-2); border-radius: var(--r-full);
	background: var(--success-soft); color: var(--success);
	font-size: var(--fs-xs); font-weight: 600;
}

/* 빈 상태 */
.empty { text-align: center; padding: var(--sp-8) var(--sp-4); color: var(--text-faint); }

/* 아이콘 */
.icon { width: 1em; height: 1em; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; flex: 0 0 auto; vertical-align: -.125em; }
.icon--lg { width: 20px; height: 20px; }

/* 통계 타일 */
.stats { display: flex; gap: var(--sp-4); flex-wrap: wrap; }
.stat { flex: 1 1 100px; }
.stat__label { font-size: var(--fs-xs); color: var(--text-faint); text-transform: uppercase; letter-spacing: .05em; }
.stat__value { font-size: var(--fs-xl); font-weight: 700; font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------------------
   6. 광고 슬롯
   ad.js가 컨테이너 폭을 재고 광고 단위를 주입한다. 여기서는 자리만 예약한다.
   -------------------------------------------------------------------------- */
.ad-slot {
	display: block; margin: var(--sp-4) 0; text-align: center;
	/* CLS 방지: 최소 높이 예약. ad.js가 폭 판정 후 실제 높이로 조정.
	   서버가 th:style 로 슬롯별 min-height 를 직접 지정하므로 여기 값은 하한이다. */
	min-height: 90px;
}
/*
	"광고" 라벨은 fragment 가 <span class="ad-slot__label"> 로 심는다.
	여기서 ::before 로 한 번 더 넣으면 라벨이 두 줄로 겹쳐 나온다.
	(2026-09-05 발견 — 광고를 켠 적이 없어 지금까지 드러나지 않았다)
	라벨 스타일은 ad.js 가 주입하는 CSS 에 있다.
*/
.ad-slot__label {
	display: block; height: 16px; margin-bottom: 4px;
	font-size: 10px; line-height: 16px; letter-spacing: .1em;
	color: var(--text-faint); text-align: center;
	-webkit-user-select: none; user-select: none;
}
.ad-slot--empty { display: none !important; }
/* 도구 페이지: 입력/버튼과 충분히 떨어뜨려 오클릭 방지 */
.ad-slot--tool { margin-top: var(--sp-8); }
/* 우측 레일 — 300x250 기준. 예전에는 600(300x600) 을 예약했는데
   화면 오른쪽이 광고 한 장으로 꽉 찼다. 작은 단위로 내렸다 */
.ad-slot--sidebar { min-height: 270px; margin: 0; position: sticky; top: calc(var(--h-header) + var(--sp-4)); }
@media (max-width: 760px) { .ad-slot--sidebar { position: static; } }

/* --------------------------------------------------------------------------
   6-2. 명령 팔레트 (⌘K)
   -------------------------------------------------------------------------- */
.palette {
	position: fixed; inset: 0; z-index: 400;
	background: rgba(10, 13, 17, .45);
	backdrop-filter: blur(2px);
	display: flex; align-items: flex-start; justify-content: center;
	padding: 12vh var(--sp-4) var(--sp-4);
}
.palette[hidden] { display: none; }
.palette__box {
	width: 100%; max-width: 560px;
	background: var(--surface); border: 1px solid var(--border);
	border-radius: var(--r-lg); box-shadow: var(--sh-2);
	overflow: hidden;
	animation: palette-in 120ms ease-out;
}
@keyframes palette-in { from { opacity: 0; transform: translateY(-6px) scale(.98); } to { opacity: 1; transform: none; } }
.palette__input {
	width: 100%; padding: var(--sp-4);
	border: 0; border-bottom: 1px solid var(--border);
	background: transparent; color: var(--text);
	font-family: inherit; font-size: var(--fs-lg);
}
.palette__input:focus { outline: none; }
.palette__list { list-style: none; margin: 0; padding: var(--sp-2); max-height: 50vh; overflow-y: auto; }
.palette__item {
	display: flex; align-items: center; gap: var(--sp-3);
	padding: var(--sp-2) var(--sp-3); border-radius: var(--r-md);
	cursor: pointer; margin: 0;
}
.palette__item.is-active { background: var(--accent-soft); }
.palette__name { font-weight: 600; font-size: var(--fs-sm); }
.palette__group { margin-left: auto; font-size: var(--fs-xs); color: var(--text-faint); }
.palette__empty { padding: var(--sp-5); text-align: center; color: var(--text-faint); font-size: var(--fs-sm); }

/* --------------------------------------------------------------------------
   6-3. 히어로 / 섹션 헤더 (홈)
   -------------------------------------------------------------------------- */
.hero {
	padding: var(--sp-8) 0 var(--sp-7);
	border-bottom: 1px solid var(--border);
	background:
		radial-gradient(60rem 22rem at 15% -20%, var(--accent-soft), transparent 60%),
		var(--bg);
}
.hero__title {
	font-size: clamp(26px, 4.4vw, 40px);
	letter-spacing: -.03em; margin-bottom: var(--sp-3);
}
.hero__lead { font-size: var(--fs-lg); color: var(--text-muted); max-width: 46ch; margin-bottom: var(--sp-5); }
.hero__actions { display: flex; gap: var(--sp-3); flex-wrap: wrap; }

/* 섹션 간격 48px -> 32px, 제목 24px -> 19px.
   홈은 섹션이 4~5개라 간격이 그대로 화면 높이로 쌓인다 */
.section { margin-top: var(--sp-6); }
.section__head {
	display: flex; align-items: baseline; gap: var(--sp-3); margin-bottom: var(--sp-2);
	padding-bottom: var(--sp-2); border-bottom: 1px solid var(--border);
}
.section__title { margin: 0; font-size: var(--fs-lg); letter-spacing: -.01em; }
.section__more { margin-left: auto; font-size: var(--fs-sm); font-weight: 600; }

/* --------------------------------------------------------------------------
   7. 유틸리티 (최소한만)
   -------------------------------------------------------------------------- */
.muted   { color: var(--text-muted); }
.faint   { color: var(--text-faint); }
.small   { font-size: var(--fs-sm); }
.xsmall  { font-size: var(--fs-xs); }
.bold    { font-weight: 700; }
.mono    { font-family: var(--font-mono); }
.nowrap  { white-space: nowrap; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.text-center { text-align: center; }
.text-right  { text-align: right; }
.mt-0 { margin-top: 0; }    .mb-0 { margin-bottom: 0; }
.mt-4 { margin-top: var(--sp-4); }  .mb-4 { margin-bottom: var(--sp-4); }
.mt-6 { margin-top: var(--sp-6); }  .mb-6 { margin-bottom: var(--sp-6); }
.hide { display: none !important; }
@media (max-width: 760px) { .hide-sm { display: none !important; } }
@media (min-width: 761px) { .show-sm { display: none !important; } }

/* 본문 렌더링 영역 (마크다운 산출물) */
.prose { max-width: var(--w-content); }
.prose > :first-child { margin-top: 0; }
.prose h2 { margin-top: var(--sp-7); padding-top: var(--sp-2); }
.prose h3 { margin-top: var(--sp-6); }
.prose img { border-radius: var(--r-md); border: 1px solid var(--border); }
.prose table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); margin-bottom: var(--sp-4); }
.prose th, .prose td { padding: var(--sp-2) var(--sp-3); border: 1px solid var(--border); text-align: left; }
.prose th { background: var(--surface-alt); font-weight: 700; }

/* --------------------------------------------------------------------------
   글 본문 (.dv-content) — 사용자가 작성한 HTML 이 그대로 들어온다

   .prose 와 달리 여기 들어오는 마크업을 우리가 통제할 수 없다.
   기존 글에는 <table width="900">, 원본 크기 <img>, 인라인 style 이 섞여 있고
   그대로 두면 본문이 레이아웃 밖으로 밀고 나간다. 그래서 규칙이 두 겹이다.
     1) .prose 와 같은 읽기 스타일을 준다
     2) 어떤 마크업이 와도 가로 폭을 넘지 못하게 막는다
   -------------------------------------------------------------------------- */
.dv-content {
	/* .prose 는 max-width 로 줄 길이를 제한하지만, 본문에는 코드·표가 많아
	   폭을 좁히면 오히려 읽기 나빠진다. 대신 컨테이너 폭에 맞춘다. */
	max-width: 100%;
	line-height: var(--lh-base);
	/* 줄바꿈 없는 긴 URL·해시값이 폭을 밀어내는 것을 막는다 */
	overflow-wrap: anywhere;
}
.dv-content > :first-child { margin-top: 0; }
.dv-content h2 { margin-top: var(--sp-7); padding-top: var(--sp-2); }
.dv-content h3 { margin-top: var(--sp-6); }

/* 표 — 새 콘텐츠는 3열 정도지만, 옛 글에는 width 속성이 박힌 표가 있다.
   width 속성은 CSS 로 덮어써야 무력화된다. */
.dv-content table {
	width: 100% !important;
	max-width: 100%;
	border-collapse: collapse;
	font-size: var(--fs-sm);
	margin-bottom: var(--sp-4);
	display: block;        /* 열이 많아 넘칠 때 표 자체가 가로 스크롤된다 */
	overflow-x: auto;
}
.dv-content th, .dv-content td {
	padding: var(--sp-2) var(--sp-3);
	border: 1px solid var(--border);
	text-align: left;
	vertical-align: top;
}
.dv-content th { background: var(--surface-alt); font-weight: 700; }

/* 이미지·미디어 — 원본 크기로 들어와도 컨테이너를 넘지 않는다 */
.dv-content img,
.dv-content video,
.dv-content iframe,
.dv-content embed {
	max-width: 100%;
	height: auto;
}
.dv-content img {
	border-radius: var(--r-md);
	border: 1px solid var(--border);
}

/* 코드 — pre 는 전역에서 이미 overflow-x:auto 다. 옛 글이 pre 없이
   긴 줄을 넣은 경우를 대비해 인라인 code 도 넘치지 않게 둔다. */
.dv-content code { overflow-wrap: anywhere; }
.dv-content pre { max-width: 100%; }

/* 옛 글의 인라인 고정 폭 무력화. width 속성과 style="width:900px" 둘 다 잡는다. */
.dv-content [width],
.dv-content [style*="width"] { max-width: 100% !important; }

.dv-content blockquote {
	border-left: 3px solid var(--border-strong);
	margin: 0 0 var(--sp-4);
	padding: var(--sp-1) var(--sp-4);
	color: var(--text-muted);
}
.dv-content ul, .dv-content ol { padding-left: var(--sp-5); }
.dv-content li + li { margin-top: var(--sp-1); }
.dv-content a { overflow-wrap: anywhere; }

/* --------------------------------------------------------------------------
   레거시 붙여넣기 정리

   기존 글 상당수가 ChatGPT · StackOverflow 화면에서 그대로 복사돼 있다.
   본문 안에 인라인 style 이 수백 줄씩 들어 있고, 거기에 그 사이트의
   글자색·배경색·폰트가 박혀 있다. 실제로 확인된 것들:
     · style="width: 628px"                → 좁은 화면에서 레이아웃을 밀어낸다
     · style="color: rgb(12,13,14)"        → 다크모드에서 검은 글자가 검은 배경에 얹힌다
     · font-family: Söhne / var(--ff-mono) → 우리 쪽에 없는 폰트·변수라 폴백이 제각각이다
     · background-color: var(--highlight-bg) → 정의되지 않은 변수라 투명하게 떨어진다

   인라인 style 은 !important 로만 덮을 수 있다. 코드 하이라이팅 색이 같이
   죽지만, 다크모드에서 글이 안 보이는 것보다는 낫다.
   근본 해결은 저장 시점에 본문을 정제하는 것이고, 이건 그때까지의 방어막이다.
   -------------------------------------------------------------------------- */

/* 폰트는 항상 우리 것으로. 없는 폰트를 참조하면 글꼴이 페이지마다 달라진다. */
.dv-content [style*="font-family"] { font-family: inherit !important; }
.dv-content [style*="font-family"] code,
.dv-content [style*="font-family"] pre { font-family: var(--font-mono) !important; }

/* 고정 폭 무력화 (위 [style*="width"] 규칙을 인라인보다 확실히 이기게 한다) */
.dv-content [style*="width"] { width: auto !important; max-width: 100% !important; }

/* 다크모드에서만 색을 되돌린다. 라이트모드에서는 원본 색이 대체로 읽히므로 두 번 손대지 않는다. */
@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) .dv-content [style*="color"] { color: var(--text) !important; }
	:root:not([data-theme="light"]) .dv-content [style*="background"] { background: transparent !important; }
	:root:not([data-theme="light"]) .dv-content pre,
	:root:not([data-theme="light"]) .dv-content code { background: var(--code-bg) !important; color: var(--code-text) !important; }
}
:root[data-theme="dark"] .dv-content [style*="color"] { color: var(--text) !important; }
:root[data-theme="dark"] .dv-content [style*="background"] { background: transparent !important; }
:root[data-theme="dark"] .dv-content pre,
:root[data-theme="dark"] .dv-content code { background: var(--code-bg) !important; color: var(--code-text) !important; }

/* 인쇄 */
@media print {
	.header, .footer, .subnav, .doc-aside, .ad-slot, .ad-rail, .toolhead__actions { display: none !important; }
	body { background: #fff; color: #000; }
	.doc-grid { display: block; }
}
