/* 공용 셸 부품 — 사이드바 메뉴 · 토글 · 드롭다운 · 브랜드 로고의 **모양 정본**.
 * F221(사장님 2026-08-07): "같은 부품을 두세 개씩 따로 운영하지 말라."
 *
 * 마크업 정본은 core/templates_common/partials/_nav_menu.html ·
 * _sidebar_toggle.html · _lang_menu.html · _theme_toggle.html ·
 * _profile_menu.html 이고, **치수와 색은 이 파일 하나**다.
 *
 * 이 파일이 닿는 곳 셋:
 *   ① 앱 셸(Zynix)      — `_locale_head.html` 이 styles.css **뒤에** 싣는다.
 *   ② 경량 셸(shell.html) — 같은 경로로 실린다.
 *   ③ 랜딩(독립 문서)    — index.html 이 직접 <link> 한다. Zynix 가 없으므로
 *                          `.nx-shell-plain` 블록이 메뉴 모양을 대신 그린다.
 *
 * ★ 앱 쪽 규칙(② 절)은 **지금 Zynix 가 그리는 값과 한 자리도 다르지 않게** 옮겨
 *   적은 것이다(styles.css 에서 실제 값을 뽑아 확인). 그래서 이 파일을 넣어도
 *   앱 화면은 안 바뀐다. 바뀌는 것은 **앞으로**다 — 변수를 고치면 앱과 랜딩이
 *   같이 움직인다. 0-2 규약(고침 자체를 의심한다)에 따라 이번 라운드는
 *   "한 곳으로 모으기"만 하고 값은 건드리지 않는다.
 *
 * ★ Zynix 벤더 CSS(styles.css)를 직접 고치지 않는다 — zynix_import.py 재수입에
 *   사라진다. 슬롯별 zynix-custom.css 에도 넣지 않는다 — 8벌 복사본은 반드시
 *   어긋난다(그래서 F221 이 생겼다).
 */

/* ===========================================================================
   ★★★★★ 층 사다리 — **뜨는 것의 순서는 여기 한 곳에서 정한다** (F492)
   ---------------------------------------------------------------------------
   사장님 지시(2026-08-16): *"모달과 팝업 등 규칙을 지금 다 다시 정해라.
   아래로 가거나 숨김이 되지 않게."*

   왜 생겼는가 — 값이 화면마다 흩어져 있었다(실측: 서랍 200 · 트리 팝업 200 ·
   보통 팝업 998/999 · 토스트 950 · 수식 1100 · 시트 1150 · 편집 팝업 1200).
   그래서 «수정창이 시트 뒤로» · «트리 팝업이 사이드바 뒤로» 가 났고, 나는
   그것을 **아래 것을 감춰서** 때웠다 — 그 결과 검정 상단바가 통째로
   사라졌다(F491-4). 편법이었다.

   ★ 규칙 1 — **숫자를 직접 적지 않는다.** 새로 뜨는 것을 만들면 아래 칸
     하나를 골라 쓴다. `test_ui_offline` 이 생짜 숫자를 막는다.
   ★ 규칙 2 — **숨기지 않는다.** 위에 뜰 것이 못 덮는다고 아래 것을 지우거나
     `display:none` 하지 않는다. 덮어야 할 것은 덮는다.
   ★ 규칙 3 — 시트(전체화면 모달) **안**에서 전체화면으로 떠야 하는 것은
     iframe 밖으로 못 나간다. 그런 것은 **시트를 가진 쪽이 연다.**
   ★ 규칙 4 — 같은 칸이 두 겹이면 `--nx-z-step` 만큼 위로 올린다.

   칸(아래 → 위):
     drawer  옆에서 밀려 나오는 판
     pop     단추에 붙는 작은 메뉴(트리 액션 등)
     modal   보통 팝업(막 + 카드)
     sheet   전체화면 시트(DOX 만들기·미리보기·정밀검수)
     over    시트가 떠 있을 때 여는 모든 것
     toast   알림 — 언제나 맨 위
   =========================================================================== */
:root {
  --nx-z-step:   10;     /* 같은 칸의 두 번째 겹 · 막 위의 카드 */
  --nx-z-drawer: 1000;
  --nx-z-pop:    1100;
  --nx-z-modal:  1200;
  --nx-z-sheet:  1300;
  --nx-z-over:   1400;
  --nx-z-toast:  1500;
}


:root {
  /* 사이드바 메뉴 — 값의 출처는 Zynix styles.css 의 .app-sidebar 계열 실측 */
  --nx-menu-item-pad-y: 0.65rem;
  --nx-menu-item-pad-x: 0.75rem;
  --nx-menu-font: 0.85rem;          /* 앱 실측 13.6px */
  --nx-menu-label-font: 0.9rem;     /* 앱 실측 14.4px — 줄 글자는 더 크다 */
  --nx-menu-row-pad-x: 1rem;        /* 앱 실측 16px — 줄 바깥 좌우 여백 */
  --nx-menu-weight: 500;
  --nx-menu-radius: 0;              /* 앱 실측 0px */
  /* ★★★★★★ 2026-09-05(사장님 지시) — **색 이름을 벤더에 맞춰 통일했다.**
     *"사이트에서 blue 로 명명된 색상을 전부 원 css 의 primary 로 · 소프트
     블루로 명명한 것은 전부 원 css 의 Light primary 로 · 앞으로도 전부 통일"*

     ⇒ 숫자를 박지 않고 **슬롯의 `--primary` 를 가리킨다.** 그래야 슬롯이
       브랜드색을 바꾸면 스위치·테두리·포커스 링이 **함께** 따라온다
     ★★★★★ 사장님 결정 2026-09-05: *"전부 primary 로 통일 → 95.106.245"*.
       그 전까지 이 사이트에는 **파랑이 둘**이었다 — 셸·랜딩의 `#2563eb`
       (37 99 235)와 뷰어(DOX 만들기)의 `#5f6af5`(95 106 245). 색이 슬롯에서
       나오게 되자 언어 고르개 테두리가 화면마다 갈렸고(harness_f483 실측),
       사장님이 **뷰어 쪽 값 하나로** 모으라 하셨다. 이제 슬롯 값이 곧 그것이다.

     ★ 「Light primary」는 벤더에서 **한 쌍**이다(옅은 배경 + 진한 글자).
       그래서 배경(.10)과 테두리(.30)를 따로 둔다 — 테두리에 .10 을 쓰면
       선이 사실상 안 보인다(실측: 언어 고르개·실행 버튼 테두리가 사라진다).
       사장님 결정 «㉠ 뜻대로 옮기기» 가 이것이다.
     ★★ `.30` 은 **사장님이 확정하신 값**이다(2026-09-05 «30 을 유지»).
       벤더 눈금(`.10 / .25 / .50`)에 없는 값이라 «눈금에 맞춘다» 며 `.25` 로
       내리기 쉬운데, `.25` 는 옛 소프트블루보다 더 옅다. **내리지 않는다.**
     ★ 폴백 `95 106 245` 는 벤더 변수가 없는 자리(랜딩 등)를 위한 것이다 —
       config.yaml 의 theme.primary 와 **같은 값**이어야 한다(검사가 지킨다). */
  --nx-primary: rgb(var(--primary, 95 106 245));
  --nx-primary-soft-bg: rgb(var(--primary, 95 106 245) / .10);
  --nx-primary-soft-bd: rgb(var(--primary, 95 106 245) / .30);
  /* ★ F687-2(사장님 지시 2026-09-01: *"sync scroll 버튼은 soft blue 가
     아니라 blue 로"*) — 그 «blue» 가 곧 **primary** 다(2026-09-05 통일).
     옛 `--nx-blue: #2563eb` 는 2026-09-05 에 `--primary`(#5f6af5) 로 모았다. */
  --nx-menu-icon-size: 1rem;
  --nx-menu-icon-gap: 0.75rem;
  --nx-menu-sub-pad-y: 0.45rem;
  --nx-menu-sub-pad-x: 1rem;
  --nx-menu-cat-pad-y: 0.75rem;
  --nx-menu-cat-pad-x: 1.25rem;
  --nx-menu-cat-font: 0.65rem;
  --nx-menu-cat-track: 0.05rem;
  /* 활성 표시(지금 보고 있는 화면) — 앱 실측: 브랜드색 · 굵기 600 */
  --nx-menu-active-color: rgb(var(--primary, 95 106 245));
  --nx-menu-active-weight: 600;

  /* 토글 버튼 — F190/F191 로 못 박은 값(뷰어·헤더·랜딩이 같아야 한다) */
  --nx-toggle-box: 34px;
  --nx-toggle-icon: 24px;   /* 사장님 2026-08-20: expand 아이콘 24px */
  /* ★ F605-4(사장님 2026-08-20): **여는 아이콘만** 20px. 닫는 쪽(X·◀)은 위
     24px 그대로다 — 두 값을 한 토큰으로 합치지 않는다(합치면 지시가 깨진다). */
  --nx-toggle-icon-open: 20px;
  --nx-toggle-color: #8b95a4;
  /* 클릭 상자 바깥 크기 — 앱은 Zynix 의 `.header-link` 여백까지 42px 다(실측).
     랜딩이 같아 보이려면 **바깥 크기까지** 같아야 한다(F225①). */
  --nx-toggle-outer: 42px;
  /* 줄 맨 왼쪽에서 토글까지 — 앱 실측 16px */
  --nx-toggle-edge: 16px;

  /* 사이드바 그릇 — Zynix 와 **같은 단위(rem)** 여야 한다.
     ★ 2026-08-07 사고: 240px 로 못 박았더니 **일본어(root 15px)** 에서
       Zynix 의 15rem = 225px 와 어긋나 브랜드 줄만 15px 넓어졌다
       (사장님 F230 지적 — 실제로 재현했다). 언어마다 root font-size 가
       다르므로(locales.yaml: ko/en 16px · ja 15px) px 로 박으면 안 된다. */
  --nx-sidebar-w: 15rem;
  /* 브랜드 줄 높이 — **헤더 막대와 같아야 한다**(= --nx-header-h).
     ★★ F604-2/3(사장님 2026-08-20, 화면 지적): "화살표2·화살표3 라인 위치가
       안 맞다" — 헤더 아래 선과 사이드바 로고줄 아래 선이 **8px 어긋나** 있었다.
       원인은 여기다(읽어서 확인):
         · Zynix 원본 `.app-sidebar .main-sidebar-header` 는
           `height:4.25rem; border-bottom-width:1px` — 헤더(`.app-header
           {height:4.25rem}`)와 **같은 높이**로 이미 맞춰져 있었다.
         · 그런데 아래 ②의 규칙이 그 높이를 `var(--nx-sidebar-brand-h)`
           = 3.75rem(60px) 로 `!important` 로 **덮었다.** 두 선의 y 가
           68px vs 60px 로 갈린 것이 사장님이 보신 어긋남이다.
       ⇒ 옛 값 3.75rem 은 "앱 실측 60px" 이라는 **낡은 관찰**이었다. 이제 헤더
         토큰을 그대로 참조한다 — 한 곳을 고치면 두 선이 같이 움직인다.
       ※ 이 값은 랜딩 드로어(`.nx-drawer__brand`)도 쓴다. 랜딩 헤더도
         `calc(var(--nx-header-h) - 1px)` + 테두리 1px = 같은 68px 라 맞는다. */
  --nx-sidebar-brand-h: var(--nx-header-h);
  /* 브랜드 줄 안쪽 여백 — 오른쪽이 좁은 이유는 **닫기 버튼 자리**다(F227).
     앱에는 그 버튼이 없지만 같은 값을 써야 두 줄이 같아 보인다. */
  --nx-sidebar-brand-pad: 0 0.5rem 0 1rem;

  /* ★ F237②(사장님 2026-08-07): "로고 위치가 메인과 **윗부분 간격**이 다르다".
     헤더 막대 높이가 갈라져 있었다 — 앱은 Zynix 의 `.app-header{height:4.25rem}`
     (16px 기준 68px), 랜딩은 `.nav-inner{height:64px}`+테두리 1px = 65px.
     둘 다 로고는 가운데 정렬이라, **막대 높이가 다르면 위 간격이 다르다**
     (실측 22px vs 20px). 값은 Zynix 쪽으로 맞춘다 — 앱이 기준이다.
     rem 인 이유는 사이드바 폭과 같다(언어마다 root 글자 크기가 다르다).
     ※ 이 값은 **테두리를 포함**한다(Tailwind preflight 가 border-box). */
  --nx-header-h: 4.25rem;

  /* 브랜드 로고 줄 */
  --nx-brand-h: 24px;
  --nx-brand-gap: 0;                /* F229②: 마크가 글자에 **붙는다**(사장님) */
  --nx-brand-color: #22262a;
  --nx-brand-color-dark: #f3f4f6;

  /* Zynix 가 없는 문서(랜딩·경량 셸)에서 쓸 메뉴 색.
     앱에서는 Zynix 의 --menu-prime-color 가 같은 자리를 맡는다. */
  --nx-menu-color: #536485;
  --nx-menu-icon-color: #7b8698;
  --nx-menu-cat-color: #9aa3b5;
  --nx-menu-hover-bg: #f4f6fa;
  --nx-menu-guide: #e3e6ec;
  --nx-surface: #ffffff;
  --nx-line: #e5e7eb;
}

html.dark,
.dark {
  --nx-brand-color: var(--nx-brand-color-dark);
  --nx-menu-color: #cbd2e0;
  --nx-menu-icon-color: #98a2b8;
  --nx-menu-cat-color: #8b95a4;
  /* ★★★★★★ dx134(사장님 2026-09-24: *"다크모드의 색상을 템플릿 css 그대로"*) — 값은 Zynix 템플릿
     `core/static_common/zynix/css/styles.css` 의 `.dark` 에서 온다: 면 35 34 38(#232226) · 선 흰색 .1. */
  --nx-menu-hover-bg: rgb(255 255 255 / 0.06);
  --nx-menu-guide: rgb(255 255 255 / 0.1);
  --nx-surface: #232226;
  --nx-line: rgb(255 255 255 / 0.1);
}

/* ===========================================================================
 * ① 브랜드 로고 (partials/_brand_logo.html)
 *
 * 전에는 이 규칙이 두 곳에 있었다 — service1/static_src/zynix-custom.css 와
 * 랜딩의 _landing_base.css. 로고를 바꿀 때마다 두 파일을 같이 고쳐야 했다.
 * =========================================================================== */
.brand-logo {
  height: var(--nx-brand-h);
  display: flex;
  align-items: center;
  gap: var(--nx-brand-gap);
  color: var(--nx-brand-color);
}

.brand-logo svg {
  height: 100%;
  width: auto;
  display: block;
}

/* 반응형 2단 — 자리가 좁으면 워드마크를 숨겨 아이콘만 남긴다.
   · 앱 사이드바: Zynix 접힘 상태는 data-toggled 가 *-close 로 끝난다
     (icon-overlay-close 등). 호버로 잠깐 펼친 상태(data-icon-overlay=open)는
     다시 보여준다. 초기값 "close"(펼침)는 -close 로 끝나지 않아 안 걸린다.
   · 좁은 화면 헤더 브랜드·랜딩: 폭으로 판단한다. */
/* ★ 사장님 F242①·② (2026-08-07): **아이콘 레일 자체를 없앴다.**
   전에는 접히면 폭이 아이콘 하나(5rem)라 워드마크를 숨겼는데, 사장님이
   "로고가 다시 아이콘으로 돌아갔다"고 지적하셨다. 레일을 없애면 사이드바는
   **온전히 열리거나 완전히 닫히거나** 둘뿐이라, 숨길 이유가 사라진다.
   그래서 이 규칙을 지웠다 — 로고는 언제나 「마크 + PalmDox」 다. */

/* ★ 사장님 지시(F231①·F232, 2026-08-07): **워드마크를 숨기지 않는다.**
   좁은 화면에서도 「아이콘 + PalmDox」가 다 들어가야 한다 — 대신 **크기를
   줄인다.** 전에는 640px 미만에서 워드마크를 숨겨 아이콘만 남겼다. */
@media (max-width: 640px) {
  .brand-logo { --nx-brand-h: 1.25rem; }
}

@media (max-width: 400px) {
  .brand-logo { --nx-brand-h: 1.05rem; }
}

/* ===========================================================================
 * ①-b 문서(DOX) 아이콘 (chrome.dox_icon — F262, 사장님 2026-08-08)
 *
 * "뿌리·제목 자리만 로고 마크, 페이지·폴더는 지금대로. 그리고 유저가 자신의
 *  이미지를 올려 아이콘으로 변경할 수 있게 하자."
 *
 * 글자 크기에 비례(1em)해서 옛 <i class="ri-..."> 아이콘과 같은 리듬으로
 * 앉는다. 사용자가 올린 이미지는 정사각 상자에 **맞춰 담는다**(잘리지 않게).
 * =========================================================================== */
.nx-dox-ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.15em;
  height: 1.15em;
  vertical-align: -0.2em;
  flex: 0 0 auto;
}
.nx-dox-ico img,
.nx-dox-ico svg {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: contain;
}
/* 기본형 슬롯(마크가 글자 배지)도 상자 안에 들어온다 */
.nx-dox-ico .nx-brand-badge {
  width: 100%;
  height: 100%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.7em;
}
/* 문서 이름 팝업의 미리보기 — 같은 부품, 크기만 키운다 */
.nx-dox-ico--lg { width: 40px; height: 40px; }

/* ===========================================================================
 * ①-c 에디터 전체화면 팝업 (editor_popup.ts — F269 에서 공용화)
 * 전에는 dox_detail.html 에만 있었다 — 뷰어(미리보기)도 같은 팝업을 쓰게
 * 되면서 정본을 여기로 옮겼다(상세는 앱 셸이, 뷰어는 chrome.head_css(parts)
 * 가 이 파일을 싣는다).
 * =========================================================================== */
/* ★ F492: 편집 팝업은 **시트 위** 칸이다 — 시트가 떠 있을 때 그 위에서 연다. */
.nxe-modal {
  position: fixed;
  inset: 0;
  z-index: var(--nx-z-over);
  background: rgba(15, 23, 42, .55);
  display: flex;
  align-items: stretch;
  justify-content: center;
}
/* ★★★★ F503-2(사장님 2026-08-17) — **그 페이지 안에서** 고친다.
   전체 화면을 덮는 대신 본문 칸에 매어 둔다. 자리·크기는 JS 가 그 칸을
   **재서** 넣는다(여기에 숫자를 적으면 화면마다 어긋난다 — 0-1b). */
.nxe-modal--in { inset: auto; }

.nxe-modal-frame {
  flex: 1;
  border: 0;
  width: 100%;
  height: 100%;
  background: var(--zx-canvas, #f4f6f9);
}

/* ===========================================================================
 * ② 앱(Zynix) 쪽은 여기서 그리지 않는다 — **정직한 기록**
 *
 * F221 에서 나는 `.app-sidebar .side-menu__item { padding: var(--nx-…) }` 같은
 * 규칙을 여기 두고 "앱과 랜딩이 같은 변수를 읽는다"고 적었다. **틀렸다.**
 * Zynix 는 같은 자리를 `!important` 와 더 긴 선택자로 잡고 있어 우리 규칙이
 * 진다 — 실측으로 확인했다(border-radius 우리 4px vs 화면 0px 등 7건 어긋남,
 * F222 에서 사장님이 스크린샷으로 잡으셨다).
 *
 * 그렇다고 `!important` 로 이기게 만들면 **접힌 사이드바(아이콘 레일)** 처럼
 * Zynix 가 상태에 따라 패딩을 바꾸는 자리가 망가진다. 그래서 방향을 뒤집었다:
 *
 *   · 앱 사이드바의 모양은 **Zynix 가 정본**이다(건드리지 않는다).
 *   · 아래 `:root` 변수의 기본값은 **앱에서 실제로 잰 값**이다.
 *   · 두 화면이 정말 같은지는 주장하지 않고 **매번 잰다** —
 *     `tests/harness_shell_parts.py` 가 크로미움으로 둘을 그려
 *     계산된 값을 비교하고, 하나라도 어긋나면 빨개진다.
 *
 * 즉 "같은 변수를 읽는다"가 아니라 **"같은지 매번 확인한다"** 가 이 파일의
 * 약속이다. 값을 바꿀 때는 하네스를 돌려 두 쪽이 함께 움직였는지 본다.
 * =========================================================================== */

/* ===========================================================================
 * ②-1 앱 사이드바의 **브랜드 줄을 되살린다** (F226, 사장님 2026-08-07)
 *
 * Zynix 는 `[data-toggled=close] .app-sidebar .main-sidebar-header{display:none}`
 * 과 `[data-toggled=open] …{display:none}` 으로 **기본 상태에서 로고를 숨긴다**
 * (원본 테마는 헤더에도 로고가 있어 사이드바 로고를 중복으로 봤다). 우리 셸은
 * 넓은 화면에서 헤더에 로고를 안 보여 주므로(`.horizontal-logo` 는 좁은 화면
 * 전용), 그대로 두면 **사이드바에 브랜드가 아예 없다** — 사장님이 잡으셨다.
 *
 * ★ 여기는 `!important` 를 쓴다. ② 절에서 "앱 모양은 Zynix 가 정본"이라 했지만,
 *   이건 **치수 다툼이 아니라 "있어야 할 것이 없는" 문제**다. 접힘 상태에서는
 *   워드마크만 숨고 아이콘은 남는다(위 ① 절의 반응형 2단 규칙).
 * =========================================================================== */
/* ★ `[data-toggled]` 존재를 조건으로 걸지 않는다 — Zynix 는 토글을 두 번
   누르면 속성을 **아예 지운다**(closed 모드). 그때 이 규칙이 빠지면
   브랜드 줄이 다시 사라진다(F226 회귀). 조건 없이 건다. */
.app-sidebar .main-sidebar-header {
  display: flex !important;
}

/* 브랜드 줄이 `position:fixed` 라 메뉴가 그 아래에서 시작해야 한다.
   Zynix 는 숨김을 전제로 해서 이 자리를 안 비워 둔다. */
.app-sidebar .main-sidebar-header {
  width: var(--nx-sidebar-w) !important;
  height: var(--nx-sidebar-brand-h) !important;
  justify-content: flex-start !important;
  padding: 0 !important;
}

/* 여백·높이는 **안쪽 브랜드 줄**이 갖는다 — 랜딩 드로어의 `.nx-drawer__brand`
   와 같은 자리여야 하네스가 둘을 맞대 볼 수 있다. */
.app-sidebar .nx-sidebar-brand {
  height: var(--nx-sidebar-brand-h);
  padding: var(--nx-sidebar-brand-pad);
}

/* Zynix 는 브랜드 줄이 숨는다는 전제로 이 자리에 자기 여백(4.25rem 계열)을
   갖고 있다. 우리 값으로 **덮는다** — 더하면 메뉴가 저 아래에서 시작한다
   (실측: 첫 카테고리 y 가 랜딩 21px vs 앱 89px 로 벌어졌다). */
.app-sidebar #sidebar-scroll {
  padding-top: var(--nx-sidebar-brand-h) !important;
  margin-top: 0 !important;
}

/* ===========================================================================
 * ②-2 앱 사이드바 색 — **상세보기(뷰어) 사이드바와 똑같이**
 *
 * ★★ F604-4~7(사장님 2026-08-20): "화살표4·5·6·7 상세보기 사이드바의 색상을
 *   적용해줘 똑같이". 기준은 `dox_viewer.html` 의 토큰 네 개다(F553 에서
 *   사장님이 정하신 값) — 여기서 **새 색을 만들지 않는다**:
 *     --pd-sb-bg   #171D26  사이드바 바탕
 *     --pd-sb-logo #000000  로고줄
 *     --pd-sb-item #aeb8c7  메뉴 글자
 *     --pd-sb-sub  #9da7b5  카테고리·하위·보조 글자
 *   ※ 뷰어는 그 CSS 파일을 `<link>` 로 싣지 않아(확인함) 변수를 물려줄 수
 *     없다. 그래서 값이 두 곳에 있고, **검사가 두 곳을 대조한다**
 *     (`test_templates_offline.py` — 한쪽만 바뀌면 빨간불).
 *
 * ★ 방법: 새 규칙을 만들지 않고 **Zynix 자신의 변수를 갈아 끼운다.** 그러면
 *   메뉴·구분선·하위 목록·접힘 상태까지 벤더 규칙이 전부 따라온다. 규칙을
 *   손으로 하나씩 덮으면 반드시 빠뜨린 자리가 남는다(F553 때 겪었다).
 *
 * ★★ 선택자가 왜 `html[data-menu-styles]` 인가 — **브라우저에 물어본 결과다.**
 *   변수만 갈아 끼웠더니 `--menu-bg` 는 23 29 38 로 들어갔는데 사이드바는
 *   여전히 흰색이었다(실측). 범인은 벤더의
 *     `[data-menu-styles=light] .app-sidebar { background-color: … !important }`
 *   다 — 변수를 무시하고 흰색을 **직접·!important 로** 박고, 덤으로
 *   `--menu-prime-color: 97 116 143` 까지 되돌린다. 그래서 구체성을 한 칸
 *   올리고(0,2,1) `!important` 로 맞선다. 값이 아니라 **이 두 줄이 사라지면**
 *   흰 사이드바가 그대로 돌아온다 — 아래 하네스가 실제로 재서 막는다.
 * =========================================================================== */
html[data-menu-styles] .app-sidebar {
  /* Zynix 형식은 «공백으로 나눈 세 숫자» 다 — `rgb(var(--menu-bg))` 로 쓰인다. */
  /* ★★★★ F612-2(사장님 지시 2026-08-21): **사이드바 배경 #000000.**
     뷰어의 `--pd-sb-bg` 도 같은 날 #000 으로 갔다 — 두 화면이 한 색이어야
     시트를 열었을 때 왼쪽 판이 이어져 보인다. */
  --menu-bg: 0 0 0;                 /* #000000 — 뷰어 --pd-sb-bg */
  --menu-prime-color: 174 184 199;  /* #aeb8c7 — 뷰어 --pd-sb-item */
  --menu-border-color: 0 0 0;       /* 뷰어 사이드바엔 칸막이 선이 없다(바탕과 같게) */

  /* 벤더가 !important 로 박은 흰 바탕·회색 테두리를 되받는다 */
  background-color: rgb(var(--menu-bg)) !important;
  border-color: rgb(var(--menu-border-color)) !important;

  /* Zynix 가 없는 자리(경량 셸·우리 부품)를 위한 같은 값 */
  --nx-menu-color: #aeb8c7;
  --nx-menu-icon-color: #9da7b5;
  --nx-menu-cat-color: #9da7b5;
  --nx-menu-hover-bg: rgb(255 255 255 / 0.06);
  --nx-menu-guide: rgb(255 255 255 / 0.12);
  --nx-surface: #000000;   /* F612-2 — 뷰어 --pd-sb-bg 와 한 값 */
  --nx-line: #171D26;
  /* ★ 워드마크는 **반드시** 밝아야 한다 — 로고줄이 검정이라 기본값
     (#22262a)이면 글자가 배경에 묻혀 브랜드가 사라진다. */
  --nx-brand-color: #f3f4f6;
  /* 사이드바 안 닫기 버튼(X)도 어두운 바탕 위다 */
  --nx-toggle-color: #9da7b5;
}

/* 로고줄만 검정 — 뷰어 `.dx-sb-logo{background:var(--pd-sb-logo)}` 와 같은 자리.
   아래 선도 같은 색이라야 뷰어처럼 «검정 → #171D26» 로 자연스럽게 넘어간다.
   ★ 여기도 벤더가 `[data-menu-styles=light] .app-sidebar .main-sidebar-header`
     로 흰색을 !important 로 박는다 — 같은 이유로 구체성을 한 칸 올린다. */
html[data-menu-styles] .app-sidebar .main-sidebar-header {
  background-color: #000000 !important;
  border-color: #000000 !important;
}

/* 카테고리 줄(MAIN · MY PAGE · TEST) — **브라우저에 물어보고 고쳤다.**
   벤더는 `.slide__category { opacity: .5 }` 다. 흰 바탕에서는 그게 «옅은 회색»
   이지만 #171D26 위에서는 글자가 거의 사라진다(실측: 색은 #aeb8c7 인데 절반이
   투명해 배경에 묻혔다). 뷰어 사이드바의 같은 자리는 또렷한 --pd-sb-sub 다. */
html[data-menu-styles] .app-sidebar .slide__category {
  opacity: 1;
  color: #9da7b5;
}
html[data-menu-styles] .app-sidebar .slide__category .category-name {
  color: #9da7b5;
}
.app-sidebar .nx-sidebar-brand {
  color: var(--nx-brand-color);
}

/* ===========================================================================
 * ③ 사이드바 메뉴 — Zynix 가 없는 문서(랜딩 드로어 · 경량 셸)
 *
 * `_nav_menu.html` 이 뿜는 **같은 마크업**에 모양을 준다. 예전 랜딩의 `.sd-*`
 * 계열(별도 마크업 + 별도 CSS)을 대체한다.
 * =========================================================================== */
.nx-shell-plain .nx-menu {
  list-style: none;
  margin: 0;
  padding: 0;
}

.nx-shell-plain .nx-menu .slide {
  list-style: none;
  margin: 0;
  padding: 0 var(--nx-menu-row-pad-x);
}

.nx-shell-plain .nx-menu .side-menu__item {
  display: flex;
  align-items: center;
  padding: var(--nx-menu-item-pad-y) var(--nx-menu-item-pad-x);
  font-size: var(--nx-menu-font);
  font-weight: var(--nx-menu-weight);
  border-radius: var(--nx-menu-radius);
  color: var(--nx-menu-color);
  text-decoration: none;
}

.nx-shell-plain .nx-menu a.side-menu__item:hover {
  background: var(--nx-menu-hover-bg);
}

.nx-shell-plain .nx-menu .side-menu__label {
  font-size: var(--nx-menu-label-font);
}

.nx-shell-plain .nx-menu .side-menu__item.active,
.nx-shell-plain .nx-menu .side-menu__item.active .side-menu__label {
  color: var(--nx-menu-active-color);
  font-weight: var(--nx-menu-active-weight);
}

.nx-shell-plain .nx-menu .side-menu__icon {
  font-size: var(--nx-menu-icon-size);
  margin-inline-end: var(--nx-menu-icon-gap);
  color: var(--nx-menu-icon-color);
  line-height: 0;
  flex: 0 0 auto;
}

.nx-shell-plain .nx-menu .slide__category {
  list-style: none;
  padding: var(--nx-menu-cat-pad-y) var(--nx-menu-cat-pad-x);
  font-size: var(--nx-menu-cat-font);
  font-weight: var(--nx-menu-weight);
  letter-spacing: var(--nx-menu-cat-track);
  text-transform: uppercase;
  color: var(--nx-menu-cat-color);
  white-space: nowrap;
}

/* 하위 그룹 — 앱은 Zynix 의 defaultmenu.min.js 가 접었다 폈다 한다.
   F222(사장님 실측): 랜딩만 늘 펼쳐 그려 앱과 눈에 띄게 달랐다. 이제 **같은
   구조·같은 클래스**로 접힌 채 시작하고, 랜딩의 짧은 배선이 `.open` 을 토글한다.
   화살표(›)도 앱처럼 보이고, 펼치면 90도 돈다. */
.nx-shell-plain .nx-menu .side-menu__angle {
  /* ★ 마크업에서 화살표는 **첫 자식**이다(Zynix 구조 그대로) — 앱에서는
     벤더 CSS 가 절대배치로 오른쪽 끝에 보낸다. 우리는 flex order 로 보낸다.
     `margin-inline-start:auto` 만으로는 첫 자식이라 안 밀린다(실측). */
  order: 9;
  margin-inline-start: auto;
  font-size: var(--nx-menu-font);
  color: var(--nx-menu-icon-color);
  transition: transform .2s;
}

.nx-shell-plain .nx-menu .slide.open > .side-menu__item .side-menu__angle {
  transform: rotate(90deg);
}

/* 하위 목록 안에서 부모 이름을 한 번 더 보여 주는 줄 — 앱은 접힌 레일에서만
   쓴다(펼친 사이드바에서는 안 보인다). 랜딩에도 안 보이게 맞춘다. */
.nx-shell-plain .nx-menu .side-menu__label1 {
  display: none;
}

.nx-shell-plain .nx-menu .slide-menu {
  display: none;
  list-style: none;
  margin: 0 0 0 1.55rem;
  padding: 0;
  border-inline-start: 1px solid var(--nx-menu-guide);
}

.nx-shell-plain .nx-menu .slide.open > .slide-menu {
  display: block;
}

.nx-shell-plain .nx-menu .slide-menu .side-menu__item {
  padding: var(--nx-menu-sub-pad-y) var(--nx-menu-sub-pad-x);
}

/* 하위를 가진 부모 줄은 누르면 펼쳐진다 — 앱과 같다 */
.nx-shell-plain .nx-menu .has-sub > .side-menu__item {
  cursor: pointer;
}

/* ===========================================================================
 * ④ 사이드바 토글 (partials/_sidebar_toggle.html)
 *
 * 어느 아이콘을 보일지는 앱에서 `<html data-toggled>` 가 정한다(nx-form.css).
 * Zynix 가 없는 문서에는 그 상태값이 없으므로 버튼 자신의 aria-expanded 로
 * 정한다 — 접근성 속성과 화면이 **한 값에서** 나온다.
 * =========================================================================== */
.nx-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

.nx-toggle__box {
  display: flex;
  width: var(--nx-toggle-box);
  height: var(--nx-toggle-box);
  align-items: center;
  justify-content: center;
}

.nx-toggle__box > i {
  font-size: var(--nx-toggle-icon);
  font-weight: 400;         /* 사장님 2026-08-20: 볼드체 아닌 것 */
  line-height: 1;
  color: var(--nx-toggle-color);
}

/* ★ F605-4(사장님 2026-08-20): *"이 사이드바 **여는 아이콘(ri-menu-line) 만**
   FONT-SIZE 20PX 로 해줘. 모달 내부 다 마찬가지. **닫는 것은 그대로**."*
   ⇒ 여는 쪽(`.menu-btn`)만 20px, 닫는 쪽(`.menu-btn-close`)은 24px 그대로다.
   ★ 두 번 쓰는 이유 — 모달 안의 토글은 `.nx-toggle__box` 를 안 거치는 자리가
     있다. 클래스 하나짜리 규칙만 두면 상자 안에서는 위 규칙(0,1,1)에 져서
     24px 로 남는다. "모달 내부 다 마찬가지" 를 지키려면 둘 다 있어야 한다. */
.menu-btn { font-size: var(--nx-toggle-icon-open); }
.nx-toggle__box > i.menu-btn { font-size: var(--nx-toggle-icon-open); }

/* F229①(사장님): 화면 왼쪽 끝에서 **보이는 아이콘까지의 여백**이 앱 사이드바
   로고(16px)와 같아야 한다. 클릭 상자(42px)가 20px 아이콘을 가운데 두므로
   그냥 두면 아이콘이 27px 에서 시작해 "미세하게" 어긋난다. 상자 여백만큼
   되당긴다 — 클릭 면적은 그대로다. 헤더 줄에서만 적용한다(뷰어·사이드바
   닫기 버튼은 자기 자리가 따로 있다).
   ★ F605-4(2026-08-20): 이 줄에 보이는 것은 **여는 아이콘**이다(헤더 토글은
     사이드바가 닫혀 있을 때만 나온다 — 아래 `[aria-expanded]` 규칙). 그래서
     기준 치수도 `--nx-toggle-icon-open` 이라야 한다. `--nx-toggle-icon`(닫는
     쪽 24px)을 쓰면 왼쪽 여백이 2px 어긋나 F229① 이 잡았던 어긋남이 돌아온다. */
.app-header .nx-toggle,
.nav-inner .nx-toggle {
  margin-inline-start: calc((var(--nx-toggle-icon-open) - var(--nx-toggle-outer)) / 2);
}

/* ★★ F258(사장님 2026-08-07): "MY DOX 페이지에서 화살표 아이콘 앞 간격이
   다시 메인과 달라졌다."

   원인 — **바깥 크기를 우리가 정하고 있지 않았다.** 앱 토글의 폭은
   `--nx-toggle-outer` 가 아니라 **벤더 패딩의 합**이었다. 브라우저에 물어본
   결과다(추측 아님 — 규약 0-1b):

     .main-header-container .header-link              padding: 0 0.25rem    (0,2,0)
     @media (max-width: 575.98px)
       .main-header-container .header-element .header-link
                                                      padding: 0.5rem 0.3rem (0,3,0)
     .nx-toggle                                       padding: 0             (0,1,0) ← 진다

   넓은 화면에서는 4 + 34 + 4 = 42px 라 **우연히** 맞았다. 그런데 576px 미만
   에서 벤더가 0.3rem 으로 바꾸면 4.8 + 34 + 4.8 = 43.6px 이 되고, 가운데 놓인
   상자가 0.8px 밀린다 — 랜딩은 42px 그대로다. 그래서 두 화면의 아이콘 앞
   간격이 갈라졌다. "고쳤다가 되돌아간다"고 보이신 것이 이것이다: 우리 값이
   이긴 적이 없고, 폭에 따라 벤더 값이 바뀌었을 뿐이다.

   고침 — 벤더 패딩과 싸우지 않고 **바깥 크기를 값으로 못 박는다.** 선택자를
   (0,4,0) 으로 올려 벤더의 좁은 화면 규칙(0,3,0)을 확실히 이긴다 — 문서
   순서에 기대지 않는다(링크 순서가 바뀌면 조용히 되돌아간다).
   `flex: 0 0` 은 헤더가 붐빌 때 토글이 **줄어드는 것**을 막는다.
   ※ 세로(padding-block)는 건드리지 않는다 — 헤더가 가운데 정렬이라 화면에
     안 보이고, 클릭 면적은 넓을수록 낫다.
   ★ 하네스 「토글 바깥 크기(폭별)」가 **폭마다 새 페이지를 열어** 지킨다. */
.app-header .main-header-container .nx-toggle.header-link,
.nav-inner .nx-toggle {
  box-sizing: border-box;
  inline-size: var(--nx-toggle-outer);
  padding-inline: 0;
  flex: 0 0 var(--nx-toggle-outer);
}

/* ===========================================================================
 * ★ 여닫는 자리는 **한 규칙**이다 (사장님 2026-08-07, F235·F236)
 *
 *   사이드바가 보이면  → 사이드바 안의 **닫기** 버튼만
 *   사이드바가 접히거나 숨으면 → 로고 앞의 **여는** 버튼만
 *
 * 전에는 사이드바가 열려 있어도 헤더 토글이 같이 보여 **여닫는 자리가 둘**
 * 이었다. 앱은 Zynix 가 `<html data-toggled>` 로 상태를 알려 주므로, 벤더 JS
 * 를 건드리지 않고 **보이는 자리만** 이 규칙에 맞춘다.
 *   · 접힘 값은 전부 `-close` 또는 `-closed` 로 끝난다(icon-overlay-close 등)
 *   · 좁은 화면에서 서랍이 나온 상태는 `open` 이다 — 그때도 사이드바가
 *     보이므로 헤더 토글은 숨고 사이드바 안 닫기 버튼이 맡는다
 * =========================================================================== */
/* ★ `data-toggled="close"` 는 **화면 폭에 따라 뜻이 다르다** — 넓은 화면에서는
 *   "펼쳐짐", 좁은 화면에서는 "서랍이 숨음"이다. `nx-form.css` 가 토글 아이콘을
 *   고를 때 이미 같은 이유로 미디어쿼리로 나눠 놨다. 여기서도 나눈다.
 *   (한 규칙으로 쓰려다 좁은 화면에서 여는 버튼이 사라졌다 — 하네스가 잡았다.) */
@media (min-width: 1025px) {
  /* 펼쳐짐(close·open 등 `-close`/`-closed` 로 안 끝나는 값) → 사이드바 안 닫기만
     ★ 0-①: 접힘 복원의 부팅 창(data-toggled='close', 표식 closed)은 예외다 —
       그때 이 규칙이 걸리면 여는 버튼이 없다가 벤더 부팅 뒤에 나타나
       로고가 옆으로 튄다(첫 페인트 깜빡임의 다른 얼굴). */
  html:not([data-toggled$="-close"]):not([data-toggled$="-closed"]):not([data-nx-sb="closed"]) .app-header .nx-toggle {
    display: none;
  }
  /* 아이콘 레일로 접힘 → 자리가 없으니 사이드바 안 닫기는 숨고 헤더가 맡는다 */
  html[data-toggled$="-close"]:not([data-icon-overlay="open"]) .nx-sidebar-close,
  html[data-toggled$="-closed"]:not([data-icon-overlay="open"]) .nx-sidebar-close {
    display: none;
  }
}

@media (max-width: 1024.98px) {
  /* 서랍이 나와 있을 때만 사이드바 안 닫기를 쓰고 헤더 토글을 숨긴다 */
  html[data-toggled="open"] .app-header .nx-toggle { display: none; }
  html:not([data-toggled="open"]) .nx-sidebar-close { display: none; }
}

.nx-toggle[aria-expanded="false"] .menu-btn-close,
.nx-toggle[aria-expanded="true"] .menu-btn {
  display: none;
}

.nx-toggle[aria-expanded="false"] .menu-btn,
.nx-toggle[aria-expanded="true"] .menu-btn-close {
  display: block;
}

/* ===========================================================================
 * ⑤ 드롭다운 (_lang_menu.html · _profile_menu.html)
 *
 * `<details>` 라 JS 가 없어도 열린다 — 랜딩·경량 셸에 preline 이 없기 때문이다.
 * =========================================================================== */
.nx-drop {
  position: relative;
}

.nx-drop__btn {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  list-style: none;
  cursor: pointer;
  padding: 0.35rem 0.5rem;
  border-radius: var(--nx-menu-radius);
  color: var(--nx-menu-color);
  font-size: var(--nx-menu-font);
}

.nx-drop__btn::-webkit-details-marker { display: none; }

/* ★★★★★ F483(사장님 2026-08-16 F477 · 2026-08-17 재지적) —
   *"언어선택은 소프트블루 외곽 테두리 있지 않나? 그런데 랜딩 페이지,
     MY DOX 등에서는 안 그런데 왜인가?"*

   왜 안 그랬나 — 마크업은 이미 한 벌(`_lang_menu.html`)이었는데, 이 규칙만
   `dox_viewer.html` 안에 `#dxLang > .nx-drop__btn` 로 **인라인으로 박혀**
   있었다. 그래서 그 화면 밖에서는 아예 존재하지 않았다.
   0-0 규약이 금하는 바로 그것이다 — *"만들면 공용 CSS 에 둔다. 템플릿 안에
   박지 않는다."*  정본이 한 화면 안에 갇히면 다른 화면은 쓸 수가 없다.

   ★ 화면 id(`#dxLang`·`#hdrLang`)가 아니라 **부품 이름**에 건다 — 그래야
     넷째 화면이 생겨도 저절로 같다.
   ★ 색은 **Light primary 정본** 하나에서 온다(`--nx-primary-soft-bd`). 새 색을
     만들지 않았다. 실행 버튼 테두리(`.ti-btn.nx-act`)와 같은 값이다.
   ★ 세 화면에서 **재서** 지킨다 — `tests/harness_f483.py`. */
.nx-drop--lang > .nx-drop__btn {
  border: 1px solid var(--nx-primary-soft-bd);
  border-radius: 0;                 /* 라운드 0 — 전 화면 규칙 */
  background: transparent;
}
/* ★ 2026-09-05 — 손으로 박아 둔 소프트블루 10% 를 **정본 토큰**으로 바꿨다.
   숫자가 남아 있으면 통일이 그 자리에서 깨진다(0-0). */
.nx-drop--lang > .nx-drop__btn:hover { background: var(--nx-primary-soft-bg); }

.nx-drop__btn:hover { background: var(--nx-menu-hover-bg); }

.nx-drop__code {
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
}

.nx-drop__caret { font-size: 0.9rem; line-height: 1; }

.nx-drop__name {
  max-width: 10rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nx-drop__menu {
  position: absolute;
  inset-inline-end: 0;
  top: calc(100% + 0.25rem);
  z-index: 60;
  min-width: 10rem;
  margin: 0;
  padding: 0;
  list-style: none;
  overflow: hidden;
  border: 1px solid var(--nx-line);
  /* F210 확정: 라운드 0. 새 공용 부품에도 예외 없이 적용한다
     (F228 — 여기에 0.5rem 을 썼다가 사장님께 지적받았다). */
  border-radius: var(--nx-menu-radius);
  background: var(--nx-surface);
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.12);
}

.nx-drop__menu--wide { min-width: 14rem; }

.nx-drop__item {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.55rem 0.9rem;
  font-size: var(--nx-menu-font);
  color: var(--nx-menu-color);
  text-decoration: none;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: start;
}

.nx-drop__item:hover { background: var(--nx-menu-hover-bg); }

.nx-drop__item.is-on { font-weight: 600; }

.nx-drop__ico { margin-inline-end: 0.5rem; }

.nx-drop__head {
  padding: 0.6rem 0.9rem;
  text-align: center;
  border-bottom: 1px solid var(--nx-line);
}

.nx-drop__headname { display: block; font-weight: 600; font-size: var(--nx-menu-font); }
.nx-drop__headrole { display: block; font-size: 0.72rem; color: var(--nx-menu-cat-color); }
.nx-drop__foot { padding: 0.5rem; border-top: 1px solid var(--nx-line); }

/* ===========================================================================
 * ⑥ 낱개 버튼 (_theme_toggle.html · _profile_menu.html)
 * =========================================================================== */
.nx-iconbtn {
  display: inline-grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  border: 0;
  background: none;
  border-radius: var(--nx-menu-radius);
  color: var(--nx-menu-color);
  cursor: pointer;
  font-size: 1.15rem;
  line-height: 1;
}

.nx-iconbtn:hover { background: var(--nx-menu-hover-bg); }

.nx-btn-brand {
  display: inline-block;
  padding: 0.4rem 0.85rem;
  border-radius: var(--nx-menu-radius);
  background: rgb(var(--primary, 95 106 245));
  color: #fff;
  font-size: var(--nx-menu-font);
  font-weight: 500;
  text-decoration: none;
  border: 0;
  cursor: pointer;
}

.nx-btn-brand--block { display: block; width: 100%; }


/* 아이콘 + 글자 (로그인 버튼) */
.nx-btn-brand--icon {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

/* 보조(고스트) — 주 동작 옆의 덜 중요한 동작 (9-130, A-1).
 * 세 셸(앱·경량·랜딩) 공통 부품이다 — 경량 셸에서 ti-btn-light 를 쓰면
 * Zynix 가 없어 브라우저 기본 버튼이 그려진다(세션 경고 팝업이 그랬다). */
.nx-btn-brand--ghost {
  background: transparent;
  color: inherit;
  border: 1px solid var(--nx-plain-line, #d5dae3);
}

.nx-btn-brand--ghost:hover { background: var(--nx-menu-hover-bg, rgba(0, 0, 0, 0.05)); }

/* ===========================================================================
 * ⑦ 좌측 드로어 — Zynix 가 없는 문서의 사이드바 (랜딩)
 *
 * 앱 사이드바와 **같은 메뉴 마크업**을 담는 그릇이다. 예전의 `.side-drawer__*`
 * 를 대체한다.
 * =========================================================================== */
.nx-drawer { position: fixed; inset: 0; z-index: var(--nx-z-drawer); }

/* F225②(사장님): 드로어가 **미끄러져 나온다.** 전에는 `hidden` 만 떼서
   그 자리에서 튀어나왔다 — 앱의 모바일 서랍은 Zynix 가 300ms 로 민다.
   `hidden` 은 `display:none` 이라 전이가 안 걸리므로, JS 가 먼저 붙이고
   다음 프레임에 `is-open` 을 준다(landing index.html 의 배선). */
.nx-drawer__panel {
  transform: translateX(-100%);
  transition: transform .3s cubic-bezier(.4, 0, .2, 1);
}

.nx-drawer.is-open .nx-drawer__panel { transform: translateX(0); }

.nx-drawer__backdrop {
  opacity: 0;
  transition: opacity .3s cubic-bezier(.4, 0, .2, 1);
}

.nx-drawer.is-open .nx-drawer__backdrop { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .nx-drawer__panel,
  .nx-drawer__backdrop { transition: none; }
}

.nx-drawer__backdrop {
  position: absolute;
  inset: 0;
  background: rgb(10 10 10 / 0.4);
}

.nx-drawer__panel {
  position: absolute;
  inset-inline-start: 0;
  top: 0;
  bottom: 0;
  width: var(--nx-sidebar-w);
  max-width: 82vw;
  background: var(--nx-surface);
  border-inline-end: 1px solid var(--nx-line);
  box-shadow: 8px 0 24px rgb(0 0 0 / 0.12);
  overflow-y: auto;
}

.nx-drawer__brand {
  display: flex;
  align-items: center;
  min-height: var(--nx-sidebar-brand-h);
  padding: var(--nx-sidebar-brand-pad);
  border-bottom: 1px solid var(--nx-line);
}

/* F227·F230(사장님): 열린 사이드바 안에 **닫기 버튼** — 랜딩 드로어와 앱
   사이드바 **둘 다**. 같은 토글 부품이고 `aria-expanded="true"` 라 닫기
   아이콘이 나온다. */
.nx-drawer__close,
.nx-sidebar-close { margin-inline-start: auto; }

/* 앱 쪽은 Zynix 의 `.sidemenu-toggle` 클래스를 물려 벤더 JS 가 닫아 준다 —
   그 클래스가 가져오는 벤더 여백은 우리 상자 크기로 되돌린다. */
.app-sidebar .nx-sidebar-close {
  padding: 0;
  flex: 0 0 auto;
}

.app-sidebar .nx-sidebar-close .menu-btn-close { display: block; }

/* ===========================================================================
 * ⑦-1 인증 화면(로그인·회원가입) 브랜드 줄 — 사장님 2026-08-07
 *
 * 다른 화면보다 크게 보여 주되, 크기는 **같은 변수**에서 나온다.
 * =========================================================================== */
.nx-auth-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 1rem;
  text-decoration: none;
}

/* ★ 인증 화면은 **로고 아이콘만** 쓴다(사장님 지시) — 워드마크는 숨긴다.
   바로 아래 줄에 브랜드명이 제목으로 다시 나오므로 글자가 두 번 나온다. */
.nx-auth-brand .brand-logo {
  --nx-brand-h: 48px;
  gap: 0;
}

.nx-auth-brand .brand-logo__word {
  display: none;
}

/* ===========================================================================
 * ⑧ 헤더 아바타(이니셜) — _profile_menu.html
 *
 * 전에는 슬롯 zynix-custom.css 8벌에 각각 있었다. 랜딩도 같은 프로필 메뉴를
 * 쓰게 되면서 Zynix 변수가 없는 문서에서도 그려져야 한다 → 폴백을 둔다.
 * =========================================================================== */
.nx-avatar-initial {
  display: inline-grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  /* ★ 사장님 확정 2026-08-07: **전부 라운드 0** — 아바타도 원이 아니다. */
  border-radius: 0;
  background-color: rgb(var(--primary, 95 106 245) / 0.12);
  color: rgb(var(--primary, 95 106 245));
  font-weight: 600;
  font-size: 0.8rem;
  flex: 0 0 auto;
}

/* ===========================================================================
 * 라운드 0 — **전부**. (사장님 확정 2026-08-07: "전부 라운드0")
 *
 * 왜 통짜 규칙인가 — 벤더(Zynix) styles.css 는 28,051 규칙이고 `!important` 를
 * 곳곳에 박아 둔다. 우리가 쓰는 클래스만 골라 덮으면 **반드시 새는 곳이 생긴다**
 * — 실제로 브라우저로 재 보니 `i.ti`(4px)와 `button.ti-btn`(5px)이 남아 있었고,
 * 이 둘은 파일을 아무리 grep 해도 우리 CSS 에는 없다(벤더 것이다).
 *
 * ★ 그리고 나는 `grep border-radius` 로 세다가 사장님께 **두 번 거짓 보고**했다
 *   ("영어 랜딩만 라운드가 남았다" · "알약 5건이 남았다" — 둘 다 이미 0 이었다).
 *   CSS 는 캐스케이드다. 세는 방법을 바꿨다: 이제 하네스가 **브라우저에 물어서**
 *   0 이 아닌 모서리가 하나라도 있으면 빨개진다(tests/harness_shell_parts.py).
 *
 * ★ 되돌리려면 이 블록 하나만 지우면 된다 — 예외를 두고 싶으면 이 뒤에
 *   그 선택자만 다시 적는다(이 규칙보다 뒤에 와야 이긴다).
 * =========================================================================== */
*,
*::before,
*::after {
  border-radius: 0 !important;
}

/* ---------------------------------------------------------------------------
 * ★★★★★★ 라운드 0 의 **첫 예외 둘** — 토글 스위치와 라디오 (사장님 지시 2026-09-05)
 *
 *   *"위 스위치를 css 의 Toggle Switch With Description 로 해주고 라운드를
 *     원 css 대로 유지해줘 · 그리고 css 에서 radio 도 라운드를 원 css 대로"*
 *
 * 왜 예외인가 — 이 둘은 **모양이 곧 뜻**인 부품이다. 네모난 토글은 «켜짐/꺼짐»
 * 으로 안 읽히고, 네모난 라디오는 **체크박스와 구별되지 않는다**(체크박스는
 * 여러 개, 라디오는 하나만 — 그 차이를 알려 주는 것이 동그라미다).
 * 나머지 전 화면은 라운드 0 그대로다(F210).
 *
 * ★ 위 통짜 규칙 **뒤에** 둔다 — 그 규칙의 주석이 «예외를 두고 싶으면 이 뒤에
 *   그 선택자만 다시 적는다» 고 적어 둔 그 자리다.
 * ★ 값은 **원 CSS 에서 그대로 베낀다**(새 숫자를 짓지 않는다 — 실측 2026-09-05):
 *     `.ti-switch`, `.ti-switch::before`      → 9999px  (알약 궤도 + 알약 손잡이)
 *     `.form-check-input[type=radio]`         → 50%     (원)
 *   두 값이 다른 것은 벤더가 그렇게 적어 두었기 때문이다.
 * ★ 맨몸 `input[type="radio"]`(클래스 없는 것 — dox_status·dox_hosting 이 쓴다)은
 *   `appearance: auto` 라 **브라우저가 직접 그린다**: border-radius 를 아예 안
 *   듣는다(크로미움 실측 — 통짜 0 도, 이 예외도 둘 다 무시된다). 그래서 그것들은
 *   손대지 않아도 원래 동그랗다. 여기 안 적는 이유가 그것이다.
 * ------------------------------------------------------------------------- */
/* ★ F915 2차(2026-09-17) — 여기 있던 싱크 **스위치 상자**의 라운드 예외를 걷었다.
 *   그 부품(`nx-cmpsync`)이 아이콘+팝업으로 바뀌면서 없어졌다 — 예외만 남으면
 *   다음 사람이 «어디 쓰는 값인가» 를 찾는다(1-1). 되살릴 때는 F687 절을 보라. */
.form-check-input[type="radio"] {
  border-radius: 50% !important;      /* = 원 CSS `.form-check-input[type=radio]` */
}

/* ===========================================================================
 * 접힘 = **완전히 닫힘**. 아이콘 레일·마우스오버 펼침 없음.
 *   (사장님 F242② 2026-08-07: "줄어든 경우의 사이드바는 없애자.
 *    마우스오버 등 사용 않는다.")
 *
 * 벤더 Zynix 는 접힘을 **아이콘 레일**로 만든다:
 *     [data-toggled=icon-overlay-close]:not([data-icon-overlay=open])
 *         .app-sidebar { width: 5rem }
 *     [data-toggled=icon-overlay-close] .app-content { margin-inline-start: 5rem }
 *     [data-toggled=icon-overlay-close][data-icon-overlay=open]
 *         .app-sidebar { width: 15rem }        ← 마우스오버로 잠깐 펼침
 *
 * 벤더 파일은 재수입하면 사라지므로 고치지 않는다. 여기서 덮는다.
 * ★ `[data-icon-overlay=open]`(호버 상태)을 **선택자에서 뺐다** — 호버로도
 *   되살아나면 안 되기 때문이다. 게다가 화면 밖으로 밀고 visibility 를 끄면
 *   마우스가 사이드바에 닿지 못해 호버 자체가 안 걸린다(이중 안전).
 * =========================================================================== */
@media (min-width: 1025px) {
  html[data-toggled$="-close"] .app-sidebar,
  html[data-toggled$="-closed"] .app-sidebar {
    /* ★ `transform` 으로 밀어내려다 **두 번 실패했다**(실측). Zynix 는
       Tailwind 방식이라 `transform: translate(var(--tw-translate-x), …)` 를
       `!important` 로 박고, 그 선택자가 우리 것보다 상세도가 높다
       (`[data-toggled=…]:not([data-icon-overlay=open]) .app-sidebar` = 0,3,1
        vs 우리 `html[data-toggled$="-close"] .app-sidebar` = 0,2,2).
       변수를 바꿔도 이겼다 졌다 해서 결과가 흔들렸다.
       → **싸우지 않는다.** 사장님이 원하는 것은 "없애자" 이므로 그냥 뗀다.
         `display: none` 은 transform 싸움과 무관하고, 마우스가 닿을 수 없어
         호버 펼침도 원천적으로 안 걸린다. */
    display: none !important;
  }
  html[data-toggled$="-close"] .app-sidebar .main-sidebar-header,
  html[data-toggled$="-closed"] .app-sidebar .main-sidebar-header {
    width: var(--nx-sidebar-w) !important;
    justify-content: flex-start !important;
  }
  /* 본문은 사이드바가 없는 만큼 왼쪽 끝까지 쓴다(벤더는 5rem 을 남긴다) */
  html[data-toggled$="-close"] .app-content,
  html[data-toggled$="-closed"] .app-content,
  html[data-toggled$="-close"] .content,
  html[data-toggled$="-closed"] .content {
    margin-inline-start: 0 !important;
  }
}

/* ===========================================================================
 * ★ 0-①(2026-08-08) — 접힘 복원의 **첫 페인트**를 벤더 JS 와 분리한다
 *
 * 증상(사장님, 9-118 뒤에도 남음): "숨긴 상태로 트리 페이지에 오면 사이드바가
 * **나타나 있다가** 숨겨진다." 최종 상태는 맞는데(F253) 첫 페인트가 틀렸다.
 *
 * 왜 — 복원 스크립트(shell_app.html <head>)는 접힘을 data-toggled='close' 로
 * 적는다. 벤더가 부팅 때 toggleSidemenu() 를 한 번 불러 close-menu-close 로
 * 뒤집어 주기를 **기다리는** 값이다(함정 5). 그런데 defaultmenu.min.js 는
 * </body> 직전에 로드된다 — 그때까지 'close' 는 위의 접힘 규칙('-close' /
 * '-closed' 로 끝나는 값)에 안 걸려 사이드바가 **펼쳐진 채** 그려진다.
 * 컨테이너 실측으로는 이 창이 48ms 지만, styles.css 2.79MB + JS 7개를
 * 네트워크로 받는 사장님 화면에서는 수백 ms 로 벌어진다.
 *
 * 그래서 <head> 스크립트가 data-toggled 와 **별개로** data-nx-sb 표식을 찍고,
 * 여기서는 그 표식만 보고 첫 페인트부터 **최종 모습**을 그린다. 벤더 부팅
 * 토글이 나중에 data-toggled 를 정리해도 화면은 이미 맞아 있어 움직이지
 * 않는다. 표식은 토글을 누를 때 rememberSidebar()(zynix_glue.ts)가 같이
 * 갱신한다.
 *
 * ★ 검증은 문자열이 아니라 **프레임**으로 한다 — probe_f253.py 가
 *   defaultmenu.min.js 응답을 300ms 지연시켜 놓고(사장님 환경 재현) rAF 로
 *   첫 페인트부터 추적해, 접힘 복원 중 사이드바가 한 프레임이라도 보이면
 *   빨개진다.
 * =========================================================================== */
@media (min-width: 1025px) {
  html[data-nx-sb="closed"] .app-sidebar { display: none !important; }
  html[data-nx-sb="closed"] .app-content,
  html[data-nx-sb="closed"] .content { margin-inline-start: 0 !important; }
  /* 헤더도 첫 페인트부터 최종 모습으로 — 벤더 기본은 사이드바 폭(15rem)을
     비워 두므로, 놔두면 로고·토글이 오른쪽에 그려졌다가 0 으로 튄다
     (같은 깜빡임의 다른 얼굴이다). */
  html[data-nx-sb="closed"] .app-header { padding-inline-start: 0 !important; }
  html[data-nx-sb="closed"] .app-header .horizontal-logo { display: block !important; }
}

/* ===========================================================================
 * 사이드바를 닫으면 **헤더도 왼쪽 끝부터** 쓴다 + 로고가 사라지지 않는다
 *   (사장님 F246·F247, 2026-08-07)
 *
 * F242② 로 접힘을 "완전히 닫힘"으로 바꾸고 나서 두 가지가 드러났다 —
 * 둘 다 **내가 만든 회귀**다:
 *
 *  ① 헤더가 여전히 사이드바 자리를 비워 둔다.
 *     Zynix 는 `.app-header { padding-inline-start: 15rem }` 로 사이드바 폭을
 *     비운다(실측: 토글이 x=245 에서 시작했다 — 랜딩은 5px). 사이드바가
 *     없는데 그 자리를 비우니 **왼쪽 여백이 서로 다르게** 보였다.
 *  ② 로고가 아예 사라진다.
 *     앱의 로고는 두 곳에 있다 — 사이드바 브랜드 줄, 그리고 헤더의
 *     `.horizontal-logo`(Zynix 가 **좁은 화면에서만** 보여 준다).
 *     사이드바를 완전히 닫으면 첫째가 없어지는데, 넓은 화면에서는 둘째가
 *     `display:none` 이라 **로고가 한 개도 안 남는다.**
 *
 * ★ 나는 하네스에서 **880px 한 폭만** 재고 "모든 값이 같다"고 보고했다.
 *   1280px 에서는 245 vs 5 였다. 폭을 하나만 재는 것은 언어를 하나만 재는
 *   것과 같은 실수다(F230 에서 이미 겪었다) — 하네스가 이제 두 폭을 다 잰다.
 * =========================================================================== */
@media (min-width: 1025px) {
  /* ★ 헤더의 왼쪽 여백은 **벤더가 이미 알아서 한다** —
     `[data-vertical-style=closed][data-toggled=close-menu-close] .app-header
      { padding-inline-start: 0 }`. `closed` 모드를 고른 덕분이다(F242②).
     ※ 정직한 기록: 나는 여기서 245px 를 봤다고 믿고 음수 여백으로 당기는
       땜질을 넣었다가 **-235px** 를 만들었다. 그 245px 는 브라우저가 물고
       있던 **낡은 페이지/CSS** 였다(하네스가 새로 렌더한 페이지에서는 5px).
       화면을 잴 때는 **그 자리에서 새로 렌더한 것**을 재야 한다. */
  /* 사이드바가 닫히면 헤더 로고를 보여 준다 — 화면에 로고가 0개가 되면 안 된다 */
  html[data-toggled$="-close"] .app-header .horizontal-logo,
  html[data-toggled$="-closed"] .app-header .horizontal-logo {
    display: block !important;
  }
}

/* ===========================================================================
 * ★ F263-간격(사장님 2026-08-08): "페이지가 바뀔 때 간격이 상단바만큼
 *   벌어졌다가 위로 올라간다. 백버튼은 안 그렇다."
 *
 * 원인(rAF 3회 재현·스냅샷 실측): 벤더 `.page` 가
 *     display:flex; flex-direction:column; min-height:100vh;
 *     justify-content:center;                ← 이것
 * 이라, **내용이 화면보다 짧은 동안** 헤더·본문 전체가 세로 가운데로 밀린다
 * (실측: 남는 68px 의 절반 = 34px 만큼 헤더가 내려앉음 → 목록·폰트가
 * 로드되어 내용이 화면을 넘는 순간 0 으로 튀어 오른다). 로드가 느릴수록
 * 오래 보이고, 백버튼은 bfcache 라 다시 그리지 않아 안 보인다.
 * 벤더 파일은 고치지 않는다(함정 4) — 여기서 덮는다. 짧은 페이지가 세로
 * 가운데 오는 것 자체가 앱 셸에서는 틀린 모습이다(헤더는 늘 맨 위).
 * html 을 붙여 특이도(0,1,1)로 벤더(0,1,0)를 확실히 이긴다(함정 7).
 * =========================================================================== */
html .page { justify-content: flex-start; }

/* ===========================================================================
 * ★ 0-① 짝(2026-08-08, 사장님 확정 "넓은 화면 MY DOX 기본은 사이드바가
 *   나와 있어야 한다") — **펼침도 첫 페인트부터** 최종 모습으로 그린다
 *
 * 접힘(9-119)과 대칭이다. 펼침으로 시작할 때 <head> 는 data-toggled 를
 * close-menu-close 로 적는다(벤더 부팅 토글이 지워 주기를 기다리는 값 —
 * 함정 5). 그런데 그 값은 벤더·위 접힘 규칙의 "접힘"에 걸려, 벤더 JS 가
 * 올 때까지 사이드바가 숨고 본문·헤더가 왼쪽 끝에 붙는다. 벤더 JS 가 늦게
 * 오면(사장님 실측 5~20초 로드) 그만큼 사이드바 없이 떠 있다가, 도착 순간
 * **본문 여백·헤더 패딩이 0→240px 로 미끄러진다**(벤더 .app-sidebar 전이
 * 150ms — 컨테이너 실측: ml 0→138→222→240, 90ms). 그게 사장님이 보신
 * "좌측 메뉴가 다시 제자리를 잡느라 깜박인다"다.
 *
 * 그래서 표식이 open 이면 첫 페인트부터 펼친 모습을 강제한다. 값이 처음부터
 * 최종값이라 벤더 부팅 토글이 와도 전이가 **일어날 것이 없다.**
 *
 * ★ 이 블록은 반드시 위 접힘 규칙들(-close/-closed · data-nx-sb=closed)보다
 *   **뒤**에 있어야 한다 — 부팅 창에는 [data-nx-sb=open] 과
 *   [data-toggled=close-menu-close] 가 **동시에** 참이고, 특이도가 같아
 *   순서가 승부를 정한다(함정 7 의 교훈 그대로).
 * ★ 값 주의: 벤더 .app-sidebar 폭도 15rem, 벤더 펼침 여백도 15rem 이다 —
 *   --nx-sidebar-w(15rem, F230)와 같은 값이라 부팅 후 화면이 안 움직인다.
 * =========================================================================== */
@media (min-width: 1025px) {
  html[data-nx-sb="open"] .app-sidebar { display: block !important; }
  html[data-nx-sb="open"] .app-content,
  html[data-nx-sb="open"] .content { margin-inline-start: var(--nx-sidebar-w) !important; }
  html[data-nx-sb="open"] .app-header { padding-inline-start: var(--nx-sidebar-w) !important; }
  /* 버튼·로고도 펼침의 최종 모습으로 — 늦게 나타나면 로고가 옆으로 튄다 */
  html[data-nx-sb="open"] .app-header .nx-toggle { display: none; }
  html[data-nx-sb="open"] .app-header .horizontal-logo { display: none !important; }
  html[data-nx-sb="open"] .app-sidebar .nx-sidebar-close { display: inline-flex; }
}

/* ===========================================================================
 * ⑨ nx-sheet — **전체화면 모달 한 벌** (F477, 사장님 지시 2026-08-16)
 *
 *   *"왜 같은 CSS를 사용하지 않는가. 같은 종류는 같은 CSS를 사용해야. 아직도
 *     개별 페이지마다 따로 만들어 적용하는가? 전 페이지를 검수해서 같은
 *     종류는 같은 CSS로 통일해라, 반드시."*
 *
 * 감사(UI_감사_2026-08-16.md)가 센 것: 전체화면 모달 껍데기가 **세 벌**이고
 * 선언이 **100% 같은데 이름만** 달랐다 —
 *   `.nxe-prev`(dox_detail) · `.nxe-prev`(dox_editor) · `.nx-work`(my_dox).
 * 상단바는 셈법이 셋이라(44px 고정 / padding 8px / padding .6rem) 높이가
 * 제각각이었고, 그래서 **X 가 바에 꽉 찰 수가 없었다** — 「닫기가 통일이 안
 * 된다」의 뿌리가 이것이다.
 *
 * 여기가 **정본 한 곳**이다. 쓰는 곳: DOX 만들기 · 미리보기 · 정밀검수 ·
 * 내용수정. 앱 셸은 이 파일을 늘 싣고, 뷰어·에디터는 Zynix 없는 독립
 * 문서지만 `chrome.head_css(parts=true)` 로 **이미 싣고 있다**
 * (못 쓰는 게 아니라 안 쓰고 있었다).
 *
 * ★ 라운드 0(F59-6) — 모서리를 만들지 않는다.
 * ★ 숫자는 **변수 하나**다. 바 높이와 X 크기가 같은 수를 봐야
 *   «높이에 꽉 차게»(F475 화살표9)가 성립한다.
 * =========================================================================== */
:root {
  --nx-sheet-h: 44px;          /* 상단바 높이 = X 한 변 */
  /* 층은 위 사다리(--nx-z-sheet)가 정한다 — 여기 숫자를 적지 않는다. */
  --nx-sheet-gap: 18px;        /* 테두리 여백 — «모달인 것»이 보이게(사장님) */
  --nx-sheet-bargap: 10px;     /* 바 안 부품 사이 — 아래 30px 셈이 이 수를 본다 */
  /* ★★★★★★ F915 — 바 한가운데 덩이(`.nx-sheet__mid`)의 **반너비**.
     탭 줄이 그 밑으로 파고들지 않게 «한가운데 − 이 값» 까지만 쓰게 한다.
     ⛔ 눈으로 고치지 마라 — `tests/harness_cmpsync_x.py` 가 폭마다·**언어마다** 실측한다.
     ★ 실측 2026-09-17 **2차**(스위치 → 아이콘+팝업으로 바뀜 뒤 다시 재다):
       zh-TW 78 · ko 88 · ja 99 · en 105 · **de 120**(가장 길다). 여유 5px 을 얹어 125.
       (1차 스위치 판은 de 253 이었다 — 글자가 짧아지면서 반으로 줄었다.) */
  --nx-sheet-midhalf: 125px;
  /* ⛔ F915 3차 — 여기 있던 `--nx-sheet-xgap: 30px`(«아이콘과 전체 닫기 ✕ 사이 30px»)을
     걷었다. 사장님이 아이콘을 **바 한가운데**로 옮기신 순간 그 간격이 있을 자리 자체가
     없어졌다(아이콘과 ✕ 사이에 바 폭의 절반이 놓인다).
     ★ 변이 C1997 이 이것을 잡았다 — 30 을 8 로 바꿔도 아무것도 안 변했다(쓰는 곳이 없었다).
       «안 잡힘» 이 가르쳐 준 것은 검사의 허점이 아니라 **죽은 값**이었다(1-1). */
  /* ★★★★ F520(사장님 2026-08-17, 화살표1·2·3): *"상단바 통일(이 종류 전부
     — DOX 만들기·정밀검수·미리보기 등). 검정 배경 없앤다. [X]와 이름표는
     검은 COLOR."*
     ★ 값은 **여기 둘**뿐이다 — 세 화면이 같은 부품(`nx-sheet`)을 쓰므로
       한 곳만 고치면 전부 따라온다(0-0). 화면마다 색을 적지 않는다. */
  /* ★★★★★★ F871(사장님 2026-09-12: *"상단바는 전부 흰색"* · *"미리보기 등 머리띠 배경
     흰색으로 해줘"*) — 시트 머리띠는 **흰색**이다.
     ⛔ 옛 `transparent` 는 판 색(`--zx-canvas` #f4f6f9)이 그대로 비쳐 **연회색**으로 보였다
       (사장님이 화살표로 가리킨 그 자리다). 판 색을 바꿔서 맞추지 않는다 — 머리띠만 흰색이다.
     ★ 값은 여기 **한 곳**이다. 쓰는 화면 넷(DOX 만들기·미리보기·정밀검수·내용수정)이 같은
       부품을 쓰므로 저절로 따라온다(0-0 · 사장님: *"같은 종류는 같은 CSS"*).
     ★ 어두운 판은 아래 `.dark` 에서 **따로** 준다 — 한 값으로 양쪽을 맞추려 하면 한쪽이
       반드시 틀린다(F872 에서 사이드바 글자로 겪은 그 일). */
  --nx-sheet-bar: #ffffff;     /* 상단바 배경 — 흰색(F871) */
  --nx-sheet-fg: #111827;      /* 이름표·[X] — 검정 */
  --nx-sheet-line: rgba(15, 23, 42, .10);   /* 바와 본문을 가르는 실선 */
}
/* 어두운 판에서는 글자가 밝아야 읽힌다 — 규칙은 같고 값만 뒤집는다. */
.dark {
  --nx-sheet-fg: #e5e7eb;
  --nx-sheet-line: rgba(255, 255, 255, .1);   /* dx134 — 템플릿 `--default-border` 흰색 .1 */
  /* ★ F871 — 어두운 판의 머리띠는 판과 같은 색이다(밝은 판의 «흰색» 자리). */
  --nx-sheet-bar: #232226;   /* dx134 — 템플릿 `--header-bg` 35 34 38 */
  /* ★★★★★ F522(2026-08-17) — **다크 소프트블루가 한 번도 안 걸리고 있었다.**
     `nx-form.css` 가 이렇게 적고 있었다:
         .dark .ti-btn.nx-act { border-color: var(--nx-softblue); }  ← 걷었다(2026-09-05)
     `#7fa9d9` 는 **변수가 없을 때만** 쓰는 값이다. 변수는 정의돼 있으므로
     그 자리는 언제나 밝은 판 색이었다 — 다크 값은 죽은 글자였다.
     ★★★★★★ 2026-09-05 — **다크 전용 값이 더는 필요 없다.** 색이
       `rgb(var(--primary) / 알파)` 라, 다크에서 배경이 어두워지면 같은
       알파가 저절로 다르게 앉는다. 값을 두 벌로 두면 한쪽만 바뀌는 날이
       온다(0-0) — 그래서 다크 판 선언을 **지웠다**(스위치가 아니라 삭제다:
       가리킬 정본이 하나뿐이라 되살릴 값이 없다). */
}

.nx-sheet {
  position: fixed;
  inset: 0;
  z-index: var(--nx-z-sheet);
  display: flex;
  padding: var(--nx-sheet-gap);
  /* ★★★★★★ F877(사장님 2026-09-12: *"마이페이지 사이드바가 보인다"*) — 막을 **불투명하게**.
     ⛔ 옛 값 `rgba(15,23,42,.55)` 는 «뒤가 비쳐야 떠 있는 것으로 읽힌다» 는 뜻이었는데,
       그 비침이 곧 **뒤 화면(앱 셸 사이드바)이 읽히는 것**이었다 — 시트 안에서 일하는 동안
       뒤의 메뉴가 같이 보이면 어느 화면에 있는지 헷갈린다.
     ★ 여백(`--nx-sheet-gap`)은 **그대로 둔다** — 사장님이 2026-08-20 에 *"미리보기는 테두리
       여백이 사라졌다. 수정해 줘"* 라 하신 자리다. 여백이 «모달인 것» 을 말하고, 막은 이제
       뒤를 **가린다**. 둘 다 지킨다.
     ★ 값은 옛 막의 **같은 색**이다(새 색을 안 짓는다 — 알파만 걷었다). */
  background: #0f172a;
}
.nx-sheet[hidden] { display: none; }
/* ★ F502·F511 — **시트 위에 서는 시트**(미리보기). 층은 사다리에서 한 칸만
   올린다 — 화면마다 숫자를 올리지 않는다(0-7 규칙 3).
   ★ 사장님 지시는 *"부모창을 **완전히** 덮어야 한다"* 이므로 테두리 여백이
     없다. 아래 시트는 여백이 있어야 «모달인 것»이 보이고, 이쪽은 덮는 것이
     일이다 — 같은 부품이지만 하는 일이 달라 이 한 줄만 갈린다. */
/* ★ F502 «완전 덮기» 중 **여백 없애기만** 사장님 지시 2026-08-20 이 되돌렸다
   (*"미리보기는 테두리 여백이 사라졌다. 수정해줘"*). 층(위에 서기)은 유지. */
.nx-sheet--over { z-index: calc(var(--nx-z-sheet) + 1); }

.nx-sheet__panel {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--zx-canvas, #f4f6f9);
  box-shadow: 0 18px 50px rgba(0, 0, 0, .38);
  /* 열림 애니메이션 — 벤더 팝업과 같은 결(살짝 커지며 페이드). */
  opacity: 0;
  transform: scale(.985);
  transition: opacity .18s ease-out, transform .18s ease-out;
}
.nx-sheet.is-open .nx-sheet__panel { opacity: 1; transform: none; }
.dark .nx-sheet__panel { background: #19181a; }   /* dx134 — 템플릿 바닥 25 24 26 */
/* 움직임을 줄여 달라는 설정은 존중한다 — 안 그러면 어지럼을 유발한다. */
@media (prefers-reduced-motion: reduce) {
  .nx-sheet__panel { transition: none; opacity: 1; transform: none; }
}

.nx-sheet__bar {
  /* ★ F687-2 — 싱크 스위치를 **바 한가운데**에 세우는 기준 상자(사장님 지시
     2026-09-01). 이 한 줄이 없으면 스위치가 화면 전체를 기준으로 선다. */
  position: relative;
  flex: 0 0 auto;
  height: var(--nx-sheet-h);
  display: flex;
  align-items: center;
  gap: var(--nx-sheet-bargap);
  padding: 0 0 0 14px;
  background: var(--nx-sheet-bar);
  color: var(--nx-sheet-fg);
  /* 배경을 걷었으니 **어디까지가 바인지** 실선 하나로 말한다(F520). */
  border-bottom: 1px solid var(--nx-sheet-line);
}
/* ★★★★ F520 화살표4 — 제목 앞 화살표. *"remix icon arrow-right-fill 을
   조금 크게 주어 강조하자."* 이름표보다 한 단계 크게 두고, 글자와 같은
   색을 쓴다(색을 새로 지어내지 않는다 — 0-0). */
.nx-sheet__ic {
  flex: 0 0 auto;
  /* ★ 사장님 지시 2026-08-20(값 그대로): 30px · 여백 0 · 검정. */
  font-size: 30px;
  line-height: 1;
  margin-inline-end: 0;
  /* ★★★★ F562(사장님 지시 2026-08-17): *"DOX 만들기·미리보기·정밀검수 등
     이 화살표가 붙은 헤드를 가진 모달은 이 화살표의 색상을 블루로 해줘."*
     ★ 색은 **Light primary 정본**(`--nx-primary-soft-bd`) 하나에서 온다 — EDIT 테두리
       (F554)·언어 고르개와 같은 값이다. 새 색을 0개 지었다(0-0).
     ★ 글자색(`--nx-sheet-fg`)으로 떨어뜨려 둔다 — 정본이 못 오는 화면에서도
       화살표가 사라지지 않는다. */
  /* ★ F562 의 소프트블루를 사장님 지시 2026-08-20 이 **검정**으로 뒤집었다. */
  color: #000;
}
/* 화면 이름표 — **좌측정렬**, 길어지면 끊는다(F477 화살표4 와 같은 규칙). */
.nx-sheet__name {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  /* ★★★★★★ F717-2(사장님 2026-09-04 · 실물 실측으로 다시 잡았다) —
     **남는 공간을 이름표 «뒤»에 몰아 [X] 를 오른쪽 끝으로 민다.**
     왜 여기인가: [X] 는 `<button>` 이라 벤더 전역 리셋
     `button,input,optgroup,select,textarea { margin: 0 !important }`
     (Zynix `styles.css`)에 걸린다. `!important` 는 특이도와 무관하게 이기므로
     **[X] 쪽에 auto margin 을 주면 죽는다**(실측: 계산값 `0px`).
     이름표는 `<span>` 이라 그 리셋에 안 걸린다 — **여기서 밀면 산다.**
     ⛔ `!important` 로 맞받지 않는다(0-7: 편법으로 이기지 않는다) — 벤더가
        건드리지 않는 자리로 옮기는 것이 제대로 된 답이다. */
  margin-inline-end: auto;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.nx-sheet__doc {
  /* ★ flex 자식의 자동 최소 크기는 min-content 다 — 이 한 줄이 없으면
     제목이 **줄어들지 않아** 바를 넘친다(F384 와 같은 종류. 하네스가 잡았다). */
  min-width: 0;
  margin-inline-start: 10px;
  font-weight: 400;
  opacity: .85;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ==========================================================================
   ★★★★★★ F915 — 시트 바의 **탭 줄**(사장님 지시 2026-09-17)

   *"[ri-pencil-ai-fill + 변환정밀검수·수정] … 이것을 탭으로 만들어서
     탭2 는 [ri-eye-line + 미리보기]"* · *"최상단 탭 옆에 제목은 필요없다"*

   ★ 이름표(`.nx-sheet__name`)를 **지우지 않았다** — 미리보기 시트는 그대로
     이름표를 쓴다(탭이 필요 없는 시트다). 탭은 **그 자리를 대신 쓰는 다른
     부품**이지 이름표의 새 판이 아니다.
   ★★★★★★ 밀어내기는 **여기가 이어받는다.** 이름표의 `margin-inline-end: auto`
     가 [X] 를 오른쪽 끝으로 밀던 자리인데(F717), 탭 시트에는 이름표가 없다.
     ⛔ [X] 쪽에 auto 를 주면 **죽는다** — 벤더 전역 리셋
       `button,input,… { margin: 0 !important }` 이 이긴다(F717 에서 한 번 뎄다).
     ⇒ `.nx-sheet__tabs` 는 `<span>` 이라 그 리셋에 안 걸린다 — 여기서 민다.
   ========================================================================== */
.nx-sheet__tabs {
  flex: 0 1 auto;
  min-width: 0;
  display: flex;
  align-items: stretch;
  align-self: stretch;
  /* ★ F717 과 **같은 이유·같은 자리**다(위 `.nx-sheet__name` 주석 참조). */
  margin-inline-end: auto;
}
.nx-sheet__tab {
  flex: 0 1 auto;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin: 0;
  padding: 0 10px;
  border: 0;
  /* 밑줄 한 줄로 «지금 여기» 를 말한다 — 바탕을 칠하지 않는다.
     투명 테두리를 미리 깔아 두면 켜질 때 글자가 1px 도 안 흔들린다. */
  border-bottom: 2px solid transparent;
  background: none;
  color: var(--nx-sheet-fg);
  opacity: .58;
  font-size: 13px;
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}
.nx-sheet__tab > i { flex: 0 0 auto; font-size: 16px; }
.nx-sheet__tab:hover { opacity: .85; }
.nx-sheet__tab.is-on {
  opacity: 1;
  font-weight: 600;
  border-bottom-color: currentColor;
  cursor: default;
}
.nx-sheet__tab:focus-visible {
  outline: 2px solid var(--nx-primary);
  outline-offset: -2px;
}

/* ==========================================================================
   ★★★★★★ F915 2차(사장님 지시 2026-09-17) — **아이콘 + 팝업 한 벌**

   사장님: *"넓은 화면에서도 스크롤과 원본보기는 스위치로 하지 말고 **모바일과
           똑같이 아이콘과 팝업**을 사용하고(위치는 중앙)"* ·
           *"원본보기 아이콘에 **원본**이란 글자 붙이고 스크롤 아이콘에도
             **스크롤** 글자 붙이고"* ·
           *"스크롤·원본 아이콘을 **바탕을 주지 말고 그냥 아이콘으로** 해라"*

   ★ 이름이 `nx-sheet__*` 가 아니다 — 이 부품은 **시트 바와 뷰어 머리띠 둘 다**에
     선다. 시트에 갇힌 이름을 붙이면 다른 화면이 못 쓴다(F483 에서 뎄던 그것).
   ★ 켜짐/꺼짐은 **색 하나**로 말한다 — 바탕을 칠하지 않는다(사장님 지시 그대로).
   ★ 폭에 따라 모양이 **안 갈린다** — 넓든 좁든 같은 아이콘·같은 팝업이다.
     그래서 옛 `is-narrow` 갈래와 `cmp-narrow` 신호를 **같이 걷었다**(1-1).
   ========================================================================== */
.nx-ipop { position: relative; }
.nx-ico {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-width: 26px;
  /* ★★★★★★ dxx311-3 화살표1(사장님 2026-09-21: *"클릭 높이 몇 번째 이야기하니"*) —
     글자 없는 아이콘 단추(원본 칸 도구줄의 ←)는 속이 **화살대 한 줄**(`--nx-rev-w` = 2px)뿐이라
     단추가 **26×2px** 이었다(재서 확인). 시트 바의 것은 dxx310-4 ① 에서 바 높이를 줬는데,
     «바 밖의 같은 아이콘은 안 건드린다» 고 적어 두고 **같은 병을 그대로 뒀다** — 두 번째다.
     ⇒ 부류로 고친다 :: 아이콘 단추는 **어디 서든** 최소한 도구줄 단추 상자(`--nx-toggle-box`)
       만큼은 높다. 뷰어 도구줄의 이웃 단추(`.dx-h-btn`)가 쓰는 그 값이다(숫자 0). */
  min-height: var(--nx-toggle-box);
  padding: 0 4px;
  border: 0;
  background: none;                /* «바탕을 주지 말고» — 사장님 지시 */
  color: inherit;
  opacity: .42;                    /* 꺼짐 = 한 톤 물러선 같은 색 */
  font-size: 15px;
  line-height: 1;
  white-space: nowrap;
  list-style: none;
  cursor: pointer;
}
.nx-ico::-webkit-details-marker { display: none; }
.nx-ico:hover { background: none; opacity: .72; }
.nx-ico.is-on { opacity: 1; }
.nx-ico.is-on:hover { background: none; }
.nx-ico:focus-visible {
  outline: 2px solid var(--nx-primary);
  outline-offset: -2px;
}
/* 글자는 아이콘보다 한 단계 작다 — 이름이지 단추가 아니다. */
.nx-ico__t { font-size: 12.5px; font-weight: 500; }
/* ★★★★★★ 2026-09-19(사장님: ***"모바일에서 스크롤·원본보기 아이콘으로 변하고
   팝업 하기로 하지 않았나? 점검해"***) — **좁은 판에서는 아이콘만 남는다.**
   ⛔ 내가 F915 2차에서 사장님 말씀(*"넓은 화면에서도 모바일과 똑같이 아이콘과
     팝업"*)을 «어느 폭에서나 아이콘+글자+팝업» 으로 읽고 폭 갈림을 **아예 지웠다.**
     그래서 390px 에서도 글자가 그대로 떴다(실측 `display:block`).
     사장님 말씀은 «넓은 판을 모바일처럼» 이었지 «모바일을 넓은 판처럼» 이 아니었다.
   ★ 누르는 것도 팝업도 그대로다 — **글자만** 감춘다. 단추는 `aria-label` 을
     이미 달고 있어(`_shell_chrome.html` 의 `summary`) 읽어 주는 이름은 안 없어진다.
   ★ 경계는 이 파일이 이미 쓰는 «좁은 판» 선(1024.98px)이다 — 새 수를 짓지 않는다. */
@media (max-width: 1024.98px) {
  .nx-ico__t { display: none; }
}
/* ★★★★★★ 화살표 둘을 **한 글리프처럼** 붙인다(⇈ / ⇅ · 사장님 지시).
   아이콘 폰트에 ⇈ 가 없어서 ↑ 둘을 붙여 만든다 — 그러니 **붙이는 규칙이
   곧 그 글리프**다. 머리띠의 아이콘과 팝업 줄이 **같은 부품**을 쓴다(0-0):
   한쪽만 벌어지면 «이 줄이 그 아이콘» 이라는 것이 안 읽힌다. */
.nx-sync2 {
  display: inline-flex;
  align-items: center;
  font-size: 16px;
  line-height: 1;
}
.nx-sync2 > i { margin-inline: -4px; }
/* 팝업은 아이콘 **아래**에 선다 — 원본이 밀려 나와도 가려지지 않는다.
   ★ 시트 바 안에서는 바 높이만큼 내려야 바 아래에 붙는다. */
.nx-ipop > .nx-drop__menu { top: calc(100% + 2px); z-index: var(--nx-z-pop); }
.nx-sheet__bar .nx-ipop > .nx-drop__menu {
  top: var(--nx-sheet-h);
  z-index: calc(var(--nx-z-sheet) + 2);
}
/* 팝업 머리 — 이름과 안내 아이콘이 한 줄에 선다(F687-3 의 ⓘ 가 여기로 옮겨 왔다). */
.nx-drop__head {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
/* ★ dx310-8 화살표2 — 안내 점이 **안 쭈그러든다.** 머리 글이 길어지면 flex 가 이 단추를
   눌러 누르는 자리가 없어진다(사장님: «클릭 높이 범위가 없다»). 크기는 공용
   `.nx-iconbtn`(2.25rem) 그대로 두고 «줄지 않는다» 만 못 박는다 — 숫자 0. */
.nx-drop__head .nx-helpdot { flex: 0 0 auto; }
/* ★★★★★★ dx132-3(사장님 2026-09-22: *"원본보기 팝업 상단 우측에 x 로 닫게 해"*) — ✕ 는 머리의
   **오른쪽 끝**에 붙박이다. 이름·ⓘ 는 그대로 한가운데(흐름에서 빼서 자리를 안 민다). */
.nx-drop__head--x { position: relative; }
.nx-drop__head--x .nx-rev__x {
  position: absolute;
  inset-inline-end: 4px;
  top: 50%;
  transform: translateY(-50%);
}

/* ==========================================================================
   ★★★★★★ F915 3차(사장님 지시 2026-09-17) — **보이는 정도는 «화살표 길이»가 말한다**

   *"팝업에서는 **좌측 화살표만** 그 끝에 20%, 원본 상단에서는 **좌측 화살표만 % 없이**"* ·
   *"화살표가 길어지더라도 **굵기까지 길어지면 안 된다**"*

   ⛔⛔ 그래서 이 화살표는 **아이콘 폰트 글리프가 아니다.**
     · 1차: 화살표 셋을 도구줄에 늘어놓았다 → 작은 `⟷` 가 스크롤 아이콘으로 읽혔다(사장님 지적).
     · 2차: 한 줄 양방향 글리프에 `scaleX` 를 걸었다 → **획까지 같이 굵어진다**(사장님 지적).
       `transform: scaleX()` 는 그린 것을 통째로 늘이므로 **굵기만 빼고 늘일 길이 없다.**
     ⇒ 3차: **CSS 로 그린다.** 길이는 `width`, 굵기는 `height` 로 **따로** 잡는다.
       굵기는 `--nx-rev-w` **한 값**이고 길이 셋은 아래 세 줄이다 — 늘여도 굵기가 그대로다.
   ★ 이것은 F485 «심볼을 지어내지 않는다» 에 걸리는 **아이콘이 아니다** — 값을 길이로 말하는
     **자(尺)** 다. 아이콘이었다면 폰트에서 가져왔을 것이다(그리고 실제로 가져와 봤다가
     굵기 때문에 못 썼다 — 그 기록이 위 1·2차다).
   ★ 방향이 **왼쪽**인 까닭: 원본은 오른쪽에서 **왼쪽으로 밀려 나온다.** 화살표가 그 움직임이다.
   ========================================================================== */
:root {
  --nx-rev-xgap: 20px;      /* 원본 칸: 아이콘 ↔ ✕ — 사장님 지시 그대로 */
  /* ★ dxx310-6 ③ — 바에서 열리는 팝업의 기본 폭. 원본보기·스크롤이 **같은 값**을 쓴다. */
  --nx-drop-pop-w: 14rem;
  --nx-rev-w: 1.5px;        /* 화살표 **굵기** — 길이와 무관하게 이 한 값 */
  --nx-rev-head: 6px;       /* 화살촉 날개 길이 */
}
/* ★ dx310-8 화살표1 — 겹치지 않는 구간에서는 이 고르개를 **감춘다**(JS 가 `hidden` 을
   단다). `display:inline-flex` 가 `hidden` 을 이기므로 여기서 눌러 준다. */
.nx-rev[hidden] { display: none; }
/* ★★★★★★ dx132-2(사장님 2026-09-22: *"원본보기에서 화살표 왼쪽으로 옮겨라"*) — 원본 칸의
   고르개는 이제 **칸 이름(「원본」) 바로 옆**이다. 옛 `margin-inline-start:auto`(오른쪽 끝)를
   걷었다 — 마크업 자리도 이름 뒤로 갔다(`dox_viewer.html`). 간격은 옆 단추들과 같은 흐름. */
.nx-rev {
  display: inline-flex;
  align-items: center;
  gap: var(--nx-rev-xgap);        /* 「x 와 20px 여유를 주고」 — 사장님 지시 */
}
/* 고르개가 왼쪽에 서므로 팝업은 **오른쪽으로** 편다(공용 `.nx-drop__menu` 는 오른쪽 끝 기준이라
   왼쪽 벽 밖으로 나갔다 — 시안에서 잡았다). */
.nx-rev .nx-drop__menu { inset-inline-start: 0; inset-inline-end: auto; }
/* 화살대 — 길이만 다르다. */
.nx-rev__arw {
  position: relative;
  display: inline-block;
  flex: 0 0 auto;
  height: var(--nx-rev-w);
  background: currentColor;
}
/* 화살촉 — **왼쪽 끝**에 날개 둘. 길이가 변해도 이 둘은 그대로다. */
.nx-rev__arw::before,
.nx-rev__arw::after {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: 0;
  width: var(--nx-rev-head);
  height: var(--nx-rev-w);
  background: currentColor;
  transform-origin: left center;
}
.nx-rev__arw::before { transform: rotate(-42deg); }
.nx-rev__arw::after { transform: rotate(42deg); }
/* 길이 셋 — 이것이 **보이는 정도**다(20 / 50 / 100). 값은 여기 세 줄뿐이다. */
.nx-rev__arw--100 { width: 52px; }
.nx-rev__arw--50 { width: 32px; }
.nx-rev__arw--20 { width: 17px; }
/* 아이콘 자리(시트 바 · 원본 칸) — % 없이 화살표 하나(사장님 지시 그대로). */
.nx-rev__arw--ico { width: 18px; }
[dir="rtl"] .nx-rev__arw { transform: scaleX(-1); }
/* 팝업 줄 — 화살표 **끝에** 백분율. 줄 너비는 가장 긴 화살표가 정한다. */
.nx-rev__row { justify-content: flex-start; gap: 10px; }
.nx-rev__pct { margin-inline-start: auto; font-variant-numeric: tabular-nums; }
.nx-rev__x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  /* ⛔ `margin-inline-start: auto` 를 여기 주면 **죽는다**(벤더 리셋 F717).
     간격은 위 `.nx-rev` 의 `gap` 이 만든다 — 숫자는 한 곳이다. */
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
}
/* 팝업 줄에서도 화살표는 **같은 한 벌**이다 — 줄 모양만 팝업 것을 쓴다. */
.nx-drop__menu--rev .nx-drop__item { justify-content: center; }
/* ★★★★★★ dxx310-6 ③(사장님 2026-09-21: *"창이 작아 글이 접힌다 기본 팝업창 넓혀라"*) —
   머리의 「원본보기」가 **두 줄로 꺾였다**(사장님 캡처).
   ⛔ 새 숫자를 짓지 않는다 :: 같은 바에서 열리는 **스크롤 팝업**이 이미 `14rem` 이다.
     두 팝업은 한 벌이므로 **그 값 하나**를 둘이 같이 쓴다 — 한쪽만 넓히면 또 갈린다(0-0).
     근거 = 「같은 바의 두 팝업은 같은 폭」. */
/* 스크롤 팝업 — 아이콘이 글자 **바로 앞**에 선다(양끝으로 벌리지 않는다). */
.nx-drop__menu--rev,
.nx-drop__menu--sync { min-width: var(--nx-drop-pop-w); }
/* ★★★★★★ dxx310-4 ①(사장님 2026-09-21: *"버튼 높이 맞춰라"*) — 시트 바의 아이콘 단추가
   **탭보다 낮았다.** 탭은 `align-self:stretch` 로 바 높이를 꽉 채우는데 아이콘 단추는
   제 글자 높이만큼만이라 누르는 자리도 그만큼 작았다.
   ⇒ **바 안에서만** 바 높이를 준다 — 값은 `--nx-sheet-h` 한 곳에서 온다(숫자 0).
   ★ 바 밖(원본 칸 도구줄)의 같은 아이콘은 안 건드린다 — 거기 바는 다른 높이다. */
.nx-sheet__bar .nx-ico { height: var(--nx-sheet-h); }
.nx-drop__menu--sync .nx-drop__item { justify-content: flex-start; gap: 10px; }
/* 지금 상태인 줄은 팝업에서도 **비활성**이다(사장님 지시 — 두 곳 같은 규칙). */
.nx-drop__item:disabled, .nx-drop__item[aria-disabled="true"] {
  opacity: .32;
  cursor: default;
}
.nx-drop__item:disabled:hover { background: none; }

/* ==========================================================================
   ★★★★★★ 부류 정본 — «스스로 구르지 않는 틀 문서» 는 쪽 스크롤바 자리를 돌려준다

   `nx-form.css:48` 이 **모든 문서**에 `html{scrollbar-gutter:stable}` 를 깐다.
   그것은 옳다 — 보통 페이지는 스크롤바가 생겼다 사라질 때 본문이 15~17px 씩
   흔들리고, 그 자리를 미리 잡아 두면 안 흔들린다(F84 의 판단).

   그런데 **틀(iframe) 안에 들어가는 문서**는 사정이 다르다. 높이가 못 박혀 있고
   (`.nx-cmp-wrap{height:100vh;overflow:hidden}` · `body.nxe-embed{overflow:hidden}`)
   안쪽 상자 하나만 구른다 — 쪽 스크롤바는 **영영 안 생긴다.** 그래서 잡아 둔
   자리가 쓰이지 않고, 윈도우처럼 막대가 자리를 먹는 시스템에서는 오른쪽 끝에
   **영구히 흰 띠**로 남는다.

   ⛔ 2026-09-20 사장님 화살표(***"우측에 스크롤바 우측에 또 여백이 있잖아"***) ::
     캡처 픽셀 :: 회색 바탕 → 뷰어 스크롤바 16px → **순백 17px** → 시트 테두리.
     실측 :: 껍데기 `창폭=1076 문서폭=1076 몸폭=1061 바=0` — 몸이 15px 좁은데
     막대는 0. 그 15(사장님 화면 17)가 바로 이 띠였다.
     ⛔ 내가 이것을 못 보고 **뷰어의 `--dx-topgap`·`scrollbar-gutter` 만** 여러 판
       고쳤다. 회색 액자 때와 **똑같이 가운데 층**(껍데기)의 것이었다.

   ★ F84(dox_editor)가 이미 같은 병을 겪고 `html{scrollbar-gutter:auto}` 를 제
     파일에 적어 두었다. 껍데기만 빠져 있었다 — 그래서 **선언을 여기 한 벌로**
     모으고, 부류에 드는 문서가 `nx-fixframe` 를 달게 한다(0-0 · 숫자·선언을
     두 곳에 적지 않는다).
   ★ 부류에 드는가의 잣대는 «높이가 못 박혀 쪽 스크롤바가 생길 수 없는 문서» 다.
     재서 넣는다 — `tests/harness_f921_fixframe.py` 가 자리를 뺀 뒤에도 안 구르는지
     폭·상태를 훑어 확인한다. 구르는 문서에 달면 **그 문서가 흔들린다.**
   ========================================================================== */
html.nx-fixframe {
  scrollbar-gutter: auto;
}

/* ==========================================================================
   ★★★★★★ F915 4차(2026-09-17) — **겸용 화면의 두 칸**(`nx-cmp-*`).

   *"css 는 css 규정을 잘 지키고 했는가?"* — 안 지킨 데가 있었다. 이 여덟 규칙은
   `dox_work.html` 의 `<style>` 안에 **박혀 있었다.** 0-0 이 금지하는 바로 그것이다
   (*"만들면 공용 CSS 에 둔다. 템플릿 안에 박지 않는다"* · 2026-08-16 감사가 센
   템플릿 인라인 CSS 523 규칙이 이렇게 불었다). 그래서 통째로 여기로 옮겼다.

   ★★ 옮겨야 했던 진짜 이유는 **숫자가 두 곳에 나뉘어 있었다는 것**이다:
     화살표의 굵기·길이(`--nx-rev-w`·`--nx-rev-head`)는 위 블록에 있는데,
     그 화살표가 **가리키는 거리**(`--nx-rev-off` — 원본 칸이 밀려 나온 정도)만
     템플릿에 있었다. 같은 `--nx-rev-*` 한 벌이 두 파일에 갈라져 있으면
     한쪽만 고치는 사고가 난다(F915 3차에서 `--nx-sheet-xgap` 이 죽은 값으로
     남아 있던 것과 같은 종류다).

   ⛔ 옮기면서 **지운 것 하나** — `border-radius: 6px`.
     라운드 0(F210)의 전면 규칙이 이 화면에도 **실제로 닿는다**(브라우저로 쟀다:
     `_locale_head.html` 이 `head_css(parts=true)` 를 불러 이 파일이 마지막에
     실린다 · 계산값 0px). 즉 그 6px 은 **한 번도 그려진 적이 없는 죽은 값**이면서
     «여기는 라운드가 있다» 고 읽히는 거짓말이었다.
   ⛔ 색 리터럴 셋도 **정본 토큰으로 바꿨다**(0-0 「새 색을 만들지 않는다」):
     `#dde0e6`→`var(--nx-line)` · `#fff`→`var(--nx-surface)` ·
     `rgba(0,0,0,.30)`→ 서랍 정본과 **같은 그림자**(`rgb(0 0 0 / 0.12)`, 부호만 반대).
     덤으로 **다크에서도 맞는다** — 리터럴이었을 때는 어두운 화면에도 흰 칸이 떴다.
   ========================================================================== */
:root {
  /* 원본 칸이 밀려 나온 정도 — 화살표 길이(위 `--nx-rev-*`)와 **같은 한 벌**이다.
     값은 JS 가 %로 얹는다(0 = 다 나옴 · 100% = 다 들어감). 기본 50%. */
  --nx-rev-off: 50%;
}

/* ★ F915 — 좌우로 미는 칸이 생겼으므로 넘친 것이 **가로 줄을 만들면 안 된다.** */
.nx-cmp-wrap {
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow: hidden;
}
/* ★ 이름표를 **자기 칸에 붙여** 한 덩이로 만든다(F525) — 어느 폭에서도
   이름이 자기 칸 위에 있다. 폭마다 규칙을 따로 쓰지 않는다. */
.nx-cmp-side {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  position: relative;
}
.nx-cmp-panes {
  flex: 1 1 auto;
  display: grid;
  /* ★ 열 폭은 아래 dxx310-9 블록이 정한다(값 하나 · `--nx-rev-off`). */
  gap: .5rem;
  /* ★★★★★★ 2026-09-20(사장님 실기동 · 번호 1,4,5,6) — **액자를 걷는다.**
     ⛔ 여기가 사장님이 가리키신 그 «회색 공간» 이다 — 칸(iframe)을 통째로 두르는
       여백이라 **안쪽 헤드와 스크롤 전부를 안고 있다.** 옛 값은 위 7.2 · 좌우 14.4 ·
       아래 14.4px 이었고, 거기에 칸 테두리 1px 이 더 있었다.
     ⛔ 내가 이것을 못 보고 **안쪽 뷰어의 24px 만** 세 판을 고쳤다. 사장님 화면의
       `doxSheetFrame` 이 `클래스=light`(= 껍데기)로 찍혀서야 층을 잘못 짚은 것이
       드러났다 — 시트 → **껍데기** → 뷰어, 세 겹인데 가운데를 건너뛰었다.
     ★ 칸 사이 `gap` 은 남긴다 — 그것은 액자가 아니라 **두 칸을 가르는 선**이다. */
  padding: 0;
  min-height: 0;
}
.nx-cmp-panes iframe {
  width: 100%;
  flex: 1 1 auto;
  min-height: 0;
  /* ★ 테두리도 액자의 한 겹이다 — 칸을 가르는 일은 위 `gap` 이 한다. */
  border: 0;
  background: var(--nx-surface);
}
/* ★★★★★★ F915 — 원본보기를 끄면 왼쪽이 **통째로 쓴다.**
   빈 칸을 남기면 «고칠 수 있는 쪽» 이 반만 한 채로 남는다. */
html:not(.nx-src-on) .nx-cmp-panes { grid-template-columns: 1fr; }
html:not(.nx-src-on) .nx-cmp-panes > .nx-cmp-side + .nx-cmp-side { display: none; }

/* ══════════════════════════════════════════════════════════════════════════
   ★★★★★★ dxx310-9 / dx310-11 (사장님 실기동 2026-09-21 · 시안 승인) —
   **1291 이상은 겹치지 않고 「진짜로 나눈다」.**

   ⛔ 병인(재서 확인 · 창 1340px · 원본 50%) ::
     겹침은 `grid-area:1/1` + `transform` 이라 **자리를 안 바꾼다.** 그래서 칸 둘이
     **둘 다 창 폭 전체**(1340)를 쓰고 오른쪽이 왼쪽을 덮었다. 왼쪽 칸은 자기를
     1340 으로 **알고** ①사이드바를 펴 놓고 ②쪽을 그 한가운데(x=473) 세웠다.
     정작 보이는 곳은 0..670 뿐 ⇒ **473px 이 버려지고 본문 794 중 197 만** 보였다.
   ⇒ 칸에 **보이는 폭 그대로**를 준다. 그러면 칸이 스스로 «나는 670 이다» 를 알아
     사이드바가 서랍으로 들어가고 본문이 한가운데 선다.

   ★ dxx311-1(사장님: *"버튼 팝업 내부 화살표들 안 듣는다"*) — 옛 판은 1448 이상을
     `1fr 1fr` 로 **못 박아** 50/20/100 이 값만 바뀌고 화면은 그대로였다(ⓑ 로 칸 팝업만
     비활성으로 보였고 **바의 팝업은 살아 보이는데 아무 일도 안 했다**). 이제 열 폭은
     어느 폭에서나 `--nx-rev-off` **하나**를 탄다 — 1291 이상은 구간이 하나다.
   ★ 두 번째 칸이 `1fr` 이라 **칸 사이 `gap` 까지 저절로** 빠진다(calc 로 8px 을 또 적지 않는다).
   ⛔ 움직임을 안 준다 — 폭이 변하면 안쪽 문서가 통째로 다시 흐른다. 198쪽을 .18초 동안
     매 프레임 흘리면 끊긴다(겹침 구간의 `transform` 은 리플로우 0 이라 움직여도 됐다).
   ★ 덮는 구간(1290 이하)은 **아래** 블록이 `grid-template-columns:1fr` 로 이 값을 덮는다 —
     그래서 이 블록은 그 블록보다 **앞**에 있어야 한다(같은 특이도 · 뒤가 이긴다).
   ══════════════════════════════════════════════════════════════════════════ */
/* ★★★★★★ dx132-1(사장님 2026-09-22: *"넓은 화면에서는 원본보기 가장 넓게가 50% 여야지 —
   비교창을 다 밀어버리면 어떡하나"* · *"화면이 줄어들면 CSS 계산으로 나타내야지"*) —
   나누는 구간에서 원본은 **반을 넘지 않는다.** 값(`--nx-rev-off` = 100 − 보이는 정도)은 그대로
   두고 **여기서 `max()` 로 잡는다**: 100(=0%) 가 와도 열은 50 이다. 좁은 창에서 100 으로 두고
   창을 넓혀도 왼쪽이 사라지지 않는다 — 값이 아니라 **폭이 정한다.** 팝업의 100 줄은 이 구간에서
   CSS 로 비활성(아래 dx132-1 블록). */
.nx-cmp-panes { grid-template-columns: max(var(--nx-rev-off, 50%), 50%) 1fr; }
/* 격자 칸은 기본이 `min-width:auto` 라 **속 넓이 아래로 안 줄어든다** —
   0 으로 못 가면 「원본 100%」 가 왼쪽을 못 없앤다.
   ⛔ `overflow:hidden` 은 **안 준다** — 이 규칙은 덮는 구간(1290 이하)에도 닿는데, 거기서
     칸이 자기 안 iframe 의 그림자(-8px … 24px)를 잘라 버린다(자기 점검에서 잡았다). 폭 0 인 칸은
     속 iframe 도 폭 0 이라 잘라 낼 것이 없다. */
.nx-cmp-panes > .nx-cmp-side { min-width: 0; }

/* 좁은 화면 — 두 칸을 **겹쳐** 놓고 원본을 오른쪽에서 밀어 넣는다.
   ★★★★★★ dxx311-3(사장님 2026-09-21: *"오버레이로 원본보기가 가야지 이 좁은 너비에 반으로
     나눈다고"*) — 판정선은 **1290**(왼쪽 사이드바가 서랍이 되는 그 수 · 뷰어 `@media(max-width:1290px)`).
     그 아래는 반으로 나눠 봐야 양쪽 다 못 읽는다(사장님 캡처 · 칸 441 에서 제목이 두 줄로 꺾였다).
   ⛔ 이 경계를 내가 **두 번 옮겼다** :: 처음 1291(사장님 «더 작아지면 덮어야지» 직후) → 630 으로
     내 멋대로 내렸다(«좌우 여백이 왜 이렇게 넓냐» 를 «881 에서도 나눠라» 로 잘못 읽었다 —
     그 지적은 스크롤바 자리 15 를 말한 것이었다) → 사장님이 다시 잡아 주셨다. **1290 이 정본.**
   ★ 옛 판정선 1447(메모 서랍)은 칸 배치와 무관해졌다 — 1291 이상은 어느 폭이든 «나눔» 한 벌이다. */
@media (max-width: 1290px) {
  /* ★★★★★★ dx310-8 화살표1(사장님: *"이 원본의 팝업은 좁은 화면에서만 뜨자"*) — **원본 칸
     자신의 고르개가 서는 구간**은 여기(덮는 구간)뿐이다. 나누는 구간에서는 바의 원본보기가 그 일을
     한다. ⛔ JS 에 경계 수를 다시 적지 않는다(F874) — **CSS 가 표식으로 말하고 JS 는 읽는다.**
     ★ 옛 이름 `rev-eff`(«50/20 이 듣는 구간» · 사장님 ⓑ)는 **뜻이 없어졌다** — dxx311-1
       («팝업 화살표 안 듣는다»)로 1291 이상도 열 폭이 `--nx-rev-off` 를 타게 되어 50/20 이 어느
       폭에서나 듣는다. 남은 뜻(«칸 고르개가 서는 구간»)만 이름을 바꿔 잇는다(0-7). */
  .nx-cmp-panes { --nx-rev-pane: 1; }
  .nx-cmp-panes,
  html:not(.nx-src-on) .nx-cmp-panes { grid-template-columns: 1fr; }
  .nx-cmp-panes > .nx-cmp-side { grid-area: 1 / 1; }
  /* ★★★★★★ dx132c(사장님 2026-09-22: *"로드 이후 창이 줄어들면 50%·20% 도 활성화돼야지"*) — 덮는
     구간에서도 원본은 **보이는 정도만큼** 밀려 나온다(100 = 전부 · 50 = 반 · 20 = 20%).
     ⛔ 병인(내가 만들었다) :: dx132-1 의 «낣은 화면에서는 50%·20% 는 비활성» 을 **좁은 화면**으로 거꾸로
       읽어 여기를 `translateX(0)` 으로 못 박고 50·20 줄을 CSS 로 죽였다. 되돌린다(F915 §5 의 원래 뜻). */
  .nx-cmp-panes > .nx-cmp-side + .nx-cmp-side {
    z-index: 8;
    transform: translateX(var(--nx-rev-off, 50%));
    transition: transform .18s ease-out;
  }
  html:not(.nx-src-on) .nx-cmp-panes > .nx-cmp-side + .nx-cmp-side {
    display: flex;
    transform: translateX(100%);
  }
  /* 밀려 들어온 판의 그림자 — **서랍 정본과 같은 값**(`.nx-drawer__panel`),
     방향만 반대다(오른쪽에서 들어오므로 부호가 −). 새 값을 짓지 않는다. */
  .nx-cmp-panes > .nx-cmp-side + .nx-cmp-side > iframe {
    box-shadow: -8px 0 24px rgb(0 0 0 / 0.12);
  }
}
@media (prefers-reduced-motion: reduce) {
  .nx-cmp-panes > .nx-cmp-side + .nx-cmp-side { transition: none; }
}
/* ★★★★★★ dx132-1(사장님 2026-09-22) — 팝업 줄의 활성/비활성은 **폭이 정한다**(JS 가 폭을 안 잰다 ·
   F874). 나누는 구간(1291 이상)은 100 이 없다(가장 넓게가 50%). 덮는 구간은 셋 다 산다(dx132c).
   ★ 시트 바 팝업은 맨 위 문서(창 폭 = 브라우저 폭)에, 원본 칸 팝업은 덮는 구간에만 서는 칸(그때
     칸 폭 = 창 폭)에 있으므로 같은 경계가 둘 다에 맞는다.
   ★ 모양은 `:disabled` 와 **같은 한 벌**(아래 `.nx-drop__item:disabled` 의 값) — 새 값 없음. */
@media (min-width: 1291px) {
  .nx-drop__menu--rev .nx-rev__row[data-amt="100"] { opacity: .32; cursor: default; pointer-events: none; }
}
/* ★ dx132c — 덮는 구간(1290 이하)에서는 **셋 다 산다**(죽이는 규칙을 걷었다 · 위 덮개 주석). */

/* 닫기 — **글자 없는 정사각**, 바 높이에 꽉 참, 오른쪽 끝에 딱(F475 화살표9). */
/* ==========================================================================
   ★★★★★★ F687 → F915 2차(사장님 지시 2026-09-17) — **스위치를 걷었다.**

   F687 의 `nx-cmpsync` 토글 스위치 한 벌(몸통·상자·손잡이·ⓘ·가운데 세우기)을
   전부 지웠다. 사장님: *"넓은 화면에서도 스크롤과 원본보기는 **스위치로 하지
   말고 모바일과 똑같이 아이콘과 팝업**을 사용하고(위치는 중앙)"*.

   ⛔ **그 부품이 지키던 뜻은 하나도 안 버렸다** — 새 몸은 위 `.nx-ico` + `nx-drop` 이다:
     · 「기본은 켬」      → 서버가 `<html class="nx-src-on">` 을 그린다(JS 가 죽어도 켜져 있다)
     · 「끌 수 있다」      → 팝업의 ✕ 줄 · 「양쪽스크롤 맞추기 해제」 줄
     · 「가운데」          → 아래 `.nx-sheet__mid` 가 그대로 한다(절대 위치 · 바 폭 50%)
     · 「ⓘ 안내」(F687-3) → 팝업 **머리 옆**으로 옮겼다(부품도 글도 같은 한 벌)
     · 「어긋나지 않는다」 → `cmp-*-state` 가 위(바)와 아래(원본 칸)로 같이 간다
   ★ 색도 하나 안 남겼다 — 아이콘은 `currentColor` 에 투명도만 다르다(새 색 0개).
   ========================================================================== */
/* ★★★★★★ F687-2 → F915 — **바의 한가운데**(사장님 지시 2026-09-01 «가운데 위치» ·
   2026-09-17 «위치는 중앙»). 왼쪽 탭 길이와 오른쪽 [X] 너비에 안 흔들리게
   **바 폭 기준 50%** 에 세운다. 세우는 것이 스위치에서 아이콘 둘로 바뀌었을
   뿐, 자리를 정하는 규칙은 **그대로**다(0-7 — 자리를 옮기지, 지우지 않는다). */
.nx-sheet__mid {
  position: absolute;
  inset-inline-start: 50%;
  transform: translateX(-50%);
  margin: 0;
  display: flex;
  align-items: center;
  gap: 18px;                     /* 아이콘 둘 사이 */
  white-space: nowrap;
}
[dir="rtl"] .nx-sheet__mid { transform: translateX(50%); }
.nx-sheet__mid[hidden] { display: none; }
/* 탭 줄이 가운데 덩이 밑으로 파고들지 않게 **한가운데 전까지만** 쓴다.
   ★ `--nx-sheet-midhalf` 는 **재서 넣은 값**이다(가운데 덩이의 반너비) —
     하네스 `harness_cmpsync_x` 가 폭마다·언어마다 «겹침 0» 을 재서 지킨다(0-1b).
     눈으로 고치지 말고 그 하네스를 돌려서 고쳐라. */
.nx-sheet__bar:has(> .nx-sheet__mid:not([hidden])) .nx-sheet__tabs {
  max-width: calc(50% - var(--nx-sheet-midhalf));
}

.nx-sheet__x {
  flex: 0 0 auto;
  width: var(--nx-sheet-h);
  height: var(--nx-sheet-h);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  /* ★★★★★★ F717(사장님 2026-09-04: *"닫기 버튼이 스크롤싱크 중지/재개 버튼
     뒤에 있는 것 같다"*) — **[X] 를 바의 오른쪽 끝으로 민다.**
     왜 필요한가: F687-2 가 이름표에 `max-width: calc(50% - 130px)` 를 씌우면서
     이름표가 더 이상 바를 다 채우지 못하게 됐다. [X] 는 `flex: 0 0 auto` 라
     **이름표 바로 뒤**, 즉 바의 한가운데쯤에 서게 됐고, 그 자리는 절대 위치로
     가운데에 선 싱크 스위치(`.nx-cmpsync-wrap`)가 **덮는 자리**다.
     ⛔⛔ **미는 자리는 여기가 아니다.** 여기에 `margin-inline-start: auto` 를
        주면 **죽는다** — 벤더 전역 리셋
        `button,input,optgroup,select,textarea { margin: 0 !important }`
        (Zynix `styles.css`)이 `!important` 로 이긴다(실측: 계산값 `0px`).
        ⇒ 미는 것은 **이름표의 `margin-inline-end: auto`** 다(위 `.nx-sheet__name`).
        이름표는 `<span>` 이라 그 리셋에 안 걸린다. 6절 함정 7(벤더는 특이도로
        이긴다)의 `!important` 판이다 — **실물을 재기 전에는 «됐다» 고 적지 마라.** */
  margin: 0;
  background: transparent;
  border: 0;
  color: inherit;
  cursor: pointer;
}
/* ★★ F477 «바탕 없음» 을 **사장님 지시 2026-08-20 이 뒤집었다**:
   *"닫는 버튼 X 의 배경을 정사각형 검정색으로, 테두리 라인 없는 검정 박스에
     흰색 X 로 — 이 라인의 높이에 꽉 차는 정사각형."*
   · 한 변 = 바 높이 변수(--nx-sheet-h) 그대로 — 줄에 꽉 찬다(종전 유지).
   · 테두리 없음(border:0 위에서 유지) · 배경 #000 · X 는 #fff. */
.nx-sheet__x { background: #000; color: #fff; opacity: 1; }
/* ★★★★ F505(사장님 2026-08-17) — *"닫기 X 에 하얀 테두리가 생긴다(열린 직후)."*
   시트를 열 때 [X] 에 포커스를 준다(`closeBtn.focus()` — 키보드 사용자가 Esc
   말고도 닫을 수 있어야 하므로 **그 포커스는 지우지 않는다**). 벤더 버튼
   기본값이 그 순간 **흰 링**을 그린다.
   ★ 접근성을 깎지 않는다 — 마우스로 연 경우에만 링을 감춘다(`:focus-visible`
     이 아닐 때). 키보드로 옮겨 오면 링이 그대로 보인다. */
.nx-sheet__x:focus { outline: none; box-shadow: none; }
.nx-sheet__x:focus-visible {
  outline: 2px solid #fff;
  outline-offset: -2px;
}
.nx-sheet__x:hover { background: #000; opacity: .88; }
.nx-sheet__x svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.6;
  stroke-linecap: round;
}

.nx-sheet__body {
  flex: 1;
  min-height: 0;
  width: 100%;
  border: 0;
  display: block;
  background: #fff;
}
.dark .nx-sheet__body { background: #19181a; }   /* dx134 — 템플릿 바닥 */

/* 시트가 떠 있는 동안 뒤 페이지는 안 구른다 — 뒤가 구르면 닫았을 때
   «그 자리»가 이미 달라져 있다(스크롤 유지가 이 구조의 값인데 스스로 깬다). */
body.nx-sheet-on { overflow: hidden; }

/* ★★★★ F491-4(사장님 2026-08-16): *"헤드가 다 없어졌다."*
   여기 있던 `.nx-sheet--pure { … display:none }` 를 **없앴다.**
   시트 안에서 연 창이 이 바를 못 덮는다고 **바를 지우는 것은 편법**이고,
   한 번 지워지면 돌아오지 않았다. 덮어야 할 것은 덮는다 — 그 길은
   `nx-modal.css` 의 `body.nx-sheet-on …`(시트 층 위로 올리기) 하나다.
   ★ 이 이름을 다시 만들지 말 것. `test_dox_offline` 이 막는다. */

/* 좁은 화면에서는 여백을 없앤다 — 420px 에서 18px 씩 빼면 알맹이가 눌린다. */
@media (max-width: 575.98px) {
  .nx-sheet { padding: 0; }
  .nx-sheet__panel { box-shadow: none; }
}

/* ==========================================================================
   도는 규칙 — **여기 한 곳**(F578 · F651 로 이사)

   이 파일은 `head_css(parts=true)` 를 통해 **모든 문서**가 싣는다(앱·뷰어·
   나란히 보기·랜딩). 옛 자리(`nx-form.css`)는 앱 화면만 실어서, 뷰어의
   핀은 **선언을 참조만 하고 돌지 않았다** — F578-1 이 «화면마다 keyframes
   를 따로 둬서 안 돌던» 그 사고의 다른 얼굴이다.
   ★ 이름을 바꾸지 않는다 — `.nx-pgrow__pin`(nx-form.css)이 그대로 쓴다.
   ========================================================================== */
@keyframes nx-pgrow-spin { to { transform: rotate(360deg); } }

/* ==========================================================================
   ★★★★★★ F778 — **스핀은 앱에 한 벌이다** (사장님 2026-09-07)

   사장님: *"팝업이 일어날 때나 대기 중, 이 사이즈의 스핀은 전부 통일한다.
   기존 기록이 있다."* — 기록은 있었다. 지키지 않은 자리가 넷 있었을 뿐이다.

   무엇이 어긋나 있었나(실측 2026-09-07):
     · 진행바 핀(`progress_pin`)  → 원호 `ri-loader-4-line` + `nx-pgrow-spin`
     · **팝업이 열릴 때**(`_modal_root`) → 바퀴살 `ti ti-loader` + tailwind
     · 소스변경 팝업(`dox_source`) → 바퀴살 · **크기는 같은 14px**
     · 에디터 부팅 · 충전 팝업     → 바퀴살
   ⇒ 같은 뜻(«기다리는 중»)에 **두 가지 모양**이 돌고 있었다.

   모양은 사장님이 2026-08-21 에 **원호**로 확정하셨다(`UI.md` 「로딩핀」).
   여기 `.nx-spin` 이 그 확정을 **한 벌로** 만든다 — 도는 규칙은 위
   `nx-pgrow-spin` 하나를 그대로 쓴다(새 keyframes 를 만들지 않는다. 그것이
   F578-1 의 잘못이었다).

   ★ tailwind `animate-spin` 에 기대지 않는다 — 뷰어·나란히 보기는 그
     유틸리티를 안 싣는 독립 문서라 거기서는 **돌지 않는다**(F651 에서 겪었다).
   ★ 크기는 자리마다 다르다(진행바 14 · 팝업 24 · 에디터 부팅 26). 크기는
     그 자리 클래스가 정하고, 여기서는 **모양과 도는 것**만 정한다.
   ========================================================================== */
.nx-spin {
  animation: nx-pgrow-spin 1s linear infinite;
  display: inline-block;
  flex: none;
}
.nx-spin--md { font-size: 18px; line-height: 18px; width: 18px; height: 18px; }
.nx-spin--lg { font-size: 24px; line-height: 24px; width: 24px; height: 24px; }

/* ==========================================================================
   nx-dlg — **브라우저 시스템 창을 대신하는 공용 대화상자** (D001, 2026-08-20)

   사장님 지시: *"alert 안 나타나게 해줘. 전체공통"*

   왜 여기인가 — 시스템 창은 «팝업 마크업이 없는 문서»의 **마지막 수단**으로
   남아 있었다(modal.ts·doxtree.ts·viewer_annot.ts 세 곳). 그 문서들(뷰어·
   랜딩·나란히 보기)은 Zynix 를 안 싣지만 **이 파일은 전부 싣는다**
   (`_shell_chrome.head_css(parts=true)`). 그래서 여기 한 벌을 두면
   **어느 문서에서도** 시스템 창이 필요 없어진다.

   ★ 이것은 `ti-modal`(앱 팝업)을 대신하지 않는다. 앱 화면은 지금처럼 그
     팝업을 쓰고, 이 부품은 **팝업이 없는 문서에서만** 선다. 부품이 둘로
     갈리지 않게, 부르는 창구는 `ts/dlg.ts` 하나다.
   ★ 층은 사다리에서 온다(0-7 규칙 3) — 시스템 창을 대신하므로 가장 위다.
   ========================================================================== */
.nx-dlg-back {
  position: fixed;
  inset: 0;
  background: rgba(17, 24, 39, 0.45);
  z-index: var(--nx-z-toast);
}
.nx-dlg {
  position: fixed;
  inset: 0;
  z-index: calc(var(--nx-z-toast) + 1);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 18px;
}
.nx-dlg__panel {
  width: min(420px, 100%);
  max-height: calc(100% - 36px);
  overflow: auto;
  background: #fff;
  color: #1f2937;
  /* 라운드 0 — 사장님 확정(전 화면 공통). test_ui_offline 이 지킨다. */
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.28);
  padding: 22px 22px 18px;
  font-size: 0.95rem;
  line-height: 1.6;
}
.dark .nx-dlg__panel { background: #232226; color: #e5e7eb; }   /* dx134 — 템플릿 면 */
.nx-dlg__msg { margin: 0 0 14px; white-space: pre-line; word-break: break-word; }

/* ★★★★★★ F651(사장님 지시 2026-08-22: *"양쪽화면의 싱크를 위해 계산중입니다
   글자와 앞에 `ri-refresh-line` 아이콘 붙여서 팝업 넣어라"*)

   **새 팝업을 짓지 않는다**(0-0). 위 `nx-dlg` 틀(막 + 판)을 그대로 쓰고,
   여기서 더하는 것은 «아이콘과 글자가 한 줄에 선다» 뿐이다.
   ★ 단추가 없다 — 사람이 닫는 것이 아니라 **준비되면 저절로** 걷힌다.
     그래서 아래 여백(`__msg` 의 14px)도 없앤다.
   ★ 도는 규칙은 위 `@keyframes nx-pgrow-spin` 한 곳이다(F578-1 이 화면마다
     따로 뒀다가 안 돌았다). 크기·색은 글자를 따라간다 — 팝업 글꼴이 바뀌면
     아이콘도 같이 바뀐다.
   ★ 라운드 0 — 전 화면 공통(예외는 도는 것 자신뿐이다. 각진 것이 돌면
     도는 것으로 안 읽힌다). */
.nx-dlg__busy {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
}
.nx-dlg__busy i {
  animation: nx-pgrow-spin 1s linear infinite;
  flex: none;
  display: inline-block;
  font-size: 1.15em;
  line-height: 1;
  color: var(--pd-primary, var(--nx-primary));
}
/* 움직임을 싫어하는 사람에게는 안 돌린다 — 접근성 기본. 글자는 그대로다. */
@media (prefers-reduced-motion: reduce) {
  .nx-dlg__busy i { animation: none; }
}

/* ★★★★★★ F651(사장님 지시 2026-08-22: *"12초씩이나 걸릴 일이 있으면 로딩팝에
   예상 시간을 계산해서 표시하고 로딩바를 주어야 한다"*)

   막대는 **이 부품의 일부**로 여기 둔다. 진행바 공용 부품(`.nx-pgrow`)은
   `nx-form.css` 에 있는데 뷰어·나란히 보기는 그 파일을 **안 싣는다**(실측) —
   거기 기대면 막대가 통째로 안 그려진다. 위 `@keyframes` 를 옮긴 것과 같은
   이유다. 이름을 `.nx-dlg__` 로 두는 것은 새 부품을 지어서가 아니라 **이
   대화상자의 부분**이기 때문이다(0-0: 같은 종류는 같은 CSS).
   ★ 라운드 0 — 전 화면 공통.
   ★ 폭은 애니메이션으로 따라간다 — 값이 튀어도 눈이 따라갈 수 있게. 이것은
     **꾸미기**라 시간을 써도 된다(0-9 규칙 10). 판정은 여전히 사실이다. */
.nx-dlg__prog {
  margin: 12px 0 0;
  height: 6px;
  background: #e5e7eb;
  overflow: hidden;
}
.dark .nx-dlg__prog { background: #2e2d32; }   /* dx134 — 템플릿 `--light` 46 45 50 */
.nx-dlg__prog > i {
  display: block;
  height: 100%;
  width: 0;
  background: var(--pd-primary, var(--nx-primary));
  transition: width .25s linear;
}
/* 남은 시간 — 값이 없으면 **글자를 안 만든다**(빈 줄을 남기지 않는다). */
.nx-dlg__eta {
  margin: 8px 0 0;
  font-size: .85em;
  opacity: .75;
}
.nx-dlg__in {
  width: 100%;
  box-sizing: border-box;
  margin: 0 0 14px;
  padding: 8px 10px;
  border: 1px solid #cbd5e1;
  background: #fff;
  color: inherit;
  font: inherit;
}
.dark .nx-dlg__in { background: #19181a; border-color: rgba(255,255,255,.1); }   /* dx134 */
.nx-dlg__foot { display: flex; justify-content: flex-end; gap: 8px; }
.nx-dlg__btn {
  min-width: 76px;
  padding: 7px 14px;
  border: 1px solid #cbd5e1;
  background: #fff;
  color: #334155;
  font: inherit;
  cursor: pointer;
}
.dark .nx-dlg__btn { background: #2e2d32; border-color: rgba(255,255,255,.1); color: #e5e7eb; }   /* dx134 */
.nx-dlg__btn--primary {
  background: var(--pd-primary, var(--nx-primary));
  border-color: var(--pd-primary, var(--nx-primary));
  color: #fff;
}
.nx-dlg__btn:focus-visible { outline: 2px solid var(--pd-primary, var(--nx-primary)); outline-offset: 2px; }

/* ─── 페이지 간 이동 부드럽게 (사장님 지시 2026-08-27 · A안) ───
   랜딩(_landing_base.css)과 같은 한 벌 — 앱 셸 쪽 문서 간 이동에도
   같은 페이드가 걸린다. 미지원 브라우저는 무시(효과 없음 = 종전과 동일). */
@view-transition { navigation: auto; }
::view-transition-old(root) { animation: 160ms ease-out both nx-vt-fade-out; }
::view-transition-new(root) { animation: 200ms ease-in  both nx-vt-fade-in; }
@keyframes nx-vt-fade-out { to { opacity: 0; } }
@keyframes nx-vt-fade-in  { from { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root), ::view-transition-new(root) { animation: none; }
}
