/* gemcare dashboard — LAYOUT and the few pieces the GemBoss kit has no primitive for.
 *
 * Colours, type, radii, shadows and the shared primitives (.ui-btn, .ui-card, .ui-tabs, .ui-table,
 * .ui-empty, .ui-textarea) come from gemboss-ui.css, which inbox.html loads FIRST. That
 * file is generated by gemboss and vendored (scripts/gemboss-ui.ts); never edit it, and never restyle
 * a kit class here — one owner per component. Every value below is a --gemboss-* / --ui-* token or on
 * the gemboss spacing/radius scale; `npm run ui:gates` holds both.
 */
:root {
  /* The kit reads --ui-* per surface. gemcare is a working tool used on phones as much as laptops, so
     every kit button is a 44px target under every pointer, not only a coarse one (fleet §9.1). */
  --ui-control-h: 44px;
}
html, body { height: 100%; margin: 0; }
body {
  font-family: var(--gemboss-serif);
  font-size: var(--gemboss-fs-body);
  line-height: 1.45;
  color: var(--gemboss-text);
  background: var(--gemboss-canvas);
}
[hidden] { display: none !important; }
button, input, select, textarea { font: inherit; color: inherit; }
/* Not textarea: the kit's .ui-textarea owns its focus state (border in --ui-accent); a ring on top drew a double border. */
button:focus-visible, select:focus-visible, input:focus-visible, summary:focus-visible {
  outline: 2px solid var(--ui-focus); outline-offset: 2px;
}

/* Inputs and selects: the kit's .ui-select is 36px and it has no text input, so gemcare keeps one field style. */
.field {
  min-height: 44px; padding: 0 12px; max-width: 100%;
  border: 1px solid var(--ui-border); border-radius: var(--ui-radius-md);
  background: var(--ui-surface); color: var(--ui-text);
}
.field:hover { border-color: var(--ui-text-muted); }
.field:focus-visible { outline: none; border-color: var(--ui-accent); box-shadow: 0 0 0 1px var(--ui-accent); }
.link {
  background: none; border: 0; padding: 0; min-height: 24px; cursor: pointer;
  color: var(--ui-text-muted); text-decoration: underline;
}
.link:hover { color: var(--ui-text); }
.form-error { color: var(--gemboss-danger); margin: 0; font-size: var(--gemboss-fs-caption); }
.muted { color: var(--ui-text-muted); margin: 4px 0 0; font-size: var(--gemboss-fs-caption); }

.brand { display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: var(--gemboss-fs-body-lg); letter-spacing: -.01em; color: var(--gemboss-text-strong); }
/* Dấu hiệu GemBoss thật (SVG vendored). `display: block` chứ không để mặc định inline: một <img>
   inline ngồi trên đường cơ sở chữ và kéo theo 4px khoảng trắng dưới đáy — ở rail 64px, 4px đó
   đủ để hàng icon đầu tiên lệch khỏi lưới. Bo góc do CHÍNH file SVG vẽ (rect rx=7), không bo
   lại ở đây: bo hai lần thì góc gặm vào nét vẽ. */
.brand-mark { display: block; width: 28px; height: 28px; }
/* ── Login ──
   Màn đăng nhập là `.gb-auth` của gemboss-ui.css (auth.css): khung, thẻ, bậc co theo chiều cao, cột
   thương hiệu từ 900×520, lớp tối. Ở đây CHỈ còn phần của gemcare, không đè luật nào của gói:
   (1) cột XANH LÁ bằng token `--gb-panel-*`. `#login` (1,1,0) thắng cả khối tối của auth.css
       (`:root:not([data-theme="light"]) .gb-auth`), nên cột giữ màu xanh ở cả hai chế độ.
       Màu từ `--gemboss-success` #007f5f, đậm dần theo đúng sắc đó để chữ trắng qua AA mọi chỗ.
   (2) dấu ngoặc kép lớn sau câu trích, vẽ bằng ::before để trình đọc màn hình không đọc dấu câu;
   (3) nút sang Help centre.
   Viên gem trong thẻ là `.gb-auth-mark img` của gói (gemboss #3497), không cỡ riêng ở đây: `.brand-mark`
   bên trên vẫn chỉ là 28px của thanh rail. */
#login.gb-auth {
  --gb-panel-bg:
    radial-gradient(880px 620px at 22% 8%, rgba(255, 255, 255, .16), transparent 62%),
    linear-gradient(152deg, #00795a 0%, #045c46 52%, #063f32 100%);
  --gb-panel-ink: #ffffff;
  --gb-panel-muted: rgba(255, 255, 255, .78);
  --gb-panel-eyebrow: rgba(255, 255, 255, .8);
}
.care-quote { position: relative; }
.care-quote::before { content: '\201C'; position: absolute; top: -.36em; left: -.34em;
  font-size: 3.2em; line-height: 1; color: rgba(255, 255, 255, .13); pointer-events: none; }
.care-panel-cta { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; margin-top: 32px;
  padding: 0 20px; border-radius: var(--gemboss-radius-sm); background: #ffffff; color: #063f32;
  font-size: var(--gemboss-fs-body); font-weight: 600; text-decoration: none;
  transition: transform .15s ease; }
.care-panel-cta:hover { transform: translateY(-1px); }
.care-panel-cta:focus-visible { outline: 2px solid #ffffff; outline-offset: 3px; }

/* ── Layout ── */
/* Cột 1 là một RAIL BIỂU TƯỢNG 64px, kiểu Intercom: các màn hình nằm đó, còn bộ lọc của hộp thư nằm ở
   đầu danh sách. Cột phải rộng hơn trước (320px) vì nay nó mang hai tab. */
.app { height: 100%; display: grid; grid-template-columns: 64px 340px minmax(0, 1fr) var(--details-w, 320px); }
/* Chưa chọn hội thoại ⇒ rãnh cột phải phải bằng 0, chứ không phải "ẩn phần tử đi".
 *
 * `#details` vào trang với `hidden`, tức `display: none` — nhưng rãnh 320px của nó VẪN nằm đó. Ở 1280
 * lúc vừa đăng nhập đó là 320px trắng trơn ở mép phải, và "Select a conversation" thì căn giữa một
 * khung lệch hẳn sang trái. Mọi phép đo bằng số vẫn xanh (không tràn ngang, target 44px, 0 pageerror);
 * chỉ một tấm ảnh chụp production mới lộ. Đây đúng là cái bẫy đã ghi cho khổ ≤900 ở cuối file — tôi vá
 * nó ở đó rồi bỏ sót chính nó ở đây.
 *
 * Sửa bằng BIẾN, không bằng một luật `grid-template-columns` thứ hai: một luật thứ hai sẽ phải được
 * ghi đè lại trong cả hai media query bên dưới (chúng đặt `.app`, độ đặc hiệu thấp hơn `.app:not(…)`,
 * nên sẽ thua và cột phải quay lại ở khổ hẹp — đúng lỗi vừa sửa xong). Biến thì không dự vào cuộc
 * tranh độ đặc hiệu nào cả. */
.app:not([data-open]) { --details-w: 0px; }
.rail {
  background: var(--gemboss-rail-bg); border-right: 1px solid var(--gemboss-divider);
  display: flex; flex-direction: column; align-items: center; padding: 12px 0; gap: 12px; min-height: 0;
}
.rail .brand { padding: 0; }
.rail-nav { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }

/* Mỗi nút là một ô 44px — ngưỡng điều khiển của §9.1 — và mang nhãn chữ hiện khi rê chuột/focus.
   Nhãn là một phần tử thật, không phải `title`: `title` không hiện khi đi bằng bàn phím. */
.rail-btn {
  position: relative; width: 44px; height: 44px; display: grid; place-items: center;
  border: 0; border-radius: var(--gemboss-radius-md); background: none; color: var(--gemboss-text-muted); cursor: pointer;
}
.rail-btn:hover { background: var(--gemboss-hover); color: var(--gemboss-text); }
.rail-btn[aria-current="true"] { background: var(--gemboss-active-bg); color: var(--gemboss-active-fg); }
/* `display: none`, KHÔNG phải `opacity: 0`.
 *
 * Một nhãn trong suốt vẫn CHIẾM CHỖ: nó nằm ở `left: calc(100% + 8px)` nên thò ra ngoài mép phải và
 * làm cả trang cuộn ngang ở khổ hẹp — đo 23/09, `#duty-tip` đẩy `scrollWidth` lên 351 ở viewport 320.
 * Mất hiệu ứng mờ dần là cái giá rẻ hơn hẳn một trang cuộn ngang trên điện thoại. */
.rail-tip {
  display: none; position: absolute; left: calc(100% + 8px); top: 50%; transform: translateY(-50%);
  white-space: nowrap; padding: 4px 8px; border-radius: var(--gemboss-radius-sm);
  background: var(--gemboss-text); color: var(--gemboss-surface); font-size: var(--gemboss-fs-caption);
  pointer-events: none; z-index: 40;
}
.rail-btn:hover .rail-tip, .rail-btn:focus-visible .rail-tip,
.rail-duty:hover .rail-tip, .rail-duty:focus-visible .rail-tip,
.rail-me:hover .rail-tip, .rail-me:focus-visible .rail-tip { display: block; }
.rail-count { position: absolute; top: 2px; right: 2px; }
.rail-duty, .rail-me {
  position: relative; width: 44px; height: 44px; display: grid; place-items: center;
  border: 0; border-radius: 50%; background: var(--gemboss-hover); color: var(--gemboss-text);
  font-size: var(--gemboss-fs-caption); font-weight: 600; cursor: pointer;
}
.rail-duty__dot { width: 10px; height: 10px; border-radius: 50%; background: var(--gemboss-text-muted); }
.rail-duty[data-status="on_duty"] .rail-duty__dot { background: var(--gemboss-success); }
.rail-duty[data-status="away"] .rail-duty__dot { background: var(--gemboss-warning); }
/* Bảng trực mở ra từ rail — nó không vừa trong 64px. */
.rail-pop {
  position: fixed; left: 72px; bottom: 12px; z-index: 50; width: min(280px, calc(100vw - 84px));
  background: var(--gemboss-surface); border: 1px solid var(--gemboss-divider);
  border-radius: var(--gemboss-radius-lg); box-shadow: 0 8px 24px rgb(0 0 0 / .14); padding: 12px;
  display: grid; gap: 12px;
}
.rail-pop__foot { margin: 0; }
.list-view { display: block; }
.list-view .field { width: 100%; }
/* Hàng nút của luồng hội thoại phải XUỐNG DÒNG: ở 320px "Customer & AI" thò ra 66px ngoài mép. */
.thread-actions { flex-wrap: wrap; }

/* Hai tab của cột phải. `role=tab` chứ không phải link: chúng đổi thứ hiện ra trong cùng một khung. */
.d-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--gemboss-divider); margin: -4px -4px 12px; padding: 0 4px; }
.d-tabs button {
  flex: 1 1 0; min-height: 44px; border: 0; background: none; cursor: pointer; font: inherit;
  color: var(--gemboss-text-muted); border-bottom: 2px solid transparent;
}
.d-tabs button[aria-selected="true"] { color: var(--gemboss-text); font-weight: 600; border-bottom-color: var(--gemboss-active-fg, var(--gemboss-text)); }
.d-panel { min-width: 0; }

/* Trợ lý. Câu hỏi nhanh trước ô nhập: người trực ca đêm không nghĩ ra câu hỏi, họ cần chỗ để bấm. */
.ai-quick { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.ai-quick__btn { min-height: 44px; text-align: left; }
.ai-ask { display: flex; gap: 8px; align-items: flex-end; }
.ai-ask .ui-textarea { flex: 1 1 auto; }
.ai-ask .ui-btn { min-height: 44px; }
.ai-thread { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.ai-turn__who { font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted); }
.ai-turn__body { white-space: pre-wrap; overflow-wrap: anywhere; }
.ai-turn[data-who="me"] .ai-turn__body { color: var(--gemboss-text-muted); }
.ai-insert { min-height: 44px; margin-top: 4px; }
.count {
  font-size: var(--gemboss-fs-caption); font-weight: 600; min-width: 20px; text-align: center; font-variant-numeric: tabular-nums;
  border-radius: var(--gemboss-radius-full); padding: 0 6px; background: var(--gemboss-hover); color: var(--gemboss-text-muted);
}
.count:empty { display: none; }
/* Bị gọi tên là việc gửi tới MỘT NGƯỜI, không tới hàng đợi: mượn màu của huy hiệu quá hạn. */
.mention-pill { display: flex; align-items: center; width: 100%; min-height: 44px; padding: 0 12px; border-radius: var(--gemboss-radius-md);
  border: 1px solid var(--gemboss-danger); background: var(--gemboss-surface); color: var(--gemboss-danger);
  font: inherit; font-weight: 600; text-align: left; cursor: pointer; }
.mention-pill:hover { background: var(--gemboss-hover); }
.mention-pill:focus-visible { outline: 2px solid var(--gemboss-danger); outline-offset: 2px; }
/* One rule (28/09/2026): it was declared twice, the second overriding most of the first. These are the
   values that were winning; `flex-direction` went with the flex it belonged to. */
.rail-foot { margin-top: auto; display: grid; align-items: center; gap: 12px; font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted); }
.team { display: flex; flex-wrap: wrap; gap: 6px; }
/* `> .team-chip`, KHÔNG `span`. Luật cũ `.team span` khớp MỌI span con cháu — vô hại khi chip chỉ
   chứa chữ, nhưng chip nay có ảnh đại diện, và `.av` lẫn `.av__initials` đều là span. Để nguyên thì
   mỗi ô ảnh tự mọc một viên thuốc có viền và một chấm online riêng: ba chấm cho một người. Đúng họ
   lỗi `element-css-collides-across-sections` — một luật con cháu với vào phần tử nó không sở hữu. */
.team > .team-chip {
  display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px 2px 2px;
  border-radius: var(--gemboss-radius-full); border: 1px solid var(--gemboss-divider); color: var(--gemboss-text);
}
.team > .team-chip::before { content: ""; order: 3; width: 8px; height: 8px; border-radius: 50%; background: var(--gemboss-divider); }
.team > .team-chip[data-online="true"]::before { background: var(--gemboss-success); }
.me { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 0 4px; color: var(--gemboss-text); }
/* Công tắc trực. Ba nút là một nhóm: trạng thái đang chọn phải NHÌN ra ngay, vì nó quyết định câu
   khách nghe lúc 2 giờ sáng. Nút cao 44px theo §9.1 (điều khiển, không phải link trong danh sách). */
.duty { display: grid; gap: 6px; padding: 0 4px; }
.duty-buttons { display: flex; gap: 6px; }
.duty-buttons .ui-btn { flex: 1 1 0; min-height: 44px; padding: 0 8px; font-size: var(--gemboss-fs-caption); }
.duty-buttons .ui-btn[aria-pressed="true"] {
  border-color: var(--gemboss-primary); color: var(--gemboss-primary); font-weight: 600;
}
.duty[data-status="on_duty"] .ui-btn[aria-pressed="true"] { border-color: var(--gemboss-success); color: var(--gemboss-success); }
.duty #duty-note { margin: 0; }

.conn { display: flex; align-items: center; gap: 8px; padding: 0 4px; }
.conn-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--gemboss-warning-border); }
.conn[data-state="live"] .conn-dot { background: var(--gemboss-success); }
.conn[data-state="offline"] .conn-dot { background: var(--gemboss-danger); }
/* The EMAIL channel, not the socket. `no_mx` is red rather than amber on purpose: a configured
   channel that silently bounces is worse than one that is plainly switched off, because the team
   believes it works. */
.intake[data-state="off"] .conn-dot { background: var(--gemboss-text-muted); }
.intake[data-state="no_mx"] .conn-dot { background: var(--gemboss-danger); }
.intake[data-state="no_mx"] { color: var(--gemboss-danger); font-weight: 600; }
/* `unknown` stays amber and stays quiet. It is not a refusal — it is our own resolver failing — and
   painting it red would teach the team that red means nothing. */
.intake[data-state="unknown"] .conn-dot { background: var(--gemboss-warning-border); }
/* `elsewhere` is red and bold like `no_mx`, and it is the worse of the two: a refused mail at least
   bounces back to the customer, while mail handed to the wrong host disappears with everyone — sender
   and team — believing it arrived. */
.intake[data-state="elsewhere"] .conn-dot { background: var(--gemboss-danger); }
.intake[data-state="elsewhere"] { color: var(--gemboss-danger); font-weight: 600; }
/* `unverified` is amber: nothing is known to be broken, and nothing is known to work either. */
/* `swapped`: model VẪN trả lời, nhưng không phải model được xin. Hổ phách như `unknown` vì tính
   năng chưa chết, nhưng ĐẬM — `unknown` cố ý "im lặng, không phải một lời từ chối", và câu to nhất
   của hàng model không được mặc áo của trạng thái không-cần-làm-gì. Ở đây CÓ việc phải làm: biết
   rằng chất lượng nháp đã đổi. */
.intake[data-state="swapped"] .conn-dot { background: var(--gemboss-warning-border); }
.intake[data-state="swapped"] { font-weight: 600; }
.intake[data-state="unverified"] .conn-dot { background: var(--gemboss-warning-border); }

.list { background: var(--gemboss-surface); border-right: 1px solid var(--gemboss-divider); display: flex; flex-direction: column; min-height: 0; }
.list-head { padding: 16px; border-bottom: 1px solid var(--gemboss-divider); }
.list-head h2 { margin: 0; font-size: var(--gemboss-fs-h3); }
.conversations { list-style: none; margin: 0; padding: 0; overflow-y: auto; flex: 1; }
.conversations button {
  width: 100%; min-height: 64px; display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px 8px; text-align: left;
  padding: 12px 16px; border: 0; border-bottom: 1px solid var(--gemboss-divider-soft); background: transparent; cursor: pointer;
  transition: background-color var(--gemboss-transition-fast) ease;
}
.conversations button:hover { background: var(--gemboss-hover); }
/* "Load more" cuối danh sách: một hàng đủ 44px, căn giữa, không trông như một hội thoại. */
.conversations .load-more-row { padding: 8px 12px; }
.conversations .load-more { width: 100%; min-height: 44px; justify-content: center; color: var(--gemboss-accent); }
.conversations button[aria-current="true"] { background: var(--gemboss-active-bg); box-shadow: inset 2px 0 0 var(--gemboss-primary); }
.c-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.c-time { color: var(--gemboss-text-muted); font-size: var(--gemboss-fs-caption); }
.c-preview { color: var(--gemboss-text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.c-meta { display: flex; gap: 6px; align-items: center; justify-self: end; }
.c-unread {
  background: var(--gemboss-accent); color: var(--ui-accent-fg); font-size: var(--gemboss-fs-micro); font-weight: 700;
  border-radius: var(--gemboss-radius-full); padding: 0 6px; min-width: 20px; text-align: center;
}
.c-assignee { font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted); }
.conversations button[data-unread="true"] .c-preview { color: var(--gemboss-text); font-weight: 500; }

.thread { display: flex; flex-direction: column; min-width: 0; min-height: 0; background: var(--gemboss-surface); }
.thread-empty { flex: 1; }
.thread-body { flex: 1; display: flex; flex-direction: column; min-height: 0; }
.thread-head {
  display: flex; align-items: center; gap: 12px; padding: 8px 16px; background: var(--gemboss-surface);
  border-bottom: 1px solid var(--gemboss-divider); flex-wrap: wrap;
}
.back { display: none; }
/* `min-width` có SÀN, không phải 0.
 *
 * `flex: 1` + `min-width: 0` cho khối tên co xuống tới KHÔNG khi hàng nút không đủ chỗ. Đo 23/09 ở
 * 1280 (khổ laptop của Chris, ngăn chi tiết đang mở): hộp tên còn **24px**, tên khách hiện ra là
 * "H…" và email là "ho…". Lỗi xuất hiện từ lúc thêm nút "Escalate to tech" ở lát 5 ① — năm nút cộng
 * một ô chọn không vừa 556px.
 *
 * Sàn 140px: hàng nút xuống dòng (header vốn `flex-wrap: wrap`) thay vì nuốt mất thứ quan trọng
 * nhất trên màn — tên người đang nói chuyện với mình. */
.who { display: grid; min-width: 140px; flex: 1; }
.who strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.who span { color: var(--gemboss-text-muted); font-size: var(--gemboss-fs-caption); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.thread-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
/* Nhãn ĐỦ khi rộng, nhãn NGẮN khi chật — cho "New request" và "Escalate to tech".
 *
 * Đo 26/09 ở 1280 — khổ laptop phổ biến nhất, lý do §9.1 thêm khổ này: thanh hành động rộng 524px mà
 * cần 591px (assignee 149 · New request 113 · Escalate to tech 134 · Resolve 83 · Snooze 80 · 4 khe
 * 8px), nên Snooze rơi xuống dòng riêng và đầu khung chiếm BA dòng. Ngưỡng 1440 là chỗ thanh có dư thật
 * cho nhãn đủ (thread ~716px).
 * Ngưỡng theo KHUNG NHÌN, không theo bề rộng của thanh: thanh vẫn gãy ở vài dải hẹp (901–982 khi ngăn
 * "Customer & AI" cũng ở trên hàng, 1100–1218) — review #143 tính ra. Truy vấn theo container là việc
 * sau; PR này nhắm 1280 và 1366.
 * Hai nút không có aria-label: tên truy cập là chữ đang hiện (xem inbox.html). */
.lbl-short { display: none; }
@media (max-width: 1439px) {
  .thread-actions .lbl-long { display: none; }
  .thread-actions .lbl-short { display: inline; }
  /* Đệm ngang 12px thay 16px — chỉ trong thanh này, không đụng `.ui-btn` của hệ thiết kế (vendored,
     có khoá sha256). Nút vẫn cao 44px: đệm dọc không đổi. */
  .thread-actions > .ui-btn, .thread-actions .snooze > summary { padding-left: 12px; padding-right: 12px; }
}
/* Ô chọn người phụ trách rộng bằng NGƯỜI ĐANG ĐƯỢC CHỌN, không bằng tên dài nhất trong danh sách (đo:
   149px với 31 agent). `field-sizing: content` làm việc ấy ở Chromium; trình duyệt chưa hiểu nó thì giữ
   hành vi cũ (tên dài nhất, trần 180px dưới đây) — không ai mất gì.
 * Bản trước ép cứng 128px ở 901–1439: "Chris Koh (you)" (146px) bị cắt thành "Chris Koh (yo" — đúng dấu
   "đây là của tôi" bị cắt trước tiên (review #143, có ảnh). Cắt chữ là đỏ ở §9.1; một hàng xuống dòng
   thì không. */
#t-assignee { field-sizing: content; }
/* Gợi ý của model: trải hết hàng (header là flex-wrap), chữ nhỏ, màu phụ. Nó KHÔNG được trông như
   một trạng thái của hội thoại — nó là một câu ai đó đoán. */
.tech-hint {
  flex: 1 0 100%; margin: 0; padding: 4px 0 0;
  font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted);
}
.assign .field { max-width: 180px; }

.messages { list-style: none; margin: 0; padding: 20px 16px; overflow-y: auto; flex: 1; display: flex; flex-direction: column; gap: 12px; }
.msg { max-width: min(640px, 85%); display: grid; gap: 4px; }
.msg-bubble { padding: 8px 12px; border-radius: var(--gemboss-radius-xl); white-space: pre-wrap; overflow-wrap: anywhere; }
.msg-bubble a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.msg-bubble a:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; border-radius: 2px; }
.msg-meta { font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted); }
.msg[data-sender="contact"] { align-self: flex-start; }
.msg[data-sender="contact"] .msg-bubble { background: var(--gemboss-primary-soft); border-bottom-left-radius: var(--gemboss-radius-xs); }
.msg[data-sender="agent"], .msg[data-sender="bot"] { align-self: flex-end; text-align: right; }
.msg[data-sender="agent"] .msg-bubble { background: var(--gemboss-primary); color: var(--ui-accent-fg); border-bottom-right-radius: var(--gemboss-radius-xs); text-align: left; }
.msg[data-private="true"] .msg-bubble { background: var(--gemboss-warning-soft); color: var(--gemboss-text); border: 1px dashed var(--gemboss-warning-border); }
.msg[data-pending="true"] { opacity: .6; }
.msg[data-failed="true"] .msg-meta { color: var(--gemboss-danger); }
.day { align-self: center; font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted); padding: 4px 0; }
/* "Earlier with this customer": chuyện trước đó, MỜ và chỉ đọc — không lẫn với tin của hội thoại đang mở. */
.earlier { display: flex; flex-direction: column; gap: 8px; padding: 12px; border: 1px dashed var(--gemboss-divider); border-radius: 12px; color: var(--gemboss-text-muted); }
.earlier__title { margin: 0; font-size: var(--gemboss-fs-caption); font-weight: 600; text-transform: uppercase; letter-spacing: .04em; }
.earlier__conv { display: flex; flex-direction: column; gap: 4px; }
.earlier__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: var(--gemboss-fs-caption); }
.earlier__msg { display: flex; flex-direction: column; font-size: var(--gemboss-fs-body); overflow-wrap: anywhere; }
.earlier__who { font-size: var(--gemboss-fs-caption); }
.earlier__gap { font-size: var(--gemboss-fs-caption); }
.typing { margin: 0; padding: 0 16px 6px; color: var(--gemboss-text-muted); font-size: var(--gemboss-fs-caption); }

.composer { background: var(--gemboss-surface); border-top: 1px solid var(--gemboss-divider); padding: 0 16px 12px; display: grid; gap: 8px; }
.composer .ui-textarea { min-height: 72px; max-height: 40vh; }
.composer[data-mode="note"] .ui-textarea { background: var(--gemboss-warning-soft); border-color: var(--gemboss-warning-border); }
/* `flex-wrap` ở MỌI khổ: bốn nút không vừa 264px, và câu báo lỗi (#composer-error) lấy riêng một dòng. */
.composer-foot { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.composer-foot .attach { width: 44px; min-width: 44px; padding: 0; justify-content: center; }

/* ── Attachments: files waiting in the composer ── */
.files { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.file {
  display: flex; align-items: center; gap: 8px; padding: 4px 4px 4px 4px; min-width: 0; width: min(280px, 100%);
  border: 1px solid var(--gemboss-divider); border-radius: var(--gemboss-radius-md); background: var(--gemboss-surface);
}
.file[data-status="error"] { border-color: var(--gemboss-danger); background: var(--gemboss-danger-soft); }
.file-preview { width: 44px; height: 44px; flex: none; object-fit: cover; border-radius: var(--gemboss-radius-sm); background: var(--gemboss-hover); }
.file-preview--doc { display: grid; place-items: center; font-size: var(--gemboss-fs-micro); font-weight: 700; color: var(--gemboss-text-muted); }
.file-body { display: grid; gap: 2px; min-width: 0; flex: 1; }
.file-name { font-size: var(--gemboss-fs-caption); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-state { font-size: var(--gemboss-fs-micro); color: var(--gemboss-text-muted); overflow-wrap: anywhere; }
.file[data-status="error"] .file-state { color: var(--gemboss-danger); }
.file-bar { height: 4px; border-radius: var(--gemboss-radius-full); background: var(--gemboss-divider-soft); overflow: hidden; }
.file-bar-fill { display: block; height: 100%; width: 0; background: var(--gemboss-accent); transition: width var(--gemboss-transition-fast) linear; }
.file[data-status="ready"] .file-bar, .file[data-status="error"] .file-bar { visibility: hidden; }
.file[data-status="finalizing"] .file-bar-fill, .file[data-status="preparing"] .file-bar-fill { width: 100%; opacity: .35; }
.file-remove { width: 44px; min-width: 44px; padding: 0; justify-content: center; flex: none; }
#composer-send:disabled { cursor: progress; }

/* ── Attachments in the thread ── */
.msg-files { display: flex; flex-wrap: wrap; gap: 6px; }
.msg[data-sender="agent"] .msg-files, .msg[data-sender="bot"] .msg-files { justify-content: flex-end; }
.file-thumb {
  padding: 0; border: 1px solid var(--gemboss-divider); border-radius: var(--gemboss-radius-lg); overflow: hidden; cursor: zoom-in;
  background: var(--gemboss-hover); line-height: 0; max-width: 100%;
}
.file-thumb img { display: block; max-width: 100%; height: auto; object-fit: cover; }
.file-thumb:hover { box-shadow: var(--gemboss-shadow-resting); }
.msg[data-private="true"] .file-thumb, .msg[data-private="true"] .file-chip { border: 1px dashed var(--gemboss-warning-border); }
.file-chip {
  display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding: 4px 12px 4px 4px; max-width: 100%;
  border: 1px solid var(--gemboss-divider); border-radius: var(--gemboss-radius-md); background: var(--gemboss-surface);
  color: var(--gemboss-text); text-decoration: none; text-align: left;
}
.file-chip:hover { background: var(--gemboss-hover); }
.file-chip:focus-visible, .file-thumb:focus-visible { outline: 2px solid var(--ui-focus); outline-offset: 2px; }
.file-chip-icon {
  width: 36px; height: 36px; flex: none; display: grid; place-items: center; border-radius: var(--gemboss-radius-sm);
  background: var(--gemboss-danger-soft); color: var(--gemboss-danger); font-size: var(--gemboss-fs-micro); font-weight: 700;
}
.file-chip-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
.file-chip-size { color: var(--gemboss-text-muted); font-size: var(--gemboss-fs-caption); flex: none; }

.thread { position: relative; }
.drop-hint {
  position: absolute; inset: 8px; z-index: 5; display: grid; place-items: center; pointer-events: none;
  border: 2px dashed var(--gemboss-accent); border-radius: var(--gemboss-radius-xl); background: var(--gemboss-primary-soft);
  color: var(--gemboss-text-strong); font-weight: 600;
}
dialog.lightbox {
  border: 0; padding: 0; background: transparent; max-width: calc(100vw - 32px); max-height: calc(100vh - 32px);
  display: none; flex-direction: column; gap: 8px; align-items: flex-end;
}
dialog.lightbox[open] { display: flex; }
dialog.lightbox::backdrop { background: rgba(20, 19, 18, .72); }
.lightbox-bar { display: flex; gap: 8px; }
.lightbox-bar .ui-btn { background: var(--gemboss-surface); }
#lightbox-img { display: block; max-width: calc(100vw - 32px); max-height: calc(100vh - 96px); object-fit: contain; border-radius: var(--gemboss-radius-md); background: var(--gemboss-surface); }
.composer-hint { color: var(--gemboss-text-muted); font-size: var(--gemboss-fs-caption); flex: 1; }
/* Lỗi của ô soạn lấy HẲN một dòng, phía trên các nút, ở mọi khổ. Để nó chia khe với dòng gợi ý thì
   một trong hai bị bóp thành cột: dòng gợi ý bốn dòng ở 1280, câu lỗi sáu dòng trong 69px ở 390 (đo
   27/09/2026). Và KHÔNG giấu dòng gợi ý: nó là chỗ duy nhất nói câu trả lời sẽ đi đâu (Telegram, email,
   hay trợ lý Gem). Chỉ nhắm đúng #composer-error — `.form-error` trong ngăn Request cũng nằm trong một
   `.composer-foot`. `margin: 0` vì `.form-error` chung có lề trên. */
#composer-error { order: -1; flex-basis: 100%; margin: 0; }
@media (max-width: 480px) {
  /* Ở khổ điện thoại, dòng gợi ý lấy một dòng riêng DƯỚI hàng nút. Để nó chen giữa "/ Replies" và Send
     thì nó thành một cột bốn dòng và đẩy Send xuống một hàng riêng; thêm dòng câu báo nữa là Send lòi
     khỏi đáy màn 320×640 (đo 27/09/2026). */
  .composer-hint { order: 1; flex-basis: 100%; }
}

.details { background: var(--gemboss-surface); border-left: 1px solid var(--gemboss-divider); padding: 16px; overflow-y: auto; }
.details h3 { font-size: var(--gemboss-fs-caption); text-transform: uppercase; letter-spacing: .06em; color: var(--gemboss-text-muted); margin: 20px 0 8px; }
.details h3:first-child { margin-top: 0; }
.details dl { margin: 0; display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 6px 12px; }
.details dt { color: var(--gemboss-text-muted); }
.details dd { margin: 0; overflow-wrap: anywhere; }
.previous { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.previous button {
  min-height: 32px; width: 100%; text-align: left; cursor: pointer; padding: 4px 12px;
  border: 1px solid var(--gemboss-divider); border-radius: var(--gemboss-radius-md); background: transparent;
}
.previous button:hover { background: var(--gemboss-hover); }

/* ── Segmented range (Reports days, Requests owner). Not the kit's .ui-seg: that is 36px under a mouse. ── */
/* `flex-wrap: wrap` đi kèm BẮT BUỘC với `min-width` ở nút con: không có nó, sàn 44px không làm ô
   rộng ra mà chỉ ĐẨY việc nghiền sang nút bên cạnh — đo 24/09 ở 320px, nút "Internal" tụt
   74,3 → 64,9px và padding phải co còn 2,6px. Cùng nguyên tắc `.thread-actions` đang dùng. */
.range { display: flex; flex-wrap: wrap; gap: 4px; background: var(--gemboss-primary-soft); border-radius: var(--gemboss-radius-md); padding: 2px; }
/* `min-width` đi CÙNG `min-height`: §9.1 đòi 44px ở CẢ HAI chiều, và một nút chữ lấy bề rộng từ
   nhãn của nó. Nhãn ngắn thì tụt xuống dưới sàn — đo trên prod 24/09 ở 390px: nút "All" = 41×44. */
.range button {
  min-height: 44px; min-width: 44px; padding: 0 12px; border: 0; border-radius: var(--gemboss-radius-sm); background: transparent; cursor: pointer;
  color: var(--gemboss-text-muted); font-weight: 500;
}
.range button:hover { color: var(--gemboss-text); }
.range button[aria-pressed="true"] { background: var(--gemboss-surface); color: var(--gemboss-text-strong); font-weight: 600; box-shadow: var(--gemboss-shadow-resting); }

/* ── Full-width screens: requests, reports, team settings ──
   By the ATTRIBUTE, not by a list of its values. The list version was three rules that each had to be
   found and extended; the third screen was added without them and rendered inside the 220px rail column
   with the conversation list still beside it. A layout rule that has to be edited for every new screen
   is a layout rule that will be missed. */
.app[data-screen] { grid-template-columns: 64px minmax(0, 1fr); }
.app[data-screen] .list, .app[data-screen] .thread, .app[data-screen] .details { display: none !important; }
.board-screen { overflow: auto; padding: 24px; display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; align-content: start; background: var(--gemboss-app-bg); min-width: 0; }
.board { display: grid; grid-template-columns: repeat(4, minmax(220px, 1fr)); gap: 12px; align-items: start; overflow-x: auto; padding-bottom: 8px; }
.column { background: var(--gemboss-primary-soft); border-radius: var(--gemboss-radius-lg); padding: 12px; display: grid; gap: 8px; min-height: 160px; align-content: start; }
.column[data-drop="true"] { outline: 2px dashed var(--gemboss-accent); outline-offset: -2px; }
.column-head { display: flex; justify-content: space-between; align-items: center; font-weight: 600; padding: 2px 4px; }
.column-head .count { background: var(--gemboss-surface); }
.card-rq {
  width: 100%; min-height: 44px; text-align: left; cursor: pointer; display: grid; gap: 4px; padding: 12px;
  background: var(--gemboss-surface); border: 1px solid var(--gemboss-divider); border-radius: var(--gemboss-radius-md);
  transition: box-shadow var(--gemboss-transition-fast) ease;
}
.card-rq:hover { box-shadow: var(--gemboss-shadow-resting); }
.card-rq[aria-grabbed="true"] { opacity: .5; }
.card-rq .rq-num { color: var(--gemboss-text-muted); font-size: var(--gemboss-fs-caption); }
.card-rq .rq-name { font-weight: 600; overflow-wrap: anywhere; }
.card-rq .rq-foot { display: flex; justify-content: space-between; gap: 8px; color: var(--gemboss-text-muted); font-size: var(--gemboss-fs-caption); }
.empty-col { color: var(--gemboss-text-muted); font-size: var(--gemboss-fs-caption); padding: 4px; margin: 0; }
dialog.drawer {
  border: 1px solid var(--gemboss-divider); border-radius: var(--gemboss-radius-xl); padding: 20px; color: var(--gemboss-text); background: var(--gemboss-surface);
  width: min(560px, calc(100vw - 32px)); max-height: calc(100vh - 48px); box-shadow: var(--gemboss-shadow-overlay);
}
dialog.drawer.small { width: min(440px, calc(100vw - 32px)); }
dialog.drawer::backdrop { background: rgba(20, 19, 18, .38); }
.drawer-close { position: absolute; top: 8px; right: 8px; }
#rq-meta { padding-right: 44px; } /* clear of the 44px close button — it sat on top of the date */
#rq-title { margin: 4px 0 8px; font-size: var(--gemboss-fs-h2); padding-right: 44px; overflow-wrap: anywhere; }
.rq-desc { white-space: pre-wrap; margin: 0 0 12px; overflow-wrap: anywhere; }
.rq-controls { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
.rq-controls label, .rq-new label { display: grid; gap: 4px; font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted); }
.rq-new .field, .rq-new .ui-textarea { color: var(--gemboss-text); font-size: var(--gemboss-fs-body); }
.rq-new { display: grid; gap: 12px; }
/* A grid item's default minimum is its MIN-CONTENT, and a <select>'s min-content is its longest option —
   so "Published — merchants can read it" pushed the article editor 6px wider than the dialog and gave it a
   horizontal scrollbar at 320px. The visual gate cannot see this (it measures the page, not the dialog);
   it was measured with boundingBox(). `min-width: 0` lets the column shrink; the select then fills it. */
.rq-new label { min-width: 0; }
.rq-new select.field { width: 100%; box-sizing: border-box; }
.rq-new h3 { margin: 0; font-size: var(--gemboss-fs-h3); }
.rq-events { list-style: none; margin: 0 0 12px; padding: 0; display: grid; gap: 8px; max-height: 40vh; overflow-y: auto; }
.rq-events li { font-size: var(--gemboss-fs-caption); border-left: 2px solid var(--gemboss-divider); padding: 2px 0 2px 12px; }
.rq-events li[data-private="true"] { border-left-color: var(--gemboss-warning-border); background: var(--gemboss-warning-soft); }
.rq-events .ev-body { white-space: pre-wrap; margin-top: 2px; overflow-wrap: anywhere; font-size: var(--gemboss-fs-body); }
.rq-events .ev-meta { color: var(--gemboss-text-muted); }
.rq-comment { display: grid; gap: 8px; }
.rq-private { display: flex; align-items: center; gap: 6px; min-height: 44px; flex: 1; }

/* ── Reports ── */
/* minmax(0, 1fr): an implicit grid column is `auto` and grows to the widest table — measured 5px of
   page scroll in Reports at 390 before this. */
.reports { overflow-y: auto; padding: 24px; display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; align-content: start; background: var(--gemboss-app-bg); min-width: 0; }
.reports-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 16px; flex-wrap: wrap; }
.reports-head h2 { margin: 0; font-size: var(--gemboss-fs-h1); }
/* Bọc các thẻ toàn đội để ẩn cả khối theo quyền — `contents` để chúng VẪN là con của lưới
   `.reports`, nếu không thì sáu thẻ dính liền nhau không còn khe 16px nào. Lỗi này tôi bỏ sót vì
   chỉ thử vai cs_agent, mà với vai đó khối này ĐANG ẨN (§9.2: ma trận trạng thái, không chỉ mặc định). */
#r-team { display: contents; }
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
.tile { display: grid; gap: 4px; align-content: start; }
.tile-label { color: var(--gemboss-text-muted); font-size: var(--gemboss-fs-caption); }
.tile-value { font-size: var(--gemboss-fs-h1); font-weight: 600; letter-spacing: -.01em; color: var(--gemboss-text-strong); font-variant-numeric: tabular-nums; }
.tile-note { color: var(--gemboss-text-muted); font-size: var(--gemboss-fs-caption); }

/* Mục tiêu và khoảng cách. Màu KHÔNG phải tín hiệu duy nhất: câu "đạt" / "chưa đạt" / "chưa có số để
   so" nằm ngay trong chữ, nên một người không phân biệt được màu vẫn đọc được kết luận (§9.2). */
.tile-goal { font-size: var(--gemboss-fs-caption); font-weight: 600; }
/* So với cửa sổ trước (thayDoi trong inbox.js). Màu chỉ khi có hướng tốt/xấu; số hội thoại thì trung tính. */
.tile-delta { font-size: var(--gemboss-fs-caption); font-variant-numeric: tabular-nums; color: var(--gemboss-text-muted); }
.tile-delta[data-state="good"] { color: var(--gemboss-success); font-weight: 600; }
.tile-delta[data-state="bad"] { color: var(--gemboss-danger); font-weight: 600; }
.tile-goal[data-state="met"] { color: var(--gemboss-success); }
.tile-goal[data-state="missed"] { color: var(--gemboss-danger); }
/* Chưa có số để so là TRUNG TÍNH, không phải cảnh báo — nó không nói gì về người này cả. */
.tile-goal[data-state="unmeasured"] { color: var(--gemboss-text-muted); font-weight: 400; }
.ui-card { min-width: 0; }
.chart { position: relative; }
.chart svg { display: block; width: 100%; height: 220px; }
.chart .grid { stroke: var(--gemboss-divider-soft); stroke-width: 1; }
.chart .axis { fill: var(--gemboss-text-muted); font-size: var(--gemboss-fs-micro); }
.chart .bar { fill: var(--gemboss-accent); pointer-events: none; }
.chart .hit { fill: transparent; cursor: default; }
.chart .hit:hover + .bar, .chart .bar.active { fill: var(--gemboss-accent-strong); }
.tooltip {
  position: absolute; pointer-events: none; white-space: nowrap; transform: translate(-50%, -110%);
  background: var(--gemboss-text-strong); color: var(--ui-accent-fg); font-size: var(--gemboss-fs-caption);
  padding: 6px 8px; border-radius: var(--gemboss-radius-sm); box-shadow: var(--gemboss-shadow-raised);
}
.table-view { margin-top: 12px; }
/* 44px, not 24: a disclosure is a control (WCAG 2.5.5), and `min-height` on an inline box does nothing —
   these four "Show as table" rows measured 24px at 320/390/768 from the day they shipped. */
.table-view summary { display: inline-flex; align-items: center; cursor: pointer; color: var(--gemboss-text-muted); min-height: 44px; margin-bottom: 8px; }
#r-agents > p { margin: 0; padding: 12px; }

/* Ở khổ rộng ngăn chi tiết luôn hiện, nên nút mở nó là một nút không làm gì.
   Rule này phải đứng TRƯỚC media query: cùng độ ưu tiên (#id) thì rule đứng sau thắng, và đặt nó ở
   dưới làm nút biến mất ở MỌI khổ — đo trên trình duyệt 23/09, nút có trong DOM mà không bao giờ bấm được. */
#t-details { display: none; }
/* Cùng lý do thứ tự như trên: rule nền đứng TRƯỚC media query. */
.details-close { display: none; }

/* ── Hàng đợi tech ── */
/* Tiêu đề ngăn giữa dãy số và danh sách việc: không có nó thì hai khối dính vào nhau và người đọc
   không biết con số bên trên nói về cửa sổ 7 ngày còn danh sách bên dưới là NGAY BÂY GIỜ. */
.tech-h3 { margin: 20px 0 8px; font-size: var(--gemboss-fs-h3); }
#tech-list { display: grid; gap: 12px; }
.tech-card {
  display: grid; gap: 8px; padding: 12px;
  background: var(--gemboss-surface); border: 1px solid var(--gemboss-divider); border-radius: var(--gemboss-radius-lg);
}
/* Quá hẹn: một VẠCH bên trái, không phải nền đỏ cả thẻ. Nền màu trên cả khối làm chữ khó đọc và
   tranh chấp với mọi trạng thái khác của thẻ — cùng lý do với vạch "gọi tên mình" bên chat. */
.tech-card.is-breached { border-left: 2px solid var(--gemboss-danger); }
.tech-card__head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.tech-card__sev { flex: none; }
.tech-card__who { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Số đồng hồ dùng chữ số ĐỀU BỀ RỘNG: một cột con số nhảy qua nhảy lại giữa các hàng là cột không
   liếc qua được. */
.tech-card__clock { flex: none; font-variant-numeric: tabular-nums; color: var(--gemboss-text-muted); }
.tech-card__clock.is-breached { color: var(--gemboss-danger); font-weight: 700; }
.tech-card__reason { margin: 0; overflow-wrap: anywhere; }
.tech-card__meta { margin: 0; font-size: var(--gemboss-fs-caption); }
.tech-card__foot { display: flex; flex-wrap: wrap; gap: 8px; }

.esc-dialog { border: 0; padding: 24px; width: min(480px, calc(100vw - 32px)); }
.esc-dialog::backdrop { background: rgb(0 0 0 / .32); }
.esc-dialog form { display: grid; gap: 12px; }
.esc-dialog h3 { margin: 0; font-size: var(--gemboss-fs-h2); }
.esc-dialog label { display: grid; gap: 6px; font-weight: 500; }
.esc-dialog .muted { margin: 0; font-size: var(--gemboss-fs-caption); }
.esc-dialog__foot { display: flex; justify-content: flex-end; gap: 8px; }
.esc-dialog__foot .ui-btn { min-height: 44px; }

/* ── Narrower screens ── */
/* Ngưỡng hạ từ 1365 xuống 1100.
 *
 * Rail cũ rộng 220px nên ở 1280 không còn chỗ cho cột phải, và nó bị ẩn hẳn — mà 1280 là khổ laptop
 * phổ biến nhất, tức thông tin khách biến mất đúng ở chỗ nhiều người làm việc nhất. Rail nay 64px:
 * 64 + 300 + 320 = 684, còn 596px cho luồng hội thoại ở 1280. Vừa. */
@media (max-width: 1099px) {
  .app { grid-template-columns: 64px 300px minmax(0, 1fr); }
  .app[data-screen] { grid-template-columns: 64px minmax(0, 1fr); }
  /* Dưới ngưỡng này cột phải không còn chỗ trong lưới — nhưng "không có chỗ" không được phép thành
     "không tới được". Nó trượt vào từ mép phải khi bấm nút Customer. */
  .details { display: none; }
  .app[data-details="open"] .details {
    display: block; position: fixed; inset: 0 0 0 auto; width: min(360px, 100vw);
    z-index: 30; box-shadow: -8px 0 24px rgb(0 0 0 / .18);
  }
  #t-details { display: inline-flex; }
  /* ≥44px theo ngưỡng điều khiển của §9.1; nổi ở góc phải trên, không đẩy nội dung xuống. */
  .details-close { display: inline-flex; position: absolute; top: 8px; right: 8px; min-width: 44px; min-height: 44px; }
  .app[data-details="open"] .details { padding-top: 60px; }
}
@media (max-width: 900px) {
  .board-screen { padding: 16px; }
  .board { grid-template-columns: repeat(4, minmax(240px, 80vw)); }
  .reports { padding: 16px; }
  /* Rail GIỮ NGUYÊN chiều dọc ở điện thoại, chỉ hẹp lại 56px.
   *
   * Luật cũ biến nó thành một hàng ngang trên đỉnh — hợp với rail CHỮ rộng 220px, vô nghĩa với rail
   * biểu tượng: 8 icon xếp ngang chiếm nguyên một dải màn hình để nói đúng thứ một cột 56px nói được.
   * Và `minmax(0, …)` chứ không phải `1fr`: một rãnh `1fr` nở ra bằng đứa con rộng nhất của nó, và cả
   * trang cuộn ngang ở 390px (đo được 8px ở hộp thư, 4px ở báo cáo).
   *
   * QUAN TRỌNG: rãnh của cột danh sách phải BIẾN MẤT, không chỉ ẩn phần tử. `display: none` giấu đứa
   * con nhưng `grid-template-columns` vẫn giữ rãnh 300px — ở viewport 320 thì 64 + 300 = 364 và trang
   * cuộn ngang 76px. Đo 23/09 ngay sau khi ghép main. */
  .app { grid-template-columns: 56px minmax(0, 1fr); }
  .app[data-screen] { grid-template-columns: 56px minmax(0, 1fr); }
  /* Một phần tử lưới mặc định `min-width: auto`, tức nó KHÔNG co được dưới bề rộng nội dung: rãnh
     tính ra 264px mà ô soạn vẫn 380px và cả trang cuộn ngang. Luật cũ vá chỗ này bằng
     `.rail > * { min-width: 0 }`; nay cần cho ba cột nội dung. */
  .list, .thread, .details { min-width: 0; }
  /* Hàng tab của ô soạn xuống dòng ở 264px. (Hàng nút dưới ô soạn xuống dòng ở MỌI khổ — xem
     `.composer-foot` ở trên: câu báo lỗi của nó lấy riêng một dòng.) */
  .composer-tabs { flex-wrap: wrap; }
  .rail { padding: 8px 0; gap: 8px; }
  /* Rail hẹp lại 56px ở điện thoại, nhưng KÍCH THƯỚC một điều khiển thì không co theo bề rộng rail:
     44px là ngưỡng §9.1 (WCAG 2.5.5) và nó áp cho cả ba loại nút, không riêng .rail-btn. Luật cũ chỉ
     nâng .rail-btn, nên công tắc trực và nút tài khoản đứng ở 40px từ #82 tới giờ (đo ở 320/390/768).

     CẢ HAI chiều, không riêng chiều cao: `.rail-duty`/`.rail-me` mang `border-radius: 50%`, nên
     44×40 không phải một nút nhỏ — nó là một hình BẦU DỤC. Nâng mỗi `height` vừa không đạt ngưỡng
     (2.5.5 đo cả bề ngang) vừa làm hỏng hình. 44 vẫn lọt rail 56px. */
  .rail-btn, .rail-duty, .rail-me { width: 44px; height: 44px; min-height: 44px; }
  .app[data-pane="thread"] .list { display: none; }
  .app:not([data-pane="thread"]) .thread { display: none; }
  .back { display: inline-flex; }
  .thread-actions { width: 100%; }
  /* `flex: 1` nghĩa là `flex-basis: 0` — ô chỉ nhận PHẦN THỪA sau khi ba nút cạnh nó lấy chỗ. Ở 390px
     chẳng còn gì, và ô Assignee co xuống **39×44**: dưới sàn chạm 44px của §9.1, đo trên prod 24/09.
     `.thread-actions` vốn đã `flex-wrap: wrap`, nên cho nó một BASIS đủ lớn thì nó xuống dòng riêng
     thay vì bị nghiền — rộng hơn hẳn 44px, và dễ bấm hơn cho ngón tay. */
  .thread-actions .assign { flex: 1 1 160px; min-width: 160px; }
  .assign .field { max-width: none; width: 100%; }
  .msg { max-width: 92%; }
  /* §9.1 measures tap targets at 320/390/768: a CONTROL is 44px there, however link-like it looks.
     Both of these read as text and are buttons — the gate counts what they are, and so do thumbs. */
  .link { min-height: 44px; }
}
@media (prefers-reduced-motion: reduce) { * { scroll-behavior: auto !important; transition: none !important; } }

/* "Load earlier messages", above the first page of a long thread. */
.older { display: flex; justify-content: center; padding: 8px 0 4px; }

/* When customers write, on their own clock. One hue for "how many"; an outline for "we were closed". */
.heat { overflow-x: auto; }
.heat-grid {
  display: grid;
  grid-template-columns: 32px repeat(24, minmax(12px, 1fr));
  gap: 2px;
  min-width: 420px;
  align-items: center;
}
.heat-corner { height: 16px; }
.heat-hour { font-size: var(--gemboss-fs-micro); color: var(--gemboss-text-muted); text-align: center; }
.heat-day { font-size: var(--gemboss-fs-micro); color: var(--gemboss-text-muted); }
.heat-cell {
  height: 16px;
  border-radius: 2px;
  /* The same iris the daily chart uses, light to dark: the two cards are one system, and a sequential ramp
     is one hue — a second colour here would read as a second variable. */
  background: color-mix(in srgb, var(--gemboss-accent) calc(var(--fill, 0) * 100%), var(--gemboss-hover));
}
/* Hours nobody is on duty: outlined rather than recoloured, so the count ramp stays readable. */
.heat-cell[data-covered="no"] { box-shadow: inset 0 0 0 1px var(--gemboss-warning); }

/* How customers rated us: one row per score, longest bar = most common. */
.csat { display: flex; flex-direction: column; gap: 6px; }
.csat-row { display: grid; grid-template-columns: 76px minmax(0, 1fr) 40px; align-items: center; gap: 8px; }
.csat-stars { font-size: var(--gemboss-fs-micro); color: var(--gemboss-text-muted); letter-spacing: 1px; }
.csat-bar { height: 8px; border-radius: 4px; background: var(--gemboss-hover); overflow: hidden; }
.csat-bar-fill { display: block; height: 100%; width: 0; background: var(--gemboss-accent); }
/* One and two stars are the review queue, not just a shorter bar. */
.csat-row[data-low="yes"] .csat-bar-fill { background: var(--gemboss-warning); }
.csat-count { font-size: var(--gemboss-fs-micro); color: var(--gemboss-text-muted); text-align: right; }

/* Search sits in the list header, where the list it filters is. */
.list-head { display: flex; flex-direction: column; gap: 8px; align-items: stretch; }
.search { display: flex; }
.search .field { width: 100%; min-height: 44px; }

/* Saved replies: a list above the composer, driven by `/` in the box. */
.replies { display: flex; flex-direction: column; gap: 2px; max-height: 240px; overflow-y: auto; margin-bottom: 4px; }
/* Bảng chọn tên ở chat đội: cùng khối `.replies`/`.reply` với ô ghi chú (một hình dạng, §9.3). Ở màn Chat nó trải hết
   ba cột của lưới ô nhập; ở cửa sổ nổi nó nằm ngay trên ô nhập. */
.chat-mention { grid-column: 1 / -1; }
.dock-win__mention { margin: 0 8px; }
.reply {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas: "shortcut title" "shortcut body";
  gap: 0 8px;
  min-height: 44px;
  padding: 6px 8px;
  border: 1px solid var(--gemboss-divider);
  border-radius: 12px;
  background: var(--gemboss-surface);
  text-align: left;
  cursor: pointer;
}
.reply[aria-selected="true"] { border-color: var(--gemboss-accent); background: var(--gemboss-hover); }
.reply-shortcut { grid-area: shortcut; align-self: center; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: var(--gemboss-fs-micro); color: var(--gemboss-accent); }
.reply-title { grid-area: title; font-size: var(--gemboss-fs-body); color: var(--gemboss-text-strong); }
.reply-body { grid-area: body; font-size: var(--gemboss-fs-micro); color: var(--gemboss-text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.replies-open { flex: 0 0 auto; }

/* The customer's name is a way in, so it looks like one. */
.t-name-btn {
  padding: 0;
  border: 0;
  background: none;
  color: var(--gemboss-text-strong);
  font: inherit;
  cursor: pointer;
  text-align: left;
  /* Ngưỡng ĐIỀU KHIỂN của §9.1, không phải ngưỡng 24px của link trong danh sách: đây là một nút mở
     một màn khác. `align-content` để hai dòng vẫn dính nhau khi khối cao hơn nội dung. */
  min-height: 44px;
  align-content: center;
}
.t-name-btn strong { font-weight: 600; }
/* Email nằm TRONG một <button>, nên mặc định nó không bôi đen được. Chrome nghe `user-select`;
   Firefox/Safari thì không cho chọn chữ trong nút dù khai gì đi nữa. Đường chắc chắn để sao chép
   email vẫn là ngăn Customer bên phải (mục Email ở `#d-contact`) — ở đó nó là văn bản thường. */
.t-name-btn #t-sub { -webkit-user-select: text; user-select: text; cursor: text; }
.t-name-btn:hover { color: var(--gemboss-accent); text-decoration: underline; }
.t-name-btn:focus-visible { outline: 2px solid var(--gemboss-primary); outline-offset: 2px; }

/* Everything this customer has said to us. */
.profile h4 { margin: 16px 0 6px; font-size: var(--gemboss-fs-body); color: var(--gemboss-text-strong); }
.profile .tiles { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
.profile .tile { padding: 8px 12px; border: 1px solid var(--gemboss-divider); border-radius: 12px; background: var(--gemboss-surface); display: flex; flex-direction: column; gap: 2px; }
.profile .t-label { font-size: var(--gemboss-fs-micro); color: var(--gemboss-text-muted); }
.profile .t-value { font-size: var(--gemboss-fs-h3); color: var(--gemboss-text-strong); }
.profile .t-sub { font-size: var(--gemboss-fs-micro); color: var(--gemboss-text-muted); }
.profile .previous button { display: grid; gap: 2px; width: 100%; min-height: 44px; padding: 8px; border: 0; border-radius: 12px; background: none; text-align: left; cursor: pointer; }
.profile .previous button:hover { background: var(--gemboss-hover); }
.profile .previous button[aria-current="true"] { background: var(--gemboss-hover); }
.profile .p-title { font-size: var(--gemboss-fs-body); color: var(--gemboss-text-strong); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.profile .p-meta { font-size: var(--gemboss-fs-micro); color: var(--gemboss-text-muted); }
/* The profile's header is a row: title and subtitle on the left, close in the corner like every other drawer. */
.profile .drawer-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; padding-right: 44px; }

/* How long this customer has been waiting, in working time. Red once it is past the line. */
.c-wait { font-size: var(--gemboss-fs-micro); color: var(--gemboss-text-muted); white-space: nowrap; }
.c-wait[data-overdue="true"] { color: var(--gemboss-danger); font-weight: 600; }
/* Dấu ĐANG HOÃN trên hàng: cùng cỡ chữ với đồng hồ chờ, nhạt hơn — nó là trạng thái, không phải báo động. */
.c-snoozed { font-size: var(--gemboss-fs-micro); color: var(--gemboss-text-muted); white-space: nowrap; }
.count[data-overdue="true"] { background: var(--gemboss-danger); color: var(--gemboss-surface); }

/* What a conversation is about: fixed topics as toggles, free tags as removable chips. */
.labels { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.chip {
  min-height: 44px;
  padding: 6px 12px;
  border: 1px solid var(--gemboss-divider);
  border-radius: 20px;
  background: var(--gemboss-surface);
  color: var(--gemboss-text-muted);
  font: inherit;
  font-size: var(--gemboss-fs-micro);
  cursor: pointer;
}
.chip:hover { color: var(--gemboss-text-strong); }
.chip[aria-pressed="true"] { border-color: var(--gemboss-accent); background: var(--gemboss-accent-soft); color: var(--gemboss-accent-strong); }
.chip--tag { border-style: dashed; }
.chip:focus-visible { outline: 2px solid var(--gemboss-primary); outline-offset: 2px; }
.tag-add .field { width: 100%; min-height: 44px; }

/* Team settings: closed days and refused mail. */
.closure-add,
.kpi-add,
.sr-add { display: flex; flex-wrap: wrap; gap: 12px; align-items: end; margin-top: 12px; }
.closure-field,
.kpi-field,
.sr-field { display: grid; gap: 4px; font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted); }
.closure-field .field,
.kpi-field .field,
.sr-field .field { width: 100%; }
/* 160px, not 220: at 320px the name field then shares its row with Add instead of pushing the button
   below the fold — measured 578px against a 568px fold before this. */
.closure-field.grow,
.kpi-field.grow,
.sr-field.grow { flex: 1 1 160px; }

/* Nội dung câu mẫu là một đoạn văn, không phải một ô chữ: nó chiếm trọn một hàng và tự xuống dòng
   cùng với hai ô trên nhờ `flex-wrap` của `.sr-add`. `flex-basis: 100%` chứ không phải `width`:
   trong một flex container, `width` vẫn để ô co lại chen cạnh ô khác. */
.sr-field.wide { flex: 1 1 100%; }
.sr-field .ui-textarea { width: 100%; }
/* Hai nút đi cùng nhau xuống hàng cuối, và giữ khe 8px cho §9.1 khi chúng nằm cạnh nhau. */
.sr-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: end; }

/* Chủ đề đã bỏ khỏi danh sách nhưng hội thoại này vẫn mang: nhạt đi để nó không mời bấm, nhưng KHÔNG
   ẩn — ẩn đi là cách một cái nhãn còn trong báo cáo mà không ai gỡ được. Chữ "(đã bỏ)" mới là thứ
   nói ra trạng thái; màu chỉ đi kèm. */
/* `.chip[aria-pressed="true"]` là 0-2-0, còn `.chip--retired` một mình chỉ 0-1-0 — mà chip đã bỏ
   CHỈ hiện ra khi đang được chọn, nên màu ở đây không bao giờ thắng: một khai báo chết. Ghép cả hai
   để nó thật sự đổi hình. Chữ "(đã bỏ)" vẫn là thứ nói ra trạng thái; nét đứt chỉ đi kèm. */
.chip--retired,
.chip--retired[aria-pressed="true"] { border-style: dashed; color: var(--gemboss-text-muted); }
.form-error { color: var(--gemboss-danger); font-size: var(--gemboss-fs-caption); margin: 8px 0 0; }
/* A day already past is history, not a setting — legible, but it stops competing with the ones ahead. */
.ui-table tr.is-past td { color: var(--gemboss-text-muted); }

.junk { display: grid; gap: 12px; margin-top: 12px; }
.junk-item {
  display: grid; gap: 8px; justify-items: start; min-width: 0; max-width: 100%;
  padding: 12px; border: 1px solid var(--gemboss-divider); border-radius: var(--gemboss-radius-md);
  background: var(--gemboss-hover);
}
/* An email address is ONE unbroken token, and a cold pitch is where the 90-character ones live. Without
   `anywhere` the header refuses to shrink and drags the card to 567px inside a 320px panel — which the
   visual gate does NOT report, because `.reports` scrolls it as an ancestor and a clipped overflow is
   legitimate for a carousel. Measured with `boundingBox()`, not with the gate. */
.junk-item header { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; min-width: 0; max-width: 100%; font-size: var(--gemboss-fs-caption); }
.junk-item header strong, .junk-subject { overflow-wrap: anywhere; min-width: 0; }
.junk-rule {
  border-radius: var(--gemboss-radius-full); padding: 2px 8px;
  background: var(--gemboss-surface); border: 1px solid var(--gemboss-divider); color: var(--gemboss-text-muted);
}
.junk-subject { margin: 0; font-weight: 600; max-width: 100%; }
/* A disclosure is a control, not a link in prose — 44px (WCAG 2.5.5). Declared at full strength here rather
   than in the ≤900px block: this file's junk rules sit AFTER that block, so an override there loses the
   cascade at equal specificity and the gate keeps measuring 24px. */
.junk-item summary { display: inline-flex; align-items: center; min-height: 44px; cursor: pointer; color: var(--gemboss-text-muted); }
.junk-body {
  margin: 8px 0 0; max-height: 240px; overflow: auto; white-space: pre-wrap; word-break: break-word;
  font: inherit; color: var(--gemboss-text-muted);
}

/* Help centre: a status chip in the list, and a title that reads as a link because it opens the editor. */
/* Cùng lý do. Đây là một `<button>` nằm trong ô bảng, và cổng đếm nó là ĐIỀU KHIỂN chứ không phải
   link — "however link-like it looks", đúng câu chú thích đã có sẵn trong file này. Đo trên prod ở
   390px với một nhãn ngắn: 29×44. */
.link-strong {
  border: 0; padding: 0; min-height: 44px; min-width: 44px; background: none; cursor: pointer; text-align: left;
  font-weight: 600; color: var(--gemboss-text-strong); text-decoration: underline;
}
.chip[data-status="published"] { border-color: var(--gemboss-success); color: var(--gemboss-success); }
.chip[data-status="draft"] { border-style: dashed; }
.chip[data-status="archived"] { color: var(--gemboss-text-muted); }

/* Mentions and the shortcut sheet. */
.mention-list { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 8px; }
.mention-list button {
  display: grid; gap: 2px; width: 100%; min-height: 44px; padding: 12px; text-align: left; cursor: pointer;
  border: 1px solid var(--gemboss-divider); border-radius: var(--gemboss-radius-md);
  background: var(--gemboss-surface); color: var(--gemboss-text);
}
.mention-list button:hover { background: var(--gemboss-hover); }
.mention-list button:focus-visible { outline: 2px solid var(--gemboss-primary); outline-offset: 2px; }
.mention-who { font-weight: 600; overflow-wrap: anywhere; }
.mention-text { color: var(--gemboss-text-muted); overflow-wrap: anywhere; }

/* Two columns that stay readable at 320: the keys are narrow, the meaning takes the rest. */
.keys { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 8px 12px; margin: 12px 0 0; align-items: baseline; }
.keys dt { white-space: nowrap; }
.keys dd { margin: 0; color: var(--gemboss-text-muted); }
kbd {
  display: inline-block; min-width: 20px; padding: 2px 6px; text-align: center;
  font: inherit; font-size: var(--gemboss-fs-caption); font-variant-numeric: tabular-nums;
  border: 1px solid var(--gemboss-divider); border-bottom-width: 2px; border-radius: var(--gemboss-radius-md);
  background: var(--gemboss-hover); color: var(--gemboss-text);
}

/* Lengths and shades as ATTRIBUTES: the page runs under style-src 'self', so an inline style is ignored
   silently — the bar would render at zero with nothing to say why. */
[data-level="0"].csat-bar-fill, [data-level="0"].file-bar-fill { width: 0%; }
[data-level="1"].csat-bar-fill, [data-level="1"].file-bar-fill { width: 5%; }
[data-level="2"].csat-bar-fill, [data-level="2"].file-bar-fill { width: 10%; }
[data-level="3"].csat-bar-fill, [data-level="3"].file-bar-fill { width: 15%; }
[data-level="4"].csat-bar-fill, [data-level="4"].file-bar-fill { width: 20%; }
[data-level="5"].csat-bar-fill, [data-level="5"].file-bar-fill { width: 25%; }
[data-level="6"].csat-bar-fill, [data-level="6"].file-bar-fill { width: 30%; }
[data-level="7"].csat-bar-fill, [data-level="7"].file-bar-fill { width: 35%; }
[data-level="8"].csat-bar-fill, [data-level="8"].file-bar-fill { width: 40%; }
[data-level="9"].csat-bar-fill, [data-level="9"].file-bar-fill { width: 45%; }
[data-level="10"].csat-bar-fill, [data-level="10"].file-bar-fill { width: 50%; }
[data-level="11"].csat-bar-fill, [data-level="11"].file-bar-fill { width: 55%; }
[data-level="12"].csat-bar-fill, [data-level="12"].file-bar-fill { width: 60%; }
[data-level="13"].csat-bar-fill, [data-level="13"].file-bar-fill { width: 65%; }
[data-level="14"].csat-bar-fill, [data-level="14"].file-bar-fill { width: 70%; }
[data-level="15"].csat-bar-fill, [data-level="15"].file-bar-fill { width: 75%; }
[data-level="16"].csat-bar-fill, [data-level="16"].file-bar-fill { width: 80%; }
[data-level="17"].csat-bar-fill, [data-level="17"].file-bar-fill { width: 85%; }
[data-level="18"].csat-bar-fill, [data-level="18"].file-bar-fill { width: 90%; }
[data-level="19"].csat-bar-fill, [data-level="19"].file-bar-fill { width: 95%; }
[data-level="20"].csat-bar-fill, [data-level="20"].file-bar-fill { width: 100%; }
.heat-cell[data-level="1"] { background: color-mix(in srgb, var(--gemboss-accent) 23%, var(--gemboss-hover)); }
.heat-cell[data-level="2"] { background: color-mix(in srgb, var(--gemboss-accent) 34%, var(--gemboss-hover)); }
.heat-cell[data-level="3"] { background: color-mix(in srgb, var(--gemboss-accent) 45%, var(--gemboss-hover)); }
.heat-cell[data-level="4"] { background: color-mix(in srgb, var(--gemboss-accent) 56%, var(--gemboss-hover)); }
.heat-cell[data-level="5"] { background: color-mix(in srgb, var(--gemboss-accent) 67%, var(--gemboss-hover)); }
.heat-cell[data-level="6"] { background: color-mix(in srgb, var(--gemboss-accent) 78%, var(--gemboss-hover)); }
.heat-cell[data-level="7"] { background: color-mix(in srgb, var(--gemboss-accent) 89%, var(--gemboss-hover)); }
.heat-cell[data-level="8"] { background: color-mix(in srgb, var(--gemboss-accent) 100%, var(--gemboss-hover)); }
.heat-cell[data-level="0"] { background: var(--gemboss-hover); }

/* The question a merchant typed, quoted back to whoever is about to answer it. Quiet, but not muted
   into invisibility: it is the reason the article exists. */
.asked { margin: 0; padding: 8px 12px; border-left: 2px solid var(--gemboss-divider);
  background: var(--gemboss-canvas); color: var(--gemboss-text-muted); font-size: var(--gemboss-fs-caption); }
.asked b { color: var(--gemboss-text-strong); font-weight: 600; }

/* The attachment bucket refusing an origin the chat runs on: red, because every screenshot sent from
   there is lost in the customer's browser and we never learn it existed. */
.intake[data-state="blocked"] .conn-dot { background: var(--gemboss-danger); }
.intake[data-state="blocked"] { color: var(--gemboss-danger); font-weight: 600; }

/* An origin that produced a SIGNED customer token is not a stranger — it is our own product being
   refused. It gets the danger colour because that is a customer who cannot reach us at all. */
.ui-table tr[data-ours="true"] td { color: var(--gemboss-danger); font-weight: 600; }

/* The schedule itself — read at a glance, so it is the biggest thing on the card. */
.hours-line { margin: 0 0 4px; font-size: var(--gemboss-fs-body-lg); color: var(--gemboss-text-strong); font-weight: 600; }

/* The hours form. Wraps to one column on a phone, like every other form on this screen. */
.hours-form { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end; margin-top: 12px; }
.hours-form .hours-field { display: flex; flex-direction: column; gap: 4px; font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted); }
.hours-form .hours-field.grow { flex: 1 1 220px; }
.hours-days { border: 1px solid var(--gemboss-divider); border-radius: var(--gemboss-radius-md); padding: 8px 12px 12px; margin: 0; flex: 1 1 100%; }
.hours-days legend { font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted); padding: 0 4px; }
.hours-days div { display: flex; flex-wrap: wrap; gap: 8px; }
.hours-days label { display: flex; align-items: center; gap: 6px; min-height: 44px; padding: 0 8px; cursor: pointer; }
.hours-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* Chat nội bộ: cột kênh + cuộc trò chuyện, cùng hình dạng với hộp thư nên không ai phải học một cách
   đọc thứ hai. Ở khổ hẹp cột kênh nằm TRÊN, cuộn ngang, thay vì biến mất. */
.chat-screen { display: grid; grid-template-columns: 220px minmax(0, 1fr); min-height: 0; overflow: hidden; }
.chat-rail { border-right: 1px solid var(--gemboss-divider); padding: 16px; overflow-y: auto; }
.chat-rail__title { font-size: var(--gemboss-fs-caption); text-transform: uppercase; letter-spacing: .06em;
  color: var(--gemboss-text-muted); margin: 0 0 8px; }
.chat-rail ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.chat-channel { width: 100%; text-align: left; min-height: 44px; padding: 0 12px; border: 0; border-radius: var(--gemboss-radius-md);
  background: none; font: inherit; color: var(--gemboss-text); cursor: pointer; }
.chat-channel:hover { background: var(--gemboss-hover); }
.chat-channel[aria-current="true"] { background: var(--gemboss-hover); font-weight: 600; }
/* Hàng kênh: tên co lại, số chưa đọc KHÔNG co. Không có `flex: none` thì một tên dài đẩy con số ra
   khỏi hàng và huy hiệu biến mất đúng ở kênh đang ồn nhất. */
.chat-channel { display: flex; align-items: center; gap: 8px; }
.chat-channel__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-channel__unread { flex: none; background: var(--gemboss-accent); color: var(--ui-accent-fg); }
/* Lời gọi tên dùng màu CẢNH BÁO, không phải màu nhấn của số chưa đọc: hai ý nghĩa khác nhau thì
   không được trông giống nhau (§9.3). Và nó có chữ "@" chứ không chỉ màu — màu một mình không đọc
   được với người mù màu, và cũng không đọc thành lời được. */
.chat-channel__calls { flex: none; background: var(--gemboss-warning); color: var(--gemboss-text-strong); font-weight: 700; }

/* Tin gọi đích danh mình: một vạch bên trái, KHÔNG phải nền màu. Nền màu trên cả khối tin làm chữ
   khó đọc và tranh chấp với mọi trạng thái khác của hàng. */
.chat-msg--calls-me { border-left: 2px solid var(--gemboss-warning); padding-left: 8px; }

.chat-quote {
  display: flex; align-items: baseline; gap: 8px; width: 100%; text-align: left; cursor: pointer;
  /* 44px, không phải 24px. Đây là một <button> thật — bấm vào là xem tin gốc, nhảy tới nó nếu nó
     đang trong danh sách, mở luồng nếu không — nên nó thuộc tầng ĐIỀU KHIỂN của §9.1, không phải
     tầng "link trong danh sách". Giá phải trả là mỗi tin trả lời cao thêm ~20px; đổi lại nó bấm
     trúng được bằng ngón tay. */
  min-height: 44px; padding: 2px 8px; margin-bottom: 2px; border: 0; border-left: 2px solid var(--gemboss-divider);
  background: none; font: inherit; font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted);
}
.chat-quote:hover { background: var(--gemboss-hover); }
/* Đang tra luồng. Nạp xong mới mở hộp thoại (xem `openChatThread`), nên chỗ báo "đang chạy"
   phải nằm ở chính cái nút vừa bấm — nếu không, cú bấm trông như không ăn. */
.chat-quote[aria-busy="true"] { opacity: .55; cursor: progress; }
.chat-quote__who { flex: none; font-weight: 600; }
/* Đoạn trích PHẢI cắt bằng ellipsis: một tin gốc dài 120 ký tự mà xuống dòng thì dòng trích cao hơn
   chính tin trả lời nó. */
.chat-quote__text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Hộp thoại luồng: tin gốc rồi tới các trả lời. Không dùng lại `.chat-msg` — hàng ở đây không có
   nút Reply, không có thẻ link, không có trạng thái "gọi tên tôi", nên vay nét của nó chỉ tạo ra một
   chủ sở hữu thị giác thứ hai cho cùng một hình dạng (§9.3). */
.thread-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.thread-msg { border-left: 2px solid var(--gemboss-divider); padding-left: 8px; }
.thread-msg--root { border-left-color: var(--gemboss-accent); }
.thread-msg__who {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px;
  font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted);
}
.thread-msg__tag { font-weight: 600; }
/* Tin trong luồng KHÔNG cắt bằng ellipsis — người mở hộp này mở nó để đọc hết câu. */
.thread-msg__body { white-space: pre-wrap; overflow-wrap: anywhere; }

/* Ô "bỏ khỏi báo cáo" trong ngăn chi tiết. Xếp dọc và cho khoảng thở: ô chọn cạnh nút trên một hàng
   ngang sẽ tràn ở khổ 320, và ngăn này vốn đã hẹp. */
.d-exclusion { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
/* Trạng thái "đã bỏ" phải ĐỌC RA ĐƯỢC, không chỉ đổi màu: một hội thoại đã bỏ trông y hệt hội thoại
   bình thường là cách người thứ hai tưởng số liệu sai. */
.d-exclusion .d-excluded { margin: 0; font-weight: 600; }
.d-exclusion .ui-select, .d-exclusion .ui-btn { width: 100%; }

/* Menu Hoãn. `<details>` chứ không phải một lớp phủ tự vẽ: Esc và bấm-ra-ngoài là thứ trình duyệt
   cho sẵn, và `summary` đã là một nút bấm được bằng bàn phím. */
.snooze { position: relative; }
.snooze > summary { list-style: none; cursor: pointer; }
.snooze > summary::-webkit-details-marker { display: none; }
/* Đang hoãn: "☾ Until Mon 09:03 PM" (hay "☾ No reminder") ở MỌI khổ — cùng dấu ☾ với chip ở hàng danh
   sách, nên nút tự nói "hoãn" bằng mắt; trình đọc màn hình nghe chữ "Snoozed," ẩn đi trước nó. */
.snooze > summary.is-snoozed { white-space: normal; text-align: left; }
.snooze-moon { margin-right: 4px; }
/* Neo vào MÉP PHẢI của nút, mở sang trái.
 *
 * Neo `left: 0` như trước thì ở 901px menu chạy tới x 971 trên khung 901: trang CUỘN NGANG — đúng lỗi
 * đoạn chú thích dưới đây đã ghi cho 768. Trên 900px mép phải nút Snooze luôn ở x ≥ 452 (cột thread bắt
 * đầu từ x ≥ 364), nên mở sang trái 200px không chạm mép trái khung nhìn; dưới 900px menu là tấm dưới
 * đáy, không neo vào nút. */
.snooze-menu {
  position: absolute; z-index: 20; top: calc(100% + 4px); right: 0; min-width: 200px;
  display: flex; flex-direction: column; gap: 4px; padding: 8px;
  background: var(--gemboss-surface); border: 1px solid var(--gemboss-divider);
  border-radius: var(--gemboss-radius-md); box-shadow: 0 8px 24px rgb(0 0 0 / .12);
}
/* Mỗi mục là một điều khiển thật: sàn 44px của §9.1, và căn trái để đọc như một danh sách. */
.snooze-menu .ui-btn { min-height: 44px; justify-content: flex-start; width: 100%; }
/* Ở khổ hẹp menu GHIM VÀO KHUNG NHÌN, không neo vào nút.
 *
 * Hai lần thử neo vào nút đều hỏng, và hỏng ở hai phía khác nhau — vì vị trí cái nút ĐỔI theo khổ:
 *   `right: 0`  → ở 320px menu nằm ở left -48 (48px ngoài mép TRÁI, bị cắt, trang không cuộn nên
 *                 phép đo "tràn ngang" một mình không thấy gì)
 *   `left: 0`   → ở 768px menu chạy tới 872 (104px ngoài mép PHẢI, và trang CUỘN NGANG)
 * Chọn phía nào cũng sai ở phía kia. Cái sai là neo vào một mốc di động.
 *
 * `position: fixed` + bốn mép của khung nhìn thì không phụ thuộc nút nằm đâu. Dựng thành một tấm
 * dưới đáy — mẫu quen trên điện thoại, và ngón tay với tới dễ hơn một menu bám mép trên. */
@media (max-width: 900px) {
  .snooze-menu {
    position: fixed; top: auto; left: 16px; right: 16px; bottom: 16px; width: auto; min-width: 0;
  }
}

/* ── Ảnh đại diện ───────────────────────────────────────────────────────────────────────────────
   Một khối, một biến `--av` cho kích thước, dùng ở hàng tin (36), rail (32) và panel You (72).
   Ô chữ cái đầu là một ô ĐẦY ĐỦ chứ không phải nền trống: hàng có ảnh và hàng không có phải cùng
   một mép chữ, nếu không thì cả danh sách đọc ra như hai danh sách chồng lên nhau. */
.av {
  --av: 36px;
  width: var(--av); height: var(--av); flex: none;
  display: inline-flex; border-radius: 50%; overflow: hidden;
  /* `overflow: hidden` cắt ảnh theo hình tròn, nhưng Safari vẫn vẽ góc vuông chồng lên khi phần tử
     nằm trong một stacking context đang transform. `isolation` dựng một context riêng, và đó là
     cách rẻ nhất để hình tròn là hình tròn ở mọi trình duyệt. */
  isolation: isolate;
}
.av__img { width: 100%; height: 100%; object-fit: cover; display: block; }
.av__initials {
  width: 100%; height: 100%; display: inline-flex; align-items: center; justify-content: center;
  font-weight: 600; font-size: calc(var(--av) * .38); line-height: 1; letter-spacing: .02em;
  /* Màu suy từ tên (xem `initialsNode`). Sắc ĐỘ cố định, chỉ đổi sắc MÀU qua 12 dải `.av--hN`.
     ĐO trong Chromium 26/09/2026, tỉ lệ tương phản WCAG của chữ trên nền, cả 12 dải:
       h0 8,78 · h1 6,86 · h2 4,75 · h3 5,21 · h4 5,43 · h5 5,35
       h6 5,18 · h7 7,93 · h8 10,87 · h9 9,75 · h10 8,01 · h11 8,52
     Thấp nhất 4,75:1 (h2, 60° vàng), trên ngưỡng AA 4,5:1 cho chữ thường. Tôi từng viết ở đây là
     "~7,9:1 ở mọi góc màu" — tính trên giấy, và sai: độ sáng HSL không phải độ sáng mắt người thấy,
     nên dải vàng–lục thấp hơn dải lam–tím gấp đôi. Ai đổi hai con số 92%/28% này thì ĐO LẠI cả 12,
     h2 là dải sát ngưỡng nhất. */
  background: hsl(var(--av-hue, 220) 45% 92%);
  color: hsl(var(--av-hue, 220) 55% 28%);
}
/* Kích thước: MỘT tập class cố định, vì CSP `style-src 'self'` chặn `style` inline (xem avatarNode). */
.av--s20 { --av: 20px; }
.av--s32 { --av: 32px; }
.av--s36 { --av: 36px; }
.av--s72 { --av: 72px; }
/* Mười hai dải màu. Sắc ĐỘ cố định ở .av__initials, chỉ đổi sắc MÀU ở đây — nên tương phản của cả
   mười hai là cùng một phép tính (xem chú thích ở .av__initials) và đã được đo trong trình duyệt. */
.av--h0 { --av-hue: 0; }   .av--h1 { --av-hue: 30; }  .av--h2 { --av-hue: 60; }   .av--h3 { --av-hue: 90; }
.av--h4 { --av-hue: 120; } .av--h5 { --av-hue: 150; } .av--h6 { --av-hue: 180; }  .av--h7 { --av-hue: 210; }
.av--h8 { --av-hue: 240; } .av--h9 { --av-hue: 270; } .av--h10 { --av-hue: 300; } .av--h11 { --av-hue: 330; }
/* Chỗ giữ cho tin được gộp: giữ CỘT, không giữ CHIỀU CAO.
   Bản đầu là `visibility: hidden` với nguyên 36×36, và đo được hệ quả: hàng gộp chỉ có một dòng chữ
   20px nhưng cao 36px, nên câu thứ ba của một mạch cách câu thứ hai 40px trong khi câu thứ hai cách
   câu đầu 24px — một mạch ba câu mà nhịp gãy ở giữa. Cột đã được `grid-template-columns: 36px` giữ
   sẵn, nên ô này chỉ cần chiếm chỗ trong cột, không cần cao. */
.av--spacer { visibility: hidden; height: 0; }

/* Hàng tin có HAI cột: ảnh, rồi mọi thứ còn lại. `minmax(0, 1fr)` chứ không `1fr` — một grid item
   mặc định không co dưới bề rộng nội dung, nên một URL dài không ngắt sẽ đẩy cả cột ra ngoài. */
.chat-msg { position: relative; display: grid; grid-template-columns: 36px minmax(0, 1fr); gap: 8px; align-items: start; }
/* ĐẶT CHỖ TƯỜNG MINH, không để lưới tự xếp.
 *
 * Ở màn rộng `.chat-acts` là `absolute` nên nó ra khỏi lưới, và tự xếp cho ra đúng: ảnh cột 1, thân
 * cột 2. Ở khổ hẹp và màn cảm ứng nó thôi `absolute` (để không đè chữ) — tức nó thành một Ô LƯỚI,
 * chiếm cột 2 ngay sau ảnh, và đẩy `.chat-msg__main` xuống dòng dưới, vào CỘT 1 rộng 36px. Ảnh chụp
 * 390px cho thấy đúng thế: "Mình vừa đặt ảnh đại diện" mỗi dòng một chữ, và "người" bị bẻ thành
 * "ngườ / i". Mọi check của cổng thị giác đều xanh cho bản đó.
 * (Từ 28/09/2026 thanh nút nằm TRONG `.chat-msg__main` nên không còn thành ô lưới được nữa; đặt chỗ tường minh vẫn
 * giữ, để con thứ ba nào sau này cũng không đẩy thân tin vào cột ảnh.) */
.chat-msg > .av { grid-column: 1; grid-row: 1; }
.chat-msg > .chat-msg__main { grid-column: 2; grid-row: 1; min-width: 0; }
.chat-msg__main { min-width: 0; }
/* Tin được gộp nằm sát tin trên: chúng là MỘT lượt nói. `.chat-messages` đặt gap 12px cho mọi hàng,
   nên phải kéo ngược lại ở đây. */
.chat-msg--grouped { margin-top: -8px; }
.chat-msg__who { display: flex; align-items: center; gap: 8px; }
/* Thanh nút nổi của một tin — kiểu Lark / Slack (chatActs trong inbox.js).
 *
 * Bản trước neo thanh vào góc phải của HÀNG, mà hàng rộng bằng cả cột: rê chuột lên tin "ok" thì thanh hiện cách chữ
 * 789px ở khổ 1280 và 1429px ở 1920, và hàng không sáng nền — không gì nối thanh với tin (đo 28/09/2026). Giờ:
 *   · hàng sáng nền khi rê chuột / focus bàn phím — mắt biết đang đứng ở tin nào;
 *   · khối nội dung co theo chữ (`fit-content`) và chừa sẵn chỗ bên phải (`--chat-acts-w`), thanh đứng NGAY sau chữ,
 *     nhô lên mép trên một chút như Slack; tin dài chạm trần bề rộng thì thanh vẫn có chỗ, không đè lên chữ;
 *   · khung hẹp (cửa sổ chat nổi 340px, màn nhỏ) thì container query đặt thanh ở góc trên phải của HÀNG.
 * Nút vẫn 44px (§9.1 — điều khiển); thanh `absolute` nên không cộng chiều cao nào vào hàng. `pointer-events: none`
 * lúc ẩn: một nút trong suốt vẫn nhận cú bấm là nút bẫy (#82). `:focus-within` để đi Tab qua các nút giữ thanh hiện. */
.chat-messages { container-type: inline-size; --chat-acts-w: 272px; }
.chat-msg { padding: 4px 12px; margin-inline: -12px; border-radius: var(--gemboss-radius-md); }
.chat-msg:hover, .chat-msg:focus-within, .chat-msg--picking { background: var(--gemboss-hover); }
.chat-msg > .chat-msg__main { position: relative; justify-self: start; width: fit-content; max-width: calc(100% - var(--chat-acts-w)); }
.chat-acts {
  position: absolute; top: -8px; left: calc(100% + 8px); z-index: 2; opacity: 0; pointer-events: none;
  display: flex; align-items: center; gap: 2px; padding: 2px;
  border: 1px solid var(--gemboss-divider); border-radius: var(--gemboss-radius-md);
  background: var(--gemboss-surface); box-shadow: var(--gemboss-shadow-resting); white-space: nowrap;
}
.chat-act {
  min-width: 44px; min-height: 44px; padding: 0 10px;
  border: 0; border-radius: var(--gemboss-radius-sm); background: transparent; cursor: pointer; font: inherit;
  font-size: var(--gemboss-fs-caption); color: var(--gemboss-accent);
}
.chat-act--emoji { padding: 0; font-size: 18px; }
.chat-act:hover { background: var(--gemboss-hover); }
.chat-act[aria-pressed="true"] { background: var(--gemboss-primary-soft); }
.chat-msg > .chat-msg__hover-time {
  grid-column: 1; grid-row: 1; align-self: center; justify-self: end; opacity: 0;
  font-size: 11px; line-height: 1; color: var(--gemboss-text-muted); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.chat-msg:hover > .chat-msg__hover-time, .chat-msg:focus-within > .chat-msg__hover-time { opacity: 1; }
.chat-msg:hover .chat-acts,
.chat-acts:focus-within,
.chat-msg--picking .chat-acts { opacity: 1; pointer-events: auto; }
@container (max-width: 640px) {
  .chat-msg > .chat-msg__main { position: static; max-width: 100%; }
  .chat-acts { left: auto; right: 4px; top: -14px; }
}
/* Thiết bị KHÔNG có hover, ở MỌI khổ — không chỉ khổ hẹp.
 *
 * Luật cũ chỉ cứu thanh nút dưới 900px, nên một iPad ngang (1024, cảm ứng) vẫn rơi vào nhánh
 * "hiện khi rê chuột" mà ở đó không có con trỏ nào để rê. Bề rộng là một phép đoán về thiết bị;
 * `hover: none` là chính câu hỏi cần hỏi. Để SAU luật base nên nó thắng mà không cần `!important`. */
@media (hover: none) {
  /* Thôi nổi ⇒ nằm trong luồng, DƯỚI nội dung tin, cùng cột với chữ. */
  .chat-msg > .chat-msg__main { max-width: 100%; }
  .chat-acts { position: static; opacity: 1; pointer-events: auto; margin-top: 4px; width: fit-content; }
  /* Luôn hiện dưới MỌI tin thì chỉ giữ ☺＋ và Reply: năm nút ở mỗi tin là một hàng nút nặng hơn cả chữ, và bảng
     chọn vẫn có đủ tám biểu tượng. */
  .chat-act--emoji { display: none; }
  .chat-msg__who { flex-wrap: wrap; }
}

/* Bảng chọn biểu tượng. Nằm TRONG luồng (không `absolute`): một lớp nổi trong một cột cuộn phải tự
   lo chuyện bị mép dưới cắt, còn một hàng trong luồng thì trình duyệt tự cuộn tới. */
.chat-picker {
  display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0;
  padding: 4px; border: 1px solid var(--gemboss-divider); border-radius: var(--gemboss-radius-md);
  background: var(--gemboss-surface); box-shadow: var(--gemboss-shadow-resting); width: fit-content; max-width: 100%;
}
.chat-picker__btn {
  min-width: 44px; min-height: 44px; padding: 0;
  border: 1px solid transparent; border-radius: var(--gemboss-radius-sm);
  background: none; cursor: pointer; font-size: 20px; line-height: 1;
}
.chat-picker__btn:hover { background: var(--gemboss-hover); }
.chat-picker__btn[aria-pressed="true"] { border-color: var(--gemboss-accent); background: var(--gemboss-primary-soft); }

/* Chip đã thả.
 *
 * Hộp bấm cao 44px theo ngưỡng ĐIỀU KHIỂN của §9.1, nhưng viên thuốc NHÌN THẤY chỉ 26px: phần đệm
 * trong suốt gánh phần còn lại. Làm cả viên thuốc cao 44px thì mỗi tin có cảm xúc dày thêm gần một
 * dòng tin, và số tin đọc được trong một màn tụt xuống — cùng phép đánh đổi mà `.chat-reply` đã cân
 * một lần ở #87, chỉ khác là ở đây giải pháp là đệm chứ không phải cho nút nổi lên. */
.chat-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 2px; }
.chat-chip {
  display: inline-flex; align-items: center; gap: 4px;
  min-height: 44px; min-width: 44px; padding: 9px 0; border: 0; background: none; cursor: pointer; font: inherit;
}
.chat-chip__emoji, .chat-chip__n {
  display: inline-flex; align-items: center; height: 26px;
  border: 1px solid var(--gemboss-divider); background: var(--gemboss-surface);
}
.chat-chip__emoji { padding: 0 4px 0 6px; border-right: 0; border-radius: 999px 0 0 999px; font-size: 14px; }
.chat-chip__n { padding: 0 8px 0 4px; border-left: 0; border-radius: 0 999px 999px 0;
  font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted); font-variant-numeric: tabular-nums; }
.chat-chip:hover .chat-chip__emoji, .chat-chip:hover .chat-chip__n { background: var(--gemboss-hover); }
/* Cảm xúc CỦA MÌNH phải đọc ra được mà không cần so sánh với cái bên cạnh — viền và nền, không chỉ
   màu chữ: một người mù màu nhìn hai chip chỉ khác nhau ở sắc độ thì thấy hai chip giống hệt nhau. */
.chat-chip.is-mine .chat-chip__emoji, .chat-chip.is-mine .chat-chip__n {
  border-color: var(--gemboss-accent); background: var(--gemboss-primary-soft); color: var(--gemboss-accent);
}
/* Thẻ tóm tắt của một link dán trong chat. Cả thẻ là một điều khiển (bấm vào là mở hội thoại/task),
   nên 44px theo §9.1 — và `flex-wrap` để ở 320px nó xuống dòng thay vì bóp chữ. */
.link-card {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  /* Có TRẦN chiều ngang: kéo hết cột thì nó đọc ra như một dải phân cách, không ra một tấm thẻ —
     và ở 1920 cái "dải" đó dài 1,5m chữ với khoảng trống ở giữa. */
  min-height: 44px; max-width: 560px; margin-top: 4px; padding: 4px 12px;
  border: 1px solid var(--gemboss-divider); border-radius: var(--gemboss-radius-md);
  background: var(--gemboss-surface); color: var(--gemboss-text); text-decoration: none;
  font-size: var(--gemboss-fs-caption);
}
.link-card:hover { background: var(--gemboss-hover); }
.link-card__kind { flex: none; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--gemboss-text-muted); }
/* Tiêu đề co được và cắt bằng ellipsis; `min-width: 0` vì một flex item mặc định không co dưới bề
   rộng nội dung, và không có nó thì một tiêu đề dài đẩy trạng thái ra khỏi thẻ. */
.link-card__title { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; color: var(--gemboss-text-strong); }
.link-card__sub { flex: none; color: var(--gemboss-text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 40%; }
.link-card__status { flex: none; }
.chat-msg__body[data-mid].is-flash { background: var(--gemboss-primary-soft); border-radius: var(--gemboss-radius-sm); }

.chat-replying {
  display: flex; align-items: center; gap: 8px; padding: 4px 8px;
  border-left: 2px solid var(--gemboss-accent); background: var(--gemboss-primary-soft);
  border-radius: var(--gemboss-radius-sm);
}
.chat-replying__text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted); }
.chat-replying button { flex: none; min-width: 44px; min-height: 44px; }
.chat-rail__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
/* Ngưỡng ĐIỀU KHIỂN §9.1: 44px cả hai chiều. Một nút "+" 24px là nút chỉ chuột mới bấm trúng. */
.chat-new { min-width: 44px; min-height: 44px; font-size: var(--gemboss-fs-h3); line-height: 1; }

.chat-dialog { border: 0; padding: 24px; width: min(420px, calc(100vw - 32px)); }
.chat-dialog::backdrop { background: rgb(0 0 0 / .32); }
.chat-dialog form { display: grid; gap: 12px; }
.chat-dialog h3 { margin: 0; font-size: var(--gemboss-fs-h2); }
.chat-dialog label { display: grid; gap: 6px; font-weight: 500; }
.chat-new__kind { display: flex; gap: 4px; flex-wrap: wrap; }
.chat-new__kind button { min-height: 44px; padding: 0 12px; border: 1px solid var(--gemboss-divider);
  border-radius: var(--gemboss-radius-md); background: transparent; cursor: pointer; font: inherit;
  color: var(--gemboss-text-muted); white-space: nowrap; }
.chat-new__kind button[aria-selected="true"] { background: var(--gemboss-hover); color: var(--gemboss-text-strong); font-weight: 600; }
/* Danh sách người phải CUỘN, không đẩy hai nút cuối hộp thoại ra khỏi màn: một đội 30 người thì
   nút Start rơi khỏi màn hình đầu, và đó là §9.1 đỏ (CTA rơi khỏi màn đầu). */
.chat-people { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; max-height: 40vh; overflow-y: auto; }
.chat-person { width: 100%; min-height: 44px; display: flex; align-items: center; justify-content: space-between;
  gap: 8px; padding: 0 12px; border: 0; border-radius: var(--gemboss-radius-md); background: none; cursor: pointer;
  font: inherit; color: var(--gemboss-text); text-align: left; }
.chat-person:hover { background: var(--gemboss-hover); }
.chat-person[aria-pressed="true"] { background: var(--gemboss-primary-soft); font-weight: 600; }
.chat-person__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-person__tick { flex: none; color: var(--gemboss-accent); font-weight: 700; }
.chat-dialog__foot { display: flex; justify-content: flex-end; gap: 8px; }
.chat-main { display: grid; grid-template-rows: auto minmax(0, 1fr) auto auto; min-height: 0; }
.chat-head { padding: 16px; border-bottom: 1px solid var(--gemboss-divider); }
.chat-head h3 { margin: 0; }
.chat-head p { margin: 4px 0 0; }
.chat-messages { list-style: none; margin: 0; padding: 16px; overflow-y: auto; display: flex; flex-direction: column; gap: 12px; }
/* Vạch "New messages": cùng màu nhấn với số chưa đọc ở rail (cùng một ý nghĩa), chữ thật ở giữa. */
/* KHÔNG đặt tên `.chat-new`: đó là nút "+" tạo kênh — dùng chung tên là nút ấy mang vạch và màu này (§9.3). */
.chat-unread-line { display: flex; align-items: center; gap: 8px; color: var(--gemboss-accent); font-size: 12px; font-weight: 600; }
.chat-unread-line::before, .chat-unread-line::after { content: ""; flex: 1; height: 1px; background: currentColor; }
.chat-msg__who { font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted); }
.chat-msg__body { white-space: pre-wrap; overflow-wrap: anywhere; }
.chat-msg__edited { font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted); }
/* Tin đang sửa: khối nội dung trải hết hàng. Bình thường nó `fit-content` (để thanh nút đứng sát tin), nên ô sửa của
   một câu ngắn chỉ rộng bằng câu ấy — đo được 188px ở khổ 1280. Lưới: `width: auto` với `justify-self: start` vẫn co
   theo nội dung, nên phải `stretch`. */
.chat-msg--editing > .chat-msg__main { justify-self: stretch; width: auto; max-width: none; }
.chat-edit { display: flex; flex-direction: column; gap: 6px; }
.chat-edit__row { display: flex; gap: 8px; justify-content: flex-end; }
.chat-confirm { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 4px; font-size: var(--gemboss-fs-caption); }
/* Nút xác nhận gỡ: bộ @gemboss/ui chưa có biến thể "nguy hiểm", nên lớp RIÊNG của gemcare tô chữ bằng token có sẵn —
   không vẽ lại .ui-btn (test/gemboss-ui.test.ts). */
.chat-confirm__yes { color: var(--gemboss-danger); }
/* Lưới HAI HÀNG, không phải một hàng flex.
 *
 * `.chat-replying` là con thứ ba của form. Để form là `display: flex` thì nó thành một cột thứ ba
 * NẰM CẠNH ô nhập và nút Send — ở 390px, ô nhập bị bóp còn ~20px, chữ "Message" xuống dòng từng chữ
 * cái, và danh sách tin trượt ngang. Ảnh chụp 390 lộ ra; phép đo tràn-ngang của TÀI LIỆU vẫn 0 vì
 * cái trượt nằm trong một khối có `overflow` riêng.
 *
 * Dòng "đang trả lời" trải hết hàng trên; ô nhập và Send ở hàng dưới. */
.chat-composer {
  /* Ba cột: 📎 · ô nhập · Send. Khay ảnh, dòng "đang trả lời" và nhãn ẩn trải hết cả ba (1 / -1). */
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 8px; align-items: end;
  padding: 16px; border-top: 1px solid var(--gemboss-divider);
}
.chat-files { grid-column: 1 / -1; margin: 0; }
.chat-attach { min-width: 44px; padding: 0; font-size: 18px; }
/* Khổ HẸP: ô nhập một hàng riêng, 📎 và Send xuống hàng dưới.
 *
 * Ba cột cạnh nhau ở 320px cho ô nhập ~105px — đo trên ảnh: chữ gợi ý gãy thành bốn dòng, và gõ một
 * câu trong một ô hẹp chừng ấy là đọc lại chính chữ mình qua một khe. Không check nào của cổng thị
 * giác đỏ cho nó (không tràn, không cắt — chỉ là chật), nên ngưỡng ở đây đặt theo ảnh chụp, không
 * theo một phép đo. Hai nút vẫn 44px và cách nhau hơn 8px. */
@media (max-width: 480px) {
  /* `order`, không `grid-template-areas`: vùng đặt tên thì hàng của khay ảnh và dòng "đang trả lời"
     VẪN tồn tại khi chúng ẩn, và mỗi hàng rỗng vẫn ăn một `gap` 8px — ô soạn phình ra 16px khoảng
     trắng ở trên mà không có gì trong đó. `order` chỉ đổi thứ tự tự xếp, không đẻ hàng. */
  .chat-composer .ui-textarea { grid-column: 1 / -1; order: 1; }
  .chat-attach { grid-column: 1; order: 2; }
  #chat-send { grid-column: 3; order: 2; }
}
/* Đang kéo ảnh vào khung tin: nói ra là thả được ở đây. Viền NÉT ĐỨT chứ không chỉ đổi màu nền —
   một nền nhạt hơn một chút là thứ mắt không bắt được giữa lúc tay đang kéo. */
.chat-main[data-dropping] { outline: 2px dashed var(--gemboss-accent); outline-offset: -8px; }
/* Ảnh trong hàng tin: dùng lại `.msg-files`/`.file-thumb` của tin khách (xem `attachmentNodes`). Tin
   chat luôn căn TRÁI — không có "tin của mình nằm bên phải" như khung khách — nên ghi đè đúng một
   thuộc tính, không định nghĩa lại khối. */
.chat-msg .msg-files { justify-content: flex-start; margin-top: 4px; }
.chat-composer .ui-textarea { min-width: 0; }
.chat-replying { grid-column: 1 / -1; }
.chat-composer .ui-btn { min-height: 44px; }
.chat-composer .ui-sr-only { grid-column: 1 / -1; }
#chat-error { margin: 0 16px 12px; }

@media (max-width: 900px) {
  /* Cột kênh thành một hàng cuộn ngang — ẩn hẳn nó là cách một màn hình mất lối vào ở điện thoại. */
  .chat-screen { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); }
  .chat-rail { border-right: 0; border-bottom: 1px solid var(--gemboss-divider); padding: 8px 16px; }
  .chat-rail ul { flex-direction: row; overflow-x: auto; gap: 8px; }
  .chat-channel { width: auto; white-space: nowrap; padding: 0 16px; }

  /* Thanh nút KHÔNG còn thôi nổi theo BỀ RỘNG ở đây (28/09/2026). Hai lý do cũ đã có chỗ đúng hơn: khung hẹp thì
   container query đặt thanh ở góc trên phải của HÀNG (không đè chữ); màn cảm ứng thì `@media (hover: none)`. Luật
   cũ ở đây ép thanh 5 nút thành một hàng tĩnh 242px dưới mỗi tin, và ở 320px nó tràn khỏi cột tin (cuộn ngang). */
  .chat-msg__who { flex-wrap: wrap; }
}

/* Danh sách việc: cả hàng bấm được, nên nó phải TRÔNG bấm được — con trỏ và nền khi rê chuột.
   Nút sắp xếp ở tiêu đề cao 44px theo cùng ngưỡng điều khiển của §9.1. */
/* Cả hàng là một điều khiển (`role="button"`), nên nó theo ngưỡng 44px của §9.1 chứ không phải ngưỡng
   24px của "link trong danh sách". Đo trước khi sửa: 37px ở ≥768. */
#board-list .ui-table tr[role="button"] { cursor: pointer; }
#board-list .ui-table tbody td { padding-block: 12px; }
#board-list .ui-table tbody tr:hover td,
#board-list .ui-table tbody tr:focus-visible td { background: var(--gemboss-hover); }
/* Nút sắp xếp của BẤT KỲ bảng nào, không chỉ `#board-list`. Bảng theo người ở màn Reports cũng
   dùng `.th-sort`, và khi luật còn scope theo id thì nó mất cả kiểu chữ lẫn SÀN 44px — mã có, đúng,
   và không áp vào đâu cả. */
.ui-table .th-sort { min-height: 44px; min-width: 44px; padding: 0 8px; font: inherit; font-weight: 600; }
/* Việc nội bộ đọc khác việc của khách ngay ở cột Kind — chữ, không chỉ màu. */
/* Chọn theo TÊN CỘT, không theo `nth-child`: tôi vừa đếm sai một lần khi chèn hai cột vào giữa, và
   một chỉ số cứng sẽ hỏng lặng lẽ mỗi lần ai đó đổi thứ tự cột. */
#board-list .ui-table tr[data-kind="internal"] td.col-kind { color: var(--gemboss-text-muted); }
/* Hàng trễ hạn: chỉ tô Ô HẠN, không tô cả hàng — tô cả hàng làm mọi cột trông như đang báo động, và
   người ta học cách bỏ qua màu đỏ. Chữ "· 3h late" trong ô mới là thứ nói ra sự thật. */
#board-list .ui-table tr[data-late="true"] td.col-due { color: var(--gemboss-danger); font-weight: 600; }

/* Ưu tiên và trễ hạn trên thẻ kanban. Chữ trước, màu sau: `Urgent` và `Overdue` đọc được kể cả khi
   không phân biệt được màu — màu chỉ để mắt bắt nhanh hơn. */
.rq-prio { font-weight: 600; }
.rq-prio[data-p="urgent"] { color: var(--gemboss-danger); }
.rq-prio[data-p="high"] { color: var(--gemboss-text); }
.rq-prio[data-p="low"] { color: var(--gemboss-text-muted); font-weight: 400; }
.rq-late { color: var(--gemboss-danger); font-weight: 600; }
#rq-due-note[data-late="true"] { color: var(--gemboss-danger); }

/* "Right now" trên màn Shifts: mỗi hàng là một người ĐÁNG LẼ đang làm, và cái nhãn nói họ đã vào chưa.
   Màu chỉ là lớp thứ hai — chữ "not checked in" phải đọc được kể cả khi không phân biệt được màu. */
.shift-now { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.shift-now li { display: flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 12px;
  border: 1px solid var(--gemboss-divider); border-radius: var(--gemboss-radius-md); }
.shift-now li[data-in="false"] { border-color: var(--gemboss-danger); }
.shift-flag { font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted); }
.shift-now li[data-in="false"] .shift-flag { color: var(--gemboss-danger); }

/* Sổ sự kiện trong ngăn chi tiết: mốc thời gian cùng cỡ nhưng nhạt hơn câu chữ, để mắt đi theo VIỆC
   trước rồi mới tới GIỜ — người trực cần biết chuyện gì xảy ra, giờ chỉ là để đối chiếu. */
.events { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.events li { display: flex; flex-direction: column; gap: 2px; font-size: var(--gemboss-fs-caption); }
.events-when { color: var(--gemboss-text-muted); font-variant-numeric: tabular-nums; }

/* Công tắc chính sách, dưới cùng thẻ giờ làm việc: một đường kẻ tách nó khỏi form giờ vì nó lưu NGAY
   khi bấm, không chờ nút Save ở trên. Hàng ≥44px theo cùng ngưỡng với .hours-days label. */
.policy { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--gemboss-divider); }
.policy-row { display: flex; align-items: flex-start; gap: 12px; min-height: 44px; cursor: pointer; }
.policy-row input { width: 20px; height: 20px; margin: 12px 0 0; flex: none; cursor: pointer; }
.policy-row > span { display: flex; flex-direction: column; gap: 2px; padding: 12px 0; }
.policy-help { display: block; max-width: 62ch; }
#duty-policy-state { margin: 8px 0 0; }


/* ── Panel "You" ────────────────────────────────────────────────────────────────────────────── */
.me-card { display: flex; align-items: center; gap: 12px; margin: 12px 0; }
.me-card__who { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.me-card__who strong { color: var(--gemboss-text-strong); }
.me-card__who span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.me-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
/* Đổi mật khẩu: một cột, mỗi ô chiếm trọn chiều ngang panel — panel hẹp (drawer small), không chia hàng. */
.me-alerts { display: flex; flex-direction: column; gap: 8px; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--gemboss-divider); }
.me-alerts .me-actions { margin-top: 0; }
#me-notify[aria-pressed="true"], #me-sound[aria-pressed="true"] { border-color: var(--gemboss-accent); color: var(--gemboss-accent); }
.me-password { display: flex; flex-direction: column; gap: 10px; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--gemboss-divider); }
.me-password__title { margin: 0; font-size: 14px; color: var(--gemboss-text-strong); }
.me-password__hint { margin: 0; }
.me-password .closure-field { width: 100%; }
.me-password .ui-btn { align-self: flex-start; min-height: 44px; }
.staff-actions { display: flex; flex-wrap: wrap; gap: 8px; }
/* `<label for>` làm nút, vì `<input type=file>` trần không đạt 44px và không nhận được kiểu của hệ
   thiết kế. Nó phải TRÔNG và CHẠM như một nút: con trỏ, chiều cao, và một vòng focus thật — vòng
   focus đi theo cái input đang ẩn, nên phải vẽ lại ở đây hoặc người dùng bàn phím không thấy mình
   đang đứng ở đâu. */
.me-upload { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; cursor: pointer; }
#me-avatar-file:focus-visible + .me-upload,
.me-upload:has(+ #me-avatar-file:focus-visible) { outline: 2px solid var(--gemboss-accent); outline-offset: 2px; }

/* ── Dock chat đội (cửa sổ nổi kiểu LinkedIn) ─────────────────────────────────────────────────
   Neo ở góc dưới phải, `fixed` nên ra khỏi lưới của #app. Thùng chứa `pointer-events: none` để
   khoảng trống GIỮA các cửa sổ không nuốt cú bấm vào hộp thư phía sau — chỉ bản thân thanh và cửa
   sổ nhận bấm. Không có dòng đó thì một dải 8px vô hình phía trên hộp thư sẽ chặn mọi cú bấm. */
.dock {
  position: fixed; right: 16px; bottom: 0; z-index: 40;
  display: flex; align-items: flex-end; gap: 8px; pointer-events: none;
}
.dock[hidden] { display: none; }
.dock-windows { display: flex; align-items: flex-end; gap: 8px; }
.dock-panel, .dock-win {
  pointer-events: auto; background: var(--gemboss-surface);
  border: 1px solid var(--gemboss-divider); border-bottom: 0;
  border-radius: var(--gemboss-radius-md) var(--gemboss-radius-md) 0 0;
  box-shadow: 0 -2px 16px rgba(0, 0, 0, .08);
}
.dock-panel { width: 280px; }
.dock-bar {
  width: 100%; min-height: 44px; display: flex; align-items: center; gap: 8px; padding: 0 12px;
  border: 0; background: none; cursor: pointer; font: inherit; font-weight: 600; color: var(--gemboss-text-strong);
}
.dock-bar__title { flex: 1; text-align: left; }
.dock-bar__chev { color: var(--gemboss-text-muted); transition: transform .14s ease; }
.dock[data-open="1"] .dock-bar__chev { transform: rotate(180deg); }
.dock-list { list-style: none; margin: 0; padding: 4px 0; max-height: 360px; overflow-y: auto; border-top: 1px solid var(--gemboss-divider); }
.dock-channel {
  width: 100%; min-height: 44px; display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 0 12px; border: 0; background: none; cursor: pointer; font: inherit; color: var(--gemboss-text); text-align: left;
}
.dock-channel:hover { background: var(--gemboss-hover); }
.dock-channel[aria-current="true"] { font-weight: 600; }
.dock-channel__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dock-empty { padding: 12px; }

/* Một cửa sổ: đầu · khung tin · lỗi · ô soạn. `minmax(0, 1fr)` cho khung tin — không có nó thì một
   kênh dài đẩy ô soạn ra khỏi đáy màn hình thay vì cuộn bên trong. */
.dock-win {
  width: 340px; height: min(460px, calc(100vh - 96px));
  display: grid; grid-template-rows: auto minmax(0, 1fr) auto auto;
}
.dock-win[data-minimized="1"] { height: auto; }
.dock-win[data-minimized="1"] > :not(.dock-win__head) { display: none; }
.dock-win__head { display: flex; align-items: center; border-bottom: 1px solid var(--gemboss-divider); }
.dock-win[data-minimized="1"] .dock-win__head { border-bottom: 0; }
.dock-win__toggle {
  flex: 1; min-width: 0; min-height: 44px; display: flex; align-items: center; gap: 8px; padding: 0 4px 0 12px;
  border: 0; background: none; cursor: pointer; font: inherit; font-weight: 600; color: var(--gemboss-text-strong); text-align: left;
}
.dock-win__title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dock-win__btn {
  flex: none; min-width: 44px; min-height: 44px; border: 0; background: none; cursor: pointer;
  font: inherit; color: var(--gemboss-text-muted); border-radius: var(--gemboss-radius-sm);
}
.dock-win__btn:hover, .dock-win__toggle:hover { background: var(--gemboss-hover); }
/* Khung tin DÙNG LẠI `.chat-messages` (cùng hàm vẽ, cùng hàng tin) — chỉ đệm nhỏ lại cho ô 340px. */
.dock-win__msgs { padding: 12px; }
.dock-win__error { margin: 0 12px 8px; }
.dock-win__composer {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: end;
  padding: 8px; border-top: 1px solid var(--gemboss-divider);
}
.dock-win__input { min-height: 44px; max-height: 120px; resize: none; }
.dock-win__send { min-height: 44px; }

/* Chế độ CỘT: đang mở một hội thoại khách ở màn ≥ 1100 — dock nằm GỌN trong cột phải (Customer / AI),
   rộng đúng bằng cột, xếp dọc. Nó che thông tin khách tạm thời, và KHÔNG BAO GIỜ che ô trả lời hay
   tin mới nhất của khách ở cột giữa. Xem `dockMode()` cho phép đo đã dẫn tới luật này.
   `right: 0` và bề rộng lấy từ CHÍNH biến của cột (`--details-w`), không gõ lại 320: cột đổi rộng
   thì dock đổi theo, không có con số thứ hai để trôi. */
.dock[data-mode="column"] {
  right: 0; width: var(--details-w, 320px);
  flex-direction: column; align-items: stretch; gap: 0;
  max-height: 100vh;
}
.dock[data-mode="column"] .dock-windows { flex-direction: column; align-items: stretch; gap: 0; }
.dock[data-mode="column"] .dock-win,
.dock[data-mode="column"] .dock-panel {
  width: 100%; border-radius: 0; border-left: 0; border-right: 0; box-shadow: none;
}
/* Một cửa sổ mở rộng mỗi lúc (xem `collapseOthers`), nên nó được chiều cao cố định: cao tối đa mà
   vẫn chừa ~40% cột cho thông tin khách phía trên — thông tin ấy bị che chứ không bị mất. */
.dock[data-mode="column"] .dock-win:not([data-minimized="1"]) { height: min(440px, 58vh); }
.dock[data-mode="column"] .dock-list { max-height: 30vh; }

/* Cột phải CHỪA đúng chiều cao dock ở đáy khi dock nằm trong nó (chế độ cột), để phần tử cuối — ô
   "Add a tag…" — cuộn lên được TRÊN dock thay vì nằm vĩnh viễn dưới nó. `--dock-h` là chiều cao ĐO
   bằng ResizeObserver (xem inbox.js), không phải một hằng số: dock cao 44px khi thu gọn, ~500px khi
   mở một cửa sổ. */
.app[data-dock="column"] .details {
  /* THU NGẮN khung cuộn, không ĐỆM nó.
   *
   * Bản trước đệm `padding-bottom` + `scroll-padding-bottom` bằng chiều cao dock. Nó làm phần cuối
   * cuộn TỚI ĐƯỢC — nhưng ở vị trí cuộn 0, thứ nằm dưới thanh dock vẫn bị đè: cổng thị giác báo
   * `content_covered` ở 1366 (một chip tag) và 1920 (một tiêu đề). Khung cuộn kết thúc NGAY TRÊN dock
   * thì dock không đè lên thứ gì bên trong nó, ở bất kỳ vị trí cuộn nào — và Tab tự cuộn đúng mà
   * không cần `scroll-padding`. */
  align-self: start;
  height: calc(100% - var(--dock-h, 0px));
})` vẫn trả về "BỊ CHE". `scroll-padding`
     là thứ trình duyệt đọc khi tự cuộn một phần tử được focus vào tầm. */
  scroll-padding-bottom: calc(var(--dock-h, 0px) + 8px);
}
