/* ============================================
   Design System — tokens + components
   단일 원본. 나중에 tokens.ts → CSS 변수로 이관

   토큰 이름은 **관리자 콘솔(llmops-console-ux) 기준**입니다 (2026-08-27 정렬).
   이용자 인터페이스(front-ux)가 이 파일을 복사해 쓰는데, 거기서 매번 이름을
   바꿔 심느라 동기화 때마다 수정분이 날아갔습니다. 이제 이름이 같습니다.

   ⚠ **값도 콘솔 기준입니다** (2026-09-08). 전에는 이 저장소가 자기 값을 고집했습니다 —
     콘솔의 success가 흰 배경과 3.45:1, warning이 2.50:1이라 대비 게이트를 통과 못 해서였습니다.
     이제 **면 색과 글자 색을 나눠서** 둘 다 만족시킵니다:
       · --success / --warning / --error       면 색. 콘솔 원색 그대로. 점 · 막대 · 옅은 면 · 테두리용
                                                (비텍스트 기준 3:1이라 원색으로 충분합니다)
       · --success-text / --warning-text / --error-text   글자용. 콘솔이 쓰던 어두운 값 (4.5:1 이상)
     콘솔이 이미 쓰던 방식이고, --primary / --primary-text 를 나눈 것과 같은 요령입니다.
     ⚠ **흰 글자를 얹는 채움에는 면 색을 쓰지 마세요** — 어두운 쪽(-text)을 씁니다.
       .badge.solid.* 와 Vuetify 테마(theme.ts)가 그 경우입니다.
   ============================================ */
:root {
  /* ⚠ 이 덩이는 앱 토큰 파일에서 받아 온 것입니다 — 여기서 고치지 말고 앱 토큰.css 를 고친 뒤 npm run tokens:pull (2026-10-06) */
  /* ── 콘솔 원본 ─────────────────────────────────────────────
     아래 블록은 shared/design/index.css 와 **값이 같아야 합니다.**
     주석까지 저쪽에서 그대로 옮겨 왔습니다. 임의로 손대지 마세요. */
  --background:#ffffff; --foreground:#1a1a1a; --card:#ffffff;
  --primary:#1f7ff0; --primary-foreground:#ffffff;
  --muted:#f4f4f5; --muted-foreground:#707070;
  --border:#e4e4e7; --input:#d6d6da; --ring:#1f7ff0;
  --border-emphasized:#c9c9cd;
  --success:#2f9e44; --error:#d6362a; --warning:#e69100;
  /* 글자용 상태색 — 위 세 개는 면 색이고 글자에는 이쪽. --success-text는 2026-09-08에
     관리자 콘솔이 늘린 것을 받아온 것입니다(흰 배경 5.01:1). 원색 #2f9e44는 3.45:1이라
     글자로 쓰면 WCAG AA 미달입니다 — 배지 바탕·점에만 쓰세요. */
  --error-text:#c92a2a; --warning-text:#9a6700; --success-text:#17803d;
  --accent-muted:#e9f2fe;
  /* 사용자 말풍선 — 면 · 글자 (2026-10-14 사용자 지정값). --accent-muted 와 라이트는 거의 같지만 다크가 다르고
     (#1a2c47 → #273e74 · 글자도 따로), --accent-muted 는 마흔 곳 넘게 써서 그 값을 바꾸지 않고 따로 둡니다 */
  --user-bubble:#ebf3fe; --user-bubble-fg:#172749;
  --overlay-hover:rgba(0,0,0,.05); --selected-fill:rgba(0,0,0,.06);
  --canvas:#f4f4f5;
  --r-el:6px; --r-ct:10px;
  --font:'Pretendard Variable', Pretendard, -apple-system, sans-serif;
  /* 탭·필터 활성 — 선·글자는 브랜드, 면은 옅은 브랜드. 면을 브랜드로 채우지 않습니다 */
  --nav-active: var(--primary);
  --nav-active-fg: var(--primary);
  --nav-active-bg: color-mix(in srgb, var(--primary) 7%, transparent);
  /* 범주형 팔레트 — '종류'에 고정으로 붙는 색 (순위·개수가 바뀌어도 색은 그대로).
     1번은 브랜드색, 나머지는 색맹 판별(ΔE)·명도대비 검증을 통과한 조합입니다.
     ⚠ 상태색(성공·경고·오류)과 섞어 쓰지 마세요 — 저쪽은 의미가 고정된 색입니다. */
  --cat-1: #1f7ff0; --cat-2: #eb6834; --cat-3: #1baf7a;
  --cat-4: #eda100; --cat-5: #e87ba4; --cat-6: #008300;
  /* 7 · 8은 관리자 콘솔이 2026-08-28에 늘린 두 칸입니다 — 그래프 라벨 열둘을 여섯 색에
     담으려니 법령 계통 하나에만 넷이 몰렸습니다. 여기 화면에는 아직 쓰는 곳이 없지만,
     토큰은 기준(콘솔)과 같은 벌이어야 합니다 — 없으면 `npm run ds:check`가 잡습니다. */
  --cat-7: #0b87a3; --cat-8: #8250c4;
  /* 지식자산 갈래 — 커넥터 파이프라인(브랜드 블루)과 다른 계통이라는 걸 색으로 못박습니다 */
  --kb: #7c3aed;
  /* 스크롤바 */
  color-scheme: light;
  --scrollbar: rgba(0, 0, 0, .28); --scrollbar-hover: rgba(0, 0, 0, .45);

  /* ── 확장 ──────────────────────────────────────────────────
     콘솔에 없지만 본편이 쓰는 것들. **위 토큰에서 파생**시켜 원본이 하나로 유지되게 했습니다.
     콘솔에도 필요해지면 저쪽에 먼저 올리고 여기서 지우세요. */

  /* 그레이 12단 — 콘솔의 회색을 계조로 늘린 것. 3·7·10·12는 콘솔 값을 그대로 물고 있습니다.
     화면 CSS는 이걸 직접 쓰지 않습니다(--muted 등 의미 토큰을 쓸 것) — 디자인시스템 내부용 */
  --gray-1: #fcfcfc;
  --gray-2: #fafafa;
  --gray-3: var(--muted);              /* #f4f4f5 */
  --gray-4: #ececee;
  --gray-5: var(--border);             /* #e4e4e7 */
  --gray-6: var(--input);              /* #d6d6da */
  --gray-7: var(--border-emphasized);  /* #c9c9cd */
  --gray-8: #b0b0b6;
  --gray-9: #96969d;
  --gray-10: var(--muted-foreground);  /* #707070 */
  --gray-11: #5c5c62;
  --gray-12: var(--foreground);        /* #1a1a1a */

  /* 반투명 회색 — 어떤 면 위에 올려도 자연스러운 구분선용 */
  --gray-a2: rgba(0,0,0,.04);
  --gray-a3: rgba(0,0,0,.06);
  --gray-a5: rgba(0,0,0,.11);
  --gray-a6: rgba(0,0,0,.14);
  --gray-a7: rgba(0,0,0,.18);
  /* 빈 단 채움 (2026-09-15) — a1 · a4 · a8. DS 와 같은 단계. 겹판 · 옅은 띠가 .08 을 제일 자주 씁니다 */
  --gray-a1: rgba(0,0,0,.02); --gray-a4: rgba(0,0,0,.08); --gray-a8: rgba(0,0,0,.24);

  /* 보더 3단 — 콘솔의 세 값에 '행 사이 구분선'만 더했습니다 */
  --border-subtle: rgba(0,0,0,.05);

  /* 브랜드 상호작용 단계 — 콘솔엔 hover/active 단계가 없어 여기서 정의합니다 */
  --primary-hover: #1c74dc;
  --primary-active: #1a6ccc;
  /* 브랜드를 **글자·링크로** 쓸 때의 색. 면(--primary #1f7ff0)은 흰 배경과 3.92:1 이라
     글자로 쓰면 WCAG AA(4.5:1)에 미달합니다. 점·링·보더는 비텍스트 기준(3:1)이라
     --primary 로 충분하고, 글자만 이 값을 씁니다(5.89:1).
     디자인시스템 원본이 쓰는 이름이라, 없으면 동기화할 때 색이 통째로 빕니다. */
  --primary-text: #0f62c4;

  /* 상태 3단 — solid(콘솔) + subtle(면) + border(테두리). 면·선은 solid에서 파생 */
  --success-subtle: color-mix(in srgb, var(--success) 6%, var(--card));
  --success-border: color-mix(in srgb, var(--success) 28%, transparent);
  --warning-subtle: color-mix(in srgb, var(--warning) 6%, var(--card));
  --warning-border: color-mix(in srgb, var(--warning) 30%, transparent);
  --error-subtle:   color-mix(in srgb, var(--error) 6%, var(--card));
  --error-border:   color-mix(in srgb, var(--error) 28%, transparent);
  --info-subtle:    color-mix(in srgb, var(--primary) 5%, var(--card));
  --info-border:    color-mix(in srgb, var(--primary) 24%, transparent);
  /* 에러 포커스 링 — 면(6%)보다 진합니다. 3.5px 띠라 면과 같은 농도로는 안 보입니다 */
  --error-ring: color-mix(in srgb, var(--error) 16%, var(--card));

  /* 뒤집힌 면(토스트·툴팁) 위에 얹는 상태색 — 현재 테마 값을 쓰면 어두운 면에
     어두운 색이 얹혀 안 보이므로 '반대 테마의 값'을 씁니다 */
  --success-on-inverse: #51cf66;
  --error-on-inverse:   #ff6b6b;

  --favorite: #e8a800;   /* 즐겨찾기 별 — 골드 (비텍스트 UI, 3:1) */
  /* ── 파일 유형 — 확장자 아이콘 · 배지 색 (2026-09-15 신설) ──────────────────
     세 곳(콘솔 문서 탐색 · 이용자 AI 드라이브 · 파일피커)이 같은 팔레트를 저마다 적고,
     답변서 · 프로젝트 · 국회질의 분류는 **다른 팔레트**(React 포트 잔재: pdf #E5484D …)를 쓰고
     있었습니다 — 같은 PDF 가 화면마다 다른 빨강. 값은 콘솔 것입니다(콘솔이 정답, 2026-08-28 규칙).
     확장자 → 토큰 매핑은 공용/파일유형.ts 한 벌입니다.
     ⚠ 다크 값은 라이트와 같습니다 — 정한 사람이 없어 지금 화면과 같게 둡니다(React 원본의
       light-dark() 짝은 포트 때 버려졌습니다). 다크에서 탁하면 그때 .dark 에 값을 주세요 */
  --ft-pdf: #d6452e; --ft-doc: #3b78d8; --ft-sheet: #2f9e44; --ft-slide: #e8730c;
  --ft-hwp: #0e8ba8; --ft-image: #8250c4; --ft-archive: #b98900; --ft-csv: #12a594;
  --ft-audio: #c2255c;                      /* 음성 — 시큐어노트 음성 소스 (2026-09-21 신설) */
  --ft-video: #4f55d6;                      /* 영상 — 영상 수사의 CCTV · 블랙박스 원본 (2026-10-15 신설). 문서 파랑과 이미지 보라 사이 남색 */
  --ft-text: var(--muted-foreground);
  /* 인용 하이라이트 — 출처 창 · 인용 서랍의 <mark>. 라이트/다크 짝은 chat.css 가 갖고 있던 값 그대로 */
  --highlight-bg: #ffe28a;
  /* 개인정보 분류 — 비식별화의 직접식별 · 준식별 · 민감(영업기밀).
     ⚠ **위험이 큰 쪽이 붉습니다** (2026-09-22 사용자 지시: "레드는 직접식별자고 준식별자는
       주황 민감정보도 주황색") — 직접식별자 **레드** → 준식별자 **주황** → 민감 **앰버**.
       앞 판은 주황 · 청록 · 보라라 **세 갈래가 서로 무관한 색**이어서, 무엇이 더 위험한지를
       색이 전혀 말하지 않았습니다(cat-2 · 7 · 8).
     ⚠⚠ 루트와 다크 **양쪽**에 적습니다 — var() 는 **선언된 자리에서 굳습니다**. 한쪽만 적으면
       다크에서도 라이트 값이 나옵니다(집 규칙 '--nav-active 3종'과 같은 함정). 앞 판이 실제로
       그랬습니다: 다크에서 cat-2 · 7 · 8 의 다크 값이 **한 번도 안 쓰였습니다**.
     ⚠ 쓰는 곳 — 비식별화 본문 마크(.cog-dax__mk) · 항목 알약(.cog-dax__cattag) · 점(.cog-dax__dot) ·
       시큐어노트 기본설정 점 · 실행 카드. 한 곳에서 바꾸면 전부 따라옵니다. */
  --pii-direct: var(--error); --pii-quasi: var(--cat-2); --pii-sensitive: var(--warning);
  /* ⚠ **면 위에 얹는 글자는 -text 쪽**입니다 — 12% 옅은 면 위에 원색을 그대로 올리면
     앰버가 배경에 묻힙니다(실측: 라이트에서 #e69100 이 대비 2.3:1). 집의 C · S · O 배지가
     이미 같은 규칙입니다(면 --error 10% + 글자 --error-text).
     ⚠ 준식별자만 -text 짝이 없어 **잉크색을 섞어** 만듭니다 — 라이트에서는 어두워지고
       다크에서는 밝아져, 두 모드에서 한 줄로 해결됩니다.
     ⚠ 면(마크 · 점 · 실행 카드)은 위의 원색 그대로입니다 — 어두워지면 형광펜이 탁해집니다. */
  --pii-direct-text: var(--error-text);
  --pii-quasi-text: color-mix(in srgb, var(--cat-2) 75%, var(--foreground));
  --pii-sensitive-text: var(--warning-text);
  /* 가장 위험 — 개인 · 그룹 기준 동시 초과(통합 프로파일링). --error 보다 어둡고 진한 진홍 */
  --error-deep: #8e1023;
  /* 그림자 4단 (2026-09-15 신설) — 55곳이 저마다 rgba 를 적고 있었습니다.
       sm 컨트롤 살짝 · md 카드 hover · 작은 팝 · lg 메뉴 · 팝오버 · 토스트 · xl 다이얼로그 · 패널
     ⚠ 그림자는 다크에서도 **검정**입니다 — --gray-a 처럼 흰색으로 뒤집으면 빛이 납니다.
       다크는 면이 어두워 같은 알파로는 안 보여서 --shadow-overlay 와 같은 요령으로 진하게 */
  --shadow-sm: 0 1px 2px rgba(0,0,0,.06);
  /* 위로 지는 sm — 판 **아래**에 붙박인 줄(비식별화 항목의 [+ 추가])이 넘친 줄 위에 떠 있을 때.
     값은 sm 그대로 y 만 뒤집은 것입니다 — 새 단계가 아니라 같은 단계의 방향 짝 (2026-09-23) */
  --shadow-up-sm: 0 -1px 2px rgba(0,0,0,.06);
  --shadow-md: 0 2px 8px rgba(0,0,0,.10);
  --shadow-lg: 0 6px 20px -6px rgba(0,0,0,.18);
  --shadow-xl: 0 12px 40px -12px rgba(0,0,0,.35);
  /* 오른쪽에서 밀려 나오는 서랍(슬라이드오버) — 그림자가 **왼쪽으로** 눕습니다.
     위 sm~xl 은 전부 아래로 지는 그림자라 세로 패널에는 맞지 않습니다.
     ⚠ 값은 관리자 콘솔 dlp.css 의 조건 편집 패널 것입니다 — 이용자 화면 두 곳(국회 답변서 · 프로젝트)이
       -8px 0 24px rgba(0,0,0,.08) 로 따로 놀던 것을 2026-09-15 에 이 한 벌로 모았습니다 */
  --shadow-drawer: -12px 0 28px -18px rgba(0,0,0,.35);
  /* 모달 뒤 가림막 — ui.css · 문의창 · 설정창이 저마다 rgba(0,0,0,.4) 였습니다. 다크도 같은 값(면이 어두워 더 진할 필요 없음) */
  --scrim: rgba(0,0,0,.4);

  /* 라운드 — 콘솔은 요소 6 / 컨테이너 10 두 단. 그 안쪽(체크박스·작은 칩)만 더합니다 */
  --r-in: 4px;
  --r-full: 9999px;

  /* ⚠⚠ **Consolas 를 빼지 마세요** — 앞의 둘은 macOS 전용이라 윈도우에서는 브라우저
     기본 고정폭(타자기체)으로 떨어져 Pretendard 옆에서 혼자 튑니다 (2026-09-11).
     ⚠ 관리자 콘솔 index.css 의 --mono 와 **같은 값**입니다 — 한쪽만 고치지 마세요 */
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace;

  /* 타이포 스케일 — 11/12/13/14/15/16/20/24. 이 밖의 값·반픽셀 금지 */
  --text-2xs:  11px;   /* 마이크로 라벨 — 배지 안, 스와치 캡션 */
  --text-xs:   12px;   /* 라벨 · 메타 · 표 헤더 */
  --text-sm:   13px;   /* 조밀한 UI — 테이블 셀, 사이드바 */
  --text-base: 14px;   /* UI 기본 — 버튼, 입력, 본문 UI. 메뉴급 */
  --text-md:   15px;   /* Prose 본문 — 읽는 글 */
  --text-lg:   16px;   /* 작은 제목 */
  --text-xl:   20px;   /* 섹션 제목 */
  --text-2xl:  24px;   /* 페이지 제목 · 카드 숫자 */
  --text-3xl:  30px;
  --text-4xl:  36px;   /* 랜딩 대표 제목 */

  --leading-tight:  1.3;    /* 제목 */
  --leading-ui:     1.5;    /* UI 텍스트 */
  --leading-normal: 1.62;   /* 기본 */
  --leading-prose:  1.8;    /* 읽는 글 */

  /* 읽는 글의 최대 줄 길이. ch 단위는 한글(전각)에서 좁게 잡혀 px로 고정합니다 */
  --measure: 720px;

  --weight-normal: 400;
  --weight-medium: 500;
  --weight-semibold: 600;

  /* 떠 있는 요소의 그림자 — 1px 윤곽 + 근거리 + 원거리 3겹. 단층은 뿌옇게 보입니다 */
  --shadow-overlay:
    0 0 0 1px var(--border),
    0 2px 4px rgba(0, 0, 0, .06),
    0 12px 28px rgba(0, 0, 0, .10);

  /* 포커스 링 — 배경 2px + 브랜드 2px 이중 링. 모든 조작 요소가 같은 링을 씁니다 */
  --focus-ring: 0 0 0 2px var(--background), 0 0 0 4px var(--primary);

  /* ── DS 사이트 전용 — 앱 토큰(공용/스타일/토큰.css)에 없는 이름. 값은 그대로 둡니다(scripts-pull-tokens.mjs) ── */
  --gray-a9: color-mix(in srgb, var(--gray-12) 36%, transparent);
  --gray-a10: color-mix(in srgb, var(--gray-12) 46%, transparent);
  --gray-a11: color-mix(in srgb, var(--gray-12) 62%, transparent);
  --gray-a12: color-mix(in srgb, var(--gray-12) 92%, transparent);
  --info: var(--primary);
  --chart-1: #1F7FF0;
  --chart-2: #16a3a3;
  --chart-3: #8b5cf6;
  --chart-4: #e8963c;
  --chart-5: #d6539b;
  --chart-6: #4f9e4f;
  --selected-foreground: var(--gray-12);
  --brand-text: var(--primary-text);
  --brand-hover: var(--primary-hover);
  --brand-active: var(--primary-active);
  --brand-subtle: var(--accent-muted);
  --on-brand: var(--primary-foreground);
  --brand: var(--primary);
  --bg: var(--background);
  --surface: var(--card);
  --border-strong: var(--input);
  --border-hover: var(--border-emphasized);
  --danger-on-inverse: var(--error-on-inverse);
  --danger-subtle: var(--error-subtle);
  --danger-border: var(--error-border);
  --danger-ring: var(--error-ring);
  --danger: var(--error);
  --sel-bg: var(--selected-fill);
  --sel-fg: var(--selected-foreground);
  --r-sm: var(--r-in);
  --r-md: var(--r-el);
  --r-xl: var(--r-ct);
  --r-lg: var(--r-el);
}

/* 다크 — 콘솔과 같은 `.dark` 를 기본으로, 구 속성 셀렉터도 함께 받습니다 */
[data-theme="dark"], .dark {
  /* ⚠ 이 덩이는 앱 토큰 파일에서 받아 온 것입니다 — 여기서 고치지 말고 앱 토큰.css 를 고친 뒤 npm run tokens:pull (2026-10-06) */
  /* ── 콘솔 원본 ─────────────────────────────────────────────
     다크 위계(2026-08-13 콘솔 확정) — 콘텐츠 면은 어두워야 글자 대비가 높아 잘 읽히고,
     캔버스를 살짝 밝은 톤으로 올려 영역을 구분합니다. (라이트와 명암 관계가 반대)
     2026-09-10 두 면을 **한 단씩 내렸습니다** — 캔버스 #232529→#181818, 콘텐츠 #17181b→#101013.
     위계(콘텐츠가 더 어둡다)는 그대로 두고 전체를 어둡게만 한 것입니다.
     ⚠ --background도 같이 내립니다(#131316→#0c0c0f). 이 값은 **콘텐츠 면 아래 한 단**이라는
       뜻이라, 안 내리면 --background를 쓰는 것들(권한 화면의 sticky 표 머리, 토큰 칩의
       color-mix 바탕, 포커스 링 안쪽 2px)이 카드보다 **밝아져** 관계가 뒤집힙니다. */
  --background:#0c0c0f; --foreground:#f4f4f5; --card:#101013;
  --primary:#4a97f5; --primary-foreground:#ffffff;   /* 다크에서도 프라이머리 버튼 글자는 흰색 */
  --muted:#222327; --muted-foreground:#a3a3ac;
  --border:#2c2d33; --input:#3a3b42; --ring:#4a97f5;
  --border-emphasized:#3a3b42;
  --success:#51cf66; --error:#ff6b6b; --warning:#ffd43b;
  --error-text:#ff8787; --warning-text:#ffd43b; --success-text:#51cf66;
  --accent-muted:#1a2c47;
  --user-bubble:#273e74; --user-bubble-fg:#ecf0f7;   /* 사용자 말풍선 (2026-10-14 사용자 지정값) */
  --overlay-hover:rgba(255,255,255,.06); --selected-fill:rgba(255,255,255,.09);
  --canvas:#181818;   /* 캔버스 — 콘텐츠보다 살짝 밝은 톤으로 영역 구분 */
  /* 다크는 자동 반전이 아니라 어두운 면(#101013)에 맞춰 따로 고른 단계입니다 */
  --cat-1: #3987e5; --cat-2: #d95926; --cat-3: #199e70;
  --cat-4: #c98500; --cat-5: #d55181; --cat-6: #008300;
  --cat-7: #2ea3bd; --cat-8: #9a72d6;
  /* 개인정보 분류 — 루트와 **같은 줄을 여기에도** 적습니다(위 머리글의 var() 함정) */
  --pii-direct: var(--error); --pii-quasi: var(--cat-2); --pii-sensitive: var(--warning);
  --pii-direct-text: var(--error-text);
  --pii-quasi-text: color-mix(in srgb, var(--cat-2) 75%, var(--foreground));
  --pii-sensitive-text: var(--warning-text);
  --kb: #8b5cf6;   /* 프라이머리와 같은 요령으로 한 단 밝게 — 흰 글자 대비 4.2:1 */
  color-scheme: dark;
  --scrollbar: rgba(255, 255, 255, .28); --scrollbar-hover: rgba(255, 255, 255, .45);

  /* ── 확장 ────────────────────────────────────────────────── */
  --gray-1: #0e0e10;
  --gray-2: #171719;
  --gray-3: var(--muted);              /* #222327 */
  --gray-4: #27282d;
  --gray-5: var(--border);             /* #2c2d33 */
  --gray-6: var(--input);              /* #3a3b42 */
  --gray-7: var(--border-emphasized);  /* #3a3b42 */
  --gray-8: #55565e;
  --gray-9: #7b7c85;
  --gray-10: var(--muted-foreground);  /* #a3a3ac */
  --gray-11: #c8c8ce;
  --gray-12: var(--foreground);        /* #f4f4f5 */

  --gray-a2: rgba(255,255,255,.04);
  --gray-a3: rgba(255,255,255,.06);
  --gray-a5: rgba(255,255,255,.11);
  --gray-a6: rgba(255,255,255,.14);
  --gray-a7: rgba(255,255,255,.18);
  --gray-a1: rgba(255,255,255,.02); --gray-a4: rgba(255,255,255,.08); --gray-a8: rgba(255,255,255,.24);

  --border-subtle: rgba(255,255,255,.06);

  --primary-hover: #5da5f7;
  --primary-active: #7ab5f8;
  /* 글자·링크용 — 어두운 배경과 7.4:1 */
  --primary-text: #6ba9f7;

  /* 뒤집힌 면이 밝으므로 어두운 상태색을 씁니다 */
  --success-on-inverse: #2f9e44;
  --error-on-inverse:   #d6362a;

  --favorite: #f5c518;
  --highlight-bg: #7a601f;
  --error-deep: #ff4d4d;
  --shadow-sm: 0 1px 2px rgba(0,0,0,.3);
  --shadow-up-sm: 0 -1px 2px rgba(0,0,0,.3);
  --shadow-md: 0 2px 8px rgba(0,0,0,.4);
  --shadow-lg: 0 6px 20px -6px rgba(0,0,0,.5);
  --shadow-xl: 0 12px 40px -12px rgba(0,0,0,.6);
  --shadow-drawer: -12px 0 28px -18px rgba(0,0,0,.6);
  --scrim: rgba(0,0,0,.4);
  /* 탭 · 필터 활성 — index.css 다크 블록에 있던 셋을 옮겨 왔습니다(값은 var 라 같습니다) */
  --nav-active: var(--primary);
  --nav-active-fg: var(--primary);
  --nav-active-bg: color-mix(in srgb, var(--primary) 7%, transparent);

  --shadow-overlay:
    0 0 0 1px var(--border),
    0 1px 2px rgba(0, 0, 0, .3),
    0 8px 24px rgba(0, 0, 0, .5);
  --focus-ring: 0 0 0 2px var(--background), 0 0 0 4px var(--primary);
  /* subtle/border/ring 공식은 --card·상태색을 참조하므로 다크에서도 그대로 유효 — 재정의 없음 */

  /* ── DS 사이트 전용 — 앱 토큰(공용/스타일/토큰.css)에 없는 이름. 값은 그대로 둡니다(scripts-pull-tokens.mjs) ── */
  --chart-1: #4593F5;
  --chart-2: #2ec4c4;
  --chart-3: #a78bfa;
  --chart-4: #f0a94e;
  --chart-5: #ec6cb0;
  --chart-6: #63b563;
  --brand-text: var(--primary-text);
  --brand-hover: var(--primary-hover);
  --brand-active: var(--primary-active);
  --brand-subtle: var(--accent-muted);
  --on-brand: var(--primary-foreground);
  --brand: var(--primary);
  --bg: var(--background);
  --surface: var(--card);
  --danger-on-inverse: var(--error-on-inverse);
  --danger: var(--error);
}

* { box-sizing: border-box; margin: 0; }
body {
  font-family: var(--font); font-size: var(--text-base); color: var(--foreground);
  background: var(--background); -webkit-font-smoothing: antialiased;
  line-height: var(--leading-normal);
  font-feature-settings: 'kern' 1, 'liga' 1, 'calt' 1;
  text-rendering: optimizeLegibility;
  transition: background .16s ease, color .16s ease;
}

/* 선택 영역도 브랜드로 */
::selection { background: var(--accent-muted); color: var(--primary-active); }
[data-theme="dark"] ::selection,
.dark ::selection { background: var(--accent-muted); color: var(--primary); }

/* ── 스크롤바 ──────────────────────────
   브라우저가 그리는 UI(스크롤바·폼 컨트롤)에 현재 테마를 알려 줍니다.
   이게 없으면 다크로 바꿔도 스크롤바만 밝은 채로 남습니다. */

/* 표준 속성 — Firefox 계열. 10px 트랙에 4px 막대라는 우리 규격을 px로 지정할 수는 없어
   thin + 색 지정으로 근사합니다. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--scrollbar) transparent;
}

/* Chromium·Safari — 의사요소로 규격(10px 트랙 · 4px 막대)을 정확히 그립니다.
   주의: scrollbar-width나 scrollbar-color가 지정돼 있으면 Chromium은
   ::-webkit-scrollbar를 통째로 무시합니다. 그래서 둘 다 auto로 되돌린 뒤 그립니다.
   @supports 블록이라 Firefox에는 적용되지 않고 위의 thin 설정이 그대로 남습니다. */
@supports selector(::-webkit-scrollbar) {
  * { scrollbar-width: auto; scrollbar-color: auto; }

  *::-webkit-scrollbar { width: 10px; height: 10px; }

  /* thumb의 3px는 보더가 아니라 안쪽 여백입니다.
     background-clip: content-box와 함께 써서 얇은 막대를 만듭니다. */
  *::-webkit-scrollbar-thumb {
    background: var(--scrollbar);
    border-radius: var(--r-full);
    /* stylelint-disable-next-line declaration-property-value-disallowed-list */
    border: 3px solid transparent;
    background-clip: content-box;
  }
  *::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-hover); background-clip: content-box; }
  *::-webkit-scrollbar-track { background: transparent; }
  /* 가로·세로가 만나는 모서리 — 기본 회색 사각형을 지웁니다 */
  *::-webkit-scrollbar-corner { background: transparent; }
}

/* 숫자가 세로로 흔들리지 않게 — 시각·용량·건수는 모두 tabular */
.mono, .tabular, td.mono, code, kbd, pre {
  font-variant-numeric: tabular-nums;
}

/* ── ds-button ── */
/* ⚠ 값은 **관리자 콘솔 ui.css 의 .btn 과 같아야 합니다** (2026-09-10 사용자 확정: 같은 기능의 버튼은
     하나만 — 콘솔에 맞춘다). 그전엔 여기가 14px/500 · 테두리 없음이라 같은 플랫폼 안에서 버튼이
     두 벌이었습니다. 고칠 일이 생기면 콘솔을 먼저 고치고 여기에 옮겨 적으세요.
   ⚠ 위계는 **채움 하나**입니다 — 채운 것(primary)과 중립(테두리) 둘뿐. secondary · ghost 는 옛
     이름이라 남겨 두되 모양은 중립과 같습니다. 옅은 면을 깐 세컨더리는 콘솔이 2026-09-08에 걷었습니다 */
.btn {
  all: unset; box-sizing: border-box; cursor: pointer; display: inline-flex;
  align-items: center; gap: 6px; height: 32px; padding: 0 12px;
  border-radius: var(--r-el); font-size: var(--text-sm); font-weight: var(--weight-normal);
  border: 1px solid var(--border); background: var(--card); color: var(--foreground);
  transition: background .16s ease, border-color .16s ease, color .16s ease; font-family: var(--font);
}
.btn:hover { background: var(--overlay-hover); }
.btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }   /* 콘솔은 --ring 이나 그 값이 --primary 입니다 */
.btn-primary { background: var(--primary); border-color: var(--primary); color: var(--primary-foreground); }
.btn-primary:hover { background: var(--primary); filter: brightness(.95); }
.btn-danger { background: var(--error); border-color: var(--error); color: var(--primary-foreground); }
.btn-danger:hover { background: var(--error); filter: brightness(.95); }
.btn[disabled] { opacity: .45; cursor: default; }
.btn[disabled]:hover { background: var(--card); filter: none; }
.btn-primary[disabled]:hover { background: var(--primary); }
.btn-danger[disabled]:hover { background: var(--error); }
/* 크기 세 단계 — 32(기본) · 36 · 40.
   기본이 가장 작은 32인 이유: 컨트롤은 대부분 툴바·필터 바·표 안에 줄지어
   놓입니다. 큰 것을 기본으로 두면 그 줄들이 전부 두꺼워지고, 화면마다
   size="sm"을 붙이게 됩니다. 자주 쓰는 값이 기본이어야 합니다.
   sm은 32의 다른 이름입니다 — 예전 코드가 깨지지 않게 남겨 둡니다. */
/* 로딩 — 글자를 지우지 않고 스피너를 앞에 붙입니다.
   글자를 스피너로 바꿔치기하면 버튼 너비가 줄었다 늘었다 해서 옆 버튼이 밀립니다.
   눌리지는 않지만 흐리게 만들지 않습니다 — 지금 일하고 있다는 뜻이지
   못 쓴다는 뜻이 아닙니다. */
.btn.is-loading { cursor: default; }
.btn.is-loading[disabled] { opacity: 1; }
/* 크기는 둘 — 기본 32 · sm 28 (콘솔 .btn.sm 과 같음). md 는 걷었습니다(콘솔에 없음).
   lg 40 은 **로그인 갈래의 제출 버튼 전용**입니다 — 콘솔에는 그 화면이 없어 견줄 값이 없습니다 */
.btn-sm { height: 28px; padding: 0 10px; font-size: var(--text-xs); }
.btn-lg { height: 40px; padding: 0 16px; font-size: var(--text-base); }

/* ── ds-input ── */
/* 폭은 담는 그릇이 정하고, 편한 입력 폭(300px)에서 멈춥니다.
   width를 300px로 고정하면 팝오버·좁은 칸에서 그릇보다 넓게 잡혀 레이아웃이 깨집니다. */
.field { display: flex; flex-direction: column; gap: 6px; width: 100%; max-width: 300px; }
.field label { font-size: var(--text-sm); font-weight: var(--weight-medium); }
/* ⚠ 값은 관리자 콘솔 ui.css 의 .field 와 같아야 합니다 (2026-09-10) — 테두리 --border, 글자 13px,
   안쪽 10px, 포커스는 브랜드 22% 링 3px */
.input {
  all: unset; box-sizing: border-box; height: 32px; padding: 0 10px;
  border: 1px solid var(--border); border-radius: var(--r-el); font-size: var(--text-sm);
  background: var(--card); color: var(--foreground);
  transition: border-color .16s ease, box-shadow .16s ease; font-family: var(--font);
}
.input::placeholder { color: var(--muted-foreground); }
.input:hover { border-color: var(--border-emphasized); }
.input:focus { border-color: var(--primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 22%, transparent); }
.input.error { border-color: var(--error); }
/* 크기 — 버튼과 같은 높이 셋: sm 28 · 기본 32 · lg 40(로그인 갈래 전용). md 는 기본의 옛 이름이라 규칙이 없습니다(부품이 클래스를 안 붙임) */
.input.sm { height: 28px; font-size: var(--text-xs); }
.input.lg { height: 40px; padding: 0 12px; font-size: var(--text-base); }
/* 읽기 전용 · 잠김 — 모양은 그대로, 커서와 옅기로만 (콘솔 ui.css 값, 2026-09-15) */
.input:disabled, .input[readonly] { cursor: default; opacity: .7; }
.input:disabled:hover, .input[readonly]:hover { border-color: var(--border); }
textarea.input { height: auto; min-height: 80px; padding: 8px 10px; resize: vertical; line-height: var(--leading-ui); }
select.input { appearance: auto; padding-right: 6px; }   /* all: unset 이 화살표까지 지웁니다 */
input[type="date"].input { font-variant-numeric: tabular-nums; }
input[type="date"].input::-webkit-calendar-picker-indicator { opacity: .5; cursor: pointer; }
/* 에러 상태의 포커스 링은 브랜드가 아니라 위험색 — 상태가 링 색으로도 읽혀야 합니다 */
.input.error:focus { border-color: var(--error); box-shadow: 0 0 0 3px color-mix(in srgb, var(--error) 22%, transparent); }
.hint { font-size: var(--text-xs); color: var(--muted-foreground); }
.hint.error { color: var(--error-text); }

/* ── ds-badge ──────────────────────────────
   외형(appearance) × 톤(variant)의 두 축입니다 (Geist·Astryx 방식).
   · subtle  기본 — 연한 면, 테두리 없음. 표·목록에 여러 개 놓여도 조용합니다.
   · solid   강조 — 채운 면 + 반전 글자. 화면당 소수만.
   · outline 최소 — 면 없이 테두리만. 배경이 있는 카드 위.
   테두리는 outline에서만 씁니다 — 상태마다 stroke가 붙으면 목록이 시끄러워집니다. */
.badge {
  display: inline-flex; align-items: center; height: 22px; padding: 0 8px;
  /* 각진 사각 (2026-08-18 확정) — 상태 라벨은 표·목록에 여러 개가 줄지어 놓이는 자리라,
     둥근 알약보다 각진 사각이 열 간격에 맞아떨어지고 글자가 가운데로 몰려 보이지 않습니다.
     알약 모양이 필요한 건 조작 가능한 Chip 쪽입니다. */
  border-radius: var(--r-in); font-size: var(--text-xs); font-weight: var(--weight-semibold);   /* 콘솔 .pill 600 */
  box-sizing: border-box; border: 1px solid transparent; white-space: nowrap;
  background: var(--muted); color: var(--muted-foreground);
}
/* 톤 — subtle (기본) */
/* 톤의 면 농도는 콘솔 .pill 과 같습니다 — 성공 12 · 주의 14 · 위험 10% (2026-09-10 정렬) */
.badge.brand   { background: var(--accent-muted); color: var(--primary); }
.badge.success { background: color-mix(in srgb, var(--success) 12%, transparent); color: var(--success-text); }
.badge.warning { background: color-mix(in srgb, var(--warning) 14%, transparent); color: var(--warning-text); }
.badge.danger  { background: color-mix(in srgb, var(--error) 10%, transparent); color: var(--error-text); }
.badge.info    { background: var(--accent-muted); color: var(--primary); }
.badge.cat-2  { background: color-mix(in srgb, var(--cat-2) 12%, var(--background)); color: var(--cat-2); }
.badge.cat-3  { background: color-mix(in srgb, var(--cat-3) 10%, var(--background)); color: var(--cat-3); }
.badge.cat-5  { background: color-mix(in srgb, var(--cat-5) 10%, var(--background)); color: var(--cat-5); }

/* 외형 — solid (채움) */
.badge.solid            { background: var(--foreground);  color: var(--background); }
.badge.solid.brand      { background: var(--primary);    color: var(--primary-foreground); }
/* ⚠ 채움에는 --success 가 아니라 --success-text(어두운 쪽)를 씁니다. 글자가 얹히기 때문입니다 —
   면 색은 콘솔 원색이라 흰 글자와 3.45:1(경고는 2.50:1)로 AA에 한참 못 미칩니다. (2026-09-08) */
.badge.solid.success    { background: var(--success-text);  color: var(--background); }
.badge.solid.warning    { background: var(--warning-text);  color: var(--background); }
.badge.solid.danger     { background: var(--error-text);   color: var(--background); }
.badge.solid.info       { background: var(--primary);     color: var(--primary-foreground); }
.badge.solid.cat-2      { background: var(--cat-2);  color: var(--background); }
.badge.solid.cat-3      { background: var(--cat-3);  color: var(--background); }
.badge.solid.cat-5      { background: var(--cat-5);  color: var(--background); }

/* 외형 — outline (테두리만) */
.badge.outline          { background: transparent; border-color: var(--input); color: var(--muted-foreground); }
/* strong — 바탕이 --muted 인 곳(사이드바 · 캔버스 · 회색 줄)에서 쓰는 회색 배지. 기본형의 면(--muted)은 바탕과 같은 색이라 묻힙니다.
   면을 한 단 짙은 --input 으로, 글자는 잉크색 (2026-11-06 Playground 에이전트 줄 꼬리표 .pg-agent__poc 에서 올림) */
.badge.strong           { background: var(--input); color: var(--foreground); }
/* count — 묶음 · 카드 · 그룹 머리 옆의 개수. 회색 채움(--muted-foreground) · 높이 18 · 11px/600 · 숫자 고정폭 · 모서리 3.
   solid(검정)은 너무 세고 기본형은 회색 줄 위에서 묻혀 그 사이 대비입니다 (2026-11-04 콘솔 지시 · 11-06 콘솔 ui.css 에서 올림) */
.badge.count            { height: 18px; min-width: 18px; padding: 0 6px; justify-content: center; border-radius: 3px; border-color: transparent;
  font-size: var(--text-2xs); font-weight: var(--weight-semibold); font-variant-numeric: tabular-nums; background: var(--muted-foreground); color: var(--background); }
.badge.outline.brand    { border-color: color-mix(in srgb, var(--primary-text) 32%, transparent);   color: var(--primary); }
.badge.outline.success  { border-color: var(--success-border); color: var(--success-text); }
.badge.outline.warning  { border-color: var(--warning-border); color: var(--warning-text); }
.badge.outline.danger   { border-color: var(--error-border);  color: var(--error-text); }
.badge.outline.info     { border-color: var(--info-border);    color: var(--primary); }
.badge.outline.cat-2    { border-color: color-mix(in srgb, var(--cat-2) 34%, transparent); color: var(--cat-2); }
.badge.outline.cat-3    { border-color: color-mix(in srgb, var(--cat-3) 32%, transparent); color: var(--cat-3); }
.badge.outline.cat-5    { border-color: color-mix(in srgb, var(--cat-5) 32%, transparent); color: var(--cat-5); }

/* 크기 */
.badge.sm { height: 18px; padding: 0 6px; font-size: var(--text-2xs); border-radius: 3px; }   /* 작은 배지는 모서리도 한 단 작게 */

/* ── ds-checkbox ──────────────────────────
   상자는 CSS(정확한 px), 표식은 뷰박스 16 고정 SVG(항상 정중앙·같은 획).
   선택하면 브랜드 면 + 반전 표식 (Geist 방식). */
.ds-check { --cb: 16px; --cb-gap: 9px; display: inline-flex; flex-direction: column; gap: 4px; }
.ds-check--sm { --cb: 14px; --cb-gap: 8px; }
.ds-check--lg { --cb: 20px; --cb-gap: 10px; }
.ds-check__row { display: inline-flex; align-items: center; gap: var(--cb-gap); }
/* ⚠ 테두리는 --border-emphasized 입니다 (2026-09-11 관리자 콘솔에 맞춤).
   --input 은 **입력칸** 테두리이고, 고르는 컨트롤은 그보다 한 단 진한 --border-emphasized 입니다.
   콘솔 index.css 의 input[type=checkbox] · input[type=radio] 가 그 값입니다 —
   이용자 화면에서만 체크박스가 옅어 두 앱이 달라 보였습니다.
   ⛔ --input 으로 되돌리지 마세요. 아래 .check(CSS 쌍둥이) · .ds-radio__mark 와 **셋이 한 값**입니다 */
.ds-check__box {
  position: relative; flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: var(--cb); height: var(--cb); border-radius: var(--r-in);
  border: 1px solid var(--border-emphasized); background: var(--card);
  transition: background .12s ease, border-color .12s ease;
}
.ds-check__box input {
  position: absolute; inset: 0; width: 100%; height: 100%; margin: 0;
  opacity: 0; cursor: pointer;
}
.ds-check__box.is-on { background: var(--primary); border-color: var(--primary); }
/* ⚠ hover 는 **기본 테두리보다 진해야** 합니다 — 2026-09-11 에 기본을 --input 에서
   --border-emphasized 로 올리면서 hover 가 기본과 **같은 값**이 되어, 마우스를 올려도
   아무 일도 안 일어났습니다. 콘솔 index.css 의 :hover:not(:checked) 와 같은
   --muted-foreground 로 맞춥니다.
   ⛔ --border-emphasized 로 되돌리지 마세요: 기본값과 같아 hover 가 죽습니다 */
.ds-check__box:hover:not(.is-on) { border-color: var(--muted-foreground); }
.ds-check__box:has(input:focus-visible) { box-shadow: var(--focus-ring); }
.ds-check__mark {
  width: 100%; height: 100%; pointer-events: none;
  fill: none; stroke: var(--primary-foreground); stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
.ds-check__label { font-size: var(--text-base); color: var(--foreground); cursor: pointer; }
.ds-check--sm .ds-check__label { font-size: var(--text-sm); }
.ds-check__hint { margin-left: calc(var(--cb) + var(--cb-gap)); }
/* 비활성 — 면을 채우지 않고 '연한 상자 + 회색 잉크'로 낮춥니다 (라디오와 같은 언어).
   면을 중간 회색으로 채우면 두 가지가 망가집니다:
   ① 테두리가 사라져 '빈 상자'가 '채워진 상자'로 읽히고(상태가 뒤집힘),
   ② 회색 면 위의 회색 표식이 2.2:1까지 떨어져 탁한 덩어리로 뭉갭니다.
   여기서는 상자 모양을 그대로 두고 채도만 죽여, 표식 대비를 3:1로 회복합니다. */
.ds-check.is-disabled { pointer-events: none; }
.ds-check.is-disabled .ds-check__box,
.ds-check.is-disabled .ds-check__box.is-on { background: var(--muted); border-color: var(--gray-6); }
.ds-check.is-disabled .ds-check__mark { stroke: var(--gray-10); }
.ds-check.is-disabled .ds-check__label { color: var(--gray-9); }

/* ── ds-radio ──────────────────────────────
   원과 안쪽 점을 모두 CSS로 그립니다.

   치수는 전부 정수(px)입니다. 소수(1.5px 테두리·1.5px 여백)를 쓰면
   배율 1배 화면에서 브라우저가 반 픽셀을 그릴 수 없어 변마다 다르게 반올림합니다 —
   링 두께가 들쭉날쭉해지고 점이 중앙에서 밀려 "깨져" 보입니다.
   상자·점을 모두 짝수로 잡아 여백까지 정수로 떨어지게 했습니다.

   점 크기는 상자의 50% — Material Design 3(20/10) · Ant Design(16/8) ·
   Bootstrap과 같은 값입니다. 전에는 고정 여백 방식이라 상자가 커질수록
   비율이 커졌습니다(50% → 56% → 65%). lg에서 특히 커 보이던 원인입니다.

     크기      상자   링   점    비율   여백
     sm        14    2    6     43%    2px
     default   16    2    8     50%    2px
     lg        20    2    10    50%    3px
*/
/* 텍스트 입력용 .field(고정 300px)를 쓰지 않습니다 — 선택지 폭은 내용이 정합니다 */
.ds-radio { --rb: 16px; --rb-dot: 8px; --rb-gap: 9px; display: inline-flex; flex-direction: column; gap: 8px; }
.ds-radio--sm { --rb: 14px; --rb-dot: 6px; --rb-gap: 8px; }
.ds-radio--lg { --rb: 20px; --rb-dot: 10px; --rb-gap: 10px; }
.ds-radio__group-label { font-size: var(--text-sm); font-weight: var(--weight-medium); }
.ds-radio__group { display: flex; flex-direction: column; gap: 10px; }
.ds-radio__group.is-inline { flex-direction: row; flex-wrap: wrap; gap: 18px; }
.ds-radio__row { display: inline-flex; align-items: flex-start; gap: var(--rb-gap); cursor: pointer; }
/* 라디오는 체크박스와 표현이 다릅니다 — 면을 채우지 않고 "링 + 안쪽 점"입니다.
   체크박스는 켜짐/꺼짐이라 면을 채우고, 라디오는 여럿 중 하나를 고르는 것이라
   점의 위치로 어느 것인지 읽습니다. 표준 라디오의 생김새이기도 합니다. */
.ds-radio__mark {
  position: relative; flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: var(--rb); height: var(--rb); border-radius: 50%;
  /* ⚠ 체크박스와 **같은 테두리**입니다 — 위 .ds-check__box 머리말 참고 */
  border: 1px solid var(--border-emphasized); background: var(--card);
  margin-top: 1px;   /* 첫 줄 글자의 중앙에 맞춤 */
  transition: border-color .12s ease;
}
.ds-radio__mark input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
/* ⚠ 체크박스와 **같은 이유·같은 값**입니다 — 위 .ds-check__box:hover 머리말 참고 */
.ds-radio__row:hover .ds-radio__mark:not(.is-on) { border-color: var(--muted-foreground); }
/* 선택 — 링을 브랜드색으로 굵히고 안쪽을 점으로 채웁니다.
   점 크기는 비율이 아니라 "링 안쪽에서 1.5px 띄운 나머지 전부"입니다.
   비율(%)로 잡으면 작은 크기에서 점이 사라지듯 작아집니다. */
/* stylelint-disable-next-line declaration-property-value-disallowed-list --
     라디오 선택 링은 2px입니다. 1px이면 안쪽 점과 링이 붙어 한 덩어리로 보입니다 */
.ds-radio__mark.is-on { border-width: 2px; border-color: var(--primary); }
.ds-radio__mark.is-on::after {
  content: ''; width: var(--rb-dot); height: var(--rb-dot);
  border-radius: 50%; background: var(--primary);
}
.ds-radio__mark:has(input:focus-visible) { box-shadow: var(--focus-ring); }
.ds-radio__text { display: flex; flex-direction: column; gap: 1px; font-size: var(--text-base); color: var(--foreground); }
.ds-radio--sm .ds-radio__text { font-size: var(--text-sm); }
.ds-radio__hint { font-size: var(--text-xs); color: var(--muted-foreground); }
.ds-radio__row.is-disabled { pointer-events: none; }
.ds-radio__row.is-disabled .ds-radio__mark { background: var(--muted); border-color: var(--gray-6); }
.ds-radio__row.is-disabled .ds-radio__mark.is-on { border-color: var(--gray-8); }
.ds-radio__row.is-disabled .ds-radio__mark.is-on::after { background: var(--gray-8); }
.ds-radio__row.is-disabled .ds-radio__text { color: var(--gray-9); }

/* ── ds-card ── */
.card { border: 1px solid var(--border); border-radius: var(--r-ct); padding: 22px; background: var(--card); }
.card h3 { font-size: var(--text-base); font-weight: var(--weight-semibold); }
.card p { color: var(--muted-foreground); font-size: var(--text-sm); margin-top: 4px; }
.card .num { font-size: var(--text-2xl); font-weight: var(--weight-semibold); letter-spacing: -0.02em; margin-top: 12px; }
/* ⚠ 증감은 **방향이 아니라 뜻**으로 색을 냅니다 — 관리자 콘솔 ui.css 의 .delta 그대로
   (2026-09-15 사용자 지시). 나쁜 지표가 늘면 빨강(--bad), 줄면 녹색(--good), 변화 없으면 회색(--flat).
   ⛔ 늘 초록(color: --success-text)으로 되돌리지 마세요: "유출 시도 +12건"이 초록으로 읽혔습니다.
   ⚠ **기본은 색을 정하지 않습니다**(물려받습니다) — 콘솔 .delta 가 그렇습니다. 바깥 .delta 는
     숫자와 비교 기준("· 직전 30일 대비")을 함께 담는 껍데기라, 여기에 회색을 깔면 숫자까지
     같이 눕습니다. 색은 쓰는 쪽이 --good/--bad/--flat 로 고르고, 기준 라벨은 콘솔 .delta-l 입니다 */
.card .delta { display: block; font-size: var(--text-xs); margin-top: 4px;
  font-variant-numeric: tabular-nums; }
.card .delta--good { color: var(--success-text); }
.card .delta--bad { color: var(--error-text); }
.card .delta--flat { color: var(--muted-foreground); }
.card .delta-l { color: var(--muted-foreground); }   /* 비교 기준("· 어제 대비") — 콘솔 .delta-l */

/* ── ds-chat ──
   글로벌 표준(Claude·ChatGPT·Gemini): 아바타·이름표 없음.
   사용자 = 오른쪽 연회색 풍선, 에이전트 = 풍선 없는 본문.
   정렬이 화자를 구분합니다. */
.chat { border: 1px solid var(--border); border-radius: var(--r-ct); overflow: hidden; background: var(--card); }
.msg { display: flex; padding: 10px 24px; }
.chat .msg:first-child { padding-top: 22px; }
.chat .msg:last-of-type { padding-bottom: 22px; }
.msg-user { justify-content: flex-end; }
/* 질문 풍선 = 이용자 화면 .cog-user-bubble (2026-11-06 제품에 맞춤) — 옅은 브랜드색 · 15px · 10/16 · 최대 80%. ⛔ 회색으로 되돌리지 마세요(2026-09-22) */
.msg-bubble {
  background: var(--user-bubble); color: var(--user-bubble-fg, var(--foreground)); border-radius: var(--r-ct);
  padding: 10px 16px; max-width: 80%;
  font-size: var(--text-md); line-height: 1.65; white-space: pre-wrap; word-break: break-word;
}
.msg-agent .msg-body { flex: 1; min-width: 0; max-width: 100%; }
.msg-text { font-size: var(--text-lg); color: var(--foreground); line-height: 1.7; }   /* 답 16 — 이용자 화면 .cog-answer-bubble */
.toolcall {
  margin: 10px 0; border: 1px solid var(--border); border-radius: var(--r-el);
  padding: 9px 13px; font-size: var(--text-xs); color: var(--muted-foreground);
  display: flex; align-items: center; gap: 8px; background: var(--gray-2);
  font-family: var(--mono);
}
/* CSS로 그리는 스피너 — Standalone 컴포넌트(ToolCallStep 등)는 Vuetify를 못 쓰므로
   DsSpinner(VProgressCircular) 대신 이걸 씁니다. 두 벌이 되는 건 어쩔 수 없지만
   보이는 규격은 같게 맞춥니다: 16px · 선 2px · 브랜드색.
   1.5px 같은 소수는 배율 1배 화면에서 변마다 다르게 반올림돼 링이 일그러집니다. */
.spinner {
  width: 16px; height: 16px;
  /* stylelint-disable-next-line declaration-property-value-disallowed-list --
     스피너 선은 2px입니다. 1px은 16px 원에서 돌 때 끊겨 보입니다 (바로 위 주석 참고) */
  border: 2px solid var(--gray-6);
  border-top-color: var(--primary); border-radius: 50%;
  animation: spin .8s linear infinite; flex-shrink: 0;
}
/* 색 있는 면 위(primary·danger 버튼) — 흰 호에 반투명 흰 밑줄.
   브랜드색 스피너를 브랜드색 버튼에 얹으면 색이 같아 아예 안 보입니다. */
.spinner.on-brand { border-color: color-mix(in srgb, var(--primary-foreground) 35%, transparent); border-top-color: var(--primary-foreground); }
/* 회색 면 위(secondary·ghost 버튼) — 글자색을 따라갑니다 */
.spinner.current { border-color: color-mix(in srgb, currentColor 25%, transparent); border-top-color: currentColor; }
/* 글자색을 따라가는 스피너 — 색 있는 버튼(primary·danger) 안에서 */
.spinner--current { border-color: color-mix(in srgb, currentColor 30%, transparent);
  border-top-color: currentColor; }
@keyframes spin { to { transform: rotate(360deg); } }
.check { color: var(--success-text); font-family: var(--font); }
/* 이용자 화면 답변의 [n] 칩(.cog-citechip)과 한 벌 (2026-11-06 제품에 맞춤) — 회색 면 · 모서리 4 · 11/600 */
.cite {
  display: inline-flex; align-items: center; padding: 0 5px; border: 0;
  border-radius: var(--r-in); background: var(--muted); color: var(--muted-foreground);
  font-size: var(--text-2xs); font-weight: var(--weight-semibold); line-height: 1.55; margin: 0 0 0 2px; vertical-align: 1px; cursor: pointer;
}
.cite:hover { color: var(--foreground); }
.cursor { display: inline-block; width: 7px; height: 15px; background: var(--foreground);
  vertical-align: -2px; animation: blink 1s step-end infinite; }
@keyframes blink { 50% { opacity: 0; } }
/* ds-stream — 흘려 보이는 답(DsStreamingText text · 2026-11-11). 새로 나온 글자만 페이드 — 글자 꼴 · 크기는 쓰는 쪽(.msg-text · .cog-answer-bubble) 몫.
   인라인 글자에 거는 것이라 transform 은 안 먹습니다 — opacity · filter 만. */
.ds-stream__new { animation: ds-stream-fade 400ms cubic-bezier(.2, 0, 0, 1) both; }
.ds-stream--blur .ds-stream__new { animation-name: ds-stream-blur; }
@keyframes ds-stream-fade { from { opacity: 0; } }
@keyframes ds-stream-blur { from { opacity: 0; filter: blur(4px); } }
@media (prefers-reduced-motion: reduce) { .ds-stream__new { animation: none; } }
.chat-input { border-top: 1px solid var(--border-subtle); padding: 12px 16px; display: flex; gap: 10px; align-items: center; }
.chat-input .input { flex: 1; border: none; height: 32px; padding: 0 4px; }
.chat-input .input:focus { box-shadow: none; }

/* ── ds-table ── */
/* ⚠ 2026-09-15 부터 **.table-wrap 아래에서만** 겁니다. 맨 th · td 로 두면 이 파일을 싣는 앱의 모든 표
   (관리자 콘솔 85개 — 줄 52px · 머리 13px/600)에 40px · 12px 규격이 얹혀 마지막 줄선이 사라지고
   .mono 칸이 12px 회색이 됐습니다. DS 표를 쓰려면 <div class="table-wrap"> 로 감싸세요 */
.table-wrap { border: 1px solid var(--border); border-radius: var(--r-ct); overflow: hidden; background: var(--card); }
.table-wrap table { width: 100%; border-collapse: collapse; }
/* 헤더·행 모두 배경 없이 — 보더와 글자 색만으로 구분합니다 */
/* ⚠ 규격은 **관리자 콘솔 ui.css 의 표 그대로**입니다 (2026-09-15 사용자 지시 "콘솔쪽이 보통 맞아"):
     머리 40px · 13px/600 · 보조색, 줄 52px · 14px, 좌우 끝 칸 20px.
     앞서 DS 가 쓰던 머리 12px/500 · 줄 40px 은 콘솔 85개 표와 한 줄에 12px 씩 어긋났습니다.
   ⚠ 머리행 고정(sticky)은 넣지 않습니다 — 스크롤을 누가 맡는지가 앱마다 달라서, 콘솔은
     .tablewrap 이 맡습니다. 여기 .table-wrap 은 테두리 · 라운드로 **감싸는** 틀입니다.
   ⛔ .mono 를 monospace 12px 로 되돌리지 마세요 — 콘솔 .mono 는 글꼴이 아니라 **자릿수 맞춤**입니다 */
.table-wrap th { text-align: left; font-size: var(--text-sm); font-weight: var(--weight-semibold);
  color: var(--muted-foreground);
  padding: 0 12px; height: 40px; white-space: nowrap; border-bottom: 1px solid var(--border); }
.table-wrap td { padding: 0 12px; height: 52px; font-size: var(--text-base); border-bottom: 1px solid var(--border); }
.table-wrap th:first-child, .table-wrap td:first-child { padding-left: 20px; }
.table-wrap th:last-child, .table-wrap td:last-child { padding-right: 20px; }
.table-wrap tr:last-child td { border-bottom: none; }
.table-wrap td.mono { font-variant-numeric: tabular-nums; font-feature-settings: 'tnum'; letter-spacing: .01em; }

/* ── ds-empty ── */
/* ⚠ 이름이 .ds-empty 입니다 (2026-09-15 개명) — 관리자 콘솔의 .empty(display:none → .show)와 겹쳐서. DsEmptyState.vue 도 같이 */
/* ⚠ **점선 테두리가 없습니다** — 관리자 콘솔 ui.css 의 .empty 그대로 (2026-09-15 사용자 지시).
   빈 상태는 이미 표·패널 안에 놓이므로 점선 상자를 한 겹 더 두르면 선이 겹칩니다
   (사용자 지시 "선은 한 겹만"). 여백 56/20 으로 비어 있음을 말합니다.
   ⛔ border: dashed 로 되돌리지 마세요 */
.ds-empty { padding: 56px 20px; text-align: center; }
.ds-empty h3 { margin: 0; font-size: var(--text-base); font-weight: var(--weight-semibold); }
.ds-empty p { margin: 6px 0 14px; font-size: var(--text-sm); color: var(--muted-foreground); }

/* ── theme toggle ── */
.theme-toggle {
  all: unset; box-sizing: border-box; position: fixed; top: 16px; right: 20px; z-index: 50;
  cursor: pointer; height: 32px; padding: 0 12px; display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--input); border-radius: var(--r-full);
  font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--muted-foreground); background: var(--card);
  font-family: var(--font);
}
.theme-toggle:hover { background: var(--muted); color: var(--foreground); }

/* ── ds-avatar ── */
/* ⚠ 낱글자와 면은 관리자 콘솔 ui.css 의 .avatar 와 같습니다 (2026-09-11 맞춤):
     글자는 **본문색 · 굵기 400**(보조색 600 이 아닙니다 — 이름 첫 자는 읽으라고 있는 것이지
     곁말이 아닙니다), 면은 --muted 에 --muted-foreground 를 4% 섞어 한 단 눕히고,
     테두리는 --muted-foreground 28% 입니다.
   ⚠ **크기는 콘솔 눈금입니다 — 기본 28 · lg 40** (2026-09-15 사용자 지시 "콘솔쪽이 보통 맞아").
     앞서 DS 가 쓰던 32 · 44 는 콘솔 표 줄(52px)에서 아바타가 줄을 꽉 채워 답답했습니다.
     .sm(24) 만 DS 쪽 여분입니다 — 콘솔에 짝이 없으니 콘솔 화면에서는 쓰지 마세요.
   ⛔ 32 · 44 로 되돌리지 마세요 */
.ds-avatar { width: 28px; height: 28px; border-radius: var(--r-full); flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--text-2xs); font-weight: var(--weight-normal); color: var(--foreground);
  background: color-mix(in srgb, var(--muted-foreground) 4%, var(--muted));
  border: 1px solid color-mix(in srgb, var(--muted-foreground) 28%, transparent); }
.ds-avatar.sm { width: 24px; height: 24px; font-size: var(--text-2xs); }
.ds-avatar.lg { width: 40px; height: 40px; font-size: var(--text-base); }
.ds-avatar.brand { background: var(--accent-muted); color: var(--primary); border-color: transparent; }
.avatar-group { display: inline-flex; }
/* stylelint-disable-next-line declaration-property-value-disallowed-list --
   겹친 아바타를 분리하는 "구멍"입니다. 보더가 아니라 배경색으로 파낸 것이라
   1px로는 겹침이 읽히지 않습니다. 2px가 허용되는 곳은 여기와 라디오 선택 링 · 타임라인 링뿐입니다. */
.avatar-group .ds-avatar { margin-left: -8px; border: 2px solid var(--background); }
.avatar-group .ds-avatar:first-child { margin-left: 0; }

/* ── ds-chip (제거 가능한 선택 항목) ── */
/* ⚠ 값은 **관리자 콘솔 ui.css 의 .chip 그대로**입니다 (2026-09-15 사용자 지시 "콘솔쪽이 보통 맞아").
   앞서 DS 가 혼자 쓰던 28px · 면 --card · 500 은 콘솔 표 칸에서 칩이 한 단 굵고 커 보였습니다.
   ⛔ 28 · --card · 500 으로 되돌리지 마세요. 면을 깐 칩이 필요하면 .chip.brand 를 쓰세요 */
.chip { display: inline-flex; align-items: center; gap: 5px; height: 24px; padding: 0 6px 0 9px;
  border-radius: var(--r-full); font-size: var(--text-xs); font-weight: var(--weight-normal);
  border: 1px solid var(--border); color: var(--foreground); background: transparent; }
.chip .x { all: unset; box-sizing: border-box; cursor: pointer; width: 16px; height: 16px;
  border-radius: 50%; display: flex; align-items: center; justify-content: center;
  font-size: var(--text-2xs); color: var(--muted-foreground); }
.chip .x:hover { background: var(--gray-4); color: var(--foreground); }
/* brand = 걸린 조건 — 면 없이 브랜드 테두리 · 글자 (2026-11-06 제품에 맞춤)(콘솔 · 이용자 화면 조건 줄 "bg 없고 Stroke만"). 28 · 모서리 6 */
.chip.brand { height: 28px; padding: 0 7px 0 10px; gap: 6px; border-radius: var(--r-el); font-size: var(--text-sm);
  background: transparent; border-color: var(--nav-active); color: var(--nav-active-fg); }
.chip.brand .x { width: 18px; height: 18px; border-radius: var(--r-el); color: var(--primary-text); }

/* ── ds-divider ── */
.divider { border: none; border-top: 1px solid var(--border); margin: 16px 0; }
.divider-label { display: flex; align-items: center; gap: 12px; margin: 16px 0;
  font-size: var(--text-xs); color: var(--muted-foreground); }
.divider-label::before, .divider-label::after { content: ""; flex: 1; border-top: 1px solid var(--border); }

/* ── ds-skeleton ── */
.skeleton { border-radius: var(--r-el); background: linear-gradient(90deg, var(--muted) 25%, var(--gray-4) 50%, var(--muted) 75%);
  background-size: 200% 100%; animation: shimmer 1.4s infinite; }
@keyframes shimmer { to { background-position: -200% 0; } }
.skeleton.text { height: 13px; }
.skeleton.circle { border-radius: 50%; }

/* ── ds-toast ──────────────────────────────
   면을 뒤집습니다 — 라이트에서는 어두운 면, 다크에서는 밝은 면.
   화면 위에 잠깐 떠 있다 사라지는 알림이라, 본문과 같은 면이면 눈에 안 들어옵니다.
   툴팁도 같은 규칙을 씁니다(반전 면).
   테두리는 두지 않습니다 — --shadow-overlay의 첫 겹(0 0 0 1px)이 이미 윤곽이라
   둘 다 쓰면 1px 선이 두 겹으로 겹칩니다. */
/* ⚠ **알약입니다** — 관리자 콘솔 ui.css 의 .toast 그대로 (2026-09-15 사용자 지시 "콘솔쪽이 보통 맞아").
   앞서 DS 가 쓰던 각진 r-el · 13px · min-width 280 은 콘솔의 짧은 알림("저장했습니다")에서
   글자보다 상자가 한참 넓어 빈 칸이 남았습니다. 자리(가운데 아래 고정)는 쓰는 쪽 몫입니다.
   ⛔ 각진 모서리로 되돌리지 마세요 */
.toast { display: inline-flex; align-items: center; gap: 12px;
  border: 0; border-radius: var(--r-full);
  background: var(--foreground); color: var(--background);
  padding: 10px 20px; font-size: var(--text-base); font-weight: var(--weight-medium);
  box-shadow: var(--shadow-lg); }

/* 앞머리는 점 대신 상태 아이콘 — 색+형태로 성공/실패가 동시에 읽힘 (Geist·Astryx 참고).
   반전 면 위라 상태색도 반전 값을 씁니다. */
.toast .t-icon { display: inline-flex; flex-shrink: 0; align-items: center; }
.toast.success .t-icon { color: var(--success-on-inverse); }
.toast.danger .t-icon { color: var(--error-on-inverse); }
.toast .t-body { flex: 1; }
/* 알약 안의 글자 버튼(실행 취소 등) — 콘솔 showToast 의 .toast-act 와 한 벌(2026-11-06 제품에 맞춤):
   알약 글자색을 물려받고 밑줄로만 누를 것임을 말합니다 — 면 · 테두리 없음 */
.toast .t-action, .toast .toast-act { all: unset; box-sizing: border-box; cursor: pointer; flex-shrink: 0; margin-left: 2px;
  font: inherit; font-weight: var(--weight-semibold); color: inherit; text-decoration: underline; text-underline-offset: 3px; }
.toast .t-action:focus-visible, .toast .toast-act:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--foreground), 0 0 0 4px var(--background); border-radius: var(--r-in); }

/* ── ds-thinking (에이전트 추론 중 — Soft Rise v1.0) ──
   도트 3개가 -4px 파동(사이클의 64%는 정지), 라벨은 브랜드 shimmer.
   스펙: DocuRAG "AI 응답 생성 인디케이터 설계_최종본" */
.thinking { display: inline-flex; align-items: center; gap: 11px; font-size: var(--text-sm); color: var(--muted-foreground); }
.thinking .dots { display: inline-flex; gap: 5px; align-items: center; }
.thinking .dots i { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0;
  animation: thinkwave 1.6s cubic-bezier(.45, 0, .55, 1) infinite; }
.thinking .dots i:nth-child(1) { background: color-mix(in srgb, var(--primary) 45%, var(--background)); }
.thinking .dots i:nth-child(2) { background: color-mix(in srgb, var(--primary) 72%, var(--background)); animation-delay: .18s; }
.thinking .dots i:nth-child(3) { background: var(--primary); animation-delay: .36s; }
@keyframes thinkwave { 0% { transform: translateY(0); opacity: .4; }
  18% { transform: translateY(-4px); opacity: 1; }
  36%, 100% { transform: translateY(0); opacity: .4; } }
.thinking .t-label { font-weight: var(--weight-medium);
  background: linear-gradient(90deg, var(--gray-10) 20%, var(--primary-text) 50%, var(--gray-10) 80%);
  background-size: 200% 100%; background-clip: text;
  /* stylelint-disable-next-line property-no-vendor-prefix --
     Safari는 아직 접두사 없는 background-clip: text를 글자에 못 먹입니다.
     빼면 사파리에서 글자가 통째로 사라집니다(-webkit-text-fill-color: transparent 때문) */
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  animation: shimmer 3s linear infinite, thinkfade .45s ease-out; }
@keyframes thinkfade { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: translateY(0); } }
/* compact — dot 4px / text 12px, shimmer 생략 */
.thinking--compact { gap: 8px; font-size: var(--text-xs); }
.thinking--compact .dots { gap: 4px; }
.thinking--compact .dots i { width: 4px; height: 4px; }
/* inline — 텍스트 뒤에 4px 도트, shimmer 생략 */
.thinking--inline { gap: 7px; }
.thinking--inline .dots { gap: 3px; }
.thinking--inline .dots i { width: 4px; height: 4px; }
.thinking--compact .t-label, .thinking--inline .t-label { background: none;
  -webkit-text-fill-color: currentColor; color: inherit; font-weight: var(--weight-normal);
  animation: thinkfade .45s ease-out; }
@media (prefers-reduced-motion: reduce) {
  .thinking .dots i { animation: none; opacity: .7; }
  .thinking .t-label { background: none; -webkit-text-fill-color: currentColor; color: inherit; animation: none; }
}

/* ── 메뉴 면 · 항목 — DsMenu(VMenu) · 콘솔 계정 메뉴 · 화면 안 더보기 메뉴가 함께 씀 ──
   2026-11-06 ds-vuetify.css 에서 옮김 — Vuetify 가 없는 관리자 콘솔도 이 한 벌을 씁니다(콘솔 ui.css 사본을 걷음).
   면 값은 콘솔 드롭다운 .pop 과 같습니다(모서리 8 · 1px 테두리 + --shadow-lg · 최소 폭 150 · hover --overlay-hover) — 값이 갈리면 콘솔이 정답.
   저수준 패널 + 항목 어휘. 항목은 소비자가 .ds-menu-item 으로 조립합니다.
   구조: <div class="ds-menu-item">[아이콘 svg] <span class="ds-menu-item__label">라벨</span>
          [<span class="ds-menu-item__trail">단축키·메타</span>]</div>
   변형: --selected(현재값·체크) · --error(파괴적) · --disabled(비활성)
   구성: .ds-menu-label(섹션 머리) · .ds-menu-sep(구분선)
   ⚠ <div> 와 <button> 둘 다 받습니다 — 아래 규칙이 버튼 기본값(테두리·회색 면·중앙 정렬·
     Arial 13.3px)까지 지웁니다. 소비자가 화면 CSS에서 button.ds-menu-item 을 다시 잡지
     않게 하려는 것입니다: 그 선택자는 (0,1,1)이라 --selected 같은 변형(0,1,0)을 이깁니다.
     실제로 font:inherit 로 눌러 둔 곳이 있었고, 선택된 항목의 굵기가 500 대신 400 이었습니다
     (2026-09-10). ⛔ 여기서 font 단축 속성을 쓰지 마세요 — 같은 사고가 DS 안으로 들어옵니다 */
.ds-menu-panel {
  background: var(--card); border: 1px solid var(--border);
  border-radius: 8px; padding: 4px; min-width: 150px; max-width: 320px;
  max-height: min(60vh, 420px); overflow-y: auto;
  font-family: var(--font); box-shadow: var(--shadow-lg);
}

.ds-menu-item {
  position: relative; display: flex; align-items: center; gap: 9px; height: 32px; padding: 0 10px;
  border-radius: var(--r-el); font-size: var(--text-sm); color: var(--foreground);
  cursor: pointer; user-select: none; white-space: nowrap;
  transition: background .12s ease, color .12s ease;
  /* <button> 으로 조립했을 때의 기본값 제거 — font-weight 는 건드리지 않습니다(변형이 정합니다) */
  width: 100%; border: 0; background: none; text-align: start;
  font-family: var(--font); font-style: inherit;
}
/* 앞머리 아이콘 — 중립 항목에서만 눌러 둡니다(글자가 먼저 읽히게).
   뜻이 있는 색(파괴적·선택됨)일 때는 아래에서 글자와 같은 색으로 되돌립니다.
   글자만 빨갛고 아이콘이 회색이면 경고가 반만 전달됩니다. */
.ds-menu-item > svg { flex: none; color: var(--muted-foreground); }
.ds-menu-item:hover,
.ds-menu-item:focus-visible { background: var(--overlay-hover); outline: none; }
.ds-menu-item:active { background: var(--gray-4); }

/* 라벨은 신축·말줄임, 우측 보조(단축키·메타)는 끝으로 */
.ds-menu-item__label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; text-align: left; }
.ds-menu-item__trail {
  flex: none; margin-left: auto; display: inline-flex; align-items: center; gap: 6px;
  color: var(--muted-foreground); font-size: var(--text-xs);
}

/* 선택됨 — 브랜드색 + 앞아이콘 강조 (소비자가 check 아이콘을 앞에 두면 체크로 읽힘) */
.ds-menu-item--selected { color: var(--primary-text); font-weight: var(--weight-medium); }
.ds-menu-item--selected > svg { color: var(--primary); }

/* 파괴적 동작 — 글자와 아이콘이 함께 빨갛습니다 */
.ds-menu-item--error, .ds-menu-item--danger { color: var(--error); }   /* --danger = 문서 · 데모가 쓰던 이름(2026-11-06 맞춤) */
.ds-menu-item--error > svg,
.ds-menu-item--error .ds-icon,
.ds-menu-item--error .v-icon,
.ds-menu-item--danger > svg,
.ds-menu-item--danger .ds-icon,
.ds-menu-item--danger .v-icon { color: var(--error); }
.ds-menu-item--error:hover,
.ds-menu-item--error:focus-visible,
.ds-menu-item--danger:hover,
.ds-menu-item--danger:focus-visible { background: var(--error-subtle); }

/* 비활성 */
.ds-menu-item--disabled { color: var(--gray-9); cursor: default; pointer-events: none; }
.ds-menu-item--disabled > svg { color: var(--gray-8); }
/* 잠김 — 못 누르지만 **까닭 툴팁(title)은 떠야** 하는 항목. --disabled 는 pointer-events 를 꺼 툴팁까지 죽습니다.
   aria-disabled="true" + title="까닭" 과 함께 씁니다 (2026-11-06 이용자 화면 대화 메뉴 .cog-cmenu__locked 에서 올림) */
.ds-menu-item--locked { opacity: .45; cursor: default; }
.ds-menu-item--locked:hover,
.ds-menu-item--locked:focus-visible { background: none; }
/* 빈 줄 — 메뉴 안 목록이 비었을 때 한 줄("옮길 프로젝트가 없습니다"). 항목이 아니라 눌리지 않습니다 (같은 날 .cog-cmenu__none 에서) */
.ds-menu-empty { padding: 8px 10px; font-size: var(--text-sm); color: var(--muted-foreground); }

/* 섹션 머리글 */
.ds-menu-label {
  padding: 6px 10px 4px; line-height: 14px;   /* 블록 24px — 4의 배수 격자 */
  font-size: var(--text-2xs); font-weight: var(--weight-semibold);
  color: var(--muted-foreground);
}
.ds-menu-panel > .ds-menu-label:first-child { padding-top: 4px; }

/* 구분선 — 얇게, 좌우 인셋.
   위 4 + 선 1 + 아래 3 = 블록 8px. 이 8과 항목 32가 모두 4의 배수라서
   구분선 사이 간격이 항상 4의 배수가 되고, 배율 1.25·1.5 에서도 모든 구분선이
   같은 소수점 위치에 앉아 같은 굵기·색으로 찍힌다. 아래가 1px 좁은 건
   눈에 보이지 않지만 이 격자를 지키는 값이다. (2026-08-27) */
/* ⚠ 2026-11-06 밤 되돌림 — 같은 날 저녁 대조에서 콘솔 .pop .sep(팝오버 · 선 없는 여백 8)에 맞춰 선을 걷었는데,
   메뉴(.ds-menu-panel — 계정 메뉴 등)는 그날 아침까지 콘솔 ui.css 에서도 1px 선이었습니다. 선이 빠지고 묶음 간격만 16으로 벌어져
   사용자가 "여백이 틀어졌다"고 짚음. 팝오버(.pop .sep)와 메뉴(.ds-menu-sep)는 다른 규칙입니다 — ⛔ 둘을 한 값으로 합치지 마세요 */
.ds-menu-sep { height: 1px; margin: 4px 6px 3px; background: var(--border-subtle); border: 0; }

/* ── ds-btn-group — 세그먼티드 컨트롤 (Astryx SegmentedControl 참고)
   회색 트랙(32px) 안에서 선택 항목만 흰 칩(26px). 구분선·외곽 보더 없음.
   활성 칩 = 흰 면 + 브랜드 선·글자 (--nav-active 개정 확장, 2026-08-19) —
   면까지 브랜드 틴트로 깔면 회색 트랙 위에서 글자 시인성이 떨어져 면은 흰색을 유지합니다. ── */
.ds-btn-group {
  display: inline-flex; align-items: center; gap: 2px;
  height: 32px; padding: 3px; border-radius: var(--r-el); background: var(--muted);
}
.ds-btn-group__seg {
  all: unset; box-sizing: border-box; cursor: pointer;
  /* justify-center — 세그먼트를 풀폭으로 늘려 쓰는 곳(상세 패널 정보/이력)에서
     라벨이 왼쪽에 붙지 않도록. 자연 폭일 때는 영향 없음 */
  display: inline-flex; align-items: center; justify-content: center;
  height: 26px; padding: 0 12px; gap: 6px;   /* gap — 아이콘 + 낱말 칸 */
  border-radius: var(--r-el); font-family: var(--font);
  font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--muted-foreground);
  transition: color .12s ease, background .12s ease, box-shadow .12s ease;
}
/* 컨트롤 스케일 32/40/48 */
.ds-btn-group--md { height: 36px; padding: 4px; }
.ds-btn-group--md .ds-btn-group__seg { height: 28px; padding: 0 13px; }
.ds-btn-group--sm .ds-btn-group__seg { height: 26px; padding: 0 12px; font-size: var(--text-xs); }
.ds-btn-group--lg { height: 40px; padding: 4px; }
.ds-btn-group--lg .ds-btn-group__seg { height: 32px; padding: 0 14px; font-size: var(--text-base); }
.ds-btn-group__seg:hover { color: var(--foreground); }
/* 포커스 링 — 브랜드 22% 3px 후광(콘솔 .btngroup · 입력칸 포커스와 한 벌, 2026-11-06). --focus-ring 의 2px 띄운 실선이 아닙니다 */
.ds-btn-group__seg:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 22%, transparent); }
.ds-btn-group__seg.is-on { background: var(--card); color: var(--nav-active-fg);
  box-shadow: 0 0 0 1px var(--nav-active); }
.ds-btn-group__seg.is-on:focus-visible { box-shadow: 0 0 0 1px var(--nav-active), 0 0 0 4px color-mix(in srgb, var(--primary) 22%, transparent); }
/* 잠긴 칸 — 흐리게 · 기본 커서 · hover 글자색 안 바뀜 (콘솔 .btngroup button:disabled 와 한 벌 — 체크박스 · .input 의 잠김과 같은 .5) */
.ds-btn-group__seg:disabled { opacity: .5; cursor: default; }
.ds-btn-group__seg:disabled:hover { color: var(--muted-foreground); }
/* 아이콘만 — 보는 방식 전환(목록 ↔ 조직도 · 카드 ↔ 표). 낱말 자리를 정사각 30 칸으로 바꾼 것뿐 — 색 · 높이 · 활성은 위 그대로.
   칸마다 title · aria-label 필수 (콘솔 .btngroup--ic 에서 올림, 2026-11-06) */
.ds-btn-group--ic .ds-btn-group__seg { width: 30px; padding: 0; justify-content: center; }
.ds-btn-group--ic .ds-btn-group__seg svg { width: 16px; height: 16px; }

/* ── 값이 걸린 필터 트리거 — 활성 규칙(--nav-active)의 세 번째 적용처 (2026-08-19) ──
   파셋 버튼("부서 : 재무팀 ×")·기간 버튼처럼 **지금 조건이 적용돼 있음**을 알리는 트리거는
   값이 걸린 동안 테두리·글자를 브랜드로 바꿉니다. 컴포넌트가 아니라 상태 클래스라,
   Select·Menu·자체 버튼 등 어떤 트리거에든 얹습니다. 값이 없으면 빼서 중립으로 돌립니다. */
.ds-filter-on { border-color: var(--nav-active); color: var(--nav-active-fg); }
.ds-filter-on .ds-icon { color: var(--nav-active-fg); }

/* ── ds-treeview (cog-tree) — 자체 폴더 트리, 평탄화 렌더.
   VTreeview 대신: 깊이별 14px 가이드(들여쓰기+세로 연결선)·30px 컴팩트 행. ── */
.cog-tree { display: flex; flex-direction: column; }
.cog-tree__row {
  display: flex; align-items: center; gap: 4px; height: 30px; padding: 0 6px; width: 100%;
  border-radius: var(--r-el); cursor: pointer; text-align: left;
  font-size: var(--text-base); color: var(--foreground); background: transparent; border: 0;
  font-family: var(--font);
}
.cog-tree__row:hover { background: var(--muted); }
.cog-tree__row:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.cog-tree__row.is-active { background: var(--selected-fill); }
.cog-tree__guide {
  flex: 0 0 14px; align-self: stretch; margin-inline-end: -4px; pointer-events: none;
  background: linear-gradient(to right, transparent 7px, var(--input) 7px, var(--input) 8px, transparent 8px);
}
.cog-tree__chev {
  flex: 0 0 16px; display: inline-flex; align-items: center; justify-content: center; color: var(--muted-foreground);
  border-radius: var(--r-in);
}
.cog-tree__chev:hover { background: var(--gray-4); }
.cog-tree__icon { flex: none; color: var(--muted-foreground); }
.cog-tree__label { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: var(--weight-medium); }

/* ── ds-timeline — 시간순 이력. **VTimeline을 버리고 직접 그립니다** (2026-08-28).
   버린 까닭: VTimeline의 점은 면을 채운 원이라 링으로 바꾸려면 .v-timeline-divider__inner-dot을
   뒤집어야 하고, 점 크기 · 선 두께 · 항목 간격이 전부 density 프리셋에 묶여 있어 우리 치수로
   통제되지 않았습니다. 타임라인은 포커스 트랩도 포지셔닝도 키보드 조작도 없는
   **시각이 전부인 부품**이라, 기여 가이드의 판단 기준상 custom이 맞습니다 (DsTreeview와 같은 길).
   치수는 naive-ui n-timeline(medium)에서 옮겼습니다 — 그쪽 titleFontSize · iconSize가 둘 다
   14px이라 우리 --text-base와 그대로 맞습니다.
     링 14px · 획 2px · 속 빔 | 선 2px | 링↔글자 26px(= 링 14 + 12) | 제목 아래 6px | 항목 사이 20px
   ⚠ 링의 세로 위치와 선의 시작점은 **제목 글자 크기에서 계산**합니다(calc).
     제목 크기를 바꾸면 링이 저절로 따라옵니다 — 숫자를 손으로 다시 박지 마세요.
   ⚠ variant 색은 **거드는 표시**입니다. 성공 · 실패를 링 색만으로 가르지 마세요
     (a11y — 색각 이상에서 사라집니다). 글로도 말해야 합니다. ── */
.ds-timeline { list-style: none; margin: 0; padding: 0; }
.ds-timeline__item { position: relative; padding: 0 0 20px 26px; }
.ds-timeline__item:last-child { padding-bottom: 0; }
/* 링 — 세로 가운데 = 제목 줄의 한가운데 (14 × 1.25 ÷ 2 − 14 ÷ 2 = 1.75px) */
.ds-timeline__item::before {
  content: ''; position: absolute; left: 0;
  top: calc(var(--text-base) * 1.25 / 2 - 7px);
  width: 14px; height: 14px; border-radius: 50%; box-sizing: border-box;
  /* stylelint-disable-next-line declaration-property-value-disallowed-list --
     타임라인 링은 2px입니다. 라디오 선택 링과 같은 이유 — 14px 원에 1px 획을 두르면
     선(2px)보다 얇아져서 링이 선에 매달린 것처럼 보입니다. 레이아웃 보더가 아니라
     그래픽 마크라 이 시스템의 1px 규칙 바깥입니다 */
  border: 2px solid var(--gray-8);
}
/* 선 — 링 바닥에서 다음 링 꼭대기까지. **링을 관통하지 않습니다** */
.ds-timeline__item::after {
  content: ''; position: absolute; left: 6px; bottom: 0;
  top: calc(var(--text-base) * 1.25 / 2 + 7px);
  width: 2px; background: var(--gray-5);
}
.ds-timeline__item:last-child::after { display: none; }
.ds-timeline__item--success::before { border-color: var(--success); }
.ds-timeline__item--danger::before  { border-color: var(--error); }
.ds-timeline__item--brand::before   { border-color: var(--brand); }
/* 글은 제목 → 본문 → 시각 차례입니다. 시각이 맨 아래인 것이 n-timeline 규격이고,
   목록을 훑을 때 먼저 읽혀야 하는 것은 '무엇이 있었나'입니다 */
.ds-timeline__title {
  margin: 0 0 6px; font-size: var(--text-base); font-weight: var(--weight-semibold);
  line-height: 1.25; color: var(--foreground);
}
.ds-timeline__body { font-size: var(--text-base); line-height: 1.5; color: var(--muted-foreground); }
.ds-timeline__time {
  display: block; margin-top: 6px; font-size: var(--text-xs);
  color: var(--muted-foreground); font-variant-numeric: tabular-nums;
}

/* ── ds-dotfield (도트 필드 로더 — 생성형 작업 대기) ── */
.dotfield { display: flex; align-items: center; justify-content: center;
  aspect-ratio: 1 / 1; width: 100%; background: var(--muted);
  border-radius: var(--r-ct); }
.dotfield-grid { display: grid; gap: 12px; }
.dotfield i { width: 4px; height: 4px; border-radius: 50%; background: var(--gray-8);
  transform: scale(.4); opacity: .18; will-change: transform, opacity; }
/* 문서 데모용 CSS 근사 애니메이션 — 실제 컴포넌트는 rAF로 매 프레임 계산 */
.dotfield--css i { animation: dotwave 1.8s infinite ease-in-out; }
@keyframes dotwave { 0%, 100% { transform: scale(.35); opacity: .12; }
  40% { transform: scale(1.1); opacity: .84; } }

/* ── ds-loading-screen (풀스크린 로딩 화면 — 3D 도트 스피어) ── */
.loading-screen { position: fixed; inset: 0; z-index: 60; display: flex;
  align-items: center; justify-content: center; background: var(--background);
  animation: loading-screen-in 0.6s ease-out; }
.loading-screen--inline { position: relative; inset: auto; z-index: auto;
  animation: none; background: none; }
.loading-screen__sphere { position: relative; }
@keyframes loading-screen-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .loading-screen { animation: none; }
}

/* ── ds-agent-input (auto-grow 입력창) ── */
.agent-input { border: 1px solid var(--input); border-radius: var(--r-ct); background: var(--card);
  transition: border-color .16s ease, box-shadow .16s ease; }
/* 포커스 표시는 바깥 상자 하나뿐입니다. 안쪽 textarea에도 링을 주면
   같은 포커스가 테두리 두 겹으로 그려집니다. */
.agent-input:focus-within { border-color: var(--primary); box-shadow: 0 0 0 3.5px var(--accent-muted); }
.agent-input textarea:focus, .agent-input textarea:focus-visible { outline: none; box-shadow: none; }
.agent-input textarea { all: unset; box-sizing: border-box; display: block; width: 100%;
  padding: 12px 14px 4px; font-size: var(--text-base); font-family: var(--font); color: var(--foreground);
  resize: none; min-height: 44px; }
.agent-input textarea::placeholder { color: var(--muted-foreground); }
.agent-input .ai-bar { display: flex; align-items: center; gap: 6px; padding: 6px 8px 8px; }
.ai-tool { all: unset; box-sizing: border-box; cursor: pointer; width: 28px; height: 28px;
  display: flex; align-items: center; justify-content: center; border-radius: var(--r-el);
  color: var(--muted-foreground); font-size: var(--text-base); }
.ai-tool:hover { background: var(--muted); color: var(--foreground); }
.ai-spacer { flex: 1; }

/* ── ds-search-result ── */
.sresult { padding: 14px 0; border-bottom: 1px solid var(--border-subtle); }
.sresult:last-child { border-bottom: none; }
.sresult h4 { font-size: var(--text-base); font-weight: var(--weight-semibold); }
.sresult h4 a { color: var(--foreground); text-decoration: none; }
.sresult h4 a:hover { color: var(--primary); }
.sresult .s-path { font-size: var(--text-xs); color: var(--muted-foreground); font-family: var(--mono); margin-top: 2px; }
.sresult p { font-size: var(--text-sm); color: var(--muted-foreground); margin-top: 5px; }
.sresult mark { background: var(--accent-muted); color: var(--primary); border-radius: 2px; padding: 0 2px; }

/* ── ds-file (드라이브) ── */
.file-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; }
.file-card { border: 1px solid var(--border); border-radius: var(--r-el); padding: 15px;
  cursor: pointer; transition: border-color .16s ease, background .16s ease; background: var(--card); }
.file-card:hover { border-color: var(--border-emphasized); background: var(--gray-2); }
.file-card.selected { border-color: var(--border-emphasized); background: var(--selected-fill); }
.file-card .f-icon { font-size: var(--text-xl); }
.file-card .f-name { font-size: var(--text-sm); font-weight: var(--weight-medium); margin-top: 8px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.file-card .f-meta { font-size: var(--text-2xs); color: var(--muted-foreground); margin-top: 2px; }
.file-row { display: flex; align-items: center; gap: 10px; height: 42px; padding: 0 13px;
  border-radius: var(--r-el); cursor: pointer; font-size: var(--text-base); transition: background .16s ease; }
.file-row:hover { background: var(--gray-2); }
.file-row.selected { background: var(--selected-fill); }
.file-row.disabled { cursor: default; opacity: .5; }
.file-row.disabled:hover { background: transparent; }
/* 체크박스(CSS 전용) — Vue를 쓰지 않는 화면(템플릿·정적 HTML)용 .ds-check의 쌍둥이입니다.
   둘은 같은 모양이어야 합니다: 16px 상자, radius 4, 브랜드 면 + 흰 표식, 획 2.
   표식을 회전한 border(::after)로 그리지 마세요 — 크기·정렬이 브라우저 반올림에 따라
   어긋나 상자 안에서 삐뚤어집니다. 표식은 항상 뷰박스가 고정된 SVG입니다. */
.check, .file-row .f-check {
  appearance: none;
  width: 16px; height: 16px; margin: 0; flex-shrink: 0;
  /* ⚠ Vue 쪽 .ds-check__box 와 **같은 테두리**입니다 — 위 머리말 참고 */
  border: 1px solid var(--border-emphasized); border-radius: var(--r-in); background: var(--card);
}
.file-row .f-check { pointer-events: none; }
/* 표식 SVG는 Vue(DsCheckbox)와 같은 뷰박스 16 · 같은 경로 · 상자 가득(contain).
   Lucide 24 뷰박스를 11px로 축소하면 획 2가 0.9px로 얇아져 Vue 쪽과 다르게 보입니다. */
.check:checked, .file-row .f-check:checked {
  border-color: var(--primary);
  background: var(--primary) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M3.5 8.5 6.5 11.5 12.5 5'/></svg>") center / contain no-repeat;
}
.check:indeterminate,
/* HTML의 indeterminate는 속성이 아니라 JS 프로퍼티입니다. 정적 HTML에서도
   부분 선택을 표현할 수 있도록 클래스 하나를 함께 받습니다. */
.check.is-mixed {
  border-color: var(--primary);
  background: var(--primary) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round'><path d='M4 8h8'/></svg>") center / contain no-repeat;
}
/* ⚠ Vue 쪽 .ds-check__box:hover 와 **같은 이유·같은 값**입니다 — 그 머리말 참고 */
.check:hover:not(:checked, :indeterminate, .is-mixed) { border-color: var(--muted-foreground); }
.check:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* ⚠ 꺼진 체크박스는 **면을 바꾸지 않고 통째로 흐립니다** — 콘솔 input[type=checkbox]:disabled
   와 같은 규격(opacity .5 · 기본 커서). 면을 --muted 로 칠하면 **켜진 채 꺼진 것**이 파란 면을
   잃어 안 켜진 것으로 보입니다 — 권한 매트릭스처럼 잠긴 칸이 많은 화면에서 곧 오독이 됩니다.
   ⛔ not-allowed 로 되돌리지 마세요: 콘솔은 기본 커서입니다(금지 표시는 마찰만 줍니다) */
.check:disabled { opacity: .5; cursor: default; }
.file-row .f-icon { width: 18px; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; color: var(--muted-foreground); }
/* 이름은 내용 폭 — 뒤 슬롯(즐겨찾기 별)이 이름 바로 옆에 붙도록. 남는 공간은 f-fill이 차지. */
.file-row .f-name { flex: 0 1 auto; min-width: 0; font-weight: var(--weight-medium); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.file-row .f-fill { flex: 1 1 0; min-width: 8px; }
.file-row .f-meta { font-size: var(--text-xs); color: var(--muted-foreground); flex-shrink: 0; }

/* ── ds-artifact-panel ── */
.artifact { border: 1px solid var(--border); border-radius: var(--r-ct); overflow: hidden; background: var(--card); }
.artifact .a-head { display: flex; align-items: center; gap: 8px; padding: 10px 14px;
  border-bottom: 1px solid var(--border); background: var(--gray-2); }
.artifact .a-title { font-size: var(--text-sm); font-weight: var(--weight-semibold); flex: 1; }
.artifact .a-body { padding: 16px; font-size: var(--text-sm); font-family: var(--mono); color: var(--muted-foreground);
  white-space: pre; overflow-x: auto; }

/* ── ds-toc — 본문 옆 목차 ──────────────────────────────
   왼쪽 선 하나로 흐름을 만들고, 지금 절만 선과 글자를 진하게 합니다.
   면을 칠하지 않는 이유: 목차는 읽는 대상이 아니라 위치 표시라
   본문보다 조용해야 합니다. */
.ds-toc { display: flex; flex-direction: column; }
.ds-toc a {
  display: block; padding: 5px 0 5px 14px;
  /* stylelint-disable-next-line declaration-property-value-disallowed-list --
     목차 왼쪽 레일은 2px입니다. 1px이면 지금 보는 항목의 브랜드색 표시가 안 읽힙니다 */
  border-left: 2px solid var(--gray-a3);
  font-size: var(--text-sm); line-height: var(--leading-ui);
  /* gray-11 — 보조 글자의 표준값. gray-10은 흰 배경에서 3.78:1로 AA에 못 미칩니다 */
  color: var(--muted-foreground); text-decoration: none;
  transition: color .16s ease, border-color .16s ease;
}
.ds-toc a:hover { color: var(--foreground); border-left-color: var(--gray-a6); }
.ds-toc a.on {
  color: var(--foreground); font-weight: var(--weight-medium);
  border-left-color: var(--foreground);
}
.ds-toc a:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: var(--r-in); }

/* ── ds-minimap — 대화 미니맵 (DsChatMinimap · 2026-11-11) ──
   레일은 면 · 테두리 없이 막대만 — 목차(.ds-toc)처럼 위치 표시라 본문보다 조용해야 합니다.
   지금 차례만 진한 글자색, 나머지는 --gray-a5. 가리키는 막대는 --muted-foreground. */
.ds-minimap {
  all: unset;
  box-sizing: border-box;
  position: relative;
  display: flex; flex-direction: column;
  max-height: 100%;
  font-family: var(--font);
  /* all: unset 은 pointer-events 를 부모 값으로 되돌립니다(상속 속성). 쓰는 쪽이 빈 자리 클릭을 통과시키려고
     그릇에 pointer-events: none 을 걸면 레일까지 마우스를 못 받아 미리보기가 안 떴습니다(2026-11-11 실제 마우스로 발견) — 그래서 직접 켭니다 */
  pointer-events: auto;
  /* 부품이 props(itemSize · pillWidth · magnification)로 인라인에 덮어쓰는 지역 값 — 여기는 기본값(HTML 로만 그릴 때도 섬) */
  --mm-size: 12px;
  --mm-pill: 2px;
  --mm-mag: 2.5;
}
/* 기본 장착 — body 로 내보내 스크롤 칸 가장자리 가운데에 고정(left · right · top · max-height 는 부품이 칸을 재서 줌).
   z 15 = 칸 안 붙박이 머리(≤10) 위 · 떠 있는 판 · 메뉴(20~)와 서랍(80) · 창 아래 — 그런 것이 열리면 레일이 그 밑으로 깔립니다 */
.ds-minimap--dock { position: fixed; transform: translateY(-50%); z-index: 15; }
.ds-minimap--right { align-items: flex-end; }
.ds-minimap--left { align-items: flex-start; }

/* 막대 줄을 담는 칸 — 자리가 모자라면 줄이 4px 까지 줄고, 그래도 넘치면(질문 수백 개) **이 칸 안에서** 굴러갑니다.
   미리보기 카드는 이 칸 밖(nav 바로 아래)이라 굴림에 잘리지 않습니다. 스크롤바는 숨김 — 레일은 위치 표시라 조용해야 함(같은 날 리뷰) */
.ds-minimap__track {
  display: flex; flex-direction: column; align-items: inherit;
  min-height: 0;
  overflow-y: auto; overscroll-behavior: contain;
  scrollbar-width: none;
}
.ds-minimap__track::-webkit-scrollbar { display: none; }

/* 줄 = 누르는 자리. 막대보다 넓게(막대 최장 길이 + 여유) 잡아 가는 막대도 쉽게 맞힙니다 */
.ds-minimap__item {
  all: unset;
  box-sizing: border-box;
  display: flex; align-items: center;
  flex: 0 1 var(--mm-size); min-height: 4px;
  width: calc(var(--mm-size) * var(--mm-mag) + 8px);
  padding: 0 4px;
  cursor: pointer;
  border-radius: var(--r-in);
}
.ds-minimap--right .ds-minimap__item { justify-content: flex-end; }
/* 포커스 링은 안쪽 — 바깥 링(--focus-ring)은 굴러가는 칸(.ds-minimap__track)의 가장자리에 잘립니다 */
.ds-minimap__item:focus-visible { box-shadow: inset 0 0 0 2px var(--primary); }

.ds-minimap__pill {
  display: block;
  height: var(--mm-pill);
  border-radius: var(--r-full);
  background: var(--gray-a5);
  transition: width .2s ease-out, background-color .16s ease;
}
.ds-minimap__item.is-lens .ds-minimap__pill { background: var(--muted-foreground); }
.ds-minimap__item.is-current .ds-minimap__pill { background: var(--foreground); }

/* 미리보기 면 = DsHoverCard(.ds-popover) 와 같은 값. 글은 UI 타이포(설명문 1.8 이 아님) */
.ds-minimap__preview {
  position: absolute;
  width: 280px;
  padding: 10px 12px;
  transform: translateY(-50%);
  background: var(--card);
  border: 1px solid var(--border);
  /* stylelint-disable-next-line declaration-property-value-disallowed-list -- 팝오버 면 모서리 8(.ds-popover 와 같은 값) */
  border-radius: 8px;
  box-shadow: var(--shadow-lg);
  pointer-events: none;
  z-index: 10;
}
.ds-minimap--right .ds-minimap__preview { right: calc(100% + 8px); }
.ds-minimap--left .ds-minimap__preview { left: calc(100% + 8px); }

.ds-minimap__title,
.ds-minimap__desc { margin: 0; overflow: hidden; }
/* 질문은 **한 줄** — 카드 폭을 넘으면 … (2026-11-11 사용자 "무조건 1줄로 · 넘으면 …으로"). 두 줄이면 카드 높이가 차례마다 달라져 훑을 때 출렁입니다.
   질문 전체는 막대 버튼의 aria-label 이 읽어 줍니다 */
.ds-minimap__title {
  white-space: nowrap; text-overflow: ellipsis;
  /* 14 · 요약 13 — 13 · 12 는 카드 안에서 작게 읽혔습니다(2026-11-11 사용자 "한 단계씩 키워줘") */
  font-size: var(--text-base); font-weight: var(--weight-medium);
  line-height: var(--leading-ui); color: var(--foreground);
}
.ds-minimap__desc {
  display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow-wrap: anywhere;
  margin-top: 4px;
  font-size: var(--text-sm); line-height: var(--leading-ui);
  color: var(--muted-foreground);
}

@media (prefers-reduced-motion: reduce) {
  .ds-minimap__pill { transition: none; }
}

/* ── ds-icon ── */
.ds-icon { flex-shrink: 0; vertical-align: -0.125em; }
.ds-icon.is-spin { animation: spin .8s linear infinite; }

/* ── 인라인 Lucide SVG (문서·템플릿용) ──
   stroke 2 — Lucide 기본값. 앱 크롬(사이드바·메뉴·툴바)이 lucide 원본을 그대로 쓰기 때문에
   DS만 1.5로 두면 같은 화면에서 굵기가 어긋납니다 (2026-08-07 확정).
   기준선 보정 -0.125em — 시스템 전체에서 이 값 하나만 씁니다.
   (아이콘+글자를 나란히 둘 때는 가급적 flex + align-items: center를 쓰고,
    인라인 흐름에 섞일 때만 이 보정에 기댑니다.) */
.lic { flex-shrink: 0; vertical-align: -0.125em; fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

/* 버튼 안 아이콘 — 광학 여백 보정.
   아이콘은 사각 캔버스 안에 여백을 갖고 있어, 글자와 같은 패딩이면
   아이콘 쪽이 더 들여 보입니다. 바깥쪽으로 2px 당깁니다. */
.btn > .lic:first-child, .btn > .ds-icon:first-child { margin-inline-start: -2px; }
.btn > .lic:last-child:not(:first-child),
.btn > .ds-icon:last-child:not(:first-child) { margin-inline-end: -2px; }

/* 툴콜 상태 아이콘 — 스피너(12px)와 체크(14~16px)의 폭이 다르면
   상태가 바뀔 때 뒤 텍스트가 밀립니다. 16px 슬롯에 고정합니다. */
.toolcall > .check, .toolcall > .spinner {
  width: 16px; height: 16px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
.toolcall > .check .lic, .toolcall > .check svg { display: block; }

/* ── ds-link ── */
/* 잉크 글자 + 밑줄 — 콘솔 표의 문(.cell-link)과 한 벌 (2026-11-06 제품에 맞춤). 파란 글자는 쓰지 않습니다(2026-09-23 "검정으로 가는 게 좋네") */
.ds-link {
  color: var(--foreground); text-decoration: underline; text-underline-offset: 2px;
  display: inline-flex; align-items: center; gap: 4px;
}
.ds-link:hover { text-decoration-thickness: 2px; }
.ds-link:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: var(--r-in); }

/* ── ds-infoi ── */
/* 라벨 옆 설명 아이콘 ⓘ — 낯선 용어(회귀 게이트 · 최고 위험 …) 옆에 답니다. 글로는 길고 늘 보일
   필요는 없는 설명을 **눌러 보는 자리**로 접습니다. 설명 본문은 data-tip 툴팁 레이어가 띄웁니다.
   2026-09-15 관리자 콘솔 ui.css 의 .infoi 를 그대로 올렸습니다(값 · 색 · 커서 셋뿐) — 아이콘 자체는
   화면이 <svg class="ic sm"> 로 넣고, 이 규칙은 자리와 색만 정합니다.
   ⚠ 정렬(vertical-align)은 놓이는 줄마다 다릅니다 — 표 머리글은 -2 · 폼 라벨은 -3. 그건 화면이 적습니다 */
.infoi { display: inline-flex; margin-left: 4px; color: var(--muted-foreground); cursor: help; }
.infoi:hover { color: var(--foreground); }

/* ── 인라인 svg 아이콘 (.ic) — 공용 부품(조건 칩 ×, 열기 배지, 상세 검색 …)이 손으로 그린 lucide 패스에 답니다.
   굵기 2 · 끝 둥글게(lucide 기본) · 16 / sm 14 / lg 18. (2026-10-06 관리자 콘솔 index.css 에서 올림 —
   두 앱이 함께 쓰는 부품이 콘솔 리셋 파일에 기대고 있어, 이용자 화면에서는 조건 칩의 × 가 안 보였습니다) */
svg.ic { width: 16px; height: 16px; flex: none; fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
svg.ic.sm { width: 14px; height: 14px; }
svg.ic.lg { width: 18px; height: 18px; }

/* ── ds-kbd ── */
/* 키캡 — 높이 20px 고정, 최소 폭 20px이라 한 글자짜리는 정사각입니다.
   글자 폭에 맡기면 키마다 폭이 달라지고 소수 상자(24.67 × 23.15)가 나옵니다. */
/* ⚠ 글꼴은 **--font(Pretendard)** 입니다 (2026-09-17 수정). 앞서는 --mono 였는데,
   Foundation 의 글꼴 표가 --mono 를 쓰는 자리를 **코드 · 툴콜 · 시각 · IP · 파일 크기**로
   적어 두고 있습니다 — 기준은 "세로로 자릿수를 맞춰 읽어야 하는 값"이고, 키캡은 거기 없습니다.
   'Ctrl' · 'Esc' 만 Consolas 계열로 나와 바로 옆 글자와 결이 어긋났습니다(키캡은 키 이름이지 코드가 아닙니다).
   ⛔ --mono 로 되돌리지 마세요.
   ⚠ 폭은 글꼴이 아니라 min-width + padding 이 잡습니다 — 글꼴을 바꿔도 상자 크기는 그대로입니다 */
.kbd {
  display: inline-flex; align-items: center; justify-content: center;
  box-sizing: border-box; height: 20px; min-width: 20px; padding: 0 5px;
  font-family: var(--font); font-size: var(--text-2xs);
  font-weight: var(--weight-medium); line-height: 1;
  background: var(--muted); color: var(--foreground);
  border: 1px solid var(--input); border-radius: var(--r-in);
  white-space: nowrap; vertical-align: middle;
}
/* 방향키·엔터는 글자가 아니라 SVG입니다 — 글꼴에 따라 모양이 달라지지 않습니다 */
.kbd svg { display: block; width: 12px; height: 12px; }
.ds-kbd-group { display: inline-flex; gap: 4px; }

/* ── ds-code ── */
.ds-code {
  font-family: var(--mono); font-size: var(--text-xs);
  color: var(--primary-text); background: var(--muted);
  padding: 2px 6px; border-radius: var(--r-in);
}
.ds-codeblock {
  position: relative; background: var(--gray-2);
  border: 1px solid var(--border); border-radius: var(--r-el);
  overflow: hidden;
}
.ds-codeblock pre { padding: 14px 16px; overflow-x: auto; margin: 0; }
.ds-codeblock code {
  font-family: var(--mono); font-size: var(--text-xs);
  color: var(--muted-foreground); white-space: pre; line-height: 1.7;
  font-variant-numeric: tabular-nums;
}
.ds-code-copy {
  all: unset; box-sizing: border-box; cursor: pointer;
  position: absolute; top: 8px; right: 8px;
  padding: 3px 9px; border-radius: var(--r-el);
  border: 1px solid var(--input); background: var(--card);
  font-family: var(--font); font-size: var(--text-2xs);
  font-weight: var(--weight-medium); color: var(--muted-foreground);
  transition: color .16s ease, border-color .16s ease;
}
.ds-code-copy:hover { color: var(--foreground); border-color: var(--border-emphasized); }

/* ── ds-time ── */
.ds-time { font-family: var(--mono); font-size: var(--text-xs); color: var(--muted-foreground); }

/* ── metalist ── */
.metalist { margin: 0; }
.metalist-row { display: flex; gap: 14px; padding: 8px 0; align-items: baseline; }
.metalist dt {
  width: 88px; flex-shrink: 0;
  font-size: var(--text-xs); color: var(--muted-foreground);
}
.metalist dd { margin: 0; font-size: var(--text-sm); color: var(--foreground); min-width: 0; }
.metalist dd.mono { font-family: var(--mono); font-size: var(--text-xs); color: var(--muted-foreground); }

/* ── searchfield ── */
.searchfield {
  display: inline-flex; align-items: center; gap: 7px;
  height: 32px; padding: 0 8px 0 9px; min-width: 240px;   /* 왼 9 · 그림 16 · 틈 7 → 글자 32 — 필터 줄 검색칸(.searchwrap: 그림 9 · 글자 32)과 같은 자리 */
  border: 1px solid var(--border); border-radius: var(--r-el);   /* --border — 필터 줄 검색칸 .searchwrap .input 과 한 벌(2026-11-06, 앞 판 --input) */
  background: var(--card);
  transition: border-color .16s ease, box-shadow .16s ease;
}
.searchfield:hover { border-color: var(--border-emphasized); }
.searchfield.sm { height: 32px; padding: 0 8px 0 10px; min-width: 200px; gap: 6px; }
.searchfield.md { height: 36px; padding: 0 9px 0 11px; }
.searchfield.lg { height: 40px; padding: 0 10px 0 12px; }
.searchfield.sm input { font-size: var(--text-sm); }
.searchfield.focused { border-color: var(--primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 22%, transparent); }   /* .input 포커스와 한 벌 */
.searchfield .sf-icon { display: inline-flex; color: var(--muted-foreground); flex-shrink: 0; }
.searchfield input {
  all: unset; flex: 1; min-width: 0;
  font-family: var(--font); font-size: var(--text-base); color: var(--foreground);
}
.searchfield input::placeholder { color: var(--muted-foreground); }
.searchfield input::-webkit-search-cancel-button { display: none; }
.sf-kbd { flex-shrink: 0; }
.sf-clear {
  all: unset; box-sizing: border-box; cursor: pointer;
  width: 22px; height: 22px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-in); color: var(--muted-foreground);
}
.sf-clear:hover { background: var(--muted); color: var(--foreground); }
/* 범위 · 제출 (2026-11-06 — 콘솔 범위 검색칸 .dssearch 와 같은 짜임: [ 범위 ▾ | 검색어…  🔍 ]).
   범위는 맨 글자 13px + 셰브런, 세로선 1px --border, 돋보기 버튼은 지우기 버튼과 같은 24 칸 */
.sf-scope {
  flex: none; max-width: 40%; height: 100%; padding: 0 18px 0 0; border: 0; background: none;
  font: inherit; font-size: var(--text-sm); color: var(--foreground); cursor: pointer;
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--muted-foreground) 50%), linear-gradient(135deg, var(--muted-foreground) 50%, transparent 50%);
  background-position: right 7px center, right 3px center; background-size: 4px 4px; background-repeat: no-repeat;
}
.sf-scope:focus-visible { outline: none; color: var(--primary); }
.sf-sep { flex: none; align-self: stretch; width: 1px; margin: 7px 2px; background: var(--border); }
.sf-go {
  flex: none; display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px;
  border: 0; border-radius: var(--r-el); background: none; color: var(--muted-foreground); cursor: pointer;
}
.sf-go:hover:not(:disabled) { background: var(--overlay-hover); color: var(--foreground); }
.sf-go:disabled { opacity: .45; cursor: default; }

/* ── sysmsg ── */
.sysmsg {
  display: flex; align-items: baseline; justify-content: center; gap: 8px;
  padding: 6px 24px; font-size: var(--text-xs); color: var(--muted-foreground);
}
.sysmsg-time { font-family: var(--mono); font-size: var(--text-2xs); color: var(--muted-foreground); }

/* ── ds-toolbar ── */
.ds-toolbar {
  display: flex; align-items: center; gap: 8px; min-height: 40px;
  padding: 0 4px; flex-wrap: wrap;
}
.ds-toolbar.dense { min-height: 32px; }
.ds-toolbar .sep { width: 1px; align-self: stretch; margin: 10px 4px; background: var(--border); }
.ds-toolbar .spacer { flex: 1; }

/* ══ 관리자 콘솔 패턴 — CSS 전용 쌍둥이 (2026-09-15 역반영) ═══════════════════════════
   콘솔은 Vuetify 없이 CSS 클래스로 부품을 씁니다. 아래는 콘솔 ui.css 가 갖고 있던 것 중 DS 에
   없던 패턴을 그대로 올린 것입니다 — 값은 콘솔이 정답(2026-08-28 규칙). 콘솔 · 이용자 두 앱이
   이 파일을 싣고, ui.css · 필터.css 에 있던 사본은 걷었습니다.
   · .iconbtn   아이콘만 있는 30px 버튼 — DsIconButton 의 CSS 쌍둥이
   · .switch    32×18 켬/끔 — DsSwitch --sm 과 같은 치수. <button role="switch" aria-checked>
   · 필터 줄 · 패널 — .selbtn · .searchwrap · .linkbtn · .fbtn · .fbadge · .resultmeta · .filpanel …
     (앞서 이용자 화면 필터.css 와 콘솔 ui.css 두 벌이던 것. 이용자 검색칸 .hfield 는 .searchwrap .input) */
.iconbtn {
  width: 30px; height: 30px; display: inline-flex; align-items: center; justify-content: center;
  border: 0; background: none; border-radius: var(--r-el); color: var(--muted-foreground); cursor: pointer; position: relative;
}
.iconbtn:hover { background: var(--overlay-hover); color: var(--foreground); }
.iconbtn:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
.iconbtn.is-on { background: var(--selected-fill); color: var(--foreground); }
/* 눌리지 않는 아이콘 버튼은 눌리지 않아 보여야 합니다 — 호버 면도 끕니다 */
.iconbtn[disabled] { opacity: .4; cursor: default; }
.iconbtn[disabled]:hover { background: none; color: var(--muted-foreground); }

.switch { position: relative; width: 32px; height: 18px; flex: none; border: 0; border-radius: var(--r-full); background: var(--border-emphasized); cursor: pointer; transition: background .15s ease; }
.switch::after { content: ''; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background: var(--card); transition: left .15s ease; }
.switch[aria-checked="true"] { background: var(--primary); }
.switch[aria-checked="true"]::after { left: 16px; }
.switch:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 22%, transparent); }
.switch[disabled] { opacity: .4; cursor: default; }   /* 켠 채로 잠긴 스위치 */
/* 손잡이 색 (2026-10-30 — 콘솔 자산 현황에서 "다크모드일 때 너무 잘 안 보인다").
   앞 판은 손잡이가 늘 --card 였습니다. 다크에서 --card 는 바탕과 같은 색이라 트랙(--border-emphasized) 안에서 묻혀
   꺼진 스위치가 빈 홈처럼 보였고, 켜진 스위치도 파란 트랙 위 어두운 점이었습니다.
   · 켜짐 = 늘 --primary-foreground(흰색) — 라이트는 전과 같은 흰색입니다
   · 다크의 꺼짐 = --foreground(밝은 회색) — shadcn Switch 다크와 같은 손(unchecked thumb = foreground) */
.switch[aria-checked="true"]::after { background: var(--primary-foreground); }
.dark .switch:not([aria-checked="true"])::after, [data-theme="dark"] .switch:not([aria-checked="true"])::after { background: var(--foreground); }

/* ── 필터 줄의 컨트롤 ─────────────────────────────────────────────────── */

/* 드롭다운·필터 트리거. 값이 걸리면 브랜드 스트로크·글자(--nav-active), 면은 그대로 */
.selbtn {
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-width: 132px;
  padding-inline: 10px;
  border: 1px solid var(--border);
  border-radius: var(--r-el);
  background: var(--card);
  font: inherit;
  font-size: 13px;
  color: var(--foreground);
  cursor: pointer;
}
.selbtn:hover { border-color: var(--border-emphasized); }
.selbtn:focus-visible {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 22%, transparent);
}
.selbtn svg { color: var(--muted-foreground); flex: none; }
.selbtn .val { color: var(--muted-foreground); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.selbtn.is-on { border-color: var(--nav-active); color: var(--nav-active-fg); }
.selbtn.is-on .val { color: var(--nav-active-fg); font-weight: 600; }
.selbtn.is-on svg { color: var(--nav-active-fg); }
/* 열린 필터 = 브랜드 스트로크 — 잉크색은 활성/포커스에서 오류처럼 보입니다 */
.selbtn.is-open { border-color: var(--nav-active); color: var(--nav-active-fg); }
.selbtn.is-open svg { color: var(--nav-active-fg); }

/* [필터] 버튼 — 줄의 첫 칸. 값이 걸린 **항목 수**를 배지로 셉니다(값 수가 아닙니다) */
.fbtn { min-width: 0; }
.fbtn__in { display: inline-flex; align-items: center; gap: 6px; }
.fbadge {
  font-style: normal;
  font-size: 11px;
  font-weight: 600;
  line-height: 16px;
  min-width: 16px;
  text-align: center;
  color: var(--primary-foreground);
  background: var(--primary);
  border-radius: 4px;
  padding: 0 4px;
}

/* 필터 줄 — [필터] · 검색칸 · 초기화 … 오른쪽 끝 [표 열 설정]. 관리자 콘솔 목록 화면의 줄이고 이용자 화면 목록(이력 · 모든 프로젝트)도 같은 줄입니다
   (2026-10-06 콘솔 ui.css 에서 올림 — 두 앱 한 벌).
   ⚠ position: relative 는 **필터 패널(.filpanel)의 기준 상자** 몫입니다 — 버튼을 감싼 작은 상자 안에 두면 패널이 그 폭으로 찌그러집니다.
   ⛔ 건수를 이 줄 오른쪽에 두지 마세요 — 건수는 아래 .resultmeta 한 줄입니다 */
.filters { position: relative; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 14px 20px 0; }

/* 검색칸 — 왼쪽 돋보기. ⚠ 폭은 **감싸개에** 줍니다(260) — 칸에 주면 감싸개와 어긋난 만큼 빈 자리가 생깁니다(콘솔 2026-09 실측) */
.searchwrap { position: relative; display: inline-flex; align-items: center; width: 260px; }
.searchwrap > svg {
  position: absolute;
  left: 9px;
  color: var(--muted-foreground);
  pointer-events: none;
}
.searchwrap .input {
  height: 32px;
  width: 100%;
  padding: 0 10px 0 32px;
  border: 1px solid var(--border);
  border-radius: var(--r-el);
  background: var(--card);
  font: inherit;
  font-size: 14px;
  color: var(--foreground);
}
.searchwrap .input:hover { border-color: var(--border-emphasized); }
.searchwrap .input:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 22%, transparent);
}

/* 초기화 등 글자 버튼 */
.linkbtn {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-size: 12px;
  color: var(--muted-foreground);
  cursor: pointer;
  text-decoration: underline;
}
.linkbtn:hover { color: var(--foreground); }
/* 잠김 — 흐리게 · 손가락 없음 · hover 색 안 바뀜 (2026-11-06 — 앞서 disabled 여도 모양이 같아 콘솔이 잠그는 대신 감췄음) */
.linkbtn:disabled { opacity: .45; cursor: default; }
.linkbtn:disabled:hover { color: var(--muted-foreground); }

/* 건수 — 필터 줄 **아래** 한 줄 */
.resultmeta {
  padding: 12px 20px 0;
  font-size: 13px;
  color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}
.resultmeta b { color: var(--foreground); font-weight: 600; }

/* 선택 막대 — 고른 줄이 있으면 건수 줄(.resultmeta) 자리를 **같은 상자로** 대신합니다(목록이 밀리지 않게).
   글자 단추입니다 — 할 일(.bulk-act)은 활성 글자색 600, 해제 · 취소(.bulk-clr)는 회색. 버튼 상자를 세우지 않습니다
   (2026-10-06 관리자 콘솔 알림 이력의 .ntf-bulk · 문서 탐색 벌크 바 규약을 DS 로 올림 — 이용자 화면 이력이 씀) */
.bulkbar { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; padding: 12px 20px 0; font-size: 13px; line-height: normal; }
.bulkbar b { font-weight: 600; color: var(--nav-active-fg); font-variant-numeric: tabular-nums; }
.bulkbar button { padding: 0; border: 0; background: none; font: inherit; cursor: pointer; }
.bulkbar .bulk-act { color: var(--nav-active-fg); font-weight: 600; }
.bulkbar .bulk-clr { color: var(--muted-foreground); }
.bulkbar .bulk-act:hover, .bulkbar .bulk-clr:hover { text-decoration: underline; text-underline-offset: 2px; }
.bulkbar button:disabled { opacity: .45; cursor: default; text-decoration: none; }

/* 걸린 조건 줄 — **필터 줄과 건수 줄 사이의 제 줄**(2026-10-06 콘솔 ui.css 에서 올림). 필터 패널은 이 줄 아래로 열립니다.
   칩은 .chip.brand 에서 **면만** 벗긴 것 — 활성 표시 한 벌(--nav-active 스트로크 · --nav-active-fg 글자) · 네모 · 28px · 13px.
   × 는 그 값 하나만 뺍니다. 걸린 게 없으면 줄째로 감춥니다. ⛔ 면을 채우지 마세요 — 조건이 여럿이면 알약 덩어리가 됩니다 */
.chipbar { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; padding: 12px 20px 0; }
.chipbar .chip.brand { height: 28px; padding: 0 7px 0 10px; gap: 6px; border-radius: var(--r-el); font-size: 13px;
  background: transparent; border-color: var(--nav-active); color: var(--nav-active-fg); }
.chipbar .chip.brand .x { width: 18px; height: 18px; border-radius: var(--r-el); }
/* Vue 화면은 조건 줄을 .filters 안 다음 줄에 둡니다(ChipBar inside) — 절대 배치인 필터 패널의 top:100% 가 저절로 그 아래가 됩니다 */
.filters > .chipbar--in { flex: 0 0 100%; padding: 0; }

/* ── 필터 패널 — 왼쪽 항목 · 오른쪽 값 ────────────────────────────────── */
.filpanel {
  /* 부품 지역 변수 — 화면이 항목 수(--fp-axes)를 넣고, --fp-h 는 옛 호출용 비상구(기본은 없음 = 아래 셈) */
  --fp-axes: 6; --fp-h: initial;
  position: absolute;
  top: calc(100% + 6px);
  left: 20px;
  z-index: 45;
  display: flex;
  width: 560px;
  max-width: calc(100% - 40px);
  border: 1px solid var(--border);
  border-radius: var(--r-ct);
  background: var(--card);
  overflow: hidden;
  box-shadow: var(--shadow-xl);
  /* 높이 = **필터 항목 수**로 정합니다 — 콘솔 공식(항목 33px + 머리말·모두 지우기 84).
     내용 높이로 두면 항목마다 패널이 갑자기 자라고, 큰 값을 못박으면 항목이 적은 화면은
     아래가 텅 빕니다. 부품(FilterPanel.vue)이 --fp-h 로 넣어 줍니다 */
  /* ══ 세로 규칙 — **한 곳에서 정합니다** (콘솔 ui.css 와 **같은 규칙**입니다. 2026-09-11 사용자 지시: "어떤건 너무 짧고
     어떤건 너무 길게 나와서") ════════════════════════════════════════════════
     화면은 **항목 수(--fp-axes)만** 넣고, 높이는 이 한 줄이 냅니다.

       height = min( max(320, 항목수 × 33 + 84), 480, 100vh − 200 )

     · 항목 33 — .fp-item 한 줄 높이(32) + gap 1
     · 머리말 · 모두 지우기 84
     · **최소 320** — 가장 키 큰 축이 잘리지 않는 값입니다. 일시의 **사용자 지정**이
       프리셋 + 기간 입력칸 + 힌트로 **282px**(2026-09-11 메일 이상탐지 실측)이고,
       거기에 여유를 둔 값입니다. 항목이 넷뿐인 화면(이력 · 접속 · 권한변경)이 216px로
       납작하던 것이 이 값으로 올라옵니다.
     · **최대 480** — 항목 12개(메일 이상탐지)가 **왼쪽 목록 스크롤 없이** 들어가는 값입니다
       (12 × 33 = 396 ≤ 왼쪽 칸 398). 앞서 420이던 화면은 12축에서 왼쪽이 잘려 굴렀습니다.
       ⚠ 드롭다운 한 벌의 통상 상한은 더 낮습니다(GitLab Pajamas 기본 312 · 최대 400) —
         우리 것은 **2단 필터 패널**이라 한 단계 위로 잡았습니다. 항목이 더 많은 화면
         (파일 로그 17축)은 여기서 왼쪽이 굴러야 맞습니다.
     · **100vh − 200** — 낮은 창에서 패널이 화면 밖으로 나가지 않게.

     ⛔ 화면에서 --fp-h 로 높이를 직접 넣지 마세요 — 그렇게 여섯 화면이 420 · 560 ·
       160 · 200 · 220 으로 갈라져 있었습니다. --fp-h 는 **옛 호출을 위한 비상구**로만
       남겨 둡니다. 새 화면은 --fp-axes 하나만 넣습니다. */
  height: var(--fp-h, min(max(320px, calc(var(--fp-axes, 6) * 33px + 84px)), 480px, calc(100vh - 200px)));
}
.filpanel .fp-side {
  flex: 0 0 184px;
  display: flex;
  flex-direction: column;
  padding: 8px;
  border-right: 1px solid var(--border);
}
.filpanel .fp-side-hd {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 4px 8px 8px;
  font-size: 12px;
  color: var(--muted-foreground);
}
.filpanel .fp-side-hd span { margin-left: auto; color: var(--primary); font-weight: 600; }
.filpanel .fp-used { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 1px; }
.filpanel .fp-item {
  flex: none;
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  height: 32px;
  padding: 0 6px 0 8px;
  border: 0;
  border-radius: var(--r-el);
  background: none;
  font: inherit;
  font-size: 13px;
  color: var(--foreground);
  cursor: pointer;
  text-align: left;
}
.filpanel .fp-item:hover { background: var(--overlay-hover); }
.filpanel .fp-item span:first-child { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.filpanel .fp-item em {
  font-style: normal;
  font-size: 11px;
  font-weight: 600;
  color: var(--primary-foreground);
  background: var(--primary);
  border-radius: 4px;
  padding: 1px 5px;
}
/* 지금 보고 있는 항목 — 테두리 없이 옅은 면과 글자색으로만 (2026-08-18 결정) */
.filpanel .fp-item.on { background: color-mix(in srgb, var(--primary) 7%, transparent); color: var(--primary); }
.filpanel .fp-clear { text-align: left; padding: 8px 8px 2px; }

.filpanel .fp-main {
  flex: 1;
  min-width: 0;
  padding: 10px 12px 12px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}
.filpanel .fp-main-hd { display: flex; align-items: baseline; gap: 10px; padding: 2px 6px 8px; font-size: 14px; }
.filpanel .fp-main-hd .linkbtn { margin-left: auto; }
.filpanel .fp-list { flex: 1; min-height: 0; overflow-y: auto; }
.filpanel .fp-list label {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 30px;
  padding: 0 8px;
  border-radius: var(--r-el);
  font-size: 13px;
  cursor: pointer;
}
.filpanel .fp-list label:hover { background: var(--overlay-hover); }
.filpanel .fp-list label span { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.filpanel .fp-list label em { font-style: normal; font-size: 12px; color: var(--muted-foreground); }

/* 값이 많은 축의 목록 검색칸 — 콘솔은 값 8개 초과부터 답니다 */
.filpanel .fp-vals { padding: 0 6px 6px; }
.filpanel .fp-q {
  width: 100%; height: 30px; padding-inline: 10px; font: inherit; font-size: 13px;
  border: 1px solid var(--border); border-radius: var(--r-el); background: var(--card); color: var(--foreground);
}
.filpanel .fp-q:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 22%, transparent); }

/* 기간처럼 값이 하나만 걸리는 축 — 값 목록과 같은 모양의 한 줄짜리 프리셋 */
/* ⚠ **기간 칸은 스스로 구릅니다** (2026-09-16 실측) — 패널 높이는 항목 수로 정해지는데
   (기본 320) 안에 달력을 펴면 **170px 이 잘려** 마지막 주를 고를 수가 없었습니다.
   패널이 overflow:hidden 이라 flex:none + visible 로는 넘친 만큼 그냥 사라집니다.
   ⛔ 패널 높이 공식을 건드리지 마세요 — 항목이 적은 화면이 텅 비게 됩니다(그 공식 머리말) */
.filpanel .fp-dates { flex: 1; min-height: 0; overflow-y: auto; }
.filpanel .fp-date {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  height: 32px;
  padding: 0 8px;
  border: 0;
  border-radius: var(--r-el);
  background: none;
  font: inherit;
  font-size: 13px;
  color: var(--foreground);
  cursor: pointer;
  text-align: left;
}
.filpanel .fp-date:hover { background: var(--overlay-hover); }
.filpanel .fp-date.on { font-weight: 600; color: var(--primary); }
/* 축이 직접 그리는 자리(달력 · 두 칸 입력 등) */
.filpanel .fp-custom { flex: 1; min-height: 0; overflow-y: auto; padding: 0 6px; }

/* 보조 한 줄이 붙는 값(의원의 정당처럼) — 줄 높이를 늘리고 아래에 옅게 */
.filpanel .fp-list label.has-sub { height: 42px; align-items: center; }
.filpanel .fp-list label.has-sub span { display: flex; flex-direction: column; line-height: 1.25; }
.filpanel .fp-list label i { font-style: normal; font-size: 11px; color: var(--muted-foreground); }
/* 아래는 콘솔 ui.css 에만 있던 필터 줄 · 패널 규칙 */
.selbtn:disabled { cursor: default; opacity: .7; }
.selbtn:disabled:hover { border-color: var(--border); }
.selbtn .x { display: inline-flex; margin: 0 -4px 0 0; color: var(--muted-foreground); }
.selbtn .x:hover { color: var(--foreground); }
.filpanel .fp-main-hd b { font-weight: 600; }
.filpanel .fp-hint { font-size: 12px; color: var(--muted-foreground); padding: 0 6px 8px; }
.filpanel .fp-empty { font-size: 13px; color: var(--muted-foreground); padding: 28px 8px; }
.filpanel .fp-cal { padding: 6px 2px 2px; }
.fp-dateio { display: flex; align-items: center; gap: 8px; padding: 6px 6px 2px; }
.fp-dateio .input { flex: 1; min-width: 0; height: 30px; font-size: 13px; cursor: pointer; font-variant-numeric: tabular-nums; }
.fp-dateio .input.on { border-color: var(--primary); }
.filpanel-calpop { position: absolute; z-index: 46; width: 288px; border: 1px solid var(--border); border-radius: var(--r-ct); background: var(--card); box-shadow: var(--shadow-xl); padding: 4px 8px 8px; }
.filpanel-calpop .cal-io { display: none; }


/* ── sr-only ── */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ── 포커스 ──────────────────────────
   all: unset은 브라우저 기본 outline까지 지웁니다. 지운 자리에 우리 링을
   다시 놓지 않으면 키보드로 이동할 때 지금 어디에 있는지 보이지 않습니다.
   마우스로 눌렀을 때는 안 나오고(:focus-visible), 키보드일 때만 나옵니다. */
.theme-toggle:focus-visible,
.chip .x:focus-visible,
.toast .t-action:focus-visible,
.ai-tool:focus-visible,
.ds-code-copy:focus-visible,
.sf-clear:focus-visible,
.ds-palette-item:focus-visible,
.file-row:focus-visible,
.file-card:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--r-in);
}

/* ══════════════════════════════════════════════════════════════
   콘솔에서 받아 온 부품 (2026-10-27) — 관리자 콘솔 공용/스타일/ui.css 의 값을 **그대로** 옮겼습니다.
   열 화면 넘게 쓰던 것들인데 DS 에 없어서, AI 가 DS 만 보고는 만들 수 없었습니다.
   ⚠ 원본은 여기입니다 — 콘솔 ui.css · shell.css 에 있던 같은 규칙은 2026-10-28 에 걷었습니다(콘솔 45화면 · 이용자 화면 7화면의 계산값을 전후로 대조해 차이 0건 — 그때 .jt 첫 · 끝 칸 하나를 잡아 아래에 적음).
     값을 바꾸려면 여기서 고치고 → npm run live → 앱에서 npm run ds:sync. 콘솔 쪽에 다시 적지 마세요 — 두 벌이면 한쪽만 고쳐집니다
   ══════════════════════════════════════════════════════════════ */

/* ── page-header — 화면 제목줄 (DsPageHeader · 콘솔 .ptitle, 35화면) ──
   제목 왼쪽 · 액션 오른쪽 한 줄. 아래 여백은 두지 않습니다 — 다음 블록의 위 16 이 맡습니다 */
.ptitle { display: flex; align-items: center; gap: 10px; padding: 18px 20px 0; }
/* line-height: normal — 콘솔은 body 가 normal 이라 24px. body 가 1.5(Vuetify) · 1.62(DS)인 앱에서는 30 · 32px 로 커집니다(이용자 화면 이력 · 비식별화가 따로 적어 두던 값) */
.ptitle h1 { flex: 1; margin: 0; font-size: var(--text-xl); font-weight: var(--weight-semibold); letter-spacing: -.02em; line-height: normal; }

/* ── status — 점 + 상태 글자 (DsStatus · 콘솔 .state, 22화면) ──
   표 칸의 '지금 상태'. 면이 없어 줄이 많아도 시끄럽지 않습니다 — 결과(완료 · 실패)는 배지 */
.state { display: inline-flex; align-items: center; gap: 7px; font-size: var(--text-base); color: var(--foreground); }
.state::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: var(--muted-foreground); }
.state.ok::before { background: var(--success); }
.state.off::before { background: var(--border-emphasized); }
.state.wait::before { background: var(--warning); }
.state.err::before { background: var(--error); }
.state.off { color: var(--muted-foreground); }
.state.err { color: var(--error-text); }

/* ── stat-card — 요약 숫자 카드 줄 (DsStatCard · 콘솔 .summary .card, 12화면) ──
   넉 장 한 줄 · 1100 이하 두 장. 넉 장보다 적으면 칸 폭은 넉 장 줄과 같게 왼쪽부터 —
   두세 장이 1/n 로 늘어나면 숫자는 구석에 있고 나머지는 빈 면이 됩니다 */
.summary { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; padding: 16px 20px 0; }
.summary .card {
  border: 1px solid var(--border); border-radius: var(--r-ct); padding: 14px 16px;
  display: flex; flex-direction: column; gap: 6px; background: var(--card);
}
.summary .l { font-size: var(--text-sm); color: var(--muted-foreground); }
.summary .v { font-size: var(--text-2xl); font-weight: var(--weight-semibold); letter-spacing: -.02em; line-height: 1.1;
  font-variant-numeric: tabular-nums; }
.summary .card .s { font-size: var(--text-xs); color: var(--muted-foreground); }
/* 단위는 숫자에 붙되 한 단 작게 — '23.7초'의 '초'가 숫자만큼 크면 값이 안 읽힙니다 (콘솔 .rep-stat .v em 과 같은 값) */
.summary .v em { font-style: normal; font-size: var(--text-md); font-weight: var(--weight-medium); margin-left: 2px; }
/* 값 옆 분모('845 / 26,482') — 한 단 작게 · 회색 */
.summary .card .v .card-of { font-size: var(--text-lg); font-weight: var(--weight-medium); color: var(--muted-foreground); letter-spacing: 0; }
/* 증감(.delta--good · --bad · --flat · .delta-l)은 위 .card .delta 한 벌이 맡습니다 */
@media (max-width: 1100px) { .summary { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1101px) { .summary:not(:has(> :nth-child(4))) { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* ── detail-table — 상세 화면의 표 (DsDetailTable · 콘솔 .jtw > .jt, 12화면) ──
   라운드 래퍼 + 면 없는 머리 행. 목록 표(DataTable: 줄 52 · 머리 40)보다 한 단 작습니다(줄 46 · 머리 38)
   ⛔ 머리 행에 회색 면을 깔지 않습니다(2026-08-21 확정) · 머리 글자는 13px — 12px 금지(2026-09-01 사용자 지시, 콘솔 전 표 일괄)
   ⚠ th.num--right 는 따로 적습니다 — 위 text-align:left 가 공용 th.num--right 보다 특이도가 높아 머리만 왼쪽에 남았습니다(2026-10-05 콘솔 실측) */
.jtw { border: 1px solid var(--border); border-radius: var(--r-ct); overflow: hidden; }
.jt { width: 100%; border-collapse: collapse; }
.jt thead th { position: static; box-shadow: none; height: 38px; padding: 0 16px; font-size: var(--text-sm); font-weight: var(--weight-medium);
  color: var(--muted-foreground); background: none; border-bottom: 1px solid var(--border); text-align: left; }
.jt thead th.num--right { text-align: right; }
.jt tbody td { height: 46px; padding: 0 16px; font-size: var(--text-base); border-bottom: 1px solid var(--border); }
.jt tbody td b { font-weight: var(--weight-semibold); }
.jt tbody tr:last-child td { border-bottom: 0; }
/* 첫 · 끝 칸도 16 — 목록 표 전역(콘솔 ui.css 의 tbody td:first-child 20px · 여기 .table-wrap 첫 칸 20px)이 특이도가 같거나 높아
   파일 순서로만 이기던 것을 적어 둡니다(2026-10-28 콘솔 사본을 걷을 때 실측: 첫 칸 16 → 20 으로 벌어짐) */
.jt thead th:first-child, .jt tbody td:first-child { padding-left: 16px; }
.jt thead th:last-child, .jt tbody td:last-child { padding-right: 16px; }
.jt td.num--right { text-align: right; }
/* 값이 링크면 옆 줄 값과 같은 크기 — .linkbtn 의 12px 은 곁말 링크 몫입니다(2026-10-28 콘솔 에이전트 로그 상세에서 찾음 · 11-06 DS 로) */
.jt td > .linkbtn { font-size: inherit; }   /* 콘솔은 td.num--right 가 정렬만 — 숫자 폭 고정은 td.num 몫 */

/* ══════════════════════════════════════════════════════════════
   Geist 에서 들인 부품 (2026-10-28) — Split Button · Gauge.
   콘솔에 아직 없던 것이라 값은 콘솔 부품 문법(.btn · .pop)에 맞췄습니다. Vuetify 없이 그려 두 앱에서 같습니다
   ══════════════════════════════════════════════════════════════ */

/* ── split-button — 기본 동작 + 그 곁 변형 (DsSplitButton) ──
   왼쪽 = 늘 하는 일, 오른쪽 ▾ = 같은 일의 다른 방법 1~4개. 두 칸은 붙어 한 덩어리입니다.
   목록 모양은 콘솔 .pop 과 같은 값(모서리 8 · 1px 테두리 + shadow-lg · 최소 폭 150 · 줄 7/10 · hover --overlay-hover) */
.splitbtn { position: relative; display: inline-flex; vertical-align: middle; }
.splitbtn > .btn:first-child { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.splitbtn > .splitbtn__more {
  width: 32px; padding: 0; margin-left: -1px; justify-content: center;
  border-top-left-radius: 0; border-bottom-left-radius: 0;
}
.splitbtn > .btn-sm.splitbtn__more { width: 28px; }
.splitbtn > .btn:focus-visible { position: relative; z-index: 1; }

/* 채운 버튼 둘이 맞붙으면 한 버튼으로 보입니다 — 사이에 흰 글자색 35% 선 한 줄 */
.splitbtn > .btn-primary.splitbtn__more { border-left-color: color-mix(in srgb, var(--primary-foreground) 35%, var(--primary)); }
.splitbtn__menu {
  position: absolute; z-index: 40; top: calc(100% + 4px); left: 0; min-width: max(100%, 150px);
  background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 4px;
  box-shadow: var(--shadow-lg);
}
.splitbtn__menu--end { left: auto; right: 0; }
.splitbtn__menu[hidden] { display: none; }
.splitbtn__item {
  display: flex; align-items: center; width: 100%; padding: 7px 10px; border: 0; border-radius: var(--r-el);
  background: none; color: var(--foreground); font-family: var(--font); font-size: var(--text-sm);
  line-height: normal;   /* 콘솔 .pop 줄 29px 그대로 — 콘솔 body 는 normal, DS body 는 1.62 라 적어 둡니다 */
  text-align: left; white-space: nowrap; cursor: pointer;
}
.splitbtn__item:hover { background: var(--overlay-hover); }
.splitbtn__item:focus-visible { background: var(--overlay-hover); outline: 2px solid var(--primary); outline-offset: -2px; }
.splitbtn__item[disabled] { opacity: .4; cursor: default; }
.splitbtn__item[disabled]:hover { background: none; }

/* ── gauge — 정해진 최대 대비 비율 하나 (DsGauge) ──
   할당량 · 사용률처럼 '얼마나 찼나'가 요점일 때. 진행 중인 작업은 ProgressBar 입니다.
   고리 = 12시에서 시계 방향. 바탕 고리는 --border — 남은 몫도 읽혀야 비율입니다(그 색 12% 는 다크에서 안 보였음)
   ⚠ 색은 부품이 정하지 않습니다 — 화면이 가진 기준 하나로 쓰는 쪽이 고릅니다(.success · .warning · .danger).
     부품 안에 임계(80% 넘으면 주황 …)를 두면 같은 숫자가 화면마다 다른 색이 됩니다(콘솔 2026-09-15 riskTone 을 걷은 까닭) */
.gauge {
  position: relative; display: inline-grid; place-items: center; flex: none;
  width: 48px; height: 48px; color: var(--primary);
  --gauge-value: 0;   /* 0 ~ 100 — 쓰는 쪽이 style 로 줍니다 */
}
.gauge svg { position: absolute; inset: 0; width: 100%; height: 100%; transform: rotate(-90deg); }
.gauge circle { fill: none; stroke-width: 3; }
.gauge__track { stroke: var(--border); }
.gauge__arc { stroke: currentColor; stroke-dasharray: var(--gauge-value) 100; transition: stroke-dasharray .3s ease; }
.gauge__value {
  position: relative; font-size: var(--text-xs); font-weight: var(--weight-semibold);
  color: var(--foreground); font-variant-numeric: tabular-nums;
}
.gauge--sm { width: 24px; height: 24px; }
.gauge--sm circle { stroke-width: 4.5; }
.gauge--lg { width: 80px; height: 80px; }
.gauge--lg circle { stroke-width: 2.7; }
.gauge--lg .gauge__value { font-size: var(--text-lg); }
.gauge.success { color: var(--success); }
.gauge.warning { color: var(--warning); }
.gauge.danger { color: var(--error); }

/* ── 움직임 줄이기 ──────────────────
   전정기관 장애가 있으면 반복 애니메이션이 어지럼을 유발합니다.
   운영체제에서 "동작 줄이기"를 켠 사용자에게는 멈춰서 보여줍니다.
   (spinner·커서 깜빡임·스켈레톤 등 무한 반복이 5종 있습니다.) */
/* stylelint-disable declaration-no-important */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}
/* stylelint-enable declaration-no-important */

/* ── 검색어 강조 — <mark class="hl"> (2026-11-06 이용자 화면 이력 · 프로젝트 홈의 mark.cog-hl 에서 올림) ──
   값은 그때 화면에 실제로 그려지던 것 그대로: 경고색 28% 면 · 잉크 글자 · 모서리 --r-in · 좌우 1px.
   (이력의 중립 회색 규칙은 특이도에서 져 한 번도 안 보였습니다 — 화면은 늘 이 값이었음) */
mark.hl { background: color-mix(in srgb, var(--warning) 28%, transparent); color: var(--foreground); border-radius: var(--r-in); padding-inline: 1px; }

/* ── 추천 질문 칩 — .suggest-chip (2026-11-06 이용자 화면 채팅 · 국회 답변서 · 콘솔 AI 질의가 쓰던 .cog-suggest-chip 세 벌을 하나로) ──
   눌러 바로 묻는 문장 하나. DsChip(고른 값 · 지우기)과 다른 물건 — 누르면 그 문장이 입력/전송됩니다.
   제 글자 폭(최대 100%) · 왼쪽 정렬 · 모서리 --r-ct. 첫 화면처럼 크게 쓰는 자리는 쓰는 쪽이 15px · 9/15 로 키웁니다 */
.suggest-chip {
  display: inline-flex; align-items: center; max-width: 100%; text-align: left;
  padding: 8px 14px; border: 1px solid var(--border); border-radius: var(--r-ct);
  background: var(--card); cursor: pointer; font-size: var(--text-base); color: var(--foreground); font-family: var(--font);
  transition: background-color .14s ease, border-color .14s ease;
}
.suggest-chip:hover { background: var(--overlay-hover); border-color: var(--border-emphasized); }
.suggest-chip:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* ════════════════════════════════════════════════════════════
   앱에서 올린 부품 (2026-11-06 — docs/ds-backlog.md 를 비움). 값은 그때 앱 화면에 그려지던 것 그대로이고,
   앱 쪽 사본은 걷었습니다 — 고칠 때는 여기서 고치고 앱은 npm run ds:sync
   ════════════════════════════════════════════════════════════ */

/* ━━ 카드 보기 · 카드 | 표 고르개 — 목록의 두 번째 보기 (콘솔 ui.css 에서, 부품: 콘솔 공용/부품/ViewToggle.vue · 공용/view-mode.ts) ━━ */
/* ── 카드 보기 — 목록의 두 번째 보기 (2026-10-29 · 공용/view-mode.ts · 부품/ViewToggle.vue) ──
   검색 모듈에서 먼저 짓고 모델 제공업체가 둘째로 쓰면서 여기로 올렸습니다. 표와 같은 줄 · 같은 쪽을 카드로 그릴 뿐입니다.
   카드 하나가 곧 문 — 면은 판과 같은 --card, 구분은 1px 선 하나. 올리면 선만 짙어집니다(면 반전 · 그림자 없음).
   안은 넷뿐: 머리(종류 · 예외 상태) · 이름 · 부제 한 줄 · 발 한 줄. ⛔ 이름표 | 값 줄(dl)을 늘어놓지 마세요 —
   같은 날 세 줄로 지었다가 사용자 지적("카드 너무 많노")으로 걷었습니다. 많이 봐야 하면 표 보기입니다 */
.vcards { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; padding: 0 20px 20px;
  align-content: start; flex: 1; overflow: auto; min-height: 0;
  /* 굴리면 윗변에 선(.is-scrolled · view-mode.ts) — 자리는 늘 잡아 둬 카드가 1px 출렁이지 않게.
     건수 줄과의 틈은 바깥(margin 12 — 표 .tablewrap 과 같은 자리)이라 선이 건수 글자에 붙지 않고, 안쪽 위 여백 0 이라 쉴 때 모양은 그대로 */
  margin-top: 12px; border-top: 1px solid transparent; }
.vcards.is-scrolled { border-top-color: var(--border); }
.vcard { position: relative; display: flex; flex-direction: column; gap: 6px; min-width: 0; padding: 16px; text-align: left; font: inherit;
  color: var(--foreground); background: var(--card); border: 1px solid var(--border); border-radius: var(--r-ct); cursor: pointer;
  transition: border-color .12s ease; }
.vcard:hover { border-color: var(--muted-foreground); }
.vcard:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 22%, transparent); }
.vcard__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 22px; }
.vcard__kind { display: inline-flex; align-items: center; gap: 6px; min-width: 0; font-size: 12px; color: var(--muted-foreground); }
.vcard__kind .ic { flex: none; color: var(--rk, var(--muted-foreground)); }   /* 종류 색 = .rk-* 한 벌(공용/res-kind.ts) */
.vcard__name { font-size: 15px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vcard__sub { font-size: 13px; color: var(--muted-foreground); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vcard__foot { margin-top: 2px; font-size: 12px; color: var(--muted-foreground); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vcard__foot b { font-weight: 600; color: var(--foreground); }
.vcards > .ds-empty, .vcards > .empty { grid-column: 1 / -1; }
.vcards > .ds-empty { padding: 48px 20px; }

/* ━━ 상태 드롭다운 태그 — 표 줄에서 상태를 바로 바꾸는 칸 (콘솔 ui.css 에서, 부품: 콘솔 공용/부품/DsTagSelect.vue) ━━ */
/* ── 상태 드롭다운 태그 (공용/부품/DsTagSelect.vue — 2026-10-05) ─────────────────────────────────
   표의 줄에서 상태를 바로 바꾸는 칸 — 트리거는 **틴트 태그 + 셰브런**, 목록은 같은 태그 + 체크.
   값은 이용자 화면 비식별화 상태 칸(.cog-dag-statusbtn)과 같습니다(28px · 12px/600 · 왼 9 오른 6 · 셰브런 14 @70%) —
   톤은 콘솔 .badge 한 벌을 그대로 씁니다. ⚠ 한쪽을 고치면 같이 고치세요.
   ⛔ 표 안 상태 칸에 테두리 있는 고르기 칸(DsSelect)을 다시 넣지 마세요 — 배지가 줄지어 선 표에서 그 칸만 입력칸으로 보였습니다 */
.tagsel-wrap { display: inline-block; vertical-align: middle; }
.badge.tagsel {
  height: 28px; padding: 0 6px 0 9px; gap: 4px; border: 0; border-radius: var(--r-el);
  font-family: inherit; font-size: var(--text-xs); font-weight: var(--weight-semibold); cursor: pointer;
}
.badge.tagsel .ic { width: 14px; height: 14px; opacity: .7; }
.badge.tagsel:hover, .badge.tagsel.is-on { filter: brightness(.96); }
.badge.tagsel:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
.tagsel-menu.pop { width: 150px; min-width: 0; }
.tagsel-menu button .ic { flex: none; color: var(--nav-active-fg); }

/* ━━ 표 열 설정 — [표 열 설정] 버튼 + 팝오버(켜기 · 끄기 · 끌어 옮기기 · 기본값) (콘솔 ui.css 에서, 부품: 콘솔 공용/부품/TableColsButton.vue · 공용/table-cols.ts) ━━ */
/* ── 표 열 설정 (2026-08-27 dlp.css에서 승격) ──
   어떤 열을 보일지 담당자가 고르는 부품입니다. 새 화면도 이 클래스를 그대로 쓰세요(화면 CSS에
   복사 금지). 쓰는 화면과 **왜 열 구성이 고정이 아닌지**:
     · 이상탐지(AI DLP) — 원조. 항목을 전부 열로 펼쳐서 안 보는 열을 끄지 않으면 넘칩니다
     · 검색 로그 — **외부 플랫폼에서 받아 오는 로그**라 배포마다 오는 필드가 다릅니다
     · 사용자 관리 (2026-09-07) — **직위 · 직책 · 직급을 회사마다 쓰는 범위가 다릅니다.**
       Vue 화면의 첫 채택입니다(앞 둘은 문자열 렌더) — 목록을 훑어 `<th>` · `<td>`를 그리는
       방식은 `UsersView.vue`의 `visCols`가 본입니다
   ⚠ 낱말은 **'표 열 설정'**입니다 — 표의 세로 칸은 콘솔 전체가 '열'로 적습니다.
     2026-09-07에 AI DLP의 '표 컬럼 설정'을 여기에 맞춰 셋이 한 낱말이 됐습니다. 되돌리지 마세요. */
.btn.btn--sq { width: 32px; padding: 0; justify-content: center; }
.pop--cols.pop { width: 248px; left: auto; right: 0; padding: 6px; }
.pop--cols.pop .head { padding: 4px 6px 6px; }
.pop--cols.pop .foot { border-top: 1px solid var(--border); margin-top: 4px; padding: 6px 6px 2px; text-align: right; }
ul.colcfg { list-style: none; margin: 0; padding: 0; max-height: 356px; overflow-y: auto; }
ul.colcfg li {
  display: flex; align-items: center; gap: 6px;
  height: 32px; padding: 0 6px; border-radius: var(--r-el);
  background: var(--card); cursor: grab;
}
ul.colcfg li:hover { background: var(--overlay-hover); }
ul.colcfg li.dragging { opacity: .45; cursor: grabbing; }
/* 끌어 옮길 때 — 목록은 **놓을 때까지 그대로**이고, 놓일 자리는 줄 사이 경계의 2px 끼움선(.cdnd-line · 공용 drop-line.ts)이 말합니다
   (2026-10-05 사용자 지시: "끌어다 놓을 때 미리 내려놓은 위치가 표시가 되었으면 — 공통으로"). 끄는 동안 지나가는 줄의 hover 면은 끕니다(선 하나만 말하게).
   ⛔ 지나가는 자리에 줄을 바로 끼워 넣던 앞 판으로 되돌리지 마세요 */
ul.colcfg:has(> li.dragging) > li:not(.dragging):hover { background: var(--card); }
ul.colcfg .grip { color: var(--muted-foreground); flex: none; }
ul.colcfg label { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; font-size: 13px; cursor: pointer; }
ul.colcfg label span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
ul.colcfg input[type="checkbox"]:disabled { opacity: .45; }
ul.colcfg li:has(input:disabled) label { cursor: default; }
ul.colcfg .req { flex: none; font-size: 11px; color: var(--muted-foreground); }

/* ━━ 버전 기록 창 — 왼쪽 고른 판의 화면 · 오른쪽 판 목록 + 복원 (콘솔 ui.css 에서, 부품: 콘솔 공용/부품/DsVersionModal.vue · 공용/version-history.js) ━━ */
/* ════════════════════════════════════════════════════════════
   버전 기록 — **콘솔 공통 규격** (2026-08-30 사용자 지시로 공통화)
   ───────────────────────────────────────────────────────────────────────────
   ⚠ 콘솔의 **모든** 버전 이력 화면은 이 한 벌을 씁니다. 원본은 검색 사전에서 다듬은
     노션식 두 칸 모달이고, 2026-08-30에 여기(ui.css)로 올려 벡터 DB · 관계도가
     같이 쓰게 했습니다. 새 화면에 버전 이력을 붙일 때도 **여기 것을 쓰세요** —
     화면 CSS에 복사하거나 자기 접두어로 다시 만들지 마세요.
   ⚠ 부품은 `shared/DsVersionModal.vue`(Vue 화면)와 `shared/version-history.js`
     (문자열 렌더 화면) 둘입니다. 모양은 여기 하나뿐이고, 그 둘은 골격만 다릅니다.

   ── 한 벌의 골격 (바꾸지 마세요) ──
     .dsv-dlg  = 화면을 채우는 두 칸 창 (창 전체를 가로지르는 머리·발줄 없음)
       .dsv-doc > .dsv-body   왼쪽 = **고른 판의 화면 그대로**
       .dsv-side              오른쪽 = 머리(제목·닫기) · 판 목록(타임라인) · 발(복원)
   ⛔ 화면 오른쪽에 붙는 패널이나, 목록만 있는 좁은 다이얼로그로 되돌리지 마세요 —
     그 둘이 어떤 문제를 냈는지는 아래 주석에 그대로 남겨 뒀습니다.
   ═══════════════════════════════════════════════════════════════════════════ */
/* ── 창 — 왼쪽이 고른 판의 화면, 오른쪽이 판 목록, 발이 복원입니다 ──
   ⚠ 예전에는 화면 오른쪽에 320px 칸을 내주고 **본문을 그 판으로 덮어썼습니다.** 그 탓에
     표가 좁아지고, 견줄 '지금 사전'이 화면에서 사라지고, 닫을 때까지 편집·검색을 막아야
     했습니다. 모달은 셋 다 풉니다 — 넓게 읽고, 뒤 화면은 그대로 둡니다.
   ⛔ 우측 칸으로 되돌리지 마세요.
   ⚠ 크기는 **화면을 따라갑니다** (2026-08-29 사용자 지시) — 위아래로 200px만 남기고
     나머지를 채웁니다. 고정 높이(760px)였을 때는 큰 화면에서 창 아래가 텅 비고,
     작은 화면에서는 목록이 스크롤에 갇혔습니다. 좌우도 같은 200px로 맞춰 대칭입니다.
     ⛔ 다시 px로 고정하지 마세요.
     ⚠ min-height는 아주 낮은 창(노트북 확대 등)에서 머리 · 발이 겹치지 않게 하는 바닥입니다 */
/* ⚠ 창은 **가로 두 칸**입니다 (2026-08-29 사용자 지시, 노션 구조 그대로) —
   창 전체를 가로지르는 머리줄 · 발줄은 없습니다. 두 칸이 각자 자기 머리를 갖고,
   복원 버튼은 **오른쪽 칸의 발**입니다: 고르는 것도 복원하는 것도 한 칸에서 끝납니다.
   ⛔ 창 전체를 덮는 머리 · 본문 구조로 되돌리지 마세요 */
/* ⚠ .dlg.dsv-dlg — 콘솔 창 .dlg 와 같은 특이도라 겹쳐 적습니다(콘솔은 ui.css 를 ds.css 뒤에 실어, 한 겹이면 .dlg 의 폭 460 이 이깁니다) */
.dlg.dsv-dlg {
  width: calc(100vw - 200px);
  height: calc(100vh - 200px);
  min-height: 360px;
  max-width: none; padding: 0;
  display: flex; overflow: hidden;
}
/* ── 왼쪽 = 그 시점 화면 ── */
.dsv-doc { flex: 1; min-width: 0; display: flex; flex-direction: column; }
/* ⛔ 왼쪽 칸에 제목줄을 달지 마세요 (2026-08-30 사용자 지시) — 무엇의 이력인지는 모달을
   연 화면이 이미 말해 주고, 줄 하나가 왼쪽 칸 위를 통째로 차지합니다.
   ⚠ 여백은 **0입니다** (2026-08-30 사용자 지시) — 내용이 칸을 꽉 채웁니다.
     ⛔ 다시 padding을 넣지 마세요. 넣을 여백이 있으면 **넣는 쪽 화면 CSS**에서 정하세요.
   내용이 길면 **이 칸만** 스크롤합니다 — 오른쪽 판 목록은 제자리에 섭니다 */
.dsv-body { flex: 1; min-height: 0; overflow: auto; padding: 0; }

/* ── 오른쪽 = 버전 기록 (머리 · 목록 · 복원) ── */
.dsv-side {
  width: 320px; flex: none; min-height: 0;
  display: flex; flex-direction: column;
  border-left: 1px solid var(--border);
}
/* 머리 — 제목 | 닫기. 닫기가 여기 있는 것도 노션과 같습니다
   ⛔ 부제('동의어 사전 · 게시 9번' 같은 줄)를 다시 달지 마세요 (2026-08-28 사용자 지시) —
     무엇의 이력인지는 모달을 연 화면이 이미 말합니다 */
/* ⛔ **밑선을 다시 긋지 마세요** (2026-09-01 사용자 지시) — 이 창에서 선이 하는 일은
     좌우 두 칸을 가르는 것 하나입니다. 거기에 머리 밑선 · 발 윗선까지 얹으니 작은 창에
     가로선이 셋이라 복잡해 보였습니다. 머리와 목록은 **높이(58px)와 여백**이 가릅니다.
   ⚠ 높이는 못박습니다 — 닫기 버튼(30) + 위아래 14가 우연히 맞던 값이라, 글자 크기를
     건드리면 왼쪽 '변경 내역' 머리와 어긋났습니다. 두 머리는 늘 같은 58px입니다 */
.dsv-side-h {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  flex: none; height: 58px; padding: 0 12px 0 20px;
}
/* 16px — 왼쪽 칸 제목과 **같은 급**입니다 (2026-09-01 사용자 지시로 15에서 올림).
   한 창에 나란히 선 두 머리라 크기가 갈리면 층이 다른 것으로 읽힙니다 */
.dsv-side-h b { font-size: 16px; font-weight: 600; letter-spacing: -.01em; }
/* 발 — 복원 하나. 오른쪽 끝에 붙입니다 (윗선 없음 — 위 ⛔ 참조) */
.dsv-foot {
  flex: none; display: flex; justify-content: flex-end;
  padding: 12px 16px;
}

/* ══ 판 목록 = 세로 타임라인 — **원본은 DS의 Timeline입니다** (2026-08-28) ══
   design-system 리포에 DsTimeline / ds.css의 .ds-timeline 으로 등록했습니다.
   https://design.cogniterm.com/#/components/timeline
   ⚠ 치수를 여기서 새로 정하지 마세요 — DS가 원본입니다. 아래 값은 그 규격 그대로이고,
     토큰 이름만 콘솔 것으로 바꿨습니다(--gray-8 → --muted-foreground, --gray-5 → --border).
       링 14px · 획 2px · 속 빔 | 선 2px | 링↔글자 26px | 제목 아래 6px | 항목 사이 20px
       글 차례: 제목(무엇을) → 본문(누가·얼마나) → 시각(일시분)
   ⚠ 콘솔은 Vuetify도 ds.css도 싣지 않아 DsTimeline을 그대로 import할 수 없습니다.
     그래서 클래스는 콘솔 접두어(dsv-)로 두되 **값은 DS를 따릅니다**. DS가 바뀌면 여기도 맞추세요.
   ⚠ DS와 일부러 다른 곳 두 가지 —
     ① 링 · 선을 판 줄 **바깥**(음수 left)에 그립니다. DS는 항목 안(left:0)에 두는데,
        우리 판 줄은 눌러서 미리보기하는 버튼이라 hover 면이 링과 선을 덮습니다.
     ② 고른 판의 링을 브랜드색으로 칠합니다. DS의 variant(success · danger · brand) 자리를
        '지금 보고 있는 판'에 쓴 것입니다 — 우리 판에는 성공/실패가 없습니다.

   ── 원래 치수의 출처 (DS 주석에도 같은 내용이 있습니다) ──
   naive-ui n-timeline(medium)에서 옮겼습니다. 그쪽 titleFontSize · iconSize가 둘 다 14px이라
   우리 타이포 스케일과 그대로 맞아 비율을 줄이지 않았습니다.
     src/timeline/src/styles/index.cssr.ts · src/timeline/styles/_common.ts · light.ts
   ⚠ 좌표: 목록 왼쪽 여백 28px · 링 10~24px(중심 17) · 선 16~18px · 글자 36px부터
     (= 링 왼쪽 10 + DS의 26px 칸). **.w의 line-height 1.25를 바꾸면 링의 top도 함께.** */
.dsv-list { flex: 1; min-height: 0; overflow-y: auto; padding: 6px 10px 8px 28px; }
/* ⛔ .dsv-vgrp(날짜 묶음 머리글)은 2026-08-28 걷었습니다 — 되살리지 마세요.
   sticky까지 붙여 뒀는데도 '언제'가 날짜(머리글) · 시:분(판 줄) 두 자리로 갈라져 있었고,
   이 사전은 하루 한 번꼴로 게시해서 묶음마다 판이 하나뿐이라 줄 수만 배로 늘었습니다.
   ⚠ 되살리면 아래 :last-child 선 끝맺음도 깨집니다 — .dsv-list의 자식이 판 줄뿐이라는
     전제 위에 있는 규칙입니다 */
/* ⛔ '현재 판 / 지나간 판' 구획 머리글(.dsv-vcap)은 2026-08-29 걷었습니다 — 사용자 지시.
   **점(링)이 이미 어느 판인지 말합니다.** 머리글과 구분선을 덧대면 한 줄로 흐르던 시간축이
   억지로 끊깁니다. 목록은 하나로 두고, 첫 줄에만 '운영 중'을 답니다.

   판 한 줄 = 누르는 영역(.dsv-item) + 더보기(.dsv-more). 더보기는 판 줄 **밖**입니다 —
   버튼 안에 버튼을 넣을 수 없고, 넣으면 미리보기와 메뉴 열기가 한 과녁에서 겹칩니다 */
.dsv-row { position: relative; }
.dsv-more {
  position: absolute; right: 4px; top: 6px;
  width: 26px; height: 26px; opacity: 0; transition: opacity .1s ease;
}
/* 늘 떠 있으면 판 목록이 점 세 개로 어지럽습니다 — 손이 닿거나 초점이 갔을 때만 */
.dsv-row:hover .dsv-more,
.dsv-more:focus-visible { opacity: 1; }
.dsv-item {
  position: relative;
  display: block; width: 100%; text-align: left; cursor: pointer;
  border: 0; background: none; border-radius: var(--r-el);
  padding: 8px 8px 10px; margin-bottom: 10px;   /* 10 + 10 = 원본 meta의 margin-bottom 20px */
}
/* 더보기가 뜨는 자리만큼 제목을 비켜 둡니다 — 겹쳐서 일시분이 가리지 않게 */
.dsv-row:hover .dsv-item .w { padding-right: 26px; }
.dsv-row:last-child .dsv-item { margin-bottom: 2px; }
/* 선 — 링 바닥(24)에서 시작해 **다음 링 꼭대기에 딱 닿습니다**(원본 top:iconSize · bottom:0).
   bottom -20 = 판 사이 10(margin) + 다음 줄 여백 8 + 다음 링 top 1.75 ≈ 20 */
.dsv-item::before {
  content: ''; position: absolute; left: -12px; top: 24px; bottom: -20px;
  width: 2px; background: var(--border);
}
/* 줄기는 목록 **끝에서만** 끊깁니다 — 가운데를 가르지 않습니다(2026-08-29 구획 폐지) */
.dsv-row:last-child .dsv-item::before { display: none; }
/* 링 — 14px, 획 2px, **속은 비어 있습니다**(원본은 background를 주지 않습니다).
   ⛔ 링 안에 아이콘을 넣지 마세요 — 원본의 icon 슬롯은 type과 짝인데 우리는 type이 없습니다 */
.dsv-item::after {
  content: ''; position: absolute; left: -18px; top: 10px;
  width: 14px; height: 14px; border-radius: 14px; box-sizing: border-box;
  /* stylelint-disable-next-line declaration-property-value-disallowed-list -- 타임라인 점의 고리(DS Timeline 과 같은 2px) · 콘솔 값 그대로 */
  border: 2px solid var(--muted-foreground);
}
.dsv-item:hover { background: var(--overlay-hover); }
/* 아직 판이 아닌 줄(게시 전 '현재 초안' · 2026-11-03) — 다음 판까지의 줄기만 점선으로. 색은 그대로, 선 모양만 '아직 쌓이지 않음'을 말함.
   ⚠ 링은 실선 그대로 — 14px 점선 링은 로딩 표시로 읽혔습니다 */
.dsv-item.is-draft::before { background: repeating-linear-gradient(to bottom, var(--border) 0 4px, transparent 4px 8px); }
/* 고른 판 = 링과 제목만 브랜드색 (원본이 circleBorderInfo로 링 색을 가르던 그 자리).
   ⛔ 판 줄을 스트로크로 감싸지 마세요 (2026-08-28 사용자 지시) — 링이 이미 그 말을 합니다 */
.dsv-item.on::after { border-color: var(--primary); }
.dsv-item.on .w { color: var(--nav-active-fg); }
/* ── 1) title — **일시분** (2026-08-29 사용자 지시로 '무엇을 했는가'에서 교체) ──
   판을 지목하는 이름이 곧 일시분이라, 그 이름이 title 자리에 옵니다.
   ⚠ line-height 1.25는 **원본 값이자 링 정렬의 기준**입니다(링 top = 14*1.25/2 - 7 = 1.75).
     바꾸면 ::after의 top도 함께 맞추세요 */
.dsv-item .w {
  display: flex; align-items: baseline; gap: 8px;
  margin: 0 0 6px; font-size: 14px; font-weight: 600; line-height: 1.25;
  color: var(--foreground); font-variant-numeric: tabular-nums;
}
/* '운영 중' — 일시분 옆 꼬리표. 굵기·크기·색을 title에서 떼어 냅니다(제목이 아니라 상태입니다).
   ⚠ 면 배경이나 배지를 두지 마세요 — 이 자리는 이미 '현재 판' 머리글이 한 번 말했습니다 */
.dsv-item .w .cur {
  flex: none; font-size: 12px; font-weight: 500; color: var(--muted-foreground);
}
/* ── 2) content — **누가**. 이 줄에 남는 것은 이름 하나뿐입니다 ──
   ⛔ 바뀐 규모(.sum — '등록 1 · 수정 2')는 2026-08-29 걷었습니다 (사용자 지시).
     무엇이 얼마나 바뀌었는지는 판을 누르면 왼쪽 사전이 그 시점으로 바뀌어 보여 줍니다.
   ⛔ 이름 앞에 프로필 아바타를 달지 마세요 (2026-08-28 사용자 지시) */
.dsv-item .ft { font-size: 14px; color: var(--muted-foreground); }
.dsv-item .by { display: block; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* ⛔ 3) time(meta) 줄(.hd · .tm)은 2026-08-29 걷었습니다 — 일시분이 title로 올라가면서
   맨 아래에 다시 적을 것이 없어졌습니다. '무엇을 했는가'(what)도 같은 날 뺐습니다:
   무엇이 바뀌었는지는 판을 누르면 **왼쪽 사전이 그대로 바뀌어** 보여 줍니다. */

/* ── 더보기 메뉴 — 판 한 줄의 ⋯ (복구하기 · 삭제하기) ──
   ⚠ fixed입니다. 판 목록이 스크롤 칸이라 안에 두면 잘립니다(자리는 JS가 재서 넣습니다).
   ⚠ z는 **버전 기록 창(.scrim 80)보다 위**여야 합니다 (2026-08-30 고침).
     60이던 때는 메뉴가 창 뒤에 깔려 **눌러도 아무것도 안 뜨는 것처럼 보였습니다** —
     버전 기록이 모달이 되면서 그 아래로 들어간 것을 못 보고 예전 값을 그대로 뒀던 탓입니다.
     ⛔ 다시 80 아래로 내리지 마세요.
   ⚠ '확인 창보다는 아래'를 걱정할 필요가 없습니다 — 복구 · 삭제를 고르는 순간 메뉴는
     닫힙니다(dicResAsk · dicDelAsk가 먼저 메뉴를 비웁니다). 둘이 함께 뜨는 순간이 없습니다 */
/* 위험한 줄의 붉은 글자는 `.pop button.danger`가 이미 갖고 있습니다 — 복사 금지 */
.dsv-menu { position: fixed; z-index: 90; min-width: 150px; }

/* 아직 판이 없는 목록 — 빈 칸을 그냥 두면 무엇이 실패한 건지 모릅니다 */
.dsv-empty { padding: 10px 4px; font-size: 13px; color: var(--muted-foreground); }

/* 버전 기록 창 바탕 — DsVersionHistory 가 씀(콘솔 .scrim · .dlg 와 같은 값: 딤 · 카드 면 · 1px · 모서리 컨테이너 · --shadow-xl) */
.dsv-overlay { position: fixed; inset: 0; z-index: 80; display: flex; align-items: center; justify-content: center; background: var(--scrim); }
.dsv-panel { max-width: calc(100vw - 32px); background: var(--card); border: 1px solid var(--border); border-radius: var(--r-ct); box-shadow: var(--shadow-xl); }

/* ━━ 자원 칩 — 관계를 적는 칩(그림 색 + 이름) · 종류 색 .rk-* (콘솔 ui.css 에서, 부품: 콘솔 공용/부품/ResChips.vue · 공용/res-kind.ts) ━━ */
/* ══ 자원 칩 — 관계를 적는 한 벌 (2026-10-27 신설 · Attio 레코드 칩 결 — 규칙은 공용/res-kind.ts 머리글) ══
   테두리 1px 알약 + 종류 색 그림 + 이름. 사용처 · 데이터 흐름 상세의 상류 · 하류가 이 모양 하나입니다.
   ⛔ 그림 칸에 면(타일)을 깔지 마세요(2026-10-20 "아이콘이 주가 돼 버려"). ⛔ 상태색(빨강 · 주황)을 종류 색으로 쓰지 마세요 */
/* folder = 검색 컨텍스트 캔버스의 문서 폴더(AI 드라이브 · 문서 탐색) — 데이터 흐름의 '문서'와 같은 것이라 같은 색(2026-10-29 사용자: "색상이 일치되면 덜 헷갈릴듯") */
.rk-conn, .rk-docs { --rk: var(--cat-7); }   /* 문서 = 커넥터가 쌓은 원본이라 같은 계통(그림이 가름 · 2026-10-29) */
.rk-rule { --rk: var(--cat-8); }
.rk-asset { --rk: var(--cat-3); }
.rk-graph, .rk-dict { --rk: var(--cat-5); }
.rk-category { --rk: var(--cat-1); }
.rk-tool { --rk: var(--cat-4); }   /* 도구(MCP · Tool · 2026-10-30) — 모델(cat-4)과 같은 에이전트 쪽 부품 색. 둘이 한 그림에 함께 서지 않음 */
.rk-agent { --rk: var(--cat-2); }
.rk-model { --rk: var(--cat-4); }
.rk-org, .rk-group { --rk: var(--cat-6); }
.rchips { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px; vertical-align: middle; max-width: 100%; }
.rchip {
  display: inline-flex; align-items: center; gap: 6px; height: 26px; max-width: 240px; padding: 0 9px 0 7px;
  border: 1px solid var(--border); border-radius: var(--r-el); background: var(--card);
  font: inherit; font-size: 13px; line-height: 1; color: var(--foreground); cursor: pointer;
  transition: border-color .12s ease, background .12s ease;
}
.rchip:hover { border-color: var(--border-emphasized); background: var(--overlay-hover); }
.rchip:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 22%, transparent); }
.rchip > .ic { flex: none; color: var(--rk, var(--muted-foreground)); }
.rchip > .t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 멈춘 자원(비활성 룰 · 일시정지 커넥터) — 이름만 흐리게. 그림 색은 그대로(무엇인지는 남김) */
.rchip.is-off > .t { color: var(--muted-foreground); }
.rchip--more { padding: 0 8px; color: var(--muted-foreground); font-variant-numeric: tabular-nums; }
.rchips:not(.is-all) > .rchip--x { display: none; }
/* 떼어 낼 수 있는 칩(ResChips removable — 에이전트 스튜디오 편집 중) — 칩은 안 눌리고 끝의 × 만 눌립니다 */
.rchip--rm { cursor: default; padding-right: 3px; }
.rchip--rm:hover { border-color: var(--border); background: var(--card); }
.rchip > .x {
  flex: none; display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; padding: 0;
  border: 0; border-radius: 4px; background: none; color: var(--muted-foreground); cursor: pointer;
}
.rchip > .x:hover { color: var(--foreground); background: var(--overlay-hover); }
.rchip > .x:focus-visible { outline: none; box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 40%, transparent); }

/* ══ 채팅 보내기 — 콘솔 채팅 입력창의 보내기 한 벌 (2026-10-27 dlp-chat.css 의 .dlc-send 에서 올림 · 이상탐지 AI 질의 · 에이전트 스튜디오 시험 대화) ══

/* ━━ 흐름 노드 — 경로 · 노코드 그림의 카드 · 연결점 · 선 · 점 배경 (앱 공용/스타일/흐름노드.css 통째로, 부품: 콘솔 공용/부품/FlowNode.vue · 공용/flow-node.ts) ━━ */
/* ══ 흐름 노드 · 흐름 캔버스 — 경로 · 노코드 그림 한 벌 (2026-10-28 신설 · 공용/부품/FlowNode.vue 머리글) ══
   쓰는 곳: 검색 컨텍스트 캔버스 · 그 버전 기록(PipelineMini) · 데이터 흐름.
   ⛔ 화면 CSS 에 카드 · 연결점 · 선 모양을 다시 적지 마세요 — 화면은 자리(배치 · 도구줄 · 잇는 손잡이 같은 그 화면만의 손)만 둡니다. */

/* ── 캔버스 — VueFlow 감싸개에 .fflow ── */
.fflow { --note-lines: 3; }   /* 부품 지역 변수 — 노드를 감싼 칸이 설명 줄 수를 넘깁니다(flow-node.ts noteLines) */
.fflow .vue-flow__node { padding: 0; border: 0; background: none; box-shadow: none; transition: opacity .18s; }
/* 이어진 것만 밝히기 — 올리거나 고른 노드와 이어진 노드 · 선은 그대로, 나머지는 흐리게(검색 컨텍스트가 본 · 2026-10-28 데이터 흐름도 같은 손) */
.fflow .vue-flow__node.is-dim { opacity: .58; }
.fflow .vue-flow__edge.is-dim { opacity: .2; }
.fflow .vue-flow__background pattern circle,
.fflow .vue-flow__background pattern rect { fill: color-mix(in srgb, var(--muted-foreground) 32%, transparent); }

/* ── 카드 ── */
.fnode {
  position: relative; display: flex; flex-direction: column; box-sizing: border-box; width: 228px; height: 84px;
  border: 1px solid var(--border); border-radius: 10px; background: var(--card); box-shadow: var(--shadow-sm);
  color: var(--foreground); cursor: pointer; transition: border-color .15s, box-shadow .15s;
}
.fnode--lg { width: 244px; height: 88px; }
.fnode--sm { width: 200px; height: 52px; }
/* 머리 — 그림 · 이름/부제 · 표식. 안여백 왼 14 · 오른 12(표식 · 펼치기 단추가 끝에 붙음) */
.fnode__head { flex: 1; min-height: 0; min-width: 0; display: flex; align-items: center; gap: 10px; padding: 0 12px 0 14px; }
.fnode__ic { flex: none; color: var(--rk, var(--muted-foreground)); }
/* ⛔ .rk-folder(노랑 --cat-4)는 2026-11-03 걷었습니다 — 에이전트 빌더의 지식자산 폴더가 LLM 노드(cat-4)와 같은 노랑이라 다른 개념으로 읽혔음.
   폴더째 고른 지식자산도 지식자산(.rk-asset) 하나입니다(사용자: "개념 자체는 같잖아 … 하나의 개념처럼") */
.fnode__tt { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; line-height: 1.4; }
.fnode__t { min-width: 0; font-size: 14px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fnode--lg .fnode__t { font-weight: 600; }
.fnode__sub { font-size: 12px; color: var(--muted-foreground); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 표식 — 기준은 그림 하나(툴팁) · 상태는 작은 배지(그림 + 낱말 · DS .badge.sm 톤 · 까닭은 툴팁) */
.fnode__ind { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; cursor: help; color: var(--muted-foreground); }
.fnode__state { flex: none; gap: 3px; cursor: help; }
.fnode__state > .ic { width: 12px; height: 12px; }
.fnode__ind.is-base { color: var(--primary-text); }
.fnode__head > .fnode__ind + .fnode__ind { margin-left: -6px; }
/* stack 짜임(데이터 흐름) — 1행 종류(20) · 틈 4 · 2행 이름(20 × 1~2줄) · 위아래 안여백 10 · 선 아래 사실 한 줄(28 + 선 1).
   ⚠ 높이 셈은 화면의 STACK_H 와 같아야 합니다: 2(테두리) + 10 + 20 + 4 + 20 × 줄 수 + 10 + 29 = 75 + 20 × 줄 수 */
.fnode--stack .fnode__head { flex-direction: column; align-items: stretch; justify-content: center; gap: 4px; padding: 10px 14px; }
.fnode__kindrow { display: flex; align-items: center; gap: 6px; height: 20px; min-width: 0; }
.fnode__kindt { flex: 1; min-width: 0; font-size: 12px; line-height: 20px; color: var(--muted-foreground); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fnode__kindrow > .fnode__ind + .fnode__ind { margin-left: -4px; }
.fnode__name {
  font-size: 14px; font-weight: 600; line-height: 20px; color: var(--foreground);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; word-break: keep-all; overflow-wrap: anywhere;
}
.fnode--stack .fnode__body { box-sizing: content-box; }
/* 몸 — 사실 한 줄. 선은 카드 끝에서 끝까지(몸이 카드 폭 전체라 안여백이 선을 자르지 않음) */
.fnode__body {
  flex: none; height: 28px; display: flex; align-items: center; gap: 4px; padding: 0 14px;
  border-top: 1px solid var(--border); font-size: 12px; color: var(--muted-foreground);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-variant-numeric: tabular-nums;
}
.fnode__body b { color: var(--foreground); font-weight: 600; }
/* 설명 — 사실 줄 아래 흐린 글(선 없이 · 12/18 · 줄 수는 부르는 화면이 --note-lines 로 넘김 — 카드 높이와 같은 셈이라 넘치지 않음). 에이전트 빌더(2026-11-02) */
.fnode__note {
  flex: none; padding: 0 14px 10px; font-size: 12px; line-height: 18px; color: var(--muted-foreground);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: var(--note-lines, 3); line-clamp: var(--note-lines, 3); overflow: hidden;
  word-break: keep-all; overflow-wrap: anywhere; white-space: pre-line;
}

/* 상태는 테두리로 말하지 않습니다 — 카드 안 배지(.fnode__state). 테두리는 선택만(아래) — 2026-10-28 사용자 지시 */
.fflow .vue-flow__node.is-err .fnode { border-color: var(--error); }

/* 올림 · 고름 — 고른 것은 브랜드 테두리 + 옅은 링(n8n 의 고른 표시 굵기) */
.fflow .vue-flow__node:hover .fnode { border-color: var(--border-emphasized); box-shadow: var(--shadow-md); }
/* 고른 노드 = 파란 2px 외곽선 하나. outline 은 자리를 안 먹어 카드 크기 · 연결점이 그대로입니다(offset −1 이라 1px 테두리를 덮고 바깥 1px) */
.fflow .vue-flow__node.is-sel .fnode,
.fflow .vue-flow__node.selected .fnode {
  outline: 2px solid var(--nav-active); outline-offset: -1px; box-shadow: var(--shadow-sm);
}

/* 점선 얇은 카드 — 만들기(카테고리 추가) · 묶음(외 N개 더). 흐린 글자, 올리면 브랜드 */
.fnode--ghost {
  flex-direction: row; align-items: center; gap: 8px; height: 40px; padding: 0 14px;
  border-style: dashed; box-shadow: none; color: var(--muted-foreground); font-size: 13px;
}
.fnode--ghost > .ic { flex: none; }
.fnode--ghost > .t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fflow .vue-flow__node:hover .fnode--ghost { color: var(--nav-active-fg); border-color: var(--nav-active); box-shadow: none; }

/* ── 연결점 — 카드 가장자리의 속 빈 작은 원. 잇는 손이 아니면 안 잡힙니다 ── */
.fflow .vue-flow__handle.fnode-h {
  width: 8px; height: 8px; min-width: 0; min-height: 0; border-radius: 50%;
  background: var(--card); border: 1.5px solid var(--border-emphasized); transition: border-color .15s, background .15s;
}
.fflow .vue-flow__handle.fnode-h:not(.connectable) { pointer-events: none; }
.fflow .vue-flow__handle.fnode-h.is-none { opacity: 0; }
.fflow .vue-flow__node.is-sel .vue-flow__handle.fnode-h { border-color: var(--primary); }

/* ── 선 — 연한 회색, 올리거나 고른 노드에 닿은 선만 진한 회색(2026-10-20 사용자 지시: 브랜드색 아님) ── */
.fflow .vue-flow__edge-path { fill: none; stroke: color-mix(in srgb, var(--muted-foreground) 32%, transparent); stroke-width: 1.25; transition: stroke .15s; }
/* 밝힌 길 — 진한 회색. ⚠ 글자색(--muted-foreground) 그대로는 카드 테두리보다 진해 선이 그림을 이겼습니다 → 65% · 1.5 (2026-10-28 "선의 색상이 약간 진해") */
.fflow .vue-flow__edge.is-hi .vue-flow__edge-path { stroke: color-mix(in srgb, var(--muted-foreground) 65%, transparent); stroke-width: 1.5; }
/* 데이터가 흐르는 선 = 흐르는 점선(.is-flow) — 검색 컨텍스트의 카테고리 → 지식자산 · 데이터 흐름의 모든 선(2026-10-28 사용자 지시:
   "데이터 연결되는거 표현하는 방식은 검색 컨텍스트에서 점점점으로 나와서 약간 더 동적으로 보이는게 더 좋은듯").
   ⚠ 구조(폴더 → 하위)처럼 흐르지 않는 선에는 달지 않습니다 — 실선 그대로. 움직임 줄이기면 멈춘 점선 */
.fflow .vue-flow__edge.is-flow .vue-flow__edge-path { stroke-dasharray: 6 5; animation: fflow-dash .9s linear infinite; }
@keyframes fflow-dash { to { stroke-dashoffset: -11; } }
@media (prefers-reduced-motion: reduce) { .fflow .vue-flow__edge.is-flow .vue-flow__edge-path { animation: none; } }
/* 흐르지 않는 길 — 멈춘 짧은 점선. 실패(그 자원에서 나가는 선) = 빨강 · 멈춤(비활성 · 일시정지 자원에 닿은 선) = 더 옅은 회색.
   흐르는 점선과는 **움직임 · 점 간격 · 색** 셋이 다 달라 흐름인지 끊김인지 한눈에 갈립니다 */
.fflow .vue-flow__edge.is-fail .vue-flow__edge-path,
.fflow .vue-flow__edge.is-off .vue-flow__edge-path { stroke-dasharray: 2 4; animation: none; }
.fflow .vue-flow__edge.is-fail .vue-flow__edge-path { stroke: color-mix(in srgb, var(--error) 60%, transparent); }
.fflow .vue-flow__edge.is-fail.is-hi .vue-flow__edge-path { stroke: var(--error); }
.fflow .vue-flow__edge.is-off .vue-flow__edge-path { stroke: color-mix(in srgb, var(--muted-foreground) 22%, transparent); }

/* ── 캔버스 조작 알약 — 왼쪽 아래(화면 맞춤 · 확대 · 축소 차례 + 그 화면의 손). --top 은 왼쪽 위 ──
   반투명 카드 + 뒤 흐림. ⛔ 화면 CSS 에 다시 그리지 마세요(앞 판 .scp-ctrls · .lin-ctrls 두 벌) */
.fflow-ctrls {
  position: absolute; left: 16px; bottom: 16px; z-index: 5; display: flex; gap: 2px; padding: 4px;
  border: 1px solid var(--border); border-radius: 10px; box-shadow: var(--shadow-md);
  background: color-mix(in srgb, var(--card) 82%, transparent); backdrop-filter: blur(8px);
}
.fflow-ctrls--top { top: 16px; bottom: auto; }
/* 문제 띠 — 그림 칸 오른쪽 위(데이터 흐름 문제 띠 · 에이전트 빌더 고칠 것 띠 한 벌 · 2026-11-03 lineage.css .lin-hi 에서 올림).
   숫자만 상태색 · 켜짐은 콘솔 활성 규칙(브랜드 글자 · 면 반전 없음) */
.fflow-ctrls.fflow-hi { left: auto; right: 16px; }
.fflow-hi button b { font-weight: 600; font-variant-numeric: tabular-nums; }
.fflow-hi button.is-fail b { color: var(--error-text); }
.fflow-hi button.is-pending b, .fflow-hi button.is-warn b,
.fflow-hi button.is-gap b, .fflow-hi button.is-cut b { color: var(--warning-text); }   /* 끊김(데이터 흐름 gap · 빌더 cut)도 주황 숫자 — 앞 판은 이 둘만 회색이라 띠에서 안 보였음(2026-11-05) */
.fflow-hi button.on { color: var(--nav-active-fg); box-shadow: inset 0 0 0 1px var(--nav-active); }
.fflow-ctrls button {
  height: 28px; min-width: 28px; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 0 6px; border: 0; border-radius: var(--r-el); background: none; color: var(--muted-foreground); font: inherit; font-size: 13px; cursor: pointer;
}
.fflow-ctrls button:hover { background: var(--overlay-hover); color: var(--foreground); }

/* ━━ 로딩 표시 한 벌 — 회전 원 · 톱니 · 끝 표시 · 파형 · 대기 글 · 결과 글 (앱 공용/스타일/로딩.css 통째로, 부품: 앱 공용/부품/Spinner · TickSpinner · DoneMark · Waveform · BusyText · ResultLine · 문자열 화면 공용/spinner-html.ts) ━━ */
/* ══ 로딩 표시 한 벌 — 관리자 콘솔 · 이용자 화면 · Playground 공용 (2026-10-19) ════════════════
   사용자가 모음 페이지(/indicators)에서 확인하고 "전체적으로 빠져 있는거 다 찾아서 개선" 지시로 들였습니다.
   부품은 공용/부품의 Spinner · TickSpinner · DoneMark · Waveform · BusyText, 문자열 렌더 화면은 공용/spinner-html.ts.
   ⚠ 모양은 **이 파일 한 곳**입니다 — 부품 .vue 에 <style> 을 두지 마세요(집 규칙). 화면 CSS 에 다시 그리지 마세요.
   ⚠ 모션 출처: ldrs (MIT · Griffin Johnston · https://uiball.com/ldrs) — 'ring' · 'line spinner' · 'waveform'.
     패키지는 들이지 않았습니다(웹 컴포넌트 그림자 DOM 이라 우리 토큰 · 다크와 따로 놂). 모션만 SVG · CSS 로.

   쓰는 자리
   · 회전 원(.cog-ring)   — 혼자 서는 자리: 버튼 안(16) · 카드 머리 · 영역 가운데(20)
   · 톱니(.cog-tick)      — 글 옆에 끼는 자리: 업로드 중 · 비식별화 중 · 찾는 중 (14 하나)
   · 끝 표시(.cog-mark)   — 회전 원 · 톱니가 끝나면 이 자리로: 체크(완료) · 느낌표(실패)가 원과 함께 그려짐
   · 파형(.cog-wave)      — 음성 상태
   · 대기 글(.cog-busy)   — 톱니 + 지금 하는 일 한 줄(12px 회색 · 끝 문장부호 없음)
   · 결과 글(.cog-result) — 그 자리에서 끝을 알리는 한 줄(연결 테스트 등)
   · 영역 가운데(.cog-region) — 회전 원 20 + 글
   ⚠ AI 답변 대기는 여기 없습니다 — 채팅.css 의 .ai-indicator(Soft Rise v1.2) 그대로(같은 날 사용자: "기존께 더 낫다").
   ⚠ 버튼 처리 중은 DS 규칙(.btn.is-loading — 글은 그대로 · 앞에 회전 원 · 흐리게 하지 않음)입니다. */

/* ── 회전 원 — ldrs 'ring': 무리가 2초에 한 바퀴 도는 동안 호가 1.5초마다 짧아졌다 길어집니다 ──
   바탕 원(14%)은 가만히 — 원은 제자리, 호만 그 위를 달립니다(사용자 지적: "공간 자체가 움직이는 것처럼 보여").
   상자(svg)는 돌지 않고 안쪽 무리(g)만 뷰박스 한가운데를 축으로 돕니다 — 소수점 좌표에서도 가장자리가 안 흔들림 */
.cog-ring {
  --ring-c: var(--primary);
  display: inline-block;
  flex: none;
  overflow: visible;
  vertical-align: -3px;
  color: var(--ring-c);
  fill: none;
  stroke: currentColor;
  stroke-width: 2px;
  animation: cog-ld-fade .2s ease-out;
}
.cog-ring__tr { opacity: .14; }
.cog-ring__rot {
  transform-box: view-box;
  transform-origin: 50% 50%;
  animation: cog-ld-rot 2s linear infinite;
}
.cog-ring__arc {
  stroke-linecap: round;
  stroke-dasharray: 1 200;
  stroke-dashoffset: 0;
  animation: cog-ld-stretch 1.5s ease-in-out infinite;
}
.cog-ring.is-on-brand { --ring-c: var(--primary-foreground); }
.cog-ring.is-current { --ring-c: currentColor; }
.cog-ring.is-on-dark { --ring-c: rgb(255 255 255); }
.cog-ring.is-inverse { --ring-c: var(--background); }
.cog-ring.is-on-brand .cog-ring__tr,
.cog-ring.is-on-dark .cog-ring__tr,
.cog-ring.is-inverse .cog-ring__tr { opacity: .28; }
@keyframes cog-ld-rot { to { transform: rotate(360deg); } }
@keyframes cog-ld-stretch {
  0% { stroke-dasharray: 0 150; stroke-dashoffset: 0; }
  50% { stroke-dasharray: 75 150; stroke-dashoffset: -25; }
  100% { stroke-dashoffset: -99; }
}
@keyframes cog-ld-fade { from { opacity: 0; } }

/* ── 톱니 — ldrs 'line spinner' 를 작은 자리에 맞춤: 살 여덟 · 1.3초 · 쉬는 살도 15% ──
   (사용자: "톱니가 약간 너무 빠르고 수가 많아 보여 — 작은 버전 고려") 앞장선 살이 밝아지며 바깥 끝이 살짝 늘고,
   뒤로 갈수록 흐려집니다. 살의 안쪽 끝(7, 3.8)을 축으로 — 상자는 움직이지 않음. 크기는 14 하나 */
.cog-tick {
  display: inline-block;
  flex: none;
  overflow: visible;
  vertical-align: -2px;
  stroke: var(--primary);
  stroke-width: 1.6;
  stroke-linecap: round;
}
.cog-tick line {
  opacity: .15;
  transform-box: view-box;
  transform-origin: 7px 3.8px;
  animation: cog-ld-line 1.3s ease-in-out infinite;
  --i: 0;   /* 살 차례 — 부품이 살마다 인라인으로 겁니다 */
  animation-delay: calc(1.3s / -8 * var(--i));
}
@keyframes cog-ld-line {
  0%, 80%, 100% { opacity: .15; transform: scaleY(.8); }
  20% { opacity: 1; transform: scaleY(1); }
}

/* ── 끝 표시 — 체크 · 느낌표가 원과 함께 그려지며 나타납니다(선 굵기는 lucide 아이콘과 같은 결) ── */
.cog-mark { display: inline-block; flex: none; vertical-align: -2px; fill: none; stroke-linecap: round; stroke-linejoin: round; }
.cog-mark.is-ok { color: var(--success); }
.cog-mark.is-fail { color: var(--error); }
.cog-mark.is-ok.is-inverse { color: var(--success-on-inverse); }
.cog-mark.is-fail.is-inverse { color: var(--error-on-inverse); }
.cog-mark .ring { stroke: currentColor; stroke-dasharray: 57; stroke-dashoffset: 57; animation: cog-ld-draw .4s cubic-bezier(.4, 0, .2, 1) forwards; }
.cog-mark .glyph { stroke: currentColor; stroke-dasharray: 14; stroke-dashoffset: 14; animation: cog-ld-draw .25s cubic-bezier(.4, 0, .2, 1) .22s forwards; }
.cog-mark .dot { stroke: currentColor; opacity: 0; animation: cog-ld-in .15s ease-out .4s forwards; }
@keyframes cog-ld-draw { to { stroke-dashoffset: 0; } }
@keyframes cog-ld-in { to { opacity: 1; } }

/* ── 파형 — 음성 상태 · ldrs 'waveform' ── */
.cog-wave {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  flex: none;
  width: 14px;
  height: 13px;
  vertical-align: -2px;
}
.cog-wave > i {
  width: 2px;
  height: 100%;
  border-radius: 1px;
  background: var(--primary);
  transform: scaleY(.3);
  animation: cog-ld-grow 1s ease-in-out infinite;
}
.cog-wave > i:nth-child(1) { animation-delay: -.45s; }
.cog-wave > i:nth-child(2) { animation-delay: -.3s; }
.cog-wave > i:nth-child(3) { animation-delay: -.15s; }
@keyframes cog-ld-grow {
  0%, 100% { transform: scaleY(.3); }
  50% { transform: scaleY(1); }
}

/* ── 처리 중인 단추는 흐리게 하지 않습니다 — DS .btn.is-loading 과 같은 규칙을 아이콘 단추(.iconbtn)에도 ──
   (콘솔 .iconbtn[disabled] 는 .4 로 흐려져 그 안의 회전 원까지 흐려졌습니다) */
.iconbtn.is-loading[disabled] { opacity: 1; cursor: default; }

/* ── 대기 글 · 결과 글 · 영역 가운데 ── */
.cog-busy {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted-foreground);
}
.cog-result {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  line-height: 1.5;
  animation: cog-ld-fade .25s ease-out;
}
.cog-result.is-ok { color: var(--success-text); }
.cog-result.is-fail { color: var(--error-text); }
.cog-region {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  text-align: center;
}
.cog-region__t { font-size: 14px; font-weight: 500; color: var(--foreground); }

/* ── 움직임 줄이기 — 돌지 않고 서 있되 '기다리는 중'은 보입니다 ── */
@media (prefers-reduced-motion: reduce) {
  .cog-ring__arc { animation: none; stroke-dasharray: 26 100; }
  .cog-tick line { animation: none; opacity: .45; }
  .cog-wave > i { animation: none; transform: scaleY(.6); }
  .cog-mark .ring, .cog-mark .glyph { animation: none; stroke-dashoffset: 0; }
  .cog-mark .dot { animation: none; opacity: 1; }
}

/* ━━ 에이전트 아이콘 · 아이콘 고르개 (앱 공용/스타일/에이전트아이콘.css 통째로, 부품: 앱 공용/부품/AgentAvatar.vue · AgentLookField.vue · 공용/에이전트아이콘.ts) ━━ */
/* ══ 에이전트 아이콘 — 두 앱 한 벌 (2026-10-28) ══
   부품: 공용/부품/AgentAvatar.vue(작은 자리 · 카드 배너) · AgentLookField.vue(고르개). 표는 공용/에이전트아이콘.ts.
   ⚠ 두 앱이 함께 읽습니다(콘솔 ui.css 는 이용자 화면이 안 읽어서 따로 둡니다 — 채팅.css 와 같은 사정).
   색 = 카테고리 토큰(--agc 로 받음) */

/* 아이콘 — **면 없는 단색 도형 하나, 두 앱 1:1** (2026-10-28 사용자 지시: "이용자 인터페이스에서는 아이콘 뒤에 배경이 없는데 왜 에이전트 목록에는 있어 ·
   공통 부품이야 · 둘이 1:1 매칭"). 같은 날 이용자 카드만 면을 걷어(에이전트목록.css · "담백한 B2B SaaS · 배경은 없고") 콘솔과 갈렸던 것을 여기로 올렸습니다.
   ⛔ 화면 CSS 에서 면 · 크기 · 색을 다시 정하지 마세요 — 자리(여백)만. 크기는 AgentAvatar 의 size(= 도형 크기, 기본 20).
   · 라이트: 면 없이 흰 바탕에 맨 선이라 노랑 · 분홍 · 에메랄드가 3:1(비텍스트 대비)에 못 미쳐 글자색을 20% 섞어 한 단 짙게(이용자 카드 실측 그대로)
   · 다크: 원래 색 그대로(11개 모두 3.6 이상) */
.cog-agav {
  --agc: var(--muted-foreground); --ags: 20px;   /* 부품 지역 변수 — 부품이 인라인으로 겁니다(색 · 크기) */
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: var(--ags, 20px); height: var(--ags, 20px);
  color: color-mix(in srgb, var(--agc) 80%, var(--foreground));
}
.dark .cog-agav { color: var(--agc); }
.cog-agav > svg, .cog-agav > img { width: 100%; height: 100%; }
/* 올린 이미지 아이콘(2026-10-30) — 색을 받지 않고 그림 그대로 · 모서리만 살짝(정사각 사진이 도형 옆에서 튀지 않게) */
.cog-agimg { display: block; flex: none; object-fit: cover; border-radius: 4px; }

/* 이름 앞 아이콘 + 이름 — 한 줄(말줄임은 이름이) */
.cog-agname { display: inline-flex; align-items: center; gap: 8px; min-width: 0; max-width: 100%; }

/* ── 고르개 — 아이콘 버튼 + 팝오버(2026-10-28 · 10-30 노션 꼴로) ──
   ⚠ 팝오버는 콘솔 .pop 한 벌(면 · 테두리 · 그림자 · 자리)을 씁니다 — 그 .pop button 규칙(0,1,1)이 견본 · 칸을 덮지 않게 여기 선택자는 둘씩 겁니다 */
.cog-aglook { position: relative; display: inline-flex; flex: none; }
/* 버튼 = 지금 모양(면 없는 색 도형 — 위 .cog-agav 와 같은 색 규칙). 높이 32 = 옆 이름 칸과 같은 줄 */
.cog-aglook__btn {
  display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0;
  border: 1px solid var(--border); border-radius: var(--r-el); cursor: pointer;
  color: color-mix(in srgb, var(--agc) 80%, var(--foreground)); background: var(--card);
}
.dark .cog-aglook__btn { color: var(--agc); }
.cog-aglook__btn > svg, .cog-aglook__btn > img { width: 18px; height: 18px; }
.cog-aglook__btn:hover, .cog-aglook__btn[aria-expanded="true"] { border-color: var(--nav-active); }
.cog-aglook__btn:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
/* inline — 표 줄의 아이콘 자리 그대로(테두리 없음 · 그림 size + 사방 4) · 노션 페이지 아이콘처럼 올리면 옅은 면 (2026-10-30) */
.cog-aglook__btn.cog-aglook__btn--inline { width: calc(var(--ags, 16px) + 8px); height: calc(var(--ags, 16px) + 8px); margin: -4px; border: 0; background: none; }
.cog-aglook__btn.cog-aglook__btn--inline > svg, .cog-aglook__btn.cog-aglook__btn--inline > img { width: var(--ags, 16px); height: var(--ags, 16px); }
.cog-aglook__btn.cog-aglook__btn--inline:hover, .cog-aglook__btn.cog-aglook__btn--inline[aria-expanded="true"] { background: var(--overlay-hover); }
/* 팝오버 = 노션 아이콘 고르개 꼴(2026-10-30) — 탭 머리 · [필터 | 무작위 | 색] · 구르는 칸(분류 머리 + 격자 열 칸) · 아래 분류 바로가기 */
.pop.cog-aglook__pop {
  z-index: 95; padding: 0; display: flex; flex-direction: column;
  width: calc(10 * 32px + 9 * 2px + 24px + 2px);
}
/* 탭 [아이콘 | 업로드] + 오른쪽 끝 [초기화] — 콘솔 밑줄 탭(.tabs) 그대로. .pop button 의 폭 · 면 · 라운드만 벗깁니다 */
.cog-aglook__pop .cog-aglook__tabs { padding: 0 6px; margin: 0; align-items: center; }
.cog-aglook__pop .cog-aglook__tabs button { width: auto; height: 40px; padding: 0 10px; border-radius: 0; font-size: 14px; }
.cog-aglook__pop .cog-aglook__tabs button:hover { background: none; }
.cog-aglook__pop .cog-aglook__tabs .cog-aglook__reset { margin-left: auto; height: 28px; margin-bottom: 0; border: 0; border-radius: var(--r-el); font-size: 13px; color: var(--muted-foreground); }
.cog-aglook__pop .cog-aglook__tabs .cog-aglook__reset:hover { background: var(--overlay-hover); color: var(--foreground); }
/* [필터 | 무작위 | 색] */
.cog-aglook__pop .cog-aglook__bar { display: flex; gap: 6px; padding: 12px 12px 8px; }
.cog-aglook__pop .cog-aglook__find {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 6px; height: 32px; padding: 0 8px;
  border: 1px solid var(--border); border-radius: var(--r-el); background: var(--card); color: var(--muted-foreground);
}
.cog-aglook__pop .cog-aglook__find:focus-within { border-color: var(--nav-active); }
.cog-aglook__pop .cog-aglook__find > svg { flex: none; width: 14px; height: 14px; }
.cog-aglook__pop .cog-aglook__find > input { flex: 1; min-width: 0; height: 100%; padding: 0; border: 0; outline: 0; background: none; font-size: 14px; color: var(--foreground); }
.cog-aglook__pop .cog-aglook__tool {
  flex: none; width: 32px; height: 32px; padding: 0; justify-content: center;
  border: 1px solid var(--border); background: var(--card); color: var(--muted-foreground);
}
.cog-aglook__pop .cog-aglook__tool > svg { width: 16px; height: 16px; }
.cog-aglook__pop .cog-aglook__tool:hover { background: var(--overlay-hover); color: var(--foreground); }
.cog-aglook__pop .cog-aglook__tool[aria-expanded="true"] { border-color: var(--nav-active); }
.cog-aglook__pop .cog-aglook__dot { width: 16px; height: 16px; border-radius: 50%; background: var(--agc); }
.cog-aglook__pop .cog-aglook__colors { display: flex; gap: 10px; padding: 4px 14px 10px; }
/* 구르는 칸 — 분류 머리(12/600 회색) + 격자 · 머리의 offsetTop 이 이 칸 기준이 되도록 relative */
.cog-aglook__pop .cog-aglook__scroll { position: relative; height: 264px; overflow-y: auto; padding: 0 12px 8px; }
.cog-aglook__pop .cog-aglook__h { margin: 0; padding: 8px 2px 6px; font-size: 12px; font-weight: 600; color: var(--muted-foreground); }
.cog-aglook__pop .cog-aglook__none { margin: 0; padding: 32px 0; text-align: center; font-size: 13px; color: var(--muted-foreground); }
/* 아래 분류 바로가기 — 지금 보는 분류 = 브랜드 글리프(활성 규칙 한 벌 · 면 없음) */
.cog-aglook__pop .cog-aglook__nav { display: flex; justify-content: space-between; padding: 4px 8px; border-top: 1px solid var(--border); }
.cog-aglook__pop .cog-aglook__nav button { width: 32px; height: 32px; padding: 0; justify-content: center; color: var(--muted-foreground); }
.cog-aglook__pop .cog-aglook__nav button > svg { width: 16px; height: 16px; }
.cog-aglook__pop .cog-aglook__nav button:hover { color: var(--foreground); }
.cog-aglook__pop .cog-aglook__nav button.on { color: var(--nav-active-fg); font-weight: 400; }
/* 업로드 탭 */
.cog-aglook__pop .cog-aglook__up { display: flex; flex-direction: column; gap: 12px; padding: 12px; min-height: 120px; border-radius: var(--r-el); }
.cog-aglook__pop .cog-aglook__up.is-drag { outline: 1px dashed var(--nav-active); outline-offset: -4px; }
.cog-aglook__pop .cog-aglook__upbtn { width: 100%; justify-content: center; font-size: 14px; padding: 0 12px; border: 1px solid var(--border); background: var(--card); height: 32px; }
.cog-aglook__pop .cog-aglook__upbtn:hover { background: var(--overlay-hover); }
.cog-aglook__pop .cog-aglook__uperr { margin: -4px 0 0; }
/* 업로드 안내 — 버튼 바로 밑 12px 회색(노션 '권장 크기' 자리) · 오류가 뜨면 그 자리를 오류가 씀 */
.cog-aglook__pop .cog-aglook__upnote { margin: -4px 0 0; font-size: 12px; line-height: 18px; color: var(--muted-foreground); text-align: center; }
.cog-aglook__pop .cog-aglook__upit { position: relative; display: inline-flex; }
/* 지우기 — 올린 아이콘에 올렸을 때만 오른쪽 위 모서리에 */
.cog-aglook__pop .cog-aglook__updel {
  position: absolute; top: -6px; right: -6px; display: none; align-items: center; justify-content: center;
  width: 16px; height: 16px; padding: 0; border: 1px solid var(--border); border-radius: 50%;
  background: var(--card); color: var(--muted-foreground); cursor: pointer;
}
.cog-aglook__pop .cog-aglook__updel > svg { width: 10px; height: 10px; }
.cog-aglook__pop .cog-aglook__updel:hover { background: var(--card); color: var(--foreground); }
.cog-aglook__pop .cog-aglook__upit:hover .cog-aglook__updel, .cog-aglook__pop .cog-aglook__updel:focus-visible { display: inline-flex; }
.cog-aglook__pop .cog-aglook__sw {
  flex: none; width: 20px; height: 20px; padding: 0; border: 0; border-radius: 50%; cursor: pointer;
  background: var(--agc);
}
/* 고른 색 = 바깥 링(브랜드 — 활성 규칙 한 벌) */
.cog-aglook__pop .cog-aglook__sw[aria-checked="true"] { box-shadow: 0 0 0 2px var(--card), 0 0 0 4px var(--nav-active); }
.cog-aglook__pop .cog-aglook__sw:hover { background: var(--agc); }
.cog-aglook__pop .cog-aglook__sw:focus-visible { outline: 2px solid var(--ring); outline-offset: 3px; }
.cog-aglook__pop .cog-aglook__grid { display: grid; grid-template-columns: repeat(10, 32px); gap: 2px; }
.cog-aglook__pop .cog-aglook__ic {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0; border: 1px solid transparent; border-radius: var(--r-el);
  background: none; color: var(--muted-foreground); cursor: pointer;
}
.cog-aglook__pop .cog-aglook__ic > svg, .cog-aglook__pop .cog-aglook__ic > img { width: 18px; height: 18px; }
.cog-aglook__pop .cog-aglook__ic:hover { background: var(--muted); color: var(--foreground); }
/* 고른 아이콘 = 그 자리에 실제 모양(면 없는 색 도형) + 브랜드 테두리 */
.cog-aglook__pop .cog-aglook__ic[aria-checked="true"] {
  color: color-mix(in srgb, var(--agc) 80%, var(--foreground)); border-color: var(--nav-active);
}
.dark .cog-aglook__pop .cog-aglook__ic[aria-checked="true"] { color: var(--agc); }
.cog-aglook__pop .cog-aglook__ic:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; }

/* 창의 [아이콘 | 이름] 한 줄 — 콘솔 에이전트 추가 · 이름 · 아이콘 · 설명 변경 */
.cog-agnamerow { display: flex; align-items: center; gap: 8px; }
.cog-agnamerow > .input { flex: 1; min-width: 0; }

/* ━━ 서랍 ━━ */
/* 서랍(Drawer) — 오른쪽에서 여는 상세 · 설정 판 (2026-11-06 콘솔 에이전트 평가 .ev-drawer · 이상탐지 상세 검색 .qb 에서 올림).
   .drawer = 딤(--scrim) 위에 뜨는 서랍 — 바깥 · Esc · ✕ 로 닫힘. 흰 패널 안쪽에 8 떠서(위 · 아래 · 오른쪽) 모서리 · 테두리 · 그림자가 컨테이너 값.
   .drawer--inset = 딤 없이 판(panel) 오른쪽에 붙는 서랍 — 조건을 고치는 동안 뒤의 표가 줄어드는 걸 봐야 할 때(상세 검색).
   폭은 --drawer-w (기본 560 · 넓은 내용은 760). 머리 · 본문 · 발은 .drawer__head · __body · __foot */
.drawer { position: fixed; inset: 0; z-index: 80; display: flex; justify-content: flex-end; padding: 8px; background: var(--scrim); }
.drawer__box {
  --drawer-w: 560px;   /* 부품 지역 변수 — 넓히는 쪽이 같은 요소에 다시 겁니다 */
  display: flex; flex-direction: column; width: var(--drawer-w); max-width: calc(100vw - 16px); height: 100%; overflow: hidden;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--r-ct); box-shadow: var(--shadow-drawer);
}
.drawer__head { display: flex; align-items: center; gap: 8px; padding: 14px 12px 14px 20px; border-bottom: 1px solid var(--border); }
.drawer__head h2 { margin: 0; font-size: var(--text-lg); font-weight: var(--weight-semibold); }
.drawer__body { flex: 1; min-height: 0; overflow: auto; padding: 16px 20px 24px; }
.drawer__foot { display: flex; align-items: center; gap: 8px; padding: 12px 20px; border-top: 1px solid var(--border); }
.drawer--inset {
  --drawer-w: 460px;
  position: absolute; top: 0; right: 0; bottom: 0; width: var(--drawer-w); max-width: 100%;
  z-index: 30; display: flex; flex-direction: column;
  background: var(--card); border-left: 1px solid var(--border); box-shadow: var(--shadow-drawer);
}

/* ━━ 문서 탭 띠 — 크롬형 탭(이용자 화면 국회 답변서 · 국회질의 분류 · 영상 수사 · 대화 옆 AI 드라이브 창이 함께 씀) ━━ */
/* ── Document Tab Bar — 파일 단위 전환 ──
   **네 곳이 함께 쓰는 한 벌 — 여기가 원본입니다**(국회 답변서 · 국회질의 분류 · 영상 수사 · 대화 옆 AI 드라이브 창).
   **크롬형 탭** (2026-10-15 사용자 지시: "우리 화면 2단으로 나올때 전에 크롬처럼 생긴걸로 썻잖아. 그게 더 자연스럽고 예쁜듯") —
   옅은 띠 위에 탭이 바닥에 붙어 서고, 켜진 탭은 **본문과 같은 면으로 아래까지 이어지며** 양 아래 모서리가 오목하게 휩니다(필렛).
   2026-10-14 레퍼런스로 바꿨던 둥근 알약 탭(띠 가운데에 뜬 32 · 옅은 면)을 되돌린 것입니다 — 알약은 탭이 본문과 떨어져 떠 있어
   '이 탭의 내용이 바로 아래'라는 이음이 안 읽혔습니다.
   · 그대로 산 것: 탭마다 에이전트 아이콘 타일(AgentTabIcon) · 이름 13px · 띠 높이 44(옆 칸 머리 밑선과 한 줄 — 2026-10-14 "여기 세로폭이 같아야지")
   ⛔ 알약(띠 가운데 · 네 모서리 둥근 · 옅은 면)으로 되돌리지 마세요.
   ⛔ 띠를 본문과 같은 면(--card)으로 두지 마세요 — 켜진 탭이 본문과 이어지는 것이 안 보입니다(10-14 의 '흰 띠' 지시는 알약 탭 때 것).
     띠는 옅은 톤 하나(foreground 6%) — 다크에서는 띠가 본문보다 조금 밝아 켜진 탭이 본문 쪽으로 열려 보입니다(VS Code 다크 탭과 같은 결) */
.doctabs {
  display: flex;
  align-items: center;   /* 탭(36)은 안쪽 높이(36)를 꽉 채워 바닥에 붙고, [+] · 일감 목록 · 창 접기 같은 버튼은 탭 줄 가운데에 섭니다 */
  gap: 4px;
  /* 높이는 밑선까지 44 = 위 8 + 탭 36 — 옆 칸 머리(.cog-sec__pane-h · 비식별화 .cog-dax__docbar)의 밑선과 한 줄로 맞습니다 */
  box-sizing: border-box;
  flex: none;
  height: 44px;
  padding: 8px 10px 0;
  background: color-mix(in srgb, var(--foreground) 6%, var(--card));
  /* ⛔ 띠 밑선(1px)을 두지 마세요 (2026-10-15 사용자 지적: "크롬 창처럼 생겼는데 Stroke 가 약간 자연스럽지 못하게") —
     앞 판은 inset 그림자로 밑선을 그었는데, 켜진 탭의 오목 곡선(필렛)이 시작되는 곳에서 선이 뚝 끊기고 곡선에는 선이 없어
     '선 → 곡선' 이음이 어색했습니다. 크롬도 탭 띠에 밑선이 없습니다 — 띠(옅은 톤)와 본문(--card)의 **면 차이**만으로 켜진 탭이 본문과 이어집니다 */
  overflow-x: auto;
  scrollbar-width: none;
}


.doctab {
  position: relative;
  display: flex;
  align-items: center;
  height: 36px;
  /* 폭 — 크롬처럼 탭은 같은 폭(200)으로 서고, 자리가 모자라면 함께 줄어 96 까지(긴 이름은 말줄임).
     ⚠ 줄어들지 않으면(앞 판 min 160) 탭이 여럿일 때 [+] · 창 접기가 띠 밖으로 밀려 안 보입니다 */
  flex: 0 1 200px;
  min-width: 96px;
  max-width: 240px;
  border: 0;
  border-radius: var(--r-ct) var(--r-ct) 0 0;   /* 위만 둥글게 — 아래는 본문(켜진 탭) · 띠(꺼진 탭)에 붙습니다 */
  background: none;
  color: var(--muted-foreground);
  transition: background-color 0.14s;
}

.doctab:not(.is-active):hover {
  background: var(--overlay-hover);
}

/* 켜진 탭 — 본문과 같은 면(--card)이라 경계 없이 아래로 이어집니다(띠의 밑선도 이 면이 덮음) */
.doctab.is-active {
  background: var(--card);
  color: var(--foreground);
}

/* 켜진 탭의 좌 · 우 아래 필렛 — 본문 면에서 탭으로 이어져 올라온 듯한 오목 곡선(크롬 탭 꼴).
   radial-gradient: 원 안(투명) = 띠 면이 비침 · 원 밖(--card) = 본문과 같은 면.
   ⚠ 경계를 1px 번지게(8.5 → 9.5) 둡니다 — 칼 경계(9 → 9)는 곡선이 계단처럼 깍둑하게 그려졌습니다(2026-10-15 확대 실측) */
.doctab.is-active::before,
.doctab.is-active::after {
  content: '';
  position: absolute;
  bottom: 0;
  width: 9px;
  height: 9px;
  pointer-events: none;
}

.doctab.is-active::before {
  left: -9px;
  background: radial-gradient(circle at 0 0, transparent 8.5px, var(--card) 9.5px);
}

.doctab.is-active::after {
  right: -9px;
  background: radial-gradient(circle at 100% 0, transparent 8.5px, var(--card) 9.5px);
}

.doctab__main {
  flex: 1;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  height: 100%;
  border: 0;
  background: none;
  padding: 0 4px 0 12px;
  font-size: 13px;
  font-weight: 500;
  color: inherit;
  cursor: pointer;
}

.doctab__name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.doctab.is-active .doctab__name {
  font-weight: 600;
}

/* 검토 필요 표시 dot 제거 — 상태는 그리드·접근성 이름으로 전달 (사용자 2026-07-20) */

.doctab__close {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  margin: 0 6px 0 2px;
  border: 0;
  border-radius: var(--r-el);
  background: none;
  color: var(--muted-foreground);
  cursor: pointer;
}

.doctab__close:hover {
  background: var(--overlay-hover);
  color: var(--foreground);
}

.doctab__add {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  margin-left: 2px; /* 띠의 align-items: center 가 탭 줄(36) 가운데에 세움 — 아래 보정이 필요 없습니다 */
  border: 0;
  border-radius: var(--r-el);
  background: none;
  color: var(--muted-foreground);
  cursor: pointer;
}

.doctab__add:hover {
  background: var(--overlay-hover);
  color: var(--foreground);
}

.doctab__main:focus-visible,
.doctab__close:focus-visible,
.doctab__add:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: -2px;
}

/* 자료 탐색 서브 탭 — 검색 아이콘으로 문서 탭과 구분 (사용자 2026-07-14).
   다른 모듈(답변서)에서 참조 — 완전 수록 차원에서 포함 */
.doctab--search .doctab__main svg {
  flex: 0 0 auto;
  color: var(--muted-foreground);
}

.doctab--search.is-active .doctab__main svg {
  color: var(--foreground);
}

/* ━━ 표 안 편집칸 ━━ */
/* 표 안 편집칸 — .cell-input (2026-11-06 이용자 화면 비식별화 .cog-dax__cell · 시큐어노트 .cog-sec__runin 의 공통 바탕을 올림).
   테두리 없는 칸이 표 칸을 **가득** 채웁니다 — 고칠 수 있다는 것은 칸의 hover 면이, 고치는 중이라는 것은 포커스 링이 말합니다.
   ⛔ 입력칸에 테두리를 다시 그리지 마세요(표 안에서 그 칸만 폼으로 보임). 높이 · 굵기 · 칸 사이 선은 쓰는 표가 정합니다 */
.cell-input {
  width: 100%; min-width: 0; border: 0; border-radius: 0; background: none;
  padding: 0 8px; font: inherit; font-size: var(--text-base); color: var(--foreground); text-align: left;
}
.cell-input:focus { outline: none; }
.cell-input::placeholder { color: var(--muted-foreground); }
.cell-input:disabled { color: var(--muted-foreground); cursor: default; }

/* ━━ 경로 제목줄 ━━ */
/* 경로 제목줄 — DsPageHeader variant="path" (2026-11-06 콘솔 셸 '제목줄 = 경로'에서 올림).
   목록은 제목 한 낱말, 상세는 `메뉴 › … › 지금`이 제목 — 16/600 · 줄 높이 32(버튼 32 와 맞춤) · 안여백 12/20 · 아래 1px 선.
   앞 마디는 흐린 글자(누르면 그 단계) · 끝 마디가 지금 위치(잉크색) · 긴 이름은 끝 마디만 줄임. ⛔ 앞 마디 크기를 달리하지 마세요(제목 + 곁말로 읽힘) */
.ptitle--path { padding: 12px 20px; border-bottom: 1px solid var(--border); }
.ptitle--path h1 { min-width: 0; font-size: var(--text-lg); line-height: 32px; letter-spacing: -.01em; }
.tcrumb { flex: 1; min-width: 0; display: flex; align-items: center; gap: 6px; margin: 0;
  font-size: var(--text-lg); font-weight: var(--weight-semibold); line-height: 32px; letter-spacing: -.01em; color: var(--foreground); }
.tcrumb__up {
  flex: none; padding: 0; border: 0; background: none; font: inherit; font-weight: var(--weight-medium); color: var(--muted-foreground);
  cursor: pointer; border-radius: var(--r-el); white-space: nowrap;
}
.tcrumb__up:hover { color: var(--foreground); text-decoration: underline; text-underline-offset: 3px; }
.tcrumb__up:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.tcrumb__sep { flex: none; font-weight: var(--weight-normal); color: var(--muted-foreground); }
.tcrumb__cur { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }


/* ── 입력칸 안 꾸밈 버튼 — .input-adorn (비밀번호 보기 등, 2026-11-06 로그인 .login__eye 에서 올림) ──
   칸을 position:relative 그릇에 담고 그 안 오른쪽 끝에 정사각 버튼. 폭 = --input-adorn(기본 32 = 칸 높이).
   칸의 padding-right 도 같은 값만큼 비워 두세요(글자가 버튼 밑으로 안 들어가게). 버튼에 aria-label 필수 */
/* 그릇 — 칸을 담고 오른쪽 끝 버튼 자리만큼 칸의 글자를 비웁니다. 버튼 폭은 이 그릇의 --input-adorn */
.input-wrap { --input-adorn: 32px; position: relative; display: flex; }
.input-wrap > .input { flex: 1 1 auto; min-width: 0; padding-right: var(--input-adorn); }
.input-adorn {
  all: unset; position: absolute; inset-block: 0; right: 0;
  display: flex; align-items: center; justify-content: center;
  width: var(--input-adorn, 32px); color: var(--muted-foreground); cursor: pointer; border-radius: var(--r-el);
}
.input-adorn:hover { color: var(--foreground); }
.input-adorn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
