/* =====================================================================
   관리자 콘솔 스타일 — 디자인 토큰 기반
   ---------------------------------------------------------------------
   색은 **여기서만** 정의한다. 이 파일 아래쪽 화면별 규칙은 역할 이름
   (--panel·--muted·--danger-fg …)만 참조한다. 종전에는 하드코딩 hex 가
   211곳(고유 66개) 흩어져 있어 톤을 한 번에 바꿀 수단이 없었다.

   다크 대응은 3단이다. 이 순서와 셀렉터를 바꾸지 말 것:
     1) :root                                        — 라이트 값을 **전량** 정의
     2) @media (prefers-color-scheme: dark)
          :root:not([data-theme="light"])            — 시스템 다크
     3) :root[data-theme="dark"]                     — 토글이 시스템을 이긴다
   ★ 색을 미디어 쿼리 **안에서만** 정의하면 시스템 라이트 사용자에게 그 색이
     아예 없다. 새 색은 반드시 :root 에 먼저 적고 2)·3) 에서 덮어쓴다.
   (같은 패턴이 public/legal/legal.css 에 이미 있다 — 그쪽이 먼저다.)

   테마 값은 서버가 <html data-theme="…"> 로 렌더한다(세션·계정에 저장).
   클라이언트가 부팅 후 붙이는 방식이 아니라 첫 화면 깜빡임이 없다.

   반응형 breakpoint 는 이 3단만 쓴다 — 화면마다 제각각 잡으면
   같은 폭에서 두 규칙이 반쯤 적용되는 어중간한 레이아웃이 생긴다.
     1024px 이하 — 2단(좌 목록 / 우 편집) 레이아웃을 1단으로
      768px 이하 — 모바일. 상단 그룹 탭을 메뉴 버튼 + 시트로 바꾼다
      480px 이하 — 소형 보정(버튼·칩 줄바꿈)
   ===================================================================== */
:root {
  /* --- 면 --- */
  --bg: #f7f8fa;
  --surface: #ffffff;
  --surface-2: #f2f4f7;
  --surface-3: #eaecf0;
  --border: #e4e7ec;
  --border-strong: #d0d5dd;

  /* --- 글자 --- */
  --text: #101828;
  --text-muted: #667085;
  /* ★ --text-subtle 은 **글자용이 아니다.** 아이콘·셰브론·자리표시처럼 읽지 않아도
     되는 것에만 쓴다. 흰 배경에서 명암비 2.58:1 이라(WCAG AA 는 4.5:1) 글자에 쓰면
     읽히지 않는다 — 실제로 .hint 가 콘솔 전역 167곳에서 그 상태였고, 설정이 무엇을
     하는지 설명하는 바로 그 문장이 가장 안 읽혔다. 읽는 글자는 --text-muted(4.97:1). */
  --text-subtle: #98a2b3;

  /* --- 액센트(indigo 한 계열만) ---
     --on-accent 는 **라이트·다크 모두 흰색**이다. 다크 액센트를 흰 글자가
     읽히는 명도로 고른 결과이며, 덕분에 아래쪽 화면 규칙의 `color:#fff`
     (버튼·배지 글자)이 두 테마에서 그대로 성립한다. */
  --accent: #4f46e5;
  --accent-hover: #4338ca;
  --accent-soft: #eef2ff;
  --accent-border: #c7d2fe;
  --on-accent: #ffffff;

  /* --- 상태 4종: 전경 / 배경 / 경계 한 벌 --- */
  --success-fg: #067647; --success-bg: #ecfdf3; --success-border: #abefc6;
  --warn-fg:    #b54708; --warn-bg:    #fffaeb; --warn-border:    #fedf89;
  --danger-fg:  #b42318; --danger-bg:  #fef3f2; --danger-border:  #fecdca;
  --info-fg:    #026aa2; --info-bg:    #f0f9ff; --info-border:    #b9e6fe;
  /* 개발자 전용 표식(권한이 아니라 '누가 보는 화면인가'를 뜻한다) */
  --dev-fg: #6941c6; --dev-bg: #f4f0ff; --dev-border: #e0d6fb;

  /* --- 코드·JSON 블록 --- */
  --code-bg: #f2f4f7;
  --code-fg: #101828;

  /* --surface-2 위에 얹히는 요소(세그먼트의 활성 알약)의 색.
     ★ 여기에 --surface 를 쓰면 안 된다 — 다크에서 --surface 가 트랙(--surface-2)보다
       어두워 '떠 있는' 관계가 뒤집히고, 그림자도 검정이라 활성 표시가 사라진다. */
  --raised: #ffffff;

  /* --- 채널 브랜드색 ---
     ★ 이것은 테마 토큰이 아니다. 각 플랫폼의 아이덴티티라 다크에서도
       바뀌지 않는다. 한곳에 모아 두기만 하고 위 역할색과 섞지 말 것. */
  --brand-kakao: #fee500;    --brand-kakao-fg: #191600;
  --brand-meta: #1877f2;     --brand-meta-fg: #ffffff;
  --brand-wechat: #07c160;   --brand-wechat-fg: #ffffff;
  --brand-naver: #03c75a;    --brand-naver-fg: #ffffff;
  --brand-instagram: #fce4ef; --brand-instagram-fg: #c1358b;

  /* --- 그림자 --- */
  --sh-sm: 0 1px 2px rgba(16, 24, 40, 0.06);
  --sh-md: 0 4px 12px rgba(16, 24, 40, 0.08);
  --sh-lg: 0 12px 32px rgba(16, 24, 40, 0.14);
  --scrim: rgba(16, 24, 40, 0.45);

  /* --- 모양 --- */
  --r-sm: 8px; --r-md: 12px; --r-lg: 16px; --r-pill: 999px;

  /* --- 치수 ---
     --header-h(상단바) 와 --subnav-h(2차 탭) 는 화면 높이에 고정되는
     본문(.content-fixed)이 빼야 하는 값이다. 하나만 바꾸면 어긋난다. */
  --ctrl-h: 36px;
  /* 표·반복 행 안의 작은 입력. ★ 이 토큰이 **정의돼 있지 않았다** — 그래서
     `height: var(--ctrl-h-sm)` 이 계산 시점에 무효가 되어 조용히 버려졌고, 반복 행의
     칸들이 저마다 내용 크기로 섰다(실측: 셀렉트 18px · 입력 19px — 1px 어긋난 채로
     나란히 놓였고, 둘 다 손가락으로 누르기엔 너무 작았다). */
  --ctrl-h-sm: 30px;
  --header-h: 56px;
  --subnav-h: 44px;
  /* 지금 **실제로 보이는** 화면 높이. 모바일 브라우저는 주소 표시줄이 보이는
     동안에도 주소줄이 숨겨진 상태의 높이를 레이아웃 기준으로 잡아, 화면 높이에
     고정된 본문과 `position: fixed` 시트의 아래가 화면 밖으로 밀린다.
     console.js 가 visualViewport 로 재서 px 로 덮어쓴다 — 아래 100dvh 는
     그 스크립트가 없는(또는 아직 안 돈) 순간을 위한 기본값이다. */
  --vh: 100dvh;
  --content-pad-x: 32px;
  --content-pad-y: 24px;
  /* 본문 최대 폭. 넓은 화면에서 폼이 끝까지 늘어나면 라벨과 입력칸이 멀어져
     시선이 가로로 튀고, 오른쪽에 쓸모없는 공백만 남는다(1440px 에서 실측). */
  --content-max: 1080px;
  /* 목록·상담처럼 가로가 넓어야 편한 화면의 상한. 두 값 사이를 오갈 뿐
     제3의 너비를 만들지 않는다 — 화면마다 폭이 다르면 옮겨 다닐 때마다
     눈이 기준선을 다시 찾는다. */
  --content-max-wide: 1600px;

  /* --- 구 토큰 별칭 ---
     이 파일 아래쪽 1,200줄이 --panel·--muted 를 참조한다. 이름을 바꾸면
     그 전부를 동시에 고쳐야 하므로 별칭으로 남긴다. 새 규칙은 오른쪽
     이름(--surface·--text-muted)을 쓴다. */
  --panel: var(--surface);
  --muted: var(--text-muted);

  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0e1015;
    --surface: #171a21;
    --surface-2: #1e222b;
    --surface-3: #262b36;
    --border: #2a2f3a;
    --border-strong: #3a414f;

    --text: #e7eaf0;
    --text-muted: #9ba4b5;
    --text-subtle: #6e778a;

    --accent: #6366f1;
    --accent-hover: #7c7bf5;
    --accent-soft: #1e1b33;
    --accent-border: #3a357a;

    --success-fg: #6ce9a6; --success-bg: #0c2b1e; --success-border: #1f5140;
    --warn-fg:    #fec84b; --warn-bg:    #2b1f0c; --warn-border:    #56421a;
    --danger-fg:  #fda29b; --danger-bg:  #2b1210; --danger-border:  #5a2723;
    --info-fg:    #7cd4fd; --info-bg:    #0b2233; --info-border:    #17456b;
    --dev-fg:     #cbb6fb; --dev-bg:     #1e1730; --dev-border:     #3b2d60;

    --code-bg: #0b0d12;
    --code-fg: #d7dce7;
    --raised: #343a47;

    --sh-sm: 0 1px 2px rgba(0, 0, 0, 0.40);
    --sh-md: 0 4px 14px rgba(0, 0, 0, 0.50);
    --sh-lg: 0 14px 36px rgba(0, 0, 0, 0.60);
    --scrim: rgba(0, 0, 0, 0.62);

    color-scheme: dark;
  }
}

:root[data-theme="dark"] {
  --bg: #0e1015;
  --surface: #171a21;
  --surface-2: #1e222b;
  --surface-3: #262b36;
  --border: #2a2f3a;
  --border-strong: #3a414f;

  --text: #e7eaf0;
  --text-muted: #9ba4b5;
  --text-subtle: #6e778a;

  --accent: #6366f1;
  --accent-hover: #7c7bf5;
  --accent-soft: #1e1b33;
  --accent-border: #3a357a;

  --success-fg: #6ce9a6; --success-bg: #0c2b1e; --success-border: #1f5140;
  --warn-fg:    #fec84b; --warn-bg:    #2b1f0c; --warn-border:    #56421a;
  --danger-fg:  #fda29b; --danger-bg:  #2b1210; --danger-border:  #5a2723;
  --info-fg:    #7cd4fd; --info-bg:    #0b2233; --info-border:    #17456b;
  --dev-fg:     #cbb6fb; --dev-bg:     #1e1730; --dev-border:     #3b2d60;

  --code-bg: #0b0d12;
  --code-fg: #d7dce7;
  --raised: #343a47;

  --sh-sm: 0 1px 2px rgba(0, 0, 0, 0.40);
  --sh-md: 0 4px 14px rgba(0, 0, 0, 0.50);
  --sh-lg: 0 14px 36px rgba(0, 0, 0, 0.60);
  --scrim: rgba(0, 0, 0, 0.62);

  color-scheme: dark;
}

/* ---------- 기본 ---------- */
* { box-sizing: border-box; }
/* ★ 세로 스크롤바 자리를 늘 비워 둔다. 내용이 짧아 스크롤바가 없는 화면과 있는
   화면 사이를 오가면 본문이 8px 씩 좌우로 튄다 — 화면마다 너비가 다른 것처럼
   보이는 원인이었다(실측: 가운데 정렬 x 좌표가 180 ↔ 173 으로 흔들렸다). */
html { scrollbar-gutter: stable; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif;
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;

  /* ── 한국어 줄바꿈 정책 ──────────────────────────────────────────────
     브라우저 기본값은 한글을 **글자 단위로** 끊는다. 그래서 좁은 칸에서
     "예약 가능 시간" 이 "예약 가능 시" / "간" 처럼 갈라져 읽는 속도가 떨어지고,
     한 줄이던 라벨이 두 줄이 되면서 옆 칸까지 밀어 내린다.
       word-break: keep-all  → 어절(띄어쓰기) 단위로만 끊는다.
       overflow-wrap: break-word → 단, 어절 하나가 칸보다 길면 그때만 쪼갠다
                                   (이게 없으면 긴 낱말이 칸을 넘겨 밀어낸다).
     식별자(경로·토큰·URL)는 어절이 없어 이 규칙으로는 못 접는다 —
     code·.mono 에 overflow-wrap:anywhere 를 따로 준다(아래). */
  word-break: keep-all;
  overflow-wrap: break-word;
}

/* 제목은 마지막 줄에 한 낱말만 남는 모양(고아 줄)을 피한다. 미지원 브라우저는
   그냥 무시하므로 안전하다. */
h1, h2, h3, .page-head h1, .card h2, .sub-title { text-wrap: pretty; }

/* ★ 제목 크기는 **요소 자체**에 준다. 종전에는 `.page-head h1` 에만 걸어 두어서,
   래퍼를 안 쓴 화면(계정 관리는 page-head-row 만 썼다)은 h1 이 브라우저 기본값
   32px 로 떨어져 혼자 큼직했다. 래퍼에 묶어 두면 새 화면이 생길 때마다 되풀이된다. */
h1 { margin: 0 0 5px; font-size: 21px; font-weight: 680; letter-spacing: -0.015em; }
h2 { margin: 0 0 10px; font-size: 15px; font-weight: 640; letter-spacing: -0.01em; }
h3 { margin: 0 0 8px; font-size: 13.5px; font-weight: 620; }
body.centered { display: flex; align-items: center; justify-content: center; min-height: 100vh; min-height: 100dvh; }

/* ---------- 로그인·비밀번호 화면 ----------
   제품의 첫 화면이다. 상단바도 네비도 없어 배경이 곧 인상이라, 흰 화면에 상자 하나
   놓아 두면 '아직 안 만든 화면'처럼 읽힌다. 액센트를 아주 옅게 두 번 얹어 바탕에
   깊이만 준다 — 이미지가 아니라 그라디언트라 로딩도 다크 대응도 따로 없다. */
.auth-page {
  display: flex; align-items: center; justify-content: center;
  min-height: 100vh; min-height: 100dvh;
  padding: 24px 16px;
  background:
    radial-gradient(60rem 30rem at 15% -10%, var(--accent-soft), transparent 60%),
    radial-gradient(45rem 28rem at 100% 110%, var(--accent-soft), transparent 55%),
    var(--bg);
}
.auth-shell { width: 100%; max-width: 400px; display: flex; flex-direction: column; gap: 14px; }
.auth-brand { display: flex; align-items: center; gap: 10px; margin-bottom: 18px; }
.auth-brand-name { font-size: 14px; font-weight: 650; letter-spacing: -0.01em; color: var(--text-muted); }
.auth-sub { margin: 6px 0 20px; font-size: 13px; color: var(--text-muted); line-height: 1.6; }
.auth-submit { width: 100%; justify-content: center; margin-top: 4px; }
/* 제품 이름을 카드 밖 아래에 한 번 더 둔다 — 어느 서비스에 로그인하는지가 카드
   안에만 있으면, 화면을 캡처해 공유했을 때 그 정보가 잘려 나간다. */
/* ⚠ --text-subtle 은 글자용이 아니다(그 토큰 주석 참조) — 여기 쓰니 2.42:1 이 나왔다. */
.auth-foot { margin: 0; text-align: center; font-size: 12px; color: var(--text-muted); }

/* 비밀번호 보기/숨기기. 버튼은 auth.js 가 만든다(없으면 이 자리는 그냥 빈 여백이다).
   ★ 버튼은 입력칸 **안쪽**에 있어야 한다. 종전에는 칸 오른쪽 테두리를 넘어 붙어 있어
     별개의 상자처럼 보였다 — 입력의 높이·반경 안에 들어와야 '이 칸의 버튼'으로 읽힌다. */
.pw-wrap { position: relative; display: block; }
.pw-wrap > input { width: 100%; padding-right: var(--pw-eye-pad, 44px); }
.pw-eye {
  position: absolute; right: 5px; top: 50%; transform: translateY(-50%);
  width: 30px; height: 30px; padding: 0; border: 0; border-radius: 7px;
  background: transparent; color: var(--text-muted); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.pw-eye .icon { width: 17px; height: 17px; }
.pw-eye:hover { background: var(--surface-2); color: var(--text); }
.pw-eye.is-on { color: var(--accent); }
.pw-eye:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
@media (max-width: 768px) {
  /* 손가락 과녁은 키우되 입력칸(--ctrl-h) 안에 머문다 — 밖으로 나가면 다시 별개 상자다. */
  .pw-wrap { --pw-eye-pad: 46px; }
  /* 입력칸 높이(36px)보다 작아야 위아래 테두리에 닿지 않는다 — 닿으면 hover 배경이
     칸을 가득 채워 다시 별개 상자처럼 보인다. */
  .pw-eye { width: 32px; height: 32px; }
  .pw-eye .icon { width: 19px; height: 19px; }
}

/* 아이콘. 전부 stroke="currentColor" 라 글자색을 따라간다 —
   테마별 아이콘 에셋을 따로 두지 않는 이유다. */
.icon {
  width: 20px; height: 20px; flex: 0 0 auto;
  fill: none; stroke: currentColor;
  stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round;
}
.icon.sm { width: 16px; height: 16px; }

/* ---------- 겹침 순서(z-index) ----------
   한 곳에 적어 둔다. 값이 흩어져 있어서 상단바를 새로 만들 때 모달이 그 뒤로
   들어갔다(실측: 모달 50 < 상단바 60 이라 툴 편집 모달의 제목이 가려졌다).
     2   화면 안 sticky(표 머리글·저장 바)
     20  인박스 알림 드롭다운
     55  2차 탭
     60  상단바
     75  계정 메뉴 팝오버
     78  모바일 시트 배경 / 79 시트
     90  모달 — **셸 크롬 전부보다 위**
     200 처리 중 오버레이 — 항상 맨 위
   ---------------------------------------------------------------- */

/* ---------- 셸 ----------
   세로 스택 하나다: 상단바 / 2차 탭 / 본문.
   종전 좌측 사이드바(.sidebar·.menu-item·오프캔버스 드로어)는 제거됐다. */
.shell { display: flex; flex-direction: column; min-height: 100vh; min-height: var(--vh); }

/* --- 상단바 --- */
.appbar {
  position: sticky; top: 0; z-index: 60;
  flex: 0 0 auto; height: var(--header-h);
  display: flex; align-items: center; gap: 18px;
  padding: 0 16px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.brand { display: flex; align-items: center; gap: 9px; flex: 0 0 auto; text-decoration: none; color: var(--text); }
.brand-mark {
  width: 28px; height: 28px; border-radius: 9px; flex: 0 0 auto;
  background: var(--accent); color: var(--on-accent);
  display: flex; align-items: center; justify-content: center;
}
.brand-name { font-size: 14px; font-weight: 650; letter-spacing: -0.01em; white-space: nowrap; }

/* 1차 그룹 탭. 6개로 묶어 두어 어떤 폭에서도 한 줄에 들어간다
   (종전 플랫 16개는 상단에 올릴 수 없었다). */
/* ★ **줄어들 수 있으면 넘칠 수도 있다.** `min-width: 0` 이 붙어 있어 탭 줄은 내용보다
   좁아지는데, 넘친 것을 다룰 방법이 없어 **탭이 상자 밖으로 흘러나와 오른쪽 묶음(조직
   선택기·알림·계정) 위에 겹쳐 그려졌다.** 실측(795px): 탭 줄 상자는 179~591 인데 탭은
   754 까지 뻗고, 오른쪽 묶음은 609 에서 시작한다 — 145px 이 서로 포개졌다.
   768px 아래로는 햄버거 메뉴로 바뀌므로, 겹치는 구간은 **768~945px** 이다(필요 폭
   합계 약 945px). 좁은 창·분할 화면이 정확히 이 구간이다.
   아래 2차 탭 줄(.subnav)이 같은 문제를 이미 가로 스크롤로 풀어 두었다 — 같은 방식으로
   맞춘다. 스크롤바는 감춘다(36px 줄에 막대가 생기면 줄 높이가 흔들린다). */
.gtabs {
  display: flex; align-items: center; gap: 2px; flex: 1 1 auto; min-width: 0;
  overflow-x: auto; scrollbar-width: none;
}
.gtabs::-webkit-scrollbar { display: none; }
.gtab {
  display: flex; align-items: center; gap: 7px;
  height: 36px; padding: 0 13px; border-radius: var(--r-sm);
  color: var(--text-muted); text-decoration: none;
  font-size: 13.5px; font-weight: 550; white-space: nowrap;
}
.gtab:hover { background: var(--surface-2); color: var(--text); }
.gtab.is-active { background: var(--accent-soft); color: var(--accent); }

.appbar-right { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; margin-left: auto; }

/* 아이콘 버튼(테마·메뉴·닫기). 모바일에서는 아래 쿼리가 40px 로 키운다. */
.iconbtn {
  width: 32px; height: 32px; flex: 0 0 auto;
  border: 0; border-radius: var(--r-sm); background: transparent;
  color: var(--text-muted); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.iconbtn:hover { background: var(--surface-2); color: var(--text); }

/* ---------- 아이콘 단추의 이름표 ----------
   글자를 뗀 단추는 **무엇인지 물어볼 데가 없다.** 브라우저 기본 `title` 은 1초쯤 뒤에,
   커서 옆에, OS 모양으로 뜬다 — 우리 화면 어디에도 그렇게 생긴 것이 없고 터치에서는
   아예 안 뜬다. 같은 이름을 우리 모양으로, 단추 **바로 위에** 띄운다.

   ★ 이름은 `aria-label` 에도 **그대로** 있어야 한다. 이 말풍선은 `::after` 로 그린
     장식이라 읽어 주는 쪽에는 존재하지 않는다 — 여기만 적으면 그 사용자는 단추가
     무엇인지 영영 모른다(검사로 묶어 둔다).
   ★ `:focus-visible` 에도 띄운다. 키보드로 훑는 사람에게도 이름이 필요하다. */
.tipbtn { position: relative; }
.tipbtn::after {
  content: attr(data-tip);
  position: absolute; bottom: calc(100% + 7px); left: 50%; transform: translateX(-50%);
  padding: 5px 9px; border-radius: 7px;
  /* 바탕은 본문 글자색, 글자는 판 색 — 라이트·다크 어느 쪽에서도 저절로 뒤집힌다. */
  background: var(--text); color: var(--panel);
  font-size: 12px; font-weight: 500; line-height: 1.3; white-space: nowrap;
  box-shadow: var(--sh-sm);
  opacity: 0; visibility: hidden; transition: opacity 0.12s ease, visibility 0.12s;
  pointer-events: none; z-index: 40;
}
.tipbtn:hover::after, .tipbtn:focus-visible::after { opacity: 1; visibility: visible; }
/* 손가락으로 누르는 화면에는 hover 가 없다 — 떴다가 손을 떼도 남는 유령을 만들지 않는다. */
@media (hover: none) { .tipbtn::after { content: none; } }

/* System Developer 조직 셀렉터 */
.org-switch { flex: 0 0 auto; }
.org-switch-select {
  height: 32px; max-width: 180px; padding: 0 8px;
  border: 1px solid var(--border); border-radius: var(--r-pill);
  background: var(--surface); color: var(--text);
  font-family: inherit; font-size: 12.5px; font-weight: 550; cursor: pointer;
}
.org-switch-select:hover { border-color: var(--border-strong); }

/* --- 계정 메뉴(우상단 드롭다운) ---
   계정 정보·언어·테마·로그아웃을 여기에 모은다. 탭에서 빼야 1차 탭이
   6개로 유지된다. */
.accountmenu { position: relative; flex: 0 0 auto; }
.avatar {
  width: 32px; height: 32px; border-radius: var(--r-pill); flex: 0 0 auto;
  border: 0; padding: 0; cursor: pointer;
  background: var(--accent); color: var(--on-accent);
  display: flex; align-items: center; justify-content: center;
  font-family: inherit; font-size: 13px; font-weight: 650;
}
.accountmenu-pop {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 75;
  min-width: 248px; padding: 6px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md); box-shadow: var(--sh-lg);
}
.accountmenu-pop[hidden] { display: none; }
.accountmenu-head { padding: 10px 10px 12px; border-bottom: 1px solid var(--border); margin-bottom: 6px; }
.accountmenu-name { font-size: 14px; font-weight: 640; }
.accountmenu-sub { font-size: 12.5px; color: var(--text-muted); margin-top: 2px; }
.accountmenu-item {
  display: flex; align-items: center; gap: 10px;
  min-height: 38px; padding: 0 10px; border-radius: var(--r-sm);
  color: var(--text); text-decoration: none; font-size: 13.5px;
  border: 0; background: transparent; width: 100%; cursor: pointer;
  font-family: inherit; text-align: left;
}
.accountmenu-item:hover { background: var(--surface-2); }
.accountmenu-item .icon { color: var(--text-subtle); }
.accountmenu-label { padding: 10px 10px 4px; font-size: 11.5px; font-weight: 680; color: var(--text-muted); }
.accountmenu-sep { height: 1px; background: var(--border); margin: 6px 0; }

/* 테마 3단 선택(시스템 / 라이트 / 다크) */
.themeseg { display: flex; gap: 2px; padding: 3px; margin: 0 6px 4px; background: var(--surface-2); border-radius: var(--r-sm); }
.themeseg button {
  flex: 1 1 0; min-height: 32px; border: 0; border-radius: 6px;
  background: transparent; color: var(--text-muted); cursor: pointer;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  font-family: inherit; font-size: 12.5px; font-weight: 550;
}
.themeseg button:hover { color: var(--text); }
.themeseg button.is-active { background: var(--raised); color: var(--text); box-shadow: var(--sh-sm); font-weight: 640; }

/* --- 2차 탭 ---
   활성 그룹에 화면이 둘 이상일 때만 렌더된다(레이아웃이 판단). */
.subnav {
  position: sticky; top: var(--header-h); z-index: 55;
  flex: 0 0 auto; height: var(--subnav-h);
  display: flex; align-items: center; gap: 2px; padding: 0 16px;
  background: var(--surface); border-bottom: 1px solid var(--border);
  overflow-x: auto; scrollbar-width: none;
}
.subnav::-webkit-scrollbar { display: none; }
/* 화면 전체에 하나뿐인 액션(지식베이스의 일괄 등록·최신화). 탭 오른쪽 끝에 붙는다 —
   탭과 붙여 두면 탭처럼 눌러 보게 되므로 auto 여백으로 확실히 떼어 놓는다.

   ★ 폭이 모자라면 탭 줄은 **가로로 스크롤한다**(줄을 늘리면 본문이 그만큼 짧아진다).
     그래서 액션은 스크롤러 안에서 sticky 로 오른쪽에 못박는다 — 안 그러면 끝까지
     밀어야 보인다(실측 390px: 탭 줄 오른쪽 끝에서 219px 바깥). */
.subnav-actions {
  /* ★ 세 값이 한 벌이다 — 스크롤러의 **오른쪽 안쪽 여백을 덮기 위한** 것이다.
       `.subnav` 는 `padding: 0 16px` 인데 sticky 의 기준선은 패딩 **안쪽**이다.
       그래서 `right: 0` 이면 오른쪽 16px 이 창처럼 뚫린 채 남고, 탭이 그 틈으로
       지나가 버튼 바깥에 잘린 글자가 뜬다 — 못박은 것이 아니라 **겹쳐 고장 난
       것**으로 읽힌다(실측 375px 시스템 탭 5개: '단가 관리'·'대화 기록 관리'가
       359~375 구간에 보였다).
         right: -16px    기준선을 패딩만큼 바깥으로 내 배경이 가장자리까지 닿게 한다.
         padding-right   그만큼을 되돌린다 — 버튼이 선 자리는 그대로다(오른쪽 359px).
         margin-right    늘어난 폭을 흐름에서 뺀다. 없으면 스크롤 폭이 16px 늘어
                         **끝까지 밀었을 때만** 버튼이 16px 안쪽으로 튄다(실측 343px).
     ⚠ `margin-right` 만으로는 안 된다. 브라우저는 sticky 기준선을 테두리 상자로
       잡아 음수 여백을 무시했다(실측: 상자 오른쪽이 359px 그대로, 틈도 그대로). */
  position: sticky; right: -16px; margin-left: auto; margin-right: -16px;
  display: flex; align-items: center; gap: 10px; flex: 0 0 auto;
  padding-left: 16px; padding-right: 16px; background: var(--surface);
}
/* 못박은 액션 아래로 탭이 지나간다. 단색으로 가리면 글자가 **중간에서 뚝 잘려**
   고장 난 화면으로 읽힌다(실측: "검색 검증"이 버튼 왼쪽 끝에서 반토막).
   24px 그라디언트로 흘려 보내면 잘린 게 아니라 **더 있다**로 읽힌다. */
.subnav-actions::before {
  content: ""; position: absolute; right: 100%; top: 0; bottom: 0; width: 24px;
  background: linear-gradient(to right, transparent, var(--surface));
  pointer-events: none;
}
/* 탭 줄 안에 들어가는 버튼은 **탭보다 작아야** 한다 — 같은 크기면 둘 다 탭처럼 보이고,
   크면 탭 줄이 버튼 줄이 된다. 높이·글자·굵기를 한 단계씩 내린다.
   ⚠ min-height 를 함께 눌러야 한다. 좁은 화면의 손가락 과녁 규칙(.btn-* 44px)이
     이 자리까지 와서 버튼이 탭 줄 높이를 꽉 채웠다(실측 390px: 44px · 둘이 바의 56%). */
.subnav-actions .btn-primary, .subnav-actions .btn-secondary {
  height: 28px; min-height: 28px; padding: 0 10px;
  font-size: 12px; font-weight: 560;
}
/* ★ **접는 기준은 768px 이 아니라 이 줄이 실제로 필요로 하는 폭이다.**
   실측(지식 화면 2026-09-15): 액션 묶음 603px + 탭 278px + 좌우 여백 32px = **913px**.
   그래서 768~913px 구간에서는 접히지도 않은 채 탭이 밀려, 마지막 탭('가져오기 설정')이
   **아예 화면 밖**에 있었다(795px 실측: 탭에 남은 자리 177px · 필요 278px). 좁은 화면부터
   보는 습관으로는 못 잡는 갈래다 — 모바일에서는 이미 접혀 멀쩡했다.
   접고 나면 같은 795px 에서 액션이 약 280px 로 줄어 탭이 온전히 선다.
   1024 로 잡는 이유: 913 에 여유를 둔다. '마지막 업데이트' 글자는 언어·시간대에 따라
   길어지는데(한국어+Asia/Seoul 이 308px), 그때마다 기준이 흔들리면 안 된다. */
@media (max-width: 1024px) {
  /* 좁은 화면에서는 탭 줄이 이미 가로로 스크롤한다. 시각·배지까지 얹으면 탭이 밀려
     정작 화면을 옮기는 수단이 안 보인다 — 버튼만 남긴다. */
  /* 못박는 것은 위에서 이미 한다. 여기서는 좁은 화면 몫으로 간격만 줄인다. */
  .subnav-actions { gap: 5px; padding-left: 8px; }
  .subnav-actions .btn-primary, .subnav-actions .btn-secondary {
    height: 26px; min-height: 26px; padding: 0 8px; font-size: 11.5px;
  }
  /* 마지막 동기화 시각은 좁은 화면에서 접는다(읽을거리이지 조작이 아니다).
     ★ 검토 버튼은 **접지 않는다** — 처리할 일이 있다는 유일한 표시이고, 여기서
       숨기면 모바일에서는 밀린 검토를 영영 못 본다(0 건이면 애초에 없다). */
  .subnav-actions .kb-last-sync { display: none; }
}
.stab {
  position: relative; flex: 0 0 auto;
  height: var(--subnav-h); display: flex; align-items: center; gap: 6px;
  padding: 0 11px; color: var(--text-muted); text-decoration: none;
  font-size: 13px; font-weight: 520; white-space: nowrap;
}
.stab:hover { color: var(--text); }
.stab.is-active { color: var(--accent); font-weight: 620; }
.stab.is-active::after {
  content: ""; position: absolute; left: 11px; right: 11px; bottom: 0;
  height: 2px; border-radius: 2px 2px 0 0; background: var(--accent);
}

/* --- 모바일 메뉴 시트 ---
   768px 이하에서만 열린다. 시트 **안에서만** 스크롤하고 배경은 잠근다.

   ★ [hidden] 을 반드시 존중해야 한다. 아래 미디어쿼리에서 `.navsheet{display:block}`
     처럼 쓰면 **클래스 규칙이 UA 의 [hidden]{display:none} 을 이겨서**, 열림/닫힘을
     hidden 으로 다루는 JS 가 무력해진다(시트가 뜬 채로 안 닫힌다 — 실제로 그랬다).
     이 저장소가 .field[hidden]·.keyguard-notice[hidden] 에서 이미 겪은 함정이다. */
.navsheet { display: none; }
.navsheet[hidden] { display: none; }
.navsheet-scrim { position: fixed; inset: 0; height: var(--vh); z-index: 78; background: var(--scrim); }
.navsheet-panel {
  position: fixed; left: 0; right: 0; z-index: 79;
  bottom: 0; bottom: calc(100lvh - var(--vh));
  display: flex; flex-direction: column;
  max-height: calc(var(--vh) * 0.88);
  background: var(--surface);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  box-shadow: var(--sh-lg);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
.navsheet-head { flex: 0 0 auto; padding: 8px 12px 10px; border-bottom: 1px solid var(--border); }
.navsheet-grip { width: 38px; height: 4px; border-radius: 2px; background: var(--surface-3); margin: 0 auto 10px; }
.navsheet-user { display: flex; align-items: center; gap: 11px; padding: 2px 4px; }
.navsheet-user .grow { flex: 1 1 auto; min-width: 0; }
.navsheet-user b { display: block; font-size: 14.5px; font-weight: 640; }
/* min-height:0 이 없으면 flex 자식이 내용만큼 늘어나 overflow-y 가 동작하지 않는다
   — 시트가 화면 밖으로 밀려나고 아래 항목을 영영 못 고른다. */
.navsheet-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 8px 8px 4px; }
.navsheet-foot { flex: 0 0 auto; border-top: 1px solid var(--border); padding: 10px 12px 14px; }
.navsheet-group { padding: 12px 10px 5px; font-size: 11.5px; font-weight: 680; color: var(--text-muted); }
.navsheet-item {
  display: flex; align-items: center; gap: 12px;
  min-height: 46px; padding: 0 10px; border-radius: var(--r-sm);
  color: var(--text); text-decoration: none; font-size: 14.5px; font-weight: 520;
}
.navsheet-item:hover { background: var(--surface-2); }
.navsheet-item .icon { color: var(--text-subtle); }
.navsheet-item.is-active { background: var(--accent-soft); color: var(--accent); font-weight: 620; }
.navsheet-item.is-active .icon { color: var(--accent); }
.navsheet-item .tag { margin-left: auto; }
body.nav-open { overflow: hidden; }

/* --- 본문 --- */
/* ★ 본문은 폭을 제한하고 **가운데로** 둔다. 종전에는 제한이 없어 넓은 화면에서
   내용이 왼쪽에 붙고 오른쪽이 통째로 비었다. 목록·상담처럼 가로를 다 쓰는
   화면(.content-fixed)만 제한을 푼다. */
.content {
  flex: 1 1 auto; min-width: 0; width: 100%;
  max-width: var(--content-max); margin-inline: auto;
  padding: var(--content-pad-y) var(--content-pad-x);
}
/* 가로를 다 쓰는 화면(목록·상담·2단 편집)도 **끝없이 늘어나지는 않는다.**
   1080 을 넘겨 넓게 쓰되 상한을 두고 가운데 정렬한다 — 상한이 없으면 27인치에서
   표 한 줄이 2500px 를 가로지르고, 눈이 행을 따라가지 못한다. 좁은 화면에서는
   상한에 닿지 않으므로 지금까지와 똑같이 꽉 찬다. */
.content-fixed { max-width: var(--content-max-wide); }

/* 화면 높이에 고정되는 본문(상담 콘솔·목록 화면).
   페이지가 늘어나지 않고 내부 영역이 스크롤된다.
   ★ 상단바·2차 탭이 이미 높이를 먹었으므로 그만큼 빼야 한다 —
     화면 높이를 그대로 주면 본문 아래가 화면 밖으로 밀려 잘린다.
   ★ 기준은 --vh(지금 보이는 높이)다. 100dvh 로 잡으면 모바일에서 주소
     표시줄이 보이는 동안 그만큼 아래가 화면 밖으로 나간다. */
.content-fixed {
  height: calc(var(--vh) - var(--header-h) - var(--subnav-h));
  /* 창이 낮으면 고정 높이 안에서 머리말·툴바가 잘리는데 overflow:hidden 이라
     스크롤로 도달할 방법이 없어진다. 하한을 두면 그 아래에서는 셸이 화면보다
     길어져 페이지 스크롤이 살아난다 — 탈출구다. */
  min-height: 440px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
/* 2차 탭이 없는 그룹(상담)에서는 그만큼 되돌려 준다. */
.shell.no-subnav .content-fixed { height: calc(var(--vh) - var(--header-h)); }

/* ---------- 화면 설명 말풍선 ----------
   설명은 흐름에서 빼내 말풍선으로 띄운다 — 매일 쓰는 사람에게 같은 문장이
   화면마다 두 줄씩 자리를 먹지 않도록. 자리를 차지하는 것은 [i] 하나뿐이고,
   말풍선 자체는 position:absolute 라 본문을 밀지 않는다.
   여닫은 상태는 화면마다 브라우저에 남는다(main.hbs 의 head 스크립트 + console.js). */
.page-tip-wrap { display: inline-flex; flex: 0 0 auto; }
.page-tip-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0; flex: 0 0 auto;
  border: 1px solid var(--border); border-radius: var(--r-pill);
  background: var(--panel); color: var(--text-muted); cursor: pointer;
}
.page-tip-btn:hover { background: var(--surface-2); color: var(--text); border-color: var(--border-strong); }
.page-tip-btn .icon { width: 15px; height: 15px; }
/* 열려 있는 동안에는 버튼이 말풍선의 주인임을 보인다. */
.page-tip-btn[aria-expanded="true"] { background: var(--accent-soft); color: var(--accent); border-color: var(--accent-border); }

/* ★ position: fixed 다(absolute 가 아니다). [i] 가 앉는 2차 탭 줄은 가로
   스크롤러(overflow-x:auto)라, absolute 로 두면 44px 높이 안에서 **잘린다.**
   상단바·탭 줄은 둘 다 sticky 로 화면 맨 위에 붙어 있으므로, 그 바로 아래를
   화면 기준으로 잡으면 스크롤과 무관하게 늘 제자리다. */
.page-tip {
  position: fixed; z-index: 70;
  /* 자리는 console.js 가 [i] 를 재서 정한다(화면마다 [i] 의 x 가 다르다).
     아래 값은 스크립트가 돌기 전 한 프레임의 자리다 — 화면 밖으로 나가지만 않으면 된다. */
  top: calc(var(--header-h) + var(--subnav-h) + 6px); right: 12px;
  width: max-content; max-width: min(440px, calc(100vw - 24px));
  display: flex; align-items: flex-start; gap: 8px;
  padding: 10px 8px 10px 13px;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-md); box-shadow: var(--sh-lg);
}
.page-tip p { margin: 0; font-size: 13px; line-height: 1.55; color: var(--text-muted); }
/* 꼬리. 사각형 하나를 45° 돌려 위 두 변만 테두리로 남긴다(모서리가 이어진다).
   x 는 스크립트가 [i] 중심에 맞춰 --tail-x 로 준다(기본값은 그 전 한 프레임용). */
.page-tip::after {
  content: ''; position: absolute; top: -6px;
  left: var(--tail-x, calc(100% - 24px)); margin-left: -5px; width: 10px; height: 10px;
  background: var(--panel);
  border-left: 1px solid var(--border); border-top: 1px solid var(--border);
  transform: rotate(45deg);
}
.page-tip-x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; flex: 0 0 auto; margin-top: -1px;
  border: 0; background: transparent; color: var(--text-muted);
  border-radius: var(--r-sm); cursor: pointer;
}
.page-tip-x:hover { background: var(--surface-2); color: var(--text); }
/* 닫힌 상태. head 의 인라인 스크립트가 첫 그림 전에 붙인다(번쩍임 방지). */
.tip-off .page-tip { display: none; }

/* ---------- 공통 요소 ---------- */
.page-head { margin-bottom: 4px; }
.page-head h1 { margin: 0 0 5px; font-size: 21px; font-weight: 680; letter-spacing: -0.015em; }
.muted { color: var(--muted); font-size: 13px; font-weight: 400; margin: 0; }
/* 잔글씨도 같다 — 뷰에서 `small` 은 사실상 전부 .hint·.muted 지만, 맨 `small` 도 있다. */
small { font-weight: 400; }

.card {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-md); box-shadow: var(--sh-sm);
  padding: 20px 22px; margin-top: 16px;
}
.card h2 { margin: 0 0 10px; font-size: 15px; font-weight: 640; letter-spacing: -0.01em; }

.inline-form { display: flex; gap: 8px; align-items: center; }

/* ⚠ **타입을 하나하나 적는 목록이다.** 여기 없는 타입은 브라우저 기본 크롬으로 그려진다 —
   조용히 깨지고, 그 화면만 다른 제품처럼 보인다.
   실제로 `search` 와 `time` 이 빠져 있었다(실측: 검색칸이 2px inset · radius 0 · 높이 22px).
   `input` 통짜로 걸지 않는 이유는 체크박스·라디오·파일·색상·버튼까지 함께 맞기 때문이다
   (체크박스·라디오는 바로 아래 '체크박스 · 라디오' 절이 따로 받는다).
   **새 입력 타입을 쓰면 이 줄에 추가할 것.** */
select, input[type="text"], input[type="password"], input[type="number"],
input[type="email"], input[type="date"], input[type="search"], input[type="time"] {
  height: var(--ctrl-h); padding: 0 11px;
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  background: var(--panel); color: var(--text);
  font-family: inherit; font-size: 13.5px;
  /* search·time 은 브라우저가 제 장식을 덧그린다(둥근 알약·스핀 화살표). 지운다. */
  appearance: none; -webkit-appearance: none;
}
/* 검색칸의 기본 지우기 버튼은 남긴다 — 없애면 입력을 비울 방법이 키보드뿐이다. */
input[type="search"]::-webkit-search-cancel-button { appearance: none; -webkit-appearance: none;
  width: 14px; height: 14px; cursor: pointer;
  background: var(--text-subtle);
  mask: center/contain no-repeat url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 6l12 12M18 6L6 18' stroke='%23000' stroke-width='2.5' stroke-linecap='round' fill='none'/%3E%3C/svg%3E");
  -webkit-mask: center/contain no-repeat url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 6l12 12M18 6L6 18' stroke='%23000' stroke-width='2.5' stroke-linecap='round' fill='none'/%3E%3C/svg%3E");
}
/* 자리표시는 **값이 아니라 안내**다.
   종전에는 색만 옅어서(`check_slot`·`data`·`ok` 같은 짧은 예시일수록) 이미 입력된
   값처럼 보였다 — 그러면 사용자가 "이미 채워져 있네" 하고 그대로 저장한다.
   기울임을 함께 줘서 한눈에 갈리게 한다. 값은 절대 기울어지지 않으므로 이 신호는
   내용에 의존하지 않는다(어느 언어든 성립한다). */
input::placeholder, textarea::placeholder {
  color: var(--text-subtle);
  font-style: italic;
}
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* ---------- 체크박스 · 라디오 ----------
   기본 상자는 **브라우저·OS 가 각자 그린다.** 같은 화면이 크롬에서는 13px 모난 상자,
   사파리에서는 파란 알약, 파이어폭스에서는 또 다른 모양으로 나오고, 체크 색도 우리
   액센트가 아니라 OS 강조색이다(맥 시스템 설정에서 바꾸면 따라 바뀐다). 콘솔에서
   유일하게 남아 있던 브라우저 크롬이었다.

   ★ **타입만으로 건다(0,1,1).** 이미 상자를 숨겨 다른 모양으로 바꿔 쓰는 자리가
     둘 있다 — `.switch-line > input`(0,2,1) 과 `.seg > label > input`(0,3,1).
     선택자를 조금이라도 강하게 잡으면 그 둘이 되살아나 **스위치 옆에 체크박스가
     같이 뜬다.** 아래에 오는 `.switch input`(0,1,1) 도 순서로 이긴다.
   ★ 체크 표시는 배경 이미지로 그린다. `input` 은 대체 요소라 `::after` 가
     사파리에서 그려지지 않는다 — 실제로 표시만 사라진 채 상자는 멀쩡해 보인다.
   ★ 색은 토큰이 아니라 흰색으로 박는다(--on-accent 는 라이트·다크 모두 #ffffff).
     데이터 URI 안에서는 `var()` 가 풀리지 않는다. */
input[type="checkbox"], input[type="radio"] {
  appearance: none; -webkit-appearance: none;
  flex: 0 0 auto;
  width: 16px; height: 16px; margin: 0;
  border: 1px solid var(--border-strong); border-radius: 4px;
  background-color: var(--panel);
  background-repeat: no-repeat; background-position: center; background-size: 100% 100%;
  cursor: pointer;
  /* 글 옆에 그냥 서는 자리(flex 가 아닌 곳)에서 글자 가운데에 맞춘다. */
  vertical-align: -3px;
  transition: background-color 0.12s ease, border-color 0.12s ease;
}
input[type="radio"] { border-radius: 50%; }
input[type="checkbox"]:hover:not(:disabled), input[type="radio"]:hover:not(:disabled) {
  border-color: var(--accent);
}
input[type="checkbox"]:checked, input[type="radio"]:checked,
input[type="checkbox"]:indeterminate {
  background-color: var(--accent); border-color: var(--accent);
}
input[type="checkbox"]:checked {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.4l3 3 5.8-6.4' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
/* 일부만 켜진 상태(JS 가 .indeterminate 를 세우는 자리). 지금 쓰는 곳이 없어도
   두는 이유: 켜졌을 때 **켜짐과 똑같이 보이는 것**이 가장 나쁜 실패다. */
input[type="checkbox"]:indeterminate {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 8h8' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
}
input[type="radio"]:checked {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3.2' fill='%23ffffff'/%3E%3C/svg%3E");
}
/* 잠긴 칸은 **켜짐·꺼짐을 그대로 읽을 수 있어야 한다.** 관리자 설정의 권한 표에는
   '전역에서 꺼서 잠긴 채 켜져 있는' 칸이 있다 — 색만 빼면 무엇이 잠겼는지 사라진다. */
input[type="checkbox"]:disabled, input[type="radio"]:disabled {
  opacity: 0.5; cursor: default;
}
input[type="checkbox"]:disabled:not(:checked):not(:indeterminate),
input[type="radio"]:disabled:not(:checked) { background-color: var(--surface-2); }
/* 고대비 모드에서는 우리가 칠한 색이 전부 OS 색으로 덮인다 — 상자가 통째로
   사라져 보인다. 그 모드에서만 네이티브 렌더링을 돌려준다. */
@media (forced-colors: active) {
  input[type="checkbox"], input[type="radio"] { appearance: auto; -webkit-appearance: auto; }
}

.btn-primary {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  height: var(--ctrl-h); padding: 0 15px;
  background: var(--accent); color: var(--on-accent);
  border: 1px solid transparent; border-radius: var(--r-sm);
  font-family: inherit; font-size: 13.5px; font-weight: 580;
  text-decoration: none; cursor: pointer;
}
.btn-primary:hover { background: var(--accent-hover); }
.btn-primary:disabled { opacity: 0.55; cursor: default; }

/* ---------- 로그인 ---------- */
.login-card {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: 30px 28px; width: 100%;
  box-shadow: var(--sh-lg);
}
/* 왼쪽 정렬. 가운데로 모으면 라벨·입력칸과 축이 어긋나 제목만 떠 보인다. */
.login-title { margin: 0; font-size: 21px; font-weight: 680; letter-spacing: -0.01em; }
.login-form { display: flex; flex-direction: column; gap: 14px; }
.field { display: flex; flex-direction: column; gap: 6px; font-size: 12.5px; font-weight: 560; color: var(--muted); }
/* hidden 속성은 UA 의 display:none 이지만 .field(display:flex) 가 덮어써 무시된다 —
   hidden 이면 확실히 숨기도록 되돌린다(메서드별 쿼리/본문 섹션 토글이 이에 의존). */
.field[hidden], .builder[hidden] { display: none; }
.field-check { display: flex; flex-direction: row; align-items: center; gap: 8px; font-size: 13px; color: var(--muted); cursor: pointer; }
.field-check input { margin: 0; }

/* 반복 입력 행(복제 치환 규칙 등) */
.repeat-list { display: flex; flex-direction: column; gap: 6px; }
.repeat-row { display: flex; align-items: center; gap: 6px; }
.repeat-row input { flex: 1 1 0; min-width: 0; }
.repeat-arrow { color: var(--muted); font-size: 13px; flex: 0 0 auto; }
.repeat-x {
  flex: 0 0 auto; width: 30px; height: 30px; line-height: 1;
  border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--panel);
  color: var(--muted); font-size: 16px; cursor: pointer;
}
.repeat-x:hover { background: var(--danger-bg); color: var(--danger-fg); border-color: var(--danger-border); }
.soon-badge {
  display: inline-block; background: var(--accent-soft); color: var(--accent);
  border: 1px solid var(--accent-border); border-radius: var(--r-pill);
  padding: 2px 10px; font-size: 12px; font-weight: 600; margin-bottom: 10px;
}

.policy-list {
  margin: 2px 0 6px; padding-left: 18px;
  color: var(--muted); font-size: 12px; line-height: 1.7;
}
.defer-form { margin-top: 14px; text-align: center; }
.btn-link {
  background: none; border: 0; color: var(--muted);
  font-family: inherit; font-size: 13px; cursor: pointer; text-decoration: underline;
}
.btn-link:hover { color: var(--text); }
/* 되돌리기 어려운 링크형 동작(키 삭제 등). 주 동작 옆에 서지만 색으로 갈린다. */
.btn-link.danger { color: var(--danger-fg); }
.btn-link.danger:hover { color: var(--danger-fg); text-decoration-thickness: 2px; }
/* 삭제는 왼쪽 끝에, 주 동작은 오른쪽 끝에 — 손가락이 미끄러져 잘못 누르지 않게. */
.form-actions .btn-link.danger { margin-right: auto; }

.login-error {
  display: flex; align-items: flex-start; gap: 8px;
  background: var(--danger-bg); color: var(--danger-fg); border: 1px solid var(--danger-border);
  padding: 10px 12px; border-radius: var(--r-sm); font-size: 13px; margin: 0 0 14px;
  line-height: 1.5;
}
.login-error .icon { flex: 0 0 auto; margin-top: 1px; }

/* ---------- U3: 설정 화면 공통 ---------- */
.flash-ok {
  background: var(--success-bg); color: var(--success-fg); border: 1px solid var(--success-border);
  padding: 10px 12px; border-radius: var(--r-sm); font-size: 13px; margin: 16px 0 0;
}
.flash-error {
  background: var(--danger-bg); color: var(--danger-fg); border: 1px solid var(--danger-border);
  padding: 10px 12px 10px 30px; border-radius: var(--r-sm); font-size: 13px; margin: 16px 0 0;
}
.flash-error li { margin: 2px 0; }

/* 잠금만 하고 **상자는 그리지 않는다.**
   여러 칸을 한 번에 잠그려고 `fieldset[disabled]` 를 쓰는데, 브라우저는 이 태그에
   테두리·안쪽 여백을 기본으로 그린다 — 편집 영역만 액자에 든 것처럼 보이고, 요청은
   '가져온 값은 고칠 수 없게'였지 '따로 떼어 두라'가 아니었다.
   `min-width: 0` 은 fieldset 의 또 다른 기본값(min-content)을 지운다 — 안 지우면
   좁은 화면에서 칸이 안 줄어 팝업이 가로로 밀린다. */
.plain-fieldset {
  border: 0; margin: 0; padding: 0; min-width: 0;
}

.form-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px 18px;
}
.form-grid.grow { flex: 1; }
/* 한 줄을 통째로 쓰는 칸. 날짜 두 개처럼 **한 칸에 컨트롤이 둘**인 경우, 격자 한 칸
   (최소 220px)에 욱여넣으면 날짜 입력이 제 최소 폭 아래로 눌려 글자가 잘린다. */
.field-span { grid-column: 1 / -1; }
/* 기간 입력: [시작] ~ [종료]. 둘이 한 쌍이라는 것이 보여야 한다. */
.date-range { display: flex; align-items: center; gap: 8px; }
.date-range > input { flex: 1 1 0; min-width: 0; }
.date-range-sep { flex: 0 0 auto; color: var(--text-muted); }
.form-actions { margin-top: 16px; display: flex; gap: 8px; align-items: center; }
/* 긴 폼에서 저장을 찾아 스크롤하지 않도록 하단에 붙인다. */
.form-actions.sticky {
  position: sticky; bottom: 0; z-index: 2;
  margin-top: 16px; padding: 12px 14px;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-md); box-shadow: var(--sh-md);
  /* 저장은 오른쪽 끝에 둔다 — 폼을 위에서 아래로 읽고 마지막에 누르는 자리다.
     왼쪽에 붙어 있으면 넓은 화면에서 바가 텅 빈 채로 버튼만 구석에 있다. */
  justify-content: flex-end;
}
/* 설명은 줄의 왼쪽 끝으로 민다 — 버튼 옆에 붙어 있으면 둘이 한 덩어리로 보여
   버튼이 어디서 시작하는지 흐려진다(오른쪽 정렬 안에서 특히). */
.form-actions.sticky > .hint { margin-right: auto; margin-top: 0; text-align: left; }

@media (max-width: 768px) {
  /* 좁은 화면에서는 한 줄에 다 못 들어간다 — 실측(375px): 버튼 셋이 335px 이고
     간격까지 351px 인데 쓸 수 있는 폭은 343px 이다. 8px 이 모자라 **저장만 홀로**
     다음 줄 오른쪽 구석으로 떨어졌고, 그게 주 동작이라 가장 나쁜 자리였다.
     ★ 억지로 한 줄에 넣지 않는다(글자를 깎으면 다른 언어에서 다시 넘친다).
       팝업을 여는 보조 동작 둘이 한 줄을 나눠 갖고, **주 동작은 아래 한 줄을
       통째로** 쓴다 — '고르는 것'과 '끝내는 것'이 줄로 갈린다. */
  .form-actions.sticky { flex-wrap: wrap; }
  /* ★ flex-basis 를 0 으로 두면 두 칸이 **똑같이** 나뉘는데, 글자 길이가 다르면 긴
     쪽이 2px 모자라 두 줄로 접힌다(실측: 쓸 수 있는 폭 102 · 글자 100 — '적용 결과 /
     미리보기'로 갈렸다). 내용만큼 갖고 남는 폭을 나누게 두고, 줄바꿈은 막는다. */
  .form-actions.sticky > .btn-secondary {
    flex: 1 1 auto; min-width: 0; white-space: nowrap; padding: 0 12px;
  }
  /* ★ 100% 로 못박지 않는다 — 그러면 **자리가 남아도** 늘 다음 줄로 내려간다
     (실측 768px: 셋이 376px 인데 710px 을 두고도 두 줄이었다).
     늘어나기만 하게 두면, 들어갈 때는 한 줄이고 모자랄 때만 내려가 그 줄을 채운다. */
  .form-actions.sticky > .btn-primary { flex: 1 1 auto; }
}
.field input[disabled], .field select[disabled], .field textarea[disabled] {
  background: var(--surface-2); color: var(--muted);
}
/* ★ **굵기를 못박는다.** 종전에는 굵기를 정하지 않아 **부모에서 물려받았다** — `.field`
   (라벨이라 560)나 `.field-check` 안에 들어간 설명만 라벨과 같은 굵기로 나왔다.
   실측(2026-09-15): 채팅 응대 설정 `.hint` 57개 중 7개, 백업·복원 `small` 20개 중 18개가
   560 이었다. **같은 화면에서 같은 클래스가 두 굵기**로 보이는 상태였다.
   설명은 언제나 본문 굵기다 — 물려받을 것이 아니라 정해 둘 것이다. */
.hint { color: var(--text-muted); font-size: 12px; font-weight: 400; line-height: 1.6; margin: 5px 0 0; }
.sub-title { font-size: 13px; font-weight: 620; margin: 18px 0 8px; }
/* 카드 안에서 조건부로 나타나는 하위 블록(시나리오 툴 옵션 등)을 시각적으로 묶는다. */
.card-inset {
  margin-top: 14px; padding: 2px 14px 12px;
  border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface-2);
}
.card-inset[hidden] { display: none; }
.card-inset .sub-title { margin-top: 12px; }

textarea {
  padding: 9px 11px; border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  font-size: 13.5px; line-height: 1.6; font-family: inherit;
  background: var(--panel); color: var(--text); resize: vertical;
  /* 긴 JSON·프롬프트가 화면을 통째로 밀어내지 않도록 상한을 둔다(안에서 스크롤). */
  max-height: calc(var(--vh) * 0.6);
}
input[type="time"] {
  height: var(--ctrl-h); padding: 0 10px;
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  font-family: inherit; font-size: 13.5px;
  background: var(--panel); color: var(--text);
}
.btn-secondary {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  height: var(--ctrl-h); padding: 0 14px;
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  font-family: inherit; font-size: 13.5px; font-weight: 560;
  text-decoration: none; color: var(--text); background: var(--panel); cursor: pointer;
}
.btn-secondary:hover { background: var(--surface-2); }
.btn-secondary:disabled { opacity: 0.55; cursor: default; }

.checkline { display: flex; align-items: center; gap: 9px; font-size: 13.5px; margin-top: 6px; }
.radio-row { display: flex; gap: 18px; flex-wrap: wrap; margin: 12px 0 6px; font-size: 13.5px; }
.radio-row label { display: flex; align-items: center; gap: 6px; }
.mode-panel[hidden] { display: none; }
.mode-panel { border-top: 1px dashed var(--border); margin-top: 12px; padding-top: 12px; }

/* 화면에서만 감춘다(포커스·폼 전송은 그대로) — 브라우저 기본 파일 입력처럼
   모양을 우리가 대신 그릴 때 쓴다. display:none 을 쓰면 포커스를 못 받는다. */
/* ---------- 2차 탭이 있는 화면의 제목 ----------
   실측(2026-09-10, 21개 화면): 2차 탭이 있는 19개 **전부** h1 이 활성 탭과 **글자까지
   같았다.** 같은 말을 두 번 적으면 본문이 그만큼 아래로 밀리고, 무엇보다 둘 중 어느
   것이 지금 위치인지 눈이 한 번 고른다.

   ★ DOM 에서 빼지 않는다 — 스크린리더의 문서 구조와 제목 탐색이 h1 에 걸려 있다.
     보이지 않을 뿐 문서에는 제목이 있어야 한다.
   ⚠ 2차 탭이 없는 화면(계정 정보 등)은 그대로 둔다. 거기서는 h1 이 유일한 이름이다
     (.no-subnav 는 탭 줄이 없다는 표시 — layouts/main.hbs 참조). */
.shell:not(.no-subnav) .page-head > div > h1,
.shell:not(.no-subnav) .page-head > h1 {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* 제목이 자리를 안 먹으므로 설명만 남은 머리말은 여백을 줄인다. 설명도 없으면
   빈 상자만 남지 않게 통째로 접는다(:has 로 '남은 것이 있나'를 본다). */
.shell:not(.no-subnav) .page-head { margin-bottom: 12px; }
.shell:not(.no-subnav) .page-head:not(:has(p, .page-head-actions, .kb-sync-actions, form, a, button)) {
  margin: 0; padding: 0;
}
/* ★ 접힌 머리말은 **아래 여백을 음수로** 갖는다.
   본문에 처음 오는 덩어리(카드·2단 레이아웃·목록 패널·탭 줄)는 저마다 16px 바깥
   여백을 갖고 있어, 본문 패딩과 겹쳐 **32px 이 통째로 비어 있었다.** 제목이 없는
   화면이라 그 공백이 무엇의 자리인지도 알 수 없다. -16px 이 그 겹침만 지우고,
   남는 것은 본문 패딩 하나다.

   ⚠ `.content:has(> .page-head:not(:has(…)))` 로 본문 패딩을 지우려 했는데
     **동작하지 않는다** — `:has()` 안에 `:has()` 를 중첩할 수 없다(실측: 32px 그대로).
   ⚠ .content-list-fill 은 뺀다. 거기는 카드 바깥 여백이 이미 0 이라 뺄 겹침이 없고,
     그대로 두면 목록이 탭 줄 **위로 6px 올라탄다**(실측).
     조건을 하나만 쓰는 이 자리에서 해결한다.
   ★ 붙는 방식이 두 갈래인데 결과는 같다: 일반 본문은 인접 여백이 상쇄되고(16-16=0),
     flex 인 높이 고정 본문은 그냥 더해진다(16+(-16)=0). */
.shell:not(.no-subnav)
  .content:not(.content-list-fill)
  > .page-head:not(:has(p, .page-head-actions, .kb-sync-actions, form, a, button)) {
  margin-bottom: -16px;
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* 프로필 */
.profile-row { display: flex; gap: 24px; align-items: flex-start; flex-wrap: wrap; }
.profile-preview { display: flex; flex-direction: column; gap: 10px; width: 200px; }
/* 미리보기 아래 동작 묶음 — 바꾸기 버튼과 삭제 체크를 한 덩이로 붙인다. */
.profile-actions { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.profile-actions .checkline { margin-top: 0; font-size: 13px; }
/* label 을 버튼처럼 쓰므로 커서를 손가락으로(기본 label 커서는 글자 커서다). */
.profile-actions label.btn-secondary { cursor: pointer; }
.avatar-slot {
  border-radius: 50%; overflow: hidden; background: var(--accent-soft); color: var(--accent);
  display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 600;
  flex-shrink: 0;
  /* 이미지가 로드되기 전/실패했을 때도 원형 배경만 보이도록(빈 사각형 방지) */
  position: relative;
}
.avatar-slot img {
  display: block; object-fit: cover; width: 100%; height: 100%;
  /* alt 텍스트나 깨진 아이콘이 비쳐 보이지 않게 배경을 깔아 둔다 */
  background: var(--accent-soft);
}
.avatar-slot img[hidden] { display: none; }
.avatar-slot span[hidden] { display: none; }

/* 필드 단위 오류 안내(이미지 등록 실패 등) */
.field-error {
  color: var(--danger-fg); font-size: 12px; margin: 6px 0 0;
  line-height: 1.5; word-break: keep-all;
}
.field-error[hidden] { display: none; }

/* 스케줄 */
.day-row { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; }
.day-chip {
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid var(--border-strong); border-radius: var(--r-pill);
  padding: 6px 12px; font-size: 13px; background: var(--panel); color: var(--text); cursor: pointer;
}
.day-chip:hover { background: var(--surface-2); }
/* 체크된 요일을 눈으로 바로 가르도록(종전에는 체크박스만 달라 구분이 어려웠다). */
.day-chip:has(input:checked) { background: var(--accent-soft); border-color: var(--accent-border); color: var(--accent); font-weight: 600; }
.time-row { display: flex; gap: 14px; flex-wrap: wrap; }

/* ---------- 지식 색인 상태 줄 ----------
   [색인 점검] + 요약 배지 + 점검 시각, 그리고 재색인 진행률.
   ★ 점검 시각을 **반드시 함께** 둔다 — 이 배지는 저장 시점의 플래그가 아니라 사람이 누른
     순간의 측정치라, 시각이 없으면 "지금 상태"로 읽힌다. */
.kb-index-bar { margin: 0 0 12px; }
.kb-index-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.kb-index-check { display: inline-flex; }
.kb-index-at { margin-left: auto; }
/* 진행률 — 숫자와 막대를 함께 둔다. 막대만 있으면 "몇 건 중 몇 건"을 알 수 없고,
   숫자만 있으면 움직이는지 한눈에 안 보인다. */
.kb-progress { margin-top: 8px; }
.kb-progress-bar {
  height: 6px; border-radius: 999px; background: var(--surface-3); overflow: hidden;
}
.kb-progress-bar > span {
  display: block; height: 100%; width: 0; background: var(--accent);
  transition: width 0.3s ease;
}

/* ---------- 요일별 운영 시간 표 ----------
   한 줄이 하루다: [사용] 요일 · 영업 시작~종료 · 휴게 시작~종료.
   요일마다 시간이 다를 수 있다는 현장 피드백에서 이 모양이 됐다(2026-09-17) — 칩 한 줄 +
   시작/종료 한 쌍으로는 "토요일만 일찍 닫는다"도, 점심 휴게도 적을 자리가 없었다.
   ★ 열 폭을 고정한다(auto 로 두면 요일마다 칸 위치가 어긋나 표로 읽히지 않는다). */
.day-grid { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }
.day-grid-head, .day-line {
  display: grid; grid-template-columns: 84px minmax(0, 1fr) minmax(0, 1fr); gap: 10px; align-items: center;
}
.day-grid-head { font-size: 11.5px; color: var(--text-muted); font-weight: 600; padding-left: 2px; }
.day-toggle { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; font-size: 13px; }
.day-toggle span { font-weight: 600; }
.day-times { display: flex; align-items: center; gap: 6px; min-width: 0; }
.day-times input[type="time"] { flex: 1 1 0; min-width: 0; }
.day-times .tilde { flex: 0 0 auto; color: var(--text-muted); }
/* 쉬는 날은 시간 칸을 흐리게 — 값은 남겨 둔다(다시 켜면 그대로 쓰라고). */
.day-line.is-off .day-times { opacity: 0.45; }

/* 안내 문구 편집기 */
.notice-editor { margin-top: 14px; }
.notice-editor .field { margin-bottom: 10px; }
.i18n-box { margin: 6px 0 12px; font-size: 13px; }
.i18n-box summary { cursor: pointer; color: var(--muted); margin-bottom: 8px; }
.actions-head { display: flex; gap: 10px; align-items: baseline; font-size: 13px; margin-bottom: 6px; }
.action-row { display: flex; gap: 8px; margin-bottom: 6px; }
.action-row input:first-child { width: 160px; flex-shrink: 0; }
.action-row input:last-child { flex: 1; }
.warn-box {
  background: var(--warn-bg); color: var(--warn-fg); border: 1px solid var(--warn-border);
  padding: 10px 12px; border-radius: var(--r-sm); font-size: 12.5px; line-height: 1.6; margin-top: 12px;
}

/* 평문(http) 전송처럼 데이터 노출 위험을 강하게 경고할 때. */
.danger-box {
  display: flex; align-items: flex-start; gap: 9px;
  background: var(--danger-bg); color: var(--danger-fg); border: 1px solid var(--danger-border);
  border-left: 4px solid var(--danger-fg);
  padding: 11px 14px; border-radius: var(--r-sm); font-size: 13px; line-height: 1.55;
  margin-top: 10px; font-weight: 600;
}
.danger-box[hidden] { display: none; }
.danger-box .danger-ico { flex-shrink: 0; margin-top: 1px; }
.danger-box .danger-sub { font-weight: 400; opacity: 0.9; }
/* 여러 경고(http·내부망)를 한 박스에 줄바꿈으로 쌓는다. */
.danger-box .danger-lines { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.url-warn-line[hidden] { display: none; }

/* 권한 목록 */
.perm-list { list-style: none; padding: 0; margin: 12px 0 0; }
.perm-item {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 0; border-bottom: 1px solid var(--border); font-size: 13.5px;
}
.perm-item:last-child { border-bottom: 0; }
.perm-mark { width: 18px; display: flex; justify-content: center; flex: 0 0 auto; }
.perm-item.is-on .perm-mark { color: var(--success-fg); }
.perm-item.is-off .perm-mark { color: var(--danger-fg); }
.perm-item.is-off .perm-label { color: var(--muted); }
.perm-key { margin-left: auto; color: var(--muted); font-size: 12px; }

/* 대시보드(로그인 첫 화면)·접근 권한 안내의 화면 목록.
   두 화면 모두 nav 를 그대로 그린다 — 권한 필터가 이미 적용된 목록이라
   여기서 다시 판단하지 않는다(dashboard.hbs 주석 참조). */
.home-groups {
  display: grid; gap: 14px; margin-top: 14px;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}
.home-group-label {
  display: flex; align-items: center; gap: 7px;
  color: var(--muted); font-size: 12px; font-weight: 640;
  letter-spacing: 0.02em; text-transform: uppercase;
  padding-bottom: 6px; border-bottom: 1px solid var(--border);
}
/* 그룹 이름 앞 아이콘은 읽는 글자가 아니라 표식이다(--text-subtle 의 용도). */
.home-group-label svg { color: var(--text-subtle); flex: 0 0 auto; }
.home-links { list-style: none; padding: 0; margin: 6px 0 0; display: flex; flex-direction: column; gap: 2px; }
.home-link {
  display: flex; align-items: center; gap: 9px;
  /* 44px — 손가락으로 누르는 최소 크기. 이 목록은 모바일에서도 첫 화면이다. */
  min-height: 44px; padding: 0 10px;
  border-radius: var(--r-sm); color: var(--text); text-decoration: none;
  font-size: 13.5px;
}
.home-link:hover { background: var(--surface-2); }
.home-link:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.home-link svg { color: var(--text-subtle); flex: 0 0 auto; }
.home-link:hover svg { color: var(--accent); }

/* 접근 권한 없음 화면: 무엇이 필요한지 / 무엇을 요청했는지 한 줄씩. */
.deny-facts { list-style: none; padding: 0; margin: 0; }
.deny-facts li {
  display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline;
  padding: 7px 0; border-bottom: 1px solid var(--border); font-size: 13.5px;
}
.deny-facts li:last-child { border-bottom: 0; }
.deny-facts .deny-label { color: var(--muted); min-width: 108px; }

/* 긴 식별자(경로·토큰·URL)는 줄바꿈 지점이 없어 **페이지 전체를 옆으로 민다**.
   실측: 대화 기록 관리 화면이 390px 폭에서 문서 폭 662px 이 됐고, 범인은
   백업 경로를 담은 <code> 였다. 잘라 내지 않고 접는다 — 관리 화면에서 값을
   숨기면 "값이 없다"와 "안 보인다"를 구분할 수 없다. */
code, .mono { overflow-wrap: anywhere; }

/* 상태를 알리는 짧은 안내 줄. 색은 **여기서** 정한다 — JS 가 style.color 로 박으면
   토큰 밖이라 다크 모드에서 그 자리만 안 읽힌다(org-tools.js 의 인증 헤더 안내). */
.is-ok { color: var(--success-fg); display: inline-flex; align-items: center; gap: 5px; }
.is-warn { color: var(--warn-fg); display: inline-flex; align-items: center; gap: 5px; }

/* 글리프(✕·✎)를 SVG 로 바꾼 자리 — 글자 크기가 아니라 아이콘 크기로 잡는다. */
.room-name-edit .icon, .tpl-panel-close .icon, .row-remove .icon, .repeat-x .icon { display: block; }
.row-remove, .repeat-x { display: inline-flex; align-items: center; justify-content: center; }
.ch-badge .icon { width: 14px; height: 14px; }

/* ---------- 긴 모달의 구획 탭 ----------
   툴 편집 팝업은 구획 다섯을 **한 번에 하나만** 보인다(tool-sections.js).

   ★ 탭 줄은 **스크롤 영역 바깥**이다(제목 줄과 본문 사이). 한때 본문 안에서
     `position: sticky` 로 붙여 두었는데, 붙였다는 것은 곧 **본문이 그 밑으로
     지나간다**는 뜻이라 스크롤할 때마다 첫 줄이 반쯤 잘려 보였다(사용자 지적).
     바깥으로 빼면 겹칠 일 자체가 없다 — 늘 보이고 아무것도 가리지 않는다.
     (붙여 두던 시절의 곁가지도 함께 사라졌다: 안쪽 여백을 자식에게 넘겨 sticky 의
      고정 한계를 피하던 보정, 좌우로 넓히던 음수 여백, 좌측 레일용 2단 그리드.)
   ★ 칩은 줄바꿈하지 않고 가로로 민다. 다섯 칸이 두 줄로 갈리면 탭 줄이 본문을 먹는다. */
.modal-tabs {
  flex: 0 0 auto;
  display: flex; flex-direction: row; flex-wrap: nowrap; gap: 4px;
  overflow-x: auto; scrollbar-width: none;
  /* 위아래를 같게 둔다. 위를 0 으로 두었더니 칩이 제목 줄 경계선에 딱 붙고 아래만
     떠서, 탭 줄이 제목 줄에서 흘러내린 것처럼 보였다(실측: 위 0px · 아래 11px). */
  padding: 10px 22px; border-bottom: 1px solid var(--border);
}
.modal-tabs::-webkit-scrollbar { display: none; }
.modal-tabs a {
  flex: 0 0 auto; white-space: nowrap;
  padding: 8px 12px; border-radius: var(--r-sm);
  color: var(--text-muted); text-decoration: none; font-size: 13px; font-weight: 550;
}
.modal-tabs a:hover { background: var(--surface-2); color: var(--text); }
.modal-tabs a.is-active { background: var(--accent-soft); color: var(--accent); font-weight: 640; }

/* 탭을 옮겨도 팝업 크기는 그대로다. 구획마다 길이가 달라(1.3~3.8장) 높이를 내용에
   맡기면 탭을 누를 때마다 상자가 커졌다 작아진다 — 누른 자리와 다음에 누를 자리가
   매번 옮겨 다닌다. 상한과 **같은 값**으로 못박고 안쪽만 스크롤한다. */
#toolModal .modal-box { height: calc(var(--vh) * 0.9); }
@media (max-width: 768px) { #toolModal .modal-box { height: calc(var(--vh) * 0.92); } }
/* 상자만 못박으면 부족하다 — 실제 flex 기둥은 그 안의 <form> 이라, 내용이 짧은 탭
   에서는 폼이 내용만큼만 자라고 **스크롤 영역이 같이 줄었다**(실측 615 → 489px).
   폼을 상자 높이에 맞추면 읽는 창의 크기도 탭과 무관해진다. */
#toolModal .modal-sticky > form { height: 100%; }
/* ★ 내용이 짧은 탭(테스트)에서도 **위에서부터** 읽는다. 읽는 창을 못박아 두면 남는
   높이가 생기는데, 그때 가운데로 모으면 짧은 탭만 글이 공중에 뜬 것처럼 보인다. */
#toolModal .modal-body { display: block; }
#toolModal .modal-main { display: block; }

/* 한 번에 한 구획만 보이므로(tool-sections.js) 구획 사이의 경계선은 필요 없다 —
   보이는 것이 하나뿐인데 위에 선이 있으면 잘려 나온 것처럼 읽힌다. */
.modal-main > .form-section + .form-section { margin-top: 0; padding-top: 0; border-top: 0; }

/* 채널별 설정처럼 **같은 모양이 여러 벌** 반복되는 묶음은 가로로 편다.
   세로로만 쌓으면 채널 수만큼 화면이 길어지고 오른쪽은 계속 비어 있다. */
.repeat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 14px; }
.repeat-grid > .sec-group {
  margin: 0; padding: 14px 16px; border: 1px solid var(--border);
  border-radius: var(--r-md); background: var(--surface-2);
}
/* ★ 그리드 안에서는 세로로 쌓을 때 쓰던 구분(위 경계선·바깥 여백)을 **전부 되돌린다**.
   .sec-group + .sec-group 은 margin-top·padding-top·border-top 세 가지를 주는데,
   그것이 **첫 항목에만 안 걸려** 웹챗(첫 항목)만 높이와 시작 위치가 달랐다.
   셋 중 하나라도 남기면 같은 어긋남이 그대로 남는다(margin-top 을 빠뜨려 재발했다). */
.repeat-grid > .sec-group + .sec-group {
  margin-top: 0; padding-top: 14px; border-top: 0;
}
/* 채널별 설정 표(채널별 응대·사진·영상만 온 메시지) — 사용자 지시 2026-10-20.
   ★ 반폭 격자(.repeat-grid)는 네 칸 세그먼트가 320px 칸에 안 들어가 글자가 겹쳤고, 줄마다 라벨을 붙인 한 줄
     목록은 폭이 모자라면 세그먼트가 제멋대로 줄을 넘어가 채널 이름·라벨·트랙의 높이가 어긋났다(실측 799px).
     그래서 **표**로 둔다: 열 제목은 머리줄에 한 번, 채널마다 같은 열에 세그먼트를 세운다.
   ★ 칸·이름 클래스는 chset- 로 둔다 — .channel-cell·.channel-name 은 다른 화면(채널 표시 칩)이 이미 쓰고 있어
     그 규칙(align-items:center 등)이 섞여 들어왔다(실측: 세로로 쌓일 때 라벨·트랙이 가운데로 몰렸다).
   ★ 줄에 배경을 칠하지 않는다 — 세그먼트 트랙이 --surface-2 라 같은 색 위에서는 트랙이 사라진다(그래서 라디오가
     떠 보였다). 줄은 경계선으로만 나눈다. */
.channel-list { container-type: inline-size; }
/* 열 폭은 **표 전체가 하나의 격자**로 정한다 — 머리줄과 줄마다 따로 격자를 두면 열이 각자 제 내용 폭이 되어
   열 제목이 세그먼트 위에 안 선다(실측: '상담사 연결' 제목이 응대 주체 트랙 위에 섰다). 줄은 subgrid 로 열을 물려받는다. */
.channel-table {
  display: grid; grid-template-columns: 112px max-content max-content 1fr; column-gap: 20px;
  border: 1px solid var(--border); border-radius: var(--r-md);
}
.channel-table.channel-table-2 { grid-template-columns: 112px max-content 1fr; }
.channel-list-head, .channel-block, .channel-line {
  grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; align-items: center;
}
.channel-list-head {
  padding: 8px 16px; background: var(--surface-2); border-radius: var(--r-md) var(--r-md) 0 0;
  font-size: 12.5px; font-weight: 560; color: var(--muted);
}
.channel-block { padding: 12px 16px; border-top: 1px solid var(--border); }
.channel-block > .handoff-panel { grid-column: 1 / -1; }
.chset-name { margin: 0; font-size: 13.5px; font-weight: 620; }
.chset-cell { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; min-width: 0; }
/* 표 안의 세그먼트는 칸 여백을 조금 줄인다(14px→10px) — 한 줄에 세그먼트 둘이 서야 해서 폭이 빠듯하다. */
.channel-table .seg > label { padding-left: 10px; padding-right: 10px; }
/* 칸 라벨은 세로로 쌓일 때만 보인다(머리줄이 그 역할을 한다). */
.cl-label { display: none; font-size: 12.5px; font-weight: 560; color: var(--muted); }
/* 표 폭이 모자라면 세로로 쌓는다 — 채널 이름 → 칸 라벨 + 세그먼트.
   기준 780px = 한 줄에 필요한 폭의 실측 최대값 + 여유(2026-10-20, 테두리·여백 포함):
   채널별 응대 ko 710 · en 737 · ja 716 · zh 642 / 사진·영상 ko 605 · en 772 · ja 635 · zh 580.
   이보다 좁은데 한 줄로 두면 마지막 칸이 잘린다(실측 799px 창 · 표 폭 약 710px 에서 '개별 설정'이 잘렸다).
   ⚠ 세그먼트 문구를 늘리면 이 값을 다시 재야 한다. */
@container (max-width: 780px) {
  .channel-table, .channel-table.channel-table-2 { display: block; }
  .channel-list-head { display: none; }
  .channel-block { display: block; }
  .channel-block:first-of-type { border-top: 0; }
  .channel-line { display: flex; flex-direction: column; align-items: stretch; gap: 10px; }
  .chset-cell { width: 100%; }
  .cl-label { display: block; }
}
/* 채널 추가 설정(AI 답변 머리말·상담 연결 버튼, 2026-10-23) — 줄 아래 **전체 폭 접기**(.chset-fold, 아래 개별 설정과 같은 꼴 —
   사용자 지시 2026-10-23). 표 열(subgrid)에 그냥 두면 112px 이름 열·max-content 열에 끼어 글자가 세로로 쌓이고 긴 안내가
   줄바꿈 없이 밀려났다. 본문 항목은 **위아래로** 쌓고(머리말 → 상담 연결 버튼), 버튼의 두 스위치는 한 줄에 나란히 둔다
   (사용자 배치 2026-10-23). 입력칸은 좁게, 안내 문구는 넓게. */
.channel-block > .chset-extra { grid-column: 1 / -1; margin-top: 14px; }
.chset-extra > .chset-fold-body { display: flex; flex-direction: column; gap: 16px; }
.chset-extra-item { min-width: 0; max-width: 820px; }
.chset-extra-item > .field { max-width: 360px; }
.chset-extra-title { display: block; font-size: 12.5px; font-weight: 560; color: var(--muted); }
.chset-extra-switches { display: flex; flex-wrap: wrap; gap: 6px 48px; margin: 6px 0 10px; }
.chset-extra-switches > .switch-line { margin-top: 0; }
/* 추가 설정 접기와 개별 설정 접기가 둘 다 보이면 붙지 않게 조금 띄운다(개별 설정 쪽 14px 는 채널 줄 기준). */
.channel-block > .chset-extra + .handoff-panel { margin-top: 10px; }
/* 펼친 상세는 줄 아래에 붙는다. */
.channel-block > .handoff-panel { margin-top: 14px; }
/* 채널별 개별 설정 접기 — 카드 접기(.card-fold)와 같은 꼴(왼쪽 요약 글 · 오른쪽 파란 뱃지 .sec-sum · 화살표).
   사용자 지시 2026-10-20: 채널마다 상세를 접고 펼 수 있게, 카드 머리와 같은 모양으로. */
.chset-fold { border: 1px solid var(--border); border-radius: var(--r-md); }
.chset-fold > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px; border-radius: var(--r-md);
}
.chset-fold > summary::-webkit-details-marker { display: none; }
.chset-fold > summary:hover { background: var(--surface-2); }
.chset-fold > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.chset-fold[open] > summary { border-bottom: 1px solid var(--border); border-radius: var(--r-md) var(--r-md) 0 0; }
.chset-fold-title { font-size: 13px; font-weight: 600; }
.chset-fold[open] > summary .sec-chev { transform: rotate(180deg); }
.chset-fold > .chset-fold-body { padding: 14px; }
.chset-fold > .chset-fold-body > :first-child { margin-top: 0; }
@media (max-width: 768px) {
  /* 카드 접기와 같은 규칙 — 좁은 화면에서는 제목이 한 줄을 차지하고 뱃지·화살표가 아랫줄로 내려간다. */
  .chset-fold > summary { flex-wrap: wrap; }
  .chset-fold > summary .sec-head { flex: 1 1 100%; }
}

/* ---------- 세그먼티드 컨트롤 ----------
   서로 배타적인 2~3개 선택지를 **라디오 그대로** 두고 모양만 바꾼다.
   폼 이름·값·서버 파서는 하나도 건드리지 않는다 — 라디오를 커스텀 위젯으로
   바꾸면 키보드 조작·폼 전송·저장된 값 복원이 전부 우리 몫이 된다.
   라디오는 시각적으로만 숨긴다(display:none 이면 키보드 포커스가 사라진다). */
/* ★ 줄바꿈하지 않는다(flex-wrap:nowrap). 종전에는 wrap 이라 좁은 화면에서 세 칸 중
   하나가 아래로 떨어졌고, 그러면 **떨어진 칸이 세그먼트의 일부로 안 보인다** —
   같은 축의 선택지인데 다른 줄에 있으면 눈이 둘을 묶지 못한다(실측 390px: '상담사가
   먼저 응대'가 혼자 둘째 줄).
   대신 칸이 줄어들 수 있게 한다: 트랙은 폭을 다 쓰고, 칸은 균등하게 나눠 갖는다. */
.seg { display: flex; align-items: center; gap: 2px; padding: 3px; margin: 0;
       background: var(--surface-2); border-radius: var(--r-sm); flex-wrap: nowrap;
       width: 100%; max-width: max-content; }
.seg > label {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  /* 넓은 화면에서는 **제 폭**을 쓴다. 균등 분할(flex:1 1 0)을 여기까지 걸었더니 칸을
     1/3 씩 똑같이 나눠 가장 긴 라벨이 제 칸에서 잘렸다(실측 1440px:
     '상담 가능 시간에만 연결' 118px 필요 · 114px 배정). 균등 분할은 가로가 모자란
     좁은 화면에서만 의미가 있다 — 아래 미디어쿼리에서 켠다. */
  flex: 0 1 auto; min-width: 0;
  padding: 7px 14px; border-radius: 6px; cursor: pointer;
  font-size: 13px; font-weight: 550; color: var(--text-muted); white-space: nowrap;
}
.seg > label:hover { color: var(--text); }
.seg > label > input[type="radio"] {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.seg > label:has(input:checked) {
  background: var(--raised); color: var(--text); font-weight: 640; box-shadow: var(--sh-sm);
}
/* 키보드로 옮겨 다닐 때 어디에 있는지 보이게 한다(라디오를 숨겼으므로 라벨이 대신 받는다). */
.seg > label:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 1px; }
/* 세그먼트 앞에 붙는 설명 라벨은 트랙 밖에 둔다. */
/* 세그먼트에 붙는 설명 라벨은 **위에** 둔다.
   ★ 옆에 나란히 두었더니 세그먼트의 '선택 안 된 칸'처럼 보여 눌러 보게 됐다
     (실사용에서 'AI 응대시간' 을 다섯 번 눌렀다는 보고). 다른 입력의 라벨과
     같은 자리·같은 모양이면 그 오해가 생기지 않는다. */
.seg-row { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; margin: 14px 0 6px; }
/* ★ .seg 는 트랙 자체에 바깥 여백이 없다(margin:0) — 바로 뒤에 오는 입력이
   붙어 보인다(타임존 칸이 모드 세그먼트에 닿아 있었다). 뒤따르는 요소에 여백을 준다. */
.seg + .field, .seg + .form-grid, .seg + .checkline, .seg + .switch-line,
.seg + .hint, .seg + .card-inset { margin-top: 16px; }

.seg-row > .field-label {
  font-size: 12.5px; font-weight: 560; color: var(--muted);
  /* 클릭할 것처럼 보이지 않게 — 커서도 글자 커서로 둔다. */
  cursor: default;
}

/* ---------- 토글 스위치 ----------
   켜고 끄는 단일 설정에만 쓴다(여러 개를 고르는 목록은 체크박스 그대로가 맞다).
   체크박스도 마찬가지로 숨기기만 하고 실물은 그대로 둔다. */
.switch-line { display: flex; align-items: center; gap: 11px; cursor: pointer; margin-top: 6px; font-size: 13.5px; }
.switch-line > input[type="checkbox"] {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); border: 0;
}
.switch-line > .switch {
  position: relative; flex: 0 0 auto; width: 38px; height: 22px;
  border-radius: var(--r-pill); background: var(--surface-3);
  transition: background 0.15s ease;
}
.switch-line > .switch::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px;
  border-radius: 50%; background: var(--raised); box-shadow: var(--sh-sm);
  transition: left 0.15s ease;
}
.switch-line:has(input:checked) > .switch { background: var(--accent); }
.switch-line:has(input:checked) > .switch::after { left: 19px; }
.switch-line:has(input:focus-visible) > .switch { outline: 2px solid var(--accent); outline-offset: 2px; }
.switch-line:has(input:disabled) { opacity: 0.55; cursor: default; }
/* 스위치 오른쪽 설명(두 줄까지) */
.switch-line .switch-text { min-width: 0; }
.switch-line .switch-text small { display: block; color: var(--text-muted); font-size: 12px; margin-top: 2px; }

/* ---------- 좁은 화면: 목록이 화면 전체인 카드 ----------
   실측(390px): 본문 좌우 16+16 과 카드 좌우 22+22 로 **91px** 이 여백에 갔고, 정작
   표가 쓸 수 있는 폭은 299px 였다(표는 729px 를 원한다). 이 화면에서 카드는 장식이
   아니라 화면 그 자체다 — 여백을 얇게 깎는다.

   ★ 한 번은 **0 까지** 깎았다가 되돌렸다. 화면 가장자리에 딱 붙으니 "어디까지가
     목록인가"가 사라졌다 — 이 표는 **가로로 스크롤**해서 오른쪽이 늘 잘려 있는데,
     테두리가 화면 끝에 있으면 잘린 것인지 화면이 끝난 것인지 구분되지 않는다.
     10px 만 남기면 테두리가 보이고, 그래도 원래보다 71px 을 되찾는다.
   ★ 위쪽은 본문 여백(16)과 카드 바깥 여백(16)이 겹쳐 **32px** 이 통째로 비어
     있었다. 제목이 없는 화면이라 그 공백이 무엇의 자리인지도 알 수 없다 — 카드
     바깥 여백을 없애고 본문 여백을 옆과 같은 10px 로 맞춘다.

   ⚠ .content-list-fill 로 한정한다. 카드가 여럿 쌓이는 화면(설정 등)에서는 카드끼리
     떨어져 보여야 하고, 거기서 여백을 없애면 어디까지가 한 카드인지 사라진다. */
@media (max-width: 768px) {
  /* ★ 위 여백은 **다른 화면과 같은 값**이다(설정·계정 등 16px). 여기만 10px 로 두었더니
     탭을 옮길 때 화면이 위아래로 6px 씩 튀었다 — 같은 자리에 있어야 할 것이 화면마다
     다르면 그 자체가 결함으로 읽힌다(사용자 지적: "상단 공백이 달라").
     좌우는 10px 로 남긴다 — 그건 여백이 아니라 **목록 영역의 테두리를 화면 안에
     들여놓는 값**이고(위 주석), 가로로 스크롤하는 표에 폭을 돌려주는 몫이다. */
  .content-list-fill { padding: var(--content-pad-y) 10px 10px; }
  .content-list-fill > .kb-list-card {
    margin-top: 0;
    padding: 12px 10px;
  }
  /* 표는 카드 안쪽 여백까지 쓴다 — 가로로 스크롤하는 표라 1px 도 아깝다.
     좌우 테두리는 **카드가 대신한다**(같은 자리에 선이 둘이면 지저분하다). */
  .content-list-fill > .kb-list-card > .table-scroll {
    margin-left: -10px; margin-right: -10px;
    border-left: 0; border-right: 0; border-radius: 0;
  }
}

/* ---------- 목록 툴바 ----------
   종전에는 검색칸이 제목 줄 아래 **별도 한 줄**을 통째로 차지했고, 동작 버튼은
   제목 줄 오른쪽에 있었다 — 같은 목록을 다루는 수단이 두 줄로 흩어져 있었다.
   검색과 동작을 한 줄에 모으고, 검색이 남는 폭을 먹는다. */
/* ---------- 목록 툴바 ----------
   [검색 ......... 동작들] 한 줄. 동작은 하나로 묶어(.toolbar-actions) 좁아져도 셋이
   제각각 흩어지지 않게 한다 — 흩어지면 줄만 늘고 어느 것이 주 동작인지 사라진다.
   실측(개선 전 390px): 툴바가 **4줄 126px**, 표가 Y=425 에서 시작해 화면 절반이
   컨트롤이었다. */
.toolbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 10px 0 12px; }
.toolbar > .grow { flex: 1 1 auto; }
.toolbar form { margin: 0; }
.toolbar-actions { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; margin-left: auto; }
.toolbar-actions form { display: flex; }

@media (max-width: 768px) {
  /* 두 줄까지만: 검색 한 줄, 동작 한 줄(오른쪽 정렬). */
  .toolbar { gap: 8px; }
  .toolbar > .search-field { flex: 1 1 100%; max-width: none; }
  .toolbar > .grow { display: none; }   /* 줄이 갈리므로 밀어낼 것이 없다 */
  .toolbar-actions { width: 100%; justify-content: flex-end; gap: 6px; }
  /* ⚠ 손가락 과녁 규칙(.btn-* 44px)이 여기까지 와서 버튼이 목록 컨트롤이 아니라
     본문처럼 커졌다(실측 44px). 목록을 다루는 버튼은 목록보다 작아야 한다. */
  .toolbar-actions .btn-primary, .toolbar-actions .btn-secondary {
    height: 34px; min-height: 34px; padding: 0 10px; font-size: 12px;
  }
  /* 아주 좁으면 보조 동작은 아이콘만 남긴다(주 동작 '추가'는 글자를 지킨다). */
  @media (max-width: 480px) {
    .toolbar-actions form .btn-secondary > span { display: none; }
    .toolbar-actions form .btn-secondary { padding: 0 9px; }
  }
}

/* 앞에 아이콘이 붙는 검색칸. 아이콘은 칸 안에 겹쳐 두고 그만큼 왼쪽을 비운다. */
.search-field { position: relative; display: flex; align-items: center; flex: 1 1 240px; min-width: 180px; max-width: 420px; }
.search-field > .icon { position: absolute; left: 10px; color: var(--text-subtle); pointer-events: none; }
.search-field > input { width: 100%; padding-left: 34px; }

/* ---------- 단위가 붙는 입력 ----------
   숫자 칸은 값만 보면 그게 초인지 분인지 일인지 알 수 없다. 종전에는 단위가
   라벨 괄호에 있거나('자동 재개까지(초)'), 화면 맨 아래 각주에만 있거나
   ('단위는 분입니다'), 아예 없었다('최소 길이'). 단위를 **칸 안**에 붙여
   값과 같은 눈길에 들어오게 한다.

   <label class="field">
     <span>자동 재개까지</span>
     <span class="input-unit"><input type="number" …><span class="unit">초</span></span>
   </label>

   ★ 이 안에서는 네이티브 증감 화살표를 숨긴다 — 화살표가 오른쪽 끝에 겹쳐 단위를
     가린다. 여기 있는 값(기간·길이·단가)은 화살표로 한 칸씩 올리는 성격이 아니고
     키보드 ↑↓ 는 그대로 동작한다. */
.input-unit { position: relative; display: flex; align-items: center; width: 100%; --unit-pad: 46px; }
.input-unit > input { width: 100%; padding-right: var(--unit-pad); }
.input-unit > .unit {
  position: absolute; right: 11px; pointer-events: none;
  font-size: 12.5px; font-weight: 560; color: var(--text-muted);
  max-width: 44%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.input-unit > input::-webkit-outer-spin-button,
.input-unit > input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.input-unit > input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
/* 단위가 길면(예: $/1M) 그만큼 더 비운다. */
.input-unit.wide { --unit-pad: 66px; }

/* ---------- 폼 구획 ----------
   길이가 긴 편집 폼(커스텀 툴 등)을 "무엇을 정하는 부분인가" 단위로 가른다.

   ★ 여기서는 <details> 로 접지 **않는다**. 이 폼에는 required 입력(이름·설명·URL)이
     있는데, 접힌 컨테이너 안에 있으면 제출 시 브라우저가 그 칸으로 포커스를 옮기지
     못해 "An invalid form control is not focusable" 로 **아무 일도 안 일어난 것처럼**
     막힌다. 접기는 required 가 없는 구획(테스트)에만 쓴다. */
.form-section + .form-section { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--border); }
.form-section-head { margin-bottom: 14px; }
.form-section-head h3 { margin: 0; font-size: 14px; font-weight: 660; letter-spacing: -0.01em; }
.form-section-head p { margin: 3px 0 0; font-size: 12.5px; color: var(--muted); }
/* 구획 안의 블록 사이 간격을 일정하게 — 종전에는 블록마다 제각각이라 어디서 끊기는지
   눈으로 가늠이 안 됐다. */
.form-section > .field,
.form-section > .builder,
.form-section > .card-inset,
.form-section > label.field { margin-top: 14px; }
.form-section > :first-child { margin-top: 0; }

/* 접히는 구획(테스트처럼 required 가 없는 것만). */
.form-fold { border-top: 1px solid var(--border); margin-top: 22px; padding-top: 6px; }
.form-fold > summary {
  list-style: none; cursor: pointer; display: flex; align-items: center; gap: 10px;
  padding: 10px 0; font-size: 14px; font-weight: 660;
}
.form-fold > summary::-webkit-details-marker { display: none; }
.form-fold > summary .icon { color: var(--text-subtle); transition: transform 0.15s ease; }
.form-fold[open] > summary .icon { transform: rotate(180deg); }
.form-fold > summary .grow { flex: 1 1 auto; }
.form-fold-body { padding-bottom: 6px; }

/* ---------- 파일 선택(끌어다 놓기) ----------
   기본 <input type="file"> 은 브라우저마다 생김새가 다르고 "파일 선택 · 선택된 파일 없음"
   같은 UA 문구가 화면 언어와 어긋난다. 그래서 입력을 **투명하게 영역 전체에 깔고** 보이는
   것은 우리가 그린다.

   ★ 입력을 숨기지(display:none) 않고 덮는 이유: 파일 입력은 원래 드롭 대상이라, 깔아 두면
     끌어다 놓기가 **스크립트 없이** 그냥 된다. 클릭도 그대로 파일 창을 연다. 스크립트는
     고른 파일 이름을 적는 일만 한다(dropzone.js). */
.dropzone {
  position: relative; display: block; margin-top: 4px;
  border: 1.5px dashed var(--border-strong); border-radius: var(--r-md);
  background: var(--surface-2);
  transition: border-color 0.15s ease, background 0.15s ease;
}
.dropzone > input[type="file"] {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; cursor: pointer;
  /* 파일 입력의 기본 높이가 영역보다 작으면 아래쪽이 안 눌린다 — 명시적으로 채운다. */
  padding: 0; border: 0; background: none;
}
.dropzone-idle, .dropzone-picked {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 22px 16px; text-align: center;
  /* 안쪽 요소가 포인터를 먹으면 그 자리에서 파일 창이 안 열린다. */
  pointer-events: none;
}
.dropzone .dropzone-ico { width: 22px; height: 22px; color: var(--text-muted); }
.dropzone-cta { font-size: 13px; font-weight: 600; }
.dropzone .hint { margin: 0; }
/* 두 겹 중 하나만 보인다. 판단은 스크립트가 has-file 로 남기고 여기서는 보이기만 한다. */
.dropzone-picked { display: none; }
.dropzone.has-file .dropzone-idle { display: none; }
.dropzone.has-file .dropzone-picked { display: flex; }
/* 파일이 지정된 상태는 **테두리와 아이콘**으로 알린다 — 배경까지 액센트로 물들이면
   그 위의 보조 문구(크기·"다시 누르면 바꿀 수 있습니다")가 4.45:1 로 떨어져 AA 에
   0.05 모자란다(실측). 대비를 맞추려고 색을 한 번만 쓰는 예외값으로 깎느니, 본문과
   같은 바탕을 쓰고 상태는 다른 신호로 말하는 편이 낫다. */
.dropzone.has-file { border-style: solid; border-color: var(--accent); background: var(--surface); }
.dropzone.has-file .dropzone-ico { color: var(--accent); }
.dropzone-name {
  /* 고른 파일 이름은 이 영역에서 **가장 중요한 글자**다. 상속에 맡기면 주변 폼 라벨을
     따라 흐려진다(실측 4.45:1 — AA 미달). 본문색으로 못박는다. */
  color: var(--text);
  font-size: 13px; font-weight: 600; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 끄는 동안. :hover 도 같이 밝혀 "누를 수 있다"를 알린다. */
.dropzone.is-over, .dropzone:hover { border-color: var(--accent); background: var(--accent-soft); }
.dropzone:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- 항목 고르기 접기 (백업 모달) ----------
   백업할 항목은 **기본이 전체**다. 접어 둔 것과 고르지 않은 것은 다르므로 접혀 있어도
   체크는 살아 있고 폼에 그대로 실린다(<details> 의 성질 — 이 파일 아래 .card-fold 주석 참조).

   요약 줄이 상태를 말한다. 스크립트 없이 :has() 로 판정하므로 **접힌 채로도 참말**을 한다 —
   상태를 못 보여 주면 결국 확인하려고 전부 펼치게 되고, 그러면 접은 의미가 없다.
   disabled 는 세지 않는다(미구현 항목이라 애초에 담기지 않는다 — 그걸 '일부만'으로 세면
   아무것도 안 건드린 사람에게 거짓말이 된다). */
.pick-fold { border-top: 0; margin-top: 14px; padding-top: 0; }
.pick-fold > summary { padding: 8px 0; font-size: 13.5px; font-weight: 600; }
.pick-fold .pick-state { margin-left: auto; font-size: 12.5px; font-weight: 500; color: var(--text-muted); }
.pick-fold .pick-some { display: none; }
.pick-fold:has(input[type="checkbox"]:not(:checked):not(:disabled):not([data-opt-in])) .pick-all { display: none; }
.pick-fold:has(input[type="checkbox"]:not(:checked):not(:disabled):not([data-opt-in])) .pick-some { display: inline; color: var(--warn-fg); }
/* 항목이 13개까지 늘어나므로 줄 간격을 좁힌다 — 다만 **데스크톱에서만**.
   ⚠ 이 규칙은 모바일 터치 크기(.checkline 44px)보다 선택자가 강해, 폭 조건 없이 두면
     좁은 화면의 손가락 과녁까지 30px 로 줄여 버린다. */
@media (min-width: 769px) {
  .pick-fold .checkline { min-height: 30px; }
}

/* ---------- 접히는 설정 카드 ----------
   <details>/<summary> 로 만든다 — JS 가 없어도 열리고 닫히며 키보드로 조작되고,
   **닫혀 있어도 안의 입력이 폼과 함께 전송된다**(접었다는 이유로 값이 빠지지 않는다).

   ★ 요약 줄(.sec-sum)이 이 패턴의 핵심이다. 지금 어떻게 설정돼 있는지가 접힌
     채로 보이지 않으면, 확인하려고 결국 전부 펼치게 되어 접기가 무의미해진다. */
.card-fold { padding: 0; overflow: hidden; }
.card-fold > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: 12px;
  padding: 16px 20px;
}
.card-fold > summary::-webkit-details-marker { display: none; }
.card-fold > summary:hover { background: var(--surface-2); }
.card-fold > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.card-fold[open] > summary { border-bottom: 1px solid var(--border); }
.sec-head { min-width: 0; flex: 1 1 auto; }
.sec-title { font-size: 15px; font-weight: 640; letter-spacing: -0.01em; }
.sec-desc { font-size: 12.5px; color: var(--muted); margin-top: 3px; }
/* 현재 설정값. 접힌 카드에서 이 한 줄이 곧 내용이다. */
.sec-sum {
  flex: 0 0 auto; max-width: 45%;
  font-size: 12.5px; font-weight: 580; color: var(--accent);
  background: var(--accent-soft); border: 1px solid var(--accent-border);
  border-radius: var(--r-pill); padding: 3px 11px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sec-chev { flex: 0 0 auto; color: var(--text-subtle); transition: transform 0.15s ease; }
/* ★ 자기 머리(> summary)의 화살표만 돌린다. 종전 `.card-fold[open] .sec-chev` 는 자손 선택자라, 카드 안에 접기가 또
   있으면(채널별 개별 설정 · 적용 결과 미리보기) 카드가 열린 동안 **안쪽 화살표까지 뒤집혀** 접힌 접기가 '열림(^)'으로
   보였다(실측 2026-10-20). 그래서 안쪽 접기들이 sec-chev 를 피해 따로 화살표를 만들어 왔다(schedule_editor 의 exc-chev). */
.card-fold[open] > summary .sec-chev { transform: rotate(180deg); }
.card-fold > .card-body { padding: 18px 20px 20px; }
.card-fold > .card-body > :first-child { margin-top: 0; }

/* 카드 안의 하위 묶음(채널 하나, 트리거 하나)을 시각적으로 가른다. */
.sec-group + .sec-group { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); }
.sec-group > h3 { margin: 0 0 10px; font-size: 13.5px; font-weight: 620; }

@media (max-width: 768px) {
  /* 좁은 화면에서는 제목과 요약이 한 줄을 다투다 둘 다 잘린다 — 요약을 아래로 내린다.
     ★ 접기 화살표도 **요약과 같은 줄**로 내려 오른쪽 끝에 세운다.
       종전에는 화살표만 첫 줄(제목 옆)에 남아, 설명이 한 줄인 카드와 두 줄인 카드에서
       높이가 달랐다 — 카드마다 화살표가 다른 자리에 있어 어디를 눌러야 접히는지
       매번 찾아야 했다. 요약 줄 오른쪽 끝은 카드가 몇 줄이든 같은 자리다.
     ⚠ 오른쪽 '끝'이지 요약 알약 바로 옆이 아니다 — 알약 폭은 글자 수에 따라
       달라져서, 거기 붙이면 자리가 다시 카드마다 달라진다. */
  .card-fold > summary { flex-wrap: wrap; padding: 14px 16px; }
  /* 요약이 있는 카드에서만 제목이 한 줄을 통째로 차지해 아래로 밀어낸다. */
  .card-fold > summary:has(.sec-sum) .sec-head { flex: 1 1 100%; }
  .sec-sum { order: 2; flex: 0 1 auto; max-width: calc(100% - 36px); margin-top: 8px; }
  .sec-chev { order: 3; margin-top: 8px; margin-left: auto; }
  .card-fold > .card-body { padding: 16px; }
}

/* ---------- 빈 상태 ---------- */

/* ---------- 상단바 미완료 알림(종) ----------
   어느 화면에서나 보여야 하는 것이라 상단바에 둔다. 종은 **0 일 때도 보인다** —
   사라지면 '알림이 없다'와 '이 기능이 없다'를 구분할 수 없다. */
.bell { position: relative; flex: 0 0 auto; }
.bell-btn { position: relative; }
/* 건수 말풍선. 종 위로 겹쳐 올리고 z-index 로 무엇에도 가리지 않게 한다 —
   가려지면 '지금 답해야 할 것이 있다'는 유일한 신호가 사라진다. */
.bell-badge {
  position: absolute; top: -3px; right: -3px; z-index: 2;
  min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: var(--r-pill); border: 2px solid var(--surface);
  background: var(--danger-fg); color: #fff;
  font-size: 10.5px; font-weight: 700; line-height: 13px; text-align: center;
}
.bell.has-pending .bell-btn { color: var(--danger-fg); }
.bell-pop {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 70;
  width: min(340px, calc(100vw - 24px));
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-md); box-shadow: var(--sh-lg); overflow: hidden;
}
.bell-head { padding: 11px 14px; border-bottom: 1px solid var(--border); font-size: 12.5px; font-weight: 650; }
.bell-empty { margin: 0; padding: 18px 14px; text-align: center; color: var(--text-muted); font-size: 13px; }
.bell-item {
  display: flex; align-items: center; gap: 10px; padding: 10px 14px;
  border-bottom: 1px solid var(--border); color: var(--text); text-decoration: none; font-size: 13px;
}
.bell-item:last-of-type { border-bottom: 0; }
.bell-item:hover { background: var(--surface-2); }
.bell-preview { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bell-time { flex: 0 0 auto; color: var(--danger-fg); font-size: 12px; font-weight: 600; }
.bell-all {
  display: block; padding: 10px 14px; text-align: center;
  border-top: 1px solid var(--border); color: var(--accent);
  font-size: 12.5px; font-weight: 600; text-decoration: none;
}
.bell-all:hover { background: var(--surface-2); }
@media (max-width: 768px) {
  /* 좁은 화면에서는 종에 매달지 않고 **화면에** 맞춘다. 종이 오른쪽 끝에 있어서
     거기 매달면 340px 짜리 목록의 왼쪽이 화면 밖으로 나간다(실측 390px). */
  .bell-pop {
    position: fixed; left: 8px; right: 8px; width: auto;
    top: calc(var(--header-h) + 6px);
  }
}

/* ---------- 조직 선택 팝업 ----------
   조직을 골라야만 볼 수 있는 화면에 조직 없이 들어왔을 때 바로 뜬다(main.hbs 주석). */
.modal-sm { max-width: 400px; }
.modal-head h2 .icon { color: var(--text-muted); margin-right: 6px; vertical-align: -4px; }
.org-pick { list-style: none; margin: 14px 0 0; padding: 0; display: grid; gap: 6px; }
.org-pick-item {
  width: 100%; display: flex; align-items: center; gap: 10px;
  padding: 12px 14px; border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--surface); color: var(--text);
  font-size: 14px; font-weight: 600; text-align: left; cursor: pointer;
}
.org-pick-item:hover { border-color: var(--accent); background: var(--accent-soft); }
.org-pick-item > .icon { color: var(--text-muted); flex: 0 0 auto; }
.org-pick-item > span { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.org-pick-item:hover > .icon { color: var(--accent); }
.org-pick-go { color: var(--text-subtle); }
@media (max-width: 768px) { .org-pick-item { min-height: 48px; } }

/* 조직 셀렉터 앞 아이콘 — 상단바에 셀렉트가 이것 하나뿐이라 아이콘이 없으면
   '무엇을 고르는 칸'인지 눌러 봐야 안다. 셀렉트 위에 겹쳐 놓고 글자를 그만큼 민다. */
.org-switch { position: relative; display: flex; align-items: center; }
.org-switch-ico {
  position: absolute; left: 10px; width: 16px; height: 16px;
  color: var(--text-muted); pointer-events: none;
}
.org-switch-select { padding-left: 32px; }

/* ---------- 길 안내 목록 ----------
   "그 기능은 저 화면에 있다"를 한 줄씩 세운다(백업·복원 화면). 링크 한 줄 + 설명 한 줄이
   한 항목이고, 항목 전체가 눌리는 면이라 좁은 화면에서도 겨냥할 곳이 넉넉하다. */
.link-list { list-style: none; margin: 0; padding: 0; }
.link-list > li + li { margin-top: 6px; border-top: 1px solid var(--border); padding-top: 6px; }
.link-list > li > a {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; margin: 0 -12px;
  border-radius: var(--r-sm);
  color: var(--text); font-weight: 600; text-decoration: none;
}
.link-list > li > a:hover { background: var(--surface-2); }
.link-list > li > a > .icon { color: var(--text-muted); flex: 0 0 auto; }
/* 화살표는 오른쪽 끝으로 민다 — 항목 이름 길이가 제각각이라 붙여 두면 줄이 들쭉날쭉해진다. */
.link-list .link-list-go { margin-left: auto; color: var(--text-subtle); }
.link-list > li > .hint { margin: 0 0 4px; }

/* ---------- 레코드 카드 목록 ----------
   행 하나가 곧 "대상 하나"(계정·조직·채널·지점)인 표를 대신한다.

   왜 표를 버렸나. 이 표들은 컬럼이 7~8개인데 **서로 비교하려고 보는 값이 아니라
   그 대상의 속성**이다. 세로로 훑을 일이 없는데 가로만 넘쳤다 — 계정 관리는
   편집 패널이 771px 라 데스크톱에서도 넘쳤고(실측 23px), 390 에서는 453px 였다.
   가로 스크롤은 열이 화면 밖으로 나가는 순간 그 열이 **없는 것과 같아진다.**

   카드는 라벨을 값 옆에 늘 데리고 다녀 헤더 행이 필요 없고, 좁아지면 한 줄로
   접히기만 하면 된다. 그래서 폭에 따라 칸 수만 바뀌고 가로 스크롤은 생기지 않는다. */
.rec-list {
  list-style: none; margin: 12px 0 0; padding: 0;
  display: grid; gap: 12px;
  /* min(100%, …) 이 없으면 좁은 칸에서 최소폭이 컨테이너를 넘겨 가로 스크롤이 되살아난다.
     300px 인 이유: 768 폭에서 2단이 서고(300×2+간격), 390 에서는 min(100%) 가 걸려 1단이 된다.
     360 이었을 때는 태블릿에서도 1단이라 카드가 쓸데없이 넓었다. */
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
}
.rec-card {
  min-width: 0; display: flex; flex-direction: column; gap: 10px;
  padding: 13px 15px; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--r-md);
}
/* 목록 패널 안에서는 카드가 이미 패널 배경 위에 있으므로 한 겹 덜 쌓는다. */
.list-panel-body .rec-card, .split-pane-body .rec-card { background: var(--surface); }
.rec-card.is-inactive { background: var(--surface-2); }
.rec-card.is-inactive .rec-title { color: var(--text-muted); }
.rec-card.is-active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }

/* ---------- 카드를 눌러 연다 ----------
   종전에는 [수정]·[복제]·[백업]·[삭제] 네 단추가 같은 크기로 나란히 서서, 한 칸 잘못
   누르면 조직이 통째로 사라졌다. 가장 흔한 동작(수정)은 **카드 자체**에 주고, 삭제는
   수정 팝업 안으로 옮겼다(채널 연동에서 먼저 쓴 규약).

   ★ 카드를 통째로 `<a>` 로 감싸지 않는다 — 안에 다른 링크가 있고, 중첩 링크는 HTML 이
     허용하지 않는다(브라우저마다 다르게 고쳐 놓는다). 제목의 링크를 **카드 크기로 늘린다**.
     진짜 링크라 키보드 이동·새 탭으로 열기·주소 복사가 모두 그대로 산다.
   ⚠ 늘린 링크가 아이콘 단추들을 덮는다 — 그쪽을 한 겹 위로 올려야 누를 수 있다. */
.rec-card:has(.rec-open),
.tool-card:has(.rec-open) { position: relative; }
.rec-card:has(.rec-open):hover,
.tool-card:has(.rec-open):hover { border-color: var(--border-strong); background: var(--surface-2); }
.rec-card:has(.rec-open:focus-visible),
.tool-card:has(.rec-open:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.rec-open { color: inherit; text-decoration: none; }
.rec-open::after { content: ''; position: absolute; inset: 0; border-radius: inherit; }
.rec-card > .rec-head > .rec-actions { position: relative; z-index: 1; }
/* 카드 위에 남은 조작(가져오기 소스의 [실행])만 늘린 링크 위로 올린다.
   ⚠ 한때 주소 줄도 함께 올렸다 — "통째로 고르는 자리라 드래그를 살려야 한다"고 봤는데
     **확인해 보니 그건 알림 화면의 `.conn-url` 이었다**(`user-select: all`). 여기 주소는
     평범한 글이고, 올려 두면 카드 한가운데가 눌리지 않는 죽은 띠가 된다. 되돌렸다. */
.tool-card .tool-card-actions { position: relative; z-index: 1; }

.rec-head { display: flex; align-items: flex-start; gap: 10px; }
.rec-title {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  font-size: 14.5px; font-weight: 650; overflow-wrap: anywhere;
}
.rec-actions { flex: 0 0 auto; display: flex; flex-wrap: wrap; gap: 6px; }
.rec-actions form { display: inline; }
/* 일괄 선택 체크박스(대화 기록 관리) — 제목 왼쪽에 둔다. */
.rec-pick { flex: 0 0 auto; margin-top: 2px; }

/* 라벨/값 2열. 라벨은 제 폭만 쓰고 값이 남는 칸을 다 쓴다.
   display:contents 로 감싼 div 를 걷어내야 두 열이 카드 전체에서 나란히 선다. */
.rec-fields { margin: 0; display: grid; grid-template-columns: minmax(0, max-content) minmax(0, 1fr); gap: 6px 14px; }
.rec-fields > div { display: contents; }
.rec-fields dt { color: var(--text-muted); font-size: 12.5px; line-height: 1.5; }
.rec-fields dd { margin: 0; min-width: 0; font-size: 13px; line-height: 1.5; overflow-wrap: anywhere; }
/* 값이 길어 두 열이 좁아지면(웹훅 URL·경로) 라벨을 값 위로 올린다. */
.rec-fields dd > code { font-size: 12px; }
/* 값이 긴 항목(웹훅 URL)은 줄 전체를 쓴다.
   ⚠ 감싼 div 가 display:contents 인 채로 grid-column 을 주면 아무 일도 없다 —
     그 div 는 격자 칸이 아니다. 여기서만 다시 박스로 만들어 두 줄로 쌓는다. */
.rec-fields > div.rec-full { display: block; grid-column: 1 / -1; }
.rec-fields > div.rec-full dt { margin-bottom: 3px; }
.rec-card > .warn-box { margin: 0; }

@media (max-width: 480px) {
  /* ★ 종전에는 여기서 라벨을 값 **위로** 쌓았다. 값이 눌리지 않게 하려던 것인데, 항목이
     여섯이면 카드가 12줄이 되어 **한 화면에 계정 한 건**만 보였다(실측 390px).
     라벨은 짧고 값도 짧다 — 쌓을 이유가 없었다.

     대신 라벨 열을 고정 폭으로 못박는다. max-content 로 두면 '마지막 로그인' 같은 긴
     라벨 하나가 열 전체를 넓혀 값 칸을 짓누른다. 6.5em 이면 그 라벨이 한 줄에 들어가고
     (12.5px × 6자), 남는 폭은 전부 값이 쓴다. 넘치는 값은 dd 의 overflow-wrap 이 접는다. */
  .rec-fields { grid-template-columns: 6.5em minmax(0, 1fr); gap: 5px 10px; }
}
.empty-state .icon { width: 26px; height: 26px; margin-bottom: 10px; }
/* ---------- U4: 고객 응대 콘솔 ---------- */


/* 남은 높이를 전부 차지하고, 스크롤은 이 안에서만 일어난다.
   min-height:0 이 없으면 flex/grid 자식이 내용 크기 아래로 줄어들지 않아
   컨테이너를 밀어내고 **페이지 전체가 스크롤된다**(이 화면에서 실제로 발생했던 문제). */
.inbox-layout {
  /* 위 여백이 없다 — 이 화면에는 제목 줄이 없어 띄울 것이 없다(inbox.hbs 주석).
     세로 한 줄이 곧 읽을 수 있는 대화 한 건이라 16px 도 그냥 두지 않는다. */
  display: grid; grid-template-columns: 320px 1fr; gap: 16px;
  flex: 1;
  min-height: 0;
}

/* 좌: 리스트 */
.conv-list {
  /* 탭 줄 높이. 그룹 머리말이 이만큼 아래에 서야 둘이 겹치지 않는다(.conv-tabs 주석). */
  --conv-tabs-h: 48px;
  /* 정렬 줄 높이. 탭 줄과 **같은 이유**로 선언한다(.conv-sort 주석).
     ★ 기본은 0 이다 — 지금 그 줄은 감춰져 있고(inbox.js SORT_PICKER_SHOWN),
       줄 없이 높이만 남으면 탭과 그룹 머리말 사이가 그만큼 빈다. */
  --conv-sort-h: 0px;
  /* 검색·필터 줄 높이. 줄을 그린 목록에서만 값을 준다(.conv-list.has-filter). */
  --conv-filter-h: 0px;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: 12px; overflow-y: auto;
  min-height: 0; /* 그리드 자식이 내용만큼 늘어나 페이지를 밀지 않도록 */
}
/* ---------- 지식 목록의 출처 탭 (전체 / 외부 동기화 / 직접 입력) ----------
   상단 2차 탭(.stab)과 다른 모양을 쓴다 — 저건 화면을 옮기는 탭이고 이건 **같은 화면 안에서
   목록을 거르는** 것이다. 둘이 같은 생김새면 어느 쪽이 화면을 바꾸는지 눌러 봐야 안다. */
/* 출처 탭 줄 — 왼쪽은 탭, 오른쪽 끝은 목록 동작(.toolbar-actions).
   동작을 검색칸 옆에 두면 "검색을 좁히는 수단"처럼 읽힌다. 목록 전체를 다루는
   동작이라 목록의 머리줄에 세운다.
   ⚠ 좁은 화면 배치는 새로 만들지 않는다 — `.toolbar-actions` 의 768 규칙이
     그대로 걸려 아래 줄로 내려가 오른쪽 정렬된다(규칙이 둘로 갈라지지 않게). */
.origin-row {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin: 12px 0 4px;
  /* ⚠ `flex: 0 0 auto` 가 없으면 **좁은 화면에서 이 줄이 통째로 짜부라진다.**
     이 카드는 세로 flex 이고, 아래 좁은 화면 규칙이 이 줄을 가로 스크롤 상자로 만든다.
     스크롤 상자가 되는 순간 자동 최소 높이(= 내용 높이)가 0 으로 풀려서, 목록에 자리를
     내주려는 flex 가 이 줄을 눌러 버린다(실측 375px: 높이 80 → 2.9px, 탭이 잘렸다). */
  flex: 0 0 auto;
}
.origin-row > .origin-tabs { margin: 0; flex: 1 1 auto; min-width: 0; }
.origin-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0 4px; }
.origin-tab {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; border: 1px solid var(--border); border-radius: var(--r-pill);
  color: var(--text-muted); font-size: 12.5px; font-weight: 600; text-decoration: none;
}
.origin-tab:hover { border-color: var(--border-strong); color: var(--text); }
.origin-tab.is-active { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.origin-n { font-weight: 700; font-size: 11.5px; opacity: 0.85; }
@media (max-width: 768px) { .origin-tab { min-height: 36px; } }

/* ---------- 인박스 탭 (상담사 응대 / AI 응대) ----------
   목록 맨 위에 붙어 스크롤을 따라가지 않는다 — 어느 쪽을 보고 있는지는 목록을 아무리
   내려도 알아야 한다. 목록 자체가 스크롤 컨테이너라 sticky 가 그 안에서 걸린다. */
.conv-tabs {
  /* ★ 높이를 **선언한다**(측정하지 않는다). 아래 그룹 머리말도 sticky 라 둘이 같은
     top:0 에 겹치면 나중에 오는 그룹 머리말이 탭 줄을 덮는다(실측: 탭이 반쯤 잘려 보였다).
     그룹 머리말을 이 높이만큼 내려 세우려면 그 값이 어딘가에 **확정돼** 있어야 하는데,
     패딩·글자 크기에서 계산해 두면 어느 한쪽만 바뀌었을 때 조용히 어긋난다.
     그래서 여기서 높이를 정하고 --conv-tabs-h 로 같이 내보낸다. 둘은 한 쌍이다. */
  height: var(--conv-tabs-h);
  position: sticky; top: 0; z-index: 3;
  display: flex; align-items: center; gap: 4px; padding: 0 10px;
  background: var(--panel); border-bottom: 1px solid var(--border);
}
.conv-tab {
  flex: 1 1 0; min-width: 0;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 8px 10px; border: 0; border-radius: var(--r-sm);
  background: transparent; color: var(--text-muted);
  font-size: 13px; font-weight: 600; cursor: pointer;
}
.conv-tab:hover { background: var(--surface-2); color: var(--text); }
.conv-tab.is-active { background: var(--accent-soft); color: var(--accent); }
.conv-tab-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 진행중 건수. 0 이면 숨긴다 — 상시 0 이 떠 있으면 아무도 안 보게 된다. */
.conv-tab-count {
  flex: 0 0 auto; min-width: 20px; padding: 0 6px; border-radius: var(--r-pill);
  background: var(--accent); color: var(--on-accent);
  font-size: 11.5px; font-weight: 700; line-height: 18px; text-align: center;
}
.conv-tab:not(.is-active) .conv-tab-count { background: var(--surface-3); color: var(--text-muted); }
@media (max-width: 768px) {
  .conv-list { --conv-tabs-h: 56px; }
  .conv-tab { min-height: 42px; }
}

/* 목록 항목의 응답자 줄(상담사 응대 목록). 색이 아니라 모양 기호(○ ●)와 글자로 가른다 —
   '응답 안 함'만 점선 테두리로 한 번 더 눈에 띄게 한다. */
.conv-resp { margin-top: 3px; font-size: 11.5px; color: var(--text-muted); }
.conv-resp[hidden] { display: none; }
.conv-resp.is-none {
  display: inline-block; padding: 0 7px; border: 1px dashed var(--border-strong);
  border-radius: var(--r-pill); font-weight: 600;
}
.conv-resp.is-named { font-weight: 600; }

/* ---------- 목록 검색 · 답변자 필터 줄 ----------
   탭 아래에 붙는다. 세 줄(탭·필터·정렬)이 모두 sticky 라 **높이를 선언**해 서로의 자리를 만든다
   (.conv-tabs 주석). 색에 기대지 않는다 — 조건이 걸린 상태는 지우기(×) 버튼이 나타나는 것으로 보이고,
   고른 값은 select 의 글자 그대로다. */
.conv-list.has-filter { --conv-filter-h: 46px; }
.conv-filter {
  height: var(--conv-filter-h);
  position: sticky; top: var(--conv-tabs-h); z-index: 2;
  display: flex; align-items: center; gap: 6px;
  padding: 0 10px; background: var(--panel); border-bottom: 1px solid var(--border);
}
.conv-filter-q { flex: 1 1 auto; min-width: 0; height: 30px; padding: 0 10px; font-size: 13px; }
/* 지우기 버튼은 우리 것(.conv-filter-clear) 하나만 둔다 — 브라우저 기본 ×가 함께 뜨면 둘이 겹쳐 보인다. */
.conv-filter-q::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.conv-filter-by {
  flex: 0 0 auto; max-width: 42%; height: 30px; padding: 0 8px; font-size: 12px; font-weight: 600;
  color: var(--text-muted); border-color: var(--border); text-overflow: ellipsis;
}
.conv-filter-by:hover { color: var(--text); }
.conv-filter-clear {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%;
  border: 1px solid var(--border); background: transparent; color: var(--text-muted);
  font-size: 16px; line-height: 1; cursor: pointer;
}
.conv-filter-clear:hover { color: var(--text); border-color: var(--border-strong); }
.conv-filter-clear[hidden] { display: none; }
/* '다른 계정' 체크박스 목록 — 흐름 안의 블록이다(sticky 아님: 높이가 계정 수에 따라 변한다). */
.conv-filter-accounts { padding: 8px 12px 10px; border-bottom: 1px solid var(--border); background: var(--panel); }
.conv-filter-accounts[hidden] { display: none; }
.conv-acc-title { font-size: 12px; font-weight: 700; color: var(--text-muted); }
.conv-acc-hint, .conv-acc-empty { margin: 2px 0 6px; font-size: 11.5px; color: var(--text-muted); }
.conv-acc-list { display: flex; flex-direction: column; gap: 2px; max-height: 168px; overflow-y: auto; }
.conv-acc { display: flex; align-items: center; gap: 8px; padding: 4px 2px; font-size: 13px; cursor: pointer; }
.conv-acc input { flex: 0 0 auto; }
.conv-acc-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.conv-acc-n { flex: 0 0 auto; font-size: 11.5px; color: var(--text-muted); }
@media (max-width: 768px) {
  .conv-list.has-filter { --conv-filter-h: 54px; }
  .conv-filter-q, .conv-filter-by { height: 38px; }
}

/* ---------- 목록 정렬 줄 ----------
   탭(누가 응대하는가) 아래, 그룹 머리말 위. 한 번 정하면 계속 쓰는 값이라 화면을
   차지하지 않게 한 줄로 두고, 무엇으로 서 있는지는 **늘 보이게** sticky 로 붙인다. */
/* 줄을 실제로 그린 목록에서만 높이를 준다(JS 가 이 클래스를 붙인다). */
.conv-list.has-sort { --conv-sort-h: 34px; }
.conv-sort {
  height: var(--conv-sort-h);
  position: sticky; top: calc(var(--conv-tabs-h) + var(--conv-filter-h, 0px)); z-index: 2;
  display: flex; align-items: center; justify-content: flex-end; gap: 6px;
  padding: 0 12px; background: var(--panel); border-bottom: 1px solid var(--border);
}
.conv-sort-label { font-size: 11.5px; color: var(--text-muted); flex: 0 0 auto; }
/* 목록 안의 보조 컨트롤이라 기본 입력칸보다 한 치수 작다. */
.conv-sort select {
  height: 26px; padding: 0 8px; font-size: 12px; font-weight: 600;
  color: var(--text-muted); border-color: var(--border);
  max-width: 60%; text-overflow: ellipsis;
}
.conv-sort select:hover { color: var(--text); }

.conv-group { border-bottom: 1px solid var(--border); }
.conv-group > summary {
  display: flex; justify-content: space-between; align-items: center;
  padding: 11px 14px; cursor: pointer; font-size: 12.5px; font-weight: 620;
  color: var(--text-muted); list-style: none;
  /* 탭 줄 + 정렬 줄 **아래**에 선다. top:0 이면 스크롤할 때 그 둘을 덮는다.
     세 줄이 모두 sticky 라 높이를 선언해 서로의 자리를 만든다 — 계산해 두면
     한쪽만 바뀌었을 때 조용히 겹친다(.conv-tabs 주석의 그 사고). */
  position: sticky; z-index: 1; background: var(--panel);
  top: calc(var(--conv-tabs-h, 0px) + var(--conv-filter-h, 0px) + var(--conv-sort-h, 0px));
}
/* 펼쳐져 있을 때만 아래 선을 긋는다.
   ★ 이 줄은 sticky 라 목록이 그 **밑으로 지나간다.** 선이 없으면 지나가다 잘린
     글자가 머리줄에 붙은 것처럼 보여, 어디까지가 머리줄인지 알 수 없다(실측 화면).
   ⚠ 접혀 있을 때는 긋지 않는다 — .conv-group 자체의 아래 선과 겹쳐 두 줄이 된다. */
.conv-group[open] > summary { border-bottom: 1px solid var(--border); }
.conv-group > summary:hover { color: var(--text); }
.conv-group > summary::-webkit-details-marker { display: none; }
.group-count {
  background: var(--accent-soft); color: var(--accent); border-radius: 999px;
  padding: 1px 8px; font-size: 12px;
}
/* 미배선 설정 섹션 — 잠갔다는 것이 한눈에 보여야 한다(경고문만으론 지나친다). */
.locked-fields { border: 0; margin: 0; padding: 0; opacity: 0.55; }
.locked-fields legend { display: none; }

.group-empty { color: var(--muted); font-size: 12px; padding: 6px 14px 12px; margin: 0; }
/* 그룹 하단 — 조회 한도 표시 + 더보기. 스크롤을 다 내린 바로 그 지점에 둔다. */
.group-footer {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 8px 14px 12px;
}
.group-shown { color: var(--muted); font-size: 12px; }
.group-more {
  font-size: 12px; padding: 6px 12px; min-height: 32px; border: 1px solid var(--border);
  border-radius: var(--r-sm); background: var(--panel); color: var(--text); cursor: pointer;
}
.group-more:hover:not(:disabled) { background: var(--surface-2); }
.group-more:disabled { opacity: 0.5; cursor: default; }

/* ★ 시각 계층이 뒤집혀 있었다 — **이름이 12px 흐린 글자**, 미리보기가 13px 진한
   글자였다. 목록에서 먼저 읽어야 하는 것은 누구와의 대화인가다. */
.conv-item {
  position: relative; display: block; padding: 11px 14px 11px 16px;
  border-top: 1px solid var(--border);
  text-decoration: none; color: var(--text);
}
.conv-item:hover { background: var(--surface-2); }
.conv-item.is-selected { background: var(--accent-soft); }
/* 선택된 대화는 왼쪽 띠로 표시한다 — 배경색만으로는 스크롤 중에 잘 안 보인다. */
.conv-item.is-selected::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--accent);
}
.conv-top { display: flex; align-items: center; gap: 8px; min-width: 0; }
.conv-user {
  flex: 1 1 auto; min-width: 0;
  font-size: 13.5px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.conv-wait { margin-left: auto; flex: 0 0 auto; font-size: 11.5px; color: var(--warn-fg); }
/* 이름을 안 붙인 대화의 식별용 짧은 키 — 이름이 아니라 곁다리라는 게 보여야 한다. */
.conv-key, .room-name .conv-key {
  margin-left: 6px; padding: 1px 6px; border-radius: var(--r-sm);
  background: var(--surface-2); color: var(--text-muted);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px; font-weight: 500; vertical-align: middle;
}
/* 미리보기는 보조 정보 — 두 줄까지 보여 준다(한 줄이면 무슨 대화인지 가늠이 안 된다). */
.conv-preview {
  font-size: 12.5px; margin-top: 4px; color: var(--muted); line-height: 1.5;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* 마지막으로 말한 사람. 미리보기 **안**에 인라인으로 둔다 — 별도 줄로 빼면 항목이
   한 줄씩 길어져 한 화면에 담기는 대화가 줄고, 두 줄 말줄임(-webkit-line-clamp)도 깨진다. */
.conv-who {
  display: inline; margin-right: 5px;
  font-size: 11.5px; font-weight: 700; color: var(--text-subtle);
}
.conv-who::after { content: '·'; margin-left: 5px; font-weight: 400; }
/* 고객이 마지막 = 우리가 답할 차례. 셋 중 이것만 눈에 걸리면 된다. */
.conv-who.is-customer { color: var(--warn-fg); }
.conv-who.is-agent { color: var(--accent); }
/* 채널 아이콘 배지: 발화 채널에 맞는 아이콘·브랜드색. */
/* ★ flex:1 0 auto 였다 — 20px 고정 배지가 남는 폭을 먹고 늘어났다. */
.ch-badge {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 5px;
  flex-shrink: 0;
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
  background: var(--surface-2);
  color: var(--muted);
}
/* 글자 배지 폴백(아이콘 파일이 없을 때). 액센트를 쓰지 않는다 —
   그 색은 활성 상태를 뜻하므로 채널 목록에서 웹챗만 선택된 것처럼 보인다. */
.ch-badge--webchat { background: var(--surface-3); color: var(--text); }
.ch-badge--kakao { background: var(--brand-kakao); color: var(--brand-kakao-fg); }
.ch-badge--instagram { background: var(--brand-instagram); color: var(--brand-instagram-fg); }
.ch-badge--facebook { background: var(--brand-meta); color: var(--on-accent); }
.ch-badge--wechat { background: var(--brand-wechat); color: var(--on-accent); }
.ch-badge--naver_talktalk { background: var(--brand-naver); color: var(--on-accent); }

/* ---------- 상담 템플릿 설정 ---------- */
.tpl-list { list-style: none; margin: 12px 0 0; padding: 0; }
.tpl-item {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 12px; border: 1px solid var(--border); border-radius: 8px;
  margin-bottom: 8px; background: var(--panel);
}
.tpl-item.is-dragging { opacity: 0.5; }
.tpl-drag { cursor: grab; color: var(--muted); font-size: 16px; line-height: 1.6; user-select: none; flex-shrink: 0; }
.tpl-main { flex: 1; min-width: 0; }
.tpl-head { display: flex; align-items: center; gap: 6px; }
.tpl-name { font-weight: 600; }
.tpl-content {
  font-size: 12.5px; color: var(--muted); margin-top: 3px;
  white-space: pre-wrap; word-break: break-word;
  max-height: 3.4em; overflow: hidden;
}
.tpl-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.tpl-locked { white-space: nowrap; }

/* on/off 스위치 */
.switch { position: relative; display: inline-block; width: 38px; height: 22px; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch-track {
  position: absolute; inset: 0; cursor: pointer; background: var(--border-strong);
  border-radius: 999px; transition: background 0.15s;
}
.switch-track::before {
  content: ''; position: absolute; height: 16px; width: 16px; left: 3px; top: 3px;
  background: var(--panel); border-radius: 50%; transition: transform 0.15s;
}
.switch input:checked + .switch-track { background: var(--accent); }
.switch input:checked + .switch-track::before { transform: translateX(16px); }

/* 채널 아이콘 이미지(views/imgs). 배지와 같은 크기로 맞춘다. */
.ch-icon {
  width: 20px; height: 20px; border-radius: 5px; flex-shrink: 0;
  object-fit: contain; vertical-align: middle; display: inline-block;
}

/* 채널 연동 목록: 아이콘 + 채널명 */
.channel-cell { display: inline-flex; align-items: center; gap: 7px; }
.channel-name { font-weight: 600; }

/* 우: 채팅방 */
.conv-room {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0; /* 위와 동일 — 타임라인이 넘칠 때 방 자체가 커지지 않게 */
  overflow: hidden;
}
/* 아무 대화도 안 골랐을 때. 가운데에 한 줄만 있으면 비어 있는 건지 고장인지 모른다. */
/* 고른 대화를 불러오는 동안. 자리를 .room-empty 와 똑같이 잡아 두 상태가 오갈 때
   글자가 튀지 않게 한다(둘 다 방 한가운데 한 줄이다). */
.room-loading {
  margin: auto; max-width: 320px; text-align: center;
  color: var(--text-muted); font-size: 13.5px; line-height: 1.6;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
.room-loading p { margin: 0; }
.room-spinner {
  width: 20px; height: 20px; border-radius: 50%;
  border: 2px solid var(--border-strong); border-top-color: var(--accent);
  animation: room-spin 0.7s linear infinite;
}
@keyframes room-spin { to { transform: rotate(360deg); } }
/* 움직임을 줄여 달라고 한 사람에게는 돌리지 않는다 — 색만으로도 '진행 중'은 읽힌다. */
@media (prefers-reduced-motion: reduce) { .room-spinner { animation: none; } }

.room-empty {
  margin: auto; max-width: 320px; text-align: center;
  color: var(--text-muted); font-size: 13.5px; line-height: 1.6;
}
.room-head {
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--border);
}
.room-who {flex: 1 1 100%;display: flex;align-items: center;gap: 8px;font-size: 14px;min-width: 0;}
.room-tools {flex: 1 0 auto;white-space: nowrap;display: flex;align-items: center;gap: 8px;}

/* 채팅방 이름 + 편집(연필) — 연필은 좌측 상단에 낮은 opacity 로 편집 가능함을 암시 */
.room-name-wrap {flex: 1 1 100%;display: flex;align-items: center;gap: 6px;min-width: 0;}
.room-name {
  font-size: 15px; font-weight: 650; letter-spacing: -0.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.room-name.is-editable { cursor: pointer; }
.room-name.is-editable:hover { text-decoration: underline dotted; }
.room-name-edit {
  order: -1; /* 이름 왼쪽에 배치 */
  background: transparent; border: 0; cursor: pointer; padding: 0 2px;
  font-size: 13px; line-height: 1; color: var(--muted);
  opacity: 0.35; transition: opacity 0.15s;
}
.room-name-wrap:hover .room-name-edit,
.room-name-edit:hover,
.room-name-edit:focus { opacity: 1; }
.room-name-input {
  flex: 1 1 100%;
  height: 35px;
  padding: 5px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 14px;
  min-width: 180px;
}
.room-name-btn {
  height: 35px;
  flex: 1 0 auto;
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--panel);
  cursor: pointer;
  font-size: 12.5px;
}
.room-name-btn.primary { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.mode-switch { display: inline-flex; align-items: center; gap: 6px; }
.mode-switch-label { font-size: 12.5px; color: var(--muted); min-width: 22px; text-align: right; }
.handoff-banner {
  flex-shrink: 0;
  background: var(--warn-bg); color: var(--warn-fg); border-bottom: 1px solid var(--warn-border);
  padding: 9px 16px; font-size: 13px;
}

/* AI 응대 일시중지 배너 — 핸드오프 배너보다 강한 색으로, 지금 결정해야 할 것임을 드러낸다 */
.ai-paused-banner {
  flex-shrink: 0;
  background: var(--info-bg); color: var(--info-fg); border-bottom: 1px solid var(--info-border);
  padding: 10px 16px; font-size: 13px;
}
.ai-paused-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.ai-paused-text { font-weight: 600; }
/* 남은 시간은 보조 정보 — 문구를 밀어내지 않게 작고 흐리게 */
.ai-paused-left { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.ai-paused-actions { display: flex; gap: 8px; margin-top: 8px; }
.ai-held {
  margin-top: 10px; padding-top: 9px; border-top: 1px dashed var(--info-border);
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.ai-held-text { font-size: 12.5px; }
/* 일시중지가 풀린 뒤에도 남는 독립 행 — 배너 안에 있을 때와 달리 자기 배경을 갖는다 */
.ai-held-standalone {
  flex-shrink: 0; margin-top: 0; padding: 10px 16px; border-top: none;
  background: var(--warn-bg); color: var(--warn-fg); border-bottom: 1px solid var(--warn-border);
}
/* 예약이 이미 실행된 경우의 경고 — 줄을 통째로 차지해 놓치지 않게 한다 */
.ai-held-warn { flex-basis: 100%; margin-top: 6px; font-size: 12px; color: var(--warn-fg); }

/* 본문: 좌측 메인(타임라인+입력) + 우측 템플릿 패널 */
.room-body { flex: 1; min-height: 0; display: flex; }
.room-main {
  flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column;
  position: relative; /* 새 메시지 알림 버튼의 기준 */
}
/* "새로운 상담내용 확인하기" — 입력창 위에 떠서 알린다 */
.new-msg-pill {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: 92px; z-index: 5;
  background: var(--accent); color: var(--on-accent); border: 0; border-radius: 999px;
  padding: 7px 16px; font-size: 13px; cursor: pointer;
  box-shadow: var(--sh-md);
}
.new-msg-pill::before { content: '↓ '; }

/* ---------- AI 가 답을 쓰는 중 ----------
   고객이 말한 뒤 답이 저장되기까지는 화면이 그대로였다 — 보내고 나서 "전달이 된 건가"를
   알 수 없었다. 답이 올 자리에 점 셋을 세워 **지금 만들고 있다**를 보인다.
   ★ 점은 장식이다 — 무슨 일인지는 `aria-label` 의 글이 말한다(inbox.js). */
/* 크기는 **한 줄짜리 말풍선과 같게** 맞춘다. 점 셋만 담으면 56×28 짜리 알약이 나오는데,
   그건 말풍선이 아니라 딱지로 보여 답이 오는 자리라는 것이 읽히지 않았다.
   ★ 키우는 것은 **여백이지 점이 아니다** — 점을 키우면 그것만 도드라진다(사용자 지적).
   위아래 18px 은 한 줄 말풍선 높이(44px = 14px × 1.6 + 여백 20 + 테두리 2)를 점 6px 로
   맞춘 값이다: 6 + 18×2 + 2 = 44. */
.msg-typing .msg-text {
  display: flex; align-items: center; justify-content: center; gap: 5px;
  padding: 18px 26px;
}
.typing-dot {
  width: 6px; height: 6px; border-radius: 50%; background: var(--text-subtle);
  animation: typing-bounce 1.2s infinite ease-in-out;
}
.typing-dot:nth-child(2) { animation-delay: 0.15s; }
.typing-dot:nth-child(3) { animation-delay: 0.3s; }
@keyframes typing-bounce {
  0%, 60%, 100% { transform: translateY(0); opacity: 0.45; }
  30% { transform: translateY(-3px); opacity: 1; }
}
/* 움직임을 줄여 달라고 한 사람에게는 흔들지 않는다 — 있다는 사실만 보이면 된다. */
@media (prefers-reduced-motion: reduce) {
  .typing-dot { animation: none; opacity: 0.7; }
}
.new-msg-pill:hover { filter: brightness(1.05); }
.room-templates {
  width: 240px; flex-shrink: 0; border-left: 1px solid var(--border);
  display: flex; flex-direction: column; min-height: 0; background: var(--surface-2);
}
.tpl-panel-title {
  flex-shrink: 0; padding: 10px 14px; font-size: 12.5px; font-weight: 600;
  color: var(--muted); border-bottom: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
}
/* 템플릿 열기 버튼·시트 뒷배경·시트 닫기는 모바일 전용이다.
   데스크톱은 우측 패널이 상시 보이므로 열고 닫을 일이 없다. */
.tpl-toggle, .tpl-scrim, .tpl-panel-close { display: none; }
.tpl-panel-close {
  background: transparent; border: 0; color: var(--muted);
  font-size: 15px; line-height: 1; padding: 2px 4px; cursor: pointer;
}
.tpl-panel-empty { color: var(--text-muted); font-size: 12.5px; padding: 14px; margin: 0; line-height: 1.6; }
.tpl-panel-list { overflow-y: auto; min-height: 0; padding: 8px; display: flex; flex-direction: column; gap: 6px; }
.tpl-panel-item {
  text-align: left; background: var(--panel); border: 1px solid var(--border); border-radius: 8px;
  padding: 8px 10px; cursor: pointer; display: flex; flex-direction: column; gap: 2px;
}
.tpl-panel-item:hover { border-color: var(--accent); background: var(--accent-soft); }
.tpl-panel-item:disabled { opacity: 0.5; cursor: not-allowed; }
.tpl-panel-name { font-size: 13px; font-weight: 600; color: var(--text); }
.tpl-panel-preview {
  font-size: 12px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* flex 자식의 기본 min-height 는 auto 라서, 이 값이 없으면 내용만큼 늘어나
   overflow-y:auto 가 아예 동작하지 않는다(스크롤이 페이지로 넘어간다). */
/* 아래 여백만 넓다 — 마지막 말풍선이 입력칸에 바짝 붙어 한 덩어리로 읽혔다. */
.timeline {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: 16px 16px 28px; display: flex; flex-direction: column; gap: 12px;
}
/* 메시지 상한으로 앞부분이 잘렸다는 고지. 말풍선이 아니므로 폭 제한(.msg)을 받지 않고
   타임라인 전체 폭을 쓴다 — 놓치면 안 되는 정보다. */
.timeline-truncated {
  align-self: stretch; text-align: center; font-size: 12px; color: var(--muted);
  background: var(--warn-bg); border: 1px dashed var(--warn-border); border-radius: 8px; padding: 8px 12px;
}
/* 넓은 화면에서 말풍선이 좌우 끝으로 벌어지면 시선이 가로로 튄다. */
.timeline > * { max-width: 860px; }
.msg { max-width: min(68%, 640px); }
/* 날짜 구분선 — 말풍선이 아니므로 폭 제한을 받지 않고 줄 전체를 쓴다. */
.day-sep {
  align-self: stretch; display: flex; align-items: center; gap: 10px;
  font-size: 11.5px; font-weight: 600; color: var(--text-muted);
  margin: 4px 0 2px;
}
.day-sep::before, .day-sep::after {
  content: ""; flex: 1 1 auto; height: 1px; background: var(--border);
}
.msg-customer { align-self: flex-start; }
.msg-ai, .msg-agent { align-self: flex-end; }
/* 보낸 사람·시각은 보조 정보 — 말풍선과 같은 쪽으로 붙인다. */
.msg-meta { display: flex; gap: 8px; font-size: 11px; color: var(--text-muted); margin-bottom: 4px; }
.msg-ai .msg-meta, .msg-agent .msg-meta { justify-content: flex-end; }
/* 고객 화면(웹챗 말풍선)과 같은 텍스트를 상담사도 본다 — 줄바꿈 규칙을 위젯과 맞춘다.
   keep-all 로 한국어 어절이 중간에서 끊기지 않게 하고, 공백 없는 URL·CJK 는 anywhere 가 받는다. */
.msg-text {
  padding: 10px 13px; font-size: 14px; line-height: 1.6;
  white-space: pre-wrap; word-break: keep-all; overflow-wrap: anywhere;
}
/* 말풍선 한쪽 모서리를 죽여 방향(누가 보냈나)을 모양으로도 알린다. */
.msg-customer .msg-text {
  border-radius: 14px 14px 14px 4px;
  background: var(--surface-2); border: 1px solid var(--border);
}
.msg-ai .msg-text {
  border-radius: 14px 14px 4px 14px;
  background: var(--accent-soft); border: 1px solid var(--accent-border); color: var(--text);
}
.msg-agent .msg-text {
  border-radius: 14px 14px 4px 14px;
  background: var(--accent); color: var(--on-accent);
}
.msg-image-link { display: block; margin-top: 4px; width: fit-content; max-width: min(100%, 320px); }
/* 오른쪽 말풍선(AI·상담사)의 이미지는 오른쪽 끝에 붙인다. 위 규칙은 고객 이미지(왼쪽)만 보고 만들어져, 말풍선 칸이
   원본 이미지 폭만큼 최대 폭까지 넓어지면 줄어든 사진이 그 칸 왼쪽에 떠 있었다(2026-10-20 상담사 이미지 발송 실측). */
.msg-ai .msg-image-link, .msg-agent .msg-image-link,
.msg-ai .msg-image-note, .msg-agent .msg-image-note { margin-left: auto; }
.msg-image {
  display: block; max-width: 100%; max-height: 320px; height: auto;
  border-radius: 12px; border: 1px solid var(--border); background: var(--surface-2);
}
.msg-image { cursor: zoom-in; }
/* 이미지 확대 뷰어 — 화면 전체를 덮는다. 사진은 화면 안에 들어오게 줄이고 원본 비율을 지킨다. */
.img-viewer {
  /* height: var(--vh) — inset: 0 만으로는 모바일에서 주소줄이 숨겨진 높이가 기준이라 아래가 화면 밖에 남는다. */
  position: fixed; inset: 0; height: var(--vh); z-index: 1000;
  display: flex; align-items: center; justify-content: center; padding: 56px 24px 24px;
  box-sizing: border-box;
}
.img-viewer-backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.82); }
/* 상담사 이미지 보내기 모달 — 틀은 공용 .modal 이고 여기는 안쪽만 정한다.
   고른 칸은 테두리 굵기·✔ 글자로도 알린다(색만으로 구분하지 않는다). */
.img-send-box { width: min(640px, 92vw); }
.img-send-drop {
  position: relative; display: flex; align-items: center; gap: 12px; margin-top: 14px; padding: 14px 16px;
  border: 1.5px dashed var(--border-strong); border-radius: var(--r-lg); cursor: pointer; color: var(--muted);
}
.img-send-drop:hover, .img-send-drop:focus-within { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.img-send-drop .icon { width: 24px; height: 24px; }
.img-send-drop-text { display: flex; flex-direction: column; gap: 2px; }
.img-send-drop-text strong { color: var(--text); font-size: 13.5px; font-weight: 600; }
.img-send-file { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.img-send-subtitle { margin: 18px 0 8px; font-size: 12.5px; font-weight: 600; color: var(--muted); }
.img-send-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 8px; }
.img-send-empty { grid-column: 1 / -1; margin: 4px 0; }
.img-send-cell { position: relative; }
.img-send-item {
  display: block; width: 100%; aspect-ratio: 1; padding: 0; border: 1px solid var(--border); border-radius: var(--r-sm);
  overflow: hidden; background: var(--surface-2); cursor: pointer; position: relative;
}
.img-send-item img { width: 100%; height: 100%; object-fit: cover; display: block; }
.img-send-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.img-send-check {
  display: none; position: absolute; left: 6px; top: 6px; padding: 0 7px; border-radius: 999px;
  background: var(--accent); color: var(--on-accent); font-weight: 700; font-size: 12px; line-height: 20px;
}
.img-send-item.is-selected { box-shadow: 0 0 0 3px var(--accent); border-color: var(--accent); }
.img-send-item.is-selected .img-send-check { display: block; }
/* 칸 오른쪽 위 편집 단추 — 누르면 우클릭과 같은 메뉴가 뜬다. 마우스가 올라왔을 때만 보이고, 터치 화면은 늘 보인다. */
.img-send-more {
  position: absolute; right: 5px; top: 5px; width: 28px; height: 28px; border-radius: 999px; border: 0; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.55); color: #fff; cursor: pointer; opacity: 0; transition: opacity .12s;
}
.img-send-more .icon { width: 15px; height: 15px; }
.img-send-cell:hover .img-send-more, .img-send-more:focus-visible, .img-send-item:focus-visible + .img-send-more { opacity: 1; }
@media (hover: none) { .img-send-more { opacity: 1; } }
/* 즐겨찾기 표시 — 별 모양 글자로 알린다(색만으로 가르지 않는다). 고름 표시(✔, 왼쪽 위)와 겹치지 않게 왼쪽 아래. */
.img-send-fav {
  position: absolute; left: 6px; bottom: 6px; padding: 0 6px; border-radius: 999px;
  background: rgba(0, 0, 0, 0.6); color: #fff; font-size: 12px; line-height: 20px;
}
.img-send-name {
  margin-top: 4px; font-size: 12px; color: var(--text); line-height: 1.35;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 고른 이미지의 이름·내용 — 보내기 전 확인용. */
.img-send-picked {
  margin-top: 14px; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--surface-2); font-size: 13px; max-height: 140px; overflow-y: auto;
}
.img-send-picked-name { font-weight: 600; }
.img-send-picked-desc { white-space: pre-wrap; color: var(--text-muted); margin-top: 2px; overflow-wrap: anywhere; }
.img-send-picked-name + .img-send-picked-desc { margin-top: 4px; }
/* 이미지 메뉴 — 계정 메뉴와 같은 떠 있는 판. 모달(.modal) 안에 붙어 모달 위에 뜬다. */
.img-send-menu {
  position: fixed; z-index: 10; min-width: 190px; padding: 6px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md); box-shadow: var(--sh-lg);
}
.img-send-menu-item {
  display: flex; align-items: center; gap: 10px; width: 100%; min-height: 36px; padding: 0 10px;
  border: 0; border-radius: var(--r-sm); background: transparent; color: var(--text);
  font-family: inherit; font-size: 13.5px; text-align: left; cursor: pointer;
}
.img-send-menu-item:hover, .img-send-menu-item:focus-visible { background: var(--surface-2); outline: none; }
.img-send-menu-item .icon { width: 16px; height: 16px; color: var(--text-subtle); }
.img-send-menu-star { width: 16px; text-align: center; font-size: 14px; color: var(--text-subtle); }
.img-send-menu-item.is-danger { color: var(--danger-fg); }
.img-send-menu-item.is-danger .icon { color: inherit; }
.img-send-menu-sep { height: 1px; background: var(--border); margin: 6px 0; }
/* 편집 화면 — 왼쪽 미리보기, 오른쪽 칸. 좁은 화면은 위아래로 쌓는다. */
.img-send-editor { display: grid; grid-template-columns: 160px 1fr; gap: 16px; margin-top: 14px; align-items: start; }
.img-send-editor-thumb {
  width: 160px; height: 160px; object-fit: cover; border-radius: var(--r-md);
  border: 1px solid var(--border); background: var(--surface-2);
}
.img-send-editor-fields { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.img-send-editor-fields input[type="text"], .img-send-editor-fields textarea { width: 100%; box-sizing: border-box; }
.img-send-editor-fields textarea { resize: vertical; min-height: 120px; }
@media (max-width: 560px) {
  .img-send-editor { grid-template-columns: 1fr; }
  .img-send-editor-thumb { width: 120px; height: 120px; }
}
.img-send-foot { justify-content: flex-end; }
/* 말풍선 아래 보낸 이미지의 이름·내용(보낸 시점 사본). */
.msg-image-note { width: fit-content; max-width: min(100%, 320px); margin-top: 4px; font-size: 12.5px; color: var(--text-muted); }
.msg-image-note summary { cursor: pointer; display: flex; gap: 6px; align-items: baseline; list-style: none; }
.msg-image-note summary::-webkit-details-marker { display: none; }
/* 오른쪽 말풍선은 이름 줄도 오른쪽 끝에 — 펼쳐서 상자가 넓어져도 접혔을 때와 같은 자리에 남는다. */
.msg-ai .msg-image-note summary, .msg-agent .msg-image-note summary { justify-content: flex-end; }
.msg-image-note summary::after { content: "▾"; font-size: 11px; }
.msg-image-note[open] summary::after { content: "▴"; }
.msg-image-note-name { font-weight: 600; color: var(--text); }
.msg-image-note-label { text-decoration: underline; }
.msg-image-note-body { white-space: pre-wrap; overflow-wrap: anywhere; margin-top: 4px; padding: 8px 10px;
  border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface-2); color: var(--text); }
.img-viewer-img {
  position: relative; max-width: 100%; max-height: 100%; object-fit: contain;
  border-radius: 6px; background: #fff;
}
.img-viewer-bar {
  position: absolute; top: 12px; right: 16px; display: flex; gap: 10px; align-items: center;
}
.img-viewer-open, .img-viewer-close {
  font: inherit; font-size: 13px; line-height: 1; padding: 9px 14px; border-radius: 8px;
  color: #fff; background: rgba(255, 255, 255, 0.16); border: 1px solid rgba(255, 255, 255, 0.4);
  text-decoration: none; cursor: pointer;
}
.img-viewer-open:hover, .img-viewer-close:hover { background: rgba(255, 255, 255, 0.28); }
.img-viewer-close:focus-visible, .img-viewer-open:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.msg-image-failed > div + div, .msg-image-failed > a { margin-top: 4px; }
.msg-image-origin { display: inline-block; font-weight: 600; text-decoration: underline; }
.msg-image-failed-sub { font-size: 12px; }
/* 만료는 색이 아니라 취소선 없는 굵은 글자 + 실선 테두리로 구분한다(색각 접근성). */
.msg-image-failed.is-expired { border-style: solid; font-weight: 600; }
.msg-image-failed.is-expired .msg-image-failed-sub { font-weight: 400; }
/* 영상(링크만)·공유 표시 — 저장된 사진과 구분되게 실선 상자 + 종류 글자로 보인다(색에 기대지 않는다). */
.msg-media-link {
  margin-top: 4px; padding: 8px 12px; font-size: 13px; width: fit-content; max-width: min(100%, 360px);
  border: 1px solid var(--border); border-radius: 12px;
}
.msg-media-link.is-expired { font-weight: 600; }
.msg-media-kind, .msg-share-label { font-size: 12px; font-weight: 600; color: var(--muted); }
.msg-share-label { margin-top: 4px; }
.msg-media-link > * + * { margin-top: 4px; }
.msg-media-caption { margin-top: 4px; font-size: 12px; max-width: min(100%, 360px); }
.msg-media-caption > summary { cursor: pointer; color: var(--muted); }
.msg-media-caption-body { white-space: pre-wrap; overflow-wrap: anywhere; margin-top: 4px; }
/* 가져오지 못한 이미지 — 색이 아니라 글자·점선 테두리로 알린다. */
.msg-image-failed {
  margin-top: 4px; padding: 8px 12px; font-size: 13px; color: var(--muted);
  border: 1px dashed var(--border); border-radius: 12px; width: fit-content;
}
.msg.is-draft .msg-text { background: var(--warn-bg); border: 1px dashed var(--warn-border); color: var(--text); }
/* 끝내 전송되지 못한 답 — 색만이 아니라 ⚠️ 글자 표식·점선 테두리·사유 문장으로 알린다. */
.msg.is-send-failed .msg-text { border: 1px dashed var(--warn-border); opacity: .75; }
.msg-send-failed-tag { color: var(--warn-fg); font-weight: 700; }
.msg-send-failed {
  margin-top: 4px; padding: 6px 10px; font-size: 12.5px; color: var(--text);
  border: 1px dashed var(--warn-border); border-radius: 10px; width: fit-content; max-width: min(100%, 420px);
}
.msg-draft { color: var(--warn-fg); font-weight: 600; }
/* 자동 발송 표식 — **AI 가 판단해서 쓴 답이 아니라 규칙대로 나간 문장**임을 알린다.
   경고가 아니라 분류라서 색을 쓰지 않는다(초안·발신 차단과 달리 조치할 일이 없다).
   말풍선 위 보조 줄에 서므로 본문을 밀지 않는다. */
.msg-auto {
  flex: 0 0 auto; padding: 0 6px; border-radius: var(--r-pill);
  background: var(--surface-3); color: var(--text-muted);
  font-size: 10.5px; font-weight: 700; line-height: 16px;
}
/* 어느 규칙이었나 — 한 단계 더 조용하게. 좁아지면 이쪽부터 접힌다. */
.msg-auto-why { color: var(--text-muted); font-size: 11px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.msg-evidence { font-size: 12px; margin-top: 4px; color: var(--muted); }
.ev-item { padding: 3px 0; word-break: break-all; }
.msg-conf { font-size: 11px; color: var(--muted); margin-top: 3px; }
/* 발신이 차단된 턴 — 폴백 안내만 남아 있으면 상담사가 무슨 일인지 알 수 없다.
   눈에 띄되 답변 본문을 가리지 않게, 경고색 얇은 띠로 둔다. */
.msg-blocked { margin-top: 4px; font-size: 11px; line-height: 1.5; border-left: 3px solid var(--warn-fg);
  padding: 3px 8px; background: var(--warn-bg); border-radius: 3px; }
.msg-blocked-tag { font-weight: 600; color: var(--warn-fg); margin-right: 6px; }
.msg-blocked-reason { color: var(--muted); }
.msg-blocked-text { margin-top: 3px; color: var(--muted); font-family: ui-monospace, monospace;
  word-break: break-all; white-space: pre-wrap; }

/* AI 처리 내역(trace): 툴 사용 + LLM 전달 데이터. 근거와 같은 접이식 블록. */
.msg-trace { font-size: 12px; margin-top: 6px; color: var(--muted); }
.msg-trace > summary { cursor: pointer; font-weight: 600; color: var(--muted); }
.trace-section { margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--border); }
.trace-section:first-of-type { border-top: none; }
.trace-section-title {
  font-weight: 700; color: var(--text); font-size: 11.5px;
  text-transform: uppercase; letter-spacing: 0.03em; margin-bottom: 6px;
}
.trace-kv { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-bottom: 6px; }
.trace-kv-item { display: inline-flex; gap: 5px; }
.trace-kv-k { color: var(--muted); }
.trace-kv-v { color: var(--text); font-weight: 600; }
.trace-label {
  font-weight: 600; color: var(--muted); margin: 6px 0 3px;
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.02em;
}
.trace-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.trace-chip {
  background: var(--panel); border: 1px solid var(--border); border-radius: 999px;
  padding: 1px 8px; font-size: 11px; color: var(--text);
}
/*
 * 업무 라우팅(워크플로우) 판정. 'LLM 전달 데이터' 안에서 노출 툴 칩 바로 위에 붙는다.
 *  - is-implied  : 모델이 아니라 코드가 포함 관계로 더한 것 — 판정과 눈으로 갈려야 한다.
 *  - is-warn     : 코드 폴백·버려진 값. "이번 턴은 분류가 실패했다"가 스쳐 지나가면 안 된다.
 *  - is-unexposed: 업무는 열었지만 모델에는 안 실린 툴(조직 설정·라우팅에서 더 걸러짐).
 */
.trace-workflow { margin-bottom: 4px; }
.trace-label.is-warn { color: var(--danger-fg); }
.trace-chip.is-implied { border-style: dashed; color: var(--muted); }
.trace-chip.is-warn { background: var(--danger-bg); border-color: var(--danger-border); color: var(--danger-fg); }
.trace-chip.is-unexposed { color: var(--muted); opacity: 0.55; text-decoration: line-through; }
/*
 * 코드가 채우거나 정정한 인자 — `키 : 값` 줄. 칩으로 그리지 않는 이유는 값이 길어서다
 * (연락처·예약번호·날짜가 배지 안에서 줄바꿈되면 항목 경계가 사라진다).
 *  - trace-val-old : 모델이 보낸 값(버려진 값이라 취소선)
 *  - trace-val-src : 코드가 그 값을 쓴 출처(앵커·날짜 확정 등) — 값의 근거이므로 값 뒤에 흐리게
 */
.trace-vals { display: flex; flex-direction: column; gap: 2px; }
.trace-val-row { display: flex; gap: 6px; align-items: baseline; }
.trace-val-k {
  flex-shrink: 0; color: var(--muted); font-size: 11px; font-weight: 600;
  min-width: 96px; word-break: break-all;
}
.trace-val-v {
  flex: 1; color: var(--text); font-family: ui-monospace, monospace; font-size: 11px;
  word-break: break-all; display: flex; flex-wrap: wrap; gap: 4px; align-items: baseline;
}
.trace-val-old { color: var(--muted); text-decoration: line-through; }
.trace-val-arrow { color: var(--muted); }
.trace-val-new { font-weight: 600; }
.trace-val-src {
  color: var(--muted); font-family: inherit; font-size: 10.5px;
  border: 1px solid var(--border); border-radius: 999px; padding: 0 6px;
}
/* 덧붙인 지시 — 키(칩) 아래 본문. 본문이 길어 pre 로 접어 둔다(trace-pre 가 스크롤을 준다). */
.trace-inject { margin-top: 4px; display: flex; flex-direction: column; gap: 3px; align-items: flex-start; }
/* 칩은 내용만큼(위 flex-start), 본문은 단 전체를 쓴다 — 지시문이 좁은 칼럼에 갇히면 못 읽는다. */
.trace-inject > .trace-pre { align-self: stretch; }
.trace-pre {
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 6px;
  padding: 6px 8px; margin: 0; font-size: 11.5px; line-height: 1.45; color: var(--text);
  white-space: pre-wrap; word-break: break-word; max-height: 220px; overflow: auto;
}
.trace-history { display: flex; flex-direction: column; gap: 3px; }
.trace-hist-row { display: flex; gap: 6px; align-items: baseline; }
.trace-hist-role {
  flex-shrink: 0; font-weight: 600; color: var(--muted); font-size: 10.5px;
  text-transform: uppercase; min-width: 62px;
}
.trace-hist-text { flex: 1; color: var(--text); word-break: break-word; }
/* 실제로 모델에 실려 간 길이 — 본문이 200자 미리보기라 이 값이 없으면 잘려서 갔다고 오해한다. */
.trace-hist-chars {
  flex-shrink: 0; color: var(--muted); font-size: 10.5px; font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.trace-step { margin-top: 8px; padding-left: 8px; border-left: 2px solid var(--border); }
.trace-step-head { display: flex; align-items: center; gap: 6px; }
.trace-step-name { font-weight: 700; color: var(--text); }
.trace-badge { font-size: 10px; font-weight: 700; border-radius: 6px; padding: 0 6px; }
.trace-badge.is-ok { background: var(--success-bg); color: var(--success-fg); }
/*
 * 누가 부른 호출인가 — 모델의 함수호출 / 코드가 대신 부른 것. 성공 배지와 다른 축이라 색도
 * 다르게 둔다(초록·빨강은 성공 여부, 이쪽은 출처). 코드 호출은 사유가 붙어 길어지므로 알약 모양.
 */
.trace-by { font-size: 10px; font-weight: 600; border-radius: 999px; padding: 0 7px; }
.trace-by.is-model { background: var(--accent-soft); color: var(--accent); }
.trace-by.is-code { background: var(--surface-2); color: var(--muted); }
.trace-badge.is-fail { background: var(--danger-bg); color: var(--danger-fg); }
.trace-empty { color: var(--muted); font-style: italic; margin: 4px 0 0; }

/* 번역 병기(§R1·R2): 고객 발화 한글 번역, 상담사 발송 번역본. */
.msg-translation { font-size: 12px; margin-top: 4px; line-height: 1.4; }
.msg-customer .msg-translation { color: var(--muted); }
.msg-agent .msg-translation { color: var(--text-muted); }
/* 오른쪽 말풍선의 부속 설명은 오른쪽에 붙인다 — 왼쪽에 남으면 어느 말풍선에
   딸린 것인지 눈으로 이어지지 않는다. */
.msg-ai .msg-translation, .msg-agent .msg-translation,
.msg-ai .msg-conf, .msg-agent .msg-conf { text-align: right; }
.msg-translation-label { font-weight: 600; }
/* ★ 흰 글자(--on-accent)가 밝은 배경(--surface-3)에 얹혀 **거의 안 보였다.**
   원래는 흰 글자 + rgba(0,0,0,.25) 어두운 칩이었는데, 토큰으로 옮기며 배경만
   밝게 바꾸고 글자색을 그대로 뒀다. 이 배지는 말풍선 **밖**(메타 줄)에 있으므로
   말풍선 위 글자색을 쓰면 안 된다 — 다른 배지와 같은 규칙으로 맞춘다. */
.msg-translated-badge {
  font-size: 11px; font-weight: 600;
  color: var(--muted); background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--r-pill); padding: 1px 7px; margin-left: 6px;
}
/* 번역 실패 = 한국어 원문이 그대로 나갔다는 경고. 정상 배지와 색으로 구분한다. */
.msg-translate-failed-badge {
  font-size: 11px; font-weight: 600; color: var(--danger-fg);
  background: var(--danger-border);
  border-radius: 8px; padding: 1px 6px; margin-left: 6px;
}

/* 입력 영역 = (카카오 안내) + 입력 상자 + (알림 한 줄)
   입력 상자(.reply-form) = 글상자 + 도구 줄 — 한 테두리 안에 묶어 **하나의 입력칸**으로 읽히게 한다.
   초점은 글상자가 아니라 상자 전체가 받는다(:focus-within). 도구 줄 왼쪽은 부가 도구, 오른쪽은 안내와 [전송].
   모든 폭에서 같은 구조다 — 폭마다 단추 자리를 따로 정하지 않는다(inbox.js '입력 상자' 주석). */
.reply-area {
  flex-shrink: 0; border-top: 1px solid var(--border); background: var(--panel);
  padding: 10px 16px 12px;
}
.reply-once-notice {
  background: var(--warn-bg); color: var(--warn-fg); border: 1px solid var(--warn-border);
  border-radius: var(--r-sm); margin-bottom: 8px;
  padding: 8px 12px; font-size: 12.5px; line-height: 1.5;
}
.reply-form {
  display: flex; flex-direction: column;
  border: 1px solid var(--border-strong); border-radius: var(--r-lg);
  background: var(--panel);
  transition: border-color .12s, box-shadow .12s;
}
.reply-form:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.reply-form.is-disabled { background: var(--surface-2); }
/* 글상자는 상자의 일부라 제 테두리·초점 고리를 갖지 않는다(상자가 대신 그린다). */
.reply-form textarea {
  display: block; width: 100%; min-height: 60px; max-height: 180px;
  border: 0; border-radius: var(--r-lg) var(--r-lg) 0 0; background: transparent;
  padding: 11px 14px 4px; resize: none;
}
.reply-form textarea:focus { border: 0; box-shadow: none; }
.reply-form textarea:disabled { background: transparent; color: var(--muted); }
.composer-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 4px 8px 8px 6px; cursor: text;
}
.composer-tools { display: flex; align-items: center; gap: 2px; min-width: 0; }
/* 부가 도구 — 테두리 없는 가벼운 단추. 주된 동작([전송])과 무게가 겹치지 않게 한다. */
.composer-tool {
  display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 10px;
  border: 0; border-radius: var(--r-sm); background: transparent; color: var(--muted);
  font-family: inherit; font-size: 13px; font-weight: 560; cursor: pointer; white-space: nowrap;
}
.composer-tool .icon { width: 18px; height: 18px; }
.composer-tool:hover { background: var(--surface-2); color: var(--text); }
.composer-tool:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* [템플릿]은 템플릿이 하단 시트로 내려가는 폭에서만(1100px 아래). 위의 .composer-tool 이 display 를 주므로
   같은 세기로는 앞쪽 `.tpl-toggle { display: none }` 을 이긴다 — 두 클래스로 다시 감춘다. */
.composer-tool.tpl-toggle { display: none; }
.composer-send { display: flex; align-items: center; gap: 12px; margin-left: auto; min-width: 0; }
.reply-form .btn-primary { height: 32px; padding: 0 16px; }
.reply-send-hint {
  font-size: 11.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.reply-area-note { margin-top: 6px; font-size: 12px; color: var(--muted); }
/* 손가락으로 쓰는 기기에서는 이 안내가 **누를 수 없는 키**를 알려 준다
   (Shift+Enter · Ctrl+Enter). 거기서는 Enter 가 줄바꿈이고 보내는 것은 [전송]
   버튼뿐이라(inbox.js 의 IS_TOUCH) 설명할 단축키가 아예 없다.
   ★ 폭이 아니라 `pointer` 로 가른다 — 데스크톱에서 창을 좁혀도 키보드는 그대로다.
   실측 375×812: 이 줄 하나가 28px, 대화가 보이는 높이의 7% 였다. */
@media (pointer: coarse) {
  .reply-send-hint { display: none; }
}

/* U5: 안내 메시지 액션 표시(상담 콘솔은 라벨만, 링크는 열지 않음) */
.msg-actions { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 5px; }
.action-chip {
  background: var(--panel); border: 1px solid var(--border); border-radius: 999px;
  padding: 2px 9px; font-size: 11px; color: var(--muted);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---------- U6: Super Admin 화면 ---------- */
.data-table { width: 100%; border-collapse: collapse; font-size: 13.5px; margin-top: 12px; }
.data-table th, .data-table td { text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--border); }
.data-table th { color: var(--muted); font-weight: 600; font-size: 12.5px; }
.data-table tr.is-inactive td { color: var(--muted); }

/* 표 셀은 **기본이 nowrap** 이다.
   접게 두면 칸이 서로 폭을 뺏어 좁아지고, 그러면 머리글이 두 줄이 되어 행 전체가
   내려앉고 한글은 어절 단위로 갈라진다(실측: 계정 관리 표가 390px 에서 칸 55px,
   '활성화 기간' 두 줄). 표는 .table-scroll 안에서 가로로 스크롤되므로 넓어져도
   잘리지 않는다 — 좁혀서 읽기 어렵게 만드는 것보다 옆으로 미는 편이 낫다.
   긴 산문·JSON 은 아래 .wrap 으로 **명시해서** 접는다. */
.data-table th, .data-table td { white-space: nowrap; }
.data-table th.wrap, .data-table td.wrap {
  white-space: normal; max-width: 340px; vertical-align: top;
}
/* 옛 이름. 이제 기본이 nowrap 이라 하는 일은 없지만 뷰가 아직 쓴다. */
.data-table td.nowrap { white-space: nowrap; }

/* ---------- 문서 목록 제목 칸 ----------
   **칸 폭을 정하는 것이 이 칸이다** — 표 셀은 기본이 nowrap 이라(위 주석) 제목과
   요약이 길면 그 길이가 그대로 칸 폭이 되고, 표가 화면 몇 개 분량으로 늘어난다.
   실측(375px): 제목 칸 혼자 1087px 을 먹어 표가 1409px 를 원했고, 오른쪽 끝
   [보기·삭제] 가 1237px 바깥이라 **가로로 화면 세 개 반을 밀어야** 닿았다.

   ★ 상한을 **화면에 맞춰** 잡는다. 고정 px 로 두면 좁은 화면에서는 여전히 넘치고
     넓은 화면에서는 쓸 수 있는 자리를 놀린다 — 말줄임이 화면과 무관하게 늘 같은
     자리에 찍힌다("말줄임 표시가 정적이야").
   ★ 셀이 아니라 **셀 안의 상자**에 건다. 표 셀의 max-width 는 브라우저가 흘려
     듣는다(table-layout:auto 에서는 힌트일 뿐이다). */
/* ── 문서 표: 남는 폭은 **제목이 쓴다** ──
   오른쪽 동작 칸([보기]·[추가 정보]·[삭제])을 걷어내자 그 폭이 갈 곳을 잃고 뒤 두 칸으로
   퍼졌다. 제목은 420px 에서 말줄임된 채 멈춰 있는데 `언어`·`색인 조각` 은 화면 오른쪽 끝에
   가 붙어, 그 사이가 텅 비었다(사용자 지적).

   `table-layout: fixed` 로 바꾸고 **뒤 두 칸만** 폭을 못박는다. 남은 것은 전부 첫 칸(제목)
   차지다 — 그 자리는 비워 둘 자리가 아니라 제목·요약이 더 보일 자리다.
   ★ `nth-last-child` 로 뒤에서 센다. 스코프 칸이 조건부라(`{{#if hasScopes}}`) 앞에서 세면
     스코프를 쓰는 조직에서 한 칸씩 밀린다. */
#kbDocTable { table-layout: fixed; }
#kbDocTable th:nth-last-child(1), #kbDocTable td:nth-last-child(1) { width: 96px; }
#kbDocTable th:nth-last-child(2), #kbDocTable td:nth-last-child(2) { width: 72px; }
/* ── 2026-09-11: 고정 상한(min(58vw, 420px))을 **없앴다.** ──
   위 표가 `table-layout: fixed` 가 되면서 셀이 제 폭을 갖는다. 그러자 그 상한이 반대로
   해를 끼쳤다 — 화면이 넓어도 제목은 늘 420px 에서 잘리고 그 뒤가 텅 빈다
   ("말줄임 표시가 너무 멀리 떨어져 있어"). 이제 상자는 셀을 그대로 쓴다.
   실측 1280: 칸 오른쪽 1056 · 글 오른쪽 1046 — 차이 10px = 셀 여백. */
.kb-title-cell { max-width: 100%; }
/* 제목 줄: 제목만 줄어들고 배지는 그대로 남는다 — 배지가 잘리면 '외부'인지
   '추가정보'인지 알 수 없어 목록의 뜻이 사라진다. */
/* ★ 바닥 높이를 배지와 같게 둔다. 안 그러면 **배지가 달린 행만 4px 높아진다**
   (실측: 배지 있는 줄 24px · 없는 줄 21px → 행 68px vs 64px). 목록을 훑을 때 줄 간격이
   들쭉날쭉해 어떤 행이 특별한 것처럼 읽힌다 — 특별한 것은 배지이지 행이 아니다. */
.kb-title-line { display: flex; align-items: center; gap: 6px; min-width: 0; min-height: var(--tag-h); }
.kb-title-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kb-title-line .tag { flex: 0 0 auto; }
/* 요약 줄: 상자 폭을 그대로 따른다(고정값이 아니다). 전문은 title 속성으로 본다.
   ★ 요약이 비어 있어도 **자리는 남긴다.** 없는 행만 13px 낮아지면(실측 55 vs 68) 목록에서
     그 행만 튀어 보이는데, 정작 알려야 할 것은 "요약이 없다"이지 "이 행이 다르다"가 아니다. */
.doc-preview {
  display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
  /* 빈 줄도 **글자 한 줄과 같은 높이**를 차지해야 한다. `em` 으로 어림잡으면 어긋난다
     (실측: 1.45em = 17.4px vs 실제 줄 19.2px). 줄 높이를 그대로 바닥으로 쓴다. */
  line-height: 1.6; min-height: 1.6em;
}

/* 검토 대기 보충문을 여는 버튼(탭 줄). 숫자 배지를 품은 작은 버튼이다 —
   배지만 두면 "몇 건 있다"만 알려 주고 처리하는 곳은 따로 찾아야 했다. */
.kb-supp-alert {
  display: inline-flex; align-items: center; gap: 4px;
  height: 28px; padding: 0 8px; flex: 0 0 auto;
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  background: var(--panel); color: var(--text-muted); cursor: pointer;
}
.kb-supp-alert:hover { background: var(--surface-2); color: var(--text); }
.kb-supp-alert .tag { pointer-events: none; }

/* ---------- 편집 칸 접이식 ----------
   드물게 쓰는 칸(요청 헤더 등)을 접어 둔다. 늘 펴 두면 자주 쓰는 칸이 그만큼 아래로
   밀린다. **값이 있으면 열어 둔다** — 설정해 둔 것을 접어 감추면 그게 있는 줄도 모른다.
   ⚠ 닫힌 details 안에는 required 를 두지 않는다: 브라우저가 포커스를 못 줘서 저장이
     조용히 막힌다(툴 편집 구획에서 겪은 것과 같은 함정). */
.field-fold { margin-top: 14px; border: 1px solid var(--border); border-radius: var(--r-sm); }
.field-fold > summary {
  display: flex; align-items: center; gap: 7px;
  padding: 9px 11px; cursor: pointer; list-style: none;
  font-size: 12.5px; font-weight: 560; color: var(--muted);
}
.field-fold > summary::-webkit-details-marker { display: none; }
.field-fold > summary:hover { color: var(--text); }
.field-fold > summary .icon { width: 15px; height: 15px; color: var(--text-subtle); transition: transform 0.15s ease; }
.field-fold[open] > summary .icon { transform: rotate(180deg); }
.field-fold > summary .grow { flex: 1 1 auto; min-width: 0; }
.field-fold > .field { margin: 0; padding: 0 11px 11px; }

/* ---------- 읽기 전용 프롬프트 접이식 ----------
   이 화면의 읽기 전용 글(공통 프롬프트·코드 고정 절)은 평소에 볼 것이 아니다 —
   무엇이 왜 그렇게 나갔는지 따질 때만 편다. 펼쳐 두었더니 편집할 수 있는 카드가
   한참 아래로 밀려, 이 화면에서 **무엇을 할 수 있는지**가 흐려졌다. */
.prompt-fold { border-top: 1px solid var(--border); }
.prompt-fold > summary {
  display: flex; align-items: center; gap: 7px;
  padding: 9px 0; cursor: pointer; list-style: none;
  font-size: 13px; font-weight: 580; color: var(--text-muted);
}
.prompt-fold > summary::-webkit-details-marker { display: none; }
.prompt-fold > summary:hover { color: var(--text); }
.prompt-fold > summary .icon { width: 15px; height: 15px; color: var(--text-subtle); transition: transform 0.15s ease; }
.prompt-fold[open] > summary .icon { transform: rotate(180deg); }
.prompt-fold > summary .grow { flex: 1 1 auto; min-width: 0; }
.prompt-fold > .prompt-preview { margin-top: 0; margin-bottom: 12px; }
.prompt-fold > .hint { margin: 0 0 8px; }
/* 고정 절 묶음은 공통 프롬프트 카드 **안**에 들어간다 — 위 내용과 선으로 가른다. */
.prompt-fixed { margin-top: 18px; padding-top: 14px; border-top: 1px dashed var(--border); }
.prompt-fixed .sub-title { margin-bottom: 4px; }

/* ---------- 대기 중 목록 ----------
   한 대화의 속성을 모은 카드다(칸끼리 견주는 값이 아니라서 표가 아니다).
   실측 375px: 4열 표가 560px 을 요구해 화면을 가로로 밀었다 — 카드는 폭을 그대로 쓴다.
   ★ 카드 전체가 링크다. 목록에서 할 일은 '그 대화를 여는 것' 하나뿐이다. */
/* 대기 목록의 갈래 탭 — 지식 화면의 출처 탭(.origin-tabs)과 같은 생김새를 쓴다.
   같은 성격(목록을 갈래로 좁히는 줄)이라 새 모양을 만들 이유가 없다.
   카드 안 첫 줄이라 위 여백만 없앤다(카드가 이미 padding 을 준다). */
.pending-tabs { margin-top: 0; }
/* 답변 현황 — 목록 위 한 줄. "기다린다"에 섞여 있던 두 가지를 가른다.
   ★ '아무도 답 안 함'만 색을 준다. 둘 다 강조하면 어느 쪽이 급한지 흐려지고, 실측에서
     그 수는 11인데 옆의 AI 답변은 125였다 — 같은 무게로 그리면 11이 묻힌다. */
.answer-sum {
  display: flex; flex-wrap: wrap; gap: 6px 16px; align-items: baseline;
  margin: 0 0 8px; font-size: 12.5px; color: var(--text-muted);
}
.answer-part > b { font-weight: 700; color: var(--text); margin-left: 2px; }
.answer-part > small { margin-left: 4px; font-size: 11.5px; }
.answer-part.is-none > b { color: var(--danger-fg); }
/* 일괄 완료 — 되돌리기가 대화 하나씩이라 주 동작(파랑) 자리를 주지 않는다.
   설명을 옆에 붙여, 누르기 전에 "무엇이 남고 무엇이 사라지는지"를 읽게 한다. */
.bulk-close { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 10px; }
.bulk-close > .hint { flex: 1 1 260px; min-width: 0; margin: 0; line-height: 1.5; }

.pending-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.pending-card {
  display: block; padding: 12px 14px;
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--panel); color: inherit; text-decoration: none;
}
.pending-card:hover { background: var(--surface-2); border-color: var(--border-strong); }
.pending-head { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
/* 기다린 시간이 이 목록의 주인공이다 — 정렬 기준이고, 급한 정도를 재는 값이다. */
.pending-time { font-size: 14.5px; font-weight: 680; white-space: nowrap; }
.pending-head .tag { white-space: nowrap; }
/* 종전 '기준' 배지(.pending-th)가 있던 자리. 82줄 전부 같은 값이라 걷어내고, **지금
   답하면 닿는가**를 대신 놓았다. 두 표기는 성격이 정반대라 무게도 다르게 준다.
   · 남은 시간은 재촉이다 — 눈에 띄되 기다린 시간(주인공)을 이기지 않는다
   · 발신 불가는 **하지 말라는 말**이다 — 사유를 옆에 붙여 다음 행동까지 말한다 */
.pending-left { font-weight: 600; }
.pending-block { font-weight: 700; }
/* 사유는 배지가 아니라 문장이다. 좁은 화면에서는 배지 줄 아래로 내려가 통째로 읽힌다.
   ★ `flex-basis: 100%` 로 줄을 끊지 않는다 — 이 파일의 .flex-break 사고와 같은 자리로,
     max-width 가 걸리면 기대한 줄바꿈이 안 일어난다. 여기서는 폭 제한이 없어 통한다. */
.pending-why {
  flex: 1 1 200px; min-width: 0;
  font-size: 12px; line-height: 1.45; color: var(--danger-fg);
}
/* 채널은 부가 정보라 오른쪽 끝으로 민다.
   ⚠ `.pending-ch` 한 글자로는 안 된다 — `.tag + .tag { margin-left: 4px }` 가 더 세서
     그 4px 이 이겼다(실측: 배지가 카드 왼쪽에서 226px 자리에 붙어 있었다). */
.pending-head .pending-ch { margin-left: auto; }
/* 채널은 부가 정보라 오른쪽 끝으로 민다. */
.pending-ch { margin-left: auto; }
.pending-card .msg-clamp { margin-top: 6px; }
/* 마지막 발화는 두 줄까지 보이고 그 뒤로 접는다 — 이 칸이 '무슨 대화인지' 가리는
   유일한 단서라 한 줄로 자르면 열어 보기 전에는 구분이 안 된다. */
.msg-clamp {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; font-size: 13px; color: var(--text-muted); line-height: 1.5;
  /* 아주 좁은 화면에서 표가 마지막 2~17px 을 못 접던 자리 — 이 칸의 최소 폭이
     '가장 긴 낱말'이라 그만큼은 절대 안 줄었다. 낱말 안에서도 접게 둔다. */
  overflow-wrap: anywhere;
}

/* ---------- 빈 목록 안내 ----------
   목록이 차지했을 자리에 가운데로 놓는다. 왼쪽 위에 회색 글자 한 줄만 두면
   "비어 있다"가 아니라 **아직 안 만든 화면**으로 읽힌다(실제로 그렇게 읽혔다). */
.empty-state {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; padding: 34px 20px; text-align: center;
  /* ★ 남는 높이를 **전부** 쓴다 — 가운데 정렬은 그래야 뜻이 생긴다.
     종전에는 이 상자가 제 내용만큼만 커서, 목록 자리가 화면 높이에 고정된 화면
     (채널 연동·커스텀 툴·템플릿…)에서는 안내가 **위쪽에 붙고 아래가 통째로 비었다**
     — 가운데 정렬이 걸려 있는데도 그랬다(사용자 지적 2026-09-14).
     높이가 정해지지 않은 부모(일반 카드) 안에서는 백분율 min-height 가 풀리지 않아
     아무 일도 일어나지 않는다 — 넓은 자리에서만 켜지는 규칙이다. */
  min-height: 100%;
}
/* 아이콘은 글자가 아니라 장식이라 --text-subtle 을 쓴다(대비 규칙 대상이 아니다).
   글자는 읽어야 하므로 --text-muted 다. */
.empty-state .empty-ico { width: 28px; height: 28px; color: var(--text-subtle); }
.empty-state p {
  margin: 0; max-width: 340px;
  font-size: 13.5px; line-height: 1.6; color: var(--text-muted);
}
/* 보조 줄 — 본문보다 한 단계 약하게. 같은 크기로 두면 두 문장이 한 덩어리로 읽혀
   줄을 나눈 뜻이 없어진다. */
.empty-state .empty-sub { margin-top: -4px; font-size: 12.5px; color: var(--text-subtle); }
/* 화면 높이를 채우는 목록(지식 엔티티·문서)은 부모가 flex 라 자라야 한다.
   높이 자체는 위의 min-height 가 준다. */
.content-list-fill .kb-list-card > .empty-state { flex: 1 1 auto; }

/* 목록 맨 위의 **상태** 한 줄(색인 결손 수 등). 설명이 아니라 지금 이 목록에 관한
   경고라 [i] 말풍선으로 접지 않고 그대로 둔다. 없을 때가 대부분이라 여백은
   자기 자신이 갖는다. */
.kb-list-alert { margin: 0 0 10px; }
@media (max-width: 768px) {
}

/* 배지. 아이콘(SVG)을 품을 수 있어야 하므로 inline-flex 다 —
   inline-block 이면 아이콘이 글자 기준선 아래로 처져 배지 높이가 들쭉날쭉해진다. */
/* 배지 높이는 **한 곳에서만** 정한다. 목록의 제목 줄이 이 값을 바닥으로 삼기 때문에
   (배지 유무로 행 높이가 달라지지 않게), 값이 두 군데 있으면 반드시 어긋난다. */
:root { --tag-h: 24px; }
.tag {
  display: inline-flex; align-items: center; gap: 4px; min-height: var(--tag-h);
  background: var(--surface-2); color: var(--muted);
  border: 1px solid transparent; border-radius: var(--r-pill);
  /* ⚠ 줄 높이를 **px 로 못박는다.** 배수(1.6)면 11.5px × 1.6 = 18.4px 라 배지 상자가
     24.4px 이 되고, 그 0.4px 때문에 배지 달린 행만 1px 높아진다(실측 68 vs 67).
     18 + 패딩 4 + 테두리 2 = 24px = --tag-h. */
  padding: 2px 8px; font-size: 11.5px; font-weight: 600; line-height: 18px;
  margin-left: 4px; vertical-align: middle;
}
/* 배지 안 아이콘은 글자에 맞춰 한 단계 줄인다. */
.tag .icon { width: 13px; height: 13px; }
.tag.warn { background: var(--warn-bg); color: var(--warn-fg); border-color: var(--warn-border); }
.tag.danger { background: var(--danger-bg); color: var(--danger-fg); border-color: var(--danger-border); }
.tag.ext { background: var(--accent-soft); color: var(--accent); }

/* 지식베이스 헤더: 제목 + 우측 최신화 액션 */
.page-head-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }

/* ---------- 모달(일괄 등록 등) ---------- */
.modal { position: fixed; inset: 0; height: var(--vh); z-index: 90; display: flex; align-items: center; justify-content: center; }
.modal[hidden] {display: none;}
.modal-backdrop { position: absolute; inset: 0; background: var(--scrim); }
.modal-box {
  position: relative; background: var(--panel);
  border: 1px solid var(--border); border-radius: var(--r-lg);
  width: min(680px, 92vw); max-height: 88vh; max-height: calc(var(--vh) * 0.88); overflow-y: auto; padding: 20px 22px;
  box-shadow: var(--sh-lg);
}
.modal-head { display: flex; align-items: center; justify-content: space-between; }
.modal-head h2 { margin: 0; font-size: 17px; font-weight: 660; letter-spacing: -0.01em; }
/* 바닥 줄의 표(가져옴 · 편집 불가). **저장이 무엇에 걸리는지**를 말하는 표라 저장 단추
   옆에 선다 — 제목 옆에 두면 화면 이름의 일부처럼 읽히고, 제 줄에 홀로 세우면 무엇을
   가리키는 말인지 보이지 않아 덩그러니 떠 보인다(둘 다 실측에서 나왔다).
   ★ 저장 자리를 대신하므로 오른쪽 끝에 선다(삭제는 왼쪽 끝, 그 사이는 빈다). */
.modal-foot > .foot-tag { margin-left: auto; align-self: center; }
.modal-x {background: transparent;border: 0;font-size: 22px;line-height: 1;cursor: pointer;color: var(--muted);text-decoration: none;}
.modal-foot { display: flex; gap: 8px; margin-top: 16px; }

/* ---------- 토스트·팝업(SweetAlert2) ----------
   저장 완료·검증 오류·1회성 토큰이 이 창으로 나간다. 라이브러리 기본 스킨을 그대로 두면
   **다크 모드에서 화면 전체가 어두운데 이 창만 흰 상자**가 된다(실측: 팝업 #ffffff ·
   글자 #545454 · 페이지 #0e1015). 라이트에서도 우리 흰색·우리 액센트와 미묘하게 달라
   "다른 제품의 창"으로 읽힌다.

   ★ **선택자로 덮어쓰지 않는다 — 변수로 준다.** v11 은 제 색을 전부 `--swal2-*` 로
     뽑아 두었다(`background: var(--swal2-background)` 꼴). 그 변수를 컨테이너에 심으면
     모달·토스트가 **같은 경로로** 우리 색을 받는다.
   ⚠ 처음에는 `.swal2-popup { background: … }` 로 덮었다가 **토스트만 흰 채로 남았다.**
     `.swal2-toast`(0,1,0)가 같은 세기인데 나중에 실려 이겼기 때문이다 — 글자만 밝은 색이
     되어 흰 바탕에 흰 글씨가 됐다. 힘으로 누르면 이런 자리가 계속 나온다.
   ⚠ 변수를 `:root` 에 두는 것도 안 된다. 저쪽도 `:root` 에 정의하는데 스타일이 **나중에**
     실린다. 컨테이너에 두면 세기 싸움 자체가 없다(같은 요소에서 겨루지 않는다).

   장막은 건드리지 않아도 된다 — 토스트일 때는 저쪽이 `body.swal2-toast-shown` 으로
   투명하게 만든다. 여기서 더 센 선택자를 쓰면 **토스트 뒤에 장막이 깔려 화면이 잠긴다.** */
.swal2-container {
  --swal2-background: var(--panel);
  --swal2-color: var(--text);
  --swal2-border: 1px solid var(--border);
  --swal2-toast-border: 1px solid var(--border);
  --swal2-border-radius: var(--r-md);
  --swal2-backdrop: var(--scrim);
  --swal2-toast-box-shadow: var(--sh-lg);
  --swal2-close-button-color: var(--text-subtle);
  /* 단추는 콘솔의 두 갈래(주/보조)와 같은 색·같은 모서리로 맞춘다. */
  --swal2-confirm-button-background-color: var(--accent);
  --swal2-confirm-button-color: var(--on-accent);
  --swal2-confirm-button-border-radius: var(--r-sm);
  --swal2-cancel-button-background-color: var(--surface-2);
  --swal2-cancel-button-color: var(--text);
  --swal2-cancel-button-border-radius: var(--r-sm);
  --swal2-deny-button-background-color: var(--danger-fg);
  --swal2-timer-progress-bar-background: var(--accent);
  --swal2-footer-border-color: var(--border);
  --swal2-validation-message-background: var(--warn-bg);
  --swal2-validation-message-color: var(--warn-fg);
  /* 포커스 테는 콘솔 입력칸과 같은 모양으로(파란 기본 테가 튄다). */
  --swal2-outline: 0 0 0 3px var(--accent-soft);
}
/* 변수가 없는 둘. 본문 글자는 우리 모달과 같이 한 단계 옅게 둔다. */
.swal2-popup { font-family: inherit; box-shadow: var(--sh-lg); }
.swal2-html-container { color: var(--text-muted); }

/* ★ 머리말과 버튼 줄은 **상자에 붙여 둔다.**
   종전에는 상자 전체가 스크롤돼 내용이 길면 저장·취소가 상자 밖으로 밀려났다
   (실측: 뷰포트 430px 에서 상자 바닥 404px · 푸터 상단 477px — 데스크톱에서도 난다).
   버튼이 안 보이면 사람들은 '저장이 없는 창'으로 읽고 Esc 로 닫는다. 방금 쓴 것과 함께.

   마크업은 손대지 않는다 — 모달마다 구조가 제각각이라(폼 안, 단계 div 안, 상자 직속)
   래퍼를 넣으려면 열몇 곳을 고쳐야 하고, sticky 는 그 넷 모두에서 상자를 기준으로 잡힌다.
   음수 margin 으로 상자 안쪽 여백(20/22px)을 되받아 좌우 끝까지 덮는다.
   .modal-sticky 는 제 방식으로 이미 고정하므로 제외한다(두 규칙이 겹치면 여백이 두 번 먹는다). */
.modal-box:not(.modal-sticky) .modal-head {
  position: sticky; top: -20px; z-index: 2;
  margin: -20px -22px 0; padding: 20px 22px 12px;
  background: var(--panel);
}
/* ⚠ 바닥 줄 규약은 **두 구조가 같아야 한다.** 종전에는 `.modal-sticky` 만 오른쪽 정렬에
   아래 여백 13px 였고, 일반 모달은 정렬 지정이 없어(=왼쪽) 아래 여백이 20px 였다.
   같은 화면을 오가며 [저장]과 [문서 추가]가 서로 다른 자리에 서 있었다(실측). 누르는
   자리가 화면마다 달라지면 매번 다시 찾아야 한다 — 정렬·여백을 sticky 쪽에 맞춘다. */
.modal-box:not(.modal-sticky) .modal-foot {
  position: sticky; bottom: -20px; z-index: 2;
  margin: 16px -22px -20px; padding: 13px 22px;
  justify-content: flex-end;
  background: var(--panel); border-top: 1px solid var(--border);
}
/* 헤더/푸터 고정 + 본문만 스크롤하는 모달 — 큰 폼(커스텀 툴·동기화 소스)용. */
.modal-box.modal-sticky { padding: 0; overflow: hidden; display: flex; flex-direction: column; max-height: 90vh; max-height: calc(var(--vh) * 0.9); }
.modal-sticky > form, .modal-sticky > .modal-inner { display: flex; flex-direction: column; min-height: 0; max-height: 90vh; max-height: calc(var(--vh) * 0.9); }
.modal-sticky .modal-head { flex: none; padding: 16px 22px; border-bottom: 1px solid var(--border); }
.modal-sticky .modal-body { flex: 1 1 auto; overflow-y: auto; padding: 16px 22px 20px; min-height: 0; }
.modal-sticky .modal-foot { flex: none; padding: 13px 22px; border-top: 1px solid var(--border); margin-top: 0; justify-content: flex-end; }
/* 단독 리스트 패널(폼을 모달로 뺀 화면) — 남은 높이를 채우고 내부만 스크롤. */
.solo-pane { flex: 1; min-height: 0; margin-top: 16px; }
.split-pane-head .head-titles { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
/* 모달 하단 테스트 영역. */
.tool-test-section { margin-top: 18px; padding-top: 14px; border-top: 1px dashed var(--border); }
.imp-steps { margin: 12px 0; padding-left: 18px; font-size: 13.5px; line-height: 2; }
.imp-preview { margin-top: 8px; font-size: 13px; }
.imp-counts { padding: 8px 0; }
.imp-block { border-radius: 8px; padding: 8px 12px; margin-top: 8px; }
.imp-block.err { background: var(--danger-bg); }
.imp-block.warn { background: var(--warn-bg); }
.imp-block .imp-h { font-weight: 600; margin-bottom: 4px; }
.imp-block ul { margin: 0; padding-left: 18px; }
.imp-ok { color: var(--success-fg); font-weight: 600; padding: 8px 0; }
/* 탭 줄 안에 들어가므로 자체 여백을 갖지 않는다(자리는 .subnav-actions 가 잡는다). */
.kb-sync-actions { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.kb-last-sync { font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.btn-secondary.sm { padding: 4px 10px; font-size: 12.5px; }

.perm-picker { margin-top: 14px; }

/* ---------- 조직 복제: 무엇이 오고, 주소가 어떻게 바뀌는가 ----------
   종전 팝업은 "URL·값 치환" 이라는 빈 칸 두 개였다. 규칙이 **어디에 먹는지**가 화면에
   없어, 툴 URL 에 지점 코드가 박혀 있다는 구현 지식을 이미 아는 사람만 쓸 수 있었다.
   실제 주소 한 줄을 놓고 결과를 바로 보여 주면 그 지식 없이도 맞는지 알 수 있다. */
.clone-preview {
  margin-top: 12px; padding: 11px 13px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-sm);
  display: grid; gap: 4px; justify-items: start;
}
.clone-preview-label { font-size: 11.5px; font-weight: 600; color: var(--text-muted); }
/* 주소는 길다 — 잘라 내면 정작 바뀌는 끝부분이 안 보인다. 접어서 다 보여 준다. */
.clone-preview code {
  font-size: 12px; line-height: 1.5; overflow-wrap: anywhere; color: var(--text-muted);
}
.clone-preview-arrow { color: var(--text-subtle); font-size: 12px; line-height: 1; }
/* 바뀐 결과만 또렷하게 — 위(원본)와 같은 색이면 무엇이 결과인지 눈으로 못 가른다. */
.clone-preview-to { color: var(--accent); font-weight: 600; }
.clone-preview .hint { margin: 2px 0 0; }
/* 규칙이 아직 없거나 안 먹을 때는 결과 줄을 비워 둔다(빈 상자가 답이다). */
.clone-preview.is-empty .clone-preview-arrow,
.clone-preview.is-empty .clone-preview-to { display: none; }

.clone-counts {
  margin: 0; display: flex; flex-wrap: wrap; gap: 6px 18px;
}
.clone-counts > div { display: flex; align-items: baseline; gap: 6px; }
.clone-counts dt { font-size: 12.5px; color: var(--text-muted); }
.clone-counts dd { margin: 0; font-size: 14px; font-weight: 660; }
.prompt-preview {
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
  padding: 12px; font-size: 12.5px; line-height: 1.6; white-space: pre-wrap;
  word-break: break-word; margin: 0 0 14px;
}
/* 근거 있음/없음 diff 미리보기 — 달라지는 구간만 눈에 띄게 강조한다. */
.prompt-preview b { background: var(--warn-bg); padding: 0 2px; border-radius: 3px; }
.src-list { margin: 10px 0 0; padding-left: 18px; color: var(--muted); font-size: 12.5px; line-height: 1.9; }
input[type="date"] {
  padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px;
  font-size: 14px; background: var(--panel);
}

/* 카카오 응답 가능 시간 배너 (§4.4) */
.kakao-window {
  flex-shrink: 0;
  background: var(--success-bg); color: var(--success-fg); border-bottom: 1px solid var(--success-border);
  padding: 7px 16px; font-size: 12.5px;
}
.kakao-window.is-closed {
  background: var(--danger-bg); color: var(--danger-fg); border-bottom-color: var(--danger-border);
}

/* 채널 연동 관리 */
.webhook-url {
  display: inline-block; max-width: 380px; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; vertical-align: middle; font-size: 12px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 6px; padding: 2px 6px;
}
.copy-btn { margin-left: 6px; }
/* 카드 안에서는 **잘리지 말고 접힌다.** 표 안에서는 가로 스크롤이 잘린 뒷부분을
   보여 줬지만 카드에는 그 스크롤이 없다 — 말줄임만 남으면 URL 뒷부분을 볼 방법이
   사라진다(실측: 390px 에서 카드가 73px 넘쳤다). max-width 도 칸에 맞춘다. */
.rec-card .webhook-url {
  max-width: 100%; white-space: normal; overflow: visible;
  text-overflow: clip; overflow-wrap: anywhere;
}

/* 탭 · 지식베이스 */
.tabs { display: flex; gap: 4px; margin-top: 16px; border-bottom: 1px solid var(--border); }
.tab {
  padding: 9px 16px; font-size: 14px; text-decoration: none; color: var(--muted);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tab:hover { color: var(--text); }
.tab.is-active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
.json-cell {
  /* 280 → 200. 감사 로그는 5열이 전부 nowrap 이라 970px 칸에서 1130px 로 넘쳤다.
     잘린 전문은 title 속성으로 본다(마크업 쪽 주석 참조). */
  font-size: 11.5px; color: var(--muted); max-width: 200px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.pager { display: flex; align-items: center; gap: 12px; margin-top: 14px; font-size: 13px; }

/* 발급된 시크릿 1회 노출 */
.token-reveal { border-color: var(--warn-border); background: var(--warn-bg); }
.token-box { display: flex; align-items: center; gap: 8px; margin: 10px 0; }
.token-box code {
  flex: 1; background: var(--panel); border: 1px solid var(--border); border-radius: 8px;
  padding: 9px 12px; font-size: 13px; word-break: break-all;
}

/* ==========================================================================
   관리자 페이지 고정 높이 레이아웃 (계정 관리 · 채널 연동 · 조직 관리)
   페이지 전체가 아니라 내부 리스트/폼만 스크롤되도록 content-fixed 안에서 동작.
   ========================================================================== */

/* 고정 레이아웃 화면의 헤더/알림은 줄어들지 않게 고정하고, 본문만 남은 높이를 채운다. */
.content-fixed > .page-head,
.content-fixed > .page-head-row,
.content-fixed > .tabs,
.content-fixed > .flash-ok,
.content-fixed > .flash-error,
.content-fixed > .token-reveal { flex-shrink: 0; }

.content-fixed > .page-head { margin-bottom: 4px; }

/* 조직 선택 셀렉터(계정 관리) — 목록 머리에서 [+ 계정 추가] 바로 옆에 선다.
   ★ margin-left:auto 로 오른쪽 묶음을 만든다. 머리줄은 space-between 이라
     그냥 두면 제목·셀렉터·버튼이 셋으로 흩어져 무엇이 한 벌인지 안 보인다. */
.head-org-select { display: flex; align-items: center; gap: 8px; }
.head-org-select .muted { font-size: 13px; }
.split-pane-head .head-org-select { margin-left: auto; }
/* 폭은 상단 헤더에 있을 때(200px)보다 좁힌다 — 목록 패널이 좁아 그 폭이면
   [+ 계정 추가] 가 다음 줄로 밀린다. 높이는 기본 입력 높이(--ctrl-h)라
   옆 버튼과 이미 같다. */
.split-pane-head .head-org-select select { min-width: 0; max-width: 190px; font-size: 13px; }

/* --- 좌/우 분할 레이아웃 (고객응대 탭과 유사): 목록 좌측 · 폼 우측 --- */
.split-layout {
  display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: 16px; margin-top: 16px; flex: 1; min-height: 0;
}
/* 폼이 큰 화면(커스텀 툴)은 우측(폼)을 더 넓게 준다. */
.split-layout.split-wide-form { grid-template-columns: minmax(0, 1fr) minmax(0, 1.5fr); }
/* 각 패널은 자체적으로 스크롤한다(min-height:0 없으면 페이지 전체가 밀린다). */
.split-pane {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: 12px; display: flex; flex-direction: column;
  min-height: 0; overflow: hidden;
}
.split-pane-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 14px 18px; border-bottom: 1px solid var(--border); flex-shrink: 0;
}
.split-pane-head h2 { margin: 0; font-size: 15px; }
/* 머리줄은 가운데 정렬(align-items:center)이라, 설명이 기본 위 여백(5px)을 갖고 있으면
   제목과 축이 어긋나 보인다 — 그 줄 안에서만 여백을 지운다.
   ★ 설명은 **오른쪽으로 민다**(제목 옆이 아니라 [추가] 왼쪽). space-between 이라
     그냥 두면 셋이 흩어져 설명이 줄 한가운데 떠 있었다 — 제목도 버튼도 아닌 것이
     가운데 있으면 눈이 거기서 한 번 멈춘다. */
.split-pane-head .hint { margin: 0 0 0 auto; }
/* 머리줄은 **한 줄로 둔다.** 숫자와 버튼이 줄바꿈으로 갈리면 어느 버튼이 무엇에 대한
   것인지 흐려지고, 줄 높이가 목록마다 들쭉날쭉해진다. 좁아지면 숫자 쪽이 줄어든다. */
.split-pane-head { flex-wrap: nowrap; }
.split-pane-head .head-titles { flex-wrap: nowrap; min-width: 0; }
.split-pane-head .kb-head-actions { flex: 0 0 auto; flex-wrap: nowrap; }

/* 지금 몇 개를 쓰고 있는가 — 읽으라고 두는 숫자다. 흐린 한 줄로 두었더니 버튼들
   옆에서 눈에 걸리지 않았다(사용자 지적). 라벨은 작게, 숫자는 또렷하게 가른다. */
.count-chips { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.count-chip {
  display: inline-flex; align-items: baseline; gap: 5px; flex: 0 0 auto;
  padding: 3px 9px; border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid var(--border);
  font-size: 11.5px; color: var(--text-muted); white-space: nowrap;
}
.count-chip b { font-size: 13px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
/* 상단 패딩 0 — 테이블 헤더(sticky)가 스크롤 영역 최상단에 딱 붙게 한다. */
.split-pane-body {position: relative;flex: 1;min-height: 0;overflow: auto;padding: 0 18px 18px;}
.split-pane-body .data-table { margin-top: 0; }
/* 폼은 헤더 바로 아래 붙지 않도록 위쪽 여백을 준다(테이블은 붙여서 sticky 유지). */
.split-pane-body > form { padding-top: 14px; }
.split-pane-body .card { border: 0; padding: 0; margin-top: 0; background: transparent; }
.split-pane-body .muted {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
}

/* 상담 템플릿 폼: 이름(상단 고정) · 내용(남은 영역 꽉 채움) · 저장/취소/추가(하단 고정).
   폼이 패널 높이를 그대로 채우고 스크롤 대신 textarea 가 늘어난다. */
.tpl-form-body { overflow: hidden; }
.tpl-form { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.tpl-form > .form-grid,
.tpl-form > .checkline,
.tpl-form > .hint,
.tpl-form > .form-actions { flex-shrink: 0; }
.tpl-form > .form-actions { margin-top: 12px; }
/* 내용 필드가 남은 세로 공간을 모두 차지하고, 그 안의 textarea 가 꽉 찬다. */
.tpl-form > .field { flex: 1 1 auto; min-height: 0; }
.tpl-form > .field textarea { flex: 1 1 auto; min-height: 0; height: auto; resize: none; }

/* ---------- 글자 수 ----------
   한도(maxlength)가 걸린 칸은 **얼마나 남았는지**를 그 자리에서 보여 준다. 종전에는
   한도가 있다는 사실이 화면 어디에도 없었다(템플릿 내용 한 칸만 예외였다) — 브라우저는
   한도를 넘는 입력을 **아무 표시 없이 버리므로**, 길게 쓰다 보면 입력이 조용히 멈춘 것처럼
   보인다. 그 순간에 무슨 일이 일어났는지 알려 주는 것이 이 숫자다.

   ★ 숫자는 칸의 **형제**로 붙고 absolute 로 뜬다(console.js). 칸을 감싸지 않는 이유는
     실측 사고다 — 감쌌더니 `.reply-form textarea { flex: 1 }` 처럼 부모가 칸에 직접 걸어 둔
     배치가 끊겨 상담 입력창이 무너졌다. absolute 는 플렉스·그리드 배치에서 빠지므로
     부모가 무엇이든 끼어들지 않는다. 좌표는 스크립트가 잰다.
   ★ 글자가 숫자 밑으로 흐르지 않도록 칸 아래쪽 안여백을 늘린다 — 위치만 잡고 자리를
     안 만들면 값과 숫자가 겹친다. */
textarea.has-char-count { padding-bottom: 24px; }
.char-count {
  position: absolute; pointer-events: none; white-space: nowrap;
  font-size: 11px; font-variant-numeric: tabular-nums;
  color: var(--text-subtle); background: var(--panel);
  padding: 1px 2px 1px 7px; border-radius: 4px;
}
/* 한도에 닿으면 색으로 말한다 — 그때부터는 더 쳐도 글자가 안 들어간다. */
.char-count.at-max { color: var(--danger-fg); font-weight: 600; }

/* --- 목록을 꽉 채우는 단일 패널 (채널 연동 · 조직 관리) --- */
.list-panel {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: 12px; display: flex; flex-direction: column;
  flex: 1; min-height: 0; overflow: hidden; margin-top: 16px;
}
.list-panel-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 14px 18px; border-bottom: 1px solid var(--border); flex-shrink: 0;
}
.list-panel-head h2 { margin: 0; font-size: 15px; }
.list-panel-body { flex: 1; min-height: 0; overflow: auto; padding: 0 18px 18px; }
/* ★ 말풍선이 있는 목록에는 **위쪽 자리**를 만든다. 이 영역은 `overflow: auto` 라 위로
   삐져나간 것은 잘린다 — 첫 줄 아이콘의 말풍선이 통째로 잘려 hover 해도 아무 일도
   일어나지 않는 것처럼 보였다(실측: 필요한 높이 33px · 실제 여백 26px).
   `:has()` 로 걸어 둔다 — 말풍선을 쓰는 목록만 자리를 얻고, 나중에 다른 목록이 쓰면
   그때 저절로 따라온다(다른 목록의 위 여백을 지금 건드리지 않는다). */
.list-panel-body:has(.tipbtn) { padding-top: 14px; }
.list-panel-body .data-table { margin-top: 0; }

/* 리스트 스크롤 시 컬럼 헤더(thead) 를 상단에 고정한다.
   스크롤 컨테이너는 *-body 자신이므로 top:0 이 그 최상단에 붙는다. */
.split-pane-body .data-table thead th,
.list-panel-body .data-table thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--panel);
}

/* 리스트 타이틀 우측의 + 추가 버튼(원형). */
/* ---------- 목록에 항목 추가 ----------
   ★ 한 가지 모양만 쓴다. 종전에는 셋이었다 — 30px 짜리 아이콘 '+'(채널·조직),
     글자만 있는 버튼(툴·웹챗), 점선 테두리의 넓은 버튼(멀티조직). 같은 일을 하는데
     생김새가 다르면 화면을 옮길 때마다 '추가가 어디 있지'를 다시 찾는다.
     특히 아이콘만 있는 30px 은 좁은 화면에서 무엇을 더하는 버튼인지 알 수 없었다.

   좁은 화면에서는 라벨을 접고 아이콘만 남긴다 — 목록 머리에 제목과 나란히 서므로
   글자까지 두면 제목이 밀린다. aria-label 은 그대로라 읽어 주는 이름은 사라지지 않는다. */
.list-add-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  height: 32px; padding: 0 12px; border: 0; border-radius: var(--r-sm);
  background: var(--accent); color: var(--on-accent);
  font-size: 13px; font-weight: 600; line-height: 1;
  cursor: pointer; flex-shrink: 0; text-decoration: none;
}
.list-add-btn:hover { filter: brightness(0.95); }
.list-add-btn .icon { width: 16px; height: 16px; stroke-width: 2; }
@media (max-width: 560px) {
  .list-add-btn { padding: 0; width: 34px; }
  .list-add-btn > span { display: none; }
}

/* 모달 안의 폼이 카드 껍데기를 다시 두르지 않도록. */
.modal-box > form .card { border: 0; padding: 0; margin: 0; background: transparent; }

/* ==================== 커스텀 툴: 카드 목록 ==================== */
.tool-cards { display: flex; flex-direction: column; gap: 10px; margin-top: 4px; }
.tool-card {
  border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px;
  background: var(--panel); display: flex; flex-direction: column; gap: 8px;
}
.tool-card.is-inactive { opacity: 0.62; }
.tool-card-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.tool-card-name { font-size: 13.5px; font-weight: 600; }
.tool-card .tag { margin-left: 0; }
.tag.ok { background: var(--success-bg); color: var(--success-fg); border-color: var(--success-border); }
.tag.off { background: var(--surface-2); color: var(--muted); }
.tool-card-desc {
  margin: 0; font-size: 12.5px; color: var(--muted); line-height: 1.5;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.tool-card-meta { display: flex; gap: 6px; flex-wrap: wrap; }
.pill {
  display: inline-block; font-size: 11px; color: var(--text);
  background: var(--surface-2); border-radius: 999px; padding: 2px 9px;
}
.pill.method { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10.5px; }
.tool-card-url { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; min-width: 0; }
.tool-card-url .webhook-url { max-width: 100%; }
.tool-card-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 2px; }
/* 가져오기 소스 카드의 마지막 줄 — 왼쪽은 경고, 오른쪽은 조작.
   ⚠ 종전에는 [실행]을 제목 줄 오른쪽 끝에 못박아(absolute) 두었다가, 바로 아래 주소
     줄과 맞붙어 **붙어 보인다**는 지적을 받았다. 흐름 안의 한 줄로 되돌린다 —
     띄우는 자리는 카드의 `gap` 이 이미 정해 두고 있다. */
.tool-card-foot { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.tool-card-flags { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; min-width: 0; }
/* 경고가 없어도 조작은 오른쪽에 선다(빈 칸이 왼쪽을 밀어 준다). */
.tool-card-foot > .tool-card-actions { margin-left: auto; margin-top: 0; }
.tool-card-foot .btn-secondary { gap: 5px; }
/* 테스트 결과는 팝업 안에서 **제 자리만큼만** 쓴다.
   ⚠ 목록 아래에 있을 때는 길어도 페이지가 길어질 뿐이었는데, 팝업 본문 안으로 들어오니
     그대로 본문을 밀어냈다(실측: 매핑 미리보기 20건에 34,806px — 저장 단추가 3만 픽셀
     아래로 갔다). 결과는 훑어보는 것이라 높이를 묶고 안에서 구르게 한다. */
#kbTestResult { max-height: 240px; overflow: auto; margin-top: 12px; }
.tool-card-actions form { margin: 0; }
.btn-secondary.sm.danger-text { color: var(--danger-fg); }
.btn-secondary.sm.danger-text:hover { background: var(--danger-bg); }
.locked-desc { background: var(--surface-2); color: var(--muted); }

/* ==================== 커스텀 툴: 구조화 빌더(파라미터/본문/쿼리) ==================== */
.field.builder { gap: 6px; }
.builder-head-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.toggle-inline { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); }

/* 열 머리글 + 각 행의 그리드 — 파라미터(키·타입·형식·허용값·설명·필수)와 키/값(2열)을 분리한다. */
.param-head, .param-row {
  display: grid;
  grid-template-columns: minmax(84px, 1fr) 88px 84px minmax(90px, 1.1fr) minmax(90px, 1.3fr) 44px 28px;
  gap: 6px; align-items: center;
}
.kv-head, .body-row, .query-row {
  display: grid; grid-template-columns: minmax(90px, 1fr) minmax(120px, 1.6fr) 28px;
  gap: 6px; align-items: center;
}
/* 응답 매핑 빌더 — 출력키 / dot-path / 설명 / 단위 / 삭제. */
.map-head, .map-row {
  display: grid;
  grid-template-columns: minmax(70px, 1fr) minmax(80px, 1.2fr) minmax(80px, 1.3fr) 64px 28px;
  gap: 6px; align-items: center;
}
.map-row { margin-bottom: 0; }
/* 열 머리글 — 폼 라벨(.field > span)과 같은 크기·굵기로 맞춘다.
   종전에는 11px/10px 라 같은 화면 안에서 이 줄만 유독 작았다. */
.param-head, .kv-head, .map-head {
  font-size: 12px; color: var(--muted); font-weight: 600;
  padding: 0 2px 6px; align-items: end;
}
.param-head span, .kv-head span, .map-head span { padding-left: 2px; }
/* 파라미터 헤더는 라벨 + 짧은 부제(무엇을 넣는지)를 두 줄로 보여 준다. */
.param-head span { display: flex; flex-direction: column; gap: 1px; }
.param-head span b { font-weight: 600; color: var(--text); font-size: 12px; }
.param-head span em {
  font-style: normal; font-weight: 400; font-size: 11px;
  color: var(--text-muted); line-height: 1.3;
}
/* 빌더 상단 안내 — 무엇을·왜 입력하는지 예시로 설명. */
.builder-intro {
  font-size: 12.5px; color: var(--muted); line-height: 1.6;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 10px 12px; margin: 2px 0 10px;
}
.builder-intro code, .builder-intro b { color: var(--text); }
/* 행 하나가 하나의 덩어리로 읽히게 한다 — 종전에는 입력칸만 떠 있어 어디까지가
   한 행인지 눈으로 가늠이 안 됐다. */
.param-row, .body-row, .query-row, .map-row {
  padding: 5px 6px; border-radius: var(--r-sm); margin-bottom: 2px;
}
/* ★ 열 머리글에 **행과 같은 좌우 여백**을 준다. 행에만 6px 이 있으면 머리글의 칸
   경계가 행의 칸 경계와 어긋난다 — 실측(1200px): 첫 칸이 머리글 319.2px · 행 316.1px,
   시작 x 가 165 대 169 로 4px 밀려 '필드'가 그 아래 입력칸과 안 맞아 보였다.
   같은 그리드를 두 요소가 나눠 그리는 구조라, 바깥 상자가 같아야 칸도 같아진다. */
.param-head, .kv-head, .map-head { padding-left: 6px; padding-right: 6px; }
.param-row:hover, .body-row:hover, .query-row:hover, .map-row:hover { background: var(--surface-2); }
/* ⚠ 체크박스는 뺀다. 이 규칙은 **글자 칸**의 키를 맞추는 것인데, 체크박스까지 걸리면
   네모가 30px 로 부풀어 그 줄만 커 보인다(--ctrl-h-sm 을 정의하자마자 드러났다 —
   전에는 토큰이 없어 선언이 통째로 버려지던 자리다). */
.param-row input:not([type="checkbox"]), .param-row select,
.body-row input:not([type="checkbox"]), .query-row input:not([type="checkbox"]),
.map-row input:not([type="checkbox"]), .map-row select {
  height: var(--ctrl-h-sm); padding: 0 9px; font-size: 13px; width: 100%; min-width: 0;
  /* 높이를 못박았으니 글자 크기도 같아야 한다 — 좁은 화면의 입력 기본값(14px)이
     셀렉트(13px)와 갈리면 나란히 놓인 두 칸의 기준선이 어긋난다. */
  line-height: 1;
}
.p-req-wrap { display: flex; align-items: center; justify-content: center; }
/* 넓은 화면에서는 열 머리글('필수')이 그 말을 하므로 글자 라벨을 감춘다.
   좁은 화면에서는 머리글이 사라져 이 글자가 유일한 설명이 된다. */
.p-req-label { display: none; }
@media (max-width: 768px) { .p-req-label { display: inline; } }
.row-remove {
  border: 0; background: transparent; color: var(--text-subtle);
  width: 28px; height: 28px; cursor: pointer; padding: 0; border-radius: 6px;
}
.row-remove:hover { background: var(--danger-bg); color: var(--danger-fg); }
.is-dup { border-color: var(--danger-border) !important; background: var(--danger-bg); }
/* 조회키(key) 행 — 잠금 상태를 시각적으로 구분(입력은 readonly/disabled 로 잠긴다). */
.param-row.is-keyrow { background: var(--accent-soft); box-shadow: inset 0 0 0 1px var(--accent-border); }
/* 파라미터 칸을 감싼 라벨. 넓은 화면에서는 **열 머리글이 이름을 말하므로** 아무것도
   그리지 않고 칸만 통과시킨다(그리드 자리는 이 래퍼가 차지한다). */
.p-cell { display: block; min-width: 0; }
.p-cell::before { display: none; }

/* ── 좁은 화면: 반복 입력 행을 가로 7칸에서 **세로 카드**로 ──
   ⚠ 이 블록은 위 기본 정의 **뒤에** 있어야 한다. 미디어쿼리는 우선순위를 올려 주지
     않으므로, 앞에 두면 뒤의 `display: grid; grid-template-columns: …` 가 이긴다
     (실측: 규칙을 앞에 뒀더니 열 머리글이 그대로 grid 였고 행 폭도 558px 그대로였다). */
@media (max-width: 768px) {
  /* 실측(375px): 파라미터 행이 7칸 그리드라 최소 폭이 **556px** 이다.
     실측(375px): 파라미터 행이 7칸 그리드라 최소 폭이 **556px** 이다. 그래서 팝업
     본문이 가로로 600px 까지 밀렸고(창 373px), '필수'와 삭제 단추에 닿으려면 옆으로
     밀어야 했다 — 붙여 둔 탭 줄까지 함께 밀린다.
     좁은 화면에서는 한 파라미터를 **한 장의 카드**로 세로로 쌓는다.
     ★ 열 머리글은 감춘다(스택에서는 어느 칸 위에 있는 머리인지 짝지을 수 없다).
       대신 각 칸의 placeholder 가 그 말을 한다 — 이미 다 붙어 있다.
     ★ 체크박스만 예외라 글자 라벨을 함께 단다(org-tools.js 의 .p-req-label). */
  .param-head, .kv-head, .map-head { display: none; }
  .param-row, .body-row, .query-row, .map-row {
    display: grid; grid-template-columns: minmax(0, 1fr) 28px; gap: 6px;
    align-items: center;
    padding: 10px; margin-bottom: 8px;
    border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--panel);
  }
  /* 삭제 단추는 첫 줄 오른쪽 끝에 세우고, 나머지 칸은 왼쪽 열에 차례로 쌓는다.
     ★ align-self:end — 첫 칸에는 **이름표가 한 줄 얹혀 있다.** 칸 전체 기준으로
       가운데를 맞추면 단추가 입력칸보다 위로 뜬다(실측 10px). 아래로 붙여
       입력칸과 바닥을 맞춘다(높이도 같은 36px 이라 가운데까지 맞는다). */
  .param-row > .row-remove, .body-row > .row-remove,
  .query-row > .row-remove, .map-row > .row-remove { grid-column: 2; grid-row: 1; align-self: end; }
  .param-row > :not(.row-remove), .body-row > :not(.row-remove),
  .query-row > :not(.row-remove), .map-row > :not(.row-remove) { grid-column: 1; }
  /* 스택이라 폭에 여유가 생겼다 — 30px 은 손가락으로 누르기 작다. */
  .param-row input:not([type="checkbox"]), .param-row select,
  .body-row input:not([type="checkbox"]), .query-row input:not([type="checkbox"]),
  .map-row input:not([type="checkbox"]), .map-row select { height: 36px; font-size: 14px; }
  /* 삭제 단추도 같은 키로 — 28px 짜리를 36px 입력 옆에 바닥만 맞춰 두면 4px 높아
     보인다(실측). 높이를 맞추면 가운데가 저절로 맞는다. */
  .param-row > .row-remove, .body-row > .row-remove,
  .query-row > .row-remove, .map-row > .row-remove { width: 28px; height: 36px; }
  .p-req-wrap { justify-content: flex-start; gap: 8px; }
  .p-req-label { font-size: 12.5px; color: var(--muted); }

  /* 파라미터 행만 두 칸으로 짠다 — 짧은 칸(타입·형식)을 한 줄에 나란히 놓으면
     한 줄이 줄어든다(실측: 카드 179 → 149px, 4개면 120px). 나머지 반복 행은
     칸이 둘뿐이라 그대로 세로로 쌓는다. */
  .param-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 28px; align-items: end; }
  .param-row > .p-cell-key { grid-column: 1 / 3; grid-row: 1; }
  /* 파라미터 행만 3열이라 단추가 3번 칸에 선다(정렬은 위 공통 규칙이 준다). */
  .param-row > .row-remove { grid-column: 3; grid-row: 1; }
  .param-row > .p-cell-type { grid-column: 1; grid-row: 2; }
  .param-row > .p-cell-format { grid-column: 2; grid-row: 2; }
  .param-row > .p-cell-enum { grid-column: 1 / 3; grid-row: 3; }
  .param-row > .p-cell-desc { grid-column: 1 / 3; grid-row: 4; }
  .param-row > .p-req-wrap { grid-column: 1 / 3; grid-row: 5; }
  /* ★ 여기서는 열 머리글이 없다 — 값이 채워진 칸은 placeholder 도 안 보이니
     **무엇을 적는 칸인지 유추해야 했다.** 칸마다 이름을 붙인다(글자는 org-tools.js 가
     로케일에서 실어 준 data-label). */
  .p-cell::before {
    content: attr(data-label);
    display: block; margin-bottom: 3px;
    font-size: 11.5px; font-weight: 560; color: var(--muted);
  }
  /* 짝을 이루는 두 칸(타입·형식)은 라벨 높이가 같아 바닥이 저절로 맞는다.
     align-items:end 는 그 밖의 한 칸짜리 줄이 위로 뜨지 않게 한다. */
}

.param-row.is-keyrow:hover { background: var(--accent-soft); }
.param-row.is-keyrow input[readonly], .param-row.is-keyrow select:disabled {
  background: var(--surface-2); color: var(--muted); cursor: not-allowed;
}
/* 조회키 사용 고지 — 파라미터 빌더 아래에 뜨는 안내 박스. */
.keyguard-notice {
  display: flex; gap: 9px; align-items: flex-start; margin-top: 10px;
  padding: 10px 12px; border: 1px solid var(--accent-border); background: var(--accent-soft);
  border-radius: var(--r-sm);
  font-size: 12.5px; line-height: 1.6; color: var(--accent);
}
.keyguard-notice[hidden] { display: none; } /* flex 가 hidden 을 덮지 않도록 */
.keyguard-notice .keyguard-ico { flex: none; }
.param-empty { font-size: 12.5px; padding: 8px 2px; }
/* ★ .field 는 flex 컬럼이라 그 안의 버튼은 기본이 stretch 다 — 가로로 늘어나
   빈 막대처럼 보였다. 폼 안의 버튼은 내용 폭만 차지하게 못박는다. */
.builder-add { margin-top: 6px; }
.field > .btn-secondary, .field > .btn-primary, .field > .builder-add,
.builder > .btn-secondary, .builder > .btn-primary { align-self: flex-start; }
.builder-actions { display: flex; gap: 8px; margin-top: 4px; flex-wrap: wrap; align-items: center; }
/* ★ 묶음 안에서는 .builder-add 의 위 여백을 걷는다. 그 6px 은 **혼자 목록 밑에 설 때**
   숨통을 트려고 준 것인데, 옆에 다른 버튼이 서면 자기만 6px 내려앉아 줄이 어긋난다
   (실측: [+ 필드 추가]가 [파라미터로 채우기]보다 6px 아래 — 두 폭 모두). */
.builder-actions > .builder-add { margin-top: 0; }
.token-chip {
  display: inline-block; border: 1px solid var(--border); background: var(--surface-2);
  color: var(--accent); border-radius: 6px; padding: 1px 7px; margin: 2px 3px 0 0;
  font-size: 11.5px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; cursor: pointer;
}
.token-chip:hover { background: var(--accent-soft); }
#bodyTokens { display: block; margin-top: 6px; }

/* ==================== 지식베이스: 2단 레이아웃 · 카드 · 공지 ==================== */
/* 페이지가 고정 높이(content-fixed)가 아니라 일반 스크롤이므로, 내부 스크롤에
   의존하지 않는 단순 2열 그리드로 구성한다(좌 목록 / 우 편집). */
.kb-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
  gap: 18px;
  align-items: start;
  margin-top: 18px;
}
.kb-split > .card { margin-top: 0; }
/* .kb-split 1열화는 아래 1024px 통합 블록이 담당한다. */

/* 카드 헤더 우측 액션 묶음(스키마 편집·엔티티 추가·자동 갱신 설정 등). */
.kb-head-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* 선택된 소스 카드 강조. */
.tool-card.is-selected { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }

/* Notify API 링크 공지(편집 불가, 인지용). */
/* ── 외부 서버 연결 정보(주소·토큰) ──
   종전에는 파란 상자(.kb-notice)로 묶었다. 그 색은 **소스 목록 위에 얹혀 있을 때**
   둘을 가르려고 준 것인데, 화면을 갈라 이 내용만 남은 뒤로는 가를 상대가 없다.
   색으로 묶는 대신 항목 사이에 가는 선을 둔다 — 카드 안에서 같은 종류의 줄이 이어진다. */
.conn-row { padding: 14px 0; border-top: 1px solid var(--border); }
.conn-row:first-of-type { border-top: 0; padding-top: 6px; }
.conn-label { font-size: 12px; font-weight: 600; color: var(--text); margin-bottom: 6px; }
.conn-value { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
.conn-value .webhook-url { max-width: 100%; }
.conn-value form { margin: 0; }
.conn-desc { font-size: 12px; color: var(--text-muted); line-height: 1.5; margin: 6px 0 0; }
/* ── 주소 한 줄: 통째로 보이고, 복사는 아이콘 하나 ──
   종전에는 말줄임(.webhook-url, max-width 380px)이라 주소 뒷부분이 잘려 있었다. 여기 붙는
   값은 조직 id 가 꼬리에 오는 주소라 **잘리는 곳이 하필 서로 다른 부분**이고, 두 주소가
   똑같아 보였다. 칸을 한 줄로 펴고 눌러서 통째로 선택되게 한다(user-select: all).
   ⚠ 접어서 여러 줄이 되게 둔다 — `overflow-x` 로 밀면 가로 스크롤 상자가 되어 세로도
     함께 잘린다(이 파일의 .origin-row 사고와 같은 자리다). */
.conn-code-row { display: flex; align-items: flex-start; gap: 6px; min-width: 0; }
.conn-code {
  flex: 1 1 auto; min-width: 0;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
  padding: 8px 10px; font-size: 12px; line-height: 1.5;
  overflow-wrap: anywhere; user-select: all;
}
/* 복사 단추는 글자 없이 아이콘만 — 주소 옆에서 같은 말('복사')이 두 번 서지 않는다.
   누른 뒤의 확인은 console.js 가 붙이는 상태 클래스로 보인다(글자를 갈아 끼우면
   아이콘이 지워진다). */
.conn-copy { margin-left: 0; }
.copy-btn.is-copied { color: var(--success-fg); background: var(--success-bg); }
/* 요청 본문 형식 — **펼쳐 둔다.** 접어 두었더니(details) 연동을 만드는 사람이 이 화면을
   열고도 계약이 있는 줄 몰랐다. 나열 대신 두 칸(이름·설명)으로 세워 훑게 한다. */
.conn-modes { margin: 8px 0 0; }
.conn-modes dt { font-size: 12px; font-weight: 600; color: var(--text); }
.conn-modes dd {
  margin: 2px 0 8px; font-size: 12px; line-height: 1.5; color: var(--text-muted);
}
.conn-modes dd:last-child { margin-bottom: 0; }
.conn-fields {
  display: grid; grid-template-columns: minmax(0, max-content) minmax(0, 1fr);
  gap: 4px 12px; margin: 8px 0 0;
}
.conn-fields dt { font-size: 11px; }
.conn-fields dd { margin: 0; font-size: 12px; line-height: 1.5; color: var(--text-muted); }
.conn-fields code { font-size: 11px; }
/* 옛 토큰을 그대로 등록하는 자리. 본문에서 한 칸 들여 "이 줄에 딸린 것"으로 읽히게 한다. */
.conn-adopt {
  margin-top: 10px; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface-2);
}
.conn-adopt > .conn-desc:first-child { margin-top: 0; }
.conn-spec .code-block {
  background: var(--code-bg); color: var(--code-fg); border-radius: 8px; padding: 10px 12px;
  font-size: 11.5px; line-height: 1.5; overflow-x: auto; margin: 6px 0; white-space: pre;
}
.conn-spec .code-block code { color: inherit; background: none; padding: 0; }
/* ── 한 주소 = 한 줄, 그 안에 요청·응답 ──
   종전에는 두 주소가 나란히 서고 '요청 본문 형식'이 **그 아래 따로** 있었다. 실제로는
   알림(POST) 전용 본문인데 화면에서는 어느 쪽 것인지 알 수 없었고, 상태 확인은 GET 이라
   본문이 아예 없다는 말은 어디에도 없었다. 계약을 주소 줄 **안으로** 들여 붙인다.
   왼쪽 선은 "위 주소에 딸린 것"이라는 표시다 — 색 상자로 묶으면 카드 안에 상자가 겹친다. */
.conn-sub {
  margin-top: 12px; padding-left: 12px;
  border-left: 2px solid var(--border);
}
.conn-subtitle { font-size: 12px; font-weight: 600; color: var(--text); margin: 0 0 4px; }
.conn-subtitle.sub { margin-top: 10px; }
.conn-sub > .conn-desc:first-of-type { margin-top: 0; }
/* 메서드 딱지 — 주소 이름만으로는 POST 인지 GET 인지 알 수 없었다(그 차이가 곧
   "본문을 보내는가"다). 이름 앞에 붙여 눈이 먼저 닿게 한다. */
.conn-verb {
  display: inline-block; margin-right: 6px; padding: 1px 6px;
  border-radius: 4px; background: var(--surface-2); border: 1px solid var(--border);
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em; color: var(--text-muted);
  vertical-align: 1px;
}
/* 좁은 화면에서는 이름·설명 두 칸을 위아래로 푼다.
   ★ `sourceUpdatedAt`·`manualOverride` 처럼 긴 이름이 max-content 로 첫 칸을 밀어
     설명 칸이 한 글자씩 끊겼다. 미디어 쿼리는 특정도를 더하지 않으니 **원 규칙 뒤**에 둔다. */
@media (max-width: 640px) {
  .conn-fields { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .conn-fields dd { margin-bottom: 6px; }
}

/* ---------- 업데이트 설정: 갈래마다 스위치 하나 ----------
   종전에는 체크박스 둘 사이에 주기 선택이 늘 끼어 있었다 — 주기를 안 쓰는 조직도 그 칸을
   지나가야 했고, 그 칸이 어느 체크박스에 딸린 것인지도 애매했다. 갈래를 줄로 가르고
   주기 선택은 **그 갈래가 켜져 있을 때만** 보인다. */
.upd-field { padding: 12px 0; border-top: 1px solid var(--border); }
.upd-field:first-of-type { border-top: 0; padding-top: 4px; }
.upd-field > .switch-line { margin-top: 0; }
/* 스위치 폭(38) + 사이 간격(11) 만큼 들여 "이 스위치에 딸린 칸"으로 읽히게 한다. */
.upd-interval { margin: 10px 0 0 49px; max-width: 240px; }
/* ★ 여닫는 판정은 여기(CSS)가 한다. 스크립트로 하면 JS 가 늦거나 죽었을 때 꺼져 있는데
     칸이 보이거나, 켰는데 안 나온다 — 상태의 진실원천은 체크박스 자신이다. */
.upd-field:has(input[name="pollEnabled"]:not(:checked)) > .upd-interval { display: none; }

/* ---------- 가져오기 설정: 좌측 세로 메뉴 (알림 설정 / 소스) ----------
   이 화면은 성격이 다른 둘을 담는다 — **외부가 우리를 부르는 계약**(알림·조회 URL·토큰·
   자동 갱신)과 **우리가 외부를 부르는 목록**(소스). 한 장에 쌓아 두었더니 계약 설명이 늘
   목록 위에 서서, 소스를 보러 온 사람이 매번 그것을 지나가야 했다
   (실측 첫 소스 카드 y: PC 1280 → 508px · 모바일 375 → 741px).

   ⚠ 상단 2차 탭(.stab)·출처 탭(.origin-tab)과 **또 다른 모양**을 쓴다. 저 둘은 각각
     화면을 옮기고 목록을 거른다. 이건 한 화면 안에서 **영역을 가른다** — 셋이 같은
     생김새면 어느 것이 무엇을 하는지 눌러 봐야 안다(.origin-tab 주석과 같은 이유). */
.kb-sec-layout {
  display: grid; grid-template-columns: 220px minmax(0, 1fr);
  gap: 16px; margin-top: 16px;
  /* 메뉴가 본문 높이만큼 늘어나지 않게 — 늘어나면 활성 항목 배경이 화면 끝까지 흐른다. */
  align-items: start;
}
.kb-sec-tabs { display: flex; flex-direction: column; gap: 4px; }
.kb-sec-tab {
  display: flex; align-items: center; gap: 8px; padding: 9px 12px;
  border: 1px solid transparent; border-radius: var(--r-sm);
  color: var(--text-muted); font-size: 13px; font-weight: 600;
  text-decoration: none; white-space: nowrap;
}
.kb-sec-tab:hover { background: var(--surface-2); color: var(--text); }
.kb-sec-tab.is-active { border-color: var(--accent-border); background: var(--accent-soft); color: var(--accent); }
.kb-sec-tab .icon { flex: 0 0 auto; }
/* ★ 격자 칸의 **자동 최소 너비**를 푼다. 좁은 화면에서 칸이 `1fr` 이면 그 값은
   `minmax(auto, 1fr)` 이라, 안에 접히지 않는 것이 하나라도 있으면 칸이 그만큼 넓어진다.
   본문 패딩이 아니라 `overflow: hidden` 이 그 초과분을 잘라서, 화면에는 **넘친 흔적 없이
   오른쪽이 사라진다**(실측 375px: 요청 본문 예시를 늘 펼치자 칸이 589px 이 됐고,
   body.scrollWidth 는 375 그대로였다 — 가로 넘침 검사로는 잡히지 않는다). */
.kb-sec-body { min-width: 0; }
/* .card 는 위 여백 16px 을 갖고 있다 — 첫 카드가 메뉴 첫 항목과 같은 줄에서 시작하게. */
.kb-sec-body > .card:first-child,
.kb-sec-body > .kb-notify-wrap > .card:first-child,
.kb-sec-body > .kb-sync-wrap > .card:first-child { margin-top: 0; }

/* ── 화면 높이에 맞추고 **안쪽만** 스크롤한다 ──
   페이지가 내용만큼 길어지면 좌측 메뉴가 화면 밖으로 밀려 올라간다 — 소스를 여럿 등록한
   조직에서는 다른 자리로 옮기려고 매번 맨 위까지 되돌아와야 했다. 본문(.content-list-fill)이
   이미 화면 높이에 고정돼 있으므로, 남는 높이를 이 레이아웃이 받아 안에서 스크롤한다.
   ⚠ flex/grid 자식이 스크롤되려면 `min-height: 0` 이 필요하다. 없으면 자동 최소 높이가
     내용 높이라 상자가 줄지 않고, 잘리는 곳이 화면 밖으로 나간다.
   ⚠ 행 높이를 `minmax(0, 1fr)` 로 못박는다 — `auto` 면 행이 내용만큼 커져서 위 min-height
     가 무의미해진다. 메뉴는 `align-items: start` 그대로라 자기 높이만 쓴다(활성 항목
     배경이 화면 끝까지 흐르지 않게). */
.content-list-fill .kb-sec-layout {
  flex: 1 1 auto; min-height: 0;
  grid-template-rows: minmax(0, 1fr);
}
.content-list-fill .kb-sec-body {
  align-self: stretch; min-height: 0; display: flex; flex-direction: column;
}
.content-list-fill .kb-sec-body > * { flex: 1 1 auto; min-height: 0; }
/* 업데이트 설정: 카드가 둘이라 가를 자리가 없다 — 본문 전체가 안에서 스크롤된다. */
.content-list-fill .kb-notify-wrap { overflow-y: auto; }
/* 소스: 카드 머리(제목·[소스 추가]·설명)는 제자리에 두고 **목록만** 스크롤한다. */
.content-list-fill .kb-sync-wrap { display: flex; flex-direction: column; }
.content-list-fill .kb-sync-wrap > .card {
  flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column;
}
.content-list-fill .kb-sync-wrap > .card > .tool-cards {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
}
/* 비어 있을 때도 남는 높이를 쓴다 — 위에 붙어 있으면 아래가 통째로 빈 채 남는다. */
.content-list-fill .kb-sync-wrap > .card > .empty-state { flex: 1 1 auto; }
/* 시험 호출 결과는 목록을 밀어내지 않는다(길이가 응답에 달려 있다). */
.content-list-fill .kb-sync-wrap > .card > .prompt-preview {
  flex: 0 0 auto; max-height: 34%; overflow: auto; margin: 10px 0 0;
}

/* ── 좁은 화면: 세로 메뉴를 가로 한 줄로 눕힌다 ──
   세로로 둔 채 접으면 본문이 220px 을 뺏겨 소스 URL 이 한 글자씩 접힌다.
   ⚠ 이 규칙은 반드시 **위 기본 정의 뒤**에 있어야 한다 — 이 파일의 미디어쿼리는
     우선순위를 올려 주지 않는다(@media 는 특정도를 더하지 않는다). */
@media (max-width: 768px) {
  .kb-sec-layout { grid-template-columns: 1fr; gap: 10px; }
  /* 한 칸이 되면 행이 둘이다(메뉴 줄 / 본문). 넓은 화면의 `minmax(0, 1fr)` 을 그대로 두면
     그 값이 **첫 행(메뉴 줄)** 에 걸려 메뉴가 화면 높이를 다 먹는다. */
  .content-list-fill .kb-sec-layout { grid-template-rows: auto minmax(0, 1fr); }
  .kb-sec-tabs {
    flex-direction: row; overflow-x: auto; scrollbar-width: none;
    /* 가로 스크롤 상자가 되면 자동 최소 높이가 0 으로 풀린다(.origin-row 에서 실측된
       사고와 같은 자리다). 이 줄은 눌리면 안 되므로 바닥을 못박는다. */
    flex: 0 0 auto;
  }
  .kb-sec-tabs::-webkit-scrollbar { display: none; }
  .kb-sec-tab { flex: 0 0 auto; min-height: 36px; border-color: var(--border); }
}

/* 넓은 모달(스키마 편집: 좌 카드 / 우 추가). */
.modal-box.modal-wide { width: min(920px, 94vw); }

/* 동기화 소스 "전달받을 데이터 형태" 빌더. */
.kbfield-head, .kbfield-row {
  display: grid; grid-template-columns: minmax(90px, 1.3fr) minmax(90px, 1.1fr) 116px 28px;
  gap: 6px; align-items: center;
}
.kbfield-row { margin-bottom: 6px; }
.kbfield-row input, .kbfield-row select { font-size: 13px; padding: 6px 8px; width: 100%; min-width: 0; }
.kb-fixed-shape {
  display: flex; flex-direction: column; gap: 6px;
  border: 1px solid var(--border); border-radius: 8px; background: var(--surface-2); padding: 10px 12px;
}
.kb-fixed-row { display: flex; align-items: center; gap: 10px; font-size: 13px; }
.kb-fixed-label { min-width: 64px; color: var(--muted); font-size: 12px; }
.kb-example { margin-top: 6px; max-height: 240px; overflow: auto; }

/* 오래 걸리는 작업(외부 연동 최신화·실행) 로딩 오버레이. */
.busy-overlay {
  position: fixed; inset: 0; height: var(--vh); z-index: 200;
  display: flex; align-items: center; justify-content: center;
  background: var(--scrim);
}
.busy-overlay[hidden] { display: none; }
.busy-box {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  background: var(--panel); border: 1px solid var(--border); border-radius: 12px;
  padding: 22px 28px; box-shadow: var(--sh-lg);
}
.busy-spinner {
  width: 34px; height: 34px; border: 3px solid var(--border);
  border-top-color: var(--accent); border-radius: 50%;
  animation: busy-spin 0.8s linear infinite;
}
.busy-text { font-size: 13.5px; color: var(--text); text-align: center; max-width: 260px; }
@keyframes busy-spin { to { transform: rotate(360deg); } }

/* 동기화 소스 삭제 선택(보존 전환 vs 전체 삭제). */
.kb-del-choices { display: flex; flex-direction: column; gap: 12px; margin: 12px 0; }
.kb-del-choice {
  border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px;
  display: flex; flex-direction: column; gap: 6px;
}
.kb-del-choice .hint { margin: 0; }
.kb-del-choice button { align-self: flex-start; }

/* ── 검색 검증(retrieval inspector) ── */
.kb-inspect-row { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; }
.kb-inspect-q { flex: 1 1 340px; }
.kb-inspect-scope { flex: 0 0 200px; }
.kb-inspect-form .btn-primary { flex: 0 0 auto; }
.table-scroll { overflow-x: auto; }
.kb-inspect-preview {
  display: inline-block; max-width: 460px; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; vertical-align: bottom; color: var(--muted); font-size: 12px;
}
/* 정렬 가능한 헤더 */
th.sortable-col { cursor: pointer; user-select: none; white-space: nowrap; }
th.sortable-col::after { content: '⇅'; opacity: .35; margin-left: 4px; font-size: 10px; }
th.sort-asc::after { content: '▲'; opacity: .9; }
th.sort-desc::after { content: '▼'; opacity: .9; }
/* 단계·종류 태그 색상 */
.tag.dev { background: var(--dev-bg); color: var(--dev-fg); }
.tag.stage-exact { background: var(--success-bg); color: var(--success-fg); }
.tag.stage-category { background: var(--accent-soft); color: var(--accent); }
.tag.stage-fuzzy { background: var(--warn-bg); color: var(--warn-fg); }
.tag.stage-vector { background: var(--dev-bg); color: var(--dev-fg); }
.tag.stage-title { background: var(--success-bg); color: var(--success-fg); }
.tag.stage-text { background: var(--accent-soft); color: var(--accent); }
.tag.kind-entity { background: var(--accent-soft); color: var(--accent); }
.tag.kind-document { background: var(--surface-2); color: var(--muted); }

/* ── 데이터 관리 목록: 내부 스크롤 + 필터 ── */
/* 목록을 페이지가 아니라 자기 영역에서 스크롤한다(헤더는 상단 고정). */
.list-scroll { max-height: 62vh; max-height: calc(var(--vh) * 0.62); overflow: auto; border: 1px solid var(--border); border-radius: 8px; }
.list-scroll .data-table { margin-top: 0; }
.list-scroll .data-table thead th {
  position: sticky; top: 0; z-index: 2; background: var(--panel);
  box-shadow: inset 0 -1px 0 var(--border);
}
/* 목록 상단 검색(필터) 입력 */
.list-filter { display: block; margin: 4px 0 10px; }
.list-filter input {
  width: 100%; max-width: 360px; padding: 7px 10px;
  border: 1px solid var(--border); border-radius: 8px; font-size: 13.5px;
}

/* 엔티티·문서 목록 탭: 리스트가 화면 하단까지 채우도록(페이지 스크롤 대신 리스트 내부 스크롤).
   .content-list-fill = content-fixed(height:100vh flex column) + 아래 규칙. */
.content-list-fill > .page-head,
.content-list-fill > .tabs { flex: 0 0 auto; }
.content-list-fill .kb-list-card {
  flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column;
}
.content-list-fill .kb-list-card .list-scroll {
  flex: 1 1 auto; min-height: 0; max-height: none;
}

/* ── 사용량(usage) 화면 ─────────────────────────────────────────── */
.usage-filter { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end; }
.usage-field { display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--muted); }
.usage-field select, .usage-field input { padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--panel); color: var(--text); }

/* ★ **칸 최소 너비는 값이 정한다.** `auto-fill` 은 자리가 남으면 칸을 하나 더 넣고
     그만큼 **모든 칸을 좁힌다** — 그래서 창을 넓힐수록 칸이 좁아진다. 130px 일 때
     실측(1280px): 칸 137px · 안쪽 111px 인데 `812,646,763` 은 19px 굵게 117px 이라
     콤마에서 끊겨 `812,646,76` / `3` **두 줄**이 됐다(1024px 에서는 칸이 149px 라
     멀쩡했다 — 넓은 화면에서만 깨져 눈에 덜 띄었다).
     160px 이면 안쪽 134px 로, 조 단위 직전(`1,234,567,890` · 133px)까지 한 줄에 선다. */
.usage-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 10px; margin: 16px 0; }
.usage-card { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 11px 13px; display: flex; flex-direction: column; gap: 4px; }
.usage-card-label { font-size: 12px; color: var(--muted); }
/* 숫자는 **절대 줄을 바꾸지 않는다.** 콤마에서 끊긴 수는 틀리게 읽히는데, 잘렸다는
   표시가 아무 데도 없다. 그래도 넘칠 만큼 큰 값이 오면 `…` 로 **잘렸음을 드러낸다** —
   조용히 반만 보여 주는 것보다 낫다. */
.usage-card-value {
  font-size: 19px; font-weight: 700; color: var(--text); line-height: 1.1;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.usage-card-sub { font-size: 11px; color: var(--muted); }

/* 차트는 대시보드형 그리드로: 활동 차트만 한 줄 전체, 토큰·비용은 2열로 나눠
   세로 점유를 줄인다. 각 차트는 높이를 고정해 와이드 화면에서도 과하게 커지지 않는다. */
.usage-charts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.usage-charts .card { margin: 0; }
.usage-chart-wide { grid-column: 1 / -1; }
.usage-chart { width: 100%; }
/* 실제 폭은 JS 가 컨테이너에서 읽어 viewBox 를 px 1:1 로 잡는다(레터박스 없음).
   width:100% + height:auto 라 표시 높이는 그 viewBox 높이(약 200px)를 따른다. */
.usage-svg { width: 100%; height: auto; display: block; }
@media (max-width: 768px) {
  .usage-charts { grid-template-columns: 1fr; }
}
.usage-grid { stroke: var(--border); stroke-width: 1; }
.usage-axis { fill: var(--muted); font-size: 11px; }
.usage-axis-y { text-anchor: end; }
.usage-axis-x { text-anchor: middle; }
.usage-legend { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 10px; font-size: 12px; color: var(--muted); }
.usage-legend-item { display: inline-flex; align-items: center; gap: 6px; }
.usage-swatch { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }

/* 조직별 합계 표 — `.data-table` 위에 숫자 칸 규칙만 얹는다.
   ★ 숫자는 **오른쪽 정렬 + tabular-nums** 여야 자리수가 세로로 맞는다. 기본 정렬(왼쪽)로
     두면 `1,204` 와 `812,646` 의 자리가 어긋나, 표를 훑어 큰 조직을 찾는 일이 안 된다.
   ★ 합계 행은 위에 굵은 선을 둔다 — 표가 가로로 스크롤되는 동안에도 "이 줄은 합계"가
     읽혀야 한다(조직 이름 칸이 화면 밖으로 나가 있을 때가 있다). */
.usage-org-table th.num, .usage-org-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
.usage-org-table tbody th[scope="row"] { color: var(--text); font-weight: 600; font-size: 13.5px; }
/* 조직 칸은 가로 스크롤에도 **따라온다.** 비용까지 켜면 열이 15개라 표가 반드시 밀리는데,
   그때 이름 칸이 화면 밖으로 나가면 남는 것은 숫자뿐이고 어느 조직 줄인지 알 수 없다.
   배경을 깔아 두지 않으면 밑으로 지나가는 숫자가 비쳐 글자가 겹쳐 읽힌다. */
.usage-org-table th:first-child {
  position: sticky; left: 0; z-index: 1;
  background: var(--panel); border-right: 1px solid var(--border);
}
.usage-org-table tfoot th, .usage-org-table tfoot td {
  border-top: 2px solid var(--border); font-weight: 700; color: var(--text);
}
/* ₩ 환산은 USD 아래 줄에 둔다 — 같은 줄에 붙이면 금액 칸이 두 배로 넓어져, 비용 3종이
   붙는 시스템 관리자 화면에서 표가 통째로 가로 스크롤로 밀린다. */
.usage-org-table .num-sub { display: block; font-size: 11px; color: var(--muted); font-weight: 400; }

/* ── 단가 관리(system-pricing) 화면 ───────────────────────────── */
.pricing-table { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.pricing-row { display: grid; grid-template-columns: 1.7fr 1fr 1fr 0.8fr 1.6fr 0.8fr auto; gap: 10px; align-items: center; }
/* 단위를 칸 제목으로 올렸으므로 값 칸은 제 폭을 다 쓴다 — `0.25` 가 `0.2` 로 잘리던 자리다. */
.pricing-unit { font-style: normal; font-weight: 500; color: var(--text-subtle); }
.pricing-head { font-size: 12px; color: var(--muted); padding: 0 2px 4px; border-bottom: 1px solid var(--border); }
.pricing-row input[type="number"], .pricing-row input[type="text"] { padding: 6px 8px; border: 1px solid var(--border); border-radius: 7px; background: var(--panel); color: var(--text); width: 100%; box-sizing: border-box; }
.pricing-model code { font-size: 13px; }
.pricing-actions { text-align: right; }
.pricing-reset { margin: -2px 0 4px auto; text-align: right; }
.muted-tag { background: var(--bg); color: var(--muted); }
@media (max-width: 768px) {
  .pricing-head { display: none; }
  .pricing-row { grid-template-columns: 1fr 1fr; }
  .pricing-model { grid-column: 1 / -1; }
}

/* ═══════════════════════════════════════════════════════════════════════
   모바일 · 태블릿 대응
   ─────────────────────────────────────────────────────────────────────
   이 콘솔은 데스크톱 전제로 만들어졌다. 좁은 화면에서 실제로 못 쓰게 만드는
   것은 폰트 크기가 아니라 **폭을 고정으로 잡은 세 가지**였다:
     1) 사이드바 240px 상시 점유 → 오프캔버스 드로어로 뺀다
     2) 2단(좌 목록 / 우 편집) 그리드 → 1단으로 쌓는다
     3) height:100vh 로 갇힌 패널 → 상담 콘솔만 남기고 고정을 푼다
   나머지(표·탭·모달)는 잘라내지 않고 각자 스크롤시킨다 — 관리 화면에서
   열을 숨기면 "값이 없다"와 "안 보인다"를 구분할 수 없다.
   ═══════════════════════════════════════════════════════════════════════ */

/* 모바일 전용 요소들. 기본은 숨기고 아래 쿼리에서만 켠다.
   (.navsheet 는 시트 자체, .navsheet-toggle 은 상단바의 메뉴 버튼) */
.navsheet-toggle, .room-back { display: none; }
/* 현재 위치 표시. 모바일 상단바에서 그룹 탭 자리를 대신한다.
   활성 그룹이 없는 화면(대시보드·계정)은 비어 있으므로 앱 이름으로 대신한다
   — 제목 없는 상단바는 지금 어디인지 알 수 없게 만든다. */
.appbar-title {
  display: none; flex: 1 1 auto; min-width: 0;
  font-size: 15px; font-weight: 640; letter-spacing: -0.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.appbar-title:empty::after { content: attr(data-fallback); }

/* ── 1024px: 2단 → 1단 ────────────────────────────────────────────── */
@media (max-width: 1024px) {
  .split-layout,
  .split-layout.split-wide-form,
  .kb-split { grid-template-columns: 1fr; }

  /* ── 좁은 화면: 편집 폼은 목록 아래가 아니라 **시트로** 뜬다 ──
     한 칸으로 접히면 폼이 목록 뒤로 밀린다. 목록이 길면 '수정'을 누른 사람이
     화면이 그대로인 줄 알고, 한참 내려가서야 폼을 만난다 — 눌렀는데 아무 일도
     안 일어난 것처럼 보이는 것이 가장 나쁘다.

     여닫는 상태는 **URL 에 있다**(?edit=<id> · ?new=1). 그래서 스크립트가 없어도
     열리고, 뒤로 가기로 닫히며, 새로고침해도 열린 채다.
     z-index 80: 상단바(60)보다 위, 모달(90)보다 아래. */
  .split-pane.pane-form { display: none; }
  .split-pane.pane-form.is-open {
    display: flex;
    position: fixed; inset: 0; height: var(--vh); z-index: 80;
    border-radius: 0; border: 0; max-height: none;
  }
  .split-pane.pane-form.is-open > .split-pane-head { padding-top: max(14px, env(safe-area-inset-top)); }
  /* 시트를 닫는 문. 넓은 화면에는 없다(폼이 늘 붙어 있으므로 닫을 것이 없다). */
  .pane-close {
    display: inline-flex; align-items: center; justify-content: center;
    width: 36px; height: 36px; margin-left: auto; flex: 0 0 auto;
    border-radius: var(--r-sm); color: var(--text-muted);
  }
  .pane-close:hover { background: var(--surface-2); color: var(--text); }
}

/* 넓은 화면에서는 폼이 늘 오른쪽에 있으므로 여닫는 문이 필요 없다. */
@media (min-width: 1025px) {
  .pane-add, .pane-close { display: none; }
}

/* ── 768px: 모바일 ────────────────────────────────────────────────── */
@media (max-width: 768px) {
  :root { --content-pad-x: 16px; --content-pad-y: 16px; }

  /* ── 셸: 그룹 탭을 메뉴 버튼 + 시트로 바꾼다 ──
     탭 6개는 좁은 화면에서 브랜드·계정과 한 줄을 다투다 둘 다 못 읽게 된다. */
  .gtabs { display: none; }
  .navsheet-toggle { display: flex; }
  .appbar-title { display: block; }
  .appbar { gap: 6px; padding: 0 8px 0 6px; }
  .brand-name { display: none; }
  /* 조직 셀렉터는 시트 안(계정 영역)으로 내려간다 — 상단바에 자리가 없다. */
  .appbar .org-switch { display: none; }
  /* ★ `.navsheet { display:block }` 로 쓰면 [hidden] 을 덮어 안 닫힌다 — :not() 로 건다. */
  .navsheet:not([hidden]) { display: block; }
  .iconbtn { width: 40px; height: 40px; }

  /* ── 화면고정 해제 ──
     2단이 세로로 쌓이면 100dvh 안에 갇혀 아래 패널을 볼 수 없다.
     상담 콘솔(.content-chat)과 **목록이 화면 전체인 화면(.content-list-fill)** 은
     예외다. 둘은 안쪽이 스크롤돼야 한다.

     ★ .content-list-fill 을 예외로 두는 이유(사용자 실측 2026-09-10): 고정을 풀면
       목록 상자에는 `max-height: 62%` 같은 상한이 다시 필요해지고, 그러면 **상자
       아래쪽이 화면 밖에 남는다.** 손가락을 상자 안에 대면 목록이 스크롤되고
       페이지는 안 움직여서, 주소줄이 접히기 전에는 마지막 줄에 닿을 길이 없다
       — 스크롤러가 둘이면 늘 이런 함정이 생긴다. 화면에 맞춰 고정해 두면
       스크롤러가 목록 하나뿐이고, 잘리는 곳이 없다. */
  .content-fixed:not(.content-chat):not(.content-list-fill) { height: auto; overflow: visible; }
  .content-fixed:not(.content-chat):not(.content-list-fill) .split-pane,
  .content-fixed:not(.content-chat):not(.content-list-fill) .list-panel { max-height: none; }
  /* 고정을 풀면 thead sticky 가 함께 죽는다 — 모바일에서는 페이지 전체 스크롤이
     더 자연스러워 이 손실은 감수한다. */

  /* ── 표: 잘라내지 않고 컨테이너 안에서 옆으로 민다 ──
     min-width 가 없으면 열이 한 글자씩 세로로 뭉개져 오히려 못 읽는다. */
  .table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  /* 열이 많은 표는 좁은 화면에서 억지로 욱여넣는 대신 가로로 스크롤한다.
     ★ `.fit` 은 그 반대다 — 열이 두셋뿐이라 접으면 그냥 들어가는 표에 붙인다.
       이 하한을 모든 표에 걸었더니 3열짜리 권한표까지 390px 에서 263px 를 넘쳐
       가로로 밀렸다(실측). 넘칠 이유가 없는 표를 넘치게 만드는 하한은 규칙이 아니라 사고다. */
  .table-scroll .data-table:not(.fit),
  .split-pane-body .data-table:not(.fit),
  .list-panel-body .data-table:not(.fit) { min-width: 560px; }
  /* 접어서 들어가는 표: 첫 열(이름·설명)이 남는 폭을 쓰고 줄바꿈한다. */
  .data-table.fit th, .data-table.fit td { white-space: normal; }
  .data-table.fit th:first-child, .data-table.fit td:first-child { width: 100%; }

  /* 지식 목록(문서·정보)은 좁은 화면에서 **가로로 밀지 않는다.**
     동작 칸을 걷어내 칸이 셋뿐이라 접으면 들어간다(그게 `.fit` 의 조건이다).
     ★ 뒤 두 칸은 더 줄인다 — 375px 에서 72+96=168px 을 떼면 제목에 185px 밖에 안 남아
       제목이 두세 글자마다 잘린다. 값이 짧은 칸이라(ko · 7) 좁혀도 읽힌다. */
  #kbDocTable th:nth-last-child(1), #kbDocTable td:nth-last-child(1) { width: 52px; }
  #kbDocTable th:nth-last-child(2), #kbDocTable td:nth-last-child(2) { width: 44px; }
  /* 제목·요약은 여전히 **한 줄**로 둔다. `.fit` 의 normal 을 그대로 받으면 제목이 여러
     줄로 늘어져 행 높이가 제각각이 되고, 목록을 훑는 일이 어려워진다(줄 높이를 맞추려고
     쓴 .kb-title-line·.doc-preview 의 계산도 무너진다). 잘린 전문은 title 로 본다. */
  #kbDocTable .kb-title-text, #kbDocTable .doc-preview { white-space: nowrap; }
  /* 정보 목록도 같은 방식이다 — 칸 폭을 정하지 않으면 이름 칸이 제 글자만큼 벌어져
     표가 넘친다(실측 375px: 이름 218 + 종류 43 + 필드 194 = 455 > 353).
     ★ 비율로 준다. 문서 표처럼 px 로 못박으면 값이 짧은 칸(ko·7)이 아니라 **이름과
       필드**라 어느 쪽도 고정 px 가 맞지 않는다. */
  /* 추가 설명 검토 — 세 칸이 375px 를 넘어 밀려 나갔다(실측 666px). 상태는 배지 하나라
     좁아도 읽히고, 동작은 단추 하나다. 남는 폭은 **어느 문서인가**에 준다 — 고르는 근거다. */
  #kbSuppTable { table-layout: fixed; }
  /* ★ 접는 것은 **본문 미리보기**다. 동작 칸을 접으면 좁은 화면에서 할 수 있는 일이
     하나도 없어진다 — 가로로 미는 것보다 나쁘다. 본문은 행을 눌러 팝업에서 읽는다. */
  #kbSuppTable th:nth-child(3), #kbSuppTable td:nth-child(3) { display: none; }
  /* 배지가 `답변에 쓰는 중`(8자)이라 27% 로는 칸을 넘쳐 옆 제목과 붙어 보였다(실측). */
  #kbSuppTable th:first-child, #kbSuppTable td:first-child { width: 34%; }
  #kbSuppTable th:nth-child(2), #kbSuppTable td:nth-child(2) { width: 40%; }
  #kbSuppTable th:last-child, #kbSuppTable td:last-child { width: 26%; white-space: normal; }
  #kbSuppTable td:first-child .tag { white-space: nowrap; }
  /* 제목도 **한 줄**로 둔다 — `.fit` 의 `white-space: normal` 이 여기까지 와서 두 줄로
     접히고 행 높이가 제각각이 됐다(실측). 잘린 전문은 행을 눌러 팝업에서 본다. */
  #kbSuppTable .supp-target { white-space: nowrap; }

  /* ⚠ `.fit` 이 첫 칸에 `width: 100%` 를 준다. 뒤 칸에만 비율을 주면 셋이 100+24+38 로
       합이 넘어 브라우저가 제멋대로 눌러 버린다(실측: 필드 값 칸이 31px 로 짜부라졌다).
       **세 칸을 모두** 지정해 합이 100% 가 되게 한다. */
  #kbEntityTable { table-layout: fixed; }
  /* ★ `정보 종류`(·지점)는 접는다. 세 칸을 375px 에 욱여넣으면 이름 칸이 141px 밖에 못 받아
     `LDM 물…`·`NDA플러…`·`PDT (ph…` 처럼 **다섯 글자에서 잘린다** — 목록만 보고는
     어느 줄을 눌러야 할지 못 고른다. 반면 종류는 125건이 전부 `시술 상품 그룹` 이라
     칸을 내주고도 잃는 것이 거의 없고, 첫 알약(`그룹 속성 | sub_group`)이 같은 것을
     말한다. 고르는 데 쓰는 이름을 살리고 겹치는 칸을 접는 쪽이 맞다.
     ⚠ 접는 칸에 표시를 달아 둔다. 조건부로 붙는 지점 칸까지 함께 접어야, `fixed` 에서
       폭을 못 받은 칸이 0px 으로 남는 일이 없다(현재 지점 0건이라 화면엔 안 나온다). */
  #kbEntityTable .col-fold { display: none; }
  #kbEntityTable th:first-child, #kbEntityTable td:first-child { width: 56%; }
  #kbEntityTable th:nth-last-child(1), #kbEntityTable td:nth-last-child(1) { width: 44%; }
  /* 좁은 화면에서도 같다 — 한 줄로 두고 이 칸만 가로로 넘긴다. */
  #kbEntityTable .fields-cell { max-width: none; }
  /* 이름도 **한 줄**로 둔다(문서 표와 같은 규약). 접어서 보여 주면 이름 길이에 따라
     행 높이가 제각각이 돼 목록을 훑는 일이 어려워진다 — 잘린 전문은 title 로 본다.
     ⚠ `.fit` 의 `white-space: normal` 이 바로 위에서 모든 칸에 걸리므로, 여기서
        다시 못박지 않으면 기본 규약(.kb-title-text)이 무효가 된다. */
  #kbEntityTable .kb-title-text { white-space: nowrap; }

  /* ── 탭: 줄바꿈 대신 가로 스크롤(지식베이스 5탭) ── */
  .tabs { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .tabs::-webkit-scrollbar { display: none; }
  .tab { flex: 0 0 auto; }

  /* ── 입력 글자 크기 ──
     ⚠ 아래 두 곳 모두 위 기본 스타일 목록과 **같은 타입을 적어야 한다.** 예전에
       search 가 빠져 검색칸만 13.5px 이었다 — iOS 에서 누르는 순간 화면이 확대되고
       돌아오지 않는다.

     ★ 좁은 화면에서 입력 글자만 16px 이면 **주변 글(13.5px)보다 확연히 커서** 칸만
       도드라진다(사용자 지적). 그런데 16px 을 준 이유는 미감이 아니라 iOS 한 가지다:
       16px 미만 입력에 포커스가 가면 사파리가 페이지를 확대하고 **되돌리지 않는다.**
       그러니 그 조건이 있는 곳에서만 16px 을 쓰고, 나머지(안드로이드·데스크톱 축소창)
       는 주변 글에 맞춘 14px 로 둔다. iOS 판별은 `-webkit-touch-callout` 지원 여부로
       한다 — iOS 웹킷에만 있고 안드로이드·데스크톱에는 없다. */
  input[type="text"], input[type="password"], input[type="number"],
  input[type="email"], input[type="time"], input[type="date"], input[type="search"],
  select, textarea { font-size: 14px; }
  @supports (-webkit-touch-callout: none) {
    input[type="text"], input[type="password"], input[type="number"],
    input[type="email"], input[type="time"], input[type="date"], input[type="search"],
    select, textarea { font-size: 16px; }
  }
  /* ── 손가락 타깃 최소 크기 ──
     ★ 체크박스·라디오는 상자 자체가 16px 이라(위 '체크박스·라디오' 절) 그것만으로는
       못 누른다. 종전 네이티브 상자는 13px 이었다.
       실제로 누르는 것은 감싼 라벨이므로 라벨에 높이를 준다(실측: checkline 20px,
       switch-line 22px 였다). 요소만 키우면 줄 간격이 무너지므로 라벨을 키운다. */
  .btn-primary, .btn-secondary, .nav-toggle { min-height: 44px; }
  .checkline, .switch-line, .field-check { min-height: 44px; }
  /* ⚠ **목록 머리줄은 예외다.** 위 규칙이 여기까지 와서 [추가] 가 44px 로 커졌다
     (실측 375px: 계정 관리·상담 템플릿 44px · 13.5px 글자 · 좌우 15px). 그 줄은
     본문이 아니라 아래 목록을 다루는 컨트롤 줄이라, 목록보다 커지면 무엇이 내용이고
     무엇이 조작인지 뒤집힌다. 지식베이스 툴바(.toolbar-actions)는 이미 같은 이유로
     34px 로 내려 있었는데 이쪽만 남아 있어 화면마다 크기가 달랐다 — 값을 맞춘다.
     ★ 저장·취소는 여기 없다(.split-pane-body 의 .form-actions 에 있다). 본문 동작의
       손가락 과녁은 그대로 44px 다. */
  /* 목록 머리줄의 설명은 접는다. 375px 에서 이 한 줄이 305px 라 제목·[추가]와 한 줄에
     설 수가 없어 **머리줄이 세 줄**이 됐다(실측). 조작이 아니라 안내문이고 넓은
     화면에는 그대로 남는다 — 탭 줄에서 마지막 동기화 시각을 접는 것과 같은 갈래다. */
  .split-pane-head .hint { display: none; }

  .split-pane-head .btn-primary,
  .split-pane-head .btn-secondary {
    height: 34px; min-height: 34px; padding: 0 10px; font-size: 12px;
  }
  /* 같은 줄의 셀렉터도 함께 낮춘다 — 36px 로 남으면 줄이 들쭉날쭉해 보인다. */
  .split-pane-head .head-org-select select { height: 34px; font-size: 12.5px; }
  /* 세그먼트는 줄을 바꾸지 않으므로(위 .seg 주석) 좁은 화면에서는 **가로를 다 쓰고
     칸을 조금 좁힌다.** 실측(390px · 트랙 341px): 세 칸일 때 칸마다 110px 이 돌아오는데,
     가장 긴 라벨 '상담사가 먼저 응대' 는 13px 로 97px 필요하다 — 좌우 14px 여백이면
     82px 만 남아 글자가 잘렸다. 여백 6px · 12.5px 로 내리면 98px 이 남아 들어간다.
     ⚠ 더 줄이지 말 것: 여기서 1~2px 만 더 깎으면 잘림이 다시 시작된다. */
  .seg { width: 100%; max-width: 100%; }
  /* 세그먼트는 **한 줄짜리 컨트롤**로 남는다(칸이 아래로 떨어지지 않는다).
     다만 칸 **안의 글자**는 접히게 둔다 — 390px 에서 세 칸이면 칸당 90px 남짓인데
     '상담 가능 시간에만 연결'(11자)은 12px 로도 114px 이 필요하다. 잘라 내면 무엇을
     고르는지 사라지고, 글자를 더 줄이면 읽을 수 없다. 두 줄로 접는 것이 유일하게
     아무것도 잃지 않는 길이다 — 컨트롤은 여전히 한 덩어리로 보인다. */
  .seg > label {
    /* 좁은 화면에서만 균등 분할 — 트랙이 가로를 다 쓰므로 셋이 같이 줄어들어야 한다. */
    flex: 1 1 0;
    min-height: 42px; padding: 5px 3px; font-size: 12px;
    white-space: normal; text-align: center; line-height: 1.2; word-break: keep-all;
  }
  .day-chip { min-height: 44px; padding: 0 15px; }
  /* 좁은 화면에서는 세 칸이 한 줄에 못 선다 — 요일을 머리로 올리고 시간 두 줄을 그 아래로.
     (실측 375px: 한 줄로 두면 시간 칸이 각 44px 로 짜부라져 값을 읽을 수 없다) */
  .day-grid-head { display: none; }
  .day-line { grid-template-columns: 1fr; gap: 4px; padding: 8px 0; border-bottom: 1px solid var(--border); }
  .day-times input[type="time"] { min-height: 40px; }
  .navsheet-item { min-height: 48px; }
  /* 상자도 조금 키운다 — 라벨을 눌러도 상태가 어디서 바뀌는지 보여야 한다.
     체크 표시는 배경 이미지라 상자 크기를 따라 같이 커진다(background-size: 100%). */
  input[type="checkbox"], input[type="radio"] { width: 18px; height: 18px; }

  /* ── 가로로 늘어서던 묶음은 줄바꿈 ── */
  .inline-form, .page-head-row, .kb-head-actions,
  .kb-inspect-row, .form-actions, .modal-foot { flex-wrap: wrap; }
  .page-head-row { align-items: flex-start; }

  /* ── 모달: 좁은 화면에서는 하단 시트로 ──
     세로로 긴 폼(툴 편집·동기화 소스)이 가운데 정렬되면 위아래가 동시에 잘린다.
     z-index 는 위(기본 90)에서 이미 셸 크롬보다 높다 — 여기서 다시 올리지 않는다. */
  .modal { align-items: flex-end; }
  .modal-box, .modal-box.modal-wide {
    width: 100%; max-width: none;
    max-height: 92vh; max-height: calc(var(--vh) * 0.92);
    border-radius: 12px 12px 0 0;
  }
  .modal-box.modal-sticky { max-height: 92vh; max-height: calc(var(--vh) * 0.92); }
  .modal-sticky > form, .modal-sticky > .modal-inner { max-height: 92vh; max-height: calc(var(--vh) * 0.92); }
  .modal-foot .btn-primary, .modal-foot .btn-secondary { flex: 1 1 auto; text-align: center; }

  /* ── 알림 드롭다운: 340px 고정이라 화면을 넘긴다 ── */

  /* ── 동기화 소스 필드 빌더: 4열 → 2열(머리글은 숨기고 값만) ──
     pricing-row 가 이미 쓰는 방식과 같다. */
  .kbfield-head { display: none; }
  .kbfield-row { grid-template-columns: 1fr 1fr; }

  /* 최신화 액션 묶음은 flex-shrink:0 + nowrap 이라 좁은 화면에서 줄지도 접히지도
     못하고 페이지를 통째로 넓힌다(375px 화면에서 문서 폭 540px 로 실측됐다). */
  .kb-sync-actions { flex-wrap: wrap; flex-shrink: 1; min-width: 0; }
  .kb-last-sync { white-space: normal; }

  /* 목록은 넓은 화면과 똑같이 **남은 높이를 전부 채운다**(위 예외 참조).
     여기서 상한을 다시 씌우면 상자 아래가 화면 밖으로 나가 닿을 수 없게 된다. */

  /* ── 상담 콘솔: 목록과 채팅방이 같은 자리를 나눠 쓴다 ──
     320px + 1fr 을 그대로 두면 양쪽 다 못 쓸 폭이 된다. 선택 전에는 목록만,
     대화를 열면 방만 보이고 방 머리의 ← 로 돌아온다.
     전환 판정은 inbox.js 가 붙이는 .has-selection 하나로 한다 — JS 에서 폭을
     재면 데스크톱/모바일 두 벌의 진실이 생겨 어긋난다. */
  .inbox-layout { grid-template-columns: 1fr; }
  .inbox-layout .conv-room { display: none; }
  .inbox-layout.has-selection .conv-list { display: none; }
  .inbox-layout.has-selection .conv-room { display: flex; }
  .room-back {
    display: inline-flex; align-items: center; justify-content: center;
    flex: 0 0 auto; width: 34px; height: 34px;
    border: 1px solid var(--border); border-radius: 8px;
    background: var(--panel); color: var(--text); font-size: 17px; line-height: 1; cursor: pointer;
  }
  .room-back:hover { background: var(--surface-2); }
  /* 방 머리가 좁아 이름과 도구가 한 줄에 안 들어간다.
     [← 배지 이름] / [응대 모드 · 응대 완료] 두 줄로 나누되, 둘째 줄은
     우측으로 몰고 높이를 맞춰 머리가 대화 영역을 덜 먹게 한다. */
  .room-head { flex-wrap: wrap; gap: 6px; padding: 8px 14px; }
  .room-tools { justify-content: flex-end; }
  /* 셀렉트(31px)와 버튼(터치 타깃 40px)이 제각각이라 둘째 줄이 들쭉날쭉했다. */
  .room-tools .mode-switch,
  .room-tools .btn-secondary {
    height: 36px; min-height: 36px; display: inline-flex; align-items: center;
  }

  /* 답장 입력: 손가락 과녁을 키운다(도구 줄 구조는 그대로). 좁은 폭에서 도구는 아이콘만 — 글자는 title·aria 로 남는다. */
  .reply-area { padding: 8px 10px 10px; }
  .composer-tool, .reply-form .btn-primary { height: 38px; }
  .reply-form .btn-primary { padding: 0 20px; }
  /* 말풍선 72% 는 좁은 화면에서 한 줄에 두세 글자만 남긴다. */
  .msg { max-width: 88%; }
}

/* ── 1100px: 상담 대화창이 숨 쉴 폭 ───────────────────────────────────
   ★ **접는 기준은 768px 이 아니라 대화가 읽히는 폭이다.**
   방은 `대화영역 + 템플릿 240px` 이고, 대화영역 = 창폭 − 655px 이다(실측: 1280→625 ·
   1024→369 · 900→**245**). 즉 900px 에서 말풍선이 **133px** 로 눌려 두세 글자마다
   줄이 바뀌었다 — 같은 대화가 375px 휴대폰에서는 250px 를 쓴다. **데스크톱 창이
   휴대폰보다 좁아지는** 자리였다(사용자 지적 2026-09-15).
   1100px 아래에서는 템플릿을 하단 시트로 내린다 — 그러면 대화영역이 창폭 − 415px 이
   되어 900px 에서도 485px 을 쓴다. 1100 위에서는 옆에 두어도 445px 이상 남는다.
   ⚠ 목록↔방 전환(단일 열)은 **그대로 768px** 이다. 그건 폭이 아니라 "한 화면에 하나만"
     이라는 다른 판단이고, 여기서 함께 올리면 900px 에서 목록이 사라진다. */
@media (max-width: 1100px) {
  /* ── 템플릿 패널: 240px 고정 사이드 → 하단 시트 ──
     343px 폭의 방에서 240px 를 떼어 주면 대화가 100px 안에 갇힌다.
     평소에는 감추고, 입력줄의 [상담 템플릿] 버튼으로 올려 쓴다. */
  .room-templates { display: none; }
  .room-templates.is-open {
    display: flex;
    position: fixed; left: 0; right: 0; z-index: 68;
    bottom: 0; bottom: calc(100lvh - var(--vh));
    width: auto; max-height: calc(var(--vh) * 0.6);
    border-left: 0; border-top: 1px solid var(--border);
    border-radius: 12px 12px 0 0;
    background: var(--panel);
    box-shadow: var(--sh-lg);
    /* 홈 인디케이터에 마지막 항목이 가리지 않도록 */
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  .tpl-scrim.is-open {
    display: block; position: fixed; inset: 0; height: var(--vh); z-index: 67;
    background: var(--scrim);
  }
  .tpl-panel-title { font-size: 13px; padding: 12px 16px; }
  .tpl-panel-close { display: inline-flex; font-size: 17px; padding: 4px 6px; }
  .tpl-panel-item { padding: 11px 12px; }
  /* 좁은 화면에서는 한 줄 자르기보다 두 줄까지 보여 주는 편이 고르기 쉽다. */
  .tpl-panel-preview { white-space: normal; overflow: hidden; max-height: 2.8em; }
  .tpl-toggle, .composer-tool.tpl-toggle { display: inline-flex; align-items: center; flex: 0 0 auto; }
}

/* ── 480px: 소형 보정 ──────────────────────────────────────────────── */
@media (max-width: 480px) {
  /* 상담 입력 상자의 부가 도구는 아이콘만 — 글자는 title·aria(버튼 안 숨긴 글자)로 남는다. [전송] 자리를 지킨다. */
  .composer-tool-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .composer-tool { padding: 0 10px; }
  /* 카드 좌우 여백 16 → 12. 세그먼트는 줄을 바꾸지 않으므로 폭이 곧 글자가 잘리느냐를
     가른다 — 여기서 얻는 8px 이 세 칸짜리 세그먼트에서 칸당 2.6px 이 된다.
     실측(390px): 이 여백이 없으면 '상담사가 먼저 응대' 가 칸을 6px 넘겼다. */
  .sec-group, .card-body { padding-left: 12px; padding-right: 12px; }
  .split-pane-head, .list-panel-head { flex-wrap: wrap; gap: 8px; }
  /* 아주 좁은 화면에서는 숫자 쪽을 조인다 — 실측 390px: 숫자 174 + 버튼 163 + 줄 간격 8
     = 345 로 쓸 수 있는 332 를 넘겨 줄이 갈렸다. 숫자 쪽에서 14px 을 아껴 한 줄에 넣는다.
     ⚠ 여유가 3px 뿐이다. 라벨이 한 글자만 길어져도 다시 갈린다 — 그때는 줄이 갈리는
       편이 맞다(글자를 더 줄이면 무슨 숫자인지 알 수 없어진다).
     ★ 360px 에서는 들어가지 않는다(필요 329 · 가능 302). 거기서는 두 줄로 간다. */
  .split-pane-head { gap: 6px; }
  .count-chips { gap: 3px; }
  .count-chip { padding: 3px 6px; font-size: 11px; }
  /* 보조 동작은 아이콘만 남긴다 — 목록 툴바(.toolbar-actions)가 쓰는 것과 같은 규약이다.
     실측 390px: '임베딩 재색인' 88 → 40px. 숫자 157 + 버튼 115 + 간격 6 = 278 로
     쓸 수 있는 320 안에 **여유 있게** 들어간다(글자를 더 깎는 것보다 튼튼하다). */
  .split-pane-head .kb-head-actions form .btn-secondary > span { display: none; }
  .split-pane-head .kb-head-actions form .btn-secondary { padding: 0 9px; }
  .group-footer, .conn-value { flex-wrap: wrap; }
}

/* ── 전역 설정 이식: 공통 프롬프트 전후 비교 ──
   전 조직 응대를 바꾸는 값이라 "다르다"는 표시만으로는 판단이 안 된다 — 전문을 나란히 보인다.
   좁은 화면에서는 위아래로 쌓아 가로 스크롤이 생기지 않게 한다. */
.gt-prompt-compare { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.gt-prompt {
  max-height: 220px; overflow: auto; white-space: pre-wrap; word-break: break-word;
  background: var(--code-bg, var(--surface-2)); border: 1px solid var(--border, var(--border));
  border-radius: 6px; padding: 8px; font-size: 12px; line-height: 1.5; margin: 4px 0 0;
}
@media (max-width: 720px) {
  .gt-prompt-compare { grid-template-columns: 1fr; }
}

/* ── 설정 배포: 프롬프트 비교는 **칸별로** ──
   달라지는 칸만 그리므로 보통 한둘이다. 칸끼리 붙어 있으면 어디까지가 한 칸인지
   안 보여, 이름표를 머리로 세우고 칸 사이를 벌린다. */
.prompt-diff + .prompt-diff { margin-top: 14px; }
.prompt-diff-name { margin: 0 0 2px; font-size: 12.5px; font-weight: 600; color: var(--text); }
/* 참·거짓 칸은 글이 아니라 상태 하나다 — 전문 상자(.gt-prompt)에 담으면 한 낱말이
   220px 상자 안에서 떠 있게 된다. 값만 한 줄로 적는다. */
.prompt-diff-bool { margin: 4px 0 0; font-size: 13px; font-weight: 560; }
/* 그대로인 칸 수. 바로 위 상자에 딸린 말로 읽히지 않게 한 칸 띄운다 —
   바뀐 칸이 하나도 없을 때는 이 줄 **하나만** 남는다(그때가 대부분이다). */
.prompt-diff-same { margin: 10px 0 0; font-size: 12.5px; color: var(--text-muted); }

/* ── 설정 배포: 주소 치환 행 ──
   이 행에는 CSS 가 **아예 없었다** — 브라우저 기본 입력 두 개가 그대로 나왔다.
   실측(1280px): 172px 짜리 둘이 간격 0 으로 맞붙어, 어디까지가 '보낸 서버 주소'이고
     어디부터가 '이 서버 주소'인지 경계가 없다.
   실측(375px): 폭이 모자라 세로로 쌓이는데 기본 폭 178px 을 그대로 들고 가
     오른쪽에 119px 이 빈 채 남는다 — 쌓인 것이 아니라 **깨진 것처럼** 보인다.
   from → to 한 쌍이므로 두 칸을 같은 폭으로 짜고, 좁으면 세로로 쌓는다. */
.rule-row {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 8px; align-items: end; margin-bottom: 8px;
}
.rule-row input { width: 100%; }

@media (max-width: 768px) {
  /* ⚠ 기본 정의 **뒤에** 둔다 — 미디어쿼리는 우선순위를 올려 주지 않는다.
     쌓고 나면 placeholder 가 값에 가려 무슨 칸인지 알 수 없다. 칸 이름은
     `.p-cell::before` 가 `data-label` 로 그린다(파라미터 행과 같은 규약이라
     규칙이 하나다 — 새 규칙을 만들면 둘이 갈라진다). */
  .rule-row { grid-template-columns: minmax(0, 1fr); gap: 10px; }
}

/* ── 엔티티 스키마: 필드 정의 행 ──
   종전에는 `{"price": "number"}` 를 손으로 치는 textarea 하나였다. 이름과 형식을
   한 줄씩 적는 표로 바꾼다 — 열 머리글이 무엇을 적는 칸인지 말한다.
   ⚠ `.repeat-row`(flex) 뒤에 와야 이 grid 가 이긴다. 파일 끝이라 그 조건은 만족한다. */
.fdef-head, .fdef-row {
  display: grid; grid-template-columns: minmax(0, 1fr) 132px 30px;
  gap: 6px; align-items: center;
}
.fdef-head {
  font-size: 12px; color: var(--muted); font-weight: 600;
  /* 좌우 여백 0 — 머리글이 아래 입력칸과 **왼쪽 끝이 맞아야** 한다.
     파라미터 표에서 쓰는 2px 을 그대로 가져왔더니 머리가 2px 밀렸다(실측 1280px:
     머리 572 · 입력 570). 이 표는 열이 셋뿐이라 그 2px 이 그대로 눈에 띈다. */
  padding: 0 0 6px; align-items: end;
}
.fdef-row > input, .fdef-row select { width: 100%; min-width: 0; }

@media (max-width: 768px) {
  /* ⚠ 기본 정의 뒤에 둔다 — 미디어쿼리는 우선순위를 올려 주지 않는다.
     열 머리글은 감춘다(세로로 쌓이면 어느 칸 위의 머리인지 짝지을 수 없다).
     대신 이름칸은 placeholder 가, 형식칸은 `.p-cell` 의 이름표가 그 말을 한다. */
  .fdef-head { display: none; }
  .fdef-row {
    grid-template-columns: minmax(0, 1fr) 30px; align-items: end;
    padding: 10px; border: 1px solid var(--border);
    border-radius: var(--r-sm); background: var(--panel);
  }
  .fdef-row > input { grid-column: 1; grid-row: 1; }
  .fdef-row > .repeat-x { grid-column: 2; grid-row: 1; }
  .fdef-row > .p-cell { grid-column: 1 / 3; grid-row: 2; }
  /* 지우기 단추도 입력칸과 같은 높이로 — 30px 을 36px 옆에 바닥만 맞추면 높아 보인다
     (툴 편집 행에서 실측하고 같은 값을 쓴 자리다). */
  .fdef-row > input, .fdef-row select { height: 36px; font-size: 14px; }
  .fdef-row > .repeat-x { width: 30px; height: 36px; }
}

/* ── 엔티티 필드 값 칸 ──
   스키마가 정한 칸을 세로로 쌓는다. `.field` 가 라벨·입력 배치를 이미 하므로 여기서는
   칸 사이 간격과 "스키마에 없음" 표시만 잡는다. */
.kb-field + .kb-field { margin-top: 12px; }
/* 라벨 옆 배지 — 앞 스키마에서 내려온 값이라는 표시다. 이름과 붙어 있으면 이름의
   일부처럼 읽히므로 한 칸 띄운다. */
.kb-field > span > .tag { margin-left: 6px; }
.kb-field textarea { min-height: 68px; resize: vertical; }

/* ── 엔티티 목록의 필드 칸 ──
   저장 형태(JSON 원문)를 내보이던 칸을 `이름: 값` 칩으로 바꿨다. 값이 여럿이면 한 줄에
   이어 붙고, 넘치면 잘라 title 로 본다(감사 로그의 `.json-cell` 과 같은 규약이다). */
/* ── 정보 목록의 필드 값: **알약** ──
   종전에는 한 줄에 `이름 값  이름 값` 을 이어 붙이고 넘치면 말줄임했다. 쌍의 경계가
   보이지 않아 어디까지가 한 값인지 읽어 내야 했고, 값에 공백이 있으면 다음 이름과
   붙어 보였다(실측: `색상 밝은 갈색 용량 2ml`).
   알약 하나가 쌍 하나다 — 테두리가 경계를 대신하고, 이름과 값 사이 세로선이 둘을 가른다.
   ★ 칸은 **한 줄**이다. 접어 쌓으면 필드 수에 따라 행 높이가 제각각이 되고(실측 375px:
     63 · 82px 혼재), 줄 수를 제한하면 이번엔 뒤쪽 값이 통째로 사라진다. 대신 이 칸만
     **가로로 넘긴다** — 표 전체는 제자리에 있고, 값은 하나도 잃지 않는다.
   ★ 넘기는 칸이므로 값은 자르지 않는다. 말줄임은 '더 볼 방법이 없을 때'의 표시다. */
/* ⚠ 칸(td)에는 배치를 걸지 않는다 — `display:flex` 를 주면 표 셀이기를 그만두고 제
   내용만큼 줄어든다(실측: 폭 134px 로 정해 둔 칸이 51px 로 짜부라졌다). 배치는 안쪽 상자.
   ⚠ 넘기는 상자도 td 가 아니라 안쪽이어야 한다. td 에 `overflow-x` 를 주면 표 셀에서는
      먹지 않거나 셀 경계가 어긋난다 — 폭을 정한 칸 안에 상자를 두고 그 상자를 넘긴다. */
.fields-cell { max-width: 320px; font-size: 12px; vertical-align: middle; }
.fields-pills {
  display: flex; flex-wrap: nowrap; gap: 4px;
  overflow-x: auto; overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  /* ★ 막대는 **그리지 않는다.** 상시로 그리면 125행에 125개의 짧은 선이 깔려 알약
     밑줄처럼 읽힌다(실측). 넘치는 칸은 알약이 칸 경계에서 잘리는 것으로 이미 드러나고,
     전문은 행을 눌러 편집 화면에서 본다. 넘기는 동작 자체는 그대로 산다. */
  scrollbar-width: none;
}
.fields-pills::-webkit-scrollbar { display: none; }
.fpair {
  display: inline-flex; align-items: stretch; flex: 0 0 auto;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface-2); overflow: hidden;
  line-height: 18px;
}
/* 이름은 값보다 한 단계 뒤로 — 훑을 때 눈이 값에 먼저 닿아야 한다.
   세로선은 이름 쪽에 붙여 하나만 그린다(양쪽에 주면 두 줄로 보인다). */
.fpair > b {
  font-weight: 500; color: var(--text-muted);
  padding: 0 6px; border-right: 1px solid var(--border);
  background: var(--panel);
  white-space: nowrap;
}
.fpair > span {
  padding: 0 7px;
  white-space: nowrap;
  color: var(--text);
}
/* 빈 값(「미지정」)은 화면이 채운 말이라 값보다 흐리게 — 저장된 값처럼 읽히지 않게 한다. */
.fpair.is-unset > span { color: var(--text-muted); font-style: italic; }

/* ── 목록 머리줄: 탭 · 동작 · 검색 (한 마크업, 두 배치) ──
   실측 375×812: 목록 첫 줄이 y=322 였다 — 화면의 40%가 목록이 아니라 그 위였다.
   동작 줄 44px + 검색칸 62px 이 그 절반이다.

   넓은 화면:  [탭 ………… 보조동작 주동작]
               [검색                      ]
   좁은 화면:  [탭 … ⋯ 주동작]            ← [⋯] 를 누르면 아래 줄이 펼쳐진다
               [검색 … 보조동작 아이콘]

   ★ 마크업은 **한 벌**이다. 배치 차이는 `order` 와 줄바꿈 자리(.flex-break)로만 낸다 —
     두 벌을 두면 반드시 한쪽만 고쳐져 어긋난다.
   ★ 팝업(드롭다운)으로 만들지 않는다. 탭 줄을 가로 스크롤 상자로 만든 적이 있는데,
     그 안에 띄운 팝업이 **열려도 보이지 않았다**(줄 높이 36px 에 잘려 나갔다).
     `overflow-x: auto` 는 세로도 함께 자른다. 좌표·display 는 정상이라 그 자리를 실제로
     짚어(elementFromPoint) 보고서야 드러났다. */
.origin-row > .origin-tabs { order: 1; }
.row-actions { order: 2; display: flex; align-items: center; gap: 6px; flex: 0 0 auto; margin-left: auto; }
.row-actions form { display: flex; margin: 0; }
.origin-row > .btn-primary { order: 3; }
/* 검색은 늘 제 줄을 쓴다(넓은 화면에서도 목록 바로 위가 제자리다).
   ⚠ `flex-basis: 100%` 로 줄을 넘기려 하면 안 된다 — `.search-field` 의
     `max-width: 420px` 이 그 값을 눌러, 자리가 남으면 **탭과 같은 줄에 붙어 버린다**
     (실측 1280px: 검색이 탭 줄 오른쪽 790~1210 에 섰다). 줄바꿈은 빈 자리(.flex-break)가
     맡고, 검색은 제 폭만 정한다. */
.flex-break { display: block; flex: 0 0 100%; height: 0; margin: 0; order: 4; }
.origin-row > .search-field { order: 5; }
.listtools-btn { display: none; }

@media (max-width: 768px) {
  /* ⚠ 기본 정의 뒤에 둔다 — 미디어쿼리는 우선순위를 올려 주지 않는다. */
  .origin-row { flex-wrap: wrap; align-items: center; gap: 6px; }
  /* 탭은 남는 폭을 쓰고 넘치면 가로로 민다. 오른쪽 끝을 흐려 "더 있다"를 알린다 —
     단색으로 끊으면 글자가 중간에서 뚝 잘려 고장 난 화면으로 읽힌다. */
  .origin-row > .origin-tabs {
    flex: 1 1 0; min-width: 0; flex-wrap: nowrap;
    overflow-x: auto; scrollbar-width: none;
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent);
            mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent);
  }
  .origin-row > .origin-tabs::-webkit-scrollbar { display: none; }
  .origin-row > .origin-tabs > .origin-tab { flex: 0 0 auto; padding: 5px 9px; font-size: 11.5px; }
  /* 줄에 서는 것은 [⋯] 와 주 동작뿐. 아이콘만 남으므로 글자 기준 여백을 걷는다. */
  .listtools-btn { display: inline-flex; order: 2; padding: 0 9px; margin-left: auto; }
  .origin-row > .btn-primary { order: 3; margin-left: 0; }
  /* 펼쳐지는 아랫줄 — 줄바꿈 자리 → 검색 → 보조 동작 차례로 선다. */
  .origin-row > .search-field { flex: 1 1 auto; max-width: none; }
  .row-actions { order: 6; margin-left: 0; }
  /* 접혀 있는 것이 기본이다. 늘 펼쳐 두면 앞서 잰 106px 이 그대로 돌아온다. */
  .flex-break, .origin-row > .search-field, .row-actions { display: none; }
  .origin-row.is-open > .flex-break { display: block; }
  .origin-row.is-open > .search-field { display: flex; }
  .origin-row.is-open > .row-actions { display: flex; }
  /* 아랫줄의 보조 동작은 아이콘만 — 검색칸이 그 줄의 대부분을 써야 한다. */
  .row-actions .btn-secondary > span { display: none; }
  .row-actions .btn-secondary { padding: 0 9px; }
  /* 위(.toolbar-actions 768 규칙)를 쓰지 않는다 — 이 줄은 제 규칙을 갖는다. */
  .origin-row > .btn-primary, .listtools-btn, .row-actions .btn-secondary {
    height: 34px; min-height: 34px; font-size: 12px;
  }
}

/* 현재 토큰 등록 — 값을 바꾸지 않고 암호문만 채우는 한 줄 폼.
   입력이 넓어야 붙여 넣은 값이 보인다(토큰은 32자다). */
.kb-token-adopt { display: flex; align-items: center; gap: 8px; margin: 6px 0 4px; flex-wrap: wrap; }
.kb-token-adopt > input { flex: 1 1 260px; min-width: 0; }
/* ---------- 채널 연동 가이드(팝업 창) ----------
   상단바·네비가 없는 별도 창이다. 플랫폼 관리자 화면과 나란히 놓고 값을 옮겨 적는
   작업이라, 폭을 좁게 잡아 두 창을 동시에 볼 수 있게 한다. */
.popup-page { background: var(--bg); }
.popup-shell {
  max-width: 720px; margin: 0 auto; padding: 20px 16px 40px;
  display: flex; flex-direction: column; gap: 14px;
}
.popup-shell .page-head h1 { display: flex; align-items: center; gap: 8px; }
.popup-shell .card p { margin: 0 0 8px; line-height: 1.7; }
.popup-shell .card p:last-child { margin-bottom: 0; }
/* ⚠ 간격이 **두 겹**으로 들어가 있었다. 이 상자는 flex 라 `gap: 14px` 가 이미 카드를
   떼어 놓는데, `.card` 의 `margin-top: 16px` 이 그 위에 또 붙어 실제 간격이 30px 였다
   (flex 자식의 margin 은 합쳐지지 않는다). 카드 사이가 카드 안 여백(20px)보다 넓어
   **한 장 한 장이 따로 떠 있는 것처럼** 보였다 — 이어지는 절차인데. gap 하나로 줄인다. */
.popup-shell > .card { margin-top: 0; }
/* 카드 위는 39px, 아래는 21px 이었다(실측). 안쪽 여백은 20px 인데 첫 `.sub-title` 의
   `margin-top: 18px` 이 더해져서다 — 제목 위만 한 칸 더 떠 보인다. 첫 줄은 여백을 지운다. */
.popup-shell .card > :first-child { margin-top: 0; }

/* 글 안의 `코드` 표시(emphasis 헬퍼가 만든다). 값 이름·메뉴 이름이라 줄 가운데 섞이므로
   배경만 옅게 깔고 글자 크기는 본문과 같이 둔다 — 작게 하면 한 줄에 두 크기가 섞인다. */
.popup-shell code {
  padding: 1px 5px; border-radius: 5px;
  background: var(--surface-2); border: 1px solid var(--border);
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  overflow-wrap: anywhere;
}
.popup-shell pre code { padding: 0; border: 0; background: none; }

/* 붙여 넣을 값. 줄바꿈을 보존해야 curl 명령이 깨지지 않으므로 pre 를 쓰고,
   긴 URL 은 창 폭을 넘기지 않도록 줄바꿈을 허용한다. */
.guide-code {
  margin: 8px 0; padding: 10px 12px; font-size: 12px; line-height: 1.6;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.guide-list { margin: 0; padding-left: 18px; line-height: 1.8; }
.guide-list li { margin-bottom: 4px; }

/* ---------- 목록 행 전체가 누르는 자리 ----------
   오른쪽 작은 단추 셋을 걷어내고 행을 열게 했다. 눌리는 줄인지 **보이지 않으면 아무도
   안 누른다** — 커서와 호버 배경으로 그 사실을 말한다. */
.data-table tr.row-open { cursor: pointer; }
.data-table tr.row-open:hover > td { background: var(--surface-2); }
.data-table tr.row-open:focus-visible {
  outline: 2px solid var(--accent); outline-offset: -2px;
}

/* ---------- 추가 정보 고지 ----------
   ★ 카드 **밖·목록 위**에 둔다. 표 안이나 행에 붙이면 스크롤과 함께 사라지고, 빈 목록일
     때는 아예 안 보인다. 이 화면에 들어온 사람이 가장 먼저 알아야 하는 것이라 상시 자리다. */
/* 어느 문서의 설명인가 — 제목 한 줄. 접히면 행 높이가 제각각이 돼 목록을 훑을 수 없다.
   잘린 전문은 행을 눌러 팝업에서 본다. */
.supp-target { max-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------- 칸 제목 옆 설명 [i] ----------
   배지 읽는 법처럼 **처음 한 번만 필요한 글**은 표 위에 상자로 두지 않는다. 매일 쓰는
   사람에게는 같은 문장이 목록 위 자리를 늘 먹을 뿐이다(화면 설명 말풍선과 같은 이유).
   말풍선 상자·여닫기는 .page-tip 을 그대로 쓴다 — 생김새가 갈라지지 않게. */
.th-tip-label { vertical-align: middle; }
/* ⚠ 말풍선 안은 **줄바꿈을 되돌린다.** `.data-table th` 가 nowrap 이라 그대로 상속되면
   글이 한 줄로 늘어서 상자 밖으로 흘러나간다(실측: 상자는 440px 인데 글이 그 두 배). */
.th-tip > .page-tip { white-space: normal; }
.th-tip > .page-tip-btn { vertical-align: middle; margin-left: 2px; }
/* 원문이 바뀐 것은 **계속 쓰인다** — 같은 무게로 적으면 멀쩡한 글을 죽은 것으로 읽는다. */
.supp-notice-live { margin-top: 6px !important; color: var(--text-muted); font-size: 12.5px !important; }

/* ---------- 문서 편집 팝업의 추가 정보 영역 ----------
   원문(위)과 추가 정보(아래)는 **주인이 다르다** — 위는 동기화가, 아래는 사람이 쓴다.
   선으로 갈라 그 경계를 보이게 한다. */
/* ★ 선 **위아래로** 자리를 준다. 종전에는 선만 긋고 여백이 없어 윗 칸 바로 밑에 붙었고,
   구분선이 앞 내용의 밑줄처럼 읽혔다 — 주인이 바뀌는 경계(위는 동기화, 아래는 사람)인데
   그 사실이 보이지 않았다. */
.doc-supp { border-top: 1px solid var(--border); margin-top: 28px; padding-top: 22px; }
/* 고른 것이 저장을 기다리는 중이라는 말. 단추 옆 제 줄에 둔다. */
.doc-supp-staged { flex: 1 1 100%; margin: 2px 0 0 !important; line-height: 1.5; color: var(--text-muted); }
.doc-supp-head { margin: 0 0 4px; font-size: 14px; font-weight: 650; }
.doc-supp-orphans {
  margin: 10px 0; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface-2);
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px;
}
.doc-supp-orphans > .field { flex: 1 1 260px; min-width: 0; margin: 0; }
/* "대체됩니다" 안내 — 단추와 **같은 상자 안**, 제 줄에 둔다. 누르기 전에 읽혀야 하는
   말이라 토스트로 미루지 않는다(토스트는 누른 뒤에 뜬다). */
.doc-supp-replace { flex: 1 1 100%; margin: 2px 0 0 !important; line-height: 1.5; }
.doc-supp-actions { display: flex; gap: 8px; margin-top: 10px; }
/* 바닥 줄의 **지우는 단추**는 왼쪽 끝으로 민다 — 주된 동작(저장) 옆에 서면 한 칸 잘못
   눌러 지운다. 화면마다 이름이 달랐던 것(.doc-del · .ch-del)을 하나로 모았다. */
.modal-foot > .foot-del, .foot-del { margin-right: auto; }

/* 토큰 단추 줄 — [보기]와 [재발급]이 각자 폼이라 나란히 서지 않았다(폼은 블록이다). */
.token-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }

/* ---------- 연동 목록 카드 ----------
   카드 전체가 수정을 여는 자리다. 눌리는 자리인지 **보이지 않으면 아무도 안 누른다** —
   커서와 테두리 강조로 그 사실을 말한다(목록 행의 .row-open 과 같은 규약, 상자만 다르다). */
.rec-card.row-open { cursor: pointer; }
.rec-card.row-open:hover { border-color: var(--accent); }
.rec-card.row-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* 웹훅 줄 — 값은 늘어나고 복사는 오른쪽 끝에 붙는다.
   ★ `min-width: 0` 이 없으면 긴 URL 이 상자를 밀어내 단추가 화면 밖으로 나간다. */
.webhook-row { display: flex; align-items: center; gap: 8px; }
.webhook-row > .webhook-url { flex: 1 1 auto; min-width: 0; }
.webhook-row > .copy-btn { flex: 0 0 auto; }

/* 아이콘만 있는 단추. 글자 단추와 높이를 맞춰 한 줄에 섞여도 어긋나지 않게 한다. */
.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--tag-h, 28px); height: var(--tag-h, 28px); padding: 0;
  border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--panel); color: var(--text-muted); cursor: pointer;
}
.icon-btn:hover { background: var(--surface-2); color: var(--text); }
.icon-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }


/* 연동 가이드 — 단계와 값 흐름 표.
   단계마다 얇은 좌측 선을 둬서 "여기부터 한 단계"가 눈으로 끊기게 한다(제목만으로는
   문단이 길어질수록 어디서 끊기는지 안 보인다). */
.guide-step { padding-left: 12px; border-left: 2px solid var(--border); margin: 14px 0; }
.guide-step:first-of-type { margin-top: 8px; }
.guide-step-title { font-weight: 640; font-size: 13px; margin-bottom: 6px; }
.guide-verified { margin: 0 0 4px; }

/* 웹훅 프로토콜 배지. 색으로 세 상태를 가른다 —
   HTTPS 전용(대부분)은 평범한 사실이라 눈에 띄지 않게 두고,
   **http 가 되는 예외**(위챗)와 **공식 문서가 말하지 않는 것**(카카오)만 눈에 걸리게 한다.
   그 둘이 사람이 잘못 판단하는 자리다. */
.tag-scheme { white-space: normal; }
.tag-scheme--no { background: var(--surface-2); color: var(--muted); }
.tag-scheme--yes { background: var(--warn-bg); color: var(--warn-fg); }
.tag-scheme--unspecified { background: var(--accent-soft); color: var(--accent); }

/* 값 흐름 표. 팝업 창이 좁아 넘칠 수 있으므로 가로 스크롤은 감싼 상자(.table-scroll)에
   맡긴다 — 그 규칙은 위에 이미 있다(머지하면서 같은 선언이 두 번 들어가 하나를 걷었다). */
.guide-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.guide-table th, .guide-table td {
  text-align: left; vertical-align: top; padding: 8px 10px;
  border-bottom: 1px solid var(--border);
}
.guide-table th { font-weight: 620; color: var(--text-muted); white-space: nowrap; }
.guide-table td .hint { margin: 4px 0 0; }

/* ── 관리자 설정: 권한 표 · 비밀번호 정책 ──────────────────────────
   권한은 **영역별로 묶어** 보인다. 평평한 목록은 늘어나면 어디까지가 한 묶음인지
   알 수 없는데, 이 목록은 시스템 관리자가 늘릴 수 있다. */
.perm-table .perm-col { white-space: normal; min-width: 88px; }
.perm-col-name { display: block; }
/* 칸 제목이 두 열의 뜻을 말한다 — 표 위 문단으로 두면 체크박스 앞에서 되돌아가 읽어야 한다. */
.perm-col-sub { display: block; margin-top: 2px; font-size: 11px; font-weight: 400;
  line-height: 1.4; color: var(--text-subtle); }
.perm-group-head th {
  padding-top: 14px; font-size: 12px; font-weight: 700; color: var(--muted);
  letter-spacing: .02em; background: var(--surface-2);
}
.perm-group-n { margin-left: 6px; font-weight: 500; color: var(--text-subtle); }
.perm-name { display: block; }
/* 권한 이름만으로는 무엇이 열리는지 모른다. 한 줄 설명을 이름 아래에 둔다(없으면 안 그린다). */
.perm-desc { display: block; margin-top: 2px; font-size: 11.5px; line-height: 1.45; color: var(--muted); }
/* 끈 줄은 한 단계 물러나 보인다 — 켠 것과 끈 것을 훑어서 가를 수 있게. */
.perm-table tr.is-off .perm-name,
.perm-table tr.is-off .perm-desc { color: var(--text-subtle); }

/* ── 좁은 화면: 칸 제목의 부연은 접는다 ──
   실측(375px): '따로 정하지 않은 관리자에게 자동 적용' 이 **네 줄(62px)** 로 접혀
   머리줄 혼자 101px 을 먹었다. 표에서 가장 좁은 칸에 가장 긴 문장이 들어간 꼴이라,
   읽히기는커녕 아래 목록을 화면 밖으로 밀어낸다.

   ★ 지우는 것은 **부연뿐**이다 — 칸 제목('사용'·'기본권한')은 그대로 남고, 체크박스마다
     `aria-label` 에 '<권한 이름> — <칸 제목>' 이 붙어 있어 읽어 주는 쪽도 잃지 않는다.
   ⚠ 권한 이름 아래 한 줄 설명(.perm-desc)은 **남긴다.** 그건 부연이 아니라 "이 권한이
     무엇을 여는가"이고, 그게 빠지면 이름만으로 골라야 한다(실측 1~3줄로 심하지도 않다). */
@media (max-width: 768px) {
  .perm-col-sub { display: none; }
}

/* 지금 규칙 한 줄. 체크박스와 숫자를 눈으로 조립하지 않아도 되게. */
.pw-summary {
  margin: 12px 0 0; padding: 9px 12px; border: 1px solid var(--border);
  border-radius: var(--r-sm); background: var(--surface-2);
  font-size: 13px; font-weight: 600; color: var(--text);
}
.pw-summary:empty { display: none; }
.pw-warn { margin: 6px 0 0; font-size: 12.5px; color: var(--danger-fg); }
.pw-warn[hidden] { display: none; }
/* 만료가 꺼져 있으면 딸린 칸이 잠긴다 — 손댈 수 있는데 아무 일도 안 하는 칸이 가장 헷갈린다. */
.pw-expiry-body { margin-top: 12px; }
.pw-expiry-body.is-off { opacity: .55; }

/* 목록 카드의 동작 줄 — 되돌릴 수 없는 것을 일상 동작과 **떼어 놓는 구분선**.
   넷이 같은 크기로 나란히 서면 한 칸 잘못 눌러 조직이 통째로 사라진다. */

/* 카드 **안**의 접기(설정 배포의 '다른 옵션으로 받기').
   ⚠ `display: flex` 없이 화살표만 넣으면 브라우저 기본 삼각형과 **둘 다** 보인다
      (실측 2026-09-15: `▶ 다른 옵션으로 받기 ⌄`). 마커를 끄는 것이 화살표를 다는 것과
      한 짝이다 — .card-fold·.form-fold 가 이미 같은 규약을 쓴다. */
.dp-advanced { margin-top: 10px; }
.dp-advanced > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; color: var(--muted);
}
.dp-advanced > summary::-webkit-details-marker { display: none; }
.dp-advanced > summary:hover { color: var(--text); }
.dp-advanced > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-sm); }
.dp-advanced[open] > summary .sec-chev { transform: rotate(180deg); }

/* ---------- 인증 메시지 발송(system-messaging) ----------
   카드 제목 줄 오른쪽에 조직 선택을 붙인다 — 조직이 10·20개로 늘어도 폼은 하나다. */
.msg-org-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.msg-org-head h2 { margin: 0; }
.msg-org-head .head-org-select { margin-left: auto; }
.msg-org-head .head-org-select select { min-width: 220px; }
.msg-gap-top { margin-top: 14px; }
/* 칸 묶음은 **정해진 열 수로만** 접는다 — auto-fit 에 맡기면 4칸이 3+1 로 흘러 한 칸만 외톨이가 된다
   (SMTP 비밀번호가 혼자 다음 줄에 남았다). 4칸 → 2칸 → 1칸, 2칸 → 1칸. */
.form-grid.msg-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.form-grid.msg-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 1100px) { .form-grid.msg-cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px) { .form-grid.msg-cols-4, .form-grid.msg-cols-2 { grid-template-columns: minmax(0, 1fr); } }
.card > .warn-box + .form-section { margin-top: 16px; }
.msg-preview-line { margin: 6px 0 0; font-size: 13px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* ---------- 드롭다운 화살표 ----------
   ★ 입력 공통 규칙이 `select` 에도 `appearance: none` 을 걸어(203694dd — 검색칸 기본 크롬 제거) **브라우저
     화살표가 콘솔 전체에서 사라졌다.** 드롭다운이 글자 칸처럼 보여 눌러 볼 생각을 못 한다(사용자 지적 2026-09-28).
     appearance 를 되돌리면 브라우저마다 모양이 다시 갈리므로 **우리가 그린다**(체크박스·라디오와 같은 방침).
   ⚠ 파일 **맨 끝**에 둔다 — 화면별 규칙(`.usage-field select` 등)이 `background`·`padding` 을 통째로 다시 적어
     앞에 두면 화살표가 지워진다. 여러 개를 고르는 목록(multiple·size)은 화살표가 없는 것이 맞다.
   색은 두 테마에서 모두 보이는 중간 회색이다(밝은 테마 --text-subtle 과 어두운 테마 사이). */
select:not([multiple]):not([size]) {
  padding-right: 30px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 6l4 4 4-4' fill='none' stroke='%238a94a6' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 14px 14px;
}
.conv-sort select:not([multiple]):not([size]) { padding-right: 24px; background-position: right 7px center; background-size: 12px 12px; }
select:not([multiple]):not([size]):disabled { background-image: none; }
/* 알림톡 미리보기 — 승인 원문은 줄바꿈까지 심사 대상이라 그대로 보여 준다. */
.msg-preview-line.msg-preview-pre { display: block; white-space: pre-wrap; }

/* 웹챗 SDK — 단일키가 없는 조직(시스템 관리자). 목록 아래에 붙는 짧은 목록이라 항목은 한 줄. */
.wc-missing { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border); }
.wc-missing-title { font-weight: 600; margin-bottom: 4px; }
.wc-missing-item { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 6px 0; }
.wc-missing-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 상담 가능 시간 설정 박스 — '상담 가능 시간에만 연결'을 골랐을 때만 나타난다.
   바로 아래 '어떤 경우에 연결할까요' 그룹과 **다른 영역**으로 보이게 제목·테두리·왼쪽 띠로 가른다. */
.sched-box {
  margin: 4px 0 0; padding: 14px 16px 16px;
  border: 1px solid var(--border-strong); border-left: 4px solid var(--accent);
  border-radius: var(--r-sm); background: var(--surface-2);
}
.sched-box-title { display: flex; align-items: center; gap: 8px; margin: 0 0 4px; font-size: 14px; font-weight: 700; }
.sched-box > .hint { margin: 0 0 12px; }
.sched-box .sched-exc { border-top-color: var(--border-strong); }
.sec-group.handoff-triggers { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--border-strong); }
.sec-group.handoff-triggers > h3 { font-size: 14.5px; }

/* 상담 가능 시간 — 공휴일 휴무 · 특정 날짜 예외(schedule_editor.hbs · schedule_exception_row.hbs).
   색에 기대지 않는다: 지난 날짜는 글자 라벨(.exc-past)로, 미리보기 결과는 모양 아이콘 + 글자로 구분한다. */
.sched-exc { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.exc-title { margin: 14px 0 2px; font-size: 13.5px; font-weight: 700; }
.exc-list { display: flex; flex-direction: column; gap: 8px; margin: 8px 0 10px; }
.exc-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.exc-row input[type="date"] { flex: 0 0 150px; }
.exc-row select { flex: 0 0 110px; }
.exc-times { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; }
.exc-times[hidden] { display: none; }
.exc-break-label { font-size: 12px; color: var(--text-muted); font-weight: 600; }
.exc-note { flex: 1 1 140px; min-width: 120px; }
.exc-past { font-size: 11.5px; color: var(--text-muted); border: 1px solid var(--border); border-radius: 999px; padding: 1px 8px; }
.exc-row.is-past { opacity: 0.7; }
/* '앞으로 14일 적용 결과' — 펼치는 **버튼**으로 보이게 한다(테두리·배경·화살표 + '보기/접기' 글자). */
.exc-preview { margin-top: 12px; }
.exc-preview-btn {
  display: flex; align-items: center; gap: 8px; width: 100%; box-sizing: border-box;
  padding: 9px 12px; cursor: pointer; list-style: none;
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  background: var(--surface-2); color: var(--text);
  font-size: 13px; font-weight: 600;
}
.exc-preview-btn::-webkit-details-marker { display: none; }
.exc-preview-btn:hover { background: var(--surface-3); }
.exc-preview-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.exc-preview-label { flex: 1 1 auto; min-width: 0; }
.exc-preview-state { flex: 0 0 auto; font-size: 12px; font-weight: 600; color: var(--text-muted); }
.exc-preview-hide, .exc-preview[open] .exc-preview-show { display: none; }
.exc-preview[open] .exc-preview-hide { display: inline; }
.exc-chev { flex: 0 0 auto; color: var(--text-subtle); transition: transform 0.15s ease; }
.exc-preview[open] .exc-chev { transform: rotate(180deg); }
.exc-preview[open] .exc-preview-btn { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.exc-preview td { white-space: nowrap; }
.exc-preview .icon { vertical-align: -2px; }
