/* neo_dashboard.css — NEO 대시보드. 스코프: .neo-root / body.neo-active / html.boot-neo */

/* 부팅 프리페인트: NEO 예정이면 레거시 첫 페인트 자체를 숨김 (boot-pro 패턴 미러 — pro_dashboard.css:21-23과 동일하게
   display:none + 배경 오버라이드 둘 다 필요. 배경은 head에서 data-neo-theme이 동기 설정되므로 var(--neo-bg)가 올바로 해석됨) */
html.boot-neo, html.boot-neo body { background: var(--neo-bg); }
html.boot-neo .page,
html.boot-neo #loading-overlay { display: none !important; }

/* NEO 활성 시 레거시 셸 완전 숨김 (pro-active 패턴 미러) */
body.neo-active .page,
body.neo-active #loading-overlay,
body.neo-active #scroll-top-btn { display: none !important; }

/* 레거시 다이얼로그(dialog.js)는 dlg-backdrop/dlg-modal 클래스에 dialog.css가 z-index 9000/9001을
   이미 부여 — .neo-root(1500)보다 위이므로 별도 보정 불필요 */

.neo-root {
  position: fixed; inset: 0; z-index: 1500;
  overflow-y: auto; overflow-x: hidden;
  background: var(--neo-bg); color: var(--neo-text);
  font-family: var(--neo-font);
}
.neo-root.hidden { display: none; }
.neo-boot-msg { padding: 40px; color: var(--neo-text-muted); }

/* ───── 레이아웃 셸 ───── */
.neo-shell { display: grid; grid-template-columns: 84px minmax(0, 1fr) 340px; min-height: 100vh; }
.neo-main { padding: 20px 24px 40px; min-width: 0; }

/* ───── 사이드바 ───── */
.neo-side {
  position: sticky; top: 0; height: 100vh;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 14px 0; background: #0D1526; border-right: 1px solid rgba(255,255,255,0.06);
}
html[data-neo-theme="light"] .neo-side { background: #0F172A; }
/* R13: 플레이스홀더 아이콘 → 실제 SAVER 화이트 로고 (레일은 양 테마 모두 다크 고정이라 화이트 정본) */
.neo-side-logo { width: 62px; margin-bottom: 10px; }
.neo-side-logo img { width: 100%; height: auto; display: block; }
.neo-side-nav, .neo-side-foot { display: flex; flex-direction: column; gap: 6px; align-items: center; }
.neo-side-foot { margin-top: auto; }
.neo-side-btn {
  position: relative; width: 44px; height: 44px; border: none; border-radius: var(--neo-radius-sm);
  background: transparent; color: #94A3B8; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background var(--neo-dur), color var(--neo-dur);
  text-decoration: none;   /* 설정 항목만 <a>라 앵커 기본 밑줄이 라벨에 노출되던 것 제거 */
}
.neo-side-btn svg { width: 21px; height: 21px; }
.neo-side-btn:hover { background: rgba(255,255,255,0.08); color: #E2E8F0; }
.neo-side-btn.is-active { background: rgba(56,189,248,0.16); color: #38BDF8; }
.neo-side-badge {
  position: absolute; top: 4px; right: 4px; min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 8px; background: var(--neo-danger); color: #fff;
  font-size: 10px; font-weight: 700; line-height: 16px; text-align: center;
}
.neo-side-btn { flex-direction: column; gap: 2px; }
.neo-side-lab { font-size: 10px; letter-spacing: -0.2px; line-height: 1; }

/* ───── 톱바 ───── */
.neo-topbar { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 18px; }
.neo-topbar-title h1 { font-size: 22px; font-weight: 700; letter-spacing: -0.02em; }
.neo-topbar-title p { font-size: 13px; color: var(--neo-text-muted); margin-top: 2px; }
.neo-search-box {
  display: flex; align-items: center; gap: 8px; width: min(320px, 40vw);
  background: var(--neo-surface); border: 1px solid var(--neo-border); border-radius: var(--neo-radius-sm);
  padding: 9px 12px; color: var(--neo-text-muted); box-shadow: var(--neo-shadow);
}
.neo-search-box svg { width: 16px; height: 16px; flex: none; }
.neo-search-box input { border: none; outline: none; background: transparent; width: 100%; font: inherit; color: var(--neo-text); }

/* ───── KPI ───── */
.neo-kpis { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; margin-bottom: 14px; }
.neo-kpi {
  display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
  background: var(--neo-surface); border: 1px solid var(--neo-border); border-radius: var(--neo-radius-sm);
  padding: 12px 14px; cursor: pointer; text-align: left; color: var(--neo-text);
  transition: border-color var(--neo-dur), box-shadow var(--neo-dur);
}
.neo-kpi:hover { border-color: var(--neo-accent); box-shadow: var(--neo-shadow-lift); }
.neo-kpi > svg { width: 18px; height: 18px; color: var(--neo-accent); flex: none; }
.neo-kpi.is-alarm > svg { color: var(--neo-danger); }
.neo-kpi.is-filter > svg { color: var(--neo-warn); }
.neo-kpi-num { font-size: 24px; font-weight: 800; font-variant-numeric: tabular-nums; }
.neo-kpi-num small { font-size: 12px; font-weight: 600; color: var(--neo-text-muted); margin-left: 2px; }
.neo-kpi-lab { font-size: 12px; color: var(--neo-text-muted); }
.neo-kpi-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%; min-height: 20px; }
.neo-kpi-delta { font-size: 11px; font-weight: 700; color: var(--neo-text-muted); font-variant-numeric: tabular-nums; }
.neo-kpi-delta.is-up { color: var(--neo-danger); }
.neo-kpi-delta.is-down { color: var(--neo-ok); }
/* 기본 = 증가 나쁨(이상/필터임박/정지). 가동률·가동중만 반전 — is-stopped 제외 필수(정지 증가=빨강) */
.neo-kpi:not(.is-alarm):not(.is-filter):not(.is-stopped) .neo-kpi-delta.is-up { color: var(--neo-ok); }
.neo-kpi:not(.is-alarm):not(.is-filter):not(.is-stopped) .neo-kpi-delta.is-down { color: var(--neo-danger); }
.neo-kpi-spark { height: 20px; opacity: 0.9; }
.neo-kpi-spark svg { display: block; }
.neo-ctrlbar { margin-bottom: 14px; }   /* 일괄 버튼 줄 ↔ 그룹 칩 줄 분리 (기존 0px — R5 수직 리듬) */
.neo-bulk { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-left: auto; }
/* R9 프리미엄 일괄 제어 — 기능별 세만틱 컬러(ON=그린 필, OFF=레드 틴트, LED=블루 필/틴트) + 아이콘 + 미세 글로우 */
.neo-bulk-btn {
  display: inline-flex; align-items: center; gap: 7px;
  border: 1px solid var(--neo-border); background: var(--neo-surface); color: var(--neo-text);
  border-radius: 10px; padding: 9px 16px; font-size: 13px; font-weight: 700; letter-spacing: -0.1px; cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.06), 0 1px 2px rgba(2,6,23,0.35);
  transition: background var(--neo-dur), border-color var(--neo-dur), color var(--neo-dur), box-shadow var(--neo-dur), filter var(--neo-dur);
}
.neo-bulk-btn svg { width: 15px; height: 15px; flex: none; }
.neo-bulk-btn:hover { border-color: var(--neo-accent); color: var(--neo-accent); }
.neo-bulk-btn.is-power-on {
  /* R11: 그라데이션+하이라이트 베벨의 아래 모서리가 여전히 줄처럼 읽힘 → 완전 플랫(단색·그림자 전무) */
  background: #10B981; border-color: transparent; color: #fff; box-shadow: none;
}
.neo-bulk-btn.is-power-on:hover { filter: brightness(1.07); color: #fff; border-color: transparent; }
.neo-bulk-btn.is-power-off {
  background: color-mix(in srgb, var(--neo-danger) 12%, transparent);
  border-color: color-mix(in srgb, var(--neo-danger) 45%, transparent); color: #FCA5A5;
}
.neo-bulk-btn.is-power-off:hover { border-color: var(--neo-danger); color: #FECACA; background: color-mix(in srgb, var(--neo-danger) 18%, transparent); }
.neo-bulk-btn.is-led-on {
  background: #0EA5E9; border-color: transparent; color: #fff; box-shadow: none;   /* R11: 플랫 단색 — 줄 요소 원천 제거 */
}
.neo-bulk-btn.is-led-on:hover { filter: brightness(1.07); color: #fff; border-color: transparent; }
.neo-bulk-btn.is-led-off {
  background: color-mix(in srgb, var(--neo-accent) 10%, transparent);
  border-color: color-mix(in srgb, var(--neo-accent) 40%, transparent); color: #7DD3FC;
}
.neo-bulk-btn.is-led-off:hover { border-color: var(--neo-accent); color: #BAE6FD; background: color-mix(in srgb, var(--neo-accent) 16%, transparent); }
.neo-bulk-btn.is-sub { background: transparent; box-shadow: none; color: var(--neo-text-muted); }
.neo-bulk-btn.is-sub:hover { color: var(--neo-accent); }
/* 라이트 모드 틴트 텍스트 대비(4.5:1) — 다크 파스텔은 밝은 배경에서 미달 */
html[data-neo-theme="light"] .neo-bulk-btn.is-power-off { color: #B91C1C; }
html[data-neo-theme="light"] .neo-bulk-btn.is-led-off { color: #0369A1; }

/* ───── 필터 툴바 ───── */
.neo-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 18px; }
.neo-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.neo-chip {
  border: 1px solid var(--neo-border); background: var(--neo-surface); color: var(--neo-text-muted);
  border-radius: 999px; padding: 7px 14px; font-size: 13px; cursor: pointer;
  transition: background var(--neo-dur), color var(--neo-dur), border-color var(--neo-dur);
}
.neo-chip:hover { border-color: var(--neo-accent); color: var(--neo-accent); }
.neo-chip.is-active { background: var(--neo-accent); border-color: var(--neo-accent); color: #fff; }
.neo-toolbar-right { display: flex; gap: 8px; }
.neo-toolbar-right select {
  border: 1px solid var(--neo-border); background: var(--neo-surface); color: var(--neo-text);
  border-radius: var(--neo-radius-sm); padding: 8px 10px; font: inherit; font-size: 13px; cursor: pointer;
}

/* ───── 그리드/그룹 ───── */
.neo-group { margin-bottom: 22px; }
.neo-group-title {
  font-size: 15px; font-weight: 700; margin-bottom: 10px;
  display: flex; align-items: baseline; gap: 8px;
}
.neo-group-count { font-size: 12px; font-weight: 500; color: var(--neo-text-muted); }
.neo-empty { padding: 60px 0; text-align: center; color: var(--neo-text-muted); }

/* ===== V2-P2 Task 3: 장치 제어 테이블 — 카드 그리드 대체 (neo_pages.css 설정 테이블 패턴 미러 + 가로 스크롤)
   .neo-pill / .neo-btn(+.neo-btn-power/.neo-btn-led의 is-on/is-off) 베이스는 그대로 유지한다 — neo_modals.js가
   .neo-pill·.neo-btn을 그대로 공유 소비하고, 행의 전원/LED 버튼도 구 카드와 동일한 is-on/is-off 클래스 계약을
   재사용하므로 삭제·재정의 대상이 아니다(카드 전용 .neo-card-*, .neo-bar*, .neo-card-overlay*만 교체 — 주의: CSS 주석은 중첩·이스케이프가 안 되므로 별표-슬래시 붙임 표기를 쓰지 말 것(과거 조기 종료로 .neo-tablewrap 규칙 드롭, T5 Issue1)). ===== */
.neo-tablewrap { overflow-x: auto; border: 1px solid var(--neo-border); border-radius: var(--neo-radius-sm); background: var(--neo-surface); }
.neo-devtable { width: 100%; min-width: 720px; border-collapse: collapse; font-size: 13px; }   /* R7: 예약/타이머 열 분리로 8열 — 최소폭 소폭 상향 */
.neo-devtable th { text-align: left; padding: 10px 12px; color: var(--neo-text-muted); font-weight: 600; font-size: 12px; border-bottom: 1px solid var(--neo-border); white-space: nowrap; }
.neo-devtable td { padding: 9px 12px; border-bottom: 1px solid var(--neo-border); vertical-align: middle; }
.neo-devtable tbody tr:last-child td { border-bottom: none; }
.neo-row { cursor: pointer; transition: background var(--neo-dur); }
.neo-row:hover, .neo-row:focus-visible { background: var(--neo-accent-soft); }
.neo-row.is-error td { background: color-mix(in srgb, var(--neo-danger) 7%, transparent); }
/* 알람 센터 항목 클릭 → 행 점프 하이라이트 (2초, JS setTimeout으로 제거 — 구 .neo-card.is-flash 미러, P2 Task 4) */
.neo-row.is-flash td { background: color-mix(in srgb, var(--neo-accent) 16%, transparent); }
/* WS 연결 끊김 시 실시간성이 상실된 행임을 시각적으로 알림 — 구 .neo-root.neo-stale .neo-card(스펙 §6) 미러 */
.neo-root.neo-stale .neo-row { opacity: 0.72; }
.neo-row-name b { font-weight: 700; }
.neo-row-err { margin-left: 8px; color: var(--neo-danger); font-size: 11px; font-weight: 700; }
.neo-row-o2 { font-variant-numeric: tabular-nums; font-weight: 700; font-size: 15px; }
.neo-row-o2 small { color: var(--neo-text-muted); font-weight: 500; }
/* R8: 예약/타이머 본문을 산소농도급 가독성으로 — muted 12px → 본문색 14px 세미볼드(사용자: "글자가 너무 안 보여") */
.neo-row-marker { color: var(--neo-text); font-size: 14px; font-weight: 600; white-space: nowrap; font-variant-numeric: tabular-nums; }
.neo-row-marker.is-short { color: var(--neo-danger); font-weight: 700; }
.neo-row-empty { color: var(--neo-text-muted); font-weight: 400; }   /* 빈 값 대시는 낮은 시각 우선순위 유지 */
/* 정지 상태의 농도 자리 대체 문구 — 숫자(15px/700)보다 확실히 낮은 위계로 둬야 옆 행의 실제 농도와
   같은 급으로 읽히지 않는다. 색도 muted: 상태 필이 이미 '대기'를 말하고 있어 여기선 보조 설명이다. */
.neo-row-o2alt { color: var(--neo-text-muted); font-weight: 600; font-size: 12.5px; letter-spacing: -0.01em; }
/* 오늘 활성 예약 시각 신호 — 구 .neo-card-resv-badge.is-today 미러(리뷰 Important 2, warn 색 의미 유지) */
.neo-row-resv.is-today { color: var(--neo-warn); font-weight: 700; }
.neo-row-gear { text-align: right; }
.neo-pill { font-size: 12.5px; font-weight: 700; border-radius: 999px; padding: 4px 11px; }   /* R8: 상태 필 확대(11px → 12.5px) */
.neo-pill.is-run { background: color-mix(in srgb, var(--neo-ok) 14%, transparent); color: var(--neo-ok); }
.neo-pill.is-idle { background: color-mix(in srgb, var(--neo-idle) 14%, transparent); color: var(--neo-idle); }
.neo-pill.is-err { background: color-mix(in srgb, var(--neo-danger) 14%, transparent); color: var(--neo-danger); }

/* 라이트 모드 필 텍스트 대비 4.5:1+ (기본 상태색은 소형 볼드 텍스트 기준 미달 — Task 11 실측 3.18~3.98) */
html[data-neo-theme="light"] .neo-pill.is-run { color: #065F46; }
html[data-neo-theme="light"] .neo-pill.is-idle { color: #334155; }
html[data-neo-theme="light"] .neo-pill.is-err { color: #991B1B; }
.neo-btn {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: 6px;
  border: 1px solid var(--neo-border); background: transparent; color: var(--neo-text-muted);
  border-radius: var(--neo-radius-sm); padding: 9px 0; font-size: 13px; font-weight: 700; cursor: pointer;
  transition: background var(--neo-dur), color var(--neo-dur), border-color var(--neo-dur);
}
.neo-btn svg { width: 15px; height: 15px; }
.neo-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.neo-btn-power.is-on { background: var(--neo-ok); border-color: var(--neo-ok); color: #fff; }
.neo-btn-power.is-off:not(:disabled):hover { border-color: var(--neo-ok); color: var(--neo-ok); }
.neo-btn-led.is-on { background: var(--neo-accent); border-color: var(--neo-accent); color: #fff; }
.neo-btn-led.is-off:not(:disabled):hover { border-color: var(--neo-accent); color: var(--neo-accent); }
/* 행 스코프 버튼 여백 — 공용 .neo-btn(카드/모달용 padding:9px 0, flex:1)은 무접촉, 테이블 셀 안에서만 축소 (리뷰 Minor 1) */
/* width 64px 고정 — 전원/LED/상세 버튼 폭 통일(텍스트 길이 따라 들쭉날쭉하던 것, R6 사용자 요청) */
.neo-devtable .neo-btn { padding: 6px 0; width: 64px; flex: none; }
/* R8: 제어/수치 열 고정폭 클러스터 — 8열 균등 분산으로 전원·LED가 200px 벌어지던 것 방지, 잔여 폭은 장치 열이 흡수 */
.neo-devtable .th-stat { width: 96px; }
.neo-devtable .th-ctl { width: 84px; }
.neo-devtable .th-o2 { width: 96px; }
.neo-devtable .th-resv { width: 150px; }
.neo-devtable .th-timer { width: 180px; }

/* ───── 인사이트 패널 ───── */
.neo-insight {
  position: sticky; top: 0; height: 100vh; overflow-y: auto;
  padding: 20px 18px; border-left: 1px solid var(--neo-border);
  display: flex; flex-direction: column; gap: 12px;
}
.neo-insight-card {
  background: var(--neo-surface); border: 1px solid var(--neo-border); border-radius: var(--neo-radius);
  padding: 14px; box-shadow: var(--neo-shadow);
}
.neo-insight-card h3 { font-size: 14px; font-weight: 700; margin-bottom: 8px; }
.neo-insight-ph { font-size: 12px; color: var(--neo-text-muted); }

/* ── 날씨/공기질 카드 (P2 Task 2) ── */
.neo-weather-loading { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--neo-text-muted); padding: 6px 0; }
.neo-weather-error {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 12px 0 6px; color: var(--neo-text-muted); font-size: 12px; text-align: center;
}
.neo-weather-error svg { width: 20px; height: 20px; color: var(--neo-warn); }
.neo-weather-retry {
  border: 1px solid var(--neo-border); background: transparent; color: var(--neo-text);
  border-radius: var(--neo-radius-sm); padding: 6px 14px; font-size: 12px; font-weight: 600; cursor: pointer;
  transition: border-color var(--neo-dur), color var(--neo-dur);
}
.neo-weather-retry:hover { border-color: var(--neo-accent); color: var(--neo-accent); }
.neo-weather-now { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
.neo-weather-icon { width: 40px; height: 40px; flex: none; color: var(--neo-accent); display: flex; }
.neo-weather-icon svg { width: 100%; height: 100%; stroke-width: 1.6; }
.neo-weather-now-txt { min-width: 0; }
.neo-weather-label { display: block; font-size: 14px; font-weight: 700; }
.neo-weather-th {
  display: flex; align-items: baseline; gap: 12px; margin-top: 2px;
  font-variant-numeric: tabular-nums;
}
.neo-weather-temp { font-size: 21px; font-weight: 800; }
.neo-weather-temp i, .neo-weather-hum i { font-style: normal; font-size: 11px; font-weight: 500; color: var(--neo-text-muted); margin-left: 1px; }
.neo-weather-hum { display: inline-flex; align-items: baseline; gap: 3px; font-size: 14px; font-weight: 700; color: var(--neo-text); }
.neo-weather-hum svg { width: 12px; height: 12px; color: var(--neo-accent); align-self: center; }
.neo-weather-dust { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }
.neo-weather-dust-row { display: flex; align-items: center; gap: 8px; font-size: 12px; }
.neo-dust-label { color: var(--neo-text-muted); flex: 1; }
.neo-weather-dust-row b { font-size: 14px; font-weight: 700; font-variant-numeric: tabular-nums; }
.neo-weather-dust-row b i { font-style: normal; font-size: 10px; font-weight: 500; color: var(--neo-text-muted); margin-left: 2px; }
.neo-dust-grade { font-size: 11px; font-weight: 700; border-radius: 999px; padding: 2px 8px; }
.neo-dust-grade.is-good     { background: color-mix(in srgb, var(--neo-ok) 14%, transparent); color: var(--neo-ok); }
.neo-dust-grade.is-moderate { background: color-mix(in srgb, var(--neo-warn) 16%, transparent); color: var(--neo-warn); }
.neo-dust-grade.is-bad      { background: color-mix(in srgb, #F97316 18%, transparent); color: #FB923C; }
.neo-dust-grade.is-verybad  { background: color-mix(in srgb, var(--neo-danger) 16%, transparent); color: var(--neo-danger); }
/* 라이트 모드 등급 텍스트 대비 4.5:1+ (필 대비 정책 미러 — 3434fb9) */
html[data-neo-theme="light"] .neo-dust-grade.is-good     { color: #065F46; }
html[data-neo-theme="light"] .neo-dust-grade.is-moderate { color: #92400E; }
html[data-neo-theme="light"] .neo-dust-grade.is-bad      { color: #9A3412; }
html[data-neo-theme="light"] .neo-dust-grade.is-verybad  { color: #991B1B; }
.neo-weather-meta { font-size: 11px; color: var(--neo-text-muted); line-height: 1.5; margin: 0; }

/* ── 농도 트렌드 차트 (P2 Task 3 — 자체 SVG, theme_topology.js .gc-* 패턴을 토큰 색으로 미러) ── */
.neo-chart-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
.neo-chart-select {
  border: 1px solid var(--neo-border); background: var(--neo-surface); color: var(--neo-text);
  border-radius: var(--neo-radius-sm); padding: 6px 8px; font: inherit; font-size: 12px; cursor: pointer;
  min-width: 0; flex: 1 1 auto; max-width: 62%;
}
.neo-chart-select:disabled { cursor: default; opacity: 0.6; }
.neo-chart-range { display: inline-flex; border: 1px solid var(--neo-border); border-radius: var(--neo-radius-sm); overflow: hidden; flex: none; }
.neo-chart-rbtn {
  border: none; background: var(--neo-surface); color: var(--neo-text-muted); font: inherit;
  font-weight: 700; font-size: 11.5px; padding: 6px 10px; cursor: pointer;
  transition: background var(--neo-dur), color var(--neo-dur);
}
.neo-chart-rbtn + .neo-chart-rbtn { border-left: 1px solid var(--neo-border); }
.neo-chart-rbtn.is-active { background: var(--neo-accent); color: #fff; }
.neo-chart-rbtn:hover:not(.is-active) { color: var(--neo-accent); }
.neo-chart-body { width: 100%; min-height: 120px; }
.neo-chart-svg { width: 100%; height: auto; display: block; }
.neo-chart-grid { stroke: var(--neo-border); stroke-width: 1; }
.neo-chart-ylab, .neo-chart-xlab { fill: var(--neo-text-muted); font-size: 10px; font-family: inherit; }
.neo-chart-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.neo-chart-loading, .neo-chart-empty {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 120px; font-size: 12px; color: var(--neo-text-muted); text-align: center;
}
.neo-chart-error {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
  min-height: 120px; padding: 12px 0 6px; color: var(--neo-text-muted); font-size: 12px; text-align: center;
}
.neo-chart-error svg { width: 20px; height: 20px; color: var(--neo-warn); }

/* ── 알람 센터 (P2 Task 4 — 이상/필터 교체/연결 실패 세 그룹 + 카드 점프) ── */
.neo-alarm-body { display: flex; flex-direction: column; gap: 14px; }
.neo-alarm-group-title {
  display: flex; align-items: center; gap: 6px;
  font-size: 12.5px; font-weight: 700; color: var(--neo-text-muted); margin-bottom: 6px;
}
.neo-alarm-group-count {
  background: var(--neo-border); color: var(--neo-text);
  border-radius: 999px; padding: 1px 7px; font-size: 11px; font-weight: 700;
}
.neo-alarm-list { display: flex; flex-direction: column; gap: 6px; list-style: none; margin: 0; padding: 0; }
.neo-alarm-item {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 8px;
  border: 1px solid var(--neo-border); border-left: 3px solid var(--neo-border); border-radius: var(--neo-radius-sm);
  padding: 8px 10px; cursor: pointer; font-size: 12px;
  transition: border-color var(--neo-dur), background var(--neo-dur);
}
.neo-alarm-item.is-alarm { border-left-color: var(--neo-danger); }
.neo-alarm-item.is-filter { border-left-color: var(--neo-warn); }
.neo-alarm-item.is-disconnected { border-left-color: var(--neo-idle); }
.neo-alarm-item:hover, .neo-alarm-item:focus-visible { border-color: var(--neo-accent); background: var(--neo-accent-soft); }
.neo-alarm-item:hover.is-alarm, .neo-alarm-item:focus-visible.is-alarm { border-left-color: var(--neo-danger); }
.neo-alarm-item:hover.is-filter, .neo-alarm-item:focus-visible.is-filter { border-left-color: var(--neo-warn); }
.neo-alarm-item:hover.is-disconnected, .neo-alarm-item:focus-visible.is-disconnected { border-left-color: var(--neo-idle); }
.neo-alarm-name { font-weight: 700; }
.neo-alarm-labels { color: var(--neo-text-muted); flex: 1 1 auto; min-width: 0; }
.neo-alarm-seen { font-size: 10.5px; color: var(--neo-text-muted); font-variant-numeric: tabular-nums; }
.neo-alarm-empty {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 18px 0 10px; color: var(--neo-ok); text-align: center;
}
.neo-alarm-empty svg { width: 26px; height: 26px; }
.neo-alarm-empty p { font-size: 12.5px; font-weight: 700; color: var(--neo-text); margin: 0; }
/* 라이트 모드 아이콘 대비 4.5:1+ (필/등급 대비 정책 미러 — 3434fb9) */
html[data-neo-theme="light"] .neo-alarm-empty { color: #065F46; }
/* 상태 확인 중 — 아직 '정상'이 아니므로 초록(안심 신호)을 쓰지 않는다. 중립 회색. */
.neo-alarm-empty.is-checking { color: var(--neo-text-muted); }
html[data-neo-theme="light"] .neo-alarm-empty.is-checking { color: #475569; }
.neo-alarm-empty-sub { font-size: 11px; color: var(--neo-text-muted); font-weight: 500; }
.neo-alarm-cs {
  font-size: 11px; color: var(--neo-text-muted); text-align: center; line-height: 1.6;
  margin: 12px 0 0; padding-top: 10px; border-top: 1px solid var(--neo-border);
}
.neo-alarm-cs a { color: var(--neo-accent); font-weight: 700; text-decoration: none; }
.neo-alarm-cs a:hover { text-decoration: underline; }
/* 알람 항목 '상세' 보조 버튼 — 카드 점프(li 자체)와 별개 액션(P2 이월, P3 Task1) */
.neo-alarm-detail-btn {
  margin-left: auto; flex: none;
  border: 1px solid var(--neo-border); background: var(--neo-surface); color: var(--neo-text);
  border-radius: 999px; padding: 3px 10px; font-size: 11px; font-weight: 700; cursor: pointer;
  transition: border-color var(--neo-dur), color var(--neo-dur);
}
.neo-alarm-detail-btn:hover, .neo-alarm-detail-btn:focus-visible { border-color: var(--neo-accent); color: var(--neo-accent); }

/* ───── 공통 위젯 ───── */
.neo-spinner {
  width: 16px; height: 16px; border-radius: 50%; flex: none;
  border: 2px solid var(--neo-border); border-top-color: var(--neo-accent);
  animation: neo-spin 0.8s linear infinite;
}
@keyframes neo-spin { to { transform: rotate(360deg); } }
.neo-loading {
  position: fixed; inset: 0; z-index: 1600;
  display: flex; align-items: center; justify-content: center; gap: 10px;
  background: var(--neo-bg); color: var(--neo-text-muted); font-size: 15px;
}
.neo-loading.hidden { display: none; }
.neo-conn-banner {
  position: fixed; top: 0; left: 68px; right: 340px; z-index: 1700;
  background: var(--neo-warn); color: #fff; text-align: center;
  padding: 6px; font-size: 13px; font-weight: 600;
}
.neo-conn-banner.hidden { display: none; }
.neo-toast {
  position: fixed; bottom: 24px; left: 50%; transform: translate(-50%, 20px); z-index: 3200;
  background: var(--neo-text); color: var(--neo-bg);
  border-radius: var(--neo-radius-sm); padding: 11px 18px; font-size: 13px; font-weight: 600;
  opacity: 0; pointer-events: none; transition: opacity var(--neo-dur), transform var(--neo-dur);
}
.neo-toast.is-show { opacity: 1; transform: translate(-50%, 0); }
.neo-toast.is-success { background: var(--neo-ok); color: #fff; }
.neo-toast.is-error { background: var(--neo-danger); color: #fff; }
.neo-scroll-top {
  position: fixed; right: 360px; bottom: 24px; z-index: 1800;
  width: 42px; height: 42px; border-radius: 50%; border: 1px solid var(--neo-border);
  background: var(--neo-surface); color: var(--neo-text); cursor: pointer; box-shadow: var(--neo-shadow-lift);
  display: flex; align-items: center; justify-content: center;
}
.neo-scroll-top svg { width: 18px; height: 18px; }
.neo-scroll-top.hidden { display: none; }

/* ───── 포커스/모션 ───── */
.neo-root :is(button, a, input, select):focus-visible { outline: 2px solid var(--neo-accent); outline-offset: 2px; }
/* P5+ 백로그: 위 규칙은 :is(button,a,input,select) 한정이라 role=button 을 쓰는 컨테이너형 요소가
   빠져 있었다 — 소형 카드(article)·예약 행(li)·표 행(tr)은 키보드 포커스를 받는데도 테두리색만
   미세하게 변해 어디에 있는지 사실상 안 보였다. 같은 링을 붙여 준다. */
.neo-root :is(.neo-smcard, .neo-sched-row, .neo-row):focus-visible {
  outline: 2px solid var(--neo-accent); outline-offset: -2px;
}
@media (prefers-reduced-motion: reduce) {
  .neo-root *, .neo-root *::before, .neo-root *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
  /* 로딩 인디케이터는 상태 전달 수단이므로 예외 — 느리게라도 회전 유지 */
  .neo-root .neo-spinner, .neo-loading .neo-spinner { animation-duration: 1.6s !important; }
  .neo-tank-badge i { animation: none !important; }
  .neo-ring-bar { transition: none !important; }   /* R14: 링 채움 트랜지션도 모션 최소화 대상 */
  .neo-smring-bar { transition: none !important; }   /* V2-P5: 소형 카드 미니 링도 동일 적용 */
  .neo-topo-odu-bar i { transition: none !important; }   /* V2-P6: 배치도 필터 게이지 */
}

/* ===== V2-P2 뷰 라우팅 — 상주 DOM 표시 토글 (리스너 보존) ===== */
.neo-shell[data-view="overview"] #neo-ctrlbar,
.neo-shell[data-view="overview"] #neo-toolbar,
.neo-shell[data-view="overview"] #neo-grid,
.neo-shell[data-view="overview"] .neo-view-stub { display: none; }
.neo-shell[data-view="devices"] #neo-kpis,
.neo-shell[data-view="devices"] .neo-insight,
.neo-shell[data-view="devices"] .neo-view-stub { display: none; }
.neo-shell[data-view="devices"],
.neo-shell[data-view="schedule"],
.neo-shell[data-view="alarms"] { grid-template-columns: 84px minmax(0, 1fr); } /* 인사이트 없이 전폭 — 3뷰 공용(T2 리뷰: schedule/alarms도 .neo-insight를 숨기므로 동일 보정 필요) */
.neo-shell[data-view="schedule"] #neo-kpis, .neo-shell[data-view="schedule"] #neo-ctrlbar,
.neo-shell[data-view="schedule"] #neo-toolbar, .neo-shell[data-view="schedule"] #neo-grid,
.neo-shell[data-view="schedule"] .neo-insight, .neo-shell[data-view="schedule"] #neo-view-alarms { display: none; }
.neo-shell[data-view="alarms"] #neo-kpis, .neo-shell[data-view="alarms"] #neo-ctrlbar,
.neo-shell[data-view="alarms"] #neo-toolbar, .neo-shell[data-view="alarms"] #neo-grid,
.neo-shell[data-view="alarms"] .neo-insight, .neo-shell[data-view="alarms"] #neo-view-schedule { display: none; }
.neo-shell[data-view="devices"] #neo-view-overview,
.neo-shell[data-view="schedule"] #neo-view-overview,
.neo-shell[data-view="alarms"] #neo-view-overview { display: none; }
.neo-view-stub { padding: 8px 2px; }
.neo-stub-card { background: var(--neo-surface); border: 1px solid var(--neo-border); border-radius: var(--neo-radius); padding: 22px; max-width: 560px; }
.neo-stub-card h2 { margin: 0 0 8px; font-size: 18px; }
.neo-stub-card p { margin: 0 0 14px; color: var(--neo-text-muted); font-size: 13px; line-height: 1.6; }
.neo-stub-actions { display: flex; gap: 8px; }

/* ───── 반응형 — 스펙 §4.1 기준표 ───── */
@media (max-width: 1279px) {
  .neo-shell { grid-template-columns: 84px minmax(0, 1fr) 280px; }
  .neo-conn-banner { right: 280px; }
  .neo-scroll-top { right: 300px; }
  .neo-kpis { grid-template-columns: repeat(3, minmax(0, 1fr)); }   /* P3 Task 4: KPI 5종 → 3열 */
}
@media (max-width: 1023px) {
  .neo-shell { grid-template-columns: 84px minmax(0, 1fr); }
  .neo-insight { position: static; height: auto; border-left: none; border-top: 1px solid var(--neo-border); grid-column: 2; }
  .neo-conn-banner { right: 0; }
  .neo-scroll-top { right: 20px; }
}
@media (max-width: 767px) {
  .neo-main { padding: 14px; }
  .neo-topbar { flex-direction: column; align-items: stretch; gap: 10px; }
  .neo-search-box { width: 100%; }
  .neo-bulk { margin-left: 0; width: 100%; }
  /* V2-P2 Task 3: 카드 그리드(.neo-cards, 열 수 반응형 캡) → 장치 제어 테이블로 대체됨 —
     좁은 화면에서는 .neo-tablewrap의 overflow-x:auto 가로 스크롤로 대응(Step7 스모크: 480px 기준) */
  .neo-donut-wrap { flex-direction: column; }
}
@media (max-width: 480px) {
  /* 사이드바 → 하단 탭바 */
  .neo-shell { grid-template-columns: minmax(0, 1fr); }
  .neo-insight { grid-column: 1; }
  .neo-side {
    position: fixed; top: auto; bottom: 0; left: 0; right: 0; height: 64px; z-index: 1900;
    flex-direction: row; justify-content: space-around; padding: 6px 10px;
    border-right: none; border-top: 1px solid rgba(255,255,255,0.08);
  }
  .neo-side-logo { display: none; }
  .neo-side-nav, .neo-side-foot { flex-direction: row; margin: 0; }
  .neo-side-foot { display: none; }   /* V2-P2: 탭바는 내비 5개만 — 테마/로그아웃은 설정 페이지 경유 */
  .neo-side-lab { font-size: 9px; }
  .neo-shell[data-view="devices"],
  .neo-shell[data-view="schedule"],
  .neo-shell[data-view="alarms"] { grid-template-columns: minmax(0, 1fr); }
  /* 고정 탭바(64px)에 마지막 스택 블록이 가려지지 않도록 — 트레일링 블록은 .neo-insight */
  .neo-main { padding-bottom: 90px; }
  .neo-insight { padding-bottom: 90px; }
  .neo-conn-banner { left: 0; }
  .neo-scroll-top { bottom: 80px; }
}
@media (max-width: 599px) {
  .neo-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }   /* P3 Task 4: KPI 5종 → 2열 */
}

/* ───────── 모달 인프라 (P3 Task 1 — js/neo_modals.js. .neo-root 형제(document.body 직속), z-index 2500 —
   .neo-root(1500)/.neo-loading(1600)/.neo-conn-banner(1700)/.neo-scroll-top(1800)보다 위, .neo-toast(3200)/
   dialog.js 레거시 다이얼로그(9000대)보다 아래. renderAll 재생성 대상 밖이라 WS 재렌더에도 생존한다. ───────── */
.neo-modal-layer { position: fixed; inset: 0; z-index: 2500; font-family: var(--neo-font); }
.neo-modal-layer.hidden { display: none; }
.neo-modal-backdrop { position: absolute; inset: 0; background: rgba(6, 12, 24, 0.56); }
.neo-modal-panel {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(520px, calc(100vw - 32px)); max-height: min(680px, calc(100vh - 48px));
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--neo-surface); color: var(--neo-text);
  border: 1px solid var(--neo-border); border-radius: var(--neo-radius);
  box-shadow: var(--neo-shadow-lift);
}
.neo-modal-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; flex: none;
  padding: 16px 18px; border-bottom: 1px solid var(--neo-border);
}
.neo-modal-title { font-size: 16px; font-weight: 700; margin: 0; }
.neo-modal-close {
  border: none; background: transparent; color: var(--neo-text-muted); cursor: pointer; flex: none;
  width: 32px; height: 32px; border-radius: 8px; display: flex; align-items: center; justify-content: center;
  transition: background var(--neo-dur), color var(--neo-dur);
}
.neo-modal-close svg { width: 18px; height: 18px; }
.neo-modal-close:hover { background: var(--neo-accent-soft); color: var(--neo-accent); }
.neo-modal-body { padding: 18px; overflow-y: auto; flex: 1 1 auto; }
.neo-modal-placeholder {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
  padding: 40px 10px; color: var(--neo-text-muted); font-size: 13px; text-align: center;
}
.neo-modal-panel:focus-visible,
.neo-modal-panel :is(button, a, input, select):focus-visible { outline: 2px solid var(--neo-accent); outline-offset: 2px; }

/* 공용 위젯(요일 선택기/시간/select 입력) — Task 3~5가 실제 모달 본문에서 소비 */
.neo-modal-weekday-picker { display: flex; gap: 6px; flex-wrap: wrap; }
.neo-wd-btn {
  width: 38px; height: 38px; border-radius: 50%; border: 1px solid var(--neo-border);
  background: var(--neo-surface); color: var(--neo-text-muted); font-size: 13px; font-weight: 700; cursor: pointer;
  transition: background var(--neo-dur), color var(--neo-dur), border-color var(--neo-dur);
}
.neo-wd-btn.is-selected { background: var(--neo-accent); border-color: var(--neo-accent); color: #fff; }
.neo-modal-field-label { display: block; font-size: 12px; font-weight: 600; color: var(--neo-text-muted); margin-bottom: 4px; }
.neo-modal-time-input, .neo-modal-select {
  border: 1px solid var(--neo-border); background: var(--neo-surface); color: var(--neo-text);
  border-radius: var(--neo-radius-sm); padding: 8px 10px; font: inherit; font-size: 13px;
}

/* ───── 컨트롤러 상세 모달 (P3 Task 2 — 실외기 캐러셀/필터 잔여/가동시간/잠금, p3-rules-reserve.md §2) ───── */
.neo-detail-top { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; min-height: 22px; }
.neo-detail-alarm-badge {
  font-size: 11px; font-weight: 700; color: var(--neo-danger);
  background: color-mix(in srgb, var(--neo-danger) 14%, transparent);
  border-radius: 999px; padding: 3px 9px;
}
.neo-detail-loading { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 40px 0; color: var(--neo-text-muted); font-size: 13px; }
.neo-detail-loading[hidden] { display: none; }   /* author display:flex 위 규칙이 [hidden] UA기본을 덮어써서 명시 필요 */
.neo-detail-section { margin-bottom: 18px; }
.neo-detail-section:last-of-type { margin-bottom: 0; }
.neo-detail-section-title { font-size: 13px; font-weight: 700; color: var(--neo-text-muted); margin: 0 0 10px; }
.neo-detail-section-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 4px; }
.neo-detail-section-head .neo-detail-section-title { margin-bottom: 0; }
.neo-detail-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 6px 0; font-size: 13px; }
.neo-detail-label { color: var(--neo-text-muted); }
.neo-detail-value { font-weight: 700; font-variant-numeric: tabular-nums; }
.neo-detail-weekdays { display: flex; gap: 5px; flex-wrap: wrap; margin: 2px 0 6px; }
.neo-detail-wd-pill {
  width: 26px; height: 26px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; background: var(--neo-accent-soft); color: var(--neo-accent);
}
.neo-detail-wd-pill.is-inactive { background: transparent; color: var(--neo-text-muted); opacity: 0.5; }
.neo-detail-odu-nav { display: flex; align-items: center; gap: 6px; }
.neo-detail-odu-nav[hidden] { display: none; }
.neo-detail-odu-btn {
  width: 26px; height: 26px; flex: none; border-radius: 8px; border: 1px solid var(--neo-border); background: var(--neo-surface);
  color: var(--neo-text-muted); cursor: pointer; display: flex; align-items: center; justify-content: center;
  transition: border-color var(--neo-dur), color var(--neo-dur);
}
.neo-detail-odu-btn svg { width: 14px; height: 14px; }
.neo-detail-odu-btn:hover:not(:disabled) { border-color: var(--neo-accent); color: var(--neo-accent); }
.neo-detail-odu-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.neo-detail-odu-tag { font-size: 12px; font-weight: 700; min-width: 60px; text-align: center; }
.neo-detail-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.neo-detail-table tr { border-bottom: 1px solid var(--neo-border); }
.neo-detail-table tr:last-child { border-bottom: none; }
.neo-detail-table th, .neo-detail-table td { padding: 8px 0; }
.neo-detail-table th { text-align: left; color: var(--neo-text-muted); font-weight: 500; }
.neo-detail-table td { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; }
.neo-detail-actions { margin-top: 16px; }
.neo-detail-settings-btn { width: 100%; }
.neo-detail-cs {
  font-size: 11px; color: var(--neo-text-muted); text-align: center; line-height: 1.6;
  margin: 16px 0 0; padding-top: 12px; border-top: 1px solid var(--neo-border);
}
.neo-detail-cs a { color: var(--neo-accent); font-weight: 700; text-decoration: none; }
.neo-detail-cs a:hover { text-decoration: underline; }

/* ───── 개별 일정제어 모달 (P3 Task 3 — 예약 토글/타이머 PUT-only/그룹에서 삭제, p3-rules-reserve.md §3,4,6) ───── */
.neo-sched { display: flex; flex-direction: column; gap: 18px; }
.neo-modal-field { display: flex; flex-direction: column; gap: 8px; }
.neo-modal-weekday-picker.is-locked .neo-wd-btn { cursor: default; opacity: 0.55; }
.neo-sched-placeholder { font-size: 13px; color: var(--neo-text-muted); letter-spacing: 0.04em; }
.neo-sched-group-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.neo-sched-group-label { font-size: 13px; font-weight: 700; color: var(--neo-accent); }
.neo-sched-group-remove-btn { flex: none; padding: 7px 12px; color: var(--neo-danger); border-color: var(--neo-danger); }
.neo-sched-group-remove-btn:hover:not(:disabled) { background: color-mix(in srgb, var(--neo-danger) 12%, transparent); }
.neo-sched-resv-row { display: flex; align-items: center; gap: 10px; }
.neo-sched-resv-row .neo-modal-time-input { flex: 1 1 auto; }
.neo-sched-toggle {
  flex: none; min-width: 58px; padding: 8px 14px; border-radius: 999px; border: 1px solid var(--neo-border);
  background: var(--neo-surface); color: var(--neo-text-muted); font-size: 12px; font-weight: 700; cursor: pointer;
  transition: background var(--neo-dur), color var(--neo-dur), border-color var(--neo-dur);
}
.neo-sched-toggle.is-on { background: var(--neo-ok); border-color: var(--neo-ok); color: #fff; }
.neo-sched-actions { margin-top: 2px; }
.neo-btn-primary { background: var(--neo-accent); border-color: var(--neo-accent); color: #fff; }
.neo-btn-primary:hover:not(:disabled) { filter: brightness(1.08); }
.neo-sched-confirm-btn { width: 100%; }
.neo-modal-applying-overlay {
  position: absolute; inset: 0; z-index: 5; border-radius: var(--neo-radius);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; text-align: center;
  background: color-mix(in srgb, var(--neo-surface) 88%, transparent); backdrop-filter: blur(2px);
}
.neo-modal-applying-title { font-size: 14px; font-weight: 700; }
.neo-modal-applying-sub { font-size: 12px; color: var(--neo-text-muted); }

/* ───── 그룹 예약 리스트 모달 (P3 Task 4 — p3-rules-group-global.md §1) ───── */
.neo-grouplist { display: flex; flex-direction: column; gap: 14px; }
.neo-grouplist-toolbar { display: flex; justify-content: flex-end; }
.neo-grouplist-add-btn { flex: none; width: auto; padding: 8px 16px; }
.neo-grouplist-body { display: flex; flex-direction: column; gap: 8px; }
.neo-modal-loading-row { display: flex; align-items: center; gap: 8px; padding: 24px 0; color: var(--neo-text-muted); font-size: 13px; justify-content: center; }
.neo-modal-placeholder-text { padding: 32px 0; text-align: center; color: var(--neo-text-muted); font-size: 13px; }
.neo-grp-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  border: 1px solid var(--neo-border); border-radius: var(--neo-radius-sm); padding: 10px 12px;
}
.neo-grp-row-name { font-size: 13px; font-weight: 700; flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.neo-grp-row-actions { display: flex; align-items: center; gap: 6px; flex: none; }
.neo-grp-edit-btn, .neo-grp-del-btn {
  border: 1px solid var(--neo-border); background: var(--neo-surface); color: var(--neo-text);
  border-radius: var(--neo-radius-sm); padding: 6px 10px; font-size: 12px; font-weight: 600; cursor: pointer;
  transition: border-color var(--neo-dur), color var(--neo-dur);
}
.neo-grp-edit-btn:hover { border-color: var(--neo-accent); color: var(--neo-accent); }
.neo-grp-del-btn:hover { border-color: var(--neo-danger); color: var(--neo-danger); }
.neo-grp-toggle-group { display: inline-flex; border: 1px solid var(--neo-border); border-radius: 999px; overflow: hidden; }
.neo-grp-toggle-btn {
  border: none; background: var(--neo-surface); color: var(--neo-text-muted); font-size: 11px; font-weight: 700;
  padding: 6px 10px; cursor: pointer; transition: background var(--neo-dur), color var(--neo-dur);
}
.neo-grp-toggle-btn + .neo-grp-toggle-btn { border-left: 1px solid var(--neo-border); }
.neo-grp-toggle-btn[data-on="1"].active { background: var(--neo-ok); color: #fff; }
.neo-grp-toggle-btn[data-on="0"].active { background: var(--neo-idle); color: #fff; }
.neo-grp-toggle-btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* ───── 그룹 예약 설정 Step1/Step2 (P3 Task 4 — p3-rules-group-global.md §2,3) ───── */
.neo-grpform { display: flex; flex-direction: column; gap: 18px; }
.neo-grpform-name-row { display: flex; align-items: center; gap: 6px; }
.neo-grpform-name-row input { flex: 1 1 auto; }
.neo-grpform-name-clear {
  flex: none; width: 28px; height: 28px; border-radius: 8px; border: none; background: transparent;
  color: var(--neo-text-muted); cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.neo-grpform-name-clear svg { width: 14px; height: 14px; }
.neo-grpform-name-clear:hover { background: var(--neo-accent-soft); color: var(--neo-accent); }
.neo-grpform-name-count { display: block; font-size: 11px; color: var(--neo-text-muted); text-align: right; margin-top: 2px; }
.neo-grpform-target-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.neo-grpform-target-text { font-size: 13px; font-weight: 600; flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.neo-grpform-target-btn { flex: none; width: auto; padding: 7px 12px; font-size: 12px; }
.neo-grpform-notice { font-size: 11px; color: var(--neo-text-muted); margin: 0; }
.neo-grpform-submit-btn { width: 100%; }   /* .neo-sched-actions는 flex 컨테이너가 아니라 .neo-btn의 flex:1이 무효 — 명시적 width 필요 */
.neo-grpform-step2-toolbar { display: flex; justify-content: flex-end; margin-bottom: 10px; }
.neo-grpform-selectall { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; cursor: pointer; }
.neo-grpform-device-list { display: flex; flex-direction: column; gap: 12px; max-height: 340px; overflow-y: auto; }
.neo-grpform-section { border: 1px solid var(--neo-border); border-radius: var(--neo-radius-sm); overflow: hidden; }
.neo-grpform-section-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 10px; background: var(--neo-bg); }
.neo-grpform-section-checkbox { display: inline-flex; align-items: center; gap: 8px; font-size: 12.5px; font-weight: 700; cursor: pointer; }
.neo-grpform-section-toggle { border: none; background: transparent; color: var(--neo-text-muted); cursor: pointer; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; }
.neo-grpform-section-toggle svg { width: 14px; height: 14px; transition: transform var(--neo-dur); }
.neo-grpform-section-toggle.is-collapsed svg { transform: rotate(-90deg); }
.neo-grpform-section-body { display: flex; flex-direction: column; }
.neo-grpform-section-body[hidden] { display: none; }   /* author display:flex 위 규칙이 [hidden] UA기본을 덮어써서 명시 필요 */
.neo-grpform-device-row { display: flex; align-items: center; gap: 8px; padding: 7px 10px; font-size: 12.5px; border-top: 1px solid var(--neo-border); cursor: pointer; }
.neo-grpform-device-row.is-disabled { color: var(--neo-text-muted); cursor: not-allowed; }
.neo-grpform-device-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.neo-grpform-device-badge {
  flex: none; font-size: 10.5px; font-weight: 700; color: var(--neo-warn);
  background: color-mix(in srgb, var(--neo-warn) 16%, transparent); border-radius: 999px; padding: 2px 8px;
}
.neo-grpform-step2-actions { display: flex; gap: 10px; }
.neo-grpform-step2-actions .neo-btn { flex: 1; }

/* ───── 일괄 타이머 모달 (P3 Task 5) — 아코디언/필드는 neo-grpform-* 재사용, 래퍼만 추가 ───── */
.neo-bulktimer { display: flex; flex-direction: column; gap: 18px; }
.neo-bulktimer-selectall-row { display: flex; justify-content: flex-end; margin-bottom: 6px; }

/* 모바일 ≤480 — 모달 풀스크린(스펙 §4.1 반응형 기준표) */
@media (max-width: 480px) {
  .neo-modal-panel {
    top: 0; left: 0; right: 0; bottom: 0; transform: none;
    width: 100%; max-width: 100%; height: 100%; max-height: 100%; border-radius: 0; border: none;
  }
}

/* ===== V2-P3 상황판 본문 ===== */
.neo-overview { display: flex; flex-direction: column; gap: 14px; }
.neo-ov-hero {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 20px; align-items: center;
  background: var(--neo-surface); border: 1px solid var(--neo-border); border-radius: var(--neo-radius);
  padding: 18px 22px; box-shadow: var(--neo-shadow);
}
.neo-ov-tankbox { position: relative; width: 194px; height: 194px; margin: 0 auto; }
.neo-tank { width: 194px; height: 194px; overflow: visible; }
/* R14→R15: 프리미엄 링 게이지 — 그라데이션 아크 + 무광 트랙 + 안쪽 소프트 디스크(워치 스타일, 틱 없음) */
.neo-ring-inner { fill: rgba(56,189,248,0.05); stroke: rgba(255,255,255,0.06); stroke-width: 1; }
html[data-neo-theme="light"] .neo-ring-inner { fill: rgba(2,132,199,0.05); stroke: rgba(15,23,42,0.07); }
.neo-ring-track { fill: none; stroke: color-mix(in srgb, var(--neo-border) 85%, transparent); stroke-width: 11; }
.neo-ring-bar {
  fill: none; stroke-width: 11; stroke-linecap: round;
  stroke-dasharray: 100; transform: rotate(-90deg); transform-origin: 100px 100px;
  transition: stroke-dashoffset 900ms cubic-bezier(0.4, 0, 0.2, 1);
}
.neo-tank-center {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; pointer-events: none;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
}
.neo-tank-big { font-size: 38px; font-weight: 800; line-height: 1; letter-spacing: -1.2px; color: var(--neo-text); font-variant-numeric: tabular-nums; }
.neo-tank-lbl { font-size: 11px; color: var(--neo-text-muted); letter-spacing: 0.02em; }
.neo-tank-sub { font-size: 11.5px; color: var(--neo-text-muted); font-variant-numeric: tabular-nums; }
.neo-tank-badge {
  display: inline-flex; align-items: center; gap: 5px; margin-top: 4px; font-size: 10px; font-weight: 800;
  color: var(--neo-accent); background: var(--neo-accent-soft); padding: 3px 9px; border-radius: 12px;
  opacity: 0; transition: opacity 0.4s;
}
.neo-tank-badge.is-on { opacity: 1; }
.neo-tank-badge i { width: 6px; height: 6px; border-radius: 50%; background: var(--neo-accent); box-shadow: 0 0 7px var(--neo-accent); animation: neoBlink 1.4s ease-in-out infinite; }
@keyframes neoBlink { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
.neo-ov-stat { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.neo-ov-o2bar-top { display: flex; justify-content: space-between; align-items: baseline; font-size: 12px; color: var(--neo-text-muted); margin-bottom: 6px; }
.neo-ov-o2bar-top b { font-size: 16px; font-weight: 800; color: var(--neo-accent); }
.neo-ov-o2bar-track { height: 8px; border-radius: 5px; background: var(--neo-bg); overflow: hidden; }
.neo-ov-o2bar-fill { height: 100%; border-radius: 5px; background: linear-gradient(90deg, var(--neo-accent), #075985); transition: width 0.6s; }
.neo-ov-rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }
.neo-ov-rows li {
  display: flex; align-items: center; gap: 8px; padding: 8px 11px; font-size: 12px; color: var(--neo-text-muted);
  background: var(--neo-bg); border: 1px solid var(--neo-border); border-radius: 8px;
}
.neo-ov-rows li b { margin-left: auto; font-size: 15px; font-weight: 800; color: var(--neo-text); font-variant-numeric: tabular-nums; }
.neo-ov-dot { width: 9px; height: 9px; border-radius: 2px; flex: none; }
.neo-ov-dot.is-run { background: var(--neo-ok); }
.neo-ov-dot.is-stop { background: var(--neo-idle); }
.neo-ov-dot.is-alarm { background: var(--neo-danger); }
.neo-ov-dot.is-off { background: var(--neo-warn); }
/* 첫 shadow 대기 — 장애가 아니므로 경고색을 쓰지 않는다(중립 muted) */
.neo-ov-dot.is-check { background: var(--neo-text-muted); }
/* P15①: 사업장 가동시간 — 상태 카운트 아래 보조 라인. 링게이지·농도 같은 1급 숫자보다 낮은 위계
   (12px muted 라벨 + 본문색 값). 데이터 미도착이면 .hidden — 자리 예약 없이 평상시 레이아웃 유지. */
.neo-ov-usage {
  display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap;
  margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--neo-border);
  font-size: 12px; color: var(--neo-text-muted);
}
.neo-ov-usage b { color: var(--neo-text); font-weight: 800; font-variant-numeric: tabular-nums; font-size: 13px; }
.neo-ov-usage .neo-ov-usage-k.is-total { margin-left: auto; }
.neo-ov-usage small { font-size: 10px; font-weight: 500; }

/* ── 히어로 스트립(오늘의 예약 / 필터 교체) ──
   새 카드를 늘리면 이미 조밀한 상황판이 더 밀린다. 카드 한 줄 높이의 1/3짜리 띠로 얹고,
   알릴 게 없으면 [hidden]으로 사라져 평상시 레이아웃을 그대로 둔다(렌더에서 제어). */
.neo-ov-strip { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 14px; }
.neo-ov-strip[hidden], .neo-strip-item[hidden] { display: none; }
.neo-strip-item {
  flex: 1 1 240px; min-width: 0;
  display: flex; align-items: center; gap: 10px;
  padding: 10px 14px; text-align: left;
  font-family: inherit; font-size: 13px; color: var(--neo-text);
  background: var(--neo-surface); border: 1px solid var(--neo-border);
  border-radius: var(--neo-radius); box-shadow: var(--neo-shadow);
  cursor: pointer;
  transition: border-color var(--neo-dur), background-color var(--neo-dur);
}
.neo-strip-item:hover { border-color: var(--neo-accent); background: var(--neo-accent-soft); }
.neo-strip-k {
  flex: none; font-size: 11.5px; font-weight: 700; color: var(--neo-text-muted);
  letter-spacing: .01em; white-space: nowrap;
  padding-right: 10px; border-right: 1px solid var(--neo-border);
}
/* 값이 길어지면(예약 3건) 줄바꿈 대신 잘라낸다 — 띠 높이가 들쭉날쭉하면 아래 차트가 밀린다 */
.neo-strip-v {
  min-width: 0; flex: 1 1 auto;
  font-size: 13px; font-weight: 600; color: var(--neo-text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-variant-numeric: tabular-nums;
}
.neo-strip-v b { color: var(--neo-text); font-weight: 800; }
.neo-strip-v b.is-due { color: var(--neo-danger); }
.neo-strip-v b.is-soon { color: var(--neo-warn); }
/* 라이트 모드의 --neo-warn(#D97706)은 흰 표면 위 13px 텍스트로 쓰면 3.19:1 — 이 파일의 4.5:1 정책 위반.
   .neo-dust-grade.is-moderate 와 같은 선례값(amber-800)으로 낮춘다. danger(#DC2626)는 4.83:1로 통과. */
html[data-neo-theme="light"] .neo-strip-v b.is-soon { color: #92400E; }
.neo-strip-more { color: var(--neo-text-muted); font-weight: 500; }
@media (max-width: 560px) {
  /* 좁은 화면에선 라벨과 값이 한 줄에 안 들어간다 — 세로로 쌓고 구분선을 없앤다 */
  .neo-strip-item { flex-direction: column; align-items: flex-start; gap: 2px; }
  .neo-strip-k { border-right: 0; padding-right: 0; }
}

.neo-ov-charts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.neo-ov-card { background: var(--neo-surface); border: 1px solid var(--neo-border); border-radius: var(--neo-radius); padding: 14px 16px; box-shadow: var(--neo-shadow); min-width: 0; }
.neo-ov-card h3 { margin: 0 0 10px; font-size: 13px; font-weight: 700; }
.neo-ov-card h3 small { font-weight: 500; color: var(--neo-text-muted); margin-left: 6px; font-size: 11px; }
/* 카드 높이는 그리드 행이 옆 차트에 맞춰 늘리므로(205~340px) 본문이 그 높이를 흡수해야 한다.
   예전엔 짧은 콘텐츠가 위에 몰리고 아래가 통째로 비었다(사용자 지적). */
.neo-ov-card { display: flex; flex-direction: column; }
.neo-ov-chartbody { min-height: 150px; flex: 1; display: flex; flex-direction: column; }
.neo-ov-chartbody > .neo-chart-empty, .neo-ov-chartbody > .neo-chart-error { flex: 1; }

/* ───── V2-P8 그룹별 가동 현황 ───── */
.neo-gr-sum { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin-bottom: 12px; }
.neo-gr-sumi {
  background: var(--neo-bg); border: 1px solid var(--neo-border); border-radius: var(--neo-radius-sm);
  padding: 8px 10px; display: flex; flex-direction: column; gap: 2px; min-width: 0;
}
.neo-gr-sumlab { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--neo-text-muted); white-space: nowrap; }
.neo-gr-sumlab i { width: 8px; height: 8px; border-radius: 2px; flex: none; background: var(--neo-idle); }
.neo-gr-sumi.is-full .neo-gr-sumlab i { background: var(--neo-ok); }
.neo-gr-sumi.is-part .neo-gr-sumlab i { background: var(--neo-accent); }
/* 숫자는 항상 본문색 — 라이트에서 accent(#0EA5E9)/warn을 큰 숫자에 쓰면 배경 대비 2.6~3.0:1로
   이 파일이 이미 세운 4.5:1 정책(.neo-dust-grade 라이트 오버라이드)을 못 넘긴다. 상태는 위 점이 담당. */
.neo-gr-sumi b { font-size: 24px; font-weight: 800; color: var(--neo-text); font-variant-numeric: tabular-nums; line-height: 1.15; }
.neo-gr-sumi b small { font-size: 11px; font-weight: 600; color: var(--neo-text-muted); margin-left: 3px; }

.neo-gr-list {
  list-style: none; margin: 0; padding: 0; flex: 1;
  display: flex; flex-direction: column; justify-content: space-evenly; gap: 4px;
}
.neo-gr-row { display: grid; grid-template-columns: minmax(64px, 1.1fr) minmax(0, 2fr) auto; align-items: center; gap: 10px; min-height: 26px; }
.neo-gr-name { font-size: 13px; font-weight: 600; color: var(--neo-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.neo-gr-track { height: 10px; border-radius: 999px; background: var(--neo-bg); overflow: hidden; border: 1px solid var(--neo-border); }
.neo-gr-track i { display: block; height: 100%; border-radius: inherit; background: var(--neo-idle); transition: width 500ms cubic-bezier(0.4, 0, 0.2, 1); }
.neo-gr-row.is-full .neo-gr-track i { background: var(--neo-ok); }
.neo-gr-row.is-part .neo-gr-track i { background: var(--neo-accent); }
.neo-gr-val { font-size: 12.5px; color: var(--neo-text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.neo-gr-val b { font-size: 16px; font-weight: 800; color: var(--neo-text); }
.neo-gr-slash { margin: 0 1px; opacity: .6; }
.neo-gr-val em { font-style: normal; display: inline-block; min-width: 38px; text-align: right; margin-left: 8px; color: var(--neo-text); font-weight: 700; }

/* ───── V2-P8 상태 분포 (링은 NeoChart.donut 그대로, 범례만 스탯 타일) ───── */
#neo-ov-donut .neo-donut-wrap { flex: 1; align-items: stretch; gap: 16px; }
/* 링 상한을 키우되 하한을 둔다 — 중앙 라벨은 viewBox 3.4단위라 링이 작아지면 판독 불가가 된다
   (120px에서 9.7px, 176px에서 14px). 세로 중앙 정렬은 wrap이 stretch라 별도로 잡는다. */
#neo-ov-donut .neo-donut-svg { width: clamp(120px, 38%, 176px); height: auto; align-self: center; }
/* 상태 4종이라 2×2가 기본. 열 수는 **뷰포트가 아니라 이 카드의 폭**에 반응해야 한다 —
   상황판이 768~1100px에서 2열을 유지하는 동안 카드가 좁아져 타일이 72px까지 줄고,
   그러면 가장 긴 라벨 '연결끊김'(약 46px)이 상시 말줄임된다. 라벨은 색 구분의 유일한 대체
   수단이라 잘리면 접근성 요건 자체가 잘린다. (auto-fit은 반대로 넓은 화면에서 3+1열이 되어 부적합.) */
#neo-ov-donut { container-type: inline-size; }
#neo-ov-donut .neo-donut-legend { flex: 1; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
@container (max-width: 420px) { #neo-ov-donut .neo-donut-legend { grid-template-columns: 1fr; } }
.neo-sd-tile {
  background: var(--neo-bg); border: 1px solid var(--neo-border); border-radius: var(--neo-radius-sm);
  padding: 8px 10px; display: flex; flex-direction: column; justify-content: space-between; gap: 6px; min-width: 0;
}
.neo-sd-tile.is-zero { opacity: .62; }
.neo-sd-top { display: flex; align-items: center; gap: 6px; min-width: 0; }
.neo-sd-top i { width: 8px; height: 8px; border-radius: 2px; flex: none; }
.neo-sd-lab { font-size: 11.5px; color: var(--neo-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.neo-sd-num { display: flex; align-items: baseline; gap: 2px; }
.neo-sd-num b { font-size: 21px; font-weight: 800; color: var(--neo-text); font-variant-numeric: tabular-nums; line-height: 1.1; }
.neo-sd-num small { font-size: 11px; color: var(--neo-text-muted); font-weight: 600; }
.neo-sd-num em { font-style: normal; margin-left: auto; font-size: 12px; font-weight: 700; color: var(--neo-text-muted); font-variant-numeric: tabular-nums; }
.neo-sd-mini { display: block; height: 3px; border-radius: 999px; background: var(--neo-border); overflow: hidden; }
.neo-sd-mini i { display: block; height: 100%; border-radius: inherit; }
/* 좁은 폭 — 2열이면 '연결끊김'(가장 긴 라벨)이 상시 말줄임된다. 라벨은 색 구분의 유일한 대체 수단이라
   잘리면 접근성 요건 자체가 잘린다. 1열로 풀고 링도 위로 올린다. */
/* 요약 타일도 같은 이유로 폭 반응 — 3열을 강제하면 좁은 카드에서 '전체 가동'이 잘린다 */
.neo-gr-sum { grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); }
.neo-ov-grow { display: flex; align-items: center; gap: 10px; padding: 6px 0; }
.neo-ov-grow-lab { flex: 0 0 96px; font-size: 12px; color: var(--neo-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.neo-ov-grow-track { flex: 1; height: 8px; border-radius: 5px; background: var(--neo-bg); overflow: hidden; }
/* transition 없음(사문 정리) — innerHTML 전량 재생성이라 트랜지션 발동 불가. 탱크·평균농도 바만 상주 노드 애니메이션 대상 */
.neo-ov-grow-track i { display: block; height: 100%; border-radius: 5px; background: var(--neo-accent); }
.neo-ov-grow-val { font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; flex: none; }
.neo-donut-wrap { display: flex; align-items: center; gap: 18px; }
.neo-donut-svg { width: 150px; height: 150px; flex: none; transform: rotate(0deg); }
.neo-donut-num { font-size: 9px; font-weight: 800; fill: var(--neo-text); }
.neo-donut-lbl { font-size: 3.4px; fill: var(--neo-text-muted); }
.neo-donut-legend { display: flex; flex-direction: column; gap: 6px; font-size: 12px; color: var(--neo-text-muted); min-width: 0; }
.neo-donut-leg { display: flex; align-items: center; gap: 6px; }
.neo-donut-leg b { color: var(--neo-text); font-variant-numeric: tabular-nums; }
.neo-donut-leg i { width: 9px; height: 9px; border-radius: 2px; flex: none; }

/* ===== V2-P3 필터 TOP5 ===== */
.neo-top5-list { list-style: none; margin: 0; padding: 0; }
.neo-top5-item { display: flex; align-items: center; gap: 9px; padding: 8px 4px; border-bottom: 1px solid var(--neo-border); cursor: pointer; border-radius: 6px; }
.neo-top5-item:last-child { border-bottom: none; }
.neo-top5-item:hover, .neo-top5-item:focus-visible { background: var(--neo-accent-soft); }
.neo-top5-rank { width: 20px; height: 20px; border-radius: 6px; background: var(--neo-bg); border: 1px solid var(--neo-border); display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 800; color: var(--neo-text-muted); flex: none; }
.neo-top5-item:first-child .neo-top5-rank { background: var(--neo-accent-soft); color: var(--neo-accent); border-color: transparent; }
.neo-top5-info { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.neo-top5-info b { font-size: 12px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.neo-top5-info small { font-size: 10px; color: var(--neo-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.neo-top5-val { font-size: 13px; font-weight: 800; font-variant-numeric: tabular-nums; display: flex; flex-direction: column; align-items: flex-end; }
.neo-top5-val small { font-size: 10px; font-weight: 600; color: var(--neo-text-muted); }
.neo-top5-item.is-warn .neo-top5-val { color: var(--neo-warn); }
.neo-top5-item.is-danger .neo-top5-val { color: var(--neo-danger); }

/* P3 T7 검증 픽스(Issue) 기원, 이제 정본 — .neo-ov-hero/.neo-ov-charts의 반응형 오버라이드(원래 §4.1 기준표 미디어
   블록에 있던 규칙)가 이 파일 뒤쪽 V2-P3 상황판 본문의 동일 선택자 기본 규칙(위 .neo-ov-hero/.neo-ov-charts)에
   소스 순서상 덮여 375px 등 좁은 화면에서도 히어로·차트가 2열로 남던 문제. §4.1 기준표 쪽의 사문화된 중복 선언은
   제거했고(P4 Task 1 하드닝), 이 캐스케이드 마지막 재선언이 해당 브레이크포인트의 유일한 정본이다. */
@media (max-width: 1023px) { .neo-ov-hero { grid-template-columns: 1fr; } }

/* ===== V2-P4 예약·타이머 화면 ===== */
.neo-sched-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 6px; }
.neo-sched-head h2 { margin: 0; font-size: 18px; }
.neo-sched-note { margin: 0 0 14px; color: var(--neo-text-muted); font-size: 12px; }
.neo-sched-list { list-style: none; margin: 0; padding: 0; }   /* 전폭 — 큰 화면에서 좌우 여백 없이 채움 */
.neo-sched-row {
  /* R12: 요일 칸 auto 폭이 행마다 달라 시간·요일 열이 뒤죽박죽 — 고정 트랙으로 전 행 정렬
     (행마다 독립 그리드라 auto는 행별로 해석됨. 170px = '일·월·화·수·목·금·토' 최장 라벨 기준) */
  display: grid; grid-template-columns: 52px minmax(0, 1fr) 64px 170px 72px;
  gap: 10px; align-items: center; padding: 11px 14px; margin-bottom: 8px;
  background: var(--neo-surface); border: 1px solid var(--neo-border); border-radius: var(--neo-radius-sm);
}
.neo-sched-row[role="button"] { cursor: pointer; }
.neo-sched-row[role="button"]:hover, .neo-sched-row[role="button"]:focus-visible { border-color: var(--neo-accent); box-shadow: var(--neo-shadow-lift); }
.neo-sched-row.is-off { opacity: 0.55; }
.neo-sched-type { font-size: 10px; font-weight: 800; text-align: center; padding: 3px 0; border-radius: 6px; background: var(--neo-accent-soft); color: var(--neo-accent); }
.neo-sched-type.is-group { background: rgba(5, 150, 105, 0.12); color: var(--neo-ok); }
.neo-sched-type.is-all { background: var(--neo-bg); color: var(--neo-text-muted); }
.neo-sched-target { font-size: 13px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.neo-sched-time { font-size: 14px; font-weight: 800; font-variant-numeric: tabular-nums; text-align: right; }   /* R12: 시간 우측 정렬 — 전 행 같은 기준선 */
.neo-sched-repeat { font-size: 12px; color: var(--neo-text-muted); white-space: nowrap; }
.neo-sched-today { font-size: 11px; font-weight: 800; color: var(--neo-warn); text-align: right; }
.neo-sched-idle { font-size: 11px; color: var(--neo-text-muted); text-align: right; }
@media (max-width: 599px) {
  .neo-sched-row { grid-template-columns: 44px minmax(0, 1fr) auto; }
  .neo-sched-repeat, .neo-sched-time { display: none; }
}

/* ===== V2-P4 알람 화면 ===== */
.neo-alarm-tabs { display: flex; gap: 6px; margin-bottom: 14px; }
.neo-alarm-tab { padding: 8px 18px; font-size: 13px; font-weight: 700; border-radius: var(--neo-radius-sm); border: 1px solid var(--neo-border); background: var(--neo-surface); color: var(--neo-text-muted); cursor: pointer; }
.neo-alarm-tab.is-active { background: var(--neo-accent); border-color: var(--neo-accent); color: #fff; }
.neo-alarm-tabbody .neo-alarm-group { background: var(--neo-surface); border: 1px solid var(--neo-border); border-radius: var(--neo-radius); padding: 12px 16px; margin-bottom: 12px; }
.neo-live-empty { padding: 28px 0; text-align: center; color: var(--neo-text-muted); }
.neo-hist-controls { display: flex; gap: 8px; margin-bottom: 10px; }
.neo-hist-controls select { padding: 7px 10px; border-radius: var(--neo-radius-sm); border: 1px solid var(--neo-border); background: var(--neo-surface); color: var(--neo-text); font-size: 12px; }
.neo-hist-note { margin: 0 0 8px; font-size: 11px; color: var(--neo-warn); }
.neo-histtable td { font-size: 12.5px; }
.neo-hist-time { font-variant-numeric: tabular-nums; white-space: nowrap; }
.neo-hist-ongoing { color: var(--neo-danger); font-weight: 800; font-size: 11px; }
.neo-hist-row.is-ongoing td:first-child b { color: var(--neo-danger); }
.neo-hist-pager { display: flex; align-items: center; gap: 12px; justify-content: center; margin-top: 12px; }
.neo-hist-pageinfo { font-size: 12px; color: var(--neo-text-muted); font-variant-numeric: tabular-nums; }
@media (max-width: 767px) { .neo-ov-charts { grid-template-columns: 1fr; } }

/* ===== V2-P5/P6 NEO_S 소형 배치도 제어 =====
   컨트롤러(허브) → CSS 연결선 → 실외기 노드. 실외기 대수(1~6)가 바뀌어도 좌표 계산이 필요 없도록
   연결선은 SVG가 아니라 스파인(세로) + 스터브(가로) 의사요소로 그린다. */
/* auto-fit + 상한 620px — auto-fill이면 장치가 1대일 때도 빈 트랙이 남아 카드가 좌측에 몰리고,
   1fr 상한이면 반대로 한 대가 화면 전폭까지 늘어나 배치도가 흩어진다.
   하한 440px = 허브 96 + 간격 26 + 실외기 행 최소(이름 58 + 게이지 + '산소필터 72%' 약 90) + 좌우 패딩. */
.neo-smgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(440px, 620px)); gap: 14px; }
/* 2열이 안 되는 폭에서는 상한을 풀어 한 열을 꽉 채운다 — 620px 카드 + 넓은 여백보다 낫다 */
@media (max-width: 1100px) { .neo-smgrid { grid-template-columns: 1fr; } }
.neo-smcard {
  position: relative; overflow: hidden;
  background: var(--neo-surface); border: 1px solid var(--neo-border); border-radius: var(--neo-radius);
  padding: 16px 18px; display: flex; flex-direction: column; gap: 12px; cursor: pointer;
  transition: border-color var(--neo-dur), box-shadow var(--neo-dur);
}
/* 가동 중 상단 액센트 — 카드 하나만 봐도 살아있는지 즉시 읽힌다(색 외 단서: 상태 필 문구) */
.neo-smcard::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 2px;
  background: linear-gradient(90deg, transparent, var(--neo-idle), transparent);
  opacity: .5; transition: opacity var(--neo-dur);
}
.neo-smcard.is-live::before { background: linear-gradient(90deg, transparent, var(--neo-accent), transparent); opacity: 1; }
.neo-smcard.is-error::before { background: linear-gradient(90deg, transparent, var(--neo-danger), transparent); opacity: 1; }
.neo-smcard:hover, .neo-smcard:focus-visible { border-color: var(--neo-accent); box-shadow: var(--neo-shadow-lift); }
.neo-smcard.is-error { border-color: color-mix(in srgb, var(--neo-danger) 45%, transparent); }
.neo-smcard.is-flash { background: color-mix(in srgb, var(--neo-accent) 16%, transparent); }
.neo-smcard-head { display: flex; align-items: center; gap: 10px; }
.neo-smcard-head b {
  font-size: 15px; font-weight: 700; min-width: 0; flex: 1;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ───── 배치도 본체 ───── */
.neo-topo { display: grid; grid-template-columns: 96px minmax(0, 1fr); gap: 0 26px; align-items: center; }
.neo-topo-hub { position: relative; display: flex; flex-direction: column; align-items: center; gap: 2px; }
.neo-topo-hub svg { width: 84px; height: 84px; display: block; }
.neo-topo-hub .neo-smcard-o2 {
  position: absolute; top: 42px; left: 0; right: 0; transform: translateY(-50%);
  text-align: center; pointer-events: none;
}
.neo-topo-hub-lab { font-size: 11px; color: var(--neo-text-muted); font-weight: 600; letter-spacing: .01em; }
.neo-smring-track { fill: none; stroke: color-mix(in srgb, var(--neo-border) 85%, transparent); stroke-width: 6; }
.neo-smring-bar {
  fill: none; stroke-width: 6; stroke-linecap: round; stroke-dasharray: 100;
  transform: rotate(-90deg); transform-origin: 32px 32px;
  transition: stroke-dashoffset 600ms cubic-bezier(0.4, 0, 0.2, 1);
}
.neo-smring-bar.is-empty { display: none; }   /* 농도 미상 — 라운드 캡 잔점 제거 */
.neo-smcard-o2 { font-size: 23px; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.1; }
.neo-smcard-o2 small { font-size: 12px; color: var(--neo-text-muted); font-weight: 600; }
/* 허브 링 안(지름 64px)에 들어가야 하므로 23px 숫자 대신 12px — 세 글자가 링을 넘지 않는 최대치 */
.neo-smcard-o2alt { font-size: 12px; font-weight: 700; color: var(--neo-text-muted); letter-spacing: -0.02em; }

/* ───── 실외기 노드 + 연결선 ───── */
.neo-topo-odus {
  position: relative; list-style: none; margin: 0; padding: 0 0 0 26px;
  display: flex; flex-direction: column; gap: 8px;
}
/* 스파인 — 첫/마지막 항목 중심까지만 그어 T자 끝이 튀지 않게 한다 */
.neo-topo-odus::before {
  content: ""; position: absolute; left: 0; top: 15px; bottom: 15px; width: 1.5px;
  background: var(--neo-border); border-radius: 2px;
}
/* 허브 → 스파인 가로 줄기 */
.neo-topo-odus::after {
  content: ""; position: absolute; left: -26px; right: calc(100% - 1.5px); top: 50%;
  height: 1.5px; background: var(--neo-border);
}
.neo-topo-odu {
  position: relative; display: grid; grid-template-columns: 58px minmax(0, 1fr) auto;
  align-items: center; gap: 10px; min-height: 30px;
}
/* 스터브 — 스파인에서 각 실외기로. 노드 점 지름만큼 짧게 끊어 점이 선 위에 얹히게 한다 */
.neo-topo-odu::before {
  content: ""; position: absolute; left: -26px; width: 17px; top: 50%;
  height: 1.5px; background: var(--neo-border);
}
/* 노드 점 — 라벨과 2px 간격(겹치면 글자 첫 획을 먹는다) */
.neo-topo-odu::after {
  content: ""; position: absolute; left: -9px; top: 50%; width: 7px; height: 7px;
  margin-top: -3.5px; border-radius: 50%;
  background: var(--neo-surface); border: 1.5px solid var(--neo-border);
}
.neo-smcard.is-live .neo-topo-odus::before,
.neo-smcard.is-live .neo-topo-odus::after,
.neo-smcard.is-live .neo-topo-odu::before { background: color-mix(in srgb, var(--neo-accent) 55%, transparent); }
.neo-smcard.is-live .neo-topo-odu::after { border-color: var(--neo-accent); }
.neo-topo-odu.is-warn::after   { border-color: var(--neo-warn); }
.neo-topo-odu.is-danger::after { border-color: var(--neo-danger); }

.neo-topo-odu-name { font-size: 12.5px; font-weight: 600; color: var(--neo-text); white-space: nowrap; }
.neo-topo-odu-bar {
  height: 6px; border-radius: 999px; overflow: hidden;
  background: color-mix(in srgb, var(--neo-border) 70%, transparent);
}
.neo-topo-odu-bar i {
  display: block; height: 100%; border-radius: inherit; background: var(--neo-ok);
  transition: width 500ms cubic-bezier(0.4, 0, 0.2, 1);
}
.neo-topo-odu-bar i.is-warn   { background: var(--neo-warn); }
.neo-topo-odu-bar i.is-danger { background: var(--neo-danger); }
.neo-topo-odu-bar.is-empty { background: repeating-linear-gradient(90deg, var(--neo-border) 0 4px, transparent 4px 8px); }
.neo-topo-odu-meta { font-size: 11.5px; color: var(--neo-text-muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.neo-topo-odu-meta b { font-weight: 700; color: var(--neo-text); }
.neo-topo-odu.is-warn   .neo-topo-odu-meta b { color: var(--neo-warn); }
.neo-topo-odu.is-danger .neo-topo-odu-meta b { color: var(--neo-danger); }
.neo-topo-empty { margin: 0; font-size: 12.5px; color: var(--neo-text-muted); }

/* ───── 하단 제어 ───── */
.neo-smcard-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding-top: 12px; border-top: 1px solid var(--neo-border);
}
/* 표(.neo-devtable .neo-btn)의 64px 고정폭은 여기 적용되지 않는다 — 폭을 안 주면 flex가 눌러
   "상세"가 두 줄로 깨진다. 소형은 터치 대상이라 표보다 크게(높이 38px) 잡는다. */
.neo-smcard-btns { display: flex; gap: 8px; }
.neo-smcard-btns .neo-btn { flex: none; min-width: 68px; padding: 9px 14px; white-space: nowrap; }

/* 좁은 폭 — 허브를 위로 올리고 연결선은 숨긴다(꺾인 선이 세로 배치에선 오히려 방해) */
@media (max-width: 560px) {
  .neo-smgrid { grid-template-columns: 1fr; }
  .neo-topo { grid-template-columns: 1fr; gap: 12px; justify-items: center; }
  .neo-topo-odus { padding-left: 0; width: 100%; }
  .neo-topo-odus::before, .neo-topo-odus::after, .neo-topo-odu::before, .neo-topo-odu::after { display: none; }
  .neo-smcard-foot { flex-direction: column; align-items: stretch; }
}

/* ===== V2-P7 소형 배치도 흐름 모션 — 연결선을 타고 흐르는 광점 =====
   base2(배치도 H)의 시그니처인 topo-dashFlow("산소가 컨트롤러에서 실외기로 흘러간다")를
   이 배치도에 맞게 재구현한다. 우리 연결선은 SVG path가 아니라 의사요소라 stroke-dashoffset을
   쓸 수 없어, 이동하는 그라디언트 스톱으로 광점을 그린다.

   ── 왜 시계를 조상(#neo-grid)에 거는가 (이 설계의 핵심) ──
   renderDevicesTable은 WS 프레임마다 재렌더된다. 방금 렌더 메모를 붙였지만 산소농도 숫자가
   바뀌면 여전히 카드가 통째로 재조립되고, 요소가 소유한 CSS 애니메이션은 그때 0부터 다시 시작한다.
   주기 8초짜리 흐름은 한 번도 완주하지 못한다. 그래서 위상(--neo-smf)을 요소가 아니라
   **재조립에서 살아남는 조상**(#neo-grid = 상주 section, renderGrid는 자식만 교체)에 두고
   상속으로 내려보낸다. 새로 삽입된 의사요소는 삽입 시점의 상속값을 그대로 읽으므로,
   광점은 재조립 직후에도 직전 프레임이 있던 자리에 그대로 있다. 리셋에 강한 게 아니라 리셋이 보이지 않는다.
   ⚠️ @property로 syntax를 선언해야 <number>로 보간된다 — 미등록 커스텀 프로퍼티는 중간값 없이 튄다.

   ── 1주기 8초, 카드가 실제로 움직이는 시간은 약 2.3초 ──
     f = 0.9 ~ 1.0   줄기: 광점이 허브에서 스파인 접합부로 건너온다
     f = 1.0 → 0     접합부에서 광점이 둘로 갈라진다 (줄기의 top:50%와 스파인의 중점이 같은 픽셀이라 이음매가 없다)
     f = 0 ~ 0.15    스파인: 갈라진 광점이 위·아래 양끝(첫/마지막 실외기)으로 빠져나간다
     f = 0.15 ~ 0.9  정지
   NEO_S는 컨트롤러 1~6대가 대상이라 시계 하나로 전 카드를 동기화한다. 카드 수가 적을 때
   동시 맥박은 "설비 전체가 한 번 숨쉰다"로 읽힌다. (대수가 많은 현장에서 동시 맥동이 거슬리면
   시계를 3개로 늘려 nth-child로 나눠 담으면 되지만, 기본 정렬이 그룹별이라 섹션마다 nth-child가
   리셋되는 함정이 있다 — 그때는 섹션 인덱스와 교차해야 한다.)

   동시에 도는 반복 모션은 이 흐름 1종뿐이다. 맥동·회전·발광은 의도적으로 넣지 않았다. */
@property --neo-smf { syntax: "<number>"; inherits: true; initial-value: 0; }
@keyframes neo-smflow { from { --neo-smf: 0; } to { --neo-smf: 1; } }

/* :has() 게이트 — 가동 중 카드가 하나도 없거나 대형(표) 모드면 시계를 아예 돌리지 않는다.
   상속 등록 프로퍼티가 매 프레임 바뀌면 서브트리 스타일 재계산이 걸리므로, 놀 때는 비용 0이어야 한다. */
#neo-grid:has(.neo-smcard.is-live) { animation: neo-smflow 8s linear infinite; }

/* ⚠️ 광점 규칙에도 **같은 :has() 프리픽스**를 붙인다. :has() 미지원 엔진에서 시계 규칙만 드롭되면
   --neo-smf가 initial-value 0에 고정돼 스파인 정중앙에 광점이 영구히 박힌다. 두 규칙이 함께
   드롭돼야 모션 도입 전과 같아진다. (반대로 아래 kill switch들은 :has() 없이 써서 어디서든 살아남게 한다.)
   ⚠️ :not(.is-error)는 특정도용이기도 하다 — 위쪽 .neo-smcard.is-live ... { background: } 단축(0,3,1)이
   background-image를 initial로 되돌리므로, 소스 순서에 기대지 않고 이기려면 특정도가 더 높아야 한다. */
#neo-grid:has(.neo-smcard.is-live) .neo-smcard.is-live:not(.is-error) .neo-topo-odus::before {
  /* 가동 중에는 선을 2.5px로 굵힌다 — 1.5px에서는 광점이 지나가도 면적이 너무 작아 눈에 안 띈다
     (실측: 1.5px 선에서는 4배 확대해야 겨우 식별됐다). 정지 카드는 1.5px 그대로라 대비도 생긴다. */
  width: 2.5px;
  /* 광점 크기: 실외기 2대면 스파인이 38px뿐이라 고정값만 쓰면 "레일 절반이 발광"이 된다 → 비율 상한을 함께 건다 */
  --neo-smf-core: min(4px, 6%);
  --neo-smf-halo: min(13px, 18%);
  --neo-smf-d: calc(50% + var(--neo-smf) * 340%);   /* 아래로 빠지는 광점 */
  --neo-smf-u: calc(50% - var(--neo-smf) * 340%);   /* 위로 빠지는 광점 */
  background-image:
    linear-gradient(to bottom,
      transparent        calc(var(--neo-smf-d) - var(--neo-smf-halo)),
      var(--neo-smf-dot) calc(var(--neo-smf-d) - var(--neo-smf-core)),
      var(--neo-smf-dot) calc(var(--neo-smf-d) + var(--neo-smf-core)),
      transparent        calc(var(--neo-smf-d) + var(--neo-smf-halo))),
    linear-gradient(to bottom,
      transparent        calc(var(--neo-smf-u) - var(--neo-smf-halo)),
      var(--neo-smf-dot) calc(var(--neo-smf-u) - var(--neo-smf-core)),
      var(--neo-smf-dot) calc(var(--neo-smf-u) + var(--neo-smf-core)),
      transparent        calc(var(--neo-smf-u) + var(--neo-smf-halo)));
}
/* 줄기는 폭이 27.5px로 항상 고정(left:-26px ~ calc(100% - 1.5px))이라 비율 상한이 필요 없다 */
#neo-grid:has(.neo-smcard.is-live) .neo-smcard.is-live:not(.is-error) .neo-topo-odus::after {
  height: 2.5px;
  --neo-smf-t: calc((var(--neo-smf) - 0.9) * 1000%);
  background-image:
    linear-gradient(to right,
      transparent        calc(var(--neo-smf-t) - 10px),
      var(--neo-smf-dot) calc(var(--neo-smf-t) - 3px),
      var(--neo-smf-dot) calc(var(--neo-smf-t) + 3px),
      transparent        calc(var(--neo-smf-t) + 10px));
}
/* 광점 색 — 순수 accent는 accent로 칠해진 레일 위에서 라이트 모드 대비가 1.6:1이라 사실상 안 보인다.
   텍스트색을 섞어 밝기 차를 만든다: 다크는 near-white(#E6EDF6)로 밝아지고 라이트는 near-black(#0F172A)으로
   어두워져 양 테마 모두 레일과 확실히 갈린다. 30:70이면 대비가 4:1을 넘어 벽걸이 거리에서도 읽힌다. */
.neo-smcard { --neo-smf-dot: color-mix(in srgb, var(--neo-accent) 30%, var(--neo-text)); }

/* 좁은 폭에서는 연결선 자체를 숨기므로(위 560px 블록) 시계도 광점도 끈다.
   background-image까지 지우는 이유: display:none에만 기대면 그 규칙을 누가 손대는 순간 정적 광점이 드러난다. */
@media (max-width: 560px) {
  #neo-grid:has(.neo-smcard.is-live) { animation: none; }
  #neo-grid:has(.neo-smcard.is-live) .neo-smcard.is-live:not(.is-error) .neo-topo-odus::before,
  #neo-grid:has(.neo-smcard.is-live) .neo-smcard.is-live:not(.is-error) .neo-topo-odus::after { background-image: none; }
}
/* ── kill switch 3종 — :has() 없이 써서 지원 여부와 무관하게 항상 적용된다 ──
   #neo-grid(1,0,0)+!important는 393행 blanket(.neo-root *, 0,1,0)도 이긴다.
   reduce에서 duration 클램프(0.01ms)로 두면 무한 루프가 "정지"가 아니라 초고속 샘플링이 된다 — 반드시 none. */
@media (prefers-reduced-motion: reduce) {
  #neo-grid { animation: none !important; }
  .neo-smcard.is-live .neo-topo-odus::before,
  .neo-smcard.is-live .neo-topo-odus::after { background-image: none !important; }
}
/* Windows 고대비 — background-color는 확정적으로 강제되지만 background-image는 엔진마다 갈린다.
   강제되지 않는 분기에서 "선 없는 빈 공간을 점이 미끄러지는" 화면이 되는 것을 막는다. */
@media (forced-colors: active) {
  #neo-grid { animation: none !important; }
  .neo-smcard.is-live .neo-topo-odus::before,
  .neo-smcard.is-live .neo-topo-odus::after { background-image: none !important; }
}
/* WCAG 2.2.2(Pause/Stop/Hide) 착지점 — 자동 시작·무한 반복이라 대상이다. prefers-reduced-motion은
   OS 신호일 뿐 "사용자가 조작할 수 있는 메커니즘"이 아니다. data-neo-theme과 같은 패턴이라
   나중에 설정에 토글을 붙일 때 CSS 추가 작업이 없다. */
html[data-neo-motion="off"] #neo-grid { animation: none !important; }
html[data-neo-motion="off"] .neo-smcard.is-live .neo-topo-odus::before,
html[data-neo-motion="off"] .neo-smcard.is-live .neo-topo-odus::after { background-image: none !important; }
