@charset "UTF-8";
/* ==========================================================================
   공통 컴포넌트 — design-system.css의 토큰만 사용합니다.
   화면설계서 v4.24 최종 상태의 모달 10종·드로어 1종·공통 확인 모달의 공통 기반입니다.
   (목록: docs/05-spec-baseline-v4.24.md §3)
   개별 모달을 따로 구현하지 말고 .modal 위에서 조립하세요.
   화면 CSS에 같은 패턴이 이미 있으면 복사하지 말고 여기로 올려서 씁니다
   (2026-09-16 승격: 카드 헤드 슬롯 · .toolbar · .page-subtitle · .cell-sub · .data-table.is-compact · .stack-* · .w-*).
   ========================================================================== */

/* ==========================================================================
   Modal
   - 배경 스크롤 차단은 js/modal.js가 body에 .is-modal-open을 붙여 처리
   - 내부 스크롤은 .modal-body가 담당
   ========================================================================== */
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  background: var(--overlay-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s7);
}
.modal-backdrop[hidden] { display: none; }

body.is-modal-open { overflow: hidden; }

.modal {
  background: var(--bg-surface);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-3);
  width: 560px;
  max-width: 100%;
  max-height: 100%;
  display: flex;
  flex-direction: column;
}

.modal-sm { width: 420px; }
.modal-lg { width: 880px; }
.modal-xl { width: 1120px; }
/* 가로로 긴 입력 그리드를 담는 모달(발주자료 등록 등) — 화면이 좁으면 .modal 의 max-width 가 줄여 준다 */
.modal-wide { width: 1100px; }

.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  padding: var(--s6);
  border-bottom: 1px solid var(--border-default);
  flex-shrink: 0;
}

.modal-title {
  margin: 0;
  font-size: var(--fs-card-title);
  font-weight: var(--fw-bold);
  color: var(--text-default);
}

.modal-close {
  appearance: none;
  background: none;
  border: none;
  padding: 0;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-sm);
  color: var(--icon-default);
  cursor: pointer;
  flex-shrink: 0;
}
.modal-close:hover { background: var(--bg-hover); }

.modal-body {
  padding: var(--s6);
  overflow-y: auto;
  flex: 1;
}

.modal-subject {
  margin: 0 0 var(--s4);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--text-default);
}

.modal-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s3);
  padding: var(--s5) var(--s6);
  border-top: 1px solid var(--border-default);
  flex-shrink: 0;
}

/* 푸터 왼쪽에 따로 두는 보조 액션 (기본값 복원 등) */
.modal-foot-start { margin-right: auto; }
/* 버튼을 왼쪽에 두는 모달 푸터(발주자료 등록의 입력 검증·등록) */
.modal-foot.is-start { justify-content: flex-start; }

/* ==========================================================================
   공통 확인 모달 — js/confirm.js가 한 번만 생성해 재사용
   설계서의 네이티브 confirm() 19곳을 대체합니다. (docs/05 §3-2)
   ========================================================================== */
.modal-confirm .modal-body {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
}

.confirm-icon {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  color: var(--icon-warning);   /* AA 비텍스트 3:1 (이전 Warning 2.69:1) */
}
.modal-confirm[data-tone="danger"] .confirm-icon { color: var(--icon-error); }

.confirm-message {
  margin: 0;
  padding-top: 1px;
  color: var(--text-default);
  white-space: pre-line;
  word-break: keep-all;
  overflow-wrap: anywhere;
}

/* ==========================================================================
   Drawer — SCR-007 입고특이사항 상세
   (SCR-016 알림 패널은 드로어가 아니라 팝오버입니다 — docs/05 §3-3 · docs/plans/SCR-016.md §2)
   ========================================================================== */
.drawer-backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
  background: var(--overlay-drawer);
}
.drawer-backdrop[hidden] { display: none; }

.drawer {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  z-index: var(--z-drawer-panel);
  width: 480px;
  max-width: 100%;
  background: var(--bg-surface);
  box-shadow: var(--sh-3);
  display: flex;
  flex-direction: column;
}
.drawer[hidden] { display: none; }

.drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  padding: var(--s6);
  border-bottom: 1px solid var(--border-default);
  flex-shrink: 0;
}

.drawer-body {
  padding: var(--s6);
  overflow-y: auto;
  flex: 1;
}

/* 드로어 하단 버튼 영역 — .modal-foot과 같은 규격(padding·구분선·오른쪽 정렬)
   보조 액션은 .modal-foot-start처럼 왼쪽에 두려면 .drawer-foot-start를 씁니다.
   열고 닫기·Esc·focus trap은 js/modal.js가 담당합니다(드로어 확장은 JS 담당). */
.drawer-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s3);
  padding: var(--s5) var(--s6);
  border-top: 1px solid var(--border-default);
  flex-shrink: 0;
}
.drawer-foot-start { margin-right: auto; }

/* ==========================================================================
   Card Head 슬롯 — 제목·건수·설명(왼쪽) + 액션(오른쪽)
   design-system.css의 .card-head 두 칸을 채웁니다.
   목록 카드 = 제목 + 건수 + 컨트롤 · 폼 카드 = 제목 + 설명 (+ 액션)
   좁은 폭에서 액션이 아랫줄로 내려가야 하면 .card-head.is-wrap
   (SCR-002-05 목록 머리, SCR-004-13·14 — 이전 page class .list-card-heading·.list-card-controls)
   ========================================================================== */
.card-head-main {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: var(--s2);
  min-width: 0;
}
/* 설명은 제목·건수 아랫줄 (.card-desc 정의는 Form Rows 절) */
.card-head-main > .card-desc { flex-basis: 100%; }

.card-count {
  font-size: var(--fs-base);
  color: var(--text-secondary);
}

.card-head-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: var(--s3);
}
/* 액션 묶음 안 인라인 라벨(정렬방법 등)은 앞 항목과 한 단계 더 띄워 라벨+컨트롤 묶음을 구분합니다 */
.card-head-actions > .toolbar-label:not(:first-child) { margin-left: var(--s2); }

.card-head.is-wrap { flex-wrap: wrap; }

/* ---------- 접었다 펴는 카드 (.card-accordion) ----------
   참고용 안내처럼 늘 펼쳐 둘 필요가 없는 카드에 씁니다.
   <details>/<summary> 로 만들어 스크립트 없이 열고 닫히며, 키보드(Enter·Space)도 그대로 됩니다.
   머리는 .card-head 를 그대로 쓰고 오른쪽 chevron 이 상태를 알립니다. */
.card-accordion > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  cursor: pointer;
  list-style: none;            /* 브라우저 기본 삼각형 제거 */
}
.card-accordion > summary::-webkit-details-marker { display: none; }
.card-accordion > summary:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
  border-radius: var(--r);
}
/* 닫혀 있을 때는 아래 여백이 필요 없습니다 */
.card-accordion:not([open]) > summary { margin-bottom: 0; }

.card-accordion-icon {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--icon-default);
  transition: transform var(--dur-fast) var(--ease-standard);
}
.card-accordion[open] > summary .card-accordion-icon { transform: rotate(180deg); }

/* 접힌 상태에서만 보이는 한 줄 요약 (선택 슬롯) — 제목 오른쪽, chevron 왼쪽에 둡니다.
   펼치면 본문이 보이므로 숨깁니다. 슬롯이 없는 기존 사용처(SCR-018)는 그대로입니다 */
.card-accordion-summary {
  flex: 1;
  min-width: 0;
  font-size: var(--fs-base);
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.card-accordion[open] > summary .card-accordion-summary { display: none; }

/* ==========================================================================
   Toolbar — 표 위 조회 범위 줄 (셀메이트 운영몰·창고 보기처럼 표시 범위만 바꾸는 선택)
   라벨+컨트롤을 .toolbar-item으로 묶고, 좁으면 줄바꿈합니다.
   드롭다운 폭은 마크업에서 .w-md 한 class로 지정합니다(맥락 조합 선택자로 폭을 정하지 않습니다).
   (SCR-002-05·06 상품목록, SCR-004-14 옵션 재고 — 이전 page class .scope-row·.scope-item·.scope-toolbar)
   ========================================================================== */
.toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s2) var(--s4);
  margin-bottom: var(--s4);
}

.toolbar-item {
  display: flex;
  align-items: center;
  gap: var(--s2);
}

/* 컨트롤 앞 인라인 라벨 (조회 범위·정렬방법) */
.toolbar-label {
  font-size: var(--fs-base);
  color: var(--text-secondary);
  white-space: nowrap;
}

/* 조회 조건을 요약해 보여 주는 안내문구 — 컨트롤과 같은 줄에 두지 않고 항상 아랫줄에 둡니다
   (폭에 따라 줄바꿈 여부가 달라지지 않도록 고정 · 1440·1024 배치 동일) */
.toolbar-note { flex-basis: 100%; }

/* 아래 표와 구분선으로 나누는 변형 */
.toolbar.is-divided {
  padding-bottom: var(--s4);
  border-bottom: 1px solid var(--border-default);
}

/* ==========================================================================
   Bulk Bar — 목록 위 일괄 적용 바 (SCR-007-13 입고상태 일괄 적용)
   선택 건수 + 일괄 적용 컨트롤 + 실행 버튼을 한 줄에 둡니다.
   훅 data-bulk-bar는 rules.json DS-BTN-01의 Filled 범위입니다 — 이 바 안의 Filled는 1개.
   고정(sticky)하지 않습니다: 표 바로 위 흐름 안에 두어 새 z-index 층을 만들지 않고
   하단 .action-bar(--z-sticky)·상단 가운데 토스트(--z-toast)와 겹치지 않게 합니다.
   선택 0건이면 hidden, 1건 이상이면 노출합니다(건수는 aria-live="polite").
   ========================================================================== */
.bulk-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s2) var(--s3);
  margin-bottom: var(--s4);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--border-selected);
  border-radius: var(--r);
  /* 회색 바탕 — 안에 드롭다운이 있어 입력 경계 3:1이 필요(선택 배경 #F0F0F0 위 2.91 미달 · bg-subtle 위 3.07) */
  background: var(--bg-subtle);
}

.bulk-bar-count {
  margin: 0;
  font-weight: var(--fw-semibold);
  color: var(--text-default);
  white-space: nowrap;
}
/* 건수 숫자만 강조 — 글자(건수)가 정보이고 색은 보조입니다 */
.bulk-bar-count strong { color: var(--text-selected); }

.bulk-bar-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-left: auto;
}

/* ==========================================================================
   Page Subtitle — 화면 제목 아래 식별값 줄 (상품코드·상품바코드 등)
   ========================================================================== */
.page-subtitle {
  margin: var(--s1) 0 0;
  font-size: var(--fs-base);
  color: var(--text-secondary);
}
.page-subtitle strong { color: var(--text-default); font-weight: var(--fw-semibold); }

/* ==========================================================================
   Table 보조 — 셀 보조 줄 · 촘촘한 입력 표 (표 기본은 design-system.css)
   ========================================================================== */
/* 값 아래 한 줄로 붙는 보조 정보 (옵션코드·운영몰·매칭 근거 뱃지) */
.cell-sub {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s0-5) var(--s2);
  margin-top: var(--s1);
  font-size: var(--fs-caption);
  color: var(--text-secondary);
  word-break: break-all;   /* 띄어쓰기 없는 긴 옵션코드 */
}

/* 행마다 입력이 있는 표 — 칸 여백을 줄입니다 (옵션·실측·판매처별 등록정보, SCR-007-09·SCR-014-02) */
.data-table.is-compact :where(th, td) { padding: var(--s2); }

/* ==========================================================================
   Table Row State — 선택 열(cell-check) · 선택 행 · 오류 행 · 머리글 고정
   표 기본(칸 여백·정렬·셀 유틸리티)은 design-system.css, 여기에는 행 단위 상태와 스크롤 변형만 둡니다.
   (SCR-007-13 일괄 적용 · SCR-015-03 확인필요 행 · SCR-007-09·10 입력표 검증)
   ========================================================================== */
/* 선택 열 — 머리글은 '전체 선택' 체크박스, 본문 칸은 행 선택 체크박스.
   칸 폭을 고정해 선택 여부에 따라 표가 흔들리지 않게 합니다. */
.data-table :is(th, td).cell-check {
  width: var(--s8);
  padding-inline: var(--s2);
  text-align: center;
}
/* 체크박스만 든 칸이라 라벨 글자 자리(gap)를 두지 않습니다 (이름은 .sr-only·aria-label로) */
.data-table :is(th, td).cell-check .check { gap: 0; }

/* 행 상태 — 선택 / 오류
   우선순위: 오류 > 선택 > hover.
   배경을 행(tr)이 아니라 칸(td·th)에 칠합니다 — .is-clickable:hover가 tr에 칠하는 배경보다 앞에 보여
   특이성 우회(:root·중복 class) 없이 우선순위가 정해집니다. 아래 두 규칙의 순서가 곧 우선순위입니다.
   색만으로 뜻을 전달하지 않습니다 — 선택은 체크박스, 오류는 사유 글자·뱃지가 함께 전달합니다(WCAG 1.4.1). */
.data-table tbody tr.is-selected :where(td, th) { background: var(--bg-selected); }

/* 오류 행 — SCR-015-03 확인필요 행, SCR-007-10 입력표 검증 오류
   (기획서의 tr.is-invalid는 쓰지 않습니다 — 기존 .is-error와 이름을 맞췄습니다) */
.data-table tbody tr.is-error :where(td, th) { background: var(--bg-error-subtle); }
/* 행 머리 표시 — 첫 칸 안쪽 3px 띠 (레이아웃을 밀지 않게 inset box-shadow) */
.data-table tbody tr.is-error :where(td, th):first-child { box-shadow: inset 3px 0 0 var(--border-error); }

/* 머리글 고정 — 긴 입력표(최대 1,000행)에서 열 이름이 사라지지 않게
   기존 .table-scroll(가로 스크롤) 위에 세로 스크롤을 더한 변형입니다.
   최대 높이는 화면마다 다르므로 page CSS에서 max-height만 조정합니다(DS-LAYOUT-01 허용 속성). */
.table-scroll.is-sticky-head {
  max-height: 60vh;
  overflow: auto;
}
.table-scroll.is-sticky-head .data-table thead th {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  /* 아래선·열 구분선은 기본 머리글 규칙(design-system.css)의 안쪽 그림자를 그대로 씁니다 */
}

/* ---------- 첫 열 고정 (.is-sticky-col) ----------
   열이 많아 가로로 넘치는 표에서 첫 열을 붙잡아 둡니다.
   근거: 화면보다 큰 표는 머리글 행과 머리글 열을 함께 고정하라는 데이터 표 권고
   (Nielsen Norman Group, "Data Tables: Four Major User Tasks").
   머리글 고정(.is-sticky-head)과 함께 쓰면 좌상단 모서리 칸도 겹쳐 고정됩니다. */
.table-scroll.is-sticky-col .data-table tbody :first-child:is(th, td) {
  position: sticky;
  left: 0;
  /* z-index 를 주지 않습니다 — 머리글(.is-sticky-head, --z-sticky)이 항상 위에 오도록
     쌓임 순서만 씁니다. 새 z 층을 만들지 않으므로 z 계약(DS-TOKEN-04)도 그대로입니다. */
  background: var(--bg-surface);
  /* 스크롤된 내용과 겹칠 때 경계를 보여 줍니다 */
  box-shadow: inset -1px 0 0 var(--border-default);
}
.table-scroll.is-sticky-col .data-table thead :first-child:is(th, td) {
  position: sticky;
  left: 0;
  background: var(--bg-subtle);
  box-shadow: inset -1px 0 0 var(--border-default), inset 0 -1px 0 var(--border-strong);
}
/* 머리글까지 고정한 표에서는 좌상단 모서리 칸이 두 방향으로 함께 고정됩니다 */
.table-scroll.is-sticky-col.is-sticky-head .data-table thead :first-child:is(th, td) {
  box-shadow: inset -1px 0 0 var(--border-default), inset 0 -1px 0 var(--border-strong);
}

/* 고정한 첫 칸이 불투명 배경을 깔기 때문에 행 상태(오류 > 선택 > hover)가 그 칸에서만
   지워집니다. 우선순위를 그대로 살리려고 고정 규칙 뒤에서 다시 칠합니다.
   순서가 곧 우선순위라는 위 행 상태 절의 계약을 여기서도 지킵니다. */
.table-scroll.is-sticky-col .data-table tbody tr.is-clickable:hover :first-child:is(th, td) {
  background: var(--bg-row-hover);
}
.table-scroll.is-sticky-col .data-table tbody tr.is-selected :first-child:is(th, td) {
  background: var(--bg-selected);
}
.table-scroll.is-sticky-col .data-table tbody tr.is-error :first-child:is(th, td) {
  background: var(--bg-error-subtle);
  /* 오류 행 머리 표시(안쪽 3px 띠)와 고정 칸 경계선을 함께 그립니다 */
  box-shadow: inset 3px 0 0 var(--border-error), inset -1px 0 0 var(--border-default);
}

/* ==========================================================================
   Filter — 검색조건 레이아웃
   항목명을 왼쪽 고정 열, 입력값을 오른쪽에 두고 항목별 행을 세로로 쌓습니다.
   같은 항목의 기간·복수 입력은 오른쪽 영역 안에서 줄바꿈합니다.
   (설계서 SCR-002-02, SCR-008-03·010-02·013-02·018-02 "필터 배치")
   2026-10-06 영역 구분: 항목별 행을 구분선으로 나누고 항목명 열을 회색 칸으로 둡니다(표 형태 검색 영역).
   위선은 강조 경계, 행 사이는 기본 경계. 항목명 칸 배경은 행 배경으로 그려 오류 문구 줄까지 이어집니다.
   ========================================================================== */
.filter-rows {
  display: flex;
  flex-direction: column;
  margin: 0;
  border-top: 1px solid var(--border-strong);
}

.filter-row {
  display: grid;
  grid-template-columns: var(--w-filter-label) minmax(0, 1fr);
  align-items: start;
  gap: var(--s2) var(--s4);
  padding: var(--s1-5) 0;   /* 행 높이 = 이전 행 간격(12px)과 같게 — 칸 형태로 바꾸며 검색 영역이 길어지지 않도록 */
  border-bottom: 1px solid var(--border-default);
  /* 항목명 칸 배경 — 첫 열 폭만큼 단색 이미지로 칠합니다(행 높이 전체 · 오류 문구 줄 포함).
     글자 대비는 contrast-pairs filter-label-on-subtle(--text-default / --bg-subtle 11.69)로 검사 */
  background-image: linear-gradient(var(--bg-subtle), var(--bg-subtle));
  background-size: var(--w-filter-label) 100%;
  background-repeat: no-repeat;
}

.filter-label {
  min-height: var(--h-input);
  display: flex;
  align-items: center;
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  color: var(--text-default);
}
/* 첫 열(항목명 칸)만 칸 안쪽 여백 — 행 안에서 컨트롤 사이에 놓인 보조 항목명(.filter-control 안)은 그대로 */
.filter-row > .filter-label { padding-left: var(--s4); }

.filter-control {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2) var(--s3);
  min-width: 0;
}

.filter-control .dropdown { width: var(--w-control-md); }
.filter-control > .input { width: auto; flex: 1 1 280px; }

/* 행 단위 오류 — 입력값 아래에 문구 표시. 노출(.filter-row.is-error)·경계(잘못된 입력의 aria-invalid)는
   design-system.css 오류 표시 계약 */
.filter-row .field-error { grid-column: 2; margin: 0; }

.filter-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
  margin-top: var(--s5);
  padding-top: var(--s5);
  border-top: 1px solid var(--border-default);
}
/* 칸 형태 검색 영역 바로 뒤에서는 마지막 행 구분선이 경계를 대신합니다 (선 두 줄 방지) */
.filter-rows + .filter-foot {
  padding-top: 0;
  border-top: 0;
}

.filter-foot-actions {
  display: flex;
  gap: var(--s3);
  margin-left: auto;
}

/* ---------- 축소형 (.is-compact) ----------
   목록 화면의 검색조건 기본. 컨트롤은 .btn-sm·.input-sm·.dropdown-sm(40px)과 함께 쓰고,
   라벨 줄 높이와 아래 줄 여백만 그에 맞춰 줄입니다. 기본값(.filter-*)은 그대로라 다른 화면은 바뀌지 않습니다 */
.filter-rows.is-compact .filter-label { min-height: var(--h-control-sm); }
.filter-foot.is-compact {
  margin-top: var(--s4);
  padding-top: var(--s4);
}
.filter-rows.is-compact + .filter-foot.is-compact { padding-top: 0; }

/* ---------- 범위 입력 (금액 최소 ~ 최대) ---------- */
.range-field {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
}

.range-field-label {
  min-width: 48px;
  font-size: var(--fs-base);
  color: var(--text-secondary);
  white-space: nowrap;
}

.range-field .input {
  width: var(--w-control-sm);
  padding: 0 var(--s4);
  text-align: right;
}

/* 같은 행에 범위 입력이 이어지면 그룹 사이를 띄움 (공급가 ~ 원 | 판매가 ~ 원) */
.range-field + .range-field { margin-left: var(--s4); }

.range-sep {
  color: var(--text-secondary);   /* 범위 기호도 정보 글자 — AA (이전 Gray 400) */
  font-size: var(--fs-base);
}
/* 단위 표기 .input-suffix는 design-system.css Form 절에 있습니다 */

/* ---------- 기간 입력 (빠른 기간 + 시작일 ~ 종료일) ---------- */
.period-quick {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--s1);
}

/* 시작일 ~ 종료일은 한 묶음으로 줄바꿈 */
.period-dates {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
}

.date-field {
  position: relative;
  display: inline-flex;
  align-items: center;
}

.date-field .input {
  width: var(--w-control-md);
  padding: 0 var(--input-icon-slot) 0 var(--s4);
}

/* 달력 아이콘은 디자인시스템 SVG로 표시하고, 브라우저 기본 아이콘은 투명하게 겹쳐 클릭만 받습니다 */
.date-field .icon {
  position: absolute;
  right: var(--s4);
  display: inline-flex;
  color: var(--icon-default);
  pointer-events: none;
}

.date-field .input::-webkit-calendar-picker-indicator {
  position: absolute;
  right: 0;
  width: var(--input-icon-slot);
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

/* ==========================================================================
   Popover — 클릭으로 여는 안내 (경고 사유 등)
   hover 전용인 .tooltip과 달리 클릭·키보드로 열고 Esc·바깥 클릭으로 닫습니다.
   동작: js/popover.js
   ========================================================================== */
.popover {
  position: absolute;
  z-index: var(--z-popover);
  width: max-content;
  max-width: 320px;
  padding: var(--s4);
  border-radius: var(--r);
  background: var(--bg-surface);
  box-shadow: var(--sh-2);
  font-size: var(--fs-caption);
  color: var(--text-default);
  text-align: left;
  white-space: normal;
  cursor: default;
}

.popover.is-floating {
  position: fixed;
  top: var(--popover-top, 0);
  left: var(--popover-left, 0);
  z-index: var(--z-floating);
}

.popover-title {
  margin: 0 0 var(--s2);
  font-size: var(--fs-base);
  font-weight: var(--fw-bold);
  color: var(--text-default);
}

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

.popover-list li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s1) var(--s2);
  align-items: baseline;
}

.popover-list .popover-desc {
  grid-column: 1 / -1;
  color: var(--text-secondary);
}

/* 경고 표시 버튼 — 목록 행의 느낌표. 선택하면 팝오버로 사유 안내 (SCR-002-09)
   보이는 원은 20px 그대로, 클릭 영역은 ::before로 24px (WCAG 2.5.8 — 배치 영향 없음) */
.row-warning-mark {
  appearance: none;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  margin-top: 1px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--fill-warning);
  color: var(--icon-on-fill);
  cursor: pointer;
  flex-shrink: 0;
}
.row-warning-mark::before {
  content: "";
  position: absolute;
  inset: calc(var(--s0-5) * -1);   /* 사방 2px 확장 → 24×24 사각 영역 */
}
.row-warning-mark svg { display: block; }
/* 펼침(사유 팝오버 열림) — 흰 2px 틈 + 2px 링 (링 흰 5.25 · 행 hover 4.85). 포커스 링(outline)과 속성이 달라 겹쳐도 둘 다 유지 */
.row-warning-mark[aria-expanded="true"] { box-shadow: 0 0 0 2px var(--bg-surface), 0 0 0 4px var(--border-warning-strong); }

/* ==========================================================================
   Notification Panel — 탑바 알림 버튼의 팝오버 (SCR-016 업무요청 알림)
   .popover와 **함께** 씁니다: .popover가 층·그림자·띄우기(js/popover.js)를 담당하고
   이 변형이 폭·머리/목록/푸터 3단 레이아웃만 덮습니다(.popover 뒤에 정의 — 같은 특이성, 순서로 이김).
   드로어가 아닙니다(docs/05 §3-3 · docs/plans/SCR-016.md §2).
   ========================================================================== */
.notification-panel {
  width: 360px;
  max-width: none;          /* .popover의 320px 상한 해제 — 1024에서도 popover.js가 뷰포트 안으로 클램프 */
  max-height: min(70vh, 520px);
  padding: 0;               /* 머리·목록·푸터가 각자 여백을 가집니다 */
  font-size: var(--fs-base);
  display: flex;
  flex-direction: column;
  overflow: hidden;         /* 목록 스크롤이 둥근 모서리 밖으로 나가지 않게 */
}

.noti-head {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s4);
  border-bottom: 1px solid var(--border-default);
  flex-shrink: 0;
}
.noti-head .popover-title { margin: 0; }
/* 안읽음 건수(평문) 뒤로 액션을 밀어냅니다 — 액션 유무와 무관하게 자리 고정 */
.noti-head .text-caption { margin-right: auto; }

.noti-list {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  list-style: none;
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;
}

/* 한 건 = 점(안읽음) + 링크 본문 + 개별 읽음 버튼. 버튼을 링크 안에 넣지 않습니다 */
.noti-item {
  display: grid;
  grid-template-columns: var(--s2) minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--s1) var(--s2);
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--border-subtle);
}
.noti-item:last-child { border-bottom: none; }
.noti-item:hover { background: var(--bg-hover); }
.noti-item:focus-within { background: var(--bg-hover); }

/* 안읽음 표시 — 색만으로 전달하지 않도록 마크업에 .sr-only "읽지 않음"을 함께 둡니다 */
.noti-dot {
  width: var(--s2);
  height: var(--s2);
  margin-top: var(--s2);
  border-radius: 50%;
  background: var(--fill-strong);
}

.noti-link {
  grid-column: 2;           /* 읽은 알림에서 점이 hidden이어도 본문 열 유지 */
  grid-row: 1;
  display: grid;
  gap: var(--s1);
  min-width: 0;
  color: var(--text-default);
  text-decoration: none;
}
.noti-link .badge { justify-self: start; }

.noti-title {
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
  word-break: keep-all;
  overflow-wrap: anywhere;
}
/* 읽은 알림은 제목 굵기를 낮춥니다 (안읽음과의 차이를 색 말고도 전달) */
.noti-item:not(.is-unread) .noti-title { font-weight: var(--fw-regular); }

.noti-desc {
  font-size: var(--fs-caption);
  color: var(--text-secondary);
  line-height: var(--lh-snug);
  word-break: keep-all;
  overflow-wrap: anywhere;
}

.noti-time {
  font-size: var(--fs-caption);
  color: var(--text-secondary);
}

.noti-foot {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s3) var(--s4);
  border-top: 1px solid var(--border-default);
  flex-shrink: 0;
}

/* 시스템 알림(카페24 재인증 등)의 이동 버튼 — 본문 열(2열) 아래 한 줄로 깔아, 점(1열)·개별 읽음
   버튼(3열)의 자리를 건드리지 않습니다. 업무요청 항목에는 이 요소가 hidden 이라 레이아웃이 그대로입니다. */
.noti-action {
  grid-column: 2;
  grid-row: 2;
  justify-self: start;
  margin-top: var(--s1);
}
/* .btn 의 display:inline-flex 가 UA 규칙 [hidden]{display:none} 을 눌러서 숨김이 먹지 않는다 — 되돌린다
   (page-accounts.css 의 .pagination[hidden] 과 같은 이유). 이게 없으면 업무요청 항목에 빈 버튼이 남는다. */
.noti-action[hidden] { display: none; }

/* 비어 있음·로딩·조회 실패는 .state-block을 그대로 쓰되 패널 폭에 맞춰 여백만 줄입니다 */
.notification-panel .state-block { padding: var(--s7) var(--s4); }

/* ==========================================================================
   Form Rows — 한 행 한 입력항목 폼 (SCR-004 기준정보, 관리자 편집 폼)
   항목명 왼쪽 고정 열, 입력·도움말·오류 문구는 오른쪽 열에 세로로 쌓습니다.
   ========================================================================== */
.form-rows {
  display: flex;
  flex-direction: column;
  gap: var(--s5);
}

.form-row {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr);
  gap: var(--s2) var(--s5);
  align-items: start;
}

.form-label {
  min-height: var(--h-input);
  display: flex;
  align-items: center;
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  color: var(--text-default);
}
/* 필수 표시(*)는 design-system.css의 전역 .required 한 곳에서 정합니다 */

/* 입력칸이 아닌 묶음(표·체크박스)에는 라벨을 위쪽에 맞춤 */
.form-row.is-top .form-label { min-height: 0; padding-top: var(--s2); }

.form-control {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  min-width: 0;
}

.form-control .field-help,
.form-control .field-error { margin: 0; }

.form-inline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2) var(--s3);
}

.form-control > .input,
.form-control > .textarea { width: 100%; max-width: 726px; }
.form-control .input-unit { width: var(--w-control-lg); text-align: right; }

/* 오류(.form-row.is-error + 대상 컨트롤 aria-invalid)의 문구 노출·경계는 design-system.css 오류 표시 계약 */

/* 섹션 단위 오류 (옵션 표·도메인 선택 등) — .field-error의 폼·섹션 변형. 글자·노출 규칙은 design-system.css와 공유, 여기서는 여백만 */
.section-error { margin: var(--s3) 0 0; }

/* ---------- 항목 묶음 소제목 (.form-group-title) ----------
   한 카드 안에서 항목을 의미별로 묶을 때 행 사이에 두는 소제목(h3 등). 래퍼 없이 .form-rows의 직계 자식으로 둡니다.
   위 묶음과는 옅은 구분선으로 나누고, 첫 묶음(:first-child)은 구분선·위 여백 없이 시작합니다 */
.form-rows > .form-group-title {
  margin: var(--s2) 0 0;
  padding-top: var(--s4);
  border-top: 1px solid var(--border-subtle);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--text-default);
}
.form-rows > .form-group-title:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

/* ---------- 폼 섹션 카드 ---------- */
/* 탑바·바로가기 바 높이 보정은 html의 scroll-padding(layout.css)이 맡고, 여기서는 여백만 */
.form-card { margin-bottom: var(--s5); scroll-margin-top: var(--s4); }

.card-desc {
  margin: var(--s1) 0 0;
  font-size: var(--fs-caption);
  color: var(--text-secondary);
}

/* ==========================================================================
   Section Nav — 긴 폼의 입력영역 바로가기 (탑바 아래에 붙음)
   ========================================================================== */
.section-nav {
  position: sticky;
  top: var(--topbar-h);
  z-index: var(--z-section-nav);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1);
  margin: 0 calc(var(--page-x) * -1) var(--s5);
  padding: var(--s3) var(--page-x);
  background: var(--bg-page);
  border-bottom: 1px solid var(--border-default);
}

.section-nav a {
  padding: var(--s1-5) var(--s3);
  border-radius: var(--r-pill);
  font-size: var(--fs-base);
  color: var(--text-secondary);
  text-decoration: none;
  white-space: nowrap;
}
.section-nav a:hover { background: var(--bg-hover); color: var(--text-default); }
.section-nav a[aria-current="true"] { background: var(--bg-selected); color: var(--text-selected); font-weight: var(--fw-semibold); box-shadow: inset 0 0 0 1px var(--border-selected); }   /* 둥근 칸이라 밑줄 대신 1px 윤곽 — 배경만으로는 3:1 미달 */

/* 오류가 있는 섹션 — 저장 시 검증에 걸린 섹션 링크를 오류색으로, 건수는 .section-nav-count (aria-current보다 뒤라 현재 섹션이어도 오류색 유지).
   건수 뱃지: 20px 알약 · 흰 글자 on --fill-error (AA 5.38, 알림 건수와 같은 쌍) · 글자 --fs-xs(12px) 이상 */
.section-nav a.is-error,
.section-nav a.is-error:hover { color: var(--text-error); }
.section-nav-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  margin-left: var(--s1);
  padding: 0 var(--s1-5);
  border-radius: var(--r-pill);
  background: var(--fill-error);
  color: var(--text-on-fill);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
}

/* ==========================================================================
   Action Bar — 하단 고정 저장 영역 (긴 폼)
   ========================================================================== */
.action-bar {
  position: sticky;
  bottom: 0;
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s3);
  margin: var(--s6) calc(var(--page-x) * -1) calc(var(--page-y) * -1);
  padding: var(--s4) var(--page-x);
  background: var(--bg-surface);
  border-top: 1px solid var(--border-default);
  box-shadow: var(--sh-up);
}

.action-bar-status {
  margin: 0 auto 0 0;
  font-size: var(--fs-base);
  color: var(--text-secondary);
}

/* ---------- 버튼 묶음 (보기 전환 등, aria-pressed) ---------- */
.btn-group {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--s1);
}

/* ==========================================================================
   Upload Tiles — 이미지 업로드 (SCR-004-07)
   대표 1개 + 추가 N개. 선택·순서·삭제는 버튼으로 제공합니다.
   ========================================================================== */
.upload-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: var(--s3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.upload-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border-default);
  border-radius: var(--r);
  background: var(--bg-surface);
  overflow: hidden;
}
.upload-tile.is-primary { border-color: var(--border-selected); box-shadow: 0 0 0 1px var(--border-selected); }

.upload-tile-img {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: contain;
  background: var(--bg-subtle);
}

.upload-tile-badge {
  position: absolute;
  top: var(--s2);
  left: var(--s2);
}

.upload-tile-meta {
  padding: var(--s2) var(--s3) 0;
  font-size: var(--fs-caption);
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.upload-tile-actions {
  display: flex;
  align-items: center;
  gap: var(--s1);
  padding: var(--s2) var(--s3) var(--s3);
}
.upload-tile-actions .link-btn { margin-right: auto; padding: var(--s1) var(--s2); font-size: var(--fs-caption); }
.upload-tile-actions .icon-btn { width: 28px; height: 28px; }

/* 추가 타일 — label로 파일 선택 input을 연결 */
.upload-tile-add {
  align-items: center;
  justify-content: center;
  gap: var(--s1);
  min-height: 148px;
  border-style: dashed;
  color: var(--text-secondary);
  cursor: pointer;
  text-align: center;
}
.upload-tile-add:hover { border-color: var(--border-selected); color: var(--text-selected); }
/* 파일 input이 label 안에 있든(focus-within) 바로 뒤 형제이든(:has) 포커스 링을 표시 */
.upload-tile-add:focus-within,
.upload-tile-add:has(+ input:focus-visible) { outline: 2px solid var(--border-focus); outline-offset: 2px; }
.upload-tile-add.is-disabled { color: var(--text-disabled); background: var(--bg-disabled); cursor: not-allowed; }

/* ==========================================================================
   File Upload — 단일 파일 선택 (SCR-015-02 엑셀등록 · SCR-007-09 자료등록)
   여러 이미지를 격자로 다루는 .upload-tiles와 달리 문서 파일 1개를 고르고 바꾸는 부품입니다.
   구조: 선택 영역(label) + 파일 input(.sr-only, label 바로 뒤 형제) + 선택된 파일 줄
   드래그 드롭은 보조 수단입니다 — 클릭·키보드(Enter·Space)만으로 모든 동작이 됩니다.
   상태: 선택 전(파일 줄 hidden) · 선택됨 · 검증 중(버튼 aria-busy + .state-block) ·
         드래그 중(.is-dragover) · 비활성(.is-disabled + input disabled) · 오류(.section-error)
   ========================================================================== */
.file-upload {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
}

.file-upload-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s1);
  padding: var(--s7) var(--s5);
  border: 1px dashed var(--border-input);
  border-radius: var(--r);
  background: var(--bg-surface);
  color: var(--text-default);
  text-align: center;
  cursor: pointer;
}
.file-upload-drop:hover { border-color: var(--border-selected); color: var(--text-selected); }
/* 파일 input이 label 안에 있든(focus-within) 바로 뒤 형제이든(:has) 포커스 링을 타일에 표시 */
.file-upload-drop:focus-within,
.file-upload-drop:has(+ input:focus-visible) { outline: 2px solid var(--border-focus); outline-offset: 2px; }

/* 형식 안내 (XLSX · CSV 등) — 보조 글자 */
.file-upload-hint {
  font-size: var(--fs-caption);
  color: var(--text-secondary);
}

/* 드래그 중 — 상태 class는 .file-upload(뿌리)에 붙입니다(dragenter·dragleave를 한 곳에서 다룸) */
.file-upload.is-dragover .file-upload-drop {
  border-color: var(--border-selected);
  border-style: solid;
  background: var(--bg-selected);
  color: var(--text-selected);
}

/* 선택된 파일 줄 — 파일명 + 부가정보 + 상태 뱃지 + 교체·삭제 액션 */
.file-upload-file {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s2);
  margin: 0;
  padding: var(--s3) var(--s4);
  border: 1px solid var(--border-default);
  border-radius: var(--r);
  background: var(--bg-surface);
}

.file-upload-name {
  font-weight: var(--fw-semibold);
  color: var(--text-default);
  word-break: break-all;   /* 띄어쓰기 없는 긴 파일명 */
  min-width: 0;
}

/* 크기·행 수 등 부가정보 */
.file-upload-meta {
  font-size: var(--fs-caption);
  color: var(--text-secondary);
}

.file-upload-actions {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin-left: auto;
}

/* 비활성 — 권한 없음·처리 중. input에도 disabled를 함께 겁니다(포인터·키보드 차단) */
.file-upload.is-disabled .file-upload-drop,
.file-upload.is-disabled .file-upload-drop:hover {
  border-color: var(--border-disabled);
  background: var(--bg-disabled);
  color: var(--text-disabled);
  cursor: not-allowed;
}
.file-upload.is-disabled .file-upload-hint { color: var(--text-disabled); }

/* ==========================================================================
   Stat — 지표 카드 (성과·추이 모달, SCR-005 요약, SCR-007 수량 요약)
   ========================================================================== */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: var(--s3);
  margin: 0;
}

.stat {
  display: flex;
  flex-direction: column;
  gap: var(--s1);
  padding: var(--s4);
  border: 1px solid var(--border-default);
  border-radius: var(--r);
  background: var(--bg-surface);
}

.stat dt,
.stat-label {
  margin: 0;
  font-size: var(--fs-caption);
  color: var(--text-secondary);
}

.stat dd,
.stat-value {
  margin: 0;
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  color: var(--text-default);
}

.stat-unit {
  margin-left: var(--s0-5);
  font-size: var(--fs-caption);
  font-weight: var(--fw-regular);
  color: var(--text-secondary);
}

/* 카드 수와 무관하게 한 줄에 균등 분할 — 요약 카드가 4개 이하일 때 (기본 auto-fill 은 빈 칸이 생깁니다).
   1279 이하에서는 2열로 접힙니다 (파일 끝 @media). */
.stat-grid.is-fixed {
  grid-template-columns: none;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
}

/* ---------- 클릭형 지표 카드 (누르면 조회범위가 바뀌는 요약 카드, SCR-005) ---------- */
/* 마크업은 <button class="stat is-clickable" aria-pressed> 입니다 — 선택은 aria-pressed 하나로 정합니다. */
.stat.is-clickable {
  width: 100%;
  text-align: left;
  font: inherit;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
}
.stat.is-clickable:hover { background: var(--bg-hover); border-color: var(--border-selected); }
.stat.is-clickable:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; }
.stat.is-clickable[aria-pressed="true"] { background: var(--bg-selected); border-color: var(--border-selected); }
.stat.is-clickable[aria-pressed="true"] .stat-value { color: var(--text-selected); }
.stat.is-clickable[aria-disabled="true"] {
  background: var(--bg-disabled);
  border-color: var(--border-default);
  cursor: not-allowed;
}
.stat.is-clickable[aria-disabled="true"] .stat-label,
.stat.is-clickable[aria-disabled="true"] .stat-value { color: var(--text-disabled); }

/* 응답이 필요한 건수는 강조 색으로 표시합니다 (설계서 '접수된 취소 요청') */
.stat-emphasis .stat-value { color: var(--text-warning); }
.stat-emphasis.is-clickable[aria-pressed="true"] .stat-value { color: var(--text-warning); }

/* ---------- 2열 입력 배치 (모달 안 폼 등) ---------- */
.field-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s4) var(--s5);
}
.field-grid .is-full { grid-column: 1 / -1; }
.field-grid .input,
.field-grid .dropdown { width: 100%; }

/* ---------- 결과 목록 (조회·매칭 결과 등) ---------- */
.result-list {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  list-style: none;
  border: 1px solid var(--border-default);
  border-radius: var(--r);
}

.result-item {
  display: grid;
  grid-template-columns: 140px minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--border-default);
}
.result-item:last-child { border-bottom: none; }
.result-item-title { font-weight: var(--fw-semibold); }
.result-item-desc { display: block; font-size: var(--fs-caption); color: var(--text-secondary); }

/* ---------- 모달 안 조회 조건 줄 ---------- */
.modal-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--s3);
  margin-bottom: var(--s5);
}
.modal-toolbar .field { gap: var(--s1); }
.modal-toolbar .dropdown { width: var(--w-control-md); }

.modal-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s1) var(--s3);
  margin-bottom: var(--s4);
}
.modal-summary strong { font-size: var(--fs-lg); }

.modal-section-title {
  margin: var(--s6) 0 var(--s3);
  font-size: var(--fs-base);
  font-weight: var(--fw-bold);
  color: var(--text-default);
}

/* ==========================================================================
   Pagination — SCR-002 주석 07
   전체 페이지 = ceil(N/S) · 시작 = (P-1)*S+1 · 끝 = min(P*S, N)
   0건이면 페이지 이동을 제공하지 않습니다.
   ========================================================================== */
.pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  margin-top: var(--s5);
}

.pagination-range {
  font-size: var(--fs-base);
  color: var(--text-secondary);
  white-space: nowrap;
}

.pagination-nav {
  display: flex;
  align-items: center;
  gap: var(--s1);
}

.page-btn {
  appearance: none;
  min-width: 36px;
  height: 36px;
  padding: 0 var(--s2);
  border: 1px solid var(--border-default);
  border-radius: var(--r-sm);
  background: var(--bg-surface);
  font-size: var(--fs-base);
  font-family: inherit;
  color: var(--text-default);
  cursor: pointer;
}
.page-btn:hover:not(:disabled) { border-color: var(--border-hover); }

.page-btn[aria-current="page"] {
  background: var(--fill-strong);
  border-color: var(--fill-strong);
  color: var(--text-on-fill);
  font-weight: var(--fw-bold);
}

/* 비활성은 이전·다음 화살표(SVG)뿐 — 아이콘 비활성 색 */
.page-btn:disabled {
  color: var(--icon-disabled);
  cursor: not-allowed;
  background: var(--bg-disabled);
}

/* 번호 생략 표시(…) — 장식(aria-hidden), js/pagination.js가 생성 */
.pagination-gap { color: var(--text-secondary); }

/* ==========================================================================
   상태 표시 — 로딩 / 데이터 없음 / 검색결과 없음 / 조회 실패
   요청목록 '공통 작업' 항목
   ========================================================================== */
.state-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s3);
  padding: var(--s8) var(--s6);
  text-align: center;
  color: var(--text-secondary);
}

.state-block .state-title {
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  color: var(--text-default);
}

.state-block .state-desc {
  font-size: var(--fs-caption);
  color: var(--text-secondary);
  margin: 0;
}

.state-error .state-title { color: var(--text-error); }

/* 로딩 스피너 */
.spinner {
  width: 28px;
  height: 28px;
  border: 3px solid var(--border-default);
  border-top-color: var(--border-selected);
  border-radius: 50%;
  animation: spin var(--dur-spin) linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .spinner { animation-duration: var(--dur-spin-reduced); }
}

/* 처리 중 버튼 — 아이콘만 회전 (버튼은 aria-disabled + aria-busy로 비활성 모양) */
@media (prefers-reduced-motion: no-preference) {
  .btn[aria-busy="true"] .btn-icon { animation: spin var(--dur-spin-icon) linear infinite; }
}

/* 스켈레톤 — 지표 카드·요약 값 등 텍스트 자리 표시 (표 로딩은 .state-block + .spinner)
   래퍼 없이도 보이도록 block·100% 기본값 */
.skeleton {
  display: block;
  width: 100%;
  min-width: var(--s8);   /* 표 셀처럼 폭이 내용에 따라 정해지는 곳에서도 0으로 줄지 않게 */
  background: linear-gradient(90deg, var(--bg-skeleton) 25%, var(--bg-skeleton-shine) 37%, var(--bg-skeleton) 63%);
  background-size: 400% 100%;
  animation: shimmer var(--dur-shimmer) var(--ease-standard) infinite;
  border-radius: var(--r-sm);
  height: 14px;
}
@keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }

/* ==========================================================================
   Toast — SCR-002 갱신 결과 안내, SCR-015 등록 결과
   ========================================================================== */
.toast-stack {
  position: fixed;
  top: calc(var(--topbar-h) + var(--s4));
  left: 50%;
  transform: translateX(-50%);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s2);
  pointer-events: none;   /* 하단 액션바·모달 푸터 버튼을 덮어 클릭을 가로채지 않도록 상단에 두고 클릭은 통과 */
}

.toast {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-width: 280px;
  max-width: 420px;
  padding: var(--s4) var(--s5);
  border-radius: var(--r);
  background: var(--fill-inverse);
  color: var(--text-on-fill);
  font-size: var(--fs-base);
  box-shadow: var(--sh-3);
}
.toast-success { background: var(--fill-success); }
.toast-error { background: var(--fill-error); }

@media (prefers-reduced-motion: no-preference) {
  .toast { animation: toast-in var(--dur-enter) var(--ease-enter); }
}
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } }

/* ==========================================================================
   Tooltip
   ========================================================================== */
.tooltip-host { position: relative; display: inline-flex; }

.tooltip-host .tooltip {
  position: absolute;
  bottom: calc(100% + var(--float-gap));
  left: 50%;
  transform: translateX(-50%);
  z-index: var(--z-popover);
  padding: var(--s2) var(--s3);
  border-radius: var(--r-sm);
  background: var(--fill-inverse);
  color: var(--text-on-fill);
  font-size: var(--fs-caption);
  font-weight: var(--fw-regular);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease-standard);
}
.tooltip-host:hover .tooltip,
.tooltip-host:focus-within .tooltip { opacity: 1; }

/* 긴 내용(요청내용 요약 등)은 줄바꿈을 허용합니다 — 기본 tooltip 은 한 줄입니다 */
.tooltip-host .tooltip-wide { max-width: 360px; white-space: normal; }

/* ==========================================================================
   Read-only 정의 목록 — SCR-004 '등록정보만 보기'(max 720px) 기반
   항목명·값을 세로로 배치하고 값은 텍스트 선택·복사가 가능해야 합니다.
   ========================================================================== */
.reader {
  max-width: var(--reader-max-w);
}

/* 값이 짧은 항목명–값 묶음은 넓은 폭에서 2열로 채웁니다. 한 열의 값 폭은 그대로 두어
   한 줄 글자수(45~75자, Bringhurst "The Elements of Typographic Style")를 지킵니다.
   문장형 값이 들어가는 reader 에는 쓰지 않습니다. 1279 이하는 1열로 돌아갑니다(파일 끝 @media). */
.reader.is-two-col {
  /* 1열 기준 720px 제한을 풀어야 2열이 실제로 카드를 채웁니다.
     열 하나가 다시 720px 을 넘지 않게 묶음 폭을 제한해 한 줄 글자수를 지킵니다. */
  max-width: 1480px;
}
.reader.is-two-col > dl {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--s7);
}

.reader-row {
  display: grid;
  grid-template-columns: 160px minmax(0, 1fr);
  gap: var(--s4);
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--border-default);
}

.reader-row dt {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--fs-base);
}

.reader-row dd {
  margin: 0;
  color: var(--text-default);
  word-break: break-word;
  user-select: text;
}

.reader-section + .reader-section { margin-top: var(--s7); }

.reader-title {
  margin: 0 0 var(--s2);
  font-size: var(--fs-card-title);
  font-weight: var(--fw-bold);
  color: var(--text-default);
}

/* 등록값이 비었을 때(미등록)와 입력 항목 자체가 없을 때(입력항목 없음)를 구분 */
.reader-row dd .value-missing { color: var(--text-warning); }

.reader details { padding: var(--s3) 0; border-bottom: 1px solid var(--border-default); }
.reader summary { cursor: pointer; color: var(--text-default); font-weight: var(--fw-semibold); }
.reader details p { margin: var(--s2) 0 0; white-space: pre-line; }

/* ==========================================================================
   Layout utility — 블록 세로 간격 · 컨트롤 폭
   화면 CSS에 margin 전용 class·폭 조합 선택자를 새로 만들지 않기 위한 최소 유틸리티입니다.
   폭은 맥락 조합 선택자(.xxx .dropdown { width })로 정하지 않고 .w-* 한 class로만 지정합니다.
   이 절을 파일 끝에 두는 것은 같은 특이성의 컴포넌트 규칙보다 뒤에 오게 하기 위해서입니다.
   (화면 CSS의 표 열 폭은 표 안 입력칸 전용 — .w-*를 쓴 컨트롤과 겹치지 않게 합니다)
   ========================================================================== */
/* 자식 사이 세로 간격을 한 값으로 고정 (자식의 위·아래 margin은 무시)
   간격 규칙: 카드 안 블록 s4 · 카드 사이 s5 · 섹션 제목 뒤 s3 */
.stack-3 > *,
.stack-4 > *,
.stack-5 > * { margin-block: 0; }
.stack-3 > * + * { margin-top: var(--s3); }
.stack-4 > * + * { margin-top: var(--s4); }
.stack-5 > * + * { margin-top: var(--s5); }

/* 컨트롤 폭 — .input·.dropdown·.textarea에 단일 class로 지정합니다 (토큰: design-system.css) */
:is(.input, .dropdown, .textarea).w-xs { width: var(--w-control-xs); }
:is(.input, .dropdown, .textarea).w-sm { width: var(--w-control-sm); }
:is(.input, .dropdown, .textarea).w-md { width: var(--w-control-md); }
:is(.input, .dropdown, .textarea).w-lg { width: var(--w-control-lg); }
:is(.input, .dropdown, .textarea).w-xl { width: var(--w-control-xl); }
:is(.input, .dropdown, .textarea).w-full { width: 100%; }

/* 카드 안 액션 줄 — 카드 마지막에 두는 저장·취소 묶음 (.modal-foot의 카드판)
   페이지 전체 저장은 .action-bar, 카드 하나를 저장·적용할 때는 이 줄을 씁니다. */
.card-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--border-default);
}
/* 액션 줄 왼쪽에 따로 두는 보조 액션 (삭제 등) */
.card-foot-start { margin-right: auto; }

/* 2열 배치 — 왼쪽 목록 + 오른쪽 편집 폼 (관리자 화면 공통 골격)
   화면마다 grid를 다시 만들지 않도록 여기에 최소 패턴만 둡니다.
   .split-main·.split-side 안에는 .card를 그대로 넣습니다(카드 여백·테두리는 .card 규칙).
   1024에서는 세로로 쌓입니다(아래 @media). 목록에서 항목을 고르면 편집 폼이 채워지는 흐름 기준. */
.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 420px;
  gap: var(--s5);
  align-items: start;
}

/* 목록이 좁고 편집·상세가 넓어야 하는 화면 (Material 3 list-detail — 목록은 고르는 곳,
   상세가 작업하는 곳). 1279 이하에서는 .split 과 똑같이 한 줄로 쌓이고, DOM 순서가
   목록 → 편집이라 좁은 폭에서도 목록이 먼저 나옵니다. */
.split.is-detail-major { grid-template-columns: 420px minmax(0, 1fr); }
.split-main,
.split-side { min-width: 0; }

/* ==========================================================================
   1024px 대응 — 컴포넌트 반응형 규칙은 기본 규칙보다 뒤(파일 끝)에 둡니다.
   같은 특이성의 @media 규칙이 기본 규칙보다 앞에 있으면 적용되지 않습니다.
   브레이크포인트 1279px은 layout.css :root 재정의와 같은 값입니다.
   ========================================================================== */
@media (max-width: 1279px) {
  .reader.is-two-col > dl { grid-template-columns: minmax(0, 1fr); }

  .stat-grid.is-fixed { grid-auto-flow: row; grid-template-columns: repeat(2, minmax(0, 1fr)); }

  :root {
    --section-nav-h: 98px;    /* 바로가기 링크가 2줄로 감김 */
    --w-filter-label: 96px;   /* 항목명 칸 — 확정값(decisions §2 L3) 유지, 칸 안쪽 여백만 12px로 */
  }
  .filter-row > .filter-label { padding-left: var(--s3); }
  .form-row { grid-template-columns: 150px minmax(0, 1fr); }
  .result-item { grid-template-columns: 110px minmax(0, 1fr) auto; }
  .split { grid-template-columns: minmax(0, 1fr); }
}
