/* owner/owner.css — style riêng slice Owner (Epic 5 rebuild follow-up,
 * story dọn CSS-owner-extraction-gap 2026-08-20).
 *
 * Trích từ style.css cũ theo đúng pattern ask/ask.css (Story 4.4) — nhưng
 * KHÁC MỘT ĐIỂM: ask.css khi trích vẫn giữ bản trùng bên style.css ("cascade
 * vô hại", định gỡ sau nhưng bị bỏ quên — xem retro epic-5-frontend-rebuild).
 * File này XOÁ HẲN bản gốc khỏi style.css khi trích, không để lại nợ tương tự.
 *
 * Chỉ chứa rule CHỈ DÙNG bởi markup #main-screen (Owner) — xác nhận bằng cách
 * đối chiếu class/id với index.html VÀ owner/*.js (nơi JS gán className động,
 * ví dụ .caption-line/.gap-marker chỉ được owner/captions.js tạo ra). Rule nào
 * dùng CHUNG với slice khác (vd .digest-entries/.pane-label/.listen-secondary-btn
 * — owner VÀ listen cùng dùng; .btn-icon/.fab-label — owner VÀ ask cùng dùng)
 * CỐ Ý ở lại style.css, không trích, để tránh 2 nguồn định nghĩa cho 1 class
 * dùng chung nhiều nơi. Token màu qua shared/tokens.css + :root của style.css
 * (AD-5), không định nghĩa lại ở đây. */

/* Main screen: lock to viewport height so captions pane scrolls internally.
   Story 11.1 (FR-30): min-height is only a floor, not a ceiling — must set
   absolute height to prevent page growth past viewport when live line
   accumulates text (before finalization). Other screens (#pin-screen,
   #listen-screen, #ask-screen) keep min-height behavior (grow freely). */
#main-screen {
  height: 100dvh;
  overflow: hidden;
}

/* Story 11.1 (review-gate fix, round 2): scoped to #main-screen specifically —
   .toolbar is a class shared with #listen-screen/#ask-screen's headers too
   (index.html) — those two keep the free-growth `min-height` behavior and
   must NOT pick up flex-shrink:0 (they don't need it and it's not this
   story's scope to touch their layout). #main-screen's toolbar (the actual
   direct child, not .lang-bar — a grandchild) needs this so it can't be
   squeezed now that #main-screen no longer has page-scroll to fall back on. */
#main-screen > .toolbar {
  flex: 0 0 auto;
}

/* Story 3.6 (FR-14, AC: "cỡ chữ đủ lớn để đọc to hoặc chiếu lên màn") — PIN
   theo buổi cho Owner, chỉ hiện khi buổi MỚI (maybeShowSessionPin() ở
   app.js). Đặt NGAY dưới toolbar, tồn tại xuyên suốt màn tạo buổi VÀ màn
   chờ (mirror screen.html's projection-block clamp() cho số đọc được từ xa,
   nhưng nhỏ hơn — đây là màn cầm tay của Owner, không phải màn chiếu hội
   trường). CH-1 chưa chốt cách trao — vị trí/độ nổi bật đủ dùng cho cả ba
   cách (đọc to/chiếu màn/gửi trước), không thiết kế riêng cho cách nào. */
/* Story 11.1 (Epic 11 UX redesign, 2026-08-23) — chỉ báo trạng thái buổi cỡ
   lớn cho Owner. Cùng bảng tín hiệu với badge Listen picker
   (.listen-live-badge trong style.css): chấm + màu + chữ, KHÔNG dựa vào màu
   một mình (UX-DR11 — accessibility floor của app này). Token có sẵn (AD-5):
   --success cho đang phát, --muted cho chưa bắt đầu, --danger cho đã kết
   thúc. `flex-shrink: 0` bắt buộc — #main-screen là flex column
   `overflow:hidden` không có page-scroll dự phòng, thiếu dòng này thì
   .captions-split (flex:1) bóp mất chỉ báo trên màn hẹp. */
.owner-session-status {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin: var(--sp-3) var(--sp-3) 0; /* IV2 2026-09-07: 8→12, cùng nhịp banner PIN */
  padding: 10px 14px;
  border-radius: var(--radius-card);
  border: 1px solid var(--border);
  font-size: var(--fs-base);
  font-weight: 700;
  flex-shrink: 0;
}

.owner-session-status-mark {
  font-size: var(--fs-md);
  line-height: 1;
}

.owner-session-status-text {
  text-align: center;
}

.owner-session-status.is-idle {
  color: var(--muted);
  border-color: var(--border);
  background: transparent;
}

.owner-session-status.is-live {
  /* Epic 14 (Story 14.2): --success làm chữ trên wash mới (composite ~2,16:1)
     dưới sàn AA — glyph ●/○/■ ở .owner-session-status-mark + wording riêng
     mỗi trạng thái đã đủ phân biệt (UX-DR11), đổi chữ sang --text (12,5:1)
     không mất tín hiệu. border-color GIỮ var(--success) — gap non-text
     "success/--bg ~2,41:1" nêu ở đây trước kia ĐÃ VÁ 2026-08-27: token
     --success đảo sang success-700 #047857 (5,21:1 trên --bg, đạt sàn 3:1
     WCAG 1.4.11 — xem shared/tokens.css). */
  color: var(--text);
  border-color: var(--success);
  background: rgba(4, 120, 87, 0.12); /* wash của --success, mirror cách
                                          --danger-wash làm — không hue mới.
                                          2026-08-27: re-derive từ --success
                                          mới (#047857 = rgb(4,120,87)), giữ
                                          alpha 0.12. */
}

/* [J21 · J22, hội đồng 2026-09-07] Vỏ hổ phách — TRƯỚC HÔM NAY KHÔNG TỒN TẠI.
   session-status.js đã trả `is-connecting` cho O2 (đang bắc cầu), O9 (không
   nhận ra chữ), `silence` (giảng viên đang im) và `listeners === 0` (chưa ai
   vào nghe), nhưng owner.css chỉ vẽ idle/live/ended/lost — nên bốn trạng thái
   này rơi về vỏ mặc định viền --border, không phân biệt được với idle.
   Dùng --warn/--warn-wash (shared/tokens.css, IV9) chứ KHÔNG hue mới: một hổ
   phách cho một nghĩa "chưa hỏng, nhưng chưa chạy đủ". Chữ --text như .is-lost
   (12,5:1) vì đây là thứ chủ buổi phải đọc được giữa lúc đang giảng — khác
   .is-ended dùng --muted (buổi xong rồi, không còn phải hành động). */
.owner-session-status.is-connecting {
  color: var(--text);
  border-color: var(--warn-line);
  background: var(--warn-wash);
}

.owner-session-status.is-ended {
  color: var(--muted);
  border-color: var(--danger);
  background: var(--danger-wash);
}

/* Fix 2026-08-28 (finding #3 review Sally+Jobs Owner/Admin) — trạng thái
   `stalled` (mất tiếng nguồn giữa buổi, session-status.js). Chữ dùng
   var(--text) (không phải --muted như is-ended) — đây là báo động cần đọc
   rõ nhất, cùng mức tương phản với .standby-deadmsg (mirror pattern trên). */
.owner-session-status.is-lost {
  color: var(--text);
  border-color: var(--danger);
  background: var(--danger-wash);
}

/* Story 11.1 — nút thu gọn "Tuỳ chọn khác". Cố ý TRÔNG NHẸ (viền mờ, chữ
   muted, không nền đặc): đây là điều khiển PHỤ, không được cạnh tranh thị
   giác với nút mic (.fab) — cùng nguyên tắc đã áp cho menu ⋮ khi dọn "Mở màn
   chiếu" khỏi hàng ngang Start/Stop (Entry & Mode Structure 2026-08-21). */
.owner-advanced-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 10px 12px 0;
  padding: 10px 12px;
  min-height: 44px; /* tap-target floor UX-DR11 */
  border: 1px dashed var(--border);
  border-radius: var(--radius-control);
  background: transparent;
  color: var(--muted);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-align: left;
  flex-shrink: 0;
}

/* [B, 24/09] Hồ sơ buổi — tái dùng .step-k/.step-opts/.step-opt, chỉ thêm lề
   cho khớp .owner-advanced-toggle ngay dưới. */
.session-presets {
  margin: 10px 12px 0;
  flex-shrink: 0;
}

.owner-advanced-summary {
  flex: 1;
  color: var(--text);
  font-weight: 700;
}

.owner-advanced-toggle-caret {
  transition: transform 150ms ease-out;
}

.owner-advanced-toggle[aria-expanded="true"] .owner-advanced-toggle-caret {
  transform: rotate(180deg);
}

.session-pin-banner {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 10px 12px;
  /* IV2 (Ive 2026-09-07): thẻ PIN dán 0px vào vạch indigo 2px của toolbar ở
     O1 (góc bo chạm vạch) trong khi O3 badge lại có 8px — cùng vị trí, hai
     nhịp. Quy tắc: con đầu tiên sau toolbar LUÔN cách --sp-3. */
  margin: var(--sp-3) var(--sp-3) 0;
  background: var(--ane-500-tint);
  border: 1px solid var(--ane-500);
  border-radius: var(--radius-card);
  flex-shrink: 0;
}

.session-pin-banner-label {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ane-300); /* v5: bậc tối Indigo cho chữ trên nền sáng (đảo vai trò từ v4) */
}

.session-pin-banner-value {
  font-size: clamp(1.8rem, 8vw, 2.6rem);
  font-weight: 800;
  letter-spacing: 0.08em;
  color: var(--text); /* v4: PIN là nội dung chính của banner — trắng, không chìm */
  line-height: 1;
}

/* Spec §5(iii) (2026-08-17): link tham dự kèm session_id, cùng banner với
   PIN — cỡ nhỏ hơn hẳn (đây là đường phụ, chiếu QR/đọc PIN vẫn là chính),
   break-all vì URL dài không xuống dòng tự nhiên trên màn hẹp. */
.session-pin-banner-link {
  flex-basis: 100%;
  font-size: var(--fs-xs);
  color: var(--muted);
  word-break: break-all;
  text-align: center;
  /* Fix 04/09 (JOBS xoá #3 — UX audit chuyến EU 10/2026): chuỗi URL+UUID
     thô luôn hiện dưới nút "Chép link mời" không phục vụ ai — nút đã chép
     hộ, QR đã quét hộ. Ẩn mặc định; chỉ hiện khi `copyInviteUrl()`
     (pin-banner.js) thật sự thất bại (http không secure/quyền chặn) và tự
     nói "chép tay link bên dưới" — lúc đó chữ thô mới có việc để làm. */
  display: none;
}

.session-pin-banner.show-link-fallback .session-pin-banner-link {
  display: block;
}

/* Lớp B (2026-08-20): QR link tham dự trong banner PIN — nền trắng do chính
   svg vendored mang <rect fill="white"> (QR cần quiet zone trắng để quét
   được trên nền tối, không phải lệch design system). */
.session-pin-banner-qr {
  flex-basis: 100%;
  display: flex;
  justify-content: center;
}

.session-pin-banner-qr svg {
  width: clamp(96px, 30vw, 140px);
  height: clamp(96px, 30vw, 140px);
  border-radius: var(--radius-card);
}

/* [B1 2026-08-31] Nút "Chép link mời" — hành động CHÍNH của banner này (đây
   là lúc trưởng đoàn trao buổi cho đoàn), nên cho nó hình dạng nút thật chứ
   không phải chữ nhạt như link. min-height 44px = sàn chạm của chính app
   (D1 đã chuẩn hoá 2026-08-30) — banner sống 10s nên bấm trượt là mất lượt. */
/* Fix 2026-09-04 (SALLY-02, Critical — UX audit chuyến EU 10/2026): bản cũ
   `color:#fff` trên `background: rgba(255,255,255,.12)` phủ trên nền banner
   `--ane-500-tint` đo được 1,05:1 — nút gần như vô hình. Đặc thù: `--text`
   (#1a2140) trên nền trắng đặc `--bg` đã khoá AAA 7:1 (tokens.css) — dùng lại
   cặp đó thay vì trong suốt, không cần đo composite alpha mới. */
.session-pin-banner-copy {
  flex-basis: 100%;
  min-height: 44px;
  margin-top: 8px;
  border: 1px solid var(--ane-500);
  border-radius: var(--radius-control);
  background: var(--bg);
  color: var(--text);
  font-size: var(--fs-base);
  font-weight: 600;
  cursor: pointer;
}

.session-pin-banner-copy:hover {
  background: var(--ane-500-tint);
}

/* Xác nhận sau khi chép (hoặc lý do không chép được). aria-live="polite" ở
   markup lo phần đọc màn hình. */
.session-pin-banner-copy-status {
  flex-basis: 100%;
  font-size: var(--fs-sm);
  color: var(--text); /* mirror fix nút chép — #fff cũ cũng gần vô hình */
  text-align: center;
  min-height: 1em; /* giữ chỗ — không đẩy layout khi chữ xuất hiện */
}

/* [Sally · ui-ux-pro-max audit 2026-09-12] `.pin-reshow-btn` (nút "Hiện lại
   mã PIN") + sibling-selector đi kèm ĐÃ XOÁ — orphan CSS: `#pin-reshow-btn`
   bị gỡ khỏi markup từ [Jobs J11 · hội đồng 2026-09-06] (xem comment
   index.html ngay trên #mode-setup-title) nhưng 2 rule CSS trỏ tới nó vẫn
   còn sống, không còn phần tử nào khớp selector. */

/* Fix 2026-08-28 (finding #2 review Sally+Jobs) — hộp xác nhận Dừng buổi.
   Scrim rgba derive từ --text (#1a2140) — KHÔNG hue mới (DESIGN.md), cùng
   quy tắc `--ane-500-tint`/`--danger-wash` đã có (alpha của hue sẵn có).
   Nút "Dừng buổi" dùng `--danger-wash` (đã có sẵn) + viền `--danger` + chữ
   `var(--text)` — KHÔNG chữ trắng trên nền đỏ đặc (bài học D7/Epic 20: trắng
   trên `--ane-action-500` chỉ 2,92:1, TRƯỢT AA; đỏ #ef4444 cũng chỉ ~3,76:1
   — dưới sàn text 4,5:1). `var(--text)` trên nền tint nhạt giữ ~contrast của
   `--text`/`--bg` đã khoá AAA 7:1 (shared/tokens.css), an toàn không cần đo
   lại composite alpha. */
.stop-confirm-overlay {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: var(--scrim); /* IV9 2026-09-07: một màn che (tokens.css) */
}

.stop-confirm-box {
  width: 100%;
  max-width: 360px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  padding: 20px;
  text-align: center;
}

.stop-confirm-title {
  margin: 0 0 16px;
  color: var(--text);
  font-size: var(--fs-base);
  font-weight: 700;
}

.stop-confirm-actions {
  display: flex;
  gap: 10px;
  justify-content: center;
}

.stop-confirm-actions .listen-secondary-btn {
  flex: 1;
  margin-top: 0;
}

.stop-confirm-ok-btn {
  flex: 1;
  min-height: 44px;
  padding: 12px 20px;
  border: 1px solid var(--danger);
  border-radius: var(--radius-control);
  background: var(--danger-wash);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: 700;
}

.stop-confirm-ok-btn:active {
  transform: scale(0.98);
}

/* Session setup — two-step reveal for source/target language (Story 3.4,
   replaces v2's .lang-bar/.lang-chip/.swap-btn above). Exactly two real
   questions; category stays a silent server-side auto-select, no UI here.
   Locking during capture hides #session-setup wholesale (see
   setSessionSetupLocked() in app.js) via the existing .hidden utility —
   no opacity-lock variant needed. */
.session-setup {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px 12px 4px;
  /* mirror #main-screen > .toolbar's flex:0 0 auto (Story 11.1) — #main-screen
     is a flex column with overflow:hidden and no page-scroll fallback, so a
     sibling without this would get squeezed by .captions-split's flex:1. */
  flex-shrink: 0;
}

/* Entry & Mode Structure (2026-08-21) — tiêu đề Mode Thiết lập, hidden/shown
   CÙNG LÚC với #session-setup qua setSetupModeVisible() (owner/main.js, đúng
   1 điểm gộp, xem docstring ở đó). Mirror .post-session-downloads-title
   (weight 600, không uppercase) — KHÁC .pane-label (đó là caption nhỏ cho
   panel bên trong captions-split, ngữ cảnh khác). */
.mode-title {
  font-weight: 600;
  font-size: var(--fs-base);
  color: var(--muted);
  padding: 12px 12px 0;
  margin: 0;
  flex-shrink: 0;
}

.step-ask {
  border: 1px solid var(--ane-500);
  border-radius: var(--radius-card);
  padding: var(--sp-3); /* IV6 2026-09-07: 13 → 12 */
  background: var(--ane-500-tint);
}

.step-q {
  font-size: var(--fs-base);
  font-weight: 600;
  margin-bottom: 10px;
}

.step-opts {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Real tap-target floor (measured CSS, not the mockup's smaller demo
   padding) — Accessibility Floor UX-DR11. */
.step-opt {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  min-width: 44px;
  padding: 8px 14px;
  border-radius: var(--radius-control);
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  font-size: var(--fs-sm);
  font-weight: 600;
}

.step-opt.selected {
  background: var(--ane-500);
  border-color: var(--ane-500);
  color: #fff;
}

/* Disabled, not hidden — the language already chosen in step 1 stays
   visible in step 2's list so "Start only enables when source != target" is
   true by construction. Real <button disabled> already blocks pointer/focus. */
.step-opt.disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.step-done {
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  padding: var(--sp-3); /* IV6 2026-09-07: 11 → 12 */
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.step-k {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.step-v {
  font-size: var(--fs-base);
  font-weight: 600;
  margin-top: 2px;
}

.step-edit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  min-width: 44px;
  padding: 8px 12px;
  border-radius: var(--radius-control);
  border: none;
  background: transparent;
  color: var(--ane-300); /* v5: bậc tối Indigo cho chữ trên nền sáng (đảo vai trò từ v4) */
  font-size: var(--fs-sm);
  font-weight: 600;
}

/* NFR-6: chọn đoàn — mirror .step-done (khung/padding), nằm dưới
   .session-setup, cùng flex-shrink:0 để không bị .captions-split (flex:1)
   bóp mất chỗ. */
.group-picker {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0 12px 10px;
  padding: var(--sp-3); /* IV6 2026-09-07: 11 → 12 */
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  flex-shrink: 0;
}

.group-picker-select {
  min-height: 44px;
  padding: 8px 10px;
  border-radius: var(--radius-control);
  border: 1px solid var(--border);
  background: transparent;
  color: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
}

/* 05/09/2026: tên buổi — mirror .group-picker (khung/padding/flex-shrink:0),
   input thay select. */
.session-name-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0 12px 10px;
  padding: var(--sp-3); /* IV6 2026-09-07: 11 → 12 */
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  flex-shrink: 0;
}

.session-name-input {
  min-height: 44px;
  padding: 8px 10px;
  border-radius: var(--radius-control);
  border: 1px solid var(--border);
  background: transparent;
  color: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
}

/* [Sếp duyệt UI 2026-09-06, mục 4] "Lịch sử buổi của tôi" — TỪ hộp thoại nổi
   (`.session-history-box` override `.stop-confirm-box`, đã GỠ) thành MÀN THẬT
   `#history-screen`. Lý do Sếp nêu: popup khó truy cập, không có thiết kế,
   không phân trang. Thân màn dùng cùng nhịp đọc với các màn khác: cột hẹp
   `max-width` + lề thở, không phải bảng dày đặc. */
.history-body {
  width: 100%;
  max-width: 560px;
  margin: 0 auto;
  padding: var(--sp-4) var(--sp-4) var(--sp-5); /* IV6 2026-09-07: 18/28 → 16/24 */
  text-align: left;
}

.history-title {
  font-size: var(--fs-lg);
  font-weight: 800;
  margin: 0 0 2px;
}

.history-count {
  color: var(--muted);
  font-size: var(--fs-sm);
  margin: 0 0 14px;
}

/* Thanh phân trang: 2 nút hai đầu, nhãn "Trang x / y" ở giữa. Nút disabled
   giữ nguyên chỗ (không biến mất) để hàng không nhảy khi sang trang.
   DÍNH ĐÁY màn (`sticky`): với 10 buổi/trang, nút "Trang sau" nằm sau ~900px
   nội dung — người đọc phải cuộn hết danh sách mới thấy nó tồn tại (chính là
   "khó truy cập" Sếp nêu, chỉ đổi chỗ chứ không mất).
   [review tầng 1 06/09] MỘT rule duy nhất: trước đó tách 2 khối `.history-pager`
   cách nhau 40 dòng — sửa 1 quên 1 là chuyện sớm muộn. */
.history-pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 16px;
  position: sticky;
  bottom: 0;
  background: var(--bg);
  padding: 10px 0 6px;
  border-top: 1px solid var(--border);
}

/* Nút phân trang: chữ KHÔNG được vỡ dòng — "← Trang trước" xuống 2 hàng làm
   thanh cao gấp đôi và lệch trục với nhãn giữa (đo bằng ảnh 06/09). */
.history-pager .listen-secondary-btn {
  white-space: nowrap;
  padding: 8px 12px;
  font-size: var(--fs-sm);
}

.history-page-label {
  color: var(--muted);
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.history-pager .listen-secondary-btn[disabled] {
  opacity: 0.45;
  cursor: default;
}

/* [Sếp duyệt UI 06/09, mục 2] Lối vào khu quản trị ở màn entry — link chữ
   thuần dưới 3 nút chính, KHÔNG phải nút thứ tư ngang hàng: nó chỉ hiện với
   1/24 người và không phải việc thường ngày của họ. */
.entry-admin-link {
  /* `block` + margin auto: đứng RIÊNG một dòng dưới 3 nút chính. `inline-block`
     làm nó nằm chen ngay cạnh nút "Lịch sử buổi của tôi" (đo bằng ảnh 06/09)
     — trông như nút thứ tư dính liền, đúng thứ cần tránh. */
  /* [Sally · ui-ux-pro-max audit 2026-09-12] `display:flex` thay `block` +
     thêm `min-height:44px` — vùng chạm thật trước đây chỉ cao ~line-height
     của `--fs-sm` (~20px), dưới sàn 44px UX-DR11 đã áp cho MỌI control khác
     trong file này (.step-opt/.owner-advanced-toggle/.session-pin-banner-copy…).
     `flex` (không phải `inline-flex`) giữ NGUYÊN hành vi `margin:auto` căn
     giữa của `width:fit-content` — chỉ đổi kiểu box, không đổi layout cha. */
  display: flex;
  align-items: center;
  justify-content: center;
  width: fit-content;
  min-height: 44px;
  margin: 16px auto 0;
  padding: 0 4px;
  font-size: var(--fs-sm);
  color: var(--muted);
  text-decoration: underline;
}

.session-history-empty {
  color: var(--muted);
  font-size: var(--fs-sm);
}

.session-history-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* [Sếp duyệt UI 06/09 tối] Buổi ĐANG MỞ phải nhận ra được từ xa: viền trái
   dày màu thương hiệu + nền nhạt. Trước đó khác biệt duy nhất là 2 chữ "Đang
   mở" cỡ 0,8rem lẫn trong dòng giờ — mở danh sách 5 buổi test thấy 5 khối y
   hệt nhau. */
.session-history-row.is-open {
  border-left: 4px solid var(--ane-500);
  background: var(--panel-2);
}

.session-history-badge {
  margin-left: 8px;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--ane-500);
  white-space: nowrap;
}

.session-history-open-hint {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-sm);
}

.session-history-row {
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  padding: 10px 12px;
}

.session-history-title {
  font-size: var(--fs-base);
  font-weight: 600;
}

.session-history-meta {
  font-size: var(--fs-sm);
  color: var(--muted);
  margin-top: 2px;
}

.session-history-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 8px;
}

.session-history-actions .listen-secondary-btn {
  margin-top: 0;
}

.session-history-audio {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  font-size: var(--fs-sm);
  color: var(--muted);
}

/* [Sally · ui-ux-pro-max audit 2026-09-12] `.reconnect`/`.reconnect-dot`
   ĐÃ XOÁ — orphan CSS: comment trước đây ở đây nói "owner/main.js VẪN
   toggle `.hidden`", nhưng `owner/main.js` (comment C2.4, dòng ~893) ghi rõ
   `#reconnect-indicator` đã xoá HẲN cả markup lẫn 3 call site JS. Không còn
   phần tử `.reconnect` nào trong index.html hay owner/*.js — 2 comment tự
   mâu thuẫn nhau, bên main.js đúng (grep xác nhận 0 kết quả). Tín hiệu "mất
   kết nối" đã gộp hẳn vào `#owner-session-status` như comment cũ mô tả. */

/* Fix 2026-08-28 (finding #6 review Sally+Jobs Owner/Admin) — cảnh báo MỀM
   (quota_warning · sample-rate degraded, buổi vẫn ghi bình thường): mirror
   `.reconnect` amber NGAY TRÊN (cùng ý nghĩa "chú ý nhưng KHÔNG chặn"),
   KHÔNG dùng `.error`/`#mic-error` — bề mặt đó dành cho lỗi CHẶN thật (mic
   denied/quota blocked/lang invalid). Không cần dot — 2 message hiện tại
   (formatQuotaWarning/DEGRADED_RATE_MESSAGE) đều là câu đơn, không lặp lại
   liên tục như reconnect. */
#mic-notice {
  margin: 0 16px 12px;
}

.notice {
  padding: 8px 16px;
  border-radius: var(--radius-card);
  color: var(--text);
  font-size: var(--fs-sm);
  background: var(--warn-wash); /* IV9 2026-09-07 */
}

/* Split screen — top half = source language as spoken, bottom half = the
   translation the reader actually needs. Each pane scrolls independently. */
.captions-split {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.pane {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.pane-translated {
  border-top: 2px solid var(--ane-500);
}

/* Story 5.3 (FR-15): third column (digest panel) — hidden below breakpoint by
   default, the two caption panes stay stacked vertically (v1 layout unchanged).
   ane-shortcut: 768px is the UX-stage DEFAULT, NOT confirmed by Sếp yet (open-item 3, PRD Open Q5) —
   verify on real devices (DEVICE-CHECKLIST.md, EU trip) before treating this as locked;
   if it needs to change, this is the ONE line to edit. */
.pane-digest {
  display: none;
}

@media (min-width: 768px) {
  .captions-split {
    flex-direction: row;
  }

  .pane-digest {
    display: flex;
    flex-direction: column;
    max-width: 320px; /* mirrors .pin-box max-width — same ratio vocabulary */
    background: var(--ane-500-tint);
    border: 1px solid var(--border);
    border-radius: var(--radius-card); /* IV4 2026-09-07 */
  }

  /* [UX review 2026-08-27, finding #3] Trước: #main-screen KHÔNG có container
     giới hạn chiều rộng ở desktop — nút "Bắt đầu buổi dịch" (.start-hero) và
     khối chọn ngôn ngữ (#session-setup) trải hết 1440px, "trông như chưa
     dựng xong" (Sally) ngay trong khi `.pane-digest` ngay phía trên ĐÃ có
     pattern ≥768px này từ lâu. Mirror ĐÚNG breakpoint + giá trị 320px ("same
     ratio vocabulary" — comment gốc trên `.pane-digest`, cũng chính là
     max-width của `.pin-box` màn đăng nhập/entry) cho các khối THIẾT LẬP có
     viền/nền (trông như "hộp" khi kéo rộng) — CHỈ những khối đó, KHÔNG đụng
     `.captions-split`/`.standby-screen`/`footer.controls` (đã có bố cục
     riêng phù hợp full-width, không phải phần bị chê). Mobile không đổi
     (nằm trong @media này). */
  /* Fix 2026-08-28 (finding #5 review Sally+Jobs Owner/Admin): `#mode-setup-
     title` ("Thiết lập buổi mới") bỏ sót khỏi danh sách gốc — đứng trơ mép
     trái trong khi mọi khối setup khác đã căn giữa cột 320px. */
  /* IV14 (Ive 2026-09-07, Low): desktop O1 nhịp bề rộng đảo liên tục
     1416px → 320px → 1416px → 320px vì thẻ PIN và thẻ "Tên buổi" không nằm
     trong danh sách. Gom MỘT cột 480px (= `.wrap` của preflight; 320 quá hẹp
     cho PIN + QR 140px + nút chép). `.start-hero` PHẢI đứng cuối group —
     owner_layout_css.test.mjs dò `#mode-setup-title` bằng vị trí của nó. */
  .session-pin-banner,
  .owner-session-status,
  .owner-advanced-toggle,
  #mode-setup-title,
  #session-setup,
  #group-picker,
  .session-name-field,
  .start-hero {
    width: 100%;
    max-width: 480px;
    margin-left: auto;
    margin-right: auto;
  }
}

/* Epic 4 (Story 4.1/4.4) — standby-screen: bề mặt MẶC ĐỊNH của #main-screen
   SAU khi bấm Bắt đầu, thay cho .captions-split (đảo mặc định của v2, có
   chủ đích — DESIGN.md:124-125). Cùng flex slot với .captions-split (sibling
   trực tiếp trong #main-screen, `flex: 1; min-height: 0`) — toggle bằng
   `.hidden` (mirror mọi màn khác trong file này), KHÔNG absolute-overlay:
   kết quả quan sát được (đúng 1 bề mặt hiện tại 1 thời điểm, chiếm đúng
   vùng captions-split) giống hệt "chồng lên" mà không cần thêm stacking
   context/z-index cho một bề mặt vốn đã opaque toàn màn. */
.standby-screen {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Fix 04/09 (SALLY-03/JOBS-01): `center` thuần co ĐỀU về 2 phía khi nội
     dung (bars + timer 9rem + hint, gap 24px×3) cao hơn chiều cao khả dụng
     — nửa trên bị đẩy khuất phía toolbar. `safe center` giữ mép TRÊN luôn
     trong khung thay vì tràn đều 2 phía.
     [SỬA 04/09, review-gate tầng 1 MEDIUM] Khai `center` trần TRƯỚC —
     browser không hiểu `safe` (Safari <15.4, khả năng thật trên iPad cũ
     mang theo chuyến EU) DROP CẢ DÒNG `justify-content: safe center` (một
     giá trị hai từ không parse được thì cả declaration vô hiệu, KHÔNG phải
     tự rơi về giá trị initial) — nếu đó là dòng DUY NHẤT thì layout mất
     luôn phép căn giữa mọi lúc, kể cả khi nội dung KHÔNG tràn (tệ hơn hẳn
     bug gốc). Khai 2 dòng: browser cũ dừng ở `center` (đã đúng most-of-the-
     -time), browser mới ăn tiếp `safe center` (đè lên, cascade thường). */
  justify-content: center;
  justify-content: safe center;
  gap: 24px;
  position: relative;
  padding: 24px;
  /* Lưới đỡ cuối — (b)+(c) dưới đây đã co timer/bars theo `vh` để vừa khung
     trong ca thường; overflow:hidden chặn phần dư (nếu có, viewport cực
     thấp) tràn xuống đè `footer.controls` thay vì chỉ cắt gọn bên trong
     box của chính standby-screen. */
  overflow: hidden;
  text-align: center;
  cursor: pointer;
  background: var(--bg);
  transition: background-color 120ms ease-out;
}

/* Review 2026-08-18: bề mặt chạm nhiều nhất của màn Owner (toggle phụ đề
   suốt buổi giảng) trước đây KHÔNG có phản hồi chạm nào — mọi nút khác
   trong file này đều có :active (.fab/.listen-entry/.listen-session…),
   standby-screen là ngoại lệ duy nhất còn sót. Tint nhẹ, KHÔNG scale (toàn
   màn, scale sẽ tạo cảm giác "co rúm" khó chịu cho một vùng cỡ viewport) —
   không đụng lớp phủ `.dead::before` (đứng trên, ::before độc lập). */
.standby-screen:active {
  background: var(--panel);
}

/* Ngoại lệ DUY NHẤT tự động chuyển cảnh (UX-DR14) — báo động, không phải
   điều hướng. `::before` phủ TOÀN bộ standby-screen, dưới nội dung (bars/
   timer/deadmsg đứng trên nhờ index tự nhiên của flow — không cần z-index
   tường minh vì ::before luôn vẽ TRƯỚC các phần tử con thật trong stacking
   order mặc định). */
.standby-screen.dead::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--danger-wash);
}

/* `proof-of-life` — thành phần quan trọng nhất của v3 (DESIGN.md:114). 5-7
   thanh `rounded/pill`, biên độ đọc từ AnalyserNode CỤC BỘ (kết cấu hình ảnh
   thuần — app.js `renderProofOfLife()` set `transform: scaleY()` mỗi khung
   hình vẽ), MÀU đọc từ class `.state-live`/`.state-quiet`/`.state-dead` do
   app.js gán theo tín hiệu MÁY CHỦ (WS `source_stalled`/`source_resumed`) +
   hoạt động caption quan sát được qua CHÍNH WS đó (is_final) — KHÔNG BAO GIỜ
   đọc biên độ cục bộ để suy ra màu (2 nguồn tách bạch, xem app.js comment
   `renderProofOfLife`). `transform: scaleY()` (GPU, không layout thrash) —
   KHÔNG `bar.style.height` như widget `.rec-bars` cũ (bug v2 story này tồn
   tại để vá, xem app.js `startVisualizer` cũ). */
.proof-of-life {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2); /* IV6 2026-09-07: 7 → 8 */
  /* Fix 04/09 (SALLY-03/JOBS-01): 96px cứng + timer 9rem + hint + 3 gap 24px
     dư ra ~300px+ nội dung — vượt chiều cao khả dụng trên viewport thấp
     (landscape, khay bàn phím, cửa sổ desktop hẹp). Co theo `vh` giữ ngôn
     ngữ hình ảnh (thanh cao = "đang sống") mà không cần biết trước layout
     cha; `transform: scaleY()` (JS) tính theo chiều cao THẬT của span nên
     đổi single-source ở đây là đủ, không sửa gì phía JS. */
  height: clamp(48px, 10dvh, 96px);
  position: relative;
}

.proof-of-life span {
  width: 11px;
  height: clamp(48px, 10dvh, 96px);
  border-radius: var(--radius-pill);
  background: var(--success);
  transform: scaleY(0.08);
  transform-origin: center;
  transition: background-color 200ms ease-out;
}

.standby-screen.state-quiet .proof-of-life span {
  background: var(--muted);
}

.standby-screen.state-dead .proof-of-life span {
  background: var(--danger);
}

/* [2026-09-11] `.standby-timer` (đồng hồ 9rem) ĐÃ GỠ khỏi markup —
   `#owner-session-timer` nay tái dùng class `.rec-timer` (index.html) thay
   vì đồng hồ riêng này. Không test nào khoá nội dung rule; dọn theo mục
   "Còn treo #5" HANDOFF 2026-09-09. */

.standby-hint {
  position: relative;
  color: var(--muted);
}

/* `.micro` (DESIGN.md typography: 0.7rem/600/uppercase/0.08em) — chưa có
   class chung trong file này, khai trực tiếp tại đây (dùng đúng 1 chỗ). */
.standby-hint.micro {
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* C2.3 (2026-09-06): dòng 2 = HÀNH ĐỘNG, nhỏ và nhẹ hơn dòng 1 (HỆ QUẢ) —
   giảng viên đọc hệ quả trước, hành động sau, không phải cùng một sức nặng.
   `display: block` để 2 <span> con thành 2 dòng thật (xem #standby-deadmsg
   trong index.html). */
#standby-deadmsg-line {
  display: block;
}

.standby-deadmsg-action {
  display: block;
  font-size: 0.85em;
  opacity: 0.85;
  margin-top: 6px;
}

.standby-deadmsg {
  position: relative;
  font-size: var(--fs-md);
  font-weight: 700;
  /* Epic 14 (Story 14.2): --danger làm chữ trên --bg mới đo 3,57:1 — dưới
     sàn AA. Cùng lý do standby-timer phía trên (danger-wash phủ toàn màn
     đã là kênh báo động chính) — đổi sang --text. */
  color: var(--text);
}

@media (prefers-reduced-motion: reduce) {
  /* Story 4.1 AC: dải sóng đứng yên, CHỈ đổi màu — trạng thái vẫn đọc được
     đủ, chỉ mất phần kết cấu (UX-DR11). Freeze bằng cách ép `scaleY` cố định
     (không animation nào chạy trên các span này ở app.js để mà tắt — đây là
     rAF-driven, không phải CSS @keyframes — nhưng khoá cứng ở đây làm lưới
     an toàn thứ 2 nếu app.js lỡ không kiểm tra media query cho 1 khung hình
     nào đó). */
  .proof-of-life span {
    transition: none;
  }
}

.captions {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* Story 5.1: was `.captions:empty::before` — the translated pane's sync pill
   (see below) is now a permanent DOM child of #captions-translated, so that
   pane is never literally :empty again once the pill exists. `:not(:has(...))`
   keeps the exact same "no actual caption yet" placeholder behavior for BOTH
   panes (#captions-original has no pill, so this is a no-op change for it). */
.captions:not(:has(.caption-line))::before {
  content: attr(data-empty);
  color: var(--muted);
  font-size: var(--fs-sm); /* v4: bỏ italic — tiếng Việt có dấu, nghiêng khó đọc */
}

/* Story 5.1 (AC #3) — sticky live/history sync pill, top of translated pane.
   DESIGN.md: live = Orange dot (--ane-action-500), history = Indigo outline,
   not filled (--ane-500). Both states share min-height so switching between
   them never shifts the pane layout. */
.sync-pill {
  position: sticky;
  top: 0;
  z-index: 1;
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  box-sizing: border-box;
  min-height: 44px;
  padding: 6px 14px;
  border-radius: var(--radius-control);
  border: 1px solid transparent;
  font-size: var(--fs-sm);
  font-weight: 700;
  cursor: default;
  background: var(--panel);
}

.sync-pill.live {
  /* Epic 14 (Story 14.2): --ane-action-500 làm chữ trên --panel mới đo
     2,63:1 — dưới sàn AA. Text "● Live" (owner/captions.js) đã có glyph —
     đổi sang --text (14,2:1) không mất tín hiệu. */
  color: var(--text);
}

.sync-pill.history {
  min-width: 44px; /* AC #3: 44x44 tap target for the actionable state */
  color: var(--ane-300); /* v4 */
  border-color: var(--ane-500);
  background: transparent;
  cursor: pointer;
}

.caption-line {
  animation: caption-in 300ms ease-out;
  line-height: 1.4;
}

/* original pane = smaller/muted (source-language reference); translated pane =
   larger/bold/near-white (the payload the reader actually needs) — weight+size
   contrast carries the hierarchy instead of color, so it still reads correctly
   if someone is color-blind. */
.pane-original .caption-line {
  font-size: var(--fs-base);
  color: var(--muted);
}

.pane-translated .caption-line {
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--text);
}

/* v4 (benchmark 2026-08-20): partial = MỜ, KHÔNG italic — tiếng Việt có dấu
   in nghiêng bóp méo dấu, khó đọc (Web Captioner dùng màu riêng cho interim,
   không nghiêng). Opacity hạ 0.75 → 0.55 để chênh lệch partial/chốt đọc được
   ngay cả khi liếc nhanh. */
.caption-line.live {
  opacity: 0.55;
}

/* Story 5.2 (AC #1/#2): gap marker — deliberately NOT `.caption-line`, so it
   never inherits the pulsing "recording" animation/CSS of a live line (AC #2
   says a marker is not spoken content, `.caption-line.live` communicates
   "currently being recorded" — the opposite meaning). `user-select: none`
   (not `pointer-events: none`) per Task 4.1: still readable by eye, but
   excluded from a copy-paste of the surrounding transcript so it can't be
   mistaken for something someone said. */
.gap-marker {
  text-align: center;
  color: var(--muted);
  font-size: var(--fs-sm);
  user-select: none;
}

#mic-error {
  margin: 0 16px 12px;
}

/* Footer — big circular mic FAB (learned from Google/Microsoft live-translate
   conversation mode): tap to toggle, pulsing ring + audio-reactive bars while
   listening, elapsed timer so a lecturer-length session doesn't feel silent
   or stuck. Label lives outside the button so the FAB stays a true circle. */
.controls {
  /* IV15 (Ive 2026-09-07): vòng pulse `.fab-ring` (inset -8px, scale tới
     1,45) vươn ~30px ra ngoài nút; với 14px padding-top nó cắt qua vạch
     border-top mỗi chu kỳ khi `.rec-status` ẩn. 24px (--sp-5): phần vượt còn
     lại chỉ ở scale >1,32 khi opacity đã <0,17 — không còn thấy đường cắt. */
  padding: var(--sp-5) var(--sp-4) 14px;
  padding-bottom: max(14px, env(safe-area-inset-bottom));
  background: var(--panel);
  border-top: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto; /* Story 11.1: prevent shrinking when #main-screen is height-locked */
}

/* [2026-09-11] `.rec-status`/`.rec-bars` (hàng + 5 vạch đỏ decorative) ĐÃ GỠ
   khỏi markup cùng đợt `#rec-status` (index.html:573, J27·E5 2026-09-07) —
   chỉ `.rec-timer` bên dưới còn sống (tái dùng cho `#owner-session-timer`).
   Không test nào khoá nội dung 2 rule đã xoá; dọn theo "Còn treo #5" HANDOFF
   2026-09-09. */
.rec-timer {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  /* Epic 14 (Story 14.2): --danger làm chữ trên --panel mới đo 3,39:1 —
     dưới sàn AA 4.5:1. Tín hiệu "đang ghi" đã có #live-dot (đỏ, pulse) +
     .rec-bars (nền đỏ, decorative) đứng cạnh — đổi số MM:SS sang --text
     (14,2:1) không mất kênh báo động nào. */
  color: var(--text);
  font-size: var(--fs-base);
}

/* [UX review 2026-08-27, finding #6] Menu overflow ⋮ ("Mở màn chiếu cho hội
   trường") ĐÃ GỠ — cả cơ chế (nút, panel, aria-haspopup, state mở/đóng) chỉ
   để cất ĐÚNG MỘT mục là phức tạp thừa (Ive), và ⋮ là ký hiệu người lớn
   tuổi ít nhận ra là bấm được (Sally). #screen-entry giờ đứng THẲNG trong
   footer, tái dùng NGUYÊN class `.listen-entry` (style.css) như
   `#listen-entry` cạnh nó — chỉ cần nạp lại display/text-decoration mà
   `.listen-entry` không tự có trên thẻ `<a>` (mirror cách `a.listen-secondary-btn`
   làm ở style.css cho đúng lý do). Hiện/ẩn vẫn state-driven qua screen-link.js
   (showFor()/hide() đọc thẳng #screen-entry, không còn toggleEl). */
#screen-entry {
  display: block;
  text-align: center;
  text-decoration: none;
}

/* Story 17.1 (Epic 17, D1 `start-hero`) — bề mặt cam lớn DUY NHẤT của app:
   khoảnh khắc cam kết duy nhất của Owner trong 3 tiếng xứng đáng bề mặt cam
   lớn nhất. `min-height: 56px` vượt sàn chạm 44px CÓ CHỦ ĐÍCH (DESIGN.md).
   Chữ dùng `--text` (KHÔNG trắng) — đo thật `tests/test_epic17_start_hero_
   contrast.py`: trắng/--ane-action-500 = 2.92:1 (DƯỚI sàn AA large-text
   3:1), --text/--ane-action-500 = 5.38:1 (đạt cả AA normal-text). Disabled
   dùng --panel-2/--muted — KHÔNG cam mờ (DESIGN.md: "cam mờ trông như lỗi
   render"). KHÔNG icon (mic là ẩn dụ sai cho hành động này). */
.start-hero {
  display: block;
  /* IV1 (Ive 2026-09-07, High): nút tràn hết 390px, hai góc pill bị mép màn
     cắt cụt, trong khi mọi khối anh em (thẻ PIN, form, thẻ lỗi) có gutter
     12–16px — dấu hiệu "chưa dựng xong" rõ nhất trong 104 ảnh. Thu bề rộng
     2×--sp-3 và để `margin auto` (đã có) tự căn giữa: gutter 12px mỗi bên,
     desktop vẫn bị `max-width` ở @media 768 chặn. KHÔNG đổi shorthand margin
     — owner_layout_css.test.mjs khoá `4px auto 0` và `auto` chính là thứ
     làm gutter đối xứng. */
  width: calc(100% - 2 * var(--sp-3));
  min-height: 56px;
  /* Fix 2026-08-28 (finding #5 review Sally+Jobs Owner/Admin): rule này đứng
     SAU @media (min-width:768px) trong file — shorthand `margin: 4px 0 0`
     (top/right/bottom/left tường minh) đè mất `margin-left/right: auto` mà
     media block set cho cột 320px desktop. `4px auto 0` (3-value: top |
     left+right | bottom) giữ ĐÚNG margin-top của rule gốc, để `auto` thắng
     ở desktop (căn giữa) và co về 0 vô hại ở mobile (width:100% không còn
     chỗ cho auto). Đo runtime Playwright 1440px trước fix: .start-hero
     margin-left/right = 0px trong khi #session-setup/.owner-session-status
     nhận auto — xem owner_layout_css.test.mjs. */
  margin: 4px auto 0;
  padding: 14px 20px;
  border: none;
  border-radius: var(--radius-pill);
  background: var(--ane-action-500);
  color: var(--text);
  font-size: var(--fs-lg); /* bậc `display` DESIGN.md typography */
  font-weight: 700;
  text-align: center;
  transition: background-color 200ms ease-out;
}

.start-hero:active:not(:disabled) {
  transform: scale(0.99);
}

.start-hero:disabled {
  background: var(--panel-2);
  color: var(--muted);
  cursor: not-allowed;
}

/* IV1: #mode-ended-new-session-btn (cùng class, O7) sống TRONG
   `.post-session-downloads` đã có padding ngang 16px — trừ thêm 24px nữa là
   gutter 28px, lệch với khối tải xuống ngay trên. Trong khung đó nút chiếm
   trọn bề rộng nội dung (gutter = của khung). */
.post-session-downloads .start-hero {
  width: 100%;
}

.fab {
  position: relative;
  width: 84px;
  height: 84px;
  border-radius: 50%;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  transition: background-color 200ms ease-out, transform 150ms ease-out;
}

.fab:active {
  transform: scale(0.96);
}

/* Start = ANE CTA orange (an invitation to act); Stop = red (recording is
   active, handle with care) — see :root comment for why Stop isn't orange too. */
.fab.start {
  background: var(--ane-action-500);
}

/* C2.2 (2026-09-06, audit Musk/Jobs/Sally): 2/3 advisor nhìn ảnh màn Đang
   chạy mà KHÔNG nhận ra đây là nút Dừng. Nút vẫn luôn tồn tại — vấn đề là độ
   nổi bật. Viền đặc cùng màu `--danger` tách hẳn khối tròn khỏi nền tối
   (trước đây chỉ có nền đặc, mất cạnh khi footer cũng tối), và sàn 64px giữ
   đường kính không bao giờ tụt dưới ngưỡng đọc-được-từ-bục dù `.fab` có bị
   thu nhỏ ở breakpoint nào. Nhãn cố định "Dừng buổi" ở #btn-label do
   renderOwnerDisplayState() ghi (O3..O6). Xác nhận 2 bước
   #stop-confirm-overlay GIỮ NGUYÊN — không đổi. */
.fab.stop {
  background: var(--danger);
  border: 2px solid var(--danger);
  min-width: 64px;
  min-height: 64px;
}

/* Review 2026-08-18: #start-stop-btn ships `disabled` in markup until source
   + target đều đã chọn (updateStartButtonState(), app.js) — trước fix này
   nút vẫn giữ NGUYÊN màu cam CTA đầy mời gọi, không có tín hiệu nào báo
   "chưa bấm được" (giảng viên tay bận, liếc màn là bấm ngay). `:disabled`
   đặt SAU `.fab.start`/`.fab.stop` để thắng specificity ngang nhau theo thứ
   tự cascade — dập màu về panel/muted + cursor rõ ràng, không đụng logic JS
   nào (chỉ CSS thuần). */
.fab:disabled {
  background: var(--panel);
  color: var(--muted);
  cursor: not-allowed;
}

.fab-ring {
  position: absolute;
  inset: -8px;
  border-radius: 50%;
  border: 2px solid var(--danger);
  opacity: 0;
}

.fab.stop .fab-ring {
  animation: fab-pulse 1.6s ease-out infinite;
}

@keyframes fab-pulse {
  0% { opacity: 0.6; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.45); }
}

/* Story 7.5 (Epic 7, review-gate tầng 4) — panel tải về hậu-buổi, tối giản:
   tái dùng .listen-secondary-btn/.listen-message hiện có, chỉ thêm layout. */
.post-session-downloads {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 16px;
  padding: 12px 16px 0;
  border-top: 1px solid var(--border);
  /* Fix 2026-08-28 (finding #9 review Sally+Jobs Owner/Admin): trước chỉ có
     `padding-top` — tiêu đề/subtitle/hàng "Khúc i/n" dính mép 0px trong khi
     cả app đi gutter 12-24px (mirror `#mic-error`/`.owner-session-status`
     16px/12px). `padding: 12px 16px 0` giữ NGUYÊN khoảng cách dọc cũ
     (12px top), thêm 16px trái/phải. */
}

.post-session-downloads-title {
  font-weight: 600;
  margin: 0;
}

.post-session-downloads-subtitle {
  margin: 8px 0 0;
  color: var(--muted);
  font-size: var(--fs-sm);
}

.download-audio-chunks {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.download-audio-chunks a.listen-secondary-btn {
  padding: 6px 10px;
  font-size: var(--fs-sm);
}

/* [P, 24/09] Ô chữ dự phòng khi trình duyệt chặn clipboard — Owner bôi đen chép tay. */
.copy-summary-fallback {
  width: 100%;
  box-sizing: border-box;
  margin-top: 8px;
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--panel);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-sm);
}
