/* ds.css — 품고 그라운드 통합 디자인 시스템 (단일 소스)
 * 설계 문서: docs/디자인시스템.md
 *
 * ⚠ 이 파일은 admin / index / carrier 3화면이 공유한다.
 *   한 줄 고치면 3화면이 동시에 바뀐다. 반드시 docs/디자인시스템.md §7-4 검증 체크포인트를 돌 것.
 *
 *   login / driver / privacy는 **의도적으로 제외**했다(2026-08-14 검증 결과).
 *   - privacy.html: 자체 *{box-sizing}만 있어 여기 *{margin:0;padding:0}이 새로 먹는다 → 문서 여백 붕괴
 *   - driver.html : 자체 .badge에 vertical-align·white-space가 없어 여기 값이 새로 먹는다
 *   - login.html  : .btn.g를 다른 의미로 쓴다(흰 배경 아웃라인 버튼)
 *   세 화면 모두 badge·dtable·toolbar·modal 같은 공용 컴포넌트를 쓰지 않아 얻는 것도 없다.
 *   나중에 각 화면의 스타일을 의도적으로 이관할 때 함께 붙일 것.
 *
 * ── 지켜야 할 규칙 4가지 (설계 문서 §6-4) ──────────────────────────────
 * 1. !important 금지.
 *    carrier-disp-shim.js가 admin-dispatch.js의 최상위 배선이 죽는 걸 막으려고 숨김 버튼 8개를
 *    인라인 display:none으로 심어둔다. 여기서 !important를 쓰면 그 스텁이 운송사 화면에 노출된다.
 * 2. 맨몸 요소 선택자(input / select / th / td / label / table) 추가 금지.
 *    공용 JS 모듈(onboard.js · arrange-view.js · account.js)이 그리는 요소를 무차별 타격한다.
 *    커밋 368fa11이 정확히 이걸로 터졌다(온보딩 체크박스가 거대 입력칸으로 렌더).
 * 3. 특이도를 페이지 대응 규칙보다 높이지 않는다. (.dtable thead th 를 .ds .dtable thead th 로 올리지 않기)
 * 4. <link>는 각 HTML의 기존 <style> "앞"에 둔다.
 *    뒤에 두면 이 파일이 이겨서 6화면이 한꺼번에 흔들린다. 앞에 두어야 페이지가 이기고,
 *    이관이 끝나지 않은 규칙은 기존 동작 그대로 남는다.
 *
 * ⚠ 참조 시 ?v= 쿼리스트링을 붙이지 말 것.
 *   build-pages.js의 역참조 검증 정규식이 ? 를 배제해 검증이 통째로 스킵되고,
 *   FILES 등록을 누락하면 6화면이 무스타일로 배포된다. 캐시는 _headers의 /*.css no-cache로 처리한다.
 *
 * ── 1단계 범위(2026-08-14) ────────────────────────────────────────────
 * admin.html / index.html / carrier.html 세 화면의 <style>에서 **선택자·선언이 완전히 동일한**
 * 53개 규칙만 원문 그대로 옮겼다. 값을 바꾸지 않았고, 페이지의 기존 <style>도 지우지 않았다.
 * 따라서 이 시점의 계산된 스타일은 정의상 이전과 같다(시각 변화 0).
 * 토큰(:root)·.btn 기본·.card·h1·.sub·.hint·.spin·.pgbtn 기본은 화면별 값이 달라 아직 제외한다.
 */

/* ═══ 1. 토큰 (5단계, 2026-08-14) ════════════════════════════════════
   이전에는 세 화면이 :root를 각자 복제해 갖고 있었고 이미 어긋나 있었다:
     admin   --st-ship 없음, --font 스택은 통일본
     index   --st-ship 있음  ← 여기만
     carrier --st-* 블록이 통째로 뒤쪽(109행)에 따로, --font 스택도 달랐음
   → 여기 한 곳으로 모으고 세 화면의 :root 선언을 지웠다. 이제 단일 소스다.

   상태 ↔ 색 매핑 표준은 docs/디자인시스템.md §5-1.
   픽업완료(주황) ≠ 운송 종료(틸) ≠ 정산완료(초록) — 세 단계가 서로 다른 색이어야 한다.
   ⚠ 2026-08-26 재배치: 입고완료·배송완료는 **같은 국면(운송 종료)**이라 둘 다 ship(틸)을 쓴다.
     종전에는 입고완료가 done이어서 정산완료와 색이 완전히 같았고(#13854a), 같은 서비스 안에서
     연속된 두 단계가 구분되지 않았다. 서비스 구분은 색이 아니라 유형 뱃지가 한다. */
:root{
  --blue:#1C6FF2; --blue-d:#1457C6; --navy:#0b1f44;
  --ink:#15202e; --muted:#5b6675; --line:#e4e8ee; --bg:#f4f7fb; --pale:#eef4ff;
  --font:"Pretendard","Malgun Gothic","Apple SD Gothic Neo",system-ui,sans-serif;
  --tr:.14s ease;
  --sbw:222px;

  /* 모서리 스케일 — 이전엔 6/7/8/9/10/11/12/14~16/20px 9종이 뒤섞여 있었다(설계 문서 §3-3) */
  --radius-sm:6px; --radius-md:8px; --radius-lg:10px; --radius-xl:14px; --radius-pill:999px;

  --st-wait-bg:#fff3cd;   --st-wait-fg:#8a6d00;    /* 접수·대기 */
  --st-info-bg:#eaf1ff;   --st-info-fg:#1b6fd6;
  --st-info2-bg:#eef4ff;  --st-info2-fg:#1457C6;   /* 배차완료 */
  --st-prog-bg:#e6f7ee;   --st-prog-fg:#0c8a5a;    /* 진행중 */
  --st-prog2-bg:#e3f5ea;  --st-prog2-fg:#0b7a53;
  --st-done-bg:#e3f5ea;   --st-done-fg:#13854a;    /* 정산완료 — 최종 종결에만 쓴다(2026-08-26) */
  --st-pick-bg:#fef3e2;   --st-pick-fg:#c07a1c;    /* 픽업완료 — 입고완료(초록)와 구분되는 주황 */
  --st-ship-bg:#d6f0ee;   --st-ship-fg:#0e857a;    /* 운송 종료 — 입고완료(픽업)·배송완료(주선) 공통(2026-08-26) */
  --st-cancel-bg:#fdecea; --st-cancel-fg:#c2362d;  /* 취소·실패 */
  --st-neutral-bg:#f0f3f8;--st-neutral-fg:#7c8aa0;
  --st-purple-bg:#f3f0ff; --st-purple-fg:#6b4fd6;  /* 결제대기 등 — 이전엔 토큰이 없어 #6b3fb8/#7a5cc0/#6b4fd6 3종이 뒤섞여 있었다 */
}
/* ⚠ --brand 와 --gray-50 은 일부러 정의하지 않는다.
   admin.html·admin-dispatch.js가 var(--brand,#1C6FF2), var(--gray-50,#f6f7f9)처럼
   폴백을 달아 쓰고 있고, 이 토큰들은 원래부터 어디에도 정의된 적이 없다.
   여기에 정의를 추가하는 순간 그 자리 색이 폴백값에서 조용히 바뀐다.
   설계 문서 §3-1의 --gray-* 원시 스케일을 도입할 때는 --gray-50 값(#eef4ff 제안)이
   현재 폴백(#f6f7f9)과 다르다는 점을 먼저 확인할 것. */

/* ═══ 2. 리셋 · 기반 ═════════════════════════════════════════════════ */
*{box-sizing:border-box;margin:0;padding:0;}
body.sb-collapsed{--sbw:60px;}
@media(max-width:820px){ :root{--sbw:56px;} }

/* ═══ 3. 상태 표시 — 뱃지 · 상태필 ═══════════════════════════════════
   변형 순서는 admin.html 원문 그대로 유지한다(뒤 규칙이 이기므로 순서를 바꾸면 잠재적 차이가 생긴다).
   상태 ↔ 색 매핑 표준은 docs/디자인시스템.md §5-1. */
/* flex:none — .badge 자신이 inline-flex이므로, flex 컨테이너(대시보드 행·툴바·스텝바 옆 등)
   안에 놓이면 flex 항목이 되어 기본 flex-shrink:1로 콘텐츠 폭 아래까지 눌린다.
   그러면 '배차중'처럼 글자가 있는 뱃지의 좌우 padding(11px)이 먹혀 폭이 제각각으로 보인다.
   white-space:nowrap은 글자 줄바꿈만 막고 박스 압축은 막지 못한다(.stepchip과 같은 원인, 2026-08-19). */
.badge{display:inline-flex;align-items:center;justify-content:center;flex:none;height:24px;line-height:1;vertical-align:middle;background:var(--pale);color:var(--blue-d);font-weight:700;font-size:12px;border-radius:999px;padding:0 11px;white-space:nowrap;}
.badge.wait,.metatag.wait{background:var(--st-wait-bg);color:var(--st-wait-fg);}
.badge.info,.metatag.info{background:var(--st-info-bg);color:var(--st-info-fg);}
.badge.info2,.metatag.info2{background:var(--st-info2-bg);color:var(--st-info2-fg);}
.badge.prog,.metatag.prog{background:var(--st-prog-bg);color:var(--st-prog-fg);}
.badge.prog2,.metatag.prog2{background:var(--st-prog2-bg);color:var(--st-prog2-fg);}
.badge.done,.metatag.done{background:var(--st-done-bg);color:var(--st-done-fg);}
.badge.cancel,.metatag.cancel{background:var(--st-cancel-bg);color:var(--st-cancel-fg);}
.badge.neutral,.metatag.neutral{background:var(--st-neutral-bg);color:var(--st-neutral-fg);}
.badge.pick,.metatag.pick{background:var(--st-pick-bg);color:var(--st-pick-fg);}
.badge.ship,.metatag.ship{background:var(--st-ship-bg);color:var(--st-ship-fg);}      /* 배송완료(주선) — 이전엔 index.html에만 정의돼 관리자·운송사에선 무색이었다 */
.badge.purple,.metatag.purple{background:var(--st-purple-bg);color:var(--st-purple-fg);} /* 결제대기 등 */
/* 분류 뱃지 — 서비스·유형 구분(밀크런 · 주선 · 파렛/박스/트럭픽업).
   상태(.badge)와 다른 정보 계층이라 "채움 vs 테두리"로 구조를 나눈다.
   ⚠ 크기 변형이 아니다(§4-11-B 저촉 아님) — 같은 24px 알약이고 채움 방식만 다르다.
   왜 필요했나: 분류 뱃지만 디자인 시스템 밖에 남아 인라인 hex로 그려지고 있었다.
     밀크런은 팔레트에 없는 핑크(#FCE7F3)라 혼자 튀었고,
     파렛픽업·주선은 #eef4ff(--st-info2)라 "배차완료" 상태 뱃지와 완전히 같은 색이어서
     같은 행에서 분류인지 상태인지 구분되지 않았다(2026-08-18).
   색은 --st-* 를 그대로 공유한다 — 컴포넌트는 둘이지만 색 체계는 하나다.
   ⚠ 반드시 위 변형 목록 "뒤"에 둘 것. 같은 특이도(0,2,0)라 순서로 background를 이긴다.
   높이는 전역 *{box-sizing:border-box} 덕에 테두리를 넣어도 24px 그대로다. */
.badge.ty{background:#fff;border:1px solid currentColor;}

/* ═══ 결제 금액 내역표·합계 ══════════════════════════════════════════
   pickup-detail.js(픽업 상세 공용 렌더러)와 견적 화면이 함께 쓴다.
   ⚠ 종전에는 index.html <style>에만 있어서, 같은 모듈이 그린 표가
     **관리자·운송사 화면에서는 테두리·헤더 없이** 렌더됐다(2026-08-24 사용자 지적).
   맨몸 요소선택자(table/th/td)는 쓰지 않는다 — 클래스 안에서만 지정한다(§4 규칙).
   index.html의 같은 정의는 값이 동일한 중복이라 화면 차이는 없다(정리 대상). */
.linetbl{width:100%;border-collapse:collapse;font-size:14px;margin-top:6px;}
.linetbl th,.linetbl td{border-bottom:1px solid var(--line);padding:8px 11px;text-align:left;}
.linetbl th{background:var(--navy);color:#fff;font-weight:700;}
.linetbl th.r,.linetbl td.r{text-align:right;}
/* 합계 스택 — 항목은 점선, 마지막 강조 행만 굵게·파랑 */
.total{display:flex;justify-content:space-between;gap:16px;padding:7px 0;border-bottom:1px dashed var(--line);font-size:14px;}
.total.big{border-bottom:none;font-size:18px;font-weight:800;padding-top:10px;}
.total.big .v{color:var(--blue-d);}
/* 주선·트럭픽업 차량별 표는 항목이 많아 한 단계 촘촘하게 */
.arr-money .linetbl{font-size:12px;}
.arr-money .total{font-size:12.5px;padding:5px 0;}
.arr-money .total.big{font-size:14px;padding-top:8px;}

/* ⚠ 컴팩트 변형(.badge.sm)은 두지 않는다 — §4-11-B.
   정산 뱃지 20px / 목록 뱃지 24px로 갈려 같은 상태가 화면마다 다른 크기로 보였다.
   실제 사용처 20곳이 전부 일반 밀도 표(정산·마스터·주문)였고, 좁은 컬럼 때문이 아니라
   SettleView가 예전에 22px 태그를 쓰던 흔적이었다. 한 종류로 못 박는다. */

/* 메타 태그 — 행·셀에 덧붙는 작은 부가 표시(분리적재 · 용차단가 · 발송됨 등).
   상태 뱃지(.badge)와 다른 컴포넌트다: 상태가 아니라 속성을 알리고, 촘촘한 패널에 여러 개가 붙는다.
   그래서 더 작고 테두리로 구분한다. 의미 색은 .badge와 같은 --st-* 변형을 공유한다
   — 컴포넌트는 모양·크기만 다르고 색 체계는 하나다.
   ⚠ 크기 변형(.sm 등)을 만들지 말 것(§4-11-B). 이전에는 9px·9.5px·10px이 뒤섞여 있었다. */
.metatag{display:inline-flex;align-items:center;height:17px;padding:0 5px;border-radius:var(--radius-sm);font-size:10px;font-weight:700;white-space:nowrap;border:1px solid rgba(0,0,0,.10);vertical-align:middle;}

/* 모달 헤더 경고 플래그 — 팝업 하나에 최대 한 개. 닫기(✕) 왼쪽에 놓는다.
   ⚠ 크기 변형이 아니다(§4-11-B 저촉 아님). .badge(상태)·.metatag(속성)와 역할이 다른 세 번째 컴포넌트다:
     "이 건은 평소와 다르게 다뤄야 한다"를 팝업을 연 순간 알리는 자리이고, 한 화면에 하나만 존재한다.
     .badge.cancel(연분홍 배경+빨간 글자)로는 헤더의 제목·스텝바 사이에서 묻혔다 — 채움 반전으로 대비를 올린다.
   왜 필요했나: 긴급배차는 운송사가 할증을 얹어 금액을 제시해야 성립하는데(시스템이 자동 계산하지 않는다),
     헤더에서 놓치면 일반 건과 같은 금액을 넣는다. 놓치면 돈이 틀어지는 정보라 강조 수준을 올렸다(2026-08-19). */
.urgent-flag{display:inline-flex;align-items:center;gap:6px;flex:none;height:32px;padding:0 14px;
  background:var(--st-cancel-fg);color:#fff;border-radius:var(--radius-pill);
  font-size:13px;font-weight:800;letter-spacing:-.01em;white-space:nowrap;vertical-align:middle;
  box-shadow:0 0 0 4px rgba(194,54,45,.16),0 2px 8px rgba(194,54,45,.28);}
.urgent-flag .uf-ico{font-size:14px;line-height:1;}
/* 시선 유도 — 링만 맥동한다(레이아웃·글자는 움직이지 않아 읽기를 방해하지 않는다) */
@keyframes urgentPulse{0%,100%{box-shadow:0 0 0 4px rgba(194,54,45,.16),0 2px 8px rgba(194,54,45,.28);}
  50%{box-shadow:0 0 0 8px rgba(194,54,45,.05),0 2px 8px rgba(194,54,45,.28);}}
.urgent-flag{animation:urgentPulse 1.8s ease-in-out infinite;}
@media (prefers-reduced-motion:reduce){ .urgent-flag{animation:none;} }
/* 헤더 전체를 붉게 물들여 팝업 성격을 한 번 더 알린다 — 플래그만으로는 우측에 갇힌다 */
.modal-head.has-urgent,.mhead.has-urgent{background:var(--st-cancel-bg);border-bottom-color:var(--st-cancel-fg);}
/* 플래그 자리 — 제목(가변) · 긴급 플래그 · 닫기(✕) 순으로 못박는다.
   종전에는 justify-content:space-between의 여유공간 분배에만 맡겨서, 제목 길이(픽업 상세는
   스텝바가 길다)와 컨테이너 gap 차이에 따라 픽업 상세와 주선 상세의 플래그 위치가 서로 달랐다.
   order로 순서를, margin-left:auto로 우측 붙임을 보장하면 두 헤더에서 같은 자리가 된다(2026-08-19). */
.modal-head>.urgent-slot,.mhead>.urgent-slot{order:2;flex:none;margin-left:auto;}
.modal-head>.modal-x,.mhead>.mx{order:3;}
/* 제목이 남은 폭을 차지해 플래그·닫기를 오른쪽으로 밀어낸다.
   ⚠ margin-left:auto만으로는 부족하다 — auto 여백은 '여유공간이 있을 때만' 작동한다.
     차량주선상세 제목은 [제목 · 접수번호 · 복사버튼 · 스텝바]로 길어서 여유공간이 0이 되고,
     그때 플래그가 제목 바로 뒤(헤더 중앙)에 붙어 픽업 상세와 자리가 달라졌다(2026-08-19).
     제목에 flex:1을 주면 길이와 무관하게 항상 오른쪽 정렬이 된다. */
.modal-head>.mh-title,.mhead>.mh-title{flex:1 1 auto;min-width:0;}
/* 플래그↔✕ 간격 10px을 두 컴포넌트에서 같게. .mhead는 gap:10px가 이미 주므로 여기만 보탠다.
   ⚠ .modal-head에 gap을 넣는 방식은 쓰지 않는다 — 전 팝업 헤더가 10px 좁아져
     제목 안 스텝바(.stepbar는 inline-flex)가 압축되는 부작용이 있었다(2026-08-19). */
.modal-head>.urgent-slot{margin-right:10px;}

.stpill{display:inline-flex;align-items:center;gap:6px;padding:6px 13px;font-size:13px;font-weight:600;border:1px solid var(--line);background:#fff;color:var(--ink);border-radius:999px;cursor:pointer;transition:var(--tr);}
.stpill .n{font-size:11px;font-weight:700;padding:1px 7px;border-radius:10px;background:var(--pale);color:var(--muted);}
.stpill:hover:not(.on){border-color:#c7d3e6;background:#f7faff;}
.stpill.on{border-color:var(--c,var(--blue));background:var(--c,var(--blue));color:#fff;}
.stpill.on .n{background:rgba(255,255,255,.28);color:#fff;}

/* KPI 타일 — ds-util.js statTile()이 그리는 마크업의 스타일.
   admin.html·index.html에 값까지 똑같은 정의가 두 벌 있었다(2026-08-18 통합).
   carrier.html에는 아예 없어서 statTile()을 운송사 화면에서 쓸 수 없었다 — 여기 올려 3화면 공용으로.
   ⚠ 정산 KPI(SettleView.kpiCard)는 값 22px·radius 14px로 아직 별개다. 통합 시 여기로 흡수할 것. */
.stat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;}
.stat-tile{background:#fff;border:1px solid var(--line);border-radius:12px;padding:14px 16px;box-shadow:0 4px 14px rgba(20,30,55,.04);}
.stat-tile.accent{background:var(--pale);border-color:#dbe8ff;}
.stat-tile .lbl{font-size:11.5px;color:var(--muted);font-weight:700;letter-spacing:.02em;}
.stat-tile .val{font-size:20px;font-weight:800;color:var(--ink);margin-top:5px;line-height:1.15;}
.stat-tile .sub{font-size:11px;color:var(--muted);margin-top:3px;}

/* ═══ 4. 표 — dtable · 빈 상태 · 로딩 ════════════════════════════════ */
.dtable{width:100%;border-collapse:collapse;font-size:13px;}
.dtable thead th{position:sticky;top:0;z-index:2;background:var(--navy);color:#fff;font-weight:700;font-size:12.5px;padding:10px 12px;text-align:left;white-space:nowrap;}
.dtable thead th.r{text-align:right;}
.dtable thead th.sortable{cursor:pointer;user-select:none;}
.dtable thead th.sortable:hover{background:#16305e;}
.dtable tbody td{padding:9px 12px;border-bottom:1px solid var(--line);vertical-align:middle;}
.dtable tbody tr:nth-child(even){background:#fafbfd;}
.dtable tbody tr:hover{background:var(--pale);}
.dtable td.r,.dtable th.r{text-align:right;font-variant-numeric:tabular-nums;}
.dtable tfoot td{font-weight:800;background:#f7f9fc;border-top:2px solid var(--line);}

/* 상세 변형 — 모달·카드 안에 들어가는 작은 표(금액 내역 · 원가 비교 · 차량 규격 등).
   목록표(.dtable)와 다른 컴포넌트다: 스크롤되는 목록이 아니라 이미 좁혀진 맥락의 내역이라
   sticky 헤더가 필요 없고, 네이비 헤더는 모달 안에서 과하다.
   그동안 이 패턴에 이름이 없어서 표마다 헤더 배경을 제각각 인라인으로 발랐다
   (연파랑 var(--pale) 4곳 · 회색 1곳 · 나머지 35곳은 아예 지정 없이 전역 th 규칙에 의존).
   ⚠ 전역 th{background:#f7f9fc}에 기대는 표가 아직 많다 — 그 규칙을 지우기 전에
      이 클래스로 옮겨야 한다(docs/디자인시스템.md 잔여 항목). */
/* 상세표는 가운데 정렬이 기본이다(2026-08-19).
   흩어져 있던 인라인 TH/TD 상수 4벌이 전부 text-align:center + white-space:nowrap이었다.
   기존 .dtable.detail 사용처 2곳(용량 PLT 표·할인율 표)도 숫자 표라 가운데가 맞다.
   ⚠ 금액 열은 .r이 우측 정렬로 덮는다(.dtable td.r) — 특이도가 같고 뒤에 오므로 이긴다. */
.dtable.detail thead th{position:static;background:var(--pale);color:var(--ink);font-size:12px;padding:6px 8px;text-align:center;white-space:nowrap;}
.dtable.detail tbody td{padding:6px 8px;font-size:12px;text-align:center;white-space:nowrap;}
.dtable.detail tbody tr:hover{background:transparent;}   /* 내역표는 행 선택 대상이 아니다 */
/* 가로로 넘치는 표를 감싸는 상자 — 넘칠 때만 양끝에 그림자가 생긴다(순수 CSS 스크롤 힌트, 2026-08-25).
   상세 팝업의 차량·일정 표는 열이 많아 좁은 화면에서 오른쪽 끝(운송상태)이 잘리는데,
   그림자가 없으면 그 열이 «아예 없는 것»으로 읽힌다(사용자 지적). */
.xwrap{overflow-x:auto;
  background:
    linear-gradient(90deg,#fff 30%,rgba(255,255,255,0)) left/26px 100% no-repeat,
    linear-gradient(90deg,rgba(255,255,255,0),#fff 70%) right/26px 100% no-repeat,
    radial-gradient(farthest-side at 0 50%,rgba(20,30,55,.16),rgba(20,30,55,0)) left/13px 100% no-repeat,
    radial-gradient(farthest-side at 100% 50%,rgba(20,30,55,.16),rgba(20,30,55,0)) right/13px 100% no-repeat;
  background-attachment:local,local,scroll,scroll;}
/* 상세표의 합계 줄 — 본문과 같은 밀도로 맞춘다.
   기본 `.dtable tfoot td`는 굵기·배경·윗줄만 정하고 크기·여백을 정하지 않는다. 그래서 .detail 안에서는
   본문이 12px·6px 8px인데 합계 줄만 .dtable(13px)을 물려받고 패딩이 0이 됐다 — 같은 표에서 줄마다 크기가 달랐다.
   (목록표 .dtable은 본문이 13px이라 문제가 없어 여태 드러나지 않았다) */
.dtable.detail tfoot td{font-size:12px;padding:6px 8px;text-align:center;}

/* 컴팩트 변형 — 지도와 반씩 나눠 쓰는 좁은 패널의 목록표(주선 배차·내역 등).
   ⚠ 이건 §4-11-B의 "크기 변형 금지"에 어긋나지 않는다. .badge/.ds-act처럼 '같은 자리에 같은 역할로
     쓰이는데 크기만 다른' 경우가 아니라, 표 자체가 다른 밀도의 컴포넌트다(폭이 절반이라 11px이 아니면 안 들어간다).
   이전에는 이 값이 관리자 #arrDispList(admin.html)와 운송사 #list(carrier.html)에 ID 스코프로
   완전히 같은 내용이 2벌 복붙돼 있었다 — 한쪽만 고치면 갈라지는 구조였다. */
.dtable.compact{font-size:11px;table-layout:fixed;}
.dtable.compact thead th{padding:5px 3px;font-size:11px;text-align:center;white-space:nowrap;}
.dtable.compact tbody td{padding:5px 3px;text-align:center;word-break:break-word;overflow-wrap:anywhere;}
.dtable.compact td.r,.dtable.compact th.r{text-align:right;}   /* 금액 열은 우측 유지 */
/* 컴팩트 표 «안»의 보조 문구 칸(배차번호처럼 곁들이는 값) — 2026-09-02.
   .hint는 본문용이라 12px인데, 컴팩트 표 본문은 11px다. 그대로 두면 보조 문구가 본문보다
   «커서» 색만 흐린 큰 글자가 된다 — 곁들이는 값으로 읽히지 않는다.
   여기서는 한 급 작게 낮춘다. 색·줄간격은 .hint 그대로 쓴다.
   ⚠ .dtable(비컴팩트)에는 닿지 않는다 — 그쪽 td.hint(설정 화면 단가표 설명)는 본문 크기가 달라 지금이 맞다. */
.dtable.compact td.hint{font-size:10px;line-height:1.4;}
.dtable.compact .btn-act{padding:2px 6px;font-size:10px;}
/* 컴팩트 표 안의 뱃지 — 컨테이너가 자기 밀도에 맞춘다.
   §4-11-B와 어긋나지 않는다: .badge.sm 같은 '아무 데나 붙이는 크기 변형'이 아니라,
   .dtable.compact라는 한 컨테이너 안에서만 일관되게 적용되는 문맥 규칙이다.
   기본 .badge(24px)를 11px·padding 5px 표에 그대로 쓰면 행이 34px로 부푼다. */
.dtable.compact .badge{height:18px;padding:0 6px;font-size:10px;}
/* 상세표(.dtable.detail) 안의 뱃지 — 컨테이너가 자기 밀도에 맞춘다. 위 .dtable.compact와 같은 문맥 규칙이고
   값도 같게 둔다: 시스템에 뱃지 크기를 24px(기본)·18px(촘촘한 표) 두 가지만 유지한다.
   §4-11-B '크기 변형 금지'에 어긋나지 않는다 — 아무 데나 붙이는 .badge.sm이 아니라
   이 컨테이너 안에서만 일관되게 적용된다. 12px·padding 6px 셀에 24px 뱃지를 쓰면 행이 부푼다.
   적용처: 픽업요청상세·차량주선상세의 차량·일정 표 운송상태 열(2026-08-19). */
.dtable.detail .badge{height:18px;padding:0 6px;font-size:10px;}

/* 우측정렬 셀 — .dtable이 아닌 표에서도 통하게 한다.
   ⚠ 이건 "맨몸 요소 선택자 금지" 규칙의 예외가 아니다. td.r / th.r은 .r 클래스를 명시적으로
     붙인 셀만 잡으므로, input/select처럼 무차별 타격하지 않는다.
   왜 필요한가: 전역 td.r,th.r 규칙이 index.html에만 있고 admin.html·carrier.html에는 없었다.
     그래서 관리자·운송사의 .dtable 아닌 표(admin-master.js·admin-dispatch.js의 나체 table 등)에서
     금액 셀에 붙인 class="r"이 아무 효과 없이 th,td{text-align:center} 기본값으로 떨어졌다.
     같은 클래스가 화면에 따라 동작하기도, 안 하기도 하던 상태 — 조용한 버그였다(2026-08-14 발견). */
td.r,th.r{text-align:right;font-variant-numeric:tabular-nums;}

.empty-state{text-align:center;padding:44px 20px;color:var(--muted);}
.empty-state .ico{font-size:26px;margin-bottom:8px;opacity:.6;display:block;}
.empty-state b{color:var(--ink);}
.loading-state{display:flex;align-items:center;justify-content:center;gap:8px;padding:40px 0;color:var(--muted);font-size:13.5px;}

/* ═══ 5. 도구모음 — 툴바 · 건수 ══════════════════════════════════════ */
.toolbar{display:flex;flex-direction:column;gap:10px;}
.toolbar-row{display:flex;gap:8px;flex-wrap:wrap;align-items:center;}
.toolbar-row+.toolbar-row{padding-top:10px;border-top:1px solid var(--line);}
/* 툴바 버튼 높이 — 같은 줄의 .ds-field(32px)와 높이가 같아야 한다.
   .btn은 line-height를 지정하지 않아 페이지 body(index.html 1.55 등)를 상속한다.
   그래서 .btn.sm 실측이 약 36px가 되어 옆 입력칸보다 4px 커 보였다(2026-08-25).
   §9 .ds-field 주석의 "'.btn.sm과 같은 줄에 놓아도 어긋나지 않음"이 실제로는 성립하지 않았다.
   ⚠ 전역 .btn.sm(앱 전체 339곳)은 건드리지 않는다 — 툴바 밖 버튼의 세로 여백은 그대로여야 한다.
     툴바 안에서만 맞추므로 영향 범위는 .toolbar-row를 쓴 필터 바로 한정된다. */
.toolbar-row .btn.sm,
.toolbar-row .btn-act{height:32px;padding:0 14px;line-height:1;display:inline-flex;align-items:center;justify-content:center;}
/* 툴바 구분선 — 성격이 다른 묶음(기간 이동 / 필터 / 문서 출력)을 가른다.
   ⚠ .toolbar-sep은 클래스만 쓰이고 정의가 없어 호출부가 인라인으로 그리고 있었다(index.html 정산 툴바). */
.toolbar-sep{width:1px;height:22px;background:var(--line);flex:none;}
.count-chip{margin-left:auto;font-size:12.5px;font-weight:700;color:var(--muted);white-space:nowrap;}
.count-chip b{color:var(--blue-d);}

/* ═══ 6. 페이저 ══════════════════════════════════════════════════════
   .hpg / .ahpg 는 고객사 전용 페이저 변형인데 같은 컴포넌트다(index.html·index-orders.js에서만 사용).
   함께 묶어 통일한다. index만 border가 #d8dee7이었던 것도 var(--line)으로 맞췄다. */
.pgbtn,.hpg,.ahpg{min-width:30px;height:30px;border:1px solid var(--line);background:#fff;border-radius:7px;font-size:12.5px;font-weight:700;cursor:pointer;color:#39424f;padding:0 8px;transition:var(--tr);}
.pgbtn:hover:not(.on):not(:disabled),.hpg:hover:not(.on):not(:disabled),.ahpg:hover:not(.on):not(:disabled){border-color:var(--blue);color:var(--blue-d);}
.pgbtn.on,.hpg.on,.ahpg.on{background:var(--blue);color:#fff;border-color:var(--blue);}
.pgbtn:disabled,.hpg:disabled,.ahpg:disabled{opacity:.4;cursor:default;}
.pager{display:flex;justify-content:center;align-items:center;gap:4px;margin-top:14px;flex-wrap:wrap;}

/* ═══ 7. 모달 ════════════════════════════════════════════════════════
   .modal 자체(max-width)는 화면별로 440 / 920 / 1480px으로 갈려 있어 아직 제외. */
.modal-bg{position:fixed;inset:0;background:rgba(11,31,68,.5);display:none;align-items:flex-start;justify-content:center;z-index:1100;padding:40px 16px;overflow-y:auto;}
.modal-bg.on{display:flex;}
.modal-head{display:flex;align-items:center;justify-content:space-between;padding:18px 22px;border-bottom:1px solid var(--line);font-size:16px;font-weight:800;flex:none;}
.modal-x{background:#eef2f7;border:none;border-radius:9px;width:40px;height:40px;flex:none;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;font-weight:800;font-size:17px;line-height:1;}
.modal-x:hover{background:#e2e8f1;}
.modal-body{padding:20px 22px 24px;overflow-y:auto;}

/* 상세 팝업 상단 액션 스트립 (2026-08-25) — 본문 최상단에 sticky로 붙어 상태·핵심수치·주 버튼을
   한자리에 고정한다. 종전에는 상태마다 '지금 할 일'이 다른 위치에 있었다.
   ⚠ 본문의 좌우 padding을 음수 마진으로 상쇄해 폭을 꽉 채운다. 값이 어긋나면 스트립만 안쪽으로
     들어가 보이므로, .modal-body(20px 22px)와 carrier .mbody(16px 18px) 양쪽을 각각 맞춘다.
   ⚠ sticky는 스크롤 컨테이너가 본문일 때만 먹는다 — carrier .mbox도 max-height+flex로 맞춰 뒀다. */
.av-strip{position:sticky;top:-20px;z-index:5;display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  background:#fff;border-bottom:1px solid var(--line);box-shadow:0 3px 10px rgba(11,31,68,.06);
  margin:-20px -22px 16px;padding:10px 22px;}
/* top이 0이 아니라 «본문 상단 padding만큼 음수»다(2026-08-26).
   스트립은 그 padding을 음수 마진으로 상쇄해 정지 위치가 본문 위쪽 경계에 맞춰져 있는데,
   sticky가 붙는 지점을 top:0으로 두면 정지 위치보다 그만큼 아래라서 스크롤을 시작하는 순간
   헤더 선과 스트립 사이에 빈 띠가 생기고 뒤 내용이 지나갔다(사용자 지적).
   top을 -padding으로 맞추면 정지 위치와 붙는 위치가 같아져 띠가 아예 생기지 않는다 —
   흰 띠를 덧대는 방식(::before)은 그 구간을 가리기만 해서 스트립 내용이 아래로 밀려 보였다. */
.mbody>.av-strip{top:-16px;margin:-16px -18px 16px;padding:10px 18px;}
.av-strip-sp{flex:1;min-width:4px;}

/* 추가운임 서브 행 — 차량 행에 종속됨을 들여쓰기와 왼쪽 축으로 표현(2026-08-25).
   금액은 추가운임 열 <td>에 그대로 들어가므로 정렬은 .dtable td.r이 맡는다. */
.dtable tr.av-exsub td{background:#fbfcfe;font-size:11px;padding-top:4px;padding-bottom:4px;}
.dtable tr.av-exsub.pend td{background:#fffdf5;}
.dtable tr.av-exsub td:nth-child(2){border-left:2px solid var(--line);}
.dtable tr.av-exsub.pend td:nth-child(2){border-left-color:#f0dea6;}
.av-strip-acts{display:inline-flex;align-items:center;gap:6px;flex-wrap:wrap;}

/* ═══ 8. 버튼 (5단계 값 통일, 2026-08-14) ════════════════════════════
   이전에는 3화면 3규격이었다:
     admin   14.5px / radius10 / 12·22 / 기본 파랑
     index   14px   / radius10 /  9·16 / 기본 배경 없음(.btn.p라야 파랑)
     carrier 13px   / radius9  /  8·14 / 기본 파랑
   → 관리자 기준으로 통일(사용자 결정). font·padding은 스케일 정렬(설계 문서 §3-3, §4-1).

   ⚠ 기본 배경을 파랑으로 두는 게 핵심이다. index 방식(.btn은 무색, .btn.p만 파랑)은
     .p를 빠뜨리면 조용히 회색 버튼이 나오는 사고 구조다. 실제로 고객사의 bare .btn 4곳
     (설정 저장 2 · 파렛 동의 · 홈 픽업요청 CTA)이 브라우저 기본 회색으로 렌더되고 있었다.
   ⚠ .btn.p는 지우지 않는다 — 고객사 마크업 9곳과 account.js가 아직 .p를 쓴다(하위 호환). */
.btn{font-family:inherit;font-weight:800;font-size:14px;border:none;border-radius:10px;padding:10px 20px;cursor:pointer;background:var(--blue);color:#fff;}
.btn:hover:not(:disabled){filter:brightness(.97);}   /* 변형(.g/.danger)에도 안전하게 먹는 방식 — carrier 기존 규칙 승계 */
.btn:disabled{background:#e5e9f0;color:#9aa4b2;cursor:not-allowed;box-shadow:none;}
.btn.p{background:var(--blue);color:#fff;}
.btn.g{background:#eef2f7;color:#39424f;}
.btn.sm{padding:8px 14px;font-size:13px;}
.btn.xs{padding:3px 9px;font-size:11px;border-radius:6px;font-weight:700;}
.btn.danger{background:#fdecea;color:#c2362d;}

/* 표 행 액션 버튼 — 상태 뱃지와 명확히 구분되는 채움/외곽선 버튼.
   이전에는 관리자(radius8·padding 6·14·그림자)와 운송사(radius6·padding 없음)가 다른 규격이었고,
   고객사에는 정의가 아예 없어 arrange-view.js가 자체 CSS를 주입해 메웠다.
   통일 기준은 관리자(2026-08-14). */
.btn-act{background:var(--blue);color:#fff;border:none;border-radius:var(--radius-md);padding:6px 14px;font-size:12px;font-weight:700;cursor:pointer;font-family:inherit;box-shadow:0 1px 3px rgba(28,111,242,.3);white-space:nowrap;}
.btn-act:hover{background:var(--blue-d);}
.btn-act.ghost{background:#fff;color:var(--blue-d);border:1.5px solid var(--blue);box-shadow:none;}
.btn-act.ghost:hover{background:var(--pale);}
/* 3차 동작(초기화 등) — .btn.g와 같은 회색을 .btn-act 밀도로 쓴다.
   이 변형이 없어서 툴바의 [초기화]가 .btn-act 갈래를 못 쓰고 .btn.g로 떨어져 있었다.
   .ghost(파란 테두리)는 3차 동작에 과하고, 채움 파랑은 더 과하다. */
.btn-act.g{background:#eef2f7;color:#39424f;box-shadow:none;}
.btn-act.g:hover{background:#e2e8f1;}
/* 확인이 끝난 고스트 버튼 — 파랑(할 일)에서 초록(끝난 일)으로만 바뀐다. 모양은 그대로다.
   '확인해야 하는 것'과 '확인한 것'이 같은 자리에서 색으로만 갈린다(2026-08-25). */
.btn-act.ghost.done{color:var(--st-done-fg);border-color:#a8ddbf;}
.btn-act.ghost.done:hover{background:var(--st-done-bg);}
/* 부정·이탈 동작(참여 안 함 등) — 색은 .btn.danger와 같은 체계를 쓰고 밀도만 .btn-act를 따른다.
   크기 변형이 아니라 색 변형이다(.ghost와 같은 층). 종전에는 입찰 바가 인라인 hex로 그렸다(2026-08-19). */
.btn-act.danger{background:var(--st-cancel-bg);color:var(--st-cancel-fg);border:1px solid #f0c2bd;box-shadow:none;}
.btn-act.danger:hover{background:#fbdedb;}
/* 표 행 안에 들어가는 작은 액션 — 한 종류만 둔다.
   종전에는 인라인으로 padding 1·6 ~ 8·16, font 10 ~ 12.5px가 18종 뒤섞여 있었다. */
.btn-act.xs{padding:3px 9px;font-size:11px;border-radius:var(--radius-sm);box-shadow:none;}
/* 비활성 — 값은 위 .btn:disabled와 같다. .btn-act에는 이 상태가 없어서 잠긴 버튼이
   평범한 파란 버튼으로 보였고, 호출부들이 인라인 opacity로 메워 왔다(4곳:
   '🔒 배정' 2곳 · 관리자 주선 처리 버튼 2곳). 그 인라인은 겹쳐도 무해하며 별도 커밋으로 정리한다.
   ⚠ .ghost/.danger는 자기 hover와 border를 따로 갖는다. .btn처럼 :hover:not(:disabled) 한 줄로는
     막을 수 없어(변형마다 hover가 있다) 변형 뒤에 두어 순서로 이긴다 — 특이도는 같다. */
.btn-act:disabled{background:#e5e9f0;color:#9aa4b2;cursor:not-allowed;box-shadow:none;border-color:transparent;}
.btn-act:disabled:hover{background:#e5e9f0;color:#9aa4b2;}

/* 지도 위에 떠 있는 버튼 — 배차 지도의 [전체 경로 표시]·[네이버 길찾기](2026-08-26).
   종전에는 세 화면이 position/top/right/z-index/box-shadow를 버튼마다 인라인으로 복제했고,
   버튼이 둘 이상인 지도(주선 배차·내역)는 같은 자리에 겹쳐 놓을 수밖에 없었다.
   → 컨테이너(.map-fabs)가 자리를 잡고 버튼은 그림자만 얹는다. 색·크기는 .btn 갈래 그대로.
   ⚠ 부모 지도 카드에 position:relative가 있어야 한다(.disp2-map·.arrdisp2-map 모두 갖고 있다). */
.map-fabs{position:absolute;top:16px;right:16px;z-index:10;display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end;}
.map-fab{box-shadow:0 2px 10px rgba(0,0,0,.2);}

/* 탭 알약 — 표 위에서 모집단을 가르는 탭(픽업 내역의 트럭픽업/밀크런 등).
   .stpill(상태 필터)과 역할이 다르다: 이건 표의 '무엇을 보여줄지'를 가르는 탭이고 개수를 함께 낸다.
   종전에는 선택 상태 3색(테두리·배경·글자)을 JS가 인라인으로 계산해 붙였다 — 색을 바꾸려면 JS를 고쳐야 했다. */
.tabchip{padding:5px 14px;font-size:12.5px;font-family:inherit;border:1px solid var(--line);background:#fff;color:var(--ink);border-radius:var(--radius-md);cursor:pointer;font-weight:500;transition:var(--tr);}
.tabchip:hover:not(.on){border-color:#c7d3e6;background:#f7faff;}
.tabchip.on{border-color:var(--blue);background:var(--pale);color:var(--blue-d);font-weight:800;}
.tabchip .n{opacity:.65;}

/* ═══ 9. 카드 · 페이지 헤더 (5단계 값 통일, 2026-08-14) ══════════════
   .card  admin·carrier는 radius16/pad24/mt20으로 이미 같았고 index만 radius12/pad14·16/mt14였다.
          → 관리자 기준으로 통일. 조밀함이 필요한 곳은 .card.tight(= index 기존 값)를 쓴다.
   h1     admin·carrier 23px / index 20px → 23px
   .sub   admin 14px·mt6 / index 13px·mt4 / carrier 13.5px·mt5 → 관리자 값

   ※ .card h2는 통일하지 않았다 — 고객사는 flex + 번호 뱃지(.card h2 .n) 레이아웃을 쓰고
     관리자는 단순 제목이라, 합치면 신청 폼 스텝 헤더가 깨진다. 각 페이지에 그대로 둔다. */
h1{font-size:23px;font-weight:800;letter-spacing:-.02em;margin:0 0 10px;}   /* 아래 여백까지 컴포넌트가 갖는다 — 화면마다 인라인으로 6/10/4/0px이 뒤섞여 있었다(2026-08-18) */
.sub{color:var(--muted);font-size:14px;margin-top:6px;}
.card{background:#fff;border:1px solid var(--line);border-radius:16px;padding:24px;box-shadow:0 8px 24px rgba(20,30,55,.05);margin-top:20px;}
.card.tight{border-radius:12px;padding:14px 16px;box-shadow:0 6px 18px rgba(20,30,55,.05);margin-top:14px;}

/* .hint — 이전 값: admin 12.5px·mt8 / index 12px·mt6 / carrier 12px·margin:4px 0 0·lh1.5
   → 고객사 값 기준으로 통일하고 carrier의 line-height만 살렸다.
   ⚠ 기본 margin-top은 그대로 둔다. 684곳 중 기본값에 의존하는 곳이 79곳 있고,
     margin:0으로 좁히면 그 79곳을 한 곳씩 판단해야 하는데 사용자가 체감하는 개선은 없다.
     인라인 style="margin:0" 196건은 중복이지만 정상 동작하므로 손대지 않는다. */
.hint{font-size:12px;color:var(--muted);margin-top:6px;line-height:1.5;}
.hint b{color:var(--blue-d);}

/* ═══ 11. 폼 컨트롤 (2026-08-14 사용자 요청) ═════════════════════════
   ⚠ 맨몸 요소 선택자(input / select / textarea)를 쓰지 않는다.
     공용 JS 모듈(onboard.js · arrange-view.js · account.js)이 그리는 요소를 무차별 타격한다.
     커밋 368fa11이 정확히 이걸로 터졌다 — 고객사 전역 input 규칙이 온보딩 체크박스에 먹어
     체크박스가 거대한 입력칸으로 렌더됐다. 그래서 전부 클래스 기반이다.

   이전 상태(실측):
     전역 input,select  admin 14.5px/11·13/r10/width:120px · index 14px/8·11/r9/width:100% · carrier 정의 없음
     type=date          5종 · type=month 8종 · type=search 5종으로 갈려 있었다

   높이 체계 — 툴바 안에서 버튼·칩과 나란히 서므로 32px로 맞춘다(.btn.sm과 같은 줄에 놓아도 어긋나지 않음). */
.ds-field{font-family:inherit;font-size:13px;height:32px;padding:0 10px;border:1px solid var(--line);border-radius:var(--radius-md);background:#fff;color:var(--ink);box-sizing:border-box;}
.ds-field:focus{outline:none;border-color:var(--blue);box-shadow:0 0 0 3px rgba(28,111,242,.12);}
.ds-field:disabled{background:#f4f7fb;color:#9aa4b2;cursor:not-allowed;}
.ds-field::placeholder{color:#9aa4b2;}

/* 폭 변형 — 툴바에서 쓰는 세 가지 패턴 */
.ds-field.date{width:150px;}                    /* type=date  — YYYY.MM.DD가 잘리지 않는 최소폭 */
.ds-field.month{width:130px;}                   /* type=month */
.ds-field.search{flex:1;min-width:200px;}       /* 검색 — 남는 폭을 채운다 */
.ds-field.full{width:100%;}                     /* 폼 안의 한 줄 입력 */

/* 숫자 입력 — 자릿수를 눈으로 비교하는 값이라 우측 정렬 + 고정폭 숫자.
   ⚠ 이 클래스는 정의가 없는 채로 이미 12개 파일 71곳이 쓰고 있었다(admin.html 45 · admin-core 6 ·
     admin-master 4 · arrange-view 3 · admin-settle 3 · index.html 4 …). 죽은 클래스였으므로
     호출부들이 style="text-align:right"를 인라인으로 덧붙여 메워 왔다(14곳 — 입찰 금액·pv-amt 등).
     그 인라인들은 여기와 같은 값이라 지금은 무해하다. 제거는 별도 커밋으로 한다.
   나머지 57곳은 이 규칙으로 처음 우측 정렬된다 — 전부 type=number라 의도한 변화다. */
.ds-field.num{text-align:right;font-variant-numeric:tabular-nums;}

/* 폼(툴바가 아닌 입력 영역) — 세로로 쌓이는 라벨+입력 조합. 툴바보다 크게. */
.ds-field.lg{height:40px;font-size:14px;padding:0 12px;border-radius:var(--radius-lg);}
textarea.ds-field{height:auto;padding:9px 10px;line-height:1.5;resize:vertical;}

/* 기간 범위 픽커 — 「📅 픽업일 [from] ~ [to]」를 한 덩어리로 보이게 하는 조합.
   테두리는 바깥 래퍼가 갖고 안쪽 입력은 테두리를 없앤다(.bare).
   포커스 링도 래퍼가 대신 그린다 — 안쪽 두 입력이 각각 링을 그리면 조합이 깨져 보인다. */
.ds-daterange{display:inline-flex;align-items:center;gap:5px;background:#fff;border:1px solid var(--line);border-radius:var(--radius-md);padding:0 9px;height:32px;}
.ds-daterange:focus-within{border-color:var(--blue);box-shadow:0 0 0 3px rgba(28,111,242,.12);}
.ds-field.bare{border:none;background:transparent;height:auto;padding:2px 4px;width:auto;}
.ds-field.bare:focus{box-shadow:none;}

/* ═══ 10. 좌측 사이드바 (2026-08-14 사용자 요청) ═════════════════════
   설계 문서 §8-3은 사이드바를 "범위 외"로 뒀으나 사용자 지시로 해제했다.

   이전 상태: 관리자·고객사는 .sidebar + body{padding-left}, 운송사만 .sb + .main{margin-left}로
   구조 자체가 달랐다. 규격도 갈려 있었다 —
     운송사  컨테이너 padding:14px 0 + 탭 radius 없음 → 활성 탭이 좌우 끝까지 채워진 파란 띠
             (관리자·고객사는 안쪽 여백 + 9px 라운드 알약)
             아이콘 15px(vs 17px) · 탭 색 #c9d6ef(vs #c3cede) · 그룹헤더 #7f93c4(vs #8595ab)
             z-index 50(vs 60) · 토글 hover 없음
     고객사  탭 14px·padding 11px(vs 13.5px·10px)

   ⚠ .tab 클래스명은 절대 바꾸지 말 것.
     admin-orders.js:19 / index-core.js:93이 맨몸 .tab으로 배선하고 URL 라우팅도 이걸 통과한다.
     같은 이유로 본문에 class="tab"을 새로 만들면 사이드바 라우팅이 오작동한다
     (모든 .tab에서 .on이 제거되고 뷰가 전부 display:none 판정을 받는다).
     admin.html에 있던 유령 .tab CSS(흰 알약)를 함께 지운 이유가 이것이다 — 그 이름을 써도
     되는 것처럼 유도했다.

   그룹 헤더만 관리자 값(10.5px)이 아니라 11px을 쓴다 — §3-3 스케일에 10.5px이 없고
   고객사·운송사가 이미 11px이다. */
/* 세로 스크롤바 자리를 항상 비워 둔다 — 내용이 길어져 스크롤바가 생기는 순간 본문 폭이
   17px 줄며 레이아웃이 튀는 것을 막는다(위 .bleed 주석과 같은 건). */
html{scrollbar-gutter:stable;}
/* 사이드바 레이아웃은 «옵트인»이다(2026-08-25). 맨몸 body 규칙이던 것을 클래스로 바꿨다.
   왜: 이 한 줄 때문에 ds.css를 사이드바가 없는 화면(login·driver·인쇄창)에 붙일 수 없었다 —
       붙이는 순간 본문이 222px 밀린다. 그래서 공용 모듈(account.js 등)이 ds 클래스를 쓰려면
       같은 값을 자기 안에 또 정의해야 했고, 값이 갈릴 위험을 안고 갔다.
   ⚠ 사이드바를 쓰는 화면은 <body class="ds-shell">를 달아야 한다(admin·carrier·index). */
body.ds-shell{padding-left:var(--sbw);transition:padding-left .2s;}
/* 본문 컨테이너 여백 — 3화면 공통. 이 값이 곧 페이지 제목(h1)의 시작 위치다.
   이전엔 관리자·운송사 22/24/60px, 고객사만 14/20/20px이라 제목이 8px 위·4px 왼쪽에서 시작했다.
   max-width는 화면별로 다르게 둔다(고객사는 신청 폼 위주라 좁은 편이 읽기 좋다) — 각 페이지에서 지정. */
.wrap{margin:22px auto;padding:0 24px 60px;}

/* 풀블리드 — 사이드바 오른쪽 전체 폭을 쓰는 화면(지도 2단·넓은 표 등).
   .wrap의 좌우 여백을 상쇄해 본문 영역 끝까지 펼친다.

   ⚠ 2026-08-19 — 종전 `width:calc(100vw - var(--sbw)); left:50%; margin-left:calc((--sbw - 100vw)/2)`는
      세로 스크롤바가 생기는 순간 화면이 어긋났다. 100vw는 세로 스크롤바 폭(윈도우 ~17px)을 포함하는데
      실제 본문 폭은 그만큼 좁다. 그래서 스크롤바가 뜨면 .bleed 폭이 17px 넘치고(가로 스크롤바 발생)
      left:50% 보정도 어긋나 본문 전체가 8.5px 왼쪽으로 밀렸다.
      → 픽업·주선 요청에서 대수를 늘리면 차량별 입·하차 시각 필드가 늘어 페이지가 길어지고,
        그 순간 세로 스크롤바가 생겨 "화면 배율이 달라지는" 것처럼 보였다(숫자를 지우면 되돌아옴).
      .wrap은 .bleed를 쓰는 3화면(고객사·관리자·운송사) 모두 max-width가 없으므로 vw 없이
      좌우 여백(-24px)만 상쇄하면 같은 폭이 되고, 스크롤바 유무와 무관해진다.
   ⚠ 이전에는 이 관용구가 화면마다 복붙돼 있었고 padding이 18 / 10 / 10 / 14 / 14px로 제각각이라
      "페이지마다 좌우 여백이 조금씩 다른" 원인이었다(2026-08-18 통일).
   좌우 여백은 .wrap과 같은 24px — 풀블리드여도 본문 시작선은 다른 화면과 맞아야 한다. */
.bleed{width:auto;max-width:none;position:relative;margin-left:-24px;margin-right:-24px;padding:0 24px;box-sizing:border-box;}
@media(max-width:820px){ .bleed{margin-left:-14px;margin-right:-14px;padding:0 14px;} }
@media(max-width:820px){ .wrap{margin:16px auto;padding:0 14px 50px;} }
.sidebar{position:fixed;left:0;top:0;bottom:0;width:var(--sbw);background:var(--navy);color:#fff;display:flex;flex-direction:column;padding:14px 10px;gap:3px;z-index:60;transition:width .2s;overflow-x:hidden;overflow-y:auto;}
.sidebar .brand{font-weight:800;font-size:16px;display:flex;align-items:center;gap:9px;padding:6px 8px 4px;white-space:nowrap;}
/* 브랜드 = 공식 워드마크 이미지 하나(2026-08-27). 종전에는 favicon 사각 아이콘 + "품고 그라운드" 텍스트를
   손으로 붙여 만든 조합이었다 — 로고가 바뀌면 화면마다 따로 손봐야 했다.
   사이드바 바탕이 --navy(어두움)이라 흰색 버전을 쓴다. 밝은 배경은 ground-logo.png.
   너비 계산: 사이드바 222 - 좌우 패딩 20 - .brand 패딩 16 - 토글 26 - gap 9 ≒ 151px 가용.
   height 20px면 원본 비율(7.21:1)로 144px — 가용폭에 여유를 두려고 21px(=151px, 정확히 꽉 참)에서 한 칸 줄였다.
   접혔을 때(60px)는 통째로 숨긴다 — 종전 아이콘+텍스트와 같은 동작이다. */
.sidebar .brand .logo{height:20px;width:auto;max-width:100%;min-width:0;flex:0 1 auto;display:block;object-fit:contain;object-position:left center;}
.sidebar .sb-tag{font-size:11px;color:#aebbd0;padding:0 9px 10px;white-space:nowrap;}
.sidebar .tab{display:flex;align-items:center;gap:11px;width:100%;justify-content:flex-start;background:transparent;border:none;color:#c3cede;font-weight:700;font-size:13.5px;padding:10px;border-radius:9px;cursor:pointer;font-family:inherit;white-space:nowrap;text-decoration:none;}
.sidebar .tab:hover{background:rgba(255,255,255,.08);color:#fff;}
.sidebar .tab.on{background:var(--blue);color:#fff;}
.sidebar .tab .ico{font-size:17px;flex:none;width:22px;text-align:center;}
.sidebar .sb-sp{flex:1;min-height:10px;}
.sidebar .sb-div{height:1px;background:rgba(255,255,255,.12);margin:8px 6px;flex:none;}
.sidebar .sb-group{font-size:11px;font-weight:800;color:#8595ab;letter-spacing:.03em;padding:12px 9px 4px;white-space:nowrap;}
.sidebar .sb-group:first-of-type{padding-top:2px;}
.sidebar .brand .sbToggle{margin-left:auto;flex:none;width:26px;height:26px;padding:0;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.1);border:none;color:#fff;border-radius:8px;cursor:pointer;font-size:17px;font-weight:800;line-height:1;}
.sidebar .brand .sbToggle:hover{background:rgba(255,255,255,.2);}
/* 접힘(60px 레일) */
body.sb-collapsed .sidebar .sb-group{color:transparent;font-size:0;padding:7px 8px 6px;}
body.sb-collapsed .sidebar .sb-group::after{content:'';display:block;height:1px;background:rgba(255,255,255,.14);}
body.sb-collapsed .sidebar .brand{justify-content:center;padding-left:0;padding-right:0;}
body.sb-collapsed .sidebar .brand .logo{display:none;}
body.sb-collapsed .sidebar .brand .sbToggle{margin-left:0;}
body.sb-collapsed .sidebar .tab .lbl,body.sb-collapsed .sidebar .sb-tag{display:none;}
body.sb-collapsed .sidebar .tab{justify-content:center;gap:0;padding:10px 0;}
/* 좁은 화면 — 항상 레일. body:not(.sb-collapsed) 방어가 없으면 ds.css의 60px가 이겨 56px이 안 먹는다
   (고객사에만 이 방어가 빠져 있어 모바일 접힘 폭이 4px 달랐다) */
@media(max-width:820px){
  :root{--sbw:56px;} body:not(.sb-collapsed){--sbw:56px;}
  .sidebar .tab .lbl,.sidebar .sb-tag{display:none;}
  .sidebar .tab{justify-content:center;gap:0;padding:10px 0;}
  .sidebar .brand{justify-content:center;padding-left:0;padding-right:0;}
  .sidebar .brand .logo{display:none;}
  .sidebar .brand .sbToggle{margin-left:0;}
  .sidebar .sb-group{color:transparent;font-size:0;padding:7px 8px 6px;}
  .sidebar .sb-group::after{content:'';display:block;height:1px;background:rgba(255,255,255,.14);}
}

/* ═══ 11-B. 액션 셀렉트 (2026-08-14 사용자 요청) ═════════════════════
   "고르면 즉시 실행"되는 표 안 처리 드롭다운. 조회·입력용 .ds-field와 반드시 구분돼야 한다.

   이전 상태: 액션 셀렉트 14곳이 8종 규격 / 6종 폭으로 갈려 있었다.
   그중 6곳(.ad-act .cs-act .acc-act .da-act .dd-act .tk-leg-act)은 **폭을 아무도 정한 적이 없고**
   호스트 페이지 전역 규칙에서 우발적으로 흘러든 값이었다:
     admin.html   input,select{width:120px}
     index.html   input,select{width:100%}
     carrier.html 전역 규칙 없음
   → 같은 .tk-leg-act(트럭픽업 상세 모달)가 관리자에선 120px, 운송사에선 auto로 렌더됐다.

   ⚠ 파란 테두리가 이번 통합의 핵심 판정이다.
     지금까지 대부분이 var(--line) 회색이라 조회용 필터 셀렉트와 눈으로 구분되지 않았다.
     "고르면 즉시 실행"되는 위험한 컨트롤이 조회 컨트롤과 같은 모습인 건 오조작 구조다.

   높이 26px: .dtable tbody td(9·12) + .badge(24px) = 행 42px을 흔들지 않는다.
              32px로 하면 50px로 8px 부푼다. 짝 버튼(admin-dispatch.js)이 이미 26px이다.
   폭 84px:   58px은 관리자가 실사용 중 84px로 늘린 이력이 있다(부족하다는 증거).
              width:auto는 못 쓴다 — select의 auto 폭은 가장 긴 option에 맞춰져 셀이 폭발한다. */
.ds-act{font-family:inherit;font-size:11px;font-weight:700;height:26px;width:84px;padding:0 4px 0 8px;border:1px solid var(--blue);border-radius:var(--radius-sm);background:#fff;color:var(--blue-d);cursor:pointer;box-sizing:border-box;}
.ds-act:hover:not(:disabled){background:var(--pale);}
.ds-act:focus{outline:none;border-color:var(--blue);box-shadow:0 0 0 3px rgba(28,111,242,.12);}
.ds-act:disabled{border-color:var(--line);color:#9aa4b2;background:#f4f7fb;cursor:not-allowed;}
/* ⚠ 컴팩트 변형(.sm)은 두지 않는다. 표 밀도에 맞춘다는 이유로 크기를 나누자,
   같은 "처리" 드롭다운이 픽업 배차·내역(26px)과 주선 배차·내역(22px)에서 다르게 보였다.
   한 종류로 못 박는다 — 좁은 컬럼은 셀 폭을 넓히거나 라벨을 줄여 대응한다(2026-08-14 사용자 지시). */

/* 체크박스·라디오 — 크기와 강조색을 한 곳에서(2026-08-18 신설).
   왜: style="width:16px;height:16px;accent-color:var(--blue);cursor:pointer;" 동일 문자열이 23회
   복사돼 있었고, account.js만 15px + hex로 갈려 있었다. accent-color는 41곳에 흩어져 있었다.
   .ds-check는 라벨과 함께 쓰는 것을 전제로 한다 — <label class="ds-checkrow"><input class="ds-check"> …
   ⚠ 맨몸 input[type=checkbox] 선택자를 쓰지 않았다. 페이지 전역 input 규칙(관리자 w120px)과
     충돌하면 온보딩 체크박스가 거대 입력칸이 되는 사고가 재발한다(커밋 368fa11). */
.ds-check{width:16px;height:16px;accent-color:var(--blue);cursor:pointer;flex:none;}
.ds-checkrow{display:inline-flex;align-items:center;gap:8px;cursor:pointer;font-size:14px;font-weight:700;}

/* 토스트 — 사라지는 결과 알림(2026-08-18 3화면 통합).
   종전에는 3규격이었다:
     관리자 상단우측 · 기본이 초록(#13854a) · warn은 주황
     고객사 상단우측 · 기본이 다크 · 스택 가능
     운송사 하단중앙 · 네이비 · CSS 없이 cssText로 즉석 생성
   위치는 상단우측으로 통일한다 — 관리자·고객사가 이미 쓰던 자리이고, 하단중앙은 모바일에서
   하단 버튼을 가린다.
   색은 의미로 나눈다(사용자 결정): 성공=초록 · 실패/경고=붉은색 · 일반 안내=검은색 계열.
   ⚠ 관리자의 기본색이 초록이었다 → 검은색으로 바뀐다. 타입 없이 호출한 338건이 여기 해당한다.
     초록을 성공에만 쓰도록 좁힌 것이다(종전엔 '변경 실패' 같은 문구도 초록으로 떴다).
   ⚠ warn과 err를 같은 붉은색으로 둔다. 지시가 "실패나 경고는 붉은색"이고, 둘을 미세하게
     다르게 하려면 토큰에 없는 새 색을 만들어야 한다 — 그건 시스템 밖으로 나가는 일이다.
   ⚠ 배너(.banner)와 역할이 다르다: 배너는 화면에 머무는 설명, 토스트는 사라지는 결과. */
.toast-wrap{position:fixed;top:74px;right:18px;z-index:3000;display:flex;flex-direction:column;
  gap:8px;align-items:flex-end;pointer-events:none;}
.toast{background:var(--ink);color:#fff;font-weight:600;font-size:13.5px;padding:11px 15px;
  border-radius:var(--radius-lg);box-shadow:0 8px 24px rgba(0,0,0,.22);max-width:340px;
  opacity:0;transform:translateX(20px);transition:opacity .25s,transform .25s;pointer-events:auto;
  word-break:break-word;white-space:pre-line;}
.toast.show{opacity:1;transform:none;}
.toast.ok{background:var(--st-prog2-fg);}
.toast.err{background:var(--st-cancel-fg);}
/* 경고는 실패와 다른 색이다(2026-08-19 재결정).
   "실패나 경고는 붉은색"으로 묶었다가 구분 요청을 받았다. 새 hex를 만들지 않고 --st-pick-fg(주황)를
   쓴다 — 종전 관리자 warn(#b8730a)에 가장 가까운 기존 토큰이고, --st-cancel-fg(붉은색)와
   나란히 놓아도 구분된다. 실패=붉은색 / 주의=주황 / 성공=초록 / 일반=검은색 네 단계가 된다. */
.toast.warn{background:var(--st-pick-fg);}

/* ═══ 11-B. 탭 — 계층별로 모양이 달라야 깊이가 읽힌다(2026-08-18 승격) ═════
     L1 서비스 전환  = 채운 알약(.setg-svcbtn, 관리자 설정 전용)
     L2 화면 내 구획 = 밑줄형(.ds-tab)          ← 여기
     L3 행 내부 탭   = 작은 테두리 알약(.arrvl-tab)
   종전에는 L2에 밑줄형(.pc-subtab)과 알약형(.setg-tab) 두 규격이 공존해 L1·L3와 모양이 겹쳤다.
   밑줄형이 "같은 화면 안의 하위 구획"을 더 잘 나타내므로 그쪽으로 통일한다.
   ⚠ 클래스명에 'tab' 단독을 쓰면 안 된다 — admin-orders.js·index-core.js가 맨몸 .tab으로
     뷰 라우팅을 배선한다. .ds-tab은 접두가 있어 안전하다.
   .pc-subtab / .pc-subtabs는 기존 마크업 호환을 위해 같은 선언을 공유한다(값 동일). */
.ds-tabs,.pc-subtabs,.setg-tabwrap{display:flex;gap:6px;margin:12px 0 14px;border-bottom:1px solid var(--line);flex-wrap:wrap;}
.ds-tab,.pc-subtab,.setg-tab{background:transparent;border:none;border-bottom:2px solid transparent;color:var(--muted);
  font-weight:700;font-size:14px;padding:9px 14px;cursor:pointer;font-family:inherit;margin-bottom:-1px;
  transition:var(--tr);}
.ds-tab:hover:not(.on),.pc-subtab:hover:not(.on),.setg-tab:hover:not(.on){color:var(--ink);}
.ds-tab.on,.pc-subtab.on,.setg-tab.on{color:var(--blue-d);border-bottom-color:var(--blue);}

/* ═══ 11-C. 진행 스텝바 ══════════════════════════════════════════════
   요청이 어느 단계인지 보여주는 칩 줄(배차완료 › 픽업완료 › 입고완료 등).
   이전에는 이 렌더 함수가 4벌 복붙돼 있었고(문자 단위 동일), 색도 hex에 33/55 알파를 붙여
   계산했다. 그래서 한 곳만 고치면 나머지 3화면이 갈라졌다
   — 실제로 픽업측 첫 단계만 "배차 필요"/빨강, 주선측은 "배차 대기"/노랑으로 갈라져 있었다.
   색은 --st-* 토큰을 --c로 받아 쓴다. 단계별 시맨틱 클래스만 붙이면 된다. */
/* flex-wrap — 칩에 flex:none을 준 뒤로는 좁은 폭에서 눌리지 않고 넘친다.
   넘쳐서 잘리는 것보다 줄바꿈이 낫다(모바일·좁은 팝업 헤더). */
.stepbar{display:inline-flex;align-items:center;flex-wrap:wrap;gap:3px;margin-left:8px;vertical-align:middle;}
.stepbar .sep{color:#c3cede;font-weight:800;margin:0 1px;}
/* flex:none — .stepbar가 inline-flex라 칩이 flex 항목이 된다. 기본 flex-shrink:1이면
   헤더가 좁을 때(긴급 플래그가 낀 경우 등) 칩이 콘텐츠 폭 아래로 눌려 좌우 padding이 찌그러진다.
   white-space:nowrap은 글자 줄바꿈만 막고 박스 압축은 막지 못한다(2026-08-19). */
.stepchip{--c:var(--st-neutral-fg);display:inline-block;flex:none;border-radius:var(--radius-pill);white-space:nowrap;}
.stepchip.wait{--c:var(--st-wait-fg);}   .stepchip.info{--c:var(--st-info-fg);}
.stepchip.info2{--c:var(--st-info2-fg);} .stepchip.pick{--c:var(--st-pick-fg);}
.stepchip.done{--c:var(--st-done-fg);}   .stepchip.ship{--c:var(--st-ship-fg);}
.stepchip.prog2{--c:var(--st-prog2-fg);}
/* prog·purple·cancel이 빠져 있었다 — 정의가 없으면 --c가 기본값(회색 neutral)으로 떨어진다.
   주선 진행 스텝바의 '수락'(purple)이 회색으로 나오고 있었고, 관리자 정산 단계 바도 같은 문제였다(2026-08-24). */
.stepchip.prog{--c:var(--st-prog-fg);}   .stepchip.purple{--c:var(--st-purple-fg);}
.stepchip.cancel{--c:var(--st-cancel-fg);}
.stepchip.cur{padding:4px 13px;font-size:12.5px;font-weight:800;background:var(--c);color:#fff;box-shadow:0 0 0 2.5px rgba(0,0,0,.06),0 2px 7px rgba(0,0,0,.12);}
.stepchip.past{padding:3px 9px;font-size:11px;font-weight:700;background:#fff;border:1px solid var(--c);color:var(--c);opacity:.75;}
.stepchip.next{padding:3px 9px;font-size:11px;font-weight:600;background:var(--st-neutral-bg);color:var(--st-neutral-fg);}

/* ═══ 12. 애니메이션 ═════════════════════════════════════════════════ */
@keyframes spin{to{transform:rotate(360deg);}}

/* ═══ 13. 운송사 사각지대 복구 (2단계, 2026-08-14) ═══════════════════
   carrier.html은 carrier-disp-shim.js를 통해 admin-dispatch.js의 렌더를 그대로 재사용하는데,
   아래 클래스들은 admin.html에만 정의돼 있고 carrier.html에는 한 줄도 없었다.
   그래서 운송사 화면에서만 조용히 깨져 있었다:
     .trans-row.on  → 목록 행을 클릭해도 선택 강조가 보이지 않음 (admin-dispatch.js:1508,1561,1594)
     (.selbtn은 2026-08-14 .ds-act로 흡수돼 정의를 제거했다)
     .calcbox       → 원가 계산 박스가 무스타일 (admin-dispatch.js:938)
     .arr-dd-2col   → 배차 상세 모달의 2단 그리드가 1단으로 흐름 (admin-dispatch.js:1380)
   값은 admin.html(106·111·218-220·237행) 원문 그대로다.
   → 관리자는 같은 규칙을 자기 <style>에도 갖고 있어 변화 없음. 운송사만 정상화된다. */
.calcbox{margin-top:12px;background:#f7f9fc;border:1px dashed #cfe0fb;border-radius:8px;padding:12px;text-align:center;color:#4a5568;font-size:13px;font-weight:600;}
/* ⚠ 자식에 min-width:0이 없으면 그리드 칸이 «내용 폭 아래로» 줄지 못한다.
   차량·일정 표처럼 열이 많은 표가 들어오면 왼쪽 칸이 제 폭을 넘겨 오른쪽(운송 로그) 위로 넘어간다
   — 표를 감싼 overflow-x:auto도 그때는 소용이 없다(부모가 이미 커져 버린다, 2026-08-26 사용자 지적). */
.arr-dd-2col{display:grid;grid-template-columns:1.7fr 1fr;gap:8px 28px;align-items:start;}
.arr-dd-2col>*{min-width:0;}
.arr-dd-2col.eq{grid-template-columns:1fr 1fr;}
@media (max-width:820px){ .arr-dd-2col,.arr-dd-2col.eq{grid-template-columns:1fr;} }
#transList tr.trans-row.on{background:#eaf1ff;}
#transList tr.trans-row.on td{border-color:#cfe0fb;}
