/* 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, .gnav-item) 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; }
/* Khoá đầu trang của màn đăng nhập: gem trong ô, tên bên dưới, căn giữa — cùng hình với gemboss và
   gemcommunity. `gap` nhỏ vì hai thứ này là MỘT dấu hiệu, không phải hai mục trong danh sách.
   PHẢI có `.brand-lockup` đứng trước: cùng một class `.brand-mark` còn dùng ở thanh rail với kích
   thước 28px, và một khai báo trần cùng độ đặc hiệu đứng SAU sẽ đè nó — biến icon 28px trong cột
   rail rộng 64px thành 56px và đẩy cả hàng nav xuống. Ảnh sáu khổ của PR này chỉ chụp màn ĐĂNG
   NHẬP, nên nó đọc "56×56 ở mọi khổ" là đạt trong khi màn đã đăng nhập đang hỏng. */
.brand-lockup { display: grid; justify-items: center; gap: 8px; margin-bottom: 4px; }
.brand-lockup .brand-mark { width: 56px; height: 56px; }
.brand-name { font-size: var(--gemboss-fs-h2); font-weight: 700; color: var(--gemboss-text-strong); letter-spacing: -.01em; }

/* ── Login ── */
/* ONE COLUMN BY DEFAULT, two where there is room. `container` makes the shell the thing the panel
   query measures, so the rule reads the SIGN-IN AREA and not the window — the same contract the
   gemboss and gemcommunity sign-ins use, and the reason all three flip at the same size. */
.login { height: 100%; container: signin / size; background: var(--gemboss-primary-soft); }
/* `height`, NOT `min-height`. A size container is queried on its OWN computed size, and
   `min-height: 100%` leaves that size `auto` — so the height half of every query below silently
   never matched. Measured: on a container whose box was 1440x900, `(min-width: 900px)` matched and
   `(min-height: 100px)` did not. The width half worked the whole time, which is exactly what makes
   this one hard to see: the query looks half-alive.
   The GRID moved off `.login` for a second reason: a container query cannot style its own container.
   `@container { .login { grid-template-columns: ... } }` parsed fine and applied to nothing — the
   columns stayed `1fr` at every width. So the columns live on a child, `.login-split`, which is how
   the gemboss and gemcommunity sign-ins are built too. */
.login-split { display: grid; grid-template-columns: 1fr; min-height: 100%; }
.login-stage { display: grid; place-items: center; padding: 24px 16px; }
.login-card { width: min(380px, 100%); padding: 24px; display: grid; gap: 16px; box-shadow: var(--gemboss-shadow-raised); }

/* Off until earned — hidden first, then shown. A panel that paints and then disappears at a
   breakpoint is a panel that flashes on every phone. */
.login-panel { display: none; }

/* Wide AND tall. Width alone is not enough: a 1440x420 window is a letterbox, and a quote plus a
   button in 420px of height is a crush. 900/620 are the same two numbers the other two sign-ins use. */
@container signin (min-width: 900px) and (min-height: 620px) {
  .login-split { grid-template-columns: minmax(420px, 1fr) minmax(0, 1.05fr); }
  /* The column is the structure now, so the card stops drawing a second one. Scoped under .login
     (0,2,0) rather than relying on coming later in the sheet — a container query carries only the
     specificity of the selectors inside it, and ties go to whichever was written first. */
  .login-split .login-stage { background: var(--gemboss-surface); }
  .login-split .login-card { box-shadow: none; border: none; }
  .login-panel {
    display: flex; align-items: center; position: relative; overflow: hidden;
    /* GREEN, and from this design system rather than invented: `--gemboss-success` #007f5f is the
       colour it already uses for resolved/healthy. Deepened along its own hue for the two darker
       stops so white body text clears AA on every part of the field. */
    background:
      radial-gradient(880px 620px at 22% 8%, rgba(255, 255, 255, .16), transparent 62%),
      linear-gradient(152deg, #00795a 0%, #045c46 52%, #063f32 100%);
    color: #ffffff;
  }
}
.login-panel-in { position: relative; box-sizing: border-box; width: 100%; max-width: 520px;
  padding: 48px clamp(32px, 5vw, 64px); }
.login-eyebrow { margin: 0 0 24px; font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
  font-size: var(--gemboss-fs-caption); font-weight: 500; letter-spacing: .12em; text-transform: uppercase;
  color: rgba(255, 255, 255, .8); }
/* The quote is gemcare's own mark on this screen — the other two sign-ins put a product promise
   here. Quotation glyph drawn as a background flourish, not as text, so a screen reader is not
   made to announce punctuation. */
.login-quote { position: relative; margin: 0 0 16px; font-size: clamp(26px, 3vw, 36px);
  line-height: 1.25; letter-spacing: -.02em; font-weight: 600; color: #ffffff; }
.login-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; }
.login-quote-by { margin: 0 0 32px; font-size: var(--gemboss-fs-body); color: rgba(255, 255, 255, .78); }
.login-panel-cta { display: inline-flex; align-items: center; gap: 8px; min-height: 44px;
  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; }
.login-panel-cta:hover { transform: translateY(-1px); }
.login-panel-cta:focus-visible { outline: 2px solid #ffffff; outline-offset: 3px; }
.login-card h1 { font-size: var(--gemboss-fs-h2); margin: 4px 0; text-align: center; }
.login-card label { display: grid; gap: 6px; font-weight: 500; }
.login-card .field { width: 100%; }

/* ── 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-foot { margin-top: auto; display: flex; flex-direction: column; align-items: center; gap: 8px; }
.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; }
.views { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.views .gnav-item { min-height: 44px; justify-content: space-between; }
/* The rail's selected state is aria-current (what a screen reader hears); painted as the kit's .gnav-item.active. */
.views .gnav-item[aria-current="true"] { background: var(--gemboss-active-bg); color: var(--gemboss-active-fg); font-weight: 600; }
.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; }
.rail-foot { margin-top: auto; display: grid; gap: 12px; font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted); }
.team { display: flex; flex-wrap: wrap; gap: 6px; }
.team span {
  display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px;
  border-radius: var(--gemboss-radius-full); border: 1px solid var(--gemboss-divider); color: var(--gemboss-text);
}
.team span::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--gemboss-divider); }
.team span[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); }
.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; }
/* 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-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; }
.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); }
.composer-foot { display: flex; align-items: center; gap: 12px; }
.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; }

.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; }
.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 nút dưới ô soạn phải xuống dòng ở 264px — bốn nút cạnh nhau không vừa. */
  .composer-foot, .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; }
.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 thì nút mang chữ "Snoozed until …" — dài hơn hẳn. Cho nó xuống dòng thay vì tràn. */
.snooze > summary.is-snoozed { white-space: normal; text-align: left; }
.snooze-menu {
  position: absolute; z-index: 20; top: calc(100% + 4px); left: 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;
  }
}

.chat-msg { position: relative; }
.chat-msg__who { display: flex; align-items: center; gap: 8px; }
/* Nút Reply là một THANH NỔI ở góc phải trên, kiểu Slack — `position: absolute` nên nó đạt ngưỡng
   ĐIỀU KHIỂN 44px của §9.1 mà không cộng 28px vào chiều cao của MỌI hàng tin. Một hàng tin cao thêm
   28px chỉ để chứa một nút hiện lúc rê chuột là cái giá trả bằng số tin đọc được trong một màn.
   Có nền và viền để nó đọc ra là một điều khiển nổi, không phải chữ lạc vào giữa tin.
   Hiện khi rê chuột HOẶC khi focus bàn phím: chỉ `:hover` thì đi bằng Tab không bao giờ thấy nó. */
.chat-reply {
  position: absolute; top: 0; right: 0; opacity: 0; pointer-events: none;
  min-width: 44px; min-height: 44px; padding: 0 12px;
  border: 1px solid var(--gemboss-divider); border-radius: var(--gemboss-radius-md);
  background: var(--gemboss-surface); cursor: pointer; font: inherit;
  font-size: var(--gemboss-fs-caption); color: var(--gemboss-accent); box-shadow: var(--gemboss-shadow-resting);
}
/* `pointer-events: none` lúc ẩn: một nút trong suốt vẫn nhận cú bấm là một nút bẫy người dùng —
   cùng họ với cái bẫy `.rail-tip` (opacity 0 vẫn chiếm chỗ) đã ghi ở #82. */
.chat-msg:hover .chat-reply, .chat-reply:focus-visible { opacity: 1; pointer-events: auto; }
/* 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; }
.chat-msg__who { font-size: var(--gemboss-fs-caption); color: var(--gemboss-text-muted); }
.chat-msg__body { white-space: pre-wrap; overflow-wrap: anywhere; }
/* 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 {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: end;
  padding: 16px; border-top: 1px solid var(--gemboss-divider);
}
.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; }

  /* Nút Reply THÔI NỔI ở khổ hẹp, và luôn hiện.
   *
   * Hai lý do, lý do thứ hai nặng hơn:
   * (a) cột tin hẹp lại nên một nút nổi ở mép phải ĐÈ LÊN CHỮ của chính tin đó — đo được ở 320/390;
   * (b) màn cảm ứng KHÔNG CÓ `hover`. Một nút chỉ hiện khi rê chuột là một nút KHÔNG TỒN TẠI trên
   *     điện thoại — §0 gọi đó là UNREACHABLE, không phải "hơi khó bấm".
   * Hàng cao thêm ~28px, và đó là cái giá đúng để trả.
   *
   * Và nó phải nằm ở media query NÀY, không phải block 900px ở trên: `.chat-reply` base khai sau
   * block đó, cùng độ đặc hiệu (0,1,0) — media query KHÔNG cộng độ đặc hiệu, nên luật đến SAU thắng.
   * Bản đầu tôi để ở block trên và phép đo vẫn đỏ y nguyên. */
  .chat-reply { position: static; opacity: 1; pointer-events: auto; align-self: center; }
  .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; }
