: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;
}

.sidebar-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 20px 16px;
  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%; }

.title-main { font-size: 0.9rem; font-weight: 600; }
.title-sub { font-size: 0.75rem; color: var(--muted); }

.nav-menu {
  display: flex;
  flex-direction: column;
  padding: 12px 8px;
  gap: 2px;
  flex: 1;
}

.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;
}
.nav-group.expanded .nav-group-items { max-height: 400px; }
.nav-group.expanded .nav-group-chevron { transform: rotate(180deg); }

.nav-item-sub { padding-left: 28px; }

.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-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; }
.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; }

.btn-refresh {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--panel-border);
  border-radius: 6px;
  padding: 7px 14px;
  font-size: 0.85rem;
  cursor: pointer;
}
.btn-refresh:hover { border-color: var(--accent); }

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; }

/* 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; }

.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 { 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; }

.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-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; }
/* 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; }

/* 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 { flex: 45 1 0; height: 100%; overflow-y: auto; min-width: 320px; }
.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 등 값이 아주 길어질 수 있는 칸 - 칸 자체 높이를 제한하고 그 안에서만
   스크롤되게 해서, 이 값 하나 때문에 좌측 칼럼 전체 높이가 늘어나지 않게 한다. */
.asset-detail-value-scroll {
  display: block;
  max-height: 180px;
  overflow-y: auto;
  min-width: 0;
}

.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); }

/* 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-assignee { font-size: 0.78rem; color: var(--muted); white-space: nowrap; 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; }

/* 수동 재정의 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; }

/* 담당자/관리자 자동완성 - 사용자 테이블에서 이름/아이디/부서로 검색해 고르는 위젯
   (index.html:renderApprovalTab, 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: 55 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); }
.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; }

/* 이력 관리 탭 안의 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; }

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; }

/* 좁은 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 가
   되어 원치 않게 흐려지는 문제)에 쓴다. */
.cell-block-line { display: block; white-space: nowrap; }

.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; }

.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; }

.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; }

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

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

#topology-root .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-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; }

.settings-actions { display: flex; gap: 8px; margin-top: 16px; }
.btn-primary, .btn-secondary {
  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); }

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

.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); }

.row-actions { display: flex; gap: 10px; white-space: nowrap; }

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

.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; }

.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; }
.modal-box-wide { max-width: 940px; width: 95vw; }
/* 자산 상세 모달만 30% 더 넓게(940px -> 1222px) - 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: 1222px; 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;
}
.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;
}

.collector-badge-ok, .collector-badge-stale, .collector-badge-info {
  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); }
