/* remox operator console — minimal, desktop-oriented styling. */

/* 글꼴: Pretendard 우선, 그다음 Noto Sans KR (2026-10-06 소유자). 가변 글꼴 하나(woff2, 굵기 45~920)를
   서버에 함께 넣어 글꼴 CDN 없이, 오프라인에서도 동작한다. 라이선스 OFL —
   static/fonts/Pretendard-OFL.txt.

   font-display 는 swap 이 아니라 optional: swap 은 대체 글꼴로 먼저 그렸다가 진짜
   글꼴이 오면 다시 배치해 글자가 튄다. optional 은 짧게 기다렸다가 못 오면 그 페이지는
   대체 글꼴로 둔다. layout 의 preload 와 /static/ 의 긴 캐시로 거의 늘 시간 안에 온다. */
@font-face {
  font-family: "Pretendard";
  font-style: normal;
  font-weight: 45 920;
  font-display: optional;
  src: url("/static/fonts/PretendardVariable.woff2") format("woff2-variations"),
       url("/static/fonts/PretendardVariable.woff2") format("woff2");
}
/* Noto Sans KR: Pretendard 다음 대체 글꼴 (2026-10-06 소유자: 두 글꼴 사용).
   Pretendard 에 없는 글자나 Pretendard 가 제때 못 왔을 때 쓴다. 400·700 만 넣었다. */
@font-face {
  font-family: "Noto Sans KR";
  font-style: normal;
  font-weight: 400;
  font-display: optional;
  src: url("/static/fonts/noto-sans-kr-v39-korean_latin-regular.woff2") format("woff2");
}
@font-face {
  font-family: "Noto Sans KR";
  font-style: normal;
  font-weight: 700;
  font-display: optional;
  src: url("/static/fonts/noto-sans-kr-v39-korean_latin-700.woff2") format("woff2");
}

/* 글자 크기는 짝수 px 만 쓴다 (2026-10-06 소유자). */
:root {
  /* Light theme (default). The remote stage stays black regardless
     (see .stage / .remote-app); only the chrome follows these. */
  --bg: #f4f6f9;
  --panel: #ffffff;
  --panel-2: #eef1f6;
  --line: #d6dbe3;
  --text: #1b2330;
  --muted: #5f6b7a;
  --accent: #2f6fe0;
  --ok: #1a8a36;
  --err: #d64545;
  --warn: #b07d12;
  /* 상단바 버튼·표시의 공통 높이. 한 값에서 나와야 나란히 놓인 것들이
     어긋나지 않는다(색 버튼은 글자가 없어서 높이를 직접 줘야 한다). */
  --mb-h: 28px;
  /* Monitoring (another operator controls; you are view-only). A calm
     steel blue, at a similar brightness to the amber so the menubar's
     default text stays readable the same way — distinct from amber
     ("you hold control, click to arm") to signal "you do not control". */
  --monitoring: #93b1cc;
  --monitoring-bg: rgba(147, 177, 204, .22);
  --monitoring-text: #2f455c;
}

* { box-sizing: border-box; }

/* hidden 속성은 UA 스타일시트의 display:none 으로 동작하는데, 작성자가 쓴
   display 규칙이 그것을 이긴다. .mb-btn 이 display 를 주고 있어서 hidden 을
   달아둔 버튼들이 그대로 보였다 — 제어 중인데 '제어 요청' 이 같이 보이고,
   포인터 강조를 끈 상태에서도 색 버튼이 남았다. 컴포넌트마다 .x[hidden]
   규칙을 덧붙이는 대신 여기서 한 번에 못 박는다. */
[hidden] { display: none !important; }

body {
  margin: 0;
  font: 14px/1.5 "Pretendard", "Noto Sans KR", -apple-system, "Malgun Gothic", Segoe UI, Roboto, Helvetica, Arial, sans-serif;
  color: var(--text);
  /* The same ground as the login page, held back. That page has one card
     in the middle of an empty screen and can afford a pattern; these pages
     are cards and tables edge to edge, so every value here is roughly two
     thirds of its login counterpart — enough that the surface is not dead
     grey, quiet enough that it never competes with a table.
     Layers are top-most first: highlight, brand tint, 176px grid, 44px
     grid. Putting the highlight last would leave it under the lines and
     wash nothing. */
  background-color: var(--bg);
  background-image:
    radial-gradient(120% 55% at 50% 0%, rgba(255, 255, 255, .75), rgba(244, 246, 249, 0) 62%),
    radial-gradient(90% 55% at 50% 112%, rgba(47, 111, 224, .07), rgba(47, 111, 224, 0) 70%),
    linear-gradient(rgba(47, 111, 224, .048) 1px, transparent 1px),
    linear-gradient(90deg, rgba(47, 111, 224, .048) 1px, transparent 1px),
    linear-gradient(rgba(17, 17, 17, .028) 1px, transparent 1px),
    linear-gradient(90deg, rgba(17, 17, 17, .028) 1px, transparent 1px);
  background-size:
    100% 100%, 100% 100%,
    176px 176px, 176px 176px,
    44px 44px, 44px 44px;
  /* Fixed, so the grid is a surface the content moves over rather than
     wallpaper that scrolls with it. */
  background-attachment: fixed;
}
/* The remote view paints its own black stage over all of this; drawing a
   pattern underneath it is work nobody sees. */
body.fullbleed { background-image: none; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

h1 { font-size: 22px; font-weight: 700; letter-spacing: -.01em; margin: 0 0 18px; }
h2 { font-size: 16px; margin: 28px 0 12px; }
h3 { font-size: 14px; margin: 20px 0 10px; }

code, .mono { font-family: "Pretendard", "Noto Sans KR", sans-serif; font-variant-numeric: tabular-nums; }
.breakall { word-break: break-all; }
.small { font-size: 12px; }
.muted { color: var(--muted); }
.nowrap { white-space: nowrap; }

/* --- layout --------------------------------------------------------------- */

.topbar {
  display: flex;
  align-items: center;
  gap: 22px;
  padding: 0 24px;
  height: 54px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 1px 3px rgba(17, 24, 39, .04);
  position: sticky;
  top: 0;
  z-index: 20;
}
.brand { font-weight: 800; font-size: 16px; color: var(--text); letter-spacing: -.01em; }
.topbar nav { display: flex; align-items: center; gap: 4px; flex: 1; }
.topbar nav a {
  padding: 6px 11px;
  border-radius: 8px;
  color: var(--muted);
  transition: background .15s ease, color .15s ease;
}
.topbar nav a:hover { background: var(--panel-2); color: var(--text); text-decoration: none; }
/* The signed-in operator's name doubles as the link to their own
   account page, so it gets link affordances without looking like
   another nav item. */
/* 계정 메뉴: 아이디를 눌러 여는 드롭다운. 아이디 링크와 로그아웃 버튼을 하나로
   합쳐, 좁은 화면에서 nav 가 줄바꿈돼도 두 항목이 흩어지지 않는다. */
.acct { position: relative; margin-left: auto; }
.topbar nav .who {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin: 0;
  padding: 6px 11px;
  background: none;
  border: 0;
  border-radius: 8px;
  color: var(--muted);
  font: inherit;
  cursor: pointer;
  text-decoration: none;
}
.topbar nav .who:hover { color: var(--text); background: var(--panel-2); }
.acct-caret {
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  opacity: .7;
}
.acct-pop {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  min-width: 150px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 14px 34px -14px rgba(17, 24, 39, .34), 0 2px 6px -2px rgba(17, 24, 39, .08);
  z-index: 30;
}
.acct-pop a,
.acct-pop button {
  display: block;
  width: 100%;
  text-align: left;
  padding: 9px 11px;
  border-radius: 7px;
  background: none;
  border: 0;
  color: var(--text);
  font: inherit;
  cursor: pointer;
  text-decoration: none;
}
.acct-pop a:hover,
.acct-pop button:hover { background: var(--panel-2); text-decoration: none; }
.acct-sep { border: 0; border-top: 1px solid var(--line); margin: 4px 2px; }
/* 다운로드 메뉴: nav 항목 바로 옆이라 왼쪽 정렬로 연다. 항목마다 아이콘 + 이름 + 용도 한 줄. */
.menu { position: relative; }
.dl-pop { left: 0; right: auto; min-width: 230px; }
.acct-pop .dl-item { display: flex; align-items: center; gap: 12px; padding: 10px 12px; }
.dl-item .ic { flex: none; color: var(--accent); }
.dl-item span { display: flex; flex-direction: column; gap: 2px; }
.dl-item b { font-weight: 600; font-size: 14px; }
.dl-item small { color: var(--muted); font-size: 12px; }
/* 폰·태블릿(주 입력이 터치)에선 다운로드 메뉴를 숨긴다 — 거기서 exe 를 받을 일이 없다
   (2026-10-12 소유자). 폭이 아니라 입력 방식으로 가린다: 좁게 줄인 PC 창에선 보인다. */
@media (hover: none) and (pointer: coarse) {
  .menu.dl { display: none; }
}

main { max-width: 1400px; margin: 0 auto; padding: 24px; }
footer {
  max-width: 1400px;
  margin: 0 auto;
  padding: 16px 24px;
  color: var(--muted);
  font-size: 12px;
}

/* --- toasts (결과 문구) ---------------------------------------------------- */
.toast-stack {
  position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
  z-index: 3500; display: flex; flex-direction: column; gap: 8px; align-items: center;
  pointer-events: none; width: max-content; max-width: calc(100vw - 32px);
}
.toast {
  pointer-events: auto; cursor: pointer;
  padding: 11px 18px; border-radius: 10px;
  font-size: 14px; font-weight: 600; color: #fff;
  box-shadow: 0 10px 28px -12px rgba(17, 24, 39, .45);
  animation: toast-in .28s ease-out, toast-out .35s ease-in 3.2s forwards;
}
.toast.ok  { background: #1f7a3a; }
.toast.err { background: #c43c3c; animation: toast-in .28s ease-out, toast-out .35s ease-in 7s forwards; }
@keyframes toast-in  { from { transform: translateY(24px); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes toast-out { to { transform: translateY(12px); opacity: 0; visibility: hidden; } }

/* --- cards & forms -------------------------------------------------------- */

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 22px;
  margin-bottom: 16px;
  box-shadow: 0 1px 2px rgba(17, 24, 39, .04);
}
.card.narrow { max-width: 380px; }

/* Admin surfaces reuse the login card as their base — same white fill,
   same 14px radius, same lift — so the console reads as one thing rather
   than as a login screen bolted to a different admin screen. The page
   background is unchanged; the card is what is new. */
.admin-panel,
.list-panel {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow:
    0 20px 44px -26px rgba(17, 24, 39, .28),
    0 2px 6px -2px rgba(17, 24, 39, .06);
}

/* One card per admin sub-page: dense forms and tables need an edge to sit
   against, which the bare page background does not give them. */
.admin-panel { padding: 26px 26px 30px; }
/* 목록 화면: 제목을 뺀 내용(채널 전환기·필터·카드)을 올리는 흰 패널. */
.list-panel { padding: 22px 24px 8px; }
.admin-panel > h1:first-child,
.admin-panel > p:first-child,
.admin-panel > .page-head:first-child { margin-top: 0; }

/* 관리 상단 탭 (2026-10-22 소유자): 카드 메뉴 대신 모든 관리 화면 위에 같은 줄. 페이지 안 탭(.tabs,
   세그먼트형)과 층위가 달라 밑줄형으로 둔다. 좁은 화면에선 가로로 민다. */
.admin-nav {
  display: flex;
  gap: 2px;
  margin: -6px 0 22px;
  border-bottom: 1px solid var(--line);
  overflow-x: auto;
  scrollbar-width: none;
}
.admin-nav::-webkit-scrollbar { display: none; }
.admin-nav-item {
  padding: 10px 14px 11px;
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  text-decoration: none;
  transition: color .15s ease, border-color .15s ease;
}
.admin-nav-item:hover { color: var(--text); text-decoration: none; }
.admin-nav-item.active { color: var(--accent); border-bottom-color: var(--accent); }


/* 페이지 제목과 오른쪽의 페이지 동작 버튼(생성 등). 돌아가기는 이 줄 위에 따로 둔다. */
.page-head {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}
.page-head h1 { margin: 0; }
/* 돌아가기는 제목 줄과 떨어진 한 줄 위의 작은 링크다. 제목 옆 버튼이면 제목의
   일부처럼 읽혔다. 삼각형은 currentColor 라 hover 색을 같이 탄다. */
/* 뒤로 가기: 눈에 띄게 테두리 있는 버튼 모양 (2026-10-06 소유자). */
.page-back {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
  padding: 6px 13px 6px 11px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
}
.page-back:hover { color: var(--accent); border-color: var(--accent); text-decoration: none; }
.page-back::before {
  content: "";
  width: 0;
  height: 0;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  border-right: 7px solid currentColor;
}
.page-head-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.cards { display: flex; flex-wrap: wrap; gap: 16px; }
.linkcard {
  flex: 1 1 220px;
  transition: border-color .15s ease, transform .12s ease, box-shadow .15s ease;
}
.linkcard:hover {
  border-color: var(--accent);
  text-decoration: none;
  transform: translateY(-2px);
  box-shadow: 0 14px 30px -18px rgba(17, 24, 39, .25);
}
.linkcard h2 { margin-top: 0; }

.stack { display: flex; flex-direction: column; gap: 12px; }
.stack.narrow { max-width: 380px; }
.stack label { display: flex; flex-direction: column; gap: 4px; }
.inline { display: inline-flex; gap: 6px; align-items: center; margin: 0; }

/* Radio/checkbox rows inside a .stack form. .stack label stacks its
   children vertically, which would put the box above its own label
   text, so these put the control and its text back on one line. */
.stack .inline-check {
  flex-direction: row;
  align-items: center;
  gap: 7px;
}
.stack .inline-check input { margin: 0; }

/* The channel checkbox list on the account form. Indented under the
   "선택한 채널만" radio it belongs to, and scrollable so a long channel
   list cannot push the submit button off the screen. */
.channel-picker {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-left: 22px;
  max-height: 190px;
  overflow-y: auto;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-2, #fafafa);
}
.channel-picker p { margin: 0; }

fieldset {
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px 14px;
  margin: 0;
}
fieldset legend { padding: 0 6px; font-size: 12px; color: var(--muted); }

input[type=text], input[type=password], input[type=number], select {
  background: #fff;
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 8px 11px;
  font: inherit;
  transition: border-color .15s ease, box-shadow .15s ease;
}
/* 잠긴 입력칸은 회색으로 — 입력할 수 없다는 것이 보이게. */
input:disabled, select:disabled { background: var(--panel-2); color: var(--muted); cursor: not-allowed; }
input:focus, select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(47, 111, 224, .15);
}

button {
  font: inherit;
  cursor: pointer;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--text);
  padding: 8px 14px;
  transition: border-color .15s ease, background .15s ease, transform .08s ease, box-shadow .15s ease;
}
/* 옅은 파랑. 순백이면 흰 글자·아이콘 버튼이 배경과 한 색이 된다. */
button:hover { border-color: var(--accent); background: #eef3fd; }
button:active { transform: translateY(1px); }
button:disabled { opacity: .45; cursor: not-allowed; }

/* .btn works on both <a> and <button>, so it carries its own surface. */
.btn {
  display: inline-block;
  padding: 7px 13px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--text);
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease, transform .08s ease, box-shadow .15s ease;
}
/* hover 는 옅은 파랑 톤으로. 예전엔 순백(#fff)이라 밝은 페이지 위에서 버튼이
   배경에 묻혀 "하얗게 사라지는" 것처럼 보였다. */
.btn:hover { border-color: var(--accent); background: #eef3fd; text-decoration: none; }
.btn:active { transform: translateY(1px); }
.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  box-shadow: 0 8px 18px -10px rgba(47, 111, 224, .6);
}
.primary:hover { background: var(--accent); border-color: var(--accent); filter: brightness(1.06); }
.linkbtn { background: none; border: none; color: var(--accent); padding: 4px; }
.linkbtn:hover { background: none; border-color: transparent; text-decoration: underline; }
.danger { color: var(--err); }
.btn-danger { background: var(--err); border-color: var(--err); color: #fff; }
.btn-danger:hover { background: var(--err); border-color: var(--err); filter: brightness(1.06); }

/* --- tables --------------------------------------------------------------- */

.grid { width: 100%; border-collapse: collapse; margin-bottom: 8px; }
.grid.narrow { max-width: 380px; }
.grid th, .grid td {
  text-align: left;
  padding: 8px 10px;
  border-bottom: 1px solid var(--line);
  /* Centred, not top-aligned: an actions cell holding selects and buttons
     is half again as tall as a cell of plain text, and top-alignment left
     every other column visibly riding high in the row. */
  vertical-align: middle;
}
.grid thead th {
  color: var(--muted);
  font-weight: 600;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .04em;
}
.grid tbody tr { transition: background .12s ease; }
.grid tbody tr:hover { background: var(--panel-2); }
/* 관리 → 설정 → 버전 (2026-10-20): OS·버전 입력·파일 버전·대기/배포 */
.version-table { max-width: 960px; margin-top: 16px; }
.version-table td { vertical-align: top; }
.version-table tr.ver-current td { background: rgba(127,127,127,.08); }
.ver-now { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.ver-upload { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ver-upload input[type=text] { flex: 1 1 240px; max-width: 420px; }
.ver-note { min-width: 200px; max-width: 360px; }
.ver-note summary { cursor: pointer; list-style: none; }
.ver-note summary::-webkit-details-marker { display: none; }
.ver-note-text { white-space: pre-line; }
.ver-note-form { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; margin-top: 6px; }
.ver-note-form textarea { width: 100%; min-width: 200px; font: inherit; }
.ver-actions { white-space: nowrap; text-align: right; }
.ver-actions form { display: inline; }

/* 관리 → 에이전트·앱 (2026-10-22 소유자, 2026-10-23 탭 분리). 탭마다: 흐름 막대(에이전트는 올리기 → 대기 →
   배포, 앱은 내려주는 버전 하나), 새 버전 올리기 카드, 버전 카드. 지금 단계만 채우고 그 단계의 버튼을 그 칸에. */
.prog-stack { display: flex; flex-direction: column; gap: 16px; max-width: 1000px; }
.prog-card { padding: 20px 22px; }
.prog-h { margin: 0 0 14px; font-size: 16px; }
.flow { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; counter-reset: none; }
.flow-single { grid-template-columns: minmax(0, 360px); }
.flow-step {
  position: relative; display: flex; align-items: flex-start; gap: 12px;
  padding: 16px 18px; border-radius: 12px; border: 1px solid var(--line); background: var(--panel);
  min-height: 92px;
}
/* 단계 사이 화살표 (마지막 칸 제외). */
.flow > .flow-step:not(:last-child)::after {
  content: ""; position: absolute; right: -9px; top: 50%; width: 10px; height: 10px; z-index: 1;
  border-top: 2px solid var(--line); border-right: 2px solid var(--line); background: transparent;
  transform: translateY(-50%) rotate(45deg);
}
.flow-num {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: 50%; font-size: 14px; font-weight: 700;
  border: 2px solid var(--line); color: var(--muted); background: var(--panel);
}
.flow-num svg { width: 16px; height: 16px; }
.flow-body { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; min-width: 0; }
.flow-title { font-size: 14px; font-weight: 700; color: var(--text); }
.flow-ver { font-size: 22px; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; color: var(--text); }
.flow-sub { font-size: 12px; color: var(--muted); }
.flow-warn { color: var(--warn); overflow-wrap: anywhere; }
.flow-btn { margin-top: 6px; }
/* 올리기 취소: 주 동작이 아니라 되돌리기 — 작게, 빨강 글씨. */
.flow-cancel { padding: 4px 10px; font-size: 12px; color: var(--err); }
/* 끝난 단계: 번호 칸을 파랗게 채운다. 남은 단계: 흐리게. */
.flow-step.is-done .flow-num { background: var(--accent); border-color: var(--accent); color: #fff; }
.flow-step.is-todo { background: var(--panel-2); border-style: dashed; }
.flow-step.is-todo .flow-title { color: var(--muted); }
/* 지금 할 단계: 파란 테두리. 배포 중: 초록. 주의·차단: 노랑·빨강. */
.flow-step.is-now { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(47, 111, 224, .12); }
.flow-step.is-now .flow-num { border-color: var(--accent); color: var(--accent); }
.flow-step.is-live { border-color: var(--ok); background: rgba(26, 138, 54, .06); }
.flow-step.is-live .flow-num { background: var(--ok); border-color: var(--ok); color: #fff; }
.flow-step.is-live .flow-title { color: var(--ok); }
.flow-step.is-warn { border-color: var(--warn); background: rgba(176, 125, 18, .06); }
.flow-step.is-warn .flow-num { border-color: var(--warn); color: var(--warn); }
.flow-step.is-err { border-color: var(--err); background: rgba(214, 69, 69, .06); }
.flow-step.is-err .flow-num { background: var(--err); border-color: var(--err); color: #fff; }
.flow-step.is-err .flow-title { color: var(--err); }
@media (max-width: 720px) {
  .flow { grid-template-columns: 1fr; }
  .flow-step { min-height: 0; }
  .flow > .flow-step:not(:last-child)::after { right: auto; left: 30px; top: auto; bottom: -9px; transform: rotate(135deg); }
}
/* 관리 → 사용자 검색·필터 (2026-10-23). 유형·상태 알약 묶음 사이 구분선, 개수는 오른쪽. */
.user-toolbar .uf-sep { align-self: stretch; width: 1px; margin: 4px 2px; background: var(--line); }
.user-toolbar #user-count, .host-toolbar #host-count { margin-left: auto; }
#host-table { min-width: 960px; }
@media (max-width: 720px) { .user-toolbar .uf-sep { display: none; } }
.state {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border-radius: 999px;
  font-size: 12px; font-weight: 600; white-space: nowrap;
}
.state::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: currentColor; }
.state-on   { color: var(--ok);    background: rgba(26,138,54,.12); }
.state-off  { color: var(--muted); background: rgba(95,107,122,.12); }
.state-warn { color: var(--warn);  background: rgba(176,125,18,.14); }
.state-err  { color: var(--err);   background: rgba(214,69,69,.12); }
.prog-card .version-table { max-width: none; }
/* 좁은 화면에서 칸을 쪼개지 않고 표만 가로로 민다 (내역 칸만 줄바꿈). */
.prog-card .version-table th,
.prog-card .version-table td:not(.ver-note) { white-space: nowrap; }
.table-wrap { overflow-x: auto; }

/* 파일 올리기 (2026-10-22 소유자: 브라우저 기본 파일 입력을 쓰지 말 것). label 이 숨긴 입력을 감싸
   누르면 고르기, 끌어 놓기도 받는다. 입력은 화면에서만 숨겨 키보드 초점은 그대로 받는다. */
.up-form { display: flex; flex-direction: column; gap: 10px; }
.file-drop {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  min-height: 76px; padding: 14px 16px;
  border: 1.5px dashed var(--line); border-radius: 10px;
  background: var(--panel-2); color: var(--muted);
  font-size: 14px; cursor: pointer;
  transition: border-color .15s ease, background .15s ease, color .15s ease;
}
.file-drop:hover, .file-drop.over { border-color: var(--accent); background: rgba(47,111,224,.06); color: var(--text); }
.file-drop:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
.file-drop.has-file { justify-content: flex-start; border-style: solid; background: var(--panel); color: var(--text); }
.file-drop.busy { cursor: default; }
.file-drop-input { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; clip: rect(0 0 0 0); }
.file-drop-empty, .file-drop-picked { display: flex; align-items: center; gap: 10px; min-width: 0; }
.file-drop-picked { width: 100%; }
.file-drop-empty[hidden], .file-drop-picked[hidden] { display: none; }
.file-drop svg { width: 22px; height: 22px; flex: none; }
.file-drop-link { color: var(--accent); font-weight: 600; text-decoration: underline; text-underline-offset: 3px; }
.file-drop-name { font-weight: 600; color: var(--text); overflow-wrap: anywhere; }
.file-drop-size { color: var(--muted); font-size: 12px; white-space: nowrap; }
.file-drop-clear {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 28px; height: 28px; margin-left: auto; padding: 0;
  border: 0; border-radius: 50%; background: transparent; color: var(--muted); cursor: pointer;
}
.file-drop-clear:hover { background: var(--panel-2); color: var(--text); }
.file-drop-clear svg { width: 16px; height: 16px; }
.file-drop.busy .file-drop-clear { visibility: hidden; }
.up-row { display: flex; gap: 8px; flex-wrap: wrap; }
.up-row input[type=text] { flex: 1 1 260px; }
.up-progress { display: flex; align-items: center; gap: 10px; }
.up-progress[hidden], .up-msg[hidden] { display: none; }
.up-bar { flex: 1; height: 6px; border-radius: 999px; background: var(--panel-2); overflow: hidden; }
.up-bar span { display: block; width: 0; height: 100%; background: var(--accent); transition: width .15s ease; }
.up-pct { min-width: 52px; text-align: right; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.up-msg { margin: 0; font-size: 12px; color: var(--ok); }
.up-msg.err { color: var(--err); }
/* Row controls line up on their centres regardless of what each one is —
   a bare link button, a button next to a select, or a lone select. */
.grid .actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.grid .actions .inline { align-items: center; }
/* In a row, form controls shrink to match the text they sit beside. The
   page-level sizing (8px 11px) makes a table row twice as tall as it
   needs to be. */
.grid select,
.grid input[type=text],
.grid input[type=password] {
  padding: 5px 9px;
  border-radius: 7px;
  font-size: 12px;
  line-height: 1.4;
}
.grid .linkbtn { padding: 5px 4px; line-height: 1.4; }
.grid .btn { padding: 5px 11px; font-size: 12px; }

.kv { border-collapse: collapse; margin-bottom: 8px; }
.kv th {
  text-align: left;
  color: var(--muted);
  font-weight: 600;
  padding: 6px 16px 6px 0;
  white-space: nowrap;
}
.kv td { padding: 6px 0; }

.pager { display: flex; gap: 12px; margin-top: 12px; }
/* 로그 (2026-10-23): 앞 네 칸은 줄바꿈 없이 여유 폭, 세부 정보가 나머지. 쪽 번호는 촘촘히, 지금 쪽은 채움. */
.audit-table th.a-time { min-width: 150px; }
.audit-table th.a-user { min-width: 96px; }
.audit-table th.a-act { min-width: 170px; }
.audit-table th.a-target { min-width: 150px; }
.audit-table td { vertical-align: top; }
.audit-toolbar select { width: auto; max-width: 240px; flex: 0 0 auto; }
.audit-toolbar .fav-sort { margin-left: auto; }
.pager { flex-wrap: wrap; align-items: center; gap: 6px; }
.pager .btn { min-width: 36px; text-align: center; padding-left: 10px; padding-right: 10px; }
.pager .pg.active { background: var(--accent); border-color: var(--accent); color: #fff; }
.pager .pg-of { margin-left: 8px; }

/* --- badges --------------------------------------------------------------- */

.badge {
  display: inline-block;
  font-size: 12px;
  font-weight: 600;
  padding: 2px 9px;
  border-radius: 999px;
  margin-left: 6px;
}
.badge.online  { background: rgba(26,138,54,.14);  color: var(--ok); }
.badge.offline { background: rgba(95,107,122,.14); color: var(--muted); }
.badge.warn    { background: rgba(176,125,18,.16);  color: var(--warn); }

/* --- host list ------------------------------------------------------------ */

.hostlist { list-style: none; padding: 0; margin: 0; }
.hostrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 13px 16px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  margin-bottom: 10px;
  transition: border-color .15s ease, box-shadow .15s ease, transform .12s ease;
}
.hostrow:hover {
  border-color: #c4ccd6;
  box-shadow: 0 12px 26px -20px rgba(17, 24, 39, .3);
}
.hostactions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* Square icon buttons for the host-row actions (open, alias, star). */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 30px;
  height: 30px;
  padding: 0 4px;
  background: none;
  border: none;
  color: var(--muted);
  border-radius: 8px;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  transition: outline-color .15s ease, transform .08s ease;
}
/* Hover: keep each icon's own colour (no accent recolour), show a
   dark-grey outline instead. */
.icon-btn:hover { outline: 1px solid #8b93a1; outline-offset: 0; text-decoration: none; }
.icon-btn:active { transform: translateY(1px); }
.icon-btn .ic { display: block; }

/* 즐겨찾기 별: 옆 편집·라벨과 같은 18px SVG 아이콘 (예전 ☆★ 글자는 폰트마다
   굵기·기준선이 달라 혼자 튀었다). 해제 상태는 외곽선, 즐겨찾기는 금색 채움. */
.star-btn { color: var(--muted); }
.star-btn.on { color: #f5b301; }

/* --- color tags (favorites grouped view) --------------------------------- */

/* 7-rainbow palette (빨주노초파남보). Each key sets --tag; chips and
   swatches read it, so the palette is defined once and stays themeable. */
.tag-red    { --tag: #e23b3b; }
.tag-orange { --tag: #e8821e; }
.tag-yellow { --tag: #d9a400; }
.tag-green  { --tag: #2faf4f; }
.tag-blue   { --tag: #2f6fe0; }
.tag-indigo { --tag: #4b4bc8; }
.tag-violet { --tag: #8a3bc0; }
.tag-none   { --tag: var(--muted); }

.fav-group { margin-bottom: 22px; }
.fav-group-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 10px 2px;
}
/* Group marker: a tag-shaped icon in the group colour with a small
   count badge. Colour comes from --tag (set by .tag-<key>). */
.tag-mark {
  position: relative;
  display: inline-flex;
  color: var(--tag);
}
.tag-mark svg { display: block; }
.tag-count {
  position: absolute;
  right: -5px;
  bottom: -4px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--tag);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid var(--panel);
}

/* Host cards that flow several per row (favorites groups + full list). */
.fav-list,
/* Two to three cards per row. A grid rather than wrapping flex so every
   card in a row is the same width and height regardless of how long its
   name or how many tags it carries — the old flex basis let a long name
   push the online badge onto a second line and left rows ragged. */
.card-grid {
  list-style: none;
  margin: 0 0 26px;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
  gap: 14px;
}
.hostcard {
  min-height: 132px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px 16px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.hostcard:hover {
  border-color: #c4ccd6;
  box-shadow: 0 12px 26px -20px rgba(17, 24, 39, .3);
}
.hostcard.dragging { opacity: .45; }
/* 다른 라벨 묶음 위로 끌고 올 때: 놓으면 그 라벨이 된다는 표시. */
.fav-group.drop-target { outline: 2px dashed var(--accent); outline-offset: 6px; border-radius: 10px; }
.badge.regblock { background: var(--err); color: #fff; }
.badge.pin { background: #3b3f8f; color: #fff; }
/* PIN 칸은 10자만 들어가면 되므로 좁게 (2026-10-06 소유자: 200px). */
input.pin-input { width: 200px; max-width: 100%; letter-spacing: .15em; align-self: flex-start; }
.pin-err { margin: 0; font-size: 12px; font-weight: 600; color: var(--err); }
.pin-page { max-width: 380px; }
.readonly-note { margin: 0 0 12px; padding: 7px 12px; border-radius: 8px; font-size: 12px; font-weight: 600; color: var(--err); background: #fff1f1; }
/* 차단된 호스트 카드: 붉은 바탕·테두리, 접속 버튼도 빨강 (2026-10-06). */
.hostcard.is-blocked { border-color: var(--err); background: #fff7f7; }
.btn.btn-blocked { background: var(--err); border-color: var(--err); color: #fff; }
.btn.btn-blocked:hover { filter: brightness(1.06); }
/* 오프라인 호스트의 접속 버튼은 눌리지 않는다 (hoststatus.js 가 상태에 따라 바꾼다). */
/* 오프라인으로 보여도 누를 수 있다 — 누르면 그 자리에서 다시 확인한다 (hoststatus.js, 2026-10-21). */
.connect-btn.is-offline { opacity: .4; filter: grayscale(.6); cursor: help; }
.connect-btn.is-still-offline { animation: hs-shake .3s ease-in-out 2; }
@keyframes hs-shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-3px); } 75% { transform: translateX(3px); } }
/* 상태 확인 실패 (로그인 만료·서버 꺼짐). 누르면 새로고침. */
.hs-fail { position: fixed; left: 16px; bottom: 16px; z-index: 50; padding: 6px 12px; border-radius: 4px;
  border: 1px solid var(--err); background: var(--panel); color: var(--err);
  font-size: 12px; cursor: pointer; }
.icon-btn:disabled { opacity: .35; cursor: not-allowed; }
.ipblock-form { display: flex; gap: 8px; flex-wrap: wrap; margin: 0 0 12px; }
.ipblock-form input[type="text"] { width: auto; min-width: 160px; }

/* Channel section heading on the grouped host list. */
.chan-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 12px;
  font-size: 14px;
}
/* 호스트 목록의 채널 구획 머리. 배경·막대 없이 아이콘·굵은 이름·개수로만
   구분한다 (2026-10-06 소유자). */
.chan-section {
  margin: 6px 0 12px;
  font-size: 16px;
  font-weight: 700;
  color: var(--text);
}
/* 채널 이름 옆 작은 수정(연필)·추가(+) 버튼 (2026-10-06). */
.chan-edit, .chan-add {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0; margin-left: 2px;
  border: 1px solid var(--line); border-radius: 6px;
  background: var(--panel); color: var(--muted); cursor: pointer;
  font-size: 14px; font-weight: 700; line-height: 1;
}
.chan-edit:hover, .chan-add:hover { color: var(--accent); border-color: var(--accent); }
.chan-add { width: 30px; height: 30px; font-size: 16px; }
.title-with-edit { display: flex; align-items: center; gap: 10px; }
.title-with-edit .chan-edit { width: 30px; height: 30px; }
.chan-head-ic { width: 18px; height: 18px; fill: var(--accent); flex: none; }
.chan-none .chan-head-ic { fill: var(--muted); }
.chan-head-count {
  font-size: 12px; font-weight: 700;
  padding: 1px 8px; border-radius: 999px;
  background: var(--panel-2); color: var(--muted);
}
.chan-head code {
  font-size: 12px;
  font-weight: 600;
  padding: 2px 7px;
  border-radius: 5px;
  background: var(--accent);
  color: #fff;
}

/* Online/offline badge on its own row, so a long host name can never
   push it out of line and make neighbouring cards different heights. */
.hostcard-state { margin-top: 2px; }

/* An inert colour swatch: the host is not favourited, so it has no
   colour row to write to. Shown rather than hidden so the control does
   not appear to be missing on this page. */
.color-btn.disabled { opacity: .3; cursor: default; }

/* The label swatches inside the edit dialog, when the host is not
   favourited and therefore has no row to store a label on. */
.tag-swatches.disabled { opacity: .35; pointer-events: none; }
.field-label { font-size: 12px; margin-bottom: 8px; }

/* Two labelled rows on each card: channels, then hashtags. The leading
   icon says which is which, so neither row spends width on a caption.
   The icon column is fixed so both rows' values line up. */
.card-meta {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: auto;   /* push to the bottom so cards align */
}
.meta-row { display: flex; align-items: flex-start; gap: 7px; min-width: 0; }
.meta-ic {
  width: 15px;
  height: 15px;
  flex: none;
  margin-top: 2px;
  fill: var(--muted);
  opacity: .75;
}
.meta-vals { display: flex; flex-wrap: wrap; gap: 5px; min-width: 0; }
.meta-date { font-size: 12px; color: var(--muted); }
.meta-none { font-size: 12px; color: var(--muted); opacity: .65; }

/* A channel chip. Deliberately unlike .ttag: a channel is assigned by
   an admin and decides reach, a hashtag is free-form and decides
   nothing, so they must not read as the same kind of thing. */
.chtag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  padding: 2px 9px;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--text);
}
.chtag code {
  font-size: 12px;
  font-weight: 700;
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--accent);
  color: #fff;
}

/* Top row: name (left) + edit / label / favourite icons (top-right). */
.hostcard-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.hostcard-top-actions { display: flex; align-items: center; gap: 2px; flex: none; }
/* The favourite forms are inline wrappers around a single icon button;
   without this they'd add a line box and misalign the star with the row. */
.hostcard-top-actions .inline { display: flex; align-items: center; }
.hostcard-name { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.hostcard-title { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* Primary line (alias if present, else the machine name): 20px, black.
   긴 토큰도 카드 밖으로 넘치지 않게 깨서 접는다. */
.hostcard-name strong { font-size: 20px; font-weight: 700; color: #111111; overflow-wrap: anywhere; }
/* Machine name shown under an alias: 14px, muted. */
.hostcard-sub { font-size: 14px; color: var(--muted); }

/* Foot row pinned to the card bottom: hashtags (left) + 원격 접속 (right).
   align-items:flex-end keeps the button on the last line when the tags
   wrap to several rows, so the button never floats mid-card. */
.hostcard-foot {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 12px;
  margin-top: auto;
  padding-top: 12px;
}
.hostcard .hostactions { gap: 8px; }

/* Shared text tags (bottom-left). */
.card-tags { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-width: 0; }

/* The card's primary action. Labelled, not just an icon, and never
   shrinks — the tags beside it give way instead. */
/* Same box as every other .btn — only the icon+label arrangement is
   added. Giving the card's primary action its own padding made it the one
   button on the page at a different size, which read as a mistake rather
   than as emphasis; the filled .primary fill is emphasis enough. */
.connect-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: none;
  font-weight: 600;
  white-space: nowrap;
  line-height: 18px;
}
.connect-btn .ic { flex: none; }
/* Clickable color tag icon in the card foot (opens the color picker).
   Filled with the tag color, or muted when no color is set. */
.color-btn { color: var(--tag, var(--muted)); }
.color-btn.none { color: var(--muted); }
.ttag {
  font-size: 12px;
  padding: 2px 9px;
  border-radius: 999px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  color: var(--text);
  white-space: nowrap;
  cursor: pointer;
  transition: border-color .15s ease, color .15s ease;
}
.ttag:hover { border-color: var(--accent); color: var(--accent); }
.ttag-edit {
  font-size: 12px;
  line-height: 1;
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px dashed var(--line);
  background: none;
  color: var(--muted);
  cursor: pointer;
}
.ttag-edit:hover { border-color: var(--accent); color: var(--accent); }

/* Color tag shown as a clickable dot on the favorites card (bottom-right):
   filled with the host's color, or a dashed outline when untagged. */
.color-dot {
  width: 26px;
  height: 26px;
  padding: 0;
  flex: none;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--tag, #fff);
  cursor: pointer;
}
.color-dot:hover { border-color: var(--accent); }
.color-dot.none { background: #fff; border-style: dashed; }

/* Top tag filter bar: click a tag to filter the host list. */
.tag-filter {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 18px;
}
.tagf {
  font-size: 12px;
  padding: 5px 12px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--muted);
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease, color .15s ease;
}
.tagf:hover { border-color: var(--accent); }
.tagf.active { background: var(--accent); border-color: var(--accent); color: #fff; }

/* 즐겨찾기 최상단 검색·정렬 툴바 (2026-10-12). */
.fav-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin: 0 0 14px;
}
.fav-search {
  flex: 1 1 200px;
  min-width: 160px;
  /* 넓은 화면에서 끝까지 늘어나면 이름 검색칸치고 너무 길다 (2026-10-30 소유자). */
  max-width: 360px;
  font-size: 14px;
  padding: 7px 12px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--text);
}
.fav-search:focus { outline: none; border-color: var(--accent); }
.fav-sort { display: flex; flex-wrap: wrap; gap: 6px; }
.sortb {
  font-size: 12px;
  padding: 5px 12px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--muted);
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease, color .15s ease;
}
.sortb:hover { border-color: var(--accent); }
.sortb.active { background: var(--accent); border-color: var(--accent); color: #fff; }
/* 정렬 중엔 끌어서 순서를 바꿀 수 없다 — 손잡이를 흐리게. */
.fav-sorted .drag-handle { opacity: .3; cursor: default; }

/* 내 원격 묶음 기준 전환 (채널/라벨, 2026-10-13 소유자). 제목 오른쪽 세그먼트 버튼. */
.view-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.view-seg { display: inline-flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
.segb {
  padding: 6px 16px; font-size: 14px; font-weight: 600;
  color: var(--muted); background: var(--panel); text-decoration: none;
}
.segb + .segb { border-left: 1px solid var(--line); }
.segb:hover:not(.active) { color: var(--text); }
.segb.active { background: var(--accent); color: #fff; }
/* 채널 보기에서도 카드의 라벨 색을 좌측 띠로 보여 준다 (2026-10-13 소유자). 라벨이 없는
   카드는 --tag 가 없어 띠가 안 그려진다. data-color 가 없는(=채널 보기) 목록에만. */
.fav-list:not([data-color]) .hostcard { position: relative; }
.fav-list:not([data-color]) .hostcard::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
  background: var(--tag); border-radius: 10px 0 0 10px;
}

/* Channel switcher above the tag filter. Deliberately heavier than
   .tagf: a tag narrows a cosmetic grouping, a channel switches which
   customer's fleet you are looking at, so it reads as navigation
   rather than as another filter chip. */
.channel-switch {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 0 0 14px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
}
.chansw {
  font-size: 14px;
  font-weight: 600;
  padding: 6px 13px;
  border-radius: 8px;
  border: 1px solid transparent;
  color: var(--muted);
  text-decoration: none;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.chansw code { font-size: 12px; opacity: .85; }
/* 게스트 칩 앞 구분선 (2026-10-16). */
.chansw-sep { align-self: stretch; width: 1px; margin: 4px 6px; background: var(--line); flex: none; }
.chansw:hover { border-color: var(--line); color: var(--text); }
.chansw.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.chansw.active code { opacity: 1; }

/* Drag handle on each favorite card. */
.drag-handle {
  cursor: grab;
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  line-height: 1;
  user-select: none;
}

/* Colour swatches in the tag editor modal. Hover keeps the colour and
   shows an outline (never a white fill). */
.tag-swatches { display: flex; flex-wrap: wrap; gap: 10px; }
.swatch {
  width: 26px;
  height: 26px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, .15);
  background: var(--tag);
  cursor: pointer;
}
.swatch:hover {
  background: var(--tag);
  filter: none;
  outline: 2px solid var(--line);
  outline-offset: 2px;
}
.swatch.selected,
.swatch.selected:hover { outline: 2px solid var(--text); outline-offset: 2px; }
.swatch-none {
  background: #fff;
  color: var(--muted);
  border: 1px dashed var(--line);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
}
.swatch-none:hover { background: #fff; }
.tag-section { margin-top: 18px; border-top: 1px solid var(--line); padding-top: 14px; }

/* --- remote view (full-bleed) --------------------------------------------- */

body.fullbleed { height: 100vh; overflow: hidden; }

.remote-app {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  background: #000;
}

/* Menubar: fixed-height strip above the remote area. */
.menubar {
  flex: 0 0 auto;
  position: relative;
  /* 3열 그리드. 양쪽 1fr 이 남는 폭을 똑같이 나누므로 가운데 칸은 좌우
     내용의 폭과 무관하게 바 전체의 가운데에 온다 — 가운데 배치는 이
     트랙 정의가 하는 일이다. 예전에는 상태 표시를 absolute 로 가운데
     박았는데, 흐름에서 빠져 있어 좌우 그룹이 자라면 그 위로 겹쳐버렸다
     ("연결됨" 이 "마우스 포인터 강조" 를 덮었다).
     minmax(0,1fr) 이라야 내용이 길어도 열이 밀리지 않는다. */
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 8px;
  height: 46px;
  padding: 0 10px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  user-select: none;
  overflow: hidden;
}
/* Two zones. The left one (identity → control state → monitor tabs) takes
   all the free space, which pins the right-hand controls to the right
   edge; because the pill lives at the left end now, its width changing
   (제어 중 / 제어 대기 / 모니터링 — …) grows into that free space
   instead of nudging the zoom or file buttons. */
/* Every zone clips to its own box. Without overflow:hidden the right
   zone's non-shrinkable groups spill LEFT out of their box (it is
   justify-content:flex-end with min-width:0) and land on top of the
   centred pill — the bar looked fine but the text overlapped as soon as
   the window narrowed. Clipping is the floor; the media queries below
   shed low-priority chrome first so it rarely comes to that. */
.mb-left  { min-width: 0; overflow: hidden; display: flex; align-items: center; gap: 6px; justify-content: flex-start; }
.mb-right { min-width: 0; overflow: hidden; display: flex; align-items: center; gap: 6px; justify-content: flex-end; }
/* 평상시 "연결됨" 은 표시하지 않는다 (2026-10-07 소유자) — 그림이 보이면 연결된 것.
   접속 중·끊김·안내 문구만 보인다. 상태 칸이 빠져도 오른쪽 묶음이 가운데 칸으로
   끌려오지 않게 늘 마지막 칸에 고정한다. */
.mb-status.idle { display: none; }
.mb-right { grid-column: -2 / -1; }
/* Set apart from the host name by a gap several times the 6px the rest of
   the bar uses, so "제어 대기" reads as its own statement about the
   session and not as a suffix on the machine name. */
.mb-control {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  margin-left: 22px;
  white-space: nowrap;
}
/* The machine name / alias yields space before anything else does,
   truncating with an ellipsis. */
.mb-title {
  font-weight: 600;
  white-space: nowrap;
  margin-right: 4px;
  min-width: 0;
  flex-shrink: 1;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mb-group { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
.mb-group[hidden] { display: none; }
.mb-viewers { white-space: nowrap; flex-shrink: 0; }

/* 반응형 전환점. 이 위에서는 예전과 똑같이 모든 컨트롤이 상단바에 늘어선다.
   이 아래에서는 remote.js 가 보조 컨트롤 묶음과 모니터 탭을 하단 막대로
   옮기고, 상단바에는 세션을 판단하는 것(상태·뷰어·제어 요청)만 남는다.
   예전에는 폭이 줄면 컨트롤을 하나씩 버렸는데, 그래도 결국 서로를 밀어내며
   겹쳤다 — 버리는 대신 옮기는 쪽이 기능도 지킨다. */
#toolbar-toggle { display: none; }

/* 접힌 배치는 remote.js 가 .menubar.collapsed 로 켠다 — 1200px 이하이거나, 그보다
   넓어도 기능 버튼이 다 안 들어갈 때 (2026-10-08). */
.menubar.collapsed {
  grid-template-columns: minmax(0, auto) auto minmax(0, 1fr);
}
.menubar.collapsed #toolbar-toggle { display: inline-flex; }

@media (max-width: 1200px) {
  #toolbar-toggle { display: inline-flex; }
  /* 축소 배치에서는 가운데에 둘 이유가 없다. 오른쪽에 막대를 여는 버튼만
     남아 가운데 칸이 화면 한가운데도 아니고, 폭도 넉넉하지 않다. 왼쪽
     묶음 옆에 붙여 호스트 이름·제어 상태와 한 줄로 읽히게 한다.
     상태 표시를 왼쪽으로 옮기는 것은 이 트랙 정의 하나가 한다 — 첫 칸이
     내용 크기가 되면서 상태 표시가 그 바로 옆에 오고, 남는 폭은 마지막
     칸이 먹어 여는 버튼만 오른쪽 끝에 남는다. 가운데 칸이 auto 라 항목이
     칸을 꽉 채우므로 justify-self 는 여기서 아무 일도 하지 않는다.
     첫 칸이 minmax(0, auto) 인 것은 호스트 이름이 먼저 줄게 하려는 것이다. */
  .menubar { grid-template-columns: minmax(0, auto) auto minmax(0, 1fr); }
}
@media (max-width: 560px) {
  /* 왼쪽 묶음은 여기서부터 여는 버튼을 밀어내기 시작한다 — 제어 상태 칩은
     min-width 가 있어 줄지 않으므로, 뷰어 수를 먼저 접는다. 이 버튼이
     가려지면 막대를 열 수단이 없어진다. 제어 상태는 마지막까지 남긴다. */
  .mb-viewers { display: none; }
}
@media (max-width: 520px) {
  .mb-title { max-width: 24vw; }
}
/* 폰 폭 (2026-10-07): 상단바에 상태 칸까지 두면 맨 오른쪽 "…" 버튼이 잘려 도구
   막대(키보드·확대 등)를 열 수 없었다. 상태 문구는 이제 토스트라 칸을 차지하지 않는다.
   남은 두 칸 중 오른쪽(여는 버튼)은 auto 라 절대 줄지 않는다. */
@media (max-width: 560px) {
  .menubar, .menubar.collapsed { grid-template-columns: minmax(0, 1fr) auto; }
  #toolbar-toggle { flex-shrink: 0; }
  /* 트랙패드·키보드·텍스트·최대화가 붙으며 컨트롤러 표시가 잘렸다 (2026-10-16 소유자).
     PC 이름은 "…" 막대 맨 위로, 최대화는 그 화면 묶음으로 (remote.js) 옮긴다. */
  .menubar .mb-title { display: none; }
  .mb-control { margin-left: 4px; }
  /* 여기선 표시 오른쪽에 밀릴 버튼이 없다 (제어 요청만) — 고정 폭을 풀어 제어 요청 자리를 지킨다. */
  .menubar .pill.control, .menubar .pill.viewer { min-width: 0; } /* 아래 92px 규칙보다 앞에 있어 무게로 이긴다 */
}
/* 제어 권한 제거의 그림은 "…" 막대 타일에서만 (상단바에선 글자 버튼). */
.menubar #release-control .ic { display: none; }
/* "…" 막대 맨 위 PC 이름 — 휴대폰 폭의 떠 있는 막대에서만. 오른쪽 위 위치 바꾸기 아이콘을 비켜 간다. */
.tb-host-name { display: none; }
@media (max-width: 560px) {
  .toolbar:not(.sidebar) .tb-host-name {
    display: flex; align-items: center; gap: 10px; min-width: 0; padding-right: 40px;
  }
  .tb-host-text { font-weight: 600; font-size: 16px; min-width: 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .tb-host-name .ctl-who { flex: none; max-width: 50%; }
}
/* 사이드바 배치(넓은 창)에서는 접기·펴기가 같은 일을 한다 (2026-10-12). */
.menubar.side-mode #toolbar-toggle { display: none; }

.mb-btn {
  /* 높이를 직접 정하고 내용을 가운데 둔다. 글자가 있는 버튼은 패딩으로
     높이가 나오지만 색 버튼은 비어 있어서, 패딩만으로는 둘이 어긋난다. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--mb-h);
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 0 8px;
  font: inherit;
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}
.mb-btn:hover { border-color: var(--accent); text-decoration: none; }
.mb-btn:disabled { opacity: .45; cursor: not-allowed; }
.mb-icon { width: 36px; text-align: center; padding: 5px 0; }
.mb-btn .ic { display: block; margin: 0 auto; }

/* 모니터 탭 줄: [◀][탭들][▶]. 자리를 넘칠 때만(.overflow) 좌우 버튼이 보이고 탭들은
   가로로 스크롤한다 (2026-10-08). 상단바에서는 제목 다음으로 줄어드는 쪽이다. */
.mb-tabs { display: flex; align-items: center; gap: 4px; min-width: 0; flex: 0 1 auto; }
.mb-tabs:empty { display: none; }
.mb-tabs-track {
  display: flex; gap: 4px; flex-wrap: nowrap; min-width: 0;
  overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.mb-tabs-track::-webkit-scrollbar { display: none; }
.mb-tab-arrow { display: none; flex: none; width: 28px; font-size: 12px; }
.mb-tabs.overflow .mb-tab-arrow { display: inline-block; }
.mb-tab-arrow:disabled { opacity: .35; }

/* 상단바 안의 보조 컨트롤 묶음. 좁아지면 이 요소가 통째로 하단 막대로
   옮겨간다(remote.js). */
.mb-tools { display: flex; align-items: center; gap: 6px; min-width: 0; }

/* 하단 도구 막대. 화면 위에 떠 있어서 자리를 차지하지 않는다 — 열고 닫을
   때마다 stage 가 다시 재단되면 16:9 맞춤 상태에서 그림 크기가 바뀐다.
   줄바꿈을 허용해서, 창이 좁아지면 항목을 버리는 대신 아래로 접힌다. */
.toolbar {
  position: absolute;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 6px 10px;
  padding: 8px 10px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  /* 검은 화면 위에 떠 있으므로 그림자로 띄운다. */
  box-shadow: 0 8px 28px rgba(0, 0, 0, .45);
}

/* 붙일 위치 네 가지. 좌우는 세로로 세우고, 상하는 가로로 눕혀 줄바꿈한다.
   기본은 상단이다 — 상단바 바로 아래라 컨트롤이 있던 자리에서 이어지고,
   가로로 눕는 쪽이 아이콘을 촘촘히 담는다. */
.toolbar.dock-right,
.toolbar.dock-left {
  top: 50%;
  transform: translateY(-50%);
  flex-direction: column;
  /* stretch 가 아니다. 늘리면 36px 아이콘이 막대 폭을 다 차지하면서
     한 줄에 하나씩 놓여, 폭은 폭대로 쓰고 높이는 높이대로 쓴다. */
  align-items: center;
  /* 항목이 많아도 화면을 넘지 않게. 넘치면 막대 안에서 스크롤한다. */
  max-height: calc(100vh - 92px);
  overflow-y: auto;
}
.toolbar.dock-right { right: 14px; }
.toolbar.dock-left { left: 14px; }
.toolbar.dock-top,
.toolbar.dock-bottom {
  left: 50%;
  transform: translateX(-50%);
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  max-width: calc(100vw - 24px);
}
/* 상단바(46px) 바로 아래. */
.toolbar.dock-top { top: 58px; }
.toolbar.dock-bottom { bottom: 14px; }

/* 세로로 세웠을 때. 막대만 세우고 그룹은 가로 줄로 둔다 — 아이콘 두세
   개가 한 줄에 들어가므로 같은 항목을 절반 높이에 담는다. 넘치면 그룹
   안에서 줄바꿈한다. */
.toolbar.dock-right .mb-group,
.toolbar.dock-left .mb-group {
  flex-wrap: wrap;
  justify-content: center;
}
/* 모니터 탭은 글자라서 가로로 붙이면 이름이 잘린다. 이것만 세로로 쌓는다
   (세로라 넘쳐도 막대 자체가 스크롤하므로 좌우 버튼은 쓰지 않는다). */
.toolbar.dock-right .mb-tabs,
.toolbar.dock-left .mb-tabs {
  align-self: stretch;
}
.toolbar.dock-right .mb-tabs-track,
.toolbar.dock-left .mb-tabs-track {
  flex: 1;
  flex-direction: column;
  align-items: stretch;
  overflow: visible;
}
.toolbar.dock-right .mb-tab-arrow,
.toolbar.dock-left .mb-tab-arrow { display: none; }
/* 위치 선택 버튼 넷은 2x2 로 접는다. 한 줄로 두면 이것 혼자 막대 폭을
   156px 로 정해버린다. */
.toolbar.dock-right .dock-picker,
.toolbar.dock-left .dock-picker { max-width: 80px; }
/* 세로 막대에서 강조 묶음은 한 줄을 다 쓴다. 라벨을 감추면 폭은 몇 px
   아끼지만 이름 없는 체크박스가 남는다 — 그쪽이 더 나쁘다. */
.toolbar.dock-right .mb-pointer,
.toolbar.dock-left .mb-pointer { align-self: stretch; }
/* 구분선은 위쪽 선이 되고, 막대 폭을 채워야 묶음 경계로 읽힌다. */
.toolbar.dock-right .mb-group + .mb-group,
.toolbar.dock-left .mb-group + .mb-group,
.toolbar.dock-right .mb-tabs + .mb-group,
.toolbar.dock-left .mb-tabs + .mb-group {
  border-left: 0;
  padding-left: 0;
  border-top: 1px solid var(--line);
  padding-top: 8px;
  align-self: stretch;
}

/* 위치 선택 버튼. 현재 위치는 눌려 있는 것으로 보인다. */
.dock-btn[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
/* 막대 안에서는 묶음이 흐름을 차지하지 않고 그룹들이 직접 줄바꿈되게 한다. */
.toolbar .mb-tools {
  display: contents;
}
/* 그룹 경계. 한 줄에 여러 그룹이 붙어 있어 구분선이 없으면 어디까지가 한
   묶음인지 읽히지 않는다. */
.toolbar .mb-group + .mb-group,
.toolbar .mb-tabs + .mb-group { border-left: 1px solid var(--line); padding-left: 10px; }
/* 막대 안의 탭은 상단바에서 쓰던 왼쪽 여백이 필요 없다. 위·아래 막대에서는 한 줄을
   넘지 않게 막대 폭까지만 — 넘치면 좌우 버튼으로 옮긴다. */
.toolbar .mb-tabs { margin-left: 0; max-width: 100%; }
.mb-tab {
  background: var(--panel-2);
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 6px 12px;
  font: inherit;
  white-space: nowrap;
  cursor: pointer;
}
.mb-tab:hover { border-color: var(--accent); }
.mb-tab.active {
  background: rgba(79,140,255,.2);
  border-color: var(--accent);
  color: var(--text);
}

/* Frame-rate buttons reuse .mb-btn; the chosen rate is highlighted. */
.fps-btn.active {
  background: rgba(79,140,255,.2);
  border-color: var(--accent);
  color: var(--text);
}

/* Control-state indicator: same box design as the menubar buttons
   (.mb-btn), just not interactive. */
.pill {
  display: inline-block;
  padding: 6px 10px;
  border-radius: 6px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  color: var(--text);
  font: inherit;
  line-height: 1;
  white-space: nowrap;
}
/* The pill text varies a lot ("제어 중" … "모니터링 — 홍길동님이 제어 중"),
   so cap it and ellipsise rather than let it push the zone wider than the
   space the bar can actually give it. */
.pill { max-width: 34vw; overflow: hidden; text-overflow: ellipsis; }
.pill.control { background: rgba(79,140,255,.2); border-color: var(--accent); }
/* 뷰어: 회색 칸. 경고가 아니므로 색을 쓰지 않는다. */
.pill.viewer { background: var(--panel-2); border-color: var(--line); color: var(--muted); font-weight: 600; }
/* 컨트롤러 ↔ 뷰어가 같은 자리에서 바뀌므로 칸 폭을 같게 고정한다 (2026-10-16 소유자) —
   글자 수가 달라 폭이 튀면 옆 버튼들이 밀린다. */
.pill.control,
.pill.viewer {
  min-width: 92px;
  text-align: center;
}
/* 다른 사람이 컨트롤러일 때 표시 옆 "컨트롤러 이름". */
.ctl-who { font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 30vw; }
.ctl-who[hidden] { display: none; }
.pill.monitoring { background: var(--monitoring-bg); border-color: var(--monitoring); color: var(--monitoring-text); font-weight: 700; }

/* Stage: the remote area, fills the rest of the viewport. */
.stage {
  position: relative;
  flex: 1 1 auto;
  display: flex; /* centres the canvas via margin:auto on .canvas-host */
  overflow: auto;
  background: #000;
}
/* "fit" mode centres the canvas and never scrolls; zoomed mode lets
   the canvas exceed the stage and scroll. */
.stage.fit { overflow: hidden; }
.stage.fit .canvas-host {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* In zoomed mode margin:auto keeps the canvas centred when it is
   smaller than the stage, yet still lets the operator scroll to every
   edge when it is larger (justify-content:center would clip those). */
.canvas-host { margin: auto; display: inline-flex; }
#screen { display: block; cursor: default; }

/* Pointer-highlight controls in the menubar. */
.mb-check {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--text);
  font: inherit;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
}
.mb-check input { cursor: pointer; margin: 0; }
.mb-check[hidden] { display: none; }
/* The current-colour chip in the menubar. Its background is set inline
   from the chosen colour, so it doubles as the "what is selected now"
   readout and the button that opens the palette. */
.mb-swatch {
  background: var(--swatch, #ffcc00);
  width: var(--mb-h);
  height: var(--mb-h);
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  cursor: pointer;
}
/* Pointer-highlight settings panel. Hung off its own button rather than
   opened as a modal: it is a two-second tweak, and the operator wants to
   see the halo change against the live picture rather than behind a dim.

   position:fixed because the menubar clips its overflow — a panel
   positioned inside the bar would be cut off at the bar's edge. Script
   places it from the button's rect. */
.cursor-opts {
  position: fixed;
  z-index: 40;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 14px 16px 16px;
  box-shadow:
    0 20px 44px -22px rgba(17, 24, 39, .42),
    0 2px 6px -2px rgba(17, 24, 39, .10);
}
.cursor-opts-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  margin-bottom: 8px;
}
.cursor-opts .swatch-grid { margin: 0 0 16px; }
.cursor-size { width: 100%; cursor: pointer; display: block; }

/* Palette grid: fixed six-per-row, like the colour drop-downs in
   Word/Excel, so the set reads as a chart rather than a reflowing list. */
.swatch-grid {
  display: grid;
  grid-template-columns: repeat(6, 34px);
  gap: 10px;
  justify-content: center;
  margin: 4px 0 18px;
}
.swatch-grid .swatch { width: 34px; height: 34px; }

/* Viewer-local highlight ring that follows the operator's pointer over
   the remote canvas. position:fixed on <body> so zoom/scroll transforms
   never shift it; pointer-events:none so it never blocks input. The OS
   cursor stays visible — the ring is a halo around it, not a replacement. */
#cursor-highlight {
  position: fixed;
  left: 0; top: 0;
  width: 56px; height: 56px;
  margin-left: -28px; margin-top: -28px;
  border-radius: 50%;
  pointer-events: none;
  z-index: 2000;
  display: none;
  opacity: .38;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, .25);
}
.stage.fit #screen { max-width: 100%; max-height: 100%; }
/* Armed: input forwards to the host. A clear outline + crosshair
   signals that the remote area is live. (unchanged) */
.stage.armed { outline: 2px solid var(--accent); outline-offset: -2px; }
.stage.armed #screen { cursor: crosshair; }
/* 제어 대기(컨트롤러인데 아직 클릭 안 함)는 알림이 표시 하나로 끝난다.
   상단바 전체를 호박색으로 물들이던 것을 뺐다 — 클릭 한 번으로 풀리는
   상태에 바 전체를 쓰는 것은 과했다. 화면 둘레의 테두리도 없다(그림이
   viewport 안에서 유일하게 테두리 있는 것으로 남게). */

/* 다른 사람이 제어 중 (monitoring): 상단바 색은 그대로 두고 (2026-10-06 소유자),
   상태 표시 안의 제어자 이름만 어두운 바탕·흰 글씨로 강조한다. */
.pill .ctl-name,
.ctl-who .ctl-name {
  display: inline-block; margin: 0 2px; padding: 1px 8px;
  border-radius: 6px; background: #1b2330; color: #fff; font-weight: 700;
}

/* File-transfer status strip: floats near the bottom of the remote
   view during an upload/download, above the screen ring but below
   modals. */
/* 파일 끌어다 놓기 안내. pointer-events:none — 드래그 이벤트가 이 층에서
   dragleave 를 일으켜 깜빡이지 않게 한다. */
.drop-overlay {
  position: fixed; inset: 12px;
  border: 3px dashed var(--accent); border-radius: 12px;
  background: rgba(37, 99, 235, .14);
  display: flex; align-items: center; justify-content: center;
  color: #fff; font-size: 20px; font-weight: 600;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .6);
  pointer-events: none; z-index: 3000;
}
.drop-overlay[hidden] { display: none; }
.drop-overlay.denied { border-color: var(--err); background: rgba(220, 38, 38, .14); }
.upload-list {
  max-height: 180px; overflow: auto; margin: 0 0 8px; padding-left: 18px;
  font-size: 12px; word-break: break-all;
}
/* 모달 안 저장 결과 (호스트 편집). 버튼 왼쪽에 잠깐 떴다가 흐려진다. */
.save-status { margin-right: auto; font-size: 12px; font-weight: 600; color: #1a8a36; opacity: 0; }
.save-status.err { color: var(--err); }
.save-status.show { animation: save-status-fade 3.5s ease forwards; }
.save-status.err.show { animation: none; opacity: 1; }
@keyframes save-status-fade { 0%, 70% { opacity: 1; } 100% { opacity: 0; } }
.file-status {
  position: fixed;
  left: 50%;
  bottom: 18px;
  transform: translateX(-50%);
  z-index: 30;
  width: min(520px, 92%);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, .18);
  padding: 10px 12px;
  font-size: 14px;
}
.file-status-row { display: flex; align-items: center; gap: 8px; }
.file-status-row > span {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.file-status-close {
  border: 0;
  background: transparent;
  cursor: pointer;
  font-size: 20px;
  line-height: 1;
  color: var(--muted);
  padding: 0 4px;
}
.file-status-track {
  margin-top: 8px;
  height: 6px;
  border-radius: 999px;
  background: var(--panel-2);
  overflow: hidden;
}
.file-status-bar {
  height: 100%;
  width: 0;
  background: var(--accent);
  border-radius: 999px;
  transition: width .15s ease;
}
.file-status-bar.indeterminate {
  width: 40%;
  animation: file-indet 1.2s ease-in-out infinite;
}
@keyframes file-indet {
  0% { margin-left: -40%; }
  100% { margin-left: 100%; }
}

.arm-hint {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.arm-hint-box {
  background: rgba(17,21,28,.82);
  border: 1px solid var(--accent);
  border-radius: 8px;
  padding: 10px 18px;
  /* Sits on the always-black stage, so its text is fixed light
     independent of the (light) page theme. */
  color: #eef2f7;
}

/* 원격 화면의 상태·안내 문구는 상단 가운데 토스트로 띄운다 (2026-10-07 소유자).
   예전엔 상단바 칸이었는데 폰에서 "…" 버튼을 밀어냈다. 평상시 "연결됨" 은 .idle 로
   감춘다. 사이트 토스트(.toast)와 같은 모양, 위치만 상단바 바로 아래. */
.mb-status {
  position: fixed; top: 56px; left: 50%; transform: translateX(-50%);
  z-index: 30; max-width: calc(100vw - 32px);
  padding: 11px 18px; border-radius: 10px;
  background: rgba(27, 35, 48, .92); color: #fff;
  font-size: 14px; font-weight: 600; line-height: 1.4;
  box-shadow: 0 10px 28px -12px rgba(0, 0, 0, .6);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  pointer-events: none;
  animation: mb-toast-in .22s ease-out;
}
.mb-status.flash { background: var(--accent); }
@keyframes mb-toast-in { from { transform: translate(-50%, -12px); opacity: 0; } to { transform: translateX(-50%); opacity: 1; } }

/* --- modals --------------------------------------------------------------- */

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0,0,0,.55);
}
.modal {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 20px;
  width: min(440px, 92vw);
}
/* 모달 오른쪽 위 X (modalx.js 가 단다). 제목이 X 밑으로 들어가지 않게 여백. */
.modal-x { position: absolute; top: 14px; right: 14px; }
/* 호스트 카드 이름 줄. 수정(연필)은 2026-10-27 에 우상단 아이콘 묶음으로 옮겼다. */
.hostcard-title { display: flex; align-items: center; gap: 4px; min-width: 0; }
.modal:has(> .modal-x) > h2 { padding-right: 36px; }
/* 확인 모달: 문구가 X 와 겹치지 않게 위·오른쪽 여백 (2026-10-15). */
#confirm-msg { margin-top: 20px; padding-right: 28px; white-space: pre-line; }
/* For dialogs that list things to pick from. 440px forces a candidate
   list into one cramped column and hides how many there are; this fits
   two or three across and still leaves the page visible around it. */
.modal-wide { width: min(720px, 94vw); }

/* A checkbox list of candidates (hosts, users, channels). Auto-fill so
   the column count follows the dialog width, and scrollable so a long
   list cannot push the 추가 button off-screen. */
.pick-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 8px;
  max-height: 46vh;
  overflow-y: auto;
  margin: 12px 0 4px;
  padding: 4px;
}
.pick {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel-2);
  cursor: pointer;
  min-width: 0;
}
.pick:hover { border-color: var(--accent); background: #fff; }
.pick[hidden] { display: none; }
/* 피커 이름 검색칸 (호스트·사용자 추가, 2026-10-13). type=search 는 공용 input 규칙을 안 받는다. */
.pick-search { display: block; width: 100%; margin-bottom: 10px; background: #fff; color: var(--text);
  border: 1px solid var(--line); border-radius: 8px; padding: 8px 11px; font: inherit; }
.pick-search:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(47, 111, 224, .15); }
/* Checked rows are filled, not just ticked — with a grid this size the
   box alone is too small to scan for what is selected. */
.pick:has(input:checked) {
  border-color: var(--accent);
  background: rgba(79, 140, 255, .12);
}
.pick input { margin: 3px 0 0; cursor: pointer; flex: none; }
.pick-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.pick-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pick-sub { font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 공유 사용자 넣고 빼기 (호스트 숨김, 2026-10-13): 좌=전체, 우=공유함 두 열.
   격자 체크박스가 모달을 넘치던 것을 고정 높이 스크롤 두 열로 바꿨다. */
/* 모달 높이 안에서 2열이 남은 공간을 채우고 각 열이 안에서 스크롤하도록.
   security-form 은 패널을 감싸는 flex 열, 숨김 패널도 flex 열. */
#edit-modal #security-form:not([hidden]) { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
#edit-modal #security-form > [data-edit-panel] { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
#edit-modal [data-edit-panel="hidden"]:not([hidden]), #bulk-modal [data-bulk-panel="hidden"]:not([hidden]) { display: flex; flex-direction: column; }
#edit-modal #share-wrap:not([hidden]), #bulk-modal #bulk-share-wrap:not([hidden]) { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; }
.share-xfer { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 12px 0 4px; flex: 1 1 auto; min-height: 0; }
.share-col { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.share-col-head { font-weight: 600; font-size: 12px; color: var(--muted); margin-bottom: 8px; }
.share-list {
  display: flex; flex-direction: column; gap: 6px;
  flex: 1 1 auto; min-height: 120px; overflow-y: auto; padding: 4px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--panel-2);
}
.share-row {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 8px 11px; border: 1px solid var(--line); border-radius: 8px;
  background: #fff; cursor: pointer; font: inherit; text-align: left; width: 100%;
}
.share-row:hover { border-color: var(--accent); }
.share-row[hidden] { display: none; }
.share-row-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.share-row-mark { flex: none; color: var(--muted); font-weight: 700; font-size: 16px; line-height: 1; }
#share-chosen .share-row, #bulk-share-chosen .share-row { border-color: var(--accent); background: rgba(79, 140, 255, .12); }
#share-chosen .share-row-mark, #bulk-share-chosen .share-row-mark { color: var(--accent); }
/* 숨긴 본인: 공유함에 고정, 빼기 불가. 흐리게 두고 "나" 로 표시. */
.share-row.is-locked { cursor: default; opacity: .85; }
.share-row.is-locked .share-row-mark { color: var(--muted); font-weight: 600; font-size: 12px; }
.share-none { text-align: center; margin: 10px 0 2px; }
@media (max-width: 560px) { .share-xfer { grid-template-columns: 1fr; } .share-list { height: 26vh; } }

/* A heading with its own action button on the right. */
.section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.section-head h2 { margin-bottom: 0; }
.modal h2 { margin: 0 0 12px; font-size: 16px; }
.modal textarea {
  width: 100%;
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 8px 9px;
  font: inherit;
  resize: vertical;
}
.modal textarea:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
.modal textarea { min-height: 132px; }
.notify-pos-label { display: block; margin: 12px 0 4px; font-size: 12px; color: var(--muted); }
.notify-pos {
  width: 100%;
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 8px 9px;
  font: inherit;
}
.notify-emphasis {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
  font-size: 12px;
  color: var(--text);
  cursor: pointer;
}
.notify-emphasis input { width: 15px; height: 15px; accent-color: var(--accent); cursor: pointer; }
.notify-emphasis em { color: var(--muted); font-style: normal; font-size: 12px; }

.modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }
.capture-hint { margin: 6px 0 0; font-size: 12px; color: var(--muted); }
/* 설정창: 프레임 두 버튼, 그 아래 컨트롤러 전용 코덱·화질 묶음 */
.fps-row { display: flex; gap: 8px; }
.fps-row .fps-btn { flex: 1; }
.capture-session { border: 0; padding: 0; margin: 4px 0 0; min-width: 0; }
.capture-session:disabled > :not(.capture-lock-note) { opacity: .55; }
.capture-lock-note { margin-top: 12px; }
.capture-range { width: 100%; margin: 8px 0 0; }
#capture-effective { font-weight: 600; color: var(--text); }
/* Push the 닫기 button to the far left of the action row. */
.notify-close-left { margin-right: auto; }

.token {
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 10px;
}

/* --- login + support (centered card over a faint grid) ------------------- */

/* Both are full-bleed, unauthenticated, single-card pages, so they share
   one ground. They differ only in how they sit in it — see the two rules
   right after this one. */
.login-page,
.support-page {
  position: fixed;
  inset: 0;
  display: flex;
  justify-content: center;
  padding: 24px;
  background-color: #f4f6fa;
  /* Layers are listed top-most first.
     The grid used to be here but could not be seen: 3.5% black on this
     ground is an 8-of-256 step, which is the threshold of visible, and a
     92%-white highlight sized 120% x 80% then covered all of it except a
     band along the bottom edge. So the page read as flat grey.
     Now: the highlight is pulled back to clear the pattern outside the
     card, the lines are dark enough to register, and a second grid four
     times the pitch gives the surface some depth rather than one even
     mesh. The blue rising off the bottom edge is the brand colour at a
     tenth strength — enough that the ground is not dead neutral, far too
     little to compete with the card. */
  background-image:
    radial-gradient(105% 68% at 50% 40%, rgba(255, 255, 255, .84), rgba(244, 246, 250, 0) 56%),
    radial-gradient(95% 60% at 50% 116%, rgba(47, 111, 224, .11), rgba(47, 111, 224, 0) 72%),
    linear-gradient(rgba(47, 111, 224, .09) 1px, transparent 1px),
    linear-gradient(90deg, rgba(47, 111, 224, .09) 1px, transparent 1px),
    linear-gradient(rgba(17, 17, 17, .05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(17, 17, 17, .05) 1px, transparent 1px);
  background-size:
    100% 100%, 100% 100%,
    176px 176px, 176px 176px,
    44px 44px, 44px 44px;
}

.login-page { align-items: center; }  /* one short card: centre it */

/* The support page's second state carries the steps AND the persistence
   notice, which is taller than a laptop viewport. Centring would clip it
   top and bottom with no way to reach either end, so it anchors to the
   top and scrolls. */
.support-page {
  align-items: flex-start;
  overflow-y: auto;
}

.login-box {
  width: min(380px, 100%);
  display: flex;
  flex-direction: column;
  align-items: center;     /* logo above, form card below, centered */
  gap: 26px;
  /* Lift slightly above dead-centre — a centred block reads better
     optically a touch above the middle. */
  margin-bottom: 6vh;
  animation: login-rise .6s cubic-bezier(.2, .7, .2, 1) both;
}
@keyframes login-rise {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}

.login-logo {
  font-size: clamp(46px, 7.5vw, 88px);
  font-weight: 800;
  letter-spacing: -.03em;
  line-height: 1;
  color: #111111;
  text-align: center;
}

/* The form sits on a clean white card floating over the grid. */
.login-form {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 18px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 28px 26px;
  box-shadow:
    0 20px 44px -26px rgba(17, 24, 39, .28),
    0 2px 6px -2px rgba(17, 24, 39, .06);
}
.login-form label {
  display: flex;
  flex-direction: column;
  gap: 7px;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
}
.login-form input {
  background: #fff;
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 12px 13px;
  font: inherit;
  font-size: 16px;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.login-form input::placeholder { color: #aab2bd; }
.login-form input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(47, 111, 224, .15);
}
.login-opts {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px 20px;
  margin: 12px 0;
}
/* .checkrow is a <label>; the prefix beats `.login-form label` so the
   checkbox sits inline (left) with its text — not stacked above it. */
.login-form .checkrow {
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  font-weight: 400;
  color: var(--text);
  cursor: pointer;
  user-select: none;
}
/* Custom checkbox matching the form's rounded, accent-driven look. */
.login-form .checkrow input {
  appearance: none;
  -webkit-appearance: none;
  width: 13px;
  height: 13px;
  margin: 0;
  padding: 8px; /* override the global text-input padding (kept symmetric so it stays square) */
  flex: none;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: #fff;
  display: inline-grid;
  place-content: center;
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease, box-shadow .15s ease;
}
.login-form .checkrow input::before {
  content: "";
  width: 11px;
  height: 11px;
  transform: scale(0);
  transition: transform .12s ease;
  background: #fff;
  clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
}
.login-form .checkrow input:hover { border-color: var(--accent); }
.login-form .checkrow input:checked { background: var(--accent); border-color: var(--accent); }
.login-form .checkrow input:checked::before { transform: scale(1); }
.login-form .checkrow input:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(47, 111, 224, .18); }

.login-submit {
  align-self: center; /* don't stretch to the full card width */
  margin-top: 8px;
  padding: 11px 40px;
  font-size: 16px;
  font-weight: 600;
  border-radius: 9px;
  box-shadow: 0 8px 18px -10px rgba(47, 111, 224, .7);
  transition: transform .08s ease, filter .15s ease, box-shadow .15s ease;
}
.login-submit:hover {
  filter: brightness(1.07);
  box-shadow: 0 10px 22px -10px rgba(47, 111, 224, .8);
}
.login-submit:active { transform: translateY(1px); }
.login-error {
  background: rgba(214, 69, 69, .1);
  border: 1px solid var(--err);
  color: var(--err);
  padding: 10px 12px;
  border-radius: 8px;
  font-size: 14px;
}

/* 로그인 카드 아래의 지원 페이지 입구. 카드와 같은 폭, 흰 바탕에 강조색 테두리.
   채워진 로그인 버튼보다는 한 단계 약하게 (테두리형). */
.login-support {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 16px 20px;
  background: #fff;
  border: 1.5px solid rgba(47, 111, 224, .45);
  border-radius: 12px;
  color: var(--text);
  text-decoration: none;
  transition: border-color .15s ease, box-shadow .15s ease, transform .12s ease;
}
.login-support:hover {
  border-color: var(--accent);
  box-shadow: 0 10px 24px -16px rgba(47, 111, 224, .6);
  transform: translateY(-1px);
  text-decoration: none;
}
.login-support-text { font-size: 16px; font-weight: 700; color: var(--accent); }
/* 오른쪽 삼각형 — 관리 페이지 돌아가기와 같은 CSS 삼각형, 방향만 반대. */
.login-support-arrow {
  flex: none;
  width: 0;
  height: 0;
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
  border-left: 8px solid var(--accent);
}

/* --- support (public, customer-facing) ----------------------------------- */

.support-box {
  width: min(520px, 100%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 22px;
  /* Top and bottom breathing room, since this page scrolls rather than
     centring (see .support-page). */
  margin: 8vh 0 6vh;
  animation: login-rise .6s cubic-bezier(.2, .7, .2, 1) both;
}

.support-logo {
  font-size: clamp(30px, 5vw, 44px);
  font-weight: 800;
  letter-spacing: -.03em;
  line-height: 1;
  color: #111111;
  text-align: center;
  text-decoration: none;
}
.support-logo:hover { text-decoration: none; }

/* Same white card as the login form, sized for prose rather than two
   fields. */
.support-card {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 16px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 30px 28px;
  box-shadow:
    0 20px 44px -26px rgba(17, 24, 39, .28),
    0 2px 6px -2px rgba(17, 24, 39, .06);
}

.support-lead {
  margin: 0;
  font-size: 14px;
  line-height: 1.65;
  color: var(--text);
}
.support-hint {
  margin: -8px 0 0;
  font-size: 14px;
  color: var(--muted);
}

.support-card label {
  display: flex;
  flex-direction: column;
  gap: 7px;
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
}

/* The code is read off a phone call and typed once. Monospace with wide
   tracking so O/0-style misreads are obvious before submitting, and
   uppercase so what is typed matches what was dictated.
   input. 을 붙인다: 공용 input[type=text] 규칙이 클래스 하나보다 세서 글자 크기가
   먹지 않았다 (2026-10-08, 28px). */
input.support-code-input {
  background: #fff;
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 14px 15px;
  font-family: "Pretendard", "Noto Sans KR", sans-serif; font-variant-numeric: tabular-nums;
  font-size: 28px;
  font-weight: 600;
  letter-spacing: .16em;
  text-align: center;
  text-transform: uppercase;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.support-code-input::placeholder {
  color: #c2c9d3;
  font-weight: 400;
  letter-spacing: .12em;
}
.support-code-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(47, 111, 224, .15);
}

.support-submit {
  align-self: center;
  margin-top: 4px;
  padding: 12px 44px;
  font-size: 16px;
  font-weight: 600;
  border-radius: 9px;
  box-shadow: 0 8px 18px -10px rgba(47, 111, 224, .7);
  transition: transform .08s ease, filter .15s ease, box-shadow .15s ease;
}
.support-submit:hover {
  filter: brightness(1.07);
  box-shadow: 0 10px 22px -10px rgba(47, 111, 224, .8);
}
.support-submit:active { transform: translateY(1px); }

/* 확인 줄: 뒤로는 왼쪽 끝, 확인은 가운데 그대로. 가운데 칸을 auto 로 두고 양옆을
   같은 1fr 로 나눠야 뒤로의 폭과 무관하게 확인이 정확히 가운데에 온다. */
.support-actions {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
}
.support-actions .support-submit,
.support-actions .support-download { grid-column: 2; }
.support-back-btn {
  grid-column: 1;
  justify-self: start;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-top: 4px;
  padding: 8px 10px;
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
  border-radius: 8px;
}
.support-back-btn:hover { color: var(--accent); background: rgba(47, 111, 224, .07); text-decoration: none; }
.support-back-btn::before {
  content: "";
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  border-right: 7px solid currentColor;
}

.support-error {
  background: rgba(214, 69, 69, .1);
  border: 1px solid var(--err);
  color: var(--err);
  padding: 10px 12px;
  border-radius: 8px;
  font-size: 14px;
}

.support-foot {
  margin: 0;
  font-size: 14px;
  color: var(--muted);
  text-align: center;
  line-height: 1.7;
}
.support-foot a { color: var(--muted); text-decoration: underline; }
.support-foot a:hover { color: var(--accent); }

/* --- support: accepted state --------------------------------------------- */

/* Which channel the code resolved to. Shown ONLY here, after the code
   proved the visitor belongs to it — never on the form and never in an
   error, because the channel list is the customer list. */
.support-channel {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
  font-size: 16px;
}
.support-channel-label {
  font-size: 12px;
  font-weight: 700;
  color: var(--accent);
  background: rgba(47, 111, 224, .1);
  border-radius: 5px;
  padding: 3px 8px;
  flex: none;
}

.support-download { display: flex; justify-content: center; }

/* Stand-in while the download step is unbuilt. Amber, not red: nothing
   has failed — the code was accepted — the file just is not there yet. */
.support-pending {
  display: flex;
  flex-direction: column;
  gap: 4px;
  background: rgba(176, 125, 18, .09);
  border: 1px solid rgba(176, 125, 18, .35);
  border-radius: 9px;
  padding: 14px 16px;
  font-size: 14px;
  color: var(--warn);
}
.support-pending span { color: var(--muted); }

.support-steps {
  margin: 0;
  padding-left: 22px;
  display: flex;
  flex-direction: column;
  gap: 9px;
  font-size: 14px;
  line-height: 1.6;
}
.support-steps code {
  background: var(--panel-2);
  border-radius: 4px;
  padding: 1px 5px;
  font-family: "Pretendard", "Noto Sans KR", sans-serif; font-variant-numeric: tabular-nums;
  font-size: 12px;
}

/* The persistence disclosure. Boxed and kept full-strength — this is
   what makes a self-served install honest, so it must not read as fine
   print (AGENTS.md "Persistence and removal"). */
.support-notice {
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 16px 18px;
}
.support-notice h2 {
  margin: 0 0 10px;
  font-size: 16px;
  font-weight: 700;
  color: var(--text);
}
.support-notice ul {
  margin: 0;
  padding-left: 18px;
  display: flex;
  flex-direction: column;
  gap: 7px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
}
.support-notice strong { color: var(--text); }

.support-back {
  align-self: center;
  font-size: 12px;
  color: var(--muted);
  text-decoration: underline;
}
.support-back:hover { color: var(--accent); }

/* --- admin: a channel's support code ------------------------------------- */

.support-token {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px 14px;
  margin-bottom: 10px;
}
/* The value an admin reads down the phone. Same monospace + tracking as
   the customer's input field, so both ends of the call see one shape. */
.support-token-code {
  font-family: "Pretendard", "Noto Sans KR", sans-serif; font-variant-numeric: tabular-nums;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: .12em;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 8px 14px;
  user-select: all;   /* one click selects the whole code */
}
.support-token-meta {
  font-size: 12px;
  color: var(--muted);
  line-height: 1.7;
}

/* The guest channel, flagged wherever channels are listed. Amber rather
   than the accent blue: it is a holding area that wants attention, not a
   destination. */
.tag-guest {
  display: inline-block;
  margin-left: 8px;
  vertical-align: middle;
  font-size: 12px;
  font-weight: 700;
  color: var(--warn);
  background: rgba(176, 125, 18, .12);
  border: 1px solid rgba(176, 125, 18, .3);
  border-radius: 5px;
  padding: 2px 7px;
}
.guest-note {
  background: rgba(176, 125, 18, .08);
  border: 1px solid rgba(176, 125, 18, .28);
  border-radius: 9px;
  padding: 12px 15px;
  margin: 0 0 18px;
  font-size: 12px;
  line-height: 1.65;
  color: var(--muted);
}
.guest-note strong { color: var(--text); }

/* Secondary line under a numbered step — the "파일 이름은 바꾸지 마세요"
   caveat belongs with its step, not as another numbered instruction. */
.support-step-sub {
  display: block;
  margin-top: 3px;
  font-size: 14px;
  color: var(--muted);
}

.support-warn { color: var(--err); font-weight: 600; }

/* Install-state badge, beside the online/offline one.
   Neutral by default: "설치됨" and "서비스" are the normal, settled
   states and should not shout. "임시 실행" is amber because it is the
   one that needs a decision — a PC that will vanish on reboot. */
.badge.install {
  background: rgba(95, 107, 122, .12);
  color: var(--muted);
}
.badge.install.temp {
  background: rgba(176, 125, 18, .16);
  color: var(--warn);
}

/* Second filter row sits tighter under the tag row. */


/* Where a support-page host came from. Quieter than a channel chip:
   it is provenance, not membership, and the two must not be confused
   when they sit two lines apart on the same card. */
.origin-tag {
  display: inline-block;
  font-size: 12px;
  color: var(--warn);
  background: rgba(176, 125, 18, .1);
  border: 1px dashed rgba(176, 125, 18, .4);
  border-radius: 6px;
  padding: 1px 8px;
}

/* A membership option that cannot be selected — currently only the
   guest channel, which nothing but the support page may put a host
   into. Greyed rather than hidden: an operator should see that the
   channel exists and is simply not theirs to assign. */
.pick.pick-locked {
  opacity: .45;
  cursor: not-allowed;
}
.pick.pick-locked .pick-sub { font-style: italic; }

/* 지원 코드 대화상자 (layout.html "supportcodes"). */
.sc-body { margin: 12px 0 4px; }
/* 채널 수정 창의 지원 코드 탭: 코드 하나 + 복사·발급 (2026-10-06). */
.sc-one-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sc-one-row .btn { white-space: nowrap; }
.sc-code-big { font-size: 20px; font-weight: 700; letter-spacing: .06em; margin-right: auto; }
.sc-one .btn.armed { background: var(--ok); border-color: var(--ok); color: #fff; }
.btn.btn-danger-outline { color: var(--err); border-color: var(--err); background: var(--panel); }
.sc-one .btn.btn-danger-outline.armed { background: var(--err); border-color: var(--err); color: #fff; }
.sc-table { width: 100%; border-collapse: collapse; }
.sc-table td { padding: 10px 6px; border-bottom: 1px solid var(--line); }
.sc-table td:last-child { text-align: right; width: 1%; white-space: nowrap; }
/* 마지막 채널 행 아래 선은 지운다 — 닫기 행의 구분선(#sc-modal .modal-actions
   border-top)과 겹쳐 이중선처럼 보였다. 채널이 여러 개면 행 사이 선은 남는다. */
.sc-table tr:last-child td { border-bottom: none; }
/* 복사·재발급은 다른 모달 버튼(.btn)과 같은 크기로. 사이 여백만 준다. */
.sc-table td:last-child .btn + .btn { margin-left: 8px; }
/* 코드는 줄바꿈하지 않는다. 재발급 무장 때 배경이 반투명 녹색에서 흰색으로
   천천히 사라지므로(JS 가 배경색을 지우면 이 transition 을 탄다) 그 전이를 여기 둔다. */
.sc-code { white-space: nowrap; border-radius: 6px; transition: background-color 3s ease; }
/* 재발급을 한 번 눌러 무장된 상태 — 폭은 그대로 두고 녹색으로 알린다.
   한 번 더 누르면 실제로 재발급된다. */
.sc-table .btn.armed { background: var(--ok); border-color: var(--ok); color: #fff; }
/* 지원 코드 대화상자는 코드·버튼이 한 줄에 들어가도록 조금 넓힌다. */
#sc-modal .modal { width: min(560px, 94vw); }
/* 지원 코드 대화상자: 닫기 버튼 행을 위 내용과 선으로 분리한다. */
#sc-modal .modal-actions { border-top: 1px solid var(--line); padding-top: 12px; }
.sc-code {
  font-family: "Pretendard", "Noto Sans KR", sans-serif; font-variant-numeric: tabular-nums;
  letter-spacing: .06em;
  font-weight: 600;
}

/* 페이지 안 탭 (내 계정). 밑줄형 — 채널 전환기(.chansw)의 알약형은 필터라서,
   섹션 전환인 탭과 모양을 달리한다. */
.tabs {
  display: inline-flex;
  gap: 2px;
  margin: 4px 0 22px;
  padding: 3px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 10px;
}
/* 세그먼트형 탭 (2026-10-06 소유자: 밑줄형에서 변경). 선택된 칸이 흰 바탕으로 떠오른다. */
.tab {
  padding: 7px 16px;
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
  border-radius: 8px;
  text-decoration: none;
  transition: background .15s ease, color .15s ease;
}
.tab:hover { color: var(--text); text-decoration: none; }
.tab.active { color: var(--accent); background: var(--panel); box-shadow: 0 1px 3px rgba(17, 24, 39, .15); }
/* 모달 안 탭 (호스트 편집). 버튼이라 기본 버튼 모양을 지운다. */
button.tab { background: none; border: 0; cursor: pointer; font-family: inherit; }
button.tab.active { background: var(--panel); }
.edit-tabs { margin: 0 0 16px; }
/* 호스트 편집 모달은 탭을 바꿔도 위치·크기가 그대로여야 한다 (2026-10-06).
   위쪽 고정 + 높이 고정, 내용이 넘치면 패널 안에서 스크롤. */
#edit-modal, #bulk-modal { align-items: flex-start; padding-top: 8vh; }
/* 2026-10-15 소유자: 채널이 많아 좁다 — 키움. 일괄 편집 창도 같은 크기 (2026-10-16). */
#edit-modal .modal, #bulk-modal .modal {
  width: min(900px, 94vw);
  height: min(680px, 86vh);
  display: flex;
  flex-direction: column;
}
#edit-modal [data-edit-panel], #bulk-modal [data-bulk-panel] { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
#edit-modal .edit-close, #bulk-modal .edit-close { margin-top: auto; }
/* 사용자 모달도 탭을 바꿔도 위치·크기 고정 (2026-10-06). */
#user-modal { align-items: flex-start; padding-top: 8vh; }
/* 채널 수정 창도 탭을 바꿔도 위치·크기 고정 (2026-10-06). */
#edit-channel-modal { align-items: flex-start; padding-top: 8vh; }
/* 2026-10-13: 공개 칸·사용자 탭이 들어가 키움 (정보 탭이 스크롤 없이 들어간다). */
#edit-channel-modal .modal { width: min(720px, 94vw); height: 520px; max-height: 86vh; display: flex; flex-direction: column; }
/* 사용자 탭: 왼쪽 접근 사용자, 오른쪽 추가 후보. 좁으면 위아래로. */
.ch-users { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; flex: 1 1 auto; min-height: 0; }
.ch-users-col { display: flex; flex-direction: column; min-height: 0; }
.ch-users-h { font-size: 14px; margin: 0 0 6px; }
/* "추가" 제목 줄: 오른쪽 끝에 전체+ (2026-10-16). */
.ch-users-hrow { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin: 0 0 6px; }
.ch-users-hrow .ch-users-h { margin: 0; }
.ch-add-all { padding: 4px 10px; font-size: 12px; }
.ch-add-all[hidden] { display: none; }
/* 넘기기 (2026-10-16): 행 안의 작은 버튼. 확인 대기 중엔 강조. */
.ch-transfer { flex: none; padding: 3px 8px; font-size: 12px; }
.ch-transfer.armed { border-color: var(--accent); color: var(--accent); }
/* 사용자 편집 묶음: 모달 높이를 채우고 목록만 스크롤. */
.ch-users-box { display: flex; flex-direction: column; gap: 10px; flex: 1 1 auto; min-height: 0; }
/* 공용 입력 규칙은 type=search 를 안 덮는다 (내 원격 검색칸은 따로 꾸밈) — 여기만 맞춘다. */
.ch-user-q { background: #fff; color: var(--text); border: 1px solid var(--line); border-radius: 8px;
  padding: 8px 11px; font: inherit; flex: none; }
.ch-user-list { list-style: none; margin: 0; padding: 0; overflow-y: auto; flex: 1 1 auto; min-height: 0;
  border: 1px solid var(--line); border-radius: 8px; }
.ch-user-list li { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-bottom: 1px solid var(--line); }
.ch-user-list li:last-child { border-bottom: 0; }
.ch-user-list > p { margin: 0; padding: 8px 10px; }
.ch-user-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ch-user-type { font-size: 12px; color: var(--muted); flex: none; }
.ch-user-act { flex: none; width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: 7px; background: var(--panel-2); color: var(--text); padding: 0; cursor: pointer; }
.ch-user-act.is-add:hover { border-color: var(--accent); color: var(--accent); }
.ch-user-act.is-del:hover { border-color: var(--err); color: var(--err); }
@media (max-width: 560px) { .ch-users { grid-template-columns: 1fr; } }
#edit-channel-modal [data-ch-panel] { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 12px; }
#edit-channel-modal [data-ch-panel] > .modal-actions { margin-top: auto; }
#edit-channel-modal [data-ch-panel][hidden] { display: none; }
/* 채널 상세의 사용자 창: 채널 수정 창 사용자 탭과 같은 크기·구성 (2026-10-16). */
#add-user-modal .modal { width: min(720px, 94vw); height: 520px; max-height: 86vh; display: flex; flex-direction: column; gap: 12px; }
#add-user-modal .modal-actions { margin-top: auto; }
input[readonly] { background: var(--panel-2); color: var(--muted); }
#user-modal .modal { height: min(600px, 84vh); display: flex; flex-direction: column; }
#user-modal #user-form { display: contents; }
#user-modal [data-user-panel] { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
#user-modal .user-foot { margin-top: auto; border-top: 1px solid var(--line); padding-top: 14px; }
#edit-modal .host-delete { margin-right: auto; }
#edit-modal .edit-close .save-status, #bulk-modal .edit-close .save-status { margin-right: 0; margin-left: auto; }
#edit-modal .host-delete + .save-status { margin-left: 0; }
.edit-head { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
.edit-head h2 { margin: 0; flex: 1 1 auto; min-width: 0; }
.edit-x { flex: none; }
.edit-close { border-top: 1px solid var(--line); padding-top: 14px; margin-top: 18px; }

/* --- 좁은 화면(휴대폰) 일반 페이지 대응 -----------------------------------
   원격 화면(fullbleed)의 반응형은 위쪽 .menubar/.toolbar 미디어쿼리가 따로
   맡는다. 여기는 로그인·목록·관리·폼 등 표준 상단바 페이지가 폰 폭에서
   가로로 넘치지 않게 한다. 로그인/지원 카드와 .page-head/.tag-filter/
   .channel-switch 는 이미 유동·wrap 이라 건드리지 않는다. */
@media (max-width: 640px) {
  /* 본문·카드 여백을 줄여 좁은 폭을 되찾는다. */
  main { padding: 16px 14px; }
  .card { padding: 16px; }

  /* 상단바: 브랜드+메뉴+이름+로그아웃이 한 줄에 안 들어간다. 브랜드는 첫 줄에
     두고 nav 를 그 아래 한 줄로 내려 필요하면 줄바꿈한다. 고정 높이(54px)를
     풀어 클리핑을 막는다(그 높이는 sticky 라 폰에선 조금 커져도 무방). */
  .topbar {
    flex-wrap: wrap;
    height: auto;
    min-height: 54px;
    gap: 6px 14px;
    padding: 8px 14px;
  }
  .topbar nav {
    flex-basis: 100%;
    flex-wrap: wrap;
    gap: 2px 6px;
  }
  /* 계정 메뉴는 compact 한 단일 컨트롤이라 nav 가 줄바꿈돼도 오른쪽 끝에
     붙어 있으면 된다(.acct 의 margin-left:auto 그대로 둔다). */
  /* 다운로드 메뉴는 폭(230px)이 있어 버튼 기준으로 열면 화면 오른쪽을 넘는다.
     상단바(sticky 라 기준 상자) 폭에 맞춰 연다. */
  .menu.dl { position: static; }
  .dl-pop { left: 14px; right: 14px; min-width: 0; }

  /* 관리 테이블은 열이 많아 폰 폭을 넘는다. 패널을 가로로 스크롤시켜 넘치는
     열에 닿게 한다. 폼(.stack)은 유동폭이라 폭을 강제하지 않으므로, 넘치는
     것은 표뿐이고 그 표만 스크롤된다. 모달은 position:fixed 라 안 잘린다. */
  .admin-panel { overflow-x: auto; padding: 18px 16px 22px; }
  .list-panel { padding: 16px 14px 4px; }

  /* 호스트 카드: 아주 좁은 폰에서 minmax(330px) 가 살짝 넘칠 수 있어 한 줄에
     하나로 채운다. */
  .fav-list,
  .card-grid { grid-template-columns: 1fr; }

  /* 관리 메뉴: 폰에서는 2열이 좁아지므로 1열로. */
}

/* --- 원격 화면 터치 조작 --------------------------------------------------
   canvas 에서 시작한 터치 제스처를 브라우저가 페이지 스크롤/줌으로 가로채지
   않게 한다. remote.js 의 터치 리스너가 제스처를 입력으로 번역한다. */
#screen { touch-action: none; }

/* 입력 도구(키보드·텍스트)는 도구 막대의 "화면 키보드" 버튼으로 켜고 끈다 (remote.js).
   [hidden] 이 .mb-group 의 display:flex 에 지지 않게 명시한다. 줄어들지 않는다. */
#input-tools { flex-shrink: 0; }
#input-tools[hidden] { display: none; }
#trackpad-toggle { flex-shrink: 0; }
#trackpad-toggle[hidden] { display: none; }
#fullscreen-toggle { flex-shrink: 0; }
#fullscreen-toggle[hidden], #fullscreen-toggle .ic[hidden] { display: none; }
/* 배율 바로가기: 최대화와 붙어 있으면 잘못 누른다 (2026-10-28 소유자) — 간격을 둔다.
   자리가 되면 상단바 정중앙 (.center, remote.js placeQuickZoom), 좌우와 겹치면 오른쪽 묶음으로. */
#quick-zoom { margin-right: 40px; }
#quick-zoom.center { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); margin: 0; }
#quick-zoom[hidden] { display: none; }
#qz-level .ic[hidden] { display: none; }
#qz-pct { font-size: 12px; }
/* 자리 비움 예고: 원격 화면을 가리지 않게 왼쪽 위 구석의 작은 판 (2026-10-28 소유자).
   left·top 은 remote.js placeIdleToast 가 사이드바·상단바를 비켜 정한다. 모달(50) 아래.
   작아도 눈에 띄게 빨간 테두리가 깜빡인다. */
.idle-toast {
  position: fixed; left: 16px; top: 64px; z-index: 40;
  background: var(--panel); border: 4px solid var(--err); border-radius: 10px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, .28);
  animation: idle-blink 1s ease-in-out infinite;
  padding: 12px 14px; display: flex; flex-direction: column; gap: 10px;
  font-size: 14px; max-width: calc(100vw - 32px);
}
.idle-toast[hidden] { display: none; }
@keyframes idle-blink {
  0%, 100% { border-color: var(--err); box-shadow: 0 0 0 0 rgba(229, 72, 77, .55), 0 6px 24px rgba(0, 0, 0, .28); }
  50% { border-color: transparent; box-shadow: 0 0 0 6px rgba(229, 72, 77, 0), 0 6px 24px rgba(0, 0, 0, .28); }
}
@media (prefers-reduced-motion: reduce) { .idle-toast { animation: none; } }
.idle-toast #idle-count { color: var(--err); font-variant-numeric: tabular-nums; }
.idle-actions { display: flex; gap: 6px; justify-content: flex-end; }
.idle-actions .btn[hidden] { display: none; }
#qz-level { width: auto; min-width: 36px; padding-left: 4px; padding-right: 4px; } /* "400%" 가 들어가게 */


/* 화면 제어 잠금(모니터링). 켜진 상태의 잠금 버튼은 채워서 눈에 띄게 한다. */
#lock-toggle.locked { background: var(--accent); border-color: var(--accent); color: #fff; }
/* 켜고 끄는 버튼(화면 키보드·트랙패드 모드): 켜짐은 제어 잠금처럼 채운다. */
.tgl-btn[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: #fff; }
/* 녹화 중: 빨간 정지 버튼 + 은은한 깜빡임 (녹화 중임을 분명히). */
.mb-btn.is-recording { color: var(--err); border-color: var(--err); }
.mb-btn.is-recording .mb-label { color: var(--err); }
/* 소리를 켰는데 그 PC 에 장치가 없거나 못 가져올 때: 눌린 채로 흐리게 (이유는 title). */
.mb-btn.audio-nodev { opacity: .55; }
/* 모니터링 모드 (2026-10-16 소유자, work.txt): 보기만 남긴다 — 소리·보내기·입력 도구·제어 요청을 숨긴다.
   JS 가 따로 hidden 을 켜고 끄는 것들이라 !important 로 덮는다. */
body.monitoring .mon-hide { display: none !important; }
/* 듣는 크기 슬라이더: 묶음 한 줄을 다 쓴다 (사이드바·떠 있는 막대 모두). */
/* order: 묶음 맨 끝 (휴대폰에서 최대화 버튼이 뒤에 붙어도 그 아래로). */
.audio-vol { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--muted); order: 99; box-sizing: border-box; padding: 0 2px; }
.audio-vol-name { flex: none; white-space: nowrap; }
.audio-vol input[type="range"] { flex: 1 1 auto; min-width: 0; width: 0; margin: 0; accent-color: var(--accent); }
.audio-vol-text { flex: none; min-width: 40px; text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; color: var(--text); }
.toolbar.sidebar .audio-vol { flex-basis: 100%; }
.toolbar:not(.sidebar) .mb-group .audio-vol { grid-column: 1 / -1; }
.menubar .audio-vol { display: none; }
.mb-btn.is-recording .ic-rec-on { animation: rec-blink 1.2s steps(1) infinite; }
@keyframes rec-blink { 50% { opacity: .35; } }
/* 잠금 해제 확인창의 무작위 숫자와 입력칸. */
.unlock-code {
  margin: 14px 0 10px;
  text-align: center;
  font-family: "Pretendard", "Noto Sans KR", sans-serif; font-variant-numeric: tabular-nums;
  font-size: 34px;
  font-weight: 700;
  letter-spacing: .3em;
  color: var(--text);
}
#unlock-input {
  display: block;
  margin: 0 auto;
  width: 120px;
  text-align: center;
  font-size: 18px;
  letter-spacing: .2em;
}
.unlock-err { margin: 10px 0 0; text-align: center; color: var(--err); font-size: 12px; }

/* 모니터링(제어 잠금) 표시. 화면 위 가운데 상단에 떠서, 입력이 왜 안 먹는지
   알린다. 그림 위에 떠 있으므로 그림자로 띄우고 클릭은 통과시킨다. */
.lock-badge {
  position: absolute;
  top: 14px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 15;
  padding: 6px 14px;
  border-radius: 999px;
  background: rgba(47, 111, 224, .92);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .01em;
  box-shadow: 0 6px 20px rgba(0, 0, 0, .4);
  pointer-events: none;
}

/* 제어 요청 승인 버튼 카운트다운 (2026-10-07): 숫자 폭이 흔들리지 않게, 5초 남으면 붉게. */
#control-approve.counting { font-variant-numeric: tabular-nums; min-width: 96px; }
#control-approve.counting.urgent { background: var(--danger, #d64545); border-color: var(--danger, #d64545); }

/* 터치 길게 누름 표시 (2026-10-07): 누른 자리에 원이 차오르고, 다 차면 우클릭.
   뷰어 로컬 장식이라 터치를 가로채지 않는다 (pointer-events:none). */
.press-ring {
  position: fixed; z-index: 60; width: 56px; height: 56px; margin: -28px 0 0 -28px;
  border-radius: 50%; pointer-events: none;
  border: 3px solid rgba(255, 255, 255, .85);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .35);
  background: rgba(47, 111, 224, .25);
  transform: scale(.35); opacity: .6;
  animation: press-fill linear forwards;
}
.press-ring.done { animation: press-done .26s ease-out forwards; background: rgba(47, 111, 224, .55); }
@keyframes press-fill { to { transform: scale(1); opacity: 1; } }
@keyframes press-done { from { transform: scale(1); opacity: 1; } to { transform: scale(1.4); opacity: 0; } }

/* 트랙패드 모드 포인터 (2026-10-12): 내 화면에만 그린다. 화살표 끝(왼쪽 위)이 누르는 자리.
   호스트 포인터는 영상으로 늦게 오므로 손가락을 따라 즉시 움직이는 표시가 따로 있다. */
.tp-cursor {
  /* 원격 화면 위, 도구 막대(20)·팝업·모달 아래 — 열어 둔 막대를 가리지 않게. */
  position: fixed; z-index: 16; width: 24px; height: 24px; pointer-events: none;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M2 2l0 18 5-5 3.5 7 3-1.5-3.5-7 7 0z' fill='%23fff' stroke='%23000' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat 0 0 / 24px 24px;
  filter: drop-shadow(0 0 2px rgba(47, 111, 224, .9));
}
.tp-cursor[hidden] { display: none; }

/* 텍스트 보내기 창 (2026-10-07). 폰 키보드가 올라와도 입력칸이 보이게 위쪽에 둔다. */
#send-text-modal { align-items: flex-start; padding-top: 10vh; }
#send-text-input { width: 100%; box-sizing: border-box; font: inherit; font-size: 16px; resize: vertical; }

/* 가상 키보드 숨은 입력: 보이지 않게, 그러나 화면 안 고정 위치 (스크롤 유발 방지).
   16px 는 iOS 가 포커스 때 페이지를 확대하지 않는 최소 글자 크기. */
#kbd-sink {
  position: fixed; left: 0; top: 0; width: 1px; height: 1px;
  opacity: 0; border: 0; padding: 0; font-size: 16px; pointer-events: none;
}
/* 키보드가 열려 있는 동안 버튼을 눌린 상태로 보인다 (다시 누르면 닫힘). */
#kbd-btn.on { background: var(--accent); border-color: var(--accent); color: #fff; }

/* 사용 가이드 (/guide). 스크린샷은 테두리로 페이지 바탕과 구분하고, 번호 목록은
   이미지의 빨간 번호와 같은 모양으로 맞춘다. */
/* 목차: 넓은 화면은 왼쪽 세로 메뉴, 좁으면 위쪽 가로 줄. 둘 다 상단바(54px) 아래에 붙어
   스크롤을 따라온다. */
.guide-wrap { display: grid; grid-template-columns: 168px minmax(0, 1fr); gap: 24px; align-items: start; }
.guide-toc {
  position: sticky; top: 70px; display: flex; flex-direction: column; gap: 2px;
  padding: 8px; background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
}
.guide-toc a {
  padding: 8px 12px; border-radius: 8px; color: var(--text); font-size: 14px; text-decoration: none;
}
.guide-toc a:hover { color: var(--accent); }
.guide-toc a.active { background: var(--accent); color: #fff; }
.guide section {
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
  padding: 20px 24px; margin: 0 0 20px; scroll-margin-top: 70px;
}
@media (max-width: 860px) {
  .guide-wrap { display: block; }
  .guide-toc {
    top: var(--guide-top, 54px); z-index: 10; flex-direction: row; overflow-x: auto; scrollbar-width: none;
    margin: 0 -24px 16px; border-radius: 0; border-width: 0 0 1px; padding: 8px 24px;
  }
  .guide-toc::-webkit-scrollbar { display: none; }
  .guide-toc a { flex: none; padding: 6px 12px; }
  .guide section { scroll-margin-top: calc(var(--guide-top, 54px) + 64px); }
}
@media (max-width: 640px) {
  .guide-toc { margin: 0 -14px 16px; padding: 8px 14px; }
}
.guide h2 { margin: 0 0 12px; font-size: 20px; }
.guide h3 { margin: 20px 0 8px; font-size: 16px; }
.guide h4 { margin: 16px 0 6px; font-size: 14px; color: var(--muted); }
.guide p, .guide li { line-height: 1.7; }
.guide figure { margin: 12px 0; }
.guide figure img {
  display: block; max-width: 100%; height: auto;
  border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,.06);
}
.guide figure.guide-narrow img { max-width: min(100%, 520px); }
.guide-marks { list-style: none; padding: 0; counter-reset: gm; }
.guide-marks li { counter-increment: gm; position: relative; padding-left: 30px; margin: 6px 0; }
.guide-marks li::before {
  content: counter(gm); position: absolute; left: 0; top: 3px;
  width: 20px; height: 20px; border-radius: 50%; background: #e5484d; color: #fff;
  font: 700 12px/20px sans-serif; text-align: center;
}
.guide-key {
  display: inline-block; padding: 0 6px; border: 1px solid var(--line); border-bottom-width: 2px;
  border-radius: 4px; background: var(--panel-2); font-size: 12px; line-height: 20px;
}
.guide-table { border-collapse: collapse; margin: 12px 0; width: 100%; max-width: 520px; }
.guide-table th, .guide-table td { border: 1px solid var(--line); padding: 8px 12px; text-align: left; font-size: 14px; }
.guide-table th { background: var(--panel-2); }
@media (max-width: 560px) {
  .guide section { padding: 16px; }
}
.guide-grip { vertical-align: middle; color: var(--muted); padding: 1px 2px; }
.guide-grip svg { display: block; width: 16px; height: 16px; }

/* 지원 페이지 뒤로: 카드 왼쪽 위 (page-back 모양). 로고는 가운데 그대로. */
/* 지원 페이지 뒤로: 주 버튼 줄 맨 왼쪽 (.support-actions 1열), 주 버튼은 가운데. */
.support-row-back { grid-column: 1; justify-self: start; margin: 4px 0 0; }
/* 다운로드 안내의 뒤로: 카드 왼쪽 아래. */
.support-foot-back { align-self: flex-start; margin: 0; }

/* 모니터 목록 (#monitor-btn, 아주 좁은 창). 상단바 바로 아래 오른쪽에 떠서 탭을 세로로
   쌓는다 — 넘치면 목록 안에서 스크롤하므로 좌우 버튼은 쓰지 않는다. */
#monitor-btn { flex-shrink: 0; }
#monitor-btn[aria-expanded="true"] { background: var(--accent); border-color: var(--accent); color: #fff; }
.monitor-pop {
  position: absolute; z-index: 21; top: 52px; right: 10px;
  max-width: calc(100vw - 20px); max-height: calc(100vh - 72px); overflow-y: auto;
  padding: 8px; background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, .45);
}
.monitor-pop[hidden] { display: none; }
.monitor-pop .mb-tabs-track { flex-direction: column; align-items: stretch; overflow: visible; }
.monitor-pop .mb-tab { text-align: left; }
.monitor-pop .mb-tab-arrow { display: none !important; }

/* 번호로 줄인 모니터 탭 (자리 모자랄 때): 숫자 한 글자라 좌우 여백을 줄여 정사각에 가깝게. */
.mb-tabs.compact .mb-tab { padding: 6px 10px; min-width: 32px; text-align: center; }

/* 설정 탭 안에서 폼이 둘 이상일 때 사이를 구분한다. */
.settings-block { margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--line); }

/* 키 보내기 목록 (#keys-pop). 포인터 강조 창과 같은 떠 있는 판. 큰 글씨·넓은 버튼 —
   나이 많은 사용자가 누르기 쉽게 (2026-10-11). */
.keys-pop { display: flex; flex-direction: column; gap: 6px; padding: 10px; min-width: 240px; }
.keys-pop[hidden] { display: none; }
.keys-pop .keys-item { display: block; width: 100%; text-align: left; font-size: 14px; padding: 9px 13px; }
.keys-pop .keys-sas { font-weight: 700; }
.keys-row { display: flex; gap: 6px; }
.keys-row .keys-key { flex: 1; font-size: 14px; padding: 9px 0; text-align: center; }

/* 사이드바 (2026-10-11 소유자). 넓은 창(900px 초과, remote.js sideNarrow)에서 기능 버튼 막대가 화면 옆에
   상단바 아래부터 바닥까지 붙는다. 원격 화면은 remote.js 가 그 폭만큼 비켜 놓는다
   (가리지 않게). 위·아래로 붙이는 선택은 사이드바에서는 뜻이 없어 감춘다. */
.toolbar.sidebar {
  bottom: 0;
  transform: none;
  max-height: none;
  border-radius: 0;
  box-shadow: none;
  border-top: 0;
  border-bottom: 0;
  padding: 12px 10px;
  justify-content: flex-start;
  overflow-y: auto;
}
.toolbar.sidebar.dock-right { right: 0; border-right: 0; }
.toolbar.sidebar.dock-left { left: 0; border-left: 0; }
/* 사이드바에서는 위치 버튼 넷 대신 좌우 바꾸기 버튼 하나 (#side-swap). 떠 있는 막대엔 없다. */
.toolbar .dock-btn { display: none; }
#side-swap { display: inline-flex; gap: 8px; padding: 0 12px; }
#side-swap .mb-label { margin-left: 0; font-size: 14px; }
.toolbar.sidebar .dock-picker { max-width: none; width: 100%; }
.toolbar.sidebar .dock-picker { margin-top: auto; }

/* 사이드바 버튼 글자 (2026-10-11 소유자). 아이콘만으로는 찾기 어려워 사이드바에서는 글자를
   붙이고 한 줄에 하나씩 넓게 놓는다. 상단바·떠 있는 막대는 자리가 좁아 아이콘만 (글자는
   title·aria-label 에 있다). 키 보내기는 원래 글자 버튼이라 어디서나 글자를 둔다. */
.mb-label { display: none; }
.mb-label-always { display: inline; }
#keys-btn .ic { display: none; }
.toolbar.sidebar { width: 216px; }
.toolbar.sidebar .mb-label { display: inline; margin-left: 8px; font-size: 14px; white-space: nowrap; }
.toolbar.sidebar .mb-btn.has-label {
  width: 100%;
  justify-content: flex-start;
  padding: 0 12px;
}
.toolbar.sidebar .mb-btn.has-label .ic:not([hidden]) { display: block; margin: 0; flex: none; }
.toolbar.sidebar #keys-btn .ic { display: block; }
.toolbar.sidebar .mb-btn.has-label { height: 40px; }
/* 다닥다닥 붙지 않게 (2026-10-11 소유자): 버튼 사이 10px, 묶음 사이는 선 위아래로 14px. */
.toolbar.sidebar { padding: 16px 14px; gap: 14px; }
.toolbar.sidebar .mb-group { gap: 10px; }
.toolbar.sidebar .mb-group + .mb-group,
.toolbar.sidebar .mb-tabs + .mb-group { padding-top: 14px; }
.toolbar.sidebar .mb-btn:not(.has-label) { height: 36px; min-width: 40px; }
/* 한 줄에 두 개 (2026-10-16 소유자). 반 폭에 "파일 다운로드" 가 들어가게 아이콘 위·이름 아래로
   세운다 (떠 있는 막대 타일과 같은 모양). 접기·위치 바꾸기·포인터 강조는 한 줄 그대로. */
.toolbar.sidebar .mb-tools .mb-group .mb-btn.has-label {
  width: calc(50% - 5px);
  height: 56px;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  padding: 4px 2px;
}
.toolbar.sidebar .mb-tools .mb-group .mb-btn.has-label .mb-label {
  margin-left: 0; font-size: 12px; line-height: 14px;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}
.toolbar.sidebar .mb-tools .mb-pointer .mb-btn.has-label { width: 100%; height: 40px; flex-direction: row; justify-content: flex-start; }
/* 홀수로 남는 버튼은 왼쪽 칸에 (가운데로 가면 짝이 어긋나 보인다). 확대 줄은 셋이 폭을 나눠 채운다. */
.toolbar.sidebar .mb-tools .mb-group { justify-content: flex-start; }
.toolbar.sidebar #zoom-out, .toolbar.sidebar #zoom-level, .toolbar.sidebar #zoom-in { flex: 1 1 0; }

/* 사이드바 접기 (맨 위) 와 접혔을 때의 가장자리 펴기 손잡이 (2026-10-11 소유자).
   화살표: 오른쪽 사이드바면 접기는 → (바깥), 펴기는 ← (안쪽). 왼쪽이면 뒤집는다. */
.side-collapse { display: none; }
.toolbar.sidebar .side-collapse { display: inline-flex; }
.side-collapse.left .ic { transform: scaleX(-1); }
.side-expand {
  position: fixed;
  z-index: 25;
  right: 0;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  padding: 12px 6px;
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--line);
  border-right: 0;
  border-radius: 10px 0 0 10px;
  font-size: 12px;
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .35);
}
.side-expand:hover { border-color: var(--accent); }
.side-expand .ic { transform: scaleX(-1); }
.side-expand.left { right: auto; left: 0; border-right: 1px solid var(--line); border-left: 0; border-radius: 0 10px 10px 0; }
.side-expand.left .ic { transform: none; }
/* 접기 버튼과 첫 묶음(포인터 강조) 사이 구분선 — 다른 묶음 경계와 같게. */
.toolbar.sidebar .side-collapse ~ .mb-tools > .mb-group:first-child {
  border-top: 1px solid var(--line);
  padding-top: 14px;
  align-self: stretch;
}

/* 사이드바 묶음 이름 (data-title). 버튼만으로 뜻이 안 읽히는 묶음에만 단다 (2026-10-11). */
.toolbar.sidebar .mb-group[data-title]::before {
  content: attr(data-title);
  flex-basis: 100%;
  text-align: left;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
}
/* 사이드바 강조 설정: 색 칩 + 글자 버튼, 체크 아래 한 줄. 색은 왼쪽 칩으로만 보인다. */
.toolbar.sidebar .mb-pointer .mb-check { order: 1; }
.toolbar.sidebar .mb-swatch {
  order: 2;
  width: 100%;
  height: 40px;
  background: var(--panel-2);
  justify-content: flex-start;
  padding: 0 12px;
  gap: 8px;
}
.toolbar.sidebar .mb-swatch::before {
  content: "";
  width: 20px;
  height: 20px;
  border-radius: 4px;
  border: 1px solid var(--line);
  background: var(--swatch, #ffcc00);
  flex: none;
}
.toolbar.sidebar .mb-swatch .mb-label { margin-left: 0; }
/* 제어 권한 없음 (사이드바에서만). */
.side-note { display: none; }
.toolbar.sidebar .side-note:not([hidden]) {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-self: stretch;
  padding: 10px 12px;
  border-radius: 8px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  font-size: 14px;
  color: var(--muted);
}
.toolbar.sidebar .side-note .btn { width: 100%; }

/* 낮은 창에서 세로 flex 가 항목을 눌러 납작하게 만들지 않게 — 넘치면 사이드바가 스크롤한다. */
.toolbar.sidebar > *,
.toolbar.sidebar .mb-tools > * { flex-shrink: 0; }
.toolbar .dock-picker,
.toolbar.dock-right .dock-picker,
.toolbar.dock-left .dock-picker { max-width: none; } /* 위치 바꾸기 글자가 줄바꿈되지 않게 */

/* --- 떠 있는 도구 막대 = 묶음별 타일 (2026-10-12 소유자) -------------------------
   좁은 창(휴대폰·좁힌 PC 창)의 "…" 막대. 아이콘만 늘어놓던 줄은 뜻이 안 읽히고 구분선이
   줄 머리에 매달렸다. 묶음(화면·보내기·제어)마다 이름을 달고, 아이콘 아래 짧은 이름을 붙인
   타일을 한 줄에 넷씩 놓는다. 사이드바(넓은 창)는 그대로. 위의 dock-* 규칙보다 뒤에 있어야
   이긴다 (선택자 무게가 같다). */
.toolbar:not(.sidebar) {
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: stretch;
  gap: 10px;
  width: min(360px, calc(100vw - 24px));
  padding: 10px 12px 12px;
  /* 가로로 눕힌 폰(높이 390 안팎)에선 타일이 화면 밖으로 넘쳐 제어 묶음에 손이 안 닿았다
     (2026-10-16, 스크린샷·녹화 타일이 늘며). 화면 안에 가두고 안에서 스크롤. */
  max-height: calc(100vh - 72px);
  max-height: calc(100dvh - 72px);
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* 높이를 가두면 묶음이 줄거나(flex-shrink) 가운데 정렬로 위쪽이 잘려 스크롤로도 못 닿는다 —
   위에서부터 쌓고 줄지 않게. */
.toolbar:not(.sidebar) { justify-content: flex-start; }
.toolbar:not(.sidebar) > *,
.toolbar:not(.sidebar) .mb-tools > * { flex-shrink: 0; }
.toolbar:not(.sidebar) .mb-group {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 6px;
  align-self: stretch;
}
.toolbar:not(.sidebar) .mb-group[hidden] { display: none; }
.toolbar:not(.sidebar) .mb-group + .mb-group,
.toolbar:not(.sidebar) .mb-tabs + .mb-group {
  border-left: 0;
  padding-left: 0;
  border-top: 1px solid var(--line);
  padding-top: 10px;
}
.toolbar:not(.sidebar) .mb-group[data-title]::before {
  content: attr(data-title);
  grid-column: 1 / -1;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
}
.toolbar:not(.sidebar) .mb-group .mb-btn {
  flex-direction: column;
  gap: 4px;
  width: auto;
  height: 60px;
  padding: 6px 2px;
  min-width: 0;
}
.toolbar:not(.sidebar) .mb-group .mb-btn[hidden] { display: none; }
.toolbar:not(.sidebar) .mb-group .mb-label {
  display: block;
  margin: 0;
  max-width: 100%;
  font-size: 12px;
  line-height: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.toolbar:not(.sidebar) #keys-btn .ic { display: block; }
/* 확대 버튼은 글자(−, +, 맞춤/130%)가 곧 그림이다. 이름은 ::after 로 (맞춤 칸 글자는
   remote.js 가 textContent 로 바꾸므로 안에 요소를 둘 수 없다). */
.toolbar:not(.sidebar) #zoom-out,
.toolbar:not(.sidebar) #zoom-in { font-size: 22px; line-height: 22px; }
.toolbar:not(.sidebar) #zoom-level { font-size: 14px; line-height: 22px; }
.toolbar:not(.sidebar) #zoom-out::after,
.toolbar:not(.sidebar) #zoom-in::after,
.toolbar:not(.sidebar) #zoom-level::after { font-size: 12px; line-height: 14px; color: var(--text); }
.toolbar:not(.sidebar) #zoom-out::after { content: "축소"; }
.toolbar:not(.sidebar) #zoom-in::after { content: "확대"; }
.toolbar:not(.sidebar) #zoom-level::after { content: "크기"; }
/* 포인터 강조 (좁힌 PC 창에서만 보임, 아래 터치 규칙): 타일이 아니라 한 줄. */
.toolbar:not(.sidebar) .mb-pointer { display: flex; justify-content: flex-start; }
/* 떠 있는 막대는 늘 위 — 위↔아래 옮기기 없음 (2026-10-21 소유자). 사이드바의 좌↔우만 남는다. */
.toolbar:not(.sidebar) .dock-picker { display: none; }

/* 마우스 포인터 강조는 폰·태블릿(주 입력이 터치)엔 뜻이 없다 — 따라갈 마우스가 없다. */
@media (hover: none) and (pointer: coarse) {
  .mb-pointer, #cursor-opts { display: none !important; }
  /* 감춘 강조 줄 다음 묶음이 맨 위가 되므로 그 위 구분선을 뗀다 (떠 있는 막대). */
  .toolbar:not(.sidebar) .mb-pointer + .mb-group { border-top: 0; padding-top: 0; }
}

/* --- 호스트 일괄 관리 (2026-10-16, bulkedit.js) ------------------------------- */
/* 일괄 관리 버튼: 정렬 알약(.sortb)과 같은 크기·모양, 켜지면 같은 파란 채움. 앞의 체크 그림으로 구별. */
.bulk-toggle.sortb { display: inline-flex; align-items: center; gap: 5px; flex: none; }
.bulk-toggle.sortb .ic { width: 14px; height: 14px; }
.bulk-toggle[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: #fff; }
.bulk-on [data-bulk-id] { cursor: pointer; }
.bulk-on .hostcard[data-bulk-id] { position: relative; }
.bulk-on .hostcard[data-bulk-id] .bulk-check { position: absolute; top: 10px; left: 10px; width: 18px; height: 18px; margin: 0; z-index: 2; pointer-events: none; }
.bulk-on .hostcard[data-bulk-id] .hostcard-top { padding-left: 24px; }
.bulk-on tr[data-bulk-id] .bulk-check { margin: 0 8px 0 0; vertical-align: middle; pointer-events: none; }
.hostcard.bulk-sel { outline: 2px solid var(--accent); outline-offset: -2px; }
tr.bulk-sel td { background: rgba(47, 111, 224, .08); }
.bulk-head-check { width: 18px; height: 18px; margin: 0 8px 0 0; flex: none; cursor: pointer; }
/* 화면 아래 고정 막대. 목록 끝 카드가 가리지 않게 선택 모드에선 아래 여백을 둔다. */
.bulk-bar { position: fixed; left: 50%; bottom: 16px; transform: translateX(-50%); z-index: 900;
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px; max-width: calc(100vw - 32px);
  padding: 10px 14px; background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, .18); }
.bulk-bar[hidden] { display: none; }
.bulk-n { font-weight: 700; font-size: 14px; margin-right: 4px; }
.bulk-gap { width: 8px; }
body.bulk-on { padding-bottom: 96px; }
/* 채널 칸의 중간 상태(일부 PC만 속함 = 그대로): 체크와 빈칸 사이로 옅게 채운다. */
.pick.is-mixed { border-color: var(--accent); background: rgba(79, 140, 255, .05); }
@media (max-width: 560px) {
  .bulk-bar { left: 16px; right: 16px; transform: none; max-width: none; }
}
/* 편집 창이 열려 있는 동안 아래 막대는 숨긴다 (창 뒤 어둠 위로 떠 보였다). */
body:has(#bulk-modal:not([hidden])) .bulk-bar { display: none; }
/* 채널 상세: 관리자 지정·넘기기 한 줄 (2026-10-16). */
.mgr-pick { display: flex; align-items: center; gap: 8px; margin: 8px 0 18px; flex-wrap: wrap; }
.mgr-pick select { min-width: 180px; }
/* 채널 사용자 탭 위 관리자 한 줄 (+ 넘기기·지정). 옅은 띠 안에 두고, 고르기 칸과 버튼 높이를
   맞춘다 (2026-10-16 소유자: 버튼만 작아 어긋나 보였다). */
.ch-mgr { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 14px;
  padding: 8px 10px 8px 14px; border: 1px solid var(--line); border-radius: 8px; background: var(--panel-2); }
.ch-mgr .ch-transfer-to { height: 34px; padding: 0 10px; min-width: 160px; width: auto; }
.ch-mgr .ch-transfer { height: 34px; padding: 0 14px; font-size: 14px; flex: none; }
.ch-mgr:empty { display: none; }
.ch-mgr-label { font-weight: 700; }
.ch-mgr-names { color: var(--text); margin-right: auto; }
.ch-transfer-to { min-width: 140px; }
.ch-mgr-x { width: 20px; height: 20px; min-width: 20px; margin-left: 4px; font-size: 14px; line-height: 1; padding: 0; vertical-align: middle; }


/* 설정 카드 (2026-10-21 소유자: 한 줄씩이라 빈 공간이 많았다). 카드 2열, 항목은 "이름 … 값" 한 줄,
   카드마다 저장 하나 (admin_settings.html). */
.set-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 16px; margin-top: 16px; align-items: start; }
.set-card { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 16px; }
.set-card.set-wide { grid-column: 1 / -1; }
.set-card h2 { font-size: 14px; margin: 0 0 8px; }
.set-form { margin: 0; }
.set-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 40px; font-size: 14px; }
.set-row + .set-row, .set-form + .set-form .set-row:first-of-type { border-top: 1px solid var(--panel-2); }
.set-check { justify-content: flex-start; cursor: pointer; }
.set-val { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
/* 행을 꽉 채우지 않게 입력칸을 낮게 (2026-10-21 소유자) — 행 높이 40px 안에 위아래 여백이 남는다. */
.set-val input[type=number], .set-val select { height: 28px; padding: 2px 8px; font-size: 14px; line-height: 1; }
.set-val input[type=number] { width: 80px; }
.set-val select { width: auto; padding-right: 4px; }
.set-note { margin: 4px 0 0; word-break: break-all; }
.set-foot { display: flex; align-items: center; justify-content: flex-end; gap: 12px; margin-top: 12px; }
.set-status { font-size: 12px; color: var(--ok); }
.set-status.err { color: var(--err); }
@media (max-width: 480px) { .set-grid { grid-template-columns: 1fr; } }

/* --- 원격 삭제 창 · 삭제됨 보관함 (2026-10-21 소유자) ------------------------------
   무엇이 지워지는지 두 갈래를 나란히 보인다. 에이전트 제거는 확인 문구 칸이 따라 나온다. */
#host-remove-modal .modal { width: min(520px, 92vw); }
.hr-target { margin: -6px 0 12px; font-weight: 600; word-break: break-all; }
.hr-opt {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 12px; margin-bottom: 8px;
  border: 1px solid var(--line); border-radius: 10px; cursor: pointer;
}
.hr-opt:has(input:checked) { border-color: var(--err); background: rgba(214, 69, 69, .05); }
.hr-opt[hidden] { display: none; }
.hr-opt input { margin-top: 4px; flex: none; }
.hr-opt > span { display: flex; flex-direction: column; gap: 2px; }
.hr-desc { font-size: 14px; color: var(--muted); }
.hr-warn { color: var(--err); }
.hr-phrase { display: flex; flex-direction: column; gap: 6px; margin: 4px 0 0; font-size: 14px; }
.hr-phrase[hidden] { display: none; }
.hr-phrase input { width: 220px; max-width: 100%; }

.chansw-deleted { color: var(--muted); }
.del-bar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 12px 0; }
.del-actions { margin-left: auto; display: flex; gap: 8px; }
.del-table .del-pick-col { width: 36px; }
.del-st.is-pending { color: var(--warn); font-weight: 600; }
.del-st.is-removed { color: var(--muted); }
.del-st.is-blocked { color: var(--err); font-weight: 600; }
.del-online { font-size: 12px; color: var(--ok); }
@media (max-width: 700px) {
  .del-table { display: block; overflow-x: auto; }
}

/* 데스크탑 앱 "새 버전 있음" (layout, 2026-10-21) */
.app-new { display: inline-flex; align-items: center; gap: 6px; font-size: 14px; }
.app-new-label { color: var(--warn, #b45309); font-weight: 600; }
.app-new-link { background: none; border: 0; padding: 0; font: inherit; color: var(--accent, #2563eb); text-decoration: underline; cursor: pointer; }
