/* ==========================================================================
   DataGrid — default theme (pure CSS, token-driven)
   All colors/sizes flow from custom properties on .dg-root so consumers can
   re-theme without touching JavaScript.
   ========================================================================== */

/* 토큰은 .dg-root 밖에 렌더링될 수 있는 팝업(.dg-menu, .dg-popup, .dg-advfilter-popup)에도
   직접 선언한다 — 팝업이 그리드 밖(예: 정적 갤러리)에 있어도 스타일이 깨지지 않도록. */
.dg-root,
.dg-menu,
.dg-popup,
.dg-advfilter-popup {
  /* ---- Design tokens (light) ---- */
  --dg-font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
    Roboto, "Helvetica Neue", Arial, "Noto Sans KR", sans-serif;
  --dg-font-size: 14px;
  --dg-background-color: #ffffff;
  --dg-foreground-color: #181d1f;
  --dg-secondary-foreground-color: rgba(24, 29, 31, 0.62);
  --dg-disabled-foreground-color: rgba(24, 29, 31, 0.38);
  --dg-border-color: #dde2eb;
  --dg-row-border-color: #eff1f6;
  --dg-header-background-color: #f8f9fb;
  --dg-header-foreground-color: #181d1f;
  --dg-accent-color: #2196f3;
  --dg-accent-foreground-color: #ffffff;
  --dg-row-hover-color: rgba(33, 150, 243, 0.08);
  --dg-selected-row-background-color: rgba(33, 150, 243, 0.12);
  --dg-odd-row-background-color: #fcfdfe;
  --dg-chip-background-color: #eef1f5;
  --dg-input-background-color: #ffffff;
  --dg-input-border-color: #c2c8d0;
  --dg-menu-background-color: #ffffff;
  --dg-menu-shadow: 0 4px 16px rgba(24, 29, 31, 0.16);
  --dg-popup-width: 420px;
  --dg-popup-backdrop-color: rgba(24, 29, 31, 0.32);
  --dg-popup-shadow: 0 12px 40px rgba(24, 29, 31, 0.22);
  --dg-popup-radius: 8px;
  --dg-popup-label-width: 120px;
  --dg-popup-field-min: 320px;   /* 2열이 이 폭을 못 주면 자동으로 1열로 떨어진다 */
  --dg-popup-input-min: 150px;   /* 필드 버튼이 붙어도 입력이 이보다 좁아지지 않는다 */
  --dg-slide-duration: 180ms;
  --dg-invalid-color: #e02525;
  --dg-required-color: #e02525;
  --dg-dirty-color: #e07c00;
  --dg-added-row-background-color: rgba(13, 138, 68, 0.08);
  --dg-deleted-row-opacity: 0.55;
  --dg-locked-background-color: rgba(24, 29, 31, 0.045);
  --dg-range-background-color: rgba(33, 150, 243, 0.14);
  --dg-group-row-background-color: #f3f6fa;
  --dg-group-indent: 20px;
  --dg-floating-filter-height: 36px;
  --dg-group-header-height: 34px;
  --dg-header-height: 48px;
  --dg-row-height: 42px;
  --dg-cell-horizontal-padding: 16px;
  --dg-wrapper-border-radius: 8px;
  --dg-border-radius: 4px;
  --dg-icon-size: 16px;
  --dg-watermark-background-color: rgba(24, 29, 31, 0.66);
  --dg-watermark-foreground-color: rgba(255, 255, 255, 0.92);
}

.dg-root {
  position: relative;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  overflow: hidden;
  height: 100%;
  min-height: 120px;
  border: 1px solid var(--dg-border-color);
  border-radius: var(--dg-wrapper-border-radius);
  background: var(--dg-background-color);
  color: var(--dg-foreground-color);
  font-family: var(--dg-font-family);
  font-size: var(--dg-font-size);
  line-height: 1.4;
  /* 셀은 문단이 아니라 UI다 — 텍스트 선택을 그리드 전체에서 끈다.
     켜 두면 셀 클릭이 collapsed 선택을 만들어 브라우저가 그 자리에 캐럿을
     그리고(캐럿 브라우징 시 깜빡임), 드래그가 셀 텍스트를 긁는다.
     복사는 Ctrl+C / copyToClipboard()가 담당한다 (BUG-012). */
  user-select: none;
}

/* 실제 입력 표면에서만 선택을 되살린다 — 인라인 에디터, 필터 메뉴, 팝업 폼.
   .dg-menu/.dg-popup은 루트 안에 append되므로 none을 상속받는다(BUG-001). */
.dg-root input,
.dg-root textarea,
.dg-menu input,
.dg-menu textarea,
.dg-popup input,
.dg-popup textarea,
.dg-advfilter-popup input,
.dg-advfilter-popup textarea { user-select: text; }

.dg-root *,
.dg-root *::before,
.dg-root *::after { box-sizing: border-box; }

/* ---- Dark theme ----
   .dg-menu / .dg-popup은 토큰을 직접 선언받으므로(위 블록) 다크 오버라이드도
   후손 셀렉터로 직접 걸어야 상속보다 우선순위가 유지된다. */
.dg-theme-dark,
.dg-theme-dark .dg-menu,
.dg-theme-dark .dg-popup,
.dg-theme-dark .dg-advfilter-popup {
  --dg-background-color: #181d1f;
  --dg-foreground-color: #ffffff;
  --dg-secondary-foreground-color: rgba(255, 255, 255, 0.62);
  --dg-disabled-foreground-color: rgba(255, 255, 255, 0.38);
  --dg-border-color: rgba(255, 255, 255, 0.16);
  --dg-row-border-color: rgba(255, 255, 255, 0.1);
  --dg-header-background-color: #222628;
  --dg-header-foreground-color: #ffffff;
  --dg-row-hover-color: rgba(33, 150, 243, 0.14);
  --dg-selected-row-background-color: rgba(33, 150, 243, 0.24);
  --dg-odd-row-background-color: #1c2124;
  --dg-chip-background-color: rgba(255, 255, 255, 0.12);
  --dg-input-background-color: #24292b;
  --dg-input-border-color: rgba(255, 255, 255, 0.28);
  --dg-menu-background-color: #24292b;
  --dg-menu-shadow: 0 4px 16px rgba(0, 0, 0, 0.55);
  --dg-popup-backdrop-color: rgba(0, 0, 0, 0.5);
  --dg-popup-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
  --dg-dirty-color: #ffa940;
  /* 4px 삼각형·작은 별표는 면적이 작아 어두운 빨강이 배경에 묻는다 — 밝은 쪽으로 */
  --dg-required-color: #ff6b6b;
  --dg-added-row-background-color: rgba(52, 199, 119, 0.12);
  --dg-deleted-row-opacity: 0.5;
  --dg-locked-background-color: rgba(255, 255, 255, 0.07);
  --dg-watermark-background-color: rgba(255, 255, 255, 0.22);
  --dg-watermark-foreground-color: rgba(255, 255, 255, 0.95);
  --dg-range-background-color: rgba(33, 150, 243, 0.26);
  --dg-group-row-background-color: #22282c;
}

/* ==========================================================================
   Header
   ========================================================================== */

/* 그리드 타이틀 바 (title 옵션) */
.dg-title-bar {
  flex: none;
  padding: 10px 16px;
  font-size: 15px;
  font-weight: 700;
  background: var(--dg-header-background-color);
  border-bottom: 1px solid var(--dg-border-color);
}

/* 내장 툴바 슬롯 (toolbar 옵션 — 소비자 DOM 주입) */
.dg-toolbar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  background: var(--dg-background-color);
  border-bottom: 1px solid var(--dg-border-color);
}

/* ==========================================================================
   Advanced filter — 요약 바 + 빌더 팝업 (advancedFilter)
   ========================================================================== */

/* 바는 끌 수 없고 조건이 없어도 자리를 지킨다. **1줄 고정**이 핵심 —
   줄바꿈을 허용하면 긴 식이 domLayout: 'fill'에서 바디를 잠식하고, 극단적으로
   바디 clientHeight가 0이 되면 무한 스크롤이 조용히 멈춘다. */
.dg-advfilter-bar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 12px;
  background: var(--dg-background-color);
  border-bottom: 1px solid var(--dg-border-color);
  font-size: 13px;
  color: var(--dg-foreground-color);
  overflow: hidden;
}

.dg-advfilter-label {
  flex: none;
  font-weight: 600;
  color: var(--dg-secondary-foreground-color);
}

.dg-advfilter-summary {
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dg-advfilter-summary.dg-advfilter-empty { color: var(--dg-secondary-foreground-color); }

/* 절단 배지 — 조건이 사라지면 결과가 **넓어지므로** 빌더를 열지 않아도 보여야 한다 */
.dg-advfilter-dropped {
  flex: none;
  padding: 2px 8px;
  border-radius: 10px;
  font-size: 12px;
  color: var(--dg-invalid-color);
  border: 1px solid var(--dg-invalid-color);
}

.dg-advfilter-actions {
  flex: none;
  display: flex;
  align-items: center;
  gap: 6px;
}
.dg-advfilter-bar .dg-btn { height: 26px; padding: 0 10px; font-size: 12px; }

.dg-advfilter-popup {
  position: fixed;
  z-index: 1000;
  width: 560px;
  max-width: calc(100vw - 16px);
  max-height: calc(100vh - 16px);
  display: flex;
  flex-direction: column;
  border: 1px solid var(--dg-border-color);
  border-radius: var(--dg-wrapper-border-radius);
  background: var(--dg-menu-background-color);
  box-shadow: var(--dg-menu-shadow);
  font-family: var(--dg-font-family);
  font-size: 13px;
  color: var(--dg-foreground-color);
}
.dg-advfilter-popup *,
.dg-advfilter-popup *::before,
.dg-advfilter-popup *::after { box-sizing: border-box; }

.dg-advfilter-header {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--dg-border-color);
}
.dg-advfilter-title { flex: 1 1 auto; font-weight: 600; }
.dg-advfilter-close {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: var(--dg-border-radius);
  background: transparent;
  color: var(--dg-secondary-foreground-color);
  cursor: pointer;
}
.dg-advfilter-close:hover { background: var(--dg-row-hover-color); color: var(--dg-foreground-color); }
.dg-advfilter-close svg { width: 14px; height: 14px; }

.dg-advfilter-tree {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  padding: 10px 12px;
}

.dg-advfilter-footer {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border-top: 1px solid var(--dg-border-color);
}
.dg-advfilter-note {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 12px;
  color: var(--dg-secondary-foreground-color);
}
.dg-advfilter-hint {
  padding: 12px 4px;
  color: var(--dg-secondary-foreground-color);
}

/* 그룹은 들여쓰기 + 왼쪽 선으로 중첩을 보인다 (깊이 3까지) */
.dg-advf-group {
  border-left: 2px solid var(--dg-border-color);
  padding-left: 8px;
}
.dg-advf-group .dg-advf-group { margin: 6px 0 6px 8px; }

.dg-advf-group-head,
.dg-advf-cond {
  display: flex;
  align-items: center;
  flex-wrap: wrap;    /* 좁아지면 열 수부터 양보한다 (§17) */
  gap: 6px;
  margin-bottom: 6px;
}

.dg-advf-group-head .dg-btn { height: 26px; padding: 0 8px; font-size: 12px; }
.dg-advf-groupop { flex: none; width: 84px; }

.dg-advf-field { flex: 1 1 120px; min-width: 100px; }
.dg-advf-op { flex: 1 1 120px; min-width: 100px; }
.dg-advf-value { flex: 1 1 110px; min-width: 90px; }

.dg-advf-type {
  flex: none;
  padding: 2px 6px;
  border-radius: 9px;
  font-size: 11px;
  background: var(--dg-header-background-color);
  color: var(--dg-secondary-foreground-color);
  border: 1px solid var(--dg-border-color);
}

.dg-advfilter-popup select,
.dg-advfilter-popup input {
  height: 28px;
  padding: 0 8px;
  font: inherit;
  font-size: 13px;
  border: 1px solid var(--dg-input-border-color);
  border-radius: var(--dg-border-radius);
  background: var(--dg-input-background-color);
  color: var(--dg-foreground-color);
}
.dg-advfilter-popup select:focus,
.dg-advfilter-popup input:focus { border-color: var(--dg-accent-color); outline: none; }
.dg-advfilter-popup .dg-btn:disabled {
  color: var(--dg-disabled-foreground-color);
  border-color: var(--dg-border-color);
  cursor: default;
}

.dg-advf-remove {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: none;
  border-radius: var(--dg-border-radius);
  background: transparent;
  color: var(--dg-secondary-foreground-color);
  cursor: pointer;
}
.dg-advf-remove:hover { background: var(--dg-row-hover-color); color: var(--dg-invalid-color); }
.dg-advf-remove svg { width: 12px; height: 12px; }

/* 0행 오버레이의 원인 축 표시 (C-2).
   기본 패널은 가로(스피너 + 문구)라 세로 전환은 필터 패널에만 건다. */
.dg-overlay-panel.dg-overlay-filtered { flex-direction: column; gap: 6px; }
.dg-overlay-detail { font-size: 12px; color: var(--dg-secondary-foreground-color); }
.dg-overlay-clear { margin-top: 2px; }

.dg-header {
  flex: none;
  overflow: hidden;
  background: var(--dg-header-background-color);
  border-bottom: 1px solid var(--dg-border-color);
}

.dg-header-row {
  display: flex;
  height: var(--dg-header-height);
  width: max-content;
  min-width: 100%;
}

/* 컬럼 그룹 헤더 (columnGroups) — 그룹 줄 1~2개, 컬럼 헤더까지 2~3단 */
.dg-header-group-row {
  display: flex;
  height: var(--dg-group-header-height);
  width: max-content;
  min-width: 100%;
  background: var(--dg-header-background-color);
}

.dg-header-group-cell {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--dg-header-foreground-color);
  /* 경계선은 오른쪽에만 — 양쪽에 긋고 margin-left: -1px로 겹치면 앞선 칸 수만큼
     위치가 밀려, 칸 수가 다른 두 그룹 줄의 세로선이 서로 어긋난다 */
  border-right: 1px solid var(--dg-border-color);
  /* 아래 경계선은 칸마다 결정한다 — 아래 줄로 이어지는 칸은 긋지 않아야
     자식 그룹이 없는 구간이 하나의 큰 칸으로 읽힌다 */
  border-bottom: 1px solid var(--dg-border-color);
}
.dg-header-group-cell:last-child { border-right: none; }
/* 아래 줄로 이어지는 칸 — 라벨을 옮기는 대신 칸을 실제로 두 줄 높이로 키운다.
   (그룹 줄은 최대 2개이므로 이어지는 칸은 언제나 정확히 2줄이다.)
   그러면 라벨은 칸의 flex 정렬로 자동 세로 중앙이고, overflow: hidden의 기준도
   68px이라 글자가 잘리지 않는다 — 라벨을 transform으로 내리면 34px 칸 밖으로
   나가 잘렸다. 아래 줄의 몸통 칸은 불투명 배경 + z-index로 덮는다
   (본문 mergeCells의 병합 시작 셀과 같은 방식). 아래 경계선은 지우지 않는다 —
   칸이 커진 만큼 저절로 블록의 바닥(68px)으로 내려간다. */
.dg-header-group-cell.dg-header-group-span {
  height: calc(2 * var(--dg-group-header-height));
  background: var(--dg-header-background-color);
  position: relative;
  z-index: 1;
}
.dg-header-group-cell.dg-pinned-left,
.dg-header-group-cell.dg-pinned-right {
  position: sticky;
  z-index: 3;
  background: var(--dg-header-background-color);
}
/* 고정 컬럼의 두 줄 칸은 아래 줄의 고정 칸(z-index 3)보다 위여야 덮을 수 있다 —
   같은 3이면 DOM 순서로 아래 줄이 이긴다. position은 sticky를 유지한다. */
.dg-header-group-cell.dg-header-group-span.dg-pinned-left,
.dg-header-group-cell.dg-header-group-span.dg-pinned-right { z-index: 4; }
.dg-header-group-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 0 8px;
}

.dg-header-cell {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 var(--dg-cell-horizontal-padding);
  flex: none;
  color: var(--dg-header-foreground-color);
  font-weight: 600;
  user-select: none;
  white-space: nowrap;
  overflow: hidden;
}

.dg-header-cell.dg-sortable { cursor: pointer; }
.dg-header-cell.dg-sortable:hover { background: rgba(33, 150, 243, 0.06); }
.dg-header-cell.dg-dragging { opacity: 0.4; }

.dg-header-cell-label {
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 0 1 auto;
}

.dg-header-cell.dg-align-right { justify-content: flex-end; }
.dg-header-cell.dg-align-center { justify-content: center; }

/* Sort indicator */
.dg-sort-icon {
  flex: none;
  width: var(--dg-icon-size);
  height: var(--dg-icon-size);
  color: var(--dg-accent-color);
  display: none;
}
.dg-sort-asc .dg-sort-icon,
.dg-sort-desc .dg-sort-icon { display: block; }
.dg-sort-desc .dg-sort-icon { transform: rotate(180deg); }

.dg-sort-order {
  flex: none;
  font-size: 10px;
  font-weight: 700;
  color: var(--dg-accent-color);
}

/* Filter indicator + menu button */
.dg-filter-icon {
  flex: none;
  width: 14px;
  height: 14px;
  color: var(--dg-accent-color);
  display: none;
}
.dg-filter-active .dg-filter-icon { display: block; }

/* 편집 가능 컬럼 표시 (editableIndicator 옵션) — 컬럼 정체성이므로 라벨 바로 옆.
   정렬·필터 아이콘(상태 표시)보다 약하게 보여 라벨을 가리지 않게 한다. */
.dg-editable-icon {
  flex: none;
  width: 12px;
  height: 12px;
  color: var(--dg-secondary-foreground-color);
  opacity: 0.55;
}
.dg-header-cell:hover .dg-editable-icon { opacity: 1; }

.dg-header-menu-btn {
  flex: none;
  margin-left: auto;
  width: 22px;
  height: 22px;
  display: none;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--dg-border-radius);
  background: transparent;
  color: var(--dg-secondary-foreground-color);
  cursor: pointer;
  padding: 0;
}
.dg-header-cell:hover .dg-header-menu-btn,
.dg-header-cell.dg-menu-open .dg-header-menu-btn,
.dg-header-cell.dg-filter-active .dg-header-menu-btn { display: inline-flex; }
.dg-header-menu-btn:hover {
  background: var(--dg-chip-background-color);
  color: var(--dg-foreground-color);
}

/* Floating filter row (헤더 아래 인라인 필터) */
.dg-floating-row {
  display: flex;
  height: var(--dg-floating-filter-height);
  width: max-content;
  min-width: 100%;
  border-top: 1px solid var(--dg-row-border-color);
  background: var(--dg-header-background-color);
}

.dg-floating-cell {
  flex: none;
  display: flex;
  align-items: center;
  padding: 0 6px;
  overflow: hidden;
}
.dg-floating-cell.dg-pinned-left,
.dg-floating-cell.dg-pinned-right {
  position: sticky;
  z-index: 3;
  background: var(--dg-header-background-color);
}

.dg-floating-input {
  width: 100%;
  height: calc(var(--dg-floating-filter-height) - 10px);
  padding: 0 8px;
  font: inherit;
  font-size: 12.5px;
  color: var(--dg-foreground-color);
  background: var(--dg-input-background-color);
  border: 1px solid var(--dg-input-border-color);
  border-radius: var(--dg-border-radius);
  outline: none;
}
.dg-floating-input:focus { border-color: var(--dg-accent-color); }
.dg-floating-input::placeholder { color: var(--dg-disabled-foreground-color); }

/* Resize handle */
.dg-header-resizer {
  position: absolute;
  top: 0;
  right: 0;
  width: 8px;
  height: 100%;
  cursor: col-resize;
  z-index: 2;
}
.dg-header-resizer::after {
  content: "";
  position: absolute;
  right: 3px;
  top: 25%;
  height: 50%;
  width: 2px;
  border-radius: 1px;
  background: transparent;
}
.dg-header-cell:hover .dg-header-resizer::after,
.dg-header-resizer.dg-resizing::after { background: var(--dg-border-color); }
.dg-header-resizer.dg-resizing::after { background: var(--dg-accent-color); }

/* Column-drop indicator */
.dg-drop-indicator {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--dg-accent-color);
  z-index: 10;
  pointer-events: none;
}

/* ==========================================================================
   Body / rows / cells
   ========================================================================== */

.dg-body {
  flex: 1 1 auto;
  overflow: auto;
  position: relative;
  background: var(--dg-background-color);
}

/* 평가판 워터마크 — 라이선스 키 미등록/비정상 시 우하단 배지.
   pointer-events: none으로 평가 사용을 방해하지 않는다(전 기능 동작 원칙).
   z-index는 고정 컬럼(3)·페이저 위, 잠금 오버레이(60) 아래. */
.dg-watermark {
  position: absolute;
  right: 10px;
  bottom: 10px;
  z-index: 50;
  padding: 4px 12px;
  border-radius: var(--dg-border-radius);
  background: var(--dg-watermark-background-color);
  color: var(--dg-watermark-foreground-color);
  font-size: 11px;
  line-height: 1.5;
  letter-spacing: 0.02em;
  pointer-events: none;
  user-select: none;
  white-space: nowrap;
  max-width: calc(100% - 20px);
  overflow: hidden;
  text-overflow: ellipsis;
}

/* setEnabled(false) — 전체 인터랙션 잠금 오버레이 */
.dg-root.dg-disabled::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 60;
  background: color-mix(in srgb, var(--dg-background-color) 55%, transparent);
  cursor: not-allowed;
}

/* showHeader: false — 헤더 영역 전체 숨김 */
.dg-root.dg-no-header .dg-header { display: none; }

/* domLayout: 'autoHeight' — 그리드가 내용 높이에 맞춰 늘어난다 (세로 가상화 없음) */
/* domLayout: 'fill' — 컨테이너를 정확히 채운다. 흐름 밖으로 빼기 때문에
   행 수가 컨테이너 높이에 영향을 주지 못한다(데이터가 없어도 안 줄고 많아도 안 늘어남).
   height:100%로는 flex 항목의 자동 최소 크기에 밀려 내용만큼 커진다. */
.dg-root.dg-fill-height {
  position: absolute;
  inset: 0;
  height: auto;
  min-height: 0;
}

.dg-root.dg-auto-height { height: auto; }
.dg-root.dg-auto-height .dg-body {
  flex: none;
  height: auto;
  overflow-y: hidden;
}

/* 상단 고정 행 (pinnedTopRows) */
.dg-pinned-top {
  flex: none;
  overflow: hidden;
  border-bottom: 2px solid var(--dg-border-color);
  background: var(--dg-background-color);
}
.dg-pinned-top .dg-row {
  position: static;
  font-weight: 600;
}

/* 셀 줄바꿈 (wrapText — autoRowHeight와 함께 사용) */
.dg-cell.dg-cell-wrap {
  white-space: normal;
  align-items: flex-start;
  padding-top: 8px;
  padding-bottom: 8px;
}
.dg-cell.dg-cell-wrap > .dg-cell-value {
  white-space: normal;
  overflow-wrap: anywhere;
}

.dg-canvas {
  position: relative;
  width: max-content;
  min-width: 100%;
}

.dg-row {
  position: absolute;
  left: 0;
  display: flex;
  width: max-content;
  min-width: 100%;
  height: var(--dg-row-height);
  border-bottom: 1px solid var(--dg-row-border-color);
  background: var(--dg-background-color);
  cursor: default;
}

.dg-zebra .dg-row.dg-row-odd { background: var(--dg-odd-row-background-color); }
.dg-row:hover { background: var(--dg-row-hover-color); }
.dg-row.dg-row-selected,
.dg-row.dg-row-selected:hover { background: var(--dg-selected-row-background-color); }

.dg-cell {
  display: flex;
  align-items: center;
  flex: none;
  padding: 0 var(--dg-cell-horizontal-padding);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  border-right: none;
}
.dg-cell > .dg-cell-value {
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  width: 100%;
}
.dg-cell.dg-align-right { justify-content: flex-end; text-align: right; }
.dg-cell.dg-align-center { justify-content: center; text-align: center; }

.dg-cell.dg-cell-focused {
  outline: 1.5px solid var(--dg-accent-color);
  outline-offset: -1.5px;
  border-radius: 2px;
}

/* I빔(cursor: text)은 "클릭하면 캐럿이 선다"는 신호다. 기본 편집 진입은 더블클릭이라
   약속을 지킬 수 없으므로 기본 커서를 쓰고, editOnSingleClick일 때만 I빔을 준다 (BUG-012). */
.dg-cell-editable { cursor: default; }
.dg-root.dg-edit-single-click .dg-cell-editable { cursor: text; }

/* 체크박스 셀은 여백을 클릭해도 토글된다 — 클릭 대상임을 커서로 알린다.
   체크박스가 비활성이면 여백도 동작하지 않으므로 포인터를 주지 않는다. */
.dg-checkbox-cell { cursor: pointer; }
.dg-checkbox-cell:has(.dg-checkbox:disabled) { cursor: default; }

/* Inline editor */
.dg-cell-editor {
  width: 100%;
  height: calc(var(--dg-row-height) - 10px);
  padding: 0 8px;
  font: inherit;
  color: inherit;
  background: var(--dg-input-background-color);
  border: 1.5px solid var(--dg-accent-color);
  border-radius: var(--dg-border-radius);
  outline: none;
}

/* date/datetime-local 에디터의 네이티브 달력 아이콘·피커를 테마에 맞춘다.
   color-scheme를 명시하지 않으면 OS 설정을 따라가서 라이트 그리드 위에
   다크 피커가 뜨는 식으로 어긋난다. */
.dg-cell-editor { color-scheme: light; }
.dg-theme-dark .dg-cell-editor { color-scheme: dark; }

/* validator 거부 / beforeCellSave 취소 상태 */
.dg-cell-editor.dg-invalid {
  border-color: var(--dg-invalid-color);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--dg-invalid-color) 18%, transparent);
}

/* 마스터-디테일 행 (rowDetail) */
.dg-detail-toggle-cell { cursor: pointer; }
.dg-row.dg-detail-row {
  background: var(--dg-group-row-background-color);
  border-bottom: 1px solid var(--dg-row-border-color);
}
.dg-detail-body {
  position: sticky;
  left: 0;
  height: 100%;
  padding: 12px 16px;
  overflow: auto;
  box-sizing: border-box;
}

/* 셀/블록 선택 (cellSelection) — background-image로 겹쳐 고정 컬럼 배경 보존 */
.dg-cell.dg-cell-range {
  background-image: linear-gradient(
    var(--dg-range-background-color),
    var(--dg-range-background-color)
  );
}

/* 컬럼 가상화 (virtualX): 창 밖 컬럼을 대신하는 폭 스페이서 */
.dg-cell.dg-colspacer { padding: 0; }

/* 셀 병합 (mergeCells): 이어지는 셀은 값 없이 위 경계선만 지운다
   (run 시작 행이 렌더 창 밖일 때의 폴백 겸용) */
.dg-cell.dg-cell-merged:not(.dg-pinned-left):not(.dg-pinned-right) { position: relative; }
.dg-cell.dg-cell-merged::before {
  content: '';
  position: absolute;
  top: -1px;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--dg-background-color);
}
.dg-zebra .dg-row-odd .dg-cell-merged::before { background: var(--dg-odd-row-background-color); }

/* 병합 run의 시작 셀: JS가 run 전체 높이를 인라인으로 지정한다.
   불투명 배경 + z-index 1로 아래 행들(불투명 배경, DOM append 순서 무관)을 덮어
   하나의 셀처럼 보이게 한다. 고정 컬럼(z-index 3)보다는 아래.
   값은 .dg-cell의 flex 정렬로 자동 세로 중앙. */
.dg-cell.dg-cell-merge-start:not(.dg-pinned-left):not(.dg-pinned-right) {
  position: relative;
  z-index: 1;
}
/* 고정 컬럼의 병합 시작 셀: 아래 행들의 고정 셀(z-index 3, 불투명, DOM 뒤 순서)이
   tall 셀을 덮지 못하게 한 단계 위로 — 그룹 헤더 span 칸이 아래 줄 고정 칸(3)을
   4로 덮는 것과 같은 패턴 (함정 §28). 같은 값이면 DOM 순서로 뒤 행이 이겨서
   병합 컬럼이 통째로 가려진다 (BUG-015의 세 번째 원인). */
.dg-cell.dg-cell-merge-start.dg-pinned-left,
.dg-cell.dg-cell-merge-start.dg-pinned-right {
  z-index: 4;
}
.dg-cell.dg-cell-merge-start {
  align-self: flex-start;
  background: var(--dg-background-color);
  border-bottom: 1px solid var(--dg-row-border-color);
  /* sticky 라벨이 셀 상자 기준이 아니라 .dg-body 스크롤포트 기준으로 붙으려면
     셀의 overflow: hidden을 풀어야 한다 — ellipsis는 .dg-cell-value가 담당 (BUG-015) */
  overflow: visible;
}
/* 병합 라벨은 run이 화면에 걸쳐 있는 동안 계속 보인다 — top은 위로 스크롤아웃될 때
   스크롤포트 상단에, bottom은 run이 아래에서 진입할 때 하단에 라벨을 붙인다.
   셀이 온전히 보일 때는 flex 세로 중앙 그대로 (BUG-015). */
.dg-cell.dg-cell-merge-start > .dg-cell-value {
  position: sticky;
  top: 8px;
  bottom: 8px;
}

/* 채우기 핸들 (fillHandle) */
.dg-cell-fill-anchor { position: relative; }
.dg-fill-handle {
  position: absolute;
  right: -1px;
  bottom: -1px;
  width: 9px;
  height: 9px;
  background: var(--dg-accent-color);
  border: 1.5px solid var(--dg-background-color);
  border-radius: 1px;
  cursor: crosshair;
  z-index: 2;
}
.dg-cell.dg-cell-fill-preview {
  outline: 1.5px dashed var(--dg-accent-color);
  outline-offset: -2px;
}

/* 필수 컬럼 (column.required)
   ─ 그리드 헤더에는 표식을 두지 않는다. 표시는 셀 마커 하나 —
     "비어 있어서 조치가 필요하다"만 가리킨다(모든 셀에 그리면 정보량 0).
   ─ `*` 표식은 팝업 폼 라벨에만 남는다(폼은 입력 화면이라 관례가 다르다).
   ─ 자리와 색을 dirty와 분리한다: dirty는 왼쪽 위 주황, 필수는 오른쪽 위 빨강.
     한 셀에 둘이 동시에 뜰 수 있으므로 겹치면 구분이 불가능해진다. */
.dg-required-star {
  margin-left: 2px;
  color: var(--dg-required-color);
  font-weight: 700;
  flex: none;
}

.dg-cell.dg-cell-required:not(.dg-pinned-left):not(.dg-pinned-right) { position: relative; }
.dg-cell.dg-cell-required::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  border: 4px solid var(--dg-required-color);
  border-left-color: transparent;
  border-bottom-color: transparent;
}

/* 변경 추적 (trackChanges): dirty 셀 모서리 마커 + 추가 행 배경 */
.dg-cell.dg-cell-dirty:not(.dg-pinned-left):not(.dg-pinned-right) { position: relative; }
.dg-cell.dg-cell-dirty::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  border: 4px solid var(--dg-dirty-color);
  border-right-color: transparent;
  border-bottom-color: transparent;
}
/* background-image로 겹쳐 고정 컬럼의 불투명 배경색을 보존한다 */
.dg-row-added .dg-cell {
  background-image: linear-gradient(
    var(--dg-added-row-background-color),
    var(--dg-added-row-background-color)
  );
}

/* softDelete: 삭제 표시된(스테이징) 행 — 취소선 + 흐림. 상태 태그에는 취소선을 긋지 않는다 */
.dg-row-deleted .dg-cell {
  text-decoration: line-through;
  opacity: var(--dg-deleted-row-opacity);
}
.dg-row-deleted .dg-cell .dg-tag { text-decoration: none; }

/* setRowEnabled(row, false) / setCellEnabled(row, field, false) — 잠긴 행·셀.
   dg-row-added와 같은 이유로 background-image를 쓴다: 고정 컬럼 셀은 sticky라
   불투명 배경이 필요한데 background로 덮으면 그 색이 사라진다.
   opacity를 안 쓰는 이유는 stacking context가 생겨 고정 컬럼의 z-index가
   행 안에 갇히기 때문 (BUG-013과 같은 계열). */
.dg-row-disabled .dg-cell,
.dg-cell.dg-cell-disabled {
  background-image: linear-gradient(
    var(--dg-locked-background-color),
    var(--dg-locked-background-color)
  );
}
.dg-row-disabled .dg-cell { color: var(--dg-disabled-foreground-color); }
/* 잠긴 셀은 편집 커서를 주지 않는다 — I빔은 "클릭하면 캐럿이 선다"는 약속 (BUG-012) */
.dg-root.dg-edit-single-click .dg-cell.dg-cell-disabled { cursor: default; }
.dg-row-disabled .dg-checkbox-cell { cursor: default; }

/* 커스텀/인라인(checkbox·radio·multiselect) 에디터가 열린 셀.
 * position: relative는 multiselect 패널의 앵커, z-index는 이웃 행 위로. */
.dg-cell.dg-cell-editing {
  box-shadow: inset 0 0 0 2px var(--dg-accent-color);
  overflow: visible;
  position: relative;
  z-index: 4;
}
.dg-cell.dg-cell-editing.dg-invalid {
  box-shadow: inset 0 0 0 2px var(--dg-invalid-color);
}

/* 라디오 에디터 — 셀 안 가로 라디오 그룹 */
.dg-radio {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  width: 16px;
  height: 16px;
  margin: 0;
  border: 1.5px solid var(--dg-input-border-color);
  border-radius: 50%;
  background: var(--dg-input-background-color);
  cursor: pointer;
  position: relative;
  transition: background 0.12s, border-color 0.12s;
}
.dg-radio:hover { border-color: var(--dg-accent-color); }
.dg-radio:focus-visible {
  outline: 2px solid var(--dg-accent-color);
  outline-offset: 1px;
}
.dg-radio:checked { border-color: var(--dg-accent-color); }
.dg-radio:checked::after {
  content: "";
  position: absolute;
  left: 3px;
  top: 3px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--dg-accent-color);
}
/* multiselect/radio/searchselect 에디터 — 셀에 앵커된 선택 패널 (스타일 공용) */
.dg-editor-multiselect,
.dg-editor-radio,
.dg-editor-searchselect {
  position: absolute;
  top: calc(100% + 2px);
  left: 0;
  z-index: 30;
  min-width: max(100%, 180px);
  max-height: 208px;
  overflow: auto;
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--dg-menu-background-color);
  border: 1px solid var(--dg-border-color);
  border-radius: var(--dg-wrapper-border-radius);
  box-shadow: var(--dg-menu-shadow);
  outline: none;
}
.dg-editor-multiselect label,
.dg-editor-radio label {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  border-radius: var(--dg-border-radius);
  cursor: pointer;
  font-size: 13px;
  white-space: nowrap;
}
.dg-editor-multiselect label:hover,
.dg-editor-radio label:hover { background: var(--dg-row-hover-color); }

/* 검색형 select 에디터 (editorSearch) — 검색 입력 + 옵션 목록 */
.dg-searchselect-input {
  width: 100%;
  box-sizing: border-box;
  height: 30px;
  padding: 0 8px;
  margin-bottom: 4px;
  font: inherit;
  font-size: 13px;
  color: var(--dg-foreground-color);
  background: var(--dg-input-background-color);
  border: 1px solid var(--dg-input-border-color);
  border-radius: var(--dg-border-radius);
  outline: none;
}
.dg-searchselect-input:focus { border-color: var(--dg-accent-color); }
.dg-searchselect-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 168px;
  overflow: auto;
}
.dg-searchselect-option {
  padding: 4px 8px;
  border-radius: var(--dg-border-radius);
  cursor: pointer;
  font-size: 13px;
  white-space: nowrap;
}
.dg-searchselect-option:hover { background: var(--dg-row-hover-color); }
.dg-searchselect-option.dg-active { background: var(--dg-selected-row-background-color); }
.dg-searchselect-msg {
  padding: 5px 8px;
  font-size: 12px;
  color: var(--dg-secondary-foreground-color);
}
.dg-searchselect-msg.dg-error { color: var(--dg-invalid-color); }

/* 검색형 multiselect (editor: 'multiselect' + editorSearch) — 칩 줄 + 체크리스트.
   칩은 셀 표시(renderers.multiselect)와 같은 .dg-tag.dg-tag-plain을 써서 편집 중과
   편집 후의 모양이 같다. 새 토큰은 만들지 않는다(전부 기존 토큰 재사용). */
.dg-searchselect-multi {
  /* 패널 대신 **목록만** 스크롤한다 — 패널이 스크롤하면 칩 줄이 위로 밀려나가
     "지금 무엇이 골라져 있는지"가 사라진다 */
  max-height: none;
  overflow: visible;
}
.dg-searchselect-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  max-height: 68px;
  overflow: auto;
  margin-bottom: 4px;
}
/* hidden 속성이 display: flex에 지므로 명시적으로 되돌린다 */
.dg-searchselect-chips[hidden] { display: none; }
.dg-searchselect-chip {
  gap: 2px;
  padding-right: 4px;
}
.dg-searchselect-chip-remove {
  flex: none;
  width: 16px;
  height: 16px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--dg-secondary-foreground-color);
  font: inherit;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}
.dg-searchselect-chip-remove:hover {
  background: var(--dg-row-hover-color);
  color: var(--dg-foreground-color);
}
.dg-searchselect-multi-option {
  display: flex;
  align-items: center;
  gap: 8px;
}
/* 옵션 안의 체크박스는 상태 표시 전용 — 클릭은 항상 옵션 행이 받는다.
   disabled를 쓰면 체크박스 자리가 클릭 사각지대가 된다 (BUG-006). */
.dg-searchselect-multi-option .dg-checkbox {
  flex: none;
  pointer-events: none;
}

/* ==========================================================================
   Row grouping + summary rows
   ========================================================================== */

.dg-row.dg-group-row {
  background: var(--dg-group-row-background-color);
  font-weight: 600;
  cursor: pointer;
}
.dg-row.dg-group-row:hover { background: var(--dg-row-hover-color); }

/* 그룹 행의 고정 컬럼도 그룹 배경을 유지해야 스크롤 시 본문을 가린다 */
.dg-row.dg-group-row .dg-cell.dg-pinned-left,
.dg-row.dg-group-row .dg-cell.dg-pinned-right { background: var(--dg-group-row-background-color); }

.dg-group-cell { gap: 6px; }
.dg-group-indent { flex: none; }

.dg-group-chevron {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--dg-icon-size);
  height: var(--dg-icon-size);
  color: var(--dg-secondary-foreground-color);
  transition: transform 0.15s ease;
}
.dg-group-chevron.dg-expanded { transform: rotate(90deg); }

.dg-group-label {
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 트리 그리드 (treeData): 트리 컬럼의 들여쓰기 + 펼침 토글.
   토글은 그룹 셰브론 스타일을 재사용하고, 리프에는 같은 폭의 스페이서를 넣어
   부모/자식 라벨의 시작 위치를 맞춘다. */
.dg-tree-indent { flex: none; }
.dg-tree-toggle { cursor: pointer; margin-right: 6px; }
.dg-tree-toggle-spacer {
  flex: none;
  width: calc(var(--dg-icon-size) + 6px);
}
/* fetchChildren 지연 로딩 중: 토글이 스피너로 회전 (dg-spin은 로딩 오버레이와 공유) */
.dg-tree-toggle.dg-tree-loading { animation: dg-spin 0.8s linear infinite; }
.dg-group-count {
  color: var(--dg-secondary-foreground-color);
  font-weight: 500;
}

/* 서버 그룹핑(groupMode: 'server')
   - 자식 블록을 받는 중이면 셰브론이 스피너처럼 돈다 (트리 토글과 같은 방식).
     애니메이션이 멈추는 환경에서도 셰브론은 제자리에 보인다.
   - "더 보기" 행: 블록의 다음 페이지를 이어 받는 합성 그룹 행. */
.dg-group-chevron.dg-tree-loading { animation: dg-spin 0.8s linear infinite; }
.dg-group-more {
  color: var(--dg-accent-color);
  font-weight: 500;
  cursor: pointer;
}
.dg-group-more-loading .dg-group-more {
  color: var(--dg-secondary-foreground-color);
  cursor: default;
}

/* 값 목록이 빈 set 필터 — "전부 체크(=필터 없음)"와 구별되게 이유를 적어 준다 */
.dg-menu-empty {
  padding: 8px 4px;
  color: var(--dg-secondary-foreground-color);
  font-size: 12.5px;
}

.dg-cell-agg { font-weight: 600; }

/* Grand total footer (non-virtualized single row under the body) */
.dg-footer {
  flex: none;
  overflow: hidden;
  border-top: 1px solid var(--dg-border-color);
  background: var(--dg-header-background-color);
}
.dg-footer-row {
  display: flex;
  height: var(--dg-row-height);
  width: max-content;
  min-width: 100%;
  align-items: stretch;
}
.dg-footer .dg-cell { font-weight: 600; gap: 6px; }
.dg-footer .dg-cell.dg-pinned-left,
.dg-footer .dg-cell.dg-pinned-right { background: var(--dg-header-background-color); }
.dg-footer-label { color: var(--dg-foreground-color); }

/* ==========================================================================
   Checkboxes (custom, tri-state)
   ========================================================================== */

.dg-checkbox {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  width: 16px;
  height: 16px;
  margin: 0;
  border: 1.5px solid var(--dg-input-border-color);
  border-radius: var(--dg-border-radius);
  background: var(--dg-input-background-color);
  cursor: pointer;
  position: relative;
  transition: background 0.12s, border-color 0.12s;
}
.dg-checkbox:hover { border-color: var(--dg-accent-color); }
.dg-checkbox:focus-visible {
  outline: 2px solid var(--dg-accent-color);
  outline-offset: 1px;
}
.dg-checkbox:checked,
.dg-checkbox:indeterminate {
  background: var(--dg-accent-color);
  border-color: var(--dg-accent-color);
}
.dg-checkbox:checked::after {
  content: "";
  position: absolute;
  left: 4px;
  top: 1px;
  width: 4px;
  height: 8px;
  border: solid var(--dg-accent-foreground-color);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.dg-checkbox:indeterminate::after {
  content: "";
  position: absolute;
  left: 2px;
  top: 5.5px;
  width: 9px;
  height: 2px;
  border-radius: 1px;
  background: var(--dg-accent-foreground-color);
}
.dg-checkbox:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.dg-checkbox-cell,
.dg-checkbox-header {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}

/* 행 번호 컬럼 (rowNumbers: true) */
.dg-rownum-cell {
  color: var(--dg-secondary-foreground-color);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  user-select: none;
}

/* ==========================================================================
   Tag / badge cell renderer
   ========================================================================== */

.dg-tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.5;
  background: var(--dg-chip-background-color);
  color: var(--dg-foreground-color);
}
.dg-tag::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.9;
}
/* 점 없는 일반 칩 — multiselect 렌더러 등 */
.dg-tag-plain::before { display: none; }

/* 표시 전용 체크박스 (renderers.checkbox) — disabled 대신 pointer-events로.
 * disabled 폼 요소는 마우스 이벤트를 삼켜 셀 dblclick 편집을 막는다 (BUG-006). */
.dg-checkbox.dg-checkbox-display {
  pointer-events: none;
  cursor: default;
}

.dg-tag-green  { background: rgba(22, 160, 78, 0.14);  color: #0d8a44; }
.dg-tag-red    { background: rgba(224, 37, 37, 0.12);  color: #d02020; }
.dg-tag-blue   { background: rgba(33, 150, 243, 0.14); color: #1180d8; }
.dg-tag-yellow { background: rgba(240, 160, 0, 0.16);  color: #b57a00; }
.dg-tag-gray   { background: var(--dg-chip-background-color); color: var(--dg-secondary-foreground-color); }
.dg-theme-dark .dg-tag-green  { color: #4cd587; }
.dg-theme-dark .dg-tag-red    { color: #ff7070; }
.dg-theme-dark .dg-tag-blue   { color: #64b5f6; }
.dg-theme-dark .dg-tag-yellow { color: #ffca4d; }

/* ==========================================================================
   Filter / column menu popup
   ========================================================================== */

.dg-menu {
  position: fixed;
  z-index: 1000;
  min-width: 220px;
  max-width: 300px;
  padding: 12px;
  border: 1px solid var(--dg-border-color);
  border-radius: var(--dg-wrapper-border-radius);
  background: var(--dg-menu-background-color);
  box-shadow: var(--dg-menu-shadow);
  font-family: var(--dg-font-family);
  font-size: 13px;
  color: var(--dg-foreground-color);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.dg-menu-title {
  font-weight: 600;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--dg-secondary-foreground-color);
}

.dg-menu select,
.dg-menu input[type="text"],
.dg-menu input[type="number"] {
  width: 100%;
  height: 32px;
  padding: 0 10px;
  font: inherit;
  color: var(--dg-foreground-color);
  background: var(--dg-input-background-color);
  border: 1px solid var(--dg-input-border-color);
  border-radius: var(--dg-border-radius);
  outline: none;
}
.dg-menu select:focus,
.dg-menu input:focus { border-color: var(--dg-accent-color); }

.dg-menu-set-list {
  max-height: 180px;
  overflow: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.dg-menu-set-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 4px;
  border-radius: var(--dg-border-radius);
  cursor: pointer;
}
.dg-menu-set-item:hover { background: var(--dg-row-hover-color); }

.dg-menu-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding-top: 4px;
  border-top: 1px solid var(--dg-row-border-color);
}

.dg-btn {
  height: 30px;
  padding: 0 12px;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  border-radius: var(--dg-border-radius);
  border: 1px solid var(--dg-input-border-color);
  background: var(--dg-input-background-color);
  color: var(--dg-foreground-color);
  cursor: pointer;
}
.dg-btn:hover { border-color: var(--dg-accent-color); color: var(--dg-accent-color); }
.dg-btn-primary {
  background: var(--dg-accent-color);
  border-color: var(--dg-accent-color);
  color: var(--dg-accent-foreground-color);
}
.dg-btn-primary:hover {
  background: #1180d8;
  border-color: #1180d8;
  color: var(--dg-accent-foreground-color);
}

/* ==========================================================================
   Pagination panel
   ========================================================================== */

/* 좁은 컨테이너에서 **줄을 바꿔 내려앉는다**. 예전에는 한 줄에 고정 높이(48px)라
   폭이 모자라면 "Page size:" 같은 라벨이 글자 단위로 접히면서 48px 밖으로 삐져나갔다
   — 그리드를 좁은 칸에 넣은 화면에서 페이저만 뭉개져 보인다. 줄바꿈은 그룹 사이에서만
   일어나야 하므로 각 그룹에는 white-space: nowrap을 준다. */
.dg-paging-panel {
  flex: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 8px 20px;
  min-height: 48px;
  padding: 6px var(--dg-cell-horizontal-padding);
  border-top: 1px solid var(--dg-border-color);
  background: var(--dg-background-color);
  color: var(--dg-secondary-foreground-color);
  font-size: 13px;
}

.dg-paging-page-size {
  display: flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
}
.dg-paging-row-summary { white-space: nowrap; }
.dg-paging-page-size select {
  height: 28px;
  padding: 0 6px;
  font: inherit;
  font-size: 13px;
  color: var(--dg-foreground-color);
  background: var(--dg-input-background-color);
  border: 1px solid var(--dg-input-border-color);
  border-radius: var(--dg-border-radius);
  outline: none;
}

.dg-paging-row-summary strong { color: var(--dg-foreground-color); font-weight: 600; }

.dg-paging-buttons {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: 2px;
}

.dg-paging-btn {
  min-width: 28px;
  height: 28px;
  padding: 0 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--dg-border-radius);
  background: transparent;
  color: var(--dg-foreground-color);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.dg-paging-btn:hover:not(:disabled) { background: var(--dg-chip-background-color); }
.dg-paging-btn:disabled { color: var(--dg-disabled-foreground-color); cursor: default; }
.dg-paging-btn.dg-paging-current {
  background: var(--dg-accent-color);
  color: var(--dg-accent-foreground-color);
  font-weight: 600;
}

/* ==========================================================================
   Infinite scroll status bar (infiniteScroll)
   ========================================================================== */

/* 높이는 상태와 무관하게 고정 — 로딩이 끝나거나 마지막에 닿아도 그리드가 흔들리지 않는다 */
.dg-infinite-status {
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
  height: 34px;
  padding: 0 var(--dg-cell-horizontal-padding);
  border-top: 1px solid var(--dg-border-color);
  background: var(--dg-background-color);
  color: var(--dg-secondary-foreground-color);
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
}
/* 크기 선택은 고정 폭, 상태 문구가 남는 폭을 먹고 넘치면 흘려보낸다 (BUG-011의 양보 순서) */
.dg-infinite-page-size {
  flex: none;
  display: flex;
  align-items: center;
  gap: 6px;
}
.dg-infinite-page-size select {
  height: 22px;
  padding: 0 4px;
  font: inherit;
  font-size: 12px;
  color: var(--dg-foreground-color);
  background: var(--dg-input-background-color);
  border: 1px solid var(--dg-input-border-color);
  border-radius: var(--dg-border-radius);
  outline: none;
}
.dg-infinite-page-size select:disabled { color: var(--dg-disabled-foreground-color); }
.dg-infinite-text {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  overflow: hidden;
}
.dg-infinite-text > span { overflow: hidden; text-overflow: ellipsis; }
.dg-infinite-status .dg-spinner { width: 13px; height: 13px; border-width: 2px; flex: none; }
/* 마지막 페이지 — 평상시 상태와 확실히 구분되도록 강조한다 */
.dg-infinite-status.dg-infinite-end {
  color: var(--dg-foreground-color);
  font-weight: 600;
  background: var(--dg-header-background-color);
}

/* ==========================================================================
   Overlays
   ========================================================================== */

.dg-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--dg-background-color) 66%, transparent);
  z-index: 5;
  color: var(--dg-secondary-foreground-color);
  font-size: 13px;
}
.dg-overlay[hidden] { display: none; }

.dg-overlay-panel {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 18px;
  border: 1px solid var(--dg-border-color);
  border-radius: var(--dg-border-radius);
  background: var(--dg-background-color);
  box-shadow: var(--dg-menu-shadow);
}

.dg-spinner {
  width: 18px;
  height: 18px;
  border: 2.5px solid var(--dg-chip-background-color);
  border-top-color: var(--dg-accent-color);
  border-radius: 50%;
  animation: dg-spin 0.8s linear infinite;
}
@keyframes dg-spin { to { transform: rotate(360deg); } }

/* ==========================================================================
   Pinned columns
   ========================================================================== */

.dg-cell.dg-pinned-left,
.dg-header-cell.dg-pinned-left {
  position: sticky;
  left: 0;
  z-index: 3;
  background: inherit;
}
.dg-cell.dg-pinned-right,
.dg-header-cell.dg-pinned-right {
  position: sticky;
  right: 0;
  z-index: 3;
  background: inherit;
}
.dg-header-cell.dg-pinned-left,
.dg-header-cell.dg-pinned-right { background: var(--dg-header-background-color); }
.dg-pinned-left.dg-pinned-edge { box-shadow: 6px 0 6px -6px rgba(24, 29, 31, 0.2); }
.dg-pinned-right.dg-pinned-edge { box-shadow: -6px 0 6px -6px rgba(24, 29, 31, 0.2); }

/* Rows need opaque backgrounds for sticky pinned cells to cover scrolling content */
.dg-row .dg-cell.dg-pinned-left,
.dg-row .dg-cell.dg-pinned-right { background: var(--dg-background-color); }
.dg-zebra .dg-row.dg-row-odd .dg-cell.dg-pinned-left,
.dg-zebra .dg-row.dg-row-odd .dg-cell.dg-pinned-right { background: var(--dg-odd-row-background-color); }
.dg-row:hover .dg-cell.dg-pinned-left,
.dg-row:hover .dg-cell.dg-pinned-right { background: #eaf4fd; }
.dg-row.dg-row-selected .dg-cell.dg-pinned-left,
.dg-row.dg-row-selected .dg-cell.dg-pinned-right { background: #e0effc; }
.dg-theme-dark .dg-row:hover .dg-cell.dg-pinned-left,
.dg-theme-dark .dg-row:hover .dg-cell.dg-pinned-right { background: #223442; }
.dg-theme-dark .dg-row.dg-row-selected .dg-cell.dg-pinned-left,
.dg-theme-dark .dg-row.dg-row-selected .dg-cell.dg-pinned-right { background: #274257; }

/* ==========================================================================
   Popup editor (popupEditor) — 행 단위 폼 편집
   .dg-popup은 토큰을 직접 선언받는다 (파일 상단 토큰 블록 + 다크 오버라이드).
   ========================================================================== */

.dg-popup-backdrop {
  position: absolute;
  inset: 0;
  z-index: 30;
  display: flex;
  background: var(--dg-popup-backdrop-color);
}
.dg-popup-center { align-items: center; justify-content: center; }
.dg-popup-left { align-items: stretch; justify-content: flex-start; }
.dg-popup-right { align-items: stretch; justify-content: flex-end; }

.dg-popup {
  display: flex;
  flex-direction: column;
  max-width: 100%;
  background: var(--dg-background-color);
  color: var(--dg-foreground-color);
  font-family: var(--dg-font-family);
  font-size: var(--dg-font-size);
  box-shadow: var(--dg-popup-shadow);
}
.dg-popup-center .dg-popup {
  max-height: calc(100% - 32px);
  border-radius: var(--dg-popup-radius);
}
/* 슬라이드 패널은 전체 높이를 채우고 바깥쪽 모서리는 각지게 둔다 */
.dg-popup-left .dg-popup,
.dg-popup-right .dg-popup { height: 100%; }

/* 진입 효과 — 의도적으로 "작은" 오프셋에서만 움직인다.
   화면 밖(translateX(±100%))이나 opacity:0에서 시작하면, 프레임이 생성되지 않는
   상황(백그라운드 탭, display:none 조상, 숨겨진 프리뷰 팬)에서 애니메이션이
   from 상태에 멈춰 팝업이 보이지 않는다 — 트랜지션도, rAF로 클래스를 붙이는
   방식도 같은 실패 모드다. 여기서는 멈춰도 24px 어긋난 정도라 완전히 보이고
   조작 가능하다. 가시성은 절대 애니메이션에 의존하지 않는다. */
@keyframes dg-popup-slide-in-left { from { transform: translateX(-24px); } }
@keyframes dg-popup-slide-in-right { from { transform: translateX(24px); } }
@keyframes dg-popup-zoom-in { from { transform: scale(0.98); } }

.dg-popup-left .dg-popup { animation: dg-popup-slide-in-left var(--dg-slide-duration) ease-out; }
.dg-popup-right .dg-popup { animation: dg-popup-slide-in-right var(--dg-slide-duration) ease-out; }
.dg-popup-center .dg-popup { animation: dg-popup-zoom-in var(--dg-slide-duration) ease-out; }

@media (prefers-reduced-motion: reduce) {
  .dg-popup-backdrop .dg-popup { animation: none; }
}

.dg-popup-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 16px 18px;
  border-bottom: 1px solid var(--dg-border-color);
}
.dg-popup-title {
  flex: 1;
  min-width: 0;
  font-size: 16px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dg-popup-close {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: none;
  border-radius: 4px;
  background: transparent;
  color: var(--dg-secondary-foreground-color);
  cursor: pointer;
}
.dg-popup-close:hover {
  background: var(--dg-header-background-color);
  color: var(--dg-foreground-color);
}
/* 왼쪽 패널의 접기 화살표는 왼쪽을 향해야 한다 */
.dg-popup-left .dg-popup-close svg { transform: rotate(180deg); }

.dg-popup-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 14px;
  padding: 18px;
  align-content: start;
  /* 아래 @container의 기준 — 필드 레이아웃이 팝업 폭에 따라 스스로 바뀐다 */
  container-type: inline-size;
}
/* 2열은 "폭이 충분할 때만" — auto-fit이라 좁으면 알아서 1열로 떨어진다.
   고정 1fr 1fr로 두면 좁은 팝업에서 라벨(고정폭)이 컨트롤을 짓눌러 깨진다. */
.dg-popup-body.dg-popup-cols-2 {
  grid-template-columns: repeat(auto-fit, minmax(var(--dg-popup-field-min), 1fr));
}
.dg-popup-body.dg-popup-cols-2 .dg-popup-field[data-span="2"] { grid-column: 1 / -1; }

.dg-popup-field {
  display: grid;
  /* minmax(0, ...)이 있어야 긴 내용이 트랙을 밀어내지 않는다 */
  grid-template-columns: minmax(0, var(--dg-popup-label-width)) minmax(0, 1fr);
  align-items: center;
  gap: 4px 12px;
  min-width: 0;
}
.dg-popup-label {
  grid-column: 1;
  color: var(--dg-secondary-foreground-color);
  overflow-wrap: anywhere;
}
.dg-popup-control {
  grid-column: 2;
  display: flex;
  align-items: center;
  flex-wrap: wrap;   /* 필드 버튼이 많으면 줄바꿈 — 입력을 짓누르지 않는다 */
  gap: 6px;
  min-width: 0;
}
.dg-popup-input {
  flex: 1 1 var(--dg-popup-input-min);
  min-width: 0;
  position: relative;
}
.dg-popup-before,
.dg-popup-after { grid-column: 1 / -1; }
.dg-popup-hint,
.dg-popup-error { grid-column: 2; font-size: 12px; }

/* 필드가 좁아지면 라벨을 위로 올린다 — 고정폭 라벨 + 좁은 컨트롤 조합이
   입력을 수십 px로 뭉개는 것을 막는다. 컨테이너 쿼리를 지원하지 않는
   브라우저에서는 위의 flex-wrap과 auto-fit만으로도 무너지지는 않는다. */
@container (max-width: 400px) {
  .dg-popup-field { grid-template-columns: minmax(0, 1fr); }
  .dg-popup-label,
  .dg-popup-control,
  .dg-popup-hint,
  .dg-popup-error { grid-column: 1; }
  .dg-popup-field-tall .dg-popup-label { padding-top: 0; }
}
.dg-popup-hint { color: var(--dg-secondary-foreground-color); }
.dg-popup-error { color: var(--dg-invalid-color); }
.dg-popup-invalid .dg-popup-label { color: var(--dg-invalid-color); }

/* 폼 안의 입력은 셀보다 넉넉하게 */
.dg-popup .dg-cell-editor {
  width: 100%;
  height: 36px;
  padding: 0 10px;
  border: 1px solid var(--dg-border-color);
  border-radius: 4px;
  background: var(--dg-background-color);
  color: var(--dg-foreground-color);
  font: inherit;
  box-sizing: border-box;
}
.dg-popup .dg-cell-editor:focus {
  outline: none;
  border-color: var(--dg-accent-color);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--dg-accent-color) 18%, transparent);
}
.dg-popup-invalid .dg-cell-editor { border-color: var(--dg-invalid-color); }
/* 표시 전용 — disabled를 쓰지 않는다 (BUG-006) */
.dg-popup .dg-popup-readonly {
  background: var(--dg-header-background-color);
  color: var(--dg-secondary-foreground-color);
  pointer-events: none;
}

/* 셀 앵커 패널(multiselect/radio)은 폼 안에서 흐름대로 펼친다 —
   절대 위치로 두면 아래 필드를 덮는다. 이 둘은 펼쳐진 채로 현재 상태
   (체크·선택)를 그대로 보여주므로 접을 이유가 없다.
   단 **검색형** multiselect(editorSearch)는 여기 해당하지 않는다 — 클래스가
   .dg-editor-searchselect라 아래 콤보박스 규칙을 타고, 칩 줄이 현재 상태를
   보여주므로 접혀도 정보가 사라지지 않는다. */
.dg-popup .dg-editor-multiselect,
.dg-popup .dg-editor-radio {
  position: static;
  top: auto;
  bottom: auto;
  width: 100%;
  min-width: 0;
  max-height: 220px;
}
/* 목록형 위젯은 세로로 길다 — 라벨을 가운데 정렬하면 목록 한복판에 뜬다 */
.dg-popup-field-tall { align-items: start; }
.dg-popup-field-tall .dg-popup-label { padding-top: 8px; }

/* 검색형 select는 폼에서 "접히는 콤보박스"가 된다 — 입력창이 현재 값을 보여주고
   목록은 조작할 때만 펼쳐진다. 늘 펼쳐 두면 다른 필드를 밀어내는 데다,
   고른 값을 표시할 자리가 없다(셀에서는 고르는 즉시 닫히므로 문제가 없었다). */
.dg-popup .dg-editor-searchselect.dg-searchselect-collapsible {
  position: relative;
  /* 셀 앵커 패널의 z-index(30)를 반드시 함께 되돌린다 — position만 relative로
     바꾸면 숫자 z-index가 살아남아 필드마다 stacking context가 생기고, 그 안에
     갇힌 목록(z-index: 31)은 형제 필드 위로 올라갈 수 없다. 같은 30끼리는 DOM
     순서로 결판나므로 "뒤에 오는 searchselect가 앞 필드의 목록을 덮는다"
     (BUG-013). multiselect/radio가 멀쩡했던 건 position: static이라 z-index가
     애초에 무효였기 때문. */
  z-index: auto;
  top: auto;
  bottom: auto;
  width: 100%;
  min-width: 0;
  max-height: none;
  overflow: visible;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: 0;
  box-shadow: none;
}
/* 접힌 상태의 입력창은 다른 폼 입력과 같은 모양이어야 한다 */
.dg-searchselect-collapsible .dg-searchselect-input {
  height: 36px;
  margin-bottom: 0;
  padding: 0 10px;
  font-size: inherit;
  background: var(--dg-background-color);
  border-color: var(--dg-border-color);
  cursor: pointer;
}
.dg-searchselect-collapsible .dg-searchselect-list { display: none; }
.dg-searchselect-collapsible.dg-searchselect-open .dg-searchselect-list {
  display: flex;
  position: absolute;
  top: calc(100% + 2px);
  bottom: auto;
  left: 0;
  right: 0;
  z-index: 31;
  max-height: 200px;
  padding: 6px;
  background: var(--dg-menu-background-color);
  border: 1px solid var(--dg-border-color);
  border-radius: var(--dg-wrapper-border-radius);
  box-shadow: var(--dg-menu-shadow);
}
/* 폼 바디는 스크롤 컨테이너(overflow: auto)라 그 밖으로 나간 목록을 그리지 않는다 —
   아래 공간이 모자라면 위로 편다. 칩 줄이 붙는 multiselect는 컨트롤이 더 높아
   목록이 아래로 밀리기 쉬우므로 특히 중요하다. */
.dg-searchselect-collapsible.dg-searchselect-up.dg-searchselect-open .dg-searchselect-list {
  top: auto;
  bottom: calc(100% + 2px);
}

.dg-popup-footer {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  padding: 14px 18px;
  border-top: 1px solid var(--dg-border-color);
}
.dg-popup-footer .dg-btn { min-width: 84px; height: 36px; }
.dg-popup-control .dg-btn { flex: none; height: 36px; }

.dg-btn-danger {
  border-color: var(--dg-invalid-color);
  color: var(--dg-invalid-color);
}
.dg-btn-danger:hover {
  background: var(--dg-invalid-color);
  border-color: var(--dg-invalid-color);
  color: #ffffff;
}
.dg-btn:disabled {
  opacity: 0.5;
  cursor: default;
  pointer-events: none;
}

/* ==========================================================================
   Icons (inline SVG via mask would need external refs; use borders/chars)
   ========================================================================== */

.dg-icon-arrow {
  display: inline-block;
  width: var(--dg-icon-size);
  height: var(--dg-icon-size);
}
