/* ─────────────────────────────────────────────────────────────
 * duolink 자체 스타일 — **webi 디자인 시스템의 «보정»만 담는다.**
 *
 * 🔴 정본은 design-system.css(이식본)이다. 색·간격·컴포넌트를 여기서 새로 만들지 마라 —
 *    새 스타일을 발명하면 화면마다 색이 갈리고, 다음 시안이 오면 전부 다시 손봐야 한다.
 *    필요한 컴포넌트가 없으면 «정본»(webi)에 만들고 다시 이식한다.
 *
 * 🔴 여기 남는 것은 셋뿐이다:
 *      ① DS 에 없는 duolink 고유 화면(공개 홈·제품 상세·문의)
 *      ② DS 컴포넌트를 이 사이트에 맞추는 «작은» 보정
 *      ③ <dialog> 기반 모달·토스트처럼 DS 와 «구현 방식»이 다른 것
 *
 * ⚠️ 이 파일은 design-system.css «뒤»에 로드된다 — 같은 명시도면 여기가 이긴다.
 *    그래서 실수로 DS 를 덮기 쉽다. DS 클래스(.ds-*)를 여기서 재정의하지 마라.
 * ───────────────────────────────────────────────────────────── */

/* 최소 스타일. 디자인 시스템은 사이트마다 갈아끼운다 — 여기선 골격이 보이기만 하면 된다. */
:root{--fg:#0f172a;--muted:#64748b;--bg:#f8fafc;--card:#fff;--line:#e2e8f0;--primary:#2563eb;--danger:#dc2626;
  --surface-soft:#f1f5f9;--surface-accent:#eff6ff;--primary-strong:#1d4ed8;--on-primary:#fff;
  --success-bg:#e7f6ec;--success-fg:#17612f;--success-line:#b7e0c4;
  --wait-bg:#fef3c7;--wait-fg:#92400e;--unknown-bg:#f3f4f6;--unknown-fg:#4b5563}
/* 🔴 관리자(.dsx — 관리자 셸·로그인 body)에서는 위 옛 변수를 «DS 토큰으로 잇는다».
 *    위 :root 값은 라이트 리터럴뿐이라, 이걸 쓰는 규칙(th·.ds-section·.detail …)이 다크 테마에서
 *    흰 상자 위 흰 글자가 됐다(2026-10-01 운영 실측: /system·/logs·/audit 의 <code>·표 머리).
 *    DS 토큰은 라이트·다크가 둘 다 정의돼 있어 여기서 한 번 이으면 테마를 저절로 따른다.
 *    ⚠️ 공개 사이트는 design-system.css 를 안 싣고 body 에 .dsx 가 없다 — 위 리터럴 그대로(라이트 전용)다.
 *    ⚠️ --danger 는 DS 와 이름이 같아 잇지 않는다(자기 참조가 된다). 관리자가 안 쓰는 옛 변수도 잇지 않는다.
 *    재발 방지 = tools/check-css-literal-colors.py(요소 선택자에 리터럴 색 금지) · tools/contrast-audit.py(실측). */
.dsx{--fg:var(--text);--muted:var(--text-2);--card:var(--surface);--line:var(--border);
  --surface-soft:var(--surface-2);--primary:var(--brand)}
*{box-sizing:border-box}
body{margin:0;font-family:system-ui,-apple-system,"Apple SD Gothic Neo","Noto Sans KR","PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans SC","Noto Sans CJK SC",sans-serif;background:var(--bg);color:var(--fg);line-height:1.6}
/* 🔴 PC 에서 960px 은 좁다(오너 2026-09-03). 다만 «무한정» 넓히지 않는다 —
   글줄이 길어지면 눈이 줄 끝에서 다음 줄 머리를 못 찾는다. 카드 그리드가
   넓이를 쓰고 본문은 안쪽에서 제한된다. */
.container{max-width:1400px;margin:0 auto;padding:28px 24px}
a{color:var(--primary)}
code{background:var(--surface-soft);padding:1px 5px;border-radius:4px;font-size:.9em}

.site-header,.admin-header{display:flex;align-items:center;gap:16px;padding:14px 20px;background:var(--card);border-bottom:1px solid var(--line)}
.brand{font-weight:700;text-decoration:none;color:var(--fg)}
.admin-nav{display:flex;gap:14px;margin-left:8px}
.admin-header .logout{margin-left:auto;display:flex;align-items:center;gap:10px}
.who{color:var(--muted);font-size:.9rem}
.site-nav{display:flex;align-items:center;gap:4px;margin-left:auto}
.site-nav a{color:var(--muted);text-decoration:none;font-size:.92rem}
.site-nav a:hover,.site-nav a:focus-visible{color:var(--primary)}
/* 2026-10-21 — 푸터 바탕은 화면 끝까지, 내용은 .site-footer__in(.site-wrap)이 본문과 같은 폭으로 둔다. */
.site-footer{margin:72px 0 0;color:var(--muted);background:var(--card);border-top:1px solid var(--line)}
.site-footer a{color:var(--muted);font-size:.85rem}

.btn{padding:9px 16px;border:1px solid var(--line);border-radius:8px;background:var(--card);cursor:pointer;font:inherit}
.btn-primary{background:var(--primary);border-color:var(--primary);color:#fff}
.btn-ghost{background:transparent}
.btn-block{width:100%;padding:12px}

.table-wrap{overflow-x:auto;background:var(--card);border:1px solid var(--line);border-radius:10px}
table{border-collapse:collapse;width:100%;min-width:520px}
th,td{padding:10px 14px;text-align:left;border-bottom:1px solid var(--line);white-space:nowrap}
th{background:var(--surface-soft);font-size:.85rem;color:var(--muted)}

/* 로그인 */
.auth-body{display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0}
.auth-wrap{width:100%;max-width:380px;padding:20px}
.auth-card{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:28px;display:flex;flex-direction:column;gap:14px}
.auth-title{margin:0 0 6px;font-size:1.25rem}
.pw-wrap{position:relative;display:block}
.pw-wrap input{padding-right:44px!important}
.pw-toggle{position:absolute;top:50%;right:6px;transform:translateY(-50%);display:flex;align-items:center;justify-content:center;
  width:34px;height:34px;border:0;background:transparent;color:var(--muted);cursor:pointer;border-radius:6px}
.pw-toggle:focus-visible{outline:2px solid var(--primary);outline-offset:1px}
.check{display:flex;align-items:center;gap:8px;font-size:.9rem;color:var(--muted)}
.sorts{color:var(--muted);font-size:.9rem}

/* ── duolink 브랜드 사이트 ─────────────────────────────────── */
.hero{padding:5.5rem 0 4rem;max-width:52rem}
.hero-kicker,.section-kicker{margin:0 0 .75rem;color:var(--primary);font-size:.75rem;font-weight:700;letter-spacing:.14em}
.hero-title{font-size:clamp(2.35rem,7vw,4.6rem);line-height:1.08;margin:0 0 1.25rem;letter-spacing:-.055em}
.hero-sub{max-width:42rem;color:var(--muted);margin:0;font-size:1.05rem}
.portfolio-stats{display:flex;gap:clamp(1.5rem,5vw,3.5rem);margin:2.5rem 0 0}
.portfolio-stats div{display:flex;flex-direction:column}
.portfolio-stats dt{font-size:1.65rem;font-weight:750;line-height:1.2}
.portfolio-stats dd{margin:.2rem 0 0;color:var(--muted);font-size:.82rem}
.section-head{scroll-margin-top:1rem;display:flex;align-items:end;justify-content:space-between;gap:2rem;margin:1rem 0 1.5rem;padding-top:1.5rem;border-top:1px solid var(--line)}
.section-heading{font-size:1.55rem;margin:0;letter-spacing:-.025em}
.section-copy{max-width:28rem;margin:0;color:var(--muted);font-size:.9rem}

/* 🔴 `repeat(2, …)` 는 화면이 아무리 넓어도 «2개»다(오너 지적). auto-fill 로
   화면이 주는 만큼 채운다 — 1400px 에서 4열, 좁아지면 3·2·1 로 스스로 줄어든다.
   ⚠️ `auto-fit` 이 아니라 `auto-fill` 이다: auto-fit 은 카드가 적을 때 남은
   칸을 없애 카드가 «과하게 늘어난다». 제품이 3개일 때 한 장이 화면을 덮는다. */
.product-grid{list-style:none;padding:0;margin:0 0 4rem;display:grid;gap:1.1rem;grid-template-columns:repeat(auto-fill,minmax(300px,1fr))}
.product-card{position:relative;cursor:pointer;display:flex;min-width:0;min-height:15rem;flex-direction:column;border:1px solid var(--line);border-radius:16px;padding:1.4rem;background:var(--card);transition:border-color .18s ease,transform .18s ease,box-shadow .18s ease}
.product-card:hover{border-color:var(--primary);transform:translateY(-2px);box-shadow:0 12px 30px color-mix(in srgb,var(--fg) 8%,transparent)}
.product-card-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:1.2rem;gap:.6rem}
/* 🔴 아이콘은 «미리 받아 둔» 파일이다. CSP 가 default-src 'self' 라 외부 이미지는
   통째로 막힌다 — 저장 위치와 다시 받는 법은 assets/product-icons/README.md. */
.product-icon{width:48px;height:48px;border-radius:12px;object-fit:contain;
  background:var(--surface,#fff);border:1px solid var(--line);padding:4px;flex:0 0 auto}
/* 아이콘이 «없는» 제품은 이니셜로. 없는 그림을 지어내지 않고 자리만 지킨다. */
.product-icon--initial{display:flex;align-items:center;justify-content:center;
  font-weight:700;font-size:1.25rem;color:var(--ink-2,#666);padding:0}
/* 🔴 아이콘이 들어오면서 번호가 오른쪽으로 밀린다 — 번호는 «장식»이라 뒤로 보낸다. */
.product-card-head .product-index{margin-left:auto}
.product-index{font:700 .75rem/1 ui-monospace,Menlo,monospace;color:var(--muted)}
.product-name{font-size:1.3rem;line-height:1.3;margin:0 0 .45rem}
.product-name a{color:var(--fg);text-decoration:none}
/* 카드 전체가 눌리는 자리 — 이름 링크의 누를 영역을 카드 크기로 넓힌다(stretched link). 키보드 포커스는 카드 테두리로 보인다. */
.product-name a::after{content:"";position:absolute;inset:0;border-radius:16px}
.product-name a:focus-visible{outline:none}
.product-card:focus-within{border-color:var(--primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--primary) 35%,transparent)}
.product-tagline{margin:0;color:var(--fg);font-size:.94rem;line-height:1.6;font-weight:600}
.product-summary{display:-webkit-box;overflow:hidden;-webkit-line-clamp:3;-webkit-box-orient:vertical;margin:.75rem 0 1.5rem;color:var(--muted);font-size:.88rem;line-height:1.65}
/* 057 개발 기간 — 카드에서는 하단(.product-card-foot) 바로가기 아래, 상세에서는 배지 옆에 한 줄로. */
.product-period{margin:0;color:var(--muted);font-size:.8rem;font-weight:600}
.product-period--inline{display:inline-block;margin:0 0 0 .5rem;vertical-align:middle}
/* 058 제공 방식 — «정보»라 링크처럼 보이지 않게: 테두리·링크색·화살표 없이 중립색 글 + 장식 아이콘(Codex 검토 2026-10-20). */
.product-platforms{list-style:none;margin:.7rem 0 0;padding:0;display:flex;flex-wrap:wrap;gap:.35rem .9rem}
.product-platforms li{display:inline-flex;align-items:center;gap:.3rem;color:#334155;font-size:.82rem;font-weight:600}
.product-platforms svg,.product-channel svg{width:15px;height:15px;flex:none;fill:none;stroke:currentColor;stroke-width:2}
/* 카드 하단 — 카드가 flex column 이라 margin-top:auto 로 맨 아래에 붙어 카드마다 위치가 같다.
   위: 바로가기 한 줄(버튼이 줄 폭을 똑같이 나눈다) · 아래: 개발 기간(오너 2026-10-20). */
.product-card-foot{margin-top:auto;padding-top:.9rem;border-top:1px solid var(--line);display:flex;flex-direction:column;gap:.6rem}
.product-links{display:flex;gap:.35rem}
/* 버튼 셋이 «언제나 한 줄» — 폭을 똑같이 나눈다(min-width:0). 이름이 짧아(웹·APP(iOS)·APP(AOS)) 최소 카드 폭 300px 에서도 든다.
   2026-10-20 오너 화면(4열·카드 ~320px)에서 「App Store·Google Play」 이름으로는 두 줄로 접혔다. */
.product-links .product-link{flex:1 1 0;min-width:0;justify-content:center;padding:0 .3rem;font-size:.78rem}
.product-link svg{width:14px;height:14px;flex:none;fill:none;stroke:currentColor;stroke-width:2}
/* 🔴 카드 전체 덮개(.product-name a::after)보다 «위층» — 안 올리면 덮개가 클릭을 가로챈다(Codex 검토 치명 1).
   44px 이상(터치 영역) · 포커스는 링크마다 따로 보인다(카드의 focus-within 테두리와 별개). */
.product-link{position:relative;z-index:1;display:inline-flex;align-items:center;gap:.25rem;min-height:44px;padding:0 .6rem;
  border:1px solid #cbd5e1;border-radius:10px;background:var(--card);color:var(--primary-strong);font-size:.82rem;font-weight:700;text-decoration:none;white-space:nowrap}
.product-link:hover{border-color:var(--primary);background:var(--surface-accent)}
.product-link:focus-visible{outline:3px solid var(--primary);outline-offset:2px}
/* 상세 «어디서 쓸 수 있나요» — 링크 있는 항목만 버튼 모양(.product-channel--link), 없는 항목은 평평한 글. */
.product-channels{margin:1.25rem 0;padding:1rem 1.1rem;border:1px solid #dbeafe;border-radius:14px;background:var(--surface-accent)}
.product-channels .section-title{margin-top:0}
.product-channels__list{list-style:none;margin:.6rem 0 0;padding:0;display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:.6rem}
.product-channel{display:flex;align-items:center;gap:.75rem;min-height:60px;padding:.6rem .9rem;border-radius:12px;color:var(--fg)}
.product-channel svg{width:20px;height:20px;color:var(--primary)}
.product-channel__text{display:flex;flex-direction:column;min-width:0;line-height:1.35}
.product-channel__text span{color:#475569;font-size:.82rem;overflow-wrap:anywhere}
.product-channel--link{background:var(--card);border:1px solid #bfdbfe;text-decoration:none}
.product-channel--link:hover{border-color:var(--primary)}
.product-channel--link:focus-visible{outline:3px solid var(--primary);outline-offset:2px}
.product-channel__go{margin-left:auto;color:var(--primary)}
.product-body{margin:1rem 0;line-height:1.85;color:var(--fg)}
.product-actions { margin: 1.5rem 0; }
.product-note { color:var(--muted);background:var(--surface-soft);padding:.7rem .9rem;border-radius:6px; }
.badge-preparing { background: #fff6e5; color: #7a5200; border: 1px solid #f0d9a8;
                   font-size: .72rem; padding: .1rem .45rem; border-radius: 999px; font-weight: 500; }

.empty { color:var(--muted);padding:2.5rem 0;text-align:center; }
.cta{display:flex;align-items:center;justify-content:space-between;gap:2rem;margin:2rem 0 3rem;padding:2rem;border:1px solid var(--line);border-radius:16px;background:var(--surface-accent);color:var(--muted)}
.cta p{margin:.25rem 0 0;color:var(--fg)}/* --muted 는 --surface-accent 위에서 4.37 — 4.5 미달(대비 실측 2026-10-01) */.cta-title{margin:0;color:var(--fg);font-size:1.35rem}/* 🔴 밑줄 제거가 `.cta .btn` 에만 걸려 있어, «다른 곳의» 링크형 버튼은 밑줄이 남았다
   (2026-09-04 상세 상단 바에서 드러났다 — 오너가 「버튼이 버튼 모양이 아니다」라고
   지적한 그 축이다). 링크를 버튼으로 쓰면 «어디서든» 밑줄이 없어야 한다. */
a.btn{text-decoration:none}
.cta .btn{flex:none;text-decoration:none}
.back { margin-top: 2rem; }

/* ── 홈 카드 썸네일 · 분류 칩 (2026-10-04 개선 방안 「카드 시각」「분류·필터」) ─────────────
   🔴 카드에 min-width:0 이 «반드시» 있어야 한다. 그리드 칸의 최소 폭은 기본이 «내용 폭»이라, 1200px 짜리
      그림이 들어오는 순간 칸이 1200px 로 벌어져 페이지에 가로 스크롤이 생겼다(2026-10-04 브라우저 실측 scrollWidth 1932).
   🔴 썸네일은 카드 안쪽 여백을 «뚫고» 카드 위 가장자리에 붙인다 — 여백 안에 두면 그림이 떠 보인다.
      비율은 마크업의 width/height(1200×675)가 미리 잡고, 여기 aspect-ratio 가 화면 크기를 정한다.
      그리드 규칙(auto-fill minmax 300px)은 «건드리지 않는다» — 카드 폭이 그림 크기를 정한다. */
.product-thumb{display:block;width:calc(100% + 2.8rem);min-width:0;max-width:none;height:auto;aspect-ratio:16/9;object-fit:cover;object-position:top center;
  margin:-1.4rem -1.4rem 1.2rem;border-radius:15px 15px 0 0;border-bottom:1px solid var(--line);background:var(--surface-soft)}
/* 칩은 «링크»다(JS 없이 동작). 고른 칩만 채운다 — 전부 채우면 무엇을 골랐는지 안 보인다. */
.filter-chips{display:flex;flex-wrap:wrap;gap:.5rem;margin:0 0 1.4rem}
.filter-chip{display:inline-flex;align-items:center;gap:.4rem;padding:.4rem .85rem;border:1px solid var(--line);border-radius:999px;
  background:var(--card);color:var(--fg);text-decoration:none;font-size:.88rem}
.filter-chip:hover,.filter-chip:focus-visible{border-color:var(--primary)}
.filter-chip.is-active{background:var(--primary);border-color:var(--primary);color:var(--on-primary)}
.filter-chip__count{font:600 .75rem/1 ui-monospace,Menlo,monospace;opacity:.8}
/* 상세 — 같은 사이트 안의 제품(주소에서 계산). 본문과 문의 블록 사이의 절. */
.product-related{margin:1.5rem 0 0}
/* 상세 — 이전·다음 프로젝트(오너 2026-10-06). 두 칸 격자: 이전은 왼쪽, 다음은 «이전이 없어도» 오른쪽 칸(방향이 자리로 읽힌다).
   🔴 칸에 min-width:0 + 이름 한 줄 말줄임 — 긴 제품명이 칸을 밀어 가로 스크롤을 만들지 않게(카드 썸네일 때 실측한 축과 같다).
   좁은 화면(≤720px, .cta 가 세로로 바뀌는 지점과 같다)에서는 한 줄씩 쌓는다 — 두 칸이면 이름이 몇 글자 안 남는다. */
.product-pager{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem;margin:2rem 0 0}
.product-pager__link{display:flex;flex-direction:column;gap:.3rem;min-width:0;padding:1rem 1.2rem;border:1px solid var(--line);border-radius:12px;
  background:var(--card);color:var(--fg);text-decoration:none}
.product-pager__link:hover,.product-pager__link:focus-visible{border-color:var(--primary)}
.product-pager__link--next{grid-column:2;text-align:right}
.product-pager__dir{color:var(--muted);font-size:.82rem}
.product-pager__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}
/* 분류 안에서 도는 중이라는 한 줄 — 두 칸 위를 가로지른다. */
.product-pager__scope{grid-column:1/-1;margin:0;color:var(--muted);font-size:.82rem}
/* 상세 — 구조로 보기(RelScope 관계도, 명세가 있는 제품만). 유형 이름은 제목 아래 한 줄. */
.product-structure{margin:1.5rem 0 0}
.product-structure__type{margin:-.35rem 0 .8rem;color:var(--muted);font-size:.85rem}
.product-structure__map{min-width:0}
/* 상세 — 샘플 결과물(가린 공개본이 있는 제품만). 보고서는 SafeMarkdown 출력이라 요소 선택자로 꾸민다
   (관리자 DS 클래스는 공개 레이아웃에 없다). 표는 좁은 화면에서 가로로만 스크롤한다 — 페이지 전체가 밀리지 않게. */
.product-sample{margin:1.5rem 0 0}
.product-sample__note{margin:-.35rem 0 .9rem;color:var(--muted);font-size:.85rem}
.product-sample__card{margin:0 0 1rem;padding:1rem 1.1rem;background:var(--card);border:1px solid var(--line);border-radius:10px;min-width:0}
.product-sample__label{margin:0 0 .6rem;font-weight:700;font-size:.9rem}
.product-sample__doc{min-width:0;font-size:.9rem;line-height:1.7;overflow-wrap:anywhere}
.product-sample__doc h3{margin:.4rem 0 .5rem;font-size:1rem}
.product-sample__doc h4{margin:1.1rem 0 .4rem;font-size:.92rem}
.product-sample__doc p{margin:.4rem 0}
.product-sample__doc blockquote{margin:0 0 .2rem;padding:.1rem .8rem;border-left:3px solid var(--primary);color:var(--muted-strong);background:var(--surface-accent)}
.product-sample__doc ul{margin:.3rem 0;padding-left:1.15rem}
.product-sample__doc li{margin:.15rem 0}
.product-sample__doc > div{overflow-x:auto;margin:.4rem 0}
/* 🔴 선택자를 .ds-table-wrap .ds-table(고정 레이아웃) 보다 강하게 — 약하면 열이 균등 분배돼 코드가 옆 칸을 덮는다(2026-10-21 실측). */
.product-sample__doc table,.product-sample__doc .ds-table-wrap .ds-table{border-collapse:collapse;table-layout:auto;width:max-content;min-width:100%;font-size:.82rem}
.product-sample__doc th,.product-sample__doc td{padding:.35rem .5rem;border:1px solid var(--line);text-align:left;vertical-align:top;white-space:normal;overflow-wrap:break-word;min-width:5rem;max-width:22rem}
.product-sample__doc code{white-space:normal;overflow-wrap:anywhere}
.product-sample__doc th{background:var(--surface-soft)}
.product-sample__more{margin:.7rem 0 0;border-top:1px dashed var(--line);padding-top:.6rem}
.product-sample__more summary{cursor:pointer;color:var(--primary-strong);font-weight:600}
.product-sample__more summary:focus-visible{outline:2px solid var(--primary);outline-offset:2px;border-radius:4px}
/* 🔴 RelScope 토큰 연결 — 공개 사이트 색을 따르게 한다(README §4). 공개 사이트는 «라이트 전용»이라
   «모든» 색 토큰을 잇는다: 하나라도 빠지면 그 토큰만 RelScope 기본 다크값으로 바뀌어(시스템 다크) 한 도식에 두 테마가 섞인다.
   사이트에 없던 값(진한 선·실패 옅은 색·상승/하락·포커스 링)은 :root 에 새로 두고 잇는다.
   ⚠️ --rm-surface-2(묶음 바탕)는 --surface-soft 가 아니라 --bg 다 — --muted 글자가 --surface-soft 위에서 4.34:1 로 대비 미달(site-check 실측).
   ⚠️ --rm-text-muted 도 --muted 보다 한 단 진한 --muted-strong 이다 — 입력·강조 노드의 옅은 파랑 바탕 위 줄 글자가 --muted 로는 4.13:1(같은 실측). */
:root{--line-strong:#cbd5e1;--muted-strong:#475569;--danger-soft:#fdecec;--up:#9a3412;--up-soft:#fde7d6;--down:#1d4ed8;--down-soft:#dbe6fb;
  --focus-ring:0 0 0 3px rgba(37,99,235,.35)}
.relscope{
  --rm-bg:var(--card);--rm-surface:var(--card);--rm-surface-2:var(--bg);
  --rm-border:var(--line);--rm-border-strong:var(--line-strong);
  --rm-text:var(--fg);--rm-text-muted:var(--muted-strong);
  --rm-accent:var(--primary);--rm-on-accent:var(--on-primary);
  --rm-ok:var(--success-fg);--rm-ok-soft:var(--success-bg);
  --rm-warn:var(--wait-fg);--rm-warn-soft:var(--wait-bg);
  --rm-fail:var(--danger);--rm-fail-soft:var(--danger-soft);
  --rm-idle:var(--unknown-fg);--rm-idle-soft:var(--unknown-bg);
  --rm-up:var(--up);--rm-up-soft:var(--up-soft);--rm-down:var(--down);--rm-down-soft:var(--down-soft);
  --rm-focus-ring:var(--focus-ring)}

@media (max-width:720px){
  .hero{padding:3.8rem 0 3rem}/* auto-fill 이 알아서 줄어든다. 강제 1열은 «두 규칙이 같은 것을 정하는» 자리가 되어 뺀다. */.section-head,.cta{align-items:flex-start;flex-direction:column}.product-pager{grid-template-columns:1fr}.product-pager__link--next{grid-column:auto}.product-card{min-height:0}.portfolio-stats{justify-content:space-between}.site-footer{align-items:flex-start;flex-direction:column}.admin-header{align-items:flex-start;flex-wrap:wrap}.admin-nav{order:3;width:100%;overflow-x:auto;margin-left:0;padding-bottom:.25rem}}

/* 폼 */
.form { max-width: 34rem; }
/* 🔴 `.field` 는 «한 번만» 정의한다. 2026-08-31 실측: 골격의 flex 정의와 사이트의 block
   정의가 둘 다 있어서 뒤가 이겼고, 그 결과 `.field-check{flex-direction:row}` 가
   애초에 안 먹고 있었다. CSS 에 «있다»와 «먹는다»는 다르다 —
   tools/check-css-classes.py 가 이제 중복 정의도 잡는다. */
.field { display: block; margin: 0 0 1.1rem; }
.field-label { display: block; font-size: .9rem; margin-bottom: .3rem; font-weight: 500; }
.field input, .field textarea, .field select { width: 100%; padding: .55rem .7rem; border: 1px solid #d1d5db;
                                border-radius: 6px; font: inherit; }
.field-error { display: block; color: #8a1f1f; font-size: .85rem; margin-top: .3rem; }
.req { color: #8a1f1f; }
.hint { font-size: .85rem; color: #6b7280; }
.alert-error { background: #fdecec; border: 1px solid #f0b4b4; color: #8a1f1f;
               padding: .7rem .9rem; border-radius: 6px; }

/* 🔴 허니팟 — 사람에게는 안 보이고 봇은 채운다.
      `display:none` 이면 일부 봇이 «숨겨진 필드»로 인식해 건너뛴다.
      화면 밖으로 밀어내고 스크린리더에는 aria-hidden 으로 감춘다. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* ── 관리자 ─────────────────────────────────────────────── */
.tabs { display: flex; gap: .25rem; flex-wrap: wrap; margin: 0 0 .75rem; }
.tab { padding: .35rem .7rem; border-radius: 6px; text-decoration: none; color: #4b5563; font-size: .9rem; }
.tab.is-current { background: #111827; color: #fff; }
.tab .count { opacity: .7; margin-left: .3rem; font-size: .8rem; }
.filters { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; margin-bottom: 1rem; }
.filters input[type=search] { padding: .45rem .6rem; border: 1px solid #d1d5db; border-radius: 6px; font: inherit; }

.table { width: 100%; border-collapse: collapse; font-size: .92rem; }
.table th, .table td { text-align: left; padding: .55rem .6rem; border-bottom: 1px solid #eef0f2; }
.table th { font-weight: 600; color: #4b5563; font-size: .85rem; }
.table .when { color: #6b7280; white-space: nowrap; }

.kv { display: grid; grid-template-columns: 7rem 1fr; gap: .4rem 1rem; margin: 1rem 0; }
.kv dt { color: #6b7280; font-size: .9rem; }
.kv dd { margin: 0; }
.message-box { background: #f9fafb; border: 1px solid #eef0f2; border-radius: 8px;
               padding: 1rem; line-height: 1.8; margin: 1rem 0; white-space: pre-wrap; }

.actions { display: flex; gap: .75rem; align-items: center; flex-wrap: wrap; margin: 1.25rem 0; }
.inline { display: inline; }
.field-inline { display: inline-flex; align-items: center; gap: .4rem; }
/* 🔴 select 만 쓰면 «같은 줄에 선 input» 이 스타일 없이 남는다 — 오류가 안 나므로
   화면을 눈으로 봐야만 드러난다(감사 로그의 날짜 칸이 그랬다). 둘을 같이 잡는다. */
.field-inline select,
.field-inline input { padding: .4rem .5rem; border: 1px solid #d1d5db; border-radius: 6px; font: inherit; }
/* 날짜 칸은 .field 의 width:100% 를 물려받아 줄을 통째로 먹는다. 여기서만 되돌린다. */
.field-inline input[type=date] { width: auto; }
/* 🔴 라벨이 «두 글자에서 쪼개지는» 것을 막는다. 브라우저로 보기 전에는 안 보인다 —
   한 줄에 선 항목이 늘면 flex 가 라벨부터 줄인다(감사 로그의 「동작」이 「동/작」이 됐다). */
.field-inline > span { white-space: nowrap; }
.btn-sm { padding: .25rem .55rem; font-size: .85rem; }
.btn-danger { background: #8a1f1f; color: #fff; border: 0; }

.notes { margin-top: 2rem; border-top: 1px solid #eef0f2; padding-top: 1.25rem; }
.note-list { list-style: none; padding: 0; margin: 1rem 0 0; }
.note-list li { border-left: 3px solid #e2e5e9; padding: .3rem 0 .3rem .8rem; margin-bottom: .9rem; }
.note-meta { font-size: .82rem; color: #6b7280; }
.note-body { line-height: 1.7; }
.failed-list { margin: .5rem 0 0; padding-left: 1.1rem; font-size: .9rem; }
.failed-list li { margin: .3rem 0; }

/* 모달 — <dialog> 기본 테두리를 지운다. showModal() 이 포커스 가둠·ESC·backdrop 을 준다. */
.modal { border: 0; border-radius: 12px; padding: 0; width: min(30rem, calc(100vw - 2rem));
         box-shadow: 0 10px 40px rgb(0 0 0 / 18%); }
.modal::backdrop { background: rgb(17 24 39 / 45%); }
.modal form { padding: 1.25rem; }
.modal-title { margin: 0 0 .75rem; font-size: 1.05rem; }
.modal-note { font-size: .9rem; color: #374151; line-height: 1.7;
              background: #f9fafb; padding: .7rem .85rem; border-radius: 6px; margin: 0 0 1rem; }
.modal-actions { display: flex; justify-content: flex-end; gap: .5rem; margin-top: 1.25rem; }

/* 토스트 — 오른쪽 «헤더 아래». top:1rem 이면 로그아웃 버튼을 덮는다. */
.toast-region { position: fixed; top: 5rem; right: 1rem; z-index: 100;
                display: flex; flex-direction: column; gap: .5rem;
                pointer-events: none; max-width: min(26rem, calc(100vw - 2rem)); }
/* 🔴 색(배경·글자)은 여기서 주지 않는다 — DS .ds-toast/.ds-toast--success/--danger 가 테마별로 준다.
 *    예전 `.toast{background:#111827}`·`.toast-success{background:#17612f}` 가 같은 명시도로 DS 를 덮었고,
 *    `.dsx .toast.ds-toast{color:inherit}` 와 겹쳐 «라이트 테마에서 어두운 상자에 어두운 글자»였다
 *    (2026-10-01 대비 실측 1.1~2.1 — 관리자 기본 테마의 모든 토스트). */
.toast { pointer-events: auto; border-radius: 8px;
         padding: .7rem .9rem; font-size: .9rem; line-height: 1.55;
         display: flex; gap: .6rem; align-items: flex-start; }
.toast-close { background: none; border: 0; color: inherit; cursor: pointer;
               font-size: 1rem; line-height: 1; padding: 0 .1rem; }  /* opacity 를 주지 않는다 — DS 글자색의 대비를 깎는다(라이트 3.85) */
@media (prefers-reduced-motion: no-preference) {
  .toast { animation: toast-in .16s ease-out; }
  @keyframes toast-in { from { opacity: 0; transform: translateY(-4px); } }
}

.banner { padding: .75rem 1rem; border-radius: 8px; margin: 0 0 1rem; line-height: 1.55;
          background: var(--card); border: 1px solid var(--line); }
.banner-warn  { background: #fff6e5; border: 1px solid #f0d9a8; color: #7a5200; }
.banner-error { background: #fdecec; border: 1px solid #f0b4b4; color: #8a1f1f; }
.badge { display: inline-block; font-size: .75rem; padding: .15rem .5rem;
         border-radius: 999px; border: 1px solid transparent; }
.badge-ok      { background:var(--success-bg);color:var(--success-fg);border-color:var(--success-line); }
.badge-bad     { background: #fdecec; color: #8a1f1f; border-color: #f0b4b4; }
.badge-unknown { background:var(--unknown-bg);color:var(--unknown-fg);border-color:var(--line); }
.code { font: .75rem/1.4 ui-monospace, Menlo, monospace; background: #f3f4f6;
        color: #4b5563; padding: .1rem .4rem; border-radius: 4px; }
.detail { color: var(--muted); font-size: .85rem; }
.admin-nav .is-current { font-weight: 600; text-decoration: underline; }

/* ── 공통 조판 ─────────────────────────────────────────────
   🔴 아래 넷(.page-title·.muted·.right·.num)은 «이미 10개 뷰가 쓰고 있는데»
      CSS 에 없었다. CSS 는 없는 클래스에 오류를 내지 않는다 — 아무 일도 안 일어날 뿐이다.
      그래서 tools/check-css-classes.py 로 «검사»를 만들었다. 규칙으로는 또 놓친다. */
.page-title{margin:0 0 18px;font-size:1.5rem;line-height:1.3}
.page-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:18px}
.page-head .page-title{margin:0}
.muted{color:var(--muted);font-size:.9rem}
.right{text-align:right}
.num{text-align:right;font-variant-numeric:tabular-nums;color:var(--muted)}
body.admin{background:var(--bg)}

/* 🔴 인라인 SVG 에는 `hidden` 속성이 «안 먹는다». UA 스타일시트의 `[hidden]{display:none}`
      은 HTML 네임스페이스로 한정돼 있어서 SVG 는 그대로 보인다 — 눈 아이콘 두 개가
      동시에 뜬다. 그래서 여기서 명시한다(2026-08-31 CSS 검사가 잡았다).
   pointer-events:none 는 별개 이유다 — 없으면 클릭의 target 이 <svg> 가 되어
      `ev.target.closest(...)` 류가 버튼을 못 찾는 사고가 난다. */
.icon-eye,.icon-eye-off{display:block;pointer-events:none}
.icon-eye[hidden],.icon-eye-off[hidden]{display:none}

/* ── 배지 ─────────────────────────────────────────────────
   🔴 뜻으로 이름 짓는다: ok=확정·정상 / wait=준비·대기 / warn=주의.
      `.badge-preparing` 처럼 «그 화면의 사정»으로 지으면 다른 화면에서 재사용이 안 된다. */
.badge-wait{background:var(--wait-bg);color:var(--wait-fg)}
.badge-warn{background:#fee2e2;color:#991b1b}

/* ── 모달 ─────────────────────────────────────────────────*/
.modal-wide{max-width:640px;width:calc(100vw - 32px)}
/* 🔴 «직계» 폼만 세로로 쌓는다. 이 규칙이 노리는 것은 `<dialog><form>필드들</form></dialog>`
 *    모양의 폼 모달(products·admins·mail-templates·design-system)이다.
 *    자손 선택자로 두면 «조각을 fetch 로 채우는» 모달(mails·inquiries)의 «안쪽» 폼까지
 *    잡아 상태 라벨·선택·버튼이 한 줄이 아니라 세로로 흩어진다 — 그 폼들은 `.inline`
 *    (display:inline) 을 달고 있지만 (0,1,1) 인 이 규칙을 못 이겨 조용히 진다.
 *    2026-09-04 실측: 수신함·문의 두 모달의 「상태 바꾸기」가 모두 3단으로 깨져 있었다. */
.modal-wide > form{display:flex;flex-direction:column;gap:12px}

/* ── 폼 ───────────────────────────────────────────────────*/
.field small{display:block;font-weight:400;color:var(--muted);font-size:.82rem;margin-top:2px}
.field-check{display:flex;flex-direction:row;align-items:flex-start;gap:8px}
.field-check input{width:auto;margin-top:4px;flex:none}
.field-check span{font-weight:500}

/* ── 설정 화면 ────────────────────────────────────────────
   🔴 칸만 두지 않는다. 항목마다 ①무엇인가 ②어디서 얻나 ③값의 모양 ④켜면 뭐가 달라지나
      ⑤지금 상태를 함께 보여준다 — 처음 보는 사람이 이 화면만으로 넣을 수 있어야 한다. */
.setting{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:18px 20px;margin-bottom:16px}
.setting-head{display:flex;align-items:center;gap:10px;margin-bottom:6px}
.setting-head h2{margin:0;font-size:1.05rem}
.setting-help{margin:0 0 6px}
.setting-meta{margin:0 0 6px;color:var(--muted);font-size:.9rem}
.setting-effect{margin:0 0 12px;font-size:.9rem}
.setting-form{display:flex;flex-direction:column;gap:10px}
.setting-actions{display:flex;gap:8px}
.setting-updated{margin:10px 0 0;font-size:.82rem}

/* ── 디자인 시스템 ────────────────────────────────────────
   🔴 이 화면 «전용» 스타일을 만들지 않는다. 아래는 «배치»만 한다 —
      전용 스타일을 쓰면 이 화면만 멀쩡하고 사이트는 깨진 상태가 가능해진다. */
.ds-section{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:18px 20px;margin-bottom:16px}
.ds-section h2{margin:0 0 4px;font-size:1.05rem}
/* ⚠️ `.ds-row` 는 «DS 의 유틸리티 이름»이다(design-system.css 의 `.dsx .ds-row`).
 *    여기서 같은 이름을 다시 정의하면 이 화면 밖까지 새어 나간다 — DS 쪽은 `flex-wrap`
 *    과 `margin-top` 을 «선언하지 않으므로» 명시도로도 못 이기고 그 둘만 조용히 살아남는다.
 *    🔴 그 `flex-wrap:wrap` 이 수신함·문의 모달의 「상태 바꾸기」 한 줄을 세로로 접었다
 *      (2026-09-04 실측). 그래서 wrap 은 아래에서 «이 화면 안으로» 되돌린다.
 *    ⚠️ `margin-top:12px` 은 그대로 둔다 — 설정·대시보드·제품 화면이 이미 이 여백 위에
 *      그려져 있어서, 걷어내면 저장 버튼이 입력칸에 달라붙는다(같은 날 실측하고 되돌렸다).
 *      이름이 겹친 것 자체가 부채이고, 정리하려면 그 화면들을 함께 고쳐야 한다. */
.ds-row{display:flex;align-items:center;gap:10px;margin-top:12px}
.ds-section .ds-row{flex-wrap:wrap}
.ds-form{display:flex;flex-direction:column;gap:12px;max-width:420px;margin-top:12px}

/* ── 제품 상세(공개) ──────────────────────────────────────*/
.product-detail{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:24px}
.product-overview{margin:2rem 0;padding-top:.25rem;border-top:1px solid var(--line)}

/* 로그인 뒤에만 나오는 제품 운영 주소. 공개 뷰는 이 데이터를 SELECT 하지 않는다. */
.ops-link-list{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:.15rem}
.ops-link-list a{font-size:.82rem;white-space:nowrap}

/* ── 보안 · 감사 로그 (R1 마감) ────────────────────────────
   🔴 새 «전용» 스타일을 최소로 둔다. 기존 .banner/.table/.modal 을 그대로 쓰고,
      여기 있는 것은 그 관용구로 표현되지 않는 넷뿐이다. */

/* 화면 안의 절 제목. h1(.page-title) 아래 단계다. */
.section-title{margin:24px 0 8px;font-size:1.05rem}

/* 「이게 뭔가」를 접어 두는 도움말. 전 사이트 공통요건 1(설정마다 사용법). */
.help{margin:8px 0 16px;padding:10px 14px;background:var(--card);border:1px solid var(--line);border-radius:10px}
.help summary{cursor:pointer;font-size:.9rem}
/* 🔴 flex 를 쓰지 않는다 — flex <summary> 는 ▶ 마커가 사라진다(접힘 여부를 못 본다). */
.help p{margin:8px 0 0;font-size:.88rem;color:var(--muted)}

/* 시각·IP 처럼 «줄바꿈되면 읽기 어려운» 칸. */
.nowrap{white-space:nowrap}

/* 감사 로그 detail 은 길다. 표를 밀어내지 않게 이 칸 안에서만 접는다. */
.detail{max-width:38ch;word-break:break-all;font-size:.82rem}

/* 빠른 기간 · CSV — 링크 줄. 🔴 자바스크립트가 아니라 «주소»라 새로고침·공유가 같은 화면을 낸다. */
.quick-range{display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;margin:0 0 16px}

/* 쪽 넘기기. */
.pager{display:flex;gap:.75rem;align-items:center;justify-content:center;margin:16px 0}

/* ─── DS 보정 (2026-09-02, webi 디자인 시스템 적용) ─────────────
 * 🔴 DS 클래스를 «재정의»하지 않는다. 여기 있는 것은 duolink 의 구현 방식이
 *    DS 와 다른 자리(<dialog> 모달)를 잇는 «다리»뿐이다.
 */

/* 🔴 명시도를 DS 와 맞춘다. `.modal-wide`(0,1,0) 는 `.dsx .ds-modal__panel`(0,2,0) 에
 *    져서 «넓은 모달이 넓어지지 않는다». CSS 에 «썼다»가 «먹는다»가 아니다. */
.dsx .modal-wide { max-width: 860px; }

/* <dialog> 는 기본이 화면 좌상단 고정이다. DS 의 ds-modal 은 바깥 컨테이너가 가운데
 * 정렬을 하는데, 우리는 dialog 를 그대로 쓰므로 여기서 가운데로 보낸다. */
.dsx dialog.modal { margin: auto; }
.dsx dialog.modal::backdrop { background: rgb(17 24 39 / 45%); }

/* 토스트는 DS 의 생김새(.ds-toast — 색 포함)를 쓰고, 위치·수명·닫기 버튼은 우리가 갖는다. */
.dsx .toast.ds-toast .toast-close { color: inherit; }

/* ─── CSP 때문에 «인라인 style 을 쓸 수 없다» (2026-09-02) ──────
 * 🔴 이 사이트의 CSP 는 `default-src 'self'` 다. `style-src` 를 따로 주지 않았으므로
 *    거기로 폴백하고, `'unsafe-inline'` 이 없어 **`style="…"` 속성이 통째로 무시된다.**
 *    webi 는 CSP 가 «아예 없어» 인라인 style 을 마음껏 쓰는데, DS 마크업을 복사하면서
 *    그 습관이 함께 넘어왔다 — 그리고 아무 오류 없이 조용히 죽었다.
 *    (오너가 「상하 여백이 이상하다」고 한 것이 이 자리다.)
 * 🔴 그래서 여백·변수 주입을 «전부 클래스로» 옮겼다. tools/check-inline-style.py 가 재발을 막는다.
 */

/* 🔴 셸 높이의 기준. webi 는 셸 위에 공통 크롬이 있어 그 높이를 빼는데 duolink 엔 없다.
 *    정의하지 않으면 `calc(100vh - var(--chrome-h))` 가 빈 값이 되어 사이드바 높이가
 *    조용히 깨진다. 전에는 <body style="--chrome-h:0px"> 로 줬는데 CSP 가 그걸 막는다. */
:root { --chrome-h: 0px; }

/* 세로 리듬 — DS 에 margin 유틸이 없어 «이 사이트의» 최소 보정으로 둔다.
 * ⚠️ 값을 늘리지 마라. 늘어나면 그건 DS 에 있어야 할 컴포넌트라는 뜻이다. */
.dsx .u-stack     { display: flex; flex-direction: column; gap: 12px; }
.dsx .u-stack-lg  { display: flex; flex-direction: column; gap: 18px; }

/* KPI 카드를 링크로 쓴다 — 숫자를 보고 바로 갈 수 있어야 한다.
 * DS 의 .ds-stat 는 <div> 기준이라 링크 표시만 여기서 지운다. */
.dsx a.ds-stat { display: block; text-decoration: none; }
.dsx a.ds-stat:hover,
.dsx a.ds-stat:focus-visible { border-color: var(--brand); }

/* 로그인 카드 안쪽 여백. .ds-login__c 는 테두리만 갖고 내부 여백은 내용이 정한다.
   🔴 2026-10-21 — 위 공용 .auth-card 의 테두리·둥근 모서리가 .ds-login__c «안»에서 한 번 더 그려져 이중 테두리였다. 관리자에서는 지운다. */
.dsx .auth-card { padding: 20px 24px 24px; border: 0; border-radius: 0; background: transparent; }
.dsx .auth-head { display: flex; flex-direction: column; gap: 2px; margin-bottom: 4px; }
.dsx .auth-title { margin: 0; font-size: 20px; font-weight: 700; color: var(--text); }
.dsx .auth-sub { margin: 0; font-size: 12.5px; color: var(--text-2); }
.dsx .auth-checks { display: flex; gap: 18px; flex-wrap: wrap; }

/* 레이아웃 배너 — 본문과 붙지 않게. */
.dsx .u-banner { margin-bottom: 16px; }
.dsx .u-submit { margin-top: 14px; }

/* ─── DS «조합» 보정 (2026-09-02) ────────────────────────────
 * 🔴 DS 는 컴포넌트를 각각 정의하지만 «패널 안에 넣었을 때»의 규칙은 없다.
 *    ds-table-wrap 은 자기 테두리·라운드를 갖고 있어 패널 안에 넣으면 테두리가 겹친다.
 *    이건 DS 재정의가 아니라 «조합»을 잇는 다리다.
 */
.dsx .ds-panel > .ds-table-wrap { border: 0; border-radius: 0; }
.dsx .ds-panel > .ds-tabs       { padding: 0 10px; }
.dsx .ds-panel > .ds-hint       { padding: 10px 18px; margin: 0; border-top: 1px solid var(--border); }
.dsx .ds-panel > .ds-empty      { padding: 44px 20px; }

/* 탭을 <a> 로 쓴다 — DS 의 .ds-tab 은 <button> 기준이라 밑줄만 지운다. */
.dsx a.ds-tab { text-decoration: none; display: inline-flex; align-items: center; gap: 6px; }


/* 알림 센터 — DS 의 ds-menu 는 «상대 위치 부모»를 전제한다(position:absolute). */
.dsx #ad-noti-wrap { position: relative; }
.dsx #ad-noti      { position: relative; }
.dsx #ad-noti-menu .ds-menu__item { align-items: flex-start; }
.dsx #ad-noti-menu .ds-menu__desc { display: block; }

/* 알림 점 — DS 의 .ds-dot 은 6px 원이다. 제목 첫 줄에 맞춰 살짝 내린다. */
.dsx #ad-noti-menu .ds-dot { flex: none; margin-top: 6px; }

/* ─── 설정 화면 — 넓은 화면에 맞춘 조합 (2026-09-02) ──────────
 * 🔴 DS 를 «재정의»하지 않는다. ds-grid-auto 가 grid·gap 을 주고 여기서는
 *    이 화면에만 맞는 «최소 폭»과 «입력칸 상한»만 정한다.
 */

/* 카드 최소 폭 — 이보다 좁아지면 한 줄로 떨어진다(auto-fill 이 알아서 한다).
 * 🔴 미디어 쿼리로 열 수를 못박지 않는다. 못박으면 사이드바를 접거나 창을 반으로
 *    나눴을 때 어긋난다 — auto-fill 은 «남은 폭»을 보고 스스로 정한다. */
/* 🔴 min(30rem, 100%) — 390px 화면에서 30rem(480px) 고정 최소폭이 카드를 화면 밖으로 밀었다(2026-10-01 브라우저 실측: 문서 494px). */
.dsx .u-setting-grid { grid-template-columns: repeat(auto-fill, minmax(min(30rem, 100%), 1fr)); }

/* 🔴 값 하나 넣는 칸이 화면 끝까지 늘어나면 눈이 라벨에서 입력칸까지 따라가지 못한다.
 *    ds-input 은 width:100% 라 부모가 상한을 준다. */
.dsx .u-setting-card .ds-input,
.dsx .u-setting-card .ds-select,
.dsx .u-setting-card .ds-textarea { max-width: 34rem; }

/* 카드 안 여백 — 설명·예시·효과가 붙어 있으면 무엇이 무엇인지 안 갈린다. */
.dsx .u-setting-card .ds-panel__body > * + * { margin-top: 10px; }
.dsx .u-setting-card .ds-hint { margin: 0; }
/* 공백 없는 긴 예시(서비스 계정 JSON 모양 등)가 카드 폭을 넘지 않게 — 390px 실측에서 이 태그 하나가 문서를 494px 로 밀었다. */
.dsx .u-setting-card .ds-tag { white-space: normal; overflow-wrap: anywhere; max-width: 100%; }
/* 연동 카드(A4) — 한 카드 안에 칸이 여럿이다. fieldset 은 검증 세션에서 «한 번에 잠그는» 그릇이라 테두리·여백을 걷는다.
 * 칸과 칸 사이는 선 하나로 가른다(어느 설명이 어느 칸 것인지 섞이지 않게). */
.dsx .u-setting-fields { border: 0; margin: 0; padding: 0; min-width: 0; display: flex; flex-direction: column; gap: 14px; }
/* PRG 로 «#set-<연동>» 에 돌아올 때 카드 머리(오류 줄)가 고정 머리띠 밑에 숨지 않게. */
.dsx .u-setting-card { scroll-margin-top: calc(var(--chrome-h) + 12px); }
.dsx .u-setting-field { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.dsx .u-setting-field + .u-setting-field { border-top: 1px solid var(--border); padding-top: 14px; }
.dsx .u-setting-field__title { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0; font-size: 13.5px; }

/* 폼 한 장짜리 화면 — 카드 폭과 입력칸 상한. 화면이 넓어져도 칸이 끝까지 늘어나지 않는다. */
.dsx .u-form-card { max-width: 42rem; }
.dsx .u-form-card .ds-input { max-width: 100%; }

/* ─── «가로 줄»에 놓인 칸은 제 폭을 정한다 (2026-09-04) ─────────
 * 🔴 `ds-input`·`ds-select` 는 `width:100%` 다. 세로 폼에서는 그게 맞지만, «가로로
 *    늘어놓는» 컨테이너 안에서는 칸 하나가 줄을 통째로 먹어 나머지가 아래로 밀린다
 *    (수신함·문의 필터바가 그랬다). `label` 로 감싸면 이번엔 폭 계산이 칸 쪽으로
 *    쏠려 «라벨 글자»가 한 자씩 세로로 접힌다 — 필터바를 고쳤더니 모달 푸터에서
 *    똑같은 증상이 다시 났다.
 * 🔴 그래서 자리를 «열거»하지 않고 판정으로 적는다: 가로 줄 컨테이너(`ds-filterbar`·
 *    `ds-row`) 안이면 칸이 스스로 폭을 정한다. 새 화면이 그 관용구를 쓰면 자동으로 걸린다.
 *    (DS 는 고치지 않는다 — 다른 자리의 `width:100%` 는 그대로 필요하다.) */
.dsx .ds-filterbar .ds-input,
.dsx .ds-filterbar .ds-select,
.dsx .ds-row .ds-input,
.dsx .ds-row .ds-select { width: auto; }

/* ── 제품 «상세» (2026-09-03 오너: 내용이 빈약하다) ────────────────────
   🔴 상세는 홈보다 «좁게» 둔다. 홈은 카드 격자라 넓이를 쓰지만, 상세는 읽는 글이라
      1400px 을 꽉 채우면 눈이 줄 끝에서 다음 줄 머리를 못 찾는다. */
.product-detail{max-width:1080px}
.product-hero-head{display:flex;align-items:flex-start;gap:1rem;margin-bottom:.4rem}
.product-hero-icon{width:64px;height:64px;border-radius:14px;object-fit:contain;
  background:var(--surface,#fff);border:1px solid var(--line);padding:6px;flex:0 0 auto}
/* 대표 이미지 — 화면 전체를 덮지 않게 최대 폭을 두고, 비율을 미리 잡아 덜컹거림을 막는다. */
.product-shot{margin:1.6rem 0 2rem;border:1px solid var(--line);border-radius:16px;overflow:hidden;background:var(--card)}
.product-shot img{display:block;width:100%;height:auto;aspect-ratio:16/9;object-fit:cover;object-position:top center}
.product-shot figcaption{padding:.55rem .9rem;font-size:.82rem;color:var(--ink-3,#777);border-top:1px solid var(--line)}
/* 본문과 곁줄. 좁아지면 한 줄로 내려온다. */
.product-cols{display:grid;grid-template-columns:minmax(0,1fr) minmax(220px,300px);gap:2rem;align-items:start}
/* 곁줄 — 스택·링크가 사는 자리. 좁아지면 본문 아래로 내려온다. */
.product-side{min-width:0}
.product-highlights{margin:.6rem 0 0;padding-left:1.15rem;line-height:1.9}
.product-stack{list-style:none;margin:.6rem 0 1.6rem;padding:0;display:flex;flex-wrap:wrap;gap:.4rem}
.product-stack .tag{font-size:.82rem;padding:.28rem .6rem;border:1px solid var(--line);
  border-radius:999px;background:var(--card);color:var(--ink-2,#555)}
@media (max-width:860px){
  .product-cols{grid-template-columns:1fr;gap:1.2rem}
  .product-hero-icon{width:48px;height:48px}
}

/* ── 제품 상세의 «상단 고정» 이동 바 (2026-09-04 오너 요청) ─────────────
   「목록으로가 하단에 있어 불편 · 버튼이 텍스트라 불편 · 스크롤해도 상단에 유지」.
   🔴 `top:0` 인 이유: 사이트 헤더가 sticky 가 «아니다»(실측). 헤더를 나중에 고정으로
      바꾸면 여기 top 을 그 높이만큼 내려야 겹치지 않는다. */
.product-bar{
  position:sticky; top:0; z-index:20;
  display:flex; gap:.5rem; align-items:center; flex-wrap:wrap;
  /* 카드 안쪽 여백을 «뚫고» 화면 폭을 쓰게 한다 — 안 그러면 고정됐을 때 가장자리가 뜬다 */
  margin:-24px -24px 1rem; padding:12px 24px;
  background:var(--card); border-bottom:1px solid var(--line);
  border-radius:12px 12px 0 0;
}
/* 🔴 고정된 «뒤»에만 그림자를 준다 — 처음부터 있으면 스크롤 전에도 떠 보인다.
   가장자리가 화면 위에 닿는 순간을 감지하는 표준 방법이 없어, 항상 옅게 둔다. */
.product-bar{ box-shadow:0 1px 0 rgba(0,0,0,.03); }
.product-bar__note{ font-size:.86rem; color:var(--ink-3,#777); }
/* 곁줄이 없을 때 본문이 폭을 다 쓴다 — 빈 칸이 남으면 「무언가 안 나온 것」처럼 보인다 */
.product-cols--single{ grid-template-columns:minmax(0,1fr); }
@media (max-width:860px){
  .product-bar{ margin:-16px -16px 1rem; padding:10px 16px; }
}

/* 대시보드 방문자 차트 — 🔴 SVG 로 그린다.
   CSP 의 style-src 에 'unsafe-inline' 이 없어 `style="height:…"` 가 막히고,
   DS 의 .ds-spark 는 그 방식이라 쓸 수 없다. 외부 차트 라이브러리도 CDN 이 막힌다.
   여기 두는 것은 «색과 크기»뿐이고 좌표는 마크업(속성)이 갖는다. */
.chart{width:100%}
.chart svg{width:100%;height:120px;display:block}
.chart__bar{fill:var(--brand,#4a7dff);opacity:.85}
.chart__bar:hover{opacity:1}

/* 수신함 표 — 🔴 제목 열이 927px 를 먹어 표가 1737px 이 됐고 「받은 때」가 화면 밖으로
   밀렸다(2026-09-08 실측, 뷰포트 1512px). ds-table-wrap 의 가로 스크롤은 «정상»이었지만,
   스크롤해야 보이는 열은 사실상 없는 것과 같다. 열 폭을 잡아 한 화면에 들어오게 한다.
   ⚠️ 제목은 «자르되» title 속성에 전문을 남긴다 — 잘렸다는 사실을 감추지 않는다. */
/* 🔴 table-layout:fixed 에서는 «폭을 안 준 열»이 남은 공간을 «균등 분배»한다.
   처음에 제목에 max-width:0 만 줬더니 제목·검사·상태가 나란히 172px 이 됐다
   — 제목이 제일 좁아진 것이다(2026-09-08 실측). 폭은 «전부» 지정해야 한다. */
.ds-table-wrap .ds-table { table-layout:fixed; width:100% }
.mail-c-dom  { width:9rem;   white-space:nowrap }
.mail-c-from { width:14rem }
.mail-c-chk  { width:5rem;   white-space:nowrap }
.mail-c-st   { width:5.5rem; white-space:nowrap }
.mail-c-when { width:9.5rem; white-space:nowrap }
.mail-c-act  { width:9rem;   white-space:nowrap }
.mail-c-label { width:7.5rem }
/* 제목이 «남는 것을 다 먹는다». auto 로 두면 균등분배에 걸린다. */
.mail-c-subj { width:auto; overflow:hidden }
/* 🔴 첨부 아이콘이 잘린 제목 «뒤»에 있어서 셀 밖으로 밀려났다(1573px 실측).
   제목만 줄이고 아이콘은 자리를 지키게 flex 로 나눈다. */
.mail-c-subj > .mail-subj-row { display:flex; align-items:center; gap:6px; min-width:0 }
.mail-c-subj .mail-subj-text  { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0 }
/* 제목이 «읽기» 버튼을 겸한다 — 버튼 기본 모양을 걷고 글자처럼 보이게 한다.
   🔴 display:block 이어야 말줄임이 듣는다(inline-block 버튼은 내용 폭으로 늘어난다). */
.mail-c-subj .mail-subj-link  { display:block; background:none; border:0; padding:0; margin:0;
                                font:inherit; color:inherit; text-align:left; cursor:pointer; max-width:100% }
.mail-c-subj .mail-subj-link:hover { color:var(--brand); text-decoration:underline }
.mail-c-subj .mail-subj-link:focus-visible { outline:2px solid var(--brand); outline-offset:2px; border-radius:2px }
/* 삭제·완료 버튼이 든 폼 — form 기본 여백을 없애 한 줄로 선다 */
.mail-act-form { margin:0 }
/* 모달의 «원문 본문» — 줄바꿈·들여쓰기를 살리고, 긴 메일은 상자 안에서 스크롤한다.
   🔴 nl2br 이 아니라 pre-wrap 이다: 원문의 공백 정렬(표처럼 쓴 메일)이 무너지지 않게. */
.mail-original-text { white-space:pre-wrap; overflow-wrap:anywhere; max-height:50vh; overflow:auto }

/* ── 메일 상세 모달 — «머리 고정 · 본문만 스크롤» (2026-09-17 오너) ─────────────────
   🔴 DS 의 `.ds-modal__panel` 은 «패널 전체»가 스크롤하고 머리를 sticky 로 붙인다. 그런데
      sticky 는 조상의 overflow 에 따라 조용히 풀린다 — 그래서 «영역을 나눈다»:
      dialog = 세로 flex(넘침 숨김) · 머리/발 = 안 줄어듦 · 본문 = 남는 높이 + 자기 스크롤.
   🔴 `min-height:0` 이 없으면 flex 자식이 내용 높이만큼 늘어나 «본문이 스크롤하지 않고»
      dialog 밖으로 넘친다(flex 의 기본 min-height:auto).
   ⚠️ `[open]` 에만 display 를 준다 — 닫힌 dialog 에 display:flex 를 주면 «닫혀도 보인다». */
.dsx dialog.mail-modal[open] { display:flex; flex-direction:column; overflow:hidden; max-height:90vh }
.mail-modal__content { display:flex; flex-direction:column; flex:1 1 auto; min-height:0 }
.dsx .mail-modal__head { flex:none; position:static; display:block; max-height:50vh; overflow:auto }
.mail-modal__body { flex:1 1 auto; min-height:0; overflow:auto }
.mail-modal__foot { flex:none }
.mail-modal__headmain { min-width:0 }
.mail-modal__title { overflow-wrap:anywhere }
/* 보낸 사람·받는 주소·받은 때 — 한 줄에 셋, 좁으면 접힌다. 긴 주소는 줄바꿈(가로 스크롤 금지). */
.mail-modal__meta { display:flex; flex-wrap:wrap; gap:4px 20px; margin:0; font-size:13px }
.mail-modal__meta > div { display:flex; gap:6px; min-width:0 }
.mail-modal__meta dt { color:var(--text-3); white-space:nowrap }
.mail-modal__meta dd { margin:0; overflow-wrap:anywhere }
.mail-modal__sep { width:1px; align-self:stretch; background:var(--border) }
.mail-modal__more > summary { display:flex; flex-wrap:wrap; align-items:center; gap:6px; cursor:pointer; list-style:revert }
.mail-modal__morebody { margin-top:8px }
/* 분류 탭 — 좁은 화면에서 «페이지»가 아니라 «탭 줄»이 가로로 스크롤한다 */
.dsx .mail-tabs { overflow-x:auto; max-width:100%; scrollbar-width:thin }
.dsx .mail-tabs > .ds-tab { flex:none; white-space:nowrap }
/* 메일 분류 화면 — 판정 순서 목록 · 미리보기 표본 */
/* 판정 순서 — 번호 카드 9칸(2026-10-21 시안 S6 QYxBketJ9ip2HBe94vLuz8). 문장은 그대로, 배치만. 처음(위험)·끝(일반) 번호에 색. */
.mail-rule-order { list-style:none; margin:0; padding:0; display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:10px; font-size:13px; counter-reset:mro }
.mail-rule-order li { counter-increment:mro; position:relative; padding:12px 12px 12px 50px; border-radius:var(--r-md,8px); background:var(--surface-2); border:1px solid var(--border); line-height:1.5 }
.mail-rule-order li::before { content:counter(mro); position:absolute; left:12px; top:12px; width:28px; height:28px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-weight:700; font-size:12px; background:var(--brand-soft); color:var(--brand-text) }
.mail-rule-order li:first-child::before { background:var(--danger-soft); color:var(--danger-text) }
.mail-rule-order li:last-child::before { background:var(--success-soft); color:var(--success-text) }
.mail-rule-order li strong { display:block }
@media (max-width:1100px){ .mail-rule-order { grid-template-columns:repeat(2,minmax(0,1fr)) } }
@media (max-width:640px){ .mail-rule-order { grid-template-columns:1fr } }
.mail-rule-samples { margin:0; padding-left:1.1rem; font-size:13px; overflow-wrap:anywhere }

/* ── 메일 시스템 안내 (2026-09-17) ───────────────────────────────────────
   접이 머리는 한 줄로 읽히고, 좁은 화면에서는 접힌다. 목록이 밀리지 않게 기본은 «접힘». */
.mail-guide__summary { display:flex; flex-wrap:wrap; align-items:center; gap:8px; cursor:pointer; list-style:revert }
.mail-guide[open] > .mail-guide__summary { margin-bottom:4px }
.mail-guide__body { margin-top:12px }
/* 세로 칸(ds-col) 안의 버튼이 칸 전체로 늘어나지 않게 — 링크 버튼은 글자 폭만큼만. */
.mail-guide__body .ds-btn { align-self:flex-start }
.mail-guide__links { margin:0; padding-left:1.1rem; display:flex; flex-direction:column; gap:6px; font-size:13.5px }
.mail-guide__links .ds-hint { display:block }
.mail-guide__stat { min-width:150px }
.mail-guide__stat-v { font-size:17px; font-weight:700 }
/* 흐름 — 번호와 내용을 두 칸으로. 좁은 화면에서도 그대로 읽힌다(가로 스크롤 없음). */
.mail-flow { margin:0; padding:0; list-style:none; display:flex; flex-direction:column; gap:10px }
.mail-flow__step { display:flex; gap:12px; align-items:flex-start }
.mail-flow__num { flex:none; width:28px; height:28px; border-radius:var(--r-full); display:flex; align-items:center;
                  justify-content:center; font-weight:700; background:var(--brand-soft); color:var(--brand-text) }
.mail-flow__body { min-width:0 }
.mail-flow__title { display:flex; flex-wrap:wrap; align-items:center; gap:6px; margin-bottom:2px }

/* 감독 세션 표 — 🔴 수신함에서 배운 것 그대로: table-layout:fixed 는 «폭을 안 준 열»에
   남은 공간을 균등 분배한다. 전부 지정하고 하나만 auto 로 남긴다. */
.sup-c-proj  { width:9rem }
.sup-c-sess  { width:7rem;   white-space:nowrap }
.sup-c-kind  { width:5rem;   white-space:nowrap }
.sup-c-out   { width:8.5rem }
.sup-c-file  { width:8rem;   white-space:nowrap }
.sup-c-last  { width:auto;   overflow:hidden }
.sup-c-claim { width:12rem;  overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.sup-c-reg   { width:5.5rem; white-space:nowrap }

/* 긴 텍스트가 있는 목록: 셀 줄바꿈, 좁은 화면은 DS 테이블 영역에서 스크롤. */
.dsx .ds-table--wrap { min-width:64rem }
.dsx .ds-table--wrap td { white-space:normal; overflow-wrap:anywhere }

/* 긴 선택 항목이 있는 필터에서도 라벨을 한 글자씩 접지 않는다. */
.dsx .ds-filterbar--fit .ds-row { min-width:0; max-width:100% }
.dsx .ds-filterbar--fit .ds-row > span { flex-shrink:0 }
.dsx .ds-filterbar--fit .ds-select { min-width:0; max-width:100% }

/* ── /infra 인프라 관제 (2026-09-17) ───────────────────────────────────────────
   🔴 디자인 시스템을 «바꾸지 않는다». 여기 있는 것은 DS 에 없는 조각(상태 점·분포 막대·
      조치 서랍)뿐이고, 색은 전부 DS 토큰을 쓴다 — 새 색을 만들면 다른 화면과 갈라진다.
   🔴 폭은 클래스로 준다(infra-w-*). 이 사이트의 CSP 는 인라인 style 속성을 무시하므로
      서버가 계산한 값을 속성으로 넘길 방법이 없다. 5% 눈금이면 막대로 충분하다. */
.dsx .infra-dot { display:inline-block; width:8px; height:8px; border-radius:var(--r-full);
                  background:currentColor; flex:none }
.dsx .infra-badge { display:inline-flex; align-items:center; gap:6px; font-size:12px; font-weight:600;
                    padding:2px 9px; border-radius:var(--r-full); border:1px solid var(--border);
                    background:var(--surface-2); color:var(--text-2) }
.dsx .infra-badge--healthy  { background:var(--success-soft); border-color:var(--success); color:var(--success-text) }
.dsx .infra-badge--warning  { background:var(--warn-soft);    border-color:var(--warn);    color:var(--warn-text) }
.dsx .infra-badge--error    { background:var(--danger-soft);  border-color:var(--danger);  color:var(--danger-text) }
.dsx .infra-badge--unknown  { background:var(--surface-2);    border-color:var(--border);  color:var(--text-2) }
.dsx .infra-badge--excluded { background:var(--surface-2);    border-color:var(--border);  color:var(--text-3) }
.dsx .infra-dot--healthy  { color:var(--success) }
.dsx .infra-dot--warning  { color:var(--warn) }
.dsx .infra-dot--error    { color:var(--danger) }
.dsx .infra-dot--unknown  { color:var(--text-3) }
.dsx .infra-dot--excluded { color:var(--text-3) }
.dsx .infra-mini { display:inline-flex; flex-wrap:wrap; gap:4px }
/* 2026-09-28 Codex UX 검수 반영 ─────────────────────────────────────────────
   · 고정 레이아웃 표는 «폭 안 준 열»을 균등 분배한다 — 상태 배지 셋이 한 줄이면 옆 «조치» 칸을 덮었다.
     상태 열에 폭을 주고, 숫자 열은 좁게 둔다. */
.dsx .ds-table th.infra-col-state { width:300px }
.dsx .ds-table th.infra-col-n { width:84px }
/* · 서버 그룹(완전 개인 / 1stad 계열) 사이 간격 — 두 번째 제목이 앞 표에 붙어 한 표처럼 보였다. */
.dsx .infra-group-h { margin-top:24px }
/* · 누른 카드(거르는 중)는 «고른 것»으로 보여야 한다 — 문구만 바뀌면 약하다. */
.dsx .infra-kpi.is-on { outline:2px solid var(--brand); outline-offset:-2px; background:var(--brand-soft) }
/* · 모바일: 카드가 한 열로 쌓여 화면을 다 먹었다 — 두 열로. */
/* 표 칸 안에서는 배지를 한 줄로 — 줄바꿈되면 행 높이가 3배로 부푼다(duolink-27 실측 99px, 2026-09-28).
   표는 ds-table-wrap 이 가로 스크롤을 맡는다. */
.dsx .ds-table .infra-mini { flex-wrap:nowrap }

/* KPI 카드를 링크로 쓴다 — 숫자를 누르면 그 조건으로 걸러진다. */
.dsx a.infra-kpi { text-decoration:none; color:inherit; transition:border-color .12s ease, background .12s ease }
.dsx a.infra-kpi:hover { border-color:var(--brand-border); background:var(--surface-2) }
.dsx .infra-kpi--flat { cursor:default }

/* 프로젝트 인프라 › 소프트웨어 (대장 v0.5, 2026-10-23) — 시안 UzfmE6rPTGJ96DqtdseEb9. 인라인 style 은 CSP 가 막아 클래스로 둔다. */
.dsx .sw-mono { font-family:var(--font-mono); font-size:12.5px }
.dsx .sw-find { margin-top:20px; padding:20px; border:1px solid var(--border); border-radius:var(--r-lg); background:var(--surface) }
.dsx .sw-find .ds-h2 { margin-top:0 }
.dsx .sw-presets { display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-bottom:14px }
.dsx .sw-presets .ds-chip { text-decoration:none }
.dsx .sw-ver { width:140px }
.dsx .sw-result { margin-top:16px }
.dsx .sw-result__row { display:flex; flex-wrap:wrap; gap:12px; align-items:center; width:100% }
.dsx .sw-result__text { flex:1 1 360px; min-width:0 }
.dsx .sw-cols { display:flex; flex-wrap:wrap; gap:20px; margin-top:20px; align-items:flex-start }
.dsx .sw-dist { flex:1 1 300px; min-width:0; padding:18px; border:1px solid var(--border); border-radius:var(--r-lg); background:var(--surface) }
.dsx .sw-dist .ds-widget__h { margin:0 0 4px }
.dsx .sw-dist__row { display:flex; gap:10px; align-items:center; padding:6px 4px; color:var(--text); text-decoration:none; border-radius:6px }
.dsx a.sw-dist__row:hover { background:var(--surface-2) }
.dsx .sw-dist__row--none { opacity:.6 }
.dsx .sw-dist__ver { width:72px; flex:none; overflow-wrap:anywhere }
.dsx .sw-dist__n { width:40px; flex:none; text-align:right }
.dsx .sw-bar { height:8px; border-radius:999px; background:var(--surface-2); overflow:hidden; flex:1; min-width:36px }
.dsx .sw-bar > span { display:block; height:100%; background:var(--brand) }
.dsx .sw-bar--eol > span { background:var(--warn) }
.dsx .sw-w-5{width:5%}.dsx .sw-w-10{width:10%}.dsx .sw-w-15{width:15%}.dsx .sw-w-20{width:20%}.dsx .sw-w-25{width:25%}.dsx .sw-w-30{width:30%}.dsx .sw-w-35{width:35%}.dsx .sw-w-40{width:40%}.dsx .sw-w-45{width:45%}.dsx .sw-w-50{width:50%}.dsx .sw-w-55{width:55%}.dsx .sw-w-60{width:60%}.dsx .sw-w-65{width:65%}.dsx .sw-w-70{width:70%}.dsx .sw-w-75{width:75%}.dsx .sw-w-80{width:80%}.dsx .sw-w-85{width:85%}.dsx .sw-w-90{width:90%}.dsx .sw-w-95{width:95%}.dsx .sw-w-100{width:100%}
.dsx .sw-hits { flex:999 1 620px; min-width:0 }
.dsx .sw-hits > .infra-group-h:first-child { margin-top:0 }
.dsx .ds-table .sw-pkg { font-size:12px; min-width:220px }
.dsx .sw-pkg__ver { overflow-wrap:anywhere }
.dsx .sw-dist__row .ds-badge { white-space:nowrap; flex:none }
.dsx .sw-hit { font-weight:700; color:var(--danger-text); background:var(--danger-soft); border-radius:4px; padding:1px 6px }
.dsx .sw-fleet { padding:16px; border:1px solid var(--border); border-radius:var(--r-lg); background:var(--surface); margin-bottom:18px }
.dsx .sw-fleet p { margin:0 0 8px }
.dsx .sw-fleet__list > summary { display:inline-flex; list-style:none; cursor:pointer }
.dsx .sw-fleet__list > summary::-webkit-details-marker { display:none }
.dsx .sw-chips { display:flex; flex-wrap:wrap; gap:6px; margin-top:12px }
.dsx .sw-chips a.ds-chip { text-decoration:none }
.dsx .sw-howto { margin-top:24px }
.dsx .sw-drawer { position:fixed; top:0; right:0; bottom:0; z-index:60; width:min(560px, 100%); display:flex; flex-direction:column;
  background:var(--surface); border-left:1px solid var(--border); box-shadow:-8px 0 24px rgba(0,0,0,.18) }
.dsx .sw-drawer__head { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; padding:16px; border-bottom:1px solid var(--border) }
.dsx .sw-drawer__title { margin:0 }
.dsx .sw-drawer__body { flex:1; overflow:auto; padding:16px }
.dsx .sw-drawer__body .ds-table th { width:110px }
.dsx .sw-drawer__h { font-size:14px; margin:18px 0 8px }
.dsx .sw-drawer__h:first-of-type { margin-top:0 }
.dsx .sw-drawer .ds-table td { white-space:normal }
.dsx .sw-drawer .ds-table .sw-pkg { min-width:0 }
/* 탭이 넷이 되자 휴대폰 폭에서 «자/격»처럼 글자가 꺾였다 — 줄바꿈 대신 가로로 민다. */
.dsx .infra-tabs { flex-wrap:nowrap; overflow-x:auto; scrollbar-width:thin }
.dsx .infra-tabs .ds-tab { white-space:nowrap; flex:none }
.dsx .sw-drawer__foot { display:flex; flex-wrap:wrap; gap:8px; align-items:center; padding:12px 16px; border-top:1px solid var(--border) }

/* 분포 막대 — 그래프 라이브러리 없이(CSP), 가로 막대 하나로. */
.dsx .infra-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); gap:12px; margin:14px 0 }
.dsx .infra-bars { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:5px }
.dsx .infra-bars__row { margin:0 }
.dsx .infra-bars__link { display:grid; grid-template-columns:8.5rem 1fr 2.5rem; align-items:center; gap:8px;
                         padding:3px 4px; border-radius:var(--r-sm); font-size:12.5px; color:var(--text-2);
                         text-decoration:none }
.dsx a.infra-bars__link:hover { background:var(--surface-2); color:var(--text) }
.dsx .infra-bars__label { overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.dsx .infra-bars__track { height:9px; border-radius:var(--r-full); background:var(--surface-3); overflow:hidden }
.dsx .infra-bars__fill { display:block; height:100%; border-radius:var(--r-full); background:var(--brand) }
.dsx .infra-bars__fill--healthy  { background:var(--success) }
.dsx .infra-bars__fill--warning  { background:var(--warn) }
.dsx .infra-bars__fill--error    { background:var(--danger) }
.dsx .infra-bars__fill--unknown  { background:var(--text-3) }
.dsx .infra-bars__fill--excluded { background:var(--border-strong) }
.dsx .infra-bars__num { text-align:right; font-variant-numeric:tabular-nums; font-weight:700; color:var(--text) }
.dsx .infra-note { font-size:11.5px; margin:8px 2px 0 }

/* 조치 서랍 — 스크립트 없이 details 로. 큰 노란 박스를 대신한다. */
.dsx .infra-drawer { border:1px solid var(--border); border-radius:var(--r-lg); background:var(--surface);
                     margin:14px 0 }
.dsx .infra-drawer__btn { display:flex; align-items:center; gap:8px; padding:10px 14px; cursor:pointer;
                          font-size:12.5px; font-weight:700; list-style:none }
.dsx .infra-drawer__btn::-webkit-details-marker { display:none }
.dsx .infra-drawer[open] .infra-drawer__btn { border-bottom:1px solid var(--border) }
.dsx .infra-drawer__body { padding:10px 14px 14px }
.dsx .infra-drawer__h { display:flex; align-items:center; gap:7px; font-size:12px; font-weight:700;
                        margin:10px 0 6px; color:var(--text-2) }
.dsx .infra-drawer__list { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:7px }
.dsx .infra-drawer__list > li { border-left:2px solid var(--border); padding:2px 0 2px 10px }
.dsx .infra-drawer__who { display:flex; flex-wrap:wrap; gap:8px; font-size:12.5px; font-weight:600 }
.dsx .infra-drawer__what { font-size:12.5px; color:var(--text-2); line-height:1.55 }

/* 상세 표: 긴 조치 문구를 칸에 쏟지 않고 접는다. */
.dsx .infra-act > summary { cursor:pointer; font-size:12px; color:var(--brand-text); font-weight:600 }
.dsx .infra-act > div { margin-top:4px; font-size:12.5px; color:var(--text-2); line-height:1.55 }
.dsx .infra-tail { display:inline-block; margin-left:6px; padding:1px 6px; border-radius:var(--r-sm);
                   background:var(--surface-3); color:var(--text-2); font-family:var(--font-mono); font-size:11px }
.dsx .infra-stale { color:var(--warn-text) }
.dsx .infra-shown { margin:14px 0 8px }
.dsx .infra-scope, .dsx .infra-adv, .dsx .infra-total { margin:0 0 14px }
/* 탭 바로 아래 여백 — 탭 밑줄에 첫 요소가 붙어 있었다(오너 지적 2026-09-28). 두 탭 모두 같은 간격. */
.dsx .infra-tabs { margin:0 0 16px }

/* 🔴 KPI 6칸이 «한 줄»에 들어가야 한다. DS 기본(190px)이면 1440px 화면에서 5칸에서 끊겨
   여섯째가 혼자 남는다 — 요약이 두 줄로 갈라지면 한눈에 읽는다는 목적이 깨진다.
   ⚠️ `.ds-kpi` 자체를 고치지 마라 — 다른 화면의 KPI 까지 좁아진다. */
.dsx .infra-kpis { grid-template-columns:repeat(auto-fit,minmax(168px,1fr)) }
/* · 모바일: 카드가 한 열로 쌓여 화면을 다 먹었다 — 두 열로. 🔴 위 기본 규칙 «뒤»에 둬야 이긴다
     (처음엔 앞에 둬서 조용히 무시됐다 — 2026-09-28 스크린샷으로 잡음). */
@media (max-width:640px) { .dsx .infra-kpis { grid-template-columns:repeat(2,minmax(0,1fr)) } }

/* 폭 제한 없음 — 목록·표는 본문 폭을 다 쓴다(CPR-02 · 2026-10-01 오너 결정). 옛 1440 제한(요청서 §27)은 풀었다. */

/* 막대 폭 눈금 — 5% 단위. 서버가 계산한 값을 «클래스»로 받는다. */
.dsx .infra-w-0 { width:0% }
.dsx .infra-w-5 { width:5% }
.dsx .infra-w-10 { width:10% }
.dsx .infra-w-15 { width:15% }
.dsx .infra-w-20 { width:20% }
.dsx .infra-w-25 { width:25% }
.dsx .infra-w-30 { width:30% }
.dsx .infra-w-35 { width:35% }
.dsx .infra-w-40 { width:40% }
.dsx .infra-w-45 { width:45% }
.dsx .infra-w-50 { width:50% }
.dsx .infra-w-55 { width:55% }
.dsx .infra-w-60 { width:60% }
.dsx .infra-w-65 { width:65% }
.dsx .infra-w-70 { width:70% }
.dsx .infra-w-75 { width:75% }
.dsx .infra-w-80 { width:80% }
.dsx .infra-w-85 { width:85% }
.dsx .infra-w-90 { width:90% }
.dsx .infra-w-95 { width:95% }
.dsx .infra-w-100 { width:100% }

/* 상세 헤더의 «돌아가는 길» — 제목 «위»에 둔다(표까지 내려가야 보이면 늦다). */
.dsx .infra-back { margin:0 0 4px; font-size:12.5px }
.dsx .infra-back a { color:var(--text-2); text-decoration:none }
.dsx .infra-back a:hover { color:var(--brand-text); text-decoration:underline }

/* 전체 현황 묶음의 «모집단» 줄 — 상세에서는 접히고, 요약에서는 펼쳐진다. */
.dsx .infra-total__sum { display:flex; flex-wrap:wrap; align-items:baseline; gap:8px;
                         cursor:pointer; padding:6px 2px; font-size:13px; list-style:none }
.dsx .infra-total__sum::-webkit-details-marker { display:none }
.dsx .infra-total__sum::before { content:'▸'; color:var(--text-3); font-size:11px }
.dsx details[open] > .infra-total__sum::before { content:'▾' }

/* ---------- 디자인 시스템 화면 전용 견본 (/design-system) ----------
 * 🔴 값은 전부 var(--토큰) — 색·크기를 새로 발명하지 않는다. 토큰이 바뀌면 견본이 같이 바뀐다.
 *    인라인 style 은 CSP 가 막으므로 견본 칠하기는 클래스로만 한다. */
.dsx .ds-demo-chip--bg{background:var(--bg)}
.dsx .ds-demo-chip--surface{background:var(--surface)}
.dsx .ds-demo-chip--surface-2{background:var(--surface-2)}
.dsx .ds-demo-chip--surface-3{background:var(--surface-3)}
.dsx .ds-demo-chip--border{background:var(--border)}
.dsx .ds-demo-chip--border-strong{background:var(--border-strong)}
.dsx .ds-demo-chip--text{background:var(--text)}
.dsx .ds-demo-chip--text-2{background:var(--text-2)}
.dsx .ds-demo-chip--text-3{background:var(--text-3)}
.dsx .ds-demo-chip--brand{background:var(--brand)}
.dsx .ds-demo-chip--brand-hover{background:var(--brand-hover)}
.dsx .ds-demo-chip--brand-soft{background:var(--brand-soft)}
.dsx .ds-demo-chip--brand-text{background:var(--brand-text)}
.dsx .ds-demo-chip--brand-border{background:var(--brand-border)}
.dsx .ds-demo-chip--success{background:var(--success)}
.dsx .ds-demo-chip--success-soft{background:var(--success-soft)}
.dsx .ds-demo-chip--success-text{background:var(--success-text)}
.dsx .ds-demo-chip--warn{background:var(--warn)}
.dsx .ds-demo-chip--warn-soft{background:var(--warn-soft)}
.dsx .ds-demo-chip--warn-text{background:var(--warn-text)}
.dsx .ds-demo-chip--danger{background:var(--danger)}
.dsx .ds-demo-chip--danger-soft{background:var(--danger-soft)}
.dsx .ds-demo-chip--danger-text{background:var(--danger-text)}
.dsx .ds-demo-chip--info{background:var(--info)}
.dsx .ds-demo-chip--info-soft{background:var(--info-soft)}
.dsx .ds-demo-chip--info-text{background:var(--info-text)}
.dsx .ds-demo-box{width:72px;height:56px;display:flex;align-items:center;justify-content:center;background:var(--surface);border:1px solid var(--border);font-size:10px;text-align:center}
.dsx .ds-demo-r--sm{border-radius:var(--r-sm)}.dsx .ds-demo-r--md{border-radius:var(--r-md)}.dsx .ds-demo-r--lg{border-radius:var(--r-lg)}
.dsx .ds-demo-r--xl{border-radius:var(--r-xl)}.dsx .ds-demo-r--full{border-radius:var(--r-full)}
.dsx .ds-demo-sh--1{box-shadow:var(--sh-1)}.dsx .ds-demo-sh--2{box-shadow:var(--sh-2)}.dsx .ds-demo-sh--3{box-shadow:var(--sh-3)}
.dsx .ds-demo-ctl{display:flex;align-items:center;padding:0 10px;background:var(--brand-soft);border:1px solid var(--brand-border);border-radius:var(--r-md);font-size:10.5px}
.dsx .ds-demo-ctl--sm{height:var(--ctl-h-sm)}.dsx .ds-demo-ctl--md{height:var(--ctl-h)}.dsx .ds-demo-ctl--lg{height:var(--ctl-h-lg)}

/* ---------- 기능 아이콘 — lucide 인라인 SVG (CPR-14, 2026-10-01) ----------
 * 그리는 쪽 = Duolink\Core\Icon(`icon('의미이름')`). 크기·굵기 토큰은 DS 의 `.ds-ico`(16px · stroke 1.75)다.
 * 🔴 DS 의 `.dsx svg{display:block}`(명시도 0-1-1)가 `.ds-ico`(0-1-0)를 이겨 글줄 안 아이콘이
 *    줄을 끊는다 — 그래서 여기서 0-2-1 로 되돌린다. 클래스를 «썼다»가 «먹는다»가 아니다.
 * 크기 변형은 이 넷뿐이다: 기본 16 · 작은 글자 문맥 14 · 빈 상태 28 · (메뉴·아이콘 버튼은 DS 규칙 그대로). */
.dsx svg.ds-ico{display:inline-block;vertical-align:-.2em}
.dsx .ds-alert > svg.ds-ico{display:block;margin-top:1px}
.dsx .ds-btn--sm svg.ds-ico,.dsx .ds-tag svg.ds-ico,.dsx .ds-hint svg.ds-ico{width:14px;height:14px}
.dsx .ds-empty > svg.ds-ico{display:block;width:28px;height:28px;color:var(--text-3);margin-bottom:8px}

/* ---------- 시스템 구성(/system) 도식 — 서버(PHP)가 만든 SVG (가이드 admin-site §D, 2026-10-01) ----------
 * 그리는 쪽 = Duolink\Service\SystemDiagram. 🔴 모양은 «여기 클래스»가 정한다 — SVG 에 style 속성·<style> 요소를 넣지 않는다
 *    (CSP style-src 'self'+nonce 가 막는다). 색은 DS 토큰이라 라이트·다크 전환에 다시 그릴 일이 없다.
 * 🔴 가로 넘침은 도식 상자 «안에서만»(overflow-x:auto) — 페이지 전체가 옆으로 밀리면 안 된다(§D-3-8). */
.dsx .sysmap{margin:0;overflow-x:auto;max-width:100%;border:1px solid var(--border);border-radius:var(--r-md);background:var(--surface);padding:8px}
.dsx .sysmap > svg.sysmap__svg{display:block;max-width:none;height:auto}
.dsx .sysmap figcaption{margin-top:6px;font-size:12px}
.dsx .sysmap__box{fill:var(--surface-2);stroke:var(--border-strong);stroke-width:1}
.dsx .sysmap__node--in .sysmap__box{fill:var(--surface)}
.dsx .sysmap__node--web .sysmap__box{fill:var(--brand-soft);stroke:var(--brand-border)}
.dsx .sysmap__node--job .sysmap__box{fill:var(--surface-2)}
.dsx .sysmap__node--out .sysmap__box{fill:var(--surface);stroke-dasharray:4 3}
.dsx .sysmap__label{fill:var(--text);font-size:12.5px;font-weight:600}
.dsx .sysmap__sub{fill:var(--text-3);font-size:10.5px}
.dsx .sysmap__lane{fill:var(--text-3);font-size:11px;font-weight:600}
.dsx .sysmap__edge{fill:none;stroke:var(--text-3);stroke-width:1.2}
.dsx .sysmap__arrow{fill:var(--text-3)}
/* 선 레이블 — 상자보다 «마지막»에 그리고 배경 사각형을 깐다. 🔴 font-size 는 SystemDiagram::LBL_FONT 와 같아야 한다(서버가 그 크기로 겹침을 계산한다) */
.dsx .sysmap__elabel{fill:var(--text-2);font-size:10.5px;paint-order:stroke;stroke:var(--surface);stroke-width:3px;stroke-linejoin:round}
.dsx .sysmap__elabel-bg{fill:var(--surface);stroke:var(--border);stroke-width:1}
.dsx .sysmap__bound{fill:none;stroke:var(--brand-border);stroke-width:1.2;stroke-dasharray:6 4}
.dsx .sysmap__bound-label{fill:var(--brand-text);font-size:11px;font-weight:600}
.dsx .sysmap__life{stroke:var(--border-strong);stroke-width:1;stroke-dasharray:3 4}
.dsx .sysmap__steps{margin:0;padding-left:22px;display:flex;flex-direction:column;gap:4px;font-size:13px}
.dsx .sysmap code[data-internal],.dsx .ds-table code[data-internal]{color:var(--text-3)}
/* 시스템 구성 ⑥ 문서 열람(SafeMarkdown 출력) — 긴 줄·표·코드는 상자 안에서만 옆으로 민다 */
.dsx .sysdoc{display:flex;flex-direction:column;gap:10px;line-height:1.65;overflow-wrap:anywhere}
.dsx .sysdoc__code{margin:0;overflow-x:auto;max-width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:var(--r-md);background:var(--surface-2);font-size:12.5px;white-space:pre}
.dsx .sysdoc__list{margin:0;padding-left:18px;display:flex;flex-direction:column;gap:3px;list-style:disc}
/* 문서 표는 열이 적고 폭이 제각각이다 — 고정 레이아웃(.ds-table-wrap .ds-table)을 풀고 셀 안에서 접는다 */
.dsx .sysdoc .sysdoc__table{table-layout:auto;min-width:0}
.dsx .sysdoc .sysdoc__table th,.dsx .sysdoc .sysdoc__table td{white-space:normal;overflow-wrap:anywhere;vertical-align:top}
.dsx .sysdoc .sysdoc__table code{white-space:normal;overflow-wrap:anywhere}
.dsx .sysdoc__quote{margin:0;padding:6px 12px;border-left:3px solid var(--border-strong);color:var(--text-2)}
.dsx .u-copy-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
/* 시스템 구성 표 열 폭 — 🔴 table-layout:fixed 라 «전부» 지정하고 하나만 auto(위 수신함 표와 같은 이유). */
/* 폭은 «%» — rem 합이 표 폭을 넘으면 auto 열이 0 이 되어 글자가 한 자씩 접힌다(2026-10-01 실측). */
.dsx .ds-table .sys-c-name{width:12%}
.dsx .ds-table .sys-c-narrow{width:8%}
.dsx .ds-table .sys-c-mid{width:14%}
.dsx .ds-table .sys-c-wide{width:22%}  /* 주소·포트 — 긴 API 경로가 한 자씩 꺾이던 칸(2026-10-21) */
.dsx .ds-table .sys-c-state{width:22%}
.dsx .ds-table .sys-c-auto{width:auto}
.dsx .ds-table--wrap code{white-space:normal;overflow-wrap:anywhere}

/* ── 방문자 상담 채팅 위젯 (2026-10-19 · 2026-10-21 공통 디자인 · 공개 전용 — consult.js 가 만든다) ─────────────
 * 🔴 공개 사이트는 «라이트 전용»이라 위 :root 토큰(--card·--fg·--line·--primary …)만 쓴다. 리터럴 색 금지.
 * 🔴 보이기/감추기는 hidden 속성 — [hidden] 이 display 를 이기도록 명시한다(display 를 준 클래스가 덮지 않게).
 * 🔴 패널 = 머리줄(고정) / 대화 스크롤 영역(.consult-body) / 입력줄(.consult-foot, 고정) 3단. 고지문은 대화 영역 «맨 위 카드».
 * 🔴 누르는 것은 최소 36px(머리줄·보내기)·44px(확인·새 상담). 폰(≤520px)은 좌우 8px 만 남기고 가득. */
:root{--consult-shadow:0 10px 32px rgb(15 23 42 / 22%)}
.consult [hidden]{display:none!important}
.consult-fab{position:fixed;right:1.25rem;bottom:1.25rem;z-index:90;min-height:48px;padding:0 1.25rem;border:0;border-radius:999px;
  background:var(--primary);color:var(--on-primary);font:inherit;font-weight:600;cursor:pointer;box-shadow:var(--consult-shadow)}
.consult-fab:hover{background:var(--primary-strong)}
.consult-fab:focus-visible,.consult-panel button:focus-visible,.consult-panel a:focus-visible,.consult-field:focus-within,.consult-input:focus-visible{outline:none;box-shadow:var(--focus-ring)}
/* 둥근 입력 상자(.consult-field)가 초점 테두리를 그린다 — 그 안 textarea 는 테두리를 또 그리지 않는다 */
.consult-field .consult-input:focus-visible{box-shadow:none}
.consult.is-open .consult-fab{display:none}
.consult-panel{position:fixed;right:1.25rem;bottom:1.25rem;z-index:95;display:flex;flex-direction:column;
  width:min(23.75rem,calc(100vw - 2rem));height:min(40rem,calc(100vh - 2.5rem));overflow:hidden;
  background:var(--card);color:var(--fg);border:1px solid var(--line);border-radius:16px;box-shadow:var(--consult-shadow);line-height:1.5}
.consult-panel svg{flex:none}
.consult-head{display:flex;align-items:center;gap:8px;padding:12px 12px 12px 16px;border-bottom:1px solid var(--line)}
.consult-head.is-thread{padding-left:8px}
.consult-avatar{width:36px;height:36px;border-radius:12px;background:var(--primary);color:var(--on-primary);display:grid;place-items:center;flex:none}
.consult-headtext{flex:1;min-width:0}
.consult-title{margin:0;font-size:15px;font-weight:700;line-height:20px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.consult-state{margin:2px 0 0;font-size:12px;color:var(--muted-strong);display:flex;align-items:center;gap:6px}
.consult-state__text{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.consult-state.is-busy{color:var(--primary);font-weight:500}
.consult-dot{width:7px;height:7px;border-radius:50%;background:var(--success-fg);flex:none}
.consult-state.is-busy .consult-dot{background:var(--primary)}
.consult-state.is-closed .consult-dot{background:var(--muted)}
.consult-iconbtn,.consult-x{width:36px;height:36px;border:0;border-radius:10px;background:transparent;color:var(--muted-strong);display:grid;place-items:center;cursor:pointer;flex:none;padding:0}
.consult-iconbtn:hover,.consult-x:hover{background:var(--surface-soft)}
.consult-finish{height:36px;border:1px solid var(--line-strong);border-radius:10px;background:var(--card);color:var(--fg);display:flex;align-items:center;gap:6px;padding:0 10px;font:inherit;font-size:12.5px;font-weight:500;cursor:pointer;flex:none}
.consult-finish:hover{background:var(--surface-soft)}
.consult-finish[aria-pressed="true"]{border-color:var(--primary);background:var(--surface-accent);color:var(--primary)}
.consult-error{margin:0;padding:.55rem 1rem;background:var(--danger-soft);color:var(--danger);font-size:.88rem}
.consult-body{flex:1;min-height:0;overflow-y:auto;background:var(--bg);padding:16px;display:flex;flex-direction:column;gap:14px}
.consult-notice{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:12px 14px;display:flex;gap:10px;flex:none;color:var(--fg)}
.consult-notice>svg{margin-top:1px;color:var(--wait-fg)}
.consult-notice__body{font-size:12.5px;line-height:19px;color:var(--fg)}
.consult-notice__body p{margin:4px 0 0}
.consult-notice__body p:first-child{margin:0}
.consult-notice__warn{font-weight:700}
.consult-notice__links{font-size:12px;color:var(--muted-strong);margin-top:6px!important}
.consult-notice__links a{color:var(--primary)}
.consult-hint{margin:.1rem 0 0;font-size:.8rem;color:var(--muted-strong)}
.consult-row{display:flex;gap:8px;align-items:flex-end;flex:none}
.consult-ava{width:28px;height:28px;border-radius:9px;background:var(--primary);color:var(--on-primary);display:grid;place-items:center;flex:none}
.consult-col{max-width:78%;min-width:0}
.consult-who{margin:0 0 4px;font-size:11.5px;color:var(--muted-strong)}
.consult-bubble{background:var(--card);border:1px solid var(--line);border-radius:4px 14px 14px 14px;padding:10px 12px;font-size:13.5px;line-height:21px;white-space:pre-wrap;overflow-wrap:anywhere}
.consult-sub{margin:0 0 8px;font-size:12px;font-weight:700;color:var(--muted-strong)}
.consult-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
.consult-list__item{display:flex;width:100%;min-height:44px;align-items:center;gap:10px;padding:10px 12px;border:1px solid var(--line);border-radius:10px;
  background:var(--card);color:var(--fg);font:inherit;text-align:left;cursor:pointer}
.consult-list__item:hover{border-color:var(--primary)}
.consult-list__title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px}
.consult-status{flex:none;padding:2px 8px;border-radius:999px;background:var(--surface-soft);color:var(--muted-strong);font-size:11px;font-weight:500}
.consult-log{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:14px;flex:none}
.consult-msg{display:flex;gap:8px;align-items:flex-start}
.consult-msg--me{flex-direction:column;align-items:flex-end;gap:0}
.consult-msg--me .consult-bubble{max-width:78%;background:var(--primary);border-color:var(--primary);color:var(--on-primary);border-radius:14px 4px 14px 14px}
.consult-typing{display:flex;gap:8px;align-items:flex-end}
.consult-typing .consult-bubble{display:flex;gap:4px;align-items:center;padding:12px 14px}
.consult-typing__note{display:block;font-size:11px;color:var(--muted-strong);margin-top:4px}
.consult-bounce{width:6px;height:6px;border-radius:50%;background:var(--muted)}
@media (prefers-reduced-motion:no-preference){
  .consult-bounce{animation:consult-bounce 1.2s infinite ease-in-out}
  .consult-bounce:nth-child(2){animation-delay:.16s}
  .consult-bounce:nth-child(3){animation-delay:.32s}
}
@keyframes consult-bounce{0%,60%,100%{transform:translateY(0);opacity:.5}30%{transform:translateY(-4px);opacity:1}}
.consult-end{display:flex;align-items:center;gap:10px;font-size:12px;color:var(--muted-strong)}
.consult-end__line{flex:1;height:1px;background:var(--line)}
.consult-end__text{display:flex;align-items:center;gap:6px}
.consult-foot{border-top:1px solid var(--line);background:var(--card);padding:10px 12px 12px;flex:none}
.consult-form{display:flex;flex-direction:column;gap:6px}
.consult-field{display:flex;align-items:flex-end;gap:8px;border:1px solid var(--line-strong);border-radius:14px;padding:6px 6px 6px 12px}
.consult-input{width:100%;border:0;outline:none;resize:none;font:inherit;font-size:14px;line-height:22px;padding:5px 0;min-height:22px;max-height:120px;background:transparent;color:var(--fg)}
.consult-field .consult-input{flex:1;width:auto}
.consult-input[type="email"]{border:1px solid var(--line-strong);border-radius:10px;padding:8px 10px;max-height:none}
.consult-sendicon{width:36px;height:36px;border:0;border-radius:10px;background:var(--primary);color:var(--on-primary);display:grid;place-items:center;cursor:pointer;flex:none;padding:0}
.consult-sendicon:disabled,.consult-send:disabled{opacity:.6;cursor:progress}
.consult-meta{display:flex;justify-content:space-between;gap:8px;font-size:11.5px;color:var(--muted-strong)}
.consult-meta--end{justify-content:flex-end}
.consult-textbtn{border:0;background:transparent;padding:0;font:inherit;color:var(--muted-strong);cursor:pointer;display:flex;align-items:center;gap:4px}
.consult-textbtn:hover{color:var(--primary)}
.consult-label{font-size:.82rem;font-weight:600}
.consult-mailbox{display:flex;flex-direction:column;gap:4px}
.consult-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
.consult-send{min-height:44px;padding:0 1.1rem;border:0;border-radius:10px;background:var(--primary);color:var(--on-primary);font:inherit;font-size:14px;font-weight:700;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:8px}
.consult-confirm{display:flex;flex-direction:column;gap:12px}
.consult-confirm__top{display:flex;gap:12px}
.consult-confirm__icon{width:36px;height:36px;border-radius:10px;background:var(--surface-accent);color:var(--primary);display:grid;place-items:center;flex:none}
.consult-confirm__title{margin:0;font-size:14.5px;font-weight:700}
.consult-confirm__desc{margin:4px 0 0;font-size:12.5px;line-height:19px;color:var(--muted-strong)}
.consult-confirm__btns{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
.consult-ghost{height:44px;border:1px solid var(--line-strong);border-radius:10px;background:var(--card);color:var(--fg);font:inherit;font-size:14px;font-weight:500;cursor:pointer}
.consult-confirm .consult-send{height:44px}
.consult-closedfoot{display:flex;flex-direction:column;gap:8px}
.consult-closedfoot .consult-hint{text-align:center;font-size:12.5px;margin:0}
@media (max-width:520px){
  .consult-panel{inset:8px;right:8px;bottom:8px;width:auto;height:auto;max-height:none}
  .consult-fab{right:1rem;bottom:1rem}
}

/* ── 운영 현황 /ops · 운영 상세 /ops/{slug} (2026-10-21 디자인 개선 — 시안 Claude 디자인 XUA14rQhXxuXJCGa7KgzZs ①②) ──
   🔴 예전 화면은 infra-kpi__num 같은 «정의 없는» 클래스를 써서 KPI 가 맨 글자로 쌓였고, 인프라 칩을 좁은 칸에 nowrap 으로 몰아
      글자가 한 자씩 세로로 꺾였다. 축은 고정 칸 넷, 넘치는 글은 «말줄임 + title»로 둔다(자르는 게 아니라 접는다 — 상세에 전문). */
.dsx .ops-dot{display:inline-block;width:8px;height:8px;border-radius:50%;flex:none;background:var(--text-3)}
.dsx .ops-dot--danger,.dsx .ops-dot--error{background:var(--danger)}
.dsx .ops-dot--warn,.dsx .ops-dot--warning{background:var(--warn)}
.dsx .ops-dot--ok,.dsx .ops-dot--healthy{background:var(--success)}
.dsx .ops-dot--partial{background:var(--info)}
.dsx .ops-dot--unknown,.dsx .ops-dot--excluded{background:var(--border-strong)}
.dsx .ops-sec{display:flex;flex-direction:column;gap:10px;margin-bottom:20px}
.dsx .ops-sec__head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;flex-wrap:wrap;font-size:12px}
.dsx .ops-sec__title{margin:0;font-size:14px;font-weight:700;color:var(--text)}
.dsx .ops-pipes{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:12px}
.dsx .ops-pipe{display:flex;flex-direction:column;gap:6px;padding:14px 16px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);color:inherit;text-decoration:none;min-width:0}
.dsx a.ops-pipe:hover{border-color:var(--brand-border,var(--brand));background:var(--surface-2)}
.dsx .ops-pipe__head{display:flex;justify-content:space-between;align-items:flex-start;gap:8px}
.dsx .ops-pipe__head>strong{min-width:0}
.dsx .ops-pipe__head>.ds-badge{flex:none;white-space:nowrap}  /* 배지는 줄바꿈하지 않는다 — 「정 / 상」으로 쪼개졌다 */
.dsx .ops-pipe__l1{font-size:12.5px;color:var(--text)}
.dsx .ops-pipe__l2{font-size:11.5px;color:var(--text-3)}
.dsx .ops-kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;margin:16px 0 10px}
.dsx .ops-kpi{display:flex;flex-direction:column;gap:4px;padding:14px 16px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);color:inherit;text-decoration:none;min-width:0}
.dsx a.ops-kpi:hover{border-color:var(--brand-border,var(--brand));background:var(--surface-2)}
.dsx .ops-kpi--flat{cursor:default}  /* 상세의 숫자 카드 — 누르는 것이 아니다 */
.dsx .ops-kpi.is-on{outline:2px solid var(--brand);outline-offset:-2px;background:var(--brand-soft)}
.dsx .ops-kpi__l{display:flex;align-items:center;gap:8px;font-size:12.5px;font-weight:600}
.dsx .ops-kpi__l small{font-weight:500;color:var(--text-3)}
.dsx .ops-kpi__v{font-family:var(--font-mono);font-size:28px;font-weight:700;line-height:1.1;letter-spacing:-.02em}
.dsx .ops-kpi__v small{font-size:13px;font-weight:500;color:var(--text-3);letter-spacing:0}
.dsx .ops-kpi__d{font-size:11.5px;color:var(--text-3)}
.dsx .ops-list{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);overflow:hidden;margin-bottom:8px}
/* 🔴 마지막 열은 «고정 폭» — 행마다 별도 grid 라 auto 로 두면 버튼 수(2·3개)에 따라 열 위치가 행마다 어긋난다(2026-10-21 실측). */
.dsx .ops-row{display:grid;grid-template-columns:minmax(190px,1.4fr) repeat(4,minmax(0,.85fr)) minmax(0,1.1fr) minmax(0,.8fr) 176px;gap:14px;align-items:center;padding:12px 16px;border-top:1px solid var(--border)}
.dsx .ops-row:hover{background:var(--surface-2)}
.dsx .ops-row--head{border-top:0;padding:9px 16px;font-size:11.5px;font-weight:700;color:var(--text-3);background:var(--surface-2)}
.dsx .ops-grp{padding:8px 16px;border-top:1px solid var(--border);background:var(--bg);font-size:12.5px;font-weight:700}
.dsx .ops-grp>div{display:flex;align-items:center;gap:8px}
.dsx .ops-svc{display:flex;flex-direction:column;gap:3px;min-width:0}
.dsx .ops-svc__name{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:13.5px}
.dsx .ops-slug{font-size:11px;color:var(--text-3);font-weight:400}
.dsx .ops-sub{font-size:11.5px;color:var(--text-3)}
.dsx .ops-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.dsx .ops-ax{display:flex;gap:8px;align-items:flex-start;min-width:0}
.dsx .ops-ax>.ops-dot{margin-top:5px}
.dsx .ops-ax__t{display:flex;flex-direction:column;gap:1px;min-width:0}
.dsx .ops-ax__t>b{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.dsx .ops-ax__t>span{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}  /* 두 줄까지 — 전문은 title·상세 */
.dsx .ops-ax__t>b{font-size:12.5px;font-weight:600}
.dsx .ops-ax__t>span{font-size:11.5px;color:var(--text-3)}
.dsx .ops-ax--plain{padding-left:16px}  /* 점 없는 칸(방문자·오류) — 점 칸과 글 시작선을 맞춘다 */
.dsx .ops-act{display:flex;gap:6px;justify-content:flex-end;flex-wrap:nowrap}
@media (max-width:1100px){
  .dsx .ops-row{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px 16px}
  .dsx .ops-row--head{display:none}
  .dsx .ops-svc,.dsx .ops-act{grid-column:1 / -1}
  .dsx .ops-act{justify-content:flex-start}
  .dsx .ops-ax{flex-wrap:wrap}
  .dsx .ops-ax::before{content:attr(data-label);flex-basis:100%;font-size:10.5px;font-weight:700;color:var(--text-3)}
  .dsx .ops-ax__t{flex:1 1 0}  /* 점과 글이 한 줄에 — 안 주면 글 상자가 다음 줄로 떨어진다(375px 실측) */
  .dsx .ops-ax--plain{padding-left:0}
}
/* 상세 */
.dsx .ops-title{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:10px}
.dsx .ops-reasons{margin:4px 0 0;padding-left:18px}
.dsx .ops-axes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px}
.dsx .ops-axis{display:flex;flex-direction:column;gap:6px;padding:14px 16px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);color:inherit;text-decoration:none;min-width:0}
.dsx a.ops-axis:hover{border-color:var(--brand-border,var(--brand));background:var(--surface-2)}
.dsx .ops-axis__head{display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap}
.dsx .ops-axis__l1{font-size:12.5px}
.dsx .ops-axis__l2{font-size:12px;color:var(--text-3)}
.dsx .ops-two{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px;margin-bottom:16px}
.dsx .ops-two>.ds-panel{margin:0}
.dsx .ops-chart{display:flex;gap:10px}
.dsx .ops-chart__y{display:flex;flex-direction:column;justify-content:space-between;height:120px;width:44px;flex:none;text-align:right;font-family:var(--font-mono);font-size:10.5px;color:var(--text-3)}
.dsx .ops-chart__plot{flex:1;min-width:0}
.dsx .ops-chart__plot svg{width:100%;height:120px;display:block}
.dsx .ops-chart__grid{stroke:var(--border);stroke-width:1;stroke-dasharray:3 3;vector-effect:non-scaling-stroke}
.dsx .ops-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;min-height:120px;padding:12px 16px;border:1px dashed var(--border);border-radius:var(--r-md);text-align:center;font-size:12.5px;color:var(--text-3)}
.dsx .ops-empty strong{color:var(--text)}
.dsx .ops-more{margin-top:10px}
.dsx .ops-more>summary{cursor:pointer;font-size:12.5px;color:var(--brand-text);font-weight:600}

/* ── 대시보드 (2026-10-21 디자인 개선 — 시안 ③) ── */
.dsx .dash-stats{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}
.dsx .dash-sec{display:flex;flex-direction:column;gap:16px}
.dsx .dash-sec>.ds-panel{margin:0}
.dsx .dash-sec__head{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap}
.dsx .dash-sec__head .ds-h2{margin:0}
.dsx .ds-tabs.dash-seg{border:1px solid var(--border);border-radius:var(--r-lg);padding:3px;gap:2px;background:var(--surface)}
.dsx .dash-seg .ds-tab{border-bottom:0;border-radius:var(--r-md);padding:5px 12px;font-size:12.5px}
.dsx .dash-seg .ds-tab.is-active{background:var(--brand);color:#fff}
.dsx .dash-fresh{margin:0;display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:12.5px}
.dsx .dash-meta>summary{cursor:pointer;font-size:12.5px;color:var(--brand-text);font-weight:600}
.dsx .dash-meta>p{margin:8px 0 0}
.dsx .dash-funnel{display:grid;grid-template-columns:minmax(0,1fr) 36px minmax(0,1fr) 36px minmax(0,1fr) 36px minmax(0,1fr);align-items:stretch;margin-bottom:12px}
.dsx .dash-step{display:flex;flex-direction:column;gap:6px;padding:14px 16px;background:var(--surface-2);border:1px solid var(--border);border-radius:var(--r-lg);min-width:0}
.dsx .dash-arrow{display:flex;align-items:center;justify-content:center;color:var(--text-3)}
@media (max-width:900px){
  .dsx .dash-funnel{grid-template-columns:1fr;gap:4px}
  .dsx .dash-arrow{transform:rotate(90deg);height:24px}
}
.dsx .dash-grid-21{display:grid;grid-template-columns:minmax(0,2fr) minmax(260px,1fr);gap:16px}
.dsx .dash-grid-11{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px}
.dsx .dash-grid-21>.ds-panel,.dsx .dash-grid-11>.ds-panel{margin:0}
@media (max-width:900px){.dsx .dash-grid-21{grid-template-columns:1fr}}
.dsx .dash-rank{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px}
.dsx .dash-rank li{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:4px 10px;align-items:center}
.dsx .dash-rank__path{font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.dsx .dash-rank li>b{font-size:12.5px;text-align:right}
.dsx .dash-rank .dash-meter{grid-column:1 / -1}
.dsx .dash-meter{display:block;width:100%;height:6px}
.dsx .dash-meter__track{fill:var(--surface-2)}
.dsx .dash-meter__fill{fill:var(--brand)}
.dsx .dash-meter--split{height:10px;margin:12px 0 8px}
.dsx .dash-meter--split .dash-meter__track{fill:var(--border-strong)}
.dsx .dash-split{display:flex;gap:28px;flex-wrap:wrap;align-items:flex-end}
.dsx .dash-split>div{display:flex;flex-direction:column;gap:4px}
.dsx .dash-split__r{font-size:13px;font-weight:600}
.dsx .dash-cell-t{display:block;margin-top:3px;font-size:11px;max-width:16em}

/* ── 표 글자가 옆 칸으로 «겹쳐» 넘치던 부류(2026-10-21 실측: /billing SKU·크레딧 이름, /notifications 제목·허브 ID) ──
   🔴 원인: 위 `.ds-table-wrap .ds-table{table-layout:fixed}` + DS 기본 `td{white-space:nowrap}`. 고정 레이아웃은 칸 폭을
      내용에 맞춰 늘리지 않으므로, 열 폭을 안 준 표에서 긴 글이 다음 칸 위로 그려진다. 넘칠 때만 줄바꿈시킨다
      (넘치지 않는 칸은 모양이 그대로다). 일부러 한 줄로 둔 칸(.nowrap · 숫자 · 열 폭 클래스 *-c-*)은 건드리지 않는다. */
.dsx .ds-table-wrap .ds-table td:not(.nowrap):not(.ds-num):not([class*="-c-"]){white-space:normal;overflow-wrap:anywhere}

/* ── 공개 사이트 머리줄·푸터·문의·문의 완료·개인정보 (2026-10-21 디자인 개선 — 시안 Claude 디자인 TDqFCVi919uQHPxQhE9qpV) ──
   🔴 머리줄·본문(.container)·푸터가 «같은 폭»을 쓴다. 예전엔 본문 1400px·푸터 960px 가운데라 줄이 어긋났다(오너 지적 화면). */
.site-wrap{max-width:1400px;margin:0 auto;padding:0 24px;box-sizing:border-box}
.site-header{padding:0}
/* width:100% — .site-header 가 flex(관리자 머리줄과 같은 규칙)라 안 주면 이 상자가 내용 폭으로 줄어 가운데로 몰린다(2026-10-21 실측). */
.site-header__in{display:flex;align-items:center;gap:24px;min-height:64px;width:100%}
.site-header .brand{font-size:1.1rem;font-weight:800;letter-spacing:-.01em}
.site-nav a{padding:8px 12px;border-radius:8px;font-weight:500;color:#475569}
.site-nav a:hover,.site-nav a:focus-visible{color:var(--fg);background:var(--surface-soft)}
.site-nav a[aria-current="page"]{color:var(--fg);font-weight:700}
.site-nav a.site-nav__cta{margin-left:8px;white-space:nowrap;background:var(--primary);color:var(--on-primary);font-weight:700;padding:8px 16px}
.site-nav a.site-nav__cta:hover,.site-nav a.site-nav__cta:focus-visible{background:var(--primary-strong);color:var(--on-primary)}
.site-footer__in{display:flex;align-items:center;gap:24px;flex-wrap:wrap;padding-top:28px;padding-bottom:28px}
.site-footer__brand{color:var(--fg)}
.site-footer__nav{display:flex;gap:20px;flex-wrap:wrap;margin-left:auto}
.site-footer__nav a{color:#475569;font-size:.9rem;text-decoration:none}
.site-footer__nav a:hover,.site-footer__nav a:focus-visible,.site-footer__nav a[aria-current="page"]{color:var(--fg);text-decoration:underline}
@media (max-width:640px){
  .site-header__in{gap:8px;min-height:56px}
  .site-nav a{padding:8px}
  .site-nav a.site-nav__cta{margin-left:4px;padding:8px 12px}  /* 문의 입구는 이 버튼 하나뿐이라 휴대폰에서도 남긴다 */
  .site-footer__in{flex-direction:column;align-items:flex-start;gap:12px}
  .site-footer__nav{margin-left:0;gap:16px}
}

/* 문의 — 왼쪽 안내 · 오른쪽 폼 카드 */
.contact-grid{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:56px;align-items:start;max-width:1200px;margin:24px auto 0}
.contact-intro{display:flex;flex-direction:column;gap:18px;padding-top:8px}
.contact-intro .section-kicker{margin:0}
.contact-title{margin:0;font-size:clamp(1.9rem,3.2vw,2.5rem);line-height:1.2;font-weight:800;letter-spacing:-.02em}
.contact-lead{margin:0;color:#475569;font-size:1.02rem}
.contact-intro .product-note{margin:0}
.contact-tips{background:var(--card);border:1px solid var(--line);border-radius:16px;padding:22px;display:flex;flex-direction:column;gap:14px}
.contact-tips ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:14px;counter-reset:tip}
.contact-tips li{display:grid;grid-template-columns:28px 1fr;column-gap:12px;counter-increment:tip}
.contact-tips li::before{content:counter(tip);grid-row:span 2;width:28px;height:28px;border-radius:8px;background:var(--surface-accent);color:var(--primary);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:.82rem}
.contact-tips li b{font-weight:600}
.contact-tips li span{font-size:.85rem;color:var(--muted)}
.contact-chat{margin:0;display:flex;gap:10px;align-items:center;padding:14px 16px;border-radius:12px;background:var(--surface-accent);color:#1e3a8a;font-size:.9rem}
.contact-chat svg{flex:none}
.contact-form{max-width:none;background:var(--card);border:1px solid var(--line);border-radius:16px;padding:32px;box-shadow:0 1px 2px rgba(15,23,42,.04),0 8px 24px rgba(15,23,42,.06)}
.contact-row{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}
.contact-form .field-label{font-weight:600;color:var(--fg)}
.contact-form .field input,.contact-form .field textarea{border:1px solid #cbd5e1;border-radius:10px;padding:.7rem .9rem;font-size:1rem;background:var(--card)}
.contact-form .field input{min-height:46px}
.contact-form .field input:focus-visible,.contact-form .field textarea:focus-visible{outline:2px solid var(--primary);outline-offset:1px;border-color:var(--primary)}
.contact-form .check{align-items:flex-start;color:var(--fg)}
.contact-form .check input{width:18px;height:18px;margin-top:3px;accent-color:var(--primary)}
.contact-submit{display:flex;align-items:center;justify-content:center;gap:8px;min-height:48px;font-weight:700;border-radius:10px;margin-top:4px}
.contact-submit:hover,.contact-submit:focus-visible{background:var(--primary-strong);border-color:var(--primary-strong)}
@media (max-width:900px){.contact-grid{grid-template-columns:1fr;gap:28px;margin-top:8px}}
@media (max-width:600px){.contact-row{grid-template-columns:1fr;gap:0}.contact-form{padding:20px}}

/* 문의 완료 — 가운데 카드 */
.done-card{max-width:640px;margin:48px auto 0;background:var(--card);border:1px solid var(--line);border-radius:20px;padding:48px 40px;text-align:center;display:flex;flex-direction:column;align-items:center;gap:14px;box-shadow:0 8px 24px rgba(15,23,42,.06)}
.done-card .page-title{margin:0;font-size:1.9rem;font-weight:800;letter-spacing:-.02em}
.done-icon{width:64px;height:64px;border-radius:50%;background:var(--success-bg);color:var(--success-fg);display:flex;align-items:center;justify-content:center}
.done-lead{margin:0;color:#475569;font-size:1.02rem}
.done-steps{list-style:none;margin:10px 0 6px;padding:0;width:100%;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}
.done-steps li{display:flex;flex-direction:column;gap:4px;padding:16px;border-radius:12px;background:var(--bg);border:1px solid var(--line);text-align:left}
.done-steps b{font-size:.75rem;letter-spacing:.12em;color:var(--primary)}
.done-steps span{font-size:.83rem;color:var(--muted)}
.done-actions{margin:0;display:flex;gap:12px;flex-wrap:wrap;justify-content:center}
.done-actions .btn{text-decoration:none;min-height:46px;display:inline-flex;align-items:center;font-weight:700;color:var(--fg)}
.done-actions .btn-primary{color:var(--on-primary)}
@media (max-width:640px){.done-card{padding:32px 20px;margin-top:16px}.done-steps{grid-template-columns:1fr}}

/* 개인정보 처리방침 — 목차 + 구역 카드 */
.doc-head{display:flex;flex-direction:column;gap:6px;max-width:1200px;margin:16px auto 24px}
.doc-head .section-kicker,.doc-head .page-title,.doc-head .hint{margin:0}
.doc-grid{display:grid;grid-template-columns:220px minmax(0,1fr);gap:32px;align-items:start;max-width:1200px;margin:0 auto}
.doc-toc{position:sticky;top:24px;display:flex;flex-direction:column;gap:2px}
.doc-toc a{display:block;padding:8px 12px;border-radius:8px;color:#475569;text-decoration:none;font-size:.9rem}
.doc-toc a:hover,.doc-toc a:focus-visible{background:var(--surface-accent);color:var(--primary-strong)}
.doc-body{display:flex;flex-direction:column;gap:16px;min-width:0}
.doc-sec{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:24px 28px;scroll-margin-top:24px}
.doc-sec h2{margin:0 0 10px;font-size:1.2rem}
.doc-sec ul{padding-left:20px}
.doc-sec li+li{margin-top:8px}
.doc-sec .table{min-width:0;margin-top:10px}
/* 🔴 전역 th,td{white-space:nowrap} 때문에 이전 표가 375px 화면에서 659px 로 벌어져 문서 전체가 가로로 넘쳤다(2026-10-21 실측) — 이 표만 줄바꿈. */
.doc-sec .table th,.doc-sec .table td{white-space:normal;overflow-wrap:anywhere;vertical-align:top}
.doc-sec .table th{width:8.5em}
@media (max-width:900px){
  .doc-grid{grid-template-columns:1fr;gap:16px}
  .doc-toc{position:static;flex-direction:row;flex-wrap:wrap;gap:6px}
  .doc-toc a{border:1px solid var(--line);background:var(--card);padding:6px 12px}
  .doc-sec{padding:20px}
}

/* 소개 — 「일하는 방식」 카드 셋 (2026-10-21 시안 XAckzJFQEXWKbo8YkwJS5u ①). 문장은 그대로, 배치만. */
.section-head .btn{flex:none;text-decoration:none}
.about-ways{list-style:none;margin:0 0 2.5rem;padding:0;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}
.about-ways li{background:var(--card);border:1px solid var(--line);border-radius:16px;padding:24px;display:flex;flex-direction:column;gap:10px}
.about-ways__ic{width:40px;height:40px;border-radius:10px;background:var(--surface-accent);color:var(--primary);display:flex;align-items:center;justify-content:center}
.about-ways b{font-size:.78rem;letter-spacing:.12em;color:var(--muted)}
.about-ways p{margin:0;color:var(--fg)}
@media (max-width:900px){.about-ways{grid-template-columns:1fr}}

/* ── 관리자 목록형 표 — 열 폭 클래스 (2026-10-21 시안 S5 CBGmea16joWLcq4kHFgYqP) ──
   🔴 .ds-table-wrap 의 표는 table-layout:fixed 라, 폭을 안 준 열은 남은 폭을 «똑같이» 나눈다. 그래서 긴 칸(제품·내용·request_id)이
      좁아져 세로로 꺾였다. 짧은 칸에만 폭을 주고 긴 칸은 비워 둔다 — 비운 칸이 남는 폭을 다 쓴다. */
.dsx .ds-table th.w-4{width:4rem}.dsx .ds-table th.w-5{width:5rem}.dsx .ds-table th.w-6{width:6rem}.dsx .ds-table th.w-7{width:7rem}
.dsx .ds-table th.w-8{width:8rem}.dsx .ds-table th.w-9{width:9rem}.dsx .ds-table th.w-10{width:10rem}.dsx .ds-table th.w-11{width:11.5rem}
.dsx .ds-table th.w-12{width:12rem}.dsx .ds-table th.w-14{width:14rem}.dsx .ds-table th.w-15{width:15rem}.dsx .ds-table th.w-18{width:18rem}
/* 한 줄 말줄임 — 전문은 title(감사 로그 내용). 해석하지 않고 원문 그대로 둔다. */
.dsx .ds-table td.ellip{max-width:0}
.dsx .ds-table td.ellip code{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;word-break:normal}

/* 보안 — 요약 카드와 설명 패널 간격(2026-10-21 시안 S5 ④) */
.dsx .sec-kpi{margin:8px 0 16px}
.dsx .sec-panel{margin-bottom:16px}
