
:root {
  --bg: #f7f9fb;
  --surface: #ffffff;
  --surface-2: #f1f5f9;
  --border: #e2e8f0;
  --border-strong: #cbd5e1;
  --text: #0f172a;
  --text-muted: #64748b;
  --text-faint: #94a3b8;
  /* Màu lấy trực tiếp từ logo CYAN PRO APP: cyan #01aac2, navy #13283b.
     Sắc cyan nguyên bản chỉ đạt 2.8:1 trên nền trắng nên chữ và nút dùng
     bản đậm hơn cùng tông (4.7:1), còn cyan gốc giữ cho viền và nền nhạt. */
  --brand: #037f92;
  --brand-strong: #02616f;
  --brand-raw: #01aac2;
  --brand-ink: #13283b;
  --brand-soft: #e7f8fb;
  --ok: #047857;
  --ok-soft: #ecfdf5;
  --warn: #b45309;
  --warn-soft: #fffbeb;
  --danger: #b91c1c;
  --danger-soft: #fef2f2;
  --radius: 10px;
  /* Chiều cao thanh điều hướng.
     Thanh trên dính (position: sticky) và có nền mờ, nên mọi thứ dính
     theo khi cuộn phải tự trừ đi khoảng này, nếu không nó trượt lên
     NẰM DƯỚI thanh và bị cắt mất phần đầu. Viết thành biến để chỉ phải
     sửa một chỗ khi thanh đổi chiều cao: .nav-inner cao 58px (min-height
     đã gồm padding vì box-sizing: border-box) cộng 1px viền dưới. Làm
     tròn lên 60px cho chắc. */
  --nav-h: 60px;
  --shadow: 0 1px 2px rgba(15,23,42,.06), 0 4px 16px rgba(15,23,42,.04);
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

/* Màu của bản tối nằm trong src/ui/theme.ts, in ra sau khối này. */


/* Người bật "giảm chuyển động" trong hệ điều hành thường làm vậy vì
   chuyển động gây chóng mặt hoặc buồn nôn, không phải vì thẩm mỹ. Tắt
   hẳn thay vì làm nhanh hơn. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  font-feature-settings: "tnum" 1;
}
a { color: var(--brand); text-decoration: none; }
a:hover { text-decoration: underline; }
h1, h2, h3 { line-height: 1.25; margin: 0 0 .5rem; letter-spacing: -.015em; }
h1 { font-size: 1.6rem; font-weight: 650; }
h2 { font-size: 1.2rem; font-weight: 620; }
h3 { font-size: 1rem; font-weight: 620; }
p { margin: 0 0 1rem; }
code, .mono { font-family: var(--mono); font-size: .875em; }

/* ---------- Bố cục ---------- */
.wrap { max-width: 1120px; margin: 0 auto; padding: 0 16px; }
.wrap-narrow { max-width: 440px; margin: 0 auto; padding: 0 16px; }
/* Khung TRÀN HẾT bề ngang, cho trang có bảng nhiều cột.
   Khung mặc định 1120px vừa cho trang chữ — chữ dài quá một dòng thì
   mỏi mắt. Nhưng bảng chín cột thì ngược lại: càng rộng càng dễ đọc,
   vì mỗi cột được thêm chỗ thay vì phải cuộn ngang. Nên trang bảng
   dùng hết bề ngang màn hình, chỉ chừa lề hai bên. */
/* Lề 16px chứ không phải 24: ở màn 1440px (laptop 14" rất phổ biến)
   thì 24px mỗi bên làm bảng thiếu đúng 2px và phải cuộn ngang — cuộn
   vì hai pixel thì vô lý. 16px đủ thoáng mà vừa khít. */
.wrap-wide { max-width: none; padding-inline: 16px; }
.main { padding: 32px 0 64px; }

/* ---------- Thanh điều hướng ---------- */
.nav {
  position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--border);
}
.nav-inner {
  /* Thanh trên rộng hơn cột nội dung. Nội dung chặn ở 1120px cho dễ
     đọc, nhưng thanh điều hướng không phải thứ để đọc dòng này sang
     dòng kia — ép nó vào cùng bề ngang chỉ làm menu bảy mục gãy xuống
     hai dòng trong khi hai bên màn hình còn trống. */
  max-width: 1320px;
  display: flex; align-items: center; gap: 20px;
  min-height: 58px; flex-wrap: wrap;
  /* row-gap chỉ có tác dụng khi thực sự xuống dòng, nên để sẵn ở đây là
     an toàn: một dòng thì không ảnh hưởng gì, hai dòng thì có khe thở. */
  row-gap: 8px;
  /* padding-block chứ KHÔNG phải viết tắt "padding: 7px 0".
     Thẻ này mang cả hai lớp .wrap và .nav-inner, mà .wrap mới là chỗ
     cấp lề ngang 16px. Viết tắt đặt luôn cả bốn phía, nên nó xoá sạch
     lề ngang đó và logo dính sát mép trái màn hình ở mọi khổ hẹp hơn
     1320px — tức là gần như mọi máy tính xách tay. */
  padding-block: 7px;
}
.brand {
  display: inline-flex; align-items: center; gap: 9px;
  font-weight: 680; font-size: 1.02rem; color: var(--text);
  letter-spacing: -.02em; white-space: nowrap;
}
.brand:hover { text-decoration: none; }
/* Logo thật, hai bản: chữ navy cho nền sáng, chữ sáng cho nền tối. */
.brand-logo { height: 34px; width: auto; display: block; flex: none; }
.brand-logo-dark { display: none; }
/* Biểu tượng vuông, mặc định ẩn. Việc chọn sáng/tối nằm gọn trong khối
   @media khổ hẹp bên dưới, không rải ra đây — hai nơi cùng quyết định
   một thứ thì thứ tự quy tắc sẽ quyết định hộ, và dễ ra kết quả sai. */
.brand-mark-img { height: 34px; width: 34px; flex: none; display: none; }
.brand-tag {
  padding-left: 9px; margin-left: 2px; border-left: 1px solid var(--border-strong);
  font-size: .9rem; font-weight: 560; color: var(--text-muted); letter-spacing: 0;
}
/* Trên điện thoại thanh nav không đủ chỗ cho logo + nhãn + nút chuyển ngôn
   ngữ + hai nút tài khoản, nên thu dần theo khổ màn thay vì để cả trang
   trượt ngang. Thứ tự hy sinh: nhãn cạnh logo trước, rồi phần thừa của
   nút ngôn ngữ, cuối cùng mới đến kích thước. Không ẩn hẳn mục nào —
   "Đăng nhập" mà biến mất trên điện thoại là mất đường vào tài khoản. */
@media (max-width: 560px) {
  .brand-tag { display: none; }
  .brand-logo { height: 29px; }
  /* Chỉ hiện mã ngôn ngữ sẽ chuyển sang; mã hiện tại và mũi tên là thừa
     khi đã biết mình đang đọc thứ tiếng nào. */
  .lang-switch { padding: 5px 8px; }
  .lang-cur, .lang-sep { display: none; }
}
/* Máy nhỏ nhất còn phổ biến (iPhone SE, 320px): thu lề và đổi logo ngang
   thành biểu tượng vuông. Thanh nav lúc đã đăng nhập còn phải chứa nút
   sang khu quản trị và nút đăng xuất, nên chỗ cho logo hết thật. */
@media (max-width: 400px) {
  .wrap, .wrap-narrow { padding: 0 12px; }
  .nav-inner { gap: 8px; }
  .nav-right { gap: 7px; }
  .brand-logo { display: none; }
  .brand-mark-light { display: block; }
  .btn-sm { padding: 5px 8px; font-size: .75rem; }
}
/* KHÔNG dùng overflow-x: auto ở đây. Khi menu dài hơn chỗ trống, trình
   duyệt trên Windows và Linux vẽ hẳn một thanh cuộn xám chạy ngang giữa
   thanh điều hướng — macOS giấu thanh cuộn nên lỗi này rất dễ lọt qua
   khâu kiểm tra. Thay vào đó menu tự xuống dòng: không giấu gì của người
   dùng, không phụ thuộc vào cách từng hệ điều hành vẽ thanh cuộn. */
.nav-links { display: flex; flex-wrap: wrap; gap: 2px; margin-left: 10px; flex: 1; min-width: 0; }
.nav-links a {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 11px; border-radius: 9px; color: var(--text-muted);
  font-size: .9rem; white-space: nowrap;
  /* ĐỘ ĐẬM GIỮ NGUYÊN ở mọi trạng thái. Tăng đậm khi hover hay khi đang
     chọn làm chữ rộng ra, và cả hàng menu nhích sang ngang một chút —
     một cú giật nhỏ nhưng xảy ra mỗi lần rê chuột qua. Màu và nền đủ để
     phân biệt trạng thái. */
  font-weight: 540;
  transition: background-color .14s ease, color .14s ease;
}
/* Biểu tượng nhạt hơn chữ: nó là thứ để nhận ra mục, không phải thứ để
   đọc. Cùng độ đậm với chữ thì cả hàng menu thành một dãy hình loè loẹt
   và mắt không biết bám vào đâu. */
.nav-links a .ic { opacity: .62; transition: opacity .14s ease; }
@media (hover: hover) {
  .nav-links a:hover { background: var(--surface-2); color: var(--text); text-decoration: none; }
  .nav-links a:hover .ic { opacity: 1; }
}
.nav-links a.active { background: var(--brand-soft); color: var(--brand); }
.nav-links a.active .ic { opacity: 1; }

/* ---------- Nhãn nổi bật của sản phẩm ---------- */
/* Hai nhãn này là LÝ DO ĐỂ BẤM VÀO, nên chúng có màu, khác hẳn dãy chip
   nền tảng màu xám vốn chỉ là thông tin kiểm tra lại sau. */
/* ---------- Chú thích nhãn trên trang giới thiệu ---------- */
/* (Bản thân nhãn khai báo cạnh .chip ở khối thẻ phần mềm bên dưới: nó là
   biến thể màu của chip, và phải nằm SAU .chip mới đè được màu nền.) */
.app-notes {
  display: grid; gap: 10px;
  margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--border);
}
.app-note { display: flex; gap: 9px; align-items: flex-start; }
.app-note .ic { width: 1.05em; height: 1.05em; margin-top: .2em; }
.app-note-ai .ic { color: var(--brand-strong); }
.app-note-trial .ic { color: var(--warn); }
.app-note p { margin: 0; font-size: .88rem; line-height: 1.6; color: var(--text-muted); }
.app-note b { font-weight: 640; color: var(--text); }

/* ---------- Biểu tượng ---------- */
/* Cỡ theo em chứ không theo px: biểu tượng nằm cạnh chữ nào thì lớn
   theo chữ đó, nên cùng một thẻ dùng được ở nút lớn lẫn dòng chữ nhỏ. */
.ic {
  width: 1.15em; height: 1.15em; flex: none;
  display: inline-block; vertical-align: -.18em;
}
.btn .ic { width: 1.05em; height: 1.05em; }

/* ---------- Nhóm công cụ trên thanh nav ---------- */
.nav-tools {
  display: flex; align-items: center; gap: 2px;
  padding: 2px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface-2);
}
.nav-sep { width: 1px; height: 22px; background: var(--border); flex: none; }
.nav-user {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: .85rem; color: var(--text-muted); max-width: 190px;
}
.nav-user-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-avatar {
  width: 28px; height: 28px; flex: none; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--brand-soft); color: var(--brand);
  font-size: .78rem; font-weight: 650; letter-spacing: 0;
}
/* Nút chỉ có biểu tượng: vuông, và luôn phải có aria-label. */
.btn-icon-only { padding: 0; width: 34px; height: 34px; }
@media (max-width: 720px) { .nav-user-name { display: none; } }

/* Màn hẹp: menu xuống hẳn dòng riêng chiếm trọn chiều ngang, còn logo và
   các nút tài khoản ở lại dòng trên. Nhồi tất cả vào một dòng rồi cho
   cuộn là cách làm hỏng — chữ bị cắt và xuất hiện thanh cuộn xấu.
   Khối này phải nằm SAU quy tắc .nav-links bên trên: hai bên cùng độ cụ
   thể nên quy tắc đứng sau thắng, đặt trước thì flex: 1 ở trên ghi đè
   mất flex-basis: 100% và menu vẫn dính lại một dòng. */
/* Mốc 1180px, không phải 900px: từ khi menu có biểu tượng, bảy mục cần
   thêm khoảng 150px. Giữa hai mốc đó menu gãy ngang GIỮA danh sách —
   năm mục dòng trên, hai mục dòng dưới — trông như vỡ layout. Xuống hẳn
   một dòng riêng thì nó là một lựa chọn thiết kế, không phải một lỗi. */
@media (max-width: 1180px) {
  /* padding-block vì cùng lý do ở trên: giữ lề ngang của .wrap. */
  .nav-inner { padding-block: 9px; row-gap: 8px; }
  .brand { order: 1; }
  .nav-right { order: 2; margin-left: auto; }
  .nav-links { order: 3; flex: 0 0 100%; margin-left: 0; }
}
/* Khu quản trị có tám mục nên hết chỗ sớm hơn hẳn trang công khai: ở
   1180px menu gãy ngang giữa danh sách, nhìn như vỡ layout. Cho nó
   xuống hẳn dòng riêng từ mốc đó. Không dùng :has() để khỏi phụ thuộc
   trình duyệt đời mới — chỉ cần đặt order và flex cho đúng ba phần. */
/* Khu quản trị: menu LUÔN nằm ở dòng riêng, không chờ màn hẹp.
   Chín mục kèm biểu tượng không đủ chỗ cạnh logo và nhóm tài khoản ở
   bất kỳ khổ màn nào, nên để nó tự gãy thì có màn gãy 7+2, có màn 8+1 —
   mỗi máy một kiểu. Một dòng riêng cho menu là bố cục quen thuộc của
   trang quản trị, và nó giống nhau trên mọi màn. */
.nav-links-wide { order: 3; flex: 0 0 100%; margin-left: 0; }
.nav-links-wide ~ .nav-right { order: 2; margin-left: auto; }
.nav-right { display: flex; align-items: center; gap: 8px; white-space: nowrap; }

/* ---------- Thẻ ---------- */
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
}
.card-pad { padding: 22px; }
.card-head {
  padding: 16px 22px; border-bottom: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
}
.card-head h2, .card-head h3 { margin: 0; }
.grid { display: grid; gap: 16px; }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
/* Hàng chỉ số: ô chỉ chứa một con số nên hẹp được, nhờ vậy cả sáu ô nằm
   gọn một hàng trên màn rộng thay vì để lẻ vài ô rơi xuống dòng dưới. */
.kpi-grid { grid-template-columns: repeat(auto-fit, minmax(158px, 1fr)); }
.stack { display: flex; flex-direction: column; gap: 16px; }
.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.row-end { justify-content: flex-end; }
.spacer { flex: 1; }

/* ---------- Nút ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 9px 16px; border-radius: 8px; border: 1px solid transparent;
  background: var(--brand); color: #fff;
  font: inherit; font-size: .9rem; font-weight: 580; cursor: pointer;
  transition: filter .12s ease, background .12s ease;
}
/* Bóng rất nhẹ dưới nút đặc: đủ để nó nổi lên khỏi mặt thẻ mà không
   thành một khối đổ bóng nặng nề. Nút viền không cần — nó vốn đã phẳng
   theo chủ ý. */
.btn { box-shadow: 0 1px 2px rgba(16, 24, 32, .08); }
.btn-ghost { box-shadow: none; }
@media (hover: hover) {
  .btn:hover { filter: brightness(1.08); text-decoration: none; }
}
/* Lún xuống 1px khi bấm. Một pixel là đủ để ngón tay tin rằng nút đã
   nhận lệnh, và vì nó là transform nên không đẩy gì xung quanh. */
.btn:active { filter: brightness(.95); transform: translateY(1px); }
@media (prefers-reduced-motion: reduce) {
  .btn:active { transform: none; }
}
.btn:disabled { opacity: .55; cursor: not-allowed; }
.btn-ghost { background: transparent; color: var(--text-muted); border-color: var(--border-strong); }
.btn-ghost:hover { background: var(--surface-2); color: var(--text); }
.btn-danger { background: var(--danger); }
/* Lối sang khu quản trị. Dùng màu navy của logo để tách khỏi màu cyan
   của các nút hành động thường — nhìn là biết đây là cửa khác. */
.btn-admin { background: var(--brand-ink); color: #fff; }
.btn-sm { padding: 5px 11px; font-size: .82rem; }
.btn-block { width: 100%; }

/* ---------- Biểu mẫu ---------- */
.field { margin-bottom: 15px; }
.field label { display: block; font-size: .85rem; font-weight: 570; margin-bottom: 5px; color: var(--text); }
.field .hint { font-size: .78rem; color: var(--text-muted); margin-top: 5px; }
input[type=text], input[type=email], input[type=password], input[type=number],
input[type=date], input[type=url], input[type=search], input[type=tel],
input[type=file], select, textarea {
  width: 100%; padding: 10px 13px; font: inherit; font-size: .92rem;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border-strong); border-radius: 10px;
  /* Một vạch sáng rất nhẹ ở mép trên: mắt đọc nó là "ô này lõm xuống",
     nên ô nhập tách hẳn khỏi nền thẻ mà không cần viền đậm thêm. Trên
     nền tối vạch này thành bóng đổ mờ, vẫn đúng chiều sáng. */
  box-shadow: inset 0 1px 0 rgba(16, 24, 32, .025);
  transition: border-color .14s ease, box-shadow .14s ease, background-color .14s ease;
}
@media (hover: hover) {
  input[type=text]:hover, input[type=email]:hover, input[type=password]:hover,
  input[type=number]:hover, input[type=date]:hover, input[type=url]:hover,
  input[type=search]:hover, input[type=tel]:hover, select:hover, textarea:hover {
    border-color: color-mix(in srgb, var(--brand) 42%, var(--border-strong));
  }
}
::placeholder { color: var(--text-muted); opacity: .72; }
input:disabled, select:disabled, textarea:disabled {
  background: var(--surface-2); color: var(--text-muted);
  cursor: not-allowed; box-shadow: none;
}

/* Mũi tên của select vẽ bằng nền, không dùng mũi tên mặc định của hệ
   điều hành: mỗi hệ vẽ một kiểu và kích thước khác nhau, nên cùng một
   biểu mẫu nhìn lệch hẳn giữa Windows với macOS. Mã màu phải viết %23
   thay cho dấu thăng — trong url() thì dấu thăng mở đầu một fragment. */
select {
  appearance: none; -webkit-appearance: none;
  padding-right: 38px; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b7a89' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 15px 15px;
}

/* Ô chọn tệp: nút bấm bên trong trông như một nút thật, phần còn lại là
   tên tệp. Mặc định của trình duyệt là một nút xám hệ điều hành, lạc
   hẳn với phần còn lại của biểu mẫu. */
input[type=file] { padding: 7px 10px; cursor: pointer; }
input[type=file]::file-selector-button {
  margin-right: 12px; padding: 7px 13px;
  border: 1px solid var(--border-strong); border-radius: 8px;
  background: var(--surface-2); color: var(--text);
  font: inherit; font-size: .86rem; font-weight: 560; cursor: pointer;
  transition: background-color .14s ease, border-color .14s ease;
}
@media (hover: hover) {
  input[type=file]:hover::file-selector-button {
    background: var(--surface); border-color: var(--brand);
  }
}

/* Ô tick và nút tròn lấy màu thương hiệu thay vì xanh mặc định của hệ
   điều hành, và to hơn một chút cho dễ bấm bằng ngón tay. */
input[type=checkbox], input[type=radio] {
  accent-color: var(--brand); width: 17px; height: 17px; cursor: pointer;
}
@media (max-width: 560px) {
  /* iOS Safari tự phóng to trang khi ô nhập có cỡ chữ dưới 16px, và sau
     đó không thu lại — người dùng phải tự chụm tay để về như cũ. */
  input[type=text], input[type=email], input[type=password], input[type=number],
  input[type=date], input[type=url], input[type=search], input[type=tel],
  select, textarea {
    font-size: 16px;
  }
}
/* :focus-visible chứ không phải :focus cho ô tick và nút bấm: bấm chuột
   vào một ô tick mà hiện vòng sáng như khi đi bằng bàn phím thì trông
   như vừa bấm nhầm. Ô nhập chữ thì vẫn dùng :focus — con trỏ đang nhấp
   nháy ở đó, vòng sáng là thứ xác nhận đúng chỗ. */
input[type=text]:focus, input[type=email]:focus, input[type=password]:focus,
input[type=number]:focus, input[type=date]:focus, input[type=url]:focus,
input[type=search]:focus, input[type=tel]:focus, input[type=file]:focus,
select:focus, textarea:focus {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 18%, transparent);
}
input[type=checkbox]:focus-visible, input[type=radio]:focus-visible,
.btn:focus-visible, .nav-links a:focus-visible, .theme-switch:focus-visible,
.lang-switch:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px;
}
textarea { min-height: 88px; resize: vertical; line-height: 1.6; }
.checkbox { display: flex; align-items: flex-start; gap: 9px; font-size: .9rem; }
.checkbox input { width: 17px; margin-top: 2px; flex: none; }

/* ---------- Thông báo ---------- */
.alert {
  padding: 11px 15px; border-radius: 8px; font-size: .9rem;
  border: 1px solid transparent; margin-bottom: 18px;
}
.alert-success { background: var(--ok-soft); color: var(--ok); border-color: color-mix(in srgb, var(--ok) 28%, transparent); }
.alert-error   { background: var(--danger-soft); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 28%, transparent); }
.alert-info    { background: var(--brand-soft); color: var(--brand); border-color: color-mix(in srgb, var(--brand) 28%, transparent); }
.alert-warn    { background: var(--warn-soft); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 28%, transparent); }

/* ---------- Nhãn trạng thái ---------- */
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 9px; border-radius: 999px;
  font-size: .76rem; font-weight: 600; letter-spacing: .01em;
  background: var(--surface-2); color: var(--text-muted);
  border: 1px solid var(--border);
}
.badge::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.badge-ok     { background: var(--ok-soft); color: var(--ok); border-color: color-mix(in srgb, var(--ok) 25%, transparent); }
.badge-warn   { background: var(--warn-soft); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 25%, transparent); }
.badge-danger { background: var(--danger-soft); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 25%, transparent); }
.badge-info   { background: var(--brand-soft); color: var(--brand); border-color: color-mix(in srgb, var(--brand) 25%, transparent); }

/* ---------- Bảng ---------- */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table { width: 100%; border-collapse: collapse; font-size: .89rem; }
th {
  text-align: left; padding: 10px 14px; font-weight: 600; font-size: .76rem;
  text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted);
  border-bottom: 1px solid var(--border); white-space: nowrap;
}
td { padding: 12px 14px; border-bottom: 1px solid var(--border); vertical-align: middle; }
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--surface-2); }
.num { text-align: right; font-variant-numeric: tabular-nums; }
.empty { padding: 44px 20px; text-align: center; color: var(--text-muted); font-size: .9rem; }

/* ---------- Ô số liệu ---------- */
.stat { padding: 18px 20px; }
.stat-value, .stat-label + div, .app-meta-value, td.num {
  font-variant-numeric: tabular-nums;
}
.stat-label { font-size: .78rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: .05em; font-weight: 600; }
.stat-value { font-size: 1.75rem; font-weight: 660; letter-spacing: -.025em; margin-top: 6px; font-variant-numeric: tabular-nums; }
.stat-sub { font-size: .8rem; color: var(--text-muted); margin-top: 3px; }

/* ---------- Linh tinh ---------- */
.muted { color: var(--text-muted); }
.small { font-size: .85rem; }
.xsmall { font-size: .78rem; }
.mt-0 { margin-top: 0; } .mb-0 { margin-bottom: 0; }
.mt-2 { margin-top: 1rem; } .mt-3 { margin-top: 1.5rem; }
.page-head { margin-bottom: 22px; }
.page-head p { margin: 4px 0 0; color: var(--text-muted); }
.keybox {
  font-family: var(--mono); font-size: .86rem; padding: 9px 13px;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 7px; word-break: break-all; user-select: all;
}
.divider { height: 1px; background: var(--border); margin: 22px 0; border: 0; }

/* ---------- Biểu đồ ----------
   Bộ ba màu trạng thái đã chạy qua công cụ đo độ phân biệt màu, không
   phải chọn bằng mắt. Cặp cam–đỏ quen dùng (#b45309 với #b91c1c) chỉ
   cách nhau ΔE 9,1 — dưới ngưỡng 15, tức người mắt bình thường cũng khó
   phân biệt, chưa nói tới người mù màu. Bộ dưới đây vượt cả ngưỡng mắt
   thường lẫn ngưỡng mù màu ở cả hai chế độ nền. Vàng nền sáng đạt 2,94:1
   độ tương phản, hơi dưới 3:1, nên mọi biểu đồ dùng nó đều kèm nhãn chữ
   và chú giải chứ không để màu tự nói. */
:root {
  --chart-bar: #037f92;
  --chart-grid: #eef2f6;
  --chart-axis: #94a3b8;
  --chart-axis-line: #cbd5e1;
  --chart-ok: #047857;
  --chart-warn: #ca8a04;
  --chart-danger: #b91c1c;
}
.chart-card .card-pad { padding-top: 10px; }
.chart-legend {
  display: flex; flex-wrap: wrap; gap: 6px 18px; margin-top: 12px; font-size: .82rem;
}
.chart-key { display: inline-flex; align-items: center; gap: 6px; color: var(--text-muted); }
.chart-key strong { color: var(--text); font-variant-numeric: tabular-nums; }
.chart-dot { width: 10px; height: 10px; border-radius: 3px; flex: none; }
.bar-list { display: flex; flex-direction: column; gap: 11px; }
.bar-row { display: grid; grid-template-columns: minmax(80px, 150px) 1fr auto; gap: 12px; align-items: center; }
.bar-label {
  font-size: .86rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bar-track { height: 14px; background: var(--surface-2); border-radius: 3px; overflow: hidden; }
.bar-fill { height: 100%; border-radius: 3px; }
.bar-value { font-size: .84rem; font-weight: 600; font-variant-numeric: tabular-nums; }
@media (max-width: 520px) {
  .bar-row { grid-template-columns: 1fr auto; row-gap: 4px; }
  .bar-track { grid-column: 1 / -1; }
}

/* ---------- Trang chờ tải ---------- */
.gate { max-width: 680px; margin: 0 auto; }
.gate-main { text-align: center; }
.gate-count {
  font-size: 1.05rem; color: var(--text-muted); margin: 0 0 16px;
  /* Số đếm lùi đổi từng giây. Chữ số bề ngang bằng nhau thì dòng chữ
     đứng yên; không thì cả dòng nhúc nhích theo mỗi nhịp đếm. */
  font-variant-numeric: tabular-nums;
}
.gate-started { margin: 14px 0 0; }
.btn-lg { font-size: 1.02rem; padding: 12px 26px; }
/* Ô quảng cáo nằm DƯỚI nút tải và có nhãn riêng. Đặt nó cạnh nút đang
   đếm ngược là mời người ta bấm nhầm — chính sách AdSense gọi đó là
   accidental click và cấm. */
.gate-ad { margin-top: 26px; }
.gate-ad-label {
  font-size: .68rem; letter-spacing: .08em; color: var(--text-faint);
  margin-bottom: 6px;
}
.gate-ad ins { min-height: 100px; }

/* ---------- Danh mục affiliate ---------- */
.btn-sm { padding: 6px 13px; font-size: .84rem; white-space: nowrap; }

/* Dải mở đầu là một BĂNG MÀU, không phải thẻ xám như các trang khác.
   Đây là trang duy nhất trên site mời người đọc đi kiếm tiền, nên nó
   được phép to tiếng hơn — nhưng chỉ MỘT khối thôi, không rải màu khắp
   trang: cái gì cũng nổi thì không cái gì nổi.

   Màu lấy từ biến thương hiệu chứ không viết cứng, nên đổi màu site
   một chỗ là băng này đổi theo. Chữ trắng trên nền đậm nên tương phản
   đạt ở cả chế độ sáng lẫn tối, không phải khai riêng bản tối. */
.aff-hero {
  position: relative; overflow: hidden; isolation: isolate;
  border-radius: calc(var(--radius) * 1.6);
  padding: 34px 30px 32px;
  color: #fff;
  /* Nền dự phòng, hiện ngay trước khi ảnh tải xong và thay cho ảnh nếu
     ảnh không về được. Chữ trắng vẫn đọc được trên nền này. */
  background: linear-gradient(135deg, var(--brand-raw) 0%, #0b6f85 55%, #123a63 100%);
  box-shadow: 0 10px 30px -14px rgba(1, 170, 194, .55);
}

/* Ảnh banner nằm ở lớp riêng phía sau, không phải background của .aff-hero:
   như vậy mới đặt được <img> có srcset, để trình duyệt tự chọn cỡ ảnh theo
   màn hình. Dùng background-image thì mọi máy tải cùng một tệp. */
.aff-hero-bg {
  position: absolute; inset: 0; z-index: -2;
  width: 100%; height: 100%; object-fit: cover; object-position: 72% 50%;
}

/* Lớp phủ tối cho chữ đọc được.
 *
 * Đây KHÔNG phải trang trí. Đo trên chính ảnh này: chỗ sáng nhất ở nửa
 * trái chỉ cho chữ trắng tương phản 2.1:1, trong khi chữ thường cần
 * 4.5:1 — tức là không có lớp phủ thì câu dẫn gần như không đọc nổi ở
 * những vệt sáng. Với lớp phủ 92% nhạt dần tới 85% bề ngang, chỗ tệ
 * nhất trong vùng chữ đạt 4.8:1. Vùng chữ vì thế bị giới hạn 46% bề
 * ngang (.aff-hero-text) — rộng hơn là tràn sang chỗ lớp phủ đã nhạt.
 *
 * Đổi ảnh khác thì phải đo lại, đừng chép nguyên mấy con số này.
 */
.aff-hero::before {
  content: ''; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(90deg,
    rgba(4, 22, 46, .92) 0%,
    rgba(4, 22, 46, .74) 38%,
    rgba(4, 22, 46, .30) 64%,
    rgba(4, 22, 46, 0) 85%);
}
.aff-hero-text { position: relative; max-width: 46%; }
.aff-hero h1 {
  margin: 0 0 10px; font-size: 2rem; line-height: 1.18; color: #fff;
  letter-spacing: -.01em;
}
.aff-hero p { margin: 0; color: rgba(255, 255, 255, .92); }

/* Hàng nhãn dưới câu dẫn: nói ngay trang có gì, không bắt người đọc
   cuộn xuống bảng mới biết. */
.aff-hero-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; }
.aff-hero-tag {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; border-radius: 99px;
  background: rgba(255, 255, 255, .14);
  border: 1px solid rgba(255, 255, 255, .24);
  font-size: .8rem; font-weight: 600;
}
.aff-hero-tag .ic { width: 1em; height: 1em; }

/* Hình trang trí ở góc phải. aria-hidden ở HTML, và thu nhỏ dần theo
   màn: dưới 680px nó chen vào sau chữ chứ không còn là nền. */
.aff-hero-art {
  position: absolute; right: -16px; bottom: -22px;
  display: flex; gap: 14px; color: #fff; opacity: .16;
  pointer-events: none;
}
.aff-hero-art .ic { width: 104px; height: 104px; }
.aff-hero-art .ic:nth-child(2) { width: 64px; height: 64px; align-self: flex-end; }
@media (max-width: 900px) { .aff-hero-art .ic { width: 76px; height: 76px; } }

/* Màn hẹp: chữ chiếm cả bề ngang nên lớp phủ phải đậm đều từ trái sang
   phải, không nhạt dần nữa — nhạt dần ở đây là chữ nằm thẳng trên vệt
   sáng của ảnh. Ảnh lùi về làm nền mờ phía sau. */
@media (max-width: 900px) {
  .aff-hero-text { max-width: none; }
  .aff-hero::before {
    background: linear-gradient(90deg,
      rgba(4, 22, 46, .92) 0%,
      rgba(4, 22, 46, .88) 60%,
      rgba(4, 22, 46, .82) 100%);
  }
  .aff-hero-bg { object-position: 78% 50%; }
}
@media (max-width: 680px) {
  .aff-hero { padding: 24px 20px; }
  .aff-hero h1 { font-size: 1.55rem; }
  .aff-hero-art { display: none; }
}

/* Con số thống kê. tabular-nums để bốn ô không nhảy chữ khi số đổi độ
   rộng — bảng số mà lệch nhau một pixel là mắt thấy ngay. */
.aff-stat {
  font-size: 1.9rem; font-weight: 700; line-height: 1.1; margin: 2px 0 4px;
  font-variant-numeric: tabular-nums;
}

/* Dòng gọn trong bốn thẻ gợi ý ở đầu trang. */
.aff-mini {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 0; border-top: 1px solid var(--border);
}
.aff-mini:first-child { border-top: 0; padding-top: 0; }
.aff-mini a { font-weight: 600; }
/* Tên dài phải co lại được, nếu không nút "Tham gia" bên phải bị đẩy
   tràn ra khỏi thẻ. flex item mặc định không co dưới kích thước nội
   dung, nên min-width: 0 là bắt buộc chứ không phải cho đẹp. */
.aff-mini > div { min-width: 0; }
/* break-word chứ KHÔNG phải anywhere: anywhere bẻ cả giữa từ, nên
   "Accesstrade" hiện ra thành "Accesstrad / e". Chỉ xuống dòng khi cả
   từ không vừa mới đúng. */
.aff-mini a { overflow-wrap: break-word; }

/* Nút "Tham gia" trong thẻ: nhỏ hơn nút trong bảng, và không được co
   lại thành hai dòng chữ.

   Cho cả dòng xuống hàng được (flex-wrap) vì tên tiếng Việt dài hơn tên
   tiếng Anh nhiều: "Accesstrade Việt Nam" đứng cạnh nút trong một thẻ
   hẹp thì bị ép thành ba dòng, mà ba thẻ bên cạnh chỉ một dòng — lại
   đúng cái cao thấp lệch nhau. Chật thì nút tự tụt xuống dòng dưới,
   tên vẫn đọc được bình thường. */
.aff-cards .aff-mini { flex-wrap: wrap; row-gap: 6px; }
.aff-join {
  flex: 0 0 auto; margin-left: auto; padding: 4px 10px;
  font-size: .72rem; white-space: nowrap;
}
/* Tên cần ít nhất chừng này mới không bị bẻ giữa chừng; hẹp hơn thì
   nút xuống dòng thay vì bóp tên. */
.aff-cards .aff-mini > div { flex: 1 1 9rem; }

/* Hàng tiêu đề của bốn thẻ phải cao bằng nhau, nếu không hàng dữ liệu
   đầu tiên của mỗi thẻ bắt đầu ở một độ cao khác nhau và bốn thẻ nhìn
   thành bốn khối rời rạc.

   Hai thẻ có liên kết phụ ("Nhóm chương trình →"), hai thẻ không. Thẻ
   rộng 536px nên liên kết nằm cùng dòng với tiêu đề và bốn hàng đầu cao
   bằng nhau; min-height giữ cho vẫn đều nếu sau này thêm thẻ. */
.aff-cards .card-head { min-height: 58px; align-items: center; }

/* Ô hình tròn có tông màu, dùng cho nhóm lĩnh vực và cách nhận tiền.
   Cùng khuôn với ô logo chương trình (.aff-logo) để bốn thẻ nhìn thành
   một bộ. Tông màu lấy từ --dot-h đặt tại chỗ dùng, nên thêm một nhóm
   mới chỉ cần thêm một con số chứ không phải thêm một lớp CSS. */
.aff-dot {
  flex: 0 0 auto;
  width: 30px; height: 30px; border-radius: 9px;
  display: inline-flex; align-items: center; justify-content: center;
  background: hsl(var(--dot-h, 200) 70% 94%);
  color: hsl(var(--dot-h, 200) 55% 34%);
}
.aff-dot svg { width: 16px; height: 16px; }
/* Logo thật trong cùng khuôn ô chấm. Cùng width/height nên dòng có ảnh
   và dòng chưa có ảnh cao bằng nhau, cột chữ bên phải không bị so le.
   contain + padding để logo chữ nhật không bị cắt mất hai đầu, và nền
   trung tính vì logo nguồn phần lớn nền trắng. */
.aff-dot-img {
  object-fit: contain; padding: 3px;
  background: var(--surface-2); color: inherit;
}

/* Logo nhóm lĩnh vực trong ô danh mục. object-fit: contain để logo
   không bị kéo méo — logo nguồn có cái vuông có cái chữ nhật. */
/* Thẻ nhóm lĩnh vực: ô logo bên trái, chữ bên phải.
 *
 * Lần trước tôi nhét logo vào .aff-tile-ic — ô chấm trang trí ở góc,
 * vốn không khai kích thước. Ảnh thật cỡ 300px bung ra phủ kín thẻ ở
 * mức mờ 45%, chữ nằm đè lên một hình minh hoạ rối mắt. Không đọc nổi
 * một dòng nào. Nay ô logo có kích thước rõ ràng và nằm ngoài luồng
 * chữ, nên không có cách nào chồng lên nhau được nữa.
 */
.aff-cat {
  display: flex; align-items: flex-start; gap: 14px;
  padding: 16px; text-decoration: none; color: inherit;
  border-left: 3px solid hsl(var(--cat-h, 187) 60% 62%);
}
.aff-cat-ic {
  flex: 0 0 auto;
  width: 48px; height: 48px; border-radius: 10px;
  display: inline-flex; align-items: center; justify-content: center;
  background: hsl(var(--cat-h, 187) 70% 95%);
  color: hsl(var(--cat-h, 187) 55% 34%);
  overflow: hidden;
}
.aff-cat-ic img { width: 48px; height: 48px; object-fit: cover; display: block; }
.aff-cat-ic .ic { width: 24px; height: 24px; }
.aff-cat-body { display: block; min-width: 0; flex: 1; }
.aff-cat-name {
  display: block; font-weight: 650; font-size: 1rem; color: var(--text);
  margin-bottom: 2px;
}
.aff-cat:hover .aff-cat-name { color: var(--brand); }
.aff-cat-count { display: block; font-size: .8rem; color: var(--text-faint); }
/* Tên vài chương trình trong nhóm, cắt gọn hai dòng: để người đọc biết
   bấm vào sẽ thấy gì, chứ một con số thì không nói lên điều đó. */
/* Đầu trang chi tiết nhóm: logo to bên trái, tên và mô tả bên phải. */
.aff-cat-head { display: flex; align-items: flex-start; gap: 16px; }
.aff-cat-ic-lg { width: 64px; height: 64px; border-radius: 12px; }
.aff-cat-ic-lg img { width: 64px; height: 64px; }
.aff-cat-teaser { max-width: 70ch; margin-top: 6px; }
@media (max-width: 560px) {
  .aff-cat-head { gap: 12px; }
  .aff-cat-ic-lg, .aff-cat-ic-lg img { width: 48px; height: 48px; }
}

.aff-cat-names {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; margin-top: 6px;
  font-size: .78rem; line-height: 1.45; color: var(--text-faint);
}

/* Bốn thẻ trên một hàng chỉ hợp từ màn rộng trở lên. Hẹp hơn thì hai
   hàng hai thẻ, còn điện thoại thì xếp dọc — bốn cột trên 390px thì
   mỗi cột hẹp hơn một ngón tay. */
/* Bốn thẻ xếp 2×2, KHÔNG phải bốn cột một hàng.
   Lý do bằng số đo, không phải cảm tính: khung trang giới hạn khoảng
   1200px, nên bốn thẻ một hàng thì mỗi thẻ chỉ 260px dù màn hình rộng
   bao nhiêu. Phần tên khi đó còn chưa tới 120px, mà "Accesstrade Việt
   Nam" cần hơn thế — tên xuống ba dòng, bốn thẻ cao thấp lệch hẳn. Hai
   cột thì mỗi thẻ 536px, tên nằm gọn một dòng, đo ở sáu khổ màn hình
   đều một chiều cao. Tên tiếng Việt dài hơn tên tiếng Anh, nên không bê
   nguyên được bố cục một hàng của trang tiếng Anh. */
.aff-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 680px) {
  .aff-cards { grid-template-columns: minmax(0, 1fr); }
}

/* Ô chữ cái đầu thay cho logo. Không đi lấy favicon từ dịch vụ bên thứ
   ba: mỗi lần hiện bảng là mỗi lượt gửi danh sách thương hiệu người đọc
   đang xem sang một máy chủ khác. */
.aff-logo {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 9px;
  object-fit: contain; background: var(--surface-2);
}
.aff-logo-letter {
  display: grid; place-items: center; font-weight: 700; font-size: .95rem;
}
.aff-profile-id .aff-logo {
  width: 54px; height: 54px; border-radius: 13px; font-size: 1.4rem;
}

.aff-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
/* Chia sẵn bề ngang. Không chia thì cột mô tả nuốt hết chỗ và cột hoa
   hồng còn lại một chữ mỗi dòng — đúng cái đã thấy ở lần dựng đầu. */
/* table-layout: fixed để các phần trăm dưới đây được tôn trọng thật.
   Ở chế độ tự động, chiều rộng tối thiểu của nội dung thắng: một chuỗi
   nhãn "Thương mại điện tử" không xuống dòng được sẽ bóp cột hoa hồng
   còn ba chữ mỗi dòng, đúng như lần dựng trước. */
/* 1370px: chín cột cần chừng này mới không cột nào phải cắt chữ — con
   số lấy từ scripts/layout-test.mjs, không phải ước lượng. Khung trang
   rộng 1120px nên bảng cuộn ngang một đoạn; từ 1400px trở lên thẻ chứa
   bảng nới rộng ra hai bên nên vừa khít, không phải cuộn. */
.aff-table { min-width: 1370px; table-layout: fixed; }
/* Tám cột. Phần trăm chia theo SỐ ĐO thật của nội dung dài nhất trong
   mỗi cột chứ không chia đều — xem scripts/layout-test.mjs, nó đo lại
   mỗi lần chạy và báo đỏ nếu cột nào hẹp tới mức phải cắt chữ. */
.aff-table th:nth-child(1), .aff-table td:nth-child(1) { width: 18%; }
.aff-table th:nth-child(2), .aff-table td:nth-child(2) { width: 11%; }
.aff-table th:nth-child(3), .aff-table td:nth-child(3) { width: 14%; }
.aff-table th:nth-child(4), .aff-table td:nth-child(4) { width: 13%; }
.aff-table th:nth-child(5), .aff-table td:nth-child(5) { width: 8%; }
.aff-table th:nth-child(6), .aff-table td:nth-child(6) { width: 14%; }
.aff-table th:nth-child(7), .aff-table td:nth-child(7) { width: 8%; }
.aff-table th:nth-child(8), .aff-table td:nth-child(8) { width: 7%; }
.aff-table th:last-child, .aff-table td:last-child { width: 7%; text-align: right; }
/* Trên điện thoại bỏ hai cột ít giá trị nhất khi đang lướt: địa chỉ
   trang và năm ra mắt. Bảy cột trên màn 390px thì cuộn ngang mãi không
   hết, mà người cầm điện thoại chỉ cần biết tên, hoa hồng và nút bấm;
   phần còn lại nằm đủ ở trang hồ sơ. */
@media (max-width: 760px) {
  /* 700px chứ không phải 620: ở 620 thì ô nhãn hẹp hơn nhãn dài nhất
     khoảng 16px, chữ bị cắt. Màn này vốn đã cuộn ngang nên thêm 80px
     không làm thay đổi cách dùng, mà nhãn thì đọc được trọn vẹn. */
  .aff-table { min-width: 700px; }
  .aff-table th:nth-child(3), .aff-table td:nth-child(3),
  .aff-table th:nth-child(5), .aff-table td:nth-child(5),
  .aff-table th:nth-child(7), .aff-table td:nth-child(7),
  .aff-table th:nth-child(8), .aff-table td:nth-child(8) { display: none; }
}
/* Màn hẹp: ô nhãn chỉ còn khoảng 111px mà nhãn dài nhất cần tới 137px.
   Thà bỏ icon, thu chữ một chút và cho mỗi nhãn một dòng riêng, còn hơn
   cắt chữ thành "Thương mại điện…" — người đọc mất thông tin thật.
   Mỗi nhãn vẫn đúng một dòng nên chiều cao vẫn bằng nhau. */
@media (max-width: 1180px) {
  .aff-table .chip-row { flex-direction: column; align-items: flex-start; }
  .aff-table .chip-row .chip { font-size: .68rem; padding: 3px 6px; }
  .aff-table .chip-row .chip > svg { display: none; }
}
.aff-table th {
  text-align: left; padding: 11px 14px; white-space: nowrap;
  font-size: .72rem; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-faint); border-bottom: 1px solid var(--border);
}
.aff-table td {
  padding: 13px 11px; border-top: 1px solid var(--border);
  vertical-align: top; overflow-wrap: anywhere;
}
.aff-table tbody tr:first-child td { border-top: 0; }

/* Cột TÊN dính lại khi cuộn ngang.
 *
 * Bảng chín cột rộng 1400px, màn hẹp hơn thì phải cuộn. Cuộn mà cột tên
 * trôi mất thì người đọc nhìn một ô "92.6" chẳng biết của chương trình
 * nào — đúng kiểu bảng rộng dùng không nổi. Giữ cột đầu đứng yên thì
 * cuộn tới đâu vẫn biết mình đang xem dòng của ai.
 *
 * Phải có nền đục, nếu không chữ của cột bên dưới chạy xuyên qua nó.
 */
.aff-table th:first-child,
.aff-table td:first-child {
  position: sticky; left: 0; z-index: 2;
  background: var(--surface);
}
.aff-table thead th:first-child { z-index: 3; }
tbody tr:hover td:first-child { background: var(--surface-2); }
/* Vạch phân cách để thấy rõ chỗ cột dính kết thúc và phần cuộn bắt đầu. */
.aff-table th:first-child::after,
.aff-table td:first-child::after {
  content: ''; position: absolute; top: 0; right: 0; bottom: 0; width: 1px;
  background: var(--border);
}
@media (hover: hover) {
  .aff-table tbody tr:hover { background: var(--surface-2); }
}
/* Không đặt min-width ở đây: với table-layout: fixed, nội dung rộng hơn
   ô không nong ô ra mà tràn đè sang cột bên cạnh. Bề ngang do phần trăm
   cột và min-width của cả bảng quyết định. */
.aff-td-name { display: flex; align-items: flex-start; gap: 11px; min-width: 0; }
.aff-td-name a { font-weight: 650; }
/* Cắt mô tả còn hai dòng. Bảng là để liếc qua rồi so sánh; ai muốn đọc
   hết thì bấm vào tên để sang trang hồ sơ, nơi có cả đoạn dài. Để
   nguyên thì một mục viết dài đẩy cao cả hàng và bảng mất tác dụng so
   sánh. */
.aff-td-sum {
  margin: 4px 0 0; font-size: .82rem; color: var(--text-muted);
  line-height: 1.55;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  line-clamp: 2; overflow: hidden;
}
.aff-year { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ---------- Nhãn trong bảng affiliate ---------- */
/* Hàng nhãn phải là flex có khoảng cách. Để inline thì hai nhãn xuống
   hai dòng dính sát nhau, đọc ra thành một khối chữ liền — đúng thứ
   bảng này dựng ra để tránh. */
.chip-row { display: flex; flex-wrap: wrap; gap: 5px 6px; min-width: 0; }
/* MỌI nhãn trong bảng phải đúng MỘT dòng và cùng MỘT chiều cao.
   Lịch sử ba lần sai ở đúng chỗ này, nên ghi lại cho rõ:

   Lần 1 — để nowrap trong bảng table-layout:fixed: nhãn dài hơn ô không
   nong ô ra mà TRÀN ra đè lên cột bên cạnh ("International transfer" đè
   lên năm 1996, chỉ trang /en mới lộ vì nhãn tiếng Anh dài hơn).

   Lần 2 — chữa bằng cách cho xuống dòng: hết tràn, nhưng nhãn nhóm cao
   hai dòng còn nhãn phụ một dòng, lại thêm bo tròn 20px nên cái hai
   dòng phình thành quả bóng cạnh mấy cái nhỏ. Đó là chỗ người dùng kêu
   "kích thước to nhỏ nhìn rất khó chịu".

   Nay: nowrap + overflow hidden + ellipsis nên không bao giờ tràn, bo
   9px, cùng min-height và cùng cỡ chữ. Cột thì chia theo số đo thật của
   nhãn dài nhất, còn màn hẹp có quy tắc riêng ở trên, nên thực tế không
   nhãn nào phải cắt chữ — ellipsis chỉ là lưới an toàn. Hai điều kiện
   này có phép kiểm canh trong scripts/layout-test.mjs, đo bằng trình
   duyệt thật ở mười khổ màn hình. */
.chip-row .chip {
  display: inline-flex; align-items: center; gap: 4px;
  max-width: 100%; text-align: left;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-height: 24px; border-radius: 9px; padding: 3px 8px;
  font-size: .745rem; line-height: 1.3;
}
.chip-row .chip > svg { flex: 0 0 auto; }
.chip-row .chip .ic { width: .95em; height: .95em; flex: 0 0 auto; opacity: .85; }

/* Bốn tông, phân theo NGHĨA chứ không phải cho vui mắt:
     xanh thương hiệu  — nhóm chính của chương trình
     xám               — nhãn phụ, thông tin bổ sung
     xanh lá           — tiền về thẳng ngân hàng trong nước
     hổ phách          — tiền phải qua trung gian quốc tế
   Màu là lớp thứ hai, không phải lớp duy nhất: chữ trong nhãn vẫn nói
   đủ nghĩa nên người không phân biệt được màu không mất gì. */
/* Viết .chip.chip-cat chứ không phải .chip-cat.
   Khối nhãn affiliate này nằm TRƯỚC quy tắc .chip gốc trong tệp, mà hai
   bên cùng độ cụ thể nên quy tắc đứng sau thắng: nền xám của .chip ghi
   đè sạch màu nhóm, và cả cột lại ra một dải xám đúng như lúc chưa tô.
   Nhân đôi lớp lên 0,2,0 thì thứ tự khai không còn quyết định nữa —
   chắc chắn hơn là đi chuyển cả khối xuống dưới rồi lần sau lại có
   người chuyển lên.

   MỖI NHÓM MỘT MÀU.
   Trước đây mọi nhãn nhóm đều xanh thương hiệu, nên nhìn cả bảng thì
   cột Nhóm chỉ là một dải xanh đều — màu có mà không phân biệt được gì,
   tức là vô dụng. Nay màu dẫn từ --cat-h (sắc độ) đặt trên từng nhãn,
   nên thêm nhóm mới chỉ cần thêm một dòng sắc độ, không phải chép lại
   cả bộ ba thuộc tính. */
.chip.chip-cat {
  /* KHÔNG đặt --cat-h ở đây.
     Quy tắc này có độ cụ thể 0,2,0 còn .cat-network và các lớp anh em
     chỉ 0,1,0, nên một giá trị mặc định đặt ở đây sẽ THẮNG sắc độ của
     từng nhóm và cả bảng lại về một màu. Giá trị dự phòng viết ngay
     trong var() thì không tham gia tranh chấp: nhóm nào có lớp riêng
     thì lấy sắc độ của nó, nhóm lạ rơi về 187.
     Nền 22% và viền 55%: bản 12%/30% trước đó nhìn cả cột vẫn ra một
     dải xám nhạt, tức là có màu mà không phân biệt được — bằng không
     có. Đậm tới mức này thì liếc qua là tách được nhóm, mà vẫn là nhãn
     chứ chưa thành cái nút. */
  background: color-mix(in srgb, hsl(var(--cat-h, 187) 70% 48%) 22%, transparent);
  color: hsl(var(--cat-h, 187) 65% 26%);
  border-color: color-mix(in srgb, hsl(var(--cat-h, 187) 70% 45%) 55%, transparent);
  font-weight: 650;
}
.chip.chip-cat .ic { opacity: 1; }
.cat-network { --cat-h: 250; }
.cat-ecommerce { --cat-h: 24; }
.cat-hosting { --cat-h: 196; }
.cat-software { --cat-h: 292; }
.cat-vpn { --cat-h: 142; }
.cat-finance { --cat-h: 172; }
.cat-education { --cat-h: 334; }
.cat-travel { --cat-h: 88; }
.cat-marketing { --cat-h: 15; }
.cat-health { --cat-h: 120; }
.cat-fashion { --cat-h: 315; }
.cat-gaming { --cat-h: 262; }
.cat-other { --cat-h: 220; }
/* Nền tối: chữ sắc độ đó ở độ sáng 32% chỉ còn khoảng 2:1 trên nền đen.
   Nâng sáng và giảm nền, giữ nguyên sắc độ để vẫn nhận ra cùng nhóm. */
:root[data-theme="dark"] .chip.chip-cat {
  color: hsl(var(--cat-h, 187) 80% 78%);
  background: color-mix(in srgb, hsl(var(--cat-h, 187) 70% 55%) 24%, transparent);
  border-color: color-mix(in srgb, hsl(var(--cat-h, 187) 70% 60%) 50%, transparent);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .chip.chip-cat {
    color: hsl(var(--cat-h, 187) 80% 78%);
    background: color-mix(in srgb, hsl(var(--cat-h, 187) 70% 55%) 24%, transparent);
    border-color: color-mix(in srgb, hsl(var(--cat-h, 187) 70% 60%) 50%, transparent);
  }
}
.chip.chip-tag { background: var(--surface-2); color: var(--text-muted); }
.chip.chip-pay-vn {
  background: color-mix(in srgb, var(--ok) 12%, transparent);
  color: var(--ok);
  border-color: color-mix(in srgb, var(--ok) 28%, transparent);
  font-weight: 600;
}
.chip.chip-pay, .chip.chip-pay-card {
  background: color-mix(in srgb, var(--warn) 10%, transparent);
  color: var(--warn);
  border-color: color-mix(in srgb, var(--warn) 24%, transparent);
}
.chip.chip-pay-wallet {
  background: color-mix(in srgb, #7c3aed 11%, transparent);
  color: #6d28d9;
  border-color: color-mix(in srgb, #7c3aed 26%, transparent);
}
/* Tím đậm trên nền tối chỉ còn khoảng 2:1, đọc không ra. Hai biến thể
   kia lấy màu từ biến theme nên tự đổi; nhãn này viết màu thẳng. */
:root[data-theme="dark"] .chip.chip-pay-wallet { color: #c4b5fd; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .chip.chip-pay-wallet { color: #c4b5fd; }
}

/* Tên miền trong cột Website: biểu tượng mắt xích nói trước rằng đây là
   đường đi ra ngoài, trước khi người đọc kịp rê chuột lên xem. */
/* Ô thống kê và thẻ nhóm: hình đặt góc trên phải, mờ, to — nó là nền
   trang trí chứ không phải thứ để bấm, nên nó không được tranh chỗ với
   con số. Đặt absolute để không đẩy chữ xuống. */
.aff-tile, .aff-cat { position: relative; overflow: hidden; }
.aff-tile-ic {
  position: absolute; top: 12px; right: 12px;
  color: var(--text-faint); opacity: .45; pointer-events: none;
}
.aff-tile-ic .ic { width: 26px; height: 26px; }
/* Chừa chỗ cho hình ở góc: nhãn dài ("TRẢ THẲNG VỀ NGÂN HÀNG VN") chạy
   thẳng vào dưới hình nếu không chừa, và hai thứ chồng lên nhau. */
.aff-tile .stat-label { padding-right: 34px; }
.aff-tile { border-top: 3px solid color-mix(in srgb, var(--brand) 45%, var(--border)); }
.aff-tile .aff-tile-ic { color: var(--brand); opacity: .35; }
.aff-tile-ok { border-top-color: var(--ok); }
.aff-tile-ok .aff-tile-ic { color: var(--ok); opacity: .55; }
.aff-tile-ok .aff-stat { color: var(--ok); }
@media (hover: hover) {
  .aff-cat:hover .aff-tile-ic { opacity: .7; }
}

/* Nhãn của từng dòng thông số: hình đứng trước chữ, cùng cỡ với chữ. */
.app-fact dt.head-ic { gap: 7px; }
.app-fact dt .ic { width: 1em; height: 1em; color: var(--text-faint); flex: 0 0 auto; }

/* Tên miền cắt bằng dấu ba chấm chứ không xuống dòng: "accesstrade." /
   "vn" trên hai dòng đọc ra như hai thứ khác nhau, mà cột này chỉ cần
   cho biết đây là trang nào. */
.aff-site {
  display: inline-flex; align-items: center; gap: 5px; max-width: 100%;
}
.aff-site .ic { width: .95em; height: .95em; flex: 0 0 auto; opacity: .7; }
.aff-site span {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}

/* Phân trang. Ô bấm 40px để ngón tay bấm trúng trên điện thoại —
   dưới mức đó là vùng bấm nhỏ hơn đầu ngón tay, và người ta bấm nhầm
   sang số bên cạnh rồi phải quay lại. */
.pager { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 16px; }
.pager-link {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 40px; height: 40px; padding: 0 12px;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface); color: var(--text); text-decoration: none;
  font-size: .88rem; font-variant-numeric: tabular-nums;
}
@media (hover: hover) {
  .pager-link:hover { border-color: var(--border-strong); background: var(--surface-2); }
}
.pager-link.is-current {
  background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 650;
}
.pager-gap {
  display: inline-flex; align-items: center; height: 40px;
  padding: 0 4px; color: var(--text-faint);
}

/* Thẻ nhóm mang đúng màu của nhóm đó, cùng bảng sắc độ với nhãn trong
   bảng — nhìn thẻ rồi nhìn bảng là nhận ra cùng một nhóm. */
.aff-cat {
  display: block; text-decoration: none;
  border-top: 3px solid hsl(var(--cat-h, 187) 60% 55%);
  background:
    linear-gradient(180deg, color-mix(in srgb, hsl(var(--cat-h, 187) 65% 50%) 8%, transparent), transparent 60%),
    var(--surface);
}
.aff-cat h3 { margin: 0 0 4px; font-size: 1.02rem; color: hsl(var(--cat-h, 187) 55% 30%); }
:root[data-theme="dark"] .aff-cat h3 { color: hsl(var(--cat-h, 187) 70% 74%); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .aff-cat h3 { color: hsl(var(--cat-h, 187) 70% 74%); }
}
.aff-cat .aff-tile-ic { color: hsl(var(--cat-h, 187) 60% 45%); opacity: .45; }

/* Thanh điểm. Chỉ là hình minh hoạ cho con số ngay bên cạnh, nên nó
   aria-hidden: đọc "ba trên năm" đã đủ, nghe thêm một thanh rỗng thì
   không. */
.aff-score {
  display: inline-block; width: 110px; height: 8px; border-radius: 99px;
  background: var(--border); overflow: hidden; vertical-align: middle;
}
.aff-score-fill { display: block; height: 100%; background: var(--brand); }
/* Ô điểm trong bảng: thanh nằm trên, con số nằm dưới. Con số là phần
   bắt buộc — một thanh màu không có số thì người đọc không biết nó so
   với cái gì, và không kiểm lại được. Bấm vào mở đúng phần năm tiêu chí
   ở trang hồ sơ. */
.aff-score-link {
  display: inline-flex; flex-direction: column; gap: 4px;
  min-width: 54px; text-decoration: none; color: inherit;
}
.aff-score-link:hover .aff-score-num { color: var(--brand); }
.aff-score-num { font-size: .72rem; font-variant-numeric: tabular-nums; color: var(--text-faint); }
/* Dấu hỏi sau điểm: còn tiêu chí chưa có dữ liệu, nên con số này mới là
   tạm. Nói ra thì hơn giấu đi. */
.aff-score-q { margin-left: 2px; opacity: .7; cursor: help; }
.aff-rate { display: inline-flex; flex-direction: column; gap: 4px; min-width: 52px; cursor: help; }
.aff-rate .aff-score { width: 100%; }
.aff-rank { font-variant-numeric: tabular-nums; white-space: nowrap; }
.aff-td-score .aff-score { width: 100%; }

.aff-score-list { list-style: none; margin: 0; padding: 0; }
.aff-score-list li {
  display: flex; gap: 10px; align-items: baseline;
  padding: 8px 0; border-top: 1px solid var(--border);
}
.aff-score-list li:first-child { border-top: 0; }
.aff-score-mark {
  flex: 0 0 auto; width: 20px; height: 20px; border-radius: 50%;
  display: grid; place-items: center; font-size: .75rem;
  background: var(--border); color: var(--text-faint);
}
.aff-score-list li.got .aff-score-mark { background: var(--ok); color: #fff; }

.aff-profile-head {
  display: flex; flex-wrap: wrap; gap: 18px 24px;
  align-items: center; justify-content: space-between;
}
.aff-profile-id { display: flex; align-items: center; gap: 14px; min-width: 0; flex: 1; }
.aff-profile-cta { display: flex; flex-wrap: wrap; gap: 10px; }

/* ---------- Mời cà phê ---------- */
/* Mã QR đứng cạnh phần chữ trên màn rộng, xuống dưới ở điện thoại. Mã
   phải đủ to để quét được bằng máy khác chĩa vào màn hình — nhỏ quá thì
   camera bắt không ra, mà đó lại là cách nhiều người dùng nhất. */
.coffee-grid { display: grid; gap: 22px; align-items: start; }
@media (min-width: 680px) {
  .coffee-grid { grid-template-columns: 240px minmax(0, 1fr); gap: 28px; }
}
.coffee-qr img {
  display: block; width: 100%; max-width: 240px; height: auto;
  /* Nền trắng cố định: mã QR in mực tối trên nền trong suốt, đặt lên
     nền tối của bản dark là máy quét không phân biệt được ô sáng ô tối. */
  background: #fff; border: 1px solid var(--border); border-radius: 12px; padding: 6px;
}
.coffee-facts { margin: 0; display: grid; gap: 8px; }
.coffee-row { display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline; }
.coffee-row dt { font-size: .78rem; color: var(--text-muted); min-width: 108px; }
.coffee-row dd { margin: 0; }
.coffee-value {
  font-family: var(--mono); font-size: .9rem; color: var(--text);
  background: var(--surface-2); border-radius: 6px; padding: 2px 8px;
  /* Một cú bấm chọn trọn số tài khoản, khỏi phải kéo bôi từng ký tự. */
  user-select: all;
}

/* ---------- Trang chủ: khối mở đầu ---------- */
/* Một cột, canh giữa, cho tới 920px. Rộng hơn thì tách hai cột: chữ bên
   trái, hình bên phải. Trước đây chữ canh giữa ở mọi bề ngang, nên trên
   màn rộng nửa phải của màn hình trống trơn còn các dòng chữ thì dài
   ngắn so le nhau — nhìn như trang chưa làm xong. */
.hero { display: grid; gap: 30px; align-items: center; padding: 48px 0 40px; text-align: center; }
.hero-copy { max-width: 620px; margin: 0 auto; }
.hero h1 { font-size: 2.05rem; line-height: 1.2; letter-spacing: -.03em; margin: 16px 0 12px; }
.hero-lead { font-size: 1.05rem; color: var(--text-muted); margin: 0 0 24px; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
.hero-art { width: 100%; max-width: 560px; margin: 0 auto; }
.hero-art img { width: 100%; height: auto; display: block; border-radius: 16px; }
/* Bản tối ẩn mặc định; theme.ts bật nó lên và tắt bản sáng.
   Bộ chọn phải cụ thể hơn .hero-art img ở trên, nếu không luật kia
   thắng và trang hiện CẢ HAI ảnh chồng nhau — đúng cái bẫy đã gặp với
   logo sáng/tối trên thanh điều hướng. */
.hero-art img.hero-art-dark { display: none; }
@media (min-width: 560px) { .hero h1 { font-size: 2.4rem; } }
@media (min-width: 920px) {
  .hero {
    grid-template-columns: minmax(0, 1fr) minmax(0, .95fr);
    gap: 52px; text-align: left; padding: 60px 0 52px;
  }
  .hero-copy { margin: 0; }
  .hero-actions { justify-content: flex-start; }
  .hero-art { margin: 0; max-width: none; }
}

/* ---------- Danh mục phần mềm ---------- */
/* Thẻ rộng hơn trước (320px thay vì 270px): thẻ giờ có ảnh bìa ngang và
   vài dòng mô tả, mà 270px thì dòng mô tả gãy thành 4-5 dòng rất vụn. */
.tool-grid { display: grid; gap: 18px; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
.tool-card {
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
  overflow: hidden; color: inherit;
  transition: border-color .12s ease, transform .12s ease;
}
/* Ảnh đầu thẻ dùng ĐÚNG khung như đầu trang chi tiết: cùng tỉ lệ 3:1,
   cùng kiểu phủ kín. Khung khác nhau giữa hai nơi là lý do một ảnh bìa
   ngang từng hiện vừa khít ở trang chi tiết nhưng lại chừa hai dải xám
   trong danh mục. */
.tool-card-cover {
  display: block; width: 100%; aspect-ratio: 3 / 1;
  object-fit: cover; object-position: center;
  background: var(--surface-2); border-bottom: 1px solid var(--border);
}
/* flex: 1 để thân thẻ chiếm hết chiều cao còn lại — nhờ vậy chân thẻ
   (giá và nút) của mọi thẻ trong cùng một hàng nằm trên một đường thẳng,
   dù mô tả dài ngắn khác nhau. */
.tool-card-body { display: flex; flex-direction: column; gap: 12px; padding: 18px 20px 20px; flex: 1; }
/* Mô tả cắt còn 3 dòng để mọi thẻ cao bằng nhau. Dùng line-clamp chứ
   không cắt chuỗi ở máy chủ: cắt theo số ký tự sẽ ra độ dài khác nhau
   tuỳ chữ, còn trình duyệt thì cắt đúng theo chỗ thật sự còn trống. */
.tool-desc {
  margin: 0; font-size: .88rem; line-height: 1.6; color: var(--text-muted);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3;
  overflow: hidden;
}
.tool-meta {
  display: flex; flex-wrap: wrap; gap: 6px 8px;
  font-size: .76rem; color: var(--text-faint);
}
.tool-card-cta { font-size: .84rem; font-weight: 600; color: var(--brand); }
/* Hiệu ứng rê chuột chỉ bật trên thiết bị THẬT SỰ có con trỏ. Trên màn
   cảm ứng, trình duyệt giữ nguyên trạng thái hover sau khi chạm, nên thẻ
   vừa bấm cứ nhô lên mãi cho tới khi chạm chỗ khác. */
@media (hover: hover) {
  .tool-card:hover {
    text-decoration: none; border-color: var(--brand);
    transform: translateY(-2px);
  }
}
@media (hover: none) {
  .tool-card:active { border-color: var(--brand); }
}
.tool-card-top { display: flex; align-items: flex-start; gap: 13px; }
.tool-icon {
  width: 42px; height: 42px; flex: none; border-radius: 10px;
  display: grid; place-items: center; font-size: 20px; font-weight: 700;
  background: var(--brand-soft); color: var(--brand);
}
.tool-icon-lg { width: 56px; height: 56px; font-size: 27px; border-radius: 13px; }
.tool-name { margin: 0; font-size: 1.02rem; }
.tool-tagline { margin: 3px 0 0; font-size: .86rem; color: var(--text-muted); }
.tool-platforms { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  display: inline-block; padding: 2px 9px; border-radius: 20px;
  background: var(--surface-2); border: 1px solid var(--border);
  font-size: .74rem; color: var(--text-muted); white-space: nowrap;
}
/* Nhãn nổi bật (MCP, dùng thử) là BIẾN THỂ MÀU của chip, không phải một
   cỡ riêng: chúng đứng ngay cạnh chip nền tảng trong cùng một hàng, nên
   to hơn một chút là cả hàng gợn, mà to hẳn thì mắt đọc ra "cái nút" rồi
   bấm vào một thứ không bấm được. Màu đã đủ để chúng nổi lên.
   Phải khai báo SAU .chip: cùng độ ưu tiên thì luật đứng sau thắng. */
.chip-ai, .chip-trial, .chip-beta {
  display: inline-flex; align-items: center; gap: 4px; font-weight: 600;
}
.chip-ai {
  background: color-mix(in srgb, var(--brand) 11%, transparent);
  color: var(--brand-strong);
  border-color: color-mix(in srgb, var(--brand) 26%, transparent);
}
.chip-trial {
  background: color-mix(in srgb, var(--warn) 12%, transparent);
  color: var(--warn);
  border-color: color-mix(in srgb, var(--warn) 28%, transparent);
}
/* Tím cho bản thử nghiệm: xanh thương hiệu và cam dùng thử đã có chủ,
   mà ba nhãn cùng tông thì mắt không tách ra được cái nào là cái nào. */
.chip-beta {
  background: color-mix(in srgb, #7c3aed 12%, transparent);
  color: #6d28d9;
  border-color: color-mix(in srgb, #7c3aed 28%, transparent);
}
/* Nền tối: tím đậm trên nền đen chỉ còn 2:1, đọc không ra. Hai biến thể
   kia lấy màu từ biến theme nên tự đổi; nhãn này viết màu thẳng nên
   phải tự khai bản tối cho mình. */
:root[data-theme="dark"] .chip-beta { color: #c4b5fd; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .chip-beta { color: #c4b5fd; }
}
.chip-ai .ic, .chip-trial .ic, .chip-beta .ic {
  width: .9em; height: .9em; vertical-align: 0;
}
.tool-tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 0; }
.tool-card-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: auto; padding-top: 14px; border-top: 1px solid var(--border);
}
.tool-price { font-weight: 640; font-size: .92rem; }

/* ---------- Bài viết ---------- */
.post-item-title { margin: 0; font-size: 1.15rem; }
.post-item-title a { color: var(--text); }
.post-item-title a:hover { color: var(--brand); }
.post-meta {
  display: flex; flex-wrap: wrap; gap: 7px; align-items: center;
  margin-top: 7px; font-size: .8rem; color: var(--text-faint);
}

/* ---------- Blog: đầu trang ---------- */
/* Tiêu đề đóng khung, đặt giữa. Cái khung làm một chữ ngắn như "Blog"
   đứng được một mình giữa khoảng trắng rộng — không có nó thì chữ trôi
   lửng giữa trang. */
.blog-head { text-align: center; margin: 8px 0 26px; }
.blog-head h1 {
  display: inline-block; margin: 0;
  padding: 10px 30px; font-size: 1.9rem; letter-spacing: -.01em;
  border: 2px solid var(--text); border-radius: 4px;
  box-shadow: 4px 4px 0 var(--surface-2);
}
.blog-head p { margin: 14px auto 0; max-width: 560px; color: var(--text-muted); }
.blog-search { max-width: 520px; margin: 0 auto 18px; }

/* Hàng thẻ chủ đề. Mặc định chỉ hiện một phần; phần còn lại mở ra bằng
   thẻ <details> nên vẫn bấm được khi tắt JavaScript. */
.tag-row {
  display: flex; flex-wrap: wrap; gap: 7px; justify-content: center;
  margin-bottom: 8px;
}
.tag-chip {
  display: inline-block; padding: 4px 12px; border-radius: 20px;
  background: var(--surface-2); border: 1px solid var(--border);
  font-size: .78rem; color: var(--text-muted); white-space: nowrap;
  transition: border-color .14s ease, color .14s ease, background-color .14s ease;
}
@media (hover: hover) {
  .tag-chip:hover {
    color: var(--brand-strong); border-color: var(--brand); text-decoration: none;
  }
}
.tag-chip.active {
  background: color-mix(in srgb, var(--brand) 13%, transparent);
  border-color: color-mix(in srgb, var(--brand) 32%, transparent);
  color: var(--brand-strong); font-weight: 600;
}
.tag-more { text-align: center; margin-bottom: 20px; }
.tag-more summary {
  display: inline-block; cursor: pointer; font-size: .78rem;
  color: var(--text-faint); list-style: none;
}
.tag-more summary::-webkit-details-marker { display: none; }
.tag-more summary:hover { color: var(--brand); }
.tag-more[open] summary { margin-bottom: 8px; }

/* ---------- Blog: lưới thẻ bài ---------- */
.post-grid { display: grid; gap: 20px; grid-template-columns: 1fr; }
@media (min-width: 560px)  { .post-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 900px)  { .post-grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1200px) { .post-grid { grid-template-columns: repeat(4, 1fr); } }

.post-card {
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 16px; overflow: hidden; box-shadow: var(--shadow);
  transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease;
}
.post-card:hover { text-decoration: none; }
@media (hover: hover) {
  .post-card:hover { transform: translateY(-4px); border-color: var(--brand); }
}
@media (prefers-reduced-motion: reduce) {
  .post-card { transition: border-color .18s ease; }
  .post-card:hover { transform: none; }
}
/* Khung ảnh bìa dọc 3:4 cho mọi thẻ cao bằng nhau, nhưng ảnh VỪA KHUNG
   chứ không cắt: ảnh bài viết hay có chữ, mà cắt ảnh có chữ thì mất
   chữ. Nền xám lấp phần thừa khi ảnh không đúng tỷ lệ. */
.post-cover {
  aspect-ratio: 3 / 4; background: var(--surface-2);
  border-bottom: 1px solid var(--border); overflow: hidden;
}
.post-cover img { width: 100%; height: 100%; object-fit: contain; object-position: top center; display: block; }
.post-cover-empty {
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 100%; color: var(--text-faint);
}
.post-cover-empty .ic { width: 34px; height: 34px; }
.post-card-body { padding: 14px 16px 18px; display: flex; flex-direction: column; gap: 7px; flex: 1; }
.post-card-date { font-size: .74rem; color: var(--text-faint); }
.post-card-title {
  margin: 0; font-size: .96rem; line-height: 1.5; color: var(--text); font-weight: 620;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden;
}
.post-card-tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: auto; padding-top: 4px; }
.post-card-tags span {
  font-size: .68rem; color: var(--text-faint);
  background: var(--surface-2); border-radius: 10px; padding: 2px 8px;
}

/* ---------- Blog: trang bài viết ---------- */
/* Hai cột bằng nhau, ảnh bên trái dính theo khi cuộn. Chỉ bật từ 1000px:
   hẹp hơn thì hai cột đều quá chật, và trên điện thoại ảnh dính lại che
   mất nửa màn hình. */
.post-layout { display: grid; gap: 28px; align-items: start; }
@media (min-width: 1000px) {
  .post-layout { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 44px; }
  /* Dừng NGAY DƯỚI thanh điều hướng, không phải ở mép trên màn hình:
     thanh trên cũng dính, nên top nhỏ hơn chiều cao của nó là ảnh chui
     xuống dưới thanh và mất phần đầu khi cuộn. */
  .post-aside { position: sticky; top: calc(var(--nav-h) + 20px); }
  /* Ảnh cao hơn khoảng màn hình còn lại thì phần chân bị cắt, và người
     đọc không có cách nào cuộn tới nó vì ảnh đang dính tại chỗ. Ép ảnh
     vừa trong khoảng còn lại; contain giữ nguyên tỷ lệ, không bóp méo. */
  .post-aside .post-hero img {
    max-height: calc(100vh - var(--nav-h) - 40px);
    object-fit: contain;
  }
}
.post-hero {
  border: 1px solid var(--border); border-radius: 16px; overflow: hidden;
  background: var(--surface-2); box-shadow: var(--shadow);
}
.post-hero img { width: 100%; height: auto; display: block; }
.post-title-box {
  border: 2px solid var(--text); border-radius: 4px;
  padding: 16px 20px; box-shadow: 4px 4px 0 var(--surface-2);
}
.post-title-box h1 { margin: 0; font-size: 1.6rem; line-height: 1.35; letter-spacing: -.01em; }
@media (min-width: 700px) { .post-title-box h1 { font-size: 1.95rem; } }
.post-article { max-width: 760px; margin: 0 auto; }
.post-article h1 { font-size: 2rem; margin-bottom: 0; }
.prose { font-size: 1.02rem; line-height: 1.75; }
.prose h2 { margin: 32px 0 10px; font-size: 1.3rem; }
.prose h3 { margin: 26px 0 8px; font-size: 1.08rem; }
.prose h4 { margin: 22px 0 6px; font-size: 1rem; }
.prose ul, .prose ol { padding-left: 22px; margin: 0 0 16px; }
.prose li { margin-bottom: 7px; }
.prose blockquote {
  margin: 18px 0; padding: 2px 0 2px 16px;
  border-left: 3px solid var(--brand); color: var(--text-muted);
}
.prose pre { margin: 18px 0; }
.prose code { background: var(--surface-2); padding: 1px 5px; border-radius: 4px; }
.prose pre code { background: none; padding: 0; }
.prose img { max-width: 100%; height: auto; border-radius: var(--radius); }

/* Nhấn nội dung blog bằng màu thương hiệu; biến màu tự đổi theo theme. */
.post-prose h2, .post-prose h3 {
  color: var(--brand-strong); width: fit-content; max-width: 100%;
  padding: .15em .4em; border-radius: 5px;
  background: linear-gradient(transparent 42%, var(--brand-soft) 42%);
  line-height: 1.5;
}
.post-prose strong {
  color: var(--brand-strong); padding: 0 .12em; border-radius: 3px;
  background: linear-gradient(transparent 35%, var(--brand-soft) 35%);
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
}
.post-prose code {
  color: var(--brand-strong); background: var(--brand-soft);
  border: 1px solid var(--border-strong); border-radius: 5px;
  padding: .1em .4em; font-size: .9em; overflow-wrap: anywhere;
}
.post-prose pre.keybox {
  color: var(--text); background: var(--brand-soft);
  border-left: 3px solid var(--brand); line-height: 1.65;
  white-space: pre-wrap !important; overflow-wrap: anywhere;
}
.post-prose blockquote {
  color: var(--text); background: var(--brand-soft);
  padding: 12px 16px; border-radius: 0 8px 8px 0;
}

/* ---------- Liên hệ ---------- */
.contact-card { display: flex; flex-direction: column; }
/* Huy hiệu biểu tượng đầu mỗi thẻ liên hệ. Mỗi kênh một màu riêng:
   người đang cần gọi gấp tìm cái ống nghe chứ không đọc tiêu đề, mà ba
   huy hiệu cùng màu thì lại phải đọc. */
.contact-ic {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border-radius: 13px; margin-bottom: 12px;
  background: var(--surface-2); color: var(--text-muted);
}
.contact-ic .ic { width: 22px; height: 22px; }
.contact-ic-phone { background: color-mix(in srgb, var(--ok) 13%, transparent); color: var(--ok); }
.contact-ic-zalo {
  background: color-mix(in srgb, var(--brand) 13%, transparent); color: var(--brand-strong);
}
.contact-ic-mail {
  background: color-mix(in srgb, var(--warn) 13%, transparent); color: var(--warn);
}
/* Tiêu đề có biểu tượng đứng trước. Dùng chung cho mọi trang, không
   riêng trang liên hệ. */
.head-ic { display: flex; align-items: center; gap: 9px; }
.head-ic .ic { width: 19px; height: 19px; color: var(--brand); flex: none; }
.btn .ic { width: 17px; height: 17px; }
.contact-value {
  font-size: 1.35rem; font-weight: 660; letter-spacing: -.02em;
  margin: 6px 0 0; font-variant-numeric: tabular-nums;
}
.plain-list { list-style: none; padding: 0; margin: 10px 0; display: flex; flex-direction: column; gap: 8px; font-size: .9rem; }
.plain-list li { padding-left: 16px; position: relative; }
.plain-list li::before { content: "·"; position: absolute; left: 4px; color: var(--brand); font-weight: 700; }
.faq summary {
  cursor: pointer; font-weight: 600; font-size: .95rem;
  list-style: none; display: flex; align-items: center; gap: 8px;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::before { content: "+"; color: var(--brand); font-weight: 700; width: 12px; }
.faq[open] summary::before { content: "−"; }

/* ---------- Nút chuyển ngôn ngữ ---------- */
/* Hai nút này nằm trong khối .nav-tools đã có viền chung, nên bản thân
   chúng bỏ viền: viền lồng trong viền chỉ làm thanh nav rối thêm. */
.lang-switch {
  display: inline-flex; align-items: center; gap: 3px;
  padding: 6px 9px; border-radius: 8px; border: 1px solid transparent;
  color: var(--text-muted); font-size: .76rem; font-weight: 650;
  letter-spacing: .02em; white-space: nowrap;
  transition: background-color .14s ease, color .14s ease;
}
@media (hover: hover) {
  .lang-switch:hover { background: var(--surface); color: var(--text); text-decoration: none; }
}
.lang-sep { opacity: .5; font-weight: 400; }

/* ---------- Nút đổi sáng / tối ---------- */
/* Cùng kích thước với nút ngôn ngữ để hai nút cạnh nhau không lệch. */
.theme-switch {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 30px; border-radius: 8px;
  border: 1px solid transparent; color: var(--text-muted);
  transition: background-color .14s ease, color .14s ease;
}
@media (hover: hover) {
  .theme-switch:hover { background: var(--surface); color: var(--text); text-decoration: none; }
}
.theme-switch svg { width: 16px; height: 16px; display: block; }

/* Nhãn "Đề xuất" nổi trên mép thẻ gói. Đặt lệch lên trên viền chứ không
   nằm trong thân thẻ: nằm trong thì nó đẩy tên gói xuống và ba thẻ cạnh
   nhau không còn thẳng hàng. */
.plan-badge {
  position: absolute; top: -11px; left: 20px;
  padding: 3px 10px; border-radius: 20px;
  background: var(--brand); color: #fff;
  font-size: .72rem; font-weight: 700; letter-spacing: .02em;
}

/* ---------- Biểu mẫu sửa gói cước trong trang quản trị ---------- */
/* Nhãn trong khối .field thường là thẻ label; ở đây cả khối là label nên
   phần chữ nằm trong span và cần cùng kiểu chữ đó. */
.field > span {
  display: block; font-size: .85rem; font-weight: 570; margin-bottom: 5px; color: var(--text);
}
.plan-edit > summary {
  list-style: none; cursor: pointer;
  padding: 9px 16px; font-size: .84rem; font-weight: 600; color: var(--brand);
  border-top: 1px solid var(--border); background: var(--surface);
}
.plan-edit > summary::-webkit-details-marker { display: none; }
.plan-edit > summary::before { content: "✎ "; }
.plan-edit[open] > summary { background: var(--surface-2); }
.plan-edit > summary:hover { background: var(--surface-2); }

/* ---------- Trang chi tiết phần mềm ---------- */
/* Bố cục theo lối cửa hàng ứng dụng: biểu tượng lớn và tên phần mềm
   chiếm đầu trang, rồi một hàng số liệu, rồi dải ảnh cuộn ngang, rồi mới
   đến chữ. Lý do không phải thẩm mỹ: người vào đây chưa biết phần mềm
   làm gì, và họ quyết định bằng ảnh chứ không bằng đoạn mô tả. Đổ cả
   đoạn mô tả dài lên trước là bắt người ta đọc xong mới được nhìn. */
/* Ảnh bìa: dải ngang ở ngay đầu trang sản phẩm, tỉ lệ như ảnh bìa của
   các cửa hàng ứng dụng (1024x500). Dùng object-fit: cover chứ không
   contain — ảnh bìa là ảnh dựng riêng, cắt bớt hai mép không mất gì,
   còn chừa hai dải trống hai bên thì dải ảnh gãy hẳn nhịp đầu trang. */
.app-cover {
  /* Khung ảnh bìa có tỉ lệ CỐ ĐỊNH và ảnh phủ kín khung (cover).
     Hai cách kia đều hỏng theo một kiểu riêng: để khung chạy theo tỉ lệ
     của ảnh thì mỗi phần mềm một chiều cao, trang danh mục nhìn như răng
     cưa; còn giữ nguyên tỉ lệ ảnh trong một khung cố định thì ảnh nào
     không đúng tỉ lệ sẽ chừa hai dải trống hai bên.
     Đổi lại, ảnh bìa phải dựng đúng khổ — xem hướng dẫn trong trang quản
     trị và trong tài liệu cho nhà phát triển: 1920x640, chữ nằm gọn
     trong vùng giữa rộng 1140px để các khổ màn hẹp cắt bớt hai mép mà
     không mất chữ. */
  display: block; width: 100%; aspect-ratio: 3 / 1;
  object-fit: cover; object-position: center;
  border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--surface-2);
  margin-bottom: 16px;
}
/* Màn hẹp: khung cao dần lên để ảnh không mỏng như một sợi chỉ. Cắt bớt
   hai mép, nên phần chữ của ảnh phải nằm ở giữa. */
@media (max-width: 900px) {
  .app-cover { aspect-ratio: 2 / 1; }
}
@media (max-width: 560px) {
  .app-cover { aspect-ratio: 16 / 9; }
}

/* Bản dùng trong trang quản trị. Khác bản công khai ở hai điểm, đều có
   lý do: thấp hơn, vì chỗ trên màn hình ở đây thuộc về những thứ cần
   thao tác; và cắt cho đầy khung thay vì giữ nguyên tỉ lệ, vì giữ tỉ lệ
   trong một khung thấp sẽ chừa hai mảng xám to hai bên. Trang công khai
   thì ngược lại — ở đó ảnh bìa là nội dung, không được xén. */
.app-cover-sm { aspect-ratio: auto; height: 190px; }

.app-head { display: flex; gap: 20px; align-items: flex-start; }
.app-icon {
  width: 88px; height: 88px; flex: none; border-radius: 20px;
  background: var(--brand-soft); color: var(--brand-strong);
  display: flex; align-items: center; justify-content: center;
  font-size: 2.1rem; font-weight: 700; overflow: hidden;
  border: 1px solid var(--border);
}
/* Ảnh nằm ĐÈ lên chữ viết tắt, không thay thế nó: khi địa chỉ ảnh hỏng,
   thẻ img tự gỡ mình đi và chữ viết tắt bên dưới lộ ra, thay vì để lại
   một ô vỡ có dấu hỏi. */
.app-icon { position: relative; }
.app-icon img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; background: var(--surface); display: block;
}
.tool-icon.product-logo { position: relative; overflow: hidden; }
.tool-icon.product-logo img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; background: var(--surface); display: block;
}
.app-title { margin: 0; font-size: 1.75rem; letter-spacing: -.02em; line-height: 1.2; }
.app-publisher {
  margin: 4px 0 0; font-weight: 600; font-size: .95rem; color: var(--brand);
}
.app-tagline { margin: 8px 0 0; color: var(--text-muted); }
.app-cta { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 18px; }
.app-cta .btn { min-width: 150px; justify-content: center; }

/* Hàng số liệu: giá trị trên, nhãn dưới, ngăn nhau bằng vạch dọc mảnh.
   Vạch vẽ bằng viền trái của từng ô và bỏ ở ô đầu, nên khi xuống dòng ở
   khổ hẹp không còn vạch nào bị treo lơ lửng giữa trời. */
.app-meta {
  display: flex; flex-wrap: wrap; margin-top: 22px;
  border-top: 1px solid var(--border); padding-top: 16px;
}
.app-meta-item {
  flex: 1 1 110px; padding: 2px 16px; text-align: center;
  border-left: 1px solid var(--border);
}
.app-meta-item:first-child { border-left: 0; }
.app-meta-value {
  font-weight: 650; font-size: 1rem; line-height: 1.3;
  display: flex; align-items: center; justify-content: center; gap: 6px;
}
.app-meta-label { font-size: .76rem; color: var(--text-muted); margin-top: 3px; }

/* Dải ảnh cuộn ngang. Lưới cũ xếp ảnh ngang và ảnh dọc chung một hàng,
   nên mỗi ô cao một kiểu và dưới mỗi ảnh là một hộp chú thích xám —
   nhìn ra một mặt trang lỗ chỗ. Ở đây ảnh có chiều cao bằng nhau, chú
   thích chuyển vào alt và vào khung xem lớn. */
.shot-strip {
  display: flex; gap: 12px; overflow-x: auto; padding: 2px 0 10px;
  scroll-snap-type: x mandatory; scrollbar-width: thin;
  /* Ít ảnh thì căn giữa, nhiều ảnh thì chạy từ trái như thường. Từ
     khoá safe lo đúng việc đó: khi nội dung tràn, trình duyệt bỏ căn
     giữa thay vì cắt mất ảnh đầu tiên — căn giữa thuần sẽ đẩy ảnh đầu
     ra ngoài vùng cuộn và không có cách nào cuộn ngược lại tới. */
  justify-content: safe center;
}
/* Tiêu đề của một dải ảnh. Nhỏ và nhạt: nó là nhãn phân loại, không
   phải một mục mới của trang — chữ to bằng tiêu đề mục sẽ cắt trang
   thành nhiều mảnh rời. */
/* Phần giới thiệu tool do đội phát triển viết, có định dạng. Chặn bề
   ngang quanh 70 ký tự như mọi khối chữ dài khác, và kéo ảnh về vừa cột
   thay vì để một ảnh 2000px đẩy cả thẻ rộng ra. */
/* Khối chữ chiếm trọn bề ngang thẻ, nhưng TỪNG ĐOẠN VĂN vẫn bị chặn
   quanh 70 ký tự.
   Trước đây cả khối bị chặn 70ch, nên trên màn rộng gần một nửa thẻ bỏ
   trống và ảnh chụp màn hình 2400px hiện ra chỉ 566px — đúng thứ người
   viết bỏ công chuẩn bị lại là thứ bị thu nhỏ nhất. Chặn ở từng đoạn
   giữ được nhịp đọc, còn ảnh, bảng và khối mã thì dùng hết chỗ. */
.app-rich { line-height: 1.75; }
.app-rich > :first-child { margin-top: 0; }
.app-rich > :last-child { margin-bottom: 0; }
.app-rich h2 { font-size: 1.3rem; margin: 30px 0 10px; max-width: 70ch; letter-spacing: -.01em; }
.app-rich h3 { font-size: 1.08rem; margin: 24px 0 8px; max-width: 70ch; }
.app-rich h4 { font-size: .95rem; margin: 18px 0 6px; max-width: 70ch; }
.app-rich p { margin: 0 0 14px; max-width: 70ch; }
.app-rich ul, .app-rich ol { margin: 0 0 14px; padding-left: 22px; max-width: 70ch; }
.app-rich li { margin-bottom: 7px; }
.app-rich img {
  max-width: 100%; height: auto; display: block;
  border-radius: 12px; border: 1px solid var(--border); margin: 18px 0;
}
/* Đoạn văn chỉ chứa mỗi tấm ảnh thì không phải là đoạn văn: bỏ trần
   chữ để ảnh dùng hết bề ngang. Trình duyệt không hiểu :has() sẽ giữ
   ảnh ở khổ cũ — hẹp hơn, nhưng không vỡ gì. */
.app-rich p:has(> img:only-child) { max-width: none; }
.app-rich blockquote {
  margin: 0 0 12px; padding: 8px 14px;
  border-left: 3px solid var(--brand); background: var(--surface-2);
  border-radius: 0 8px 8px 0; color: var(--text-muted);
}
/* Bảng và khối mã dùng hết bề ngang: một bảng hai cột bị ép vào 70ch
   thì chữ trong ô gãy thành từng chữ một. */
.app-rich table { width: 100%; margin: 18px 0; }
.app-rich th, .app-rich td { padding: 10px 12px; vertical-align: top; }
.app-rich th { text-align: left; }
.app-rich pre { max-width: none; margin: 18px 0; }
.app-rich figure { margin: 18px 0; }
.app-rich figcaption {
  font-size: .82rem; color: var(--text-muted); margin-top: 6px; text-align: center;
}

/* Đầu trang thanh toán: biểu tượng sản phẩm, tên, mô tả một dòng và
   các nhãn nền tảng. Cùng lối trình bày với đầu trang giới thiệu sản
   phẩm, để người vừa từ đó bấm sang nhận ra ngay mình đang mua đúng
   thứ vừa xem. */
.buy-head {
  display: flex; gap: 16px; align-items: flex-start;
  padding-bottom: 18px; margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
}
.buy-head-text { min-width: 0; }
.buy-head-text h2 { margin: 2px 0 4px; font-size: 1.35rem; letter-spacing: -.02em; }
.buy-head-text p { margin: 0 0 10px; color: var(--text-muted); font-size: .92rem; }
.buy-head-meta { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
@media (max-width: 560px) {
  .buy-head { gap: 12px; }
  .buy-head-text h2 { font-size: 1.15rem; }
}

.shot-group-head {
  font-size: .8rem; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-muted);
  margin: 18px 0 8px;
}
.shot-group-head:first-child { margin-top: 0; }
.shot-strip::-webkit-scrollbar { height: 8px; }
.shot-strip::-webkit-scrollbar-thumb {
  background: var(--border-strong); border-radius: 99px;
}
.shot-strip a {
  flex: none; scroll-snap-align: start; display: block;
  height: 200px; border-radius: 12px; overflow: hidden;
  border: 1px solid var(--border); background: var(--surface-2);
}
.shot-strip a:hover { border-color: var(--brand); }
.shot-strip img { height: 100%; width: auto; display: block; object-fit: cover; }
@media (min-width: 760px) {
  .shot-strip a { height: 260px; }
}
/* Màn rộng thì ảnh to hẳn lên. Giữ 260px trên màn 1400px làm một dải
   ảnh con con nằm nép bên trái với nửa thẻ bỏ trống — mà ảnh chụp màn
   hình chính là thứ người xem đọc kỹ nhất trước khi quyết định tải. */
@media (min-width: 1100px) {
  .shot-strip a { height: 340px; }
}
@media (min-width: 1500px) {
  .shot-strip a { height: 400px; }
}

/* Chữ do người dùng nhập: giữ nguyên xuống dòng họ đã gõ, và giới hạn
   bề ngang quanh 70 ký tự. Dòng dài hết chiều rộng màn 1280px đọc rất
   mỏi vì mắt phải dò lại đầu dòng sau mỗi lần xuống hàng. */
.app-prose { white-space: pre-wrap; margin: 0; max-width: 68ch; line-height: 1.75; }

/* Phần còn lại của mô tả dài. Phần đầu luôn hiện thành chữ thật nên
   không cần hiệu ứng mờ dần, và trang vẫn đọc được khi tắt JavaScript. */
.app-about { margin-top: 12px; }
.app-about summary {
  list-style: none; cursor: pointer; margin-top: 10px;
  font-weight: 600; font-size: .88rem; color: var(--brand);
}
.app-about summary::-webkit-details-marker { display: none; }
.app-about summary::after { content: " ↓"; }
.app-about[open] summary::after { content: " ↑"; }
.app-about[open] summary { margin-bottom: 12px; }

/* Hàng thông tin ở cuối trang: nhãn trái, giá trị phải, mỗi dòng một
   gạch ngăn — giống phần thông tin ứng dụng ở cuối trang cửa hàng. */
.app-facts { display: flex; flex-direction: column; }
.app-fact {
  display: flex; justify-content: space-between; gap: 18px;
  padding: 11px 0; border-top: 1px solid var(--border); font-size: .9rem;
}
.app-fact:first-child { border-top: 0; }
.app-fact dt { color: var(--text-muted); margin: 0; }
.app-fact dd { margin: 0; font-weight: 550; text-align: right; }

@media (max-width: 560px) {
  .app-head { gap: 14px; }
  .app-icon { width: 68px; height: 68px; border-radius: 16px; font-size: 1.6rem; }
  .app-title { font-size: 1.4rem; }
  .app-cta .btn { flex: 1 1 100%; }
}

/* ---------- Thư viện ảnh phần mềm ---------- */
/* Ảnh chụp phần mềm có tỉ lệ rất khác nhau (toàn màn hình 16:9, ảnh dọc
   của bản mobile). Nên khung giữ tỉ lệ cố định và ảnh dùng object-fit:
   contain — cắt ảnh chụp giao diện là cắt mất đúng phần người ta muốn
   xem, còn để mỗi ô một chiều cao thì hàng ảnh gãy như răng cưa. */
.shots { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.shot {
  display: block; background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden; color: inherit;
}
.shot:hover { border-color: var(--brand); text-decoration: none; }
.shot-frame { aspect-ratio: 16 / 10; display: block; }
.shot-frame-tall { aspect-ratio: 3 / 5; }
.shot img { width: 100%; height: 100%; object-fit: contain; display: block; }
.shot figcaption { padding: 9px 12px; font-size: .82rem; color: var(--text-muted); }
/* Ảnh mở to: dùng thẻ dialog của trình duyệt, không tự dựng lớp phủ.
   Nhờ vậy phím Esc, bẫy tiêu điểm và việc chặn cuộn trang do trình duyệt
   lo, và trang vẫn xem được ảnh khi JavaScript bị tắt (ảnh là một liên
   kết tới chính tệp ảnh). */
dialog.shot-zoom {
  border: 0; padding: 0; background: transparent; max-width: 96vw; max-height: 94vh;
}
dialog.shot-zoom::backdrop { background: rgba(8, 14, 24, .82); }
dialog.shot-zoom img { max-width: 96vw; max-height: 86vh; border-radius: var(--radius); }
dialog.shot-zoom .shot-zoom-bar {
  display: flex; justify-content: space-between; align-items: center; gap: 14px;
  color: #e8eef6; font-size: .85rem; padding: 8px 2px 0;
}
dialog.shot-zoom form { margin: 0; }

/* ---------- Tab phân loại danh mục ---------- */
.seg {
  display: inline-flex; flex-wrap: wrap; gap: 3px; padding: 3px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 9px;
}
.seg a {
  padding: 6px 13px; border-radius: 7px; font-size: .85rem; font-weight: 580;
  color: var(--text-muted); white-space: nowrap;
}
.seg a:hover { color: var(--text); text-decoration: none; }
.seg a.active {
  background: var(--surface); color: var(--text); box-shadow: var(--shadow);
}

/* ---------- Thanh tìm kiếm và lọc của danh mục ---------- */
/* Là BIỂU MẪU GET thường, không phải bộ lọc chạy bằng JavaScript: kết
   quả lọc phải nằm trong địa chỉ trang để gửi và lưu được, và phải chạy
   cả khi tập lệnh chưa tải xong. Đổi lại người dùng phải bấm "Lọc" —
   đáng, vì cái mất khi không có địa chỉ riêng lớn hơn một cú bấm. */
.filter-bar {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  margin-top: 14px; padding: 14px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
}
.filter-search { position: relative; display: flex; align-items: center; flex: 1 1 250px; }
/* Kính lúp nằm TRONG ô, nên phải cho con trỏ đi xuyên qua: bấm trúng
   biểu tượng mà ô không nhận con trỏ là lỗi người dùng không hiểu nổi. */
.filter-search .ic {
  position: absolute; left: 12px; width: 1.05em; height: 1.05em;
  color: var(--text-faint); pointer-events: none;
}
.filter-search input { padding-left: 36px; }
/* Nút xoá sẵn có của type=search trên WebKit chen vào sát mép phải. */
.filter-search input::-webkit-search-cancel-button { margin-right: 2px; }
.filter-field { flex: 1 1 155px; min-width: 140px; }
.filter-toggles {
  display: flex; flex-wrap: wrap; gap: 8px 20px; width: 100%;
  padding-top: 12px; border-top: 1px solid var(--border);
}
.filter-toggles .checkbox { font-size: .86rem; color: var(--text-muted); }
.filter-result {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 8px; margin-top: 16px; font-size: .88rem; color: var(--text-muted);
}
@media (max-width: 620px) {
  /* Trên điện thoại ô tìm kiếm chiếm trọn một dòng, ba ô chọn chia nhau
     dòng dưới — ba ô chọn nằm cùng dòng với ô tìm kiếm thì ô nào cũng
     hẹp đến mức không đọc được nhãn bên trong. */
  .filter-search { flex: 1 1 100%; }
  .filter-bar .btn { flex: 1 1 100%; justify-content: center; }
}

/* Thứ chỉ cần tới khi KHÔNG có JavaScript — nút "Lọc" của biểu mẫu GET.
   Script lọc tại chỗ gắn class has-live lên thẻ html, và khi đó nút này
   là một cái nút không bao giờ phải bấm, nên ẩn đi. Để lại thì người
   dùng bấm vào một thứ tưởng là cần, hoá ra không làm gì thêm. */
.has-live .live-optional { display: none; }

/* Trong lúc chờ kết quả mới: mờ đi một chút để thấy là đang có gì đó
   xảy ra, nhưng không chặn chuột — danh sách cũ vẫn bấm được. */
[data-live][aria-busy='true'] { opacity: .55; transition: opacity .12s ease; }

/* Nhãn chỉ dành cho trình đọc màn hình: ô nhập nào cũng phải có nhãn,
   nhưng ở thanh lọc thì chữ gợi ý trong ô đã nói đủ cho người nhìn
   thấy. Không dùng display:none — trình đọc màn hình bỏ qua luôn. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ---------- Hướng dẫn sử dụng ---------- */
/* Mục lục dính theo khi cuộn, nhưng chỉ ở màn rộng: trên điện thoại nó
   sẽ chiếm nửa chiều cao màn hình. */
.guide-layout { display: grid; gap: 26px; align-items: start; }
@media (min-width: 900px) {
  .guide-layout { grid-template-columns: 230px 1fr; }
  /* Cùng lý do với ảnh bìa bài viết: trừ đi chiều cao thanh điều hướng
     dính ở trên, nếu không mục đầu tiên của mục lục nằm khuất sau nó. */
  .guide-toc { position: sticky; top: calc(var(--nav-h) + 18px); max-height: calc(100vh - var(--nav-h) - 36px); overflow-y: auto; }
}
.guide-toc ol { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 7px; }
.guide-toc a { color: var(--text-muted); font-size: .86rem; }
.guide-toc a:hover { color: var(--text); }

/* ---------- Chân trang ---------- */
.footer {
  border-top: 1px solid var(--border); padding: 36px 0 24px; margin-top: 48px;
  color: var(--text-muted); font-size: .85rem;
}
.footer a { color: var(--text-muted); }
.footer a:hover { color: var(--text); }
.footer-grid {
  display: grid; gap: 28px;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.footer-col { display: flex; flex-direction: column; gap: 7px; align-items: flex-start; }
/* Liên kết mạng xã hội ở chân trang: biểu tượng đứng trước chữ, cùng
   một hàng. Biểu tượng trần không có chữ thì người đọc phải đoán, mà
   đoán sai thì họ không bấm. */
.footer-social { display: inline-flex; align-items: center; gap: 7px; }
.footer-social .ic { width: 17px; height: 17px; }
.footer-col h4 {
  margin: 0 0 4px; font-size: .78rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--text);
}
.footer-brand .brand-logo { height: 30px; margin-bottom: 4px; }
.footer-bottom {
  margin-top: 28px; padding-top: 18px; border-top: 1px solid var(--border);
  font-size: .8rem;
}
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]):not([data-theme="dark"]) { 
  --bg: #0b1220;
  --surface: #111a2b;
  --surface-2: #17213480;
  --border: #1e293b;
  --border-strong: #334155;
  --text: #e8eef6;
  --text-muted: #94a3b8;
  --text-faint: #64748b;
  --brand: #22ccdf;
  --brand-strong: #7de6f2;
  --brand-raw: #01aac2;
  --brand-ink: #0b1220;
  --brand-soft: #06323a;
  --ok: #34d399;
  --ok-soft: #022c22;
  --warn: #fbbf24;
  --warn-soft: #2a1f05;
  --danger: #f87171;
  --danger-soft: #2d1013;
  --shadow: 0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.25);
  --chart-bar: #22ccdf;
  --chart-grid: #1b2639;
  --chart-axis: #64748b;
  --chart-axis-line: #334155;
  --chart-ok: #10b981;
  --chart-warn: #eab308;
  --chart-danger: #ef4444;
 } 
:root:not([data-theme="light"]):not([data-theme="dark"]) .brand-logo-light { display: none; }
:root:not([data-theme="light"]):not([data-theme="dark"]) .brand-logo-dark { display: block; }
/* Ảnh banner trang chủ có hai bản vẽ riêng cho nền sáng và nền tối —
   không phải cùng một ảnh lọc màu. Ảnh bản sáng đặt trên nền tối sẽ là
   một mảng trắng chói giữa trang. */
:root:not([data-theme="light"]):not([data-theme="dark"]) .hero-art img.hero-art-light { display: none; }
:root:not([data-theme="light"]):not([data-theme="dark"]) .hero-art img.hero-art-dark { display: block; }
/* Nền tối: nút đặc có nền cyan sáng nên chữ phải tối mới đọc được. Nút
   viền thì nền trong suốt, dùng lại chữ tối ở đó sẽ gần như tàng hình,
   nên trả về màu chữ riêng của nó. */
:root:not([data-theme="light"]):not([data-theme="dark"]) .btn { color: #04222b; }
:root:not([data-theme="light"]):not([data-theme="dark"]) .btn-ghost { color: var(--text-muted); }
:root:not([data-theme="light"]):not([data-theme="dark"]) .btn-ghost:hover { color: var(--text); }
/* Navy của logo trùng gần hết với nền tối, nên nút quản trị ở chế độ tối
   dùng xám xanh sáng hơn kèm viền để vẫn tách khỏi nền. */
:root:not([data-theme="light"]):not([data-theme="dark"]) .btn-admin { background: #334155; color: #e8eef6; border-color: #475569; }
/* Ảnh chụp phần mềm thường có nền trắng. Trên nền tối, một ô trắng toát
   chói hơn mọi thứ quanh nó, nên giảm nhẹ độ sáng và thêm viền. */
:root:not([data-theme="light"]):not([data-theme="dark"]) .shot img { filter: brightness(.94); }
 }
@media (prefers-color-scheme: dark) and (max-width: 400px) { 
/* Quy tắc hiện logo bản tối có bộ chọn cụ thể hơn .brand-logo, mà độ cụ
   thể thắng trước thứ tự xuất hiện — thiếu mấy dòng này thì khổ hẹp ở
   chế độ tối hiện CẢ logo ngang lẫn biểu tượng vuông. */
:root:not([data-theme="light"]):not([data-theme="dark"]) .brand-logo-dark { display: none; }
:root:not([data-theme="light"]):not([data-theme="dark"]) .brand-mark-light { display: none; }
:root:not([data-theme="light"]):not([data-theme="dark"]) .brand-mark-dark { display: block; }
 }
:root[data-theme="dark"] { 
  --bg: #0b1220;
  --surface: #111a2b;
  --surface-2: #17213480;
  --border: #1e293b;
  --border-strong: #334155;
  --text: #e8eef6;
  --text-muted: #94a3b8;
  --text-faint: #64748b;
  --brand: #22ccdf;
  --brand-strong: #7de6f2;
  --brand-raw: #01aac2;
  --brand-ink: #0b1220;
  --brand-soft: #06323a;
  --ok: #34d399;
  --ok-soft: #022c22;
  --warn: #fbbf24;
  --warn-soft: #2a1f05;
  --danger: #f87171;
  --danger-soft: #2d1013;
  --shadow: 0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.25);
  --chart-bar: #22ccdf;
  --chart-grid: #1b2639;
  --chart-axis: #64748b;
  --chart-axis-line: #334155;
  --chart-ok: #10b981;
  --chart-warn: #eab308;
  --chart-danger: #ef4444;
 }

:root[data-theme="dark"] .brand-logo-light { display: none; }
:root[data-theme="dark"] .brand-logo-dark { display: block; }
/* Ảnh banner trang chủ có hai bản vẽ riêng cho nền sáng và nền tối —
   không phải cùng một ảnh lọc màu. Ảnh bản sáng đặt trên nền tối sẽ là
   một mảng trắng chói giữa trang. */
:root[data-theme="dark"] .hero-art img.hero-art-light { display: none; }
:root[data-theme="dark"] .hero-art img.hero-art-dark { display: block; }
/* Nền tối: nút đặc có nền cyan sáng nên chữ phải tối mới đọc được. Nút
   viền thì nền trong suốt, dùng lại chữ tối ở đó sẽ gần như tàng hình,
   nên trả về màu chữ riêng của nó. */
:root[data-theme="dark"] .btn { color: #04222b; }
:root[data-theme="dark"] .btn-ghost { color: var(--text-muted); }
:root[data-theme="dark"] .btn-ghost:hover { color: var(--text); }
/* Navy của logo trùng gần hết với nền tối, nên nút quản trị ở chế độ tối
   dùng xám xanh sáng hơn kèm viền để vẫn tách khỏi nền. */
:root[data-theme="dark"] .btn-admin { background: #334155; color: #e8eef6; border-color: #475569; }
/* Ảnh chụp phần mềm thường có nền trắng. Trên nền tối, một ô trắng toát
   chói hơn mọi thứ quanh nó, nên giảm nhẹ độ sáng và thêm viền. */
:root[data-theme="dark"] .shot img { filter: brightness(.94); }

@media (max-width: 400px) { 
/* Quy tắc hiện logo bản tối có bộ chọn cụ thể hơn .brand-logo, mà độ cụ
   thể thắng trước thứ tự xuất hiện — thiếu mấy dòng này thì khổ hẹp ở
   chế độ tối hiện CẢ logo ngang lẫn biểu tượng vuông. */
:root[data-theme="dark"] .brand-logo-dark { display: none; }
:root[data-theme="dark"] .brand-mark-light { display: none; }
:root[data-theme="dark"] .brand-mark-dark { display: block; }
 }