/* ⛔⛔ ĐỔI TÊN LỚP 03/09/2026 — TÔI ĐẶT TRÙNG TÊN VỚI chat.css.
   `.tk-o` bên chat.css là **Ô TÌM KIẾM** (`position: sticky; display: flex`),
   `.tk-hang` cũng đã có ở đó. Cả hai tệp CÙNG được nạp trên một trang ⇒ lớp
   của họ ăn vào biểu mẫu của tôi: nhãn nằm NGANG HÀNG với ô nhập thay vì ở
   trên, và ô bị dính `sticky`.
   ⛔ Tôi đã quét trùng NHƯNG CHỈ QUÉT hop.css — đúng lớp thước hỏng 'khoanh
     sai phạm vi'. Phải quét CẢ HAI tệp vì trang nạp cả hai.
   ⇒ Lớp của tôi đổi thành `.dk-*` và `.tk-dong`. ⛔ ĐỪNG đặt lớp mới bắt đầu
     bằng `.tk-` nữa nếu chưa quét chat.css. */
/* ═══════════════════════════════════════════════════════════════════════════
   STARVIEW · TRANG HỌP — BẢNG MÀU & BỐ CỤC
   ───────────────────────────────────────────────────────────────────────────
   Token màu chép ĐÚNG từ nguồn chân lý của app StarMeet
   (lib/design/themes.dart — 6 chủ đề: office · game · relax · holiday · kitty ·
   highContrast). §2.1 quy ước icon: màu LẤY TỪ TOKEN, cấm chép hex vào chỗ
   dùng. Đổi chủ đề = đổi lớp trên <html>, không sửa tay chỗ nào.

   §4.2 handoff — điều bắt buộc, không thương lượng:
     · chế độ tối là MẶC ĐỊNH (họp thường trong phòng thiếu sáng)
     · KHÔNG trượt ngang ở 360 · 768 · 1280 · 1920 px
     · tương phản đạt WCAG AA
     · chạy được Safari/iPhone  ⇒ dùng 100dvh + safe-area-inset
   ═══════════════════════════════════════════════════════════════════════════ */

/* ───────────────────────── chủ đề (mặc định: office) ────────────────────── */
:root,
:root.sv-office {
  --navy: #1F3050;         --navy-dark: #16233B;   --navy-deep: #0E1728;
  --gold: #C9A227;         --gold-soft: #E3C766;
  --surface: #1F3050;      --surface-raised: #29405F;
  /* ⭐⭐ 16/09 anh Triệu: *"bất cứ nút nào đang màu hồng nhạt thì phải đổi hết
     sang màu vàng, thống nhất cho cả nền tảng"*.
     ⭐ ĐO TRƯỚC KHI ĐỔI (đúng cảnh báo phiên Mobile). Kết quả trên WEB:
       chỉ 4 nút mang màu ấy — `cMic`/`cCam` khi TẮT · `cLeave2` (rời phòng) ·
       `hGiaiTan` (giải tán phòng). CẢ BỐN đều là *thiết bị đang tắt* hoặc
       *việc ⛔ không hồi lại được*. ⇒ Web ⛔ KHÔNG có nút chỉ báo nào hồng như
       nhánh Mobile, nên ⛔ KHÔNG có nút nào để tô vàng.
     ⛔⛔ VÀ TÔ VÀNG BỐN NÚT ẤY LÀ SAI: vàng là màu nút THƯỜNG của hệ thống;
       tô vàng nút Rời phòng / Giải tán phòng là **xoá mất dấu hiệu cảnh báo
       duy nhất** trước một thao tác ⛔ không rút lại được.
     ⭐ Bệnh THẬT là màu ấy **quá ngả hồng** — và đo ra nó còn **RỚT TƯƠNG PHẢN**:
       chữ trắng trên `#E05A4E` chỉ đạt **3,66** (chuẩn đòi 4,5).
     ⇒ Đổi sang ĐỎ GẠCH `#C62828`: sắc trung tính (lục = lam, hết ngả hồng) và
       tương phản chữ trắng lên **5,62**.
     ⛔⛔ PHẢI TÁCH HAI VAI, nếu không là hỏng: `--danger` còn được dùng làm
       **MÀU CHỮ** trên nền tối (hộp lỗi, chip, biểu tượng). Nền đậm hơn thì
       chữ càng tối ⇒ càng khó đọc (đo: `#E05A4E` trên `--surface-raised` chỉ
       **2,88**). ⇒ `--danger-chu` riêng cho vai CHỮ: `#FF9A8F`, thấp nhất
       **5,15** trên cả bốn nền tối.
     ⛔ Các chủ đề khác ⛔ KHÔNG khai `--danger-chu` ⇒ tự rơi về `--danger`,
       tức GIỮ NGUYÊN hành vi cũ. ⛔ Đừng "dọn cho gọn" bằng cách bỏ bản lùi ấy. */
  --success: #3DBE7B;      --warning: #E0A93E;     --danger: #C62828;  --info: #4A86E8;
  --danger-chu: #FF9A8F;
  --ink: #F2F4F8;          --ink-2: rgba(242,244,248,.72);  --ink-3: rgba(242,244,248,.62);
  --line: rgba(242,244,248,.12);
  --on-accent: #101724;

  /* ═══ THANG CHỮ — 02/09/2026 ═══
     Anh Triệu chê "font ⛔ không đều". Đo được: web rải **19 cỡ rời rạc** trong
     khi app có **7 kiểu token hoá một chỗ** (`SVType`). ⛔ Không có thang thì
     mỗi lần thêm giao diện là thêm một cỡ mới, và ⛔ không ai biết cỡ nào là
     "đúng" — đó là gốc của cảm giác lộn xộn, ⛔ không phải do một chỗ nào sai.
     ⇒ Khai thang theo ĐÚNG app. Đêm nay mới bỏ được hai cỡ nhỏ nhất và các nấc
       nửa pixel; phần còn lại phải đổi dần và NHÌN từng màn, ⛔ không snap hàng
       loạt lúc ⛔ không ai xem — snap 160 chỗ trong đêm là cách làm vỡ bố cục.
     ⛔ MÃ MỚI DÙNG TOKEN, ⛔ đừng viết số. */
  --fs-caption: 12px;   /* app SVType.caption */
  --fs-body:    15px;   /* app SVType.body    */
  --fs-button:  16px;   /* app SVType.button  */
  --fs-h2:      18px;   /* app SVType.h2      */
  --fs-h1:      20px;   /* app SVType.h1      */
  --fs-brand:   28px;   /* app SVType.brand   */
  --fs-display: 34px;   /* app SVType.display */
}
/* ═══════════════ CHỦ ĐỀ "SÁNG" — 02/09/2026 ═══════════════
   App mobile ĐÃ có chủ đề sáng; web thì ⛔ chưa. Nhưng ⛔ KHÔNG chép nguyên
   bảng màu của app sang: đo lại bản app thì **icon vàng thương hiệu trên nền
   sáng chỉ được 2,17:1** — dưới xa ngưỡng 3:1 của WCAG cho đồ hoạ, tức là
   trên máy để ngoài sáng gần như ⛔ KHÔNG nhìn thấy.
   Đo lại chính con vàng ấy (#C9A227) trên nền sáng của web: **2,28:1** — cùng
   một cái bẫy, và nó bẫy được vì màu thương hiệu vốn được chọn cho nền TỐI.
   ⇒ Ở chủ đề sáng, sắc vàng phải ĐẬM lại. Mọi con số dưới đây đã đo bằng công
     thức WCAG 2.1 trên nền KHẮC NGHIỆT NHẤT (nền trang #E9EDF3):
       --ink 13,36:1 · --gold 5,04:1 · --gold-soft 5,11:1 · --success 4,54:1
       --warning 5,00:1 · --danger 5,59:1 · --info 5,20:1 · chữ trên vàng 5,92:1
     ⇒ mọi cặp đều ĐẠT 4,5:1 (ngưỡng CHỮ), ⛔ không chỉ 3:1 (ngưỡng icon).
   ⚠ BẪY NGƯỢC ĐỜI ĐÃ TRÁNH: trên nền tối, "soft" nghĩa là SÁNG HƠN. Bê nguyên
     nghĩa đó sang nền sáng thì "soft" thành NHẠT HƠN ⇒ tụt thẳng xuống dưới
     ngưỡng. Nên ở đây `--gold-soft` phân biệt bằng **độ bão hoà** (#75603C, ám
     nâu, dịu mắt) chứ ⛔ KHÔNG bằng độ sáng. Đây đúng là chỗ app trượt. */
:root.sv-light {
  --navy: #FFFFFF;         --navy-dark: #F2F5F9;   --navy-deep: #E9EDF3;
  --gold: #7A6110;         --gold-soft: #75603C;
  --surface: #FBFCFE;      --surface-raised: #FFFFFF;
  --success: #1D7A4A;      --warning: #8A5B00;     --danger: #B02A20;  --info: #1B5FBF;
  --ink: #16233B;          --ink-2: rgba(22,35,59,.72);  --ink-3: rgba(22,35,59,.66);
  --line: rgba(22,35,59,.16);
  --on-accent: #FFFFFF;
}

/* ⛔ LOGO TRẮNG TRÊN NỀN SÁNG = TÀNG HÌNH — bắt được lúc chụp màn 02/09.
   `logo-white.png` là ảnh **trắng**; nền sáng nuốt sạch nó.
   ⛔ ⛔ ĐỪNG chữa bằng `filter: invert()` hay `brightness(0)`: đọc từng điểm
     ảnh của tệp thì logo ⛔ KHÔNG đơn sắc — **5% số điểm là ĐỎ (~#A02020) và
     XANH (~#204080)**, tức là có dấu hiệu nhận diện có màu. `invert` biến đỏ
     thành lơ, `brightness(0)` bôi đen sạch cả hai ⇒ **làm hỏng nhãn hiệu** để
     đổi lấy chút tiện tay. ⛔ Đó là cái giá ⛔ không được phép trả.
   ⇒ Giữ NGUYÊN tệp ảnh, đặt nó lên **tấm nền navy của chính thương hiệu**.
     Cách này an toàn với mọi màu trong logo, kể cả màu thêm vào sau này. */
:root.sv-light .top .brand img {
  background: #1F3050;
  padding: 4px 9px;
  border-radius: 7px;
  box-sizing: content-box;
}
:root.sv-game {
  --navy: #121826;         --navy-dark: #0A0E18;   --navy-deep: #05060A;
  --gold: #22E5D6;         --gold-soft: #6BF0E6;
  --surface: #121826;      --surface-raised: #1B2436;
  --success: #2BE07B;      --warning: #FFB020;     --danger: #FF4D5E;  --info: #3AA0FF;
  --ink: #EAF6FF;          --ink-2: rgba(234,246,255,.72); --ink-3: rgba(234,246,255,.62);
  --line: rgba(234,246,255,.14);
  --on-accent: #05060A;
}
:root.sv-relax {
  --navy: #2A3A4D;         --navy-dark: #223041;   --navy-deep: #1A2230;
  --gold: #7FD1C0;         --gold-soft: #A8E6D8;
  --surface: #2A3A4D;      --surface-raised: #35485E;
  --success: #7FD1A6;      --warning: #E6C67F;     --danger: #E39A9A;  --info: #8FB8E8;
  /* ⛔ relax có nền sáng nhất trong 6 chủ đề (#35485E) nên alpha .62 chỉ đạt
     4.33:1 — DƯỚI ngưỡng WCAG AA. Riêng chủ đề này nâng lên .72 (tự đo: 5.0:1).
     Bắt được ở vòng rà cuối, khi đo tương phản trên CẢ 6 chủ đề chứ không chỉ
     chủ đề mặc định — bài học: đo một chủ đề là chưa đủ. */
  --ink: #EEF2F5;          --ink-2: rgba(238,242,245,.80); --ink-3: rgba(238,242,245,.72);
  --line: rgba(238,242,245,.14);
  --on-accent: #1A2230;
}
:root.sv-holiday {
  --navy: #143020;         --navy-dark: #10271A;   --navy-deep: #0B1E14;
  --gold: #E5B93B;         --gold-soft: #F2D375;
  --surface: #143020;      --surface-raised: #1C3E2A;
  --success: #3DBE7B;      --warning: #E0A93E;     --danger: #D8382F;  --info: #4A86E8;
  --ink: #F3F6EF;          --ink-2: rgba(243,246,239,.74); --ink-3: rgba(243,246,239,.62);
  --line: rgba(243,246,239,.14);
  --on-accent: #0B1E14;
}
:root.sv-kitty {
  --navy: #3A1E2E;         --navy-dark: #331726;   --navy-deep: #241019;
  --gold: #FF7EB6;         --gold-soft: #FFA9CE;
  --surface: #3A1E2E;      --surface-raised: #4E2A3D;
  --success: #5BD6A0;      --warning: #F0B84E;     --danger: #FF5D7A;  --info: #7FA9F0;
  --ink: #FDF0F6;          --ink-2: rgba(253,240,246,.74); --ink-3: rgba(253,240,246,.62);
  --line: rgba(253,240,246,.14);
  --on-accent: #241019;
}
:root.sv-high_contrast {
  --navy: #0A0A0A;         --navy-dark: #000000;   --navy-deep: #000000;
  --gold: #FFD400;         --gold-soft: #FFE85C;
  --surface: #0A0A0A;      --surface-raised: #1A1A1A;
  --success: #00E676;      --warning: #FFC400;     --danger: #FF3B30;  --info: #40C4FF;
  --ink: #FFFFFF;          --ink-2: #E0E0E0;       --ink-3: #9E9E9E;
  --line: rgba(255,255,255,.35);
  --on-accent: #000000;
}

/* ⚠ SAI LỆCH CÓ CHỦ Ý so với tokens.dart của app: app đặt textDisabled ở
   alpha 0x66 (=.40). Trang này dùng --ink-3 cho CHỮ PHỤ/GỢI Ý (không phải chữ
   bị vô hiệu), và tự đo trên trình duyệt thấy alpha .46 chỉ đạt 3.79:1 trên nền
   surface — KHÔNG đủ WCAG AA (4.5:1) mà §4.3 handoff bắt buộc. Nâng lên .62 thì
   đạt ≥4.78:1 trên cả nền sáng nhất (surface-raised). Tên token giữ nguyên.
   ⇒ ĐỀ NGHỊ CHỈ HUY: app điện thoại có thể đang mắc đúng lỗi tương phản này. */
:root {
  --r: 12px;               --r-s: 8px;
  --bar-h: 78px;
  --panel-w: 340px;
  --shadow: 0 10px 30px rgba(0,0,0,.45);
  --fast: 150ms cubic-bezier(.33,1,.68,1);
  --normal: 250ms cubic-bezier(.33,1,.68,1);
  /* Safari/iPhone: vùng an toàn tai thỏ + thanh gạt dưới */
  --sat: env(safe-area-inset-top, 0px);
  --sab: env(safe-area-inset-bottom, 0px);
  --sal: env(safe-area-inset-left, 0px);
  --sar: env(safe-area-inset-right, 0px);
}

/* ───────────────────────────── nền tảng ───────────────────────────────── */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
  height: 100%;
  /* ⛔ chốt chặn "không trượt ngang" ở MỌI bề rộng (cổng §7 mục 7) */
  overflow-x: hidden;
  max-width: 100%;
}
body {
  background: var(--navy-deep);
  color: var(--ink);
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior: none;
}
#app {
  /* 100dvh: Safari/iOS thanh địa chỉ co giãn làm 100vh sai → dvh mới đúng.
     Có fallback vh cho trình duyệt cũ. */
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding-left: var(--sal);
  padding-right: var(--sar);
}
/* ⛔ LỖI TỰ ĐO BẮT ĐƯỢC 05/08: thuộc tính `hidden` của HTML chỉ là display:none
   trong bảng kiểu của TRÌNH DUYỆT, nên khai báo display:flex của .mid/.bar/.center
   ở đây ĐÈ NÓ — khung họp và thanh dưới vẫn chiếm chỗ khi đang ở tiền sảnh (thấy
   rõ dải trống dưới màn). Chốt một dòng cho cả trang. */
[hidden] { display: none !important; }

button { font: inherit; color: inherit; cursor: pointer; background: none; border: none; }
button:disabled { cursor: not-allowed; }
input, select, textarea { font: inherit; color: inherit; }
:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* ─────────────────────────── icon dùng chung ──────────────────────────── */
/* SVG nội tuyến tô bằng currentColor (⛔ H9: var() không dùng được trong fill=) */
.ibtn svg, .ic svg { width: 100%; height: 100%; display: block; }
.ic { display: inline-block; width: 20px; height: 20px; vertical-align: -4px; color: inherit; flex: none; }
.ic.s18 { width: 18px; height: 18px; }
.ic.s28 { width: 28px; height: 28px; }
/* ⛔⛔ 03/09 — BA cỡ icon dưới đây được JS dùng mà ⛔ CHƯA BAO GIỜ ĐƯỢC KHAI:
   `.s14` (2 chỗ: nút xoá phòng gần đây, dấu ✓ xác thực), `.s20` (1), `.s12`
   (1). Thiếu luật thì icon giữ nguyên 20px mặc định — ⛔ không lỗi, ⛔ không
   cảnh báo, chỉ SAI CỠ. Đúng họ bẫy `currentColor` 01/09: nạp được, ⛔ không
   báo gì, mà nhìn vẫn sai. */
.ic.s12 { width: 12px; height: 12px; }
.ic.s14 { width: 14px; height: 14px; }
.ic.s20 { width: 20px; height: 20px; }
/* ⛔ `.flip` dùng ở nút TRANG TRƯỚC (hop-app.js) để lật mũi tên `chev` 180°.
   ⛔ Chưa khai ⇒ HAI mũi tên phân trang cùng chỉ MỘT HƯỚNG. */
.flip { transform: scaleX(-1); }
/* ⛔ `.hourglass-anim` ở màn "đang chờ chủ họp duyệt" — ⛔ chưa khai nên đồng
   hồ cát ĐỨNG IM, người dùng tưởng máy treo. Lật chậm, ⛔ không quay tít. */
@keyframes sv-lat-cat { 0%,45% { transform: rotate(0) } 55%,100% { transform: rotate(180deg) } }
.hourglass-anim { animation: sv-lat-cat 2.4s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .hourglass-anim { animation: none; } }
/* ⛔ `.hang-chon`: hàng có ô tick + <b> + <br> + dòng gợi ý. ⛔ Chưa khai nên
   dòng gợi ý TRÀN XUỐNG DƯỚI Ô TICK thay vì thẳng hàng với chữ. */
.hang-chon { display: grid; grid-template-columns: auto 1fr; gap: 4px 8px;
             align-items: start; line-height: 1.45; }
.hang-chon > input { margin-top: 3px; }
/* ⛔ `.qr-phai`: cột phải hộp QR. `.tx-chu`: nhãn chữ của tin tự xoá. */
.qr-phai { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.tx-chu { opacity: .75; font-size: 12px; }

.ibtn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; padding: 9px;
  border-radius: 50%; color: var(--ink-2);
  transition: background var(--fast), color var(--fast);
}
.ibtn:hover:not(:disabled) { background: rgba(255,255,255,.09); color: var(--ink); }
.ibtn:disabled { color: var(--ink-3); }
/* ⛔ VÁ 14/09 — NÚT MỜ MÀ VẪN BẤM ĐƯỢC (luật §9 + bài học `chat.css:888`).
   `disabled` của HTML nuốt sạch sự kiện chuột ⇒ trên máy CẢM ỨNG tooltip
   KHÔNG BAO GIỜ hiện, người dùng bấm mãi một nút câm. Nút PTZ của máy SIP vì
   vậy dùng `aria-disabled` để VẪN NHẬN được cú bấm rồi NÓI RÕ LÝ DO.
   ⇒ Kiểu dáng phải GIỐNG HỆT `:disabled` ngay trên, kẻo nút mờ trông như nút
     thường — tức là nút giả về mặt THỊ GIÁC vẫn còn.
   ⛔ Dòng hover thứ hai là BẮT BUỘC: `[aria-disabled]` ⛔ KHÔNG khớp `:disabled`
     nên `.ibtn:hover:not(:disabled)` ở trên VẪN làm nó sáng lên khi rê chuột. */
.ibtn[aria-disabled="true"] { color: var(--ink-3); cursor: not-allowed; }
.ibtn[aria-disabled="true"]:hover { background: none; color: var(--ink-3); }
.btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }
.btn.sec[aria-disabled="true"]:hover { border-color: var(--line); color: var(--ink); }
/* Nhãn lý do trong bảng chọn camera — `.sub` của trang chỉ có `.row .sub` và
   `.card .sub`, ⛔ không có `.sub` trần ⇒ phải khai riêng, kẻo chữ ⛔ không tô. */
.ptz-chon .ptz-ai .sub { font-size: 11.5px; color: var(--ink-3); }
.ibtn[aria-pressed="true"] { color: var(--gold); }

/* Nút tròn thanh họp: 46px theo §2.3 quy ước icon */
.cbtn {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; padding: 11px;
  border-radius: 50%;
  background: var(--surface-raised); color: var(--ink);
  transition: background var(--fast), color var(--fast), transform var(--fast);
}
.cbtn:hover:not(:disabled) { background: color-mix(in srgb, var(--surface-raised) 80%, var(--ink) 20%); }
/* phản hồi tức thì khi bấm — §4.3 "nút bấm có phản hồi tức thì, không chờ mạng" */
.cbtn:active:not(:disabled) { transform: scale(.93); }
.cbtn.on { background: var(--gold); color: var(--on-accent); }
.cbtn.off { background: var(--danger); color: #fff; }
.cbtn.danger { background: var(--danger); color: #fff; }
.cbtn.rec { background: var(--danger); color: #fff; }
.cbtn:disabled { background: var(--surface); color: var(--ink-3); }
.cbtn .badge-dot {
  position: absolute; top: -1px; right: -1px; min-width: 17px; height: 17px;
  border-radius: 9px; background: var(--danger); color: #fff;
  font-size: 11.5px; font-weight: 700; line-height: 17px; text-align: center;
  padding: 0 4px; display: none;
}
.cbtn .badge-dot.on { display: block; }
.cbtn.wide { width: auto; border-radius: 23px; padding: 11px 18px; gap: 8px; }
.cbtn.wide .ic { width: 22px; height: 22px; }

/* nhóm nút có nhãn chữ (đỡ cho người mới, vẫn có tooltip) */
/* ⛔⛔ 16/09 — THẺ <a class="btn"> BỊ GẠCH CHÂN NHƯ LIÊN KẾT.
   Đo trên trang thật: `<a class="btn">` cho `text-decoration-line: underline`,
   còn `<button class="btn">` cho `none` ⇒ lối vào "chat riêng" trông như một
   liên kết lạc chỗ giữa các nút.
   ⭐⭐ Và ⛔ KHÔNG chỉ mình nó: đếm được **5 thẻ `<a class="btn">` khác** đang
   sống (4 ở hop-app.js, 1 ở chat-app.js) — tất cả cùng dính. ⇒ Vá ở LUẬT
   CHUNG, ⛔ đừng dán kiểu riêng cho một chỗ (họ bọ "hai khối cùng việc, một
   khối được vá" đã cắn nhiều lần). */
a.btn, a.btn:link, a.btn:visited, a.btn:hover, a.btn:active { text-decoration: none; }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 12px 18px; border-radius: 10px; font-weight: 600;
  background: var(--gold); color: var(--on-accent);
  transition: filter var(--fast), transform var(--fast);
}
.btn:hover:not(:disabled) { filter: brightness(1.08); }
.btn:active:not(:disabled) { transform: scale(.98); }
.btn.sec { background: transparent; border: 1px solid var(--line); color: var(--ink); font-weight: 500; }
.btn.sec:hover:not(:disabled) { border-color: var(--gold); color: var(--gold-soft); }
.btn.dgr { background: var(--danger); color: #fff; }
.btn.sm { padding: 7px 12px; font-size: 13px; border-radius: 8px; }
.btn:disabled { opacity: .5; }
.btn.full { width: 100%; }

/* ─────────────────────────── thanh trên ───────────────────────────────── */
.top {
  display: flex; align-items: center; gap: 10px;
  padding: calc(10px + var(--sat)) 14px 10px;
  background: var(--navy-dark); border-bottom: 1px solid var(--line);
  flex: none; min-width: 0;
}
/* Logo Starview thay cho chữ. Cao cố định theo thanh, rộng tự co; tệp là chữ
   TRẮNG nền trong suốt nên không cần tô màu. */
.top .brand { display: flex; align-items: center; flex: none; text-decoration: none; }
.top .brand img { height: 20px; width: auto; max-width: 190px; display: block; object-fit: contain; }
.top .brand-txt { color: var(--gold); font-weight: 700; letter-spacing: .06em; font-size: 14px; white-space: nowrap; }
.top .rn {
  font-size: 13px; color: var(--ink-2); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.top .sp { flex: 1 1 auto; min-width: 0; }
.top .grp { display: flex; align-items: center; gap: 2px; flex: none; }
.chip {
  display: inline-flex; align-items: center; gap: 5px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 999px; padding: 4px 10px; font-size: 12px; color: var(--ink-2);
  white-space: nowrap; flex: none;
}
.chip .ic { width: 15px; height: 15px; }
.chip.ok { color: var(--success); border-color: color-mix(in srgb, var(--success) 45%, transparent); }
.chip.warn { color: var(--warning); border-color: color-mix(in srgb, var(--warning) 45%, transparent); }
.chip.bad { color: var(--danger-chu, var(--danger)); border-color: color-mix(in srgb, var(--danger) 45%, transparent); }
.chip.gold { color: var(--gold-soft); border-color: color-mix(in srgb, var(--gold) 45%, transparent); }

/* Bộ chọn ngôn ngữ — hiện RÕ hai thứ tiếng đang có, không phải quả cầu mù mờ */
.langsw {
  display: inline-flex; align-items: center; gap: 1px; flex: none;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 999px; padding: 3px 4px 3px 8px;
}
.langsw .ic.lg { width: 15px; height: 15px; color: var(--ink-3); margin-right: 3px; }
.langsw button {
  min-width: 30px; padding: 3px 7px; border-radius: 999px;
  font-size: 12px; font-weight: 700; letter-spacing: .04em; color: var(--ink-3);
  transition: background var(--fast), color var(--fast);
}
.langsw button:hover { color: var(--ink); background: rgba(255,255,255,.08); }
.langsw button.on { background: var(--gold); color: var(--on-accent); }
.langsw button.add { font-weight: 400; font-size: 14px; min-width: 22px; padding: 2px 5px; }

/* ───────────────────── băng thông báo (4 trạng thái §1) ───────────────── */
.banner {
  display: flex; align-items: center; gap: 9px;
  padding: 9px 14px; font-size: 13px; flex: none;
  background: var(--surface); border-bottom: 1px solid var(--line);
}
.banner.rec { background: color-mix(in srgb, var(--danger) 22%, var(--navy-dark)); color: var(--ink); }
.banner.warn { background: color-mix(in srgb, var(--warning) 20%, var(--navy-dark)); }
.banner.info { background: color-mix(in srgb, var(--info) 18%, var(--navy-dark)); }
.banner .ic { flex: none; }
.banner .txt { flex: 1; min-width: 0; }
.blink { animation: sv-blink 1.6s ease-in-out infinite; }
@keyframes sv-blink { 0%,100% { opacity: 1 } 50% { opacity: .35 } }
.spin { animation: sv-spin 1.1s linear infinite; }
@keyframes sv-spin { to { transform: rotate(360deg) } }

/* ─────────────────────────── khung giữa ───────────────────────────────── */
.mid { flex: 1 1 auto; display: flex; min-height: 0; min-width: 0; }
.stagewrap { flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0; min-width: 0; }

/* lưới video — số cột do JS đặt qua --cols (tự xếp theo số người) */
#stage {
  flex: 1 1 auto; display: grid; gap: 8px; padding: 10px;
  grid-template-columns: repeat(var(--cols, 1), minmax(0, 1fr));
  grid-auto-rows: minmax(0, 1fr);
  min-height: 0; min-width: 0;
  transition: grid-template-columns var(--normal);
}
/* ⭐ 16/08/2026 — BỐ CỤC "NGƯỜI NÓI LỚN" LÊN FULL MÀN, lệnh anh Triệu:
   *"chế độ người nói lớn ... lên full màn hình, chỉ để lại một góc dưới chân
   màn để hiển thị các site còn lại"* — và cú BẤM VÀO Ô (vốn ghim + chuyển
   speaker từ 11/08) nhờ đó thành: bấm = full màn ô đó, bấm lại = về lưới.
   Dùng đúng khung 2 hàng của chế độ trình chiếu bên dưới (đã sống từ 14/08):
   hero chiếm trọn hàng trên, các site còn lại thành dải nhỏ sát đáy.
   (Bản cũ: hero chỉ chiếm cả HÀNG ĐẦU của lưới đều — to hơn tí chứ không full.)
   Ô dải nhỏ ăn lớp simulcast thấp là hành vi đúng của adaptiveStream —
   bài học 12/08, KHÔNG ép HIGH cho dải. */
#stage.speaker {
  grid-template-columns: repeat(var(--cols, 1), minmax(0, 1fr));
  grid-template-rows: minmax(0, 1fr) minmax(0, var(--dai-cao, 21%));
}
#stage.speaker > .tile.hero { grid-column: 1 / -1; grid-row: 1; }
#stage.speaker > .tile:not(.hero) { grid-row: 2; }
/* Một mình trong phòng (hero là ô duy nhất): chiếm luôn cả hai hàng, khỏi chừa dải trống */
#stage.speaker > .tile.hero:only-child { grid-row: 1 / -1; }
@media (max-width: 560px) { #stage.speaker { --dai-cao: 16%; } }

/* ⭐⭐ CHẾ ĐỘ TRÌNH CHIẾU — 12/08/2026, lệnh anh Triệu.
   *"khi màn hình shared là phải full toàn bộ màn hình, còn màn hình hội thoại
   thì tạo 1 cửa sổ nhỏ dưới chân màn hình thôi. Cả người share và người nhận
   share đều vậy."*

   Trước bản vá: ô chia sẻ chỉ là MỘT ô trong lưới đều — nội dung slide/bảng
   tính bị co còn 1/3 màn, trong khi ba khuôn mặt chiếm 2/3. Ngược hẳn thứ tự
   ưu tiên: lúc có người trình chiếu thì NỘI DUNG mới là thứ cả phòng cần đọc.

   Nay: nội dung chiếm trọn phần trên, người dự lùi thành dải nhỏ sát đáy.
   ⛔ Dải người KHÔNG bị bỏ đi (luật cấm xoá) — chỉ nhường chỗ, vẫn thấy mặt,
     vẫn thấy viền "đang nói", vẫn bấm được để phóng to. */
#stage.trinhchieu {
  grid-template-columns: repeat(var(--cols, 1), minmax(0, 1fr));
  grid-template-rows: minmax(0, 1fr) minmax(0, var(--dai-cao, 21%));
}
#stage.trinhchieu > .tile.screen { grid-column: 1 / -1; grid-row: 1; }
#stage.trinhchieu > .tile:not(.screen) { grid-row: 2; }
/* Màn hẹp (điện thoại dọc): dải người mỏng hơn nữa, nội dung được ưu tiên tối đa */
@media (max-width: 560px) { #stage.trinhchieu { --dai-cao: 16%; } }

.tile {
  position: relative; overflow: hidden; min-width: 0; min-height: 0;
  background: var(--navy); border-radius: var(--r);
  display: flex; align-items: center; justify-content: center;
}
/* VÁ 07/08/2026 — LỖI NHÁY TOÀN LƯỚI.
   Trước đây `animation: tile-in` nằm thẳng trên .tile. Mà veLuoi() trong
   hop-app.js xoá sạch #stage rồi createElement lại TỪNG ô, nên mỗi lần vẽ lại
   là mọi ô đều là phần tử MỚI ⇒ hoạt ảnh chạy lại từ opacity 0. Người dùng
   thấy: chỉ cần một người bất kỳ tắt/bật mic, giơ tay, vào/ra, hoặc mình kéo
   đổi cỡ cửa sổ, là video của CẢ PHÒNG chớp mờ một cái 250ms. Phòng 8-16 người
   thì mic bật tắt liên tục nên màn hình nháy gần như không ngừng.
   Bản vá kho thẻ <video> sáng nay giữ được thẻ video sống (hết trễ/nhoè) nhưng
   không chạm cái vỏ .tile nên phần nháy còn nguyên.
   ⛔ KHÔNG xoá hiệu ứng — chỉ hẹp phạm vi: chỉ ô LẦN ĐẦU xuất hiện (có lớp
   .moi) mới chạy hoạt ảnh vào. Ô đã có sẵn mà chỉ vẽ lại thì đứng yên.
   ⚠ CẦN VẾ THỨ HAI Ở hop-app.js (tệp của phiên khác): veLuoi() phải ghi nhớ
   tập dataset.id trước khi xoá #stage, rồi gắn thêm ' moi' cho ô nào không có
   trong tập đó. Chưa có vế đó thì ô mới hiện thẳng không mờ vào — mất một
   hiệu ứng trang trí, KHÔNG mất chức năng nào, và vẫn hơn hẳn cảnh nháy cũ. */
.tile.moi { animation: tile-in var(--normal) both; }
@keyframes tile-in { from { opacity: 0; transform: scale(.97) } to { opacity: 1; transform: none } }
.tile video {
  width: 100%; height: 100%; object-fit: cover; display: block;
  background: var(--navy-dark);
}
.tile.screen video { object-fit: contain; background: #000; }
/* ⭐ 24/09/2026 — O PiP CAMERA NGUOI DANG CHIA SE (lenh anh Trieu: 1/25 dien tich
   man toan canh ⇒ 1/5 rong x 1/5 cao — kich thuoc dat bang style tu hop dong loi).
   pointer-events:none ⇒ bam vao PiP van la bam o chia se (= toan man). */
.tile.screen .pipcs {
  position: absolute; top: 8px; left: 8px; z-index: 3; overflow: hidden;
  border-radius: 8px; background: var(--navy-dark);
  box-shadow: 0 0 0 1px rgba(255,255,255,.28), 0 4px 14px rgba(0,0,0,.45);
  pointer-events: none;
}
.tile.screen .pipcs video { object-fit: cover; background: var(--navy-dark); }
/* nhan "dang chia se" nhuong goc trai cho o PiP */
.tile.screen.co-pipcs .tag { left: auto; right: 8px; }
/* ⭐ 24/09 — BAM O CHIA SE = TOAN MAN: chi con o chia se, kin khung. */
#stage.toanman-cs { grid-template-rows: minmax(0, 1fr); padding: 0; gap: 0; background: #000; }
#stage.toanman-cs > .tile:not(.screen) { display: none; }
#stage.toanman-cs > .tile.screen { grid-column: 1 / -1; grid-row: 1 / -1; border-radius: 0; }
/* người đang nói: viền sáng (§4.1) */
.tile.talking { box-shadow: inset 0 0 0 3px var(--gold); }
.tile.me .nm::after { content: ''; }
/* ⛔ §4.2: người tắt camera KHÔNG được là ô đen trống — luôn có chữ cái đầu tên */
.tile .avatar {
  width: clamp(48px, 22%, 96px); aspect-ratio: 1; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; color: #fff; font-size: clamp(20px, 9cqw, 40px);
  container-type: inline-size;
}
.tile .nm {
  position: absolute; left: 8px; bottom: 8px; right: 8px;
  display: flex; align-items: center; gap: 6px;
  font-size: 12px; color: #fff;
  background: rgba(0,0,0,.55); border-radius: 7px; padding: 3px 8px;
  max-width: calc(100% - 16px); min-width: 0;
}
.tile .nm .who { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tile .nm .ic { width: 15px; height: 15px; flex: none; }
.tile .nm .ic.mut { color: var(--danger-chu, var(--danger)); }
.tile .nm .ic.hnd { color: var(--gold); }
.tile .tag {
  position: absolute; top: 8px; left: 8px;
  font-size: 11.5px; background: rgba(0,0,0,.55); color: #fff;
  border-radius: 6px; padding: 2px 7px;
}
/* biểu cảm bay lên */
.tile .reax {
  position: absolute; right: 10px; bottom: 40px;
  display: flex; flex-direction: column-reverse; gap: 4px; pointer-events: none;
}
.reax .r1 { color: var(--gold-soft); animation: fly 2.4s ease-out both; }
.reax .r1 .ic { width: 26px; height: 26px; }
@keyframes fly { 0% { opacity: 0; transform: translateY(6px) scale(.7) } 15% { opacity: 1; transform: none } 100% { opacity: 0; transform: translateY(-46px) scale(1.1) } }

.hint { text-align: center; color: var(--ink-3); font-size: 13px; padding: 8px 12px; }
.pager {
  display: none; align-items: center; justify-content: center; gap: 14px;
  padding: 4px 8px 8px; color: var(--ink-2); font-size: 13px; flex: none;
}
.pager.on { display: flex; }

/* ───────────────────── bảng bên phải (4 tab) ──────────────────────────── */
/* §4.1: mở/đóng trượt ngang, KHÔNG đẩy vỡ lưới video ⇒ dùng width + flex:none,
   lưới tự co bằng minmax(0,1fr); trên hẹp thì thành lớp phủ. */
#panel {
  flex: none; width: 0; overflow: hidden;
  background: var(--navy-dark); border-left: 1px solid var(--line);
  display: flex; flex-direction: column; min-height: 0;
  transition: width var(--normal);
}
#panel.on { width: var(--panel-w); }
.pn-hd { display: flex; align-items: center; gap: 4px; padding: 8px 4px 8px 8px; border-bottom: 1px solid var(--line); flex: none; }
/* ⛔ LỖI TỰ ĐO BẮT ĐƯỢC 05/08: 5 tab xếp NGANG có nhãn thì tràn khỏi bảng
   340px — hai tab cuối ("Hoạt động", "Chủ họp điều hành") bị cắt mất, người
   dùng không biết là có. Nay xếp LƯỚI ĐỀU: icon trên, nhãn nhỏ dưới, mỗi tab
   một phần bằng nhau nên bao nhiêu tab cũng vừa.
   ⛔ VÁ 07/08/2026 — ĐO LẠI THÌ PHÉP TÍNH CŨ SAI, và nay đã có tab thứ 7.
   Chỗ thật cho .pn-tabs trong bảng 340px là 340 − 8 (đệm trái .pn-hd) − 4 (đệm
   phải) − 40 (nút đóng .ibtn) − 4 (khe) = 284px, chứ không phải cả 340px —
   phép tính "6 tab ⇒ mỗi tab ~60px" ở dưới quên trừ nút đóng. Nhồi 7 tab
   (chủ họp) vào một hàng chỉ được 38,9px/tab ⇒ nhãn bị cắt còn 4-6 ký tự:
   "Ngườ… Trò c… Chat… Trợ l… Hoạt… Công… Chủ h…". Trên điện thoại bảng là lớp
   phủ toàn màn nên `title=` của trình duyệt KHÔNG bao giờ hiện, người dùng
   phải bấm thử từng tab mới biết tab nào là gì — nặng nhất là không tách được
   "Trò chuyện" (chat trong họp, tan họp là hết) với "Chat bảo mật" (Matrix,
   còn mãi).
   Nay cho tab XUỐNG HÀNG thay vì nhồi một hàng: auto-fit chọn 4 cột ở bảng
   340px (≈69px/tab, gần gấp đôi) và 5 cột ở bảng 380px. KHÔNG tab nào bị bỏ,
   chỉ đổi cách xếp; thanh tiêu đề cao thêm ~48px, phần thân bảng tự co. */
.pn-tabs { display: grid; grid-template-columns: repeat(auto-fit, minmax(62px, 1fr));
           gap: 2px; flex: 1; min-width: 0; }
.tab {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  /* cỡ chữ 10px + gần như không đệm ngang: giữ nguyên để nhãn dài nhất
     ("Chat bảo mật", "AI assistant") vừa được một cột 69px. */
  padding: 6px 1px; border-radius: 8px; font-size: 11.5px; line-height: 1.22;
  letter-spacing: -.01em;
  color: var(--ink-2); text-align: center; min-width: 0;
  transition: background var(--fast), color var(--fast);
}
.tab > span:last-child {
  display: block; width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tab .ic { width: 19px; height: 19px; }
.tab:hover { background: rgba(255,255,255,.07); color: var(--ink); }
.tab[aria-selected="true"] { background: var(--surface-raised); color: var(--gold-soft); }
.pn-body { flex: 1 1 auto; overflow-y: auto; overflow-x: hidden; min-height: 0; padding: 12px; }
.pn-foot { flex: none; padding: 10px; border-top: 1px solid var(--line); }

.sec-h { font-size: 11.5px; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); margin: 4px 0 8px; }
.sec-h:not(:first-child) { margin-top: 18px; }

.row {
  display: flex; align-items: center; gap: 9px;
  padding: 8px 6px; border-radius: 8px; min-width: 0;
}
.row:hover { background: rgba(255,255,255,.05); }
.row .av {
  width: 32px; height: 32px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 700; color: #fff;
}
.row .nm2 { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; }
.row .sub { font-size: 11.5px; color: var(--ink-3); }
/* ⛔ VÁ 07/08/2026 — HAI NÚT NGUY HIỂM DÍNH NHAU, BẤM TRƯỢT LÀ HỎNG VIỆC THẬT.
   Trong .acts là các cặp: "Cho vào" đứng sát "Từ chối" (hàng chờ duyệt), và
   "Tắt mic" đứng sát "Đuổi khỏi phòng" (hàng người trong phòng). Trước đây ô
   bấm chỉ 32×32px (≈8,5mm — dưới mức tối thiểu 44pt iOS / 48dp Android) và khe
   giữa hai ô đúng 1px, tức gần như dính liền. Ở ≤900px bảng bên phải thành lớp
   phủ toàn màn nên chủ họp duyệt người bằng NGÓN TAY là chuyện thường ngày.
   Bấm trượt vài pixel sang phải là gọi thẳng duyetNguoi(w, false): người xin
   vào bị gỡ khỏi hàng chờ ngay lập tức, chủ họp không có cách nào gọi họ lại,
   họ phải tự bấm xin vào từ đầu.
   Đây mới là vế KÍCH THƯỚC. Vế thứ hai — bắt "Từ chối" hỏi lại một câu như
   "Đuổi khỏi phòng" vốn đã hỏi — nằm ở hop-app.js (tệp của phiên khác).
   Không xoá nút nào, không đổi hành vi "Cho vào" (vẫn một nhát). */
.row .acts { display: flex; gap: 8px; flex: none; }
.row .acts .ibtn { width: 40px; height: 40px; padding: 10px; }
/* điện thoại: nới đủ 44px theo mức tối thiểu iOS/Android. Tối đa 2 nút trong
   .acts nên 2×44 + khe 10 = 98px, bảng phủ rộng 360px vẫn thừa chỗ cho tên. */
@media (max-width: 560px) {
  .row .acts { gap: 10px; }
  .row .acts .ibtn { width: 44px; height: 44px; padding: 12px; }
}

.empty-note { color: var(--ink-3); font-size: 13px; text-align: center; padding: 20px 8px; }
.empty-note .ic { width: 30px; height: 30px; margin: 0 auto 8px; display: block; color: var(--ink-3); }

/* trò chuyện */
#msgs { display: flex; flex-direction: column; gap: 8px; }
.msg { max-width: 90%; padding: 7px 10px; border-radius: 10px; font-size: 13px; word-wrap: break-word; overflow-wrap: anywhere; }
.msg.me { background: var(--surface-raised); align-self: flex-end; }
.msg.them { background: var(--surface); align-self: flex-start; }
.msg .who { font-size: 11.5px; color: var(--ink-3); margin-bottom: 2px; }
.chat-in { display: flex; gap: 6px; align-items: flex-end; }
.chat-in textarea {
  flex: 1; min-width: 0; resize: none; max-height: 96px;
  background: var(--navy-deep); border: 1px solid var(--line); border-radius: 9px;
  padding: 9px 10px; font-size: 13px; line-height: 1.4;
}
.note { font-size: 11.5px; color: var(--ink-3); margin-top: 6px; }

/* phụ đề */
.cap-line { padding: 7px 0; border-bottom: 1px solid var(--line); font-size: 13px; }
.cap-line .spk { color: var(--gold-soft); font-size: 11.5px; display: block; }

/* ───────────────── tab Công cụ: mỗi lệnh của lõi một khối ──────────────── */
.ct-note {
  font-size: 12px; color: var(--ink-2); line-height: 1.5;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-s); padding: 9px 10px; margin-bottom: 10px;
}
.ct-item {
  border: 1px solid var(--line); border-radius: var(--r-s);
  padding: 9px 10px; margin-bottom: 8px; background: var(--surface);
}
.ct-item.mo { opacity: .62; }
.ct-hd { display: flex; align-items: center; gap: 8px; }
.ct-hd .ic { flex: none; color: var(--gold-soft); }
.ct-hd .ct-t { flex: 1; min-width: 0; font-size: 13px; font-weight: 600; }
.ct-hd .btn { flex: none; }
.ct-mo { font-size: 12px; color: var(--ink-2); line-height: 1.5; margin-top: 5px; }
.ct-mo.canh { color: var(--warning); }
.ct-in { margin: 8px 0 2px; }
.ct-in label { font-size: 11.5px; }
.ct-in input { padding: 8px 10px; font-size: 13px; }
/* khung kết quả: hiện NGUYÊN TRẠNG máy chủ trả về, kèm mã HTTP để truy được */
.ct-ra {
  display: flex; gap: 7px; align-items: flex-start; margin-top: 8px;
  font-size: 12px; line-height: 1.5; border-radius: var(--r-s);
  padding: 7px 9px; word-break: break-word; overflow-wrap: anywhere;
  background: var(--navy-deep); border: 1px solid var(--line);
}
.ct-ra .ic { flex: none; }
.ct-ra.ok { border-color: color-mix(in srgb, var(--success) 45%, transparent); }
.ct-ra.ok .ic { color: var(--success); }
.ct-ra.bad { border-color: color-mix(in srgb, var(--danger) 45%, transparent); }
.ct-ra.bad .ic { color: var(--danger-chu, var(--danger)); }
.ct-ra.warn { border-color: color-mix(in srgb, var(--warning) 45%, transparent); }
.ct-ra.warn .ic { color: var(--warning); }
.ct-ra.plan { border-color: color-mix(in srgb, var(--gold) 45%, transparent); }
.ct-ra.plan .ic { color: var(--gold); }
/* Bộ MƯỜI trạng thái của quy ước icon v2 §1.6 — mỗi trạng thái MỘT màu token,
   không tự chọn màu: chặn=danger · cảnh báo=warning · chưa rõ bảo mật=warning ·
   mất mạng máy=danger · chờ=textSecondary */
.ct-ra.chan { border-color: color-mix(in srgb, var(--danger) 45%, transparent); }
.ct-ra.chan .ic { color: var(--danger-chu, var(--danger)); }
.ct-ra.canh { border-color: color-mix(in srgb, var(--warning) 45%, transparent); }
.ct-ra.canh .ic { color: var(--warning); }
.ct-ra.chuaro { border-color: color-mix(in srgb, var(--warning) 45%, transparent); }
.ct-ra.chuaro .ic { color: var(--warning); }
.ct-ra.mangmat { border-color: color-mix(in srgb, var(--danger) 45%, transparent); }
.ct-ra.mangmat .ic { color: var(--danger-chu, var(--danger)); }
.ct-ra.warn .ic { color: var(--ink-2); }
/* hết hạn mức kỳ này (429) — warning, KHÔNG dùng gold như 402/403: gói vẫn có
   tính năng, chỉ là đã dùng hết phần của kỳ. */
.ct-ra.hetmuc { border-color: color-mix(in srgb, var(--warning) 45%, transparent); }
/* ⭐ 15/09 — LỆNH ĐANG BAY. Trước đây bấm nút AI xong ⛔ không có gì đổi suốt
   1–3 giây (đo: 5/8 nút), người dùng tưởng nút chết rồi bấm lại. Nay có khung
   nhấp nháy + nút tự khoá. Dùng lại `sv-blink` đã có, ⛔ đừng đẻ keyframe mới. */
.ct-ra.dang { border-color: color-mix(in srgb, var(--gold) 55%, transparent); }
/* ⭐ 15/09 anh Triệu: khung chữ phải NÓI TRẠNG THÁI DỊCH VỤ, ⛔ không chỉ nói kết
   quả lần bấm cuối. Hai trạng thái thường trực — xanh là ĐANG BẬT, xám là ĐANG
   TẮT. ⛔ Xám chứ ⛔ KHÔNG đỏ: tắt là bình thường, ⛔ không phải lỗi. */
.ct-ra.batroi { border-color: color-mix(in srgb, var(--success) 45%, transparent); }
.ct-ra.batroi .ic { color: var(--success); }
.ct-ra.tatroi { border-color: var(--line); opacity: .82; }
.ct-ra.tatroi .ic { color: var(--ink-2); }
.ct-ra.dang .ic { color: var(--gold); animation: sv-blink 1.1s ease-in-out infinite; }
.ct-item .btn.sm:disabled { cursor: progress; }
@media (prefers-reduced-motion: reduce) { .ct-ra.dang .ic { animation: none; } }
.ct-ra.hetmuc .ic { color: var(--warning); }

/* nút theo từng nền tảng ngoài — cái nào lõi chưa khai thì MỜ + nói rõ, không ẩn */
.lk-plat { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin: 8px 0 4px; }
.lk-p {
  display: flex; align-items: center; gap: 7px; min-width: 0;
  padding: 8px 9px; border-radius: var(--r-s);
  background: var(--navy-deep); border: 1px solid var(--line); color: var(--ink);
  font-size: 12px; text-align: left;
}
.lk-p:hover:not(:disabled) { border-color: var(--gold); }
.lk-p .ic { flex: none; color: var(--gold-soft); }
.lk-p .n { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lk-p .s { flex: none; font-size: 11px; color: var(--ink-3); }
.lk-p.mo { opacity: .55; }
.lk-p.mo .s { color: var(--warning); }

/* thẻ trạng thái "chưa cấu hình" / "gói chưa có" — KHÔNG ẩn chức năng */
.state-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r); padding: 14px; text-align: center;
}
.state-card .ic { width: 30px; height: 30px; margin: 0 auto 8px; display: block; color: var(--warning); }
.state-card .st-t { font-weight: 600; margin-bottom: 4px; }
.state-card .st-s { font-size: 12px; color: var(--ink-2); line-height: 1.55; }
.state-card.plan .ic { color: var(--gold); }

/* ───────────────────────── thanh dưới ─────────────────────────────────── */
.bar {
  flex: none; display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 12px 12px calc(12px + var(--sab));
  background: var(--navy-dark); border-top: 1px solid var(--line);
  min-height: var(--bar-h); position: relative;
  flex-wrap: nowrap;
}
/* ⛔ 🔴 LỖI TỰ ĐO BẮT ĐƯỢC 05/08 (vòng rà cuối, ở bề rộng 768px): hai nhóm cạnh
   trước đây định vị TUYỆT ĐỐI, nhóm giữa thì căn giữa ⇒ ở 768px nhóm giữa kết
   thúc ở 631px trong khi nhóm phải bắt đầu ở 554px ⇒ NÚT ĐÈ LÊN NHAU. Bốn phép
   đo "không trượt ngang" trước đó KHÔNG bắt được, vì phần tử định vị tuyệt đối
   không làm tăng scrollWidth — phải đo CHỒNG LẤN mới thấy.
   Nay dàn ba phần bằng flex thật: hai cạnh co giãn đều, nhóm giữa không bị đè. */
.bar .grp-l, .bar .grp-r { position: static; display: flex; gap: 6px; align-items: center; flex: 1 1 0; min-width: 0; }
.bar .grp-l { justify-content: flex-start; }
.bar .grp-r { justify-content: flex-end; }
.bar .grp-c { flex: 0 0 auto; }
/* nút Rời tách riêng bên phải, đỏ (§4.1) */
.bar .leave { background: var(--danger); color: #fff; }

/* vỉ biểu cảm */
.reax-pop {
  position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%);
  background: var(--surface-raised); border: 1px solid var(--line);
  border-radius: 999px; padding: 7px 9px; display: none; gap: 4px;
  box-shadow: var(--shadow); z-index: 30;
  max-width: min(94vw, 420px); overflow-x: auto; scrollbar-width: none;
}
.reax-pop::-webkit-scrollbar { display: none; }
.reax-pop.on { display: flex; }
.reax-pop .ibtn { width: 38px; height: 38px; padding: 8px; color: var(--ink); }
.reax-pop .ibtn:hover { background: var(--gold); color: var(--on-accent); }

/* vỉ chọn bố cục lưới — mỗi bố cục một nút, có dấu đang chọn */
.layout-pop { left: 0; right: auto; min-width: 210px; }
.layout-pop .lo {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 9px 10px; border-radius: 8px; font-size: 13px; color: var(--ink); text-align: left;
}
.layout-pop .lo:hover { background: rgba(255,255,255,.09); }
.layout-pop .lo.on { background: var(--gold); color: var(--on-accent); font-weight: 600; }
.layout-pop .lo .ic { flex: none; }

/* menu "thêm" */
.menu {
  position: absolute; bottom: calc(100% + 8px); right: 0;
  background: var(--surface-raised); border: 1px solid var(--line);
  border-radius: var(--r); padding: 6px; display: none; flex-direction: column;
  min-width: 232px; box-shadow: var(--shadow); z-index: 30;
  /* ⛔ VÁ 07/08/2026 — VỈ "THÊM" TRỒI RA NGOÀI MÉP TRÊN, KHÔNG BẤM TỚI ĐƯỢC.
     Ở màn hẹp (manHep < 560px) moMenuThem dồn tới 18 mục cho chủ họp. Đo theo
     chính bảng kiểu này: nút 38,25px × 18 + tiêu đề .mh 26,5 × 3 + vạch .sep
     11 × 3 + đệm/viền 14 = 815px. Chỗ trống phía trên thanh dưới chỉ 562-633px
     tuỳ máy (iPhone 15 Pro dựng đứng 633, Android 360×640 chỉ 562). Vỉ không
     kẹp chiều cao nên phần thừa nằm ở toạ độ ÂM, mà #app cao đúng 100dvh và
     html/body chỉ khai overflow-x ⇒ trang KHÔNG cuộn dọc để với lên.
     Người dùng mất đúng các mục ĐẦU vỉ: Giơ tay · Biểu cảm · Trợ lý AI · Công
     cụ — mà ở màn hẹp đây là lối DUY NHẤT tới chúng (nút Giơ tay đã bị bỏ khỏi
     thanh dưới, nút Biểu cảm bị display:none).
     Không xoá mục nào — chỉ kẹp chiều cao rồi cho cuộn, cùng khuôn với
     .modal .card và .cd-body vốn đã làm vậy. Trừ cả --bar-h lẫn --sab rồi trừ
     thêm 24px là cố ý trừ dư cho chắc. */
  max-height: calc(100dvh - var(--bar-h) - var(--sab) - 24px);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
}
/* trình duyệt cũ không hiểu dvh — đặt SAU khối trên để không đè bản dvh */
@supports not (height: 100dvh) {
  .menu { max-height: calc(100vh - var(--bar-h) - 24px); }
}
.menu.on { display: flex; }
.menu button {
  display: flex; align-items: center; gap: 10px; text-align: left;
  padding: 9px 10px; border-radius: 8px; font-size: 13px; color: var(--ink);
  width: 100%; min-width: 0;
}
.menu button:hover:not(:disabled) { background: rgba(255,255,255,.09); }
.menu button:disabled { color: var(--ink-3); }
.menu button .ic { flex: none; }
/* ⭐ 02/09/2026 — NHÃN NÚT: **THU NHỎ CHO VỪA**, ⛔ KHÔNG cắt ba chấm.
   Anh Triệu bắt được trên app: nút hiện *"Kết ..."* thay vì *"Kết thúc"* —
   nhãn bị cắt là **MẤT NGHĨA**, ⛔ không phải chỉ xấu. Tiếng Việt cắt sớm hơn
   tiếng Anh nhiều vì có dấu. Phiên Mobile đã đổi sang thu-nhỏ bên app
   (`FittedBox(scaleDown)`); đây là bản tương đương của web.
   ⛔ GIỮ `nowrap`: thu nhỏ chứ ⛔ KHÔNG được xuống dòng — nhãn nút xếp dọc
     từng chữ còn khó đọc hơn cả cắt.
   ⚠ Đây mới là LỚP (a). Lớp (b) — **trần độ dài nhãn** ở tệp ngôn ngữ — mới là
     thứ bắt lỗi TRƯỚC khi nó tới màn hình; xem cổng canh `kiem_nhan_nut.js`.
   ⛔ CHỈ đổi quy tắc này. 24 chỗ `ellipsis` còn lại trong hai tệp CSS đều áp cho
     TÊN NGƯỜI và NỘI DUNG — cắt ở đó là ĐÚNG, ⛔ đừng đụng vào (đã soi từng
     selector một, ⛔ không sửa hàng loạt). */
.menu button .lbl {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  /* thu nhỏ dần theo bề ngang còn lại, sàn 10px để ⛔ không nhỏ tới mức mù */
  font-size: clamp(10px, 3.1cqw, 1em);
  container-type: inline-size;
}
/* ⛔ Trình duyệt cũ ⛔ không hiểu `cqw` sẽ bỏ qua dòng trên và nhãn giữ nguyên cỡ
   — lúc đó vẫn cần một lối chót để chữ ⛔ không tràn ra ngoài nút. */
@supports not (font-size: 1cqw) {
  .menu button .lbl { text-overflow: ellipsis; }
}
.menu button .on-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--success); flex: none; }
.menu .sep { height: 1px; background: var(--line); margin: 5px 2px; }
.menu .mh { font-size: 11px; letter-spacing: .05em; text-transform: uppercase; color: var(--ink-3); padding: 7px 10px 3px; }

/* ───────────────── màn chờ / tiền sảnh / lỗi (giữa trang) ─────────────── */
.center { flex: 1 1 auto; display: flex; align-items: center; justify-content: center; padding: 16px; min-height: 0; overflow-y: auto; }
.card {
  width: 100%; max-width: 460px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 16px; padding: 22px; box-shadow: var(--shadow);
}
.card h1 { font-size: 20px; margin-bottom: 4px; }
.card .sub { color: var(--ink-2); font-size: 13px; margin-bottom: 16px; line-height: 1.55; }
.card.wide { max-width: 900px; }
.field { margin-bottom: 12px; }
.field label { display: block; font-size: 12px; color: var(--ink-2); margin-bottom: 5px; }
.field input, .field select {
  width: 100%; background: var(--navy-deep); border: 1px solid var(--line);
  border-radius: 9px; padding: 11px 12px; font-size: 15px;
}
/* mũi tên của <select> tự vẽ: bản gốc của trình duyệt đè lên chữ khi ô hẹp
   (tự đo: "Thiết bị mặc định" bị cắt ở cột 3 ô thiết bị) */
.field select {
  appearance: none; -webkit-appearance: none;
  padding-right: 30px;
  background-image: linear-gradient(45deg, transparent 49%, currentColor 50%),
                    linear-gradient(-45deg, transparent 49%, currentColor 50%);
  background-size: 6px 6px, 6px 6px;
  background-position: calc(100% - 16px) calc(50% + 1px), calc(100% - 11px) calc(50% + 1px);
  background-repeat: no-repeat;
}
.field .fh { font-size: 11.5px; color: var(--ink-3); margin-top: 4px; }

/* ⭐ 02/09 — Ô NHẬP BỊ TẮT PHẢI NHÌN RA LÀ TẮT.
   ⛔ 🔴 ĐO TRƯỚC KHI VIẾT: cả `hop.css` ⛔ KHÔNG có một luật `:disabled` nào
   chạm `.field input` (grep `disabled` → chỉ có `button`, `.ibtn`, `.cbtn`,
   `.btn`, `.lk-p`, `.menu button`, `.cd-sw input`). Nghĩa là đặt
   `input.disabled = true` thì ô trông Y HỆT ô đang dùng được: người dùng bấm
   vào, gõ không lên chữ, và ⛔ không có gì giải thích. Đó đúng lớp bọ "NÚT GIẢ"
   — có phản hồi giả, ⛔ không có sự thật. Vá kèm ngay tầng CSS, ⛔ không để mã
   bật cờ mà mắt ⛔ không thấy. */
.field input:disabled, .field select:disabled {
  opacity: .45; cursor: not-allowed;
  background: var(--surface); color: var(--ink-3);
}
.field.tat label { color: var(--ink-3); }
/* ba ô thiết bị nằm ngang trên máy rộng — giữ thẻ tiền sảnh lọt màn 720px cao */
.grid3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.grid3 .field { margin-bottom: 12px; min-width: 0; }
.grid3 select { padding: 9px 8px; font-size: 13px; }
/* ⛔ 🔴 VÁ 06/08 (anh Triệu: *"menu Micro, Camera, Loa chữ đang bị đè ô"*).
   BA NGUYÊN NHÂN cộng lại, phải xử cả ba chứ vá một là còn:
     ① `padding-right: 30px` của `.field select` bị dòng `.grid3 select` GHI ĐÈ
        (`padding: 9px 8px`) ⇒ chữ chạy thẳng xuống dưới mũi tên;
     ② tên thiết bị thật rất dài ("Mặc định của máy · MacBook Pro Microphone")
        mà `<select>` KHÔNG tự cắt bằng "…" nếu không cho `text-overflow`;
     ③ ba cột ép nhau tới 760px mới xuống dòng — ở 1000–1300px mỗi cột chỉ còn
        ~180px, hẹp hơn cả tên thiết bị ngắn nhất.
   Cách vá: giữ đủ chỗ cho mũi tên, cắt bằng "…", và xuống 1 cột sớm hơn. */
.grid3 select {
  padding: 9px 30px 9px 10px;      /* 30px bên phải = chỗ của mũi tên tự vẽ */
  text-overflow: ellipsis;          /* tên dài cắt bằng "…" thay vì đè mũi tên */
  overflow: hidden; white-space: nowrap;
  line-height: 1.35;
}
.grid3 .field label {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;   /* nhãn cũng không tràn */
}
/* 1024px trở xuống: ba cột quá hẹp cho tên thiết bị ⇒ xếp DỌC cho dễ đọc.
   1025–1400px: giữ ba cột nhưng chữ nhỏ hơn một bậc để tên vừa khung. */
@media (max-width: 1024px) { .grid3 { grid-template-columns: 1fr; gap: 0; } }
@media (min-width: 1025px) and (max-width: 1400px) {
  .grid3 select { font-size: 12px; padding-right: 28px; }
}

/* khối gấp lại (chìa chủ họp) — đỡ chiều cao khi không dùng tới */
.det { border-top: 1px solid var(--line); margin: 4px 0 12px; }
.det > summary {
  cursor: pointer; padding: 10px 2px; font-size: 12px; color: var(--ink-2);
  list-style: none; display: flex; align-items: center; gap: 6px;
}
.det > summary::-webkit-details-marker { display: none; }
.det > summary::before { content: '+'; color: var(--gold); font-weight: 700; }
.det[open] > summary::before { content: '−'; }

.err-box {
  display: none; gap: 9px; align-items: flex-start;
  background: color-mix(in srgb, var(--danger) 20%, var(--navy-deep));
  border: 1px solid color-mix(in srgb, var(--danger) 50%, transparent);
  border-radius: 9px; padding: 11px; font-size: 13px; margin-top: 12px; line-height: 1.5;
}
.err-box.on { display: flex; }
.err-box .ic { color: var(--danger-chu, var(--danger)); flex: none; }

/* tiền sảnh: xem trước camera */
.prejoin { display: flex; gap: 20px; align-items: flex-start; }
.prejoin .pv { flex: 1 1 52%; min-width: 0; }
.prejoin .fm { flex: 1 1 48%; min-width: 0; }
.pv-box {
  position: relative; aspect-ratio: 16/9; width: 100%;
  background: var(--navy-deep); border-radius: var(--r); overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.pv-box video { width: 100%; height: 100%; object-fit: cover; transform: scaleX(-1); }
.pv-box .off { color: var(--ink-3); font-size: 13px; text-align: center; padding: 12px; }
.pv-box .off .ic { width: 34px; height: 34px; margin: 0 auto 8px; display: block; }
.pv-ctl { display: flex; justify-content: center; gap: 10px; margin-top: 12px; }

/* đồng hồ chờ duyệt */
.wait-ico { width: 46px; height: 46px; margin: 0 auto 12px; display: block; color: var(--gold); }

/* ───────────────────── vỉ nhỏ "bấm để bật tiếng" ──────────────────────── */
#unmute {
  display: none; position: fixed; left: 50%; top: calc(12px + var(--sat));
  transform: translateX(-50%); z-index: 60;
  background: var(--gold); color: var(--on-accent); font-weight: 700;
  padding: 11px 20px; border-radius: 999px; box-shadow: var(--shadow);
  align-items: center; gap: 8px; max-width: calc(100vw - 24px);
}
#unmute.on { display: inline-flex; }

/* ───────────────────────── vỉ thông báo ngắn ──────────────────────────── */
#toasts {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(var(--bar-h) + 14px + var(--sab)); z-index: 70;
  display: flex; flex-direction: column; gap: 8px; align-items: center;
  width: min(92vw, 440px); pointer-events: none;
}
.toast {
  background: var(--surface-raised); border: 1px solid var(--line);
  color: var(--ink); border-radius: 10px; padding: 10px 14px; font-size: 13px;
  box-shadow: var(--shadow); animation: toast-in var(--normal) both;
  display: flex; gap: 8px; align-items: flex-start; width: 100%;
}
.toast .ic { flex: none; }
.toast.ok .ic { color: var(--success); }
.toast.bad .ic { color: var(--danger-chu, var(--danger)); }
.toast.warn .ic { color: var(--warning); }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px) } to { opacity: 1; transform: none } }

/* ─────────────────────────── hộp thoại ────────────────────────────────── */
.modal {
  position: fixed; inset: 0; z-index: 80; display: none;
  align-items: center; justify-content: center; padding: 16px;
  background: rgba(0,0,0,.6);
}
.modal.on { display: flex; }
.modal .card { max-height: calc(100dvh - 32px); overflow-y: auto; }
.modal-h { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
.modal-h h2 { font-size: 17px; flex: 1; min-width: 0; }
.modal-f { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; flex-wrap: wrap; }

/* ══════════════════ hộp CÀI ĐẶT: 4 mục, cấu hình chi tiết ═══════════════ */
#uiModalCard:has(#cdBox) { max-width: 620px; }
/* ⛔⛔ VÁ 03/09/2026 — CHÍNH TÔI VỪA LÀM HỎNG CHỖ NÀY.
   Thêm thẻ thứ năm "Tài khoản" vào hàng `grid-auto-flow: column` ⇒ năm cột chia
   đều một hàng ⇒ ở 375px mỗi thẻ chỉ còn ~63px, và **"Phần cứng" với "Chức năng"
   bị cắt cụt thành "Phần c…" / "Chức n…"**. Đo được bằng `scrollWidth` của chính
   thẻ chữ, ⛔ không phải nhìn áng chừng.
   Trước bản vá của tôi bốn thẻ vừa khít — tức đây đúng là **vá làm hỏng thứ
   đang chạy tốt**, thứ anh Triệu cấm.
   ⇒ Cho hàng thẻ XUỐNG DÒNG khi hẹp: năm thẻ thành 3+2, chữ còn nguyên vẹn.
   ⛔ ĐỪNG chữa bằng cách cho cuộn ngang — thẻ trôi khỏi mép là người dùng
     ⛔ KHÔNG biết còn thẻ nữa, mà đây là chỗ duy nhất vào được nhóm Tài khoản.
   ⛔ ĐỪNG chữa bằng cách giấu chữ chỉ chừa icon — năm icon ⛔ không nhãn thì
     ⛔ không ai đoán được cái nào là cái nào. */
.cd-tabs { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 3px; margin-bottom: 12px; }
@media (max-width: 560px) {
  .cd-tabs {
    display: flex; flex-wrap: wrap;
    /* mỗi thẻ tối thiểu ~1/3 hàng ⇒ 3 thẻ/hàng, đủ chỗ cho nhãn dài nhất */
    --tk-co: calc((100% - 6px) / 3);
  }
  .cd-tab { flex: 1 1 var(--tk-co); }
}
.cd-tab {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 8px 3px; border-radius: var(--r-s); font-size: 11px; color: var(--ink-2);
  background: var(--navy-deep); min-width: 0;
}
.cd-tab > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.cd-tab:hover { color: var(--ink); }
.cd-tab[aria-selected="true"] { background: var(--surface-raised); color: var(--gold-soft); }
.cd-body { max-height: min(62dvh, 560px); overflow-y: auto; overflow-x: hidden; padding-right: 2px; }

.cd-item { border-bottom: 1px solid var(--line); padding: 11px 0; }
.cd-item:last-child { border-bottom: 0; }
.cd-lb { font-size: 13px; font-weight: 600; margin-bottom: 6px; }
.cd-gh { font-size: 11.5px; color: var(--ink-3); line-height: 1.5; margin-top: 6px; }
.cd-gh.canh { color: var(--warning); }
.cd-item select, .cd-item input[type="text"], .cd-item input[type="password"] {
  width: 100%; background: var(--navy-deep); border: 1px solid var(--line);
  border-radius: 9px; padding: 9px 10px; font-size: 13px;
}
.cd-item select { appearance: none; -webkit-appearance: none; padding-right: 30px;
  background-image: linear-gradient(45deg, transparent 49%, currentColor 50%),
                    linear-gradient(-45deg, transparent 49%, currentColor 50%);
  background-size: 6px 6px, 6px 6px;
  background-position: calc(100% - 16px) calc(50% + 1px), calc(100% - 11px) calc(50% + 1px);
  background-repeat: no-repeat; }
.cd-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.cd-kv {
  display: flex; align-items: baseline; gap: 8px; justify-content: space-between;
  font-size: 12px; padding: 3px 0; min-width: 0;
}
.cd-kv > span { color: var(--ink-3); flex: none; }
.cd-kv > b { min-width: 0; overflow-wrap: anywhere; text-align: right; }
.cd-ck { display: flex; align-items: center; gap: 8px; font-size: 13px; padding: 4px 0; cursor: pointer; }
.cd-ck input { width: 16px; height: 16px; accent-color: var(--gold); }
.cd-fn { display: flex; align-items: center; gap: 9px; }
.cd-fn .ic { flex: none; color: var(--gold-soft); }
.cd-fn .cd-lb { margin-bottom: 0; }
.cd-ok { font-size: 11.5px; color: var(--success); flex: none; }
.cd-cam { font-size: 11.5px; color: var(--warning); flex: none; text-align: right; max-width: 46%; }

/* công tắc bật/tắt */
.cd-sw { position: relative; display: inline-block; width: 40px; height: 22px; flex: none; cursor: pointer; }
.cd-sw input { position: absolute; opacity: 0; width: 0; height: 0; }
.cd-sw span {
  position: absolute; inset: 0; border-radius: 999px;
  background: var(--surface-raised); border: 1px solid var(--line);
  transition: background var(--fast);
}
.cd-sw span::after {
  content: ''; position: absolute; left: 3px; top: 3px; width: 14px; height: 14px;
  border-radius: 50%; background: var(--ink-3); transition: transform var(--fast), background var(--fast);
}
.cd-sw input:checked + span { background: var(--gold); border-color: var(--gold); }
.cd-sw input:checked + span::after { transform: translateX(18px); background: var(--on-accent); }
.cd-sw input:disabled + span { opacity: .5; cursor: not-allowed; }

/* người dùng chọn giảm chuyển động trong Cài đặt ▸ Hệ thống */
:root.sv-it-chuyendong *, :root.sv-it-chuyendong *::before, :root.sv-it-chuyendong *::after {
  animation-duration: .001ms !important; transition-duration: .001ms !important;
}

/* ═════════════════════════ ĐÁP ỨNG BỀ RỘNG ════════════════════════════ */
/* Cổng §7 mục 7 kiểm 360 · 768 · 1280 · 1920 — không trượt ngang ở đâu cả. */

/* ≤ 900px: bảng phải thành LỚP PHỦ để lưới video không bị bóp méo */
@media (max-width: 900px) {
  /* ⛔ LỖI TỰ ĐO BẮT ĐƯỢC 05/08: bảng phủ này trước đây trượt bằng cách CHẠY
     `width` 0 → 360px. Đo trên trình duyệt thật thấy chuyển động width có thể
     TREO GIỮA ĐƯỜNG (trình duyệt tiết giảm hoạt ảnh khi không vẽ) và bảng kẹt
     ở 1px — nội dung coi như mất. Nay trượt bằng `transform`: dù hoạt ảnh có bị
     tiết giảm thì bảng vẫn ở đúng một trong hai vị trí, không kẹt nửa vời. */
  #panel {
    position: fixed; top: 0; right: 0; bottom: 0; z-index: 50;
    width: min(360px, 100vw); max-width: 100%;
    padding-top: var(--sat); padding-bottom: var(--sab);
    box-shadow: var(--shadow);
    transform: translateX(101%);
    transition: transform var(--normal);
    visibility: hidden;
  }
  #panel.on { width: min(360px, 100vw); transform: none; visibility: visible; }
  .prejoin { flex-direction: column; }
  .prejoin .pv, .prejoin .fm { flex: 1 1 auto; width: 100%; }
}

/* ≤ 560px (điện thoại): thanh dưới gọn lại, nhóm cạnh không chồng nút giữa */
@media (max-width: 560px) {
  :root { --bar-h: 70px; }
  .top { padding-left: 10px; padding-right: 6px; gap: 6px; }
  .top .brand { font-size: 12px; }
  /* ⛔ LỖI TỰ ĐO BẮT ĐƯỢC 05/08 ở bề rộng 360px: thương hiệu + tên phòng + 3
     nhãn tròn + 2 nút = 398px > 360px, nên hai nút Đổi ngôn ngữ / Cài đặt bị
     ĐẨY RA NGOÀI MÀN (html có overflow-x:hidden nên không thấy thanh trượt —
     nút cứ thế mất tăm, đúng kiểu lỗi "vá nằm ở màn không ai mở tới"). Nay:
     nhãn nào chỉ để tham khảo thì ẩn ở màn hẹp, nhóm nhãn co được. */
  /* ⛔ 🔴 VÁ 05/08 vòng 15 — TRƯỚC ĐÂY `overflow: hidden` CẮT ÂM THẦM ĐÈN TRẠNG
     THÁI. Nguyên tắc: NÚT cho cuộn, ĐÈN phải GHIM (phiên App Mobile vấp đúng lớp
     lỗi này và nhắc tôi soi lại). Nay nhóm đèn KHÔNG cắt, KHÔNG co; chỗ nhường
     lấy từ TÊN PHÒNG (`.rn` vốn đã có ellipsis) và từ các đèn tham khảo. */
  .top #uiTopChips { min-width: 0; overflow: visible; flex: 0 0 auto; }
  .top .chip.den { flex: 0 0 auto; }        /* đèn trạng thái: ghim, không co */
  .top .chip.only-wide { display: none; }   /* nhãn gói: chỉ tham khảo */
}
/* màn rất hẹp: nhường thêm chỗ bằng cách bỏ đèn THAM KHẢO (đồng hồ · chất lượng
   mạng), còn đèn trạng thái thì KHÔNG ĐƯỢC bỏ.
   ⛔ ĐÍNH CHÍNH 07/08/2026 — chú thích cũ ở đây viết là hai thứ này "có ở chỗ
   khác (bảng chẩn đoán trong Công cụ)". Rà lại thì chỉ ĐÚNG MỘT NỬA:
     · chất lượng mạng: CÓ THẬT — Cài đặt ▸ Mạng ▸ Tình trạng hiện tại, và nút
       Chẩn đoán đường truyền ▸ Đo ngay.
     · thời lượng cuộc họp: KHÔNG CÓ ở đâu cả. Chuỗi uiClockTxt chỉ xuất hiện ở
       đúng ba chỗ trong hop-app.js và cả ba đều thuộc chính cái đèn này. Bảng
       "Tình trạng hiện tại" chỉ có 4 dòng (chất lượng mạng · wss · phòng ·
       số người), bảng "Chẩn đoán đường truyền" chỉ trả rtt/bitrate/lost/jitter.
   ⇒ Khách họp bằng điện thoại (mọi máy ≤430px: iPhone 15 Pro 393, đa số Android
   360-412) không có cách nào biết cuộc họp đã kéo dài bao lâu, trong khi trên
   máy tính thì có.
   ⛔ KHÔNG hiện lại đèn đồng hồ ở đây để "vá cho nhanh": phép đo ngay dưới cho
   thấy ở 360px thanh trên chỉ còn dư ~13px sau ba nhượng bộ, mà đèn đồng hồ
   dạng gọn nhất vẫn ăn ~47px ⇒ nút Cài đặt lại bị đẩy ra ngoài mép, đúng lớp
   lỗi đã phải vá một lần. Đường đúng là THÊM một dòng "thời lượng" vào bảng
   Tình trạng hiện tại — việc đó nằm ở hop-app.js (veCdMang + batDongHo), tệp
   của phiên khác; đã ghi vào báo cáo bàn giao. */
@media (max-width: 430px) {
  .top .chip.hep-an { display: none; }
  /* ⛔ ĐO THẬT ở 360px: thanh trên cần ~441px (logo 124 + đèn 110 + nhóm nút 161 +
     đệm) ⇒ THIẾU ~89px, và hệ quả là nhóm nút Ngôn ngữ/Cài đặt bị đẩy ra ngoài
     màn — đúng lớp lỗi tôi đã vá một lần ở thanh dưới. Nhường chỗ bằng cách bỏ
     HÌNH THỨC, KHÔNG bỏ CHỨC NĂNG (§9 cấm ẩn chức năng):
       · logo thu nhỏ                      −40px
       · đèn trạng thái về dạng CHỈ-BIỂU-TƯỢNG (câu đầy đủ vẫn trong tooltip) −34px
       · bỏ quả cầu TRANG TRÍ trong bộ chọn ngôn ngữ (VI · EN · ＋ vẫn còn đủ) −20px
     Tổng nhường ~94px ≥ 89px cần. Không nút nào, không đèn nào bị bỏ. */
  .top .brand img { max-width: 84px; }
  .top .chip.den .txt { display: none; }
  .top .langsw .ic.lg { display: none; }
}
/* ⛔ ĐO THẬT ở 320px (iPhone SE đời đầu / Android cũ): sau ba nhượng bộ trên thì
   đèn đủ nhưng **nút Cài đặt vẫn lọt ra ngoài mép**. Thu logo thêm một bậc — vẫn
   là logo Starview, chỉ nhỏ hơn; không bỏ nút nào. */
@media (max-width: 340px) {
  .top { padding-left: 6px; padding-right: 4px; gap: 4px; }
  .top .brand img { max-width: 52px; }
  .top .rn { font-size: 12px; }
  .top .brand img { height: 16px; max-width: 116px; }
  /* ⛔ màn hẹp: MỘT HÀNG, không wrap. Trước đây cho wrap thì 15 nút xếp thành
     3 hàng, thanh cao 165px và vẫn tràn — đo được ở 360px. Nay số nút trên thanh
     do JS giảm bớt (phần còn lại vào bảng chọn Thêm, có nhãn chữ). */
  .bar { gap: 6px; padding: 9px 6px calc(9px + var(--sab)); flex-wrap: nowrap; justify-content: center; }
  .bar .grp-l, .bar .grp-r { flex: 0 0 auto; }
  .bar .grp-l:empty, .bar .grp-r:empty { display: none; }
  .cbtn { width: 44px; height: 44px; padding: 10px; }
  .cbtn.wide { padding: 10px 14px; }
  #stage { gap: 6px; padding: 6px; }
  .card { padding: 18px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   🔴 VÁ 09/08/2026 — TRẢ LẠI HIỆU LỰC CHO CẢ KHỐI NHƯỜNG CHỖ MÀN HẸP Ở TRÊN
   ══════════════════════════════════════════════════════════════════════════
   TRIỆU CHỨNG ĐO ĐƯỢC (trong PHÒNG HỌP, khung 360px, đo trên lõi sống):
     · phòng mã 5 chữ số ("40360"): nút bánh răng #uiSettings nằm 332→372
       ⇒ **thò 12px ra ngoài màn**, chỉ còn 28/40px bấm tới được;
     · phòng tên chữ ("phong-hop-ban-giam-doc" — đúng khuôn mã mà lõi cho phép):
       bánh răng nằm **451→491 ⇒ ra ngoài 131px, bấm được 0/40px**, và bộ chọn
       ngôn ngữ cũng bay theo. `html/body` đều `overflow-x: hidden` nên KHÔNG có
       thanh trượt ngang — hai nút cứ thế biến mất, không dấu vết.

   NGUYÊN NHÂN GỐC — KHÔNG phải thiếu chỗ, mà là **CSS TRANG KHÁC ĐÈ LÊN**:
     `hop.html` nạp `hop.css` (dòng 35) rồi nạp `chat.css` (dòng 39). `chat.css`
     có BA luật viết TRẦN nhắm thẳng vào `.top` — mà thanh trên của trang họp
     cũng đúng là `.top`:
         chat.css:13  .top   { gap: 10px; padding: 8px 12px; flex: none; … }
         chat.css:23  .top   { flex-wrap: nowrap; }
         chat.css:24  .top > * { flex: none; }          ← thủ phạm chính
     Cùng độ đặc hiệu (một lớp) mà nạp SAU ⇒ chat.css thắng. Hậu quả:
       ① mọi nhượng bộ `.top { padding…; gap… }` trong hai khối `@media` ở trên
          (≤560px và ≤340px) **chưa bao giờ chạy** — đo được: ở 360px thanh vẫn
          giữ `padding 12px` và `gap 10px` của chat, không phải 10/6 và 6px;
       ② `.top > * { flex: none }` cướp mất quyền CO của tên phòng `.rn`. `.rn`
          được thiết kế để co và cắt bằng "…" (`min-width:0; overflow:hidden;
          text-overflow:ellipsis`) — ba thuộc tính ấy VÔ NGHĨA khi `flex-shrink:0`.
          Thanh hết chỗ thì thay vì tên phòng ngắn lại, cả nhóm nút bị đẩy ra ngoài.

   ⚠ Đây ĐÚNG lớp lỗi mà `chat.css` đã tự vá một lần cho `.mid/.ds/.tin` (xem
     chú thích chat.css ~dòng 288 "luật của cột chat ăn thẳng vào #uiMid của
     TRANG HỌP"). Vòng đó rà SÓT vì chỉ soi các luật CON (`.top .toi`,
     `.chi-nho` — trang họp không có nên vô hại) mà bỏ qua ba luật nhắm vào
     CHÍNH `.top`.

   VÌ SAO VÁ Ở ĐÂY CHỨ KHÔNG SỬA `chat.css`:
     `chat.css` là bộ mặt của trang chat (bàn giao của phiên khác); đổi `.top`
     ở đó là đổi thanh trên của trang chat — sửa một trang, hỏng một trang.
     Ở đây chỉ **nâng độ đặc hiệu**, không đổi HTML, không đụng tệp của chat, và
     chỉ giành lại đúng những thuộc tính mà khối `@media` ở trên vốn đã định đặt.

   🔴 BỘ CHỌN PHẢI HẸP — CÓ BA THẺ `.top` KHÁC NHAU, ĐỪNG GỘP:
     Cách viết "cho nặng cân" quen tay (`.top.top`) là SAI ở đây, vì `.top`
     không chỉ có một:
       · `hop.html:46`  `#app > header.top`            ← thanh họp, CHỖ CẦN VÁ
       · `hop.html:99`  `header.top#scTop`   (ngoài `#app`) — chat toàn màn ở
                                              TIỀN SẢNH, cùng trang
       · `chat.html:18` `#app > header.top#cTop`       — và `chat.html` NẠP CẢ
                                              `hop.css` (dòng 12) lẫn `chat.css`
     ⇒ luật viết rộng sẽ đổi luôn thanh trên của trang chat: đúng cái lỗi "xô
       trang khác" mà bản vá này đang đi sửa. Nên bám `#app > .top:not(#cTop)`:
       khớp DUY NHẤT thanh họp (0-2-1 > 0-1-0 nên vẫn thắng chat.css).

   ⛔ KHÔNG ĐỔI BỐ CỤC Ở BỀ RỘNG LỚN:
     · phần `padding`/`gap` nằm nguyên trong hai `@media` hẹp như cũ;
     · riêng `flex-shrink` của `.rn` đặt ở mọi bề rộng — CÓ CHỦ Ý và an toàn:
       `flex-shrink` chỉ có tác dụng KHI đã tràn. Chưa tràn thì nó không dịch
       một điểm ảnh nào (đã đo lại ở 768px và 1280px: các mốc x giữ y nguyên).
       Đặt nó trong media hẹp thì cửa sổ 561px với tên phòng dài vẫn vỡ. */
#app > .top:not(#cTop) > .rn { flex: 0 1 auto; }   /* trả quyền co + cắt "…" */

@media (max-width: 560px) {
  #app > .top:not(#cTop) { padding-left: 10px; padding-right: 6px; gap: 6px; }
}
@media (max-width: 340px) {
  #app > .top:not(#cTop) { padding-left: 6px; padding-right: 4px; gap: 4px; }
}

/* ≥ 1600px: giữ lưới không giãn quá thưa, bảng phải rộng hơn chút */
@media (min-width: 1600px) {
  :root { --panel-w: 380px; }
  #stage { gap: 10px; padding: 14px; }
}

/* nằm ngang trên điện thoại: bớt chiều cao thanh */
@media (max-height: 460px) and (orientation: landscape) {
  :root { --bar-h: 60px; }
  .bar { padding: 6px 8px calc(6px + var(--sab)); }
  .cbtn { width: 40px; height: 40px; padding: 9px; }
  .top { padding-top: calc(6px + var(--sat)); padding-bottom: 6px; }
}

/* người dùng chọn giảm chuyển động — tắt hiệu ứng */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

/* ══ Bốn đường vào họp ở tiền sảnh (vòng 14, 05/08/2026) ══════════════════════
   Lỗ vận hành: tiền sảnh cũ chỉ có một đường "gõ mã phòng". Khối này phải NỔI
   nhưng không được đẩy thẻ tiền sảnh dài quá màn 1280×720 — nên nút xếp cuộn
   ngang trên màn hẹp thay vì xuống dòng thành 4 hàng. */
.pj-loi { margin: 10px 0 4px; padding-top: 10px; border-top: 1px solid var(--line); }
.pj-loi-h { font-size: 12px; color: var(--ink-3); margin-bottom: 8px; }

/* ⛔ 🔴 11/08/2026 (chiều) — VIẾT LẠI BỐ CỤC MÀN CHỜ.
   Anh Triệu: "lệch bố cục rất xấu và ko chuyên nghiệp" · "mất hình camera test"
   · "mất 3/4 màn hình".

   NGUYÊN NHÂN LÀ BẢN VÁ SÁNG NAY CỦA CHÍNH TÔI: tách hai lối vào bằng hai KHỐI
   CÓ VIỀN nhồi hết vào cột phải, cộng thêm ghi chú và hàng công cụ ⇒ cột phải
   cao vọt, cột trái (camera 16:9 + 2 nút ≈ 300px) ngắn tũn ⇒ lệch, thẻ tràn
   khỏi màn, ô xem trước bị đẩy khuất.

   ⭐ NAY: cân hai cột bằng cách chuyển 3 ô thiết bị sang cột trái, và tách hai
   lối vào bằng NHÃN + VẠCH thay vì hai cái hộp to. Vẫn tách bạch đúng lệnh
   sáng, chỉ nhẹ đi. Mục tiêu đo được: thẻ lọt màn 1280×720 KHÔNG phải cuộn. */

/* ba ô thiết bị nằm dưới ô xem trước — đúng nghĩa, và kéo cột trái dài ra cho cân */
.pv-tb { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line); }
.pv-tb .field { margin-bottom: 9px; }
.pv-tb .field:last-child { margin-bottom: 0; }
.pv-tb select { padding: 8px 30px 8px 9px; font-size: 13px; }
.pv-tb label { font-size: 11.5px; }

/* nhãn nhẹ cho lối vào ① — thay cho khối viền nặng của bản sáng */
/* ⭐ 16/09 — dòng gợi ý dưới nhãn MÃ PHÒNG: một ô phục vụ hai việc nên
   PHẢI nói rõ nút nào làm gì, ⛔ đừng bắt người dùng đoán. */
.pj-goi { font-size: 12px; color: var(--ink-3); line-height: 1.5;
  margin: -2px 0 8px; }

/* ⭐ 16/09 anh Triệu: *"để 2 nút đó gần nhau và đều màu vàng"*.
   ⛔ Hai nút CHIA ĐÔI bề ngang (`flex: 1 1 0`) và `min-width: 0` để chữ dài
     ⛔ không đẩy nút kia tràn ra ngoài. Màn hẹp thì tự xuống hai dòng. */
.pj-doi { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.pj-doi .btn { flex: 1 1 0; min-width: 132px; margin-top: 0;
  /* ⛔ Nút cạnh nhau chỉ còn ~một nửa bề ngang ⇒ ép MỘT DÒNG, kẻo nút này cao
     69px còn nút kia 44px, nhìn như hai thứ khác loại. */
  white-space: nowrap; padding-left: 10px; padding-right: 10px; gap: 6px; }
.pj-doi .btn .ic { flex: 0 0 auto; }
.pj-doi .btn .ic svg { width: 17px; height: 17px; }

.pj-nhan { font-size: 12px; font-weight: 700; color: var(--gold);
  letter-spacing: .2px; margin: 12px 0 6px; }

/* vạch "hoặc": hai gạch ngang kẹp một chữ, không ảnh, không tài nguyên ngoài */
.pj-hoac { display: flex; align-items: center; gap: 10px; margin: 11px 2px 9px; }
.pj-hoac::before, .pj-hoac::after { content: ''; flex: 1 1 auto; height: 1px; background: var(--line); }
.pj-hoac > span { font-size: 11px; color: var(--ink-3); text-transform: uppercase; letter-spacing: .6px; }

.pj-congcu { margin: 9px 0 0; padding-top: 8px; border-top: 1px solid var(--line); }
.pj-congcu-h { font-size: 11.5px; color: var(--ink-3); margin-bottom: 7px; }
.btn.sm { padding: 7px 10px; font-size: 12px; }
.mt8 { margin-top: 8px; }
.pj-loi-b { display: flex; gap: 8px; flex-wrap: wrap; }
.pj-loi-b .btn { flex: 1 1 auto; min-width: 132px; justify-content: center; }
.pj-ds { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; max-height: 216px; overflow-y: auto; }
.pj-hop { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 9px 11px; border-radius: 10px; border: 1px solid var(--line);
  background: var(--surface-raised); color: var(--ink); cursor: pointer; font: inherit; }
.pj-hop:hover { border-color: var(--gold); }
.pj-gio { font-variant-numeric: tabular-nums; color: var(--gold); font-weight: 600; flex: 0 0 auto; }
.pj-ten { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pj-vmr { flex: 0 0 auto; font-size: 12px; color: var(--ink-3); }
.pj-11 { margin-top: 8px; }
@media (max-width: 560px) {
  .pj-loi-b { flex-wrap: nowrap; overflow-x: auto; padding-bottom: 4px; }
  .pj-loi-b .btn { flex: 0 0 auto; min-width: 128px; }
}

/* ══ Nhãn mô tả khi đưa chuột vào nút (vòng 19, 05/08/2026) ═══════════════════
   Anh Triệu: "khi đưa chuột đến các nút thì nên có text mô tả chức năng".
   Trang vốn có `title=`, nhưng `title` của trình duyệt chờ 1–2 giây mới hiện,
   chữ nhỏ, màu của hệ điều hành, và trên máy cảm ứng KHÔNG hiện bao giờ.
   Nhãn dưới đây là của chính trang: hiện sau 120ms, đúng phông + màu chủ đề.
   ⛔ Vẫn giữ `title` + `aria-label` cho bộ đọc màn hình.
   ⛔ Dùng `attr(data-tip)` nên KHÔNG nội suy HTML — chuỗi đã qua esc() ở JS. */
[data-tip] { position: relative; }
[data-tip]::after {
  content: attr(data-tip);
  position: absolute; left: 50%; bottom: calc(100% + 8px); transform: translateX(-50%);
  background: var(--surface-raised); color: var(--ink);
  border: 1px solid var(--line); border-radius: 8px;
  padding: 5px 9px; font-size: 12px; line-height: 1.35; white-space: nowrap;
  box-shadow: 0 6px 18px rgba(0,0,0,.34);
  opacity: 0; pointer-events: none; transition: opacity .12s ease .12s;
  z-index: 60;
}
[data-tip]::before {                       /* mũi nhọn chỉ vào nút */
  content: ''; position: absolute; left: 50%; bottom: calc(100% + 3px);
  transform: translateX(-50%); border: 5px solid transparent;
  border-top-color: var(--line); opacity: 0; transition: opacity .12s ease .12s; z-index: 60;
}
[data-tip]:hover::after, [data-tip]:focus-visible::after,
[data-tip]:hover::before, [data-tip]:focus-visible::before { opacity: 1; }
/* thanh dưới: nhãn phải nằm TRÊN nút (nút ở sát đáy màn) — mặc định đã vậy.
   Nút ở THANH TRÊN thì nhãn phải nằm DƯỚI, không thì bị cắt ngoài khung. */
.top [data-tip]::after { bottom: auto; top: calc(100% + 8px); }
.top [data-tip]::before { bottom: auto; top: calc(100% + 3px);
  border-top-color: transparent; border-bottom-color: var(--line); }
/* VÁ 07/08/2026 — dãy tab của bảng bên phải nằm sát mép TRÊN bảng, nhãn mô tả
   phải đổ XUỐNG chứ không thì bị cắt ra ngoài khung (cùng khuôn với .top ở
   trên). Nhãn này là của chính trang nên máy CẢM ỨNG cũng đọc được tên đầy đủ
   của tab — `title=` của trình duyệt không hiện trên màn cảm ứng.
   ⚠ Chỉ có tác dụng khi hop-app.js gắn thêm data-tip cho .tab (tệp của phiên
   khác); chưa gắn thì mấy dòng này không khớp phần tử nào, vô hại. */
.pn-tabs [data-tip]::after { bottom: auto; top: calc(100% + 8px); }
.pn-tabs [data-tip]::before { bottom: auto; top: calc(100% + 3px);
  border-top-color: transparent; border-bottom-color: var(--line); }
/* màn hẹp: nhãn dài có thể tràn mép — cho xuống dòng và giới hạn bề rộng */
@media (max-width: 560px) {
  [data-tip]::after { white-space: normal; max-width: 46vw; text-align: center; }
}

/* Trạng thái cuộc họp trong danh sách tiền sảnh — ĐỦ BA trạng thái (bài học từ
   phiên App Mobile: thiếu "đã kết thúc" thì cuộc đã xong hiện số ÂM). Màu theo
   token, không tự chọn màu. */
.pj-tt { flex: 0 0 auto; font-size: 11.5px; padding: 2px 7px; border-radius: 999px;
         border: 1px solid transparent; white-space: nowrap; }
.pj-tt.dang { color: var(--success); border-color: color-mix(in srgb, var(--success) 45%, transparent); }
.pj-tt.xong { color: var(--ink-3); border-color: color-mix(in srgb, var(--ink-3) 40%, transparent); }
.pj-tt.sap  { color: var(--gold); border-color: color-mix(in srgb, var(--gold) 45%, transparent); }
/* công cụ Cuộc họp: mỗi dòng một cuộc, ba nút Vào/Sửa/Xoá không tràn ở 360px */
.ch-tieu { font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--ink-3); margin: 12px 0 6px; }
.ch-hang { display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 8px 0; border-bottom: 1px solid var(--line); }
.ch-hang .ch-n { flex: 1; min-width: 140px; }
.ch-hang .ch-n b { display: block; font-size: 14px; }
.ch-hang .ch-n span { display: block; font-size: 12px; color: var(--ink-3); }
/* ⛔ 🔴 VÁ 09/08/2026 — Ở 360px NÚT "XOÁ" RỚT XUỐNG MỘT DÒNG RIÊNG.
   Đo trên trang thật (viewport 360, hộp thoại Cuộc họp đang MỞ): hàng rộng 282px,
   `.ch-n` chiếm 173px (min-width:140 + flex:1), còn lại 109px cho BA nút —
   "Vào" 48 + "Sửa" 49 + 2 khe 6px = 109px vừa khít, nên "Xoá" 48px bị `flex-wrap`
   đẩy hẳn xuống dòng thứ ba, dính lề trái, trông như nút của hàng bên dưới.
   Nút XOÁ là nút NGUY HIỂM NHẤT trong ba nút — để nó lạc sang hàng khác là mời
   người dùng bấm nhầm vào cuộc họp không định xoá.
   ⇒ Màn hẹp: cho khối tên chiếm TRỌN dòng đầu, ba nút xuống dòng sau và ở cạnh
     nhau. 282px thừa sức chứa 157px nút + khe (kể cả nhãn tiếng Anh
     Join/Edit/Delete dài hơn). ⛔ Không thu nhỏ nút, không ẩn nút nào (§9).
   Mốc 560px dùng lại đúng mốc màn hẹp có sẵn của tệp này, không đẻ mốc thứ hai. */
@media (max-width: 560px) {
  .ch-hang { row-gap: 8px; }
  .ch-hang .ch-n { flex: 1 1 100%; min-width: 0; }
}
/* ⭐ 09/08/2026 — hai nút lọc "Của tôi" / "Tất cả" + câu nói rõ đang lọc theo ai.
   `flex-wrap` để ở 360px câu chú thích tự xuống dòng chứ không đẩy nút ra ngoài. */
.ch-loc { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 10px 0 2px; }
.ch-loc .fh { flex: 1 1 100%; }
/* ⭐ CHẶNG 6 — dải "Tạo case" ở đầu tab Chat trong họp (chỉ chủ họp thấy) */
.case-dai { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 0 0 8px; margin-bottom: 8px; border-bottom: 1px solid var(--line); }
.case-ds { list-style: none; margin: 0 0 6px; padding: 0; max-height: 190px; overflow: auto; }
.case-ds li { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  padding: 4px 0; border-bottom: 1px solid var(--line); font-size: 13px; }

/* ═══════════ HỌP TOÀN MÀN HÌNH (yêu cầu chủ đầu tư 07/08/2026) ═══════════
   `#app` vốn đã 100dvh nên Fullscreen API tự vừa khung — phần thêm ở đây là
   DÀNH THÊM CHỖ CHO VIDEO: vào toàn màn hình mà thanh trên/dưới vẫn dày như cũ
   thì gần như không được lợi gì so với cửa sổ thường.
   ⛔ CHỈ thu gọn khoảng đệm — KHÔNG ẩn nút, KHÔNG bỏ chip nào (§9 cấm làm
      biến mất thứ đã có). Mọi lệnh vẫn bấm được y nguyên.
   Giữ safe-area (--sat/--sab) vì iPad vào toàn màn hình vẫn còn vùng khuyết. */
body.full-screen .top { padding: calc(4px + var(--sat)) 14px 4px; }
body.full-screen .bar { padding: 6px 12px calc(6px + var(--sab)); }

/* ⭐ 11/08/2026 — BÀN ĐIỀU KHIỂN CAMERA (PTZ).
   Bố cục theo mẫu chung: nút xếp giữa, khoảng cách bằng thang `--gap` đang dùng
   cho các hàng nút khác, không tự chế số riêng. */
.ptz-pad { display: flex; flex-direction: column; align-items: center; gap: 6px; margin: 6px 0 10px; }
.ptz-r { display: flex; align-items: center; justify-content: center; gap: 8px; flex-wrap: wrap; margin: 4px 0; }
.ptz-r .btn { min-width: 96px; justify-content: center; }

/* ⭐ 11/08 — bảng chọn camera cần điều khiển */
.ptz-chon { display: flex; flex-direction: column; gap: 8px; margin: 8px 0 12px; }
.ptz-chon .ptz-ai { justify-content: flex-start; gap: 10px; }

/* ⭐ 17/08/2026 — CHẤM TRẠNG THÁI EP trong danh bạ (anh Triệu: *"trên Danh Bạ EP
   cần phải có trạng thái: online/offline/busy"*). Ba màu, KHÔNG chỉ đổi chữ:
   chủ họp nhìn một cái là biết máy nào gọi được. Màu lấy đúng bộ đã dùng cho
   toast (ok/bad) để trang không sinh bảng màu thứ hai. */
.ep-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 9px;
          display: inline-block; margin-right: 8px; }
.ep-dot.online  { background: #22c55e; box-shadow: 0 0 0 3px rgba(34,197,94,.18); }
.ep-dot.busy    { background: #f59e0b; box-shadow: 0 0 0 3px rgba(245,158,11,.18); }
.ep-dot.offline { background: #6b7280; }
/* ⛔⛔ VÁ 16/09 (anh Triệu: *"2 icon gọi và xoá xếp cùng dòng"*).
   ĐO ĐƯỢC: luật xếp hàng ngang viết cho `.row .acts` (dòng ~591) nhưng thẻ
   thật trong danh bạ thiết bị là **`.ep-row`** ⇒ `.acts` ở đây ⛔ KHÔNG hề
   có `display:flex` ⇒ hai nút rơi xuống XẾP DỌC.
   ⭐ Đúng họ bọ *"vá đúng luật mà sai BỘ CHỌN"* — luật tồn tại, chỉ là ⛔
     không với tới thẻ này. ⇒ Khai thêm cho `.ep-row`, ⛔ KHÔNG sửa luật cũ
     (nó đang phục vụ `.row` chạy tốt). */
.ep-row .acts { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.ep-row .acts .ibtn { width: 36px; height: 36px; padding: 8px; }

.ep-row { display: flex; align-items: center; gap: 6px; padding: 7px 0;
          border-bottom: 1px solid rgba(255,255,255,.06); }
.ep-row .ep-nm { flex: 1 1 auto; min-width: 0; overflow: hidden;
                 text-overflow: ellipsis; white-space: nowrap; }
.ep-row .ep-why { font-size: 11px; opacity: .72; margin-left: 4px; }
.ep-lich { font-size: 11px; opacity: .62; margin: -4px 0 6px 17px;
           overflow-x: auto; white-space: nowrap; }

/* ⭐ 02/09/2026 — MỜI USER STARMEET VÀO PHÒNG (anh Triệu: *"người offline thì
   làm mờ đi, trạng thái online và offline có đèn như EP cứ thế mà phân biệt"*).
   ⛔ CỐ Ý ⛔ KHÔNG có bộ đèn thứ hai: hàng người dùng lại NGUYÊN `.ep-row` +
   `.ep-dot` ở ngay trên — một khuôn, một bảng màu. Dưới đây CHỈ thêm phần
   "làm mờ", thứ danh bạ EP ⛔ không có.
   ⛔ ĐỪNG dùng `display:none` cho người offline: anh Triệu chốt LÀM MỜ chứ
   ⛔ không ẩn — ẩn đi thì chủ họp tưởng người đó không tồn tại. */
.mu-row.mo { opacity: .45; }
/* Nút trong hàng mờ phải NHÌN LÀ BIẾT bấm không được; `opacity` của hàng cha
   một mình ⛔ chưa đủ (nút vẫn giữ con trỏ bàn tay ⇒ vẫn mời gọi bấm). */
.mu-row.mo .acts button[disabled] { cursor: not-allowed; }
/* Dòng lý do nằm RIÊNG một hàng, ⛔ không nhồi vào `.ep-why` cùng dòng tên:
   câu "đang offline — mời qua email hoặc lịch họp" dài hơn hẳn nhãn EP, nhét
   chung một dòng là bị `text-overflow: ellipsis` của `.ep-nm` cắt cụt. */
.mu-why { font-size: 11px; opacity: .72; margin: -5px 0 7px 17px;
          line-height: 1.35; }

/* ⭐ 01/09 — dấu chính danh cạnh tên người dự. Danh tính do LÕI xác minh (thẻ
   OpenID → hồ sơ Synapse) rồi KÝ vào vé LiveKit ⇒ dấu này không giả được. */
.xac-thuc { color: var(--ok, #4ade80); margin-left: 4px; vertical-align: -2px;
            display: inline-flex; }
.xac-thuc svg { width: 14px; height: 14px; }

/* ⭐ 07/09 — DẤU CHỦ HỌP. ⛔ MÀU KHÁC HẲN dấu chính danh (`--ok`, xanh lá):
   hai dấu nằm cạnh nhau trên cùng một dòng tên, cùng màu là người đọc gộp
   chúng thành một. Đây là VAI TRÒ, kia là DANH TÍNH. */
.chu-hop { color: var(--accent, #6aa9ff); margin-left: 4px; vertical-align: -2px;
           display: inline-flex; }
.chu-hop svg { width: 14px; height: 14px; }

/* ⭐ 07/09/2026 (anh Triệu: *"web làm như box cả 2 việc đó"*).
   ⛔⛔ MÀU: `--ink-3` = CHỮ PHỤ, ⛔ TUYỆT ĐỐI ⛔ KHÔNG dùng `--warning`/`--danger`.
     Câu của phiên Hộp: *tô đỏ một vị khách lương thiện là VU OAN BẰNG MÀU SẮC*.
     Người chưa xác thực ⛔ không làm gì sai — họ chỉ chưa đăng nhập.
   ⭐ `--ink-3` có mặt ở CẢ BỐN khối chủ đề (dòng 32 · 74 · 99 · 112) nên hai lớp
     này ⛔ không bị mù ở chủ đề nào — án "màu chỉ khai trong MỘT khối" đã trả giá. */
.chua-xt { color: var(--ink-3); font-size: 10.5px; margin-left: 5px; opacity: .9; }

/* Dải "tin này được lưu khi nào" — LUÔN HIỆN ở đầu tab Chat.
   ⛔ ⛔ ĐỪNG chuyển nó vào `.empty-note`: màn rỗng biến mất ngay khi có tin
     đầu tiên, tức biến mất ĐÚNG LÚC người ta bắt đầu tin vào bảng chat. */
.chat-luu-note { display: flex; align-items: center; gap: 6px; color: var(--ink-3);
                 font-size: 11.5px; line-height: 1.35; padding: 7px 10px;
                 border-bottom: 1px solid var(--line, rgba(128,128,128,.22)); }
.chat-luu-note .ic { width: 16px; height: 16px; flex: 0 0 16px; }

/* ⭐ 01/09 — DẢI TƯ CÁCH ở màn vào họp: nói thẳng đang là KHÁCH hay đã có tài
   khoản StarMeet (hai nhánh anh Triệu chốt). ⛔ Không chặn khách vào họp. */
.pj-tucach { display: flex; align-items: center; gap: 9px; padding: 7px 10px;
             border-radius: 10px; margin-bottom: 10px;
             border: 1px solid var(--vien, rgba(255,255,255,.12)); }
.pj-tucach.khach { background: rgba(255,255,255,.04); }
.pj-tucach.co-tk { background: rgba(74,222,128,.10);
                   border-color: rgba(74,222,128,.35); }
.pj-tucach .tc-noi { flex: 1 1 auto; min-width: 0; display: flex;
                     flex-direction: column; gap: 2px; }
.pj-tucach .tc-noi b { font-size: 12px; line-height: 1.25; }
.pj-tucach .tc-noi .fh { font-size: 11.5px; opacity: .72; line-height: 1.3;
                         /* ⛔ ĐỪNG `nowrap`+ellipsis ở đây: nút bên phải ăn hết
                            chỗ nên câu bị cắt thành "…xong cu…" — đọc chả hiểu
                            gì. Cho xuống 2 dòng, vẫn gọn. */
                         display: -webkit-box; -webkit-line-clamp: 2;
                         -webkit-box-orient: vertical; overflow: hidden; }
.pj-tucach .btn.sm { flex: 0 0 auto; }
.pj-donvet { text-align: center; margin: 8px 0 2px; }
.lien-ky-thuat.nho { font-size: 11px; opacity: .7; }
@media (max-width: 520px) {
  .pj-tucach { flex-wrap: wrap; }
  .pj-tucach .btn.sm { width: 100%; }
}

/* ⭐ 01/09 — hai nhánh E2EE đứng cùng hàng ở màn sảnh (QĐ Eric) */
.pj-loi-b.hai-cot { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.pj-loi-b .btn.sm.full { width: 100%; }
@media (max-width: 380px) { .pj-loi-b.hai-cot { grid-template-columns: 1fr; } }

/* ⛔ 01/09 — CHỐNG TRÀN CỘT SẢNH (lỗi anh Triệu bắt: tiêu đề + khung video bị
   cắt). Cột phải dài hơn khung thì CUỘN TRONG NÓ, ⛔ đừng để trôi ra ngoài rồi
   bị cắt cụt hai đầu. */
/* ⛔⛔ 04/09 — GỐC THẬT của *"khung vàng đang mất khung"* (anh Triệu bắt ở ô
   MÃ PHÒNG `#fRoom`). ⛔ KHÔNG phải hai tệp CSS đè nhau như sổ đã ngờ: đo lại
   987 luật của CẢ hop.css + chat.css thì **đúng 1 luật** khai `outline` khớp ô
   này — `:focus-visible` ở hop.css:199. `chat.css` vô can (luật của nó là
   `.fl input:focus`, mà `#fRoom` nằm trong `.field`, ⛔ KHÔNG có tổ tiên `.fl`).
   Bệnh là BỊ XÉN, ⛔ không phải bị đè:
     · Khối này cuộn dọc. Một trục ⛔ không `visible` thì trục kia cũng ⛔ KHÔNG
       còn `visible` được ⇒ `.fm` thành HỘP CẮT cả hai chiều.
     · Ô nhập rộng 100% nên mép TRÁI ô trùng khít mép hộp cắt (padding-left = 0).
     · Vòng focus vẽ NGOÀI viền: 2px offset + 2px nét = 4px ⇒ ⛔ hết chỗ.
   Đo 04/09 (1440×900, chủ đề office): **cắt trái 4,0px · cắt phải 0px** — bên
   phải sống sót nhờ `padding-right: 4px` vốn có. Hở đúng một cạnh nên nhìn như
   "mất khung" và như thể ô lệch khỏi khung.
   ⛔ ĐỪNG chữa bằng cách bỏ `outline`: đó là thứ DUY NHẤT chỉ chỗ con trỏ bàn
     phím — bỏ là ⛔ mất lối đi bằng phím Tab.
   ⭐ Thuốc: CHỪA CHỖ cho vòng focus ở CẢ HAI mép. 6px = 4px vòng focus + 2px dự
     phòng (mức phóng ⛔ không tròn số làm mép vừa khít rụng mất nét).
   ⭐ Vá ở KHỐI CHỨA nên MỌI ô và nút trong cột sảnh cùng được cứu, ⛔ không phải
     riêng ô mã phòng. */
.prejoin .fm { max-height: calc(100vh - 150px); overflow-y: auto;
               overflow-x: hidden;
               padding-left: 6px; padding-right: 6px;
               /* Cuộn tới ô nào thì chừa sẵn lề cho vòng focus của ô ấy, ⛔ để
                  cạnh trên/dưới ⛔ không bị xén khi đi bằng phím Tab. */
               scroll-padding: 6px; }
.prejoin .fm::-webkit-scrollbar { width: 6px; }
.prejoin .fm::-webkit-scrollbar-thumb { background: rgba(255,255,255,.18);
                                        border-radius: 3px; }

/* ⛔ 01/09 — ĐƯỜNG THOÁT BẮT BUỘC cho MỌI hộp thoại (lỗi kẹt cứng anh Triệu
   bắt). Góc trên phải, ⛔ không chồng lên nội dung. */
#uiModalCard { position: relative; }
.modal-x { position: absolute; top: 8px; right: 8px; width: 32px; height: 32px;
           display: inline-flex; align-items: center; justify-content: center;
           border: 0; border-radius: 8px; cursor: pointer; z-index: 5;
           background: rgba(255,255,255,.07); color: inherit; opacity: .75; }
.modal-x:hover { background: rgba(255,255,255,.14); opacity: 1; }
.modal-x svg { width: 18px; height: 18px; }
#uiModalCard > .modal-h { padding-right: 40px; }

/* ⭐ 01/09 — THANH ĐIỀU KHIỂN CUỘC GỌI ĐẦY ĐỦ (lỗi "ngõ cụt" anh Triệu báo).
   Hai hàng: dòng trạng thái trên, hàng nút dưới. Nút CÚP đỏ, to nhất, cuối
   hàng — ⛔ đường thoát phải luôn nhìn thấy được. */
.sc-goi-thanh { display: flex; flex-direction: column; gap: 10px;
                padding: 12px 14px; }
.sc-goi-tt { display: flex; align-items: center; gap: 8px; min-width: 0; }
.sc-goi-tt .txt { flex: 1 1 auto; min-width: 0; overflow: hidden;
                  text-overflow: ellipsis; white-space: nowrap; }
.sc-goi-nut { display: flex; align-items: center; justify-content: center;
              gap: 10px; flex-wrap: wrap; }
.sc-goi-nut .ib { width: 44px; height: 44px; border-radius: 50%;
                  background: rgba(255,255,255,.10); }
.sc-goi-nut .ib:hover { background: rgba(255,255,255,.18); }
.sc-goi-nut .btn.dgr { border-radius: 22px; padding: 0 18px; height: 44px;
                       display: inline-flex; align-items: center; gap: 6px; }
@media (max-width: 420px) {
  .sc-goi-nut { gap: 7px; }
  .sc-goi-nut .ib { width: 40px; height: 40px; }
}


/* ⭐ 02/09/2026 — NÚT ĐANG CHẠY LỆNH (xem `lenhChuHop` trong hop-app.js).
   Nút bị khoá trong lúc lệnh chạy ⇒ ⛔ không bấm hai lần thành hai lệnh.
   Phải THẤY ĐƯỢC là đang chạy, ⛔ không chỉ khoá âm thầm — nút xám câm khiến
   người dùng tưởng máy treo rồi bấm loạn. */
.btn.dang-chay {
  opacity: .62;
  cursor: progress;
  position: relative;
}
.btn.dang-chay::after {
  content: '';
  position: absolute; right: 8px; top: 50%;
  width: 12px; height: 12px; margin-top: -6px;
  border: 2px solid currentColor; border-right-color: transparent;
  border-radius: 50%;
  animation: sv-xoay .7s linear infinite;
}
@keyframes sv-xoay { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .btn.dang-chay::after { animation: none; }
}


/* ⭐ 02/09/2026 — NĂM NHÓM TRONG BẢNG ĐIỀU KHIỂN CHỦ HỌP.
   Khuôn lấy từ mobile 1.1.0 để hai nhánh nhìn giống nhau:
     · đầu nhóm = nhãn CHỮ HOA nhỏ, màu vàng, kèm gạch ngang;
     · MỘT cỡ icon duy nhất trong cả bảng (trước đây web có 4 cỡ lẫn lộn);
     · mọi nút full-width — nút thêm sau tự đúng, ⛔ không phải bọc từng cái. */
.sec-h.nhom {
  margin: 18px 0 8px;
  font-size: 11px;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--vang, #e8b64c);
  display: flex;
  align-items: center;
  gap: 10px;
}
.sec-h.nhom::after {
  content: '';
  flex: 1 1 auto;
  height: 1px;
  background: currentColor;
  opacity: .28;
}
.sec-h.nhom:first-of-type { margin-top: 4px; }

/* Thân nhóm: mọi nút kéo hết bề ngang. ⛔ Dùng `stretch` của cột thay vì bọc
   từng nút trong khối riêng — nút thêm sau tự đúng mà ⛔ không phải sửa gì. */
.nhom-than {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
}
.nhom-than .btn { width: 100%; justify-content: flex-start; }
.nhom-than .ic { font-size: 20px; }        /* MỘT cỡ icon cho cả bảng */

/* VÙNG NGUY HIỂM: viền đỏ mờ để mắt dừng lại trước khi tay bấm. */
.sec-h.nhom:last-of-type { color: var(--do, #e0685f); }

/* ═══════════════════════════════════════════════════════════════════════════
   ⭐ 02/09/2026 — DANH SÁCH **MỌI** PHÒNG NHÓM (cửa `GET /meet/breakout/all`)
   ───────────────────────────────────────────────────────────────────────────
   Khối này vẽ CẢ nhóm đang mở LẪN nhóm đã đóng, nên phải nhìn là phân biệt
   được ngay — nếu không, chủ họp bấm "Chuyển" vào một nhóm đã đóng và không
   hiểu vì sao người kia biến mất khỏi cuộc họp.
   ⛔ BÀI HỌC ĐÃ TRẢ GIÁ HÔM NAY: vá đủ MÃ + CHUỖI mà QUÊN TẦNG KIỂU HIỂN THỊ
     ⇒ nút lên hình gần như VÔ HÌNH. Ba tầng phải đi cùng nhau.
   ⛔ Dùng biến màu sẵn có (`--line`, `--ink-3`, `--vang`) — ⛔ không đặt màu
     thô, kẻo lệch khỏi chủ đề sáng/tối của trang.
   ═══════════════════════════════════════════════════════════════════════════ */
.bo-nhom {
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 8px 10px;
  margin-top: 8px;
}
.bo-dau {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 4px;
}
.bo-dau .nm2 {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 13px; font-weight: 600;
}
/* Nhãn trạng thái: ĐANG MỞ = vàng nổi bật · ĐÃ ĐÓNG = xám chìm. */
.bo-tt {
  flex: none;
  font-size: 11.5px; letter-spacing: .06em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 999px;
  color: var(--vang, #e8b64c);
  border: 1px solid currentColor;
  opacity: .9;
}
.bo-tt.dong { color: var(--ink-3); opacity: .75; }
/* Nhóm đã đóng chìm hẳn xuống — ⛔ nhưng ⛔ KHÔNG `display:none`: chủ họp vẫn
   cần TRA LẠI ai từng ngồi nhóm nào, đó chính là lý do cửa `/all` tồn tại. */
.bo-nhom.da-dong { opacity: .62; }
.bo-trong { font-size: 11.5px; color: var(--ink-3); padding: 4px 6px; }

/* ═══════════════════════════════════════════════════════════════════════
   CÔNG TẮC DẠNG HÀNG — 02/09/2026 (bố cục phiên Mobile chốt, anh Triệu duyệt)
   ⛔ TẦNG NÀY TỪNG BỊ QUÊN: một lượt trước tôi đẩy xong mã + chuỗi ngôn ngữ
   nhưng ⛔ KHÔNG viết kiểu, nên ba lớp mới **gần như vô hình trên nền navy** mà
   mọi cổng canh vẫn xanh — mã chạy, chuỗi đủ, chỉ là ⛔ không ai nhìn thấy.
   ⇒ Đếm đủ BA TẦNG: mã · chuỗi · KIỂU.
   ⛔ Dùng biến màu (`--surface-raised`, `--success`…) chứ ⛔ không viết mã màu
   thẳng: bảng màu có hai bộ (sáng/tối) khai ở đầu tệp, viết cứng là hỏng một bộ.
   ═══════════════════════════════════════════════════════════════════════ */
.ct-row {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 10px; border-radius: 10px; min-width: 0;
  background: var(--surface-raised); border: 1px solid var(--line);
}
.ct-row .ct-nhan {
  flex: 1; min-width: 0; font-size: 13px; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Ô bấm 44×26 — chiều cao vùng chạm thực tế lấy thêm từ padding của hàng,
   đủ ngưỡng 44pt iOS đã chốt ở bản vá 07/08 cho các nút nguy hiểm. */
.ct-sw {
  position: relative; flex: 0 0 44px; width: 44px; height: 26px; padding: 0;
  border-radius: 999px; cursor: pointer;
  border: 1px solid var(--line); background: rgba(128,128,128,.28);
  transition: background .15s ease, border-color .15s ease;
}
.ct-sw .ct-nub {
  position: absolute; top: 2px; left: 2px; width: 20px; height: 20px;
  border-radius: 50%; background: var(--ink-2);
  transition: left .15s ease, background .15s ease;
}
.ct-sw.on { background: var(--success); border-color: var(--success); }
.ct-sw.on .ct-nub { left: 20px; background: #fff; }
/* Hàng NGUY HIỂM (đóng băng): bật lên thì đỏ, để nhìn là biết phòng đang bị
   khoá cứng — cùng ý đồ với nút `dgr` của bản cũ. */
.ct-row.ct-nguy .ct-sw.on { background: var(--danger); border-color: var(--danger-chu, var(--danger)); }
.ct-sw:disabled { opacity: .5; cursor: not-allowed; }
.ct-sw:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

/* Hàng "ĐI TIẾP" — nửa còn lại của luật hình: chevron = đi tiếp.
   ⛔ Chevron cố ý MỜ HƠN công tắc: nó chỉ mở ra một danh sách, ⛔ không đổi
   trạng thái phòng. Hai việc khác hạng thì ⛔ không được nổi bật như nhau. */
.ct-row.ct-di { cursor: pointer; }
.ct-row.ct-di:hover { border-color: var(--ink-3); }
.ct-chev {
  flex: 0 0 30px; width: 30px; height: 30px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: none; background: none; cursor: pointer;
  color: var(--ink-3); border-radius: 8px;
  transition: color .15s ease, background .15s ease;
}
.ct-row.ct-di:hover .ct-chev { color: var(--ink); background: rgba(128,128,128,.16); }
.ct-chev:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

/* Dải huy hiệu CHÍNH SÁCH PHÒNG — 02/09/2026 (app có, web thiếu).
   ⛔ Đây là thứ để ĐỌC, ⛔ không phải để bấm: ⛔ không con trỏ tay, ⛔ không hiệu
   ứng rê chuột. Bày một thứ trông bấm được mà bấm ⛔ không ra gì là nút giả. */
.cs-dai { display: flex; flex-wrap: wrap; gap: 6px; margin: 2px 0 4px; }
.cs-hh {
  font-size: 11.5px; line-height: 1.5; padding: 2px 9px; border-radius: 999px;
  color: var(--ink-3); border: 1px solid var(--line); background: transparent;
  white-space: nowrap;
}
/* Trạng thái ĐANG BẬT nổi hơn — mắt lướt qua là biết phòng đang đặt thế nào. */
.cs-hh.on { color: var(--vang); border-color: color-mix(in srgb, var(--vang) 45%, transparent); }

/* Dòng phụ dưới nhãn công tắc — 02/09/2026 (app có, web thiếu).
   ⛔ Chú thích nổi (`title=`) KHÔNG thay được dòng phụ: màn cảm ứng ⛔ không có
   con trỏ để rê, nên câu nằm trong `title` là câu người dùng điện thoại ⛔ KHÔNG
   BAO GIỜ đọc được. Câu nào cần đọc thì phải nằm trên màn. */
.ct-row .ct-doc { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.ct-row .ct-doc .ct-nhan { flex: none; }
.ct-row .ct-phu {
  font-size: 11.5px; line-height: 1.35; color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Hàng có dòng phụ cao hơn ⇒ nới đệm dọc cho cân, ⛔ đừng để chữ dính mép. */
.ct-row.ct-co-phu { padding-top: 7px; padding-bottom: 7px; }

/* ── DẢI "PHÒNG GẦN ĐÂY" ở tiền sảnh — 03/09/2026 ──
   ⛔ Dải này nằm ngay dưới ô mã phòng nên PHẢI xuống dòng được: năm mã phòng
   dài (`hop-ky-thuat-thu-4`) trên màn 360px mà ⛔ không wrap là đẩy tràn cả cột.
   Mọi màu lấy từ token ⇒ theo đúng cả sáu chủ đề, kể cả chủ đề Sáng. */
.pgd { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: -2px 0 10px; }
.pgd-nhan { font-size: var(--fs-caption); color: var(--ink-3); flex: none; }
.pgd-chip {
  font-size: var(--fs-caption); font-weight: 600; color: var(--ink-2);
  background: var(--surface-raised); border: 1px solid var(--line);
  border-radius: 999px; padding: 3px 10px; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  transition: background var(--fast), color var(--fast), border-color var(--fast);
}
.pgd-chip:hover { color: var(--ink); border-color: var(--gold); }
.pgd-xoa { color: var(--ink-3); padding: 3px 5px; border-radius: 6px; flex: none; }
.pgd-xoa:hover { color: var(--danger-chu, var(--danger)); }

/* ══════ MÀN TÀI KHOẢN + CỔNG ĐIỀU KHOẢN (sv-taikhoan.js) — 03/09/2026 ══════
   ⛔ Tệp JS đó CỐ Ý ⛔ không mượn hộp thoại của hop-app.js, vì `chat.html`
   ⛔ không nạp hop-app.js. Nên lớp phủ có kiểu dáng riêng ở đây — nhưng MỌI
   màu đều lấy từ token, nên nó theo đúng cả sáu chủ đề kể cả chủ đề Sáng. */
.tk-phu {
  position: fixed; inset: 0; z-index: 9000; display: flex;
  align-items: center; justify-content: center; padding: 18px;
  background: rgba(0,0,0,.62); overflow: auto;
}
.tk-hop {
  background: var(--surface); border: 1px solid var(--line); border-radius: 12px;
  max-width: 640px; width: 100%; padding: 22px; max-height: 88vh;
  overflow: auto; color: var(--ink);
}
.tk-dau { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.tk-dau h2 { margin: 0; font-size: var(--fs-h1); }
.tk-hint { color: var(--ink-3); font-size: var(--fs-caption); margin: 0 0 14px; }
.tk-vb {
  border: 1px solid var(--line); border-radius: 8px;
  margin-bottom: 12px; overflow: hidden;
}
.tk-vb-dau {
  display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
  padding: 9px 12px; background: var(--surface-raised); font-size: var(--fs-body);
}
.tk-ver { font-size: var(--fs-caption); color: var(--ink-3); flex: none; }
/* ⛔ Văn bản pháp lý có thể rất dài. Cho cuộn TRONG khối, ⛔ đừng để nó đẩy nút
   Đồng ý ra ngoài màn — người dùng ⛔ không tìm thấy nút là cổng thành ngõ cụt. */
.tk-vb-than {
  padding: 12px; max-height: 34vh; overflow: auto;
  font-size: var(--fs-caption); line-height: 1.6; color: var(--ink-2);
  white-space: pre-wrap;
}
.tk-ck { display: flex; align-items: center; gap: 8px; margin: 4px 0 12px; font-size: var(--fs-body); }
.tk-chan { display: flex; gap: 8px; align-items: center; }
.tk-chan .full { flex: 1; }
.tk-ra:empty { display: none; }
.tk-loi {
  background: rgba(224,90,78,.12); border: 1px solid var(--danger);
  color: var(--danger-chu, var(--danger)); border-radius: 8px; padding: 8px 11px;
  font-size: var(--fs-caption); margin-bottom: 10px;
}
.tk-chuamo {
  border: 1px dashed var(--line); border-radius: 8px;
  padding: 13px 14px; margin: 10px 0; background: var(--surface-raised);
}
.tk-chuamo-dau { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; }
.tk-chuamo p { margin: 0 0 8px; color: var(--ink-3); font-size: var(--fs-caption); line-height: 1.55; }
.tk-cua {
  display: block; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11.5px; color: var(--ink-3); background: var(--navy-deep);
  border: 1px solid var(--line); border-radius: 6px; padding: 7px 9px;
  overflow-x: auto; white-space: nowrap;
}

/* ── Nhóm màn TÀI KHOẢN trong thẻ Cài đặt ▸ Tài khoản — 03/09/2026 ── */
.tk-dang-nap { display: flex; align-items: center; gap: 8px; color: var(--ink-3);
  font-size: var(--fs-caption); padding: 14px 0; }
.tk-muc { padding: 14px 0; border-bottom: 1px solid var(--line); }
.tk-muc:last-child { border-bottom: 0; }
.tk-lb { font-size: var(--fs-caption); font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-3); margin-bottom: 9px; }
.tk-chao { display: flex; flex-direction: column; gap: 3px; padding: 12px 14px;
  background: var(--surface-raised); border: 1px solid var(--line); border-radius: 8px; }
.tk-chao b { font-size: var(--fs-h2); }
.tk-chao span { color: var(--ink-2); font-size: var(--fs-body); }
.tk-nguon { color: var(--ink-3); font-size: 11.5px; font-style: normal; margin-top: 3px; }
.tk-kv { display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  padding: 6px 0; font-size: var(--fs-body); }
.tk-kv span { color: var(--ink-3); }
.tk-hh { font-size: var(--fs-caption); font-weight: 700; padding: 2px 9px; border-radius: 999px; }
.tk-hh.ok   { color: var(--success); background: rgba(61,190,123,.14); }
.tk-hh.chua { color: var(--warning); background: rgba(224,169,62,.14); }
.tk-hh.bo   { color: var(--ink-3);   background: var(--surface-raised); }
/* ⛔ Ô nhập PHẢI co được: ba ô trên một hàng ở màn 360px là tràn ngay.
   `min-width:0` là thứ bắt buộc trong flex/grid — thiếu nó thì ô ⛔ không chịu
   nhỏ hơn nội dung và đẩy vỡ cả cột. */
.tk-dong { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; flex-wrap: wrap; }
.tk-dong input { flex: 1 1 160px; min-width: 0; }
.tk-luoi { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 8px; margin-bottom: 9px; }
.tk-luoi input { min-width: 0; }
/* ⭐ 03/09/2026 — ô đăng nhập bằng mật khẩu trong hộp QR của màn HỌP.
   Đứng TRÊN khối "chưa có tài khoản": người cầm sẵn mật khẩu trong thư là
   trường hợp phổ biến hơn người cần đăng ký mới, nên nó phải gặp trước. */
.qr-mk { margin: 14px 0 4px; padding-top: 12px; border-top: 1px solid var(--line); }
.qr-mk .fl { display: block; margin-bottom: 8px; font-size: var(--fs-caption); color: var(--ink-3); }
.qr-mk .fl input {
  display: block; width: 100%; margin-top: 4px; padding: 8px 10px;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--surface); color: var(--ink); font-size: var(--fs-body);
}
.qr-mk .fl input:focus { outline: 2px solid var(--gold); outline-offset: 1px; }
.qr-mk-ra { margin-top: 7px; font-size: var(--fs-caption); min-height: 1em; }
.qr-mk-ra.tot { color: var(--success); }
.qr-mk-ra.xau { color: var(--danger-chu, var(--danger)); }
.kk-quen {
  display: flex; gap: 12px; align-items: center; justify-content: space-between;
  margin-top: 12px; padding: 10px 12px; border-radius: 10px;
  background: var(--surface-raised); border: 1px solid var(--line);
}
.kk-quen b { display: block; font-size: var(--fs-caption); color: var(--ink); }
.kk-quen p { margin: 2px 0 0; font-size: 11.5px; color: var(--ink-3); line-height: 1.45; }
.kk-quen > button { flex: 0 0 auto; }
/* ⭐ 03/09/2026 — danh sách THIẾT BỊ trong tab Tài khoản.
   Máy ĐANG DÙNG được đánh dấu và ⛔ KHÔNG có nút đăng xuất: tự đá mình ra
   giữa cuộc họp là một cú bấm nhầm ⛔ không ai cứu được. */
.tb-hang {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
.tb-hang:last-of-type { border-bottom: 0; }
.tb-nay { opacity: .95; }
.tb-chu { min-width: 0; }
.tb-chu b { display: block; font-size: var(--fs-body); font-weight: 500; }
.tb-chu em {
  display: block; font-style: normal; font-size: 11.5px; color: var(--ink-3);
  margin-top: 1px; font-family: ui-monospace, Menlo, monospace;
}
.chip.c-dat-tk {
  font-size: 11px; font-weight: 600; padding: 3px 9px; border-radius: 999px;
  white-space: nowrap; background: rgba(61,190,123,.12);
  color: var(--success); border: 1px solid var(--success);
}
/* ⭐ 03/09/2026 — TẤM MỜI KHÁCH VÀO PHÒNG (hợp đồng phiên Mobile).
   Ba nhóm, thứ tự CÓ CHỦ Ý: trong StarMeet → chép để dán → gửi ra ngoài.
   Nhóm cuối tuồn số phòng ra ngoài hệ thống, nên có dải cảnh báo khép tấm. */
.mv-so {
  display: block; width: 100%; text-align: center; cursor: pointer;
  background: var(--surface-raised); border: 1px solid var(--line);
  border-radius: 12px; padding: 12px 14px 14px; margin: 4px 0 6px;
}
.mv-so:hover { border-color: var(--gold); }
.mv-so:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.mv-so-nhan {
  display: block; font-size: var(--fs-caption); color: var(--ink-3);
  letter-spacing: .06em; text-transform: uppercase; margin-bottom: 3px;
}
/* Chữ to nhất tấm — người dự có thể ngồi trước EP One và gõ bằng remote. */
.mv-so-gt {
  display: block; font-size: 30px; font-weight: 800; letter-spacing: .14em;
  color: var(--ink); font-variant-numeric: tabular-nums;
}
.mv-nhom {
  font-size: 11px; letter-spacing: .11em; text-transform: uppercase;
  color: var(--ink-3); font-weight: 600; margin: 15px 0 6px;
}
.mv-goi { margin: 0 0 8px; }
.mv-goi b { display: block; font-size: var(--fs-body); color: var(--ink); }
.mv-goi em { display: block; font-style: normal; font-size: var(--fs-caption); color: var(--ink-3); margin-top: 2px; }
.mv-hang {
  display: flex; align-items: center; gap: 11px; width: 100%; cursor: pointer;
  text-align: left; background: none; border: 1px solid var(--line);
  border-radius: 10px; padding: 10px 12px; margin-bottom: 6px; color: var(--ink);
}
.mv-hang:hover { border-color: var(--gold); background: var(--surface-raised); }
.mv-hang:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.mv-hang > .ic { flex: 0 0 auto; color: var(--gold); }
.mv-chu { min-width: 0; }
.mv-chu b { display: block; font-size: var(--fs-body); font-weight: 500; }
.mv-chu em { display: block; font-style: normal; font-size: var(--fs-caption); color: var(--ink-3); margin-top: 1px; }
/* Cảnh báo KHÉP TẤM — nói hệ quả TRƯỚC khi người ta dán, ⛔ không phải sau. */
.mv-canh {
  display: flex; gap: 9px; align-items: flex-start; margin-top: 14px;
  padding: 10px 12px; border-radius: 10px;
  background: rgba(224,169,62,.12); border: 1px solid var(--warning);
  font-size: var(--fs-caption); color: var(--ink-2); line-height: 1.5;
}
.mv-canh > .ic { color: var(--warning); flex: 0 0 auto; margin-top: 1px; }
/* Kết quả gửi thư mời — ⛔ hiện ĐÚNG số lõi trả về, ⛔ không làm tròn. */
.mv-ra-tot, .mv-ra-xau {
  margin-top: 9px; padding: 9px 11px; border-radius: 9px;
  font-size: var(--fs-caption); border: 1px solid;
}
.mv-ra-tot { background: rgba(61,190,123,.12); border-color: var(--success); color: var(--success); }
.mv-ra-xau { background: rgba(224,169,62,.12); border-color: var(--warning); color: var(--warning); }
/* ⭐ 03/09/2026 — MÀN HOÀN TẤT ĐĂNG KÝ: nơi DUY NHẤT trên đời còn giữ mật
   khẩu tạm và cụm ký tự khoá khi thư ⛔ không tới. Nên nó phải đọc được, chép
   được, và ⛔ KHÔNG cho đi tiếp trước khi người dùng xác nhận đã lưu. */
.dk-nhac {
  display: flex; gap: 9px; align-items: flex-start;
  padding: 10px 12px; margin: 4px 0 12px; border-radius: 9px;
  background: var(--surface-raised); border-left: 3px solid var(--gold);
  font-size: var(--fs-caption); color: var(--ink-2); line-height: 1.5;
}
.dk-nhac > .ic { color: var(--gold); flex: 0 0 auto; margin-top: 1px; }
.dk-the {
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface-raised); padding: 4px 12px; margin: 10px 0;
}
.dk-bm {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
.dk-bm:last-child { border-bottom: 0; }
.dk-bm > span { flex: 0 0 40%; font-size: var(--fs-caption); color: var(--ink-3); }
/* ⛔ `user-select:all` để bấm MỘT lần là bôi đen cả chuỗi — trang mở qua
   `http://` ⛔ KHÔNG có clipboard, chép tay là đường lùi duy nhất. */
.dk-bm > code {
  flex: 1 1 auto; min-width: 0; user-select: all; word-break: break-all;
  font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
  font-size: 13.5px; color: var(--ink); letter-spacing: .02em;
}
.dk-chep {
  flex: 0 0 auto; background: none; border: 1px solid var(--line);
  border-radius: 7px; padding: 4px 7px; cursor: pointer; color: var(--ink-2);
  display: inline-flex; align-items: center;
}
.dk-chep:hover { color: var(--ink); border-color: var(--gold); }
.dk-chep:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.dk-nguy {
  display: flex; gap: 10px; margin-top: 12px; padding: 11px 12px;
  border: 1px solid var(--danger); border-radius: 10px;
  background: rgba(224,90,78,.08);
}
.dk-nguy > .ic { color: var(--danger-chu, var(--danger)); flex: 0 0 auto; }
.dk-nguy b { color: var(--danger-chu, var(--danger)); display: block; margin-bottom: 4px; }
.dk-nguy p { margin: 0; font-size: var(--fs-caption); color: var(--ink-2); }
.dk-tick {
  display: flex; align-items: center; gap: 9px; margin: 12px 0 2px;
  font-size: var(--fs-caption); color: var(--ink); cursor: pointer;
}
.dk-tick input { width: 17px; height: 17px; accent-color: var(--gold); cursor: pointer; }
/* ⭐ 03/09/2026 — KHỐI KHOÁ KÝ trong menu Tài khoản.
   Mỗi dòng là MỘT phép đo riêng, ⛔ không gộp thành một chữ "ổn": đúng cái
   gộp ấy khiến một tài khoản `crossSigning=true / khoTuMat=false` được báo
   là SẴN SÀNG trong khi nó sắp mất sạch tin cũ. */
.kk-chot {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 12px; border-radius: 10px; margin: 8px 0 10px;
  font-size: var(--fs-body); border: 1px solid var(--line);
}
.kk-chot-du     { background: rgba(61,190,123,.12); border-color: var(--success); color: var(--success); }
.kk-chot-thieu  { background: rgba(224,169,62,.12); border-color: var(--warning); color: var(--warning); }
.kk-hang {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; padding: 6px 2px; border-bottom: 1px solid var(--line);
  font-size: var(--fs-caption);
}
.kk-hang:last-of-type { border-bottom: 0; }
.kk-hang > span { color: var(--ink-3); }
.kk-hang > b    { text-align: right; }
.kk-xanh { color: var(--success); }
.kk-do   { color: var(--warning); }
/* Đường PHÁ HUỶ để riêng trong khung đỏ — ⛔ đừng xếp cạnh hai nút lành,
   nút đặt lại bấm nhầm là mất toàn bộ tin đã mã hoá. */
.kk-nguy {
  display: flex; gap: 10px; margin-top: 14px; padding: 11px 12px;
  border: 1px solid var(--danger); border-radius: 10px;
  background: rgba(224,90,78,.08);
}
.kk-nguy > .ic { color: var(--danger-chu, var(--danger)); flex: 0 0 auto; }
.kk-nguy b { color: var(--danger-chu, var(--danger)); display: block; margin-bottom: 4px; }
.kk-nguy p { margin: 0 0 8px; font-size: var(--fs-caption); color: var(--ink-2); }
/* ⭐ 03/09/2026 — HAI LỐI RẼ Ở MÀN ĐĂNG NHẬP CHAT + màn quên mật khẩu.
   ⛔ Tên lớp mở đầu `dn-`/`qm-`: hop.css VÀ chat.css cùng nạp trên chat.html,
   nên tên trần (kiểu `.lien`) là mời một vụ đụng tên nữa — đã dính một lần
   hôm 03/09 với `.tk-o` (chat.css dùng nó làm ô tìm kiếm dính mép). */
.dn-loi-re {
  display: flex; align-items: center; justify-content: center;
  gap: 8px; margin-top: 12px; flex-wrap: wrap;
}
.dn-lien {
  background: none; border: 0; padding: 2px 4px; cursor: pointer;
  color: var(--gold); font: inherit; font-size: var(--fs-caption);
  text-decoration: underline; text-underline-offset: 3px;
  border-radius: 6px;
}
.dn-lien:hover { color: var(--ink); }
.dn-lien:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.dn-cach { color: var(--ink-3); font-size: var(--fs-caption); user-select: none; }
/* Vạch ngăn bước ②: người dùng phải bấm liên kết trong THƯ trước — khối này
   chỉ hiện SAU khi thư đã gửi, và nói đúng việc phải làm. */
.qm-buoc2 {
  margin: 14px 0 10px; padding: 9px 11px; border-radius: 8px;
  background: var(--surface-raised);
  border-left: 3px solid var(--gold);
  font-size: var(--fs-caption); color: var(--ink);
}
.tk-ok {
  background: rgba(61,190,123,.12); border: 1px solid var(--success);
  color: var(--success); border-radius: 8px; padding: 8px 11px;
  font-size: var(--fs-caption); margin-top: 8px;
}
.tk-muc .tk-loi { margin-top: 8px; margin-bottom: 0; }

/* ── THANH ĐI LẠI GIỮA CÁC TRANG (sv-taikhoan.js) — 03/09/2026 ──
   ⛔ Nó là ANH EM của header.top chứ ⛔ không nằm trong, vì chat.html vẽ lại
   ruột thanh đỉnh mỗi lần đổi trạng thái. */
.tk-dilai {
  display: flex; gap: 4px; align-items: center;
  padding: 5px 10px; background: var(--navy-dark);
  border-bottom: 1px solid var(--line); flex: none;
  overflow-x: auto; scrollbar-width: none;
}
.tk-dilai::-webkit-scrollbar { display: none; }
.tk-dilai[hidden] { display: none; }
.tk-dl {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  padding: 5px 12px; border-radius: 999px; text-decoration: none;
  font-size: var(--fs-caption); font-weight: 600; color: var(--ink-3);
  transition: background var(--fast), color var(--fast);
}
.tk-dl:hover { color: var(--ink); background: var(--surface-raised); }
/* ⛔ Trang ĐANG MỞ phải khác hẳn, ⛔ không chỉ đậm hơn một chút: người dùng
   phải biết mình đang ở đâu mà ⛔ không cần đọc thanh địa chỉ. */
.tk-dl.on { color: var(--on-accent); background: var(--gold); }
.tk-dl.on:hover { color: var(--on-accent); background: var(--gold); }

/* ── Danh sách "mời qua Chat E2EE" trong hộp Mời — 03/09/2026 ── */
.iv-ds { display: flex; flex-direction: column; gap: 5px; max-height: 30vh; overflow: auto; }
.iv-hang {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 6px 10px; background: var(--surface-raised);
  border: 1px solid var(--line); border-radius: 8px;
}
/* ⛔ min-width:0 bắt buộc: tên dài ⛔ không chịu co thì nút Mời bị đẩy ra ngoài */
.iv-ten { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-body); }
.iv-hang .btn { flex: none; }
.iv-hang .btn.on { background: var(--success); color: var(--on-accent); border-color: var(--success); }

/* ══ Ô QR ĐĂNG NHẬP thiết kế lại + MÀN ĐĂNG KÝ — 03/09/2026 ══ */
/* ⛔ Thẻ hộp thoại chung bị ghim `max-width: 460px` — đo được: cột phải của ô
   QR chỉ còn **146px**, nút "Đăng ký tài khoản" vỡ làm hai dòng và ba bước
   hướng dẫn bị bóp thành cột chữ hẹp khó đọc.
   ⇒ Nới RIÊNG hộp thoại NÀO CÓ ô QR, bằng `:has()` (đã đo trình duyệt hỗ trợ).
   ⛔ ĐỪNG nới `.card` toàn cục: hai chục hộp thoại khác đang vừa vặn ở 460px,
   nới hết là làm hỏng cả loạt thứ đang chạy tốt. */
.modal:has(.qr-khung) .card { max-width: 660px; }
.qr-khung { display: grid; grid-template-columns: auto 1fr; gap: 20px; align-items: start; margin: 6px 0 4px; }
/* ⛔ Màn hẹp: XUỐNG DÒNG. Hai cột ở 360px là mã QR co lại còn ~120px — quét
   không nổi, mà quét được mới là cả điểm của cái ô này. */
@media (max-width: 560px) { .qr-khung { grid-template-columns: 1fr; } }
.qr-trai { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.qr-o {
  width: 208px; height: 208px; display: flex; align-items: center; justify-content: center;
  background: #fff; border-radius: 12px; padding: 10px; box-sizing: border-box; flex: none;
}
.qr-o img { width: 100%; height: 100%; display: block; }
.qr-han { font-size: var(--fs-caption); color: var(--ink-3); text-align: center; min-height: 1.4em; }
.qr-buoc { margin: 0 0 14px; padding-left: 1.15em; display: flex; flex-direction: column; gap: 7px; }
.qr-buoc li { font-size: var(--fs-body); color: var(--ink-2); line-height: 1.5; }
.qr-chuaco { border-top: 1px solid var(--line); padding-top: 12px; }
.qr-chuaco-h { font-size: var(--fs-caption); color: var(--ink-3); margin-bottom: 7px; }
.qr-chuaco .btn { width: 100%; }

.dk-o { margin-bottom: 11px; }
.dk-o label { display: block; font-size: var(--fs-caption); color: var(--ink-3); margin-bottom: 4px; }
.dk-o input { width: 100%; box-sizing: border-box; }
/* ⛔ Ô CHỈ ĐỌC phải TRÔNG khác ô gõ được — người dùng phải nhìn là biết
   ⛔ không gõ vào đây, ⛔ đừng để họ bấm vào rồi tưởng hỏng. */
.dk-o input[readonly] { background: var(--navy-deep); color: var(--ink-3); cursor: default; }
.tk-ghi { font-size: 11.5px; color: var(--ink-3); margin-top: 4px; line-height: 1.45; }
.tk-the-dn {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  background: var(--surface-raised); border: 1px solid var(--line);
  border-radius: 8px; padding: 10px 12px; margin-bottom: 12px;
}
.tk-the-dn span { font-size: var(--fs-caption); color: var(--ink-3); }
.tk-the-dn b { font-size: var(--fs-h2); word-break: break-all; }
.tk-the-dn em { font-size: 11.5px; color: var(--warning); font-style: normal; }

/* hàng có ô nhập + nút (đổi email, kích hoạt SĐT) */
.tk-dong { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 8px; }
.tk-dong input { flex: 1 1 150px; min-width: 0; }
.tk-dong .btn { flex: none; }
.tk-dong[hidden] { display: none; }

/* ═══════════ ⭐ LINK 3RD — hộp "Vào họp nền tảng khác" (08/09/2026) ═══════════
   Lệnh anh Triệu: *"thiết kế cho nó đẹp và chuyên nghiệp vào, nhìn app mobile
   mà làm theo"*. Bố cục chép từ màn app: mô tả → ô dán lớn → HAI nút (Nhận
   diện · Dán) → gạch ngang → danh sách nền tảng có huy hiệu màu → câu chốt.
   ⛔ Mọi màu lấy từ TOKEN của hệ, TRỪ bốn màu huy hiệu — đó là màu THƯƠNG HIỆU
     của chính bốn hãng, ⛔ không phải màu ta tự chọn. */
.l3-in {
  width: 100%; min-height: 96px; resize: vertical;
  padding: 12px 14px; margin: 4px 0 10px;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--navy-deep); color: var(--ink);
  font: inherit; font-size: 14px; line-height: 1.5;
}
.l3-in::placeholder { color: var(--ink-3); }
.l3-in:focus { outline: none; border-color: var(--gold); }
.l3-hang { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.l3-vach { height: 1px; background: var(--line); margin: 16px 0 12px; }
.l3-tieu {
  font-size: 11px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--ink-3); margin-bottom: 10px;
}
.l3-nt { display: flex; gap: 10px; margin-bottom: 12px; align-items: flex-start; }
/* Huy hiệu vuông bo góc, chữ cái đầu — đúng khuôn app. */
.l3-hh {
  flex: none; width: 22px; height: 22px; border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; color: #fff; margin-top: 1px;
}
.l3-hh.zoom  { background: #2D8CFF; }
.l3-hh.meet  { background: #00897B; }
.l3-hh.teams { background: #6264A7; }
.l3-hh.webex { background: #00BCEB; }
.l3-than { flex: 1; min-width: 0; }
.l3-ten { font-size: 15px; font-weight: 600; color: var(--ink); line-height: 1.3; }
.l3-mo { font-size: 12.5px; line-height: 1.55; color: var(--ink-2); margin-top: 3px; }
.l3-mo .nhan { color: var(--gold-soft); }
.l3-chot {
  font-size: 12.5px; line-height: 1.55; color: var(--ink-3);
  padding-top: 4px; margin-bottom: 4px;
}
/* Khối kết quả: cách hẳn ra để ⛔ không lẫn vào danh sách nền tảng. */
#l3Out:not(:empty) {
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line);
}
#l3Out .btn { margin-top: 8px; }


/* ═══ BẢN XEM BIÊN BẢN (khổ A4, lưu được thành PDF) ═══════════════════════
   ⭐ 15/09 anh Triệu: *"khi click vào sẽ hiện ra biên bản dạng pdf"*.
   ⛔ Dựng TRONG TRANG chứ ⛔ không mở cửa sổ mới — đo được `window.open` trả
     null khi trình duyệt chặn cửa sổ bật lên. */
#bbXem { position: fixed; inset: 0; z-index: 9999; background: rgba(20,22,26,.86);
         display: flex; flex-direction: column; }
#bbXem .bbx-thanh { display: flex; gap: 10px; align-items: center;
                    background: #2b2f36; color: #fff; padding: 10px 16px; }
#bbXem .bbx-thanh b { font-weight: 600; font-size: 14px; margin-right: auto; }
#bbXem .bbx-cuon { flex: 1; overflow: auto; padding: 18px 12px 40px; }
#bbXem .bbx-to { max-width: 210mm; margin: 0 auto; background: #fff; color: #111;
                 padding: 18mm 16mm; box-shadow: 0 2px 22px rgba(0,0,0,.5);
                 font: 14px/1.62 -apple-system, "Segoe UI", Roboto, Arial, sans-serif; }
#bbXem .bbx-to h1 { font-size: 20px; margin: 0 0 4px; color: #111; }
#bbXem .bbx-meta { color: #666; font-size: 12.5px; margin: 0 0 18px;
                   border-bottom: 1px solid #ddd; padding-bottom: 10px; }
#bbXem .bbx-to p { margin: .45em 0; }
#bbXem .bbx-to ul { margin: .3em 0 .7em 1.1em; padding: 0; }
#bbXem .bbx-to li { margin: .22em 0; }
#bbXem .bbx-to hr { border: 0; border-top: 1px solid #ddd; margin: 14px 0; }
@media print {
  /* ⛔ Chỉ in TỜ GIẤY, giấu sạch phần còn lại của trang họp. */
  body > *:not(#bbXem) { display: none !important; }
  #bbXem { position: static; background: #fff; display: block; }
  #bbXem .bbx-thanh { display: none !important; }
  #bbXem .bbx-cuon { overflow: visible; padding: 0; }
  #bbXem .bbx-to { max-width: none; margin: 0; padding: 0; box-shadow: none; }
  @page { size: A4; margin: 18mm 16mm; }
}