/* ============================================================
   1. 변수
   ============================================================ */
:root {
  --color-bg: #f4f6f8;
  --color-card: #ffffff;
  --color-primary: #2e4099;
  --color-primary-dark: #1f2c6e;
  --color-primary-tint: #e8ecfa;
  --color-danger: #c0392b;
  --color-danger-tint: #fdecea;
  --color-success: #2e7d32;
  --color-success-tint: #e6f4ea;
  --color-warning: #b45309;
  --color-warning-tint: #fef3e2;
  /* 장비 구분(도로). 상태가 아니라 분류라 경고·성공 계열과 겹치지 않는 청록을 쓴다 —
     지상은 기본색(--color-primary)이 맡는다. */
  --color-road: #0f766e;
  --color-road-tint: #dcf2ef;
  /* 원시자료의 '미수집'(그 시각 행이 안 옴). 글자가 비슷한 '미수신'(주황)과 나란히 나오므로
     회색이 아니라 확실히 다른 색을 준다 — 도로 청록보다 푸른 시안이라 분류색과도 겹치지 않는다. */
  --color-nodata: #0891b2;
  --color-nodata-tint: #e0f4f8;
  /* 이력조회 달력의 '청소' 점. 정기(초록)·수시(주황)·장애(빨강)·원격요청(회색)과 갈리는 보라. */
  --color-cheongso: #6d5bd0;
  --color-text: #222831;
  --color-muted: #6b7280;
  --color-border: #e2e5ea;
  --sidebar-width: 220px;
  --header-height: 56px;
  --radius: 8px;
  --radius-sm: 6px;
}

/* ============================================================
   2. 기본
   ============================================================ */
* { box-sizing: border-box; }

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

[hidden] { display: none !important; }

.icon { vertical-align: -3px; }

/* ============================================================
   3. 레이아웃: 사이드바 + 헤더 + 콘텐츠
   ============================================================ */
.sidebar {
  position: fixed;
  top: 0; left: 0; bottom: 0;
  width: var(--sidebar-width);
  background: var(--color-card);
  border-right: 1px solid var(--color-border);
  display: flex;
  flex-direction: column;
  z-index: 40;
  transition: transform 0.2s ease;
}
.sidebar-brand {
  display: block;
  padding: 18px 20px 14px;
  text-decoration: none;
  border-bottom: 1px solid var(--color-border);
}
.sidebar-brand-title { display: block; font-size: 16px; font-weight: 700; color: var(--color-primary); }
.sidebar-brand-sub { display: block; font-size: 11px; color: var(--color-muted); margin-top: 2px; }
.sidebar-nav { flex: 1; overflow-y: auto; padding: 6px 0 10px; }
.side-group-label {
  font-size: 11px;
  color: var(--color-muted);
  padding: 14px 20px 4px;
}
.side-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  margin: 1px 10px;
  border-radius: var(--radius-sm);
  font-size: 14px;
  color: var(--color-text);
  text-decoration: none;
}
.side-item .icon { flex-shrink: 0; opacity: 0.75; }
.side-item:hover { background: var(--color-bg); }
.side-item.active {
  background: var(--color-primary-tint);
  color: var(--color-primary);
  font-weight: 600;
}
.side-item.active .icon { opacity: 1; }
.sidebar-footer {
  border-top: 1px solid var(--color-border);
  padding: 12px 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 12px;
  color: var(--color-muted);
}
.sidebar-user { display: flex; align-items: center; gap: 6px; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.sidebar-logout-form { margin: 0; }
.sidebar-logout-btn {
  display: flex;
  align-items: center;
  gap: 5px;
  background: none;
  border: none;
  padding: 4px;
  color: var(--color-muted);
  font-size: 12px;
  font-family: inherit;
  cursor: pointer;
}
.sidebar-logout-btn:hover { color: var(--color-danger); }

.sidebar-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.35);
  z-index: 35;
}

.main-area {
  margin-left: var(--sidebar-width);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.header {
  position: sticky;
  top: 0;
  height: var(--header-height);
  background: var(--color-card);
  border-bottom: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 20px;
  z-index: 20;
}
.menu-toggle {
  display: none;
  background: none;
  border: none;
  padding: 6px;
  color: var(--color-text);
  cursor: pointer;
  line-height: 0;
}
.breadcrumb {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--color-muted);
  flex: 1;
  min-width: 0;
}
.breadcrumb a { color: var(--color-muted); text-decoration: none; white-space: nowrap; }
/* 중간 마디도 링크와 같이 한 줄로. 접히면 헤더가 두 줄이 된다 — 폭이 모자랄 때는
   맨 끝(.crumb-current)이 말줄임으로 흡수한다. */
.breadcrumb > span { white-space: nowrap; }
.breadcrumb a:hover { color: var(--color-primary); }
.breadcrumb > * + *::before { content: "›"; margin-right: 8px; color: #b6bcc8; }
.crumb-current {
  color: var(--color-text);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.header-user { font-size: 12px; color: var(--color-muted); white-space: nowrap; display: flex; align-items: center; gap: 12px; }
.session-timer {
  font-variant-numeric: tabular-nums;
  color: var(--color-muted);
  padding: 2px 8px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}
.session-timer.session-timer-warn { color: var(--color-danger); border-color: var(--color-danger); }

.content {
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px 24px;
}

/* 넓은 모니터(1400px+): 본문을 화면 폭에 꽉 채운다.
   1200px 고정폭은 1920 화면에서 좌우 250px씩을 버리고, 전폭인 헤더와 본문 시작선도 어긋난다.
   다만 전폭이 되면 "한 줄짜리 값"들이 그만큼 늘어나 읽기 나빠지므로 — 세로 폼의 입력창,
   키/값 표, 검색창 — 이 셋만 개별 상한을 준다. 표·카드그리드·차트는 폭이 늘수록 이득이라 그대로 둔다.
   1399px 이하(노트북·태블릿·모바일)에는 아무 영향이 없다. */
@media (min-width: 1400px) {
  .content { max-width: none; padding: 22px 28px; }
  /* .stack-form-row 는 가로 배치(수집 폼 등)라 좁히면 오히려 줄이 접힌다 — 세로 폼만 제한. */
  .stack-form:not(.stack-form-row) { max-width: 720px; }
  /* ★ 글쓰기 폼만 예외다. 마크다운 편집기는 '한 줄짜리 값'이 아니라 위 주석이 말하는
     표·차트 쪽에 가깝다 — 좌우로 나눠 쓰는 자리라 720px 안에서는 양쪽 다 290px 밖에
     못 받는다. 폼의 상한은 풀되, 그 안의 한 줄짜리 칸들은 그대로 720px 로 붙든다. */
  .post-form:not(.stack-form-row) { max-width: 1180px; }
  .post-form > label,
  .post-form > .form-hint,
  .post-form > .form-notice,
  .post-form > .post-writer-hint,
  .post-form > .post-file-edit,
  .post-form > .inline-check { max-width: 720px; }
  .detail-table { max-width: 940px; }
  .search-bar { max-width: 720px; }
}

@media (max-width: 959px) {
  .sidebar { transform: translateX(-100%); }
  body.sidebar-open .sidebar { transform: none; box-shadow: 0 0 24px rgba(0, 0, 0, 0.18); }
  .main-area { margin-left: 0; }
  .menu-toggle { display: inline-flex; }
  .header { padding: 0 12px; }
  .content { padding: 14px; }
  .data-table { min-width: 560px; }
}

/* ============================================================
   4. 공통 컴포넌트
   ============================================================ */

/* --- 버튼 (배지와 같은 은은한 틴트 팔레트) --- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 10px 18px;
  border-radius: 10px;
  border: 1px solid transparent;
  background: var(--color-primary-tint);
  color: var(--color-primary);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.1px;
  text-decoration: none;
  cursor: pointer;
  font-family: inherit;
  white-space: nowrap;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
  transition: background-color .15s ease, box-shadow .15s ease, transform .05s ease, border-color .15s ease;
}
.btn:hover { background: #dbe2f7; box-shadow: 0 2px 6px rgba(16, 24, 40, 0.08); }
.btn:active { transform: translateY(1px); box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06); }
.btn:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.btn:disabled { opacity: .55; cursor: not-allowed; box-shadow: none; transform: none; }

/* 눌러서 몇 초가 걸리는 단추. 글자만 바뀌면 눌렸는지조차 잘 안 보인다 — 도는 동그라미를
   앞에 붙여 '지금 일하고 있다'를 눈에 보이게 한다.
   ★ :disabled 뒤에 둔다. 잠긴 단추의 흐림(.55)까지 겹치면 도는 것이 잘 안 보인다. */
.btn.is-busy { cursor: progress; opacity: .9; }
.btn.is-busy::before {
  content: "";
  flex: 0 0 auto;
  width: 11px;
  height: 11px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: btn-spin .7s linear infinite;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }
/* 움직임을 줄여 달라고 한 사람에게는 천천히 돌린다 — 멈춰 세우면 '도는 중'이라는 신호가
   통째로 사라져 원래 문제로 돌아간다. */
@media (prefers-reduced-motion: reduce) {
  .btn.is-busy::before { animation-duration: 2.4s; }
}

.btn-on { background: var(--color-success-tint); color: var(--color-success); }
.btn-on:hover { background: #d2ecd8; }

.btn-off { background: var(--color-danger-tint); color: var(--color-danger); }
.btn-off:hover { background: #fbdad6; }

.btn-off-text {
  background: var(--color-danger-tint);
  color: var(--color-danger);
  border: 1px solid transparent;
  box-shadow: none;
}
.btn-off-text:hover { background: #fbdad6; box-shadow: none; }

.btn-sm { padding: 3px 12px; font-size: 12px; border-radius: 6px; }

/* 센서명/소모품명 추가 폼 — 입력창이 남는 폭을 모두 채우고 '추가' 버튼이 바로 옆에 붙도록 */
.type-add-form { flex-wrap: nowrap; align-items: flex-end; }
.type-add-form > label { flex: 1 1 auto; min-width: 0; }
.type-add-form > .btn { flex: 0 0 auto; margin-right: 8px; }

.type-manage-table { margin-top: 12px; }
.type-manage-table td { padding: 3px 4px; }
.type-manage-table td:first-child { width: 100%; }
.type-manage-table td:last-child { text-align: right; padding-right: 8px; white-space: nowrap; }
.type-manage-table form { margin: 0; display: inline-block; }

.form-actions { display: flex; gap: 8px; }
.form-actions .btn { flex: 1; text-align: center; }

/* --- 배지 (인라인 JS가 .badge/.badge-danger 문자열을 렌더링하므로 클래스명 유지) --- */
.badge {
  display: inline-block;
  background: var(--color-primary-tint);
  color: var(--color-primary);
  font-size: 11px;
  font-weight: 600;
  padding: 2px 9px;
  border-radius: 999px;
  margin-left: 4px;
  white-space: nowrap;
}
.data-table .badge { margin-left: 0; }
.badge-danger { background: var(--color-danger-tint); color: var(--color-danger); }
.badge-success { background: var(--color-success-tint); color: var(--color-success); }
.badge-warning { background: var(--color-warning-tint); color: var(--color-warning); }
.badge-purple { background: #f1e9fb; color: #6b3fa0; }
/* 외부진입 지점 표시 — 지점명 옆 작은 별. 알약 배지로 두면 장비·구분 배지와 섞여
   어느 것이 장비인지 한눈에 안 들어오고, 좌우 패딩(9px)만큼 이름과 사이가 벌어진다. */
.entry-star {
  margin-left: 1px;          /* 이름에 바짝 붙인다 — 별은 이름의 꼬리표지 따로 선 값이 아니다 */
  color: var(--color-warning);
  font-size: 11px;           /* ★ 글리프는 박스가 글꼴 크기만큼(12px→12px) 넓다 — 한 급 줄여 덜 튄다 */
  line-height: 1;
  vertical-align: 1px;       /* 기준선보다 살짝 낮게 앉아 이름 가운데에 오게 올린다 */
}

/* --- 원시자료조회 보유 기간 줄 (observations.html) ------------------
   '언제부터 볼 수 있나'를 화면 맨 위에 한 줄로. 조회 화면의 주인공은 아래 뷰어라
   카드로 세우지 않고 얇은 띠 하나로 둔다. */
.obs-keep {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 12px; margin-bottom: 10px;
  border: 1px solid var(--color-border); border-radius: var(--radius-sm);
  background: var(--color-card); font-size: 13px;
}
.obs-keep-k { color: var(--color-muted); }
.obs-keep-v { font-size: 14px; }
.obs-keep-sub { color: var(--color-muted); font-size: 12px; }
.obs-keep-edit { display: flex; align-items: center; gap: 6px; }
.obs-keep-edit input { width: 84px; }
.obs-keep-unit { color: var(--color-muted); }
.obs-keep-hint { color: var(--color-muted); font-size: 11px; }
.obs-keep-actions { margin-left: auto; display: flex; gap: 6px; }
.obs-keep-edit:not([hidden]) ~ .obs-keep-actions { margin-left: auto; }
.obs-keep-note {
  margin: -4px 0 12px; padding: 8px 12px; border-radius: var(--radius-sm);
  background: var(--color-warning-tint); color: var(--color-warning); font-size: 12px;
}

/* --- 계정관리 권한 매트릭스 (account.html) -------------------------
   계정 × 권한을 한 판에 놓아, 계정을 하나씩 열어보지 않고도 '누구한테 무엇이 열려 있나'를
   본다. 칸의 모양이 곧 상태다:
     기본       — 흰 칸(꺼짐) / 남색 칸(켜짐)
     is-auto    — 관리자라 자동으로 켜진 칸. 연한 남색이고 누를 수 없다(DB 값과 다를 수 있다).
     is-lock    — 잠긴 칸(마지막 관리자 · 비활성 계정). 흐리게 두되 ✓ 여부는 그대로 읽힌다.
     is-changed — 아직 저장하지 않은 칸. 칸 배경을 물들여 저장 줄의 숫자와 맞춰 본다. */
.data-table th.c-perm, .data-table td.c-perm { width: 58px; text-align: center; padding-left: 2px; padding-right: 2px; }
.data-table td.c-perm.is-changed { background: var(--color-warning-tint); }

.perm-cb {
  width: 20px; height: 20px; padding: 0;
  border: 1px solid var(--color-border);
  border-radius: 5px;
  background: var(--color-card);
  color: transparent;
  font-size: 12px; line-height: 1;
  cursor: pointer;
  transition: background-color .12s ease, border-color .12s ease;
}
.perm-cb:hover:not(:disabled) { border-color: var(--color-primary); }
.perm-cb.is-on { background: var(--color-primary); border-color: var(--color-primary); color: #fff; }
.perm-cb.is-auto { background: var(--color-primary-tint); border-color: var(--color-primary-tint); color: var(--color-primary); cursor: default; }
.perm-cb.is-lock { opacity: .45; cursor: not-allowed; }

.perm-legend { font-size: 11px; color: var(--color-muted); line-height: 1.8; margin: 10px 0 0; }
.perm-legend b { color: var(--color-text); font-weight: 600; }

.perm-bar {
  display: flex; align-items: center; gap: 10px;
  margin-top: 12px; padding: 10px 14px;
  border-radius: var(--radius);
  background: var(--color-warning-tint);
  color: var(--color-warning);
  font-size: 13px; font-weight: 600;
}
.perm-bar-actions { margin-left: auto; display: flex; gap: 8px; }

/* 지점조회 도로 열(노선·방향·이정표·지사)은 값이 짧아 접히면 오히려 읽기 나쁘다
   ('대관령지사'가 '대관령지/사'로 갈렸다). 폭을 조금 더 주고 줄바꿈을 막는다. */
.data-table th.c-road, .data-table td.c-road { white-space: nowrap; }

/* --- 도로 지점 기본정보 (지점조회 상세) ---
   짧은 값 넷(노선·방향·이정표·지사)은 맨 위 배지로 올리고, 줄이 긴 둘(차단방법·비고)만
   아래 카드로 낸다. 차단방법은 현장 안전과 직결돼 눈에 걸리는 색을 준다. */
.road-badges { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 12px; }
.road-badges .badge { margin-left: 0; font-size: 12px; padding: 3px 10px; }
.badge-road { background: var(--color-road-tint); color: var(--color-road); }

.road-head { display: flex; align-items: center; gap: 8px; margin: 18px 0 8px; }
.road-head h3 { margin: 0; font-size: 14px; color: var(--color-road); }
.road-head::after { content: ""; flex: 1; height: 1px; background: var(--color-border); }

.road-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.road-cell {
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
  min-width: 0;                      /* 긴 값이 격자를 밀어내지 않게 */
}
.road-cell.wide { grid-column: 1 / -1; }
.road-cell .k { display: block; font-size: 11px; color: var(--color-muted); margin-bottom: 2px; }
.road-cell .v { font-size: 14px; font-weight: 600; color: var(--color-text); word-break: keep-all; }
.road-cell .v.normal { font-weight: 500; }   /* 비고는 읽는 글이라 덜 굵게 */
.road-cell.block { background: var(--color-warning-tint); border-color: #f3d9ae; }
.road-cell.block .k, .road-cell.block .v { color: var(--color-warning); }

/* --- 데이터 테이블 --- */
.table-wrap {
  overflow-x: auto;
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}

/* 가로로 잘린 표에 '옆에 더 있다'는 신호(app.js 가 껍데기를 씌우고 클래스를 켠다).
 *
 * .table-wrap 에 직접 그리지 못하는 이유가 둘이다.
 *  - 배경으로 그리면 th(#f9fafb)·짝수 행(#fafbfc) 배경이 그 위를 덮어 줄마다 보였다 안 보인다.
 *  - ::after 로 그리면 스크롤 컨테이너 안이라 내용과 함께 옆으로 흘러가 버린다.
 * 그래서 스크롤하지 않는 껍데기(.table-shell)에 얹어 내용 위에 고정한다.
 *
 * 끝까지 민 뒤에도 그늘이 남으면 '아직 더 있다'는 거짓말이 되므로, 방향별로 따로 켠다. */
.table-shell { position: relative; }
.table-shell::before,
.table-shell::after {
  content: "";
  position: absolute; top: 1px; bottom: 1px; width: 24px;
  pointer-events: none;
  opacity: 0; transition: opacity .15s ease;
}
.table-shell::after {
  right: 1px; border-radius: 0 var(--radius) var(--radius) 0;
  background: linear-gradient(to left, rgba(16, 24, 40, .13), rgba(16, 24, 40, 0));
}
.table-shell::before {
  left: 1px; border-radius: var(--radius) 0 0 var(--radius);
  background: linear-gradient(to right, rgba(16, 24, 40, .13), rgba(16, 24, 40, 0));
}
.table-shell.is-cut-right::after,
.table-shell.is-cut-left::before { opacity: 1; }
.data-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.data-table th {
  text-align: left;
  padding: 9px 12px;
  font-size: 12px;
  font-weight: 600;
  color: var(--color-muted);
  background: #f9fafb;
  border-bottom: 1px solid var(--color-border);
  white-space: nowrap;
}
.data-table td { padding: 8px 12px; border-bottom: 1px solid var(--color-border); }
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table tbody tr:nth-child(even) { background: #fafbfc; }
.data-table td.num, .data-table th.num { text-align: right; }
.data-table tr[data-href] { cursor: pointer; }
.data-table tr[data-href]:hover { background: var(--color-primary-tint); }
.data-table tr.row-clickable { cursor: pointer; }
.data-table tr.row-clickable:hover { background: var(--color-primary-tint); }
.data-table a { color: var(--color-primary); text-decoration: none; }
.data-table a:hover { text-decoration: underline; }
.data-table th.sortable { cursor: pointer; user-select: none; position: relative; }
.data-table th.sortable:hover { color: var(--color-primary); }
.data-table th.sortable .sort-arrow { display: inline-block; width: 12px; text-align: center; }
.data-table.sortable-table { table-layout: fixed; }
.data-table.sortable-table td { overflow: hidden; }

/* --- 폰에서 표 접기 (.compact-sm) -----------------------------------
   폰(≤640px)에서는 .data-table 이 min-width:560px 이라 열이 잘리고 가로로 밀어야 했다
   (자재관리 화면들 — 소모품·예비품 목록/품목별 목록). 표 모양은 그대로 두고 열 수만 줄인다.
     .sm-hide  : 폰에서 감출 열 — th 와 td 양쪽에 같이 준다.
     .sm-extra : 폰에서만 보이는 보조 표기 — 감춘 열의 값을 남은 칸에 합쳐 넣는다.
                 .sm-line 을 함께 주면 이름 아래 작은 줄로 붙는다.
   641px 이상(태블릿·데스크톱)에는 아무 영향이 없다 — 열 구성이 지금 그대로다.
   정렬·열필터(enhanceDataTable)는 .sm-extra 를 값에서 빼고 읽는다(app.js cellText 참고) —
   그러지 않으면 폰에서 이름 열의 정렬 기준이 '이름+보조줄' 이 돼버린다. */
.sm-extra { display: none; }
@media (max-width: 640px) {
  .sm-hide { display: none; }
  .sm-extra { display: inline; }
  .sm-extra.sm-line { display: block; margin-top: 2px; font-size: 11px; font-weight: 400; color: var(--color-muted); }
  .data-table.compact-sm { min-width: 0; }
  .data-table.compact-sm th, .data-table.compact-sm td { padding: 8px 9px; }
  .data-table.compact-sm td.num { white-space: nowrap; }
  /* 날짜 칸(당직표·당직설정) — 폭이 모자라면 브라우저가 하이픈에서 끊어 '2026-/09-/05' 로
     세 줄이 된다. 한 줄로 묶으면 109px 을 차지하고, 그래도 네 열이 343px 안에 들어간다. */
  .data-table.compact-sm td.c-date, .data-table.compact-sm th.c-date { white-space: nowrap; }
  /* 접은 표 안의 단추는 좌우 여백을 줄인다(18px → 10px). 세로는 그대로라 손가락 타깃은
     유지되고, 열 하나가 16px 씩 좁아져 320px 화면에서 표가 넘치지 않는다. */
  .data-table.compact-sm .btn { padding-left: 10px; padding-right: 10px; }

  /* 계정관리 — 아이디·권한 열을 접어 '이름(아이디) + 권한 배지' 와 '최근 접속' 두 칸으로.
     권한은 색으로 구분하는 것이 빠르므로 배지를 글자로 바꾸지 않고 아랫줄로 내린다. */
  .compact-sm td.c-user { font-weight: 600; }
  .compact-sm td.c-user .sm-extra:not(.sm-roles) { font-weight: 400; color: var(--color-muted); }
  .compact-sm td.c-user .sm-roles { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
  .compact-sm td.c-last {
    text-align: right; vertical-align: top; white-space: nowrap;
    font-size: 11px; color: var(--color-muted);
  }
}

/* --- 폰에서 표를 카드로 (.cards-sm) ---------------------------------
   열을 접어도 남는 폭이 좁은 표(이력조회의 요청·조치 문장, 지점조회의 주소)는
   행 하나를 카드 한 장으로 바꿔 내용이 폭을 다 쓰게 한다. 표 마크업은 그대로다 —
   .table-wrap 에 cards-sm 과 배치 모양(cards-log / cards-branch)을 함께 준다.
   정렬·검색·행 클릭(상세 모달·지점 이동)은 손대지 않는다.
   본문 칸에 data-label 을 주면 그 값이 앞의 작은 라벨로 붙는다(요청/조치·점검자/특이사항).
   641px 이상은 지금의 표 그대로다. */
@media (max-width: 640px) {
  .table-wrap.cards-sm { border: none; background: none; border-radius: 0; overflow: visible; }
  .cards-sm .data-table { display: block; min-width: 0; font-size: 12.5px; }
  .cards-sm thead { display: none; }
  .cards-sm tbody { display: block; }
  .cards-sm tbody tr {
    display: grid;
    gap: 3px 7px;
    margin-bottom: 8px;
    padding: 10px 12px;
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
  }
  .cards-sm tbody tr:nth-child(even) { background: var(--color-card); }  /* 카드에는 줄무늬가 없다 */
  .cards-sm td { display: block; padding: 0; border: none; }
  /* .cards-sm td 가 .sm-hide 보다 세서, 이 한 줄이 없으면 카드 안에서 열 감추기가 먹지 않는다. */
  .cards-sm td.sm-hide { display: none; }
  .cards-sm td[data-label]::before {
    content: attr(data-label);
    flex: 0 0 auto; min-width: 30px;
    font-size: 11px; font-weight: 600; color: var(--color-primary);
    padding-top: 1px;
  }
  .cards-sm .clamp-2 { max-width: none; }

  /* 이력형 카드(이력조회·청소) — 열 순서를 그대로 쓴다:
     1 지점명 · 2 구분(배지) · 3 날짜 · 4·5 본문(요청/조치, 점검자/특이사항). */
  .cards-log tbody tr { grid-template-columns: auto 1fr auto; align-items: center; }
  .cards-log td:nth-child(1) { grid-area: 1 / 2; font-size: 14px; font-weight: 600; }
  .cards-log td:nth-child(2) { grid-area: 1 / 1; }
  .cards-log td:nth-child(3) {
    grid-area: 1 / 3; text-align: right; font-size: 11px; color: var(--color-muted);
  }
  .cards-log td:nth-child(3) .muted { font-size: 11px; }
  .cards-log td:nth-child(4), .cards-log td:nth-child(5) {
    grid-column: 1 / -1; display: flex; gap: 6px; line-height: 1.45;
  }
  /* 뒷줄(조치·특이사항)은 앞줄과 색을 달리해 '요청 → 조치' 가 눈으로 구분되게 */
  .cards-log td:nth-child(5)[data-label]::before { color: var(--color-success); }

  /* 지점형 카드(지점조회) — 열 수가 '전체'(구분 열 있음)와 '지상/도로'에서 달라지므로
     자리를 nth-child 가 아니라 셀 클래스로 잡는다. 첫 줄은 배지들 + 지점명(지점번호),
     둘째 줄은 주소 한 줄(넘치면 …로 자른다 — 행 높이를 일정하게 두려는 것). */
  .cards-branch tbody tr { grid-template-columns: auto auto 1fr; align-items: center; }
  .cards-branch td.c-name { font-size: 14px; font-weight: 600; }
  .cards-branch td.c-name .sm-extra { color: var(--color-muted); font-weight: 400; font-size: 12.5px; }
  .cards-branch td.c-addr {
    grid-column: 1 / -1; color: var(--color-muted);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }

  /* 파일형 카드(메뉴얼 목록·검색) — 표에서는 제목이 첫 열이지만 사람이 찾는 것은 파일명이다.
     카드에서는 그 둘을 뒤집어 파일명을 제목 자리에 크게 두고, 제목은 등록일·용량·등록자와
     함께 작은 한 줄로 내린다(그 셋은 제목 칸의 .sm-extra 로 합쳐 온다).
       첫 줄 : 파일명 (길어서 접히므로 어디서든 끊는다)
       둘째 줄: [카테고리] 제목 · 등록일 · 용량 · 등록자   ← 카테고리는 검색 결과에만 있다
       셋째 줄: 미리보기 / 다운로드 / 삭제 */
  .cards-file tbody tr { grid-template-columns: auto 1fr; gap: 3px 6px; }
  .cards-file td.c-file {
    grid-area: 1 / 1 / auto / -1;
    font-size: 13px; font-weight: 600; line-height: 1.4; word-break: break-all;
  }
  .cards-file td.c-cat { grid-area: 2 / 1; font-size: 11px; }
  .cards-file td.c-title { grid-area: 2 / 2; font-size: 11px; color: var(--color-muted); }
  /* 단추가 셋이라 한 줄에 안 들어갈 수 있다 — 접히게 두고, 손가락 간격만 지킨다. */
  .cards-file td.c-act {
    grid-area: 3 / 1 / auto / -1;
    display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px;
    white-space: normal;               /* 카테고리 화면의 인라인 nowrap 을 덮는다 */
  }
  .cards-file td.c-act .btn { padding: 7px 12px; font-size: 12px; }

  /* 지도 미표시 지점형 카드 — 지점형(.cards-branch)과 같은 모양이지만 열 순서가 다르다
     (지점명·주소·사유·작업). 주소가 둘째 줄로 내려가야 하는데 DOM 에서는 둘째 칸이라
     자동 배치로는 그 자리에 못 간다 — 네 칸 모두 자리를 못박는다. */
  .cards-maps tbody tr { grid-template-columns: auto 1fr auto; align-items: center; }
  .cards-maps td:nth-child(1) { grid-area: 1 / 1; font-size: 14px; font-weight: 600; }
  .cards-maps td:nth-child(3) { grid-area: 1 / 2; }
  .cards-maps td:nth-child(4) { grid-area: 1 / 3; text-align: right; }
  .cards-maps td:nth-child(2) {
    grid-area: 2 / 1 / auto / -1; color: var(--color-muted);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  /* '미표시 지점이 없습니다' 한 줄(colspan=4)은 위 자리들과 무관하게 카드 전체를 쓴다. */
  .cards-maps td[colspan] { grid-area: auto / 1 / auto / -1; }

  /* 형식승인형 카드(형식승인조회) — 한 칸짜리 두 줄.
     첫 줄은 기상측기명 + (형식승인번호), 둘째 줄은 '센서부 모델 / 지시기록부 모델'을
     한 줄로 잇고 넘치면 … 로 자른다. 자세한 값은 행을 눌러 뜨는 상세 모달에 다 있다. */
  .cards-appr tbody tr { grid-template-columns: 1fr; gap: 1px; padding: 9px 12px; }
  .cards-appr td.c-instr { font-size: 14px; font-weight: 600; }
  /* 측기명 칸의 .clamp-2 는 -webkit-box(=블록)라 그대로 두면 '(승인번호)' 가 다음 줄로 떨어진다.
     카드에서는 줄 수를 우리가 정하므로 인라인으로 되돌린다. */
  .cards-appr td.c-instr .clamp-2 { display: inline; }
  .cards-appr td.c-instr .c-no {
    margin-left: 4px; font-size: 12px; font-weight: 600; color: var(--color-primary);
  }
  .cards-appr td.c-instr .sm-line {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }

  /* MTMS 실시간조회형 카드 — 열이 MTMS 에서 오는 대로라 자리를 정해둘 수 없다.
     첫 칸(지점명 등)을 제목으로 크게 두고, 나머지는 '열이름 값' 한 줄씩 쌓는다.
     빈 칸('-')은 감춘다 — 여섯 열 중 절반이 비는 화면이 있어 그냥 두면 카드가 '-' 로 찬다. */
  .cards-mtms tbody tr { grid-template-columns: 1fr; gap: 2px; padding: 10px 12px; }
  .cards-mtms td:nth-child(1) { font-size: 14px; font-weight: 600; }
  .cards-mtms td:nth-child(1)::before { display: none; }
  .cards-mtms td[data-label] { display: flex; gap: 6px; align-items: baseline; }
  .cards-mtms td[data-label]::before { min-width: 52px; }
  .cards-mtms td.is-empty { display: none; }

  /* 센서형 카드(지점 상세의 센서 목록) — 한 칸짜리 두 줄.
     첫 줄은 센서명(누르면 MTMS 실시간값 조회) + 모델 배지, 둘째 줄은 S/N·제조사·검정일을
     한 줄로 잇고 넘치면 … 로 자른다(센서가 열 개 가까이라 행 높이를 일정하게 둔다). */
  .cards-sensor tbody tr { grid-template-columns: 1fr; gap: 2px; padding: 9px 12px; }
  .cards-sensor td.c-name { font-size: 14px; font-weight: 600; }
  .cards-sensor td.c-name .badge { margin-left: 6px; }
  .cards-sensor td.c-name .sm-line {
    font-weight: 400; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
}

/* --- 폰용 정렬 버튼 줄 (.sort-bar) ----------------------------------
   폰에서 표가 카드로 바뀌면 표 헤더가 사라져 '제목 클릭 정렬'을 쓸 수 없다.
   그 자리를 대신하는 버튼 줄이다 — 누를 때마다 오름차순 → 내림차순 → 기본(최신순) 으로
   도는 것까지 데스크톱 헤더 클릭과 같게 맞춘다. 641px 이상은 표 헤더가 그대로라 감춘다. */
.sort-bar { display: none; }
@media (max-width: 640px) {
  .sort-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 8px; }
  .sort-bar .sort-label { font-size: 11px; color: var(--color-muted); }
  .sort-bar button {
    display: inline-flex; align-items: center; gap: 3px;
    padding: 7px 12px; border-radius: 999px;
    border: 1px solid var(--color-border); background: var(--color-card);
    color: var(--color-text); font-family: inherit; font-size: 12px; cursor: pointer;
  }
  .sort-bar button.active {
    border-color: var(--color-primary); background: var(--color-primary-tint);
    color: var(--color-primary); font-weight: 600;
  }
  .sort-bar button .sort-arrow { font-size: 10px; }
}

/* 엑셀식 열 필터 (enhanceDataTable filter 옵션) — ▾ 버튼 + 체크박스 드롭다운 패널 */
.col-filter-btn {
  border: none; background: none; cursor: pointer; padding: 0 3px; margin-left: 3px;
  font-size: 15px; line-height: 1; color: var(--color-muted); vertical-align: middle;
}
.col-filter-btn:hover { color: var(--color-primary); }
.col-filter-btn.active { font-size: 19px; color: var(--color-primary); font-weight: 700; }
.col-filter-panel {
  /* 표가 .table-wrap 스크롤 컨테이너 안에 있어 body에 fixed로 띄운다 (위치는 JS가 지정) */
  position: fixed; z-index: 30; min-width: 160px; max-width: 240px;
  background: #fff; border: 1px solid var(--color-border); border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, .12); padding: 8px;
  font-weight: 400; text-align: left; cursor: default;
}
.col-filter-search {
  width: 100%; box-sizing: border-box; margin-bottom: 6px; padding: 4px 6px;
  border: 1px solid var(--color-border); border-radius: 4px; font-size: 12px;
}
.col-filter-items { max-height: 220px; overflow-y: auto; }
.col-filter-item {
  display: flex; align-items: center; gap: 6px; padding: 3px 2px;
  font-size: 12px; color: var(--color-text); cursor: pointer; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.col-filter-item:hover { background: var(--color-primary-tint); }
.col-filter-item input { width: auto; margin: 0; }
.col-filter-all { border-bottom: 1px solid var(--color-border); margin-bottom: 4px; padding-bottom: 5px; }

.clamp-2 {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  max-width: 320px;
}

/* --- 통계 카드 --- */
.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
  margin-bottom: 14px;
}
.stat-card {
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 12px 14px;
}
.stat-label { font-size: 11px; color: var(--color-muted); }
.stat-value { font-size: 20px; font-weight: 700; color: var(--color-primary); margin-top: 2px; }
.stat-value.danger { color: var(--color-danger); }

/* 폰에서는 카드 4장이 2x2 로 접히며 세로 140px 을 먹어 정작 볼 목록이 화면 밖으로 밀렸다.
   칸을 나눈 한 줄짜리 띠로 바꿔 50px 안에 넣는다(자재관리 두 화면만 stat-row 를 쓴다). */
@media (max-width: 640px) {
  .stat-row {
    grid-template-columns: repeat(4, 1fr);
    gap: 0;
    margin-bottom: 10px;
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    overflow: hidden;
  }
  .stat-card {
    border: none;
    border-left: 1px solid var(--color-border);
    border-radius: 0;
    padding: 8px 3px;
    text-align: center;
  }
  .stat-card:first-child { border-left: none; }
  .stat-label { font-size: 10px; white-space: nowrap; }
  .stat-value { font-size: 17px; }
  .stat-value .muted { font-size: 11px; }
}

/* 상세 페이지 재고 그리드 (stat 카드와 동일 톤) */
.stock-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-bottom: 12px;
}
.stock-item {
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 10px 4px;
  text-align: center;
}
.stock-qty { font-size: 20px; font-weight: 700; color: var(--color-primary); }
.stock-label { font-size: 11px; color: var(--color-muted); margin-top: 2px; }

/* --- 검색/필터 툴바 --- */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
}
.search-bar { display: flex; gap: 8px; margin-bottom: 10px; }
.search-bar input, #sensor-search, #spare-search, #history-search {
  flex: 1;
  padding: 8px 12px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: 13px;
  width: 100%;
  background: var(--color-card);
}
.search-bar input:focus, #sensor-search:focus, #spare-search:focus, #history-search:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 2px var(--color-primary-tint);
}
.search-bar button { padding: 8px 14px; }

/* --- 탭 --- */
.tabs { display: flex; gap: 8px; margin-bottom: 12px; }
.tab-btn {
  flex: 1;
  padding: 8px;
  border: 1px solid var(--color-border);
  background: var(--color-card);
  border-radius: var(--radius-sm);
  font-size: 13px;
  cursor: pointer;
  font-family: inherit;
  color: var(--color-text);
}
.tab-btn.active {
  background: var(--color-primary-tint);
  color: var(--color-primary);
  border-color: var(--color-primary);
  font-weight: 600;
}
/* 원시자료조회 항목 넘기기 — 지점별 추이 제목 바로 아래 */
.obs-field-nav { display: flex; align-items: center; gap: 8px; margin: 2px 0 10px; }
.obs-field-now { font-weight: 600; font-size: 14px; min-width: 9em; text-align: center; }
/* 원시자료조회 지상/도로 — 탭이 링크라 글자 밑줄·색을 단추와 맞춘다 */
.obs-kind-tabs .tab-btn { text-align: center; text-decoration: none; }
.tab-panel { display: none; }
.tab-panel.active { display: block; }

/* 이력조회의 '구분(정기·수시·장애)' 고르기 줄 — 위의 지상/도로 줄과 층을 나누려고 한 단계 작게.
   같은 크기로 두 줄이 겹치면 어느 쪽이 상위 구분인지 읽히지 않는다. */
/* 칸이 많은 상단 탭 줄(설정 7칸, MTMS 4칸). 균등폭(.tab-btn{flex:1})으로 나누면 '대시보드'
   같은 긴 이름이 두 줄로 접히면서 줄 전체가 두 배로 두꺼워진다 — 글자 폭에 맞추고, 넘치면
   옆으로 민다. 폰만의 문제가 아니라 좁아지면 어디서든 그러므로 미디어쿼리 밖에 둔다. */
.tabs-scroll { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
.tabs-scroll::-webkit-scrollbar { display: none; }
.tabs-scroll .tab-btn { flex: 0 0 auto; white-space: nowrap; padding: 8px 14px; }

.tabs-sub { gap: 6px; margin-bottom: 10px; align-items: center; flex-wrap: wrap; }
.tabs-sub .tabs-label { font-size: 11px; color: var(--color-muted); }
.tabs-sub .tab-btn { flex: 0 1 auto; padding: 6px 14px; font-size: 12px; border-radius: 999px; }
@media (min-width: 960px) { .tabs-sub .tab-btn { min-width: 76px; } }

/* 이력조회 '기간' 줄의 날짜 두 칸 — 옆에 선 칩과 같은 높이로 맞춘다(칩 6px + 테두리).
   폰에서는 날짜 칸이 줄바꿈되며 칩이 다음 줄로 밀리는데, .tabs-sub 가 이미 flex-wrap 이라
   그대로 접힌다. */
.tabs-sub .range-date {
  padding: 5px 8px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-card);
  color: var(--color-text);
  font-size: 12px;
  font-family: inherit;
}
.tabs-sub .range-sep { font-size: 12px; color: var(--color-muted); }

/* ── 이력조회 달력(실적) ─────────────────────────────────────────
   목록과 같은 자료(조치이력 + 청소)를 그날 칸에 지점명만 적는다. 색 점이 구분이다 — 목록 배지와
   같은 결로 정기는 초록, 장애는 빨강, 수시는 주황, 원격요청은 회색, 청소는 보라.
   폰은 칸이 좁아 이름 대신 건수만. */
.hcal-view { margin: 0 0 0 auto; }
.hcal-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.hcal-title { font-size: 16px; min-width: 104px; text-align: center; }
.hcal-sum { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.hcal-pill {
  font-size: 12px; padding: 3px 10px; border-radius: 999px;
  border: 1px solid var(--color-border); background: var(--color-card);
}
.hcal-pill .hcal-dot { margin-right: 5px; }
.hcal-grid {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  border-top: 1px solid var(--color-border); border-left: 1px solid var(--color-border);
  background: var(--color-card);
}
.hcal-wd, .hcal-pad, .hcal-day {
  border-right: 1px solid var(--color-border); border-bottom: 1px solid var(--color-border);
}
.hcal-wd { text-align: center; font-size: 12px; color: var(--color-muted); padding: 6px 0; background: var(--color-bg); }
.hcal-wd.is-sun, .hcal-day.is-sun .hcal-num { color: var(--color-danger); }
.hcal-wd.is-sat, .hcal-day.is-sat .hcal-num { color: var(--color-primary); }
.hcal-pad { background: var(--color-bg); }
.hcal-day {
  display: flex; flex-direction: column; align-items: stretch; min-width: 0; min-height: 104px;
  padding: 5px 6px; text-align: left; font: inherit; color: inherit; cursor: pointer;
  background: none; border-top: 0; border-left: 0; border-radius: 0;
}
.hcal-day:hover { background: var(--color-bg); }
.hcal-day.is-sel { box-shadow: inset 0 0 0 2px var(--color-primary); }
.hcal-num { align-self: flex-start; font-size: 12px; color: var(--color-muted); margin-bottom: 2px; }
.hcal-day.is-today .hcal-num { background: var(--color-primary); color: #fff; border-radius: 999px; padding: 0 6px; }
.hcal-names {
  display: grid; grid-auto-flow: column; grid-template-columns: repeat(var(--cal-cols, 1), minmax(0, 1fr));
  grid-template-rows: repeat(var(--cal-rows, 1), auto); column-gap: 6px; min-width: 0;
}
.hcal-name { display: block; font-size: 11.5px; line-height: 1.55; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hcal-name .hcal-dot { margin-right: 4px; vertical-align: 1px; }
.hcal-more { font-size: 11px; line-height: 1.55; color: var(--color-muted); }
.hcal-counts { display: none; }
/* 구분은 색과 **모양**을 함께 쓴다 — 색만으로는 장애(빨강)·수시(주황)가 잘 안 갈린다.
   정기 ● · 수시 ▼ · 장애 ✕ · 원격요청 ○(현장에 안 간 일) · 청소 ⬢. 아래 점검 현황의
   ▲ 밀림 · ◆ 예정 · ■ 대상 아님과 겹치지 않게 골랐다(정기 ● 은 '다녀온 곳'이라 완료와 뜻이 같다). */
.hcal-dot { display: inline-block; flex: 0 0 8px; width: 8px; height: 8px; border-radius: 50%; }
.hcal-k-regular { background: var(--color-success); }
.hcal-k-susi { background: var(--color-warning); border-radius: 0; flex-basis: 9px; width: 9px; clip-path: polygon(0 5%, 100% 5%, 50% 95%); }
.hcal-k-fault {
  background: var(--color-danger); border-radius: 0; flex-basis: 9px; width: 9px; height: 9px;
  clip-path: polygon(20% 0, 50% 30%, 80% 0, 100% 20%, 70% 50%, 100% 80%, 80% 100%, 50% 70%, 20% 100%, 0 80%, 30% 50%, 0 20%);
}
.hcal-k-remote { background: transparent; box-shadow: inset 0 0 0 1.5px var(--color-muted); }
.hcal-k-clean { background: var(--color-cheongso); border-radius: 0; flex-basis: 9px; width: 9px; height: 9px; clip-path: polygon(25% 3%, 75% 3%, 100% 50%, 75% 97%, 25% 97%, 0 50%); }
.hcal-detail {
  margin-top: 12px; padding: 12px 14px; font-size: 13px;
  background: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius);
}
.hcal-detail:empty { display: none; }
.hcal-detail p { margin: 0; }
.hcal-detail-head { font-weight: 600; margin-bottom: 6px; }
.hcal-detail-row { display: flex; gap: 10px; line-height: 1.7; padding: 2px 0; }
.hcal-detail-kind { flex: 0 0 88px; display: inline-flex; align-items: center; gap: 5px; color: var(--color-muted); white-space: nowrap; }
.hcal-detail a { color: var(--color-primary); text-decoration: none; }
.hcal-detail a:hover { text-decoration: underline; }
.hcal-note { font-size: 12px; margin: 8px 0 0; }
/* 달력 아래 점검 현황 — 지상(지도 색, 213곳 전부) · 도로 정기 · 청소. 이번 달에만 선다. */
.hcal-left {
  margin-top: 12px; padding: 12px 14px; font-size: 13px;
  background: var(--color-card); border: 1px solid var(--color-border); border-radius: var(--radius);
}
.hcal-left p { margin: 0; }
.hcal-left-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.hcal-left-head .muted { font-size: 12px; }
.hcal-left-row { border-top: 1px solid var(--color-border); margin-top: 10px; padding-top: 10px; }
.hcal-left-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.hcal-left-label { display: inline-flex; align-items: center; gap: 5px; min-width: 140px; font-weight: 600; }
.hcal-left-label .muted { font-weight: 400; font-size: 12px; }
.hcal-left-fill { flex: 1; }
.hcal-left-meter { flex: 1; min-width: 80px; height: 6px; border-radius: 3px; background: var(--color-bg); overflow: hidden; }
.hcal-left-meter i { display: block; height: 100%; }
.hcal-left-meter .is-road { background: var(--color-success); }
.hcal-left-meter .is-clean { background: var(--color-cheongso); }
.hcal-left-count { color: var(--color-danger); font-weight: 600; }
.hcal-left-done { color: var(--color-success); font-weight: 600; }
.hcal-left-tag {
  font: inherit; font-size: 12px; padding: 1px 9px; border: 1px solid transparent; border-radius: 999px; cursor: pointer;
}
.hcal-left-tag:hover { border-color: currentColor; }
.hcal-left-tag.is-on { border-color: currentColor; font-weight: 600; }
.hcal-left-tag.is-off { opacity: .45; }
.hcal-left-tag.is-late { background: var(--color-danger-tint); color: var(--color-danger); }
.hcal-left-tag.is-soon { background: var(--color-warning-tint); color: var(--color-warning); }
.hcal-left-tag.is-exempt { background: var(--color-primary-tint); color: var(--color-primary); }
.hcal-left-tag.is-done { background: var(--color-success-tint); color: var(--color-success); }
.hcal-left-tag.is-upgrade { background: var(--color-nodata-tint); color: var(--color-nodata); }
.hcal-left-tag.is-newgrace { background: var(--color-primary-tint); color: var(--color-primary); }
/* 상태는 색과 **모양**을 함께 쓴다 — 색만으로는 빨강·초록이 잘 안 갈리는 눈이 있고, 흑백 인쇄에서도
   읽혀야 한다. 밀림 ▲(경고) · 예정 ◆ · 완료 ● · 도입 유예 ★(첨단화 청록 · 신규 파랑) · 대상 아님 ■. 머리의 태그에도 같은 모양이 붙어 범례가 된다. */
.hcal-left-mark { display: inline-block; width: 8px; height: 8px; margin-right: 6px; vertical-align: 0; }
.hcal-left-mark.is-late { background: var(--color-danger); width: 10px; height: 9px; clip-path: polygon(50% 0, 100% 100%, 0 100%); }
.hcal-left-mark.is-soon { background: var(--color-warning); width: 9px; height: 9px; clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%); }
.hcal-left-mark.is-done { background: var(--color-success); border-radius: 50%; }
.hcal-left-mark.is-exempt { background: var(--color-primary); border-radius: 1px; }
.hcal-left-mark.is-upgrade, .hcal-left-mark.is-newgrace {
  width: 11px; height: 11px; vertical-align: -1px;
  clip-path: polygon(50% 0, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
}
.hcal-left-mark.is-upgrade { background: var(--color-nodata); }
.hcal-left-mark.is-newgrace { background: var(--color-primary); }
.hcal-left-tag .hcal-left-mark { margin-right: 5px; }
/* 한 줄은 좁게 묶고, 넓은 화면에서는 여러 단으로 흘린다 — 이름 칸이 화면 폭만큼 늘어나면
   이름과 날짜가 멀어져 한 줄로 안 읽힌다. 단은 위에서 아래로 채워져 '오래된 순'이 그대로 읽힌다. */
.hcal-left-list { margin-top: 6px; column-width: 340px; column-gap: 32px; }
.hcal-left-li {
  display: grid; grid-template-columns: minmax(0, 1fr) 70px 92px 60px; gap: 8px;
  padding: 4px 0; border-top: 1px dashed var(--color-border); font-size: 12.5px;
  break-inside: avoid; white-space: nowrap;
}
.hcal-left-li > span { overflow: hidden; text-overflow: ellipsis; }
.hcal-left-area { text-align: right; color: var(--color-muted); }
.hcal-left a { color: var(--color-primary); text-decoration: none; }
.hcal-left a:hover { text-decoration: underline; }
@media (max-width: 640px) {
  .hcal-left-label { min-width: 0; }
  .hcal-left-li { grid-template-columns: minmax(0, 1fr) 70px 64px; }
  .hcal-left-li > .muted { display: none; }
}
@media (max-width: 640px) {
  .hcal-day { min-height: 60px; padding: 4px; }
  .hcal-names { display: none; }
  .hcal-counts { display: flex; flex-direction: column; gap: 1px; }
  .hcal-count { display: inline-flex; align-items: center; gap: 3px; font-size: 11px; }
  .hcal-detail-kind { flex-basis: 60px; }
}
@media (min-width: 960px) {
  .tab-btn { flex: 0 0 auto; min-width: 140px; }
}

/* --- 페이지네이션 --- */
.pagination {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 6px;
  margin-top: 12px;
}
.pagination button {
  min-width: 32px;
  padding: 6px 8px;
  border: 1px solid var(--color-border);
  background: var(--color-card);
  border-radius: var(--radius-sm);
  font-size: 13px;
  color: var(--color-text);
  cursor: pointer;
  font-family: inherit;
}
.pagination button.active {
  background: var(--color-primary);
  color: #fff;
  border-color: var(--color-primary);
}
.pagination button:disabled { opacity: 0.4; cursor: not-allowed; }
.pagination-ellipsis { color: var(--color-muted); font-size: 13px; padding: 0 2px; }

/* --- 카드형 리스트 (연락처 등 유지 페이지 + JS 렌더링 결과) --- */
.list-card {
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 12px 14px;
  margin-bottom: 8px;
  text-decoration: none;
  color: var(--color-text);
  display: block;
}
a.list-card:hover { border-color: var(--color-primary); }
.list-card-title { font-weight: 600; font-size: 14px; margin-bottom: 4px; }
.list-card-sub { font-size: 12px; color: var(--color-muted); }

/* 목록 카드의 '수정' — 카드마다 한 줄을 쓰던 것을 오른쪽 위 연필 아이콘으로 줄였다.
   접기(details) 구조는 그대로라, 눌렀을 때 그 카드 안에서 폼이 펼쳐지는 동작은 종전과 같다.
   연락처·소모품 입고/출고 이력·예비품 이력이 같은 모양을 쓴다 — 연필이 달린 카드만 고른다. */
.list-card:has(> .card-edit) { position: relative; }
.list-card:has(> .card-edit) .list-card-title { padding-right: 34px; }   /* 아이콘 자리 확보 */
.card-edit { margin: 0; }
.card-edit > summary {
  position: absolute; top: 8px; right: 8px;
  width: 30px; height: 30px; border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  color: var(--color-muted); cursor: pointer;
  list-style: none;                       /* 기본 삼각형(▸) 제거 */
}
.card-edit > summary::-webkit-details-marker { display: none; }
.card-edit > summary:hover { background: var(--color-bg); color: var(--color-primary); }
.card-edit[open] > summary { background: var(--color-primary-tint); color: var(--color-primary); }
.card-edit > summary:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
/* 폼이 펼쳐질 때 카드 제목과 붙지 않게 */
.card-edit[open] .stack-form { margin-top: 4px; }

/* 연락처의 전화번호 — 눌러서 바로 거는 링크다(tel:). 글자로만 있던 때보다 손가락에 맞게
   줄 높이를 준다. 데스크톱에서도 링크로 두어 소프트폰이 있으면 그대로 걸린다. */
.tel-row {
  display: flex; align-items: center; gap: 6px;
  padding: 5px 0; font-size: 14px;
  color: var(--color-primary); text-decoration: none;
}
.tel-row b { font-weight: 600; letter-spacing: .2px; }
.tel-row:hover b { text-decoration: underline; }
@media (max-width: 640px) { .tel-row { padding: 7px 0; } }
.history-card { padding: 10px 14px; margin-bottom: 6px; }

/* --- 섹션 --- */
.section {
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 14px;
}
.section h2 { font-size: 15px; margin: 0 0 10px; }
.section h3 { font-size: 13px; color: var(--color-muted); margin: 14px 0 6px; }
.section-compact { padding: 6px 16px; }
.section-compact > details { margin: 0; }
.section-compact > details > summary { padding: 8px 0; }

.detail-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.detail-table th, .detail-table td { text-align: left; padding: 7px 4px; border-bottom: 1px solid var(--color-border); }
.detail-table th { width: 110px; color: var(--color-muted); font-weight: 500; }
.detail-table tr:last-child th, .detail-table tr:last-child td { border-bottom: none; }
/* 여러 줄로 입력한 비고·조치내용은 입력한 모양 그대로 보여준다 —
   textarea 로 받은 값을 그냥 출력하면 줄바꿈이 접혀 한 줄로 쭉 이어진다.
   (detail-table 셀뿐 아니라 목록 카드의 비고 줄도 같은 규칙을 쓴다) */
.pre-wrap, .detail-table td.pre-wrap { white-space: pre-wrap; word-break: break-word; }

.check-history-row { padding: 6px 4px; border-bottom: 1px solid var(--color-border); font-size: 13px; }
.check-history-row:last-child { border-bottom: none; }

.back-link { display: inline-block; margin-bottom: 10px; color: var(--color-primary); text-decoration: none; font-size: 13px; }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.muted { color: var(--color-muted); font-size: 12px; }

.notify-row { margin-top: 12px; }
.notify-group { display: flex; align-items: center; gap: 10px; }
/* ★ 라벨은 줄바꿈하지 않는다.
   .notify-row 는 '상태 + 배지' 와 '라벨 + 선택칸' 두 묶음을 한 줄에 양끝으로 놓는데,
   폰 폭에서는 자리가 모자라 뒤 묶음이 짓눌린다. 그때 한글 라벨은 글자 사이에서 접혀
   '수집 주기' 가 수/집/주/기 로 세로로 쪼개진다(실제로 그렇게 보였다).
   묶음을 안 줄이고 라벨을 안 접으면, 대신 flex-wrap 이 그 묶음을 아랫줄로 통째로 내린다.
   (실행 시각 줄에만 있던 .time-field/.time-label 처리를 같은 모양의 다른 줄로 넓힌 것) */
.notify-group { flex-shrink: 0; }
.notify-group > label, .notify-group > .muted { white-space: nowrap; }

/* 상세 기준 네 칸. 숫자 입력이 문장 안에 박혀 있어야 '무엇의 몇 분'인지가 읽힌다. */
.gap-thresholds { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; margin-top: 6px; }
.gap-thresholds label { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; }
.gap-thresholds input { width: 68px; text-align: right; }

/* 감지 종류 다섯 줄. 문단으로 늘어놓으면 무엇이 몇 가지인지 세어야 읽힌다. */
.gap-kinds { margin: 4px 0 10px; padding-left: 18px; }
.gap-kinds li { margin: 2px 0; }
.notify-row input[type="number"] {
  width: 80px;
  padding: 8px 10px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-family: inherit;
}
.notify-row select {
  padding: 8px 10px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-family: inherit;
  background: #fff;
  cursor: pointer;
}
.notify-row input[type="number"]:focus,
.notify-row select:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 2px var(--color-primary-tint);
}
/* 자동수집 실행 시각 — 24시간제 시/분 드롭다운.
   (flex-shrink·nowrap 은 위 .notify-group 규칙이 이제 모든 줄에 해준다) */
.time-picker { display: inline-flex; align-items: center; gap: 6px; }
.time-picker select { text-align: center; text-align-last: center; cursor: pointer; }
.time-colon { color: var(--color-muted); font-weight: 700; }

/* 관측 미수신 감지 — '지금 점검' 미리보기 결과. 표가 길어질 수 있어 자체 스크롤을 둔다
   (지점이 많이 끊긴 날 카드가 화면을 통째로 밀어내지 않게). */
.obs-gap-preview { margin-top: 12px; }
/* 판정 시험 줄 — 기준 시각 입력 + 실행 버튼. 카드 안의 다른 줄과 같은 입력 높이를 쓴다. */
.obs-gap-try input[type="datetime-local"] {
  padding: 8px 10px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-family: inherit;
  background: #fff;
}
.obs-gap-try input[type="datetime-local"]:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 2px var(--color-primary-tint);
}
.obs-gap-preview .table-wrap { max-height: 320px; overflow-y: auto; }
.obs-gap-preview p { margin: 6px 0; }

.empty { color: var(--color-muted); font-size: 13px; padding: 12px 0; text-align: center; }

/* 지도조회 — 지점 검색 결과 드롭다운 */
.map-search-results {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 30;
  background: var(--color-card); border: 1px solid var(--color-border);
  border-radius: var(--radius-sm); box-shadow: 0 4px 12px rgba(16, 24, 40, 0.1);
  max-height: 240px; overflow-y: auto; margin-top: 2px;
}
.map-search-results .result-item {
  padding: 7px 10px; font-size: 13px; cursor: pointer;
  border-bottom: 1px solid var(--color-border);
}
.map-search-results .result-item:last-child { border-bottom: none; }
.map-search-results .result-item:hover { background: var(--color-primary-tint); }
.map-search-results .result-empty { padding: 8px 10px; font-size: 12px; color: var(--color-muted); }

/* 지도조회 — 마커 클릭 팝업(InfoWindow) 내용 */
/* ★ 너비를 우리가 못박는다. 카카오 인포윈도우는 내용을 **DOM 에 붙기 전에** 재서 상자
   크기를 정하는데, 그때 잰 값이 min-width 라 그보다 넓은 주소·날짜가 오면 오른쪽과
   아래가 잘린다(신남(585) 처럼 주소가 긴 지점). 폭이 고정이면 재는 값과 그리는 값이
   같아져 잘리지 않는다. */
.map-popup { width: 264px; box-sizing: border-box; padding: 10px 12px 12px; }
.map-popup-head {
  flex-wrap: wrap;
  display: flex; align-items: baseline; gap: 7px;
  margin-bottom: 8px; padding-bottom: 8px;
  border-bottom: 1px solid var(--color-border);
}
.map-popup-name { font-size: 15px; font-weight: 700; color: #222; }
.map-popup-code {
  font-size: 12px; font-weight: 600; color: var(--color-primary);
  background: var(--color-primary-tint); padding: 2px 8px; border-radius: 8px;
}
.map-popup-badge-overdue {
  font-size: 11px; font-weight: 600; color: #fff;
  background: #e03131; padding: 2px 8px; border-radius: 8px;
}
.map-popup-close {
  margin-left: auto; padding: 0 2px;
  font-size: 16px; line-height: 1; color: var(--color-muted);
  cursor: pointer;
}
.map-popup-close:hover { color: #222; }
/* 한글은 낱말 단위로 접어야 '제3군수지원여단)' 같은 덩어리가 중간에서 끊기지 않는다 */
.map-popup-addr {
  font-size: 12.5px; color: #666; line-height: 1.5;
  margin-bottom: 8px; word-break: keep-all;
}
.map-popup-row {
  display: flex; justify-content: space-between; gap: 10px;
  font-size: 12.5px; color: #444; padding: 3px 0;
}
.map-popup-row .map-popup-label { color: var(--color-muted); flex: 0 0 auto; }
.map-popup-row > span:last-child { text-align: right; word-break: keep-all; }
/* 차단방법 — 현장 안전과 직결돼 넷 중 놓치면 안 되는 한 줄이다(지점 상세와 같은 색). */
.map-popup-row .map-popup-block { color: var(--color-warning); font-weight: 600; }
/* 여백이 넓으면 '상세보기·로드뷰·영상' 셋이 264px 안에 못 들어가 영상만 아래로 밀린다 */
.map-popup-link {
  display: inline-block; margin-top: 10px;
  font-size: 12.5px; font-weight: 600; color: #fff;
  background: var(--color-primary); padding: 6px 10px;
  border-radius: 8px; text-decoration: none; white-space: nowrap;
}
.map-popup-link:hover { opacity: 0.88; }
.map-popup-actions { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.map-popup-link-sub { background: #fff; color: var(--color-primary); border: 1px solid var(--color-primary); }
/* 길찾기는 이 화면을 떠나는 길이라 한 급 낮춰 둔다 — 상세보기·영상과 무게가 같으면 안 된다 */
.map-popup-nav { margin-top: 8px; }
.map-popup-navlink {
  border: 1px solid var(--color-border); border-radius: 999px;
  padding: 4px 10px; font-size: 12px; font-weight: 600;
  color: #444; text-decoration: none;
}
.map-popup-navlink:hover { border-color: var(--color-primary); color: var(--color-primary); }

/* 지도조회 — 마커 상시 라벨(지점명/지점번호) */
.map-marker-label {
  padding: 2px 7px;
  background: rgba(255, 255, 255, 0.92);
  color: #333;
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-radius: 10px;
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.18);
  pointer-events: none;
  transform: translate(-50%, -46px);
}
/* 카카오가 라벨을 감싸는 틀은 제자리(핀 바로 위)에 남고 글자만 transform 으로 올라갑니다.
   그 투명한 틀이 핀을 덮어 마커 클릭을 가로채므로 틀도 클릭을 흘려보내게 합니다 —
   도로 라벨이 이정표로 두 줄이 되면서 틀이 핀을 거의 다 덮게 됐습니다. */
div:has(> .map-marker-label) { pointer-events: none; }
/* 도로 이정표 — 이름 아래 둘째 줄, 이름보다 한 단계 흐리게 */
.map-marker-km { text-align: center; font-weight: 500; color: #666; }
/* 이정표 앞 장비 표시 — 지점조회 표의 장비 배지와 같은 색으로 맞춘다 */
.map-marker-kind {
  display: inline-block; padding: 0 5px; border-radius: 999px;
  background: var(--color-primary-tint); color: var(--color-primary);
  font-size: 10px; font-weight: 600; line-height: 15px;
}
.map-marker-kind + .map-marker-mp { margin-left: 4px; }

/* 지도조회 — 표시 필터 칩(눌러서 켜진 것을 색으로 보인다 — '켜기/끄기' 글자를 두지 않는다) */
.map-filters { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
.map-filters-label { font-size: 12px; color: var(--color-muted); }
.map-filters-sep { width: 1px; height: 16px; background: var(--color-border); margin: 0 2px; }
.map-chip {
  border: 1px solid var(--color-border); background: #fff; color: var(--color-muted);
  padding: 5px 11px; border-radius: 999px;
  font-size: 12px; font-weight: 600; cursor: pointer;
}
.map-chip.on {
  background: var(--color-primary-tint);
  border-color: var(--color-primary);
  color: var(--color-primary);
}
/* 좌표 동기화는 도는 동안 잠긴다 — 눌러도 되는 것처럼 보이면 두 번 누른다 */
.map-chip:disabled { opacity: 0.55; cursor: default; }
/* 도로를 끄면 방향 줄은 걸 대상이 없다 — 흐리게만 두고 고른 값은 남긴다 */
.map-filters.is-idle { opacity: 0.45; }

/* 지도조회 — 지도 무대(로드뷰를 열면 지도와 좌우로 나눠 쓴다) */
.map-stage {
  display: flex;
  gap: 8px;
  width: 100%;
  height: calc(100vh - var(--header-height) - 120px);
  min-height: 420px;
}
.map-pane { position: relative; flex: 1 1 auto; min-width: 0; }

/* 지도조회 — 카카오지도 컨테이너 */
#map {
  width: 100%;
  height: 100%;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

/* 지도조회 — 지도 위에 얹는 일반/위성/로드뷰 전환 */
.map-type-ctrl {
  position: absolute; top: 10px; right: 10px; z-index: 3;
  display: flex; background: #fff;
  border: 1px solid var(--color-border); border-radius: var(--radius-sm);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18); overflow: hidden;
}
.map-type-ctrl button {
  border: none; border-left: 1px solid var(--color-border);
  background: #fff; color: #444;
  padding: 7px 12px; font-size: 12px; font-weight: 600; cursor: pointer;
}
.map-type-ctrl button:first-child { border-left: none; }
.map-type-ctrl button.on { background: var(--color-primary); color: #fff; }
/* 내 위치 — 휴대폰 지도 앱의 파란 점. 둘레가 천천히 번져 지점 마커와 한눈에 갈린다 */
.my-loc-dot {
  width: 16px; height: 16px; border-radius: 50%;
  background: #1a73e8; border: 3px solid #fff; box-sizing: border-box;
  animation: my-loc-pulse 2s ease-out infinite;
  pointer-events: none;
}
@keyframes my-loc-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(26, 115, 232, 0.5), 0 1px 4px rgba(0, 0, 0, 0.35); }
  100% { box-shadow: 0 0 0 14px rgba(26, 115, 232, 0), 0 1px 4px rgba(0, 0, 0, 0.35); }
}
/* 로드뷰 자리를 고를 때 — 카카오가 자기 레이어에 커서를 걸어둬서 자식까지 눌러 덮는다 */
#map.rv-picking, #map.rv-picking * { cursor: crosshair !important; }

/* 지도조회 — 옆 패널(로드뷰와 지점 영상이 번갈아 쓴다) */
.side-pane {
  flex: 0 0 42%; min-width: 0;
  display: flex; flex-direction: column;
  border: 1px solid var(--color-border); border-radius: var(--radius-sm); overflow: hidden;
}
.side-pane[hidden] { display: none; }  /* display:flex 가 hidden 을 덮어써서 다시 눌러준다 */
.side-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 6px 10px; background: var(--color-primary); color: #fff;
  font-size: 13px; font-weight: 600;
}
.side-head button {
  border: none; background: transparent; color: #fff;
  font-size: 18px; line-height: 1; padding: 0 2px; cursor: pointer;
}
.side-body { flex: 1 1 auto; min-height: 0; }
.side-body[hidden] { display: none; }
.side-media {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; padding: 8px; background: #111; overflow: auto;
}
.side-media video, .side-media img { max-width: 100%; max-height: 100%; border-radius: 4px; }
.side-media-msg { color: #ddd; font-size: 13px; }
.side-media-link { color: #cbd5ff; font-size: 12px; font-weight: 600; text-decoration: none; }
.side-media-link:hover { text-decoration: underline; }

@media (max-width: 959px) {
  .map-stage { flex-direction: column; }
  .side-pane { flex: 0 0 45%; }
}
/* 버튼이 다섯이라 좁은 폰에서 지도 폭을 다 먹지 않게 줄인다 */
@media (max-width: 640px) {
  .map-type-ctrl button { padding: 6px 7px; font-size: 11px; }
}

details { margin: 10px 0; }
summary { cursor: pointer; font-size: 13px; color: var(--color-primary); font-weight: 600; padding: 6px 0; }

/* --- 폼 --- */
.stack-form { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
.stack-form label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--color-muted); }
.stack-form input, .stack-form textarea, .stack-form select {
  padding: 8px 10px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: 14px;
  resize: vertical;
  background: var(--color-card);
  font-family: inherit;
  width: 100%;
  box-sizing: border-box;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.stack-form input:focus, .stack-form textarea:focus, .stack-form select:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 2px var(--color-primary-tint);
}
.stack-form select {
  appearance: none;
  -webkit-appearance: none;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--color-muted) 50%),
    linear-gradient(135deg, var(--color-muted) 50%, transparent 50%);
  background-position: calc(100% - 18px) center, calc(100% - 13px) center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 30px;
  cursor: pointer;
}
.stack-form input[type="file"] {
  padding: 6px;
  border-style: dashed;
  color: var(--color-muted);
  font-size: 13px;
}
.stack-form input[type="file"]::file-selector-button {
  padding: 7px 14px;
  margin-right: 10px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  background: var(--color-bg);
  color: var(--color-text);
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}
.stack-form input[type="file"]::file-selector-button:hover {
  background: var(--color-primary-tint);
  border-color: var(--color-primary);
  color: var(--color-primary);
}
.stack-form-row { flex-direction: row; flex-wrap: wrap; align-items: flex-end; }
.stack-form-row label { flex: 1 1 120px; }
.stack-form-row .btn { flex: 0 0 auto; }

.phone-group-label { font-size: 12px; color: var(--color-muted); margin-bottom: 2px; }
.phone-row { display: flex; gap: 4px; align-items: center; margin-bottom: 6px; }
.phone-row input { flex: 1 1 auto; width: auto; min-width: 0; margin: 0; }
.phone-row-remove {
  flex: 0 0 auto; width: 26px; height: 26px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: none; border-radius: 50%;
  background: none; color: var(--color-muted); font-size: 17px; line-height: 1; cursor: pointer;
}
.phone-row-remove:hover { background: var(--color-danger-tint); color: var(--color-danger); }
.phone-add-btn {
  align-self: flex-start;
  border: none; background: none; padding: 2px 0;
  color: var(--color-primary); font-weight: 600; font-size: 12px; cursor: pointer; font-family: inherit;
}
.phone-add-btn:hover { text-decoration: underline; }

.inline-form { display: flex; gap: 6px; margin-top: 8px; }
.inline-form input { flex: 1; padding: 6px 8px; border: 1px solid var(--color-border); border-radius: var(--radius-sm); font-size: 12px; }

/* 게시판 분류 관리 (board_categories.html) — 줄마다 폼이 서너 개 들어간다. */
.cat-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.cat-table th {
  text-align: left; font-size: 11.5px; font-weight: 600; color: var(--color-muted);
  padding: 6px 10px; border-bottom: 1px solid var(--color-border); white-space: nowrap;
}
.cat-table td { padding: 6px 10px; border-bottom: 1px solid var(--color-border); vertical-align: middle; }
.cat-table .inline-form { margin-top: 0; align-items: center; }
.cat-order { display: flex; gap: 4px; }
.cat-order .btn { padding: 2px 8px; line-height: 1.5; }
.cat-count { white-space: nowrap; }
.cat-table select { padding: 5px 8px; border: 1px solid var(--color-border); border-radius: var(--radius-sm); font-size: 12px; }
.cat-add { flex: 1; display: flex; align-items: center; gap: 8px; font-size: 13px; }
/* 분류 관리로 나가는 탭은 분류가 아니다 — 색을 빼서 탭 줄에서 한 걸음 물러서게 한다. */
.tab-btn-manage { color: var(--color-muted); border-style: dashed; }

/* ============================================================
   5. 페이지 전용
   ============================================================ */

/* --- 로그인 --- */
.auth-wrap {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.auth-brand { text-align: center; margin-bottom: 18px; }
.auth-brand-title { font-size: 20px; font-weight: 700; color: var(--color-primary); }
.auth-brand-sub { font-size: 12px; color: var(--color-muted); margin-top: 4px; }
.auth-card {
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: 12px;
  padding: 28px 22px;
  width: 100%;
  max-width: 340px;
}
.auth-card h1 { font-size: 18px; margin: 0 0 18px; text-align: center; }
.auth-card .stack-form { margin-top: 0; }
.auth-error {
  background: var(--color-danger-tint);
  color: var(--color-danger);
  border-radius: var(--radius);
  padding: 8px 12px;
  font-size: 12px;
  margin-bottom: 12px;
}
/* 로그인 상태 유지 — .stack-form 의 label 은 세로 배치라 체크박스만 가로로 되돌린다. */
.auth-remember {
  flex-direction: row;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  cursor: pointer;
}
.auth-remember input { width: auto; margin: 0; }
.auth-note { font-size: 12px; color: var(--color-muted); margin: -4px 0 4px; line-height: 1.5; }
.auth-card .btn { width: 100%; margin-top: 4px; padding: 11px; font-size: 14px; }

/* --- 홈 퀵메뉴 --- */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 12px;
  margin-top: 8px;
}
.feature-card {
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  padding: 16px;
  display: flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
  color: var(--color-text);
}
.feature-card:hover { border-color: var(--color-primary); }
.feature-icon {
  width: 42px;
  height: 42px;
  border-radius: var(--radius);
  background: var(--color-primary-tint);
  color: var(--color-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.feature-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.feature-label { font-size: 14px; font-weight: 600; }
.feature-desc { font-size: 12px; color: var(--color-muted); }

/* --- 연락처 --- */
.contacts-grid { display: flex; flex-direction: column; }
.contacts-grid .list-card { height: 100%; }
@media (min-width: 720px) {
  .contacts-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
  }
  .contacts-grid .list-card { margin-bottom: 0; }
}
@media (min-width: 1100px) {
  .contacts-grid { grid-template-columns: repeat(3, 1fr); }
}
/* 본문이 전폭이 되는 구간 — 3열이면 카드 하나가 550px까지 늘어나 여백만 남는다. */
@media (min-width: 1400px) {
  .contacts-grid { grid-template-columns: repeat(4, 1fr); }
}

/* --- MTMS수집 섹션 그리드 ---
   카드 높이가 제각각이라 grid-template-columns로 나란히 짝지으면 짧은 카드 아래
   빈 공간이 생겨 정렬이 어긋나 보인다. columns(멀티컬럼)로 위에서부터 순서대로
   채워 내려가게 하여 이 문제를 피한다. */
@media (min-width: 1100px) {
  .exports-grid {
    columns: 2;
    column-gap: 14px;
  }
  .exports-grid .section {
    break-inside: avoid;
    margin-bottom: 14px;
  }
}

/* --- MTMS수집 폼 정렬 · 입력 스타일 통일 --- */
.exports-grid form:not(.stack-form) {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 6px;
}
.exports-grid form:not(.stack-form) .btn { margin-left: auto; }
.exports-grid input[type="month"],
.exports-grid input[type="date"],
.exports-grid select {
  height: 36px;
  padding: 0 10px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: 13px;
  font-family: inherit;
  background: var(--color-card);
  color: var(--color-text);
  box-sizing: border-box;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.exports-grid input[type="month"]:focus,
.exports-grid input[type="date"]:focus,
.exports-grid select:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 2px var(--color-primary-tint);
}
.exports-grid select { cursor: pointer; min-width: 88px; }
/* 다운로드 섹션: 설명 왼쪽 · 버튼 오른쪽 (다른 폼과 동일한 정렬) */
.exports-grid .download-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 6px;
}
.exports-grid .download-row .muted { margin: 0; flex: 1; }
.exports-grid .download-row .btn { margin-left: auto; flex: 0 0 auto; }
/* 체크리스트 저장 폼: 날짜칸이 과하게 늘어나지 않게 */
.exports-grid .stack-form-row { align-items: flex-end; gap: 10px; }
.exports-grid .stack-form-row label { flex: 0 1 220px; }
/* MTMS 수집 폼: 시작월+종료월+장비+버튼이 한 줄에 들어가도록 220px보다 좁게 */
.exports-grid .collect-form label { flex: 0 1 150px; }
.exports-grid .collect-form label.equip-field { flex: 0 1 100px; }
/* 수집 버튼은 줄의 오른쪽 끝에 세운다 — 칸 수가 폼마다 달라서(청소수집엔 장비 칸이 없다)
   그냥 두면 버튼만 저 혼자 왼쪽으로 밀려 네 폼의 세로선이 어긋난다. 줄이 접히면 접힌
   줄의 오른쪽 끝에 선다. */
.exports-grid .collect-form .btn { margin-left: auto; }
/* '이미 받은 건도 다시 조회'(정기·수시)는 아랫줄 전체폭으로 내린다 — 시작월+종료월+장비+버튼
   한 줄에 끼우면 좁은 화면에서 그 줄이 뭉개진다(지점 칸과 같은 처리).
   ★ .stack-form label 이 세로 배치(라벨 위, 칸 아래)라 그대로 두면 체크박스가 글자 위에
     얹힌다 — 여기서만 가로로 되돌린다. 체크박스도 .stack-form input 의 width:100% 를
     물려받아 칸 전체로 늘어나므로(글자가 가운데로 밀려 보인다) 폭을 되돌려야 한다. */
.exports-grid .collect-form:has(label.refresh-field) { flex-wrap: wrap; }
.exports-grid .collect-form label.refresh-field {
  flex: 1 1 100%;
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  gap: 6px;
  margin-top: -2px;
  font-size: 12px;
  cursor: pointer;
}
.exports-grid .collect-form label.refresh-field input[type="checkbox"] {
  width: auto;
  margin: 0;
  flex: 0 0 auto;
  cursor: pointer;
}
/* MTMS 수집 폼(장애/정기/수시): 좁은 화면에서도 시작월+종료월+장비+버튼이 한 줄에 들어가도록
   라벨을 숨기고 칸/버튼을 눈에 띄게 줄인다. 청소수집(장비 없음)은 이미 한 줄로 들어가 영향 없음. */
@media (max-width: 480px) {
  .exports-grid .collect-form { gap: 4px; flex-wrap: nowrap; }
  /* 시작월/종료월은 "2026년 07월" 표시가 잘리지 않도록 최소 폭을 보장하고,
     장비 선택("전체"/"지상"/"도로")과 버튼("수집")은 내용이 짧아 훨씬 좁게 줄인다. */
  .exports-grid .collect-form label { flex: 1 1 88px; min-width: 88px; }
  .exports-grid .collect-form label.equip-field { flex: 0 1 46px; min-width: 40px; }
  /* ★ 지점처럼 자유 입력인 칸은 이 한 줄에 끼우지 않는다.
     월·날짜 칸은 "2026-08-16" 이 88px 에 들어가지만, 지점은 안내문이 보여야 무엇을 적는
     칸인지 알 수 있는데 같은 줄에 서면 다섯 글자도 안 들어간다('비우면 전…' 로 잘렸다).
     그 칸만 아랫줄 전체폭으로 내리고, 여기서는 라벨도 되살린다(윗줄과 달리 자리가 있다). */
  .exports-grid .collect-form label:has(input[type="text"]) { flex: 1 1 100%; }
  .exports-grid .collect-form label:has(input[type="text"]) .field-label { display: block; }
  .exports-grid .collect-form:has(input[type="text"]) { flex-wrap: wrap; }
  .exports-grid .collect-form .field-label { display: none; } /* "시작월"/"종료월"/"시작일"/"장비" 라벨 텍스트 */
  .exports-grid .collect-form input[type="month"],
  .exports-grid .collect-form input[type="date"] { padding: 0 2px; font-size: 12px; }
  .exports-grid .collect-form select {
    padding: 0 12px 0 2px;
    font-size: 12px;
    min-width: 0;
    background-position: calc(100% - 4px) center, calc(100% - 1px) center;
  }
  /* 버튼도 옆 입력칸(36px)과 높이를 맞춰 나란히 정렬돼 보이게 한다 */
  .exports-grid .collect-form .btn {
    flex: 0 0 auto; height: 36px; padding: 0 10px; font-size: 12px;
    line-height: 1; box-sizing: border-box;
  }
}

/* 일괄 동기화 버튼줄: 3등분 균등 배치 */
.exports-grid .sync-actions { display: flex; gap: 8px; margin-top: 6px; }
.exports-grid .sync-actions .sync-btn { flex: 1; }

/* --- MTMS수집 모바일 정리 --- */
@media (max-width: 640px) {
  /* 월/셀렉트 + 다운로드 폼(정기점검 계획조회·용역수행결과서):
     입력칸은 한 줄에서 폭을 채우고, 버튼은 아래 전체폭으로 내려 정렬을 통일한다. */
  .exports-grid form:not(.stack-form) { gap: 8px 10px; }
  .exports-grid form:not(.stack-form) input[type="month"] { flex: 1 1 120px; min-width: 0; }
  .exports-grid form:not(.stack-form) select { flex: 1 1 90px; min-width: 0; }
  .exports-grid form:not(.stack-form) .btn { margin-left: 0; flex: 1 1 100%; }

  /* 자동수집 설정: 상태/실행시각 줄이 접힐 때 간격 확보, 실행시각은 오른쪽 정렬 유지 */
  .exports-grid .notify-row.row-between { row-gap: 12px; }
  .exports-grid .time-field { margin-left: auto; }
  .exports-grid .time-picker select { min-width: 60px; }

  /* 마지막수집 정보 + 끄기/저장: 정보는 한 줄, 버튼은 아래 반반 폭으로 */
  .exports-grid .auto-collect-foot { row-gap: 10px; }
  .exports-grid .auto-collect-buttons { width: 100%; }
  .exports-grid .auto-collect-buttons .btn { flex: 1; }
}

/* --- 메뉴얼 미리보기 (PDF.js) ---------------------------------------
   폰에서 PDF 를 못 여는 것이 이 화면의 존재 이유다(templates/manual_view.html 참고).
   조작줄은 위에 고정하지 않는다 — 폰 화면에서 세로를 먹는 것이 더 아깝고,
   쪽 이동은 한 쪽을 다 본 뒤에 쓰는 것이라 위로 올라가는 걸음이 부담스럽지 않다. */
.pdfv-bar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  margin-bottom: 8px;
}
.pdfv-pages, .pdfv-zoom { display: inline-flex; align-items: center; gap: 4px; }
.pdfv-zoom { margin-left: auto; }
.pdfv-bar .btn { min-width: 44px; min-height: 40px; }   /* 손가락 타깃 */
.pdfv-count { font-size: 13px; color: var(--color-muted); padding: 0 4px; white-space: nowrap; }
.pdfv-count b { color: var(--color-text); }
.pdfv-name { margin: 0 0 10px; word-break: break-all; }

/* 확대하면 그림이 화면보다 넓어진다 — 그때만 이 상자가 좌우로 스크롤한다.
   페이지(body)가 함께 흔들리지 않도록 넘침을 여기서 가둔다. */
.pdfv-stage {
  overflow: auto; -webkit-overflow-scrolling: touch;
  background: var(--color-bg);
  border: 1px solid var(--color-border); border-radius: var(--radius-sm);
  min-height: 240px;
  display: flex; justify-content: center; align-items: flex-start;
  padding: 4px;
}
.pdfv-canvas {
  display: block; max-width: none;      /* 확대 배율을 CSS 가 되돌리지 못하게 */
  background: #fff; box-shadow: 0 1px 4px rgba(16, 24, 40, .12);
}
@media (max-width: 640px) {
  /* 폰에서는 그림이 곧 내용이라 화면 세로를 최대한 내준다. */
  .pdfv-stage { min-height: 60vh; }
  .pdfv-bar .btn { flex: 0 0 auto; }
  /* 쪽 이동·확대만으로 한 줄이 찬다. 다운로드는 어차피 아랫줄로 밀리므로,
     왼쪽에 혼자 걸쳐 있는 것보다 한 줄을 통째로 쓰게 해 일부러 내린 것처럼 보이게 한다. */
  .pdfv-bar > .btn-on { flex: 1 0 100%; }
}

/* --- MTMS --- */
.fault-html { font-size: 13px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; }
.fault-html b { color: var(--color-primary); }
.fault-html code { background: var(--color-bg); padding: 1px 4px; border-radius: 4px; }

.mtms-form-table { width: 100%; border-collapse: collapse; margin-top: 8px; }
.mtms-form-table th, .mtms-form-table td { text-align: left; padding: 8px 4px; border-bottom: 1px solid var(--color-border); vertical-align: top; }
.mtms-form-table th { width: 88px; font-size: 12px; color: var(--color-muted); font-weight: 500; padding-top: 12px; }
@media (min-width: 960px) {
  .mtms-form-table th { width: 140px; }
}
.mtms-form-table input, .mtms-form-table select, .mtms-form-table textarea {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-family: inherit;
}
.mtms-form-table input[readonly], .mtms-form-table select:disabled,
.stack-form input[readonly] {
  background: var(--color-bg);
  color: var(--color-muted);
  cursor: not-allowed;
}
.mtms-inline { display: flex; gap: 6px; }
.mtms-inline input { flex: 1; }
.mtms-defect-row select { flex: 1; min-width: 0; }
.mtms-form-actions { margin-top: 14px; }
.mtms-form-actions .btn { flex: 1; text-align: center; margin: 0 4px; }
.mtms-form-actions .btn:first-child { margin-left: 0; background: var(--color-muted); }
.mtms-form-actions .btn:last-child { margin-right: 0; }

/* ============================================================
   6. 피드백 (토스트 / 진행 팝업)
   ============================================================ */
/* 토스트. 원래는 한 줄짜리 알약이었는데, 수집 결과처럼 여러 줄짜리 요약도 이리로 온다 —
   nowrap 이면 그런 글이 화면 밖으로 뻗어나가 뒷부분을 아예 못 읽는다(실제로 그랬다).
   그래서 폭을 묶고 줄바꿈을 살린다:
     · pre-line  — 서버가 넣은 줄바꿈(├ … └ 요약)을 그대로 살리고 잉여 공백만 접는다
     · max-width — 좁은 화면에서도 좌우 여백이 남는다
     · radius 14px — 여러 줄이 되면 999px 은 양끝이 통짜로 둥근 스타디움이 되어 어색하다.
       한 줄일 때는 높이가 37px 이라 이 값으로도 충분히 알약으로 읽힌다. */
.toast {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%) translateY(20px);
  background: rgba(0, 0, 0, 0.85);
  color: #fff;
  padding: 10px 18px;
  border-radius: 14px;
  font-size: 13px;
  line-height: 1.55;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s, transform 0.2s;
  z-index: 60;
  white-space: pre-line;
  text-align: left;
  max-width: min(560px, calc(100vw - 32px));
  /* 한글은 단어 중간에서 끊으면 읽기 나쁘다 — 어절 단위로 접는다. */
  word-break: keep-all;
  overflow-wrap: anywhere;
}
.toast.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.mtms-progress-popup {
  position: fixed;
  right: 16px;
  bottom: 24px;
  z-index: 50;
  /* 라벨에 월 진척([1/2])까지 들어가므로 200px에서는 잘린다 */
  width: 250px;
  max-width: calc(100vw - 32px);
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
  padding: 10px 12px;
}
.mtms-progress-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--color-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-bottom: 6px;
}
.mtms-progress-bar {
  height: 6px;
  border-radius: 999px;
  background: var(--color-bg);
  overflow: hidden;
}
.mtms-progress-fill {
  height: 100%;
  width: 0%;
  background: var(--color-primary);
  transition: width 0.3s ease;
}
.mtms-progress-count {
  margin-top: 6px;
  font-size: 11px;
  color: var(--color-muted);
  text-align: right;
}

/* --- 상세정보 모달 (공용) --- */
.modal-overlay {
  position: fixed; inset: 0; background: rgba(0, 0, 0, 0.45);
  display: flex; align-items: center; justify-content: center; z-index: 1000;
}
.modal-overlay[hidden] { display: none; }
.modal-box {
  background: var(--color-card); border-radius: var(--radius); width: 520px; max-width: 90vw;
  max-height: 85vh; overflow-y: auto; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
}
.modal-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 20px; border-bottom: 1px solid var(--color-border);
}
.modal-header h3 { margin: 0; font-size: 16px; }
.modal-close {
  border: none; background: transparent; font-size: 22px; line-height: 1;
  cursor: pointer; color: var(--color-muted); padding: 0 4px;
}
.modal-close:hover { color: var(--color-text); }
.modal-body { padding: 16px 20px; }
.detail-grid { display: grid; grid-template-columns: 130px 1fr; row-gap: 10px; column-gap: 12px; }
.detail-grid dt { color: var(--color-muted); font-size: 13px; }
.detail-grid dd { margin: 0; font-size: 14px; white-space: pre-wrap; }

/* --- 당직관리 --- */
/* .form-actions 는 .btn 에 flex:1 을 줘서 옆에 긴 텍스트가 오면 버튼이 짓눌린다.
   당직 화면의 월 이동·동기화 줄은 버튼이 제 너비를 지켜야 해서 따로 둔다. */
.duty-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
}
.duty-toolbar .btn { flex: 0 0 auto; }
.duty-toolbar-title { font-weight: 700; font-size: 17px; letter-spacing: -0.01em; }
.duty-toolbar-spacer { flex: 1 1 auto; }
.duty-sync-status { font-size: 12px; color: var(--color-muted); }

/* 월 이동은 위험한 동작이 아닌데 .btn-off-text 는 빨강(삭제용)이라 어울리지 않는다.
   화살표만 담는 중립적인 정사각 버튼을 따로 둔다. */
.duty-nav-btn {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  line-height: 1;
  color: var(--color-muted);
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  text-decoration: none;
  cursor: pointer;
}
.duty-nav-btn:hover { background: var(--color-bg); color: var(--color-text); }

/* 배정 진행 상황 — 문장보다 숫자가 먼저 읽히게 배지로. */
.duty-count {
  font-size: 12px;
  font-weight: 600;
  color: var(--color-primary);
  background: var(--color-primary-tint);
  padding: 3px 10px;
  border-radius: 999px;
}
.duty-count.done { color: var(--color-success); background: var(--color-success-tint); }
.duty-count.none { color: var(--color-muted); background: var(--color-bg); }

/* 달력/목록 토글 + 배정 동작을 한 줄에 둔다. */
.duty-actions { margin-bottom: 14px; }
/* .tabs 는 원래 한 줄을 통째로 쓰는 요소라 아래 여백과 늘어남을 여기서 눌러준다. */
.duty-actions .tabs { margin-bottom: 0; flex: 0 0 auto; }
/* 탭(<a>)과 버튼(<button>)은 기본 높이가 달라(33 vs 24) 한 줄에 두면 어긋난다. 한 규칙으로 맞춘다. */
.duty-actions .tab-btn,
.duty-actions .btn {
  height: 34px;
  padding: 0 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
}
.duty-actions .tab-btn { flex: 0 0 auto; min-width: 88px; }
@media (max-width: 640px) {
  /* 좁으면 토글이 한 줄을 차지하고 버튼들이 아래로 접힌다. */
  .duty-actions .tabs { flex: 1 1 100%; }
  .duty-actions .tab-btn { flex: 1; min-width: 0; }
  /* 여백을 살려두면 '당직설정'만 혼자 다음 줄로 밀린다. */
  .duty-actions .duty-toolbar-spacer { display: none; }
}
.duty-hint { font-size: 12px; color: var(--color-muted); margin: 0 0 8px; }

/* 월 달력 그리드 — 저장소에 달력 UI가 없어 새로 만든다.
   gap 1px + 배경색으로 칸 사이 선을 그린다(테두리 겹침 없이 균일한 격자). */
.duty-cal {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 1px;
  background: var(--color-border);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  overflow: hidden;
}
.duty-cal-head {
  background: #fafbfc;
  padding: 9px 4px;
  text-align: center;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--color-muted);
}
.duty-cal-head.sat { color: #2563eb; }
.duty-cal-head.sun { color: var(--color-danger); }

/* 칸 전체가 클릭 대상이라 <button>이다 — 기본 버튼 모양을 지우고 격자 칸처럼 보이게 한다. */
.duty-cell {
  position: relative;
  background: var(--color-card);
  min-height: 96px;
  padding: 7px 8px 8px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
  border: 0;
  border-radius: 0;
  font: inherit;
  text-align: left;
  color: inherit;
  cursor: pointer;
  transition: background 0.12s ease;
}
.duty-cell.empty { background: #fafbfc; }
/* 이웃 달(지난달 말일·다음달 첫날) 칸 — 흐린 배경에 당직자·메모까지 옅게 보여주고,
   눌러서 그 달로 이동하는 링크다. 주말/공휴일이어도 배경 tint는 죽여 현재 달과 구분한다. */
.duty-cell.other-month,
.duty-cell.other-month.duty {
  background: #fafbfc;
  text-decoration: none;
  justify-content: flex-start;
}
.duty-cell.other-month .duty-cell-day { color: var(--color-muted); font-weight: 500; }
.duty-cell.other-month.sat .duty-cell-day { color: #93b0ef; }
.duty-cell.other-month.sun .duty-cell-day,
.duty-cell.other-month.holiday .duty-cell-day { color: #e6a3a3; }
.duty-cell.other-month .duty-cell-holiday { opacity: 0.7; }
.duty-cell.other-month .duty-cell-name,
.duty-cell.other-month .duty-cell-memo { opacity: 0.6; }
.duty-cell.other-month:hover,
.duty-cell.other-month.duty:hover { background: #eef1f6; }
/* 당직 대상일(토·일·공휴일)만 배경을 살려 평일과 한눈에 구분되게 한다. */
.duty-cell.duty { background: var(--color-primary-tint); }
.duty-cell:hover { background: #eef1f6; }
.duty-cell.duty:hover { background: #dde3f6; }
.duty-cell:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -2px; }

.duty-cell-day {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  align-self: flex-start;
  font-size: 13px;
  font-weight: 600;
  color: var(--color-text);
}
.duty-cell.sat .duty-cell-day { color: #2563eb; }
.duty-cell.sun .duty-cell-day,
.duty-cell.holiday .duty-cell-day { color: var(--color-danger); }
/* 오늘은 날짜에 동그라미 — 옆에 '오늘' 글자를 붙이면 좁은 칸을 잡아먹는다. */
.duty-cell.today .duty-cell-day {
  background: var(--color-primary);
  color: #fff;
  border-radius: 999px;
}

.duty-cell-holiday {
  font-size: 10px;
  line-height: 1.25;
  font-weight: 500;
  color: var(--color-danger);
  word-break: keep-all;
}

/* 당직자는 이 화면의 주인공이라 알약 배지로 확실히 띄운다.
   메모와 생김새가 같으면 '송용훈'(당직자)과 '송용훈 대체휴무'(메모)가 헷갈린다. */
.duty-cell-name {
  align-self: flex-start;
  max-width: 100%;
  background: var(--color-primary);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.4;
  padding: 2px 8px;
  border-radius: 999px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 메모는 왼쪽 세로선을 둬서 '적어둔 글'로 읽히게 한다. */
.duty-cell-memo {
  font-size: 11px;
  line-height: 1.35;
  color: var(--color-muted);
  padding-left: 6px;
  border-left: 2px solid var(--color-border);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  white-space: pre-wrap;
  word-break: break-word;
}
.duty-cell.duty .duty-cell-memo { border-left-color: #b9c4e8; }

@media (max-width: 640px) {
  .duty-cal-head { padding: 6px 2px; font-size: 11px; }
  .duty-cell { min-height: 66px; padding: 4px 3px 5px; gap: 2px; }
  .duty-cell-day { min-width: 18px; height: 18px; font-size: 11px; }
  .duty-cell-holiday { font-size: 9px; }
  .duty-cell-name { font-size: 10px; padding: 1px 5px; }
  .duty-cell-memo { font-size: 9px; padding-left: 4px; -webkit-line-clamp: 2; }
}

/* .tab-btn 은 원래 <button>용이라 <a>로 쓰면 정렬·밑줄이 어긋난다(당직표 달력/목록 토글). */
a.tab-btn { display: block; text-align: center; text-decoration: none; }

/* MTMS 작업 진행 중(body.mtms-busy) 이면 수집/동기화 버튼을 잠근다 — MTMS는 한 번에 한 작업만
   돌아, 진행 중 다른 버튼을 누르면 '사용 중' 오류로 튕기므로 아예 못 누르게 흐리고 클릭을 막는다.
   (app.js가 /api/progress 폴링으로 body.mtms-busy 를 토글한다.) */
body.mtms-busy .collect-form button[type="submit"],
body.mtms-busy .sync-actions .sync-btn {
  pointer-events: none;
  opacity: 0.5;
}
/* 진행 중임을 알리는 안내줄 — 평소 숨김, 작업 중에만 표시 */
.mtms-busy-note { display: none; margin: 4px 0 10px; color: #b45309; font-size: 13px; }
body.mtms-busy .mtms-busy-note { display: block; }

/* 일괄(한 번에) 수집 박스 */
.bulk-collect {
  border: 1px solid #dbe3f4; border-radius: 8px; padding: 12px 14px;
  margin: 4px 0 16px; background: #f7f9ff;
}
.bulk-collect h3 { margin-top: 0; }

/* 지점조회 실시간 관측값 패널 */
.obs-panel {
  border: 1px solid #dbe3f4; border-radius: 8px; padding: 10px 12px;
  margin: 4px 0 12px; background: #f7f9ff;
}
.obs-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; }
.obs-controls label { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; }
.obs-controls input, .obs-controls select { width: auto; }
a.obs-sensor { color: #2b56d4; text-decoration: none; font-weight: 500; cursor: pointer; }
a.obs-sensor:hover { text-decoration: underline; }
.obs-card { margin-top: 8px; }
.obs-card-head { margin-bottom: 4px; }
#obs-result:empty { display: none; }

/* ── 원시자료 조회: 항목별 small multiples 선그래프 ─────────────────────
   단위가 다른 항목을 한 축에 겹치면 의미가 없어, 항목마다 자기 눈금을 가진
   작은 차트를 격자로 놓는다. 선 색 #374ba5 는 앱 기본 파랑(#2e4099)과 같은
   색조에서 데이터 마크용 명도 밴드(OKLCH L 0.43~0.77)에 들어오는 단계다
   (기본색은 L=0.412 로 얇은 선에서 표면과 덜 분리된다). 흰 카드 대비 7.7:1. */
/* 지역을 고른 상태를 되돌리는 단추(결과 머리줄 오른쪽). 눌리는 자리지만 조회를 새로 하는
   무거운 동작이 아니라 '거른 것을 푸는' 일이라, 실행 단추(.btn)보다 가볍게 둔다. */
.obs-area-clear {
  border: 1px solid var(--color-border); border-radius: 999px;
  background: var(--color-card); color: var(--color-primary);
  font-size: 12px; font-family: inherit; padding: 3px 10px; cursor: pointer;
}
.obs-area-clear:hover { background: var(--color-primary-tint); border-color: var(--color-primary-tint); }

.obs-chart-grid {
  display: grid; gap: 14px;
  /* auto-fill 이어야 한다 — auto-fit 은 빈 트랙을 접어서, 항목이 1개인 지점(센서 하나짜리)에서
     차트가 화면 전체로 늘어난다. auto-fill 은 빈 트랙을 남겨 항목 수와 무관하게 같은 폭을 유지한다.
     min(300px, 100%) 이어야 한다 — 그냥 300px 로 두면 트랙이 컨테이너보다 넓어질 수 있다.
     360px 폰에서 실측: 컨테이너 298px 인데 트랙이 300px 라 카드 오른쪽이 잘려 나갔다
     (body{overflow-x:hidden} 이라 스크롤도 안 되고 그냥 잘린다. 320px 화면이면 42px 가 사라진다). */
  grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
}
.obs-chart {
  margin: 0; padding: 10px 8px 6px; position: relative;
  border: 1px solid var(--color-border); border-radius: 8px; background: #fff;
}
.obs-chart figcaption {
  font-size: 13px; margin-bottom: 1px; display: flex; align-items: center;
  gap: 6px; flex-wrap: wrap;
}
/* 수집 상태('수집 n/1,440분' · '~시각')는 오른쪽 끝으로 민다 — 제목과 붙여 두면 어디까지가
   이름이고 어디부터가 상태인지 눈이 매번 다시 가른다. 이상 신호(배지)는 아랫줄이 맡는다. */
.obs-cap-end { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; }
/* 이상 신호 줄. 배지가 하나도 없으면 이 줄 자체가 만들어지지 않는다(observations.js
   chartCaption) — 멀쩡한 카드까지 키가 커지면 100개소 격자가 줄마다 들쭉날쭉해진다. */
.obs-cap2 { display: flex; align-items: center; flex-wrap: wrap; gap: 5px; margin: 0 0 3px; }
/* overflow:hidden 이어야 한다 — SVG 기본값(visible)이면 축 밖으로 뻗은 마크가 카드를 넘어
   아래 표까지 세로줄처럼 그려지고, 그 부분에도 호버가 먹어 엉뚱한 자리에서 툴팁이 뜬다.
   축이 데이터를 품게 만드는 건 chartGeom 쪽이고, 여기는 그래도 새면 잘라 내는 마지막 방어선. */
/* touch-action: pan-y — 세로 스와이프는 브라우저가 페이지 스크롤로 가져가고, 가로 움직임만
   우리에게 온다. 그래야 '가로로 문지르면 값 읽기'(wireHover)가 스크롤과 싸우지 않는다. */
.obs-chart svg { width: 100%; height: 130px; display: block; overflow: hidden; touch-action: pan-y; }
/* 지연 렌더(지점이 많을 때) 자리표시자. SVG 와 같은 높이여야 채워질 때 스크롤이 튀지 않는다. */
.obs-chart-ph { height: 130px; }

/* 눈금값(축 글자)을 SVG 밖에 얹는 층. SVG 는 가로로만 늘여 그리는 도면이라 그 안의 글자는
   카드 폭만큼 찌그러진다 — 글자는 HTML 로 두고 위치만 %로 맞춘다(observations.js chartInner 참고).
   figcaption 높이가 한 줄/두 줄로 달라지므로 SVG 를 감싼 상자를 기준으로 삼는다. */
.obs-plot { position: relative; }
.obs-labs {
  position: absolute; inset: 0;
  pointer-events: none;          /* 호버·클릭은 아래 SVG 가 받아야 한다 */
  font-size: 9px; color: var(--color-muted); font-variant-numeric: tabular-nums;
  line-height: 1;
}
.obs-lab { position: absolute; white-space: nowrap; }
/* y 눈금값: 오른쪽 끝을 눈금선 시작점(--obs-gut)에 못 박고 왼쪽으로 자란다.
   폭을 고정하면 '1013.2' 처럼 긴 값이 상자를 넘쳐 잘린 것처럼 보인다 — 자릿수가 달라도
   오른쪽은 항상 같은 선에 맞고, 긴 값만 카드 안쪽 여백 쪽으로 조금 더 뻗는다. */
.obs-lab-y { right: calc(100% - var(--obs-gut)); text-align: right; transform: translateY(-50%); }
/* x 시각: 눈금 위치를 중심으로, x축 선 아래 여백(PB=22px)에 앉힌다. */
.obs-lab-x { bottom: 4px; transform: translateX(-50%); }
/* 축이 끝나는 시각. 그 자리가 곧 카드의 오른쪽 끝이라, 가운데 정렬로 두면 글자 절반이
   카드 밖으로 나간다 — 오른쪽 끝을 축 끝에 못 박고 왼쪽으로 자라게 한다(y 눈금값과 같은 수법).
   정시 눈금('18시')과 달리 분까지 적는다: 형태가 달라야 '이건 끝'으로 읽힌다. */
/* 본문색(#222831)이면 눈금값 중 혼자만 새까매서 그림보다 먼저 읽힌다 — 한 단계 낮추고
   굵기로 구분한다. 어차피 '분까지 적혀 있다'는 형태만으로도 '이건 끝'으로 읽힌다. */
.obs-lab-x.obs-lab-end { transform: translateX(-100%); color: #4b5563; font-weight: 500; }
/* 축이 시작하는 시각(오늘 늦게 들어온 지점). 끝 라벨을 좌우로 뒤집은 것이다 — 왼쪽 끝을
   축 시작에 못 박고 오른쪽으로 자란다. 형태(분까지)와 색도 끝 라벨과 같게 둔다: 두 라벨이
   한 쌍으로 '이 그림은 여기서 여기까지'를 말한다. */
.obs-lab-x.obs-lab-start { transform: none; color: #4b5563; font-weight: 500; }
/* ★ vector-effect 가 반드시 있어야 한다. 작은 차트는 460 도면을 카드 폭(≈284px)에 **가로로만**
   눌러 넣는데(preserveAspectRatio="none"), SVG 스트로크는 그 변환 **뒤에** 그려진다.
   그래서 세로 획만 x배율(≈0.62)을 맞고 가로 획은 안 맞아, 같은 선인데 방향에 따라
   1.2px / 2px 로 그려졌다 — 선이 미묘하게 우글거리고 1분 스파이크의 세로 획은 실오라기가 된다.
   non-scaling-stroke 면 굵기가 변환을 타지 않아 어느 방향이든 2px 다.
   확대 팝업은 viewBox 가 1:1 이라 이 속성이 있으나 없으나 결과가 같다. */
.obs-line {
  fill: none; stroke: #374ba5; stroke-width: 2;
  stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke;
}
/* 0 이 바닥인 항목의 면(적설·강수·일사 …). 띠(.obs-band)보다 더 옅어야 한다 — 저건 '그 구간의
   폭'이라는 정보고 이건 형태를 읽히게 하는 배경이다. 조건은 observations.js chartInner 참고. */
.obs-area { fill: #374ba5; opacity: 0.07; stroke: none; }
/* 작은 차트만 한 단계 얇게. 1,440분을 카드 폭(≈284px)에 넣으면 점 간격이 0.2px 라, 굵기를
   변환에서 떼어내고 나니(위 vector-effect) 2px 이 점 간격의 열 배가 되어 촘촘한 구간이
   덩어리로 뭉쳤다. 확대 팝업은 한 점이 0.5px 이상이라 2px 그대로가 맞다. */
.obs-chart .obs-line { stroke-width: 1.6; }
/* 요약 구간의 최소~최대 띠(항목 기준 조회). 선과 같은 색을 옅게 깔아 '같은 계열의 폭'으로 읽히게 한다 —
   다른 색을 쓰면 두 번째 계열처럼 보인다. 선보다 먼저 그려야 선을 가리지 않는다. */
.obs-band { fill: #374ba5; opacity: 0.16; stroke: none; }
/* 미수신·미수집 표식. 1,200분을 900px 에 그리면 1분이 0.75px 이라(작은 차트는 0.2px) 선 끊김만으로는
   보이지 않는다 — 최소 폭은 JS(gapMarks)가 보장하고, 여기서는 '어느 결인지'만 정한다.
   두 가지를 색으로 가른다: 미수신(경고색) = 값이 빔 → 계기를 본다,
                            미수집(청록)  = 그 시각 행이 안 옴 → 수집을 다시 돌린다.
   (미수집은 예전에 회색이었는데, '미수신'과 글자가 비슷해 나란히 놓이면 헷갈려 색을 확실히 갈랐다.)
   띠는 선 아래에 깔리도록 옅게 두고, 놓치지 않도록 x축 위에 진한 눈금을 하나 더 얹는다. */
.obs-gap { fill: var(--color-warning); opacity: 0.15; }
.obs-gap-tick { fill: var(--color-warning); opacity: 0.85; }
.obs-nodata { fill: var(--color-nodata); opacity: 0.16; }
.obs-nodata-tick { fill: var(--color-nodata); opacity: 0.85; }
/* 안내 문구의 색 열쇠. 차트 표식과 같은 색이어야 글과 그림이 이어진다. */
.obs-key-gap { color: var(--color-warning); }
.obs-key-nodata { color: var(--color-nodata); }
.obs-key-behind { color: var(--color-warning); }
.obs-key-spike { color: var(--color-danger); }
/* 부분 수집 안내. 미수신 배지(경고색)와 나란히 놓이므로 눈에 덜 띄어야 한다 — 원인이 다른 정보다. */
.obs-sub { font-size: 11px; }
/* 그래프가 그려진 마지막 시각. 축의 끝은 전 지점 공용이라, 이 지점의 선이 어디서
   끊겼는지는 글자로만 알 수 있다. 평소에는 배경에 가깝게 두고(늘 붙는 값이라
   전부 진하면 격자가 시끄러워진다), 다른 지점보다 눈에 띄게 뒤처진 것만 경고색으로 띄운다
   (기준은 observations.js 의 BEHIND_MINUTES — 마지막 칸은 늘 채워지는 중이라 한 칸
    차이로는 칠하지 않는다. 그랬다면 2026-08-22 17:1x 실측 기준 101곳 중 75곳이 주황이었다). */
.obs-last {
  font-size: 11px; color: var(--color-muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.obs-last.is-behind {
  color: var(--color-warning); background: var(--color-warning-tint);
  border-radius: 999px; padding: 1px 7px;
}
/* 계기 꼬리표(철관). 지점명 바로 옆에 붙는 '무엇으로 잰 값인지'라 미수신 배지(경고색)와 달리
   중립색을 쓴다 — 이상 신호가 아니라 분류 정보다. */
.obs-tag {
  font-size: 11px; color: var(--color-primary); background: var(--color-primary-tint);
  border-radius: 999px; padding: 1px 7px; margin-left: 4px; white-space: nowrap;
}
/* 값이 안 온 지점 목록. 차트에서 뺀 것들이라 배경에 가깝게 두되, 접힌 상태로도 개수는 보이게 한다. */
.obs-no-value { font-size: 12px; margin: 0 0 10px; }
.obs-no-value > summary { color: var(--color-muted); cursor: pointer; }
.obs-no-value > p { margin: 6px 0 0; line-height: 1.6; word-break: keep-all; }
/* 격자·축은 그림보다 뒤에 있어야 한다. 예전에는 격자선이 카드 테두리(--color-border)와
   **정확히 같은 색**이라 틀이 그림만큼 진했다 — 강조를 더하는 대신 배경을 빼는 쪽으로 고쳤다.
   잉크가 줄면 미수신 띠·튐 눈금이 저절로 앞으로 나온다. */
.obs-grid { stroke: #f0f2f5; stroke-width: 1; }
.obs-axis { stroke: #dfe3e8; stroke-width: 1; }
.obs-tick { fill: var(--color-muted); font-size: 9px; font-variant-numeric: tabular-nums; }
/* 세로선이라 .obs-line 과 똑같이 x배율을 맞는다 — 0.62px 로 그려져 흐릿했다. */
.obs-cross {
  stroke: var(--color-muted); stroke-width: 1; stroke-dasharray: 3 3;
  vector-effect: non-scaling-stroke;
}
/* 작은 차트의 값 점. SVG 안의 <circle> 은 가로로 눌려 타원이 되므로(실측 4.3×7) HTML 로 얹는다 —
   눈금값을 밖으로 뺀 것과 같은 수법이다. 확대 팝업은 1:1 이라 .obs-dot(<circle>)을 그대로 쓴다. */
.obs-dot-h {
  position: absolute; width: 7px; height: 7px; border-radius: 50%;
  background: #374ba5; box-shadow: 0 0 0 2px #fff;
  transform: translate(-50%, -50%); pointer-events: none;
}
.obs-dot { fill: #374ba5; stroke: #fff; stroke-width: 2; }
.obs-miss {
  font-size: 11px; color: var(--color-warning); background: var(--color-warning-tint);
  border-radius: 999px; padding: 1px 7px;
}
/* 미수집(청록 띠)의 배지. 미수신 배지와 같은 모양이되 색은 띠와 맞춘다 —
   원인이 다른 두 증상을 한 색으로 적으면 배지를 봐도 어느 띠 얘기인지 알 수 없다. */
.obs-nodata-badge {
  font-size: 11px; color: var(--color-nodata); background: var(--color-nodata-tint);
  border-radius: 999px; padding: 1px 7px;
}
/* 튄 자리. 미수신(주황)과 색으로 가른다 — 원인이 다른 증상이라 같은 색을 쓰면 안 된다.
   띠는 옅게(선을 덮지 않게), 눈금은 진하게(띠를 놓쳐도 이건 남게). */
.obs-spike { fill: var(--color-danger); opacity: 0.28; }
.obs-spike-tick { fill: var(--color-danger); opacity: 0.9; }
.obs-spike-dot { fill: var(--color-danger); stroke: #fff; stroke-width: 1.5; }
.obs-spike-badge {
  font-size: 11px; color: var(--color-danger); background: var(--color-danger-tint);
  border-radius: 999px; padding: 1px 7px;
}
/* top 은 JS 가 값 위치에 맞춘다(wireHover). 카드 아래 고정이던 것을 옮긴 것이라
   bottom 을 남겨 두면 두 값이 싸운다. */
.obs-tip {
  position: absolute; top: 0; pointer-events: none;
  background: var(--color-text); color: #fff; font-size: 11px;
  padding: 2px 7px; border-radius: 4px; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
/* ★ 공통 .data-table 은 폰(≤959px)에서 min-width:560px 이다. 이 표에는 해롭다 —
   열이 적은 지점(값을 내는 항목이 두셋뿐이거나 '미수신 있는 시각만 보기'로 좁힌 때)에서
   내용보다 넓게 늘어나는데, 값이 우측 정렬이라 그 늘어난 폭만큼 오른쪽으로 밀려 화면 밖에
   가 붙는다. 화면에는 시각 열과 빈칸만 남아 '값이 안 나온다'로 보였다(실측 371px: 표는
   560px, 첫 값은 x≈550 — 한 자리도 안 보인다).
   열이 많은 날은 내용이 이미 560 을 넘으므로 이 줄이 있어도 종전과 같이 가로로 민다. */
.obs-table { font-variant-numeric: tabular-nums; min-width: 0; }
/* 남는 폭을 혼자 흡수하는 빈 끝열(renderTable 이 넣는다).
   이 표는 열 수가 지점·날짜마다 달라 둘일 때도 스물셋일 때도 있다. 표는 폭을 100% 채우므로
   열이 적으면 그 남는 폭이 열들에 나뉘어 들어가는데, 값이 우측 정렬이라 각 값이 제 열의
   오른쪽 끝까지 밀려난다 — 시각과 값 사이가 화면 하나만큼 벌어졌다(실측 1600px 화면, 2열).
   빈 열 하나가 남는 폭을 다 먹으면 값들은 시각 옆에 붙고, 표는 그대로 폭을 채워
   줄무늬·테두리가 카드 끝까지 간다. 열이 많아 내용이 이미 넘칠 때는 이 열이 0 이 된다. */
.obs-table .obs-fill { width: 100%; padding: 0; }
/* 미수신도 값 자리다 — 왼쪽에 붙여 두면 그 줄만 숫자 열에서 빠져나와 보인다.
   크기·자간은 옆 값과 같게 둔다. 낱말('미수신')을 적던 시절엔 한 단계 작게 줬는데, 지금은
   -999 라는 숫자라 혼자만 작으면 같은 열에서 글자가 들쭉날쭉해 보인다.
   색만 흐리게 남긴다 — 실측값이 아니라는 표시는 그것으로 충분하다. */
.obs-table td.obs-null { color: var(--color-muted); text-align: right; }

/* ── 격자 화면 모바일 (≤640px) ───────────────────────────────────────
   한 열밖에 못 들어가는 폭이라, 카드 사이·안쪽 여백을 줄여 그림 폭으로 돌린다. */
@media (max-width: 640px) {
  .obs-chart-grid { gap: 10px; }
  .obs-chart { padding: 8px 6px 4px; }
  /* 눌리지 않는 글자가 되었으니 9px 은 폰에서 굳이 작다 — 한 단계 키운다. */
  .obs-labs { font-size: 10px; }

  /* 표는 열이 지점 수만큼 늘어난다(14~50열). 가로로 밀어서 보게 되는데 첫 열(시각)이 같이
     밀려 나가면 지금 보는 값이 몇 시 것인지 알 수 없다 — 시각만 왼쪽에 세워 둔다. */
  .obs-table th:first-child,
  .obs-table td:first-child {
    position: sticky; left: 0; z-index: 1;
    background: var(--color-card);
    /* border-collapse 표에서는 sticky 셀의 테두리가 같이 밀려 사라진다 — 그림자로 경계를 긋는다. */
    box-shadow: 1px 0 0 var(--color-border);
  }
  .obs-table thead th:first-child { background: #f9fafb; z-index: 2; }
  /* 얼룩무늬(짝수 행)를 고정 셀도 따라가야 한 줄로 읽힌다 — 안 하면 첫 칸만 흰 띠로 뜬다. */
  .obs-table tbody tr:nth-child(even) td:first-child { background: #fafbfc; }
  /* 좌우 여백을 줄이면 한 화면에 지점이 한둘 더 들어온다(비교가 목적인 표다). */
  .obs-table th, .obs-table td { padding-left: 8px; padding-right: 8px; }

  /* 쪽 버튼도 손가락 타깃(권장 44px)에 맞춘다 — 표는 1시간씩 24쪽, 차트는 지점 수만큼 넘긴다.
     앱 공통 .pagination 을 건드리지 않고 이 화면 것만 키운다. */
  #obs-pagination button,
  #obs-chart-pagination button { min-width: 42px; padding: 11px 10px; }
}

/* ── 원시자료 조회: 자료 있는 날만 고르는 미니 달력 ──────────────────────
   격자선은 .duty-cal 과 같은 트릭(gap:1px + 배경)을 쓰지만, .duty-cell(min-height:96px)은
   날짜 피커로 쓰기엔 너무 커서 컴팩트한 셀을 따로 둔다. */
.obs-cal-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 10px 0 6px; }
.obs-cal-title { font-weight: 700; font-size: 15px; min-width: 108px; text-align: center; }
.obs-cal {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px;
  background: var(--color-border); border: 1px solid var(--color-border); border-radius: 8px;
  overflow: hidden;
  /* 캡이 없으면 .content(1200px) 를 다 먹어 셀 하나가 165px 로 벌어진다.
     375px 화면에서는 캡이 걸리지 않아 셀 44px(터치 타깃 최소치)이 그대로 유지된다. */
  max-width: 340px;
}
.obs-cal-head {
  background: #fafbfc; text-align: center; font-size: 11px; font-weight: 700;
  color: var(--color-muted); padding: 5px 0;
}
.obs-cal-head.sun { color: var(--color-danger); }
.obs-cal-head.sat { color: #2563eb; }
.obs-cal-cell {
  /* aspect-ratio 는 쓰지 않는다 — 폭이 가변이라 세로 높이가 같이 튄다. */
  min-height: 38px; border: 0; background: #fff; font: inherit; font-size: 13px;
  color: var(--color-text); cursor: pointer; padding: 0;
  /* 자료 표식(아래 점)의 기준. flex 중앙정렬로 바꿔야 점을 깔아도 숫자가 밀리지 않는다. */
  position: relative; display: flex; align-items: center; justify-content: center;
}
.obs-cal-cell.empty { background: #fafbfc; cursor: default; }
.obs-cal-cell:disabled { background: #fafbfc; color: #c9ced6; cursor: default; }
.obs-cal-cell:not(:disabled):hover { background: var(--color-primary-tint); }
.obs-cal-cell:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -2px; }
/* 자료가 있는 날 — 옅은 틴트 하나로는 회색 격자 속에서 잘 보이지 않았다.
   테두리·진한 글자색·아래 점을 겹쳐 준다. 색만으로 구분하지 않으므로 색각 이상에서도 읽힌다. */
.obs-cal-cell.has-data {
  background: var(--color-primary-tint); color: var(--color-primary); font-weight: 700;
  box-shadow: inset 0 0 0 1.5px var(--color-primary);
}
.obs-cal-cell.has-data::after {
  content: ""; position: absolute; left: 50%; bottom: 4px; transform: translateX(-50%);
  width: 4px; height: 4px; border-radius: 50%; background: currentColor;
}
/* 자료가 없지만 눌러서 수집할 수 있는 날 — 있는 날(파란 틴트)과 헷갈리지 않게 점선 테두리로만 힌트를 준다 */
.obs-cal-cell.can-collect { color: var(--color-muted); box-shadow: inset 0 0 0 1px #dfe3ea; }
.obs-cal-cell.can-collect:hover { background: var(--color-success-tint); color: var(--color-success); }
/* 선택한 날. has-data 와 명시도가 같으므로 반드시 뒤에 와야 이긴다.
   점은 currentColor 라 여기서 글자색만 흰색으로 바꾸면 함께 흰 점이 된다. */
.obs-cal-cell[aria-current="date"] {
  background: var(--color-primary); color: #fff; font-weight: 700;
  box-shadow: inset 0 0 0 1.5px var(--color-primary);
}

/* 달력 범례 — 칠해만 두면 무엇이 무엇인지 알 수 없다. 셀과 같은 스타일을 그대로 축소해 쓴다. */
.obs-cal-legend { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; margin: 6px 0 0; }
.obs-cal-legend .key { display: inline-flex; align-items: center; gap: 5px; }
.obs-cal-legend .swatch {
  width: 14px; height: 14px; border-radius: 3px; display: inline-block; flex: none;
}
.obs-cal-legend .swatch.data {
  background: var(--color-primary-tint); box-shadow: inset 0 0 0 1.5px var(--color-primary);
}
.obs-cal-legend .swatch.picked { background: var(--color-primary); }
.obs-cal-legend .swatch.collect { background: #fff; box-shadow: inset 0 0 0 1px #dfe3ea; }
.obs-cal-note { font-size: 12px; margin: 6px 0 0; }
.obs-cal-refresh {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 8px;
}
.obs-cal-refresh span { font-size: 12px; font-variant-numeric: tabular-nums; }

/* ── 관측자료 차트 확대 팝업 ─────────────────────────────────────────
   래퍼에 .obs-chart 를 붙이지 않는다 — .obs-line/.obs-grid/.obs-tick 등은 독립 클래스라
   그대로 적용되고, .obs-chart svg{height:130px} 오버라이드 전쟁이 아예 사라진다. */
.obs-chart { cursor: zoom-in; }
.obs-zoom-btn {
  border: 0; background: none; padding: 0; font: inherit; color: inherit;
  cursor: zoom-in; text-align: left;
}
.obs-zoom-btn:hover { color: var(--color-primary); text-decoration: underline; }
.obs-zoom-btn:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
/* 차트 제목(지점명·항목명) → 축을 바꾼 화면으로 가는 링크. 평소 모습은 제목 버튼과 같게 두고
   커서만 다르다 — 여기서는 확대가 아니라 이동이라는 걸 누르기 전에 알려 준다. */
.obs-title-link {
  color: inherit; text-decoration: none; cursor: pointer;
}
.obs-title-link:hover { color: var(--color-primary); text-decoration: underline; }
.obs-title-link:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
/* 제목이 이동으로 바뀐 자리를 메우는 확대 버튼. 카드마다 붙으므로
   미수신 배지보다 눈에 띄면 안 된다 — 작고 흐리게. */
.obs-zoom-inline {
  border: 0; background: none; padding: 0; font: inherit; font-size: 11px;
  color: var(--color-muted); cursor: zoom-in; text-decoration: underline;
}
.obs-zoom-inline:hover { color: var(--color-primary); }
.obs-zoom-inline:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.modal-box.modal-wide { width: 1100px; overscroll-behavior: contain; }
/* 회전·닫기가 제목에 붙지 않게. 헤더는 space-between 이라 이 둘만 오른쪽에 남는다. */
.obs-zoom .modal-header { gap: 8px; }
/* 가로 보기 토글. 켜져 있는 동안 눌린 상태를 보여야 다시 눌러 되돌릴 수 있다고 읽힌다. */
.obs-zoom-rotate {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
  border: none; background: transparent; padding: 0 4px;
  font-size: 20px; line-height: 1; color: var(--color-muted); cursor: pointer;
}
.obs-zoom-rotate:hover { color: var(--color-text); }
.obs-zoom-rotate[aria-pressed="true"] { color: var(--color-primary); }
.obs-zoom-nav { display: flex; align-items: center; gap: 8px; }
/* 이름 + 현재값 두 줄 묶음. 최소폭을 주는 이유는 종전과 같다 — 항목마다 이름 길이가 달라
   그대로 두면 좌우 화살표가 항목을 넘길 때마다 옆으로 움직인다(같은 자리를 연타할 수 없다).
   가운데 정렬이던 것은 왼쪽으로 돌린다: 아래 큰 숫자와 왼쪽 끝이 맞아야 한 덩어리로 읽힌다. */
.obs-zoom-id { min-width: 170px; }
.obs-zoom-nav h3 { text-align: left; }
/* 보고 있는 구간의 마지막 값. 이 화면에서 가장 먼저 읽혀야 하는 숫자다. */
.obs-zoom-now { display: flex; align-items: baseline; gap: 8px; margin: 2px 0 0; }
.obs-zoom-val { font-size: 24px; font-weight: 600; font-variant-numeric: tabular-nums; }
.obs-zoom-val .obs-zoom-unit { font-size: 14px; font-weight: 400; margin-left: 1px; }
/* 구간 시작 대비 변화. 오름/내림을 빨강·파랑으로 가르지 않는다 — 이 화면의 빨강은 '튐'이라
   색이 겹치면 '값이 올랐다'가 '이상이 있다'로 읽힌다. 방향은 ▲▼ 로만 말한다. */
.obs-zoom-delta { font-size: 12px; color: var(--color-warning); font-variant-numeric: tabular-nums; }
.obs-zoom-at { font-size: 12px; font-variant-numeric: tabular-nums; }
.obs-zoom-bar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 8px;
}
/* 붙은 한 덩어리로 고르는 자리(범위). 낱개 단추 넷으로 두면 '네 가지 동작'처럼 보이는데,
   실은 넷 중 하나를 고르는 라디오다 — 붙여 놓으면 그 뜻이 모양으로 드러난다. */
.obs-seg {
  display: inline-flex; border: 1px solid var(--color-border);
  border-radius: 999px; overflow: hidden;
}
.obs-seg button {
  border: 0; border-right: 1px solid var(--color-border);
  background: var(--color-card); color: #4b5563;
  padding: 5px 12px; font-size: 12px; font-family: inherit; cursor: pointer;
}
.obs-seg button:last-child { border-right: 0; }
.obs-seg button:hover { background: var(--color-bg); }
.obs-seg button[aria-pressed="true"] { background: var(--color-primary); color: #fff; }
.obs-zoom-window { font-size: 12px; font-variant-numeric: tabular-nums; }
/* 간격(몇 분 단위로 볼지) — 범위 버튼과 다른 축이라 라벨을 붙여 구분한다. */
.obs-zoom-step {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--color-muted); margin: 0;
}
.obs-zoom-step select { width: auto; min-width: 72px; padding: 4px 24px 4px 8px; font-size: 12px; }
/* 시각 이동. 화살표 사이에 지금 보고 있는 구간을 끼워 '여기를 좌우로 옮긴다'로 읽히게 한다.
   줄의 오른쪽 끝에 세운다 — 왼쪽 둘은 '무엇을 볼지'(범위·간격), 이건 '어디를 볼지'다. */
.obs-zoom-move { display: inline-flex; align-items: center; gap: 6px; margin-left: auto; }
.obs-zoom-move .duty-nav-btn:disabled { opacity: 0.35; cursor: default; }
.obs-zoom-plot { position: relative; height: 56vh; min-height: 260px; }
.obs-zoom-plot svg {
  width: 100%; height: 100%; display: block;
  overflow: hidden;              /* 팬·줌 중에는 창 밖 구간이 계산되므로 반드시 잘라 낸다 */
  touch-action: none;            /* 없으면 Android 가 첫 touchmove 를 스크롤로 가로챈다 */
  user-select: none; -webkit-user-select: none;
  cursor: grab;
}
.obs-zoom-plot svg.is-dragging { cursor: grabbing; }
.obs-zoom-plot svg:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
/* viewBox 를 실제 픽셀에 1:1 로 맞추므로 작은 차트처럼 가로로 늘어나지 않는다 → 크기 보정 */
.obs-zoom .obs-tick { font-size: 11px; }
.obs-zoom .obs-line { stroke-width: 1.6; }
.obs-zoom .obs-tip { bottom: auto; }   /* JS 가 top 을 데이터 점 위에 붙인다 */
/* 통계 네 칸. 값은 크게, 이름은 작게 — 어느 칸이 무엇인지는 자리로 기억하고 숫자만 읽는다. */
.obs-zoom-stats {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px; margin: 10px 0 0;
}
.obs-zoom-stats > div { background: #f7f8fa; border-radius: var(--radius-sm); padding: 7px 10px; }
.obs-zoom-stats dt { font-size: 11px; color: var(--color-muted); margin-bottom: 2px; }
.obs-zoom-stats dd {
  margin: 0; font-size: 15px; font-weight: 500; font-variant-numeric: tabular-nums;
}
/* 미수신이 있는 구간은 그 칸만 색을 바꾼다 — 0분인 날이 대부분이라, 늘 켜져 있는 색은 곧 배경이 된다. */
.obs-zoom-stats > div.is-warn { background: var(--color-warning-tint); }
.obs-zoom-stats > div.is-warn dd { color: var(--color-warning); }
/* 칸에 안 들어가는 말들 — 미수신이 난 시각, 미수집 구간, 튐 횟수, 간격 보기 주석. */
.obs-zoom-notes { font-size: 12px; margin: 8px 0 0; font-variant-numeric: tabular-nums; }
.obs-zoom-notes:empty { display: none; }
.obs-zoom-hint {
  display: flex; align-items: center; gap: 5px; font-size: 12px; margin: 6px 0 0;
}
.obs-zoom-hint .icon { flex: 0 0 auto; }
@media (max-height: 700px) { .obs-zoom-plot { height: 44vh; } }

/* ── 확대 팝업 모바일 (≤640px) ───────────────────────────────────────
   375px 화면에서 재 보면 팝업이 338px(90vw), 그림이 298px 로 화면의 79%만 썼다 —
   나머지 21%는 오버레이 여백과 카드 안쪽 여백이다. '확대해서 보려고' 연 화면이라
   그림 폭이 곧 정보량이므로, 좁은 화면에서는 전면으로 깐다. */
/* 가로로 눕힌 폰(812×375)도 같이 받는다 — 폭 조건만 두면 여기서만 90vw 여백이 되살아난다. */
@media (max-width: 640px), (max-height: 480px) {
  .obs-zoom .modal-box.modal-wide {
    width: 100%; max-width: 100%; border-radius: 0;
    /* dvh 는 주소창이 접히면 같이 늘어난다. 미지원 브라우저용으로 vh 를 먼저 둔다. */
    max-height: 100vh; max-height: 100dvh;
  }
  .obs-zoom .modal-header { padding: 10px 12px; }
  .obs-zoom .modal-body { padding: 10px 12px 12px; }
  /* 제목이 화살표·닫기와 한 줄을 다툰다 — 최소폭을 풀고 넘치면 말줄임(줄바꿈은 헤더가 두 줄이 된다). */
  .obs-zoom .obs-zoom-nav { flex: 1 1 auto; min-width: 0; }
  .obs-zoom .obs-zoom-id { min-width: 0; }
  .obs-zoom .obs-zoom-nav h3 {
    font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .obs-zoom-val { font-size: 21px; }
  /* 폰에서는 넷을 한 줄에 놓으면 칸마다 글자가 두 줄로 접힌다 — 2×2 로 눕힌다. */
  .obs-zoom-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* 세로도 벌린다 — 아래 안내가 세 줄에서 한 줄로 줄어든 만큼 그림에 준다. */
  .obs-zoom-plot { height: 62vh; height: 62dvh; }
  /* 손가락 타깃. 기본 32px 은 오조작이 잦다(권장 44px). */
  #obs-zoom-ranges button { min-width: 46px; padding: 10px 12px; }
  .obs-zoom-step select { min-width: 84px; padding: 8px 26px 8px 10px; font-size: 13px; }
  .obs-zoom-move .duty-nav-btn { width: 44px; height: 44px; }
  /* 좁은 화면에서는 이 줄이 통째로 다음 줄로 접힌다 — 오른쪽에 붙여 두면 왼쪽 절반이 빈 채로
     남아 '무엇의 오른쪽인지' 알 수 없는 덩어리가 된다. 접힌 줄은 폭을 다 쓰고,
     화살표를 양 끝에 세워 그 사이의 구간을 옮기는 것으로 읽히게 한다. */
  .obs-zoom-move { width: 100%; margin-left: 0; }
  .obs-zoom-move .obs-zoom-window { flex: 1; text-align: center; }
  /* 조작줄이 두세 줄로 접히면 그림이 밀린다 — 줄 간격을 줄여 한 줄어치만 쓰게 한다. */
  .obs-zoom-bar { gap: 8px; margin-bottom: 6px; }
  .obs-zoom .modal-close { font-size: 26px; padding: 0 8px; }
  /* 회전은 이 화면에서 가장 자주 누르는 단추다 — 손가락 타깃(권장 44px)을 지킨다. */
  .obs-zoom .obs-zoom-rotate { font-size: 24px; min-width: 44px; height: 44px; padding: 0; }
}

/* 가로 보기(전체화면)일 때. 브라우저는 :fullscreen 요소 뒤를 검게 칠하는데, 우리 오버레이는
   반투명이라 그대로 두면 흰 카드가 검은 바다에 뜬 것처럼 보인다 — 여백째로 카드를 깐다. */
.obs-zoom:fullscreen { background: var(--color-card); }
.obs-zoom:fullscreen .modal-box.modal-wide {
  width: 100%; max-width: 100%; max-height: 100%; border-radius: 0;
}
/* 손가락 타깃은 눕혀도 그대로 필요하다(가로 전용 미디어쿼리가 폭 조건에 안 걸리는 경우 대비). */
.obs-zoom:fullscreen #obs-zoom-ranges button { min-width: 46px; padding: 10px 12px; }
.obs-zoom:fullscreen .obs-zoom-move .duty-nav-btn { width: 44px; height: 44px; }

/* 눕힌 폰(812×375)처럼 세로가 짧으면 min-height(260px)가 vh 를 이겨 그림이 화면보다 커지고,
   통계·안내가 스크롤 밖으로 밀린다(실측: 내용 424px > 화면 375px). 여기서는 그림을 줄여 다 넣는다. */
@media (max-height: 480px) {
  .obs-zoom-plot { height: 55vh; height: 55dvh; min-height: 0; }
}

/* 조작 안내는 입력장치마다 다른 말을 해야 한다 — 폰에서 '휠·키보드'를 읽히면 세 줄을 먹고
   그중 쓸 수 있는 건 두 개뿐이다. 화면폭이 아니라 포인터 종류로 가른다(태블릿+마우스 대응). */
.obs-hint-touch { display: none; }
@media (hover: none) and (pointer: coarse) {
  .obs-hint-pointer { display: none; }
  .obs-hint-touch { display: inline; }
}

/* 지점 상세에 임베드할 때 — 이미 .section 카드 안이라 카드 겹침을 피하고 여백만 준다 */
.obs-embed-block { margin-bottom: 14px; }
.obs-embed-block h2 { font-size: 14px; margin: 0 0 8px; }

/* ── MTMS 실시간조회 (/mtms) ─────────────────────────────────────────
   상단 탭 아래 한 겹 더 놓이는 화면 선택 줄. 상단 탭과 같은 모양이면 어느 것이
   상위인지 읽히지 않아, 여기만 조금 작고 옅게 둔다. */
.tabs-sub { margin-top: -4px; }
.tabs-sub .tab-btn { font-size: 12px; padding: 5px 10px; }

/* 목록 아래 '더보기'. 쪽 이동(‹이전 · 다음›) 대신이다 — 누르면 갈아끼우지 않고 아래로
   이어붙으므로, 지금까지 몇 건을 불러왔는지 옆에 같이 적어야 어디까지 왔는지 보인다.
   쪽이 하나뿐이면 아무것도 안 들어와 :empty 로 줄째 사라진다. */
.mtms-more {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--color-border);
}
.mtms-more:empty { display: none; }
.mtms-more .btn { min-width: 170px; }
.mtms-more .btn.is-loading { opacity: .6; cursor: progress; }
.mtms-more-count { font-size: 12px; }
.mtms-more-error { font-size: 12px; color: var(--color-danger); }

/* 이미 쓰인 점검표 목록. 한 줄이 곧 한 장이고 눌러서 연다 — 표로 그리면 눌러야 하는
   자리가 어디인지 안 보인다(원본이 그렇다). */
.report-sheets { list-style: none; margin: 10px 0 0; padding: 0; }
.report-sheets li { border-top: 1px solid var(--color-border); }
.report-sheets a {
  display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap;
  padding: 10px 4px; text-decoration: none; color: var(--color-text); font-size: 13px;
}
.report-sheets a:hover { background: var(--color-primary-tint); }
.report-sheets .muted { font-size: 12px; }

/* 점검표(결과등록). 항목이 60개 넘게 늘어서는 표라, 한 줄이 짧고 촘촘해야 끝까지 읽힌다.
   항목명은 왼쪽에 붙이고 입력칸이 남는 폭을 다 쓴다. */
/* ★ 공통 .data-table 은 min-width:560px 이다 — 목록 표는 열이 많아 가로로 미는 편이 낫다는
   전제다(.table-wrap 이 감싼다). 점검표는 목록이 아니라 입력 폼이라 그 전제가 안 맞는다.
   그대로 두면 375px 화면에서 표가 560px 이 돼 옆으로 밀어야 하고, 항목이 60개 넘게
   이어지는 화면에서 그건 현장에서 한 손으로 못 쓴다. */
.report-table { table-layout: fixed; min-width: 0; }
/* ★ 공통 .data-table th 는 white-space:nowrap 이다(목록 표의 열 이름은 접히면 안 되니까).
   점검표의 첫 칸은 열 이름이 아니라 '풍향센서', 'AWS 다목적 영상관측' 같은 항목명이라
   접혀야 한다 — 안 접으면 표가 화면보다 넓어져 옆으로 밀린다(375px 에서 560px 이 됐다). */
.report-table th {
  text-align: left; font-weight: 600; width: 38%; vertical-align: middle;
  white-space: normal; word-break: keep-all;
}
.report-table td { vertical-align: middle; }

/* 입력칸. 이 표는 .stack-form 밖이라 공통 폼 모양이 안 걸린다 — 같은 모양으로 맞춘다.
   현장에서 장갑 낀 손으로 누르는 칸이라 공통 폼(8px)보다 한 단계 높인다. */
.report-table input[type="text"], .report-table textarea {
  width: 100%;
  max-width: 100%;
  min-height: 40px;
  padding: 9px 12px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-card);
  font-size: 14px;
  font-family: inherit;
  color: var(--color-text);
  box-sizing: border-box;
  transition: border-color .15s ease, box-shadow .15s ease, background-color .15s ease;
}
.report-table textarea { min-height: 76px; resize: vertical; line-height: 1.5; }
.report-table input[type="text"]:hover, .report-table textarea:hover {
  border-color: var(--color-border-strong, #cfd4dc);
}
.report-table input[type="text"]:focus, .report-table textarea:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 2px var(--color-primary-tint);
}
/* 단위 안내(Ω, 0.0 DCV …)는 값이 아니다 — 옅게 둬야 적어 넣은 값과 구분된다. */
.report-table input::placeholder { color: #aab0bb; }
/* 값이 들어 있는 칸은 한눈에 — 60줄을 훑을 때 어디까지 적었는지가 보여야 한다. */
.report-table input[type="text"]:not(:placeholder-shown):not(:focus) {
  border-color: var(--color-primary);
  background: #fbfcff;
}
.report-note {
  display: block; font-size: 11px; color: var(--color-muted); font-weight: 400;
  white-space: normal;
}

/* 한 줄에 칸이 여럿인 항목(소모품의 팬·리드스위치·베어링). 칸마다 이름을 앞에 붙이고
   남는 폭을 나눠 갖는다 — 원본이 한 줄에 두는 것들이라 여기서도 한 줄이어야 한다. */
.report-boxes { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.report-boxes label { display: flex; align-items: center; gap: 8px; flex: 1 1 120px; margin: 0; }
.report-boxes input[type="text"] { flex: 1 1 auto; min-width: 0; }
/* 칸 이름(팬·리드스위치·베어링)은 폭을 맞춰 둔다 — 안 맞추면 칸 시작점이 들쭉날쭉해진다. */
.report-sub {
  flex: 0 0 auto;
  font-size: 12px; color: var(--color-muted); white-space: nowrap;
}

/* 양호·무. 불량은 일부러 없다 — 결함부위·요청사항이 딸려야 뜻이 되는데 그건 원본 몫이다. */
/* 양호·무. 고른 쪽이 색으로 드러나야 60줄을 훑으며 빠진 곳을 찾을 수 있다.
   라디오 동그라미는 남겨둔다 — :has() 를 모르는 브라우저에서도 읽혀야 한다. */
.report-judge { display: flex; gap: 8px; }
.report-judge label {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  cursor: pointer; margin: 0;
  min-height: 40px; min-width: 88px; padding: 0 14px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-card);
  transition: border-color .15s ease, background-color .15s ease;
}
.report-judge label:hover { border-color: var(--color-primary); }
.report-judge input { margin: 0; accent-color: var(--color-primary); }
.report-judge span { font-size: 13px; }
.report-judge label:has(input:checked) {
  border-color: var(--color-primary);
  background: var(--color-primary-tint);
}
.report-judge label:has(input:checked) span { color: var(--color-primary); font-weight: 600; }
.report-judge label:focus-within { box-shadow: 0 0 0 2px var(--color-primary-tint); }

/* 저장 단추는 표 끝에 있다 — 항목이 길어 화면 밖으로 나가므로 위쪽에도 두지 않는다
   (위에 두면 다 채우기 전에 눌리기 쉽다). */
.report-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 14px; }

/* MOMS 로 내보내는 자리에 붙는 주의 문구. 되돌릴 수 없는 일이라 폼 바로 아래 둔다 —
   눌러버린 뒤에 읽는 안내는 안내가 아니다. */
.mtms-write-note { font-size: 12px; margin: 8px 0 0; }
.stack-form .req { color: var(--color-danger); font-size: 11px; font-weight: 600; margin-left: 4px; }
.mtms-shots form { margin-top: 6px; }

/* 방금 이어붙은 줄. 사이에 구분선을 긋는 대신 잠깐 떠오르게만 한다 — 구분선은 한 번
   더보기를 누를 때마다 남아 목록을 토막내지만, 이건 눈이 따라간 뒤 사라진다. */
.mtms-more-new { animation: mtms-more-in .3s ease-out; }
@keyframes mtms-more-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .mtms-more-new { animation: none; }
}

/* 현장사진(긴급보수 진척내역). 폭을 고정하지 않고 칸에 맞춰 접는다 — 사진이 한 장일 때도
   여덟 장일 때도 같은 규칙으로 놓인다. */
.mtms-shots { display: flex; flex-wrap: wrap; gap: 12px; }
.mtms-shots figure { margin: 0; width: 260px; max-width: 100%; }
.mtms-shots img {
  width: 100%;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  display: block;
}
.mtms-shots figcaption { font-size: 12px; color: var(--color-muted); margin-top: 6px; }
/* 파일명과 설명은 다른 것이다 — 한 줄로 이으면 설명이 파일명처럼 읽힌다.
   파일명은 더 흐리게 두어 눈이 설명으로 먼저 간다(원본도 그 순서다). */
.mtms-shots .shot-name { display: block; word-break: break-all; opacity: .75; }
.mtms-shots .shot-memo { display: block; margin-top: 2px; color: var(--color-text); }

/* 검색시각·보기 이동 단추 줄 (영상관측조회). 탭처럼 보이면 안 된다 — 탭은 '어디에
   있는지'를, 이건 '무엇을 볼지'를 바꾼다. 그래서 더 작고 촘촘하게 둔다. */
.mtms-steps { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.mtms-steps a {
  padding: 5px 10px;
  border: 1px solid var(--color-border);
  background: var(--color-card);
  border-radius: var(--radius-sm);
  font-size: 12px;
  color: var(--color-text);
  text-decoration: none;
  white-space: nowrap;
}
.mtms-steps a:hover { border-color: var(--color-primary); color: var(--color-primary); }
.mtms-steps a.on { background: var(--color-primary); border-color: var(--color-primary); color: #fff; }

/* 줄이 곧 사진 한 장인 목록(영상관측조회). 폭에 맞춰 저절로 접히게 두면 열 수를
   화면 크기마다 정해줄 필요가 없다. */
.mtms-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 14px; }
.mtms-card { margin: 0; }
.mtms-card img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-bg);
  display: block;
}
.mtms-card figcaption { display: flex; flex-wrap: wrap; gap: 8px; align-items: baseline; margin-top: 6px; font-size: 13px; }
.mtms-card figcaption .muted { font-size: 12px; }
.mtms-card a { text-decoration: none; color: inherit; }
/* 영상 보기(?media=video). 사진과 같은 틀이라 격자가 흔들리지 않는다 — 표지는 같은 시각의 사진. */
.mtms-card video {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  display: block;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: #000;
}
.mtms-card.is-novideo video { opacity: 0.45; }
/* 영상 수신 배지. 확인 전에는 흐리게, 들어왔으면 초록, 없으면 빨강 — 없는 쪽이 눈에 띄어야 한다. */
.mtms-clip-state {
  font-size: 11px; border-radius: 999px; padding: 1px 7px;
  color: var(--color-muted); background: var(--color-bg);
}
.mtms-clip-state[data-state="ok"] { color: var(--color-success); background: var(--color-success-tint); }
.mtms-clip-state[data-state="missing"] { color: var(--color-danger); background: var(--color-danger-tint); font-weight: 600; }
/* 제목 줄 오른쪽의 사진/영상 전환. 시각 이동 단추와 같은 모양이라 새로 배울 게 없다. */
.mtms-media { margin-bottom: 0; }
/* '영상 없음만'(?media=missing). 확인이 끝나 영상이 없다고 나온 카드와 확인 실패만 남긴다 —
   확인 중·영상 수신 카드는 접는다. 남는 카드는 전부 '없음'이라 흐리게 할 이유가 없다. */
.mtms-cards[data-media="missing"] .mtms-card:not(.is-novideo):not(.is-probe-error) { display: none; }
.mtms-cards[data-media="missing"] .mtms-card.is-novideo video { opacity: 1; }
.mtms-clip-summary { margin: 0 0 10px; font-size: 13px; }

/* 이 시각에 영상이 안 들어온 지점 (영상관측조회). 목록은 '있는 것'만 보여주므로,
   없는 것은 여기서 말해주지 않으면 화면 어디에도 나타나지 않는다.
   지점 하나가 칩 하나 — 개수를 눈으로 세지 않아도 한눈에 들어온다. */
.mtms-missing {
  margin-bottom: 14px;
  padding: 10px 12px;
  border: 1px solid var(--color-danger);
  border-left-width: 3px;
  border-radius: var(--radius-sm);
  background: var(--color-danger-tint);
}
.mtms-missing > strong { display: block; font-size: 13px; color: var(--color-danger); }
.mtms-missing ul {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.mtms-missing li {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 4px 9px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-card);
  font-size: 13px;
  white-space: nowrap;
}
.mtms-missing li .muted { font-size: 11px; }
.mtms-missing > p { margin: 8px 0 0; font-size: 11px; line-height: 1.5; }

.mtms-clip { width: 100%; max-width: 640px; border-radius: var(--radius-sm); background: #000; display: block; margin-bottom: 10px; }

/* MTMS 접속 준비 중 표시. 몇 %인지 알 수 없는 대기라 진행률이 아니라 '도는 중'만 보인다 —
   가짜 진행률을 그리면 90%에서 멈춰 더 답답해 보인다. */
.wait-bar { height: 4px; background: var(--color-border); border-radius: 2px; overflow: hidden; max-width: 420px; }
.wait-bar-fill { height: 100%; width: 40%; background: var(--color-primary); border-radius: 2px; animation: wait-slide 1.2s ease-in-out infinite; }
@keyframes wait-slide {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}

/* ── MTMS 화면의 폰 대응 (640px 이하) ────────────────────────────
   여기 규칙은 전부 폰에서 실제로 재보고 고친 것들이다(375px 기준). */
@media (max-width: 640px) {
  /* 서브탭 3개. 균등폭으로 나누면 긴 이름이 두 줄로 접혀 줄이 두 배로 두꺼워진다
     (실측: 탭 하나 52px). 글자 폭에 맞추고, 그래도 넘치면 옆으로 밀어 본다.
     (같은 처리가 필요한 상단 탭 줄은 .tabs-scroll 로 따로 있다 — MTMS 만의 것이 아니다.) */
  .tabs-sub { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .tabs-sub::-webkit-scrollbar { display: none; }
  .tabs-sub .tab-btn { flex: 0 0 auto; white-space: nowrap; padding: 8px 12px; }

  /* ★ 예외: 이력조회·청소이력의 기간 줄(#range-toggle). .tabs-sub 를 같이 쓰지만 탭이 아니라
     날짜 입력 둘 + 단추 셋이다. 위 규칙대로 밀면 폰(371px)에서 467px 이 돼 맨 끝 '전체' 가
     화면 밖으로 나간다 — 누를 것이 있는 줄도 모른다. 탭과 달리 순서가 뜻을 갖지 않으므로 접는다. */
  #range-toggle { flex-wrap: wrap; overflow-x: visible; row-gap: 8px; }

  /* 시각 이동 단추는 이 화면의 주 조작이다 — 손가락으로 누를 크기여야 한다(28px → 36px). */
  .mtms-steps { gap: 8px; }
  .mtms-steps a { padding: 9px 12px; font-size: 13px; }

  /* 사진은 두 장씩. 한 장씩 세우면 6장을 보는 데 화면을 네 번 넘겨야 한다. */
  .mtms-cards { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .mtms-card figcaption { display: block; font-size: 12px; line-height: 1.45; }
  .mtms-card figcaption .muted { display: block; }

  /* 현장사진은 반대다 — 볼 것이 사진 자체라 폭을 다 쓴다. */
  .mtms-shots figure { width: 100%; }

  /* 점검표는 항목명 40% + 입력칸으로는 폰에서 둘 다 좁다. 위아래로 쌓는다. */
  /* 폰에서는 표를 접는다 — 항목명과 입력칸을 위아래로. 옆으로 미는 화면은 현장에서
     한 손으로 못 쓴다. 항목이 60개 넘게 이어지므로 줄 사이 여백도 줄인다. */
  .report-table, .report-table tbody, .report-table tr,
  .report-table th, .report-table td { display: block; width: auto; }
  .report-table th { border-bottom: none; padding: 8px 12px 2px; }
  .report-table td { padding: 0 12px 10px; }
  .report-table tr { border-bottom: 1px solid var(--color-border); }
  .report-table tr:last-child { border-bottom: none; }

  /* ★ 폰의 입력칸 글자는 16px 이어야 한다. 그보다 작으면 iOS 사파리가 칸을 누를 때마다
     화면을 확대해버리고, 다음 칸으로 넘어갈 때마다 손으로 되돌려야 한다. */
  .report-table input[type="text"], .report-table textarea {
    font-size: 16px; min-height: 44px; padding: 11px 12px;
  }
  .report-table textarea { min-height: 88px; }

  /* 판정은 손가락으로 누른다 — 화면 폭을 절반씩 나눠 갖게 둔다. */
  .report-judge label { flex: 1 1 0; min-height: 44px; padding: 0 10px; }
  .report-judge span { font-size: 14px; }

  /* 한 줄에 칸이 여럿인 것(소모품의 팬·리드스위치·베어링)은 폰에서도 한 줄로 둔다.
     들어가는 값이 숫자 한두 자리라 칸이 넓을 이유가 없다 — 위아래로 쌓으면 세 줄을
     먹고, 60개 항목이 이어지는 화면에서 그 세 줄이 그대로 스크롤이 된다.
     대신 칸 이름을 옆이 아니라 위로 올린다. 옆에 두면 '리드스위치' 다섯 글자가
     칸보다 넓어져 정작 적을 자리가 없다. */
  .report-boxes { gap: 8px; }
  .report-boxes label {
    flex: 1 1 78px; min-width: 0;
    flex-direction: column; align-items: stretch; gap: 3px;
  }
  .report-sub { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  /* 숫자 한두 자리라 가운데가 읽기 편하다 */
  .report-boxes input[type="text"] { text-align: center; padding-left: 6px; padding-right: 6px; }

  /* 더보기는 목록 끝에서 손가락으로 누르는 단추다 — 폭을 다 주고, 건수는 그 아래로 내린다
     (옆에 붙이면 단추가 좁아진다). */
  .mtms-more { gap: 8px; }
  .mtms-more .btn { flex: 1 0 100%; padding: 12px 18px; }
}

/* ============================================================
   홈 현황판 (web/templates/index.html · web/home_dashboard.py)

   원칙 하나가 이 블록 전체를 정한다 — **이상이 없으면 조용하다.**
   숫자가 0 인 칸은 회색(quiet)이고 왼쪽 띠도 얇다. 늘 색이 들어와 있으면 사람이 그 색을
   안 보게 되는데, 이 프로젝트에서 이미 두 번 겪었다(영상관측 미수신을 방금 시각으로
   견줬을 때 매번 네댓 곳, 차트 뒤처짐을 축 끝으로 쟀을 때 101곳 중 75곳).
   색만으로 말하지도 않는다 — 띠의 '두께'가 흑백 출력·색각이상에서도 남는다.
   ============================================================ */

/* ── 상태 스트립 ─────────────────────────────────────────────── */
.hb-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 12px;
  margin-bottom: 16px;
}
.hb-tile {
  position: relative;
  display: block;
  padding: 14px 16px 13px;
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  color: inherit;
  overflow: hidden;
}
.hb-tile::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  background: var(--color-border);
}
.hb-tile[data-level="warn"]::before  { width: 5px; background: var(--color-warning); }
.hb-tile[data-level="alert"]::before { width: 5px; background: var(--color-danger); }

/* 누를 수 있는 칸. 색을 바꾸지 않는다 — 이 칸은 색으로 '이상 있음'을 말하는 자리라
   링크 색이 섞이면 그 신호가 흐려진다. 눌리는 것은 테두리와 그림자로만 알린다. */
.hb-tile.is-link { text-decoration: none; transition: border-color .12s, box-shadow .12s; }
.hb-tile.is-link:hover { border-color: var(--color-primary); box-shadow: 0 2px 8px rgba(16, 24, 40, .08); }
.hb-tile.is-link:active { transform: translateY(1px); }
.hb-tile.is-link:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

.hb-tile-label {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  font-size: 12px;
  color: var(--color-muted);
  margin-bottom: 4px;
}
/* 제목이 길어져도 시각을 밀어내지 않는다 — 밀리는 쪽은 제목이다(모바일에서 한 칸이 좁다). */
.hb-tile-label .t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 갱신 시각 — 제목과 같은 줄 오른쪽 끝. 숫자보다 흐리게 둬야 건수를 먼저 읽는다. */
.hb-tile-at {
  flex: none;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  opacity: .75;
}
.hb-tile-num {
  display: flex;
  align-items: baseline;
  gap: 3px;
  font-size: 28px;
  font-weight: 700;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  color: var(--color-muted);
}
.hb-tile-num .unit { font-size: 13px; font-weight: 500; }
.hb-tile-num .hb-unknown { font-weight: 400; }
.hb-tile[data-level="warn"]  .hb-tile-num { color: var(--color-warning); }
.hb-tile[data-level="alert"] .hb-tile-num { color: var(--color-danger); }
.hb-tile-sub {
  display: block;
  margin-top: 4px;
  font-size: 12px;
  color: var(--color-muted);
  min-height: 18px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── 2단 배치 ────────────────────────────────────────────────── */
.hb-cols { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); gap: 16px; align-items: start; }
.hb-col { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
/* .section 의 기본 여백은 세로로 쌓는 화면용이다 — 여기서는 gap 이 그 역할을 한다. */
.hb-panel { margin: 0; padding: 0; overflow: hidden; }
.hb-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--color-border);
}
.hb-panel-head h2 { margin: 0; font-size: 14px; }
.hb-body { padding: 14px 16px; }
.hb-panel .empty { padding: 22px 16px; }

/* ── 조치 목록 ───────────────────────────────────────────────── */
/* 묶음 머리(장애 · 수시 미완료). 줄 하나를 통째로 쓰기엔 아까운 정보라 얇은 띠로 둔다 —
   목록 안에서 '여기부터 결이 다르다'만 말하면 된다. */
.hb-group {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 7px 16px;
  background: #fafbfc;
  border-top: 1px solid var(--color-border);
  font-size: 12px;
}
.hb-rows + .hb-group { border-top: 1px solid var(--color-border); }
.hb-group-name { font-weight: 600; }
.hb-group[data-level="alert"] .hb-group-name { color: var(--color-danger); }
.hb-group[data-level="warn"] .hb-group-name { color: var(--color-warning); }
.hb-group-n { color: var(--color-muted); font-variant-numeric: tabular-nums; }
/* 묶음 머리의 꼬리말(장애 의심의 '어디까지 들어온 자료인가'). 이름과 건수 사이에서
   남는 폭만 쓰고, 좁으면 잘린다 — 건수가 밀려나면 묶음 머리로서 못 쓴다. */
.hb-group-note {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0 10px;
  font-size: 11.5px;
  font-weight: 400;
  color: var(--color-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 지점명 링크. 줄 안에서 이름만 눌리는 자리라 평소에는 본문처럼 두고, 손을 올렸을 때만
   밑줄로 알린다 — 목록 전체가 파란 글씨가 되면 무엇이 이상 신호인지 흐려진다. */
.hb-row-link { color: inherit; text-decoration: none; }
.hb-row-link:hover { text-decoration: underline; text-underline-offset: 2px; }
.hb-row-link:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

.hb-rows { list-style: none; margin: 0; padding: 0; }
.hb-panel-head + .hb-group { border-top: 0; }
.hb-rows li + li { border-top: 1px solid var(--color-border); }
.hb-row {
  display: grid;
  grid-template-columns: 4px minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 10px 16px 10px 12px;
  color: inherit;
}
.hb-row-mark { width: 4px; height: 26px; border-radius: 2px; background: var(--color-border); }
.hb-row[data-level="alert"] .hb-row-mark { background: var(--color-danger); }
.hb-row[data-level="warn"]  .hb-row-mark { background: var(--color-warning); }
.hb-row-main { min-width: 0; }
.hb-row-title { font-size: 13.5px; font-weight: 500; }
.hb-row-title .code { font-size: 12px; color: var(--color-muted); margin-left: 4px; }
/* 그 지점의 자료처리기(로거) 모델·제조사. 곁들이는 정보라 지점명보다 한 톤 낮춘다.
   ★ 통째로 다음 줄로 내려가게 둔다(nowrap) — 좁은 폭에서 모델 이름 가운데가 끊기면
     'HJIR-'/'600W' 처럼 갈려 무슨 모델인지 읽을 수 없게 된다. */
.hb-row-gear {
  margin-left: 8px;
  font-size: 11.5px;
  font-weight: 400;
  color: var(--color-muted);
  white-space: nowrap;
}
.hb-row-desc {
  display: block;
  font-size: 12px;
  color: var(--color-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 데스크톱에서는 근거를 끝까지 보인다 — 장애 의심 한 줄에 근거가 여럿 이어지면
   ('습도 · 4번 값 튐 / 기온 · 2번 값 튐 / 전 항목 · 어제 같은 시각 값으로 96분 …')
   뒤쪽이 잘려 정작 결정적인 근거를 못 본다. 폰은 줄이 길어지면 목록이 끝없이 늘어나므로
   종전대로 한 줄에서 자른다(≤900px 은 아래 미디어 쿼리 폭과 같다).
   ★ keep-all — 한글 낱말 가운데서 끊기지 않게 한다('산발적 / 미수신'처럼 낱말 단위로 넘어간다). */
@media (min-width: 901px) {
  .hb-row-desc {
    overflow: visible;
    white-space: normal;
    word-break: keep-all;
    overflow-wrap: anywhere;
  }
}
.hb-row-when {
  font-size: 11.5px;
  color: var(--color-muted);
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.hb-row-when b { display: block; color: var(--color-text); font-weight: 600; }

/* 눌러서 더 볼 것이 있는 줄(수시의 기본정보). 목록 안에서 '이 줄은 더 볼 게 있다'만
   말하면 되므로 손 모양과 옅은 바탕까지다(정기점검 펼침 단추와 같은 어법).
   ★ 글자('기본정보')를 함께 둔다 — 손 모양은 손을 올려야 알 수 있는데, 폰에는 그 '올림'이
     없어서 누를 수 있다는 것을 알 방법이 사라진다. */
.hb-row.is-open { cursor: pointer; }
.hb-row.is-open:hover { background: #f7f9fb; }
.hb-row.is-open:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -2px; }
.hb-row-more { margin-left: 6px; font-size: 11px; color: var(--color-primary); }
/* 줄 옆에 서버가 그려 두는 모달 조각. 옮겨 담기 전까지는 아무 자리도 차지하지 않는다. */
.hb-info { display: none; }
/* 모달 맨 아래 한 줄 — 언제 적어 둔 값인가. 홈은 MTMS 를 부르지 않으므로 여기 뜨는 표는
   마지막 갱신 회차의 것이다. 그 말이 없으면 방금 받아온 것으로 읽힌다. */
.modal-foot {
  margin: 14px 0 0;
  padding-top: 10px;
  border-top: 1px solid var(--color-border);
  font-size: 11.5px;
  color: var(--color-muted);
}
.modal-link-row { margin: 12px 0 0; }
.modal-link { font-size: 12.5px; }
/* 모달 맨 위에 서는 안내 한 줄('아직 상세를 못 받아왔다'). 목록에서 쓰는 여백은 위가
   비어 있을 때의 것이라, 여기서는 아래로 돌려준다 — 표와 붙어 있어야 표에 대한 말로 읽힌다. */
.modal-body .hb-note { margin: 0 0 12px; }

/* ── 장애 의심의 갈래 줄 ─────────────────────────────────────────
   근거끼리 나눠 접어 둔 갈래(산발 미수신·튀는 값·값 고정·…). 목록 줄과 같은 격자를 쓴다
   (왼쪽 4px 띠 + 본문 + 오른쪽 숫자) — 같은 목록 안이라 결이 갈리면 다른 것으로 읽힌다.
   눌리는 자리라는 것은 손 모양과 삼각형, 손을 올렸을 때의 옅은 바탕까지만 말한다. */
.hb-cat {
  display: grid;
  grid-template-columns: 4px auto minmax(0, 1fr) auto 12px;
  gap: 10px;
  align-items: center;
  width: 100%;
  padding: 9px 16px 9px 12px;
  border: 0;
  border-top: 1px solid var(--color-border);
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color .12s ease;
}
.hb-cat:hover, .hb-cat[aria-expanded="true"] { background: #f7f9fb; }
.hb-cat:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -2px; }
.hb-cat-mark { width: 4px; height: 20px; border-radius: 2px; background: var(--color-border); }
.hb-cat[data-level="warn"] .hb-cat-mark { background: var(--color-warning); }
.hb-cat-name { font-size: 13px; font-weight: 600; }
.hb-cat[data-level="warn"] .hb-cat-name { color: var(--color-warning); }
/* 접힌 채로도 무엇이 들었는지 한 조각은 보인다 — 남는 폭만 쓰고, 좁으면 잘린다. */
.hb-cat-hint {
  font-size: 12px;
  color: var(--color-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.hb-cat-n { font-size: 12px; color: var(--color-muted); font-variant-numeric: tabular-nums; }
.hb-cat .hb-caret { margin-left: 0; }
.hb-cat[aria-expanded="true"] .hb-caret { transform: rotate(180deg); }

/* ── 점검 실적 ───────────────────────────────────────────────── */
/* 이번 달 실적 — 지상 한 칸 · 도로 한 칸 · 청소 한 칸을 **세로로** 쌓는다.
   이 칸이 사는 자리가 오른쪽 열(1fr, 1440px 화면에서 450px 남짓)이라 칸을 가로로 세울 수가
   없다: 지상·도로를 나란히만 두어도 한 칸이 220px 로 눌리고, 그 안의 셋(장애·정기·수시)이
   다시 삼등분되어 58px가 되면서 '지난달 47건'이 두 줄로 접혔다(실측).
   세로로 쌓으면 어느 폭에서든 셋이 130px 이상을 갖는다. 두 칸의 크기가 같은 것도 그대로다 —
   한쪽을 작게 두면 '총계의 내역'으로 읽혀 밀린 쪽을 놓친다. */
.hb-checks { display: grid; grid-template-columns: minmax(0, 1fr); }
.hb-check { padding: 14px 16px; min-width: 0; }
.hb-check + .hb-check { border-top: 1px solid var(--color-border); }
.hb-check-label { display: block; font-size: 12px; color: var(--color-muted); }
.hb-check-num {
  display: block;
  margin-top: 2px;
  font-size: 22px;
  font-weight: 700;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
}
.hb-check-num .unit { font-size: 12px; font-weight: 500; color: var(--color-muted); margin-left: 2px; }
/* 한 칸 안에 장애·정기·수시 셋을 같은 크기로 나란히 둔다 — 셋 다 보고서가 따로 나가는
   실적이라, 하나를 크게 두면 나머지가 곁들이로 읽힌다. */
.hb-trio { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 4px; }
.hb-trio > div { min-width: 0; }
.hb-trio .who { display: block; font-size: 11.5px; color: var(--color-muted); }
.hb-trio .n {
  display: block;
  font-size: 20px; font-weight: 700; line-height: 1.25;
  font-variant-numeric: tabular-nums;
}
.hb-trio .n .unit { font-size: 11.5px; font-weight: 500; color: var(--color-muted); margin-left: 1px; }
/* 도로 숫자만 색을 달리해 세로로 훑을 때 같은 계열끼리 묶여 보이게 한다. */
.hb-check.is-road .hb-trio .n { color: var(--color-road); }
/* 0 은 조용히 — 다만 자리는 지킨다. 칸이 사라지면 '원래 없는 항목'으로 읽혀
   빠뜨린 것을 못 알아챈다. */
.hb-trio .n.is-zero, .hb-check.is-road .hb-trio .n.is-zero { color: var(--color-muted); font-weight: 600; }
.hb-trio .p { display: block; font-size: 11px; color: var(--color-muted); }
/* '남은 곳'은 지난달 비교와 달리 **지금 손이 갈 일**이다 — 옅은 회색에 묻히지 않게 한 칸
   올려 둔다(경고색까지는 쓰지 않는다. 밀린 게 아니라 이번 달 차례라는 뜻이다). */
.hb-trio .p.is-left { color: var(--color-text); font-weight: 600; }

/* 장비 구분 꼬리표. 이상 신호가 아니라 분류라 옅게 둔다. */
.hb-tag {
  display: inline-block;
  font-size: 10px; padding: 1px 6px; border-radius: 999px; white-space: nowrap;
}
.hb-tag.is-ground { background: var(--color-primary-tint); color: var(--color-primary); }
.hb-tag.is-road { background: var(--color-road-tint); color: var(--color-road); }
/* 지금 붙어 있는 사람. 이름 옆에서 상태를 말하고, 오른쪽 시각은 '마지막으로 다녀간 때'로
   비워 둔다 — 그 자리에 '접속 중'을 넣으면 지금 있는 사람의 시각이 사라진다. */
.hb-tag.is-live { background: var(--color-success-tint); color: var(--color-success); margin-left: 5px; }
/* 장애종류 꼬리표(비장애중단 …). 분류일 뿐이라 줄의 무게를 뺏지 않게 가장 옅게 둔다. */
.hb-tag.is-muted { background: #f0f2f5; color: var(--color-muted); margin-left: 5px; }

/* 청소만 분모가 있다(49곳 = 한 바퀴). 분자와 분모의 크기를 갈라 '완료 / 목표'로 읽히게 한다. */
.hb-check-num .of { font-size: 15px; font-weight: 600; color: var(--color-muted); margin-left: 3px; }
.hb-check-prev { display: block; margin-top: 2px; font-size: 11.5px; color: var(--color-muted); }

/* 두 바퀴 도는 달(5·6·9·10월) 꼬리표. 이상 신호가 아니라 '이 달은 규칙이 다르다'는 표시라
   경고색을 쓰되 배지로 눌러 둔다. 지금 몇 회차인지는 이 배지가 아니라 꼬리말이 말한다. */
.hb-twice {
  font-size: 10px; margin-left: 5px; padding: 1px 6px; border-radius: 999px;
  background: var(--color-warning-tint); color: var(--color-warning);
  vertical-align: 1px;
}

/* 진행바 — 이번 바퀴의 진척만 그린다. 회차가 바뀌면 0 부터 다시 차고, 몇 회차인지는
   막대 아래 꼬리말이 말한다('2회차 진행중'). */
.hb-track {
  display: flex;
  height: 6px;
  margin-top: 7px;
  border-radius: 3px;
  background: #fafbfc;
  border: 1px solid var(--color-border);
  overflow: hidden;
}
.hb-fill { height: 100%; background: var(--color-primary); }
.hb-fill.is-done { background: var(--color-success); }
/* 도로 막대는 도로 색으로 찬다 — 위 도로 칸의 숫자와 같은 계열이라 세로로 훑을 때 묶여
   보인다. 다 채운 막대는 둘 다 완료색(초록)으로 넘어간다. */
.hb-fill.is-road { background: var(--color-road); }

/* 마지막 칸만 반으로 갈린다 — 도로 정기점검 | 청소. 분모가 서 있는 둘이고, 둘 다
   '숫자 · 막대 · 꼬리말' 석 줄이라 같은 높이로 선다.
   ★ 실적 칸을 넷으로 늘리는 대신 한 칸을 가른 이유: 이 패널이 길어지면 오른쪽 열 아래의
     당직·접속 현황이 그만큼 접힌 화면 밖으로 밀린다.
   가운데 세로줄은 칸 사이 가로줄(.hb-check + .hb-check)과 같은 색이다 — 둘이 한 칸 안의
   서로 다른 실적임을 같은 문법으로 말한다. */
.hb-duo { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }
.hb-duo-half { min-width: 0; }
.hb-duo-half + .hb-duo-half {
  padding-left: 14px;
  border-left: 1px solid var(--color-border);
}
.hb-duo-half.is-road .hb-check-num { color: var(--color-road); }
/* 누르면 남은 곳이 펼쳐지는 반쪽. 단추가 되어도 **생김새는 칸 그대로**여야 한다 — 여기서
   테두리나 배경을 주면 옆의 여섯 자리(장애·정기·수시)와 결이 갈려 '이 둘만 다른 것'으로
   읽힌다. 알리는 것은 손 모양과 작은 삼각형, 그리고 손을 올렸을 때의 옅은 바탕까지다.
   ★ 음수 여백으로 글자를 제자리에 둔 채 누를 수 있는 넓이만 키운다. 라벨만 눌리는 단추는
     모바일에서 잘 안 맞는다. */
.hb-duo-half.is-click {
  display: block;
  width: 100%;
  margin: -8px -10px;
  padding: 8px 10px;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color .12s ease;
}
.hb-duo-half.is-click:hover { background: #f1f4f9; }
.hb-duo-half.is-click:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
/* 반쪽 둘이 다 단추면 음수 여백만큼 가운데 선 쪽으로 붙는다 — 그만큼 되돌린다. */
.hb-duo-half.is-click + .hb-duo-half.is-click { margin-left: 4px; }
.hb-caret {
  display: inline-block;
  width: 0; height: 0;
  margin-left: 5px;
  vertical-align: 2px;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid var(--color-muted);
  transition: transform .15s ease;
}
.hb-duo-half[aria-expanded="true"] .hb-caret { transform: rotate(180deg); }

/* 펼친 목록. 칸이 아니라 **칸에 딸린 것**이라 바탕을 반 톤 낮춰 실적 칸들과 갈라 둔다. */
.hb-drawer { border-top: 1px solid var(--color-border); background: #fcfdfe; }
.hb-drawer[hidden] { display: none; }
.hb-drawer .hb-row { padding-top: 8px; padding-bottom: 8px; }
.hb-drawer-more {
  display: block;
  width: 100%;
  padding: 9px 16px;
  border: 0;
  border-top: 1px solid var(--color-border);
  background: none;
  color: var(--color-primary);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.hb-drawer-more:hover { background: #f1f4f9; }
.hb-drawer-more:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -2px; }
/* 무엇을 세었는지 밝히는 꼬리말. 목록이 '왜 이 곳들인가'를 여기서만 말한다. */
.hb-drawer-note {
  margin: 0;
  padding: 9px 16px;
  border-top: 1px solid var(--color-border);
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--color-muted);
}
/* 꼬리표와 이름을 한 줄에 두면 '도로 정기점검'이 좁은 반쪽에서 접힌다 — 접히더라도 꼬리표가
   이름과 떨어지지 않게 붙여 둔다. */
.hb-duo-half .hb-check-label .hb-tag { margin-right: 4px; }

/* ── 오른쪽 칸의 값 줄 ───────────────────────────────────────── */
.hb-kv { display: flex; flex-direction: column; gap: 8px; }
.hb-kv .kv { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 12.5px; }
.hb-kv .kv .k { color: var(--color-muted); white-space: nowrap; }
.hb-kv .kv .v { font-weight: 500; font-variant-numeric: tabular-nums; text-align: right; }
/* 한 묶음이 여러 날이면(주말 토·일, 연휴) 줄을 바꿔 쌓는다 — 한 줄에 이어 붙이면
   누가 어느 날인지 흐려진다. */
.hb-kv .kv .v .hb-duty-one { display: block; }
.hb-kv .kv .v .hb-duty-one + .hb-duty-one { margin-top: 3px; }
/* 이름 뒤에 붙는 날짜 — 이름을 먼저 읽어야 하므로 흐리고 작게. */
.hb-kv .kv .v .hb-when { margin-left: 6px; font-weight: 400; font-size: 11.5px; color: var(--color-muted); }
.hb-kv .kv .v.is-warn { color: var(--color-warning); }
.hb-kv .kv .v.is-ok { color: var(--color-success); }

/* 칸 머리에 붙는 작은 단추 줄(이번 달 수집: 장애·정기·수시·청소·전체).
   좁은 화면에서는 제목 아래로 접힌다 — 다섯을 한 줄에 욱여넣으면 글자가 뭉갠다. */
.hb-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 5px; }
.hb-actions-label { font-size: 11.5px; color: var(--color-muted); margin-right: 1px; }
.hb-actions .btn { padding: 3px 9px; }

/* 칸 아래 한 줄 짜리 알림. 값들과 같은 칸 안에 있으므로 위쪽 여백만 둔다. */
.hb-note { margin: 10px 0 0; font-size: 11.5px; color: var(--color-muted); }
/* 칸 몸통(.hb-body) 밖에 서는 안내 한 줄 — 몸통이 주던 여백을 스스로 챙긴다. */
.hb-note-pad { margin: 0; padding: 10px 16px 0; }
.hb-note.is-warn { color: var(--color-warning); }

.hb-duty {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-bottom: 12px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--color-border);
}
.hb-duty-badge {
  flex: 0 0 auto;
  width: 44px; height: 44px;
  display: grid;
  place-content: center;
  text-align: center;
  line-height: 1.15;
  border-radius: var(--radius-sm);
  background: var(--color-primary-tint);
  color: var(--color-primary);
}
.hb-duty-badge b { font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; }
.hb-duty-badge i { font-size: 10px; font-style: normal; }
.hb-duty-who b { display: block; font-size: 15px; font-weight: 600; }
.hb-duty-who i { font-size: 12px; font-style: normal; color: var(--color-muted); }

/* ── 바로가기 ────────────────────────────────────────────────── */
.hb-shortcuts { margin-top: 20px; }
.hb-shortcuts-head {
  font-size: 11px;
  letter-spacing: .1em;
  color: var(--color-muted);
  margin-bottom: 10px;
}

@media (max-width: 900px) {
  /* 한 줄로 서면 왼쪽 칸(조치 대기)이 먼저고 오른쪽 칸(실적·당직·접속 현황)이 뒤다 —
     급한 것이 위에 온다. 실적이 오른쪽 칸 안에 있어 따로 순서를 세울 일이 없다. */
  .hb-cols { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 640px) {
  .hb-strip { grid-template-columns: repeat(2, 1fr); gap: 8px; }
  /* 반쪽 둘이 서기엔 좁다 — 세로로 쌓고, 가르는 선도 세로줄에서 가로줄로 바꾼다. */
  .hb-duo { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .hb-duo-half + .hb-duo-half {
    padding-left: 0;
    padding-top: 12px;
    border-left: 0;
    border-top: 1px solid var(--color-border);
  }
  /* 세로로 쌓이면 단추의 음수 여백이 칸 밖으로 삐져나온다 — 그 자리에선 여백을 되돌린다.
     (뒤에 두어야 위의 `padding-left: 0` 을 덮는다 — 같은 무게라 순서로 갈린다.) */
  .hb-duo-half.is-click { margin: 0; padding: 6px 8px; }
  .hb-duo-half.is-click + .hb-duo-half.is-click { margin-left: 0; padding-top: 16px; }
  /* 제목과 단추 줄이 한 줄에 다 안 들어간다 — 단추를 아래로 내리고 왼쪽에 맞춘다. */
  .hb-panel-head { flex-wrap: wrap; }
  .hb-actions { width: 100%; }
  .hb-tile { padding: 11px 12px 10px; }
  .hb-tile::before { width: 3px; }
  .hb-tile[data-level="warn"]::before, .hb-tile[data-level="alert"]::before { width: 4px; }
  .hb-tile-num { font-size: 23px; }
  /* 갈래 줄에서 대표 이름은 접는다 — 이름·건수·삼각형까지 넣으면 '삼척미로 외 7곳'이
     '삼…'으로 잘려 아무 말도 못 하는 자리가 된다. 한 번 누르면 바로 보이는 것들이다.
     (빈 칸은 그대로 남아 건수를 오른쪽 끝에 세운다 — 목록 줄의 시각과 같은 선이다.) */
  .hb-cat-hint { display: none; }
  /* 날짜를 오른쪽에 붙이면 제목이 눌린다 — 아래로 내린다. */
  .hb-row { grid-template-columns: 4px minmax(0, 1fr); }
  .hb-row-when { grid-column: 2; text-align: left; }
  .hb-row-when b { display: inline; margin-right: 6px; }
}

/* ── 대시보드 표시 설정(/dashboard) ─────────────────────────────────────
   항목마다 '모두 · 관리자만 · 안 함' 셋 중 하나다. 드롭다운으로 두면 지금 무엇이 골라져
   있는지 한 줄씩 눌러 봐야 알 수 있어서, 셋을 펼친 조각단추로 둔다 — 다섯 줄을 한눈에
   훑으면 '무엇이 감춰져 있나'가 글자를 읽기 전에 색으로 먼저 읽힌다.
   ★ 라디오는 자리만 없애고(opacity:0) display:none 으로 지우지 않는다 — 지우면 키보드
     tab 이 그 칸을 통째로 건너뛴다. 초점 테두리는 :focus-visible 로 라벨에 옮겨 그린다. */
.dash-list { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; }
.dash-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px 16px;
  flex-wrap: wrap;
  padding: 12px 14px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-bg);
}
.dash-name { display: flex; flex-direction: column; gap: 3px; flex: 1 1 260px; min-width: 200px; }
.dash-name b { font-size: 14px; }
.dash-desc { font-size: 12px; color: var(--color-muted); line-height: 1.5; }

/* 묶음 이름(상태 스트립). 줄들을 안쪽으로 들이고 왼쪽에 띠를 둬, 넷이 한 덩어리에서 갈려
   나온 칸이라는 것을 글자를 읽기 전에 알아보게 한다. */
.dash-group { margin: 14px 0 2px; font-size: 13px; font-weight: 600; }
.dash-group:first-child { margin-top: 0; }
.dash-group span { font-weight: 400; font-size: 12px; color: var(--color-muted); }
.dash-row.is-grouped { margin-left: 14px; border-left: 3px solid var(--color-primary-tint); }

.seg {
  display: inline-flex;
  flex-shrink: 0;
  padding: 3px;
  background: var(--color-card);
  border: 1px solid var(--color-border);
  border-radius: 999px;
}
.seg-opt { position: relative; display: flex; }
.seg-opt input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.seg-opt > span {
  display: block;
  padding: 6px 14px;
  border-radius: 999px;
  font-size: 13px;
  color: var(--color-muted);
  white-space: nowrap;
  transition: background .15s ease, color .15s ease;
}
.seg-opt input:hover + span { color: var(--color-text); }
.seg-opt input:focus-visible + span { box-shadow: 0 0 0 2px var(--color-primary-tint); }
.seg-opt input:checked + span { font-weight: 600; }
/* 고른 값마다 색이 다르다 — 셋 다 같은 색이면 '열린 것'과 '감춘 것'을 글자로 세어야 한다. */
.seg-opt input[value="all"]:checked + span { background: var(--color-success-tint); color: var(--color-success); }
.seg-opt input[value="admin"]:checked + span { background: var(--color-warning-tint); color: var(--color-warning); }
.seg-opt input[value="off"]:checked + span { background: var(--color-border); color: var(--color-text); }

.dash-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-top: 14px; }

/* 표시와 상관없이 권한이 가르는 것들. 표로 그리면 값이 화면 오른쪽 끝까지 밀려나
   무엇에 대한 설명인지 눈으로 이어 붙여야 한다 — 이름 바로 옆에 붙여 둔다. */
.dash-notes { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
.dash-note { font-size: 13px; line-height: 1.6; }
.dash-note b { margin-right: 8px; }
.dash-note .who { font-weight: 600; color: var(--color-warning); }
.dash-note .why { color: var(--color-muted); }

@media (max-width: 640px) {
  /* 폰에서는 셋이 줄 하나를 똑같이 나눈다. span 까지 늘려야 고른 칸의 색이 제 몫만큼
     칠해진다 — 라벨만 늘리면 색이 글자 폭에만 남아 가운데가 아닌 것처럼 보인다. */
  .seg { width: 100%; }
  .seg-opt { flex: 1; }
  .seg-opt > span { flex: 1; padding: 7px 4px; text-align: center; }
}

/* ── 게시판 (board.html / board_view.html / board_form.html) ───────────────
   목록은 앱 공통 .data-table 을 그대로 쓰고, 게시판에만 필요한 것(분류 탭·번호·조회·
   댓글 수)만 여기서 더한다. */
.board-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.board-tabs .tab-btn { flex: 0 0 auto; padding: 6px 14px; font-size: 12px; border-radius: 999px; }
.board-count { font-size: 11px; color: var(--color-muted); margin-left: 2px; }
.board-tabs .tab-btn.active .board-count { color: inherit; }

.board-bar { align-items: center; gap: 8px; }
.board-search { display: flex; gap: 8px; flex: 1; min-width: 0; }
.board-field {
  padding: 8px 10px; border: 1px solid var(--color-border); border-radius: var(--radius-sm);
  background: var(--color-card); color: var(--color-text); font-size: 13px; font-family: inherit;
}

/* 목록은 표가 아니라 '줄'이다.
   표였을 때는 제목 한 줄만 보여 열어봐야 아는 글이 너무 많았고, 폰에서는 고정폭 여섯 칸이
   제목에 69px 밖에 남기지 않아 카드로 접어야 했다(그 규칙들도 함께 걷어냈다).
   이제 한 줄은 [분류] 제목 · 본문 두 줄 · 쓴 사람/시각/조회 로 선다. */
.post-list { display: flex; flex-direction: column; }
.post-row {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 13px 4px; border-bottom: 1px solid var(--color-border);
}
.post-row:last-child { border-bottom: none; }
.post-row:hover { background: var(--color-bg); }
/* 공지는 목록 안에서 바로 갈라 보여야 한다 — 배지 하나로는 훑을 때 묻힌다. */
.post-row.is-notice { background: var(--color-warning-tint); }

.post-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.post-row-title {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  color: var(--color-text); text-decoration: none; font-size: 14px;
}
.post-row-title .t { font-weight: 600; }
.post-row-title:hover .t { color: var(--color-primary); text-decoration: underline; }
.post-row-title .badge { margin-left: 0; }
/* 댓글 수는 제목 뒤에 붙는 작은 숫자다(색이 있어야 제목 글자와 섞이지 않는다). */
.post-row-cmt {
  font-size: 12px; font-weight: 700; color: var(--color-primary);
  font-variant-numeric: tabular-nums;
}
.post-row-clip {
  display: inline-flex; align-items: center; gap: 2px;
  font-size: 11px; color: var(--color-muted);
}
/* 본문 미리보기는 정확히 두 줄에서 끊는다 — 세 줄이 되면 줄 높이가 들쭉날쭉해져
   목록을 훑는 눈이 걸린다. */
.post-row-snip {
  margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--color-muted);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; word-break: break-word;
}
.post-row-meta {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  font-size: 11.5px; color: var(--color-muted); font-variant-numeric: tabular-nums;
}
.post-row-who { color: var(--color-text); font-weight: 500; }
.post-row-sep { opacity: 0.45; }
.post-row-del { flex: none; margin: 0; }
/* 목록에서 바로 지우는 단추(관리자만). 눈에 먼저 걸리면 곤란한 자리라 평소엔 흐리게 두고
   손이 닿았을 때만 붉어진다. */
.post-row-del button {
  padding: 2px 8px; font-size: 11px; font-family: inherit; cursor: pointer;
  border: 1px solid var(--color-border); border-radius: 6px;
  background: var(--color-card); color: var(--color-muted);
}
.post-row-del button:hover {
  color: var(--color-danger); border-color: var(--color-danger);
}

/* 아바타는 이름 첫 글자다. 사진을 둘 자리가 아니라서, 줄마다 눈이 걸릴 점 하나만 만든다. */
.post-avatar, .comment-avatar {
  display: inline-grid; place-items: center; flex: none;
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--color-primary-tint); color: var(--color-primary);
  font-size: 10.5px; font-weight: 700;
}
.comment-avatar { width: 24px; height: 24px; font-size: 11px; }

.board-heading { font-size: 15px; margin: 0 0 10px; }
.board-heading-n { color: var(--color-muted); font-weight: 400; font-size: 13px; }
/* 목록을 갈아끼우는 동안. 내용을 비우지 않고 흐리기만 한다 — 비우면 화면이 한 번 접혔다
   펴지면서 스크롤 자리가 튄다. */
#board-list.is-loading { opacity: 0.5; pointer-events: none; }

/* 페이지 이동은 <a> 다(주소가 있어야 새로고침·뒤로가기가 자리를 지킨다).
   공통 .pagination 규칙은 <button> 을 겨냥하고 있어 링크 몫을 여기서 맞춘다. */
.pagination a {
  min-width: 32px; padding: 6px 8px; text-align: center;
  border: 1px solid var(--color-border); background: var(--color-card);
  border-radius: var(--radius-sm); font-size: 13px; color: var(--color-text); text-decoration: none;
}
.pagination a:hover { border-color: var(--color-primary); }
.pagination a.active { background: var(--color-primary); color: #fff; border-color: var(--color-primary); }

.post-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.post-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 12px; }
.post-meta .badge { margin-left: 0; }
.post-writer { display: inline-flex; align-items: center; gap: 4px; font-weight: 600; font-size: 13px; }
.post-actions { display: flex; gap: 6px; }
/* 본문 바깥 여백. 안쪽 짜임은 .md-body 가 맡는다(옛 평문 글도 이 껍데기를 그대로 쓴다). */
.post-body { margin: 14px 0; font-size: 14px; line-height: 1.75; word-break: break-word; }
.post-body a { color: var(--color-primary); }

.post-images { display: flex; flex-direction: column; gap: 10px; margin: 14px 0; }
.post-images img { max-width: 100%; border: 1px solid var(--color-border); border-radius: var(--radius-sm); }

.post-files h3 { font-size: 13px; margin: 0 0 6px; }
.post-files ul { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 6px; }
.post-files li { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.post-files a { display: inline-flex; align-items: center; gap: 5px; color: var(--color-primary); text-decoration: none; }

.comment-list { list-style: none; padding: 0; margin: 0 0 14px; display: flex; flex-direction: column; }
.comment {
  display: grid; grid-template-columns: 24px 1fr; gap: 10px;
  padding: 11px 0; border-top: 1px solid var(--color-border);
}
.comment:first-child { border-top: none; }
.comment-main { min-width: 0; }
.comment-head { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.comment-del-form { margin: 0 0 0 auto; }
.comment-writer { font-size: 13px; font-weight: 600; }
.comment-del {
  padding: 2px 8px; font-size: 11px; font-family: inherit; cursor: pointer;
  border: 1px solid var(--color-border); border-radius: 6px;
  background: var(--color-card); color: var(--color-muted);
}
.comment-del:hover { color: var(--color-danger); border-color: var(--color-danger); }
.comment-body { font-size: 13px; line-height: 1.65; word-break: break-word; }
.comment-form { display: flex; flex-direction: column; gap: 8px; }
.comment-form textarea {
  padding: 9px 11px; border: 1px solid var(--color-border); border-radius: var(--radius-sm);
  background: var(--color-card); color: var(--color-text);
  font-size: 13px; font-family: inherit; resize: vertical;
}
.comment-form button { align-self: flex-start; }

/* 폼의 체크박스 줄. .stack-form label 은 세로 배치라 그대로 두면 글자가 칸 아래로 내려간다. */
.post-form .inline-check {
  flex-direction: row; align-items: center; gap: 6px;
  font-size: 13px; color: var(--color-text);
}
.post-form .inline-check input { width: auto; }
.post-form .form-hint { font-size: 12px; color: var(--color-muted); }
.post-writer-hint { margin: 0 0 4px; }
.post-file-edit { display: flex; flex-direction: column; gap: 6px; }

/* ── 마크다운 본문 (.md-body) ──────────────────────────────────────────
   글 보기의 본문과 글쓰기의 미리보기가 **같은 규칙**을 쓴다. 둘이 갈리면 미리보기가
   거짓말을 하게 되는데, 그러면 미리보기를 둔 뜻이 없어진다.
   서버가 그려 보내는 태그만 여기 있다(web/board_markdown.py 참고). */
.md-body > *:first-child { margin-top: 0; }
.md-body > *:last-child { margin-bottom: 0; }
.md-body h1, .md-body h2, .md-body h3, .md-body h4 {
  font-size: 15px; font-weight: 700; margin: 20px 0 8px; line-height: 1.4;
}
.md-body h1 { font-size: 17px; }
.md-body h2 { font-size: 16px; }
.md-body p { margin: 0 0 11px; }
.md-body a { color: var(--color-primary); }
.md-body ul, .md-body ol { margin: 0 0 11px; padding-left: 22px; }
.md-body li { margin-bottom: 3px; }
.md-body hr { border: none; border-top: 1px solid var(--color-border); margin: 18px 0; }

/* 체크리스트. 진짜 <input type=checkbox> 가 아니라 상자 모양이다 — 글은 읽는 것이지
   여기서 눌러 고치는 것이 아니라서, 눌리는 것처럼 보이면 오히려 헷갈린다. */
.md-body li.md-task { list-style: none; margin-left: -22px; display: flex; gap: 7px; align-items: baseline; }
.md-body .md-box {
  width: 12px; height: 12px; flex: none; position: relative; top: 2px;
  border: 1.5px solid var(--color-muted); border-radius: 3px;
}
.md-body .md-box.done { background: var(--color-success); border-color: var(--color-success); }
.md-body .md-box.done::after {
  content: "✓"; position: absolute; inset: 0;
  color: #fff; font-size: 9px; line-height: 10px; text-align: center;
}

.md-body blockquote {
  margin: 0 0 11px; padding: 2px 0 2px 13px;
  border-left: 3px solid var(--color-warning); color: var(--color-muted);
}
.md-body code {
  font-family: ui-monospace, SFMono-Regular, Consolas, "D2Coding", monospace;
  font-size: 12px; background: var(--color-bg);
  border: 1px solid var(--color-border); border-radius: 4px; padding: 1px 5px;
}
/* 로그를 그대로 붙여넣는 자리다. 줄바꿈을 지키되 긴 줄은 옆으로 흐르게 둔다 —
   접어 버리면 로그의 자릿수가 어긋나 읽을 수 없다. */
.md-body pre {
  background: var(--color-bg); border: 1px solid var(--color-border);
  border-radius: var(--radius-sm); padding: 12px 13px; overflow-x: auto; margin: 0 0 12px;
}
.md-body pre code {
  background: none; border: none; padding: 0; display: block;
  font-size: 11.5px; line-height: 1.65; white-space: pre;
}
/* 표는 제 칸 안에서만 옆으로 흐른다(본문이 통째로 밀리지 않게). */
.md-body table {
  border-collapse: collapse; font-size: 12.5px; margin: 0 0 12px;
  display: block; overflow-x: auto; max-width: 100%; width: max-content;
}
.md-body th {
  text-align: left; background: #f9fafb; color: var(--color-muted);
  font-weight: 600; font-size: 11.5px; padding: 7px 11px;
  border: 1px solid var(--color-border); white-space: nowrap;
}
.md-body td { padding: 7px 11px; border: 1px solid var(--color-border); }
/* 본문에 붙여넣은 그림. 원래 크기가 커도 칸을 넘지 않게. */
.md-body img {
  max-width: 100%; height: auto; border: 1px solid var(--color-border);
  border-radius: var(--radius-sm); margin: 4px 0;
}

/* 사진 여러 장(web/board_markdown.py 의 _galleries 가 묶어 보낸다).
   ★ 잘라내지 않는다(object-fit:cover 를 쓰지 않는다) — 점검 사진은 단자대나 표시창을
     찍은 것이라, 칸을 맞추자고 가장자리를 자르면 정작 봐야 할 것이 사라진다.
     높이가 들쭉날쭉해도 안 잘리는 쪽이 맞다. */
.md-body .md-gallery {
  display: grid; gap: 6px; margin: 4px 0 12px; align-items: start;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.md-body .md-gallery-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.md-body .md-gallery img { margin: 0; width: 100%; }
/* 폰에서는 3열이 너무 좁다 — 무엇을 찍었는지 알아볼 수 없다. */
@media (max-width: 640px) {
  .md-body .md-gallery-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ── 글쓰기 편집기 (board_form.html · board_editor.js) ─────────────────
   보이는 그대로 쓰는 위지윅 편집기(Toast UI)가 #md-mount 에 붙는다.

   ★ 편집기가 뜨면 .is-live 가 달리고 원래 textarea 가 감춰진다. **지워지지는 않는다** —
     서버로 가는 값이 그 칸이고, 자바스크립트가 없으면 그것만 남아 예전처럼 돈다.
     그래서 두 벌의 모양을 다 지녀야 한다: 편집기가 없을 때의 textarea 와, 있을 때의 편집기. */
.md-editor { position: relative; }
.md-mount:empty { display: none; }

/* 편집기가 아직(또는 끝내) 없을 때 — 예전 그대로의 입력칸. */
.md-editor:not(.is-live) {
  border: 1px solid var(--color-border); border-radius: var(--radius-sm); overflow: hidden;
}
/* ★ .stack-form 이 폼 안의 textarea 를 한 벌로 꾸미고 있어 여기서 되돌린다
   (테두리는 바깥 .md-editor 가 이미 그리고 있다 — 겹치면 이중선이 된다). */
.post-form .md-editor textarea {
  border: none; border-radius: 0; resize: vertical; min-height: 340px;
  padding: 12px 13px; background: var(--color-card); color: var(--color-text);
  font-size: 13.5px; line-height: 1.75;
}
.post-form .md-editor textarea:focus { outline: none; }
.md-editor.is-live .md-input-wrap { display: none; }

/* 편집기 본체. 남의 CSS 를 통째로 받아들이는 자리라, 이 프로젝트의 테두리·모서리에만
   맞춘다 — 안쪽까지 손대면 편집기를 올릴 때마다 다시 맞춰야 한다. */
.toastui-editor-defaultUI {
  border-color: var(--color-border) !important; border-radius: var(--radius-sm);
}
.toastui-editor-defaultUI .ProseMirror { font-size: 13.5px; line-height: 1.75; }

/* 쓰다 만 글 알림 줄 */
.md-draft {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 9px 13px; margin-bottom: -4px;
  background: var(--color-warning-tint); border: 1px solid var(--color-warning);
  border-radius: var(--radius-sm); font-size: 12.5px; color: var(--color-warning);
}
.md-draft .btn { margin-left: 0; }
.md-draft .btn:last-child {
  background: none; border-color: transparent; color: var(--color-muted);
}

/* 붙여넣기·끌어놓기 자리 */
.md-drop {
  border: 1.5px dashed var(--color-border); border-radius: var(--radius-sm);
  padding: 12px; background: var(--color-bg);
  display: flex; flex-direction: column; gap: 9px;
}
.md-drop.hot { border-color: var(--color-primary); background: var(--color-primary-tint); }
.md-drop-hint { margin: 0; font-size: 12.5px; color: var(--color-muted); }
.md-drop-hint kbd {
  font-family: ui-monospace, Consolas, monospace; font-size: 11px; color: var(--color-text);
  background: var(--color-card); border: 1px solid var(--color-border);
  border-bottom-width: 2px; border-radius: 5px; padding: 1px 5px;
}
.md-uploads:empty { display: none; }
.md-uploads { display: flex; flex-direction: column; gap: 6px; }
.md-upload {
  display: flex; align-items: center; gap: 10px; font-size: 12px;
  background: var(--color-card); border: 1px solid var(--color-border);
  border-radius: 6px; padding: 7px 10px;
}
.md-upload-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.md-upload-bar {
  flex: 1; min-width: 50px; height: 4px; border-radius: 999px;
  background: var(--color-border); overflow: hidden;
}
.md-upload-bar i { display: block; height: 100%; width: 0; background: var(--color-primary); }
.md-upload-state {
  margin-left: auto; font-size: 11px; color: var(--color-muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.md-upload.done .md-upload-bar i { background: var(--color-success); }
.md-upload.done .md-upload-state { color: var(--color-success); }
.md-upload.failed .md-upload-bar i { background: var(--color-danger); }
.md-upload.failed .md-upload-state { color: var(--color-danger); }

/* 옛 형식 글을 고칠 때 뜨는 안내(저장하면 마크다운이 된다는 것). */
.form-notice {
  margin: 0; padding: 9px 12px; border-radius: var(--radius-sm);
  background: var(--color-warning-tint); color: var(--color-warning);
  font-size: 12.5px; line-height: 1.55;
}
.comment-count { color: var(--color-muted); font-weight: 400; font-size: 13px; }

/* ── 태그 · 검색 표시 ─────────────────────────────────────────────────── */

/* 태그 칩. 분류 배지(.badge)와 일부러 다르게 생겼다 — 분류는 하나뿐인 서랍이고
   태그는 여럿이 붙는 꼬리표라, 같은 모양이면 목록에서 둘이 섞여 읽힌다. */
.tag {
  display: inline-block; white-space: nowrap; text-decoration: none;
  font-size: 11px; color: var(--color-muted);
  border: 1px solid var(--color-border); border-radius: 999px; padding: 1px 8px;
}
.tag:hover { border-color: var(--color-primary); color: var(--color-primary); }
.tag.on {
  background: var(--color-primary-tint); border-color: var(--color-primary-tint);
  color: var(--color-primary); font-weight: 600;
}

.hot-tags {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin: -2px 0 12px;
}
.hot-tags-cap { font-size: 11.5px; color: var(--color-muted); }

/* 검색어 표시. 배경만 칠하고 글자색은 건드리지 않는다 — 표시가 본문보다 튀면
   정작 문장이 안 읽힌다. */
mark {
  background: var(--color-warning-tint); color: var(--color-warning);
  border-radius: 3px; padding: 0 2px; font-weight: 600;
}

.board-heading-clear {
  margin-left: 8px; font-size: 12px; font-weight: 400;
  color: var(--color-primary); text-decoration: none;
}
.board-heading-clear:hover { text-decoration: underline; }

/* ── 협업 기능 (안 읽은 글 · 알림 · 반응 · 대댓글 · 멘션) ─────────────── */

/* 사이드바 '게시판' 옆 미확인 개수. 붉은 점 하나로 충분해서 크기를 키우지 않는다. */
.side-badge {
  margin-left: auto; min-width: 17px; height: 17px; padding: 0 5px;
  display: grid; place-items: center; border-radius: 999px;
  background: var(--color-danger); color: #fff;
  font-size: 10px; font-weight: 700; font-variant-numeric: tabular-nums;
}
.side-item.active .side-badge { background: var(--color-danger); }

/* 안 읽은 글. 굵기만으로는 공지·일반이 섞인 목록에서 잘 안 걸려 배지를 하나 더 붙인다. */
.post-row.is-unread .post-row-title .t { font-weight: 700; }
.pill-new {
  font-size: 9.5px; font-weight: 700; letter-spacing: 0.04em;
  color: var(--color-danger); background: var(--color-danger-tint);
  border-radius: 4px; padding: 1px 5px;
}

/* ── 헤더 알림 종 ────────────────────────────────────────────────────── */
.noti { position: relative; }
.noti-btn {
  display: grid; place-items: center; position: relative;
  width: 30px; height: 30px; padding: 0; cursor: pointer;
  background: none; border: 1px solid transparent; border-radius: 8px;
  color: var(--color-muted);
}
.noti-btn:hover { background: var(--color-bg); color: var(--color-text); }
.noti-dot {
  position: absolute; top: -1px; right: -2px;
  min-width: 15px; height: 15px; padding: 0 4px;
  display: grid; place-items: center; border-radius: 999px;
  background: var(--color-danger); color: #fff;
  font-size: 9px; font-weight: 700; font-variant-numeric: tabular-nums;
}
.noti-pop {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 50;
  width: 310px; max-width: calc(100vw - 24px);
  background: var(--color-card); border: 1px solid var(--color-border);
  border-radius: var(--radius); box-shadow: 0 6px 20px rgba(16, 24, 40, 0.12);
  overflow: hidden;
}
.noti-cap {
  padding: 9px 13px; font-size: 12px; font-weight: 600; color: var(--color-muted);
  border-bottom: 1px solid var(--color-border);
}
.noti-list { max-height: 340px; overflow-y: auto; }
.noti-empty { margin: 0; padding: 18px 13px; text-align: center; font-size: 12.5px; color: var(--color-muted); }
.noti-item {
  display: block; padding: 10px 13px; text-decoration: none; color: var(--color-text);
  border-bottom: 1px solid var(--color-border);
}
.noti-item:last-child { border-bottom: none; }
.noti-item:hover { background: var(--color-bg); }
/* 안 읽은 알림은 왼쪽에 선을 세운다 — 배경색으로 칠하면 목록이 얼룩덜룩해진다. */
.noti-item.unread { box-shadow: inset 3px 0 0 var(--color-primary); }
.noti-line { display: block; font-size: 12.5px; line-height: 1.45; }
.noti-sub {
  display: block; margin-top: 2px; font-size: 11px; color: var(--color-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── 이모지 반응 ─────────────────────────────────────────────────────── */
.reacts { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin-top: 14px; }
.react {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 11px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--color-border); background: var(--color-card);
  color: var(--color-text); font-size: 12px; font-family: inherit; line-height: 1.5;
}
.react:hover { border-color: var(--color-primary); }
.react .n { font-size: 11.5px; color: var(--color-muted); font-variant-numeric: tabular-nums; }
/* 내가 누른 것. 색으로만 갈라도 충분하다 — 눌린 것 옆에 표시를 또 붙이면 시끄럽다. */
.react.mine { background: var(--color-primary-tint); border-color: var(--color-primary-tint); }
.react.mine .n { color: var(--color-primary); font-weight: 700; }
.react-add { position: relative; display: inline-flex; }
.react-more { color: var(--color-muted); padding: 4px 12px; }
.react-menu {
  position: absolute; bottom: calc(100% + 6px); left: 0; z-index: 30;
  display: flex; gap: 2px; padding: 5px;
  background: var(--color-card); border: 1px solid var(--color-border);
  border-radius: 999px; box-shadow: 0 4px 14px rgba(16, 24, 40, 0.12);
}
.react-pick {
  border: none; background: none; cursor: pointer; border-radius: 999px;
  padding: 4px 7px; font-size: 15px; line-height: 1;
}
.react-pick:hover { background: var(--color-primary-tint); }

/* ── 대댓글 ──────────────────────────────────────────────────────────── */
/* 한 겹만 판다. 두 겹째부터는 들여쓰기가 글자 폭을 다 먹는다(thread_comments 참고). */
.comment.is-reply {
  margin-left: 26px; padding-left: 13px;
  border-top: none; border-left: 2px solid var(--color-border);
}
.comment-acts { margin-left: auto; display: flex; align-items: center; gap: 8px; }
.comment-reply {
  padding: 2px 8px; font-size: 11px; font-family: inherit; cursor: pointer;
  border: 1px solid transparent; border-radius: 6px;
  background: none; color: var(--color-muted);
}
.comment-reply:hover { color: var(--color-primary); border-color: var(--color-border); }
/* 누구에게 답하는 중인지 — 폼 위에 한 줄. 이게 없으면 답글인 줄 모르고 쓴다. */
.comment-replying {
  margin: 0; padding: 6px 11px; border-radius: var(--radius-sm);
  background: var(--color-primary-tint); color: var(--color-primary);
  font-size: 12px; display: flex; align-items: center; gap: 8px;
}
.comment-replying span { font-weight: 600; }
.comment-replying button {
  margin-left: auto; border: none; background: none; cursor: pointer;
  color: var(--color-primary); font: inherit; font-size: 11.5px; text-decoration: underline;
}

/* ── @멘션 ───────────────────────────────────────────────────────────── */
.mention {
  background: var(--color-primary-tint); color: var(--color-primary);
  font-weight: 600; border-radius: 4px; padding: 1px 5px;
}
/* 자동완성 목록은 입력칸 **아래**에 붙인다(board.js 의 wireMentions 머리말 참고). */
.comment-input-wrap, .md-input-wrap { position: relative; display: flex; flex-direction: column; }
.md-input-wrap { flex: 1; }
.mention-pop {
  position: absolute; top: 100%; left: 0; z-index: 40;
  width: 240px; max-width: 100%; margin-top: 2px;
  background: var(--color-card); border: 1px solid var(--color-border);
  border-radius: var(--radius-sm); box-shadow: 0 4px 14px rgba(16, 24, 40, 0.12);
  overflow: hidden; max-height: 210px; overflow-y: auto;
}
.mention-opt {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 7px 11px; border: none; background: none; cursor: pointer;
  font: inherit; font-size: 12.5px; color: var(--color-text); text-align: left;
}
.mention-opt small { margin-left: auto; font-size: 11px; color: var(--color-muted); }
.mention-opt:hover, .mention-opt.on { background: var(--color-primary-tint); color: var(--color-primary); }
.mention-opt.on .mention-opt-name { font-weight: 600; }


@media (max-width: 640px) {
  /* 줄 목록은 폰에서 따로 접을 것이 없다 — 제목·미리보기·메타가 이미 세로로 선다.
     (표였을 때 쓰던 .cards-post 카드 규칙 한 뭉치를 여기서 걷어냈다.) */
  .post-row { padding: 12px 2px; }
  .post-row-snip { -webkit-line-clamp: 3; }
  .board-bar { flex-wrap: wrap; }
  .board-bar > .btn { flex: 1 0 100%; }
  /* 편집기 툴바는 폰에서 줄바꿈으로 흘린다 — 가로로 잘리면 뒤쪽 단추를 못 누른다. */
  .toastui-editor-defaultUI-toolbar { flex-wrap: wrap; height: auto; }
}
