/* ============================================================
   포털관리 모듈 전용 스타일.

   모듈 안에서만 쓰이므로 Blazor Common 으로 올리지 않는다.
   ============================================================ */

.ad-subhead {
  margin: 1.5rem 0 0.75rem;
  font-size: var(--jsini-fs-lg);
  font-weight: 700;
}

/* 값이 숫자가 아니라 글자인 통계 타일. 기본 크기(1.5rem)로 두면
   저장소 경로 같은 긴 값이 두 줄로 넘쳐 타일 높이가 들쭉날쭉해진다. */
.ad-stat--text {
  font-size: var(--jsini-fs-lg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ad-stamp {
  font-size: var(--jsini-fs-sm);
  color: var(--jsini-text-muted);
}

/* 도장 몇 개를 한 줄에 늘어놓는 자리(어디서 · 언제 읽은 것인가).
   `jsini-toolbar` 를 쓰지 않는 이유는 그것이 **조건줄**이라는 뜻이라서다 —
   조건이 아닌 것을 거기 담으면 조건이 어디 있는지가 흐려진다. */
.ad-stamps {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.75rem;
  margin-bottom: 0.6rem;
}

/* ── 조직도 ─────────────────────────────────────────────── */

/* 캔버스와 미소속 목록이 나란히 선다. 목록을 켜면 오른쪽에 붙고 캔버스가
   남은 폭을 쓴다 — `min-width: 0` 이 없으면 캔버스가 줄지 않아 목록이
   화면 밖으로 밀려난다. */
.ad-orgc-wrap {
  display: flex;
  flex: 1;
  min-height: 0;
  gap: 1rem;
}

/* 잘라 내는 바깥 상자. 안쪽 판은 JS 가 옮기고 키운다(org-chart.js).
   `touch-action: none` 이 없으면 손가락 끌기를 브라우저가 먼저 가져간다. */
.ad-orgc {
  position: relative;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  border: 1px solid var(--jsini-border);
  border-radius: 0.6rem;
  background: var(--jsini-bg);
  cursor: grab;
  touch-action: none;
  user-select: none;
}

.ad-orgc--grabbing { cursor: grabbing; }

.ad-orgc__stage {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
}

/* 연결선은 판의 크기를 정하는 자리이기도 하다(노드는 절대 배치라 크기에
   보태지 않는다). 그래서 이것만 흐름 안에 둔다. */
.ad-orgc__links {
  display: block;
  overflow: visible;
  pointer-events: none;
}

.ad-orgc__links path {
  fill: none;
  stroke: var(--jsini-border);
  stroke-width: 2;
  stroke-dasharray: 3 3;
}

.ad-orgc__legend,
.ad-orgc__zoom {
  position: absolute;
  top: 0.6rem;
  z-index: 2;
}

.ad-orgc__legend {
  left: 0.6rem;
  max-width: 24rem;
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--jsini-border);
  border-radius: 0.4rem;
  background: var(--jsini-surface);
  font-size: var(--jsini-fs-xs);
  color: var(--jsini-text-muted);
  pointer-events: none;
}

.ad-orgc__zoom {
  right: 0.6rem;
  display: flex;
  gap: 0.25rem;
}

.ad-orgc__zoom button {
  width: 1.9rem;
  height: 1.9rem;
  border: 1px solid var(--jsini-border);
  border-radius: 0.4rem;
  background: var(--jsini-surface);
  color: var(--jsini-text);
  cursor: pointer;
}

.ad-orgc__busy {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--jsini-fs-sm);
  color: var(--jsini-text-muted);
  pointer-events: none;
}

/* ── 노드 카드 ──────────────────────────────────────────
   180×50 이다. 이 값이 배치 계산(OrgChart.razor)과 맞물려 있어
   여기서만 바꾸면 연결선이 카드에 닿지 않는다. */

.ad-orgc__node {
  position: absolute;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  width: 180px;
  height: 50px;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--jsini-border);
  border-radius: 0.5rem;
  background: var(--jsini-surface);
  cursor: move;
}

.ad-orgc__node--company {
  border-width: 2px;
  border-color: var(--jsini-accent);
  cursor: default;
}

.ad-orgc__node--dept { border-left: 3px solid var(--jsini-accent); }

.ad-orgc__node--user { background: var(--jsini-bg); }

.ad-orgc__icon,
.ad-orgc__face {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--jsini-theme-hover);
  font-size: var(--jsini-fs-2xs);
  overflow: hidden;
}

.ad-orgc__face img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.ad-orgc__text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.ad-orgc__name,
.ad-orgc__sub {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ad-orgc__name { font-weight: 600; }

.ad-orgc__sub {
  font-size: var(--jsini-fs-xs);
  color: var(--jsini-text-muted);
}

.ad-orgc__btn {
  flex-shrink: 0;
  padding: 0.1rem 0.25rem;
  border: 0;
  border-radius: 0.3rem;
  background: transparent;
  color: var(--jsini-accent-text);
  cursor: pointer;
}

.ad-orgc__btn:hover { background: var(--jsini-theme-hover); }

/* ── 미소속 사용자 ──────────────────────────────────────── */

.ad-orgc__side {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  width: 17rem;
  overflow: hidden;
  border: 1px solid var(--jsini-border);
  border-radius: 0.6rem;
}

.ad-orgc__side-head {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.4rem 0.6rem;
  border-bottom: 1px solid var(--jsini-border);
}

.ad-orgc__side-title { font-weight: 600; }

.ad-orgc__side-hint {
  padding: 0.4rem 0.6rem;
  border-bottom: 1px solid var(--jsini-border);
  font-size: var(--jsini-fs-xs);
  color: var(--jsini-text-muted);
}

.ad-orgc__side-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0.4rem;
}

.ad-orgc__side-empty {
  padding: 1.5rem 0;
  text-align: center;
  font-size: var(--jsini-fs-sm);
  color: var(--jsini-text-muted);
}

.ad-orgc__row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.4rem;
  border: 1px solid var(--jsini-border);
  border-radius: 0.4rem;
  cursor: move;
}

.ad-orgc__row + .ad-orgc__row { margin-top: 0.35rem; }

/* ── 이동 대상 고르기 ───────────────────────────────────── */

.ad-orgc__picks {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  max-height: 20rem;
  overflow-y: auto;
  margin: 0.5rem 0;
  padding: 0.35rem;
  border: 1px solid var(--jsini-border);
  border-radius: 0.4rem;
}

.ad-orgc__pick {
  text-align: left;
  padding: 0.35rem 0.5rem;
  border: 1px solid transparent;
  border-radius: 0.35rem;
  background: transparent;
  color: var(--jsini-text);
  cursor: pointer;
}

.ad-orgc__pick:hover { background: var(--jsini-theme-hover); }

/* 고른 것은 면으로 칠한다. 테두리만 두르면 그 줄만 키가 커져 목록이 들썩인다. */
.ad-orgc__pick--on {
  background: var(--jsini-theme-picked);
  font-weight: 600;
}

/* ── 설정 줄 ────────────────────────────────────────────── */

.ad-settings { margin-bottom: 1rem; }

.ad-setting {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--jsini-border);
}

.ad-setting__name { font-weight: 600; }

.ad-setting__desc {
  margin-top: 0.15rem;
  font-size: var(--jsini-fs-sm);
  color: var(--jsini-text-muted);
}

/* ── 편집 폼 ─────────────────────────────────────────────

   팝업 안의 DxFormLayout. 칸 사이가 붙어 있어 답답해 보여서 조금 벌린다.
   ---------------------------------------------------------- */

.ad-editform {
  --dxbl-form-layout-item-spacing-y: 0.4rem;
}

/* 그리드가 띄우는 편집 창. `CommPopup` 은 `Width` 로 받지만 그리드 쪽 창은
   DevExpress 가 직접 띄우므로 클래스로 준다(`PopupEditFormCssClass`).

   **`width` 를 주지 않고 상한 변수를 덮는다.** DevExpress 규칙이
   `width: var(--dxbl-popup-width)`(=100%)에 `max-width: var(--dxbl-popup-max-width)`
   (=500px)로 묶는 모양이라, 폭을 값으로 주면 좁은 화면에서 창이 화면 밖으로
   나간다. 상한만 올리면 넓은 화면에서는 720px, 좁은 화면에서는 그 100% 가
   이겨 저절로 접힌다.

   선택자를 DevExpress 것과 같은 모양으로 적는 이유는 **자릿수** 다 —
   저쪽이 `.dxbl-modal > .dxbl-modal-root > .dxbl-popup`(0,3,0)이라
   `.ad-editpopup` 한 마디로는 진다. */
.dxbl-modal > .dxbl-modal-root > .dxbl-popup.ad-editpopup {
  --dxbl-popup-max-width: 720px;
}

/* 날짜 칸이 `ad-checks` 안에 네모들과 나란히 설 때. flex 줄에서는 편집기가
   제 폭을 잃고 「비…」 처럼 찌그러지므로 여기서 못을 박는다. */
.ad-editform__date {
  flex: 0 0 auto;
  width: 11rem;
}

/* 칸 아래 붙는 한 줄 설명. 네모 옆에 길게 달면 두 줄로 접혀 다음 칸의
   표제와 나란해진다 — 아래로 내리고 살짝 띄운다. */
.ad-editform .jsini-hint {
  margin: 0.25rem 0 0;
}

/* ── AI 코드 추천 (AiCodeSuggest) ─────────────────────────

   등록 창의 이름 칸 아래에 한 줄로 붙는다. 폼 칸이 아니라 **거들어 주는
   줄**이라, 눈에 띄되 입력 칸처럼 보이지는 않아야 한다.
   ---------------------------------------------------------- */

.ad-ai-suggest {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin: 0.15rem 0 0.35rem;
  min-height: 1.9rem;
}

.ad-ai-suggest__hint {
  color: var(--jsini-text-muted);
}

/* 한 칸에 체크박스를 여럿 늘어놓는 자리(메뉴 관리의 「보이기」·「허용 동작」).
   `jsini-inline` 보다 넓게 띄운다 — 이름이 두세 글자라 0.5rem 으로는
   앞 항목의 글자와 뒤 항목의 네모가 한 덩어리로 읽힌다. */
.ad-checks {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: center;
}

/* ── 좌우 분할 (묶음 ↔ 항목) ─────────────────────────────

   공통코드처럼 「왼쪽에서 고르면 오른쪽이 바뀌는」 화면이 쓴다.
   좁은 화면에서는 위아래로 쌓인다 — 380px 짜리 판 둘을 나란히 두면
   둘 다 못 읽는다.
   ---------------------------------------------------------- */

.ad-split {
  display: grid;
  grid-template-columns: minmax(320px, 26%) 1fr;
  gap: 1rem;
  align-items: start;
}

@media (max-width: 991px) {
  .ad-split { grid-template-columns: 1fr; }
}

/* 「내 이동경로」의 목록 판은 조금 넓다. 기본 폭(26%)에서는 칸 넷 중
   **머문시간이 통째로 잘려 나간다** — 그 값이 이 화면을 여는 까닭인데
   가로로 굴려야 보였다. 「위치 지도」는 칸이 셋이라 기본 폭으로 충분하다. */
@media (min-width: 992px) {
  .ad-split--track { grid-template-columns: minmax(420px, 38%) 1fr; }
}

.ad-split__side,
.ad-split__main {
  min-width: 0;
}

/* 자료 영역이 둘 이상일 때 사이를 벌리는 공통 규칙(app.css 의
   `.commcont + .commcont`)은 **위아래로 쌓인** 경우를 본 것이다. 좌우로
   앉히면 오른쪽 판만 그만큼 내려앉아 두 판의 머리 줄이 어긋난다.
   여기서 사이를 벌리는 일은 격자의 `gap` 이 한다. */
.ad-split > .commcont + .commcont {
  margin-top: 0;
}

/* ── 분할이 본문 높이를 채운다 ────────────────────────────

   공통 규칙(app.css)은 `.commcont` 가 셸 본문의 **직속 자식**일 때만 높이를
   채운다. 좌우 분할에서는 판 둘이 `.ad-split` 안에 들어 있어 그 규칙이 안
   걸리므로, 같은 일을 이 분할에 대해 한 번 더 적는다. 안 적으면
   `.commcont__body > .commgrd > .dxbl-grid` 가 높이 제한을 풀어 놓은 채로
   남아서 표가 자료만큼 늘어나고 쪽이 통째로 구른다.

   좁은 화면은 건드리지 않는다. 거기서는 판이 위아래로 쌓이므로 남은 높이를
   둘이 나눠 가지면 둘 다 못 읽는다 — 그때는 쪽이 구르는 것이 맞다.
   ---------------------------------------------------------- */

@media (min-width: 992px) {
  .jsini-shell__content:has(> .ad-split) {
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }

  .jsini-shell__content > .ad-split {
    flex: 1;
    min-height: 0;

    /* 채울 때는 위로 붙이지 않는다. `align-items: start` 로 두면 판이
       내용만큼만 크고 아래가 빈다. */
    align-items: stretch;

    /* **줄 높이를 내용이 정하지 않게 한다.** 격자 줄의 기본은 `auto` 라서,
       분할의 높이를 정해 줘도 줄이 자료만큼 늘어나 그대로 넘친다.
       `minmax(0, 1fr)` 이어야 남은 높이가 상한이 된다. */
    grid-template-rows: minmax(0, 1fr);
  }

  /* 격자 자식은 `min-height: auto` 라 내용보다 작아지지 않는다. 풀어 주지
     않으면 위의 상한이 이 판에서 다시 무너진다. */
  .ad-split > .commcont {
    min-height: 0;
  }

  .ad-split > .commcont > .commcont__body {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }

  /* 나무(`DxTreeList`)는 `CommGrd` 로 감싸이지 않아 app.css 의 표 규칙이
     걸리지 않는다. 여기서 같은 일을 해 준다 — 안 해 주면 자료만큼 늘어나
     판을 넘고, 본문이 `overflow: hidden` 이라 **넘은 부분이 잘려** 아래 띠를
     누를 수 없다. 실제로 그렇게 잘렸다(아래 띠가 화면 밖 1997px 자리). */
  .ad-split > .commcont > .commcont__body > .dxbl-grid {
    flex: 1;
    min-height: 0;
  }
}

/* ── 구역 제목 ───────────────────────────────────────────

   한 화면에 표가 둘 이상일 때 그 사이를 가른다. `<h2>` 를 그냥 두면
   화면 제목(`PageHeading`)과 크기가 비슷해 어느 쪽이 위인지 헷갈린다.
   ---------------------------------------------------------- */

.ad-section {
  margin: 1.4rem 0 0.5rem;
  font-size: var(--jsini-fs-lg);
  font-weight: 600;
}

.ad-section:first-of-type {
  margin-top: 0.6rem;
}

/* ── 메뉴 관리 ───────────────────────────────────────────── */

/* 중지된 메뉴는 옅게. 지운 것이 아니라 꺼 둔 것이라 목록에는 남는다. */
.ad-menu--off {
  opacity: 0.5;
  text-decoration: line-through;
}

.ad-menu__perm,
.ad-menu__trail {
  font-size: var(--jsini-fs-xs);
}

.ad-menu__perm {
  opacity: 0.75;
}

/* ── 배포 진행 기록 ──────────────────────────────────────

   스크립트가 뱉은 줄들이다. **등폭 글꼴**이라야 읽힌다 — 진행 막대와
   자릿수를 맞춘 출력이 섞여 있다.
   ---------------------------------------------------------- */

.ad-runlog {
  max-height: 46vh;
  overflow: auto;
  padding: 0.6rem 0.75rem;
  background: var(--jsini-surface);
  border: 1px solid var(--jsini-border);
  border-radius: 0.25rem;
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: var(--jsini-fs-xs);
  line-height: 1.55;
}

.ad-runlog__line {
  display: flex;
  gap: 0.5rem;
  white-space: pre-wrap;
  word-break: break-all;
}

.ad-runlog__at {
  flex: 0 0 auto;
  opacity: 0.5;
}

.ad-runlog__step {
  flex: 0 0 auto;
  opacity: 0.8;
  font-weight: 600;
}

.ad-runlog__msg {
  flex: 1;
  min-width: 0;
}

/* 색은 테두리 없이 글자만 바꾼다. 배경을 칠하면 테마마다 대비가 달라진다. */
.ad-runlog__line--warn .ad-runlog__msg { color: #b8860b; }
.ad-runlog__line--error .ad-runlog__msg { color: #c0392b; }

/* ── 끌어서 나누는 좌우 분할 (`ad-vsplit`) ────────────────

   `ad-split`(격자)과 무엇이 다른가 — **폭을 사용자가 정한다.** 격자 쪽은
   26% 로 못 박혀 있어서, 왼쪽에 들어오는 글자 길이가 사람마다 다른 화면
   (사람롤의 이름·부서·회사)에서는 모자라거나 남는다.

   셸의 사이드바와 **같은 부품**이다(`DxSplitter`). 그래서 치수와 규칙도
   그쪽에서 정한 것을 그대로 따른다 — 자세한 이유는 app.css 의
   `.jsini-shell__split` 머리말에 있고, 여기서는 그중 이 화면에 필요한
   것만 다시 적는다.

   [높이를 부모가 정해야 한다]

   DxSplitter 는 자기 높이를 부모에게서 받는다. 안 주면 판이 통째로
   안 보인다 — 메뉴롤의 나무가 한 줄만 그려지던 것과 같은 종류의,
   **오류가 안 나는 고장**이다.
   ---------------------------------------------------------- */

@media (width >= 992px) {
  .jsini-shell__content:has(> .ad-vsplit) {
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }

  .jsini-shell__content > .ad-vsplit {
    flex: 1;
    min-height: 0;
  }
}

/* 판 안쪽 여백을 걷어낸다. 규칙과 다투지 않고 **그 규칙이 읽는 변수**를
   덮는다 — 셸에서와 같은 방법이고, 크기 모드마다 값이 달라 자릿수를
   넘겨야 세 모드에서 모두 걸린다. */
.jsini-shell .ad-vsplit.dxbl-splitter {
  --dxbl-splitter-pane-padding-x: 0;
  --dxbl-splitter-pane-padding-y: 0;
}

.ad-vsplit > .dxbl-splitter-pane > .dxbl-splitter-pane-content {
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* 판 하나. **자기 안에서 구른다** — 판 밖으로 넘기면 셸 본문이
   `overflow: hidden` 이라 잘린다. */
.ad-vsplit__pane {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* 구분선에 내용이 닿지 않게 한다. 붙어 있으면 어느 쪽 글자인지 눈이 헷갈린다. */
.ad-vsplit__pane--left { padding-right: 0.8rem; }

/* 오른쪽은 **스스로 구르지 않는다.** 안의 두 칸이 각자 구른다 —
   판이 구르면 칸 제목까지 함께 올라가 무엇을 보고 있는지 잃는다. */
.ad-vsplit__pane--right { padding-left: 0.8rem; overflow: hidden; }

/* 왼쪽은 **제목을 붙잡고 목록만 구른다.** 통째로 구르면 「사람 (43)」이
   올라가 버려 지금 무엇을 보고 있는지 잃는다. */
.ad-vsplit__pane--left { overflow: hidden; }

/* ── 판 자신이 `CommCont` 인 경우 (알림관리) ───────────────

   사람롤·역할 관리는 판에 맨 `<section>` 을 세우고 제목을 직접 적는다.
   알림관리는 판이 곧 자료 영역이라 `CommCont` 를 그대로 판으로 쓴다 —
   제목 줄·건수·단추를 다른 화면과 같은 모양으로 얻는다(헬프데스크의
   `hd-vsplit` 이 같은 선택을 했다).

   그러면 **머리줄과 몸통이 한 겹 더 들어간다.** 위 규칙들은 판의 직속
   자식을 겨누므로 몸통(`.commcont__body`)에는 안 걸린다 — 안 걸어 주면
   몸통이 제 내용만큼만 커서 안의 목록이 판 높이를 못 받는다.
   ---------------------------------------------------------- */

.ad-vsplit__pane.commcont > .commcont__body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* 메시지 발송의 받는 사람 표. **높이를 값으로 주지 않는다** — 판이 이미
   정해 주므로 flex 에 맡긴다. 값으로 주면 아래 띠(쪽 넘김·엑셀)가 판 밖으로
   밀려나고, 판이 `overflow: hidden` 이라 그 단추가 아예 사라진다
   (사람롤에서 두 번 밟은 것과 같다). */
.ad-vsplit__pane--left > .commgrd {
  flex: 1;
  min-height: 0;
}

/* ── 그런데 **표 자신도 판을 채워야 한다** ────────────────

   위 규칙은 `.commgrd`(감싸개)만 판 높이로 늘린다. 그 안의 `.dxbl-grid` 는
   app.css 가 걸어 둔 `max-height: 58vh` 에 묶여 **자기 내용만큼만** 크다.

   그 둘이 어긋나면 **아래 띠의 아이콘이 표 밖으로 떨어진다.** 띠는
   `position: absolute; bottom: 0` 으로 감싸개 바닥에 붙는데(app.css 의
   「아이콘을 페이저 줄에 얹는다」), 표가 그보다 위에서 끝나면 얹힐 페이저
   줄이 거기 없기 때문이다 — 아이콘만 빈 자리에 덩그러니 남고 쪽·건수는
   저 위 표 안에 있다. 메시지 발송과 역할 관리에서 실제로 그렇게 보였다.

   `CommCont` 안에 든 표는 app.css 가 같은 일을 이미 해 준다
   (`.commcont__body > .commgrd > .dxbl-grid`). 여기는 판이 자료 영역을
   대신하는 자리라 그 규칙이 안 걸려서, 같은 것을 이 판에 대해 적는다.

   **넓은 화면에서만 건다.** 좁은 화면에서는 판이 `display: block` 으로
   풀려 높이를 안 주므로(아래 `width < 992px`), 그때 `height: 100%` 는
   기댈 바닥이 없다. 거기서는 `max-height: 58vh` 가 표를 붙잡는 유일한
   것이라 그대로 둔다.
   ---------------------------------------------------------- */
@media (width >= 992px) {
  .ad-vsplit__pane--left > .commgrd > .dxbl-grid {
    height: 100%;
    max-height: none;
  }
}

.ad-vsplit__pane--left > .ad-targets {
  flex: 1;
  min-height: 0;

  /* 판이 이미 높이를 정해 준다. 60vh 를 그대로 두면 판보다 길어져
     스크롤이 두 겹이 된다. */
  max-height: none;
}

/* ── 사람롤: 오른쪽 판의 가로 두 칸 (권한 · 메뉴) ─────────

   **탭이 아니라 나란히 둔다.** 역할을 걸고 나서 무엇이 열렸는지 확인하는
   자리라, 탭으로 가르면 그 확인이 탭 왕복이 된다.

   위아래로 쌓지 않는 이유는 둘 다 길어서다 — 역할 열둘과 메뉴 백 줄을
   쌓으면 둘 다 반쪽만 보인다. **칸마다 자기 안에서 구른다.**
   ---------------------------------------------------------- */

.ad-userrole {
  flex: 1;
  min-height: 0;
  display: flex;
  gap: 1rem;
}

.ad-userrole__col {
  flex: 1 1 0;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;

  /* 제목은 붙박이, 내용만 구른다 — 통째로 구르면 「권한 (1)」이 올라가
     지금 무엇을 보고 있는지 잃는다. */
  overflow: hidden;
}

/* 역할 칩 목록. 칸이 정해 준 높이 안에서 구른다. */
.ad-userrole__col > .ad-roles {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  margin-bottom: 0;
}

/* 메뉴 나무. **58vh 어림값을 쓰지 않는다** — 칸이 높이를 정확히 주므로
   그 값을 그대로 두면 칸 바닥과 어긋난다(길면 넘치고 짧으면 빈다).

   **아래 띠가 칸 밖으로 밀려나는 것을 두 번 밟았다.** `max-height: 100%` 도
   `height: 100%` 도 「나무 전체 높이」를 가리켜서, 줄이 120개면 표가 칸을
   꽉 채우고 그 아래 띠(펼치기·엑셀)가 칸 밖으로 나갔다 — 칸이
   `overflow: hidden` 이라 **눌러야 할 단추가 사라졌다.**

   높이를 값으로 주지 않고 **flex 에 맡긴다.** 표는 남은 높이를 차지하고
   (`flex: 1`), 아래 띠는 자기 높이를 지킨다(`--commtree-bar-height` 가
   `min-height` 로 걸려 있다). 그러면 줄 수와 무관하게 늘 칸 안이다. */
.ad-userrole__col > .commtree {
  flex: 1;
  min-height: 0;
}

.ad-userrole__col > .commtree > .dxbl-grid {
  flex: 1;
  min-height: 0;
  height: auto;
  max-height: none;
}

/* 좁은 화면에서는 두 칸을 위아래로 쌓는다. 나란히 두면 한 칸이 200px 이
   되어 메뉴 이름이 다 잘린다. 그때는 쪽이 구르므로 칸이 높이를 묶지 않는다. */
@media (width < 1200px) {
  .ad-userrole {
    display: block;
    overflow-y: auto;
  }

  .ad-userrole__col {
    display: block;
    overflow: visible;
  }

  .ad-userrole__col > .ad-roles { overflow-y: visible; }

  /* 쪽이 구르는 자리라 나무는 다시 어림값(58vh)으로 묶는다. */
  .ad-userrole__col > .commtree > .dxbl-grid {
    height: auto;
    max-height: var(--commtree-max-height);
  }
}

/* 구분선. **끌 수 있다는 것은 해 보기 전에는 보이지 않는다** — 마우스를
   올리면 강조색을 옅게 깔아 알린다(셸 구분선과 같은 뜻이다). */
.ad-vsplit > .dxbl-splitter-separator {
  background: var(--jsini-border);
}

.ad-vsplit > .dxbl-splitter-separator:hover {
  background: color-mix(in srgb, var(--jsini-accent) 55%, transparent);
}

/* 좁은 화면에서는 위아래로 쌓는다.

   **판을 지우지 않고 상자만 없앤다**(`display: contents`). 마크업을 화면
   크기로 갈아 끼우면 경계를 넘을 때마다 고른 대상과 조회 결과가 통째로
   다시 만들어진다 — 셸이 같은 이유로 같은 방법을 쓴다.

   DevExpress 가 판에 인라인으로 넣는 폭·좌표는 상자가 없어지면 아무 일도
   하지 않는다. 구분선은 감춘다 — 끌 자리가 없다. */
@media (width < 992px) {
  .ad-vsplit,
  .ad-vsplit > .dxbl-splitter-pane,
  .ad-vsplit > .dxbl-splitter-pane > .dxbl-splitter-pane-content {
    display: contents;
  }

  .ad-vsplit > .dxbl-splitter-separator { display: none; }

  /* 쪽이 구르는 자리라 판이 스스로 높이를 묶으면 안 된다. */
  .ad-vsplit__pane {
    display: block;
    overflow: visible;
    padding: 0;
  }

  .ad-vsplit__pane--right { overflow: visible; }

  .ad-vsplit__pane--left > .ad-targets { max-height: 60vh; }

  /* 판이 `CommCont` 면 위아래로 쌓일 때 사이를 벌려야 한다. 넓은 화면에서
     그 일을 하던 것(`padding`)이 여기서 걷히고, 공통 규칙
     (`.commcont + .commcont`)은 판 둘이 DOM 형제가 아니라서 안 걸린다. */
  .ad-vsplit__pane.commcont { margin-bottom: 1.4rem; }

  .ad-vsplit__pane.commcont > .commcont__body { display: block; }
}

/* ── 회사 관리: 오른쪽 탭 ───────────────────────────────
   탭 틀은 역할 관리의 `ad-roletabs` 를 그대로 쓴다. 수정 탭은 폼만 구르고
   저장 단추는 제자리다.
   ---------------------------------------------------------- */

.ad-company__head {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.6rem;
  margin-top: 0;
}

.ad-company__body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;

  /* 스크롤 막대가 칸 테두리에 붙지 않게 한다. */
  padding-right: 0.4rem;
}

.ad-company__foot {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--jsini-border);
  margin-top: 0.5rem;
}

@media (width < 992px) {
  .ad-company__body { overflow: visible; padding-right: 0; }
  .ad-company__head { margin-top: 1.5rem; }
}

/* ── 역할 관리: 오른쪽 판의 탭 ────────────────────────────

   **사람롤과 달리 여기는 탭이다.** 그쪽은 권한과 메뉴를 가로로 나란히 두는데,
   여기 메뉴 권한 표는 메뉴명 + 권한 열다섯 칸이라 반 폭에서는 체크박스가
   다 잘린다. 옛 Vue 화면도 같은 이유로 탭이었다.

   탭 줄은 제자리에 두고 **안쪽만 구른다.**
   ---------------------------------------------------------- */

.ad-roletabs {
  flex: 1;
  min-height: 0;

  /* **세로 flex 로 만든다.** DevExpress 는 이 뿌리를 `display: block` 으로
     둔다 — 그러면 아래의 내용 상자가 판 높이와 무관하게 흘러 넘친다. */
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* ── 탭의 내용 상자 둘이 높이를 **안 물려준다** (실제로 밟음) ─────

   증상은 「나무를 펼치면 권한 저장 단추가 화면 밖으로 사라진다」였다.
   재어 보니 끊기는 자리가 분명했다.

   | 상자 | 높이 |
   |---|---|
   | `.ad-roletabs` (탭 뿌리) | 562px ← 판이 정해 준 값 |
   | `.dxbl-tabs-content-panel` | **8579px** ← 여기서 터진다 |
   | `.dxbl-tabs-content` | 8579px |
   | `.commtree` | 8569px |

   둘 다 `min-height: auto` 인 flex 항목이라 **안쪽 내용보다 작아지지
   않는다.** 나무가 180줄로 펼쳐지면 그만큼 그대로 늘어나고, 그 아래 띠는
   판 밖으로 밀려난다. 나무에 `flex: 1; min-height: 0` 을 준 것만으로는
   안 되는 이유가 이것이다 — **끊기는 자리가 그보다 위**다.

   그래서 두 상자를 여기서 묶는다. 묶고 나면 나무는 자기 몫의 높이를 받아
   DevExpress 가 안쪽에서 굴린다(칸 머리와 아래 띠는 제자리).
   ---------------------------------------------------------- */

.ad-roletabs > .dxbl-tabs-content-panel {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

.ad-roletabs .dxbl-tabs-content {
  height: 100%;
  min-height: 0;
  overflow: hidden;
}

.ad-roletabs__body {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding-top: 0.6rem;
}

/* 표·나무가 남은 높이를 채운다. **높이를 값으로 주지 않는다** — 아래 띠가
   판 밖으로 밀려난다(사람롤에서 두 번 밟았다). */
.ad-roletabs__body > .commgrd,
.ad-roletabs__body > .commtree {
  flex: 1;
  min-height: 0;
}

.ad-roletabs__body > .commtree > .dxbl-grid,
.ad-roletabs__body > .commgrd > .dxbl-grid {
  flex: 1;
  min-height: 0;
  height: auto;
  max-height: none;
}

/* 메뉴명 칸. 두 줄이다 — 위가 이름(과 일괄 단추), 아래가 코드·주소. */
.ad-rolemenu__name {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  min-width: 0;
  max-width: 100%;
  line-height: 1.3;
}

/* 이름과 일괄 단추. 줄바꿈되면 나무가 들쭉날쭉해진다. */
.ad-rolemenu__head {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  max-width: 100%;
  white-space: nowrap;
}

/* 개발용 이름과 주소. **작고 흐리게** — 눈이 이름 줄에 먼저 가야 한다.
   `ad-stamp` 를 그대로 쓰지 않는 이유는 그것이 본문 크기 기준이라
   이름과 거의 같은 크기로 보이기 때문이다.

   길면 칸 밖으로 비어져 나가지 말고 **…로 잘린다.** 전체 글자는 `title`
   풍선으로 남는다 — 잘린 자리에서 주소를 끝까지 봐야 할 때가 있다. */
.ad-rolemenu__code {
  max-width: 100%;
  font-size: 0.75rem;
  color: var(--jsini-text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ── 사람롤: 대상 목록 ───────────────────────────────────

   사람 마흔몇 명과 회사·부서를 같은 모양으로 늘어놓는다. 표로 두지 않은
   이유는 **고르는 것이 목적**이기 때문이다 — 표는 정렬·필터를 위한 것이고,
   여기서는 한 줄이 곧 단추다.
   ---------------------------------------------------------- */

.ad-targets {
  max-height: 60vh;
  overflow: auto;
  border: 1px solid var(--jsini-border);
  border-radius: 0.25rem;
}

.ad-target {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  padding: 0.45rem 0.6rem;
  border: 0;
  border-bottom: 1px solid var(--jsini-border);
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.ad-target:hover {
  background: color-mix(in srgb, currentcolor 7%, transparent);
}

/* 고른 줄은 왼쪽 선으로 표시한다. 배경을 칠하면 테마마다 대비가 달라진다. */
.ad-target--on {
  box-shadow: inset 3px 0 0 0 var(--jsini-accent);
  font-weight: 600;
}

/* 사진 대신 이름 첫 글자. 사진이 있는 사람이 마흔 중 하나뿐이라
   **없는 쪽이 정상**이고, 없는 것이 오류처럼 보이면 안 된다. */
.ad-target__avatar {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: hsl(var(--seed) 45% 82%);
  color: hsl(var(--seed) 55% 25%);
  font-size: var(--jsini-fs-sm);
  font-weight: 600;
}

.ad-target__body {
  flex: 1;
  min-width: 0;
}

.ad-target__name,
.ad-target__meta {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ad-target__meta {
  font-size: var(--jsini-fs-xs);
  opacity: 0.6;
}

/* ── 사람롤: 역할 목록 ─────────────────────────────────── */

.ad-roles {
  display: grid;
  gap: 0.3rem;
  margin-bottom: 1rem;
}

.ad-role {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--jsini-border);
  border-radius: 0.25rem;
}

/* 걸려 있는 역할만 눈에 들어오게 한다. 안 걸린 것이 훨씬 많다. */
.ad-role--on {
  border-color: var(--jsini-accent);
}

.ad-role__name {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  flex-wrap: wrap;
}

/* ── AI 정밀 확인 칸 ─────────────────────────────────────

   단추 · 결과 배지 · 이유가 한 칸에 들어간다. 이유가 길어서 줄바꿈을
   허용해야 하는데, 그러면 행 높이가 들쭉날쭉해진다 — 이유만 작게 접어 둔다.
   ---------------------------------------------------------- */

.ad-deep {
  display: flex;
  align-items: center;
  gap: 0.3rem;
}

.ad-deep .ad-stamp {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── 메뉴롤 — 오른쪽 판 ───────────────────────────────────

   표 둘과 목록 셋이 쌓인다. 좌우 분할이 판에 남은 높이를 정해 주고 셸 본문이
   `overflow: hidden` 이라, 안쪽에서 굴리지 않으면 **아래가 통째로 잘린다** —
   잘린 자리에 해제 단추가 있어 눌러야 할 것을 누를 수 없게 된다.

   좁은 화면(≤991px)에서는 판이 위아래로 쌓이고 높이 상한이 없으므로 쪽이
   구른다. 그때 이 상자에 높이가 없어 `overflow` 는 아무 일도 하지 않는다.
   ---------------------------------------------------------- */

.ad-menurole {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

/* 닿는 대상 표. **높이를 묶는다** — 한 메뉴에 사람 200명이 걸리는 일이 있고,
   그러면 회사·부서 표가 화면 위로 밀려 올라가 무엇을 보고 있었는지 잃는다.
   옛 Vue 화면이 탭마다 300px 로 못 박아 둔 것과 같은 뜻이다.

   **이름을 `ad-targets` 로 두었다가 밟았다.** 그 이름은 사람롤의 대상 목록이
   이미 쓰고 있어서(위 「사람롤: 대상 목록」), 여기 한 줄을 더한 것만으로
   **다른 화면의 목록 높이가 60vh → 18rem 으로 줄었다.** 오류도 경고도 없고
   고친 화면과 망가진 화면이 달라 알아채기 어렵다 —
   web/CLAUDE.md 의 「이름을 새로 쓸 때 정의부터 찾는다」가 이것이다. */
.ad-reach {
  max-height: 18rem;
  overflow: auto;
}

/* ── 메뉴롤 — 역할 × 권한 표 ──────────────────────────────

   칸이 메뉴마다 달라진다(그 메뉴가 쓰는 권한만 나온다). 칸 수가 정해지지
   않아 그리드로 옮길 대상이 아니라 손으로 쓴 표를 그대로 둔다.
   ---------------------------------------------------------- */

.ad-scroll {
  overflow-x: auto;
}

.ad-perm {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--jsini-fs-sm);
}

.ad-perm th,
.ad-perm td {
  padding: 0.3rem 0.45rem;
  border-bottom: 1px solid var(--jsini-border);
  text-align: left;
  vertical-align: middle;
  white-space: nowrap;
}

.ad-perm thead th {
  font-weight: 600;
  color: var(--jsini-text-muted);
}

/* 권한이 한 줄이라도 걸린 역할. 걸린 것과 안 걸린 것이 같아 보이면
   「누가 쓸 수 있나」를 한눈에 못 읽는다. */
.ad-perm tbody tr.is-on td:first-child {
  font-weight: 700;
}

.ad-via {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  margin-right: 0.4rem;
}

/* 이미지 정리 확인 창의 목록 (D17).
   태그가 수십 개일 수 있어 창이 화면 밖으로 자라지 않게 여기서만 구른다. */
.ad-cleanup-list {
  max-height: 16rem;
  overflow-y: auto;
  margin: 0.5rem 0;
  padding-left: 1.25rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--jsini-fs-sm);
  line-height: 1.6;
}

/* ── 공지 본문 편집기 (DxHtmlEditor) ──────────────────────

   등록·수정 창 안에 들어간다. 창이 폼 높이를 따라 자라므로 편집기에 높이를
   주지 않으면 글이 늘어날 때마다 창이 화면 밖까지 자란다.
   ---------------------------------------------------------- */

.ad-notice-editor {
  height: 20rem;
}

/* 메일 본문 편집기. 공지(20rem)보다 낮다 — 이 화면은 편집기 위에 조건줄과
   안내가, 아래에 첨부와 보내기 단추가 있어서 같은 키로 두면 **「보내기」가
   판 밖으로 나간다**(`ad-send__tabs` 가 탭 안쪽을 굴리므로 잘리지는 않지만,
   보내려면 매번 굴려야 한다). */
.ad-mail-editor {
  height: 14rem;
}

/* 편집 창에서 이미 매달려 있는 첨부 목록.
   이름·크기·빼기가 한 줄이고, 이름이 길면 줄바꿈으로 흘린다. */
.ad-attached {
  margin: 0 0 0.4rem;
  padding: 0;
  list-style: none;
}

.ad-attached li {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.15rem 0;
  font-size: var(--jsini-fs-sm);
}

.ad-attached li a {
  min-width: 0;
  word-break: break-all;
}

/* ── 내 정보 (Profile) ────────────────────────────────────

   왼쪽에 얼굴·이름, 오른쪽에 `DxTabs`(탭 머리가 왼쪽)와 그 내용.

   탭 목록·이름값 목록·고정탭 두 판은 **DevExpress 부품이 그린다.** 여기 남은
   것은 그 부품에 없는 것뿐이다 — 얼굴 동그라미(이미지·카드 부품이 없다),
   사진 격자(갤러리 부품이 없다), 설정 줄.

   좁은 화면에서는 위아래로 쌓는다 — 240px 짜리 판을 옆에 붙여 두면 본문이
   읽을 수 없을 만큼 좁아진다.
   ---------------------------------------------------------- */

.ad-profile {
  display: grid;
  grid-template-columns: minmax(200px, 220px) 1fr;
  gap: 1rem;
  align-items: start;
}

@media (max-width: 1199px) {
  .ad-profile { grid-template-columns: 1fr; }
}

.ad-profile__side,
.ad-profile__body {
  min-width: 0;
}

.ad-profile__side {
  padding: 1rem 0.75rem;
  border: 1px solid var(--jsini-border);
  border-radius: 0.35rem;
  text-align: center;
}

/* 얼굴. 눌러서 사진 관리 탭으로 가므로 단추여야 한다 —
   div 로 두면 키보드로 갈 수 없다. */
.ad-profile__photo {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 96px;
  height: 96px;
  padding: 0;
  overflow: hidden;
  border: 2px dashed var(--jsini-border);
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
}

.ad-profile__photo:hover { border-color: var(--jsini-accent); }

.ad-profile__photo-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* 사진이 없는 계정이 훨씬 많다. 첫 글자를 크게 두어 **비어 보이지 않게** 한다. */
.ad-profile__photo-initial {
  font-size: 2.2rem;
  font-weight: 600;
  color: var(--jsini-text-muted);
}

/* 얹었을 때만 보이는 글자. 안 얹으면 얼굴을 가리지 않는다. */
.ad-profile__photo-veil {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgb(0 0 0 / 45%);
  color: #fff;
  font-size: var(--jsini-fs-xs);
  opacity: 0;
  transition: opacity 0.15s;
}

.ad-profile__photo:hover .ad-profile__photo-veil,
.ad-profile__photo:focus-visible .ad-profile__photo-veil {
  opacity: 1;
}

.ad-profile__who { margin-top: 0.75rem; }

.ad-profile__name {
  font-size: var(--jsini-fs-lg);
  font-weight: 700;
}

.ad-profile__id,
.ad-profile__org {
  font-size: var(--jsini-fs-sm);
  color: var(--jsini-text-muted);
}

/* DxTabs 의 탭 머리 폭을 고정한다.

   안 정하면 가장 긴 이름(「프로필 사진 관리」)이 들어가지 못해 DevExpress 가
   좌우 스크롤 단추를 붙이고, 탭을 옮길 때마다 본문 왼쪽 선이 움직인다.

   **선택자 이름을 눈대중으로 적으면 안 된다** — 떠 있는 화면에서 읽은 것이다.
   `dxbl-tabs-nav` 로 적어 두었던 동안 이 규칙은 아무 일도 하지 않았다.
   26.1 의 구조는 이렇다.

     dxbl-tabs.dxbl-tabs-left
       ├ dxbl-tab-list.dxbl-tabs-tablist   ← 탭 머리
       │   └ dxbl-tab-item.dxbl-tabs-item
       └ div.dxbl-tabs-content-panel       ← 내용

   [768px 아래에서는 걸지 않는다]

   휴대폰에서는 화면이 탭 머리를 **위로** 올린다(`Profile.razor` 의
   `TabsPosition.Top`). 그 모양에 이 규칙이 그대로 걸리면 가로로 누운 머리
   줄의 **높이**가 9.5rem 으로 못 박혀 화면 절반이 탭 이름으로 찬다.

   가르는 조건을 `dxbl-tabs-left` 같은 자리 클래스로 적지 않았다 — 그 클래스가
   우리 `class` 와 **같은 요소에 붙는지** 확인하지 않았고, 아니면 규칙이
   조용히 죽는다(이 구역 위에 적어 둔 `dxbl-tabs-nav` 와 같은 사고다).
   화면 폭으로 가르는 것은 어디에 붙든 옳다. */
@media (min-width: 768px) {
  .ad-profile__tabs .dxbl-tabs-tablist {
    flex: 0 0 9.5rem;
  }

  /* 세로 목록에서는 글자가 왼쪽에 붙어야 읽힌다. 가운데로 두면 길이가
     다른 일곱 줄이 들쭉날쭉해진다. 가로로 누웠을 때는 반대라 — 그때는
     DevExpress 의 가운데 정렬이 맞다 — 이 규칙도 함께 여기 둔다. */
  .ad-profile__tabs .dxbl-tabs-item {
    justify-content: flex-start;
    text-align: left;
  }
}

.ad-profile__tabs .dxbl-tabs-content-panel {
  min-width: 0;
}

/* 얼굴을 감싼 자리. **딱지 하나 때문에 생겼다** — 연필 딱지가 동그라미
   모서리에 반쯤 걸쳐야 하는데, 동그라미 쪽은 사진을 원으로 자르느라
   `overflow: hidden` 이라 그 안에 두면 잘려 나간다. */
.ad-profile__avatar {
  position: relative;
  display: inline-block;
  line-height: 0;
}

/* 휴대폰에서만 선다(아래 좁은 화면 규칙). 데스크톱에는 얹으면 뜨는 덮개가
   이미 있어서 같은 말을 두 번 하는 셈이 된다. */
.ad-profile__photo-badge {
  display: none;
}

/* ── 휴대폰의 항목 목록 ───────────────────────────────────

   탭 머리 대신 서는 일곱 줄이다. 휴대폰의 설정 앱이 모두 쓰는 모양 —
   왼쪽에 그림, 가운데 이름과 지금 값, 오른쪽에 화살표.

   DevExpress 에 이 모양의 부품이 없다. `DxListBox` 는 한 줄에 글자 하나뿐이고
   (두 줄 + 딱지 + 화살표를 넣을 수 없다), `DxAccordion` 은 펼치는 것이라
   주소(`?tab=`)와 짝이 안 맞는다. 얼굴 동그라미·사진 격자와 같은 사유다.

   **색을 하나도 직접 정하지 않는다** — 전부 `--jsini-*` 다. 그 변수는
   DevExpress 가 내보내는 `--dxbl-*`·`--dxds-*` 에서 나오므로(app.css 머리말)
   테마 스물둘 중 무엇을 골라도 따라온다.
   ---------------------------------------------------------- */

.ad-hub {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--jsini-border);
  border-radius: var(--jsini-radius-lg);
  background: var(--jsini-surface-raised);
}

/* 줄 하나가 통째로 과녁이다. 이름에만 걸어 두면 56px 짜리 줄에서 실제로
   눌리는 자리는 글자 폭뿐이고, 오른쪽 절반을 눌러도 아무 일이 없다. */
.ad-hub__row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  min-height: 3.5rem;
  padding: 0.6rem 0.85rem;
  border: 0;
  border-top: 1px solid var(--jsini-border);
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.ad-hub__row:first-child {
  border-top: 0;
}

.ad-hub__row:hover,
.ad-hub__row:focus-visible {
  background: var(--jsini-surface-hover);
}

/* 손가락에는 얹기가 없다. 눌린 동안이라도 표가 나야 「먹었다」를 안다. */
.ad-hub__row:active {
  background: var(--jsini-accent-soft);
}

.ad-hub__icon {
  --jsini-icon-size: 1.25rem;

  flex: 0 0 auto;
  color: var(--jsini-accent-text);
}

.ad-hub__text {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}

.ad-hub__name {
  font-weight: 600;
}

/* 지금 값. 두 줄로 늘어나면 줄 키가 들쭉날쭉해져 목록이 계단처럼 보인다 —
   한 줄로 자른다. 잘린 값은 들어가면 다 보인다. */
.ad-hub__desc {
  overflow: hidden;
  font-size: var(--jsini-fs-sm);
  color: var(--jsini-text-muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ad-hub__badge {
  flex: 0 0 auto;
}

/* 오른쪽을 가리키는 그림이 따로 없다 — `chevron-down` 을 눕힌다. */
.ad-hub__go {
  --jsini-icon-size: 1rem;

  flex: 0 0 auto;
  color: var(--jsini-text-muted);
  transform: rotate(-90deg);
}

/* ── 되돌아가는 줄 ────────────────────────────────────────

   휴대폰에서 탭 머리를 감춘 자리에 선다. 「지금 어디에 서 있는가」와
   「나가는 길」을 이 줄 하나가 말한다.

   **굴러도 붙어 있다.** 보안 설정처럼 긴 조각에서는 바닥까지 내려간 사람이
   목록으로 돌아가려면 다시 끝까지 올려야 한다.
   ---------------------------------------------------------- */

.ad-crumb {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
  padding: 0.25rem 0;
  border-bottom: 1px solid var(--jsini-border);
  background: var(--jsini-bg);
}

.ad-crumb__back {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 0.2rem;
  min-height: 2.75rem;
  padding: 0 0.4rem 0 0;
  border: 0;
  background: transparent;
  color: var(--jsini-accent-text);
  font: inherit;
  cursor: pointer;
}

.ad-crumb__arrow {
  --jsini-icon-size: 1.1rem;

  transform: rotate(90deg);
}

.ad-crumb__here {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 이름·값 줄. `DxFormLayoutItem` 안에 넣은 읽기 전용 값이다 —
   입력기가 아니라 글자라서 세로 가운데를 직접 맞춘다. */
.ad-desc__value {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  min-height: 1.9rem;
  font-size: var(--jsini-fs-sm);
}

/* 주소·식별자는 등폭이라야 자릿수가 읽힌다. */
.ad-desc__mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  word-break: break-all;
}

/* jsini-badge 에는 위험 색이 없다(켬·끔·주의 셋뿐). 만료된 비밀번호는
   「주의」로는 모자라서 여기서만 하나 더 둔다. */
.ad-badge--err {
  border-color: #c0392b;
  color: #c0392b;
}

/* ── 고정탭 관리 ──────────────────────────────────────────

   두 판 모두 `DxListBox` 다. 왼쪽이 고정된 것(순서가 뜻을 가진다),
   오른쪽이 아닌 것(138건이라 검색 칸이 있어야 쓸 수 있다).
   ---------------------------------------------------------- */

.ad-pin {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  align-items: start;
}

@media (max-width: 991px) {
  .ad-pin { grid-template-columns: 1fr; }
}

.ad-pin__pane { min-width: 0; }

.ad-pin__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.4rem;
}

.ad-pin__title { font-weight: 600; }

/* 목록 높이를 못 정하면 DxListBox 가 내용만큼 자란다. 오른쪽은 138건이라
   판이 화면 밖까지 늘어난다 — 목록 안에서 구르게 가둔다. */
.ad-pin__list { height: 22rem; }

/* ── 프로필 사진 관리 ─────────────────────────────────────

   정사각 카드 격자. DevExpress 에 이미지 갤러리 부품이 없어 이 조각만
   우리가 그린다.

   [색을 하나도 직접 정하지 않는다]

   전부 `--jsini-*` 로 만든다. 그 변수는 DevExpress 가 내보내는 `--dxbl-*`
   에서 나오므로(app.css 머리말) 테마 스물둘 중 무엇을 골라도 따라온다.
   덮개의 어두운 면만 예외인데, 그것도 색이 아니라 **검정에 투명도**라
   어느 테마에서든 사진 위 글자가 읽힌다.

   [처음 그림이 촌스러웠던 이유 셋]

   ① 카드마다 **아래에 단추 띠**가 늘 펼쳐져 있었다. 사진 열몇 장을 늘어놓으면
      사진보다 단추가 먼저 보인다. 조작은 얹었을 때만 뜨게 옮겼다.
   ② 파일 이름이 카드 밖 회색 줄로 한 칸을 차지했다. 카드 키가 사진 + 띠 +
      이름으로 세 토막이 나서 격자가 자잘해 보였다. 이름도 덮개로 넣었다.
   ③ 모서리가 0.35rem 이고 테두리가 1px 실선이라 **표 칸처럼** 보였다.
      모서리를 키우고, 테두리 대신 얹었을 때 살짝 뜨는 그림자를 쓴다.

   [조작을 「얹었을 때만」 보이게 하지 않았다]

   한 번 그렇게 만들어 봤는데 되돌렸다. 이유가 둘이다.

   · **손가락에는 얹기가 없다.** `:focus-within` 을 함께 걸면 눌러서 띄울 수는
     있지만, 첫 번째 누름이 「띄우기」로 먹히고 두 번째가 실제 조작이 된다 —
     사진을 지우려고 두 번 누르는 흐름은 설명해야 알 수 있는 것이다.
   · **안 보이는 과녁이 사진 위에 남는다.** `opacity: 0` 은 자리를 그대로
     차지해서, 안 보이는 단추 셋이 사진 아래쪽을 덮고 있게 된다.

   그래서 아래 띠는 **늘 보인다.** 대신 사진 위에 얹힌 띠라(카드 밖의 회색
   줄이 아니라) 카드가 한 덩어리로 읽히고, 이름과 조작이 한 줄에 들어간다.
   ---------------------------------------------------------- */

.ad-photos {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 1rem;
  margin: 1rem 0;
  padding: 0;
  list-style: none;
}

.ad-photo {
  position: relative;
  overflow: hidden;
  border-radius: 0.75rem;
  background: var(--jsini-surface);

  /* 테두리를 그리지 않고 안쪽 실선으로 둔다. 대표 표시(굵은 링)로 바뀔 때
     테두리 두께가 변하면 카드 크기가 1px 씩 달라져 격자가 어긋난다. */
  box-shadow: inset 0 0 0 1px var(--jsini-border);
  transition: box-shadow 0.15s ease-out, transform 0.15s ease-out;
}

.ad-photo:hover,
.ad-photo:focus-within {
  transform: translateY(-2px);
  box-shadow:
    inset 0 0 0 1px var(--jsini-border),
    0 6px 18px rgb(0 0 0 / 14%);
}

/* 대표는 강조색 링으로 표시한다. 안쪽 그림자라 자리를 차지하지 않는다. */
.ad-photo.is-rep {
  box-shadow: inset 0 0 0 2px var(--jsini-accent);
}

.ad-photo.is-rep:hover,
.ad-photo.is-rep:focus-within {
  box-shadow:
    inset 0 0 0 2px var(--jsini-accent),
    0 6px 18px rgb(0 0 0 / 14%);
}

.ad-photo__img {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;

  /* 그림이 없을 때(개발 장비) 카드가 뻥 뚫려 보이지 않게 옅은 면을 깔아 둔다. */
  background: color-mix(in srgb, currentcolor 5%, transparent);
}

.ad-photo__rep {
  position: absolute;
  top: 0.5rem;
  left: 0.5rem;
  z-index: 1;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  background: var(--jsini-accent);
  color: #fff;
  font-size: var(--jsini-fs-2xs);
  font-weight: 700;
  letter-spacing: 0.02em;
}

/* 아래에서 올라오는 덮개. 이름과 조작이 함께 들어간다. */
.ad-photo__veil {
  position: absolute;
  inset: auto 0 0 0;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.4rem 0.5rem;

  /* 사진 위에 글자를 얹는 자리라 위쪽이 서서히 사라지는 면을 쓴다.
     한 색으로 칠하면 잘린 선이 생겨 덧붙인 판처럼 보인다.

     **테마 색이 아니라 검정에 투명도다.** 여기서만 그렇게 한다 — 아래에
     어떤 사진이 올지 모르므로 테마의 면 색을 깔면 밝은 사진 위에서 글자가
     묻힌다. 검정 78% 위의 흰 글자는 사진이 무엇이든 읽힌다. */
  background: linear-gradient(to top, rgb(0 0 0 / 78%), rgb(0 0 0 / 0%));
  color: #fff;
}

.ad-photo__name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--jsini-fs-xs);
}

.ad-photo__acts {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 0.1rem;
}

/* 덮개가 어두우므로 그 안의 링크 단추만 흰 글자로 둔다. 테마의 링크 색은
   밝은 배경을 전제한 값이라 여기서는 읽히지 않는다. 아이콘은 마스크에
   `currentcolor` 를 쓰므로 글자색만 바꾸면 함께 따라온다. */
.ad-photo__acts .dxbl-btn {
  --dxbl-btn-text-link-color: #fff;
  --dxbl-btn-text-link-hover-color: #fff;

  color: #fff;
  padding: 0.15rem 0.25rem;
}

.ad-photo__acts .dxbl-btn:hover {
  background: rgb(255 255 255 / 18%);
  border-radius: 0.35rem;
}

/* ── 내 정보 — 휴대폰 ─────────────────────────────────────

   경계 767px 는 **`Profile.razor` 의 `DxLayoutBreakpoint MaxWidth`** 와 같은
   값이다. 그쪽이 탭 머리를 위로 올리고 표의 칸을 접는 일을 하고, 여기서는
   CSS 로 되는 나머지를 한다 — 둘 중 하나만 고치면 어느 쪽도 아닌 폭이 생긴다.

   [고친 것 다섯]

   ① 얼굴 판이 화면 한 폭을 세로로 먹고 있었다. 96px 동그라미를 가운데 두고
      이름·아이디·소속을 그 아래 쌓아서, 탭에 닿기 전에 180px 를 굴려야 했다.
      가로로 눕힌다 — 얼굴은 왼쪽 56px, 글자는 그 오른쪽이다.

   ② 얼굴의 「사진 관리」 덮개는 **얹었을 때만** 떴다. 손가락에는 얹기가 없어서
      휴대폰에서는 그 글자를 한 번도 볼 수 없었고, 동그라미가 눌린다는 것도
      알 길이 없었다. 한동안 늘 보이는 아래 띠로 두었다가 — 얼굴의 4할을
      검게 덮는 값이라 — **모서리의 연필 딱지**로 다시 줄였다.

   ③ 탭 머리 일곱이 한 줄을 나눠 쓰느라 서넛만 보였다. 머리 줄을 통째로
      감추고 화면이 목록과 되돌아가는 줄을 대신 그린다(`ad-hub`·`ad-crumb`).
      손가락 과녁도 작았다 — 사진 카드의 단추 셋이 24px 였다(권장 44px).

   ④ 고정탭 두 판이 22rem 씩이라 위아래로 쌓으면 그것만 700px 다. 휴대폰에서
      그 화면은 「목록 하나」로 읽혀야 하므로 절반 아래로 줄인다.

   ⑤ 저장·되돌리기 같은 단추가 오른쪽 끝에 작게 매달려 있었다. 한 줄을
      나눠 갖게 해서 엄지가 닿는 크기로 만든다.
   ---------------------------------------------------------- */

@media (max-width: 767px) {

  .ad-profile {
    gap: 0.75rem;
  }

  /* ① 얼굴 판을 눕힌다. 아래 목록(`ad-hub`)과 나란히 읽혀야 해서 모서리와
     바탕을 그쪽에 맞춘다 — 둘이 한 벌로 보이면 그 사이에 계단이 없다. */
  .ad-profile__side {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.7rem 0.85rem;
    border-radius: var(--jsini-radius-lg);
    background: var(--jsini-surface-raised);
    text-align: left;
  }

  .ad-profile__avatar {
    flex: 0 0 auto;
  }

  .ad-profile__photo {
    flex: 0 0 auto;
    width: 56px;
    height: 56px;
  }

  .ad-profile__photo-initial {
    font-size: 1.5rem;
  }

  /* 이름 쪽이 남은 폭을 갖는다. `min-width: 0` 이 없으면 긴 소속 이름이
     판을 밀어 얼굴이 찌그러진다. */
  .ad-profile__who {
    min-width: 0;
    margin-top: 0;
  }

  .ad-profile__name,
  .ad-profile__id,
  .ad-profile__org {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* ② 덮개는 걷고 모서리에 연필 딱지를 붙인다.

     한동안 여기 덮개가 늘 보이는 아래 띠였다(동그라미의 아래 4할을 검게
     덮었다). 손가락에 얹기가 없어 「눌린다」를 말할 자리가 필요했던 것인데,
     **그 말에 얼굴의 4할이 필요하지 않다.** 사진을 올려 둔 계정일수록
     가려지는 것이 아깝다. */
  .ad-profile__photo-veil {
    display: none;
  }

  .ad-profile__photo-badge {
    position: absolute;
    right: -2px;
    bottom: -2px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;

    /* 바탕색 테두리로 동그라미에서 한 겹 띄운다. 맞대어 두면 사진의 어두운
       구석에서 딱지의 윤곽이 사라진다. */
    border: 2px solid var(--jsini-bg);
    border-radius: var(--jsini-radius-full);
    background: var(--jsini-accent);
    color: #fff;

    /* 단추 밖의 형제라 여기를 눌러도 아무 일이 없는데, 사람은 이 딱지를
       과녁으로 읽는다. 누름을 아래 단추로 흘려 보낸다. */
    pointer-events: none;
  }

  .ad-profile__photo-badge [class^='jsini-icon-'] {
    --jsini-icon-size: 0.8rem;
  }

  /* ③ 탭 머리를 걷는다.

     위로 올린 머리라도 폭을 일곱이 나눠 쓴다 — 360px 에서 한 칸이 51px 라
     이름을 줄여도 서넛만 보이고 나머지는 쓸어 넘겨야 닿는다. 대신 화면이
     목록(`ad-hub`)과 되돌아가는 줄(`ad-crumb`)을 그린다(`Profile.razor`).

     **`DxTabs` 자체는 남겨 둔다.** 일곱 조각의 내용이 `DxTabPage` 안에 있어서,
     그것을 밖으로 꺼내면 같은 마크업을 데스크톱·휴대폰 두 벌로 유지하게 된다.
     여기서 감추는 것은 머리 줄 하나고 고른 조각은 그대로 그려진다.

     선택자는 **눈대중이 아니다** — 이 파일 위쪽의 폭 규칙이 이미 쓰고 있는
     이름이고, 테마 CSS 에서도 `.dxbl-tabs > .dxbl-tabs-tablist` 로 확인했다
     (fluent 26.1.4 `core.min.css`). 자손 선택자로 적어 두면 우리 `class` 가
     `.dxbl-tabs` 에 붙든 그 위의 상자에 붙든 둘 다 걸린다. */
  .ad-profile__tabs .dxbl-tabs-tablist {
    display: none;
  }

  /* ④ 고정탭. 두 판이 위아래로 쌓이므로 하나가 화면을 다 먹으면 안 된다. */
  .ad-pin {
    gap: 0.75rem;
  }

  .ad-pin__list {
    height: 13rem;
  }

  /* 옮기는 단추 셋이 한 줄을 나눠 갖는다. 오른쪽 끝에 작게 붙여 두면
     「위로」와 「아래로」 사이가 엄지 너비보다 좁다. */
  .ad-pin .jsini-actions .dxbl-btn {
    flex: 1 1 6rem;
    min-height: 2.75rem;
  }

  /* ⑤ 저장·되돌리기 같은 단추 줄. 빈칸(`__spacer`)이 줄을 통째로 차지하지
     못하게 눌러 두고 단추가 남은 폭을 나눠 갖게 한다. */
  .ad-profile .jsini-toolbar__spacer {
    flex: 0 0 0;
  }

  .ad-profile .jsini-toolbar .dxbl-btn {
    flex: 1 1 8rem;
    min-height: 2.75rem;
  }

  /* 설정 줄. 켬·끔 네모가 줄 끝에 12px 짜리 과녁으로 붙어 있었다.
     네모 자체는 테마가 그리므로 **그것을 감싼 자리를 넓힌다.** */
  .ad-setting {
    gap: 0.75rem;
    min-height: 3.25rem;
    padding: 0.55rem 0;
  }

  /* 설명이 길면 글자 쪽이 줄어들어야 한다. `min-width: 0` 이 없으면 대신
     오른쪽 과녁이 밀려 좁아진다 — 넓히려고 한 자리가 도로 좁아진다. */
  .ad-setting > :first-child {
    min-width: 0;
  }

  .ad-setting > :last-child {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: flex-end;
    min-width: 2.75rem;
    min-height: 2.75rem;
  }

  /* 사진 격자. 160px 로 두면 360px 짜리 화면에서 두 칸이 겨우 들어가
     좌우 여백이 사라진다 — 칸 수를 못 박고 사이를 좁힌다. */
  .ad-photos {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.6rem;
  }

  /* 좁은 카드에서 이름과 단추 셋이 한 줄을 다투면 단추가 먼저 눌린다 —
     이름을 내린다. 어차피 크게 보기 창의 제목이 같은 것을 말한다. */
  .ad-photo__name {
    display: none;
  }

  .ad-photo__veil {
    justify-content: space-evenly;
    padding: 0.25rem;
  }

  .ad-photo__acts {
    gap: 0.25rem;
  }

  .ad-photo__acts .dxbl-btn {
    min-width: 2.5rem;
    min-height: 2.5rem;
    padding: 0.35rem;
  }
}

/* ── MSA 서버 상태 — 카드 ─────────────────────────────────

   카드 자체는 DevExpress 가 그린다(`DxFormLayoutGroup Decoration="Card"`).
   여기서 더하는 것은 **상태를 색으로 말하는 것** 하나다.

   `DxFormLayout` 은 12칸 격자라 카드마다 `ColSpanXs/Md/Xl` 만 적으면 폭에
   따라 열 수가 바뀐다. Vue 때 카드가 세 줄로 넘쳐 세로 스크롤이 생겼던 것을
   `auto-fill` 로 고쳤는데, 여기서는 그 일을 DevExpress 가 한다.
   ---------------------------------------------------------- */

.ad-deck {
  margin-bottom: 1rem;
}

/* ── MSA 상태: 타일 + 상세 ────────────────────────────────

   카드 한 장에 모든 값을 담으면 서비스 수만큼 세로로 자란다. 왼쪽에 한 줄
   타일을 세우고 오른쪽에 고른 것 하나만 편다 — 서비스가 스물이 되어도
   화면 높이는 그대로다.

   `ad-split` 을 쓰지 않는 이유는 그쪽이 **본문 높이를 통째로 차지**하는
   규칙을 물고 있어서다. 이 화면은 아래에 구역이 셋 더 쌓여 있어 그 규칙을
   걸면 나머지가 잘린다.
   ---------------------------------------------------------- */

.ad-msa {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
  margin-bottom: 1rem;
  align-items: start;
}

@media (min-width: 1200px) {
  .ad-msa { grid-template-columns: 1fr 380px; }
}

/* 왼쪽 칸 — 목록 둘(서비스 · 컨테이너)을 쌓는다.

   상세를 오른쪽 한 자리에 모으면서 왼쪽이 두 덩이가 됐다. 제목과 목록이
   서로 붙지 않게 이 칸이 간격을 갖는다 — 목록에 `margin` 을 주면 칸이
   하나뿐인 다른 화면에서도 따라 벌어진다. */
.ad-msa__panes {
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* 첫 제목은 이미 위 구역과 간격이 있다. 둘째부터만 띄운다. */
.ad-msa__panes > .ad-subhead:not(:first-child) {
  margin-top: 1rem;
}

/* 카드가 늘어나도 목록만 구른다. 페이지가 함께 자라면 아래 구역들이 밀린다.

   **둘이 쌓이므로 각자 낮게 잡는다.** 26rem 씩 두면 왼쪽이 52rem 이 되어
   오른쪽 상세가 화면 밖으로 밀린다 — 나란히 보려고 합친 것이 무의미해진다.

   휴대폰에서는 이 높이를 도로 푼다(바로 아래 `max-width: 767px`). */
.ad-msa__list {
  min-width: 0;
  max-height: 17rem;
  overflow-y: auto;

  /* **줄이 아니라 칸이 채워진다.** 서비스가 늘 때 아래로 자라지 않는 것이
     이 화면의 요점이라, 열 수를 폭이 정하게 둔다(`auto-fill`). */
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 0.5rem;
  align-content: start;
}

/* 휴대폰에서는 묶음마다 구르지 않는다.

   위 `max-height` 는 **왼쪽 목록 둘과 오른쪽 상세를 나란히** 두려고 건 것이라
   폭이 넓을 때만 뜻이 있다. 좁은 화면에서는 셋이 위아래로 쌓이므로 그 제약이
   남으면 17rem 짜리 창 안에서 카드를 다시 굴려야 한다 — **페이지 스크롤 안에
   또 스크롤**이라, 손가락이 어느 것을 미는지 알 수 없고 카드 열몇 장 중
   빨간 것을 찾으려면 묶음마다 따로 끝까지 내려야 한다.

   그래서 휴대폰에서는 높이를 풀어 전부 펴 둔다. 페이지가 길어지지만 훑는 일은
   세로 스크롤 하나로 끝난다. */
@media (max-width: 767px) {
  .ad-msa__list {
    max-height: none;
    overflow-y: visible;
  }
}

/* 카드는 단추다 — 눌러서 오른쪽을 바꾸는 것이므로 키보드로도 닿아야 한다.
   상태는 왼쪽 굵은 선으로 말한다(`ad-card` 와 같은 규칙). 글자(상태 이름)도
   함께 두는 이유는 색으로만 구분하면 색각 이상이 있는 사용자가 못 읽는다. */
.ad-msa__card {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--jsini-border, #e0e0e0);
  border-left-width: 3px;
  border-radius: 0.5rem;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.ad-msa__card:hover { background: color-mix(in srgb, currentcolor 4%, transparent); }

/* 고른 카드. 테두리 폭을 바꾸면 카드가 흔들려 보이므로 테를 덧그린다. */
.ad-msa__card.is-on {
  border-color: var(--jsini-accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--jsini-accent) 35%, transparent);
}

.ad-msa__card--up { border-left-color: #2e7d32; }
.ad-msa__card--warn { border-left-color: #b26a00; }
.ad-msa__card--down { border-left-color: #d32f2f; }

.ad-msa__row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
}

.ad-msa__dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  flex: none;
  margin-left: auto;
  background: currentcolor;
  opacity: 0.35;
}

.ad-msa__card--up .ad-msa__dot { background: #2e7d32; opacity: 1; }
.ad-msa__card--warn .ad-msa__dot { background: #b26a00; opacity: 1; }
.ad-msa__card--down .ad-msa__dot { background: #d32f2f; opacity: 1; }

.ad-msa__name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}

.ad-msa__state { font-size: var(--jsini-fs-sm); }

.ad-msa__card--up .ad-msa__state { color: #2e7d32; }
.ad-msa__card--warn .ad-msa__state { color: #b26a00; }
.ad-msa__card--down .ad-msa__state { color: #d32f2f; }

/* 딸린 것 요약. 카드에서 「DB 가 죽었다」를 알아채고 눌러 보게 만드는 일만 한다. */
.ad-msa__deps { gap: 0.2rem; }

.ad-msa__deps-label {
  font-size: var(--jsini-fs-sm);
  color: var(--jsini-text-muted);
}

.ad-msa__depdot {
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  flex: none;
}

.ad-msa__depdot--up { background: #2e7d32; }
.ad-msa__depdot--warn { background: #b26a00; }
.ad-msa__depdot--down { background: #d32f2f; }

.ad-msa__latency {
  flex: none;
  margin-left: auto;
  font-size: var(--jsini-fs-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* 상세 안에 접어 넣은 AI 공급자 카드는 한 줄에 한 장이다. 칸이 380px 이라
   두 장을 나란히 두면 모델 이름과 한도가 줄바꿈으로 뭉개진다. */
.ad-msa__detail .ad-deck { margin-bottom: 0; }

/* ── 오른쪽 상세 ──────────────────────────────────────────── */

.ad-msa__detail {
  border: 1px solid var(--jsini-border, #e0e0e0);
  border-radius: 0.35rem;
  padding: 0.9rem 1rem;
  min-width: 0;
}

/* 넓은 화면에서는 상세가 **따라다닌다.** 왼쪽 목록 둘을 굴려 내려가도
   지금 펴 놓은 값이 눈에 남아야 둘을 견줄 수 있다 — 그것이 상세를 한 자리로
   모은 이유다. 좁은 화면에서는 위아래로 쌓이므로 붙이지 않는다. */
@media (min-width: 1200px) {
  .ad-msa__detail {
    position: sticky;
    top: 0.5rem;
    max-height: calc(100vh - var(--jsini-header-height, 3.5rem) - 6rem);
    overflow-y: auto;
  }
}

.ad-msa__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.2rem;
}

/* 이름표와 값을 두 칸으로. 값이 주소처럼 길어도 이름표 칸은 안 밀린다. */
.ad-msa__facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.3rem 0.75rem;
  margin: 0.75rem 0 0;
}

.ad-msa__facts dt {
  color: var(--jsini-text-muted);
  font-size: var(--jsini-fs-sm);
  white-space: nowrap;
}

.ad-msa__facts dd {
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

.ad-msa__section {
  margin: 0.9rem 0 0.4rem;
  font-weight: 600;
  font-size: var(--jsini-fs-sm);
}

.ad-msa__deplist {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.ad-msa__deplist li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem;
}

/* 컨테이너 카드의 셋째 줄 — 「Up 3 days」 같은 가동 문구.
   서비스 카드의 그 자리에는 연결 점이 있고, 컨테이너에는 딸린 것이 없어
   비는 자리라 여기 채운다. **한 줄로 자른다** — 「Up 2 hours (healthy)」
   처럼 길어지면 카드 키가 그 장만 늘어 격자가 들쭉날쭉해진다. */
.ad-box__uptime {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--jsini-fs-sm);
  color: var(--jsini-text-muted);
}

/* 상태를 왼쪽 굵은 선으로 말한다. 카드 열몇 장 중에 빨간 것 하나를 찾는
   자리라, 글자를 읽기 전에 색이 먼저 걸려야 한다. 글자(배지)도 함께 두는
   이유는 색으로만 구분하면 색각 이상이 있는 사용자가 못 읽기 때문이다. */
.ad-card {
  border-left: 3px solid var(--jsini-border, #e0e0e0);
}

.ad-card--up { border-left-color: #2e7d32; }
.ad-card--warn { border-left-color: #b26a00; }
.ad-card--down { border-left-color: #d32f2f; }

/* 키가 없는 AI 제공자. 고장이 아니라 **아직 안 켠 것**이라 흐리게 둔다. */
.ad-card--off { border-left-color: var(--jsini-text-muted); }

/* 카드 머리 — 이름 · 상태 배지 · 응답 시간이 한 줄이다.
   `DxFormLayoutGroup` 의 CaptionTemplate 안에 들어간다. */
/* 카드 머리는 DevExpress 가 이미 flex 로 그린다. 사이만 벌려 준다 —
   이름 · 배지 · 응답 시간이 붙어 있으면 배지가 이름의 일부처럼 읽힌다. */
.ad-card .dxbl-group-header .dxbl-text {
  gap: 0.4rem;
}

/* 이름이 길어도 배지와 응답 시간을 밀어내지 않게 한다 — DevExpress 의
   머리줄은 `flex-wrap: nowrap` 이라 밀리면 그대로 카드 밖으로 나간다. */
.ad-card__name {
  font-weight: 700;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ad-card .jsini-badge,
.ad-card__latency {
  flex-shrink: 0;
}

/* 응답 시간은 오른쪽 끝으로 민다 — 카드마다 같은 자리에 있어야 훑을 수 있다. */
.ad-card__latency {
  margin-left: auto;
  padding-left: 0.5rem;
  font-variant-numeric: tabular-nums;
  font-size: var(--jsini-fs-sm);
  color: var(--jsini-text-muted);
}

.ad-lat--slow { color: #b26a00; font-weight: 700; }
.ad-lat--bad { color: #d32f2f; font-weight: 700; }

.ad-card__sub {
  font-size: var(--jsini-fs-sm);
  color: var(--jsini-text-muted);
}

.ad-card__addr {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
  margin-top: 0.2rem;
}

/* 실패 사유. 카드에서 가장 중요한 한 줄이라 바탕을 깔아 띄운다. */
.ad-card__why {
  padding: 0.35rem 0.5rem;
  border-left: 2px solid #d32f2f;
  background: color-mix(in srgb, #d32f2f 8%, transparent);
  color: #d32f2f;
  font-size: var(--jsini-fs-sm);
  overflow-wrap: anywhere;
}

/* 딸린 것 배지들. 서너 개라 줄바꿈으로 흘린다. */
.ad-deps {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}

/* 중지·오류를 빨강으로. `jsini-badge--off`(흐림)만 있으면 「꺼져 있다」와
   「죽었다」가 같아 보인다. 포털관리에서만 쓰므로 공용으로 올리지 않는다. */
.jsini-badge.ad-badge--err {
  border-color: #d32f2f;
  color: #d32f2f;
}

/* 게이트웨이 카드의 숫자 셋. 아래 카드 열몇 장을 세지 않아도 되게 한다. */
.ad-tally {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.5rem;
}

.ad-tally__cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.1;
}

.ad-tally__num {
  font-size: var(--jsini-fs-3xl);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.ad-tally__label {
  font-size: var(--jsini-fs-xs);
  color: var(--jsini-text-muted);
}

.ad-tally--up .ad-tally__num { color: #2e7d32; }
.ad-tally--warn .ad-tally__num { color: #b26a00; }
.ad-tally--down .ad-tally__num { color: #d32f2f; }

/* 다시 읽기와 「언제 기준인지」는 오른쪽 끝에 붙인다. */
.ad-tally__tail {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-left: auto;
}

/* 하루 한도. 숫자만 적으면 「800 / 1000」이 얼마나 남은 것인지 한눈에
   안 들어온다 — 막대가 그 일을 한다. */
.ad-quota {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.ad-quota__bar {
  flex: 1;
  min-width: 6rem;
}

/* 고를 수 있는 모델이 수십 개인 제공자가 있다(OpenRouter). 그대로 두면
   그 카드만 화면 밖까지 자라 옆 카드와 높이가 어긋난다. */
.ad-modellist {
  max-height: 4.5rem;
  overflow-y: auto;
  overflow-wrap: anywhere;
}

/* ── 메시지 발송 (`/admin/push/send`) ─────────────────────────

   받는 사람을 **체크 목록**으로 고른다. 표(`CommGrd`)를 쓰지 않는 이유는
   여기서 하는 일이 자료를 읽는 것이 아니라 고르는 것이라서다 — 정렬·쪽나누기
   보다 「지금 몇 명인가」와 「한 번에 여럿 켜기」가 중요하다.

   목록이 길어지므로 높이를 묶고 그 안에서만 구른다. 안 묶으면 사람 수백 명
   아래로 보낼 내용이 밀려나 화면을 계속 굴려야 한다.
   ---------------------------------------------------------- */

/* 받는 사람 고르는 상자. **판이 높이를 정해 준다** — 메시지 발송 화면에서
   이것이 `ad-vsplit` 왼쪽 판에 들어가 있고, 거기서는 남는 높이를 다 쓴다.
   `max-height` 를 값으로 박으면 판보다 짧아져 아래가 비고, 반대로 판보다
   길면 스크롤이 두 겹이 된다(사람롤에서 밟은 것과 같다). */
.ad-picklist {
  flex: 1;
  min-height: 0;
  margin-bottom: 1rem;
  border: 1px solid var(--jsini-border);
  border-radius: 0.25rem;
  overflow-y: auto;
}

.ad-pick {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.6rem;
  border-bottom: 1px solid color-mix(in srgb, var(--jsini-border) 50%, transparent);
  cursor: pointer;
}

.ad-pick:hover { background: color-mix(in srgb, currentColor 5%, transparent); }

/* 고른 줄은 **면으로** 칠한다. 테두리를 두르면 그 줄만 키가 커져 목록이 들썩인다
   (테마 서랍에서 같은 이유로 같은 선택을 했다). */
.ad-pick--on { background: color-mix(in srgb, var(--jsini-primary, #3f7ad6) 10%, transparent); }

.ad-pick__name { font-weight: 600; }

.ad-pick__id,
.ad-pick__where {
  font-size: var(--jsini-fs-sm);
  color: var(--jsini-text-muted);
}

/* ── 메시지 발송: 오른쪽 판 (`ad-send`) ───────────────────

   탭이 판 높이를 꽉 채우고 **탭 안쪽만 구른다.** `DxTabs` 의 상자 둘
   (`dxbl-tabs-content-panel` · `dxbl-tabs-content`)은 `min-height: auto` 인
   flex 항목이라 **안쪽 내용보다 작아지지 않는다** — 그대로 두면 메일 본문
   열 줄짜리 폼이 판을 밀어내 「보내기」 단추가 판 밖으로 나간다.
   역할 관리(`ad-roletabs`)에서 같은 것을 밟았다.
   ---------------------------------------------------------- */

.ad-send { overflow: hidden; }

.ad-send__tabs {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.ad-send__tabs .dxbl-tabs-content-panel,
.ad-send__tabs .dxbl-tabs-content {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

/* 보낼 내용 칸. 폼이 화면 폭만큼 늘어나면 한 줄이 너무 길어 읽기 나쁘다.
   **자기 안에서 구른다** — 판이 구르면 탭 머리까지 올라가 어느 길로 보내는
   중인지 잃는다. */
.ad-sendform {
  max-width: 46rem;
  height: 100%;
  padding-top: 0.75rem;
  padding-right: 0.5rem;
  overflow-y: auto;
}

.ad-field { margin-bottom: 0.9rem; }

.ad-field__label {
  display: block;
  margin-bottom: 0.25rem;
  font-weight: 600;
}

.ad-field__must { color: #d32f2f; }

/* 아이콘 주소 칸은 옆의 빠른 선택 단추를 빼고 남는 폭을 다 쓴다. */
.ad-field__grow { flex: 1; min-width: 12rem; }

/* ── 계정 관리: 알림 칸의 「기본값」 표시 ──────────────────

   알림 설정을 **한 번도 저장한 적이 없는 사람**의 딱지다. 값 자체는 맞지만
   그 사람이 고른 것이 아니라 기본값이라, 일부러 켜 둔 사람과 같은 무게로
   보이면 안 된다.

   **글자를 바꾸지 않는다.** 「받음(기본)」처럼 적으면 칸이 넓어지고 표의
   다른 칸이 밀린다. 점선 테두리와 옅은 색으로만 가르고, 무슨 뜻인지는
   마우스를 올렸을 때 뜬다(`title`).
   ---------------------------------------------------------- */

.ad-notify--default {
  border-style: dashed;
  opacity: 0.65;
}

/* ── 주소 찾기 덮개 (`AddrFind` · js/addr-find.js) ──────────────

   카카오(다음) 우편번호 위젯을 앉히는 자리다. 위젯 자신은 자기 안쪽만
   그리므로 **바깥 껍데기는 우리가 준다** — 덮개·창·머리·닫개.

   [z-index 가 여기서 중요하다]

   주소 칸은 DevExpress 팝업 편집 폼 안에 있고 그 팝업이 1000 대를 쓴다.
   덮개가 그보다 낮으면 **열리긴 하는데 편집 창 뒤에 가려** 아무 일도
   안 일어난 것으로 보인다. 잠금화면(20000)보다는 낮게 둔다 — 자리를
   비운 사이 주소 창이 그 위에 떠 있으면 안 된다.

   [높이를 고정한다]

   위젯이 안에서 화면을 갈아 끼운다(검색 → 결과 → 상세). 높이를 내용에
   맡기면 그때마다 창이 늘었다 줄고, 그 사이 마우스 밑에 있던 단추가
   옮겨 간다. 480px 는 결과 목록이 한 화면에 들어가는 높이다.
   ---------------------------------------------------------- */

.ad-addr {
  position: fixed;
  inset: 0;
  z-index: 19000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background: rgb(0 0 0 / 45%);
}

.ad-addr__box {
  display: flex;
  flex-direction: column;
  width: min(30rem, 100%);
  height: min(480px, 100%);
  overflow: hidden;
  border: 1px solid var(--jsini-border);
  border-radius: 0.5rem;
  background: var(--jsini-bg);
  box-shadow: 0 1.25rem 2.5rem rgb(0 0 0 / 28%);
}

.ad-addr__head {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  padding: 0.6rem 0.75rem;
  border-bottom: 1px solid var(--jsini-border);
  background: var(--jsini-surface);
  color: var(--jsini-text);
  font-weight: 600;
}

.ad-addr__close {
  margin-left: auto;
  padding: 0 0.35rem;
  border: 0;
  background: none;
  color: var(--jsini-text-muted);
  font-size: var(--jsini-fs-xl);
  line-height: 1;
  cursor: pointer;
}

.ad-addr__close:hover { color: var(--jsini-text); }

/* 위젯이 여기에 자기 iframe 을 꽂는다. 남는 높이를 다 준다. */
.ad-addr__frame {
  flex: 1 1 auto;
  min-height: 0;
}

.ad-addr__frame > * {
  width: 100% !important;
  height: 100% !important;
}

/* 우편번호 칸과 「주소 찾기」 단추를 한 줄에 앉힌다. 칸이 남는 폭을 쓰고
   단추는 글자만큼만 차지한다 — 반대로 두면 좁은 화면에서 단추 글자가 접힌다. */
.ad-addr-row {
  display: flex;
  gap: 0.4rem;
  align-items: center;
}

.ad-addr-row__grow { flex: 1 1 auto; min-width: 0; }

/* 단추는 글자만큼만 차지하고 **접히지 않는다.** 좁은 칸에 넣으면 flex 가
   단추를 줄이려 들어 「주소찾기」가 한 자씩 세로로 쌓인다. */
.ad-addr-row > .dxbl-btn {
  flex: 0 0 auto;
  white-space: nowrap;
}

/* ── 패스키(지문·얼굴) 기기 목록 ─────────────────────────

   「내 정보 → 보안 설정」의 등록된 기기들. **그리드가 아니라 줄이다** —
   칸이 넷뿐이고 정렬·검색할 것이 없는데 표를 세우면 휴대폰에서 옆으로
   구른다(같은 화면의 접속 기록 표가 겪은 일이다).
   ---------------------------------------------------------- */

.ad-passkeys {
  margin: 0 0 1rem;
  padding: 0;
  list-style: none;
}

.ad-passkey {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--jsini-border);
}

.ad-passkey__name { font-weight: 600; }

.ad-passkey__meta {
  margin-top: 0.15rem;
  font-size: var(--jsini-fs-sm);
  color: var(--jsini-text-muted);
}

.ad-passkey__acts {
  display: flex;
  gap: 0.4rem;
  flex-shrink: 0;
}

/* 휴대폰에서는 단추 둘이 이름을 밀어낸다. 설정 줄과 같은 경계·같은 방식으로
   아래로 내린다(이 파일의 `.ad-setting` 좁은 화면 규칙과 짝이다). */
@media (max-width: 767px) {
  .ad-passkey {
    flex-direction: column;
    align-items: stretch;
    gap: 0.5rem;
  }

  .ad-passkey__acts { justify-content: flex-end; }
}

/* 아직 안 붙인 소셜 공급자의 「연결」 단추들.

   설정 줄(`.ad-setting`)의 오른쪽에 세우지 않고 **아래 줄로 내린다.** 공급자가
   셋이면 단추도 셋인데, 그것을 설명 글 옆에 밀어 넣으면 좁은 화면에서 글이
   한 글자씩 접힌다. 목록(`.ad-passkeys`)과 힌트 사이에 서는 자리다.

   나눠 가지지 않고 내용 크기로 둔다 — 「구글 연결」과 「카카오 연결」의 폭이
   달라야 무엇을 누르는지가 글자 길이로도 읽힌다. */
.ad-social-add {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0.75rem 0 0;
}

/* 로그인 방식 우선순위 고르개. 설정 줄의 켬·끔 자리에 서는 유일한 목록이라
   폭을 정해 준다 — 안 정하면 고른 글자 길이에 따라 줄마다 오른쪽 끝이
   들쑥날쑥해진다(바로 위아래가 체크박스라 더 눈에 띈다).

   좁은 화면에서는 `.ad-setting > :last-child` 가 과녁을 44px 로 키우므로
   높이는 그쪽에 맡기고 여기서는 폭만 말한다. */
.ad-setting__pick {
  flex: 0 0 auto;
  width: 11rem;
  max-width: 45vw;
}

/* ── 쪽지함 (`NoteBoxPage`) ───────────────────────────────────

   쓰는 판(`NoteWritePanel`)은 상단 띠에서도 열려서 공용 `app.css` 에 있다.
   여기 있는 것은 **읽는 창**뿐이다 — 그 창은 이 화면에만 있다.
   ---------------------------------------------------------- */

/* 안 읽은 쪽지의 제목. 받은함에서 눈이 먼저 가야 하는 줄이다. */
.ad-note__title--unread {
  font-weight: 700;
}

/* 보낸 이·받는 이·때. 이름표와 값이 두 칸으로 선다 — 한 줄로 이어 두면
   이름이 길 때 어디까지가 값인지 안 갈린다. */
.ad-note__meta {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.15rem 0.7rem;
  margin: 0 0 0.8rem;
  padding-bottom: 0.7rem;
  border-bottom: 1px solid var(--jsini-border);
  font-size: var(--jsini-fs-sm);
}

.ad-note__meta dt {
  color: var(--jsini-text-muted);
}

.ad-note__meta dd {
  margin: 0;
}

.ad-note__subject {
  margin: 0 0 0.5rem;
  font-size: var(--jsini-fs-lg);
  font-weight: 600;
}

/* 본문은 평문이다. 줄바꿈을 살리고, 긴 주소가 창을 밀지 않게 끊는다 —
   HTML 로 그리지 않는 까닭은 화면에 적어 두었다(남이 보낸 글자다). */
.ad-note__body {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* ============================================================
   계정 앱 현황 (`AccountAppPage`)
   ============================================================ */

/* 조회줄 첫 칸은 고르는 값이 아니라 **누구를 보고 있는가**다. 편집기
   높이에 맞춰 두어야 옆의 기간 고르개와 글자 밑선이 어긋나지 않는다. */
.ad-appstat__who {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
  min-height: 2rem;
}

/* 수신·발신을 **색으로도** 가른다. 표에서 둘이 한 줄기로 섞여 내려오므로
   글자만으로는 눈이 방향을 따라가지 못한다 — 받은 것이 대부분인 목록에서
   드문 발신 줄을 찾는 것이 이 화면의 쓰임 중 하나다. */
.jsini-badge.ad-appstat--recv {
  border-color: var(--jsini-accent);
  color: var(--jsini-accent);
}

.jsini-badge.ad-appstat--sent {
  border-color: #6a4bb2;
  color: #6a4bb2;
}

/* ============================================================
   위치 지도 (`LocationMapPage`)

   타일과 점은 JS 가 붙이고(`js/geo-map.js`) 여기서는 자리와 색만 정한다.
   ============================================================ */

/* 지도 상자. **자리를 잘라 내는 쪽**이라 `overflow: hidden` 이 있어야
   타일이 판 밖으로 새지 않고, `position: relative` 가 있어야 안쪽 것들이
   이 상자를 기준으로 놓인다. */
.ad-geomap {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--jsini-border);
  border-radius: var(--jsini-radius-md);
  background: var(--jsini-surface);

  /* 좁은 화면에서는 판이 위아래로 쌓여 남은 높이가 없다. 지도는 **높이가
     0 이면 아무것도 안 그려지므로**(타일 범위가 비어 버린다) 바닥을 둔다. */
  min-height: 360px;

  /* 손가락 끌기를 브라우저의 쪽 스크롤에 뺏기지 않는다 — 조직도 캔버스와
     같은 자리다. 이것이 없으면 휴대폰에서 지도가 안 움직인다. */
  touch-action: none;
  cursor: grab;
}

.ad-geomap.is-grabbing {
  cursor: grabbing;
}

/* 넓은 화면에서는 판이 본문 높이를 채운다. `.ad-split > .commcont` 규칙이
   본문을 세로 flex 로 만들어 두므로 여기서 남은 높이를 다 먹으면 된다. */
@media (min-width: 992px) {
  .ad-split .ad-geomap {
    flex: 1;
    min-height: 0;
  }
}

/* 타일과 점은 같은 자리에 겹쳐 깔린다. 점이 위다. */
.ad-geomap__stage,
.ad-geomap__tiles,
.ad-geomap__pins {
  position: absolute;
  inset: 0;
}

.ad-geomap__pins {
  /* 판은 점을 안 가로챈다 — 끌기는 아래 타일 층이 받는다. 점만 되살린다. */
  pointer-events: none;
}

/* 타일 한 장. `transform` 으로 놓으므로 왼쪽 위에 붙여 둔다. */
.ad-geomap__tile {
  position: absolute;
  top: 0;
  left: 0;
  width: 256px;
  height: 256px;

  /* 끌 때 브라우저의 그림 끌기가 먼저 걸리면 지도가 안 움직인다. */
  user-select: none;
  -webkit-user-drag: none;
}

/* 점 하나. 가운데가 좌표에 오도록 자기 크기의 절반만큼 당긴다. */
.ad-geomap__pin {
  position: absolute;
  transform: translate(-50%, -50%);
  pointer-events: auto;

  display: flex;
  align-items: center;
  gap: 0.35rem;

  padding: 0.15rem 0.5rem 0.15rem 0.2rem;
  border: 1px solid var(--jsini-accent);
  border-radius: var(--jsini-radius-full);
  background: var(--jsini-bg);
  color: var(--jsini-text);
  font-size: var(--jsini-fs-2xs);
  line-height: 1.5;
  white-space: nowrap;
  cursor: pointer;

  /* 타일 위에 떠 있는 것으로 읽혀야 한다. 테두리만으로는 지도 무늬에 묻힌다. */
  box-shadow: 0 1px 4px rgb(0 0 0 / 28%);
}

/* 이름 앞의 동그라미. `::before` 로 두어 DOM 을 늘리지 않는다. */
.ad-geomap__pin::before {
  content: "";
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 50%;
  background: var(--jsini-accent);
  flex: none;
}

.ad-geomap__pin:hover {
  z-index: 3;
}

/* 고른 점은 **커지지 않고 색이 찬다.** 커지면 그 순간 가리키는 자리가
   미세하게 달라 보이고, 겹쳐 벌려 둔 이웃을 밀어낸 것처럼 읽힌다. */
.ad-geomap__pin.is-picked {
  z-index: 4;
  background: var(--jsini-accent);
  color: #fff;
  font-weight: 600;
}

.ad-geomap__pin.is-picked::before {
  background: #fff;
}

/* 쪽지가 안 닿는 사람. **지우지 않고 흐리게 둔다** — 까닭은 화면 머리말에. */
.ad-geomap__pin.is-muted {
  border-color: var(--jsini-text-muted);
  color: var(--jsini-text-muted);
}

.ad-geomap__pin.is-muted::before {
  background: var(--jsini-text-muted);
}

/* ── 작은 핀 (「내 이동경로」) ────────────────────────────────

   이름표(위)는 「저 점이 누구냐」를 묻는 화면의 꼴이다. 이동경로는 묻는 것이
   다르다 — **몇 번째로 들른 자리냐**이고, 하루치 자리는 한 동네에 모이기
   일쑤라(출근·점심·퇴근이 같은 블록이다) 긴 이름표끼리 서로를 통째로 덮는다.
   그래서 숫자 하나만 지닌 물방울 핀으로 줄인다. 이름은 `title` 과 고른 자리의
   카드가 들고 있다.

   꼴은 **정사각형을 45도 돌린 것**이다. 한 귀퉁이만 각지게 두면(`border-radius`
   의 넷째 값 0) 돌아간 뒤 그 귀퉁이가 아래를 가리켜 핀 끝이 된다. 삼각형을
   따로 붙이지 않는 까닭은 `::after` 로 만든 삼각형은 테두리·그림자·고른 색이
   본체와 따로 놀아, 색이 바뀔 때마다 두 군데를 맞춰야 하기 때문이다. */
.ad-geomap__pin--spot {
  /* 글자 칸이 아니라 **정해진 크기의 표식**이다. 글자 크기 설정(xxsmall ~
     large)을 따라 커지면 큰 설정에서 지도가 핀으로 덮인다. */
  width: 1.375rem;
  height: 1.375rem;
  padding: 0;
  gap: 0;
  justify-content: center;

  /* 세 귀퉁이는 둥글고 **왼쪽 아래만 각지다.** 돌리면 그 귀퉁이가 아래로 간다. */
  border-radius: 50% 50% 50% 0;

  /* 숫자는 색이 찬 바탕 위의 흰 글씨다. 테두리만으로는 이 크기에서 지도
     무늬에 묻힌다. */
  background: var(--jsini-accent);
  border-color: var(--jsini-accent);
  color: #fff;
  font-weight: 600;

  /*
     **끝이 좌표에 닿는다**(이름표는 가운데가 닿는다).

     각진 귀퉁이는 가운데에서 반대각선만큼 떨어져 있다 — 한 변이 1.375rem 이니
     그 절반의 √2 배, 곧 0.972rem 이다. 가운데를 그만큼 위로 올리면 돌린 뒤의
     귀퉁이가 정확히 좌표에 선다.
  */
  transform: translate(-50%, -50%) translateY(-0.972rem) rotate(-45deg);

  /* 돌아간 상자라 **방향이 있는 그림자는 같이 돌아간다.** 둥근 것으로 둔다. */
  box-shadow: 0 0 4px rgb(0 0 0 / 35%);
}

/* 이름표의 동그라미는 여기서 쓸 자리가 없다 — 핀 자체가 그 동그라미다. */
.ad-geomap__pin--spot::before {
  content: none;
}

/* 순번. **본체가 돌아가 있으므로 되돌려 세운다.** */
.ad-geomap__seq {
  /* 두 자리(12)가 보통이고 세 자리도 들어가야 한다. 머리 지름이 22px 이라
     9~10px 이면 셋까지 담긴다. */
  font-size: 0.625rem;
  line-height: 1;
  transform: rotate(45deg);
}

/* 한 번만 관측된 자리. 지우지 않고 **색만 죽인다** — 까닭은 화면 머리말에. */
.ad-geomap__pin--spot.is-muted {
  background: var(--jsini-text-muted);
  border-color: var(--jsini-text-muted);
  color: #fff;
}

/* 고른 핀. 이름표는 「색이 찬다」로 갈라졌는데 **작은 핀은 이미 색이 차 있어**
   그 수가 안 통한다. 흰 테를 둘러 갈라 놓는다 — 크기를 키우지 않는 까닭은
   이름표와 같다(가리키는 자리가 달라 보이고, 벌려 둔 이웃을 민 것처럼 읽힌다). */
.ad-geomap__pin--spot.is-picked {
  background: color-mix(in srgb, var(--jsini-accent) 82%, #000);
  border-color: #fff;
  box-shadow:
    0 0 0 2px color-mix(in srgb, var(--jsini-accent) 55%, transparent),
    0 0 5px rgb(0 0 0 / 45%);
}

/* 「내 이동경로」의 하루 요약 넷. 공용 `.jsini-facts` 를 그대로 쓰되
   **지도·목록과 너무 붙지 않게** 아래만 조금 띄운다 — 조회 판과 분할 사이에
   끼어 있어서, 공용 여백(1rem)만으로는 조회 조건의 일부처럼 읽힌다. */
.ad-track__sum {
  margin-bottom: 0.75rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--jsini-border);
}

/* 점들을 잇는 선(이동 경로). **타일 위 · 점 아래**다 — 점보다 위면 선이
   번호를 덮고, 타일 아래면 아예 안 보인다. 끌기는 아래 타일 층이 받아야
   하므로 이 판도 포인터를 안 가로챈다. */
.ad-geomap__trail {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: visible;
}

/* 선 자체. **채우기를 끈다** — `polyline` 의 기본값이 검은 채우기라,
   끄지 않으면 지나온 자리를 이은 다각형이 통째로 까맣게 칠해진다. */
.ad-geomap__trail-line {
  fill: none;
  stroke: var(--jsini-accent);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;

  /* 타일 무늬(도로도 선이다) 위에서 갈리라고 조금 비친다. */
  opacity: 0.7;
}

/* 같은 자리에서 벌려 놓은 점이 원래 자리를 가리키는 선. */
.ad-geomap__link {
  position: absolute;
  height: 1px;
  transform-origin: 0 50%;
  background: var(--jsini-text-muted);
  opacity: 0.55;
}

/* 확대·축소 단추. 오른쪽 위 — 지도 판의 관례다. */
.ad-geomap__zoom {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

/* 타일을 내주는 쪽이 요구하는 표시. 오른쪽 아래에 작게. */
.ad-geomap__credit {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: 5;
  padding: 0.05rem 0.35rem;
  border-radius: var(--jsini-radius-sm) 0 0 0;
  background: color-mix(in srgb, var(--jsini-bg) 82%, transparent);
  font-size: var(--jsini-fs-2xs);
}

.ad-geomap__credit a {
  color: var(--jsini-text-muted);
  text-decoration: none;
}

/* 고른 사람의 카드. **지도 위에 얹는다** — 아래에 두면 점을 누를 때마다
   눈이 지도 밖으로 내려갔다 올라온다. */
.ad-geomap__card {
  position: absolute;
  left: 0.5rem;
  bottom: 0.5rem;
  z-index: 5;
  width: min(17rem, calc(100% - 1rem));
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--jsini-border);
  border-radius: var(--jsini-radius-md);
  background: var(--jsini-bg);
  box-shadow: 0 2px 10px rgb(0 0 0 / 22%);
}

.ad-geomap__who {
  font-weight: 600;
  margin-bottom: 0.35rem;
}

/* 이름표와 값이 두 칸으로 선다 — 쪽지 읽기 창의 `ad-note__meta` 와 같은 꼴. */
.ad-geomap__facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.1rem 0.6rem;
  margin: 0 0 0.5rem;
  font-size: var(--jsini-fs-sm);
}

.ad-geomap__facts dt {
  color: var(--jsini-text-muted);
}

.ad-geomap__facts dd {
  margin: 0;
  overflow-wrap: anywhere;
}

.ad-geomap__acts {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.ad-geomap__out {
  font-size: var(--jsini-fs-sm);
  color: var(--jsini-text-muted);
}

/* 목록에서 쪽지가 안 닿는 사람. 점의 흐림과 같은 뜻을 같은 색으로 말한다. */
.ad-geomap__off {
  color: var(--jsini-text-muted);
}

/* ── 위치 지도 · 휴대폰 ───────────────────────────────────

   좁은 화면에서는 목록과 지도가 **한 번에 하나씩** 선다. 까닭은
   `LocationMapPage` 머리말에 있다 — 나란히 쌓으면 표가 화면을 통째로
   먹고 지도는 스크롤 저편이다.

   **마크업을 갈아 끼우지 않는다.** 지도 판은 늘 그려져 있고 여기서
   감추기만 한다 — `@ref` 로 잡아 둔 손잡이가 죽은 DOM 을 가리키면
   돌아올 때 지도를 처음부터 새로 만들어야 한다.
   ---------------------------------------------------------- */

@media (max-width: 767px) {

  /* 고르개. 일정표의 보기 고르개(`pm-gc__views`)와 같은 꼴이되 **폭을
     채운다** — 칸이 둘뿐이라 오른쪽에 붙여 두면 누를 자리가 작다. */
  .ad-geoview {
    display: flex;
    margin-bottom: 0.4rem;
    overflow: hidden;
    border: 1px solid var(--jsini-border);
    border-radius: var(--jsini-radius-full);

    /* 위아래 판이 남은 높이를 나눠 갖는 동안 이 줄은 눌리지 않는다. */
    flex: none;
  }

  .ad-geoview__btn {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.3rem;
    height: 2.2rem;
    font: inherit;
    color: var(--jsini-text);
    white-space: nowrap;
    cursor: pointer;
    background: transparent;
    border: 0;
  }

  .ad-geoview__btn--on {
    font-weight: 600;
    color: #fff;
    background: var(--jsini-accent);
  }

  /* 선 판만 남긴다. 감추는 쪽이 `display: none` 이라 자리를 안 먹는다. */
  .ad-split--phone.ad-split--map > .ad-split__side,
  .ad-split--phone.ad-split--list > .ad-split__main {
    display: none;
  }

  /* 선 판이 본문의 남은 높이를 다 쓴다. 넓은 화면에서 좌우 분할이 하는
     일과 같고(파일 위쪽 `@media (min-width: 992px)` 묶음), 사슬이 한 곳만
     끊겨도 지도가 바닥값(360px)으로 주저앉는다.

     본문은 `overflow: auto` 그대로 둔다 — 조회 판을 펴면 이 판이 눌릴
     자리가 없어 넘치는데, 그때는 쪽이 구르는 것이 맞다. */
  .jsini-shell__content:has(> .ad-split--phone) {
    display: flex;
    flex-direction: column;
  }

  .jsini-shell__content > .ad-split--phone {
    flex: 1;

    /* 격자 자식은 `min-height: auto` 라 내용보다 작아지지 않는다. */
    min-height: 16rem;
    align-items: stretch;

    /* 줄 높이를 내용이 정하지 않게 한다 — `auto` 로 두면 카드 목록이
       자료만큼 늘어나 위의 `flex: 1` 이 상한 노릇을 못 한다. */
    grid-template-rows: minmax(0, 1fr);
  }

  .ad-split--phone > .commcont {
    min-height: 0;
  }

  .ad-split--phone > .commcont > .commcont__body {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }

  .ad-split--phone .ad-geomap {
    flex: 1;
    min-height: 0;
  }

  /* 고른 사람의 카드는 판 아래를 가로로 채운다. 17rem 짜리 상자를 왼쪽에
     띄워 두면 360px 화면에서 지도의 절반을 가리면서 글자는 줄바꿈된다. */
  .ad-split--phone .ad-geomap__card {
    right: 0.5rem;
    width: auto;
  }

  /* 단추 둘이 한 줄에 서되 손가락이 닿을 만큼 벌린다. */
  .ad-split--phone .ad-geomap__acts {
    gap: 1rem;
  }
}

/* 카드를 닫는 ×. 휴대폰에서만 그린다(`_isPhone`). */
.ad-geomap__close {
  position: absolute;
  top: 0.2rem;
  right: 0.2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  padding: 0;
  color: var(--jsini-text-muted);
  cursor: pointer;
  background: transparent;
  border: 0;
}

/* ── 위치 지도 · 휴대폰의 카드 목록 ───────────────────────

   표(`CommGrd`)를 대신한다. 머리줄·쪽나누기·아래 띠가 없어 같은 높이에
   갑절이 들어오고, 칸 셋이 360px 안에서 줄바꿈되지 않는다.
   ---------------------------------------------------------- */

.ad-geolist {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

/* 찾는 칸. **목록과 함께 구르지 않는다** — 굴리다 조건을 고치려면 맨 위로
   올라와야 하고, 그 사이에 무엇을 보고 있었는지 잃는다. */
.ad-geolist__find {
  position: relative;
  flex: none;
  margin-bottom: 0.4rem;
}

.ad-geolist__find-icon {
  position: absolute;
  top: 50%;
  left: 0.6rem;
  transform: translateY(-50%);
  color: var(--jsini-text-muted);
  pointer-events: none;
}

.ad-geolist__input {
  width: 100%;
  height: 2.3rem;
  padding: 0 0.6rem 0 2rem;
  font: inherit;
  color: var(--jsini-text);
  background: var(--jsini-surface-raised);
  border: 1px solid var(--jsini-border);
  border-radius: var(--jsini-radius-md);
}

.ad-geolist__empty {
  margin: 1rem 0;
  color: var(--jsini-text-muted);
  text-align: center;
}

.ad-geolist__items {
  flex: 1;
  min-height: 0;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  list-style: none;
}

.ad-geolist__item {
  display: flex;
  align-items: stretch;
  gap: 0.3rem;
  border-bottom: 1px solid var(--jsini-border);
}

.ad-geolist__item--on {
  background: color-mix(in srgb, var(--jsini-accent) 12%, transparent);
}

/* 줄 전체가 「지도에서 보기」다. 좁은 화면에서 누를 자리를 따로 두면
   손가락이 빗나간다. */
.ad-geolist__go {
  flex: 1;
  min-width: 0;
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr) max-content;
  align-items: baseline;
  gap: 0.1rem 0.5rem;
  padding: 0.55rem 0.2rem;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  background: transparent;
  border: 0;
}

.ad-geolist__who {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ad-geolist__where {
  min-width: 0;
  overflow: hidden;
  color: var(--jsini-text-muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ad-geolist__when {
  color: var(--jsini-text-muted);
  font-size: var(--jsini-fs-2xs);
  white-space: nowrap;
}

.ad-geolist__mail {
  flex: none;
  width: 2.6rem;
  color: var(--jsini-accent);
  cursor: pointer;
  background: transparent;
  border: 0;
}

/* 쪽지가 안 닿는 사람. 점의 흐림·목록의 흐림과 같은 뜻을 같은 색으로. */
.ad-geolist__mail:disabled {
  color: var(--jsini-text-muted);
  cursor: default;
  opacity: 0.5;
}

/* ── 가입 신청: 신청자 얼굴과 가입 경로 ──────────────────────────────

   사진은 소셜 공급자가 준 것뿐이다. 없으면 이름 첫 글자를 같은 크기의
   동그라미에 담아 줄이 들쭉날쭉하지 않게 한다. */
.ad-signup__who {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  min-width: 0;
}

.ad-signup__face {
  flex: none;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  object-fit: cover;
  background: var(--jsini-border, #e5e7eb);
}

.ad-signup__face--letter {
  display: grid;
  place-items: center;
  color: var(--jsini-text-muted);
  font-size: 0.8125rem;
  font-weight: 600;
}

.ad-signup__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 가입 경로. 소셜은 공급자 색 — 로그인·가입 화면의 단추와 같은 색이라
   「어느 단추로 들어왔는지」가 한눈에 이어진다(app.css 의 `.jsini-social__btn--*`). */
.ad-signup__route {
  display: inline-block;
  padding: 0.125rem 0.5rem;
  border: 1px solid var(--jsini-border, #e5e7eb);
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
}

.ad-signup__route--kakao { border-color: #fee500; background: #fee500; color: rgb(0 0 0 / 85%); }
.ad-signup__route--naver { border-color: #03c75a; background: #03c75a; color: #fff; }
.ad-signup__route--google { border-color: #dadce0; background: #fff; color: #1f1f1f; }

.ad-signup__route--direct {
  color: var(--jsini-text-muted);
  font-weight: 500;
}

/* ── 오류 추적 상세 ──────────────────────────────────────────
   읽는 사람이 표에서 줄 하나를 고르고 **그 아래에서 눈을 떼지 않는다.**
   그래서 요청 정보는 좁은 정의 목록으로 눌러 두고, 자리의 대부분을
   스택에 준다 — 실제로 읽는 것이 그것이다. */
.ad-error__facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.3rem 1rem;
  margin: 0 0 0.75rem;
  font-size: var(--jsini-fs-md);
}

.ad-error__facts dt {
  color: var(--jsini-text-muted);
  white-space: nowrap;
}

.ad-error__facts dd {
  margin: 0;
  min-width: 0;
  word-break: break-all;
}

/* 기기 문자열은 한 줄이 200자를 넘는다. 줄여 두고 눌러서 펴 보게 할 수도
   있지만, 이 칸이야말로 「모바일에서만 난다」를 확인하는 자리라 통째로 둔다. */
.ad-error__ua {
  font-size: 0.8125rem;
  color: var(--jsini-text-muted);
}

/* 스택은 **접지 않는다.** 긴 줄을 꺾으면 들여쓰기가 무너져서 호출 순서를
   눈으로 좇을 수 없다 — 가로로 굴리는 편이 읽힌다. */
.ad-error__stack {
  margin: 0;
  padding: 0.75rem 1rem;
  max-height: 32rem;
  overflow: auto;
  font-size: 0.8125rem;
  line-height: 1.5;
  background: var(--jsini-surface-sunken, #f5f5f7);
  border: 1px solid var(--jsini-border, #e5e7eb);
  border-radius: 6px;
  white-space: pre;
}

/* ── 차량 관리 — 번호판이 말한 것 ─────────────────────────── */

/* 차량번호 칸 바로 아래 한 줄. 운송관리 쪽(`ct-plate`)과 같은 모양이지만
   모듈이 둘이라 복제한다 — 세 번째가 생기면 그때 app.css 로 올린다. */
.ad-plate {
  padding: var(--jsini-sp-sm) var(--jsini-sp-md);
  border-left: 3px solid var(--jsini-border);
  border-radius: var(--jsini-radius-sm);
  background: var(--jsini-surface-2);
  font-size: var(--jsini-fs-sm);
}

.ad-plate--ok { border-left-color: var(--jsini-success); }
.ad-plate--warn { border-left-color: var(--jsini-warning); }
.ad-plate--idle { color: var(--jsini-text-dim); }

.ad-veh-plate {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
}

.ad-veh-plate-read {
  font-size: var(--jsini-fs-xs);
  color: var(--jsini-text-dim);
}

/* ── LLM 장비 상태 ────────────────────────────────────────────
   GPU 두 장을 나란히 놓고, 아래로 CPU·메모리·서비스·디스크를 쌓는다.
   카드 틀(.ad-card)과 배지(.jsini-badge)는 MSA 서버 상태 화면과 같은 것을 쓴다 */

.ad-llm__head {
    margin: 1.5rem 0 .6rem;
    font-size: .95rem;
    font-weight: 600;
    color: var(--tb-text);
    display: flex;
    align-items: baseline;
    gap: .6rem;
}

.ad-llm__dim { color: var(--tb-muted); font-weight: 400; font-size: .85em; }

.ad-llm__panel {
    background: var(--tb-surface);
    border: 1px solid var(--tb-border);
    border-radius: .5rem;
    padding: .75rem 1rem;
    overflow-x: auto;
}

/* 가로로 채우는 막대 — VRAM·메모리 */
.ad-llm__bar {
    position: relative;
    height: 1.5rem;
    background: var(--tb-surface-3);
    border-radius: .25rem;
    overflow: hidden;
    margin-bottom: .6rem;
}

.ad-llm__bar-fill { height: 100%; transition: width .4s ease; }
.ad-llm__bar--up   { background: rgba(var(--tb-green-rgb), .45); }
.ad-llm__bar--warn { background: rgba(var(--tb-yellow-rgb), .5); }
.ad-llm__bar--down { background: rgba(var(--tb-red-rgb), .45); }

.ad-llm__bar-text {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: .78rem;
    color: var(--tb-text);
}

/* 이름-값 줄. 칸이 좁아지면 한 줄로 접힌다 */
.ad-llm__kv {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: .25rem .75rem;
    margin: 0 0 .4rem;
    font-size: .82rem;
}

.ad-llm__kv dt { color: var(--tb-muted); white-space: nowrap; }
.ad-llm__kv dd { margin: 0; }

/* 코어별 막대. 서른두 개가 한 줄에 들어가야 해서 세로 막대다 */
.ad-llm__cores {
    display: flex;
    align-items: flex-end;
    gap: 2px;
    height: 2.2rem;
    margin-top: .5rem;
}

.ad-llm__core {
    flex: 1 1 auto;
    min-width: 3px;
    height: 100%;
    background: var(--tb-surface-3);
    border-radius: 1px;
    display: flex;
    align-items: flex-end;
}

.ad-llm__core-fill { width: 100%; border-radius: 1px; transition: height .4s ease; }

.ad-llm__procs { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .4rem; }

.ad-llm__proc {
    background: var(--tb-surface-3);
    border-radius: .25rem;
    padding: .15rem .45rem;
    font-size: .76rem;
    display: inline-flex;
    gap: .35rem;
    align-items: baseline;
}

.ad-llm__table {
    width: 100%;
    border-collapse: collapse;
    font-size: .82rem;
    margin-bottom: .5rem;
}

.ad-llm__table th,
.ad-llm__table td {
    text-align: left;
    padding: .35rem .5rem;
    border-bottom: 1px solid var(--tb-border);
    white-space: nowrap;
}

.ad-llm__table th { color: var(--tb-muted); font-weight: 500; }
.ad-llm__table tr:last-child td { border-bottom: none; }

/* 0 이 아니면 눈에 걸려야 하는 숫자 — 재시작 횟수·오류 수 */
.ad-llm__warnnum { color: var(--tb-red); font-weight: 600; }

.ad-llm__foot { display: block; margin-top: 1rem; }

/* ── 메뉴 사용기록 타임라인 (`ad-usagetl`) ─────────────────

   **표로 그리지 않는 까닭은 읽는 방향이 다르기 때문이다.** 표는 칸을 견주어
   보는 것이고, 여기서 사람이 보는 것은 「위에서 아래로 하루가 어떻게
   흘렀나」다. 날짜가 머리띠로 서고 그 아래에 시각이 줄줄이 서야 그 읽기가
   된다.

   판(`.commcont__body`)이 세로 flex 라 **여기서 남은 높이를 받아 스스로
   구른다.** `min-height: 0` 이 없으면 내용만큼 부풀어 판 밖으로 넘친다 —
   이 저장소에서 높이 계산이 통째로 어긋나는 바로 그 자리다.
   ---------------------------------------------------------- */

.ad-usagetl {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding-right: .25rem;
}

.ad-usagetl__day + .ad-usagetl__day { margin-top: 1.1rem; }

/* 날짜 머리띠는 **구르는 동안 붙어 있다.** 하루가 수백 줄이면 한참 내려간
   뒤에 「지금 보는 것이 며칟날인가」를 알 길이 없어진다. */
.ad-usagetl__date {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .5rem;
  padding: .35rem 0;
  background: var(--tb-surface);
  border-bottom: 1px solid var(--tb-border);
}

.ad-usagetl__datetext { font-weight: 600; }

.ad-usagetl__list {
  list-style: none;
  margin: 0;
  padding: .4rem 0 0 0;

  /* 점들을 잇는 세로줄. 줄 자체가 「이어진 하루」를 말한다. */
  border-left: 2px solid var(--tb-border);

  /* **시각이 설 자리를 왼쪽에 비워 둔다.** 시각은 줄 밖(`left: -4.3rem`)에
     놓이는데, 이 여백이 그보다 좁으면 판이 구르느라 가로도 잘라서
     「0:00:00」이 「:00:00」으로 보인다(실제로 밟음). */
  margin-left: 4.6rem;
}

.ad-usagetl__item {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .5rem;
  padding: .22rem 0 .22rem .9rem;
  font-size: .86rem;
}

/* 시각은 세로줄 **왼쪽**에 둔다. 줄 안에 두면 화면 이름의 시작 자리가
   시각의 글자 수에 따라 흔들린다. */
.ad-usagetl__time {
  position: absolute;
  left: -4.3rem;
  width: 3.4rem;
  text-align: right;
  color: var(--tb-muted);
  font-variant-numeric: tabular-nums;
}

.ad-usagetl__dot {
  position: absolute;
  left: -.3rem;
  top: .55rem;
  width: .42rem;
  height: .42rem;
  border-radius: 50%;
  background: var(--tb-primary);
}

.ad-usagetl__name { font-weight: 500; }

.ad-usagetl__who { color: var(--tb-muted); }

/* 「앞 칸에서 여기까지」. 값이지 설명이 아니라서 옅게 두되 지우지는 않는다 —
   하루가 어디서 끊겼는지는 이 숫자로만 보인다. */
.ad-usagetl__gap {
  color: var(--tb-muted);
  font-size: .78rem;
  font-variant-numeric: tabular-nums;
}

/* 휴대폰에서는 시각을 줄 안으로 들인다. 왼쪽 여백 3.4rem 은 좁은 화면에서
   화면 이름이 설 자리를 그만큼 빼앗는다. */
@media (max-width: 767px) {
  .ad-usagetl__list { margin-left: .4rem; }

  .ad-usagetl__time {
    position: static;
    width: auto;
    text-align: left;
  }
}

/* ── 보고서 메일 — 배치 목록 + 좌우 분할 ──────────────────

   이 화면은 셸 본문의 직속 자식이 셋이다(조회줄 · 배치 목록 · 좌우 분할).
   그중 **배치 목록을 맨 `.commcont` 로 두면 안 된다** — 공통 규칙(app.css 의
   `.jsini-shell__content:has(> .commcont) > .commcont`)이 그 판에 `flex: 1` 을
   걸어 남은 높이를 통째로 먹고, 아래 좌우 분할이 눌린다. 그래서 `.rm-top` 으로
   한 겹 감싼다 — 감싸면 그 규칙의 `> ` 가 안 맞아 안 걸리고, 좌우 분할의
   높이 규칙(`.ad-split`)만 남는다.

   그 대신 목록의 높이는 **쪽나누기가 묶는다**(`PagerVisible="true"`).
   가상 스크롤로 두면 판이 자료만큼 늘어나 같은 자리로 돌아온다.
   ---------------------------------------------------------- */

.rm-top {
  /* 제 내용만큼만. 늘어나는 것은 아래 좌우 분할이다. */
  flex: none;
}

/* 좌우 분할 바로 위라 사이를 벌린다. `.commcont + .commcont` 는 둘 다
   `.commcont` 일 때만 걸리는데 여기서는 사이에 `.rm-top` 이 끼어 있다. */
.rm-top + .ad-split {
  margin-top: 1.4rem;
}

/* 오른쪽 판 — 표 하나와 폼이 쌓인다. **안쪽에서 구른다**(메뉴롤의
   `.ad-menurole` 과 같은 사정) — 판이 `overflow: hidden` 이라 넘기면 잘린다. */
.rm-edit {
  flex: 1;
  min-height: 0;
  overflow-y: auto;

  /* **가로는 막는다.** 한 축만 `auto` 로 두면 다른 축이 따라서 `auto` 가 되고
     (CSS 규칙이다), 안쪽 표가 1px 만 넘쳐도 **가로 막대가 판 아래에 걸린다** —
     잴 때 실제로 그랬다. 넘칠 것은 표가 제 안에서 가로로 구른다. */
  overflow-x: hidden;
}

/* 시·분 두 칸을 한 줄로. 칸 사이의 쌍점이 무엇과 무엇 사이인지 말해 준다. */
.rm-time {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.rm-time .dxbl-spin-edit {
  width: 5rem;
}

/* 저장·삭제 줄. 표의 아래 띠가 아니라 폼 아래라서 여기서 자리를 잡는다. */
.rm-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 1rem;
}

/* 되돌릴 수 없는 단추만 경고색. 테마가 정한 값을 그대로 읽는다. */
.rm-actions__warn {
  color: var(--dxbl-danger, #d63939);
}

/* ── 내 알림함 · 휴대폰의 카드 목록 (`NotificationHistory`) ────

   표(`CommGrd`)를 대신한다. 그 표는 칸이 일곱이고(체크 · 받은 때 · 구분 ·
   제목 · 내용 · 도착 · 상태) 그중 둘은 글이라 넓어야 해서, 360px 화면에서는
   **가로로 굴려야** 제목 너머를 볼 수 있었다. 알림함에서 가장 자주 읽는 값인
   「내용」이 바로 그 스크롤 저편에 있었다.

   여기서는 한 건이 한 장이고 **세로로 편다** — 윗줄에 딱지와 받은 때,
   그 아래 제목, 그 아래 내용. 칸이 하나라 가로로 구를 일이 없다.
   먼저 같은 길을 간 자리가 헬프데스크의 `.hd-req-list` 다.
   ---------------------------------------------------------- */

/* ── 그 목록이 실린 판은 **쪽이 구른다** ─────────────────────

   셸 본문은 자료 판(`CommCont`)을 품으면 남은 높이를 그 판에 다 주고 넘치는
   것을 잘라 낸다(`app.css` 의 `.jsini-shell__content:has(> .commcont)`).
   표에는 맞는 규칙이다 — 표가 제 안에서 구르니까. 그런데 카드 목록은
   **줄이 전부 깔린다.** 그대로 두면 스무 줄 중 화면에 든 몇 줄만 보이고
   나머지와 「더보기」가 통째로 잘린다(오류는 안 난다).

   화면이 휴대폰일 때만 이 클래스를 붙이므로 폭 조건을 따로 걸지 않는다.
   자릿수가 `app.css` 쪽과 같고 **admin.css 가 뒤에 실리므로** 이긴다. */
.jsini-shell__content:has(> .ad-notebox--phone),
.jsini-shell__content:has(> .ad-logbox--phone) {
  overflow: auto;
}

.jsini-shell__content:has(> .ad-notebox--phone) > .ad-notebox--phone,
.jsini-shell__content:has(> .ad-logbox--phone) > .ad-logbox--phone {
  flex: 0 0 auto;
}

.jsini-shell__content:has(> .ad-notebox--phone) > .ad-notebox--phone > .commcont__body,
.jsini-shell__content:has(> .ad-logbox--phone) > .ad-logbox--phone > .commcont__body {
  display: block;
}

/* 일괄 동작 띠. 표의 `Toolbar` 와 같은 자리에 같은 것이 선다.

   흘려 담는다 — 「전체」 체크 · 고른 건수 · 단추 둘이 360px 한 줄에 다 들어가지
   않으면 아래로 접힌다. 옆으로 넘치게 두면 카드를 세로로 편 뜻이 띠에서
   무너진다. */
.ad-notecards__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 0.6rem;
  padding: 0.5rem 1rem;
  border-bottom: 1px solid var(--jsini-border);
}

/* 단추 둘은 늘 오른쪽 끝에 붙어 다닌다. 줄이 접혀도 둘은 안 갈라진다. */
.ad-notecards__acts {
  display: flex;
  gap: 0.4rem;
  margin-left: auto;
}

.ad-notecards {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* 한 장은 **띠 둘과 줄 하나**다. 띠는 밀기 전에는 폭이 0 이라 안 보이고
   (바로 아래 묶음), 보이는 것은 `.ad-notecards__row` 뿐이다.

   `overflow: hidden` 은 밀려난 줄이 카드 밖으로 비어져 나오지 않게 한다 —
   목록에 가로 스크롤이 생기면 미는 동안 판이 함께 흔들린다. */
.ad-notecards__item {
  position: relative;
  overflow: hidden;
  border-top: 1px solid var(--jsini-border);
}

/* 판이 이미 테두리를 두르고 있어 첫 줄 위의 선은 두 겹이 된다. */
.ad-notecards__item:first-child {
  border-top: 0;
}

/* 한 줄은 `체크 | 글 | ✓` 세 칸이다. 글 칸만 늘어나고 양옆은 제 폭을 지킨다 —
   `minmax(0, 1fr)` 이 없으면 긴 제목이 칸을 밀어 **다시 가로로 구른다**.

   줄 높이를 못 박지 않는다. 내용이 길면 그만큼 늘어나면 되고, 쪽이 구르므로
   아래가 밀릴 뿐이다. */
.ad-notecards__row {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr) max-content;
  align-items: start;
  gap: 0.2rem 0.5rem;
  padding: 0.6rem 1rem;
}

/* 안 읽은 것은 **왼쪽에 선 하나**로 가른다. 표에서는 「읽음」 딱지가 붙고 안
   붙고로 갈렸는데, 카드에서는 그 딱지가 딱지 셋 사이에 섞여 눈에 안 들어온다.
   안쪽 그림자라 자리를 안 먹어 글이 줄마다 같은 자리에서 시작한다.

   **카드가 아니라 줄에 건다.** 카드에 걸면 오른쪽으로 미는 동안 그 선이
   제자리에 남아, 열린 초록 띠 위에 파란 줄이 한 가닥 떠 있게 된다. */
.ad-notecards__item--unread > .ad-notecards__row {
  box-shadow: inset 3px 0 0 var(--jsini-accent);
}

/* ── 밀어서 읽음 · 밀어서 삭제 ──────────────────────────────

   서랍의 카드(`app.css` 의 `.jsini-note-drawer__card`)와 **같은 짝이고 같은
   변수**다 — 대는 쪽이 같은 모듈이기 때문이다(`js/note-swipe.js`). 생김새
   규칙만 여기 따로 적는다: 서랍은 카드가 떠 있는 상자이고 이쪽은 선으로
   나뉜 목록이라, 모서리와 테두리를 함께 쓸 수 없다.

     --note-swipe     밀린 거리. **부호가 있다** — 양수 오른쪽(읽음), 음수 왼쪽(삭제)
     --note-swipe-ms  줄과 띠의 전환 시간. 끄는 동안은 0
     --note-ready     문턱을 넘었나(0/1)
     --note-alive     살아 있나(1) 스러지는 중인가(0)

   `touch-action: pan-y` 가 이 기능의 핵심 한 줄이다. 알림함은 한 달치가
   쌓이는 목록이라 **굴리는 것이 미는 것보다 잦다** — 세로 끌기는 브라우저가
   제 스크롤로 가져가고 가로 끌기만 우리에게 온다. 안 걸면 옆으로 밀어도
   브라우저가 스크롤로 삼키고, 통째로 막으면 이번엔 카드 위에서 목록을
   위아래로 굴릴 수 없다. */
.ad-notecards__item[data-note] {
  --note-swipe: 0px;
  --note-swipe-ms: 0ms;
  --note-ready: 0;
  --note-alive: 1;

  touch-action: pan-y;
  opacity: var(--note-alive);
  transition: opacity 180ms ease;
}

/* 좌우에서 열리는 띠. **미는 만큼만 넓어진다**(`max()` 가 부호 하나에서 두
   폭을 뽑는다) — 가만히 있을 때는 둘 다 폭이 0 이라 보이지 않는다.

   카드 밑에 깔아 두고 줄로 덮는 흔한 방법을 쓰지 않았다. 그러려면 줄에
   불투명한 바탕을 깔아야 하는데, 카드 바탕은 테마가 정하는 값이라 어긋나는
   쪽이 반드시 나온다.

   **안쪽 여백을 주지 않는다.** 주면 폭이 0 이어도 상자가 그만큼 남아
   (`width` 는 여백 아래로 못 내려간다) 줄마다 양옆에 **실오라기**가 늘 선다. */
.ad-notecards__act {
  position: absolute;
  inset-block: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 0.25rem;
  overflow: hidden;
  color: #fff;
  font-size: var(--jsini-fs-2xs);
  white-space: nowrap;
  pointer-events: none;
  transition: width var(--note-swipe-ms) ease;
}

/* 초록은 ✓ 단추가 하는 일과 같은 뜻이다. 빨강은 **되돌릴 수 없다**는 뜻이고,
   둘이 갈려야 문턱 앞에서 「지금 놓으면 어느 쪽이 되나」를 눈으로 가린다.
   색은 서랍의 띠와 **같은 값**이다 — 같은 손짓이 자리마다 다른 색이면
   둘이 다른 일로 읽힌다. */
.ad-notecards__act--read {
  left: 0;
  width: max(0px, var(--note-swipe));
  background: #2e7d32;
}

.ad-notecards__act--del {
  right: 0;
  justify-content: flex-end;
  width: max(0px, calc(-1 * var(--note-swipe)));
  background: #c62828;
}

/* 문턱을 넘으면 그림이 커지고 글자가 또렷해진다. **손을 떼기 전에**
   「놓으면 처리된다」를 알려 주는 것이 이 둘의 일이다. */
.ad-notecards__act-mark {
  flex: 0 0 auto;
  font-size: var(--jsini-fs-sm);
  line-height: 1;
  transform: scale(calc(0.7 + (0.3 * var(--note-ready))));
  transition: transform 120ms ease;
}

.ad-notecards__act--read .ad-notecards__act-mark { margin-left: 0.6rem; }
.ad-notecards__act--del .ad-notecards__act-mark { margin-right: 0.6rem; }

.ad-notecards__act-text {
  opacity: calc(0.45 + (0.55 * var(--note-ready)));
  transition: opacity 120ms ease;
}

/* 밀리는 것은 카드가 아니라 **줄**이다. 카드 자신을 옮기면 띠도 함께 가서
   아무것도 열리지 않는다. 띠 위에 놓는 것(`z-index`)도 필요하다 — 띠가 절대
   자리라 그대로 두면 문서에서 뒤인 띠가 줄을 덮어 제목이 색에 가린다.

   바탕을 깐다. 목록은 줄이 맞닿아 있어서, 미는 줄 아래로 **다음 줄의 글자가
   비쳐 보이면** 두 알림이 한 장처럼 읽힌다 — 카드가 떠 있는 서랍과 갈리는
   자리가 여기다. */
.ad-notecards__item[data-note] > .ad-notecards__row {
  position: relative;
  z-index: 2;
  background: var(--jsini-bg);
  transform: translateX(var(--note-swipe));
  transition: transform var(--note-swipe-ms) ease;
}

/* 움직임을 줄여 달라고 한 사람에게는 **결과만** 준다 — 처리됐다는 것은
   카드가 목록에서 빠지는 것으로 이미 말한다. */
@media (prefers-reduced-motion: reduce) {
  .ad-notecards__item[data-note],
  .ad-notecards__item[data-note] > .ad-notecards__row,
  .ad-notecards__act,
  .ad-notecards__act-mark,
  .ad-notecards__act-text {
    transition: none;
  }
}

.ad-notecards__check {
  display: flex;
  align-items: center;
  min-height: 1.6rem;
}

/* 누르는 자리는 글 칸 전체다. 손가락에는 「열기」 단추 하나보다 이쪽이 낫다. */
.ad-notecards__body {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 0;
  cursor: pointer;

  /* 누를 때 안드로이드 크롬이 칠하는 파란 네모를 걷는다 — 칸 전체가 누르는
     자리라 그 네모가 카드만 하게 번쩍인다. */
  -webkit-tap-highlight-color: transparent;
}

.ad-notecards__body:active {
  background: var(--jsini-surface);
}

/* 얹는 한 줄 — 표에서 칸 넷이던 것(구분 · 도착 · 상태 · 받은 때)이 여기 든다.
   흘려 담으므로 딱지가 몇 개든 아래로 접힐 뿐 옆으로 안 넘친다. */
.ad-notecards__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.2rem 0.35rem;
  font-size: var(--jsini-fs-xs);
  line-height: 1.2;
  color: var(--jsini-text-muted);
}

/* 받은 때는 줄 끝으로 민다. 딱지가 몇 개든 날짜는 늘 같은 쪽에 있어 눈이
   훑기 쉽다. 줄이 접히면 제 줄의 끝으로 간다. */
.ad-notecards__when {
  margin-left: auto;
  font-size: var(--jsini-fs-2xs);
  white-space: nowrap;
}

/* 두 줄까지 받고 그 뒤는 … 로 자른다. 안 자르면 긴 제목 하나가 카드 한 장을
   통째로 먹어 목록이 목록으로 안 보인다. */
.ad-notecards__title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-weight: 600;
  line-height: 1.35;
  color: var(--jsini-text);
}

/* 표에서 가로로 굴려야 보이던 그 칸이다. 네 줄까지 받는다 — 더 긴 것은 눌러서
   그 알림이 가리키는 화면에서 본다. */
.ad-notecards__text {
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin: 0;
  font-size: var(--jsini-fs-sm);
  line-height: 1.4;
  color: var(--jsini-text-muted);
  white-space: pre-line;
}

/* 읽음 처리 단추 자리. 읽은 줄에는 아예 안 그리므로(머리말) 그때는 이 칸이
   폭 0 으로 접히고 글이 그만큼 넓어진다. */
.ad-notecards__mark {
  display: flex;
  align-items: center;
  min-height: 1.6rem;
}

.ad-notecards__empty {
  margin: 0;
  padding: 1.5rem 1rem;
  text-align: center;
  color: var(--jsini-text-muted);
}

/* 「더보기」 띠. 목록 **맨 끝**이 맞는 자리다 — 다 보고 나서 누르는 단추라
   화면 아래에 붙박아 두면 아직 볼 것이 남았는데도 손가락 밑에서 어른거린다.

   좌우 여백은 이 띠가 스스로 갖는다. 휴대폰에서는 판 안쪽 여백이 걷혀 있어
   (tabler.css) 안 주면 단추가 판 가장자리에 붙는다. 값은 카드 줄과 같은
   1rem 이라 단추의 양 끝이 위 글과 세로줄을 맞춘다.

   아래로 4.25rem 을 비우는 것은 **떠다니는 단추**(☰ · ✎) 때문이다 — 폭을
   끝까지 쓴 단추의 양 끝이 그 동그라미에 덮인다. 값은 `.jsini-shell__fab`
   의 자리(3rem + 0.75rem)에 숨 쉴 틈을 더한 것이다. */
.ad-notecards__more {
  padding: 0.6rem 1rem 0;
  margin-bottom: 4.25rem;
}

.ad-notecards__more-go {
  width: 100%;
}

/* 다 깔고 나서도 **자리에 남는다 — 눌리지 않는 채로.** 걷으면 「끝」이라는
   말도 함께 걷히는데, 표 머리줄도 페이저도 없는 화면이라 목록이 끊긴 자리에
   아무 표시가 없으면 사람은 그것을 「덜 불러온 것」으로 읽는다. */
.ad-notecards__more-go:disabled {
  opacity: 0.6;
}

.ad-notecards__more-sentinel {
  display: none;
}

@media (max-width: 767px) {
  .ad-notecards__more-sentinel {
    display: block;
    height: 1px;
  }
}

/* ── 발송 이력 — 휴대폰 카드 목록 (`PushLogs`) ────────────────────
   모바일에서는 폭이 좁아 표의 여러 칸(보낸 때, 대상, 구분, 제목, 내용, 결과,
   실패 사유, 보낸 사람)을 가로 스크롤 없이 편하게 읽을 수 있도록 세로 카드로 편다. */
.ad-logcards {
  list-style: none;
  margin: 0;
  padding: 0;
}

.ad-logcards__item {
  position: relative;
  overflow: hidden;
  border-top: 1px solid var(--jsini-border);
}

.ad-logcards__item:first-child {
  border-top: 0;
}

.ad-logcards__body {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 0.75rem 1rem;
}

.ad-logcards__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.4rem;
  font-size: var(--jsini-fs-xs);
  line-height: 1.2;
}

.ad-logcards__when {
  margin-left: auto;
  font-size: var(--jsini-fs-2xs);
  color: var(--jsini-text-muted);
  white-space: nowrap;
}

.ad-logcards__title {
  font-weight: 600;
  font-size: var(--jsini-fs-base);
  line-height: 1.35;
  color: var(--jsini-text);
  word-break: break-word;
}

.ad-logcards__text {
  margin: 0;
  font-size: var(--jsini-fs-sm);
  line-height: 1.4;
  color: var(--jsini-text-muted);
  white-space: pre-line;
  word-break: break-word;
}

.ad-logcards__footer {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 0.8rem;
  font-size: var(--jsini-fs-xs);
  color: var(--jsini-text-muted);
  margin-top: 0.15rem;
}

.ad-logcards__info-label {
  font-weight: 500;
  color: var(--jsini-text-muted);
}

.ad-logcards__failure {
  font-size: var(--jsini-fs-xs);
  color: var(--jsini-danger, #d63939);
  background: var(--jsini-danger-subtle, rgba(214, 57, 57, 0.08));
  padding: 0.3rem 0.5rem;
  border-radius: var(--jsini-radius-sm, 4px);
  margin-top: 0.2rem;
  word-break: break-word;
}

.ad-logcards__failure-label {
  font-weight: 600;
  margin-right: 0.2rem;
}

/* ── 알림관리 (/admin/system/notify-policy) ──────────────────
   보고서 메일(`.rm-*`)과 뼈대가 같다 — 좌우 분할에 왼쪽이 목록, 오른쪽이
   폼이다. 이름을 나눠 둔 것은 **오른쪽에 쌓이는 것이 다르기 때문**이다:
   그쪽은 표 둘과 폼이고 이쪽은 설명 묶음과 표 하나다. */

/* 오른쪽 판 — **안쪽에서 구른다**. 판이 `overflow: hidden` 이라 넘기면 잘린다
   (`.rm-edit` 과 같은 사정). 가로를 막는 까닭도 거기 적어 두었다. */
.np-edit {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
}

/* ── 왼쪽: 이벤트 카드 목록 ───────────────────────────────

   칸이 둘(이벤트 · 받는 설정)뿐인 표였다. 정작 고르는 데 필요한 것 —
   **설명과 발생처** — 은 오른쪽을 눌러 봐야 보였다. 이벤트가 열다섯뿐이라
   한 장씩 펴 두어도 목록이 길지 않다.

   **목록만 구른다.** 판이 `overflow: hidden` 이라 여기서 안 구르면
   아래쪽 카드가 잘려 아예 못 고른다.
   ---------------------------------------------------------- */

.np-events-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.6rem;
}

.np-events-head .commcont__title {
  margin: 0;
}

.np-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

@media (width < 992px) {
  .np-list {
    max-height: none;
    overflow-y: visible;
  }
}

@media (width >= 992px) {
  .np-list {
    flex: 1;
    min-height: 6rem;
    max-height: none;
    overflow-y: auto;
  }
}

.np-list__empty {
  margin: 0.4rem 0;
  font-size: var(--jsini-fs-sm);
  color: var(--jsini-text-muted);
}

/* 카드는 단추다 — 눌러서 오른쪽을 바꾸는 것이므로 키보드로도 닿아야 한다.
   지금 상태는 왼쪽 굵은 선으로 말한다(`ad-card`·`ad-msa__card` 와 같은
   규칙). 색만으로 가르지 않는다 — 아래 줄에 받는 설정이 늘 글자로 선다. */
.np-card {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 0.5rem 0.65rem;
  border: 1px solid var(--jsini-border, #e0e0e0);
  border-left-width: 3px;
  border-radius: 0.5rem;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.np-card:hover { background: color-mix(in srgb, currentcolor 4%, transparent); }

/* 고른 카드. 테두리 폭을 바꾸면 카드가 흔들려 보이므로 테를 덧그린다.

   **왼쪽 변은 건드리지 않는다.** `border-color` 한 줄로 네 변을 칠하면 아래
   갈래 규칙보다 선택자가 세서(`.np-card.is-on`) 고른 카드만 제 상태 색을
   잃는다 — 하필 지금 보고 있는 그 카드다. */
.np-card.is-on {
  border-top-color: var(--jsini-accent);
  border-right-color: var(--jsini-accent);
  border-bottom-color: var(--jsini-accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--jsini-accent) 35%, transparent);
}

/* 역할을 걸어 둔 것(on) · 제한 없이 나가는 것(free) · 꺼 둔 것(warn) ·
   정책이 안 걸리는 것(off). 네 갈래가 이 화면에서 묻는 것의 전부다.

   「제한 없음」에 **파랑을 쓰지 않는다.** 지금 열다섯 중 열하나가 그 상태라
   목록이 통째로 한 색인데, 강조색(`--jsini-accent`)도 파랑이라 **고른
   카드의 테와 구분이 안 된다**(재어 보니 `#066fd1` 과 `#1565c0` 이었다). */
.np-card--on { border-left-color: #2e7d32; }
.np-card--free { border-left-color: #00838f; }
.np-card--warn { border-left-color: #b26a00; }
.np-card--off { border-left-color: var(--jsini-text-muted); }

.np-card__top {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
}

/* 이름은 한 줄로 자른다. 두 줄로 흐르면 카드 높이가 제각각이 되어
   목록을 훑는 눈이 걸린다. */
.np-card__name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}

.np-card__switch {
  display: inline-flex;
  align-items: center;
  flex: none;
  cursor: default;
}

.np-card__switch .dxbl-checkbox {
  margin: 0;
}

/* 설명은 **두 줄까지**. 이것을 보여 주려고 표를 걷은 것이라 한 줄로
   자르면 뜻이 끊기고, 다 펴면 카드 하나가 화면을 먹는다. */
.np-card__desc {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  font-size: var(--jsini-fs-sm);
  line-height: 1.45;
  color: var(--jsini-text-muted);
}

.np-card__meta {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
  font-size: var(--jsini-fs-sm);
}

.np-card__chan {
  flex: none;
  font-weight: 600;
}

/* 발생처는 오른쪽 끝으로 민다 — 「어느 서비스가 보내나」는 훑는 값이지
   읽는 값이 아니다. */
.np-card__src {
  margin-left: auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--jsini-text-muted);
}

/* 이벤트 설명 한 줄. 제목 바로 아래라 본문보다 조금 작고 흐리다. */
.np-desc {
  margin: 0.2rem 0 0.9rem;
  font-size: 0.9em;
  line-height: 1.5;
  color: var(--dxbl-text-muted, #6c757d);
}

/* 발생처·알림구분·대상·길 네 쌍. **표로 두지 않는다** — 줄이 넷뿐이라
   표 머리글이 자리만 먹고, 좁은 화면에서는 한 열로 접혀야 한다. */
.np-facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.25rem 0.8rem;
  margin: 0 0 0.9rem;
  font-size: 0.9em;
}

.np-facts dt {
  color: var(--dxbl-text-muted, #6c757d);
}

.np-facts dd {
  margin: 0;
}

.np-switch {
  margin: 0.9rem 0;
}

/* ── 역할별 수신 목록 ──────────────────────────────────── */
.np-roles-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1rem;
  margin-top: 1rem;
  margin-bottom: 0.6rem;
}

.np-roles-head .ad-section {
  margin: 0;
}

.np-roles-all {
  display: flex;
  align-items: center;
  gap: 1rem;
  font-size: var(--jsini-fs-sm, 0.85rem);
}

.np-roles {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin-bottom: 0.8rem;
}

.np-role-item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1rem;
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--jsini-border, #e0e0e0);
  border-radius: 0.5rem;
  background: var(--jsini-bg, #fff);
}

.np-role-info {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}

.np-role-name {
  font-weight: 600;
  color: var(--jsini-text, inherit);
}

.np-role-id {
  font-size: var(--jsini-fs-sm, 0.85rem);
}

.np-role-checks {
  display: flex;
  align-items: center;
  gap: 1.2rem;
  margin-left: auto;
}

@media (max-width: 576px) {
  .np-role-item {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.45rem;
  }

  .np-role-checks {
    margin-left: 0;
    width: 100%;
    padding-top: 0.35rem;
    border-top: 1px dashed var(--jsini-border, #e0e0e0);
  }
}

/* 받는 사람 등 작업 버튼. 표의 아래 띠가 아니라 폼 아래라서 여기서 잡는다. */
.np-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 1rem;
}

/* 「받는 사람」 창의 비고 칸 — 못 닿는 까닭이다. 길어서 줄이 넘어간다. */
.np-why {
  display: inline-block;
  line-height: 1.4;
  white-space: normal;
  color: var(--dxbl-text-muted, #6c757d);
}

/* ── 알림관리 탭 (`.ad-nptabs`) ─────────────────────────────
   이벤트 중심 / 역할 중심 탭이 판 높이를 채우고 안쪽 splitter 가
   남은 높이를 쓴다.
   ---------------------------------------------------------- */
.ad-nptabs {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.ad-nptabs > .dxbl-tabs-content-panel,
.ad-nptabs .dxbl-tabs-content {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

@media (width >= 992px) {
  .jsini-shell__content:has(> .ad-nptabs) {
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }

  .jsini-shell__content > .ad-nptabs {
    flex: 1;
    min-height: 0;
  }

  .ad-nptabs .ad-vsplit {
    flex: 1;
    min-height: 0;
  }
}

.np-role-event-desc {
  width: 100%;
  margin: 0.15rem 0 0.35rem;
  font-size: var(--jsini-fs-sm, 0.85rem);
  color: var(--jsini-text-muted);
}


/* ── 메일 보내기 (`/admin/mail/send`) ──────────────────────────

   「메시지 발송」의 메일 탭(`.ad-sendform`)과 **치수를 같이 쓴다** — 둘 다
   사람이 글을 쓰는 자리라 한 줄이 화면 폭만큼 길어지면 읽기 나쁘다.
   갈라지는 것은 여기만 가진 것들(최근 주소 딱지 · 간편 틀 · 요약 칸)뿐이다.
   ---------------------------------------------------------- */

.ad-mailsend {
  max-width: 48rem;
  padding-top: 0.75rem;
  padding-right: 0.5rem;
}

/* 보낸 메일함 탭. 표가 들어오므로 폭을 묶지 않는다 — 묶으면 받는 사람 칸이
   줄바꿈되고, 메일 주소는 줄바꿈되면 읽을 수가 없다. */
.ad-mailsend__box {
  padding-top: 0.5rem;
}

/* ── 최근 주소 딱지 ───────────────────────────────────────

   **누르는 자리가 둘인 딱지다.** 가운데를 누르면 받는 사람에, 오른쪽
   「참조」를 누르면 참조에 든다. 하나로 두고 어디에 넣을지 또 묻게 하면
   주소 하나 넣는 데 누름이 둘이 된다 — 이 줄은 「그냥 눌러 담는」 자리다. */

.ad-mailsend__recent {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  margin-top: 0.45rem;
}

.ad-mailsend__recent-label {
  font-size: var(--jsini-fs-sm, 0.85rem);
  color: var(--jsini-text-muted);
}

.ad-mailsend__chip {
  display: inline-flex;
  align-items: stretch;
  border: 1px solid var(--bs-border-color, #dee2e6);
  border-radius: var(--bs-border-radius, 6px);
  overflow: hidden;
  font-size: var(--jsini-fs-sm, 0.85rem);
  line-height: 1.6;
}

.ad-mailsend__chip-main,
.ad-mailsend__chip-cc {
  border: 0;
  background: transparent;
  padding: 0.1rem 0.5rem;
  color: inherit;
  cursor: pointer;
}

.ad-mailsend__chip-main:hover,
.ad-mailsend__chip-cc:hover {
  background: var(--bs-secondary-bg, #f1f3f5);
}

/* 「참조」 쪽은 옅게 둔다 — 눌릴 일이 적은 쪽이고, 같은 무게로 두면
   주소를 넣으려다 참조에 넣는 일이 생긴다. */
.ad-mailsend__chip-cc {
  border-left: 1px solid var(--bs-border-color, #dee2e6);
  color: var(--jsini-text-muted);
}

/* ── 간편 작성 틀 ─────────────────────────────────────────

   **접힌다.** 익숙해진 사람에게는 본문 편집기 하나면 충분해서, 늘 펴 두면
   보낼 때마다 그만큼 스크롤이 는다. 처음에는 펴 둔다 — 접혀 있으면 아무도
   안 쓴다. */

.ad-tpl {
  border: 1px solid var(--bs-border-color, #dee2e6);
  border-radius: var(--bs-border-radius, 6px);
  margin-bottom: 0.9rem;
}

.ad-tpl__head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.5rem 0.75rem;
  border: 0;
  background: var(--bs-secondary-bg, #f8f9fa);
  color: inherit;
  cursor: pointer;
  text-align: left;
}

.ad-tpl__title { font-weight: 600; }

.ad-tpl__hint {
  flex: 1;
  font-size: var(--jsini-fs-sm, 0.85rem);
  color: var(--jsini-text-muted);
}

.ad-tpl__body { padding: 0.75rem; }

/* 접힌 머리줄의 설명은 좁은 화면에서 접는다 — 남기면 제목과 화살표가
   두 줄로 갈린다. */
@media (width < 768px) {
  .ad-tpl__hint { display: none; }
}

/* ── 본문 편집기 ──────────────────────────────────────────

   「메시지 발송」(`.ad-mail-editor`)보다 높다. 그쪽은 왼쪽에 명단이 함께
   서는 좁은 판이고, 여기는 화면을 통째로 쓰는 자리라 메일 한 통이 한눈에
   들어와야 한다. */
.ad-mailsend-editor { min-height: 20rem; }

/* ── AI 요약 ──────────────────────────────────────────────

   **본문과 다른 바탕으로 둔다.** 아직 본문이 아니라는 것이 눈에 보여야
   「넣기」를 누를 까닭이 생긴다. */
.ad-summary__box {
  margin-top: 0.4rem;
}

.ad-summary__box .dxbl-memo textarea,
.ad-summary__box textarea {
  background: var(--bs-secondary-bg, #f8f9fa);
}

/* ── 사용자 고르기 창 ─────────────────────────────────────

   거르개와 표를 세로로 쌓는다. 창의 높이는 `CommPopup` 이 묶으므로
   (`MaxHeight`) 표가 길어도 바닥 단추는 제자리에 남는다. */
.ad-mailsend__pick {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

/* ── 보낸 메일 읽기 창 ────────────────────────────────────

   `ad-note__meta`(쪽지)와 같은 모양이다. 베껴 둔 까닭은 칸이 다르기
   때문이다 — 쪽지는 보낸 사람·받는 사람 둘인데 메일은 받는 사람·참조·
   첨부·상태 넷이고, 첨부 줄만 길다. */

.ad-mailsend__meta {
  display: grid;
  grid-template-columns: 6rem 1fr;
  gap: 0.25rem 0.75rem;
  margin: 0 0 0.75rem;
}

.ad-mailsend__meta dt {
  color: var(--jsini-text-muted);
  font-weight: 500;
}

.ad-mailsend__meta dd {
  margin: 0;
  word-break: break-all;
}

.ad-mailsend__subject {
  margin: 0 0 0.5rem;
  font-size: 1.05rem;
  font-weight: 600;
}

/* 본문은 남이 쓴 서식이 들어오는 자리다. **가둬 둔다** — 안 가두면 넓은
   표 하나가 창을 가로로 밀어낸다. */
.ad-mailsend__body {
  max-width: 100%;
  overflow-x: auto;
  border-top: 1px solid var(--bs-border-color, #dee2e6);
  padding-top: 0.75rem;
}

.ad-mailsend__body img { max-width: 100%; }

/* ── 차단 접근 상세 ────────────────────────────────────────── */
.ad-blocked__facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.35rem 1rem;
  margin: 0 0 0.75rem;
  font-size: var(--jsini-fs-sm);
}

.ad-blocked__facts dt {
  color: var(--jsini-text-muted);
  white-space: nowrap;
}

.ad-blocked__facts dd {
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}
