/* Tách từ <style> inline của index.html (02/09/2026, audit Phase 2.1).
   Biến (token) nay ở css/tokens.css — nạp trước tệp này. */
* { box-sizing: border-box; }
/* Thanh cuộn mảnh, không nền, chỉ hiện con trượt. Mặc định của Chromium
   trên Windows là thanh xám dày 15px có nút mũi tên hai đầu — chiếm chỗ và
   chọi hẳn với giao diện phẳng ở đây. */
/* ⚠ KHÔNG khai `scrollbar-width` / `scrollbar-color` ở đây.
 *
 * Từ Chromium 121, hễ một trong hai thuộc tính CHUẨN đó được khai là trình
 * duyệt bỏ qua TOÀN BỘ nhóm `::-webkit-scrollbar-*` — kể cả `border-radius`.
 * Bản trước khai cả hai, nên luật bo tròn ngay bên dưới nằm đó mà không chạy:
 * thanh cuộn ra hình chữ nhật vuông góc. Đo được: app đang chạy Chromium 130 và
 * `getComputedStyle(...).scrollbarWidth` trả 'thin'.
 * Bỏ hai thuộc tính chuẩn thì nhóm webkit có hiệu lực trở lại. Đây là app
 * Electron nên không cần lo Firefox.
 */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
/* `background-clip: content-box` + viền trong suốt = con trượt THUN LẠI còn 6px
   và không chạm mép, nhìn như một viên thuốc nổi thay vì một thanh dính vào
   cạnh khung. */
::-webkit-scrollbar-thumb {
  background: var(--control-line);
  border: 2px solid transparent;
  background-clip: content-box;
  border-radius: var(--radius-pill);
}
::-webkit-scrollbar-thumb:hover { background: var(--text-faint); background-clip: content-box; }
::-webkit-scrollbar-corner { background: transparent; }

/* button, input, select, textarea KHÔNG thừa kế font từ body — trình duyệt
   dùng font mặc định của biểu mẫu. Đo được bằng getComputedStyle: nút
   "Làm mới" đang vẽ bằng **Arial** trong khi cả trang dùng font Việt, nên
   dấu tiếng Việt (ớ, ộ, ữ) đặt lệch và nét khác hẳn phần còn lại. */
button, input, select, textarea { font: inherit; color: inherit; }
body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--font-ui);
  color: var(--text);
  background: var(--body-bg);
}
header { max-width: 920px; margin-bottom: var(--sp-32); }
h1 { margin: 0 0 var(--sp-10); font-size: clamp(36px, 6vw, 76px); line-height: var(--leading-display); letter-spacing: -0.06em; }
header p { max-width: 720px; color: var(--text-faint); font-size: var(--text-xl); line-height: var(--leading-relaxed); }
code { color: var(--accent); }
a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  padding: var(--sp-10) var(--sp-14);
  color: var(--accent-contrast);
  background: var(--accent);
  text-decoration: none;
  font-weight: var(--weight-semibold);
}
.small-link { color: var(--text); background: var(--surface-strong); }

body {
  height: 100vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* Lề ngang 12px thay vì 20px. Cộng dồn với lề trong của hai cột (cũng đã hạ
     16 → 10) thì mỗi bên tiết kiệm được 14px cho nội dung — cột danh sách vốn
     hay cắt cụt tên dự án nên chỗ đó đáng giá.
     Lề TRÊN phải chừa đúng chiều cao thanh trên: nó là position:fixed nên
     không tự đẩy nội dung xuống. */
  /* ⚠ KHÔNG chừa lề TRÊN/DƯỚI quanh khung hai cột.
     Đường kẻ dọc ngăn hai cột chính là viền phải của cột danh sách, nên nó chỉ
     dài bằng cột. Chừa lề 12px ở body là hai đầu đường kẻ hụt đúng 12px —
     đo được: cột chạy 70→949 trong cửa sổ cao 961.
     Khoảng thở vẫn còn, do LỀ TRONG của từng cột lo (.slide-list và
     .render-machine đều có padding riêng). */
  padding: var(--topbar-h) var(--sp-12) 0;
}
.dashboard-header {
  /* Thanh NGANG HẾT màn hình, phủ lên cả thanh điều hướng dọc. Không nằm trong
     luồng bố cục nữa nên phải là fixed + body chừa lề trên. Dáng thanh (nền,
     viền) vẫn do .app-bar trong app-bar.css lo — dùng chung với lớp phủ xem
     slide, đừng sửa ở đó. */
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: var(--z-topbar);
  height: var(--topbar-h);
  max-width: none;
  margin: 0;
}
.kicker { margin: 0 0 var(--sp-6); color: var(--text-faint); font-size: var(--text-xs); font-weight: var(--weight-bold); letter-spacing: 0.16em; text-transform: uppercase; }
.header-tools {
  display: flex;
  align-items: center;
  gap: var(--sp-10);
  flex-wrap: wrap;
  flex: 1 1 auto;
  min-width: 0;
  justify-content: flex-end;
}
.guide-header-btn {
  white-space: nowrap;
  padding-inline: var(--sp-14);
}
.dashboard-shell {
  display: grid;
  /* Chia đôi đều: phần chức năng mới là việc chính, danh sách chỉ để chọn.
     Trước đây cột trái bị chặn cứng ở 520px nên ở màn 1460px thành 36/64 —
     cột làm việc hẹp hơn hẳn cột chỉ để nhìn.
     Giữ minmax chứ không viết `1fr 1fr` trần: cửa sổ hẹp nhất là 1180px,
     chia đôi còn ~570px mỗi bên, vẫn trên mức tối thiểu của cả hai cột. */
  grid-template-columns: minmax(360px, 1fr) minmax(480px, 1fr);
  gap: var(--sp-18);
  width: 100%;
  max-width: none;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  margin: 0 auto;
  align-items: stretch;
}
/* Hai cột KHÔNG còn là thẻ nổi: không nền, không viền, không đổ bóng.
   Trước đây mỗi cột là một hộp bo 24px có nền riêng, bên trong lại là các
   hộp con cũng có nền và viền — ba lớp khung lồng nhau, nhìn rối. Nay chỉ
   còn MỘT đường kẻ dọc ngăn hai cột. */
.panel {
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
}
.slide-list { border-left: 1px solid var(--line); }
.render-machine {
  width: 100%;
  justify-self: stretch;
  align-self: start;
  height: auto;
  max-height: 100%;
  min-height: 0;
  padding: var(--sp-16) var(--sp-10) var(--sp-24);
  position: static;
  overflow-y: auto;
  overscroll-behavior: contain;
  scroll-padding-bottom: 40px;
  -webkit-overflow-scrolling: touch;
}
.slide-list {
  height: 100%;
  min-height: 0;
  padding: var(--sp-16) var(--sp-10);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
.panel-head { display: flex; justify-content: space-between; margin-bottom: var(--sp-12); }
/* Tiêu đề bên trái, nút "Làm mới" đẩy sát mép phải cùng một dòng.
   `flex: 1 1 auto` là BẮT BUỘC: .list-title là con DUY NHẤT của .panel-head
   nên mặc định chỉ rộng bằng nội dung, `space-between` không có chỗ để đẩy
   và nút dính ngay sau tiêu đề thay vì ra mép phải. */
/* `nowrap` chứ KHÔNG `wrap`: cột này hẹp (một nửa cửa sổ), mà `h2` là 22px
   nên "Danh sách dự án (6)" + nút "Làm mới" vừa đủ chạm mép → flex xuống
   dòng và nút rơi xuống hàng riêng, tốn nguyên một hàng cho một nút phụ.
   Ép cùng hàng, và cho `h2` co lại được (`min-width: 0`) thay vì đẩy nút đi. */
.list-title { flex: 1 1 auto; display: flex; align-items: center; justify-content: space-between; gap: var(--sp-12); flex-wrap: nowrap; }
.list-title h2 { margin: 0; min-width: 0; white-space: nowrap; }
/* Ô tìm kiếm chiếm chỗ của tiêu đề cũ — co giãn để cụm nút bên phải giữ
   nguyên kích thước, chỉ ô này hẹp lại khi cửa sổ nhỏ. */
.list-tim {
  flex: 1 1 auto; min-width: 0; height: 32px;
  padding: 0 var(--sp-12); font: inherit; font-size: var(--text-md);
  color: var(--text); background: transparent;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.list-tim::placeholder { color: var(--text-faint); }
/* --control-line chứ KHÔNG --accent. --accent là trắng tinh ở nền tối và đen
   kịt ở nền sáng, nên vừa bấm vào ô là hiện một khung gắt nhất màn hình. Nay
   chỉ nhích LÊN MỘT BẬC so với lúc nghỉ (--line → --control-line). */
.list-tim:focus { outline: none; border-color: var(--control-line); }
/* Nút "Làm mới" ở tiêu đề: chỉ chữ + icon, không nền không viền — nó là
   thao tác phụ, không cần khung để tranh chú ý với tiêu đề ngay bên cạnh. */
.list-title .refresh-btn {
  flex: 0 0 auto;
  border: 0;
  background: none;
  color: var(--text-faint);
  padding-inline: var(--sp-6);
}
.list-title .refresh-btn:hover { background: none; color: var(--text); }
h2 { margin: 0; font-size: var(--text-3xl); letter-spacing: -0.04em; }
.tab-icon,
.field-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  flex: 0 0 auto;
  border-radius: var(--radius-pill);
  color: var(--text-soft);
  background: var(--surface-strong);
  box-shadow: none;
  font-size: var(--text-sm);
  line-height: var(--leading-none);
  text-shadow: none;
}
.field { display: grid; gap: var(--sp-8); margin: var(--sp-10) 0; }
/* Tốc độ audio: ô nhập bé vừa đủ 4 ký tự, phần còn lại nhường cho 4 mức
   chọn nhanh — cả cụm trên một dòng. */
/* flex-wrap: de dong nhac #renderSpeedNote xuong HANG RIENG khi no hien,
   thay vi chen vao giua o nhap va ba nut chon nhanh. */
.speed-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-8); min-width: 0; }
.speed-row #renderSpeed { width: 72px; flex: 0 0 auto; text-align: center; }
.speed-row .speed-presets { display: flex; gap: var(--sp-6); margin: 0; flex: 1 1 auto; min-width: 0; }
.speed-row .speed-preset { flex: 1 1 0; min-width: 0; padding-inline: var(--sp-4); }
/* Loi bao khi app phai sua so nguoi dung go — chiem tron mot hang. */
.speed-row .speed-note {
  flex: 1 0 100%;
  margin: 0;
  color: var(--warn-text);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-normal);
}
.field span { color: var(--text-faint); font-size: var(--text-sm); font-weight: var(--weight-bold); }
.field-label { display: inline-flex; align-items: center; gap: var(--sp-8); }
.mode-toggle {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-8);
  margin: var(--sp-10) auto var(--sp-12);
}
.mode-toggle label {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-8);
  min-height: 38px;
  border: 1px solid var(--control-line);
  border-radius: var(--radius-md);
  color: var(--text-soft);
  background: var(--surface);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
}
.mode-toggle label:has(input:checked) {
  color: var(--brand-text);
  background: var(--brand);
  border-color: transparent;
}
/* ĐỘ PHÂN GIẢI: KHÔNG nền, KHÔNG viền, ở cả hai trạng thái.
   Nút chọn tròn bên trong đã tự nói cái nào đang chọn. Thêm nền hay thêm
   viền đều là tín hiệu thứ hai nói lại đúng một điều — mà lại còn vẽ ra hai
   cái khung không cần thiết giữa bảng. Chỉ đậm nhạt màu chữ. */
.size-toggle label,
.size-toggle label:has(input:checked) {
  background: none;
  border-color: transparent;
}
.size-toggle label:has(input:checked) { color: var(--text); }
.config-state {
  min-width: 0;
  color: var(--text-faint);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  line-height: var(--leading-tight);
}
/* Hai lớp này được gán từ lâu trong render-page.js (`config-state ok` /
   `config-state thieu`) nhưng CHƯA HỀ có CSS — nên "Đã lưu" và "API key
   không dùng được" hiện ra cùng một màu xám nhạt như nhau. */
.config-state.ok { color: var(--ok); }
.config-state.thieu { color: var(--warn); }
.api-key-field { margin-top: var(--sp-8); }
/* Ô dán key: chữ THƯỜNG, không đậm. `.field span` đặt font-weight 900 cho
   nhãn, mà input nằm trong span `.api-key-row` + `font: inherit` nên
   placeholder lẫn key gõ vào cũng bị đậm theo — chủ dự án chê (29/08). */
.api-key-row input { font-weight: var(--weight-normal); }
.api-key-panel[hidden] { display: none !important; }
.render-primary-actions { margin-top: var(--sp-12); }
/* Khong con la hop rieng — chi cach ra bang mot duong ke tren. */
.advanced-settings {
  max-width: 520px;
  margin: var(--sp-16) auto 0;
  border: 0;
  border-top: 1px solid var(--line);
  border-radius: 0;
}
.advanced-body {
  display: grid;
  border-top: 1px solid var(--control-line-soft);
}
/* ⚠ KHÔNG gộp hai luật dưới thành một token (đã thử ở CSS bước 2, phải trả lại).
   Giữa chúng có luật `.field input:not([type="radio"]):not([type="checkbox"])`
   (độ ưu tiên 0,3,1) đặt `background: var(--field-bg)`:
     · nền tối  → luật đó THẮNG, nên `background` ngay dưới đây là luật CHẾT;
     · nền sáng → `body.theme-light …` (0,3,2) thắng cả hai.
   Bỏ luật theme-light và để token ở đây (0,2,1) là ô chọn tệp mất màu kem ở nền
   sáng — đo được bằng computed style, ảnh chụp KHÔNG thấy vì ô này đang ẩn.
   Dọn tử tế thuộc về CSS bước 3 (nhóm màu ô nhập). */
.field input[type="file"] {
  min-height: 54px;
  padding: var(--sp-8);
  color: var(--text-soft);
  background: rgba(255, 255, 255, 0.035);
}
body.theme-light .field input[type="file"] {
  background: rgba(255, 251, 244, 0.62);
}
.field input[type="file"]::file-selector-button {
  min-height: 36px;
  margin-right: var(--sp-12);
  border: 0;
  border-radius: var(--radius-md);
  padding: var(--sp-8) var(--sp-12);
  color: var(--text);
  background: var(--surface-strong);
  font-family: inherit;
  font-weight: var(--weight-bold);
  cursor: pointer;
}
.advanced-settings {
  background: var(--advanced-bg);
  box-shadow: none;
}
.advanced-body {
  gap: var(--sp-10);
  padding: var(--sp-12);
}
.advanced-body .field,
.advanced-body .check,
.advanced-body .speed-presets {
  max-width: none;
  margin-left: 0;
  margin-right: 0;
}
.advanced-body .field {
  display: grid;
  grid-template-columns: minmax(132px, 0.72fr) minmax(0, 1fr);
  align-items: center;
  gap: var(--sp-10) var(--sp-14);
  margin-top: 0;
  margin-bottom: 0;
}
.advanced-body .field:first-child { margin-top: 0; }
.advanced-body .field-label {
  align-self: center;
  gap: var(--sp-8);
  min-width: 0;
}
.advanced-body .field-label span:last-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* PHẢI loại trừ radio/checkbox: luật này vốn để làm ô nhập chữ cao 42px,
   nhưng `input` trần thì trúng cả nút tròn — nút chọn độ phân giải phình
   thành hình tròn 42px, đẩy chữ "1080 × 1920" xuống dòng. */
.advanced-body .field input:not([type="radio"]):not([type="checkbox"]),
.advanced-body .field select {
  min-height: 42px;
  border-radius: var(--radius-md);
}
.advanced-body .speed-presets {
  justify-content: flex-start;
  width: auto;
  margin-top: -2px;
  margin-left: calc(132px + 14px);
  margin-bottom: 0;
  gap: var(--sp-8);
}
/* Cùng chiều cao và cùng độ bo với ô nhập bên cạnh. Bản cũ để 34px/12px
   trong khi ô nhập là 42px/14px — đo được lệch 8px chiều cao và thụt vào
   3px so với mép trên ô nhập, nhìn như hai thứ rơi từ hai bộ giao diện. */
.advanced-body .speed-preset {
  min-height: 42px;
  padding: var(--sp-8) var(--sp-12);
  border-radius: var(--radius-md);
}
/* PHẢI đứng sau `.advanced-body .speed-presets` ở trên và phải đủ 3 lớp
   chọn: luật kia có `margin-left: calc(132px + 14px)` (chừa chỗ cho nhãn
   khi cụm nút nằm ở DÒNG RIÊNG) cộng `flex-wrap: wrap`, nên khi nhét vào
   cùng dòng thì 4 nút bị đẩy sang phải rồi xuống dòng thành một cột dọc. */
.advanced-body .speed-row .speed-presets {
  margin-left: 0;
  flex-wrap: nowrap;
  justify-content: stretch;
}
.advanced-body .speed-row .speed-preset { padding: var(--sp-8) var(--sp-6); }
/* `.mode-toggle` gốc có `margin: 10px auto 12px` để đứng riêng một khối.
   Nằm trong ô lưới của `.field` thì lề đó đẩy nó lệch khỏi nhãn bên trái. */
.advanced-body .size-toggle { margin: 0; }
.advanced-body .size-toggle label { min-height: 42px; border-radius: var(--radius-md); white-space: nowrap; }

/* ── HAI HÀNG THÔNG SỐ dùng CHUNG một lưới ba cột ────────────────────────
   Cột 1 và 3 chia đôi, cột giữa rộng 1px chính là VẠCH NGĂN.
   Vì sao phải chung lưới: trước đây hàng trên là lưới còn hàng dưới là flex
   space-between, nên hai vạch không thẳng nhau — đo được lệch 8px (x=1104 và
   x=1095). Chung một lưới thì chúng trùng khít, và mép trái của "Chất lượng
   audio" với "Độ phân giải" cũng thẳng theo luôn.
   PHẢI đứng sau .advanced-body .field: cùng (0,2,0) nên hoà nhau, luật sau mới
   bỏ được lưới nhãn|ô của luật kia. */
.advanced-body .field-hai-cot,
.advanced-body .field-doi {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 1px minmax(0, 1fr);
  gap: var(--sp-10) var(--sp-14);
}
.advanced-body .field-hai-cot { align-items: stretch; }
.advanced-body .field-doi { align-items: center; }
/* align-self: stretch là BẮT BUỘC ở hàng dưới. Hàng đó căn giữa theo chiều
   dọc (align-items: center), mà vạch là một span RỖNG — không có chữ thì
   không có chiều cao, đo được đúng 0px, tức vạch tàng hình. Hàng trên đã
   stretch sẵn nên không dính. */
.field-hai-cot .hc-ngan,
.field-doi .doi-ngan { align-self: stretch; background: var(--line); }

/* Cột dựng video hẹp thì xếp dọc và bỏ vạch — vạch chỉ có nghĩa khi nó đứng
   GIỮA hai cụm. Dùng @media chứ không @container: container-type phải đặt lên
   chính hàng này, mà một container KHÔNG tự hỏi được bề ngang của chính nó
   (@container chỉ tô được cho con cháu). Đặt lên .advanced-body thì càng hỏng:
   container-type kéo theo contain: layout, biến nó thành khối chứa cho mọi con
   position: fixed — bảng chọn giọng và popup ID định vị bằng đúng cách ấy.
   Hai mốc dưới đây là hai mốc CÓ SẴN của bố cục: 1400px là lúc cột phải hẹp
   lại, 1060px là lúc cả bảng xếp dọc nên cột lại rộng ra. Đo: viewport 1456 →
   cột 679px (đủ), 1189 → 546px (không đủ), dưới 1060 → xếp dọc ~850px (đủ). */
@media (max-width: 1400px) {
  .advanced-body .field-hai-cot,
  .advanced-body .field-doi { grid-template-columns: minmax(0, 1fr); }
  .field-hai-cot .hc-ngan,
  .field-doi .doi-ngan { display: none; }
}
@media (max-width: 1060px) {
  .advanced-body .field-hai-cot,
  .advanced-body .field-doi { grid-template-columns: minmax(0, 1fr) 1px minmax(0, 1fr); }
  .field-hai-cot .hc-ngan,
  .field-doi .doi-ngan { display: block; }
}

.field-hai-cot .hc-o { display: grid; gap: var(--sp-8); min-width: 0; align-content: start; }
.field-doi .doi-o { display: flex; align-items: center; gap: var(--sp-10); min-width: 0; }
.field-doi .field-label { flex: 0 0 auto; }
/* MỌI nhãn trong khối Thông số đều ĐẬM, không riêng hai hàng dưới. --muted
   (55%) làm nhãn nhạt ngang với chữ phụ nên mắt không bắt được đâu là mốc.
   Đổi MÀU chứ không tăng độ đậm — thang độ đậm dừng ở 700 (xem tokens.css)
   và .field span đã ở đúng 700 rồi.

   Dùng dấu > và ba :not() chứ không quét mọi span con:
     · .field-icon    — icon dẫn nhãn, cố ý nhạt hơn chữ;
     · .config-state  — câu trạng thái/lỗi, có màu riêng của nó;
     · .field-hint    — chú thích trong ngoặc, phải nhạt;
   và dấu > chừa luôn #elevenVoiceCount nằm LỒNG trong span chữ — con số
   "(20)" cạnh "Giọng đọc" vẫn giữ màu mờ như cũ. */
.render-machine .advanced-body .field-label > span:not(.field-icon):not(.config-state):not(.field-hint) {
  color: var(--text);
}
/* Nhãn có ba mảnh (tên · câu trạng thái · nút ID) nên phải cho xuống dòng,
   không thì câu báo lỗi dài đẩy ngang làm vỡ cột. */
.field-hai-cot .field-label { flex-wrap: wrap; }
/* Nút "Tự điền ID giọng" dạt hẳn sang mép phải của CHÍNH cột Giọng đọc.
   Trong markup nó đứng SAU câu trạng thái, không phải trước: đứng trước thì
   margin-left:auto đẩy luôn cả câu trạng thái ra sau nó. */
.field-hai-cot .voice-id-mo { margin-left: auto; }

/* Ba nút chọn nhanh CO theo chữ. Luật gốc cho mỗi nút flex 1 1 0 để chúng chia
   đều cả hàng — đo được mỗi nút phình ra 96px và nút cuối cách ô nhập gần
   400px. Chúng là lối tắt của chính ô số bên trái nên phải đứng sát ô đó. */
.field-doi .speed-row .speed-presets { flex: 0 0 auto; }
.field-doi .speed-row .speed-preset { flex: 0 0 auto; padding: var(--sp-8) var(--sp-12); }
/* mode-toggle gốc là lưới hai cột bằng nhau kéo hết bề ngang; nằm trong hàng
   ngang này thì phải co theo chữ. */
.field-doi .size-toggle { grid-template-columns: auto auto; }

/* Vạch ngang chia bảng thông số: trên là giọng đọc, dưới là thông số video. */
.advanced-body .hang-ngan {
  height: 1px;
  margin: var(--sp-4) 0;
  background: var(--line);
}
/* ElevenLabs + tab "Tải file" là trường hợp DUY NHẤT không có gì phía trên
   vạch: khối cấu hình của engine đó chỉ chứa phần "API", tab "Tải file" thì
   nó ẩn sạch. Vạch lúc ấy ngăn cách một bên trống — bỏ đi.
   Dùng :has() để không phải nối thêm dây JS: điều kiện đã nằm sẵn trong DOM
   dưới dạng thuộc tính hidden mà syncElevenMode bật/tắt. */
.advanced-body:has([data-advanced-engine="elevenlabs"]:not([hidden]) > [data-eleven-mode-pane="tts"][hidden]) .hang-ngan {
  display: none;
}

.advanced-body .check {
  min-height: 40px;
  margin-top: var(--sp-8);
  margin-bottom: var(--sp-8);
  padding: var(--sp-8) var(--sp-12);
  border: 1px solid var(--control-line-soft);
  border-radius: var(--radius-md);
  background: var(--surface);
}
.file-picker {
  width: 100%;
  min-height: 54px;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: var(--sp-12);
  border: 1px solid var(--control-line);
  border-radius: var(--radius-md);
  padding: var(--sp-8);
  color: var(--text-soft);
  background: var(--field-file-bg);
  cursor: pointer;
}
.file-picker input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.file-picker-button {
  min-height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);
  padding: var(--sp-8) var(--sp-14);
  color: var(--text);
  background: var(--surface-strong);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  white-space: nowrap;
}
.advanced-settings .advanced-body {
  max-height: none;
  overflow: visible;
  padding-bottom: var(--sp-20);
}
@media (max-width: 720px) {
  .advanced-body .field { grid-template-columns: 1fr; }
  .advanced-body .speed-presets { margin-left: 0; }
}
/* ⚠ ĐỪNG đặt lại `background`/`box-shadow` cho `.field-icon` ở đây: mọi
   `.field-icon` đều nằm trong `.render-machine`, mà render-layout.css có
   `.render-machine .field-icon { background: none }` — CÙNG độ ưu tiên (0,2,0)
   nhưng nạp SAU nên nó thắng. Ba luật cũ ở đây (nền xanh + vệt sáng) đã nằm
   chết như vậy nhiều tháng; đo computed style thấy nền thật là trong suốt.
   Icon này chỉ còn là một ký hiệu, nên `color` phải đọc được TRÊN NỀN TRANG. */
.field-label .field-icon {
  color: var(--text-soft);
  font-size: var(--text-xs);
}
/* Loại trừ radio/checkbox: đây là luật tạo ô nhập chữ (cao 40px, viền, nền
   riêng). `input` trần thì trúng luôn nút tròn — đo được nút chọn độ phân
   giải thành 46×40px, trong khi nút tròn bình thường chỉ 13×13px. */
.field input:not([type="radio"]):not([type="checkbox"]),
.field select {
  width: 100%;
  min-height: 40px;
  border: 1px solid var(--control-line);
  border-radius: var(--radius-md);
  padding: var(--sp-8) var(--sp-12);
  color: var(--text);
  background: var(--field-bg);
}

/* ⚠ PHẢI TÔ RIÊNG `option` VÀ `optgroup` — ĐỪNG BỎ.
   Danh sách xổ xuống của `<select>` là một lớp vẽ RIÊNG, không nằm trong
   cây bố cục của trang. Nó KHÔNG thừa kế `color`/`background` đặt trên
   `select` ở trên, và dòng `button, input, select, textarea { color: inherit }`
   cũng không nhắc tới `option` nên chúng rơi về màu mặc định.
   Hậu quả đo được ở giao diện tối: chữ xám nhạt trên nền nhạt, gần như
   không đọc nổi — thấy rõ nhất ở ô chọn giọng ElevenLabs vì nó có cả
   `optgroup` ("Tiếng Việt (Voice Library)" / "Dựng sẵn").
   `--field-bg` trong suốt một phần nên KHÔNG dùng được ở đây: lớp vẽ này
   không có gì phía sau để trong suốt lên, sẽ ra nền đen thui hoặc trắng
   toát tuỳ hệ. Dùng `--option-bg` đục hẳn. */
select option {
  background-color: var(--option-bg);
  color: var(--text);
}
select optgroup {
  background-color: var(--option-bg);
  color: var(--text-faint);
  font-weight: var(--weight-bold);
}
.speed-presets {
  display: flex;
  align-items: center;
  justify-content: center;
  width: fit-content;
  max-width: 100%;
  flex-wrap: wrap;
  gap: var(--sp-8);
  margin-top: -2px;
  margin-bottom: var(--sp-12);
}
/* KHÔNG có nền ở cả hai trạng thái — nút đang chọn nhận diện bằng VIỀN
   xanh. Bản cũ ba nút nền xám, một nút nền xanh đặc: nhìn ra bốn khối màu
   chứ không phải một dãy lựa chọn, và chữ trên nền đặc khó đọc hơn hẳn.
   (Luật này phải đứng SAU mọi luật `.speed-preset` khác trong file — cùng
   độ ưu tiên thì cái sau thắng. Đặt ở đầu file là bị đè, đã đo.) */
.speed-preset {
  min-height: 34px;
  padding: var(--sp-8) var(--sp-14);
  border: 1px solid var(--control-line-faint);
  border-radius: var(--radius-md);
  color: var(--text-soft);
  background: none;
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  cursor: pointer;
}
/* Nút chưa chọn LÙI LẠI bằng độ mờ; nút đang chọn nhận diện bằng một mảng
   nền RẤT NHẠT, không phải viền xanh. Viền xanh vẽ thêm một đường kẻ giữa
   dãy nút, còn nền nhạt thì chỉ làm sáng lên đúng chỗ cần. */
.speed-preset { opacity: 0.42; }
.speed-preset:hover { opacity: 0.75; }
.speed-preset.active {
  color: var(--text);
  background: color-mix(in srgb, var(--brand) 20%, transparent);
  border-color: transparent;
  opacity: 1;
}
.selected-box {
  max-width: 440px;
  margin: 0 auto;
  border: 1px solid var(--control-line-soft);
  border-radius: var(--radius-lg);
  padding: var(--sp-10) var(--sp-12);
  background: var(--surface);
  text-align: center;
}
.selected-box strong { display: block; margin-bottom: var(--sp-4); }
.selected-box span { display: block; color: var(--text-faint); font-size: var(--text-sm); }
.form-actions,
.actions { display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-6); flex-wrap: nowrap; white-space: nowrap; }
.status {
  display: flex;
  align-items: center;
  gap: var(--sp-8);
  border: 0;
  padding: var(--sp-8) var(--sp-12);
  background: none;
  color: var(--text-soft);
  font-size: var(--text-md);
  line-height: var(--leading-snug);
  margin-top: var(--sp-10);
}
/* BẮT BUỘC: luật trên khai display nên nó ĐÈ thuộc tính hidden — thiếu dòng
   này thì dòng thông báo rỗng vẫn chiếm một khoảng trống có viền. */
.status[hidden] { display: none !important; }
.status-ic { display: inline-flex; flex: 0 0 auto; }
.status-ic svg { width: 15px; height: 15px; display: block; }
.render-machine .status {
  margin: var(--sp-10) auto 0;
  justify-content: center;
}
.status.good { color: var(--good-text); }
.status.warn { color: var(--warn-text); }
/* Dòng HỎNG cũng KHÔNG vỏ — chỉ chữ đỏ, và icon dẫn đầu đã là dấu cảnh báo.
   Bản trước đổ nền đỏ nhạt cho nó nổi hơn hai loại kia, nhưng khi cả ba loại
   còn lại là chữ trần thì một mình nó có hộp lại thành lạc lõng chứ không
   thành nhấn mạnh. Màu đỏ trên nền phẳng đã đủ bắt mắt. */
.status.bad { color: var(--danger-text); }
.tabs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-8);
  margin-top: var(--sp-12);
  margin-bottom: var(--sp-12);
}
.tab,
.refresh-btn,
.delete-project-btn,
.icon-btn {
  border: 1px solid var(--control-line-faint);
  border-radius: var(--radius-md);
  padding: var(--sp-8) var(--sp-10);
  cursor: pointer;
  color: var(--text-soft);
  background: var(--surface);
  font-weight: var(--weight-bold);
  gap: var(--sp-6);
  min-height: 36px;
  font-size: var(--text-sm);
  line-height: var(--leading-none);
  flex: 0 0 auto;
}
.tab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-8);
}
.tab.active .tab-icon {
  text-shadow: none;
}
.tab[data-engine="elevenlabs"] .tab-icon {
  color: var(--text-soft);
  background: var(--surface-strong);
}
.tab[data-engine="edgetts"] .tab-icon {
  color: var(--text);
  background: var(--surface-strong);
}
.tab[data-engine="beeknoee"] .tab-icon {
  color: var(--text);
  background: var(--surface-strong);
}
.tab.active .tab-icon { color: var(--text); background: var(--surface-strong); box-shadow: none; }
.icon-btn { display: inline-flex; align-items: center; justify-content: center; text-decoration: none; }
.icon-btn[hidden],
.small-link[hidden] { display: none !important; }
.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: var(--radius-pill);
  /* ⚠ MÀU NÀY ĐÈ LÊN MÀU CỦA NÚT CHA — và nét SVG vẽ bằng `currentColor`, nên
     đặt màu cho nút mà không cho `.btn-icon` thì ICON VẪN XÁM. Hỏng im lặng:
     `getComputedStyle(nut).color` trả về đúng màu mình đặt, chỉ có điểm ảnh là
     sai. Đã dính thật — nút "có video" tô xanh `--ok` mà chụp màn hình đếm ra
     0 điểm ảnh xanh (16/09/2026). Nút nào có màu riêng thì PHẢI thêm
     `.<nút> .btn-icon { color: inherit; }` — xem `.tp-chay`, `.tp-nut`,
     `.nhan-khung`, `.tb-khung`. */
  color: var(--text-soft);
  /* KHÔNG nền. Sửa TẠI GỐC thay vì gỡ từng nút một: bản gốc cho mọi
     `.btn-icon` một chấm tròn xám, nên cứ thêm nút mới là lại mọc thêm một
     chấm và phải đi gỡ lần nữa. Nút nào thật sự cần nền (chỉ "Tạo video")
     đã có luật riêng của nó. */
  background: none;
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  line-height: var(--leading-none);
  box-shadow: none;
  text-shadow: none;
}
.small-link .btn-icon {
  color: var(--text-soft);
  background: var(--surface-strong);
}
.theme-toggle .btn-icon {
  color: var(--text-soft);
  background: var(--surface-strong);
  box-shadow: none;
}
/* Chip tròn của nút "Làm mới": KHÔNG nền. Nút này vốn trong suốt, để lại
   một chấm xám bên trong nhìn như sót lại của kiểu cũ.
   Luật dùng id nên nó thắng mọi luật class — sửa ở chính đây, đừng thêm
   luật class ở nơi khác rồi tưởng đã đè được (đã đo: vẫn ra 0.067). */
#refreshList .btn-icon {
  color: var(--text-soft);
  background: none;
  box-shadow: none;
}
/* CHỈ nút "Tạo video" — nó là nút duy nhất có nền đặc màu nhấn, nên
   chip bên trong phải lấy màu theo nền đó chứ không theo nền trang.
   Nút Upload từng nằm chung luật này; nay nó là nút trong suốt nên chip
   tròn xanh đặc lạc lõng hẳn giữa mấy nút phẳng bên cạnh. */
#startRender .btn-icon,
.start .btn-icon {
  /* Chip TRẮNG, icon xanh lấy ngay đầu dải nền — nút nay là dải xanh nên chip
     phải bật lên khỏi nó, không thể dùng --primary-* (trắng trên trắng). */
  color: var(--nut-tao-dau);
  background: var(--nut-tao-chu);
  box-shadow: none;
}
/* KHÔNG có bóng đổ. Bản trước đặt một vệt sáng hổ phách
   (`--warn` 20%, nhoè 18px) dưới chip icon của "Mở video cuối" và "Mở trong
   thư mục". Trên nền sáng nó là một vệt nâu mờ không ai để ý; trên nền tối nó
   thành một mảng nâu bẩn lấm dưới cái icon — chủ dự án chụp lại và gọi đúng
   tên: "bóng đen, nhìn đen xì". Chip này là một hình tròn trung tính, không có
   lý do gì để phát sáng màu cảnh báo. */
.small-link.icon-btn .btn-icon {
  color: var(--text);
  background: var(--surface-strong);
  box-shadow: none;
}
/* Nút "Mở video cuối": KHÔNG chip tròn nữa — chỉ còn hình tam giác TÔ ĐẶC
   màu xanh lá, giống hệt nút play trong danh sách dự án. Chip đặc cộng hình
   rỗng bên trong là hai lớp cho một tín hiệu; tô đặc thì mảng màu liền một
   khối, nhìn từ xa vẫn thấy. */
.row-video-slot .small-link.icon-btn .btn-icon,
a.small-link.icon-btn[href*="/output/"] .btn-icon {
  color: var(--ok);
  background: none;
  box-shadow: none;
}
.row-video-slot .small-link.icon-btn .btn-icon svg,
a.small-link.icon-btn[href*="/output/"] .btn-icon svg,
.project-row .icon-only.play .ic-o svg { fill: currentColor; }
/* Chip tròn sau icon: BỎ. Nút này trong suốt như "Mở video cuối" bên cạnh,
   để lại một chấm xám (kèm bóng tím sót từ bản cũ) là ba nút cùng hàng mà
   một nút có thêm chi tiết thừa. */
.reveal-output-btn .btn-icon {
  color: var(--text-soft);
  background: none;
  box-shadow: none;
}
/* Ba nút cùng hàng thì cùng một nền. Nút này thừa `background: var(--surface)`
   của .icon-btn nên nó xám hơn hai nút bên cạnh — đo được rgba(0,0,0,0.035)
   trong khi "Mở video cuối" trong suốt. */
.render-primary-actions .reveal-output-btn { background: none; }
.stop-render-btn {
  color: var(--nut-dung-chu);
  background: var(--nut-dung-nen);
  border-color: var(--nut-dung-vien);
}
/* Icon KHÔNG còn chip đỏ đặc: nút nay đã là một mảng hồng có viền, thêm một
   chấm đỏ đặc bên trong là hai lớp màu chồng nhau trong một nút bé. Icon lấy
   luôn màu chữ. */
.stop-render-btn .btn-icon {
  color: inherit;
  background: none;
  box-shadow: none;
  text-shadow: none;
}
.delete-project-btn .btn-icon {
  color: #fff5f5;
  background: var(--danger-solid);
  box-shadow: 0 7px 18px color-mix(in srgb, var(--danger) 22%, transparent);
  text-shadow: none;
}
.icon-btn.disabled,
.icon-btn:disabled {
  opacity: 0.42;
  cursor: not-allowed;
}
.icon-btn.disabled {
  pointer-events: none;
}
.tab.active {
  color: var(--brand-text);
  background: var(--brand);
  border-color: transparent;
  box-shadow: 0 10px 28px var(--accent-glow);
}
.refresh-btn.dang-quay .btn-icon { animation: beeQuay 0.7s linear infinite; }
@keyframes beeQuay { to { transform: rotate(360deg); } }
.refresh-btn:disabled { cursor: progress; }
.refresh-btn .btn-icon {
  text-shadow: none;
}
/* Nut phu: nen xam nhat, CHU mau chu binh thuong.
    Truoc day dat color: var(--accent-contrast) — mau danh cho chu NAM TREN
    nen nhan. Doi bang mau don sac thi nen nhan la trang/den, nen chu bi
    trang tren nen xam nhat, gan nhu khong doc duoc. */
/* Nút phụ: trong suốt, chỉ hiện khung khi rê chuột — cùng lối với nút
   Edit/Copy Script trong danh sách và nút ▶/thùng rác. Cả giao diện chỉ
   còn MỘT nút có nền đặc là "Tạo video". */
.refresh-btn {
  min-height: 42px;
  color: var(--text-soft);
  background: none;
  border-color: transparent;
  box-shadow: none;
}
.refresh-btn:hover {
  background: var(--surface);
  border-color: var(--control-line-soft);
  color: var(--text);
}
.refresh-btn .btn-icon {
  background: none;
  color: inherit;
  box-shadow: none;
}
.refresh-btn.guide-header-btn {
  color: var(--text-soft);
  background: none;
  box-shadow: none;
}
.delete-project-btn {
  color: var(--delete-text);
  background: var(--danger-bg);
  border-color: var(--danger-line);
}
.delete-project-btn:hover { background: var(--danger-bg-hover); }
.check { display: flex; align-items: center; gap: var(--sp-8); margin: var(--sp-8) 0 var(--sp-12); color: var(--text-soft); font-size: var(--text-md); }
/* Nút hành động CHÍNH — chỉ một nút duy nhất trên màn hình mang dải xanh
   thương hiệu, để mắt biết bấm vào đâu. */
/* Cùng cỡ chữ, cùng cỡ icon, cùng chiều cao với nút "Mở trong thư mục" ngay
   cạnh (`.icon-btn`: 12px / 36px / icon 20px). Trước đây to hơn hẳn nên hai
   nút đứng cạnh nhau nhìn như hai cỡ khác nhau. Khác biệt để ở MÀU, không
   để ở kích thước. */
.start {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-8);
  min-height: 36px;
  border: 0;
  border-radius: var(--radius-md);
  padding: 0 var(--sp-16);
  color: var(--nut-tao-chu);
  /* Nền là ẢNH, không phải dải màu CSS: dải CSS chỉ nội suy thẳng giữa mấy
     điểm dừng nên vệt sáng chạy chéo trong ảnh gốc không dựng lại được.
     Ảnh đã CẮT BỎ hai đầu bo tròn của bản gốc (lề 60px ngang, 12px dọc) để
     còn một hình chữ nhật ĐẶC hoàn toàn — bản gốc bo góc sẵn kèm alpha, kéo
     giãn ra là hở bốn góc trong suốt. Bo góc để CSS lo.
     Dải màu vẫn giữ làm lớp DƯỚI: ảnh chưa kịp tải hay hỏng đường dẫn thì nút
     vẫn xanh chứ không phải một ô trắng. */
  background-color: var(--nut-tao-giua);
  background-image: var(--nut-tao-anh),
    linear-gradient(100deg, var(--nut-tao-dau), var(--nut-tao-giua) 55%, var(--nut-tao-cuoi));
  background-size: 100% 100%, auto;
  background-repeat: no-repeat;
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  line-height: var(--leading-none);
  cursor: pointer;
}
/* Nút chép tên dự án: nằm sát tên, chỉ hiện khi rê chuột vào hàng — để nó
   không tranh chỗ với chính cái tên. Chép xong đổi sang dấu tích XANH LÁ
   trong 1,4 giây rồi trở lại. */
/* Tên + nút chép ĐỨNG CHUNG một hàng con, KHÔNG đụng .project-main.
   .project-main là lưới một cột (tên ở hàng 1, nhãn slide ở hàng 2); nhét
   thêm nút vào đó là nó chiếm hàng thứ ba, đẩy cả dòng cao lên — đã chụp
   thấy dấu tích rơi xuống dưới tên. */
.project-title-row { display: flex; align-items: center; gap: var(--sp-6); min-width: 0; }
/* Số thứ tự trong hàng tiêu đề. ẨN theo mặc định — bố cục rộng vẫn dùng cột
   `.row-index`; chỉ bố cục ba cột mới bật nó lên (xem `bien-tap.css`), vì ở đó
   cột STT đã bỏ để lấy lại lề trái. Hai chỗ không bao giờ cùng hiện. */
.stt-hang { display: none; }
.project-title-row .project-name { min-width: 0; }
.copy-name-btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--text-faint);
  background: none;
  opacity: 0;
  cursor: pointer;
  transition: opacity 0.12s ease, color 0.12s ease;
}
.project-row:hover .copy-name-btn,
.copy-name-btn:focus-visible,
.copy-name-btn.da-copy { opacity: 1; }
.copy-name-btn:hover { color: var(--text); background: var(--surface); }
/* THÙNG RÁC: giấu cho tới khi rê vào hàng, đúng nếp của nút chép tên ngay
   trên. Nó là việc KHÔNG THỂ HOÀN TÁC mà lại nằm cạnh hai nhãn đọc suốt ngày;
   hiện thường trực thì mỗi hàng có một nút đỏ mời bấm nhầm.
   ⚠ `:focus-visible` phải nằm trong danh sách hiện: giấu bằng `opacity` thì
   nút vẫn trong luồng Tab, không có luật này là người dùng bàn phím tab tới
   một nút vô hình. */
.project-row .delete-project-btn { opacity: 0; transition: opacity 0.12s ease; }
.project-row:hover .delete-project-btn,
.project-row .delete-project-btn:focus-visible { opacity: 1; }
.copy-name-btn .btn-icon { width: 16px; height: 16px; background: none; color: inherit; }
.copy-name-btn .btn-icon svg { width: 14px; height: 14px; }
.copy-name-btn.da-copy,
.copy-name-btn.da-copy:hover { color: var(--ok); background: none; }
.render-machine .form-actions { justify-content: center; }
/* Đang dựng: GIỮ NGUYÊN dải màu, không làm mờ. Nút lúc đó đọc "Đang dựng…" —
   đó là trạng thái ĐANG CHẠY, thứ đáng nhìn nhất màn hình; làm mờ đi thì nó
   trông như hỏng. Bản cũ để opacity 0.6 nên nút xám xịt suốt lượt render. */
.start:disabled { cursor: wait; }
/* Khoá vì THIẾU CẤU HÌNH, khác hẳn khoá vì đang chạy: `cursor: wait` ở luật
   trên nói "chờ chút", mà đây là "bạn phải làm gì đó trước đã". Lý do cụ thể
   nằm ở `title`, rê chuột vào là thấy.

   GIỮ NGUYÊN ảnh nền, chỉ hạ độ đục. Bản trước xoá sạch dải màu cho nút
   phẳng và xám, nhưng như thế nút đổi HÌNH DẠNG chứ không phải đổi trạng
   thái: người dùng thấy hai nút khác hẳn nhau ở hai thời điểm và phải đoán
   chúng có phải một không. Mờ thì vẫn là đúng nút ấy, chỉ lùi ra sau — và
   con trỏ hình cấm đã nói rõ "chưa bấm được", không cần màu nói lại. */
.start:disabled.thieu-nguon {
  cursor: not-allowed;
  opacity: 0.45;
}
.render-state {
  width: 100%;
  margin: var(--sp-14) 0 0;
  /* Nền phẳng, và chỉ KẺ TRÊN + KẺ DƯỚI, không bo góc.
     Khung đủ bốn cạnh biến bảng thành một hộp thoại chen vào giữa cột; hai
     đường kẻ ngang thì chỉ nói "đoạn này là một phần riêng" mà vẫn để bảng
     chạy hết bề ngang cột, thẳng lề với mọi thứ bên trên. */
  border: 0;
  border-top: 1px solid var(--control-line-faint);
  border-bottom: 1px solid var(--control-line-faint);
  /* Lề trong chỉ còn trên/dưới — bỏ lề ngang thì nội dung thẳng cột với khối
     Thông số ngay phía trên, không thụt vào 14px. */
  padding: var(--sp-14) 0;
  color: var(--good-text);
  background: none;
}
.state-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-10);
  margin-bottom: var(--sp-12);
  color: var(--text);
  font-size: var(--text-base);
}
.state-head strong { font-weight: var(--weight-bold); }
/* Ô vuông bo góc mang dấu hiệu "đang có việc chạy" — cùng dải màu với nút
   Tạo video, nên nhìn là biết khối này nói về lượt vừa bấm. */
.rs-bieu {
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-md);
  background-color: var(--nut-tao-giua);
  background-image: url("../assets/nen-nut-tao-video.webp");
  background-size: 100% 100%;
}
.rs-bieu .btn-icon { width: auto; height: auto; color: var(--nut-tao-chu); background: none; }
.rs-bieu .btn-icon svg { width: 17px; height: 17px; }
/* Hai viên thông tin. Viên TRÁI là việc đang làm (đổi liên tục), viên PHẢI là
   tổng giờ — đẩy hẳn sang mép phải để hai thứ không lẫn vào nhau. */
.rs-vien {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-6);
  padding: var(--sp-6) var(--sp-12);
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
}
.rs-chay { color: var(--brand-dam); background: var(--brand-nhat); }
/* Lúc ĐÃ XONG, viên trái không còn là "việc đang làm · %" mà là một câu ghi
   chú dài. Câu đó bọc trong viên xanh thì đọc ra như một nhãn trạng thái THỨ
   HAI, tranh chỗ với chính tiêu đề ngay bên trái nó. Bỏ vỏ, hạ màu, thêm dấu
   chấm ngăn — thành mẩu phụ chú đứng sau tiêu đề, đúng vai nó đang làm.
   Chỉ đổi ở tone `done`: lúc đang chạy viên xanh vẫn là chỗ mắt cần bám. */
.render-state.done .rs-chay {
  padding: 0;
  color: var(--text-soft);
  background: none;
  font-weight: var(--weight-medium);
}
.render-state.done .rs-chay::before {
  content: '·';
  margin-right: var(--sp-8);
  color: var(--text-faint);
  font-weight: var(--weight-bold);
}
.rs-thoigian {
  margin-left: auto;
  color: var(--text-soft);
  background: var(--surface);
}
/* Khi viên bên phải đang nói DỰ KIẾN chứ không phải giờ đã chạy thì đậm hơn
   một bậc — đó là con số người ta ngồi chờ, không phải số liệu phụ. */
.rs-thoigian.rs-dukien { color: var(--text); }
.rs-vien[hidden] { display: none; }
/* ---- Vạch 5 bước render ----------------------------------------------
   Năm bước cố định, hiện SẴN ngay từ đầu rồi sáng dần: người dùng thấy
   trước còn bao nhiêu việc, thay vì một dòng chữ đổi liên tục chẳng biết
   đang ở đâu trong hành trình. Tám bước bên trong máy được gộp còn năm —
   ba bước kỹ thuật (dựng trang, tính mốc, quay) người dùng không cần phân
   biệt, với họ chỉ là "đang quay hình". */
.render-steps {
  display: grid;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-10);
  padding: 0;
  list-style: none;
}
.render-steps li {
  display: grid;
  /* huy hiệu · nội dung · viên trạng thái · giờ */
  grid-template-columns: 26px minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--sp-12);
  padding: var(--sp-8) var(--sp-10);
  border-radius: var(--radius-md);
  /* Chữ bước CHƯA tới lượt vẫn phải đọc được: --text-faint quá mờ, người
     dùng không đọc nổi năm bước sắp làm gì. */
  color: var(--text-soft);
  font-size: var(--text-md);
  line-height: var(--leading-snug);
  transition: color var(--dur-slow) ease;
}
/* Huy hiệu đầu dòng. Số do JS điền chứ không counter: bước ĐÃ XONG thay số
   bằng dấu tích, mà counter thì không đổi nội dung theo lớp được. */
.rs-so {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  color: var(--text-faint);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
  transition: all var(--dur-slow) ease;
}
.render-steps li { position: relative; }
.render-steps li:not(:last-child)::after {
  content: '';
  position: absolute;
  /* 10px lề trong + 13px nửa huy hiệu − 1px nửa nét = 22px */
  left: 22px;
  top: calc(50% + 16px);
  bottom: -12px;
  border-left: 2px dashed var(--line);
}
.render-steps li.done::after { border-left-style: solid; border-left-color: var(--ok); }
.rs-noi { display: grid; gap: var(--sp-2); min-width: 0; }
.rs-ten { font-size: var(--text-md); font-weight: var(--weight-bold); }
/* Viên trạng thái của từng bước. */
.rs-trang {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-6);
  padding: var(--sp-4) var(--sp-10);
  border-radius: var(--radius-pill);
  color: var(--text-faint);
  background: var(--surface);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  white-space: nowrap;
}
.rs-trang-ic { display: inline-flex; }

/* Chân bảng: nhắc đang dựng cho deck nào. Hai dòng chứ không một — tên deck
   là chuỗi dài có gạch nối, ghép vào cuối câu thì câu bị đẩy tràn. */
.rs-chan {
  display: flex;
  align-items: center;
  gap: var(--sp-10);
  margin-top: var(--sp-12);
  padding-top: var(--sp-12);
  border-top: 1px solid var(--control-line-faint);
  color: var(--text-faint);
  font-size: var(--text-sm);
}
.rs-chan .btn-icon { flex: 0 0 auto; color: var(--text-faint); background: none; }
.rs-chan-chu { display: grid; gap: var(--sp-2); min-width: 0; }
.rs-chan-chu b {
  color: var(--text);
  font-weight: var(--weight-bold);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rs-chan[hidden] { display: none; }
.rs-trang-ic svg { width: 14px; height: 14px; }
/* Giờ của từng bước: cột hẹp, số canh phải để năm dòng thẳng cột với nhau. */
.rs-gio {
  min-width: 46px;
  color: var(--text-faint);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* ĐÃ XONG: huy hiệu xanh lá đặc, viên trạng thái xanh lá nhạt. */
.render-steps li.done { color: var(--text-soft); }
.render-steps li.done .rs-so {
  border-color: transparent;
  background: var(--ok);
  color: var(--on-ok);
}
/* Icon căn giữa ô bằng chính lưới của .rs-so; line-height 0 để hộp dòng của
   span không đẩy hình lệch xuống. */
.rs-so-ic { display: flex; }
.rs-so-ic svg { width: 15px; height: 15px; display: block; }
/* Bỏ nền viên cho bước ĐÃ XONG: năm dòng mà ba dòng có mảng xanh thì cả bảng
   rối màu, trong khi bước đã xong là thứ KHÔNG cần nhìn nữa. Giữ nền cho mỗi
   bước đang chạy — đó mới là chỗ đáng để mắt dừng lại. */
.render-steps li.done .rs-trang { color: var(--good-text); background: none; }

/* ĐANG CHẠY: cả hàng có nền, huy hiệu tô màu nhấn, viên trạng thái xanh dương.
   Nền cả hàng là thứ dẫn mắt nhanh nhất — không cần đọc chữ cũng biết đang ở
   bước nào. */
.render-steps li.current {
  color: var(--text);
  background: var(--surface);
}
.render-steps li.current .rs-so {
  border-color: transparent;
  background: var(--brand-dam);
  color: var(--nut-tao-chu);
}
.render-steps li.current .rs-trang { color: var(--brand-dam); background: var(--brand-nhat); }
.render-steps li.current .rs-trang-ic { animation: rsQuay 1.1s linear infinite; }
@keyframes rsQuay { to { transform: rotate(360deg); } }
.render-steps li .step-note {
  color: var(--text-soft);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
}
/* Hỏng thì bước đang chạy chuyển đỏ — chỉ bước đó, các bước xong vẫn xanh. */
.render-state.failed .render-steps li.current { color: var(--danger-text); }
/* Viên trạng thái của bước đang dở lúc HỎNG / ĐÃ DỪNG: bỏ màu xanh "đang
   chạy". Chữ trên viên cũng đổi theo (xem TRANG_DANG_DO ở jobs.js) — để xanh
   thì hai dấu hiệu nói ngược nhau: chấm số đỏ mà viên bảo vẫn đang chạy. */
.render-state.failed .render-steps li.current .rs-trang {
  color: var(--danger-text);
  background: var(--danger-bg);
}
.render-state.cancelled .render-steps li.current .rs-trang {
  color: var(--text-soft);
  background: var(--surface);
}
/* Lúc HỎNG, dòng chi tiết mang LÝ DO hỏng — chỉ đổi MÀU CHỮ, không đổ nền.
   Icon dẫn đầu đã là dấu cảnh báo (xem ICON_LOG_TONE ở jobs.js), hai dấu hiệu
   là đủ. */
.render-state.failed .state-list li { color: var(--danger-text); }
.render-state.failed .render-steps li.current .rs-so {
  border-color: transparent;
  background: var(--danger);
  color: var(--nut-tao-chu);
}
.render-state.failed .render-steps li.current .rs-trang-ic,
.render-state.cancelled .render-steps li.current .rs-trang-ic { animation: none; }
.render-state.cancelled .render-steps li.current .rs-so {
  border-color: transparent;
  background: var(--warn);
  color: #fff;
  animation: none;
}

.state-list {
  display: grid;
  gap: var(--sp-8);
  margin: 0;
  padding: 0;
  list-style: none;
}
/* Dòng chi tiết: KHÔNG vỏ. Nó đứng ngay dưới năm bước vốn cũng không có vỏ —
   thêm một mảng xám nữa là bảng có hai kiểu nền chồng nhau. Loại dòng nay nói
   bằng ICON dẫn đầu và màu chữ, không cần cái hộp. */
.state-list li {
  display: flex;
  align-items: center;
  gap: var(--sp-8);
  overflow: hidden;
  padding: var(--sp-6) 0 0;
  color: var(--text-soft);
  background: none;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sl-ic { display: inline-flex; flex: 0 0 auto; }
.sl-ic svg { width: 14px; height: 14px; display: block; }
/* min-width: 0 là BẮT BUỘC để ellipsis ăn: ô flex mặc định không co nhỏ hơn
   nội dung của nó, nên câu dài sẽ đẩy rộng hàng thay vì bị cắt. */
.sl-chu { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.state-list li::before {
  content: none;
}
.list-head,
.project-row {
  display: grid;
  /* STT | tên dự án | hành động. Cột trạng thái cũ (112px) nay là STT
     38px, và bỏ nút "Chọn" nên cột hành động hẹp bớt — phần dôi ra dồn hết
     cho tên dự án, vốn hay bị cắt giữa chừng.
     Cột tên PHẢI là `minmax(0, 1fr)`, không được đặt sàn cứng: cụm nút bên
     phải rộng 293px không co được (78+118+38+38 + 3 khe), nên sàn 300px cũ
     bắt hàng phải rộng tối thiểu 655px. Cửa sổ hẹp nhất 1180px chia đôi chỉ
     còn ~541px cho hàng → lưới tràn và **tên dự án đè lên nút Edit**. Tên
     đã có `text-overflow: ellipsis` nên co lại là cắt gọn, không vỡ. */
  /* Cột STT 26px: số lớn nhất từng gặp là 3 chữ số, ở 12px tabular-nums đo
     được ~22px — 38px cũ dư hẳn một khoảng trống trước tên dự án. */
  /* Cột ba 96px: nó KHÔNG còn là cụm nút (gỡ 15/09/2026) mà là NGÀY CẬP NHẬT.
     Để nguyên 306px là chừa một khoảng trống bằng một phần ba cột chỉ để hiện
     một cái ngày — phần dôi ra trả hết cho tên dự án. */
  grid-template-columns: 26px minmax(0, 1fr) 96px;
  gap: var(--sp-12);
  align-items: center;
}
/* Chữ THƯỜNG, không in hoa, và nhỏ hơn một bậc. Ba nhãn này là chú thích cột
   chứ không phải tiêu đề: viết hoa toàn bộ + giãn chữ 0.12em làm chúng nặng
   ngang với tên dự án ngay bên dưới, mà tên dự án mới là thứ cần đọc. */
.list-head {
  padding: 0 var(--sp-10) var(--sp-10);
  /* --text-soft (0.80) chứ không --muted (0.55): chỉ 55% độ đậm thì phải nhìn
     kỹ mới đọc ra. Cỡ chữ --text-md (13px) — bằng đúng nhãn "6 slide" và dòng
     trạng thái ngay dưới nó, để ba nhãn cột nằm cùng một họ cỡ với nội dung
     cột chứ không bé hơn hẳn. */
  color: var(--text-soft);
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.02em;
}
.list-head span:last-child {
  justify-self: center;
  text-align: center;
}
.project-list {
  display: grid;
  gap: var(--sp-10);
  margin: 0;
  padding: 0;
  list-style: none;
}
.project-row {
  border: 0;
  border-bottom: 1px solid var(--line);
  border-radius: 0;
  padding: var(--sp-12) var(--sp-10);
  background: none;
  transition: background 0.18s ease;
}
.project-row:last-child { border-bottom: 0; }
/* Bo góc GIỐNG hàng đang chọn. Trước đây chỉ .selected có bo, còn hover ra một
   dải chữ nhật cắt ngang bảng — hai trạng thái của cùng một hàng mà hai hình
   dáng khác nhau. */
.project-row:hover { background: var(--surface); border-radius: var(--radius-md); }

/* Dòng "Xem thêm" — KHÔNG dùng lưới 3 cột của .project-row (nó chỉ có một
   nút, để lưới thì nút dính vào cột 38px đầu tiên rồi teo lại). */
.project-row.row-xem-them {
  display: block;
  padding: var(--sp-10);
  background: none;
  /* Chữ ra GIỮA danh sách. Nút là inline-flex nên nó tự co theo chữ; căn giữa
     phải làm ở hàng chứa nó, không phải ở chính cái nút. */
  text-align: center;
}
.project-row.row-xem-them:hover { background: none; }
/* Chỉ là CHỮ, không phải một cái nút.
   Bản trước là khung nét đứt rộng hết bề ngang danh sách — nặng ngang một hàng
   dự án thật, trong khi nó chỉ làm mỗi việc nạp thêm mười dòng. Nay là một dòng
   chữ mờ, rê chuột thì đậm lên. Vẫn là <button> để bàn phím Tab tới được. */
.xem-them-btn {
  display: inline-flex;
  padding: var(--sp-6) 0;
  border: 0;
  background: none;
  color: var(--text-faint);
  font: inherit;
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  transition: color 0.18s ease;
}
/* Di chuột chỉ nhích lên MỘT bậc màu, và KHÔNG đổi độ đậm: đổi font-weight là
   chữ giãn ra, cả dòng nhích ngang một nhịp ngay dưới con trỏ. */
.xem-them-btn:hover { color: var(--text-soft); }
.project-row .actions {
  display: grid;
  /* Bốn nút sát lại: khe 8 -> 4px, và hai nút có chữ ÔM SÁT nội dung thay vì
     đứng trong cột rộng cố định 78/118px. Khoảng trống nhìn thấy trước đây
     phần lớn KHÔNG phải khe giữa nút mà là chỗ thừa bên trong cột — nội dung
     canh giữa nên hụt hai bên. Cả cụm co từ 296px còn ~240px; phần dôi ra trả
     hết cho cột tên dự án (cột đó là minmax(0,1fr) nên tự ăn chỗ thừa).
     ⚠ Vẫn KHÔNG đặt sàn cứng cho cột: xem chú thích ở .list-head/.project-row.
     Nhãn hai nút là chuỗi cố định nên bề ngang ổn định giữa các hàng. */
  grid-template-columns: auto auto 38px 38px;
  justify-content: end;
  gap: var(--sp-4);
  min-width: 0;
  white-space: nowrap;
}
.project-row .actions .icon-btn,
.project-row .actions .small-link,
.project-row .actions .delete-project-btn {
  width: 100%;
  min-width: 0;
  min-height: 42px;
  padding: var(--sp-8) var(--sp-8);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
}
.project-row .actions .btn-icon {
  width: 22px;
  height: 22px;
  flex: 0 0 22px;
}
.project-row .row-video-slot {
  min-width: 0;
  width: 100%;
}
/* Dang chon = mot thanh danh dau ben trai + nen dam hon mot chut.
    Ban cu rua ca dong bang xanh la, nang len 2px va do bong — trong giao
    dien don sac thi mot mang mau nhu vay at het moi thu con lai. */
/* Dòng đang chọn: nền BO GÓC, không phải một dải chữ nhật cắt ngang bảng.
   Bảng dự án không có đường kẻ dọc nào, nên một mảng vuông vức nằm giữa
   nhìn như bị lỗi hơn là như một lựa chọn. */
.project-row.selected { background: var(--surface-strong); border-radius: var(--radius-md); }
.project-row.selected .project-name { color: var(--text); }
/* Ảnh đại diện | cột chữ. Đổi .project-main từ lưới sang hàng NGANG và đẩy
   phần chữ xuống .project-chu, thay vì thêm một cột vào .project-row: cột
   lưới của hàng bị ba @media ghi đè lại (1400/1060/720px), thêm cột là phải
   sửa đúng cả bốn chỗ và quên một chỗ thì lệch mà không ai thấy ngay. */
.project-main { display: flex; align-items: center; gap: var(--sp-12); min-width: 0; }
/* Khe giữa TÊN và dòng thông tin: 2px. 4px làm hai dòng của cùng một dự án
   trông như hai khối rời — chúng phải đọc ra là một cụm. */
.project-chu { display: grid; gap: var(--sp-2); min-width: 0; }
/* ⚠ CỠ CHỮ 13px, không phải 18px. Đây là TÊN TRONG MỘT HÀNG DANH SÁCH, không
   phải tiêu đề trang: 18px đậm làm mỗi hàng cao lên và cả cột đọc như một
   chồng tiêu đề. Hai lượt trước tôi thu nhầm `.list-head` (nhãn cột) — chủ dự
   án vẫn thấy y nguyên, vì thứ to là dòng này. */
.project-name {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--text-md); font-weight: var(--weight-bold); letter-spacing: -0.01em;
}
/* Ngày sửa gần nhất — hai dòng, ngày trên giờ dưới, đúng như bảng của chủ dự
   án. Cột hẹp nên không nhồi được một dòng "12/09/2026 14:23". */
/* Ngày + giờ CĂN PHẢI: chúng là cột cuối, căn giữa thì mép phải của chúng
   lởm chởm theo độ dài từng chuỗi giờ. */
.project-ngay {
  display: grid; gap: 1px; justify-items: end; text-align: right;
  font-variant-numeric: tabular-nums; line-height: var(--leading-tight);
}
.pn-ngay { color: var(--text-soft); font-size: var(--text-xs); }
.pn-gio { color: var(--text-mo); font-size: var(--text-xs); }
/* Nhãn 16:9 cho deck ngang. Chỉ deck ngang mới có, nên nó vừa là dấu nhận
   biết vừa là lời nhắc: bộ kích thước bên phải sẽ khác. Dùng token, không
   viết mã màu — `verify:css` chặn. */
/* Nhãn KHỔ VIDEO cạnh tên deck — nay là ICON, cho CẢ HAI khổ (16/09/2026).
   Trước chỉ deck ngang có chữ "16:9", nên deck dọc chẳng có dấu hiệu nào — mà
   dọc mới là đa số, và cái cần là PHÂN BIỆT hai loại, không phải ghi lại một
   con số ai cũng biết. Điện thoại dựng = short, máy tính bảng nằm = ngang.
   ⚠ Hai màu khác nhau: xanh `--info` cho ngang (giữ đúng màu chữ "16:9" cũ),
   xám nhạt cho dọc — nếu cùng màu thì ở cỡ 13px hai hình gần như một. */
/* ⚠ KHÔNG viền, KHÔNG nền — chỉ nét icon. Viền quanh một icon 13px tạo ra ô
   thứ hai ngay cạnh tên deck, mắt đọc thành một cái nút bấm được. */
.nhan-khung {
  flex: none;
  margin-left: var(--sp-6);
  padding: 0;
  display: inline-flex; align-items: center;
}
.nhan-khung .btn-icon { background: none; box-shadow: none; display: inline-flex; color: inherit; }
.nhan-khung svg { width: 13px; height: 13px; display: block; }
.nhan-ngang { color: var(--info); }
.nhan-doc { color: var(--text-faint); }

/* ⚠ KHÔNG viền, KHÔNG viên tròn (16/09/2026). "11 slide" là một CON SỐ đọc
   lướt, không phải nút bấm; đóng khung nó lại thì mỗi hàng dự án có thêm một
   hình chữ nhật tranh chỗ với tên. Bỏ viền còn trả lại ~20px cho nhãn trạng
   thái bên cạnh — nhãn dài nhất ("Đang thiết kế") trước đó bị cắt vài px. */
.project-slide-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: fit-content;
  min-height: 26px;
  padding: 0;
  border: 0;
  color: var(--text-faint);
  background: none;
  box-shadow: none;
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
}
/* Còn dùng cho chỗ khác trong giao diện gốc — danh sách deck nay dùng
   .project-status thay cho vòng thuốc này. */
/* Cột STT — số thứ tự. Trạng thái không còn là cột riêng mà nằm cạnh số
   slide ở dòng dưới tên dự án (xem .project-status). */
.row-index {
  justify-self: center;
  color: var(--text-faint);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}
/* Số slide + trạng thái, cùng một dòng dưới tên dự án. Chấm tròn màu
   làm tín hiệu, chữ giữ cỡ nhỏ và mờ để không tranh chỗ với tên. */
.project-meta { display: inline-flex; align-items: center; gap: var(--sp-10); flex-wrap: wrap; }
.project-status {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-6);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--text-faint);
  cursor: default;
}
.project-status::before {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: var(--radius-pill);
  background: currentColor;
  flex: 0 0 auto;
}
/* ══ BẢNG MÀU TRẠNG THÁI ═══════════════════════════════════════════════
   Mỗi trạng thái MỘT màu riêng. Bản trước chỉ có 4 màu cho 7 trạng thái,
   và trùng đúng ở hai cặp hay phải phân biệt nhất:
     · "Đã có video" và "Đang dựng" cùng #38bdf8
     · "Đang thiết kế" và "Đang chờ" cùng #a78bfa (khác mỗi độ mờ)
   Nay xếp theo Ý NGHĨA, không theo họ màu:
     xám  = chưa biết      · chì   = chưa làm gì
     lam  = đang chạy      · tím   = AI đang ghi
     hổ phách = cần làm lại · đỏ  = hỏng, chưa chạy được
     lục  = xong, có video
   Xanh LÁ để dành cho ĐÍCH ĐẾN. Trước đây nó là "Sẵn sàng" — một trạng
   thái mà thật ra chưa làm được gì — nên deck xong và deck chưa làm nhìn
   na ná nhau, đúng chỗ cần liếc một cái là biết deck nào còn phải làm. */

/* Đang dò: XÁM và mờ, cố ý không màu nào cả.
   Đây là "chưa biết", không phải một trạng thái nữa — cho nó màu riêng là
   người dùng đọc thành một kết luận, mà cả ý nghĩa của nó là chưa có kết
   luận nào. Chấm nhấp nháy để thấy ngay là còn đang chạy. */
.project-status.load { color: var(--text-faint); opacity: 0.75; }
.project-status.load::before { animation: bee-status-nhay 1.1s ease-in-out infinite; }

/* Sẵn sàng — màu CHÌ, cố ý nhạt. Đủ điều kiện dựng, nhưng chưa có gì trong
   tay; nó là điểm xuất phát, không phải thành tựu. */
.project-status.ok   { color: var(--status-idle); }
/* Đang chờ — cùng họ lam với "Đang dựng" vì cùng nằm trong dây chuyền
   dựng, nhưng nhạt hơn và chấm KHÔNG nháy: chưa tới lượt nó. */
.project-status.wait { color: var(--status-wait); }
.project-status.run  { color: var(--status-run); }
/* Đang thiết kế — tím, việc của AI ghi deck, khác hẳn dây chuyền dựng. */
.project-status.wip  { color: var(--status-wip); }
/* Video cũ — hổ phách. Có video nhưng script đã đổi: phải dựng lại. */
.project-status.old  { color: var(--warn); }
/* Thiếu script — ĐỎ. Đây là thứ duy nhất khiến deck KHÔNG dựng được. Tách
   khỏi "Video cũ" vì hai bệnh khác nhau hẳn: một cái chặn đứng, một cái
   chỉ là nên làm lại. */
.project-status.warn { color: var(--danger); }
/* Đã có video — XANH LÁ, đích đến. */
.project-status.done { color: var(--ok); }
/* Chấm nhấp nháy CHỈ cho "đang dựng" — việc đang chạy thì đáng để mắt bị
   kéo về; các trạng thái tĩnh mà cũng nháy thì cả bảng loạn. */
.project-status.run::before { animation: bee-status-nhay 1.1s ease-in-out infinite; }
@keyframes bee-status-nhay { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) {
  .project-status.run::before { animation: none; }
}
/* Nền sáng: cùng thứ tự ý nghĩa, đậm hơn một nấc cho đủ tương phản. */
/* Nút Edit lúc deck đang được dựng: vẫn giữ chỗ để hàng không nhảy, nhưng
   không bấm được. */
.actions .small-link.disabled {
  opacity: .38;
  pointer-events: none;
  cursor: default;
}
/* Nut chi co icon — khong nen, khong vien, khong chu.
    Dung cho "Mo video" (mui ten xanh) va "Xoa" (thung rac do). Mau chinh
    la thu duy nhat phan biet chung, nen khong can them khung. */
.icon-only {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  justify-self: center;
  padding: 0;
  border: 0;
  border-radius: var(--radius-md);
  background: none;
  box-shadow: none;
  font-size: var(--text-lg);
  line-height: var(--leading-none);
  cursor: pointer;
  transition: background 0.15s ease, opacity 0.15s ease;
}
.icon-only:hover { background: var(--surface-strong); }
/* Ô icon trần cho hai nút chỉ-hình trong hàng dự án. KHÔNG dùng .btn-icon:
   luật đó tự đặt color: var(--text-soft) với độ ưu tiên (0,1,0), màu thừa kế
   không thắng nổi — nút phát mất xanh, nút xoá mất đỏ. */
.icon-only .ic-o { display: inline-flex; }
.icon-only .ic-o svg { width: 17px; height: 17px; }
.icon-only.play  { color: var(--ok); }
.icon-only.trash { color: var(--danger); }
.icon-only.disabled,
.icon-only:disabled { opacity: 0.28; cursor: default; background: none; }

/* Nút "trong suốt": không nền, không viền; chỉ hiện khung khi rê chuột.
   Cùng lối với nút ▶ và thùng rác bên cạnh, để cả hàng nút nhìn như một
   cụm chứ không phải 2 thẻ đặc + 2 icon trần. */
.project-row .actions .icon-btn.ghost {
  border-color: transparent;
  background: none;
  color: var(--text-soft);
}
/* Rê chuột (và lúc đang bấm) KHÔNG vẽ viền — mảng nền mờ cùng màu chữ đậm lên
   là đủ báo "đang trỏ vào đây". Thêm viền nữa thì mỗi hàng dự án có tới bốn
   khung con bật lên cùng lúc. */
.project-row .actions .icon-btn.ghost:hover {
  border-color: transparent;
  background: var(--surface);
  color: var(--text);
}
.project-row .actions .icon-btn.ghost .btn-icon {
  background: none;
  color: inherit;
  box-shadow: none;
}
.muted { color: var(--text-faint); }
.row-video-slot { display: inline-flex; align-items: center; flex: 0 0 auto; }
[hidden] { display: none !important; }
/* Cửa sổ hẹp: bỏ CHỮ trên hai nút hành động, chỉ để icon.
   Cụm nút rộng 293px không co được, nên ở cửa sổ nhỏ nhất (1180px, chia đôi
   còn ~541px cho hàng) cột tên chỉ còn ~186px — tên cắt sớm và dòng trạng
   thái bị đẩy xuống hàng, hàng cao gấp rưỡi. Bỏ chữ thì cụm nút còn 173px,
   trả lại ~120px cho tên. Cả hai nút đều có `title` nên rê chuột vẫn biết
   là nút gì. */
@media (max-width: 1400px) {
  /* Phải hạ CẢ cột thứ ba, không chỉ ẩn chữ: `minmax(0, 306px)` là track có
     trần cố định, mà lưới nở track có trần TRƯỚC rồi mới chia phần còn lại
     cho `1fr`. Nên chỉ ẩn chữ thôi thì cột vẫn giữ nguyên 306px, tên vẫn bị
     ép còn 121px — đã đo. Đặt cứng 173px = 4×38 + 3 khe 7px.
     Đặt cho CẢ `.list-head` để nhãn "HÀNH ĐỘNG" không lệch khỏi cụm nút. */
  .list-head,
  .project-row { grid-template-columns: 26px minmax(0, 1fr) 164px; }
  /* 164px = 4x38 + 3 khe 4px, khớp với cột thứ ba ở luật ngay trên. */
  .project-row .actions { grid-template-columns: repeat(4, 38px); }
  .project-row .actions .icon-btn span:not(.btn-icon) { display: none; }
  .project-row .actions .icon-btn { justify-content: center; padding-inline: 0; }
}

@media (max-width: 1060px) {
  body { height: auto; min-height: 100vh; overflow: auto; display: block; }
  .dashboard-header { align-items: flex-start; }
  .dashboard-shell { grid-template-columns: 1fr; min-height: auto; overflow: visible; }
  .render-machine { align-self: auto; height: auto; min-height: 0; position: static; max-height: none; overflow: visible; }
  .slide-list { height: auto; overflow: visible; }
}
@media (max-width: 720px) {
  body { padding: var(--sp-20) var(--sp-14); }
  .dashboard-header { display: grid; }
  .header-tools { justify-content: flex-start; }
  .list-head { display: none; }
  .project-row { grid-template-columns: 1fr; align-items: stretch; }
  .project-row .actions {
    grid-template-columns: repeat(5, minmax(0, 1fr));
    justify-content: stretch;
    overflow: visible;
    padding-bottom: var(--sp-2);
  }
  .project-row .actions .icon-btn,
.project-row .actions .small-link,
.project-row .actions .delete-project-btn {
    padding: var(--sp-8) var(--sp-6);
    font-size: var(--text-xs);
  }
}
