:root {
  --bg: #0f172a;
  --panel: #1e293b;
  --panel-border: #334155;
  --text: #e2e8f0;
  --muted: #94a3b8;
  --nav-text: #cbd5e1;
  --accent: #38bdf8;
  --accent-2: #34d399;
  --danger: #f87171;
}

* { box-sizing: border-box; }

/* ---------------------------------------------------------------- 전역 로딩바 */
/* 조회/검색(fetchJSON) 전체에 공통 적용 - 저장/삭제 등 쓰기 액션(sensorApi)은 별개라
   여기서 안 건드린다. 결정형(determinate) 진행률은 응답 시간을 미리 알 수 없어 의미가
   없으므로, 흔한 "쓸어가는" 형태의 비결정형(indeterminate) 표시로 둔다. */
.global-loading-bar {
  position: fixed;
  top: 0;
  left: 0;
  height: 3px;
  width: 0;
  background: var(--accent);
  z-index: 9999;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s;
}
.global-loading-bar.active {
  opacity: 1;
  animation: global-loading-bar-sweep 1.1s ease-in-out infinite;
}
@keyframes global-loading-bar-sweep {
  0% { width: 0%; margin-left: 0%; }
  50% { width: 60%; margin-left: 20%; }
  100% { width: 0%; margin-left: 100%; }
}

body {
  margin: 0;
  font-family: -apple-system, "Segoe UI", "Malgun Gothic", sans-serif;
  background: var(--bg);
  color: var(--text);
}

.app-shell {
  display: flex;
  min-height: 100vh;
}

/* .app-shell/.login-screen 둘 다 display:flex 를 직접 지정하므로, 브라우저 기본
   [hidden]{display:none} 과 동일 우선순위(author CSS 가 UA 스타일시트를 이김)라 그냥
   두면 hidden 속성이 무시된다 — 속성 선택자를 겹쳐 specificity 를 더 높여 확실히
   이기게 한다(로그인 후 화면 전환이 안 되던 원인, 2026-07-15 실측). */
.app-shell[hidden],
.login-screen[hidden] {
  display: none;
}

/* ---------------------------------------------------------------- 사이드바 */

.sidebar {
  width: 240px;
  flex-shrink: 0;
  background: var(--panel);
  border-right: 1px solid var(--panel-border);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  transition: width 0.15s;
  /* z-index 없으면 auto라 본문 쪽에 z-index 있는 요소(예: 토폴로지의 React Flow 캔버스,
   * 자체 z-index 1~5)가 새 stacking context를 만들 때 사이드바보다 나중에 그려져 덮어버릴
   * 수 있다(접힌 상태에서 네트워크 구성도 화면 - 사용자 보고). 사이드바는 항상 본문 위에
   * 있어야 하니 모달(z-index:100~)보다는 낮고 본문에 나올 수 있는 값보다는 확실히 높게. */
  z-index: 50;
}

/* 좌측 메뉴 접기/펼치기(사용자 요청) - 사이드바 오른쪽 경계에 겹쳐 뜨는 원형 버튼.
   .sidebar-header 안에 넣지 않고 별도로 절대배치하는 이유: 접힌 폭(72px)에서
   로고+제목+버튼을 한 줄에 다 넣기엔 너무 좁아진다 - 헤더 레이아웃과 무관하게
   항상 같은 자리에 떠 있어야 접힌 상태에서도 다시 펼칠 방법이 있다. */
.btn-sidebar-toggle {
  position: absolute;
  top: 24px;
  right: -12px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  cursor: pointer;
  z-index: 10;
}
.btn-sidebar-toggle:hover { color: var(--text); border-color: var(--accent); }
.sidebar-toggle-icon {
  width: 14px;
  height: 14px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 0.15s;
}
.sidebar.collapsed .sidebar-toggle-icon { transform: rotate(180deg); }

/* 접힌 상태 - @media (max-width: 800px) 의 모바일 자동 축소 규칙과 같은 모양이지만,
   뷰포트 폭과 무관하게 버튼으로 수동 토글한다(그쪽 규칙은 건드리지 않음 - 독립적으로
   공존, 좁은 화면에서는 어차피 같은 모양이라 충돌 없음). */
.sidebar.collapsed { width: 72px; }
.sidebar.collapsed .sidebar-header { justify-content: center; }
.sidebar.collapsed .sidebar-header .sidebar-title,
.sidebar.collapsed .nav-label,
.sidebar.collapsed .nav-group-text,
.sidebar.collapsed .nav-group-chevron { display: none; }
.sidebar.collapsed .nav-group { position: relative; }
.sidebar.collapsed .nav-group-items {
  position: absolute;
  left: 100%;
  top: 0;
  z-index: 40;
  display: none;
  max-height: none;
  overflow: visible;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 8px;
  box-shadow: 4px 4px 16px rgba(0, 0, 0, 0.35);
  min-width: 180px;
  padding: 6px;
}
.sidebar.collapsed .nav-group.expanded .nav-group-items { display: flex; }
.sidebar.collapsed .nav-group.expanded .nav-item-sub .nav-label,
.sidebar.collapsed .nav-group.expanded .nav-group-divider .nav-label { display: inline; }

.sidebar-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 16px 8px;
  border-bottom: 1px solid var(--panel-border);
}

.logo {
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.logo img { width: 100%; height: 100%; }

/* 와이드 로고(logo/sphere_insight.png)는 아이콘+"Sphere Insight"+태그라인이 한 이미지에
   다 들어있어 정사각 아이콘용 .logo 크기(36x36, object-fit 없음)를 그대로 쓰면 찌그러진다
   - width는 원본 비율대로 auto, height만 지정해서 맞춘다. 사이드바 펼침 상태에서는 이
   와이드 로고만, 접힘 상태에서는 기존 정사각 아이콘(.logo-icon)만 보이게 서로 뒤바꾼다
   (펼침에서 아이콘이 로고 안에도 있고 옆에도 또 있으면 중복돼 보임) - .sidebar-title 로
   .logo-brand 를 감싸서 접힘 시 숨기는 규칙은 기존 .sidebar-title 규칙을 그대로 재사용. */
.logo-brand { width: auto; height: auto; }
.logo-brand img { width: auto; height: 50px; }
.login-box .logo-brand img { height: 88px; }
.sidebar:not(.collapsed) .logo-icon { display: none; }

.nav-menu {
  display: flex;
  flex-direction: column;
  padding: 12px 8px;
  gap: 2px;
  flex: 1;
  /* 사이드바(.sidebar)가 height:100vh 고정이라, 모든 그룹을 동시에 펼치는 등 전체
     메뉴 높이가 뷰포트를 넘기면 스크롤 없이는 아래쪽 항목이 화면 밖으로 잘렸다
     (2026-08-27, 하위메뉴 클리핑 수정과 같은 원인 계열). */
  overflow-y: auto;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: 8px;
  color: var(--nav-text);
  text-decoration: none;
  font-size: 0.9rem;
  position: relative;
}

.nav-item:hover { background: rgba(148, 163, 184, 0.1); color: var(--text); }
.nav-item.active { background: rgba(56, 189, 248, 0.15); color: var(--accent); }
.nav-icon { font-size: 1rem; width: 20px; text-align: center; }
.nav-label { flex: 1; }

/* 최상위 단독 메뉴(Dashboard/Reports)도 그룹 제목(.nav-group-label)과 같은 대문자·볼드 스타일로 —
   중첩된 하위 메뉴(.nav-item-sub)는 .nav-menu 의 직계 자식이 아니므로 영향받지 않음 */
.nav-menu > .nav-item > .nav-label { text-transform: uppercase; letter-spacing: 0.03em; font-weight: 700; }

/* 사이드바 아이콘을 이모지 대신 선(line) 스타일 SVG로 통일 — 텍스트와 같은 색상 규칙(muted/hover/active)을
   그대로 상속해 톤이 어긋나지 않는다 */
svg.nav-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.nav-badge {
  background: var(--danger);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 700;
  border-radius: 999px;
  padding: 1px 7px;
  min-width: 18px;
  text-align: center;
}

.nav-group { display: flex; flex-direction: column; gap: 2px; margin-top: 6px; }
.nav-group-label {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 8px 12px;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--nav-text);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
}
.nav-group-label:hover { color: var(--text); }
.nav-group-text { flex: 1; text-align: left; }
.nav-group-chevron { font-size: 0.7rem; transition: transform 0.15s; }

.nav-group-items {
  display: flex;
  flex-direction: column;
  gap: 2px;
  overflow: hidden;
  max-height: 0;
}
/* 그룹(예: Exposure)의 하위메뉴 항목 수가 늘어날 때마다 고정 max-height(예전 400px)를
   넘어 overflow:hidden에 아래쪽 항목이 통째로 가려지는 문제가 반복돼(2026-08-27,
   사용자 보고 - "10개 이하는 안 보임") 하위메뉴 한 줄 높이를 줄이고(아래 .nav-item-sub
   padding) 여유를 넉넉히 둔 값으로 올렸다 - 그룹 전체가 화면 높이를 넘길 정도로
   많아지면 사이드바 자체 스크롤(.nav-menu, 상단 flex:1 오버플로)로 흡수된다. */
.nav-group.expanded .nav-group-items { max-height: 900px; }
.nav-group.expanded .nav-group-chevron { transform: rotate(180deg); }

/* 하위메뉴 텍스트 시작지점을 상위메뉴(.nav-group-label) 텍스트 시작지점과 맞춘다(사용자
   요청, 2026-09-02) - .nav-group-label의 아이콘(18px)+gap(8px)+자기 padding-left(12px) =
   38px 지점에서 텍스트가 시작하므로, 아이콘이 없는 .nav-item-sub도 같은 38px로 맞춘다. */
.nav-item-sub { padding-left: 38px; padding-top: 6px; padding-bottom: 6px; }

/* Exposure 그룹처럼 하위메뉴가 "탐지 결과" 화면과 "관리 설정"(참조 데이터 CRUD) 화면이
   섞여 13개나 평면 나열되던 걸 시각적으로만 구분한다(사용자 요청, 2026-08-31) - 링크는
   그대로 두고 구분선+라벨만 추가하는 최소 변경. */
.nav-group-divider {
  padding-left: 38px;
  padding-top: 10px;
  margin-top: 4px;
  border-top: 1px solid var(--panel-border);
}
.nav-group-divider .nav-label {
  font-size: 0.72rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.sidebar-footer {
  padding: 14px 16px;
  border-top: 1px solid var(--panel-border);
  color: var(--muted);
  font-size: 0.75rem;
}

.sidebar-user {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 8px;
  color: var(--text);
}

/* 권한 없는 쓰기 동작 버튼/메뉴를 숨긴다 — 백엔드 403 과 별개로 권한 없는 진입 자체를
   UX 상 막는다(docs/USER_MANAGEMENT_PLAN.md 5절, RBAC 세분화는 2026-07-31 - app.js::
   applyPermissionGating 이 로그인한 사용자의 유효 권한에 따라 body에 perm-missing-*
   클래스를 붙인다). 권한 키 개수만큼 정적으로 둔다 - backend/app/services/rbac.py::
   PERMISSION_CATALOG 과 반드시 같은 목록이어야 한다. */
body.perm-missing-asset_manage [data-requires-permission="asset_manage"] { display: none !important; }
body.perm-missing-sensor_manage [data-requires-permission="sensor_manage"] { display: none !important; }
body.perm-missing-policy_manage [data-requires-permission="policy_manage"] { display: none !important; }
body.perm-missing-cloud_manage [data-requires-permission="cloud_manage"] { display: none !important; }
body.perm-missing-connector_manage [data-requires-permission="connector_manage"] { display: none !important; }
body.perm-missing-saas_manage [data-requires-permission="saas_manage"] { display: none !important; }
body.perm-missing-vuln_manage [data-requires-permission="vuln_manage"] { display: none !important; }
body.perm-missing-user_manage [data-requires-permission="user_manage"] { display: none !important; }
body.perm-missing-org_manage [data-requires-permission="org_manage"] { display: none !important; }
body.perm-missing-notification_manage [data-requires-permission="notification_manage"] { display: none !important; }

/* ---------------------------------------------------------------- 로그인 화면 */

.login-screen {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
}

.login-box {
  width: 320px;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 12px;
  padding: 32px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  text-align: center;
}

.login-box .logo { margin: 0 auto 12px; }
.login-box h1 { margin: 0; font-size: 1.1rem; }
.login-subtitle { color: var(--muted); font-size: 0.82rem; margin: 0 0 16px; }
.login-box .field { text-align: left; margin-bottom: 10px; }
/* 로그인/MFA 입력을 실제 <form> 으로 감쌌다(2026-09-07, 크롬 비밀번호 관리자 자동입력/저장 인식 조건) -
   form 자체를 레이아웃에서 지워 .login-box 의 flex 자식으로 그 내부 요소들이 그대로 참여하게 한다
   (감싸기 전과 동일한 간격/정렬 유지). */
.login-box form { display: contents; }
.login-submit { margin-top: 8px; width: 100%; }

/* ---------------------------------------------------------------- 메인 영역 */

.main-area {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20px 32px;
  border-bottom: 1px solid var(--panel-border);
}

.topbar h1 { margin: 0; font-size: 1.2rem; }

.topbar-actions { display: flex; align-items: center; gap: 10px; }

.btn-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--panel-border);
  border-radius: 6px;
  cursor: pointer;
  position: relative;
}
.btn-icon:hover { border-color: var(--accent); }

svg.topbar-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.topbar-alarm { position: relative; }
.topbar-profile { position: relative; }

.topbar-profile-dropdown { width: 220px; padding: 6px; }

.topbar-profile-id {
  padding: 10px 12px;
  border-bottom: 1px solid var(--panel-border);
  margin-bottom: 6px;
}
.topbar-profile-display-name { font-size: 0.9rem; font-weight: 600; }
.topbar-profile-username { font-size: 0.78rem; color: var(--muted); }

.topbar-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 12px;
  background: none;
  border: none;
  border-radius: 6px;
  color: var(--text);
  font-size: 0.85rem;
  cursor: pointer;
}
.topbar-menu-item:hover { background: var(--panel-border); }

.topbar-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 16px;
  height: 16px;
  padding: 0 3px;
  border-radius: 8px;
  background: var(--danger);
  color: #fff;
  font-size: 0.65rem;
  line-height: 16px;
  text-align: center;
}

.topbar-dropdown {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: 30;
  margin-top: 6px;
  width: 340px;
  background: var(--bg);
  border: 1px solid var(--panel-border);
  border-radius: 8px;
  box-shadow: 4px 4px 16px rgba(0, 0, 0, 0.35);
  padding: 10px;
}

.topbar-dropdown-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 8px;
}
.topbar-dropdown-header a { font-weight: 400; font-size: 0.8rem; color: var(--accent); }
.topbar-dropdown-header a:hover { color: var(--text); }

.topbar-alarm-list { display: flex; flex-direction: column; gap: 6px; max-height: 320px; overflow-y: auto; }

.topbar-alarm-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px;
  border: 1px solid var(--panel-border);
  border-radius: 6px;
  font-size: 0.8rem;
}
.topbar-alarm-item-title { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.topbar-alarm-item-meta { color: var(--muted); font-size: 0.75rem; }

.topbar-profile-status {
  margin-top: 6px;
  padding: 8px 12px 2px;
  border-top: 1px solid var(--panel-border);
  color: var(--muted);
  font-size: 0.75rem;
}

main {
  padding: 24px 32px;
  display: flex;
  flex-direction: column;
  gap: 24px;
  flex: 1;
  min-width: 0;
}

.page { display: flex; flex-direction: column; gap: 24px; }
.page[hidden] { display: none; }

/* ---------------------------------------------------------------- 카드/패널 */

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 16px;
}

.card {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 10px;
  padding: 18px;
}

.card-value { font-size: 2rem; font-weight: 700; color: var(--accent); }
.card-label { color: var(--muted); font-size: 0.85rem; margin-top: 4px; }
/* Ingest 큐 용량 사용률 카드(2026-09-04) - 트리밍 상한에 가까워지면 경고색으로 */
.card-value-danger { color: var(--danger); }

/* Exposure 개요 - 클릭해서 해당 화면으로 이동 가능한 카드 vs 데이터 소스가 없어 아직
   지원하지 않는 유형을 안내만 하는 카드(회색조, 클릭 불가) */
.card-link { cursor: pointer; }
.card-link:hover { border-color: var(--accent); }
.card-sublabel { color: var(--muted); font-size: 0.78rem; margin-top: 6px; }
.card-unsupported { opacity: 0.55; }
.card-unsupported .card-value { color: var(--muted); font-size: 1.1rem; }

/* Collector 상태(Ingest 큐 상태) 카드만 값 글자를 줄인다(사용자 요청, 2026-09-11) - 이 화면의
   값은 대시보드처럼 큰 숫자 하나가 아니라 "ACKED", "계산 중", "0 / 0" 같은 짧은 문자열이라
   2rem 이면 같은 화면의 조밀한 표들에 비해 과하게 컸다. */
#page-collector-health .card-value { font-size: 1.5rem; }

/* 사용자 상세 모달 - 개인정보 3개(사용자명/부서/이메일) + 요약 지표 4개(자산/NIC/
   SaaS-App/Exposure)를 한 줄에 나열한다(사용자 요청, 여러 줄로 나뉜 asset-detail-row/
   org-status-metrics 조합 대신). 좁은 화면에서는 줄바꿈. */
.user-detail-summary-line { display: flex; flex-wrap: wrap; align-items: baseline; gap: 18px; margin-bottom: 16px; font-size: 0.88rem; }
.user-detail-summary-line-label { color: var(--muted); margin-right: 4px; }
.user-detail-summary-line-value { font-weight: 600; }

/* 조직별 현황 - 위: 최상위 조직 카드(전체/미배정 포함), 아래: 위에서 고른 조직의 하위
   조직 전체를 작은 카드로(5열 고정, 배경을 둬서 위 영역과 구분). 단일 큰 숫자
   (.card-value)를 쓰는 다른 카드들과 달리 카드 하나에 지표가 5개(서브넷/자산/
   인터페이스/Exposure/사용자)라 전용 미니 그리드를 쓴다. */
.org-status-sub-section {
  margin-top: 20px;
  padding: 16px;
  background: var(--bg);
  border: 1px solid var(--panel-border);
  border-radius: 10px;
}
.org-status-subheading { margin: 0 0 10px; font-size: 0.95rem; color: var(--muted); }
.cards-compact { grid-template-columns: repeat(5, 1fr); }

.org-status-card-title { font-weight: 600; margin-bottom: 10px; }
.org-status-card-subtitle { color: var(--muted); font-size: 0.75rem; margin: -6px 0 10px; }
.org-status-card-selected { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
.org-status-metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.org-status-metric-value { display: block; font-size: 1.3rem; font-weight: 700; color: var(--accent); }
.org-status-metric-label { display: block; color: var(--muted); font-size: 0.78rem; margin-top: 2px; }
.org-status-metric-link { background: none; border: none; padding: 0; text-align: inherit; font: inherit; cursor: pointer; }
.org-status-metric-link:hover .org-status-metric-value { text-decoration: underline; }

.org-status-subcard { padding: 12px; }
.org-status-subcard .org-status-card-title { font-size: 0.88rem; margin-bottom: 2px; }
.org-status-subcard .org-status-metric-value { font-size: 1.05rem; }

.sensor-status-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
  margin-top: 12px;
}
.sensor-status-card {
  background: var(--bg);
  border: 1px solid var(--panel-border);
  border-radius: 8px;
  padding: 12px 14px;
}
.sensor-status-name { font-weight: 600; display: flex; align-items: center; }
.sensor-status-meta { color: var(--muted); font-size: 0.8rem; margin-top: 4px; }

.panels {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}
/* 대시보드 "자산 가시성"처럼 패널이 2개보다 많이 들어갈 수 있는 줄 - 고정 2열 대신
   auto-fit으로 넓이에 맞춰 자연스럽게 줄바꿈된다. */
.panels-3 { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }

@media (max-width: 800px) {
  .panels { grid-template-columns: 1fr; }
  .sidebar { width: 72px; }
  .sidebar-header .sidebar-title, .nav-label, .title-sub, .nav-group-text, .nav-group-chevron { display: none; }
  .sidebar-header { justify-content: center; }

  /* 하위 메뉴(.nav-item-sub, 예: "전체 자산")는 그룹 아이콘 하나를 공유할 뿐 자체
     아이콘이 없다 - 사이드바 폭 자체를 넓히면(이전 시도) 좁은 폰 화면에서는 메뉴가
     화면 대부분을 차지해버린다(사용자 보고, 2026-07-29). 대신 펼친 그룹의 하위 메뉴만
     아이콘 열 옆에 겹쳐 뜨는 팝오버로 띄운다 - 본문 레이아웃을 전혀 밀어내지 않는다.
     데스크톱의 아코디언(max-height 애니메이션)과는 다른 표현이라 여기서 통째로
     덮어쓴다. */
  .nav-group { position: relative; }
  .nav-group-items {
    position: absolute;
    left: 100%;
    top: 0;
    z-index: 40;
    display: none;
    max-height: none;
    overflow: visible;
    background: var(--panel);
    border: 1px solid var(--panel-border);
    border-radius: 8px;
    box-shadow: 4px 4px 16px rgba(0, 0, 0, 0.35);
    min-width: 180px;
    padding: 6px;
  }
  .nav-group.expanded .nav-group-items { display: flex; }
  .nav-group.expanded .nav-item-sub .nav-label,
  .nav-group.expanded .nav-group-divider .nav-label { display: inline; }
}

.panel {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 10px;
  padding: 18px;
  overflow-x: auto;
}

.panel h2 { margin: 0 0 12px; font-size: 1rem; }

/* 패널 두 개를 한 줄에 비율(예: 3:1)로 배치할 때 - 좁은 화면에서는 줄바꿈되어 세로로
   쌓인다(각 패널 자체의 min-width 확보). */
.panel-row { display: flex; gap: 20px; flex-wrap: wrap; }
.panel-row .panel { min-width: 260px; }
.asset-status-chart-3 { flex: 3 1 0; }
.asset-status-chart-2 { flex: 2 1 0; }

.panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.hint { color: var(--muted); font-size: 0.78rem; }

.bulk-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 12px;
  font-size: 0.85rem;
  color: var(--muted);
}
.bulk-actions[hidden] { display: none; }
.bulk-actions .btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-primary:disabled, .btn-secondary:disabled { opacity: 0.5; cursor: not-allowed; }

th.col-select, td.col-select { width: 32px; white-space: nowrap; }
table.hide-select-col .col-select { display: none; }

/* 역할 관리(#table-roles) "권한" 칼럼 - 권한 배지가 많아지면 한 줄로 이어붙여 표를
   옆으로 밀어내던 것을 여러 줄로 감싸게 한다(사용자 요청, 2026-08-07). 전역 th,td의
   white-space:nowrap(아래 808행 근처)만 이 칼럼에서 되돌리면 충분하다 - 배지 자체는
   .approval-badge 가 이미 white-space:nowrap+display:inline-block이라 배지 하나가
   중간에서 잘리지 않고, 배지와 배지 사이(공백)에서만 줄이 바뀐다.*/
#table-roles td.col-permissions, #table-roles th.col-permissions { white-space: normal; }

#table-assets { table-layout: fixed; }
#table-assets th, #table-assets td { overflow: hidden; text-overflow: ellipsis; }
.asset-sort-th { cursor: pointer; user-select: none; white-space: nowrap; }
.asset-sort-th:hover { color: var(--accent); }
#table-assets th.col-vendor, #table-assets td.col-vendor { width: 10%; }
#table-assets th.col-product, #table-assets td.col-product { width: 5%; }
#table-assets th.col-services, #table-assets td.col-services { width: 16%; }
#table-assets th.col-risk, #table-assets td.col-risk { width: 4.8%; overflow: visible; text-overflow: clip; }
#table-assets th.col-approval, #table-assets td.col-approval { width: 4.8%; overflow: visible; text-overflow: clip; }
#table-assets th.col-assignee, #table-assets td.col-assignee { width: 4.5%; }
/* MAC/IP는 너비가 줄어도 잘리지 않고 전체가 다 보여야 한다 — 내용에 맞는 고정 너비를
   주고 이 칼럼만 ellipsis 적용에서 뺀다(넘치면 패널의 overflow-x: auto로 가로 스크롤). */
#table-assets th.col-mac, #table-assets td.col-mac { width: 150px; overflow: visible; text-overflow: clip; }
#table-assets th.col-ip, #table-assets td.col-ip { width: 100px; overflow: visible; text-overflow: clip; }
/* IP 칼럼 안쪽은 td 가 아니라 .cell-block-line(span)이 실제로 텍스트를 담고 있고, 그
   span 은 다른 화면(외부 통신 이상 목록)의 축약 용도로 자체 overflow:hidden/ellipsis 를
   갖고 있어서 위 td 레벨의 clip 지정이 안 먹혔다(2026-08-13 사용자 요청으로 넣은 규칙과
   충돌) - 이 칼럼 안에서만 다시 풀어준다. */
#table-assets td.col-ip .cell-block-line { overflow: visible; text-overflow: clip; }

/* 클라우드 자산의 ID 칼럼(로드밸런서/인증서 ARN, Route53 레코드 ID)이 매우 길어 표를
   밀어내므로 15%로 제한하고 넘치면 말줄임 + title 툴팁으로 전체 값을 보게 한다. */
#table-cloud-assets { table-layout: fixed; }
#table-cloud-assets th, #table-cloud-assets td { overflow: hidden; text-overflow: ellipsis; }
#table-cloud-assets th.col-id, #table-cloud-assets td.col-id { width: 15%; }

/* "마지막 동기화" 칼럼이 실패 사유(벤더 API 에러 메시지, 길면 수백 자)를 그대로 보여줘서
   길어지면 표 전체가 옆으로 밀려 화면이 깨졌다 - 위 #table-cloud-assets 의 col-id 와 같은
   원인/같은 해법(고정 폭 + 말줄임, 전체 텍스트는 이미 있던 title 툴팁으로). */
#table-connectors { table-layout: fixed; }
#table-connectors th, #table-connectors td { overflow: hidden; text-overflow: ellipsis; }
#table-connectors th.col-last-sync, #table-connectors td.col-last-sync { width: 320px; white-space: nowrap; }
#table-connectors th.row-actions, #table-connectors td.row-actions { overflow: visible; text-overflow: clip; width: 280px; }

/* filter-search 입력창부터 서브넷 체크박스까지를 하나의 영역으로 묶어 보여준다(사용자
   요청, 2026-07-31) - .filters-panel 이 테두리 있는 박스, 오른쪽의 "조회" 버튼은 flex
   stretch로 select 줄 + 서브넷 체크박스 줄을 합친 높이만큼 자동으로 늘어난다. */
.filters-panel {
  display: flex;
  align-items: stretch;
  gap: 12px;
  background: var(--bg);
  border: 1px solid var(--panel-border);
  border-radius: 8px;
  padding: 10px 12px;
  /* .panel-header 가 justify-content: space-between 이라, flex-grow 없이는 이 박스가
     내용물 너비만큼만 차지하고 화면이 넓어질수록 h2 제목과의 사이 여백만 늘어나며 우측
     정렬된 것처럼 보인다(사용자 보고, 2026-07-31) - 남는 가로 폭을 전부 가져가게 한다.
     flex-basis:100%(.panel-header 의 flex-wrap과 합쳐짐)로 h2 제목과 항상 줄을 분리한다
     (사용자 요청, 2026-07-31) - #assets-view-title 은 자기 줄에 단독으로 남는다. */
  flex: 1 1 100%;
  min-width: 0;
}
.filters-rows { display: flex; flex-direction: column; gap: 8px; flex: 1; min-width: 0; }
.filters-search-btn { flex-shrink: 0; padding: 0 20px; font-size: 0.9rem; }

.filters { display: flex; gap: 8px; flex-wrap: wrap; }
.filters select, .filters input, .filters button {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--panel-border);
  border-radius: 6px;
  padding: 6px 10px;
  font-size: 0.85rem;
}
.filters-date-sep { display: flex; align-items: center; color: var(--muted); }
.filters input { min-width: 30px; }
/* 제조사 목록은 이름이 길어(예: "REALTEK SEMICONDUCTOR CORP.") 다른 필터 select 보다
   눈에 띄게 넓어 보였다 - 폭을 20%가량 줄이고, 좁아진 만큼 선택된 값이 넘치면 말줄임표로
   자른다(사용자 요청, 2026-08-06). */
#filter-vendor { max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 브라우저 기본 달력 아이콘이 검정색으로 그려져서 어두운 배경(--bg)에 안 보이는 문제
   (사용자 보고, 2026-07-31) - 이 앱은 항상 다크 테마 고정이라(라이트 모드 토글 없음)
   무조건 반전해도 된다. */
input[type="date"]::-webkit-calendar-picker-indicator { filter: invert(1); cursor: pointer; }
.filters button { cursor: pointer; }
.filters button:hover { border-color: var(--accent); }

.filters-subnet-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}
.filters-subnet-label { color: var(--muted); font-size: 0.82rem; }
.filter-checkbox-group { display: flex; flex-wrap: wrap; gap: 4px 12px; }
.filter-checkbox {
  display: flex;
  align-items: center;
  gap: 4px;
  color: var(--muted);
  font-size: 0.82rem;
  cursor: pointer;
}

/* 조직관리 트리 - 사용자 요청(2026-07-31)의 "계층구조를 쉽게 관리" - 네이티브 HTML5
   드래그앤드롭(라이브러리 없음, 이 프로젝트 전체 관례)으로 재배치, 형제 순서는 버튼으로. */
.org-unit-drop-zone {
  border: 1px dashed var(--panel-border);
  border-radius: 6px;
  padding: 8px 12px;
  margin: 8px 0;
  color: var(--muted);
  font-size: 0.82rem;
  text-align: center;
}
.org-unit-drop-zone.drag-over { border-color: var(--accent); color: var(--accent); background: rgba(56, 189, 248, 0.08); }
.org-unit-tree ul { list-style: none; margin: 0; padding-left: 24px; }
.org-unit-tree > ul { padding-left: 0; }
.org-unit-node-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 8px;
  border-radius: 6px;
  cursor: grab;
}
.org-unit-node-row:hover { background: rgba(148, 163, 184, 0.08); }
.org-unit-node-row.drag-over { background: rgba(56, 189, 248, 0.15); outline: 1px dashed var(--accent); }
.org-unit-toggle {
  width: 16px;
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 0.75rem;
  padding: 0;
}
.org-unit-toggle.leaf { visibility: hidden; }
.org-unit-name { font-weight: 600; }
.org-unit-description { color: var(--muted); font-size: 0.78rem; }
.org-unit-node-actions { margin-left: auto; display: flex; gap: 2px; align-items: center; }
.org-unit-node-actions button {
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 0.8rem;
  padding: 2px 5px;
}
.org-unit-node-actions button:hover { color: var(--accent); }
.org-unit-node-actions .danger:hover { color: var(--danger); }

.private-mac-badge { margin-left: 4px; font-size: 0.8rem; cursor: help; }
.badge-icon {
  width: 16px;
  height: 16px;
  vertical-align: -3px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.cell-link {
  background: none;
  border: none;
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  padding: 0;
  text-align: left;
  text-decoration: underline;
  text-decoration-color: transparent;
}
.cell-link:hover { text-decoration-color: currentColor; }

/* min-height:0 이 없으면 flex 자식(body/right)의 기본 min-height:auto 때문에 내용 높이만큼
   부모를 밀어내 버려서(overflow-y:auto 가 있어도 무용지물) 모달 자체가 탭/내용에 따라
   늘었다 줄었다 한다 - 0으로 낮춰야 아래 두 칸이 부모(고정 높이 85vh) 안에서 각자
   overflow-y:auto 로 스크롤된다. */
.asset-detail-layout { display: flex; gap: 24px; flex-wrap: wrap; flex: 1; min-height: 0; }
/* asset-detail-body(기본정보, 배경 자동갱신으로 다시 그려짐)와 asset-detail-approval-embed
   (승인 폼, 담당자/관리자 검색 입력 중인 내용이 지워지지 않도록 배경 자동갱신 대상에서
   제외됨 - refreshOpenAssetDetail 참고)를 한 스크롤 영역에서 세로로 쌓는 래퍼. 두 영역을
   같은 innerHTML 교체 대상으로 묶지 않아야 승인 폼이 15초 주기 갱신에 안 지워진다. */
.asset-detail-left { flex: 35 1 0; height: 100%; overflow-y: auto; min-width: 320px; display: flex; flex-direction: column; }
.asset-detail-body { flex: 0 0 auto; }
.asset-detail-approval-embed { flex: 0 0 auto; }
.asset-detail-row { display: flex; gap: 10px; padding: 5px 0; border-bottom: 1px solid var(--panel-border); font-size: 0.88rem; }
.asset-detail-row:last-child { border-bottom: none; }
.asset-detail-label { width: 110px; flex-shrink: 0; color: var(--muted); }
.asset-detail-subheading { margin: 14px 0 4px; font-size: 0.9rem; }

/* User-Agent 등 값이 아주 길어질 수 있는 칸 - 높이 제한 없이 전체 값을 그대로 보여준다
   (사용자 요청, 2026-09-01 - 예전엔 80px로 잘라 내부 스크롤시켰으나 전체가 보이는 쪽을
   선호). 줄바꿈은 그대로 되게 display:block만 유지. */
.asset-detail-value-scroll {
  display: block;
  min-width: 0;
}

/* SSDP 기기타입 - urn:schemas-... 같은 공백 없는 긴 값이 여러 개 잡히면 좌측 칼럼
   너비를 밀어내거나 칸이 세로로 계속 늘어날 수 있어, 위 User-Agent 칸과 같은 원칙으로
   가로는 고정폭 안에서 줄바꿈, 세로는 80px 안에서만 스크롤되게 한다. */
.asset-detail-value-ssdp {
  display: block;
  max-height: 80px;
  overflow-y: auto;
  overflow-wrap: anywhere;
  min-width: 0;
}

/* 외부통신이상 목적지 칸 - IP + 국가 + 도메인(rDNS 포함)을 한 칸에 이어붙이면 도메인이
   길 때 표 전체가 옆으로 늘어난다. auto 테이블 레이아웃에서는 td의 max-width가 칼럼
   폭을 제한하지 못해(내용에 맞춰 표 자체가 계속 늘어남) 이 표만 table-layout: fixed로
   바꾸고 목적지 칼럼에 실제 width를 줘서 그 안에서 줄바꿈되게 한다. */
#table-asset-outbound-profiles,
#table-exposure-outbound {
  table-layout: fixed;
}
.col-outbound-dest {
  width: 25%;
  white-space: normal;
}
/* 외부 통신 이상(Beaconing) 목록 화면은 MAC/Hostname/IP/포트/근거 칼럼이 더 있어(자산상세
   탭보다 칼럼 수가 많음) 목적지/점수를 더 좁게 잡는다(사용자 요청, 2026-08-13) - 공유
   클래스(.col-outbound-dest)의 25%를 이 표에서만 ID 선택자로 덮어쓴다. */
#table-exposure-outbound .col-outbound-dest {
  width: 16%; /* 20 → 16 % (2026-09-08, 관측 칸 신설 - 아래 주석) */
}
.col-outbound-score {
  width: 6%;
}
/* 나머지 칼럼 너비도 같은 이유(사용자 요청, 2026-08-13) - 남는 공간을 근거 칼럼에 더
   준다. 근거는 자산당 여러 줄(<br>로 join)이라 본문 글자만 살짝 줄여 세로 공간도 아낀다. */
.col-outbound-mac { width: 10%; }
.col-outbound-hostname {
  width: 15%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.col-outbound-ip {
  width: 8%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.col-outbound-port { width: 7%; }
#table-exposure-outbound td.col-outbound-reasons { font-size: 0.85em; }
/* 관측 칸(사용자 요청, 2026-09-08 - "목록 테이블이 깨진다"): 상태 배지 옆에 관측 회수, 그 아래 처음~마지막
   관측을 두 줄(from ~ / to)로 고정폭 안에 넣는다. 예전엔 폭 지정이 없어 한 줄 "from ~ to" 가 어중간하게
   접히며 표가 깨졌다. IP 10→8 %, 목적지 20→16 % 로 좁혀 관측(14 %)을 만들고 남는 폭은 근거가 갖는다. */
#table-exposure-outbound .col-outbound-hostname { width: 13%; }
.col-outbound-seen { width: 14%; }
.outbound-seen-count { margin-left: 0.35em; white-space: nowrap; }
.outbound-seen-range span { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Insights "오늘의 변화" / Exposure "현재 Open" (같은 행 렌더러 app-insights.js::_insightRowHtml):
   세부 칸(도메인·인증서 SAN·바뀐 값 등)이 길면 auto 레이아웃에서 표가 계속 늘어났다(사용자 요청, 2026-09-08).
   외부 통신 이상 표와 같은 처방 - table-layout: fixed + 칼럼 폭, 세부는 2줄까지만 보이고(줄 수 제한 + 말줄임)
   전체 값은 title 툴팁. 세부 칼럼은 폭을 안 줘 남는 공간을 모두 갖는다. */
#table-insights-changes,
#table-exposure-open {
  table-layout: fixed;
}
/* 전역 td 는 nowrap 이라 고정폭 안에서 긴 hostname·담당자 이름이 옆 칸으로 넘친다 - 이 두 표만 칸 안에서 줄바꿈. */
#table-insights-changes td,
#table-exposure-open td {
  white-space: normal;
  overflow-wrap: anywhere;
}
.col-insight-score { width: 5%; }
.col-insight-kind { width: 13%; }
.col-insight-title { width: 17%; }
.col-insight-when { width: 12%; }
.col-insight-status { width: 12%; }
.col-insight-actions { width: 15%; }
/* 자산 상세 탭 데이터 로드 실패 표시(app-assets.js::markAssetDetailTabFailed) */
.detail-tab-btn.detail-tab-failed::after { content: " ⚠"; color: var(--danger); }

.insight-detail-clamp {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  white-space: normal;
  overflow-wrap: anywhere;
}

/* 다중 클라이언트 의심 표의 판정 근거 - 증거 문자열을 세미콜론으로 이어붙이면 길어질 수
   있어(사용자 요청, 2026-08-31) auto 테이블 레이아웃에서 폭 지정이 안 먹는 문제를
   col-outbound-dest 와 같은 방식(table-layout: fixed)으로 해결하고 줄바꿈을 허용한다.
   fixed 레이아웃은 폭을 안 준 칼럼을 남는 공간으로 균등 분배하는 게 아니라 첫 행 셀
   내용 기준으로 잡아버려(MAC 등 nowrap 칼럼이 짧게 잡히며 옆 칼럼과 겹쳐 보이는 문제
   실측) 나머지 칼럼도 전부 폭을 명시해야 한다. */
#table-exposure-multi-client { table-layout: fixed; }
#table-exposure-multi-client .col-mc-hostname { width: 12%; }
#table-exposure-multi-client .col-mc-mac { width: 13%; }
#table-exposure-multi-client .col-mc-ip { width: 9%; }
#table-exposure-multi-client .col-mc-devicetype { width: 8%; }
#table-exposure-multi-client .col-mc-osfamilies { width: 10%; }
#table-exposure-multi-client .col-multi-client-evidence {
  width: 40%;
  white-space: normal;
}
#table-exposure-multi-client .col-mc-risk { width: 8%; }
/* 도메인 주소가 한 줄을 넘으면 줄바꿈 대신 자르고(ellipsis), 전체 값은 title 속성
   네이티브 툴팁으로 마우스오버 시 보여준다(사용자 요청, 2026-08-12) - 도메인이 계속
   늘어나 셀/행 높이가 들쭉날쭉해지는 것을 막는다. */
.outbound-dest-domain {
  display: block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--muted);
  font-size: 0.9em;
}

.ip-history-row {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 4px 0;
  font-size: 0.85rem;
}
.ip-history-ip { font-family: "Cascadia Code", Consolas, monospace; }
.ip-history-time { color: var(--muted); }

/* 중복 자산 관리(docs/DUPLICATE_ASSET_MANAGEMENT_PLAN.md) - 후보 쌍을 좌우 비교
   카드로 보여준다. */
.dup-card {
  background: var(--bg);
  border: 1px solid var(--panel-border);
  border-radius: 8px;
  padding: 14px;
  margin-bottom: 14px;
}
.dup-card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
  font-size: 0.85rem;
}
.dup-basis-badge {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  font-weight: 600;
}
.dup-evidence { color: var(--muted); }
.dup-card-body {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}
.dup-asset-box {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 6px;
  padding: 10px 12px;
}
.dup-asset-name { font-weight: 600; margin-bottom: 6px; }
.dup-actions {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-top: 12px;
  flex-wrap: wrap;
}
.dup-status-badge { color: var(--muted); font-size: 0.85rem; margin-right: auto; }

@media (max-width: 800px) {
  .dup-card-body { grid-template-columns: 1fr; }
}

/* STP(PVST+) 논리 장비 그룹 - 자산 목록에서 같은 물리 스위치의 VLAN별 MAC들을 접어서
   보여준다(app.js::computeStpGroups 참고). */
.asset-group-header { background: rgba(56, 189, 248, 0.06); }
.asset-group-header .col-mac { font-weight: 600; }
.stp-group-toggle {
  background: none;
  border: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  padding: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.stp-group-toggle:hover { color: var(--accent); }
.asset-group-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  white-space: nowrap;
  background: rgba(56, 189, 248, 0.15);
  color: var(--accent);
}
.asset-group-member { background: rgba(148, 163, 184, 0.05); }
.asset-group-member .col-mac { padding-left: 28px; }

.risk-badge, .approval-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
}
.asset-list-approval { text-align: center; }
.asset-list-os-version { font-size: 0.78rem; color: var(--muted); white-space: nowrap; }
.asset-list-model { font-size: 0.78rem; color: var(--muted); white-space: nowrap; }
.risk-badge.risk-low { background: rgba(52, 211, 153, 0.15); color: var(--accent-2); }
.risk-badge.risk-mid { background: rgba(251, 191, 36, 0.15); color: #fbbf24; }
.risk-badge.risk-high { background: rgba(248, 113, 113, 0.15); color: var(--danger); }
/* 클라우드 자산 위험도는 5단계(낮음/보통/높음/매우높음/심각) — risk-high 위에 두 단계를
   더 얹는다(온프레미스는 3단계라 이 두 클래스를 안 씀). */
.risk-badge.risk-very-high { background: rgba(248, 113, 113, 0.28); color: var(--danger); }
.risk-badge.risk-critical { background: var(--danger); color: #fff; }

/* risk-badge 와 같은 모양/색상이지만 의미가 반대(높을수록 좋음)라 클래스명을 분리한다 -
   "신뢰도 95%인데 risk-low 클래스"처럼 헷갈리지 않도록. */
.confidence-badge { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 0.72rem; font-weight: 600; white-space: nowrap; margin-left: 6px; cursor: help; }
.confidence-badge.confidence-high { background: rgba(52, 211, 153, 0.15); color: var(--accent-2); }
.confidence-badge.confidence-mid { background: rgba(251, 191, 36, 0.15); color: #fbbf24; }
.confidence-badge.confidence-low { background: rgba(248, 113, 113, 0.15); color: var(--danger); }

/* confidence-badge 와 같은 모양이되 의미가 다르다(신뢰도 아니라 IP 할당방식 종류) -
   DHCP는 직접 관측된 확정값이라 accent-2(초록), 정적(추정)/불명은 추정/무근거라 각각
   주의색/muted로 구분한다. */
.ip-assign-badge { display: inline-block; padding: 1px 7px; border-radius: 999px; font-size: 0.7rem; font-weight: 600; white-space: nowrap; margin-left: 6px; cursor: help; }
.ip-assign-badge.ip-assign-dhcp { background: rgba(52, 211, 153, 0.15); color: var(--accent-2); }
.ip-assign-badge.ip-assign-static { background: rgba(251, 191, 36, 0.15); color: #fbbf24; }

.approval-badge.approval-approved { background: rgba(52, 211, 153, 0.15); color: var(--accent-2); }
.approval-badge.approval-unapproved { background: rgba(148, 163, 184, 0.15); color: var(--muted); }
.approval-badge.approval-exception { background: rgba(56, 189, 248, 0.15); color: var(--accent); }
.approval-badge.approval-blocked { background: rgba(248, 113, 113, 0.15); color: var(--danger); }
.approval-badge.approval-review-needed { background: rgba(251, 191, 36, 0.15); color: #fbbf24; }

.asset-detail-risk-reasons { display: flex; flex-direction: column; gap: 4px; margin: 6px 0; }
.asset-detail-risk-reasons span { font-size: 0.82rem; color: var(--muted); }
.asset-detail-risk-reasons span::before { content: "· "; }

.asset-detail-approval-form { display: flex; flex-direction: column; gap: 4px; }
.asset-detail-approval-form .btn-primary { align-self: flex-start; }
/* 승인 탭(승인상태/담당자/관리자)만 세로 배치 + 좁은 폭으로 조정(사용자 요청,
   2026-08-07) - 모달이 넓어져(1344px) 옆으로 쭉 늘어난 select/input이 어색해 보였다.
   .field-row 는 다른 화면(설정 폼 등)에서 가로 배치로 계속 쓰이므로 이 폼 안에서만
   덮어쓴다. */
.asset-detail-approval-form .field-row { flex-direction: column; gap: 4px; }
.asset-detail-approval-form .field { max-width: 280px; }

/* 수동 재정의 4종(벤더/장비유형/OS/제품명) 전용 - 탭 패널 폭(~600px)에 비해 라벨+입력만
   세로로 쌓으면 좌우가 텅 비어 보인다(사용자 요청, 2026-08-05). 입력을 왼쪽 고정폭에,
   설명글을 가운데 남는 공간에, 저장 버튼+결과 메시지를 오른쪽에 배치해 가로 폭을 실제로
   활용한다. */
.override-row {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  padding: 10px 0;
  border-bottom: 1px solid var(--panel-border);
}
.override-row:last-of-type { border-bottom: none; }
.override-row-field { flex: 0 0 260px; display: flex; flex-direction: column; gap: 6px; }
.override-row-field span { font-size: 0.85rem; color: var(--muted); }
.override-row-field input {
  padding: 6px 8px;
  border-radius: 6px;
  border: 1px solid var(--panel-border);
  background: var(--bg);
  color: var(--text);
  font-size: 0.85rem;
}
.override-row-hint { flex: 1; min-width: 160px; font-size: 0.8rem; color: var(--muted); padding-top: 26px; }
.override-row-actions {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
  padding-top: 22px;
  min-width: 110px;
}
.override-row-actions .field-hint { text-align: right; margin: 0; }

/* 담당자/관리자 자동완성 - 사용자 테이블에서 이름/아이디/부서로 검색해 고르는 위젯
   (app-assets.js:renderApprovalEmbed, app.js:wireUserPicker) */
.user-picker-field { position: relative; }
.user-picker { position: relative; }
.user-picker-dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 20;
  margin-top: 2px;
  background: var(--bg);
  border: 1px solid var(--panel-border);
  border-radius: 6px;
  max-height: 200px;
  overflow-y: auto;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.user-picker-item {
  padding: 8px 10px;
  font-size: 0.85rem;
  cursor: pointer;
}
.user-picker-item:hover, .user-picker-item.active { background: var(--panel-border); }
.user-picker-empty { padding: 8px 10px; font-size: 0.82rem; color: var(--muted); }

.asset-detail-right {
  flex: 65 1 0;
  min-width: 280px;
  display: flex;
  flex-direction: column;
  border-left: 1px solid var(--panel-border);
  padding-left: 24px;
  height: 100%;
  min-height: 0;
}

.detail-tabs { display: flex; gap: 4px; margin-bottom: 12px; border-bottom: 1px solid var(--panel-border); }
/* 자산 상세 탭만 11개(Connections~External Sources, 2026-08-31 영문화 이후 라벨 길이가
   늘어남)라 모달 폭(asset-detail-right, 최대 1478px 모달의 65%)에서 한 줄로 다 못 들어가
   마지막 탭이 잘렸다 - 다른 detail-tabs(cloud-asset-detail-tabs 4개, asset-history-subtabs
   4개)는 여유가 있어 그대로 두고 이 탭바만 줄바꿈 허용. */
#asset-detail-tabs { flex-wrap: wrap; row-gap: 4px; }
.detail-tab-btn {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--muted);
  padding: 8px 12px;
  font-size: 0.85rem;
  font-family: inherit;
  cursor: pointer;
}
.detail-tab-btn:hover { color: var(--text); }
.detail-tab-btn.active { color: var(--accent); border-bottom-color: var(--accent); }

.detail-tab-panel {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  /* flex 아이템의 기본 min-width 는 auto(내용 기준) 라, 안에 넓은 표가 들어가면 이
     패널 자체가 늘어나 모달 밖으로 넘친다(overflow-x:auto 가 있어도 무용지물) - 0으로
     낮춰야 표 쪽의 overflow-x 스크롤이 실제로 작동한다(실측 2026-07-21, 애플리케이션
     탭 표 추가 후 Shadow IT 칼럼이 모달 밖으로 잘려나감). 세로도 같은 이유로 min-height:0
     이 없으면 내용이 길 때 overflow-y:auto 대신 부모(모달 전체)를 늘려버린다. */
  min-width: 0;
  min-height: 0;
}
.detail-tab-panel[hidden] { display: none; }

/* Administrator > 알림 페이지의 탭(채널/규칙/이력) - .detail-tab-panel 을 그대로
   재사용하면 자산 상세 모달의 전역 querySelectorAll(".detail-tab-panel") 이 이 페이지의
   패널까지 같이 숨겨버려서(위 "이력 관리" 중첩 탭과 같은 문제) 전용 클래스로 분리한다.
   탭 버튼은 "#notification-tabs .detail-tab-btn"로 스코프된 별도 함수(switchNotificationTab)
   가 처리하므로 .detail-tab-btn 스타일은 그대로 재사용해도 안전하다. */
.notification-tab-panel[hidden] { display: none; }

/* 탐지정책 페이지의 탭(기본설정/프로토콜탐지/데이터보존/기타, 2026-09-08) - notification-tab-panel
   과 같은 이유로 전용 클래스로 분리한다. .settings-panel(flex column + gap)을 그대로 물려받아
   탭으로 나누기 전과 같은 필드 간격을 유지한다. */
.dp-tab-panel[hidden] { display: none; }

/* 이력 관리 탭 안의 IP/Hostname/OS 중첩 탭 - .detail-tab-panel 을 그대로 재사용하면
   switchAssetDetailTab() 의 "document.querySelectorAll('.detail-tab-panel')" 이 스코프
   없이 전체 문서를 훑어서 이 중첩 패널까지 같이 숨겨버린다(바깥 탭 전환마다 안쪽 탭이
   깨짐) - 그래서 별도 클래스로 분리한다. 탭 버튼(.detail-tab-btn)은 바깥 전환 로직이
   "#asset-detail-tabs .detail-tab-btn"로 스코프돼 있어 그대로 재사용해도 안전하다. */
.history-subtab-panel {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}
.history-subtab-panel[hidden] { display: none; }

#asset-detail-topology {
  flex: 1;
  display: flex;
  flex-direction: column;
  height: 380px;
}
#asset-detail-topology .empty-state {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}
.asset-detail-topology-more { color: var(--muted); font-size: 0.75rem; margin: 6px 0 0; text-align: center; }

.topology-node-center .topology-node-icon {
  width: 40px;
  height: 40px;
  font-size: 18px;
  box-shadow: 0 0 0 3px var(--accent);
}
.topology-node-center .topology-node-title { font-weight: 700; }

.pie-chart-wrap { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.pie-chart { flex-shrink: 0; }
.pie-svg { display: block; }
.pie-slice { stroke: var(--panel); stroke-width: 2; transition: opacity 0.15s; }
.pie-slice:hover { opacity: 0.82; }

.pie-legend { display: flex; flex-direction: column; gap: 8px; flex: 1; min-width: 160px; }
.pie-legend .legend-item { font-size: 0.85rem; color: var(--text); }
.legend-value { color: var(--muted); margin-left: 4px; }

/* 단일 계열 수평 막대그래프(renderBarChart) - 카테고리가 많을 수 있는 데이터(예: 서브넷별
   자산 수)용. 범례 없이 라벨을 막대 옆에 바로 붙이는 형태라 파이 차트보다 항목이 많을 때
   더 읽기 쉽다. */
.bar-chart { display: flex; flex-direction: column; gap: 8px; }
.bar-chart-row { display: grid; grid-template-columns: 150px 1fr 44px; align-items: center; gap: 10px; font-size: 0.82rem; }
.bar-chart-label { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-chart-track { background: var(--panel-border); border-radius: 4px; height: 10px; overflow: hidden; }
.bar-chart-fill { display: block; height: 100%; background: var(--accent); border-radius: 4px; }
.bar-chart-value { text-align: right; font-weight: 600; }
.bar-chart-omitted { color: var(--muted); font-size: 0.78rem; margin-top: 2px; }

.risk-index-headline { display: flex; align-items: baseline; gap: 12px; margin-bottom: 8px; }
.risk-index-score { font-size: 2.4rem; font-weight: 700; color: var(--text); line-height: 1; }

table { width: 100%; border-collapse: collapse; margin-top: 12px; font-size: 0.85rem; }
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--panel-border); white-space: nowrap; }
th { color: var(--muted); font-weight: 600; }
tr.is-new td:first-child { border-left: 3px solid var(--accent-2); }

/* asset-detail-right 처럼 좁은 패널 안에 표를 넣을 때 - 칼럼이 넘치면 잘리는 대신
   패널이 아니라 표 자체가 가로 스크롤되게 한다(모달 전체 레이아웃이 깨지지 않도록). */
.table-scroll { overflow-x: auto; min-width: 0; }

/* 자산상세 Risk 탭 위험 신호 목록 - 칼럼이 하나뿐이라 위 기본 white-space:nowrap 이
   길어질 때 가로 스크롤만 만들고 텍스트는 안 보이게 잘랐다(사용자 요청, 2026-09-01) -
   이 표만 줄바꿈되게 되돌린다. */
#table-asset-detail-risk-reasons td { white-space: normal; }

/* 좁은 asset-detail-right 패널 안에서 7개 칼럼이 가로 스크롤 없이 들어가도록 -
   table-layout: fixed 로 colgroup 폭을 고정하고, 셀은 줄바꿈을 허용한다(막으면
   내용 기준으로 폭이 늘어나 결국 스크롤이 발생함). */
#table-asset-detail-applications { table-layout: fixed; }
#table-asset-detail-applications th, #table-asset-detail-applications td { white-space: normal; }
#table-asset-detail-applications .approval-badge { white-space: nowrap; display: inline-block; }
/* 날짜/시간을 한 줄로 이어붙이면 칼럼폭 때문에 어중간한 지점에서 줄바꿈된다 - 처음부터
   날짜/시간을 별도 줄로 분리한다(formatDateTwoLine()). */
.cell-date-line { display: block; white-space: nowrap; }
.cell-date-line:last-child { color: var(--muted); font-size: 0.8em; }

/* cell-date-line 과 같은 "칼럼 안에서 두 줄로 분리" 용도이지만, :last-child 색상/크기
   자동 축소가 붙으면 안 되는 곳(예: 승인 배지 - 담당자 없을 때 배지 혼자 last-child 가
   되어 원치 않게 흐려지는 문제)에 쓴다. */
/* overflow: hidden/text-overflow: ellipsis(사용자 요청, 2026-08-13) - 목적지 칼럼을
   더 좁게 잡은 화면(외부 통신 이상 목록)에서 IP+국가 줄이 넘치면 옆으로 새지 않고
   "..."로 잘리게 한다. */
.cell-block-line { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.empty-state { color: var(--muted); font-size: 0.85rem; margin-top: 8px; }

/* ---------------------------------------------------------------- 토폴로지 */

.legend {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-bottom: 12px;
  font-size: 0.78rem;
  color: var(--muted);
}
.legend-item { display: flex; align-items: center; gap: 5px; }
.legend-dot { width: 10px; height: 10px; border-radius: 999px; display: inline-block; }
.legend-icon { font-size: 0.85rem; line-height: 1; }
.legend-line { width: 18px; height: 0; border-top: 2px dashed var(--accent-2); display: inline-block; }
.legend-line-confirmed { border-top-style: solid; }

.topology-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: 0.82rem;
  cursor: pointer;
}

.topology-container {
  overflow: auto;
  background: var(--bg);
  border: 1px solid var(--panel-border);
  border-radius: 8px;
  padding: 12px;
  min-height: 520px;
  max-height: 75vh;
}

.topology-svg { display: block; }

.topo-root rect { fill: var(--accent); }
.topo-root-label { fill: #0f172a; font-size: 13px; font-weight: 700; }

.topo-subnet rect { fill: var(--panel); stroke: var(--panel-border); stroke-width: 1; }
.topo-subnet-label { fill: var(--text); font-size: 12px; font-weight: 600; }
.topo-subnet-count { fill: var(--muted); font-size: 10px; }

.topo-line { stroke: var(--panel-border); stroke-width: 1.5; }
.topo-line-thin { stroke-width: 1; opacity: 0.6; }

.topo-asset { cursor: pointer; }
.topo-asset circle { stroke: var(--bg); stroke-width: 2; }
.topo-asset:hover circle { stroke: var(--accent); }
.topo-asset-icon { font-size: 13px; pointer-events: none; }
.topo-asset-label { fill: var(--muted); font-size: 9px; }

.topo-neteq rect { fill: var(--panel); stroke: #facc15; stroke-width: 1.5; }
.topo-neteq:hover rect { stroke: var(--accent); }
.topo-neteq-label { fill: var(--text); font-size: 11px; font-weight: 600; pointer-events: none; }

/* 클라우드 자산 간 "잠재적 연결 가능성"(보안그룹 규칙 기반 추정) — 실제 Zeek 관측 통신
   (온프레미스 토폴로지, frontend/topology.js 는 이 엣지를 인라인 style로 그림)과 의미가
   완전히 다르므로 색/대시 패턴을 다르게 써서 절대 같은 시각 언어로 오인되지 않게 한다. */
.topo-edge-potential {
  stroke: #f59e0b;
  stroke-width: 1.3;
  stroke-dasharray: 2 5;
  opacity: 0.55;
  transition: opacity 0.15s;
}
.topo-edge-potential.dim { opacity: 0.06; }
.topology-container.hide-potential-links .topo-edge-potential { display: none; }

/* 로드밸런서↔EC2(타겟그룹 등록)/인증서(리스너 ARN)/도메인(Route53 DNS 이름) 연결 — 위
   .topo-edge-potential 과 달리 SG 규칙 추정이 아니라 AWS 가 보고한 ID/ARN을 직접 대조한
   것이라 실선으로 구분한다(사용자 요청, 2026-08-07 - cloud_topology.py::
   compute_load_balancer_links). */
.topo-edge-confirmed {
  stroke: #38bdf8;
  stroke-width: 1.4;
  opacity: 0.7;
}
/* 인증서 → 로드밸런서 연결선만 별도 색으로 구분(사용자 요청, 2026-08-10) - 도메인/EC2
   연결선(위 .topo-edge-confirmed, 파란색)과 같은 굵기/투명도, 색만 다르게. */
.topo-edge-confirmed-cert {
  stroke: #f472b6;
  stroke-width: 1.4;
  opacity: 0.7;
}

/* EC2에 연결된 탄력적 IP(Elastic IP) 배지 — 촘촘한 VPC 그리드에서는 별도 노드/엣지 대신
   인스턴스 원 위에 겹쳐 그리는 작은 점으로 표시한다(사용자 요청, 2026-08-07). */
.topo-eip-badge { fill: #fbbf24; stroke: var(--bg); stroke-width: 1.5; }

/* AWS VPC 통합 뷰(사용자 요청, 2026-08-11) - VPC마다 별도 패널/SVG로 나뉘어 있던 것을
   하나의 캔버스에 VPC별 "영역"(경계 상자 + 라벨)으로 묶어서 보여준다. 개별 VPC
   다이어그램(renderCloudVpcTopologyDiagram이 만드는 내부 요소들)은 그대로 재사용하고
   이 영역 상자만 그 바깥에 덧그린다. */
.topo-vpc-region { fill: var(--panel); stroke: var(--panel-border); stroke-width: 1.5; opacity: 0.5; }
.topo-vpc-region-label { fill: var(--muted); font-size: 11px; font-weight: 600; }
.topo-region-empty { fill: var(--muted); font-size: 11px; }

.cloud-vpc-panel { margin-bottom: 16px; }
.cloud-vpc-panel h3 { margin: 0 0 8px; font-size: 0.9rem; }
.cloud-vpc-panel .topology-container { min-height: 260px; }
.cloud-vpc-lb-section { margin-top: 12px; }
.cloud-vpc-lb-section h4 { margin: 0 0 8px; font-size: 0.82rem; color: var(--muted); }
.cloud-vpc-lb-section .topology-container { min-height: 340px; }

/* --------------------------------------------------- 토폴로지 (React Flow) */

#topology-root {
  height: 75vh;
  border: 1px solid var(--panel-border);
  border-radius: 8px;
  overflow: hidden;
}

#topology-root .react-flow,
#asset-detail-topology .react-flow {
  --xy-background-color-default: var(--bg);
  --xy-background-pattern-dots-color-default: var(--panel-border);
  --xy-edge-stroke-default: var(--panel-border);
  --xy-edge-stroke-width-default: 1.5;
  --xy-node-border-default: var(--panel-border);
  --xy-node-background-color-default: var(--panel);
  --xy-node-color-default: var(--text);
  --xy-minimap-background-color-default: var(--panel);
  --xy-minimap-mask-background-color-default: rgba(15, 23, 42, 0.6);
  --xy-controls-button-background-color-default: var(--panel);
  --xy-controls-button-background-color-hover-default: var(--bg);
  --xy-controls-button-color-default: var(--text);
  --xy-controls-button-color-hover-default: #fff;
  --xy-controls-button-border-color-default: var(--panel-border);
}

.topology-node {
  position: relative; /* 아이콘 중심에 고정되는 CenterHandles 의 기준점 */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  width: 112px;
  cursor: pointer;
}
.topology-node:hover .topology-node-icon { filter: brightness(1.25); transform: scale(1.08); }

/* 인터넷 노출 자산(Exposure) - 토폴로지2에만 적용(topology-shared.js::AssetNode 주석
   참고). 아이콘 배경색은 device_type 구분에 이미 쓰이고 있어 덮어쓰지 않고, 빨간
   링(box-shadow)만 아이콘 둘레에 얹어 두 정보(장비유형/노출여부)를 동시에 보여준다. */
.topology-node-exposed .topology-node-icon {
  box-shadow: 0 0 0 3px #ef4444, 0 0 10px 2px rgba(239, 68, 68, 0.55);
}
.topology-node-exposed .topology-node-title { color: #ef4444; }

.topology-node-icon {
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  margin-bottom: 2px;
  transition: transform 0.1s;
}

.topology-node-title {
  width: 100%;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text);
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topology-node-sub {
  width: 100%;
  font-size: 0.68rem;
  color: var(--muted);
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.topology-neteq-node {
  position: relative; /* 박스 중심에 고정되는 CenterHandles 의 기준점 */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 150px;
  height: 46px;
  padding: 0 10px;
  background: var(--panel);
  border: 2px solid var(--panel-border);
  border-radius: 7px;
  cursor: pointer;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}
.topology-neteq-node:hover { filter: brightness(1.15); }
.topology-neteq-icon { font-size: 14px; flex: none; }
.topology-neteq-label {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.topology-internet-node {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 130px;
  height: 40px;
  background: var(--accent);
  color: #0f172a;
  font-size: 0.82rem;
  font-weight: 700;
  border-radius: 8px;
}

.topology-subnet-group {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  border: 1px dashed var(--panel-border);
  border-radius: 10px;
  background: rgba(148, 163, 184, 0.06);
}
.topology-subnet-group-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 14px;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--text);
}
.topology-subnet-group-count {
  font-size: 0.72rem;
  font-weight: 400;
  color: var(--muted);
}

/* ---------------------------------------------------------------- 설정 */

.settings-panel { max-width: 480px; display: flex; flex-direction: column; gap: 4px; }
.settings-panel h2:not(:first-child) { margin-top: 16px; }
.settings-panel-wide { max-width: 720px; }

/* 폭이 넓은 탭 패널에서 관련된 두 필드(담당자/관리자 등)를 나란히 배치할 때 사용
   (사용자 요청, 2026-08-05) - .field 를 그대로 감싸므로 각 필드 자체의 스타일은 안 바뀐다. */
.field-row { display: flex; gap: 16px; }
.field-row .field { flex: 1; min-width: 0; }

.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 4px; }
.field span { font-size: 0.85rem; color: var(--muted); }
.field input, .field select {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--panel-border);
  border-radius: 6px;
  padding: 8px 10px;
  font-size: 0.9rem;
}

.field input:disabled, .field select:disabled { opacity: 0.5; cursor: not-allowed; }

.field-hint { color: var(--muted); font-size: 0.78rem; margin: 4px 0 0; }
.field-hint code { color: var(--accent); }

.field-checkbox { flex-direction: row; align-items: center; gap: 8px; }
.field-checkbox input { flex: 0 0 auto; }

/* 탐지정책 화면 — 프로토콜/OT 체크박스 그리드 + 자산 병합 규칙 고정 목록 */
.policy-checkbox-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 8px 16px;
  margin: 8px 0 16px;
}
.policy-checkbox-grid label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.88rem;
}
.policy-disabled-option { color: var(--muted); cursor: help; }

.policy-fixed-rules {
  margin: 8px 0 16px;
  padding-left: 20px;
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1.8;
}

.pagination { display: flex; align-items: center; gap: 4px; margin-top: 12px; flex-wrap: wrap; }
.pagination span { color: var(--muted); font-size: 0.85rem; }
.pager-btn { padding: 6px 10px; font-size: 0.82rem; }
.pager-num { min-width: 32px; }
.pager-num.active { background: var(--accent); color: #0f172a; border-color: var(--accent); font-weight: 700; }

/* 자산 목록 전체 수/from~to/페이지당 개수 선택(사용자 요청, 2026-09-01) - 위 .pagination과
   같은 줄에 나란히 두되, renderPager()가 .pagination(#assets-pagination) 내부만
   통째로 다시 그리므로 요약 텍스트/페이지당 선택은 형제 엘리먼트로 따로 둔다. */
/* 사용자 요청(2026-09-02) - 페이지 버튼(<< < 1 2 3 > >>)은 왼쪽 그대로 두고,
   전체 수/from~to 요약과 페이지당 선택은 오른쪽으로 묶어서 배치한다. */
.pagination-bar { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-top: 12px; flex-wrap: wrap; }
.pagination-bar .pagination { margin-top: 0; }
.pagination-bar #assets-pager-summary { color: var(--muted); font-size: 0.85rem; }
.pager-right-group { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.page-size-select { display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: 0.85rem; }

.settings-actions { display: flex; gap: 8px; margin-top: 16px; }
.btn-primary, .btn-secondary, .btn-danger {
  border-radius: 6px;
  padding: 8px 16px;
  font-size: 0.85rem;
  cursor: pointer;
  border: 1px solid var(--panel-border);
}
.btn-primary { background: var(--accent); color: #0f172a; font-weight: 600; border-color: var(--accent); }
.btn-secondary { background: transparent; color: var(--text); }
.btn-secondary:hover { border-color: var(--accent); }
/* Administrator > 데이터 초기화(사용자 요청, 2026-09-02)처럼 되돌릴 수 없는 파괴적
   액션 전용 - btn-primary(강조)와 시각적으로 확실히 구분한다. */
.btn-danger { background: var(--danger); color: #fff; font-weight: 600; border-color: var(--danger); }
.btn-danger:disabled { background: var(--panel-border); border-color: var(--panel-border); color: var(--muted); cursor: not-allowed; }

/* ---------------------------------------------------------------- 센서 관리 */

.status-dot { display: inline-block; width: 8px; height: 8px; border-radius: 999px; margin-right: 6px; }
.status-dot.online { background: var(--accent-2); }
.status-dot.offline { background: var(--muted); }
.status-dot.danger { background: var(--danger); }

.token-cell { display: flex; align-items: center; gap: 8px; }
.token-cell code { color: var(--muted); }

.btn-link {
  background: none;
  border: none;
  color: var(--accent);
  cursor: pointer;
  font-size: 0.82rem;
  padding: 0;
  text-decoration: underline;
}
.btn-link:hover { color: var(--text); }
.btn-link.danger { color: var(--danger); }

/* display:flex를 쓰면 td가 테이블 행 높이에 맞춰 늘어나지 않아(익명 셀로 분리됨),
   행이 다른 셀 때문에 높아질 때 border-bottom이 중간에서 끊겨 보임 - margin으로 간격만 줌 */
.row-actions { white-space: nowrap; }
.row-actions > * + * { margin-left: 10px; }

/* ---------------------------------------------------------------- 모달 */

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}
.modal-overlay[hidden] { display: none; }
/* saas-usage-detail-modal 은 자산 상세 모달의 애플리케이션 탭에서 "그 위에 겹쳐" 열릴
   수 있다 - 같은 z-index(100)에 DOM 순서만으로 쌓으면 뒤에 나오는 asset-detail-modal
   이 항상 위에 그려져 안 보인다(2026-07-21 실측). 다른 모달과 동시에 뜨는 유일한
   모달이라 여기만 z-index 를 높여 항상 최상단에 오게 한다. */
#saas-usage-detail-modal { z-index: 110; }
/* vuln-detail-modal 도 자산 상세 모달의 취약점 탭에서 같은 이유로 그 위에 겹쳐 열린다. */
#vuln-detail-modal { z-index: 110; }
/* saas-usage-events-modal(사용자 요청, 2026-09-04 - "사용내역 클릭하면 세부 내역
   조회")은 saas-usage-detail-modal 위에 겹쳐 열린다 - 위와 같은 이유로 saas-usage-
   detail-modal(110)보다 더 높여야 한다. */
#saas-usage-events-modal { z-index: 120; }

.modal-box {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: 10px;
  padding: 20px;
  width: 100%;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.modal-box h3 { margin: 0; font-size: 1rem; }
.detail-subsection-title { margin: 16px 0 8px; font-size: 0.85rem; color: var(--muted); }
/* .modal-box 는 flex-direction:column + gap 을 직계 자식에만 준다 - AWS/Azure 자격증명
   필드를 provider별로 묶어 토글하려고 <div> 로 감쌌더니 그 안의 label 필드끼리 간격이
   없어져서 붙어보였다(2026-08-10) - 래퍼 자체에도 같은 flex+gap을 준다.
   :not([hidden]) 필수 - ID 선택자(display:flex)가 UA 기본 [hidden]{display:none} 보다
   우선해버려서, 이 규칙 없이는 hidden 속성으로 숨긴 쪽도 계속 보였다(실측). */
#cloud-account-form-aws-fields:not([hidden]),
#cloud-account-form-azure-fields:not([hidden]),
#cloud-account-form-gcp-fields:not([hidden]) {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
#cloud-account-form-gcp-key { font-family: monospace; font-size: 0.8rem; resize: vertical; }
.modal-box-wide { max-width: 940px; width: 95vw; }
/* 자산 상세 모달만 더 넓게(940px -> 1222px, 2026-08-07 요청으로 1222px -> 1344px, 2026-08-18
   요청으로 1344px -> 1478px 추가 10% 확대) - cloud-asset-detail-modal 은 같은
   .modal-box-wide 를 공유하므로 여기서 그대로 키우면 같이 넓어져 범위를 벗어난다. 높이도
   max-height 가 아니라 height 로 고정해, 탭을 옮겨 다니거나 내용이 짧은/긴 탭을 오갈 때
   모달 자체가 늘었다 줄었다 하지 않게 한다 - 안쪽(asset-detail-body/-right)이 각자
   overflow-y:auto 로 스크롤을 떠맡는다. */
#asset-detail-modal .modal-box-wide { max-width: 1478px; height: 85vh; }
/* saas-usage-detail-modal 도 asset-detail-modal 처럼 표+페이징을 담기엔 기본
   modal-box-wide(940px) 로 충분하지만, 세로는 고정 height 로 둬서 페이지를 넘길 때
   모달 크기가 늘었다 줄었다 하지 않게 한다. */
#saas-usage-detail-modal .modal-box-wide { height: 70vh; }
#saas-usage-detail-modal #table-saas-usage-detail { flex: 1 1 0; height: 100%; overflow-y: auto; display: block; }
#saas-usage-detail-modal #table-saas-usage-detail thead,
#saas-usage-detail-modal #table-saas-usage-detail tbody { display: table; width: 100%; table-layout: fixed; }

.field-error { color: var(--danger); }

.connection-info {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--panel-border);
  border-radius: 6px;
  padding: 10px;
  font-family: "Cascadia Code", Consolas, monospace;
  font-size: 0.82rem;
  resize: none;
}

/* ---------------------------------------------------------------- 시스템 */

.panel-header-actions { display: flex; gap: 8px; align-items: center; }

.metric-chart-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-top: 12px;
}
@media (max-width: 900px) {
  .metric-chart-grid { grid-template-columns: 1fr; }
}

.metric-chart {
  background: var(--bg);
  border: 1px solid var(--panel-border);
  border-radius: 8px;
  padding: 12px 14px;
}
/* Reports 개요 - "기간 내 변화"/"현재 상태" 카드 그룹 라벨(사용자 피드백, 2026-09-02 -
   두 종류 지표가 섞여 보여 전부 선택 기간 기준으로 오해될 수 있다는 지적에서 분리). */
.reports-section-label { color: var(--muted); font-size: 0.8rem; font-weight: 600; margin: 16px 0 8px; }
.reports-section-label:first-child { margin-top: 0; }
.metric-chart-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 4px;
}
.metric-chart-title { font-size: 0.85rem; font-weight: 600; }
.metric-chart-legend { display: flex; gap: 12px; font-size: 0.78rem; color: var(--muted); }
.metric-chart-legend-item { display: inline-flex; align-items: center; gap: 4px; }
.metric-chart-swatch { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.metric-chart-svg { width: 100%; height: 140px; }
.metric-chart-axis { stroke: var(--panel-border); stroke-width: 1; }
.metric-chart-range {
  display: flex;
  justify-content: space-between;
  font-size: 0.72rem;
  color: var(--muted);
  margin-top: 2px;
}

/* 국기 - 유니코드 이모지 대신 실제 SVG(사용자 보고, 2026-08-13: Windows 기본 이모지
   폰트가 국기를 이미지로 안 그리고 텍스트로 대체하는 문제) - app-assets.js::
   countryFlagHtml 참고. 텍스트 라인 안에 자연스럽게 얹히도록 크기/정렬만 잡는다. */
.flag-icon {
  width: 16px;
  height: 12px;
  vertical-align: middle;
  object-fit: cover;
  border-radius: 2px;
}

.collector-badge-ok, .collector-badge-stale, .collector-badge-info, .collector-badge-warn {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  white-space: nowrap;
}
.collector-badge-ok { background: rgba(52, 211, 153, 0.15); color: var(--accent-2); }
.collector-badge-stale { background: rgba(248, 113, 113, 0.15); color: var(--danger); }
.collector-badge-info { background: rgba(148, 163, 184, 0.15); color: var(--muted); }
.collector-badge-warn { background: rgba(251, 191, 36, 0.18); color: #b45309; }
/* 센서 로컬 진단(app-system.js::sensorStatusRowHtml) 펼침 표 - 바깥 표 안에 들어가므로 여백·글자를 줄인다 */
.sensor-status-detail { margin: 4px 0 8px; font-size: 0.8rem; }
.sensor-status-detail td { padding: 3px 8px; border-bottom: 1px solid var(--panel-border); }
.sensor-status-detail td:first-child { width: 56px; }

/* SPHERE AI 챗봇 - 우측에서 슬라이드로 열리는 전체 높이 레이어 (2026-08-24) */
#ai-chat-widget { position: fixed; right: 24px; bottom: 24px; z-index: 200; }
.ai-chat-fab {
  width: 52px; height: 52px; border-radius: 50%; border: none;
  background: var(--accent); color: #0f172a; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
}
.ai-chat-fab-icon { width: 26px; height: 26px; fill: none; stroke: currentColor; stroke-width: 2; }
.ai-chat-panel {
  position: fixed; top: 0; right: 0; bottom: 0; width: 380px; max-width: 90vw;
  background: var(--panel); border-left: 1px solid rgba(148, 163, 184, 0.2);
  box-shadow: -8px 0 24px rgba(0, 0, 0, 0.4);
  display: flex; flex-direction: column; overflow: hidden;
  transform: translateX(100%);
  transition: transform 0.25s ease;
  z-index: 201;
}
.ai-chat-panel.ai-chat-panel-open { transform: translateX(0); }
.ai-chat-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 16px; border-bottom: 1px solid rgba(148, 163, 184, 0.15);
  font-weight: 600; color: var(--text);
}
.ai-chat-messages { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 8px; }
.ai-chat-bubble { max-width: 88%; padding: 8px 12px; border-radius: 10px; font-size: 0.85rem; line-height: 1.4; white-space: pre-wrap; }
.ai-chat-bubble-user { align-self: flex-end; background: var(--accent); color: #0f172a; }
.ai-chat-bubble-assistant { align-self: flex-start; background: rgba(148, 163, 184, 0.15); color: var(--text); }
.ai-chat-thinking { opacity: 0.6; }
.ai-chat-evidence { align-self: flex-start; font-size: 0.7rem; color: var(--muted); }
.ai-chat-input-row { display: flex; gap: 6px; padding: 10px; border-top: 1px solid rgba(148, 163, 184, 0.15); }
.ai-chat-input-row input { flex: 1; }

/* 사용자 엑셀 업로드 결과(app-administrator.js::showUserImportResult) - 행별 오류 목록 */
#user-import-result { margin: 8px 0 12px; }
.user-import-errors { margin: 6px 0 0; padding-left: 18px; max-height: 240px; overflow-y: auto; }
.user-import-errors li { margin: 2px 0; }
.user-import-preview-table-wrap { max-height: 50vh; overflow: auto; }
#table-user-import-preview th, #table-user-import-preview td { white-space: nowrap; }

/* 자산 상세 Raw Events 표 - 요약(raw JSON 요약, 공백 없는 긴 토큰 가능) 칸이 폭을 밀어내
   표 전체가 옆으로 늘어나던 문제(사용자 요청, 2026-09-07) - col-outbound-dest 와 같은
   원칙: table-layout: fixed + 칼럼별 고정폭 + overflow-wrap: anywhere로 셀 안에서만
   줄바꿈되게 한다. */
#table-asset-raw-events { table-layout: fixed; }
#table-asset-raw-events td { overflow-wrap: anywhere; white-space: normal; }
#table-asset-raw-events .col-raw-ts { width: 13%; }
/* 요약 카드 값(기간 내 건수·통신 상대 IP)은 다른 화면의 2rem 숫자 카드보다 길어 한 단계 줄인다(2026-09-08 요청). */
#asset-raw-events-summary .card-value { font-size: 1.35rem; }
#table-asset-raw-events .col-raw-log { width: 8%; }
#table-asset-raw-events .col-raw-orig { width: 17%; }
#table-asset-raw-events .col-raw-resp { width: 20%; }
#table-asset-raw-events .col-raw-summary { width: 42%; }


/* ---- Threat > MITRE ATT&CK (app-attck.js) ---- */
.attck-matrix { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(150px, 1fr); gap: 8px; align-items: start; min-width: 2200px; }
.attck-tactic { display: flex; flex-direction: column; gap: 4px; }
.attck-tactic-head { font-size: 0.78rem; font-weight: 600; color: var(--muted); padding: 4px 6px; border-bottom: 2px solid var(--panel-border); white-space: nowrap; }
.attck-tactic-head small { display: block; font-weight: 400; }
.attck-cell { border: 1px solid var(--panel-border); border-radius: 6px; padding: 4px 6px; font-size: 0.74rem; line-height: 1.25; cursor: pointer; background: var(--panel); position: relative; }
.attck-cell:hover { border-color: var(--accent); }
.attck-cell .attck-id { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.7rem; color: var(--muted); display: flex; justify-content: space-between; gap: 4px; }
.attck-cell .attck-name { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.attck-cell .attck-count { font-weight: 700; }
.attck-cell .attck-sub-toggle { color: var(--accent); }
.attck-sub { margin-left: 10px; border-left: 2px solid var(--panel-border); padding-left: 4px; display: flex; flex-direction: column; gap: 3px; }
.attck-cell.attck-detected { background: rgba(31, 122, 77, 0.28); border-color: rgba(31, 122, 77, 0.6); }
.attck-cell.attck-behavior { background: rgba(90, 164, 105, 0.16); }
.attck-cell.attck-exposure { background: repeating-linear-gradient(135deg, rgba(217, 119, 6, 0.22) 0 6px, transparent 6px 12px); }
.attck-cell.attck-telemetry_required { background: rgba(148, 163, 184, 0.14); color: var(--muted); }
.attck-cell.attck-unsupported { opacity: 0.55; }
.attck-cell.attck-highlight { outline: 2px solid var(--accent); }
.attck-legend { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0; font-size: 0.78rem; }
.attck-cell-legend { border: 1px solid var(--panel-border); border-radius: 6px; padding: 2px 8px; }
.attck-cell-legend.attck-detected { background: rgba(31, 122, 77, 0.28); }
.attck-cell-legend.attck-behavior { background: rgba(90, 164, 105, 0.16); }
.attck-cell-legend.attck-exposure { background: repeating-linear-gradient(135deg, rgba(217, 119, 6, 0.22) 0 6px, transparent 6px 12px); }
.attck-cell-legend.attck-telemetry_required { background: rgba(148, 163, 184, 0.14); color: var(--muted); }
.attck-cell-legend.attck-unsupported { opacity: 0.55; }
#table-attck-rules { table-layout: fixed; }
#table-attck-rules th, #table-attck-rules td { white-space: normal; word-break: break-word; vertical-align: top; }
#table-attck-rules th:nth-child(1), #table-attck-rules td:nth-child(1) { width: 300px; }
#table-attck-rules th:nth-child(2), #table-attck-rules td:nth-child(2) { width: 140px; }
#table-attck-rules th:nth-child(3), #table-attck-rules td:nth-child(3) { width: 56px; }
#table-attck-rules th:nth-child(4), #table-attck-rules td:nth-child(4) { width: 180px; }
#table-attck-rules th:nth-child(5), #table-attck-rules td:nth-child(5) { width: 140px; }
#table-attck-rules th:nth-child(6), #table-attck-rules td:nth-child(6) { width: 48px; }
.attck-drawer { position: fixed; top: 0; right: 0; width: min(440px, 90vw); height: 100vh; background: var(--panel); border-left: 1px solid var(--panel-border); box-shadow: -8px 0 24px rgba(0,0,0,0.25); padding: 16px; overflow-y: auto; z-index: 60; font-size: 0.85rem; }
.attck-drawer-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; margin-bottom: 8px; }
.attck-drawer-head h3 { margin: 0; font-size: 1rem; }
.attck-drawer dl { display: grid; grid-template-columns: 92px 1fr; gap: 4px 8px; margin: 8px 0; }
.attck-drawer dt { color: var(--muted); }
.attck-drawer dd { margin: 0; }
.attck-badge { display: inline-block; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.68rem; border: 1px solid var(--panel-border); border-radius: 4px; padding: 0 4px; margin-left: 4px; color: var(--muted); text-decoration: none; }
.attck-badge:hover { border-color: var(--accent); color: var(--accent); }
/* Attack Story(P5) 전술 진행 표시 - app-exposure.js::incidentChainHtml */
.incident-chain { font-size: 0.78rem; white-space: nowrap; }
.incident-chain-step { color: var(--muted); }
.incident-chain-step-on { color: var(--accent); font-weight: 600; }
.incident-chain-arrow { color: var(--muted); margin: 0 2px; }
