/* ===========================================================================
   컴포넌트 — 카드·배지·KPI·토글·표·칩
   색과 간격은 tokens.css 만 쓴다.
   =========================================================================== */

/* ---------- 아이콘 ----------
   크기를 주지 않은 svg 는 폭 100% 로 퍼진다. 쓰는 자리마다 규칙을 적다가
   하나 빼먹으면 카드가 통째로 깨지므로 기본값을 둔다. */
.ic { width: 16px; height: 16px; flex: 0 0 auto; }

/* ---------- 카드 ---------- */
.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); box-shadow: var(--sh-1);
}
.card-head {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--line-2);
}
.card-title { font-size: var(--fs-4); font-weight: 800; margin: 0; }
.card-body { padding: var(--sp-4); }
.card-pad  { padding: var(--sp-4); }

/* ---------- 배지 ----------
   상태 네 단계를 지킨다: 정상 / 주의 / 경보 / 꺼짐.
   이름과 색이 어긋나지 않게 (전 앱은 .badge.off 가 빨강이었다). */
.badge {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  padding: 3px var(--sp-2); border-radius: var(--r-pill);
  font-size: var(--fs-2); font-weight: 700; white-space: nowrap;
}
.badge .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.badge.ok   { background: var(--ok-bg);   color: var(--ok-text); }
.badge.warn { background: var(--warn-bg); color: var(--warn); }
.badge.bad  { background: var(--bad-bg);  color: var(--bad); }
.badge.off  { background: var(--off-bg);  color: var(--off); }
/* 어두운 상단바 위에 얹는 배지 */
.topbar .badge.ok   { background: rgba(22, 163, 74, .18);  color: #86efac; }
.topbar .badge.bad  { background: rgba(239, 68, 68, .18);  color: #fca5a5; }
.topbar .badge.warn { background: rgba(245, 158, 11, .18); color: #fcd34d; }
.topbar .badge.off  { background: rgba(255, 255, 255, .10); color: var(--nav-dim); }

/* ---------- KPI 카드 ---------- */
.kpi { padding: var(--sp-4); }
.kpi-top {
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-3); color: var(--dim); font-weight: 700;
}
.kpi-top .ic { width: 18px; height: 18px; flex: 0 0 auto; }
.kpi-val {
  display: flex; align-items: baseline; gap: var(--sp-1);
  margin-top: var(--sp-2);
  font-size: var(--fs-7); font-weight: 800; line-height: 1.05;
  font-variant-numeric: tabular-nums;
}
.kpi-val .u { font-size: var(--fs-4); font-weight: 700; color: var(--dim); }
.kpi-sub {
  margin-top: var(--sp-2); font-size: var(--fs-2); color: var(--dim);
}
.kpi.is-bad  .kpi-val { color: var(--bad); }
.kpi.is-warn .kpi-val { color: var(--warn); }
.kpi.is-off  .kpi-val { color: var(--off); }

/* ---------- 설비 제어 카드 ---------- */
.ctl { padding: var(--sp-4); }
.ctl-head {
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  font-size: var(--fs-4); font-weight: 800;
}
.ctl-head .ic { width: 18px; height: 18px; }
.ctl-val {
  margin-top: var(--sp-2); font-size: var(--fs-6); font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.ctl-val .u { font-size: var(--fs-3); color: var(--dim); font-weight: 700; margin-left: 2px; }
.ctl-row {
  display: flex; align-items: center; gap: var(--sp-2);
  margin-top: var(--sp-3); flex-wrap: wrap;
}
.ctl-sub { font-size: var(--fs-2); color: var(--dim); margin-top: var(--sp-1); }

/* ---------- 버튼 ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: var(--touch); padding: 0 var(--sp-4);
  border: 1px solid var(--accent); background: var(--accent); color: #fff;
  border-radius: var(--r-sm); font: inherit; font-weight: 700; cursor: pointer;
  transition: filter var(--dur), transform var(--dur);
}
.btn:hover { filter: brightness(1.06); }
.btn:active { transform: translateY(1px); }
.btn.ghost { background: var(--surface); color: var(--ink); border-color: var(--line); }
.btn.ghost:hover { background: var(--surface-2); }
.btn.sm { min-height: 34px; padding: 0 var(--sp-3); font-size: var(--fs-3); }
.btn.icon { width: var(--touch); padding: 0; }
.btn:disabled { opacity: .45; cursor: not-allowed; }

/* ---------- 토글 ---------- */
.toggle {
  position: relative; display: inline-flex; align-items: center;
  min-height: var(--touch); gap: var(--sp-2); cursor: pointer;
  background: none; border: 0; padding: 0; font: inherit;
}
.toggle .track {
  width: 46px; height: 26px; border-radius: var(--r-pill);
  background: var(--off); position: relative;
  transition: background var(--dur);
}
.toggle .knob {
  position: absolute; top: 3px; left: 3px;
  width: 20px; height: 20px; border-radius: 50%;
  background: #fff; box-shadow: var(--sh-1);
  transition: transform var(--dur);
}
.toggle[aria-pressed="true"] .track { background: var(--ok); }
.toggle[aria-pressed="true"] .knob  { transform: translateX(20px); }
.toggle .lb { font-size: var(--fs-3); font-weight: 700; color: var(--dim); }

/* ---------- 슬라이더 ---------- */
.slider { width: 100%; accent-color: var(--accent); height: var(--touch); }

/* ---------- 칩 (기간 선택 등) ---------- */
.chips { display: inline-flex; gap: var(--sp-1); flex-wrap: wrap; }
.chip {
  min-height: var(--touch); padding: 0 var(--sp-4);
  display: inline-flex; align-items: center;
  border: 1px solid var(--line); background: var(--surface);
  border-radius: var(--r-sm); font: inherit; font-size: var(--fs-3);
  font-weight: 700; color: var(--ink-2); cursor: pointer;
  transition: background var(--dur), border-color var(--dur), color var(--dur);
}
.chip:hover { background: var(--surface-2); }
.chip.on { background: var(--accent); border-color: var(--accent); color: #fff; }

/* ---------- 입력 ---------- */
.field, select.field, input.field, textarea.field {
  min-height: var(--touch); padding: 0 var(--sp-3);
  border: 1px solid var(--line); background: var(--field);
  border-radius: var(--r-sm); font: inherit; color: var(--ink); width: 100%;
}
textarea.field { min-height: 96px; padding: var(--sp-3); line-height: 1.5; }
.lb { display: block; font-size: var(--fs-2); color: var(--dim); font-weight: 700;
      margin-bottom: var(--sp-1); }

/* ---------- 표 ---------- */
.tbl { width: 100%; border-collapse: collapse; }
.tbl th {
  text-align: left; font-size: var(--fs-2); color: var(--dim); font-weight: 700;
  padding: var(--sp-2) var(--sp-3); background: var(--surface-3);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0;
}
.tbl td {
  padding: var(--sp-3); border-bottom: 1px solid var(--line-2);
  font-size: var(--fs-3); font-variant-numeric: tabular-nums;
}
.tbl tr:last-child td { border-bottom: 0; }
.tbl .num { text-align: right; }
.tbl-wrap { overflow: auto; max-height: 360px; max-width: 100%;
             -webkit-overflow-scrolling: touch; }
/* 폭이 모자라면 칸을 한 글자씩 접지 말고 가로로 밀게 한다 */
.tbl { min-width: 520px; }
#utbl { min-width: 640px; }

/* ---------- 토양 카드 ---------- */
.soil { padding: var(--sp-3) var(--sp-4); }
.soil-head {
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-4); font-weight: 800;
}
.soil-head .soil-ic { display: inline-flex; }
.soil-head .soil-ic .ic { width: 17px; height: 17px; }
.soil-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.soil-where {
  display: flex; gap: var(--sp-2); align-items: baseline; flex-wrap: wrap;
  margin-top: 2px; font-size: var(--fs-2); color: var(--dim);
}
.soil-where .soil-temp { margin-left: auto; }
.soil-vals {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-2); margin-top: var(--sp-3);
}
.soil-vals .cell { min-width: 0; }
.soil-vals .k {
  display: flex; align-items: center; gap: 4px;
  font-size: var(--fs-2); color: var(--dim); font-weight: 700;
}
.soil-vals .k .ic { width: 13px; height: 13px; }
.soil-vals .v {
  font-size: var(--fs-5); font-weight: 800; font-variant-numeric: tabular-nums;
}
.soil-vals .v.warn { color: var(--warn); }
.soil-vals .v.bad  { color: var(--bad); }
.soil.is-off { opacity: .6; }

/* ---------- 카메라 ---------- */
.cam { overflow: hidden; }
.cam-head {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3); font-size: var(--fs-3); font-weight: 700;
}
.cam-view {
  position: relative; background: #0b1016; aspect-ratio: 16 / 9;
  display: flex; align-items: center; justify-content: center;
}
.cam-view img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cam-view .ts {
  position: absolute; left: var(--sp-2); top: var(--sp-2);
  background: rgba(0, 0, 0, .55); color: #fff;
  font-size: var(--fs-1); padding: 2px 6px; border-radius: 4px;
  font-variant-numeric: tabular-nums;
}
.cam-view .none { color: var(--nav-dim); font-size: var(--fs-3); }
.cam-foot {
  display: flex; gap: var(--sp-2); padding: var(--sp-2) var(--sp-3);
  border-top: 1px solid var(--line-2);
}

/* ---------- 상태 메시지 ---------- */
.empty, .note {
  padding: var(--sp-6); text-align: center;
  color: var(--dim); font-size: var(--fs-3);
}
.skel {
  background: linear-gradient(90deg, var(--line-2) 25%, var(--surface-2) 37%, var(--line-2) 63%);
  background-size: 400% 100%; animation: skel 1.3s ease-in-out infinite;
  border-radius: var(--r-sm); min-height: 14px;
}
.skel-card { height: 112px; }
@keyframes skel { 0% { background-position: 100% 0 } 100% { background-position: 0 0 } }
@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}
.errbar {
  background: var(--bad-bg); color: var(--bad);
  border: 1px solid var(--bad-fill); border-radius: var(--r-md);
  padding: var(--sp-3) var(--sp-4); margin-bottom: var(--sp-4);
  font-size: var(--fs-3); font-weight: 700;
}
.stalebar {
  background: var(--warn-bg); color: var(--warn);
  border: 1px solid var(--warn-fill); border-radius: var(--r-md);
  padding: var(--sp-3) var(--sp-4); margin-bottom: var(--sp-4);
  font-size: var(--fs-3); font-weight: 700;
}
body.is-stale main > :not(.stalebar):not(.errbar) { opacity: .55; }

/* ===========================================================================
   그래프
   눈금과 축은 뒤로 물러나고 데이터가 앞에 온다 (dataviz: recessive grid).
   =========================================================================== */
.chart { width: 100%; height: auto; display: block; max-width: 100%; }
.chart-box { padding: var(--sp-2) var(--sp-3) var(--sp-3); }
.chart-card { margin-bottom: var(--sp-3); }

.chart .grid line { stroke: var(--line-2); stroke-width: 1; }
.chart text.ax { font-size: 11px; fill: var(--dim); }
.chart text.end { font-size: 11px; font-weight: 700; fill: var(--ink-2); }
.chart path.ln { fill: none; stroke-width: 2; stroke-linejoin: round;
                 stroke-linecap: round; }
.chart path.band { fill: var(--s-band); stroke: none; }
.chart line.cross { stroke: var(--ink-2); stroke-width: 1; stroke-dasharray: 3 3;
                    pointer-events: none; }

/* 상태 띠 */
.chart rect.lane-bg { fill: var(--s-off); }
.chart rect.lane-on { fill: var(--s-on); }
.chart text.lane-n { font-size: 11px; font-weight: 700; fill: var(--ink-2); }

/* 작은 배수 — 네 계열부터는 색으로 구분이 안 되니 쪼갠다 */
.small-mult { display: grid; gap: var(--sp-3); padding: var(--sp-3); }
.sm-one { min-width: 0; border: 1px solid var(--line-2); border-radius: var(--r-sm);
          padding: var(--sp-2); background: var(--surface); }
.sm-t { font-size: var(--fs-2); font-weight: 800; color: var(--ink-2);
        margin-bottom: 2px; }
.sm-v { font-size: var(--fs-4); font-weight: 800; text-align: right;
        font-variant-numeric: tabular-nums; }
.sm-v .u { font-size: var(--fs-1); color: var(--dim); font-weight: 700; }

/* 십자선 설명풍선 */
.chart-tip {
  position: fixed; z-index: 70; pointer-events: none;
  background: var(--nav); color: var(--nav-ink);
  border-radius: var(--r-sm); padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-2); line-height: 1.6; box-shadow: var(--sh-3);
  display: flex; flex-direction: column; gap: 1px; max-width: 260px;
}
.chart-tip[hidden] { display: none !important; }
.chart-tip span { display: flex; align-items: center; gap: var(--sp-2);
                  font-variant-numeric: tabular-nums; }
.chart-tip i { width: 9px; height: 9px; border-radius: 2px; flex: none; }
.chart-tip b { font-weight: 800; }
.chart-tip span b { margin-left: auto; }
.chart-sum {
  display: flex; gap: var(--sp-6); flex-wrap: wrap;
  padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--line-2);
}
.chart-sum .it .k { font-size: var(--fs-2); color: var(--dim); font-weight: 700; }
.chart-sum .it .v {
  font-size: var(--fs-6); font-weight: 800; font-variant-numeric: tabular-nums;
}
.legend {
  display: flex; gap: var(--sp-4); flex-wrap: wrap;
  padding: 0 var(--sp-4) var(--sp-2); font-size: var(--fs-2); color: var(--dim);
}
.legend .it { display: inline-flex; align-items: center; gap: var(--sp-1); }
.legend .sw { width: 14px; height: 3px; border-radius: 2px; }

/* ===========================================================================
   로그인
   =========================================================================== */
.login-page { background: var(--nav); }
.login {
  min-height: 100dvh; display: grid; place-items: center; padding: var(--sp-5);
}
.login-box {
  width: 100%; max-width: 380px; background: var(--surface);
  border-radius: var(--r-lg); padding: var(--sp-7) var(--sp-6);
  box-shadow: 0 18px 50px rgba(0, 0, 0, .35);
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.login-brand {
  display: flex; align-items: center; gap: var(--sp-3);
  margin-bottom: var(--sp-5);
}
.login-brand .leaf { width: 30px; height: 30px; color: var(--ok); flex: none; }
.login-site { font-size: var(--fs-5); font-weight: 800; color: var(--ink); }
.login-sub { font-size: var(--fs-2); color: var(--dim); }
.login-box .lb { margin-top: var(--sp-3); }
.login-box .btn { margin-top: var(--sp-5); }
.btn.lg { min-height: 46px; font-size: var(--fs-4); }
.login-note {
  margin: var(--sp-5) 0 0; font-size: var(--fs-2); color: var(--dim);
  line-height: 1.6;
}

/* 알림 줄 — 오류(errbar)보다 약한 단계 */
.notebar {
  background: var(--accent-bg); color: var(--ink);
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: var(--r-md); padding: var(--sp-3) var(--sp-4);
  font-size: var(--fs-3); margin-bottom: var(--sp-4);
}
.notebar.dim { border-left-color: var(--off); background: var(--surface-2); color: var(--dim); }
.notebar a { color: var(--accent); font-weight: 700; }

/* ===========================================================================
   설정
   =========================================================================== */
/* 드롭다운 머리 — 사이드바 링크와 똑같이 보이되 누르면 접히고 펼쳐진다 */
.nav-group {
  display: flex; align-items: center; gap: var(--sp-3); width: 100%;
  min-height: var(--touch); padding: 0 var(--sp-3);
  background: none; border: 0; border-radius: var(--r-sm);
  color: var(--nav-dim); font: inherit; font-size: var(--fs-3); font-weight: 700;
  cursor: pointer; text-align: left;
  transition: background var(--dur), color var(--dur);
}
.nav-group:hover { background: var(--nav-2); color: var(--nav-ink); }
.nav-group.active { background: var(--nav-active); color: #fff; }
.nav-group .ic { width: 18px; height: 18px; flex: 0 0 auto; }
.nav-group .caret {
  width: 15px; height: 15px; margin-left: auto;
  transition: transform var(--dur);
}
.nav-group[aria-expanded="true"] .caret { transform: rotate(180deg); }

/* 상단바 사이드바 접기 버튼 */
.topbar .navtoggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; flex: 0 0 auto; margin-right: var(--sp-1);
  background: rgba(255, 255, 255, .08); color: var(--nav-ink);
  border: 0; border-radius: var(--r-sm); cursor: pointer;
}
.topbar .navtoggle:hover { background: rgba(255, 255, 255, .18); }
.topbar .navtoggle .ic { width: 18px; height: 18px; }

/* 사이드바 하위 메뉴 — 설정 안으로 또 들어가지 않게 메뉴에서 바로 간다 */
/* 기본은 접힘. display 를 .side-sub 에 직접 주면 브라우저 기본값인
   [hidden]{display:none} 을 우선순위로 눌러 버려서, 눌러도 접히지 않았다.
   펼침을 class 로 드러내 둔다 — 무엇이 열고 닫는지 CSS 만 봐도 보인다. */
.side-sub, .side-sub[hidden] { display: none; }
.side-util.is-open .side-sub {
  display: flex; flex-direction: column; gap: 1px;
  margin: var(--sp-1) 0 var(--sp-2) var(--sp-3);
  padding-left: var(--sp-2);
  border-left: 1px solid rgba(255, 255, 255, .12);
}
.side-sub a {
  display: flex; align-items: center; gap: var(--sp-2);
  min-height: 34px; padding: 0 var(--sp-2); border-radius: var(--r-sm);
  color: var(--nav-dim); font-size: var(--fs-2); font-weight: 600;
  text-decoration: none;
}
.side-sub a:hover { background: var(--nav-2); color: var(--nav-ink); }
.side-sub a.active { color: #fff; background: rgba(37, 99, 235, .55); font-weight: 700; }
.side-sub a .ic { width: 14px; height: 14px; color: inherit; opacity: .9; }

/* 좁은 화면·아이콘 띠에는 사이드바 하위 메뉴가 없다. 본문 위에 가로줄로 둔다. */
.set-tabs { display: none; }
.set-tabs a {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3); border-radius: var(--r-pill);
  border: 1px solid var(--line); background: var(--surface);
  color: var(--ink-2); font-size: var(--fs-3); font-weight: 700;
  text-decoration: none; white-space: nowrap;
}
.set-tabs a .ic { width: 15px; height: 15px; color: var(--dim); }
.set-tabs a.active { background: var(--accent); border-color: var(--accent); color: #fff; }
.set-tabs a.active .ic { color: #fff; }

.set-main { display: flex; flex-direction: column; gap: var(--sp-5); min-width: 0; }
.set-main .set-sec { max-width: 980px; }
.set-sec { scroll-margin-top: var(--sp-5); }
.set-h3 {
  margin: var(--sp-6) 0 var(--sp-3); font-size: var(--fs-3);
  text-transform: none; color: var(--dim); font-weight: 700;
  border-top: 1px solid var(--line-2); padding-top: var(--sp-4);
}
.set-sec .set-h3:first-child { margin-top: 0; border-top: 0; padding-top: 0; }
.set-room + .set-room { border-top: 1px solid var(--line-2); margin-top: var(--sp-4); padding-top: var(--sp-4); }
.set-room-h { display: flex; gap: var(--sp-3); align-items: baseline; margin-bottom: var(--sp-2); }

/* 이름-값 목록 — 설정 화면 전체에서 같은 모양 */
.kv { display: grid; grid-template-columns: 150px minmax(0, 1fr); gap: var(--sp-2) var(--sp-4); margin: 0; }
.kv dt { color: var(--dim); font-size: var(--fs-3); }
.kv dd { margin: 0; font-size: var(--fs-3); color: var(--ink); display: flex;
         gap: var(--sp-2); align-items: center; flex-wrap: wrap;
         overflow-wrap: anywhere; }

/* 폼 */
.form { display: flex; flex-direction: column; gap: var(--sp-3); max-width: 520px; }
.form-grid { max-width: 640px; }
.form-row { display: grid; grid-template-columns: 150px minmax(0, 1fr); gap: var(--sp-3);
            align-items: center; }
.form-row .lb { margin: 0; }
.form-row .hint { grid-column: 2; font-size: var(--fs-2); color: var(--dim); margin-top: -4px; }
.form-check .chk { display: flex; gap: var(--sp-2); align-items: center;
                   font-size: var(--fs-3); color: var(--ink-2); }
.form-check input { width: 18px; height: 18px; accent-color: var(--accent); }
.form-act { display: flex; gap: var(--sp-3); align-items: center;
            margin-top: var(--sp-3); padding-left: 150px; }
.form-msg { font-size: var(--fs-3); font-weight: 600; }
.form-msg.ok  { color: var(--ok-text); }
.form-msg.bad { color: var(--bad); }
.form-msg.dim { color: var(--dim); }
/* 채움 버튼에 .danger 를 붙이면 바탕이 그대로 파란색이라 빨간 글씨가 얹혀
   읽히지 않았다. 채움은 빨강으로, 외곽선(ghost)은 빨간 글씨로 가른다. */
.btn.danger { background: var(--bad-fill); border-color: var(--bad-fill); color: #fff; }
.btn.ghost.danger { background: var(--surface); color: var(--bad); border-color: var(--bad); }
.btn.ghost.danger:hover { background: var(--bad-bg); }
.btn.full { width: 100%; justify-content: center; }
.tbl td.right { text-align: right; white-space: nowrap; }
.tbl td.right .btn + .btn { margin-left: var(--sp-2); }

/* ===========================================================================
   상단바 사용자 — 누구로 들어와 있는지와 나가는 길
   사이드바 맨 아래에 뒀더니 접었을 때와 폰(아래 탭바)에서 보이지 않았다.
   상단바는 어느 폭에서도 늘 보인다.
   =========================================================================== */
.side-util { margin-top: var(--sp-2); padding-top: var(--sp-3);
             border-top: 1px solid rgba(255, 255, 255, .09); }

.topbar .topuser {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  flex: 0 0 auto; padding-left: var(--sp-3);
  border-left: 1px solid rgba(255, 255, 255, .14);
}
.topbar .avatar {
  width: 28px; height: 28px; border-radius: 50%; flex: none;
  background: var(--accent); color: #fff; display: grid; place-items: center;
  font-weight: 800; font-size: var(--fs-2);
}
.topbar .tu-t { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
.topbar .tu-t b { color: var(--nav-ink); font-size: var(--fs-3); font-weight: 700;
                  white-space: nowrap; }
.topbar .tu-t span { color: var(--nav-dim); font-size: var(--fs-1); white-space: nowrap; }

.topbar .tu-form { display: flex; flex: 0 0 auto; }
.topbar .tu-out {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: var(--r-sm);
  background: rgba(255, 255, 255, .08); color: var(--nav-ink);
  border: 0; cursor: pointer;
}
.topbar .tu-out:hover { background: rgba(255, 255, 255, .18); }
.topbar .tu-out .ic { width: 17px; height: 17px; }


/* ===========================================================================
   큰 화면 모드 — 벽걸이 모니터에서 멀리서 읽는다
   =========================================================================== */
body.kiosk { --fs-3: 15px; --fs-4: 17px; --fs-5: 21px; --fs-6: 28px; --fs-7: 40px; }
body.kiosk .side { font-size: var(--fs-4); }
body.kiosk .page-desc, body.kiosk .sec-note { display: none; }

/* 사이드바가 접혀 있으면 하위 메뉴를 볼 수 없다. 본문 위에 가로줄로 낸다. */
.nav-min .set-tabs {
  display: flex; gap: var(--sp-2); overflow-x: auto;
  margin-bottom: var(--sp-4); padding-bottom: var(--sp-2);
  -webkit-overflow-scrolling: touch;
}

@media (max-width: 860px) {
  /* 아래 탭바에 설정까지 같이 놓는다. 로그아웃은 설정 안에 있다. */
  .side-util {
    margin: 0; padding: var(--sp-1); border: 0;
    flex: 0 0 68px;              /* 한 칸만 차지한다 — 주 메뉴를 밀지 않게 */
    border-left: 1px solid rgba(255, 255, 255, .09);
  }
  .side-util .nav-group { min-height: 52px; width: 100%;
    flex-direction: column; gap: 2px; padding: var(--sp-1) 2px;
    font-size: var(--fs-1); justify-content: center; }
  .side-util .nav-group .caret { display: none; }
  .side-util.is-open .side-sub { display: none; }   /* 아래 탭바에는 펼칠 자리가 없다 */
  .set-tabs { display: flex; gap: var(--sp-2); overflow-x: auto;
              margin-bottom: var(--sp-4); padding-bottom: var(--sp-2); }
  .topbar .navtoggle { display: none; }
  /* 상단바가 좁다. 이름·역할 글자는 빼고 동그라미와 나가기만 남긴다 */
  .topbar .tu-t { display: none; }
  .topbar .topuser { border-left: 0; padding-left: 0; }
}
.set-out { margin-top: var(--sp-4); }
.set-out .btn .ic { width: 15px; height: 15px; }

@media (max-width: 620px) {
  .form-row { grid-template-columns: 1fr; gap: var(--sp-1); }
  .form-row .hint { grid-column: 1; }
  .form-act { padding-left: 0; }
  .kv { grid-template-columns: 1fr; gap: 0; }
  .kv dt { margin-top: var(--sp-3); }
}

/* ---------- 걸러내기 줄 (추이 화면) ----------
   격자로 폭을 똑같이 나누면 기간 버튼 네 개가 두 줄로 접힌다. */
.filters {
  display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-4);
  align-items: end;
}
.filters .f-keep { flex: 0 0 auto; }
.filters .f-grow { flex: 1 1 180px; min-width: 150px; }
@media (max-width: 620px) {
  .filters { gap: var(--sp-3); }
  .filters .f-grow, .filters .f-keep { flex: 1 1 100%; }
  .filters .f-keep .btn { width: 100%; }
}

/* 영상을 못 받았을 때. 검은 칸만 남기면 '꺼진 카메라' 와 구분이 안 된다. */
.cam-view .cam-none {
  position: absolute; inset: 0; display: grid; place-items: center;
  padding: var(--sp-4); text-align: center;
  color: var(--nav-dim); font-size: var(--fs-3); line-height: 1.5;
}

/* ===========================================================================
   누르는 칸 — 폰에서 재어 보니 24~34px 짜리가 있었다.
   장갑 낀 손으로 누르는 현장이라 44px(--touch)을 지켜야 한다.
   =========================================================================== */
@media (max-width: 860px) {
  /* 상단바 '알림 N건' 은 링크다. 24px 짜리 배지를 손으로 누르기 어렵다. */
  .topbar a.badge {
    min-height: 40px; padding: 0 var(--sp-3); font-size: var(--fs-3);
  }
  .topbar .gear, .topbar .tu-out { width: 40px; height: 40px; }
  /* 카드 안 작은 버튼(전체화면 등)도 한 단 키운다 */
  .btn.sm { min-height: 40px; padding: 0 var(--sp-4); }
  /* 체크박스는 네모 자체를 키우고, 글자까지 포함한 줄 전체가 눌리게 한다 */
  .form-check .chk { min-height: 44px; }
  .form-check input { width: 22px; height: 22px; }
}

/* ===========================================================================
   배치도 — 컨테이너 평면
   가로 비율을 도면과 같게 둔다 (재배실 5,000 : 설비실 2,000 : 재배실 5,000).
   =========================================================================== */
.plan {
  display: grid; gap: var(--sp-3);
  grid-template-columns: 5fr 2.4fr 5fr;
  align-items: start;
}
.plan.zones-2 { grid-template-columns: 5fr 2.4fr; }
.plan.zones-1 { grid-template-columns: 1fr; }

.pz {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); box-shadow: var(--sh-1);
  min-width: 0; overflow: hidden;
}
.pz-head {
  display: flex; align-items: baseline; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  background: var(--nav); color: var(--nav-ink);
}
.pz-t { font-size: var(--fs-4); font-weight: 800; }
.pz-sub { font-size: var(--fs-2); color: var(--nav-dim); }
.pz-envs {
  display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4);
  padding: var(--sp-2) var(--sp-4);
  border-bottom: 1px solid var(--line-2); background: var(--surface-2);
  font-size: var(--fs-3); font-variant-numeric: tabular-nums;
}
.pz-env b { color: var(--dim); font-size: var(--fs-2); font-weight: 700; margin-right: 4px; }
.pz-env .off, .pm .off { color: var(--off); }
/* 재배실 바닥 — 도면 그대로 [모듈 열][중앙 작업통로][모듈 열].
   통로 폭은 도면의 1,500mm 을 모듈 폭에 견줘 잡았다. */
.pz-floor {
  display: grid; grid-template-columns: minmax(0, 1fr) 104px minmax(0, 1fr);
  gap: var(--sp-2); padding: var(--sp-3);
}
.pz-col { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }

.pz-aisle {
  display: flex; flex-direction: column; gap: var(--sp-2);
  align-items: stretch; padding: var(--sp-2) 4px;
  border-left: 1px dashed var(--line); border-right: 1px dashed var(--line);
  background: repeating-linear-gradient(135deg,
      var(--surface-2) 0 6px, transparent 6px 12px);
}
.pz-aisle-t {
  display: flex; flex-direction: column; align-items: center;
  font-size: var(--fs-1); color: var(--dim); font-weight: 700; line-height: 1.3;
  text-align: center;
}
.pz-aisle-t span { color: var(--off); font-weight: 600; }

/* 통로에 놓인 양액기 — 좁은 칸이라 흘깃 보고 알 것만 담는다 */
.pa {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: var(--sp-2) 4px;
  text-align: center; cursor: pointer; font: inherit; color: var(--ink);
  transition: border-color var(--dur), background var(--dur);
}
.pa:hover { border-color: var(--accent); }
.pa.is-sel { border-color: var(--accent); background: var(--accent-bg); }
.pa.is-off { opacity: .66; }
.pa-top { display: flex; align-items: center; gap: 3px; }
.pa-ic { color: var(--c-soil); display: inline-flex; }
.pa-ic .ic { width: 15px; height: 15px; }
.pa-n { font-size: var(--fs-1); font-weight: 800; }

/* 상태 — 색이 먼저 읽힌다 */
.pa-st {
  display: block; width: 100%; border-radius: var(--r-pill);
  padding: 1px 4px; font-size: var(--fs-1); font-weight: 800;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pa-st.ok  { background: var(--ok-bg);  color: var(--ok-text); }
.pa-st.bad { background: var(--bad-bg); color: var(--bad); }
.pa-st.off { background: var(--off-bg); color: var(--off); }

.pa-v {
  font-size: var(--fs-2); color: var(--ink-2); font-weight: 700;
  font-variant-numeric: tabular-nums; line-height: 1.35;
}
.pa-v.off { color: var(--off); font-weight: 600; font-size: var(--fs-1); }

/* 펌프 네 칸 — 도는 것만 채운다. 숫자는 눌러서 본다 */
.pa-bars { display: flex; gap: 2px; }
.pa-b {
  width: 14px; height: 6px; border-radius: 2px;
  background: var(--line); display: inline-block;
}
.pa-b.on { background: var(--ok); }
.pa-mode { font-size: var(--fs-1); color: var(--dim); font-weight: 700; }

/* ---- 모듈 한 칸 ---- */
.pm {
  display: flex; flex-direction: column; gap: var(--sp-2);
  text-align: left; font: inherit; cursor: pointer;
  background: var(--surface-2); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: var(--sp-2) var(--sp-3);
  transition: border-color var(--dur), background var(--dur);
}
.pm:hover { border-color: var(--accent); }
.pm.is-sel { border-color: var(--accent); background: var(--accent-bg); }
.pm.is-off { opacity: .72; }
.pm.is-part { border-left: 3px solid var(--warn-fill); }
.pm-head { display: flex; align-items: center; gap: var(--sp-2); }
.pm-no {
  width: 20px; height: 20px; flex: none; display: grid; place-items: center;
  font-size: var(--fs-3); font-weight: 800; color: var(--ok-text);
}
.pm-name { font-size: var(--fs-3); font-weight: 800; }
.pm-body { display: flex; flex-direction: column; gap: 2px; }
.pm-tier {
  display: grid; grid-template-columns: 34px auto 1fr 22px 1fr;
  gap: var(--sp-2); align-items: center;
  padding: 3px 0; border-top: 1px dashed var(--line-2);
  font-size: var(--fs-2); font-variant-numeric: tabular-nums;
}
.pm-tname { color: var(--dim); font-weight: 700; }
.pm-bulbs { display: inline-flex; gap: 1px; color: var(--off); }
.pm-bulbs .ic { width: 12px; height: 12px; }
.pm-bulbs.on { color: var(--c-light); }
.pm-v { font-size: var(--fs-3); font-weight: 700; }
.pm-v .u { font-size: var(--fs-1); color: var(--dim); font-weight: 700; }
.pm-chip {
  display: inline-grid; place-items: center; width: 20px; height: 20px;
  border-radius: 5px; background: var(--off-bg); color: var(--off);
}
.pm-chip .ic { width: 13px; height: 13px; }
.pm-chip.on { background: var(--ok-bg); color: var(--ok-text); }
.pm-foot {
  display: flex; align-items: center; gap: var(--sp-2);
  border-top: 1px solid var(--line-2); padding-top: var(--sp-2);
}
.pm-ftext { font-size: var(--fs-2); color: var(--dim); }
.pm-empty { font-size: var(--fs-2); color: var(--dim); padding: var(--sp-2) 0; }

/* ---- 설비실 ---- */
.pz-plant .pz-head { background: #243247; }
.pp { padding: var(--sp-2) var(--sp-3); }
.pp-row {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) 0; border-bottom: 1px solid var(--line-2);
  font-size: var(--fs-3);
}
.pp-row:last-child { border-bottom: 0; }
.pp-row.is-off { opacity: .66; }
.pp-ic { color: var(--c-soil); display: inline-flex; }
.pp-ic .ic { width: 18px; height: 18px; }
.pp-n { font-weight: 700; display: flex; flex-direction: column; min-width: 0; }
.pp-r { font-size: var(--fs-1); color: var(--dim); font-weight: 600; }
.pp-v { font-size: var(--fs-2); color: var(--ink-2); font-variant-numeric: tabular-nums; }
.pp-fixed {
  display: flex; flex-direction: column; gap: var(--sp-1);
  padding: var(--sp-3); border-top: 1px solid var(--line-2);
  background: var(--surface-2);
}
.pp-f {
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-2); color: var(--dim); font-weight: 700;
}
.pp-f .ic { width: 15px; height: 15px; }

/* '가동' 보기 — 켜진 것만 색으로 남기고 나머지는 죽인다 */
.plan.v-fill .pm:not(.is-off) { background: var(--ok-bg); }
.plan.v-fill .pm.is-off { background: var(--surface-2); }

@media (max-width: 1100px) {
  .plan, .plan.zones-2 { grid-template-columns: 1fr; }
}
@media (max-width: 760px) {
  /* 통로를 세로로 세울 자리가 없다. 모듈 아래로 내리고 가로로 눕힌다. */
  .pz-floor { grid-template-columns: 1fr; }
  .pz-aisle {
    flex-direction: row; align-items: center; flex-wrap: wrap;
    border: 1px dashed var(--line); border-radius: var(--r-sm);
    padding: var(--sp-2);
  }
  .pz-aisle-t { flex-direction: row; gap: var(--sp-1); }
  .pa { flex-direction: row; flex-wrap: wrap; gap: var(--sp-2);
        flex: 1 1 170px; text-align: left; justify-content: flex-start; }
  .pa-st { width: auto; }
  .pa-v { line-height: 1.2; }
}
@media (max-width: 620px) {
  .pz-floor { padding: var(--sp-2); }
  .pm-tier { grid-template-columns: 30px auto 1fr 20px 1fr; }
}
/* config 가 부르는 이름(선반 1)은 작게 곁들인다 — 현장 설정과 배치도 용어를
   둘 다 보여야 어느 모듈인지 헷갈리지 않는다. */
.pm-alias { font-size: var(--fs-1); color: var(--dim); font-weight: 600; }
.pm-small { font-size: var(--fs-2); font-weight: 700; color: var(--ink-2); }

/* ===========================================================================
   세로 배치 (현장 키오스크)

   현장 키오스크 모니터가 세로로 서 있다. 가로 3단(재배실–설비실–재배실)을
   그대로 두면 칸이 눌려 읽을 수 없다. 세로에서는 위에서 아래로 쌓는다.

   .lay-tall / .lay-wide 는 ui.js 가 <html> 에 붙인다 — '자동' 이면 화면
   방향을 따르고, 고정이면 사람이 고른 쪽을 쓴다. 기기마다 다르게 둘 수
   있어야 해서(현장 키오스크는 세로, 원격은 가로) CSS 의 방향 질의만으로는
   모자란다. 중첩 문법은 쓰지 않는다 — 현장 브라우저가 오래된 경우가 있다.
   =========================================================================== */
.lay-tall .plan,
.lay-tall .plan.zones-2,
.lay-tall .plan.zones-1 { grid-template-columns: 1fr; }

/* 세로에서는 높이가 넉넉하다. 재배실 안은 [모듈|통로|모듈] 을 지킨다 —
   도면과 같은 모양이라야 현장에서 어느 모듈인지 바로 짚는다.
   (폭이 760 밑으로 내려가면 아래의 폭 규칙이 통로를 아래로 내린다) */
.lay-tall .pz-floor { grid-template-columns: minmax(0, 1fr) 112px minmax(0, 1fr); }

/* 설비실은 센서가 없어 읽을 것이 적다. 세로로 길게 뽑지 말고 가로로 눕힌다. */
.lay-tall .pz-plant .pp-fixed {
  flex-direction: row; flex-wrap: wrap; gap: var(--sp-2) var(--sp-5);
}

/* ---- 세로 + 큰 화면 모드 = 벽에 세워 둔 현장 키오스크 ----
   멀리서 읽는 화면이라 글자와 칸을 한 단 더 키우고, 장식은 뺀다. */
.lay-tall body.kiosk {
  --fs-1: 13px; --fs-2: 15px; --fs-3: 17px; --fs-4: 19px;
  --fs-5: 24px; --fs-6: 34px; --fs-7: 48px;
}
.lay-tall body.kiosk .pz-head { padding: var(--sp-4) var(--sp-5); }
.lay-tall body.kiosk .pz-envs { padding: var(--sp-3) var(--sp-5); gap: var(--sp-3) var(--sp-6); }
.lay-tall body.kiosk .pm { padding: var(--sp-3) var(--sp-4); }
.lay-tall body.kiosk .pm-tier { padding: 5px 0; }
.lay-tall body.kiosk .notebar { font-size: var(--fs-3); }
/* 키오스크는 보는 화면이다. 안내 배너는 자리만 먹는다. */
.lay-tall body.kiosk .notebar.dim { display: none; }

/* 배치도가 화면을 채우게. 벽에 세운 화면에서 아래 40% 가 비어 있으면
   멀리서 읽을 글자만 작아진다. 세 칸이 높이를 나눠 갖는다.
   height:100% 를 빼먹었더니 행만 늘고 카드는 그대로여서 사이가 벌어졌다. */
.lay-tall body.kiosk .plan {
  min-height: calc(100dvh - var(--bar-h) - 120px);
  grid-template-rows: 1fr auto 1fr;
  align-content: stretch;
}
.lay-tall body.kiosk .pz { display: flex; flex-direction: column; height: 100%; }
.lay-tall body.kiosk .pz-floor { flex: 1 1 auto; align-items: stretch; }
.lay-tall body.kiosk .pz-col { height: 100%; }
.lay-tall body.kiosk .pm { flex: 1 1 0; justify-content: space-between; }
.lay-tall body.kiosk .pz-aisle { justify-content: flex-start; }

/* 가로 고정 — 세로 모니터에서도 가로 배치를 쓰고 싶을 때.
   폭이 모자라면 아래의 폭 규칙이 알아서 한 줄로 떨어뜨린다. */
.lay-wide .plan { grid-template-columns: 5fr 2.4fr 5fr; }
.lay-wide .plan.zones-2 { grid-template-columns: 5fr 2.4fr; }
.lay-wide .plan.zones-1 { grid-template-columns: 1fr; }

/* ===========================================================================
   카메라 한 대만 크게 보기
   전체화면 API 가 막혀 있어도 화면 안에서 그 카메라만 꽉 차게 보인다.
   =========================================================================== */
.cam-full {
  position: fixed; inset: 0; z-index: 60;
  display: flex; flex-direction: column;
  background: #05080c;
}
.cam-full[hidden] { display: none !important; }
body.cam-open { overflow: hidden; }      /* 뒤 화면이 같이 스크롤되지 않게 */

.cf-bar {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4); min-height: 52px;
  background: var(--nav); color: var(--nav-ink); flex: none;
}
.cf-name { font-size: var(--fs-5); font-weight: 800; }
.cf-ts {
  font-size: var(--fs-3); color: var(--nav-dim);
  font-variant-numeric: tabular-nums;
}
.cf-hint { font-size: var(--fs-2); color: var(--nav-dim); }
.cf-bar .btn { background: rgba(255, 255, 255, .1); color: var(--nav-ink);
               border-color: rgba(255, 255, 255, .2); }

.cf-view {
  flex: 1 1 auto; position: relative; min-height: 0;
  display: grid; place-items: center; background: #05080c;
}
/* 잘라내지 않고 비율을 지켜 채운다 — 한 대만 볼 때는 화면 밖으로 넘기면 안 된다 */
.cf-view img { max-width: 100%; max-height: 100%; object-fit: contain; display: block; }
.cf-note {
  position: absolute; inset: 0; display: grid; place-items: center;
  color: #64748b; font-size: var(--fs-4); text-align: center; padding: var(--sp-5);
}
.cf-note[hidden] { display: none !important; }

@media (max-width: 620px) {
  .cf-bar { padding: var(--sp-2) var(--sp-3); gap: var(--sp-2); }
  .cf-hint { display: none; }          /* 폰에는 Esc 키가 없다 */
  .cf-name { font-size: var(--fs-4); }
}

/* ===========================================================================
   양액기 카드
   =========================================================================== */
/* 양액기는 볼 것이 많아 한 칸(210px)에 넣으면 숫자가 서로 붙는다.
   격자에서 두 칸을 쓰게 한다. */
.g-ctl .nut { grid-column: span 2; }
.nut { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
.nut .errbar.nut-fault { margin: 0; padding: var(--sp-2) var(--sp-3); font-size: var(--fs-2); }
.nut-vals {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: var(--sp-2) var(--sp-4);
}
.nut-vals > div { min-width: 0; }
.nut-vals .ctl-val { font-size: var(--fs-6); white-space: nowrap; }
.nut-vals .ctl-sub { margin-top: 0; }

/* 펌프 4개 — 도는 것만 색이 들어온다 */
.nut-pumps { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-1); }
.nut-p {
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  padding: var(--sp-1) 2px; border-radius: var(--r-sm);
  background: var(--off-bg); color: var(--off);
  font-size: var(--fs-1); line-height: 1.3; text-align: center;
}
.nut-p b { font-size: var(--fs-2); font-weight: 800; }
.nut-p.on { background: var(--ok-bg); color: var(--ok-text); }

.nut-act {
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  padding-top: var(--sp-2); border-top: 1px solid var(--line-2);
}
.nut-act .lb { margin: 0; white-space: nowrap; }
.nut-pair { display: inline-flex; align-items: center; gap: var(--sp-2); }
.field.sm { min-height: 36px; width: 84px; padding: 0 var(--sp-2); }
.nut-note { color: var(--dim); font-size: var(--fs-1); }
.nut-msg { min-height: 16px; font-weight: 700; }

@media (max-width: 620px) {
  .nut-vals { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .nut-act .btn { flex: 1 1 auto; }
}
/* 상태 띠 — 뜻이 나쁜 상태(고장·비상정지·리모트정지·제상)는 경보색으로 */
.chart rect.lane-on.bad { fill: var(--bad-fill); }

/* 범례를 눌러 계열을 고른다 */
.legend .it {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  background: none; border: 1px solid transparent; border-radius: var(--r-pill);
  padding: 2px var(--sp-2); font: inherit; font-size: var(--fs-2);
  color: var(--dim); cursor: pointer; min-height: 28px;
}
.legend .it:hover { background: var(--surface-2); }
.legend .it.on { border-color: var(--accent); background: var(--accent-bg);
                 color: var(--ink); font-weight: 700; }
.legend .it.dim { opacity: .45; }
.legend-hint { margin-left: var(--sp-2); }

/* 작은 배수 칸도 눌러 고른다 */
.sm-one { cursor: pointer; transition: border-color var(--dur), opacity var(--dur); }
.sm-one:hover { border-color: var(--accent); }
.sm-one.on { border-color: var(--accent); background: var(--accent-bg); }
.sm-one.dim { opacity: .45; }

/* 표 머리 — 어느 계열의 수치인지 먼저 말한다 */
.tbl-head {
  display: flex; align-items: baseline; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4) 0;
}
.tbl-head b { font-size: var(--fs-4); font-weight: 800; }

/* ===========================================================================
   종합 추이 — 타일 한 장에 '지금 · 추세 · 폭'
   지표마다 큰 그래프를 쌓으면 농장 상태를 보려고 열두 번 스크롤해야 한다.
   =========================================================================== */
.ov-grid {
  display: grid; gap: var(--sp-2); padding: var(--sp-3);
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.ov {
  display: flex; flex-direction: column; gap: 2px; min-width: 0;
  text-align: left; font: inherit; color: var(--ink); cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: var(--sp-2) var(--sp-3);
  transition: border-color var(--dur), background var(--dur);
}
.ov:hover { border-color: var(--accent); background: var(--surface); }
.ov-h { font-size: var(--fs-3); font-weight: 800; }
.ov-w { font-size: var(--fs-1); color: var(--dim); white-space: nowrap;
        overflow: hidden; text-overflow: ellipsis; }
.ov-v {
  font-size: var(--fs-6); font-weight: 800; line-height: 1.1;
  font-variant-numeric: tabular-nums; margin-top: 2px;
}
.ov-v .u { font-size: var(--fs-3); color: var(--dim); font-weight: 700; margin-left: 2px; }
.ov-v.off { font-size: var(--fs-4); color: var(--off); }
.ov-k { margin: 2px 0; }
.ov-k .spark { width: 100%; height: 34px; display: block; }
.ov-s { font-size: var(--fs-1); color: var(--ink-2); font-variant-numeric: tabular-nums; }
.ov-s.off { color: var(--off); }
.ov-s .sec-note { margin-left: var(--sp-2); }

@media (max-width: 620px) {
  .ov-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); padding: var(--sp-2); }
  .ov-v { font-size: var(--fs-5); }
}
/* 종합 타일을 재배실별로 묶는다 — 43칸을 그냥 늘어놓으면 어디가 어딘지 모른다 */
.ov-group {
  grid-column: 1 / -1; display: flex; align-items: baseline; gap: var(--sp-2);
  margin-top: var(--sp-2); padding-bottom: 2px;
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-3); font-weight: 800; color: var(--ink-2);
}
.ov-group:first-child { margin-top: 0; }
/* 로그인 유지 */
.login-keep { display: flex; align-items: center; gap: var(--sp-2);
              margin-top: var(--sp-4); font-size: var(--fs-3); color: var(--ink-2); }
.login-keep input { width: 18px; height: 18px; accent-color: var(--accent); }
/* 장비 고르기 칩 줄 */
.picks {
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  margin-top: var(--sp-3); padding-top: var(--sp-3);
  border-top: 1px solid var(--line-2);
}
.picks[hidden] { display: none !important; }
.picks .lb { margin: 0; }
.picks .chip { min-height: 34px; padding: 0 var(--sp-3); font-size: var(--fs-2); }

/* 설비 카드 조작 결과 한 줄 */
.ctl-msg { min-height: 1.2em; font-size: var(--fs-3); color: var(--text-2, #8a96a8); margin-top: var(--sp-1, 4px); }
.ctl-msg.bad { color: var(--bad, #d9534f); }
.card.ctl .ctl-row { display: flex; gap: var(--sp-2, 8px); align-items: center; margin-top: var(--sp-2, 8px); }

/* 시간표 */
.sch-card { margin-bottom: var(--sp-3, 12px); }
.sch-grid { display: flex; flex-wrap: wrap; gap: var(--sp-3, 12px); margin-top: var(--sp-3, 12px); }
.sch-f { display: flex; flex-direction: column; gap: 4px; font-size: var(--fs-3); }
.sch-f > span { color: var(--text-2, #8a96a8); }
.sch-f.wide .field { min-width: 220px; }
.sec-note .bad, .bad { color: var(--bad, #d9534f); }
