/* db_components.css — 페이지 경계를 넘는 공용 db-* 컴포넌트 정본.
   2026-07-28 properties_grid.css 에서 이관(센서스 ② 후속) — 규칙 내용·내부 순서는 원본 그대로.
   교차 사용 실측: db-select*(clients·properties·listings·presentations·shared/_db_select),
   db-input(13파일), db-btn*(12파일·db_confirm.js), db-confirm-modal*(db_confirm.js 전역),
   db-action-btn(15파일·shared/_action_button).
   ⚠️ unlayered 시트 — Tailwind utilities(layered)를 항상 이긴다. @layer 로 감싸지 말 것(승부 뒤집힘). */

/* 커스텀 셀렉트 (button + panel) */
.db-select-wrap { position: relative; display: inline-block; }
/* native <select> 숨김 — JS 미연결/캐시 시에도 커스텀 버튼과 이중 렌더 방지(값은 유지) */
.db-select-wrap > select[data-db-select-target="native"] { display: none; }
.db-select-button {
  /* width:100% 는 shared/_db_select.html.erb 가 인라인으로 주던 것(2026-08-05 회수).
     그 파셜이 이 클래스의 유일한 생성처라(JS 생성 0건) 정의에 흡수해도 동등하다. */
  width: 100%;
  display: flex; align-items: center; gap: 6px;
  /* 높이 = 공용 검색바 인풋(height:32px)과 동일 정렬. box-sizing:border-box 로 보더 포함 32px 고정. */
  height: 32px; box-sizing: border-box;
  width: 100%; padding: 0 30px 0 10px;
  background-color: var(--db-color-card);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none' stroke='%2370737C' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'><polyline points='2.5,4.5 6,8 9.5,4.5'/></svg>");
  background-repeat: no-repeat; background-position: right 10px center; background-size: 12px;
  border: 1px solid var(--db-color-outline);
  border-radius: var(--db-radius-input);
  font: var(--db-text-label-2); color: var(--db-color-text);
  text-align: left; cursor: pointer; outline: none;
  transition: border-color var(--db-duration-fast), box-shadow var(--db-duration-fast);
}
.db-select-wrap .db-select-button:hover { background-color: var(--db-color-fill-medium); }
.db-select-button:focus,
.db-select--open .db-select-button { border-color: var(--db-color-outline); }
.db-select-panel {
  position: absolute; top: calc(100% + 4px); left: 0;
  min-width: 100%; max-height: 280px; overflow-y: auto; overflow-x: hidden;
  /* 스크롤 체이닝 차단 — 패널이 사이드바(overflow-y:auto) 자식이라, 없으면 휠이 사이드바로 새어
     스크롤/reposition churn 으로 패널이 닫힘. contain 으로 내부 스크롤에 가둠. */
  overscroll-behavior: contain;
  /* 스크롤바 숨김 — 스크롤 기능(overflow-y:auto)은 유지, 바만 감춘다.
     (닫힘 버그는 lib/single_dropdown.js 스크롤 리스너 가드로 해결됨 — 스크롤바 유무와 무관.) */
  scrollbar-width: none; -ms-overflow-style: none;
  background: var(--db-color-card); border: 1px solid var(--db-color-outline);
  border-radius: var(--db-radius-input); box-shadow: var(--db-shadow-overlay);
  padding: 4px; z-index: 50; box-sizing: border-box;
}
.db-select-panel::-webkit-scrollbar { display: none; }
.db-select__option {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 10px; border-radius: var(--db-radius-xs);
  font: var(--db-text-label-2); color: var(--db-color-text);
  cursor: pointer; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.db-select__option:hover { background: var(--db-color-fill-subtle); }
.db-select__option--selected { color: var(--db-color-primary); font-weight: 600; }
.db-select__option--selected::after { content: "✓"; margin-left: auto; color: var(--db-color-primary); }

/* 콤보박스(combo_controller) — 위 db-select 껍데기를 그대로 쓰고 아래 3개만 더한다.
   .db-select-input : 버튼 자리를 <input> 으로 바꿀 때. .db-select-button 의 display:flex 를
                      되돌려야 한다(input 에 flex 를 걸면 커서/placeholder 정렬이 깨진다).
   .db-select__group: 그룹 헤더(가족/회사/그 외/직접 추가). datalist 로는 못 만드는 바로 그것.
   .db-select__hint : "외 N건 더 입력해 좁히세요" 같은 안내 — 클릭 대상이 아니다. */
.db-select-input { display: block; }
.db-select-input::placeholder { color: var(--db-color-text-muted, #9ca3af); }
.db-select__group {
  padding: 6px 10px 2px; margin-top: 2px;
  font: var(--db-text-label-2); font-size: 10px; font-weight: 700;
  letter-spacing: .04em; color: var(--db-color-text-muted, #9ca3af);
  text-transform: none; user-select: none;
}
.db-select__group:first-child { margin-top: 0; }
.db-select__hint {
  padding: 6px 10px; font: var(--db-text-label-2); font-size: 11px;
  color: var(--db-color-text-muted, #9ca3af); cursor: default; user-select: none;
}
/* 키보드(↑↓) 이동 표시 — hover 와 같은 톤. 선택 확정(--selected)과는 다른 상태다. */
.db-select__option--active { background: var(--db-color-fill-subtle); }

/* 칩 모드(combo 의 chips 값) — 관계·직책처럼 2~3글자짜리 어휘를 알약으로 펼친다.
   모바일 고객폼(daesin-mobile-v2 clients/_relation_type_chips)과 같은 모양이다.
   ⚠️ 색(배경/글자)은 여기 없다 — 그룹색이라 combo_controller 가 lib/badge_colors 에서
      읽어 인라인으로 얹는다. 색 정본이 CSS 와 JS 두 곳에 살면 한쪽만 바뀐다.
   ⚠️ 순수 CSS 여야 한다 — 이 패널을 쓰는 화면 하나(dsv2_properties/consult_window)가
      `layout:false` 독립 팝업이라 tailwind.css 를 안 싣는다(shared_relation.css 머리주석). */
.db-select-panel--chips { padding: 6px; }
.db-select__chips {
  display: flex; flex-wrap: wrap; gap: 6px; padding: 2px 4px 4px;
}
.db-select__chip {
  padding: 4px 10px; border-radius: 999px;
  font: var(--db-text-label-2); font-size: 13px; font-weight: 600; line-height: 1.25;
  cursor: pointer; user-select: none; white-space: nowrap;
}
.db-select__chip:hover { filter: brightness(0.96); }
/* 지금 값으로 확정된 칩. 목록판(--selected)은 ✓ 를 뒤에 붙이지만 칩은 폭이 흔들리면
   줄바꿈이 통째로 밀리므로 테두리로만 표시한다. */
.db-select__chip--selected { box-shadow: inset 0 0 0 2px currentColor; }
/* 키보드(↑↓) 이동 표시. _move 는 칩·목록 구분 없이 같은 클래스를 토글한다 —
   그래서 여기서 칩 모양에 맞는 표시로 덮어쓴다(컨트롤러에 분기를 늘리지 않으려고). */
.db-select__chip.db-select__option--active {
  outline: 2px solid var(--db-color-primary); outline-offset: 1px;
}

/* 공용 인풋 (텍스트/숫자/textarea) — 셀렉박스(.db-select-button)와 동일 토큰·패딩·높이.
   정본 1곳: 폼 인풋은 이 클래스만 쓴다. (인라인 스타일 금지) */
.db-input {
  width: 100%; box-sizing: border-box; margin-top: 2px;
  padding: 6px 10px;
  border: 1px solid var(--db-color-outline);
  border-radius: var(--db-radius-input);
  /* 폼 컨트롤은 font-family 를 상속 안 함 — Tailwind preflight 없는 화면(독립 팝업 등)에서
     UA 기본(textarea=monospace)으로 떨어지는 것 방지. 공용 컴포넌트 자립. */
  font-family: inherit;
  font-size: 12px; font-weight: 500; line-height: 1.385; /* 셀렉박스 line-height 와 동일 → 높이 일치 */
  color: var(--db-color-text);
  background-color: var(--db-color-card);
  outline: none;
  transition: border-color var(--db-duration-fast);
}
/* 인풋 높이 = 셀렉박스(.db-select-button)와 동일 32px 정렬. textarea 는 제외(여러 줄 높이 보존). */
input.db-input, select.db-input { height: 32px; }
.db-input::placeholder { color: var(--db-color-text-sub); }
/* 클릭(포커스) 시 파란 아웃라인/보더 하이라이트 제거 — 기본 보더색 유지 */
.db-input:focus {
  outline: none;
  box-shadow: none;
  border-color: var(--db-color-outline);
}
/* 잠긴 칸 — "볼 수는 있지만 못 고친다"가 눌러보기 전에 보여야 한다.
   2026-08-21 신설: 종전엔 :disabled 시각이 없어 **활성 칸과 똑같이 보이는데 안 눌리는**
   상태였다(실측: 이 저장소에서 db-input+disabled 사용처 0건 — 그래서 아무도 안 밟았다).
   매물등록 연결고객의 "이미 등록된 관계"가 이 시각을 처음 쓴다.
   readonly 가 아니라 disabled 를 전제로 한다 — readonly 는 값이 폼으로 전송되지만
   disabled 는 HTML 표준상 제외되고, 읽기전용 표시에 필요한 건 후자다. */
.db-input:disabled,
.db-select-button:disabled {
  /* 관계 위젯의 반쪽 배경(shared_relation.css .rel-half)과 같은 값 — 잠긴 칸이 그 위에
     얹혀도 한 덩어리로 읽힌다. 토큰 이름을 지어내지 말 것: db_tokens.css 에
     surface-tertiary 는 **없다**(실측 2026-08-21). 폴백값을 함께 적는 것도 그래서다. */
  background-color: var(--md-sys-color-surface-container, #f1f3f4);
  color: var(--db-color-text-sub);
  cursor: not-allowed;
}
/* 잠긴 콤보는 펼칠 수 없으므로 화살표를 지운다 — 남겨두면 "누르면 열린다"는 거짓 신호다.
   hover 배경도 끈다(:hover 규칙이 disabled 를 안 가려서 손을 올리면 색이 바뀐다). */
.db-select-button:disabled { background-image: none; }
.db-select-wrap .db-select-button:disabled:hover {
  background-color: var(--md-sys-color-surface-container, #f1f3f4);
}

/* --strong : 테두리를 **진하게**만 한다. --search : 거기에 상단 바 한 줄 배치를 더한다.
   ① 테두리를 진하게(두 변형 공통). 흰 카드 위에 버튼·페이저와 나란히 선 자리에서 기본
      --db-color-outline(회색 22% 불투명도)은 "여기가 입력칸"이라는 신호가 너무 약하다.
      회장님이 법인고객·공단입주업체 목록에서 **입력칸이 어디인지 안 보인다**고 지목한
      자리다(2026-08-18). 색은 새로 만들지 않고 이미 있는 --db-color-text-sub(#70737C,
      플레이스홀더와 같은 값)를 테두리로 올려 쓴다. 폭은 1px 그대로다 — 굵히면 같은 줄의
      셀렉트·버튼과 획 두께가 어긋난다.
   ② (--search 만) 본체의 «라벨 아래 full-width 폼칸» 기본값을 푼다(.db-addr-input · .rel-input-flush 와 같은 손질).
      법인고객 **등록/수정 폼**은 정확히 그 «라벨 아래 full-width» 배치라 ②가 필요 없다 —
      그래서 --strong 으로 갈라 뒀다. 폼칸 16개가 같은 병(유령 클래스 `ds-input`)이었는데
      거기에 --search 를 그대로 붙이면 16칸이 전부 16rem 로 굳어 2열 그리드가 깨진다.
      ⚠️ 폭을 Tailwind `w-48` 로 줄 수 없다 — 이 시트가 tailwind 뒤에 실려서 특이도가 같은
         `.db-input { width:100% }` 가 유틸리티를 이긴다(layouts/application 적재 순서).
         그래서 폭은 **컴포넌트 쪽에서** 준다. 두 화면이 같은 폭을 쓴다(플레이스홀더 길이는
         다르지만 16rem 안에 둘 다 들어가고, 나란히 놓인 화면끼리 칸 폭이 다를 이유가 없다).
   포커스 때만 주 파랑으로 바뀐다 — .db-input:focus 의 «보더색 유지»를 이 변형에서만 뒤집는다.
   진한 테두리를 이미 달고 있으니 "지금 어디를 치고 있나"를 색으로 한 번 더 말해줄 여지가 생긴다. */
.db-input--strong,
.db-input--search { border-color: var(--db-color-text-sub); }
.db-input--strong::placeholder,
.db-input--search::placeholder { color: var(--db-color-text-sub); }
.db-input--strong:focus,
.db-input--search:focus { border-color: var(--db-color-primary); }
/* --search 만 추가로 갖는 것 = ② 의 한 줄 배치. --strong 은 폼칸(라벨 아래 full-width)이라 건드리지 않는다. */
.db-input--search { width: 16rem; margin-top: 0; }

/* 버튼 (검색바 검색/초기화 등) */
.db-btn {
  height: 32px; box-sizing: border-box; padding: 0 10px;
  border-radius: var(--db-radius-btn); font: var(--db-text-label-2); font-family: inherit;
  cursor: pointer; border: 1px solid transparent; white-space: nowrap;
  display: inline-flex; align-items: center; justify-content: center;
  -webkit-appearance: none; appearance: none; text-decoration: none; line-height: 1;
}
.db-btn-primary {
  background: var(--db-color-primary); color: var(--db-color-on-primary);
  border-color: var(--db-color-primary);
}
.db-btn-primary:hover { background: var(--db-color-primary-hover); border-color: var(--db-color-primary-hover); }
.db-btn-primary:active { background: var(--db-color-primary-pressed); border-color: var(--db-color-primary-pressed); }
.db-btn-secondary { background: var(--db-color-fill-light); color: var(--db-color-text-sub); text-decoration: none; }
.db-btn-secondary:hover { background-color: var(--db-color-fill-medium); }
/* 중립(진회색 solid) 변형 — 검색바 "문자·카톡" 버튼의 룩이 정본이다(조이 지목 2026-08-03).
   그 호출부는 raw hex(#6b7280)를 인라인으로 박고 있는데, 같은 값의 토큰
   --db-color-action-neutral 이 이미 있다(db_tokens.css:33, .db-action-btn 중립 변형용).
   새로 쓰는 자리는 토큰을 쓴다 — 색 정의가 hex 로 흩어지지 않게. 기존 인라인 2곳은 이번 범위 밖.
   hover 는 .db-action-btn:hover 관례(brightness 0.92)를 그대로 따른다 — solid 버튼끼리 톤 통일. */
.db-btn-neutral {
  background: var(--db-color-action-neutral); color: var(--db-color-on-primary);
  border-color: var(--db-color-action-neutral); transition: filter 0.12s;
}
.db-btn-neutral:hover { filter: brightness(0.92); }
/* --xs : 글줄 안에 끼워 넣는 버튼 (고객 상세 헤더의 [관계도], 조이 확정 2026-08-03).
   높이 22px 의 근거 = 옆에 선 이름 <h2 text-[18px] leading-tight> 의 줄높이(18×1.25≈22.5px).
   기본 32px 로 두면 이름 줄이 버튼 높이만큼 벌어져 헤더가 한 줄 더 두꺼워진다.
   같은 줄의 .db-badge--sm(≈16px)보다는 6px 크다 — 뱃지(정보)와 버튼(액션)이 안 헷갈릴 만큼의 차이다.
   **높이만** 줄인다. font(label-2 = 12px/1.385 ≈ 16.6px)는 건드리지 않는다 — 22px 안에 들어가고,
   글자까지 줄이면 베껴온 버튼과 다른 물건이 된다("디자인은 그대로, 높이만" 조이 확정).
   .db-btn 본체를 줄이지 않은 이유: 검색바 "고객 등록"·메모 "등록"까지 같이 작아진다.

   radius 를 굳이 다시 적는 이유(조이 지적 2026-08-03 "더 둥글어 보인다") — 값이 아니라 **비율** 때문이다.
   --db-radius-btn(10px)은 32px 버튼 기준이라 높이의 31%인데, 높이만 22px 로 줄이면 같은 10px 가
   45%가 되어 알약에 가까워진다(브라우저 computed 실측: 10px/32px=31% vs 10px/22px=45%).
   그래서 같은 **비율**을 유지하도록 22 × 0.3125 ≈ 7px 로 다시 잡는다 — 옆에 선 큰 버튼과 모서리가 같은 톤이 된다. */
.db-btn--xs { height: 22px; padding: 0 8px; border-radius: 7px; }

/* 공용 confirm 모달 (window.dbConfirm / dbAlert — 삭제 확인 등). db_confirm.js 참조. */
.db-confirm-modal__overlay {
  position: fixed; inset: 0; z-index: 10000;
  background: rgba(15, 23, 42, 0.45);
  display: flex; align-items: center; justify-content: center; padding: 16px;
}
.db-confirm-modal {
  background: var(--db-color-card);
  border-radius: var(--db-radius-md);
  box-shadow: var(--db-shadow-overlay);
  padding: 20px; min-width: 300px; max-width: 420px;
  display: flex; flex-direction: column; gap: 8px; /* 본문↔버튼 등 세로 간격 축소 */
  font-family: var(--db-font-sans);
}
.db-confirm-modal__title { font-size: 14px; font-weight: 600; color: var(--db-color-text); }
.db-confirm-modal__body { font-size: 14px; color: var(--db-color-text); white-space: pre-wrap; line-height: 1.5; margin-top: -4px; /* 제목↔본문 위쪽 살짝 띄움(카드 gap 8px 중 4px만 상쇄) */ }
/* dbPrompt(한 줄 입력 모달)의 입력칸은 **정본 .db-input 을 그대로 입는다**(위 .db-input 정의).
   여기서 border/background 를 다시 적지 않는다 — 그 시도에서 존재하지 않는 토큰
   (--db-color-border / --db-color-surface) 을 써 테두리가 통째로 사라졌었다.
   2026-08-15 Playwright 스크린샷으로 발견. __input 은 선택자 훅 전용이다. */
.db-confirm-modal__actions { display: flex; justify-content: flex-end; gap: 8px; }
/* 확인 버튼 색은 앱 primary 토큰 그대로(.db-btn-primary 상속) — 별도 오버라이드 없음(주 파랑=남색 #1e40af, db_tokens.css 정본). */
/* dbConfirm 이 열리며 확인 버튼을 auto-focus → 브라우저 기본 outline(검정 보더) 제거 */
.db-confirm-modal .db-btn:focus, .db-confirm-modal .db-btn:focus-visible { outline: none; box-shadow: none; }
.db-confirm-modal--danger .db-confirm-modal__title { color: var(--db-color-danger); }
.db-confirm-modal--danger .db-btn-primary { background: var(--db-color-danger); border-color: var(--db-color-danger); }
.db-confirm-modal--danger .db-btn-primary:hover { background: var(--db-color-danger-hover); border-color: var(--db-color-danger-hover); }

/* 공용 액션 버튼 (shared/_action_button) — 배경색은 --btn-color 로 변경 가능, hover 자동 */
.db-action-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 4px;
  height: 32px; padding: 0 12px; box-sizing: border-box;
  border: none; border-radius: var(--db-radius-btn); /* .db-btn(10px)과 radius 통일 — 닫기/저장 동일 */
  /* 기본색 하드코딩 #1e40af → 토큰(--db-color-action) 경유로 정정 — 렌더값 동일(#1e40af). */
  background: var(--btn-color, var(--db-color-action)); color: #fff;
  /* M3 typescale 의존 제거(2026-07-29) — 정의처 m3-tokens.css 는 이 파일을 로드하는 6개 문서 중
     2곳(application·embed_modal)에만 있어 print_form·독립팝업 3개에선 이 shorthand 가 미해석이었다.
     해석되던 값(500 12px/16px)을 명시해 6개 문서 렌더를 일치시킨다. */
  font-family: inherit; font-size: 12px; font-weight: 600; line-height: 16px;
  cursor: pointer; user-select: none; white-space: nowrap;
  transition: filter 0.12s;
}
/* 중립(회색) 변형 — 주 파랑 액션과 톤을 가르는 보조 도구 버튼용(휴지통 등).
   종전엔 호출부가 `style="--btn-color:…"` 를 인라인으로 박았는데 그건 인라인 style 래칫에
   걸린다. 같은 값을 클래스로 준다(2026-08-15). */
.db-action-btn--neutral { --btn-color: var(--db-color-action-neutral); }
.db-action-btn:hover { filter: brightness(0.92); }
.db-action-btn:active { filter: brightness(0.85); }

/* ══════════════════════════════════════════════════════════════════════════
   공용 파셜 회수분 (2026-08-05) — `app/views/shared/**` 인라인 style 을 여기로 걷었다.
   여기(db_components)에 두는 근거는 **도달 범위**다: `<html>` 문서 7개 중 6개가 이 시트를
   싣고, 안 싣는 하나(layouts/mailer)는 이 파셜들을 렌더하지 않는다(전수 실측).
   화면 전용 시트에 뒀다면 독립 팝업·인쇄 문서에서 그 파셜만 스타일이 사라졌을 것이다 —
   인라인은 어디서나 먹었지만 시트는 그렇지 않다. 이게 이 회수의 유일한 진짜 위험이었다.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── 계정 메뉴 (shared/_account_menu) ─────────────────────────────────── */
.db-account-avatar { background-color: var(--db-color-primary); }
.db-account-icon   { font-size: 18px; }

/* ── 액션 버튼 아이콘 (shared/_action_button) ─────────────────────────── */
.db-action-btn__icon { font-size: 16px; line-height: 1; }

/* ── 모달 셸 (shared/_modal) ──────────────────────────────────────────
   ⚠️ `.db-modal-panel` 의 `pointer-events:auto` 는 Tailwind prebuilt 에 그 유틸이 없어서
   인라인으로 주던 것이다(파셜 주석의 실측 결론). 클래스로 내려도 unlayered 라 유틸에 안 진다.
   패널 크기는 여전히 `panel_style` 파라미터로 받는다 — 그건 부채가 아니라 **컴포넌트 API** 다.

   dsv2_clients 의 `.dc-modal-overlay`·`.dc-modal-panel` 은 같은 값의 복제였다(2026-08-05
   직전 슬라이스에서 내가 만든 것) — 이번에 이 둘로 합치고 그쪽 정의는 지웠다. */
.db-modal-overlay { background: rgba(15, 23, 42, 0.45); }
/* dim 만 끈 배경 — 색이 투명해질 뿐 **클릭 판정은 그대로 남는다**(바깥 클릭으로 닫힌다).
   `pointer-events:none` 을 쓰면 안 된다: 그러면 배경 클릭이 뒤 화면으로 새어 목록이 눌리고
   정작 팝업은 안 닫힌다. 쓰는 곳 = shared/_modal 의 `dim: false`(2026-08-25 써니 요청 —
   상담 중에 뒤 목록이 계속 보여야 한다). */
.db-modal-overlay--clear { background: transparent; }
.db-modal-panel   { pointer-events: auto; }

/* ── 팝업 이동(드래그) 손잡이 — 동작 정본은 `lib/modal_drag.js` (2026-08-24) ──
   ⚠️ 아래 셀렉터는 modal_drag.js 의 손잡이 판정과 **같은 규칙**이어야 한다.
      갈라지면 "손 모양은 뜨는데 안 끌린다"(또는 그 반대)가 되고 둘 다 조용히 틀린다.
      modal_drag_canon_test 가 두 곳이 갈라지는 걸 막는다.
   `user-select:none` 이 헤더 제목의 드래그 선택을 막는다 — 덕분에 JS 가 pointerdown 을
   preventDefault 하지 않아도 된다(취소하면 뒤따르는 click 호환 이벤트까지 사라진다).
   `touch-action:none` 은 터치로 헤더를 끌 때 페이지가 대신 스크롤되는 걸 막는다. */
.db-modal-panel > header,
[data-drag-handle] {
  cursor: move;
  user-select: none;
  -webkit-user-select: none;
  touch-action: none;
}
/* 자리 저장 상태 칩 — "위치 저장 중…" / "위치 저장됨" (써니 2026-08-24 요청).
   모양의 정본은 사진 회전 칩(lib/dsv2_attachment_popup 의 `.iv-save`)이다: 알약, 작은 글씨,
   결과는 잠깐 떴다 사라진다. 여기선 팝업 헤더 안이라 어두운 배경 대신 토큰 색을 쓴다.

   ⚠️ `margin-left:auto` 가 이 칩의 자리를 정한다. 헤더가 `justify-between` 이라 그냥 넣으면
      제목과 X 사이에 끼어 셋이 고르게 벌어진다 — auto 마진이 남는 공간을 먼저 다 먹어서
      제목은 왼쪽에 그대로 두고 칩만 오른쪽(X 왼쪽)으로 붙인다.
      JS 는 닫기 X 앞에 끼워 넣는다(modal_drag.js 의 chipFor). 자리 규칙은 상담 타임라인의
      저장 인디케이터("헤더 우측 X 왼쪽")와 같다.
   ⚠️ `user-select` 를 되살리지 않는다 — 손잡이 안이라 칩을 잡고 끌어도 팝업이 움직여야 한다. */
.db-modal-drag-chip {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  flex: none;
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
  padding: 4px 10px;
  border-radius: 999px;
  white-space: nowrap;
  background: var(--db-color-bg-alt);
  color: var(--db-color-text-sub);
}
.db-modal-drag-chip[hidden] { display: none; }
.db-modal-drag-chip[data-state="saved"],
.db-modal-drag-chip[data-state="cleared"] {
  background: var(--db-color-primary-container);
  color: var(--db-color-on-primary-container);
}
.db-modal-drag-chip[data-state="failed"] {
  background: var(--db-color-fill-subtle);
  color: var(--db-color-danger);
}

/* ── 대각선 크기조절 손잡이 — 동작 정본은 `lib/modal_drag.js` (2026-08-26 써니) ──
   `shared/_modal` 이 `resizable: true` 일 때만 그린다. 패널이 `relative` + `overflow-hidden`
   이라 우하단 **모서리 안쪽**에 앉는다.
   ⚠️ 셀렉터가 클래스가 아니라 **표식**인 것은 의도다 — 위 `[data-drag-handle]` 과 같은 관행.
      셸·CSS·JS 가 «같은 문자열 하나»를 보게 해서 한 곳만 이름이 바뀌는 사고를 없앤다.
      그 사고는 전부 «에러 0 · 조용히 없음» 이다: 안 그려지거나(셸), 안 보이거나(CSS),
      안 잡히거나(JS). modal_drag_canon_test 가 셋을 묶는다.
   아이콘 폰트도 SVG 도 안 쓴다(이 셸은 어떤 화면에서든 뜬다) — 사선 두 줄은 그라디언트로.
   `touch-action:none` 은 터치로 끌 때 페이지가 대신 스크롤되는 걸 막는다(이동 손잡이와 같은 이유). */
[data-resize-grip] {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 18px;
  height: 18px;
  z-index: 2;
  cursor: nwse-resize;
  touch-action: none;
  opacity: 0.55;
  background-image:
    linear-gradient(135deg, transparent 0 44%, var(--db-color-text-sub) 44% 56%, transparent 56%),
    linear-gradient(135deg, transparent 0 72%, var(--db-color-text-sub) 72% 84%, transparent 84%);
}
[data-resize-grip]:hover { opacity: 1; }
/* 끄는 동안 본문 글자가 선택되는 걸 막는다 — 포인터가 목록 위를 지나가기 때문이다.
   JS 가 손잡이와 패널 **양쪽**에 이 표식을 건다(modal_drag.js 의 onGripDown). */
.db-modal-panel[data-resizing],
[data-drag-panel][data-resizing] {
  user-select: none;
  -webkit-user-select: none;
}
/* 끄는 동안은 손을 뗄 때까지 진하게 — 지금 이걸 잡고 있다는 신호. */
[data-resize-grip][data-resizing] { opacity: 1; }

/* 크기를 받은 패널은 셸이 걸어 둔 **상한을 푼다**. 안 풀면 `max-w-*` 유틸과 인라인
   `max-height` 가 먼저 잘라서 «커지는 쪽으로는 아무 일도 안 일어난다» — 화면엔
   "안 커지네" 로만 보이고 에러가 없다.

   ⚠️ 이 해제가 **CSS 에 있는 것이 요점**이다. JS 가 `style.maxHeight` 를 "none" 으로
      덮으면 되지 않느냐 — 안 된다. 그 `max-height` 는 셸이 `panel_style` 로 걸어 둔
      **인라인 원본**이라(건물 요약 팝업 = `max-height: 82vh`), 인라인을 건드리는 순간
      원본이 사라지고 더블클릭으로 기본 크기로 돌아가도 **상한이 영영 안 돌아온다**.
      호실이 많은 건물에서 팝업이 화면을 넘고, 그것도 조용하다.
      stylesheet 의 `!important` 는 인라인(non-important)을 이기므로, 원본을 손대지 않고
      «크기를 받은 동안만» 덮을 수 있다. 표식을 떼면 원본이 그대로 되살아난다. */
.db-modal-panel[data-resized],
[data-drag-panel][data-resized] {
  max-width: none !important;
  max-height: none !important;
}

/* 손잡이 안의 조작 요소(닫기 X 등)는 제 커서를 지킨다 — JS 도 이 요소들 위에서 시작한
   pointerdown 은 드래그로 치지 않는다(modal_drag.js 의 INTERACTIVE). */
.db-modal-panel > header :is(button, a, input, select, textarea, label),
[data-drag-handle] :is(button, a, input, select, textarea, label) {
  cursor: pointer;
  user-select: auto;
  -webkit-user-select: auto;
  touch-action: auto;
}

/* 헤더 닫기 X 의 상자는 셸이 소유한다 — `w-8 h-8`(32px) 안에 18px 아이콘을 flex 로 가운데 둔다.
 *
 * ⚠️ 2026-08-19 운영 실측 사고: 매물등록창(register_window)에서 **X 가 통째로 안 보였다.**
 *   그 화면 전용 시트의 요소 셀렉터 `button { padding: 8px 16px; … }`
 *   (dsv2_register_window.css)가 32px 버튼에 좌우 16px 씩을 얹어 **content box 를 0** 으로
 *   만들고, flex 아이템인 <svg> 가 0 폭으로 찌그러진 것이다.
 *   버튼 자체는 34px 로 남아 **클릭은 되는데 아이콘만 사라진다** — 에러 0·콘솔 0.
 *   (라이브 실측: svg width 0 → padding 0 으로 하면 18 복원. edit 화면은 그 시트가 없어 정상이었다.)
 *
 * 두 규칙 다 unlayered 라 순서로 갈리는데, 화면 시트가 공용 시트보다 뒤에 실린다(embed_modal
 * 의 yield :head). 그래서 **특이도**로 이긴다 — 여기 (0,1,2) > `button` (0,0,1).
 *
 * 범위는 헤더로 좁힌다. footer 닫기 버튼은 `px-4 py-2` 여백이 필요해서 같이 0 으로 만들면 안 된다.
 *
 * 남은 부채(고치지 않음): 같은 규칙이 radius 도 6px 로 덮어 셸의 `rounded-lg`(8px)와 2px 다르다.
 *   눈에 안 띄는 차이라 값을 여기 또 적어 두지 않았다(적으면 Tailwind 클래스와 정의가 갈린다).
 *   진짜 해결은 그 시트의 `button` 요소 셀렉터를 `.btn-primary`/`.btn-cancel` 로 좁히는 것이다. */
.db-modal-panel header button { padding: 0; }

/* ── 검색바 아이콘 (shared/_search_bar) ───────────────────────────────── */
.db-search-icon {
  position: absolute; right: 9px; top: 50%;
  transform: translateY(-50%); pointer-events: none;
}
/* 돋보기를 **누를 수 있게** 할 때만 쓰는 껍데기(search_bar 의 icon_action 옵션).
   아이콘 자체는 pointer-events:none 이라 클릭을 못 받는다 — 그래서 감싸는 버튼이 받는다.
   위치·리셋 값은 아래 .db-date-toggle 과 같은 모양이다(같은 자리에 놓인 같은 성격의 버튼). */
.db-search-toggle {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  background: none; border: none; padding: 0 3px; cursor: pointer; line-height: 0;
}
.db-search-toggle .db-search-icon { position: static; transform: none; }

/* ── 날짜 입력 (shared/_date_field) ───────────────────────────────────── */
.db-date-label  { font: var(--db-text-caption-1); color: var(--db-color-text-sub); }
.db-date-toggle {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  background: none; border: none; padding: 0; cursor: pointer; line-height: 0;
}
.db-date-icon { width: 14px; height: 14px; color: var(--db-color-text-sub); }

/* ── 기간 필터 (shared/_date_filter) ──────────────────────────────────── */
.db-date-filter       { display: flex; align-items: center; gap: var(--db-space-2); flex: 0 0 auto; }
.db-date-filter-label { font: var(--db-text-label-2); color: var(--db-color-text-sub); }
.db-date-filter-label--nowrap { white-space: nowrap; }

/* ── 숫자 구간 (shared/_number_range_field) ────────────────────────────
   날짜 구간(.db-date-filter)의 숫자판. 칸 자체는 `.db-input` 정본을 그대로 쓰고
   여기는 **배치만** 정한다 — 칸 룩(테두리·높이 32px)을 여기 복사하면 정의가 두 벌이 된다.
   라벨을 위, 칸을 아래로 쌓는다: 모달 안 세로 폼이라 가로로 두면 칸이 너무 좁아진다. */
.db-num-range        { display: flex; flex-direction: column; gap: var(--db-space-2); }
.db-num-range-label  { font: var(--db-text-label-2); color: var(--db-color-text-sub); }
.db-num-range-inputs { display: flex; align-items: center; gap: var(--db-space-2); }
.db-num-range-sep,
.db-num-range-suffix { font: var(--db-text-label-2); color: var(--db-color-text-sub); flex: 0 0 auto; }
.db-num-range-suffix { min-width: 2.2em; }

/* ── 금액 입력 (shared/_money_field) ───────────────────────────────────
   칸 하나 + 뒤에 단위 라벨. 칸 룩은 `.db-input` 정본을 그대로 쓰고 여기는 **배치만** 정한다
   (.db-num-range 와 같은 규율 — 칸 룩을 여기 복사하면 정의가 두 벌이 된다).

   ⚠️ 인라인 style 로 쓰지 말 것. view_style_canon_test 의 인라인 `style=` 예산이 래칫이라
      새로 생기면 CI 가 막는다(shared/_area_pyeong_field 의 인라인은 그 래칫 이전 잔재다).

   단위 라벨은 `.db-num-range-suffix` 와 **같은 글꼴·색**이다 — 성격이 같은 글자가 화면마다
   달라 보이지 않게. 다만 min-width 는 주지 않는다: 여기 단위는 "만" 한 글자라 자리를 예약하면
   칸이 그만큼 줄어든다(구간 필터는 "만원" 두 글자라 예약이 필요했다). */
/* 값 + 단위 라벨 한 줄. 금액("만")과 단위("m"·"kw")가 **같은 모양**이어야 해서 규칙을 공유한다
   — 폼 안에서 두 칸이 나란히 서면 라벨 높이·간격이 다른 게 바로 보인다.
   클래스를 하나로 합치지 않은 이유: 마크업 계약이 다르다(금액은 보이는 칸+숨은 칸 2개,
   단위는 1개). 이름이 쓰임을 말해야 다음 사람이 금액 칸을 층고에 갖다 쓰지 않는다. */
.db-money-field,
.db-unit-field { display: flex; align-items: center; gap: var(--db-space-2); width: 100%; }
.db-money-field > .db-input,
.db-unit-field > .db-input { flex: 1 1 0; min-width: 0; }
.db-money-suffix,
.db-unit-suffix { font: var(--db-text-label-2); color: var(--db-color-text-sub); flex: 0 0 auto; }

/* ── 주소 재조회 바 (shared/_addr_retry_bar) ──────────────────────────
   뷰의 `<style>` 블록(@media print 한 줄)도 여기로 걷었다 — 그 파셜의 인라인 <style> 은 0이 됐다. */
.db-addr-bar {
  max-width: 720px; margin: 0 auto 12px;
  font-family: -apple-system, "Malgun Gothic", sans-serif;
}
.db-addr-row {
  display: flex; gap: 8px; align-items: center;
  background: #fff; border: 1px solid #e5e7eb; border-radius: 12px; padding: 10px 12px;
}
.db-addr-label { font-size: 12px; color: var(--db-color-primary); font-weight: 700; white-space: nowrap; }
.db-addr-input { flex: 1; min-width: 0; margin-top: 0; }
/* 호실칸(면적정보 팝업 전용) — 지번칸이 넓이를 먹고 호는 짧다. flex 로 늘리지 않는다. */
.db-addr-ho { flex: 0 0 7.5rem; min-width: 0; margin-top: 0; }
.db-addr-hint  {
  font-size: 12px; font-weight: 500; color: #6b7280;
  margin-top: 6px; padding-right: 2px; text-align: right;
}
@media print {
  .addr-retry-bar { display: none !important; }
}

/* ============================================================
   재생 아이콘 각 죽이기 (조이 지시 2026-08-13 "너무 각졌는데 끝을 살짝 뭉툭하게")

   ⭐ 정본 = 여기. daesin-mobile-v2 는 `app/assets/tailwind/application.css` 의 @layer base
      에 **같은 값**으로 미러한다 — 같은 재생기가 웹/앱에서 다른 각이면 안 된다.

   ## 왜 lucide 인데도 각져 보였나

   `stroke-linejoin="round"` 가 만드는 모서리 반경 = **stroke-width 의 절반**이다.
   lucide 기본은 stroke-width 2(24 뷰박스) → 반경 1. 이걸 15~16px 로 줄이면 **0.6px** 라
   사실상 직각으로 보인다. 아이콘을 잘못 쓴 게 아니라 원본 값이 이 크기에선 안 보이는 것이다.

   ## play 와 pause 는 손대는 곳이 다르다

   · play  — 삼각형 꼭짓점. 속을 채우고(fill) 같은 색 stroke 를 **두껍게** 얹으면
             linejoin=round 가 그 두께의 절반만큼 굴린다. 4 → 반경 2.
             ⚠️ stroke 는 바깥으로 절반(2)씩 번진다. polygon 이 6~20 이라 4~22 가 되어
                viewBox 24 안에 들어온다 — 더 키우면 잘린다.
   · pause — 막대 모서리. rect 의 `rx` 라 svg 루트 속성으로는 못 주고 여기서 잡는다.
             ⚠️ pause 에 stroke 를 두껍게 쓰면 **안 된다.** 막대 사이 간격이 4뿐이라
                stroke 3이면 양쪽 1.5씩 번져 간격이 1로 붙는다. rx 만 건드린다.
             ⚠️ **속도 채운다**(조이 2026-08-14 "일시정지 버튼 안에 색깔 채워주면 안되?").
                lucide 기본은 `fill="none"` 이라 흰 윤곽선만 남고 **속은 버튼 배경(파랑)이
                비쳤다** — play 는 위에서 채웠는데 pause 만 안 채워 둘이 짝이 안 맞았다.
                `currentColor` 라 버튼의 `text-white` 를 그대로 받는다(색을 여기 박지 않는다).

   ⚠️ lucide 는 치환한 <svg> 에 `data-lucide` 를 남긴다(0.452.0 `G0` 실측) — 그래서 이
      선택자가 성립한다. 라이브러리를 올릴 때 이 동작이 유지되는지 같이 본다.
   ============================================================ */
[data-lucide="play"] { fill: currentColor; stroke-width: 4; }
[data-lucide="pause"] rect { fill: currentColor; rx: 1.8px; }

/* ============================================================
   추천 필터 칩 (.db-preset-chip) — 「누르면 담기는」 카드형 칩
   ============================================================
   쓰는 곳: messages/_group_modal (발송 그룹 설정 = 장바구니).

   왜 .db-select__chip 을 못 쓰나: 그건 2~3글자 어휘를 고르는 **알약**이고
   값 하나만 싣는다. 여기 칩은 라벨 + 숫자 두 개(명단 / 문자가능)를 세로로 쌓는
   카드다 — 알약에 숫자 둘을 우겨넣으면 줄바꿈이 통째로 밀린다.

   ⚠️ 숫자가 **둘인 게 이 컴포넌트의 요점**이다. 실측(2026-09-01, org=1) 법인 고객은
      명단 908명인데 문자가 가는 건 78명이다(번호가 대부분 유선). 「908명」 하나만
      보여주면 사용자가 908명에게 보낸다고 읽는다. 숫자를 하나로 줄이려는 변경은
      이 주석을 지우고 하라.

   ⚠️ 색을 여기 박지 않고 토큰만 쓴다 — 이 칩은 shared/modal 안에 살고, 그 모달을
      쓰는 화면 중엔 tailwind 를 안 싣는 독립 팝업이 있다(.db-select__chip 주석과 같은 이유).
      그래서 순수 CSS 로만 성립해야 한다. */
.db-preset-chips {
  display: flex; flex-wrap: wrap; gap: 8px;
}
.db-preset-chip {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
  min-width: 132px; padding: 8px 12px;
  border: 1px solid var(--db-color-outline);
  border-radius: var(--db-radius-input);
  background: var(--db-color-card);
  cursor: pointer; user-select: none; text-align: left;
  /* 폼 컨트롤은 font-family 를 상속 안 한다 — .db-input 과 같은 이유로 명시한다. */
  font-family: inherit;
}
.db-preset-chip:hover { background: var(--db-color-fill-subtle); }
.db-preset-chip:active { filter: brightness(0.97); }
.db-preset-chip:focus-visible {
  outline: 2px solid var(--db-color-primary); outline-offset: 1px;
}
/* 담는 중 — 두 번 눌러도 명단이 늘진 않지만(by-filter 멱등) 요청이 겹치면 헷갈린다. */
.db-preset-chip[disabled] { opacity: .55; cursor: progress; }

.db-preset-chip__label {
  font-size: 13px; font-weight: 600; line-height: 1.3;
  color: var(--db-color-text);
}
.db-preset-chip__total {
  font-size: 15px; font-weight: 700; line-height: 1.2;
  color: var(--db-color-text);
}
.db-preset-chip__sub {
  font-size: 11px; line-height: 1.3;
  color: var(--db-color-text-muted);
}
/* 명단 대비 문자가능이 반도 안 되는 칩(법인 908 → 78). 결과를 보고 놀라지 않게
   **누르기 전에** 표시한다.
   ⚠️ 이 레포엔 「주의」 색 토큰이 없다(danger 하나뿐 — db_tokens.css). 토큰을 새로
      만드는 건 이 컴포넌트의 일이 아니라서 danger 를 쓰되 **테두리와 부제에만**
      얹는다. 칩 전체를 빨갛게 칠하면 「누르면 안 되는 것」으로 읽히는데, 실제로는
      눌러도 되는 정상 데이터다. 주의 토큰이 생기면 여기 두 줄만 바꾸면 된다. */
.db-preset-chip--thin { border-color: var(--db-color-danger); }
.db-preset-chip--thin .db-preset-chip__sub {
  color: var(--db-color-danger); font-weight: 600;
}

/* ── ↗ 새 탭으로 여는 작은 링크 (.active-bar-open) ─────────────────────────
 *
 * 「지금 화면을 떠나지 않고, 저건 저기서 본다」를 뜻하는 표시다.
 * 글리프는 material-symbols 의 `open_in_new` 이고, 마크업은 소비자가 적는다:
 *
 *   <a class="active-bar-open" target="_blank" rel="noopener" href="…">
 *     <span class="material-symbols-outlined active-bar-open-icon">open_in_new</span>
 *   </a>
 *
 * ⚠️ 원래 `dsv2_consult_window.css` 안에 있었는데 소비자가 둘이 되어(상담창 ↗, 광고 문자
 *    미리보기의 매물장 ↗) 2026-09-04 에 여기로 **옮겼다**(복사가 아니다 — 그쪽에서 지웠다).
 *    그 팝업은 tailwind 를 안 싣지만 db_components 는 싣는다(consult_window.html.erb:23).
 *
 * ⚠️ `color:#9ca3af` 가 토큰이 아니라 리터럴인데 옮기면서 **값을 안 바꿨다.**
 *    이사에 「개선」을 섞으면 시각 변화 0 을 증명할 수 없다. 토큰화는 별건이다. */
.active-bar-open {
  display: inline-flex; align-items: center;
  padding: 2px; border-radius: 4px;
  color: #9ca3af; text-decoration: none;
}
.active-bar-open:hover {
  color: var(--db-color-primary);
  background: color-mix(in srgb, var(--db-color-primary) 10%, transparent);
}
.active-bar-open-icon { font-size: 18px; }
