@charset "utf-8";

/* ===================================================== MOCA.CSS — Component Styles (이미지 파일 미사용 / SVG inline / CSS 변수 기반) ===================================================== */

/* ── SVG 아이콘 모음 ─────────────────────────────────── */

/* 각 버튼별 SVG data URI를 아래에 정의합니다. */

/* ─── 1. MOCA-FRAME ─────────────────────────────────── */
.moca-frame > div { display: flex; flex-direction: column; flex: 1; width: 100%; height: 100%; overflow: hidden; }

/* ─── 2. 검색 테이블 (moca-table) ───────────────────── */
.moca_table_search { display: block; width: 100%; background: var(--c-surface); border: 1px solid var(--c-border); border-radius: var(--radius-md); padding: 15px; flex-shrink: 0; }
/* 접기 되는 검색영역 — form 으로 선언해 data-m-toolbarfold 를 붙인 경우(SKILL PART A 표준).
   이때 여백이 두 겹이 된다: 이 박스의 padding 15px 안에 엔진이 .moca_form_body(padding 15px)를
   또 만든다. 합쳐 30px 이라 접어서 툴바만 남아도 위아래가 휑하게 뜬다.
   게다가 바깥 패딩이 툴바까지 15px 안으로 밀어, 제목·접기 버튼이 아래 그리드 툴바(padding 0 10px)와
   세로줄이 어긋난다. 바깥 패딩만 0으로 내린다 — 본문 여백은 .moca_form_body 가 이미 갖고 있다.
   overflow:hidden 은 툴바 배경이 둥근 모서리를 넘지 않게 한다. */
div[data-m-type="form"].moca_table_search { padding: 0; overflow: hidden; }
.moca_table_search [data-m-type="tbody"] { display: block; }
.moca_table_search [data-m-type="tr"] { display: flex; align-items: center; flex-wrap: wrap; gap: 20px; }
/* th 라벨영역 기본 폭 — 5글자(12px×5=60px) 기준·우측정렬로 전 화면 통일. 개별 화면은 style="width:…" 로 예외 지정 */
:root { --m-th-label-width: 60px; }
/* 화면 좌우 시작선 — 본문(.mdi_panel)과 헤더가 같은 값을 써야 한 화면에 시작선이 하나가 된다.
   따로 적어 두면 조용히 갈라진다(실제로 헤더 20px vs 본문 15px 로 어긋나 있었다). */
:root { --m-edge: 15px; }
.moca_table_search [data-m-type="th"] { display: flex; align-items: center; justify-content: flex-end; width: var(--m-th-label-width); flex-shrink: 0; height: 28px; font-size: 12px; color: var(--c-text-sub); white-space: nowrap; margin-right: -10px; font-weight: bold;}
.moca_table_search [data-m-type="td"] { display: flex; align-items: center; gap: 8px; height: 28px; }

/* 조회 버튼 */
.moca_table_search [data-m-type="td"] button.btn_search { height: 28px; padding: 0 16px; background: var(--c-primary); color: var(--c-white); border-radius: var(--radius-sm); font-size: 12px; font-weight: 600; letter-spacing: 0.3px; transition: background 0.15s; box-shadow: var(--shadow-sm); }
.moca_table_cont [data-m-type="td"] button:hover { background: var(--c-primary-dark); }

/* ── 조회 버튼 — 아웃라인 시안 ── */
.moca_table_search [data-m-type="td"] button.btn_search, .btn_search {height: 28px; padding: 0 16px; background: unset; border: 1px solid var(--c-primary); color: var(--c-primary); box-shadow: none;}
.moca_table_search [data-m-type="td"] button.btn_search:hover, .btn_search:hover {background: var(--c-primary-light);}

/* ─── 3. MOCA-GRID 외부 래퍼 ─────────────────────────── */
.moca_grid { position: relative; /* gridDetail_body 등 absolute 오버레이 포지셔닝 기준 */ display: flex; flex-direction: column; width: 100%; background: var(--c-white); border: 1px solid var(--c-border); border-radius: var(--radius-md); box-shadow: var(--shadow-md); overflow: hidden; }
.moca_grid.fauto { flex: 1; min-height: 0; }

/* ─── 4. 그리드 툴바 ─────────────────────────────────── */
.moca_grid_toolbar { display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; height: 38px; background: var(--c-grid-toolbar); border-bottom: 1px solid var(--c-border); padding: 0 10px; }
.moca_grid_toolbar > .lta { display: flex; align-items: center; gap: 6px; flex: 1; min-width: 0; overflow: hidden; min-width: 0; overflow: hidden; }
.grid_title { font-size: 14px; font-weight: 700; color: var(--c-text); white-space: nowrap; }
.moca_table_title { font-size: 12px; color: var(--c-text-muted); white-space: nowrap; }
.grid_total { font-size: 12px; color: var(--c-text-muted); white-space: nowrap; }
.grid_total .txt_blue { color: var(--c-primary); font-weight: 700; }
.moca_grid_toolbar > .rta { display: flex; align-items: center; gap: 4px; flex-shrink: 0; flex-shrink: 0; }

/* ─── 5. 툴바 아이콘 버튼 (이미지 없이 SVG) ─────────── */
.moca_grid_toolbar .button { width: 28px; height: 28px; border-radius: var(--radius-sm); background: var(--c-white) no-repeat center / 16px 16px; border: 1px solid var(--c-border); transition: all 0.15s; cursor: pointer; box-shadow: var(--shadow-sm); }
.moca_grid_toolbar .button:hover { background-color: var(--c-primary-light); border-color: var(--c-primary-mid); }
.moca_grid_toolbar .button:active { background-color: var(--c-primary-mid); }

/* 행 추가 — Plus */
.button.add_row { background-image: url('../images/icon_add_row.svg'); }

/* 행 삭제 — Trash */
.button.del_row { background-image: url('../images/icon_del_row.svg'); }

/* 엑셀 다운로드 — DownloadArrow */
.button.excel_dn { background-image: url('../images/icon_excel_dn.svg'); }

/* 엑셀 업로드 — UploadArrow */
.button.excel_up { background-image: url('../images/icon_excel_up.svg'); }

/* 전체화면 — Expand */
.button.grid_full { background-image: url('../images/icon_grid_full.svg'); }
.button.grid_default { background-image: url('../images/icon_grid_reduce.svg'); }

/* 디테일뷰 — List */
.button.grid_detail { background-image: url('../images/icon_grid_detail.svg'); }

/* 다음 — ChevronRight */
.button.read_next { background-image: url('../images/icon_read_next.svg'); }

/* 더블클릭 — CursorClick */
.button.grid_dblclick { background-image: url('../images/icon_grid_dblclick.svg'); }

/* 접기 — ChevronUp */
.button.grid_fold { background-image: url('../images/icon_grid_fold.svg'); }
/* 펼치기는 접기 아이콘을 뒤집어 쓴다 — 전용 아이콘이 없어 종전에는 버튼이 빈 칸으로 남았다
   (Form 접기도 같은 증상이었다, 2026-09-01). 방향이 반대라는 뜻이 그대로 전달된다. */
.button.grid_unfold { background-image: url('../images/icon_grid_fold.svg'); transform: scaleY(-1); }

/* 컬럼숨기기 — Gear */
.button.col_showhide { background-image: url('../images/icon_col_showhide.svg'); }

/* 닫기 — X */
.button.btn_close { background-image: url('../images/icon_close.svg'); }
.button.grid_detail_close { background-image: url('../images/icon_grid_detail_close.svg'); }

/* colTh 버튼 — Layout 아이콘 (1단/2단/3단) */
.button.colTh1 { background-image: url('../images/icon_col_th1.svg'); }
.button.colTh2 { background-image: url('../images/icon_col_th2.svg'); }
.button.colTh3 { background-image: url('../images/icon_col_th3.svg'); }

/* 텍스트 레이블 커스텀 버튼 */
.moca_grid_toolbar .grid_btn { display: flex; align-items: center; }
.moca_grid_toolbar .grid_btn button { height: 28px; padding: 0 12px; color: var(--c-primary-dark); border: 1px solid var(--c-primary); border-radius: var(--radius-sm); font-size: 12px; font-weight: 600; white-space: nowrap; cursor: pointer; transition: background 0.15s, border-color 0.15s, color 0.15s; box-shadow: var(--shadow-sm); }
.moca_grid_toolbar .grid_btn button:hover { background: var(--c-primary-light); }
.moca_grid_toolbar .btn_save { height: 28px; padding: 0 14px; background: var(--c-primary); color: var(--c-white); border: 1px solid var(--c-primary-dark); border-radius: var(--radius-sm); font-size: 12px; font-weight: 600; white-space: nowrap; cursor: pointer; transition: background 0.15s; box-shadow: var(--shadow-sm); }
.moca_grid_toolbar .btn_save:hover { background: var(--c-primary-dark); }
/* 커스텀 버튼 비활성(innerDisabled) — 인라인 색 하드코딩 대신 CSS(테마 변수)로. hover 규칙보다 뒤에 선언(비활성 시 hover 배경 무효) */
.moca_grid_toolbar .grid_btn button:disabled { background: var(--c-bg); color: var(--c-text-muted); border-color: var(--c-border); box-shadow: none; cursor: not-allowed; }
/* 그리드 셀 버튼(data-m-celltype="button") — 종전엔 모양 규칙이 없어 다크 테마에서 흐린 글자로만 보였다(2026-09-30 WithPlanPlus 회원관리 [허용/해제]). */
td[data-m-celltype="button"] .grid_btn { display: flex; align-items: center; justify-content: center; }
td[data-m-celltype="button"] .grid_btn button { display: inline-flex; align-items: center; justify-content: center; height: 24px; padding: 0 12px; border: 1px solid var(--c-primary); border-radius: var(--radius-sm, 4px); background: transparent; color: var(--c-primary); font-size: 12px; font-weight: 600; line-height: 1; white-space: nowrap; cursor: pointer; transition: background .15s, color .15s; }
td[data-m-celltype="button"] .grid_btn button:hover { background: var(--c-primary); color: var(--c-on-primary, #fff); }
td[data-m-celltype="button"] .grid_btn button:focus-visible { outline: 2px solid var(--c-primary); outline-offset: 2px; }
td[data-m-celltype="button"] .grid_btn button:disabled { background: transparent; color: var(--c-text-muted); border-color: var(--c-border); cursor: not-allowed; }

/* 커스텀 툴바 아이템(data-m-toolbar*) — 라벨스팬/입력/콤보/라디오/체크박스. 색상 전부 CSS 변수 → 라이트/다크 테마 공용 */
.moca_grid_toolbar .grid_label_span { display: flex; align-items: center; gap: 5px; font-size: 12px; color: var(--c-text-sub); white-space: nowrap; flex-shrink: 0; }
.moca_grid_toolbar .grid_label_span .label { font-weight: 600; color: var(--c-text-sub); }
/* 값/강조색 — 다크 오버라이드 `.lta span { color … !important }` 를 이겨야 해서 !important (선언 순서: value → txt_*) */
.moca_grid_toolbar .grid_label_span .value { font-weight: 700; color: var(--c-text) !important; font-variant-numeric: tabular-nums; }
.moca_grid_toolbar .grid_label_span .txt_green { color: var(--c-success) !important; }
.moca_grid_toolbar .grid_label_span .txt_blue  { color: var(--c-primary) !important; }
.moca_grid_toolbar .grid_label_span .txt_red   { color: var(--c-danger)  !important; }
/* 툴바 input — 레이아웃(높이/폭)만. 시각·포커스는 박스 입력 공통 베이스 */
.moca_grid_toolbar .grid_toolbar_input { height: 28px; width: 100px; padding: 0 8px; }
.moca_grid_toolbar .grid_toolbar_combo { flex-shrink: 0; }
.moca_grid_toolbar .grid_toolbar_combo select { height: 28px; padding-top: 0; padding-bottom: 0; }
.moca_grid_toolbar .grid_toolbar_checkbox { display: flex; align-items: center; }
.moca_grid_toolbar .grid_toolbar_checkbox .moca_checkbox_label { font-size: 12px; color: var(--c-text-body); cursor: pointer; }

/* ─── 6. 그리드 리스트 영역 (핵심 레이아웃) ──────────── */
.moca_grid_list { position: relative; /* grid_body absolute 포지셔닝 기준 */ flex: 1; min-height: 0; overflow: hidden; }

/* grid.js가 style="right:18px" 인라인 설정 → absolute 필요 */
.moca_grid_body { position: absolute; top: 0; left: 0; right: 9px; bottom: 0; overflow-x: auto; overflow-y: hidden; }
.moca_grid_body table { width: 100%; border-collapse: collapse; table-layout: fixed; }

/* 커스텀 세로 스크롤바 */
.moca_scrollY_type1 { position: absolute; top: 0; right: 0; width: 9px; bottom: 0; overflow-y: scroll; overflow-x: hidden; }
/*
/* 스트립의 네이티브 스크롤바는 숨김 — 아래 엔진 썸(.moca_grid_swipethumb)이 모양·조작을 대체한다
   (iOS 는 ::-webkit-scrollbar 미지원·안드로이드는 상시표시로 제각각이라 전 기기 통일, 2026-07-22) */
.moca_scrollY_type1 { scrollbar-width: none; }
.moca_scrollY_type1::-webkit-scrollbar { display: none; }
/* 엔진이 그리는 세로 스크롤 썸 — PC/모바일 공통. grid.js _updateSwipeThumb 가 위치·표시 제어,
   _swipeThumbDragStart 로 드래그 조작(PC 마우스·터치 공통). 스크롤 여지 없으면 표시 안 됨 */
.moca_grid_swipethumb { position: absolute; top: 0; right: 2px; width: 6px; border-radius: 3px;
    background: rgba(120, 130, 145, 0.65); opacity: 0; transition: opacity 0.25s;
    cursor: pointer; touch-action: none; z-index: 20; }
.moca_grid_swipethumb:hover { background: rgba(100, 112, 130, 0.85); }
*/

/* ─── 7. 그리드 헤더 (thead) ─────────────────────────── */
.moca_grid_body thead { position: sticky; top: 0; z-index: 10; }
.moca_grid_body thead tr { background: var(--c-grid-th); }
.moca_grid_body thead th { height: 32px; padding: 0 12px; font-size: 12px; font-weight: 700; color: var(--c-text-sub); border-right: 1px solid var(--c-border); border-bottom: 1px solid var(--c-border); text-align: center; overflow: hidden; user-select: none; white-space: nowrap; position: relative; }
.moca_grid_body thead th:last-child { border-right: none; }

/* th 내부 — sort_box는 flex wrapper (팝업 아님) */
.moca_grid_body thead th .moca_grid_sort_box { display: flex; align-items: center; justify-content: center; gap: 3px; height: 100%; overflow: hidden; }
.moca_grid_body thead th .moca_grid_sort_box > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 정렬 버튼 — SVG 아이콘, 텍스트("정렬취소") 숨김 */
.moca_grid_sort_btn { flex-shrink: 0; width: 14px; height: 14px; background: no-repeat center / 12px; background-image: url('../images/icon_sort.svg'); border: none; cursor: pointer; font-size: 0; color: transparent; padding: 0; transition: opacity 0.12s; }
.moca_grid_sort_btn:hover { background-image: url('../images/icon_sort_hover.svg'); }
.moca_grid_sort_btn.sort_asc { background-image: url('../images/icon_sort_asc.svg'); }
.moca_grid_sort_btn.sort_desc { background-image: url('../images/icon_sort_desc.svg'); }

/* 필터 버튼 — SVG Funnel (mask 방식: 색을 테마 변수로 칠한다)
   background-image 가 아니라 mask + background-color 라, 색이 각 테마의 CSS 변수를 그대로 따른다.
   그래서 theme-light/hc 의 filter 재색상 목록에서 이 버튼은 제외된다(변수 기반이므로). */
.moca_grid_body thead th .moca_grid_filter_btn { flex-shrink: 0; width: 14px; height: 14px; background-color: var(--c-text-muted); -webkit-mask: url('../images/icon_filter.svg') no-repeat center / 12px; mask: url('../images/icon_filter.svg') no-repeat center / 12px; border: none; cursor: pointer; font-size: 0; color: transparent; padding: 0; }
.moca_grid_body thead th .moca_grid_filter_btn:hover { background-color: var(--c-primary); }

/* 필터 적용 상태 — 엔진(_setFilterAppliedMark)이 th 에 data-m-filterapplied="true" 를 라이브 반영.
   미적용=선(outline)만 있는 깔때기(색: --c-text-muted), 적용=속이 꽉 찬(solid) 깔때기(색: --c-primary).
   mask 라 색은 테마의 --c-primary 를 그대로 따르고, 모양(윤곽선 vs 채움 덩어리)도 달라 이중으로 구분된다. */
.moca_grid_body thead th[data-m-filterapplied="true"] .moca_grid_filter_btn,
.moca_grid_body thead th[data-m-filterapplied="true"] .moca_grid_filter_btn:hover { background-color: var(--c-primary); -webkit-mask-image: url('../images/icon_filter_on.svg'); mask-image: url('../images/icon_filter_on.svg'); }

/* 필터 해제 i 태그 (JS 제어) */
.moca_grid_body thead th .moca_grid_sort_box > i { display: none; }

/* 컬럼 리사이즈 핸들 */
.groupbar { position: absolute; right: 0; top: 15%; width: 4px; height: 70%; cursor: col-resize; background: transparent; }
.groupbar:hover { background: var(--c-primary-mid); border-radius: 2px; }

/* 마지막 컬럼의 우측 경계(테이블 끝)는 리사이즈 대상 아님 — 핸들 숨김(엔진 _gridColDown 도 이벤트 제외) */
.moca_grid_body thead th:last-child > .groupbar { display: none; }

/* 필수 컬럼 */
.moca_grid_body thead th.req::after { content: ' *'; color: var(--c-danger); font-size: 11px; }

/* th 전체선택 체크박스 */
/* ── 체크박스 시각 base — 어디서든 재사용할 수 있게 스코프 없이 둔다.
   같은 모양이 .checkboxGroup / 그리드 셀 / 여기 세 곳에 중복 정의돼 있어서, 화면이 체크박스를 쓰려면
   컴포넌트 안이 아니고선 재사용할 길이 없었다(실제로 그 때문에 어떤 화면이 체크박스를 새로 만든 사고가 있었다 — 2026-07-26).
   ↓ 아래의 그리드·checkboxGroup 규칙이 뒤에 와서 각자 필요한 것만 덮는다(기존 화면 영향 없음). */
.moca_checkbox_input { -webkit-appearance: none; appearance: none; width: 14px; height: 14px; border: 1.5px solid var(--c-white); border-radius: 2px; background: var(--c-surface); cursor: pointer; flex-shrink: 0; }
.moca_checkbox_input:checked { background-color: var(--c-surface); background-image: url('../images/icon_check.svg'); background-repeat: no-repeat; background-position: center; background-size: 10px 10px; border-color: var(--c-white); }
.moca_checkbox_input:disabled { opacity: 0.5; cursor: default; }

.moca_checkbox_grid { display: flex; align-items: center; justify-content: center;}
.moca_checkbox_grid .moca_checkbox_input { width: 14px; height: 14px; cursor: pointer; accent-color: var(--c-primary); }

/* "label" 텍스트 숨김 */
.moca_checkbox_grid .moca_checkbox_label { font-size: 0; line-height: 0; }

/* ─── 8. 그리드 바디 (tbody) ─────────────────────────── */
.moca_grid_body tbody tr { border-bottom: 1px solid var(--c-border); transition: background 0.08s; }
.moca_grid_body tbody tr:nth-child(even) { background: var(--c-grid-row-alt); }
.moca_grid_body tbody tr:hover { background: var(--c-grid-hover) !important; }
.moca_grid_body tbody tr.selected { background: var(--c-grid-selected) !important; }
.moca_grid_body tbody tr.selected td { color: #f1f5f9 !important; }
.moca_grid_body tbody td { height: 26px; padding: 0 2px; font-size: 12px; color: var(--c-text); border-right: 1px solid var(--c-border); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; }
.moca_grid_body tbody td:last-child { border-right: none; }
/* readonly 좌측정렬 텍스트는 편집 컨트롤의 내부 여백과 비슷한 시작선을 갖게 한다.
   버튼·콤보·편집 셀과 좁은 특수컬럼에는 적용하지 않는다. */
.moca_grid_body tbody td.tal[data-m-readonly="true"][data-m-celltype="input"]:not(.moca_cell_slim) { padding-left: 8px; }

/* 셀 텍스트 넘침 = 우측 가장자리 페이드아웃 — … 와 달리 모든 행이 정확히 같은 x 지점에서 끝나 정렬이 균일.
   "실제로 넘칠 때만" 페이드: td(overflow:hidden)를 scroll-state 컨테이너로 두고 내부 div 는 클립하지 않아
   넘침이 td 의 scrollable 상태로 전파되게 함 → 넘침 시에만 div 에 마스크(페이드+클립) 적용.
   마스크는 순수 시각효과라 scrollWidth 를 바꾸지 않으므로 상태 되먹임(깜빡임) 없음.
   마스크는 td 가 아닌 내부 요소에만 건다(td 에 걸면 컬럼 구분선까지 페이드됨). 체크박스/버튼입력 래퍼 제외 */
.moca_grid_body tbody td { container-type: scroll-state; }
/* slim 특수컬럼(행번호 등) 제외 — 표시함수가 scaleX 로 자체 축소하는데 transform 은 시각효과라
   레이아웃 폭이 남아(글리프 폭 추정오차 포함) 넘침으로 오감지 → 축소돼 다 보이는데도 페이드됨 */
.moca_grid_body tbody td.moca_cell_slim { container-type: normal; }
.moca_grid_body tbody td > div:not(.moca_checkbox_grid):not(.moca_ibn) { white-space: nowrap; }
.moca_grid_body tbody td > div.moca_radio_grid {display: flex; gap: 5px;}
@container scroll-state(scrollable: right) {
    .moca_grid_body tbody td > div:not(.moca_checkbox_grid):not(.moca_ibn) {
        -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 14px), transparent);
        mask-image: linear-gradient(to right, #000 calc(100% - 14px), transparent);
        -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;   /* 마스크 밖(넘친 부분)은 투명 = 클립 역할 */
        -webkit-mask-size: 100% 100%; mask-size: 100% 100%;
    }
}
/* scroll-state 미지원 브라우저 폴백(iOS WebKit 등) — 클립은 항상, 페이드는 실측 마킹된 셀에만.
   grid.js _scheduleFadeMarks 가 렌더 후 넘침을 실측해 td[data-m-fade] 를 붙인다.
   (구 폴백은 항상 페이드라 우측정렬 숫자 끝자리가 넘치지 않아도 흐려 보였다 — 2026-07-22) */
@supports not (container-type: scroll-state) {
    .moca_grid_body tbody td:not(.moca_cell_slim) > div:not(.moca_checkbox_grid):not(.moca_ibn) {
        overflow: hidden;
    }
    .moca_grid_body tbody td[data-m-fade] > div:not(.moca_checkbox_grid):not(.moca_ibn) {
        -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 14px), transparent);
        mask-image: linear-gradient(to right, #000 calc(100% - 14px), transparent);
    }
}
/* ※ 넘침 페이드는 readonly 표시 셀(위 scroll-state 로 실제 넘칠 때만 페이드되는 <div>)에만 건다.
   편집형 셀(<input>: celltype input/combo/searchCombo 등)에는 페이드를 걸지 않는다 — 종전엔 input 에도
   "항상 오른쪽 14px 페이드" mask 를 걸어(넘침 여부와 무관) 편집 셀마다 우측이 흐리고, combo 는 화살표까지
   지워졌다. 편집 셀은 넘치면 네이티브 input 스크롤/클립으로 처리하고 편집 시 전체가 보이면 충분하다(2026-07-21). */

/* 셀 넘침(fade) 툴팁 — 페이드로 잘린 셀 hover 시 전체 텍스트 (grid.js 문서 위임 리스너가 표시/위치 제어)
   색상: DevTools 자동완성 선택행 느낌(doc/툴팁느낌.png) — 선명한 파랑 배경 + 흰 글자로 주변과 강한 대비 */
.moca_cell_tooltip { position: fixed; max-width: 60vw; padding: 6px 10px; background: #2B6BE0; color: #fff; border: 1px solid #4D8DFF; border-radius: var(--radius-sm); font-size: 12px; font-weight: 500; line-height: 1.5; white-space: pre-wrap; word-break: break-all; box-shadow: 0 4px 14px rgba(0,0,0,0.55); z-index: 99980; opacity: 0; pointer-events: none; transition: opacity 0.12s; }
.moca_cell_tooltip.visible { opacity: 1; }

/* 좌우 여백 최소 셀 — 상태(St) 등 좁은 특수컬럼용. 기본 12px 패딩을 4px 로 줄여 좁은 폭에서도 내용이 보이게 한다 */
.moca_grid_body thead th.moca_cell_slim, .moca_grid_body tbody td.moca_cell_slim { padding-left: 4px; padding-right: 4px; }
.moca_grid_body tbody td.moca_cell_slim input { padding: 0; text-align: center; }
/* slim 셀의 체크박스 — label 이 폭 0 인데 gap 4px 가 들어가 체크박스가 중앙에서 왼쪽으로 밀리는 것 방지 */
.moca_grid_body thead th.moca_cell_slim .moca_checkbox_grid, .moca_grid_body tbody td.moca_cell_slim .moca_checkbox_grid { gap: 0; }
/* 특수컬럼 헤더 아이콘(행상태/행번호 등) — 텍스트 대신 아이콘 표시, 의미는 th title 툴팁으로 안내.
   14px = 전체선택 체크박스(.moca_checkbox_input)와 동일 크기, display:block 으로 baseline 오프셋 제거
   → 옆 delcheck 헤더 체크박스와 상·하가 나란히 정렬됨(둘 다 flex 중앙정렬 래퍼) */
.moca_grid_body thead th .moca_th_icon { width: 14px; height: 14px; display: block; }

/* 집계행(소계/합계 — grid.js §49 data-m-calc/subtotal/total). 전용 변수(--c-grid-agg-*)로
   배경·글씨를 짝으로 지정 — 일반 행/선택색과 뚜렷이 구분(테마 3종 값은 base.css/theme-hc.css).
   행 hover(!important)보다 우선해야 소계행이 hover 색으로 씻겨 보이지 않는다. */
.moca_grid_body tbody tr.moca_grid_agg,
.moca_grid_body tbody tr.moca_grid_agg:hover { background: var(--c-grid-agg-bg) !important; }
.moca_grid_body tbody tr.moca_grid_agg td { color: var(--c-grid-agg-text); font-weight: 600; }
.moca_grid_body tbody tr.moca_grid_agg_total,
.moca_grid_body tbody tr.moca_grid_agg_total:hover { background: var(--c-grid-agg-total-bg) !important; }
.moca_grid_body tbody tr.moca_grid_agg_total td { color: var(--c-grid-agg-total-text); font-weight: 700; border-top: 1px solid var(--c-border-dark); }
/* 합계행 고정(§49) — _genTbody 가 합계행을 스크롤 창과 무관하게 항상 렌더하고, bottom 위치일 때
   in-flow 위치가 뷰포트 아래로 밀리면 sticky 가 하단 모서리에 붙인다(미리보기 반행 위를 덮는다).
   sticky 는 tr 이 아니라 td 에 준다(tr sticky 는 브라우저별로 불안정) — tr 배경은 sticky 셀을 따라오지
   않으므로 td 에 배경을 직접 주고, 위 경계선은 collapse+sticky 조합에서 떨어져 나가므로 box-shadow 로 그린다. */
.moca_grid_body tbody tr.moca_grid_agg_total td { position: sticky; bottom: 0; z-index: 3; background: var(--c-grid-agg-total-bg); box-shadow: 0 -1px 0 var(--c-border-dark); }
.moca_grid_body tbody tr.moca_grid_agg td > div { font-weight: inherit; }
/* colMerge(§49) — 진짜 rowspan 병합. 병합 셀(.moca_grid_cellmerged, rowspan>1)은 배경을 불투명하게 —
   아래 덮인 행들의 지브라 줄무늬가 셀 안에 비쳐 보이지 않게 한다. 배경 변수는 --c-white 가 아니라
   --c-surface(그리드 실배경 — .moca_grid 전 테마 공통 오버라이드, 아래 1128행 부근).
   병합 셀엔 hover/selected 강조를 주지 않는다(여러 행에 걸친 셀이라 특정 행 강조가 어색). */
.moca_grid_body tbody td.moca_grid_cellmerged { background: var(--c-surface); }
/* colmerge 컬럼의 단일(비병합) 셀도 평상시 지브라를 죽여 컬럼 전체 톤을 통일 — hover/selected 강조는 유지 */
.moca_grid_body tbody tr:not(:hover):not(.selected) td.moca_grid_cellmergecol { background: var(--c-surface); }
/* 그리드 셀 inputPop/inputCalendar — 폼 컴포넌트의 버튼 클래스(.moca_ipp_btn/.moca_ica_btn)를 재사용하는 셀 래퍼.
   inputPop 버튼은 절대배치(우측 겹침), inputCalendar 버튼은 플렉스 배치(입력창 옆) — 각 폼 원형과 동일한 레이아웃 */
.moca_grid_ippcell, .moca_grid_calcell { position: relative; display: flex; align-items: center; width: 100%; }
.moca_grid_ippcell input { padding-right: 26px; }
.moca_grid_calcell input { flex: 1 1 auto; min-width: 0; }
.moca_grid_calcell .moca_ica_btn { margin-left: 2px; }
/* inputCalendar 셀 — 폼 컴포넌트(div[data-m-type=inputCalendar])처럼 래퍼 div 에 테두리+배경을 주고,
   안쪽 input 은 무테·투명(컴포넌트 .moca_ica input 과 동일). 그리드 공통 input 테두리 규칙을 안쪽 input 에서 되돌린다. */
.moca_grid_body tbody td .moca_grid_calcell { height: 22px; border: 1px solid var(--c-border-dark); border-radius: var(--radius-sm); background: var(--c-bg-input); box-sizing: border-box; transition: border-color 0.15s, box-shadow 0.15s; }
.moca_grid_body tbody td .moca_grid_calcell:focus-within { border-color: var(--c-primary); box-shadow: 0 0 0 2px var(--c-primary-pale); }
.moca_grid_body tbody td .moca_grid_calcell > input.moca_input,
.moca_grid_body tbody td .moca_grid_calcell > input.moca_input:focus { border: 0; background: none; box-shadow: none; }

/* 정렬 클래스(tac/tar)를 flex 기반 셀에도 반영 — radio(.moca_radio_grid)·inputCalendar(.moca_grid_calcell) 는
   flex 라 td 의 text-align 이 자식에 안 먹는다(input 은 UA 기본 text-align:start, radio 는 flex 배치). tal 은 기본값이라 규칙 불필요. */
.moca_grid_body tbody td.tac > div.moca_radio_grid { justify-content: center; }
.moca_grid_body tbody td.tar > div.moca_radio_grid { justify-content: flex-end; }
.moca_grid_body tbody td.tac > .moca_grid_calcell input { text-align: center; }
.moca_grid_body tbody td.tar > .moca_grid_calcell input { text-align: right; }

/* 상태 */
.moca_grid_body tbody td[data-status="C"] { color: var(--c-primary); font-weight: 600; }
.moca_grid_body tbody td[data-status="U"] { color: var(--c-warning); font-weight: 600; }
.moca_grid_body tbody td[data-status="D"] { color: var(--c-danger); text-decoration: line-through; }

/* 편집 컨트롤 셀 — 여백의 주인 옮기기: 읽기 셀은 td 패딩(12px)이 텍스트 여백이지만,
   컨트롤(input/combo)이 든 셀은 바깥 12px 가 죽은 공간이라 td 는 2px 만 주고 컨트롤이 셀을 채운다.
   텍스트 들여쓰기는 컨트롤 내부에서: td 2px + 테두리 1px + 내부 9px = 12px → 읽기 셀과 텍스트 시작 위치 동일(전환 시 안 튐) */
.moca_grid_body tbody td:not(.moca_cell_slim):has(> input[type="text"]), .moca_grid_body tbody td:not(.moca_cell_slim):has(> .moca_combo) { padding: 0 2px; }
.moca_grid_body tbody td > .moca_combo { width: 100%; }

/* 셀 인풋 — 평상시 투명 테두리 1px 를 상시 확보(포커스 시 색만 바뀌어 글자가 1px 도 안 밀림) */
/* 편집형 그리드 셀 input — 그리드가 편집가능이면(=readonly 아님) 일반 moca 컴포넌트 input 과 동일하게
   테두리(색 --c-border-dark · radius-sm)를 보인다. readonly 셀은 <input> 이 아니라 <div> 로 렌더돼 자연히 무테. */
.moca_grid_body tbody td input[type="text"], .moca_grid_body tbody td input:not([type]) { width: 100%; height: 22px; padding: 0 8px; border: 1px solid var(--c-border-dark); border-radius: var(--radius-sm); background: var(--c-bg-input); color: var(--c-text); font-size: 12px; outline: none; text-overflow: ellipsis; box-sizing: border-box; transition: border-color 0.15s, box-shadow 0.15s; }
.moca_grid_body tbody td input:focus { background: var(--c-bg-input-focus); border-color: var(--c-primary); box-shadow: 0 0 0 2px var(--c-primary-pale); color: var(--c-text) !important; }
.moca_grid_body tbody td input[readonly] { cursor: default; }

/* 셀 체크박스 */
.moca_grid_body tbody td input[type="checkbox"] { -webkit-appearance: none; appearance: none; width: 14px; height: 14px; border: 1.5px solid var(--c-white); border-radius: 2px; background: var(--c-surface); cursor: pointer; flex-shrink: 0; vertical-align: middle; }
.moca_grid_body tbody td input[type="checkbox"]:checked { background-color: var(--c-surface); background-image: url('../images/icon_check.svg'); background-repeat: no-repeat; background-position: center; background-size: 10px 10px; border-color: var(--c-white); }
/* 셀 라디오 */
.moca_grid_body tbody td input[type="radio"] { -webkit-appearance: none; appearance: none; width: 14px; height: 14px; border: 1.5px solid var(--c-white); border-radius: 50%; background: var(--c-surface); cursor: pointer; flex-shrink: 0; vertical-align: middle; }
.moca_grid_body tbody td input[type="radio"]:checked { border-color: var(--c-white); background: radial-gradient(circle at center, var(--c-white) 31%, var(--c-surface) 31%); }
.moca_grid_body tbody td .moca_checkbox_label { font-size: 0; }
.moca_grid_body tbody td .moca_checkbox_grid { display: flex; align-items: center; justify-content: center; }

/* 삭제선택 컬럼(공통 DEL_CHK) — 체크박스 대신 X 아이콘: 평소 얇은 회색 X, 체크 시 primary 원형 배지(X 부분이 투명 컷아웃) + 팝 애니메이션. 선택용 체크박스(☐/☑)와 시각 구분. mask 방식이라 색은 background-color 변수로 주입 — 체크 상태의 모양(배지 mask)은 여기가 단일 정의처고 theme-light-fix.css 는 색만 재정의한다 */
.moca_grid_body th[id="H_DEL_CHK"] .moca_checkbox_input, .moca_grid_body td[id="DEL_CHK"] input[type="checkbox"] { -webkit-appearance: none; appearance: none; width: 14px; height: 14px; border: none; border-radius: 0; background-color: var(--c-text-body); -webkit-mask: url('../images/icon_del_x.svg') no-repeat center / 12px 12px; mask: url('../images/icon_del_x.svg') no-repeat center / 12px 12px; cursor: pointer; }
.moca_grid_body th[id="H_DEL_CHK"] .moca_checkbox_input:hover, .moca_grid_body td[id="DEL_CHK"] input[type="checkbox"]:hover { background-color: var(--c-text-sub); }
.moca_grid_body th[id="H_DEL_CHK"] .moca_checkbox_input:checked, .moca_grid_body td[id="DEL_CHK"] input[type="checkbox"]:checked { background-color: var(--c-primary); background-image: none; -webkit-mask: url('../images/icon_del_x_badge.svg') no-repeat center / 14px 14px; mask: url('../images/icon_del_x_badge.svg') no-repeat center / 14px 14px; animation: moca_delchk_pop 0.18s ease-out; }
@keyframes moca_delchk_pop { 0% { transform: scale(1); } 50% { transform: scale(1.35); } 100% { transform: scale(1); } }

/* ─── 9. 페이징 ──────────────────────────────────────── */
.moca_grid_paging { flex-shrink: 0; display: flex; align-items: center; justify-content: center; gap: 3px; height: 36px; background: transparent; border-top: 1px solid var(--c-border); padding: 0 10px; }
/* 숫자 버튼 그룹: 아이콘 버튼(first/prev/next/last)과 동일하게 flex 정렬·간격 맞춤 */
.moca_grid_paging .num { display: inline-flex; align-items: center; gap: 3px; }
.moca_grid_paging button { min-width: 26px; height: 26px; border: 1px solid var(--c-border-dark); border-radius: var(--radius-sm); background: transparent no-repeat center / 13px; cursor: pointer; font-size: 11px; font-weight: 600; color: var(--c-text-sub); padding: 0 6px; transition: all 0.12s; }
.moca_grid_paging button:hover { background-color: var(--c-primary-light); border-color: var(--c-primary); color: var(--c-primary); }
.moca_grid_paging button.on { border-color: var(--c-primary-dark); color: var(--c-primary);}

/* 페이징 아이콘 버튼 (텍스트 숨김) */
.moca_grid_paging button.first { background-image: url('../images/icon_page_first.svg'); font-size: 0; color: transparent; }
.moca_grid_paging button.prev { background-image: url('../images/icon_page_prev.svg'); font-size: 0; color: transparent; }
.moca_grid_paging button.next { background-image: url('../images/icon_page_next.svg'); font-size: 0; color: transparent; }
.moca_grid_paging button.last { background-image: url('../images/icon_page_last.svg'); font-size: 0; color: transparent; }

/* ─── 10. 입력 컨트롤 ────────────────────────────────── */

/* frame.js: moca-input → div 변환 후 div.moca_input 안에 input 삽입 외부 div는 레이아웃 컨테이너, 시각 스타일은 내부 input에만 적용 */
.moca_input { display: inline-flex; align-items: center; min-width: 100px; }
/* input 컴포넌트 기본 폭 — 콤보(.moca_combo)와 동일 140px. 화면에서 style="width:…" 지정 시 인라인이 우선.
   (컴포넌트 래퍼 div 에만 적용 — 그리드/서치콤보 내부의 순수 input.moca_input 은 각자 규칙 유지) */
div[data-m-type="input"].moca_input { width: 140px; }
/* ── 박스형 입력 공통 베이스 (유지보수 단일화 · 2026-07-21) ──────────────────
   테두리 있는 박스형 텍스트 입력이 여러 곳에서 같은 선언을 복붙해 라디우스/포커스링이 갈렸던 것을
   하나로 모은다. 폼·디테일뷰편집칸·툴바·inputPop·searchCombo·컬럼필터·달력칸이 공유하고,
   크기·레이아웃(폭/높이/패딩)만 각 맥락 규칙에서 override 한다. 색은 전부 테마 변수라 라이트/다크 자동.
   ※ 그리드 셀 input(.moca_grid_body tbody td input)은 밀도 우선(투명 테두리·평상 무테)이라 의도적 제외.
   ※ 테두리색은 --c-border-dark(컨트롤 경계, WCAG 1.4.11 비텍스트 3:1) — 장식용 --c-border 금지. */
.moca_input input,
.gridDetail td div[contenteditable],
.moca_grid_toolbar .grid_toolbar_input,
[data-m-type="inputPop"] input,
[data-m-type="searchCombo"] .filterheader input.moca_input,
.moca_searchCombo .filterheader input.moca_input,
.itemTable .filterheader input[type="text"].moca_input,
.calendar_ipt .moca_input {
  border: 1px solid var(--c-border-dark); border-radius: var(--radius-sm);
  background: var(--c-bg-input); color: var(--c-text);
  font-size: 12px; font-family: inherit; outline: none; box-sizing: border-box;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.moca_input input:focus,
.gridDetail td div[contenteditable]:focus,
.moca_grid_toolbar .grid_toolbar_input:focus,
[data-m-type="inputPop"] input:focus,
[data-m-type="searchCombo"] .filterheader input.moca_input:focus,
.moca_searchCombo .filterheader input.moca_input:focus,
.itemTable .filterheader input[type="text"].moca_input:focus,
.calendar_ipt .moca_input:focus {
  border-color: var(--c-primary); background: var(--c-bg-input-focus);
  box-shadow: 0 0 0 2px var(--c-primary-pale);
}
.moca_input input[readonly],
.gridDetail td div[contenteditable="false"],
[data-m-type="inputPop"] input[readonly],
[data-m-type="searchCombo"] .filterheader input.moca_input[readonly] {
  background: var(--c-bg-input-readonly); color: var(--c-text-muted);
}
/* 그리드 searchCombo 접힘 input 은 readonly(클릭=열기 UX)지만 폼 컴포넌트와 동일하게 활성 배경으로 보여야 하므로
   위 readonly muted 규칙에서 제외 — 정상 배경(위 공통 베이스 bg-input) 유지 */
/* 폼 입력/디테일뷰 편집칸 — 공통 베이스 위에 레이아웃(크기)만 */
.moca_input input, .gridDetail td div[contenteditable] { width: 100%; padding: 5px 8px; }

/* ─── 10-0-1. inputPop — input + 구분선 + 돋보기(팝업호출) 버튼 (combo 우측 버튼 패턴) ─── */
[data-m-type="inputPop"] { display: inline-flex; align-items: center; min-width: 100px; position: relative; }
/* inputPop — 레이아웃만(우측 30px 은 돋보기 버튼 자리). 시각·포커스·readonly 는 박스 입력 공통 베이스 */
[data-m-type="inputPop"] input { flex: 1 1 auto; min-width: 0; width: 100%; padding: 5px 30px 5px 8px; }

/* inputPop 기본 폭 — 인풋/콤보와 동일 140px. 화면에서 style="width:…" 지정 시 인라인이 우선 */
div[data-m-type="inputPop"] { width: 140px; }
.moca_ipp_btn { position: absolute; right: 1px; top: 50%; transform: translateY(-50%); width: 24px; height: 24px; border: none; background: transparent no-repeat center / 14px; background-image: url('../images/icon_search.svg'); cursor: pointer; padding: 0; z-index: 1; opacity: 0.75; transition: opacity 0.12s; }
.moca_ipp_btn::before { content: ''; position: absolute; left: 0; top: 20%; width: 1px; height: 60%; background: var(--c-border); }
.moca_ipp_btn:hover { opacity: 1; }
.moca_ipp_btn:disabled { opacity: 0.3; cursor: default; }

/* 콤보 — 화살표 SVG inline */
.moca_combo select { width: 100%; padding: 4px 8px; border: 1px solid var(--c-border-dark); border-radius: var(--radius-sm); font-size: 12px; font-family: inherit; color: var(--c-text); background: var(--c-white); outline: none; transition: border-color 0.15s, box-shadow 0.15s; box-sizing: border-box; }
.moca_combo select:focus, .moca_combo select:focus { border-color: var(--c-primary); box-shadow: 0 0 0 3px rgba(35,168,242,0.12); }
.moca_combo select:disabled { background: var(--c-bg); color: var(--c-text-muted); }

/* ─── 10-1. Checkbox Group ───────────────────────── */
.checkboxGroup { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px; }
/* 세로 정렬 — 렌더 결과가 [input][label][input][label]… 평면이라 flex-column 으로는 항목 하나가 두 줄이 된다.
   2열 grid 로 두면 한 행이 곧 한 항목이 된다(마크업 변경 없음). 2026-07-26 CAL001 첫 사용 때 발견. */
.checkboxGroup.vertical { display: grid; grid-template-columns: auto minmax(0, 1fr);
                          align-items: center; column-gap: 6px; row-gap: 7px; }
.checkboxGroup .moca_checkbox_input { -webkit-appearance: none; appearance: none; width: 14px; height: 14px; border: 1.5px solid var(--c-white); border-radius: 2px; background: var(--c-surface); cursor: pointer; flex-shrink: 0; }
.checkboxGroup .moca_checkbox_input:checked { background-color: var(--c-surface); background-image: url('../images/icon_check.svg'); background-repeat: no-repeat; background-position: center; background-size: 10px 10px; border-color: var(--c-white); }
.checkboxGroup .moca_checkbox_label { font-size: 12px; color: var(--c-text-body); cursor: pointer; }
/* 항목별 비활성(itemset 아이템 disabled) — 체크박스만 흐려지면 라벨이 살아 있어 눌러도 되는 것처럼 보인다 */
.checkboxGroup .moca_checkbox_input:disabled + .moca_checkbox_label { opacity: 0.5; cursor: default; }

/* ─── 10-2. Radio Group ──────────────────────────── */
/* .grid_toolbar_radio = 그리드 커스텀 툴바 라디오(data-m-toolbarradio) — radio 컴포넌트와 동일 스타일 공유 */
[data-m-type="radio"], .grid_toolbar_radio { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 2px; }

/* 라디오 원 — 네온 시안 도트 + 선택 시 글로우 */
:is([data-m-type="radio"], .grid_toolbar_radio) input[type="radio"] { -webkit-appearance: none; appearance: none; width: 20px; height: 20px; border: 1.5px solid var(--c-border); border-radius: 50%; background: var(--c-bg-input); cursor: pointer; flex-shrink: 0; transition: border-color 0.15s, box-shadow 0.15s; }
:is([data-m-type="radio"], .grid_toolbar_radio) input[type="radio"]:hover { border-color: var(--c-primary); }
:is([data-m-type="radio"], .grid_toolbar_radio) input[type="radio"]:checked { border-color: var(--c-primary); background: radial-gradient(circle at center, var(--c-primary) 34%, var(--c-bg-input) 37%); box-shadow: 0 0 5px rgba(0,240,255,0.55); }

/* 라벨 — 선택값 highlight (primary-light 칩 + primary 텍스트, 공통 선택 패턴) */
:is([data-m-type="radio"], .grid_toolbar_radio) label { font-size: 12px; color: var(--c-text-body); cursor: pointer; padding: 3px 10px; border-radius: 12px; border: 1px solid transparent; transition: background 0.15s, color 0.15s, border-color 0.15s; }
:is([data-m-type="radio"], .grid_toolbar_radio) label:hover { color: var(--c-primary); }
:is([data-m-type="radio"], .grid_toolbar_radio) input[type="radio"]:checked + label { background: var(--c-primary-light); border-color: var(--c-primary); color: var(--c-primary); font-weight: 600; }

/* 라디오 그룹 — 앞 라벨과 경계 구분 (좌측 구분선 + 여백) */
[data-m-type="radio"].radio_sep { margin-left: 8px; padding-left: 18px; border-left: 1px solid var(--c-border); }

/* input↔label 페어 사이 gap(4px), 그룹 간 추가 여백 */
.mr15 { margin-right: 10px; }

/* ─── 10-2b. Toggle 컴포넌트 — 라벨(선택) + ON/OFF 스위치. 외형 변형은 클래스로만 입힌다 ─── */
.moca_toggle { display: inline-flex; align-items: center; gap: 6px; }
.moca_toggle .mtg_label { font-size: 12px; font-weight: 600; color: var(--c-text-sub); white-space: nowrap; user-select: none; }
.moca_toggle .mtg_switch { position: relative; width: 44px; height: 24px; padding: 0; border: 1px solid var(--c-primary); border-radius: 12px; background: var(--c-bg-input); cursor: pointer; flex-shrink: 0; transition: background 0.2s, border-color 0.2s; }
.moca_toggle .mtg_switch:disabled { opacity: 0.5; cursor: default; }
.moca_toggle .mtg_switch.mtg_on { background: var(--c-primary); border-color: var(--c-primary); }
.moca_toggle .mtg_knob { position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; border-radius: 50%; background: var(--c-surface); border: 1px solid var(--c-primary); box-sizing: border-box; transition: transform 0.2s, border-color 0.2s; }
.moca_toggle .mtg_switch.mtg_on .mtg_knob { transform: translateX(20px); }
/* 테마 토글 변형(.moca_toggle_theme — index.html 헤더) — knob 아이콘: 꺼짐=달(다크), 켜짐=해(라이트).
   mask 로 그려 CSS 변수 색 사용(라이트/다크 자동) */
.moca_toggle_theme .mtg_knob::before { content: ''; position: absolute; inset: 2px; background-color: var(--c-primary); -webkit-mask: url('../images/icon_theme_moon.svg') no-repeat center / contain; mask: url('../images/icon_theme_moon.svg') no-repeat center / contain; transition: background-color 0.2s; }
.moca_toggle_theme .mtg_switch.mtg_on .mtg_knob::before { -webkit-mask-image: url('../images/icon_theme_sun.svg'); mask-image: url('../images/icon_theme_sun.svg'); }

/* ─── 10-4. Slider ──────────────────────────────── */
.moca_slider { display: flex; flex-direction: column; width: 100%; min-width: 0; gap: 5px; }
.moca_slider .msl_head { display: flex; align-items: center; justify-content: space-between; min-height: 22px; gap: 8px; }
.moca_slider .msl_label { color: var(--c-text-sub); font-size: 13px; font-weight: 600; }
.moca_slider .msl_value { margin-left: auto; color: var(--c-primary); font-size: 14px; font-weight: 700; }
.moca_slider .msl_input { width: 100%; height: 28px; margin: 0; padding: 0; border: 0; background: transparent; cursor: pointer; outline: none; -webkit-appearance: none; appearance: none; }
.moca_slider .msl_input::-webkit-slider-runnable-track { height: 6px; border: 1px solid var(--c-border-dark); border-radius: 999px; background: linear-gradient(to right, var(--c-primary) 0 var(--m-slider-percent), var(--c-bg-input) var(--m-slider-percent) 100%); }
.moca_slider .msl_input::-webkit-slider-thumb { width: 20px; height: 20px; margin-top: -7px; border: 2px solid var(--c-primary); border-radius: 50%; background: var(--c-surface); box-shadow: var(--shadow-sm); -webkit-appearance: none; appearance: none; }
.moca_slider .msl_input::-moz-range-track { height: 6px; border: 1px solid var(--c-border-dark); border-radius: 999px; background: var(--c-bg-input); box-sizing: border-box; }
.moca_slider .msl_input::-moz-range-progress { height: 6px; border-radius: 999px; background: var(--c-primary); }
.moca_slider .msl_input::-moz-range-thumb { width: 20px; height: 20px; border: 2px solid var(--c-primary); border-radius: 50%; background: var(--c-surface); box-shadow: var(--shadow-sm); }
.moca_slider .msl_input:focus-visible { outline: 2px solid var(--c-primary); outline-offset: 2px; border-radius: 999px; }
.moca_slider .msl_input:disabled { cursor: default; opacity: 0.55; }
.moca_slider .msl_scale { display: flex; justify-content: space-between; color: var(--c-text-muted); font-size: 12px; line-height: 1; }

/* ─── 10-3. SearchCombo ── 이중 테두리 구조: 바깥 박스(.filterheader) 안에 자체 테두리 input + 우측 콤보 화살표.
   "타이핑 가능한 콤보"를 아이콘 기호가 아니라 구조로 표시 — 구 좌측 돋보기 표식은 inputPop 의 돋보기 버튼과 혼동되어 대체(2026-07-20) */
[data-m-type="searchCombo"] { position: relative; display: inline-block; }
/* 폼 컴포넌트와 그리드 셀(.moca_searchCombo 마커)이 동일 룩을 공유 — 아래 규칙 전부 두 셀렉터에 함께 적용 */
[data-m-type="searchCombo"] .filterheader,
.moca_searchCombo .filterheader { position: relative; display: flex; align-items: center; border: 1px solid var(--c-border-dark); border-radius: var(--radius-sm); background: var(--c-header-bg); padding: 2px 24px 2px 2px; box-sizing: border-box; transition: border-color 0.15s; }
[data-m-type="searchCombo"] .filterheader:focus-within,
.moca_searchCombo .filterheader:focus-within { border-color: var(--c-primary); }
[data-m-type="searchCombo"] .filterheader:not(:has(.btn_cmb)),
.moca_searchCombo .filterheader:not(:has(.btn_cmb)) { padding-right: 2px; }
/* searchCombo 내부 input — 레이아웃만. 시각·포커스·readonly 는 박스 입력 공통 베이스 */
[data-m-type="searchCombo"] .filterheader input.moca_input,
.moca_searchCombo .filterheader input.moca_input { display: block; width: 100%; height:100%; min-width: 0; padding: 2px 6px; }
[data-m-type="searchCombo"] .filterheader input.moca_input::placeholder,
.moca_searchCombo .filterheader input.moca_input::placeholder { color: var(--c-text-muted); font-size: 11px; }

/* searchCombo 기본 폭 — 인풋/콤보와 동일 140px. 화면에서 style="width:…" 지정 시 인라인이 우선 */
div[data-m-type="searchCombo"] { width: 140px; }
.btn_cmb { position: absolute; right: 1px; top: 50%; transform: translateY(-50%); width: 22px; height: 22px; background: transparent no-repeat center / 12px; background-image: url('../images/icon_cmb_arrow.svg'); border: none; cursor: pointer; z-index: 1; padding: 0; }
.btn_cmb:hover { background-image: url('../images/icon_cmb_arrow_hover.svg'); }
[data-m-type="searchCombo"] .searchCmbTable,
.moca_searchCombo .searchCmbTable { position: absolute; top: calc(100% + 2px); left: 0; min-width: 100%; background: var(--c-header-bg); border: 1px solid var(--c-border-dark); border-radius: var(--radius-sm); box-shadow: var(--shadow-md); z-index: 6200; max-height: 200px; overflow-y: auto; }
/* 폼 SearchCombo 목록은 layout pane(overflow:auto)의 클리핑 경계를 벗어나야 한다. 좌표는 searchCombo.js가 viewport 기준으로 부여한다. */
div[data-m-type="searchCombo"] > .searchCmbTable { position: fixed; top: auto; left: auto; }
[data-m-type="searchCombo"] .searchCmbTable ul,
.moca_searchCombo .searchCmbTable ul { list-style: none; margin: 0; padding: 4px 0; }
[data-m-type="searchCombo"] .searchCmbTable li,
.moca_searchCombo .searchCmbTable li { padding: 6px 10px; font-size: 12px; color: var(--c-text-body); cursor: pointer; border-bottom: 1px solid var(--c-border); transition: background 0.1s; white-space: nowrap; }
[data-m-type="searchCombo"] .searchCmbTable li:last-child,
.moca_searchCombo .searchCmbTable li:last-child { border-bottom: none; }
[data-m-type="searchCombo"] .searchCmbTable li:hover,
.moca_searchCombo .searchCmbTable li:hover { background: var(--c-grid-hover); color: var(--c-primary); }

/* 선택(하이라이트 .on — 키보드 ↑/↓ 이동 포함)은 hover 와 달리 면으로 확실히 —
   primary 채움 배경엔 글자를 반드시 짝 변수 --c-on-primary 로(§48-8 대비 사고 재발 방지, 테마 3종 자동) */
[data-m-type="searchCombo"] .searchCmbTable li.on,
.moca_searchCombo .searchCmbTable li.on { background: var(--c-primary); color: var(--c-on-primary); font-weight: 600; }

/* ─── 10-3-1. 그리드 셀 combo/searchCombo(celltype="combo"/"searchCombo") ───────────────
   searchCombo 셀은 위 폼 컴포넌트 규칙을 .moca_searchCombo 마커로 그대로 공유 — 룩은 완전 동일.
   차이는 위치뿐: 그리드 셀의 목록(.searchCmbTable)만 스크롤에 잘리지 않게 position:fixed(좌표는 JS 가 인라인 부여).
   폼 searchCombo 루트에도 .moca_searchCombo 클래스가 있으므로 반드시 그리드 셀 범위로 한정한다. */
.moca_grid_body tbody td .moca_searchCombo, .moca_grid_body tbody td .moca_searchCombo .filterheader { height: 100%; }
.moca_grid_body tbody td .moca_searchCombo .searchCmbTable { position: fixed; top: auto; left: auto; }

/* 편집가능 combo 셀 접힘상태(_getInputSelectTag) — 클릭 전에도 콤보임이 보이도록 화살표+박스(네이티브 select 화살표와 동일 처리).
   (readonly combo 셀은 라벨 텍스트만 렌더돼 input 이 없으므로 영향 없음. searchCombo 셀은 .filterheader 구조라 미해당) */
.moca_grid_body tbody td .moca_combo > input.moca_select {
  -webkit-appearance: none; appearance: none; cursor: pointer;
  width: 100%; height: 100%;   /* 셀(컨테이너) 높이를 꽉 채워 searchCombo 셀·다른 셀과 세로 정렬 일치 */
  border: 1px solid var(--c-border-dark); border-radius: var(--radius-sm); background: var(--c-bg-input); color: var(--c-text);
  padding: 0 26px 0 8px;
  background-image: url('../images/icon_cmb_arrow.svg'),
                    linear-gradient(var(--c-border), var(--c-border)),
                    linear-gradient(var(--c-surface), var(--c-surface));
  background-repeat: no-repeat;
  background-position: right 6px center, right 22px center, right center;
  background-size: 12px 8px, 1px 60%, 22px calc(100% - 2px);
}
.moca_grid_body tbody td .moca_combo > input.moca_select:hover {
  background-image: url('../images/icon_cmb_arrow_hover.svg'),
                    linear-gradient(var(--c-border), var(--c-border)),
                    linear-gradient(var(--c-surface), var(--c-surface));
}
/* combo 셀을 클릭하면 접힘 input 이 네이티브 <select> 로 교체되는데, select 기본 높이가 접힘 input(컨테이너 100%)보다
   커서 교체 순간 셀이 1px 튀었다 → 컨테이너 높이에 맞춰 튐 제거(2026-07-21). */
.moca_grid_body tbody td .moca_combo > select.moca_select { height: 100%; box-sizing: border-box; }

/* ─── 11. 버튼 시스템 ──────────────────────────────────── */
/* 베이스: .btn 클래스로 기본 형태 적용 */
.btn { display:inline-flex; align-items:center; justify-content:center; height:28px; padding:0 14px; border:1px solid transparent; border-radius:var(--radius-sm); font-size:12px; font-weight:600; cursor:pointer; white-space:nowrap; transition:background 0.15s, filter 0.15s, border-color 0.15s, color 0.15s; }
/* 범용 솔리드 버튼 — 패널 CRUD 버튼은 아래의 상태별 우선순위 규칙으로 다시 정의한다. */
.btn_primary,  .bd_btn_edit                          { background:var(--c-primary); border:1px solid var(--c-primary); color:var(--c-bg); }
.btn_primary:hover,  .bd_btn_edit:hover               { filter:brightness(0.88); }
/* 솔리드 — 그린 (저장) */
.btn_success, .bd_btn_save, .btn_save                { background:var(--c-success); border:1px solid var(--c-success); color:var(--c-bg); }
.btn_success:hover, .bd_btn_save:hover, .btn_save:hover { filter:brightness(0.88); }
/* 솔리드 — 레드 (삭제) */
.btn_danger,  .bd_btn_del                            { background:var(--c-danger);  border:1px solid var(--c-danger);  color:#fff; }
.btn_danger:hover,  .bd_btn_del:hover                 { filter:brightness(0.88); }
/* 아웃라인 — 시안 (조회) */
.btn_outline, .btn_search                            { background:transparent; border:1px solid var(--c-primary); color:var(--c-primary); }
.btn_outline:hover, .btn_search:hover                { background:var(--c-primary-light); }
/* 아웃라인 — 중립 (신규·닫기) */
.btn_neutral, .bd_btn_new, .bd_btn_close, .btn_close { background:transparent; border:1px solid var(--c-border-dark); color:var(--c-text-sub); }
/* .btn_neutral:hover, .bd_btn_new:hover, .bd_btn_close:hover, .btn_close:hover { background:var(--c-grid-hover); border-color:var(--c-text-sub); color:var(--c-text); } */
/* 범용 뮤트 버튼 */
.btn_muted                                            { background:var(--c-grid-hover); border:1px solid var(--c-border-dark); color:var(--c-text-sub); }
.btn_muted:hover                                      { background:var(--c-bg-hover); color:var(--c-text); }

/* 패널 CRUD 우선순위(테마 공통):
   조회 단계의 신규·수정·삭제는 동등한 선택지라 모두 보더형,
   편집 단계의 저장은 주 행동이라 면 채움형, 취소는 보조 행동이라 중립 보더형이다.
   색상은 기존 의미론적 컬러(신규=Warning, 수정=Primary, 삭제=Danger, 저장=Success)를 유지한다. */
.bd_btn_new   { background:transparent; border:1px solid var(--c-warning); color:var(--c-warning); }
.bd_btn_new:hover   { background:var(--c-grid-hover); filter:none; }
.bd_btn_edit  { background:transparent; border:1px solid var(--c-primary); color:var(--c-primary); }
.bd_btn_edit:hover  { background:var(--c-primary-light);  filter:none; }
.bd_btn_del   { background:transparent; border:1px solid var(--c-danger); color:var(--c-danger); }
.bd_btn_del:hover   { background:rgba(248,113,113,0.12); border-color:var(--c-danger); color:var(--c-danger); filter:none; }
.bd_btn_cancel       { background:transparent; border:1px solid var(--c-border-dark); color:var(--c-text-sub); }
.bd_btn_cancel:hover { background:var(--c-bg-hover); border-color:var(--c-text-sub); color:var(--c-text); filter:none; }

/* 다이얼로그 확인/취소 (베이스 색상만; 크기는 컨텍스트에서 지정) */
.moca_btn_confirm { background:var(--c-success); border:1px solid var(--c-success); color:var(--c-bg); }
.moca_btn_confirm:hover { filter:brightness(0.88); }
.moca_btn_cancel  { background:var(--c-grid-hover); border:1px solid var(--c-border-dark); color:var(--c-text-sub); }
.moca_btn_cancel:hover  { background:var(--c-bg-hover); color:var(--c-text); }
/* 아이콘 버튼 */
.btn_plus { width:24px; height:24px; background:var(--c-primary) no-repeat center/14px; background-image:url('../images/icon_plus_white.svg'); border-radius:var(--radius-sm); border:none; cursor:pointer; }

/* ─── 12. 알럿 / 컨펌 ────────────────────────────────── */
.moca_alert_overlay, .moca_confirm_overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.25); display: flex; align-items: center; justify-content: center; z-index: 9000; }
.moca_alert_box, .moca_confirm_box { background: var(--c-white); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); min-width: 320px; overflow: hidden; border: 1px solid var(--c-border); }
.moca_alert_title, .moca_confirm_title { background: var(--c-bg); color: var(--c-text); font-size: 13px; font-weight: 700; padding: 12px 16px; border-bottom: 1px solid var(--c-border); }
.moca_alert_msg, .moca_confirm_msg { padding: 20px 16px; font-size: 13px; color: var(--c-text); line-height: 1.6; }
.moca_alert_btn_area, .moca_confirm_btn_area { padding: 10px 16px; border-top: 1px solid var(--c-border); display: flex; justify-content: flex-end; gap: 6px; }

/* ─── 13. 칼렌더 인풋 ────────────────────────────────── */
.moca_ica { display: inline-flex; align-items: center; gap: 3px; }

/* 입력칸 폭 — 표시형식 글자수(--m-ica-chars, inputCalendar.js 가 렌더 시 세팅)에서 계산한다.
   날짜(10자) 기준 100px 을 하한으로 두어 기존 화면은 그대로고, 시각 포함(19자 "####-##-## ##:##:##")처럼
   더 필요한 형식만 넓어진다. 종전엔 100px 고정이라 일시 기간에서 뒤 26px 가 잘렸다(2026-07-31). */
.moca_ica input { width: max(100px, calc(var(--m-ica-chars, 10) * 1ch + 14px)); height: 26px; padding: 0 7px; border: 0; text-align: center; border-radius: var(--radius-sm); font-size: 12px; outline: none; background: none; color: var(--c-text); box-sizing: border-box; transition: border-color 0.15s, box-shadow 0.15s; }
.moca_ica input:focus { border: 1px solid var(--c-primary); box-shadow: 0 0 0 2px var(--c-primary-pale); }

/* 캘린더 버튼 — Calendar SVG (스크립트 생성 class: moca_ica_btn) */
.ico_ica_btn, .ica_ica_btn, .moca_ica_btn { width: 22px; height: 22px; margin: 2px; padding: 0; font-size: 0; color: transparent; background: var(--c-primary) no-repeat center / 15px; mask: url('../images/icon_calendar.svg'); border: 1px solid var(--c-border-dark); border-radius: var(--radius-sm); cursor: pointer; flex-shrink: 0; transition: all 0.12s; }

/* ─── 14. 서치콤보 ───────────────────────────────────── */
.moca_ibn_btn { width: 20px; height: 20px; background: no-repeat center / 14px; background-image: url('../images/icon_search.svg'); border: none; cursor: pointer; }
.itemTable { position: fixed; min-width: 220px; background: var(--c-surface); border: 1px solid var(--c-primary); border-radius: var(--radius-md); box-shadow: 0 8px 24px rgba(0,0,0,0.55); z-index: 6200; overflow: hidden; }

/* 헤더: 전체선택 + 버튼 + 검색 */
.itemTable .filterheader { padding: 8px 10px 8px; background: var(--c-grid-toolbar); border-bottom: 1px solid var(--c-border); display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.itemTable .filterheader > span { display: flex; align-items: center; gap: 6px; flex: 1; }
.itemTable .filterheader .fr { display: flex; align-items: center; gap: 4px; }
.itemTable .filterheader .fr button.moca_ibn_btn { height: 24px; width: auto; padding: 0 8px; font-size: 11px; color: var(--c-text-sub); background: var(--c-bg-elevated); border: 1px solid var(--c-border-dark); border-radius: var(--radius-sm); cursor: pointer; transition: all 0.12s; white-space: nowrap; background-image: none; }
.itemTable .filterheader .fr button.moca_ibn_btn.arrow, .itemTable .filterheader .fr button.moca_ibn_btn.arrow:hover { background-image: url(../images/icon_cmb_arrow.svg); background-repeat: no-repeat; background-position: 50%; width: 24px; background-size: 16px; border: 0 }
.itemTable .filterheader .fr button.moca_ibn_btn.arrow:hover { background-image: url('../images/icon_cmb_arrow_hover.svg'); }
.itemTable .filterheader .fr button.moca_ibn_btn:hover { background: var(--c-bg-hover); color: var(--c-primary); border-color: var(--c-primary); }
/* 컬럼필터 팝업 input — 레이아웃만. 시각·포커스는 박스 입력 공통 베이스 */
.itemTable .filterheader input[type="text"].moca_input { width: 100%; height: 28px; padding: 0 8px; flex-basis: 100%; }
.itemTable .filterheader input[type="text"].moca_input::placeholder { color: var(--c-text-muted); }
.itemTable .filterheader input[type="checkbox"] { width: 14px; height: 14px; accent-color: var(--c-primary); cursor: pointer; }
.itemTable .filterheader label { font-size: 12px; color: var(--c-text-sub); cursor: pointer; }

/* 목록 ul — li 1개 약 28px 기준으로 5개 고정 */
.itemTable > ul { list-style: none; margin: 0; padding: 0; height: 140px; overflow-y: auto; }
.itemTable > ul::-webkit-scrollbar { width: 4px; }
.itemTable > ul::-webkit-scrollbar-track { background: var(--c-bg); }
.itemTable > ul::-webkit-scrollbar-thumb { background: var(--c-border-dark); border-radius: 2px; }

/* 목록 li */
.itemTable > ul > li { display: flex; align-items: center; gap: 8px; padding: 5px 12px; border-bottom: 1px solid var(--c-border); cursor: pointer; transition: background 0.1s; }
.itemTable > ul > li:last-child { border-bottom: none; }
.itemTable > ul > li:hover { background: var(--c-grid-hover); }
.itemTable > ul > li.on { background: var(--c-grid-selected); }
.itemTable > ul > li input[type="checkbox"] { width: 14px; height: 14px; accent-color: var(--c-primary); cursor: pointer; flex-shrink: 0; }
.itemTable > ul > li label { font-size: 12px; color: var(--c-text-body); cursor: pointer; flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.itemTable table { width: 100%; border-collapse: collapse; }
.itemTable tr:hover td { background: var(--c-grid-hover); }
.itemTable td { padding: 5px 10px; font-size: 12px; color: var(--c-text-body); border-bottom: 1px solid var(--c-border); cursor: pointer; }

/* ─── 15. Textarea ───────────────────────────────────── */
.moca_textarea { width:100% }
.moca_textarea > * { width: 100%; padding: 5px 8px; border: 1px solid var(--c-border-dark); border-radius: var(--radius-sm); font-size: 12px; font-family: inherit; color: var(--c-text); background: var(--c-white); outline: none; transition: border-color 0.15s, box-shadow 0.15s; box-sizing: border-box; }
.moca_textarea > * { height: 100%; resize: vertical; line-height: 1.6; align-self: flex-start; }
.moca_textarea > *:focus { border-color: var(--c-primary); box-shadow: 0 0 0 3px rgba(35,168,242,0.12); }
.moca_textarea > *[contenteditable="false"] { background: var(--c-bg); color: var(--c-text-muted); cursor: default; }

/* 실행 로그 표시용(opt-in) — 방금 한 동작 한 건만 보여주는 자리라 본문(12px)보다 키워 읽기 쉽게 한다 */
.moca_textarea.log_view > * { font-size: 14px; line-height: 1.5; }
.moca_textarea > *[contenteditable="false"]:focus { border-color: var(--c-border-dark); box-shadow: none; }

/* ─── 16. 디테일뷰 ───────────────────────────────────── */
/* 디테일뷰 본문 — form 의 moca_form_body(padding:15px) 와 통일 */
.gridDetail_body { position: absolute; inset: 0; width: 100%; height: 100%; background: var(--c-surface); z-index: 20; overflow-y: auto; box-sizing: border-box; padding: 15px; }
/* 툴바 — 그리드 공용 툴바(.moca_grid_toolbar)를 그대로 재사용(버튼 스타일·다크 override 공유).
   여기선 오버레이 상단 고정과 우측 정렬만 덧입힌다. 본문 패딩(15px)을 상쇄해 상단에 꽉 차게. */
.moca_grid_toolbar_detail { position: sticky; top: -15px; z-index: 1; margin: -15px -15px 15px; justify-content: flex-end; }
/* 활성 단(1·2·3) 표시 — 공용 툴바엔 없는 detail 전용 상태만 추가 */
.moca_grid_toolbar_detail .button.on { border-color: var(--c-primary); background-color: var(--c-primary-light); }
/* 테이블 골격 — moca_table_form 과 동일: 표 좌·상 테두리 + 셀 우·하 테두리로 격자 완성 */
.gridDetail { width: 100%; table-layout: fixed; border-collapse: collapse; border-top: 1px solid var(--c-border); border-left: 1px solid var(--c-border); margin-bottom: 15px; }
.gridDetail:last-child { margin-bottom: 0; }
/* 라벨 th — form th(grid-toolbar 배경, text-muted) 통일, 좌측정렬 */
.gridDetail th { width: 130px; background: var(--c-grid-toolbar); color: var(--c-text-muted); font-size: 12px; font-weight: 600; padding: 8px 12px; border-right: 1px solid var(--c-border); border-bottom: 1px solid var(--c-border); text-align: left; vertical-align: middle; word-break: keep-all; }
/* 값 td — form td(bg-elevated 배경) 통일, 좌측정렬 */
.gridDetail td { background: var(--c-bg-elevated); padding: 8px 10px; border-right: 1px solid var(--c-border); border-bottom: 1px solid var(--c-border); font-size: 12px; color: var(--c-text); vertical-align: middle; text-align: left; }
/* 값 셀 내부 콘텐츠도 모두 좌측정렬 — 체크박스/라디오가 가운데 정렬되던 것 교정 */
.gridDetail td > div { text-align: left; }
.gridDetail td .moca_checkbox_grid, .gridDetail td .moca_radio_grid { justify-content: flex-start; }
/* 상세뷰 안 폼 컴포넌트는 2200 Form 과 동일한 컴포넌트를 그대로 재사용(테두리/화살표/pill 동일),
   폭만 상세 레이아웃에 맞춰 셀을 채운다(폼 기본 140px 고정을 여기서만 해제). */
.gridDetail td > [data-m-type="input"], .gridDetail td > [data-m-type="combo"] { display: block; width: 100%; max-width: 420px; min-width: 0; }
.gridDetail td > [data-m-type="combo"] .moca_select, .gridDetail td > [data-m-type="combo"] select { width: 100%; }
/* 입력값 박스의 시각 스타일(테두리/배경/포커스)은 공용 .moca_input input 규칙을 재사용한다(아래 §11).
   여기선 빈 값일 때 높이가 무너지지 않도록 최소 높이만 보강. */
.gridDetail td div[contenteditable] { min-height: 24px; }

/* ─── 17. 컬럼 숨기기 팝업 ──────────────────────────── */
.PopColgroup { position: absolute; top: 38px; right: 0; background: var(--c-white); border: 1px solid var(--c-border); border-radius: var(--radius-md); box-shadow: var(--shadow-lg); z-index: 200; min-width: 400px; }
.groupListHeader { padding: 8px 12px; background: var(--c-bg); border-bottom: 1px solid var(--c-border); border-radius: var(--radius-md) var(--radius-md) 0 0; }
.ly_column { display: flex; gap: 8px; padding: 10px; }
.ly_col_cont { background: var(--c-bg); border: 1px solid var(--c-border); border-radius: var(--radius-sm); padding: 8px; }
.moca_ibn { display: flex; align-items: center; gap: 4px; }
.moca_input_color { width: 28px; height: 24px; border: 1px solid var(--c-border-dark); border-radius: var(--radius-sm); cursor: pointer; padding: 1px 2px; }
.groupList, .groupListSet { width: 100%; border-collapse: collapse; }
.groupList td, .groupListSet td { padding: 5px 6px; font-size: 12px; border-bottom: 1px solid var(--c-border); }
.groupColList li { padding: 5px 8px; font-size: 12px; color: var(--c-text); border-bottom: 1px solid var(--c-border); cursor: pointer; border-radius: var(--radius-sm); }
.groupColList li:hover { background: var(--c-grid-hover); }
.groupColList li.on { background: var(--c-primary); color: var(--c-white); }

/* ─── 18. 툴팁 ───────────────────────────────────────── */
.moca_grid_tooltip { position: fixed; background: var(--c-header-bg); color: #f1f5f9; font-size: 11px; padding: 4px 9px; border-radius: var(--radius-sm); box-shadow: var(--shadow-sm); z-index: 9999; pointer-events: none; white-space: nowrap; }

/* ─── 19. 로딩 스피너 ────────────────────────────────── */
.moca_loading { position: fixed; inset: 0; background: rgba(255,255,255,0.7); display: flex; align-items: center; justify-content: center; z-index: 8000; }
.moca_loading::after { content: ''; width: 32px; height: 32px; border: 3px solid var(--c-border); border-top-color: var(--c-primary); border-radius: 50%; animation: moca-spin 0.7s linear infinite; }
@keyframes moca-spin {
    to { transform: rotate(360deg); }
}

/* ─── 20. 전체화면 그리드 ────────────────────────────── */
.moca_grid_full_wrap { position: fixed; inset: 0; background: var(--c-white); z-index: 7000; display: flex; flex-direction: column; padding: 10px; }

/* 그리드 전체화면 오버레이 */
/* height/min-height !important: 모바일 세로스택의 pane 높이 규칙(.moca_layout_pane.moca_grid flex fill/min-height 320px 등)이
   특이성으로 이겨서 전체화면 높이가 어긋나는 것을 차단 — 전체화면은 어떤 height 소스보다 우선한다 */
.overlayer { position: fixed !important; inset: 0 !important; height: auto !important; min-height: 0 !important; z-index: 7000; background: var(--c-bg); display: flex; flex-direction: column; padding: 10px; box-sizing: border-box; }
.overlayer .moca_grid_list { flex: 1; min-height: 0; }
.overlayer .moca_scrollY_type1 { width: 100% !important; left: 0 !important; }

/* ─── 기타 ───────────────────────────────────────── */
.sc_row1 { overflow-x: auto; overflow-y: hidden; }

/* ─── 20-1. 그리드 트리 셀 (celltype="tree") ────────── */

/* 트리 셀 span 컨테이너: flex 수평 정렬.
   vertical-align:top 필수 — 기본 baseline 정렬이면 라인박스에 디센더 여백이 생겨
   행높이가 지정 셀높이(26px)를 초과(30px)하고, 가상스크롤 피치(26+1px)와 어긋나
   스크롤 끝에서 마지막 행이 잘린다. */
td[data-m-celltype="tree"] > span { display: inline-flex; vertical-align: top; align-items: center; height: 100%; white-space: nowrap; overflow: hidden; }

/* 연결선 공통 */
.moca_grid_line, .moca_grid_last, .moca_grid_midddle { display: inline-block; position: relative; width: 14px; height: 26px; flex-shrink: 0; }

/* │ 수직선 (조상 레벨 연속선) */
.moca_grid_line::before { content: ''; position: absolute; left: 50%; top: 0; bottom: 0; width: 1px; background: var(--c-border-dark); }

/* └ 마지막 자식 */
.moca_grid_last::before { content: ''; position: absolute; left: 50%; top: 0; height: 50%; width: 1px; background: var(--c-border-dark); }
.moca_grid_last::after { content: ''; position: absolute; left: 50%; top: 50%; right: 0; height: 1px; background: var(--c-border-dark); }

/* ├ 중간 자식 */
.moca_grid_midddle::before { content: ''; position: absolute; left: 50%; top: 0; bottom: 0; width: 1px; background: var(--c-border-dark); }
.moca_grid_midddle::after { content: ''; position: absolute; left: 50%; top: 50%; right: 0; height: 1px; background: var(--c-border-dark); }

/* + 접힌 부모 노드 / - 펼친 부모 노드 / • 리프 노드 */
.moca_grid_plus, .moca_grid_minus, .moca_grid_leaf { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; flex-shrink: 0; border-radius: 3px; }
.moca_grid_plus, .moca_grid_minus { cursor: pointer; background: var(--c-primary-light); border: 1px solid var(--c-primary); transition: background 0.12s; }
.moca_grid_plus:hover, .moca_grid_minus:hover { background: var(--c-primary); }

/* + 접힘 플러스 */
.moca_grid_plus::before {
	content: '';
	display: block;
	width: 8px;
	height: 8px;
	background-image:
		linear-gradient(var(--c-primary), var(--c-primary)),
		linear-gradient(var(--c-primary), var(--c-primary));
	background-size: 8px 1px, 1px 8px;
	background-repeat: no-repeat;
	background-position: center;
	transition: background-image 0.12s;
}
.moca_grid_plus:hover::before {
	background-image:
		linear-gradient(var(--c-bg), var(--c-bg)),
		linear-gradient(var(--c-bg), var(--c-bg));
}

/* - 펼침 마이너스 */
.moca_grid_minus::before {
	content: '';
	display: block;
	width: 8px;
	height: 8px;
	background-image: linear-gradient(var(--c-primary), var(--c-primary));
	background-size: 8px 1px;
	background-repeat: no-repeat;
	background-position: center;
	transition: background-image 0.12s;
}
.moca_grid_minus:hover::before {
	background-image: linear-gradient(var(--c-bg), var(--c-bg));
}

/* • 리프 노드 점 */
.moca_grid_leaf { cursor: default; }
.moca_grid_leaf::before { content: ''; display: block; width: 5px; height: 5px; border-radius: 50%; background: var(--c-text); }

/* 트리 라벨 */
td[data-m-celltype="tree"] label { margin-left: 5px; font-size: 12px; color: inherit; cursor: pointer; overflow: hidden; text-overflow: ellipsis; }
#lin_dashed { pointer-events: none; }

/* ── 모달 오버레이 ── */
.bd_overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.35); z-index: 3000; align-items: center; justify-content: center; }
.bd_overlay.on { display: flex; }

/* 비모달 팝업($p.openPop({modal:'false'})) — 가리지도, 클릭을 가로채지도 않는다.
   오버레이는 화면 전체를 덮는 요소라 투명하기만 해서는 아래 팝업·화면이 눌리지 않는다(실측) → 팝업 박스만 클릭을 받는다.
   ★ 선택자를 `.bd_overlay.bd_overlay_nomodal`(0,2,0)로 겹쳐 쓴다 — 아래쪽 테마 블록의 `.bd_overlay{background:…0.65}`(0,1,0)가
   뒤에 있어 한 클래스만으로는 배경이 그대로 어두웠다(실측: 비모달인데 화면이 딤 처리돼 "아직 모달 같다"). */
.bd_overlay.bd_overlay_nomodal { background: transparent; backdrop-filter: none; pointer-events: none; }
.bd_overlay.bd_overlay_nomodal > .bd_modal { pointer-events: auto; }

/* ── 모달 박스 ── */
/* ★ font-size 를 선언해 둔다 — 화면이 폭을 `em` 으로 줄 수 있게 하는 **기준**이다(§15-3-4).
   선언이 없으면 em 이 body 를 따라가 배율과 어긋난다. 폭도 em 이라 글자가 커지면 팝업도 함께 넓어진다
   (px 로 두면 배율 200%에서 한 줄 글자수가 절반이 되어 문장이 잘게 쪼개진다).
   좁은 화면은 max-width:96vw 가, 모바일은 mobile.css 의 전체폭 규칙이 받아 준다. */
.bd_modal { background: var(--c-white); font-size: 13px; width: 66em; max-width: 96vw; max-height: 92vh; border-radius: var(--radius-lg); box-shadow: 0 12px 40px rgba(0,0,0,0.2); display: flex; flex-direction: column; overflow: hidden; }

/* ── 모달 헤더 ── */
.bd_modal_header { flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: var(--c-bg); border-bottom: 2px solid var(--c-primary); }

/* 헤더를 잡아 팝업 이동(popup.js _enableDrag) — 드래그 중 텍스트 선택·터치 스크롤 방지 */
.bd_modal_drag { cursor: move; user-select: none; touch-action: none; }
.bd_modal_dragging { cursor: grabbing; }
/* 제목 크기 — 15px. 14px 이던 시절엔 앞의 세로 막대에 비해 글자가 작아 보였다(2026-09-01 지적).
   ★ 막대 높이(::before)가 em 이라 여기만 키우면 막대도 같은 비율로 따라온다 — 정렬은 그대로 유지된다.
     막대를 px 로 바꾸면 이 관계가 끊기므로 바꾸지 않는다. */
.bd_modal_title { font-size: 15px; font-weight: 700; color: var(--c-text); display: flex; align-items: center; gap: 8px; }
/* 제목 앞 세로바 — 글자를 따라 커진다(em). px 로 두면 배율 200%에서 글자 옆에 점처럼 남는다(§15-3-4) */
/* 제목 앞 아이콘 — 세로 막대를 접고 아이콘으로 바꿨다(2026-09-01 지시).
   막대는 "글자와 키가 같은가" 가 한눈에 보여 조금만 어긋나도 어긋나 보였다.
   ★ 높이 0.93em 은 실측값이다 — Pretendard 15px bold 로 '일정 상세' 의 actualBoundingBox 를
     재니 0.933em 이었다. 예전에는 이 값이 기기마다 틀렸는데(폰트가 달랐다), 이제 엔진이
     Pretendard 를 웹폰트로 실어 **모든 기기가 같은 글꼴**로 그리므로 이 숫자가 어디서나 맞는다.
     글꼴을 바꾸면 이 값도 다시 재야 한다(base.css 의 @import 참조). */
.bd_modal_title::before { content: ''; display: inline-block; flex: 0 0 auto; width: 0.93em; height: 0.93em; background-color: var(--c-primary); -webkit-mask: url('../images/icon_pop_title.svg') no-repeat center / contain; mask: url('../images/icon_pop_title.svg') no-repeat center / contain; }
/* 제목 아이콘(popup 의 titleIcon) — 파일 팝업에서 형식을 그림으로 말한다.
   아이콘이 붙으면 색막대는 감춘다: 제목 앞 표식은 하나여야 한다. */
.moca_pop_title_icon { flex: 0 0 auto; width: 20px; height: 20px; object-fit: contain; }
.bd_modal_title:has(.moca_pop_title_icon)::before { display: none; }
/* 모달 우상단 닫기 ✕ (popup.js 헤더 — option.closeBtn:false 면 안 그린다) */
.bd_modal_close {
	flex: 0 0 auto; width: 28px; height: 28px; padding: 0;
	border: 0; border-radius: var(--radius-sm);
	background: none; color: var(--c-text-sub);
	font-size: 15px; line-height: 1; cursor: pointer;
}
.bd_modal_close:hover { background: var(--c-hover); color: var(--c-text); }
.bd_modal_btns { display: flex; gap: 6px; }
/* 팝업 페이지 하단 버튼영역 — 우측 정렬 (선택/취소 등) */
.pop_btn_area { justify-content: flex-end; margin-top: 10px; }
/* 버튼 크기 규격은 조회버튼(.btn_search)과 동일: 높이 28px / 패딩 0 16px / 폰트 12px */
.bd_modal_btns button { height: 28px; transition: filter 0.15s, background 0.15s; padding:0 16px; border-radius: var(--radius-sm); font-size: 12px; font-weight: 600;}

/* ── FloatingButton(FAB) 컴포넌트 — data-m-type="floatingButton" ── */

/* 기본 외형: 우하단 fixed 원형. 라벨 있으면 알약형(moca_fab_ext). touch-action:none = 꾹눌러 드래그 이동용(floatingButton.js) */
.moca_fab { display: flex; position: fixed; right: 18px; bottom: 22px; z-index: 6900; width: 48px; height: 48px; border-radius: 50%; align-items: center; justify-content: center; background: var(--c-primary); border: none; color: var(--c-bg); box-shadow: var(--shadow-md); cursor: pointer; touch-action: none; user-select: none; -webkit-user-select: none; }
.moca_fab:active { filter: brightness(0.88); }
.moca_fab.moca_fab_ext { width: auto; padding: 0 18px; gap: 7px; border-radius: 24px; font-size: 13px; font-weight: 700; }
.moca_fab .moca_fab_icon { display: flex; align-items: center; justify-content: center; }
.moca_fab .moca_fab_icon_img { width: 22px; height: 22px; }
.moca_fab .moca_fab_icon_txt { font-size: 20px; line-height: 1; }
/* mask 아이콘(data-m-iconmode="mask") — 파일 아이콘을 currentColor 로 채워 색(테마·상태) 자동추종. url 은 --m-fab-icon 인라인 변수로 전달(floatingButton.js) */
.moca_fab .moca_fab_icon_mask { width: 22px; height: 22px; background: currentColor; -webkit-mask: var(--m-fab-icon) no-repeat center / contain; mask: var(--m-fab-icon) no-repeat center / contain; }
.moca_fab.moca_fab_dragging { opacity: 0.85; transform: scale(1.08); cursor: grabbing; filter: none; }

/* buttonGroup 모바일 신규 FAB 변형(§16) — 기본 숨김이 단일 소스, 표시는 mobile.css 모바일 2컨텍스트만 켠다(pcwin 은 mobile.css 자체가 꺼져 항상 숨김). 색은 신규 버튼 정체색 */
.moca_btg_fab { display: none; background: var(--c-warning); }

/* ── 모달 바디 ── */
.bd_modal_body { flex: 1; overflow-y: auto; padding: 15px; }

/* ── 폼 테이블 ── */
.moca_table_form { width: 100%; border-collapse: collapse; margin-bottom: 15px; table-layout: fixed; }
.moca_table_form th { background: var(--c-bg); padding: 8px 12px; font-size: 12px; font-weight: 600; color: var(--c-text-sub); border: 1px solid var(--c-border); text-align: right; white-space: nowrap; width: 90px; }
.moca_table_form td { padding: 6px 10px; border: 1px solid var(--c-border); }
.req_mark { color: var(--c-danger); margin-left: 2px; }

/* 필수입력(data-m-required="true") 컴포넌트 — 입력박스 자체를 시각 구분.
   공통은 테두리만(--c-req-border — base.css 다크·라이트, theme-hc.css 고대비 정의).
   배경 틴트(--c-req-bg)는 테마 정책이 달라 각 테마 파일이 준다: 다크 없음 / 라이트 theme-light-fix.css / 고대비 theme-hc.css.
   form.validate() 와 같은 규약([data-m-type][data-m-required="true"])이라 input·combo·달력 등 어떤 컴포넌트든 동일 적용.
   값 입력 여부와 무관하게 속성 기준으로 표시한다(비우면 다시 흰 배경으로 돌아오는 방식 아님). */
[data-m-type][data-m-required="true"] input:not([type="checkbox"]):not([type="radio"]):not([type="button"]),
[data-m-type][data-m-required="true"] select,
[data-m-type][data-m-required="true"] textarea,
/* 그리드 셀 required 도 동일 규약 합류 — 그리드는 required 를 input(.req)·combo(.moca_select.req)·searchCombo(.filterheader.req)
   에 .req 클래스로 마킹한다. 컴포넌트와 같은 --c-req-border 를 쓰게 해 "따로 노는" 색 불일치 제거(2026-07-21). */
.moca_grid_body tbody td input.req,
.moca_grid_body tbody td .moca_combo > input.moca_select.req,
.moca_grid_body tbody td .filterheader.req input.moca_input {
  border-color: var(--c-req-border);
}

/* ── moca_table_form: moca-table(div) 버전 — Flex 행 단위 ── */
div.moca_table_form [data-m-type="table"] { display: block; width: 100%; border-top: 1px solid var(--c-border); border-left: 1px solid var(--c-border); margin-bottom: 18px; }
div.moca_table_form [data-m-type="tbody"] { display: block; border-top: 1px solid var(--c-border);}
div.moca_table_form [data-m-type="tr"] { display: flex; align-items: stretch; min-height: 34px; }
/* 폼 th 도 5글자 기준(라벨 60px + 좌우 패딩 24px) — 검색영역 th 와 동일 기준으로 통일 */
div.moca_table_form [data-m-type="th"] { flex-shrink: 0; width: calc(var(--m-th-label-width) + 60px); background: var(--c-bg); padding: 8px 12px; font-size: 12px; font-weight: 600; color: var(--c-text-sub); border-right: 1px solid var(--c-border); border-bottom: 1px solid var(--c-border); white-space: nowrap; display: flex; align-items: center; justify-content: flex-end; }
div.moca_table_form [data-m-type="td"] { flex: 1; min-width: 0; padding: 10px; background: var(--c-white); border-bottom: 1px solid var(--c-border); display: flex; align-items: center; gap: 8px; }

/* 한 줄에 2개 필드 배치용 비율 (예: 제목 70% / 작성자 30%) */
div.moca_table_form [data-m-type="td"].td_w70 { flex: 7; }
div.moca_table_form [data-m-type="td"].td_w30 { flex: 3; }

/* 라벨 없이 내용(에디터 등)을 셀 전체에 꽉 채우는 td (CK 병합용) */
div.moca_table_form [data-m-type="td"].td_cont { align-items: stretch; padding: 0; }

/* ── moca_form_fill — 마지막 행이 카드 높이를 세로로 꽉 채운다(opt-in) ──────────────
   로그·메모처럼 "한 칸이 카드를 다 쓰는" 폼용. 종전엔 textarea 높이를 data-m-rows 로 어림잡아
   카드 아래가 남거나 모자랐다. 카드(툴바+본문) → 본문 → tbody → 마지막 tr → td → 컴포넌트까지
   높이를 이어준다. ⚠ 카드 자신이 높이를 가진 칸(레이아웃 pane)일 때만 의미가 있다 —
   내용 높이(fit) 칸에 쓰면 채울 높이가 없어 rows 기준 그대로다. */
div[data-m-type="form"].moca_form_fill { display: flex; flex-direction: column; min-height: 0; }
div[data-m-type="form"].moca_form_fill > .moca_form_body { flex: 1; min-height: 0; display: flex; flex-direction: column; }
div[data-m-type="form"].moca_form_fill > .moca_form_body > [data-m-type="tbody"] { flex: 1; min-height: 0; display: flex; flex-direction: column; }
div[data-m-type="form"].moca_form_fill [data-m-type="tr"]:last-child { flex: 1; min-height: 0; }
div[data-m-type="form"].moca_form_fill [data-m-type="tr"]:last-child > [data-m-type="td"] { align-items: stretch; }
div[data-m-type="form"].moca_form_fill [data-m-type="tr"]:last-child .moca_textarea { height: 100%; }

/* ── Form 컴포넌트 (그리드 카드/툴바와 통일) ── */
/* 카드 테두리는 "헤더(툴바)가 있는 폼"에만 적용 — 헤더 없이 레이아웃으로만 쓰는
   폼(패널 내 상세폼 등)은 이전 table 처럼 순수 레이아웃으로 렌더된다. */
div[data-m-type="form"]:has(> .moca_grid_toolbar) { border: 1px solid var(--c-border); border-radius: var(--radius-md); box-shadow: var(--shadow-md); overflow: hidden; margin-bottom: 0px; }
/* 헤더는 .moca_grid_toolbar(고정 높이 38px) 를 그대로 재사용 → 그리드와 높이 일치 */
.moca_form_body { padding: 15px;}
.moca_form_body > [data-m-type="tbody"] { margin-bottom: 0; }

/* addition 버튼 래퍼 — 버튼 자체 스타일은 innerClass 의 업무 버튼 규칙(.btn + .btn_*)을 따른다 */
.moca_grid_toolbar .mocaButton { display: inline-flex; align-items: center; }

/* ── 첨부파일 섹션 ── */
.bd_file_section { border: 1px solid var(--c-border); border-radius: var(--radius-sm); overflow: hidden; }
.bd_file_header { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; background: var(--c-grid-th); border-bottom: 1px solid var(--c-border); }
.bd_file_header span { font-size: 12px; font-weight: 700; color: var(--c-text-sub); }
.bd_file_add_btn { height: 26px; padding: 0 12px; background: var(--c-primary); color: #fff; border: none; border-radius: var(--radius-sm); font-size: 11px; font-weight: 600; cursor: pointer; transition: background 0.15s; }
.bd_file_add_btn:hover { background: var(--c-primary-dark); }
.bd_file_list_table { width: 100%; border-collapse: collapse; }
.bd_file_list_table th { background: var(--c-bg); padding: 6px 10px; font-size: 11px; font-weight: 600; color: var(--c-text-sub); border-bottom: 1px solid var(--c-border); text-align: center; }
.bd_file_list_table td { padding: 6px 10px; font-size: 12px; color: var(--c-text); border-bottom: 1px solid var(--c-border); vertical-align: middle; }
.bd_file_list_table tr:last-child td { border-bottom: none; }
.bd_file_nm_cell { cursor: pointer; color: var(--c-primary); text-decoration: underline; text-underline-offset: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 340px; }
.bd_file_nm_cell:hover { color: var(--c-primary-dark); }
.bd_new_badge { display: inline-block; background: var(--c-primary-light); color: var(--c-primary-dark); font-size: 10px; font-weight: 700; padding: 1px 5px; border-radius: 10px; margin-left: 4px; vertical-align: middle; }
.pending_row td { background: var(--c-primary-light); }
.bd_dl_btn { height: 22px; padding: 0 10px; background: var(--c-white); color: var(--c-primary); border: 1px solid var(--c-primary); border-radius: var(--radius-sm); font-size: 11px; cursor: pointer; transition: all 0.12s; }
.bd_dl_btn:hover { background: var(--c-primary); color: #fff; }
.bd_file_del_btn { height: 22px; padding: 0 10px; background: var(--c-white); color: var(--c-danger); border: 1px solid var(--c-danger); border-radius: var(--radius-sm); font-size: 11px; cursor: pointer; transition: all 0.12s; }
.bd_file_del_btn:hover { background: var(--c-danger); color: #fff; }
.bd_empty_row td { text-align: center; padding: 20px; color: var(--c-text-muted); font-size: 12px; }

/* ─── 21. Treeview ───────────────────────────────────── */
.moca_treeview { display: flex; flex-direction: column; width: 100%; overflow-y: auto; }
.moca_treeview .moca-tree, .moca_treeview .moca-tree ul { list-style: none; margin: 0; padding: 0; }

/* 자식 ul 접힘/펼침 */
.moca_treeview .tree-node > ul { display: none; }
.moca_treeview .tree-node.open > ul { display: block; }

/* 공통 행 */
.moca_treeview .tree-row { display: flex; align-items: center; gap: 5px; height: 30px; padding-right: 10px; margin: 1px 6px; border-radius: var(--radius-sm); font-size: 13px; color: var(--c-text-body); transition: background 0.12s, color 0.12s; user-select: none; }

/* 그룹 행 (자식 있음) */
.moca_treeview .has-children > .tree-row { cursor: pointer; font-weight: 600; color: var(--c-text-sub); }
.moca_treeview .has-children > .tree-row:hover { background: var(--c-primary-pale); color: var(--c-primary); }

/* 리프 행 */
.moca_treeview .tree-leaf { cursor: pointer; }
.moca_treeview .tree-leaf:hover { background: var(--c-primary-pale); color: var(--c-primary); }
.moca_treeview .tree-leaf.active { background: var(--c-primary-light); color: var(--c-primary); font-weight: 600; }
.moca_treeview .tree-leaf.active::before { content: ''; position: absolute; left: 0; }

/* 토글 / 점 아이콘 */
.moca_treeview .tree-toggle, .moca_treeview .tree-dot { width: 14px; text-align: center; font-size: 10px; color: var(--c-text-muted); flex-shrink: 0; line-height: 1; }
.moca_treeview .has-children > .tree-row .tree-toggle { color: var(--c-primary-mid); }

/* 레이블 */
.moca_treeview .tree-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 좌측 메뉴(LNB) 전용 보정 — 컴포넌트 기본값은 업무화면 기준이고, 사이드바 톤·여백은 여기서만 입힌다.
   (구조: index.html 의 #left.left > COM_LEFT.html. 종전엔 이 3가지가 컴포넌트 기본값에 섞여 있어
    업무화면에 트리를 놓으면 상단 25px 여백과 사이드바 글씨색이 그대로 따라왔다) */
.left .moca_treeview { margin-top: 25px; }
.left .moca_treeview .tree-leaf { color: var(--c-sidebar-text); }
.left .moca_treeview .tree-leaf:hover,
.left .moca_treeview .has-children > .tree-row:hover { background: var(--c-sidebar-hover); }

/* 업무화면 패널(lay_bd_detail) 안에서 트리가 남은 높이를 채우고 스크롤은 트리 내부에서 */
.lay_bd_detail_body > .moca_treeview { flex: 1; min-height: 0; }

/* ─── 21-2. VirtualTree (data-m-type="virtualTree") ─────────────
   보이는 만큼만 DOM 을 만드는 트리. 구조: [.mvt_spacer(전체높이)] + [.mvt_window(translateY 이동)].
   ★ 행 높이는 JS 가 스크롤 위치 ↔ 행 인덱스 계산에 쓰는 상수다(--mvt-row-h, 컴포넌트가 주입).
     반응형을 포함해 **어디서도 행 높이를 바꾸지 않는다** — 바꾸면 스크롤 위치가 어긋난다(SKILL PART C §57). */
.moca_vtree { position: relative; width: 100%; }
/* ── 툴바(data-m-toolbar="true") — 전체펼침·전체접기 + 건수 ──
   ★ 아이콘 모양·배치 규칙은 아래. 스크롤 구조는 툴바 유무와 무관하게 하나다(그 아래 규칙).
   ★ 아이콘은 **트리 자기 노드의 +/− 와 같은 모양**이다(선·색·모서리 동일, 크기만 크다) —
     "이 버튼이 저 +/− 를 전부 누른 것과 같다"가 눈으로 읽혀야 한다.
   ★ 버튼이 앞, 건수는 뒤 — 건수는 버튼 높이에 맞춰 세로 가운데 정렬한다(2026-08-31 지시). */
/* 루트는 스크롤하지 않는다 — 안쪽 .mvt_scroll 이 맡는다(툴바 유무 무관, JS 주석 참조).
   끌 수 있는 스크롤바 막대가 루트에 얹히므로 루트가 position:relative·overflow:hidden 이어야 한다. */
.moca_vtree { display: flex; flex-direction: column; overflow: hidden; }
.moca_vtree > .mvt_scroll { position: relative; flex: 1 1 auto; min-height: 0; overflow: auto; }
/* ★ 좌우 padding 을 두지 않는다 — 툴바의 첫 항목이 **아래 트리 행·위 조회버튼과 같은 세로선**에서
   시작해야 한다(2026-08-31 지시: 「앞 공간이 불필요하게 남으면 조회버튼 시작과 줄맞추기」).
   여백을 주면 그 값만큼 혼자 안쪽으로 밀려 한 화면 안에 시작선이 두 개가 된다(실측: 244 vs 254). */
.moca_vtree .mvt_toolbar { flex: 0 0 auto; display: flex; align-items: center; gap: 8px;
                  padding: 6px 0; background: var(--c-surface);
                  border-bottom: 1px solid var(--c-border); }
/* ★ 바탕을 **진하게** 채운다 — 아래 노드 토글(연한 바탕 + 진한 선)과 명암을 뒤집어
   「전체를 한 번에」 라는 무게를 준다(2026-08-31 지시). 종전에는 둘 다 연한 바탕이라
   1만 개 토글 사이에서 이 버튼이 묻혔다. 모양(크기·모서리·+/− 선)은 그대로 두어
   "저 토글을 전부 누르는 것"이라는 뜻은 유지한다.
   ★ 색은 주색(--c-primary)이 아니라 **스크롤바 손잡이와 같은 색**이다(2026-08-31 지시).
     주색은 조회·저장 같은 「화면이 시키는 일」의 색이라, 그 색을 쓰면 컴포넌트 자기 기능인데도
     화면이 붙인 외부 버튼처럼 읽힌다. 스크롤바·행번호처럼 **컴포넌트가 자기를 조작하는 부품**은
     같은 중성색으로 묶는다. */
.moca_vtree .mvt_btn { position: relative; display: inline-flex; align-items: center; justify-content: center;
                  width: 26px; height: 26px; padding: 0; flex-shrink: 0; cursor: pointer;
                  border: 1px solid var(--c-border-dark); border-radius: 4px;
                  background: var(--c-border-dark); transition: background 0.12s; }
.moca_vtree .mvt_btn:hover { background: var(--c-text-sub); border-color: var(--c-text-sub); }
/* +/− 는 노드 토글과 같은 방법으로 그린다(가로선·세로선 두 겹) — 색만 바탕 위라 반전한다 */
.moca_vtree .mvt_btn::before { content: ''; display: block; width: 12px; height: 12px;
                  background-image: linear-gradient(var(--c-bg), var(--c-bg)), linear-gradient(var(--c-bg), var(--c-bg));
                  background-size: 12px 1px, 1px 12px; background-repeat: no-repeat; background-position: center; }
.moca_vtree .mvt_btn_minus::before { background-size: 12px 1px, 0 0; }   /* 세로선 제거 = − */
/* 건수 — 버튼과 같은 줄에서 세로 가운데. 좁아지면 이 글자가 줄어든다(버튼은 고정) */
.moca_vtree .mvt_stat { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
                  color: var(--c-text-sub); font-size: 12px; line-height: 26px; }
.lay_bd_detail_body > .moca_vtree { flex: 1; min-height: 0; }
.moca_vtree .mvt_spacer { width: 1px; }
.moca_vtree .mvt_window { position: absolute; top: 0; left: 0; min-width: 100%; will-change: transform; }
.moca_vtree .mvt_row { display: flex; align-items: center; height: var(--mvt-row-h, 30px); width: max-content; min-width: 100%; padding-right: 10px; box-sizing: border-box; cursor: pointer; user-select: none; }
/* ★ 행번호 — 그리드와 같은 자리·같은 성격(맨 왼쪽, 우측정렬, 고르는 대상이 아님).
   position:sticky 로 가로 스크롤에도 왼쪽에 남는다 — 행이 max-content 라 옆으로 밀릴 수 있는데
   그때 번호까지 사라지면 "지금 몇 번째인지"를 다시 알 수 없다.
   배경을 깔아야 sticky 로 겹칠 때 뒤 글자가 비쳐 보이지 않는다. */
.moca_vtree .mvt_no { position: sticky; left: 0; z-index: 1; flex: 0 0 auto;
                  width: var(--mvt-no-w, 34px); padding-right: 6px; margin-right: 6px;
                  box-sizing: border-box; overflow: hidden;
                  font-size: 11px; color: var(--c-text-muted);
                  background: var(--c-surface); border-right: 1px solid var(--c-border);
                  align-self: stretch; user-select: none;
                  /* ★ text-align 이 아니라 flex 로 오른쪽에 붙인다 — 내용이 칸보다 넓을 때
                     text-align:right 는 기준점을 잡지 못해 오른쪽으로 삐져나간다(실측 3px).
                     그러면 축소 기준(transform-origin:right)도 함께 어긋나 끝자리가 잘린다. */
                  display: flex; align-items: center; justify-content: flex-end; }
/* 자릿수가 칸을 넘으면 가로로 눌러 넣는다(그리드 행번호와 같은 방식) — 오른쪽 끝을 고정하고 줄인다 */
.moca_vtree .mvt_no i { display: block; flex: 0 0 auto; transform-origin: right center; font-style: normal; white-space: nowrap; }
.moca_vtree .mvt_row:hover  .mvt_no { background: var(--c-bg-hover); }
.moca_vtree .mvt_row.active .mvt_no { background: var(--c-primary-light); color: var(--c-primary); font-weight: 700; }
.moca_vtree .mvt_row:hover { background: var(--c-primary-pale); }
.moca_vtree .mvt_row.active { background: var(--c-primary-light); }

/* depth 라인 — 조상 레벨의 수직선(│) + 자기 레벨의 꺾임(├ / └) */
.moca_vtree .mvt_line, .moca_vtree .mvt_elbow, .moca_vtree .mvt_pad { position: relative; width: var(--mvt-indent, 16px); height: 100%; flex-shrink: 0; }
.moca_vtree .mvt_line_on::before { content: ''; position: absolute; left: 50%; top: 0; bottom: 0; width: 1px; background: var(--c-border-dark); }
.moca_vtree .mvt_elbow::before { content: ''; position: absolute; left: 50%; top: 0; bottom: 0; width: 1px; background: var(--c-border-dark); }
.moca_vtree .mvt_elbow_last::before { bottom: 50%; }
.moca_vtree .mvt_elbow::after { content: ''; position: absolute; left: 50%; top: 50%; right: 0; height: 1px; background: var(--c-border-dark); }

/* +(접힘) / −(펼침) 토글 · •(리프) — 그리드 트리셀과 같은 시각 언어(CSS 로 그려 별도 이미지 없음) */
.moca_vtree .mvt_toggle, .moca_vtree .mvt_dot { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; flex-shrink: 0; border-radius: 3px; }
.moca_vtree .mvt_toggle { cursor: pointer; background: var(--c-primary-light); border: 1px solid var(--c-primary); transition: background 0.12s; }
.moca_vtree .mvt_toggle:hover { background: var(--c-primary); }
.moca_vtree .mvt_toggle::before { content: ''; display: block; width: 8px; height: 8px; background-image: linear-gradient(var(--c-primary), var(--c-primary)), linear-gradient(var(--c-primary), var(--c-primary)); background-size: 8px 1px, 1px 8px; background-repeat: no-repeat; background-position: center; transition: background-image 0.12s; }
.moca_vtree .mvt_minus::before { background-size: 8px 1px, 0 0; }
.moca_vtree .mvt_toggle:hover::before { background-image: linear-gradient(var(--c-bg), var(--c-bg)), linear-gradient(var(--c-bg), var(--c-bg)); }
.moca_vtree .mvt_dot::before { content: ''; display: block; width: 5px; height: 5px; border-radius: 50%; background: var(--c-text-muted); }

/* 라벨 — 그룹은 살짝 강조, 선택행은 primary */
.moca_vtree .mvt_label { margin-left: 6px; font-size: 13px; color: var(--c-text-body); white-space: nowrap; }
.moca_vtree .mvt_group > .mvt_label { font-weight: 600; color: var(--c-text-sub); }
.moca_vtree .mvt_row.active > .mvt_label { color: var(--c-primary); font-weight: 600; }

/* ─── 22. 달력 팝업 ──────────────────────────────────── */

/* 컨테이너 — 팝업의 position 기준점 */
div[data-m-type="inputCalendar"], div[data-m-type="inputMultiCalendar"] { position: relative; border: 1px solid var(--c-border-dark); border-radius: var(--radius-sm); background: var(--c-bg-input); }

/* inputCalendar 기본 폭 — 인풋/콤보와 동일 140px 이 하한이고, 표시형식이 길면(일시 등) 그만큼 넓어진다
   (+32px = 달력버튼·구분선 몫). 내부 input 이 남는 폭을 흡수. 화면에서 style="width:…" 지정 시 인라인이 우선 */
div[data-m-type="inputCalendar"] { width: max(140px, calc(var(--m-ica-chars, 10) * 1ch + 46px)); }
div[data-m-type="inputCalendar"] > input.moca_input { flex: 1 1 auto; min-width: 0; }

/* multiCalendar 내부 div: flex로 한 줄 정렬 (whitespace gap 제거) */
div[data-m-type="inputMultiCalendar"] > div { display: flex; align-items: center; }

/* 폼 달력 컴포넌트 우측 버튼 — combo와 같이 구분선 뒤에 기능 아이콘을 둔다.
   기존 element mask를 풀고 pseudo-element에 달력 mask를 적용해 구분선이 mask에 잘리지 않게 한다. */
div[data-m-type="inputCalendar"] > .moca_ica_btn,
div[data-m-type="inputMultiCalendar"] > div > .moca_ica_btn {
    position: relative;
    width: 24px; height: 26px; margin: 0; padding: 0;
    border: 0; border-radius: 0; background: transparent; mask: none;
}
div[data-m-type="inputCalendar"] > .moca_ica_btn::before,
div[data-m-type="inputMultiCalendar"] > div > .moca_ica_btn::before {
    content: ''; position: absolute; left: 0; top: 20%; width: 1px; height: 60%; background: var(--c-border);
}
div[data-m-type="inputCalendar"] > .moca_ica_btn::after,
div[data-m-type="inputMultiCalendar"] > div > .moca_ica_btn::after {
    content: ''; position: absolute; inset: 5px;
    background: var(--c-primary);
    -webkit-mask: url('../images/icon_calendar.svg') no-repeat center / contain;
    mask: url('../images/icon_calendar.svg') no-repeat center / contain;
}
div[data-m-type="inputCalendar"] > .moca_ica_btn:disabled,
div[data-m-type="inputMultiCalendar"] > div > .moca_ica_btn:disabled { opacity: 0.3; cursor: default; }

/* 단일 달력 팝업 */
/* ★ 달력 드롭다운 치수는 em — 글자만 배율을 타고 상자가 px 로 고정이면 날짜가 칸에 꽉 껴 답답해진다(§15-3-4).
   font-size 선언이 em 의 기준이다. 21.67em×12=260px 라 100% 에서는 종전과 같은 모양. */
.moca_calendar { position: absolute; top: calc(100% + 2px); left: 0; z-index: 1000; background: var(--c-white); border: 1px solid var(--c-border-dark); border-radius: var(--radius-md); box-shadow: var(--shadow-md); font-size: 12px; width: 21.67em; }

/* 기간 달력 팝업 */
.moca_calendar_fromto { position: absolute; top: calc(100% + 2px); left: 0; z-index: 1000; background: var(--c-white); border: 1px solid var(--c-border-dark); border-radius: var(--radius-md); box-shadow: var(--shadow-md); display: flex; min-width: 45em; }

/* ── 헤더 ── */
.moca_calendar_header { display: flex; align-items: center; justify-content: space-between; padding: 5px 6px; background: var(--c-surface); border-radius: var(--radius-md) var(--radius-md) 0 0; border-bottom: 1px solid var(--c-border-dark); gap: 2px; }
.moca_calendar_header button { background: none; border: 1px solid transparent; color: var(--c-primary); cursor: pointer; font-size: 11px; padding: 0.18em 0.55em; border-radius: var(--radius-sm); white-space: nowrap; transition: background 0.12s; }
.moca_calendar_header .moca_calendar_btn_prev, .moca_calendar_header .moca_calendar_btn_next { border: 1px solid var(--c-border-dark); color: var(--c-text-sub); }
.moca_calendar_header button:hover { background: var(--c-primary-light); }
.moca_calendar_dynamic { display: flex; gap: 3px; align-items: center; flex: 1; justify-content: center; }
.moca_calendar_year, .moca_calendar_month { font-size: 12px; font-weight: 700; color: var(--c-text); }

/* ── 바디 ── */
.moca_calendar_body { padding: 4px; }
.moca_calendar_body table { border-collapse: collapse; width: 100%; }
.moca_calendar_body th { height: 1.83em; text-align: center; font-size: 11px; font-weight: 600; color: var(--c-text-sub); padding: 0; }
.moca_calendar_body th.sun { color: var(--c-danger); }
.moca_calendar_body th.sat { color: #2563eb; }
.moca_calendar_body td { height: 2.17em; text-align: center; padding: 1px; }
.moca_calendar_body td button { width: 100%; height: 100%; background: none; border: none; cursor: pointer; font-size: 11px; color: var(--c-text); border-radius: var(--radius-sm); transition: background 0.1s, color 0.1s; display: inline-flex; align-items: center; justify-content: center; }
.moca_calendar_body td:not(.moca_calendar_date_disabled):hover button { background: var(--c-primary-light); color: var(--c-primary); }
.moca_calendar_holiday button { color: var(--c-danger) !important; }
.moca_calendar_date_disabled { opacity: 0.35; pointer-events: none; }

/* 선택가능 일자범위(data-m-datemin/datemax) 밖 — 이동·오늘 버튼도 갈 곳이 없으면 잠긴다 */
.moca_calendar_header button:disabled, .moca_calendar_btn_today:disabled { opacity: 0.35; cursor: default; pointer-events: none; }
.moca_calendar_today { border-radius: var(--radius-sm); background : var(--c-black) !important; }
.moca_calendar_today button { background: var(--c-bg); color: var(--c-primary); font-weight: 700; }
.moca_calendar_selected { outline: 1px solid var(--c-primary); outline-offset: -1px; border-radius: var(--radius-sm); }
.moca_calendar_selected button { color: var(--c-primary) !important; font-weight: 700; }

/* ── 시각 선택 줄 (data-m-datetype 에 시각이 포함된 달력에만 생성) ── */
.moca_calendar_time { display: flex; align-items: center; justify-content: center; gap: 2px; padding: 5px 6px; border-top: 1px solid var(--c-border); background: var(--c-surface); }
.moca_calendar_time .cmb_hhmmss { height: 2em; padding: 0 0.33em; font-size: 12px; color: var(--c-text); background: var(--c-bg-input); border: 1px solid var(--c-border-dark); border-radius: var(--radius-sm); cursor: pointer; }
.moca_calendar_time .cmb_hhmmss:focus { border-color: var(--c-primary); outline: none; }
.moca_cal_timeunit { font-size: 12px; font-style: normal; color: var(--c-text-sub); margin-right: 6px; }

/* 기간 달력의 시각 선택 — 좌·우 달력 아래 입력칸 밑에 붙는다 */
.hhmmss_box { display: flex; align-items: center; justify-content: center; gap: 2px; padding: 0 0 4px; }
.hhmmss_box .cmb_hhmmss { height: 22px; padding: 0 3px; font-size: 12px; color: var(--c-text); background: var(--c-bg-input); border: 1px solid var(--c-border-dark); border-radius: var(--radius-sm); cursor: pointer; }

/* ── 푸터 ── */
.moca_calendar_footer { display: flex; justify-content: space-between; align-items: center; padding: 4px 6px 5px; border-top: 1px solid var(--c-border); gap: 4px; }
.moca_calendar_footer .lta, .moca_calendar_footer .rta { display: flex; gap: 4px; }
/* height 고정 금지 — 글자 배율(fontScale)을 쓰는 프로젝트에서 글자(18.7px)가 22px 버튼을
   넘쳐 헤더 버튼과 일관성이 깨졌다(2026-08-10). min-height 라 기본 테마는 22px 그대로,
   글자가 커지면 버튼도 따라 커진다 */
.moca_calendar_btn_today, .moca_calendar_btn_cancel, .moca_calendar_btn_ok { min-height: 1.83em; padding: 0.25em 0.75em; border-radius: var(--radius-sm); font-size: 11px; font-weight: 600; cursor: pointer; border: 1px solid; transition: all 0.12s; line-height: 1; }
.moca_calendar_btn_today { background: var(--c-primary-light); color: var(--c-primary); border-color: var(--c-primary); }
.moca_calendar_btn_today:hover { background: var(--c-primary); color: var(--c-bg); }
.moca_calendar_btn_cancel { background: transparent; border: 1px solid var(--c-border-dark); color: var(--c-text-sub); }
.moca_calendar_btn_cancel:hover { background: var(--c-bg); }
.moca_calendar_btn_ok { background: var(--c-success); color: var(--c-bg); border-color: var(--c-success); }
.moca_calendar_btn_ok:hover { background: var(--c-primary-dark); }

/* ─── 23-0. 화면 루트 래퍼 & 타이틀 바 ─────────────────── */
.page_wrap { padding: 15px; gap: 15px; display: flex; flex-direction: column; min-height: 100%; height:max-content; }
.moca_layout .page_wrap:not(.moca_tab_panel > .page_wrap){padding:0;}
/* 좌우분할(LR) 레이아웃: MDI 영역을 스크롤 없이 꽉 채움 (height 고정) */
.page_wrap:has(.bd_lr_wrap) { min-height: 0; height: 100%; overflow: hidden; }

/* 상하분할(bd_split) 레이아웃도 MDI를 스크롤 없이 꽉 채움 (그리드 fauto가 남은 높이 채움) */
.page_wrap:has(.bd_split_wrap) { min-height: 100%;overflow: hidden; }

/* 탭 컴포넌트를 담은 화면: 높이를 꽉 채워 고정 → 스크롤은 탭 패널 안에서만 발생 */
.page_wrap:has(.moca_tab) { min-height: 0; height: 100%; overflow: hidden; }

/* 레이아웃 컴포넌트를 담은 화면: 높이를 꽉 채워 고정(분할이 남은 높이를 채우도록) */
.page_wrap:has([data-m-layout]) { min-height: 0; height: 100%; overflow: hidden; }

/* ── 프레임 pane/탭패널 → 안쪽 화면 높이 전달: % 대신 flex 체인 (iOS WebKit 대응, 2026-07-22) ──
   안쪽 화면의 page_wrap 은 min-height:100%(%참조)로 부모 높이를 받는데, WebKit(아이폰 Safari/Chrome)은
   flex 로 자란 부모에 대한 자식 %높이 해석이 Blink 와 달라 이 링크가 끊긴다 — 실기기에서만
   프레임 속 그리드(TPL009 트리)가 주저앉고 PC/에뮬레이션은 정상이던 원인.
   경계를 flex 컨테이너로 만들고 안쪽 page_wrap 을 flex:1 1 0(부모 크기만큼 정확 채움)으로 이으면
   %가 개입하지 않는다. 내용이 더 크면 page_wrap 밖으로 흘러나와(overflow 가시) 조상 스크롤러가 받는다.
   ⚠ 스코프 주의: [data-m-type="frame"] 전체로 걸면 MDI 패널(역시 frame)까지 매치되고,
   basis:auto(shrink 0)로 걸면 그리드 '내용' 높이가 바닥이 되어 화면이 늘어난다 — 실제 회귀 사례.
   반드시 '레이아웃 pane 인 프레임'과 탭패널만, basis 0 으로 잇는다.
   min-height:0 = 기본 min-height:100%(%) 를 끄는 것(% 를 아예 안 쓰는 게 목적). */
.moca_layout_pane[data-m-type="frame"]:has(> .page_wrap),
.moca_tab_panel:has(> .page_wrap) { display: flex; flex-direction: column; }
.moca_layout_pane[data-m-type="frame"] > .page_wrap,
.moca_tab_panel > .page_wrap { flex: 1 1 0; min-height: 0; }

/* ── Layout 컴포넌트 (data-m-layout) ─────────────────────────────
   방향(v=세로/h=가로) + 비율을 layout.js 가 각 칸 flex-grow 인라인으로 부여한다.
   여기서는 flex 컨테이너/칸 기본 골격과 반응형·고정크기 규칙만 담당. */
.moca_layout { display: flex; flex: 1; min-width: 0; min-height: 0; --m-layout-gap: 15px; gap: var(--m-layout-gap); }
.moca_layout.moca_layout_row { flex-direction: row; }     /* h = 가로 */
.moca_layout.moca_layout_col { flex-direction: column; }  /* v = 세로 */
/* 칸(pane): basis 0 + shrink 로 비율 분할, grow 값은 layout.js 가 --m-grow 로 전달.
   (--m-grow 기본 1 은 미지정 시 폴백). 숫자·auto 칸 공통. auto=남은공간(grow 1). */
.moca_layout > .moca_layout_pane { flex: var(--m-grow, 1) 1 0; min-width: 0; min-height: 0; overflow: auto; }
/* 고정 px 칸(예: 200px): 주축(row=폭/col=높이) 기준 고정. basis 는 layout.js 가 --m-basis 로 전달.
   (인라인 flex 가 아니라 변수인 이유 = 모바일 미디어쿼리가 인라인에 안 눌리게 하기 위함) */
.moca_layout > .moca_layout_pane.moca_layout_pane_fixed { flex: 0 0 var(--m-basis, auto); }
/* fit 칸: 내용 높이로 hug(비율/fill 아님). 헤더·검색영역처럼 컨텐츠만큼만 차지 → 여백 안 남음. */
.moca_layout > .moca_layout_pane.moca_layout_pane_fit { flex: 0 0 auto; }
/* 접힌 그리드는 툴바 높이만 차지한다 — 그래야 같은 칸의 형제가 그만큼 넓어진다.
   이게 없으면 접어도 빈 자리만 남아 "접는 의미" 가 없다(비율 칸은 flex 가 고정이라
   내용이 줄어도 크기를 그대로 유지한다). grid._foldGrid 와 한 세트다. */
.moca_layout > .moca_layout_pane[data-m-type="grid"].fold { flex: 0 0 auto; overflow: visible; }
/* bare 래퍼 칸(그룹핑용 순수 div): 레이아웃과 '같은' gap(--m-layout-gap)을 물려받는 flex 스택.
   → 칸 안에 쌓은 블록도 칸 사이와 동일한 여백. gap 은 컨테이너 단위라 중첩돼도
     같은 거터에 두 번 겹치지 않음(=30px 중첩 없음). margin 은 gap 과 더해지지 않게 0 으로. */
.moca_layout > .moca_layout_pane.moca_layout_pane_stack { display: flex; flex-direction: column; gap: var(--m-layout-gap); overflow: visible; }
.moca_layout > .moca_layout_pane.moca_layout_pane_stack > * { margin: 0; }
/* 고정크기 모드(data-m-width/height): flex 해제, 넘치면 부모(.page_wrap)가 스크롤.
   크기값은 layout.js 가 --m-fixed-w/h 변수로 넘긴다(인라인 style 아님 — 위 --m-basis 와 같은 이유:
   인라인이면 아래 창팝업 규칙 같은 컨텍스트별 재정의가 못 이겨 조용히 무시된다) */
.moca_layout.moca_layout_fixed { flex: none; width: var(--m-fixed-w, auto); height: var(--m-fixed-h, auto); }
.page_wrap:has(> .moca_layout_fixed) { overflow: auto; }
/* 모바일(≤768px): 방향 무관 세로 스택 + 전체 스크롤 + 카드/차트/그리드 최소 높이
   → 데스크톱은 "스크롤 없이 꽉", 모바일은 "세로로 쌓이고 페이지가 스크롤"(자연스러운 반응형). */
@media (max-width: 768px) {
	.moca_layout.moca_layout_row, .moca_layout.moca_layout_col { flex-direction: column; }
	.moca_layout > .moca_layout_pane { flex: 0 0 auto; overflow: visible; }
	/* 고정 px 칸도 모바일에선 내용 높이로 풀어 세로 스택 일관성 유지(가로 고정폭이 남지 않게) */
	.moca_layout > .moca_layout_pane.moca_layout_pane_fixed { flex: 0 0 auto; }
	/* 레이아웃 화면은 고정높이/overflow:hidden 해제 → 쌓인 내용이 잘리지 않고 스크롤됨 */
	.page_wrap:has([data-m-layout]) { height: auto; min-height: 100%; overflow: visible; }
	/* 단, 중첩 레이아웃의 pane 래퍼(page_wrap 이면서 pane)는 min-height:100% 제외 —
	   부모 높이가 확정(flex 체인)이면 이 100% 가 살아나 pane 이 레이아웃 전체 높이를 먹고
	   형제 칸과 합쳐 초과 스크롤을 만든다(TPL010 탭 회귀 실측 2026-07-22). 최상위 page_wrap 만 100% 필요. */
	.page_wrap.moca_layout_pane { min-height: 0; }
	/* 차트·그리드가 내용높이 컨텍스트에서 찌그러지지 않도록 최소 높이 확보 */
	.moca_layout .widget_chart_body { min-height: 200px; }
	/* 그리드로 이어지는 칸은 남은 뷰포트 높이를 채워 화면 바닥에 딱 붙임(스티키풋터 flex 패턴).
	   (구) height:60vh 고정 → 항상 페이지 스크롤 발생 + iOS 구식 vh(큰 뷰포트 기준)가
	   svh 기반 셸보다 툴바 높이만큼 커서 하단이 잘려 보이던 원인. vh 를 아예 쓰지 않는다.
	   그리드 2개 이상 스택이면 남은 높이를 나눠 갖고, 부족하면 min-height 로 페이지 스크롤.
	   flex-basis 는 반드시 0(확정값) — auto 로 자란 높이는 자식에게 indefinite 취급이라
	   프레임·탭 안에 로드된 화면의 page_wrap min-height:100% 가 무시돼 그리드가 주저앉는다.
	   (칸이 내용보다 작아지는 건 automatic minimum size(overflow:visible)가 막아준다.) */
	.moca_layout > .moca_layout_pane:has(.moca_grid) { flex: 1 1 0; }
	.moca_layout > .moca_layout_pane.moca_grid { flex: 1 1 0; height: auto; /*min-height: 320px;*/ }
	/* 달력(scheduleCalendar) — 남은 높이는 채우되(grow 1) 내용보다 작아지지는 않는다(shrink 0).
	   모바일 세로스택 기본값(0 0 auto)이면 남는 높이를 안 채워 바닥에 빈 틈이 남는다. */
	.moca_layout > .moca_layout_pane:has(.moca_schcal) { flex: 1 0 auto; }
	.moca_layout > .moca_layout_pane.moca_schcal { flex: 1 0 auto; height: auto; }
	/* 화면이 px 토큰(v…:300px 등)으로 그리드 칸 높이를 명시했으면 모바일에서도 그 높이 유지.
	   (일반 고정 칸은 모바일에서 내용 높이로 풀지만, 그리드는 내용 높이가 없어 풀면 붕괴)
	   fill(1 1 0) 규칙보다 특이성이 높아(클래스 4개) 항상 이긴다. */
	.moca_layout > .moca_layout_pane.moca_layout_pane_fixed.moca_grid { flex: 0 0 var(--m-basis, auto); }
	/* 칸 마커 data-m-mobileheight="360": 모바일 세로스택에서 이 칸만 높이 고정(--m-mbh 는 layout.js 가 세팅).
	   h(가로) 레이아웃은 토큰이 '폭'이라 모바일 높이를 못 명시하는 공백을 메우는 마커 — PC 는 완전 무영향.
	   클래스 반복으로 특이성(0,4,0)을 만들어 fill·px토큰 규칙 어느 쪽보다도 뒤에서 이긴다. */
	.moca_layout > .moca_layout_pane.moca_layout_pane[data-m-mobileheight] { flex: 0 0 auto; height: var(--m-mbh); min-height: 0; }
}

/* ── 레이아웃 골격 미리보기(개발용) ──────────────────────────────────────────
   data-m-layout-part="root" 레이아웃마다 좌측 하단에 FAB 1개(layout.js 주입 —
   실체는 floatingButton 컴포넌트, 이 class 가 .moca_fab 기본 외형을 전부 덮는다).
   클릭(또는 Alt+L)하면 '그 루트 레이아웃'에 .moca_layout_debug 토글 → 화면별 독립.
   켜지면 그 루트 하위 레이아웃/칸에 가이드라인 + 스펙 라벨을 그리고 실제 컴포넌트는 감춘다. */
.moca_layout:has(> .moca_layout_debug_fab) { position: relative; }   /* FAB 절대배치 기준 */
/* 좌측 하단에 둔다(2026-09-01 지시 — 종전 우측 하단).
   ★ right:auto 를 함께 적는다 — .moca_fab 기본값(right:18px)이 남아 있으면 left 와 함께 걸려
     over-constrained 가 되고, 그때 어느 쪽이 이기는지는 쓰기 방향(LTR/RTL)에 맡겨진다. */
.moca_layout_debug_fab {
	position: absolute; left: -14px; right: auto; bottom: -14px; z-index: 10;
	width: 40px; height: 40px; padding: 0;
	display: inline-flex; align-items: center; justify-content: center;
	border: 1px solid var(--c-border); border-radius: 50%;
	background: var(--c-surface); color: var(--c-text-muted);
	box-shadow: var(--shadow-sm); cursor: pointer; opacity: 0.55;
	transition: opacity .15s, color .15s, background .15s, box-shadow .15s, border-color .15s;
}
.moca_layout_debug_fab:hover { opacity: 1; box-shadow: var(--shadow-md); color: var(--c-primary); border-color: var(--c-primary); }
.moca_layout_debug_fab:active { filter: none; }                                 /* .moca_fab:active 밝기효과 상쇄 — 기존 버튼 모양 유지 */
.moca_layout_debug_fab .moca_fab_icon_mask { width: 20px; height: 20px; }       /* 구 인라인 SVG 와 동일한 20px */
/* 탭패널 안에 로드된 화면의 FAB: -14px 돌출을 안쪽 배치로 전환.
   MDI 에서는 mdi_panel padding 15px 가 돌출을 흡수하지만, 탭패널은 overflow:auto 경계 +
   padding 0 이라 돌출분이 그대로 1~2px 스크롤 초과를 만든다(실측 2026-07-22).
   스크롤 컨테이너 안에서 절대배치 요소를 경계 밖으로 내밀면 안 된다 — 스크롤 영역이 된다. */
.moca_tab_panel .moca_layout_debug_fab { left: 0; bottom: 0; }
.moca_layout_debug_fab.on { opacity: 1; color: #06131a; background: var(--c-primary); border-color: var(--c-primary); box-shadow: 0 0 10px var(--c-primary); }

/* 골격 라벨/화살표 가이드(실제 요소) — 평소엔 숨김. 켜졌을 때만 표시. */
.moca_layout_tag, .moca_layout_guide { display: none; }

/* 켜짐 상태 — 칸/레이아웃 가이드라인
   (.moca_layout_debug 는 루트 레이아웃 자신에 붙으므로, 루트 자신 + 하위 레이아웃 둘 다 매치) */
.moca_layout.moca_layout_debug,
.moca_layout_debug .moca_layout { position: relative; outline: 1px solid rgba(0,240,255,.5); outline-offset: -1px; }
.moca_layout_debug .moca_layout_pane { position: relative; outline: 1px dashed rgba(0,240,255,.75); outline-offset: -1px; background: rgba(0,240,255,.06); }
/* 실제 컴포넌트(잎)만 감춤 — 중첩 레이아웃/칸(구조)·스펙 라벨·화살표 가이드는 유지 */
.moca_layout_debug .moca_layout_pane > *:not(.moca_layout):not(.moca_layout_pane):not(.moca_layout_tag):not(.moca_layout_guide) { visibility: hidden; }

/* 레이아웃 스펙 라벨(vfit:auto / h3:7 …) — 좌상단, 크고 잘 보이게. hover 트리거. */
.moca_layout_debug .moca_layout_tag {
	display: inline-block; position: absolute; top: 0; left: 0; z-index: 5; visibility: visible;
	padding: 4px 12px; font: 700 14px/1.4 ui-monospace, monospace;
	color: #06131a; background: var(--c-primary); border-radius: 0 0 8px 0;
	box-shadow: 0 0 10px rgba(0,240,255,.55); pointer-events: auto; user-select: none;
}
/* 라벨 hover — 주황(현재 초점 색)으로 전환 + 글로우 */
.moca_layout_debug .moca_layout_tag:hover { font:700 25px/1.4 ui-monospace, monospace }

/* 라벨 hover → 그 레이아웃의 '직계 칸마다' 솔리드 주황으로 채움(칸별 분리 강조).
   ::after 오버레이(z-2, 불투명)라 칸 안 컨텐츠/중첩구조를 완전히 덮어 다른 게 안 비침.
   형제결합자(~) 라 그 레이아웃의 직계 칸만 → 레벨 격리(안쪽 라벨 hover=안쪽 칸만). */
.moca_layout_debug .moca_layout_tag:hover ~ .moca_layout_pane { outline-color: #602F32; }
.moca_layout_debug .moca_layout_tag:hover ~ .moca_layout_pane::after {
	content: ''; position: absolute; inset: 0; z-index: 2; pointer-events: none; background: #602F32;
}

/* 칸 토큰 라벨(fit / auto / 3 / 200px …) — 각 칸 중앙. 평소 숨김, 좌상단 라벨 hover 시에만 표시. */
.moca_layout_debug .moca_layout_pane::before {
	content: attr(data-m-token);
	position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
	z-index: 4; visibility: visible; opacity: 0; transition: opacity .12s;
	padding: 3px 12px; font: 700 13px/1.5 ui-monospace, monospace;
	color: var(--c-primary); background: rgba(6,19,26,.92);
	border: 1px solid rgba(0,240,255,.5); border-radius: 6px; pointer-events: none; white-space: nowrap;
}
/* 좌상단 라벨에 hover → 같은 레이아웃의 직계 칸 토큰만 표시(형제결합자, 레벨 격리) */
.moca_layout_debug .moca_layout_tag:hover ~ .moca_layout_pane::before { opacity: 1; }

/* 칸 크기 화살표 가이드 — 부모 레이아웃 방향에 맞춰 점선+양끝 삼각촉이 칸 끝까지.
   화살촉은 CSS border-삼각형(폰트 글리프는 글자마다 여백이 달라 크기 제각각 → 기하 삼각형으로 통일).
   크기 조절: --m-arrow-w(밑변 절반) / --m-arrow-d(높이·깊이). 모든 화살촉 동일.
   중앙 토큰 칩(z-4)이 선을 끊어 ◄──[값]──►. 평소 숨김, 라벨 hover 시에만 표시. */
:is(.moca_layout_debug.moca_layout_row, .moca_layout_debug .moca_layout_row) > .moca_layout_pane > .moca_layout_guide,
:is(.moca_layout_debug.moca_layout_col, .moca_layout_debug .moca_layout_col) > .moca_layout_pane > .moca_layout_guide {
	display: block; position: absolute; z-index: 3; opacity: 0; transition: opacity .12s; pointer-events: none;
	--m-arrow-w: 12px; --m-arrow-d: 20px; --m-arrow-c: var(--c-primary);
}
/* 모든 화살촉 공통(동일 크기 보장) */
.moca_layout_debug .moca_layout_pane > .moca_layout_guide::before,
.moca_layout_debug .moca_layout_pane > .moca_layout_guide::after {
	content: ''; position: absolute; width: 0; height: 0; border-style: solid; border-color: transparent;
}
/* 가로(h = row): 좌우 삼각촉 ◄ ──── ► */
:is(.moca_layout_debug.moca_layout_row, .moca_layout_debug .moca_layout_row) > .moca_layout_pane > .moca_layout_guide {
	top: 50%; left: var(--m-arrow-d); right: var(--m-arrow-d); height: 0; transform: translateY(-50%);
	border-top: 2px dashed var(--m-arrow-c);
}
:is(.moca_layout_debug.moca_layout_row, .moca_layout_debug .moca_layout_row) > .moca_layout_pane > .moca_layout_guide::before {
	top: 50%; left: calc(-1 * var(--m-arrow-d)); transform: translateY(-50%);
	border-width: var(--m-arrow-w) var(--m-arrow-d) var(--m-arrow-w) 0; border-right-color: var(--m-arrow-c);
}
:is(.moca_layout_debug.moca_layout_row, .moca_layout_debug .moca_layout_row) > .moca_layout_pane > .moca_layout_guide::after {
	top: 50%; right: calc(-1 * var(--m-arrow-d)); transform: translateY(-50%);
	border-width: var(--m-arrow-w) 0 var(--m-arrow-w) var(--m-arrow-d); border-left-color: var(--m-arrow-c);
}
/* 세로(v = col): 위아래 삼각촉 ▲ | ▼ */
:is(.moca_layout_debug.moca_layout_col, .moca_layout_debug .moca_layout_col) > .moca_layout_pane > .moca_layout_guide {
	left: 50%; top: var(--m-arrow-d); bottom: var(--m-arrow-d); width: 0; transform: translateX(-50%);
	border-left: 2px dashed var(--m-arrow-c);
}
:is(.moca_layout_debug.moca_layout_col, .moca_layout_debug .moca_layout_col) > .moca_layout_pane > .moca_layout_guide::before {
	left: 50%; top: calc(-1 * var(--m-arrow-d)); transform: translateX(-50%);
	border-width: 0 var(--m-arrow-w) var(--m-arrow-d) var(--m-arrow-w); border-bottom-color: var(--m-arrow-c);
}
:is(.moca_layout_debug.moca_layout_col, .moca_layout_debug .moca_layout_col) > .moca_layout_pane > .moca_layout_guide::after {
	left: 50%; bottom: calc(-1 * var(--m-arrow-d)); transform: translateX(-50%);
	border-width: var(--m-arrow-d) var(--m-arrow-w) 0 var(--m-arrow-w); border-top-color: var(--m-arrow-c);
}
/* 라벨 hover 시 그 레이아웃의 직계 칸 화살표만 표시 */
.moca_layout_debug .moca_layout_tag:hover ~ .moca_layout_pane > .moca_layout_guide { opacity: 1; }

/* ── 디버그 스코프 경계 ──────────────────────────────────────────
   탭패널·프레임에 로드된 '다른 화면'은 이 화면의 잎(컴포넌트)이다 — 바깥 디버그가
   그 안의 레이아웃 구조까지 그리면 안 된다(실제로 라벨·토큰이 숨긴 컨텐츠 위로
   떠올라 보이는 사고가 있었다 2026-07-22). 디버그 선택자가 전부 자손 결합자라
   경계 안쪽까지 매치되므로 여기서 일괄 무효화한다. layout.js 도 가이드/주입을
   경계 안쪽은 건너뛰지만, 이미 렌더돼 있던 안쪽 요소를 덮는 이중 안전장치.
   (안쪽 화면 '자체' FAB 로 켠 디버그는 debug 루트가 경계 안에 있으므로 영향 없음) */
.moca_layout_debug :is(.moca_tab_panel, [data-m-type="frame"]) .moca_layout,
.moca_layout_debug :is(.moca_tab_panel, [data-m-type="frame"]) .moca_layout_pane { outline: none !important; background: none !important; }
.moca_layout_debug :is(.moca_tab_panel, [data-m-type="frame"]) .moca_layout_tag,
.moca_layout_debug :is(.moca_tab_panel, [data-m-type="frame"]) .moca_layout_guide { display: none !important; }
.moca_layout_debug :is(.moca_tab_panel, [data-m-type="frame"]) .moca_layout_pane::before,
.moca_layout_debug :is(.moca_tab_panel, [data-m-type="frame"]) .moca_layout_pane::after { content: none !important; }
/* !important 인 이유: 테마 파일(theme-light-fix 등)이 :root.theme-light 접두로 더 높은
   특이성의 디버그 색 규칙을 덧칠한다 — 경계 무효화는 어떤 테마보다 우선해야 한다. */

/* ── Echart 컴포넌트 (data-m-type="echart") ─────────────────────
   크기는 부모가 결정하고 컴포넌트는 부모를 꽉 채운다(높이 0 방지 클래스 — echart.js 가 자동 부여).
   모바일 고정 높이는 mobile.css 에서 처리. */
.moca_echart { width: 100%; height: 100%; min-height: 0; }
/* 업무화면 차트 패널 — lay_bd_detail 카드(헤더+바디) 안에서 차트 바디가 남은 높이를 채움 */
.lay_bd_detail .chart_panel_body { flex: 1; min-height: 0; padding: 15px; overflow: hidden; }

/* 레이아웃 기반 게시판 데모(TDEMO_LAYOUT_BOARD) — 상세 패널이 v8:2 칸에서 높이를 채움 */
.moca_layout > .moca_layout_pane.lay_bd_detail { display: flex; flex-direction: column; min-height: 0; overflow: hidden;
	background: var(--c-surface); border: 1px solid var(--c-border); border-radius: var(--radius-md); }
.lay_bd_detail_body { flex: 1; min-height: 0; overflow: auto; display: flex; flex-direction: column; padding: 15px; }
.lay_bd_detail_body > .moca_table_form { flex: 1; min-height: 0; }

/* 카드 바디의 마지막(유일) 폼은 아래 여백을 뺀다 — 바디 padding 15px 이 이미 여백이라, margin 이 남으면 flex 높이를 넘쳐 마지막 행이 잘린다 */
.lay_bd_detail_body > .moca_table_form:last-child { margin-bottom: 0; }

/* 패널 헤더 우측 보조정보(건수·안내) / 패널 바디 하단 안내문구 */
.panel_hd_info { flex-shrink: 0; margin-left: 10px; font-size: 12px; color: var(--c-text-muted); text-align: right; }
.panel_hint { flex-shrink: 0; margin-top: 12px; padding: 10px 12px; background: var(--c-primary-pale); border-left: 2px solid var(--c-primary); border-radius: var(--radius-sm); font-size: 12px; line-height: 1.7; color: var(--c-text-sub); }

/* 설명란을 레이아웃 칸으로 쓸 때(우측 사이드) — 칸 사이 간격은 layout gap 이 단일 소스라 자체 margin 은 뺀다 */
.moca_layout > .moca_layout_pane.panel_hint { margin-top: 0; }

/* 설명란 안 구조 — 좁은 사이드 칸에서 한 덩어리 문단은 읽히지 않는다. 제목 한 줄 + 짧은 항목으로 끊는다.
   base.css 가 ul 의 list-style 을 지워놔서 여기서 되살린다(설명란 안에서만). */
.panel_hint .hint_title { display: block; margin-bottom: 7px; padding-bottom: 6px; border-bottom: 1px solid var(--c-border); font-size: 12.5px; font-weight: 700; color: var(--c-text); }
.panel_hint ul { margin: 0; padding-left: 15px; list-style: disc; }
.panel_hint li { margin-bottom: 6px; }
.panel_hint li:last-child { margin-bottom: 0; }
.panel_hint li::marker { color: var(--c-primary); }
.panel_hint b { color: var(--c-primary); font-weight: 600; }

/* 레이아웃 데모 화면(TPL025~TPL030) 전용 — 분할 칸 시각화. 라벨(스펙) 아래 보조설명(.sub) 한 줄 */
.demo_pane { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; padding: 6px 10px; box-sizing: border-box; text-align: center; border: 1px solid var(--c-border); border-radius: var(--radius-md); background: var(--c-surface); color: var(--c-text-sub); font-size: 14px; font-weight: 700; overflow: auto; min-height: 60px; }
.demo_pane .sub { font-size: 12px; font-weight: 500; color: var(--c-text-muted); }
/* 레이아웃 데모(TDEMO_LAYOUT) 요약 카드 */
.lay_stat_card { display: flex; align-items: center; justify-content: space-around; gap: 8px;
	height: 100%; box-sizing: border-box; padding: 12px 16px;
	border: 1px solid var(--c-border); border-radius: var(--radius-md); background: var(--c-surface); }
.lay_stat_item { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.lay_stat_label { font-size: 12px; font-weight: 500; color: var(--c-text-muted); }
.lay_stat_value { font-size: 26px; font-weight: 800; line-height: 1; color: var(--c-text-sub); }
.lay_stat_value.cyan { color: var(--c-primary); }
.lay_stat_divider { width: 1px; align-self: stretch; margin: 8px 0; background: var(--c-border); }
/* ══ 찾기 순회 UI 표준 — [◀] [n / m] [▶] ═══════════════════════════════════════
   조회(btn_search)·CRUD(bd_btn_*) 는 표준이 있었는데 **찾기 순회는 없었다**(2026-08-31 지적).
   그래서 화면마다 「첫 일치로 이동」 같은 버튼을 제각각 만들었고, 일치가 여럿일 때 두 번째부터는
   갈 방법이 없었다. 실제로 TPL024 는 이동 버튼에 `bd_btn_new`(신규 색)를 써 뜻도 어긋나 있었다.

   ★ 순회는 **동작(이동)** 이라 CRUD 색을 쓰지 않는다 — 테두리만 있는 중립 버튼이다.
     신규(bd_btn_new)·삭제(bd_btn_del) 색을 이동에 쓰면 "누르면 뭔가 바뀐다"로 읽힌다.
   ★ 건수 표시(n / m)를 버튼 사이에 둔다 — 몇 개 중 몇 번째인지 모르면 순회 버튼이 의미가 없다.
     못 찾았을 때는 is_none 으로 분명히 알린다(조용히 0 이면 눌러도 반응이 없어 고장으로 보인다).
   ★ 컴포넌트 계약은 find/findNext/findPrev/clearFind/getFindInfo 로 통일한다
     (textviewer·VirtualTree 동일 — SKILL §82). 화면은 이 클래스로 UI 만 얹는다. */
.moca_find_bar   { display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0; }
.moca_find_btn   { display: inline-flex; align-items: center; justify-content: center;
                   min-width: 30px; height: 30px; padding: 0 6px;
                   border: 1px solid var(--c-border-dark); border-radius: var(--radius-sm);
                   background: var(--c-bg-elevated, var(--c-bg)); color: var(--c-text);
                   font-size: 13px; line-height: 1; cursor: pointer; }
.moca_find_btn:hover:not(:disabled) { border-color: var(--c-primary); color: var(--c-primary); }
.moca_find_btn:disabled { opacity: .4; cursor: not-allowed; }
.moca_find_hits  { min-width: 54px; padding: 0 2px; color: var(--c-text-sub); font-size: 13px;
                   text-align: center; white-space: nowrap; }
.moca_find_hits.is_none { color: var(--c-danger, #d9534f); font-weight: 600; }
/* ★ 순번은 **입력칸**이다 — 900개 중 41번째에서 500번째로 가려면 ▶ 를 459번 눌러야 한다.
   pdfviewer 의 쪽 이동([◀] [1] / 123 [▶])과 같은 모양·같은 동작으로 맞춘다(2026-08-31 지시).
   포커스가 빠지면 바로 이동한다(Enter 도 같이 — 폰 IME 는 Enter 를 안 줄 때가 있다: §79 찾기와 같은 이유).
   글자는 툴바 기본(12px)보다 크게 — 눌러서 고치는 값이라 읽고 지우기 쉬워야 한다. */
.moca_find_input { width: 52px; height: 30px; padding: 0 4px; box-sizing: border-box;
                   background: var(--c-bg-input, var(--c-bg)); color: var(--c-text);
                   border: 1px solid var(--c-border-dark); border-radius: 3px;
                   font-size: 14px; text-align: center; }
.moca_find_input:focus { outline: none; border-color: var(--c-primary); }
.moca_find_input:disabled { opacity: .4; cursor: not-allowed; }
.moca_find_total { color: var(--c-text-sub); font-size: 14px; white-space: nowrap; }
.moca_find_total.is_none { color: var(--c-danger, #d9534f); font-weight: 600; }

.page_title_bar { display: flex; align-items: center; gap: 8px; flex-shrink: 0; border-bottom: 2px solid var(--c-border-color); padding-bottom: 10px; }
.page_title_bar .page_title_icon { font-size: 18px; line-height: 1; flex-shrink: 0; }
/* 빈 아이콘/경로/메뉴ID span 은 flex 아이템에서 제거 — 폭 0 이어도 gap(8px)이 남아 타이틀이 들여써지는 것 방지(아래 요소들과 좌측 정렬 통일) */
.page_title_bar .page_title_icon:empty, .page_title_bar .page_title_path:empty, .page_title_bar .page_title_id:empty { display: none; }
/* 홈부터의 메뉴 경로(브레드크럼) — 타이틀바 맨 우측(margin-left:auto), 홈은 아이콘. 좁으면 잘림(타이틀 우선) */
/* ★ 경로(브레드크럼)는 **줄지 않는다** — 끝까지 다 보여준다(2026-08-31 지시: 「트」로 잘렸다).
   경로는 짧고 「지금 어디에 있는가」를 알려주는 값이라, 잘리면 그 목적 자체가 사라진다.
   좁아지면 **제목이 먼저 줄어든다**(제목은 …으로 잘려도 [코드]와 앞부분이 남아 알아볼 수 있다).
   그래서 shrink 0 + overflow 를 풀어 둔다 — min-width:0 도 두지 않는다(있으면 다시 줄어든다). */
/* ★ padding-left 로 **최소 간격을 보장**한다(2026-08-31 지시) — margin-left:auto 는 남는 자리를
   흡수하는 여백이라 **자리가 없으면 0 이 되어** 왼쪽 아이콘과 경로가 딱 붙는다(실측: 폰에서 붙었다).
   auto 는 "넓을 때 오른쪽으로 밀기", padding 은 "좁을 때도 떨어뜨리기" — 둘 다 필요하다.
   좁아지면 이 간격만큼 제목이 더 줄어든다(제목이 shrink 999 로 제일 먼저 양보한다). */
.page_title_bar .page_title_path { margin-left: auto; padding-left: 8px; display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--c-text-muted); white-space: nowrap; flex: 0 0 auto; }
.page_title_bar .page_title_home { width: 14px; height: 14px; flex-shrink: 0; background: var(--c-text-muted); -webkit-mask: url('../images/icon_home.svg') no-repeat center / 14px 14px; mask: url('../images/icon_home.svg') no-repeat center / 14px 14px; }
/* SPA 화면 새로고침 버튼 — 타이틀바 맨 우측(브레드크럼 옆). 정사각 버튼, 기본부터 밝은(활성) 상태 — hover 는 회전+글로우만 */
.page_title_bar .page_refresh_btn { position: relative; width: 26px; height: 26px; padding: 0; border: 1px solid var(--c-primary); border-radius: var(--radius-sm); background: var(--c-bg-elevated); cursor: pointer; flex-shrink: 0; transition: box-shadow 0.15s; }
.page_title_bar .page_refresh_btn::before { content: ''; position: absolute; inset: 0; background: var(--c-primary); -webkit-mask: url('../images/icon_refresh.svg') no-repeat center / 15px 15px; mask: url('../images/icon_refresh.svg') no-repeat center / 15px 15px; transition: transform 0.25s; }
.page_title_bar .page_refresh_btn:hover { box-shadow: 0 0 6px rgba(0,240,255,0.35); }
.page_title_bar .page_refresh_btn:hover::before { transform: rotate(180deg); }
/* 타이틀 구성요소 크기는 메뉴아이디(16px) 기준으로 통일 */
.page_title_bar .page_title_id   { font-size: 16px; font-weight: 700; color: var(--c-primary); flex-shrink: 0; }
/* ★ 제목은 **한 줄 + …** 이다 — 좁은 폭에서 두 줄로 접히지 않는다(2026-08-31 폰 지적:
   「가상트리(VirtualTree)」가 두 줄이 되면서 타이틀바 높이가 화면마다 들쭉날쭉해졌다).
   ★ display:flex 를 쓰지 않는다 — flex 컨테이너의 글자는 **익명 flex 항목**이 되어
     text-overflow 가 아예 적용되지 않는다(그래서 종전에는 …이 안 나오고 줄바꿈만 됐다).
     이 요소는 flex 부모의 자식이라 어차피 블록화되므로 display 를 지정할 필요가 없다.
   ★ min-width:0 이 없으면 flex 항목의 최소폭이 내용폭이라 줄어들지 않는다(줄이려면 이게 먼저다).
   ★ 타이틀바에서 **제일 먼저 양보하는 것이 제목**이다(shrink 999) — 코드·아이콘·경로를 다 보여준 뒤
     남는 자리에 들어간다. 제목은 …으로 잘려도 [코드]와 앞부분이 남아 무슨 화면인지 알아볼 수 있지만,
     경로는 잘리면 「지금 어디인가」라는 목적 자체가 사라진다(2026-08-31 지시). */
.page_title_bar .page_title_text { color: var(--c-text); font-size: 16px; font-weight: 600;
                  flex: 0 999 auto; min-width: 0;
                  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* ══ 타이틀바 아이콘 버튼 — 셋(소스경로·소스보기·새로고침)이 **같은 박스**다 (2026-08-31 지시)
   종전에는 새로고침만 테두리 박스였고 소스경로·소스보기는 맨 아이콘이라, 같은 줄에서 셋이
   서로 다른 물건처럼 보였다. 크기·테두리·배경·hover 를 한 규칙으로 묶는다.
   ★ 아이콘은 <img> 가 아니라 **mask** 로 그린다 — img 는 파일 안의 색(#cbd5e1)이 그대로 나와
     박스 안에서 홀로 회색으로 뜬다. mask 면 currentColor 를 따라 테마·강조색이 그대로 적용된다. */
.page_title_bar .page_info_btn,
.page_title_bar .page_source_btn,
.page_title_bar .page_refresh_btn {
                  position: relative; display: inline-flex; align-items: center; justify-content: center;
                  width: 26px; height: 26px; padding: 0; flex-shrink: 0;
                  border: 1px solid var(--c-primary); border-radius: var(--radius-sm);
                  background: var(--c-bg-elevated); color: var(--c-primary);
                  cursor: pointer; transition: box-shadow 0.15s; }
.page_title_bar .page_info_btn:hover,
.page_title_bar .page_source_btn:hover,
.page_title_bar .page_refresh_btn:hover { box-shadow: 0 0 6px rgba(0,240,255,0.35); }
.page_title_bar .page_info_btn::before,
.page_title_bar .page_source_btn::before {
                  content: ''; position: absolute; inset: 0; background: currentColor; }
.page_title_bar .page_info_btn::before {
                  -webkit-mask: url('../images/icon_page_info.svg') no-repeat center / 15px 15px;
                          mask: url('../images/icon_page_info.svg') no-repeat center / 15px 15px; }
.page_title_bar .page_source_btn::before {
                  -webkit-mask: url('../images/icon_page_source.svg') no-repeat center / 15px 15px;
                          mask: url('../images/icon_page_source.svg') no-repeat center / 15px 15px; }
/* 마크업에 <img> 가 남아 있어도 박스만 보이게 — 아이콘은 위 mask 가 그린다(구 화면 호환) */
.page_title_bar .page_info_btn img, .page_title_bar .page_source_btn img { display: none; }
/* (구 규칙 삭제 — 맨 아이콘 시절의 크기·opacity·scale hover. 위 「타이틀바 아이콘 버튼」 한 규칙으로 통합했다.
   그대로 두면 아래 규칙이 위를 덮어 박스가 다시 풀린다 — 셋을 묶은 뒤에는 개별 규칙이 남으면 안 된다.) */
/* 툴팁 표준 스타일(파랑 배경+흰 글자, moca_engine 스킬 §툴팁 참조)과 통일 */
.page_info_tooltip { position: fixed; background: #2B6BE0; color: #fff; border: 1px solid #4D8DFF; font-size: 11px; font-weight: 500; padding: 5px 10px; border-radius: 4px; white-space: nowrap; user-select: text; cursor: text; z-index: 9999; opacity: 0; pointer-events: none; transition: opacity 0.15s; box-shadow: 0 4px 14px rgba(0,0,0,0.55); }
.page_info_tooltip.visible { opacity: 1; pointer-events: auto; }

/* ─── 23. 그리드 + 하단 패널 분할 래퍼 ─────────────────── */
.bd_split_wrap { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 15px; }

/* 그리드는 항상 최소 180px 보장 (툴바 38px + 행 영역) */
.bd_split_wrap > .moca_grid { min-height: 180px; }

/* ─── 24. 하단 상세 폼 패널 ────────────────────────────── */
.bd_panel { background: var(--c-white); border: 1px solid var(--c-border); border-radius: var(--radius-md); flex-shrink: 0; overflow-y: auto; max-height: 34vh; }
.bd_lr_wrap .bd_panel { overflow-y: unset; max-height: unset; }
.bd_panel_header { display: flex; align-items: center; justify-content: space-between; padding: 15px; background: var(--c-bg); border-bottom: 1px solid var(--c-border); border-radius: var(--radius-md) var(--radius-md) 0 0; }
.bd_panel_body { padding: 15px; overflow-y: auto; }
.bd_cont_view { width: 100%; min-height: 120px; max-height: 400px; padding: 8px 10px; box-sizing: border-box; line-height: 1.6; word-break: break-word; overflow-y: auto; border: 1px solid var(--c-border); background: var(--c-white); border-radius: 3px; font-size: 13px; }

/* ── 연도/월 선택 그리드 ── */
.moca_calendar_picgrp { list-style: none; margin: 0; padding: 6px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; }
.moca_calendar_item { text-align: center; }
.moca_calendar_item button { width: 100%; padding: 6px 0; background: none; border: 1px solid transparent; border-radius: var(--radius-sm); font-size: 11px; color: var(--c-text); cursor: pointer; transition: all 0.1s; }
.moca_calendar_item button:hover { background: var(--c-primary-light); color: var(--c-primary); border-color: var(--c-primary); }

/* 강조 배경(--c-primary) 위 글씨는 --c-bg 로 짝을 맞춘다 — #fff 고정이면 다크의 시안(#00F0FF) 위에서 1.41:1 로 사라진다(PART A §1-6 "변수 쌍 불일치") */
.moca_calendar_item.active button { background: var(--c-primary); color: var(--c-bg); font-weight: 700; border-color: var(--c-primary-dark); }

/* ── multiCalendar 팝업 내부 ── */
/* contain:size — 좌측 버튼 수가 팝업 높이를 밀어 올리지 않게 높이 산정에서 제외. 팝업 높이는 우측 달력이 결정하고, 버튼이 넘치면 .moca_calendar_list 만 스크롤 (모바일 가로배치에선 해제) */
.moca_calendar_lta { width: 100px; flex-shrink: 0; contain: size; border-right: 1px solid var(--c-border); display: flex; flex-direction: column; border-radius: var(--radius-md) 0 0 var(--radius-md); overflow: hidden; background: var(--c-header-bg); }
.moca_calendar_list { list-style: none; margin: 0; padding: 8px 6px 4px; flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 4px; }
.moca_calendar_list li { margin-bottom: 0; }
.moca_calendar_list button { width: 100%; text-align: center; padding: 5px 6px; background: transparent; border: 1px solid var(--c-border-dark); font-size: 11px; color: var(--c-text-sub); cursor: pointer; border-radius: var(--radius-sm); transition: all 0.12s; font-weight: 500; line-height: 1.3; white-space: nowrap; }
.moca_calendar_list li.active button { background: var(--c-primary-light); border-color: var(--c-primary); color: var(--c-primary); font-weight: 600; }
.moca_calendar_list button:hover { background: var(--c-grid-hover); border-color: var(--c-primary); color: var(--c-text); }
.moca_calendar_lta .moca_calendar_footer { border-top: 1px solid var(--c-border); padding: 8px 6px; flex-direction: row; flex-wrap: nowrap; justify-content: center; gap: 6px; }
.moca_calendar_lta .moca_calendar_btn_cancel, .moca_calendar_lta .moca_calendar_btn_ok { flex: 1 1 0; width: auto; min-width: 0; height: 30px; padding: 0; display: flex; align-items: center; justify-content: center; }
.moca_calendar_lta .moca_calendar_btn_cancel::before, .moca_calendar_lta .moca_calendar_btn_ok::before { content: ""; width: 14px; height: 14px; background: currentColor; mask-position: center; mask-repeat: no-repeat; mask-size: contain; -webkit-mask-position: center; -webkit-mask-repeat: no-repeat; -webkit-mask-size: contain; }
.moca_calendar_lta .moca_calendar_btn_cancel::before { mask-image: url('../images/icon_close.svg'); -webkit-mask-image: url('../images/icon_close.svg'); }
.moca_calendar_lta .moca_calendar_btn_ok::before { mask-image: url('../images/icon_check.svg'); -webkit-mask-image: url('../images/icon_check.svg'); }
.moca_calendar_lta .moca_calendar_btn_cancel { background: transparent; border-color: var(--c-border-dark); color: var(--c-text-sub); }
.moca_calendar_lta .moca_calendar_btn_cancel:hover { background: var(--c-bg); color: var(--c-text); }
.moca_calendar_lta .moca_calendar_btn_ok { background: var(--c-primary); border-color: var(--c-primary); color: var(--c-bg); }
.moca_calendar_lta .moca_calendar_btn_ok:hover { background: var(--c-primary-dark); border-color: var(--c-primary-dark); color: var(--c-bg); }
.moca_calendar_rta { flex: 1; padding: 6px; overflow: auto; }
.moca_calendar_row { display: flex; gap: 8px; }
.moca_calendar_fl { flex: 1; min-width: 175px; }
.calendar_ipt { display: flex; justify-content: center; padding: 3px 0; }
/* 달력 팝업 시작/종료 입력칸 — 레이아웃(중앙정렬·소형 크기)만. 시각·포커스는 박스 입력 공통 베이스
   (베이스 셀렉터에 .calendar_ipt .moca_input 를 포함해 body 로 포탈돼도 자립 적용) */
.calendar_ipt .moca_input { text-align: center !important; font-size: 11px !important; width: 88px !important; height: 22px !important; padding: 0 6px; }

/* fromto 안의 개별 달력은 static (이미 popup 안에 있으므로) */
.moca_calendar_fromto .moca_calendar { position: static; border: none; box-shadow: none; border-radius: 0; width: auto; }
.moca_calendar_fromto .moca_calendar .moca_calendar_header { border-radius: 0; }

/* ─── Popup 로딩 인디케이터 ─────────────────────────────── */
.moca_popup_body { min-height: 120px; }

/* 팝업 표준 골격 보정 — 팝업 페이지도 layout 으로 와이어프레임을 잡는다(PART A §15).
   바디(.bd_modal_body 18/20px)가 이미 패딩을 가지므로 자동삽입 .page_wrap 의 15px 는 눌러 이중 패딩 방지,
   버튼영역(pop_btn_area)의 margin-top 도 layout gap(15px) 단일 소스에 맡긴다 */
.moca_popup_body .page_wrap { padding: 0; }
.moca_popup_body .pop_btn_area { margin-top: 0; }

/* 팝업의 고정높이 layout(page_wrap overflow:auto 모드)에서 스크롤 주체는 .bd_modal_body 다 —
   page_wrap 자체 스크롤을 풀어 개발용 FAB(좌하단 -14px 걸침)가 가로/세로 스크롤을 만들지 않게 한다 */
.moca_popup_body .page_wrap:has(> .moca_layout_fixed) { overflow: visible; }

/* ─── 브라우저 창 팝업 셸(winpop.html) ────────────────────
   같은 팝업 페이지를 레이어로도 새 창으로도 띄우기 때문에, 창일 때만 두 가지를 바꾼다.
   ① 바디가 창 전체를 채운다(레이어는 .bd_modal 안이라 필요 없던 규칙).
   ② 팝업 페이지가 선언한 기준 높이(data-m-height → .moca_layout_fixed)를 창 크기로 늘린다 —
      레이어에선 "이 정도 높이"가 맞지만 창에서는 창이 곧 크기이기 때문. */
body.moca_win_body > [data-m-kind="winbox"] { height: 100%; display: flex; flex-direction: column; }
body.moca_win_body .moca_popup_body { flex: 1 1 0; min-height: 0; box-sizing: border-box; }
body.moca_win_body .moca_layout.moca_layout_fixed { width: auto; height: auto; flex: 1 1 auto; }
body.moca_win_body .page_wrap:has(> .moca_layout_fixed) { height: 100%; overflow: hidden; }
.moca_popup_loading { display: flex; align-items: center; justify-content: center; gap: 8px; height: 120px; color: var(--c-text-muted); font-size: 13px; }
.moca_popup_loading span { display: inline-block; width: 16px; height: 16px; border: 2px solid var(--c-border); border-top-color: var(--c-primary); border-radius: 50%; animation: moca_spin 0.7s linear infinite; }
/* 화면 로드 실패 안내(frame._renderLoadError) — 팝업·탭 어디서든 「로딩 중」 자리에 대신 그린다 */
.moca_load_error { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; min-height: 140px; padding: 20px 16px; text-align: center; color: var(--c-text); }
.moca_load_error p { margin: 0; }
.moca_load_error_tit { font-size: 14px; font-weight: 600; }
.moca_load_error_sub { font-size: 12px; color: var(--c-text-muted); }
.moca_load_error_retry { margin-top: 8px; padding: 8px 18px; border: 1px solid var(--c-primary); border-radius: 6px; background: transparent; color: var(--c-primary); font: inherit; font-size: 13px; cursor: pointer; }
.moca_load_error_retry:hover { background: var(--c-primary); color: #fff; }
@keyframes moca_spin {
    to { transform: rotate(360deg); }
}

/* ─── MessageBox (alert / error / confirm) ──────────────── */
/* z-index — 메시지박스는 무엇보다 위여야 한다. 달력 팝업(99000)·통신 프로그레스(99990)보다 높게 둔다
   (9999 였을 때는 달력 위에서 띄운 오류 메시지가 달력 뒤에 가려 보이지 않았다) */
.moca_messagebox_modal { position: fixed; inset: 0; background: rgba(26,43,56,0.45); z-index: 99995; display: flex; align-items: center; justify-content: center; }
/* 입력칸이 있는 confirm(moca_mb_top) — 위쪽 정렬. 세로 중앙은 모바일 키보드에 입력칸·버튼이 가려진다(2026-09-06). 키보드로 뷰포트가 줄면 스크롤로 받는다 */
.moca_messagebox_modal.moca_mb_top { align-items: flex-start; padding-top: max(24px, 6vh); overflow-y: auto; }
/* ★ 치수를 em 으로 잡는다 — 글자만 배율을 타고 그릇은 px 로 고정이면, 배율을 올릴수록
   한 줄에 들어가는 글자수가 반비례로 줄어 되레 읽기 나빠진다(2026-08-30 지적).
   실측: 폭 360px 고정 + 글자 13px→26px(200%) 이면 한 줄 23자 → 11자로 반토막이었다.
   읽기 좋은 줄 길이는 픽셀이 아니라 **글자수**로 정해지므로(한글 본문 30~40자) em 이 맞다.
   ★ font-size 를 이 요소에 선언해 두는 것이 핵심이다 — 화면 배율기(fontScale 류)가
   스타일시트의 px font-size 를 다시 쓰는 방식이라, 선언이 있어야 em 의 기준도 함께 커진다.
   ★ width 를 `min(…, calc(100vw - 32px))` 로 두면 PC/모바일 분기가 필요 없다 —
   넓은 화면에서는 시원하게 쓰고 좁은 화면에서는 자동으로 화면 안에 들어온다. */
.moca_messagebox { background: var(--c-bg); border: 1px solid var(--c-primary); border-radius: var(--radius-lg); box-shadow: 0 12px 40px rgba(0,0,0,0.6); font-size: 13px; width: min(34em, calc(100vw - 32px)); padding: 28px 26px 22px; display: flex; flex-direction: column; align-items: center; gap: 12px; animation: moca_mb_fadein 0.18s ease; }
@keyframes moca_mb_fadein {
    from { transform: translateY(-10px) scale(0.96); opacity: 0; }
    to { transform: translateY(0) scale(1); opacity: 1; }
}
/* 원도 글자를 담는 그릇이라 em 이다 — px 로 두면 배율에서 글자가 원을 뚫는다(위 주석) */
.moca_messagebox .ico { width: 2.4em; height: 2.4em; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 22px; font-weight: 700; line-height: 1; margin-bottom: 4px; flex-shrink: 0; }
.moca_messagebox.alert .ico { color: var(--c-success); border: 4px solid var(--c-success); }
.moca_messagebox.confirm .ico { color: var(--c-danger); border: 4px solid var(--c-danger); }

/* error 는 확인 1개(alert 구조)라 confirm 과 버튼 수로 구분되지 않는다 → 박스 테두리까지 danger 로 물려 한눈에 오류로 읽히게 한다.
   색은 --c-danger 변수라 테마(라이트·고대비)와 프로젝트 오버라이드(ERP 는 바이올렛)를 자동으로 따른다. */
.moca_messagebox.error { border-color: var(--c-danger); }
.moca_messagebox.error .ico { color: var(--c-danger); border: 4px solid var(--c-danger); }
.moca_messagebox .moca_message { text-align: center; color: var(--c-text); font-size: 13px; line-height: 1.65; margin: 0; word-break: keep-all; }
.moca_messagebox .moca_message p { margin: 0; }
/* ★ 좁아서 두 버튼이 한 줄에 안 들어가면 **글자를 접지 말고 버튼을 다음 줄로 내린다**(flex-wrap).
   종전에는 버튼이 줄어들며 라벨이 "확/인", "취/소" 로 쪼개졌다(2026-08-30 지적).
   배치를 바꾸는 것이 글자를 쪼개거나 폰트를 줄이는 것보다 낫다(§15-3-3). */
.moca_messagebox .moca_btnbox { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; justify-content: center; }
/* 다이얼로그 버튼: 일반 버튼보다 크게.
   ★ height 고정을 쓰지 않는다 — 글자만 커지면 상하 여백이 사라져 답답해지고(34px 상자에 26px 글자),
   터치 최소치(44px)에도 못 미친다. 게다가 이 버튼은 **파괴적 동작의 마지막 관문**이라
   화면에서 가장 정확히 눌려야 하는 자리다. min-height + em 패딩으로 배율을 따라 자라게 한다. */
/* flex:0 0 auto + nowrap — 줄어들지 않으니 라벨이 쪼개지지 않고, 안 들어가면 위 wrap 이 줄을 바꾼다 */
.moca_messagebox .moca_btn_confirm, .moca_messagebox .moca_btn_cancel { flex: 0 0 auto; min-height: max(40px, 2.4em); height: auto; padding: 0.5em 1.6em; font-size: 13px; font-weight:600; white-space: nowrap; border-radius: var(--radius-sm);}

/* confirm 메시지 안에 넣는 라디오 선택 목록 (com.confirm(_message) HTML 로 삽입) */
/* confirm 입력 옵션({input:{…}}) — 메시지 아래 한 줄 입력칸(값 입력 프롬프트). 글자 배율을 따라 커지도록 em */
.moca_confirm_input { margin-top: 12px; width: 100%; }
.moca_confirm_ipt   { width: 100%; box-sizing: border-box; min-height: max(40px, 2.6em); padding: 0 0.8em; border: 1px solid var(--c-border-dark); border-radius: var(--radius-sm); background: var(--c-bg-input); color: var(--c-text); font-family: inherit; font-size: 14px; }
.moca_confirm_ipt:focus { outline: 2px solid var(--c-primary); outline-offset: 1px; border-color: var(--c-primary); }
.moca_confirm_radio { display: flex; flex-direction: column; gap: 8px; margin-top: 14px; width: 100%; text-align: left; }
.moca_confirm_radio label { display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; border: 1px solid var(--c-border-dark); border-radius: var(--radius-sm); cursor: pointer; transition: border-color 0.15s, background 0.15s; }
.moca_confirm_radio label:hover { border-color: var(--c-primary); background: var(--c-primary-pale); }
/* ★ 라디오는 네이티브를 쓰지 않고 직접 그린다 (2026-09-01).
   accent-color 만 준 네이티브 컨트롤은 ① 다크 배경에서 작은 점으로만 보였고
   ② 크기가 OS 고정이라 글자 배율(fontScale.js 는 px font-size 규칙만 키운다)을 따라가지 못해
   글자만 커지고 동그라미는 그대로인 상태가 됐다. em 으로 그리면 둘 다 해결된다.
   font: inherit 가 핵심 — 폼 컨트롤은 글꼴·크기를 상속하지 않아서, 이게 없으면
   em 의 기준이 브라우저 기본값(13.333px)이 돼 배율을 그대로 놓친다. */
.moca_confirm_radio input[type="radio"] {
  appearance: none; -webkit-appearance: none;
  font: inherit; flex-shrink: 0;
  width: 1.15em; height: 1.15em;
  /* 첫 줄(span b, line-height 1.5) 의 한가운데에 맞춘다 — 배율이 바뀌어도 비율이 유지된다 */
  margin: calc((1.5em - 1.15em) / 2) 0 0;
  border: max(1.5px, 0.11em) solid var(--c-border-dark);
  border-radius: 50%;
  background: var(--c-bg-input);
  cursor: pointer;
  display: grid; place-content: center;
  transition: border-color 0.15s;
}
.moca_confirm_radio input[type="radio"]::before {
  content: ''; width: 0.55em; height: 0.55em; border-radius: 50%;
  background: var(--c-primary); transform: scale(0); transition: transform 0.12s ease;
}
.moca_confirm_radio input[type="radio"]:checked { border-color: var(--c-primary); }
.moca_confirm_radio input[type="radio"]:checked::before { transform: scale(1); }
.moca_confirm_radio input[type="radio"]:focus-visible { outline: 2px solid var(--c-primary); outline-offset: 2px; }
.moca_confirm_radio input[type="radio"]:checked + span b { color: var(--c-primary); }
.moca_confirm_radio label:has(input:checked) { border-color: var(--c-primary); background: var(--c-primary-light); }
.moca_confirm_radio span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
/* line-height 는 위 라디오의 세로 맞춤 계산이 참조한다 — 바꾸면 margin 도 같이 고친다 */
.moca_confirm_radio span b { font-size: 13px; font-weight: 600; line-height: 1.5; color: var(--c-text); }
.moca_confirm_radio span .radio_desc { font-size: 11px; color: var(--c-text-sub); line-height: 1.4; }

/* ─── 좌우 분할 레이아웃 (bd_lr_*) ──────────────────────── */
.bd_lr_wrap { display: flex; flex-direction: row; flex: 1; min-height: 0; }
.bd_lr_left { display: flex; flex-direction: column; width: 50%; flex-shrink: 0; min-height: 0; }

/* 좌우 3:7 비율 변형 (좌측 그리드 30% / 우측 패널 70%) */
.bd_lr_wrap.bd_lr_37 .bd_lr_left { width: 30%; }

/* 상하 3:7 비율 변형 (상단 그리드 30% / 하단 패널 70%) — 패널은 mobileview 래퍼 안에 있음 */
.bd_split_wrap.bd_split_37 > .moca_grid { flex: 3; min-height: 0; }
.bd_split_wrap.bd_split_37 > [data-m-mobileview] { flex: 7; min-height: 0; display: flex; flex-direction: column; }
.bd_split_wrap.bd_split_37 > [data-m-mobileview] .bd_panel { flex: 1; min-height: 0; max-height: none; }
.bd_lr_right { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; margin-left: 15px; }
.bd_lr_right .bd_panel { border-top: none; flex: 1; display: flex; flex-direction: column; min-height: 0; }
.bd_lr_right .bd_panel_body { flex: 1; min-height: 0; overflow: hidden; display: flex; flex-direction: column; }
.bd_lr_right .bd_panel_body .moca_table_form { flex: 1; min-height: 0; overflow: hidden; }
.bd_lr_right .bd_panel_body [data-m-type="fileupload"] { flex-shrink: 0; }

/* fileupload data-m-autoheight="false" — 첨부 개수와 무관하게 박스 형태 유지(부모 flex 칸을 채움) */
[data-m-type="fileupload"][data-m-autoheight="false"] { flex: 1 1 0; min-height: 0; }

/* fileupload 모바일 다운로드 버튼 셀 — 모바일 전용 컬럼(fileupload.js).
   보이는 건 다운로드 아이콘 하나이고 라벨("받기")은 font-size:0 으로 DOM 에만 남긴다(접근성 이름).
   아이콘은 ::before 에 mask + background-color 로 칠해 **테마 색을 자동으로 따른다**(엔진 아이콘 공통 기법).
   ★ mask 를 버튼 자신에 걸지 않는 이유: 브라우저 기본 버튼(24x16)은 손가락 대상으로 너무 작아
     버튼을 셀만큼 키워야 하는데, 마스크가 버튼 자체에 걸리면 탭 영역까지 아이콘 모양으로 깎일 수 있다. */
[data-m-type="fileupload"] td[id="FILE_DOWN"] { padding: 0 2px; }
[data-m-type="fileupload"] td[id="FILE_DOWN"] .grid_btn { display: flex; }
[data-m-type="fileupload"] td[id="FILE_DOWN"] .grid_btn button {
	position: relative; flex: 1; height: 24px; padding: 0;
	font-size: 0; color: transparent; background: transparent;
	border: 1px solid var(--c-primary); border-radius: var(--radius-sm); cursor: pointer;
}
[data-m-type="fileupload"] td[id="FILE_DOWN"] .grid_btn button::before {
	content: ''; position: absolute; inset: 0; background-color: var(--c-primary);
	-webkit-mask: url('../images/icon_download.svg') no-repeat center / 14px 14px;
	mask: url('../images/icon_download.svg') no-repeat center / 14px 14px;
}

/* ─── File Upload Popup (TERP_FILEUPLOAD) ────────────── */
.fu_wrap { display: flex; flex-direction: column; gap: 14px; height: 100%; }

/* 파일추가 버튼 */
.fu_btn_add { width: 100%; height: 44px; color: var(--c-primary); border: 1px solid var(--c-primary); border-radius: var(--radius-sm); font-size: 14px; font-weight: 700; letter-spacing: 0.5px; cursor: pointer; transition: background 0.15s; flex-shrink: 0; box-shadow: var(--shadow-sm); }
.fu_btn_add:hover { background: var(--c-primary-light); }

/* 드래그앤드롭 영역 */
.fu_drop_zone { flex: 1; min-height: 200px; border: 2px dashed var(--c-border-dark); border-radius: var(--radius-md); background: var(--c-bg); display: flex; flex-direction: column; position: relative; overflow-y: auto; transition: background 0.15s, border-color 0.15s; }
.fu_drop_zone.fu_drag_over { background: var(--c-primary-light); border-color: var(--c-primary); border-style: solid; }

/* 힌트 문구 */
.fu_drop_hint { flex: 1; display: flex; align-items: center; justify-content: center; color: var(--c-text-muted); font-size: 13px; pointer-events: none; }

/* 파일 목록 */
.fu_file_list { list-style: none; margin: 0; padding: 10px; display: flex; flex-direction: column; gap: 6px; }
.fu_file_item { display: flex; align-items: center; gap: 8px; padding: 7px 12px; background: var(--c-white); border: 1px solid var(--c-border); border-radius: var(--radius-sm); font-size: 12px; box-shadow: var(--shadow-sm); }
.fu_file_name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--c-text); }
.fu_file_size { flex-shrink: 0; color: var(--c-text-muted); min-width: 60px; text-align: right; font-size: 11px; }
.fu_file_del { flex-shrink: 0; width: 20px; height: 20px; background: var(--c-text-muted); color: var(--c-white); border: none; border-radius: 50%; font-size: 13px; line-height: 1; cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0; transition: background 0.15s; }
.fu_file_del:hover { background: var(--c-danger); }

/* 하단 업로드 버튼 */
.fu_btn_area { display: flex; justify-content: flex-end; flex-shrink: 0; }
.fu_btn_upload { height: 36px; padding: 0 32px; background: var(--c-primary); color: var(--c-white); border: none; border-radius: var(--radius-sm); font-size: 13px; font-weight: 600; cursor: pointer; transition: background 0.15s; box-shadow: var(--shadow-sm); }
.fu_btn_upload:hover { background: var(--c-primary-dark); }

/* 파일 업로드 진행률 (원형 프로그레스바) */
.fileProgress { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; pointer-events: none; }
.fileProgress_ring { position: relative; width: 130px; height: 130px; }
.file_progressbar { display: block; width: 130px; height: 130px; }
.white_dim_txt { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 12px; font-weight: 700; color: var(--c-text); text-align: center; pointer-events: none; white-space: nowrap; line-height: 1.4; }

/* ========================================================== ■ TECHNICAL DARK THEME — CSS 변수 기반 overrides (design4 기반) 모든 색상은 base.css :root 변수로 관리 ========================================================== */

/* ── 그리드 래퍼 ── */
.moca_grid { background: var(--c-surface); border-color: var(--c-border); box-shadow: none; }

/* ── 그리드 툴바 ── */
.moca_grid_toolbar { background: var(--c-grid-toolbar); border-bottom-color: var(--c-border); }
.lta span { color: var(--c-text-sub) !important; }

/* ── 그리드 헤더 ── */
.moca_grid_body thead tr { background: var(--c-grid-th); }
.moca_grid_body thead th { background: var(--c-grid-th); color: var(--c-text-muted); border-color: var(--c-border); }

/* ── 그리드 바디 ── */
.moca_grid_body tbody tr { border-bottom-color: var(--c-border); }
.moca_grid_body tbody tr:nth-child(even) { background: var(--c-grid-row-alt); }
.moca_grid_body tbody td { color: var(--c-text-body); border-right-color: var(--c-border); }

/* ── 선택 행 ── */
.moca_grid_body tbody tr.selected { background: var(--c-grid-selected) !important; box-shadow: inset 3px 0 0 var(--c-primary); }
.moca_grid_body tbody tr.selected td { color: var(--c-text-selected) !important; }

/* ── 입력 컨트롤 ── */
/* 테두리는 --c-border-dark(컨트롤 경계용, WCAG 1.4.11 비텍스트 3:1 충족) 를 쓴다.
   장식용 --c-border 는 대비가 낮아(1.1~1.4:1) 컨트롤 경계로 쓰면 기준 미달이다.
   ※ 이 줄이 위 §입력박스(--c-border-dark 선언)를 덮고 있어서, 여기서 --c-border 를 쓰면
     그 선언이 무효가 된다 — 종전에 그래서 입력창 테두리가 1.55:1 이었다(2026-07-15 수정). */
/* (폼 입력/디테일뷰 편집칸의 배경·테두리·포커스·readonly 는 §10 박스 입력 공통 베이스로 이관 — 중복 제거 2026-07-21) */

/* ── 콤보 ── */
.moca_combo select { background: var(--c-bg-input); border-color: var(--c-border-dark); color: var(--c-text); }
.moca_combo select:focus { border-color: var(--c-primary); box-shadow: 0 0 0 2px var(--c-primary-pale); }
.moca_combo select:disabled { background: var(--c-bg-input-readonly); color: var(--c-text-muted); }

/* 콤보 기본 폭 — 화면에서 style="width:…" 로 지정하면 인라인이 우선 */
.moca_combo { width: 140px; }
/* 펼치기 화살표를 버튼처럼 — 네이티브 화살표 제거 후 우측 24px 을 구분선 있는 버튼 영역으로 렌더
   (배경 레이어: ①화살표 아이콘 ②세로 구분선 ③버튼 영역 배경. background 축약형 규칙들보다 뒤에 선언해야 함) */
.moca_combo select {
    -webkit-appearance: none; -moz-appearance: none; appearance: none;
    padding-right: 30px;
    background-image: url('../images/icon_cmb_arrow.svg'),
                      linear-gradient(var(--c-border), var(--c-border)),
                      linear-gradient(var(--c-surface), var(--c-surface));
    background-repeat: no-repeat;
    background-position: right 6px center, right 24px center, right center;
    background-size: 12px 8px, 1px 60%, 24px calc(100% - 2px);
}
.moca_combo select:hover:not(:disabled) {
    background-image: url('../images/icon_cmb_arrow_hover.svg'),
                      linear-gradient(var(--c-border), var(--c-border)),
                      linear-gradient(var(--c-surface), var(--c-surface));
}
/* disabled — 위 background 축약형(:disabled)이 이미지를 지우므로 버튼 영역을 다시 그림(배경만 readonly 톤) */
.moca_combo select:disabled {
    background-image: url('../images/icon_cmb_arrow.svg'),
                      linear-gradient(var(--c-border), var(--c-border)),
                      linear-gradient(var(--c-bg-input-readonly), var(--c-bg-input-readonly));
    background-repeat: no-repeat;
    background-position: right 6px center, right 24px center, right center;
    background-size: 12px 8px, 1px 60%, 24px calc(100% - 2px);
}

/* ── Textarea ── */
.moca_textarea > * { background: var(--c-bg-input); border-color: var(--c-border); color: var(--c-text); }
.moca_textarea > *:focus { border-color: var(--c-primary); box-shadow: 0 0 0 2px var(--c-primary-pale); background: var(--c-bg-input-focus); }
.moca_textarea > *[contenteditable="false"] { background: var(--c-bg-input-readonly); color: var(--c-text-muted); }

/* ── 폼 테이블 ── */
.moca_table_form th, div.moca_table_form [data-m-type="th"] { background: var(--c-grid-toolbar); color: var(--c-text-muted); border-color: var(--c-border); }
.moca_table_form td, div.moca_table_form [data-m-type="td"] { background: var(--c-bg-elevated); border-color: var(--c-border); }
div.moca_table_form [data-m-type="table"] { border-color: var(--c-border); }

/* ── 패널 (상하 레이아웃 상세영역) ── */
.bd_panel { background: var(--c-surface); border-color: var(--c-border); }
.bd_panel_header { background: var(--c-grid-toolbar); border-bottom-color: var(--c-border); }
.bd_modal_title { color: var(--c-text); }
/* 색만 바꾼다 — background 단축을 쓰면 위에서 준 mask 계열은 안 지워지지만
   background-image·size 가 초기화돼 나중에 배경을 쓰는 순간 조용히 어긋난다. */
.bd_modal_title::before { background-color: var(--c-primary); }

/* ── 패널 버튼 → 하단 버튼 시스템에서 통합 관리 ── */

/* ── 첨부파일 섹션 ── */
.bd_file_section { border-color: var(--c-border); }
.bd_file_header { background: var(--c-grid-toolbar); border-bottom-color: var(--c-border); }
.bd_file_header span { color: var(--c-text-sub); }
.bd_file_list_table th { background: var(--c-grid-toolbar); color: var(--c-text-muted); border-bottom-color: var(--c-border); }
.bd_file_list_table td { color: var(--c-text-body); border-bottom-color: var(--c-border); }
.bd_file_nm_cell { color: var(--c-primary); }
.bd_new_badge { background: var(--c-primary-light); color: var(--c-primary); }
.pending_row td { background: var(--c-primary-pale); }
.bd_dl_btn { background: transparent; color: var(--c-primary); border-color: var(--c-primary); }
.bd_dl_btn:hover { background: var(--c-primary); color: var(--c-bg); }
.bd_file_del_btn { background: transparent; color: var(--c-danger); border-color: var(--c-danger); }
.bd_file_del_btn:hover { background: var(--c-danger); color: #fff; }

/* ── 모달 ── */
.moca_alert_overlay, .moca_confirm_overlay { background: rgba(0,0,0,0.65); }
.moca_alert_box, .moca_confirm_box { background: var(--c-header-bg); border-color: var(--c-border); box-shadow: var(--shadow-lg); }
.moca_alert_title, .moca_confirm_title { background: var(--c-grid-toolbar); color: var(--c-text); border-bottom-color: var(--c-border); }
.moca_alert_msg, .moca_confirm_msg { color: var(--c-text-body); }
.moca_alert_btn_area, .moca_confirm_btn_area { border-top-color: var(--c-border); }
.moca_btn_cancel { background: var(--c-grid-hover) !important; color: var(--c-text-sub) !important; border-color: var(--c-border-dark) !important; }
.moca_btn_cancel:hover { background: var(--c-bg-hover) !important; color: var(--c-text) !important; }

/* ── bd_modal (팝업 모달) ── */
.bd_overlay { background: rgba(0,0,0,0.65); }
.bd_modal { background: var(--c-header-bg); border: 1px solid var(--c-border); box-shadow: var(--shadow-lg); }
.bd_modal_header { background: var(--c-grid-toolbar); border-bottom-color: var(--c-primary); }
.bd_modal_body { background: var(--c-surface); }

/* ── 로딩 스피너 ── */
.moca_loading { background: rgba(11,17,22,0.80); }
.moca_loading::after { border-color: var(--c-border); border-top-color: var(--c-primary); }

/* ── 전체화면 그리드 ── */
.moca_grid_full_wrap { background: var(--c-bg); }

/* ── 서치콤보 드롭다운: 다크테마 오버라이드는 기본 스타일에 통합됨 ── */

/* ── 달력 팝업 ── */
.moca_calendar, .moca_calendar_fromto { background: var(--c-header-bg); border-color: var(--c-primary); }

/* ── 컬럼 숨기기 팝업 ── */
.PopColgroup { background: var(--c-header-bg); border-color: var(--c-border); }
.groupListHeader { background: var(--c-grid-toolbar); border-bottom-color: var(--c-border); }
.ly_col_cont { background: var(--c-surface); border-color: var(--c-border); }
.groupColList li { color: var(--c-text-body); border-bottom-color: var(--c-border); }
.groupColList li:hover { background: var(--c-grid-hover); color: var(--c-primary); }
.groupColList li.on { background: var(--c-primary); color: var(--c-bg); }

/* ── 툴팁 ── */
.moca_grid_tooltip { background: var(--c-primary); color: var(--c-bg); }

/* ── 드래그앤드롭 업로드 영역 ── */
.fu_drop_zone { background: var(--c-bg-elevated); border-color: var(--c-border-dark); }
.fu_drop_zone.fu_drag_over { background: var(--c-primary-pale); border-color: var(--c-primary); }
.fu_file_item { background: var(--c-surface); border-color: var(--c-border); }
.fu_file_name { color: var(--c-text-body); }

/* ========================================================== ■ 버튼 시스템 통합 정의 유틸리티 버튼: 다크 아웃라인 업무 버튼: 역할별 색상 (신규·취소 = 중립, 저장 = 그린, 수정 = 시안, 삭제 = 레드) ========================================================== */

/* ── 툴바 아이콘 버튼 (엑셀다운, 전체화면 등) ── */
.moca_grid_toolbar .button { background-color: transparent; border-color: var(--c-border-dark); box-shadow: none; }
.moca_grid_toolbar .button:hover { background-color: var(--c-primary-light); border-color: var(--c-primary); }
.moca_grid_toolbar .button:active { background-color: var(--c-primary-light); }
.moca_grid_toolbar .btn_save { background: var(--c-success); border: 1px solid var(--c-success); color: var(--c-bg); box-shadow: none; }
.moca_grid_toolbar .btn_save:hover { filter: brightness(0.88); }


/* ── page_root 높이 픽스 ── */
.page_root { display: flex; flex-direction: column; height: 100%; background: var(--c-bg); }

/* ── CKEditor 다크 테마 ── */
/* 원본 textarea 다크 — CKEDITOR.replace 로 교체되기 전(화면 오픈~에디터 생성 사이)
   UA 기본 흰 배경 textarea 가 그대로 노출되어 흰색 플래시로 보이는 것 방지. 일반 textarea 에도 다크 통일 */
textarea { background: var(--c-bg-input); color: var(--c-text); border: 1px solid var(--c-border); border-radius: var(--radius-sm); }
.cke_inner { background: none !important; }

/* 외곽 크롬 */
.cke_chrome { width: 100%; border: 0 !important; border-radius: var(--radius-sm) !important; background: var(--c-surface) !important; box-shadow: 0 0 0 1px var(--c-border) !important; }

/* 툴바 영역 */
.cke_top { background: var(--c-surface) !important; border-bottom: 1px solid var(--c-border) !important; padding: 4px 6px 2px !important; border-radius: 6px 6px 0 0 !important; }

/* 툴바 구분선 */
.cke_toolbar_separator { background-color: var(--c-border) !important; }
.cke_toolgroup { border-right: 1px solid var(--c-border) !important; }

/* 버튼 */
.cke_button { border-radius: 3px !important; }
.cke_button:hover { background: var(--c-bg-hover) !important; border-color: var(--c-border-dark) !important; }
.cke_button_on, .cke_button_on:hover { background: rgba(0,240,255,0.12) !important; border: 1px solid rgba(0,240,255,0.4) !important; }

/* 아이콘 밝게 (다크 배경에 맞게) */
.cke_button_icon { filter: brightness(0) invert(0.85); }
.cke_button_on .cke_button_icon { filter: brightness(0) invert(1) sepia(1) hue-rotate(152deg) saturate(5); }

/* 콤보(스타일/단락 드롭다운) */
.cke_combo_button { background: var(--c-bg-elevated) !important; border: 1px solid var(--c-border) !important; border-radius: 3px !important; }
.cke_combo_button:hover { background: var(--c-bg-hover) !important; border-color: var(--c-border-dark) !important; }
.cke_combo_text { color: var(--c-text-sub) !important; }
.cke_combo_arrow { border-top-color: var(--c-text-sub) !important; }

/* 하단 상태바 */
.cke_bottom { background: var(--c-bg) !important; border-top: 1px solid var(--c-border) !important; border-radius: 0 0 6px 6px !important; }
.cke_path_item, .cke_path_empty { color: var(--c-text-muted) !important; }
.cke_path_item:hover { background: var(--c-bg-hover) !important; color: var(--c-primary) !important; }
.cke_resizer { border-color: transparent var(--c-border) transparent transparent !important; }

/* 편집 영역 */
.cke_contents { border: none !important; }
.cke_wysiwyg_frame, .cke_wysiwyg_div { background: var(--c-bg-input) !important; }

/* 로딩 화이트 플래시 방지 — iframe 내부는 기본 contents.css(흰 배경) → ckeditor-theme.css(다크) 순서라
   그 사이 흰색이 노출됨. 편집영역을 기본 숨기고 instanceReady 시 .moca_cke_ready(textarea.js 전역 훅)로 표시.
   훅 미동작 대비 1.2s 후 자동 표시(keyframes 폴백). 숨김 동안엔 .cke_chrome 다크 배경이 보임 */
.cke_contents { opacity: 0; animation: mocaCkeReveal 0.15s ease 1.2s forwards; }
.cke.moca_cke_ready .cke_contents { opacity: 1; animation: none; }
@keyframes mocaCkeReveal { to { opacity: 1; } }

/* 소스 보기 모드 */
textarea.cke_source { background: var(--c-bg-input) !important; color: var(--c-text) !important; border: none !important; font-family: 'Consolas', 'Monaco', monospace !important; font-size: 12px !important; line-height: 1.6 !important; caret-color: var(--c-primary) !important; padding: 12px 16px !important; }

/* 드롭다운 패널 다크 테마 */
.cke_panel_block { background: var(--c-surface) !important; }
.cke_panel_grouptitle { background: var(--c-bg-elevated) !important; color: var(--c-text-sub) !important; border-bottom: 1px solid var(--c-border) !important; font-size: 10px !important; }
.cke_panel_list { background: var(--c-surface) !important; max-height: 260px !important; overflow-y: auto !important; }
.cke_panel_listItem a:link, .cke_panel_listItem a:visited { color: var(--c-text) !important; }
.cke_panel_listItem a:hover, .cke_panel_listItem:hover a { background: var(--c-bg-hover) !important; color: var(--c-primary) !important; }

/* 반응형 — 패널(.cke_panel)이 .cke 클래스를 공유하므로 반드시 제외 */
.cke:not(.cke_panel) { width: 100% !important; height: 100% !important; display: flex !important; flex-direction: column !important; }
.cke_top { flex-shrink: 0; }
.cke_chrome:not(.cke_panel) { height: 100% !important; }
.cke:not(.cke_panel) > .cke_inner { height: 100% !important; display: flex !important; flex-direction: column; }
.cke_panel.cke, .cke_panel.cke_chrome { width: auto !important; height: auto !important; display: block !important; }
.cke_panel .cke_inner { height: auto !important; display: block !important; }
.cke_contents { flex: 1 !important; height: auto !important; }
.cke_chrome { box-sizing: border-box !important; }
.flex-grow1 { flex-grow:1 }
.moca_table_form.flex [data-m-type="tbody"] { display:flex; flex-direction:column; height:100% }

/* ─── Dashboard 공통 스타일 ─────────── */
.dash_wrap { height: 100%; overflow: hidden; display: flex; flex-direction: column; background: var(--c-bg); box-sizing: border-box; }
.dash_inner { flex: 1; min-height: 0; display: flex; flex-direction: column; padding: 20px; min-width: 680px; }
.dash_header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 18px; flex-shrink: 0; }
.dash_title { font-size: 18px; font-weight: 700; color: var(--c-text); letter-spacing: -0.3px; display: flex; align-items: center; gap: 8px; }
.dash_subtitle { font-size: 12px; color: var(--c-text-muted); margin-top: 4px; }
.dash_btn_refresh { background: var(--c-primary); color: var(--c-bg); border: none; border-radius: var(--radius-md); padding: 7px 16px; font-size: 12px; font-weight: 700; cursor: pointer; transition: background .15s; white-space: nowrap; }
.dash_btn_refresh:hover { background: var(--c-primary-dark); }
.kpi_row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; flex-shrink: 0; }
.kpi_card { background: var(--c-bg); border: 1px solid var(--c-border); border-radius: var(--radius-lg); padding: 16px 18px; box-shadow: var(--shadow-sm); display: flex; align-items: center; gap: 14px; }
.kpi_icon { width: 48px; height: 48px; border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: 700; flex-shrink: 0; letter-spacing: -1px; }
.kpi_icon.sale { background: rgba(0,240,255,0.12); color: var(--c-primary); border: 1px solid rgba(0,240,255,0.25); }
.kpi_icon.member { background: rgba(34,211,160,0.12); color: var(--c-success); border: 1px solid rgba(34,211,160,0.25); }
.kpi_icon.project { background: rgba(255,160,64,0.12); color: var(--c-warning); border: 1px solid rgba(255,160,64,0.25); }
.kpi_icon.visit { background: rgba(255,104,104,0.12); color: var(--c-danger); border: 1px solid rgba(255,104,104,0.25); }
.kpi_info { flex: 1; min-width: 0; }
.kpi_label { font-size: 12px; color: var(--c-text-muted); margin-bottom: 5px; white-space: nowrap; }
.kpi_value { font-size: 22px; font-weight: 700; color: var(--c-text); line-height: 1.1; white-space: nowrap; }
.kpi_unit { font-size: 12px; font-weight: 400; color: var(--c-text-muted); margin-left: 2px; }
.chart_row { display: grid; gap: 14px; margin-bottom: 14px; flex: 1; min-height: 0; }
.chart_row:last-child { margin-bottom: 0; }
.chart_row.r6_4 { grid-template-columns: 6fr 4fr; }
.chart_row.r5_5 { grid-template-columns: 1fr 1fr; }
.chart_row.r3_7 { grid-template-columns: 3fr 7fr; }
.chart_box { background: var(--c-bg); border: 1px solid var(--c-border); border-radius: var(--radius-lg); padding: 16px 18px 12px; box-shadow: var(--shadow-sm); display: flex; flex-direction: column; min-height: 0; }
.chart_box_title { font-size: 12px; font-weight: 600; color: var(--c-text-sub); margin-bottom: 10px; letter-spacing: 0.2px; flex-shrink: 0; }
.chart_box .chart_el { min-height: 0; }

@media (max-width: 1024px) {
    .kpi_row { grid-template-columns: repeat(2,1fr); }
    .chart_row.r6_4, .chart_row.r5_5, .chart_row.r3_7 { grid-template-columns: 1fr; }
    /* 차트가 1열로 쌓이면 꽉채움 대신 스크롤 폴백 (각 차트 충분한 높이 확보) */
    .dash_wrap { overflow-y: auto; }
    .dash_inner { display: block; }
    .chart_row { flex: none; }
    .chart_box .chart_el { min-height: 220px; }
}
@media (max-width: 540px) {
    .kpi_row { grid-template-columns: 1fr; }
    .dash_inner { padding: 12px 14px; min-width:unset; }
    .page_wrap { padding: 12px; }
}

/* ─── 위젯 대시보드 (TDEMO_WIDGET_DASHBOARD) ────────────────── */

/* 래퍼 */
.wdash_wrap  { height:100%; overflow-y:auto; background:var(--c-bg); }
.wdash_inner { min-width:680px; }

/* 헤더 */
.wdash_hd                   { display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:18px; gap:12px; }
.wdash_hd_left .wdash_title { font-size:18px; font-weight:700; color:var(--c-text); letter-spacing:-.3px; display:flex; align-items:center; gap:8px; }
.wdash_hd_left .wdash_ts    { font-size:12px; color:var(--c-text-muted); margin-top:4px; }
.wdash_hd_right             { display:flex; align-items:center; gap:8px; flex-shrink:0; }
.wdash_edit_buttons         { display:flex; align-items:center; gap:8px; }
.wdash_edit_buttons[hidden] { display:none; }

/* 헤더 버튼 */
.btn_dsh_refresh { padding:6px 14px; background:transparent; color:var(--c-text-muted); border:1px solid var(--c-border); border-radius:var(--radius-md); font-size:12px; font-weight:600; cursor:pointer; transition:all .15s; }
.btn_dsh_refresh:hover { color:var(--c-primary); border-color:var(--c-primary); }
.btn_dsh_edit    { padding:6px 16px; background:var(--c-surface); color:var(--c-text-sub); border:1px solid var(--c-border-dark); border-radius:var(--radius-md); font-size:12px; font-weight:600; cursor:pointer; transition:all .15s; }
.btn_dsh_edit:hover    { border-color:var(--c-primary); color:var(--c-primary); }

.btn_dsh_save    { padding:6px 16px; background:var(--c-primary); color:var(--c-bg); border:none; border-radius:var(--radius-md); font-size:12px; font-weight:700; cursor:pointer; transition:background .15s; }
.btn_dsh_save:hover    { background:var(--c-primary-dark); }
.btn_dsh_cancel  { padding:6px 16px; background:transparent; color:var(--c-text-muted); border:1px solid var(--c-border); border-radius:var(--radius-md); font-size:12px; font-weight:600; cursor:pointer; transition:all .15s; }
.btn_dsh_cancel:hover  { color:var(--c-text); border-color:var(--c-border-dark); }
.btn_dsh_add     { display:flex; align-items:center; gap:5px; padding:6px 14px; background:transparent; color:var(--c-primary); border:1px dashed var(--c-primary); border-radius:var(--radius-md); font-size:12px; font-weight:600; cursor:pointer; transition:all .15s; }
.btn_dsh_add:hover     { background:var(--c-primary-light); }

/* 편집 모드 안내 배너 */
.edit_banner      { display:none; align-items:center; gap:8px; padding:8px 14px; background:rgba(0,240,255,.06); border:1px solid rgba(0,240,255,.2); border-radius:var(--radius-md); margin-bottom:16px; }
.edit_banner.show { display:flex; }
.edit_banner_txt  { font-size:12px; color:var(--c-text-sub); }

/* 위젯 그리드
   기준 행 단위: 150px
   기본 아이템은 span 2 (150×2 + gap 14 = 314px ≈ 1행)
   달력처럼 높이가 필요한 위젯도 6주짜리 월에 여유 있게 표시됨
   1행 높이를 조정하려면 grid-auto-rows 값을 변경 */
.widget_grid { display:grid; grid-template-columns:repeat(4,1fr); grid-auto-rows:150px; gap:14px; }
/* 로드 전 카드 진행표시(widget.js _fillSource) — frame.load 완료 시 내용으로 교체돼 사라진다 */
.widget_loading { display:flex; align-items:center; justify-content:center; height:100%; min-height:80px; }
.widget_loading span {
	width:22px; height:22px; border-radius:50%;
	border:2px solid var(--c-border-dark); border-top-color:var(--c-primary);
	animation: widget_spin .8s linear infinite;
}
@keyframes widget_spin { to { transform: rotate(360deg); } }

/* 위젯 아이템 — flex 컬럼으로 편집 바 + 카드를 쌓음 */
.widget_item       { display:flex; flex-direction:column; border-radius:var(--radius-lg); border:2px solid transparent; transition:border-color .15s, box-shadow .15s, opacity .2s; min-height:0; grid-row:span 2; }
/* 너비 클래스 */
.widget_item.ws_c2 { grid-column:span 2; }
.widget_item.ws_c3 { grid-column:span 3; }
.widget_item.ws_c4 { grid-column:span 4; }
/* 높이 클래스 (기준 단위의 배수) */
.widget_item.ws_rh { grid-row:span 1; }  /* ½행 = 150px */
.widget_item.ws_r2 { grid-row:span 4; }  /* 2행 = 150×4+gap×3 = 648px */
.widget_item.ws_r3 { grid-row:span 6; }  /* 3행 = 150×6+gap×5 = 980px */

/* 편집 모드 상태 */
.widget_grid.edit_mode .widget_item          { cursor:grab; border-color:var(--c-border-dark); border-style:dashed; }
.widget_grid.edit_mode .widget_item:hover    { border-color:var(--c-primary); }
.widget_grid.edit_mode .widget_item.dragging { opacity:.3; cursor:grabbing; }
.widget_grid.edit_mode .widget_item.drag_over { border-color:var(--c-primary); border-style:solid; box-shadow:0 0 0 3px rgba(0,240,255,.2); }

/* 위젯 편집 바 — 일반 flow (absolute 아님), 카드 위에 쌓임 */
.widget_edit_bar { display:none; flex-shrink:0; align-items:center; justify-content:space-between; padding:5px 8px; background:rgba(11,17,22,.92); border-radius:var(--radius-lg) var(--radius-lg) 0 0; overflow:visible; }
.widget_grid.edit_mode .widget_edit_bar { display:flex; }
.widget_drag_handle { color:var(--c-text-muted); font-size:15px; cursor:grab; user-select:none; padding:0 4px; line-height:1; letter-spacing:1px; }
.widget_drag_handle:hover { color:var(--c-primary); }
.widget_remove_btn  { width:20px; height:20px; display:flex; align-items:center; justify-content:center; background:transparent; border:1px solid var(--c-border); border-radius:50%; color:var(--c-text-muted); font-size:12px; cursor:pointer; transition:all .15s; }
.widget_remove_btn:hover { background:var(--c-danger); border-color:var(--c-danger); color:#fff; }
/* 크기 조절 피커 */
.wsp_wrap          { position:relative; }
.widget_resize_btn { background:rgba(0,240,255,.1); border:1px solid rgba(0,240,255,.3); color:var(--c-primary); border-radius:6px; font-size:12px; padding:2px 7px; cursor:pointer; white-space:nowrap; transition:background .15s; }
.widget_resize_btn:hover { background:rgba(0,240,255,.2); }
.ws_picker         { display:none; position:absolute; top:calc(100% + 4px); left:0; z-index:300; background:#0d1b2a; border:1px solid var(--c-border-dark); border-radius:10px; padding:10px 12px; min-width:190px; box-shadow:0 8px 24px rgba(0,0,0,.6); }
.ws_picker.open    { display:block; }
.wsp_row           { display:flex; align-items:center; gap:5px; margin-bottom:8px; }
.wsp_row:last-child { margin-bottom:0; }
.wsp_lbl           { font-size:12px; color:var(--c-text-muted); min-width:28px; flex-shrink:0; }
.wsp_btn           { background:rgba(255,255,255,.06); border:1px solid var(--c-border); color:var(--c-text-sub); border-radius:6px; font-size:12px; padding:3px 9px; cursor:pointer; transition:all .15s; }
.wsp_btn:hover     { background:rgba(0,240,255,.15); border-color:var(--c-primary); color:var(--c-primary); }
.wsp_btn.active    { background:rgba(0,240,255,.18); border-color:var(--c-primary); color:var(--c-primary); font-weight:700; }

/* 위젯 카드 — flex:1로 나머지 공간 채움 */
.widget_card { background:var(--c-surface); border:1px solid var(--c-border); border-radius:var(--radius-lg); overflow:hidden; flex:1; min-height:0; display:flex; flex-direction:column; }
/* 편집 모드에서 카드 상단 모서리 제거 (편집 바와 자연스럽게 연결) */
.widget_grid.edit_mode .widget_item .widget_card { border-color:transparent; border-top-left-radius:0; border-top-right-radius:0; }
.widget_card_hd    { display:flex; align-items:center; padding:12px 16px 10px; border-bottom:1px solid var(--c-border); flex-shrink:0; }
.widget_card_title { font-size:13px; font-weight:700; color:var(--c-text); flex:1; min-width:0; }
/* source HTML 위젯은 widget_body → frame → definition 루트까지 높이를 이어받는다. */
.widget_body, .moca_widget_frame { flex:1; min-height:0; display:flex; flex-direction:column; }
[data-m-widget-definition] { flex:1; min-height:0; }
[data-m-widget-definition]:not(.widget_kpi_inner) { display:flex; flex-direction:column; }

/* KPI 위젯 */
.widget_kpi_inner { flex:1; display:flex; align-items:center; gap:14px; padding:18px 16px; }
.widget_kpi_icon  { width:46px; height:46px; border-radius:var(--radius-md); display:flex; align-items:center; justify-content:center; font-size:18px; font-weight:700; flex-shrink:0; }
.widget_kpi_icon.cyan   { background:rgba(0,240,255,.12);  color:var(--c-primary); }
.widget_kpi_icon.green  { background:rgba(34,211,160,.12); color:var(--c-success); }
.widget_kpi_icon.purple { background:rgba(168,85,247,.12); color:var(--c-warning); }
.widget_kpi_icon.orange { background:rgba(255,160,64,.12); color:var(--c-danger); }
.widget_kpi_label { font-size:12px; color:var(--c-text-muted); margin-bottom:5px; }
.widget_kpi_value { font-size:22px; font-weight:800; color:var(--c-text); line-height:1.1; }
.widget_kpi_value .ku { font-size:13px; font-weight:500; color:var(--c-text-muted); margin-left:2px; }

/* 차트 위젯 — 남은 높이 전체 채움 */
.widget_chart_body { flex:1; min-height:0; padding:8px 12px 12px; display:flex; flex-direction:column; }
.chart_el { flex:1; width:100%; min-height:160px; }

/* 목록 위젯 */
.widget_list_body       { flex:1; overflow-y:auto; min-height:0; }
.widget_list_item       { display:flex; align-items:center; gap:10px; padding:9px 16px; border-bottom:1px solid var(--c-border); transition:background .1s; cursor:default; }
.widget_list_item:last-child { border-bottom:none; }
.widget_list_item:hover { background:var(--c-bg-hover); }
.widget_list_badge        { flex-shrink:0; width:6px; height:6px; border-radius:50%; }
.widget_list_badge.cyan   { background:var(--c-primary); }
.widget_list_badge.green  { background:var(--c-success); }
.widget_list_badge.orange { background:var(--c-warning); }
.widget_list_badge.red    { background:var(--c-danger); }
.widget_list_text { flex:1; min-width:0; }
.widget_list_main { font-size:12px; color:var(--c-text); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.widget_list_sub  { font-size:12px; color:var(--c-text-muted); margin-top:1px; }
.widget_list_date { font-size:12px; color:var(--c-text-muted); flex-shrink:0; }

/* 위젯 추가 오버레이 */
.add_widget_overlay     { display:none; position:fixed; inset:0; background:rgba(0,0,0,.55); z-index:500; align-items:center; justify-content:center; }
.add_widget_overlay.open { display:flex; }
.add_widget_panel        { background:var(--c-surface); border:1px solid var(--c-border-dark); border-radius:var(--radius-lg); padding:22px; min-width:340px; max-width:460px; width:92%; box-shadow:var(--shadow-lg); }
.add_widget_panel_title  { font-size:15px; font-weight:700; color:var(--c-text); margin-bottom:4px; }
.add_widget_panel_sub    { font-size:12px; color:var(--c-text-muted); margin-bottom:16px; }
.add_widget_list         { display:flex; flex-direction:column; gap:6px; max-height:300px; overflow-y:auto; margin-bottom:14px; }
.add_widget_btn          { display:flex; align-items:center; gap:12px; padding:10px 14px; background:var(--c-bg); border:1px solid var(--c-border); border-radius:var(--radius-md); cursor:pointer; text-align:left; transition:all .15s; }
.add_widget_btn:hover    { border-color:var(--c-primary); background:var(--c-primary-pale); }
.add_widget_btn.is_added { opacity:.55; cursor:default; }
.add_widget_btn.is_added:hover { border-color:var(--c-border); background:var(--c-bg); }
.add_widget_btn_icon     { width:34px; height:34px; border-radius:var(--radius-sm); background:var(--c-surface); display:flex; align-items:center; justify-content:center; font-size:15px; flex-shrink:0; }
.add_widget_btn_name     { font-size:13px; color:var(--c-text); font-weight:600; }
.add_widget_btn_type     { font-size:12px; color:var(--c-text-muted); margin-top:1px; }
.add_widget_empty        { text-align:center; padding:28px; color:var(--c-text-muted); font-size:12px; }
.add_widget_close        { width:100%; padding:8px; background:transparent; border:1px solid var(--c-border); border-radius:var(--radius-md); color:var(--c-text-muted); font-size:12px; cursor:pointer; transition:all .15s; }
.add_widget_close:hover  { border-color:var(--c-border-dark); color:var(--c-text); }

/* 모바일 ↑↓ 이동 버튼 (데스크톱에서는 숨김) */
.widget_move_btns { display:none; align-items:center; gap:4px; }
.widget_move_btn  { width:22px; height:22px; display:flex; align-items:center; justify-content:center; background:transparent; border:1px solid var(--c-border); border-radius:var(--radius-sm); color:var(--c-text-muted); font-size:13px; cursor:pointer; transition:all .15s; }
.widget_move_btn:hover { background:var(--c-primary-light); border-color:var(--c-primary); color:var(--c-primary); }
.widget_move_btn:disabled { opacity:.3; cursor:default; }

/* ── 날씨 위젯 ───────────────────────────────────── */
.ww_wrap     { display:flex; flex-direction:column; height:100%; padding:16px; gap:12px; }
.ww_loading  { flex:1; display:flex; align-items:center; justify-content:center; color:var(--c-text-muted); font-size:13px; }
.ww_top      { display:flex; align-items:center; gap:14px; }
.ww_icon     { font-size:46px; line-height:1; flex-shrink:0; }
.ww_main     { flex:1; }
.ww_temp     { font-size:34px; font-weight:700; color:var(--c-text); line-height:1; }
.ww_deg      { font-size:16px; font-weight:400; color:var(--c-text-sub); margin-left:2px; }
.ww_city     { font-size:12px; color:var(--c-text-sub); margin-top:3px; }
.ww_desc     { font-size:12px; color:var(--c-primary); margin-top:2px; }
.ww_details  { display:grid; grid-template-columns:repeat(4,1fr); gap:4px; padding:10px 0; border-top:1px solid var(--c-border); border-bottom:1px solid var(--c-border); }
.ww_det      { display:flex; flex-direction:column; align-items:center; gap:3px; }
.ww_lbl      { font-size:12px; color:var(--c-text-muted); }
.ww_val      { font-size:12px; font-weight:600; color:var(--c-text-body); }
.ww_forecast { display:flex; gap:4px; }
.ww_fc       { display:flex; flex-direction:column; align-items:center; gap:2px; flex:1; padding:6px 2px; background:rgba(255,255,255,0.03); border-radius:8px; }
.ww_fc_day   { font-size:12px; color:var(--c-text-muted); }
.ww_fc_ico   { font-size:15px; }
.ww_fc_hi    { font-size:12px; font-weight:600; color:var(--c-text-body); }
.ww_fc_lo    { font-size:12px; color:var(--c-text-muted); }
.ww_no_data { display:flex; align-items:center; justify-content:center; height:100%; text-align:center; font-size:13px; color:var(--c-text-muted); line-height:1.8; }

/* ── 도시 피커 ───────────────────────────────────── */
.ww_city_wrap { position:relative; flex-shrink:0; margin-right:6px; }
.ww_city_btn { padding:2px 8px; font-size:12px; background:#0e1e2d; border:1px solid var(--c-border-dark); color:var(--c-text-body); border-radius:4px; cursor:pointer; white-space:nowrap; max-width:72px; overflow:hidden; text-overflow:ellipsis; }
.ww_city_btn:hover { border-color:rgba(0,240,255,0.5); color:var(--c-primary); }
.ww_city_list { display:none; position:absolute; top:calc(100% + 4px); right:0; z-index:200; background:var(--c-surface); border:1px solid var(--c-border-dark); border-radius:6px; width:100px; padding:4px 0; box-shadow:0 8px 24px rgba(0,0,0,0.7); }
.ww_city_list.open { display:block; }
.ww_city_item { padding:5px 12px; font-size:12px; color:var(--c-text-sub); cursor:pointer; user-select:none; }
.ww_city_item:hover { color:var(--c-primary); background:var(--c-primary-pale); }
.ww_city_item.active { color:var(--c-primary); font-weight:700; }

/* ── 날씨 지역 이미지 ─────────────────────────────── */
.ww_region_img { flex-shrink:0; display:flex; align-items:center; }
.ww_rimg { width:64px; height:64px; opacity:0.75; filter:drop-shadow(0 0 8px rgba(0,240,255,0.45)); }

/* ── 달력 위젯(demo) — calendar 컴포넌트를 카드 높이에 맞춰 채운다 ───────────
   컴포넌트 기본은 "내용만큼"이라, 카드처럼 높이가 정해진 자리에서는 격자가 남은 높이를 나눠 갖게 한다 */
.wgt_cal_wrap { display:flex; flex-direction:column; gap:6px; height:100%; padding:12px; box-sizing:border-box; }
.wgt_cal_wrap .moca_cal { flex:1; min-height:0; padding:0; border:none; background:transparent; }
.wgt_cal_wrap .moca_cal .mcal_body { flex:1; }
.wgt_cal_wrap .moca_cal .mcal_grid_day { flex:1; min-height:0; grid-auto-rows:1fr; }
.wgt_cal_wrap .moca_cal .mcal_day { height:auto; min-height:22px; }
/* 카드가 좁으므로 범례 여백만 줄인다(글자 크기는 12px 하한 유지) */
.wgt_cal_wrap .moca_cal .mcal_legend { margin-top:6px; padding-top:6px; gap:2px 10px; }
.wgt_cal_pick { flex:none; text-align:right; font-size:12px; color:var(--c-text-sub); }

/* ── 달력 위젯(구 수기 마크업) — ERP 대시보드(DSH001)가 아직 이 클래스로 자기 달력을 그린다.
      demo 위젯은 calendar 컴포넌트로 이관했으므로, ERP 도 이관하면 이 블록을 지운다 ── */
.wc_wrap    { display:flex; flex-direction:column; height:100%; padding:16px; }
.wc         { display:flex; flex-direction:column; height:100%; gap:12px; }
.wc_hd      { display:flex; align-items:center; justify-content:space-between; }
.wc_title   { font-size:14px; font-weight:700; color:var(--c-text); letter-spacing:-.3px; }
.wc_nav     { background:rgba(255,255,255,0.05); border:none; color:var(--c-text-sub); width:28px; height:28px; border-radius:8px; cursor:pointer; font-size:18px; display:flex; align-items:center; justify-content:center; padding:0; transition:all .15s; }
.wc_nav:hover { background:var(--c-primary-light); color:var(--c-primary); }
.wc_grid    { display:grid; grid-template-columns:repeat(7,1fr); }
.wc_dow     { font-size:12px; font-weight:600; text-align:center; padding:2px 0 8px; color:var(--c-text-muted); }
.wc_dow_sun { color:#FF6868; }
.wc_dow_sat { color:#60A5FA; }
/* 구분선: grid-column span 7 */
.wc_sep     { grid-column:1 / -1; height:1px; background:var(--c-border); margin-bottom:4px; }
.wc_day     { display:flex; align-items:center; justify-content:center; padding:3px 0; }
/* 원형 날짜 셀 — 고정 크기로 정원 보장 */
.wc_di      { width:28px; height:28px; display:flex; align-items:center; justify-content:center; border-radius:50%; font-size:12px; color:var(--c-text-sub); transition:background .12s,color .12s; cursor:pointer; }
.wc_di:hover  { background:rgba(255,255,255,0.08); color:var(--c-text); }
.wc_sun .wc_di { color:#FF6868; }
.wc_sat .wc_di { color:#60A5FA; }
.wc_today .wc_di { background:var(--c-primary) !important; color:#0a1628 !important; font-weight:700; box-shadow:0 0 8px rgba(0,240,255,.4); }

/* 뉴스 위젯 — source 색 강조 */
.wl_source { color:var(--c-primary) !important; }

.edit_banner_icon { color:var(--c-primary); font-size:13px; }
.rank_c4 { color:var(--c-text-muted); }
.player_c_jungho  { color:var(--c-primary); }
.player_c_ara     { color:var(--c-success); }
.player_c_lope    { color:#FFA040; }
.player_c_ohtani  { color:#A78BFA; }
.player_c_other   { color:var(--c-text-muted); }

/* ── MDI 컴포넌트 (data-m-type="mdi") ── */
.moca_mdi { display:flex; flex-direction:column; height:100%; overflow:hidden; }
.mdi_box { display:flex; flex-direction:column; height:100%; overflow:hidden; }
.mdi_ly { display:flex; flex-direction:column; flex:1; overflow:hidden; }
.mdi_ly_sub { display:none;}
.resizingbar { display:none; width:4px; flex-shrink:0; border-left:1px solid var(--c-footer-text); border-right:1px solid var(--c-footer-text); cursor:col-resize; transition:background 0.15s; }
.resizingbar:hover { background:var(--c-primary); border-color:var(--c-primary); }
.mdi_box.mdi_split { flex-direction:row; }
.mdi_box.mdi_split .resizingbar { display:block; }
.mdi_box.mdi_split .mdi_ly { min-width:280px; box-sizing:border-box; }
.mdi_box.mdi_split .mdi_ly:not(.mdi_ly_sub) { flex:none; width:50%; }
.mdi_box.mdi_split .mdi_ly_sub { flex:1; display:flex; }
.mdi_drop_hint { display:none; flex-direction:column; align-items:center; justify-content:center; gap:14px; height:100%; color:var(--c-text-muted); font-size:13px; pointer-events:none; }
.mdi_drop_hint::before { content:''; display:block; width:52px; height:52px; background:var(--c-text-muted); -webkit-mask:url('../images/icon_mdi_drop_right.svg') no-repeat center / 52px 52px; mask:url('../images/icon_mdi_drop_right.svg') no-repeat center / 52px 52px; }
.mdi_ly:not(.mdi_ly_sub) .mdi_drop_hint::before { -webkit-mask-image:url('../images/icon_mdi_drop_left.svg'); mask-image:url('../images/icon_mdi_drop_left.svg'); }
.mdi_drop_hint span { opacity:0.6; }
.mdi_ly:not(:has(.mdi_tab)) .mdi_drop_hint { display:flex; }
.mdi_ly:not(:has(.mdi_tab)) .mdi_body { border:2px dashed var(--c-border-dark); border-radius:var(--radius-md); margin:8px; box-sizing:border-box; }
.mdi_drag_over:not(:has(.mdi_tab)) .mdi_body { border-color:var(--c-primary); }
.mdi_drag_over:not(:has(.mdi_tab)) .mdi_drop_hint { color:var(--c-primary); }

/* 탭 드래그 재배치 — 삽입 위치 표시선(레이아웃 흔들림 없는 inset box-shadow). before=이 탭 앞, end=맨 뒤 */
.mdi_tab.mdi_tab_drop_before { box-shadow: inset 2px 0 0 var(--c-primary); }
.mdi_tab.mdi_tab_drop_end { box-shadow: inset -2px 0 0 var(--c-primary); }
/* 탭바 전체를 mdi_body 위로 1px 겹침(margin-bottom:-1px + z-index:1) —
   탭 개별 -1px 는 .mdi_tab_scroll(overflow-y:hidden)에 잘려서 활성탭 하단보더가 body 상단보더를 못 덮었음.
   활성탭 하단보더(--c-bg)만 body 상단보더를 가리고, 비활성탭(투명)·빈영역은 body 보더가 그대로 비침 */
.mdi_tabbar { display:flex; align-items:flex-end; background:transparent; flex-shrink:0; position:relative; z-index:1; margin-bottom:-1px; padding:15px; padding-bottom: 0;}
/* 가로 스크롤바 숨김 — 스크롤 자체는 좌우 시프트 버튼(◀▶)·휠로 동작 */
.mdi_tab_scroll { display:flex; align-items:flex-end; flex:1; gap:6px; overflow-x:auto; overflow-y:hidden; scrollbar-width:none; }
.mdi_tab_scroll::-webkit-scrollbar { display:none; }
/* 비활성탭: 하단보더 없음 + margin-bottom:1px — 박스가 body 상단보더 1px 위에서 끝나
   경계선이 탭 아래로 온전히 이어짐. (투명 하단보더 방식은 좌/우 보더가 모서리 픽셀에1
   대각선으로 침범해 경계선 위에 점처럼 보이는 문제가 있었음)
   margin 1px = 활성탭 하단보더 1px → flex-end 정렬에서 텍스트 수직위치 동일 유지 */
.mdi_tab { position:relative; z-index:0; display:flex; align-items:center; gap:6px; padding:7px 10px 7px 16px; width:170px; box-sizing:border-box; flex-shrink:0; background:var(--c-grid-toolbar); border:1px solid var(--c-border); border-bottom:none; margin-bottom:1px; border-radius:6px 6px 0 0; cursor:pointer; color:var(--c-text); font-size:13px; outline:none; transition:background 0.15s, }
.mdi_tab_pinned { padding:7px 22px; }
.mdi_tab:hover { background:var(--c-bg-elevated); }
/* 활성탭: 하단보더 없이 padding-bottom +1px(margin-bottom:0) 로 박스를 1px 내려
   배경(--c-bg)이 body 상단보더를 덮음(컨텐츠와 연결). 하단보더(--c-bg) 방식은 좌/우 primary
   보더와의 모서리 대각분할 때문에 body 좌측 세로경계선 끝에 어두운 점이 생겼음 —
   보더 없이 배경으로 덮으면 좌/우 보더가 박스 바닥까지 곧게 내려가 body 세로보더와 그대로 이어짐 */
.mdi_tab.active { position:relative; background:var(--c-bg); color:var(--c-primary); border-top:1px solid var(--c-primary); border-left:1px solid var(--c-primary); border-right:1px solid var(--c-primary); border-bottom:none; padding-bottom:8px; margin-bottom:0; }
.mdi_tab_label { user-select:none; flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.mdi_tab_close_btn { position:relative; width:16px; height:16px; border:none; background:transparent; cursor:pointer; border-radius:3px; flex-shrink:0; transition:background 0.12s; }
.mdi_tab_close_btn::before { content:''; position:absolute; inset:0; background:var(--c-text-muted); -webkit-mask:url('../images/icon_close.svg') no-repeat center / 10px 10px; mask:url('../images/icon_close.svg') no-repeat center / 10px 10px; transition:background 0.12s; }
.mdi_tab_close_btn:hover { background:rgba(255,80,80,0.2); }
.mdi_tab_close_btn:hover::before { background:#ff6b6b; }
/* 분할/전체닫기 버튼 — 좌우 시프트 버튼(.mdi_tabnav_btn)과 동일 규격(stretch 높이·폭 22px·margin-bottom 2px)으로 통일 */
/* 분할 버튼 — 기본부터 밝은(활성) 상태: primary 테두리/아이콘, hover 는 글로우만 */
.mdi_split_btn { position:relative; align-self:stretch; margin-left:5px; height:auto; min-height:25px; margin-bottom:5px; width:22px; padding:0; border:1px solid var(--c-primary); border-radius:var(--radius-sm); background:var(--c-bg-elevated); cursor:pointer; flex-shrink:0; transition:box-shadow 0.15s; }
.mdi_split_btn::before { content:''; position:absolute; inset:0; background:var(--c-primary); -webkit-mask:url('../images/icon_mdi_split.svg') no-repeat center / 14px 14px; mask:url('../images/icon_mdi_split.svg') no-repeat center / 14px 14px; }
.mdi_split_btn:hover { box-shadow:0 0 6px rgba(0,240,255,0.35); }
.mdi_split_btn.active::before { -webkit-mask:url('../images/icon_mdi_merge.svg') no-repeat center / 14px 14px; mask:url('../images/icon_mdi_merge.svg') no-repeat center / 14px 14px; }
.mdi_closeall_btn { position:relative; align-self:stretch; margin-left:5px; height:auto; min-height:25px; margin-bottom:5px; width:22px; padding:0; border:1px solid var(--c-border); border-radius:var(--radius-sm); background:var(--c-primary-light); cursor:pointer; flex-shrink:0; display:flex; align-items:center; justify-content:center; }
.mdi_closeall_btn::before { content:''; position:absolute; inset:0; background:var(--c-text-muted); -webkit-mask:url('../images/icon_trash.svg') no-repeat center / 14px 14px; mask:url('../images/icon_trash.svg') no-repeat center / 14px 14px; }
.mdi_closeall_btn:hover { background:rgba(255,80,80,0.15); border:1px solid var(--c-danger); }
.mdi_closeall_btn:hover::before { background:var(--c-danger) }
/* 탭 좌우 이동(한 칸씩 스크롤) 버튼 — CSS 셰브론(별도 SVG 불필요) */
/* 좌우 시프트 버튼 — 탭바를 stretch 로 채우되 margin-bottom:5px 로 body 상단 경계선과 여백을 둠
   (탭바가 body 위 1px 겹침: 1px 겹침 보정 + 4px 간격 = 예전 가운데정렬(25px) 버튼의 바닥 여백과 동일감.
   높이는 stretch 가 margin 만큼 자동으로 줄임)
   활성(이동 가능) 버튼은 기본부터 밝은 상태(primary 테두리/셰브론) — hover 는 글로우만. 비활성(끝 도달)만 어둡게 */
.mdi_tabnav_btn { position:relative; align-self:stretch; height:auto; margin-bottom:5px; width:22px; padding:0; border:1px solid var(--c-primary); border-radius:var(--radius-sm); background:var(--c-bg-elevated); cursor:pointer; flex-shrink:0; transition:box-shadow 0.15s; }
.mdi_tabnav_prev { margin-right:5px; }
.mdi_tabnav_next { margin-left:5px; }
.mdi_tabnav_btn::before { content:''; position:absolute; top:50%; left:50%; width:7px; height:7px; border-left:2px solid var(--c-primary); border-bottom:2px solid var(--c-primary); }
.mdi_tabnav_prev::before { transform:translate(-30%,-50%) rotate(45deg); }
.mdi_tabnav_next::before { transform:translate(-70%,-50%) rotate(-135deg); }
.mdi_tabnav_btn:hover { box-shadow:0 0 6px rgba(0,240,255,0.35); }
.mdi_tabnav_btn:disabled { opacity:0.35; cursor:default; border-color:var(--c-border); background:var(--c-primary-light); box-shadow:none; }
.mdi_tabnav_btn:disabled::before { border-color:var(--c-text-muted); }
.mdi_body { flex:1; overflow:hidden; position:relative; background:var(--c-bg); border:1px solid var(--c-primary);}
.mdi_panel { position:absolute; inset:0; overflow-y:auto; overflow-x:hidden; padding:var(--m-edge); }

/* 화면 루트(page_wrap) — 라이트 테마의 패널 카드 스타일은 theme-light-fix.css 에서 부여 */
.mdi_panel > .page_wrap { padding:0; overflow: unset;}

/* ── Tab 컴포넌트 (data-m-type="tab") ── */
.moca_tab { display:flex; flex-direction:column; width:100%; flex-grow:1; min-height:0; }
.moca_tab_header { display:flex; align-items:flex-end; padding:4px 0 0; gap:4px; }
/* mdi_tab 과 동일 — 투명 하단보더로 활성/비활성 박스높이 통일(텍스트 수직위치 고정) */
.moca_tab_btn { position:relative; z-index:0; background:var(--c-grid-toolbar); border:1px solid var(--c-border); border-bottom:1px solid transparent; border-radius:var(--radius-sm) var(--radius-sm) 0 0; margin-bottom:-1px; padding:7px 22px; font-size:13px; color:var(--c-text-muted); cursor:pointer; white-space:nowrap; transition:color 0.15s, background 0.15s; }
.moca_tab_btn:hover { color:var(--c-text); background:var(--c-bg-elevated); }
/* z-index:2 — 패널(z-index:1)보다 위에 그려 활성탭 하단보더(--c-bg)가 패널 상단보더를 덮게 함(탭↔컨텐츠 경계선 제거) */
/* 보더는 MDI(primary)보다 한 단계 어두운 primary-deeper — MDI 안에 Tab 이 중첩될 때(TDEMO_TAB) 경계 구분 목적 */
.moca_tab_btn.active { position:relative; z-index:2; color:var(--c-primary); font-weight:600; background:var(--c-bg); border: 1px solid var(--c-text-muted); border-bottom: 0;}
/* 비활성 탭(data-m-disabled) — 반투명+금지 커서. 네이티브 :disabled 를 안 쓰므로(클릭 이벤트 보존
   — ontabclick 은 비활성 탭에서도 발화해야 함) hover 효과를 기본색으로 되돌려 무효화한다 */
.moca_tab_btn[data-m-disabled="true"] { opacity:0.45; cursor:not-allowed; }
.moca_tab_btn[data-m-disabled="true"]:hover { color:var(--c-text-muted); background:var(--c-grid-toolbar); }

/* 닫기(✕) 붙은 탭 — 라벨과 ✕ 를 한 줄에 세운다(data-m-closable="true" 또는 addTab({closable:true})) */
.moca_tab_btn:has(.moca_tab_close) { display:inline-flex; align-items:center; gap:6px; padding-right:12px; }
.moca_tab_close { position:relative; width:14px; height:14px; flex-shrink:0; border-radius:3px; cursor:pointer; transition:background 0.12s; }
.moca_tab_close::before { content:''; position:absolute; inset:0; background:var(--c-text-muted); -webkit-mask:url('../images/icon_close.svg') no-repeat center / 9px 9px; mask:url('../images/icon_close.svg') no-repeat center / 9px 9px; transition:background 0.12s; }
.moca_tab_close:hover { background:var(--c-bg-elevated); }
.moca_tab_close:hover::before { background:var(--c-danger); }
.moca_tab_panel { position:relative; z-index:1; flex:1; min-height:0; padding:0; background:var(--c-bg); border:1px solid var(--c-text-muted); border-radius:0 var(--radius-md) var(--radius-md) var(--radius-md); overflow:auto; }
.moca_tab_panel_inner { padding:14px; }

/* ── 사이드바 접기/펼치기 ── */
.left { position:relative; transition:width 0.25s ease; }
.left.folded { width:0px; overflow:hidden; cursor:pointer; }
.menufold {z-index:2; position: absolute; right: 0; top: 5px; width: 15px; height: 40px; background: var(--c-surface); border: 1px solid var(--c-border); border-radius: 3px 0 0 3px; cursor: pointer; transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;}
.menufold::before { content:''; position:absolute; inset:0; background:var(--c-text-muted); -webkit-mask:url('../images/icon_chevron.svg') no-repeat center / 8px 8px; mask:url('../images/icon_chevron.svg') no-repeat center / 8px 8px; transition:background 0.15s; }
.menufold:hover { background:var(--c-primary-light); border-color:var(--c-primary); box-shadow:0 0 6px rgba(0,240,255,0.2); }
.menufold:hover::before { background:var(--c-primary); }
.left.folded .menufold {position: fixed; top: 59px; left: 0; border-radius: 0 3px 3px 0;}
.left.folded .menufold::before {rotate:180deg}
.left.folded:hover { background:var(--c-primary-light) !important; }

/* ── 통신 진행 프로그레스 (tran.exe dim/modal) ── */
.moca_progress_dim { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; background: rgba(5,10,14,0.6); z-index: 99990; }
.moca_progress_box { display: flex; flex-direction: column; align-items: center; gap: 14px; padding: 26px 36px; background: var(--c-bg-elevated); border: 1px solid var(--c-primary); border-radius: 10px; box-shadow: 0 0 18px rgba(0,240,255,0.35), 0 8px 30px rgba(0,0,0,0.55); }
.moca_progress_spinner { width: 50px; height: 50px; background: url('../images/progress_spinner.svg') no-repeat center / contain; filter: drop-shadow(0 0 6px rgba(0,240,255,0.55)); animation: moca-progress-spin 0.9s linear infinite; }
.moca_progress_text { font-size: 13px; font-weight: 600; letter-spacing: 1px; color: var(--c-primary); }
.moca_progress_time { font-size: 12px; font-weight: 600; letter-spacing: 0.5px; color: var(--c-text-muted); font-variant-numeric: tabular-nums; }
/* 「12.3초 / 30초」의 상한 쪽 — 움직이는 값(경과)이 주인공이고 상한은 기준선이라 한 단계 흐리게 */
.moca_progress_limit { font-weight: 500; opacity: 0.62; }

@keyframes moca-progress-spin {
    to { transform: rotate(360deg); }
}

/* ── 장시간 데이터 수집 상태 카드 ── */
.moca_table_search [data-m-type="td"] button.btn_search.ain_collect_btn { border-color: var(--c-success); color: var(--c-success); }
.moca_table_search [data-m-type="td"] button.btn_search.ain_collect_btn:hover { background: var(--c-primary-light); }
.moca_table_search [data-m-type="td"] button.btn_search.ain_collect_btn:disabled { border-color: var(--c-border); color: var(--c-text-muted); background: var(--c-bg-elevated); cursor: wait; }
.moca_table_search .ain_collect_status_row { flex-basis: 100%; width: 100%; gap: 0; margin-top: -4px; padding-top: 12px; border-top: 1px solid var(--c-border); }
.moca_table_search .ain_collect_status_row[hidden] { display: none; }
.moca_table_search .ain_collect_status_row > [data-m-type="td"] { width: 100%; height: auto; }
.ain_collect_status { display: flex; align-items: center; gap: 13px; width: 100%; min-width: 0; padding: 12px 14px; background: var(--c-bg-elevated); border: 1px solid var(--c-border); border-radius: var(--radius-md); box-shadow: var(--shadow-sm); }
.ain_collect_signal { position: relative; width: 38px; height: 38px; flex: 0 0 38px; border: 1px solid var(--c-primary); border-radius: 50%; background: var(--c-primary-light); }
.ain_collect_signal::before { content: ''; position: absolute; inset: 8px; border: 2px solid var(--c-primary); border-top-color: transparent; border-radius: 50%; animation: moca-progress-spin 1.1s linear infinite; }
.ain_collect_signal::after { content: ''; position: absolute; left: 50%; top: 50%; width: 5px; height: 5px; border-radius: 50%; background: var(--c-primary); transform: translate(-50%, -50%); }
.ain_collect_content { flex: 1; min-width: 0; }
.ain_collect_heading { display: flex; align-items: center; gap: 10px; min-width: 0; margin-bottom: 8px; }
.ain_collect_label { flex: 0 0 auto; padding: 3px 7px; border-radius: 100px; background: var(--c-primary-light); color: var(--c-primary); font-size: 12px; font-weight: 700; letter-spacing: 0.7px; }
.ain_collect_step { min-width: 0; overflow: hidden; color: var(--c-text); font-size: 13px; font-weight: 600; white-space: nowrap; text-overflow: ellipsis; }
.ain_collect_percent { margin-left: auto; color: var(--c-primary); font-size: 14px; font-variant-numeric: tabular-nums; }
.ain_collect_track { width: 100%; height: 7px; overflow: hidden; border-radius: 100px; background: var(--c-primary-light); }
.ain_collect_fill { display: block; width: 0; height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--c-primary), var(--c-success)); transition: width 0.35s ease; }
.ain_collect_meta { display: flex; justify-content: space-between; gap: 12px; margin-top: 7px; color: var(--c-text-muted); font-size: 12px; line-height: 1.3; }
.ain_collect_meta span:last-child { flex: 0 0 auto; color: var(--c-text); font-variant-numeric: tabular-nums; }

/* ============================================================
 * scheduleCalendar 컴포넌트 (plugins/scheduleCalendar.js)
 * ============================================================ */

/* swiper 코어 최소 스타일 — vendor/swiper_11.0.6/swiper-bundle.js 는 JS 만 있고
   CSS 가 없어, 슬라이드 동작에 필수인 코어 규칙만 여기서 정의한다. */
.swiper         { position: relative; overflow: hidden; list-style: none; padding: 0; margin: 0; z-index: 1; }
.swiper-wrapper { position: relative; width: 100%; height: 100%; z-index: 1; display: flex;
                  box-sizing: content-box; transition-property: transform; }
.swiper-slide   { flex-shrink: 0; width: 100%; height: 100%; position: relative; transition-property: transform; }

/* ── 컴포넌트 루트 — 세로 flex. 높이는 내용만큼 자란다(height:auto).
   "뷰포트를 채우는 최소높이"(min-height)는 화면 CSS 가 정한다 — 조상 % 체인이 아니라 dvh 같은 확정값으로 줘야
   한다(% 는 auto 조상에서 해소되지 않는다, §10-3-4). 넘친 높이는 화면 문서 스크롤이 받으므로 클리핑하지 않는다. ── */
.moca_schcal    { display: flex; flex-direction: column; height: auto; min-height: 0; overflow: visible;
                  background: var(--c-surface); border: 1px solid var(--c-border); border-radius: 10px; }

/* ── 헤더 (이전 아이콘 · 연월 · 다음 아이콘 · 오늘 아이콘) ── */
/* ★ 헤더의 치수는 em 이다 — 연월 제목만 글자 배율을 따라 커지고 버튼은 px 로 고정이면
   배율을 올릴수록 제목 옆 버튼들이 상대적으로 쪼그라들어 균형이 깨진다(2026-08-30 지적).
   font-size 를 여기 선언해 두는 것이 em 의 기준이다(§15-3-4). */
/* ★ 여백은 clamp 로 **덜 커지게**, 버튼은 그대로 커지게 한다(2026-08-30 지적).
   gap·padding 까지 배율만큼 부풀면 그 여백이 버튼을 밀어내 정작 버튼이 찌그러지고
   맨 오른쪽 [오늘] 이 화면 밖으로 나간다(실측: 64px 여야 할 버튼이 36px 로 눌렸다).
   여백은 읽기와 무관한 「사이 공간」이라 조금만 커져도 된다 — 커져야 하는 건 누르는 것이다.
   그래도 안 들어가면 flex-wrap 이 [오늘] 을 다음 줄로 내린다(잘리는 것보다 낫다). */
.msc_header     { display: flex; flex-wrap: wrap; align-items: center;
                  gap: 6px; padding: clamp(3px, 0.45em, 6px) 4px;
                  font-size: 13px; border-bottom: 1px solid var(--c-border); }
/* ★ 이 글자에는 상한을 두지 않는다 — **배율기 아래서는 px·rem 어느 쪽으로도 상한이 안 걸린다**(2026-08-30 실측).
   · clamp(20px, 2em, 30px) → 배율기가 선언 안의 px 를 전부 다시 써 clamp(40px,2em,60px) 가 된다(상한 소멸)
   · clamp(…, 1.875rem) → 루트(html)의 font-size 도 배율기가 키우므로 rem 도 같이 커진다(상한 소멸)
   상한이 필요하면 배율기가 손대지 않는 절대단위(pt 등)를 써야 하는데, 그러면 배율을 올린 사용자가
   정작 제목만 못 키우게 된다. 헤더가 좁으면 flex-wrap 으로 [오늘]을 내리는 쪽을 택했다.
   제목은 줄어들 수 있어야 한다 — 버튼(고정)보다 먼저 양보한다.
   ★ min-width 를 두지 않는다 — 이 요소의 font-size 는 20px 라 4.8em 이 배율 200%에서 **192px** 가 되고,
   그 최소폭이 [오늘] 을 다음 줄로 밀어냈다(실측). 폭이 필요한 것은 입력 모드뿐이고
   그쪽은 아래 input.msc_title 이 자기 width 를 따로 갖는다. */
/* letter-spacing 0 — 글자 사이도 「사이 여백」이라 한 줄에 들어가는지를 좌우한다.
   px 라 배율기가 손대지 않아 보이지만 7글자면 그만큼이 그대로 폭이 된다(2026-08-30). */
/* ★ margin: 0 auto — 남는 가로 여백을 **연월 좌우로 똑같이** 나눠 준다(2026-08-30).
   종전에는 [오늘] 이 margin-left:auto 로 남는 폭을 통째로 삼켜 [>] 와 [오늘] 사이만 텅 비고
   연월은 왼쪽으로 쏠려 있었다. flex 는 auto 여백에 남는 공간을 먼저 배분하므로,
   좌우 auto 를 주면 [<] 와 [>] 가 연월에서 같은 거리에 놓인다.
   ★ 여기서 flex-grow 로 늘리면 안 된다 — 이 요소는 titleedit 모드에서 input 이라
     늘어난 폭이 그대로 입력상자 폭이 돼 [오늘] 을 다음 줄로 밀어낸다(실측). */
.msc_title      { margin: 0 auto; min-width: 0; flex: 0 1 auto; text-align: center;
                  font-size: 20px; font-weight: 700; letter-spacing: 0; color: var(--c-text); }
/* 연월 직접입력(data-m-titleedit="true") — 평소엔 제목과 같아 보이고 포커스에서만 입력칸으로 드러난다.
   ★ 폭·높이는 반드시 em(글자 기준)이다 — px 로 고정하면 글자 배율을 키우는 프로젝트에서
     (예: 설정 200%) 상자만 그대로라 "2026.08" 이 잘려 연도만 보인다(2026-08-20 실측).
     h2 제목은 내용만큼 자라니 문제가 없었는데, input 은 상자 크기가 곧 표시 폭이라 직격이다.
     4.3em ≈ 7글자("2026.08" ≈ 3.6em) + 여유. 입력 중에도 폭이 변하지 않아 좌우 화살표가 밀리지 않는다.
     ★ 4.8em 에서 줄였다(2026-08-30) — 배율 200%에서 이 한 칸이 192px 라 헤더 총폭 352px 를 만들어
       [오늘] 이 다음 줄로 밀렸다. 여기가 헤더에서 가장 큰 덩어리라 여유폭을 깎는 것이 가장 효과가 크다.
     height 를 고정하지 않는 것도 같은 이유(세로 잘림) — 100% 에서의 모양은 min-height 로 유지한다. */
/* ★ 폭 단위를 em → ch 로 바꿨다(2026-09-05 실기기: iPhone 12 Pro · Chrome · 배율 110%에서 「2026.09」의 09 가 잘렸다).
     em 은 글자 높이 기준이라 **숫자 폭과 관계가 없다** — 같은 20px 라도 글꼴이 바뀌면 숫자가 넓어져 4.3em 을 넘는다.
     ch 는 「0」 한 글자의 실제 폭이라 어떤 글꼴에서도 자릿수만큼 자란다. "2026.09" = 7자(점은 더 좁다) → 8ch 면 여유가 있다.
     배율에도 유리하다: 200%에서 8ch 는 종전 4.3em 보다 오히려 좁아 [오늘] 버튼을 덜 밀어낸다. */
input.msc_title { width: 8ch; min-height: 32px; padding: 2px 4px; font-family: inherit; cursor: text;
                  background: transparent; border: 1px solid transparent; border-radius: 8px; }
input.msc_title:hover  { border-color: var(--c-border-dark); }
input.msc_title:focus  { outline: none; border-color: var(--c-primary); background: var(--c-surface); }
input.msc_title::placeholder { font-weight: 400; letter-spacing: 0; color: var(--c-text-sub); }
/* flex:0 0 auto — 좁아져도 눌리지 않는다. 눌리면 정사각형이 세로로 긴 알약처럼 일그러진다(위 주석) */
.msc_nav        { flex: 0 0 auto; width: clamp(30px, 2.3em, 36px); height: clamp(30px, 2.3em, 36px); border-radius: 0.6em; cursor: pointer;
                  background: var(--c-surface); border: 1px solid var(--c-border-dark);
                  color: var(--c-text-sub); font-size: 13px; line-height: 1; }
.msc_nav:hover  { background: var(--c-bg-hover); color: var(--c-primary); border-color: var(--c-primary); }
/* 이전/다음 달 — 특수문자 대신 공용 SVG를 mask로 써 테마의 currentColor를 따른다. */
.msc_prev, .msc_next { display: inline-flex; align-items: center; justify-content: center; }
.msc_prev::before, .msc_next::before { content: ''; width: clamp(18px, 1.38em, 22px); height: clamp(18px, 1.38em, 22px); background-color: currentColor; }
.msc_prev::before { -webkit-mask: url('../images/icon_page_prev.svg') no-repeat center / contain;
                            mask: url('../images/icon_page_prev.svg') no-repeat center / contain; }
.msc_next::before { -webkit-mask: url('../images/icon_page_next.svg') no-repeat center / contain;
                            mask: url('../images/icon_page_next.svg') no-repeat center / contain; }
/* 메뉴(햄버거) 버튼 — data-m-menubutton="true" 일 때만. 아이콘은 mask 라 색이 테마를 따른다 */
.msc_menubtn    { display: inline-flex; align-items: center; justify-content: center; }
.msc_menubtn::before { content: ''; width: clamp(17px, 1.3em, 21px); height: clamp(17px, 1.3em, 21px); background-color: currentColor;
                  -webkit-mask: url('../images/icon_menu.svg') no-repeat center / contain;
                          mask: url('../images/icon_menu.svg') no-repeat center / contain; }
/* ★ 이 버튼은 글자 라벨을 감추려고 font-size:0 을 쓴다 — 그래서 **버튼 쪽 em 은 0** 이라 쓸 수 없다.
   크기 기준을 아이콘(::before)에 두고 거기서 font-size 를 선언한다. 배율기는 스타일시트의
   px font-size 를 다시 쓰므로 가상요소 규칙도 함께 커진다(§15-3-4). 버튼은 아이콘에 맞춰 auto. */
/* margin-left:auto 를 쓰지 않는다 — 남는 폭을 이 버튼이 다 먹으면 [>] 와 사이가 텅 빈다.
   여백 배분은 .msc_title 의 좌우 auto 가 맡는다(위 주석). */
.msc_todaybtn   { display: inline-flex; flex: 0 0 auto; align-items: center; justify-content: center; margin-left: 0;
                  width: auto; height: auto; padding: 0; border-radius: 0.6em; cursor: pointer;
                  background: transparent; border: 1px solid var(--c-primary); color: var(--c-primary);
                  font-size: 0; }
.msc_todaybtn::before { content: ''; font-size: 13px; width: clamp(20px, 2.1em, 30px); height: clamp(20px, 2.1em, 30px); margin: 0 2px;
                  background-color: currentColor;
                  -webkit-mask: url('../images/icon_today.svg') no-repeat center / contain;
                          mask: url('../images/icon_today.svg') no-repeat center / contain; }
.msc_todaybtn:hover { background: var(--c-primary-light); }

/* ── 요일행 ── */
.msc_week       { display: grid; grid-template-columns: repeat(7, 1fr);
                  border-bottom: 1px solid var(--c-border); background: var(--c-grid-th); }
.msc_week span  { padding: 8px 0; text-align: center; font-size: 13px; font-weight: 600; color: var(--c-text-sub); }

/* ── 스와이프 달력 그리드 (7열×6주) ──
   ★ 높이 체인은 % 를 쓰지 않고 flex 로만 잇는다(스와이퍼→래퍼→슬라이드→그리드→주 행).
      % 는 조상이 auto 면 해소되지 않아 예전에 JS 실측이 필요했던 순환의 원인이었다(§10-3-4).
   ★ 모든 단계가 `flex: 1 0 auto` — grow 1(남는 높이는 채운다) + **shrink 0**(내용보다 작아지지 않는다).
      shrink 를 1 로 두면 일정이 많을 때 행이 눌려 칸이 잘리거나 아래 주를 침범한다. */
.msc_swiper     { display: flex; flex-direction: column; flex: 1 0 auto; min-height: 0; width: 100%; }
.msc_swiper > .swiper-wrapper { flex: 1 0 auto; height: auto; }
.msc_swiper .swiper-slide     { display: flex; flex-direction: column; height: auto; }
.msc_grid       { display: flex; flex-direction: column; flex: 1 0 auto; width: 100%; height: auto; }
.msc_row        { display: flex; flex: 1 0 auto; min-height: 0; }
.msc_day        { flex: 1 1 0; min-width: 0; min-height: 0; overflow: hidden; cursor: pointer; position: relative;
                  border-bottom: 1px solid var(--c-border); border-right: 1px solid var(--c-border); }
.msc_day:hover  { background: var(--c-bg-hover); }
.msc_row .msc_day:last-child { border-right: none; }
.msc_row:last-child .msc_day { border-bottom: none; }
.msc_date       { display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; vertical-align: top;
                  min-width: 24px; height: 24px; padding: 0 4px;
                  font-size: 13px; color: var(--c-text-body); }
/* 보조 날짜 라벨(dayInfo.sub — 음력 등). 글자는 배율을 따라 커지고 사이 여백(3px)은 고정이다.
   날짜와 한 줄에 붙어야 하므로 줄바꿈하지 않고, 칸이 좁으면 넘치는 대신 잘린다(칸이 .msc_day 에서 클리핑). */
.msc_datesub    { margin-left: 3px; font-size: 11px; font-weight: 500; color: var(--c-text-muted); white-space: nowrap; vertical-align: middle; }
.msc_date_holiday .msc_datesub { margin-left: 4px; }
.msc_daynum.msc_now .msc_datesub, .msc_date.msc_now .msc_datesub { color: inherit; }
.msc_date_holiday { flex-direction: column; align-items: stretch; justify-content: flex-start; box-sizing: border-box; width: 100%; padding: 0; }
.msc_date_holiday .msc_date_top, .msc_date_holiday .msc_daynum { display: flex; flex: 1 1 50%; align-items: center; box-sizing: border-box; min-width: 0; min-height: 0; padding: 0 4px; font-size: 12px; line-height: 1; }
.msc_date_holiday .msc_date_top { gap: 3px; }
.msc_date_holiday .msc_daynum { justify-content: flex-start; }
.msc_sun .msc_date, .msc_week .msc_sun { color: var(--c-danger); }
.msc_sat .msc_date, .msc_week .msc_sat { color: var(--c-info); }
.msc_out .msc_date { opacity: 0.35; }
.msc_date.msc_now, .msc_daynum.msc_now { background: var(--c-primary); color: var(--c-bg); font-weight: 700; }
/* ★ 깃발(data-m-todayflag="true")을 띄운 칸에서는 날짜 배경을 빼고 굵기만 남긴다(2026-08-30).
   같은 「오늘」을 깃발과 색면이 두 번 말하면 칸이 시끄럽고, 깃발 바로 아래 진한 사각형이
   겹쳐 붙어 서로를 깎아먹는다. 깃발을 안 쓰는 프로젝트는 위 기본값(색면)을 그대로 쓴다 —
   그쪽에서 배경을 없애면 오늘 표시가 통째로 사라진다. */
.msc_flagcell .msc_date.msc_now, .msc_flagcell .msc_daynum.msc_now {
                  background: transparent; color: var(--c-text); font-weight: 800; }
.msc_day_body   { display: flex; flex-direction: column; gap: 2px; margin-top: 2px; overflow: hidden; font-size: 12px; }

/* ── 오늘 깃발(data-m-todayflag="true") — 오늘 칸 위쪽으로 넘쳐 나와 자유롭게 펄럭인다(AS-IS 동일 배치).
      깃대 밑동을 축으로 흔들어 셀에 꽂힌 깃발처럼 보이게 한다. 클릭은 아래 셀이 받는다(pointer-events:none). ── */
.msc_flag       { position: absolute; top: -30px; left: 13px; width: 55px; height: 55px; z-index: 3; pointer-events: none;
                  background: url('../images/icon_flag.gif') no-repeat center center; background-size: contain;}

/* 깃발이 있는 칸만 클리핑 해제(깃발이 셀 밖으로 나가야 하므로) + 이웃 칸 위에 그려지도록 z-index.
   대신 일정 목록이 같이 새지 않도록 body 에 상한을 준다(셀 높이가 확정돼 있으므로 % 가 해소된다). */
.msc_flagcell   { overflow: visible; z-index: 2; }
.msc_flagcell .msc_day_body { max-height: calc(100% - 30px); overflow: hidden; }

/* 첫 주 칸 — 위로 넘칠 공간이 없어(스와이퍼가 잘라낸다) 셀 안쪽 위에 세운다 */
.msc_flagcell_top .msc_flag { top: 2px; }
@keyframes msc_flag_wave {
  0%, 100% { transform: rotate(-3deg) skewY(2deg) scaleX(1); }
  35%      { transform: rotate(1.5deg) skewY(-1deg) scaleX(0.98); }
  70%      { transform: rotate(3deg) skewY(-2deg) scaleX(1); }
}

/* 모션 최소화 설정(접근성) — 깃발은 두되 흔들지 않는다 */
@media (prefers-reduced-motion: reduce) {
  .msc_flag { animation: none; }
}

/* ── 일정 표시(setData) — 날짜영역 메타(우상단: 날씨·뱃지) ── */
.msc_meta       { position: absolute; top: 5px; right: 6px; display: inline-flex; align-items: center; gap: 3px; max-width: 68%; overflow: hidden; font-size: 12px; }
.msc_meta_inline { position: static; flex: 0 0 auto; max-width: 45%; margin-left: auto; }
.msc_meta i     { font-style: normal; }
.msc_holi       { min-width: 0; color: var(--c-danger); font-weight: 600; text-align: left; white-space: nowrap; overflow: hidden; }
.msc_holiday .msc_date { color: var(--c-danger); }

/* ── 일정 바(setData) — .msc_day_body > span.msc_bar*N (래퍼 없음). 여백은 최소 유지(좁은 칸에서 제목 글자 수 확보) ── */
/* 세로중앙은 flex 로 맞춘다 — line-height 로 맞추면 마커(.msc_mark)처럼 키 큰 인라인 요소가
   베이스라인을 끌어내려 글씨가 따라 내려간다(실측: 4px 밀림 + 아래 2px 잘림). */
.msc_bar        { display: flex; align-items: center; box-sizing: border-box; height: 20px; line-height: 20px; font-size: 12px; background: transparent; color: var(--c-text-body); white-space: nowrap; }
.msc_bar em     { font-style: normal; min-width: 0; overflow: hidden; }
.msc_cru        { color: var(--c-danger); margin-right: 1px; font-weight: 400; }

/* ── 연속일정 이음매 — 칸 사이 경계선 위에 같은 배경색을 1px 덧그려 바가 끊기지 않게 한다.
   칸 패딩이 0 이라 바는 칸 폭을 정확히 채우고, 남는 틈은 왼쪽 칸의 border-right 1px 뿐이다.
   그래서 **오른쪽으로만** 덮으면 된다 — m/e 의 왼쪽은 앞 칸(s/m)의 ::after 가 이미 덮는다.
   background:inherit 라 인라인으로 들어온 그룹 색·완료 일정의 transparent 를 그대로 따라간다.
   토요일은 다음 날이 다음 줄이라 제외(이으면 우측 끝에 유령 조각이 남는다). ── */
.msc_bar:is(.msc_bar_s, .msc_bar_m) { position: relative; }
.msc_day:not(.msc_sat) .msc_bar:is(.msc_bar_s, .msc_bar_m)::after {
    content: ''; position: absolute; top: 0; bottom: 0; left: 100%; width: 1px; background: inherit; }
/* 이음매가 칸 경계선(padding box 바깥) 위로 나가야 하므로 그 칸만 클리핑을 푼다.
   행이 내용보다 작아지지 않으므로(.msc_row flex-shrink:0) 세로로 샐 일은 없다. */
.msc_day:not(.msc_sat):has(.msc_bar_s, .msc_bar_m) { overflow: visible; }
.msc_day:not(.msc_sat):has(.msc_bar_s, .msc_bar_m) > .msc_day_body { overflow: visible; }

/* 종료일 바는 우측정렬 — flex 라 text-align 이 아니라 justify-content 로 민다 */
.msc_bar.msc_bar_e { justify-content: flex-end; }

/* 시작/종료 마커 — 진한색 세로 불릿(시작·단일 맨 앞, 종료일 맨 뒤 — 마크업이 위치 결정).
   flex 아이템이라 align-self:stretch 로 바 높이를 그대로 쓴다(vertical-align 불필요) */
.msc_mark       { flex: none; align-self: stretch; width: 4px; margin: 0 2px 0 0; }
.msc_bar.msc_bar_e .msc_mark { margin: 0 0 0 2px; }

/* 완료 일정 — 배경 없이 기본 글씨 + 취소선(마커·아이콘은 마크업에서 숨김) */
.msc_bar.msc_bar_done    { background: transparent; color: var(--c-text-body); }
.msc_bar.msc_bar_done em { text-decoration: line-through; opacity: 0.6; }

/* ── 바 스타일 line (data-m-barstyle="line", 2026-09-02) — 색면 칩 대신 왼쪽 세로 색 바 + 잉크색 제목.
   좁은 칸에서 잉크를 줄이고, 달력 색이 무엇이든 글자 대비를 보장한다(색은 바가 들고 글자는 항상 본문색).
   연속일정도 날마다 [바][제목] 을 반복한다 — 색면이 이어지지 않으므로 빈 바만으론 무슨 일정인지 모른다. ── */
/* line-height 1.1 — 줄 사이 간격은 글자가 커져도 고정(2px gap)이어야 하는데 1.2 는 글자에 비례해 줄 상자를 키워 벌어졌다(운영 실측 2026-09-02) */
.msc_bar.msc_bar_line   { gap: 2px; padding: 0 2px 0 1px; line-height: 1.1; background: transparent; color: var(--c-text-body); justify-content: flex-start; }
/* 바 색은 span 의 --msc-lc(_barsHtml 이 인라인으로 심는다) — 세로 바와 배경 틴트가 한 값을 나눠 쓴다 */
.msc_line               { flex: none; align-self: stretch; width: 3px; margin: 1px 0; border-radius: 2px; background: var(--msc-lc, var(--c-primary)); }
/* 이모지(작성자 상징 등) — 제목 자리를 빼앗지 않게 85% 크기 인라인. 배경·고정 폭을 두지 않는다 —
   원(1.3em + 회색 배경 + 간격)으로 감쌌더니 운영 실측에서 글자 두 칸을 먹어 제목이 한 글자만 남았다(2026-09-02). */
.msc_ico                { flex: none; display: inline-block; font-style: normal; font-size: 0.85em; line-height: 1; }
/* line 모드의 작성자 이모지 — 글자 한 칸 반을 먹어 폰에서 제목이 한두 글자 잘렸다(2026-09-07 지시, 1안).
   이모지를 70% 크기로 줄여 **세로 색 바 자리에 겹쳐** 놓고 제목은 그 뒤(0.95em)에서 시작한다 — 이모지 없는 칩(바 3px+간격 2px)보다
   5~6px 만 더 쓴다. 색 바는 이모지 위·아래로 비쳐 달력 색도 남는다. :has() 로 이모지 있는 칩만 건드린다. */
/* 2026-09-07 3차: 이모지 90% + 제목 앞 여백 2px(calc) — 100%·여백 0 은 붙어 보였다 */
.msc_bar.msc_bar_line:has(> .msc_ico) { position: relative; padding-left: calc(0.9em + 2px); }
.msc_bar.msc_bar_line:has(> .msc_ico) > .msc_line { position: absolute; left: 1px; top: 1px; bottom: 1px; margin: 0; }
.msc_bar.msc_bar_line > .msc_ico { position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 1em; text-align: left; font-size: 0.9em; }
/* 색면 채우기(bartint) 칩도 같은 자리 — 들인 바(2px·1px)와 어긋나지 않게 */
.msc_bar.msc_bar_line.msc_bar_done .msc_line { opacity: 0.35; }
/* line 모드의 오늘 — 색면 사각형 대신 원형 배지(숫자만 감싼다). 폭=높이여야 원이 된다 */
.moca_schcal_line .msc_date.msc_now { border-radius: 50%; min-width: 1.7em; width: 1.7em; height: 1.7em; padding: 0; line-height: 1; }
/* 날짜 줄 높이를 모든 칸에서 원형 배지(1.7em)와 같게 — 오늘 칸만 배지가 커서 그 칸 일정이 이웃보다 아래로 밀려 줄이 안 맞았다(폰 실측 2026-09-06).
   (0,3,0) 으로 둔 이유: 프로젝트 CSS 가 .wp_cal_shell .msc_date(0,2,0) 로 height:auto 를 주므로 그보다 세야 한다 */
.moca_schcal.moca_schcal_line .msc_date { min-height: 1.7em; }
/* 날짜 숫자는 굵게, 일정 제목은 보통 — 둘 다 같은 굵기면 칸 안 글자가 같은 목소리라 정신없다(위젯과 같은 규칙, 2026-09-02) */
.moca_schcal_line .msc_date { font-weight: 700; }
/* ── line 모드의 「오늘」 버튼 — 이전/다음과 같은 가족(같은 크기·테두리)에 **달력 윤곽 안 오늘 날짜 숫자** 아이콘(.msc_todaynum — _syncTitle 이 dd 를 넣는다).
   각진 청록 테두리 + 글자는 셋 중 하나만 다른 옷이었다(2026-09-02 지적). 오늘의 달을 보고 있을 땐(disabled) 자리를 지킨 채 숨긴다 — 헤더가 흔들리지 않게. ── */
.moca_schcal_line .msc_todaybtn { width: clamp(30px, 2.3em, 36px); height: clamp(30px, 2.3em, 36px); padding: 0; border-radius: 0.6em;
                  border: 1px solid var(--c-border-dark); background: var(--c-surface); color: var(--c-primary); font-size: 13px; }
.moca_schcal_line .msc_todaybtn::before { display: none; }
.moca_schcal_line .msc_todaynum { display: inline-block; min-width: 1.5em; padding: 2px 2px 1px; box-sizing: border-box;
                  border: 1.5px solid currentColor; border-top-width: 4px; border-radius: 3px;
                  font-size: 0.72em; font-weight: 700; line-height: 1; letter-spacing: -0.02em; text-align: center; }
.moca_schcal_line .msc_todaybtn:hover { background: var(--c-bg-hover); border-color: var(--c-primary); }
.moca_schcal_line .msc_todaybtn:disabled { visibility: hidden; }
.msc_todaynum { display: none; }   /* fill 모드는 종전 아이콘(::before) — 숫자는 line 모드에서만 */
/* line 모드는 세로 칸 경계선을 지운다 — 바가 열의 시작을 표시하므로 선이 필요 없다(가로 주 경계선만 남긴다).
   주 경계선 바로 밑에 다음 주 숫자가 붙으면 답답하다(운영 실측) — 칸 위 2px 여백. 요일 밑줄과 첫 주 사이도 같은 값. */
.moca_schcal_line .msc_day { border-right-color: transparent; padding-top: 2px; }
/* 날짜 숫자 가운데 정렬 — 요일 헤더(가운데)와 열 축을 맞추고 위젯(가운데 숫자 + 왼쪽 바)과 같은 규칙.
   보조 라벨(음력 등)이 하나라도 있으면(msc_has_sub — setData 가 단다) 「4 7.23」처럼 넓어지므로 전체를 왼쪽으로. */
.moca_schcal_line .msc_day { text-align: center; }
.moca_schcal_line.msc_has_sub .msc_day { text-align: left; }
/* 공휴일 — line 모드에서는 「공휴일명/숫자」 2줄 날짜 영역 대신 일정 줄 맨 위 고정 행. 빨간 글자·빨간 바로 일정과 구별.
   클릭은 ondayclick(data-m-i 가 없어 onitemclick 대상이 아니다), 일정 개수에도 들어가지 않는다. */
.msc_bar.msc_bar_holi   { --msc-lc: var(--c-danger); color: var(--c-danger); font-weight: 600; }
/* 기념일 줄(dayInfo.anniv) — 공휴일 줄 클래스를 같이 달아 틴트·이음매 제외를 물려받고, 색만 회색으로. 바 없이 글자만 */
.msc_bar.msc_bar_holi.msc_bar_anniv { --msc-lc: transparent; color: var(--c-text-muted); font-weight: 500; padding-left: 4px; }
.msc_bar.msc_bar_holi.msc_bar_anniv > .msc_line { display: none; }

/* ── line 모드 배경 틴트 (data-m-bartint="true" / setBarTint — 루트 .moca_schcal_tint, 2026-09-06) ──
   세로 바만으론 달력(채널)이 한눈에 안 갈린다는 취향이 있어, 바 색을 옅게 깐 색면을 **설정으로** 켠다.
   글자는 여전히 본문색이라 대비가 흔들리지 않고(색은 바가 들고 배경은 틴트만), 완료 일정은 종전대로 배경 없음.
   ★ 색은 **명도를 고정한 상대색(oklch(from … L c h))** 으로 만든다 — 바 색의 색상(h)만 빌리고 밝기는 라이트 93% / 다크 32% 로
   맞춰, 진한 파랑도 연한 노랑도 **같은 밝기의 색면**이 된다(채도는 0.7배 — 원색 그대로면 파랑 계열이 튄다).
   투명 혼합(color-mix … transparent)으로 했더니 파랑은 적당한데 위드플랜 개인색처럼 **원래 옅은 색은 18% 에서 거의 안 보였다**
   (실측 2026-09-06) — 비율 하나로는 진한 색과 옅은 색을 동시에 못 맞춘다. 상대색 미지원 브라우저(Chrome<119·iOS<16.4)만 color-mix 폴백.
   연속일정은 s/m 오른쪽·m/e 왼쪽 모서리를 각지게 두어 날마다 반복되는 [바][제목] 이 한 띠로 읽힌다
   (칸 사이 1px 경계는 위 ::after 이음매가 background:inherit 로 덮는다). */
.moca_schcal_tint { --msc-tint: 26%; --msc-tint-l: 32%; }
:root.theme-light .moca_schcal_tint { --msc-tint: 18%; --msc-tint-l: 93%; }
.moca_schcal_tint .msc_bar.msc_bar_line:not(.msc_bar_done) { background: color-mix(in srgb, var(--msc-lc, var(--c-primary)) var(--msc-tint), transparent); border-radius: 3px; }
@supports (color: oklch(from red 90% c h)) {
    .moca_schcal_tint .msc_bar.msc_bar_line:not(.msc_bar_done) { background: oklch(from var(--msc-lc, var(--c-primary)) var(--msc-tint-l) calc(c * 0.7) h); }
}
/* 아이템 자체 색면(msc_bar_own — bg/fg 를 가진 아이템) — 틴트 대신 그 배경·글자색 그대로. 진한 배경 + 밝은 글자(위드플랜 「선명한」 단)처럼
   명도 정규화 틴트로는 표현이 안 되는 조합을 위해서다(실측 2026-09-06: 선명한 로즈를 골랐는데 달력엔 연한 핑크만 깔렸다).
   팔레트가 대비를 보증한다는 전제이므로 화면이 bg/fg 를 줄 때만 걸린다. */
.moca_schcal_tint .msc_bar.msc_bar_line.msc_bar_own { background: var(--msc-bg); color: var(--msc-fg); }
/* 색면 채우기 칩의 세로 바 = 화면이 준 바 색(--msc-lc = 배경보다 한 단계 진한 색), **원래 자리·원래 굵기(3px)** — 모든 칩 한 규칙(2026-09-07 지시).
   폐기한 것: ① 전경색(글자색)으로 바를 덮는 규칙 ② 밝은 글자 칩만 1px 들여 2px 로 그리던 규칙(msc_bar_own_light — 글자색 바가 흰 칸과
   이어져 깨져 보이던 때의 처방. 바가 배경보다 살짝 진한 색이 되면서 그 문제 자체가 없어졌다). 엔진이 붙이는 msc_bar_own_light 클래스는 남지만 CSS 는 쓰지 않는다 */
/* 2026-09-07: 색면 칩의 바도 화면이 준 바 색(--msc-lc = 배경보다 조금 진한 색)을 쓴다 — 전경색(글자색) 덮어쓰기는 폐기.
   모든 칩에서 「바 = 배경보다 한 단계 진한 색」 한 규칙 */
.moca_schcal_tint .msc_bar.msc_bar_line.msc_bar_own .msc_line { background: var(--msc-lc); }
/* 공휴일 행은 채우기 제외 — 연한 빨강 바탕 + 빨강 글자는 대비가 약해 존재감이 없었다(폰 실측 2026-09-06). 빨간 글자 + 빨간 바만.
   ★ 틴트 선택자에 :not(.msc_bar_holi) 를 덧붙이는 방식은 쓰지 않는다 — 명시도가 (0,5,0) 으로 올라 자기 색면 규칙(msc_bar_own, 0,4,0)을
     덮어 모든 칩이 반투명이 되는 사고가 났다(2026-09-06). 여기처럼 **뒤에 오는 더 센 규칙**으로 공휴일만 끈다. */
.moca_schcal.moca_schcal_tint .msc_bar.msc_bar_line.msc_bar_holi { background: transparent; border-radius: 0; }
/* 채우기 모드의 칸 사이 이음매 — **연속일정만** 잇고 단일일정 칩은 칸 경계선 1px 간격을 둔다(2026-09-07 지시). s/m 의 이음매는 위 공통 ::after 규칙이 처리한다.
   종료일(e) 칩은 오른쪽으로 잇지 않는다 — 종료 칩의 후방 바를 없애면서(아래) 바 색 이음매도 함께 뺐다(2026-09-07 지시) */
/* 연속일정 칩은 불투명도 85% — 단일일정과 살짝 다르게 읽히도록(2026-09-07 결정). 시작·중간·종료 세 칸 모두, 완료·공휴일 제외 */
.moca_schcal_tint .msc_bar.msc_bar_line:is(.msc_bar_s, .msc_bar_m, .msc_bar_e):not(.msc_bar_done):not(.msc_bar_holi) { opacity: 0.85; }
/* 채우기 칩의 왼쪽 모서리 — 바(3px, 둥근 끝, 위아래 1px 여백)가 칩 안쪽에 떠 있어 왼쪽만 뾰족·울퉁불퉁하게 보였다(2026-09-07 지적).
   바를 칩 왼쪽 끝에 붙여 세로로 꽉 채우고 칩이 모서리를 잘라(overflow:hidden) 좌우 모서리가 같은 둥근 모양이 되게 */
/* 채우기 칩의 모서리 = 왼쪽 각·오른쪽 둥글게(2026-09-07 지시) — 바가 붙은 왼쪽은 곧은 띠로, 오른쪽만 3px 라운드 */
.moca_schcal_tint .msc_bar.msc_bar_line:not(.msc_bar_done):not(.msc_bar_holi) { padding-left: 0; border-radius: 0; }   /* 라운드 0(3 → 8 → 5 → 0). ★ overflow:hidden 을 두지 않는다 — 연속일정 이음매(::after, 아래 .msc_bar_s/m 규칙)가 잘려 칸 사이에 흰 틈이 생겼다(2026-09-07) */
.moca_schcal_tint .msc_bar.msc_bar_line:not(.msc_bar_done):not(.msc_bar_holi) > .msc_line { margin: 0; border-radius: 0; }
/* 단일일정 칩(연속일정 s/m/e 아님) — 모바일에서도 칩 사이가 느껴지게 오른쪽에 2px 여백을 두고, 오른쪽 모서리만 7px 둥글게(2026-09-07 지시 — 4px/10px → 2px/7px).
   왼콽은 바가 붙은 곧은 띠 그대로. 연속일정은 간격 없이 이어지는 규칙(위 ::after)을 유지한다 */
.moca_schcal_tint .msc_bar.msc_bar_line:not(.msc_bar_done):not(.msc_bar_holi):not(.msc_bar_s):not(.msc_bar_m):not(.msc_bar_e) { margin-right: 2px; border-radius: 0 7px 7px 0; }
.moca_schcal_tint .msc_bar.msc_bar_line:not(.msc_bar_done):not(.msc_bar_holi):has(> .msc_ico) > .msc_line { left: 0; top: 0; bottom: 0; }
/* 이모지·글자 간격을 모두 같게(2026-09-07 지시 "간격이 모두 균등하게") — 바 3px │2px│ 이모지 │2px│ 글자.
   종전에는 이모지를 절대배치(left:-2px)하고 글자 여백을 calc(0.9em+3px) 로 맞췄는데, 이모지 글리프의 좌우 여백이 글꼴마다 달라
   PC(Segoe UI Emoji)에선 맞고 안드로이드(Noto Color Emoji)에선 이모지–글자 사이가 벌어졌다(실기기 캡처 대조). 이모지를 흐름 안(static) 에 두고
   양쪽에 같은 2px 를 주면 글리프 여백이 좌우 대칭이라 어느 글꼴에서든 두 간격이 같아 보인다. 바는 absolute 그대로(left:0, 3px) */
.moca_schcal_tint .msc_bar.msc_bar_line:not(.msc_bar_done):not(.msc_bar_holi):has(> .msc_ico) { padding-left: 0; }
.moca_schcal_tint .msc_bar.msc_bar_line:not(.msc_bar_done):not(.msc_bar_holi) > .msc_ico { position: static; left: auto; top: auto; transform: none; flex: 0 0 auto; width: 1em; text-align: center; overflow: visible; line-height: 1; margin-left: 2px; }   /* 바 3px 뒤 2px = 글자 앞 2px(gap) — 4px 로 두었을 때 PC 에서 왼쪽이 더 넓어 보였다(2026-09-07 지적). ★ 상자를 1em 으로 고정하고 글리프를 가운데 둔다 — 이모지 글꼴의 전진폭(advance)이 안드로이드(Noto)에선 1.2em 이상이라 width:auto 로 두면 좌우에 큰 빈칸이 생겼다(실기기 2차 캡처 2026-09-07). 넘치는 만큼은 양쪽으로 같게 나간다 */
/* emph(아이템 emph:true) — 채우기가 꺼진 line 모드에서 제목 글자도 바 색. 바 3px 만으로는 같은 색상의 다른 단과 구분되지 않는
   「진한 배경 + 밝은 글자」 색면용(위드플랜 「선명한」 단) — 이름대로 가장 눈에 띈다. 채우기가 켜지면 색면(bg/fg)이 글자색을 맡는다. */
.moca_schcal:not(.moca_schcal_tint) .msc_bar.msc_bar_line.msc_bar_emph em { color: var(--msc-lc); font-weight: 600; }
.moca_schcal_tint .msc_bar.msc_bar_line:is(.msc_bar_s, .msc_bar_m) { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.moca_schcal_tint .msc_bar.msc_bar_line:is(.msc_bar_m, .msc_bar_e) { border-top-left-radius: 0; border-bottom-left-radius: 0; }
/* 연속일정의 바(채우기 칩) — fill 모드 규칙과 같게: 시작일 왼쪽 바 · 중간일 바 없음 · 종료일 오른콝 끝 바(2026-09-07 지시).
   색면이 이어지므로 날마다 왼쪽 바를 두면 하루짜리가 줄줄이 붙은 것처럼 읽혔다. 제목은 날마다 남긴다(칸이 좁아 어디서 시작했는지 잃지 않게).
   채우기 꺼진 line 모드는 종전대로 매일 왼쪽 바(색면이 없어 바가 유일한 식별자) */
.moca_schcal_tint .msc_bar.msc_bar_line.msc_bar_m:not(.msc_bar_done) > .msc_line { display: none; }
.moca_schcal_tint .msc_bar.msc_bar_line.msc_bar_m:not(.msc_bar_done):not(.msc_bar_holi) { padding-left: 3px; }
/* 연속일정의 중간 칩은 글자를 표시하지 않는다(2026-09-07 지시) — 시작 칩이 제목을 보였고 색면이 이어지므로 중간은 띠만. 종료 칩은 그대로 */
.moca_schcal_tint .msc_bar.msc_bar_line.msc_bar_m:not(.msc_bar_holi) > em { visibility: hidden; }   /* ★ display:none 이 아니다 — 화면이 .msc_bar 높이를 auto 로 풀어 쓰면(위드플랜 .wp_cal_shell) 자식이 다 사라져 칩 높이가 0 이 되고 배경까지 없어졌다(2026-09-07 실측). 글줄은 남기고 글자만 안 보이게 */
/* 연속일정 종료 칩의 후방(오른쪽) 바는 표시하지 않는다(2026-09-07 지시) — 색면이 끝나는 것으로 충분하다 */
.moca_schcal_tint .msc_bar.msc_bar_line.msc_bar_e:not(.msc_bar_done) > .msc_line { display: none; }
.moca_schcal_tint .msc_bar.msc_bar_line.msc_bar_e:not(.msc_bar_done):not(.msc_bar_holi) { padding-left: 3px; padding-right: 0; }
.moca_schcal_tint .msc_bar.msc_bar_line.msc_bar_e:not(.msc_bar_done):not(.msc_bar_holi):has(> .msc_ico) > .msc_line { left: auto; right: 0; }
.moca_schcal_tint .msc_bar.msc_bar_line.msc_bar_e:not(.msc_bar_done):not(.msc_bar_holi):has(> .msc_ico) { padding-left: 0; padding-right: 3px; }
.moca_schcal_tint .msc_bar.msc_bar_line.msc_bar_e:not(.msc_bar_done):not(.msc_bar_holi) > .msc_ico { margin-left: 2px; }   /* 종료 칩은 왼쪽에 바가 없다 — 칩 끝에서 2px */
/* 연속일정의 작성자 이모지는 시작일·종료일에만(2026-09-07 제안 채택) — 중간일은 제목만. line·채우기 두 모드 공통(fill 모드 중간일은 원래 색면만) */
.msc_bar.msc_bar_line.msc_bar_m > .msc_ico { display: none; }
.msc_bar.msc_bar_line.msc_bar_m:has(> .msc_ico) { padding-left: 1px; }
.moca_schcal_tint .msc_bar.msc_bar_line.msc_bar_m:not(.msc_bar_done):not(.msc_bar_holi):has(> .msc_ico) { padding-left: 3px; }

/* 7칸 전부 당월 밖인 주(여섯째 줄이 통째로 다음 달) — 이번 달 일정이 못 들어오는 줄이라 자라지 않고
   숫자 한 줄 높이로 접는다. 그만큼의 높이가 일정 있는 주에 돌아간다(6줄 구조는 유지). */
.msc_row.msc_row_out            { flex: 0 0 auto; }
.msc_row.msc_row_out .msc_day   { min-height: 0; }
.msc_row.msc_row_out .msc_date  { height: 1.4em; min-width: 1.4em; font-size: 11px; }

/* 오늘 버튼 — 오늘이 속한 달을 보고 있으면 비활성(누를 이유가 없다는 상태 표시) */
.msc_todaybtn:disabled          { opacity: 0.35; cursor: default; border-color: var(--c-border); }
.msc_todaybtn:disabled:hover    { background: transparent; }

/* ── 높이 규칙(2026-07-28) — 달력은 내용만큼 자라고, 넘친 높이는 화면 문서 스크롤이 받는다. ──
   JS 실측·Swiper autoHeight 로 높이를 정하지 않는다(둘 다 CSS 를 인라인으로 덮어 어긋난다).
   달력이 자라려면 그것을 담은 칸·래퍼의 고정높이와 클리핑을 함께 풀어야 한다(아래 3줄).
   "뷰포트를 채우는 최소높이" 앵커는 화면 CSS 가 dvh 로 준다(예: `.내화면_main .moca_schcal{min-height:calc(100dvh - …)}`). */
.page_wrap:has(.moca_schcal) { height: auto; min-height: 100%; overflow: visible; }
.page_wrap:has(.moca_schcal) > .moca_layout { flex: none; }
.moca_layout > .moca_layout_pane.moca_schcal,
.moca_layout > .moca_layout_pane:has(> .moca_schcal) { flex: 1 0 auto; overflow: visible; }

/* 달력 화면이 "남은 높이를 채우게" 하는 opt-in — 루트 레이아웃에 class="moca_layout_fill".
   위 flex:none 은 달력이 내용만큼 자라게 하려고 루트를 hug 로 만드는데, 그 대가로 내용이 짧은 달에는
   화면 아래가 비어 보인다(TPL041 실측: 780px 패널에 111px 여백). 이 클래스를 준 화면만 루트가
   남은 높이를 받아(grow 1) 달력이 채우고, 내용이 길면 basis:auto·칸 shrink:0 이라 종전처럼 자라서
   문서 스크롤이 받는다(실측: 강제 1200px 내용에서 잘림 없이 스크롤).
   ★ 기본값(flex:none)을 바꾸지 않은 이유 — 이 규칙은 scheduleCalendar 를 쓰는 모든 프로젝트에 걸린다.
   자체 dvh 앵커로 이미 높이를 잡아 둔 화면은 기본값을 바꾸면 그 화면의 높이 계산까지 함께 흔들린다.
   그래서 채움은 화면이 명시적으로 선택한다. */
.page_wrap:has(.moca_schcal) > .moca_layout.moca_layout_fill { flex: 1 1 auto; }

/* ═══ Calendar 컴포넌트 (data-m-type="calendar") — 붙박이 날짜 선택 달력 ═══════════════
   ⚠ 루트 클래스가 `moca_calendar` 가 아니라 **`moca_cal`** 이다 — moca_calendar 는 위쪽
      inputCalendar 팝업 크롬(.moca_calendar_header 등)이 이미 쓰고 있어서, 같은 토큰을 쓰면
      두 달력의 스타일이 서로 샌다. 내부 클래스 접두사도 msc_(일정달력)와 구분해 mcal_ 이다.
   높이는 내용만큼(자기 완결) — 부모를 채우려면 화면이 `.moca_cal{height:100%}` 등으로 준다. */
.moca_cal { display: flex; flex-direction: column; box-sizing: border-box; width: 100%; padding: 10px; background: var(--c-surface); border: 1px solid var(--c-border); border-radius: var(--radius-md); }

/* ── 헤더 (이전 · 제목(누르면 상위 뷰) · 다음 · 오늘) ── */
.moca_cal .mcal_header { display: flex; align-items: center; gap: 4px; margin-bottom: 8px; }
.moca_cal .mcal_title { flex: 1; min-width: 0; height: 30px; padding: 0 6px; border: 1px solid transparent; border-radius: var(--radius-sm); background: transparent; color: var(--c-text); font-size: 14px; font-weight: 700; cursor: pointer; }
.moca_cal .mcal_title:hover:not(:disabled) { border-color: var(--c-primary); color: var(--c-primary); }
.moca_cal .mcal_title:disabled { cursor: default; }
.moca_cal .mcal_nav { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; padding: 0; border: 1px solid var(--c-border-dark); border-radius: var(--radius-sm); background: var(--c-surface); color: var(--c-text-sub); cursor: pointer; }
.moca_cal .mcal_nav:hover { background: var(--c-bg-hover); border-color: var(--c-primary); color: var(--c-primary); }
.moca_cal .mcal_nav::before { content: ''; width: 16px; height: 16px; background-color: currentColor; }
.moca_cal .mcal_prev::before { -webkit-mask: url('../images/icon_page_prev.svg') no-repeat center / contain; mask: url('../images/icon_page_prev.svg') no-repeat center / contain; }
.moca_cal .mcal_next::before { -webkit-mask: url('../images/icon_page_next.svg') no-repeat center / contain; mask: url('../images/icon_page_next.svg') no-repeat center / contain; }
.moca_cal .mcal_todaybtn { display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 28px; padding: 0; border: 1px solid var(--c-primary); border-radius: var(--radius-sm); background: transparent; color: var(--c-primary); font-size: 0; cursor: pointer; }
.moca_cal .mcal_todaybtn::before { content: ''; width: 26px; height: 26px; background-color: currentColor; -webkit-mask: url('../images/icon_today.svg') no-repeat center / contain; mask: url('../images/icon_today.svg') no-repeat center / contain; }
.moca_cal .mcal_todaybtn:hover { background: var(--c-primary-light); }

/* 본문은 세로 flex — 부모 높이를 채우는 배치(위젯 카드 등)에서 격자가 남은 높이를 나눠 갖게 하는 통로 */
.moca_cal .mcal_body { display: flex; flex-direction: column; min-height: 0; }

/* ── 요일행 ── */
.moca_cal .mcal_week { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); margin-bottom: 2px; }
.moca_cal .mcal_week span { padding: 4px 0; text-align: center; font-size: 12px; font-weight: 600; color: var(--c-text-sub); }
.moca_cal .mcal_week .mcal_sun { color: var(--c-danger); }
.moca_cal .mcal_week .mcal_sat { color: var(--c-primary); }

/* ── 칸 격자 — 일(7열) / 월·연(3열). 칸은 button 이라 키보드 이동·포커스가 기본으로 동작한다 ── */
.moca_cal .mcal_grid { display: grid; gap: 2px; }
.moca_cal .mcal_grid_day { grid-template-columns: repeat(7, minmax(0, 1fr)); }
.moca_cal .mcal_grid_month, .moca_cal .mcal_grid_year { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.moca_cal .mcal_cell { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px; min-width: 0; padding: 0; border: 1px solid transparent; border-radius: var(--radius-sm); background: transparent; color: var(--c-text); font-size: 12px; cursor: pointer; transition: background 0.12s, color 0.12s; }
.moca_cal .mcal_day { height: 34px; }
.moca_cal .mcal_mon, .moca_cal .mcal_yr { height: 44px; font-size: 13px; }
/* hover 는 칸 전체를 둥근 네모로 칠한다 — 선택 칸은 제외(아래 전용 규칙이 배경+글씨를 짝으로 준다).
   제외하지 않으면 hover 배경(--c-bg-hover)만 덮이고 글씨는 --c-on-primary(짙은 잉크)로 남아
   다크에서 "어두운 배경 + 어두운 글씨"가 된다(2026-07-31 지적) */
.moca_cal .mcal_cell:hover:not(:disabled):not(.mcal_sel) { background: var(--c-bg-hover); }
.moca_cal .mcal_cell:disabled { color: var(--c-text-muted); opacity: 0.45; cursor: default; }
/* 날짜 숫자 — 칩 장식 없이 자리만 잡는다(강조는 칸 전체가 담당, 아래 mcal_today/mcal_sel) */
.moca_cal .mcal_dd { display: flex; align-items: center; justify-content: center; width: 26px; height: 18px; box-sizing: border-box; line-height: 1; }

/* 일요일·토요일·공휴일(색은 테마 변수라 라이트/고대비 자동 추종) */
.moca_cal .mcal_sun, .moca_cal .mcal_holi { color: var(--c-danger); }
.moca_cal .mcal_sat { color: var(--c-primary); }
/* 이웃 달 칸 — 격자는 유지하되 톤을 낮춘다(6주 고정이라 칸을 지우면 달마다 높이가 달라진다) */
.moca_cal .mcal_out { color: var(--c-text-muted); opacity: 0.55; }
/* 비영업일(setData 의 off / data-m-weekendoff) — 사선 배경으로 "쉬는 날"을 색과 별개로 표시 */
.moca_cal .mcal_off { background-image: repeating-linear-gradient(135deg, var(--c-bg-hover) 0 3px, transparent 3px 6px); }
/* 오늘 = 테두리 / 선택 = 채움 — 일·월·연 칸이 모두 **칸 전체(둥근 네모, hover 와 같은 모양·크기)** 로 표시된다.
   (2026-07-31 변경 — 종전엔 일자만 26px 원형 칩이라 hover 네모와 크기·모양이 어긋났다)
   ★ 채운 면 위 글씨는 반드시 **짝 변수**(--c-primary / --c-on-primary)로 지정한다.
     다크 primary 는 밝은 시안이라 잉크가 어두워야 하고, 라이트 primary 는 딥 틸이라 흰 글씨여야 한다 —
     한쪽 기준으로 색을 박으면 다른 테마에서 "배경과 글씨가 같은 색"이 된다(2026-07-31 실측 사고). */
.moca_cal .mcal_today { border-color: var(--c-primary); font-weight: 700; }
.moca_cal .mcal_sel { background: var(--c-primary); border-color: var(--c-primary); color: var(--c-on-primary); font-weight: 700; }
/* 선택 칸 hover — 배경과 글씨를 항상 짝으로 바꾼다(한쪽만 바뀌면 대비가 무너진다) */
.moca_cal .mcal_sel:hover:not(:disabled) { background: var(--c-primary-dark); border-color: var(--c-primary-dark); color: var(--c-on-primary); }
.moca_cal .mcal_sel .mcal_holinm, .moca_cal .mcal_sel .mcal_badge { color: inherit; }
/* ── 범례(data-m-legend="true") — 견본은 실제 칸과 같은 모양이라 눈으로 바로 이어진다 ── */
.moca_cal .mcal_legend { display: flex; flex-wrap: wrap; gap: 4px 12px; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--c-border); }
.moca_cal .mcal_lg { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--c-text-sub); }
.moca_cal .mcal_lg i { width: 14px; height: 14px; border: 1px solid transparent; border-radius: var(--radius-sm); box-sizing: border-box; flex-shrink: 0; }
.moca_cal .mcal_lg_today i { border-color: var(--c-primary); }
.moca_cal .mcal_lg_sel i { background: var(--c-primary); border-color: var(--c-primary); }
.moca_cal .mcal_lg_holi i { background: var(--c-danger); border-color: var(--c-danger); }
.moca_cal .mcal_lg_off i { border-radius: var(--radius-sm); border-color: var(--c-border-dark); background-image: repeating-linear-gradient(135deg, var(--c-bg-hover) 0 3px, transparent 3px 6px); }

/* 공휴일명·뱃지 — 칸이 좁으므로 한 줄 말줄임 */
.moca_cal .mcal_holinm { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; line-height: 1.1; }
.moca_cal .mcal_badge { position: absolute; top: 1px; right: 3px; font-size: 12px; line-height: 1; color: var(--c-primary); }

/* ── 개인정보 처리방침 ─────────────────────────────────── */
.policy_html { height: auto; min-height: 100%; overflow-x: hidden; overflow-y: auto; scroll-behavior: smooth; scrollbar-gutter: stable; }
.policy_page { height: auto; min-height: 100vh; margin: 0; overflow: visible; color: #243841; background: #EDF3F5; font-family: "Pretendard", "Noto Sans KR", "Malgun Gothic", sans-serif; }
.policy_page * { box-sizing: border-box; }
.policy_hero { position: relative; min-height: 360px; padding: 42px 24px 112px; color: #F4FCFD; background: linear-gradient(135deg, #082A35 0%, #0A4653 58%, #0C6D70 100%); overflow: hidden; }
.policy_hero::before { content: ''; position: absolute; width: 520px; height: 520px; right: -170px; top: -290px; border: 1px solid rgba(132, 232, 219, 0.14); border-radius: 50%; box-shadow: 0 0 0 80px rgba(132, 232, 219, 0.035), 0 0 0 170px rgba(132, 232, 219, 0.022); }
.policy_hero::after { content: ''; position: absolute; left: 7%; bottom: 34px; width: 7px; height: 7px; border-radius: 50%; background: #74E2CF; box-shadow: 0 0 26px 7px rgba(116, 226, 207, 0.56); }
.policy_hero_inner { position: relative; z-index: 1; width: min(100%, 1120px); margin: 0 auto; }
.policy_brand { display: inline-flex; align-items: center; min-height: 38px; margin-bottom: 48px; text-decoration: none; }
.policy_brand img { display: block; width: 152px; height: auto; }
.policy_kicker { display: block; margin-bottom: 13px; color: #76DEC9; font-size: 12px; font-weight: 800; letter-spacing: 0.18em; }
.policy_hero h1 { margin: 0 0 18px; color: #FFFFFF; font-size: clamp(34px, 4.4vw, 52px); font-weight: 760; line-height: 1.2; letter-spacing: -0.05em; }
.policy_hero p { margin: 0; color: #B9D1D5; font-size: 15px; line-height: 1.75; letter-spacing: -0.01em; }
.policy_shell { position: relative; z-index: 2; display: grid; grid-template-columns: 230px minmax(0, 1fr); gap: 26px; width: min(calc(100% - 48px), 1120px); margin: -72px auto 80px; align-items: start; }
.policy_toc { position: sticky; top: 24px; padding: 22px 18px 20px; border: 1px solid #D7E3E6; border-radius: 18px; background: rgba(255, 255, 255, 0.97); box-shadow: 0 14px 38px rgba(24, 64, 74, 0.09); backdrop-filter: blur(12px); }
.policy_toc_title { display: block; padding: 0 7px 15px; border-bottom: 1px solid #E4ECEE; color: #173A45; font-size: 14px; font-weight: 800; }
.policy_toc nav { display: grid; gap: 3px; padding-top: 12px; }
.policy_toc a { display: grid; grid-template-columns: 28px minmax(0, 1fr); align-items: center; min-height: 37px; padding: 6px 7px; border-radius: 8px; color: #60757D; font-size: 12px; line-height: 1.4; text-decoration: none; transition: color 0.16s ease, background 0.16s ease, transform 0.16s ease; }
.policy_toc a span { color: #159A91; font-size: 12px; font-weight: 800; }
.policy_toc a:hover { color: #153E49; background: #EAF7F5; transform: translateX(3px); }
.policy_toc a:focus-visible, .policy_footer a:focus-visible, .policy_brand:focus-visible { border-radius: 7px; outline: 3px solid rgba(62, 190, 172, 0.34); outline-offset: 3px; }
.policy_document { min-width: 0; padding: 52px 58px 34px; border: 1px solid #DCE6E9; border-radius: 24px; background: #FFFFFF; box-shadow: 0 20px 55px rgba(22, 62, 72, 0.11); }
.policy_document_head { padding-bottom: 37px; }
.policy_document_head > span { display: block; margin-bottom: 12px; color: #159A91; font-size: 12px; font-weight: 800; letter-spacing: 0.15em; }
.policy_document_head h2 { margin: 0 0 19px; color: #142F39; font-size: 27px; font-weight: 760; line-height: 1.35; letter-spacing: -0.035em; }
.policy_document_head p { margin: 0; padding: 20px 22px; border-left: 3px solid #45BFAE; border-radius: 0 12px 12px 0; color: #536A73; background: #F3F8F8; font-size: 14px; line-height: 1.85; }
.policy_section { padding: 38px 0 34px; border-top: 1px solid #E4ECEE; scroll-margin-top: 24px; }
.policy_section h2 { margin: 0 0 18px; color: #173945; font-size: 19px; font-weight: 760; line-height: 1.45; letter-spacing: -0.025em; }
.policy_section p { margin: 0 0 14px; color: #52666E; font-size: 14px; line-height: 1.9; word-break: keep-all; }
.policy_section p:last-child { margin-bottom: 0; }
.policy_section strong { color: #174E5D; font-weight: 750; }
.policy_section ul, .policy_section ol { display: grid; gap: 10px; margin: 17px 0 0 21px; padding: 0; color: #52666E; font-size: 14px; line-height: 1.8; }
.policy_section ul { list-style: disc; }
.policy_section ol { list-style: decimal; }
.policy_section li { padding-left: 4px; word-break: keep-all; }
.policy_section li::marker { color: #1DAA9E; font-weight: 800; }
.policy_section li > ul, .policy_section li > ol { margin: 9px 0 3px 18px; padding: 12px 16px; border-radius: 10px; background: #F4F8F9; }
.policy_contact { margin-top: 14px; padding: 29px 30px 28px; border: 0; border-radius: 16px; color: #E8F9F6; background: linear-gradient(125deg, #0C4C58, #0B746E); }
.policy_contact > span { display: block; margin-bottom: 9px; color: #7DE3D0; font-size: 12px; font-weight: 800; letter-spacing: 0.14em; }
.policy_contact h2 { margin-bottom: 10px; color: #FFFFFF; }
.policy_contact p, .policy_contact strong { color: #D4EDEB; }
.policy_footer { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-top: 34px; padding-top: 24px; border-top: 1px solid #E4ECEE; }
.policy_footer a { display: inline-flex; align-items: center; gap: 8px; color: #52717A; font-size: 13px; font-weight: 700; text-decoration: none; }
.policy_footer a:hover { color: #0E8E85; }

/* 이용 약관 — 개인정보 처리방침과 구조는 공유하고 인디고·바이올렛 팔레트로 구분한다. */
.terms_page { color: #30364A; background: #F0F2F8; }
.terms_page .policy_hero { background: linear-gradient(135deg, #151D3D 0%, #273361 56%, #51477E 100%); }
.terms_page .policy_hero::before { border-color: rgba(183, 173, 255, 0.17); box-shadow: 0 0 0 80px rgba(183, 173, 255, 0.04), 0 0 0 170px rgba(183, 173, 255, 0.025); }
.terms_page .policy_hero::after { background: #B8ACFF; box-shadow: 0 0 26px 7px rgba(184, 172, 255, 0.52); }
.terms_page .policy_kicker { color: #B9B0FF; }
.terms_page .policy_toc { border-color: #DDE0EC; box-shadow: 0 14px 38px rgba(35, 41, 80, 0.1); }
.terms_page .policy_toc_title { color: #282E53; border-color: #E6E7F0; }
.terms_page .policy_toc a { color: #696E84; }
.terms_page .policy_toc a span { color: #776BC7; }
.terms_page .policy_toc a:hover { color: #30365E; background: #F0EEFC; }
.terms_page .policy_toc a:focus-visible, .terms_page .policy_footer a:focus-visible, .terms_page .policy_brand:focus-visible { outline-color: rgba(122, 108, 204, 0.36); }
.terms_page .policy_document { border-color: #E0E2EC; box-shadow: 0 20px 55px rgba(38, 43, 81, 0.12); }
.terms_page .policy_document_head > span { color: #7569C4; }
.terms_page .policy_document_head h2, .terms_page .policy_section h2 { color: #292F54; }
.terms_page .policy_document_head p { border-left-color: #8376D1; color: #5D6277; background: #F5F4FB; }
.terms_page .policy_section { border-color: #E7E8F0; }
.terms_page .policy_section p, .terms_page .policy_section ul, .terms_page .policy_section ol { color: #5B6074; }
.terms_page .policy_section strong { color: #3F4779; }
.terms_page .policy_section li::marker { color: #7A6DCA; }
.terms_page .terms_inline_link { color: #6659B5; font-weight: 750; text-decoration-color: rgba(102, 89, 181, 0.35); text-underline-offset: 3px; }
.terms_page .terms_inline_link:hover { color: #4D438E; text-decoration-color: currentColor; }
.terms_page .policy_contact { background: linear-gradient(125deg, #28315E, #5D4F8C); }
.terms_page .policy_contact > span { color: #C2B9FF; }
.terms_page .policy_contact h2 { color: #FFFFFF; }
.terms_page .policy_contact p, .terms_page .policy_contact strong { color: #ECEAFA; }
.terms_page .policy_footer { border-color: #E7E8F0; }
.terms_page .policy_footer a:hover { color: #6659B5; }

/* ── 라이선스 배너 — moca.js 라이선스 검증(SKILL PART C §63)이 위반 시 body 에 주입. 표시만 하고 조작을 막지 않는다(pointer-events:none). 다크 배경+앰버 글씨 — 3테마 공통 가독 + ERP 붉은계열 금지 규칙 준수 */
.moca_license_banner { position:fixed; right:12px; bottom:12px; z-index:2147483000; padding:6px 14px; border-radius:6px; background:rgba(28,28,38,.9); color:#FFC94D; font-size:12px; letter-spacing:.02em; pointer-events:none; box-shadow:0 2px 8px rgba(0,0,0,.35); }

/* ── 무활동 화면 전체잠금 오버레이 — moca.js idleLock(SKILL PART C §64)이 잠금 시 body 에 주입. 블러+딤으로 내용을 가린다(보안 효력은 logoutUrl 세션 종료가 담당) */
.moca_idle_lock { position:fixed; inset:0; z-index:2147482000; display:flex; align-items:center; justify-content:center; background:rgba(12,12,18,.72); -webkit-backdrop-filter:blur(14px); backdrop-filter:blur(14px); }
.moca_idle_lock_box { display:flex; flex-direction:column; align-items:center; gap:14px; padding:36px 48px; border-radius:var(--radius-md); background:rgba(28,28,38,.92); box-shadow:0 8px 32px rgba(0,0,0,.5); }
.moca_idle_lock_icon { font-size:44px; line-height:1; }
.moca_idle_lock_msg { font-size:14px; color:#ECEAF2; }
.moca_idle_lock_btn { height:34px; padding:0 28px; border-radius:var(--radius-sm); background:#FFC94D; color:#1C1C26; font-size:13px; font-weight:700; }
.moca_idle_lock_btn:hover { background:#FFD97A; }

/* ── 뷰어 도움말 모달 (뷰어 7종 공통 — 툴바 ? 버튼 → com.alert) ────────────
   메시지박스는 기본 360px·가운데 정렬이라 목록형 도움말이 답답하다.
   도움말이 들어간 경우에만 넓히고 왼쪽 정렬로 바꾼다(:has 로 그 경우만 고른다).
   컴포넌트별 CSS 에 7벌 복제하지 않고 메시지박스가 사는 코어에 둔다. */
.moca_messagebox:has(.moca_view_help) { width: min(520px, 92vw); align-items: stretch; }
.moca_view_help { text-align: left; color: var(--c-text); font-size: 13px; line-height: 1.7; }
.moca_view_help ul { margin: 8px 0 0; padding-left: 18px; }
.moca_view_help li { margin: 3px 0; }
.moca_view_help b  { color: var(--c-primary); }
/* 화면 설명(앞)과 뷰어 조작법(뒤)을 가르는 줄 — 두 벌이 한 모달에 들어간다 */
.moca_view_help_sep { margin: 12px 0; border: 0; border-top: 1px solid var(--c-border); }
/* 화면 설명 패널을 그대로 가져오므로 그쪽 제목 클래스도 모달에서 읽히게 한다 */
.moca_view_help .hint_title { display: block; font-weight: 700; color: var(--c-primary); margin-bottom: 4px; }

/* ── 파일 형식 아이콘 (fileupload [형식] 컬럼 — mocaFileTypeBadge) ───────────
   images/icon_file_*.png 14종(앱 아이콘 — 둥근 사각 바탕이 그림 안에 있다. 정사각이라 폭·높이가 같다).
   어느 그림을 쓸지는 moca.$g.fileIconSrc 한 곳이 정한다(아이콘 전용 확장자 표 + 뷰어 매핑). */
.moca_fileicon { width: 20px; height: 20px; object-fit: contain; vertical-align: middle; }

/* ── 뷰어 툴바 인쇄 아이콘 (뷰어 4종 공통 — pdf·doc·ppt·hwp) ────────────────
   글자 「인쇄」는 폰 툴바에서 자리를 너무 먹어 다른 조작을 아래 줄로 밀어낸다.
   버튼 안은 비워 두고 여기서 그린다 — 글자를 남긴 채 숨기면 currentColor 가
   투명해져 아이콘까지 사라진다(색을 글자색에서 가져오기 때문).
   mask + currentColor 라 테마(다크·라이트)를 그대로 따라간다. */
.moca_view_print::before {
	content: ''; display: inline-block; width: 15px; height: 15px; vertical-align: middle;
	background-color: currentColor;
	-webkit-mask: url('../images/icon_print.svg') no-repeat center / 15px 15px;
	        mask: url('../images/icon_print.svg') no-repeat center / 15px 15px;
}

/* ── 뷰어 툴바 내려받기 버튼 (뷰어 6종 공통 — pdf·doc·ppt·hwp·txt·zip) ─────────
   글자 「⤓」 는 작고 흐려 폰에서 눈에 안 띈다(2026-08-28 폰 지적). 아이콘(icon_download.svg)으로 바꾸되
   테마별로 강조 방식을 나눈다(같은 날 2차 지적 — 다크에서 주색 채움은 네온색이라 너무 튄다):
     다크(기본) → 배경·테두리는 다른 버튼과 동일, **아이콘만 주색** (어두운 바탕 위 주색이라 충분히 띈다)
     라이트     → **주색 채움 + 흰 아이콘** (밝은 바탕에선 채워야 눈에 들어온다)
   특이도: 뷰어별 .moca_*_btn 규칙이 코어보다 뒤에 로드되므로 button. 접두로 이긴다. */
button.moca_view_down::before {
	content: ''; display: inline-block; width: 16px; height: 16px; vertical-align: middle;
	background-color: var(--c-primary);
	-webkit-mask: url('../images/icon_download.svg') no-repeat center / 16px 16px;
	        mask: url('../images/icon_download.svg') no-repeat center / 16px 16px;
}
:root.theme-light button.moca_view_down { background: var(--c-primary); border-color: var(--c-primary); }
:root.theme-light button.moca_view_down::before { background-color: #fff; }
:root.theme-light button.moca_view_down:hover {
	background: color-mix(in srgb, var(--c-primary) 80%, #000);
	border-color: var(--c-primary);
}

/* ── 뷰어 공용 드래그 스크롤바 (moca.$g.attachDragScrollbar) ─────────────────
   폰 기본 스크롤바는 얇은 오버레이라 손으로 잡을 수 없다. 엔진이 자기 막대를 그려
   어느 기기에서나 같게 동작시킨다 — 막대를 끌거나, 막대의 빈 곳을 눌러 그 위치로 바로 간다. */
.moca_dragbar {
	position: absolute; right: 2px; width: 14px;
	z-index: 5; border-radius: 7px;
	background: color-mix(in srgb, var(--c-border) 35%, transparent);
	touch-action: none;            /* 막대 위에서는 페이지가 따라 움직이지 않게 */
}
.moca_dragbar_thumb {
	position: absolute; left: 2px; right: 2px; top: 0;
	border-radius: 5px;
	background: var(--c-border-dark);
	cursor: grab; touch-action: none;
}
.moca_dragbar_thumb:hover { background: var(--c-text-sub); }
.moca_dragbar.is_drag .moca_dragbar_thumb { background: var(--c-primary); cursor: grabbing; }
/* 폰에서는 더 두껍게 — 엄지로 잡는다 */
@media (max-width: 768px) {
	.moca_dragbar { width: 18px; right: 1px; }
	.moca_dragbar_thumb { left: 3px; right: 3px; }
}
/* ★ 손가락 기기(pointer:coarse)에서는 폭을 더 준다 (2026-08-31 지시).
   종전에는 손잡이 실폭이 10px 이라 "보이기는 하는데 잡히지는 않는" 상태였다 —
   높이는 최소 48px 로 보장되지만 **폭이 손가락보다 얇으면 겨냥 자체가 안 된다.**
   화면 폭이 아니라 **입력 장치**로 판정한다(태블릿·터치 노트북도 손가락이다). */
@media (pointer: coarse) {
	.moca_dragbar { width: 24px; right: 1px; border-radius: 12px; }
	.moca_dragbar_thumb { left: 3px; right: 3px; border-radius: 9px; }
}
