/*
 * 우리 화면 규약. INSPINIA 템플릿 CSS(app.css) 뒤에 실려 값만 덮는다.
 *
 * 왜 SCSS(_variables.scss)가 아니라 여기인가 —
 * 서버에 node/npx 가 없어 sass 재빌드를 할 수 없다. app.css 는 템플릿에서 나온
 * 그대로 두고, 우리가 바꾸는 몇 값만 이 파일이 갖는다. 손으로 고칠 수 있고
 * 업로드도 이 파일 하나면 끝난다.
 *
 * 색·간격의 큰 축은 여전히 _variables.scss 다. 여기에는 빌드 없이 못 넘어가는 것만 둔다.
 * 규칙이 늘어나 이 파일이 두꺼워지면 그때 SCSS 로 옮기고 한 번 빌드한다.
 *
 * 주의: 이 빌드의 부트스트랩 CSS 변수 접두사는 --bs- 가 아니라 --theme- 다.
 */

/* ==========================================================================
 * 색 체계 — 라이트와 다크가 **같은 이름 위에서** 갈린다
 *
 * 두 모드는 색을 뒤집는 관계가 아니라 **층을 쌓는 방향이 반대인** 관계다.
 * 라이트는 흰 종이 위에 얹는 화면이라 카드가 희고 그 위 요소를 회색으로 눌러
 * 층을 만든다. 다크는 어두운 바닥에서 띄우는 화면이라 뜬 것일수록 밝다.
 * 그래서 값은 다르되 **이름과 역할은 하나**여야 한다 — 그러지 않으면 컴포넌트마다
 * "라이트일 때는 이 색, 다크일 때는 저 색"을 따로 적게 되고, 새 화면이 생길 때마다
 * 그 목록이 한 줄씩 어긋난다.
 *
 * ── 고치기 전에 무엇이 어긋나 있었나 ─────────────────────────
 *
 *  1. **한 화면에 색 계열이 둘 이상 섞여 있었다.** 사이드바(#faf9f5)와 표 머리
 *     (#f5f4ee)는 종이 톤으로 옮겨 두었는데, 템플릿에서 그대로 온 페이지 바탕
 *     (#f8fafc)·테두리(#cbd5e1)·본문 글자(#1e293b)는 여전히 **푸른 회색(slate)**
 *     이었다. 다크는 더 심해서 페이지·카드·입력칸이 slate, 사이드바·표 머리가
 *     올리브라 파란 어둠과 누런 어둠이 한 화면에 같이 있었다.
 *     푸른 회색은 라임의 보색 쪽이라, 나란히 두면 서로를 탁하게 만든다.
 *
 *  2. **다크에 레이어가 없었다.** 템플릿은 카드 · 드롭다운 · 모달 · 팝오버 ·
 *     페이지네이션 · 목록을 **전부 `--theme-secondary-bg` 하나**로 칠한다. 재 보니
 *     카드·입력칸·드롭다운이 모두 `rgb(30,31,39)` — 같은 색이었다. 입력칸이 카드와
 *     같으면 어디가 칠 수 있는 칸인지 안 보이고, 드롭다운이 카드와 같으면 떠 있지 않다.
 *
 *  3. **같은 뜻의 색이 파일 곳곳에 손으로 적혀 있었다.** `#047857`(success)이 13번,
 *     `#b45309`(warning)이 13번… 배지에서 한 번, 버튼에서 한 번, 옅은 면 버튼에서
 *     또 한 번. 한 곳을 고치면 나머지가 남는다.
 *
 * ── 어떻게 다시 짰나 ─────────────────────────────────────────
 *
 * **온도를 종이 쪽으로 통일한다.** 이미 손대 둔 사이드바·표 머리가 기준이고,
 * 브랜드가 라임(황록)이라 중립에 아주 옅은 따뜻함이 도는 계열이 부딪히지 않는다.
 *
 * **면은 층으로 읽는다.** 값이 아니라 '어느 높이의 면인가'로 부른다.
 *
 *     --sf-page     페이지 바탕
 *     --sf-card     카드 · 패널             (라이트: 흰 종이 / 다크: 바닥에서 뜬 판)
 *     --sf-sunken   입력칸처럼 파인 면
 *     --sf-raised   표 머리 · 카드 안의 강조 면
 *     --sf-float    떠 있는 것 — 드롭다운 · 모달 · 팝오버
 *     --sf-hover    손이 얹힌 면
 *     --sf-inset    카드에 딸린 면(펼친 입출고 상세)
 *
 * **선은 두 굵기다.** `--ln` 은 상자를 긋는 선(입력칸·카드 머리·드롭다운),
 * `--ln-soft` 는 같은 상자 안을 가르는 선(표 줄·divider)이다. 하나로 두면
 * 표의 격자가 상자 테두리만큼 진해져 내용보다 먼저 읽힌다.
 *
 * **의미 색은 세 갈래로 나눠 둔다** — 글자로 쓰는 값(`--st-*`), 면으로 칠하는
 * 값(`--st-*-fill`), 줄 전체를 덮는 값(`--st-*-row`). 같은 초록이라도 흰 글자를
 * 얹을 면과 어두운 글자를 얹을 면의 밝기가 다르기 때문이고, 이 셋을 구분해 두지
 * 않았던 것이 위 3번의 원인이다.
 * **면(fill)은 두 모드가 같은 값을 쓴다** — 흰 글자를 얹는 자리라 바탕이 어두워져도
 * 그대로 읽히고, 무엇보다 같은 뜻의 색이 모드에 따라 달라 보이면 안 된다.
 * 갈리는 것은 **어두운 바탕에 글자로 얹을 때**(`--st-*`)와 **줄 색**(`--st-*-row`)뿐이다.
 * ========================================================================== */

/* --------------------------------------------------------------------------
 * 라이트 — 흰 종이 위에 얹는다
 *
 * **면에는 색을 섞지 않는다.** 한때 이 값들이 종이 톤이었다(#f5f4f0 · #f5f4ee) —
 * R 이 B 보다 5 높은, 아주 옅은 누런 기다. 브랜드가 황록이라 온도를 맞춘 것이었는데
 * 실제로 보면 **면이 바래 보인다.** 형광 녹색은 회색 위에 놓일 때 가장 형광이고,
 * 바탕까지 같은 쪽으로 데우면 포인트가 바탕에 녹아 화면 전체가 한 세대 옛것으로 읽힌다.
 *
 * 그래서 면·선·글자는 **중립 회색(zinc)** 으로 두고, 색은 라임 하나만 쓴다.
 * 현장 화면(`mobile.css`)이 이미 그 계열이라 PC 와 폰이 같은 회색을 쓰게 된다.
 * -------------------------------------------------------------------------- */
:root {
    --sf-page: #f7f7f8;
    --sf-card: #ffffff;
    --sf-sunken: #ffffff;      /* 입력칸. 흰 카드 위에서는 선이 칸을 만든다 */
    --sf-raised: #f4f4f5;
    --sf-float: #ffffff;
    --sf-hover: #ededf0;
    --sf-inset: #fafafa;

    --ln: #e4e4e7;
    --ln-soft: #f0f0f2;

    --tx: #18181b;
    --tx-strong: #09090b;
    --tx-muted: #6b6b73;       /* 흰 바탕 5.3:1 · 표 머리(--sf-raised) 위 4.8:1.
                                  표 머리에도 쓰이므로 흰 바탕만 보고 정하면 그쪽에서 4.5 를 못 넘긴다 */
    --tx-faint: #a1a1aa;       /* 읽히기보다 자리를 지키는 글자 — 빈 상태 아이콘 등 */

    /* 의미 색. 글자값은 각 옅은 면 위에서 4.5:1 을 넘긴 값이다(상태 배지 절). */
    --st-success: #046c4e;
    --st-warning: #92400e;
    --st-danger: #b91c1c;
    --st-info: #0369a1;
    --st-neutral: #52525b;        /* 흰 바탕 7.6:1 — 사이드바 메뉴·표 머리 */

    --st-success-fill: #047857;
    --st-success-press: #036249;
    --st-warning-fill: #b45309;
    --st-warning-press: #96450a;
    --st-danger-fill: #dc2626;
    --st-danger-press: #b91c1c;
    --st-info-fill: #0369a1;
    --st-info-press: #025683;
    --st-neutral-fill: #52525b;   /* 눌렀을 때의 면. 흰 글자 7.6:1 */

    /* 줄 색. 어두운 글자를 얹는 옅은 면이고, hover 는 같은 색조로 한 단계 진해진다. */
    --st-success-row: #cff1e6;
    --st-success-row-hover: #c6e9dd;
    --st-warning-row: #fdecce;
    --st-warning-row-hover: #fbedc9;
    --st-danger-row: #fcdada;
    --st-danger-row-hover: #f8c9c9;
    --st-info-row: #cfedfb;
    --st-info-row-hover: #c6e2f6;
    --st-primary-row: #f4f9e2;
    --st-primary-row-hover: #eaf3cd;
    --st-active-row: #ededf0;          /* '수정 중'인 줄 — 색이 아니라 밝기로 말한다 */
    --st-active-row-hover: #e4e4e8;

    /* 모달 뒤를 덮는 막. */
    --scrim: rgba(9, 9, 11, 0.45);
}

/* --------------------------------------------------------------------------
 * 다크 — 어두운 바닥에서 띄운다
 *
 * 층은 **바닥에서 뜬 높이**로 읽는다. 아래로 파인 것(입력칸)만 카드보다 어둡다.
 * 페이지 9.4% → 카드 12.8% → 표 머리 15.5% → 뜬 것 17.6% 로, 한 단계마다
 * 눈이 알아볼 만큼(약 3%p)씩 벌린다. 더 벌리면 카드가 흰 판처럼 뜨고,
 * 좁히면 지금까지처럼 층이 사라진다.
 * -------------------------------------------------------------------------- */
[data-bs-theme="dark"] {
    --sf-page: #18181b;
    --sf-card: #212126;
    --sf-sunken: #1a1a1e;
    --sf-raised: #292930;
    --sf-float: #2e2e36;
    --sf-hover: #33333c;
    --sf-inset: #26262b;       /* 카드에 딸린 면 — 라이트의 #fafafa 와 짝 */

    --ln: #3a3a44;
    --ln-soft: #2c2c34;

    --tx: #e4e4e7;
    --tx-strong: #fafafa;
    --tx-muted: #a1a1aa;
    --tx-faint: #6b6b75;

    /* 어두운 면 위에서 글자로 읽히는 밝기로 올린다. 진한 라이트 값(#047857 등)은
       여기서 바탕에 잠긴다. 면(fill)은 라이트와 같은 값을 그대로 쓴다. */
    --st-success: #34d399;
    --st-warning: #fbbf24;
    --st-danger: #f87171;
    --st-info: #38bdf8;
    --st-neutral: #d4d4d8;

    /* 줄 색. 어두운 바탕에서는 **밝아지는 것**이 떠오르는 방향이라 hover 가 더 밝다. */
    --st-success-row: #16362c;
    --st-success-row-hover: #1c4536;
    --st-warning-row: #392f16;
    --st-warning-row-hover: #4a3c1a;
    --st-danger-row: #3a1f22;
    --st-danger-row-hover: #4a262a;
    --st-info-row: #16303f;
    --st-info-row-hover: #1c3e51;
    --st-primary-row: #262b1a;
    --st-primary-row-hover: #313827;
    --st-active-row: #2e2f38;
    --st-active-row-hover: #383944;

    /* 다크에서는 더 짙게. 뒤가 이미 어두워 옅은 막으로는 앞뒤가 갈리지 않는다. */
    --scrim: rgba(0, 0, 0, 0.62);
}

/* --------------------------------------------------------------------------
 * 템플릿 토큰을 램프에 잇는다
 *
 * **컴포넌트마다 색을 새로 적지 않는다.** 이 두 블록이 카드 · 표 · 입력칸 ·
 * 드롭다운 · 모달 · 페이지네이션 · 목록 · 알림 · 페이지 바탕을 한꺼번에 정한다.
 * 나중에 붙는 컴포넌트도 같은 토큰을 읽으므로 저절로 따라온다.
 *
 * `--theme-light` 는 이름과 달리 **표의 선과 줄무늬**가 읽는 값이다(`.table` 기본값이
 * `--theme-table-border-color: var(--theme-light)`). 상자 선(`--theme-border-color`)과
 * 갈라 두어야 표 격자만 한 단계 옅어진다.
 * -------------------------------------------------------------------------- */
:root {
    --theme-body-bg: var(--sf-page);
    --theme-body-bg-rgb: 247, 247, 248;
    --theme-body-color: var(--tx);
    --theme-body-color-rgb: 24, 24, 27;

    --theme-secondary-bg: var(--sf-card);
    --theme-tertiary-bg: var(--sf-raised);

    --theme-emphasis-color: var(--tx-strong);
    --theme-secondary-color: var(--tx-muted);
    --theme-secondary-color-rgb: 107, 107, 115;
    --theme-tertiary-color: var(--tx-faint);

    --theme-border-color: var(--ln);
    --theme-border-color-translucent: var(--ln);
    --theme-light: var(--ln-soft);
    --theme-light-rgb: 240, 240, 242;

    --theme-secondary-bg-subtle: rgba(113, 113, 122, 0.14);
}

[data-bs-theme="dark"] {
    --theme-body-bg: var(--sf-page);
    --theme-body-bg-rgb: 24, 24, 27;
    --theme-body-color: var(--tx);
    --theme-body-color-rgb: 228, 228, 231;

    --theme-secondary-bg: var(--sf-card);
    --theme-secondary-bg-rgb: 33, 33, 38;
    --theme-tertiary-bg: var(--sf-raised);
    --theme-tertiary-bg-rgb: 41, 41, 48;

    /* 템플릿은 emphasis(#b0bbc5)를 본문(#cbd5e1)보다 **어둡게** 두었다.
       강조가 본문보다 묻히면 `.text-emphasis` 를 쓴 자리가 오히려 약해진다. */
    --theme-emphasis-color: var(--tx-strong);
    --theme-heading-color: var(--tx-strong);
    --theme-secondary-color: var(--tx-muted);
    --theme-secondary-color-rgb: 161, 161, 170;
    --theme-tertiary-color: var(--tx-muted);

    --theme-border-color: var(--ln);
    --theme-border-color-translucent: var(--ln);
    --theme-light: var(--ln-soft);
    --theme-light-rgb: 44, 44, 52;

    --theme-secondary-bg-subtle: rgba(161, 161, 170, 0.16);

    /*
     * 옅은 면 버튼(`btn-soft-*`)이 읽는 값.
     *
     * 템플릿은 이것을 **반투명**(원색 20%)으로 두는데, 그러면 뒤에 무엇이 있느냐에
     * 따라 같은 버튼이 카드 위와 페이지 위에서 다른 색이 된다. 뜻이 같은 옅은 면은
     * 문맥 줄과 알림이 쓰는 값 하나로 모은다 — 화면에서 '주의'는 언제나 같은 노랑이다.
     */
    --theme-success-bg-subtle: var(--st-success-row);
    --theme-warning-bg-subtle: var(--st-warning-row);
    --theme-danger-bg-subtle: var(--st-danger-row);
    --theme-info-bg-subtle: var(--st-info-row);
}

/* --------------------------------------------------------------------------
 * 떠 있는 것 — 드롭다운 · 모달 · 팝오버 · 오프캔버스 · 확인창
 *
 * 템플릿은 이 넷을 카드와 **같은 토큰**(`--theme-secondary-bg`)으로 칠한다.
 * 라이트에서는 둘 다 흰 종이라 그림자가 층을 만들어 주지만, 다크에서는 그림자가
 * 보이지 않아 카드 위에 열린 드롭다운이 카드와 한 몸이 된다.
 * -------------------------------------------------------------------------- */
[data-bs-theme="dark"] .dropdown-menu,
[data-bs-theme="dark"] .modal-content,
[data-bs-theme="dark"] .popover,
[data-bs-theme="dark"] .offcanvas,
[data-bs-theme="dark"] .app-confirm-panel,
[data-bs-theme="dark"] .login-conflict-panel {
    background-color: var(--sf-float);
    border-color: var(--ln);
}

/* 손이 얹힌 줄. 라이트는 종이 톤으로 눌리고, 다크는 한 단계 떠오른다. */
.dropdown-item:hover,
.dropdown-item:focus {
    background-color: var(--sf-hover);
}

[data-bs-theme="dark"] .dropdown-item:hover,
[data-bs-theme="dark"] .dropdown-item:focus {
    color: var(--tx-strong);
}

/* --------------------------------------------------------------------------
 * 파인 것 — 입력칸 · select · 검색형 선택 상자
 *
 * 템플릿은 입력칸 배경도 `--theme-secondary-bg`(카드와 같은 값)로 칠한다.
 * 라이트에서는 흰 카드 위 흰 칸이라 선이 칸을 만들어 주지만, 다크에서는 선까지
 * 옅어서 **어디가 칠 수 있는 칸인지 보이지 않았다.** 한 단계 내려 파인 면으로 만든다.
 * -------------------------------------------------------------------------- */
[data-bs-theme="dark"] .form-control,
[data-bs-theme="dark"] .form-select,
[data-bs-theme="dark"] .input-group-text,
[data-bs-theme="dark"] .choices__inner {
    background-color: var(--sf-sunken);
}

.form-control::placeholder {
    color: var(--tx-faint);
}

/*
 * 비활성 — **색을 지우는 것이 아니라 한 겹 가라앉히는 것**이다.
 * 글자까지 사라지면 무엇이 잠긴 칸인지 읽을 수 없어, 왜 못 고치는지도 알 수 없다.
 */
.form-control:disabled,
.form-select:disabled,
.form-control[readonly] {
    background-color: var(--sf-raised);
    color: var(--tx-muted);
}

[data-bs-theme="dark"] .form-control:disabled,
[data-bs-theme="dark"] .form-select:disabled,
[data-bs-theme="dark"] .form-control[readonly] {
    background-color: var(--ln-soft);
    color: var(--tx-muted);
}

/* --------------------------------------------------------------------------
 * 페이지네이션 · 목록 · 알림 · divider
 * -------------------------------------------------------------------------- */
.page-link {
    background-color: transparent;
    border-color: var(--ln);
}

.page-link:hover {
    background-color: var(--sf-hover);
}

.page-item.disabled .page-link {
    background-color: transparent;
    color: var(--tx-faint);
}

.list-group-item {
    background-color: transparent;
    border-color: var(--ln-soft);
}

/* 알림 상자 — 옅은 면 + 같은 뜻의 글자색. 배지·옅은 면 버튼과 같은 값을 쓴다. */
.alert-success { --theme-alert-bg: var(--st-success-row); --theme-alert-color: var(--st-success); --theme-alert-border-color: transparent; }
.alert-warning { --theme-alert-bg: var(--st-warning-row); --theme-alert-color: var(--st-warning); --theme-alert-border-color: transparent; }
.alert-danger  { --theme-alert-bg: var(--st-danger-row);  --theme-alert-color: var(--st-danger);  --theme-alert-border-color: transparent; }
.alert-info    { --theme-alert-bg: var(--st-info-row);    --theme-alert-color: var(--st-info);    --theme-alert-border-color: transparent; }

.dropdown-divider,
hr {
    border-color: var(--ln-soft);
    opacity: 1;
}

/* 빈 상태의 큰 아이콘. 라이트의 아주 옅은 회색을 다크에 그대로 두면 보이지 않는다. */
.text-body-tertiary {
    color: var(--tx-faint) !important;
}

/* --------------------------------------------------------------------------
 * 색 없는 버튼 — 면이 램프 위에 놓이도록 못 박는다
 *
 * 템플릿의 다크 `.btn-light` 는 면을 **반투명 흰색 5%** 로 준다. 그 자체는 얌전한
 * 값이지만 **뒤에 무엇이 있느냐에 따라 색이 달라진다** — 페이지 위에서는 한 톤,
 * 카드 위에서는 또 한 톤이라 같은 버튼이 자리마다 다르게 보인다.
 * 층이 정해진 면(`--sf-raised`)으로 바꿔 어디에 놓이든 같게 만든다.
 *
 * 다크 선택자를 함께 적는다 — 템플릿의 `[data-bs-theme=dark] .btn-light` 가 더 무거워서
 * 클래스 하나짜리 규칙은 **다크에서만 조용히 밀린다**(`html` 까지 붙어 한 단계 더 무겁다).
 * -------------------------------------------------------------------------- */
.btn-light,
html[data-bs-theme="dark"] .btn-light {
    --theme-btn-bg: var(--sf-raised);
    --theme-btn-border-color: var(--ln);
    --theme-btn-color: var(--tx);
    --theme-btn-hover-bg: var(--sf-hover);
    --theme-btn-hover-border-color: var(--ln);
    --theme-btn-hover-color: var(--tx-strong);
    --theme-btn-active-bg: var(--sf-hover);
    --theme-btn-active-border-color: var(--ln);
    --theme-btn-active-color: var(--tx-strong);
}

/* --------------------------------------------------------------------------
 * select 의 화살표
 *
 * 템플릿은 이 아이콘을 **CSS 안의 SVG 문자열**로 넣어 두고 색을 글자색과 따로 박아
 * 둔다(라이트 #1e293b · 다크 #cbd5e1 — 둘 다 슬레이트). 글자를 종이 톤으로 옮기면
 * 칸 안에서 화살표만 푸른 회색으로 남는다. 같은 그림에 색만 바꿔 다시 넣는다.
 * -------------------------------------------------------------------------- */
.form-select {
    --theme-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%236b6f60' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}

[data-bs-theme="dark"] .form-select {
    --theme-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23a1a1aa' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}

/* 잘못 채운 칸의 선도 같은 danger 를 쓴다 — 배지·알림과 다른 빨강이면 뜻이 갈린다. */
:root,
[data-bs-theme="dark"] {
    --theme-form-invalid-color: var(--st-danger);
    --theme-form-invalid-border-color: var(--st-danger);
}

/* ==========================================================================
 * 화면의 인상을 정하는 값들
 *
 * 템플릿에서 나온 그대로 쓰면 '부트스트랩 관리자 템플릿'으로 읽힌다.
 * 마크업(.wrapper · .sidenav-menu · .card)은 그대로 두고 값만 우리 것으로 덮는다.
 * 아래 다섯 덩어리가 그 값이다 — 글자 크기 · 제목 · 표면 · 표 · 강조.
 * ========================================================================== */

/* --------------------------------------------------------------------------
 * 0. 한국어 줄바꿈
 *
 * CSS 기본값(`word-break: normal`)에서 **한국어는 글자 사이 아무 데서나 끊긴다.**
 * 서양 글자는 단어 안에서 못 끊게 되어 있는데 한글에는 그 규칙이 없어서, 칸이
 * 조금만 좁아도 "쓰는 만큼만 사용하세" / "요" 처럼 마지막 한 글자가 다음 줄로
 * 떨어진다. 옛날 웹페이지처럼 보이는 이유의 절반이 이것이다.
 *
 * `keep-all` 은 **띄어쓰기에서만** 끊게 한다. 대신 띄어쓰기가 없는 긴 덩어리(주소·
 * 코드·바코드)가 칸을 밀고 나갈 수 있어 `overflow-wrap` 을 짝으로 둔다 — 끊을 데가
 * 정말 없을 때만 글자 사이에서 끊는다.
 *
 * 제목은 한 걸음 더 간다. `text-wrap: balance` 가 줄 길이를 고르게 나눠, 두 줄짜리
 * 제목의 둘째 줄에 한 낱말만 남는 모양을 막는다. 모르는 브라우저는 이 줄만 무시하고
 * 지금까지처럼 그린다.
 *
 * 다섯 화면(app · auth · mobile · 소개 · 약관)이 모두 이 파일을 읽으므로 여기 한 번만 둔다.
 * -------------------------------------------------------------------------- */
body {
    word-break: keep-all;
    overflow-wrap: break-word;
}

h1, h2, h3, h4, h5, h6,
.lp-title, .lp-h2 {
    text-wrap: balance;
}

/* --------------------------------------------------------------------------
 * 1. 글자 크기 단계
 *
 * 템플릿 기본은 본문 13px 인데, 페이지 제목까지 13px 이라 화면에 크기 단계가
 * 사실상 없었다. 굵기와 회색조로만 구분하면 눈이 층을 못 읽고 전체가 평평해 보인다.
 *
 * 본문을 14px 로 올려 네 단계를 만든다 — 22 / 16 / 14 / 13.
 * 표는 13px 로 남긴다. MES 는 한 화면에 들어가는 줄 수가 곧 쓸모라,
 * 목록까지 키우면 스크롤이 늘어 오히려 일이 느려진다.
 * 보조 설명에 쓰는 fs-13 은 이제 '본문보다 작은 글자'가 되어 제 역할을 한다.
 * -------------------------------------------------------------------------- */
:root {
    --theme-body-font-size: 0.875rem; /* 14px */
}

.table {
    font-size: 0.8125rem; /* 13px — 목록 밀도는 지킨다 */
}

/* --------------------------------------------------------------------------
 * 2. 페이지 제목
 *
 * 템플릿은 .page-main-title 을 font-size-sm(13px) + uppercase 로 그린다.
 * 화면에서 가장 중요한 글자가 본문과 같은 크기였다.
 * uppercase 는 한글에 아무 효과가 없어 '작은 굵은 글씨'만 남는다.
 *
 * 제목을 둘러싼 회색 띠도 걷는다. 층을 나누는 일은 여백이 하고,
 * 띠·테두리가 겹칠수록 '상자 안의 상자'가 되어 템플릿 냄새가 난다.
 * -------------------------------------------------------------------------- */
.page-main-title {
    font-size: 1.375rem !important; /* 22px */
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: -0.01em;
    text-transform: none;
}

.page-title-head {
    min-height: 0;
    margin: 0 0 1.25rem;
    padding: 0;
    background-color: transparent;
    border-bottom: 0;
}

/* --------------------------------------------------------------------------
 * 3. 표면 — 카드의 모서리 · 테두리 · 그림자
 *
 * 템플릿은 모든 모서리가 4.8px 하나이고, 카드 그림자가 투명도 3% 라 사실상 안 보인다.
 * 그래서 층 구분을 전부 '선'이 하고 있었다 — 그게 상자 안의 상자로 보이는 이유다.
 *
 * 선을 빼고 그림자로 띄운다. 모서리는 두 단계로 벌린다 —
 * 카드 10px / 버튼·입력·배지 6px. 같은 반경이면 크기 감각이 안 생긴다.
 *
 * 카드의 테두리 '두께'는 남기고 '색'만 지운다. 재고 미달·지연처럼
 * .border-danger 로 강조하는 카드가 있는데, 두께를 0 으로 만들면 그 강조가 사라진다.
 * -------------------------------------------------------------------------- */
:root {
    --theme-border-radius: 0.375rem; /* 6px — 버튼 · 입력 · 배지 */
}

.card {
    --theme-card-border-radius: 0.625rem; /* 10px */
    --theme-card-border-color: transparent;

    box-shadow:
        0 1px 2px rgba(15, 23, 42, 0.04),
        0 1px 3px rgba(15, 23, 42, 0.07);
}

/* 어두운 배경에서는 그림자가 안 보인다. 층은 면끼리의 밝기 차가 만들고
   (색 체계 절: 페이지 → 카드), 테두리는 그 경계를 다지는 정도로만 남긴다. */
[data-bs-theme="dark"] .card {
    --theme-card-border-color: var(--ln-soft);

    box-shadow: none;
}

/* --------------------------------------------------------------------------
 * 색칠된 행의 hover
 *
 * 지연·미달·불합격 행은 table-danger 로 옅은 분홍을 깐다. 그런데 마우스를 올리면
 * 부트스트랩이 그 색을 검정과 섞어 hover 색을 만들어서, **옅은 분홍이 탁한 중간톤으로
 * 뒤집히고 글자가 흰색으로 바뀐다.**
 *
 *   평상  #fcdada + 어두운 글자 → 13.75:1
 *   hover #91828b + 흰 글자    →  3.64:1   ← 읽기가 더 어려워진다
 *
 * 색조까지 바뀌어(분홍 → 회보라) 같은 행인지도 잠깐 헷갈린다.
 * 마우스를 올리는 이유는 그 줄을 '더 잘 보려고'인데 반대로 가는 셈이다.
 *
 * 그래서 **같은 색조로 한 단계만 진하게** 하고 글자색은 건드리지 않는다.
 * 어느 줄에 있는지는 밝기 차이만으로 충분히 보인다.
 * -------------------------------------------------------------------------- */
/*
 * 뜻이 있는 줄 — 미달 · 만료 · 취소 · 수정 중.
 *
 * **두 모드가 같은 규칙을 쓴다.** 값만 색 체계 절에서 갈린다(`--st-*-row`).
 *
 * 세 가지를 함께 정해야 한다.
 *
 *  1. **면.** 템플릿에는 이 줄들의 다크 값이 **아예 없었다.** 라이트용 연분홍
 *     (#fcdada)·연노랑이 그대로 남아, 어두운 카드 위에 밝은 띠가 통째로 떴다 —
 *     재고 현황의 미달 줄, 품목 목록의 미달 줄, 고객사의 만료 줄이 그랬다.
 *
 *  2. **hover.** 부트스트랩 기본은 이 색을 검정과 섞어 hover 를 만든다.
 *     그러면 옅은 분홍이 탁한 중간톤(#91828b)으로 뒤집히고 글자까지 흰색이 되어
 *     **13.75:1 이던 대비가 3.64:1 로 떨어진다.** 색조도 분홍→회보라로 바뀌어
 *     같은 줄인지 잠깐 헷갈린다. 마우스를 올리는 이유는 그 줄을 더 잘 보려는
 *     것인데 반대로 가는 셈이다. 그래서 **같은 색조로 한 단계만** 옮긴다 —
 *     라이트는 진해지는 쪽, 다크는 밝아지는 쪽이 각각 '떠오르는' 방향이다.
 *
 *  3. **글자와 선.** 템플릿이 이 줄에 라이트용 값(거의 검정 글자, 연분홍 선)을
 *     박아 둔다. 면만 다크로 바꾸면 어두운 면 위에 검은 글씨가 놓인다.
 */
.table-danger  { --theme-table-bg: var(--st-danger-row);  --theme-table-hover-bg: var(--st-danger-row-hover); }
.table-warning { --theme-table-bg: var(--st-warning-row); --theme-table-hover-bg: var(--st-warning-row-hover); }
.table-success { --theme-table-bg: var(--st-success-row); --theme-table-hover-bg: var(--st-success-row-hover); }
.table-info    { --theme-table-bg: var(--st-info-row);    --theme-table-hover-bg: var(--st-info-row-hover); }

/* primary 가 브랜드 색으로 바뀌었으므로 이 줄의 강조도 라임 쪽으로 옮긴다. */
.table-primary { --theme-table-bg: var(--st-primary-row); --theme-table-hover-bg: var(--st-primary-row-hover); }

/* '수정 중'인 줄. 색을 얹지 않고 밝기만 한 단계 옮긴다 — 상태가 아니라
   '지금 고르고 있는 줄'이라는 표시다(브랜드 색 절: 고른 상태에는 라임을 쓰지 않는다). */
.table-active  { --theme-table-bg: var(--st-active-row);  --theme-table-hover-bg: var(--st-active-row-hover); }

.table-danger,
.table-warning,
.table-success,
.table-info,
.table-primary,
.table-active {
    --theme-table-color: var(--theme-body-color);
    --theme-table-hover-color: var(--theme-body-color);
    --theme-table-border-color: var(--theme-light);
}

/* --------------------------------------------------------------------------
 * 4. 표 머리 — 템플릿 기본(table-light 회색 면)을 그대로 둔다
 *
 * 한때 회색을 빼고 밑줄 하나로 가르는 안을 넣었다가 되돌렸다.
 * 열이 20~30개인 표(수불부 · 생산일보 · 불량분석)가 많은 화면이라,
 * 머리에 면이 있는 편이 가로로 훑을 때 기준선 노릇을 한다.
 * 다시 빼고 싶어지면 아래 두 규칙을 되살리면 된다 —
 * 셀 배경은 --theme-table-bg 와 inset box-shadow 두 경로로 칠해지므로 둘 다 꺼야 한다.
 *
 *   .table > thead.table-light { --theme-table-bg: transparent;
 *                                --theme-table-accent-bg: transparent; }
 *   .table > thead > tr > th   { border-bottom: 2px solid var(--theme-border-color); }
 * -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
 * 5. 상태 배지
 *
 * 크기 — 부트스트랩 기본값 0.75em 은 부모 글자를 따라간다. 표 안에서 9.75px 까지
 * 내려가 읽히지 않았다. 상태 배지는 어디에 놓이든 같은 크기여야 하므로 rem 으로 못 박는다.
 *
 * 색 — `bg-{색}-subtle text-{색}` 조합의 대비를 재 보면 13px 기준(4.5:1)에 크게 못 미쳤다.
 *   warning 1.91:1 · success 2.19:1 · danger 3.11:1 · primary 4.20:1
 * 원인은 팔레트가 아니라 쓰임이다 — #10b981 · #f59e0b 는 '채우기용 중간톤'이라
 * 글자색으로 쓸 밝기가 아니다. 부트스트랩의 text-*-emphasis 로 바꿔도 여전히 미달이다.
 *
 * 그래서 **배지 글자에만** 한 단계 진한 값을 쓴다. 채우기용 $success · $warning 은
 * 그대로 두므로 팔레트를 건드리지 않는다. 아래 값은 각 subtle 배경 위에서 4.5:1 을 넘긴다.
 * -------------------------------------------------------------------------- */
.badge {
    --theme-badge-font-size: 0.8125rem; /* 13px */
    --theme-badge-padding-x: 0.5em;
}

/*
 * 상태 배지의 **모양** — 점 + 얇은 테두리.
 *
 * 옅은 면에 색 글자를 얹는 모양이었는데, 버튼도 같은 옷을 입게 되면서
 * (`btn-soft-*`, 8절) 표의 한 줄에 같은 얼굴이 둘 서게 됐다. '입고완료' 배지와
 * 그 옆의 '수정 · 중지' 버튼이 나란히 놓이면 **어느 것이 눌리는 것인지 색만으로는
 * 안 갈린다.** 실제로 배지를 눌러 보는 일이 생긴다.
 *
 * 배지는 누르는 것이 아니라 **읽는 것**이다. 그래서 면을 한 단계 더 빼고, 대신
 *
 * 면을 옅게 빼고 얇은 테두리로 덩어리를 잡는다. 색은 그대로 읽히면서 '누를 수 있는
 * 면'과 구별된다 — 버튼은 면이 차 있고 배지는 선으로만 서 있다.
 *
 * 앞에 색 점을 찍는 안도 해 봤는데 걷어냈다. 한 화면에 상태가 여럿 뜨는 표에서는
 * 그 점들이 또 하나의 얼룩진 열이 되고, 정작 읽어야 할 것은 점이 아니라 글자다. 색 자체는 여전히 Enum 의
 * `badgeClass()` 하나가 정한다 — 여기서 바꾸는 것은 그 색을 **어떻게 입느냐**뿐이다.
 *
 * 면과 테두리를 `color-mix` 로 **글자색에서 뽑아 쓴다.** 색마다 규칙을 새로 쓰지
 * 않으므로 나중에 상태가 늘어도 저절로 따라온다. 이 함수를 모르는 브라우저에서는
 * 선언이 통째로 버려져 예전의 `bg-*-subtle` 면이 그대로 남는다 — 그때도 읽힌다.
 */
.badge:not(.badge-count):not(.topbar-badge) {
    display: inline-flex;
    align-items: center;
    gap: 0.35em;
    padding-left: 0.6em;
    padding-right: 0.6em;
    border: 1px solid var(--theme-border-color);
    border-color: color-mix(in srgb, currentColor 26%, transparent);
    background-color: color-mix(in srgb, currentColor 9%, transparent) !important;
    font-weight: 500;
    vertical-align: baseline;
}

/* 값은 색 체계 절이 정한다. 다크에서는 같은 이름이 밝은 값을 물고 오므로
   여기에 모드별 규칙을 따로 두지 않는다. */
.badge.text-success { color: var(--st-success) !important; }
.badge.text-warning { color: var(--st-warning) !important; }
.badge.text-danger  { color: var(--st-danger) !important; }
.badge.text-primary { color: var(--brand-emph) !important; }  /* 라이트 올리브 · 다크 라임 */
.badge.text-info    { color: var(--st-info) !important; }

/* 취소·비활성 배지(text-bg-light text-muted)는 회색 위 회색이라 3.86:1 이었다.
   '지금은 중요하지 않다'는 뜻은 남기되 읽히기는 해야 하므로 한 단계만 내린다. */
.badge.text-bg-light.text-muted {
    color: var(--st-neutral) !important;
}

/* 채운 배지·버튼도 같은 이유로 흰 글자 대비가 모자랐다(success 2.54 · warning 2.15 · info 2.77).
   면 색을 한 단계 내려 흰 글자가 읽히게 한다.

   **버튼은 배경을 직접 덮지 않고 변수를 바꾼다.** background-color 를 !important 로 박으면
   hover · active · disabled 가 전부 같은 색으로 굳어 눌리는 느낌이 사라진다.
   테두리 버튼(btn-outline-*)의 hover 배경도 같은 변수를 타므로 함께 잡힌다. */
.btn-success {
    --theme-btn-bg: var(--st-success-fill);
    --theme-btn-border-color: var(--st-success-fill);
    --theme-btn-hover-bg: var(--st-success-press);
    --theme-btn-hover-border-color: var(--st-success-press);
    --theme-btn-active-bg: var(--st-success-press);
    --theme-btn-active-border-color: var(--st-success-press);
    --theme-btn-disabled-bg: var(--st-success-fill);
    --theme-btn-disabled-border-color: var(--st-success-fill);
}

.btn-outline-success {
    --theme-btn-color: var(--st-success-fill);
    --theme-btn-border-color: var(--st-success-fill);
    --theme-btn-hover-bg: var(--st-success-fill);
    --theme-btn-hover-border-color: var(--st-success-fill);
    --theme-btn-active-bg: var(--st-success-fill);
    --theme-btn-active-border-color: var(--st-success-fill);
}

.btn-warning {
    --theme-btn-color: #fff;
    --theme-btn-bg: var(--st-warning-fill);
    --theme-btn-border-color: var(--st-warning-fill);
    --theme-btn-hover-color: #fff;
    --theme-btn-hover-bg: var(--st-warning-press);
    --theme-btn-hover-border-color: var(--st-warning-press);
    --theme-btn-active-color: #fff;
    --theme-btn-active-bg: var(--st-warning-press);
    --theme-btn-active-border-color: var(--st-warning-press);
    --theme-btn-disabled-color: #fff;
    --theme-btn-disabled-bg: var(--st-warning-fill);
    --theme-btn-disabled-border-color: var(--st-warning-fill);
}

.btn-outline-warning {
    --theme-btn-color: var(--st-warning-fill);
    --theme-btn-border-color: var(--st-warning-fill);
    --theme-btn-hover-color: #fff;
    --theme-btn-hover-bg: var(--st-warning-fill);
    --theme-btn-hover-border-color: var(--st-warning-fill);
    --theme-btn-active-bg: var(--st-warning-fill);
    --theme-btn-active-border-color: var(--st-warning-fill);
}

.btn-info {
    --theme-btn-bg: var(--st-info-fill);
    --theme-btn-border-color: var(--st-info-fill);
    --theme-btn-hover-bg: var(--st-info-press);
    --theme-btn-hover-border-color: var(--st-info-press);
    --theme-btn-active-bg: var(--st-info-press);
    --theme-btn-active-border-color: var(--st-info-press);
    --theme-btn-disabled-bg: var(--st-info-fill);
    --theme-btn-disabled-border-color: var(--st-info-fill);
}

.btn-danger {
    --theme-btn-bg: var(--st-danger-fill);
    --theme-btn-border-color: var(--st-danger-fill);
    --theme-btn-hover-bg: var(--st-danger-press);
    --theme-btn-hover-border-color: var(--st-danger-press);
    --theme-btn-active-bg: var(--st-danger-press);
    --theme-btn-active-border-color: var(--st-danger-press);
    --theme-btn-disabled-bg: var(--st-danger-fill);
    --theme-btn-disabled-border-color: var(--st-danger-fill);
}

/* 채운 배지는 상태를 나타낼 뿐 눌리지 않는다. 배경을 그대로 덮어도 잃는 것이 없다. */
.text-bg-success { background-color: var(--st-success-fill) !important; color: #fff !important; }
.text-bg-warning { background-color: var(--st-warning-fill) !important; color: #fff !important; }
.text-bg-info    { background-color: var(--st-info-fill) !important; color: #fff !important; }
.text-bg-danger  { background-color: var(--st-danger-fill) !important; color: #fff !important; }


/* --------------------------------------------------------------------------
 * 5-1. 옅은 면 버튼(btn-soft-*) — 테두리만 있던 버튼이 여기로 온다
 *
 * 한 화면에 `btn-outline-secondary` 가 예닐곱 개씩 떠 있었다. 테두리 버튼은 면이
 * 없어서 표 안에 놓이면 **버튼인지 글자인지 구분이 안 된다** — '거래명세서'와
 * '건 전체 취소'가 나란히 선 자리가 특히 그랬다. 그래서 누를 수 있는 것에는 전부
 * 옅은 면을 깐다.
 *
 * **라임(btn-primary)은 그대로 하나만 남는다.** 옅은 면은 채도가 낮아 라임 면과
 * 경쟁하지 않는다 — '누르면 일이 일어나는 자리' 하나라는 규칙(브랜드 색 절)은
 * 그대로 지켜진다.
 *
 * 템플릿의 `btn-soft-*` 를 그대로 쓰되 두 곳을 고친다.
 *
 *  1. **글자색.** 기본값이 채우기용 중간톤(#ef4444 · #10b981 · #f59e0b)이라
 *     subtle 배경 위에서 2~3:1 밖에 안 나온다. 배지에서 이미 잡은 것과 **같은 값**을
 *     쓴다(5절) — 같은 뜻의 색이 배지와 버튼에서 다르면 화면이 두 말을 한다.
 *  2. **disabled.** 템플릿이 `--theme-btn-disabled-bg` 에 **진한 원색**을 넣어 두어,
 *     비활성 버튼이 오히려 제일 진해진다. 옅은 면을 그대로 유지한다.
 * -------------------------------------------------------------------------- */
.btn-soft-secondary {
    --theme-btn-color: var(--st-neutral);
    --theme-btn-hover-bg: var(--st-neutral-fill);
    --theme-btn-hover-border-color: var(--st-neutral-fill);
    --theme-btn-active-bg: var(--st-neutral-fill);
    --theme-btn-active-border-color: var(--st-neutral-fill);
    --theme-btn-disabled-color: var(--st-neutral);
    --theme-btn-disabled-bg: var(--theme-secondary-bg-subtle);
    --theme-btn-disabled-border-color: transparent;
}

.btn-soft-danger {
    --theme-btn-color: var(--st-danger);
    --theme-btn-hover-bg: var(--st-danger-fill);
    --theme-btn-hover-border-color: var(--st-danger-fill);
    --theme-btn-active-bg: var(--st-danger-press);
    --theme-btn-active-border-color: var(--st-danger-press);
    --theme-btn-disabled-color: var(--st-danger);
    --theme-btn-disabled-bg: var(--theme-danger-bg-subtle);
    --theme-btn-disabled-border-color: transparent;
}

.btn-soft-success {
    --theme-btn-color: var(--st-success);
    --theme-btn-hover-bg: var(--st-success-fill);
    --theme-btn-hover-border-color: var(--st-success-fill);
    --theme-btn-active-bg: var(--st-success-press);
    --theme-btn-active-border-color: var(--st-success-press);
    --theme-btn-disabled-color: var(--st-success);
    --theme-btn-disabled-bg: var(--theme-success-bg-subtle);
    --theme-btn-disabled-border-color: transparent;
}

.btn-soft-warning {
    --theme-btn-color: var(--st-warning);
    --theme-btn-hover-color: #fff;
    --theme-btn-hover-bg: var(--st-warning-fill);
    --theme-btn-hover-border-color: var(--st-warning-fill);
    --theme-btn-active-color: #fff;
    --theme-btn-active-bg: var(--st-warning-press);
    --theme-btn-active-border-color: var(--st-warning-press);
    --theme-btn-disabled-color: var(--st-warning);
    --theme-btn-disabled-bg: var(--theme-warning-bg-subtle);
    --theme-btn-disabled-border-color: transparent;
}

.btn-soft-info {
    --theme-btn-color: var(--st-info);
    --theme-btn-hover-bg: var(--st-info-fill);
    --theme-btn-hover-border-color: var(--st-info-fill);
    --theme-btn-active-bg: var(--st-info-press);
    --theme-btn-active-border-color: var(--st-info-press);
    --theme-btn-disabled-color: var(--st-info);
    --theme-btn-disabled-bg: var(--theme-info-bg-subtle);
    --theme-btn-disabled-border-color: transparent;
}

/* 브랜드 색 버튼의 옅은 판. 면은 라임 20%, 글자는 올리브, 누를 때만 라임 면이 된다 —
   `.btn-outline-primary` 가 하던 것과 같은 규칙이다(브랜드 색 절). */
.btn-soft-primary {
    --theme-btn-color: var(--brand-emph);
    --theme-btn-bg: var(--theme-primary-bg-subtle);
    --theme-btn-hover-color: var(--brand-ink);
    --theme-btn-hover-bg: var(--brand-accent);
    --theme-btn-hover-border-color: var(--brand-accent);
    --theme-btn-active-color: var(--brand-ink);
    --theme-btn-active-bg: var(--brand-accent-press);
    --theme-btn-active-border-color: var(--brand-accent-press);
    --theme-btn-disabled-color: var(--brand-emph);
    --theme-btn-disabled-bg: var(--theme-primary-bg-subtle);
    --theme-btn-disabled-border-color: transparent;
}


/*
 * 입력칸에 붙는 버튼(검색 · 조회)은 테두리를 되살린다.
 *
 * `btn-soft-*` 는 테두리가 transparent 라, 그대로 두면 입력칸의 네 변 중 오른쪽만
 * 끊겨 상자가 반쯤 열린 것처럼 보인다. 면은 그대로 두고 선만 잇는다.
 */
.input-group > .btn-soft-secondary,
.input-group > .btn-soft-primary {
    --theme-btn-border-color: var(--theme-border-color);
}

/* --------------------------------------------------------------------------
 * 아이콘 자리를 미리 비워 둔다 — 화면을 옮길 때마다 메뉴가 다시 그려지던 문제
 *
 * 아이콘은 `<i data-lucide="…">` 라는 **빈 껍데기**로 나가고, 문서 맨 아래의
 * `app.js` 가 `lucide.createIcons()` 로 그것을 `<svg>` 로 바꿔 끼운다. 빈 `<i>` 는
 * inline 이라 **폭도 높이도 0** 이다. 그래서 첫 그림에서는 메뉴 글자가 아이콘 자리를
 * 차지한 채 왼쪽에 붙어 그려지고, 스크립트가 도착하는 순간 아이콘이 튀어나오면서
 * 모든 줄이 오른쪽으로 밀린다. 화면을 옮길 때마다 전체 페이지를 다시 받는 구조라
 * (SPA 가 아니다) 이 밀림이 **메뉴를 누를 때마다** 반복된다.
 *
 * 껍데기에 아이콘과 같은 크기의 상자를 준다. `1em` 이라 아이콘 크기를 바꾸는 자리
 * (`fs-16` · `fs-22` · 접힌 사이드바의 1.25배)를 그대로 따라간다.
 *
 * **바뀐 뒤에는 저절로 빠진다.** lucide 는 `<i>` 를 `<svg>` 로 갈아 끼우므로
 * 이 선택자가 더 이상 걸리지 않는다 — 실제 아이콘 크기는 원래 규칙이 정한다.
 * -------------------------------------------------------------------------- */
i[data-lucide] {
    display: inline-block;
    width: 1em;
    height: 1em;
    vertical-align: -0.125em;
}

/* --------------------------------------------------------------------------
 * 검색형 선택 상자(Choices)가 오류일 때
 *
 * 부트스트랩은 `.is-invalid` 를 **입력칸 자신에** 칠하는데, Choices 는 원래
 * select 를 감춘 채 `.choices` 안으로 옮기고 자기 상자를 대신 세운다. 그래서
 * 빨개져야 할 자리가 화면에 없고, 상자는 멀쩡한 얼굴로 남는다.
 *
 * 감춰진 select 의 상태를 보고 대신 세워진 상자에 옮겨 칠한다.
 * -------------------------------------------------------------------------- */
.choices:has(select.is-invalid) .choices__inner {
    border-color: var(--theme-form-invalid-border-color);
}

/* --------------------------------------------------------------------------
 * 인쇄
 *
 * 공장에서는 아직 종이가 돈다 — 작업지시를 뽑아 현장에 들고 가고, 수불부를 인쇄해
 * 회계와 대조한다. 그런데 그냥 인쇄하면 사이드바·상단바·버튼이 같이 찍혀
 * 정작 표가 한쪽으로 밀리고 두 장이 된다.
 *
 * 전용 인쇄 화면을 따로 만들지 않는다. 화면마다 인쇄본을 두면 화면이 바뀔 때
 * 인쇄본이 뒤처져 '화면과 다른 종이'가 나온다 — 그게 더 나쁘다.
 * 대신 지금 보고 있는 화면에서 **조작하는 것만 걷어낸다.** 무엇이 찍힐지는
 * 화면에 보이는 그대로다.
 *
 * 브라우저의 인쇄 대화상자에서 '배경 그래픽'을 켜야 배지·표 머리의 색이 나온다.
 * 기본값이 꺼짐이라, 색 없이도 읽히도록 표에 테두리를 준다.
 * -------------------------------------------------------------------------- */
@media print {
    /*
     * 조작하는 것은 종이에서 의미가 없다.
     *
     * **화면 제목줄(`page-title-head`)도 통째로 뺀다.** 종이에는 인쇄본 머리글이
     * 따로 서서 회사명·제목·기간·요약을 말한다 — 그 위에 화면 제목이 한 번 더 찍히면
     * 같은 말이 두 줄이 되고, 부제("인쇄하면 …로 나옵니다")는 이미 종이인 자리에서
     * 할 말이 아니다.
     */
    .sidenav-menu,
    .app-topbar,
    .page-title-head,
    .card-header .btn,
    .card-header form,
    .btn,
    .pagination,
    .nav-pills,
    .dropdown-menu,
    .alert-dismissible .btn-close,
    #theme-settings-offcanvas,
    .sf-topbar {
        display: none !important;
    }

    /* 사이드바가 빠졌으니 본문이 그 자리를 쓴다. */
    .wrapper,
    .content-page,
    .page-content,
    .content {
        margin: 0 !important;
        padding: 0 !important;
        width: 100% !important;
    }

    /*
     * 종이에는 그림자·둥근 모서리가 인쇄 잉크만 먹는다.
     *
     * **`break-inside: avoid` 를 카드 전체에 걸지 않는다.** 목록 카드는 표 하나가
     * 통째로 들어 있어 한 장을 훌쩍 넘는데, 그때 브라우저는 '안 쪼개려고' 먼저 다음
     * 장으로 통째로 밀어 본다 — 그래서 **첫 장이 텅 빈 채로 나오고** 내용이 2쪽부터
     * 시작했다. 결국 쪼개기는 하므로 얻는 것도 없다.
     *
     * 쪼개는 자리는 아래 `.table tr` 이 이미 정한다(줄 가운데서 자르지 않는다).
     * 통째로 지켜야 하는 것은 짧은 요약 카드뿐이라 거기에만 남긴다.
     */
    .card {
        border: 1px solid #999 !important;
        border-radius: 0 !important;
        box-shadow: none !important;
    }

    .stat-row .card {
        break-inside: avoid;
    }

    /* 표도 각지게. 카드 안에 담기며 깎이던 첫·마지막 줄의 모서리를 되돌린다. */
    .table,
    .table > :not(caption) > * > *,
    .card > .table-responsive:first-child .table > thead > tr > *:first-child,
    .card > .table-responsive:first-child .table > thead > tr > *:last-child {
        border-radius: 0 !important;
    }

    /* 표는 색을 못 써도 줄이 구분돼야 한다. */
    .table > :not(caption) > * > * {
        border-color: #999 !important;
    }

    .table thead {
        /* 여러 장으로 넘어가도 머리글이 따라간다. */
        display: table-header-group;
    }

    .table tr,
    .stat-row > [class*="col-"] {
        break-inside: avoid;
    }

    /* 가로 스크롤 영역은 종이에서 잘린다. 폭에 맞춰 접는다. */
    .table-responsive {
        overflow: visible !important;
    }

    a[href]::after {
        /* 링크 주소를 뒤에 붙이지 않는다 — 표 안의 코드 링크가 전부 URL 로 늘어난다. */
        content: none !important;
    }

    /*
     * 종이에 옮길 때의 밀도. **눈금은 수불부가 정한다.**
     *
     * 화면 여백을 그대로 찍으면 한 장에 **열 줄 남짓**밖에 안 들어간다 —
     * 한 달치 수불부가 서른 장이 되고, 그 종이는 아무도 넘겨보지 않는다.
     * 결산에서 한 줄씩 대조하는 자료라 한 장에 많이 들어가는 편이 낫다.
     *
     * 한때 이 값이 두 갈래였다 — 수불부만 조밀하고(0.22rem · 잔글씨 0.7rem) 나머지
     * 목록은 한 단계 성겼다(0.28rem · 0.8125rem). 같은 결산에서 나란히 놓고 대조하는
     * 종이들이라 **줄 높이가 다르면 눈이 그 사이를 옮길 때마다 다시 맞춰야 한다.**
     * 가장 조밀한 쪽(수불부)으로 모두 맞춘다.
     */
    .table > :not(caption) > * > * {
        padding: 0.22rem 0.3rem !important;
    }

    /*
     * 잔글씨도 한 값이다.
     *
     * 화면의 크기 클래스가 자리마다 다른데(품목 코드는 `fs-13`, 어떤 칸은 `small`),
     * 종이에서는 그 차이가 뜻을 갖지 않는다 — 어느 것이나 본문 옆에 붙는 보조 값이다.
     */
    .table .fs-11,
    .table .fs-12,
    .table .fs-13,
    .table .fs-14,
    .table small,
    .table .badge {
        font-size: 0.7rem !important;
    }

    /*
     * 수불부는 열이 열하나라 화면에서 **한 칸을 두 줄로** 나눠 두었다 —
     * 품목명 아래에 코드, 수량 아래에 박스로 친 값. 화면에서는 그 편이 읽기 좋지만,
     * 종이에서는 줄 높이가 그대로 두 배가 되어(49px → 24px) 쪽수가 두 배가 된다.
     *
     * 그래서 **종이에서만** 같은 줄로 되돌린다. 값은 하나도 빠지지 않는다.
     *
     * **이것만은 수불부 표에 건다.** 입고·출고 목록의 펼친 명세에도 두 줄짜리 칸이
     * 있는데, 그쪽은 표 안의 표라 같은 규칙을 걸면 상자가 통째로 접힌다.
     */
    .ledger-table td > div {
        display: inline;
        margin-left: 0.3rem;
    }

    /*
     * 루트 글자 크기를 줄인다 — 한 장에 최대한 담는 것이 이 종이의 일이다.
     *
     * **`body { font-size }` 로는 거의 안 줄어든다.** 화면의 잔글씨(`fs-13` 등)와
     * 표의 셀 여백이 전부 `rem` 이라 `body` 가 아니라 루트를 따라간다 — 루트를
     * 한 번 내리면 글자와 여백이 같은 비율로 함께 줄어 표가 고르게 조밀해진다.
     *
     * **`print-dense` 가 붙은 문서에만 건다**(`layouts/app`). 거래명세서는 'A4 한 장에
     * 두 벌'로 실측해 맞춘 양식이라(5.2절) 루트가 흔들리면 그 계산이 통째로 어긋나는데,
     * 두 레이아웃이 이 파일을 같이 쓴다.
     */
    html.print-dense {
        font-size: 12px;   /* 기본 16px — 잔글씨와 셀 여백이 함께 25% 줄어든다 */
    }

    body {
        background: #fff !important;
        font-size: 8.5pt;
    }

    /* 머리글은 장마다 따라가지 않는다 — 첫 장에 한 번이면 된다.
       대신 표와 갈라지지 않게 붙여 둔다. */
    .print-head {
        break-after: avoid;
    }

    @page {
        margin: 12mm;
    }
}

/* --------------------------------------------------------------------------
 * 인쇄본 머리글
 *
 * 종이에는 사이드바도 필터 상자도 없다(위 @media print 가 걷어낸다). 그래서
 * 뽑아 놓은 종이만 보면 어느 회사의 무슨 기간 내역인지 알 수 없다 — 결산이나
 * 거래처 대조로 뽑는 종이라 그것이 없으면 쓸 수가 없다.
 *
 * 화면에도 같은 모양으로 보여준다. 인쇄 대화상자를 닫아도 무엇을 뽑으려던
 * 화면인지 남고, 미리보기와 종이가 어긋나지 않는다.
 * -------------------------------------------------------------------------- */
.print-head {
    margin-bottom: 0.7rem;
}

.print-head-top {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding-bottom: 0.35rem;
    border-bottom: 2px solid var(--theme-body-color);
}

/*
 * 회사 이름과 제목은 **한 줄에 나란히** 선다. 두 줄로 나누면 그만큼 표가 밀리는데,
 * 종이에서 값진 것은 위쪽 잔글씨가 아니라 아래 내용이다.
 */
.print-head-name {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    min-width: 0;
}

/*
 * 머리글의 잔글씨는 **한 값이다**(0.75rem). 회사명 · 조건 · 요약 · 뽑은 시각 · 안내가
 * 전부 같은 무게의 보조 정보라, 크기로 가르면 무엇이 더 중요한지 잘못 말한다.
 * 크게 서는 것은 제목 하나뿐이다 — 무슨 종이인지가 첫 물음이다.
 */
.print-head-tenant {
    font-size: 0.7rem;
    color: var(--theme-secondary-color);
}

.print-head-title {
    margin: 0;
    font-size: 1.0625rem;
    font-weight: 700;
    letter-spacing: -0.02em;
}

/* 언제 뽑았고 누가 뽑았나. 한 줄로 둔다 — 같은 기간을 두 번 뽑았을 때 어느 것이
   나중 것인지 가릴 수 있으면 되는 값이라, 두 줄을 쓸 만큼 값지지 않다. */
.print-head-stamp {
    font-size: 0.7rem;
    white-space: nowrap;
    text-align: right;
    color: var(--theme-secondary-color);
}

/*
 * 조건과 요약은 **한 줄로 흐른다.**
 *
 * 한때 라벨과 값을 두 칸 grid 로 세워 한 쌍이 한 줄씩 차지했다. 수불부는 조건 다섯에
 * 요약 다섯이라 머리글만 열한 줄이 되어 첫 장의 4분의 1을 먹었는데, 그 줄들은 전부
 * 짧은 값이라 옆으로 이어 붙이면 두 줄이면 끝난다.
 *
 * **쌍은 `div` 한 겹이 잡는다** — 줄이 바뀔 때 라벨과 값이 갈라지면
 * ('창고' 다음 줄에 '본사창고') 그 줄은 읽을 수 없다.
 */
.print-head-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.15rem 0.9rem;
    margin: 0.4rem 0 0;
    font-size: 0.7rem;
}

.print-head-meta > div {
    display: flex;
    align-items: baseline;
    gap: 0.3rem;
}

.print-head-meta dt {
    font-weight: 400;
    white-space: nowrap;
    color: var(--theme-secondary-color);
}

.print-head-meta dd {
    margin: 0;
}

.print-head-note {
    color: var(--theme-secondary-color);
}

.print-head-clip {
    margin: 0.3rem 0 0;
    font-size: 0.7rem;
    color: var(--theme-secondary-color);
}

/* 종이에만 해당하는 한 마디(전표 순서 등). 화면과 다르게 뽑는 것이 있으면
   그것을 종이가 스스로 말해야 대조하다 헷갈리지 않는다. */
.print-head-note-line {
    margin: 0.25rem 0 0;
    font-size: 0.7rem;
    color: var(--theme-secondary-color);
}

/* --------------------------------------------------------------------------
 * 입력 칸의 hover · focus 테두리
 *
 * 템플릿은 두 상태 모두 테두리를 --theme-dark-border-subtle(#334155, slate-700)로 바꾼다.
 * 평소 테두리가 #cbd5e1(slate-300)이라 한 번에 여섯 단계를 건너뛴다 — 검은 선이 그어진 것처럼 보인다.
 *
 * 검색창처럼 input-group 으로 묶인 자리에서는 그게 더 두드러진다.
 *  - 색이 바뀌는 것은 .form-control 뿐이고 앞의 돋보기 칸(.input-group-text)은 연한 채로 남는다.
 *  - 게다가 템플릿이 .input-group > .form-control:focus 에 z-index:5 를 줘서, 입력칸의
 *    진한 테두리가 아이콘 칸의 테두리 위에 그려진다. 한 덩어리여야 할 것의 이음매가 어긋나 보인다.
 *
 * 그래서 두 가지를 함께 고친다.
 *  - **색을 한 단계씩만 옮긴다.** hover 는 gray-400(한 칸 진하게), focus 는 primary(파랑).
 *    focus 가 색상 자체를 바꾸므로 hover 와 구분되고, 어느 쪽도 검게 보이지 않는다.
 *  - **묶음 전체가 같이 반응한다.** 아이콘 칸을 짚어도, 입력칸에 커서가 들어가도
 *    그룹 안의 테두리가 한 색이 된다. 색이 같으면 z-index 로 겹쳐 그려져도 이음매가 안 보인다.
 *
 * **hover 쪽에 :not(:focus) 를 단다.** 커서가 든 칸에 마우스가 올라간 상태가 실제로 흔한데,
 * :not() 이 붙은 hover 선택자가 특이도가 더 높아 그냥 두면 focus 파랑을 회색이 덮어쓴다.
 * 순서로 해결하려 하면 나중에 규칙을 하나 끼울 때 다시 깨진다 — 두 상태를 아예 배타로 둔다.
 * -------------------------------------------------------------------------- */
.form-control:hover:not(:focus):not(:disabled):not([readonly]),
.form-select:hover:not(:focus):not(:disabled),
.input-group:hover:not(:focus-within) > .input-group-text,
.input-group:hover:not(:focus-within) > .form-control,
.input-group:hover:not(:focus-within) > .form-select,
.input-group:hover:not(:focus-within) > .btn:not(:hover) {
    border-color: var(--theme-gray-400);
}

.form-control:focus,
.form-select:focus,
.input-group:focus-within > .input-group-text,
.input-group:focus-within > .form-control,
.input-group:focus-within > .form-select,
.input-group:focus-within > .btn {
    border-color: var(--theme-primary);
}

/*
 * 묶음 안의 **버튼**도 같이 반응한다.
 *
 * 품목 칸은 왼쪽에 바코드 버튼이 붙은 한 덩어리인데, 위 규칙에 `.btn` 이 빠져 있어
 * 칸에 마우스를 올리면 **입력칸만 물들고 바코드 쪽 테두리는 그대로였다** — 붙어 있는
 * 두 조각이 따로 노는 것처럼 보인다. 비밀번호의 눈 버튼, 조건 줄의 조회 버튼도 같다.
 *
 * `:not(:hover)` 는 버튼 자신에 손이 올라갔을 때를 비켜 준다. 그때는 버튼이 제 색으로
 * 채워지는 것이 맞고, 여기서 테두리를 덮으면 그 반응이 지워진다.
 *
 * 평소 테두리도 맞춘다. `btn-outline-secondary` 의 기본 테두리(#64748b)는 입력칸
 * (#cbd5e1)보다 여섯 단계 진해서, 손을 올리기 전부터 왼쪽 조각만 도드라져 있었다.
 */
.input-group > .btn[data-scan-open],
.input-group > .btn[data-toggle-password] {
    --theme-btn-border-color: var(--theme-border-color);
}

/* 검색형 선택 상자(Choices.js)도 같은 필터 바에 나란히 놓인다. 하나만 다르게 반응하면
   그것대로 이질감이 된다. 템플릿은 이 상자의 바깥 테두리에 hover·focus 규칙이 아예 없어
   눌러도 아무 반응이 없었고, 안쪽 검색칸만 dark-border-subtle 로 검게 변했다. */
.choices:hover:not(.is-focused):not(.is-open) .choices__inner,
.choices[data-type*="select-one"] .choices__input:hover {
    border-color: var(--theme-gray-400);
}

.choices.is-focused .choices__inner,
.choices.is-open .choices__inner,
.choices[data-type*="select-one"] .choices__input:focus {
    border-color: var(--theme-primary);
}

/* --------------------------------------------------------------------------
 * 검색형 선택 상자의 높이 (.choices-sm)
 *
 * Choices.js 는 원본 select 을 감추고 자기 마크업(.choices > .choices__inner)을 그린다.
 * 그 마크업은 언제나 기본 크기다 — **원본에 붙은 form-select-sm 이 옮겨지지 않는다.**
 * 필터 바는 전부 -sm(입력칸·선택칸·버튼)인데 검색형 선택 상자만 기본 크기로 그려져
 * 위아래로 5px 쯤 높았고, 한 줄에 나란히 놓인 컨트롤들의 높이가 어긋났다.
 *
 *   .form-select-sm  padding .3125rem / font .788rem  → 약 31px
 *   .choices__inner  padding .4532rem / font 13px     → 약 36px
 *
 * 레이아웃의 Choices 초기화가 원본이 -sm 이면 래퍼에 .choices-sm 을 달아 준다.
 * 여기서는 그 표식에 -sm 의 값을 그대로 준다 — 두 숫자가 갈리면 다시 어긋난다.
 *
 * 기본 크기는 손대지 않는다. .choices__inner 의 padding 이 이미 .form-select 와 같은
 * .4532rem 이라 원래 맞아 있었다. 어긋난 것은 -sm 뿐이다.
 * -------------------------------------------------------------------------- */
.choices-sm .choices__inner {
    padding: 0.3125rem 0.7rem !important; /* .form-select-sm 과 같은 값 */
    font-size: 0.788rem;
    border-radius: var(--theme-border-radius-sm) !important;
}

/* 화살표는 top:18px 로 박혀 있다 — 기본 높이(36px)의 한가운데라는 뜻이다.
   높이가 달라지면 어긋나므로 '가운데'로 바꿔 말한다. 기본 크기에서는 값이 같아 변화가 없다. */
.choices[data-type*="select-one"]:after {
    top: 50%;
}

/* --------------------------------------------------------------------------
 * 상단바 알림 배지 (.topbar-badge)
 *
 * 종 아이콘 위에 얹는 숫자다. 탭 옆의 .badge-count 와 쓰임이 다르다 —
 * 그건 줄 안에 자리를 차지하는 값이고, 이건 아이콘 위에 겹쳐 얹는 표시다.
 *
 * 부트스트랩 예제의 `top-0 start-100 translate-middle` 을 그대로 쓰면
 * **배지의 가운데가 버튼 모서리에 놓여 절반이 버튼 밖으로 나간다.**
 * 옆 아이콘을 덮고, 오른쪽 끝에 있으면 화면을 넘어간다.
 * 그래서 버튼 '안쪽' 모서리에 붙이고, 넘치는 만큼만 살짝 띄운다.
 *
 * .badge-count 의 min-width(24px)도 여기서는 과하다. 한 자리 숫자를 위해
 * 아이콘만 한 원을 그릴 이유가 없다 — 폭은 내용을 따라간다.
 * -------------------------------------------------------------------------- */
.topbar-badge {
    position: absolute;
    top: -0.125rem;
    inset-inline-end: -0.25rem;

    /* **높이와 같은 min-width 로 정원을 만든다.**
       한때 좌우 여백만 주고 폭을 내용에 맡겼는데, line-height 가 만드는 높이(16px)가
       글자 한 자의 폭(14px)보다 커서 '세로로 긴 타원'이 됐다. 숫자 배지에서는
       가로가 세로보다 좁아지는 경우를 아예 없애야 한다.

       탭 옆의 .badge-count 는 라운드 사각형인데 여기만 정원인 이유는 쓰임이 달라서다 —
       그건 줄 안에 자리를 차지하는 값이고, 이건 아이콘 위에 얹는 표시다.
       두 자리를 넘으면 좌우로만 늘어나 알약이 된다. */
    display: inline-flex;
    align-items: center;
    justify-content: center;

    min-width: 1.125rem; /* 18px — height 와 같은 값이 정원의 근거다 */
    height: 1.125rem;
    padding: 0 0.25em;

    font-size: 0.6875rem; /* 11px — 아이콘 위에 얹는 표시라 본문보다 작아도 된다 */
    line-height: 1;
    font-variant-numeric: tabular-nums;
    border-radius: 999px;

    /* 어두운 상단바에서도 숫자가 아이콘에 묻히지 않게 테두리로 띄운다. */
    box-shadow: 0 0 0 2px var(--theme-topbar-bg, var(--theme-secondary-bg));
}

/* 상단바 오른쪽 끝에 여백이 없어 마지막 항목이 화면에 붙는다.
   배지가 아니라도 손이 닿기 어려우므로 조금 띄운다. */
.app-topbar .topbar-menu {
    padding-inline-end: 0.75rem;
}

/* --------------------------------------------------------------------------
 * 건수 배지 (.badge-count)
 *
 * 탭(nav-pill) 옆에 붙는 '전체 128 / 미달 4' 같은 숫자다. 글자가 아니라 수를 세는 칸이다.
 *
 * **원형을 쓰지 않는다.** 전에는 `rounded-pill` 이었는데, 그 클래스는 '높이의 절반'을
 * 반지름으로 잡을 뿐 폭을 정해주지 않아서 폭이 글자 하나의 폭을 그대로 따라갔다.
 * 숫자마다 폭이 달라(1 은 좁고 4·8 은 넓다) 같은 한 자리인데도 어떤 탭은 동그랗고
 * 어떤 탭은 눌린 타원이었다. 높이와 같은 min-width 로 정원을 만들 수도 있었지만,
 * **두 자리를 넘어가면 결국 길쭉한 알약이 되어 한 화면 안에 원과 알약이 섞인다.**
 *
 * 그래서 상태 배지와 **같은 모서리(라운드 사각형)** 를 쓴다. 이 앱의 배지는 원래
 * 전부 라운드 사각형이고 건수만 원형이던 것이 예외였다 — 모양을 하나로 두면
 * 자릿수가 늘어도 같은 배지가 옆으로 넓어질 뿐, 다른 물건으로 보이지 않는다.
 *
 * 그래서 여기서 정하는 것은 두 가지뿐이다.
 *  - min-width — 한 자리 배지가 홀쭉해 보이지 않게 하고, 나란한 탭들의 폭을 맞춘다.
 *  - tabular-nums — 숫자 폭을 고정한다. 같은 자릿수면 11 과 48 의 폭이 갈리지 않는다.
 *
 * 모서리·여백·정렬은 .badge 가 이미 갖고 있으므로 다시 쓰지 않는다.
 * -------------------------------------------------------------------------- */
.badge-count {
    /* 라운드 사각형이다. .badge 가 이미 그 모서리를 갖고 있지만 여기 못 박아 둔다 —
       바깥에서 rounded-pill 이 섞여 들어와도 모양이 흔들리지 않는다. */
    border-radius: var(--theme-border-radius);

    /* 한 자리가 홀쭉해 보이지 않게, 나란한 탭들의 폭도 맞춘다. */
    min-width: 1.5rem;

    /* **flex 안에서 줄어들지 않게 한다.**
       알림 드롭다운처럼 배지가 flex 항목이면 flex-shrink 기본값(1)이
       min-width 를 눌러 버려 폭이 글자보다 좁아진다. 그러면 높이는 그대로인데
       폭만 줄어 '세로로 긴 타원'이 된다 — 숫자가 길어질수록 더 눌린다. */
    flex: 0 0 auto;

    /* 같은 자릿수면 11 과 48 의 폭이 갈리지 않는다. */
    font-variant-numeric: tabular-nums;

    /* 세 자리를 넘어가도 가로로만 늘어난다. 줄바꿈으로 두 줄이 되면 높이가 튄다. */
    white-space: nowrap;
}

/* --------------------------------------------------------------------------
 * 표 밀도
 *
 * 한 화면에 들어가는 줄 수가 곧 쓸모다. 목록 화면이 대부분이라 여백을 좁게 잡는다.
 * .table-sm 은 더 좁게 둬야 구분이 남는다.
 * -------------------------------------------------------------------------- */
.table > :not(caption) > * > * {
    padding: 0.5rem 0.5rem;
}

.table-sm > :not(caption) > * > * {
    padding: 0.35rem 0.4rem;
}

/* --------------------------------------------------------------------------
 * 빠른 작업 타일 (대시보드)
 *
 * 대시보드에서 다른 화면으로 떠나는 자리다 — 입고 등록 · 출고 등록 · 품목 등록 ·
 * 재고 조회 넷이 대등하게 선다.
 *
 * **버튼(`btn-soft-secondary`)으로 두지 않는다.** 회색으로 찬 덩어리 넷이 나란히
 * 서면 오른쪽 열에서 가장 무거운 것이 되어, 정작 급한 '재고 부족' 카드보다 먼저
 * 읽힌다. 게다가 그 색은 이제 '보조 동작'을 뜻하는데(8절) 여기 넷은 이 카드의
 * 전부다 — 보조가 아니다.
 *
 * 평소에는 **테두리만 있는 빈 타일**로 두고 아이콘에만 브랜드 색을 얹는다.
 * 손을 올리면 옅은 라임이 스며든다 — 카드 머리의 `.card-more` 와 같은 어법이다.
 * 다만 그쪽처럼 라임으로 꽉 채우지는 않는다. 타일은 면적이 넓어 그러면 너무 세다.
 * -------------------------------------------------------------------------- */
.quick-action {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.375rem;
    width: 100%;
    height: 100%;
    padding: 0.875rem 0.5rem;
    border: 1px solid var(--theme-border-color);
    border-radius: 0.5rem;
    color: var(--theme-body-color);
    font-size: 0.8125rem;
    font-weight: 500;
    text-align: center;
    text-decoration: none;
    transition: background-color 0.12s ease, border-color 0.12s ease;
}

/* 아이콘만 브랜드 색이다. 네 타일이 비어 보이지 않으면서 회색 덩어리도 아니게 된다.
   어두운 바탕에서는 이 값이 라임이다(브랜드 색 절). */
.quick-action i,
.quick-action svg {
    width: 1.25rem;
    height: 1.25rem;
    color: var(--brand-emph);
}

.quick-action:hover,
.quick-action:focus-visible {
    background: var(--theme-primary-bg-subtle);
    border-color: var(--brand-accent);
    color: var(--theme-body-color);
}

/* --------------------------------------------------------------------------
 * 카드 머리의 '더 보기' 단추
 *
 * 카드 제목 옆에서 오른쪽으로 빠지는 길이다 — '이 목록을 더 보라'는 한 가지 뜻뿐이라
 * 글자로 설명할 것이 없고, 화살표가 방향까지 같이 말한다.
 *
 * **일반 버튼(`btn-soft-*`)으로 두지 않는다.** 카드 머리는 제목이 주인공인 자리인데
 * 채워진 네모가 옆에 서면 그것이 먼저 읽힌다. 평소에는 테두리만 있는 동그라미로
 * 두고, 손을 올리면 브랜드 면이 차오른다 — 누를 수 있다는 것은 그때 말하면 된다.
 *
 * 읽어 주는 기기를 위해 화면마다 `aria-label` 로 말을 남긴다.
 * -------------------------------------------------------------------------- */
.card-more {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 1.75rem;
    height: 1.75rem;
    border: 1px solid var(--theme-border-color);
    border-radius: 999px;
    color: var(--theme-secondary-color);
    text-decoration: none;
    transition: background-color 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}

.card-more:hover,
.card-more:focus-visible {
    background: var(--brand-accent);
    border-color: var(--brand-accent);
    color: var(--brand-ink);
}

/* --------------------------------------------------------------------------
 * 요약 카드 (.stat-row)
 *
 * 화면 맨 위에 2~4칸으로 깔리는 숫자 카드다. 마크업은
 *   <div class="row g-3 mb-4 stat-row"> … <div class="card h-100"><div class="card-body">
 * 로 고정돼 있고, 그 안은 '작은 라벨 + 큰 숫자 + 작은 보조설명' 세 줄이다.
 *
 * 두 가지가 겹쳐 칸이 내용보다 훨씬 커 보였다.
 *  - 카드 기본 여백이 20px 인데 본문 글자가 13px 이다. 표 밀도를 좁힌 것과 같은 이유로 과하다.
 *  - 숫자를 22px(fs-3 · fs-22)로 키워 뒀는데, 라벨·보조설명이 13px 이라 한 칸 안에서 격차가 크다.
 *
 * h-100 이라 네 칸의 높이는 '가장 내용이 많은 칸'이 정한다. 그래서 그 칸(예: 작업지시
 * 상세의 '지시 수량' — 라벨·숫자·진행바·보조설명 네 줄)을 줄이면 나머지 세 칸의
 * 빈 여백도 같이 줄어든다. 값을 여기 한 곳에서 잡아야 25개 화면이 같이 움직인다.
 *
 * 카드 안의 다른 글자 크기(fs-4 · fs-5 = 16 · 13.5px)는 이미 본문에 가까워 건드리지 않는다.
 * -------------------------------------------------------------------------- */
/*
 * **아래 여백이 위보다 20px 더 컸던 이유.**
 *
 * 여백 값이 어긋난 것이 아니었다 — 위아래 다 12px 이다. 카드가 **내용보다 20px 더
 * 커져** 있었고, 내용은 위로 붙으니 그 남는 자리가 전부 아래로 갔다.
 *
 * 템플릿의 `.card` 는 `margin-bottom: 20px` 을 갖는다. 그런데 이 카드들은 `h-100`
 * (`height: 100%`)이라 **칸 높이를 따라간다.** 칸의 높이는 그 안에 든 카드의 바깥
 * 높이(내용 + 아래 margin)로 정해지므로, 카드는 자기 margin 만큼 더 늘어난다.
 * `h-100` 과 `margin-bottom` 이 같이 있으면 언제나 이렇게 된다.
 *
 * 줄 사이는 `.row` 의 `g-3` 거터가 이미 벌려 준다 — 카드의 margin 은 여기서 필요 없다.
 */
.stat-row .card {
    margin-bottom: 0;
}

.stat-row .card-body {
    padding: 0.875rem 1rem;
}

/*
 * 숫자.
 *
 * 한때 22px → 18px 로 줄여 두었다. "칸이 내용보다 훨씬 커 보인다" 는 이유였는데,
 * 실제 원인은 글자 크기가 아니라 위의 20px 이었다. 그것을 걷어냈으니 요약 카드가
 * 할 일(숫자를 먼저 읽히게)로 돌아간다. 22px 까지 올리지는 않는다 — 라벨·보조설명이
 * 13px 이라 한 칸 안의 격차가 그만큼 벌어진다.
 *
 * `tabular-nums` 는 자릿수가 다른 숫자 넷이 나란히 설 때 흔들리지 않게 한다.
 */
.stat-row .fs-3,
.stat-row .fs-22 {
    font-size: 1.25rem !important;  /* app.css 의 fs-* 가 !important 라 여기도 필요하다 */
    line-height: 1.25;
    font-variant-numeric: tabular-nums;
}

/* 라벨은 '무엇을 세는 칸인가'다. 회색으로만 두면 보조설명과 같은 무게로 읽힌다. */
.stat-row .card-body > :first-child {
    font-weight: 500;
    letter-spacing: -0.01em;
}

/* 숫자와 보조설명 사이만 살짝 띄운다. 라벨과 숫자는 붙어 있어야 한 덩어리로 읽힌다. */
.stat-row .card-body > :last-child:not(:first-child) {
    margin-top: 0.125rem;
}

/* --------------------------------------------------------------------------
 * 인증 화면 (로그인 · 가입 · 강제 비밀번호 변경)
 *
 * **폰이 기준이고 넓어지면 카드가 된다.** 창고 담당자는 관리자가 발급한 계정으로
 * 폰에서 처음 로그인한다. 그 화면이 이 서비스의 첫인상이고, 여기서 막히면
 * 나머지 화면은 볼 일이 없다.
 *
 * 템플릿(app.css)이 이 화면에 맞지 않는 값 넷을 갖고 있다. app.css 는 건드리지
 * 않는다 — 템플릿을 다시 받으면 되돌아갈 수정이다.
 *
 *  1. .auth-box 여백이 사방 50px 다. 360px 폭 폰에서 좌우 100px 가 먼저 빠져
 *     카드가 260px 로 눌린다.
 *  2. min-height 가 100vh 다. 모바일 브라우저의 주소창 높이를 포함해 계산돼
 *     아래가 잘린다.
 *  3. --theme-border-radius 가 4px 다. 목록 화면의 촘촘한 표에는 맞지만,
 *     48px 짜리 입력칸에 4px 각을 두면 높이만 큰 옛날 관리자 폼으로 읽힌다.
 *  4. .form-control:focus 의 box-shadow 가 none 이다. 칸을 눌러도 티가 안 나
 *     폰에서는 지금 어디에 쓰고 있는지 알기 어렵다.
 * -------------------------------------------------------------------------- */
.auth-box {
    padding: 1.25rem;
    /* 주소창이 접혔다 펴져도 높이가 흔들리지 않는다. dvh 를 모르는 브라우저는 app.css 의 100vh 를 쓴다. */
    min-height: 100dvh !important;
    /* 아이폰 홈 인디케이터에 버튼이 가리지 않게. */
    padding-bottom: calc(1.25rem + env(safe-area-inset-bottom));
}

@media (min-width: 576px) {
    .auth-box {
        padding: 2rem;
    }
}

/*
 * **입력 글자를 16px 로 올린다.**
 *
 * 이보다 작은 칸에 포커스하면 iOS 사파리가 페이지를 통째로 확대한다. 확대된 채로
 * 남기 때문에 다음 칸과 로그인 버튼이 화면 밖으로 나가고, 사용자는 손으로 다시
 * 줄여야 한다. 본문 13px 규약의 예외이고, 예외인 이유는 이것 하나다.
 *
 * 확대 자체를 막는 길(maximum-scale=1)도 있지만 인증 화면에서는 쓰지 않는다 —
 * 눈이 나쁜 사람이 회사코드를 확인할 방법까지 같이 없어진다.
 */
.auth-box .form-control,
.auth-box .form-select {
    font-size: 16px;
    min-height: 52px;
    padding: 0.75rem 0.875rem;
    border-radius: 12px;
}

/*
 * 눈 버튼(비밀번호 보기)은 칸에 붙은 별개의 버튼이 아니라 **칸의 일부**로 보여야 한다.
 * 기본 btn-outline-secondary 는 회색 테두리가 한 겹 더 생겨 두 덩어리로 읽힌다.
 *
 * **색은 변수로 갈아 끼운다.** `background-color` 를 직접 덮으면 기본 상태만 바뀌고
 * hover·active 는 btn-outline-secondary 가 들고 있는 회색 면 그대로 남는다 — 누르는
 * 순간 칸 오른쪽 끝만 회색으로 채워진다. 누를 수 있다는 신호는 아이콘이 진해지는
 * 것으로 충분하다.
 *
 * 면은 **입력칸이 쓰는 변수를 그대로** 쓴다(`--theme-secondary-bg`). 한 번
 * `--theme-body-bg` 로 적었다가 버튼만 다른 톤으로 떴는데, 이 템플릿에서 그 둘은
 * 다른 값이다(#f8fafc / #fbfbfd) — 나란히 붙어 있으면 그 차이가 그대로 보인다.
 *
 * **전환을 끈다.** `.btn` 만 색·배경·테두리를 0.15초에 걸쳐 바꾸는 규칙을 들고 있어서,
 * 칸을 누르면 입력칸은 곧바로 물드는데 **이 버튼만 한 박자 늦게 따라온다** — 버튼이
 * 흐려졌다 나타나는 것처럼 보인다. 현장 화면에서 같은 것을 이미 껐다(mobile.css 30절).
 */
.auth-box .input-group > .btn {
    --theme-btn-color: var(--theme-secondary-color);
    --theme-btn-bg: var(--theme-secondary-bg);
    --theme-btn-border-color: var(--theme-border-color);
    --theme-btn-hover-color: var(--theme-body-color);
    --theme-btn-hover-bg: var(--theme-secondary-bg);
    --theme-btn-hover-border-color: var(--theme-border-color);
    --theme-btn-active-color: var(--theme-body-color);
    --theme-btn-active-bg: var(--theme-secondary-bg);
    --theme-btn-active-border-color: var(--theme-border-color);
    --theme-btn-active-shadow: none;

    transition: none;
    min-height: 52px;
    border-radius: 0 12px 12px 0;
    border-left: 0;
}

/* 포커스 고리는 칸 전체가 이미 그린다. 버튼에 하나 더 얹으면 두 겹이 된다.
   이 버튼은 tabindex="-1" 이라 키보드로 닿지도 않는다. */
.auth-box .input-group > .btn:focus,
.auth-box .input-group > .btn:focus-visible {
    box-shadow: none;
}

/*
 * 오른쪽 테두리도 뺀다. 버튼의 왼쪽 테두리만 지우면 **입력칸의 오른쪽 선**이 그대로
 * 남아 칸 한가운데에 세로선이 그어진 꼴이 된다 — 눈 버튼을 칸의 일부로 보이게 하려던
 * 것이 오히려 칸을 둘로 가른다. 바깥 테두리는 버튼 쪽이 계속 그린다.
 */
.auth-box .input-group > .form-control {
    border-radius: 12px 0 0 12px;
    border-right: 0;
}

/*
 * 포커스 테두리는 칸 전체에 돈다. 절반만 물들면 어긋나 보인다.
 *
 * 색은 아래 '브랜드 색' 절과 같은 잉크다. 파랑으로 두었더니 **비밀번호 칸만** 파랗게
 * 잡혔다 — 이 선택자가 그 절의 `.auth-box .form-control:focus` 보다 한 단계 구체적이라
 * 눈 버튼이 붙은 칸에서만 브랜드 색을 이겼다.
 */
.auth-box .input-group:focus-within > .btn,
.auth-box .input-group:focus-within > .form-control {
    border-color: var(--auth-ink);
}

.auth-box .input-group:focus-within {
    border-radius: 12px;
}

/*
 * 오류일 때는 칸 **전체**가 빨개진다.
 *
 * `is-invalid` 는 입력칸에만 붙어서, 그대로 두면 왼쪽 세 변만 빨갛고 눈 버튼은 회색으로
 * 남는다 — 한 덩어리로 보이게 만들어 둔 칸이라 반쪽만 틀린 것처럼 읽힌다. 포커스가
 * 들어간 상태까지 함께 잡는다(위 규칙이 더 구체적이라 그냥 두면 잉크가 이긴다).
 */
.auth-box .input-group:has(> .form-control.is-invalid) > .btn,
.auth-box .input-group:has(> .form-control.is-invalid) > .form-control,
.auth-box .input-group:focus-within:has(> .form-control.is-invalid) > .btn,
.auth-box .input-group:focus-within:has(> .form-control.is-invalid) > .form-control {
    border-color: var(--theme-form-invalid-border-color);
}

.auth-box .input-group:focus-within:has(> .form-control.is-invalid) {
    box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.15);
}

/* 위 그림자가 칸 전체를 감싸므로 입력칸 자체의 그림자는 뺀다. 두 겹이 되면 번져 보인다. */
.auth-box .input-group > .form-control:focus {
    box-shadow: none;
}

/* 한 손 엄지로 누르는 버튼. 현장 화면(layouts/mobile)의 크기와 맞춘다. */
.auth-box .btn-auth {
    min-height: 52px;
    border-radius: 12px;
    font-size: 1rem;
    font-weight: 600;
}

/*
 * 워드마크.
 *
 * 로그인 화면에는 제목이 없다 — 로고와 '로그인' 버튼이 이미 어디인지 말한다.
 * 그래서 이 로고가 화면의 머리 역할을 대신하고, 그만한 크기와 자리를 갖는다.
 */
.auth-box .auth-brand {
    text-align: center;
    margin-bottom: 2rem;
}

.auth-box .auth-brand-link {
    display: inline-flex;
    align-items: center;
    gap: 0.625rem;
}

.auth-box .auth-brand-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    font-size: 28px;
    line-height: 1;
    border-radius: 16px;
}

.auth-box .auth-brand-name {
    font-size: 1.875rem;
    line-height: 1;
    letter-spacing: -0.03em;
}

/*
 * 로고 아래 한 줄 소개.
 *
 * **본문 13px 보다 한 단계 크다.** 이 화면에서 문장은 이것 하나뿐이고, 처음 온
 * 사람이 무엇을 하는 서비스인지 아는 유일한 줄이다. 회색 13px 는 창고 조명과
 * 바깥 햇빛에서 먼저 사라지는 크기라, 읽히라고 세운 글이 장식이 된다.
 * 대신 로고보다 앞서면 안 되므로 더 키우지 않고 색으로 물러난다.
 * 여백은 로고에 붙는 값 하나뿐이다. 아래 여백은 .auth-brand 가 이미 갖고 있다.
 */
.auth-box .auth-brand-tagline {
    margin-top: 0.75rem;
    font-size: 0.875rem;
    line-height: 1.5;
    letter-spacing: -0.01em;
    color: var(--theme-secondary-color);
}

/*
 * 제목을 화면 제목 크기로 올린다. 무슨 화면인지 헷갈릴 수 있는 곳에만 쓴다.
 *
 * 템플릿의 h4 는 16.3px 라 본문 13px 과 거의 같다. 목록 화면 안에서는 그게 맞지만
 * 인증 화면의 제목은 그 하나로 여기가 어디인지 말해야 한다.
 */
.auth-box .auth-headings {
    text-align: center;
    margin-bottom: 2rem;
}

.auth-box .auth-heading {
    font-size: 1.5rem;
    font-weight: 700;
    letter-spacing: -0.02em;
}

.auth-box .form-label {
    font-size: 0.8125rem;
    margin-bottom: 0.375rem;
}

/* 기본 체크박스는 13px 이라 폰에서 조준이 안 된다. */
.auth-box .form-check-input {
    width: 1.25rem;
    height: 1.25rem;
    margin-top: 0.05rem;
}

.auth-box .form-check-label {
    padding-left: 0.35rem;
    font-size: 0.875rem;
    line-height: 1.5;
}

/*
 * 폰에서는 카드 테두리가 정보를 더하지 않는다. 좁은 화면에서 테두리는 안쪽 폭만
 * 깎아먹으므로 장식을 걷는다. 576px 부터 원래 카드로 돌아간다.
 *
 * **대신 폭은 끝까지 뻗지 않는다.** 테두리를 걷고 화면을 꽉 쓰게 두었더니 큰 폰에서
 * 입력칸과 로그인 버튼이 좌우 끝에 닿아 한 줄이 필요 이상으로 길어졌다. 20rem 에서
 * 멈춘다 — 360px 보다 좁은 폰에서는 좌우 여백이 먼저 걸려 이 값이 닿지 않으므로
 * 작은 화면은 지금 그대로다.
 */
@media (max-width: 575.98px) {
    .auth-box .card {
        /* 레이아웃이 카드에 인라인 style 로 max-width 를 박아 둔다. 덮으려면 !important 다. */
        max-width: 20rem !important;
        border: 0;
        box-shadow: none;
        background: transparent;
        margin-bottom: 0;
    }

    .auth-box .card-body {
        padding: 0 !important;
    }
}

@media (min-width: 576px) {
    .auth-box .card {
        border-radius: 16px;
    }
}

/* --------------------------------------------------------------------------
 * 글꼴 — Pretendard 가변폰트 하나
 *
 * 바꾸지 않는다. 한글 UI 에서 지금 가장 무난한 선택이고 이미 로컬에 있다.
 * (CDN 은 쓰지 않는다. `public/assets/fonts/PretendardVariable.woff2` 와
 *  `css/` 의 형제 관계를 깨면 app.css 의 @font-face 가 파일을 못 찾는다.)
 *
 * 대신 **폴백 순서**를 고친다. 템플릿 기본값은
 *   "Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont,
 *   system-ui, "Malgun Gothic", sans-serif
 * 인데, 한글이 그려지는 순간 문제가 둘 있다.
 *
 *  1. `-apple-system`(SF Pro)에는 한글이 없다. 그래서 한글만 다시 아래로 떨어져
 *     라틴과 한글이 서로 다른 글꼴로 섞여 나온다.
 *  2. `Malgun Gothic` 은 윈도우 폰트다. 안드로이드·iOS 에는 없어서 결국
 *     `sans-serif` 까지 내려간다.
 *
 * 각 기기의 한글 기본 글꼴을 이름으로 끼워 넣어, 폰트가 도착하기 전 구간에도
 * 한글이 제 글꼴로 그려지게 한다. 도착 후 바뀌는 폭도 그만큼 작아진다.
 * -------------------------------------------------------------------------- */
:root {
    --theme-font-sans-serif:
        "Pretendard Variable", Pretendard,
        -apple-system, BlinkMacSystemFont, system-ui,
        "Apple SD Gothic Neo",   /* iOS · macOS 의 한글 */
        "Noto Sans KR",          /* 안드로이드의 한글(본고딕) */
        "Malgun Gothic",         /* 윈도우의 한글 */
        sans-serif;
}

/* --------------------------------------------------------------------------
 * 품목 자동완성 (x-item-select)
 *
 * select2 · Choices 같은 라이브러리를 쓰지 않고 직접 그린다. CDN 을 안 쓰는 원칙과
 * 맞고, 무엇보다 **스캐너가 타이핑해 넣는 입력칸**을 라이브러리가 감싸는 순간
 * 그 흐름이 먼저 깨진다. 여기서는 평범한 input 위에 목록만 얹는다 —
 * 스크립트가 죽어도 입력칸은 그대로 동작한다.
 *
 * 목록은 .input-group **밖**, 입력칸만 감싸는 .item-select-field 를 기준으로 연다.
 * 안에 두면 입력칸이 마지막 자식이 아니게 되어 부트스트랩이 오른쪽 모서리를 0 으로
 * 깎는다(`.input-group > :not(:last-child)`). 그 선택자는 6단계라 덮을 수도 없다.
 * -------------------------------------------------------------------------- */
.item-select-field {
    position: relative;
}

/*
 * 스캔 버튼이 감춰졌을 때(스크립트가 안 실린 경우) 입력칸의 왼쪽 모서리를 되돌린다.
 *
 * `.input-group` 은 **첫 자식이 아닌** 요소의 왼쪽 모서리를 깎는데 `:first-child` 는
 * 감춰진 형제도 센다. 수량 칸의 `pack-idle` 과 같은 사정이다 — 옆에 아무것도 없는데
 * 한쪽만 각진 칸이 남는다.
 */
.input-group:has(> .btn[data-scan-open][hidden]) > .form-control {
    border-radius: var(--theme-border-radius) !important;
}

/* --------------------------------------------------------------------------
 * 칸에 붙은 아이콘 버튼 — 눈(비밀번호 보기) · 바코드(카메라로 읽기)
 *
 * 둘 다 **칸의 일부**지 칸 옆에 놓인 별개의 버튼이 아니다. 그런데 기본
 * `btn-outline-secondary` 는 자기 테두리를 그리고, `.input-group` 은 맞닿은 두
 * 테두리를 1px 로 겹쳐 놓는다 — 그 결과 **칸 안에 세로선(|) 이 한 줄 그어진다.**
 * 하나였던 칸이 둘로 갈려 보이고, 왼쪽 바코드 자리에서는 아이콘이 칸 밖에 붙은
 * 것처럼 읽힌다.
 *
 * 로그인 화면에서는 이미 같은 것을 고쳐 두었다(`.auth-box .input-group > .btn`).
 * 그 규칙이 `.auth-box` 안에만 걸려 있어 업무 화면(비밀번호 변경 · 입고 · 출고)에는
 * 선이 그대로 남았다. 컴포넌트는 하나인데 화면에 따라 다르게 보이던 셈이라,
 * 여기서 **버튼을 가리켜** 잡는다 — 검색·추가처럼 진짜 버튼인 부착물은 그대로 둔다.
 *
 * 색은 변수로 갈아 끼운다. `background-color` 를 직접 덮으면 hover·active 는
 * btn-outline-secondary 의 회색 면이 그대로 남아, 누르는 순간 칸 한쪽 끝만 회색으로
 * 채워진다. 면은 입력칸이 쓰는 변수(`--theme-secondary-bg`)를 그대로 쓴다.
 * -------------------------------------------------------------------------- */
.input-group > .btn[data-toggle-password],
.input-group > .btn[data-scan-open] {
    --theme-btn-color: var(--theme-secondary-color);
    --theme-btn-bg: var(--theme-secondary-bg);
    --theme-btn-border-color: var(--theme-border-color);
    --theme-btn-hover-color: var(--theme-body-color);
    --theme-btn-hover-bg: var(--theme-secondary-bg);
    --theme-btn-hover-border-color: var(--theme-border-color);
    --theme-btn-active-color: var(--theme-body-color);
    --theme-btn-active-bg: var(--theme-secondary-bg);
    --theme-btn-active-border-color: var(--theme-border-color);
    --theme-btn-active-shadow: none;

    /* `.btn` 은 색을 0.15초에 걸쳐 바꾼다. 칸은 곧바로 물드는데 이 버튼만 한 박자
       늦게 따라와서, 버튼이 흐려졌다 나타나는 것처럼 보인다. */
    transition: none;
}

/* 눈 버튼은 칸 오른쪽. 버튼의 왼쪽 테두리만 지우면 **입력칸의 오른쪽 선**이 남아
   선이 그대로이므로, 맞닿은 두 변을 함께 지운다. 바깥 테두리는 버튼이 계속 그린다. */
.input-group > .btn[data-toggle-password] {
    border-left: 0;
}

.input-group:has(> .btn[data-toggle-password]) > .form-control {
    border-right: 0;
}

/* 바코드 버튼은 칸 왼쪽. 스크립트가 안 실리면 `hidden` 으로 남으므로, 그때는
   입력칸이 왼쪽 테두리를 도로 가져야 한다 — 위 모서리 규칙과 같은 사정이다. */
.input-group > .btn[data-scan-open] {
    border-right: 0;
}

.input-group:has(> .btn[data-scan-open]:not([hidden])) > .form-control {
    border-left: 0;
}

/* 포커스와 오류는 칸 **전체**에 돈다. 절반만 물들면 붙여 놓은 것이 도로 갈라진다.
   오류가 포커스를 이겨야 하므로 순서가 뒤다. */
.input-group:focus-within > .btn[data-toggle-password],
.input-group:focus-within > .btn[data-scan-open] {
    border-color: var(--brand-emph);
}

.input-group:has(> .form-control.is-invalid) > .btn[data-toggle-password],
.input-group:has(> .form-control.is-invalid) > .btn[data-scan-open] {
    border-color: var(--theme-form-invalid-border-color);
}

.item-suggest {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    z-index: 1056;   /* 모달(1055) 위에서도 열려야 한다 */
    max-height: 17rem;
    margin: 0;
    padding: 0.25rem;
    overflow-y: auto;
    list-style: none;
    background: var(--theme-secondary-bg);
    border: 1px solid var(--theme-border-color);
    border-radius: 0.625rem;
    box-shadow: 0 4px 12px rgba(15, 23, 42, 0.08), 0 16px 32px -20px rgba(15, 23, 42, 0.45);
    /* 폰에서 목록을 훑을 때 관성 스크롤이 걸리게 한다. */
    -webkit-overflow-scrolling: touch;
}

.item-suggest-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.625rem;
    border-radius: 0.5rem;
    cursor: pointer;
}

/*
 * 키보드로 고른 줄과 마우스를 올린 줄을 **같은 색**으로 칠한다.
 * 둘이 다르면 화살표로 내려가는 도중 마우스가 놓인 줄이 같이 밝아져
 * 어느 것이 선택된 줄인지 알 수 없게 된다.
 */
.item-suggest-row:hover,
.item-suggest-row.is-active {
    background: var(--theme-primary-bg-subtle, #e7effd);
}

.item-suggest-name {
    font-weight: 600;
    line-height: 1.35;
}

.item-suggest-meta {
    font-size: 0.8125rem;
    color: var(--theme-secondary-color);
    line-height: 1.35;
}

/* 현재고는 오른쪽에 붙여 세로로 줄을 맞춘다. 목록을 훑는 눈이 한 줄로 내려간다. */
.item-suggest-stock {
    flex-shrink: 0;
    font-size: 0.8125rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--theme-secondary-color);
}

/* --------------------------------------------------------------------------
 * 없는 품목을 만났을 때의 길 (x-item-select)
 *
 * 스캔한 코드가 등록되지 않았을 때 빨간 안내 아래에 뜬다. 링크가 아니라 **면이 있는
 * 줄**로 두는 이유 — 빨간 글씨 바로 밑에 파란 글씨가 붙으면 오류 문구의 일부로
 * 읽힌다. 누를 수 있는 자리라는 것이 모양으로 보여야 한다.
 * -------------------------------------------------------------------------- */
.item-select-create {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    margin-top: 0.5rem;
    padding: 0.375rem 0.625rem;
    border: 1px solid var(--theme-border-color);
    border-radius: 0.5rem;
    background: var(--theme-secondary-bg);
    font-size: 0.8125rem;
    font-weight: 600;
    text-decoration: none;
}

.item-select-create:hover {
    border-color: var(--theme-primary);
}

/* --------------------------------------------------------------------------
 * 처리 결과 모달 (입고 · 출고를 끝냈을 때)
 *
 * 화면 맨 위의 초록 띠였던 것을 **가운데 상자**로 바꿨다. 띠는 화면이 원래 달고 있던
 * 안내와 같은 자리·같은 모양이라 방금 내가 한 일인지가 구분되지 않았고, 현장에서는
 * 처리 뒤 같은 폼으로 돌아오기 때문에 그것을 보려면 눈을 한 번 올렸다 내려야 했다.
 *
 * 확인을 누르기 전까지 사라지지 않는다 — 그 한 번이 "처리 후 재고"를 읽게 만든다.
 *
 * 성공을 초록으로 칠하지 않는다 — 초록(success)은 목록의 '정상' 배지에 이미 쓰이고
 * 있어서, 상자까지 초록이면 같은 화면 안에서 뜻이 겹친다. 아이콘 하나만 초록을 쓴다.
 *
 * 내용은 App\Support\MovementResult 가 만들고 layouts/partials/flash 가 그린다.
 * -------------------------------------------------------------------------- */
.flash-result-dialog {
    position: fixed;
    inset: 0;
    z-index: 1090;              /* 모달(1055)·품목 목록(1056)보다 위 */
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
}

.flash-result-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(15, 23, 42, 0.45);
    opacity: 0;
    transition: opacity 0.18s ease;
}

.flash-result-dialog.is-open .flash-result-backdrop {
    opacity: 1;
}

/* 상자 자체. 뒤 화면을 가리는 것이 일이므로 폭을 넘기지 않는다. */
.flash-result {
    position: relative;
    width: 100%;
    max-width: 26rem;
    max-height: calc(100dvh - 2rem);
    overflow-y: auto;
    padding: 1.25rem;
    border-radius: 0.875rem;
    background: var(--theme-secondary-bg);
    box-shadow: 0 24px 48px -12px rgba(15, 23, 42, 0.35);
    opacity: 0;
    transform: translateY(8px) scale(0.98);
    transition: opacity 0.18s ease, transform 0.18s ease;
}

.flash-result-dialog.is-open .flash-result {
    opacity: 1;
    transform: none;
}

/* 움직임을 줄여 달라고 한 사람에게는 나타나기만 한다. */
@media (prefers-reduced-motion: reduce) {
    .flash-result,
    .flash-result-backdrop {
        transition: none;
    }
}

/* 상자가 떠 있는 동안 뒤 화면이 따라 움직이지 않게 한다. */
body.flash-result-open {
    overflow: hidden;
}

.flash-result-head {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
}

.flash-result-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 42px;
    height: 42px;
    border-radius: 999px;
    background: var(--theme-success-bg-subtle, #d1fae5);
    color: var(--theme-success, #10b981);
}

.flash-result-title {
    font-size: 1.125rem;
    font-weight: 700;
    line-height: 1.3;
}

.flash-result-subject {
    margin-top: 0.125rem;
    font-size: 0.9375rem;
    line-height: 1.35;
}

.flash-result-meta {
    font-size: 0.8125rem;
    color: var(--theme-secondary-color);
}

/*
 * 확인해야 할 두 숫자 — 넣은 수량과 그래서 지금 얼마인지.
 *
 * **나머지와 크기를 나눈다.** 처음에는 창고·공급처·금액까지 같은 크기로 한 줄에
 * 늘어놓았는데, 그러면 정작 봐야 할 두 값이 그 사이에 묻혔다. 사람이 이 상자를
 * 여는 이유는 그 둘을 확인하는 것이고 나머지는 맞는지 훑기만 한다.
 *
 * 반씩 나눠 나란히 둔다. 세로로 쌓으면 상자가 길어져 확인 버튼이 접히는 폰에서 밀린다.
 */
.flash-result-primary {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: start;
    gap: 0.75rem;
    margin: 1rem 0 0;
    padding: 0.875rem 1rem;
    border-radius: 0.625rem;
    background: var(--sf-raised);
}

.flash-result-primary dt {
    font-size: 0.75rem;
    font-weight: 500;
    color: var(--theme-secondary-color);
}

.flash-result-primary dd {
    margin: 0.125rem 0 0;
    font-size: 1.375rem;
    font-weight: 700;
    line-height: 1.2;
    font-variant-numeric: tabular-nums;

    /* 긴 숫자가 칸을 넘겨도 글자 가운데서 끊지 않는다. */
    word-break: keep-all;
}

/* 포장 환산처럼 큰 숫자에 딸리는 값. 크게 내면 어느 쪽이 답인지 흐려진다.
   선택자를 `dd` 까지 붙여야 위의 큰 글씨 규칙(0,1,1)을 이긴다. */
.flash-result-primary dd.flash-result-primary-sub {
    margin: 0.25rem 0 0;
    font-size: 0.8125rem;
    font-weight: 500;
    line-height: 1.3;
    color: var(--theme-secondary-color);
}

/*
 * 큰 값이 하나뿐인 경우(여러 줄짜리 건의 '품목 N개').
 *
 * 두 칸짜리 격자에 하나만 놓이면 오른쪽 절반이 통째로 빈다 — 상자가 고장 난
 * 것처럼 보인다. 그때는 한 칸으로 편다.
 */
.flash-result-primary:has(> div:only-child) {
    grid-template-columns: 1fr;
}

/* 훑는 값들. 라벨을 작게 앞에 붙여 한 줄로 흐르게 둔다. */
.flash-result-details {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.875rem;

    /* 큰 숫자 상자 바로 아래 붙으면 그 상자의 일부처럼 읽힌다. 선으로 가른다. */
    margin-top: 0.875rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--theme-border-color);
    font-size: 0.8125rem;
}

.flash-result-details em {
    margin-right: 0.1875rem;
    color: var(--theme-secondary-color);
    font-style: normal;
}

/* 다음에 할 일. 확인 버튼과 한 줄에 두지 않는다 — 닫으려다 엉뚱한 화면으로 넘어간다. */
.flash-result-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 0.875rem;
}

.flash-result-action {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.3125rem 0.625rem;
    border: 1px solid var(--theme-border-color);
    border-radius: 999px;
    font-size: 0.8125rem;
    font-weight: 600;
    text-decoration: none;
}

.flash-result-action:hover {
    border-color: var(--theme-primary);
}

/*
 * 확인. 이 상자를 닫는 것이 전부라 한 칸을 통째로 준다.
 *
 * **브랜드 포인트 색이다.** 이 상자에서 누르는 자리는 여기 하나뿐이라, 화면의 다른
 * 기본 버튼(`.btn-primary`)과 같은 얼굴이어야 한다 — 한때 올리브 면에 흰 글자였는데,
 * 올리브는 **글자로 쓰는 브랜드 색**이고 면으로 쓰는 색이 아니다(8절). 그래서 이
 * 버튼만 화면의 다른 실행 버튼과 다른 색으로 서 있었다.
 *
 * 라임 면 위의 글자는 언제나 잉크다 — 흰 글자는 1.6:1 로 사라진다.
 */
.flash-result-ok {
    display: block;
    width: 100%;
    min-height: 42px;
    margin-top: 1rem;
    padding: 0.5rem 1rem;
    border: 0;
    border-radius: 0.5rem;
    background: var(--brand-accent);
    color: var(--brand-ink);
    font-weight: 600;
}

.flash-result-ok:hover {
    background: var(--brand-accent-press);
}

/* 포커스 테두리는 라임 위에서 보여야 하므로 올리브다. 옅은 라임은 면에 묻힌다. */
.flash-result-ok:focus-visible {
    outline: 3px solid var(--brand-emph);
    outline-offset: 2px;
}

/* --------------------------------------------------------------------------
 * 브랜드 로고
 *
 * 로고는 **두 벌**이다 — 밝은 바탕용(ink)과 어두운 바탕용(paper). 한 벌로 버티면
 * 둘 중 한쪽에서 반드시 묻힌다. 사이드바는 템플릿의 `.logo-light` / `.logo-dark` 가
 * 알아서 갈라 주고, 그 밖의 자리는 아래 `.brand-on-*` 규칙이 테마를 보고 고른다.
 *
 * 높이만 정하고 폭은 auto 로 둔다. 로고 파일의 가로세로 비가 바뀌어도 화면이 안 깨진다.
 * -------------------------------------------------------------------------- */
.brand-logo {
    height: 26px;
    width: auto;
    /*
     * **사파리에서 로고가 가로로 눌리던 것.** 이 이미지는 flex 상자 안에 있고,
     * 원래 폭(576px)이 flex 기준 크기가 된다. 자리가 모자라면 flex 가 그 폭을 줄이는데
     * 높이는 여기서 못 박혀 있어 가로만 찌그러진다. 줄이지 못하게 막는다.
     */
    flex-shrink: 0;
    object-fit: contain;
}

/* 사이드바를 접었을 때 남는 마크. 접힌 폭(70px) 안에 들어가야 한다. */
.brand-mark {
    height: 22px;
    width: auto;
    flex-shrink: 0;
    object-fit: contain;
}

/*
 * 테마로 갈리는 자리(로그인·랜딩). 사이드바처럼 템플릿이 갈라 주지 않으므로
 * 둘 다 넣어 두고 하나만 보여준다 — 스크립트 없이 테마 전환을 따라간다.
 */
.brand-on-dark {
    display: none;
}

[data-bs-theme="dark"] .brand-on-light {
    display: none;
}

[data-bs-theme="dark"] .brand-on-dark {
    display: inline-block;
}

/* 로그인·가입 화면의 로고. 카드 안에서 가장 먼저 읽히는 것이라 한 단계 크게. */
.auth-box .auth-brand-logo {
    height: 46px;
    width: auto;
    flex-shrink: 0;
    object-fit: contain;
}

/* 랜딩 상단 띠. 메뉴 높이에 맞춰 사이드바보다 조금 크게. */
.navbar .brand-logo {
    height: 30px;
}

/* --------------------------------------------------------------------------
 * 로그인 · 회사 등록 — 브랜드 색
 *
 * **로그인 전 화면은 로고와 같은 얼굴이어야 한다.** 소개 화면(랜딩)이 잉크와 라임으로
 * 말하는데 로그인만 파란 버튼이면, 같은 서비스로 이어지지 않는다.
 * 로그인 뒤의 업무 화면도 뒤이어 같은 색으로 옮겼다 — 아래 '관리자 화면의 브랜드 색'
 * 절이 그것이다. 템플릿 기본값인 파랑(#2563eb)은 이제 어디에도 쓰지 않는다.
 *
 * **라임은 면에만 쓴다.** 흰 바탕 위 글자로 쓰면 1.4:1 이라 읽히지 않고,
 * 라임 면 위의 잉크 글자는 15:1 이다. 현장 화면에서 쓰는 규칙과 같다.
 * -------------------------------------------------------------------------- */
.auth-box {
    --auth-ink: #17190f;
    --auth-accent: #b2e02c;
    --auth-accent-press: #9ecb1f;
}

/* 이 화면에서 색이 칠해지는 유일한 자리 — 로그인 · 회사 등록 · 비밀번호 변경 버튼. */
.auth-box .btn-accent {
    background: var(--auth-accent);
    border-color: var(--auth-accent);
    color: var(--auth-ink);
    font-weight: 700;
}

.auth-box .btn-accent:hover,
.auth-box .btn-accent:focus {
    background: var(--auth-accent-press);
    border-color: var(--auth-accent-press);
    color: var(--auth-ink);
}

.auth-box .btn-accent:focus-visible {
    outline: 3px solid rgba(23, 25, 15, 0.25);
    outline-offset: 2px;
}

/*
 * 칸을 누르면 잉크로 테두리를 잡는다.
 *
 * 라임으로 잡지 않는 이유 — 흰 칸 위 1px 라임 선은 있는지 없는지 모를 만큼 흐리다
 * (경계선도 3:1 은 나와야 보인다). 포커스는 '지금 여기'를 말하는 표시라 확실해야 한다.
 */
.auth-box .form-control:focus,
.auth-box .form-select:focus,
.auth-box .input-group:focus-within {
    border-color: var(--auth-ink);
    box-shadow: 0 0 0 3px rgba(23, 25, 15, 0.1);
}

/* 아래 링크(회사 등록하기 · 로그인)도 잉크로. 파란 링크 하나가 남으면 그것만 튄다. */
.auth-box a:not(.btn) {
    color: var(--auth-ink);
    text-underline-offset: 3px;
}

.auth-box a:not(.btn):hover {
    color: var(--auth-ink);
    text-decoration: underline;
}

/*
 * 가입 화면의 요금제 안내. 파란 정보 상자(alert-info)를 걷고 종이 톤으로 둔다 —
 * 그 상자는 "읽어야 할 경고"처럼 보이는데, 여기 담긴 것은 그냥 시작 조건이다.
 */
.auth-box .alert-info {
    background: #f2f2ea;
    border-color: #e4e4e0;
    color: var(--auth-ink);
}

.auth-box .alert-info i,
.auth-box .alert-info svg {
    color: #6b6f60;
}

/* 어두운 화면에서는 종이 톤이 눈을 찌른다. 면만 낮추고 글자는 밝게 둔다. */
[data-bs-theme="dark"] .auth-box {
    --auth-ink: #fafafa;
}

[data-bs-theme="dark"] .auth-box .btn-accent {
    color: #17190f;
}

[data-bs-theme="dark"] .auth-box .form-control:focus,
[data-bs-theme="dark"] .auth-box .form-select:focus,
[data-bs-theme="dark"] .auth-box .input-group:focus-within {
    box-shadow: 0 0 0 3px rgba(250, 250, 250, 0.15);
}

[data-bs-theme="dark"] .auth-box .alert-info {
    background: rgba(250, 250, 250, 0.06);
    border-color: rgba(250, 250, 250, 0.12);
    color: #fafafa;
}

/* --------------------------------------------------------------------------
 * 관리자 화면의 브랜드 색
 *
 * 파랑(#2563eb) 자리를 로고의 색으로 바꾼다. 소개·로그인 화면이 잉크와 라임으로
 * 말하는데 로그인 뒤부터 파랑이면 같은 서비스로 이어지지 않는다.
 *
 * **한 색을 두 자리에 쓰지 않는다.** 라임은 밝고(흰 바탕 대비 1.4:1) 글자로 쓸 수
 * 없어서, 브랜드 색을 통째로 primary 에 넣으면 링크와 배지 글씨가 사라진다.
 * 그래서 둘로 나눈다.
 *
 *   라임 #b2e02c  — **면에만.** 기본 버튼 · 활성 표시 · 진행 막대. 위에 얹는 글자는 잉크.
 *   올리브 #3f6212 — **글자로 쓰는 브랜드 색.** 링크 · text-primary · 테두리. 흰 바탕 7.4:1.
 *
 * 어두운 바탕(사이드바 · 다크 모드)에서는 반대다. 거기서는 라임이 글자로도 충분히
 * 읽히고(11:1) 올리브가 오히려 묻힌다 — 그래서 아래에서 값을 맞바꾼다.
 *
 * 템플릿이 읽는 --theme-* 변수를 갈아 끼우는 것이 먼저다. 컴포넌트마다 규칙을 새로
 * 쓰지 않아도 링크·배지·포커스·테두리가 한꺼번에 따라온다.
 * -------------------------------------------------------------------------- */
:root {
    --brand-ink: #17190f;
    --brand-accent: #b2e02c;
    --brand-accent-press: #9ecb1f;
    --brand-emph: #3f6212;          /* 흰 바탕에서 글자로 쓸 수 있는 브랜드 색 */
    --brand-emph-strong: #2f4a0e;   /* 그것의 hover */

    --theme-primary: var(--brand-emph);
    --theme-primary-rgb: 63, 98, 18;
    --theme-primary-bg-subtle: rgba(178, 224, 44, 0.2);
    --theme-primary-border-subtle: rgba(63, 98, 18, 0.3);
    --theme-primary-text-emphasis: var(--brand-emph-strong);

    --theme-link-color: var(--brand-emph);
    --theme-link-color-rgb: 63, 98, 18;
    --theme-link-hover-color: var(--brand-emph-strong);
    --theme-link-hover-color-rgb: 47, 74, 14;

    --theme-focus-ring-color: rgba(63, 98, 18, 0.25);
}

[data-bs-theme="dark"] {
    /* 어두운 바탕에서는 라임을 글자로 쓴다. 올리브는 여기서 거의 안 보인다. */
    --brand-emph: #bce93c;
    --brand-emph-strong: #cdf25e;

    --theme-primary: var(--brand-emph);
    --theme-primary-rgb: 188, 233, 60;
    --theme-primary-bg-subtle: rgba(178, 224, 44, 0.16);
    --theme-primary-border-subtle: rgba(188, 233, 60, 0.35);
    --theme-primary-text-emphasis: var(--brand-emph-strong);

    --theme-link-color: var(--brand-emph);
    --theme-link-color-rgb: 188, 233, 60;
    --theme-link-hover-color: var(--brand-emph-strong);
    --theme-link-hover-color-rgb: 205, 242, 94;

    --theme-focus-ring-color: rgba(188, 233, 60, 0.3);
}

/*
 * 기본 버튼. **화면에서 라임 면이 칠해지는 자리**다.
 * 글자는 언제나 잉크 — 라임 위 흰 글자는 1.6:1 로 읽히지 않는다.
 */
.btn-primary {
    --theme-btn-color: var(--brand-ink);
    --theme-btn-bg: var(--brand-accent);
    --theme-btn-border-color: var(--brand-accent);
    --theme-btn-hover-color: var(--brand-ink);
    --theme-btn-hover-bg: var(--brand-accent-press);
    --theme-btn-hover-border-color: var(--brand-accent-press);
    --theme-btn-active-color: var(--brand-ink);
    --theme-btn-active-bg: var(--brand-accent-press);
    --theme-btn-active-border-color: var(--brand-accent-press);
    --theme-btn-disabled-color: var(--brand-ink);
    --theme-btn-disabled-bg: var(--brand-accent);
    --theme-btn-disabled-border-color: var(--brand-accent);
    --theme-btn-focus-shadow-rgb: 63, 98, 18;
    font-weight: 600;
}

/* 테두리만 있는 버튼은 글자가 본체라 올리브로 두고, 누를 때만 라임 면이 된다. */
.btn-outline-primary {
    --theme-btn-color: var(--brand-emph);
    --theme-btn-border-color: var(--brand-emph);
    --theme-btn-hover-color: var(--brand-ink);
    --theme-btn-hover-bg: var(--brand-accent);
    --theme-btn-hover-border-color: var(--brand-accent);
    --theme-btn-active-color: var(--brand-ink);
    --theme-btn-active-bg: var(--brand-accent);
    --theme-btn-active-border-color: var(--brand-accent);
    --theme-btn-focus-shadow-rgb: 63, 98, 18;
}

/* 면으로 쓰는 유틸리티. 라임 위 글자는 잉크로 못 박는다. */
.bg-primary,
.text-bg-primary,
.badge.text-bg-primary {
    background-color: var(--brand-accent) !important;
    color: var(--brand-ink) !important;
}

/*
 * 옅은 면 + 글자 배지(bg-primary-subtle text-primary).
 * 라임을 20% 로 깔면 종이처럼 보이고, 그 위 올리브 글자가 6.5:1 로 읽힌다.
 */
.badge.text-primary,
.text-primary {
    color: var(--brand-emph) !important;
}

[data-bs-theme="dark"] .badge.text-primary,
[data-bs-theme="dark"] .text-primary {
    color: var(--brand-emph) !important;   /* 다크에서는 이 값이 라임이다 */
}

/*
 * 고른 상태 — 페이지네이션 · 체크박스 · 탭.
 *
 * 여기는 라임이 아니라 **잉크**다. 활성 표시는 화면에 여러 개가 동시에 떠 있고
 * (탭 하나 + 페이지 하나 + 체크 여러 개), 그것들이 전부 라임이면 정작 눌러야 할
 * 버튼이 묻힌다. 라임은 '누르면 일이 일어나는 자리' 하나로 남긴다.
 */
.page-item.active .page-link {
    background-color: var(--brand-ink);
    border-color: var(--brand-ink);
    color: #fff;
}

.form-check-input:checked {
    background-color: var(--brand-ink);
    border-color: var(--brand-ink);
}

.form-check-input:focus {
    border-color: var(--brand-ink);
    box-shadow: 0 0 0 3px rgba(23, 25, 15, 0.12);
}

[data-bs-theme="dark"] .page-item.active .page-link,
[data-bs-theme="dark"] .form-check-input:checked {
    background-color: var(--brand-accent);
    border-color: var(--brand-accent);
    color: var(--brand-ink);
}

/* 진행 막대는 면이라 라임을 그대로 쓴다. */
.progress-bar {
    background-color: var(--brand-accent);
}

/*
 * 사이드바의 활성 메뉴 — 색은 여기서 정하지 않는다.
 *
 * 한때 이 자리에 `.side-nav-item.active > a { color: var(--brand-accent) !important }`
 * 가 있었다. 주석은 "메뉴가 어두운 바탕이라 라임을 글자로 쓴다" 였는데 **범위가
 * 없어서** 밝은 사이드바(`data-menu-color="light"`, 지금 레이아웃이 쓰는 값)에도
 * 그대로 걸렸다. 밝은 바탕 위 라임 글자는 1.4:1 이라 사실상 안 보인다.
 *
 * 눈에 띈 자리는 **2차 메뉴뿐**이었다. 템플릿이 1차는 `!important` 로 잡고
 * (`.side-nav > .side-nav-item.active > a`, 0-3-1) 2차는 그냥 잡는데(0-4-1),
 * 이 규칙이 `!important` 라 2차만 이겼기 때문이다. 그래서 고른 2차는 흐린 라임
 * 글자가 되고 부모 1차만 라임 면을 써서, **그룹 제목이 선택된 것처럼** 보였다.
 *
 * 색은 표면마다 이미 `--theme-sidenav-item-active-color` 에 정해져 있다
 * (밝은 메뉴 = 잉크 / 어두운 메뉴 · 다크 모드 = 라임). 그것을 그대로 쓴다.
 */
.side-nav-item.active > a .menu-icon,
.side-nav-item.active > a i,
.side-nav-item.active > a svg {
    color: inherit;
}

/*
 * '지금 여기' 표시는 **실제로 고른 줄 하나**에만 둔다.
 *
 * 2차를 고르면 `<li>` 두 개가 함께 active 가 된다 — 고른 2차와, 그것을 담은 1차다.
 * 템플릿은 그 둘을 다르게 그렸다: 1차는 라임 면 + 색 글자, 2차는 색 없는 글자.
 * 그래서 하위 없는 1차(대시보드)를 고를 때와 2차(입고)를 고를 때가 서로 다른
 * 모양이 되고, 정작 고른 줄보다 그 위의 그룹 제목이 더 강조돼 보였다.
 *
 *   1. 고른 2차에 1차와 **똑같은** 라임 면을 준다.
 *   2. 하위가 고른 상태인 1차에서는 면을 걷는다 — 그 줄은 '펼쳐진 그룹'일 뿐이다.
 *      글자는 굵게 남아(아래 data-menu-color="light" 규칙) 어느 그룹인지는 보인다.
 *
 * 결과적으로 라임 면은 화면에 언제나 하나뿐이고, 1차를 고르든 2차를 고르든
 * 같은 모양이 된다. 라임을 '고른 상태'에 남발하지 않는 8절 규칙과도 맞는다.
 */
.side-nav > .side-nav-item.active .side-nav-item.active > a {
    color: var(--theme-sidenav-item-active-color);
    background-color: var(--theme-sidenav-item-active-bg);
}

/* 템플릿이 1차 색을 `!important` 로 잡으므로 여기서도 같은 무게가 필요하다.
   선택자는 한 단계 더 구체적이라(0-4-1 대 0-3-1) 그 위에서 이긴다. */
.side-nav > .side-nav-item.active:has(.sub-menu .side-nav-item.active) > a {
    color: var(--theme-sidenav-item-hover-color) !important;
    background-color: transparent;
}

/* 입력칸 포커스. 브랜드 색이지만 글자 옆 1px 선이라 진한 쪽(올리브)을 쓴다. */
.form-control:focus,
.form-select:focus {
    border-color: var(--brand-emph);
}

/* --------------------------------------------------------------------------
 * 사이드바와 상단 띠
 *
 * 템플릿의 어두운 메뉴는 **남색**이다 — 바탕 #141d33, 글자 #9fb0c7, 활성 #1d4ed8,
 * hover #60a5fa. 파랑을 걷어낸 화면 옆에 그것만 남으니 사이드바만 다른 서비스처럼
 * 보였다. 로고와 같은 잉크로 맞춘다.
 *
 * **활성 메뉴는 라임 글자 + 옅은 라임 면**이다. 어두운 바탕에서는 라임이 글자로도
 * 11:1 로 읽히고, 지금 보고 있는 메뉴 하나만 색을 가지므로 어디에 있는지가 한눈에 온다.
 * (밝은 바탕에서 라임을 글자로 쓰지 않는 규칙과 어긋나지 않는다 — 바탕이 반대다.)
 * -------------------------------------------------------------------------- */
html[data-menu-color="dark"] {
    /*
     * 로고의 잉크(#17190f)를 한 단계 들어 올린 값이다.
     *
     * 아이콘처럼 작은 면에서는 잉크 그대로가 좋지만, 사이드바는 화면 왼쪽을 통째로
     * 덮는 넓은 면이라 같은 검정도 훨씬 무겁게 깔린다. 한 단계 올리면 옆의 흰 본문과
     * 부딪히는 느낌이 줄고 메뉴 글자도 덜 눌린다.
     */
    --theme-sidenav-bg: var(--sf-card);                 /* 상단 띠와 같은 층 */
    --theme-sidenav-border-color: var(--ln-soft);
    --theme-sidenav-item-color: var(--tx-muted);        /* 이 바탕 위 6.1:1 */
    --theme-sidenav-item-hover-color: var(--tx-strong); /* 손이 닿은 줄만 밝아진다 */
    --theme-sidenav-item-hover-bg: var(--sf-raised);    /* 한 단계 위로 */
    --theme-sidenav-item-active-color: #bce93c;
    --theme-sidenav-item-active-bg: rgba(178, 224, 44, 0.16);
}

/*
 * 접었을 때 뜨는 2차 메뉴 상자도 같은 잉크로. 여기만 남색으로 남으면
 * 사이드바에서 떨어져 나온 다른 조각처럼 보인다.
 */
html[data-menu-color="dark"] .side-nav .side-nav-item .side-nav-second-level,
html[data-menu-color="dark"] .side-nav .side-nav-item .side-nav-third-level {
    background-color: transparent;
}

/*
 * 상단 띠는 흰 바탕 그대로 두고 **파란 hover 만** 잉크로 바꾼다.
 * 띠까지 잉크로 칠하면 사이드바와 붙어 화면 위쪽이 통째로 검은 덩어리가 된다.
 */
html[data-topbar-color="light"] {
    --theme-topbar-bg: var(--sf-card);
    --theme-topbar-item-color: var(--tx);
    --theme-topbar-item-hover-color: var(--tx-strong);
    --theme-topbar-search-border: var(--ln);
}

/*
 * 다크의 상단 띠.
 *
 * 템플릿은 이 값을 `html[data-skin=…][data-bs-theme=dark][data-topbar-color=light]`
 * 라는 **세 겹 선택자**로 다시 정한다(#252630 — 푸른 회색). `data-skin` 은 화면에
 * 적혀 있지 않고 config.js 가 붙이므로, 그 무게를 맞추지 않으면 여기서 정한 값이
 * 조용히 밀린다 — 사이드바에서 이미 겪은 자리다.
 *
 * 띠는 카드와 같은 층이다. 사이드바(바닥)와 본문 카드 사이에서 가로로 이어지는 판이라,
 * 카드보다 어두우면 화면 위쪽만 가라앉아 보인다.
 */
html[data-skin][data-bs-theme="dark"][data-topbar-color="light"],
html[data-bs-theme="dark"][data-topbar-color="light"] {
    --theme-topbar-bg: var(--sf-card);
    --theme-topbar-item-color: var(--tx-muted);
    --theme-topbar-item-hover-color: var(--brand-emph);
    --theme-topbar-search-bg: var(--sf-sunken);
    --theme-topbar-search-border: var(--ln);
}

/*
 * 다크 모드의 사이드바.
 *
 * 템플릿은 `html[data-skin=…][data-bs-theme=dark][data-menu-color=dark]` 라는 세 겹
 * 선택자로 값을 다시 정한다. 그쪽이 더 무거워서 위의 잉크 값이 다크에서만 밀려나
 * **라이트에서는 잉크, 다크에서는 회색**이 되던 것을 여기서 맞춘다.
 * 무게를 같게 잡고(속성 셋) 나중에 실리는 이 파일이 이긴다.
 */
html[data-skin][data-bs-theme="dark"][data-menu-color="dark"],
html[data-bs-theme="dark"][data-menu-color="dark"] {
    --theme-sidenav-bg: var(--sf-card);
    --theme-sidenav-border-color: var(--ln-soft);
    --theme-sidenav-item-color: var(--tx-muted);
    --theme-sidenav-item-hover-color: var(--tx-strong);
    --theme-sidenav-item-hover-bg: var(--sf-raised);
    --theme-sidenav-item-active-color: #bce93c;
    --theme-sidenav-item-active-bg: rgba(178, 224, 44, 0.16);
}

/* --------------------------------------------------------------------------
 * 밝은 사이드바
 *
 * 이 앱은 흰 카드 위주의 밝은 화면이다. 사이드바만 검으면 화면이 세로로 두 동강 나
 * 보이고, 밝게 가면 한 장으로 이어져 표가 더 넓어 보인다.
 *
 * 두 가지를 함께 지켜야 밝은 메뉴가 성립한다.
 *
 *  1. **틀과 바닥을 가른다.** 사이드바는 상단 띠와 함께 화면의 **틀**이라 카드와 같은
 *     층(`--sf-card`)에 두고, 본문이 놓이는 바닥만 한 단계 눌러(`--sf-page`) 가른다.
 *     한때 사이드바를 바닥과 **같은 값**으로 두었는데, 그러면 세로선 하나가 유일한
 *     경계가 되는 데다 **상단 띠가 사이드바 폭에서 한 번 끊겨** 보였다 — 로고 자리만
 *     바닥 색이고 그 오른쪽부터 띠 색이라, 가로로 이어져야 할 띠가 왼쪽만 파였다.
 *     지금은 사이드바와 띠가 ㄱ자로 이어지고 본문만 바닥 위에 놓인다.
 *  2. **활성 표시는 잉크 글자 + 옅은 라임 면.** 밝은 바탕에서 라임 글자는 1.4:1 이라
 *     보이지 않는다. 면으로 깔고 글자는 잉크로 쓴다(15:1).
 *
 * 템플릿 기본값은 hover 도 활성도 파랑(#2563eb)이라 그대로 두면 사이드바만 파랗게 남는다.
 * -------------------------------------------------------------------------- */
html[data-menu-color="light"] {
    --theme-sidenav-bg: var(--sf-card);                 /* 상단 띠와 같은 층 — 둘이 ㄱ자로 이어진다 */
    --theme-sidenav-border-color: var(--ln);
    --theme-sidenav-item-color: var(--st-neutral);       /* 이 바탕 위 7.1:1 — 메뉴는 늘 읽는 글자라 보조 글자보다 진하다 */
    --theme-sidenav-item-hover-color: var(--tx-strong);
    --theme-sidenav-item-hover-bg: var(--sf-hover);
    --theme-sidenav-item-active-color: #17190f;
    /*
     * 라임을 35% 로 깔았더니 메뉴 한 줄이 형광펜처럼 떠서, 옆의 실행 버튼(라임 100%)과
     * 세기가 비슷해졌다. 선택은 "지금 여기"를 알리는 표시지 실행이 아니다 —
     * 알아볼 만큼만 남기고 세기를 절반으로 낮춘다.
     */
    --theme-sidenav-item-active-bg: rgba(178, 224, 44, 0.18);
}

/* 활성 메뉴는 글자도 굵게 둔다. 옅은 면 하나만으로는 스쳐 지나간다. */
html[data-menu-color="light"] .side-nav-item.active > a {
    font-weight: 700;
}

/* 다크 모드에서는 밝은 메뉴가 성립하지 않는다. 템플릿이 어두운 값으로 돌리는 자리에
   브랜드 잉크를 넣어 준다(무게를 같게 잡고 나중에 실리는 이 파일이 이긴다). */
html[data-skin][data-bs-theme="dark"][data-menu-color="light"],
html[data-bs-theme="dark"][data-menu-color="light"] {
    --theme-sidenav-bg: var(--sf-card);
    --theme-sidenav-border-color: var(--ln-soft);
    --theme-sidenav-item-color: var(--tx-muted);
    --theme-sidenav-item-hover-color: var(--tx-strong);
    --theme-sidenav-item-hover-bg: var(--sf-raised);
    --theme-sidenav-item-active-color: #bce93c;
    --theme-sidenav-item-active-bg: rgba(178, 224, 44, 0.16);
}
/* --------------------------------------------------------------------------
 * 확인 창 (브라우저 confirm 대신)
 *
 * 브라우저의 confirm() 은 우리 화면과 아무 관계 없는 모양으로 뜨고, 제목에 도메인이
 * 박히고, **무엇을 지우는지 강조할 방법이 없다** — 줄바꿈도 굵은 글자도 색도 못 쓴다.
 * 되돌릴 수 없는 삭제와 그냥 확인하는 일이 똑같이 생긴 상자로 뜨는 것이 가장 나쁘다.
 *
 * 부트스트랩 모달을 쓰지 않는다. 이 창은 **다른 모달 위에서도 떠야 하고**(고객사 상세의
 * 사용자 발급 모달 안에서 초기화를 누르는 경로가 있다) 열고 닫기 말고는 할 일이 없다.
 *
 * 마크업은 layouts/partials/confirm 하나뿐이고, 화면들은 data-confirm 속성만 붙인다.
 * -------------------------------------------------------------------------- */
.app-confirm {
    position: fixed;
    inset: 0;
    z-index: 1085;              /* 부트스트랩 모달(1055)보다 위 */
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
}

.app-confirm-backdrop {
    position: absolute;
    inset: 0;
    background: var(--scrim);
    opacity: 0;
    transition: opacity 0.16s ease;
}

.app-confirm.is-open .app-confirm-backdrop {
    opacity: 1;
}

.app-confirm-panel {
    position: relative;
    width: 100%;
    max-width: 25rem;
    padding: 1.25rem;
    border-radius: 0.875rem;
    background: var(--theme-secondary-bg);
    box-shadow: 0 24px 48px -12px rgba(15, 23, 42, 0.35);
    opacity: 0;
    transform: translateY(8px) scale(0.98);
    transition: opacity 0.16s ease, transform 0.16s ease;
}

.app-confirm.is-open .app-confirm-panel {
    opacity: 1;
    transform: none;
}

@media (prefers-reduced-motion: reduce) {
    .app-confirm-panel,
    .app-confirm-backdrop {
        transition: none;
    }
}

body.app-confirm-open {
    overflow: hidden;
}

.app-confirm-head {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
}

/* 묻는 일과 되돌릴 수 없는 일을 아이콘 색으로 먼저 가른다. */
.app-confirm-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 38px;
    height: 38px;
    border-radius: 999px;
    background: var(--sf-raised);
    color: var(--theme-secondary-color);
}

.app-confirm-icon.is-danger {
    background: var(--st-danger-row);
    color: var(--st-danger);
}

.app-confirm-title {
    font-size: 1.0625rem;
    font-weight: 700;
    line-height: 1.35;
}

.app-confirm-text {
    margin-top: 0.375rem;
    color: var(--theme-secondary-color);
    font-size: 0.9375rem;
    line-height: 1.55;
}

/*
 * 버튼 둘. **확인이 오른쪽**이다 — 되돌아가기가 손에 먼저 닿아야 잘못 눌렀을 때
 * 빠져나오기 쉽다. 두 버튼의 폭을 같게 두지 않는다(같으면 눈이 한 번 더 읽는다).
 */
.app-confirm-foot {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    margin-top: 1.25rem;
}

.app-confirm-foot .btn {
    min-width: 5.5rem;
    font-weight: 600;
}

/* --------------------------------------------------------------------------
 * 공지·환영 창
 *
 * 확인 창(app-confirm)의 뼈대를 그대로 쓰고 폭과 본문만 바꾼다. 두 벌로 두면
 * 어느 날 한쪽만 고쳐진다.
 *
 * 확인 창은 한 문장을 묻는 자리라 좁아도 되지만, 이쪽은 **읽는 자리**다 —
 * 점검 안내 한 문단이 다섯 줄로 접히면 아무도 끝까지 읽지 않는다.
 * -------------------------------------------------------------------------- */
.app-notice .app-confirm-panel {
    max-width: 32rem;

    /*
     * **창이 화면 높이만큼 자란다.**
     *
     * 한때 본문에 고정 상한(26rem)을 두었더니 환영 인사처럼 긴 글은 화면이
     * 아무리 커도 스크롤이 생겼다. 그렇다고 상한만 키우면 노트북과 폰에서
     * 창이 화면 밖으로 나가 **닫기 버튼을 못 누른다** — 그때는 빠져나갈 길이 없다.
     *
     * 그래서 창의 상한을 화면에 묶고(위아래 여백 1rem 씩), 남는 자리는 본문이
     * 채우게 한다. 높은 화면에서는 스크롤이 아예 없고, 낮은 화면에서만
     * 본문이 스크롤한다. 머리와 단추 줄은 어느 쪽이든 늘 보인다.
     */
    max-height: calc(100vh - 2rem);
    display: flex;
}

/* 폼이 창의 세로 흐름을 그대로 물려받아야 본문만 늘어난다. */
.app-notice .app-confirm-panel > form {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-height: 0;
}

/*
 * 머리에는 아이콘과 제목뿐이다.
 *
 * 확인 창은 제목 **아래에 본문 한 줄**이 붙어 있어 위로 맞추는(flex-start) 것이
 * 맞지만, 이쪽은 본문이 아래 상자(.app-notice-body)로 빠져 제목만 남는다.
 * 그대로 두면 38px 짜리 아이콘 옆에서 한 줄 제목이 위로 붙어 어긋나 보인다.
 */
.app-notice .app-confirm-head {
    align-items: center;
}

/*
 * 남는 자리를 본문이 채우고, 모자랄 때만 여기서 스크롤한다.
 * min-height: 0 이 없으면 flex 항목이 내용 크기 아래로 줄지 않아 스크롤이 안 생긴다.
 */
.app-notice-body {
    flex: 1;
    min-height: 0;
    margin-top: 1rem;
    overflow-y: auto;
}

/* 공지가 여럿이면 선으로 가른다. 어디까지가 한 건인지 보이지 않으면 한 덩어리로 읽힌다. */
.app-notice-item + .app-notice-item {
    margin-top: 0.875rem;
    padding-top: 0.875rem;
    border-top: 1px solid var(--ln-soft);
}

/* --------------------------------------------------------------------------
 * 화면 이용 안내 (layouts/partials/guide)
 *
 * 화면을 막으로 덮고 **짚는 자리 하나만 뚫어** 보인다. 뚫린 자리는 따로 그리지 않고
 * 상자 하나의 아주 큰 그림자(`200vmax`)가 막이 된다 — 막을 네 조각으로 잘라 붙이면
 * 스크롤할 때 조각 사이가 한 프레임씩 벌어진다.
 *
 * 확인 창(1085)보다 한 단계 아래다. 안내 중에 확인 창이 뜰 일은 없지만, 뜬다면
 * 묻는 쪽이 위에 있어야 한다.
 * -------------------------------------------------------------------------- */
.guide-tour {
    position: fixed;
    inset: 0;
    z-index: 1080;
}

/* 누르는 것을 막는 투명한 판. 막(그림자)은 누르는 것을 막지 못한다. */
.guide-blocker {
    position: absolute;
    inset: 0;
}

.guide-spot {
    position: fixed;
    border-radius: 0.625rem;
    /* 라임 테두리는 막 위에 놓인다. 흰 바탕에서는 글자로 못 쓰는 색이지만(8절),
       여기서는 어두운 막과 맞닿아 또렷하다. */
    box-shadow: 0 0 0 3px var(--brand-accent), 0 0 0 200vmax var(--scrim);
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.16s ease, left 0.2s ease, top 0.2s ease, width 0.2s ease, height 0.2s ease;
}

.guide-tour.is-open .guide-spot {
    opacity: 1;
}

/* 짚을 자리가 없는 단계는 화면 가운데에 뜬다. 막만 남긴다. */
.guide-tour.is-center .guide-spot {
    left: 50% !important;
    top: 50% !important;
    width: 0 !important;
    height: 0 !important;
    box-shadow: 0 0 0 200vmax var(--scrim);
}

.guide-pop {
    position: fixed;
    width: min(22rem, calc(100vw - 1rem));
    padding: 1rem 1.125rem;
    border-radius: 0.875rem;
    background: var(--sf-float);
    border: 1px solid var(--ln);
    box-shadow: 0 24px 48px -12px rgba(15, 23, 42, 0.35);
    opacity: 0;
    transition: opacity 0.16s ease;
}

.guide-tour.is-open .guide-pop {
    opacity: 1;
}

/* 스크롤이 멈출 때까지 감춰 둔다. 따라 움직이면 위아래 자리를 바꾸며 흔들린다. */
.guide-tour.is-open .guide-pop.is-moving {
    opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
    .guide-spot,
    .guide-pop {
        transition: none;
    }
}

.guide-pop-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.guide-count {
    color: var(--tx-muted);
    font-size: 0.8125rem;
    font-variant-numeric: tabular-nums;
}

.guide-pop-head .btn-close {
    font-size: 0.75rem;
}

.guide-progress {
    height: 3px;
    margin: 0.5rem 0 0.875rem;
    border-radius: 999px;
    background: var(--sf-raised);
    overflow: hidden;
}

/* 진행 막대는 라임 면을 쓰는 자리다(8절 색상 팔레트). */
.guide-progress > span {
    display: block;
    height: 100%;
    background: var(--brand-accent);
    transition: width 0.2s ease;
}

.guide-title {
    font-size: 1.0625rem;
    font-weight: 700;
    line-height: 1.35;
}

.guide-body {
    margin: 0.375rem 0 0;
    color: var(--tx);
    font-size: 0.9375rem;
    line-height: 1.6;
    word-break: keep-all;
}

.guide-hint {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    margin: 0.75rem 0 0;
    color: var(--tx-muted);
    font-size: 0.8125rem;
}

/* '다음'이 오른쪽 끝에 있어야 손이 같은 자리를 계속 누른다. */
.guide-foot {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    margin-top: 1rem;
}

.guide-foot .btn {
    min-width: 4.5rem;
    font-weight: 600;
}

/*
 * 짚을 자리를 화면 안으로 끌어올 때 고정된 상단 띠 밑으로 숨지 않게 한다.
 * 이 여백은 scrollIntoView 에만 쓰이고 화면 모양은 바꾸지 않는다.
 */
[data-guide] {
    scroll-margin-top: 5.5rem;
    scroll-margin-bottom: 1rem;
}

@media print {
    .guide-tour {
        display: none !important;
    }
}

/* --------------------------------------------------------------------------
 * 막대 그래프와 표
 *
 * 차트 라이브러리를 쓰지 않는다(CLAUDE.md). 대시보드의 일별 막대는 flex 로 그리고,
 * 색만 여기서 정한다.
 *
 * **막대에는 테두리를 준다.** 라임은 흰 카드 위에서 1.4:1 이라 면만으로는 어디까지가
 * 막대인지 흐리다 — 그림도 3:1 은 나와야 형태가 읽힌다. 면은 브랜드 색으로 두고
 * 한 단계 진한 선으로 윤곽을 잡으면 둘 다 지킨다.
 *
 * 색은 **둘뿐이다.** 들어오는 것은 브랜드 색, 나가는 것은 잉크. 범례를 한 번 읽으면
 * 다시 볼 일이 없다.
 * -------------------------------------------------------------------------- */
.chart-bar {
    border-radius: 0.25rem 0.25rem 0 0;
}

.chart-bar.is-in {
    background: #b2e02c;
    border: 1px solid #7ea31c;      /* 흰 바탕 위 3.4:1 — 막대의 윤곽 */
    border-bottom: 0;
}

.chart-bar.is-out {
    background: var(--tx-muted);    /* 잉크 계열 회색. 두 모드가 같은 이름을 쓴다 */
}

[data-bs-theme="dark"] .chart-bar.is-in {
    border-color: #cdf25e;
}

/* 범례의 색 조각. 막대와 같은 색·같은 테두리여야 눈이 둘을 잇는다. */
.chart-key {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 2px;
    vertical-align: middle;
}

.chart-key.is-in {
    background: #b2e02c;
    border: 1px solid #7ea31c;
}

.chart-key.is-out {
    background: var(--tx-muted);
}

/*
 * 표 머리.
 *
 * 템플릿 기본은 푸른기가 도는 회색(#f1f5f9 계열)이다. 화면에서 파랑을 걷어내고 나면
 * 표 머리에만 그 기운이 남아 카드 안이 미묘하게 어긋난다. 같은 밝기의 **종이 톤**으로
 * 옮긴다 — 눈에 띄는 변화가 아니라 옆의 카드·사이드바와 온도를 맞추는 일이다.
 */
.table > thead.table-light th,
.table > thead.table-light td {
    --theme-table-bg: var(--sf-raised);
    --theme-table-color: var(--tx-muted);
    border-bottom-color: var(--ln);
}

/*
 * 카드에 표가 바로 담길 때의 모서리와 아래 선.
 *
 * 카드는 모서리가 둥근데(`--theme-card-border-radius`) 그 안의 `.table-responsive` 는
 * 사각이라, 카드 머리(`card-header`) 없이 표가 바로 오는 화면에서는 **표 머리의 회색
 * 면이 둥근 모서리 밖으로 각지게 삐져나온다.** 요금제 비교표가 그런 자리다.
 *
 * `.table-responsive` 는 `overflow-x: auto` 라 세로축도 `auto` 로 계산되어 이미
 * 잘라내는 상자다. 그래서 모서리 값만 주면 그대로 깎인다 — 가로 스크롤은 그대로 둔다.
 *
 * 아래 선도 같은 자리에서 나온다. 표의 마지막 줄은 부트스트랩 기본값으로 아래
 * 테두리를 갖는데, 표가 카드의 끝이면 그 선이 **카드 테두리 바로 안쪽에 한 줄 더**
 * 그어져 이중선으로 보인다. 카드 바닥(`card-footer`)이 이어질 때도 그쪽의 위
 * 테두리와 겹쳐 같은 일이 난다. 경계는 카드가 긋는다.
 */
.card > .table-responsive:first-child {
    border-top-left-radius: var(--theme-card-border-radius);
    border-top-right-radius: var(--theme-card-border-radius);
}

.card > .table-responsive:last-child {
    border-bottom-left-radius: var(--theme-card-border-radius);
    border-bottom-right-radius: var(--theme-card-border-radius);
}

.card > .table-responsive > .table > tbody > tr:last-child > th,
.card > .table-responsive > .table > tbody > tr:last-child > td {
    border-bottom-width: 0;
}

/*
 * 표의 줄 hover · 줄무늬.
 *
 * 템플릿 기본은 푸른 회색 계열(`--theme-light` 의 45%)이라 파랑을 걷어낸 화면에서
 * 표에만 그 기운이 남는다. 손이 얹힌 줄은 **아주 옅은 라임**으로 바꾼다 —
 * 지금 어느 줄을 보고 있는지 가리키는 순간이라, 브랜드 색이 스치는 자리로 맞다.
 *
 * 12% 다. 더 진하게 하면 표 전체가 노랗게 물든 것처럼 보이고, 셀 안의 상태 배지
 * (초록·빨강)와도 부딪힌다. 줄무늬는 색을 빼고 종이 톤으로만 둔다 —
 * 줄무늬는 늘 깔려 있는 것이라 색이 있으면 hover 가 묻힌다.
 *
 * 미달 행(.table-danger) 처럼 뜻이 있는 줄은 자기 색을 그대로 쓴다(위쪽 3절).
 */
.table {
    --theme-table-hover-bg: rgba(178, 224, 44, 0.12);
    --theme-table-hover-color: var(--theme-body-color);
    --theme-table-striped-bg: var(--sf-raised);
}

[data-bs-theme="dark"] .table {
    --theme-table-hover-bg: rgba(188, 233, 60, 0.1);
    --theme-table-striped-bg: var(--sf-raised);
}

/* 목록 카드 안의 줄(list-group)도 표와 같은 손맛이어야 한다. */
.list-group-item-action:hover,
.list-group-item-action:focus {
    background-color: rgba(178, 224, 44, 0.1);
}

[data-bs-theme="dark"] .list-group-item-action:hover,
[data-bs-theme="dark"] .list-group-item-action:focus {
    background-color: rgba(188, 233, 60, 0.08);
}

/* --------------------------------------------------------------------------
 * 로그인 — 다른 기기에서 쓰고 있을 때의 확인 상자
 *
 * 로그인 화면에는 관리자 레이아웃의 확인 창(app-confirm)이 실리지 않는다.
 * 여기서 필요한 것도 다르다 — 이것은 되묻는 창이 아니라 **길이 둘로 갈리는 자리**라
 * (돌아가기 · 밀어내고 들어가기) 버튼 둘이 같은 무게로 서야 한다.
 * -------------------------------------------------------------------------- */
.login-conflict {
    position: fixed;
    inset: 0;
    z-index: 1090;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
}

.login-conflict-backdrop {
    position: absolute;
    inset: 0;
    background: var(--scrim);
}

.login-conflict-panel {
    position: relative;
    width: 100%;
    max-width: 26rem;
    padding: 1.5rem;
    border-radius: 0.875rem;
    background: var(--theme-secondary-bg);
    box-shadow: 0 24px 48px -12px rgba(15, 23, 42, 0.4);
}

/* 경고가 아니라 알림이다 — 잘못한 일이 아니라 흔한 상황이라 빨강을 쓰지 않는다. */
.login-conflict-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 42px;
    height: 42px;
    border-radius: 999px;
    background: rgba(178, 224, 44, 0.25);
    color: #17190f;
}

[data-bs-theme="dark"] .login-conflict-icon {
    color: #fafafa;
}

/* --------------------------------------------------------------------------
 * 카메라 바코드 스캔
 *
 * 화면을 통째로 덮는다. 창고에서 한 손으로 폰을 들고 물건에 대는 일이라, 카메라 그림이
 * 클수록 겨누기 쉽다 — 작은 상자 안에서 맞추게 하면 손이 흔들린다.
 *
 * 라이브러리를 쓰지 않고 브라우저의 BarcodeDetector 를 쓴다(자세한 사정은
 * layouts/partials/barcode-scan 의 주석에).
 * -------------------------------------------------------------------------- */
.scan-overlay {
    position: fixed;
    inset: 0;
    z-index: 1090;
    display: flex;
    flex-direction: column;
    background: #000;
    opacity: 0;
    transition: opacity 0.15s ease;
}

.scan-overlay.is-open {
    opacity: 1;
}

.scan-head {
    position: relative;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 1rem;
    padding-top: calc(1rem + env(safe-area-inset-top));
    color: #fff;
}

.scan-title {
    font-weight: 600;
}

/* 어두운 바탕 위의 닫기 버튼. 상단 띠 밖이라 색을 여기서 준다. */
.scan-head .m-close {
    background: rgba(255, 255, 255, 0.15);
    color: #fff;
}

.scan-video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    /* 채워서 덮는다. contain 으로 두면 위아래에 검은 띠가 생겨 그림이 작아진다. */
    object-fit: cover;
}

/*
 * 가운데 창.
 *
 * 실제로 그 안만 읽는 것은 아니지만(디코더는 화면 전체를 본다), 어디에 대야 하는지
 * 손이 따라갈 자리가 있어야 한다. 없으면 사람이 폰을 이리저리 흔든다.
 */
.scan-frame {
    position: relative;
    z-index: 1;
    width: min(18rem, 78vw);
    aspect-ratio: 3 / 2;
    margin: auto;
    border: 2px solid rgba(255, 255, 255, 0.9);
    border-radius: 0.75rem;
    box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.45);
}

.scan-hint {
    position: relative;
    z-index: 2;
    margin: 0;
    padding: 1.5rem 1rem;
    padding-bottom: calc(1.5rem + env(safe-area-inset-bottom));
    color: rgba(255, 255, 255, 0.85);
    font-size: 0.9375rem;
    text-align: center;
}

/*
 * 카메라를 못 여는 기기에서 이유만 보여줄 때.
 *
 * 카메라 그림도 겨냥할 창도 없으니 검은 화면에 글 한 덩이만 남는다 —
 * 그 글이 위에 붙어 있으면 오류처럼 보인다. 가운데에 세워 안내로 읽히게 한다.
 */
.scan-overlay.is-plain .scan-hint {
    margin: auto;
    max-width: 22rem;
    font-size: 1rem;
    line-height: 1.65;
}

/*
 * 단위 선택이 아직 안 뜬 수량 칸.
 *
 * `.input-group` 은 **마지막 자식이 아닌** 요소의 오른쪽 모서리를 깎는다. `:last-child` 는
 * 구조를 보는 선택자라 뒤에 있는 단위 칸이 `hidden` 이어도 그대로 깎이고, 품목을 고르기
 * 전에는 수량 칸만 오른쪽이 각진 네모로 남았다.
 *
 * 감춰져 있는 동안만 값을 되돌린다(layouts/partials/pack-unit 이 토글한다).
 * 현장 화면은 모서리가 14px 이라 mobile.css 가 다시 맞춘다.
 *
 * **`!important` 가 필요하다.** 부트스트랩의 그 규칙은 `:not()` 을 넷 물고 있어
 * 우선순위가 0,6,0 이다(`:not()` 안의 것까지 센다). 클래스를 몇 개 겹쳐 이겨 봐야
 * 다음 사람이 읽고 왜 그렇게 썼는지 알 수 없다.
 */
.input-group.pack-idle > .form-control {
    border-top-right-radius: var(--theme-border-radius) !important;
    border-bottom-right-radius: var(--theme-border-radius) !important;
}

/*
 * 포장이 없는 품목의 단위 표시.
 *
 * 이 칸은 단위 select **앞**에 있어서, 눈에 보이는 마지막 칸이어도 `:last-child` 가
 * 아니다. 그래서 오른쪽 모서리를 여기서 돌려준다(`pack-idle` 과 같은 사정).
 */
.input-group.pack-base > [data-unit-label] {
    border-top-right-radius: var(--theme-border-radius) !important;
    border-bottom-right-radius: var(--theme-border-radius) !important;
}

/* --------------------------------------------------------------------------
 * 다중 입출고 — 명세 줄 입력과 목록 펼치기
 *
 * 한 거래처에서 여러 품목이 한 번에 온다. 거래명세서 한 장이 화면에서도
 * 한 덩어리로 보여야 하고, 등록할 때도 같은 공급처와 날짜를 다시 고르지 않아야 한다.
 * -------------------------------------------------------------------------- */

/* 등록 화면의 명세 한 줄. 번호 · 칸들 · 삭제 버튼. */
.entry-line-row {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    padding: 0.5rem 0;
    border-bottom: 1px dashed var(--theme-border-color);
}

.entry-line-row:last-child {
    border-bottom: 0;
}

/* 줄 번호. 오류 메시지의 번호와 눈으로 맞추는 자리라 항상 보인다. */
.entry-line-no {
    flex: 0 0 1.5rem;
    padding-top: 0.4rem;
    font-size: 0.8125rem;
    color: var(--theme-secondary-color);
    text-align: right;
}

.entry-line-fields {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 0.5rem;
    flex: 1 1 auto;
    min-width: 0;
}

/* 품목 칸이 남는 폭을 다 쓴다. 이름이 길어 잘리면 무엇을 고른 건지 안 보인다. */
.entry-line-item {
    flex: 1 1 14rem;
    min-width: 0;
}

.entry-line-qty {
    flex: 0 0 11rem;
}

.entry-line-price {
    flex: 0 0 8rem;
}

/*
 * 지우기(x) 버튼은 **입력칸 한 칸의 높이다.**
 *
 * 이 버튼은 아이콘 하나라 `p-1` 로 여백을 줄여 두었는데, 그 값이 템플릿의 버튼
 * padding 을 덮어써서 옆의 입력칸(`form-control-sm` 의 `min-height`)보다 낮았다.
 * 한 줄에 네 상자가 나란히 서는 자리라 그 차이가 그대로 보인다.
 *
 * 한때 `align-self: stretch` 로 줄 높이를 따라가게 했는데, **품목을 고르면 품목 칸
 * 아래에 이름과 현재고가 한 줄 붙는다**(`data-item-hint`). 그 줄까지 줄 높이에
 * 들어가 버튼이 세로로 두 배가 됐다 — 상자 넷 중 하나만 길쭉하게 튄다.
 *
 * 그래서 입력칸과 **같은 식**으로 높이를 준다(`form-control-sm` 의 min-height).
 * 버튼(`btn-sm`)과 입력칸의 글자 크기가 같아서(0.788rem) `em` 이 같은 값이 되고,
 * 템플릿이 입력칸 크기를 바꾸면 이 식도 함께 따라간다.
 */
.entry-line-remove {
    flex: 0 0 auto;
}

.entry-line-remove .btn {
    height: calc(1.5em + 0.625rem + calc(var(--theme-border-width) * 2));
}

/* 줄이 하나뿐이면 지울 수 없다. 빈 명세로 저장되는 길을 막는다. */
.entry-line-remove .btn:disabled {
    opacity: 0.25;
}

.entry-line-error {
    flex-basis: 100%;
    margin-left: 2rem;
    font-size: 0.8125rem;
    color: var(--theme-danger);
}

/*
 * 창고별 재고 (inventory/_warehouse-stock-panel) — 이동·조정·폐기 등록의 오른쪽.
 *
 * 폼에서 고른 창고 줄을 **브랜드 색의 옅은 줄**로 칠한다. 상태(미달·만료)를 말하는
 * 줄 색과 겹치지 않고, '지금 이 줄을 다루고 있다'는 뜻만 갖는다.
 */
.warehouse-stock tr.is-picked > * {
    --theme-table-bg-state: var(--st-primary-row);
}

/* 보냄 · 받음 · 선택. 읽는 표시라 배지처럼 선으로만 선다(상태 배지 규칙). */
.warehouse-stock-role {
    display: inline-block;
    margin-left: 0.375rem;
    padding: 0 0.375rem;
    border: 1px solid var(--ln);
    border-radius: 999px;
    color: var(--tx-strong);
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1.5;
}

.warehouse-stock tfoot th {
    border-bottom: 0;
}

/* 고른 첨부파일 이름. 브라우저 기본 표시는 "3개 파일"이라 무엇인지 안 보인다. */
/* --------------------------------------------------------------------------
 * 첨부파일 — 끌어다 놓는 면
 *
 * 브라우저 기본 파일 칸은 고른 뒤에도 '파일 3개'라고만 적혀 **무엇을 골랐는지
 * 안 보이고**, 하나를 잘못 고르면 처음부터 다시 고르는 수밖에 없다. 창고에서 찍은
 * 명세서 사진을 올리는 자리라 그 둘이 그대로 불편으로 남는다.
 *
 * `label` 이 면 전체를 덮으므로 **스크립트가 없어도 눌러서 고르는 길은 열려 있다.**
 * -------------------------------------------------------------------------- */
.file-drop {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.25rem;
    width: 100%;
    padding: 1.25rem 1rem;
    border: 1px dashed var(--ln);
    border-radius: 0.5rem;
    background: var(--sf-sunken);
    text-align: center;
    cursor: pointer;
    transition: border-color 0.12s, background-color 0.12s;
}

.file-drop:hover {
    border-color: var(--theme-primary);
    background: var(--sf-hover);
}

/* 끌어온 것이 이 면 위에 있다. 놓아도 되는 자리라는 것을 색이 말한다. */
.file-drop.is-over {
    border-color: var(--theme-primary);
    border-style: solid;
    background: var(--sf-hover);
}

.file-drop.is-invalid {
    border-color: var(--st-danger);
}

/*
 * 칸 자체는 감추되 **없애지 않는다.** display:none 으로 지우면 화면 낭독기가 읽지
 * 못하고, 검증 오류가 붙을 자리도 사라진다.
 */
.file-drop-input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

.file-drop-icon {
    width: 22px;
    height: 22px;
    color: var(--tx-muted);
}

.file-drop-title {
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--tx);
}

.file-drop-hint {
    font-size: 0.75rem;
    color: var(--tx-muted);
}

/* 고른 파일 — 한 줄에 미리보기 · 이름 · 크기 · 빼기 */
.file-picked {
    list-style: none;
    margin: 0.5rem 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
}

.file-picked-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.375rem 0.5rem;
    border: 1px solid var(--ln-soft);
    border-radius: 0.375rem;
    background: var(--sf-card);
}

/*
 * 그림은 미리보기가 뜬다 — 명세서 사진은 이름이 `IMG_4821.jpg` 라 이름을 적어 줘도
 * 무엇인지 모른다. 그림이 아닌 것은 같은 크기의 상자에 형식을 적어, 줄 높이가
 * 파일 종류에 따라 들쭉날쭉하지 않게 한다.
 */
.file-picked-thumb {
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    object-fit: cover;
    border-radius: 0.25rem;
    background: var(--sf-raised);
}

.file-picked-thumb.is-doc {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.625rem;
    font-weight: 700;
    color: var(--tx-muted);
}

.file-picked-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1 1 auto;
}

/* 이름이 길면 줄을 넘기지 않고 줄인다 — 한 줄이 두 줄이 되면 목록이 흔들린다. */
.file-picked-name {
    font-size: 0.8125rem;
    color: var(--tx);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.file-picked-size {
    font-size: 0.75rem;
    color: var(--tx-muted);
}

.file-picked-size.is-over {
    color: var(--st-danger);
}

.file-picked-remove {
    flex: 0 0 auto;
}

/*
 * 목록의 펼치기.
 *
 * 화살표만 돌린다. 열림·닫힘을 색이나 배경으로 말하면 목록 전체가 시끄러워지고,
 * '고른 상태'에 쓰는 표시와 헷갈린다(8절).
 */
.entry-toggle .entry-caret {
    transition: transform 0.15s ease-in-out;
}

.entry-toggle[aria-expanded="true"] .entry-caret {
    transform: rotate(90deg);
}

/* 명세를 감싼 줄은 표의 다른 줄처럼 hover 되면 안 된다 — 내용이 아니라 상자다. */
.entry-detail-row,
.entry-detail-row:hover {
    --theme-table-hover-bg: transparent;
    --theme-table-accent-bg: transparent;
}

/*
 * 펼친 명세.
 *
 * **표 머리보다 한 단계 깊다.**
 *
 * 한때 표 머리와 **똑같은 값**(#f5f4ee)을 썼다. "표에 딸린 영역이니 표가 쓰는 색을
 * 그대로" 라는 생각이었는데, 화면에서는 머리띠와 펼친 영역이 같은 회색 띠로 보여
 * 어디까지가 표이고 어디부터가 펼친 내용인지 눈으로 갈리지 않았다.
 *
 * 온도는 그대로 둔다 — 카드·사이드바와 맞춘 종이 톤이라 여기만 푸르게 하면 카드 안에
 * 다른 계열의 면이 하나 생긴다(위 '표 머리' 절). 갈라 놓는 것은 밝기뿐이다.
 *
 * **카드와 표 머리 사이에 둔다.** 한때 표 머리보다 더 진하게 내렸는데, 그러면 위계가
 * 뒤집힌다 — 표 머리는 칸 이름을 다는 **띠**이고 이 자리는 **내용**인데, 읽을 것이
 * 이름표보다 진한 면 위에 놓인다. 게다가 이 안에는 명세의 머리줄이 또 하나 있어서
 * 그것이 더 진한 바닥 위에 얹힌다.
 *
 * 그래서 카드(흰 면)에서 아주 조금만 옮긴다 — 흰색에 따뜻한 기운만 얹은 값이다.
 * 본문 줄과 갈라 주는 일은 위아래 테두리와 들여쓰기가 이미 하고 있으므로, 면은
 * "여기부터 딸린 내용" 정도만 거들면 된다.
 *
 * 한때 왼쪽에 라임 세로선을 그었는데 걷어냈다. 라임은 '누르면 일이 일어나는 자리'
 * 하나로 남긴다는 규칙이 있고(8절), 목록을 펼칠 때마다 그 선이 서면 정작 아래의
 * 실행 버튼과 세기가 겹친다. 경계는 위아래 테두리와 들여쓰기가 이미 말한다.
 */
.entry-detail {
    padding: 0.75rem 1rem;
    background: var(--sf-inset);             /* 카드(흰 면)에 따뜻한 기운만 얹는다 */
    border-top: 1px solid var(--ln-soft);
    border-bottom: 1px solid var(--ln-soft);
}

/* 다크에서도 규칙은 같다 — 카드에 딸린 면(`--sf-inset`). 방향만 반대다. */
[data-bs-theme="dark"] .entry-detail {
    background: var(--sf-inset);
    border-top-color: var(--ln-soft);
    border-bottom-color: var(--ln-soft);
}

/* 안쪽 표는 자기 바탕을 칠하지 않는다 — 칠하면 위 종이 톤 위에 흰 판이 하나 뜬다. */
.entry-lines {
    --theme-table-bg: transparent;
    --theme-table-accent-bg: transparent;

    /*
     * 손을 올리면 **그 품목 줄만** 밝아진다.
     *
     * 바깥 표는 건 단위로 hover 하는데, 펼친 명세 안에서까지 건 전체가 물들면
     * 품목이 여럿일 때 어느 줄을 겨누고 있는지 알 수 없다. 여기서 세는 단위는
     * 품목이므로 hover 도 품목 줄이다.
     *
     * **값을 여기서 다시 정해야 한다.** 바로 위 `.entry-detail-row` 가 상자를
     * 물들이지 않으려고 `--theme-table-hover-bg` 를 transparent 로 꺼 두는데,
     * 안쪽 표가 그 값을 그대로 물려받아 품목 줄의 hover 까지 함께 죽어 있었다.
     *
     * 색은 펼친 명세의 종이 톤(#fffef9)에서 한 단계 내린 값이다 — 카드 안에
     * 밝기가 다른 계열의 면을 새로 만들지 않는다. 바탕이 흰색에 가까워졌으므로
     * hover 도 그만큼 옅어야 두 면이 붙지 않는다.
     */
    --theme-table-hover-bg: var(--sf-hover);
    --theme-table-hover-color: var(--theme-table-color);
}

[data-bs-theme="dark"] .entry-lines {
    --theme-table-hover-bg: #303038;   /* --sf-inset 에서 한 단계 위 */
}

.entry-lines > thead th {
    font-size: 0.75rem;
    font-weight: 400;
    color: var(--theme-secondary-color);
    border-bottom-color: var(--theme-border-color);
}

.entry-lines > tbody > tr:last-child > * {
    border-bottom: 0;
}

/* 명세 아래 한 줄 — 메모 · 담당자 · 첨부 · 취소. */
.entry-detail-foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-top: 0.5rem;
}

.entry-detail-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    font-size: 0.8125rem;
    color: var(--theme-secondary-color);
}

/* 첨부파일. 이미지는 작은 판으로, 나머지는 이름표로. */
.entry-files {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

.entry-file-thumb {
    display: block;
    width: 3.5rem;
    height: 3.5rem;
    overflow: hidden;
    border: 1px solid var(--theme-border-color);
    border-radius: var(--theme-border-radius);
}

.entry-file-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.entry-file-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    max-width: 14rem;
    padding: 0.25rem 0.5rem;
    font-size: 0.8125rem;
    text-decoration: none;
    border: 1px solid var(--theme-border-color);
    border-radius: var(--theme-border-radius);
}

/* 파일 이름은 길다. 줄바꿈 대신 끝을 접어 한 줄로 둔다. */
.entry-file-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 확인 카드의 명세 목록(여러 줄을 한 번에 넣었을 때). */
.flash-result-lines {
    margin: 0.75rem 0 0;
    padding: 0;
    list-style: none;
    font-size: 0.8125rem;
}

.flash-result-lines li {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.375rem 0;
    border-bottom: 1px dashed var(--theme-border-color);
}

.flash-result-lines li:last-child {
    border-bottom: 0;
}

.flash-result-line-name em,
.flash-result-line-balance em {
    font-style: normal;
    color: var(--theme-secondary-color);
}

.flash-result-line-qty {
    font-weight: 600;
}

@media print {
    /* 종이에서는 명세가 늘 펼쳐져 있다. 들여쓰기와 회색 면은 잉크만 먹는다. */
    .entry-detail {
        padding: 0.25rem 0 0.5rem 1rem;
        background: transparent !important;
    }

    .entry-detail-row {
        break-inside: avoid;
    }
}

/* --------------------------------------------------------------------------
 * 작성중 / 완료 — 줄 전체로 펼치기, 저장 버튼, 거래명세서
 * -------------------------------------------------------------------------- */

/*
 * 줄 전체가 펼치기 버튼이다. 화살표만 눌러야 하면 과녁이 12px 밖에 안 된다.
 * 손이 얹히면 눌리는 자리임을 알린다 — 표의 기본 hover 만으로는 '읽는 줄'과 같다.
 */
.entry-row {
    cursor: pointer;
}

.entry-row:focus-visible {
    outline: 2px solid var(--brand-emph);
    outline-offset: -2px;
}

.entry-row .entry-caret {
    transition: transform 0.15s ease-in-out;
}

.entry-row[aria-expanded="true"] .entry-caret {
    transform: rotate(90deg);
}

/*
 * 줄을 고르는 칸.
 *
 * 체크박스만 서는 칸이라 좁게 둔다 — 기본 셀 여백이면 품목 이름이 그만큼 밀린다.
 * 고르는 것은 드문 일이므로 자리를 많이 내주지 않는다.
 */
.entry-line-pick {
    width: 2.25rem;
    text-align: center;
}

/* 고를 수 있는 표에서는 줄 어디를 눌러도 체크되게 커서로 알린다.
   (실제 토글은 라벨이 아니라 체크박스가 한다 — 품목 링크를 누르러 갔는데
   체크가 되면 더 나쁘다.) */
.entry-lines-pickable .entry-line-pick {
    cursor: pointer;
}

/* 명세 아래의 단추들. 메모·첨부와 같은 줄에 놓되 오른쪽으로 민다. */
.entry-detail-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin-left: auto;
}

/*
 * 명세 줄의 머리 — **따라온다.**
 *
 * 줄을 더할수록 폼이 아래로 길어져 '품목 추가' 버튼과 줄 수가 화면 밖으로 밀려났다.
 * 열 줄이 넘는 건에서는 한 줄 넣을 때마다 위로 스크롤해 버튼을 다시 찾아야 했다.
 *
 * **명세 영역만 스크롤시키는 길은 못 쓴다** — 품목 칸의 후보 목록이 `position: absolute`
 * 라 `overflow` 상자 안에서 잘리고, 마지막 줄에서는 거의 보이지 않는다. 머리 줄만
 * 붙여 두면 후보 목록은 그대로 열리고 버튼은 늘 손 닿는 자리에 있다.
 *
 * 면을 깔아 둔다 — 투명하면 아래 줄이 머리 글자 위로 흘러 지나간다.
 */
.entry-lines-head {
    position: sticky;
    top: 0;
    z-index: 3;
    padding: 0.35rem 0;
    background: var(--sf-card);
}

/*
 * 줄 수 칸을 **버튼 높이에 맞춘다.**
 *
 * 템플릿의 `.btn-sm` 은 높이를 정하지 않고 padding 과 `line-height: normal` 로
 * 결정된다(높이를 명시하는 것은 정사각형인 `.btn-icon.btn-sm` 뿐이다). 반면 입력칸은
 * `min-height: calc(1.5em + 0.625rem + 2px)` 를 들고 있어 몇 px 더 높다 — 옆에 붙은
 * 두 상자에서 그 차이는 바로 보인다.
 *
 * **버튼에는 손대지 않는다.** 여기서 버튼을 키우면 이 줄의 '품목 추가'만 화면 오른쪽
 * 위의 단추들과 높이가 달라진다. padding 과 글자 크기는 이미 같으므로, 입력칸에서
 * 남는 두 값만 버튼 쪽으로 맞추면 저절로 같아진다.
 */
.entry-lines-head .input-group-sm > .form-control,
.entry-lines-head .input-group-sm > .input-group-text {
    min-height: 0;
    line-height: normal;
}

/*
 * 저장 버튼 둘.
 *
 * '처리'가 위에 혼자 넓게 선다 — 대부분은 물건과 명세서가 같이 오고, 그때
 * 두 버튼이 나란히 같은 크기면 어느 쪽이 보통인지 매번 읽어야 한다.
 */
.entry-submit .btn {
    min-height: 42px;
}

/* --------------------------------------------------------------------------
 * 거래명세서 (건 하나 인쇄)
 *
 * 국내에서 쓰는 표준 양식 그대로다 — 문방구에서 파는 2단 양식지와 칸이 같다.
 * 종이를 받는 쪽이 이미 그 모양에 익숙하고, 회계에 넘길 때 대조하기도 쉽다.
 *
 * **선 색이 이 문서의 절반이다.** 위 장은 빨강(공급자 보관용), 아래 장은 파랑
 * (공급받는자 보관용)이다. 두 장을 받아 든 사람이 **색만 보고** 자기 것을 고를 수
 * 있어야 한다 — 글자를 읽어야 알 수 있으면 결국 둘 다 챙기거나 둘 다 놓는다.
 *
 * 색은 `--slip-line` 하나로 흐르고 두 장은 그 값만 다르다. 규칙을 두 벌 쓰면
 * 한쪽만 고치는 날이 반드시 온다.
 *
 * **여기서만 브랜드 색을 쓰지 않는다.** 라임과 올리브는 화면의 말이고, 이 종이는
 * 거래처가 갖는다. 관행이 정한 색을 그대로 쓰는 편이 낫다.
 *
 * ── 치수는 'A4 한 장에 두 벌'이 정한다 ──────────────────────────
 *
 * 처음에는 화면에서 읽기 좋은 크기로 잡았다가 두 벌이 두 장으로 갈라졌다.
 * **두 장으로 나오면 이 양식의 뜻이 없어진다** — 한 장을 잘라 나눠 갖는 종이인데
 * 두 장이면 그냥 같은 것을 두 번 뽑은 셈이고, 앞장만 건네고 뒷장을 잃는다.
 *
 * 그래서 아래 값은 전부 **여백에서 깎아 낸 것**이다. 본문 11px, 셀 여백 2px,
 * 빈 줄 높이 1.05rem. 양식지의 글씨가 원래 이만큼 작다 — 칸이 아홉이라 크게
 * 잡을 자리가 없고, 손으로 적어 넣는 종이라 인쇄 글씨는 작아도 된다.
 *
 * **치수를 키울 일이 생기면 두 벌이 한 장에 남는지부터 확인한다.**
 * 인쇄 미리보기에서 2페이지가 되면 이 절의 어딘가를 되돌려야 한다.
 * -------------------------------------------------------------------------- */
/*
 * **한 장을 가득 채우고 가운데서 갈린다.**
 *
 * 두 벌이 종이 위쪽에 몰리면 자르는 선이 3분의 1쯤에 걸리고, 그 종이를 반으로 접으면
 * 한쪽에만 글자가 있다. 양식지가 위아래 똑같이 생긴 이유가 그것이다.
 *
 * 세로를 A4 한 장으로 못 박고 두 벌이 남는 높이를 반씩 나눠 갖는다. 각 벌 안에서는
 * **명세 표가 늘어나는 자리**다 — 늘어날 데가 표 말고 없다(위 상자는 칸이 정해져
 * 있고 아래 잔글씨는 늘어나면 빈 줄로 읽힌다). 표가 늘면 그 안의 줄들이 남는
 * 높이를 나눠 가지므로, 줄이 열두 줄보다 적은 건도 선이 가운데 온다.
 *
 * `height` 가 아니라 `min-height` 다 — 줄이 열두 줄을 넘는 건은 잘리지 않고
 * 그대로 길어져 두 장이 되어야 한다. 잘라 먹는 것보다 두 장이 낫다.
 */
.slip-sheet {
    display: flex;
    flex-direction: column;
    min-height: 281mm;      /* A4 세로 297mm − 위아래 여백 8mm(아래 @page) */
    max-width: 46rem;
    margin: 0 auto;
}

.slip {
    --slip-line: #c0272d;          /* 공급자 보관용 — 빨강 */
    --slip-tint: #fdf2f2;

    /* 남는 높이를 두 벌이 반씩 나눠 갖는다. min-height:0 이 없으면 flex 항목이
       내용 높이 아래로 줄지 못해 계산이 어긋난다. */
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    min-height: 0;

    padding: 0.7rem 0.9rem 0.9rem;
    background: #fff;
    color: #111;
    font-size: 0.6875rem;          /* 11px — 칸이 아홉이라 본문 크기로는 안 들어간다 */
    line-height: 1.25;

    /* 화면에서만 종이처럼 세운다. 인쇄에서는 아래에서 걷어낸다. */
    border: 1px solid var(--theme-border-color);
    border-radius: var(--theme-card-border-radius);
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06);
}

.slip-buyer {
    --slip-line: #1d4ed8;          /* 공급받는자 보관용 — 파랑 */
    --slip-tint: #eff4fe;
}

/* 머리 — 제목과 '어느 쪽 보관용'인지. 둘은 한 줄에 나란히 선다.
   따로 두 줄을 쓰면 그것만으로 한 장에서 15px 가 나간다. */
.slip-head {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 0.6rem;
    margin-bottom: 0.4rem;
}

.slip-title {
    margin: 0;
    font-size: 1.125rem;
    font-weight: 700;
    letter-spacing: 0.3em;         /* '거 래 명 세 서' — 문서 제목의 관행이다 */
    text-indent: 0.3em;            /* 자간이 마지막 글자 뒤에도 붙어 가운데가 밀린다 */
    color: var(--slip-line);
}

.slip-copy {
    font-size: 0.6875rem;
    color: var(--slip-line);
}

/*
 * 표 셋(공급자 상자 · 합계금액 · 명세)은 **한 덩어리로 이어져 보여야 한다.**
 * 따로 서면 종이가 세 조각으로 읽힌다. 위아래를 붙이고 겹치는 선은 하나로 만든다.
 */
.slip table {
    width: 100%;
    border-collapse: collapse;
    table-layout: fixed;
}

.slip-parties,
.slip-sum,
.slip-table,
.slip-sign {
    border: 1px solid var(--slip-line);
}

.slip-sum,
.slip-table {
    border-top: 0;
}

.slip th,
.slip td {
    padding: 0.15rem 0.3rem;
    border: 1px solid var(--slip-line);
    vertical-align: middle;
    word-break: break-all;         /* 칸이 좁아 긴 품명이 표를 밀어내는 것을 막는다 */
}

/* 칸 이름은 옅은 면에 얹는다. 값과 이름이 같은 무게면 어느 쪽이 내용인지 안 보인다. */
.slip-label {
    background: var(--slip-tint);
    font-weight: 500;
    text-align: center;
    color: var(--slip-line);
    line-height: 1.2;

    /* **줄바꿈을 막는다.** 칸 이름이 두 줄이 되면 상자의 네 줄 중 하나가 두 배로
       높아지고, 그것만으로 두 벌이 한 장에 안 남는다. 넘칠 말은 아예 짧게 쓴다. */
    white-space: nowrap;
}

/* 도장 자리. 이름 뒤에 (인) 을 옅게 둔다 — 찍을 자리가 있다는 표시다. */
.slip-seal-mark {
    margin-left: 0.25rem;
    color: #999;
    font-size: 0.5625rem;
}

/* '공 급 자' 세로 글자. 표준 양식이 이 자리를 세로로 쓴다. */
.slip-side {
    background: var(--slip-tint);
    color: var(--slip-line);
    font-weight: 600;
    text-align: center;
    line-height: 1.1;
    letter-spacing: 0;
}

/* 왼쪽 — 날짜와 받는 쪽. 네 줄을 통으로 쓰는 칸이라 안에서 스스로 자리를 잡는다.
   **이 칸의 높이가 공급자 상자 전체의 높이다**(rowspan 4). 여백을 여기서 깎는다. */
.slip-to {
    padding: 0.3rem 0.5rem;
}

.slip-date {
    color: #444;
}

.slip-to-name {
    margin-top: 0.25rem;
    font-size: 0.875rem;
    font-weight: 600;

    /* 상호가 비어 있어도 밑줄 자리는 남는다 — 손으로 적을 칸이다. */
    min-height: 1.25rem;
    border-bottom: 1px solid var(--slip-line);
    padding-bottom: 0.1rem;
}

.slip-to-suffix {
    margin-left: 0.3rem;
    font-size: 0.6875rem;
    font-weight: 400;
    color: #444;
}

.slip-to-note {
    margin-top: 0.25rem;
    font-size: 0.625rem;
    color: #555;
}

/* 합계금액 — 한글과 숫자를 한 줄에. 이 줄이 종이에서 제일 먼저 읽힌다.
   본문보다 한 단계만 키운다. 더 키우면 줄 높이가 그만큼 따라 올라간다. */
.slip-sum-words {
    font-size: 0.875rem;
    font-weight: 600;
}

.slip-sum-figure {
    width: 8.5rem;
    text-align: right;
    font-size: 0.875rem;
    font-weight: 600;
    white-space: nowrap;
}

/* 명세 표. **한 벌 안에서 늘어나는 자리는 여기뿐이다.** */
.slip-table {
    flex: 1 1 auto;
}

.slip-table thead th,
.slip-table tfoot th {
    background: var(--slip-tint);
    color: var(--slip-line);
    font-weight: 500;
    text-align: center;
}

.slip-table tfoot th {
    text-align: right;
}

.slip-col-day {
    width: 1.5rem;
    text-align: center;
}

.slip-col-spec {
    width: 4.5rem;
}

.slip-col-num {
    width: 4.6rem;
    text-align: right;
    white-space: nowrap;
}

.slip-col-memo {
    width: 2.6rem;
    text-align: center;
}

/* 품목코드는 이름 뒤에 옅게. 이름만으로 못 찾는 품목이 있다. */
.slip-code,
.slip-sub {
    color: #777;
    font-size: 0.5625rem;
}

.slip-code {
    margin-left: 0.25rem;
}

/*
 * 되돌려진 줄.
 *
 * **지우지 않는다.** 수불부와 같은 이유다(2.5절) — 이 종이의 합계가 왜 이 숫자인지
 * 설명하려면 상계된 줄도 보여야 한다. 대신 줄을 그어 '이 줄은 살아 있지 않다'를
 * 한눈에 말한다.
 */
.slip-line-cancelled {
    color: #999;
    text-decoration: line-through;
}

/*
 * 명세 줄의 높이.
 *
 * **빈 줄에만 높이를 주면 안 된다.** 표가 남는 높이를 메우려고 늘어날 때(위 flex),
 * 브라우저는 높이를 지정하지 않은 줄에 그 남는 높이를 몰아준다. 한때 빈 줄에만
 * `height` 가 있어서, 품목이 한 줄뿐인 명세서를 뽑으면 **그 한 줄만 71px 로 부풀고**
 * 아래 빈 줄은 18px 로 남았다 — 표가 아니라 머리글 하나가 얹힌 상자처럼 보였다.
 *
 * 내용 줄과 빈 줄에 **같은 높이**를 준다. 그러면 남는 높이가 줄 수만큼 고르게
 * 나뉘어, 품목이 한 줄이든 여덟 줄이든 줄 간격이 같다. 손으로 적어 넣는 종이라
 * 줄 간격이 일정한 것이 이 표의 쓸모다.
 */
.slip-table > tbody > tr > td {
    height: 1.15rem;
}

/* 종이 아래의 잔글씨. */
.slip-notes {
    margin: 0.35rem 0 0.4rem;
}

.slip-note {
    margin: 0 0 0.15rem;
    font-size: 0.625rem;
    color: #444;
}

.slip-note-muted {
    color: #777;
}

.slip-note-label {
    display: inline-block;
    min-width: 2rem;
    margin-right: 0.3rem;
    color: var(--slip-line);
}

.slip-draft {
    margin: 0 0 0.35rem;
    padding: 0.2rem 0.45rem;
    border: 1px dashed var(--slip-line);
    color: var(--slip-line);
    font-size: 0.625rem;
}

.slip-sign .slip-value {
    text-align: center;
}

/*
 * 인수자 칸의 (인).
 *
 * **오른쪽 끝에 붙인다.** 이 칸은 받는 사람이 **이름을 손으로 적고 그 뒤에 도장을
 * 찍는** 자리다. (인)이 가운데 있으면 이름을 적을 자리가 좌우로 갈려, 어디에
 * 적어야 할지 사람마다 다르게 읽는다.
 *
 * 공급자 상자의 성명 칸(#slip-parties)은 그대로 둔다 — 거기서는 이미 찍혀 나온
 * 이름 바로 뒤에 붙는 표시라 이어져 있어야 한다.
 */
.slip-sign .slip-value.slip-seal {
    text-align: right;
}

/*
 * 자르는 선.
 *
 * 가위 자국이 없으면 어디를 접거나 잘라야 하는지 사람마다 다르게 읽고,
 * 결국 두 장의 폭이 달라진다.
 */
.slip-cut {
    position: relative;
    margin: 0.7rem 0;
    text-align: center;
    color: #aaa;
    font-size: 0.625rem;
}

.slip-cut::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 0;
    right: 0;
    border-top: 1px dashed #bbb;
}

.slip-cut span {
    position: relative;
    padding: 0 0.5rem;
    background: var(--theme-body-bg);
}

.slip-actions {
    display: flex;
    justify-content: center;
    gap: 0.5rem;
    margin-top: 1rem;
}

/*
 * 다크 모드에서도 **종이는 흰색이다.**
 *
 * 이 화면은 인쇄 미리보기 노릇을 겸한다. 검은 바탕에 흰 글씨로 보다가 종이를 뽑으면
 * 전혀 다른 것이 나와서, 미리 보는 일 자체가 쓸모없어진다. 그래서 `.slip` 안은
 * 테마를 따라가지 않고 위에서 정한 값을 그대로 쓴다 — 겉의 여백만 테마를 따른다.
 */
[data-bs-theme="dark"] .slip {
    border-color: #333;
    box-shadow: none;
}

/*
 * 종이 여백은 **`layouts/print.blade.php` 안에서** 정한다(8mm).
 *
 * `@page` 는 선택자로 좁힐 수 없어 문서 전체에 걸린다. 한때 이 자리에 적어 두었더니,
 * 파일 뒤쪽에 있다는 이유만으로 같은 스타일시트를 쓰는 **목록 인쇄의 12mm 까지**
 * 덮어 버렸다. 명세서만 쓰는 레이아웃 안으로 옮겼다.
 */

@media print {
    /* 종이에서는 상자가 필요 없다. 페이지 여백이 이미 그 일을 한다. */
    .slip-sheet {
        max-width: none;
    }

    .slip {
        padding: 0;
        border: 0;
        border-radius: 0;
        box-shadow: none;

        /* 한 벌이 두 페이지에 걸쳐 잘리면 두 벌이 아니라 네 조각이 된다. */
        break-inside: avoid;
        page-break-inside: avoid;
    }

    .slip-cut {
        margin: 0.5rem 0;
    }

    .slip-cut span {
        background: #fff;
    }

    .slip-actions {
        display: none !important;
    }

    /*
     * 색은 **인쇄에서 사라진다** — 브라우저의 '배경 그래픽' 옵션이 기본으로 꺼져 있어
     * 옅은 면(--slip-tint)이 안 나온다. 선과 글자는 배경이 아니라 남으므로,
     * 이 양식이 색으로 말하는 부분은 그대로 지켜진다. 면에 기대지 않는 이유다.
     */
    .slip-label,
    .slip-side,
    .slip-table thead th,
    .slip-table tfoot th {
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }
}

/* --------------------------------------------------------------------------
 * 상단바의 사용자 메뉴
 *
 * **이니셜 동그라미를 뺐다.** 사진이 들어갈 자리가 아니라 이름의 첫 글자를 한 번 더
 * 적는 판이었고, 바로 옆에 이름 전체가 있으니 같은 말을 두 번 하는 셈이었다.
 * 브랜드 색 면이라 사이드바의 활성 메뉴와 세기가 겹치는 문제도 있었다 —
 * 라임은 '누르면 일이 일어나는 자리' 하나로 남긴다(8절).
 *
 * 대신 **꺾쇠**를 둔다. 눌러야 열린다는 표시가 없어서 이름을 눌러도 되는지
 * 알 길이 없었다.
 * -------------------------------------------------------------------------- */
.topbar-user {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.375rem 0.625rem;
    border-radius: var(--theme-border-radius);
    color: var(--theme-body-color);
    text-decoration: none;
}

/* 링크 버튼의 기본 밑줄·파란 글자를 걷는다. */
.topbar-user:hover,
.topbar-user:focus,
.topbar-user:active,
.topbar-user.show {
    color: var(--theme-body-color);
    background: var(--sf-raised);
    text-decoration: none;
}

.topbar-user:focus-visible {
    outline: 2px solid var(--brand-emph);
    outline-offset: 1px;
    box-shadow: none;
}

/*
 * 이름은 언제나 보인다.
 *
 * 예전에는 좁은 화면에서 이름을 감추고 동그라미만 남겼는데, 동그라미가 없어진
 * 지금 그렇게 두면 **누를 것이 아무것도 안 보인다.** 대신 길면 끝을 접는다.
 */
.topbar-user-name {
    max-width: 10rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 600;
}

.topbar-user-caret {
    flex-shrink: 0;
    color: var(--theme-secondary-color);
    transition: transform 0.15s ease-in-out;
}

.topbar-user[aria-expanded="true"] .topbar-user-caret {
    transform: rotate(180deg);
}

.topbar-user-menu {
    min-width: 16.5rem;
    padding-top: 0;
    padding-bottom: 0.375rem;
}

/*
 * 머리 — **현장 화면의 서랍과 같은 어법으로 짠다.**
 *
 * 한때 세 줄이 좁은 간격으로 붙어 있고 역할만 배지로 튀어 있었다. 이 상자에서 제일
 * 강조된 것이 '무슨 권한인가'가 되면서, 정작 찾아온 이유인 '내 정보 · 비밀번호 변경'
 * 보다 먼저 읽혔다.
 *
 * 층을 셋으로 나눈다 — 아이디(진하게) · 회사(조용히) · 직전 접속(라벨을 앞에).
 * 역할은 아이디 옆에 `·` 로 붙는다(마크업 주석 참고).
 */
.topbar-user-head {
    padding: 0.875rem 1rem;
    margin-bottom: 0.375rem;
    background: var(--sf-raised);
    border-bottom: 1px solid var(--theme-border-color);
}

.topbar-user-id {
    font-size: 0.9375rem;
    font-weight: 600;
    line-height: 1.3;
}

/* 역할. 배지를 만들지 않는다 — 현장 서랍의 `.m-drawer-role` 과 같은 규칙이다. */
.topbar-user-role {
    font-weight: 400;
    color: var(--theme-secondary-color);
}

.topbar-user-role::before {
    content: "·";
    margin: 0 0.3rem;
}

.topbar-user-tenant {
    margin-top: 0.125rem;
    font-size: 0.8125rem;
    color: var(--theme-secondary-color);
}

/* 직전 접속은 보안을 위해 보여주는 값이다. 잔글씨로 눌러 두면 아무도 안 읽는다 —
   라벨을 앞에 세워 무엇을 말하는 줄인지 먼저 읽히게 한다. */
.topbar-user-last {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--theme-border-color);
    font-size: 0.75rem;
    color: var(--theme-secondary-color);
}

.topbar-user-last-label {
    margin-right: 0.25rem;
    font-weight: 500;
    color: var(--theme-body-color);
}

/* 항목마다 아이콘. 글자만 늘어서 있으면 찾는 줄을 매번 읽어야 한다.
   여백은 현장 서랍(0.875rem)에 맞춘다 — 훑고 지나가는 자리라 촘촘할 이유가 없다. */
.topbar-user-link {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding-block: 0.5rem;
}

.topbar-user-link i,
.topbar-user-link svg {
    flex-shrink: 0;
    color: var(--theme-secondary-color);
}

/* 로그아웃의 아이콘은 글자와 같은 빨강이어야 한 덩어리로 읽힌다. */
.topbar-user-link.text-danger i,
.topbar-user-link.text-danger svg {
    color: inherit;
}

/* --------------------------------------------------------------------------
 * 접힌 사이드바(condensed)의 hover 메뉴
 *
 * 레일만 남기고 접으면, 아이콘에 손을 얹었을 때 오른쪽으로 이름표와 2차 메뉴가
 * 펼쳐 나오는 것이 템플릿의 설계다. 그런데 **그 패널이 잘려 나왔다.**
 *
 * 범인은 사이드바를 감싼 SimpleBar 다. `.simplebar-mask` 가 `overflow: hidden` 이라
 * 레일 폭(75px) 밖으로 나가는 것을 전부 자른다. 펼쳐 나온 패널은 260px 언저리까지
 * 뻗으므로 대부분이 잘리고, 남은 조각에 1차 메뉴명과 2차 목록이 뭉개져 보였다.
 *
 * 접었을 때만 자르기를 푼다 — 그때 메뉴는 아이콘만 남아 세로로 짧아서, 잘라내며
 * 스크롤을 만들 이유가 없다. 펼친 상태에서는 긴 메뉴를 스크롤해야 하므로 그대로 둔다.
 * (`overflow-x` 만 풀 수는 없다 — 한 축이 visible 이 아니면 다른 축도 auto 로 계산된다.)
 * -------------------------------------------------------------------------- */
html[data-sidenav-size="condensed"] .sidenav-menu .simplebar-mask,
html[data-sidenav-size="condensed"] .sidenav-menu .simplebar-offset,
html[data-sidenav-size="condensed"] .sidenav-menu .simplebar-content-wrapper,
html[data-sidenav-size="condensed"] .sidenav-menu .simplebar-content {
    overflow: visible !important;
}

/*
 * 패널의 기준 상자를 못 박는다.
 *
 * 2차 목록은 `position: absolute` 인데 템플릿은 기준이 될 상자를 지정하지 않았다.
 * 그러면 어느 조상이 잡히느냐가 화면 상태에 따라 달라져, 어떤 때는 메뉴 옆에
 * 어떤 때는 사이드바 맨 위에 뜬다.
 */
html[data-sidenav-size="condensed"] .sidenav-menu .side-nav > .side-nav-item {
    position: relative;
}

/*
 * 펼쳐 나온 이름표와 2차 목록은 **한 장의 카드**로 보여야 한다.
 *
 * 템플릿 기본은 사이드바와 같은 바탕에 옅은 그림자뿐이라, 밝은 메뉴에서는 본문 위에
 * 떠 있는 것인지 사이드바의 일부인지 구분되지 않았다. 테두리와 그림자를 세운다.
 */
html[data-sidenav-size="condensed"] .sidenav-menu .side-nav > .side-nav-item:hover > .side-nav-link,
html[data-sidenav-size="condensed"] .sidenav-menu .side-nav > .side-nav-item:hover > .collapse > .sub-menu {
    z-index: 5;   /* 본문 카드 위에 뜬다 */
    border: 1px solid var(--theme-sidenav-border-color);
    box-shadow: 0 8px 20px rgba(15, 23, 42, 0.14);
    background: var(--theme-sidenav-bg);
}

/* 이름표와 목록이 맞닿는 변에서는 테두리와 모서리를 없앤다 — 한 장으로 이어져야 한다. */
html[data-sidenav-size="condensed"] .sidenav-menu .side-nav > .side-nav-item:hover > .side-nav-link[data-bs-toggle="collapse"] {
    border-bottom-color: transparent;
    border-end-end-radius: 0;
    border-end-start-radius: 0;
}

html[data-sidenav-size="condensed"] .sidenav-menu .side-nav > .side-nav-item:hover > .collapse > .sub-menu {
    border-top: 0;
    padding: 0.25rem 0;
}

/*
 * 이름표 글자.
 *
 * 접힌 레일에서는 아이콘이 가운데 정렬이라(`justify-content: center`) 이름이 펼쳐지면
 * 글자가 가운데로 몰려 아이콘과 겹쳐 보였다. 펼쳐진 동안에는 왼쪽 정렬로 되돌린다.
 */
html[data-sidenav-size="condensed"] .sidenav-menu .side-nav > .side-nav-item:hover > .side-nav-link {
    justify-content: flex-start;
    gap: var(--theme-sidenav-item-gap);
}

html[data-sidenav-size="condensed"] .sidenav-menu .side-nav > .side-nav-item:hover > .side-nav-link .menu-text {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* 2차 목록의 줄. 접힌 상태의 큰 여백은 그대로 두되 왼쪽 들여쓰기를 되돌린다 —
   펼친 사이드바에서 아이콘 자리를 비우던 값이라, 아이콘이 없는 패널에서는 빈 칸만 된다. */
html[data-sidenav-size="condensed"] .sidenav-menu .side-nav .sub-menu .side-nav-item .side-nav-link {
    padding-inline-start: 1rem;
}

/* 화살표는 패널에서 쓸모가 없다 — 이미 펼쳐져 있다. */
html[data-sidenav-size="condensed"] .sidenav-menu .side-nav > .side-nav-item:hover > .side-nav-link .menu-arrow {
    display: none;
}
