/*
  셸 레이아웃. DevExpress 테마 **뒤에** 실린다(JSiniHead 의 link 순서).

  [DevExpress 데모의 껍데기를 본떴다]

  demos.devexpress.com/blazor 의 배치를 그대로 가져왔다 — 얇은 상단 바,
  왼쪽에 검색이 달린 메뉴 판, 본문 위의 현재 위치 줄.
  업무 화면이 145개라 껍데기가 스스로를 드러내지 않는 편이 낫고,
  그 데모가 정확히 그렇게 생겼다.

  [여기에 넣는 것과 넣지 않는 것]

  · 넣는 것 — 셸의 뼈대(헤더·사이드바·탭·브레드크럼), 로그인·오류 화면,
    모듈 여섯이 함께 쓰는 화면 조각(제목 줄·안내 줄·통계 타일)
  · 넣지 않는 것 — 한 업무만 쓰는 스타일. 그건 그 모듈의 wwwroot 로 간다
    (`IPortalModule.StyleSheet`). 여기에 업무별 규칙이 쌓이면 모듈을 나눈
    의미가 스타일에서부터 사라진다.

  [색을 DevExpress 변수에 붙이는 이유]

  테마가 다섯이고 사용자가 고른다. 우리 색을 직접 박으면 그리드·달력·팝업만
  테마를 따라가고 사이드바·헤더는 그대로 남아서 **한 화면에 두 테마**가 된다.
  DevExpress 가 내보내는 변수(`--dxbl-*`)를 먼저 쓰고, 없을 때만 우리 값으로
  떨어진다.
*/

:root {
  /*
    사이드바의 **시작 폭**이다. 지금 폭은 여기서 정하지 않는다 —
    구분선을 끌면 스플리터의 판이 정하고(`MainLayout` 의 `_sidebarWidth`,
    브라우저에 남는다) 이 값은 그 기본값(330px, DevExpress 데모와 같다)과
    좁은 화면의 덮개 폭에만 쓰인다.

    **두 곳이 같은 값이어야 한다** — `MainLayout._sidebarWidth` 의 기본값이
    다르면 첫 그림에서 격자 칸과 판의 폭이 어긋난다.
  */
  --jsini-sidebar-width: 330px;
  /* 데모의 3.5rem 이 「보통」의 값이다. 글자를 작게 하면 그 배율만큼 낮아지고,
     크게 해도 여기서 멈춘다(`--jsini-chrome-scale`).

     낮아지는 것까지 함께 하는 이유는, 아이콘과 로고만 줄이면 **띠는 그대로
     높아서** 작은 아이콘이 빈 띠 가운데 떠 보이기 때문이다. 이 값 하나로
     격자·모바일 사이드바·테마 서랍이 다 따라온다. */
  --jsini-header-height: calc(3.5rem * var(--jsini-chrome-scale));

  /*
    아이콘 한 변의 길이.

    **한동안 `1em` 이었다.** 글자 한 자와 똑같은 크기라는 뜻인데, 대문자
    높이가 1em 의 0.7 쯤이라 아이콘이 **글자처럼 보이고 단추처럼 안 보였다.**
    헤더 도구줄과 표 아래 엑셀 아이콘이 모두 그래서 작았다.

    한 자와 같은 크기로 두지 않는다 — 아이콘은 글자가 아니라 과녁이라 조금
    커야 한다. 다만 크기 모드는 따라간다(아래 사다리의 1.32배). 사용자가
    Large 를 골랐는데 누르는 자리만 그대로면 그것대로 어긋나 보인다.
    자리마다 다르게 하고 싶으면 그 자리에서 이 변수만 덮는다.
  */
  --jsini-icon-size: calc(var(--jsini-fs-base) * 1.32);

  /* DevExpress 가 주는 값이 있으면 그것을 쓴다. 두 번째 값은 그 테마가
     변수를 안 내보낼 때를 위한 것이다. */
  --jsini-bg: var(--dxbl-body-bg, #ffffff);
  --jsini-surface: var(--dxbl-panel-bg, #f7f8fa);
  --jsini-border: var(--dxbl-border-color, #e3e5e8);
  --jsini-text: var(--dxbl-body-color, #1f2328);
  --jsini-text-muted: #6b7280;

  /* 강조색만 이름이 하나가 아니다. Fluent 무리는 고른 색을
     `--dxds-color-surface-primary-default-rest`(=`--dxds-primary-90`)로 내보내고,
     Classic·Bootstrap 무리는 `--bs-primary` 로 내보낸다. 실린 테마가 주지 않는
     이름은 통째로 건너뛰므로 var() 를 겹쳐 셋을 차례로 본다.

     **`--dxbl-primary` 를 보고 있었다.** 그런 변수는 DevExpress 어디에도 없어서
     스물둘 중 무엇을 골라 두었든 늘 맨 뒤의 보라로 굳어 있었다 — 테마를 바꿔도
     따라오지 않던 것(떠다니는 메뉴 단추 · 탭 밑줄 · 즐겨찾기 별)이 다 이것이다. */
  --jsini-accent: var(--dxds-color-surface-primary-default-rest, var(--bs-primary, #7b1fa2));
}

:root[data-theme='dark'] {
  --jsini-bg: var(--dxbl-body-bg, #16181d);
  --jsini-surface: var(--dxbl-panel-bg, #1e2128);
  --jsini-border: var(--dxbl-border-color, #2d313a);
  --jsini-text: var(--dxbl-body-color, #e6e8ea);
  --jsini-text-muted: #9aa1ab;
  --jsini-accent: var(--dxds-color-surface-primary-default-rest, var(--bs-primary, #c07ee0));
}

/* ── 글자 크기 사다리 ───────────────────────────────────────

   [왜 사다리가 필요한가 — `0.8rem` 을 박으면 안 되는 이유]

   `rem` 은 `<html>` 의 크기(16px)에 묶인다. **그런데 본문은 16px 이 아니다.**
   DevExpress 테마가 `body { font-size: var(--dxds-font-size-base-md) }` 로
   14px 을 걸어 두기 때문이다. 그래서 `0.8rem` 라벨(12.8px)은 옆에 선
   DevExpress 편집기(14px)보다 작고, 그 어긋남이 화면마다 조금씩 쌓여
   **"내용이 좀 작게 느껴진다"** 가 된다.

   더 나쁜 것은 사용자가 크기를 Large 로 바꿔도 이 값들은 꿈쩍하지 않는다는
   점이다 — 단추와 그리드만 커지고 라벨·배지·통계 숫자는 그대로다.

   그래서 모든 글자 크기를 이 사다리로만 적는다. 사다리의 뿌리는 DevExpress
   크기 모드를 따라가므로, 크기를 바꾸면 화면의 글자가 통째로 함께 움직인다.

   [뿌리 값은 DevExpress 가 정한 것이다]

   `--dxds-font-size-base-sm/md/lg` 를 그대로 옮겨 적었다. 우리가 고른 값이
   아니다 — 다르게 적으면 우리 글자와 DevExpress 부품의 글자가 어긋난다.

     Small   0.75rem  (12px)
     Medium  0.875rem (14px)   ← DevExpress 기본
     Large   1rem     (16px)

   `data-dx-size` 는 theme.js 가 <head> 안에서 동기로 세운다. 스타일시트를
   기다리지 않으므로 첫 그림부터 맞는 크기다.

   [배수는 눈대중이 아니라 옛 값에서 뽑았다]

   Medium 에서 각 단이 옛 하드코딩 값과 거의 같은 픽셀이 되도록 잡았다
   (2xs 10.9 · xs 12.0 · sm 13.0 · md 14.0 · lg 15.4 · xl 17.1 · 2xl 20.3 ·
   3xl 24.5 · 4xl 28.7). 그래서 Medium 화면은 이 작업 전과 사실상 같고,
   달라지는 것은 **Small·Large 를 골랐을 때 비로소 따라온다**는 점뿐이다.
   ------------------------------------------------------------ */

:root {
  --jsini-fs-base: 0.875rem;

  /* 헤더 껍데기(아이콘 · 로고 · 띠 높이)가 따라갈 배율.

     [글자와 따로 두는 이유가 둘이다]

     하나는 **작아질 때만 따라가야** 한다는 것이다. 글자를 크게 하면 사다리는
     1rem 까지 오르지만 헤더 아이콘까지 커지면 띠에 아이콘이 꽉 차 답답해진다.

     그래서 이 값은 **「조금작게」에서 멈춘다(0.929).** 「보통」·「크게」를 골라도
     껍데기는 더 커지지 않는다 — 글자만 커진다. 한동안 캡이 「보통」(1)이었는데
     그 둘이 여전히 크게 느껴졌다.

     또 하나는 헤더가 본문보다 한 단계 큰 아이콘을 쓰기 때문이다
     (`.jsini-header` 의 `--jsini-icon-size`). 글자 사다리를 그대로 쓰면
     그 「한 단계 크게」가 없어진다. 배율만 받아 자기 기준값에 곱한다.

     값은 `--jsini-fs-base / 0.875rem`(보통 기준)이다. CSS 는 길이를 길이로
     나눌 수 없어서 미리 계산해 적는다 — 사다리를 고치면 이 줄도 같이 고친다. */
  --jsini-chrome-scale: 0.929;

  --jsini-fs-2xs: calc(var(--jsini-fs-base) * 0.78);
  --jsini-fs-xs: calc(var(--jsini-fs-base) * 0.86);
  --jsini-fs-sm: calc(var(--jsini-fs-base) * 0.93);
  --jsini-fs-md: var(--jsini-fs-base);
  --jsini-fs-lg: calc(var(--jsini-fs-base) * 1.1);
  --jsini-fs-xl: calc(var(--jsini-fs-base) * 1.22);
  --jsini-fs-2xl: calc(var(--jsini-fs-base) * 1.45);
  --jsini-fs-3xl: calc(var(--jsini-fs-base) * 1.75);
  --jsini-fs-4xl: calc(var(--jsini-fs-base) * 2.05);
}

/* 뿌리 값. **DevExpress 가 정한 세 값 사이에 우리 두 단계를 끼운 것이다.**

   0.75(Small) · 0.875(Medium) · 1(Large) 은 DevExpress 가 정한 값을 그대로
   옮긴 것이고, 아래 둘은 그 사이·아래로 우리가 넣었다.

     xxsmall  0.625rem   10px   Small 아래
     xsmall   0.6875rem  11px   Small 아래
     small    0.75rem    12px   ← DevExpress Small
     compact  0.8125rem  13px   Small~Medium 사이
     medium   0.875rem   14px   ← DevExpress Medium (기본값, :root 에 있다)
     large    1rem       16px   ← DevExpress Large

   **부품 크기는 이 사다리와 별개다.** 우리가 넣은 세 단계는 DevExpress Small 로 접히므로
   (`ThemeSize.Steps`) 줄 높이·단추 높이는 「작게」와 같고 글자만 다르다.
   그것이 그 단계들의 뜻이다 — 같은 밀도에서 글자만 조절한다. */
:root[data-dx-size='xxsmall'] { --jsini-fs-base: 0.625rem; --jsini-chrome-scale: 0.714; }
:root[data-dx-size='xsmall'] { --jsini-fs-base: 0.6875rem; --jsini-chrome-scale: 0.786; }
:root[data-dx-size='small'] { --jsini-fs-base: 0.75rem; --jsini-chrome-scale: 0.857; }
/* 여기가 껍데기의 **최대**다. 아래 둘이 이 값을 그대로 쓴다. */
:root[data-dx-size='compact'] { --jsini-fs-base: 0.8125rem; --jsini-chrome-scale: 0.929; }

/* **「보통」·「크게」의 배율은 「조금작게」와 같다.** 사다리는 0.875 → 1rem 으로
   계속 오르지만 껍데기는 여기서 멈춘다 — 글자만 커진다.
   (`:root` 의 기본값도 같은 0.929 다 — 기본 단계가 「보통」이므로.) */
:root[data-dx-size='medium'] { --jsini-fs-base: 0.875rem; --jsini-chrome-scale: 0.929; }
:root[data-dx-size='large'] { --jsini-fs-base: 1rem; --jsini-chrome-scale: 0.929; }

/* ── 간격·모서리·그림자 사다리 (base-design) ────────────────

   [글자 사다리와 같은 이유로 필요하다]

   위에서 글자 크기를 사다리로 묶은 것과 **똑같은 문제가 여백에 있었다.**
   `0.85rem` · `0.9rem` · `0.7rem` · `0.4rem` 처럼 자리마다 다른 값이 박혀
   있어서, 나란히 놓인 두 판의 안쪽 여백이 1~2px 씩 어긋난다. 한 화면만
   보면 안 보이고 화면을 옮겨 다닐 때 「뭔가 들쭉날쭉하다」로만 느껴진다.

   [뿌리 값은 DevExpress 가 정한 것이다]

   DevExpress Design System 의 기본 토큰(`--dxds-*`)을 그대로 딛는다
   (docs.devexpress.com/DesignSystem/405636/foundation). 우리가 고른 숫자가
   아니다 — 그리드·팝업·달력의 안쪽 여백이 이미 그 사다리로 잡혀 있어서,
   다른 사다리를 쓰면 우리 판과 DevExpress 부품의 여백이 영영 안 맞는다.

   그쪽 이름은 `160 = 1rem` 인 **1px 격자의 숫자**다(`--dxds-spacing-40`
   = 0.25rem). 숫자를 그대로 쓰면 읽는 쪽이 매번 나눗셈을 해야 해서,
   글자 사다리와 같은 이름(2xs…3xl)으로 다시 붙였다.

   두 번째 값은 그 테마가 토큰을 안 내보낼 때를 위한 것이다 — Classic·
   Bootstrap 테마에는 `--dxds-*` 가 없다.

   [답답하던 것을 여기서 푼다]

   판의 안쪽 여백이 **한 단계씩 좁게** 잡혀 있었다(조회 영역 13.6px,
   본문 위 17.6px). 사다리로 옮기면서 그 자리들을 한 단 올렸다 —
   늘어나는 것은 여백뿐이고 글자와 줄 높이는 그대로다.
   ------------------------------------------------------------ */

:root {
  /* 셸 본문의 바깥 여백. **위·아래·좌우를 따로 둔다** — 셋이 서로 다른
     값이고 폭에 따라 또 갈린다(휴대폰 값은 바로 아래에서 다시 적는다).

     눈금(`--jsini-sp-*`)을 쓰지 않는 것은 좌우가 2px 이기 때문이다. 눈금에
     없는 값이고, 가까운 칸(3xs=2px)을 당겨 쓰면 그 칸을 쓰는 다른 자리들이
     본문 여백을 고칠 때마다 함께 움직인다.

     쓰는 곳은 `.jsini-shell__content` 뿐이다 — `app.css` 에 셋,
     `tabler.css` 에 `!important` 로 하나. */
  --jsini-shell-pad-top: 1rem;
  --jsini-shell-pad-bottom: 1rem;
  --jsini-shell-pad-inline: 0.5rem;

  /* 업무 화면이 제 바닥에 세우는 단추 줄(`.jsini-actbar`)의 높이.

     휴대폰에서 그 줄은 **화면의 맨 아래 칸**(하단 띠 바로 위)을 갖고,
     떠다니는 단추(☰ · ✎)는 그 위로 올라선다. 올라서는 거리를 재는 자가
     이 값이다 — 줄 자신은 `min-height` 로 이만큼을 **보장하고**, 셸은
     같은 값만큼 단추를 민다. 둘이 같은 토큰을 보므로 한쪽만 고쳐 어긋날
     일이 없다.

     3.5rem(56px)은 **재어 나온 값**이다 — 390×844 에서 단추 한 줄에 줄의
     위아래 여백(0.5rem 씩)을 더해 57px 이었다(Tabler 테마. 테마를 안 입히면
     49px 이라, 두꺼운 쪽에 맞춘다). 떠다니는 단추는 여기에 제 틈
     (76px 중 16px)을 더 갖고 올라서므로 1~2px 의 어긋남은 그 틈이 먹는다. */
  --jsini-actbar-height: 3.5rem;

  /* 휴대폰 아래 띠(`.jsini-bottom-nav`)의 높이. **안전 영역은 안 들어 있다** —
     그 몫은 쓰는 자리마다 `env(safe-area-inset-bottom)` 으로 따로 더한다.

     띠가 자리를 차지하면 본문 · 사이드바 · 프로필 판 · 떠다니는 단추 ·
     오른쪽 서랍 · 단추 줄이 그만큼 비켜선다 — 세어 보면 아홉 자리다.
     숫자를 베껴 적어 두면 **띠 높이를 한 번 바꿀 때 그 아홉을 모두 찾아야
     하고 하나는 반드시 빠진다.** 빠진 자리는 「마지막 줄이 띠에 깔린다」나
     「띠 위에 빈 띠가 하나 더 있다」로 보인다.

     [이름을 감추면 이 값이 줄어든다]

     띠의 칸은 아이콘(20px) 위에 이름(11px)을 얹는다. 환경설정에서 **이름을
     감추기로** 하면 그 줄이 통째로 빠지므로 띠도 그만큼 얇아야 한다
     (`.jsini-shell--bottomnav-nolabel`). 줄어든 16px 은 **본문이 가져간다** —
     위 아홉 자리가 모두 이 토큰을 보고 있어서 저절로 따라온다. */
  --jsini-bottomnav-h: 60px;

  --jsini-sp-3xs: var(--dxds-spacing-20, 0.125rem);   /*  2px */
  --jsini-sp-2xs: var(--dxds-spacing-40, 0.25rem);    /*  4px */
  --jsini-sp-xs: var(--dxds-spacing-60, 0.375rem);    /*  6px */
  --jsini-sp-sm: var(--dxds-spacing-80, 0.5rem);      /*  8px */
  --jsini-sp-md: var(--dxds-spacing-120, 0.75rem);    /* 12px */
  --jsini-sp-lg: var(--dxds-spacing-160, 1rem);       /* 16px */
  --jsini-sp-xl: var(--dxds-spacing-200, 1.25rem);    /* 20px */
  --jsini-sp-2xl: var(--dxds-spacing-240, 1.5rem);    /* 24px */
  --jsini-sp-3xl: var(--dxds-spacing-320, 2rem);      /* 32px */

  --jsini-radius-sm: var(--dxds-border-radius-40, 0.25rem);
  --jsini-radius-md: var(--dxds-border-radius-60, 0.375rem);
  --jsini-radius-lg: var(--dxds-border-radius-80, 0.5rem);
  --jsini-radius-xl: var(--dxds-border-radius-120, 0.75rem);
  --jsini-radius-full: var(--dxds-border-radius-full, 62rem);

  --jsini-shadow-xs: var(--dxds-box-shadow-xs, 0 1px 2px rgb(0 0 0 / 14%));
  --jsini-shadow-sm: var(--dxds-box-shadow-sm, 0 2px 4px rgb(0 0 0 / 14%));
  --jsini-shadow-md: var(--dxds-box-shadow-md, 0 4px 8px rgb(0 0 0 / 14%));
  --jsini-shadow-lg: var(--dxds-box-shadow-lg, 0 8px 16px rgb(0 0 0 / 14%));

  /*
    라벨이 **안에 떠 있는** 입력 칸의 높이.

    DevExpress 기본 편집기는 32px 이다. 그 안에 라벨을 얹을 자리가 없어서
    (글자 14px + 위아래 5px) 라벨이 들어가는 칸만 이만큼 키운다.

    **모든 편집기를 키우지 않는다.** 표 안의 거르개 줄·셀 편집기까지 커지면
    줄 높이가 통째로 늘어 한 화면에 보이는 줄이 줄어든다 — 그쪽은 라벨이
    없으므로 키울 까닭도 없다.
  */
  /*
    라벨이 **들어가 앉을 수 있는** 칸 높이. DevExpress 기본 편집기는 32px 인데
    그 안에 라벨이 내려앉을 자리가 없다(글자 14px + 위아래 5px).

    **32px 이다 — DevExpress 기본 편집기와 같은 값.** 라벨이 테두리로 나가
    있으므로 칸 안에는 글자 한 줄만 있으면 되고, 그러면 기본값보다 키울
    까닭이 없다. 글자(20px)에 위아래 4.8px 씩과 테두리 2px 을 더한 값이다.

    42 → 38 → 32 로 내려왔다. 앞의 둘은 라벨이 칸 **안**에 있던 시절의
    값이거나(42) 그 자국이 남은 값이다(38).
  */
  --jsini-field-h: 2rem;

  /* 떠오른 라벨의 한가운데가 테두리에 오도록 **위로 반 줄** 올린다. 칸 위끝이
     0 이라 자기 높이의 절반만큼 음수다. 0.6 은 글자 크기에 줄 간격(1.2)을
     반으로 접은 값이다. */
  --jsini-field-label-top: calc(var(--jsini-fs-2xs) * -0.6);


}

/* [휴대폰은 값이 다르다]

   위 0.5rem · 아래 1rem · 좌우 2px. 좌우를 2px 까지 좁히는 것은 좁은 화면에서
   한 뼘이 통째로 자리값이기 때문이고, 위아래를 남기는 것은 머리글 줄과
   하단 띠에 본문이 바싹 붙지 않게 하기 위해서다.

   값을 규칙마다 다시 적지 않고 **토큰만 다시 적는다.** 이 여백을 보는 자리가
   네 군데인데(`app.css` 셋, `tabler.css` 의 `!important` 하나) 그중 하나라도
   빠뜨리면 **그 한 폭에서만 옛 값이 남는다** — 실제로 그랬다. 8px 짜리 테마
   규칙 하나를 못 보고 넘겨서, 토큰을 내린 뒤에도 휴대폰만 8px 로 서 있었다.

   **아래쪽은 이 값이 전부가 아니다.** 하단 띠(60px + 안전영역)만큼은 따로
   비켜야 마지막 줄에 닿을 수 있고, 그것은 여백이 아니라 자리 비킴이라
   `calc()` 안에서 이 값에 더해진다. */
@media (max-width: 767px) {
  :root {
    --jsini-shell-pad-top: 0.5rem;
    --jsini-shell-pad-bottom: 1rem;
    --jsini-shell-pad-inline: 2px;
  }
}

/*
  글꼴은 **여기서 정하지 않는다.** 테마가 거는 것을 쓴다.

  글자 크기는 위 사다리의 뿌리로 덮는다. DevExpress 의 global.min.css 가
  body 에 `--dxds-font-size-base-md` 를 박아 두는데, 그 값은 크기 모드와
  무관하게 늘 14px 이라 Large 를 골라도 본문 글자가 안 커진다.

  줄 간격도 함께 덮는다. 테마가 주는 것은 `1.25rem` 처럼 **고정 길이**라
  글자만 커지면 줄이 붙어 버린다. 비율(1.45)로 두면 따라 늘어난다 —
  1.45 는 테마의 14px/1.25rem 과 같은 비율이라 Medium 에서는 달라지지 않는다.
*/
html,
body {
  height: 100%;
  margin: 0;
  background: var(--jsini-bg);
  color: var(--jsini-text);
}

body {
  font-size: var(--jsini-fs-md);

  /* 데모(demos.devexpress.com/blazor)에서 잰 값이다 — 14px 글자에 21px 줄,
     곧 1.5. 우리는 1.45 였고 그 0.05 가 글이 많은 화면에서 답답함으로 쌓인다. */
  line-height: 1.5;
}

.jsini-hidden { display: none !important; }

/* ── 셸 뼈대 ─────────────────────────────────────────────────

   **DevExpress 데모(demos.devexpress.com/blazor)의 치수를 그대로 옮겼다.**
   데모의 `dx-demo.css` 에서 값을 읽어 왔다 — 눈대중이 아니다.

     헤더        3.5rem   (56px)
     사이드바     330px
     검색 칸      3rem     (48px)
     브레드크럼   3rem
     본문 여백    1.1rem 1.5rem 0

   격자 두 줄 · 두 칸이다.

     ┌──────────────────────────────┐
     │ 헤더 (두 칸에 걸친다)          │
     ├───────────┬──────────────────┤
     │ 사이드바   │ 본문              │
     └───────────┴──────────────────┘

   사이드바를 접을 때 **격자를 바꾸지 않고 폭만 0 으로** 만든다. 데모도 그렇게
   한다(`width: 0` + `transition: width .2s`). 격자를 바꾸면 본문이 통째로 다시
   배치되어 스크롤 위치가 튄다.
   ------------------------------------------------------------ */

.jsini-shell {
  display: grid;
  grid-template-columns: var(--jsini-sidebar-width) 1fr;
  grid-template-rows: var(--jsini-header-height) 1fr;
  height: 100vh;
  overflow: hidden;
  transition: grid-template-columns 0.2s ease-in-out;
}

.jsini-shell--collapsed {
  grid-template-columns: 0 1fr;
}

/* 아래 띠의 **이름을 감추기로** 한 사람에게 붙는 표시(`MainLayout.ShellCss`).

   띠가 아이콘 한 줄 높이로 얇아지고, 띠를 피해 서 있던 것들이 그 차이
   (16px)만큼 아래로 내려온다 — 본문이 세로로 그만큼을 돌려받는다.

   **44px 은 손가락이 빗나가지 않는 최소 높이다.** 아이콘(20px)만 남으면
   28px 까지 줄일 수도 있지만, 그 띠는 칸 여덟이 나란히 선 자리라 세로로
   좁히는 만큼 오타가 는다. 줄이는 것은 글자가 쓰던 16px 까지다. */
.jsini-shell--bottomnav-nolabel {
  --jsini-bottomnav-h: 44px;
}

/* ── 좁은 화면에서 메뉴를 여닫는 둘 ───────────────────────

   덮개(`__scrim`)와 떠다니는 단추(`__fab`)다. 생김새는 여기 한곳에 적고
   **보이고 안 보이고는 아래 미디어 쿼리(≤767px)가 정한다** — 둘 다 기본은
   안 보임이다. 넓은 화면에는 늘 자리를 차지하는 사이드바와 구분선의 접기
   화살표가 있어서, 덮을 것도 띄울 것도 없다.

   `MainLayout` 이 **마크업은 늘 그린다.** 화면 크기로 갈아 끼우지 않는
   그 파일의 규칙대로고, 그래야 프리렌더(화면 크기를 모른다)에서 나간
   그림과 회로가 붙은 뒤의 그림이 같다.
   ---------------------------------------------------------- */

.jsini-shell__scrim {
  display: none;
  position: fixed;

  /* 헤더는 덮지 않는다. 사용자 메뉴(`.jsini-user__scrim`)의 덮개와 같은
     자리다 — 메뉴가 펴져 있어도 알림·프로필은 눌려야 한다. 다시 닫는 일은
     떠다니는 단추가 맡는다(이 덮개보다 위에 뜬다).

     덮지 않을 뿐 **누르면 닫힌다** — 그 띠의 누름은 레이아웃이 직접 받는다
     (`MainLayout.OnHeaderClick`). 덮개를 여기까지 늘리면 단추가 한 번에
     안 눌린다. */
  inset: var(--jsini-header-height) 0 0 0;

  /* 사이드바(1030)보다 **아래**, 본문보다 위. 판은 덮개 위에 떠 있어야 하고
     본문은 그 아래로 잠겨야 한다. */
  z-index: 1029;

  /* 옅게 어둡힌다. 아예 투명하면 「눌러서 닫는 자리」인 줄 모르고, 짙으면
     본문이 없어진 것처럼 보여 메뉴를 열어 보기가 망설여진다. */
  background: rgb(0 0 0 / 35%);
}

/* 떠다니는 메뉴 단추. **왼쪽 아래**다 — 사이드바가 나오는 쪽이고, 한 손으로
   쥔 휴대폰에서 엄지가 닿는 쪽이다.

   `env(safe-area-inset-*)` 을 더한다. 아이폰의 홈 막대와 노치 있는 기기의
   왼쪽 여백 안에 들어가면 **단추의 아래 절반이 안 눌린다.** 지원하지 않는
   브라우저에서는 0 이라 0.75rem 그대로다. */
.jsini-shell__fab {
  display: none;
  position: fixed;
  left: calc(0.75rem + env(safe-area-inset-left));
  bottom: calc(0.75rem + env(safe-area-inset-bottom));
  z-index: 1031;

  /* 손가락으로 누르는 단추다. 접근성 안내가 말하는 최소 44px 를 넘긴다. */
  width: 3rem;
  height: 3rem;
  align-items: center;
  justify-content: center;
  padding: 0;
  border-radius: 50%;
  box-shadow: 0 3px 10px rgb(0 0 0 / 30%);
  cursor: pointer;

  /* ── 속을 채우지 않는다 ──────────────────────────────────────

     한동안 이 단추만 강조색으로 **통째로 칠해져** 있었다. `opacity: 0.8` 은
     그때도 똑같이 걸려 있었는데, 짙은 색이 깔린 동그라미는 0.8 이어도 뒤가
     안 비친다 — **재어 봤다**(390px 폭 · 글줄 위에 겹쳐 찍어 비교).
     왼쪽 아래 귀퉁이에 걸린 글줄이 통째로 지워졌다.

     그래서 요청 등록 단추(`--help`)와 **같은 꼴로 맞춘다** — 판 색을 깔고
     테두리와 그림만 강조색으로 둔다. 둘의 `opacity` 는 원래 같았으므로
     여기서 달라지는 것은 **바탕 하나**이고, 그것이 사람이 「투명도」로
     읽던 바로 그 차이다.

     구분은 **그림이 한다**(☰ ↔ ✎, 메뉴가 펴지면 ✕). 색으로 가르던 것을
     그림으로 옮기는 셈인데, 색은 테마마다 달라지고 그림은 안 달라진다.

     `--jsini-surface` 는 판 색이라 본문 배경과 가깝다. 뒤가 아예 안 비치는
     것이 아니라 **0.8 만큼 비친다** — 글줄 위에 서도 글자가 읽힌다. */
  background: var(--jsini-surface, #fff);
  color: var(--jsini-accent);
  border: 1px solid color-mix(in srgb, var(--jsini-accent) 45%, transparent);

  /* 뒤에 글자가 가려져도 존재를 인지할 수 있도록 평상시 살짝 반투명하게 둔다. */
  opacity: 0.8;
  transition: opacity 0.2s ease, background 0.15s ease, transform 0.15s ease;

  /* ── 오래 눌러도 브라우저가 끼어들지 않게 ──────────────────

     이 단추는 **길게 누르면 제 설정 창이 뜬다**(`js/fab-hold.js`). 그 손짓은
     브라우저가 이미 쓰고 있는 손짓이기도 해서, 그대로 두면 우리 창이 뜨기
     전에 **아이폰의 말풍선(「복사 · 공유」)과 글자 선택**이 먼저 걸린다.
     안드로이드의 바로가기 차림표는 JS 쪽에서 막는다 — 그쪽은 CSS 로 못 막는다. */
  -webkit-touch-callout: none;
  user-select: none;
}

/* 누르고 있는 동안의 표시(`is-held` — JS 가 붙였다 뗀다).

   길게 누르기는 **0.5초 동안 아무 일도 안 일어나는** 손짓이라, 표시가 없으면
   그 사이가 「먹통」으로 읽혀 손을 떼 버린다. 살짝 줄어드는 것으로 「받고
   있다」를 말한다 — 색을 바꾸면 그냥 누른 것(`:active`)과 구분이 안 된다. */
.jsini-shell__fab.is-held {
  opacity: 1;
  transform: scale(0.92);
}

.jsini-shell__fab:hover,
.jsini-shell__fab:focus-visible,
.jsini-shell__fab:active,
.jsini-shell--nav-open .jsini-shell__fab {
  opacity: 1;
}

.jsini-shell__fab:active {
  background: color-mix(in srgb, var(--jsini-accent) 12%, var(--jsini-surface, #fff));
}

/* 그림은 단추보다 한 뼘 크게. 기본값(1.35rem)으로 두면 동그라미 가운데가
   비어 보인다. */
.jsini-shell__fab [class^='jsini-icon-'] {
  width: 1.6rem;
  height: 1.6rem;
}

/* ── 모바일 메뉴 단추 위치 ─────────────────────────────────────
   환경설정에서 4가지 위치(왼쪽 위/아래, 오른쪽 위/아래)를 고를 수 있다.
   위쪽 위치는 상단 헤더 띠(var(--jsini-header-height)) 아래에 안전 여백을 더해 배치한다. */
.jsini-shell__fab--bottom-left {
  left: calc(0.75rem + env(safe-area-inset-left));
  bottom: calc(0.75rem + env(safe-area-inset-bottom));
  top: auto;
  right: auto;
}

.jsini-shell__fab--top-left {
  left: calc(0.75rem + env(safe-area-inset-left));
  top: calc(var(--jsini-header-height) + 0.75rem + env(safe-area-inset-top));
  bottom: auto;
  right: auto;
}

.jsini-shell__fab--bottom-right {
  right: calc(0.75rem + env(safe-area-inset-right));
  bottom: calc(0.75rem + env(safe-area-inset-bottom));
  top: auto;
  left: auto;
}

.jsini-shell__fab--top-right {
  right: calc(0.75rem + env(safe-area-inset-right));
  top: calc(var(--jsini-header-height) + 0.75rem + env(safe-area-inset-top));
  bottom: auto;
  left: auto;
}

/* ── 떠다니는 요청 등록 단추 (`--help`) ────────────────────────

   헬프데스크 요청 등록으로 바로 가는 단추다. **생김새를 여기 따로 적지
   않는다** — 속을 채우지 않는 그 꼴이 위의 `.jsini-shell__fab` 로 올라갔고,
   이제 메뉴 단추도 같은 꼴이다(그 규칙의 주석 참고).

   그러므로 `--help` 는 **자리를 가리키는 이름표**로만 남는다 —
   감추기(`.jsini-shell--hdfab-hidden`)와 밀기(`--stacked`)가 이 이름으로
   이 단추만 집는다. 구분은 안에 든 그림이 한다(☰ ↔ ✎). */

/* [넓은 화면에도 띄운다 — 물어서]

   떠다니는 단추 둘은 아래 `@media (max-width: 767px)` 안에서만 켜진다.
   휴대폰에서 손가락이 닿기 어려운 자리를 메우는 것이고, 책상에서는 헤더와
   메뉴가 그 일을 하기 때문이다.

   **요청 등록만은 셈이 다르다.** 고장·문의를 적는 일은 자리에 앉아서도
   생기고(전화를 받다가, 회의 중에), 메뉴를 열고 업무를 고르고 두 단계를 더
   내려가야 닿는 것은 넓은 화면에서도 똑같다. 그래서 **물어서** 띄운다 —
   늘 띄우면 책상에서 한 번도 안 쓰는 사람의 오른쪽 아래를 영영 먹는다.

   표시를 붙이는 쪽은 `MainLayout.ShellCss` 이고 저장은 브라우저의
   `jsini-hdfab-desktop` 이다. 고르는 자리는 장례식장 환경설정. */
.jsini-shell--hdfab-desktop .jsini-shell__fab--help { display: inline-flex; }

/* 환경설정에서 **요청 등록 단추를 감춘 사람**(`jsini-hdfab-hidden`).
   표시를 붙이는 쪽은 `MainLayout.ShellCss` 다.

   **메뉴 단추의 감추기와 갈라 둔다.** 한동안 그쪽이 `.jsini-shell__fab`
   전부를 잡고 있었는데, 그대로 두면 메뉴 단추를 감춘 사람에게서 **요청 등록
   단추까지 함께 사라진다** — 둘은 따로 켜고 끄는 것이다.

   **폭을 가리지 않는다.** 한동안 이 규칙이 휴대폰 블록 안에 있었는데, 바로
   위에서 넓은 화면에도 띄울 수 있게 된 뒤로는 거기 두면 **감춰 둔 사람에게
   책상에서만 되살아난다.** 「감췄다」는 넓든 좁든 안 보겠다는 말이다.

   **바로 위 규칙보다 뒤에 둔다.** 자릿수가 같아서(클래스 둘) 뒤엣것이 이긴다 —
   순서를 뒤집으면 감춘 사람에게 단추가 다시 나온다.

   **권한이 없는 사람은 여기 오지 않는다.** 그때는 마크업 자체가 없다
   (`MainLayout.HelpDeskFabAllowed`). */
.jsini-shell--hdfab-hidden .jsini-shell__fab--help { display: none; }

/* ── 같은 귀퉁이에 둘이 설 때 (`--stacked`) ────────────────────

   [왜 `margin` 인가]

   자리를 정하는 `top`·`bottom` 은 **다른 규칙이 `!important` 로 덮어쓰는
   값**이다(아래 띠를 쓰는 사람에게 76px 로 올리는 규칙). 여기서 같은 칸을
   다시 쓰면 둘 중 하나가 조용히 지고, 어느 쪽이 이기는지는 띠를 쓰는지에
   따라 갈린다 — 「띠를 껐더니 단추 둘이 포개진다」가 된다.

   `margin` 은 그 계산에 **더해진다.** 붙는 자리가 위든 아래든, 띠가 있든
   없든 언제나 한 칸(단추 3rem + 틈 0.75rem)만큼 민다.

   미는 것은 **요청 등록 단추 쪽**이다(`MainLayout.HelpDeskFabStacked`) —
   메뉴 단추를 감춘 사람에게는 밀지 않는다. */
.jsini-shell__fab--stacked.jsini-shell__fab--bottom-left,
.jsini-shell__fab--stacked.jsini-shell__fab--bottom-right {
  margin-bottom: 3.75rem;
}

.jsini-shell__fab--stacked.jsini-shell__fab--top-left,
.jsini-shell__fab--stacked.jsini-shell__fab--top-right {
  margin-top: 3.75rem;
}

/* [넓은 화면에서는 밀지 않는다]

   미는 까닭은 **같은 귀퉁이에 메뉴 단추가 서 있어서**인데, 넓은 화면에서
   그 단추는 아예 안 보인다(아래 `@media` 안에서만 켠다). 그런데 미는 표시를
   붙일지는 서버가 정하고(`MainLayout.HelpDeskFabStacked`) 거기서는 화면 폭을
   보지 않으므로, 같은 귀퉁이를 골라 둔 사람이 요청 등록 단추를 넓은 화면에도
   띄우면 **아무도 없는 귀퉁이 위에 한 칸 떠 있는 단추**가 된다.

   그 한 칸만 여기서 되돌린다. **`margin` 칸을 쓰는 것도 그대로다** — 자리
   칸(`top`·`bottom`)은 아래 띠 규칙이 `!important` 로 덮어쓰는 자리라
   여기서 건드리면 어느 쪽이 이기는지가 띠를 쓰는지에 따라 갈린다.

   「위로 가기」 단추는 반대로 **여기서 비켜 서야 한다** — 그쪽에 표시를
   붙이는 셈은 `MainLayout.ScrollTopFabCss` 가 한다. */
@media (min-width: 768px) {
  .jsini-shell--hdfab-desktop .jsini-shell__fab--help.jsini-shell__fab--stacked {
    margin-top: 0;
    margin-bottom: 0;
  }
}

/* 같은 귀퉁이에 **셋**이 설 때. 떠다니는 단추 둘이 다 오른쪽 아래에 있고
   그 위에 「위로 가기」가 서는 경우뿐이라 실제로 쓰는 쪽은 아래짝 하나지만,
   위짝도 함께 적어 둔다 — 한쪽만 있으면 위 귀퉁이로 옮긴 날 조용히 포갠다.

   값은 위의 **두 배가 아니라** 한 칸(3.75rem)을 한 번 더 더한 것이다.
   단추 3rem + 틈 0.75rem 이 한 칸이라 셋째는 7.5rem 이다. */
.jsini-shell__fab--stacked2.jsini-shell__fab--bottom-left,
.jsini-shell__fab--stacked2.jsini-shell__fab--bottom-right {
  margin-bottom: 7.5rem;
}

.jsini-shell__fab--stacked2.jsini-shell__fab--top-left,
.jsini-shell__fab--stacked2.jsini-shell__fab--top-right {
  margin-top: 7.5rem;
}

/* ── 「위로 가기」 단추 (`--scroll-top`) ────────────────────────

   본문을 **300px 넘게 내려 읽는 동안에만** 뜬다. 그 표시(`is-visible`)를
   붙였다 떼는 것은 브라우저다(theme.js) — 구르는 자리를 서버가 알 길이 없고,
   알았다 해도 한 번 구를 때마다 왕복이 난다.

   **구르는 것이 `.jsini-shell__content` 하나가 아니다.** 업무 화면의 기본
   꼴(`CommCont`)에서 그 상자는 `overflow: hidden` 이고 **자료 판만 제 안에서
   구른다**(아래 「본문 높이를 꽉 채운다」). 그래서 theme.js 는 본문 **안에서**
   구른 상자를 받는다 — 거기까지 넓히기 전에는 넓은 화면에서 이 단추가 한
   번도 안 떴다(2026-10-11).

   [넓은 화면에도 그린다]

   떠다니는 단추 둘(☰ · ✎)은 **휴대폰에서만** 켜진다(위 `@media` 안의
   `.jsini-shell__fab { display: inline-flex }`). 이것은 반대로 양쪽 다
   켠다 — 본문이 길어 내려 읽는 일은 화면이 넓을수록 잦고, 1440px 짜리
   화면에서 맨 위로 돌아가는 길은 굴림 막대를 끝까지 끌어 올리는 것뿐이다.

   [자리는 오른쪽 아래다]

   넓은 화면에서는 거기 아무것도 없고(다른 단추 둘은 안 보인다), 휴대폰에서는
   메뉴 단추가 기본으로 **왼쪽** 아래라 비켜 선다. 같은 귀퉁이를 고른
   사람에게는 한 칸씩 밀어 올린다(`--stacked`·`--stacked2`) — 미는 칸 수를
   세는 쪽은 `MainLayout.ScrollTopFabCss` 다.

   [`display` 로 감추지 않는다]

   감췄다 보이면 단추가 **툭 튀어나온다.** 자리는 늘 잡아 두고 `opacity` 로
   스며 나오게 한다 — 그동안 눌리지 않게 `pointer-events: none` 을 함께 건다.

   자릿수를 하나 올려 적는 까닭이 있다(`.jsini-shell` 을 앞에 붙였다).
   위의 `.jsini-shell--nav-open .jsini-shell__fab { opacity: 1 }` 이 **같은
   자릿수**라, 클래스 하나로 적으면 휴대폰에서 메뉴를 펴는 순간 안 보여야 할
   이 단추가 함께 떠오른다. */
.jsini-shell__fab--scroll-top {
  display: inline-flex;
  right: calc(0.75rem + env(safe-area-inset-right));
  bottom: calc(0.75rem + env(safe-area-inset-bottom));
  left: auto;
  top: auto;
}

.jsini-shell .jsini-shell__fab--scroll-top:not(.is-visible) {
  opacity: 0;
  pointer-events: none;

  /* 살짝 가라앉혀 두었다가 떠오른다. 누르고 있을 때의 `scale` 과 같은 칸을
     쓰므로(`transform`) **그 표시가 붙으면 이 값이 진다** — 안 보이는 동안의
     일이라 겹칠 일이 없다. */
  transform: translateY(0.5rem);
}

/* 휴대폰에서 메뉴가 펴져 있는 동안에는 걷는다. 그때 화면 오른쪽 아래는
   덮개 위이고, 거기서 「위로 가기」는 **덮인 본문**을 가리켜 아무 뜻이 없다.
   자릿수가 위의 `:not(.is-visible)` 과 같으므로 **뒤에 둔다.** */
.jsini-shell--nav-open .jsini-shell__fab--scroll-top {
  opacity: 0;
  pointer-events: none;
}

/* 바닥 칸을 가진 화면에서는 그 줄 위에 선다 — **넓은 화면에서도** 그렇다.

   그 줄(`.jsini-actbar`)은 본문 바닥에 붙박여(`position: sticky`) 단추를
   **오른쪽에 모은다.** 이 단추도 오른쪽 아래라, 그대로 두면 맨 끝 단추를
   덮는다 — 모형으로 재어 보니(1400×900) 겹치는 넓이가 35×32px 이고, 그 단추
   **한가운데를 눌러도 `elementFromPoint` 가 집는 것은 이쪽**이었다(가려 보이는
   것이 아니라 실제로 안 눌린다).

   **비켜 설 몫을 토큰으로 적는다.** 그 줄의 아래끝은 화면 맨 아래가 아니라
   본문 여백(`--jsini-shell-pad-bottom`)과 판 여백(`--jsini-sp-lg`)만큼
   올라와 있다 — 숫자를 베껴 적으면 그 둘을 고치는 날 조용히 어긋난다.
   휴대폰 갈래에는 같은 결의 규칙이 이미 있다(아래 `@media` 의 `76px + 줄 높이`
   — 그쪽은 본문 여백을 띠가 쓰고 있어 셈이 다르다).

   `!important` 를 안 붙인다 — 휴대폰 쪽 규칙이 그것을 달고 있어 그대로 이긴다. */
.jsini-shell:has(.jsini-actbar) .jsini-shell__fab--scroll-top {
  bottom: calc(var(--jsini-shell-pad-bottom) + var(--jsini-sp-lg)
               + var(--jsini-actbar-height) + 0.75rem);
}

/* ── 사이드바 ↔ 본문 사이의 구분선 (DxSplitter) ───────────

   [격자 칸 둘을 부품 하나가 대신한다]

   위 격자는 여전히 두 칸이지만, **넓은 화면에서는 스플리터가 그 두 칸에
   걸쳐 앉고**(`grid-column: 1 / -1`) 사이드바·본문은 그 부품의 판 안으로
   들어간다. 그래서 폭을 정하는 것이 `--jsini-sidebar-width` 가 아니라
   부품의 `Size` 다.

   격자 정의를 그대로 둔 이유는 **휴대폰 갈래가 아직 그것을 쓰기 때문**이다
   (그때는 스플리터를 쓰지 않는다 — 사이드바가 본문을 덮어야 한다).

   [높이가 흘러야 한다]

   `.jsini-shell` 이 `height: 100vh` 고 이 줄이 `1fr` 이다. 그 높이가 판
   안쪽까지 내려가야 사이드바 트리와 본문이 각자 안에서 구르므로, 판과
   그 안쪽에 `min-height: 0` 을 함께 준다 — 한 겹만 빠뜨려도 본문이 판을
   넘어가고, 넘어간 부분은 잘려서 아래 띠를 누를 수 없게 된다.
   ---------------------------------------------------------- */

.jsini-shell__split {
  grid-row: 2;
  grid-column: 1 / -1;
  min-width: 0;
  min-height: 0;
}

/* 판 안쪽. **여백을 걷어낸다.**

   DevExpress 는 판 안쪽에 여백을 준다(크기 모드마다 다르고 지금 테마에서는
   16px). 문서 안의 분할 화면에는 맞는 값이지만 여기 들어오는 것은 **셸의
   판 둘**이라, 그 여백이 사이드바를 안쪽으로 밀어 헤더와 세로줄이 어긋난다 —
   재어 보니 사이드바가 좌우로 16px, 위아래로 16px 씩 들어와 있었다.

   여백을 쓰는 규칙과 다투지 않고 **그 규칙이 읽는 변수**를 덮는다. 그 값을
   정하는 규칙이 `.dxbl-splitter.dxbl-lg`(클래스 둘)이라 셋으로 적었다 —
   크기 모드마다 값이 다르므로 자릿수를 넘겨야 세 모드 모두에서 걸린다. */
.jsini-shell .jsini-shell__split.dxbl-splitter {
  --dxbl-splitter-pane-padding-x: 0;
  --dxbl-splitter-pane-padding-y: 0;
}

.jsini-shell__split .dxbl-splitter-pane-content {
  min-width: 0;
  min-height: 0;
}

/* 사이드바·본문이 판을 꽉 채우고 넘치는 것은 각자 안에서 구른다. */
.jsini-shell__split .dxbl-splitter-pane-content > .jsini-shell__sidebar,
.jsini-shell__split .dxbl-splitter-pane-content > .jsini-shell__main {
  height: 100%;
  min-height: 0;
}

/* ── 구분선 ────────────────────────────────────────────────

   바탕을 비운다. 사이드바가 이미 오른쪽에 선과 안쪽 그림자를 갖고 있어서
   구분선까지 칠하면 경계가 두 겹으로 보인다. 대신 **마우스를 올리면**
   강조색을 옅게 깔아 「여기를 끌 수 있다」를 알린다 — 끌 수 있다는 것은
   해 보기 전에는 보이지 않는 종류다.

   손잡이 그림과 접기 화살표는 DevExpress 가 그린 것을 그대로 둔다.
   그 둘이 이 자리에서 유일한 단서다.
   ---------------------------------------------------------- */

.jsini-shell__split > .dxbl-splitter-separator {
  background-color: transparent;

  /* 잡이(아래 `::before`)가 상자 밖으로 나가야 한다. */
  position: relative;
  overflow: visible;

  /* **본문 판보다 위에 있어야 한다.** 본문 판이 DOM 에서 뒤에 있어서, 층을
     올리지 않으면 넓혀 둔 잡이의 **오른쪽 절반이 본문에 먹힌다** — 재어 보니
     선 왼쪽 4px 에서는 구분선이 잡히고 오른쪽 4px 에서는 본문이 잡혔다. */
  z-index: 1;
}

/* ── 구분선은 자리를 차지하지 않는다. 잡는 자리만 10px ────

   [왜 폭을 0 으로 두는가]

   DevExpress 기본값이 12px 인데 그만큼 **본문이 오른쪽으로 밀린다.** 본문에는
   이미 좌우 여백(1.5rem)이 있어서 사이드바 오른쪽에 12+24=36px 의 빈 띠가
   생기고, 탭 줄과 브레드크럼도 그만큼 밀려 헤더·사이드바와 세로줄이 어긋난다.

   2px 로 줄여도 **여전히 벌어져 보인다** — 사이드바에 이미 1px 테두리가
   있어서 그 옆의 빈 2px 이 「선 두 개」로 읽힌다. 그래서 0 으로 둔다.
   경계에 보이는 선은 사이드바의 테두리 하나뿐이고, 본문은 그 선에 바로 붙는다.

   폭이 0 이어도 끌 수 있다. 잡는 일은 상자가 아니라 **상자 밖으로 나간
   의사요소**가 하기 때문이다(아래 `::before`, 좌우 5px씩 10px).

   [본문을 구분선 아래로 겹치게 두지 않는다]

   판을 왼쪽으로 당겨 겹치면(음수 여백) 구분선이 **본문의 첫 12px 위에** 놓인다.
   그 띠는 화면 왼쪽 끝 전체를 따라 내려가고, 사람들이 실제로 누르는 자리
   (표의 첫 칸·트리의 펼침 화살표)가 거기 있다 — 벌어져 보이는 것보다
   **누른 것이 안 눌리는 것**이 나쁘다. 그리고 겹치는 값은 판 크기와 따로
   관리되는 두 번째 값이 되어, DevExpress 가 크기를 다시 셈할 때마다 어긋난다.

   그래서 **선 자체를 얇게** 한다(2px). 자리 차지는 2px 이고, 잡는 자리는
   `::before` 로 좌우 4px 씩 넓혀 10px 로 둔다 — 상자 밖으로 나가므로 판을
   밀지 않고, 마우스는 예전처럼 넉넉히 잡는다. VS Code 도 같은 방식이다.

   손잡이 점과 접기 화살표는 DevExpress 가 이미 상자 밖으로 넘겨 그리고 있어
   (재어 보니 32px 단추가 12px 상자에서 넘쳐 있었다) 얇아져도 그대로 보인다.
   ---------------------------------------------------------- */

.jsini-shell__split > .dxbl-splitter-separator::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline: -5px;
}

/* 마우스를 올리면 **경계에 2px 선**이 뜬다. 잡이(10px)를 통째로 칠하면 방금
   없앤 「벌어져 보임」이 마우스를 올릴 때마다 되돌아오므로, 잡이 가운데
   2px 만 칠한다. 자리 차지는 여전히 0 이다 — 칠하는 것은 상자 밖의
   의사요소다. */
.jsini-shell__split > .dxbl-splitter-separator:hover::before {
  background: linear-gradient(
    to right,
    transparent calc(50% - 1px),
    var(--jsini-accent) calc(50% - 1px) calc(50% + 1px),
    transparent calc(50% + 1px));
}

/* ── 끄는 도중에 미리 접어 보이기 ──────────────────────────

   구분선을 100px(`SidebarSnapPx`) 안쪽까지 끌면 **놓기 전에** 사이드바가
   접힌 모습이 된다. 손을 되돌려 문턱을 넘으면 그 자리에서 다시 펴진다 —
   놓아 봐야 알 수 있는 것을 끌면서 알 수 있게 하는 표시다.

   붙이고 떼는 일은 theme.js 가 한다(같은 이름). 여기서는 **판의 폭만**
   0 으로 만든다 — DevExpress 가 끌면서 적어 넣는 `flex`·`min-width` 를
   덮어야 해서 `!important` 다. 부품의 접힘(`Collapsed`)은 건드리지 않는다.
   진짜 접힘은 놓을 때 C# 이 하고, 그때 이 표시는 걷힌다.
   ---------------------------------------------------------- */

.jsini-shell--snap .jsini-shell__split > .dxbl-splitter-pane:first-of-type {
  flex-basis: 0 !important;
  min-width: 0 !important;
  overflow: hidden !important;

  /* 접히는 쪽만 미끄러진다. 표시를 뗄 때는 이 규칙째 사라지므로 **되펴짐은
     그 자리에서 일어난다** — 그래야 마우스가 있는 폭으로 곧바로 돌아온다.
     판에 늘 걸어 두면 끄는 내내 폭이 마우스보다 늦게 따라온다. */
  transition: flex-basis 0.12s ease-out;
}

/* 접힘과 같은 모습이라야 한다 — 폭만 0 이고 테두리가 남으면 본문 왼쪽에
   선 한 줄이 떠 있는 것으로 보인다. */
.jsini-shell--snap .jsini-shell__sidebar {
  border-right-width: 0;
  box-shadow: none;
}

/* ── 헤더 ─────────────────────────────────────────────────

   데모: 높이 3.5rem, 아래에 옅은 그림자(테두리가 아니다).
   그림자를 쓰면 스크롤한 본문이 헤더 밑으로 들어갈 때 층이 생긴다.
   ---------------------------------------------------------- */

.jsini-header {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  height: var(--jsini-header-height);

  /* 헤더는 띠 하나고 여기 있는 것이 전부 아이콘 단추다. 본문보다 한 단계
     크게 — 데모의 상단 바가 그 정도다.

     그 1.35rem 이 「보통」의 값이고, 글자를 작게 하면 배율만큼 줄어든다.
     크게 해도 여기서 멈춘다 — 이유는 `--jsini-chrome-scale` 머리말에 있다.

     **글자 사다리(`--jsini-fs-*`)를 직접 쓰지 않는다.** 그러면 「본문보다
     한 단계 크게」가 없어지고, 크게 골랐을 때 아이콘이 띠를 꽉 채운다. */
  --jsini-icon-size: calc(1.35rem * var(--jsini-chrome-scale));
  padding: 0 0.5rem;
  background: var(--jsini-bg);
  box-shadow: 0 2px 6px 0 rgb(0 0 0 / 12%);
  z-index: 1030;
}

/* 메뉴 접기 단추. 데모는 동그란 단추다. */
.jsini-header .jsini-header__toggle .dxbl-btn {
  border-radius: 50%;
  padding: 0.5rem;
}

.jsini-header__brand {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-left: 0.5rem;
  color: inherit;
  text-decoration: none;
}

/* 32px 이 「보통」의 값이다. 아이콘과 같은 배율을 쓴다 — 로고만 그대로 두면
   아이콘이 줄어든 옆에서 혼자 커 보인다. */
.jsini-header__logo {
  width: calc(32px * var(--jsini-chrome-scale));
  height: calc(32px * var(--jsini-chrome-scale));
}

/* ── 브랜드 심볼 (BrandMark) ──────────────────────────────────

   두 획의 색을 테마 토큰으로 준다. 어두운 테마에서는 두 값이 함께 뒤집혀
   **고르는 일 없이** 따라온다 — 옛 Vue 부품이 밝은 것과 녹아웃 두 벌을
   들고 있던 자리다. */
.jsini-brand-mark { display: block; }

/* 헤더의 심볼은 배율을 따른다.

   [왜 CSS 로 덮어야 하나]

   `BrandMark` 는 크기를 **SVG 속성**(`width` · `height`)으로 준다. 속성은
   CSS 가 덮을 수 있지만 CSS 를 안 쓰면 그 값이 그대로 남는다 — 그래서
   한동안 아이콘만 줄고 **로고는 꿈쩍하지 않았다.**

   `height: auto` 로 두면 `viewBox`(84×60)가 비율을 지킨다. 높이를 함께
   계산해 적으면 그 비율을 두 곳에서 관리하게 된다.

   [38 은 `BrandMark.Size` 의 기본값이다]

   그 기본값을 고치면 이 줄도 같이 고쳐야 한다. 헤더로 좁혀 둔 이유는
   다른 자리에서 `Size` 를 주고 쓸 때 그 값이 이기게 하려는 것이다 —
   지금 쓰는 곳은 헤더 하나뿐이지만, 좁혀 두면 늘어나도 안 깨진다. */
.jsini-header .jsini-brand-mark {
  width: calc(38px * var(--jsini-chrome-scale));
  height: auto;
}

/* J 가 본문 글자색, S 가 옅은 색이다. 둘을 같은 색으로 두면 겹친 자리가
   한 덩어리로 뭉개져 「JS」 로 읽히지 않는다. */
.jsini-brand-mark__j { fill: var(--jsini-text); }
.jsini-brand-mark__s { fill: var(--jsini-text-muted); }

/* 로고와 글자 사이의 세로 선. 데모의 `.logo-sep` 과 같다. */
.jsini-header__sep {
  width: 1px;

  /* 로고와 같은 배율. 선만 그대로 두면 낮아진 띠를 위아래로 넘친다. */
  height: calc(2rem * var(--jsini-chrome-scale));
  background: currentcolor;
  opacity: 0.15;
}

/* 로고 옆의 권한 그룹. 역할이 둘이면 「시스템관리자 · 프로젝트관리 관리자」
   처럼 길어져서 헤더의 다른 것들을 밀어낸다. 자르고 `title` 로 남긴다.

   글자를 벌리지 않는다(`letter-spacing`). 「JSINI」 를 적던 자리라 벌려
   두었는데, 사람이 읽는 이름에 그것을 걸면 한글 자모가 성기게 보인다. */
.jsini-header__brand-text {
  font-weight: 600;
  max-width: 16rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.jsini-header__spacer { flex: 1; }

.jsini-header__login {
  padding: 0 0.6rem;
  color: var(--jsini-accent-text);
  text-decoration: none;
}

/* ── 헤더의 사용자 얼굴과 그 목록 ─────────────────────────

   눌러서 펼치는 판이다. DevExpress 드롭다운을 쓰지 않은 이유는
   `UserMenu.razor` 머리말에 있다 — 단추 얼굴이 사진이고 판 맨 위에
   이름·소속 두 줄이 들어가서, 항목만 넣을 수 있는 부품으로는 안 된다.

   자리를 잡는 것은 `position: absolute` 한 줄이다. 기준은 `.jsini-user` 고,
   그것이 헤더 오른쪽 끝이라 판을 오른쪽에 붙이면 화면 밖으로 나가지 않는다.
   ---------------------------------------------------------- */

.jsini-user {
  position: relative;
  display: flex;
  align-items: center;
  padding-left: 0.35rem;
}

.jsini-user__face {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--jsini-border);
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
}

.jsini-user__face:hover,
.jsini-user__face.is-on {
  border-color: var(--jsini-accent);
}

.jsini-user__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* 사진이 없는 계정이 훨씬 많다. 첫 글자를 넣어 **비어 보이지 않게** 한다. */
.jsini-user__initial {
  font-size: var(--jsini-fs-md);
  font-weight: 700;
  color: var(--jsini-text-muted);
}

/* 덮개는 헤더를 덮지 않는다 — 덮으면 방금 누른 얼굴로 다시 닫을 수 없다.
   테마 서랍과 같은 이유이고 같은 자리다. */
.jsini-user__scrim {
  position: fixed;
  inset: var(--jsini-header-height) 0 0 0;
  z-index: 1040;
}

.jsini-user__menu {
  position: absolute;
  top: calc(100% + 0.35rem);
  right: 0;
  z-index: 1041;
  width: 250px;
  max-height: calc(100vh - var(--jsini-header-height) - 1rem);
  overflow-y: auto;
  padding: 0.35rem 0;
  border: 1px solid var(--jsini-border);
  border-radius: 0.35rem;
  background: var(--jsini-bg);
  color: var(--jsini-text);
  box-shadow: 0 8px 24px rgb(0 0 0 / 18%);

  /* 닫혀 있을 때는 이 판을 아예 그리지 않는다(UserMenu.razor 참고).
     그래서 감췄다 보이는 상태가 없고, 여기에 적을 것도 없다. */
}

@media (max-width: 767px) {
  .jsini-user__menu {
    position: fixed;
    top: var(--jsini-header-height);
    left: 0;
    right: 0;
    bottom: 0;
    width: auto;
    border: none;
    border-radius: 0;
    overflow-y: auto;
    /* 모바일 하단 네비게이션바 높이만큼 여백을 주어 로그아웃 버튼 등이 가려지지 않게 한다. */
    padding-bottom: calc(var(--jsini-bottomnav-h) + env(safe-area-inset-bottom) + 0.35rem);
  }

  /* 환경설정에서 하단 띠를 숨긴 경우 여백을 되돌린다. */
  .jsini-shell--bottomnav-hidden .jsini-user__menu {
    padding-bottom: calc(env(safe-area-inset-bottom) + 0.35rem);
  }
}


.jsini-user__head {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.6rem 0.75rem;
  border-bottom: 1px solid var(--jsini-border);
}

.jsini-user__head-img,
.jsini-user__head-initial {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  overflow: hidden;
  border-radius: 50%;
  object-fit: cover;
}

.jsini-user__head-initial {
  background: color-mix(in srgb, currentcolor 10%, transparent);
  font-size: var(--jsini-fs-lg);
  font-weight: 700;
  color: var(--jsini-text-muted);
}

.jsini-user__head-text { min-width: 0; }

.jsini-user__head-name {
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.jsini-user__head-sub {
  font-size: var(--jsini-fs-xs);
  color: var(--jsini-text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 묶음 제목. 항목이 열 개가 넘어서 없으면 한 덩어리로 읽힌다. */
.jsini-user__group {
  padding: 0.45rem 0.75rem 0.15rem;
  font-size: var(--jsini-fs-2xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--jsini-text-muted);
}

.jsini-user__item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.4rem 0.75rem;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.jsini-user__item:hover {
  background: color-mix(in srgb, currentcolor 8%, transparent);
}

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

/* 아이콘 집안이 둘이다. 이 판에 박아 둔 것은 `jsini-icon-*`(크기를
   `--jsini-icon-size` 로 잰다)이고, 「개인화」 묶음은 **메뉴에서 온**
   `jsini-mi--*`(크기를 `em` 으로 잰다)다. 둘을 같은 자로 맞춰 둔다 —
   안 맞추면 그 묶음만 그림이 미묘하게 작아 줄이 들쭉날쭉해 보인다.
   아래 띠(`.jsini-bottom-nav__icon`)·칸 바꾸기 판과 같은 규칙이다. */
.jsini-user__item > .jsini-mi {
  width: var(--jsini-icon-size);
  height: var(--jsini-icon-size);
}

.jsini-user__sep {
  margin: 0.35rem 0;
  border-top: 1px solid var(--jsini-border);
}

/* 로그아웃만 색을 달리한다. 되돌리기 어려운 항목이라 나머지와 같아 보이면
   안 된다 — 목록 맨 끝이라 잘못 누르기도 쉽다. */
.jsini-user__item--out { color: #c0392b; }

.jsini-user__item--out [class^='jsini-icon-'] { background-color: #c0392b; }

/* ── 사이드바 ─────────────────────────────────────────────

   데모: 330px, 오른쪽에 안쪽 그림자와 1px 선.
   안쪽 그림자가 있어야 본문과 판이 갈려 보인다 — 선만으로는 밋밋하다.
   ---------------------------------------------------------- */

.jsini-shell__sidebar {
  grid-row: 2;
  grid-column: 1;
  background: var(--jsini-bg);
  border-right: 1px solid var(--jsini-border);
  box-shadow: inset -140px -8px 30px -100px rgb(0 0 0 / 8%);
  overflow: hidden;
  min-width: 0;
}

.jsini-shell--collapsed .jsini-shell__sidebar {
  border-right-width: 0;
  box-shadow: none;
}

/* 사이드바 안쪽 판. **폭을 스스로 정하지 않는다.**

   한동안 `--jsini-sidebar-width`(330px)를 그대로 물고 있었다. 격자가 폭을
   정하던 시절에는 같은 값이라 문제가 없었는데, 구분선을 끌어 폭을 바꾸게
   되면서 **판만 330px 에 남았다** — 사이드바를 520px 로 넓혀도 검색 칸과
   트리는 330px 자리에 그대로 있고 오른쪽 190px 이 빈 채로 남았다.

   지금 폭의 임자는 스플리터의 판이다(`DxSplitterPane.Size`). 여기서는
   받은 자리를 꽉 채운다. 좁은 화면에서는 사이드바가 본문을 덮는 고정
   덮개라 그때만 아래 미디어 쿼리가 폭을 다시 정한다. */
.jsini-sidebar {
  display: flex;
  flex-direction: column;
  height: 100%;
  width: 100%;
  min-width: 0;
}

/* ── 사이드바 검색 ───────────────────────────────────────

   **검색 칸은 우리가 그린다.** 한동안 DxTreeView 의 `ShowFilterPanel` 이
   그린 칸을 썼는데, 그것은 트리 **안쪽** 맨 위에 붙는다. 검색 · 탭 · 트리
   차례가 되면서 그 칸이 탭보다 아래로 내려가 버려 자리가 맞지 않았다.

   거르는 일은 여전히 DevExpress 몫이다 — 여기서 받은 글자를 `FilterString`
   으로 넘기기만 한다(SidebarMenu.razor 참고).

   생김새는 DevExpress 데모의 "Find demo by keyword" 줄 그대로다.
   높이 3rem · 바탕 한 톤 짙게 · 아래 선 하나 · 테두리 없음.
   ---------------------------------------------------------- */

.jsini-sidebar__search {
  flex: 0 0 auto;
  height: 3rem;
  display: flex;
  align-items: center;
  padding: 0 0.75rem;
  background: var(--jsini-surface);
  border-bottom: 1px solid var(--jsini-border);
}

/* 줄 전체가 검색 칸처럼 보이게 편집기의 테두리·바탕을 걷어낸다. */
.jsini-sidebar__search-box {
  width: 100%;
  --dxbl-text-edit-bg: transparent;
  --dxbl-text-edit-border-color: transparent;
  --dxbl-text-edit-box-shadow: none;

  /* ── Fluent 은 **아래 선을 따로 그린다** ──────────────────

     Classic(Bootstrap 계열)에서는 위 세 줄로 깔끔했는데 Fluent 에서는
     칸 아래에 짙은 선 하나가 남아 있었다. 테두리 색을 안 따라간 것이 아니라
     **다른 변수로 그리기 때문**이다 —

         .dxbl-text-edit { border-bottom-color: var(--dxbl-text-edit-underline-color) }

     그 밑줄이 Fluent 입력칸의 표식이다. 상태마다 변수가 따로 있고, 고른
     상태에서는 테마가 `--…-underline-color` 를 `--…-underline-focused-color`
     로 갈아 끼우므로 **넷을 다 비워야** 어느 상태에서도 선이 안 남는다.
     (그래서 하나만 비웠을 때는 눌러 넣는 순간 선이 되돌아왔다.)

     [고른 상태에도 아무것도 그리지 않는다]

     한동안 선 대신 옅은 바탕(강조색 8%)을 깔아 「지금 여기에 있다」를
     알렸는데, 그 바탕이 둥근 네모로 보여서 **테두리가 되돌아온 것처럼**
     읽혔다. 이 칸에서 원하는 것은 깔끔함이고, 그래서 바탕도 뺐다.

     남는 단서는 **깜박이는 글자 자리(캐럿)와 지우기 단추**다. 자판으로만
     옮겨 다니는 사람에게는 약한 단서지만, 이 칸은 사이드바 맨 위에 하나뿐이라
     「어디에 있는지」를 잃을 자리가 아니다. 표 안의 편집칸처럼 여러 칸이
     나란한 자리에서는 이 방식을 그대로 쓰지 않는다. */
  --dxbl-text-edit-underline-color: transparent;
  --dxbl-text-edit-underline-hovered-color: transparent;
  --dxbl-text-edit-underline-active-color: transparent;
  --dxbl-text-edit-underline-focused-color: transparent;

  /* 테두리 쪽도 상태마다 변수가 따로다. **`--…-border-color` 하나만 비우면
     마우스를 올릴 때 네 변이 다시 나타난다** — 그때는 테마가 그 값을
     `--…-border-hovered-color` 로 갈아 끼우기 때문이다(누를 때는
     `-active-color`). 위 밑줄과 같은 얘기라 둘을 나란히 적어 둔다.

     [왜 갈아 끼우는 규칙과 다투지 않는가]

     그 규칙들은 `:not()` 을 넷 달아 자릿수가 다섯이라 우리가 이길 수 없다.
     대신 **그 규칙이 읽어 가는 변수**를 비운다 — 변수를 정하는 쪽은
     `.dxbl-text-edit` 하나뿐이고, app.css 가 테마보다 뒤에 꽂히므로
     같은 자릿수로도 우리 값이 남는다. */
  --dxbl-text-edit-border-hovered-color: transparent;
  --dxbl-text-edit-border-active-color: transparent;
}

/* ── 사이드바 탭 (메뉴 · 즐겨찾기) ────────────────────────

   `DxTabs` 를 쓰지 않는 이유는 SidebarMenu.razor 에 적어 두었다.
   고른 탭은 색이 아니라 **아래 선**으로 표시한다 — 색만으로 가르면
   색각 이상이 있는 사용자에게는 두 탭이 같아 보인다.
   ---------------------------------------------------------- */

.jsini-sidebar__tabs {
  flex: 0 0 auto;
  display: flex;
  border-bottom: 1px solid var(--jsini-border);
}

/* ── 좁은 사이드바에서는 탭이 그림만 남는다 ────────────────

   구분선으로 90px 까지 좁힐 수 있게 되면서 이 두 칸이 **가로로 넘쳤다** —
   「메뉴」와 「즐겨찾기 4」에 99px 이 필요한데 판이 89px 이었다. 넘친 것은
   잘리지 않고 판을 밀어서, 사이드바 안에 가로 스크롤이 생겼다.

   [미디어 질의가 아니라 **컨테이너 질의**다]

   기준이 화면 폭이 아니라 **사이드바 폭**이다. 사람이 구분선을 끌어 정하는
   값이라 화면 크기와 아무 상관이 없다 — 1920px 모니터에서도 90px 사이드바를
   쓸 수 있다. `container-type: inline-size` 를 사이드바에 걸어 두면 그 폭으로
   물을 수 있다.

   경계값 180px 은 **재서 정했다.** 「즐겨찾기 4」가 그림·글자·개수까지
   들어가려면 69px, 「메뉴」가 30px, 둘 사이 여백까지 99px 이다. 여유를 두어
   180px 아래에서 글자를 접는다 — 그 밑에서는 두 칸 중 하나가 반드시 잘린다.
   ---------------------------------------------------------- */

.jsini-sidebar {
  container-type: inline-size;
  container-name: jsini-sidebar;
}

/* 「메뉴」 칸의 그림은 좁을 때만 나온다. 넓을 때 함께 두면 글자와 그림이
   같은 말을 두 번 하고, 옆 칸(별표+즐겨찾기)과 무게가 어긋난다.

   **자릿수를 하나 올려 적는다.** 아이콘 묶음의 `[class^='jsini-icon-']` 이
   `display: inline-block` 을 걸고 그 규칙이 이 파일에서 **뒤에** 있어서,
   클래스 하나로 적으면 안 감춰진다 — 재어 보니 330px 에서도 그림이 나와
   있었다. */
.jsini-sidebar__tab > .jsini-sidebar__tab-icon { display: none; }

@container jsini-sidebar (max-width: 180px) {
  .jsini-sidebar__tab > .jsini-sidebar__tab-icon { display: inline-block; }

  .jsini-sidebar__tab-text,
  .jsini-sidebar__tab-count {
    display: none;
  }
}

.jsini-sidebar__tab {
  flex: 1;

  /* **`min-width: 0` 이 없으면 줄어들지 않는다.** flex 칸의 기본 최소 크기가
     내용 크기(min-content)라, 글자를 감춘 뒤에도 남은 그림과 여백이 판보다
     넓으면 그대로 밀고 나간다. */
  min-width: 0;
  overflow: hidden;

  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  padding: 0.6rem 0.5rem;
  border: 0;
  border-bottom: 2px solid transparent;
  background: none;
  color: inherit;
  font: inherit;

  /* 사다리 단으로 적는다. `rem` 으로 박으면 크기 여섯 단계 중 어느 것을
     골라도 탭만 그대로 있다 — 바로 아래 트리는 따라가므로 그 어긋남이
     눈에 띈다. sm 은 보통에서 13.0px 로, 박아 두었던 0.85rem(13.6px)과
     사실상 같다. */
  font-size: var(--jsini-fs-sm);
  cursor: pointer;

  /* 눌린 탭과 자리가 어긋나지 않게 굵기 자리를 미리 잡아 둔다.
     `font-weight` 만 바꾸면 고를 때마다 글자 폭이 늘어 탭이 흔들린다. */
  letter-spacing: 0.01em;
}

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

.jsini-sidebar__tab.is-on {
  border-bottom-color: var(--jsini-accent);
  color: var(--jsini-accent-text);
}

/* 담아 둔 개수. 탭 이름과 같은 크기면 어느 쪽이 이름인지 헷갈린다. */
.jsini-sidebar__tab-count {
  font-size: var(--jsini-fs-2xs);
  opacity: 0.7;
  flex-shrink: 0;   /* 개수가 글자에 밀려 사라지지 않는다 */
}

/* ── 탭 텍스트가 세로로 나열되지 않게 한다 ────────────────
   좁은 사이드바에서 flex 칸 안의 글자가 한 글자씩 내려가는
   현상을 막는다. 공간이 부족하면 글자가 잘려 보이는 것이
   세로로 나열되는 것보다 낫다. */
.jsini-sidebar__tab-text {
  white-space: nowrap;     /* 줄바꿈 금지 */
  overflow: hidden;        /* 넘치는 글자 숨김 */
  text-overflow: ellipsis; /* 잘릴 때 … 표시 */
  min-width: 0;            /* flex shrink 허용 */
}

/* 즐겨찾기가 비었을 때, 그리고 메뉴에 없는 즐겨찾기를 알릴 때. */
.jsini-sidebar__empty {
  margin: 0;
  padding: 1rem;
  font-size: var(--jsini-fs-sm);
  line-height: 1.6;
  color: var(--jsini-text-muted);
}

/* ── 사이드바 트리 ───────────────────────────────────────

   [실제로 그려지는 모양]

     div.jsini-sidebar__tree.dxbl-treeview
       └ dxbl-treeview.dxbl-treeview-container
          └ dxbl-scroll-viewer                            ← 여기만 구른다
             └ div.dxbl-scroll-viewer-content
                └ ul.dxbl-treeview-items-container[data-is-root]
                   └ li.dxbl-treeview-item
                      └ div.dxbl-treeview-item-content
                         ├ button                         펼침
                         └ div.dxbl-treeview-item-container[aria-level]

   겉껍데기가 세 겹(`dxbl-treeview` · `dxbl-treeview-container` ·
   `dxbl-scroll-viewer`)이라 **그 셋에 다 flex 를 걸어야** 위쪽 검색·탭이
   고정되고 트리만 구른다. 한 겹이라도 빠뜨리면 높이가 안 흘러서 트리가
   사이드바 밖으로 넘친다.
   ---------------------------------------------------------- */

.jsini-sidebar__tree {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 0;

  /* 데모가 자기 CSS 에서 바꾸는 것은 이 둘과 최상위 글자 굵기뿐이다.
     여기서 색과 선을 직접 칠하기 시작하면 테마 스물둘이 전부 어긋난다. */
  --dxbl-treeview-spacing-x: 0.5rem;
  --dxbl-treeview-spacing-y: 0.5rem;
  --dxbl-treeview-navigation-filter-bottom-spacing: 0;
}

.jsini-sidebar__tree > .dxbl-treeview-container {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* 트리 본문만 구른다. 트리 전체를 굴리면 메뉴를 내려보다가 검색 칸이 화면
   밖으로 나가서, 179개 중에 찾다 말고 다시 올라와야 한다. */
.jsini-sidebar__tree .dxbl-scroll-viewer {
  flex: 1;
  min-height: 0;
  padding: 0 var(--dxbl-treeview-spacing-x) var(--dxbl-treeview-spacing-y);

  /* [스크롤 막대는 감추고 구르기는 남긴다]

     여기 보이는 세로 막대는 **브라우저가 그린 것이 아니다.** 그래서
     `scrollbar-width: none` 도 `::-webkit-scrollbar` 도 하나도 안 걷힌다
     (그 둘을 걸어 두었다가 「여전히 보인다」는 말을 들었다).

     실제로 구르는 칸은 `.dxbl-scroll-viewer-content` 이고 그쪽 네이티브
     막대는 DevExpress 가 이미 감춰 두었다(그쪽 CSS 에 `scrollbar-width:
     none` 이 있다 — 재어 보니 `offsetWidth - clientWidth = 0`). 눈에
     걸리는 것은 그 위에 **DevExpress 가 따로 그린 div** —
     `.dxbl-scroll-viewer-vert-scroll-bar` 와 그 안의 `dxbl-thumb` 이다
     (폭 8px, 엄지 3.2px).

     `display: none` 으로 지우려면 DevExpress 가 켤 때 쓰는 규칙
     (`…-vert-scroll-bar:not(.dxbl-disabled):not(:disabled).dxbl-active`,
     클래스 다섯)을 `!important` 로 눌러야 한다. 대신 **그 막대가 폭을
     꺼내 쓰는 변수를 0 으로 돌린다** — 변수는 상속되므로 특이도 싸움이
     없고(`.dxbl-scroll-viewer` 한 겹보다 여기가 깊다), 구르기는 네이티브
     `overflow: auto` 그대로라 **마우스 휠이 살아 있다**(실측: 휠 한 번에
     scrollTop 0 → 65, 막대 폭은 8 → 0).

     `hover-width` 까지 같이 0 으로 둬야 한다 — 막대 위에 마우스를 올리거나
     엄지를 끌 때 쓰는 폭이 그쪽이라, 빼놓으면 지나갈 때만 다시 나타난다. */
  --dxbl-scroll-viewer-vert-scroll-bar-width: 0;
  --dxbl-scroll-viewer-vert-scroll-bar-hover-width: 0;
}

/* 최상위(업무 묶음)만 굵게. 데모의 사이드바가 그렇게 생겼다.

   `aria-level` 로 고른다. 부모를 여섯 겹 타고 내려가는 선택자보다 짧고,
   DevExpress 가 겉껍데기를 한 겹 더 넣어도 안 깨진다. */
.jsini-sidebar__tree .dxbl-treeview-item-container[aria-level='1'] {
  --dxbl-treeview-font-weight: 600;
}

/* 메뉴 제목을 대문자로 바꾸지 않는다 — 한글에는 뜻이 없고, 영문 제목만
   혼자 커 보인다. */
.jsini-sidebar__tree .dxbl-treeview-item-container {
  text-transform: none;
}

/* 검색에 걸린 것이 없을 때 DevExpress 가 넣는 줄. 기본값이
   "No data to display" 인데, 한국어 화면에서 가장 자주 보이는 자리 중
   하나가 영어로 남는다. 한국어 리소스는 DevExpress 지역화 서비스로 따로
   받아야 해서 여기서는 글자만 갈아 끼운다.

   **트리를 우리가 감추고 대신 안내를 띄우는 방법은 쓰지 않는다.** 그러려면
   무엇이 걸리는지를 우리가 다시 판정해야 하는데, 그 판정이 DevExpress 와
   조금이라도 어긋나면 **있는 결과를 감추는** 쪽으로 틀린다. 글자만 바꾸면
   그 틀릴 자리가 없다. */
.jsini-sidebar__tree .dxbl-treeview-empty-data {
  font-size: 0;
}

.jsini-sidebar__tree .dxbl-treeview-empty-data::after {
  content: "찾는 메뉴가 없습니다.";
  font-size: var(--jsini-fs-sm);
  color: var(--jsini-text-muted);
}

/* ── 메뉴 한 줄: [아이콘] 제목 …………… [펼침] ──────────────

   옛 vben 사이드바의 모양이다. 아이콘은 `SidebarMenu.NodeText` 가 그리고
   (DB 의 iconify 이름 → `menu-icons.css` 의 mask 클래스), **펼침 표시는
   DevExpress 가 제목 앞에 그린 단추를 여기서 오른쪽 끝으로 옮긴다.**

   [왜 flex 순서가 아니라 절대 자리인가]

   `order` 로 오른쪽에 보내면 그 단추가 **강조 알약 바깥**에 남는다. 색이
   깔리는 것은 제목을 감싼 `…item-container` 하나뿐이고, 단추는 그 형제라서
   마우스를 올렸을 때 알약이 화살표 앞에서 끊긴다.

   절대 자리로 띄우면 알약이 줄 전체를 채우고 화살표가 그 위에 앉는다 —
   vben 이 한 줄 통째로 강조하던 모양과 같아진다. 그 대신 제목이 화살표
   아래로 흐르지 않게 오른쪽 여백을 준다.

   `…item-content` 는 이미 `position: relative` 다(DevExpress 가 그렇게 준다.
   재어 확인했다). 그래도 여기 한 줄 적어 둔다 — 그 값이 바뀌면 화살표가
   사이드바 바깥으로 튀어 나가므로 우리 쪽에서 붙잡고 있어야 한다.

   [`.dxbl-btn-tool` 을 선택자에 하나 더 붙인 이유]

   DevExpress 도 그 단추에 `position: relative` 를 주고 그 규칙이 우리와
   **자릿수가 같다**(클래스 셋). 테마 CSS 는 theme.js 가 꽂으므로 app.css
   **뒤**에 들어가고, 같은 자릿수면 뒤엣것이 이긴다 — 그래서 규칙은 실려
   있는데 화살표가 안 움직였다(브라우저에서 재어 확인했다).

   클래스를 하나 더 붙여 한 칸 이긴다. `!important` 를 쓰지 않는 이유는
   그것이 이 파일에서 처음이 되기 때문이다 — 한 번 쓰면 다음 사람이 같은
   자리에서 또 쓰고, 그러면 무엇이 무엇을 이기는지 아무도 모르게 된다.
   ---------------------------------------------------------- */

.jsini-sidebar__tree .dxbl-treeview-item-content {
  position: relative;
  /* li 와 li 사이 간격을 item-content 의 마진으로 줘서 li 끼리 붙어 있어도 시각적 간격이 유지되게 한다 */
  padding-bottom: 0;
  margin-bottom: var(--dxbl-treeview-item-spacing-y);
}

/* ── 선택된 메뉴 및 호버 영역이 li 너비 전체를 차지하도록 확장 ──
   DevExpress 기본 동작에서는 dxbl-treeview-item-indent 가 flex 흐름 상
   왼쪽 폭을 밀어내어, 그 뒤에 오는 .dxbl-treeview-item-container 의 폭이
   li 전체 너비보다 줄어들고 들여쓰기된 만큼 배경색이 비게 된다.
   indent 폭을 0으로 만들어 item-container 가 li 전체 폭(100%)을 채우도록 하고,
   계층별 들여쓰기는 item-container 의 padding-left 로 전달한다. */
.jsini-sidebar .jsini-sidebar__tree .dxbl-treeview-item > .dxbl-treeview-item-content > .dxbl-treeview-item-indent {
  min-width: 0;
  width: 0;
}

.jsini-sidebar .jsini-sidebar__tree .dxbl-treeview-item > .dxbl-treeview-item-content > .dxbl-treeview-item-container {
  width: 100%;
  padding-left: calc(0.5rem + var(--dxbl-treeview-item-indent, 0) * var(--dxbl-treeview-item-content-indent, 0.9375rem));
}

.jsini-sidebar__tree .dxbl-treeview-item-content > .dxbl-btn.dxbl-btn-tool {
  position: absolute;
  right: 0;

  /* 줄 높이가 아니라 **단추 높이**를 지킨 채 가운데로 놓는다.
     `top: 0; bottom: 0` 으로 잡으면 단추가 줄 높이만큼 늘어나고, 그 안의
     화살표는 위쪽 여백(9px)에 붙어 있어 눈으로는 위로 치우쳐 보인다. */
  top: 50%;
  transform: translateY(-50%);
}

/* ── 화살표 크기는 글자 크기를 따라간다 ───────────────────

   [무엇이 문제였나 — 재어 본 값]

   테마 서랍에서 크기를 바꾸면 제목과 메뉴 아이콘은 함께 움직이는데
   **화살표만 두 칸에서 12px 로 같았다.** DevExpress 가 그 그림을 글자가
   아니라 여백 토큰으로 재기 때문이다 —

       --dxbl-treeview-btn-icon-size-s: var(--dxds-spacing-120)   → 12px
       --dxbl-treeview-btn-icon-size-m: var(--dxds-spacing-120)   → 12px, 같다
       --dxbl-treeview-btn-icon-size-l: var(--dxds-spacing-160)   → 16px

   | 크기 | 제목 | 메뉴 아이콘 | 화살표(전) | 화살표(후) |
   |---|---|---|---|---|
   | 작게 | 12px | 13.8px | 12px | 12px |
   | 보통 | 14px | 16.1px | **12px** | **14px** |
   | 크게 | 16px | 18.4px | 16px | 16px |

   바뀌는 것은 보통 한 칸뿐이다(12 → 14px). 작게·크게는 DevExpress 가 고른
   값과 우연히 같다 — 그래서 이 고침은 **보통에서만 눈에 띈다.**

   [그림 규칙과 다투지 않고 **그쪽 변수**를 덮는다]

   화살표 그림을 겨누는 DevExpress 규칙은 클래스 여섯 개짜리다 —

       .dxbl-treeview .dxbl-treeview-item > .dxbl-treeview-item-content
         > .dxbl-btn:not(.dxbl-nav-expand-btn-custom) > .dxbl-image

   그것을 이기려면 우리 선택자도 그만큼 길어져야 하고, 그 길이가 곧
   **DevExpress 마크업 구조에 대한 의존**이 된다(한 겹만 바뀌어도 안 걸린다).
   그래서 그 규칙이 **읽는 변수**를 나무 뿌리에서 덮는다. 뿌리에서 그 값을
   정하는 규칙은 클래스 둘(`.dxbl-treeview.dxbl-sm`)이라 셋으로 이긴다.

   값은 **그 줄의 글자 크기 그대로**다(`--dxbl-treeview-font-size`). 우리가
   고른 배수가 아니라 DevExpress 가 크기 단마다 정해 둔 값을 그대로 쓰는 것이라,
   테마를 올려 단 값이 바뀌어도 함께 따라온다. 크게에서는 DevExpress 가
   원래 고른 16px 과 같아진다.
   ---------------------------------------------------------- */

.jsini-sidebar .jsini-sidebar__tree.dxbl-treeview {
  /* ── 메뉴 글자는 **여섯 단계를 그대로 따라간다** ──────────

     [고르는 단계는 여섯인데 DevExpress 는 셋이다]

     테마 서랍의 크기는 가장작게 · 아주작게 · 작게 · 조금작게 · 보통 · 크게
     여섯이고, 그중 앞의 넷이 DevExpress **Small 하나로 접힌다**
     (`ThemeSize.Steps` — 없는 SizeMode 를 흘리면 그리드·달력·팝업이 안 따라와
     한 화면에 두 크기가 된다). 그래서 메뉴 글자가 DevExpress 의 단 값
     (`--dxbl-treeview-font-size`)에 묶여 있는 동안은 **앞의 네 단계가 전부
     12px 로 같았다.** 재어 확인했다 —

         가장작게 12 · 아주작게 12 · 작게 12 · 조금작게 12 · 보통 14 · 크게 16

     여섯 단계를 아는 것은 우리 사다리뿐이므로(`--jsini-fs-base` 를
     `data-dx-size` 로 고른다) 나무의 글자 크기를 그 값으로 덮는다. 그러면

         가장작게 10 · 아주작게 11 · 작게 12 · 조금작게 13 · 보통 14 · 크게 16

     이 되고, 화살표(바로 아래 줄)와 메뉴 아이콘(`1.15em`)이 함께 따라온다.

     [줄 높이는 건드리지 않는다]

     우리가 넣은 네 단계는 **밀도는 「작게」 그대로 두고 글자만 줄이는 것**이
     뜻이다(사다리 머리말). 줄 높이·단추 높이는 DevExpress 가 Small 로 잡은
     값을 그대로 쓴다 — 여기서 그것까지 비율로 바꾸면 서랍의 단계가 사실상
     여섯 개의 밀도가 되어, 부품(그리드·달력)과 어긋나는 자리가 다시 생긴다.

     자릿수로 이긴다: 이 값을 정하는 테마 규칙이 `.dxbl-treeview.dxbl-sm`
     (클래스 둘)이라 셋으로 적었다. */
  --dxbl-treeview-font-size: var(--jsini-fs-base);

  --dxbl-treeview-btn-icon-size: var(--dxbl-treeview-font-size);
}

/* 화살표가 앉을 자리. 제목이 길면 여기서 잘린다.

   `em` 이라 화살표와 함께 커진다. 보통에서 26.6px 로 단추 폭(24px)보다 조금
   넓다 — 딱 맞추면 긴 제목의 마지막 글자가 화살표에 닿는다.

   선택자가 긴 이유는 위와 같다. 테마가 이 칸에 `padding` 을 **약칭으로**
   걸어 두어(클래스 넷) 짧게 적으면 우리 값이 덮인다 — 그래서 한동안 이 여백이
   실제로는 안 걸려 있었다. */
.jsini-sidebar .jsini-sidebar__tree .dxbl-treeview-item > .dxbl-treeview-item-content > .dxbl-treeview-item-container {
  padding-right: 1.9em;
}

/* 아이콘과 제목. 아이콘은 줄지 않고(`flex: 0 0 auto` 는 `.jsini-mi` 가 갖는다)
   제목만 줄어들어야 하므로 `min-width: 0` 을 준다 — 없으면 제목이 안 잘리고
   줄 폭을 밀어낸다. */
.jsini-sidebar__node {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}

/* 아이콘은 제목보다 한 톤 옅게. 색을 죽이면 제목이 먼저 읽힌다 —
   179개를 훑어 내려갈 때 그림이 글자만큼 진하면 둘이 서로를 가린다.
   고른 줄에서는 글자와 같은 색으로 돌아온다(아래 규칙). */
.jsini-sidebar__node > .jsini-mi {
  opacity: 0.65;
}

.jsini-sidebar__tree .dxbl-treeview-item-container.dxbl-active .jsini-mi,
.jsini-sidebar__tree .dxbl-treeview-item-container:hover .jsini-mi {
  opacity: 1;
}

/* 제목이 길면 자른다. 메뉴 이름이 스무 자를 넘는 것이 실제로 있고
   (「빈소 디스플레이 재생목록 관리」), 줄바꿈하면 트리가 들쭉날쭉해진다.

   두 자리에 건다 — 우리 템플릿의 제목과, 템플릿이 없을 때 DevExpress 가
   그리는 제목(즐겨찾기·검색 결과도 같은 템플릿을 쓰므로 지금은 앞엣것만
   나오지만, 템플릿을 떼면 뒤엣것으로 돌아간다). */
.jsini-sidebar__node-title,
.jsini-sidebar__tree .dxbl-treeview-item-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── 사이드바 바닥 띠 (누구로 접속했나) ───────────────────

   [늘 바닥에 붙는 것은 이 두 줄이 한다]

   `.jsini-sidebar` 가 세로 flex 이고 트리가 `flex: 1; min-height: 0` 이다.
   그래서 이 띠를 `flex: 0 0 auto` 로 두면 **남는 높이를 트리가 다 먹고**
   띠는 바닥에 남는다. `position: absolute` 로 붙이지 않는다 — 그러면 트리가
   띠 아래로 흘러 들어가 마지막 메뉴가 가려진다.

   높이를 고정하지 않는다. 소속이 없는 계정, 아이디가 이름과 같은 계정처럼
   줄 수가 달라지는 경우가 있어서다 — 고정하면 그때 글자가 잘리거나 빈 칸이 남는다.
   ---------------------------------------------------------- */

.jsini-sidebar__foot {
  flex: 0 0 auto;
  padding: 0.6rem 0.75rem 0.7rem;
  border-top: 1px solid var(--jsini-border);
  background: var(--jsini-surface);

  /* 메뉴보다 한 단계 작게. 늘 보이는 띠라 본문과 같은 크기면 시선을 끈다. */
  font-size: var(--jsini-fs-xs);
  line-height: 1.45;
}

.jsini-sidebar__foot-top {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* 이름이 길면 자른다 — 나가기 단추를 밀어내면 안 된다. */
.jsini-sidebar__foot-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--jsini-fs-sm);
  font-weight: 600;
}

.jsini-sidebar__foot-out {
  flex: 0 0 auto;
  margin: 0;
}

/* DevExpress 단추를 쓰지 않는다. 이것은 폼의 제출 단추여야 하고
   (SidebarFooter 머리말 — 회로가 끊긴 뒤에도 눌려야 한다) DxButton 은
   자기 클릭 처리를 붙인다. */
.jsini-sidebar__foot-button {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.2rem 0.5rem;
  border: 1px solid var(--jsini-border);
  border-radius: 0.25rem;
  background: var(--jsini-bg);
  color: var(--jsini-text-muted);
  font: inherit;
  font-size: var(--jsini-fs-2xs);
  cursor: pointer;
}

.jsini-sidebar__foot-button:hover {
  color: var(--jsini-text);
  background: var(--jsini-surface);
}

.jsini-sidebar__foot-line {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--jsini-text-muted);
}

.jsini-sidebar__foot-ip {
  display: flex;
  gap: 0.3rem;

  /* 주소는 숫자와 점이라 고정폭이 읽기 쉽다. */
  font-variant-numeric: tabular-nums;
}

.jsini-sidebar__foot-label {
  flex: 0 0 auto;
  opacity: 0.75;
}

/* 휴대폰·태블릿에서는 이 띠를 통째로 감춘다.

   좁은 화면에서 사이드바는 본문을 **덮고** 메뉴를 누르면 곧 접힌다.
   몇 초 밖에 안 떠 있는 판인데, 그 판의 바닥 네 줄을 이름·소속·
   아이디·접속 주소가 가져가면 **정작 메뉴가 그만큼 줄어든다.**

   나가기를 잃는 것이 아니다 — 좁은 화면에도 헤더의 프로필 메뉴
   (`UserMenu`)가 남아 있고(「모바일 상단 띠에는 아이콘 넷」), 그것이
   같은 로그아웃 폼을 제출한다. 누구로 접속했는지도 그 메뉴가 말해 준다.

   경계값 1023px 은 `MainLayout` 의 휴대폰(≤767)·태블릿(≤1023) 구분과
   같다 — 한 쪽만 옮기면 메뉴는 태블릿으로 걸러졌는데 띠는 남아 있는
   폭이 생긴다.

   `@if` 로 갈아 끼우지 않는 까닭은 나머지와 같다 — 경계를 넘을 때마다
   부품이 다시 만들어지고, 그러면 접속 주소를 물고 오는 프리렌더 값도
   함께 버려진다. */
@media (max-width: 1023px) {
  .jsini-sidebar__foot {
    display: none;
  }
}

/* ── 아이콘 고르개 (IconPicker) ───────────────────────────

   메뉴 수정 창 안에 들어가므로(팝업 안) **떠 있는 판을 쓰지 않는다.**
   펼치면 폼이 길어지고, 격자가 스스로 굴러 창이 화면을 넘지 않는다.
   ---------------------------------------------------------- */

.jsini-iconpick__row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* 지금 값의 그림. 고르기 전에도 보여야 한다 — 저장된 이름이 맞는지
   확인할 자리가 여기밖에 없다. */
.jsini-iconpick__preview {
  flex: 0 0 auto;
  width: 1.5rem;
  height: 1.5rem;
}

.jsini-iconpick__text { flex: 1; min-width: 0; }

/* 펼친 판. **창 바탕보다 한 단 눌러 앉은 색**이라야 「창 안의 판」으로 읽힌다.

   `--jsini-surface` 였다. 그 값은 밝은 쪽에서 창 바탕과 **똑같은 흰색**이라
   (둘 다 `#ffffff`) 판이 1px 선만 남고 사라졌다. 창의 바닥 띠가 쓰는 것과
   같은 눌린 표면(`--jsini-surface-muted`)으로 바꾼다 — 밝은 쪽에서도
   어두운 쪽에서도 창과 갈라진다. */
.jsini-iconpick__panel {
  margin-top: 0.5rem;
  padding: 0.6rem;
  border: 1px solid var(--jsini-border);
  border-radius: var(--jsini-radius-md);
  background: var(--jsini-surface-muted);
}

.jsini-iconpick__search { width: 100%; }

/* 격자. `auto-fill` 이라 창 폭이 바뀌면 칸 수가 따라온다 — 칸 수를 박으면
   좁은 화면에서 가로 스크롤이 생긴다.

   높이를 묶어 두고 여기만 구른다. 안 묶으면 아이콘이 140개라 폼이 화면보다
   길어지고, 그러면 창 전체가 굴러 아래의 「저장」 단추가 멀어진다. */
.jsini-iconpick__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(2.4rem, 1fr));
  gap: 0.25rem;
  max-height: 13rem;
  overflow-y: auto;
  margin-top: 0.5rem;
}

.jsini-iconpick__cell {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 0.25rem;
  background: transparent;
  color: inherit;
  cursor: pointer;
}

.jsini-iconpick__cell > span {
  width: 1.35rem;
  height: 1.35rem;
}

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

/* 고른 칸은 **면으로** 칠한다. 테두리만 두르면 그 칸이 1px 커져 격자가
   들썩인다 — 테마 서랍에서 같은 이유로 같은 선택을 했다.

   **면 색은 강조색이다.** 처음에는 테마 서랍과 같은 회색
   (`--jsini-theme-picked`)을 썼는데, 그 회색은 서랍에서만 옳다 — 거기서는
   **고르는 대상이 강조색 자신**이라 강조색으로 칠할 수가 없다. 여기는
   그럴 이유가 없고, 표의 고른 줄이 쓰는 값
   (`color-mix(… 18%, var(--jsini-surface-raised))`)과 같아야 포털 안에서
   「고른 것」이 늘 같은 색으로 읽힌다. */
.jsini-iconpick__cell.is-on {
  background: color-mix(in srgb, var(--jsini-accent) 18%, var(--jsini-surface-raised));
  border-color: var(--jsini-accent);
}

.jsini-iconpick__empty,
.jsini-iconpick__note {
  margin: 0.5rem 0 0;
  font-size: var(--jsini-fs-2xs);
  color: var(--jsini-text-muted);
}

/* ── 본문 쪽 ─────────────────────────────────────────────── */

.jsini-shell__main {
  grid-row: 2;
  grid-column: 2;
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
  transition: margin-right 0.22s cubic-bezier(0.2, 0, 0, 1);
}

@media (min-width: 768px) {
  body.jsini-has-pinned-drawer .jsini-shell__main {
    margin-right: min(26rem, 100vw);
  }
}

/*
   본문 여백. **3px 다** — 넓은 화면에서도 좁은 화면에서도 같다.

   [24px 에서 내려왔다 (2026-10-07)]

   전에는 데모(demos.devexpress.com/blazor)를 재어 맞춘 24px 이었다. 그 글은
   문서 사이트의 글줄을 기준으로 한 값인데, 여기 실리는 것은 글이 아니라
   **표와 입력 폼**이다. 표는 폭이 넓을수록 한 줄에 담기는 칸이 늘고, 좁은
   화면에서는 24px 둘이 쓸 수 있는 폭의 13% 였다 — 좁은 화면 규칙이 이미
   같은 까닭으로 0.2rem 까지 내려와 있었고(아래 `max-width: 767px`),
   넓은 화면만 옛 값으로 남아 있던 셈이다.

   읽는 데 필요한 틈은 **안쪽 판이 이미 준다**(`commsch`·`commcont` 의 제
   여백). 바깥에서 한 겹 더 두면 그 틈이 두 겹이 된다.

   아래쪽만 좁은 화면에서 예외다 — 하단 띠(60px + 안전영역)가 고정으로 떠
   있어서 그만큼 비워 두지 않으면 마지막 줄에 영영 닿지 못한다. 그것은
   여백이 아니라 자리 비킴이다.
*/
.jsini-shell__content {
  flex: 1;
  padding: var(--jsini-shell-pad-top) var(--jsini-shell-pad-inline)
           var(--jsini-shell-pad-bottom);
  overflow: auto;

  /* 옮기는 동안의 덮개(`.jsini-pageload`)가 여기에 자리를 잡는다.
     아래 「화면을 옮기는 동안의 표시」 참고. */
  position: relative;
}

/* ── 탭 줄 ───────────────────────────────────────────────

   데모에는 없다(문서 사이트라 탭이 필요 없다). vben 에 있던 것을 되살린
   자리라, 데모의 브레드크럼 줄과 같은 높이·같은 바탕으로 맞춰 두 줄이
   한 덩어리로 보이게 한다.
   ---------------------------------------------------------- */

.jsini-tabs {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  height: 2.5rem;
  padding: 0 0.5rem;
  background: var(--jsini-surface);
  border-bottom: 1px solid var(--jsini-border);
}

/* 탭이 많아지면 가로로 넘긴다. 줄바꿈하면 탭 줄이 두세 줄이 되어
   본문이 그만큼 좁아진다. */
.jsini-tabs__list {
  display: flex;
  flex: 1;
  gap: 0.15rem;
  height: 100%;
  align-items: stretch;
  overflow-x: auto;
  scrollbar-width: none;
}

.jsini-tabs__list::-webkit-scrollbar { display: none; }

.jsini-tab {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  max-width: 190px;
  border-bottom: 2px solid transparent;
}

.jsini-tab:hover {
  background: color-mix(in srgb, currentcolor 6%, transparent);
}

/* 켜진 탭은 **아래 선**으로 표시한다. 배경을 칠하면 테마 22가지에서
   저마다 다른 색과 부딪친다. */
.jsini-tab--active {
  border-bottom-color: var(--jsini-accent);
}

/* 글자 크기를 정하지 않는다 — 테마가 주는 기본값을 쓴다(파일 첫머리 참고). */
.jsini-tab__link {
  display: block;
  padding: 0 0.3rem 0 0.65rem;
  line-height: 2.4rem;
  color: inherit;
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.jsini-tab--active .jsini-tab__link { font-weight: 600; }

.jsini-tab__close {
  border: 0;
  background: none;
  cursor: pointer;
  padding: 0 0.45rem 0 0.15rem;
  opacity: 0.5;
  font: inherit;
  line-height: 1;
  color: inherit;
}

.jsini-tab__close:hover { opacity: 1; }

/* ── 브레드크럼 ──────────────────────────────────────────

   **헤더 안, 로고 옆이다.** 데모는 본문 위에 제 줄(3rem)로 두는데 우리는
   그 위에 탭 줄까지 있어서 본문이 시작되기 전에 9rem 이 사라졌다. 헤더
   오른쪽은 도구 단추가 끝에 붙어 가운데가 늘 비어 있으므로 거기 얹는다.

   그래서 **제 배경도, 아래 선도, 정해진 높이도 없다** — 헤더의 띠에 얹힌
   글자 줄이다. 칸은 opacity .6 이고 사이의 구분은 **작은 삼각형**이다
   (글자 › 가 아니다).
   ---------------------------------------------------------- */

.jsini-crumbs {
  display: flex;
  align-items: center;

  /* **늘이지 않고 줄기만 한다**(`0 1 auto`). 늘이면 헤더의 빈자리
     (`.jsini-header__spacer`)를 이것이 먹어 도구 단추가 가운데로 딸려 온다.

     `min-width: 0` 이 있어야 실제로 줄어든다 — flex 항목의 기본 최소 폭이
     내용 크기라, 이 줄이 없으면 줄기는 대신 **도구 단추를 헤더 밖으로
     밀어낸다.** 줄어든 만큼은 아래 `overflow-x` 가 받는다. */
  flex: 0 1 auto;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}

/* 로고·권한 그룹과 갈라 주는 세로 선. 생김새는 `.jsini-header__sep` 이
   정하고 여기서는 **줄지 않게만** 묶는다 — 폭 1px 짜리라 헤더가 좁아지면
   0 으로 눌려 사라진다. */
.jsini-crumbs__lead {
  flex: 0 0 auto;

  /* 왼쪽은 브랜드 묶음의 간격(`.jsini-header__brand` 의 `gap`)과 같은 값이다.
     이 `<nav>` 은 그 묶음 **밖**이라 gap 이 안 걸린다 — 안 주면 선이 권한
     그룹 글자에 바로 붙는다. */
  margin: 0 0.8rem 0 0.75rem;
}

.jsini-crumbs::-webkit-scrollbar { display: none; }

/* **줄지 않게 묶는다.** flex 항목이라 기본으로 눌리는데, 그러면 헤더가
   좁아질 때 칸마다 조금씩 찌그러져 글자가 잘린다 — 줄이지 않아야 넘친
   만큼을 `.jsini-crumbs` 의 가로 스크롤이 받는다. */
.jsini-crumbs__item {
  display: inline-block;
  flex: 0 0 auto;
  margin-right: 0.8rem;
  white-space: nowrap;
  opacity: 0.6;
  color: inherit;
  text-decoration: none;
}

/* 묶음과 지금 화면은 `<button>` 이다 — 갈 곳은 없지만 눌러서 사이드바를
   그 자리로 편다(`Breadcrumb.razor` 머리말). **생김새는 링크와 같아야
   한다** — 한 줄 안에서 어떤 칸은 단추처럼, 어떤 칸은 글자처럼 보이면
   무엇이 눌리는 칸인지 오히려 알기 어렵다. */
button.jsini-crumbs__item {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  line-height: inherit;
  cursor: pointer;
}

/* 누를 수 있는 칸은 마우스를 올리면 또렷해진다. 이것이 「눌린다」는 유일한
   표시라 링크와 단추에 똑같이 건다. */
a.jsini-crumbs__item:hover,
button.jsini-crumbs__item:hover { opacity: 1; }

/* 키보드로 짚었을 때. 헤더가 어두운 테마에서도 보이게 현재 글자색으로 긋는다. */
.jsini-crumbs__item:focus-visible {
  opacity: 1;
  outline: 2px solid currentcolor;
  outline-offset: 2px;
  border-radius: 2px;
}

.jsini-crumbs__item--current {
  opacity: 1;
  font-weight: 600;
}

/* 삼각형 구분자. 데모가 테두리로 만든다 — 글꼴에 없는 글자를 쓰지 않아
   어느 환경에서나 같게 보인다. */
.jsini-crumbs__sep {
  display: inline-block;
  flex: 0 0 auto;
  width: 0;
  height: 0;
  margin-right: 0.8rem;
  border: 5px solid transparent;
  border-left-color: currentcolor;
  opacity: 0.45;
}

/* ── 아이콘 ───────────────────────────────────────────────

   글꼴을 하나 더 싣지 않는다. 필요한 것이 열몇 개뿐이라
   그림 문자를 마스크로 쓰면 **테마 색을 그대로 따라온다** —
   컬러 이모지를 그대로 쓰면 다크 모드에서 혼자 튄다.
   ---------------------------------------------------------- */

[class^='jsini-icon-'],
[class*=' jsini-icon-'] {
  display: inline-block;
  width: var(--jsini-icon-size);
  height: var(--jsini-icon-size);

  /* 글자보다 큰 그림이라 `middle` 로 세운다. 예전처럼 밑선(baseline) 기준으로
     조금 내리면, 글자보다 큰 만큼이 전부 위로 삐져나가 머리가 무거워 보인다. */
  vertical-align: middle;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.jsini-icon-blank { background-color: transparent; }

.jsini-icon-menu {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2 3.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5m0 4a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5m0 4a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2 3.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5m0 4a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5m0 4a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5'/%3E%3C/svg%3E");
}

.jsini-icon-home {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M7.313 1.262a1 1 0 0 1 1.374 0l4.844 4.579c.3.283.469.678.469 1.09v5.57a1.5 1.5 0 0 1-1.5 1.5h-2A1.5 1.5 0 0 1 9 12.5V10a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v2.5A1.5 1.5 0 0 1 5.5 14h-2A1.5 1.5 0 0 1 2 12.5V6.93c0-.412.17-.807.47-1.09zM8 1.988l-4.844 4.58A.5.5 0 0 0 3 6.93v5.57a.5.5 0 0 0 .5.5h2a.5.5 0 0 0 .5-.5V10a1.5 1.5 0 0 1 1.5-1.5h1A1.5 1.5 0 0 1 10 10v2.5a.5.5 0 0 0 .5.5h2a.5.5 0 0 0 .5-.5V6.93a.5.5 0 0 0-.156-.363z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M7.313 1.262a1 1 0 0 1 1.374 0l4.844 4.579c.3.283.469.678.469 1.09v5.57a1.5 1.5 0 0 1-1.5 1.5h-2A1.5 1.5 0 0 1 9 12.5V10a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v2.5A1.5 1.5 0 0 1 5.5 14h-2A1.5 1.5 0 0 1 2 12.5V6.93c0-.412.17-.807.47-1.09zM8 1.988l-4.844 4.58A.5.5 0 0 0 3 6.93v5.57a.5.5 0 0 0 .5.5h2a.5.5 0 0 0 .5-.5V10a1.5 1.5 0 0 1 1.5-1.5h1A1.5 1.5 0 0 1 10 10v2.5a.5.5 0 0 0 .5.5h2a.5.5 0 0 0 .5-.5V6.93a.5.5 0 0 0-.156-.363z'/%3E%3C/svg%3E");
}

/* 접었다 펴는 판(`<details>`)의 꺾쇠. 펴지면 CSS 가 뒤집는다 — 위를 보는
   꺾쇠를 따로 두지 않는 이유는 둘이 갈리면 각도가 어긋나기 때문이다. */
.jsini-icon-chevron-down {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.646 5.646a.5.5 0 0 1 .708 0L8 9.293l3.646-3.647a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 0-.708'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.646 5.646a.5.5 0 0 1 .708 0L8 9.293l3.646-3.647a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 0-.708'/%3E%3C/svg%3E");
}

/* ── 표의 「관리」 칸이 쓰는 아이콘 ────────────────────────────

   관리 칸의 단추는 **글자 없이 아이콘만** 쓴다. 까닭은 `.commgrd__actions`
   머리말에 적어 두었다. 여기 일곱은 그래서 새로 필요해진 것들이고, 출처는
   위 아이콘들과 같은 DevExpress Icon Library 다.
   ------------------------------------------------------------ */

/* 첨부 고르기 (FilePicker). 글자 없는 아이콘 단추라 이 그림이 곧 이름이다 —
   읽어 주는 글은 단추의 `title` 에 있다. */
.jsini-icon-paperclip {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4.5 3a2.5 2.5 0 0 1 5 0v9a1.5 1.5 0 0 1-3 0V5a.5.5 0 0 1 1 0v7a.5.5 0 0 0 1 0V3a1.5 1.5 0 1 0-3 0v9a2.5 2.5 0 0 0 5 0V5a.5.5 0 0 1 1 0v7a3.5 3.5 0 1 1-7 0z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4.5 3a2.5 2.5 0 0 1 5 0v9a1.5 1.5 0 0 1-3 0V5a.5.5 0 0 1 1 0v7a.5.5 0 0 0 1 0V3a1.5 1.5 0 1 0-3 0v9a2.5 2.5 0 0 0 5 0V5a.5.5 0 0 1 1 0v7a3.5 3.5 0 1 1-7 0z'/%3E%3C/svg%3E");
}

/* 소속·역할을 뗀다 (subtract) */
.jsini-icon-subtract {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3 8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9A.5.5 0 0 1 3 8'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3 8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9A.5.5 0 0 1 3 8'/%3E%3C/svg%3E");
}

/* 승인 · 완료 (checkmark_circle) */
.jsini-icon-check-circle {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 2a6 6 0 1 1 0 12A6 6 0 0 1 8 2m0 1a5 5 0 1 0 0 10A5 5 0 0 0 8 3m-.75 6.042 2.87-2.878a.5.5 0 0 1 .766.637l-.058.07-3.224 3.232a.5.5 0 0 1-.638.059l-.07-.058-1.75-1.75a.5.5 0 0 1 .638-.765l.07.057zl2.87-2.878z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 2a6 6 0 1 1 0 12A6 6 0 0 1 8 2m0 1a5 5 0 1 0 0 10A5 5 0 0 0 8 3m-.75 6.042 2.87-2.878a.5.5 0 0 1 .766.637l-.058.07-3.224 3.232a.5.5 0 0 1-.638.059l-.07-.058-1.75-1.75a.5.5 0 0 1 .638-.765l.07.057zl2.87-2.878z'/%3E%3C/svg%3E");
}

/* 거절 (close_circle) */
.jsini-icon-close-circle {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 2a6 6 0 1 1 0 12A6 6 0 0 1 8 2m0 1a5 5 0 1 0 0 10A5 5 0 0 0 8 3M5.839 5.966l.057-.07a.5.5 0 0 1 .638-.057l.07.057L8 7.293l1.396-1.397a.5.5 0 0 1 .638-.057l.07.057a.5.5 0 0 1 .057.638l-.057.07L8.707 8l1.397 1.396a.5.5 0 0 1 .057.638l-.057.07a.5.5 0 0 1-.638.057l-.07-.057L8 8.707l-1.396 1.397a.5.5 0 0 1-.638.057l-.07-.057a.5.5 0 0 1-.057-.638l.057-.07L7.293 8 5.896 6.604a.5.5 0 0 1-.057-.638l.057-.07z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 2a6 6 0 1 1 0 12A6 6 0 0 1 8 2m0 1a5 5 0 1 0 0 10A5 5 0 0 0 8 3M5.839 5.966l.057-.07a.5.5 0 0 1 .638-.057l.07.057L8 7.293l1.396-1.397a.5.5 0 0 1 .638-.057l.07.057a.5.5 0 0 1 .057.638l-.057.07L8.707 8l1.397 1.396a.5.5 0 0 1 .057.638l-.057.07a.5.5 0 0 1-.638.057l-.07-.057L8 8.707l-1.396 1.397a.5.5 0 0 1-.638.057l-.07-.057a.5.5 0 0 1-.057-.638l.057-.07L7.293 8 5.896 6.604a.5.5 0 0 1-.057-.638l.057-.07z'/%3E%3C/svg%3E");
}

/* 발인 · 출상 처리 (arrow_export_right) */
.jsini-icon-export {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M1.5 3a.5.5 0 0 1 .5.5V12a.5.5 0 1 1-1 0V3.5a.5.5 0 0 1 .5-.5m9.146.646a.5.5 0 0 1 .708 0l3.5 3.5a.5.5 0 0 1 0 .707l-3.5 3.5a.5.5 0 0 1-.708-.707L13.293 8H4.5a.5.5 0 0 1 0-1h8.793l-2.647-2.647a.5.5 0 0 1 0-.707'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M1.5 3a.5.5 0 0 1 .5.5V12a.5.5 0 1 1-1 0V3.5a.5.5 0 0 1 .5-.5m9.146.646a.5.5 0 0 1 .708 0l3.5 3.5a.5.5 0 0 1 0 .707l-3.5 3.5a.5.5 0 0 1-.708-.707L13.293 8H4.5a.5.5 0 0 1 0-1h8.793l-2.647-2.647a.5.5 0 0 1 0-.707'/%3E%3C/svg%3E");
}

/* 영정 사진 (image) */
.jsini-icon-image {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M11.5 5.502a1.002 1.002 0 1 1-2.004 0 1.002 1.002 0 0 1 2.004 0M2 4.5A2.5 2.5 0 0 1 4.5 2h7A2.5 2.5 0 0 1 14 4.5v7a2.5 2.5 0 0 1-2.5 2.5h-7A2.5 2.5 0 0 1 2 11.5zM4.5 3A1.5 1.5 0 0 0 3 4.5v7c0 .232.052.45.146.647l3.652-3.652a1.7 1.7 0 0 1 2.404 0l3.652 3.652c.094-.196.146-.415.146-.647v-7A1.5 1.5 0 0 0 11.5 3zm7.647 9.854L8.495 9.202a.7.7 0 0 0-.99 0l-3.652 3.652c.196.094.415.146.647.146h7c.232 0 .45-.053.647-.146'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M11.5 5.502a1.002 1.002 0 1 1-2.004 0 1.002 1.002 0 0 1 2.004 0M2 4.5A2.5 2.5 0 0 1 4.5 2h7A2.5 2.5 0 0 1 14 4.5v7a2.5 2.5 0 0 1-2.5 2.5h-7A2.5 2.5 0 0 1 2 11.5zM4.5 3A1.5 1.5 0 0 0 3 4.5v7c0 .232.052.45.146.647l3.652-3.652a1.7 1.7 0 0 1 2.404 0l3.652 3.652c.094-.196.146-.415.146-.647v-7A1.5 1.5 0 0 0 11.5 3zm7.647 9.854L8.495 9.202a.7.7 0 0 0-.99 0l-3.652 3.652c.196.094.415.146.647.146h7c.232 0 .45-.053.647-.146'/%3E%3C/svg%3E");
}

/* 화면을 끈다 (visibility_eye_off) */
.jsini-icon-eye-off {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='m10.12 10.827 4.026 4.027a.5.5 0 0 0 .708-.708l-13-13a.5.5 0 1 0-.708.708l3.23 3.23A6 6 0 0 0 3.2 6.182a6.7 6.7 0 0 0-1.117 1.982c-.021.061-.047.145-.047.145l-.018.062s-.076.497.355.611a.5.5 0 0 0 .611-.355l.001-.003.008-.025.035-.109a5.7 5.7 0 0 1 .945-1.674 5 5 0 0 1 1.124-1.014l1.578 1.578a2.5 2.5 0 1 0 3.446 3.446m-.74-.74A1.5 1.5 0 1 1 7.413 8.12zM6.32 4.2l.854.854Q7.564 5 8 5c2.044 0 3.286.912 4.028 1.817a5.7 5.7 0 0 1 .945 1.674q.025.073.035.109l.008.025v.003l.001.001a.5.5 0 0 0 .966-.257v-.003l-.001-.004-.004-.013a2 2 0 0 0-.06-.187 6.7 6.7 0 0 0-1.117-1.982C11.905 5.089 10.396 4 8.002 4c-.618 0-1.177.072-1.681.199'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='m10.12 10.827 4.026 4.027a.5.5 0 0 0 .708-.708l-13-13a.5.5 0 1 0-.708.708l3.23 3.23A6 6 0 0 0 3.2 6.182a6.7 6.7 0 0 0-1.117 1.982c-.021.061-.047.145-.047.145l-.018.062s-.076.497.355.611a.5.5 0 0 0 .611-.355l.001-.003.008-.025.035-.109a5.7 5.7 0 0 1 .945-1.674 5 5 0 0 1 1.124-1.014l1.578 1.578a2.5 2.5 0 1 0 3.446 3.446m-.74-.74A1.5 1.5 0 1 1 7.413 8.12zM6.32 4.2l.854.854Q7.564 5 8 5c2.044 0 3.286.912 4.028 1.817a5.7 5.7 0 0 1 .945 1.674q.025.073.035.109l.008.025v.003l.001.001a.5.5 0 0 0 .966-.257v-.003l-.001-.004-.004-.013a2 2 0 0 0-.06-.187 6.7 6.7 0 0 0-1.117-1.982C11.905 5.089 10.396 4 8.002 4c-.618 0-1.177.072-1.681.199'/%3E%3C/svg%3E");
}

/* 새 판을 받아 간다 (arrow_download) */
.jsini-icon-download {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 13h9a.5.5 0 0 1 .09.992L12.5 14h-9a.5.5 0 0 1-.09-.992zh9zM7.91 1.008 8 1a.5.5 0 0 1 .492.41l.008.09v8.792l2.682-2.681a.5.5 0 0 1 .638-.058l.07.058a.5.5 0 0 1 .057.638l-.058.069-3.535 3.536a.5.5 0 0 1-.638.057l-.07-.057-3.535-3.536a.5.5 0 0 1 .638-.765l.069.058L7.5 10.292V1.5a.5.5 0 0 1 .41-.492L8 1z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 13h9a.5.5 0 0 1 .09.992L12.5 14h-9a.5.5 0 0 1-.09-.992zh9zM7.91 1.008 8 1a.5.5 0 0 1 .492.41l.008.09v8.792l2.682-2.681a.5.5 0 0 1 .638-.058l.07.058a.5.5 0 0 1 .057.638l-.058.069-3.535 3.536a.5.5 0 0 1-.638.057l-.07-.057-3.535-3.536a.5.5 0 0 1 .638-.765l.069.058L7.5 10.292V1.5a.5.5 0 0 1 .41-.492L8 1z'/%3E%3C/svg%3E");
}

/* 즐겨찾기. **모양까지 다르다** — 빈 별과 채운 별은 색을 못 보는 사람에게도
   갈린다. 아래 금색은 채운 쪽을 한 번 더 구분해 주는 덤이다. */
.jsini-icon-star {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M7.194 2.102a.9.9 0 0 1 1.614 0l1.521 3.082 3.401.494a.9.9 0 0 1 .5 1.535l-2.462 2.4.581 3.387a.9.9 0 0 1-1.306.948L8.001 12.35l-3.042 1.6a.9.9 0 0 1-1.306-.95l.58-3.387-2.46-2.399a.9.9 0 0 1 .499-1.535l3.4-.494zM8 2.726 6.546 5.673a.9.9 0 0 1-.677.493l-3.253.472L4.97 8.933a.9.9 0 0 1 .258.796l-.555 3.24 2.91-1.53a.9.9 0 0 1 .837 0l2.91 1.53-.556-3.24a.9.9 0 0 1 .258-.796l2.354-2.295-3.253-.472a.9.9 0 0 1-.677-.493z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M7.194 2.102a.9.9 0 0 1 1.614 0l1.521 3.082 3.401.494a.9.9 0 0 1 .5 1.535l-2.462 2.4.581 3.387a.9.9 0 0 1-1.306.948L8.001 12.35l-3.042 1.6a.9.9 0 0 1-1.306-.95l.58-3.387-2.46-2.399a.9.9 0 0 1 .499-1.535l3.4-.494zM8 2.726 6.546 5.673a.9.9 0 0 1-.677.493l-3.253.472L4.97 8.933a.9.9 0 0 1 .258.796l-.555 3.24 2.91-1.53a.9.9 0 0 1 .837 0l2.91 1.53-.556-3.24a.9.9 0 0 1 .258-.796l2.354-2.295-3.253-.472a.9.9 0 0 1-.677-.493z'/%3E%3C/svg%3E");
}

.jsini-icon-star-on {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M7.194 2.102a.9.9 0 0 1 1.614 0l1.521 3.082 3.401.494a.9.9 0 0 1 .5 1.535l-2.462 2.4.581 3.387a.9.9 0 0 1-1.306.948L8.001 12.35l-3.042 1.6a.9.9 0 0 1-1.306-.95l.58-3.387-2.46-2.399a.9.9 0 0 1 .499-1.535l3.4-.494zM8 2.726 6.546 5.673a.9.9 0 0 1-.677.493l-3.253.472L4.97 8.933a.9.9 0 0 1 .258.796l-.555 3.24 2.91-1.53a.9.9 0 0 1 .837 0l2.91 1.53-.556-3.24a.9.9 0 0 1 .258-.796l2.354-2.295-3.253-.472a.9.9 0 0 1-.677-.493z'/%3E%3Cpath d='M8.002 2.726 6.548 5.673a.9.9 0 0 1-.678.493l-3.253.472 2.354 2.295a.9.9 0 0 1 .259.796l-.556 3.24 2.91-1.53a.9.9 0 0 1 .837 0l2.91 1.53-.556-3.24a.9.9 0 0 1 .259-.796l2.353-2.295-3.252-.472a.9.9 0 0 1-.678-.493z' opacity='.4'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M7.194 2.102a.9.9 0 0 1 1.614 0l1.521 3.082 3.401.494a.9.9 0 0 1 .5 1.535l-2.462 2.4.581 3.387a.9.9 0 0 1-1.306.948L8.001 12.35l-3.042 1.6a.9.9 0 0 1-1.306-.95l.58-3.387-2.46-2.399a.9.9 0 0 1 .499-1.535l3.4-.494zM8 2.726 6.546 5.673a.9.9 0 0 1-.677.493l-3.253.472L4.97 8.933a.9.9 0 0 1 .258.796l-.555 3.24 2.91-1.53a.9.9 0 0 1 .837 0l2.91 1.53-.556-3.24a.9.9 0 0 1 .258-.796l2.354-2.295-3.253-.472a.9.9 0 0 1-.677-.493z'/%3E%3Cpath d='M8.002 2.726 6.548 5.673a.9.9 0 0 1-.678.493l-3.253.472 2.354 2.295a.9.9 0 0 1 .259.796l-.556 3.24 2.91-1.53a.9.9 0 0 1 .837 0l2.91 1.53-.556-3.24a.9.9 0 0 1 .259-.796l2.353-2.295-3.252-.472a.9.9 0 0 1-.678-.493z' opacity='.4'/%3E%3C/svg%3E");
}

/* 담긴 상태만 색으로 구분한다. 모양까지 바꾸면 두 아이콘을 관리하게 된다. */
.jsini-icon-star-on { background-color: #e2a400; }

/* 표 아래 띠의 칸별 검색 단추 둘. 깔때기와, 조건이 걸린 깔때기. */
/* ── 아이콘은 **DevExpress Icon Library** 의 것이다 ──────────

   위아래 `.jsini-icon-*` 서른 남짓은 손으로 그린 것이 아니라 DevExpress 의
   아이콘 라이브러리(Fluent · Regular · Monochrome · 16px)에서 **모양을 그대로
   옮겨 온 것**이다. 고르는 자리는 demos.devexpress.com/blazor/IconExplorer 다.

   덕분에 우리 화면의 아이콘과 DevExpress 부품이 스스로 그리는 아이콘
   (달력 · 드롭다운 화살표 · 닫기)이 같은 손에서 나온 그림이 된다. 섞여 있으면
   획 두께와 모서리가 미묘하게 달라 「뭔가 짝이 안 맞는다」로 느껴진다.

   [왜 파일을 참조하지 않고 옮겨 적었나]

   `DxButton` 은 아이콘을 **CSS 클래스**로 받는다(`IconCssClass`). 그 자리에
   SVG 조각을 넣을 수 없고, `mask-image: url(스프라이트.svg#이름)` 도 안 된다 —
   `<symbol>` 은 조각으로 지목해도 그려지지 않는다.

   게다가 그 라이브러리는 아직 **CTP** 라 `DevExpress.Images.Blazor` 패키지가
   우리 nuget 피드에 올라와 있지 않다(찾아봤다). 정식 배포되면 그때
   `_content/DevExpress.Images.Blazor/...` 를 직접 물리면 되고, **그때 고칠 곳은
   이 파일 하나**다.

   색과 크기는 지금까지처럼 `currentColor` 와 `--jsini-icon-size` 가 쥔다 —
   마스크라 모양만 쓰기 때문이다.

   이름 대조표는 아래 각 규칙 옆에 적지 않았다. 그 대신 여기 —
   plus=add · refresh=arrow_sync · filter=filter_funnel ·
   filter-off=filter_funnel_clear · excel=arrow_download · edit=edit_pen ·
   trash=delete · search=search · star=star_outline · star-on=star ·
   bell=alert · user=user_male · logout=sign_out · menu=menu_hamburger ·
   palette=color_palette · moon=time_night · sun=weather_sunny ·
   eye=visibility_eye · lock=lock · pin=pin · home=home · check=checkmark ·
   close=cancel · undo=arrow_undo · more=dots_horizontal · fold=text_collapse_all ·
   unfold=text_expand_all · expand=full_screen_maximize ·
   child-plus=add_expand_square · copy=copy · chevron-down=chevron_down ·
   mail=mail

   **`jsini-icon-blank` · `jsini-icon-robot` · `jsini-icon-bolt` 만 우리 것이다** —
   첫째는 자리만 차지하는 빈칸이고, 나머지 둘에 맞는 그림이 그 라이브러리에 없다.
   ------------------------------------------------------------ */
.jsini-icon-filter {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m5.707 8.016.707-.745c.375.395.586.932.586 1.49v3.13l2 1.053V8.763c0-.56.21-1.096.586-1.491L12 4.726V3.054H4v1.672L6.414 7.27zM3.293 5.471l2.414 2.545c.188.198.293.466.293.746v3.128c0 .4.214.765.553.943l2 1.054c.665.35 1.447-.159 1.447-.943V8.763c0-.28.105-.548.293-.746l2.414-2.545c.188-.197.293-.466.293-.745V3.054C13 2.472 12.552 2 12 2H4c-.552 0-1 .472-1 1.054v1.672c0 .28.105.548.293.745'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m5.707 8.016.707-.745c.375.395.586.932.586 1.49v3.13l2 1.053V8.763c0-.56.21-1.096.586-1.491L12 4.726V3.054H4v1.672L6.414 7.27zM3.293 5.471l2.414 2.545c.188.198.293.466.293.746v3.128c0 .4.214.765.553.943l2 1.054c.665.35 1.447-.159 1.447-.943V8.763c0-.28.105-.548.293-.746l2.414-2.545c.188-.197.293-.466.293-.745V3.054C13 2.472 12.552 2 12 2H4c-.552 0-1 .472-1 1.054v1.672c0 .28.105.548.293.745'/%3E%3C/svg%3E");
}

.jsini-icon-filter-off {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M9.996 2a1 1 0 0 1 1 1v2.022a5.6 5.6 0 0 0-1 .001V3h-8v3.293L4.703 9l.067.073a1 1 0 0 1 .226.634v2.44l.29.105c.155.46.368.895.633 1.292l-1.427-.514a.75.75 0 0 1-.496-.706V9.707l-2.78-2.78a.75.75 0 0 1-.22-.53V3a1 1 0 0 1 1-1z'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.5 15a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9m1.854-6.354a.5.5 0 0 1 0 .708L11.207 10.5l1.147 1.146a.5.5 0 0 1-.708.708L10.5 11.207l-1.146 1.147a.5.5 0 0 1-.708-.708L9.793 10.5 8.646 9.354a.5.5 0 1 1 .708-.708L10.5 9.793l1.146-1.147a.5.5 0 0 1 .708 0'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M9.996 2a1 1 0 0 1 1 1v2.022a5.6 5.6 0 0 0-1 .001V3h-8v3.293L4.703 9l.067.073a1 1 0 0 1 .226.634v2.44l.29.105c.155.46.368.895.633 1.292l-1.427-.514a.75.75 0 0 1-.496-.706V9.707l-2.78-2.78a.75.75 0 0 1-.22-.53V3a1 1 0 0 1 1-1z'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.5 15a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9m1.854-6.354a.5.5 0 0 1 0 .708L11.207 10.5l1.147 1.146a.5.5 0 0 1-.708.708L10.5 11.207l-1.146 1.147a.5.5 0 0 1-.708-.708L9.793 10.5 8.646 9.354a.5.5 0 1 1 .708-.708L10.5 9.793l1.146-1.147a.5.5 0 0 1 .708 0'/%3E%3C/svg%3E");
}

/* 조회 영역의 단추 둘. 돋보기와 되돌리기. */
.jsini-icon-search {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M9.309 10.016a4.5 4.5 0 1 1 .707-.707l3.838 3.837a.5.5 0 0 1-.708.708zM10 6.5a3.5 3.5 0 1 0-7 0 3.5 3.5 0 0 0 7 0'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M9.309 10.016a4.5 4.5 0 1 1 .707-.707l3.838 3.837a.5.5 0 0 1-.708.708zM10 6.5a3.5 3.5 0 1 0-7 0 3.5 3.5 0 0 0 7 0'/%3E%3C/svg%3E");
}

.jsini-icon-undo {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3 2.5a.5.5 0 0 1 1 0v3.843l3.171-3.171a4 4 0 0 1 5.657 5.656l-5.025 5.026a.5.5 0 0 1-.707-.708l5.025-5.025A3 3 0 1 0 7.878 3.88L4.757 7H8.5a.5.5 0 0 1 0 1H3.6a.6.6 0 0 1-.6-.6z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3 2.5a.5.5 0 0 1 1 0v3.843l3.171-3.171a4 4 0 0 1 5.657 5.656l-5.025 5.026a.5.5 0 0 1-.707-.708l5.025-5.025A3 3 0 1 0 7.878 3.88L4.757 7H8.5a.5.5 0 0 1 0 1H3.6a.6.6 0 0 1-.6-.6z'/%3E%3C/svg%3E");
}

.jsini-icon-refresh {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M7.146.646a.5.5 0 0 1 .708 0l1.5 1.5a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 1 1-.708-.708l.643-.642a5 5 0 0 0-2.937 8.88.5.5 0 1 1-.63.777A6 6 0 0 1 7.797 2.003l-.65-.65a.5.5 0 0 1 0-.707m3.929 2.766a.5.5 0 0 1 .703-.073 6 6 0 0 1-3.575 10.658l.65.65a.5.5 0 0 1-.707.707l-1.5-1.5a.5.5 0 0 1 0-.708l1.5-1.5a.5.5 0 0 1 .708.708l-.643.642a5 5 0 0 0 2.937-8.88.5.5 0 0 1-.073-.704'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M7.146.646a.5.5 0 0 1 .708 0l1.5 1.5a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 1 1-.708-.708l.643-.642a5 5 0 0 0-2.937 8.88.5.5 0 1 1-.63.777A6 6 0 0 1 7.797 2.003l-.65-.65a.5.5 0 0 1 0-.707m3.929 2.766a.5.5 0 0 1 .703-.073 6 6 0 0 1-3.575 10.658l.65.65a.5.5 0 0 1-.707.707l-1.5-1.5a.5.5 0 0 1 0-.708l1.5-1.5a.5.5 0 0 1 .708.708l-.643.642a5 5 0 0 0 2.937-8.88.5.5 0 0 1-.073-.704'/%3E%3C/svg%3E");
}

.jsini-icon-expand {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.75 3a.75.75 0 0 0-.75.75V5.5a.5.5 0 0 1-1 0V3.75C2 2.784 2.784 2 3.75 2H5.5a.5.5 0 0 1 0 1zM10 2.5a.5.5 0 0 1 .5-.5h1.75c.966 0 1.75.784 1.75 1.75V5.5a.5.5 0 0 1-1 0V3.75a.75.75 0 0 0-.75-.75H10.5a.5.5 0 0 1-.5-.5M2.5 10a.5.5 0 0 1 .5.5v1.75c0 .414.336.75.75.75H5.5a.5.5 0 0 1 0 1H3.75A1.75 1.75 0 0 1 2 12.25V10.5a.5.5 0 0 1 .5-.5m11 0a.5.5 0 0 1 .5.5v1.75A1.75 1.75 0 0 1 12.25 14H10.5a.5.5 0 0 1 0-1h1.75a.75.75 0 0 0 .75-.75V10.5a.5.5 0 0 1 .5-.5'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.75 3a.75.75 0 0 0-.75.75V5.5a.5.5 0 0 1-1 0V3.75C2 2.784 2.784 2 3.75 2H5.5a.5.5 0 0 1 0 1zM10 2.5a.5.5 0 0 1 .5-.5h1.75c.966 0 1.75.784 1.75 1.75V5.5a.5.5 0 0 1-1 0V3.75a.75.75 0 0 0-.75-.75H10.5a.5.5 0 0 1-.5-.5M2.5 10a.5.5 0 0 1 .5.5v1.75c0 .414.336.75.75.75H5.5a.5.5 0 0 1 0 1H3.75A1.75 1.75 0 0 1 2 12.25V10.5a.5.5 0 0 1 .5-.5m11 0a.5.5 0 0 1 .5.5v1.75A1.75 1.75 0 0 1 12.25 14H10.5a.5.5 0 0 1 0-1h1.75a.75.75 0 0 0 .75-.75V10.5a.5.5 0 0 1 .5-.5'/%3E%3C/svg%3E");
}

.jsini-icon-bell {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 2a4.5 4.5 0 0 0-4.5 4.5v2.401l-.964 2.414A.5.5 0 0 0 3 12h3c0 1.108.892 2 2 2s2-.892 2-2h3a.5.5 0 0 0 .464-.685L12.5 8.9V6.5A4.5 4.5 0 0 0 8 2m1 10c0 .556-.444 1-1 1s-1-.444-1-1zM4.5 6.5a3.5 3.5 0 1 1 7 0v2.498a.5.5 0 0 0 .036.185L12.262 11H3.738l.726-1.817a.5.5 0 0 0 .036-.185z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 2a4.5 4.5 0 0 0-4.5 4.5v2.401l-.964 2.414A.5.5 0 0 0 3 12h3c0 1.108.892 2 2 2s2-.892 2-2h3a.5.5 0 0 0 .464-.685L12.5 8.9V6.5A4.5 4.5 0 0 0 8 2m1 10c0 .556-.444 1-1 1s-1-.444-1-1zM4.5 6.5a3.5 3.5 0 1 1 7 0v2.498a.5.5 0 0 0 .036.185L12.262 11H3.738l.726-1.817a.5.5 0 0 0 .036-.185z'/%3E%3C/svg%3E");
}

/* 상단 띠의 「쪽지 쓰기」 단추. DevExpress 이름표로는 `mail` 이다. */
.jsini-icon-mail {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 3A2.5 2.5 0 0 0 1 5.5v5A2.5 2.5 0 0 0 3.5 13h9a2.5 2.5 0 0 0 2.5-2.5v-5A2.5 2.5 0 0 0 12.5 3zM2 5.5A1.5 1.5 0 0 1 3.5 4h9A1.5 1.5 0 0 1 14 5.5v.24l-6 3.2-6-3.2zm0 1.374 5.765 3.075a.5.5 0 0 0 .47 0L14 6.874V10.5a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 2 10.5z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 3A2.5 2.5 0 0 0 1 5.5v5A2.5 2.5 0 0 0 3.5 13h9a2.5 2.5 0 0 0 2.5-2.5v-5A2.5 2.5 0 0 0 12.5 3zM2 5.5A1.5 1.5 0 0 1 3.5 4h9A1.5 1.5 0 0 1 14 5.5v.24l-6 3.2-6-3.2zm0 1.374 5.765 3.075a.5.5 0 0 0 .47 0L14 6.874V10.5a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 2 10.5z'/%3E%3C/svg%3E");
}

.jsini-icon-user {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M11.524 8c.9 0 1.689.774 1.424 1.712-.216.769-.628 1.827-1.393 2.702C10.775 13.304 9.629 14 8 14s-2.774-.696-3.554-1.586c-.765-.875-1.177-1.933-1.394-2.702C2.788 8.774 3.576 8 4.477 8zM4.477 9a.47.47 0 0 0-.357.148.31.31 0 0 0-.065.302c.197.702.557 1.603 1.184 2.32C5.853 12.47 6.723 13 8 13s2.147-.53 2.76-1.23c.628-.717.988-1.618 1.185-2.32a.31.31 0 0 0-.065-.302.47.47 0 0 0-.357-.148zM8 1.5A2.75 2.75 0 1 1 8 7a2.75 2.75 0 0 1 0-5.5m0 1A1.75 1.75 0 1 0 8 6a1.75 1.75 0 0 0 0-3.5'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M11.524 8c.9 0 1.689.774 1.424 1.712-.216.769-.628 1.827-1.393 2.702C10.775 13.304 9.629 14 8 14s-2.774-.696-3.554-1.586c-.765-.875-1.177-1.933-1.394-2.702C2.788 8.774 3.576 8 4.477 8zM4.477 9a.47.47 0 0 0-.357.148.31.31 0 0 0-.065.302c.197.702.557 1.603 1.184 2.32C5.853 12.47 6.723 13 8 13s2.147-.53 2.76-1.23c.628-.717.988-1.618 1.185-2.32a.31.31 0 0 0-.065-.302.47.47 0 0 0-.357-.148zM8 1.5A2.75 2.75 0 1 1 8 7a2.75 2.75 0 0 1 0-5.5m0 1A1.75 1.75 0 1 0 8 6a1.75 1.75 0 0 0 0-3.5'/%3E%3C/svg%3E");
}

.jsini-icon-lock {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0M5 4h-.5A2.5 2.5 0 0 0 2 6.5v5A2.5 2.5 0 0 0 4.5 14h7a2.5 2.5 0 0 0 2.5-2.5v-5A2.5 2.5 0 0 0 11.5 4H11v-.5a3 3 0 0 0-6 0zm1-.5a2 2 0 1 1 4 0V4H6zM11.5 5A1.5 1.5 0 0 1 13 6.5v5a1.5 1.5 0 0 1-1.5 1.5h-7A1.5 1.5 0 0 1 3 11.5v-5A1.5 1.5 0 0 1 4.5 5z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0M5 4h-.5A2.5 2.5 0 0 0 2 6.5v5A2.5 2.5 0 0 0 4.5 14h7a2.5 2.5 0 0 0 2.5-2.5v-5A2.5 2.5 0 0 0 11.5 4H11v-.5a3 3 0 0 0-6 0zm1-.5a2 2 0 1 1 4 0V4H6zM11.5 5A1.5 1.5 0 0 1 13 6.5v5a1.5 1.5 0 0 1-1.5 1.5h-7A1.5 1.5 0 0 1 3 11.5v-5A1.5 1.5 0 0 1 4.5 5z'/%3E%3C/svg%3E");
}

.jsini-icon-logout {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8.37 2.018a.6.6 0 0 1 .63.6V7h4.293l-.646-.647-.065-.078a.5.5 0 0 1 .693-.693l.079.064 1.5 1.5.064.078q.013.022.023.043l.014.027a.5.5 0 0 1 .032.098l.003.022q.005.023.008.044v.028L15 7.5l-.002.01v.032q-.001.011-.004.022l-.003.02q0 .012-.003.021a.5.5 0 0 1-.033.1l-.01.02q-.013.026-.027.05l-.064.078-1.5 1.5-.079.065a.5.5 0 0 1-.693-.694l.064-.078.647-.646H9v5.382a.6.6 0 0 1-.63.6l-.068-.008-5.801-.967A.6.6 0 0 1 2 12.415v-8.83a.6.6 0 0 1 .501-.592l5.8-.967zM11 12.5a.5.5 0 0 1-.5.5H10V9h1zM3 3.923v8.153l5 .833V3.09zM6.25 8a.75.75 0 1 1 0 1.5.75.75 0 0 1 0-1.5m4.25-5a.5.5 0 0 1 .5.5V6h-1V3z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8.37 2.018a.6.6 0 0 1 .63.6V7h4.293l-.646-.647-.065-.078a.5.5 0 0 1 .693-.693l.079.064 1.5 1.5.064.078q.013.022.023.043l.014.027a.5.5 0 0 1 .032.098l.003.022q.005.023.008.044v.028L15 7.5l-.002.01v.032q-.001.011-.004.022l-.003.02q0 .012-.003.021a.5.5 0 0 1-.033.1l-.01.02q-.013.026-.027.05l-.064.078-1.5 1.5-.079.065a.5.5 0 0 1-.693-.694l.064-.078.647-.646H9v5.382a.6.6 0 0 1-.63.6l-.068-.008-5.801-.967A.6.6 0 0 1 2 12.415v-8.83a.6.6 0 0 1 .501-.592l5.8-.967zM11 12.5a.5.5 0 0 1-.5.5H10V9h1zM3 3.923v8.153l5 .833V3.09zM6.25 8a.75.75 0 1 1 0 1.5.75.75 0 0 1 0-1.5m4.25-5a.5.5 0 0 1 .5.5V6h-1V3z'/%3E%3C/svg%3E");
}

.jsini-icon-palette {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M7.5 5.5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1m2.5 0a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0M11.5 7a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0M11 9.5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1m-.5 1.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0M8 2a6 6 0 0 0-6 6 2 2 0 0 0 2 2c.708 0 1.085-.378 1.34-.633l.014-.013C5.598 9.109 5.72 9 6 9a1 1 0 0 1 1 1v1a3 3 0 0 0 3 3c1.334 0 2.354-.746 3.017-1.854C13.672 11.05 14 9.579 14 8a6 6 0 0 0-6-6M3 8a5 5 0 0 1 10 0c0 1.458-.305 2.737-.841 3.632C11.63 12.515 10.9 13 10 13a2 2 0 0 1-2-2v-1a2 2 0 0 0-2-2c-.708 0-1.085.378-1.34.633l-.014.013C4.402 8.891 4.28 9 4 9a1 1 0 0 1-1-1'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M7.5 5.5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1m2.5 0a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0M11.5 7a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0M11 9.5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1m-.5 1.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0M8 2a6 6 0 0 0-6 6 2 2 0 0 0 2 2c.708 0 1.085-.378 1.34-.633l.014-.013C5.598 9.109 5.72 9 6 9a1 1 0 0 1 1 1v1a3 3 0 0 0 3 3c1.334 0 2.354-.746 3.017-1.854C13.672 11.05 14 9.579 14 8a6 6 0 0 0-6-6M3 8a5 5 0 0 1 10 0c0 1.458-.305 2.737-.841 3.632C11.63 12.515 10.9 13 10 13a2 2 0 0 1-2-2v-1a2 2 0 0 0-2-2c-.708 0-1.085.378-1.34.633l-.014.013C4.402 8.891 4.28 9 4 9a1 1 0 0 1-1-1'/%3E%3C/svg%3E");
}

/* 밝기 두 칸의 그림. 데모에도 해와 달이 붙어 있다 —
   Light/Dark 는 글자만으로도 알지만, 그림이 있으면 굴리는 중에 눈이 먼저 잡는다. */
.jsini-icon-sun {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0v-1A.5.5 0 0 1 8 1m0 10a3 3 0 1 0 0-6 3 3 0 0 0 0 6m0-1a2 2 0 1 1 0-4 2 2 0 0 1 0 4m6.5-1.5a.5.5 0 0 0 0-1h-1a.5.5 0 0 0 0 1zM8 13a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0v-1A.5.5 0 0 1 8 13M2.5 8.5a.5.5 0 0 0 0-1h-1a.5.5 0 0 0 0 1zm.646-5.354a.5.5 0 0 1 .708 0l1 1a.5.5 0 1 1-.708.708l-1-1a.5.5 0 0 1 0-.708m.708 9.708a.5.5 0 1 1-.708-.707l1-1a.5.5 0 0 1 .708.707zm9-9.708a.5.5 0 0 0-.708 0l-1 1a.5.5 0 0 0 .708.708l1-1a.5.5 0 0 0 0-.708m-.708 9.708a.5.5 0 0 0 .708-.707l-1-1a.5.5 0 0 0-.708.707z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0v-1A.5.5 0 0 1 8 1m0 10a3 3 0 1 0 0-6 3 3 0 0 0 0 6m0-1a2 2 0 1 1 0-4 2 2 0 0 1 0 4m6.5-1.5a.5.5 0 0 0 0-1h-1a.5.5 0 0 0 0 1zM8 13a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0v-1A.5.5 0 0 1 8 13M2.5 8.5a.5.5 0 0 0 0-1h-1a.5.5 0 0 0 0 1zm.646-5.354a.5.5 0 0 1 .708 0l1 1a.5.5 0 1 1-.708.708l-1-1a.5.5 0 0 1 0-.708m.708 9.708a.5.5 0 1 1-.708-.707l1-1a.5.5 0 0 1 .708.707zm9-9.708a.5.5 0 0 0-.708 0l-1 1a.5.5 0 0 0 .708.708l1-1a.5.5 0 0 0 0-.708m-.708 9.708a.5.5 0 0 0 .708-.707l-1-1a.5.5 0 0 0-.708.707z'/%3E%3C/svg%3E");
}

.jsini-icon-moon {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M7.85 3.015a5 5 0 1 1-4.585 7.712c1.403-.38 3.316-1.302 4.16-3.551.552-1.474.584-2.938.425-4.16M13.456 8a6 6 0 0 0-6.21-5.996.5.5 0 0 0-.475.592c.23 1.214.28 2.728-.283 4.228-.8 2.134-2.802 2.84-4.077 3.071a.5.5 0 0 0-.361.71A6 6 0 0 0 13.456 8'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M7.85 3.015a5 5 0 1 1-4.585 7.712c1.403-.38 3.316-1.302 4.16-3.551.552-1.474.584-2.938.425-4.16M13.456 8a6 6 0 0 0-6.21-5.996.5.5 0 0 0-.475.592c.23 1.214.28 2.728-.283 4.228-.8 2.134-2.802 2.84-4.077 3.071a.5.5 0 0 0-.361.71A6 6 0 0 0 13.456 8'/%3E%3C/svg%3E");
}

.jsini-icon-check {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M13.864 3.656a.5.5 0 0 1-.021.707l-7.93 7.474a.6.6 0 0 1-.839-.016l-2.68-2.72a.5.5 0 0 1 .712-.702l2.406 2.442 7.645-7.206a.5.5 0 0 1 .707.021'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M13.864 3.656a.5.5 0 0 1-.021.707l-7.93 7.474a.6.6 0 0 1-.839-.016l-2.68-2.72a.5.5 0 0 1 .712-.702l2.406 2.442 7.645-7.206a.5.5 0 0 1 .707.021'/%3E%3C/svg%3E");
}

.jsini-icon-pin {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M10.071 2.442a1.503 1.503 0 0 0-2.39.353L5.658 6.587l-2.815.937a.5.5 0 0 0-.196.828l2.15 2.147-2.65 2.647L2 14l.855-.146 2.65-2.648 2.15 2.147a.5.5 0 0 0 .83-.195l.938-2.812 3.784-2.024a1.5 1.5 0 0 0 .355-2.381zm-1.506.823a.5.5 0 0 1 .797-.117l3.49 3.499a.5.5 0 0 1-.118.794l-3.96 2.117a.5.5 0 0 0-.239.283l-.745 2.233L3.93 8.217l2.235-.745a.5.5 0 0 0 .284-.239z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M10.071 2.442a1.503 1.503 0 0 0-2.39.353L5.658 6.587l-2.815.937a.5.5 0 0 0-.196.828l2.15 2.147-2.65 2.647L2 14l.855-.146 2.65-2.648 2.15 2.147a.5.5 0 0 0 .83-.195l.938-2.812 3.784-2.024a1.5 1.5 0 0 0 .355-2.381zm-1.506.823a.5.5 0 0 1 .797-.117l3.49 3.499a.5.5 0 0 1-.118.794l-3.96 2.117a.5.5 0 0 0-.239.283l-.745 2.233L3.93 8.217l2.235-.745a.5.5 0 0 0 .284-.239z'/%3E%3C/svg%3E");
}
.jsini-icon-pin-filled {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M10.071 2.442a1.503 1.503 0 0 0-2.39.353L5.658 6.587l-2.815.937a.5.5 0 0 0-.196.828l2.15 2.147-2.65 2.647L2 14l.855-.146 2.65-2.648 2.15 2.147a.5.5 0 0 0 .83-.195l.938-2.812 3.784-2.024a1.5 1.5 0 0 0 .355-2.381z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M10.071 2.442a1.503 1.503 0 0 0-2.39.353L5.658 6.587l-2.815.937a.5.5 0 0 0-.196.828l2.15 2.147-2.65 2.647L2 14l.855-.146 2.65-2.648 2.15 2.147a.5.5 0 0 0 .83-.195l.938-2.812 3.784-2.024a1.5 1.5 0 0 0 .355-2.381z'/%3E%3C/svg%3E");
}

.jsini-icon-close {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='m2.589 2.716.057-.07a.5.5 0 0 1 .638-.057l.07.057L8 7.293l4.646-4.647a.5.5 0 0 1 .708.708L8.707 8l4.647 4.646a.5.5 0 0 1 .057.638l-.057.07a.5.5 0 0 1-.638.057l-.07-.057L8 8.707l-4.646 4.647a.5.5 0 0 1-.708-.708L7.293 8 2.646 3.354a.5.5 0 0 1-.057-.638'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='m2.589 2.716.057-.07a.5.5 0 0 1 .638-.057l.07.057L8 7.293l4.646-4.647a.5.5 0 0 1 .708.708L8.707 8l4.647 4.646a.5.5 0 0 1 .057.638l-.057.07a.5.5 0 0 1-.638.057l-.07-.057L8 8.707l-4.646 4.647a.5.5 0 0 1-.708-.708L7.293 8 2.646 3.354a.5.5 0 0 1-.057-.638'/%3E%3C/svg%3E");
}

/* 탭 줄의 「URL 복사」. DevExpress 쪽 이름은 copy 다. */
.jsini-icon-copy {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M6.75 5A1.75 1.75 0 0 0 5 6.75v5.5c0 .966.784 1.75 1.75 1.75h5.5A1.75 1.75 0 0 0 14 12.25v-5.5A1.75 1.75 0 0 0 12.25 5zM6 6.75A.75.75 0 0 1 6.75 6h5.5a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-.75.75h-5.5a.75.75 0 0 1-.75-.75z'/%3E%3Cpath d='M3.75 2A1.75 1.75 0 0 0 2 3.75v5.5c0 .966.784 1.75 1.75 1.75H4v-1h-.25a.75.75 0 0 1-.75-.75v-5.5A.75.75 0 0 1 3.75 3h5.5a.75.75 0 0 1 .75.75V4h1v-.25A1.75 1.75 0 0 0 9.25 2z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M6.75 5A1.75 1.75 0 0 0 5 6.75v5.5c0 .966.784 1.75 1.75 1.75h5.5A1.75 1.75 0 0 0 14 12.25v-5.5A1.75 1.75 0 0 0 12.25 5zM6 6.75A.75.75 0 0 1 6.75 6h5.5a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-.75.75h-5.5a.75.75 0 0 1-.75-.75z'/%3E%3Cpath d='M3.75 2A1.75 1.75 0 0 0 2 3.75v5.5c0 .966.784 1.75 1.75 1.75H4v-1h-.25a.75.75 0 0 1-.75-.75v-5.5A.75.75 0 0 1 3.75 3h5.5a.75.75 0 0 1 .75.75V4h1v-.25A1.75 1.75 0 0 0 9.25 2z'/%3E%3C/svg%3E");
}

/* 자동 줄바꿈 토글 아이콘. (text-wrap) */
.jsini-icon-wrap {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M2 3.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm0 4a.5.5 0 0 1 .5-.5h7a2.5 2.5 0 0 1 0 5H6.707l1.147-1.146a.5.5 0 1 0-.708-.708l-2 2a.5.5 0 0 0 0 .708l2 2a.5.5 0 0 0 .708-.708L6.707 13H9.5a3.5 3.5 0 1 0 0-7H2.5a.5.5 0 0 1-.5-.5zm0 5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M2 3.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm0 4a.5.5 0 0 1 .5-.5h7a2.5 2.5 0 0 1 0 5H6.707l1.147-1.146a.5.5 0 1 0-.708-.708l-2 2a.5.5 0 0 0 0 .708l2 2a.5.5 0 0 0 .708-.708L6.707 13H9.5a3.5 3.5 0 1 0 0-7H2.5a.5.5 0 0 1-.5-.5zm0 5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}


.jsini-icon-more {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M5 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0m4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0m3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M5 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0m4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0m3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2'/%3E%3C/svg%3E");
}

/* 헤더의 「빠른 지시」 단추. **DevExpress 라이브러리에 번개가 없어 직접 그렸다** —
   찾아본 것 중 뜻이 가장 가까운 것이 `flash`(카메라 플래시)였는데, 그 그림은
   전구에 가까워 「빠름」으로 안 읽힌다.

   말풍선(DB 메뉴 아이콘은 `lucide:message-square-plus` 다)을 쓰지 않는 이유:
   헤더 오른쪽에는 이미 AI 대화 서랍이 있고 그쪽이 진짜 말풍선 일을 한다.
   두 단추가 같은 계열 그림이면 **어느 쪽이 대화고 어느 쪽이 지시인지** 가
   그림으로는 안 갈린다. */
.jsini-icon-bolt {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M10.2 1 3.3 9.2h3.6L5.8 15l6.9-8.2H9.1z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M10.2 1 3.3 9.2h3.6L5.8 15l6.9-8.2H9.1z'/%3E%3C/svg%3E");
}

/* ── 좁은 화면 ───────────────────────────────────────────

   휴대폰에서는 사이드바가 본문을 **덮는다.** 밀어내면 240px 짜리 판 때문에
   본문이 화면 밖으로 나간다. 메뉴를 누르면 레이아웃이 알아서 접는다.
   ---------------------------------------------------------- */

@media (max-width: 767px) {
  .jsini-shell {
    grid-template-columns: 1fr;
  }

  /* ── 좁은 화면에서는 스플리터를 **상자만 없앤다** ────────

     사이드바가 본문을 덮어야 하는 폭이다. 판을 나눠 갖는 부품으로는 덮을 수
     없으므로 `display: contents` 로 판·판안쪽의 상자를 지운다. 그러면 그
     안의 사이드바와 본문이 **다시 격자 칸이 되어** 아래 규칙들이 그대로
     걸린다(사이드바는 `position: fixed` 로 덮고, 본문은 첫 칸을 쓴다).

     부품을 지우지 않고 상자만 없애는 이유는 **마크업을 갈아 끼우지 않기
     위해서**다. 화면 크기로 갈아 끼우면 경계를 넘을 때마다 사이드바 트리와
     열려 있던 화면이 통째로 다시 만들어진다.

     DevExpress 가 판에 인라인으로 넣는 폭·좌표는 상자가 없어지면 아무 일도
     하지 않는다. 구분선은 감춘다 — 끌 자리가 없다. */
  .jsini-shell__split,
  .jsini-shell__split > .dxbl-splitter-pane,
  .jsini-shell__split > .dxbl-splitter-pane > .dxbl-splitter-pane-content {
    display: contents !important;
  }

  .jsini-shell__split > .dxbl-splitter-separator {
    display: none !important;
  }

  /* 데모의 330px 를 그대로 두면 360px 짜리 화면을 거의 다 덮어서 본문이
     보이지 않는다. 화면 폭의 85% 로 묶는다. */
  .jsini-shell__sidebar,
  .jsini-sidebar {
    width: min(330px, 85vw);
  }

  .jsini-shell__sidebar {
    position: fixed;
    top: var(--jsini-header-height);
    bottom: calc(var(--jsini-bottomnav-h) + env(safe-area-inset-bottom));
    left: 0;
    z-index: 1030;
    box-shadow: 0 0 24px rgb(0 0 0 / 25%);
  }

  /* **펴진 표시가 붙었을 때만 그린다.** 접힘(`--collapsed`)을 보고 감추던
     자리인데, 그러면 **프리렌더가 나갈 때 사이드바가 펴진 채**라(화면 크기를
     모르니 휴대폰인 줄도 모른다) 첫 그림에서 판이 본문을 덮었다가 회로가
     붙은 뒤에 걷혔다.

     표시를 붙이는 쪽은 `MainLayout.ShellCss` 다 — 휴대폰이면서 펴져 있을
     때만 붙는다. */
  .jsini-shell__sidebar { display: none; }

  .jsini-shell--nav-open .jsini-shell__sidebar { display: block; }

  /* 덮개와 떠다니는 단추는 이 폭에서만 켠다(생김새는 위에 있다). */
  .jsini-shell__scrim { display: block; }

  .jsini-shell__fab { display: inline-flex; }

  /* 환경설정에서 **떠다니는 단추를 감춘 사람**(`jsini-fab-hidden`).
     표시를 붙이는 쪽은 `MainLayout.ShellCss` 다.

     **감춘 김에 ☰ 를 되돌리지 않는다.** 한동안 그렇게 했는데(여는 길이
     하나도 안 남는 줄 알았다) 휴대폰에서는 **헤더의 로고가 이미 그 일을
     한다** — `MainLayout.OnBrandClick` 이 `_isPhone` 일 때 사이드바를 여닫고,
     기본 이동은 막는다. 감췄더니 ☰ 가 되살아나는 것은 「귀퉁이를 가리는 것이
     싫어서 감췄는데 이번에는 띠 왼쪽을 먹는다」로 읽힌다.

     그래서 감추면 **정말로 아무것도 안 보인다.** 여는 길은 로고, 닫는 길은
     로고·덮개·뒤로 가기·헤더 누르기다. */
  .jsini-shell--fab-hidden .jsini-shell__fab--menu { display: none; }

  /* 요청 등록 단추를 감추는 규칙은 **이 블록 안에 없다.** 그 단추는 넓은
     화면에도 띄울 수 있어서(`--hdfab-desktop`) 감추기도 폭을 가리지 않아야
     한다 — 위쪽의 `--help` 머리말 자리에 함께 적어 두었다. */

  /* 헤더의 ☰ 는 휴대폰에서 뺀다. **떠다니는 단추와 하는 일이 똑같아서**
     (`_sidebarOpen` 하나를 여닫는다) 같은 화면에 둘을 놓을 까닭이 없고,
     360px 짜리 띠에서 그 자리는 도구 단추가 더 잘 쓴다.

     여는 자리를 아래쪽 하나로 모으는 편이 손에도 낫다 — 한 손으로 쥔
     휴대폰에서 화면 맨 위 왼쪽 모서리는 쥔 손을 고쳐 잡아야 닿는다.

     **닫는 길은 그대로 넷이다.** 떠다니는 단추(덮개보다 위에 뜬다) ·
     덮개를 누르기 · 뒤로 가기 · 헤더를 누르기. ☰ 가 없어져도 막다른 곳이
     생기지 않는다. */
  .jsini-header__toggle { display: none; }

  .jsini-shell__main { grid-column: 1; }

  /* 본문 여백은 **거의 주지 않는다.** 360px 짜리 화면에서 좌우 24px 씩은
     쓸 수 있는 폭의 13% 라, 표 한 칸이 줄바꿈되고 조건 입력칸이 좁아진다.
     읽는 데 필요한 틈은 안쪽 판(`commsch`·`commcont`)이 이미 준다.

     아래만 예외다 — 하단 띠(60px + 안전영역)가 고정으로 떠 있어서 그만큼
     비워 두지 않으면 **마지막 줄에 영영 닿지 못한다.** 여백이 아니라 자리
     비킴이라 여기서 줄이지 않는다. 떠다니는 단추(FAB)는 모서리에 겹쳐
     지나가게 둔다. */
  .jsini-shell__content {
    padding: var(--jsini-shell-pad-top) var(--jsini-shell-pad-inline)
             calc(var(--jsini-bottomnav-h) + env(safe-area-inset-bottom) + var(--jsini-shell-pad-bottom));
  }

  /* 모바일 화면에서는 탭 줄을 숨긴다. 본문 공간을 확보하고,
     열린 화면 이름은 헤더의 로고 옆에 표시한다.

     **탭 메뉴까지 함께 사라지지는 않는다** — 그 이름을 누르면 탭 줄이
     들고 있는 창이 손가락 닿은 자리에 열린다(`TabMenuRequest` 머리말).
     창을 그리는 마크업은 이 상자 **밖**이라(`TabBar.razor` 의 형제 노드)
     여기서 감춰도 멀쩡하다. */
  .jsini-tabs { display: none; }

  /* 헤더의 브레드크럼은 모바일에서 앞쪽 경로와 구분자를 숨기고,
     열린 화면의 이름(마지막 항목)만 로고 옆에 남긴다. */
  .jsini-crumbs { min-width: 0; }

  .jsini-crumbs__item:not(.jsini-crumbs__item--current),
  .jsini-crumbs__item:not(:last-child),
  .jsini-crumbs__sep { display: none; }

  /* 이 칸은 휴대폰에서 **탭 메뉴를 여는 단추**다(`Breadcrumb.razor` 머리말).
     그래서 글자 묶음이 아니라 줄임표와 화살표를 나란히 두는 상자로 만든다 —
     `inline-block` 에 `text-overflow` 만 걸면 화살표까지 줄임표 안쪽으로
     들어가 잘린다. */
  .jsini-crumbs__item--current {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;

    /* 빼 두는 268px 는 **띠에서 이 이름 말고 다른 것이 쓰는 폭**이다 —
       헤더 좌우 여백 · 로고 · 오른쪽 단추 일곱.

       **230px 이던 값을 단추 하나(38px)만큼 키웠다** — 테마 단추가 휴대폰
       띠로 돌아오면서 칸이 여섯에서 일곱이 되었다(아래 `.jsini-theme-anchor`).
       38px 는 390px 폭에서 실제로 잰 아이콘 단추 하나의 폭이다.

       **이름이 먼저 접히게 하려고 여기서 빼 준다** — 이름은 줄임표로
       접히지만 단추는 접을 데가 없다. 그래도 모자라는 화면(320px 짜리 ·
       글자를 크게 고른 사람)이 있어서 뒤를 아래 `min-width: 0` 이 받는다. */
    max-width: min(16rem, calc(100vw - 268px));
    margin-right: 0;

    /* 손가락이 닿는 자리를 넓힌다. 헤더 높이 안에서 위아래로만 준다 —
       좌우로 주면 로고에 붙거나 도구 단추를 민다. */
    padding: 0.35rem 0;
  }

  /* 줄임표는 글자 쪽에만 건다. 화살표는 그 밖이라 늘 보인다.

     `min-width: 0` 이 있어야 실제로 줄어든다 — flex 항목의 기본 최소 폭이
     내용 크기라, 이 줄이 없으면 긴 이름이 줄임표로 접히는 대신 **화살표를
     상자 밖으로 밀어낸다**(`.jsini-crumbs` 가 같은 이유로 들고 있는 줄이다). */
  .jsini-crumbs__item--current > .jsini-crumbs__label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* **눌린다는 유일한 표시.** 넓은 화면에서는 탭 줄의 「⋯」 이 그 노릇을
     하는데 여기서는 그 줄이 통째로 감춰져, 표시가 없으면 열린 화면 이름이
     그냥 글자로 보인다.

     생김새는 브레드크럼 구분자와 같은 **테두리로 그린 삼각형**이다(위쪽
     `.jsini-crumbs__sep` 과 같은 이유 — 글꼴에 없는 글자를 쓰지 않는다).
     아래를 가리키게 돌려 「여기서 뭔가 펼쳐진다」를 말한다. */
  .jsini-crumbs__caret {
    flex: 0 0 auto;
    width: 0;
    height: 0;
    margin-top: 2px;
    border: 4px solid transparent;
    border-top-color: currentcolor;
    opacity: 0.55;
  }

  /* 좁은 화면에서 브랜드 글자는 뺀다. 도구 단추가 밀려 나간다.
     **선까지 함께 뺀다** — 글자만 감추면 로고 옆에 세로 선 하나가 남아
     무엇이 빠진 것처럼 보인다(레이아웃이 역할을 못 읽었을 때 선까지 함께
     감추는 것과 같은 이유다). */
  .jsini-header__brand-text,
  .jsini-header__brand .jsini-header__sep { display: none; }

  /* 헤더 오른쪽에 남는 것은 **일곱이다** — 빠른 지시 · AI 작업 요청 ·
     쪽지 · 알림 · AI 대화 · 테마 · 프로필.

     360px 짜리 띠에는 로고가 이미 앉아 있어서, 단추 열이 그 뒤에 늘어서면
     하나하나가 손가락보다 좁아진다. 눌리지 않는 단추 열보다 눌리는 단추
     일곱이 낫다.

     빠지는 것은 즐겨찾기 · 새로 고침 · 전체화면 셋뿐이다(`--wide` 표를 붙여
     둔 셋, `HeaderTools.razor` 머리말). 화면을 오래 붙들고 다듬는 일이라
     책상에서 하고, 뒤의 둘은 휴대폰 브라우저가 이미 제 것을 가지고 있다. */
  .jsini-header__tool--wide { display: none; }

  /* [테마 단추는 **휴대폰에서도 선다** (2026-10-11)]

     2026-09-24 에 노란 번개(AI 작업 요청)를 휴대폰에 다시 세우면서 그 자리를
     이쪽이 내주었다 — 테마는 프로필 메뉴의 「테마 변경」으로 들어갔다.
     그런데 **밝기·색은 하루에도 몇 번 바꾸는 것**이라, 그때마다 얼굴을 눌러
     판을 펴고 항목을 찾는 두 걸음이 되었다. 되돌린다.

     자리는 띠에 이미 있다 — 390px 에서 재어 보니 `.jsini-header__spacer`
     가 **54px 를 비워 두고 있었다**(단추 하나가 38px). 모자라는 폭에서는
     위 `.jsini-crumbs__item--current` 의 몫에서 먼저 줄어든다(거기 빼 둔
     값을 단추 하나만큼 키워 두었다).

     감추던 줄(`.jsini-shell .jsini-theme-anchor { display: none }`)을 지웠다.
     한 칸 더 짚어 두었던 것은 훨씬 뒤에 오는 `display: inline-flex` 를
     이기려던 것이라, 줄 자체가 없어지면 그 셈도 함께 없어진다. */

  /* 좌우 여백을 0.75rem 에서 0.4rem 으로 줄여 **11px 를 띠에 돌려준다.**
     책상에서는 그대로다 — 좁은 띠에서만 아깝다.

     **`min-width: 0` 이 뒤를 받는다.** 헤더는 격자 칸이라 기본 최소 폭이
     제 내용 크기(min-content)다. 그래서 내용이 넘치면 **줄어드는 대신 칸이
     넓어지고**, 셸이 `overflow: hidden` 이라 넘친 만큼이 화면 밖에서 잘린다 —
     잘리는 쪽은 언제나 맨 오른쪽, 즉 **프로필 얼굴**이다. 0 으로 풀면 띠가
     화면 폭에 맞춰지고 줄어들 수 있는 것(열린 화면 이름)이 먼저 줄어든다.

     **칸을 하나 더 짚는 이유**는 뒤에 오는 「포털 셸 리프레시」 층이
     `.jsini-header` 에 0.75rem 을 다시 적어 두기 때문이다. 같은 무게면
     뒤가 이기므로, 여기서 한 칸(`.jsini-shell >`) 더 무겁게 적는다. */
  .jsini-shell > .jsini-header {
    padding: 0 0.4rem;
    min-width: 0;
  }
}

/* ── 공통 조회 영역 (CommSch) ─────────────────────────────────

   화면 구성을 두 층으로 나눈다 — 위가 조회 영역, 아래가 자료 영역.

     ┌────────────────────────────────────────────────┐
     │ 검색어 [       ]  상태 [   ]  [등록][초기화][조회] │  ← 여기
     ├────────────────────────────────────────────────┤
     │                     표                          │
     └────────────────────────────────────────────────┘

   조건을 표 안쪽 도구줄에 두면 표가 넓어질수록 조건이 밀려난다. 판을 따로
   세우면 표가 어떻게 생겼든 조건은 늘 같은 자리, 같은 모양이다.
   화면이 백 개가 넘으므로 **같은 자리에 있는 것이 예쁜 것보다 중요하다.**

   바탕을 한 톤 짙게 하고 테두리를 두른다. 조건과 자료가 한 색이면 어디까지가
   조건인지 눈으로 안 갈린다 — 브레드크럼·탭 줄과 같은 `--jsini-surface` 다.
   ---------------------------------------------------------- */

.commsch {
  display: flex;
  flex-wrap: wrap;

  /*
    **위를 맞춘다.** 가운데로 맞춰 두면 조건이 많아 왼쪽이 두세 줄이 될 때
    단추가 그 한가운데로 내려간다 — 조건이 늘 때마다 단추가 움직이는 셈이라
    화면을 옮겨 다니며 누르는 사람에게는 자리가 없는 것과 같다.
  */
  align-items: flex-start;
  gap: var(--jsini-sp-md) var(--jsini-sp-lg);
  padding: var(--jsini-sp-lg);
  margin-bottom: var(--jsini-sp-lg);

  /*
    **회색 판이 아니라 흰 카드다.**

    한동안 바탕을 한 톤 짙게(`--jsini-surface`) 깔았다. 조건과 자료를 가르려던
    것인데, 데모(demos.devexpress.com/blazor)는 같은 일을 **흰 바탕 + 옅은
    테두리**로 한다 — 판이 여럿 쌓이는 화면에서 회색이 겹치면 그 회색이
    화면을 눌러 앉힌다. 가르는 일은 테두리 한 줄로 충분하다.

    치수도 데모에서 잰 값이다(테두리 1px · 모서리 4px).
  */
  background: var(--jsini-bg);
  border: 1px solid var(--jsini-border);
  border-radius: var(--jsini-radius-sm);
}

/* 조건은 왼쪽에 흘러 담긴다. 개수가 화면마다 둘에서 예닐곱까지라
   줄바꿈으로 받는다 — 칸 수를 고정하면 조건이 하나 늘 때마다 판이 깨진다. */
.commsch__fields {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.7rem 1rem;

  /*
    **`0` 에서 자란다(`auto` 가 아니다).** `auto` 로 두면 이 묶음이 제
    내용만큼의 폭을 먼저 요구하고, 조건이 많으면 그 요구가 판보다 커져
    **단추 묶음이 통째로 다음 줄로 밀려난다.** 0 에서 자라면 남는 폭만
    가져가므로 조건은 제자리에서 여러 줄로 접히고 단추는 오른쪽 위에 남는다.
  */
  flex: 1 1 0;
  min-width: 0;
}

/* 라벨은 칸 왼쪽에 붙는다. 조건이 대개 둘에서 넷이라 한 줄에 다 들어가고,
   판이 한 줄 높이로 끝난다 — 본문을 꽉 채워 쓰는 화면에서 그 한 줄이
   표의 한 줄이다. */
.commsch__item {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
}

/* 화면이 입력 부품에 고정 폭을 준 경우에도 조건 판이 가로로 넘치지 않게
   한다. 데스크톱에서는 선언한 폭을 존중하고, 좁은 화면에서는 판의 남은
   폭을 쓰게 해 조회 단추와 조건이 서로 밀어내지 않도록 한다. */
.commsch__item > :last-child {
  min-width: 0;
  max-width: 100%;
}

/* ── 조건이 많은 판은 **격자**로 선다 (2026-09-28) ───────────

   흘려 담기(`flex-wrap`)는 줄마다 **앞줄과 무관하게** 왼쪽부터 채운다.
   조건이 둘·셋일 때는 한 줄로 끝나서 티가 안 나는데, 여섯을 넘어 두세 줄로
   접히기 시작하면 폭이 제각각인 칸들이 **줄마다 다른 자리에서 시작한다** —
   라벨도 입력칸도 세로로 하나도 안 맞는 모자이크가 된다. 라벨 길이가
   「회사」 두 자에서 「등록일(시작)」 여덟 자까지라 그 어긋남이 더 커진다.

   격자로 세우면 **칸이 기둥을 이룬다.** 줄이 몇이 되든 같은 자리에서
   시작하고, 창을 좁히면 기둥 수만 줄어든다.

   [왜 판마다 켜지 않고 칸 수로 고르나]

   화면이 백 개가 넘고, 어느 판이 접히는지는 **창 폭에 딸린 일**이라 화면이
   미리 알 수 없다. 화면마다 켜게 두면 켠 화면과 안 켠 화면이 갈리고, 그것이
   이 판을 공통으로 세운 이유를 지운다(`CommSch` 의 접기와 같은 사정이다).

   **조건이 적은 판은 건드리지 않는다.** 한 줄로 끝나는 판을 격자로 만들면
   조건 사이가 오히려 휑하게 벌어진다. 여섯째 칸이 있는 판만 고른다 —
   1280px 에서 조건 여섯이 판을 넘기기 시작한다.

   `Stacked` 판은 뺀다. 그쪽은 라벨이 칸 **위**에 얹혀서 아래 라벨 규칙이
   거꾸로 작용한다.

   좁은 화면(≤767px)도 뺀다. 거기서는 이미 칸 하나에 한 줄씩 쌓이고
   (아래 `@media (max-width: 767px)`) 기둥을 세울 폭 자체가 없다.
   ---------------------------------------------------------- */
@media (min-width: 768px) {
  .commsch:not(.commsch--stacked) .commsch__fields:has(> .commsch__item:nth-child(6)) {
    display: grid;
    /* 17rem(272px) = 라벨 칸 4.25rem + 틈 + **가장 넓은 입력칸**(날짜 195px).
       기둥 하나가 그보다 좁아지면 날짜가 잘리므로, 그 폭이 안 나오면
       기둥 수를 줄인다 — 좁은 창에서 기둥이 하나가 되는 것은 고장이 아니다. */
    grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
    align-items: center;
    gap: 0.6rem 1rem;
  }

  /* 기둥 **안에서도** 왼쪽 끝을 맞춘다. 라벨 폭이 제각각이면 기둥을 세워도
     입력칸이 기둥마다 다른 자리에서 시작한다. 짧은 라벨에 최소 폭을 주고
     오른쪽으로 붙여 입력칸에 잇닿게 한다 — 좁은 화면에서 같은 이유로 이미
     쓰고 있는 손이다(`min-width: 3.25rem`). 그보다 긴 라벨은 제 폭을 쓴다. */
  .commsch:not(.commsch--stacked) .commsch__fields:has(> .commsch__item:nth-child(6)) .commsch__label {
    flex: 0 0 auto;
    min-width: 4.25rem;
    text-align: right;
  }

  /* 입력칸은 기둥의 남은 폭을 채우되 **14rem 에서 멈춘다.**

     채우는 까닭 — 화면이 적어 둔 고정 폭(180px · 90px …)은 **한 줄에
     늘어놓을 때의 값**이라, 기둥이 그보다 넓으면 오른쪽 끝이 들쭉날쭉해져
     기둥을 세운 뜻이 절반은 사라진다.

     멈추는 까닭 — 기둥이 하나뿐일 때(사이드바를 편 1024px 언저리) 끝까지
     채우면 날짜 칸 하나가 424px 이 된다. 멈춰 두면 그때도 오른쪽 끝은
     가지런하고 칸만 사람 손에 맞는 크기로 남는다.

     14rem(224px)은 **가장 넓은 칸**이 기준이다 — 날짜 칸이 195px 이라
     그보다 좁게 잡으면 「2026-09…」로 잘린다(그 값의 내력은 화면 주석에 있다). */
  .commsch:not(.commsch--stacked) .commsch__fields:has(> .commsch__item:nth-child(6)) .commsch__item > :last-child {
    flex: 1 1 auto;
    width: auto !important;
    max-width: 14rem;
  }
}

/* `CommSch Stacked="true"` 일 때만 세로로 돌린다. 판이 붙인 클래스를 받으므로
   **한 판의 칸 전부**가 함께 돌아간다. 칸마다 다르면 그것이 제일 지저분하다. */
/* 세로 배치에서는 **칸끼리** 아래를 맞춘다 — 라벨이 위에 얹혀 칸 높이가
   제각각이라, 위를 맞추면 입력칸 바닥이 들쭉날쭉해진다.

   **판(`.commsch`)까지 덮지 않는다.** 덮으면 단추가 다시 아래로 내려간다 —
   위에서 `flex-start` 로 올려 둔 것이 여기서 풀린다(실제로 밟을 뻔했다). */
.commsch--stacked .commsch__fields {
  align-items: flex-end;
}

.commsch--stacked .commsch__item {
  flex-direction: column;
  align-items: flex-start;
  gap: 0.2rem;
}

.commsch__label {
  font-size: var(--jsini-fs-xs);
  font-weight: 600;
  color: var(--jsini-text-muted);
  white-space: nowrap;
}

/* 단추는 **오른쪽 위**에 붙박는다. 조건이 몇 줄이 되든 자리가 안 움직인다.

   `flex: 0 0 auto` 로 제 폭을 지킨다 — 줄어들 수 있게 두면 조건이 많을 때
   단추 글자가 접힌다(「조회」가 두 줄이 된다). 이 폭이 곧 왼쪽에 내주지 않는
   자리이고, 조건은 그것을 뺀 나머지 안에서 접힌다. */
.commsch__actions {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex: 0 0 auto;
  margin-left: auto;

  /* 판이 `flex-start` 라 이미 위에 붙지만, 판의 정렬을 바꾸는 화면이
     생겨도 단추만은 위에 남게 여기서 한 번 더 박아 둔다. */
  align-self: flex-start;
}

/* 조회·초기화 묶음. 화면 단추와 **따로 묶여 있는 이유는 하나뿐**이다 —
   접힌 휴대폰 화면에서 이 둘만 사라져야 하기 때문이다(아래 접기 묶음).
   간격이 바깥과 같아서 한 줄로 이어져 보인다. */
.commsch__run {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

/* ── 조회 판의 접기 줄 (`CommSch`) ──────────────────────────

   **데스크톱에는 없다.** 여기서 숨겨 두고 좁은 화면에서만 켠다 — 반대로
   두면(좁을 때 숨기고 넓을 때 보이기) 화면 백 개의 판이 넓은 창에서
   한 줄씩 더 먹는다.

   접힘은 클래스(`.commsch--folded`)로 표시하지만 **그 클래스가 무엇을
   숨기는지는 좁은 화면 안에서만 정의한다.** 그래서 창을 넓히면 접은 채로
   두었던 판이 저절로 펴진 모습이 된다 — 회로를 건드리지 않는다.
   ---------------------------------------------------------- */

.commsch__toggle {
  display: none;
}

@media (max-width: 767px) {
  .commsch__toggle {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    padding: 0.15rem 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
  }

  .commsch__toggle-label {
    flex: 0 0 auto;
    font-size: var(--jsini-fs-xs);
    font-weight: 600;
    color: var(--jsini-text-muted);
  }

  /* 고른 조건. **한 줄로 자른다** — 조건이 많은 화면에서 이 줄이 두세 줄이
     되면 접어서 아낀 자리를 도로 내주게 된다. */
  .commsch__toggle-sum {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    font-size: var(--jsini-fs-sm);
    font-weight: 600;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .commsch__toggle-caret {
    flex: 0 0 auto;
    background-color: var(--jsini-text-muted);
    transition: transform 0.15s ease;
  }

  /* 펴져 있으면 꺾쇠가 위를 본다. 위를 보는 그림을 따로 두지 않는 이유는
     둘이 갈리면 각도가 어긋나기 때문이다(아이콘 묶음과 같은 규칙). */
  .commsch:not(.commsch--folded) .commsch__toggle-caret {
    transform: rotate(180deg);
  }

  /* 접히면 조건이 사라지고 머리줄과 화면 단추만 남는다. 판의 여백도
     같이 줄인다 — 한 줄짜리 판에 사방 여백이 그대로면 두 줄처럼 보인다.

     **클래스를 둘 겹쳐 적는다**(`.commsch.commsch--folded`). 아래쪽 좁은
     화면 묶음에 `.commsch { padding: 0.5rem }` 이 있고 그것이 이 줄보다
     뒤에 있어서, 하나만 적으면 같은 값싸움에서 뒤엣것이 이긴다. */
  .commsch.commsch--folded {
    padding-block: 0.35rem;

    /* 접힌 줄에는 머리줄과 화면 단추가 나란히 선다. 판의 기본값
       (`flex-start`)대로 두면 단추가 더 높아서 **머리줄 글자만 위로
       붙는다** — 펴져 있을 때 위를 맞추는 까닭(조건이 여러 줄일 때 단추가
       안 움직이게)이 여기서는 없다. */
    align-items: center;
  }

  .commsch--folded .commsch__fields {
    display: none;
  }

  /* 접히면 머리줄이 **제 폭만** 차지한다. 펴져 있을 때처럼 100% 로 두면
     화면 단추가 통째로 다음 줄로 밀려 접은 값이 반으로 준다. */
  .commsch--folded .commsch__toggle {
    width: auto;
    flex: 1 1 auto;
    min-width: 0;
  }

  /* 화면 단추(등록 따위)는 접혀도 남는다 — 「조회 조건」 뒤에 숨으면
     휴대폰에서 등록할 길이 없다. 아래 좁은 화면 묶음이 이 묶음을 통째로
     다음 줄로 내리므로(`width: 100%`) 여기서 되돌린다 —
     **자릿수가 하나 높아 순서와 무관하게 이긴다.** */
  .commsch--folded .commsch__actions {
    width: auto;
    margin-left: auto;

    /* 판이 접힌 줄에서만 가운데로 맞추므로(위) 그 묶음에 박아 둔
       `flex-start` 도 여기서 풀어 준다. */
    align-self: center;
  }

  /* 조회·초기화만 사라진다. 조건이 안 보이는 동안 누를 일이 없고,
     조회는 접는 순간 이미 한 번 나갔다(`CommSch.SearchAsync`). */
  .commsch--folded .commsch__run {
    display: none;
  }
}

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

/* 좁은 화면에서는 단추 줄이 통째로 아래로 내려간다. 오른쪽에 매달아 두면
   조건 한 칸과 단추 셋이 한 줄에 끼어 둘 다 못 읽는다. */
@media (max-width: 767px) {
  .commsch__actions {
    margin-left: 0;
    width: 100%;
    justify-content: flex-end;
  }

  .commsch__fields {
    width: 100%;
  }

  .commsch__item {
    flex: 1 1 100%;
    width: 100%;
  }

  .commsch__item > :last-child {
    flex: 1 1 auto;
    width: min(100%, 18rem) !important;
  }

  /* 라벨이 칸 앞에 서는데(tabler.css) 라벨 길이가 제각각이라, 칸 하나에
     한 줄씩 쌓이는 좁은 화면에서는 **칸의 왼쪽 끝이 들쭉날쭉해진다.**
     짧은 라벨에 최소 폭을 주어 기둥을 맞춘다 — 그보다 긴 라벨(「등록일(시작)」)은
     제 폭을 쓴다. */
  .commsch__item > .commsch__label {
    flex: 0 0 auto;
    min-width: 3.25rem;
  }

  .commgrd:has(.dxbl-grid-bottom-panel) .dxbl-grid-bottom-panel {
    flex-wrap: wrap;
    min-height: 3.5rem;
    padding-left: 0.65rem;
  }

  .commgrd:has(.dxbl-grid-bottom-panel) .commgrd__footer {
    position: static;
    order: 2;
    width: 100%;
    height: auto;
    min-height: 2.5rem;
    justify-content: flex-start;
  }

  .jsini-dashboard-section__head {
    align-items: flex-start;
  }

  .jsini-dashboard-section__spacer {
    display: none;
  }
}

/* ── 자료 영역 (CommCont) ────────────────────────────────

   조회 영역 아래에서 내용을 담는 판. 화면은 이렇게 쌓아 만든다 —

     PageHeading  →  CommSch  →  CommCont  →  (CommCont)

   **테두리를 두르지 않는다.** 안에 들어오는 것이 대개 `CommGrd` 인데 그 표에
   이미 테두리가 있어, 한 겹 더 두르면 선이 두 줄로 겹친다. 이 판이 주는 것은
   제목 줄과 **간격**이고, 그 둘이 통일의 대부분이다.
   ---------------------------------------------------------- */

.commcont {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* ── 본문 높이를 꽉 채운다 ────────────────────────────────

   업무 화면은 **쪽 전체가 구르면 안 된다.** 조건은 늘 위에, 표의 아래 띠는
   늘 아래에 있어야 하고, 구르는 것은 자료뿐이어야 한다. 쪽이 통째로 구르면
   자료를 볼 때마다 조건이 화면 밖으로 밀려나고, 아래 띠를 누르려면 끝까지
   내려가야 한다.

   `:has()` 로 **`CommCont` 를 쓰는 화면에만** 건다. 아직 안 옮긴 화면
   백몇 개는 지금처럼 쪽이 구른다 — 한꺼번에 바꾸면 그 화면들이 잘린다.
   ---------------------------------------------------------- */

.jsini-shell__content:has(> .commcont) {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* 조회 영역·제목 줄은 제 높이만큼, 자료 영역이 남은 높이를 다 먹는다.
   `min-height: 0` 이 없으면 flex 자식이 내용만큼 부풀어 넘친다 — 이것 하나로
   높이 계산이 통째로 어긋나는 자리다. */
.jsini-shell__content:has(> .commcont) > .commcont {
  flex: 1;
  min-height: 0;
}

.jsini-shell__content:has(> .commcont) > .commcont > .commcont__body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* 자료 영역이 둘 이상일 때 사이를 벌린다. 마지막 것 아래는 벌리지 않는다 —
   본문 여백이 이미 있어서 두 번 벌어진다. */
.commcont + .commcont {
  margin-top: 1.4rem;
}

.commcont__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
}

/* 화면 제목(PageHeading)보다 한 단계 작다. 같은 크기면 어느 쪽이 위인지
   눈으로 안 갈린다. */
.commcont__title {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 600;
}

.commcont__hint {
  color: var(--jsini-text-muted);
}

.commcont__spacer { flex: 1; }

.commcont__body { min-width: 0; }

/* ── 공통 그리드 (CommGrd) ────────────────────────────────

   **이름을 부품 이름에 맞춘다.** 화면에서 개발자 도구를 열었을 때
   `commgrd__footer` 라고 적혀 있으면 어느 파일을 고쳐야 하는지 바로 보인다.
   `jsini-` 로 시작하는 것들은 여러 화면이 함께 쓰는 것이고, 여기 있는 것은
   `CommGrd.razor` 가 그린 것뿐이다 — 그 구분이 이름에 드러나야 한다.

   그래서 겉모습이 같아도 `jsini-toolbar` 를 빌려 쓰지 않는다. 빌려 쓰면
   공용 규칙을 고칠 때 이 표까지 함께 움직이고, 그 사실이 어디에도 안 적힌다.
   ---------------------------------------------------------- */

.commgrd {
  display: flex;
  flex-direction: column;
  min-width: 0;

  /* 아래 띠의 높이. 페이저 줄과 같은 값이어야 한 줄로 보인다.
     둘 다 이 변수로 정하므로 어긋날 일이 없다. */
  --commgrd-bar-height: 3rem;

  /*
    표의 최대 높이.

    **없으면 아래 띠가 화면 밖으로 나간다.** 표는 자기 내용만큼 늘어나고
    페이저 줄은 그 맨 아래에 붙으므로, 스무 줄짜리 표면 이미 한 화면을
    넘어선다 — 등록·조회·엑셀 아이콘을 보려고 매번 굴려 내려가야 한다.
    실제로 「아이콘이 전혀 안 보인다」로 나타났다(그때는 한 줄이 152px 이라
    표가 3157px 였다).

    묶어 두면 DevExpress 가 표 안쪽에서 굴린다. 칸 머리와 페이저 줄은
    제자리에 남고 자료만 움직인다 — 데모의 표가 그렇게 생겼다.

    화면마다 위쪽에 놓이는 것이 다르므로(제목 줄 · 조회 영역 · 안내 줄)
    화면에서 이 변수만 덮으면 된다.
  */
  --commgrd-max-height: 58vh;
}

/* ── 표의 머리줄 (제목 · 건수) ─────────────────────────────

   **표에 붙은 띠다. 표 위에 뜬 글자가 아니다.**

   처음에는 글자만 놓고 아래로 0.5rem 을 띄웠는데, 표만 담은 판은
   `padding: 0` 이라(tabler.css) 그 글자가 **카드 왼쪽 테두리에 바짝
   붙고** 표와의 사이에는 빈 틈만 남아, 판 안에 글자 한 줄이 떠 있는
   그림이 됐다. 그래서 안쪽 여백을 스스로 갖는다.

   **바탕도 선도 두지 않는다.** 한동안 아래에 선을 그어 두었는데, 바로
   아래 칸 머리가 이미 제 바탕으로 경계를 내고 있어 선이 한 겹 더 붙는
   꼴이었다. 띠는 자리만 잡고 카드 바탕 위에 그대로 앉는다.

   `--commgrd-bar-height` 를 쓰지 않는다. 그것은 아래 띠와 페이저 줄을
   한 줄로 맞추려고 둔 값이고(3rem), 머리줄은 아이콘 단추가 없어 그만큼
   높을 까닭이 없다.

   **단추를 두지 않는다** — 표의 동작은 오른쪽 클릭 창이고
   (`CommGrd.BuildMenu`), 여기에 하나라도 두면 같은 표의 동작이 두 자리로
   갈린다(아래 띠에서 걷어낸 것과 같은 사정).

   제목은 `h3` 다. 화면 제목(`h1`)도 `CommCont` 의 구역 제목(`h2`)도 아닌
   **그 아래**라, 표가 둘 놓인 화면에서 차례가 뒤집히지 않는다. */
.commgrd__head {
  display: flex;
  flex-wrap: wrap;

  /* **밑줄을 맞춘다.** 제목과 건수는 크기가 다른 글자라 가운데로 맞추면
     작은 쪽이 떠 보인다 — 글줄의 밑선을 맞춰야 한 줄로 읽힌다. */
  align-items: baseline;
  gap: 0.1rem 0.6rem;
  min-width: 0;
  min-height: 2.5rem;
  padding: 0.35rem 0.75rem;
  background: transparent;
  border: 0;
}

/* 제목이 선 머리줄은 **왼쪽 여백을 갖지 않는다** — 제목 앞의 막대가
   표의 왼쪽 끝 선과 같은 자리에 서야 하기 때문이다(아래). */
.commgrd__head:has(.commgrd__title) {
  padding-left: 0;
}

/* 제목. 앞에 테마색 막대를 세운다 — 카드가 여럿 놓인 화면에서 **무엇의
   표인가**가 한눈에 잡히고, 막대가 표의 왼쪽 끝 선 위에 서므로 그 아래
   자료와 한 기둥으로 읽힌다.

   막대는 글자 높이만큼만 선다(`em` 으로 잡는다). 띠 높이에 맞추면 글자보다
   길어져 제목이 아니라 띠를 가리키는 것처럼 보인다. */
.commgrd__title {
  position: relative;
  margin: 0;
  padding-left: 0.7rem;
  font-size: var(--jsini-fs-lg);
  line-height: 1.35;
}

.commgrd__title::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.14em;
  bottom: 0.14em;
  width: 3px;
  border-radius: 1px;
  background: var(--jsini-accent);
}

/* 건수. 「총」·「건」은 단위라 옅게 두고 **수만 진하게** 한다 — 눈이 수에
   먼저 닿는다. 자릿수가 늘어도 옆 글자가 안 밀리게 등폭 숫자를 쓴다. */
.commgrd__total {
  color: var(--jsini-text-muted);
  font-size: var(--jsini-fs-sm);
  font-variant-numeric: tabular-nums;
}

.commgrd__total b {
  font-weight: 600;
  color: var(--jsini-text);
}

/* 낱자(「건」·「대」·「개」)는 수가 아니다. 수와 같은 색으로 두면 「525건」이
   한 덩어리로 보여 자릿수가 한눈에 안 들어온다. */
.commgrd__unit {
  color: var(--jsini-text-muted);
  font-weight: 400;
}

/* 표 위 도구줄. 조회 영역(CommSch)을 따로 둔 화면에서는 그려지지 않는다. */
.commgrd__toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin-bottom: 0.75rem;
}

/* 왼쪽 묶음과 오른쪽 묶음을 갈라 놓는 빈 칸. */
.commgrd__spacer { flex: 1; }

/* ── 표 안 관리 칸의 단추 묶음 ────────────────────────────

   **이 칸의 단추는 글자를 달지 않는다. 아이콘만 쓴다.**

   칸 하나에 단추가 둘에서 넷까지 들어가는데(수정·삭제에 화면마다 하나둘이
   더 붙는다) 글자를 달면 그만큼 칸이 넓어진다. 표는 이미 가로로 모자란
   자리이고, 「관리」를 오른쪽에 붙박아 둔 뒤로는 그 폭이 **자료 칸에서
   그대로 빠진다.**

   글자를 뗀 자리는 `Title` 이 받는다 — **모든 단추에 반드시 적는다.**
   아이콘만으로 뜻이 갈리는 짝은 연필·휴지통 정도뿐이고, 「발인」·「승인」
   같은 업무 동작은 마우스를 올려 봐야 알 수 있다.

   색으로 한 겹 더 가른다 — 되돌릴 수 없는 것은 `commgrd__icon--warn`,
   켜져 있는 것은 `commgrd__icon--on`.
   ------------------------------------------------------------ */
.commgrd__actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
}

/* 자료 칸의 기본 정렬. 어느 칸에 붙일지는 CommGrd 가 정한다 —
   칸이 TextAlignment 를 직접 정한 자리에는 이 클래스가 안 붙는다.

   DevExpress 의 `.dxbl-align-*` 은 클래스 하나(0,1,0)라 이 규칙이 이긴다.
   그래서 **붙는 자리를 좁히는 것**으로 뜻을 지킨다 — 여기서 더 넓게 잡으면
   화면이 오른쪽으로 맞춰 둔 금액 칸까지 가운데로 끌려온다. */
.commgrd .dxbl-grid-table > tbody > tr > td.commgrd__cell--center {
  text-align: center;
}

/* 순번. 자료가 아니라 자리 표시라 옅게, 가운데로. 자릿수가 늘어도 흔들리지
   않게 등폭 숫자를 쓴다 — 9 에서 10 으로 넘어갈 때 칸이 밀리지 않는다. */
.commgrd__no {
  display: block;
  text-align: center;
  color: var(--jsini-text-muted);
  font-variant-numeric: tabular-nums;
}

.commgrd__empty {
  padding: 2rem;
  text-align: center;
  color: var(--jsini-text-muted);
}

/* 빈 표는 오류가 아니라 현재 조건의 결과라는 것을 표면으로 드러낸다.
   고정 높이를 주지 않고 최소 높이만 두어 긴 화면의 스크롤 계산을 깨지
   않으면서도, 표가 접혀 보이는 문제를 막는다. */
.commgrd__empty::before {
  content: "⌕";
  display: block;
  margin-bottom: 0.35rem;
  font-size: 1.5rem;
  opacity: 0.55;
}

/* 카드·차트·표가 섞인 대시보드는 구역 사이의 간격과 제목 계층을 공통으로
   가져야 긴 화면에서 현재 위치를 잃지 않는다. */
.jsini-dashboard-section {
  min-width: 0;
  margin-block: 0 1.25rem;
}

.jsini-dashboard-section__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 0.75rem;
  min-height: 2.5rem;
  margin-bottom: 0.65rem;
}

.jsini-dashboard-section__title {
  margin: 0;
  font-size: var(--jsini-fs-lg);
  line-height: 1.35;
}

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

.jsini-dashboard-section__spacer {
  flex: 1 1 auto;
}

.jsini-dashboard-section__body {
  min-width: 0;
}

/* 표 아래 띠. 왼쪽 끝이 엑셀 자리다.

   **DxGrid 에는 하단 슬롯이 없다.** 있는 것은 상단 `ToolbarTemplate` 과
   칸별 `ColumnFooterTemplate` 뿐이라 우리가 그린다. 페이저가 DevExpress 의
   자리(표 안쪽 아래)에 그대로 있으므로 이 띠는 그 아래 한 줄로 앉는다 —
   겹쳐 올리면 DevExpress 마크업에 묶여서 버전이 오를 때 함께 깨진다. */
.commgrd__footer {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-height: var(--commgrd-bar-height);
  padding: 0.25rem 0.15rem 0;

  /* 글자 없이 아이콘만 있는 자리라 조금 키운다. 글자가 옆에 있으면 그 글자가
     무엇인지 알려 주지만, 여기서는 아이콘이 유일한 단서다.

     그 1.35rem 은 헤더와 같은 「조금작게」 기준값이고, 헤더와 같은 배율을
     쓴다(`--jsini-chrome-scale`) — 등록·조회·필터·엑셀 아이콘이 글자 크기를
     따라 줄어든다. 한동안 고정이라 글자를 「가장작게」로 두면 표 글자만
     작아지고 이 띠의 아이콘은 그대로 커서 아래쪽이 유독 무거웠다. */
  --jsini-icon-size: calc(1.35rem * var(--jsini-chrome-scale));
}

/* 켜진 토글과, 걸린 조건을 지우는 단추. 색으로만 구분한다 — 모양까지 바꾸면
   아이콘을 두 벌 관리하게 된다. */
.commgrd__icon--on { color: var(--jsini-accent-text); }
.commgrd__icon--warn { color: #d9534f; }

/* ── 줄무늬 ──────────────────────────────────────────────

   짝수 줄에 옅은 바탕. `CustomizeElement` 가 클래스를 붙이고 색은 여기서 정한다.

   **고정색을 쓰지 않는다.** 테마가 스물둘이고 어두운 것이 섞여 있어, 옅은
   회색을 박으면 어두운 테마에서 그 줄만 밝게 뜬다. 글자색을 4% 섞어 만들면
   밝은 테마에서는 어둡게, 어두운 테마에서는 밝게 — 어느 쪽이든 「한 톤 차이」가
   된다. 테마 서랍의 고른 항목(`--jsini-theme-picked`)과 같은 방식이다.

   **선택 줄을 이기지 않아야 한다.** DevExpress 의 선택 규칙이 클래스 여섯 개
   짜리(0,6,0)라, 이 규칙을 그보다 낮게(0,3,2) 두어 고른 줄에서는 선택색이
   그대로 이긴다. 여기에 클래스를 더 붙이면 고른 줄이 줄무늬에 묻힌다.

   **반투명이어야 한다 — 바탕색에 섞지 않는다(두 번 밟았다).**

   한때 `var(--jsini-bg)` 에 섞어 불투명하게 두었다. 「관리」 칸을 오른쪽에
   붙박자 줄무늬 줄에서 **아래를 지나가는 글자가 비쳐** 그것을 막으려던
   것이었는데, 그 바람에 **줄무늬 줄을 고르면 선택색이 안 보이게 됐다.**

   까닭은 이 규칙이 선택 규칙보다 **약하게(0,3,2)** 놓여 있다는 데 있다.
   DevExpress 의 선택색은 칸(`td`)에 칠해지고, 줄(`tr`)의 줄무늬가 반투명일
   때 그 위로 비쳐 보인다. 불투명하게 만들면 줄무늬가 선택색을 덮는다.

   그래서 줄무늬는 반투명으로 되돌리고, **비치는 문제는 붙박인 칸에서**
   따로 막는다(바로 아래). 그쪽은 선택 규칙보다 약하게 두어 고른 줄에서는
   선택색이 그대로 이긴다. */
.commgrd .dxbl-grid-table > tbody > tr.commgrd__row--alt {
  background-color: color-mix(in srgb, var(--dxbl-grid-color, currentColor) 4%, transparent);
}

/* 붙박인 칸은 `position: sticky` 로 굴러가는 칸 **위에** 떠 있다. 그 바탕이
   조금이라도 비치면 아래를 지나가는 글자가 그대로 보인다. 그래서 여기서만
   불투명하게 칠한다 — 보이는 색은 줄무늬와 같고 비치지만 않는다.

   [선택자 우선순위(Specificity) 주의]
   DevExpress(Fluent 테마 등)는 고정 칸에 아래 규칙을 둔다:
     .dxbl-grid .dxbl-grid-table > tbody > tr > td.dxbl-grid-fixed-cell { background-color: inherit; }
   단순한 `.commgrd .dxbl-grid-fixed-cell`(0,2,0)이나 `.commgrd tr.commgrd__row--alt .dxbl-grid-fixed-cell`(0,3,1)은
   DevExpress 규칙(0,3,3)에 밀려 무시되고 `inherit` 이 걸린다.
   그 결과 짝수 줄(`commgrd__row--alt`)의 반투명 배경이 그대로 상속되어 뒤의 글자가 비쳐 보인다.
   따라서 DevExpress 규칙 이상의 상세 선택자(0,4,3 및 0,5,3)로 불투명 배경을 명시한다.

   단, 고른 줄(selected)·포커스 줄(focused)·호버 줄에서는 DevExpress 가 줄(tr)에
   칠한 강조색을 그대로 상속받아야 하므로 `inherit` 으로 되돌린다. */
.commgrd .dxbl-grid .dxbl-grid-table > tbody > tr > td.dxbl-grid-fixed-cell {
  background-color: var(--jsini-bg);
}

.commgrd .dxbl-grid .dxbl-grid-table > tbody > tr.commgrd__row--alt > td.dxbl-grid-fixed-cell {
  background-color: color-mix(in srgb, var(--dxbl-grid-color, currentColor) 4%, var(--jsini-bg));
}

.commgrd .dxbl-grid .dxbl-grid-table > tbody > tr.dxbl-grid-focused-row > td.dxbl-grid-fixed-cell,
.commgrd .dxbl-grid .dxbl-grid-table > tbody > tr.dxbl-grid-selected-row > td.dxbl-grid-fixed-cell,
.commgrd .dxbl-grid .dxbl-grid-table.dxbl-grid-row-hover-enabled > tbody > tr:hover > td.dxbl-grid-fixed-cell {
  background-color: inherit;
}

/* ── 칸 머리는 가운데 정렬 ────────────────────────────────

   DevExpress 의 머리 칸은 flex 고, 그 안 첫 `span` 이 글자, 뒤따르는 것이
   정렬 화살표와 깔때기다. 그 span 이 남은 폭을 다 먹으므로(`flex-grow: 1`)
   글자만 가운데로 보내면 아이콘은 제자리에 남는다.

   자료 칸은 건드리지 않는다 — 숫자는 오른쪽, 글자는 왼쪽이 읽기 좋고
   그것은 칸마다 `TextAlignment` 로 정할 일이다. */
.commgrd .dxbl-grid-header-content > span:first-child {
  text-align: center;
}

/* ── 아이콘을 페이저 줄에 얹는다 ──────────────────────────

   원하는 모양은 한 줄이다 — 왼쪽에 아이콘, 오른쪽에 쪽 이동.

     ┌──────────────────────────────────────────┐
     │  ＋ ↻ ▽ ⌸                    ‹ 1 2 3 ›   │
     └──────────────────────────────────────────┘

   그런데 **페이저는 DxGrid 안쪽에 있고 우리 띠는 바깥에 있다.** DOM 이
   갈라져 있어 한 줄로 만들려면 둘 중 하나다 —
   페이저를 우리가 새로 만들거나(공개 `DxPager` 부품이 없다), 우리 띠를
   그 줄 위에 얹거나. 뒤엣것을 골랐다.

   [DevExpress 클래스에 기대는 유일한 자리다]

   `.dxbl-grid-bottom-panel` 하나에 기댄다. 그 이름이 바뀌면 `:has()` 가
   안 맞고, 띠는 **예전처럼 표 아래 한 줄로 돌아간다.** 깨지는 것이 아니라
   모양만 되돌아가는 쪽으로 실패한다 — 기대는 값을 하나로 줄인 이유다.

   페이저가 아예 없는 표(`PagerVisible="false"`)에서도 `:has()` 가 안 맞아
   저절로 아래 한 줄이 된다. 그래서 따로 분기하지 않는다.
   ---------------------------------------------------------- */

/* 표를 화면 안에 묶는다. 넘치는 자료는 DevExpress 가 표 안쪽에서 굴린다.

   `CommCont` 안에 있으면 아래 규칙이 이것을 대신한다 — 그때는 남은 높이를
   정확히 채우므로 어림값(58vh)이 필요 없다. 이 규칙은 표를 본문에 바로
   놓은 화면(아직 안 옮긴 것들)을 위한 것이다. */
.commgrd > .dxbl-grid {
  max-height: var(--commgrd-max-height);
}

/* 자료 영역 안에서는 남은 높이를 정확히 채운다. 모니터 크기와 무관하다 —
   vh 어림값이 아니라 부모가 준 높이를 그대로 쓴다. */
.commcont__body > .commgrd {
  flex: 1;
  min-height: 0;
}

/* 나무(`DxTreeList`)와 맨 `DxGrid` 는 `CommGrd` 로 감싸이지 않아 위 규칙이
   걸리지 않는다. 같은 일을 해 준다 — 안 해 주면 자료만큼 늘어나 판을 넘고,
   본문이 `overflow: hidden` 이라 **넘은 부분이 잘려** 아래 띠를 누를 수 없다.

   `max-height` 는 건드리지 않는다. 자료 판이 높이를 안 주는 좁은 화면에서는
   그 상한이 표를 붙잡아 주는 유일한 것이다. */
.commcont__body > .dxbl-grid {
  flex: 1;
  min-height: 0;
}

.commcont__body > .commgrd > .dxbl-grid {
  height: 100%;
  max-height: none;
}

/* 머리줄이 선 표는 `height: 100%` 로 두면 **판을 머리줄 높이만큼 넘는다** —
   그 높이는 판 전체를 가리키므로 위에 한 줄이 생긴 만큼 아래가 밀린다.
   본문이 `overflow: hidden` 이라 넘은 부분은 잘리고, 잘리는 것이 하필
   페이저 줄과 아래 띠다. 남은 높이를 flex 로 받게 한다. */
.commcont__body > .commgrd:has(> .commgrd__head) > .dxbl-grid {
  flex: 1;
  min-height: 0;
  height: auto;
}

.commgrd:has(.dxbl-grid-bottom-panel) { position: relative; }

/* 페이저 줄. 높이를 우리가 정하고 페이저를 오른쪽 끝으로 민다. */
.commgrd:has(.dxbl-grid-bottom-panel) .dxbl-grid-bottom-panel {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  min-height: var(--commgrd-bar-height);

  /* 왼쪽에 아이콘 자리를 비워 둔다. 표가 좁아도 페이저가 아이콘 위로
     올라오지 않는다. */
  padding-left: 10rem;
}

/* 그 줄 왼쪽에 얹는다. 폭은 내용만큼만 차지하므로 페이저 클릭을 가리지 않는다. */
.commgrd:has(.dxbl-grid-bottom-panel) .commgrd__footer {
  position: absolute;
  left: 0.15rem;
  bottom: 0;
  height: var(--commgrd-bar-height);
  min-height: 0;
  padding: 0;
}

/* ── 공통 나무 (CommTree) ────────────────────────────────

   `CommGrd` 와 나란히 두는 규칙이다. 이름을 부품 이름에 맞추는 것은 표와 같은
   이유다 — 개발자 도구에 `commtree__footer` 라고 적혀 있으면 어느 파일을
   고쳐야 하는지 바로 보인다.

   **표의 규칙에 묶어 쓰지 않는다.** 겉모습이 같아도 한 선택자로 합치면 표의
   아래 띠를 고칠 때 나무까지 함께 움직이고, 그 사실이 어디에도 안 적힌다.
   대신 값이 어긋나지 않게 두 곳에 같은 크기를 적어 둔다.

   [DevExpress 는 나무에도 `dxbl-grid*` 클래스를 쓴다]

   `DxTreeList` 의 뿌리 요소가 `<dxbl-tree-list class="dxbl-grid">` 이고 표·칸
   머리·페이저 줄의 클래스 이름도 그리드와 같다 — 메뉴 관리 화면의 DOM 에서
   확인했다. 그래서 안쪽을 겨누는 규칙은 **바깥 클래스만 바꿔** 그대로 옮길 수
   있다. 아래가 그렇게 옮겨 온 것들이다.
   ---------------------------------------------------------- */

.commtree {
  display: flex;
  flex-direction: column;
  min-width: 0;

  /* 아래 띠의 높이. 표(`--commgrd-bar-height`)와 같은 값이어야 표와 나무를
     오갈 때 아이콘 줄이 위아래로 튀지 않는다. */
  --commtree-bar-height: 3rem;

  /*
    나무의 최대 높이. 없으면 아래 띠가 화면 밖으로 나간다 — 자세한 것은 위
    `--commgrd-max-height` 머리말에 있다.

    **나무에서는 이것 말고 크기를 다스릴 것이 없다.** 쪽나누기를 끄고 있어서
    (CommTree 머리말 6번) 줄 수만큼 그대로 늘어난다. 묶어 두면 DevExpress 가
    나무 안쪽에서 굴린다 — 칸 머리는 제자리에 남고 자료만 움직인다.
  */
  --commtree-max-height: 58vh;
}

/* 나무 위 도구줄. 조회 영역(CommSch)을 따로 둔 화면에서는 그려지지 않는다. */
.commtree__toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin-bottom: 0.75rem;
}

/* 왼쪽 묶음과 오른쪽 묶음을 갈라 놓는 빈 칸. */
.commtree__spacer { flex: 1; }

/* 나무 안 관리 칸의 단추 묶음 (하위 추가 · 수정 · 삭제). */
.commtree__actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
}

/* 자료 칸의 기본 정렬. 어느 칸에 붙일지는 CommTree 가 정한다.

   **첫 칸에는 붙지 않는다.** 나무의 첫 자료 칸이 계층 칸이라 들여쓰기와 펼침
   화살표가 그 셀 안에 함께 있다. 가운데로 맞추면 깊이마다 제목이 다른 자리에서
   시작해 계층이 눈에 안 들어온다. */
.commtree .dxbl-grid-table > tbody > tr > td.commtree__cell--center {
  text-align: center;
}

.commtree__empty {
  padding: 2rem;
  text-align: center;
  color: var(--jsini-text-muted);
}

/* 나무 아래 띠. 왼쪽 끝이 등록 자리다.

   표와 달리 **페이저 줄에 얹지 않는다** — 쪽나누기를 끄고 있어서 얹을 줄이
   없다. 화면에서 쪽나누기를 되살리면 이 띠는 페이저 줄 아래 한 줄로 남는다. */
.commtree__footer {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-height: var(--commtree-bar-height);
  padding: 0.25rem 0.15rem 0;

  /* 글자 없이 아이콘만 있는 자리라 조금 키운다. 표의 아래 띠와 같은 값이다. */
  --jsini-icon-size: 1.35rem;
}

/* 켜진 토글과, 걸린 조건을 지우는 단추. 색으로만 구분한다. */
.commtree__icon--on { color: var(--jsini-accent-text); }
.commtree__icon--warn { color: #d9534f; }

/* 줄무늬. 고정색을 쓰지 않는 이유와, 선택 줄을 이기지 않게 규칙을 낮게 두는
   이유는 표의 `commgrd__row--alt` 머리말에 적어 두었다. */
.commtree .dxbl-grid-table > tbody > tr.commtree__row--alt {
  background-color: color-mix(in srgb, var(--dxbl-grid-color, currentColor) 4%, transparent);
}

/* 붙박인 칸 불투명 처리 (commgrd 와 동일) */
.commtree .dxbl-grid .dxbl-grid-table > tbody > tr > td.dxbl-grid-fixed-cell {
  background-color: var(--jsini-bg);
}

.commtree .dxbl-grid .dxbl-grid-table > tbody > tr.commtree__row--alt > td.dxbl-grid-fixed-cell {
  background-color: color-mix(in srgb, var(--dxbl-grid-color, currentColor) 4%, var(--jsini-bg));
}

.commtree .dxbl-grid .dxbl-grid-table > tbody > tr.dxbl-grid-focused-row > td.dxbl-grid-fixed-cell,
.commtree .dxbl-grid .dxbl-grid-table > tbody > tr.dxbl-grid-selected-row > td.dxbl-grid-fixed-cell,
.commtree .dxbl-grid .dxbl-grid-table.dxbl-grid-row-hover-enabled > tbody > tr:hover > td.dxbl-grid-fixed-cell {
  background-color: inherit;
}

/* 칸 머리는 가운데 정렬. 계층 칸의 머리도 함께 맞춘다 — 머리에는 들여쓰기가
   없어서 자료 칸과 달리 어긋나지 않는다. */
.commtree .dxbl-grid-header-content > span:first-child {
  text-align: center;
}

/* 나무를 화면 안에 묶는다. `CommCont` 안에 있으면 아래 규칙이 이것을 대신한다 —
   그때는 남은 높이를 정확히 채우므로 어림값(58vh)이 필요 없다. */
.commtree > .dxbl-grid {
  max-height: var(--commtree-max-height);
}

/* 자료 영역 안에서는 남은 높이를 정확히 채운다. 모니터 크기와 무관하다. */
.commcont__body > .commtree {
  flex: 1;
  min-height: 0;
}

.commcont__body > .commtree > .dxbl-grid {
  height: 100%;
  max-height: none;
}

/* ── 로그인 · 안내 화면 ──────────────────────────────────────

   [디자인은 소개 사이트(:5556)를 따른다]

   같은 회사의 두 화면이 서로 다른 언어로 말하지 않게 한다. 값은 그쪽
   `site.css` 에서 **그대로 가져왔다** — 눈대중으로 비슷하게 만들지 않는다.

   | | 값 | 소개 사이트의 어디 |
   |---|---|---|
   | 먹 | `#0a0a0a` | `--ink` |
   | 흑연 | `#1c1c1e` | `--graphite` |
   | 강철 | `#6e6e73` | `--steel` |
   | 안개 | `#d2d2d7` | `--mist` |
   | 칸 이름 | 0.75rem · 대문자 · 자간 0.15em | `.field__label` |
   | 입력칸 | 1px `--mist` · 여백 `0.75rem 1rem` · 0.875rem | `.field input` |
   | 초점 | 테두리만 `--ink` | `.field input:focus` |
   | 단추 | 1px 테두리 · 누르면 안팎이 뒤집힌다 | `.btn-outline-dark` |
   | 조용한 링크 | 0.875rem · `--steel` → `--ink` | `.link-quiet` |
   | 눈썹 줄 | 0.75rem · 대문자 · 자간 0.3em | `.hero__eyebrow` |

   **강조색을 쓰지 않는다.** 「강조는 색이 아니라 대비로 낸다」가 그 사이트의
   규칙이다. 그래서 여기에는 `--jsini-accent` 가 한 번도 안 나온다 — 테마를
   스물둘 중 무엇으로 골라 두었든 로그인 화면은 같은 얼굴이다. 로그인 전에는
   그 선택을 읽을 수도 없다(테마는 사람의 것이고 아직 사람이 없다).

   **모서리를 둥글리지 않는다.** 그 사이트에 둥근 모서리가 한 군데도 없다.

   [글꼴은 가져오지 않았다]

   본문 글꼴(S-CoreDream)이 한 벌에 350KB 다. 이 화면은 모두가 가장 먼저 받는
   화면이라(회로와 DevExpress 를 걷어낸 자리다) 700KB 를 얹으면 그 일이
   헛것이 된다. 자간·굵기·크기만 같게 맞췄다.

   [쓰는 곳이 다섯이다]

   로그인 · 가입 신청 · 비밀번호 찾기 · 비밀번호 재설정 · 비밀번호 변경.
   좌우로 갈린 판(`jsini-auth`)은 **로그인만** 쓰고, 카드 안쪽
   (`jsini-login__*`)은 다섯이 함께 쓴다.
   ---------------------------------------------------------- */

.jsini-bare {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
}

/* 로그인은 화면을 꽉 채운다. 가운데 정렬과 여백은 카드 하나를 세우는
   화면(가입 신청 등)의 것이라 이때는 걷어낸다. */
.jsini-bare:has(> .jsini-auth) {
  display: block;
  padding: 0;
}

/* ── 로그인만: 좌우로 갈린 판 ────────────────────────────── */

.jsini-auth {
  min-height: 100vh;
  display: grid;

  /* 좁은 화면에서는 브랜드 판이 통째로 빠진다(아래 `display: none`).
     한 칸으로 두면 폼이 그대로 가운데에 선다. */
  grid-template-columns: 1fr;
}

.jsini-auth__brand {
  display: none;
  position: relative;
  overflow: hidden;
  background: #0a0a0a;
  color: #fff;
}

.jsini-auth__brand-inner {
  display: flex;
  flex-direction: column;
  justify-content: center;
  height: 100%;
  padding: 4rem 3.5rem;
}

/* 로고를 글자 크기에 묶지 않는다 — SVG 라 폭만 주면 비율은 viewBox 가 지킨다. */
.jsini-auth__logo {
  width: 9rem;
  height: auto;
}

/* 히어로의 눈썹 줄과 같은 값이다(`.hero__eyebrow`). */
.jsini-auth__eyebrow {
  margin: 2.5rem 0 0;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.3em;
  color: #6e6e73;
}

.jsini-auth__lead {
  margin: 1.25rem 0 0;
  max-width: 24rem;
  font-size: 0.875rem;
  line-height: 1.7;
  color: #d2d2d7;
}

/* 브랜드 판의 사선. 소개 사이트의 상승 조각과 **같은 각도**(사선 22 : 높이
   100 → 102.4deg)다 — 그 사이트가 구분선·강조에 반복해서 쓰는 모양이다. */
.jsini-auth__brand::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    102.4deg,
    transparent 0 46%,
    rgb(255 255 255 / 4%) 46% 54%,
    transparent 54%
  );
  pointer-events: none;
}

.jsini-auth__panel {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem 1rem;
  background: #fff;
}

/* 브랜드 판은 넓을 때만 선다. 판 둘을 360px 화면에 나란히 두면 폼이 못 쓴다. */
@media (width >= 62rem) {
  .jsini-auth { grid-template-columns: 1.05fr 1fr; }
  .jsini-auth__brand { display: block; }

  /* 왼쪽에 이미 로고가 있으므로 카드 안의 것은 뺀다. */
  .jsini-login__logo { display: none; }
}

/* ── 다섯 화면이 함께 쓰는 카드 ──────────────────────────── */

.jsini-login__card {
  width: min(23rem, 100%);
  background: #fff;
  color: #0a0a0a;
}

/* 카드 안 로고. 좁은 화면에서만 보인다(위 질의). */
.jsini-login__logo {
  width: 7.5rem;
  height: auto;
  margin-bottom: 2rem;
}

/* 제목. 소개 사이트의 `.h-display` 와 같은 자간·굵기다. */
.jsini-login__title {
  margin: 0;
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.05;
  color: #0a0a0a;
}

.jsini-login__sub {
  margin: 0.75rem 0 2.25rem;
  font-size: 0.875rem;
  line-height: 1.7;
  color: #6e6e73;
}

.jsini-login__field {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 1.25rem;
}

.jsini-login__label {
  display: block;
  margin: 0;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: #6e6e73;
}

.jsini-login__input {
  width: 100%;
  padding: 0.75rem 1rem;
  border: 1px solid #d2d2d7;
  border-radius: 0;
  background: #fff;
  color: #0a0a0a;
  font: inherit;
  font-size: 0.875rem;
  outline: none;
  box-sizing: border-box;
}

/* 초점은 **테두리만** 짙어진다. 그림자나 바탕을 깔면 그 순간 강조색이
   필요해지고, 그러면 이 화면의 규칙이 무너진다. */
.jsini-login__input:focus { border-color: #0a0a0a; }

.jsini-login__input::placeholder { color: #d2d2d7; }

/* 아이디 기억하기. 네모는 브라우저가 그린다 — 우리가 다시 그리지 않는다. */
.jsini-login__remember {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0.25rem 0 1.75rem;
  font-size: 0.875rem;
  color: #1c1c1e;
  cursor: pointer;
}

.jsini-login__remember input {
  width: 1rem;
  height: 1rem;
  margin: 0;
  flex-shrink: 0;
  accent-color: #0a0a0a;
  cursor: pointer;
}

/* 단추. 누르면 안팎이 뒤집힌다 — 소개 사이트의 `.btn-outline-dark` 그대로다. */
.jsini-login__submit {
  width: 100%;
  padding: 0.75rem 2rem;
  border: 1px solid #0a0a0a;
  border-radius: 0;
  background: transparent;
  color: #0a0a0a;
  font: inherit;
  font-size: 0.875rem;
  cursor: pointer;
  transition: background-color 0.2s, color 0.2s;
}

.jsini-login__submit:hover,
.jsini-login__submit:focus-visible {
  background: #0a0a0a;
  color: #fff;
}

/* ── 지문·얼굴로 로그인 ───────────────────────────────────────────────

   이 화면의 규칙을 그대로 지킨다 — 색을 쓰지 않고 대비로만 말한다.
   단추는 로그인 단추와 **같은 모양의 반대말**이다: 저쪽이 먹 테두리에 흰
   바탕이라면 이쪽은 회색 테두리라, 무엇이 기본 동작인지가 한눈에 보인다.

   판 전체는 기본이 감춤(`hidden`)이고 `passkey.js` 가 지원하는 브라우저에서만
   꺼낸다. 여기서 `display` 를 정하지 않는 이유가 그것이다 — 정해 두면
   `hidden` 을 이겨서 못 쓰는 브라우저에도 단추가 보인다. */
.jsini-login__passkey {
  margin-top: 1.75rem;
}

/* 「또는」. 가운뎃줄은 양옆으로 늘어나고 글자 자리만 비운다. */
.jsini-login__or {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 1.25rem;
  font-size: 0.75rem;
  letter-spacing: 0.15em;
  color: #6e6e73;
}

.jsini-login__or::before,
.jsini-login__or::after {
  content: '';
  flex: 1;
  height: 1px;
  background: #d2d2d7;
}

.jsini-login__passkey-btn {
  width: 100%;
  /* 손가락 과녁 44px 를 넘긴다. 휴대폰에서 누르는 단추다. */
  padding: 0.75rem 2rem;
  border: 1px solid #d2d2d7;
  border-radius: 0;
  background: transparent;
  color: #1c1c1e;
  font: inherit;
  font-size: 0.875rem;
  cursor: pointer;
  transition: border-color 0.2s, color 0.2s;
}

.jsini-login__passkey-btn:hover,
.jsini-login__passkey-btn:focus-visible {
  border-color: #0a0a0a;
  color: #0a0a0a;
}

/* 누르는 동안은 기기가 답하기를 기다리는 중이다. 「눌렸다」가 보여야
   사용자가 한 번 더 누르지 않는다. */
.jsini-login__passkey-btn:disabled {
  color: #6e6e73;
  border-color: #e8e8ed;
  cursor: progress;
}

/* 기기가 거절했을 때의 한 줄. 실패 안내와 같은 규칙을 쓰되 판이 아니라
   글줄이라 테두리를 세우지 않는다 — 폼 위의 `__error` 와 무게가 같아지면
   어느 쪽을 봐야 할지 알 수 없다. */
.jsini-login__passkey-note {
  margin: 0.75rem 0 0;
  font-size: 0.8125rem;
  line-height: 1.6;
  color: #6e6e73;
}

/* ── 지문을 먼저 묻는 기기 ────────────────────────────────────────────

   「내 정보 → 보안 설정」에서 [로그인 방식 우선순위] 를 지문·얼굴로 둔
   기기에서만 걸린다. 딱지는 `passkey.js` 가 붙인다 — 서버는 그 값을 모른다
   (로그인 전이라 누구인지 모르고, 그 값은 계정이 아니라 브라우저의 것이다).

   **마크업을 두 벌 두지 않는다.** 차례만 바뀌고 안에 든 것은 같다. */
.jsini-login__methods {
  display: flex;
  flex-direction: column;
}

.jsini-login__methods--passkey-first .jsini-login__passkey {
  order: -1;
  /* 맨 위로 올라갔다. 위 여백은 「또는」 아래에 붙어 있을 때 필요했던 값이다. */
  margin-top: 0;
}

/* 판 안의 차례도 뒤집는다 — 단추가 먼저고 「또는」이 마지막이다. 안 뒤집으면
   카드 맨 위에 「또는」이 서서 **무엇의 또인지 알 수 없는 줄**이 된다.

   `:not([hidden])` 이 붙어 있어야 한다. 이 판은 기본이 감춤이고 `passkey.js`
   가 쓸 수 있는 브라우저에서만 꺼내는데, 그냥 `display: flex` 를 주면 그것이
   `hidden` 을 이겨 **못 쓰는 브라우저에도 단추가 보인다.** */
.jsini-login__methods--passkey-first .jsini-login__passkey:not([hidden]) {
  display: flex;
  flex-direction: column;
}

.jsini-login__methods--passkey-first .jsini-login__passkey-note { order: 1; }

/* 아래 여백을 0 으로 두면 안 된다. 판이 flex 라 안쪽 여백이 바깥과
   합쳐지지 않아서, 이 줄과 비밀번호 폼이 딱 붙는다. */
.jsini-login__methods--passkey-first .jsini-login__or {
  order: 2;
  margin: 1.75rem 0 1.25rem;
}

/* 기본 동작이 바뀌었으니 **무게도 바뀐다.** 이 화면은 색을 쓰지 않으므로
   무엇이 먼저인지는 테두리의 짙기로만 말할 수 있다 — 먹 테두리가 기본,
   회색 테두리가 그다음이다(두 단추의 평소 모습을 그대로 맞바꾼다). */
.jsini-login__methods--passkey-first .jsini-login__passkey-btn {
  border-color: #0a0a0a;
  color: #0a0a0a;
}

.jsini-login__methods--passkey-first .jsini-login__passkey-btn:hover,
.jsini-login__methods--passkey-first .jsini-login__passkey-btn:focus-visible {
  background: #0a0a0a;
  color: #fff;
}

/* 기기가 답하기를 기다리는 동안은 위 규칙보다 이쪽이 이겨야 한다. 순서로
   이기게 두면 짙은 단추가 눌린 채로 남아 「눌렸다」가 안 보인다. */
.jsini-login__methods--passkey-first .jsini-login__passkey-btn:disabled {
  background: transparent;
  color: #6e6e73;
  border-color: #e8e8ed;
}

.jsini-login__methods--passkey-first .jsini-login__submit {
  border-color: #d2d2d7;
  color: #1c1c1e;
}

.jsini-login__methods--passkey-first .jsini-login__submit:hover,
.jsini-login__methods--passkey-first .jsini-login__submit:focus-visible {
  background: transparent;
  border-color: #0a0a0a;
  color: #0a0a0a;
}

/* 실패 안내. **빨강을 쓰지 않는다** — 이 화면에 색이 하나도 없어서 붉은 줄
   하나가 유일한 색이 되면 그것만 보인다. 대신 먹 테두리를 왼쪽에 세워
   대비로 알린다(그 사이트가 강조를 내는 방식이다). */
.jsini-login__error {
  margin-bottom: 1.5rem;
  border-left: 2px solid #0a0a0a;
  padding: 0.75rem 1rem;
  background: #f5f5f7;
  font-size: 0.875rem;
  line-height: 1.6;
  color: #1c1c1e;
}

/* 실패가 아닌 안내 — 소셜에서 되돌아온 「가입 신청을 받았다」 같은 것.

   **실패 판과 갈라 둔 이유.** 이 화면은 색을 쓰지 않으므로 무게는 왼쪽 줄의
   짙기로만 말할 수 있다. 실패가 먹(`#0a0a0a`)이고 이쪽은 회색(`#86868b`)이라,
   「무엇이 잘못됐다」와 「이렇게 됐다」가 한눈에 갈린다. 같은 판으로 그리면
   신청이 접수된 사람이 자기가 실패한 줄 안다. */
.jsini-login__notice {
  margin-bottom: 1.5rem;
  border-left: 2px solid #86868b;
  padding: 0.75rem 1rem;
  background: #f5f5f7;
  font-size: 0.875rem;
  line-height: 1.6;
  color: #1c1c1e;
}

/* ── 소셜로 로그인 · 가입 ─────────────────────────────────────────────

   구글 · 네이버 · 카카오. **여기만은 공급자의 색을 쓴다** — 카드의 다른 곳은
   「강조는 색이 아니라 대비로」가 규칙이지만, 카카오와 네이버는 로그인 단추의
   바탕색과 상징을 지키라는 규정을 두고 있고 사람들도 그 색으로 단추를 찾는다.
   대신 높이·글자 크기는 로그인 단추와 같게 두어 카드의 결을 지킨다.

   켜지 않은 공급자도 「준비 중」으로 자리를 보인다(`SocialButtons`). 그
   단추는 색을 걷어 회색으로 가라앉힌다 — 누를 수 없는 것이 색을 가지면
   누를 수 있는 것과 구별되지 않는다.

   지문 판과 달리 기본이 감춤이 아니다. 여기는 브라우저가 할 수 있는지를
   따질 것이 없고, 그릴지 말지는 **서버가 준 목록**이 이미 정했다. */
.jsini-login__social {
  margin-top: 1.75rem;
}

/* 한 줄에 하나씩 쌓는다. 셋을 나란히 두면 「카카오로 신청하기」가 두 줄로
   접히고, 휴대폰에서는 과녁이 좁아진다. */
.jsini-social {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

/* 상징은 왼쪽에 박고 글자는 가운데에 둔다 — 카카오·네이버·구글의 단추 규정이
   모두 이 배치다. 손가락 과녁 44px 를 넘긴다. */
.jsini-social__btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 2.875rem;
  padding: 0 3rem;
  border: 1px solid transparent;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.3;
  text-decoration: none;
  transition: filter 0.15s, box-shadow 0.15s;
}

a.jsini-social__btn:hover,
a.jsini-social__btn:focus-visible {
  filter: brightness(0.96);
  box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
}

a.jsini-social__btn:focus-visible {
  outline: 2px solid #0a0a0a;
  outline-offset: 2px;
}

.jsini-social__icon {
  position: absolute;
  left: 1rem;
  top: 50%;
  width: 1.25rem;
  height: 1.25rem;
  transform: translateY(-50%);
}

/* 네이버의 N 은 네모를 꽉 채우는 글자라 같은 크기면 혼자 커 보인다. */
.jsini-social__icon--sm {
  width: 0.875rem;
  height: 0.875rem;
  left: 1.1875rem;
}

/* 그림이 없는 공급자(설정만으로 붙인 것). 머리글자를 동그라미에 담는다. */
.jsini-social__icon--letter {
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: #0a0a0a;
  color: #fff;
  font-size: 0.6875rem;
  font-weight: 700;
}

.jsini-social__btn--kakao {
  background: #fee500;
  color: rgb(0 0 0 / 85%);
}

.jsini-social__btn--kakao .jsini-social__icon { color: #000; }

.jsini-social__btn--naver {
  background: #03c75a;
  color: #fff;
}

.jsini-social__btn--google {
  border-color: #dadce0;
  background: #fff;
  color: #1f1f1f;
}

/* 모르는 공급자는 이 카드의 보통 단추 말투를 쓴다. */
.jsini-social__btn:not(.jsini-social__btn--kakao, .jsini-social__btn--naver, .jsini-social__btn--google) {
  border-color: #d2d2d7;
  background: #fff;
  color: #1c1c1e;
}

/* 준비 중. 색을 전부 걷고 점선으로 「아직 없는 자리」임을 보인다. */
.jsini-social__btn.is-soon {
  border: 1px dashed #d2d2d7;
  background: #fafafa;
  color: #aeaeb2;
  font-weight: 500;
  cursor: not-allowed;
}

.jsini-social__btn.is-soon .jsini-social__icon {
  color: #c7c7cc;
  filter: grayscale(1);
  opacity: 0.45;
}

.jsini-social__soon {
  position: absolute;
  right: 0.75rem;
  top: 50%;
  transform: translateY(-50%);
  padding: 0.125rem 0.5rem;
  border-radius: 999px;
  background: #ececf0;
  color: #8e8e93;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}

/* 처음 누르는 사람이 「로그인」을 기대했다가 승인 대기를 만나면 고장으로
   읽는다. 누르기 **전에** 말해 두는 한 줄이다. */
.jsini-login__social-note {
  margin: 0.75rem 0 0;
  font-size: 0.8125rem;
  line-height: 1.6;
  color: #6e6e73;
}

/* 칸 검증 메시지도 같은 규칙으로 조용히 둔다. */
.jsini-bare .validation-message {
  font-size: 0.75rem;
  color: #1c1c1e;
}

/* 가입 신청은 칸이 일곱이라 로그인 카드보다 넓어야 한다. */
.jsini-login__card--wide {
  width: min(30rem, 100%);
}

/* ── 가입 신청: 판 위에 올린 카드 ───────────────────────────────────

   로그인은 좌우로 갈린 판이 흰 바탕을 깔아 주지만, 가입 신청은 회색 바탕에
   카드만 선다. 테두리도 여백도 없으면 칸들이 바탕 위에 흩어져 보여서
   카드를 **종이 한 장**으로 세운다.

   이름표도 여기서는 로그인의 대문자·넓은 자간을 풀어 준다. 그 말투는 영문
   두어 단어짜리에 맞춘 것이라, 한글 일곱 줄에 걸면 「아 이 디」처럼 글자가
   흩어진다. */
.jsini-login__card--panel {
  margin: 2rem 0;
  padding: 2.5rem 2.25rem 2rem;
  border: 1px solid #e8e8ed;
  box-shadow:
    0 1px 2px rgb(0 0 0 / 4%),
    0 16px 40px -16px rgb(0 0 0 / 14%);
  box-sizing: border-box;
}

.jsini-login__card--panel .jsini-login__sub {
  margin-bottom: 1.75rem;
}

.jsini-login__card--panel .jsini-login__field {
  gap: 0.375rem;
  margin-bottom: 1rem;
  min-width: 0;
}

.jsini-login__card--panel .jsini-login__label {
  font-size: 0.8125rem;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  color: #1c1c1e;
}

.jsini-login__card--panel .jsini-login__input {
  padding: 0.6875rem 0.875rem;
}

.jsini-login__card--panel .jsini-login__or {
  margin: 1.75rem 0 1.5rem;
  letter-spacing: 0.02em;
}

/* 소셜이 맨 위에 올 때는 위 여백이 제목 아래 여백과 겹친다. */
.jsini-login__social--top {
  margin-top: 0;
}

/* 「선택」 꼬리표. 반드시 넣을 칸과 아닌 칸을 이름표에서 가른다. */
.jsini-login__optional {
  margin-left: 0.25rem;
  font-size: 0.75rem;
  font-weight: 400;
  color: #86868b;
}

/* 짧은 칸 둘을 나란히. 좁은 화면에서는 한 줄씩 떨어진다. */
.jsini-login__row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 0.75rem;
}

/* 가입 신청의 단추는 칸을 다 채운 뒤 누르는 **하나뿐인 끝**이라 먹으로 채운다.
   로그인의 테두리 단추는 지문·소셜과 나란히 서야 해서 가볍게 둔 것이다. */
.jsini-login__submit--solid {
  margin-top: 0.5rem;
  padding-block: 0.875rem;
  background: #0a0a0a;
  color: #fff;
  font-weight: 600;
}

.jsini-login__submit--solid:hover,
.jsini-login__submit--solid:focus-visible {
  background: #2c2c2e;
}

.jsini-login__submit:disabled {
  opacity: 0.55;
  cursor: progress;
}

/* 카드 맨 아래 「이미 계정이 있으신가요? 로그인」. */
.jsini-login__foot {
  margin: 1.5rem 0 0;
  text-align: center;
  font-size: 0.875rem;
  color: #6e6e73;
}

.jsini-login__foot a {
  margin-left: 0.25rem;
  color: #0a0a0a;
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* 휴대폰: 종이를 걷고 화면 전체를 카드로 쓴다. 테두리가 화면 가장자리와
   겹치면 틀만 두 겹이 된다. */
@media (width < 34rem) {
  .jsini-login__card--panel {
    margin: 0;
    padding: 1.75rem 0.25rem;
    border: 0;
    box-shadow: none;
    background: transparent;
  }

  .jsini-login__row { grid-template-columns: 1fr; }
}

/* 여러 줄 입력도 같은 칸 모양을 쓴다. 높이만 내용에 맡긴다. */
.jsini-login__textarea {
  resize: vertical;
  min-height: 4.5rem;
}

/* 카드 아래의 「비밀번호를 잊으셨나요?」 · 「가입 신청」.
   소개 사이트의 `.link-quiet` 와 같다 — 평소 강철, 올리면 먹. */
.jsini-login__links {
  display: flex;
  justify-content: space-between;
  gap: 0.75rem;
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid #d2d2d7;
  font-size: 0.875rem;
}

.jsini-login__links a {
  color: #6e6e73;
  text-decoration: none;
  transition: color 0.2s;
}

.jsini-login__links a:hover { color: #0a0a0a; }

/* 부제가 없는 카드에서 제목과 첫 칸 사이를 벌린다. 제목에는 아래 여백이
   없어서(`.jsini-login__title`) 이것이 없으면 이름표가 제목에 붙는다.
   `.jsini-login__sub` 의 아래 여백과 같은 값이다. */
.jsini-login__gap { height: 2.25rem; }

/* 비밀번호 찾기에서 「메일을 보냈다」 뒤에 무엇을 해야 하는지 적는 차례 목록.

   **번호를 브라우저에 그리게 두지 않는다** — 기본 `list-style` 번호는
   글머리 위치가 글꼴마다 달라 들여쓰기가 제각각이 된다. 칸을 고정으로
   잡고 번호를 직접 찍으면 줄이 길어져 접혀도 둘째 줄이 글에 맞춰 선다. */
.jsini-steps {
  margin: 1.25rem 0;
  padding: 0;
  list-style: none;
  counter-reset: jsini-step;
  font-size: 0.875rem;
  line-height: 1.7;
  color: #1c1c1e;
}

.jsini-steps li {
  position: relative;
  padding: 0 0 0 1.75rem;
  margin-bottom: 0.6rem;
}

.jsini-steps li::before {
  counter-increment: jsini-step;
  content: counter(jsini-step);
  position: absolute;
  left: 0;
  top: 0.15rem;
  width: 1.15rem;
  height: 1.15rem;
  line-height: 1.15rem;
  text-align: center;
  font-size: 0.6875rem;
  background: #0a0a0a;
  color: #fff;
}

/* ── 로그인 흐름의 카드 안에 놓인 안내 줄 ──────────────────

   [테마를 따라가게 두면 어두운 테마에서 글자가 사라진다] (실제로 밟음)

   `.jsini-notice` 는 업무 화면용이라 색을 테마 변수에서 얻는다
   (`--jsini-text` · `--jsini-border` · `--jsini-warning-soft`). 그런데
   **이 카드는 테마를 따라가지 않는다** — 소개 사이트를 따라 흰 바탕에
   먹 글씨로 박아 두었다(`.jsini-login__card`). 그래서 어두운 테마를 고른
   사람에게는 흰 바탕 위에 **거의 흰 글자**가 얹혀 한 글자도 안 읽혔다.

   카드가 색을 고정했으니 그 안의 안내 줄도 고정한다. 값은 옆에 있는
   `.jsini-login__error` 에서 그대로 가져왔다 — 지어내지 않는다.

   [세 말투를 색으로 가르지 않는다]

   이 화면에는 색이 하나도 없다. 붉은 줄 하나를 들이면 그것만 보인다
   (`.jsini-login__error` 의 같은 줄). 대신 왼쪽 글자(`i` · `!` · `×`)로
   가른다 — 색각과 무관하게 읽히기도 한다.
   ---------------------------------------------------------- */

.jsini-login__card .jsini-notice {
  margin: 0 0 1.5rem;
  line-height: 1.6;

  /* 모서리도 그림자도 없다. 이 화면의 다른 상자들이 각져 있다. */
  border: 0;
  border-left: 2px solid #0a0a0a;
  border-radius: 0;
  box-shadow: none;
  background: #f5f5f7;
  color: #1c1c1e;
  font-size: 0.875rem;
}

.jsini-login__card .jsini-notice::before {
  color: #0a0a0a;
}

/* 비밀번호 변경 화면에서 「나중에 하기」. 만료로 끌려온 사람에게는 안 보인다. */
/* 「나중에 하기」 · 「로그인 화면으로」 같은 조용한 링크.

   **색과 크기를 테마 변수에서 얻지 않는다.** 이 카드는 테마를 따라가지
   않아서(`.jsini-login__card .jsini-notice` 의 머리말) 어두운 테마에서는
   `--jsini-text-muted` 가 밝은 회색이 되어 흰 바탕에서 사라진다.
   값은 카드 아래 링크(`.jsini-login__links a`)와 같게 맞췄다. */
.jsini-password__back {
  display: block;
  margin-top: 0.9rem;
  text-align: center;
  font-size: 0.875rem;
  color: #6e6e73;
  text-decoration: none;
  transition: color 0.2s;
}

.jsini-password__back:hover { color: #0a0a0a; }

/* 카드 안의 곁들이는 한 줄도 같은 이유로 고정한다. */
.jsini-login__card .jsini-hint {
  font-size: 0.8125rem;
  color: #6e6e73;
}

/* 단추처럼 생긴 링크(재설정을 마친 뒤 「로그인 화면으로」).
   `jsini-login__submit` 은 <button> 기준이라 링크에는 이 둘이 더 필요하다. */
.jsini-password__go {
  display: block;
  text-align: center;
  text-decoration: none;
  box-sizing: border-box;
}

.jsini-message {
  text-align: center;
  max-width: 480px;
}

.jsini-message__trace code {
  user-select: all; /* 사용자가 추적 번호를 한 번에 집을 수 있게 */
}

/* 번호가 길어서(55자) 좁은 화면에서는 손으로 못 집는다. 그래서 단추를 둔다 —
   휴대폰에서 난 오류를 전화로 불러 주는 자리가 실제로 이 기능의 대상이다. */
.jsini-message__copy {
  margin-left: 0.5rem;
  padding: 0.1rem 0.5rem;
  font-size: 0.75rem;
  color: #6e6e73;
  background: transparent;
  border: 1px solid #d2d2d7;
  border-radius: 4px;
  cursor: pointer;
}

.jsini-message__copy:hover { color: #0a0a0a; }

/* ── 진단 화면 ───────────────────────────────────────────── */

.jsini-diagnostics__facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.35rem 1rem;
  margin-top: 1.5rem;
  font-size: var(--jsini-fs-md);
}

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

.jsini-diagnostics__facts dd {
  margin: 0;
}

/* ── 준비 중 안내 (아직 안 옮긴 화면) ────────────────────── */

.jsini-pending {
  max-width: 620px;
  padding: 2rem 0;
}

.jsini-pending__title {
  margin-bottom: 0.75rem;
}

.jsini-pending__lead {
  font-size: var(--jsini-fs-lg);
  line-height: 1.6;
}

.jsini-pending__facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.35rem 1rem;
  margin: 1.25rem 0;
  padding: 0.9rem 1rem;
  border-radius: 6px;
  background: var(--jsini-surface-muted, rgba(127, 127, 127, 0.08));
  font-size: var(--jsini-fs-md);
}

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

.jsini-pending__facts dd {
  margin: 0;
}

.jsini-pending__facts code {
  user-select: all; /* 주소를 한 번에 집어 신고에 붙일 수 있게 */
}

.jsini-pending__hint {
  font-size: var(--jsini-fs-md);
  color: var(--jsini-text-muted);
  line-height: 1.6;
}

.jsini-pending__actions {
  display: flex;
  gap: 0.5rem;
  margin-top: 1.5rem;
}

/* ── 화면 공통 (모듈 여섯이 함께 쓴다) ───────────────────── */

.jsini-page-head {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
}

.jsini-page-head__title {
  margin: 0;
  font-size: var(--jsini-fs-2xl);
  font-weight: 700;
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}

/* 업무 이름. 제목과 같은 크기로 두면 어느 쪽이 화면 이름인지 헷갈린다. */
.jsini-page-head__scope {
  font-size: var(--jsini-fs-sm);
  font-weight: 400;
  color: var(--jsini-text-muted);
}

.jsini-page-head__actions {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

/* 조건줄. 좁은 화면에서 줄바꿈되게 둔다 — 가로 스크롤이 생기면
   오른쪽 끝의 조회 단추가 화면 밖으로 나간다. */
.jsini-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin-bottom: 1rem;
}

.jsini-toolbar__spacer { flex: 1; }

/* ── 배치 — Bootstrap 유틸리티 대신 쓰는 이름들 ───────────── */
/* 기본 테마인 Fluent 에는 Bootstrap 이 없다. `d-flex` 는 `display: block`,
   `gap-1` 은 `gap: normal` 이라 **아무 오류 없이** 배치만 어긋난다.
   Bootstrap 계열 테마로 보는 사람에게는 멀쩡해 보여서 더 늦게 발견된다.
   web/CLAUDE.md 「화면에 Bootstrap 유틸리티 클래스를 쓰지 않는다」 참고. */

/* 단추 묶음(표의 「관리」 칸)과 이름 앞 딱지 묶음. 하는 일이 달라 이름을
   나누어 두었다 — 한쪽 간격만 손볼 일이 생기면 그때 갈라 쓴다.
   좁은 칸에서 줄바꿈되게 둔다. 넘쳐서 잘리면 삭제 단추가 사라진다. */
.jsini-actions,
.jsini-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  align-items: center;
}

/* 한 칸 안에 조작을 나란히 놓는 자리. 단추 묶음보다 넉넉히 띄운다 —
   체크박스와 입력칸은 붙어 있으면 어느 이름이 어느 것인지 흐려진다. */
.jsini-inline {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

/* 왼쪽에 설명, 오른쪽에 조작을 두고 벌리는 줄. */
.jsini-split {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  justify-content: space-between;
}

/* 테두리 상자. Bootstrap `.card` 자리다. */
.jsini-card {
  padding: 0.85rem 1rem;
  margin-bottom: 1rem;
  border: 1px solid var(--jsini-border, #e0e0e0);
}

/* 흐린 글씨. 세 화면이 이미 이 이름을 쓰고 있었는데 정의가 없어서
   여느 글씨와 똑같이 보이고 있었다. */
.jsini-text-muted { color: var(--jsini-text-muted); }

/* 곁들이는 한 줄(관측 시각·읽기 전용 안내). 흐리고 작다.
   크기는 사다리에서 얻는다 — `rem` 으로 박으면 크기 모드를 안 따라간다. */
.jsini-hint {
  font-size: var(--jsini-fs-sm);
  color: var(--jsini-text-muted);
}

/* 차트 툴팁 속 내용. DxChartTooltip 은 테두리·바탕만 그리고 안쪽 여백은
   주지 않아서, 없으면 글자가 상자 모서리에 붙는다. 대시보드·보고서
   열한 화면이 같은 모양을 쓴다. */
.jsini-tip { padding: 0.35rem 0.5rem; }

/* 확인 창 (ConfirmDialog). 글은 줄바꿈을 살린다 — 무엇이 사라지는지
   두 줄로 적는 경우가 있다. */
.jsini-confirm__body {
  padding: 0.5rem 0.25rem;
  white-space: pre-line;
  line-height: 1.5;

  /* 묻는 글에는 사람이 지은 이름이 그대로 들어간다(「{대상}」 에서 …).
     그 이름이 띄어쓰기 없이 길면 — 저장소 경로·URL·`ai-task-runner-01`
     같은 것들 — 한 줄이 창보다 길어져 오른쪽이 잘린다. 창을 화면 안에
     가두는 것만으로는 이 줄이 안 접힌다. `anywhere` 는 넘칠 때만 끊어서
     평범한 문장의 줄바꿈 자리는 건드리지 않는다. */
  overflow-wrap: anywhere;
}

.jsini-notice {
  margin: 0.75rem 0;
  padding: 0.6rem 0.85rem;
  font-size: var(--jsini-fs-md);
  border-left: 3px solid;
}

.jsini-notice--info {
  border-color: var(--jsini-text-muted);
  background: color-mix(in srgb, var(--jsini-text-muted) 8%, transparent);
}

.jsini-notice--warning {
  border-color: #b26a00;
  background: color-mix(in srgb, #b26a00 10%, transparent);
}

.jsini-notice--error {
  border-color: #d32f2f;
  background: color-mix(in srgb, #d32f2f 10%, transparent);
  color: #d32f2f;
}

/* 상태 표시. 글자만으로도 읽히게 두고 색은 거들기만 한다 —
   색으로만 구분하면 색각 이상이 있는 사용자가 못 읽는다. */
/* ── 설정 줄 (`.jsini-setting`) ────────────────────────────────

   **왼쪽에 이름과 설명, 오른쪽에 스위치** 한 줄. 개인 설정 화면의 기본
   모양이고, 지금 알림 설정(`NotificationPanel`)이 쓴다.

   포털관리 CSS 에 `ad-setting` 으로 있던 것을 올렸다 — 그 판을 장례식장
   환경설정도 함께 쓰게 되면서 모듈 CSS 에 두면 한쪽에서만 모양이 산다.
   ---------------------------------------------------------- */

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

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

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

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

/* 화면 안의 작은 제목. 한 화면이 여러 묶음으로 나뉠 때 그 사이를 가른다. */
.jsini-subhead {
  margin: 1.5rem 0 0.75rem;
  font-size: var(--jsini-fs-lg);
  font-weight: 700;
}

/* ── 알림 구독을 권하는 창 (`PushAskPopup`) ────────────────

   로그인한 뒤 한 번 뜬다. 본문은 「무엇이 오는가」 한 줄과 「무엇이
   일어나는가」 목록이 전부다 — 이 창에서 정할 것은 받느냐 마느냐 하나라,
   길어지면 사람이 읽지 않고 닫는다.
   ---------------------------------------------------------- */

.jsini-pushask__lead { margin: 0 0 0.75rem; }

.jsini-pushask__facts {
  margin: 0;
  /* 점 자리만큼만 들여쓴다. 기본값(2.5rem)은 좁은 창에서 글이 오른쪽으로
     밀려 한 줄에 몇 글자 못 들어간다. */
  padding-left: 1.1rem;
  font-size: var(--jsini-fs-sm);
  color: var(--jsini-text-muted);
}

.jsini-pushask__facts li + li { margin-top: 0.35rem; }

/* 사파리에 적어 주는 설치 순서. **단추를 놓을 수 없는 자리를 글이 대신한다**
   (`PushAskPopup` 머리말) — 그래서 위 목록과 달리 흐리게 두지 않는다. 여기가
   시키는 대로 따라 해야 하는 유일한 대목이다. */
.jsini-pushask__howto {
  margin: 0.75rem 0 0;
  padding: 0.6rem 0.75rem 0.6rem 1.9rem;
  border-radius: var(--jsini-radius-md);
  background: var(--jsini-surface-muted);
  font-size: var(--jsini-fs-sm);
}

.jsini-pushask__howto li + li { margin-top: 0.35rem; }

/* ── 알림 판의 덩어리 셋 (`NotificationPanel`) ──────────────

   **상자를 만들지 않는다**(`display: contents`). 이 판을 그냥 쓰면 덩어리
   셋이 예전처럼 위에서 아래로 쌓이고, 화면이 격자를 깔면 셋이 **그 격자의
   칸으로 바로 들어간다** — 겉상자가 있으면 셋이 그 안에 갇혀 한 칸을
   차지하므로 벌려 놓을 수가 없다.

   그래서 여기서는 이름만 붙이고 생김새는 정하지 않는다. 벌려 놓는 규칙은
   그렇게 쓰는 화면이 갖는다(장례식장 `funeral.css` 의 `.fn-env`).
   ---------------------------------------------------------- */
.jsini-npanel { display: contents; }

/* 알림 서버에 못 닿았을 때 그 구역 자리에 접히는 한 덩어리.

   왼쪽에 까닭 · 오른쪽에 「다시 시도」다. 자리가 좁으면(환경설정의 격자 칸은
   260px 남짓이다) 단추가 눌려 글자가 세로로 쪼개지므로 아래로 내려 보낸다 —
   `.fn-env` 의 셋째 칸이 같은 까닭으로 같은 손을 쓰고 있다. */
.jsini-npanel__down {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1rem;
}

.jsini-npanel__down-text {
  min-width: 0;
  flex: 1 1 12rem;
}

/* ── 휴대폰의 「등록된 기기」 카드 (`jsini-npanel__devices`) ────

   이 표는 칸이 셋이다 — 기기 · 등록 · 빼기. 휴대폰에서는 공통 규칙이 그것을
   카드로 펴는데(`commgrd--cards`), 펴진 그대로는 **한 장이 147px** 이었다
   (390×844 에서 재었다). 기기 다섯이면 그것만 735px 이라 쪽이 통째로 그 표다.

   길이를 먹는 것이 둘이다. 둘을 걷어 **111px** 이 되었다(같은 자리에서 다시
   재었다 — 넉 장이면 144px 이 준다).

   [하나 — 「빼기」가 제 줄을 차지한다]

   그 칸에는 이름이 없어서(`Caption=""`) 카드에서 **값 없는 한 줄**로 떨어진다.
   카드 바닥에 휴지통 하나가 왼쪽에 덩그러니 서서 32px 을 먹었다.

   체크 칸·펼침 단추가 이미 쓰는 수법 그대로 **귀퉁이로 올린다**(위 카드 규칙의
   `dxbl-grid-selection-cell`). 그 둘과 달리 여기는 자리로만 가려낼 수 없어 —
   `td` 에 붙는 표시가 없다 — **속의 단추를 짚는다**(`:has(> …)`, 단추의 이름은
   `NotificationPanel` 이 단다).

   오른쪽 위다. 체크 칸과 같은 자리지만 이 표에는 체크가 없고, 카드에서 지우는
   단추는 **제목 바로 옆**에 있어야 어느 기기를 지우는지 눈이 헷갈리지 않는다.

   [둘 — 이름표가 폭의 5rem 을 먹는다]

   카드는 칸마다 왼쪽에 이름을 적는다(`td[data-caption]::after`). 머리줄을
   감추니 보통은 그것이 맞다. **그런데 이 표에서는 아니다** — 「기기」 칸의
   값은 기기 이름이고(「Chrome · Windows」), 「등록」 칸의 값은 날짜다.
   둘 다 **보면 무엇인지 아는 것**이라 이름이 뜻을 더해 주지 않는다.
   그 대가로 값이 쓸 폭이 **280px** 뿐이었다(360px 에서 이름표 몫 80px 을
   뺀 것) — 긴 기기 이름이 그만큼 한 줄 더 접힌다. 이름을 걷으면 320px 이다
   (휴지통 자리로 비켜 둔 몫을 뺀 값).

   그래서 이름을 걷고 값을 줄의 처음부터 쓴다. 날짜만 흐리게 눕혀 **제목과
   덧말**의 꼴로 둔다 — 날짜가 이름과 같은 무게로 서면 둘 중 무엇이 그 카드를
   가리키는지 흐려진다.
   ---------------------------------------------------------- */
@media (max-width: 767px) {

  /* 오른쪽 위에 단추가 설 자리를 비운다. 안 비우면 긴 기기 이름이 휴지통
     밑으로 들어가 글자가 가려진다. */
  .jsini-npanel__devices .commgrd--cards .dxbl-grid-table > tbody > tr[data-visible-index] {
    padding-right: 3.2rem;
  }

  /* 공통 규칙이 카드 안의 모든 칸을 `position: static !important` 로 풀어
     두므로(가로로 구를 일이 없으니) 여기서 다시 `!important` 로 세운다.
     줄(`tr`)이 이미 `position: relative` 다. */
  .jsini-npanel__devices .commgrd--cards .dxbl-grid-table > tbody > tr > td:has(> .jsini-npanel__devrm) {
    position: absolute !important;
    top: 0.25rem;
    right: 0.3rem;
    width: auto !important;
    margin: 0 !important;
  }

  /* 이름표를 걷는다. `display: flex` 도 함께 되돌린다 — 이름이 빠지면
     나란히 세울 것이 값 하나뿐이다. */
  .jsini-npanel__devices .commgrd--cards .dxbl-grid-table > tbody > tr > td[data-caption] {
    display: block;
  }

  .jsini-npanel__devices .commgrd--cards .dxbl-grid-table > tbody > tr > td[data-caption]::after {
    content: none;
  }

  /* 제목 다음에 오는 칸 — 여기서는 등록 날짜 하나다. 흐린 덧말로 눕힌다. */
  .jsini-npanel__devices .commgrd--cards .dxbl-grid-table > tbody > tr > td[data-caption] ~ td[data-caption] {
    font-size: var(--jsini-fs-xs);
    color: var(--jsini-text-muted);
  }
}

/* 상태 넷을 나란히 놓는 자리(「이 브라우저」 — 실행 방식·서비스워커·권한·등록).
   칸 하나가 「이름 / 딱지」 한 쌍이고, 자리가 좁으면 한 줄씩 쌓인다.

   `<dl>` 을 쓰되 격자는 `<div>` 칸에 건다 — dt·dd 를 직접 격자 항목으로
   두면 이름과 값이 서로 다른 줄에 흩어져 짝이 안 보인다. */
.jsini-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0.5rem 1rem;
  margin: 0 0 1rem;
}

.jsini-fact { min-width: 0; }

.jsini-fact dt {
  font-size: var(--jsini-fs-xs);
  color: var(--jsini-text-muted);
  margin-bottom: 0.2rem;
}

.jsini-fact dd { margin: 0; }

/* ── 내 위치 날씨 (`NotificationPanel` 의 위치 칸) ──────────────

   스위치 목록(`.jsini-settings`)과 **선 하나로 가른다.** 여기서 하는 일은
   켜고 끄는 것이 아니라 좌표 하나를 잡아 두는 일이고, 잡은 뒤에 「여기가
   맞나」를 되묻는 자리라 줄 하나로는 안 된다. 선이 없으면 위 체크들과
   같은 목록의 연장으로 읽힌다.
   ---------------------------------------------------------- */
.jsini-weather-loc {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--jsini-border);
}

/* 저장된 자리의 이름 한 줄. 아래 칸들은 단계별로 갈라 놓은 것이라
   「어디인가」를 한눈에 말해 주지 못한다. */
.jsini-weather-loc__place {
  margin-bottom: 0.6rem;
  font-size: var(--jsini-fs-lg);
}

/* ── 내 위치 지도 (`LocationMap`) ──────────────────────────────

   OpenStreetMap 의 끼워 넣기 판이다. **높이를 여기서 정한다** — iframe 은
   내용에 맞춰 늘지 않아서 높이를 안 주면 기본 150px 로 찌부러진다.

   폭은 판을 꽉 채우고 높이는 220px 로 고정한다. 비율로 두면 넓은 화면에서
   지도만 한 화면을 차지하고, 좁은 화면에서는 동네 이름이 안 읽힌다.
   ---------------------------------------------------------- */
.jsini-map { margin: 0.75rem 0; }

.jsini-map__frame {
  display: block;
  width: 100%;
  height: 220px;
  border: 1px solid var(--jsini-border);
  background: var(--jsini-surface-alt, rgba(127, 127, 127, 0.08));
}

/* 좌표와 「큰 지도로 보기」. **판 밖이다** — 타일이 막힌 망에서 지도가
   비어도 확인할 것은 남아야 한다. */
.jsini-map__foot {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
  margin-top: 0.35rem;
  font-size: var(--jsini-fs-sm);
}

.jsini-map__coord { color: var(--jsini-text-muted); }

/* 알림창에 뜰 모양 그대로 보여 주는 자리. **바탕을 깔아 둘러싼다** —
   이것이 화면의 설명이 아니라 **밖에서 올 알림**이라는 것이 보여야 한다. */
.jsini-weather-preview {
  margin: 0.75rem 0;
  padding: 0.75rem 0.9rem;
  background: var(--jsini-surface-alt, rgba(127, 127, 127, 0.08));
  border: 1px solid var(--jsini-border);
}

.jsini-weather-preview__title {
  margin-top: 0.35rem;
  font-weight: 600;
}

/* 본문은 줄바꿈이 있는 한 덩이 글이다(서버가 `\n` 으로 만든다).
   `pre-line` 이 아니면 두세 줄이 한 줄로 붙어 알림과 다른 모양이 된다. */
.jsini-weather-preview__body {
  white-space: pre-line;
  font-size: var(--jsini-fs-sm);
  color: var(--jsini-text-muted);
}

/* 「마지막 저장 …」처럼 작게 덧붙이는 한 줄. */
.jsini-stamp {
  font-size: var(--jsini-fs-sm);
  color: var(--jsini-text-muted);
}

.jsini-badge {
  display: inline-block;
  padding: 0.1rem 0.5rem;
  font-size: var(--jsini-fs-xs);
  border: 1px solid;
}

.jsini-badge--on { border-color: #2e7d32; color: #2e7d32; }
.jsini-badge--off { border-color: var(--jsini-text-muted); color: var(--jsini-text-muted); }
.jsini-badge--warn { border-color: #b26a00; color: #b26a00; }
.jsini-badge--err { border-color: #d32f2f; color: #d32f2f; }

/* 통계 타일 묶음. 화면 위쪽에 숫자 넷 안팎을 늘어놓는 자리다. */
.jsini-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0.75rem;
  margin-bottom: 1.25rem;
}

.jsini-stat {
  padding: 0.85rem 1rem;
  border: 1px solid var(--jsini-border, #e0e0e0);
}

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

.jsini-stat__value {
  font-size: var(--jsini-fs-3xl);
  font-weight: 700;
  line-height: 1.2;
}

/* ── 묶음 메뉴 길잡이 ────────────────────────────────────── */

.jsini-group-links {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 0.75rem;
  margin-top: 1rem;
}

.jsini-group-link {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--jsini-border);
  color: inherit;
  text-decoration: none;
  transition: border-color 0.15s, background-color 0.15s;
}

.jsini-group-link:hover {
  border-color: var(--jsini-accent, #3b6cff);
  background: color-mix(in srgb, var(--jsini-accent, #3b6cff) 6%, transparent);
}

.jsini-group-link__title { font-weight: 600; }

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

/* ── 테마 서랍 ────────────────────────────────────────────

   DevExpress 데모의 테마 판을 본떴다. **떠 있는 창이 아니라 서랍이다** —
   헤더 아래부터 화면 바닥까지 오른쪽을 채우고 옆에서 밀려 들어온다.

   안은 데모와 같은 구성이다: 묶음 제목 · 밝기 두 칸 · 색 동그라미 격자 ·
   사용자 지정 색 · Classic 목록 · Bootstrap 목록.

   [닫혀 있을 때도 화면에 있다]

   `display: none` 이 아니라 **화면 밖으로 밀어 둔다**. 그래야 여닫는 것이
   미끄러져 보인다. 자리에서 사라지지 않아도 `position: fixed` 라
   가로 스크롤을 만들지 않는다.
   ---------------------------------------------------------- */

/*
  서랍 안에서 「얹은 것」과 「고른 것」을 나타내는 면.

  **테마 변수(`--jsini-surface`)를 쓰지 않는다.** 그 값은 판 배경과 같아지는
  테마가 있어서, 그런 테마에서는 고른 항목이 표시가 안 난다. 글자색을 옅게
  섞으면 밝은 테마에서도 어두운 테마에서도 늘 배경과 갈라진다.

  데모의 고른 줄은 흰 바탕에 `#f4f4f3` 이다 — 아래 11% 가 그 언저리다.
*/
:root {
  --jsini-theme-hover: color-mix(in srgb, var(--jsini-text) 6%, transparent);
  --jsini-theme-picked: color-mix(in srgb, var(--jsini-text) 11%, transparent);
}

.jsini-theme-anchor { display: inline-flex; }

/* 덮개. 헤더 아래부터 깐다 — 헤더까지 덮으면 방금 누른 테마 아이콘으로
   다시 닫을 수 없다. */
.jsini-theme-scrim {
  position: fixed;
  inset: var(--jsini-header-height) 0 0 0;
  background: rgb(0 0 0 / 28%);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease-in-out, visibility 0.2s;
  z-index: 1035;
}

.jsini-theme-scrim--on {
  opacity: 1;
  visibility: visible;
}

.jsini-theme-panel {
  position: fixed;
  top: var(--jsini-header-height);
  right: 0;
  bottom: 0;
  width: min(340px, 100vw);
  display: flex;
  flex-direction: column;
  background: var(--jsini-bg);
  color: var(--jsini-text);
  border-left: 1px solid var(--jsini-border);
  box-shadow: -10px 0 28px rgb(0 0 0 / 14%);
  transform: translateX(100%);
  transition: transform 0.22s ease-in-out;
  z-index: 1036;
}

.jsini-theme-panel--on { transform: none; }

/* 접힌 서랍 안으로 탭 이동이 들어가면 안 된다 — 보이지도 않는 단추에
   초점이 가서 화면이 엉뚱한 곳으로 굴러간다. */
.jsini-theme-panel:not(.jsini-theme-panel--on) {
  visibility: hidden;
  transition: transform 0.22s ease-in-out, visibility 0.22s;
}

.jsini-theme-panel__head {
  display: flex;
  align-items: center;
  height: 3rem;
  min-height: 3rem;
  padding: 0 0.5rem 0 1rem;
  background: var(--jsini-surface);
  border-bottom: 1px solid var(--jsini-border);
}

.jsini-theme-panel__title {
  flex: 1;
  font-weight: 600;
}

/* 묶음이 셋(Fluent · Classic · Bootstrap)이라 낮은 화면에서는 넘친다.
   머리말은 붙박이로 두고 목록만 구른다. */
.jsini-theme {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.75rem 1rem 1.25rem;
  overflow: hidden auto;
}

/*
  **줄들이 눌리지 않게 한다.** 세로 flex 의 자식은 기본으로 줄어들 수 있어서
  (`flex-shrink: 1`), 내용이 판보다 길어지는 순간 굴리는 대신 **전부 납작해진다.**

  실제로 밟았다 — 밝기·크기 칸의 높이가 34px 이어야 하는데 12px 로 찌그러져
  글자가 잘려 나왔다. 목록(grid)은 멀쩡해 보여서 더 찾기 나빴다.
*/
.jsini-theme > * { flex: 0 0 auto; }

/* 묶음 제목. **밑줄이 제목 아래**에 온다 — 데모가 그렇다.
   한동안 다음 묶음의 윗선으로 그렸는데, 그러면 선이 제목보다 위에 있어
   "무엇을 가르는 선인지" 가 반대로 읽힌다. */
.jsini-theme__group {
  font-size: var(--jsini-fs-md);
  font-weight: 700;
  padding-bottom: 0.4rem;
  border-bottom: 1px solid var(--jsini-border);
}

.jsini-theme__group:not(:first-child) { margin-top: 0.9rem; }

.jsini-theme__label {
  font-size: var(--jsini-fs-md);
  color: var(--jsini-text-muted);
  margin-top: 0.35rem;
}

/* ── 한 줄에서 하나만 고르는 것 (밝기 · 크기) ─────────────

   칸이 붙어 있는 한 덩어리다. 낱개 단추를 나란히 두는 것과 다르다 —
   붙어 있으면 **셋 중 하나** 라는 것이 생김새로 드러난다.

   폭을 늘리지 않는다(`width: max-content`). 데모도 판 너비의 절반쯤에서
   끝난다. 늘리면 글자 두 자짜리 칸이 판 끝까지 뻗어 허전해 보인다.
   ---------------------------------------------------------- */

.jsini-theme__segmented {
  display: flex;
  width: max-content;
  max-width: 100%;
  border: 1px solid var(--jsini-border);
  border-radius: 0.25rem;
  overflow: hidden;
}

/* 크기 칸은 **여섯**이라 판 너비를 다 쓴다.

   밝기(Light/Dark)는 둘이라 `width: max-content` 로 두어도 판의 절반쯤에서
   끝나는데, 다섯을 그렇게 두면 판을 넘어가 `overflow: hidden` 에 잘린다 —
   마지막 칸(「크게」)이 사라져도 오류가 안 나서 눈치채기 어렵다.
   그래서 크기 줄만 폭을 채우고 칸을 고르게 나눈다. */
.jsini-theme__segmented--fill {
  width: 100%;
}

.jsini-theme__seg {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-width: 5.2rem;
  padding: 0.45rem 0.7rem;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: var(--jsini-fs-md);
  cursor: pointer;
}

/* 채우는 줄의 칸.

   `min-width: 0` 이 있어야 한다 — flex 항목의 기본 최소 폭이 내용 크기라,
   그것 없이는 여섯 칸이 안 줄어들어 판을 넘어간다.

   글자를 사다리에서 두 단 내린다(`--jsini-fs-2xs`). 칸이 여섯이 되면서
   하나가 50px 도 안 되는데, 「크게」를 골랐을 때 `--jsini-fs-xs` 로 두면
   네 글자(「가장작게」)가 그 칸을 넘긴다. */
.jsini-theme__segmented--fill > .jsini-theme__seg {
  flex: 1 1 0;
  min-width: 0;
  padding: 0.45rem 0.15rem;
  font-size: var(--jsini-fs-2xs);
  white-space: nowrap;
}

.jsini-theme__seg + .jsini-theme__seg {
  border-left: 1px solid var(--jsini-border);
}

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

/* 고른 칸은 옅은 면으로 칠한다. 데모가 목록에서 쓰는 것과 같은 표시라
   판 안에서 「고른 것」이 늘 같은 모양으로 보인다. */
.jsini-theme__seg--on {
  background: var(--jsini-theme-picked);
  font-weight: 700;
}

/* ── 색 네모 + 이름 목록 (Preset · Classic · Bootstrap) ────

   셋 다 같은 생김새다 — 데모가 그렇고, 실제로 같은 일을 한다.
   두 칸으로 두면 스물한 줄이 열한 줄로 줄어 굴리지 않고 다 보인다.
   ---------------------------------------------------------- */

.jsini-theme__list {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.25rem;
}

.jsini-theme__item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
  padding: 0.45rem 0.5rem;
  border: 0;
  border-radius: 0.375rem;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: var(--jsini-fs-md);
  text-align: left;
  cursor: pointer;
}

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

/* 고른 것은 **면으로** 칠한다. 예전에는 테두리를 두르고 굵게 했는데,
   테두리를 두르면 그 줄만 키가 커져 목록이 들썩인다. */
.jsini-theme__item--on {
  background: var(--jsini-theme-picked);
  font-weight: 700;
}

/* 색 네모. 옅은 테두리를 함께 두른다 — Flatly 처럼 흰색에 가까운 색은
   테두리가 없으면 판 배경에 묻혀 네모가 없는 것처럼 보인다. */
.jsini-theme__chip {
  flex: 0 0 auto;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 0.3rem;
  background: var(--swatch);
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / 10%);
}

/* 이름이 길면 줄이지 않고 …로 자른다. 두 칸이라 자리가 넉넉하지 않다. */
.jsini-theme__name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.jsini-theme__custom {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

/* 글자 칸이 남는 자리를 다 먹는다. 데모의 `#hex` 칸이 그렇다. */
.jsini-theme__hex { flex: 1 1 auto; min-width: 0; }

.jsini-theme__picker {
  flex: 0 0 auto;
  width: 2.1rem;
  height: 2.1rem;
  padding: 0;
  border: 1px solid var(--jsini-border);
  border-radius: 0.25rem;
  background: none;
  cursor: pointer;
}

/* 도구줄 아이콘 두 개 더. 나머지는 위 아이콘 묶음에 있다. */

.jsini-icon-plus {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8.545 2.545a.545.545 0 1 0-1.09 0v4.91h-4.91a.545.545 0 1 0 0 1.09h4.91v4.91a.545.545 0 1 0 1.09 0v-4.91h4.91a.545.545 0 1 0 0-1.09h-4.91z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8.545 2.545a.545.545 0 1 0-1.09 0v4.91h-4.91a.545.545 0 1 0 0 1.09h4.91v4.91a.545.545 0 1 0 1.09 0v-4.91h4.91a.545.545 0 1 0 0-1.09h-4.91z'/%3E%3C/svg%3E");
}

.jsini-icon-excel {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 13h9a.5.5 0 0 1 .09.992L12.5 14h-9a.5.5 0 0 1-.09-.992zh9zM7.91 1.008 8 1a.5.5 0 0 1 .492.41l.008.09v8.792l2.682-2.681a.5.5 0 0 1 .638-.058l.07.058a.5.5 0 0 1 .057.638l-.058.069-3.535 3.536a.5.5 0 0 1-.638.057l-.07-.057-3.535-3.536a.5.5 0 0 1 .638-.765l.069.058L7.5 10.292V1.5a.5.5 0 0 1 .41-.492L8 1z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 13h9a.5.5 0 0 1 .09.992L12.5 14h-9a.5.5 0 0 1-.09-.992zh9zM7.91 1.008 8 1a.5.5 0 0 1 .492.41l.008.09v8.792l2.682-2.681a.5.5 0 0 1 .638-.058l.07.058a.5.5 0 0 1 .057.638l-.058.069-3.535 3.536a.5.5 0 0 1-.638.057l-.07-.057-3.535-3.536a.5.5 0 0 1 .638-.765l.069.058L7.5 10.292V1.5a.5.5 0 0 1 .41-.492L8 1z'/%3E%3C/svg%3E");
}

/* 「위로 가기」 단추 안의 그림. **크기·색은 여기서 안 적는다** — 위의
   `[class^='jsini-icon-']` 묶음이 이미 적어 두었고, 떠다니는 단추 안에서는
   그쪽 규칙이 한 뼘 더 키운다(`.jsini-shell__fab [class^='jsini-icon-']`).
   한동안 여기서 1.6rem 을 박아 두었는데, 그러면 이 그림만 단추 밖에서도
   커져서 묶음의 `--jsini-icon-size` 가 안 먹었다. */

.jsini-icon-arrow-up {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M7.646 2.646a.5.5 0 0 1 .708 0l4 4a.5.5 0 0 1-.708.708L8.5 4.207V13.5a.5.5 0 0 1-1 0V4.207L4.354 7.354a.5.5 0 1 1-.708-.708z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M7.646 2.646a.5.5 0 0 1 .708 0l4 4a.5.5 0 0 1-.708.708L8.5 4.207V13.5a.5.5 0 0 1-1 0V4.207L4.354 7.354a.5.5 0 1 1-.708-.708z'/%3E%3C/svg%3E");
}

/* 표 안 관리 칸. 연필과 휴지통은 아이콘만으로도 뜻이 갈리는 몇 안 되는 짝이다. */

.jsini-icon-edit {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M14.236 1.764a2.62 2.62 0 0 0-3.707 0L2.657 9.635a2.96 2.96 0 0 0-.772 1.355l-.87 3.386a.5.5 0 0 0 .61.608l3.385-.869c.512-.131.98-.398 1.354-.772l7.872-7.872a2.62 2.62 0 0 0 0-3.707m-3 .707a1.621 1.621 0 1 1 2.293 2.293l-.779.779-2.293-2.293zM9.75 3.957l2.293 2.293-6.386 6.386a1.95 1.95 0 0 1-.896.51l-2.567.66.66-2.567c.086-.34.263-.649.51-.896z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M14.236 1.764a2.62 2.62 0 0 0-3.707 0L2.657 9.635a2.96 2.96 0 0 0-.772 1.355l-.87 3.386a.5.5 0 0 0 .61.608l3.385-.869c.512-.131.98-.398 1.354-.772l7.872-7.872a2.62 2.62 0 0 0 0-3.707m-3 .707a1.621 1.621 0 1 1 2.293 2.293l-.779.779-2.293-2.293zM9.75 3.957l2.293 2.293-6.386 6.386a1.95 1.95 0 0 1-.896.51l-2.567.66.66-2.567c.086-.34.263-.649.51-.896z'/%3E%3C/svg%3E");
}

.jsini-icon-trash {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M7 3h2a1 1 0 0 0-2 0M6 3a2 2 0 1 1 4 0h4a.5.5 0 0 1 0 1h-.564l-1.205 8.838A2.5 2.5 0 0 1 9.754 15H6.246a2.5 2.5 0 0 1-2.477-2.162L2.564 4H2a.5.5 0 0 1 0-1zm1 3.5a.5.5 0 0 0-1 0v5a.5.5 0 0 0 1 0zM9.5 6a.5.5 0 0 1 .5.5v5a.5.5 0 0 1-1 0v-5a.5.5 0 0 1 .5-.5m-4.74 6.703A1.5 1.5 0 0 0 6.246 14h3.508a1.5 1.5 0 0 0 1.487-1.297L12.427 4H3.573z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M7 3h2a1 1 0 0 0-2 0M6 3a2 2 0 1 1 4 0h4a.5.5 0 0 1 0 1h-.564l-1.205 8.838A2.5 2.5 0 0 1 9.754 15H6.246a2.5 2.5 0 0 1-2.477-2.162L2.564 4H2a.5.5 0 0 1 0-1zm1 3.5a.5.5 0 0 0-1 0v5a.5.5 0 0 0 1 0zM9.5 6a.5.5 0 0 1 .5.5v5a.5.5 0 0 1-1 0v-5a.5.5 0 0 1 .5-.5m-4.74 6.703A1.5 1.5 0 0 0 6.246 14h3.508a1.5 1.5 0 0 0 1.487-1.297L12.427 4H3.573z'/%3E%3C/svg%3E");
}

/* 미리보기. 눈은 「고치지 않고 보기만 한다」를 연필·휴지통과 한눈에 가른다. */
.jsini-icon-eye {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2.984 8.625v.003a.5.5 0 0 1-.612.355c-.431-.114-.355-.611-.355-.611l.018-.062s.026-.084.047-.145a6.7 6.7 0 0 1 1.117-1.982C4.096 5.089 5.605 4 8 4s3.904 1.089 4.802 2.183a6.7 6.7 0 0 1 1.117 1.982 4 4 0 0 1 .06.187l.003.013v.004l.001.002a.5.5 0 0 1-.966.258l-.001-.004-.008-.025-.035-.109a5.696 5.696 0 0 0-.945-1.674C11.286 5.912 10.045 5 8 5s-3.285.912-4.028 1.817a5.7 5.7 0 0 0-.945 1.674l-.035.109zM8 7a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5M6.5 9.5a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2.984 8.625v.003a.5.5 0 0 1-.612.355c-.431-.114-.355-.611-.355-.611l.018-.062s.026-.084.047-.145a6.7 6.7 0 0 1 1.117-1.982C4.096 5.089 5.605 4 8 4s3.904 1.089 4.802 2.183a6.7 6.7 0 0 1 1.117 1.982 4 4 0 0 1 .06.187l.003.013v.004l.001.002a.5.5 0 0 1-.966.258l-.001-.004-.008-.025-.035-.109a5.696 5.696 0 0 0-.945-1.674C11.286 5.912 10.045 5 8 5s-3.285.912-4.028 1.817a5.7 5.7 0 0 0-.945 1.674l-.035.109zM8 7a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5M6.5 9.5a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0'/%3E%3C/svg%3E");
}

/* 나무 관리 칸의 「하위 추가」. 아래 띠의 「등록」과 **다른 그림이어야 한다** —
   같은 더하기를 쓰면 한 줄 안에서 「여기 아래」와 「최상위」가 구분되지 않는다.
   꺾인 선이 「한 단 아래」를 말한다. */
.jsini-icon-child-plus {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2 4.5A2.5 2.5 0 0 1 4.5 2h7A2.5 2.5 0 0 1 14 4.5v7a2.5 2.5 0 0 1-2.5 2.5h-7A2.5 2.5 0 0 1 2 11.5zM4.5 3A1.5 1.5 0 0 0 3 4.5v7A1.5 1.5 0 0 0 4.5 13h7a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 11.5 3zM8 4.5a.5.5 0 0 1 .5.5v2.5H11a.5.5 0 0 1 0 1H8.5V11a.5.5 0 0 1-1 0V8.5H5a.5.5 0 0 1 0-1h2.5V5a.5.5 0 0 1 .5-.5'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2 4.5A2.5 2.5 0 0 1 4.5 2h7A2.5 2.5 0 0 1 14 4.5v7a2.5 2.5 0 0 1-2.5 2.5h-7A2.5 2.5 0 0 1 2 11.5zM4.5 3A1.5 1.5 0 0 0 3 4.5v7A1.5 1.5 0 0 0 4.5 13h7a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 11.5 3zM8 4.5a.5.5 0 0 1 .5.5v2.5H11a.5.5 0 0 1 0 1H8.5V11a.5.5 0 0 1-1 0V8.5H5a.5.5 0 0 1 0-1h2.5V5a.5.5 0 0 1 .5-.5'/%3E%3C/svg%3E");
}

/* 나무 아래 띠의 펼치기·접기 한 짝. 삼각형이 벌어지면 펼치기,
   모이면 접기다 — 아이콘 하나로 상태까지 말한다. */
.jsini-icon-unfold {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2 2.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5m0 12a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5m7-4a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5M4.5 12a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7M5 6.5a.5.5 0 0 0-1 0V8H2.5a.5.5 0 0 0 0 1H4v1.5a.5.5 0 0 0 1 0V9h1.5a.5.5 0 0 0 0-1H5z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2 2.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5m0 12a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5m7-4a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5M4.5 12a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7M5 6.5a.5.5 0 0 0-1 0V8H2.5a.5.5 0 0 0 0 1H4v1.5a.5.5 0 0 0 1 0V9h1.5a.5.5 0 0 0 0-1H5z'/%3E%3C/svg%3E");
}

.jsini-icon-fold {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2 2.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5m0 12a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5m7-4a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5M8 8.5a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0m-6 0a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 0-1h-4a.5.5 0 0 0-.5.5'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2 2.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5m0 12a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5m7-4a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5M8 8.5a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0m-6 0a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 0-1h-4a.5.5 0 0 0-.5.5'/%3E%3C/svg%3E");
}

/* ── 공지 팝업 (NoticePopup) ───────────────────────────────

   사용자에게 공지가 실제로 보이는 창. 관리 화면의 미리보기가 같은 것을 쓴다.

   본문이 서식 편집기로 쓴 HTML 이라 **여기서 그 서식의 모양을 정한다.**
   테마 CSS 는 DevExpress 부품만 겨누므로 `<h2>`·`<ul>`·`<table>` 같은 것은
   브라우저 기본값으로 나온다 — 그대로 두면 제목이 본문보다 두 배 크고
   목록에 점이 안 붙는다(테마 reset 이 `list-style` 을 지운다).
   ---------------------------------------------------------- */

/* ── 끌 수 있는 창의 머리는 잡는 자리다 ──────────────────────

   `AllowDrag` 를 켜면 **DevExpress 는 동작만 붙이고 표시는 아무것도 남기지
   않는다.** 떠 있는 화면에서 재어 보면 머리의 클래스는

     dxbl-modal-header dxbl-popup-header

   두 개뿐이고, 그 요소에 걸리는 `cursor` 규칙이 **하나도 없다**(브라우저
   기본값 `default` 로 보인다). 잡을 수 있다는 표시가 없으면 아무도 잡아
   보지 않으므로, 기능이 있는데 없는 것과 같아진다.

   [`.dxbl-drag` 를 겨누면 안 된다 — 한 번 그렇게 하고 안 걸렸다]

   테마 CSS 에 그 클래스를 겨눈 규칙이 여럿 있어서 DevExpress 가 붙여 주는
   줄 알았는데, **붙지 않는다.** 그 규칙들은

     .dxbl-popup-header > .dxbl-modal-header.dxbl-drag

   처럼 **겹친 구조**를 전제하는데 지금은 두 클래스가 한 요소에 함께 있다.
   어느 쪽도 걸리지 않는 죽은 규칙이다.

   그러니 표시는 **끌기를 켠 쪽이 함께 붙인다.** 로그인 뒤 공지 팝업은
   `CommPopup` 을 거치므로 그 부품이 스스로 붙인다(화면이 `HeaderCssClass` 를
   주면 덮지 않고 잇는다). 로그인 화면의 정적 공지 팝업
   (`.jsini-snotice`)도 **같은 클래스**를 쓴다. 그 창은 DevExpress 가 아니라
   theme.js 가 끄므로 `dxbl-popup-dragging` 이 안 붙어, 끄는 동안의 표시는
   위 선택자 목록의 `.jsini-snotice--dragging` 이 받는다.

   [`!important` 는 남겨 둔다]

   지금 이 자리를 다투는 규칙은 없다(재어 보았다). 그래도 남기는 이유는
   **테마가 스물둘이고 사용자가 바꿀 수 있다**는 것이다. Bootstrap 계열
   테마에는 위의 그 규칙이 실려 있어, 언젠가 DevExpress 가 구조를 되돌리면
   그때부터 조용히 안 걸린다. 커서 하나를 못 박는 값은 싸다. */
.jsini-drag-head {
  cursor: move !important;

  /* 글자 위에서 끌면 글이 선택되면서 창이 안 따라온다. 머리에 있는 것은
     제목뿐이라 골라 담을 일이 없다. */
  user-select: none;
}

/* 잡고 있는 동안은 손이 닫힌 모양이다. 「지금 내가 들고 있다」가 손에 남아,
   놓을 자리를 고르는 동안 창을 눈으로 좇지 않아도 된다.

   `:active` 만으로는 모자란다 — DevExpress 가 끄는 동안 포인터를 가로채므로
   머리에서 손이 떨어진 것으로 잡힐 수 있다. 끄는 동안 창에 붙는
   `dxbl-popup-dragging` 도 함께 본다. */
.jsini-drag-head:active,
.dxbl-popup-dragging .jsini-drag-head,
.dxbl-popup-dragging.dxbl-popup .jsini-drag-head,
.jsini-snotice--dragging .jsini-drag-head {
  cursor: grabbing !important;
}

/* 머리 안의 단추(닫기 X)는 **누르는** 자리다. 머리에서 물려받은 `move` 를
   되돌린다 — 옮기는 모양이면 닫을 수 있는지 헷갈린다. */
.jsini-drag-head button,
.jsini-drag-head .dxbl-btn {
  cursor: pointer !important;
}

.jsini-notice-popup__title {
  min-width: 0;
}

.jsini-notice-popup__meta {
  margin-top: 0.15rem;
  font-size: var(--jsini-fs-xs);
  font-weight: 400;
  color: var(--jsini-text-muted);
}

.jsini-notice-popup__foot {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.jsini-notice-popup__gap { flex: 1; }

/* 한 줄짜리 공지도 있다. 최소 높이가 없으면 창이 제목 줄만 하게 쪼그라들어
   공지가 아니라 알림처럼 보인다. 최대 높이는 창(86vh)이 이미 잡고 있고,
   여기서는 넘칠 때 본문만 구르게 한다 — 창째로 구르면 제목이 밀려 올라간다. */
.jsini-notice-popup__body {
  min-height: 8.5rem;
  max-height: 56vh;
  overflow-y: auto;
  line-height: 1.7;
}

.jsini-notice-popup__body > :first-child { margin-top: 0; }
.jsini-notice-popup__body > :last-child { margin-bottom: 0; }

.jsini-notice-popup__body p { margin: 0 0 0.6rem; }

.jsini-notice-popup__body h1,
.jsini-notice-popup__body h2,
.jsini-notice-popup__body h3,
.jsini-notice-popup__body h4 {
  margin: 0.9rem 0 0.4rem;
  font-weight: 600;
}

.jsini-notice-popup__body h1 { font-size: var(--jsini-fs-xl); }
.jsini-notice-popup__body h2 { font-size: var(--jsini-fs-lg); }
.jsini-notice-popup__body h3,
.jsini-notice-popup__body h4 { font-size: var(--jsini-fs-md); }

.jsini-notice-popup__body ul,
.jsini-notice-popup__body ol {
  margin: 0 0 0.6rem;
  padding-left: 1.4rem;
  list-style: revert;
}

.jsini-notice-popup__body blockquote {
  margin: 0 0 0.6rem;
  padding-left: 0.75rem;
  border-left: 3px solid var(--jsini-border);
  color: var(--jsini-text-muted);
}

.jsini-notice-popup__body pre {
  margin: 0 0 0.6rem;
  padding: 0.5rem 0.75rem;
  overflow-x: auto;
  background: var(--jsini-surface);
  border-radius: 0.25rem;
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: var(--jsini-fs-sm);
}

.jsini-notice-popup__body img {
  max-width: 100%;
  height: auto;
  border-radius: 0.25rem;
}

.jsini-notice-popup__body a {
  color: var(--jsini-accent-text);
  text-decoration: underline;
}

.jsini-notice-popup__body table {
  width: 100%;
  margin: 0 0 0.6rem;
  border-collapse: collapse;
}

.jsini-notice-popup__body th,
.jsini-notice-popup__body td {
  padding: 0.25rem 0.5rem;
  border: 1px solid var(--jsini-border);
  text-align: left;
}

.jsini-notice-popup__body th {
  font-weight: 600;
  background: var(--jsini-surface);
}

/* 공지가 여럿일 때 어디쯤인지 알려 주는 점들. 지금 보는 것만 길쭉해진다 —
   색으로만 가르면 옅은 테마에서 켜진 점과 꺼진 점이 구분되지 않는다. */
.jsini-notice-popup__dots {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

.jsini-notice-popup__dot {
  width: 0.4rem;
  height: 0.4rem;
  padding: 0;
  border: 0;
  border-radius: 9999px;
  background: color-mix(in srgb, currentcolor 30%, transparent);
  cursor: pointer;
  transition: width 0.15s, background-color 0.15s;
}

.jsini-notice-popup__dot:hover {
  background: color-mix(in srgb, currentcolor 55%, transparent);
}

.jsini-notice-popup__dot.is-on {
  width: 1rem;
  background: var(--jsini-accent);
}

/* 첨부. 이름을 자르지 않고 줄바꿈으로 흘린다 — 실제 첨부에 마흔 자짜리가
   있어 `…` 로 가리면 무엇을 내려받는지 알 수 없고, 확장자가 잘리면 더 나쁘다.
   파일명에는 띄어쓰기가 없는 경우가 많아 `word-break` 는 끊을 자리를 못 찾는다. */
.jsini-notice-popup__files {
  margin-top: 0.9rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--jsini-border);
}

.jsini-notice-popup__files-head {
  margin-bottom: 0.4rem;
  font-size: var(--jsini-fs-xs);
  font-weight: 600;
  color: var(--jsini-text-muted);
}

.jsini-notice-popup__file-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0;
  padding: 0;
  list-style: none;
  min-width: 0;
}

.jsini-notice-popup__file-list li { max-width: 100%; }

.jsini-notice-popup__file {
  display: inline-flex;
  align-items: flex-start;
  gap: 0.35rem;
  max-width: 100%;
  padding: 0.2rem 0.5rem;
  border: 1px solid var(--jsini-border);
  border-radius: 0.25rem;
  font-size: var(--jsini-fs-xs);
  text-decoration: none;
  color: inherit;
}

.jsini-notice-popup__file:hover {
  border-color: var(--jsini-accent);
  color: var(--jsini-accent-text);
}

.jsini-notice-popup__file-name {
  min-width: 0;
  word-break: break-all;
}

.jsini-notice-popup__file-size {
  flex: 0 0 auto;
  white-space: nowrap;
  color: var(--jsini-text-muted);
}

/* ── 로그인 화면의 공개 공지 팝업 (`.jsini-snotice`) ──────────

   [왜 DevExpress 팝업이 아닌가]

   이 팝업은 **회로가 붙기 전에** 보여야 한다(`PublicNoticePopup` 머리말).
   DxPopup 은 정적 렌더에서 껍데기만 내놓고 내용을 브라우저에서 만들기
   때문에 첫 HTML 에 실을 수 없다. 그래서 골격만 여기 다시 세웠다.

   **안에 들어가는 것은 로그인 뒤 팝업의 클래스를 그대로 쓴다**
   (`jsini-notice-popup__body` · `__files` · `__dots` · `__meta`).
   여기 적는 것은 바탕막·카드·머리띠·바닥띠 넷뿐이다.

   [치수는 눈대중이 아니다]

   떠 있는 DevExpress 팝업(Fluent Light)에서 잰 값을 옮겼다.

   | | 값 |
   |---|---|
   | 바탕막 | `rgba(0, 0, 0, 0.4)` |
   | 카드 | 모서리 8px, `0 32px 64px rgba(0,0,0,.24)` + `0 0 8px rgba(0,0,0,.2)` |
   | 머리띠 | `24px 24px 12px`, 20px/600 |
   | 본문 | `4px 24px` |
   | 바닥띠 | `12px 24px 24px`, 사이 8px |

   테마를 바꿔도 이 팝업은 로그인 화면에만 뜨고 그 화면은 Fluent 계열
   변수(`--jsini-*`)로만 그려지므로, 색은 변수로 두고 치수만 박는다. */
.jsini-snotice {
  position: fixed;
  inset: 0;
  z-index: 1500;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
}

/* `hidden` 을 flex 가 이긴다 — 속성만으로는 안 감춰진다. */
.jsini-snotice[hidden] {
  display: none;
}

.jsini-snotice__back {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
}

/* 바탕을 눌러도 닫히지 않는다. 로그인 뒤 팝업(`CommPopup`)과 같다 —
   공지는 읽어야 하는 것이라 스치듯 닫히면 안 된다. */
.jsini-snotice__card {
  position: relative;
  width: min(640px, calc(100vw - 2rem));
  max-height: 86vh;
  display: flex;
  flex-direction: column;

  /* 창의 표면이다 — 본문 카드(`--jsini-surface`)가 아니라 **한 단 떠 있는**
     쪽을 쓴다. 로그인 뒤 팝업이 같은 값으로 그려지므로(DevExpress 토큰),
     여기만 다른 색이면 같은 공지가 로그인 앞뒤로 달라 보인다. */
  background: var(--jsini-surface-raised);
  color: var(--jsini-text);
  border-radius: 8px;
  box-shadow:
    0 32px 64px rgba(0, 0, 0, 0.24),
    0 0 8px rgba(0, 0, 0, 0.2);
}

/* 닫기(×). 머리띠가 공지마다 따로라 카드에 붙인다. 오른쪽 여백은 머리띠와
   같은 24px 이고, 위쪽은 제목 첫 줄과 눈높이가 맞도록 조금 올렸다. */
.jsini-snotice__x {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 1;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: none;
  border-radius: 4px;
  background: transparent;
  color: var(--jsini-text-muted);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
}

.jsini-snotice__x:hover {
  background: color-mix(in srgb, var(--jsini-text) 8%, transparent);
  color: var(--jsini-text);
}

/* 공지 한 건. 한 번에 하나만 보인다(`hidden` 을 JS 가 옮긴다). */
.jsini-snotice__page {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.jsini-snotice__page[hidden] {
  display: none;
}

.jsini-snotice__head {
  flex: 0 0 auto;
  /* 오른쪽만 넉넉히 — 닫기 단추가 앉을 자리다. */
  padding: 24px 3.5rem 12px 24px;
  font-size: 20px;
  font-weight: 600;
}

/* 공지가 길면 **본문만** 구른다. 카드째로 구르면 제목이 밀려 올라간다. */
.jsini-snotice__scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 4px 24px;
}

/* 본문 안쪽은 로그인 뒤 팝업과 같은 규칙이 그린다. 그쪽이 자기 높이를
   직접 잡고 있어(min/max-height) 여기서는 풀어 준다 — 카드가 이미
   86vh 로 묶여 있고 구르는 자리도 위 상자다. */
.jsini-snotice__scroll .jsini-notice-popup__body {
  max-height: none;
  overflow-y: visible;
}

.jsini-snotice__foot {
  flex: 0 0 auto;
  padding: 12px 24px 24px;
}

/* 끌어 옮기는 동안 글이 선택되지 않게 한다. 머리에는 `user-select: none` 이
   이미 걸려 있지만(`jsini-drag-head`), 포인터가 머리를 벗어나 본문이나 뒤의
   로그인 폼 위로 지나가면 **그쪽 글자가 잡힌다** — 창을 놓고 나면 엉뚱한
   글이 선택된 채로 남는다. */
.jsini-snotice--dragging,
.jsini-snotice--dragging * {
  user-select: none;
}

/* 「오늘 하루 보지 않기」. DxCheckBox 자리라 글자 크기를 본문에 맞춘다. */
.jsini-snotice__check {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  cursor: pointer;
  font-size: var(--jsini-fs-md);
}

.jsini-snotice__check input {
  margin: 0;
  cursor: pointer;
}

/* 「공지 2 / 3」. 제목 아래 곁줄이라 색·크기는 `__meta` 가 정한다. */
.jsini-snotice__count:empty {
  display: none;
}

/* 단추 둘(이전 · 다음/닫기). DevExpress 기본 단추에서 잰 값이다 —
   여백 5px 12px, 모서리 4px, 14px/600. */
.jsini-snotice__btn {
  flex: 0 0 auto;
  padding: 5px 12px;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: var(--jsini-text);
  font: inherit;
  font-size: var(--jsini-fs-md);
  font-weight: 600;
  cursor: pointer;
}

.jsini-snotice__btn:hover:not(:disabled) {
  background: color-mix(in srgb, var(--jsini-text) 8%, transparent);
}

.jsini-snotice__btn:disabled {
  color: var(--jsini-text-muted);
  cursor: default;
}

.jsini-snotice__btn[hidden] {
  display: none;
}

.jsini-snotice__btn--primary {
  background: var(--jsini-accent);
  color: #fff;
}

.jsini-snotice__btn--primary:hover:not(:disabled) {
  background: color-mix(in srgb, var(--jsini-accent) 88%, #000);
}

/* ── 잠금화면 (D7) ────────────────────────────────────────────
   레이아웃 격자 바깥에 덮는다. 격자 안에 두면 본문 자리에만 깔리고
   헤더·사이드바가 그대로 눌린다.

   z-index 를 크게 두는 이유: DevExpress 의 드롭다운·팝업이 1000 대를 쓴다.
   덮개가 그보다 낮으면 열어 둔 차림표가 덮개 **위로** 뜬다. */
.jsini-lock {
  position: fixed;
  inset: 0;
  z-index: 20000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--jsini-bg);
  /* 뒤에 있던 화면이 비쳐 보이면 「덮었다」가 아니라 「멈췄다」로 읽힌다.
     불투명하게 덮되, 살짝 비치게 두어 어디로 돌아가는지는 알 수 있게 한다. */
  background: color-mix(in srgb, var(--jsini-bg) 92%, transparent);
  backdrop-filter: blur(6px);
}

.jsini-lock__panel {
  width: min(22rem, calc(100vw - 2rem));
  padding: 2rem 1.75rem;
  border: 1px solid var(--jsini-border);
  border-radius: 0.75rem;
  /* 다른 창과 같은 표면. 이 판은 바탕막 위에 떠 있으므로 본문 카드가 아니다. */
  background: var(--jsini-surface-raised);
  box-shadow: 0 1.5rem 3rem rgb(0 0 0 / 25%);
  text-align: center;
}

.jsini-lock__icon {
  width: 2.5rem;
  height: 2.5rem;
  margin: 0 auto 0.75rem;
  background-color: var(--jsini-accent);
}

.jsini-lock__title {
  margin: 0 0 0.25rem;
  font-size: var(--jsini-fs-xl);
}

.jsini-lock__who {
  margin: 0 0 1.25rem;
  color: var(--jsini-text-muted);
}

.jsini-lock__form {
  display: grid;
  gap: 0.6rem;
}

/* ── 푸는 길이 둘이다 (비밀번호 · 지문) ──────────────────────
   **마크업을 두 벌 두지 않는다.** 차례만 바뀌고 안에 든 것은 같다 —
   로그인 화면의 `.jsini-login__methods` 와 같은 수법이다. */
.jsini-lock__methods {
  display: grid;
  gap: 1rem;
}

/* 갈라 주는 줄. 두 길이 붙어 있으면 아래 단추가 위 단추의 짝으로 읽힌다.
   기본 차례(비밀번호 먼저)에서는 지문이 아래라 **위쪽**에 줄을 긋는다. */
.jsini-lock__passkey {
  padding-top: 1rem;
  border-top: 1px solid var(--jsini-border);
}

.jsini-lock__passkey-btn {
  width: 100%;
}

/* 지문을 먼저 묻는 기기. 단추가 위로 올라가므로 줄도 **아래쪽**으로 옮긴다.
   안 옮기면 카드 맨 위에 걸린 줄 하나가 제목과 단추를 갈라놓는다. */
.jsini-lock__methods--passkey-first .jsini-lock__passkey {
  order: -1;
  padding-top: 0;
  padding-bottom: 1rem;
  border-top: 0;
  border-bottom: 1px solid var(--jsini-border);
}

.jsini-lock__hint {
  margin: 1.25rem 0 0.25rem;
  color: var(--jsini-text-muted);
  font-size: var(--jsini-fs-sm);
  line-height: 1.5;
}

.jsini-lock__logout {
  margin: 0;
}

/* ── AI 대화 (D11) ────────────────────────────────────────────
   화면(/site/ai/chat)과 헤더 서랍이 같은 부품을 쓰므로 스타일도 한 벌이다.
   높이는 부모가 정한다 — 서랍은 화면 높이, 화면은 site.css 의 .site-ai-chat 이 정한다. */
.jsini-chat {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}

/* [지금 누가 답하는가]

   자동 전환 때문에 **고른 것과 실제로 답하는 것이 다를 수 있다.** 전환 안내는
   '바뀐 순간' 에만 뜨는 곁말이라, 지금 상태를 늘 보여 주는 자리가 따로 필요하다.

   대화 기록 **밖**에 둔다 — 안에 두면 굴려 올렸을 때 사라지고, 그때가 바로
   "지금 무슨 AI 지?" 를 묻는 순간이다. */
.jsini-chat__who {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0 0.15rem 0.4rem;
  color: var(--jsini-text-muted);
  /* 사다리를 쓴다. rem 으로 박으면 이 글자만 크기 여섯 단계를 안 따라온다. */
  font-size: var(--jsini-fs-xs);
}

.jsini-chat__who-dot {
  flex: 0 0 auto;
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  /* 아직 실제로 답한 것을 모르는 상태(설정상의 기본값). 속을 비워 둔다. */
  border: 1px solid currentColor;
}

/* 실제로 답한 공급자를 아는 상태. 채워서 '살아 있는 값' 임을 가른다. */
.jsini-chat__who-dot.is-live {
  border-color: var(--jsini-accent);
  background: var(--jsini-accent);
}

.jsini-chat__who-text {
  /* 서랍은 좁다. 이름이 길면 줄이 늘지 않고 끝을 줄인다 —
     한 줄이 두 줄이 되면 대화 자리가 그만큼 줄어든다. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.jsini-chat__log {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0.75rem;
  border: 1px solid var(--jsini-border);
  border-radius: 0.5rem;
  background: var(--jsini-bg);
}

.jsini-chat__empty {
  margin: 2rem 0;
  color: var(--jsini-text-muted);
  text-align: center;
}

.jsini-chat__row {
  display: flex;
  margin-bottom: 0.6rem;
}

.jsini-chat__row--me { justify-content: flex-end; }
.jsini-chat__row--ai { justify-content: flex-start; }

.jsini-chat__bubble {
  max-width: 80%;
  padding: 0.55rem 0.75rem;
  border-radius: 0.6rem;
  /* 줄바꿈을 살린다. 답을 HTML 로 그리지 않으므로 이 한 줄이 서식의 전부다. */
  white-space: pre-wrap;
  word-break: break-word;
  line-height: 1.55;
}

.jsini-chat__row--me .jsini-chat__bubble {
  background: var(--jsini-accent);
  color: #fff;
}

.jsini-chat__row--ai .jsini-chat__bubble {
  background: var(--jsini-surface);
  border: 1px solid var(--jsini-border);
}

/* 안내는 말풍선이 아니다 — 대화 문맥에 들어가지 않는 곁말이라 다르게 보인다. */
.jsini-chat__notices {
  margin-top: 0.5rem;
}

.jsini-chat__notice {
  padding: 0.35rem 0.6rem;
  margin-bottom: 0.3rem;
  border-left: 3px solid var(--jsini-accent);
  color: var(--jsini-text-muted);
  font-size: var(--jsini-fs-sm);
}

.jsini-chat__typing {
  padding: 0.35rem 0.2rem;
  color: var(--jsini-text-muted);
  font-size: var(--jsini-fs-sm);
}

/* 글상자가 여러 줄이라 **아래를 맞춘다.** 가운데로 두면 글이 길어질수록
   「전송」이 글상자 한가운데로 떠올라, 눈이 찾는 자리(오른쪽 아래 구석)에서
   벗어난다. 줄이 하나일 때의 모습은 양쪽 끝이 같으므로 달라지지 않는다. */
.jsini-chat__input {
  display: flex;
  align-items: flex-end;
  gap: 0.4rem;
  padding-top: 0.6rem;
}

/* `min-width: 0` 이 있어야 긴 한 줄이 「전송」을 밀어내지 않는다 —
   주제 고르개와 같은 까닭이다(flex 항목은 제 내용보다 작아지지 않는다). */
.jsini-chat__box {
  flex: 1;
  min-width: 0;
}

/* 손잡이를 지운다. `ResizeMode="Auto"` 가 이미 내용에 맞춰 늘리는데
   손으로 끈 높이가 남으면 **그다음 글에도 그 높이가 따라붙는다** — 한 줄만
   쓰려는데 여섯 줄짜리 칸이 서 있는 모양이 된다. 오른쪽 아래 구석의 그
   삼각형이 「전송」 바로 위에 겹쳐 보이는 것도 걸린다. */
.jsini-chat__box textarea {
  resize: none;
}

/* ── 주제 고르개 ─────────────────────────────────────────────
   대화 줄기를 고르고, 새로 열고, 이름을 고치고, 지운다.

   **한 줄로 끝낸다.** 이 부품은 26rem 짜리 서랍 안에도 들어가는데,
   목록을 판으로 펴면 정작 대화가 들어갈 자리가 없다. 고르개가 남는 폭을
   다 먹고(`flex: 1`) 단추 넷은 제 크기만 쓴다.

   아래에 선을 하나 긋는다 — 고르개와 대화는 다른 켜이고, 선이 없으면
   첫 말풍선이 고르개에 붙어 그 줄의 일부처럼 보인다. */
.jsini-chat__topics {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--jsini-border);
}

/* `min-width: 0` 이 있어야 긴 주제 이름이 단추를 밀어내지 않는다 —
   flex 항목은 기본으로 제 내용보다 작아지지 않는다. */
.jsini-chat__pick,
.jsini-chat__rename {
  flex: 1;
  min-width: 0;
}

/* ── AI 대화 서랍 ────────────────────────────────────────────
   본문 위에 겹친다. 닫혀도 DOM 에 남는다 — 지웠다 만들면 대화가 사라진다.
   `visibility` 를 함께 끄는 이유: transform 만으로 밀어 두면 화면 밖의 것이
   여전히 탭 순서에 남아 Tab 키가 보이지 않는 칸으로 들어간다.

   **아래 끝은 휴대폰에서 달라진다** — 화면 아래 띠에 글상자가 가려지지
   않도록 띠 높이만큼 올려 세운다. 까닭은 아래 ≤767px 묶음에 적었다. */
.jsini-ai-drawer {
  position: fixed;
  top: var(--jsini-header-height);
  right: 0;
  bottom: 0;
  width: min(26rem, 100vw);
  z-index: 1040;
  display: flex;
  flex-direction: column;
  background: var(--jsini-bg);
  border-left: 1px solid var(--jsini-border);
  box-shadow: -0.5rem 0 1.5rem rgb(0 0 0 / 12%);
  transform: translateX(100%);
  visibility: hidden;
  transition: transform 0.2s ease-in-out, visibility 0.2s;
}

.jsini-ai-drawer--open {
  transform: translateX(0);
  visibility: visible;
}

.jsini-ai-drawer__head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.5rem 0.5rem 0.9rem;
  border-bottom: 1px solid var(--jsini-border);
}

.jsini-ai-drawer__title {
  flex: 1;
  font-weight: 600;
}

.jsini-ai-drawer__body {
  flex: 1;
  min-height: 0;
  padding: 0.75rem;
}

.jsini-ai-drawer__foot {
  margin: 0;
  padding: 0 0.9rem 0.75rem;
  color: var(--jsini-text-muted);
  font-size: var(--jsini-fs-xs);
  line-height: 1.5;
}

/* ── 알림함 서랍 ────────────────────────────────────────────
   헤더의 종을 누르면 오른쪽에서 밀려 들어온다. 테마 서랍과 같은 자리·같은
   움직임이라 값도 그쪽에 맞췄다(덮개 28%, 0.22s, 왼쪽 그림자).

   [머리 · 몸통 · 발로 가른다]

   가운데 뜨는 창이던 때는 「모두 읽음 처리」가 카드 목록 **뒤에** 딸려 있어서
   알림이 쌓일수록 멀어졌다 — 정작 그 단추가 필요한 때가 그때인데. 굴리는
   것을 몸통에만 주고 발을 붙박이로 두면 몇 건이 쌓이든 단추는 제자리다.

   [휴대폰에서는 화면을 다 채운다]

   360px 에서 옆으로 밀려 들어오는 판은 이미 화면 전체다. 가장자리 여백만
   남겨 두면 창도 화면도 아닌 것이 되므로 아래에서 `inset: 0` 으로 편다.
   그때는 **헤더까지 덮으므로** 머리의 닫기 단추가 유일한 출구다.

   [닫혀 있을 때도 화면에 있다]

   `display: none` 이 아니라 화면 밖으로 밀어 둔다 — 그래야 미끄러져 보인다.
   `position: fixed` 라 자리를 차지하지도, 가로 스크롤을 만들지도 않는다.
   ---------------------------------------------------------- */

/* 덮개. 헤더 아래부터 깐다 — 헤더까지 덮으면 방금 누른 종으로 다시 닫을 수
   없다. 테마 서랍과 같은 까닭이고 같은 자리다. */
.jsini-note-scrim {
  position: fixed;
  inset: var(--jsini-header-height) 0 0 0;
  background: rgb(0 0 0 / 28%);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease-in-out, visibility 0.2s;
  z-index: 1042;
}

.jsini-note-scrim--on {
  opacity: 1;
  visibility: visible;
}

.jsini-note-drawer {
  position: fixed;
  top: var(--jsini-header-height);
  right: 0;
  bottom: 0;
  width: min(24rem, 100vw);
  display: flex;
  flex-direction: column;
  background: var(--jsini-bg);
  color: var(--jsini-text);
  border-left: 1px solid var(--jsini-border);
  box-shadow: -10px 0 28px rgb(0 0 0 / 14%);
  transform: translateX(100%);
  transition: transform 0.22s ease-in-out;
  z-index: 1043;
}

.jsini-note-drawer--on { transform: none; }

/* 접힌 서랍 안으로 탭 이동이 들어가면 안 된다 — 보이지도 않는 카드에
   초점이 가서 화면이 엉뚱한 곳으로 굴러간다. */
.jsini-note-drawer:not(.jsini-note-drawer--on) {
  visibility: hidden;
  transition: transform 0.22s ease-in-out, visibility 0.22s;
}

.jsini-note-drawer__head {
  display: flex;
  align-items: center;
  height: 3rem;
  min-height: 3rem;
  padding: 0 0.5rem 0 1rem;
  background: var(--jsini-surface);
  border-bottom: 1px solid var(--jsini-border);
}

.jsini-note-drawer__title {
  flex: 1;
  font-weight: 600;
}

/* **여기만 구른다.** `min-height: 0` 이 없으면 세로 flex 의 자식이 알맹이
   높이만큼 벌어져서, 카드가 늘어날 때 발(모두 읽음 처리)을 화면 밖으로
   밀어낸다 — 붙박이로 두려던 것이 도로 목록 뒤로 간다. */
.jsini-note-drawer__body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0.75rem;
}

.jsini-note-drawer-custom,
.jsini-note-drawer {
  font-size: var(--jsini-fs-md);
}

.jsini-note-drawer__status {
  font-size: var(--jsini-fs-sm);
}

.jsini-note-drawer__card {
  cursor: pointer;
  font-size: var(--jsini-fs-md);
}

/* 한 줄의 짜임 — [프로필(얼굴+단추)] [글]. 글 영역이 남은 폭을 온전히 쓴다. */
.jsini-note-drawer__row {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
}

/* 프로필 사진과 그 아래 읽음·삭제 단추 묶음 — 가로 공간을 아끼기 위해 단추를 얼굴 아래로 모은다. */
.jsini-note-drawer__profile {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
}

/* 앱 알림에 떴던 그림. **동그랗게 자른다** — 대개 사람 얼굴이라 휴대폰
   알림창의 모양과 같아야 「같은 그것」으로 읽힌다.

   `object-fit: cover` 가 없으면 세로로 긴 사진이 눌려 납작해진다.
   바탕을 깔아 두는 것은 **받아 오기 전의 빈 네모** 때문이다 — 없으면
   카드 위에 구멍이 뚫린 것처럼 보인다. */
.jsini-note-drawer__face {
  flex: 0 0 auto;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  object-fit: cover;
  background: var(--jsini-surface);
  border: 1px solid var(--jsini-border);
}

/* **`min-width: 0` 이 있어야 긴 글이 접힌다.** flex 자식의 기본 최소 너비가
   「내용 크기」라, 이것이 없으면 띄어쓰기 없는 긴 제목 하나가 줄을 통째로
   밀어내 단추를 카드 밖으로 내보낸다. */
.jsini-note-drawer__text {
  flex: 1;
  min-width: 0;
}

.jsini-note-drawer__text .card-title {
  font-size: var(--jsini-fs-md);
  font-weight: 600;
  line-height: 1.35;
  overflow-wrap: anywhere;
}

.jsini-note-drawer__text .card-text {
  font-size: var(--jsini-fs-sm);
  line-height: 1.45;
  overflow-wrap: anywhere;
}

.jsini-note-drawer__date {
  font-size: var(--jsini-fs-2xs);
  line-height: 1.25;
}

/* 데스크톱 알림 관리 단추(읽음 · 삭제). 평소에는 눈에 안 띄게 두고 카드에 손이 오면 또렷해진다 —
   목록에서 먼저 읽혀야 하는 것은 알림 내용이지 이 단추가 아니다.

   프로필 사진 아래에 배치하여 가로 공간의 활용도를 높인다.
   휴대폰에서는 스와이프로 처리하므로 데스크톱 폭에서만 보인다. */
.jsini-note-drawer__actions {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.15rem;
  --jsini-icon-size: calc(var(--jsini-fs-base) * 1.05);
}

.jsini-note-drawer__mark {
  flex: 0 0 auto;
  display: inline-flex;
  opacity: 0.55;
  transition: opacity 0.15s ease-in-out;
}

.jsini-note-drawer__mark .dxbl-btn {
  padding: 0.05rem;
  min-width: 0;
  line-height: 1;
}

.jsini-note-drawer__card:hover .jsini-note-drawer__mark,
.jsini-note-drawer__mark:hover {
  opacity: 1;
}

@media (hover: none) {
  .jsini-note-drawer__mark { opacity: 0.8; }
}

@media (max-width: 767px) {
  .jsini-note-drawer__actions {
    display: none;
  }
}

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

   ✓ 단추 하나로는 치우기의 절반만 된다 — 읽음은 「봤다」라서 「안 읽은 것만」을
   풀면 도로 나오고, 볼 일이 영영 없는 알림은 그래도 남는다. **오른쪽은 읽음,
   왼쪽은 삭제**다. 휴대폰 알림함에서 손이 이미 아는 짝이다.

   움직이는 값을 **전부 인라인 사용자 지정 속성으로** 받는다. 클래스로 받지
   않는 이유는 이 목록이 손짓 한 번에 다시 그려지기 때문이다 — Blazor 가 카드의
   `class` 를 제 손으로 쥐고 있어서, 손짓 중에 그 갱신이 한 번 끼면 **JS 가
   붙인 클래스가 소리 없이 지워지고 카드가 밀린 자리에 굳는다.** `style` 은
   Blazor 가 아예 그리지 않으므로(카드에 `style` 속성이 없다) 그 자리는
   건드리지 않는다. 대는 쪽은 `js/note-swipe.js`.

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

   `touch-action: pan-y` 가 이 기능의 핵심 한 줄이다. 알림함은 백 건이 쌓이는
   목록이라 **굴리는 것이 미는 것보다 잦다** — 세로 끌기는 브라우저가 제
   스크롤로 가져가고 가로 끌기만 우리에게 온다. 안 걸면 옆으로 밀어도
   브라우저가 스크롤로 삼키고, 통째로 막으면 이번엔 카드 위에서 목록을
   위아래로 굴릴 수 없다.

   `overflow: hidden` 은 밀려난 본문이 카드 밖으로 비어져 나오지 않게 한다 —
   서랍 몸통에 가로 스크롤이 생기면 미는 동안 판이 함께 흔들린다. */
.jsini-note-drawer__card[data-note] {
  --note-swipe: 0px;
  --note-swipe-ms: 0ms;
  --note-ready: 0;
  --note-alive: 1;

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

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

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

   **안쪽 여백을 주지 않는다.** 주면 폭이 0 이어도 상자가 그만큼 남아
   (`width` 는 여백 아래로 못 내려간다) 카드마다 양옆에 **실오라기**가 늘 선다.
   밀기 전에는 한 픽셀도 없어야 한다 — 글자를 들이는 일은 안의 그림이 제
   바깥 여백으로 한다. */
.jsini-note-drawer__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;
}

/* 초록은 ✓ 단추가 하는 일과 같은 뜻이다. 빨강은 **되돌릴 수 없다**는 뜻이고,
   둘이 갈려야 문턱 앞에서 「지금 놓으면 어느 쪽이 되나」를 눈으로 가린다. */
.jsini-note-drawer__act--read {
  left: 0;
  width: max(0px, var(--note-swipe));
  background: #2e7d32;
}

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

/* 문턱을 넘으면 그림이 커지고 글자가 또렷해진다. **손을 떼기 전에**
   「놓으면 처리된다」를 알려 주는 것이 이 둘의 일이다 — 넘기 전에 놓으면
   카드는 제자리로 돌아간다. */
.jsini-note-drawer__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;
}

.jsini-note-drawer__act--read .jsini-note-drawer__act-mark { margin-left: 0.6rem; }
.jsini-note-drawer__act--del .jsini-note-drawer__act-mark { margin-right: 0.6rem; }

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

/* 밀리는 것은 카드가 아니라 **본문**이다. 카드 자신을 옮기면 띠도 함께 가서
   아무것도 열리지 않는다. 띠 위에 놓는 것(`z-index`)도 필요하다 — 띠가 절대
   자리라 그대로 두면 문서에서 뒤인 띠가 본문을 덮어 제목이 색에 가린다. */
.jsini-note-drawer__card[data-note] > .jsini-note-drawer__row {
  position: relative;
  z-index: 2;
  transform: translateX(var(--note-swipe));
  transition: transform var(--note-swipe-ms) ease;
}

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

/* 발. 휴대폰에서 화면을 다 채울 때 아래 턱(홈 바)에 단추가 물리지 않게
   `safe-area` 만큼 더 띄운다 — 넓은 화면에서는 그 값이 0 이다. */
.jsini-note-drawer__foot,
.jsini-note-drawer-custom .jsini-right-drawer__foot {
  flex: 0 0 auto;
  display: flex;
  justify-content: flex-end;
  padding: 0.75rem 0.9rem;
  padding-bottom: calc(0.75rem + env(safe-area-inset-bottom));
  background: var(--jsini-surface);
  border-top: 1px solid var(--jsini-border);
}

.jsini-note-drawer__readall {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* 모바일 화면에서는 단추가 좁아 누르기 불편하지 않도록 가로 폭을 꽉 채우고
   세로 터치 영역(최소 44px)과 안쪽 여백을 넉넉히 준다. */
.jsini-note-drawer__readall--phone {
  width: 100% !important;
  min-height: 44px;
  padding: 0.625rem 1rem;
  font-size: var(--jsini-fs-sm, 0.875rem);
  font-weight: 600;
}

/* 좁은 화면에서는 단추가 발을 꽉 채운다 — 한 줄에 그것 하나뿐이라
   오른쪽 끝에 작게 붙어 있으면 누를 데를 찾게 된다.
   상하 터치 높이도 44px 이상으로 키워 모바일에서 좁지 않고 편하게 누르게 한다. */
@media (max-width: 767px) {
  .jsini-note-scrim {
    inset: 0;
  }

  .jsini-note-drawer,
  .jsini-note-drawer-custom {
    top: 0;
    width: 100vw;
    border-left: none;
  }

  .jsini-note-drawer__readall {
    width: 100% !important;
    min-height: 44px;
    padding: 0.625rem 1rem;
    font-size: var(--jsini-fs-sm, 0.875rem);
    font-weight: 600;
  }

  .jsini-note-drawer__foot,
  .jsini-note-drawer-custom .jsini-right-drawer__foot {
    justify-content: stretch;
  }
}

@media (pointer: coarse) and (max-width: 991px) {
  .jsini-note-drawer__readall {
    width: 100% !important;
    min-height: 44px;
    padding: 0.625rem 1rem;
    font-size: var(--jsini-fs-sm, 0.875rem);
    font-weight: 600;
  }

  .jsini-note-drawer__foot,
  .jsini-note-drawer-custom .jsini-right-drawer__foot {
    justify-content: stretch;
  }
}

/* ── 빠른 지시 서랍 ──────────────────────────────────────────
   AI 대화 서랍과 같은 자리·같은 움직임이다. 다른 것이 둘이다.

   [더 넓다]

   AI 대화는 말풍선 한 줄기라 26rem 이면 넉넉한데, 이쪽 알맹이는 글상자
   아래에 **카드를 가로 둘씩** 깐다(`pm-ask__list` 는 `1fr 1fr` 고정이다).
   26rem 에 넣으면 카드 하나가 12rem 이 되어 제목이 거의 다 잘린다.
   화면 자체의 읽기 좋은 폭이 52rem 이라 그보다는 좁게 둔다.

   [굴리는 것은 판이 아니라 몸통이다]

   AI 대화는 알맹이가 제 안에서 굴렀지만 이쪽은 위에서 아래로 길게 쌓인다
   (글상자 · 단추 줄 · 카드 · 더보기). 몸통에 `overflow-y` 를 주지 않으면
   카드가 늘어날 때 판 밖으로 흘러 「더보기」를 누를 수 없다.

   [휴대폰에서는 아예 그리지 않는다]

   그 크기에서는 ⚡ 가 서랍을 열지 않으므로(`HeaderTools`) 평소에는 뜰 일이
   없다. 다만 **책상에서 열어 둔 채 창을 좁히는 길**이 있어서 막아 둔다 —
   360px 에서 이 판은 화면을 통째로 덮는다. 상태는 건드리지 않으므로 창을
   다시 넓히면 펴져 있던 그대로 돌아온다. */
.jsini-quick-drawer {
  position: fixed;
  top: var(--jsini-header-height);
  right: 0;
  bottom: 0;
  width: min(42rem, 100vw);
  z-index: 1040;
  display: flex;
  flex-direction: column;
  background: var(--jsini-bg);
  border-left: 1px solid var(--jsini-border);
  box-shadow: -0.5rem 0 1.5rem rgb(0 0 0 / 12%);
  transform: translateX(100%);
  visibility: hidden;
  transition: transform 0.2s ease-in-out, visibility 0.2s;
}

.jsini-quick-drawer--open {
  transform: translateX(0);
  visibility: visible;
}

.jsini-quick-drawer__head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.5rem 0.5rem 0.9rem;
  border-bottom: 1px solid var(--jsini-border);
}

.jsini-quick-drawer__title {
  flex: 1;
  font-weight: 600;
}

.jsini-quick-drawer__body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0.75rem;
}

/* 판 안에서는 화면의 읽기 좋은 폭(52rem)이 도리어 걸린다 — 판이 이미
   그보다 좁아서 아무 일도 안 하지만, 나중에 판을 넓히면 알맹이만 가운데
   덜 찬 채로 남는다. 판을 꽉 채우게 풀어 둔다. */
.jsini-quick-drawer .pm-ask {
  max-width: none;
}

@media (max-width: 767px) {
  .jsini-quick-drawer {
    display: none;
  }
}

.jsini-icon-robot {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M7.2 1h1.6v1.6h2.4A2.8 2.8 0 0 1 14 5.4v5.2a2.8 2.8 0 0 1-2.8 2.8H4.8A2.8 2.8 0 0 1 2 10.6V5.4a2.8 2.8 0 0 1 2.8-2.8h2.4zM5.8 6.6a1.2 1.2 0 1 0 0 2.4 1.2 1.2 0 0 0 0-2.4zm4.4 0a1.2 1.2 0 1 0 0 2.4 1.2 1.2 0 0 0 0-2.4zM0 6.4h1.2v3.2H0zm14.8 0H16v3.2h-1.2z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M7.2 1h1.6v1.6h2.4A2.8 2.8 0 0 1 14 5.4v5.2a2.8 2.8 0 0 1-2.8 2.8H4.8A2.8 2.8 0 0 1 2 10.6V5.4a2.8 2.8 0 0 1 2.8-2.8h2.4zM5.8 6.6a1.2 1.2 0 1 0 0 2.4 1.2 1.2 0 0 0 0-2.4zm4.4 0a1.2 1.2 0 1 0 0 2.4 1.2 1.2 0 0 0 0-2.4zM0 6.4h1.2v3.2H0zm14.8 0H16v3.2h-1.2z'/%3E%3C/svg%3E");
}

/* ── 첨부 고르기 (D5) ────────────────────────────────────────
   생김새는 DevExpress `DxFileInput` 이 그린다 — 끌어놓기 칸 · 파일 목록 ·
   파일별 진행 막대 · 빼기 · 취소까지 전부 그쪽 것이다. 여기 남는 것은
   **부품과 안내 줄을 세로로 쌓는 것**뿐이다.

   2026-09-13 이전에는 표준 `InputFile` 위에 그 전부를 직접 그렸고, 그래서
   이 자리에 클래스가 일곱 개 있었다(`__drop` · `__progress` · `__list` ·
   `__item` · `__name` · `__size`). 지금은 쓰는 곳이 없어 지웠다 —
   남겨 두면 「어디서 쓰이나」를 다음 사람이 다시 찾는다.

   **`DxUpload` 은 여전히 못 쓴다**(브라우저가 직접 POST 하는데 BFF 라
   토큰이 없다). 갈리는 지점은 FilePicker 머리말에 있다. */
.jsini-picker {
  display: grid;
  gap: 0.5rem;

  /* 단추가 칸 너비를 다 먹지 않게 한다. grid 는 기본이 `stretch` 라,
     안 적으면 아이콘 하나짜리 단추가 폼 너비만큼 늘어난다. */
  justify-items: start;
}

/* 고르기 단추. 글자가 없으니 정사각으로 세운다 —
   DevExpress 단추의 좌우 여백은 글자가 있을 때를 재어 둔 값이라
   그대로 두면 아이콘이 가로로 긴 칸 가운데 떠 보인다. */
.jsini-picker__pick {
  padding-inline: 0;
  min-width: 2.25rem;
}

/* 제 단추를 끈 자리(`ShowSelectButton="false"`)에 빈 판이 남는다.
   높이는 없지만 격자 간격(0.5rem)을 한 번 더 먹어서, 안 지우면
   단추와 파일 목록 사이가 두 칸 벌어진다.

   파일 목록도 **고른 것이 없을 때** 같은 일을 한다 — 비어 있어도
   위쪽 여백 22px 을 들고 서 있어서, 재어 보면 단추와 안내 줄 사이가
   30px 벌어진다(실측). 둘 다 비었을 때만 접는다.

   `:empty` 는 주석 노드를 세지 않으므로 Blazor 가 박아 두는 `<!--!-->`
   표시가 있어도 걸린다. 줄이 생기면 규칙이 저절로 풀린다. */
.jsini-picker .dxbl-upload-panel:empty,
.jsini-picker .dxbl-upload-file-list-view:empty {
  display: none;
}

/* 그 둘을 접고 나서도 **빈 통이 위 여백을 들고 선다** — 재어 보면
   `.dxbl-upload` 19.2px · 목록 22px 이고, 둘 다 「제 단추 아래」를 띄우려고
   둔 값이라 단추가 밖으로 나온 지금은 띄울 것이 없다. 사이는 격자 간격
   (0.5rem) 하나로 충분하다 — 걷어내면 단추와 안내 줄 사이가 90px → 71px 다. */
.jsini-picker .dxbl-upload,
.jsini-picker .dxbl-upload-file-list-view {
  padding-top: 0;
}

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

/* ── 사진 그룹 (ImageGroup) ────────────────────────────────

   여러 장을 격자로 늘어놓고, 마지막 칸이 「사진 추가」다.
   칸 크기를 고정하는 이유: 사진마다 비율이 달라 그대로 두면 줄이 들쭉날쭉해지고
   편집 창의 높이가 사진에 따라 춤춘다. */

.jsini-imggroup {
  display: grid;
  gap: 0.5rem;
}

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

.jsini-imggroup__list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.jsini-imggroup__item {
  position: relative;
  width: 8rem;
  height: 6rem;
  margin: 0;
  overflow: hidden;
  border: 1px solid var(--jsini-border);
  border-radius: 0.4rem;
}

/* 대표 사진. 테두리 색만 바꾼다 — 굵기를 바꾸면 그 칸만 크기가 달라진다. */
.jsini-imggroup__item--rep { border-color: var(--jsini-accent); }

.jsini-imggroup__item img {
  width: 100%;
  height: 100%;

  /* 비율은 그대로 두고 넘치는 가장자리만 자른다. */
  object-fit: cover;
}

/* 단추는 사진 위에 겹친다. 마우스를 올렸을 때만 보인다 —
   늘 보이면 사진 여덟 장이 단추 열여섯 개로 읽힌다. */
.jsini-imggroup__tools {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  display: flex;
  gap: 0.25rem;
  justify-content: center;
  padding: 0.2rem;
  background: rgb(0 0 0 / 55%);
  opacity: 0;
  transition: opacity 0.12s ease-out;
}

.jsini-imggroup__item:hover .jsini-imggroup__tools,
.jsini-imggroup__item:focus-within .jsini-imggroup__tools {
  opacity: 1;
}

.jsini-imggroup__tools button {
  padding: 0.1rem 0.35rem;
  border: 0;
  border-radius: 0.25rem;
  background: transparent;
  color: #fff;
  font-size: var(--jsini-fs-2xs);
  cursor: pointer;
}

.jsini-imggroup__tools button:disabled { opacity: 0.6; cursor: default; }

.jsini-imggroup__add {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 8rem;
  height: 6rem;
  border: 1px dashed var(--jsini-border);
  border-radius: 0.4rem;
  background: var(--jsini-surface);
  font-size: var(--jsini-fs-xs);
  color: var(--jsini-text-muted);
  text-align: center;
  cursor: pointer;
}

/* 파일 고르개는 숨기고 칸 전체를 누르게 한다. `display: none` 이 아니라
   투명하게 덮어 두어야 키보드 초점이 살아 있다. */
.jsini-imggroup__add input[type='file'] {
  position: absolute;
  width: 8rem;
  height: 6rem;
  opacity: 0;
  cursor: pointer;
}

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

/* ── 바깥 화면 끼워 넣기 (Embedded.razor) ─────────────────

   유형이 EMBEDDED 인 메뉴를 받는 공용 화면 하나가 쓴다. 모듈이 아니라 셸의
   화면이라 여기 둔다 — 전에는 같은 규칙이 `pm-frame`(프로젝트관리)과
   `ad-frame`(포털관리) 두 벌로 갈려 있었고, 그중 한 벌만 고쳐 둔 적이 있다.

   [바깥 여백을 숫자로 세지 않는다]

   `calc(100vh - 56px - 10rem)` 으로 재던 자리다. 그 `10rem` 은 머리줄 · 탭 줄 ·
   길잡이 · 안내 줄 · 본문 여백을 눈대중으로 합친 값이라 맞을 이유가 없었고,
   실제로 틀렸다 — 안내 줄이 한 줄 늘어난 화면에서 iframe 이 58px 넘쳐
   쪽에 세로 막대가 생겼다.

   본문 칸을 세로 flex 로 세우고 iframe 에 남은 높이를 준다. 안내 줄이 몇
   줄이 되든 화면은 그것을 몰라도 된다. `CommCont` 가 표에 쓰는 것과 같은
   수(위 「본문 높이를 꽉 채운다」)이고, `:has()` 로 이 화면에만 건다.
   ---------------------------------------------------------- */

/* ── 끼워 넣은 화면의 세로 막대를 감춘다 ─────────────────────

   그 막대는 **끼워 넣은 문서가 그리는 것**이다. 출처가 다르면 브라우저가
   우리 CSS 를 그 안으로 들여보내지 않아(`contentDocument` 가 null) 얇게
   만들 방법이 없다. 지금 띄우는 것이 그렇다 — 포털은 jsini.co.kr 이고
   띄우는 것은 61208 포트의 다른 서버다.

   그래서 **감춘다.** iframe 을 막대 폭만큼 넓혀 막대를 감싸개 밖으로 밀어낸
   뒤, 감싸개가 잘라 낸다. 휠과 손가락 스크롤은 그대로 된다 — 없어지는 것은
   막대 그림뿐이다.

   [잃는 것도 적어 둔다]

   지금 어디를 보고 있는지·얼마나 남았는지를 알려 주던 표시가 사라진다.
   끼워 넣는 것이 대시보드라 「위에서 아래로 한 번 훑는」 화면이어서 골랐다.
   되돌리려면 이 규칙 묶음만 지우면 된다.

   [`--jsini-embed-bar` 는 막대 폭이다]

   17px 은 Windows·Linux 의 흔한 값이다. macOS 처럼 막대가 내용 위에 겹쳐
   뜨는(overlay) 환경에서는 막대가 자리를 안 차지하므로 **그만큼 내용이
   잘린다.** 운영에서 보는 자리가 사무실 데스크톱이라 이 값으로 두었고,
   어긋나면 이 한 줄만 고치면 된다.
   ---------------------------------------------------------- */

:root {
  --jsini-embed-bar: 17px;
}

.jsini-embed-clip {
  /* 밖으로 민 막대를 여기서 잘라 낸다. 테두리와 모서리도 이쪽이 쥔다 —
     iframe 에 두면 넓힌 만큼 오른쪽 테두리가 잘려 나간다. */
  overflow: hidden;

  /* :has() 를 모르는 브라우저를 위한 바닥. 아래 규칙이 이기면 안 쓰인다. */
  height: calc(100vh - 56px - 10rem);

  /* 창이 아주 낮을 때의 바닥. 그보다 줄이면 끼워 넣은 화면을 읽을 수 없다. */
  min-height: 360px;

  border: 1px solid var(--jsini-border, #e0e0e0);
  border-radius: 8px;
  background: #fff;
}

.jsini-embed {
  display: block;
  width: calc(100% + var(--jsini-embed-bar));
  height: 100%;
  border: 0;
}

.jsini-shell__content:has(> .jsini-embed-clip) {
  display: flex;
  flex-direction: column;
}

/* 안내 줄은 제 높이만큼만. 없으면 iframe 에 자리를 뺏겨 글이 눌린다. */
.jsini-shell__content:has(> .jsini-embed-clip) > *:not(.jsini-embed-clip) {
  flex: none;
}

.jsini-shell__content:has(> .jsini-embed-clip) > .jsini-embed-clip {
  flex: 1;
  height: auto;
}

/* ── 화면을 옮기는 동안의 표시 (PageSpinner) ───────────────

   이력: Vue `content-spinner.vue` + `VbenSpinner`.

   [늘 DOM 에 두고 클래스만 바꾼다]

   보일 때 만들어 넣으면 나타나는 전환이 걸리지 않아 그대로 튀어나온다.
   깔아 두고 `opacity`·`visibility` 만 움직인다.

   [나타날 때만 늦춘다]

   `transition-delay` 가 Vue 의 타이머 둘(50ms 뒤 표시 · 최소 500ms 유지)을
   대신한다. 0.12초보다 빨리 끝난 이동에서는 켜졌다 꺼지는 동안 화면에
   아무것도 안 나타나고, 그보다 오래 걸린 이동만 표시가 뜬다. 사라질 때는
   늦추지 않고 0.28초에 걸쳐 스러진다.

   Blazor Server 에서 타이머는 곧 왕복이라, 이동 한 번에 그림을 두 번 더
   밀어내게 된다. CSS 로 하면 그 왕복이 없다.

   [본문 안에 깐다]

   `.jsini-shell__content` 가 자리를 잡아 준다(`position: relative`).
   머리줄·탭 줄·길잡이는 덮지 않는다 — 어디로 가는 중인지는 보여야 한다.
   ---------------------------------------------------------- */

.jsini-pageload {
  position: absolute;
  inset: 0;
  z-index: 30;

  display: flex;
  align-items: center;
  justify-content: center;

  /* 바탕을 덮되 비치게 둔다. 뒤에 무엇이 있었는지 보이면 「가는 중」으로
     읽히고, 꽉 막으면 「화면이 사라졌다」로 읽힌다. */
  background: color-mix(in srgb, var(--jsini-surface, #fff) 72%, transparent);
  backdrop-filter: blur(2px);

  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.28s ease, visibility 0.28s ease;
}

.jsini-pageload.is-on {
  visibility: visible;
  opacity: 1;

  /* 여기가 「깜빡임 막기」다. 위 머리말 참고. */
  transition-delay: 0.12s;
}

/* 상승 조각 넷. Vue 판의 좌표와 엇박을 그대로 옮겼다 —
   심볼(JS 인터록)은 굴리지 않는다. 브랜드 규칙이 기울이기·회전을 막는다. */
.jsini-loader {
  width: 3rem;
  height: 3rem;
  color: var(--jsini-text, currentColor);
}

.jsini-loader__bar {
  fill: currentColor;
  transform-origin: center bottom;
  animation: jsini-loader-rise 1.1s ease-in-out infinite;
}

.jsini-loader__bar:nth-child(1) { animation-delay: 0s; }
.jsini-loader__bar:nth-child(2) { animation-delay: 0.12s; }
.jsini-loader__bar:nth-child(3) { animation-delay: 0.24s; }
.jsini-loader__bar:nth-child(4) { animation-delay: 0.36s; }

@keyframes jsini-loader-rise {
  0%, 100% { transform: scaleY(0.55); }
  50% { transform: scaleY(1); }
}

/* 안 보일 때는 애니메이션을 멈춘다. 이동이 없을 때도 계속 돌면 그만큼
   브라우저가 매 프레임 다시 그린다 — 하루 종일 띄워 두는 화면들이다. */
.jsini-pageload:not(.is-on) .jsini-loader__bar { animation-play-state: paused; }

/* 움직임을 원하지 않는 사람에게는 멈춘 그림을 준다. */
@media (prefers-reduced-motion: reduce) {
  .jsini-loader__bar { animation: none; }
}

/* ── 연결이 끊겼을 때 ────────────────────────────────────────

   **아무것도 안 보인다.**

   그래도 셸은 `#components-reconnect-modal` 을 빈 채로 그린다
   (`App.razor`). 그 요소가 문서에 있으면 프레임워크는 자기 상자를 만들지
   않고 **거기에 상태 클래스만 갈아 끼우므로**(`UserSpecifiedDisplay`),
   **비워 두는 것이 지우는 길이다.** 걷어내면 프레임워크가 제 상자를 끼워
   넣어 화면 한가운데에 영문 안내가 뜬다.

   [상자 → 띠 → 아무것도 — 2026-10-11]

   한동안 여기에 화면을 덮는 대화상자가 있었다. 상태마다 다른 글을 보여 주고
   새로고침·다시 시도·잠시 멈추기를 고르게 했고, 거절당한 자리에서는
   「5초 뒤에 화면을 새로 엽니다」를 셌다. **고를 것이 없는 선택이었다** —
   거절당했다는 것은 하던 일이 서버에 없다는 뜻이라 어느 단추를 눌러도 끝은
   새로고침 하나이고, 서버에 닿지도 못한 자리에서는 새로고침이 브라우저 오류
   화면만 부른다.

   그래서 상자를 걷고 위쪽에 작은 띠 하나만 남겼다(「연결이 잠시 끊겼습니다 ·
   다시 잇는 중」). 그것도 걷었다. **보여 주는 값이 없는 알림이었다** —
   회선이 튀는 것은 1~2초 안에 저절로 이어지고, 오래 끊긴 자리는 셸이 조용히
   다시 잇고(`js/reconnect.js`), 되돌릴 수 없는 자리는 묻지 않고 새로 연다.
   사람이 거기서 할 일이 없는데 움직이는 점이 달린 띠가 뜨는 것 자체가
   「사고가 났다」로 읽혀, 아무 일도 아닌 터널 몇 초마다 하던 일을 멈추게 했다.

   **끊긴 동안 화면은 끊기기 전 그대로다.** 읽던 것을 읽고 굴린다.

   [상태 일곱을 **그래도 다 적는다**]

   show · retrying · paused · failed · resume-failed · rejected · hide.
   비워 둔 `<div>` 는 어차피 안 보이지만, 한 줄로 뭉뚱그리지 않고 일곱을
   다 세워 둔다 — 나중에 누가 어느 상태에 뭔가를 그려 넣으면 **그 상태만
   혼자 뜬다.** 여기 이름이 다 있으면 그 자리가 눈에 띈다.
   ------------------------------------------------------------ */

#components-reconnect-modal,
#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-retrying,
#components-reconnect-modal.components-reconnect-paused,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-resume-failed,
#components-reconnect-modal.components-reconnect-rejected,
#components-reconnect-modal.components-reconnect-hide {
  display: none !important;
}

/* ── 좁은 화면에서는 창을 화면 안에 가둔다 ────────────────────

   창 너비는 화면이 픽셀로 준다(`Width="420px"` · `"880px"` …). 전부
   데스크톱을 보고 고른 값이라 **폭이 360px 인 기기에서는 창이 화면보다
   넓다.** DevExpress 는 창을 가운데 두므로(`justify-self: center`) 넘친
   만큼이 양옆으로 똑같이 잘린다 — 묻는 글의 첫 글자와 끝 글자가 함께
   사라지고, 바닥의 「취소」도 화면 밖으로 나간다.

   `max-width` 로 가두면 화면이 준 `width` 는 그대로 두고 좁은 기기에서만
   접힌다. 창은 `box-sizing: border-box` 라 테두리까지 이 안이다.

   **우리 창에만 건다**(`jsini-popup` · `jsini-gridpopup`). `.dxbl-popup` 을
   통째로 겨누면 DevExpress 가 좁은 화면에서 바닥부터 올리는 판
   (`dxbl-btn-dropdown-modal`)과 달력·목록 드롭다운까지 같이 걸려, 화면을
   꽉 채워야 할 판이 양옆에 틈을 두고 뜬다.

   `1.5rem` 은 좌우 0.75rem 씩이다. 창이 화면에 딱 붙으면 뒤가 덮인 것인지
   페이지가 그렇게 생긴 것인지 구별이 안 된다.

   **`!important` 가 필요하다.** 폭을 직접 준 창에는 DevExpress 가
   `dxbl-popup-fixed-width` 를 붙이고, 테마에

       .dxbl-modal > .dxbl-modal-root > .dxbl-popup.dxbl-popup-fixed-width
       { max-width: unset }

   가 있다 — 「폭을 박았으니 아무것도 줄이지 마라」는 뜻이고, 그 규칙은
   미디어 쿼리 밖이라 휴대폰에서도 산다. 겨누는 자리를 늘려 이기는 방법도
   있지만 그러면 DevExpress 가 다음 판에서 마크업을 한 겹 바꾸는 순간
   말없이 풀린다.
   ------------------------------------------------------------ */

@media (max-width: 767px) {
  .jsini-popup,
  .jsini-gridpopup {
    max-width: calc(100vw - 1.5rem) !important;
    margin: 0 auto !important;
  }
}

/* ── 표의 편집 창을 CommPopup 과 같게 보이게 한다 ──────────────

   표의 편집 창은 **`CommPopup` 이 아니다.** DevExpress 그리드가 자기 창을
   직접 그린다(`GridEditMode.PopupEditForm`). 부품을 갈아 끼우려면 새로
   만들기·복사본 뜨기·저장·검증을 우리가 다시 짜야 하고, 그것은 표를 쓰는
   화면 여든넷의 등록·수정 경로를 통째로 건드리는 일이다.

   그래서 **부품은 그대로 두고 몸가짐만 맞춘다.** 사용자가 보는 차이는
   여기서 사라진다.

   [맞추는 것 둘]

   1. 키가 큰 폼은 본문만 구른다. DevExpress 의 이 창은 `overflow: unset` 이라
      칸이 열몇 개인 폼이 화면보다 길어지면 **바닥의 「저장」이 화면 밖으로
      나간다** — 스크롤로도 닿지 않는다. CommPopup 이 `MaxHeight`+`Scrollable`
      로 막아 둔 것과 같은 일을 여기서 한다.
   2. 머리를 잡는 손 모양. 끌기 자체는 이 창도 이미 된다
      (`allow-drag-by-header-only`). 다만 `jsini-drag-head` 가 안 붙어 손
      모양이 달랐다 — 잡을 수 있는 줄 모르면 있으나 마나다.
   ------------------------------------------------------------ */

.jsini-gridpopup {
  max-height: 86vh;
}

.jsini-gridpopup .dxbl-modal-content {
  max-height: inherit;
  overflow: hidden;
}

/* 머리와 바닥은 제자리에 남고 본문만 구른다. */
.jsini-gridpopup .dxbl-modal-body {
  overflow: auto;
  min-height: 0;
}

/* 손 모양은 `jsini-drag-head` 와 같은 값이다. 그 클래스를 이 창의 머리에
   붙일 방법이 없어(머리를 DevExpress 가 그린다) 규칙만 같게 적는다.

   **`!important` 가 필요하다.** DevExpress 가 머리에 `cursor` 를 더 세게
   걸어 두어 그냥 적으면 먹지 않는다(재어 확인했다 — `default` 로 남았다).
   `jsini-drag-head` 가 같은 이유로 같은 표시를 달고 있다. */
.jsini-gridpopup .dxbl-modal-header {
  cursor: move !important;
  user-select: none;
}

.dxbl-popup-dragging.jsini-gridpopup .dxbl-modal-header {
  cursor: grabbing !important;
}

/* 머리 안의 닫기 단추는 **누르는** 자리다. 물려받은 `move` 를 되돌린다. */
.jsini-gridpopup .dxbl-modal-header .dxbl-btn {
  cursor: pointer !important;
}

/* ── 스크롤 막대 ─────────────────────────────────────────────

   기본 막대는 두껍고(윈도우 17px) 회색 홈이 늘 보여서, 표·팝업·사이드바가
   겹쳐 있는 화면에서 **막대만 여러 줄** 그어진 것처럼 보인다.

   화면에 보이는 막대는 **3px 로 맞춘다.** DevExpress 가 제 손으로 그리는
   막대의 엄지가 3.2px 이라(위쪽 사이드바 트리 절), 그 옆에 선 브라우저 막대만
   혼자 굵으면 한 화면에 굵기가 두 가지로 보인다. 홈은 비우고, 손잡이는
   글자색을 옅게 섞어 만들므로 밝은 테마에서는 어둡게, 어두운 테마에서는
   밝게 — 테마를 따로 안 적어도 된다.

   [두 가지 방법을 **함께 적으면 안 된다**]

   `::-webkit-scrollbar` 는 그 요소의 `scrollbar-width`·`scrollbar-color`
   가 `auto` 가 아니면 **통째로 무시된다**(크로미움의 규칙이다). 예전에는
   여기서 `html` 에 `scrollbar-color` 를 적어 두었는데 — 이 속성은
   물려받는다 — 그 바람에 화면의 **모든 칸**이 webkit 규칙을 버리고
   브라우저 기본 막대를 그렸다. `width: 8px` 은 한 번도 먹은 적이 없다
   (크롬 153 헤드리스 실측: textarea 17px · 보통 칸 15px).
   `tabler.css` 에도 같은 `* { scrollbar-color }` 가 있어서 그쪽까지
   같이 걷었다.

   그래서 굵기와 색을 **브라우저별로 갈라 적는다.**

   - 웹킷(크롬·엣지·사파리): `::-webkit-scrollbar` 로 3px, 색은 엄지에
     직접 칠한다. `scrollbar-width`/`scrollbar-color` 는 **적지 않는다.**
   - 파이어폭스: `::-webkit-scrollbar` 가 아예 없으니 `scrollbar-width`/
     `scrollbar-color` 로 간다. 가르는 자리는
     `@supports not selector(::-webkit-scrollbar)` — 크로미움은 이 선택자를
     알아서 이 블록을 건너뛰고, 파이어폭스만 들어온다. 다만 거기서는 `thin`
     말고 px 를 못 받으므로 3px 까지는 못 간다. 그것이 끝이다.
   - `scrollbar-width` 는 **물려받지 않는다**(실측: `html` 에 `thin` 을
     적어도 자식 칸은 `auto` 로 계산된다). 그래서 파이어폭스 쪽은 `html`
     이 아니라 `*` 로 적는다.

   [일부러 3px 보다 얇게 둔 곳은 건드리지 않는다]

   탭 줄과 길잡이는 `scrollbar-width: none` 으로 아예 감춰 두었고, 휴대폰도
   그렇다. 그쪽은 클래스 선택자이거나 뒤에 오는 규칙이라 여기를 이긴다 —
   그대로 남는다.

   **사이드바 트리는 여기도 저기도 아니다.** 그 막대는 브라우저가 아니라
   DevExpress 가 div 로 그린 것이라 이 절의 규칙이 아예 닿지 않는다 —
   감추는 자리는 위쪽 `.jsini-sidebar__tree .dxbl-scroll-viewer` 다.

   [iframe 안쪽은 못 바꾼다]

   끼워 넣은 화면(`/embed/...`)의 막대는 **그 문서의 것**이다. 출처가 다르면
   (지금 쓰는 곳이 그렇다 — 포털은 jsini.co.kr, 띄우는 것은 61208 포트의
   다른 서버) 브라우저가 우리 CSS 를 그 안으로 들여보내지 않는다. 막는 것이
   맞는 동작이라 우회할 방법도 없다.
   ---------------------------------------------------------- */

:root {
  --jsini-scroll-thumb: color-mix(in srgb, var(--jsini-text) 28%, transparent);
  --jsini-scroll-thumb-hover: color-mix(in srgb, var(--jsini-text) 45%, transparent);
}

@media (max-width: 767px) {
  :root {
    --jsini-scroll-thumb: color-mix(in srgb, var(--jsini-text) 5%, transparent);
    --jsini-scroll-thumb-hover: color-mix(in srgb, var(--jsini-text) 10%, transparent);
  }
}

/* 파이어폭스 전용. 크로미움은 `selector(::-webkit-scrollbar)` 를 알아서
   이 블록에 들어오지 않는다 — 들어오면 아래 webkit 규칙이 통째로 죽는다. */
@supports not selector(::-webkit-scrollbar) {
  * {
    scrollbar-width: thin;
    scrollbar-color: var(--jsini-scroll-thumb) transparent;
  }
}

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

@media (max-width: 767px) {
  * {
    scrollbar-width: none;
    -ms-overflow-style: none;
  }
  *::-webkit-scrollbar {
    display: none;
  }
}

/* 홈은 비운다. 칠해 두면 내용이 짧아 막대가 필요 없는 칸에도 회색 띠가 남는다. */
*::-webkit-scrollbar-track {
  background: transparent;
}

*::-webkit-scrollbar-thumb {
  border-radius: 999px;
  background: var(--jsini-scroll-thumb);
}

*::-webkit-scrollbar-thumb:hover {
  background: var(--jsini-scroll-thumb-hover);
}

/* 가로·세로가 만나는 모서리. 기본값이 옅은 회색 네모라 홈만 비우면
   그 칸만 남아 점처럼 보인다. */
*::-webkit-scrollbar-corner {
  background: transparent;
}

/* [DevExpress 가 제 손으로 걸어 둔 자리를 되돌린다]

   **여러 줄 글상자(DxMemo)의 세로 막대만 혼자 굵었다** — 「3px 로 맞췄는데
   textarea 만 8px 로 보인다」던 그 자리다. DevExpress 테마가 그 textarea 에
   `scrollbar-width: thin` 과 `scrollbar-color` 를 직접 걸어 두는데, 위에 적은
   대로 크로미움에서는 그 한 줄이 이 칸의 `::-webkit-scrollbar` 를 통째로
   무시하게 만든다. `auto` 로 되돌리면 webkit 규칙이 살아나 3px 이 된다.

   **웹킷에서만 되돌린다.** 파이어폭스는 webkit 규칙을 쓸 수 없으므로
   `auto` 로 돌리면 거기서만 기본 굵기(17px)로 커진다 — DevExpress 가 적어
   둔 `thin` 을 그대로 두는 편이 낫다. */
@supports selector(::-webkit-scrollbar) {
  .dxbl-text-edit.dxbl-memo-edit > textarea {
    scrollbar-width: auto;
    scrollbar-color: auto;
  }
}

/* 서식 편집기(DxHtmlEditor)의 멘션 목록. 그 막대는 브라우저가 아니라
   DevExpress 가 div 로 그려서 webkit 규칙이 닿지 않는다 — 사이드바 트리와
   같은 수로, 폭을 꺼내 쓰는 변수를 3px 로 돌린다(기본값 8px). */
.dx-popup-wrapper.dx-suggestion-list-wrapper {
  --dxbl-html-editor-mentions-scrollbar-width: 3px;
}

/* ── 포털 셸 리프레시 ─────────────────────────────────────

   Tabler Admin의 업무용 다크 톤을 기준으로 셸의 기본 인상을 정리한다.
   메뉴·헤더·업무 카드는 같은 표면 계층을 공유하고, 선택 상태는 블루
   액센트와 얕은 배경을 함께 사용해 색만으로 구분하지 않는다.
   ---------------------------------------------------------- */

:root {
  --jsini-bg: #f4f7fb;
  --jsini-surface: #ffffff;
  --jsini-border: #e4eaf2;
  --jsini-text: #182433;
  --jsini-text-muted: #708096;
  --jsini-accent: var(--jsini-theme-primary, #206bc4);

  /* 강조색을 **앞에 세울 때**(글자 · 아이콘 · 밑줄 · 표시선)의 색.
     밝은 바탕에서는 강조색 그대로다 — 까닭은 어두운 쪽
     (`:root[data-theme='dark']`)에 적어 두었다. */
  --jsini-accent-text: var(--jsini-accent);

  --jsini-surface-raised: #ffffff;
  --jsini-surface-muted: #eef3f8;
  --jsini-focus: rgb(32 107 196 / 24%);
  --jsini-shadow-card: 0 1px 2px rgb(24 36 51 / 5%), 0 8px 24px rgb(24 36 51 / 4%);
}

:root[data-theme='dark'] {
  /* Tabler dark 기준: body #182433 / surface #1a2b3e / border #26334a */
  --jsini-bg: #182433;
  --jsini-surface: #1a2b3e;
  --jsini-border: #26334a;
  --jsini-text: #e7edf7;
  --jsini-text-muted: #91a0b5;
  --jsini-accent: var(--jsini-theme-primary, #4299ff);

  /* ── 어두운 바탕에서 강조색을 글자로 쓰면 안 읽힌다 ──────────

     `--jsini-theme-primary` 는 테마 창에서 고른 **그 색 그대로**다
     (theme.js 의 `primaryColor`). 그런데 그 열넷은 다 밝은 바탕에 얹으라고
     만든 색이라 **밝기를 낮춰 놓은 것**이고, 어두운 쪽에서 밝기만 그대로
     쓰면 바탕과 붙어 버린다. 위 `--jsini-accent` 의 `#4299ff` 는 그것을
     알고 고른 값이지만, theme.js 가 늘 변수를 세우므로 **그 대비값은
     한 번도 쓰이지 않는다.**

     재어 본 것(어두운 surface #1a2b3e 위, WCAG 대비):

       tables #1a3145 → 1.08 · new berry #5c3d85 → 1.69 · moss #486860 → 2.35
       blue   #0f6cbd → 2.67 (기본값) · ubuntu #e95420 → 3.94 (가장 높은 것)

     **열넷이 하나도 4.5 를 넘지 못한다.** 기본 테마(Fluent Dark + Blue)가
     2.67 이라 아무 설정도 안 건드린 사람이 바로 이것을 본다.

     그래서 **앞에 세울 때만** 흰색을 반씩 섞어 띄운다. 섞은 뒤에는 가장 낮은
     것(tables)이 4.5 를 넘고 나머지는 5.4~7.3 이다. 색조는 그대로 남으므로
     「고른 색」이라는 느낌은 잃지 않는다.

     **강조색 자체(`--jsini-accent`)를 바꾸지 않는다.** 그것은 바탕·테두리·
     옅은 칠(`color-mix(… 8%, transparent)`)에도 쓰이는데, 거기서는 낮은
     밝기가 오히려 맞다 — 띄운 색으로 8% 를 깔면 그 자국이 안 보인다.

     [쓰는 자리 — 「앞에 세운다」의 뜻]

     `color:` 로 적는 모든 자리와, 선택을 말하는 얇은 표시(탭 밑줄 · 트리
     왼쪽 3px 선)다. 한동안 사이드바 탭 하나에만 걸려 있어서 **나머지가 다
     원래 강조색으로 남아 있었다** — 고른 탭 글자, 고른 메뉴 항목, 누르고
     있는 칸의 라벨, 표 머리글이 전부 그 1.00~3.83 이다.

     **새 규칙을 적을 때의 가름**: 글자·아이콘·얇은 선이면 이것,
     바탕·옅은 칠·굵은 테두리면 `--jsini-accent`. */
  --jsini-accent-text: color-mix(in srgb, var(--jsini-accent), #ffffff 50%);

  --jsini-surface-raised: #1e3048;
  --jsini-surface-muted: #142032;
  --jsini-focus: rgb(66 153 255 / 28%);
  --jsini-shadow-card: 0 1px 2px rgb(0 0 0 / 22%), 0 12px 28px rgb(0 0 0 / 18%);
}

body {
  font-family:
    Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
    "Segoe UI", sans-serif;
  background: var(--jsini-bg);
}

.jsini-header {
  padding: 0 0.75rem;
  background: var(--jsini-surface);
  border-bottom: 1px solid var(--jsini-border);
  box-shadow: none;
}

.jsini-header__brand {
  gap: 0.65rem;
  margin-left: 0.35rem;
}

.jsini-header .dxbl-btn,
.jsini-header__toggle .dxbl-btn {
  border-radius: var(--jsini-radius-md);
}

.jsini-header .dxbl-btn:hover,
.jsini-header .dxbl-btn:focus-visible {
  background: color-mix(in srgb, var(--jsini-accent) 10%, transparent);
}

.jsini-shell__sidebar {
  background: var(--jsini-surface-muted);
  border-right-color: var(--jsini-border);
  box-shadow: inset -1px 0 0 rgb(255 255 255 / 2%);
}

.jsini-sidebar__search {
  height: 3.25rem;
  padding: 0 0.875rem;
  background: var(--jsini-surface-muted);
  border-bottom-color: var(--jsini-border);
}

.jsini-sidebar__search-box {
  padding: 0.1rem 0.35rem;
  border: 1px solid var(--jsini-border);
  border-radius: var(--jsini-radius-md);
  background: var(--jsini-surface);
}

.jsini-sidebar__tabs {
  gap: 0.25rem;
  padding: 0.5rem 0.65rem 0;
  background: var(--jsini-surface-muted);
  border-bottom: 1px solid var(--jsini-border);
}

.jsini-sidebar__tab {
  border-radius: var(--jsini-radius-md) var(--jsini-radius-md) 0 0;
  border-bottom-width: 3px;
  color: var(--jsini-text-muted);
}

.jsini-sidebar__tab:hover {
  background: color-mix(in srgb, var(--jsini-accent) 8%, transparent);
  color: var(--jsini-text);
}

/* 고른 탭. **글자와 밑줄에는 띄운 강조색을 쓴다** — 어두운 쪽에서 원래
   강조색은 이 바탕(`--jsini-surface`)과 붙어 버린다(`--jsini-accent-text`).
   밝은 쪽에서는 둘이 같은 값이라 달라지는 것이 없다.

   밑줄까지 함께 옮기는 이유는, 글자만 띄우면 **어느 탭을 보고 있는지 말하는
   표시가 둘인데 하나만 보이기** 때문이다. 기본 테마에서 재어 보니 밑줄이
   띠 바탕(#142032) 위에서 3.04 라 3:1 을 겨우 넘는데, 그 값이 테마마다
   1.22(tables)까지 내려간다. */
.jsini-sidebar__tab.is-on {
  background: var(--jsini-surface);
  color: var(--jsini-accent-text);
  border-bottom-color: var(--jsini-accent-text);
}

.jsini-sidebar__tree .dxbl-treeview-item-container {
  border-radius: var(--jsini-radius-md);
  transition: background-color 0.15s ease, color 0.15s ease;
}

.jsini-sidebar__tree .dxbl-treeview-item-container:hover {
  background: color-mix(in srgb, var(--jsini-accent) 8%, transparent);
}

.jsini-sidebar__tree .dxbl-treeview-item-container.dxbl-active {
  background: color-mix(in srgb, var(--jsini-accent) 16%, transparent);
  color: var(--jsini-accent-text);
  font-weight: 600;
}

.jsini-shell__main {
  background: var(--jsini-bg);
}

.jsini-tabs {
  height: 2.75rem;
  padding: 0 0.75rem;
  background: var(--jsini-surface);
  border-bottom-color: var(--jsini-border);
}

.jsini-tab {
  border-radius: var(--jsini-radius-md) var(--jsini-radius-md) 0 0;
}

.jsini-tab:hover {
  background: color-mix(in srgb, var(--jsini-accent) 8%, transparent);
}

/* 켜진 탭. **밑줄에도 띄운 강조색을 쓴다** — 사이드바 탭과 같은 까닭이다
   (`.jsini-sidebar__tab.is-on` 머리말). 이 띠의 바탕(`--jsini-surface`) 위에서
   원래 강조색은 대비가 1.06(tables)~3.64(ubuntu)라, 글자만 띄우면 **선택을
   말하는 표시가 둘인데 하나만 보인다.**

   바탕의 9% 칠은 원래 강조색 그대로 둔다. 옅은 칠은 밝기가 낮은 쪽이 맞다 —
   띄운 색으로 9% 를 깔면 그 자국이 안 보인다. */
.jsini-tab--active {
  background: color-mix(in srgb, var(--jsini-accent) 9%, transparent);
  border-bottom-color: var(--jsini-accent-text);
}

/* **좁은 화면은 건드리지 않는다.** 이 규칙은 파일 위쪽의
   `@media (max-width: 767px)` 본문 여백보다 **뒤에 있어서**, 폭 조건 없이
   적으면 같은 힘으로 나중에 이겨 휴대폰의 아래쪽 자리 비킴(하단 띠 60px)을
   지워 놓는다. 한동안 그렇게 죽어 있었다 — 그래서 여기에 하한을 박는다.

   값은 위 기본값과 같은 3px 다. 넓은 화면만 따로 둘 까닭이 없어졌는데도
   규칙을 남기는 것은 그 하한 때문이다. */
@media (min-width: 768px) {
  .jsini-shell__content {
    padding: var(--jsini-shell-pad-top) var(--jsini-shell-pad-inline)
             var(--jsini-shell-pad-bottom);
  }
}

.jsini-page-head {
  margin-bottom: 1.25rem;
}

.jsini-page-head__title {
  letter-spacing: -0.02em;
}

.commsch {
  background: var(--jsini-surface-raised);
  --jsini-field-label-bg: var(--jsini-surface-raised);
  border-color: var(--jsini-border);
  border-radius: var(--jsini-radius-lg);
  box-shadow: var(--jsini-shadow-card);
}

.commcont {
  padding: var(--jsini-sp-lg);
  border: 1px solid var(--jsini-border);
  border-radius: var(--jsini-radius-lg);
  background: var(--jsini-surface-raised);
  --jsini-field-label-bg: var(--jsini-surface-raised);
  box-shadow: var(--jsini-shadow-card);
}

.commcont__head {
  margin-bottom: var(--jsini-sp-md);
}

.commcont__title {
  font-size: var(--jsini-fs-lg);
  letter-spacing: -0.01em;
}

.commgrd > .dxbl-grid,
.commtree > .dxbl-grid {
  border-color: var(--jsini-border);
  border-radius: var(--jsini-radius-md);
  overflow: hidden;
}

/* 창 안에서도 같다 — 까닭은 아래 「창은 `.jsini-shell__content` 밖에 있다」. */
.jsini-shell__content :is(button, a, input, textarea, select):focus-visible,
.jsini-popup :is(button, a, input, textarea, select):focus-visible,
.jsini-gridpopup :is(button, a, input, textarea, select):focus-visible {
  outline: 2px solid var(--jsini-focus);
  outline-offset: 2px;
}

/* ── 공통 업무 부품 마감 ─────────────────────────────────

   Tabler의 핵심은 화면마다 다른 장식이 아니라 표·페이저·입력·팝업이
   같은 표면과 상태색을 공유하는 데 있다. DevExpress의 구조와 기능은
   그대로 두고, 공용 컴포넌트가 그리는 영역만 이 토큰으로 마감한다.
   ---------------------------------------------------------- */

:root {
  --jsini-success: #2fb344;
  --jsini-warning: #f59f00;
  --jsini-danger: #d63939;
  --jsini-info: #4299ff;
  /* Tabler light thead: #f6f8fb (bg-surface-tertiary) */
  --jsini-grid-header: #f0f4f8;
  --jsini-grid-hover: #edf3fd;
}

:root[data-theme='dark'] {
  /* Tabler dark thead: bg-surface-secondary / bg-surface-tertiary 계열 #1e3048~#243b57 */
  --jsini-grid-header: #1e3050;
  --jsini-grid-hover: #243b57;
}

/* ── 창은 `.jsini-shell__content` 밖에 있다 (실제로 확인) ───────

   이 마감 규칙들이 한동안 **본문에만** 걸려 있었다. 그런데 DevExpress 의
   창은 선언한 자리에 남지 않는다 — `dx-blazor-all.js` 를 뜯어 보면

       _createPopupElement() {
         const e = document.createElement('dxbl-popup-root');
         document.body.appendChild(e); …
       }

   창을 `<body>` 바로 밑의 `dxbl-popup-root` 로 **옮겨 담는다**(선언한
   자리에는 `dxbl-popup-portal` 이라는 빈 껍데기만 남는다).

   그래서 `.jsini-shell__content …` 로 적은 규칙은 **어느 창에도 닿지 않는다.**
   모서리·굵기 같은 이 마감이 창 안에서만 빠져서, 창은 남색
   (`--jsini-surface-raised` = `#1e3048`)인데 안의 입력 칸만 각지고 초점 테가
   달랐다. 칸이 스물 남짓인 메뉴 수정 창에서 가장 크게 보인다.

   그래서 본문과 **우리 창 둘**을 함께 적는다. `:is()` 한 마디로 묶지 않는
   이유는 이 파일의 다른 규칙들과 읽는 모양을 맞추기 위해서다.

   **바탕색은 여기서 다루지 않는다.** 그쪽은 선택자로는 이길 수 없어서
   DevExpress 변수로 덮는다 — 아래 「창·판의 바탕색은 선택자가 아니라
   토큰으로 준다」.

   DevExpress 자신의 드롭다운 목록 칸에는 이 마감을 얹지 않는다 —
   고른 줄 표시가 눌린다.
   ---------------------------------------------------------- */

/* 버튼·입력·선택 상자 */
.jsini-shell__content .dxbl-btn:not(.dxbl-btn-link),
.jsini-shell__content .dxbl-btn-group,
.jsini-shell__content .dxbl-text-edit,
.jsini-shell__content .dxbl-combobox,
.jsini-shell__content .dxbl-spin-edit,
.jsini-shell__content .dxbl-date-edit,
.jsini-popup .dxbl-btn:not(.dxbl-btn-link),
.jsini-popup .dxbl-btn-group,
.jsini-popup .dxbl-text-edit,
.jsini-popup .dxbl-combobox,
.jsini-popup .dxbl-spin-edit,
.jsini-popup .dxbl-date-edit,
.jsini-gridpopup .dxbl-btn:not(.dxbl-btn-link),
.jsini-gridpopup .dxbl-btn-group,
.jsini-gridpopup .dxbl-text-edit,
.jsini-gridpopup .dxbl-combobox,
.jsini-gridpopup .dxbl-spin-edit,
.jsini-gridpopup .dxbl-date-edit {
  border-radius: var(--jsini-radius-md);
}

.jsini-shell__content .dxbl-btn:not(.dxbl-btn-link),
.jsini-popup .dxbl-btn:not(.dxbl-btn-link),
.jsini-gridpopup .dxbl-btn:not(.dxbl-btn-link) {
  font-weight: 600;
}

.jsini-shell__content .dxbl-btn:not(.dxbl-btn-link):hover,
.jsini-popup .dxbl-btn:not(.dxbl-btn-link):hover,
.jsini-gridpopup .dxbl-btn:not(.dxbl-btn-link):hover {
  filter: brightness(1.04);
}

.jsini-shell__content .dxbl-text-edit:focus-within,
.jsini-shell__content .dxbl-combobox:focus-within,
.jsini-shell__content .dxbl-spin-edit:focus-within,
.jsini-shell__content .dxbl-date-edit:focus-within,
.jsini-popup .dxbl-text-edit:focus-within,
.jsini-popup .dxbl-combobox:focus-within,
.jsini-popup .dxbl-spin-edit:focus-within,
.jsini-popup .dxbl-date-edit:focus-within,
.jsini-gridpopup .dxbl-text-edit:focus-within,
.jsini-gridpopup .dxbl-combobox:focus-within,
.jsini-gridpopup .dxbl-spin-edit:focus-within,
.jsini-gridpopup .dxbl-date-edit:focus-within {
  box-shadow: 0 0 0 0.2rem var(--jsini-focus);
}

/* 조회 조건은 카드 안의 조용한 작업 영역으로 보이게 한다. */
.commsch {
  gap: var(--jsini-sp-md) var(--jsini-sp-xl);
  box-shadow: var(--jsini-shadow-card);
}

.commsch__actions .dxbl-btn {
  min-width: 4.5rem;
}

/* 표의 표면·머리·행 상태 */
.commgrd > .dxbl-grid,
.commtree > .dxbl-grid {
  background: var(--jsini-surface-raised);
  --jsini-field-label-bg: var(--jsini-surface-raised);
  box-shadow: var(--jsini-shadow-card);
}

.commgrd .dxbl-grid-table,
.commtree .dxbl-grid-table {
  color: var(--jsini-text);
}

.commgrd .dxbl-grid-header,
.commtree .dxbl-grid-header {
  background: var(--jsini-grid-header);
  border-top: 1px solid var(--jsini-border);
  border-bottom: 2px solid var(--jsini-border);
}

.commgrd .dxbl-grid-header-content,
.commtree .dxbl-grid-header-content {
  min-height: 2.75rem;
  padding: 0.6rem 0.75rem;
  /* Tabler: thead 텍스트는 muted보다 약간 선명한 중간 톤 */
  color: var(--jsini-text-muted);
  font-size: var(--jsini-fs-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.commgrd .dxbl-grid-table > tbody > tr,
.commtree .dxbl-grid-table > tbody > tr {
  border-bottom: 1px solid var(--jsini-border);
  transition: background-color 0.12s ease;
}

.commgrd .dxbl-grid-table > tbody > tr:last-child,
.commtree .dxbl-grid-table > tbody > tr:last-child {
  border-bottom: 0;
}

.commgrd .dxbl-grid-table > tbody > tr:hover,
.commtree .dxbl-grid-table > tbody > tr:hover {
  background-color: var(--jsini-grid-hover);
}

.commgrd .dxbl-grid-table > tbody > tr > td,
.commtree .dxbl-grid-table > tbody > tr > td {
  padding: 0.7rem 0.75rem;
  border-color: var(--jsini-border);
}

.commgrd .dxbl-grid .dxbl-grid-table > tbody > tr > td.dxbl-grid-fixed-cell,
.commtree .dxbl-grid .dxbl-grid-table > tbody > tr > td.dxbl-grid-fixed-cell {
  background-color: var(--jsini-surface-raised);
}

.commgrd .dxbl-grid .dxbl-grid-table > tbody > tr.commgrd__row--alt > td.dxbl-grid-fixed-cell,
.commtree .dxbl-grid .dxbl-grid-table > tbody > tr.commtree__row--alt > td.dxbl-grid-fixed-cell {
  background-color: color-mix(in srgb, var(--jsini-grid-header) 72%, var(--jsini-surface-raised));
}

.commgrd .dxbl-grid-table > tbody > tr.dxbl-grid-focused-row,
.commgrd .dxbl-grid-table > tbody > tr.dxbl-grid-selected-row,
.commtree .dxbl-grid-table > tbody > tr.dxbl-grid-focused-row,
.commtree .dxbl-grid-table > tbody > tr.dxbl-grid-selected-row {
  background-color: color-mix(in srgb, var(--jsini-accent) 14%, var(--jsini-surface-raised));
  box-shadow: inset 3px 0 0 var(--jsini-accent);
}

.commgrd .dxbl-grid-table > tbody > tr.dxbl-grid-focused-row > td.dxbl-grid-fixed-cell,
.commgrd .dxbl-grid-table > tbody > tr.dxbl-grid-selected-row > td.dxbl-grid-fixed-cell,
.commtree .dxbl-grid-table > tbody > tr.dxbl-grid-focused-row > td.dxbl-grid-fixed-cell,
.commtree .dxbl-grid-table > tbody > tr.dxbl-grid-selected-row > td.dxbl-grid-fixed-cell {
  background-color: inherit;
}

.commgrd__actions .dxbl-btn,
.commgrd__footer .dxbl-btn,
.commtree__actions .dxbl-btn,
.commtree__footer .dxbl-btn {
  width: 2rem;
  height: 2rem;
  border-radius: var(--jsini-radius-md);
}

.commgrd__actions .dxbl-btn:hover,
.commgrd__footer .dxbl-btn:hover,
.commtree__actions .dxbl-btn:hover,
.commtree__footer .dxbl-btn:hover {
  background: color-mix(in srgb, var(--jsini-accent) 12%, transparent);
}

.commgrd__icon--warn,
.commtree__icon--warn {
  color: var(--jsini-danger);
}

.commgrd__empty,
.commtree__empty {
  min-height: 9rem;
  display: grid;
  place-items: center;
  padding: var(--jsini-sp-2xl);
  background: var(--jsini-surface-raised);
}

/* DevExpress 페이저를 Tabler의 작은 알약형 페이지 네비게이션처럼 보이게 한다.
   실제 페이저의 세부 클래스가 테마별로 달라도 bottom-panel 아래의 버튼과
   링크에는 공통으로 적용된다. */
.commgrd:has(.dxbl-grid-bottom-panel) .dxbl-grid-bottom-panel {
  min-height: var(--commgrd-bar-height);
  gap: 0.35rem;
  padding: 0.4rem 0.65rem 0.4rem 10rem;
  border-top: 1px solid var(--jsini-border);
  background: var(--jsini-surface-raised);
  color: var(--jsini-text-muted);
}

.commgrd .dxbl-grid-bottom-panel :is(.dxbl-pager, .dxbl-pager-container) {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.commgrd .dxbl-grid-bottom-panel :is(button, a, .dxbl-btn) {
  min-width: 2rem;
  height: 2rem;
  padding: 0 0.55rem;
  border: 1px solid transparent;
  border-radius: var(--jsini-radius-md);
  background: transparent;
  color: var(--jsini-text-muted);
  font-size: var(--jsini-fs-sm);
  font-weight: 600;
  text-decoration: none;
}

.commgrd .dxbl-grid-bottom-panel :is(button, a, .dxbl-btn):hover {
  background: color-mix(in srgb, var(--jsini-accent) 10%, transparent);
  color: var(--jsini-accent-text);
}

.commgrd .dxbl-grid-bottom-panel :is(.dxbl-pager-page-item-active, .dxbl-pager-active, [aria-current='page']) {
  border-color: var(--jsini-accent);
  background: var(--jsini-accent);
  color: #fff;
}

.commgrd .dxbl-grid-bottom-panel :is(.dxbl-pager-page-item-disabled, :disabled, [aria-disabled='true']) {
  opacity: 0.45;
  cursor: default;
}

.commgrd .dxbl-grid-bottom-panel :is(.dxbl-pager-summary, .dxbl-pager-page-size) {
  color: var(--jsini-text-muted);
  font-size: var(--jsini-fs-xs);
}

/* 상태 배지 */
.jsini-badge {
  border: 0;
  border-radius: var(--jsini-radius-full);
  padding: 0.25rem 0.6rem;
  background: var(--jsini-surface-muted);
  font-size: var(--jsini-fs-xs);
  font-weight: 700;
}

.jsini-badge--on {
  background: color-mix(in srgb, var(--jsini-success) 15%, transparent);
  border-color: transparent;
  color: var(--jsini-success);
}

.jsini-badge--off {
  background: color-mix(in srgb, var(--jsini-text-muted) 13%, transparent);
  border-color: transparent;
}

.jsini-badge--warn {
  background: color-mix(in srgb, var(--jsini-warning) 16%, transparent);
  border-color: transparent;
  color: var(--jsini-warning);
}

.jsini-badge--err {
  background: color-mix(in srgb, var(--jsini-danger) 15%, transparent);
  border-color: transparent;
  color: var(--jsini-danger);
}

/* ── 창·판의 바탕색은 **선택자가 아니라 토큰**으로 준다 ────────

   [증상 — 시스템 색을 쓰는 창이 하나도 없었다]

   창 껍데기를 우리 색으로 칠하는 규칙이 여기 있었다.

       .jsini-popup .dxbl-modal-content { background: var(--jsini-surface-raised) }
       .jsini-popup .dxbl-modal-header  { background: var(--jsini-surface-raised) }
       .jsini-popup .dxbl-modal-footer  { background: var(--jsini-surface-muted) }

   **한 줄도 먹지 않았다.** 겨누는 힘(specificity)에서 진다 —
   DevExpress 테마(26.1)가 같은 자리를 이렇게 적어 둔다.

       .dxbl-modal > .dxbl-modal-root > .dxbl-popup > .dxbl-modal-content
       { background-color: var(--dxbl-popup-bg) }                       (0,4,0)

       … > .dxbl-modal-content > .dxbl-modal-header                     (0,5,0)
       … > .dxbl-modal-content > .dxbl-modal-body                       (0,5,0)
       … > .dxbl-modal-content > .dxbl-modal-footer                     (0,5,0)

   우리 쪽은 클래스 둘(0,2,0)이다. app.css 가 테마보다 뒤에 실려도
   **힘이 다르면 순서는 보지 않는다.** 그래서 포털의 창은 전부 —
   `CommPopup` 마흔몇 개도, 표의 편집 창도, 지움 확인 창도 —
   DevExpress 테마 색 그대로였다. 기본 테마(Fluent Dark)에서
   창은 중성 회색(`#252525`)인데 그 뒤의 화면은 남색(`#182433`)이다.

   드롭다운은 사정이 하나 더 나빴다. `.dxbl-dropdown-popup` 과
   `.dxbl-toast-container` 로 겨누고 있었는데 **26.1 에는 그런 클래스가
   아예 없다**(옛 판의 이름이다). 힘을 따질 것도 없이 아무것도 안 맞았다.

   [고치는 법 — 겨누기를 이기려 하지 않는다]

   선택자를 길게 늘여 이길 수도 있지만, 그러면 DevExpress 가 다음 판에서
   마크업을 한 겹 바꾸는 순간 말없이 풀린다(`jsini-gridpopup` 의 `!important`
   주석에 같은 이야기가 있다).

   **색은 전부 변수에서 나온다.** 위 규칙들이 읽는 `--dxbl-popup-bg` 를
   DevExpress 자신이 `.dxbl-modal`(0,1,0)에 세워 두므로, 같은 자리에 같은
   힘으로 우리 값을 적으면 **뒤에 실린 쪽이 이긴다.** 그러면 그 변수를 읽는
   모든 창이 한꺼번에 따라온다 — 우리가 만든 창뿐 아니라 DevExpress 가 스스로
   그리는 창(표의 지움 확인 · 열 고르기 · 일정 편집 · HtmlEditor 대화상자)까지.

   **이름이 테마 무리마다 같다**(재어 확인했다). Fluent 22가지는
   `--dxbl-popups-*` 를 거쳐 `--dxbl-popup-*` 로 내려오고 Classic·Bootstrap
   여섯은 `--dxbl-popup-*` 를 바로 쓴다. 우리는 **아래쪽 이름**만 덮으므로
   스물둘 중 무엇을 골라도 같게 걸린다.

   [재어 본 것 — 창 하나를 네 테마에 띄우고 계산된 색을 읽었다]

   기본 테마(Fluent Dark)의 메뉴 수정 창:

     | | 고치기 전 | 고친 뒤 |
     |---|---|---|
     | 껍데기(`.dxbl-popup`) | `#1e3048` | `#1e3048` |
     | 본문판(`.dxbl-modal-content`) | **`#252525`** | `#1e3048` |
     | 머리·본문 | **`#252525`** | `#1e3048` |
     | 바닥 띠 | **`#252525`** | `#142032` |
     | 드롭다운 목록 · 달력 | **`#252525`** | `#1e3048` |

   껍데기만 우리 색이고 **안은 전부 DevExpress 회색**이었다는 뜻이다.
   Fluent Light · Classic(blazing-dark) · Classic(office-white) 도 같은
   방법으로 확인했고, 넷 다 창·목록·달력이 한 표면으로 모인다.

   [색만 덮고 여백·모서리는 두지 않는다]

   지운 규칙에는 `padding` 과 `border-bottom` 도 있었지만 되살리지 않는다.
   그쪽은 DevExpress 의 크기 사다리(`--dxds-spacing-*`)가 쥐고 있고, 이 파일은
   맨 앞에서 그 사다리를 따르기로 해 두었다 — 여기서만 다른 여백을 박으면
   창 안의 폼과 그리드가 서로 어긋난다.
   ------------------------------------------------------------ */

.dxbl-modal,
.dxbl-window {
  --dxbl-popup-bg: var(--jsini-surface-raised);
  --dxbl-popup-color: var(--jsini-text);
  --dxbl-popup-border-color: var(--jsini-border);
  --dxbl-popup-header-bg: var(--jsini-surface-raised);
  --dxbl-popup-header-color: var(--jsini-text);
  --dxbl-popup-body-bg: var(--jsini-surface-raised);
  --dxbl-popup-body-color: var(--jsini-text);

  /* 바닥 띠만 한 단 눌러 앉는다. 단추가 서는 자리를 본문과 갈라 놓는
     것이고, 아이콘 고르개의 펼친 판이 같은 이유로 같은 값을 쓴다. */
  --dxbl-popup-footer-bg: var(--jsini-surface-muted);
  --dxbl-popup-footer-color: var(--jsini-text);

  /* DxWindow 는 아직 쓰는 화면이 없다. 그래도 함께 적어 두는 이유는,
     창을 하나 띄우는 순간 **그것만 회색으로 남는** 것이 이 작업 전의
     모습이기 때문이다. 값은 위와 같다. */
  --dxbl-window-bg: var(--jsini-surface-raised);
  --dxbl-window-color: var(--jsini-text);
  --dxbl-window-border-color: var(--jsini-border);
  --dxbl-window-header-bg: var(--jsini-surface-raised);
  --dxbl-window-header-color: var(--jsini-text);
  --dxbl-window-body-bg: var(--jsini-surface-raised);
  --dxbl-window-body-color: var(--jsini-text);
  --dxbl-window-footer-bg: var(--jsini-surface-muted);
  --dxbl-window-footer-color: var(--jsini-text);
}

/* 눌러서 펼치는 판도 창이다 — 목록(`DxComboBox` 161군데 · `DxTagBox`),
   달력(`DxDateEdit` 52군데), 오른쪽 단추 메뉴가 전부 여기로 뜬다.

   **창과 같은 표면을 쓴다.** 하나만 남겨 두면 그 화면에서 색 계열이 둘이
   된다 — 남색 창 위에 회색 목록이 뜨는 모양이다.

   달력을 함께 적는 이유: 달력은 자기 바탕(`--dxbl-calendar-bg`)을 따로
   갖고 있어서, 감싼 드롭다운만 바꾸면 **판 안에 회색 네모**가 남는다. */
.dxbl-dropdown,
.dxbl-itemlist-dropdown,
.dxbl-edit-dropdown,
.dxbl-context-menu,
.dxbl-context-menu-submenu,
.dxbl-menu,
.dxbl-menu-dropdown,
.dxbl-calendar {
  --dxbl-dropdown-bg: var(--jsini-surface-raised);
  --dxbl-edit-dropdown-bg: var(--jsini-surface-raised);
  --dxbl-edit-dropdown-color: var(--jsini-text);
  --dxbl-edit-dropdown-border-color: var(--jsini-border);
  --dxbl-context-menu-bg: var(--jsini-surface-raised);
  --dxbl-context-menu-color: var(--jsini-text);
  --dxbl-menu-bg: var(--jsini-surface-raised);
  --dxbl-menu-color: var(--jsini-text);
  --dxbl-calendar-bg: var(--jsini-surface-raised);
  --dxbl-calendar-color: var(--jsini-text);
  --dxbl-calendar-border-color: var(--jsini-border);
}

/* 창 바깥 껍데기(테두리·모서리·그림자). 이쪽은 DevExpress 가 `.dxbl-popup`
   하나(0,1,0)로만 적어 두어 **선택자로 이긴다** — 위 토큰과 달리 그대로 둔다.

   `--jsini-field-label-bg` 는 떠오르는 라벨이 테두리를 파고들 때 뒤에 까는
   색이다. 창 안에서는 본문 바탕(`--jsini-bg`)이 아니라 창의 표면이어야
   글자가 지워 낸 자리에 맞는다. */
.jsini-popup .dxbl-modal-content,
.jsini-gridpopup .dxbl-modal-content,
.dxbl-popup {
  --jsini-field-label-bg: var(--jsini-surface-raised);
}

.dxbl-popup {
  border: 1px solid var(--jsini-border);
  border-radius: var(--jsini-radius-lg);
  background: var(--jsini-surface-raised);
  box-shadow: 0 18px 48px rgb(0 0 0 / 24%);
}

/* 모바일 하단 네비게이션바 */
.jsini-bottom-nav {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    height: calc(var(--jsini-bottomnav-h) + env(safe-area-inset-bottom));
    padding-bottom: env(safe-area-inset-bottom);
    background-color: var(--jsini-bg, #ffffff);
    border-top: 1px solid var(--jsini-border, #e0e0e0);
    z-index: 1030;
    box-shadow: 0 -2px 10px rgba(0,0,0,0.05);

    /* 칸 글자의 좌우 여백(아래 `> span:last-child` 의 `0 2px`)이 폭을 꽉 채운
       칸에서 2px 넘친다. 띠가 `position: fixed` 라 쪽 스크롤이 생기지는
       않지만, 넘친 글자가 테두리 밖으로 비어져 나온다. */
    overflow: hidden;
}

.jsini-bottom-nav__item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    color: var(--jsini-text-muted, #6c757d);
    text-decoration: none;
    flex: 1;
    height: 100%;
    font-size: 11px;
    border: none;
    background: none;
    padding: 0;
    cursor: pointer;

    /* **이 한 줄이 칸을 줄어들게 한다.** flex 항목의 기본 최소 폭은
       `auto`(= 내용 폭)이고, 그 내용에는 `white-space: nowrap` 인 이름이
       들어 있다 — 그래서 이름이 긴 칸은 `flex: 1` 을 주어도 **제 글자 폭을
       쥐고 버틴다.**

       칸이 다섯일 때는 드러나지 않았다. 사람이 칸을 여덟까지 놓을 수 있게
       되면서(`BottomNav.MaxItems`) 320px 에 네 글자 이름 여덟을 채워 재 보니
       띠가 **376px** 이 되어 끝 칸(대개 「메뉴」 — 띠에서 못 닿는 화면을 전부
       여는 칸이다)이 화면 밖으로 밀려 아예 안 눌렸다.

       0 으로 두면 칸이 고르게 줄고, 남는 글자는 아래 규칙이 말줄임으로
       자른다. 같은 조건에서 다시 재니 320px·360px 모두 띠가 화면 폭에
       딱 맞고 끝 칸이 돌아왔다. */
    min-width: 0;
}

.jsini-bottom-nav__item:hover,
.jsini-bottom-nav__item:active {
    color: var(--jsini-accent-text, #007bff);
}

.jsini-bottom-nav__icon {
    font-size: 20px;
    margin-bottom: 4px;

    /* 아이콘 집안이 둘이다. 띠에 박아 둔 것은 `jsini-icon-*`(크기를
       `--jsini-icon-size` 로 잰다)이고, 사람이 고른 칸은 메뉴에서 온
       `jsini-mi--*`(크기를 `em` 으로 잰다)다. 둘을 같은 20px 로 맞춰 둔다 —
       안 맞추면 고른 칸만 미묘하게 크거나 작아 띠가 들쭉날쭉해 보인다. */
    --jsini-icon-size: 20px;
}

.jsini-bottom-nav__icon.jsini-mi {
    width: 20px;
    height: 20px;
}

/* 프로필 칸의 얼굴. **아이콘 자리를 그대로 쓴다**(20px + 아래 4px) — 조금만
   키워도 그 칸만 글자가 한 픽셀 내려앉아 띠가 어긋나 보인다.
   동그라미와 테두리는 헤더의 얼굴(`.jsini-user__face`)과 같은 뜻이다. */
.jsini-bottom-nav__avatar {
    width: 20px;
    height: 20px;
    margin-bottom: 4px;
    border-radius: 50%;
    object-fit: cover;
    background: color-mix(in srgb, currentColor 12%, transparent);
    flex: 0 0 auto;
}

/* 사진이 없는 계정이 훨씬 많다. 첫 글자를 넣어 **비어 보이지 않게** 한다
   (헤더와 같은 처리). 20px 동그라미라 글자는 사다리 맨 아래 단이다. */
.jsini-bottom-nav__initial {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--jsini-fs-2xs);
    font-weight: 700;
    line-height: 1;
}

/* 띠의 글자는 한 줄로 자른다. 이름을 사람이 짓는 자리라(환경설정) 길게
   적으면 칸 하나가 두 줄이 되고, 그 칸만 높아져 띠가 어긋난다. */
.jsini-bottom-nav__item > span:last-child {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding: 0 2px;
}

/* ── 이름을 감춘 띠 (`.jsini-shell--bottomnav-nolabel`) ──────────

   환경설정의 「아이콘만 보기」다. 아이콘 아래의 11px 짜리 이름 줄이 빠지고,
   띠는 그만큼 얇아진다(`--jsini-bottomnav-h` 60px → 44px). **얇아진 몫은
   본문이 가져간다** — 띠를 피해 서 있던 아홉 자리가 모두 그 토큰을 본다.

   [글자를 `display: none` 으로 지우지 않는다]

   칸은 `<button>` 이고 그 안에 든 것은 아이콘과 이름 둘뿐인데, 아이콘은
   `aria-hidden` 이고 얼굴 그림은 `alt=""` 다. 그래서 이름을 지우면
   **단추의 읽을 이름이 통째로 사라진다** — 화면 낭독기에는 「단추」만 다섯
   줄로 남고, 어느 것이 홈인지 알 길이 없다. 눈으로 보는 화면은 멀쩡하므로
   이 고장은 눈으로는 안 드러난다.

   자리만 뺏고 글자는 남긴다(1px 상자 + `clip-path`). 띠가
   `position: fixed` 라 이 `absolute` 는 띠를 기준으로 서고, 띠는
   `overflow: hidden` 이라 비어져 나올 자리도 없다. */
.jsini-shell--bottomnav-nolabel .jsini-bottom-nav__item > span:last-child {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* 아이콘 아래 틈(4px)도 함께 걷는다. 그 틈은 **이름과의 사이**를 벌리려고
   둔 것이라, 이름이 없는 띠에서는 아이콘을 4px 위로 밀어 올리기만 한다 —
   얼굴 그림(프로필 칸)도 같은 틈을 쓰므로 함께 적는다. */
.jsini-shell--bottomnav-nolabel .jsini-bottom-nav__icon,
.jsini-shell--bottomnav-nolabel .jsini-bottom-nav__avatar {
    margin-bottom: 0;
}

/* ── 띠가 꽉 찼을 때 무엇과 바꿀지 고르는 창 ─────────────────

   탭 메뉴의 「휴대폰 아래 띠에 넣기」가 다섯이 다 찬 채로 눌렸을 때 뜬다
   (`BottomNavSwapDialog`). 창은 휴대폰에서 열리는 일이 대부분이라 **줄을
   손가락으로 고르는 크기**로 짠다 — 44px 는 손가락 하나가 빗나가지 않는
   최소 높이다. */
.jsini-navswap__lead {
  margin-bottom: 0.75rem;
  line-height: 1.5;
}

.jsini-navswap__list {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

/* 줄 전체가 단추다. 아이콘만·글자만 눌리면 손가락으로는 자주 빗나간다. */
.jsini-navswap__row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  min-height: 44px;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--jsini-border);
  border-radius: var(--jsini-radius-md);
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.jsini-navswap__row:hover,
.jsini-navswap__row:focus-visible {
  background: color-mix(in srgb, currentcolor 8%, transparent);
}

/* 띠의 아이콘과 같은 규칙으로 크기를 맞춘다 — 집안이 둘이라
   (`jsini-icon-*` 와 `jsini-mi--*`) 한쪽만 맞추면 줄이 들쭉날쭉해진다. */
.jsini-navswap__icon {
  flex: 0 0 auto;
  font-size: 20px;
  --jsini-icon-size: 20px;
}

.jsini-navswap__icon.jsini-mi {
  width: 20px;
  height: 20px;
}

/* 이름이 길면 줄임표. 줄바꿈시키면 줄 높이가 제각각이 되어 다섯을
   한눈에 훑을 수 없다. */
.jsini-navswap__name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.jsini-navswap__slot {
  flex: 0 0 auto;
  font-size: var(--jsini-fs-2xs);
  color: var(--jsini-text-muted);
}

/* ── 떠다니는 단추를 오래 눌렀을 때 뜨는 창 (`FabHoldMenu`) ───

   그 단추의 자리와 감추기를 **그 자리에서** 고친다. 손가락으로 쓰는 창이라
   고르는 칸을 전부 44px 위로 잡는다(위 `navswap` 과 같은 까닭). */
.jsini-fabhold__lead {
  margin-bottom: 0.9rem;
  line-height: 1.5;
  color: var(--jsini-text-muted);
  font-size: var(--jsini-fs-sm);
}

.jsini-fabhold__group {
  margin-bottom: 0.4rem;
  font-size: var(--jsini-fs-2xs);
  font-weight: 700;
  color: var(--jsini-text-muted);
}

/* 네모 넷을 **화면처럼** 벌여 놓는다 — 두 칸씩 두 줄이라 격자의 자리가
   곧 화면의 귀퉁이다. 목록으로 늘어놓으면 글자를 읽어야 어디인지 안다. */
.jsini-fabhold__corners {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.4rem;
}

.jsini-fabhold__corner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  min-height: 44px;
  padding: 0.5rem 0.3rem;
  border: 1px solid var(--jsini-border);
  border-radius: var(--jsini-radius-md);
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.jsini-fabhold__corner:hover,
.jsini-fabhold__corner:focus-visible {
  background: color-mix(in srgb, currentcolor 8%, transparent);
}

/* 지금 서 있는 자리. **표시가 없으면 네 번 눌러 봐야** 어디인지 안다. */
.jsini-fabhold__corner.is-on {
  border-color: var(--jsini-accent);
  background: color-mix(in srgb, var(--jsini-accent) 12%, transparent);
  color: var(--jsini-accent);
}

/* 화면을 본뜬 작은 네모와 그 안의 점(= 단추). 가로세로 비가 휴대폰과
   비슷해야 「아래」가 아래로 읽힌다. */
.jsini-fabhold__box {
  position: relative;
  width: 2rem;
  height: 2.6rem;
  border: 1px solid currentcolor;
  border-radius: 0.25rem;
  opacity: 0.55;
}

.jsini-fabhold__dot {
  position: absolute;
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background: currentcolor;
}

.jsini-fabhold__corner--top-left .jsini-fabhold__dot {
  top: 0.22rem;
  left: 0.22rem;
}

.jsini-fabhold__corner--top-right .jsini-fabhold__dot {
  top: 0.22rem;
  right: 0.22rem;
}

.jsini-fabhold__corner--bottom-left .jsini-fabhold__dot {
  bottom: 0.22rem;
  left: 0.22rem;
}

.jsini-fabhold__corner--bottom-right .jsini-fabhold__dot {
  bottom: 0.22rem;
  right: 0.22rem;
}

.jsini-fabhold__corner-name {
  font-size: var(--jsini-fs-2xs);
  white-space: nowrap;
}

/* 아래 두 줄(감추기 · 환경설정). 귀퉁이 넷과 **선 하나로 가른다** —
   자리를 고르는 일과 화면을 떠나는 일은 되돌리는 품이 다르다. */
.jsini-fabhold__acts {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  margin-top: 0.9rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--jsini-border);
}

.jsini-fabhold__act {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  min-height: 44px;
  padding: 0.4rem 0.5rem;
  border: 0;
  border-radius: var(--jsini-radius-md);
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.jsini-fabhold__act:hover,
.jsini-fabhold__act:focus-visible {
  background: color-mix(in srgb, currentcolor 8%, transparent);
}

.jsini-fabhold__act [class^='jsini-icon-'] {
  flex: 0 0 auto;
  --jsini-icon-size: 20px;
}

.jsini-fabhold__act-text {
  flex: 1;
  min-width: 0;
}

@media (max-width: 767px) {
    .jsini-bottom-nav {
        display: flex;
    }

    /* 환경설정에서 **띠를 쓰지 않기로 한 사람**(`jsini-bottomnav-hidden`).
       표시를 붙이는 쪽은 `MainLayout.ShellCss` 다.

       띠 자체는 레이아웃이 안 그리므로 여기서 감출 것이 없다. **되돌릴 것은
       띠가 비켜 달라고 만들어 둔 자리**다 — 본문 아래 여백, 떠다니는 단추를
       띠 높이 + 16px 위로 올린 것, 사이드바 트리 아래 여백, AI 대화 서랍의
       아래 끝 넷. 안 되돌리면 띠는 사라졌는데 화면 아래로 그 높이가 빈 채로
       남고, 그 자리에서 사람은 「안 없어졌다」로 읽는다.

       **여기 적힌 숫자는 `--jsini-bottomnav-h` 를 보지 않는다.** 띠가 없는
       자리에 남기는 숨 쉴 틈이라, 이름을 감춰 띠가 얇아진다고 함께 줄 값이
       아니다 — 애초에 띠가 없으므로 그 표시도 안 붙는다
       (`MainLayout.ShellCss` 가 둘 중 하나만 돌려준다). */
    .jsini-shell--bottomnav-hidden .jsini-shell__content {
        padding-bottom: calc(0.75rem + env(safe-area-inset-bottom)) !important;
    }

    .jsini-shell--bottomnav-hidden .jsini-shell__fab--bottom-left,
    .jsini-shell--bottomnav-hidden .jsini-shell__fab--bottom-right,
    .jsini-shell--bottomnav-hidden .jsini-shell__fab--scroll-top {
        bottom: calc(0.75rem + env(safe-area-inset-bottom)) !important;
    }

    .jsini-shell--bottomnav-hidden .jsini-sidebar__tree .dxbl-scroll-viewer {
        padding-bottom: calc(60px + env(safe-area-inset-bottom)) !important;
    }

    /* 띠를 안 쓰면 서랍도 바닥까지 내려온다 — 위 셋과 같은 되돌림이다. */
    .jsini-shell--bottomnav-hidden .jsini-right-drawer {
        bottom: 0;
    }

    /* ── 오른쪽 서랍이 띠를 피한다 ──────────────────────────

       서랍은 `bottom: 0` 이라 띠와 같은 자리를 쓰는데, **띠가 위에 그려져서**
       글상자가 가려졌다. 쌓는 순서를 올려서 될 일이 아니다 — 서랍은 헤더
       안에 있고 헤더가 `z-index: 1030` 으로 제 쌓임 맥락을 만들어, 그 안의
       1040 은 맥락 바깥의 띠(1030, DOM 에서 뒤)를 못 넘는다. 알림함 서랍이
       격자 바깥으로 나간 것과 같은 벽이다.

       390×844 에서 재 보면 띠(위 783)가 글상자(767~802)를 19px 먹고,
       글상자 한가운데를 눌러도 `elementFromPoint` 는 띠의 칸을 집는다 —
       **가려 보이는 것이 아니라 실제로 안 눌렸다.** 발의 안내 줄(814~844)은
       통째로 띠 뒤였다.

       그래서 서랍 아래 끝을 띠 높이만큼 끌어올린다. 띠를 덮는 쪽으로 가지
       않는 이유: 덮으려면 서랍을 헤더 밖으로 꺼내야 하고(단추와 판이
       갈라진다), 띠가 남아 있으면 대화를 열어 둔 채 다른 화면으로 옮겨 갈
       수 있다 — 서랍의 요점인 「보던 화면을 그대로 두고 옆에서 묻는다」와
       같은 결이다.

       값은 띠의 높이와 같다(`.jsini-bottom-nav`). 띠가 안전 영역만큼
       두꺼워지므로 그 몫도 함께 센다. */
    .jsini-right-drawer {
        bottom: calc(var(--jsini-bottomnav-h) + env(safe-area-inset-bottom));
    }

    
    /* 위 본문 여백 규칙과 같은 값이다. 여기 것이 뒤에 있어 실제로 쓰이는 쪽이라
       둘을 어긋나게 두면 한쪽만 고쳤을 때 조용히 옛 값으로 돌아간다 —
       그래서 숫자를 베껴 적지 않고 같은 토큰을 본다. */
    .jsini-shell__content {
        padding-bottom: calc(var(--jsini-bottomnav-h) + env(safe-area-inset-bottom) + var(--jsini-shell-pad-bottom)) !important;
    }
    
    .jsini-shell__fab--bottom-left, 
    .jsini-shell__fab--bottom-right,
    .jsini-shell__fab--scroll-top {
        bottom: calc(var(--jsini-bottomnav-h) + 16px) !important;
    }

    /* ── 업무 화면의 단추 줄이 **맨 아래 칸**을 갖는다 (2026-10-11) ──

       [무엇이 어긋나 있었나]

       바닥에 붙박이는 단추 줄(`.jsini-actbar` — 지시 한 건 화면의
       「이어서 지시 · 재시도 · 확인」)은 `position: sticky` 라 **제 자리를
       본문의 안쪽 상자에서 센다.** 그런데 그 상자는 위 규칙이 하단 띠만큼
       (60px + 1rem) 이미 비워 둔 뒤이고, 줄은 거기에 띠 높이를 **한 번 더**
       더하고 있었다. 390×844 에서 재 보면 줄의 아래끝이 바닥에서 136px —
       떠다니는 단추(76~124px)보다 **위**다.

       그래서 화면을 쓰는 사람에게는 ☰ 와 ✎ 가 제일 아래에 있고 정작 그
       화면의 단추가 그 위에 떠 있는 꼴로 보였다. 손가락이 먼저 닿는 자리를
       셸의 보조 단추가 가져간 셈이다.

       [어떻게 되돌리나 — 자리 비킴을 한 번만 센다]

       **본문**은 띠 몫(60px)만 비우고 숨 쉴 틈(1rem)은 걷는다. 그 틈은
       단추 줄이 제 여백으로 이미 갖고 있어서 두 겹이 된다.
       **단추 줄**은 거기에 `bottom: 0` 으로 붙는다(projmng.css). 둘을
       더하면 줄의 아래끝이 띠의 윗금에 정확히 맞는다.
       **떠다니는 단추**는 그 줄 높이(`--jsini-actbar-height`)만큼 올라선다.

       `:has()` 로 **그런 줄이 있는 화면에서만** 건다 — 없는 화면 백몇 개는
       지금 그대로다. 줄 자신은 같은 토큰으로 `min-height` 를 지키므로
       (projmng.css) 여기서 미는 거리와 어긋나지 않는다.

       [「위로 가기」도 셋 다 함께 센다]

       그 단추는 자리를 가리키는 이름(`--bottom-right`)을 안 달고 제 규칙으로
       오른쪽 아래에 선다. 그래서 **위의 76px 규칙이 집지 못했고**, 휴대폰에서
       바닥에서 12px — 하단 띠 위에 겹쳐 떠 있었다(단추가 띠보다 위층이라
       띠의 칸 하나를 가린다). 띠를 피하는 줄과 단추 줄을 피하는 줄 둘 다에
       이름을 함께 적어 셋이 같은 층계를 쓰게 한다. */
    .jsini-shell__content:has(.jsini-actbar) {
        padding-bottom: calc(var(--jsini-bottomnav-h) + env(safe-area-inset-bottom)) !important;
    }

    /* 줄은 **판의 아래 끝에 닿아야** 한다. 판(`CommCont`)의 안쪽 여백이 그
       밑에 남으면, 구를 것이 없는 짧은 화면에서 줄이 띠 위로 그만큼 떠서
       선다 — 재어 보니 17px 이었다(여백 16 + 테두리 1). 붙박인 줄은 구르는
       동안에는 띠에 붙어 있다가 **끝까지 내려간 순간 그만큼 올라서는** 꼴로
       보이기도 했다. 바닥 칸을 가진 동안에는 그 여백을 줄이 제 몫으로
       갖는다(위아래 0.5rem).

       **판 아래의 틈(`margin-bottom`)도 같이 걷는다** — 휴대폰에서 판과 판
       사이를 벌리려고 테마가 두는 8px 인데(tabler.css), 바닥 칸을 가진 판
       아래에는 이어질 판이 없다. 두면 줄이 그만큼 또 떠서, 테마를 입힌
       화면에서만 9px 이 남았다(여백 8 + 테두리 1). */
    .commcont:has(> .commcont__body > .jsini-actbar) {
        padding-bottom: 0;
        margin-bottom: 0;
    }

    .jsini-shell:has(.jsini-actbar) .jsini-shell__fab--bottom-left,
    .jsini-shell:has(.jsini-actbar) .jsini-shell__fab--bottom-right,
    .jsini-shell:has(.jsini-actbar) .jsini-shell__fab--scroll-top {
        bottom: calc(var(--jsini-bottomnav-h) + 16px + var(--jsini-actbar-height)) !important;
    }

    /* 띠를 쓰지 않기로 한 사람. 위 되돌림 셋과 같은 결이다 — 띠가 없으면
       단추 줄이 **바닥까지** 내려가고, 떠다니는 단추는 그 줄 위에 선다.
       선택자 무게가 위 두 줄과 같아서 **뒤에 둔 이것이 이긴다.** */
    .jsini-shell--bottomnav-hidden .jsini-shell__content:has(.jsini-actbar) {
        padding-bottom: env(safe-area-inset-bottom) !important;
    }

    .jsini-shell--bottomnav-hidden:has(.jsini-actbar) .jsini-shell__fab--bottom-left,
    .jsini-shell--bottomnav-hidden:has(.jsini-actbar) .jsini-shell__fab--bottom-right,
    .jsini-shell--bottomnav-hidden:has(.jsini-actbar) .jsini-shell__fab--scroll-top {
        bottom: calc(0.75rem + env(safe-area-inset-bottom) + var(--jsini-actbar-height)) !important;
    }

    .jsini-sidebar__tree .dxbl-scroll-viewer {
        padding-bottom: calc(var(--jsini-bottomnav-h) + 60px + env(safe-area-inset-bottom)) !important;
    }
}

/* ── 알림 보내기 창 (`NotifySendPopup`) ───────────────────────

   업무 화면 어디서나 뜨는 창이라 **모듈 CSS 가 아니라 여기** 있다. 모듈에
   두면 같은 창이 모듈마다 조금씩 다르게 생긴다.

   칸 이름은 `jsini-notify__*` 다 — 창 안에서만 쓰는 이름이라 바깥과 겹칠
   일이 없고, 겹치면 그것이 곧 다른 화면이 이 창의 모양을 빌려 쓴 것이다.
   ---------------------------------------------------------- */

.jsini-notify__who {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  margin-bottom: 0.8rem;
}

.jsini-notify__who-label {
  font-size: var(--jsini-fs-xs);
  font-weight: 600;
  color: var(--jsini-text-muted);
}

/* 받는 사람 한 명. 얼굴과 이름을 한 알로 묶는다 — 이름만 쉼표로 이어 두면
   여럿일 때 어디까지가 한 사람인지 눈으로 안 갈린다. */
.jsini-notify__chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.15rem 0.5rem 0.15rem 0.15rem;
  border: 1px solid var(--jsini-border);
  border-radius: 999px;
  background: var(--jsini-bg);
  font-size: var(--jsini-fs-sm);
}

.jsini-notify__face {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  border-radius: 50%;
  object-fit: cover;
  background: var(--jsini-surface-muted, rgba(127, 127, 127, 0.18));
  font-size: var(--jsini-fs-2xs);
  font-weight: 700;
}

.jsini-notify__face--text {
  color: var(--jsini-text-muted);
}

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

/* 보낼 길. 체크 둘이라 한 줄이면 충분하다. */
.jsini-notify__ways {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding-bottom: 0.6rem;
  margin-bottom: 0.6rem;
  border-bottom: 1px solid var(--jsini-border);
}

.jsini-notify__field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin-bottom: 0.7rem;
}

.jsini-notify__label {
  font-size: var(--jsini-fs-xs);
  font-weight: 600;
  color: var(--jsini-text-muted);
}

/* 입력 칸은 창 폭을 그대로 쓴다. DevExpress 가 제 내용만큼만 넓히므로
   적어 두지 않으면 짧은 칸 두 개가 왼쪽에 붙어 버린다. */
.jsini-notify__input {
  width: 100%;
}

/* ── 토스트는 꼭 맞게, 한 줄로 선다 ───────────────────────────

   [무엇이 컸나]

   판(DevExpress)의 기본 치수는 **알림 하나에 한 문단**을 담는 크기다.
   그런데 우리가 담는 것은 「저장했습니다.」 같은 **한 토막**이고
   제목(`Header`)은 아예 쓰지 않는다(`Toasts`). 그 결과 글자 한 줄짜리
   토스트가 **300px 폭에 62px 높이**로 섰다 — 글자가 차지하는 20px
   말고는 전부 여백이었고, 셋이 겹치면 화면 오른쪽 아래가 280px 넘게
   덮였다(헤드리스로 재었다).

   [세 군데를 줄인다]

   1. **폭은 글자만큼만.** 판이 준 폭(`Width`, 300px)은 이제 **상한**이다.
      「저장했습니다.」는 그 절반도 안 되는 폭으로 서고, 서버 이유가 붙는
      긴 문구만 상한까지 늘어난 뒤 줄바꿈한다. 사람이 고른 자리(왼쪽 ·
      가운데 · 오른쪽)에 붙여 세우는 일은 `margin: auto` 가 한다 —
      판의 정렬 클래스를 그대로 읽으므로 자리를 바꿔도 따라온다.
   2. **한 줄에 모두 선다.** 아이콘·글·닫기를 가운데로 맞추고
      (`align-items`), 판이 제목을 전제하고 넣어 둔 위쪽 여백
      (`--dxbl-toast-icon-top-padding` · `--dxbl-toast-text-content-top-padding`)
      을 0 으로 돌린다. 제목이 없으므로 그 여백은 글을 **아래로 밀기만** 한다.
   3. **여백과 글자를 한 단 낮춘다.** 본문 여백·사이 간격·띠 폭을 줄이고
      글자는 우리 사다리의 `--jsini-fs-xs` 로 맞춘다. 사다리를 쓰므로
      서랍에서 고른 크기(여섯 단계)를 그대로 따라간다 — 고정 px 로 적으면
      「가장작게」를 고른 사람 화면에서만 토스트가 혼자 크다.

   [그 뒤로 한 단을 더 걷었다]

   위 셋으로 162×33 까지 왔는데(「보통」 기준, 헤드리스 측정), 그래도
   **글자가 차지하는 것은 그중 70px** 뿐이었다. 남은 자리를 넷에서 더 줄인다.

   4. **글자를 `--jsini-fs-2xs` 로 한 단 더 내린다.** 토스트는 읽는 글이
      아니라 **눈길 한 번으로 확인하는 표시**라, 본문과 같은 급일 까닭이 없다.
      아이콘·닫기 그림도 같은 사다리를 따라가므로 함께 작아진다.
   5. **줄 바깥의 빈 틈을 걷는다.** 아이콘 칸과 닫기 단추가 `inline` 이라
      **글자 줄높이만큼 위아래가 부풀어 있었다** — 아이콘은 14.4px 인데
      칸은 23.4px 였다. 둘을 `flex` 로 세우고 줄높이를 0 으로 돌리면
      그 부푼 몫이 그대로 사라진다. 높이를 정하는 것이 이 둘이라(글자는
      16px) 토스트 높이가 바로 따라 내려온다.
   6. **짧은 토스트에서 남은 초를 뺀다.** 2초짜리를 「2초 · 1초」로 세는 것은
      **할 수 있는 일이 없는 숫자**고, 그 두 글자가 폭의 6분의 1이었다.
      줄어드는 막대는 그대로 두므로 「곧 걷힌다」는 여전히 보인다. 숫자는
      읽고 옮겨 적어야 하는 쪽(60초)에만 남는다.
   7. **바깥 여백도 함께 줄인다.** 판이 작아진 만큼 모서리에서 떼어 놓는
      거리와 겹칠 때의 사이도 줄인다 — 안 줄이면 토스트보다 여백이 커진다.

   줄이지 **않은** 것 둘. 색과 그림자는 테마가 쥔 그대로 두고(`Toasts`
   머리말), 닫기 단추의 **누를 자리는 손가락 폭을 지킨다** — 작게 만들면
   휴대폰에서 빗나가고, 그것이 쓸어 치우기를 넣은 까닭이었다. 그래서
   그림만 줄이고, 손가락으로 쓰는 화면(`pointer: coarse`)에서는 여백을
   도로 넓혀 누를 자리를 되돌린다.
   ------------------------------------------------------------ */

/* 판을 화면 모서리에서 떼어 놓는 여백. 토스트가 작아진 만큼 함께 줄인다. */
.dxbl-toast-portal {
  --dxbl-toast-bound-margin: 0.375rem;
  --dxbl-toast-bound-padding-right: 0.375rem;
  --dxbl-toast-bound-padding-bottom: 0.375rem;
}

.dxbl-toast-portal .dxbl-toast {
  --dxbl-toast-margin-top: 0.1875rem;
  --dxbl-toast-accent-line-width: 0.125rem;
  --dxbl-toast-body-padding: 0.15rem 0.3rem 0.15rem 0.35rem;
  --dxbl-toast-content-gap: 0.25rem;
  --dxbl-toast-text-content-gap: 0.1rem;
  --dxbl-toast-icon-top-padding: 0;
  --dxbl-toast-text-content-top-padding: 0;
  --dxbl-toast-template-padding: 0;
  --dxbl-toast-template-gap: 0.25rem;
  --dxbl-toast-text-font-size: var(--jsini-fs-2xs);
  --dxbl-toast-text-line-height: 1.3;
  --dxbl-toast-header-font-size: var(--jsini-fs-xs);
  --dxbl-toast-header-line-height: 1.3;

  /* 판이 준 폭은 상한으로만 쓴다. `max-content` 는 줄바꿈 없이 재므로
     긴 문구에서는 `max-width` 에 걸려 그 자리에서 접힌다. */
  width: max-content;
  max-width: 100%;
}

/* 고른 자리 쪽에 붙여 세운다. 왼쪽은 적을 것이 없다 — 블록 상자의
   기본이 그쪽이다. */
.dxbl-toast-portal.dxbl-toast-align-right .dxbl-toast {
  margin-left: auto;
}

.dxbl-toast-portal.dxbl-toast-align-center .dxbl-toast {
  margin-left: auto;
  margin-right: auto;
}

/* 아이콘·글·닫기를 한 줄에 세운다. 기본은 `stretch` 라 아이콘이 위에
   붙고 글만 길어지면 둘의 높이가 어긋나 보인다. */
.dxbl-toast-portal .dxbl-toast > .dxbl-toast-body {
  align-items: center;
}

/* 아이콘 칸. **글자 줄높이만큼 부풀어 있었다** — 그림은 14px 인데 칸이
   23px 라, 토스트 높이를 정하는 것이 글이 아니라 이 빈 틈이었다. 안에
   든 것이 `<svg>` 하나뿐이라 `inline` 으로 설 까닭이 없다. */
.dxbl-toast-portal .dxbl-toast .dxbl-toast-icon-column {
  display: flex;
  align-items: center;
  line-height: 0;
}

/* 아이콘은 글자 사다리를 따라간다. 판의 기본(16px)은 우리 글자보다 커서
   한 줄 안에서 혼자 눈에 띈다. */
.dxbl-toast-portal .dxbl-toast .dxbl-toast-icon-column .dxbl-toast-icon {
  width: calc(var(--jsini-fs-2xs) * 1.2);
  height: calc(var(--jsini-fs-2xs) * 1.2);
}

/* **그림 둘레의 2px 를 걷는다.** `.dxbl-image` 는 글줄 안에 섞여 설 때를
   생각해 위아래 여백을 달고 나오는데(`margin: 2px 0`), 여기서는 둘 다
   제 칸을 혼자 쓰는 `flex` 아이템이라 그 여백이 **토스트 높이에 4px 를
   그대로 얹는다** — 높이를 정하는 것이 글이 아니라 이 둘이라 바로 보인다. */
.dxbl-toast-portal .dxbl-toast .dxbl-toast-icon-column .dxbl-toast-icon,
.dxbl-toast-portal .dxbl-toast .dxbl-toast-close svg.dxbl-toast-close-btn {
  margin: 0;
}

/* 닫기 단추. **그림만 줄이고 누를 자리는 남긴다** — 휴대폰에서 빗나가는
   것이 쓸어 치우기를 넣은 까닭이었다(「토스트 쓸어 치우기」).

   단추도 아이콘 칸과 같은 이유로 부풀어 있었다(그림 13px · 단추 23px).
   `flex` + `line-height: 0` 으로 그 몫을 걷고, 남길 여백은 아래에서
   **누르는 손에 따라** 따로 준다. */
.dxbl-toast-portal .dxbl-toast .dxbl-toast-close button.dxbl-toast-close-btn {
  --dxbl-btn-padding-x: 0.2rem;
  --dxbl-btn-padding-y: 0.2rem;

  display: flex;
  align-items: center;
  line-height: 0;
}

/* 손가락으로 쓰는 화면에서는 누를 자리를 되돌린다. 마우스는 몇 픽셀을
   정확히 짚지만 손가락은 못 짚는다 — 그래서 **좁히는 것은 마우스 쪽만**이다.
   여백만 넓히므로 그림 크기는 그대로고, 늘어나는 것은 토스트 높이 2~3px 다. */
@media (pointer: coarse) {
  .dxbl-toast-portal .dxbl-toast .dxbl-toast-close button.dxbl-toast-close-btn {
    --dxbl-btn-padding-x: 0.3rem;
    --dxbl-btn-padding-y: 0.3rem;
  }
}

.dxbl-toast-portal .dxbl-toast .dxbl-toast-close svg.dxbl-toast-close-btn {
  width: calc(var(--jsini-fs-2xs) * 1.1);
  height: calc(var(--jsini-fs-2xs) * 1.1);
}

/* ── 토스트의 남은 시간 (`Toasts`) ─────────────────────────────

   [무엇이 없었나]

   알림은 잠깐 떴다 지고 실패는 **영영 남아** 있었다. 둘 다 같은 것을
   말해 주지 않는다 — **언제 없어지는가**. 읽는 중에 사라져 버리거나,
   반대로 언제까지 붙어 있을지 몰라 손으로 닫게 된다.

   [판은 그대로 두고 표시만 덧그린다]

   DevExpress 에 이 표시가 없다. 서비스로 띄우는 길이 받는 것(`ToastOptions`)은
   글·색·시간뿐이고 내용을 갈아 끼우는 `Template` 은 `DxToast` 쪽에만 있다.
   그래서 **토스트를 손으로 만들지 않고**(`ToastTests`) `Toasts` 가 붙이는
   클래스 하나에 기대어 가상 요소 둘로 얹는다 — 테마 스물둘의 색과 고른
   크기는 그대로 따라간다.

   [초는 여기서 정하지 않는다]

   얼마나 떠 있는지는 `Toasts` 가 정하고 이 파일은 **그 값에 맞는 칸**을
   갖고 있을 뿐이다. 둘이 어긋나면 막대가 다 줄어든 뒤에도 토스트가 남거나
   그 반대가 되므로, 시간표가 양쪽에 다 있는지는 테스트가 대조한다
   (`ToastTests.남은_시간표가_CSS_에도_있다`). */

/* 남은 초를 세는 값. 등록해 두어야 정수로 **사이값이 이어진다** —
   그냥 쓰면 애니메이션이 0 과 60 사이를 건너뛴다. */
@property --jsini-toast-left {
  syntax: '<integer>';
  inherits: false;
  initial-value: 0;
}

.dxbl-toast.jsini-toast-time {
  position: relative;

  /* 막대가 둥근 모서리를 넘어가지 않게 한다. */
  overflow: hidden;
}

/* 줄어드는 막대. 2px 짜리 띠 하나라 width 로 줄여도 값이 싸다.

   **한 줄짜리 토스트의 높이를 늘리지 않는다** — 본문 **아래에 겹쳐**
   깔리고 자리를 따로 비우지 않는다. 한동안 여기에 1.4rem 을 비워 두었는데
   (아래 「남은 초」가 그 자리에 앉아 있었다), 그 한 줄이 토스트 높이의
   3분의 1이었다. */
.dxbl-toast.jsini-toast-time::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 2px;
  background: var(--dxbl-toast-accent-line-color, currentColor);
  animation: jsini-toast-drain var(--jsini-toast-span) linear forwards;
}

/* 남은 초. 왼쪽 띠와 같은 색을 쓰지 않는다 — 실패의 빨강으로 적으면
   문구보다 숫자가 먼저 읽힌다.

   [왜 토스트가 아니라 닫기 단추에 붙나]

   예전에는 토스트의 `::before` 를 **본문 아래 오른쪽 구석에 절대 배치**
   했다. 그러려면 그 줄만큼 본문 아래를 비워야 하고(위의 1.4rem),
   한 줄짜리 「저장했습니다」가 두 줄 높이가 된다.

   닫기 단추 칸에 붙이면 **이미 있는 줄 안**에 들어간다 — 비울 자리도
   없고, 읽는 차례도 「문구 → 남은 초 → 닫기」로 자연스럽다. 그 칸은
   판이 늘 그리므로(`ShowCloseButton` 기본값) 사라질 걱정이 없다. */
.dxbl-toast.jsini-toast-time .dxbl-toast-close {
  display: flex;
  align-items: center;
  gap: 0.15rem;
}

.dxbl-toast.jsini-toast-time .dxbl-toast-close::before {
  content: counter(jsini-toast-left) '초';
  counter-reset: jsini-toast-left var(--jsini-toast-left);
  font-size: var(--jsini-fs-2xs);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  opacity: 0.6;
  animation: jsini-toast-count var(--jsini-toast-span)
             steps(var(--jsini-toast-secs), end) forwards;

  /* 「2」와 「초」가 갈라져 두 줄이 되지 않게 한다 — 닫기 칸은 좁다. */
  white-space: nowrap;
  flex: none;

  /* **자리를 미리 잡아 둔다.** 토스트 폭이 글자만큼이라(위 「꼭 맞게」)
     숫자가 한 자리로 줄면 토스트가 그만큼 좁아진다 — 60초짜리 실패는
     1분 동안 폭이 두 번 움찔한다. 가장 긴 숫자만큼 비워 두고 오른쪽에
     붙이면 그 동안 폭이 한 번도 안 바뀐다. */
  min-width: var(--jsini-toast-digits, 2.6em);
  text-align: right;
}

/* **짧은 토스트에는 숫자를 안 쓴다.** 2초를 「2초 · 1초」로 세어 봐야
   그 사이에 할 수 있는 일이 없고, 그 두 글자가 토스트 폭의 6분의 1이다
   (같은 문구로 재어 147.8 → 117px). 줄어드는 막대는 그대로 두므로 「곧 걷힌다」는
   여전히 보이고, 숫자는 읽고 옮겨 적어야 하는 쪽(60초)에만 남는다.

   칸(`--jsini-toast-span`)은 그대로 둔다 — 막대가 그 값으로 줄어든다. */
.dxbl-toast.jsini-toast-time--2s .dxbl-toast-close::before {
  content: none;
}

/* 눌러 둔 동안은 멈춘다. DevExpress 가 누른 토스트를 **닫지 않으므로**
   (`MainLayout` 의 `FreezeOnClick`) 표시만 계속 줄면 화면이 거짓말을 한다.
   이 표시를 붙이는 것은 theme.js 다. */
.dxbl-toast.jsini-toast-time--held .dxbl-toast-close::before,
.dxbl-toast.jsini-toast-time--held::after {
  animation-play-state: paused;
}

@keyframes jsini-toast-drain {
  from {
    width: 100%;
  }

  to {
    width: 0;
  }
}

/* 마지막 1초를 「1초」로 보여 주려고 걸음으로 센다. 고르게 이으면 절반쯤
   지난 뒤부터 「0초」가 떠서, **아직 남았는데 끝난 것처럼** 보인다. */
@keyframes jsini-toast-count {
  from {
    --jsini-toast-left: var(--jsini-toast-secs);
  }

  to {
    --jsini-toast-left: 0;
  }
}

/* `Toasts` 가 붙이는 시간표. 없는 초를 붙이면 막대가 아예 그려지지 않는다.
   `--jsini-toast-digits` 는 **숫자가 설 자리**다 — 자릿수가 하나 늘면
   여기도 함께 늘린다(안 늘리면 숫자가 잘린다). 짧은 쪽에는 그 칸이 없다 —
   숫자를 아예 안 그리기 때문이다(바로 위). */
.dxbl-toast.jsini-toast-time--2s {
  --jsini-toast-span: 2s;
  --jsini-toast-secs: 2;
}

.dxbl-toast.jsini-toast-time--60s {
  --jsini-toast-span: 60s;
  --jsini-toast-secs: 60;
  --jsini-toast-digits: 2.6em;
}

/* ── 토스트 쓸어 치우기 (`theme.js` 의 「쓸어 치우기」) ─────────

   [무엇이 불편했나]

   알림은 5초, 실패는 60초를 머문다(`Toasts`). **다 읽은 뒤 남는 시간**은
   쓸모가 없는데, 치우려면 오른쪽 위 닫기 단추를 정확히 눌러야 한다 —
   그 단추가 손가락보다 작아서 휴대폰에서는 두어 번 빗나간다. 그래서
   대개 그냥 기다리고, 넷까지 쌓이면(`MaxToastCount`) 아래 것이 가린다.

   [손가락이 가는 대로 둔다]

   옆으로 쓸면 바로 걷힌다. 끄는 동안은 토스트가 손가락을 그대로 따라가고
   (transform 을 theme.js 가 직접 적는다), 손을 뗄 때 충분히 갔으면 마저
   날아가고 모자라면 제자리로 돌아온다. 왼쪽·오른쪽 둘 다 받는다 — 판의
   자리를 사람이 고를 수 있어(환경설정의 토스트 위치) **바깥쪽이 어느
   쪽인지 정해져 있지 않다.**

   [마우스는 건드리지 않는다]

   실패 문구는 **끌어서 고르고 복사하는** 것이 쓰임새다(`Toasts` 머리말).
   마우스 끌기를 가로채면 그것이 막힌다. 그래서 손가락과 펜에서만 받는다. */

/* 가로는 우리가 받고 세로는 브라우저에 남긴다. 이 줄이 없으면 첫 움직임을
   브라우저가 스크롤로 채 가서 토스트가 손가락을 안 따라온다. */
.dxbl-toast {
  touch-action: pan-y;
}

/* 손을 뗀 뒤 — 날아가거나 제자리로 돌아오는 동안만 곁들인다. 끄는 동안은
   이 클래스가 없어야 손가락과 어긋나지 않는다. */
.dxbl-toast.jsini-toast-swipe--settling {
  transition: transform 0.18s ease-out, opacity 0.18s ease-out;
}

/* 판이 그리는 등장 애니메이션(`--dxbl-toast-animation-type`)을 멈춘다.
   안 멈추면 뜬 직후에 쓸었을 때 그쪽 transform 이 우리 것을 이겨
   **토스트가 제자리에 붙어 있는다.** 애니메이션이 inline 스타일보다
   세기 때문이다. 판의 규칙(`.dxbl-toast-portal .dxbl-toast`)보다 앞서야
   해서 판 이름을 하나 더 붙였다. */
.dxbl-toast-portal .dxbl-toast.jsini-toast-swipe--dragging,
.dxbl-toast-portal .dxbl-toast.jsini-toast-swipe--settling {
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  .dxbl-toast.jsini-toast-swipe--settling {
    transition: none;
  }
}

/* ── 토스트는 「보이는 화면」 안에 선다 ───────────────────────

   [무엇이 안 보였나]

   판은 화면에 붙여 두었다(`MainLayout` 의 `StickToViewport`). 그런데
   `position: fixed` 가 붙잡는 것은 **레이아웃 뷰포트**고, 사람이 실제로
   보고 있는 것은 **비주얼 뷰포트**다. 둘은 평소에 겹쳐 있지만 갈라지는
   자리가 셋 있다.

     · 손가락으로 확대했을 때 — 확대 잠금을 푼 사람(환경설정 · zoom.js)과
       넓은 화면(잠금은 좁은 화면에만 건다)
     · 휴대폰 브라우저의 주소창이 보였다 사라졌다 할 때
     · 글자 칸을 눌러 화면 자판이 올라왔을 때

   그때 「오른쪽 아래」 토스트는 보이는 자리보다 **아래·오른쪽으로 밀려
   나가** 한 조각도 안 보인다. 2.5 배로 확대해 왼쪽 위를 보고 있으면,
   토스트는 1000×800 짜리 레이아웃 뷰포트의 오른쪽 아래 구석(701,711)에
   서는데 그때 보이는 것은 (0,160) 에서 시작하는 400×320 뿐이다.
   고친 뒤에는 같은 자리에서 (281,444) 에 선다 — 둘 다 진짜 토스트를
   띄워 헤드리스로 재어 확인했다.

   [보이는 만큼 안으로 들여 세운다]

   theme.js 가 `visualViewport` 를 지켜보다가 **보이는 자리와 레이아웃
   뷰포트의 차이**를 네 방향 칸에 적는다. 판은 0 대신 그 칸을 읽는다 —
   사람이 고른 자리(왼쪽 위 · 가운데 아래 …)는 그대로고 그 자리를 재는
   기준만 「창」에서 「보이는 곳」으로 바뀐다.

   확대했을 때는 크기도 되돌린다. 300px 짜리 토스트가 2.5 배 화면에서는
   750px 를 먹어 가로로 넘치기 때문이다. `1/확대배율` 로 줄이면 **눈에
   보이는 크기가 늘 같아** 브라우저가 스스로 그리는 알림처럼 움직인다.
   ------------------------------------------------------------ */

/* 줄일 때 붙잡을 모서리. 고른 자리 쪽을 붙잡아야 줄어드는 동안에도
   그 모서리가 제자리에 남는다. 기본은 왼쪽 위다. */
.dxbl-toast-portal {
  --jsini-toast-origin-x: left;
  --jsini-toast-origin-y: top;
}

.dxbl-toast-portal.dxbl-toast-align-top {
  top: var(--jsini-toast-inset-top, 0px);
}

.dxbl-toast-portal.dxbl-toast-align-bottom {
  bottom: var(--jsini-toast-inset-bottom, 0px);
  --jsini-toast-origin-y: bottom;
}

.dxbl-toast-portal.dxbl-toast-align-left {
  left: var(--jsini-toast-inset-left, 0px);
}

.dxbl-toast-portal.dxbl-toast-align-right {
  right: var(--jsini-toast-inset-right, 0px);
  --jsini-toast-origin-x: right;
}

/* 가운데는 좌우를 **둘 다** 들여야 한다 — 그래야 판이 갖고 있는
   `margin: auto` 가 보이는 자리의 가운데를 잡는다. 한쪽만 들이면
   확대한 채로 옆을 볼 때마다 가운데가 조금씩 어긋난다. */
.dxbl-toast-portal.dxbl-toast-align-center {
  left: var(--jsini-toast-inset-left, 0px);
  right: var(--jsini-toast-inset-right, 0px);
  --jsini-toast-origin-x: center;
}

/* **확대한 동안에만 얹는다.** 늘 얹어 두면 `scale(1)` 이라도 판이 제
   래스터 층으로 올라가 글자가 흐려지는 브라우저가 있다. 표를 붙이고
   떼는 일은 theme.js 가 한다. */
html.jsini-toast-zoomed .dxbl-toast-portal {
  transform: scale(var(--jsini-toast-scale, 1));
  transform-origin: var(--jsini-toast-origin-x) var(--jsini-toast-origin-y);
}

/* ── 쪽지 (`NoteWritePanel` · `NoteWritePopup`) ────────────────

   상단 띠의 ✉ 가 어디서나 여는 판이고 메뉴의 「쪽지 쓰기」 화면도 같은 것을
   그린다. **모듈 CSS 가 아니라 여기** 있는 까닭은 알림 보내기 창과 같다 —
   헤더는 레이아웃이라 업무 모듈의 스타일시트를 물지 않는다.

   `jsini-notify__*`(알림 보내기 창)와 이름을 나눈다. 생긴 것이 닮았지만
   하는 일이 다르고, 한 벌을 나눠 쓰면 한쪽을 손볼 때마다 다른 쪽이 따라
   움직인다 — 그쪽은 **고른 사람들**에게 한 통, 이쪽은 **적어서 보내는** 글이다.
   ---------------------------------------------------------- */

.jsini-note__field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin-bottom: 0.7rem;
}

.jsini-note__label {
  font-size: var(--jsini-fs-xs);
  font-weight: 600;
  color: var(--jsini-text-muted);
}

/* 입력 칸은 판 폭을 그대로 쓴다. DevExpress 가 제 내용만큼만 넓히므로
   적어 두지 않으면 짧은 칸들이 왼쪽에 붙어 버린다. */
.jsini-note__input {
  width: 100%;
}

/* 받는 사람 딱지들이 놓이는 자리. 비어 있어도 높이를 남긴다 —
   딱지가 처음 생길 때 아래 칸들이 통째로 밀리지 않게 한다. */
.jsini-note__to {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  min-height: 1.9rem;
}

/* 딱지 하나 = 받는 사람 하나. 글자로 쉼표를 이어 두면 쉼표를 빠뜨렸을 때
   두 사람이 한 사람으로 뭉치는데, 그것이 화면에서는 안 보인다. */
.jsini-note__chip {
  display: inline-flex;
  align-items: center;
  gap: 0.1rem;
  padding: 0.1rem 0.15rem 0.1rem 0.55rem;
  border: 1px solid var(--jsini-border);
  border-radius: 999px;
  background: var(--jsini-bg);
  font-size: var(--jsini-fs-sm);
}

.jsini-note__pick {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin-top: 0.35rem;
}

/* 찾기 칸이 「추가」 단추보다 훨씬 넓어야 한다. flex 기본은 내용 크기라
   적어 두지 않으면 둘이 반반이 된다. */
.jsini-note__pick .jsini-note__input {
  flex: 1 1 auto;
  min-width: 0;
}

/* 단추는 안 줄어든다. 안 적어 두면 좁은 화면에서 눌려 **「추」 「가」 두 줄**이
   된다(390px 에서 재어 보았다) — 두 글자짜리 단추가 그 꼴이면 고장으로 읽힌다. */
.jsini-note__pick .dxbl-btn {
  flex: 0 0 auto;
  white-space: nowrap;
}

/* 찾은 사람들. 목록이 길어지면 판이 통째로 늘어나 보내기 단추가 화면
   밖으로 나가므로 여기서 구른다. */
.jsini-note__matches {
  max-height: 11rem;
  margin: 0.35rem 0 0;
  padding: 0;
  overflow-y: auto;
  border: 1px solid var(--jsini-border);
  border-radius: 0.3rem;
  list-style: none;
}

.jsini-note__match {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
  width: 100%;
  padding: 0.3rem 0.55rem;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.jsini-note__match:hover,
.jsini-note__match:focus-visible {
  background: var(--jsini-surface-muted, rgba(127, 127, 127, 0.12));
}

.jsini-note__match-name {
  font-size: var(--jsini-fs-sm);
}

/* 부서와 메일 주소. 이름보다 작고 흐리게 — 고르는 눈이 이름에 먼저 가야 한다. */
.jsini-note__match-sub {
  font-size: var(--jsini-fs-2xs);
  color: var(--jsini-text-muted);
}

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

/* 접어 둔 제목 칸을 펴는 줄. 단추와 안내가 한 줄에 나란히 선다 —
   안내를 아래로 내리면 접힌 상태가 펴진 상태보다 키가 커진다. */
.jsini-note__more {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
}

/* 어떻게 전해지는지 알려 주는 줄. 고르는 자리가 아니라 읽는 자리다 —
   두드림은 받는 사람이 자기 설정에서 정한다(`NoteWritePanel` 머리말). */
.jsini-note__ways {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  padding-bottom: 0.6rem;
  margin-bottom: 0.6rem;
  border-bottom: 1px solid var(--jsini-border);
}

.jsini-note__send {
  display: flex;
  gap: 0.4rem;
}

/* ── 빠른 선택 (`NoteWritePanel`) ─────────────────────────────

   아무것도 치지 않은 동안 보이는 줄이다. 최근에 보낸 사람을 딱지로 늘어놓아
   **한 번 눌러** 담게 한다 — 두 글자를 치는 것이 휴대폰에서는 자판을 띄우고
   한글을 조합하는 일이다.

   `jsini-note__chip`(담긴 사람)과 **다른 이름인 까닭**은 하는 일이 반대라서다.
   저쪽은 「담겼다」를 보여 주고 ×로 빼는 자리고, 이쪽은 「담을 수 있다」를
   보여 주고 눌러서 담는 자리다. 한 벌을 나눠 쓰면 한쪽을 손볼 때마다 다른
   쪽이 따라 움직인다.
   ---------------------------------------------------------- */

.jsini-note__quick {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem;
  margin-top: 0.45rem;
}

.jsini-note__quick-label {
  font-size: var(--jsini-fs-2xs);
  font-weight: 600;
  color: var(--jsini-text-muted);
}

/* 손가락으로 누르는 자리라 세로 여백을 딱지보다 넉넉히 둔다. 글자는 안 접는다 —
   이름이 두 줄로 갈리면 줄 높이가 들쭉날쭉해져 어디를 눌러야 할지 흐려진다. */
.jsini-note__quick-chip {
  padding: 0.25rem 0.6rem;
  border: 1px solid var(--jsini-border);
  border-radius: 999px;
  background: var(--jsini-bg);
  color: inherit;
  font: inherit;
  font-size: var(--jsini-fs-sm);
  line-height: 1.4;
  white-space: nowrap;
  cursor: pointer;
}

.jsini-note__quick-chip:hover,
.jsini-note__quick-chip:focus-visible {
  border-color: var(--bs-primary, #066fd1);
}

/* 담긴 사람. **목록에서 지우지 않는다** — 여덟 개짜리 줄이 누를 때마다
   흔들리면 옆 사람을 잘못 누른다. 한 번 더 누르면 빠진다. */
.jsini-note__quick-chip--on {
  border-color: var(--bs-primary, #066fd1);
  background: var(--bs-primary, #066fd1);
  color: #fff;
}

/* ── 화면을 채우는 모습 (휴대폰) ──────────────────────────────

   상단 띠의 ✉ 가 휴대폰에서 여는 모습이다(`NoteWritePopup.Fill`).
   **마크업은 창과 같고** 여기서 모습만 갈린다.

   가르는 것을 미디어 질의가 아니라 클래스로 둔 까닭 — 창을 띄우는 자리가
   넷이고(헤더 · 쪽지함의 답장 · 위치 지도의 점 · 메뉴 화면) 그중 **채워야
   하는 것은 헤더 하나**다. 폭으로만 가르면 좁은 화면에서 답장 창까지 함께
   화면을 먹는데, 그쪽은 뒤에 있는 목록이 보여야 어느 쪽지에 답하는지 안다.

   [DevExpress 창의 속을 **재어 보고** 적었다 (2026-10-10)]

   헤드리스 크롬 390×844 로 띄워 상자를 하나씩 쟀다 — 눈대중으로는 못 맞춘다.

     dxbl-popup-root > dxbl-popup-cell > dxbl-modal > dxbl-modal-root
       > dxbl-modal-dialog.dxbl-popup   ← `CssClass` 가 붙는 자리
           > div.dxbl-modal-content
               > dxbl-popup-header · div.dxbl-modal-body

   · 바깥 상자(`dxbl-modal-root`)가 이미 `position: fixed` 로 화면을 덮는다.
     그러니 창을 띄울 자리를 우리가 만들 것이 없고, **그 안의 판을 늘리면**
     된다. 재기 전에는 있지도 않은 `.dxbl-popup-root` 를 겨누고 있었다.
   · 판 자신은 `margin: 8px` · `max-width: 374px` · 내용만큼의 높이(697px)였다.
     셋을 다 풀어야 한다 — 하나만 풀면 좌우만 붙거나 위아래만 붙는다.
   · 글이 구르는 칸은 `.dxbl-popup-body` 가 아니라 **`.dxbl-modal-body`** 다.

   [`!important` 를 쓰는 까닭]

   tabler.css 가 ≤767px 에서 모든 창에 `width: calc(100vw - 1rem) !important`
   를 건다(「창 — 좌우 8px 만 남기고」). 그 선언과 겨루려면 이쪽도 `!important`
   여야 하고, 그러면 **자릿수로 갈린다** — 저쪽이
   `.dxbl-modal > .dxbl-modal-root > .dxbl-popup`(0,3,0)이라 클래스 하나를
   더 붙여 (0,4,0) 으로 적는다. 파일 순서와 무관하게 이긴다.
   ---------------------------------------------------------- */

.dxbl-modal > .dxbl-modal-root > .dxbl-popup.jsini-note-popup--full {
  width: 100vw !important;
  max-width: 100vw !important;
  height: 100% !important;
  max-height: 100% !important;
  margin: 0 !important;
  border-radius: 0 !important;
}

/* 창 몸통이 남은 높이를 그대로 넘겨받아야 판이 바닥까지 닿는다. 한 자리라도
   끊기면 판이 제 내용만큼만 서고, 그러면 보내기 줄이 화면 가운데에 뜬다.

   **`max-height` 는 `!important` 여야 한다.** DevExpress 가 그 값을 JS 로
   재어 **인라인 스타일**로 박는다(재어 보니 788px — 창이 넓어지기 전의
   값이다). 인라인은 자릿수로 못 이기므로 `!important` 하나만 길이 된다.
   스타일시트에는 그 규칙이 없어서 **CSS 만 뒤져서는 범인이 안 보인다.** */
.jsini-note-popup--full .dxbl-modal-content {
  height: 100% !important;
  max-height: none !important;
  border-radius: 0;
}

.jsini-note-popup--full .dxbl-modal-body {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1 1 auto;
}

.jsini-note--fill {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1 1 auto;
}

/* 내용 칸이 남은 높이를 먹는다. `min-height: 0` 이 없으면 flex 항목의 기본
   최소 높이(내용 크기) 때문에 **줄어들지 않아** 보내기 줄이 밀려난다. */
.jsini-note--fill .jsini-note__field--grow {
  flex: 1 1 auto;
  min-height: 7rem;
}

/* 그 높이를 **편집기까지 흘려보낸다.** 칸만 늘리면 DevExpress 가 `Rows` 로
   잡아 둔 키(여섯 줄 = 140px)를 그대로 쓰고, 남은 자리는 빈 채로 남는다 —
   재어 보니 칸 338px 에 편집기 140px 이었다.

   이름이 `.dxbl-memo` 가 아니라 **`.dxbl-memo-edit`** 다(재어 확인). 끌어
   늘리는 손잡이는 끈다 — 이미 남은 높이를 다 쓰고 있어 늘릴 데가 없다. */
.jsini-note--fill .jsini-note__field--grow .dxbl-memo-edit {
  flex: 1 1 auto;
  min-height: 0;
}

.jsini-note--fill .jsini-note__field--grow textarea,
.jsini-note--fill .jsini-note__field--grow textarea.dxbl-v-resize {
  height: 100%;
  resize: none;
}

/* 보내기 줄은 바닥에 붙는다. 받는 사람을 열 명 담고 글을 길게 쓰면 이 줄이
   스크롤 저편으로 가는데, 그 상태에서 자판까지 올라오면 **글을 다 쓰고도
   보낼 수가 없다.** 바탕을 칠하는 것은 아래 글이 비쳐 지나가지 않게 함이다. */
.jsini-note--fill .jsini-note__send {
  position: sticky;
  bottom: 0;
  padding: 0.6rem 0 calc(0.6rem + env(safe-area-inset-bottom, 0px));
  margin-top: auto;
  background: var(--jsini-surface, #fff);
  border-top: 1px solid var(--jsini-border);
}

/* 채우는 모습에서는 단추가 가로를 채운다 — 엄지로 누르는 자리다. */
.jsini-note--fill .jsini-note__send .dxbl-btn {
  flex: 1 1 auto;
  justify-content: center;
}

/* 받을 사람 목록이 길어지면 판 전체가 아니라 목록만 구른다. 창보다 넉넉히
   잡는 것은 화면을 다 쓰고 있어서다. */
.jsini-note--fill .jsini-note__matches {
  max-height: 40vh;
}

/* ── 상단 띠의 숫자 배지 (`HeaderTools`) ──────────────────────

   ✉ 에 안 읽은 쪽지 수를 얹는다. 단추 안에 넣을 수 없어(아이콘 자리를
   DevExpress 가 쥔다) **단추를 감싼 자리에 절대 배치**한다.

   감싼 자리는 `inline-flex` 다 — `inline-block` 으로 두면 단추가 줄 바닥선에
   앉아 띠 안에서 아래로 처진다.
   ---------------------------------------------------------- */

.jsini-header__badged {
  position: relative;
  display: inline-flex;
}

.jsini-header__badge {
  position: absolute;
  top: 0;
  right: 0;
  min-width: 1rem;
  padding: 0 0.2rem;
  border-radius: 999px;
  background: var(--jsini-danger, #d13438);
  color: #fff;
  font-size: var(--jsini-fs-2xs);
  font-weight: 700;
  line-height: 1rem;
  text-align: center;

  /* 숫자를 누르면 단추가 아니라 배지가 먹는다. 그러면 ✉ 가 「가끔 안 눌리는
     단추」가 된다 — 안 읽은 쪽지가 있을 때만 그러니 재현도 어렵다. */
  pointer-events: none;
}

/* ── 빠른 지시 요청 (노란 번개) ───────────────────────────

   「적어 두는 요청」 단추다(`HeaderTools`). 그림은 ⚡(빠른 지시)와 같은
   번개라 **색으로만** 가른다.

   권한이 둘 다 있는 사람(관리자)에게는 **흰 번개 옆에 나란히 선다**
   (2026-09-24). 그래서 이 색이 곧 「지금 돌리는 것이 아니다」라는 뜻이다.
   **휴대폰에서도 나란히 선다** — 한동안 좁다고 뺐는데, 적어 두는 요청은
   책상에 없을 때 하는 일이라 그 자리가 가장 아쉬웠다. 자리는 헤더 좌우
   여백과 열린 화면 이름에서 냈다(`@media (max-width: 767px)` 안).

   아이콘이 마스크 + `currentColor` 라 그림 쪽 배경색을 바꾼다. 단추 글자색을
   바꾸면 DevExpress 가 테마마다 덮어써 어떤 테마에서는 노랗지 않다.
   노랑은 밝은 띠에서 흐려지므로 호박색(amber)으로 잡고, 어두운 테마에서도
   같은 값을 쓴다 — 거기서는 오히려 더 잘 보인다.
   ---------------------------------------------------------- */

.jsini-header__tool--request .jsini-icon-bolt {
  background-color: var(--jsini-request-bolt, #f2a900);
}

/* ----------------------------------------------------------
   글꼴 고르기 — [화면 설정 → 글꼴]

   theme.js 가 `<html data-tb-font="…">` 를 세우면 여기서 `--tb-font` 를 갈아
   끼운다. 본문·DevExpress 부품·Bootstrap 이 모두 그 변수 하나를 읽으므로
   (tabler.css 의 `--bs-body-font-family: var(--tb-font)`) 한 줄이면 화면 전체가 바뀐다.

   **기본(system)에는 표시를 세우지 않는다.** tabler.css 의 원래 값이 그대로
   쓰여야 「지금 쓰는 글꼴」이 정말 지금과 같다.

   파일은 **고른 사람만 받는다** — `@font-face` 는 그 글꼴로 그릴 글자가
   생겨야 내려받는다. 그래서 기본을 쓰는 사람의 첫 화면은 조금도 무거워지지
   않는다(로그인 카드 머리말의 「글꼴을 가져오지 않았다」와 같은 걱정이다).

   뒤따르는 한글 글꼴을 꼭 적는다. Play 는 라틴 문자뿐이라 한글을 모르고,
   S-CoreDream 도 파일을 받기 전 잠깐은 뒤엣것으로 그린다(`swap`).
   ---------------------------------------------------------- */

/* S-CoreDream 은 무게 둘만 가져왔다(하나에 350KB). 범위로 선언해 보통 글자는
   Light 로, 500 이상은 Bold 로 그리게 한다 — 브라우저가 가짜 굵기를 만들면
   획이 뭉개진다. */
@font-face {
  font-family: 'S-CoreDream';
  font-style: normal;
  font-weight: 100 450;
  font-display: swap;
  src: url('fonts/S-CoreDream-3Light.woff') format('woff');
}

@font-face {
  font-family: 'S-CoreDream';
  font-style: normal;
  font-weight: 451 900;
  font-display: swap;
  src: url('fonts/S-CoreDream-6Bold.woff') format('woff');
}

@font-face {
  font-family: 'Play';
  font-style: normal;
  font-weight: 100 550;
  font-display: swap;
  src: url('fonts/Play-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2122;
}

@font-face {
  font-family: 'Play';
  font-style: normal;
  font-weight: 551 900;
  font-display: swap;
  src: url('fonts/Play-700-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2122;
}

:root[data-tb-font='scoredream'] {
  --tb-font: 'S-CoreDream', 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans KR',
    system-ui, -apple-system, 'Segoe UI', sans-serif;
}

:root[data-tb-font='play'] {
  --tb-font: 'Play', 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans KR',
    system-ui, -apple-system, 'Segoe UI', sans-serif;
}

/* 서랍의 글꼴 단추는 **자기 글꼴로** 이름을 쓴다 — 눌러 보기 전에 모양을 본다.
   선택자를 두 겹으로 둔다. tabler.css 가 이 파일보다 **뒤에** 실리면서 단추에
   `font: inherit` 를 걸어, 한 겹이면 그쪽이 이긴다. */
.tb-custom .tb-pill.tb-pill--font-system { font-family: system-ui, -apple-system, 'Segoe UI', 'Malgun Gothic', sans-serif; }
.tb-custom .tb-pill.tb-pill--font-scoredream { font-family: 'S-CoreDream', 'Malgun Gothic', sans-serif; }
.tb-custom .tb-pill.tb-pill--font-play { font-family: 'Play', 'Malgun Gothic', sans-serif; }

/* 로그인 · 가입 신청 카드 맨 아래의 개인정보처리방침 · 이용약관. 조용히 두되
   방침만 굵게 — 법이 「쉽게 확인할 수 있게」를 요구한다. */
.jsini-login__legal {
  display: flex;
  justify-content: center;
  gap: 0.5rem;
  margin: 1.25rem 0 0;
  font-size: 0.75rem;
  color: #86868b;
}

.jsini-login__legal a {
  color: #6e6e73;
  text-decoration: none;
}

.jsini-login__legal a:hover {
  color: #0a0a0a;
  text-decoration: underline;
}

/* ── 그림 미리보기 (`ImagePreviewHost`) ───────────────────────

   화면을 통째로 덮는다. 창(`CommPopup`)으로 두지 않은 이유는 **이 창의 본문이
   그림 한 장뿐**이어서다 — 키울 수 있는 만큼 키워 보려고 여는 자리라, 창틀이
   차지하는 만큼이 그대로 손해다.

   확대·회전·색은 여기서 정하지 않는다. 그것은 브라우저가 인라인 스타일로
   직접 그리고(`js/image-preview.js`), 이 파일은 **창틀과 도구띠만** 쥔다.

   [쌓임 차례]

   19000 이다. DevExpress 의 창·드롭다운(1000대)과 알림함 서랍(1040대)보다
   위고, **잠금 덮개(20000)와 다시 붙는 상자(20500)보다는 아래**다 — 자리를
   비운 사이에 미리보기가 열려 있었다고 해서 그 위로 그림이 보이면 잠금이
   아니고, 회로가 끊긴 뒤에 걷을 수 없는 그림이 화면을 덮고 있으면 되돌릴
   길이 없다.

   [단추 선택자가 두 겹인 까닭]

   tabler.css 가 이 파일보다 **뒤에** 실리면서 단추에 제 값을 건다. 한 겹으로
   적으면 그쪽이 이겨서 도구띠 글자가 검게 나온다(서랍의 글꼴 단추와 같은 일). */

.jsini-imgview {
  position: fixed;
  inset: 0;
  z-index: 19000;
  display: flex;
  flex-direction: column;
  /* 96% 다. 완전한 검정으로 두지 않는 것은 **덮개라는 것을 알리기 위해서**다 —
     뒤가 희미하게 비쳐야 「다른 화면으로 옮겨 온 것」이 아니라 「위에 덮인 것」
     으로 읽힌다.

     처음에 88% 로 두고 헤드리스로 찍어 보니 **뒤 화면의 제목이 그대로
     읽혔다.** 사진의 어두운 자리와 겹치면 그 글자가 사진의 일부처럼 보인다.
     96% 면 뒤 화면이 제 밝기의 4% 로 남아 자취만 비친다. */
  background: rgb(0 0 0 / 96%);
  color: #f5f5f7;
  user-select: none;
}

/* 뒤 화면이 함께 구르지 않게 한다. 켜고 끄는 것은 JS 쪽이다. */
body.jsini-imgview-open {
  overflow: hidden;
}

.jsini-imgview__bar,
.jsini-imgview__tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem;
  padding: 0.5rem 0.75rem;
  background: rgb(0 0 0 / 35%);
}

.jsini-imgview__bar {
  border-bottom: 1px solid rgb(255 255 255 / 12%);
}

.jsini-imgview__tools {
  justify-content: center;
  border-top: 1px solid rgb(255 255 255 / 12%);
}

/* 이름이 길어도 도구가 밀려나지 않는다. 파일 이름은 잘려도 읽을 수 있지만
   닫기 단추가 화면 밖으로 나가면 창을 걷을 수가 없다. */
.jsini-imgview__title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.875rem;
}

.jsini-imgview__count,
.jsini-imgview__read {
  flex: none;
  padding: 0 0.375rem;
  font-size: 0.8125rem;
  color: #c7c7cc;
  font-variant-numeric: tabular-nums;
}

.jsini-imgview__gap {
  flex: 1 1 auto;
}

.jsini-imgview .jsini-imgview__btn {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2rem;
  height: 2rem;
  padding: 0 0.5rem;
  border: 1px solid rgb(255 255 255 / 18%);
  border-radius: 4px;
  background: rgb(255 255 255 / 8%);
  color: #f5f5f7;
  font-size: 0.8125rem;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
}

.jsini-imgview .jsini-imgview__btn:hover {
  background: rgb(255 255 255 / 18%);
}

/* 켜진 것은 눌린 채로 둔다 — 흑백으로 보는 중인지 아닌지가 그림만 봐서는
   헷갈린다(원래 흑백인 사진이 있다). */
.jsini-imgview .jsini-imgview__btn--on {
  background: #f5f5f7;
  border-color: #f5f5f7;
  color: #1c1c1e;
}

.jsini-imgview .jsini-imgview__btn--close {
  font-size: 1rem;
}

.jsini-imgview__sep {
  flex: none;
  width: 1px;
  height: 1.25rem;
  margin: 0 0.25rem;
  background: rgb(255 255 255 / 18%);
}

/* 그림이 노는 칸. 남는 높이를 전부 먹는다.

   `overflow: hidden` 이라야 한다 — 키운 그림이 이 칸 밖으로 나가면 문서가
   길어져서 창 아래에 스크롤 막대가 생긴다.

   `touch-action: none` 은 **두 손가락 확대를 우리가 받기 위해서**다. 안 걸면
   브라우저가 제 확대를 먼저 해 버려서 그림이 아니라 화면 전체가 커진다. */
.jsini-imgview__stage {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  touch-action: none;
}

/* 처음에는 화면에 맞춰 선다. 그 크기가 곧 확대율 100% 다 — 「원래 크기」는
   따로 있고(1:1), 그쪽은 파일의 실제 픽셀을 뜻한다. */
.jsini-imgview__img {
  max-width: 100%;
  max-height: 100%;
  transform-origin: center center;
  -webkit-user-drag: none;
}

.jsini-imgview .jsini-imgview__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 1;
  width: 2.75rem;
  height: 2.75rem;
  border: 0;
  border-radius: 50%;
  background: rgb(0 0 0 / 45%);
  color: #f5f5f7;
  font-size: 1.75rem;
  line-height: 1;
  cursor: pointer;
}

.jsini-imgview .jsini-imgview__nav:hover {
  background: rgb(0 0 0 / 70%);
}

.jsini-imgview .jsini-imgview__nav--prev {
  left: 0.75rem;
}

.jsini-imgview .jsini-imgview__nav--next {
  right: 0.75rem;
}

/* 눌러서 크게 볼 수 있는 그림이라는 표시. 본문에 박힌 그림은 화면이 하나씩
   손댈 수 없으므로(`data-imgview-scope`) 여기서 한 번에 준다. */
[data-imgview-scope] img {
  cursor: zoom-in;
}

/* 휴대폰. 도구가 열여섯 개라 두 줄이 되는데, 그 줄만큼 그림이 눌린다.
   글자를 줄이고 사이를 좁혀 한 줄에 더 담는다. */
@media (max-width: 575.98px) {
  .jsini-imgview__bar,
  .jsini-imgview__tools {
    padding: 0.375rem 0.5rem;
    gap: 0.1875rem;
  }

  .jsini-imgview .jsini-imgview__btn {
    min-width: 1.75rem;
    height: 1.75rem;
    padding: 0 0.375rem;
    font-size: 0.75rem;
  }

  .jsini-imgview__sep {
    margin: 0 0.125rem;
  }
}



/* 공통 오른쪽 서랍 */
.jsini-right-drawer {
  position: fixed;
  top: var(--jsini-header-height);
  right: 0;
  bottom: 0;
  width: min(26rem, 100vw);
  z-index: 1040;
  display: flex;
  flex-direction: column;
  background: var(--jsini-bg);
  border-left: 1px solid var(--jsini-border);
  box-shadow: -4px 0 16px rgba(0, 0, 0, 0.05);
  transform: translateX(100%);
  transition: transform 0.22s cubic-bezier(0.2, 0, 0, 1), visibility 0.22s;
  visibility: hidden;
}

.jsini-right-drawer--open {
  transform: translateX(0);
  visibility: visible;
}

.jsini-right-drawer__head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.5rem 0.5rem 0.9rem;
  border-bottom: 1px solid var(--jsini-border);
}

.jsini-right-drawer__title {
  flex: 1;
  font-weight: 600;
}

.jsini-right-drawer__body {
  flex: 1;
  min-height: 0;
  padding: 0.75rem;
  overflow-y: auto;
}

.jsini-right-drawer__foot {
  margin: 0;
  padding: 0 0.9rem 0.75rem;
  color: var(--jsini-text-muted);
  font-size: var(--jsini-fs-xs);
  line-height: 1.5;
}

/* ── 휴대폰에서 표를 카드로 편다 (CommGrd `MobileCards`) ─────────────

   [왜 표를 버리지 않았나]

   칸이 예닐곱인 표는 360px 에 절대 안 들어간다. 재어 보면 「AI 사용량」의
   표는 390px 화면의 340px 짜리 자리 안에서 **624px** 을 차지했다 — 값의
   절반이 가로 스크롤 저편에 있었고, 한 손으로 쥔 휴대폰에서 굴려야 보이는
   값은 사실상 없는 값이다.

   먼저 간 두 화면(헬프데스크 「요청 처리」· 「내 알림함」)은 휴대폰용 카드
   목록을 **따로 짰다.** 화면 둘이면 되지만 표가 아흔 장이라 그 길로는
   못 간다 — 줄 누르기 · 선택 · 오른쪽 클릭 창 · 엑셀 · 셀 템플릿이 화면
   수만큼 갈라진다.

   그래서 여기서는 **DevExpress 가 그린 표 그대로**를 CSS 로 편다. 바뀌는
   것이 생김새뿐이라 위의 다섯 가지는 전부 같은 코드로 돈다.

   [회로는 폭을 몰라도 된다]

   `DxLayoutBreakpoint` 로 가르면 그 값이 **첫 그림 뒤에** 와서 표가 한 번
   섰다 걷히고, 걷히는 쪽이 제 속을 만지다 회로를 내리는 길이 열린다
   (`CommGrd` 의 `_gone` 머리말). 미디어 쿼리는 서버가 알 것도, 다시 그릴
   것도 없다.

   [기대는 자리 — DevExpress 가 바꾸면 여기가 먼저 깨진다]

   · `tr[data-visible-index]` 가 **자료 줄**이다. 가상 스크롤이 위아래에
     끼우는 빈 줄(`dxbl-top-virtual-spacer-element`)에는 그 칸이 없어서,
     이것으로 가르지 않으면 **빈 카드 두 장**이 목록 끝에 생긴다.
   · `td[role="gridcell"]` 이 자료 칸이다.
   · 칸 이름은 C# 이 `data-caption` 으로 적어 둔다(`LabelForCard`).
     머리줄을 감추므로 그 이름이 아니면 어느 값인지 알 길이 없다.
   ---------------------------------------------------------- */

@media (max-width: 767px) {

  /* 표의 뼈대를 푼다. `colgroup` 의 칸 너비가 살아 있으면 블록으로 바꿔도
     칸이 제 폭을 주장해 가로 스크롤이 그대로 남는다. */
  .commgrd--cards .dxbl-grid-table > colgroup {
    display: none;
  }

  .commgrd--cards .dxbl-grid-table {
    display: block;
    width: 100% !important;
    min-width: 0 !important;
  }

  /* 머리줄은 카드에서 쓸모가 없다 — 이름은 값 옆에 붙는다.
     칸별 검색 줄도 여기 들어 있어 함께 감춰진다. */
  .commgrd--cards .dxbl-grid-table > thead {
    display: none;
  }

  .commgrd--cards .dxbl-grid-table > tbody {
    display: block;
  }

  /* 가로 스크롤 막대는 더 이상 뜰 일이 없다. 자리만 먹는 것을 막는다. */
  .commgrd--cards .dxbl-scroll-viewer-hor-scroll-bar {
    display: none !important;
  }

  /* ── 카드 한 장 = 자료 줄 하나 ───────────────────────── */
  .commgrd--cards .dxbl-grid-table > tbody > tr[data-visible-index] {
    display: block;
    position: relative;
    margin: 0 0 0.5rem;
    padding: 0.5rem 0.7rem;
    border: 1px solid var(--jsini-border);
    border-radius: 10px;
    background: var(--jsini-bg);
  }

  /* 고른 카드. 표에서는 진한 강조색이 줄 하나를 통째로 칠하지만, 카드에서는
     글이 많아 흰 글자가 읽기 어렵다. **테와 옅은 바탕**으로 말한다. */
  .commgrd--cards .dxbl-grid-table > tbody > tr.dxbl-grid-focused-row,
  .commgrd--cards .dxbl-grid-table > tbody > tr.dxbl-grid-selected-row {
    border-color: var(--jsini-accent);
    background: color-mix(in srgb, var(--jsini-accent) 7%, var(--jsini-bg)) !important;
    color: inherit !important;
  }

  .commgrd--cards .dxbl-grid-table > tbody > tr.dxbl-grid-focused-row > td,
  .commgrd--cards .dxbl-grid-table > tbody > tr.dxbl-grid-selected-row > td {
    color: inherit !important;
    background: transparent !important;
  }

  /* 줄무늬는 끈다. 카드는 테가 이미 경계를 말하므로, 거기에 바탕색까지
     갈리면 **반쯤 고른 것처럼** 보인다. */
  .commgrd--cards .dxbl-grid-table > tbody > tr.commgrd__row--alt:not(.dxbl-grid-focused-row):not(.dxbl-grid-selected-row) {
    background: var(--jsini-bg);
  }

  /* ── 칸 하나 = 카드 안의 한 줄 ───────────────────────── */
  .commgrd--cards .dxbl-grid-table > tbody > tr > td {
    display: block;
    width: auto !important;
    max-width: none !important;
    min-width: 0 !important;
    border: 0 !important;
    padding: 0 !important;
    text-align: left !important;

    /* 붙박은 칸(`FixedPosition`)을 푼다. 가로로 구를 일이 없으니
       붙박을 자리도 없다 — 안 풀면 카드 위에 떠서 글을 가린다. */
    position: static !important;
    box-shadow: none !important;

    /* 표는 `TextWrapEnabled="false"` 로 한 줄을 지키지만(가로로 구르므로)
       카드에서는 접히는 편이 낫다. 긴 글이 「…」로 잘리지 않는다. */
    white-space: normal !important;
    overflow: visible !important;
    text-overflow: clip !important;
  }

  /* 오른쪽 끝의 빈 채움 칸. 카드에서는 빈 줄 하나다. */
  .commgrd--cards .dxbl-grid-table > tbody > tr > td.dxbl-grid-empty-cell {
    display: none !important;
  }

  /* [칸 이름은 `::after` 로 그린다 — `::before` 는 DevExpress 것이다]

     고른 줄·강조 줄의 **바탕을 칠하는 것**이 `td::before` 다(테마 CSS 의
     `content:""` + `position:absolute` + `z-index:-1`). 자릿수가 우리보다
     훨씬 높아서 거기에 이름을 담으면 **고른 카드에서만 이름이 통째로
     사라진다** — 재어 보고 알았다(2026-10-11).

     카드에서는 그 바탕칠이 필요 없다. 칠할 것은 셀이 아니라 카드고,
     그것은 줄(`tr`)에 직접 준다. */
  .commgrd--cards .dxbl-grid-table > tbody > tr > td::before {
    content: none !important;
  }

  /* 이름이 적힌 칸 — 왼쪽에 이름, 오른쪽에 값. */
  .commgrd--cards .dxbl-grid-table > tbody > tr > td[data-caption] {
    display: flex;
    gap: 0.5rem;
    align-items: baseline;
    padding: 0.1rem 0 !important;
  }

  .commgrd--cards .dxbl-grid-table > tbody > tr > td[data-caption]::after {
    content: attr(data-caption);

    /* 뒤에 그려지는 것을 앞으로 돌린다. 가짜 요소도 flex 항목이라 듣는다. */
    order: -1;

    flex: 0 0 5rem;
    color: var(--jsini-text-muted);
    font-size: 0.78rem;
    line-height: 1.55;
    word-break: keep-all;
  }

  /* 값 쪽이 남은 폭을 다 쓴다. DevExpress 의 셀 속은 감싸개 없이 바로
     오는 경우가 있어(글자 노드) `:where()` 로 무엇이 와도 늘어나게 둔다. */
  .commgrd--cards .dxbl-grid-table > tbody > tr > td[data-caption] > * {
    min-width: 0;
  }

  /* **첫 값 칸이 제목이다.** 「앞에 같은 것이 없는 하나」가 그것이다 —
     순번·체크·관리 칸에는 이름을 안 적어 두었으므로(`LabelForCard`)
     이 셈에서 저절로 빠진다. 뒤따르는 칸을 되돌려 첫 칸만 남긴다. */
  .commgrd--cards .dxbl-grid-table > tbody > tr > td[data-caption] {
    font-weight: 600;
    font-size: 0.95rem;
    padding-bottom: 0.25rem !important;
  }

  .commgrd--cards .dxbl-grid-table > tbody > tr > td[data-caption] ~ td[data-caption] {
    font-weight: normal;
    font-size: inherit;
    padding-bottom: 0.1rem !important;
  }

  /* 붙박았던 칸의 바탕과 그림자를 지운다. 표에서는 굴러 들어오는 글을 가리려고
     불투명해야 했지만(위 `dxbl-grid-fixed-cell` 규칙들) 카드에서는 그 사각형이
     카드 안에 흰 판으로 떠 보인다. */
  .commgrd--cards .dxbl-grid .dxbl-grid-table > tbody > tr > td.dxbl-grid-fixed-cell,
  .commgrd--cards .dxbl-grid .dxbl-grid-table > tbody > tr.commgrd__row--alt > td.dxbl-grid-fixed-cell,
  .commgrd--cards .dxbl-grid .dxbl-grid-table > tbody > tr.dxbl-grid-focused-row > td.dxbl-grid-fixed-cell,
  .commgrd--cards .dxbl-grid .dxbl-grid-table > tbody > tr.dxbl-grid-selected-row > td.dxbl-grid-fixed-cell {
    background-color: transparent;
  }

  /* ── 이름이 없는 칸들 ────────────────────────────────── */

  /* 순번. 카드 오른쪽 위에 작게 앉는다 — 값이 아니라 자리표다.
     칸만 오른쪽으로 밀어서는 안 된다 — 속의 `.commgrd__no` 가 블록이라
     제 폭을 다 쓰고 그 안에서 가운데로 선다(표에서는 그것이 맞다). */
  .commgrd--cards .dxbl-grid-table > tbody > tr > td.commgrd__cell--seq {
    font-size: 0.75rem;
    line-height: 1.2;
  }

  .commgrd--cards .dxbl-grid-table > tbody > tr > td.commgrd__cell--seq .commgrd__no {
    text-align: right;
  }

  /* 관리(수정·삭제). 카드 맨 아래에 선을 긋고 오른쪽으로 민다. */
  .commgrd--cards .dxbl-grid-table > tbody > tr > td.commgrd__cell--act {
    margin-top: 0.35rem;
    padding-top: 0.3rem !important;
    border-top: 1px dashed var(--jsini-border) !important;
  }

  .commgrd--cards .dxbl-grid-table > tbody > tr > td.commgrd__cell--act .commgrd__actions {
    justify-content: flex-end;
  }

  /* 체크 칸. 이름이 없으므로 위 제목 셈에 끼지 않고, 제목 줄 오른쪽 위에
     떠서 **카드를 고르는 손잡이**가 된다. 카드가 그만큼 오른쪽을 비운다. */
  .commgrd--cards .dxbl-grid-table > tbody > tr[data-visible-index]:has(> td.dxbl-grid-selection-cell) {
    padding-right: 2.6rem;
  }

  .commgrd--cards .dxbl-grid-table > tbody > tr > td.dxbl-grid-selection-cell {
    position: absolute !important;
    top: 0.45rem;
    right: 0.6rem;
    width: 1.8rem !important;
  }

  /* 펼침 단추(`DetailRowTemplate`)도 값이 아니라 손잡이다. 체크 칸과 같은
     위쪽 귀퉁이에 두되, 체크가 오른쪽을 쓰므로 **왼쪽**에 선다. */
  .commgrd--cards .dxbl-grid-table > tbody > tr > td.dxbl-grid-expand-button-cell {
    position: absolute !important;
    top: 0.3rem;
    left: 0.4rem;
  }

  /* 그 단추가 선 카드는 제목 줄이 그 자리를 비켜 준다. */
  .commgrd--cards .dxbl-grid-table > tbody > tr[data-visible-index]:has(> td.dxbl-grid-expand-button-cell) {
    padding-top: 1.9rem;
  }

  /* 펼친 속줄은 카드가 아니다 — 테와 여백만 카드에 맞춘다. */
  .commgrd--cards .dxbl-grid-table > tbody > tr.dxbl-grid-detail-row > td {
    padding: 0 0 0.5rem !important;
  }

  /* ── 쪽 넘김 줄 ──────────────────────────────────────────

     카드에서는 가상 스크롤 대신 쪽 넘김이 선다(`ApplyScrollMode`). 넓은
     화면용 규칙이 그대로 오면 둘이 걸린다. */

  /* 왼쪽 10rem 은 **아이콘 자리를 비워 두려고** 준 값인데(위 `commgrd__footer`),
     342px 짜리 띠에서 그만큼을 빼면 쪽 단추가 오른쪽으로 밀려 잘린다. */
  .commgrd--cards:has(.dxbl-grid-bottom-panel) .dxbl-grid-bottom-panel {
    padding-left: 0.5rem;
    justify-content: center;
  }

  /* 「Page 1 of 14 (200 items)」. **건수는 머리줄이 이미 한국말로 말한다** —
     여기서는 쪽 단추를 밀어낼 뿐이다. */
  .commgrd--cards .dxbl-pager-summary {
    display: none !important;
  }

  /* ── 카드 목록은 **쪽이 구른다** ─────────────────────────

     업무 화면은 쪽이 구르면 안 된다는 것이 기본 규칙이고(위 「본문 높이를
     꽉 채운다」), 표에서는 그것이 맞다 — 조건은 늘 위에, 아래 띠는 늘
     아래에, 구르는 것은 자료뿐이어야 한다.

     **카드에서는 그 셈이 뒤집힌다.** 휴대폰의 본문은 610px 남짓인데
     조건 줄과 타일을 빼면 자료 자리가 400px 쯤이고, 카드 한 장이 290px 라
     **한 장 반이 보이는 상자 안에서** 또 굴려야 한다. 쪽을 굴리는 편이
     손가락에 훨씬 가깝다 — 먼저 간 「내 알림함」이 같은 판단을 했다
     (`admin.css` 의 `ad-notebox--phone`).

     **곧장 매달린 자료 판만** 그렇게 한다(`>` 넷). 좌우 분할·지도처럼
     높이를 받아야 사는 판은 그 사슬에 안 걸린다. */
  .jsini-shell__content:has(> .commcont > .commcont__body > .commgrd--cards) {
    overflow: auto;
  }

  .jsini-shell__content:has(> .commcont > .commcont__body > .commgrd--cards) > .commcont {
    flex: 0 0 auto;
    min-height: 0;
  }

  .jsini-shell__content:has(> .commcont > .commcont__body > .commgrd--cards) > .commcont > .commcont__body {
    display: block;
  }

  /* 판이 제 높이를 갖게 되었으니 표도 안에서 굴릴 것이 없다. */
  .jsini-shell__content:has(> .commcont > .commcont__body > .commgrd--cards) .commgrd--cards > .dxbl-grid {
    height: auto;
    max-height: none;
  }
}
