/* 공통 레이아웃·컴포넌트.
 *
 * 색·크기·간격은 전부 `tokens.css` 의 변수를 쓴다. 여기에 hex 를 직접 적지 않는다.
 * 한국어 조판: 어절 단위 줄바꿈(word-break: keep-all) · 본문 17px/1.7 · 13px 미만 금지.
 */

*,
*::before,
*::after {
  box-sizing: border-box;
}

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

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  font-weight: var(--fw-regular);
  line-height: var(--lh-body);
  word-break: keep-all;
  overflow-wrap: anywhere;
  color: var(--ink);
  background: var(--canvas-soft);
}

h1,
h2,
h3,
p,
ul,
ol,
figure {
  margin: 0;
}

ul,
ol {
  padding: 0;
  list-style: none;
}

a {
  color: var(--primary);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

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

:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: var(--space-sm) var(--space-lg);
  background: var(--canvas);
  border: 1px solid var(--primary);
  border-radius: var(--radius-sm);
}

.skip-link:focus {
  left: var(--space-md);
  top: var(--space-md);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --- 상단바 ---------------------------------------------------------------- */

.topbar {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  flex-wrap: wrap;
  min-height: var(--topbar-height);
  padding: var(--space-sm) var(--space-xl);
  background: var(--canvas);
  border-bottom: 1px solid var(--hairline);
}

.topbar__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
}

.topbar__brand:hover {
  text-decoration: none;
}

.topbar__logo {
  height: 28px;
  width: auto;
}

/* 라이트/다크 로고 교체(2026-08-19 사용자 승인 · 확정). 어느 쪽이 보일지는
 * tokens.css 가 정한다 — 여기에 테마 분기를 두지 않는다. 브랜드 잉크(딥네이비)는
 * 다크 상단바 위에서 명도비 1.03 으로 사라지므로 다크에서는 반전 로고를 쓴다.
 * 양쪽 모두 같은 alt 를 달아 어느 테마에서든 접근 가능한 이름이 남는다. */
.topbar__logo--standard {
  display: var(--logo-standard-display);
}

.topbar__logo--reversed {
  display: var(--logo-reversed-display);
}

.topbar__spacer {
  flex: 1 1 auto;
}

/* 화면 테마 전환 — 시스템 설정 따름 / 라이트 / 다크.
 * JS 가 꺼져 있으면 `hidden` 이 그대로 남아 눌러도 아무 일 없는 버튼을 남기지 않는다.
 * 아이콘만 두지 않고 한국어 라벨을 그대로 노출한다(13px 하한 준수). */
.theme-switch {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xxs);
  padding: var(--space-xxs);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  background: var(--canvas-soft);
}

.theme-switch[hidden] {
  display: none;
}

.theme-switch__option {
  min-height: 38px;
  padding: 0 14px;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink-mute);
  font-family: inherit;
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
}

.theme-switch__option:hover {
  color: var(--ink-secondary);
}

.theme-switch__option[aria-pressed="true"] {
  background: var(--canvas);
  border-color: var(--hairline-table);
  color: var(--ink-secondary);
}

.topbar__user {
  display: inline-flex;
  align-items: center;
  gap: var(--space-md);
  flex-wrap: wrap;
  font-size: var(--fs-body-sm);
  color: var(--ink-secondary);
}

.topbar__username {
  font-weight: var(--fw-medium);
}

.topbar__department {
  color: var(--ink-mute);
}

/* 미확인 알림 배지 — 숫자만으로 두지 않고 "미확인 N건" 을 함께 읽히게 한다. */
.notice-badge {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-xs);
  padding: 3px 10px;
  border: 1px solid var(--tint-rose-ink);
  border-radius: var(--radius-pill);
  background: var(--tint-rose);
  color: var(--tint-rose-ink);
  font-size: var(--fs-caption);
  font-variant-numeric: tabular-nums;
}

.notice-badge--none {
  border-color: var(--hairline-table);
  background: var(--tint-gray);
  color: var(--tint-gray-ink);
}

.notice-badge__count {
  font-weight: var(--fw-bold);
}

/* --- 주 메뉴 (상단바 안 · 로고와 같은 행) ----------------------------------
 * 2026-08-20 개정 — 좌측 사이드바를 걷어내고 상단 2depth 메뉴로 옮겼다.
 *
 * **스크립트가 없어도 열린다.** 좁은 화면 토글(바깥 `<details>`)과 2depth(안쪽
 * `<details>`)를 전부 네이티브 요소로 만들었다. `mainnav.js` 는 바깥 클릭·Escape 로
 * 닫는 편의만 얹는다 — 없어도 메뉴는 동작한다.
 *
 * 이 파일은 데스크톱 우선이다. 여기(기본 캐스케이드)가 **넓은 화면**이고, 좁은 화면은
 * 아래 `@media (max-width: 1099px)` 에서 되돌린다.
 * ------------------------------------------------------------------------- */

.mainnav {
  min-width: 0;
}

/* 넓은 화면에는 토글이 없다 — 패널이 늘 펼쳐져 있다. */
.mainnav__toggle {
  display: none;
}

/* 닫힌 `<details>` 안에 패널이 갇히지 않게 두 엔진 모두를 푼다.
 * 구형(Firefox·Safari)은 UA 의 `display: none` 을 작성자 규칙이 덮고,
 * 신형(Chromium)은 `::details-content` 의 `content-visibility` 를 풀어야 한다.
 * 이 선택자를 모르는 엔진은 규칙 하나를 통째로 무시하므로 서로를 방해하지 않는다. */
.mainnav__panel {
  display: block;
}

.mainnav::details-content {
  content-visibility: visible;
}

.mainnav__list {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  flex-wrap: wrap;
  list-style: none;
}

/* 2depth 패널의 위치 기준. */
.mainnav__item {
  position: relative;
}

.mainnav__link,
.mainnav__summary,
.usermenu__trigger {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  padding: 9px 12px;
  border-radius: var(--radius-md);
  font-size: var(--fs-body-sm);
  color: var(--ink-mute);
  white-space: nowrap;
}

/* 네이티브 삼각형을 지우고 아래 `::after` 캐럿으로 갈음한다. */
.mainnav__summary,
.mainnav__toggle,
.usermenu__trigger {
  list-style: none;
  cursor: pointer;
}

.mainnav__summary::-webkit-details-marker,
.mainnav__toggle::-webkit-details-marker,
.usermenu__trigger::-webkit-details-marker {
  display: none;
}

/* **글리프를 쓰지 않는다.** 폰트에 없는 문자나 뭉개진 이스케이프는 CSS 로는 유효해서
 * 화면에서만 드러난다 — 실제로 프로필 캐럿이 U+25BE(▾) 이스케이프를 잃고 제어문자
 * 0x15 + "BE" 로 배포까지 갔다(2026-08-23). 테두리로 그리면 그 부류가 생기지 않는다. */
.mainnav__summary::after,
.usermenu__trigger::after {
  content: "";
  width: 6px;
  height: 6px;
  margin-left: var(--space-xxs);
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform 150ms ease;
}

.mainnav__group[open] > .mainnav__summary::after,
.usermenu[open] > .usermenu__trigger::after {
  transform: translateY(1px) rotate(-135deg);
}

/* hover 어포던스는 **실제로 갈 수 있는 곳**에만 준다. 비활성 항목까지 밝아지면
 * 눌러도 아무 일 없는 메뉴를 누르게 된다. */
a.mainnav__link:hover,
.mainnav__summary:hover,
.usermenu__trigger:hover {
  background: var(--canvas-soft);
  color: var(--ink-secondary);
  text-decoration: none;
}

.mainnav__link[aria-current="page"] {
  background: var(--primary-subdued);
  color: var(--primary-deep);
  font-weight: var(--fw-medium);
}

/* 2depth 가 현재 위치면 접혀 있는 1depth 도 함께 켠다 — 어느 묶음에 있는지가 보여야
 * 사용자가 자기 위치를 안다. `:has()` 를 모르는 엔진에서는 표시만 빠지고 메뉴는 성립한다. */
.mainnav__group:has(.mainnav__sublink[aria-current="page"]) > .mainnav__summary {
  color: var(--primary-deep);
  font-weight: var(--fw-medium);
}

/* 아직 화면이 없는 메뉴. 링크가 아니라 예고다.
 *
 * ⚠ **P8 현재 이 규칙을 쓰는 항목은 없다.** 마지막 사용처였던 `STR·CTR (M6 예정)` 이
 * K-12(STR·CTR 범위 밖)에 따라 메뉴에서 제거됐다 — 오지 않을 화면을 예고하는 항목은
 * 「없는 화면은 링크하지 않는다」가 막으려던 것보다 나쁘다. 규칙 자체는 유효하므로
 * (다음에 화면 없는 메뉴가 생기면 이 모양으로 그린다) 선언을 지우지 않고 남긴다.
 *
 * 색만으로 비활성을 알리지 않고 도착 마일스톤을 텍스트로 병기한다(AC-34 와 같은 원칙).
 * `opacity` 로 흐리면 명도비가 같이 무너지므로 전용 잉크 토큰을 쓴다 — `--ink-placeholder`
 * 는 조용하되 흰 면 위에서 AA(4.5)를 지키는 자리다. */
.mainnav__link--soon {
  color: var(--ink-placeholder);
  cursor: default;
}

.mainnav__soon {
  flex: 0 0 auto;
  padding: 2px 8px;
  border: 1px solid var(--hairline-table);
  border-radius: var(--radius-pill);
  background: var(--tint-gray);
  color: var(--tint-gray-ink);
  font-size: var(--fs-caption);
  line-height: 1.45;
  white-space: nowrap;
}

/* --- 2depth 패널 -----------------------------------------------------------
 * 새 표면이다 — 면·테두리·그림자를 전부 토큰으로 잡는다. 다크에서 `--elevation-raised`
 * 는 값이 달라지고 `--hairline` 이 카드 경계 하한을 지키므로 선으로도 분리된다. */

.mainnav__sub,
.usermenu__panel {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 60;
  min-width: 208px;
  padding: var(--space-sm);
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--elevation-raised);
  list-style: none;
}

/* 닫힌 상태의 감춤은 `display` 하나로만 가른다 — 신형 엔진의 `content-visibility` 가
 * 그 위에 겹치면 `:hover`·`:focus-within` 이 통하지 않는다. */
.mainnav__group::details-content {
  content-visibility: visible;
}

/* **`:hover` 만 걸면 마우스 없는 사용자에게는 메뉴가 없는 것과 같다.**
 * `:focus-within` 은 `<summary>` 에 포커스가 닿는 순간 열리고, 열린 뒤에는 하위 링크가
 * 탭 순서에 들어와 포커스가 유지된다. 클릭은 네이티브 `[open]` 이 맡는다. */
.mainnav__group:hover > .mainnav__sub,
.mainnav__group:focus-within > .mainnav__sub,
.mainnav__group[open] > .mainnav__sub {
  display: block;
}

.mainnav__sublink,
.usermenu__link {
  display: block;
  width: 100%;
  padding: 9px 12px;
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--ink-secondary);
  font-family: inherit;
  font-size: var(--fs-body-sm);
  line-height: var(--lh-compact);
  text-align: left;
  white-space: nowrap;
}

.mainnav__sublink:hover,
.usermenu__link:hover {
  background: var(--canvas-soft);
  text-decoration: none;
}

.mainnav__sublink--button,
.usermenu__link--button {
  cursor: pointer;
}

.mainnav__sublink[aria-current="page"],
.usermenu__link[aria-current="page"] {
  background: var(--primary-subdued);
  color: var(--primary-deep);
  font-weight: var(--fw-medium);
}

/* --- 프로필 드롭다운 (상단바 우측 · M5 DC-92 · 2026-08-23 개정) --------------
 * **주 메뉴와 같은 UI 다** — 위 규칙들이 `.usermenu__*` 를 함께 싣는다(사용자 요청).
 * 한쪽만 손보면 두 메뉴가 갈리므로 값을 복사해 두지 않았다. 여기 남는 것은 주 메뉴와
 * **다를 수밖에 없는 것**뿐이다: 오른쪽 정렬 · 여는 조건 · 부서/직급 줄.
 *
 * **여는 방법이 주 메뉴와 다르다.** 주 메뉴는 `:hover`·`:focus-within` 으로도 열리지만
 * 프로필은 **클릭으로만** 연다(2026-08-23 사용자 요청). 열고 닫는 일은 네이티브
 * `<details>` 가 하므로 JS 는 필요 없고, `<summary>` 는 포커스를 받아 Enter·Space 로
 * 토글되므로 `:focus-within` 을 지워도 **마우스 없는 사용자의 경로가 남는다.**
 * ------------------------------------------------------------------------- */

.usermenu {
  position: relative;
}

/* 상단바 오른쪽 끝에 붙는다 — 주 메뉴처럼 왼쪽 기준이면 화면 밖으로 넘친다. */
.usermenu__panel {
  left: auto;
  right: 0;
}

.usermenu[open] > .usermenu__panel {
  display: block;
}

/* 닫혀 있는 동안은 패널을 렌더 트리에서도 내린다. 주 메뉴는 `[open]` 없이 hover 로
 * 열리므로 늘 `visible` 이어야 하지만, 클릭 전용인 이 메뉴는 그럴 이유가 없다. */
.usermenu::details-content {
  content-visibility: hidden;
}

.usermenu[open]::details-content {
  content-visibility: visible;
}

.usermenu__name {
  font-weight: var(--fw-medium);
}

/* 주 메뉴에는 없는 줄 — 부서·직급. 항목과 좌우 여백을 맞춰 같은 열에 선다. */
.usermenu__meta {
  margin: 0 0 var(--space-xs);
  padding: 0 12px;
  color: var(--ink-mute);
  font-size: var(--fs-caption);
  line-height: var(--lh-compact);
  word-break: keep-all;
}

.usermenu__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* --- 셸 (콘텐츠 1단) ------------------------------------------------------- */

.shell {
  display: block;
  min-height: calc(100vh - var(--topbar-height));
}

/* 폭을 가두지 않고 화면 전체를 쓴다(2026-08-20 개정). 대신 좌우 여백을 화면폭에 따라
 * 부드럽게 준다 — 좁은 화면에서 글이 가장자리에 붙지 않고, 넓은 화면에서 표가 숨 쉰다. */
.content {
  width: 100%;
  min-width: 0;
  padding: var(--space-xl) clamp(var(--space-lg), 3vw, var(--space-section));
}

/* --- 페이지 머리 ----------------------------------------------------------- */

.page-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-lg);
  flex-wrap: wrap;
  margin-bottom: var(--space-xl);
}

.page-title {
  font-size: var(--fs-display-lg);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-display);
}

.page-subtitle {
  font-size: var(--fs-body-sm);
  color: var(--ink-mute);
}

.section-title {
  font-size: var(--fs-heading-lg);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-heading);
}

.card-title {
  font-size: var(--fs-heading-sm);
  font-weight: var(--fw-medium);
  line-height: 1.5;
}

.text-mute {
  color: var(--ink-mute);
}

.text-sm {
  font-size: var(--fs-body-sm);
}

.caption {
  font-size: var(--fs-caption);
  line-height: 1.45;
  color: var(--ink-mute);
}

/* --- 카드 ------------------------------------------------------------------ */

.card {
  padding: var(--space-xl);
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--elevation-card);
}

.card + .card,
.card + .stack,
.stack + .card {
  margin-top: var(--space-xl);
}

.card__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
  flex-wrap: wrap;
  margin-bottom: var(--space-lg);
}

.card__note {
  margin-top: var(--space-lg);
}

.card-quiet {
  padding: var(--space-lg);
  background: var(--surface-inset);
  border: 1px solid var(--hairline-soft);
  border-radius: var(--radius-lg);
}

.stack > * + * {
  margin-top: var(--space-md);
}

.empty-state {
  padding: var(--space-xxl);
  background: var(--surface-inset);
  border: 1px dashed var(--hairline-input);
  border-radius: var(--radius-lg);
  color: var(--ink-mute);
  font-size: var(--fs-body-sm);
  text-align: center;
}

/* H-01 홈 위젯 ② 막대그래프 자리. **비어 있어도 화면이 성립한다** — 같은 값을 바로
 * 아래 표가 서버 렌더로 이미 보여 준다. Plotly 가 안 오면 이 상자만 접힌다. */
.chart-frame {
  min-height: 0;
  margin-bottom: var(--space-md);
}

.chart-frame:empty {
  display: none;
}

/* --- 버튼 (전부 알약 · 화면당 채워진 버튼 하나) ---------------------------- */

/* 행동 막대의 오른쪽 묶음 — 이동 계열과 **데이터를 바꾸는 행동**을 갈라 놓는다
 * (2026-09-18 요청 ⑮). 같은 줄이되 손이 가는 거리가 다르다. */
.button-row__end {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-left: auto;
}


.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  min-height: 44px;
  padding: 10px 20px;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  background: var(--canvas);
  color: var(--ink-secondary);
  font-family: inherit;
  font-size: var(--fs-button);
  font-weight: var(--fw-medium);
  line-height: 1;
  cursor: pointer;
}

.button:hover {
  text-decoration: none;
}

.button:disabled,
.button[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

.button--primary {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--on-primary);
}

.button--primary:active {
  background: var(--primary-press);
  border-color: var(--primary-press);
}

.button--secondary {
  background: var(--canvas);
  border-color: var(--primary);
  color: var(--primary);
}

.button--neutral {
  background: var(--canvas);
  border-color: var(--hairline);
  color: var(--ink-secondary);
  font-size: var(--fs-button-sm);
}

.button--ghost {
  background: transparent;
  border-color: transparent;
  color: var(--ink-mute);
  font-size: var(--fs-button-sm);
}

.button--danger {
  background: var(--canvas);
  border-color: var(--hairline);
  color: var(--semantic-error);
  font-size: var(--fs-button-sm);
}

.button--link {
  min-height: 0;
  padding: 0;
  background: transparent;
  border-color: transparent;
  color: var(--primary);
  font-size: var(--fs-body-sm);
}

.button-row {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  flex-wrap: wrap;
}

/* 비활성 버튼 + 사유를 **세로로** 쌓는다(전수 검수). 한 줄에 나란히 두면 긴 사유가
   행동막대의 버튼 리듬을 끊는다. */
.detail-bar__locked--stacked {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-xxs);
}

/* --- 폼 -------------------------------------------------------------------- */

.form-grid {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.field__label {
  display: block;
  margin-bottom: var(--space-xs);
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  line-height: 1.4;
  color: var(--ink-secondary);
}

.field__required {
  margin-left: var(--space-xxs);
  color: var(--semantic-error);
}

.field input[type="text"],
.field input[type="password"],
.field input[type="email"],
.field input[type="number"],
.field input[type="tel"],
.field input[type="date"],
.field select,
.field textarea {
  width: 100%;
  min-height: var(--control-height);
  padding: 11px 14px;
  background: var(--canvas);
  border: 1px solid var(--hairline-input);
  border-radius: var(--radius-sm);
  color: var(--ink);
  font-family: inherit;
  font-size: var(--fs-body);
  line-height: var(--lh-compact);
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-subdued);
}

/* 입력이 둘인 한 항목 (W-01 생년월일 = 캘린더 + 부분입력).
   좁은 화면에서는 두 칸이 위아래로 쌓인다 — 가로로 눌러 담으면 캘린더 위젯이 잘린다. */
.field__pair {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-md);
}

.field__pair-item {
  flex: 1 1 180px;
  min-width: 0;
}

.field__sublabel {
  display: block;
  margin-bottom: var(--space-xxs);
  font-size: var(--fs-caption);
  font-weight: var(--fw-regular);
  line-height: 1.4;
  color: var(--ink-mute);
  word-break: keep-all;
}

.field__help {
  margin-top: var(--space-xs);
  font-size: var(--fs-caption);
  color: var(--ink-mute);
}

.field__error {
  margin-top: var(--space-xs);
  font-size: var(--fs-body-sm);
  color: var(--semantic-error);
}

.field--error input,
.field--error select {
  border-color: var(--semantic-error);
}

.form-errors {
  margin-bottom: var(--space-lg);
  padding: var(--space-md) var(--space-lg);
  background: var(--tint-rose);
  border: 1px solid var(--tint-rose-ink);
  border-radius: var(--radius-md);
  color: var(--tint-rose-ink);
  font-size: var(--fs-body-sm);
}

.form-actions {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  flex-wrap: wrap;
  margin-top: var(--space-xl);
}

.form-actions__spacer {
  flex: 1 1 auto;
}

/* --- 표 (행 구분은 hairline-table, 열 구분은 한 단계 약하게) --------------- */

.table-scroll {
  overflow-x: auto;
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--canvas);
  font-size: var(--fs-body-sm);
}

/* **머리글은 값과 같은 축에 선다**(2026-09-18 전수 검수).
 *
 * 종전 기본값은 `center` 였다. 값은 텍스트면 왼쪽, 숫자면 오른쪽이라 **열 이름과 값이
 * 서로 다른 선**에 섰고, 숫자 열에서 특히 나빴다 — 자릿수를 오른쪽으로 맞춰 놓고 그
 * 이름은 가운데 떠 있으면 눈이 두 번 움직인다.
 *
 * 기본을 왼쪽으로 두고 숫자 열만 오른쪽으로 되돌린다(`.num` 규칙이 `th` 에도 걸린다).
 * 가운데 정렬이 필요한 표는 자기 클래스로 다시 정한다(`.attachment-matrix`). */
.data-table th {
  padding: var(--space-md);
  background: var(--canvas-soft);
  border-bottom: 1px solid var(--hairline-table);
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  color: var(--ink-secondary);
  text-align: left;
  white-space: nowrap;
}

.data-table td {
  padding: var(--space-md);
  border-bottom: 1px solid var(--hairline-table);
  vertical-align: top;
}

.data-table tbody tr:hover {
  background: var(--canvas-soft);
}

/* 건수 칸에 붙는 한정 문구는 숫자와 **줄을 나눈다.** 붙여 놓으면 `2대조 절단` 이
   한 낱말로 읽히고, 우측 정렬이라 숫자가 문구에 밀려 자릿수 정렬도 깨진다. */
.data-table td.num .caption {
  display: block;
  text-align: right;
}

/* 금액·점수·건수 — 자릿수를 세로로 정렬한다. **머리글도 같이 간다**(위 §머리글 주석). */
.data-table th.num {
  text-align: right;
}

.data-table .num,
.metric,
.tabular {
  font-size: var(--fs-tabular);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--ls-tabular);
  text-align: right;
}

/* 숫자를 낼 수 없는 카운터. **숫자가 아니라 사실을 적은 칸**이므로 자릿수 정렬을 풀고
   본문 서체로 되돌린다 — `미조회`(보러 가지 않았다)나 `대조 못함`(보러 갔는데 못 봤다)이
   우측 정렬된 숫자처럼 보이면 그것이 곧 값으로 읽힌다. 색으로 구분하지 않는다(문구가
   이미 상태를 말한다). */
.tabular.is-not-a-number {
  font-variant-numeric: normal;
  letter-spacing: normal;
  color: var(--ink-mute);
}

.metric {
  font-size: var(--fs-metric-xl);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-display);
}

/* 탐지 칩 — 이력 그리드의 「탐지정보」 칸(2026-09-18 요청 ③). 소스와 점수를 한 덩어리로
 * 묶어 여러 건이 섞여 읽히지 않게 한다. 배지가 아니라 칩인 이유는 **판정이 아니기
 * 때문**이다: 색으로 등급을 말하지 않는다. */
.detection-chip {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-xs);
  margin: 0 var(--space-xs) var(--space-xs) 0;
  padding: var(--space-xxs) var(--space-sm);
  background: var(--canvas-soft);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  font-size: var(--fs-caption);
}

.detection-chip__source {
  color: var(--ink-secondary);
}

.detection-chip__score {
  font-weight: var(--fw-semibold);
  color: var(--ink);
}

/* === W-02 검색결과 — 요약 배지 + 탐지 카드 (2026-09-18 요청 ⑤) ==============
 *
 * 참조 이미지 `docs/watchlist.png`. 표로 늘어놓던 결과를 **한 건 = 한 카드**로 되돌린다.
 * 카드가 말하는 순서는 왼쪽부터 *얼마나 센가(점수) → 무엇인가(배지·이름) → 무엇이
 * 걸렸나(일치/미대조)* 이고, 그 순서는 요청 ⑤-3 의 「입력정보가 어느 소스에 어떻게
 * 일치하는지 중심」을 그대로 옮긴 것이다.
 *
 * **색만으로 말하는 자리가 없다.** 점수 막대 옆에는 늘 숫자가, 배지 색 안에는 늘 낱말이
 * 있다(DESIGN.md §접근성 · AC-34 와 같은 규약). */

.result-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
  margin: 0 0 var(--space-lg);
}

.result-summary__badge {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-xs);
  padding: var(--space-xs) var(--space-md);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  font-size: var(--fs-label);
  font-weight: var(--fw-semibold);
}

/* 괄호 안 보조 문구 — 같은 배지 안에서 한 단계 약하게. 낱말을 지우지 않는 이유는
   `일치` 와 `주의` 가 각각 **정확일치·일부포함**이라는 사실을 배지 밖에서 다시 찾게
   하지 않기 위해서다. */
.result-summary__hint {
  margin-left: var(--space-xxs);
  font-size: var(--fs-caption);
  font-weight: var(--fw-regular);
  opacity: 0.85;
}

.result-summary__badge--hit {
  background: var(--tint-rose);
  border-color: var(--tint-rose-ink);
  color: var(--tint-rose-ink);
}

.result-summary__badge--caution {
  background: var(--tint-amber);
  border-color: var(--tint-amber-ink);
  color: var(--tint-amber-ink);
}

.result-summary__badge--top,
.result-summary__badge--total {
  background: var(--canvas-soft);
  color: var(--ink-secondary);
}

.result-summary__subject {
  margin-left: auto;
  color: var(--ink-secondary);
  font-size: var(--fs-body-sm);
}

.hit-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* 카드 — 왼쪽 점수 기둥 + 본문. 막대는 카드 아래를 가로지른다(격자의 3열이 아니라
   `grid-column: 1 / -1`). 좁은 화면에서는 기둥이 본문 위로 접힌다. */
.hit-card {
  display: grid;
  grid-template-columns: minmax(96px, auto) minmax(0, 1fr);
  gap: var(--space-md) var(--space-lg);
  padding: var(--space-lg);
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-left: 3px solid var(--tint-amber);
  border-radius: var(--radius-lg);
}

.hit-card__score {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
}

.hit-card__score-value {
  font-size: var(--fs-metric-lg);
  font-weight: var(--fw-semibold);
  line-height: 1.1;
  color: var(--ink);
}

.hit-card__score-unit {
  font-size: var(--fs-caption);
  color: var(--ink-mute);
}

.hit-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  min-width: 0;
}

.hit-card__badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

.hit-card__name {
  font-size: var(--fs-heading-sm);
  font-weight: var(--fw-semibold);
  color: var(--ink);
  text-decoration: none;
}

.hit-card__name:hover,
.hit-card__name:focus {
  color: var(--primary);
  text-decoration: underline;
}

.hit-card__facts,
.hit-card__siblings {
  margin: 0;
  color: var(--ink-secondary);
  font-size: var(--fs-body-sm);
}

/* 대상명 + 배지 한 줄. 이름이 먼저 서고 배지가 그 뒤를 따라 감싼다 — 배지를 윗줄에
   따로 두면 이름이 한 칸 아래로 밀리고, 카드마다 그 한 칸이 쌓인다.
   `baseline` 정렬이라 글자 크기가 다른 이름과 배지가 같은 선에 앉는다. */
.hit-card__headline {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-xs) var(--space-sm);
}

/* 대조하지 못한 것이 **있을 때만** 서는 줄(AC-562). 있을 때는 눈에 띄어야 하므로
   사실 줄(`__facts`)과 같은 회색으로 두지 않는다 — 이 줄의 존재 자체가 신호다.
   색만으로 말하지 않는다: 낱말(`대조하지 못함`)이 언제나 함께 선다. */
.hit-card__uncompared {
  margin: 0;
  font-size: var(--fs-body-sm);
  color: var(--semantic-warning);
}

.hit-card__meter {
  grid-column: 1 / -1;
  height: 6px;
  background: var(--hairline-soft);
  border-radius: var(--radius-pill);
  overflow: hidden;
}

.hit-card__meter-fill {
  display: block;
  height: 100%;
  /* 막대는 **보조 표시**다 — 값은 왼쪽에 숫자로 있다. 글자색(`…-ink`)을 쓰면 면적이
     넓어 화면에서 가장 어두운 덩어리가 되고, 정작 읽어야 할 이름·점수를 누른다. */
  background: var(--semantic-warning);
  opacity: 0.65;
}

@media (max-width: 759px) {
  .hit-card {
    grid-template-columns: minmax(0, 1fr);
  }

  .result-summary__subject {
    margin-left: 0;
  }
}

/* --- 상태 배지 (AC-34 · 색 + 텍스트 병기) ---------------------------------
 * 배경 틴트를 지워도 테두리와 라벨 텍스트가 남아 상태를 읽을 수 있어야 한다.
 * 색은 보조 신호일 뿐이며 유일한 신호가 되지 않는다. */

.status-badge {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-xs);
  padding: 3px 10px;
  border: 1px solid var(--tint-gray-ink);
  border-radius: var(--radius-pill);
  background: var(--tint-gray);
  color: var(--tint-gray-ink);
  font-size: var(--fs-caption);
  line-height: 1.45;
  white-space: nowrap;
}

.status-badge--critical {
  border-color: var(--tint-rose-ink);
  background: var(--tint-rose);
  color: var(--tint-rose-ink);
}

.status-badge--warning {
  border-color: var(--tint-amber-ink);
  background: var(--tint-amber);
  color: var(--tint-amber-ink);
}

.status-badge--info {
  border-color: var(--tint-sky-ink);
  background: var(--tint-sky);
  color: var(--tint-sky-ink);
}

.status-badge--done {
  border-color: var(--tint-mint-ink);
  background: var(--tint-mint);
  color: var(--tint-mint-ink);
}

.status-badge--accent {
  border-color: var(--tint-indigo-ink);
  background: var(--tint-indigo);
  color: var(--tint-indigo-ink);
}

/* --- 플래시 메시지 ---------------------------------------------------------
 * 화면 알림(Notification) 을 대체하지 않는다. 자동 소멸하는 안내 전용이다. */

.flash {
  margin-bottom: var(--space-xl);
}

.flash__item {
  padding: var(--space-md) var(--space-lg);
  border-radius: var(--radius-md);
  border: 1px solid var(--tint-indigo-ink);
  background: var(--tint-indigo);
  color: var(--tint-indigo-ink);
  font-size: var(--fs-body-sm);
}

.flash__item + .flash__item {
  margin-top: var(--space-sm);
}

.flash__item--warning {
  border-color: var(--tint-amber-ink);
  background: var(--tint-amber);
  color: var(--tint-amber-ink);
}

.flash__item--error {
  border-color: var(--tint-rose-ink);
  background: var(--tint-rose);
  color: var(--tint-rose-ink);
}

.flash__item--success {
  border-color: var(--tint-mint-ink);
  background: var(--tint-mint);
  color: var(--tint-mint-ink);
}

.flash__label {
  font-weight: var(--fw-medium);
}

/* --- 배너 ------------------------------------------------------------------ */

.banner-warning {
  padding: var(--space-md) var(--space-lg);
  border: 1px solid var(--tint-amber-ink);
  border-radius: var(--radius-md);
  background: var(--tint-amber);
  color: var(--tint-amber-ink);
  font-size: var(--fs-body-sm);
}

.banner-info {
  padding: var(--space-md) var(--space-lg);
  border: 1px solid var(--tint-indigo-ink);
  border-radius: var(--radius-md);
  background: var(--tint-indigo);
  color: var(--tint-indigo-ink);
  font-size: var(--fs-body-sm);
}

/* ===========================================================================
 * C-01 고객 목록 · C-02 고객정보상세 (M1)
 * ===========================================================================
 * 색은 전부 토큰이다. 이 파일에는 테마 분기도 hex 도 없다 — 테마는 tokens.css 한 곳에서만
 * 갈린다(test_theme.py 가 강제한다).
 * ------------------------------------------------------------------------- */

/* --- 폼 배치 --------------------------------------------------------------- */

.form-grid--cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  align-items: start;
}

.field--span {
  grid-column: 1 / -1;
}

.field--radio {
  padding: 0;
  border: 0;
  margin: 0;
}

.field--radio div,
.field--radio label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  margin-right: var(--space-lg);
}

.field--check .field__check-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  color: var(--ink-secondary);
}

/* 조회조건 격자 안의 체크박스 — **입력칸 줄에 맞춰 선다**(W-01 `PEP 추가검색`).
   다른 칸은 라벨이 위에 있어 입력이 아랫줄에 서는데, 체크박스는 라벨이 옆에 붙어 칸
   맨 위에 앉는다. 그대로 두면 이름·국가 입력칸보다 **한 줄 위로 떠서** 같은 행에 있는
   것으로 읽히지 않는다. 칸 아래에 붙이고(`align-self: end`) 입력칸과 같은 높이를 줘
   세 칸의 아랫변이 맞는다. */
.field--check-inline {
  align-self: end;
}

.field--check-inline .field__check-label {
  min-height: var(--control-height);
  /* 조회조건의 다른 라벨과 같은 무게로 선다 — 이 칸만 작게 두면 «부가 설정» 으로 읽힌다. */
  font-size: var(--fs-body);
  color: var(--ink);
}

/* 네이티브 체크박스는 기본 13px 라 라벨 옆에서 사라진다. 조회 범위를 정하는 입력이므로
   눌러야 할 자리가 눈에 보여야 한다. */
.field--check-inline input[type="checkbox"] {
  width: 1.25rem;
  height: 1.25rem;
  accent-color: var(--primary);
}

.field input[type="file"] {
  width: 100%;
  padding: var(--space-sm) 0;
  color: var(--ink);
  font-family: inherit;
  font-size: var(--fs-body-sm);
}

/* 비활성 조건(M2 의존)은 회색으로만 두지 않는다 — 배지로 이유를 병기한다. */
.field-lock {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  align-items: flex-start;
}

.field-group {
  margin-top: var(--space-lg);
  padding: var(--space-lg);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  background: var(--surface-inset);
}

.field-group[hidden] {
  display: none;
}

.field-group__legend {
  padding: 0 var(--space-sm);
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  color: var(--ink-secondary);
}

.field-group__note {
  margin-bottom: var(--space-md);
  font-size: var(--fs-body-sm);
  color: var(--ink-mute);
}

.row-form {
  margin-top: var(--space-lg);
  padding: var(--space-lg);
  border: 1px solid var(--hairline-soft);
  border-radius: var(--radius-lg);
  background: var(--surface-inset);
}

/* --- 목록 (C-01) ----------------------------------------------------------- */

.card__head .card__note {
  margin-top: 0;
}

.row-link {
  cursor: pointer;
}

.button--xs {
  min-height: 32px;
  padding: 6px 12px;
  font-size: var(--fs-button-sm);
}

.hint-text {
  font-size: var(--fs-caption);
  color: var(--ink-mute);
}

/* --- 상세 상단 고정 바 (C-02) ---------------------------------------------- */

.detail-bar {
  position: sticky;
  top: 0;
  z-index: 20;
  margin-bottom: var(--space-xl);
  padding: var(--space-lg) var(--space-xl);
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--elevation-card);
}

.detail-bar__top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
}

.detail-bar__identity {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  flex-wrap: wrap;
}

.detail-bar__title {
  font-size: var(--fs-heading-lg);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-heading);
  color: var(--ink);
}

.detail-bar__no {
  color: var(--ink-mute);
}

.detail-bar__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-lg);
  font-size: var(--fs-body-sm);
}

.detail-bar__meta dt {
  color: var(--ink-mute);
  font-size: var(--fs-caption);
}

.detail-bar__meta dd {
  color: var(--ink);
}

.detail-bar__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
}

/* 위험 액션은 저장 버튼 옆에 붙이지 않는다(PRD §6.9) — 구분선을 두고 끝으로 민다. */
.detail-bar__divider {
  width: 1px;
  align-self: stretch;
  margin: 0 var(--space-sm);
  background: var(--hairline);
}

.detail-bar__locked {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.jump-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-lg);
  margin-top: var(--space-md);
  padding-top: var(--space-sm);
  border-top: 1px solid var(--hairline-soft);
}

.jump-nav__item {
  padding-bottom: var(--space-xs);
  border-bottom: 2px solid transparent;
  color: var(--ink-mute);
  font-size: var(--fs-button-sm);
  font-weight: var(--fw-medium);
  text-decoration: none;
}

.jump-nav__item:hover,
.jump-nav__item:focus {
  border-bottom-color: var(--primary);
  color: var(--primary);
}

/* --- 상세 본문 ------------------------------------------------------------- */

.detail-bar + .banner-warning,
.detail-bar + .banner-info,
.banner-warning + .card,
.banner-info + .card {
  margin-bottom: var(--space-xl);
}

.banner-warning + .card,
.banner-info + .card {
  margin-top: var(--space-xl);
  margin-bottom: 0;
}

.banner__title {
  font-weight: var(--fw-medium);
}

.blocker-list {
  margin: var(--space-sm) 0 0 var(--space-lg);
  list-style: disc;
}

.subsection {
  margin-top: var(--space-section);
  padding-top: var(--space-xl);
  border-top: 1px solid var(--hairline);
}

.subsection__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
  flex-wrap: wrap;
  margin-bottom: var(--space-lg);
}

.subsection__head .card__note {
  margin-top: 0;
}

.subsection .banner-info {
  margin-bottom: var(--space-lg);
}

.confirmed-line {
  margin-bottom: var(--space-lg);
  font-size: var(--fs-body-sm);
  color: var(--ink-secondary);
}

/* 행 추가 접이식 (2026-09-18 전수 검수) — 여는 자리는 **버튼처럼 생긴 `<summary>`** 다.
 * `list-style: none` 으로 기본 삼각형을 지우고, 열림 상태는 배경 대비로 알린다
 * (`.confirm`·`.disclosure` 와 같은 문법). */
.row-add {
  margin-top: var(--space-lg);
}

.row-add__trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  list-style: none;
  cursor: pointer;
}

.row-add__trigger::-webkit-details-marker {
  display: none;
}

.row-add[open] > .row-add__trigger {
  margin-bottom: var(--space-md);
}

/* --- 인라인 행 편집 (PRD §6.4(1)) ------------------------------------------
 * 편집기는 **그리드 안**에서 펼쳐진다. 모달로 띄우지 않는 이유는 실소유자 수정이
 * 다른 행의 지분율을 보면서 이뤄져야 하기 때문이다 — 배경을 가리면 그 맥락이 사라진다.
 * 그래서 이 블록에는 오버레이·스크림이 없다. 면과 좌측 강조선으로만 구분한다. */

.row-edit > td {
  padding: var(--space-lg) var(--space-xl);
  background: var(--surface-inset);
  border-bottom: 2px solid var(--primary);
  box-shadow: inset 3px 0 0 var(--primary);
}

.row-edit__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
  flex-wrap: wrap;
  margin-bottom: var(--space-md);
}

.row-edit__title {
  font-size: var(--fs-heading-sm);
  font-weight: var(--fw-medium);
  color: var(--ink);
}

.row-edit__note {
  font-size: var(--fs-caption);
  color: var(--ink-mute);
}

.row-edit__form .form-actions {
  margin-top: var(--space-lg);
}

.row-form-locked {
  margin-top: var(--space-lg);
  padding: var(--space-md) var(--space-lg);
  border: 1px dashed var(--hairline-input);
  border-radius: var(--radius-lg);
  background: var(--surface-inset);
  color: var(--ink-mute);
  font-size: var(--fs-body-sm);
}

/* 판정 영향 필드 — 고치면 확정이 풀린다는 사실을 **고치기 전에** 알린다. */
.judgement-fields {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-lg);
  margin-top: var(--space-sm);
  list-style: disc;
  padding-left: var(--space-xl);
}

.field-judgement {
  padding: var(--space-sm);
  margin: calc(var(--space-sm) * -1);
  border-radius: var(--radius-sm);
  background: var(--tint-amber);
}

.field__warn {
  margin-top: var(--space-xs);
  font-size: var(--fs-caption);
  color: var(--tint-amber-ink);
}

.field--readonly .identifier {
  min-height: var(--control-height);
  padding: 11px 14px;
  border: 1px dashed var(--hairline-input);
  border-radius: var(--radius-sm);
  background: var(--canvas);
  color: var(--ink-secondary);
}

/* --- C. 위험평가 요약 (표시 전용 · 값은 M2) --------------------------------
 * KPI 칸과 같은 기하를 쓰되 숫자를 강조하지 않는다 — 아직 값이 없고, `미산정` 을 굵게
 * 키우면 없는 것을 있는 것처럼 읽게 만든다. */

.risk-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-lg);
  margin-top: var(--space-lg);
}

.risk-summary__cell {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-lg);
  background: var(--canvas-sunken);
  border: 1px solid var(--hairline-table);
  border-radius: var(--radius-lg);
}

.risk-summary__label {
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  color: var(--ink-secondary);
}

.risk-summary__value {
  font-size: var(--fs-metric-lg);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--ls-tabular);
  color: var(--ink-mute);
}

/* --- 고유식별정보 (마스킹 + 열람) ------------------------------------------ */

.identifier {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.identifier__value {
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--ls-tabular);
}

.identifier__plain {
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--semantic-error);
  border-radius: var(--radius-sm);
  color: var(--semantic-error);
}

/* 증빙서류 분류별 대비표 — **4열 균등 · 가운데 정렬**(2026-09-18 요청 ⑬·⑭).
 * 분류명만 왼쪽으로 둔다: 가운데로 밀면 길이가 제각각인 분류명이 들쭉날쭉해져 훑기 어렵다.
 * `table-layout: fixed` 가 있어야 25% 가 실제로 지켜진다 — 없으면 브라우저가 내용 폭으로
 * 다시 나눠 「관리」 칸이 표를 지배한다. */
.attachment-matrix {
  table-layout: fixed;
}

.attachment-matrix th,
.attachment-matrix td {
  width: 25%;
  text-align: center;
  vertical-align: middle;
}

.attachment-matrix tbody th[scope="row"] {
  text-align: left;
}

/* 한 분류에 파일이 여럿이면 버튼 쌍이 줄바꿈으로 쌓인다 — 가로로 밀어 표를 넓히지 않는다. */
.attachment-actions {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin: 0 var(--space-xs) var(--space-xs) 0;
}

/* 우편번호 찾기 버튼 — **스크립트가 세운다**(`static/js/postcode.js`). 서버 마크업에는
 * 없으므로 이 규칙도 그 버튼이 실제로 설 때만 쓰인다. 입력칸 바로 아래 왼쪽에 붙여
 * 「이 칸을 채우는 버튼」으로 읽히게 한다. */
.postcode-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

/* 입력칸이 남는 폭을 먹고 버튼은 제 폭만 쓴다. `min-width: 0` 이 없으면 플렉스 항목의
   기본 최소폭 때문에 버튼이 밖으로 밀린다. */
.postcode-row > input {
  flex: 1 1 auto;
  min-width: 0;
}

.postcode-button {
  flex: 0 0 auto;
  white-space: nowrap;
}

/* --- 드래그앤드롭 업로드 ---------------------------------------------------- */

.dropzone {
  padding: var(--space-lg);
  border: 1px dashed var(--hairline-input);
  border-radius: var(--radius-lg);
  background: var(--surface-inset);
  text-align: center;
}

.dropzone--over {
  border-color: var(--primary);
  background: var(--primary-subdued);
}

.dropzone__hint {
  margin-bottom: var(--space-sm);
  font-size: var(--fs-body-sm);
  color: var(--ink-mute);
}

.dropzone__picked {
  margin-top: var(--space-sm);
  font-size: var(--fs-body-sm);
  color: var(--ink-secondary);
}

.dropzone__picked[hidden] {
  display: none;
}

/* --- 모달 (미리보기·삭제·확정은 별도 URL 을 만들지 않는다) ------------------ */

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-lg);
  background: var(--scrim);
  overflow-y: auto;
}

.modal-backdrop[hidden] {
  display: none;
}

.modal {
  width: 100%;
  max-width: 520px;
  padding: var(--space-xl);
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--elevation-overlay);
}

.modal--wide {
  max-width: 840px;
}

.modal__note {
  margin-top: var(--space-sm);
  font-size: var(--fs-body-sm);
  color: var(--ink-mute);
}

.modal__image {
  display: block;
  width: 100%;
  max-height: 60vh;
  margin-top: var(--space-lg);
  object-fit: contain;
  background: var(--canvas-sunken);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
}

/* === M2 위험평가 =========================================================== */

/* 절 주석줄 — **여러 줄이면 2컬럼으로 흐른다**(2026-09-18 전수 검수 · 요청 2).
 * `columns` 는 항목 수가 적으면 자동으로 한 단만 쓴다: 한 줄짜리 절은 그대로다.
 * `break-inside: avoid` 가 없으면 한 문장이 두 단에 걸쳐 잘린다. */
.doc-notes {
  columns: 2;
  column-gap: var(--space-xxl);
  margin-top: var(--space-lg);
}

.doc-notes > .doc-note {
  break-inside: avoid;
  margin: 0 0 var(--space-xs);
}

@media (max-width: 759px) {
  .doc-notes {
    columns: 1;
  }
}

/* --- 문서 절의 판정 강조 행 (2026-09-18 요청 ⑤) ------------------------------
 * 「위험등급 · 고객확인의무」처럼 그 절이 내린 **결론**을 표 위에 좌우로 편다. 배지가
 * 아니라 행인 이유는 값이 둘 다 문장이 아니라 판정어라서다 — 나란히 놓으면 대비가 읽힌다.
 * 색만으로 구분하지 않는다: 라벨이 항상 값 위에 붙어 있다. */
.doc-highlight {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-md);
  margin: 0 0 var(--space-md);
  padding: var(--space-sm) var(--space-md);
  background: var(--canvas-soft);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
}

/* 셀이 셋 이상이 되면 가운데 것들이 균등하게 벌어진다 — 지금은 둘이라 좌우 끝이다. */
.doc-highlight__cell {
  min-width: 0;
}

.doc-highlight__cell:last-child {
  text-align: right;
}

.doc-highlight__label {
  margin: 0;
  font-size: var(--fs-caption);
  color: var(--ink-secondary);
}

.doc-highlight__value {
  margin: 0;
  font-size: var(--fs-heading-sm);
  font-weight: var(--fw-semibold);
  color: var(--ink);
}

/* --- C-03 입력 그리드 -------------------------------------------------------
 * 15행 × 9컬럼이다. 축소하지 않고 `.table-scroll` 로 가로 스크롤한다(DESIGN.md §표).
 * 숫자 셀은 tabular + 우측 정렬(`.num` 이 이미 그렇게 한다). */

.detail-bar__subject {
  font-size: var(--fs-body);
  color: var(--ink-secondary);
}

.score-grid {
  min-width: 1120px;
}

.score-grid__category {
  min-width: 96px;
  padding: var(--space-md) var(--space-sm);
  vertical-align: top;
  text-align: left;
  background: var(--canvas-soft);
  border-right: 1px solid var(--hairline);
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  color: var(--ink);
}

.score-grid__factor {
  min-width: 220px;
}

.score-grid__no {
  display: inline-block;
  min-width: 2ch;
  margin-right: var(--space-xs);
  color: var(--ink-mute);
}

.score-grid__title {
  font-weight: var(--fw-medium);
}

/* 평가 설명·징후 예시는 **별도 열이 아니라 요소명 아래**다(스펙 §4-1-5). 열로 두면 가장
   긴 글이 표 폭을 지배하고 정작 선택값 칸이 밀린다. 본문보다 작게 찍되 13px 아래로는
   내리지 않는다(한국어 조판 규약). */
.score-grid__guide {
  max-width: 340px;
  margin: var(--space-xs) 0 0;
  font-size: var(--fs-body-sm);
  color: var(--ink-secondary);
}

/* 배점(=가중치)은 요소명과 한 덩어리로 읽힌다 — 표에 같은 값의 열이 따로 있어도
   "이 항목이 몇 점짜리인가" 는 요소명 옆에서 읽는 것이 자연스럽다(스펙 §4-1-5). */
.score-grid__allot {
  display: inline-block;
  margin-left: var(--space-xs);
  font-size: var(--fs-caption);
  color: var(--ink-mute);
}

/* 선택값 열은 표 폭의 40% 를 차지한다(2026-09-18 요청 ①). 선택지 문구가 줄바꿈으로
   접히면 "보면서 고른다" 는 라디오의 이점이 사라진다 — 나머지 열은 숫자라 좁아도 읽힌다.
   `min-width: 1120px` 위에서의 비율이므로 가로 스크롤 계약은 그대로다. */
.score-grid__option {
  width: 40%;
}

/* --- 선택값 라디오 (P4 · 스펙 §4-1-4) ----------------------------------------
 * select box 는 접혀 있어 선택지를 **보면서** 고를 수 없었다. 라디오는 문구와 입력점수를
 * 함께 펴 둔다. 최초 선택값은 없다 — 미선택과 0점은 다른 사건이고, 그 구분은 화면에서도
 * 유지된다(고른 것이 없으면 점수 칸이 `-` 로 남는다). */
.score-choice {
  display: flex;
  flex-direction: column;
  gap: var(--space-xxs);
  min-width: 320px;
  margin: 0;
  padding: 0;
  border: 0;
}

.score-choice__item {
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
  padding: 4px 6px;
  border-radius: var(--radius-sm);
  font-size: var(--fs-body-sm);
  line-height: 1.6;
  cursor: pointer;
}

.score-choice__item:hover {
  background: var(--canvas-soft);
}

/* 고른 줄은 **색만으로** 알리지 않는다 — 라디오 자체가 형태로 남고, 입력점수 칸에 값이
   그대로 찍힌다. 배경은 눈으로 찾기 쉬우라는 보조 신호일 뿐이다. */
.score-choice__item:focus-within {
  background: var(--canvas-soft);
  box-shadow: 0 0 0 2px var(--primary-subdued);
}

.score-choice__text {
  flex: 1 1 auto;
  word-break: keep-all;
}

.score-choice__score {
  flex: 0 0 auto;
  color: var(--ink-secondary);
}

/* --- 국가군 툴팁 (네이티브 `<details>` — JS 없이 열린다) --------------------- */

.tooltip {
  position: relative;
  display: inline-block;
  margin-left: var(--space-xs);
}

.tooltip__trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border: 1px solid var(--hairline-input);
  border-radius: var(--radius-pill);
  background: var(--canvas);
  color: var(--ink-secondary);
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  cursor: pointer;
  list-style: none;
}

.tooltip__trigger::-webkit-details-marker {
  display: none;
}

.tooltip__panel {
  position: absolute;
  z-index: 60;
  top: calc(100% + var(--space-xs));
  left: 0;
  width: min(420px, 70vw);
  max-height: 50vh;
  padding: var(--space-lg);
  overflow-y: auto;
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--elevation-raised);
}

.tooltip__list > div + div {
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--hairline-soft);
}

.tooltip__list dt {
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  color: var(--ink);
}

.tooltip__list dd {
  margin: var(--space-xxs) 0 0;
  font-size: var(--fs-body-sm);
  color: var(--ink-secondary);
}

.tooltip__note {
  color: var(--ink-mute);
}

/* --- C-03 하단 결과 --------------------------------------------------------- */

.score-total {
  margin-top: var(--space-xl);
  padding: var(--space-lg);
  background: var(--canvas-soft);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
}

.score-total__figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-lg);
  margin: var(--space-lg) 0 0;
}

.score-total__figures > div {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-lg);
  background: var(--canvas-sunken);
  border: 1px solid var(--hairline-table);
  border-radius: var(--radius-lg);
}

.score-total__figures dt {
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  color: var(--ink-secondary);
}

.score-total__figures dd {
  margin: 0;
  font-size: var(--fs-metric-lg);
  font-weight: var(--fw-bold);
  color: var(--ink);
}

.score-total__legend {
  margin-top: var(--space-lg);
  font-size: var(--fs-body-sm);
  color: var(--ink-secondary);
}

/* --- 펼치기 disclosure (네이티브 `<details>` — JS 없이 열린다) ---------------
 * P6 이 두 자리에서 같은 동작을 요구했다: W-01 선택 조회조건 6종(스펙 §3-1-3)과
 * W-02 상세의 `미표시 항목 보기`(K-7 · 스펙 §3-3-4). 둘 다 **기본 접힘**이고 여는
 * 동작은 브라우저의 것이다 — `<button>` + JS 로 만들면 스크립트가 꺼진 화면에서
 * 항목이 영영 사라진다(M1 MED-1 과 같은 모양).
 *
 * `.row-editor` 와 생김새가 같지만 이름을 나눈다. 그쪽은 「행 편집기」라는 업무
 * 낱말이고, 접기는 그 업무와 무관한 배치 원시형이다. */

.disclosure {
  margin-top: var(--space-lg);
}

.disclosure__trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  min-height: 36px;
  padding: 0 14px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  background: var(--canvas);
  color: var(--ink-secondary);
  font-size: var(--fs-button-sm);
  font-weight: var(--fw-medium);
  cursor: pointer;
}

.disclosure[open] > .disclosure__trigger {
  margin-bottom: var(--space-md);
  border-color: var(--primary);
  color: var(--primary);
}

/* --- W-01 개인/사업자 탭 (스펙 §3-1-1) ---------------------------------------
 *
 * **탭처럼 보이지만 라디오다.** 값은 폼 필드 `subject_kind` 하나이고 고르는 일은
 * 브라우저가 한다 — `<button role="tab">` + JS 로 만들면 스크립트가 없을 때 눌러도
 * 아무 일이 없고, 그대로 제출하면 대상유형이 빈 채로 서버에 닿는다.
 *
 * 활성 탭은 **색 + 굵기 + 밑줄** 세 가지로 표시한다. 색만으로 구분하지 않는다. */

.kind-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-bottom: var(--space-lg);
  border-bottom: 1px solid var(--hairline);
}

/* 화면에서만 지운다. `display: none` 은 키보드 초점과 라디오 그룹 이동까지 지운다. */
.kind-tabs__radio {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.kind-tabs__tab {
  padding: var(--space-sm) var(--space-lg);
  font-size: var(--fs-button);
  font-weight: var(--fw-medium);
  line-height: var(--lh-body);
  color: var(--ink-secondary);
  border-bottom: 2px solid transparent;
  cursor: pointer;
}

.kind-tabs__tab:hover {
  color: var(--ink);
}

.kind-tabs__radio:checked + .kind-tabs__tab {
  color: var(--primary);
  font-weight: var(--fw-semibold);
  border-bottom-color: var(--primary);
}

/* 라디오가 안 보이므로 초점 표시를 탭이 대신 받는다 — 없으면 키보드 사용자가
 * 지금 어느 탭에 있는지 알 수 없다. */
.kind-tabs__radio:focus-visible + .kind-tabs__tab {
  outline: 2px solid var(--primary);
  outline-offset: -2px;
}

/* --- A-03 인라인 행 편집기 (네이티브 `<details>` — JS 없이 열린다) ----------
 * 15항목 × 84선택지의 폼을 전부 펼쳐 두면 정작 기준표를 읽을 수 없다. 접는 것은
 * 스크립트가 아니라 브라우저가 한다. */

.row-editor {
  margin-top: var(--space-sm);
}

.row-editor__trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  min-height: 36px;
  padding: 0 14px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  background: var(--canvas);
  color: var(--ink-secondary);
  font-size: var(--fs-button-sm);
  font-weight: var(--fw-medium);
  cursor: pointer;
}

.row-editor[open] > .row-editor__trigger {
  margin-bottom: var(--space-md);
  border-color: var(--primary);
  color: var(--primary);
}

.row-editor-row > td {
  background: var(--surface-inset);
  box-shadow: inset 3px 0 0 var(--primary);
}

/* --- 확인 disclosure (고객 삭제 — 네이티브 `<details>`) ---------------------
 * 페이지를 떠나지 않는다(C-02 는 단일 페이지 폼이라 이동하면 미저장 입력이 날아간다).
 * 전면 스크림을 쓰지 않는 이유: 스크림이 덮으면 `<summary>` 를 다시 눌러 닫을 수 없고,
 * 닫기 버튼은 스크립트를 필요로 한다 — 그러면 다시 JS 전제가 된다. */

.confirm {
  position: relative;
}

.confirm > summary {
  list-style: none;
  cursor: pointer;
}

.confirm > summary::-webkit-details-marker {
  display: none;
}

.confirm__panel {
  position: absolute;
  z-index: 80;
  top: calc(100% + var(--space-sm));
  right: 0;
  width: min(460px, 88vw);
  padding: var(--space-xl);
  text-align: left;
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--elevation-overlay);
}

.confirm[open] > summary {
  background: var(--canvas-soft);
}

@media (max-width: 559px) {
  .confirm__panel {
    position: static;
    width: 100%;
  }
}

/* --- 읽기전용 기록 목록 (고객확인 작성본·평가 사유) ------------------------ */

.record-list {
  margin-top: var(--space-lg);
}

.record-list > div + div {
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--hairline-soft);
}

.record-list dt {
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  color: var(--ink-secondary);
}

.record-list dd {
  margin: var(--space-xs) 0 0;
  white-space: pre-line;
}

.risk-summary__meta {
  margin-top: var(--space-lg);
}

/* === M3 외부데이터 수집·Import (W-04 · W-05) =================================
 *
 * 새 색·새 간격을 만들지 않는다 — 전부 tokens.css 변수다. 이 블록이 하는 일은
 * ① 9개 소스를 한눈에 훑는 카드 격자 ② 컬럼이 많은 표의 가로 스크롤 보조
 * ③ 해시·URL 처럼 **끊을 자리가 없는 문자열**의 줄바꿈 ④ 사실 목록의 2단 배치뿐이다.
 *
 * 한국어 조판 규칙은 상위(body)에서 이미 `word-break: keep-all` 로 잡혀 있고, 여기서는
 * **그 예외**(해시·URL)만 되돌린다. */

/* --- 소스 카드 격자 (W-04 소스 선택) ---------------------------------------- */

.source-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: var(--space-lg);
  margin: var(--space-lg) 0 0;
  padding: 0;
  list-style: none;
}

.source-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-lg);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  background: var(--surface-inset);
}

.source-card__title {
  font-size: var(--fs-heading-sm);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-heading);
  color: var(--ink);
  text-decoration: none;
}

.source-card__title:hover,
.source-card__title:focus {
  text-decoration: underline;
}

.source-card__badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin: 0;
}

.source-card__facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--space-sm) var(--space-md);
  margin: 0;
}

.source-card__facts dt {
  font-size: var(--fs-caption);
  color: var(--ink-mute);
}

.source-card__facts dd {
  margin: var(--space-xxs) 0 0;
  font-size: var(--fs-body-sm);
}

/* --- W-05 요약 표 ----------------------------------------------------------- */

.watch-summary__name {
  min-width: 200px;
  text-align: left;
  font-weight: var(--fw-medium);
}

.watch-summary__name .caption {
  display: block;
}

.watch-summary__version {
  white-space: nowrap;
}

/* 행 안의 버튼 묶음 — 줄바꿈은 허용하되 버튼끼리 붙지 않게 한다. */
.watch-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

/* --- 사실 목록 2단 (컬럼이 20개 가까운 실행 정보) ---------------------------- */

.record-list--cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 0 var(--space-xl);
}

/* 2단이 되면 "위 항목과의 구분선" 규칙이 첫 열에만 맞는다 — 모든 칸에 위 선을 준다. */
.record-list--cols > div {
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--hairline-soft);
}

.record-list--cols > div + div {
  margin-top: var(--space-md);
}

/* --- Diff 샘플 --------------------------------------------------------------- */

.record-sample {
  margin: var(--space-sm) 0 0;
  padding-left: var(--space-xl);
}

.record-sample > li + li {
  margin-top: var(--space-xs);
}

/* --- 끊을 자리가 없는 문자열 (SHA-256 · URL) --------------------------------
 * 한국어 본문은 `keep-all` 이 맞지만 64자 해시와 긴 URL 은 그대로 두면 표를 밀어낸다.
 * 이 예외는 **해당 칸에만** 준다. */

.break-any {
  overflow-wrap: anywhere;
  word-break: break-all;
}

/* --- 서버 파일 접수 라디오 --------------------------------------------------- */

.inbox-choice .caption {
  margin-left: var(--space-sm);
  color: var(--ink-mute);
}

/* --- 반응형 (표는 축소하지 말고 가로 스크롤) ------------------------------ */

@media (max-width: 1099px) {
  /* 좁은 화면 — 메뉴는 버튼 하나로 접힌다. 여는 것은 **네이티브 `<details>`** 이므로
   * JS 가 꺼져 있어도 눌러서 열린다(눌러도 아무 일 없는 버튼을 남기지 않는다). */
  .mainnav {
    order: 10;
    width: 100%;
  }

  .mainnav__toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    min-height: 44px;
    padding: 0 16px;
    border: 1px solid var(--hairline);
    border-radius: var(--radius-pill);
    background: var(--canvas-soft);
    color: var(--ink-secondary);
    font-size: var(--fs-button-sm);
    font-weight: var(--fw-medium);
  }

  .mainnav__panel {
    display: none;
    margin-top: var(--space-md);
  }

  .mainnav[open] > .mainnav__panel {
    display: block;
  }

  .mainnav::details-content {
    content-visibility: hidden;
  }

  .mainnav[open]::details-content {
    content-visibility: visible;
  }

  .mainnav__list {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-xxs);
  }

  /* 2depth 는 겹쳐 띄우지 않고 **그 자리에서 펼친다** — 좁은 화면에서 떠 있는 패널은
   * 화면 밖으로 넘치고, 터치에서는 hover 가 끈적여 의도치 않게 열린다. */
  .mainnav__sub {
    position: static;
    min-width: 0;
    margin: var(--space-xxs) 0 var(--space-xs) var(--space-lg);
    border-radius: var(--radius-md);
    box-shadow: none;
  }

  .mainnav__group:hover > .mainnav__sub,
  .mainnav__group:focus-within > .mainnav__sub {
    display: none;
  }

  .mainnav__group[open] > .mainnav__sub {
    display: block;
  }

  .mainnav__group::details-content {
    content-visibility: hidden;
  }

  .mainnav__group[open]::details-content {
    content-visibility: visible;
  }

  .mainnav__link,
  .mainnav__summary,
  .mainnav__sublink {
    white-space: normal;
  }

  .mainnav__summary {
    justify-content: space-between;
  }
}

/* 캐럿 회전 말고는 움직이는 것이 없다 — 그 하나를 끈다. */
@media (prefers-reduced-motion: reduce) {
  .mainnav__summary::after,
  .usermenu__trigger::after {
    transition: none;
  }
}

@media (max-width: 559px) {
  .content {
    padding: var(--space-lg);
  }

  .page-title {
    font-size: var(--fs-display-md);
  }
}

/* --- M4 W-01 · W-02 · W-03 -------------------------------------------------
 *
 * **색은 보조 신호다.** 신호등 점을 통째로 지워도 옆의 텍스트가 남아 상태를 읽을 수 있다
 * (AC-457) — 그래서 점에는 `aria-hidden` 이 걸려 있고 여기서는 크기와 색만 준다.
 * 판정은 서버가 했고 이 파일에는 임계값도 규칙도 없다. */

.signal {
  display: inline-block;
  width: 0.75rem;
  height: 0.75rem;
  margin-right: var(--space-xs);
  vertical-align: baseline;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  background: var(--tint-gray);
}

.signal--red {
  background: var(--semantic-error);
  border-color: var(--semantic-error);
}

.signal--green {
  background: var(--semantic-success);
  border-color: var(--semantic-success);
}

.signal--grey {
  background: var(--tint-gray);
  border-color: var(--ink-mute-2);
}

.signal__text {
  font-weight: var(--fw-semibold);
  word-break: keep-all;
}

.signal-table__basis {
  max-width: 34rem;
}

.signal-table__basis .caption {
  display: block;
  word-break: keep-all;
}

/* `해당사항 없음` 은 **눈에 띄게** 표시한다. 빈 화면이나 `0건` 으로 대신하지 않는다
 * (정본 §7.2).
 *
 * ⚠ 2026-09-18 전수 검수에서 줄였다. 종전에는 카드 폭을 다 쓰는 가운데 정렬 제목
 * 크기였고, 바로 아래 요약 배지 줄과 **역할이 겹쳐** 시선이 둘로 갈렸다. 정본이 요구한
 * 것은 «크게»이지 «폭을 다 쓰라»가 아니다 — 배지 줄과 같은 줄에 서되 글자와 색으로
 * 구분한다(다른 배지보다 한 단계 큰 글자 + 채운 배경). */
.verdict {
  display: inline-flex;
  align-items: center;
  margin: 0;
  padding: var(--space-xs) var(--space-lg);
  font-size: var(--fs-body-lg);
  font-weight: var(--fw-bold);
  line-height: var(--lh-heading);
  word-break: keep-all;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
}

.verdict--clear {
  color: var(--tint-mint-ink);
  background: var(--tint-mint);
  border-color: var(--tint-mint-ink);
}

.verdict--partial {
  color: var(--tint-amber-ink);
  background: var(--tint-amber);
  border-color: var(--tint-amber-ink);
}

.result-cards {
  display: grid;
  gap: var(--space-lg);
  margin: var(--space-lg) 0 0;
  padding: 0;
  list-style: none;
}

.result-card {
  padding: var(--space-lg);
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
}

.result-card__head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  align-items: center;
  justify-content: space-between;
}

.result-card__badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

.result-card__score {
  margin: 0;
  font-size: var(--fs-metric-lg);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--ls-tabular);
}

.result-card__score .caption {
  margin-right: var(--space-sm);
}

.result-card__name {
  margin: var(--space-sm) 0;
  font-size: var(--fs-heading-sm);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-heading);
  word-break: keep-all;
}

/* W-03 두 탭. 별도 메뉴가 아니라 같은 화면의 두 관점이다. */
.tabs {
  display: flex;
  gap: var(--space-xs);
  margin-bottom: var(--space-lg);
  border-bottom: 1px solid var(--hairline);
}

.tabs__item {
  padding: var(--space-sm) var(--space-lg);
  font-size: var(--fs-button);
  font-weight: var(--fw-medium);
  color: var(--ink-secondary);
  text-decoration: none;
  border-bottom: 2px solid transparent;
}

.tabs__item:hover {
  color: var(--ink);
}

.tabs__item--current {
  color: var(--primary);
  font-weight: var(--fw-semibold);
  border-bottom-color: var(--primary);
}

/* 원본 추가정보 — **원문 그대로**. 줄바꿈을 보존하되 가로 스크롤은 자기 안에서 처리한다. */
.raw-json {
  max-height: 24rem;
  margin: 0;
  padding: var(--space-lg);
  overflow: auto;
  font-family: var(--font-sans);
  font-size: var(--fs-body-sm);
  line-height: var(--lh-body);
  white-space: pre-wrap;
  background: var(--canvas-sunken);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
}

tr[data-deleted="1"] {
  background: var(--canvas-sunken);
}

/* --- W-01 소스 최신성 압축 카드 (사용자 확정 요구 2026-08-22) ----------------
 *
 * 소스당 **2줄**이다 — 1행 소스명, 2행 소스상태. 9개가 한눈에 들어오도록 줄였을 뿐
 * 접거나 뭉치지 않는다. **색은 보조**이므로 배지 옆에 낱말이 항상 붙어 있고, 이 파일에는
 * 임계값도 상태 판정도 없다(서버가 문자열로 내려준다). */

.source-strip {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  gap: var(--space-sm);
  margin: 0;
  padding: 0;
  list-style: none;
}

.source-chip {
  display: flex;
  flex-direction: column;
  gap: var(--space-xxs);
  padding: var(--space-sm) var(--space-md);
  background: var(--canvas-soft);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
}

.source-chip__name {
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-body);
  word-break: keep-all;
}

.source-chip__state {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-xs);
}

/* 배지가 낱말을 담고 이 칸은 보조 사실(경과일)만 담는다. 배지 스타일이 통째로 빠져도
 * 낱말은 배지 요소 안에 텍스트로 남는다 — 색각 이상·흑백 인쇄·CSS 미로드. */
.source-chip__state-detail {
  font-size: var(--fs-caption);
  line-height: 1.45;
  color: var(--ink-mute);
  word-break: keep-all;
}

/* `<details>` 상세 토글. 네이티브 요소라 스크립트가 없어도 열린다 —
 * 기준일자·반영일시·버전의 도달 가능성이 JS 에 걸려 있지 않다. */
summary.subsection__head {
  display: block;
  cursor: pointer;
  font-size: var(--fs-body-sm);
  font-weight: var(--fw-medium);
  color: var(--ink-secondary);
}

summary.subsection__head:hover {
  color: var(--ink);
}

/* ⚠ **W-02 결과 그리드(`.result-grid`) 규칙은 2026-09-18 에 사라졌다**(요청 ⑤-1).
 * 결과가 표에서 다시 카드로 돌아갔다 — 그 카드의 규칙은 위쪽 「W-02 검색결과」 절에 있다.
 * 빈 선택자를 남겨 두면 다음 사람이 그 이름으로 마크업을 되살린다. */

/* ===========================================================================
 * KPE P2 — 레이아웃 원시형
 * ===========================================================================
 *
 * 스펙 §1 의 화면 문법을 **실제 화면보다 먼저** 하나씩 만들어 둔 자리다. 여기에는
 * 고객·위험평가·고객확인 같은 업무 이름이 하나도 없다 — P3~P6 이 가져다 쓴다.
 * 원시형을 화면마다 다시 지으면 여덟 화면이 여덟 가지 규칙을 갖게 되고, 그때
 * 고치는 비용은 CSS 한 벌이 아니라 화면 수만큼이다(개발계획 §6 리스크 4).
 *
 * 새 색·새 크기를 짓지 않는다 — 전부 `tokens.css` 변수다(`test_theme.py` 가 hex 를
 * 막고, 컴포넌트 CSS 에 테마 분기가 생기는 것도 막는다).
 * ------------------------------------------------------------------------- */

/* --- ① 좁은 본문 (`.content--narrow`) ---------------------------------------
 *
 * 스펙 §1 "좌우 패딩 각각 10%". **문서·폼 페이지 전용**이다. 위험평가 7열 그리드나
 * Watch 결과 9행 소스표에 걸면 1280px 화면에서 본문이 768px 로 줄어 가로 스크롤이
 * 나고, 그것이 정확히 스펙이 피하라는 "되도록 스크롤 없이 한 화면"의 반대다.
 *
 * 넓은 표를 좁은 문서 **안**에 두어야 할 때는 모디파이어를 떼지 말고 그 블록만
 * `.content-bleed` 로 원폭으로 되돌린다. 10% 는 화면 폭 기준이고 자식의 포함블록은
 * 그 80% 이므로 되돌리는 값은 12.5% 다(0.1 ÷ 0.8). **두 숫자는 한 쌍이다** — 한쪽만
 * 고치면 넓은 표가 화면 밖으로 밀리거나 가운데가 어긋난다.
 *
 * 좁은 화면에서 10% 는 사치다. 1100px 미만은 `.content` 의 여백 규칙으로 돌아간다
 * (DESIGN.md §Responsive 태블릿 경계). 그 구간에서는 bleed 도 할 일이 없다. */

.content--narrow {
  padding-right: 10%;
  padding-left: 10%;
}

.content--narrow > .content-bleed {
  margin-right: -12.5%;
  margin-left: -12.5%;
}

@media (max-width: 1099px) {
  .content--narrow {
    padding-right: clamp(var(--space-lg), 3vw, var(--space-section));
    padding-left: clamp(var(--space-lg), 3vw, var(--space-section));
  }

  .content--narrow > .content-bleed {
    margin-right: 0;
    margin-left: 0;
  }
}

/* --- ② 문서 본문 2컬럼 (`.doc-grid`) ----------------------------------------
 *
 * 스펙 §1 "body 항목은 Edit·View 모두 2컬럼". Edit 의 입력 짝(`.field`)과 View 의
 * 읽기 짝(`.doc-field`)이 **같은 격자**에 들어간다 — 두 페이지에서 항목 순서와
 * 줄바꿈 자리가 같아야 View 가 Edit 의 미리보기로 읽히고, 그 View 를 PDF 가 그대로
 * 렌더한다(D-2).
 *
 * `minmax(0, 1fr)` 인 이유: 끊을 자리가 없는 값(영문명·여권번호·SHA-256)이 들어와도
 * 칸이 밀려 나가지 않는다. `1fr` 만 쓰면 최소 콘텐츠 폭이 바닥이 되어 격자가 깨진다.
 * 한 줄을 통째로 쓰는 항목은 기존 `.field--span` 을 그대로 쓴다 — 같은 뜻의 클래스를
 * 두 개 만들지 않는다. */

.doc-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-lg) var(--space-xl);
  align-items: start;
}

/* 읽기 전용 항목 한 쌍. View 에는 input·select 가 없다(스펙 §1) — 라벨과 값뿐이다. */
.doc-field__label {
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  line-height: 1.4;
  color: var(--ink-mute);
}

.doc-field__value {
  margin: var(--space-xxs) 0 0;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--ink);
  white-space: pre-line;
}

/* **빈 값을 빈칸으로 두지 않는다.** 칸이 비어 있으면 "아직 안 적었다" 와 "적을 것이
   없다" 가 구분되지 않고, 그 문서는 증적으로 쓸 수 없다. 문구는 서버가 내려준다. */
.doc-field__value--empty {
  color: var(--ink-mute);
}

/* 절(節) 하나. sticky 섹션 내비게이션이 제목을 덮지 않도록 앵커 도착 지점을 내린다.
   이 값을 지우면 `#앵커` 로 이동했을 때 제목이 내비게이션 뒤에 숨는다. */
.doc-section {
  scroll-margin-top: calc(var(--control-height) + var(--space-xl));
}

.doc-section + .doc-section {
  margin-top: var(--space-xl);
}

/* ⚠ **문서 절은 모두 같은 폭이다**(2026-09-18 전수 검수).
 *
 * 종전에는 열이 일곱 이상인 절만 `.content-bleed` 로 원폭을 되찾았다. 그 결과 고객
 * View 에서 ①③⑤⑥⑦ 은 좁고 ②④ 는 넓어, 스크롤할 때마다 카드의 좌우 경계가 안팎으로
 * 튀었다 — 전수 스크린샷에서 «통일감 없음»의 가장 큰 원인이었다.
 *
 * **폭을 하나로 맞추고 넓은 표는 카드 안에서 가로 스크롤한다.** `.table-scroll` 이
 * 이미 그 일을 하고 있었다(글자를 줄이지 않는다는 규약도 그대로다). 문서가 «쪽»으로
 * 읽히려면 쪽의 폭이 일정해야 한다.
 *
 * `.content-bleed` 클래스 자체는 남긴다 — 화면(문서가 아닌 곳)에서 넓은 표를 펴는
 * 쓰임이 따로 있고, 그 자리까지 이 판단을 끌고 갈 이유는 없다. */
/* ⚠ 선택자가 `.content--narrow > .content-bleed`(아래 §①)와 **같은 특이도면 뒤에 온
 * 쪽이 이긴다** — 그 규칙이 이 파일 뒤쪽에 있으므로 여기서 한 겹 더 좁혀 이긴다. */
.content--narrow > .doc-section.content-bleed {
  margin-right: 0;
  margin-left: 0;
}

@media (max-width: 759px) {
  .doc-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* --- ③ 섹션 내비게이션 (`.section-nav`) -------------------------------------
 *
 * 스펙 §1 "섹션 네비게이션을 상단에 두고 스크롤 시 행고정, 선택 시 해당 섹션으로
 * 이동". **한 도메인의 모든 절이 한 페이지에 있으므로** 탭이 아니라 이동 막대다.
 *
 * DESIGN.md `jump-nav`/`jump-nav-active` 의 시각 규칙을 그대로 쓴다(흰 면 · 활성은
 * 인디고 글자 + 2px 밑줄). `.jump-nav` 는 `.detail-bar` **안**에 사는 부품이고 이쪽은
 * 스스로 고정되는 독립 막대라 선택자를 나눴다 — 두 자리의 배치 규칙이 다르다.
 *
 * 활성 표시를 `[aria-current="true"]` 에 건다. 색만으로 구분하지 않기 위해 밑줄을
 * 함께 주고, **보조기술이 읽는 상태와 눈에 보이는 상태가 같은 속성 하나에서 나오게**
 * 한다 — 클래스로 칠하면 언젠가 둘이 갈리고, 갈린 쪽은 화면에 보이지 않는 쪽이다. */

.section-nav {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-lg);
  min-height: var(--control-height);
  margin-bottom: var(--space-xl);
  padding: var(--space-sm) var(--space-lg);
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--elevation-card);
}

/* 절 내비 오른쪽의 행동 묶음(저장·삭제) — **스크롤을 따라다니는 자리**다
 * (2026-09-18 요청 ⑯). `margin-left: auto` 하나로 오른쪽 끝에 붙는다: 스페이서
 * 요소를 두면 그 빈 칸이 보조기술의 읽기 순서에 낀다. */
.section-nav__end {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-left: auto;
}

/* 위 행동막대가 화면에 보이는 동안에는 감춘다(`sectionnav.js` 가 속성을 단다).
   **기본은 보이는 상태**다 — 스크립트가 없으면 버튼이 하나 더 있을 뿐, 아무것도 깨지지
   않는다. `visibility` 인 이유: `display:none` 은 내비 항목을 좌우로 밀어 스크롤할 때마다
   막대가 흔들린다. */
.section-nav__end[data-top-bar-visible] {
  visibility: hidden;
}

.section-nav__item {
  padding-bottom: var(--space-xs);
  border-bottom: 2px solid transparent;
  color: var(--ink-mute);
  font-size: var(--fs-button-sm);
  font-weight: var(--fw-medium);
  text-decoration: none;
}

.section-nav__item:hover,
.section-nav__item:focus {
  border-bottom-color: var(--primary);
  color: var(--primary);
}

.section-nav__item[aria-current="true"] {
  border-bottom-color: var(--primary);
  color: var(--primary);
}

/* 앵커 이동은 링크가 하는 일이고 스크립트는 거들 뿐이다. 부드러운 이동은 편의라
   움직임을 줄여 달라고 한 사용자에게는 주지 않는다. */
html:has(.section-nav) {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html:has(.section-nav) {
    scroll-behavior: auto;
  }
}

/* --- ④ 레이어팝업 (`<dialog class="dialog">`) --------------------------------
 *
 * 스펙 §1·domain-map §5: 레이어팝업은 `<dialog>` 이고 **JS 가 꺼져도 화면이 성립해야
 * 한다**. 그런데 `<dialog>` 는 기본적으로 `showModal()` 없이는 열리지 않는다 — 이
 * 저장소가 `<details class="confirm">` 로 풀었던 문제가 그대로 돌아온 셈이다.
 *
 * 그래서 여는 동작을 **브라우저의 것**으로 되돌린다. 팝업에 `id` 를 주고 여는 버튼을
 * `<a href="#그 id">` 로 두면, 스크립트가 하나도 없어도 브라우저가 `:target` 을
 * 세우고 아래 규칙이 팝업을 편다. 닫기 링크는 맞는 것이 없는 조각(`#dialog-dismissed`)
 * 을 가리켜 `:target` 을 푼다. `dialog.js` 는 그 위에 상위 레이어·포커스 가둠·ESC 를
 * 얹을 뿐이고, **없어도 팝업은 열리고 닫힌다.**
 *
 * 스크림을 `::backdrop` 이 아니라 팝업 자신이 그린다. `::backdrop` 은 상위 레이어에만
 * 존재해서 `:target` 경로에서는 아예 만들어지지 않는다 — 한 곳에서 그려야 두 경로의
 * 모습이 갈리지 않는다.
 *
 * 팝업 자체가 화면을 채우는 껍데기이고 실제 상자는 `.dialog__panel` 이다. 바깥을
 * 눌러 닫는 판정(`event.target === dialog`)이 상자의 여백까지 "바깥" 으로 세지 않게
 * 하려면 이 두 겹이 필요하다. */

.dialog {
  position: fixed;
  inset: 0;
  z-index: 100;
  width: 100%;
  max-width: none;
  height: 100%;
  max-height: none;
  margin: 0;
  padding: var(--space-lg);
  border: 0;
  background: var(--scrim);
  overflow: hidden;
}

.dialog::backdrop {
  background: transparent;
}

/* `[open]` 은 서버가 내려준 열림(예: 서버가 거부해 팝업을 다시 펴야 할 때)과
   `showModal()` 이 세운 열림 둘 다다. `:target` 은 스크립트 없는 경로다.
   **둘 중 하나라도 지우면 그 경로가 조용히 죽는다.** */
.dialog[open],
.dialog:target {
  display: flex;
  align-items: center;
  justify-content: center;
}

.dialog__panel {
  width: 100%;
  max-width: 520px;
  max-height: 88vh;
  padding: var(--space-xl);
  overflow-y: auto;
  text-align: left;
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--elevation-overlay);
}

.dialog--wide > .dialog__panel {
  max-width: 840px;
}

.dialog__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
  margin-bottom: var(--space-lg);
}

.dialog__title {
  font-size: var(--fs-heading-md);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-heading);
  color: var(--ink);
}

.dialog__close {
  flex: none;
  font-size: var(--fs-button-sm);
  font-weight: var(--fw-medium);
  color: var(--ink-mute);
  text-decoration: none;
}

.dialog__close:hover,
.dialog__close:focus {
  color: var(--primary);
}

.dialog__body {
  font-size: var(--fs-body-sm);
  color: var(--ink-secondary);
}

.dialog__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-sm);
  margin-top: var(--space-xl);
}

/* --- ⑦ 차트 카드 (도넛 자리) -------------------------------------------------
 *
 * DESIGN.md `chart-card`. 담는 상자만 여기서 만들고 그리는 일은 P8 이 한다.
 *
 * **차트는 장식이다.** 같은 값을 서버가 표로 이미 렌더하므로 Plotly 가 안 와도
 * 사라지는 숫자가 없어야 한다 — 그 규율은 기존 `.chart-frame:empty { display: none }`
 * 이 이미 강제하고 있으므로 프레임을 새로 만들지 않고 그것을 그대로 쓴다.
 * 여기서 더하는 것은 **도넛이 들어갈 자리의 크기**뿐이다. 비어 있으면 `:empty` 가
 * 이겨 상자째 접힌다. */

.chart-card {
  padding: var(--space-lg);
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--elevation-card);
}

.chart-frame--donut {
  width: 100%;
  max-width: 340px;
  min-height: 260px;
  margin-right: auto;
  margin-left: auto;
}

/* ===========================================================================
 * KPE P7 — 관리 화면 2컬럼 좌우분할 (`.split`)
 * ===========================================================================
 *
 * 스펙 §6·§7 이 관리자 화면 여섯 곳에 같은 요구를 한다 — 코드관리(좌 그룹선택 / 우
 * 코드수정) · 코드수정 · 위험평가기준 상세 · 사용자목록 · 계정등록 · 프로필. **화면마다
 * 좌우분할을 새로 지으면 여섯 가지 규칙이 생기고**, 그때 고치는 비용은 CSS 한 벌이
 * 아니라 화면 수만큼이다(P2 레이아웃 원시형과 같은 이유).
 *
 * `.doc-grid` 와 혼동하지 않는다. 저쪽은 **항목 짝**(라벨-값 · 라벨-입력)을 두 열로
 * 흘리는 격자이고, 이쪽은 **카드 덩어리**를 좌우 두 단으로 세우는 배치다. 한 페이지에
 * 둘 다 쓴다 — 좌단 안의 폼이 다시 `.doc-grid`/`.form-grid--cols` 로 갈린다.
 *
 * 비율이 세 가지인 이유: 좌우가 대등한 화면(프로필 기본정보/내정보)과 한쪽이 본문이고
 * 반대쪽이 곁다리인 화면(위험평가기준 = 기준 구조 / 버전 메타)이 섞여 있다. **비율을
 * 화면에서 인라인 스타일로 정하지 않는다** — 세 모디파이어 밖이 필요하면 여기에 넷째를
 * 만들고 이름을 붙인다.
 *
 * `minmax(0, …)` 인 이유는 `.doc-grid` 와 같다: 끊을 자리가 없는 값(영문 코드값·해시)이
 * 들어와도 단이 밀려 나가지 않는다. `1fr` 만 쓰면 최소 콘텐츠 폭이 바닥이 되어 좁은
 * 단에 넣은 표가 격자를 통째로 밀어낸다.
 *
 * ⚠ **넓은 표를 좁은 단에 넣지 마라.** 한 단은 본문 폭의 절반 이하다 — 위험평가 7열
 * 그리드·Watch 결과 9행 소스표를 여기 넣으면 `.content--narrow` 를 그 화면에 건 것과
 * 같은 결과가 난다(개발계획 §6 리스크 4). 열 수가 많은 표는 `.split__full` 로 전체
 * 폭에 두거나 `.table-scroll` 안에 둔다.
 *
 * 1100px 미만에서는 한 단으로 접는다 — DESIGN.md §Responsive 의 태블릿 경계이고,
 * 그 아래에서 두 단을 유지하면 각 단이 표 하나도 담지 못한다. */

.split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-xl);
  align-items: start;
}

/* 두 칸의 **높이를 맞춘다.** 한쪽이 길고 한쪽이 짧으면 짧은 쪽 아래가 통째로 빈다 —
   홈의 도넛(580px)과 「처리해야하는 업무」(200px)가 그랬다. 카드 안 내용은 위에서부터
   쌓이고 남는 자리는 카드가 가진다. */
.split--even {
  align-items: stretch;
}

.split--even > .card {
  height: 100%;
}

/* 좌 본문 · 우 곁다리. 위험평가기준 상세(좌 기준 구조 / 우 버전 메타)가 이 모양이다. */
.split--aside {
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
}

/* 좌 곁다리 · 우 본문. 코드관리(좌 코드그룹 선택 / 우 코드 수정)가 이 모양이다 —
   스펙 §6 이 정한 좌우 역할이라 뒤집지 않는다. */
.split--rail {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr);
}

/* 한 단. 카드를 여러 장 쌓을 때 쓴다 — 카드 한 장이면 카드를 바로 `.split` 의 자식으로
   둔다(껍데기를 한 겹 더 두면 `.card + .card` 규칙이 단 사이로 새어 나간다). */
.split__col {
  min-width: 0;
}

.split__col > .card + .card {
  margin-top: var(--space-xl);
}

/* 단을 카드 없이 쓸 때(카드 **안**에서 조회조건을 좌우로 가르는 A-01 목록) 붙이는 제목.
   전역 리셋이 제목 여백을 0 으로 지워 두므로 여기서 한 칸만 되돌린다. */
.split__col > .card-title {
  display: block;
  margin-bottom: var(--space-md);
}

/* 두 단을 가로지르는 줄. 프로필의 계정보안(하단 전체폭)·폼 전체의 저장 버튼 줄이 여기다. */
.split__full {
  grid-column: 1 / -1;
}

/* 격자 안에서는 `.card + .card` 의 세로 여백이 단을 어긋나게 민다 — 간격은 `gap` 이 낸다.
   `.card + .card`(0,2,0)와 같은 특정성으로 걸어야 이긴다 — 전역 리셋은 `*` 라 특정성이
   모자라 밀린다(2026-09-21 전수 검수, home-my-tasks 위 24px 여백). */
.split > .card + .card,
.split > .card + .stack,
.split > .stack + .card {
  margin-top: 0;
}

.card + .split,
.split + .card,
.split + .split,
.stack + .split,
.split + .stack {
  margin-top: var(--space-xl);
}

/* 좌단이 「선택 목록」일 때(A-02 코드그룹) 지금 열려 있는 행. **색만으로 구분하지
   않는다** — 왼쪽 굵은 선이 형태로 남고, 행 안에는 `선택됨` 배지가 텍스트로 남는다
   (AC-34). 배경 틴트는 셋 중 가장 약한 신호라 지워도 선택 상태가 읽힌다. */
.data-table tbody tr[aria-current="true"] > * {
  background: var(--surface-inset);
}

.data-table tbody tr[aria-current="true"] > *:first-child {
  box-shadow: inset 3px 0 0 var(--primary);
}

@media (max-width: 1099px) {
  .split,
  .split--aside,
  .split--rail {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ===========================================================================
 * KPE P8 — 입력 원시형 2종 (`.js-date-mask` · `.checkdrop`)
 * ===========================================================================
 *
 * 둘 다 **공용 클래스 한 벌**이다. 화면마다 날짜칸과 다중선택칸을 새로 지으면 규칙이
 * 화면 수만큼 생기고, 그때 고치는 비용은 CSS 한 벌이 아니라 화면 수만큼이다
 * (`.split` 을 P7 에서 한 벌로 만든 것과 같은 이유).
 *
 * --- 날짜 마스킹 입력 -------------------------------------------------------
 *
 * 네이티브 `<input type="date">` 를 텍스트 입력으로 되돌린 자리다(사용자 확정, §1-D).
 * 모양은 `.field input[type="text"]` 가 이미 잡으므로 여기서는 **숫자 조판만** 더한다 —
 * 자간이 제각각이면 `YYYY-MM-DD` 의 자리수가 눈으로 세어지지 않는다.
 *
 * 폭을 고정하지 않는다. 조회조건 격자(`.form-grid--cols`)의 한 칸을 그대로 쓰는 편이,
 * 화면마다 다른 `max-width` 를 재는 것보다 싸다. */

.field input.js-date-mask {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}

/* --- 체크형 다중선택 (`.checkdrop`) ----------------------------------------
 *
 * **원래 컨트롤은 `<select multiple>` 이고, 그것이 값을 제출한다.** 이 블록이 하는 일은
 * `checkdrop.js` 가 세운 대리 UI 를 그리는 것과, 그 UI 가 섰을 때만 원래 셀렉트를
 * 감추는 것뿐이다. `.checkdrop--on` 은 **스크립트가 붙였다는 증거**라서, 스크립트가
 * 없으면 이 규칙 전체가 적용되지 않고 셀렉트가 그대로 보인다(무JS 기본 동작 보존).
 *
 * `display: none` 인 폼 컨트롤도 값을 제출한다 — 제출을 막는 것은 `disabled` 뿐이다.
 * 그래서 감추는 데 `hidden`/`display:none` 을 써도 조회조건이 사라지지 않는다.
 *
 * 패널은 **자리를 차지하지 않고 떠 있다**(`position: absolute`). 조회조건 격자 안에서
 * 펼칠 때 칸 높이가 늘면 옆 칸들이 함께 밀려 내려가고, 닫으면 다시 올라온다 —
 * 그 흔들림은 「무엇을 골랐는지」보다 먼저 눈에 띈다. 주 메뉴 2depth 와 같은 방식이다. */

.checkdrop {
  min-width: 0;
}

.checkdrop--on .field select[multiple] {
  display: none;
}

.checkdrop__menu {
  position: relative;
}

/* 닫힌 상태에서 브라우저가 패널을 감추는 방식이 엔진마다 다르다 — `display` 하나로만
   가르도록 신형 엔진의 `content-visibility` 를 푼다(주 메뉴와 같은 처리). */
.checkdrop__menu::details-content {
  content-visibility: visible;
}

/* 닫힌 셀렉트박스처럼 보이는 줄. 값은 서버가 렌더한 `<option>` 문구에서 온다. */
.checkdrop__trigger {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  width: 100%;
  min-height: var(--control-height);
  padding: 11px 14px;
  background: var(--canvas);
  border: 1px solid var(--hairline-input);
  border-radius: var(--radius-sm);
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: var(--lh-compact);
  list-style: none;
  cursor: pointer;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.checkdrop__trigger::-webkit-details-marker {
  display: none;
}

/* **글리프를 쓰지 않는다** — 테두리로 캐럿을 그린다(주 메뉴 `::after` 와 같은 이유:
   폰트에 없는 문자나 뭉개진 이스케이프는 CSS 로는 유효해서 화면에서만 드러난다). */
.checkdrop__trigger::after {
  content: "";
  flex: 0 0 auto;
  width: 6px;
  height: 6px;
  margin-left: auto;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform 150ms ease;
}

.checkdrop__menu[open] > .checkdrop__trigger::after {
  transform: translateY(1px) rotate(-135deg);
}

.checkdrop__trigger:focus-visible {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-subdued);
}

.checkdrop__panel {
  display: none;
  position: absolute;
  top: calc(100% + var(--space-xxs));
  left: 0;
  z-index: 40;
  min-width: 100%;
  max-height: 272px;
  padding: var(--space-xs);
  background: var(--canvas);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  box-shadow: var(--elevation-raised);
  overflow-y: auto;
}

.checkdrop__menu[open] > .checkdrop__panel {
  display: block;
}

/* 한 줄 = 라벨과 체크박스가 **한 덩어리**다. 체크박스만 누를 수 있으면 표적이 너무 작다. */
.checkdrop__option {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  font-size: var(--fs-body-sm);
  line-height: var(--lh-compact);
  cursor: pointer;
}

.checkdrop__option:hover {
  background: var(--canvas-soft);
}

.checkdrop__option input[type="checkbox"] {
  flex: 0 0 auto;
  margin: 0;
}

.checkdrop__option-text {
  min-width: 0;
  word-break: keep-all;
}

/* 캐럿 회전도 애니메이션이다 — 주 메뉴와 같은 자리에서 같은 이유로 끈다. */
@media (prefers-reduced-motion: reduce) {
  .checkdrop__trigger::after {
    transition: none;
  }
}

/* ===========================================================================
 * KPE P8 §A — 홈 대시보드 지표 카드 (`.kpi-card`)
 * ===========================================================================
 *
 * DESIGN.md 「대시보드 컴포넌트 12종」의 `kpi-card` 를 그대로 옮긴다:
 * **라벨 15px/500 + 값 `metric-xl`(700 · tnum)**, 면을 한 단계 눌러(`--canvas-sunken`)
 * 카드 안에서 칸이 구분되게 하고 테두리는 `--hairline-table`. **값에 색은 칠하지 않고
 * 굵기로만 강조**한다(2026-07-30 작업자 결정 — 도입부 핵심 정보는 한눈에 들어와야 하고,
 * 네 칸에 각각 색을 주면 어느 색이 무슨 뜻인지가 새 학습거리가 된다).
 * 그림자는 쓰지 않는다 — 면과 선으로 구분한다(카드 안의 카드에 그림자를 겹치면 층이
 * 두 겹으로 읽힌다).
 *
 * 숫자 크기는 `.metric` 이 낸다(`--fs-metric-xl` · 700 · 음수 자간). 이 블록은 **칸**만
 * 그린다 — 타이포를 여기서 다시 정의하면 KPI 서체가 두 곳에서 갈린다.
 *
 * **칸은 항상 4개이고 전부 링크다.** 카드 전체를 `<a>` 로 덮는 이유는 표적 크기다 —
 * 30px 숫자 하나만 링크로 만들면 클릭 지점이 글자 한 자다.
 *
 * `auto-fit` + `minmax` 로 접는다: 1열 폭이 200px 아래로 내려가면 두 줄(2×2)이 되고
 * 좁은 화면에서는 한 줄씩 쌓인다. **개수를 CSS 에 박지 않는다** — 지표가 5개가 되는
 * 날에도 격자가 따라간다(개수는 서버 계약이고 CSS 의 관심사가 아니다). */

/* 그림 + 표 한 쌍 — 같은 값을 다르게 말하는 둘을 나란히 둔다(2026-09-18 전수 검수).
   좁은 화면에서는 한 단으로 접힌다. 도넛 자리가 `:empty` 로 접히면 표가 온 폭을 쓴다. */
.chart-with-table {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-xl);
  align-items: center;
}

@media (max-width: 899px) {
  .chart-with-table {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* 빈 상태는 **가운데**에 둔다. 높이를 맞춘 카드(`.split--even`)에서 위에만 붙어 있으면
   아래가 통째로 빈 것처럼 읽힌다 — 비어 있다는 사실 자체는 문장이 이미 말한다. */
.split--even .empty-state {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 6rem;
}

.kpi-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-md);
  list-style: none;
}

.kpi-row__item {
  min-width: 0;
}

.kpi-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  height: 100%;
  padding: var(--space-lg);
  background: var(--canvas-sunken);
  border: 1px solid var(--hairline-table);
  border-radius: var(--radius-md);
  color: var(--ink);
  text-decoration: none;
}

/* 링크 어포던스는 **면**으로 준다 — 30px 숫자에 밑줄을 그으면 자릿수 판독이 흐려진다. */
.kpi-card:hover {
  background: var(--surface-inset);
  border-color: var(--primary);
  text-decoration: none;
}

.kpi-card:focus-visible {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-subdued);
}

.kpi-card__label {
  font-size: var(--fs-label);
  font-weight: var(--fw-medium);
  line-height: 1.4;
  color: var(--ink-secondary);
  word-break: keep-all;
}

/* 크기·굵기·자간은 `.metric` 이 낸다. 여기서는 **자릿수 정렬**만 확실히 해 둔다 —
   네 칸의 숫자가 세로로 줄이 맞아야 한 덩어리로 읽힌다. */
.kpi-card__value {
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* 화면은 칸마다 `.kpi-card--{key}`(`--total` · `--customer_open` · `--risk_open` ·
   `--due_diligence_open`)를 함께 낸다. **지금 그 선택자에 규칙은 없다** — 값에 색을
   칠하지 않는 것이 DESIGN.md 의 결정이기 때문이다(위 주석). 후크를 미리 두는 이유는
   지표 문구(`label`)가 바뀌어도 `key` 는 바뀌지 않으므로, 나중에 한 칸만 달리 그려야
   할 때 **문구를 선택자로 쓰는 일이 생기지 않게** 하기 위함이다(서버 계약 §A-1). */
