:root {
  --bg: #f4f6f8;
  --surface: #ffffff;
  --text: #1a2027;
  --text-dim: #6b7684;
  --border: #e4e8ec;
  --primary: #2b6cb0;
  --primary-dim: #e7f0fa;
  --bubble-mine: #2b6cb0;
  --bubble-mine-text: #ffffff;
  --bubble-theirs: #ffffff;
  --danger: #d64545;
  --online: #2ecc71;
}

/* 시스템/브라우저가 다크 테마일 때 자동 적용 — "내 정보"에서 라이트를 명시적으로 고르면(data-theme="light") 무시한다 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #14171c;
    --surface: #1c2028;
    --text: #eef1f4;
    --text-dim: #9aa4b2;
    --border: #2a2f38;
    --primary: #4d8fd6;
    --primary-dim: #1f2c3a;
    --bubble-mine: #3a7bc8;
    --bubble-mine-text: #ffffff;
    --bubble-theirs: #2a2f38;
  }
}

/* "내 정보"에서 다크를 명시적으로 고르면(data-theme="dark") 시스템 설정과 무관하게 항상 적용 */
:root[data-theme="dark"] {
  --bg: #14171c;
  --surface: #1c2028;
  --text: #eef1f4;
  --text-dim: #9aa4b2;
  --border: #2a2f38;
  --primary: #4d8fd6;
  --primary-dim: #1f2c3a;
  --bubble-mine: #3a7bc8;
  --bubble-mine-text: #ffffff;
  --bubble-theirs: #2a2f38;
}

* { box-sizing: border-box; }
/* 기본 브라우저/OS 스크롤바는 두껍고 테마와 무관한 회색이라 카카오톡 등과 비교해 튀어 보인다.
   앱의 --border/--text-dim 색과 라운드 스타일에 맞춰 얇게 다시 그린다 — PC(Electron)에서 가장
   체감되고, 모바일은 브라우저가 이미 오버레이형이라 거의 영향 없다. */
* { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: var(--border); border-radius: 8px; border: 2px solid transparent; background-clip: padding-box; }
*::-webkit-scrollbar-thumb:hover { background: var(--text-dim); background-clip: padding-box; }
html, body { height: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Malgun Gothic", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
  /* 마우스로 목록/버튼 위를 드래그하면 웹페이지처럼 글자가 파랗게 선택돼 앱 같지 않아 보였다 —
     기본을 선택 불가로 막고, 실제로 복사가 필요한 채팅 메시지 말풍선만 예외로 다시 켠다(PC에서는
     이미 .bubble에 user-select:text가 따로 있음, 아래 참고). 입력창/textarea의 글자 선택·편집은
     user-select의 영향을 안 받는 브라우저 기본 동작이라 이 규칙과 무관하게 그대로 된다. */
  user-select: none; -webkit-user-select: none;
}
#app { height: 100%; display: flex; justify-content: center; }

/* PC 트레이 앱(Electron) 전용 커스텀 제목표시줄 — main.js가 frame:false로 OS 기본 제목표시줄을
   아예 없앤 대신 이 자리에 아이콘/이름/알림 버튼/최소화/닫기까지 전부 직접 그린다(app.js가
   window.electronTray 유무로 body에 "electron" 클래스를 붙여야 보인다). titleBarStyle:'hidden'+
   titleBarOverlay(WCO)도 시도했으나 OS 합성 레이어가 웹 콘텐츠를 그대로 덮어버리는 문제가 있어
   frame:false로 바꿨다 — 이제는 OS가 그려주는 버튼이 없으므로 오른쪽 여백을 남길 필요가 없다. */
.electron-titlebar { display: none; }
body.electron { display: flex; flex-direction: column; height: 100%; }
/* #app의 기본 규칙(위쪽 "height: 100%")이 그대로 남아있으면 body 전체 높이(창 높이)를 다시 요구해서,
   제목표시줄(32px)만큼 넘쳐 body 자체가(스크롤 없어야 할 창 전체가) 세로로 스크롤되는 버그가 있었다
   — 그 상태에서 스크롤하면 제목표시줄까지 화면 밖으로 밀려 올라가 보였다. flex:1이 이미 남은 공간을
   정확히 계산해주므로 height는 auto로 되돌려서 제목표시줄 높이를 침범하지 않게 한다. */
body.electron #app { height: auto; flex: 1; min-height: 0; }
/* .app-shell(아래)의 "min-height: 100dvh"는 부모(#app) 크기와 무관하게 항상 창 전체 높이를 요구해서,
   위 #app 수정과 별개로 여전히 제목표시줄(32px)만큼 넘쳤다 — 진짜 원인은 이거였다. #app이 flex로
   이미 정확한 높이를 주고 있고 align-items 기본값(stretch)이 그 높이를 그대로 채워주므로,
   뷰포트 기준 min-height는 여기서 무의미하게 만든다(모바일/브라우저 쪽 100dvh는 그대로 둠). */
body.electron .app-shell { min-height: 0; }
body.electron .electron-titlebar {
  display: flex; align-items: center; gap: 8px;
  flex: 0 0 auto; height: 32px; padding: 0 0 0 8px;
  background: var(--primary); color: #fff; font-size: 13px; font-weight: 600;
  -webkit-app-region: drag; user-select: none;
}
body.electron .electron-titlebar img { width: 16px; height: 16px; -webkit-app-region: no-drag; }
body.electron .electron-titlebar span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
body.electron .electron-titlebar .spacer { flex: 1; -webkit-app-region: drag; }
body.electron .electron-titlebar button {
  -webkit-app-region: no-drag;
  width: 28px; height: 24px; border-radius: 4px; border: none; cursor: pointer;
  background: rgba(255,255,255,.15); color: #fff; font-size: 14px; line-height: 1;
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
/* 위 display:flex가 [hidden]의 기본 display:none보다 우선순위가 높아서, 팝업 창에서
   notifyBtn.hidden = true(app.js)로 숨긴 알림 버튼이 이 규칙 없이는 그대로 보여버린다. */
body.electron .electron-titlebar button[hidden] { display: none; }
body.electron .electron-titlebar button:hover { background: rgba(255,255,255,.3); }
body.electron .electron-titlebar button.notify-off { opacity: .55; }
/* 최소화/최대화/닫기는 OS가 안 그려주므로 직접 그린다 — Windows 11 기본 제목표시줄과 동일하게 굵기·
   정렬이 통일된 10x10 선 아이콘(SVG, index.html 참고)을 쓴다. 닫기만 호버 시 빨간색으로 강조한다. */
body.electron .electron-titlebar .window-btn {
  width: 40px; height: 32px; border-radius: 0;
  background: transparent;
}
body.electron .electron-titlebar .window-btn svg { width: 10px; height: 10px; }
body.electron .electron-titlebar .window-btn:hover { background: rgba(255,255,255,.15); }
body.electron .electron-titlebar .window-btn-close:hover { background: #e5484d; }

/* 화면을 켜둔 채로 새 버전이 배포됐을 때 새로고침을 안내하는 배너(#app 바깥, 항상 최상단 고정) */
.app-shell {
  width: 100%;
  max-width: 480px;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  position: relative;
}
/* PC(마우스)에서는 모바일 화면 폭(480px)에 갇혀있을 이유가 없으니, 창을 넓히는 만큼 그대로 넓어지게
   상한을 없앤다. 터치 기기는 그대로 480px 상한을 유지한다(태블릿 등에서 굳이 화면 끝까지 늘어난
   모바일 레이아웃을 보여줄 필요는 없어서 — pointer:fine으로 마우스 환경만 구분). */
@media (pointer: fine) {
  .app-shell { max-width: none; }
}

/* ---- header ---- */
.topbar {
  position: sticky; top: 0; z-index: 10;
  display: flex; align-items: center; gap: 8px;
  padding: calc(env(safe-area-inset-top, 0px) + 12px) 16px 12px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.topbar h1 { font-size: 18px; margin: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.topbar button.icon-btn {
  background: none; border: none; color: var(--primary); font-size: 15px; cursor: pointer; padding: 6px;
  border-radius: 8px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
}
.topbar button.icon-btn svg { width: 20px; height: 20px; display: block; }
/* 검색 아이콘을 누른 동안(입력창이 펼쳐진 상태)에는 눌려있는 느낌을 준다 — 다른 곳의 탭/설정 메뉴
   활성 표시와 톤을 맞춰 옅은 배경으로. */
.topbar button.icon-btn.active { background: var(--primary-dim); }
/* 타이틀 옆 검색어 입력창(예: 연락처/쪽지함/채팅 탭) — 검색 아이콘 버튼을 눌러야 펼쳐지고(shell.js),
   평소엔 숨겨서 타이틀이 눌리지 않게 한다. 상자 안에 돋보기 아이콘을 따로 안 넣는다 — 펼치는 토글
   버튼 자체가 이미 돋보기 아이콘이라 상자 안에 또 넣으면 같은 아이콘이 나란히 두 번 보였다. */
.topbar-search {
  display: flex; align-items: center; gap: 6px; flex-shrink: 0;
  width: 198px; padding: 7px 0;
}
/* [hidden]의 기본 display:none과 이 규칙이 클래스 선택자로 동일 우선순위라 소스 순서로 밀릴 수 있어
   (이 세션에서 몇 번 겪은 패턴 — 잠금화면/알림 버튼 등) 명시적으로 다시 적어 반드시 이기게 한다. */
.topbar-search[hidden] { display: none; }
.topbar-search input {
  border: none; background: none; padding: 0; font-size: 13px; width: 100%; color: var(--text);
}
/* 쪽지 쓰기 모달의 "받는 사람 검색" 입력창 아이콘 — 쪽지함 상단의 검색 아이콘(SEARCH_ICON_SVG)과
   같은 SVG를 재사용하므로(memos.js), 위 .topbar-search와 달리 입력창이 항상 펼쳐져 있어 아이콘을
   토글 버튼이 아니라 입력창 안쪽 장식으로 직접 넣는다. */
.memo-recipient-search-icon svg { width: 14px; height: 14px; display: block; }
.topbar-search input:focus { outline: none; }

/* ---- content ---- */
.content { flex: 1; overflow-y: auto; padding-bottom: 8px; }
/* 연락처(내 프로필 카드+탭 고정)/쪽지함(받은/보낸 탭 고정) 화면 전용 — .content 자체는 더 이상
   스크롤하지 않고(overflow:hidden), 그 안의 상단 요소(프로필 카드/탭)는 제자리에 그대로 둔 채
   .scroll-area(그 화면의 실제 목록 영역)만 남은 높이를 차지해 스크롤되게 한다. min-height:0이 없으면
   flex 자식은 내용만큼 커지려 해서 스크롤 대신 부모를 밀어내며 넘친다. */
.content.split-scroll { display: flex; flex-direction: column; overflow: hidden; padding-bottom: 0; }
.content.split-scroll > .scroll-area { flex: 1; overflow-y: auto; min-height: 0; }

/* ---- bottom nav ---- */
.bottom-nav {
  position: sticky; bottom: 0; z-index: 10;
  display: flex;
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
.bottom-nav button {
  flex: 1; border: none; background: none; padding: 10px 0 8px;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  color: var(--text-dim); font-size: 11px; cursor: pointer;
}
/* shell.js가 이모지 대신 currentColor 선 아이콘(SVG)을 넣는다 — 이모지는 폰트/OS에 따라 채도 없는
   회색조로 그려지는 경우가 많아(특히 연락처 👥), 글자색을 줘도 적용이 안 되고(이모지는 currentColor를
   안 탐) 다크모드에서 유독 잘 안 보였다. SVG는 버튼의 color를 그대로 물려받아 테마/선택 상태와
   무관하게 항상 또렷하다. 카카오톡처럼 둥근 배경 칩도 깔아서(선택된 탭은 --primary-dim로 더 강조)
   대비를 한 번 더 보장한다. */
.bottom-nav button .emoji {
  width: 30px; height: 30px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg);
}
.bottom-nav button .emoji svg { width: 18px; height: 18px; }
.bottom-nav button.active { color: var(--primary); }
.bottom-nav button.active .emoji { background: var(--primary-dim); }

/* ---- login ---- */
.login-view {
  flex: 1; display: flex; flex-direction: column; justify-content: center; padding: 32px 24px;
  gap: 16px;
}
.login-view .brand { text-align: center; margin-bottom: 16px; }
.login-view .brand .emoji { font-size: 48px; }
.login-view .brand h1 { font-size: 22px; margin: 8px 0 0; }
.login-view .brand p { color: var(--text-dim); font-size: 13px; margin: 4px 0 0; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field label { font-size: 13px; color: var(--text-dim); }
input[type="text"], input[type="password"], input[type="date"], select, textarea {
  width: 100%; padding: 12px 14px; border-radius: 10px; border: 1px solid var(--border);
  background: var(--surface); color: var(--text); font-size: 16px; box-sizing: border-box;
  /* input[type=date]는 이 규칙이 없으면 브라우저 기본 스타일(작은 글씨, 다른 테두리)로 떨어져서
     같은 폼 안 다른 입력칸들과 안 어울렸다("내 정보 수정"의 생일 칸 — 쪽지 검색 팝업의 시작/종료
     일자도 같은 문제라 별도로 스타일을 줬었는데, 여기 공용 규칙에 넣어두면 앞으로 date input을
     추가할 때마다 매번 따로 맞출 필요가 없다). font-family는 상속 안 되는 브라우저가 있어 명시한다. */
  font-family: inherit;
}
.checkbox-row input[type="checkbox"] { width: 18px; height: 18px; accent-color: var(--primary); flex-shrink: 0; }
/* 비밀번호 입력창 안쪽 오른쪽 끝에 겹쳐 놓는 표시/숨기기 버튼 */
.password-field { position: relative; }
.password-field input { padding-right: 44px; }
.password-field button {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  background: none; border: none; padding: 6px; cursor: pointer; line-height: 1; color: var(--text-dim);
  display: flex; align-items: center; justify-content: center;
}
.password-field button img { width: 20px; height: 20px; display: block; }
textarea { resize: vertical; font-family: inherit; }
button.primary {
  padding: 13px; border-radius: 10px; border: none; background: var(--primary); color: white;
  font-size: 16px; font-weight: 600; cursor: pointer;
}
button.primary:disabled { opacity: .5; }
/* 로그인 버튼만 폼 가운데로(다른 화면의 .primary 버튼들은 폭이 꽉 차거나 flex:1로 나눠 쓰는 경우가 많아
   전역으로 바꾸지 않고 이 버튼만 따로 지정한다). */
#login-submit { display: block; margin: 8px auto 0; padding: 13px 32px; }
button.secondary {
  padding: 10px; border-radius: 10px; border: 1px solid var(--border); background: var(--surface);
  color: var(--text); font-size: 14px; cursor: pointer;
}
.error-banner {
  background: #fdecec; color: var(--danger); padding: 10px 14px; border-radius: 8px; font-size: 13px;
}

/* ---- lists ---- */
.list-item {
  display: flex; align-items: center; gap: 12px; padding: 12px 16px;
  border-bottom: 1px solid var(--border); background: var(--surface); cursor: pointer;
}
/* 마우스로 훑을 때 지금 어느 행 위에 있는지 보이게(데스크톱 앱의 흔한 관례) — 누르는 순간(:active)의
   더 진한 강조색과는 구분되게 옅은 회색만 쓴다. 순서상 :active를 뒤에 둬서 클릭 중엔 그쪽이 이긴다. */
.list-item:hover { background: var(--bg); }
.list-item:active { background: var(--primary-dim); }
/* 새 대화/초대 등에서 고를 사람을 목록으로 보여줄 때, 작은 체크박스만으로는 선택 여부가 눈에 잘 안
   띌 수 있어(특히 모바일) 선택된 행 전체를 배경색으로도 표시한다. */
.list-item.pickable.selected { background: var(--primary-dim); }
.avatar {
  width: 44px; height: 44px; border-radius: 50%; background: var(--primary-dim); color: var(--primary);
  display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 16px;
  flex-shrink: 0; position: relative; overflow: hidden;
}
.avatar img { width: 100%; height: 100%; object-fit: cover; }
/* 알림봇 프로필처럼 사진 대신 아이콘(SVG)을 아바타 안에 넣는 경우 — 원의 절반 정도 크기로. */
.avatar svg { width: 50%; height: 50%; }
/* 프로필 사진 우측 하단 상태 배지(util.js avatarWithStatus) — .avatar가 overflow:hidden이라 배지는
   바깥 래퍼(.avatar-wrap)에 얹는다. 배지는 클릭을 가로채지 않게 해서(pointer-events:none) 사진을
   눌렀을 때 기존처럼 프로필/이미지뷰어로 연결되게 한다. */
.avatar-wrap { position: relative; flex-shrink: 0; width: fit-content; line-height: 0; }
.status-badge {
  position: absolute; right: -3px; bottom: -3px; width: 19px; height: 19px; box-sizing: border-box;
  border-radius: 50%; border: 2px solid var(--surface); pointer-events: none;
  display: flex; align-items: center; justify-content: center;
}
.status-badge svg { width: 68%; height: 68%; display: block; }
.avatar-wrap.lg .status-badge { right: 2px; bottom: 2px; width: 30px; height: 30px; border-width: 3px; }
/* 오늘 생일인 사람(util.js avatarWithStatus의 isBirthdayToday) — 사진에 분홍·금색 2겹 테두리 링을
   둘러 멀리서도 한눈에 띄게 하고, 상태 배지와 겹치지 않는 반대쪽(좌측 상단)에 케이크 배지를 얹는다.
   box-shadow를 겹쳐 쌓으면 conic-gradient/mask 없이도 다단 색 링을 안정적으로 그릴 수 있다. */
.avatar-wrap.birthday .avatar {
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px #ff8fc2, 0 0 0 6px #ffd257;
}
.birthday-badge {
  position: absolute; left: -3px; top: -3px; width: 19px; height: 19px; box-sizing: border-box;
  border-radius: 50%; border: 2px solid var(--surface); pointer-events: none;
  background: linear-gradient(135deg, #ff8fc2, #ffd257);
  display: flex; align-items: center; justify-content: center; font-size: 11px; line-height: 1;
}
.avatar-wrap.lg .birthday-badge { left: 2px; top: 2px; width: 30px; height: 30px; border-width: 3px; font-size: 17px; }
/* 연락처 화면 맨 위 고정 내 프로필 카드(contacts.js) — 목록 항목(.list-item)과 같은 여백/구분선을
   쓰고, 조직도에서 내 계정을 눌렀을 때와 동일한 액션시트가 뜨므로 클릭 가능함을 hover/active로도
   보여준다(우측 상태 select 자체는 별도 조작 영역이라 클릭이 그쪽으로 버블링되지 않게 따로 막음). */
.my-profile-card { display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--border); background: var(--surface); cursor: pointer; }
.my-profile-card:hover { background: var(--bg); }
.my-profile-card:active { background: var(--primary-dim); }
.my-profile-card .avatar { box-shadow: 0 1px 4px rgba(0, 0, 0, .12); }
.my-profile-card .meta { flex: 1; min-width: 0; }
.my-profile-card .name { font-weight: 600; font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.my-profile-card .name .nick { font-weight: 400; color: var(--text-dim); font-size: 13px; }
.my-profile-card .loginid { font-size: 12px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* select의 전역 스타일(입력창 폼 기준, width:100%+큰 여백/글자)을 그대로 두면 이 좁은 카드 한 줄
   안에서 select가 너무 넓어져 이름/아이디가 거의 안 보일 만큼 눌린다 — 카드에 맞게 작고 고정 폭으로 줄인다. */
.my-profile-card select {
  flex-shrink: 0; width: auto; max-width: 132px;
  padding: 6px 8px; font-size: 13px; border-radius: 8px;
}
.list-item .meta { flex: 1; min-width: 0; }
.list-item .meta .row1 { display: flex; justify-content: space-between; align-items: center; gap: 8px; min-width: 0; }
/* 조직도 깊은 depth에서는 들여쓰기 때문에 이름이 들어갈 폭이 좁아진다 — 줄바꿈되면 행 높이가
   들쭉날쭉해지므로(row1 정렬이 깨져 보임) 한 줄로 고정하고 넘치면 "…"로 줄인다. */
.list-item .meta .name {
  font-weight: 600; font-size: 15px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
/* 그룹웨어 외근/근태 현황(contacts.js memberRow의 m.attendance) — 옅은 회색 상태 텍스트와 같은 톤이면
   "잘 안 띈다"는 피드백이 있어, 배경을 채운 필(pill) 배지로 바꿨다. 빨강(--danger)은 이 앱에서 이미
   "오류/삭제" 의미로 쓰고 있어 근태 정보가 경고처럼 보이지 않도록 피하고, 자리비움 상태 배지와 같은
   계열의 호박색을 썼다 — "그룹웨어가 지금 알려주는 정보"라는 톤으로 통일된다. */
.attendance-badge {
  /* 위아래 패딩이 1px/2px로 비대칭이었고, line-height:1.6이 만드는 글자 위아래 여백(leading)도
     정확히 반반은 아니라서 "외근" 글자가 배경(pill) 안에서 살짝 위/아래로 치우쳐 보였다("배경 위아래
     정렬" 신고). 패딩을 위아래 동일하게 주고 line-height를 1로 낮춰(순전히 글자 높이만 남게) 여기
     남는 위아래 공간이 패딩뿐이게 만들면, align-items:center가 정확히 반반으로 나눈다. */
  display: inline-flex; align-items: center; margin-left: 4px; padding: 3px 7px;
  border-radius: 999px; background: #f0a020; color: #fff; font-weight: 700; font-size: 11.5px;
  line-height: 1; vertical-align: middle;
  /* 프로필 상세(.profile-card-header h2)는 align-items:baseline인 flex라, 텍스트 기준 베이스라인에
     맞춰지면 패딩 있는 필 배지가 이름/직위 글자와 어긋나 보인다("외근 표시 라인정렬" 신고) — 이
     배지만 가운데 정렬로 맞춘다. 목록 행(memberRow)에서는 flex 자식이 아니라 그냥 인라인 span이라
     이 속성 자체가 적용되지 않으므로 거기엔 영향이 없다. */
  align-self: center;
}
/* 프로필 상세는 이미 .profile-card-header h2가 요소 사이 gap(6px)을 주고 있어서, 목록 행(인라인 텍스트
   흐름이라 gap이 없어 margin-left가 필요한 곳)과 같은 4px를 또 더하면 직위 글자와 유난히 떨어져 보였다
   ("외근 표시가 직위 쪽으로 더 붙게" 요청) — 여기서는 gap만으로 충분하니 그 여분을 뺀다. */
.profile-card-header .attendance-badge { margin-left: 0; }
/* 조직도/친구목록 행(contacts.js의 memberRow) — 직위를 이름과 같은 줄에 옅은 보조 텍스트로 붙인다
   (예전엔 별도 줄이라 세로 공간을 더 차지했다는 피드백). .name 안에 중첩되므로 굵기/크기만
   오버라이드하면 되고, .nick(대화명)과 같은 톤으로 통일해 "부가 정보"임을 시각적으로 구분한다. */
.list-item .meta .name .position { font-weight: 400; color: var(--text-dim); font-size: 13px; }
.list-item .meta .name .nick { font-weight: 400; color: var(--text-dim); font-size: 13px; }
.list-item .meta .name .status-text { font-weight: 400; color: var(--text-dim); font-size: 13px; }
/* 그룹방 목록의 인원수 — 처음엔 row1을 name과 나란한 형제로 두고 justify-content:space-between으로
   오른쪽 끝에 붙였는데, 이름이 짧은 방은 인원수만 뚝 떨어져 보인다는 피드백(카카오톡 캡처 참고 —
   이름 바로 옆에 작게 붙는 형태)으로 .name-line 안에 같이 묶었다. .name-line이 row1의 유일한
   자식이라 공간을 stretch하지 않고 내용 폭만큼만 차지하며, 그 안에서 .name만 줄어들다 "…"로
   잘리고(min-width:0) .member-count는 늘 잘리지 않고 바로 옆에 붙어 있는다(flex-shrink:0). */
.list-item .meta .row1 .name-line {
  display: flex; align-items: baseline; gap: 4px; min-width: 0; overflow: hidden;
}
.list-item .meta .row1 .name-line .name { flex: 0 1 auto; }
.list-item .meta .row1 .member-count {
  font-size: 12px; font-weight: 600; color: var(--primary); flex-shrink: 0;
}
/* 채팅 탭(chatList.js의 #room-list)만 이름/대화내용 폰트를 조금 줄여달라는 요청 — 연락처/조직도/
   친구목록도 같은 .name/.preview 클래스를 쓰므로, 거기엔 영향 없게 #room-list로 범위를 좁힌다. */
#room-list .list-item .meta .name { font-size: 14px; }
#room-list .list-item .meta .preview { font-size: 12px; }
/* .presence-tag(특별 상태 문구)/.presence-icons(접속기기 아이콘)는 이제 memberRow에서 row1 안이
   아니라 .list-item의 직속 자식으로 렌더링된다(contacts.js 참고) — 아바타와 나란히 .list-item의
   align-items:center로 항목 전체 높이 기준 가운데 정렬되고, 간격도 부모의 gap(12px)으로 처리되므로
   margin-left는 더 필요 없다. */
.presence-tag { font-size: 12px; color: var(--primary); font-weight: 500; flex-shrink: 0; }
.presence-tag.online { color: var(--online); }
.presence-tag.offline { color: var(--text-dim); font-weight: 400; }
.presence-icons { display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0; }
.presence-icons svg { width: 18px; height: 18px; display: block; }
.presence-icons.online { color: var(--online); }
.presence-icons.offline { color: var(--text-dim); }
/* .list-item-side(채팅 목록의 시간+안읽음 배지 세로 묶음)도 같이 적용 — 시간을 .meta 밖으로 뺐더니
   이 규칙이 더 이상 안 먹어서 글자가 커져 보이던 문제(원래 크기로 원복). */
.list-item .meta .time, .list-item-side .time { font-size: 11px; color: var(--text-dim); white-space: nowrap; }
.list-item .meta .preview {
  font-size: 13px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 상태메시지는 직급(.preview)과 다른 줄에, 기울임+따옴표로 "사실 정보"와 구분되는 자유 문구임을
   보여준다(연락처 조직도에서 직급 옆에 바로 붙어 있던 걸 분리해달라는 피드백). .list-item(조직도/
   친구목록)뿐 아니라 .my-profile-card(연락처 상단 내 프로필)에도 같은 스타일로 쓴다. */
.meta .status-message {
  font-size: 12px; color: var(--primary); font-style: italic; margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.meta .status-message::before { content: '\201C'; }
.meta .status-message::after { content: '\201D'; }
.list-item .meta .preview .sticker-preview-thumb { height: 20px; vertical-align: middle; }
.badge {
  background: var(--danger); color: white; font-size: 11px; min-width: 18px; height: 18px; border-radius: 9px;
  display: flex; align-items: center; justify-content: center; padding: 0 5px; flex-shrink: 0;
}
/* 채팅 목록의 시간/안읽음 배지 — 예전엔 배지가 .list-item의 직속 자식으로 따로 떠서 항목 전체
   높이 기준으로 가운데 정렬돼(시간과 무관하게) 있었는데, 카카오톡처럼 시간 바로 아래 배지가
   오도록 둘을 세로로 묶은 열로 뺐다(chatList.js 참고 — 채팅 목록 전용, 다른 화면의 .badge는
   그대로 예전 방식). */
.list-item-side {
  display: flex; flex-direction: column; align-items: flex-end; gap: 6px; flex-shrink: 0;
}
.section-title {
  padding: 14px 16px 6px; font-size: 12px; color: var(--text-dim); font-weight: 600;
}
.empty-state { padding: 40px 20px; text-align: center; color: var(--text-dim); font-size: 14px; }
/* "내 정보" 알림 안내 문구(안드로이드 배터리 최적화/iOS 홈 화면 추가) — profile.js의 notifyDelayHintHtml */
.settings-hint {
  margin: 4px 16px 12px; padding: 10px 12px; border-radius: 8px;
  background: var(--primary-dim); color: var(--text-dim); font-size: 12px; line-height: 1.5;
}
/* 확인 없이 잠깐 떴다 사라지는 안내(util.js의 showToast) — 예: 업데이트 설치가 백그라운드에서
   시작됐다는 안내. 다크 배경 고정 색을 써서(테마 무관하게 항상 어두운 배경+흰 글씨) 밝은/어두운
   테마 어느 쪽 위에 떠도 항상 또렷하게 읽힌다(image-viewer.html의 동일 용도 토스트와 같은 톤). */
.app-toast {
  position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%);
  background: rgba(20, 23, 28, .92); color: #fff; padding: 10px 18px; border-radius: 20px;
  font-size: 13px; max-width: calc(100% - 32px); text-align: center;
  z-index: 500; pointer-events: none; opacity: 0; transition: opacity .2s ease;
}
.app-toast.show { opacity: 1; }

/* ---- tabs (contacts) ---- */
.tabs { display: flex; background: var(--surface); border-bottom: 1px solid var(--border); }
.tabs button {
  flex: 1; padding: 10px; border: none; background: none; color: var(--text-dim); font-size: 14px; cursor: pointer;
  border-bottom: 2px solid transparent;
}
.tabs button.active { color: var(--primary); border-bottom-color: var(--primary); font-weight: 600; }

/* ---- 설정 화면(profile.js): 상단 메뉴 + 하단 상세 ---- */
/* 한동안 PC(마우스)는 좌측 세로 사이드바, 모바일은 상단 가로 탭으로 갈라뒀다가, 둘 다 좌측
   사이드바로 통일했다가, 다시 기기 구분 없이 상단 가로 탭으로 되돌렸다 — 최종적으로 PC/모바일
   전부 상단 탭 하나로 통일. */
.settings-shell { display: flex; flex-direction: column; height: 100%; }
.settings-menu {
  display: flex; overflow-x: auto; flex-shrink: 0;
  background: var(--surface); border-bottom: 1px solid var(--border);
}
.settings-menu button {
  flex: 1; padding: 12px 8px; border: none; background: none; white-space: nowrap;
  color: var(--text-dim); font-size: 13px; cursor: pointer; border-bottom: 2px solid transparent;
}
.settings-menu button.active { color: var(--primary); border-bottom-color: var(--primary); font-weight: 600; }
.settings-detail { flex: 1; overflow-y: auto; }

/* 설정 상세 영역 — 예전엔 항목이 화면 끝까지 이어지는 밋밋한 표 하나였는데, iOS/macOS 설정 화면처럼
   옅은 배경(--bg) 위에 항목을 둥근 카드(--surface)로 묶어서 요즘 앱에서 흔히 보는 그룹형 스타일로
   바꿨다. .info-list/.info-row/.profile-header는 연락처 프로필 팝업(contacts.js)/그룹방 설정
   (chatList.js)에서도 그대로 쓰는 공용 클래스라, 다른 화면까지 같이 바뀌지 않게 .settings-detail
   안에서만 덮어쓴다. */
/* 보안/화면/정보 탭은 .profile-header 같은 자체 여백이 없는 .info-list가 맨 위에 바로 오다 보니
   내용이 상단 경계에 바짝 붙어 답답해 보였다 — 위쪽 여백을 줘서 다른 탭(내정보)과도 통일감 있게 한다. */
.settings-detail { background: var(--bg); padding-top: 16px; padding-bottom: 16px; }
.settings-detail .profile-header { background: var(--surface); margin-bottom: 12px; }
.settings-detail .profile-header .avatar { box-shadow: 0 2px 8px rgba(0, 0, 0, .15); }
.settings-detail .info-list { margin: 0 16px 12px; border-radius: 12px; overflow: hidden; box-shadow: 0 1px 3px rgba(0, 0, 0, .06); }
.settings-detail .info-row { align-items: center; justify-content: space-between; gap: 12px; }
.settings-detail .info-row:last-child { border-bottom: none; }
.settings-detail .info-row .k { width: auto; flex-shrink: 0; color: var(--text); }
.settings-detail .info-row .v { flex: 0 1 auto; color: var(--text-dim); text-align: right; }
.settings-detail > button.secondary { border-radius: 12px; box-shadow: 0 1px 3px rgba(0, 0, 0, .06); }
.settings-detail #logout-btn { border-color: var(--danger); }

/* iOS 스타일 토글 스위치 — chatList.js의 "새 대화" 멤버 선택 체크박스(.pick-check)는 여러 명을 한 번에
   고르는 용도라 계속 작은 사각 체크박스로 두고, 설정 화면처럼 하나를 켜고/끄는 스위치에만 쓴다. */
.toggle-switch {
  -webkit-appearance: none; appearance: none; margin: 0;
  width: 44px; height: 26px; border-radius: 13px; background: var(--border);
  position: relative; flex-shrink: 0; cursor: pointer; transition: background-color .15s ease;
}
.toggle-switch::before {
  content: ''; position: absolute; top: 2px; left: 2px; width: 22px; height: 22px;
  border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
  transition: transform .15s ease;
}
.toggle-switch:checked { background: var(--primary); }
.toggle-switch:checked::before { transform: translateX(18px); }
.toggle-switch:disabled { opacity: .45; cursor: not-allowed; }
.group-header {
  padding: 10px 16px 4px; font-size: 13px; font-weight: 600; color: var(--text-dim);
  display: flex; justify-content: space-between; align-items: center;
}
/* 그룹 인원 수 — 예전엔 그냥 "(3)"처럼 괄호 텍스트였는데, Slack/Discord 채널 인원수처럼 옅은 배경의
   둥근 배지로 바꿨다(조직도/친구 그룹 헤더 공용, contacts.js 참고). */
.count-badge {
  display: inline-block; background: var(--border); color: var(--text-dim);
  font-size: 11px; font-weight: 600; padding: 1px 7px; border-radius: 10px; vertical-align: middle;
}
/* 조직도 트리(details/summary) — 예전엔 깊이마다 세로선(border-left)을 겹겹이 쌓아 표현했는데,
   들여쓰기 선이 여러 겹이면 스프레드시트/탐색기 같은 인상을 줘서 오히려 어수선해 보였다. 대신
   최상위 부서만 둥근 카드(그림자)로 묶고(설정 화면의 그룹형 카드와 통일감), 그 안의 하위부서/사원은
   같은 카드 안에서 구분선 + 깊이별 들여쓰기(--depth)만으로 표현한다. 클래스라 연락처 화면의 조직도
   탭뿐 아니라 "새 대화" 모달의 조직도 선택 탭에서도 그대로 재사용한다(다만 카드 스타일은 org-group-card
   클래스가 있는 곳에서만 적용되므로, 그 모달은 예전 방식 그대로 유지된다).
   --depth는 renderOrgTree(contacts.js)가 각 <details>에 인라인으로 심어주는 중첩 단계(0,1,2…). */
.org-tree { padding: 10px 12px 4px; }
.org-tree .org-group-card {
  background: var(--surface); border-radius: 12px; overflow: hidden;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .06); margin-bottom: 10px;
}
.org-tree .org-group-card:last-child { margin-bottom: 0; }
/* 카드 안의 하위부서(최상위가 아닌 details)만 위 구분선을 그어 바로 위 내용(사원 목록 또는 이전
   하위부서)과 갈라준다 — 카드 자체는 그림자로 이미 구분되므로 최상위엔 필요 없다. */
.org-tree details[data-group]:not(.org-group-card) { border-top: 1px solid var(--border); }
/* 부서명이 사원 이름(.list-item .name, 15px/600)보다 작고(13px) 옅은 색(--text-dim)이라 목록과 잘
   안 구분된다는 피드백이 있어, 부서명을 더 굵고 크게(700/14px) 항상 진한 색으로 보여주고, 옅은 배경
   띠(--bg)를 얹어 카드의 흰 사원 목록 영역과 시각적으로 확실히 갈라지게 했다(중첩된 하위부서 헤더도
   동일 — 최상위 카드 경계 말고는 구분 수단이 없던 깊은 depth일수록 이 배경 띠가 더 도움이 된다). */
/* 오른쪽 여백(인원 수 카운트 배지 쪽)은 depth와 무관하게 항상 16px 고정 — 부서 깊이마다 들쑥날쑥
   밀리지 않고 모든 카운트 배지가 같은 x좌표(오른쪽 끝)에 가지런히 정렬되게 한다. 왼쪽만 depth에
   따라 들여써서 계층을 보여준다(셰브런/부서명 쪽).
   조직도가 3~4단계까지 깊어질 수 있다는 요청으로 두 가지를 더했다: 1) min(--depth,3) — 들여쓰기가
   depth 3 이후로는 더 안 밀리게 캡을 씌워서, 좁은 화면에서 깊은 부서일수록 이름/인원 표시 공간이
   끝없이 줄어드는 것을 막는다. 2) color-mix로 depth가 깊을수록 헤더 배경을 아주 조금씩 더 진하게
   해서(캡도 depth 3까지만) 세로 들여쓰기 선 없이도 "지금 몇 단계인지"를 은은하게 구분해준다(색이
   미지원 브라우저면 바로 위 plain background 선언으로 조용히 대체됨). */
.org-tree summary.group-header {
  cursor: pointer; user-select: none; list-style: none;
  position: relative;
  padding: 12px 16px 12px calc(10px + min(var(--depth, 0), 3) * 16px);
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  background: var(--bg);
  background: color-mix(in srgb, var(--bg) calc(100% - min(var(--depth, 0), 3) * 6%), var(--border) calc(min(var(--depth, 0), 3) * 6%));
  color: var(--text); font-weight: 700; font-size: 14px;
  transition: background-color .1s ease;
}
.org-tree summary.group-header:hover { background: var(--border); }
.org-tree summary.group-header::-webkit-details-marker { display: none; }
/* 회전하는 셰브런: 예전엔 border 모서리를 45도 돌려 만든 CSS 삼각형이었는데, 굵기/각이 다른
   아이콘들과 안 맞아 어설퍼 보였다 — 앱 전역에서 쓰는 것과 같은 선 아이콘(SVG, stroke-width 2)으로
   바꿨다. group-title로 감싸 이름과 함께 묶어야 count-badge가 반대쪽 끝에 붙는 space-between이
   깨지지 않는다. 이 새 마크업(.group-title)이 없는 곳(예: "새 대화" 모달의 조직도 선택 탭 — 아직
   count-badge 등 예전 스타일 그대로라 손대지 않음)은 :not(:has())로 걸러 예전 CSS 삼각형을 그대로 쓴다. */
.org-tree summary.group-header .group-title {
  display: flex; align-items: center; gap: 4px; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.org-tree summary.group-header .chevron-icon {
  width: 14px; height: 14px; flex-shrink: 0; color: var(--text-dim);
  transition: transform .15s ease;
}
.org-tree details[open] > summary.group-header .chevron-icon { transform: rotate(90deg); }
.org-tree summary.group-header:not(:has(.group-title))::before {
  content: '';
  position: absolute; left: calc(18px + min(var(--depth, 0), 3) * 16px); top: 50%;
  width: 7px; height: 7px;
  border-style: solid; border-color: var(--text-dim); border-width: 0 2px 2px 0;
  transform: translateY(-50%) rotate(-45deg);
  transition: transform .15s ease;
}
.org-tree summary.group-header:not(:has(.group-title)) { padding-left: calc(40px + min(var(--depth, 0), 3) * 16px); }
.org-tree details[open] > summary.group-header:not(:has(.group-title))::before { transform: translateY(-50%) rotate(45deg); }
/* 사원 행은 소속 그룹의 헤더와 같은 들여쓰기를 쓴다 — 예전엔 한 단계(+40px) 더 밀어서 프로필이
   화면 오른쪽으로 쏠려 보였는데, 헤더와 맞춰 전체 폭을 더 넓게 쓰도록 줄였다. */
.org-tree .member-list .list-item { padding-left: calc(16px + min(var(--depth, 0), 3) * 16px); background: none; }
.org-tree .nick { font-weight: 400; color: var(--text-dim); font-size: 13px; }

/* 친구 탭의 그룹 카드 — 조직도 탭의 최상위 부서 카드(.org-group-card)와 같은 모양(둥근 모서리+그림자)
   으로 통일해서, 그룹명이 옅은 회색 한 줄로만 떠 있던 예전보다 그룹 구분이 뚜렷하게 보이게 한다.
   <details>/<summary>로 감싸서 조직도 탭처럼 접고 펼 수 있다(회전하는 셰브런도 동일한 패턴). */
.friends-tab { padding: 10px 12px 4px; }
.friends-tab .friend-group-card {
  background: var(--surface); border-radius: 12px; overflow: hidden;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .06); margin-bottom: 10px;
}
.friends-tab .friend-group-card:last-child { margin-bottom: 0; }
.friends-tab .friend-group-header {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 12px 8px 12px 16px; border-bottom: 1px solid var(--border);
  cursor: pointer; user-select: none; list-style: none;
}
.friends-tab .friend-group-header::-webkit-details-marker { display: none; }
.friends-tab .friend-group-header .group-title {
  display: flex; align-items: center; gap: 8px;
  font-size: 15px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.friends-tab .friend-group-header .chevron-icon {
  width: 14px; height: 14px; flex-shrink: 0; color: var(--text-dim);
  transition: transform .15s ease;
}
.friends-tab .friend-group-card[open] > .friend-group-header .chevron-icon { transform: rotate(90deg); }
.friends-tab .friend-group-header .group-actions { display: flex; gap: 2px; flex-shrink: 0; }
.friends-tab .friend-group-header .group-actions button {
  display: flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 8px; border: none; background: transparent;
  color: var(--text-dim); cursor: pointer; padding: 0;
}
.friends-tab .friend-group-header .group-actions button svg { width: 16px; height: 16px; }
.friends-tab .friend-group-header .group-actions button:hover { background: var(--bg); color: var(--text); }

/* ---- chat room ---- */
.chat-room { display: flex; flex-direction: column; height: 100%; position: relative; }
/* 파일 드래그앤드롭 시 대화방 전체 위에 뜨는 오버레이(평소엔 안 보임, 클릭/스크롤을 막지 않음). */
.drop-overlay {
  position: absolute; inset: 0; z-index: 50; display: none; align-items: center; justify-content: center;
  background: rgba(43, 108, 176, .12); pointer-events: none;
}
.drop-overlay.active { display: flex; }
.drop-overlay-inner {
  background: var(--surface); border: 2px dashed var(--primary); color: var(--primary);
  font-size: 15px; font-weight: 600; padding: 20px 28px; border-radius: 16px;
}
/* .messages 자신은 jumpToMessage/loadRoom이 innerHTML을 통째로 갈아치우므로, 그 안에는 "최신 대화로
   이동" 버튼을 못 둔다(다시 그릴 때마다 같이 사라짐) — 안 지워지는 이 래퍼에 버튼을 함께 두고
   position:absolute로 .messages 뷰포트 하단에 띄운다. */
.messages-wrap { flex: 1; min-height: 0; display: flex; flex-direction: column; position: relative; }
.messages { flex: 1; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 8px; }
/* 대화 내용 검색/날짜로 찾기로 옛날 대화로 이동했을 때만 보이는 버튼 — 맨 아래로 스크롤하거나
   눌러서 최신 대화로 돌아가면 다시 숨는다(chatRoom.js의 jumpToMessage/scroll 리스너 참고). */
.jump-to-latest-btn {
  position: absolute; bottom: 12px; left: 50%; transform: translateX(-50%); z-index: 5;
  display: flex; align-items: center; gap: 6px;
  background: var(--surface); color: var(--primary); border: 1px solid var(--border);
  border-radius: 20px; padding: 8px 14px; font-size: 13px; font-weight: 600;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .15); cursor: pointer;
}
.jump-to-latest-btn:hover { background: var(--bg); }
.jump-to-latest-btn[hidden] { display: none; }
.jump-to-latest-btn svg { width: 15px; height: 15px; flex-shrink: 0; }
/* 위로 스크롤해서 과거 메시지를 이어붙일 때 맨 위에 잠깐 뜨는 표시 — 작은 회전 스피너를 곁들인다
   (.centered-loading과 같은 @keyframes spin 재사용, app.css 아래쪽 참고). */
.older-loading {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  font-size: 12px; color: var(--text-dim); padding: 6px 0;
}
.older-loading::before {
  content: '';
  width: 12px; height: 12px; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--primary);
  animation: spin .7s linear infinite;
}
.msg-row { display: flex; }
.msg-row.mine { justify-content: flex-end; }
/* 모바일(터치)에서는 카카오톡처럼 길게 누르기로 메뉴를 띄우므로, 길게 누를 때 브라우저 기본
   텍스트선택/말풍선 메뉴가 먼저 뜨지 않게 막는다. 복사는 메뉴의 "복사" 버튼으로 대신한다. */
.bubble:not(.deleted-bubble) {
  -webkit-touch-callout: none; -webkit-user-select: none; user-select: none;
}
/* PC(마우스)에서는 반대로 마우스 드래그로 글자를 직접 선택(반전)할 수 있어야 하므로 다시 풀어준다
   — 우클릭/롱프레스로 띄운 메뉴의 "복사"를 누르면 이 선택 영역만 복사된다(chatRoom.js 참고). */
@media (pointer: fine) {
  .bubble:not(.deleted-bubble) {
    -webkit-user-select: text; user-select: text;
  }
}

/* 말풍선을 길게 눌렀을 때 뜨는 카카오톡 스타일 미니 메뉴(복사/삭제 등). 본인 메시지는 리액션이
   빠져서 최대 4개(공지고정/답장/복사/삭제)뿐이라 웬만한 화면 너비에서 한 줄에 다 들어간다 —
   그래서 폭을 280px로 미리 좁혀두지 않고 화면 너비만큼 쓰게 한다. 그래도 아주 좁은 화면에서
   넘치면(flex-wrap) 다음 줄로 자연스럽게 넘어간다.
   버튼은 flex:1 1 auto로 남는 공간까지 억지로 늘어나면(줄바꿈된 마지막 항목 혼자 화면 끝까지
   늘어나 보기 나빴다) 안 되므로 flex:0 0 auto로 내용 너비만큼만 차지하게 한다.
   width는 auto(기본값) 대신 max-content로 지정한다 — width:auto인 flex 컨테이너는 내용이
   max-width보다 좁아도 브라우저가 알아서 한 줄에 다 들어가는 폭으로 맞춰주지 않고 일부만
   줄바꿈해버리는 경우가 있어서(실측으로 확인), 항상 "한 줄로 쳤을 때의 실제 너비"를 우선
   쓰게 하고 그게 max-width를 넘을 때만 줄바꿈되게 한다. */
.msg-context-menu {
  position: fixed; z-index: 200; display: flex; flex-wrap: wrap;
  width: max-content; max-width: calc(100vw - 24px);
  background: #2c2c2ee6; border-radius: 10px;
  overflow: hidden; box-shadow: 0 4px 16px rgba(0,0,0,.3); animation: msg-context-pop .12s ease-out;
}
.msg-context-menu button {
  background: none; border: none; color: #fff; font-size: 13.5px; padding: 11px 12px;
  white-space: nowrap; cursor: pointer; flex: 0 0 auto;
}
.msg-context-menu button + button { border-left: 1px solid rgba(255,255,255,.18); }
.msg-context-menu button.danger { color: #ff6b6b; }
/* 말풍선 아래 이모지 리액션 뱃지. 비어있으면(리액션 없음) 자리를 차지하지 않게 숨긴다. */
.msg-reactions { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 2px; }
.msg-reactions:empty { display: none; }
.msg-row.mine .msg-reactions { justify-content: flex-end; }
.reaction-pill {
  display: inline-flex; align-items: center; gap: 3px; font-size: 12px; line-height: 1;
  border: 1px solid var(--border); background: var(--surface); border-radius: 12px;
  padding: 3px 8px; cursor: pointer; color: var(--text);
}
.reaction-pill.mine { border-color: var(--primary); background: var(--primary-dim); color: var(--primary); }
/* 롱프레스 메뉴에서 "리액션"을 고르면 뜨는 이모지 선택 팝업. */
.emoji-picker {
  position: fixed; z-index: 200; display: flex; gap: 2px; background: var(--surface);
  border: 1px solid var(--border); border-radius: 24px; padding: 6px 8px;
  box-shadow: 0 4px 16px rgba(0,0,0,.2); animation: msg-context-pop .12s ease-out;
}
.emoji-picker button {
  background: none; border: none; font-size: 22px; line-height: 1; padding: 4px; cursor: pointer;
  border-radius: 50%;
}
.emoji-picker button:active { background: var(--primary-dim); }

/* 공지(고정 메시지) 배너 — 대화방 상단바 바로 아래 고정. */
.pinned-banner {
  display: flex; align-items: center; gap: 8px; padding: 8px 14px; background: var(--primary-dim);
  border-bottom: 1px solid var(--border); font-size: 12.5px; cursor: pointer;
}
/* 펼쳐진 상태는 여러 줄이 될 수 있어 아이콘/버튼을 세로 중앙이 아니라 위쪽에 맞춘다. */
.pinned-banner.expanded { align-items: flex-start; }
.pinned-banner-body { flex: 1; min-width: 0; }
.pinned-banner-text {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); display: block;
}
.pinned-banner.expanded .pinned-banner-text {
  white-space: normal; overflow: visible; text-overflow: clip; word-break: break-word;
}
.pinned-banner-meta { display: none; margin-top: 3px; font-size: 11px; color: var(--text-dim); }
.pinned-banner.expanded .pinned-banner-meta { display: block; }
.pinned-banner-expand {
  background: none; border: none; font-size: 11px; color: var(--text-dim); cursor: pointer; flex-shrink: 0;
  padding: 2px 4px;
}
.pinned-banner-close { background: none; border: none; font-size: 14px; color: var(--text-dim); cursor: pointer; flex-shrink: 0; }

/* @멘션 하이라이트 */
.mention { color: var(--primary); font-weight: 600; }
.mention-me { background: var(--primary-dim); border-radius: 4px; padding: 0 2px; }
.msg-row.mentioned-me .bubble:not(.deleted-bubble) { box-shadow: 0 0 0 2px var(--primary) inset; }
/* 메시지 안의 URL 링크 — 말풍선 글자색을 그대로 물려받고(파란 말풍선 안에서도 파란 글자로 안 묻히게)
   밑줄만 그어서 눌러볼 수 있는 링크라는 걸 표시한다. 긴 URL이 말풍선 밖으로 안 튀어나가게 줄바꿈도 허용. */
.bubble a { color: inherit; text-decoration: underline; word-break: break-all; }
/* 메시지 안 마크다운(매터모스트에서 이관된 대화 등) 최소 스타일 — 좁은 말풍선 안에서도 자연스럽게
   보이도록 본문 대비로만 살짝 키우고 여백도 촘촘하게. 색은 currentColor로 말풍선 글자색을 그대로
   물려받는다(내 파란 말풍선/상대 흰 말풍선 둘 다 자동으로 어울리게). */
.bubble .md-h { font-weight: 700; margin: 4px 0 2px; line-height: 1.3; }
.bubble .md-h1 { font-size: 1.25em; }
.bubble .md-h2 { font-size: 1.15em; }
.bubble .md-h3 { font-size: 1.08em; }
.bubble .md-h4 { font-size: 1.03em; }
.bubble .md-quote { margin: 4px 0; padding: 2px 10px; border-left: 3px solid currentColor; opacity: .85; }
.bubble .md-hr { border: none; border-top: 1px solid currentColor; opacity: .3; margin: 8px 0; }
.bubble .md-list { margin: 4px 0; padding-left: 1.3em; }
.bubble .md-list li { margin: 2px 0; }
.bubble .md-table-wrap { overflow-x: auto; margin: 6px 0; max-width: 100%; }
.bubble .md-table { border-collapse: collapse; font-size: 0.92em; }
.bubble .md-table th, .bubble .md-table td { border: 1px solid currentColor; padding: 4px 8px; text-align: left; opacity: .95; }
.bubble .md-table th { font-weight: 700; background: rgba(128, 128, 128, .18); }
.bubble code { background: rgba(128, 128, 128, .18); border-radius: 4px; padding: 1px 5px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.9em; }

/* @멘션 자동완성 목록(입력창 바로 위) */
.mention-picker {
  position: absolute; left: 8px; right: 8px; bottom: 66px; z-index: 60;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  max-height: 180px; overflow-y: auto; box-shadow: 0 -4px 16px rgba(0,0,0,.12);
}
.mention-candidate { padding: 10px 14px; font-size: 14px; cursor: pointer; }
.mention-candidate:hover, .mention-candidate:active { background: var(--primary-dim); }
.mention-candidate .nick { color: var(--text-dim); font-size: 12px; }

/* 이모티콘 선택 팝업 — @멘션 팝업과 같은 자리. "기본"(문자 삽입)/"스티커"(이미지 전송) 두 탭 */
.emoticon-picker {
  position: absolute; left: 8px; right: 8px; bottom: 66px; z-index: 60;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  max-height: 260px; box-shadow: 0 -4px 16px rgba(0,0,0,.12);
  display: flex; flex-direction: column; overflow: hidden;
}
.emoticon-tabs { display: flex; border-bottom: 1px solid var(--border); flex-shrink: 0; }
.emoticon-tab {
  flex: 1; padding: 8px; border: none; background: none; cursor: pointer;
  font-size: 13px; color: var(--text-dim);
}
.emoticon-tab.active { color: var(--primary); font-weight: 600; box-shadow: inset 0 -2px 0 var(--primary); }
.emoticon-grid {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: 2px; padding: 8px; overflow-y: auto;
}
.emoticon-item {
  background: none; border: none; cursor: pointer; border-radius: 8px; padding: 6px; font-size: 22px;
  display: flex; align-items: center; justify-content: center;
}
.emoticon-item:hover, .emoticon-item:active { background: var(--primary-dim); }
.emoticon-item img { width: 36px; height: 36px; }
/* 말풍선 안이 아니라 이미지 그 자체를 메시지로 보여준다(카카오톡 이모티콘처럼 배경/테두리 없음) */
.sticker-bubble { width: 96px; height: 96px; display: block; }

/* 커스텀(사용자 등록) 이모티콘 탭 — 각 타일 우상단에 등록자 본인(또는 관리자)에게만 삭제 버튼이 뜬다 */
.custom-emoticon-notice {
  grid-column: 1 / -1; font-size: 11px; color: var(--text-dim); padding: 2px 4px 6px; line-height: 1.4;
}
.custom-emoticon-item { position: relative; }
.custom-emoticon-remove {
  position: absolute; top: -2px; right: -2px; width: 16px; height: 16px; line-height: 14px;
  border-radius: 50%; border: none; background: var(--text-dim); color: var(--surface);
  font-size: 10px; cursor: pointer; padding: 0;
}
.custom-emoticon-add {
  background: none; border: 1px dashed var(--border); cursor: pointer; border-radius: 8px; padding: 6px;
  font-size: 20px; color: var(--text-dim); display: flex; align-items: center; justify-content: center;
}
.custom-emoticon-add:hover, .custom-emoticon-add:active { background: var(--primary-dim); }

/* 과거에 수정된 적 있는 메시지 표시(수정 기능 자체는 제거됐지만, 이미 수정 이력이 있는 메시지엔 계속 남겨서 보여준다) */
.edited-tag { font-size: 11px; color: var(--text-dim); margin-left: 4px; }
.bubble.deleted-bubble .edited-tag { display: none; }

@keyframes msg-context-pop {
  from { opacity: 0; transform: scale(.92); }
  to { opacity: 1; transform: scale(1); }
}
/* 말풍선 폭 상한은 반드시 폭이 확정된 조상(.msg-row, 화면 폭 기준)에 대해 걸어야 한다.
   예전엔 이 max-width가 .bubble 자신에게 있었는데, .bubble의 부모(.bubble-stack)가 내용에 맞춰
   저절로 줄어드는(shrink-to-fit) 요소라서 "내용 크기의 X%"라는 자기참조적인 값이 되어버렸고,
   그 결과 "진동4"처럼 짧은 메시지도 실제 필요한 폭보다 훨씬 좁게 잘려서 글자 하나마다 줄바꿈되는
   버그가 있었다(화면 여유 공간과 무관하게 발생). 폭이 확정된 .msg-row의 직계 자식(.msg-content)에
   상한을 걸면 이 문제가 없어진다.
   상한 값은 75%였다가 82%로 올렸다 — 이 안에 말풍선(.bubble-stack)과 옆의 시간(.msg-time)이
   같은 줄에 함께 들어가야 하는데, 앱 전체가 480px 폭 셸(.app-shell) 안에서만 표시되는 걸 감안하면
   75%는 실제로 화면에 남는 여백이 충분한데도 시간 몇 px 때문에 메시지가 불필요하게 줄바꿈되는
   경우가 있었다(실측: 말풍선+시간에 337.6px 필요한데 75% 상한은 330.75px뿐이라 7px 부족해서
   줄바꿈됨, 정작 방 폭은 441px로 여유가 있었음). */
.msg-content { max-width: 82%; min-width: 0; }
.bubble {
  max-width: 100%; padding: 9px 13px; border-radius: 16px;
  /* 내정보에서 고른 대화창 폰트/크기(profile.js, chatFont.js) — 안 골랐으면 기본값 그대로. */
  font-family: var(--chat-font-family, inherit); font-size: var(--chat-font-size, 14.5px); line-height: 1.4;
  /* 한글은 영어와 달리 공백이 없어도 음절 사이 어디서든 줄바꿈이 허용되는 문자라, word-break:break-word를
     쓰면 단어/공백 경계가 아니라 음절 중간에서 끊기기 쉽다. keep-all로 공백 기준 줄바꿈을 우선하고,
     그래도 폭을 넘는 긴 토큰(URL 등)만 overflow-wrap으로 최후 수단으로 강제 줄바꿈한다. */
  white-space: pre-wrap; word-break: keep-all; overflow-wrap: break-word;
}
.msg-row.mine .bubble { background: var(--bubble-mine); color: var(--bubble-mine-text); border-bottom-right-radius: 4px; }
/* 상대 말풍선은 배경이 흰색(라이트 테마)이라 옅은 테두리를 둘러서 페이지 배경(--bg)과 구분되게 한다. */
.msg-row.theirs .bubble { background: var(--bubble-theirs); color: var(--text); border: 1px solid var(--border); border-bottom-left-radius: 4px; }
.msg-row.mine .bubble.deleted-bubble,
.msg-row.theirs .bubble.deleted-bubble {
  background: transparent; border: 1px solid var(--border); color: var(--text-dim); font-style: italic;
}
.msg-row.mine.deleted { cursor: default; }
.bubble-stack { display: flex; flex-direction: column; gap: 4px; max-width: 100%; }
.msg-row.mine .bubble-stack { align-items: flex-end; }
.msg-row.theirs .bubble-stack { align-items: flex-start; }
/* max-width/max-height(가변 크기)였을 땐 이미지가 실제로 로드되기 전까지 자리를 전혀 차지하지 않다가,
   로드되자마자 실제 크기로 갑자기 늘어나며 그 아래 메시지들을 밀어내고 스크롤이 튀는(깜빡이는) 원인이
   됐다 — 고정 크기(+object-fit:cover)로 바꿔서 <img> 태그가 그려지는 즉시(이미지가 아직 안 왔어도)
   최종 크기만큼 자리를 미리 차지하게 한다. 스티커 말풍선(.sticker-bubble)도 이미 같은 이유로 고정
   크기를 쓰고 있다. */
.attachment-image {
  width: 220px; height: 180px; border-radius: 12px; display: block; object-fit: cover; cursor: pointer;
}
/* .attachment-image가 display:block이라, 그걸 감싸는 <a>(기본 display:inline)는 block 요소를
   담은 inline 요소로 취급돼 브라우저가 부모 컨테이너 가득 넓게(block처럼) 그려버린다 — 실제 사진은
   220x180만 차지하는데 그 오른쪽 빈 공간을 눌러도 이미지 뷰어가 열리는 문제가 있었다("이미지 영역
   까지만 눌렀을 때 뷰어가 열리게 해달라"는 요청). inline-block으로 바꿔 <a> 자신도 내용물(사진) 크기에
   딱 맞게 감싸지도록 한다(채팅방/쪽지 팝업 둘 다 이 클래스를 공유해서 씀).
   */
.attachment-image-link { display: inline-block; }
.attachment-card {
  display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: 12px;
  background: var(--bubble-theirs); color: var(--text); text-decoration: none; max-width: 240px;
}
.attachment-icon { font-size: 22px; flex-shrink: 0; }
.attachment-info { display: flex; flex-direction: column; min-width: 0; }
.attachment-name {
  font-family: var(--chat-font-family, inherit); font-size: var(--chat-font-size, 14.5px);
  /* 예전엔 한 줄로 자르고 말줄임표(...)를 붙였는데, 파일명이 안 잘리고 전부 보이도록 줄바꿈으로 바꿨다. */
  white-space: normal; word-break: break-all;
}
.attachment-size { font-size: 11px; color: var(--text-dim); }
/* 링크 미리보기 카드(chatRoom.js의 linkPreviewHtml) — 서버가 메시지 저장 후 비동기로 채워 넣으므로
   컨테이너(.link-preview-holder)는 항상 그려두고 비어있으면 아무 크기도 안 차지한다(.msg-reactions와
   동일 패턴). 보낸 사람 말풍선 색과 무관하게 카카오톡/슬랙처럼 중립적인 카드 톤을 쓴다. */
.link-preview {
  display: block; width: 240px; max-width: 100%; border-radius: 12px; overflow: hidden;
  background: var(--surface); border: 1px solid var(--border); text-decoration: none; color: inherit;
}
.link-preview-img {
  width: 100%; height: 120px; background-size: cover; background-position: center; background-color: var(--bg);
}
.link-preview-body { padding: 8px 10px; }
.link-preview-site {
  font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: .02em; margin-bottom: 2px;
}
.link-preview-title {
  font-size: 13px; font-weight: 600; color: var(--text);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.link-preview-desc {
  font-size: 12px; color: var(--text-dim); margin-top: 2px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* white-space:nowrap이 없으면 말풍선 텍스트가 길어서 옆 공간이 좁아질 때 "오전"과 "11:20" 사이
   공백에서 시간 자체가 줄바꿈돼버린다(말풍선이 줄어드는 대신 시간이 줄어드는 게 맞다). */
.msg-time { font-size: 10px; color: var(--text-dim); margin: 0 6px; align-self: flex-end; white-space: nowrap; flex-shrink: 0; }
.read-tag { font-size: 10px; color: #f0b400; font-weight: 700; align-self: flex-end; margin: 0 2px 2px; }
/* 내 메시지는 안읽음 수(read-tag)와 시각(msg-time)을 가로로 나란히(한 줄로) 묶어서 말풍선 옆에 둔다. */
.msg-meta { display: flex; flex-direction: row; align-items: flex-end; gap: 2px; }
.typing-indicator { font-size: 12px; color: var(--text-dim); padding: 0 16px 6px; }
/* 연결이 끊겨 못 보낸 메시지(chatRoom.js의 failedBubbleHtml) — 진짜로 전송된 말풍선과 헷갈리지
   않게 옅게 흐려두고, "다시 보내기" 버튼을 눌러 재시도할 수 있게 한다. */
.msg-row.failed-send .bubble,
.msg-row.failed-send .sticker-bubble { opacity: .55; }
.resend-btn {
  background: none; border: none; padding: 0; margin: 0 2px 2px;
  font-size: 11px; font-weight: 600; color: var(--danger); white-space: nowrap; cursor: pointer;
}
.resend-btn:hover { text-decoration: underline; }

/* 나가기/강퇴처럼 대화방 자체의 변화를 알리는 시스템 안내(카카오톡의 회색 가운데 문구). */
.system-notice { text-align: center; font-size: 12px; color: var(--text-dim); padding: 4px 0; }
/* 메시지 전송 실패(연결 끊김) 안내(chatRoom.js의 sendChatMessage) — 일반 시스템 안내(회색, 눈에 잘
   안 띔)와 달리 실제 문제 상황이라 바로 눈에 띄어야 해서 경고 톤으로 구분한다. */
.system-notice.warning { color: var(--danger); font-weight: 500; }
/* 날짜가 바뀔 때 대화 중간에 끼워넣는 구분선 — 선이 아니라 카카오톡처럼 알약 모양 뱃지로 표시. */
.date-divider { display: flex; justify-content: center; margin: 14px 0; }
.date-divider span {
  background: var(--primary-dim); color: var(--primary); font-size: 12px; font-weight: 600;
  padding: 5px 14px; border-radius: 999px;
}
/* 검색결과/답장 인용을 눌러 이동했을 때 대상 말풍선을 잠깐 강조. */
.msg-row.highlight .bubble:not(.deleted-bubble) { outline: 2px solid var(--primary); outline-offset: 2px; }

/* 말풍선 안, 본문 위에 붙는 답장 인용 블록(누르면 원본으로 이동). */
.reply-quote {
  display: flex; flex-direction: column; gap: 1px; padding: 6px 10px; border-left: 3px solid var(--primary);
  border-radius: 8px; background: rgba(43,108,176,.08); font-size: 12.5px; cursor: pointer; max-width: 100%;
}
.reply-quote-sender { font-weight: 600; color: var(--primary); }
.reply-quote-body { color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.msg-row.mine .bubble.deleted-bubble ~ .reply-quote,
.msg-row.mine .reply-quote { background: rgba(255,255,255,.16); border-left-color: rgba(255,255,255,.6); }
.msg-row.mine .reply-quote .reply-quote-sender { color: var(--bubble-mine-text); }
.msg-row.mine .reply-quote .reply-quote-body { color: rgba(255,255,255,.85); }

/* 작성 중 답장 대상 미리보기(취소 가능), 입력창 바로 위에 붙는다. */
.reply-preview-bar {
  display: flex; align-items: center; gap: 8px; padding: 8px 14px; background: var(--bg);
  border-top: 1px solid var(--border); font-size: 12.5px;
}
.reply-preview-bar .reply-preview-body {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-dim);
}
.reply-preview-bar .reply-preview-sender { font-weight: 600; color: var(--primary); margin-right: 6px; }
.reply-preview-bar button { background: none; border: none; font-size: 16px; color: var(--text-dim); cursor: pointer; }

/* 이미지 붙여넣기(Ctrl+V) 미리보기 — 전송 버튼을 눌러야 실제로 나간다(실수로 바로 전송 방지) */
.paste-preview-bar {
  display: flex; gap: 8px; padding: 10px 14px; background: var(--bg);
  border-top: 1px solid var(--border); overflow-x: auto;
}
.paste-preview-item { position: relative; flex-shrink: 0; }
.paste-preview-item img {
  width: 64px; height: 64px; object-fit: cover; border-radius: 10px; display: block; border: 1px solid var(--border);
}
.paste-preview-remove {
  position: absolute; top: -6px; right: -6px; width: 20px; height: 20px; border-radius: 50%;
  background: var(--text-dim); color: #fff; border: 2px solid var(--bg); font-size: 11px; line-height: 1;
  cursor: pointer; padding: 0;
}

.composer {
  display: flex; align-items: flex-end; gap: 8px; padding: 10px 12px calc(env(safe-area-inset-bottom, 0px) + 10px);
  background: var(--surface); border-top: 1px solid var(--border);
}
.composer textarea {
  flex: 1; min-height: 40px; max-height: 120px; padding: 10px 14px; border-radius: 20px;
  scrollbar-width: none; -ms-overflow-style: none; /* 스크롤은 되지만 스크롤바는 안 보이게 */
  /* 브라우저 기본 textarea 크기조절 손잡이(모서리의 빗금 아이콘)도 없앤다 — 자동 높이조절은
     JS(app.js)가 따로 담당하고 있어서 이 손잡이는 그냥 시각적으로 거슬리는 잔재일 뿐이고,
     없애도 자동 높이조절이나 내용이 넘칠 때의 스크롤 자체에는 전혀 영향이 없다. */
  resize: none;
  /* 내정보에서 고른 대화창 폰트/크기(.bubble과 동일한 변수)를 입력창에도 그대로 적용 — 보낸 뒤
     말풍선에 찍히는 글자와 입력하는 동안 보이는 글자가 서로 다른 폰트면 어색하다. */
  font-family: var(--chat-font-family, inherit); font-size: var(--chat-font-size, 14.5px);
}
.composer textarea::-webkit-scrollbar { display: none; }
/* 알림봇 1:1 방처럼 메시지를 보낼 수 없는 방의 입력창 — 자리표시자 문구로 이유를 알려주고 흐리게 잠근다.
   한 줄로 고정하고(여러 줄로 늘어나 화면이 커졌다 작아졌다 하는 것보다 깔끔함), 너비에 안 맞는
   부분은 "…"로 줄인다 — text-overflow:ellipsis는 textarea에서 렌더링되지 않아(모든 브라우저 공통
   제약) chatRoom.js에서 실제 문구 자체를 캔버스로 측정해 직접 잘라낸다(refreshLockPlaceholder).
   여기 white-space:nowrap/overflow:hidden은 혹시 계산이 살짝 어긋나도 줄바꿈되지 않게 하는 안전장치. */
.composer textarea:disabled {
  background: var(--bg); color: var(--text-dim); cursor: default;
  white-space: nowrap; overflow: hidden;
}
.composer button {
  border: none; background: var(--primary); color: white; border-radius: 50%; width: 40px; height: 40px;
  font-size: 16px; cursor: pointer; flex-shrink: 0;
}
.composer button#attach-btn,
.composer button#emoticon-btn {
  background: var(--primary-dim); color: var(--primary); font-size: 18px;
}
.composer button#attach-btn:disabled { opacity: .6; cursor: default; }
.composer button:disabled { opacity: .4; }

/* ---- profile ---- */
.profile-header { display: flex; flex-direction: column; align-items: center; padding: 28px 16px; gap: 10px; }
.profile-header .avatar { width: 84px; height: 84px; font-size: 28px; }
.profile-header h2 { margin: 0; font-size: 18px; }
.profile-header p { margin: 0; color: var(--text-dim); font-size: 13px; }
/* 프로필 상세 팝업(contacts.js showProfileModal) 상단 — 큰 사진 + 이름/직위 + 조직·사번 칩. 위쪽에
   옅은 브랜드색 그라데이션을 깔아 빈 여백 없이 카드처럼 보이게 한다. (.profile-header는 설정 > 내정보
   탭이 그대로 쓰는 공용 클래스라 건드리지 않고 별도 클래스로 둔다.) */
.profile-card-header {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 22px 16px 18px; margin-bottom: 4px; border-radius: 16px;
  background: linear-gradient(180deg, var(--primary-dim) 0%, transparent 100%);
}
.profile-card-header .avatar {
  width: 96px; height: 96px; font-size: 32px; border: 3px solid var(--surface);
  box-shadow: 0 4px 14px rgba(0, 0, 0, .18);
}
.profile-card-header h2 {
  /* align-items:baseline이었을 때, 텍스트(이름/직위)는 글자 기준선끼리 자연스럽게 맞아도 필(pill)
     배지처럼 자기 테두리/패딩을 가진 요소는 그 "기준선"이 눈으로 보는 중심과 달라 위로 붕 뜬
     것처럼 보였다("외근 표시가 여전히 라인이 안 맞는다" 신고 — 배지에만 align-self:center를 줬던
     시도로는 이 기준선 자체의 부정확함을 완전히 못 없앴다). 아예 전부 center로 맞추면 요소 종류와
     무관하게 항상 정확히 줄 중앙에 맞는다 — 이름(21px)과 직위(15px)처럼 글자 크기가 달라도 각자의
     중심이 나란히 놓여 자연스럽다. */
  margin: 4px 0 0; display: flex; align-items: center; justify-content: center; gap: 6px;
  flex-wrap: wrap; text-align: center;
}
.profile-card-header .pc-name { font-size: 21px; font-weight: 700; }
.profile-card-header .pc-position { font-size: 15px; font-weight: 500; color: var(--text-dim); }
.profile-card-header .pc-chips { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; }
.profile-card-header .pc-chip {
  padding: 4px 11px; border-radius: 999px; font-size: 12.5px; line-height: 1.3;
}
.profile-card-header .pc-chip-org { background: var(--primary-dim); color: var(--primary); font-weight: 600; }
.profile-card-header .pc-chip-id {
  background: var(--bg); color: var(--text-dim); border: 1px solid var(--border);
  font-variant-numeric: tabular-nums; letter-spacing: .02em;
}
.profile-card-header .pc-status { font-size: 13.5px; font-weight: 700; }
.profile-card-header .pc-status-message {
  max-width: 100%; font-size: 13px; color: var(--text-dim); text-align: center; line-height: 1.45;
  overflow-wrap: anywhere; font-style: italic;
}
.profile-card-header .pc-birthday { font-size: 13px; font-weight: 700; color: #d1479a; }
.profile-card-header .pc-chip-status { background: var(--surface); color: var(--text); border: 1px solid var(--border); font-weight: 600; }
/* 쪽지 검색 팝업(memos.js openMemoSearchModal) — 시작/종료 일자를 한 줄에 나란히. */
.memo-search-dates { display: flex; align-items: flex-end; gap: 8px; }
.memo-search-dates label { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--text-dim); }
.memo-search-dates input {
  width: 100%; min-width: 0; box-sizing: border-box; font: inherit; font-size: 14px; color: var(--text);
  padding: 10px 10px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface);
}
.memo-search-dash { padding-bottom: 12px; color: var(--text-dim); }
.memo-search-summary { font-size: 12px; color: var(--text-dim); }
.memo-search-summary:empty { display: none; }
.info-list { background: var(--surface); }
.info-row { display: flex; padding: 12px 16px; border-bottom: 1px solid var(--border); font-size: 14px; }
.info-row .k { width: 90px; color: var(--text-dim); flex-shrink: 0; }
.info-row .v { flex: 1; }
/* select는 기본적으로 폰트를 상속받지 않아 "테마"/"상태" 글자와 다른 글꼴로 보이던 것을 맞춘다 */
#theme-select, #presence-select, #chat-font-family-select, #effect-select { font: inherit; padding: 4px 6px; }

.fab-bar { display: flex; gap: 8px; padding: 12px 16px; }

.modal-backdrop {
  position: fixed; inset: 0; background: rgba(0,0,0,.4); display: flex; align-items: flex-end; z-index: 100;
}
.modal-sheet {
  background: var(--surface); width: 100%; max-width: 480px; margin: 0 auto; border-radius: 16px 16px 0 0;
  padding: 20px; display: flex; flex-direction: column; gap: 12px;
  padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 20px);
  /* 액션/후보 목록이 길면(예: 초대 대상, 그룹 선택) 화면 밖으로 잘리지 않고 시트 안에서 스크롤되게 한다. */
  max-height: 80vh;
  overflow-y: auto;
  /* 프로필 보기(contacts.js)처럼 우측 상단에 닫기 아이콘을 얹는 화면을 위한 위치 기준점. */
  position: relative;
}
.modal-sheet h3 { margin: 0; font-size: 16px; }
/* showConfirm/showAlert(util.js)의 본문 문구 — 시스템 confirm()/alert() 대신 쓰는 공용 팝업(그룹 삭제,
   업데이트 확인 등)의 메시지 줄. */
.modal-message { margin: 0; font-size: 14px; color: var(--text); line-height: 1.5; white-space: pre-line; }
/* 쪽지 쓰기 등에서 PC 드래그앤드롭으로 파일을 끌어다 놓을 때(memos.js) 놓을 수 있는 곳임을 알려준다. */
.modal-sheet.drag-over { outline: 2px dashed var(--primary); outline-offset: -8px; background: var(--primary-dim); }

/* 조직/그룹 탭의 "바로가기" 인앱팝업(contacts.js의 openShortcutsModal) — 데이터 관리자에 등록된 링크 목록. */
.shortcut-list { display: flex; flex-direction: column; gap: 8px; }
.shortcut-item {
  display: flex; flex-direction: column; gap: 2px; padding: 12px 14px; border-radius: 12px;
  background: var(--surface-alt, var(--bubble-theirs)); border: 1px solid var(--border);
  color: var(--text); text-decoration: none;
  /* SSO 항목(그룹웨어)은 <a> 대신 <button>으로 그려서(누른 시점에 티켓을 새로 받아야 하므로) 기본
     버튼 스타일을 지우고 위의 <a> 항목과 완전히 같은 모양으로 맞춘다. */
  width: 100%; text-align: left; font: inherit; cursor: pointer;
}
.shortcut-item:disabled { opacity: .6; cursor: default; }
.shortcut-title { font-size: 14px; font-weight: 600; }
.shortcut-url { font-size: 12px; color: var(--text-dim); word-break: break-all; }
.shortcut-sso-tag {
  font-size: 10px; font-weight: 700; color: var(--primary); background: var(--primary-dim);
  border-radius: 4px; padding: 1px 5px; margin-left: 4px; vertical-align: middle;
}

/* 채팅방 "날짜로 찾기" 달력(chatRoom.js) — 메시지가 있는 날짜만 활성화해서 누르면 그 날짜로 이동한다. */
.calendar-header { display: flex; align-items: center; justify-content: space-between; }
.calendar-header #cal-title { font-weight: 600; font-size: 14px; }
.calendar-header button {
  background: none; border: none; font-size: 20px; line-height: 1; color: var(--primary);
  cursor: pointer; padding: 4px 12px; border-radius: 8px;
}
.calendar-header button:hover { background: var(--bg); }
.calendar-weekdays {
  display: grid; grid-template-columns: repeat(7, 1fr); text-align: center;
  font-size: 12px; color: var(--text-dim); padding: 0 2px;
}
.calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; padding: 0 2px; }
.cal-day {
  aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
  border: none; background: none; border-radius: 50%;
  font-size: 13px; color: var(--border); cursor: default;
}
.cal-day.active { color: var(--text); background: var(--primary-dim); font-weight: 600; cursor: pointer; }
.cal-day.active:hover { background: var(--primary); color: #fff; }
.modal-actions { display: flex; gap: 8px; margin-top: 4px; }
.modal-actions button { flex: 1; }
/* showConfirm(util.js)의 danger:true — 그룹 삭제처럼 되돌릴 수 없는 동작의 확인 버튼을 빨간색으로 강조. */
.modal-actions button.primary.danger { background: var(--danger); }
/* 프로필 화면의 "친구 삭제"처럼, 채워진 버튼까진 아니고 글자/테두리만 빨갛게 해서 경고 톤만 주는 경우. */
.modal-actions button.secondary.danger { color: var(--danger); border-color: var(--danger); }
/* 알림봇 프로필의 "채팅 보내기 잠금"처럼, 버튼은 그대로 보여주되 누를 수 없는 상태임을 흐리게 표시. */
.modal-actions button:disabled { opacity: .5; cursor: not-allowed; }
/* 모달 헤더의 닫기(X) 버튼 — 예전엔 class="icon-btn"만 붙어있고 정작 스타일은 없어서(그 클래스는
   .topbar 안에서만 스타일이 있음) 브라우저 기본 버튼 테두리가 그대로 보여 조잡했다. SVG 아이콘 +
   둥근 호버 배경으로 다른 곳의 아이콘 버튼들과 톤을 맞췄다. */
.modal-close-btn {
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 28px; height: 28px; border-radius: 50%; border: none; background: transparent;
  color: var(--text-dim); cursor: pointer; padding: 0;
}
.modal-close-btn:hover { background: var(--bg); color: var(--text); }
.modal-close-btn svg { width: 16px; height: 16px; }

/* 채팅방 "⋮ > 사진·파일" 모아보기(chatRoom.js의 openMediaGalleryModal) 사진 탭 — 3열 정사각 그리드. */
.media-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 3px; padding: 8px 0; }
.media-grid-thumb { width: 100%; aspect-ratio: 1; object-fit: cover; cursor: pointer; background: var(--bg); }

/* 설정 > 보안 > 잠금모드(lockScreen.js) — 열려있는 모달/팝업까지 전부 덮어야 해서 z-index를 가장 높게 둔다.
   PC 창(body.electron)에서는 제목표시줄(32px) 영역은 비워둬서, 잠긴 동안에도 최소화/최대화/닫기는
   그대로 쓸 수 있게 한다(알림 버튼만 body.locked일 때 따로 숨김, 아래 참고). */
#lock-screen {
  position: fixed; inset: 0; z-index: 99999;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg);
}
/* #lock-screen이 ID 선택자로 display:flex를 항상 걸어두다 보니, hideOverlay()가 지우는
   hidden 속성의 기본 동작(display:none)보다 우선순위가 높아서 실제로는 안 가려지고 있었다 —
   PIN을 맞혀도(잠금 상태는 실제로 풀리는데) 화면만 그대로 "잠겨 있습니다"에 남아있던 원인이었다.
   속성 선택자를 더해 우선순위를 올려서 hidden일 때는 확실히 사라지게 한다. */
#lock-screen[hidden] { display: none; }
body.electron #lock-screen { top: 32px; }
body.locked .electron-titlebar #electron-notify-toggle { display: none; }
.lock-box { text-align: center; padding: 24px; max-width: 280px; }
.lock-icon { font-size: 40px; margin-bottom: 8px; }
.lock-box h2 { margin: 0 0 4px; font-size: 18px; }
.lock-box p { margin: 0 0 16px; color: var(--text-dim); font-size: 13px; }
.lock-box form { display: flex; flex-direction: column; gap: 12px; }
.lock-box input {
  width: 100%; text-align: center; font-size: 24px; letter-spacing: 12px; padding: 10px 0 10px 12px;
  border: 1px solid var(--border); border-radius: 8px; background: var(--surface); color: var(--text);
}
.lock-box input:disabled { opacity: .6; }
#lock-unlock-btn {
  padding: 10px; border-radius: 8px; border: none; background: var(--primary); color: #fff;
  font-size: 14px; font-weight: 600; cursor: pointer;
}
#lock-unlock-btn:disabled { opacity: .6; cursor: not-allowed; }
.lock-error {
  margin: 0; padding: 9px 12px; border-radius: 8px; font-size: 13px; font-weight: 600; line-height: 1.4;
  background: #fdecec; color: var(--danger); border: 1px solid var(--danger); text-align: center;
}
.lock-error[hidden] { display: none; }
.lock-box input.lock-input-error { border-color: var(--danger); animation: lock-shake .35s ease; }
@keyframes lock-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-8px); }
  40% { transform: translateX(8px); }
  60% { transform: translateX(-5px); }
  80% { transform: translateX(5px); }
}

.checkbox-row { display: flex; align-items: center; gap: 8px; padding: 8px 0; font-size: 14px; }
.pick-check { width: 20px; height: 20px; flex-shrink: 0; accent-color: var(--primary); border: 1px solid var(--border); border-radius: 3px; }
.group-avatar { background: var(--primary-dim); font-size: 18px; }
/* 그룹방 목록 아바타: 상대방 사진을 최대 4명까지, 하나의 큰 원이 아니라 각자 작은 원으로 모아
   보여준다(카카오톡/디스코드 그룹 아이콘 방식). 바깥 .avatar의 원 클리핑/배경은 여기선 끄고,
   각 collage-item이 자기만의 흰 테두리 원으로 겹쳐 앉는다. */
.avatar.group-collage { border-radius: 0; overflow: visible; background: none; position: relative; }
.group-collage .collage-item {
  position: absolute; border-radius: 50%; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  background: var(--primary-dim); color: var(--primary); font-size: 10px; font-weight: 700;
  border: 1.5px solid var(--surface);
}
.group-collage.count-2 .collage-item { width: 65%; height: 65%; }
.group-collage.count-2 .collage-item:nth-child(1) { top: 0; left: 0; }
.group-collage.count-2 .collage-item:nth-child(2) { bottom: 0; right: 0; }
.group-collage.count-3 .collage-item { width: 58%; height: 58%; }
.group-collage.count-3 .collage-item:nth-child(1) { top: 0; left: 21%; }
.group-collage.count-3 .collage-item:nth-child(2) { bottom: 0; left: 0; }
.group-collage.count-3 .collage-item:nth-child(3) { bottom: 0; right: 0; }
.group-collage.count-4 .collage-item { width: 52%; height: 52%; }
.group-collage.count-4 .collage-item:nth-child(1) { top: 0; left: 0; }
.group-collage.count-4 .collage-item:nth-child(2) { top: 0; right: 0; }
.group-collage.count-4 .collage-item:nth-child(3) { bottom: 0; left: 0; }
.group-collage.count-4 .collage-item:nth-child(4) { bottom: 0; right: 0; }
.member-chip {
  display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; border-radius: 14px;
  background: var(--primary-dim); color: var(--primary); font-size: 12.5px; margin: 2px;
}
.msg-sender { font-size: 11.5px; color: var(--text-dim); margin: 0 2px 2px; }

/* "불러오는 중..." 텍스트만 덩그러니 있던 걸 작은 회전 스피너와 함께 보여준다 — HTML은 그대로
   (<div class="centered-loading">불러오는 중...</div>) 둔 채 ::before로만 그려서, 이 클래스를 쓰는
   모든 화면(채팅방/쪽지함/설정 등)에 한 번에 적용된다. */
/* flex:1은 부모가 display:flex일 때만(예: 채팅방의 .messages) 의미가 있어서 실제로 화면을 채워
   가운데 정렬됐는데, 쪽지함(#memo-list)처럼 부모가 flex 컨테이너가 아닌 곳에서는 flex:1이 아무
   효과가 없어 내용 높이만큼만 차지하고 맨 위에 붙어버렸다 — min-height를 기본으로 깔아서 부모의
   flex 여부와 무관하게 항상 어느 정도 높이를 확보하고 그 안에서 가운데 정렬되게 한다(모달 등
   더 작은 영역에서 쓰는 곳은 기존처럼 인라인 style="min-height:120px"로 그대로 덮어쓸 수 있다). */
.centered-loading {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; color: var(--text-dim); font-size: 13px; min-height: 200px;
}
.centered-loading::before {
  content: '';
  width: 22px; height: 22px; border-radius: 50%;
  border: 2.5px solid var(--border); border-top-color: var(--primary);
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
