/* Bảng Xếp Hạng (SoW 7.2) — docs/acceptance/2026-09-03-bang-xep-hang.md
 *
 * Khuôn Figma: 1125 x 2436 (iPhone X @3x). Mọi toạ độ dưới đây là % của .lb-stage,
 * nên cả trang co giãn theo bề rộng cột mà không cần media query.
 *
 * .lb-stage GIỮ đúng tỉ lệ thiết kế ⇒ ở mọi máy thật, trang CAO HƠN viewport và phải
 * cuộn. Thiết kế xếp thẻ hàng dưới cách thanh nav đúng 2px, chỉ vừa khít màn iPhone X;
 * bê nguyên bố cục một-màn-hình sẽ cắt mất hàng dưới trên mọi máy khác.
 *
 * Hai chỗ CỐ Ý lệch thiết kế, đều vì Figma quy đổi thẳng sẽ trượt cổng chất lượng:
 *   1. Vùng chạm nút tab nới ra tối thiểu 56px (quy đổi thẳng ra 34px ở bề rộng 320px).
 *      Nút CHỌN KỲ là ngoại lệ: thiết kế chỉ chừa 16.6-30px khe trống nên không nới được
 *      tới 44px mà không nuốt cú chạm của hàng xóm. Nó chỉ nới LÊN 6px — đủ để hụt tay
 *      không còn nhảy sang tab khác, mà ba tab vẫn ≥44px — xem khối .lb-period__select.
 *   2. Cỡ chữ tên bé có SÀN 11px (khối 2 dòng của Figma chỉ cao 14px ở 320px, tức
 *      ~7px mỗi dòng — bé không đọc được).
 */

.lb-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 1125 / 2436;
  container-type: inline-size;
  background-image: url("../images/portal/leaderboard/lb-bg.jpg");
  background-size: cover;
  background-position: center top;
  overflow: hidden;
}

/* Ảnh nền đã cắt đúng vùng khung máy nên không cần lớp phủ; màu này chỉ để tránh
   nháy trắng trước khi ảnh nền tải xong. */
.lb-stage {
  background-color: #090743;
}

/* ---------- tiêu đề ---------- */

.lb-title {
  position: absolute;
  left: 9.24%;
  top: 11.99%;
  width: 81.33%;
}

/* ---------- viền panel ---------- */

.lb-panel {
  position: absolute;
  left: 3.47%;
  top: 22.54%;
  width: 92.80%;
  height: 66.26%;
  pointer-events: none;
}

/* ---------- ba nút tab ---------- */

/* Khối chung trải từ x=55 tới x=1065 của khuôn — bao trọn cả ba trạng thái, vì vị trí
   từng nút ĐỔI theo nút nào đang được chọn (nút đang chọn to hơn và đẩy hai nút kia). */
.lb-tabs {
  position: absolute;
  left: 4.89%;
  top: 22.21%;
  width: 89.78%;
  height: 6.98%;
}

.lb-tab {
  position: absolute;
  display: block;
}

.lb-tab img {
  width: 100%;
  height: 100%;
  display: block;
}

/* Nút tab quy đổi thẳng từ Figma chỉ cao 34px ở bề rộng 320px — dưới sàn chạm 44px của
   Apple HIG. Nới vùng chạm bằng ::after chứ không bằng padding: padding sẽ đẩy ảnh nền
   và làm lệch bố cục đã đo. Cùng cơ chế .home-tap-safe của thanh nav. */
/* Neo MÉP DƯỚI và nở LÊN, chứ không canh tâm. Canh tâm thì phần thừa thò xuống đúng chỗ nút
   chọn kỳ đứng, hai vùng chạm giành nhau và tab thua còn 37px (QC P1-03). Nở lên thì không
   vướng gì — phía trên chỉ có ảnh tiêu đề và .lb-panel (pointer-events:none).

   56px chứ không phải 44px, và CỐ Ý không đặt z-index để .lb-period (z-index 7) thắng ở dải
   chồng: thiết kế vẽ nút chọn kỳ ĐÈ LÊN hàng tab (mép trên nút ở 27.96%, đáy hàng tab ở
   29.19%), nên chỗ nào MẮT thấy nút thì cú chạm phải vào nút — cho tab thắng ở đó là bé bấm
   nút chọn kỳ mà lại nhảy sang tab khác. Phần tab bị nút ăn mất đo được 1.8px (tab thường)
   tới 11.9px (tab đang chọn ở 448px), nên 44 + 12 = 56 mới đủ bù ở CẢ ba bề rộng. */
.lb-tab::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  min-width: 44px;
  min-height: 56px;
  width: 100%;
  height: 100%;
}

/* Trạng thái "Tuần" đang chọn */
.lb-stage[data-tab="weekly"] .lb-tab--weekly  { left: 0;      top: 0;      width: 35.74%; height: 100%; }
.lb-stage[data-tab="weekly"] .lb-tab--monthly { left: 34.65%; top: 15.29%; width: 30.99%; height: 70.59%; }
.lb-stage[data-tab="weekly"] .lb-tab--onair   { left: 66.93%; top: 15.29%; width: 30.89%; height: 70.59%; }

/* Trạng thái "Tháng" đang chọn */
.lb-stage[data-tab="monthly"] .lb-tab--weekly  { left: 1.98%;  top: 15.29%; width: 30.99%; height: 70.59%; }
.lb-stage[data-tab="monthly"] .lb-tab--monthly { left: 31.68%; top: 0;      width: 35.74%; height: 100%; }
.lb-stage[data-tab="monthly"] .lb-tab--onair   { left: 66.93%; top: 15.29%; width: 30.89%; height: 70.59%; }

/* Trạng thái "Lên Sóng" đang chọn */
.lb-stage[data-tab="onair"] .lb-tab--weekly  { left: 1.98%;  top: 15.29%; width: 30.99%; height: 70.59%; }
.lb-stage[data-tab="onair"] .lb-tab--monthly { left: 34.36%; top: 15.29%; width: 30.89%; height: 70.59%; }
.lb-stage[data-tab="onair"] .lb-tab--onair   { left: 64.26%; top: 0;      width: 35.74%; height: 100%; }

/* Nút ĐANG CHỌN cao hơn hai nút kia nên mép dưới của nó thò xuống DƯỚI đỉnh vùng chạm của nút
   chọn kỳ — 6px nới lên của nút đó (xem .lb-period__select) cắt thẳng vào phần dùng được, còn
   41px @320 và 43px @390, tức thủng sàn 44px. Bù bằng 64px thay cho 56px:

       bề rộng   trước 6px   sau 6px   sau khi bù 64px
       320         47px       41px         49.4px
       390         49px       43px         47.6px
       448         50px       50px         50px      (100% đã là 67.7px, min-height không đụng tới)

   Phần bù mọc LÊN, và dừng đúng ngang mép trên vùng chạm của hai nút không-active (138.3 so
   với 139.0 @320) nên không sinh thêm một lớp nào phía trên — chỉ san bằng cho ba nút. */
.lb-stage[data-tab="weekly"]  .lb-tab--weekly::after,
.lb-stage[data-tab="monthly"] .lb-tab--monthly::after,
.lb-stage[data-tab="onair"]   .lb-tab--onair::after {
  min-height: 64px;
}

/* ---------- bộ chọn kỳ ---------- */

/* Nút này là 9-SLICE của chính asset Figma (node 335:472), không phải hình dạng CSS tự vẽ.
   Trước 2026-09-08 nó là một pill gradient xanh dương phẳng với hai <svg> icon vẽ tay, nên
   nhìn không giống thiết kế. Nay ba mảnh cắt từ ảnh nguồn của thiết kế:

     lb-period-left.png   63x61   bo góc trái + icon lịch (+ sparkle)
     lb-period-mid.png    32x61   dải nền trơn, KÉO GIÃN ngang
     lb-period-right.png  54x61   nút tròn mũi tên + bo góc phải

   Vì sao phải cắt chứ không dùng thẳng cả nút: bản Figma bake sẵn chữ "Tuần…" vào ảnh (và cắt
   chữ ngay trong file thiết kế — câu hỏi D-f), trong khi chữ thật ĐỘNG theo kỳ đang chọn. Ba lát
   cắt lấy đúng hai đầu có hình và bỏ hẳn dải chữ ở giữa; mốc cắt đo trên kênh alpha của ảnh
   nguồn: icon hết ở x=69, chữ chạy x=77..145, dải nền trơn x=148..180, nút tròn x=180..210.

   Hai mảnh đầu giữ TỈ LỆ theo chiều cao (`width:auto`), nên nút dài ra bao nhiêu thì icon vẫn
   đúng hình — không cần tính lại vị trí icon theo cqw như bản cũ. Mảnh giữa nằm trong phần tử
   RIÊNG (`.lb-period__mid`) chứ không đặt làm nền của cả nút: nền trải hết nút sẽ lộ ra hai góc
   VUÔNG dưới phần bo tròn của hai mảnh đầu.

   2026-09-16 — user báo nút "bị lệch (phần cắt, nối thêm)". Đo từng cột pixel ra HAI lỗi cắt,
   đều nằm trong FILE ẢNH chứ không phải trong CSS, nên sửa bằng
   tools/figma-assets/align-leaderboard-period-slices.py và KHÔNG đổi dòng CSS nào:
     1. Mảnh TRÁI lệch LÊN đúng 1px so với hai mảnh kia (mực ở hàng 0-59, trong khi mảnh phải ở
        hàng 1-60). Ba mảnh cùng bị kéo về một chiều cao khi vẽ ⇒ icon lịch ngồi cao hơn nút
        tròn mũi tên, và vạch sáng đáy viền đứt đoạn ngay tại mối nối.
     2. Mảnh GIỮA cắt ở x=148 của ảnh nguồn còn mảnh TRÁI cắt ở x=62 — cách nhau 86px trên một
        viền có độ sáng đổi theo chiều ngang, nên hai cột giáp nhau chênh tới 197/255.
   Nay mảnh giữa được DỰNG LẠI bằng nội suy tuyến tính giữa đúng hai cột giáp nó, nên cả hai
   mối nối chênh 0 theo định nghĩa — và mảnh mực chữ còn sót trong cột phải của nó (hàng 24-35)
   cũng biến mất. Dải giữa của thiết kế vốn là dốc màu phẳng (54,10,145) → (22,15,141) nên
   thay bằng dốc nội suy không mất chi tiết nào; phần có chi tiết là HAI ĐẦU thì giữ byte gốc. */
.lb-period {
  position: absolute;
  /* Neo theo mép PHẢI (x=1037 của khuôn) chứ không theo mép trái như Figma: chữ trong nút là
     chữ THẬT và động ("Mùa Đông 2026" dài hơn "Tuần 33/2026"), trong khi ô Figma rộng 230px
     tức chỉ 80px ở máy 390 — chính vì thế chữ trong file thiết kế cũng đang bị cắt. Neo phải
     thì nút nở sang trái khi cần mà mép phải vẫn đúng thiết kế. */
  right: 7.82%;
  top: 27.96%;
  /* Trên MỌI lớp khác của sân khấu, kể cả lớp phủ "chưa có quán quân" (z-index 6): nút này
     chồng lên góc thẻ Mộng Mơ theo đúng thiết kế, và ở trạng thái rỗng thì nó chính là lối
     thoát duy nhất — mờ đi hay bị đè là bé kẹt lại ở kỳ rỗng. */
  z-index: 7;
  width: max-content;
  min-width: 20.44%;
  max-width: 52%;
  /* CHIỀU CAO THEO FIGMA (75/2436), KHÔNG phải 44px.
     Trước 2026-09-04 chỗ này đặt thẳng min-height:44px lên chính nút NHÌN THẤY. Sai hai lần:
       1. Nút phình gấp đôi thiết kế (132×44 so với 65×21 ở 320px) — mọi khối khác của trang
          khớp Figma 0.0-0.6%, riêng nút này +101% (QC P2-04).
       2. Phần phình ra đè xuống tab "Lên Sóng" và NUỐT vùng chạm của nó, còn 37px (QC P1-03).
     Đúng quy ước docs/CONVENTIONS.md:196 — sàn chạm thuộc về phần tử BẤM (.lb-period__select),
     không thuộc phần tử hiển thị; riêng ở đây phần tử bấm cũng không đạt 44px được, xem khối
     .lb-period__select. Bề RỘNG vẫn phải vượt Figma vì chữ ở đây là chữ thật và
     động ("Tuần 36/2026"), trong khi ô Figma 230px hẹp tới mức chính file thiết kế cũng đang
     cắt chữ — đó là câu hỏi D-f đang chờ team design. */
  height: 3.08%;
  min-height: 20px;
  display: flex;
  align-items: stretch;
  gap: 0;
  padding: 0;
  border: 0;
  background: none;
  color: #fff;
  font-size: max(11px, 2.5cqw);
  font-weight: 700;
  line-height: 1.1;
  white-space: nowrap;
  /* KHÔNG cần overflow:hidden ở đây, và đừng thêm cho "chắc ăn". Việc cắt chữ đã có
     .lb-period__label lo bằng text-overflow, nền bo góc thì border-radius tự cắt. Trước
     2026-09-04 chỗ này còn là bẫy thật: <select> khi đó tràn xuống dưới nút, overflow:hidden
     cắt luôn vùng chạm và đo ra 0×0 ở 320px. Nay <select> bám đúng hộp nên bẫy đó hết, nhưng
     thêm một thuộc tính không ai cần vẫn là thêm một chỗ để hỏng. */
  cursor: pointer;
  appearance: none;
}

/* width:auto + height:100% = giữ nguyên tỉ lệ của mảnh ảnh ở MỌI bề rộng nút. Đây là chỗ
   thay cho việc "đặt lại vị trí icon sau khi tăng width": không có toạ độ nào phải tính. */
.lb-period__cap {
  flex: 0 0 auto;
  height: 100%;
  width: auto;
}

/* Dải nền giữa hai đầu. background-size 100% 100% kéo giãn theo CẢ hai chiều — giãn ngang thì
   liền mạch vì lát cắt này là nền trơn, còn giãn dọc thì giữ nguyên gradient dọc của thiết kế
   (tím ở trên → xanh ở dưới) đúng tỉ lệ chiều cao nút. */
/* Margin ÂM 1px mỗi bên = dải giữa LUỒN XUỐNG DƯỚI hai mảnh đầu, không đặt cạnh chúng.
   Hộp CSS của ba mảnh khít nhau tuyệt đối (`mid.right - cap.left` đo được 0.0000px ở mọi bề
   rộng), nhưng nền của dải giữa và ảnh của mảnh đầu được RASTER ĐỘC LẬP rồi làm tròn về lưới
   pixel thiết bị, nên ở một số bề rộng chúng làm tròn ra hai phía và hở đúng 1 css px. Khe đó
   để lọt NỀN SẢNH — một vạch dọc sáng chạy hết chiều cao nút, ngay trước nút tròn mũi tên.
   Đây là P1-01 của QC 08/09 (user hoãn lúc đó), user báo lại 2026-09-16 trong cùng mục
   "nút chọn kỳ bị lệch". Đo tại bề rộng 390: cột 800-807 của ảnh chụp @dsf8 chuyển thành ĐỎ
   khi sơn `.lb-period{background:red}` ⇒ hở thật, không phải vạch vẽ sẵn trong ảnh.
   1px chồng lên không che mất gì: sau khi dựng lại lb-period-mid.png, cột ngoài cùng của dải
   giữa TRÙNG KHỚP TỪNG BYTE với cột giáp nó của mảnh đầu (xem
   tools/figma-assets/align-leaderboard-period-slices.py), nên chồng hay không chồng đều ra
   đúng một màu. Đừng đổi sang cách "đặt nền dải giữa lên chính .lb-period": nền trải hết nút
   sẽ lộ hai góc VUÔNG dưới phần bo tròn của hai mảnh đầu — lý do đã ghi ở khối trên. */
.lb-period__mid {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0 -1px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: url("../images/portal/leaderboard/lb-period-mid.png") 0 0 / 100% 100% no-repeat;
}

.lb-period__label {
  overflow: hidden;
  text-overflow: ellipsis;
  /* Hai đầu đã có hình riêng nên chữ chỉ cần hở khỏi chúng một chút. */
  padding: 0 0.6cqw;
}

/* Thẻ <select> thật nằm chồng trong suốt lên nút: giữ được bàn phím, trình đọc màn hình
   và bộ chọn gốc của hệ điều hành — bé bấm là hiện danh sách kỳ như mọi app khác, không
   phải dropdown tự vẽ. */
/* Vùng chạm KHÔNG đạt sàn 44px, và CỐ Ý chỉ nới LÊN đúng 6px — không nới xuống, không nới
   ngang. Hai lần user chốt, giữ cả hai:

   (1) 2026-09-04 lượt 1: bỏ min-height 44/48px. Khe trống THẬT (không dính mực của hàng tab
       lẫn của thẻ) mà thiết kế chừa cho nút này, đo bằng kênh alpha của chính file PNG, giới
       hạn trong dải cột của nút:

           bề rộng   Tuần    Tháng   Lên Sóng     viên thuốc cao   sàn cần
           320      20.3px  21.7px   16.6px          21.3px         44px
           390      24.8px  26.4px   20.3px          26.0px         44px
           448      28.5px  30.0px   24.0px          29.9px         44px

       Viên thuốc ĐÃ chiếm trọn khe đó; 44px chỉ tự đến từ bề rộng ~590px trở lên. Ba cách nới
       đã thử rồi trượt QC, ĐỪNG dựng lại:
         - nở XUỐNG 48px: lớp trong suốt phủ huy hiệu Mộng Mơ + 12.2px mép trên ảnh tác phẩm
           @320 → bé chạm vào ảnh thì bật ra bảng chọn kỳ (QC P0-01 SMOKE, vi phạm C4).
         - nở LÊN 12.7px: ăn quá sâu vào mực nút tab, tab tụt dưới sàn 44px (P1-03 cũ).
         - ép chính viên thuốc cao 44px: nút nuốt trọn huy hiệu Mộng Mơ ở 320px.

   (2) 2026-09-04 lượt 4 (QC lan4 P0-01): nới LÊN đúng 6px. Lý do: hai vùng chạm xếp khít
       nhau KHÔNG còn một pixel đệm nào, nên hụt tay lên trên 2px là nhảy hẳn sang tab khác —
       mất luôn chỗ đang xem. Đo lại bằng % của CSS + alpha của PNG tab:

           bề rộng   mực tab hết ở   đỉnh viên thuốc   hở      đỉnh huy hiệu Mộng Mơ
           320         193.35          193.74         0.39px      213.4  (đã chồng 1.7px)
           390         235.65          236.12         0.47px      260.1
           448         270.69          271.23         0.54px      298.6

       Trên nút chỉ còn 0.4px trống rồi tới MỰC nút tab, dưới nút thì đã chạm huy hiệu ⇒ không
       có dải trong suốt nào để làm dải đệm; mọi cách nới đều phải ăn vào pixel bé NHÌN THẤY.
       Chọn ăn của tab vì tab đang dư (57px) còn nút này thiếu (21px):

           bề rộng   vùng chạm nút   vùng chạm tab ở cột nút   ghi chú
           320       21.3 → 27.3px       54.3 → 48.3px         vẫn ≥ 44
           390       26.0 → 32.0px       53.9 → 47.9px         vẫn ≥ 44
           448       29.9 → 35.9px       53.6 → 47.6px         vẫn ≥ 44

       6px chứ không hơn: 12px là tab tụt xuống ~42px, đúng lỗi P1-03 cũ. Số này là px CỐ ĐỊNH
       (ngón tay đo bằng px, không đo bằng %), và CHỈ nới lên — mép dưới vẫn phải trùng khít
       đáy viên thuốc để C4 giữ nguyên "0px lớp vô hình chồng lên thẻ".

   Riêng tab Lên Sóng: viên thuốc vẫn đè lên 13-16px góc TRONG SUỐT phía trên hộp thẻ Mộng Mơ —
   đó là bố cục Figma (thẻ skin Lên Sóng bắt đầu cao hơn hai skin kia), không phải phần nới thêm,
   và trong dải đó bé nhìn thấy đúng cái nút. Không sửa được nếu không dời lưới thẻ. */
.lb-period__select {
  position: absolute;
  left: 0;
  right: 0;
  /* -6px / +6px: xem khối chú thích ngay trên. Đổi số này là đổi tiêu chí C1 — phải sửa kèm HAI
     chỗ đang canh đúng 6px này: tools/ui-check/leaderboard-qc-fixes-check.mjs (gate của lô) và
     tools/ui-check/regression/leaderboard.mjs mục L8 (pack chống hồi quy, đo cả bằng chạm thật). */
  top: -6px;
  width: 100%;
  height: calc(100% + 6px);
  opacity: 0;
  cursor: pointer;
}

/* Trong lúc lượt đổi tab/kỳ đang bay: làm mờ NHẸ phần nội dung sẽ bị thay, để bé thấy trang
   đang phản hồi. Cố ý KHÔNG đặt pointer-events:none và KHÔNG che bằng lớp phủ — cả hai đều tạo
   ra đúng thứ mà tiêu chí C4 và gate L6 cấm: một lớp vô hình nằm trên thẻ. Lớp này biến mất
   cùng phần tử cũ khi outerHTML bị thay, nên không cần gỡ tay. */
.lb-stage.is-switching .lb-card,
.lb-stage.is-switching .lb-empty {
  opacity: .55;
  transition: opacity .12s ease-out;
}

/* ---------- bốn thẻ ---------- */

.lb-card {
  position: absolute;
  /* Huy hiệu nhô lên trên mép thẻ (top âm ở vài Căn cứ) nên KHÔNG được cắt. */
  overflow: visible;
}

.lb-card__frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 2;
  pointer-events: none;
}

/* Ảnh tác phẩm nằm DƯỚI khung: khung là PNG rỗng ruột (đo được 79% pixel trong suốt,
   alpha tại tâm = 0), nên ảnh thật lộ ra qua đúng cái lỗ đó. Toạ độ lỗ đo bằng kênh
   alpha của chính file khung, không tính tay từ toạ độ Figma. */
/* Từ 2026-09-16 bộ --lb-art-* do tools/figma-assets/build-leaderboard-card-window.py SINH RA,
   không đặt tay nữa. Bản đặt tay trước đó hụt vào trong 1-4px trên khuôn 404px ở cả bốn phía,
   và phần hụt đó lọt NỀN SẢNH (tím sáng) vào giữa ảnh và khung — user báo 2026-09-16: "hình
   tác phẩm phần trên chưa chiếm hết frame, vẫn nhìn thấy background bên dưới". Riêng "góc phải
   bên dưới" là cả một hình nêm: ở skin Tuần/Tháng đáy cửa sổ KHÔNG nằm ngang mà XIÊN, chạm
   xuống y=491 ở mép phải trong khi ô ảnh cũ dừng ở y=473.
   Script tự kiểm: mọi pixel trong hộp bao mà không thuộc cửa sổ đều phải là mực khung (leak=0),
   nên nới ô ảnh ra đúng hộp bao không làm ảnh lòi ra ngoài thẻ. */
.lb-card__art {
  position: absolute;
  left: var(--lb-art-left);
  top: var(--lb-art-top);
  width: var(--lb-art-width);
  height: var(--lb-art-height);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: #0B0B2B;
}

/* Ảnh nằm ĐÈ lên chữ dự phòng nhờ thứ tự DOM, không nhờ JS — chữ đứng trước trong markup.
   Ảnh 404 thì onerror gỡ nó đi và chữ lộ ra; không có JS thì cùng lắm là icon ảnh vỡ của
   trình duyệt, vẫn không làm sập bố cục thẻ (E6). */
.lb-card__artimg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Link phủ .lb-card__link (z-index 5) KHÔNG đủ: Chrome nhìn xuyên lớp phủ trong suốt và vẫn
     thêm "Save Image As…" vào menu chuột phải (đo trên prod 2026-10-08). Loại hẳn ảnh khỏi phép
     dò thì menu chỉ còn mục của link — cùng cách `.gal-card__img` trong gallery.css. */
  pointer-events: none;
}

/* Khối chờ. Phủ luôn chữ dự phòng trong lúc tải để bé không đọc thấy "chưa xem được" rồi ảnh
   mới hiện ra. Lớp này CHỈ sống khi class is-loading còn trên ô — cả onload lẫn onerror đều gỡ
   nó, nên ảnh 404 không để lại khối chờ quay mãi (đúng P1-2, và cùng bài học đã ghi ở trang
   Kết quả). Không JS thì HTML render sẵn không có class này, ô hiện ảnh như thường. */
/* QC 2026-09-04 (P1-05) chụp được 4 thẻ phẳng lì trong lúc tải và kết luận "không có khối chờ".
   Chẩn đoán trong báo cáo sai chỗ — phép đo nhắm vào ::before, còn khối chờ nằm ở ::after này —
   nhưng triệu chứng thì có thật, do HAI lỗi trong chính đoạn dưới:

     1. TƯƠNG PHẢN: dải sáng #1E2570 nằm trên nền #0B0B2B, tức navy trên navy. Trên màn điện
        thoại ngoài trời gần như không phân biệt được là đang chuyển động.
     2. DẢI CHẾT: background-size 300% + background-position chạy 150% → -150% là quãng đi 6 lần
        bề rộng ô, trong khi dải sáng chỉ rộng ~1 lần. Hơn 3/4 chu kỳ cửa sổ nhìn thấy nằm NGOÀI
        ảnh gradient nên trình duyệt kéo dài màu chặn cuối = phẳng hoàn toàn. Ảnh chụp của QC rơi
        đúng vào một frame phẳng như vậy.

   Sửa: nền sáng hơn nền ô để cả mảng đọc ra là "đang tải" chứ không phải "ô trống", dải sáng
   tăng tương phản, và size 200% + quãng đi đúng 1 lần bề rộng ⇒ dải sáng LUÔN có mặt trong
   khung, không còn frame nào phẳng. Vẫn giữ nguyên cơ chế cũ: lớp này CHỈ sống khi còn class
   is-loading, mà cả onload lẫn onerror đều gỡ, nên ảnh 404 không để lại khối chờ quay mãi. */
.lb-card__art.is-loading::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  background: linear-gradient(100deg, #161D5C 25%, #3D4CB4 50%, #161D5C 75%);
  background-size: 200% 100%;
  animation: lb-shimmer 1.4s linear infinite;
}

@keyframes lb-shimmer {
  from { background-position: 100% 0; }
  to   { background-position: 0 0; }
}

@media (prefers-reduced-motion: reduce) {
  .lb-card__art.is-loading::after { animation: none; background: #131A52; }
}

.lb-card__artmsg {
  padding: 0 8%;
  color: #C9D4FF;
  font-size: max(11px, 2.8cqw);
  line-height: 1.25;
  text-align: center;
}

/* Chip loại hình — CHỈ ICON (▶ Video / ♪ Nhạc), user chốt 2026-10-07. Nền/viền/màu chép từ
   `.gal-card__type` (gallery.css) nên vẫn đọc ra cùng một "họ" chip với Gallery.

   Vì sao không dùng chip CHỮ như Gallery: user muốn chip sát góc trên-trái ô ảnh, mà ở đó huy
   hiệu Căn cứ (z-index 4) thòng xuống 40-46px dưới mép trên ô ảnh, chừa bên trái nó chỉ ~35px
   @390 (~29px @320). Chip chữ "Video" ở sàn 11px (CONVENTIONS) rộng 42px ⇒ bị pill huy hiệu đè.

   Hai ràng buộc, đổi số nào là phải đo lại cả hai (3 skin × 320-1280):
     1. Mép phải chip < mép trái huy hiệu.
     2. Góc trên-trái chip nằm NGOÀI vát góc của cửa sổ khung (khung z-index 2 đè lên ô ảnh).
   cqw ở đây quy theo `.lb-stage` (container của trang), nên chip co giãn cùng thẻ. */
.lb-card__type {
  position: absolute;
  left: 1.4cqw;
  top: 3.6cqw;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  width: max(18px, 5.1cqw);
  aspect-ratio: 1;
  border-radius: 50%;
  background: rgba(10, 16, 44, 0.82);
  backdrop-filter: blur(4px);
  border: 1px solid rgba(160, 190, 255, 0.32);
  color: #e8efff;
  pointer-events: none;
}

.lb-card__type svg {
  width: 62%;
  height: 62%;
  fill: currentColor;
}

/* Link phủ trọn thẻ có quán quân (2026-10-07). Nằm TRÊN mọi lớp (huy hiệu z-index 4) để chạm
   vào đâu trên thẻ cũng rơi vào link. Thẻ rỗng không có phần tử này. */
.lb-card__link {
  position: absolute;
  inset: 0;
  z-index: 5;
}

.lb-card__link:focus-visible {
  outline: 3px solid #FFD84D;
  outline-offset: 2px;
  border-radius: 8px;
}

.lb-card__ava {
  position: absolute;
  left: var(--lb-ava-left);
  top: var(--lb-ava-top);
  width: var(--lb-ava-size);
  aspect-ratio: 1;
  z-index: 3;
  border-radius: 50%;
  object-fit: cover;
}

.lb-card__badge {
  position: absolute;
  z-index: 4;
  pointer-events: none;
}

/* MỘT dòng kể từ 2026-09-08 (bỏ dòng "[biệt danh]", xem Index.cshtml). Trước đó khối này có hai
   dòng và cao gần GẤP ĐÔI ô chữ của thiết kế, nên nó tràn xuống đè lên thanh vẽ sẵn trong khung.
   Nay một dòng ở sàn 11px cao ~12,7px trong khi ô thiết kế cao ~14,2px @320 ⇒ nằm gọn.

   min-height chứ KHÔNG phải height: chữ luôn thấp hơn ô (cả hai đều tính theo cqw nên tỉ lệ giữ
   ở mọi bề rộng, biên hẹp nhất là 1,6px @320 khi sàn 11px kích hoạt), nhưng khoá cứng height thì
   một lần đổi font là cắt mất chữ mà không ai thấy. Ràng SÀN, để trần tự do — cùng lý do đã ghi
   ở docs/CONVENTIONS.md về phép đo một chiều. */
.lb-card__name {
  position: absolute;
  left: var(--lb-name-left);
  /* Neo MÉP TRÊN của ô chữ Figma, KHÔNG neo tâm: nếu khối có nở ra thì phải nở XUỐNG (phía
     dưới là mặt thanh tên, còn chỗ), chứ nở lên là đè vào vùng ảnh tác phẩm — đo được 2,9px
     @320 hồi còn hai dòng. Ô chữ của cả 4 Căn cứ ở cả 2 skin đều bắt đầu THẤP HƠN mép dưới lỗ
     ảnh nên hướng này luôn an toàn. */
  top: var(--lb-name-top);
  width: var(--lb-name-width);
  /* Chiếm trọn ô chữ của thiết kế để một dòng nằm đúng giữa thanh tên, thay vì dính lên mép trên. */
  min-height: var(--lb-name-height);
  z-index: 3;
  display: flex;
  flex-direction: column;
  justify-content: center;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
  overflow: hidden;
}

/* SÀN 11px: khối tên trong Figma chỉ cao 50px trên khuôn 1125, tức ~7px mỗi dòng ở bề
   rộng 320px. Đo bằng clientHeight / line-height chứ không bằng scrollHeight — phần tử
   có clamp đặt cả top lẫn bottom sẽ vẽ dấu "…" mà scrollHeight vẫn bằng clientHeight. */
.lb-card__name b {
  display: block;
  font-size: max(11px, 3.1cqw);
  line-height: 1.15;
  font-weight: 800;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------- ô rỗng ---------- */

/* Căn cứ chưa có quán quân: ô GIỮ CHỖ, không dồn. Dồn thì huy hiệu của Căn cứ này sẽ
   trôi sang vị trí của Căn cứ khác và người xem không nhận ra là đang nhìn sai bảng. */
/* MẶT NẠ chỉ đặt ở trạng thái RỖNG, không đặt cho mọi thẻ. Cửa sổ ảnh KHÔNG phải hình chữ
   nhật (góc trên vát chéo, đáy xiên ở skin Tuần/Tháng; bát giác có khía ở skin Lên Sóng), nên
   `.lb-card__art` — vốn là hộp chữ nhật bao trọn cửa sổ — luôn thừa ra ngoài cửa sổ. Với thẻ
   CÓ ảnh thì phần thừa nằm sau khung đục và không ai thấy; hơn nữa vành vát của khung có một
   dải bán trong suốt (alpha 130-249, chiếm 8-22% phần thừa) mà thiết kế cố ý để ảnh tác phẩm
   ánh qua — che nó đi là mất hiệu ứng. Với thẻ RỖNG thì khác hẳn: nền đặc rgba(10,8,51,.72)
   nằm dưới một cái khung đã bị làm mờ còn .55 ⇒ chính bốn GÓC VUÔNG của hộp chữ nhật hiện
   xuyên qua khung, đọc ra như một tấm nền dán đè lên thẻ (user báo 2026-09-16). Mặt nạ cắt
   tấm nền đó theo đúng hình cửa sổ, đo từ kênh alpha của chính file khung bằng
   tools/figma-assets/build-leaderboard-card-window.py.
   Engine không hỗ trợ mask thì rơi về đúng hành vi cũ (hộp chữ nhật), không sập bố cục. */
.lb-card--empty .lb-card__art {
  background: rgba(10, 8, 51, .72);
  -webkit-mask-image: var(--lb-art-mask);
          mask-image: var(--lb-art-mask);
  -webkit-mask-size: 100% 100%;
          mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
}

.lb-card--empty .lb-card__frame {
  opacity: .55;
}

/* ---------- trạng thái rỗng toàn bảng ---------- */

/* Cả bốn Căn cứ đều rỗng: phủ NGUYÊN vùng lưới thẻ chứ không đặt một hộp lửng ở giữa —
   hộp lửng đè lên thẻ trông như lỗi chồng lớp, và chữ rỗng của từng thẻ vẫn thò ra hai bên.
   Phủ cả vùng thì đọc ra là có chủ ý, và bốn khung vẫn thấy mờ phía sau nên bé vẫn hiểu
   bảng có bốn Căn cứ. */
/* Vùng phủ đo từ lưới thẻ THẬT, không đặt tay: mép trên = mép trên thẻ hàng đầu của skin cao
   nhất (Lên Sóng, 29.10%), mép dưới = mép dưới thẻ hàng hai của skin thấp nhất (Lên Sóng,
   90.02%). Lấy tay hụt 1.2% là chừa một dải thẻ thò ra dưới lớp phủ.
   pointer-events: none — lớp phủ chỉ để NHÌN. Không có nó thì chính câu "bé chọn kỳ khác ở
   nút phía trên" lại bị lớp phủ nuốt mất cú chạm vào nút đó (elementFromPoint tại tâm nút
   chọn kỳ trả về .lb-empty, đo được 2026-09-04). */
.lb-empty {
  position: absolute;
  left: 3.47%;
  top: 29.10%;
  width: 92.80%;
  height: 61.00%;
  z-index: 6;
  pointer-events: none;
  /* Bo góc theo khung panel bao quanh: mép vuông cắt ngang nền sảnh trông như một hộp bị đặt
     nhầm, bo tròn thì đọc ra là một lớp phủ có chủ ý. */
  border-radius: 6cqw;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 6cqw;
  background: rgba(9, 7, 67, .80);
  backdrop-filter: blur(2px);
  color: #EAF0FF;
  font-size: max(12px, 3.4cqw);
  line-height: 1.45;
  text-align: center;
}

/* ---------- vị trí bốn thẻ trên khuôn ---------- */

.lb-card--sieu-anh-hung { left: 4.62%;  top: 30.17%; width: 44.89%; }
.lb-card--mong-mo       { left: 50.49%; top: 31.49%; width: 44.80%; }
.lb-card--tham-hiem     { left: 4.62%;  top: 59.40%; width: 44.89%; }
.lb-card--sang-tao      { left: 50.49%; top: 60.47%; width: 44.98%; }

.lb-card--onair.lb-card--sieu-anh-hung { left: 4.89%;  top: 29.10%; width: 44.44%; }
.lb-card--onair.lb-card--mong-mo       { left: 50.76%; top: 29.15%; width: 44.36%; }
.lb-card--onair.lb-card--tham-hiem     { left: 4.89%;  top: 58.33%; width: 44.44%; }
.lb-card--onair.lb-card--sang-tao      { left: 50.76%; top: 58.29%; width: 44.44%; }

/* ---------- hình học trong từng thẻ (sinh bằng script, xem ghi chú đầu file) ---------- */

.lb-card--sieu-anh-hung {
  --lb-art-mask: url("../images/portal/leaderboard/lb-card-sieu-anh-hung-window.png");
  --lb-art-left: 4.95%;  --lb-art-top: 9.27%;
  --lb-art-width: 90.10%; --lb-art-height: 77.10%;
  --lb-ava-left: 5.94%; --lb-ava-top: 82.80%; --lb-ava-size: 16.63%;
  --lb-name-left: 31.77%; --lb-name-top: 83.98%;
  --lb-name-width: 56.84%; --lb-name-height: 6.99%;
  aspect-ratio: 505 / 715;
}
.lb-card--mong-mo {
  --lb-art-mask: url("../images/portal/leaderboard/lb-card-mong-mo-window.png");
  --lb-art-left: 4.70%;  --lb-art-top: 5.85%;
  --lb-art-width: 90.59%; --lb-art-height: 80.44%;
  --lb-ava-left: 6.35%; --lb-ava-top: 82.58%; --lb-ava-size: 16.47%;
  --lb-name-left: 31.98%; --lb-name-top: 83.24%;
  --lb-name-width: 56.63%; --lb-name-height: 7.32%;
  aspect-ratio: 504 / 683;
}
.lb-card--tham-hiem {
  --lb-art-mask: url("../images/portal/leaderboard/lb-card-tham-hiem-window.png");
  --lb-art-left: 4.95%;  --lb-art-top: 9.28%;
  --lb-art-width: 90.10%; --lb-art-height: 77.06%;
  --lb-ava-left: 5.94%; --lb-ava-top: 82.63%; --lb-ava-size: 16.83%;
  --lb-name-left: 31.72%; --lb-name-top: 83.62%;
  --lb-name-width: 56.89%; --lb-name-height: 7.00%;
  aspect-ratio: 505 / 714;
}
.lb-card--sang-tao {
  --lb-art-mask: url("../images/portal/leaderboard/lb-card-sang-tao-window.png");
  --lb-art-left: 4.95%;  --lb-art-top: 5.81%;
  --lb-art-width: 90.35%; --lb-art-height: 80.04%;
  --lb-ava-left: 5.73%; --lb-ava-top: 82.46%; --lb-ava-size: 16.80%;
  --lb-name-left: 31.86%; --lb-name-top: 82.76%;
  --lb-name-width: 56.75%; --lb-name-height: 7.25%;
  aspect-ratio: 506 / 690;
}

/* Skin Lên Sóng: khung khác hẳn hai tab kia.
   --lb-ava-*: đo THẲNG TỪ FILE PNG bằng tools/figma-assets/erase-onair-baked-face.py ngày
   2026-09-04, sau khi script đó xoá ảnh mặt em bé bị nướng cứng vào đĩa (QC P0-01). Đĩa nằm
   cùng một toạ độ tuyệt đối ở cả bốn file (tâm 75.5/513, r 35.5 trên khuôn rộng 404px) nên
   left/size giống hệt nhau; chỉ --lb-ava-top khác vì bốn file cao khác nhau. */
.lb-card--onair.lb-card--sieu-anh-hung {
  --lb-art-mask: url("../images/portal/leaderboard/lb-card-onair-sieu-anh-hung-window.png");
  --lb-art-left: 6.19%;  --lb-art-top: 10.58%;
  --lb-art-width: 87.87%; --lb-art-height: 64.58%;
  --lb-ava-left: 9.90%; --lb-ava-top: 76.52%; --lb-ava-size: 17.57%;
  --lb-name-left: 36.89%; --lb-name-top: 77.26%;
  --lb-name-width: 49.99%; --lb-name-height: 6.48%;
  aspect-ratio: 500 / 772;
}
.lb-card--onair.lb-card--mong-mo {
  --lb-art-mask: url("../images/portal/leaderboard/lb-card-onair-mong-mo-window.png");
  --lb-art-left: 6.19%;  --lb-art-top: 11.17%;
  --lb-art-width: 87.62%; --lb-art-height: 68.19%;
  --lb-ava-left: 9.90%; --lb-ava-top: 80.80%; --lb-ava-size: 17.57%;
  --lb-name-left: 36.71%; --lb-name-top: 81.58%;
  --lb-name-width: 50.17%; --lb-name-height: 6.85%;
  aspect-ratio: 499 / 730;
}
.lb-card--onair.lb-card--tham-hiem {
  --lb-art-mask: url("../images/portal/leaderboard/lb-card-onair-tham-hiem-window.png");
  --lb-art-left: 6.19%;  --lb-art-top: 10.59%;
  --lb-art-width: 87.87%; --lb-art-height: 64.53%;
  --lb-ava-left: 9.90%; --lb-ava-top: 76.65%; --lb-ava-size: 17.57%;
  --lb-name-left: 36.83%; --lb-name-top: 77.18%;
  --lb-name-width: 50.05%; --lb-name-height: 6.49%;
  aspect-ratio: 500 / 771;
}
.lb-card--onair.lb-card--sang-tao {
  --lb-art-mask: url("../images/portal/leaderboard/lb-card-onair-sang-tao-window.png");
  --lb-art-left: 6.19%;  --lb-art-top: 10.56%;
  --lb-art-width: 88.12%; --lb-art-height: 64.64%;
  --lb-ava-left: 9.90%; --lb-ava-top: 76.40%; --lb-ava-size: 17.57%;
  --lb-name-left: 36.60%; --lb-name-top: 77.11%;
  --lb-name-width: 50.28%; --lb-name-height: 6.47%;
  aspect-ratio: 500 / 773;
}

/* ---------- vị trí huy hiệu, tính theo THẺ nên dùng chung cho tab Tuần và Tháng ---------- */

.lb-card--sieu-anh-hung .lb-card__badge { left: 25.15%; top: 1.54%;  width: 47.13%; }
.lb-card--mong-mo       .lb-card__badge { left: 26.19%; top: -2.49%; width: 47.22%; }
.lb-card--tham-hiem     .lb-card__badge { left: 26.53%; top: 0.14%;  width: 47.33%; }
.lb-card--sang-tao      .lb-card__badge { left: 26.28%; top: -3.48%; width: 47.23%; }

.lb-card--onair.lb-card--sieu-anh-hung .lb-card__badge { left: 26.60%; top: 3.63%; width: 47.60%; }
.lb-card--onair.lb-card--mong-mo       .lb-card__badge { left: 27.05%; top: 3.70%; width: 47.70%; }
.lb-card--onair.lb-card--tham-hiem     .lb-card__badge { left: 26.40%; top: 3.11%; width: 47.80%; }
.lb-card--onair.lb-card--sang-tao      .lb-card__badge { left: 26.80%; top: 2.85%; width: 47.80%; }
