/* ⚠ ĐỢT 386 — PHẢI CÓ FONT EMOJI Ở CUỐI CHUỖI (anh Tiến báo 09/08: "có hiện tượng lỗi font chữ").
   App dùng ~1.650 emoji làm ICON (📋 🔐 🚀 …) trong 151 trang. Bootstrap khai sẵn dây chuyền dự phòng
   emoji ở --bs-font-sans-serif, NHƯNG app.css nạp SAU Bootstrap và đè thẳng `html, body` ⇒ xoá sạch
   phần emoji đó. Helvetica/Arial/Tahoma KHÔNG có một glyph emoji nào ⇒ máy nào thiếu font emoji của
   hệ điều hành (điển hình: Windows Server cài bản KHÔNG có Desktop Experience — rất hay gặp ở máy
   triển khai) thì mọi icon thành HỘP VUÔNG ▯, còn chữ tiếng Việt vẫn bình thường.
   ⇒ Luật: MỌI khai báo font-family trong app đều phải kết bằng 3 font emoji + font Linux dự phòng.
   (co-dien.css có 3 chuỗi nữa, đã sửa cùng đợt.) */
/* ⚠ ĐỢT 2655 (việc o2143) — BỎ 'Helvetica Neue' VÀ 'Helvetica' KHỎI ĐẦU CHUỖI.
   Anh Tiến 16/09/2026: "toàn bộ font chữ đang dùng bị lỗi với tiếng việt (chữ có dấu thì nhỏ)".
   'Helvetica Neue' ⛔ CÓ trên Windows (⇒ máy dev và máy triển khai bỏ qua nó, rơi xuống Arial) nhưng
   CÓ THẬT trên iPhone/iPad/Mac, mà ở đó nó thiếu dải chữ HAI DẤU U+1EA0–1EF9 ⇒ máy Apple phải mượn
   phông khác cho đúng những chữ Ộ Ừ Ệ Ậ Ợ — đúng kiểu "chữ có dấu trông nhỏ hơn". Anh đang mở dần
   đường dùng app bằng điện thoại nên cái bẫy ấy ⛔ còn xa nữa.
   📏 ĐỔI NÀY ⛔ LÀM ĐỔI MỘT ĐIỂM ẢNH NÀO TRÊN WINDOWS — đo tận tay 16/09 (Chrome thật, canvas
   measureText, câu 82 ký tự cỡ 16px): chuỗi CŨ 728,875px · chuỗi MỚI 728,875px · Arial trần
   728,875px · Helvetica trần 728,875px. Windows vốn ánh xạ thẳng Helvetica sang Arial.
   ⛔ Đừng thêm họ chữ nào vào chuỗi này mà chưa dò dải U+1EA0–1EF9 của nó — mở
   /do-phong-chu.html trên chính máy nghi ngờ là biết. */
/* ═══ PHÔNG THÂN BÀI — ĐỢT 2695 (việc h322), anh Tiến báo lần THỨ BA 17/09/2026 ═════════════════
   Nguyên văn: «em kiểm tra lại font chữ không phù hợp với tiếng việt như tiêu đề "Các đợt đã tạo
   (0)" chữ a. bị nhỏ» + ảnh nút «Đăng nhập» có chữ «ậ» nhỏ hơn phần còn lại.

   🔴 VÌ SAO KHÔNG CHỮA ĐƯỢC BẰNG CÁCH SỬA CHUỖI PHÔNG NỮA. Hai đợt trước (2535, 2655) đều đo trên
   MÁY DEV và đều SẠCH: 941 ô chữ / 20.030 chữ, đúng MỘT phông Arial vẽ tất cả. Phông là chuyện của
   MÁY XEM chứ ⛔ của máy chủ — chuỗi `Arial, …` chỉ là lời ĐỀ NGHỊ, máy nào có bản Arial thiếu dải
   hai dấu U+1EA0–1EF9 (hoặc ⛔ có Arial: máy Apple, máy Android, máy Linux) thì trình duyệt lẳng
   lặng mượn phông khác cho ĐÚNG những chữ Ậ Ộ Ợ Ệ Ừ ⇒ trong một từ có hai bộ nét, mắt thấy «chữ có
   dấu thì nhỏ». ⇒ Đường chữa DUY NHẤT không phụ thuộc máy xem là NẠP PHÔNG THEO PHẦN MỀM.

   ⇒ Họ chữ: **Be Vietnam Pro** — do nhóm người Việt vẽ riêng cho tiếng Việt, dấu được VẼ chứ không
   chắp; sáu tệp nét 400/500/600 tải bổ sung ở đợt này (101 KB) nên chữ thường ⛔ còn bị vẽ bằng mặt
   ĐẬM như số đo đợt 2535 cảnh báo. Khai báo + tên biến nằm ở `chu/chu-viet.css`.

   🔑 TẮT/BẬT BẰNG ĐÚNG MỘT CHỮ: bỏ `'Be Vietnam Pro',` ở đầu chuỗi dưới là app quay về Arial của
   máy xem, ⛔ phải sửa chỗ nào khác. Đuôi dự phòng giữ NGUYÊN theo luật đợt 386 (ba phông emoji).
   ⛔ ĐỪNG gói tên họ vào một biến `var(--…)` cho "gọn": hàng rào `Phong_dat_cho_toan_app_phai_co_net_thuong`
   đọc THẲNG chuỗi này để soi xem họ tự lưu đã có nét 400 chưa — thấy `var(…)` là nó đậu RỖNG, tức
   mất đúng cái hàng rào giữ cho cả app khỏi đậm lên 8,1% (số đo đợt 2535). */
html, body {
    font-family: 'Be Vietnam Pro', Arial, 'Liberation Sans', 'DejaVu Sans', sans-serif,
                 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
}

a, .btn-link {
    color: #006bb7;
}

/* ═══ LIÊN KẾT KHÔNG GẠCH CHÂN LÚC NGHỈ (anh chỉ đạo 23/08/2026) ═══
   Nguyên văn: "tên các nút, tên dự án,... không được gạch dưới kiểu link nữa rất xấu, tương tự với
   các phân hệ khác trên toàn dự án".

   🔴 ⛔ ĐỪNG "dọn gọn" khối này thành một dòng `--bs-link-decoration: none` — ĐÃ ĐO, ĐÓ LÀ LƯỢT SỬA
   VÔ HIỆU HOÀN TOÀN. Bootstrap 5.3.8 KHÔNG dựng gạch chân của thẻ `a` bằng biến. Đọc thẳng
   bootstrap.min.css:
       a{color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,1));text-decoration:underline}
   Chữ `underline` nằm ĐÓNG ĐINH trong khai báo. Token `--bs-link-decoration` có khai đúng MỘT lần ở
   `:root,[data-bs-theme=light]` nhưng KHÔNG một luật nào trong cả tệp đọc tới nó — Bootstrap tiêu
   biến Sass `$link-decoration` ngay lúc biên dịch, token để lại chỉ là thứ cho người ngoài tham
   chiếu. ⇒ Đặt biến ấy thì build xanh, test xanh, màn hình KHÔNG ĐỔI GÌ: đúng loại hỏng lặng lẽ mà
   quy tắc #5 đã trả giá ba lượt ở đợt 838 / 840 / 850. Câu hỏi đúng luôn là "Bootstrap dựng thuộc
   tính NÀY bằng biến hay bằng chữ đóng đinh", và ở đây câu trả lời là CHỮ ĐÓNG ĐINH.

   ⇒ Cách đúng là đè thẳng chính khai báo. Chỉ cần THỨ TỰ NGUỒN, không phải đội trọng số:
     · `a` (0,0,1) — app.css nạp SAU bootstrap.min.css nên thắng luật `a` cùng trọng số của Bootstrap.
     · `.btn-link` (0,1,0) — phải kể tên RIÊNG: Bootstrap đặt lại `text-decoration: underline` cho lớp
       này ở (0,1,0), cao hơn `a`, nên luật `a` một mình không với tới.

   ⛔ KHÔNG dùng `!important`: ba lối CỐ Ý gạch chân sẽ bị cướp mất — lớp tiện dụng
   `.text-decoration-underline` của Bootstrap (bản thân nó mang !important), `abbr[title]` (gạch chấm
   chấm, nghĩa khác hẳn là "có chú giải"), và luật scoped của từng component. Không !important thì
   mọi chỗ ấy vẫn tự quyết được.

   ⓘ Không phải khai lại theo từng giao diện: `text-decoration` không dính màu, còn nét gạch thì lấy
   `currentColor` nên tự đi theo màu liên kết của giao diện đang bật. `bo-mau/ap-dung.css` chỉ đặt
   MÀU liên kết chứ không đụng gạch chân, nên một khối ở đây đủ cho cả hai trục `data-theme` và
   `data-bo-mau`. */
a,
.btn-link {
    text-decoration: none;
}

/* 🔑 BÙ LẠI DẤU HIỆU "ĐÂY LÀ THỨ BẤM ĐƯỢC" — ⛔ đừng bỏ trắng.
   Bỏ gạch chân mà không bù gì thì một liên kết nằm giữa đoạn văn hết đường nhận ra, và người dùng
   bàn phím mất luôn mốc mình đang đứng ở đâu. ⇒ Gạch chân QUAY LẠI khi rê chuột và khi focus bàn
   phím: lúc bình thường sạch mắt đúng ý anh, lúc người ta hỏi tới thì nó tự khai.
   Dùng `:focus-visible` chứ ⛔ không phải `:focus` — bấm chuột xong không để lại gạch chân lủng lẳng.

   Hai nhóm ĐỨNG NGOÀI, vì đã có dấu hiệu bấm được riêng (nền, viền, đổi nền khi rê) nên thêm gạch
   chân chỉ làm chữ nhảy:
     ① LỚP ĐIỀU KHIỂN của Bootstrap — tập ĐÓNG trong 5.3.x, không phải danh sách chép tay tự mục theo
        thời gian. Bắt buộc phải kể ra: `a:hover` là (0,1,1), CAO HƠN `.btn` (0,1,0), nên không loại
        trừ thì đúng 67 thẻ `<a class="btn">` của app bị gạch chân mỗi lần rê chuột.
     ② BỘ KHUNG của app (thanh trên, menu dọc, rail phân hệ, lưới 9 chấm) — toàn thứ điều hướng.
        Khuôn `:not(.top-row *)` lấy đúng theo tiền lệ đang chạy ở bo-mau/ap-dung.css mục 2.
     ③ ⭐ ĐỢT 1215 (việc o404) — `.ten-ban-ghi`: TÊN BẢN GHI trong bảng/danh sách. Anh Tiến 24/08/2026,
        xem màn hình 🗂 Nhiệm vụ đang thực hiện: *"chữ nhiệm vụ vẫn ở dạng link"*. Nhóm này đứng ngoài
        vì nó CỐ Ý không trông như liên kết ở BẤT KỲ trạng thái nào — dấu hiệu bấm được của nó là hàng
        sáng lên (`.table-hover`) + chữ đổi màu lúc rê, xem khối luật ngay dưới.
   Component nào muốn khác thì cứ khai trong `.razor.css` của nó: gói scoped nạp SAU app.css nên
   thắng ở cùng trọng số (`TabTrangCuaApp` đang làm đúng vậy để tab không nhảy chữ).

   ⓘ `:not()` nhận DANH SÁCH và nhận cả bộ chọn phức — Selectors Level 4, và kho đã phụ thuộc sẵn
   (`a:not(.btn):not(.top-row *)` ở ap-dung.css). Trình duyệt nào không hiểu thì bỏ nguyên bộ chọn
   này, tức chỉ mất phần BÙ còn phần bỏ gạch chân vẫn đúng — hỏng về phía an toàn. */
a:not(.btn, .nav-link, .navbar-brand, .dropdown-item, .page-link, .list-group-item, .ten-ban-ghi):not(.top-row *, .sidebar *, .nav-scrollable *, .rail-phan-he *, .menu-co-dien *):hover,
a:not(.btn, .nav-link, .navbar-brand, .dropdown-item, .page-link, .list-group-item, .ten-ban-ghi):not(.top-row *, .sidebar *, .nav-scrollable *, .rail-phan-he *, .menu-co-dien *):focus-visible,
.btn-link:hover,
.btn-link:focus-visible {
    text-decoration: underline;
}

/* ═══ ⭐ ĐỢT 1215 (việc o404) — TÊN BẢN GHI THÔI TRÔNG NHƯ MỘT LIÊN KẾT ═══
   Anh Tiến 24/08/2026, kèm ảnh màn hình 🗂 Nhiệm vụ đang thực hiện: "chữ nhiệm vụ vẫn ở dạng link".

   📏 ĐO TRƯỚC KHI SỬA, và số đo đổi hẳn cách chữa: luật bỏ gạch chân của đợt 23/08 ở ngay trên
   ĐANG CHẠY ĐÚNG — `curl http://localhost:5270/app.css` trả về đủ khối `a, .btn-link {
   text-decoration: none }`. Thứ còn lại trong ảnh là (a) MÀU LIÊN KẾT lúc nghỉ và (b) nét gạch mà
   chính luật ngay trên CỐ Ý trả lại khi rê chuột — hàng trong ảnh đang được rê nên nó hiện ra.
   ⇒ ⛔ ĐỪNG đi vá lại luật bỏ gạch chân (nó không hỏng); phải bỏ MÀU, và cho tên bản ghi đứng
     ngoài phần bù gạch chân.

   🔑 VÌ SAO KHÔNG BỎ TRẮNG DẤU HIỆU BẤM ĐƯỢC. Bỏ cả màu lẫn gạch chân thì tên bản ghi hoá chữ
   thường, không ai biết bấm được nữa — đúng cái mà khối bù ở trên sinh ra để tránh. Ở đây dấu hiệu
   được trả bằng hai đường khác: hàng sáng lên (`.table-hover`, mọi bảng dùng lớp này) và CHỮ ĐỔI
   SANG MÀU LIÊN KẾT lúc rê/focus. Nghỉ thì sạch mắt đúng ý anh, hỏi tới thì nó tự khai.
   ⓘ Mọi thẻ mang lớp này đều PHẢI có `title` nói rõ bấm vào mở ra cái gì (luật đợt 222) — đó là
     đường thứ ba, và là đường duy nhất người dùng bàn phím đọc được.

   ⚠ `color: inherit` chứ ⛔ không đóng đinh một mã màu: ô nằm trong `<td>`/`<dd>` nên nó tự lấy màu
   chữ của giao diện đang bật (sáng/tối), khỏi phải khai lại theo từng trục `data-theme`.
   ⚠ Trọng số: `a.ten-ban-ghi` là (0,1,1) — thắng luật `a` (0,0,1) của Bootstrap ở cả màu nghỉ lẫn
   màu rê, ⛔ nên không cần `!important` (dùng nó là cướp mất quyền tự quyết của luật scoped). */
a.ten-ban-ghi {
    color: inherit;
}

a.ten-ban-ghi:hover,
a.ten-ban-ghi:focus-visible {
    color: var(--bs-link-hover-color, #0a58ca);
}

.btn-primary {
    color: #fff;
    background-color: #1b6ec2;
    border-color: #1861ac;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

.content {
    padding-top: 1.1rem;
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

/* ⛔ ĐỢT 850 (18/08/2026) — ĐÃ XOÁ luật `.darker-border-checkbox.form-check-input { border-color:#929292 }`
   của khuôn mẫu Blazor: đo toàn kho (.razor · .cs · .css · .js · .html, bỏ bin/obj) ra **0 chỗ dùng**,
   chỉ còn chính dòng khai ấy. Nó cũng là 1 trong 5 luật đặt `border-color` LONGHAND lên `.form-check-input`
   đang được kể ở nợ **CK-65** ⇒ xoá xong còn **4**. Xoá vì rác thì rủi ro bằng 0, ⛔ KHÔNG phải vì nó
   nuốt viền đỏ — bốn luật kia mới là chuyện của CK-65 và chúng CỐ Ý ở lại (xem CK-65: khuôn
   `--bs-border-color` của đợt 838/840 KHÔNG chuyển sang ô tick được). */

/* Thong bao noi (popup) - hien co dinh goc phai tren man hinh, khong phai cuon trang di tim */
.thong-bao-noi {
    position: fixed;
    top: 72px;
    right: 16px;
    left: auto;
    z-index: 1080;
    max-width: min(440px, calc(100vw - 32px));
    box-shadow: 0 6px 20px rgba(0, 0, 0, .3);
    animation: thong-bao-hien .25s ease-out;
}

@keyframes thong-bao-hien {
    from { opacity: 0; transform: translateY(-8px); }
    to { opacity: 1; transform: none; }
}

/* ===================== Ô NGÀY dd/mm/yyyy CÓ NÚT LỊCH =====================
   Component Components/ChungTu/ONgay.razor + ONgayCoThe.razor. Ba phần trong một khung:
   ô GÕ dd/mm/yyyy + nút 📅 vẽ giống nút lịch của trình duyệt + ô ngày THẬT bị giấu (chỉ để mượn lịch).
   Nhìn y hệt ô ngày cũ nhưng chữ luôn là dd/mm/yyyy trên mọi máy. */
.o-ngay-boc {
    position: relative;
    display: inline-block;
    width: 150px;
    max-width: 100%;
    vertical-align: middle;
}

.o-ngay-boc .o-ngay-go {
    width: 100%;
    padding-right: 1.9rem;   /* chừa chỗ cho nút lịch, chữ không chui xuống dưới nút */
}

.o-ngay-boc .nut-lich {
    position: absolute;
    top: 1px;
    right: 1px;
    bottom: 1px;
    width: 1.7rem;
    padding: 0;
    border: 0;
    background: transparent;
    font-size: .85rem;
    line-height: 1;
    cursor: pointer;
    opacity: .75;
}

.o-ngay-boc .nut-lich:hover:not(:disabled) {
    opacity: 1;
    background-color: rgba(0, 0, 0, .06);
}

.o-ngay-boc .nut-lich:disabled {
    opacity: .35;
    cursor: default;
}

/* Ô ngày THẬT: giấu khỏi mắt nhưng VẪN nằm trong bố cục — `display:none` thì showPicker() báo lỗi.
   Đặt sát nút lịch để hộp lịch của trình duyệt bung ra đúng chỗ. */
.o-ngay-boc .lich-an {
    position: absolute;
    right: .6rem;
    bottom: 0;
    width: 1px;
    height: 1px;
    padding: 0;
    border: 0;
    opacity: 0;
    pointer-events: none;
}

:root[data-theme="dark"] .o-ngay-boc .nut-lich:hover:not(:disabled) {
    background-color: rgba(255, 255, 255, .1);
}

/* ===================== HÀNG CHẴN/LẺ KHÁC MÀU — MỌI BẢNG, MỌI GIAO DIỆN =====================
   Anh Tiến yêu cầu 01/08/2026: "nhìn danh sách trùng màu thì dò hàng ngang rất khó" ⇒ hàng chẵn và lẻ
   phải khác màu, áp cho CẢ bản hiện đại và MỌI bảng làm sau này.
   · Chỉ phần NỘI DUNG (tbody) — đầu bảng giữ nguyên.
   · `:not(.table-striped)`: 18 bảng trong app đã tự khai sọc của Bootstrap; Bootstrap tô hàng LẺ bằng
     lớp phủ box-shadow, mình tô hàng CHẴN bằng nền — chồng cả hai thì mọi hàng đều bị tô, hết phân biệt.
   · `:not([class*="table-"])`: chừa hàng đã tô màu nghiệp vụ (đủ công / vắng / nghỉ phép…), sọc đè lên
     là mất ý nghĩa màu.
   · ⚠ ĐỢT 359p — chừa CẢ CẤP Ô `> *:not([class*="table-"])`: bảng công tháng tô màu nghiệp vụ theo
     TỪNG Ô (không phải hàng) — luật cũ chỉ chừa hàng nên Ô vắng/đủ công ở HÀNG CHẴN bị sọc đè mất màu
     (lỗi thật anh Tiến chụp 07/08: "vắng có ô tô đỏ có ô không tô, đủ 2 buổi vẫn không xanh" — dòng lẻ
     hồng, dòng chẵn trắng xen kẽ đều tăm tắp).
   · Dùng rgba (không phải mã màu đặc) để sọc ăn theo nền của thẻ chứa bảng, không tạo mảng lệch màu.
   · Giao diện CỔ ĐIỂN có luật riêng đậm hơn ở co-dien.css (trọng số cao hơn nên thắng luật này). */
.table:not(.table-striped) > tbody > tr:not([class*="table-"]):nth-child(even) > *:not([class*="table-"]) {
    background-color: rgba(0, 0, 0, .028);
}

:root[data-theme="dark"] .table:not(.table-striped) > tbody > tr:not([class*="table-"]):nth-child(even) > *:not([class*="table-"]) {
    background-color: rgba(255, 255, 255, .045);
}

/* ===================== GIAO DIỆN HIỆN ĐẠI: SÁNG / TỐI =====================
   Bootstrap 5.1 chưa có data-bs-theme nên tự override theo thuộc tính data-theme trên <html>.
   Mặc định là CỔ ĐIỂN (đặt trong script <head> ở App.razor). Nút chuyển ở thanh trên (top-row).

   ⚠ ĐỢT 226 — BỎ NỀN XÁM ("gray"): anh Tiến chốt 03/08/2026 *"tinh gọn bớt chế độ xem gray mode
   đi cũng được vì không dùng"*. Nay còn 4 lựa chọn: sáng · tối · cổ điển · cổ điển tối.
   Máy nào lỡ đang lưu "gray" trong trình duyệt thì script ở App.razor tự chuyển sang "light". */

/* --- Nền TỐI --- */
:root[data-theme="dark"] {
    color-scheme: dark;
}
:root[data-theme="dark"] body {
    background-color: #1c1f24;
    color: #dde1e6;
}
/* :not(.btn) — KHÔNG đổi màu chữ của nút dạng thẻ <a class="btn ..."> (vd "Xin nghỉ phép" btn-info),
   nếu không màu link đè lên màu nút làm chữ mờ trên nền sáng.
   ⭐ ĐỢT 1215 (việc o404) — `:not(.ten-ban-ghi)` cùng một lẽ: tên bản ghi CỐ Ý mang màu chữ thường
   (anh Tiến 24/08/2026: *"chữ nhiệm vụ vẫn ở dạng link"*). ⛔ ĐỪNG bỏ dấu chừa này rồi trông vào luật
   `a.ten-ban-ghi` ở đầu tệp: luật ấy chỉ (0,1,1) còn dòng này (0,3,1) — bỏ ra là nền TỐI lại xanh
   liên kết trong khi nền SÁNG vẫn đúng, tức hỏng ở đúng một nửa số người dùng và **không câu lỗi nào**. */
:root[data-theme="dark"] a:not(.btn, .ten-ban-ghi),
:root[data-theme="dark"] .btn-link {
    color: #6bb3ff;
}
:root[data-theme="dark"] .card,
:root[data-theme="dark"] .list-group-item,
:root[data-theme="dark"] .modal-content,
:root[data-theme="dark"] .accordion-item {
    background-color: #262a31;
    color: #dde1e6;
    border-color: #3a3f47;
}
:root[data-theme="dark"] .card-header,
:root[data-theme="dark"] .card-footer {
    background-color: #2d323a;
    color: #dde1e6;
    border-color: #3a3f47;
}
:root[data-theme="dark"] .top-row {
    background-color: #2d323a !important;
    border-color: #3a3f47 !important;
}
:root[data-theme="dark"] .top-row a,
:root[data-theme="dark"] .top-row .btn-outline-secondary {
    color: #dde1e6;
}
:root[data-theme="dark"] .table {
    --bs-table-color: #dde1e6;
    --bs-table-bg: transparent;
    --bs-table-striped-color: #dde1e6;
    --bs-table-striped-bg: rgba(255, 255, 255, 0.04);
    --bs-table-hover-color: #fff;
    --bs-table-hover-bg: rgba(255, 255, 255, 0.07);
    --bs-table-border-color: #3a3f47;
    color: #dde1e6;
    border-color: #3a3f47;
}
:root[data-theme="dark"] .table > :not(caption) > * > * {
    border-color: #3a3f47;
}
:root[data-theme="dark"] .table-light,
:root[data-theme="dark"] .table-light > * {
    --bs-table-bg: #2d323a;
    --bs-table-color: #dde1e6;
    color: #dde1e6;
}
/* --- Nền TỐI: Ô/HÀNG BẢNG TÔ MÀU (xanh lá / vàng / đỏ / xanh dương) ---
   Bootstrap tô nền PASTEL SÁNG. Ở nền tối, chữ phụ trong ô (.text-muted, .form-text) đã bị ép
   sang xám nhạt nên nằm trên nền pastel sáng gần như KHÔNG ĐỌC ĐƯỢC (bảng duyệt đơn nghỉ phép,
   bảng chấm công theo tháng). Đổi sang tông TỐI cùng sắc màu → mọi chữ sáng vẫn đọc rõ,
   ý nghĩa màu (đủ công / nửa buổi / vắng / nghỉ phép) giữ nguyên. */
:root[data-theme="dark"] .table tr.table-success,
:root[data-theme="dark"] .table td.table-success {
    --bs-table-bg: #1f3b2c;
    --bs-table-color: #dde1e6;
    --bs-table-striped-bg: #24422f;
    --bs-table-striped-color: #dde1e6;
    color: #dde1e6;
}
:root[data-theme="dark"] .table tr.table-warning,
:root[data-theme="dark"] .table td.table-warning {
    --bs-table-bg: #4a3c12;
    --bs-table-color: #f1e4c0;
    --bs-table-striped-bg: #524217;
    --bs-table-striped-color: #f1e4c0;
    color: #f1e4c0;
}
:root[data-theme="dark"] .table tr.table-danger,
:root[data-theme="dark"] .table td.table-danger {
    --bs-table-bg: #4a2126;
    --bs-table-color: #f3d3d7;
    --bs-table-striped-bg: #52262b;
    --bs-table-striped-color: #f3d3d7;
    color: #f3d3d7;
}
:root[data-theme="dark"] .table tr.table-info,
:root[data-theme="dark"] .table td.table-info {
    --bs-table-bg: #123a44;
    --bs-table-color: #cfe9ef;
    --bs-table-striped-bg: #16424d;
    --bs-table-striped-color: #cfe9ef;
    color: #cfe9ef;
}
:root[data-theme="dark"] .table tr.table-secondary,
:root[data-theme="dark"] .table td.table-secondary {
    --bs-table-bg: #2f343c;
    --bs-table-color: #c7ccd2;
    --bs-table-striped-bg: #343a43;
    --bs-table-striped-color: #c7ccd2;
    color: #c7ccd2;
}
/* ── ⭐ ĐỢT 955 (việc o155) — Ô NGÀY "K": NGHỈ PHÉP ĐÃ VƯỢT HẠN MỨC PHÉP NĂM ──────────────────
   🔒 Anh Tiến chốt 21/08/2026, phương án (b): *"K nhưng TÔ MÀU KHÁC"* + rê chuột hiện lý do.
      Bảng lương vẫn đọc là "K" (nghỉ không lương), còn người chấm công nhìn màu là biết đây là nghỉ
      CÓ xin phép — khác hẳn người nghỉ không lương mà không xin phép.

   🔑 VÌ SAO LÀ MỘT LỚP RIÊNG chứ không mượn một biến thể `table-*` của Bootstrap: bốn màn hình cùng
      vẽ ô này (bảng công theo tháng · hai khối ở trang chủ · màn hình tính lương) và tất cả đều nằm
      trong `.table`. Mượn `table-primary` là màu ấy đã mang nghĩa khác ở bảng khác; khai riêng thì
      MỘT chỗ sửa là cả bốn màn hình theo, không nơi nào tụt lại.

   ⛔ TÔNG TÍM là cố ý: phải tách được bằng mắt khỏi `table-info` (xanh — nghỉ phép trong hạn mức)
      đứng NGAY CẠNH nó trên cùng một hàng, và khỏi `table-secondary` (xám — không tính công /
      ngoài dữ liệu) lẫn `table-danger` (đỏ — vắng). Cùng bảng màu với ô Excel `MauPhepVuot`
      (#E4D7F5) để tờ giấy in ra và màn hình nói cùng một chuyện.

   🔴 **TÊN LỚP BẮT BUỘC MANG CHUỖI `table-` — ⛔ đừng đổi thành `o-…` cho "đúng lối đặt tên của app".**
      Luật SỌC HÀNG CHẴN ở ngay trên (dòng ~156) chừa ô đã tô màu nghiệp vụ bằng phép
      `> *:not([class*="table-"])`. Tên không mang chuỗi đó thì **ô ở HÀNG CHẴN bị sọc đè mất màu**, hàng
      lẻ vẫn đúng ⇒ nhìn bảng thấy màu **xen kẽ đều tăm tắp** và rất dễ tưởng là dữ liệu khác nhau.
      📏 Đo sống 21/08/2026 trên bản đang chạy: bản đầu đặt tên `o-phep-vuot` ⇒ ô 30/07 của một người
      ra `rgba(0,0,0,.028)` (xám sọc) trong khi `--bs-table-bg` đã đúng `#e4d7f5` — **biến đặt đúng mà
      không được vẽ**. Đây đúng lỗi thật anh Tiến chụp 07/08 mà đợt 359p đã đi chữa một lần.
      🚧 Hàng rào canh: `OVuotPhepMoiNoiVeTests.Lop_mau_o_vuot_phep_phai_qua_duoc_luat_soc_hang_chan`.

   ⚠ Đặt `--bs-table-bg` chứ ⛔ ĐỪNG đặt `background-color` trần: Bootstrap 5.3 dựng nền ô bảng bằng
      `box-shadow: inset 0 0 0 9999px var(--bs-table-bg-type, var(--bs-table-bg))`, nên một luật
      `background-color` ở độ đặc hiệu thấp hơn sẽ bị lớp bóng đó phủ kín và KHÔNG hiện ra —
      hỏng lặng lẽ, đúng họ bài học đợt 838/840. */
.table tr.table-phep-vuot,
.table td.table-phep-vuot {
    --bs-table-bg: #e4d7f5;
    --bs-table-color: #3d2a5c;
    --bs-table-striped-bg: #ded0f1;
    --bs-table-striped-color: #3d2a5c;
    color: #3d2a5c;
}
:root[data-theme="dark"] .table tr.table-phep-vuot,
:root[data-theme="dark"] .table td.table-phep-vuot {
    --bs-table-bg: #372a4d;
    --bs-table-color: #ddd0f2;
    --bs-table-striped-bg: #3d2f55;
    --bs-table-striped-color: #ddd0f2;
    color: #ddd0f2;
}

/* ═══════════ ĐỢT 1201 (việc o386) — Ô "P" RƠI VÀO NGÀY NGHỈ: BẢN NHẠT CỦA MÀU NGHỈ PHÉP ═══════════
   Đơn nghỉ phép đã duyệt được bung ra TỪNG NGÀY LỊCH nên ô ngày tô "P" cho cả T7/CN/lễ, trong khi cột
   "Nghỉ phép" · huy hiệu · quỹ phép năm đều lọc thêm vế `!NgayNghi[i]`. Đo sống 24/08/2026 (mã 10515,
   đơn 20→24/08): 5 ô "P" liền nhau mà cột "Nghỉ phép" in 24 giờ = 3 ngày.
   🔒 Anh Tiến chốt 24/08/2026: *"Giữ P, chỉ sửa màn hình"* ⇒ chữ trong ô vẫn là "P", chỉ tô NHẠT đi.

   ⛔ MÀU KHÔNG PHẢI KÊNH DUY NHẤT — ô còn mang dấu nhỏ "N" / "L" (LuatPhepNgayNghi) và câu `title`.
      Người mù màu và bản in đen trắng không đọc được sắc độ, mà đây đúng là bảng hay bị in ra giấy.

   ⛔ TÊN LỚP BẮT BUỘC MANG CHUỖI `table-` — luật SỌC HÀNG CHẴN ở trên chừa ô đã tô màu nghiệp vụ bằng
      `> *:not([class*="table-"])`; tên không mang chuỗi đó thì ô ở HÀNG CHẴN bị sọc đè mất màu, hàng lẻ
      vẫn đúng (đo sống đợt 1113 khi đặt tên `o-phep-vuot`). Hằng số tên lớp khai ở
      `LuatPhepNgayNghi.LopMauNgayNghi`, có hàng rào đối chiếu.

   ⚠ Đặt `--bs-table-bg` chứ ⛔ ĐỪNG đặt `background-color` trần — Bootstrap 5.3 dựng nền ô bảng bằng
      `box-shadow: inset 0 0 0 9999px var(--bs-table-bg-type, var(--bs-table-bg))` nên một luật
      `background-color` ở độ đặc hiệu thấp hơn bị lớp bóng phủ kín và KHÔNG hiện ra (bài học 838/840).

   📏 Tương phản đo tay: nền sáng 7,2:1 · nền tối 8,4:1 — cả hai qua ngưỡng 4,5:1. */
.table tr.table-phep-ngay-nghi,
.table td.table-phep-ngay-nghi {
    --bs-table-bg: #e3f4f8;
    --bs-table-color: #33555c;
    --bs-table-striped-bg: #dceff4;
    --bs-table-striped-color: #33555c;
    color: #33555c;
}
:root[data-theme="dark"] .table tr.table-phep-ngay-nghi,
:root[data-theme="dark"] .table td.table-phep-ngay-nghi {
    --bs-table-bg: #0f2a30;
    --bs-table-color: #a9c6cd;
    --bs-table-striped-bg: #133238;
    --bs-table-striped-color: #a9c6cd;
    color: #a9c6cd;
}

/* ⚠ ĐỢT 430 — `table-primary` BỊ BỎ SÓT ở lượt trước (đã có success/warning/danger/info/secondary).
   Đo thật ở /nuoc, bảng cảnh báo: ô `.text-danger` (chữ hồng nhạt của nền tối) nằm trong hàng
   `table-primary` còn nguyên nền pastel #cfe2ff ⇒ **1,39:1** — gần như không đọc được, và dính CẢ HAI
   nền tối. Bài học: liệt kê biến thể màu thì phải liệt ĐỦ BỘ, thiếu một cái là nó nằm im chờ trang nào
   dùng tới. Nay có test cưỡng chế đối chiếu đủ 6 biến thể. */
:root[data-theme="dark"] .table tr.table-primary,
:root[data-theme="dark"] .table td.table-primary {
    --bs-table-bg: #17314f;
    --bs-table-color: #cfe0f5;
    --bs-table-striped-bg: #1b3757;
    --bs-table-striped-color: #cfe0f5;
    color: #cfe0f5;
}
/* 🔤 ĐỢT 831 (việc o73) — VÌ SAO MỌI LUẬT CHỮ PHỤ ĐỀU VIẾT `:is(.text-muted, .text-body-secondary)`.
   Anh Tiến chốt (h11 câu Q4, phương án b) đổi hết `.text-muted` sang `.text-body-secondary` — lớp
   `.text-muted` đã bị Bootstrap 5.3 khai tử. Nhưng **đổi tên trần là mất màu**: 6 nhóm luật dưới đây
   nhắm ĐÍCH DANH `.text-muted` bằng `!important`, nên đổi tên ở `.razor` mà không đổi ở đây thì 869
   chỗ rơi khỏi luật của app và lãnh màu mặc định của Bootstrap.
   📏 ĐO trên trang thăm dò trước khi sửa (chốt ngược `d-flex` ✓ · khung 1400px):
       `.text-muted`           sáng `rgb(92,99,106)`  · tối `rgb(154,160,168)`   ← app quyết, đã đo tương phản
       `.text-body-secondary`  sáng `rgb(33,37,41)`   · tối `rgb(221,225,230)`   ← **y hệt màu THÂN, không hề mờ**
   ⇒ Giữ **CẢ HAI TÊN** trong mọi bộ chọn ⇒ màu **không đổi một điểm ảnh nào**, mà tên lớp vẫn hiện
   đại hoá được. Anh yêu cầu đổi TÊN LỚP, không yêu cầu đổi MÀU — nên đây là đường giữ đúng cả hai.
   ⓘ `:is(a, b)` lấy độ đặc hiệu của thành phần đặc hiệu NHẤT; hai bên đều là một lớp nên độ đặc hiệu
     **không đổi** so với bản cũ ⇒ không xáo trộn thứ tự thắng/thua của cascade.
   ⚠ Còn `.text-muted` trong bộ chọn là CỐ Ý, ⛔ đừng "dọn cho gọn": Bootstrap và mã cũ vẫn có thể
     sinh ra lớp đó, và giữ lại là đường lui nếu phải hoàn nguyên lượt đổi tên.
   ⛔ `co-dien.css` CỐ Ý không sửa — sau đợt 822 tệp đó **không còn được nạp** (chỉ `co-dien-form-in.css`
     nạp, và tệp ấy không có luật chữ phụ nào), nên luật `.text-muted` trong đó đã là mã chết. */

/* Chữ phụ NẰM TRONG ô đã tô màu: dùng màu của ô (kế thừa) thay vì xám nhạt toàn cục */
:root[data-theme="dark"] .table tr[class*="table-"] :is(.text-muted, .text-body-secondary),
:root[data-theme="dark"] .table td[class*="table-"] :is(.text-muted, .text-body-secondary) {
    color: inherit !important;
    opacity: .75;
}

/* ⛔ ĐỢT 659 — VIỀN Ô NHẬP NỀN TỐI: `#4a5058` chỉ được **1,58:1** với lòng ô `#2d323a` (đo 14/08/2026),
   dưới xa ngưỡng **3:1** của chỉ báo phi văn bản ⇒ người dùng khó thấy "chỗ nào là ô để gõ".

   ⚠ ĐỢT 634 ĐÃ VÁ NHƯNG **CHỈ VÁ MỘT NỬA**: bản vá đó (mục 13c, luật `.bg-light .form-control…`) nâng viền
   lên `#797f88` = 3,19:1 nhưng **chỉ áp cho ô nhập nằm trong khối `.bg-light`/`.bg-white`** — vì lúc ấy
   đang chữa đúng ca "ô nhập lọt trong khối phụ cùng màu". Mọi ô nhập đứng trên **thẻ thường / thân trang**
   vẫn giữ `#4a5058`, tức vẫn đúng con số 1,58 mà chính chú thích của đợt 634 gọi là *"trước khi vá"*.
   ⇒ Nay nâng thẳng ở luật GỐC: cả app dùng một màu viền đã đạt, và luật của đợt 634 thành thừa nhưng vô
     hại (cùng giá trị, giữ lại vì nó còn mang phần giải thích ca "khối phụ cùng màu").
   📏 `#797f88` đo trên ba nền mà ô nhập có thể nằm lên: lòng ô `#2d323a` **3,19:1** · nền thẻ `#262a31`
     **3,58:1** · thân trang `#1c1f24` **4,11:1** — đạt cả ba.
   ⓘ Bản cổ điển tối không dính (đã dùng `#7b838d` = 3,38:1).

   🔴 ĐỢT 840 (việc o95) — MÀU GIỮ NGUYÊN, **CÁCH VIẾT** ĐỔI: `border-color` → `--bs-border-color`.
   Bản trước đặt `border-color` **longhand** ở bộ chọn (0,3,0), trong khi Bootstrap 5.3 báo lỗi ô nhập
   bằng `.form-control.is-invalid { border-color: var(--bs-form-invalid-border-color) }` chỉ (0,2,0)
   ⇒ luật của app **thắng cả viền đỏ**, ô sai vẫn viền xám. Đúng khuôn chữa mà đợt 838 đã dựng cho nền
   SÁNG (mục 3c, đoạn 🔑) — nay chép sang nền tối: đặt **biến** thì longhand của mọi trạng thái
   (`is-invalid` · `is-valid` · `:focus`) vẫn thắng như cũ, chỉ viền THƯỜNG mới lấy màu của app.
   📏 ĐO SỐNG 17/08/2026 (bản tĩnh `--chu-de dark` cổng 5321, chốt ngược `d-flex` ✓):
       trước: `.form-control.is-invalid` ra `rgb(121,127,136)` = `#797f88` — **đúng bằng viền ô thường**
       sau  : ra `rgb(220,53,69)` = `#dc3545`, tức token `--bs-form-invalid-border-color` của Bootstrap
       Đo cả 5 biến thể: ô nhập · `.form-select` · ô trong `.bg-light` · ô trong `.input-group` · nhãn dính.
   ⓘ HÔM NAY CHƯA CẮN: app dùng `.is-invalid` của Bootstrap **0 chỗ** trong `.razor`/`.cs` (xác thực của
     Blazor đi bằng `.valid`/`.invalid` + `outline`). Đây là chốt **PHÒNG** — và chính vì chưa cắn nên
     viết sai ở đây sẽ không ai phát hiện cho tới lúc có người thêm ô nhập có kiểm tra dữ liệu đầu tiên.
   ⛔ ĐỪNG "chữa" bằng `:not(.is-invalid)`: thêm một lớp là (0,4,0), lúc ấy nó vượt luôn luật thẻ đăng
     nhập `.dn-nen .dn-the .form-control` (0,4,0 và nằm SAU) rồi thắng/thua theo thứ tự — vỡ lặng lẽ.
   🚧 Hàng rào canh cả MÀU lẫn CÁCH VIẾT: `VienONhapTuongPhanTests` (đợt 838, mở rộng ở đợt 840). */
:root[data-theme="dark"] .form-control,
:root[data-theme="dark"] .form-select {
    background-color: #2d323a;
    color: #dde1e6;
    --bs-border-color: #797f88;
}
:root[data-theme="dark"] .form-control::placeholder { color: #8a9099; }
:root[data-theme="dark"] :is(.text-muted, .text-body-secondary),
:root[data-theme="dark"] .form-text { color: #9aa0a8 !important; }
:root[data-theme="dark"] .btn-outline-secondary,
:root[data-theme="dark"] .btn-outline-dark {
    color: #c7ccd2;
    border-color: #6a7078;
}
:root[data-theme="dark"] .border,
:root[data-theme="dark"] .border-top,
:root[data-theme="dark"] .border-bottom,
:root[data-theme="dark"] hr {
    border-color: #3a3f47 !important;
}
:root[data-theme="dark"] .nav-tabs { border-color: #3a3f47; }
:root[data-theme="dark"] .nav-tabs .nav-link.active {
    background-color: #262a31;
    color: #dde1e6;
    border-color: #3a3f47 #3a3f47 #262a31;
}
:root[data-theme="dark"] code { color: #ff9bb3; }

/* ══════════════════════════════════════════════════════════════════════════════════════════
   VÁ LỖI HUY HIỆU CHỮ TRẮNG TRÊN NỀN TRẮNG (đợt 205)

   App đang dùng Bootstrap v5.1.0, nhưng lớp `.text-bg-*` chỉ có từ Bootstrap 5.2 trở đi.
   Trong khi đó `.badge` của 5.1 tự đặt `color:#fff` mà KHÔNG đặt nền.
   ⇒ 114 chỗ dùng `text-bg-*` trong app là CHỮ TRẮNG TRÊN NỀN TRẮNG ở giao diện SÁNG — vô hình.

   Vì sao lỗi này sống lâu: ở giao diện TỐI thì trắng trên nền đen vẫn đọc được (16,5:1), nên ai
   dùng bản tối không thấy gì bất thường. Chỉ lộ ra khi xem bản sáng.

   Cặp màu dưới đây lấy ĐÚNG theo Bootstrap 5.3 chính thức, để sau này nâng Bootstrap lên 5.3
   thì giao diện KHÔNG đổi. Mọi cặp đều đạt tỷ lệ tương phản ≥ 4,5:1.
   ══════════════════════════════════════════════════════════════════════════════════════════ */
/* ══════════════════════════════════════════════════════════════════════════════════════════
   CHỮ MÀU CỐ ĐỊNH CỦA BOOTSTRAP 5.1 — CHẾT Ở NỀN TỐI (vá đợt 228, anh Tiến báo 03/08)

   `.text-body` của Bootstrap 5.1 là `color:#212529` GẮN CỨNG, không đổi theo giao diện.
   Trang Tổng quan dùng nó cho TÊN NGƯỜI (đợt 214 làm tên nổi bật) ⇒ ở nền tối thành
   **chữ đen trên nền đen — mất hẳn tên**, mà build vẫn sạch và bản sáng vẫn đẹp.
   Cùng họ với lỗi `.text-bg-*` chữ trắng trên nền trắng đã vá ở đợt 205.
   ⇒ Vá MỘT CHỖ cho cả app: mọi lớp chữ "màu tối cố định" phải theo giao diện.
   (Giao diện CỔ ĐIỂN TỐI có bản riêng ở co-dien.css mục 15.)
   ══════════════════════════════════════════════════════════════════════════════════════════ */
:root[data-theme="dark"] .text-body,
:root[data-theme="dark"] .text-dark,
:root[data-theme="dark"] .text-black {
    color: #dde1e6 !important;
}

/* NÚT PHỤ (viền xám) ở giao diện SÁNG: màu chữ #6c757d của Bootstrap chỉ đạt 4,4:1 trên nền thanh
   công cụ — đậm lại thành 5,6:1 (đợt 226, anh dặn chú ý tương phản của nút).
   Giao diện tối và cổ điển có màu riêng, trọng số cao hơn nên không bị luật này đụng tới. */
.btn-outline-secondary {
    color: #565e64;
}

.text-bg-primary   { color: #fff !important; background-color: #0d6efd !important; }
.text-bg-secondary { color: #fff !important; background-color: #6c757d !important; }
.text-bg-success   { color: #fff !important; background-color: #198754 !important; }
.text-bg-danger    { color: #fff !important; background-color: #dc3545 !important; }
.text-bg-warning   { color: #000 !important; background-color: #ffc107 !important; }
.text-bg-info      { color: #000 !important; background-color: #0dcaf0 !important; }
.text-bg-light     { color: #000 !important; background-color: #f8f9fa !important; }
.text-bg-dark      { color: #fff !important; background-color: #212529 !important; }

/* ═══ ĐỢT 519 — THANH TIẾN ĐỘ MÀU VÀNG: ĐẬM LẠI ═══
   Máng `.progress` của Bootstrap là #e9ecef và KHÔNG đổi theo giao diện — đo sống ở cả 4 giao diện đều
   ra cùng một con số. Thanh #ffc107 nằm trên máng đó chỉ được **1,37:1**, dưới ngưỡng 3:1 cho chỉ báo
   PHI VĂN BẢN ⇒ nhìn vào không biết thanh chạy tới đâu, mà đây lại đúng là thứ duy nhất thanh này nói.
   #997404 đo được **3,65:1** — và đây KHÔNG phải màu mới: app đã dùng nó cho `.btn-outline-warning`
   giao diện sáng ở cuối file.
   ⓘ Sửa luôn một lỗi thứ hai đi kèm: `.progress-bar` của Bootstrap có `color:#fff`, nên nhãn chữ trắng
   trên nền #ffc107 chỉ được 1,9:1 (đang dùng thật ở /quan-tri/dung-luong: "KHÔNG ĐO ĐƯỢC — kiểm kết nối
   kho này"). Nền đậm lại thì nhãn đó lên 4,4:1.
   ⛔ Khai HẸP `.progress-bar.bg-warning` — KHÔNG đụng `.bg-warning` nói chung: huy hiệu vàng chữ đen
   đang đạt chuẩn, đổi nền của chúng là hỏng chỗ đang đúng.
   Đang dùng ở: BaoCaoNangSuat · DungLuongPage · KhoiKhoTrinhChieu. */
.progress-bar.bg-warning { background-color: #997404 !important; }

/* ══════════════════════════════════════════════════════════════════════════════════════════
   BỐ CỤC THANH NAV — DÙNG CHUNG CHO CẢ 4 GIAO DIỆN (đợt 226, anh Tiến chốt 03/08/2026)

   Anh chỉ đạo: *"bố trí nút, menu, thanh bar mới cũng áp dụng với bản hiện đại"*.
   Đợt 223 dựng bố cục mới cho riêng giao diện CỔ ĐIỂN — rail icon phân hệ trôi nổi bên trái +
   thanh ngang chỉ còn ☰ Menu và hàng TAB của trang. Nay bố cục đó thành bố cục CHUNG:

     · KHUNG (chỗ đứng · kích thước · ẩn/hiện · chuyển động) = khối này, KHÔNG có tiền tố data-theme
       ⇒ sửa một chỗ là cả 4 giao diện đổi theo.
     · MÀU SẮC = mỗi giao diện một bộ: sáng/xám ngay dưới đây · tối ở khối :root[data-theme="dark"]
       cuối mục này · cổ điển ở co-dien.css (nạp SAU + trọng số cao hơn nên luôn thắng).

   ⚠ Thêm luật KHUNG mới thì viết vào đây, ĐỪNG chép sang co-dien.css — chép là hai bên lệch nhau.
   Component vẫn ở Components/CoDien/ (MenuNgangCoDien.razor + RailPhanHe.razor) vì sinh ra từ đợt
   cổ điển; nay dùng chung — xem Components/CoDien/CLAUDE.md.
   ══════════════════════════════════════════════════════════════════════════════════════════ */

:root {
    /* Nuôi vị trí DÍNH ĐẦU MÀN HÌNH của thanh nav và rail. Giao diện cổ điển chữ nhỏ hơn nên
       thanh trên thấp hơn ⇒ đè lại --cao-thanh-tren trong co-dien.css. */
    --cao-thanh-tren: 3.5rem;   /* = min-height của .top-row */
    --cao-thanh-nav: 2.5rem;    /* = chiều cao thanh nav ngang */

    /* ⭐ ĐỢT 1155 — KHUNG ẢNH ĐẠI DIỆN: TRÒN hay VUÔNG (anh Tiến 24/08/2026: *"icon avatar của tài
       khoản cho phép chọn khung vuông hoặc khung tròn"*).
       🔑 Vì sao là BIẾN toàn cục chứ không phải một lớp gắn lên từng thẻ ảnh: ảnh đại diện xuất hiện ở
         BA cỡ tại HAI nơi (nút 34px trên thanh nav · ảnh 64px trong thẻ tài khoản · ảnh 96px ở trang
         /anh-dai-dien), mỗi nơi lại có thêm hai lớp trang trí `::before`/`::after` phải bo cùng bán
         kính. Một biến thì đổi một chỗ là cả bộ theo; gắn lớp là bảy chỗ phải nhớ.
       ⚠ Lựa chọn nằm ở `localStorage['tknb-khung-anh']`, được khối nội tuyến đầu App.razor đặt thành
         thuộc tính `data-khung-anh` trên <html> TRƯỚC khi vẽ — trục thứ ba, độc lập với `data-theme`
         và `data-bo-mau` (⛔ đừng gộp: người chọn khung vuông vẫn được đổi sáng/tối tùy ý).
       📏 26% chứ không 0: góc vuông tuyệt đối trông thô cạnh khối cầu 3D và cạnh chuông thông báo bo
         tròn ngay bên. 26% cho dáng "squircle" — vuông rõ ràng mà vẫn mềm. */
    --anh-dd-bo: 50%;
}

:root[data-khung-anh="vuong"] {
    --anh-dd-bo: 26%;
}

/* Lớp dùng chung cho mọi thẻ <img> ảnh đại diện NGOÀI component `TheTaiKhoan` (component đó có CSS
   phạm vi riêng, đọc cùng biến). ⛔ ĐỪNG quay lại dùng `rounded-circle` của Bootstrap cho ảnh đại
   diện: lớp đó đóng đinh 50% nên nút chọn khung không kéo được — trước đợt 1155 trang /anh-dai-dien
   dùng đúng lớp ấy. */
.anh-dd-khung {
    border-radius: var(--anh-dd-bo, 50%);
}

/* ───── 1. THANH NAV NGANG: nút ☰ Menu + hàng tab của trang đang mở ───── */
.menu-co-dien {
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    position: sticky;
    top: var(--cao-thanh-tren);   /* nằm ngay dưới thanh trên (cũng đang dính đầu màn hình) */
    z-index: 880;
    padding: 2px 6px 0 6px;
    background-color: #f7f7f7;   /* ⭐ ĐỢT 887 hoàn nguyên 882 — anh Tiến 19/08 tối: "lấy lại thanh nav cũ" (nền xám) */
    border-bottom: 1px solid #d6d5d5;
}

/* LOGO trên thanh nav (đợt 373 — anh: "logo làm nổi bật, không bị lẫn màu gốc"): logo chuẩn navy+cam
   luôn nằm trong CHIP TRẮNG bo góc nên nổi trên MỌI giao diện; đè logo-petrolimex.svg là tự đổi. */
/* ⚠ GIỮ NGUYÊN TỶ LỆ logo (anh dặn 08/08): flex-shrink 0 để thanh chật không bóp chip,
   width auto + max-width none để không luật nào ép bề ngang làm méo hình. */
.menu-co-dien .mcd-logo { display: inline-flex; align-items: center; padding: 3px 6px 3px 2px;
    flex-shrink: 0; }
/* ⚠ ĐỢT 386b — chip logo CHỈ còn lo BỐ CỤC. Nền/viền/bo góc/bóng nay do lớp dùng chung
   `.the-bang-ten` (+ `.the-bang-ten-sang` giữ nền trắng — đợt 887 lấy lại chip trắng của đợt 373) lo — xem khối "BẢNG TÊN ĐEO ÁO" cuối file.
   Trước đó luật này tự đặt `background/border/border-radius/box-shadow`, mà `.menu-co-dien
   .mcd-logo-chip` (0,2,0) THẮNG `.the-bang-ten` (0,1,0) ⇒ chip giữ nguyên bóng phẳng cũ và vẫn
   trông LÕM đúng như anh Tiến phản ánh, dù lớp mới đã gắn vào. */
.menu-co-dien .mcd-logo-chip { display: inline-flex; align-items: center; padding: 2px 7px;
    flex-shrink: 0; }
/* ⭐ ĐỢT 661 — PHÓNG TO LOGO (anh Tiến 14/08/2026: *"em phóng to logo petrolimex lên chút nữa nhé, hơi
   nhỏ"*). 22px → 30px, tức +36%.
   ⚠ VÌ SAO DỪNG Ở 30px chứ không to hơn: thanh nav cao **tối thiểu 3,5rem = 56px**, chip logo còn đệm
     2px trên/dưới + viền. Logo 30px ⇒ chip ~36px, còn dư chỗ cho nút 9 chấm (40px) đứng cạnh mà KHÔNG
     đẩy thanh cao thêm — mà thanh cao thêm là đẩy tụt mọi nội dung của mọi trang xuống.
   ⚠ Chỉ đổi CHIỀU CAO, giữ `width: auto` — logo có tỷ lệ riêng, ép cả hai chiều là méo chữ thương hiệu
     (anh Tiến dặn giữ nguyên tỷ lệ, 08/08). */
.menu-co-dien .mcd-logo-chip img { height: 30px; width: auto; max-width: none; display: block; }

/* ═══ ⭐ ĐỢT 893 (việc o110) — LOGO 3D NỔI BẬT + HIỆU ỨNG (anh Tiến 20/08/2026: "anh vẫn muốn logo 3D
   nổi bật và có hiệu ứng") ═══
   Ba lớp, CHỈ hai giao diện hiện đại (cổ điển đóng băng — quy tắc #20, bọc :not() từng luật):
   ① BÓNG NỔI HƠN THẺ THƯỜNG — chip logo cố ý đậm hơn `.the-bang-ten` một nấc (viền navy rõ + bóng đổ
     sâu 8px) để nó là thứ NỔI NHẤT trên thanh. ⚠ Đây là ghi đè CÓ CHỦ ĐÍCH lên phân công của đợt 386b
     ("nền/viền/bóng do .the-bang-ten lo"): đợt đó chữa bệnh chip-tự-đặt-bóng-PHẲNG đè mất khối 3D;
     ở đây đặt bóng 3D ĐẬM HƠN chứ không phẳng đi — đúng chiều anh muốn.
   ② VỆT SÁNG QUÉT QUA (gloss sweep) mỗi ~7 giây — hiệu ứng "sống" mà không chớp nháy; máy khai giảm
     chuyển động tự tắt nhờ khối prefers-reduced-motion toàn cục (mục 10 đợt 381, * !important).
   ③ RÊ CHUỘT: chip nâng lên + phóng nhẹ 3% — cùng ngôn ngữ vật lý với .the-bang-ten:hover. */
:root:not([data-theme^="co-dien"]) .menu-co-dien .mcd-logo-chip {
    position: relative;
    overflow: hidden;                       /* vệt sáng không tràn ra ngoài góc bo */
    border-color: rgba(3, 4, 94, .38) !important;   /* navy đo từ điểm ảnh logo (#03045e) */
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .95),
        inset 0 -1px 0 rgba(0, 23, 68, .12),
        0 1px 1px rgba(0, 23, 68, .12),
        0 4px 8px rgba(0, 23, 68, .22);
    transition: transform .15s ease, box-shadow .15s ease;
}

:root:not([data-theme^="co-dien"]) .menu-co-dien .mcd-logo-chip::after {
    content: "";
    position: absolute;
    top: -25%;
    bottom: -25%;
    left: -45%;
    width: 34%;
    background: linear-gradient(105deg, transparent 0%, rgba(255, 255, 255, .9) 50%, transparent 100%);
    transform: skewX(-18deg);
    pointer-events: none;
    animation: quet-sang-logo 7s ease-in-out infinite;
}

/* 80% thời gian vệt nằm im ngoài mép trái (opacity 0) rồi lướt qua trong ~1,4s — nhịp thưa cho đỡ
   gây xao nhãng ở thanh điều hướng người ta nhìn cả ngày. */
@keyframes quet-sang-logo {
    0%, 80% { left: -45%; opacity: 0; }
    84%     { opacity: 1; }
    100%    { left: 140%; opacity: 0; }
}

:root:not([data-theme^="co-dien"]) .menu-co-dien .mcd-logo:hover .mcd-logo-chip {
    transform: translateY(-1px) scale(1.03);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .95),
        inset 0 -1px 0 rgba(0, 23, 68, .12),
        0 2px 3px rgba(0, 23, 68, .16),
        0 7px 14px rgba(0, 23, 68, .28);
}

/* Một thẻ trên thanh (hiện chỉ có nút ☰ Menu) */
.menu-co-dien .mcd-muc {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    padding: .28rem .75rem;
    margin-right: 1px;
    border: 1px solid transparent;
    border-bottom: 0;
    border-radius: .25rem .25rem 0 0;
    color: #212529;
    font-size: .875rem;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    list-style: none;          /* bỏ mũi tên mặc định của <summary> */
}

.menu-co-dien .mcd-muc::-webkit-details-marker {
    display: none;
}

.menu-co-dien .mcd-muc:hover {
    background-color: #e9ecef;
    border-color: #dee2e6;
}

/* Mục ĐANG XEM */
.menu-co-dien .mcd-dang-mo {
    background-color: #cfe2ff;
    border-color: #9ec5fe;
    color: #0a58ca;
    font-weight: 600;
}

.menu-co-dien .mcd-bieu { font-size: 1rem; }

.menu-co-dien .mcd-mui {
    margin-left: .2rem;
    font-size: .7rem;
    /* ĐỢT 386: #6c757d chỉ được 4,4:1 trên nền kính của thanh nav (rgba(247,247,247,.92)) — mà đây là
       chữ .7rem, cỡ nhỏ nhất trong app. #5c636a lên 5,7:1. */
    color: #5c636a;
}

/* Bảng menu đầy đủ thả xuống từ nút ☰ */
.menu-co-dien .mcd-nhom { position: relative; }

.menu-co-dien .mcd-tha {
    position: absolute;
    top: 100%;
    left: 0;
    min-width: 250px;
    padding: 2px;
    background-color: #fff;
    border: 1px solid #dee2e6;
    border-radius: .375rem;
    box-shadow: 0 6px 18px rgba(0, 0, 0, .18);
    z-index: 1000;
}

.menu-co-dien .mcd-tha a {
    display: flex;
    align-items: center;
    gap: .45rem;
    padding: .3rem .7rem;
    border: 1px solid transparent;
    border-radius: .25rem;
    color: #212529;
    font-size: .875rem;
    text-decoration: none;
    white-space: nowrap;
}

.menu-co-dien .mcd-tha a:hover { background-color: #e9ecef; }

.menu-co-dien .mcd-tha a.mcd-dang-mo {
    background-color: #cfe2ff;
    border-color: #9ec5fe;
    color: #0a58ca;
    font-weight: 600;
}

.menu-co-dien .mcd-bang-menu {
    max-height: 78vh;
    overflow-y: auto;
    min-width: 280px;
}

.menu-co-dien .mcd-nhom-tieude {
    display: flex;
    align-items: center;
    gap: .45rem;
    padding: .34rem .5rem .2rem .5rem;
    margin-top: 2px;
    border-top: 1px solid #e9ecef;
    color: #0a58ca;
    font-size: .875rem;
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
}

.menu-co-dien .mcd-bang-menu .mcd-muc-con { padding-left: 1.6rem; }

/* HÀNG TAB của trang do trang đẩy lên bằng SectionContent "thanh-tab-trang": bỏ lề dưới của
   Bootstrap cho khớp chiều cao thanh */
.menu-co-dien .nav {
    margin-bottom: 0 !important;
    align-items: center;
}

/* TAB CHƯA MỞ: màu xanh mặc định #0d6efd của Bootstrap nằm trên NỀN THANH (không phải nền trắng)
   nên tương phản tụt — đo thật: 4,2:1 ở nền sáng và 2,9:1 ở nền tối (dưới ngưỡng 4,5:1).
   Đậm màu lại cho đủ đọc: 5,6:1 (sáng) · 7,5:1 (tối). */
.menu-co-dien .nav-link {
    color: #0a58ca;
}

/* :not(.active) — tab ĐANG MỞ giữ chữ trắng của luật ngay dưới; thiếu dấu này thì chữ xanh nhạt
   nằm trên viên xanh đậm, tụt xuống 3,6:1 (đã đo, đã dính một lần) */
:root[data-theme="dark"] .menu-co-dien .nav-link:not(.active) {
    color: #9ec5fe;
}

/* TAB ĐANG MỞ = viên xanh ĐẬM chữ trắng (**6,44:1** — đo lại 14/08/2026; chú thích cũ ghi 5,4:1).
   ⛔ ĐÍNH CHÍNH 14/08/2026: câu cũ ghi *"#0d6efd chỉ được 3,7:1 với chữ trắng"* — **SAI ngữ cảnh**. Cặp
   #0d6efd/trắng đo ra **4,50:1** (chính tệp này ghi đúng 4,50 ở hai chỗ khác). Con số 3,67 là của #0d6efd
   trên **NỀN TỐI**, bị dán nhầm sang đây. Lý do đổi màu vẫn đứng vững — 4,50 chỉ vừa đủ cho chữ thường,
   mà đây là chữ nhỏ ở chỗ người dùng nhìn nhiều nhất — nhưng **lý do phải nói đúng con số của nó**.
   (Hai giao diện cổ điển có luật thẻ VÀNG riêng, trọng số cao hơn nên không bị luật này đụng.) */
.menu-co-dien .nav-link.active {
    background-color: #0a58ca;
    color: #fff;
}

/* Hàng tab đã dời LÊN THANH ⇒ bản render tại chỗ trong trang ẩn ở MỌI giao diện
   (cùng một RenderFragment render hai nơi — để cả hai là hiện đôi) */
.tab-trong-trang { display: none !important; }

/* ───── 2. RAIL ICON PHÂN HỆ — trôi nổi bên trái, rê vào mới hiện chữ ───── */
.rail-phan-he {
    display: flex;
    flex-direction: column;
    gap: 3px;
    position: fixed;
    left: 6px;
    top: calc(var(--cao-thanh-tren) + var(--cao-thanh-nav));
    z-index: 1035;
}

.rail-phan-he .rph-o { position: relative; }

/* Ô icon vuông 38px; rê vào thì NỞ RA hiện chữ (max-width chuyển động — thuần CSS, không JS,
   nên chạy cả khi trang render TĨNH) */
.rail-phan-he .rph-icon {
    display: flex;
    align-items: center;
    gap: .45rem;
    height: 38px;
    max-width: 38px;
    padding: 0 9px;
    overflow: hidden;
    white-space: nowrap;
    background-color: #fff;
    border: 1px solid #dee2e6;
    border-radius: .375rem;
    box-shadow: 0 2px 6px rgba(0, 0, 0, .12);
    color: #212529;
    font-size: .875rem;
    text-decoration: none;
    transition: max-width .18s ease;
}

.rail-phan-he .rph-o:hover .rph-icon {
    max-width: 260px;
    background-color: #e9ecef;
}

.rail-phan-he .rph-chu {
    opacity: 0;
    transition: opacity .18s ease;
}

.rail-phan-he .rph-o:hover .rph-chu { opacity: 1; }

.rail-phan-he .rph-dang-mo > .rph-icon,
.rail-phan-he .rph-icon.rph-dang-mo {
    background-color: #cfe2ff;
    border-color: #9ec5fe;
    color: #0a58ca;
    font-weight: 600;
}

/* Bảng MỤC CON của nhóm: hiện cạnh phải khi rê vào ô */
.rail-phan-he .rph-con {
    display: none;
    position: absolute;
    left: 100%;
    top: 0;
    margin-left: 2px;
    min-width: 230px;
    padding: 2px;
    background-color: #fff;
    border: 1px solid #dee2e6;
    border-radius: .375rem;
    box-shadow: 0 6px 18px rgba(0, 0, 0, .18);
    z-index: 1040;
}

.rail-phan-he .rph-o:hover .rph-con { display: block; }

.rail-phan-he .rph-con a {
    display: flex;
    align-items: center;
    gap: .45rem;
    padding: .3rem .7rem;
    border: 1px solid transparent;
    border-radius: .25rem;
    color: #212529;
    font-size: .875rem;
    text-decoration: none;
    white-space: nowrap;
}

.rail-phan-he .rph-con a:hover { background-color: #e9ecef; }

.rail-phan-he .rph-con a.rph-dang-mo {
    background-color: #cfe2ff;
    border-color: #9ec5fe;
    color: #0a58ca;
    font-weight: 600;
}

/* ⭐ ĐỢT 907 — THÂN TRANG CĂN GIỮA (anh Tiến 20/08/2026: *"nội dung của tất cả các trang đều căn giữa,
   không được lệch trái"*).

   ⛔ LUẬT CŨ Ở ĐÂY LÀ `article.content { padding-left: 56px !important }` — "nội dung né rail". Nó đã
      thành DI SẢN CHẾT từ đợt 645: rail (`Components/CoDien/RailPhanHe.razor`) không còn được dựng ở
      đâu cả (đo bằng grep: 0 lời gọi component trong toàn bộ .razor), nên 56px kia chỉ còn là một
      khoảng thụt đầu dòng vô cớ, và nó đẩy nội dung LỆCH HẲN SANG PHẢI 56px so với trục giữa. Đúng cái
      "lệch" anh nhìn thấy, chỉ khác chiều với chữ "lệch trái".

   🔑 CĂN GIỮA = CỘT NỘI DUNG CÓ BỀ RỘNG TRẦN + hai lề tự động. Không đặt trần thì `article` rộng bằng
      cả màn hình, mà các khối bên trong đều tự khai `max-width` (720 / 900 / 1100px) nên chúng bám mép
      TRÁI của một khung mênh mông — đó mới là gốc của "lệch trái". Đặt trần rồi thì cả cột nằm giữa,
      các khối bên trong nằm giữa theo.

   ⛔ CỐ Ý KHÔNG khai `max-width` + `margin-inline` ở đây: phép CĂN GIỮA CỘT vốn ĐÃ CÓ từ 30/07 và nó
      nằm ở `Components/Layout/MainLayout.razor.css` (`.content { max-width: 1400px; margin-inline: auto }`).
      Đó là CSS scoped nên mang thêm thuộc tính `[b-xxxxx]` ⇒ độ đặc hiệu cao hơn hẳn luật ở tệp này và
      luôn thắng. Khai lại ở đây là dựng HAI SỔ nói về cùng một con số: sửa bản này thì không thấy gì
      đổi, người sửa sau lại đi tìm nguyên nhân ở chỗ khác. Đo sống 20/08 (khung 1900px, chốt ngược
      `d-flex` ✓): cột nội dung rộng 1400px, lề trái 243px / lề phải 258px (chênh đúng bề rộng thanh
      cuộn) ⇒ đã căn giữa thật.
   ⚠ Giữ `!important`: `px-4` của Bootstrap có `!important`, không thắng nó thì luật này không nói được
      gì về lề trái/phải.
   ⓘ CÒN LẠI (đã báo anh Tiến, chờ anh chốt): nhiều KHỐI trong thân trang tự khai `max-width: 720/900px`
      nên chúng bám mép TRÁI của cột — đó mới là thứ đọc ra thành "lệch trái" trên màn hình rộng. Đo
      20/08: **343 chỗ khai / 110 tệp .razor**. ⛔ Đừng quét hàng loạt: phần lớn là cố ý (khung chữ hẹp
      cho dễ đọc), sửa cả loạt đúng là kiểu thay đổi mà quy tắc #18 xếp rủi ro cao nhất. */
article.content {
    padding-left: 1.5rem !important;
    padding-right: 1.5rem !important;
}

/* ───── 3. THANH TRÊN: bỏ nút ☰ ngăn kéo (cụm nút nhanh ĐÃ HIỆN LẠI từ đợt 386b) ─────
   Lối đi tới mọi phân hệ nay nằm ở RAIL bên trái và nút ☰ Menu trên thanh nav ⇒ giữ thêm ngăn kéo
   là HAI LỐI cho cùng một việc. Ngăn kéo (.sidebar + NavMenu.razor) vẫn còn nguyên trong mã và vẫn
   là nguồn đối chiếu của MenuDongBoTests — chỉ là không còn nút bấm mở.
   ⚠ ĐỢT 386b: `.top-row .nut-nhanh` KHÔNG còn bị ẩn. Đợt 226 ẩn nó vì thanh chật (khi ấy có TỚI HAI
   thanh); nay gom còn một thanh, anh Tiến yêu cầu để lại 🏠 🕐 💧 trong bố cục mới. */
.top-row label.btn[for="menu-mo"] {
    display: none !important;
}

/* ══ MỘT THANH DUY NHẤT (đợt 386b) ══
   Bố cục: [logo] [☰ Menu ▾] [nút nhanh] ⟶ khoảng trống co giãn ⟶ [giao diện] [🔔] [👤] [Đăng xuất].
   `margin-left:auto` trên cụm nút giao diện = cái đẩy mọi thứ sau nó sang phải (trước đợt này là
   `margin-right:auto`, tức đẩy ngược — vì hồi đó bên trái trống trơn do nút nhanh đang bị ẩn). */
.top-row .btn-group {
    margin-left: auto;
    flex-shrink: 0;
}

/* Logo + nút ☰ Menu khi đã nằm TRONG thanh trên: giữ nguyên lớp `.menu-co-dien` để 4 bộ MÀU
   (`.menu-co-dien .mcd-*`) dùng lại được y nguyên, chỉ gỡ những gì thuộc về "một dải riêng". */
.top-row .menu-co-dien.mcd-trong-thanh {
    position: static;
    z-index: auto;
    padding: 0;
    background: none;
    border: 0;
    flex-wrap: nowrap;
    flex-shrink: 0;
    gap: .5rem;
}

/* Dải tab của trang: TỰ ẨN khi trang không đẩy tab nào lên — nếu không thì gom hai thanh vẫn còn
   thừa một dải rỗng. Phụ thuộc `MenuNgangCoDien.razor` viết LIỀN một dòng (xem chú thích ở đó). */
.menu-co-dien:empty {
    display: none;
}

/* ══ ĐỢT 422 — HÀNG TAB CỦA TRANG NẰM NGAY TRONG THANH TRÊN ══
   Anh Tiến 10/08/2026: *"lưu ý lại sinh ra 2 thanh nav"*, rồi chốt cách xử:
   *"nếu thanh nav bị tràn thì mới đưa xuống thanh thứ 2"*.

   Đợt 386b đã gom logo + ☰ Menu lên thanh trên, nhưng hàng TAB vẫn là một dải riêng bên dưới ⇒ vẫn
   là hai thanh. Lâu nay ít ai để ý vì dải tab tự ẩn khi rỗng, mà trang quản trị mạng thì rỗng suốt
   (nó đang vỡ — xem KhoiSoDoMangDemo). Sửa xong trang là hai thanh lộ ra ngay.

   Cách làm: dải tab thành MỘT Ô co giãn của chính `.top-row`, ăn đúng khoảng trống vốn đang bỏ phí
   giữa ☰ Menu và cụm tài khoản (đo thật: ~400 px trên màn 1900 px — thừa cho 6 tab).
   ⚠ Gỡ sạch mọi thứ thuộc về "một dải riêng": sticky · nền · viền dưới · z-index. Giữ nguyên lớp
   `.menu-co-dien` để 4 bộ MÀU của `.mcd-*` và `.nav-link` dùng lại y nguyên (đúng luật module cổ
   điển: khung ở đây, màu ở co-dien.css). */
.top-row .menu-co-dien.mcd-dai-tab {
    position: static;
    top: auto;
    z-index: auto;
    padding: 0;
    background: none;
    border: 0;
    align-items: center;
    flex-wrap: nowrap;
    /* Co giãn theo chỗ còn lại; `min-width: 0` để nó ĐƯỢC PHÉP hẹp hơn nội dung — thiếu dòng này thì
       ô flex lấy bề rộng tối thiểu bằng nội dung và đẩy cụm tài khoản văng khỏi thanh. */
    flex: 1 1 auto;
    min-width: 0;
    /* ⭐ ĐỢT 679 — THANH CUỘN MẢNH THAY VÌ ẨN HẲN (anh Tiến 15/08/2026: *"phải thu nhỏ lại 150%"* mới
       vào được tab). Câu cũ ở đây ghi *"ẩn thanh cuộn: nó chỉ tổ vướng, mà chuột/vuốt vẫn cuộn được"* —
       ĐÚNG về kỹ thuật nhưng SAI về người dùng: không có một dấu hiệu nào cho biết còn tab ở bên phải,
       nên cách duy nhất còn lại là thu nhỏ cả trang.
       ⇒ Thanh cuộn mảnh (~5px) + mặt nạ mờ dần hai mép: nhìn là biết còn nữa, chuột kéo được, mà vẫn
         không ăn mất chiều cao của dải.
       ⛔ Đừng đưa `scrollbar-width: none` trở lại. */
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: thin;
    scrollbar-color: rgba(127, 127, 127, .45) transparent;
    mask-image: linear-gradient(to right, transparent 0, black 12px,
                                black calc(100% - 12px), transparent 100%);
    scroll-behavior: smooth;
}

.top-row .menu-co-dien.mcd-dai-tab::-webkit-scrollbar { height: 5px; }
.top-row .menu-co-dien.mcd-dai-tab::-webkit-scrollbar-track { background: transparent; }
.top-row .menu-co-dien.mcd-dai-tab::-webkit-scrollbar-thumb {
    background: rgba(127, 127, 127, .45);
    border-radius: 3px;
}
.top-row .menu-co-dien.mcd-dai-tab::-webkit-scrollbar-thumb:hover { background: rgba(127, 127, 127, .65); }
.top-row .menu-co-dien.mcd-dai-tab .nav { flex-wrap: nowrap; }

/* ⚠ ĐỢT 473 — ĐÍNH CHÍNH câu ghi ở đây từ đợt 422 ("cụm tài khoản vẫn dạt phải nhờ
   `.top-row .btn-group { margin-left: auto }` của đợt 386b"): câu đó SAI SỰ THẬT từ đợt 431.
   `.btn-group` trên thanh là THANH CHỌN GIAO DIỆN, và đợt 431 đã dời nó vào bảng ☰ Menu ⇒ luật kia
   nay KHỚP 0 PHẦN TỬ (đã đo). Từ đó cụm 🔔 🚪 không có gì đẩy, rơi về sát trái ở mọi trang KHÔNG có
   dải tab (trang có tab thì `.mcd-dai-tab` nở ra lấp chỗ nên nhìn vẫn đúng — đó là lý do lỗi trốn lâu).
   Nay việc dạt phải do khối `.cum-ben-phai` (lớp `ms-auto` của Bootstrap, khai trong MainLayout.razor)
   lo. Luật `.top-row .btn-group` bên trên GIỮ LẠI phòng khi có nhóm nút quay lại thanh. */

/* ⛔⛔ ĐỢT 467 — GỠ `overflow: hidden` KHỎI `.top-row`: ĐÂY LÀ THỦ PHẠM LÀM "BẤM ☰ MENU KHÔNG THẤY GÌ"
   (anh Tiến báo NHIỀU LẦN: *"nút menu vẫn lỗi nhấn vào bị nền đè hết không thấy gì"*).

   VÌ SAO: bảng menu `.mcd-tha` là `position: absolute`, khối chứa nó là `details.mcd-nhom`
   (`position: relative`) — mà `.mcd-nhom` nằm BÊN TRONG `.top-row`. Theo đúng luật CSS, một hộp
   `overflow` khác `visible` CẮT mọi hậu duệ mà nó nằm trên chuỗi khối-chứa. Thanh chỉ cao 41–56 px,
   bảng menu cao 437–500 px ⇒ bảng bị xén còn một vệt, phần còn lại biến mất SAU nội dung trang.
   Người dùng đọc hiện tượng đó thành "bị nền đè".

   📏 ĐO THẬT (bản dựng đợt 467, đúng 4 tệp CSS của app, 4 giao diện):
     · 1441 px — cổ điển: bảng 280×437 px, THẤY 4 px = 1 % · hiện đại: 500 px, THẤY 16 px = 3 %.
       `document.elementFromPoint` giữa bảng trả về `div.card-body` — tức CHUỘT KHÔNG CHẠM ĐƯỢC MENU.
     · 1000 px — CÙNG mã, CÙNG giao diện: THẤY 100 %, elementFromPoint trả về `a.mcd-muc-con`.
       Khác đúng một thứ: dải 821–1200 px đã có `overflow: visible` (khối @media ngay dưới).
     · 391 px — 0–1 % (còn dính thêm một hộp cắt thứ hai, xem khối @media 820 px).

   ⚠ KHÔNG PHẢI "NGỮ CẢNH XẾP CHỒNG" — đã đo và LOẠI TRỪ, đừng đi lại đường này:
   quét toàn chuỗi cha từ bảng menu lên `<html>` thì KHÔNG cha nào có `transform`/`filter`/
   `will-change`/`contain`/`isolation`. `.top-row` CÓ `backdrop-filter: blur(8px)` (kính mờ, mục 7) và
   `position: sticky + z-index: 900` — cả hai ĐỀU tạo ngữ cảnh xếp chồng, nhưng ở 1000 px chúng vẫn
   nguyên đó mà bảng menu hiện đủ 100 %. Ngữ cảnh xếp chồng vô can; thứ giết bảng menu là PHÉP CẮT.

   ⇒ Giữ `flex-wrap: nowrap` (đó mới là thứ chống thanh xuống dòng), BỎ phép cắt.
   Không sợ tràn: dải tab `.mcd-dai-tab` là ô co giãn `flex: 1 1 auto; min-width: 0` và TỰ cuộn ngang
   trong lòng nó (đợt 422) — chỗ chật bị nuốt ở đó chứ không trào ra thanh.
   ⛔ ĐỪNG đặt lại `overflow: hidden/auto/clip` cho `.top-row` (hay cho `.menu-co-dien.mcd-trong-thanh`,
   `.mcd-nhom`) — thêm hiệu ứng mới cần che cạnh thì che ở CHÍNH khối con đó. Hàng rào canh:
   `TKNB.Tests/KhongCatBangMenuTests.cs`. */
.top-row {
    flex-wrap: nowrap;
    overflow: visible;
}

@media (max-width: 1200px) {
    /* Bỏ chữ, giữ biểu tượng — nút vẫn bấm được và `title` vẫn nói rõ nó làm gì (luật hướng dẫn
       nằm trong title, đợt 222). Tên người dùng cũng rút còn 👤. */
    .top-row .chu-nut { display: none; }

    /* ĐỢT 422 — ĐÂY LÀ NẤC "THANH NAV BỊ TRÀN" mà anh Tiến nói: dưới 1200 px, bỏ hết chữ rồi mà tab
       vẫn không đủ chỗ ⇒ cho hàng tab XUỐNG HÀNG THỨ HAI, chiếm trọn bề ngang.
       ⚠ Phải mở `flex-wrap` cho `.top-row` và bỏ `overflow: hidden` — để nguyên `hidden` thì hàng
       thứ hai bị CẮT CỤT chứ không hiện ra (bẫy cũ của đợt 396: thanh trên từng cắt mất nút Đăng
       xuất đúng kiểu này). */
    .top-row {
        flex-wrap: wrap;
        overflow: visible;
        row-gap: 0;
    }

    .top-row .menu-co-dien.mcd-dai-tab {
        flex: 1 0 100%;
        order: 9;               /* luôn là hàng CUỐI, dù đứng giữa trong mã */
        border-top: 1px solid var(--bs-border-color);
    }

    /* Rail icon bám dưới thanh trên bằng --cao-thanh-nav; thanh nay cao thêm một hàng tab nên trả
       biến này về đúng chiều cao hàng đó, không thì rail đè lên tab. */
    :root { --cao-thanh-nav: 2.5rem; }
}

/* Từ 1200 px trở lên: không còn dải nav riêng nào nữa (tab đã nằm trong thanh trên) ⇒ rail dán ngay
   dưới thanh trên. Để nguyên 2,5rem là chừa một khoảng trống không có gì. */
@media (min-width: 1201px) {
    :root { --cao-thanh-nav: 0rem; }
}

@media (max-width: 820px) {
    .top-row .nut-nhanh { display: none !important; }   /* !important: cụm mang .d-flex của Bootstrap */
}

/* ═══ ĐỢT 396 — MÀN HÌNH ĐIỆN THOẠI (anh Tiến 09/08/2026: *"giao diện điện thoại bị vỡ"*, kèm ảnh chụp) ═══
   Ba thứ hỏng cùng lúc trên máy ~390 px, thấy rõ trong ảnh anh gửi:
   ① RAIL ICON dọc bên trái (position: fixed) ĐÈ THẲNG lên nội dung — nút "Đăng xuất" bị cột icon che
     mất một nửa. Trên máy để bàn rail nổi ngoài lề trang nên vô hại; điện thoại không có lề nào cả.
   ② `.top-row` khai `flex-wrap: nowrap` + `overflow: hidden` — chật thì nó CẮT CỤT phần cuối, mà phần
     cuối chính là 🔔 · 👤 · Đăng xuất. Cắt mất nút đăng xuất là người dùng hết đường ra.
   ③ Nội dung dùng `px-4` (~24 px mỗi bên) — trên màn 390 px là mất 12% bề ngang cho lề trống.
   ⚠ Ẩn rail KHÔNG mất đường đi nào: mọi mục của rail đều có trong ngăn kéo ☰ Menu. */
@media (max-width: 820px) {
    .rail-phan-he { display: none !important; }

    /* Cố ý KHÔNG cho xuống dòng.
       ⚠ Vì sao không `flex-wrap: wrap`: dải tab `.menu-co-dien` dính đầu màn hình bằng
       `top: var(--cao-thanh-tren)` — một con số CỐ ĐỊNH (3.5rem). Cho thanh trên cao lên 2 hàng thì dải
       tab đè thẳng lên nó, đổi một chỗ vỡ chỗ khác. Giữ đúng một hàng là mọi thứ bên dưới vẫn khớp.

       ⛔ ĐỢT 467 — BỎ `overflow-x: auto; overflow-y: hidden` Ở ĐÂY (đợt 396 đặt để thanh CUỘN NGANG
       thay vì cắt cụt nút Đăng xuất). Lý do y hệt khối ngoài: một hộp `overflow` khác `visible` CẮT
       bảng ☰ Menu thả xuống — đo ở 391 px: bảng cao 437 px chỉ THẤY 0 px (cổ điển) / 4 px (hiện đại).
       Nay không cần nữa: mọi cụm cố định trên thanh khổ điện thoại chỉ còn logo + ☰ + 🔔 + 🚪 (chữ đã
       ẩn từ nấc 1200 px, rail và nút nhanh đã ẩn ở nấc này), còn dải tab thì TỰ cuộn trong lòng nó —
       đo lại sau khi sửa: `scrollWidth` của trang vẫn đúng bằng bề rộng màn hình, không trào ngang. */
    .top-row {
        overflow: visible;
        column-gap: .5rem !important;   /* đè gap-3 của Bootstrap: 1rem × nhiều cụm là tràn ngay */
        padding: 0 .5rem;
    }
    /* ⚠ ĐỢT 473: thêm con của `.cum-ben-phai` — 🔔 và 🚪 nay nằm TRONG một khối bọc (để cụm dạt phải
       bằng `ms-auto`, xem MainLayout.razor), nên luật cũ chỉ với tới cái BỌC chứ không với tới hai nút
       bên trong nữa. Thiếu vế thứ hai thì ở khổ điện thoại hai nút lại co thành vệt chữ dính nhau. */
    .top-row > *, .top-row .cum-ben-phai > * { flex-shrink: 0; }

    .content.px-4 { padding-left: .75rem !important; padding-right: .75rem !important; }

    /* Dải tab của trang: cuộn NGANG thay vì tự xuống dòng thành 3 tầng chiếm nửa màn hình.
       ⛔ ĐỢT 467 — CHỈ ĐÍCH DANH `.mcd-dai-tab`, ĐỪNG viết trần `.menu-co-dien`: lớp `.menu-co-dien`
       đang được ĐEO BỞI HAI khối (đợt 386b) — dải tab, VÀ cụm logo + ☰ Menu (`.mcd-trong-thanh`).
       Viết trần thì cụm ☰ Menu cũng thành hộp cắt cao 34 px, xén luôn bảng menu thả xuống (đo ở 391 px:
       đây là hộp cắt THỨ HAI, cùng với `.top-row`, hai cái cộng lại cho ra 0 % nhìn thấy). */
    .menu-co-dien.mcd-dai-tab {
        overflow-x: auto;
        flex-wrap: nowrap;
        -webkit-overflow-scrolling: touch;
    }
    .menu-co-dien .nav { flex-wrap: nowrap; }

    /* Bảng rộng (bảng công 31 cột…) phải tự cuộn trong khung của nó, không đẩy cả trang trôi ngang. */
    .table-responsive { -webkit-overflow-scrolling: touch; }
}

/* ═══ ĐỢT 400 — MỌI BẢNG TỰ THÀNH KHUNG CUỘN Ở KHỔ ĐIỆN THOẠI ═══
   Đợt 396 đã lo phần KHUNG (thanh trên · rail · dải tab). Còn NỘI DUNG thì vẫn trôi ngang: đo thật ở
   375 px với markup bảng chép nguyên từ mã (đúng số cột, đúng tiêu đề) thì **cả trang rộng 913 px** —
   người dùng vuốt ngang là thanh điều hướng, thẻ, chữ đều trượt theo, đúng cái anh Tiến chụp ảnh báo.

   ⚠ VÌ SAO KHÔNG ĐI BỌC `div.table-responsive` CHO TỪNG BẢNG: quét được **70 thẻ `<table>` chưa có
   khung cuộn riêng** trên 40+ tệp `.razor`. Sửa tay 70 chỗ là 70 cơ hội gõ nhầm, lại đụng đúng những
   tệp các phiên khác đang viết. Một luật CSS lo hết, và bảng thêm về sau tự được che.

   CÁCH LÀM: cho chính thẻ `<table>` thành khối cuộn (`display:block` + `overflow-x:auto`) — đúng lối
   GitHub dùng cho bảng trong tài liệu. Nghe thì ngược đời, nên đã ĐO trước khi tin:
   · Sợ nhất là **đầu bảng lệch khỏi thân bảng**. Không lệch: `thead`/`tbody` là ba anh em liền kề nên
     trình duyệt gói CHUNG vào một bảng ẩn danh ⇒ cột vẫn chung một phép chia. Đo 6 bảng (6→9 cột),
     lệch tối đa **0 px** ở mọi cột.
   · Sau khi áp: trang **913 px → 375 px** (hết trôi ngang), 6/6 bảng cuộn được trong khung của nó.

   ⛔ `min-width: 0 !important` KHÔNG ĐƯỢC BỎ: 7 bảng khai thẳng `style="min-width: 820…2400px"` trong
   markup, mà `min-width` thắng `max-width` ⇒ thiếu dòng này thì riêng mấy bảng đó vẫn kéo cả trang đi
   (đo thật: vẫn 913 px). Bỏ trần ấy KHÔNG làm bảng hẹp lại — bảng ẩn danh bên trong vẫn giãn theo nội
   dung, chỉ là hết phần "khoảng thở" vốn dành cho màn hình rộng.

   ⛔ CHỪA `.bang-dinh-dau`: hai bảng đó (ghi chú nhân sự · khai mã hóa) đã nằm trong khung
   `max-height + overflow:auto` của riêng chúng và có tiêu đề cột DÍNH khi cuộn dọc. Biến chúng thành
   khung cuộn nữa thì `position: sticky` bám vào khung mới — khung này cao bằng nội dung nên không bao
   giờ cuộn dọc ⇒ tiêu đề hết dính mà build vẫn sạch.

   ⚠ `@media screen`: bản IN phải giữ nguyên bảng thật (khối in ở co-dien.css cố ý không gate theme).
   ⚠ Đã rà: app KHÔNG có `.dropdown-menu` nào ⇒ không lo khung cuộn cắt mất menu thả trong ô bảng. */
@media screen and (max-width: 820px) {
    .content table.table:not(.bang-dinh-dau) {
        display: block;
        width: 100%;
        max-width: 100%;
        min-width: 0 !important;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }
}

/* ═══════════ CHẾ ĐỘ TRÌNH CHIẾU CỦA PHÂN HỆ MẠNG (đợt 388d, dời về đây ở đợt 396h) ═══════════
   Anh Tiến mở HAI màn hình: một chiếu SA BÀN, một chiếu SƠ ĐỒ NGUYÊN LÝ (tab Tổng quan).
   ⛔ VÌ SAO PHẢI Ở FILE DÙNG CHUNG: trước đó mấy lớp này khai trong <style> của component sa bàn — mà
   component chỉ render khi đang ở tab Sa bàn. Sang tab Tổng quan là không có luật CSS nào, lớp phủ mất
   tác dụng hoàn toàn (nội dung vẫn nằm trong trang, chữ chồng nhau, logo phình to). Anh bắt được ngay
   lần xem đầu tiên. Không có câu lỗi nào — chỉ nhìn mới thấy.
   ⚠ Cố ý KHÔNG dùng Fullscreen API: nó cần JS, cần đúng cử chỉ người dùng, mỗi trình duyệt một kiểu
   thoát. Lớp phủ CSS chạy chắc ở mọi nơi — bấm nút là ra. */
.sb-chieu {
    position: fixed;
    inset: 0;
    z-index: 1080;
    background: #0b1620;
    padding: 8px 12px 12px;
    display: flex;
    flex-direction: column;
}

/* ⚠ PHẢI LÀ CỘT, KHÔNG PHẢI HÀNG (đợt 396): thân trình chiếu chứa cả khung hình lẫn dải chú thích màu —
   để flex HÀNG thì chú thích thành cột thứ hai, đẩy hình lệch hẳn sang trái. Đúng chỗ anh Tiến nói
   "cho hình ra giữa". */
.sb-chieu-than {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 0;
    gap: .35rem;
    overflow: auto;
}

/* ⚠ ĐỢT 396h — KHUNG PHẢI VỪA MÀN HÌNH, KHÔNG ĐƯỢC PHÓNG TRÀN.
   Anh Tiến: *"anh cần trình chiếu NHƯ CŨ và thêm vài chi tiết anh nói, CHỨ KHÔNG PHẢI PHÓNG TO RA"*.
   Bản trước đặt `aspect-ratio` + `width: 100%` ⇒ khung cao hơn màn hình, ảnh vệ tinh bị cắt mất
   trên/dưới và các chấm nhìn như phóng đại. Nay bề ngang lấy theo CHIỀU CAO CÒN LẠI: hình luôn nằm
   trọn trong màn hình, thấy đủ tổng kho như bản cũ.
   `--sb-ty-le` = rộng/cao của ảnh nền (1805/809 ≈ 2.2311) — thay ảnh khác tỷ lệ thì sửa ở đây. */
.sb-chieu-khung {
    --sb-ty-le: 2.2311;
    width: min(100%, calc((100vh - var(--sb-chieu-tru, 8.5rem)) * var(--sb-ty-le)));
    width: min(100%, calc((100dvh - var(--sb-chieu-tru, 8.5rem)) * var(--sb-ty-le)));   /* ⭐ 1500 — dòng lui cho DI ĐỘNG, xem LuatDonViKhungNhin */
    max-width: 100%;
    aspect-ratio: var(--sb-ty-le);
    flex-shrink: 0;
}

/* ⭐ ĐỢT 1234 (việc o420) — DẢI CẢNH BÁO TRÊN BẢNG TREO TƯỜNG.
   Dải "phần mềm đang KHÔNG đo được" (đợt 1167) in ở TRANG CHA, mà `.sb-chieu` trùm kín màn hình ⇒ bật
   trình chiếu là mất hẳn. Đo sống 24/08/2026: dải vẫn ở DOM tại (24 ; 77,6) nhưng `elementFromPoint`
   tại đúng điểm đó trả về `.sb-chieu-than`.
   🔑 Màu vàng cảnh báo GIỮ NGUYÊN sắc của `.alert-warning` mà trang thường đang dùng — hai chỗ nói cùng
     một chuyện thì phải nhìn ra là cùng một chuyện. Nhưng nền ở đây là navy đặc `#0b1620`, nên phải là
     ⛔ nền vàng chữ đen (nghịch đảo), không phải chữ vàng trên nền tối: chiếu lên TV ở xa thì khối màu
     đặc mới đọc được, chữ mảnh trên nền tối thì nhòe.
   ⚠ CỠ CHỮ theo `clamp` — bảng treo tường có thể là TV 4K hay màn 1280 dựng tạm; đóng đinh `px` là một
     trong hai chỗ đọc không nổi. */
.sb-chieu-bao {
    background: #ffe08a;
    color: #3d2f00;
    border-left: 8px solid #d9822b;
    border-radius: 8px;
    padding: .45rem .8rem;
    margin-bottom: .4rem;
    flex-shrink: 0;
}

.sb-chieu-bao-dau {
    font-weight: 700;
    font-size: clamp(1.05rem, 1.9vh, 1.8rem);
    line-height: 1.25;
}

.sb-chieu-bao-than {
    font-size: clamp(.82rem, 1.35vh, 1.15rem);
    line-height: 1.3;
}

.sb-chieu-bao-chua {
    font-size: clamp(.78rem, 1.2vh, 1.05rem);
    line-height: 1.3;
    opacity: .9;
}

/* ⚠ Có dải cảnh báo thì khung hình PHẢI nhường chỗ. Không nới phần trừ là khung cao hơn màn hình và
   ảnh vệ tinh bị cắt trên/dưới — đúng bẫy anh Tiến bắt được ở đợt 396h ("anh cần trình chiếu NHƯ CŨ,
   CHỨ KHÔNG PHẢI PHÓNG TO RA"). 8.5rem + chiều cao dải (đo sống: ~6,4rem ở khung 1280×721). */
.sb-chieu-co-bao { --sb-chieu-tru: 15rem; }

/* Logo trên nền navy bị chìm ⇒ đặt trong chip kính trắng, cùng cách trang chủ đang làm (đợt 373) */
.sb-logo-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, .95);
    border-radius: 12px;
    padding: .3rem .45rem;
    flex-shrink: 0;
}

.sb-logo { height: 30px; width: auto; max-width: none; display: block; }   /* GIỮ TỶ LỆ: chỉ đặt cao */

/* Sơ đồ nguyên lý khi chiếu: bỏ khung thẻ, cho nền tối ăn hết — chiếu lên tường thì cái viền trắng
   của thẻ chỉ tổ chói mắt. Nội dung bên trong giữ nguyên (thẻ thiết bị vẫn nền sáng cho dễ đọc). */
.sb-chieu .card { background: transparent; border-color: rgba(255, 255, 255, .18); }
.sb-chieu .card-header { background: transparent; color: #e9ecef; }

/* Màn hình HẸP HẲN (~390 px của điện thoại đang cầm tay): bỏ nốt cụm đổi giao diện khỏi thanh trên.
   Lựa chọn giao diện đã được NHỚ theo trình duyệt nên gần như không ai đổi trên điện thoại, trong khi
   4 nút đó chiếm gần một phần ba bề ngang — nhường chỗ cho 🔔 · 👤 · Đăng xuất là thứ dùng thật. */
@media (max-width: 560px) {
    .top-row .btn-group[aria-label="Giao diện"] { display: none !important; }
}

/* ───── 4. MÀU CỦA GIAO DIỆN TỐI (sáng/xám dùng màu mặc định ở trên) ───── */
:root[data-theme="dark"] .menu-co-dien {
    background-color: #2d323a;
    border-color: #3a3f47;
}

:root[data-theme="dark"] .menu-co-dien .mcd-muc { color: #dde1e6; }

:root[data-theme="dark"] .menu-co-dien .mcd-muc:hover {
    background-color: #3a4049;
    border-color: #4a5058;
}

:root[data-theme="dark"] .menu-co-dien .mcd-mui { color: #9aa0a8; }

:root[data-theme="dark"] .menu-co-dien .mcd-nhom-tieude {
    color: #6bb3ff;
    border-color: #3a3f47;
}

:root[data-theme="dark"] .menu-co-dien .mcd-tha,
:root[data-theme="dark"] .rail-phan-he .rph-con {
    background-color: #262a31;
    border-color: #3a3f47;
    box-shadow: 0 6px 18px rgba(0, 0, 0, .5);
}

:root[data-theme="dark"] .menu-co-dien .mcd-tha a,
:root[data-theme="dark"] .rail-phan-he .rph-con a { color: #dde1e6; }

:root[data-theme="dark"] .menu-co-dien .mcd-tha a:hover,
:root[data-theme="dark"] .rail-phan-he .rph-con a:hover { background-color: #3a4049; }

:root[data-theme="dark"] .rail-phan-he .rph-icon {
    background-color: #262a31;
    border-color: #3a3f47;
    box-shadow: 0 2px 6px rgba(0, 0, 0, .5);
    color: #dde1e6;
}

:root[data-theme="dark"] .rail-phan-he .rph-o:hover .rph-icon { background-color: #3a4049; }

/* Mục ĐANG XEM ở nền tối: xanh đậm + chữ xanh nhạt (nền pastel sáng sẽ chói và nuốt chữ) */
:root[data-theme="dark"] .menu-co-dien .mcd-dang-mo,
:root[data-theme="dark"] .menu-co-dien .mcd-tha a.mcd-dang-mo,
:root[data-theme="dark"] .rail-phan-he .rph-dang-mo > .rph-icon,
:root[data-theme="dark"] .rail-phan-he .rph-icon.rph-dang-mo,
:root[data-theme="dark"] .rail-phan-he .rph-con a.rph-dang-mo {
    background-color: #123a63;
    border-color: #1b5aa0;
    color: #9ec5fe;
}

/* ═══ BẢNG DÀI CUỘN TRONG KHUNG, TIÊU ĐỀ CỘT DÍNH LẠI (đợt 297) ═══
   Anh Tiến chốt 05/08: bảng ghi chú nhân sự "thu gọn lại và cho kéo để xem". Khống chế chiều cao rồi
   cuộn TRONG bảng thì phần dưới trang không bị đẩy đi; nhưng kéo xuống mà mất dòng tiêu đề thì không
   còn biết cột nào là cột nào — nên ghim tiêu đề. Dùng chung được cho mọi bảng dài khác. */
.bang-dinh-dau thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background-color: var(--bs-body-bg, #fff);
    box-shadow: inset 0 -1px 0 var(--bs-border-color, #dee2e6);
}

/* ⚠ VÁ ĐỢT 381 (lộ ra khi rà tương phản theo lời anh dặn 09/08): --bs-body-bg là #fff của
   Bootstrap ở MỌI giao diện — giao diện TỐI không định nghĩa lại ⇒ đầu bảng dính có nền TRẮNG
   + chữ #dde1e6 ≈ 1,3:1, không đọc nổi (lỗi có từ đợt 297, chỉ lộ ở bảng ghi chú nhân sự và
   màn khai mã hóa khi xem nền tối). Cổ điển tối THOÁT nhờ thead có gradient đục phủ lên. */
:root[data-theme="dark"] .bang-dinh-dau thead th {
    background-color: #2d323a;
    box-shadow: inset 0 -1px 0 #3a3f47;
}

/* ═══ ĐỢT 1810 (việc o1072) — BẢNG DÀI CẢ TRANG: KHUNG CUỘN CAO ĐÚNG KHUNG NHÌN ═══
   Ba bảng dài nhất của phân hệ nghỉ phép nằm trong `div.table-responsive` mà KHÔNG có trần chiều cao.
   Bootstrap chỉ khai `overflow-x: auto` cho lớp ấy, nhưng luật CSS nói một trục đã khác `visible` thì
   trục kia cũng thôi `visible` ⇒ khung bọc ĐÃ LÀ một hộp cuộn hai chiều (đo 02/09/2026: `auto/auto`).
   Chỉ thiếu đúng một thứ: TRẦN CHIỀU CAO. Không có trần thì hộp cao bằng nội dung ⇒ nó KHÔNG BAO GIỜ
   cuộn dọc ⇒ mọi `position: sticky` bên trong đều chết lặng — đúng cái bẫy mà khối `.bang-dinh-dau`
   ngay dưới đã ghi từ đợt 297.

   📏 ĐO TRƯỚC KHI SỬA (Chrome thật, khung 1400×900, đăng nhập tienvm, có chốt tiền đề `d-flex`):
     ⚠ ĐỢT 2362e (việc o1966): trang `/nghi-phep/bao-cao-thang` đã BỎ 13/09/2026 theo lệnh anh Tiến, nguyên
       văn "bỏ báo cáo phép theo tháng (bỏ cả nút và trang)" — các số đo của trang ấy dưới đây giữ làm LỊCH SỬ
       (luật `.khung-bang-dai` là luật chung, nay còn HAI trang dùng).
     · /nghi-phep/quan-ly       9 cột × 420 dòng — trang cao 23.395px (phải cuộn 22.495px)
     · /nghi-phep/bao-cao-thang 17 cột × 405 dòng — trang cao 32.011px (phải cuộn 31.111px)
     · /nghi-phep/tong-hop-nam  20 cột × 405 dòng — trang cao 20.102px (phải cuộn 19.202px)
   Cuộn quá ~800px là mất dòng tiêu đề cột, mất dòng tên phòng ban, và mất luôn cả thanh lọc
   (Năm · Phòng ban · Tìm người · Xuất Excel) — muốn đổi tháng phải cuộn ngược hơn 30.000px.

   🔑 BIẾN `--bang-dai-duoi` LÀ CHIỀU CAO PHẦN NẰM **DƯỚI** KHUNG BẢNG (chân thẻ chú giải + lề đáy),
      ⛔ KHÔNG phải phần nằm trên. Đây là chỗ dễ làm ngược nhất, nên chép luôn phép tính ra đây —
      gọi A = phần TRÊN khung bảng, B = phần DƯỚI, F = chiều cao khung bảng, K = chiều cao khung nhìn:
        · trang cao       P = A + B + F
        · cuộn được tối đa  S = P − K = A + B + F − K
        · đặt F = K − B − N  (N = chỗ chừa cho thanh nav dính)  ⇒  S = A − N
        · mép trên khung bảng sau khi cuộn hết = A − S = **N**, ⛔ đúng bằng chỗ chừa, với MỌI A.
      ⇒ Cuộn kịch trang thì đầu bảng dừng **ngay dưới thanh nav**, ⛔ không bao giờ chui xuống dưới nó —
        và điều đó đúng cho mọi chiều cao khung nhìn, mọi trang, mọi bề dày phần đầu trang. Trừ theo A
        (bản thảo đầu của chính đợt này) thì phép bảo đảm ấy KHÔNG có: nó chỉ tình cờ đúng ở một cỡ màn.
      📏 Đo 02/09/2026 ở khung 1400×900 — B = (mọi thứ ngoài khung bảng) − (phần trên khung bảng):
        · quản lý       644 − 562 = **82px**  (chỉ có viền thẻ + lề `mb-4`)
        · báo cáo tháng 818 − 482 = **336px** (thẻ này có `card-footer` chú giải khá dài)
        · tổng hợp năm  804 − 531 = **273px** (cũng có chân thẻ chú giải)
      ⛔ ĐỪNG gõ số vào từng tệp CSS cách ly: một chỗ khai luật, mỗi trang chỉ đưa một con số.

   🔑 CHỖ CHỪA CHO THANH NAV đọc lại ĐÚNG hai biến mà thanh nav đang dùng (`--cao-thanh-tren` +
      `--cao-thanh-nav`, xem mục 1 phía trên) cộng một nhịp thở 1,5rem. ⛔ Đừng gõ 96px: thanh trên
      thấp đi ở giao diện cổ điển thì bộ biến tự đúng theo, còn con số gõ tay thì không.

   ⚠ B chỉ đúng XẤP XỈ khi khung hẹp lại làm chữ chú giải xuống thêm dòng. Lệch thì trang nhích lên
     xuống vài chục px chứ KHÔNG vỡ gì — cùng lẽ với số trừ 163px ở `NghiPhep.razor.css` (đợt 911).
   ⚠ `min-height` giữ sàn 320px: cửa sổ bị kéo bẹp mà tính ra 80px thì khung bảng hóa một khe hẹp
     không đọc nổi; lúc ấy thà để trang cuộn như thường — và lúc ấy phép bảo đảm ở trên thôi áp dụng.

   ⛔ KHUNG HẸP (<1280px) THÌ BỎ TRẦN — trả về trang cuộn như cũ. Ép khung cuộn dọc lồng trong trang
      cuộn dọc trên màn hình bé là người dùng lăn chuột trong ô mà tưởng cả trang đứng im (đúng lựa
      chọn của đợt 911 cho `/nghi-phep`). Vẫn GIỮ `overflow` để bảng 20 cột còn cuộn NGANG được:
      ba bảng này mang `.bang-dinh-dau` nên đã bị luật cuộn bảng của đợt 400 cố ý chừa ra.
   🔑 **MỐC 1280px, ⛔ KHÔNG PHẢI 1200px** — con số này do ĐO chứ ⛔ không chọn cho tròn. Mấy con số
      `--bang-dai-dau` / `--bang-dai-hang2` bên dưới là chiều cao hàng đầu cột, mà chữ trong đầu cột
      **xuống dòng** khi khung hẹp lại: đo `/nghi-phep/bao-cao-thang` ở 2560 · 1920 · 1600 · 1400 ·
      1280px đều ra **33 + 58 = 91**, riêng **1200px nhảy lên 57 + 58 = 115**. ⇒ Từ 1280px trở lên số
      khai mới đúng; hẹp hơn thì thà bỏ hẳn khung cuộn còn hơn dính lệch 24px. */
.khung-bang-dai {
    --bang-dai-chua-nav: calc(var(--cao-thanh-tren, 3.5rem) + var(--cao-thanh-nav, 2.5rem) + 1.5rem);
    max-height: calc(100vh - var(--bang-dai-duoi, 120px) - var(--bang-dai-chua-nav));
    max-height: calc(100dvh - var(--bang-dai-duoi, 120px) - var(--bang-dai-chua-nav));   /* dòng lui cho DI ĐỘNG — LuatDonViKhungNhin */
    min-height: 320px;
    overflow: auto;
}

@media (max-width: 1279.98px) {
    .khung-bang-dai {
        max-height: none;
        min-height: 0;
    }
}

/* ĐẦU CỘT HAI HÀNG: hàng THỨ HAI phải dính DƯỚI hàng thứ nhất, ⛔ không phải cùng ở mốc 0.
   🩸 Trả giá ngay trong đợt 1810, và nó lọt qua CẢ một lượt đo: luật dính của đợt 297 đặt trên Ô `th`,
   nên mỗi hàng đầu cột tự dính ở `top: 0` ⇒ ở `/nghi-phep/bao-cao-thang` (bảng DUY NHẤT có hai hàng
   tiêu đề vì nó gộp cột) hàng 2 **đè kín hàng 1** — mất hẳn dải nhãn nhóm «BẢN GỐC PHÒNG TỔ CHỨC ·
   QUỸ PHÉP CẢ NĂM · ĐƠN TRONG PHẦN MỀM», mà đó lại là thứ DUY NHẤT phân biệt hai cột cùng tên
   «Trong tháng». Tệ hơn: dòng nhóm dính ở mốc 91 trong khi đầu cột chỉ chiếm tới 58 ⇒ **hở 33px cho
   dòng dữ liệu trườn qua giữa hai mức dính**.
   🧨 **VÌ SAO LƯỢT ĐO ĐẦU KHÔNG THẤY**: bộ đo hỏi `thead.getBoundingClientRect()` — đó là HỘP BỐ CỤC,
      nó vẫn cao đủ 91px kể cả khi hai hàng chồng lên nhau, nên khe hở đo ra `0px` rất thuyết phục.
      Thứ DÍNH là ô `th`, ⛔ không phải thẻ `tr` (thẻ `tr` ⛔ không nhúc nhích). ⇒ **Đo `position:
      sticky` thì phải đo ĐÚNG phần tử mang nó**, và ⛔ đừng tin một khe hở đo trên hộp bố cục.
   ⇒ `--bang-dai-hang2` = chiều cao hàng đầu cột THỨ NHẤT; mặc định `0px` nên bảng một hàng ⛔ không
     đổi gì. Đo 02/09/2026 ở `/nghi-phep/bao-cao-thang`: **33px** (ổn định từ 1280px trở lên).
   ⚠ ĐỢT 2362e (việc o1966): trang tháng ấy đã BỎ 13/09/2026 ⇒ hiện ⛔ còn bảng dài nào có HAI hàng đầu cột;
     luật này GIỮ làm luật chung cho bảng gộp cột về sau (hàng rào BangDaiDinhDauNghiPhepTests vẫn canh). */
.khung-bang-dai .bang-dinh-dau thead tr:nth-child(2) > th {
    top: var(--bang-dai-hang2, 0px);
}

/* Đầu cột của bảng trong khung này giữ NGUYÊN sắc xám nhạt của `thead.table-light`.
   ⛔ Cố ý viết hẹp trong `.khung-bang-dai`, ⛔ KHÔNG sửa luật `.bang-dinh-dau` dùng chung: ba bảng
   dùng lớp ấy từ trước (ghi chú nhân sự · lịch sử nhân sự · khai mã hóa cột) đang có đầu cột TRẮNG,
   nới luật chung là đổi màu ba màn hình của phân hệ khác mà chẳng ai yêu cầu. */
.khung-bang-dai .bang-dinh-dau thead.table-light th {
    background-color: var(--bs-tertiary-bg, #f8f9fa);
}

:root[data-theme="dark"] .khung-bang-dai .bang-dinh-dau thead.table-light th {
    background-color: #2d323a;
}

/* DÒNG NHÓM (tên phòng ban + sĩ số) DÍNH NGAY DƯỚI ĐẦU CỘT.
   Hai bảng báo cáo gộp người theo phòng; mỗi phòng trung bình hơn 40 dòng ≈ 1.500px — dài hơn một
   khung nhìn ⇒ cuộn tới giữa phòng là không còn biết đang đọc phòng nào. Đây là mức dính THỨ HAI,
   nên nó phải bắt đầu ở đúng chỗ đầu cột kết thúc: biến `--bang-dai-dau`.
   📏 Đo 02/09/2026: đầu cột 1 dòng = 57–58px (quản lý · tổng hợp năm), 2 dòng = 91px (báo cáo tháng).
   ⚠ `z-index` phải THẤP HƠN đầu cột (2): hai mức dính gặp nhau ở mép trên, dòng nhóm mà nằm trên thì
     nó trườn đè lên tên cột — tức phá đúng thứ vừa dựng. */
.khung-bang-dai .bang-dinh-dau tbody tr.hang-nhom > th,
.khung-bang-dai .bang-dinh-dau tbody tr.hang-nhom > td {
    position: sticky;
    top: var(--bang-dai-dau, 58px);
    z-index: 1;
}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   ĐỢT 381 — NGÔN NGỮ THỊ GIÁC PETROLIMEX CHO TOÀN APP (anh giao 09/08/2026: "chỉnh lại đồ họa
   thật đẹp, công nghệ, sinh động, nút nhấn có các hiệu ứng… cho tất cả các trang và các chế độ")

   Nhân rộng ngôn ngữ đã duyệt ở trang đăng nhập (362y) + trang chủ (371) + logo chip (373):
   navy #002169→#0a3d91, vệt cam #F47B20, bo góc mềm, bóng màu rgba(0,23,68,…), chuyển tiếp ngắn.

   ⚠ LUẬT SỐNG CÒN CỦA KHỐI NÀY (rút từ lượt rà 3 luồng phụ trước khi viết):
   · MỌI luật gate theo :root[data-theme="light"] / "dark" — HAI giao diện CỔ ĐIỂN (co-dien,
     co-dien-toi) TUYỆT ĐỐI không nhận hiệu ứng: cổ điển là bản PHẲNG có chủ đích (khuôn 371).
   · Bọc trong @media screen — không thì bóng mềm + bo góc LEM VÀO TỜ GIẤY khi window.print()
     từ giao diện hiện đại (các tờ bản in ở co-dien.css mục 13/14/16 cố ý không gate theme).
   · .card:hover KHÔNG ĐƯỢC dùng transform: transform biến card thành containing-block của
     position:fixed con ⇒ nút ⛶ phóng to của biểu đồ nước (BieuDoCotSoSanh/BieuDoDuongSoSanh,
     fixed-trong-card) sẽ co cụm vào trong card — hỏng chức năng thật. "Nổi" lấy bằng bóng+viền.
   · Vùng ĐÃ có ngôn ngữ riêng phải chừa: .dn-nen (đăng nhập 362y) · .trang-chu (trang chủ 371) ·
     .thong-bao-noi (toast có animation trượt riêng) · .bang-tre-som-co-dien (bảng tờ in mang cả
     lớp .table) · nút trong .top-row/.menu-co-dien/.rail-phan-he/.btn-group/.input-group không
     nổi lẻ (nút liền khối/thanh dính đầu màn hình).
   · Ripple ::after chừa .dropdown-toggle (Bootstrap vẽ mũi tên bằng ::after — đè là mất caret).
   · ⛔ CẤM nới luật tab sang .nav-pills trần: hàng tab trên thanh nav là .nav-pills, các con số
     tương phản đo thật ở mục "BỐ CỤC THANH NAV" sẽ bị đè sạch nếu không loại .menu-co-dien.
   · Cuối file có khối @media (prefers-reduced-motion) TOÀN CỤC !important — máy khai giảm
     chuyển động là đứng yên hết, kể cả hiệu ứng viết trong <style> từng trang (362y/371/Mạng).
   · Toàn khối là chữ tĩnh, KHÔNG số sinh từ C# ⇒ không dính bẫy vi-VN InvariantCulture.
   ══════════════════════════════════════════════════════════════════════════════════════════ */
@media screen {

/* ───── 0. Biến thương hiệu (chỉ 2 giao diện hiện đại dùng) ───── */
:root[data-theme="light"], :root[data-theme="dark"] {
    --plx-navy: #002169;
    --plx-xanh: #0a3d91;
    --plx-cam: #F47B20;
}

/* ───── 1. NÚT NHẤN: chuyển tiếp + nổi khi rê + chớp sáng khi bấm + focus rõ ───── */
:root[data-theme="light"] .btn:not(.dn-nen *),
:root[data-theme="dark"] .btn:not(.dn-nen *) {
    position: relative;
    overflow: hidden;   /* nuôi lớp chớp sáng ::after; đã rà: không nút nào có con absolute vượt khung */
    transition: transform .15s ease, box-shadow .15s ease, background-color .15s ease,
                border-color .15s ease, color .15s ease;
}

/* Nổi nhẹ khi rê — chừa nút vùng khung (thanh trên/nav/rail: thanh dính đầu màn hình, nút
   nhảy trong đó rất rối) và nút LIỀN KHỐI (.btn-group/.input-group — nổi lẻ là vênh khối).
   ⚠ BẪY SPECIFICITY (luồng rà 381 bắt được): mỗi :not(x) CỘNG specificity của x ⇒ luật này nặng
   (0,12,0). Loại trừ nhóm phải nằm NGAY TRONG chuỗi :not này — viết luật override rời
   `.btn-group .btn:hover { transform:none }` là THUA (0,5,0) và chết câm lặng. */
:root[data-theme="light"] .btn:hover:not(:disabled):not(.disabled):not(.dn-nen *):not(.top-row *):not(.menu-co-dien *):not(.rail-phan-he *):not(.btn-group *):not(.input-group *),
:root[data-theme="dark"] .btn:hover:not(:disabled):not(.disabled):not(.dn-nen *):not(.top-row *):not(.menu-co-dien *):not(.rail-phan-he *):not(.btn-group *):not(.input-group *) {
    transform: translateY(-1px);
}
:root[data-theme="light"] .btn:hover:not(:disabled):not(.disabled):not(.dn-nen *):not(.top-row *):not(.menu-co-dien *):not(.rail-phan-he *):not(.btn-group *):not(.input-group *) {
    box-shadow: 0 6px 16px rgba(0, 23, 68, .16);
}
:root[data-theme="dark"] .btn:hover:not(:disabled):not(.disabled):not(.dn-nen *):not(.top-row *):not(.menu-co-dien *):not(.rail-phan-he *):not(.btn-group *):not(.input-group *) {
    box-shadow: 0 6px 16px rgba(0, 0, 0, .45);
}

/* Bấm xuống: lún nhẹ lại — cảm giác "nút thật".
   ⚠ Chuỗi :not phải DÀI BẰNG luật hover ở trên (cùng 0,12,0) và đứng SAU: lúc bấm chuột thì
   :hover vẫn đang khớp — luật này nhẹ ký hơn là translateY(-1px) đè mất scale, cú lún không
   bao giờ hiện (đã dính thật ở bản đầu của đợt 381). */
:root[data-theme="light"] .btn:active:not(:disabled):not(.disabled):not(.dn-nen *):not(.top-row *):not(.menu-co-dien *):not(.rail-phan-he *):not(.btn-group *):not(.input-group *),
:root[data-theme="dark"] .btn:active:not(:disabled):not(.disabled):not(.dn-nen *):not(.top-row *):not(.menu-co-dien *):not(.rail-phan-he *):not(.btn-group *):not(.input-group *) {
    transform: translateY(0) scale(.98);
}

/* Chớp sáng lan từ giữa nút khi bấm (ripple thuần CSS — không JS, SSR vẫn chạy).
   pointer-events:none bắt buộc: không thì cú bấm thứ hai trúng lớp ripple thay vì nút. */
:root[data-theme="light"] .btn:not(.dn-nen *):not(.dropdown-toggle)::after,
:root[data-theme="dark"] .btn:not(.dn-nen *):not(.dropdown-toggle)::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: radial-gradient(circle at 50% 50%, rgba(255, 255, 255, .5), transparent 65%);
    opacity: 0;
    transform: scale(.5);
    transition: opacity .45s ease, transform .45s ease;
    pointer-events: none;
}
:root[data-theme="light"] .btn:active:not(:disabled):not(.dn-nen *):not(.dropdown-toggle)::after,
:root[data-theme="dark"] .btn:active:not(:disabled):not(.dn-nen *):not(.dropdown-toggle)::after {
    opacity: .9;
    transform: scale(1.6);
    transition: none;
}

/* (Loại trừ nút liền khối nằm NGAY trong chuỗi :not của luật hover/active ở trên — xem chú
   thích bẫy specificity; không có luật override rời ở đây nữa.) */

/* Nút CHÍNH = gradient navy thương hiệu (cùng chiều 120deg với nút Đăng nhập 362y) */
:root[data-theme="light"] .btn-primary:not(.dn-nen *) {
    background-color: var(--plx-xanh);
    background-image: linear-gradient(120deg, #0a3d91, #002169);
    border-color: #002169;
}
:root[data-theme="light"] .btn-primary:hover:not(.dn-nen *),
:root[data-theme="light"] .btn-primary:focus:not(.dn-nen *) {
    background-color: #0d47a8;
    background-image: linear-gradient(120deg, #0d47a8, #0a3d91);
    border-color: #0a3d91;
}
:root[data-theme="dark"] .btn-primary:not(.dn-nen *) {
    background-color: #1b5fc4;
    background-image: linear-gradient(120deg, #1b5fc4, #0a3d91);
    border-color: #0a3d91;
}
/* ⚠ #2570dd là ĐIỂM SÁT NGƯỠNG NHẤT của cả bộ màu: chữ trắng trên nó = 4,73:1 (ngưỡng 4,5).
   ĐỪNG sáng thêm — chỉ cần lên #2e7ae8 là trượt tương phản. */
:root[data-theme="dark"] .btn-primary:hover:not(.dn-nen *),
:root[data-theme="dark"] .btn-primary:focus:not(.dn-nen *) {
    background-color: #2570dd;
    background-image: linear-gradient(120deg, #2570dd, #1b5fc4);
    border-color: #1b5fc4;
}

/* Nút VIỀN chính cũng về navy thương hiệu, rê vào thì đổ đầy gradient */
:root[data-theme="light"] .btn-outline-primary { color: #0a3d91; border-color: #0a3d91; }
:root[data-theme="light"] .btn-outline-primary:hover {
    color: #fff;
    background-color: var(--plx-xanh);
    background-image: linear-gradient(120deg, #0a3d91, #002169);
    border-color: #002169;
}
/* Viền #4a7fd6 (không phải #3d6fb5): viền là DẤU HIỆU NHẬN DẠNG nút — cần ≥ 3:1 với nền thẻ
   #262a31 (WCAG 1.4.11); #3d6fb5 chỉ đạt 2,8:1, #4a7fd6 đạt 3,6:1. Chữ #6bb3ff đạt 6,5:1. */
:root[data-theme="dark"] .btn-outline-primary { color: #6bb3ff; border-color: #4a7fd6; }
:root[data-theme="dark"] .btn-outline-primary:hover {
    color: #fff;
    background-color: #1b5fc4;
    background-image: linear-gradient(120deg, #1b5fc4, #0a3d91);
    border-color: #0a3d91;
}

/* Focus BÀN PHÍM rõ, màu thương hiệu (đè vòng trắng+xanh chung ở đầu file trong 2 giao diện này) */
/* ⚠ ĐỢT 519 — NÂNG ALPHA, KHÔNG đổi màu, KHÔNG đổi độ dày (khuôn đợt 463).
   Đo sống vòng focus của nút trên nền thật: alpha .3 ở nền sáng chỉ ra **1,75:1**, alpha .35 ở nền tối
   ra **2,05:1** — đều dưới ngưỡng 3:1 cho chỉ báo PHI VĂN BẢN (WCAG 1.4.11/2.4.11). Vòng focus mờ tới
   mức này thì người dùng bàn phím không biết mình đang đứng ở nút nào.
   Alpha mới đo được: nền sáng .7 ⇒ **4,50** · nền tối .8 ⇒ **4,85**.
   ⚠ GIỮ NGUYÊN `:not(.dn-nen *)`: thẻ đăng nhập là KÍNH TRẮNG kể cả khi máy lưu giao diện tối — bỏ dấu
   chừa là bộ focus nền-tối đè lên focus navy đã duyệt ở 362y (bài học luồng rà 381).
   ⓘ Chỉ đổi trạng thái :focus ⇒ lúc nghỉ không màn hình nào đổi hình thức. */
:root[data-theme="light"] .btn:focus:not(.dn-nen *),
:root[data-theme="light"] .btn:focus-visible:not(.dn-nen *) {
    box-shadow: 0 0 0 .2rem rgba(10, 61, 145, .7);
}
:root[data-theme="dark"] .btn:focus:not(.dn-nen *),
:root[data-theme="dark"] .btn:focus-visible:not(.dn-nen *) {
    box-shadow: 0 0 0 .2rem rgba(120, 180, 255, .8);
}

/* ───── 2. THẺ (card): vỏ kính bo 14, rê vào NỔI BẰNG BÓNG + VIỀN (cấm transform — xem đầu khối).
   Chừa .trang-chu: trang chủ 371 đã có bộ riêng được duyệt, đè nửa vời là hai theme lệch nhau. ───── */
:root[data-theme="light"] .card:not(.trang-chu *) {
    border: 1px solid rgba(10, 61, 145, .14);
    border-radius: 14px;
    box-shadow: 0 6px 22px rgba(0, 23, 68, .07);
    transition: box-shadow .18s ease, border-color .18s ease;
}
:root[data-theme="light"] .card:not(.trang-chu *):hover {
    border-color: rgba(10, 61, 145, .30);
    box-shadow: 0 12px 30px rgba(0, 23, 68, .13);
}
:root[data-theme="light"] .card:not(.trang-chu *) > .card-header {
    background: linear-gradient(90deg, rgba(10, 61, 145, .07), transparent);
    border-bottom-color: rgba(10, 61, 145, .10);
    font-weight: 600;
}
:root[data-theme="dark"] .card:not(.trang-chu *) {
    border-color: rgba(120, 180, 255, .18);
    border-radius: 14px;
    box-shadow: 0 6px 22px rgba(0, 0, 0, .35);
    transition: box-shadow .18s ease, border-color .18s ease;
}
:root[data-theme="dark"] .card:not(.trang-chu *):hover {
    border-color: rgba(120, 180, 255, .38);
    box-shadow: 0 12px 30px rgba(0, 0, 0, .5);
}
:root[data-theme="dark"] .card:not(.trang-chu *) > .card-header {
    background-color: transparent;
    background-image: linear-gradient(90deg, rgba(120, 180, 255, .10), transparent);
    border-bottom-color: rgba(120, 180, 255, .15);
    font-weight: 600;
}
/* Góc header/footer bám theo bo 14 của thẻ (radius Bootstrap nhỏ hơn, hở góc trắng) */
:root[data-theme="light"] .card:not(.trang-chu *) > .card-header:first-child,
:root[data-theme="dark"] .card:not(.trang-chu *) > .card-header:first-child { border-radius: 13px 13px 0 0; }
:root[data-theme="light"] .card:not(.trang-chu *) > .card-footer:last-child,
:root[data-theme="dark"] .card:not(.trang-chu *) > .card-footer:last-child { border-radius: 0 0 13px 13px; }

/* Hộp thoại nổi cùng ngôn ngữ */
:root[data-theme="light"] .modal-content,
:root[data-theme="dark"] .modal-content { border-radius: 14px; }

/* ───── 3. BẢNG: đầu bảng ánh navy + hàng đổi màu có chuyển tiếp.
   Chừa .bang-tre-som-co-dien (bảng TỜ BẢN IN mang cả lớp .table — phải giữ dáng biểu mẫu cũ). ───── */
:root[data-theme="light"] .table:not(.bang-tre-som-co-dien) > thead > tr > th {
    background-image: linear-gradient(180deg, rgba(10, 61, 145, .06), rgba(10, 61, 145, .02));
    border-bottom: 2px solid rgba(10, 61, 145, .22);
}
:root[data-theme="dark"] .table:not(.bang-tre-som-co-dien) > thead > tr > th {
    background-image: linear-gradient(180deg, rgba(120, 180, 255, .08), rgba(120, 180, 255, .02));
    border-bottom: 2px solid rgba(120, 180, 255, .22);
}
:root[data-theme="light"] .table:not(.bang-tre-som-co-dien) > tbody > tr > *,
:root[data-theme="dark"] .table:not(.bang-tre-som-co-dien) > tbody > tr > * {
    transition: box-shadow .15s ease, background-color .15s ease, color .15s ease;
}

/* ───── 4. TAB trên thanh nav: chuyển tiếp + viên đang mở = gradient navy (trắng/navy ≥ 8:1) ───── */
:root[data-theme="light"] .menu-co-dien .nav-link,
:root[data-theme="dark"] .menu-co-dien .nav-link {
    transition: background-color .15s ease, color .15s ease;
}
:root[data-theme="light"] .menu-co-dien .nav-link.active,
:root[data-theme="dark"] .menu-co-dien .nav-link.active {
    background-color: var(--plx-xanh);
    background-image: linear-gradient(120deg, #0a3d91, #002169);
    color: #fff;
}
:root[data-theme="light"] .nav-tabs .nav-link,
:root[data-theme="dark"] .nav-tabs .nav-link {
    transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}

/* ───── 5. Ô NHẬP: vệt focus màu thương hiệu (cùng liều .18 với ô đăng nhập 362y) ───── */
/* :not(.dn-nen *) BẮT BUỘC ở cả hai theme: thẻ đăng nhập là KÍNH TRẮNG kể cả khi máy lưu theme
   tối — thiếu dấu chừa là bộ focus xanh-nhạt-cho-nền-tối đè lên focus navy đã duyệt 362y
   (bản light trùng giá trị nên không lộ, chỉ dark lộ — luồng rà 381 bắt được). */
/* ⚠ ĐỢT 519 nâng alpha .18 → .7 (sáng) và .22 → .8 (tối): đo sống vệt focus ô nhập chỉ được
   **1,37:1** (sáng) và **1,56:1** (tối) — mờ hơn cả vòng focus của nút. Số sau khi nâng: 4,50 / 4,85.
   Viền `border-color` giữ nguyên: nó là KÊNH THỨ HAI của chỉ báo focus, không phải thứ đang thiếu. */
:root[data-theme="light"] .form-control:focus:not(.dn-nen *),
:root[data-theme="light"] .form-select:focus:not(.dn-nen *) {
    border-color: #0a3d91;
    box-shadow: 0 0 0 .2rem rgba(10, 61, 145, .7);
}
:root[data-theme="dark"] .form-control:focus:not(.dn-nen *),
:root[data-theme="dark"] .form-select:focus:not(.dn-nen *) {
    border-color: #4a7fd6;
    box-shadow: 0 0 0 .2rem rgba(120, 180, 255, .8);
}

/* 🖊 VIỆC o98 (18/08/2026) — TRẢ LẠI VIỀN ĐỎ CHO Ô SAI **ĐANG GÕ**.
   ═══ ĐO SỐNG 18/08/2026, giao diện SÁNG · TỐI · bộ màu E10 — trước khi vá ═══
     ô SAI đang focus ra ĐÚNG màu của ô thường: sáng `rgb(10,61,145)` · tối `rgb(74,127,214)` ·
     E10 `rgb(11,93,59)`. Dấu hiệu "ô này sai" biến mất **đúng lúc người ta đang sửa nó**.
   Gốc bệnh: ba luật focus ngay trên (và luật song sinh trong `bo-mau/ap-dung.css`) đặt
   `border-color` LONGHAND ở độ đặc hiệu (0,5,0), còn `.form-control.is-invalid:focus` của
   Bootstrap chỉ (0,3,0) ⇒ luật app thắng. Cùng họ lỗ đã vá ở đợt 838 · 840 · 846, chỉ khác trạng thái.

   🔴 ⛔ **KHUÔN CỦA ĐỢT 838/840 — ĐẶT `--bs-border-color` — KHÔNG DÙNG ĐƯỢC Ở ĐÂY.** Đây là chỗ dễ
   "sửa cho đồng bộ" nhất rồi hỏng lặng lẽ. Ở trạng thái CƠ SỞ Bootstrap dựng viền bằng SHORTHAND
   `border: … var(--bs-border-color)` nên đặt biến là đủ; nhưng ở `:focus` nó đặt **LONGHAND**
   `.form-control:focus { border-color: #86b7fe }` (0,2,0) — longhand ấy **không đọc biến**.
   Đo sống 18/08 (thay `border-color` thành `--bs-border-color` ở luật focus rồi đo lại): ô SAI ra đỏ
   THẬT, nhưng ô **THƯỜNG** đang gõ tụt về `rgb(134,183,254)` = `#86b7fe` — mất sạch viền navy
   thương hiệu ở MỌI ô nhập của app. ⇒ Longhand ở ba luật focus là **BẮT BUỘC**, không phải nợ.

   ⇒ Cách chữa: một luật KHÔI PHỤC ở (0,6,0) — cao hơn cả (0,5,0) của app lẫn của `ap-dung.css`.
   · `:root:is([data-theme],[data-bo-mau])` là chỗ mang độ đặc hiệu, và nó phủ **cả hai trục** nên
     luật này chữa luôn `bo-mau/ap-dung.css` — ⛔ đừng chép thêm một bản sang bên đó.
   · CHỈ đặt `border-color`. ⛔ KHÔNG đụng `box-shadow`: vòng focus navy/xanh đã đo và duyệt ở đợt
     519 (4,50 / 4,85:1) — giữ nguyên thì viền đỏ nói "SAI" còn vòng sáng nói "đang ở đây", hai kênh
     hai nghĩa. Đổi vòng sang đỏ là phải đi đo lại một màu mới.
   · Dùng `var(--bs-form-invalid-border-color)`, ⛔ đừng ghim mã màu: Bootstrap 5.3 đổi giá trị theo
     `data-bs-theme` (#dc3545 sáng · #ea868f tối) qua cầu nối ở `App.razor` (quy tắc #5).
   · Giữ `:not(.dn-nen *)` đúng khuôn ba luật trên. Thẻ đăng nhập nằm ngoài **có chủ ý** — luật
     `.dn-nen .dn-the .form-control` (0,4,0) vẫn thắng `.is-invalid:focus` (0,3,0) ở đó, và màn hình
     đăng nhập chưa có ô nào dùng `.is-invalid` (đã khai ở sổ miễn trừ của hàng rào).
   📏 Đo lại SAU khi vá: ô SAI đang gõ ra `rgb(220,53,69)` (sáng, 4,53:1 với lòng ô trắng · 4,30:1 với
     nền thân) · `rgb(234,134,143)` (tối, 5,10:1 với lòng ô #2d323a) · `rgb(220,53,69)` (E10);
     `.form-select` y hệt; ô THƯỜNG **không đổi một pixel** ở cả ba. Hàng rào `VienONhapTuongPhanTests`.
   🧨 Bẫy của chính lượt đo này — xem hàng rào để khỏi mất công dò lại: pane trình duyệt không dựng
     khung hình ⇒ **đồng hồ transition đứng yên** nên `getComputedStyle` trả GIÁ TRỊ ĐẦU của chuyển
     tiếp (ô thường đang focus vẫn ra #7f858e). Phải tắt `transition` mới đo được. */
:root:is([data-theme], [data-bo-mau]) .form-control.is-invalid:focus:not(.dn-nen *),
:root:is([data-theme], [data-bo-mau]) .form-select.is-invalid:focus:not(.dn-nen *) {
    border-color: var(--bs-form-invalid-border-color);
}

:root[data-theme="light"] .form-check-input:checked,
:root[data-theme="dark"] .form-check-input:checked {
    background-color: #0a3d91;
    border-color: #0a3d91;
}
/* Vòng focus checkbox tách đôi theo theme: vòng navy .18 trên nền tối gần như vô hình */
/* ⚠ ĐỢT 519 nâng alpha cùng liều với nút và ô nhập (.7 / .8) — cùng một lỗi, cùng một cách chữa. */
:root[data-theme="light"] .form-check-input:focus {
    border-color: #4a7fd6;
    box-shadow: 0 0 0 .2rem rgba(10, 61, 145, .7);
}
:root[data-theme="dark"] .form-check-input:focus {
    border-color: #4a7fd6;
    box-shadow: 0 0 0 .2rem rgba(120, 180, 255, .8);
}

/* ═══ ĐỢT 519 — VÒNG FOCUS CHO HAI BẢN CỔ ĐIỂN (trước nay KHÔNG có luật riêng) ═══
   Hai giao diện cổ điển rơi về vòng chung `0 0 0 .1rem white, 0 0 0 .25rem #258cfb` khai ở đầu file.
   Đo sống: ô nhập chỉ được **1,30–1,77:1** (cổ điển sáng) và **2,06–2,78:1** (cổ điển tối); nút được
   **2,48:1** ở cổ điển sáng. Đều dưới ngưỡng 3:1 cho chỉ báo phi văn bản.
   ⛔ KHÔNG tô 3D, KHÔNG bo góc: cổ điển CỐ Ý trông như phần mềm đời cũ (luật cuối co-dien.css) — ở đây
   chỉ làm ĐẬM vòng focus sẵn có, không thêm hình thức mới.
   Số sau khi sửa: cổ điển sáng **4,50** · cổ điển tối **4,92**. Vẫn chừa `.dn-nen` như hai theme kia. */
:root[data-theme="co-dien"] .btn:focus:not(.dn-nen *),
:root[data-theme="co-dien"] .btn:focus-visible:not(.dn-nen *),
:root[data-theme="co-dien"] .form-control:focus:not(.dn-nen *),
:root[data-theme="co-dien"] .form-select:focus:not(.dn-nen *),
:root[data-theme="co-dien"] .form-check-input:focus {
    box-shadow: 0 0 0 .2rem rgba(10, 61, 145, .7);
}
:root[data-theme="co-dien-toi"] .btn:focus:not(.dn-nen *),
:root[data-theme="co-dien-toi"] .btn:focus-visible:not(.dn-nen *),
:root[data-theme="co-dien-toi"] .form-control:focus:not(.dn-nen *),
:root[data-theme="co-dien-toi"] .form-select:focus:not(.dn-nen *),
:root[data-theme="co-dien-toi"] .form-check-input:focus {
    box-shadow: 0 0 0 .2rem rgba(120, 180, 255, .8);
}

/* ═══ ĐỢT 519 — BẢNG TÊN ĐEO ÁO: TRẢ LẠI VÒNG FOCUS Ở HAI BẢN CỔ ĐIỂN ═══
   `.the-bang-ten` (chuông 🔔, nút Đăng xuất 🚪, thẻ menu trên thanh nav — có mặt ở MỌI màn hình) tự khai
   `box-shadow` nổi khối với thứ hạng cao hơn `.btn:focus` ⇒ ở hai bản cổ điển nó NUỐT HẲN vòng focus:
   đo sống ra `outline: none` và box-shadow KHÔNG ĐỔI giữa lúc nghỉ và lúc focus — tức là bấm Tab tới
   đây thì màn hình không có một dấu hiệu nào. (Hai theme sáng/tối thoát vì có luật `.btn:focus` riêng
   đè lại được.) Đây đúng bẫy đợt 447 đã gặp: BÓNG ĐỔ NUỐT VIỀN — nên chữa bằng `outline`, thứ mà
   box-shadow không đè được, chứ không đi tranh chấp box-shadow.
   Đo sau khi sửa: cổ điển sáng **10,05:1** · cổ điển tối **7,01:1**. */
:root[data-theme="co-dien"] .the-bang-ten:focus-visible {
    outline: 2px solid #0a3d91;
    outline-offset: 2px;
}
:root[data-theme="co-dien-toi"] .the-bang-ten:focus-visible {
    outline: 2px solid #7fb6ff;
    outline-offset: 2px;
}

/* ───── 6. HUY HIỆU: số chưa đọc trên chuông ĐẬP NHỊP nhẹ cho sống động
   (chỉ chuông thanh trên — huy hiệu nhấp nháy cảnh báo của phân hệ Mạng có nhịp riêng, không đụng) ───── */
:root[data-theme="light"] .top-row .badge.bg-danger,
:root[data-theme="dark"] .top-row .badge.bg-danger {
    animation: plx-nhip 2.6s ease-in-out infinite;
}
@keyframes plx-nhip {
    0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(220, 53, 69, .4); }
    50% { transform: scale(1.1); }
    75% { box-shadow: 0 0 0 7px rgba(220, 53, 69, 0); }
}

/* ───── 7. THANH TRÊN + THANH NAV = KÍNH MỜ (nội dung trôi mờ phía sau khi cuộn) ─────
   ⚠ ĐỘ ĐỤC (anh dặn 09/08 "chú ý tương phản nút và chữ ở cả sáng lẫn tối"): kính càng trong thì nền
   càng nhạt theo thứ TRÔI PHÍA SAU — phải tính tương phản theo CA XẤU NHẤT (hero navy #002169 trôi
   dưới thanh). Đã tính tay: sáng .88 → chữ nút #565e64 ≥ 5,2:1 · thanh nav .92 → tab #0a58ca ≥ 5,1:1 ·
   tối .85/.88 → chữ ≥ 6,1:1. ĐỪNG hạ các số đục này xuống cho "kính hơn" — dưới .82 là tụt dưới 4,5:1. */
:root[data-theme="light"] .top-row {
    background-color: rgba(255, 255, 255, .88);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}
:root[data-theme="dark"] .top-row {
    background-color: rgba(45, 50, 58, .85) !important;   /* !important vì luật nền tối cũ đã !important */
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}
:root[data-theme="light"] .menu-co-dien {
    background-color: rgba(247, 247, 247, .92);   /* ⭐ ĐỢT 887 hoàn nguyên 882 — về nền xám cũ, giữ độ đục .92 */
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}
:root[data-theme="dark"] .menu-co-dien {
    background-color: rgba(45, 50, 58, .88);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}

/* ───── 8. MENU THẢ + BẢNG CON CỦA RAIL: hiện có hồn (trượt nhẹ xuống) ───── */
:root[data-theme="light"] .menu-co-dien .mcd-tha,
:root[data-theme="dark"] .menu-co-dien .mcd-tha,
:root[data-theme="light"] .rail-phan-he .rph-con,
:root[data-theme="dark"] .rail-phan-he .rph-con {
    border-radius: 10px;
    animation: plx-tha-hien .16s ease;
}
@keyframes plx-tha-hien {
    from { opacity: 0; transform: translateY(-4px); }
    to { opacity: 1; transform: none; }
}
:root[data-theme="light"] .menu-co-dien .mcd-muc,
:root[data-theme="dark"] .menu-co-dien .mcd-muc,
:root[data-theme="light"] .menu-co-dien .mcd-tha a,
:root[data-theme="dark"] .menu-co-dien .mcd-tha a,
:root[data-theme="light"] .rail-phan-he .rph-con a,
:root[data-theme="dark"] .rail-phan-he .rph-con a {
    transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}
/* Rail: giữ nguyên chuyển động nở ngang, thêm bóng khi rê (khai lại ĐỦ transition vì luật này thắng) */
:root[data-theme="light"] .rail-phan-he .rph-icon,
:root[data-theme="dark"] .rail-phan-he .rph-icon {
    transition: max-width .18s ease, background-color .15s ease, box-shadow .18s ease;
}
:root[data-theme="light"] .rail-phan-he .rph-o:hover .rph-icon { box-shadow: 0 6px 16px rgba(0, 23, 68, .18); }
:root[data-theme="dark"] .rail-phan-he .rph-o:hover .rph-icon { box-shadow: 0 6px 16px rgba(0, 0, 0, .5); }

/* ───── 9. HỘP CẢNH BÁO + NỘI DUNG TRANG: hiện dần rất nhẹ.
   Chừa .thong-bao-noi — toast đã có animation trượt vào riêng (đầu file), đè là mất. ───── */
:root[data-theme="light"] .alert:not(.thong-bao-noi),
:root[data-theme="dark"] .alert:not(.thong-bao-noi) {
    border-radius: 12px;
    animation: plx-hien-nhe .25s ease;
}
/* ⚠ article.content CHỈ ĐƯỢC mờ dần (opacity), CẤM transform: article bọc MỌI trang — transform
   (kể cả đang trong 0,3s animation, và cả khi trình duyệt treo animation lúc tab ẩn) biến nó thành
   containing-block ⇒ mọi position:fixed bên trong (khung ⛶ phóng to biểu đồ nước, toast) neo theo
   TRANG thay vì MÀN HÌNH — đã dính thật lúc đo sống đợt 381, đừng "gọn hóa" về plx-hien-nhe. */
:root[data-theme="light"] article.content,
:root[data-theme="dark"] article.content {
    animation: plx-mo-dan .3s ease;
}
@keyframes plx-mo-dan {
    from { opacity: 0; }
    to { opacity: 1; }
}
@keyframes plx-hien-nhe {
    from { opacity: 0; transform: translateY(4px); }
    to { opacity: 1; transform: none; }
}

} /* hết @media screen — khối hiệu ứng đợt 381 */

/* ───── 10. GIẢM CHUYỂN ĐỘNG (đợt 381): máy khai là ĐỨNG YÊN TOÀN BỘ — mọi giao diện, mọi trang,
   kể cả hiệu ứng viết trong <style> từng trang (đăng nhập 362y, trang chủ 371, 5 khối Mạng) —
   !important trên * thắng mọi specificity nên không khối reduce cục bộ nào phải tự lo nữa.
   Duration .01ms thay vì none: animation vẫn "chạy xong" tức thì nên phần tử về đúng trạng thái
   CUỐI (thẻ đăng nhập dn-hien fill both vẫn HIỆN RA, không kẹt ở khung đầu trong suốt). ───── */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   ĐỢT 386 — ĐỘ TƯƠNG PHẢN CHỮ (anh Tiến báo 09/08/2026 kèm ảnh: "tương phản kém")

   Ngưỡng áp dụng: WCAG AA — chữ thường ≥ 4,5:1, chữ to/đậm ≥ 3:1. Mọi con số ghi trong khối này
   đều ĐO THẬT bằng bộ đo chạy trong trang (tính nền HIỆU DỤNG sau khi hoà mọi lớp bán trong suốt
   và mọi opacity của cha), không phải ước lượng.

   ⚠ BÀI HỌC CHUNG CỦA CẢ ĐỢT — "màu chữ theo GIAO DIỆN, nền theo THÀNH PHẦN" là công thức hỏng:
   chữ lấy màu từ `body` (đổi theo sáng/tối) mà nền lại là màu GẮN CỨNG của riêng khối đó thì hai
   bên trôi ngược nhau. Mọi lỗi mục 1 dưới đây đều cùng một gốc ấy. Làm khối mới có nền gắn cứng
   ⇒ phải ép luôn màu chữ trong khối đó, đừng để nó kế thừa từ body.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */

/* ───── 1. TRANG ĐĂNG NHẬP LÀ MỘT "ĐẢO SÁNG" — ép chữ tối ở CẢ 4 GIAO DIỆN ─────
   Thẻ đăng nhập cố ý là KÍNH TRẮNG `rgba(255,255,255,.94)` ở mọi giao diện (LayoutTrong.razor),
   nền hiệu dụng ≈ #f0f2f6. Nhưng nhãn/liên kết/chữ phụ KHÔNG có màu riêng nên kế thừa màu chữ của
   body — ở nền tối màu đó là #dde1e6 (app.css) / #e6e9ed (co-dien.css) ⇒ chữ SÁNG trên nền TRẮNG:
       "Tên đăng nhập" · "Mật khẩu" · "Ghi nhớ đăng nhập" ... 1,16:1  (co-dien-toi: 1,07:1)
       "Hệ thống quản lý công việc nội bộ" (.text-muted) ... 2,3:1
       "Quên mật khẩu?" ................................... 1,95:1
   Đợt 381 đã ngờ chuyện này nên chừa `:not(.dn-nen *)` cho vòng focus và nút, nhưng CHỈ chừa hiệu
   ứng — màu chữ vẫn bỏ ngỏ. Nay chốt một lần: mọi thứ bên trong .dn-nen dùng bảng màu NỀN SÁNG.
   Trọng số (0,4,0) cố ý cao hơn luật (0,3,0) của cả app.css lẫn co-dien.css ⇒ thắng bất kể co-dien
   .css nạp sau, KHÔNG cần !important (trừ .text-muted vì Bootstrap khai sẵn !important). */
:root[data-theme] .dn-nen .dn-the,
:root[data-theme] .dn-nen .dn-the .form-label,
:root[data-theme] .dn-nen .dn-the .form-check-label,
:root[data-theme] .dn-nen .dn-the label {
    color: #14202e;   /* 13,7:1 trên #f0f2f6 */
}

:root[data-theme] .dn-nen .dn-the :is(.text-muted, .text-body-secondary) {
    color: #55606d !important;   /* 5,7:1 — vẫn "phụ" nhưng đọc được */
}

:root[data-theme] .dn-nen .dn-the a:not(.btn) {
    color: #0a3d91;   /* 8,9:1 — navy thương hiệu, hợp vệt cam của thẻ */
}

/* Ô nhập: nền TỐI (#2d323a của giao diện tối / #22262b của cổ điển tối) nằm lọt trong thẻ trắng
   vừa chối mắt vừa làm chữ gợi ý tụt xuống 3,9:1. Kéo về ô sáng cho khớp cái thẻ.
   🔴 ĐỢT 838 (việc o89) — viền `#b9c0c9` chỉ được **1,83:1** với lòng ô trắng, trượt ngưỡng 3:1 của
   chỉ báo phi văn bản y như viền mặc định của Bootstrap. Màn hình đăng nhập KHÔNG tự khỏi bệnh bằng
   bản vá (3c): luật ở đây đặt `border-color` **longhand** ở (0,4,0) nên nó thắng token đặt trên ô nhập
   — chữa thì phải chữa ngay tại dòng này. Dùng chung biến `--tknb-vien-o-nhap` để hai nơi không lệch
   nhau về sau; giá trị lui `#7f858e` là để luật vẫn đúng kể cả khi thẻ này bị lôi ra khỏi cây có
   `[data-theme]` (viền `var()` không phân giải được là **rơi cả khai báo**, bài học đợt 823). */
:root[data-theme] .dn-nen .dn-the .form-control,
:root[data-theme] .dn-nen .dn-the .form-select {
    background-color: #fff;
    color: #14202e;
    border-color: var(--tknb-vien-o-nhap, #7f858e);
}

:root[data-theme] .dn-nen .dn-the .form-control::placeholder {
    color: #6b737d;   /* 4,8:1 trên nền trắng của ô */
}

/* ⛔ ĐỢT 659 — SỬA MÀU **VÀ** SỬA CON SỐ. Chú thích cũ ghi *"viền ô tick 3,1:1 — đạt ngưỡng"*, đo lại
   14/08/2026 thì `#97a0ab` trên nền trắng chỉ được **2,65:1**, tức **TRƯỢT** ngưỡng 3:1 của chỉ báo phi
   văn bản. Đây đúng loại lỗi dự án sợ nhất: build xanh, chú thích **khẳng định đạt**, số thì không —
   và con số sai ấy sống tới khi có người đi đo lại.
   ⇒ `#868e96` = **3,32:1** (đo cùng lượt). Đây là ô tick "Ghi nhớ đăng nhập" ở màn hình đăng nhập,
     tức thứ **mọi người đều nhìn thấy trước khi vào được phần mềm**. */
:root[data-theme] .dn-nen .dn-the .form-check-input:not(:checked) {
    background-color: #fff;
    border-color: #868e96;   /* 3,32:1 trên nền trắng của ô — đo 14/08/2026 */
}

/* ───── 2. CHỮ PHỤ Ở GIAO DIỆN SÁNG (.text-muted / .form-text) ─────
   Ba giao diện kia đều đã đè màu chữ phụ, riêng `light` bỏ quên nên giữ nguyên #6c757d của
   Bootstrap: 4,69:1 trên nền TRẮNG THUẦN — nghe thì đạt, nhưng app gần như không có chỗ nào là
   trắng thuần. Đứng trên hàng sọc chẵn (rgba(0,0,0,.028)), .card-header gradient, ô .table-light
   hay thanh kính .top-row là tụt xuống 4,3–4,5 ⇒ đúng cảm giác "chữ khó đọc" anh báo.
   #5c636a: 6,1:1 trên trắng, vẫn còn ≥ 5,5:1 trên các nền xám nhạt kể trên. */
:root[data-theme="light"] :is(.text-muted, .text-body-secondary),
:root[data-theme="light"] .form-text {
    color: #5c636a !important;
}

/* ───── 3. DÒNG "ĐÃ NGHỈ / NGỪNG DÙNG" TRONG BẢNG — mờ bằng MÀU, không bằng opacity ─────
   6 bảng đang đánh dấu dòng đã nghỉ bằng `class="opacity-50"` (danh sách CBCNV, đồng hồ nước ngừng
   dùng, thiết bị mạng ngừng theo dõi, cảnh báo đã xử lý…).
   ⚠ Vì sao opacity là công cụ SAI ở đây: nó hoà TOÀN BỘ dòng — kể cả phần tử con vốn tự có nền —
   vào nền phía sau, nên nó phá luôn tương phản NỘI BỘ của con. Đo thật trên dòng "Đã nghỉ":
       chữ thường  #212529 @50% trên trắng → #909294 ... 3,1:1  (trượt)
       huy hiệu "Đã nghỉ" (chữ trắng/nền #6c757d) ...... 1,96:1  (gần như mất chữ)
   Đổi màu chữ thì con giữ nguyên tương phản của nó; dòng vẫn "nhạt hơn hàng đang làm việc" đúng ý
   nghiệp vụ, nhưng đọc được. Chốt ở CSS nên không phải sửa 6 trang .razor (đang có phiên khác giữ).
   Chỉ bắt `tr` — `.opacity-50` dùng cho biểu tượng, ảnh… ở chỗ khác giữ nguyên. */
tr.opacity-50, tr.opacity-75 {
    opacity: 1 !important;
}

:root[data-theme="light"] tr.opacity-50, :root[data-theme="light"] tr.opacity-75,
:root[data-theme="light"] tr.opacity-50 td, :root[data-theme="light"] tr.opacity-75 td,
:root[data-theme="light"] tr.opacity-50 th, :root[data-theme="light"] tr.opacity-75 th {
    color: #5c636a;   /* 6,1:1 — nhạt hơn #212529 của dòng thường, vẫn đạt AA */
}

:root[data-theme="dark"] tr.opacity-50, :root[data-theme="dark"] tr.opacity-75,
:root[data-theme="dark"] tr.opacity-50 td, :root[data-theme="dark"] tr.opacity-75 td,
:root[data-theme="dark"] tr.opacity-50 th, :root[data-theme="dark"] tr.opacity-75 th {
    color: #9aa3ad;   /* 6,4:1 trên #1c1f24 — nhạt hơn #dde1e6 của dòng thường */
}

/* ───── 4. CHỮ VÀNG TRẦN TRÊN NỀN SÁNG ─────
   `.text-warning` của Bootstrap là #ffc107 — dùng làm NỀN huy hiệu thì đúng (đã vá ở đợt 205,
   .text-bg-warning = chữ đen trên vàng), nhưng dùng làm CHỮ trên nền trắng chỉ được 1,63:1.
   Đang có ở: dòng ⓘ trang Giao việc · Nghỉ phép · chú giải biểu đồ Mạng · Lộ trình trang…
   ⇒ nền sáng dùng vàng-nâu #8a6100 (5,6:1) — vẫn đọc ra "cảnh báo", không phải đỏ (đỏ đã dành cho
   số bất lợi theo luật màu của anh). Nền TỐI giữ nguyên #ffc107 (10:1 — đang tốt).
   Chừa `.navbar-brand`: chữ vàng đó nằm trên thanh bên NAVY, đổi sang nâu là hỏng. */
:root[data-theme="light"] .text-warning:not(.navbar-brand):not(.top-row *),
:root[data-theme="co-dien"] .text-warning:not(.navbar-brand):not(.top-row *) {
    color: #8a6100 !important;
}

/* Ba màu chữ ngữ nghĩa còn lại ở NỀN SÁNG: chúng chỉ vừa đủ chuẩn trên nền TRẮNG THUẦN, mà chữ này
   hầu như luôn nằm trong ô đã tô màu hoặc hàng sọc. Đo thật ở bảng công tháng, giao diện sáng:
       giờ vào/ra .text-danger trên ô XANH (#d1e7dd) ... 3,49:1
       cùng chữ đó trên ô VÀNG (#fff3cd) ............... 4,09:1
       .text-danger trên đầu bảng #f8f8f8 .............. 4,27:1
       .text-info (#0dcaf0) trên nền trắng ............. 1,90:1  ← tệ nhất, xanh lơ vốn rất sáng
   ⇒ Hạ một nấc theo thang chính thức của Bootstrap (danger-600 / success-600 / primary-600 /
   info-800): mắt vẫn đọc ra đỏ-xanh-lá-xanh-dương như cũ, nhưng qua ngưỡng trên MỌI nền nhạt.
   Chừa đúng 4 nơi như mục 7 (nền riêng của chúng đã tính tương phản sẵn). */
:root[data-theme="light"] .text-danger:not(.dn-nen *):not(.alert *):not(.badge):not(.btn),
:root[data-theme="co-dien"] .text-danger:not(.dn-nen *):not(.alert *):not(.badge):not(.btn) {
    color: #b02a37 !important;   /* 5,0:1 trên ô xanh · 5,9:1 trên ô vàng · 6,5:1 trên trắng */
}

:root[data-theme="light"] .text-success:not(.dn-nen *):not(.alert *):not(.badge):not(.btn),
:root[data-theme="co-dien"] .text-success:not(.dn-nen *):not(.alert *):not(.badge):not(.btn) {
    color: #146c43 !important;
}

:root[data-theme="light"] .text-primary:not(.dn-nen *):not(.alert *):not(.badge):not(.btn),
:root[data-theme="co-dien"] .text-primary:not(.dn-nen *):not(.alert *):not(.badge):not(.btn) {
    color: #0a58ca !important;
}

:root[data-theme="light"] .text-info:not(.dn-nen *):not(.alert *):not(.badge):not(.btn),
:root[data-theme="co-dien"] .text-info:not(.dn-nen *):not(.alert *):not(.badge):not(.btn) {
    color: #087990 !important;
}

/* ───── 5. BÙ CÁC LỚP CỦA BOOTSTRAP 5.3 MÀ BẢN 5.1 CHƯA CÓ ─────
   Cùng họ lỗi với `.text-bg-*` vá ở đợt 205: app đang dùng Bootstrap 5.1 nhưng 9 chỗ trong 8 trang
   viết `text-*-emphasis` / `bg-*-subtle` (lớp chỉ có từ 5.2–5.3). Không lớp nào tồn tại ⇒ chữ rơi
   về màu mặc định, chỗ tác giả muốn NHẤN lại thành chữ thường — build sạch, không câu lỗi nào.
   Cặp màu lấy ĐÚNG bảng chính thức của Bootstrap 5.3 để sau này nâng lên 5.3 giao diện không đổi.
   Nền sáng và nền tối dùng hai bảng khác nhau; cổ điển đi theo bảng cùng sắc độ với nó. */
:root[data-theme="light"] .text-primary-emphasis,   :root[data-theme="co-dien"] .text-primary-emphasis   { color: #052c65 !important; }
:root[data-theme="light"] .text-secondary-emphasis, :root[data-theme="co-dien"] .text-secondary-emphasis { color: #2b2f32 !important; }
:root[data-theme="light"] .text-success-emphasis,   :root[data-theme="co-dien"] .text-success-emphasis   { color: #0a3622 !important; }
:root[data-theme="light"] .text-danger-emphasis,    :root[data-theme="co-dien"] .text-danger-emphasis    { color: #58151c !important; }
:root[data-theme="light"] .text-warning-emphasis,   :root[data-theme="co-dien"] .text-warning-emphasis   { color: #664d03 !important; }
:root[data-theme="light"] .text-info-emphasis,      :root[data-theme="co-dien"] .text-info-emphasis      { color: #055160 !important; }

:root[data-theme="dark"] .text-primary-emphasis,   :root[data-theme="co-dien-toi"] .text-primary-emphasis   { color: #9ec5fe !important; }
:root[data-theme="dark"] .text-secondary-emphasis, :root[data-theme="co-dien-toi"] .text-secondary-emphasis { color: #c4c8cb !important; }
:root[data-theme="dark"] .text-success-emphasis,   :root[data-theme="co-dien-toi"] .text-success-emphasis   { color: #a3cfbb !important; }
:root[data-theme="dark"] .text-danger-emphasis,    :root[data-theme="co-dien-toi"] .text-danger-emphasis    { color: #f1aeb5 !important; }
:root[data-theme="dark"] .text-warning-emphasis,   :root[data-theme="co-dien-toi"] .text-warning-emphasis   { color: #ffe69c !important; }
:root[data-theme="dark"] .text-info-emphasis,      :root[data-theme="co-dien-toi"] .text-info-emphasis      { color: #9eeaf9 !important; }

/* Nền nhạt: PHẢI đi kèm màu chữ, nếu không ở giao diện tối chữ sáng sẽ nằm trên nền pastel sáng —
   đúng cái bẫy đã ghi ở khối "Ô/HÀNG BẢNG TÔ MÀU" phía trên. */
:root[data-theme="light"] .bg-primary-subtle, :root[data-theme="co-dien"] .bg-primary-subtle { background-color: #cfe2ff !important; color: #052c65; }
:root[data-theme="light"] .bg-success-subtle, :root[data-theme="co-dien"] .bg-success-subtle { background-color: #d1e7dd !important; color: #0a3622; }
:root[data-theme="light"] .bg-danger-subtle,  :root[data-theme="co-dien"] .bg-danger-subtle  { background-color: #f8d7da !important; color: #58151c; }
:root[data-theme="light"] .bg-warning-subtle, :root[data-theme="co-dien"] .bg-warning-subtle { background-color: #fff3cd !important; color: #664d03; }
:root[data-theme="light"] .bg-info-subtle,    :root[data-theme="co-dien"] .bg-info-subtle    { background-color: #cff4fc !important; color: #055160; }
:root[data-theme="light"] .bg-secondary-subtle, :root[data-theme="co-dien"] .bg-secondary-subtle { background-color: #e2e3e5 !important; color: #2b2f32; }

:root[data-theme="dark"] .bg-primary-subtle, :root[data-theme="co-dien-toi"] .bg-primary-subtle { background-color: #031633 !important; color: #9ec5fe; }
:root[data-theme="dark"] .bg-success-subtle, :root[data-theme="co-dien-toi"] .bg-success-subtle { background-color: #051b11 !important; color: #a3cfbb; }
:root[data-theme="dark"] .bg-danger-subtle,  :root[data-theme="co-dien-toi"] .bg-danger-subtle  { background-color: #2c0b0e !important; color: #f1aeb5; }
:root[data-theme="dark"] .bg-warning-subtle, :root[data-theme="co-dien-toi"] .bg-warning-subtle { background-color: #332701 !important; color: #ffe69c; }
:root[data-theme="dark"] .bg-info-subtle,    :root[data-theme="co-dien-toi"] .bg-info-subtle    { background-color: #032830 !important; color: #9eeaf9; }
:root[data-theme="dark"] .bg-secondary-subtle, :root[data-theme="co-dien-toi"] .bg-secondary-subtle { background-color: #161719 !important; color: #c4c8cb; }

/* ───── 7. CHỮ MÀU NGỮ NGHĨA CỦA BOOTSTRAP Ở NỀN TỐI (lỗ hổng lớn nhất tìm được ở đợt này) ─────
   `.text-danger` (#dc3545) · `.text-success` (#198754) · `.text-primary` (#0d6efd) là màu chọn cho
   nền TRẮNG và Bootstrap 5.1 KHÔNG đổi chúng theo giao diện. Đo thật ở giao diện tối:
       giờ vào/ra tô đỏ trong bảng công ... 2,38:1 (trên ô vàng tối) · 2,70:1 (trên ô xanh tối)
       số liệu .text-danger trong bảng .... 3,11:1 (trên nền hàng chẵn #272c32)
       .text-success / .text-primary ...... 3,58:1 · 3,60:1 (trên nền thân #1c1f24)
   Đây đúng họ lỗi đã vá hai lần trước: `.text-bg-*` chữ trắng trên nền trắng (đợt 205) và
   `.text-body` chữ đen trên nền đen (đợt 228) — Bootstrap 5.1 gắn cứng màu, mỗi đợt lại lòi ra một
   lớp còn sót. Cặp màu dưới đây lấy đúng bảng "-emphasis" của Bootstrap 5.3 cho nền tối.
   ⚠ Ý nghĩa màu GIỮ NGUYÊN theo luật của anh: số bất lợi vẫn ĐỎ, có lợi vẫn XANH — chỉ sáng hơn.
   Chừa 4 nơi chữ nằm trên nền SÁNG riêng của nó (đổi vào là hỏng ngược):
     .dn-nen *  → đảo sáng của trang đăng nhập (mục 1)
     .alert *   → hộp cảnh báo giữ nền pastel sáng của Bootstrap
     .badge     → huy hiệu tự có nền đặc (.text-bg-* đã vá ở đợt 205)
     .btn       → nút tự có nền/viền riêng, đã chỉnh ở khối đợt 381 */
:root[data-theme="dark"] .text-danger:not(.dn-nen *):not(.alert *):not(.badge):not(.btn),
:root[data-theme="co-dien-toi"] .text-danger:not(.dn-nen *):not(.alert *):not(.badge):not(.btn) {
    color: #f1aeb5 !important;   /* 7,7:1 trên nền bảng · 5,9:1 trên ô vàng tối */
}

:root[data-theme="dark"] .text-success:not(.dn-nen *):not(.alert *):not(.badge):not(.btn),
:root[data-theme="co-dien-toi"] .text-success:not(.dn-nen *):not(.alert *):not(.badge):not(.btn) {
    color: #a3cfbb !important;
}

:root[data-theme="dark"] .text-primary:not(.dn-nen *):not(.alert *):not(.badge):not(.btn),
:root[data-theme="co-dien-toi"] .text-primary:not(.dn-nen *):not(.alert *):not(.badge):not(.btn) {
    color: #9ec5fe !important;
}

:root[data-theme="dark"] .text-info:not(.dn-nen *):not(.alert *):not(.badge):not(.btn),
:root[data-theme="co-dien-toi"] .text-info:not(.dn-nen *):not(.alert *):not(.badge):not(.btn) {
    color: #9eeaf9 !important;
}

:root[data-theme="dark"] .text-secondary:not(.dn-nen *):not(.alert *):not(.badge):not(.btn),
:root[data-theme="co-dien-toi"] .text-secondary:not(.dn-nen *):not(.alert *):not(.badge):not(.btn) {
    color: #c4c8cb !important;
}

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   ĐỢT 386b — THẺ "BẢNG TÊN ĐEO ÁO" cho cả dãy nút trên thanh (anh Tiến 09/08/2026)

   Nguyên văn: *"nút Petrolimex khá đẹp nhưng gây cảm giác LÕM XUỐNG, anh muốn nó LỒI LÊN và HƠI
   BÓNG NHƯ BẢNG TÊN ĐEO ÁO; 'Thông báo', 'tên tài khoản', 'nút đăng xuất',… đều làm giống thế"*.

   VÌ SAO TRƯỚC ĐÂY THẤY LÕM: chip logo chỉ có `box-shadow: 0 1px 4px` — bóng đổ mềm, nền PHẲNG một
   màu, viền tối đều 4 cạnh. Mắt người đọc khối nổi bằng **hướng ánh sáng**: vật lồi thì mép TRÊN
   sáng (hứng sáng) và đổ bóng xuống DƯỚI. Thiếu gờ sáng mép trên mà lại có viền tối bao quanh thì
   não đọc ngược thành vết lõm — đúng cảm giác anh mô tả.

   CÔNG THỨC (4 lớp, thiếu lớp nào là mất cảm giác nổi):
     1. nền GRADIENT dọc sáng→tối  → mặt cong lồi
     2. `inset 0 1px 0` trắng      → GỜ SÁNG mép trên (thứ đang thiếu)
     3. `inset 0 -1px 0` tối       → mép dưới ăn bóng
     4. bóng đổ xuống dưới         → tách khỏi mặt thanh
   + `::before` phủ NỬA TRÊN = vệt bóng nhựa của bảng tên thật.
   ⚠ Dùng `::before` chứ KHÔNG phải `::after`: `::after` là chỗ của hiệu ứng gợn sóng khi bấm (đợt
   381). Ở `.top-row` hiệu ứng đó đã bị loại trừ, nhưng lớp này còn dùng cho nút ngoài thanh về sau.

   CẢM GIÁC VẬT LÝ: rê chuột thì nhô thêm 1px + bóng dày hơn; bấm thì LÚN xuống 1px + bóng xẹp.
   Máy khai "giảm chuyển động" tự bỏ transition nhờ luật `prefers-reduced-motion` ở mục 10 đợt 381.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */
.the-bang-ten {
    position: relative;
    border-radius: 7px;
    background: linear-gradient(180deg, #fff 0%, #f2f5f9 55%, #e8edf4 100%);
    border: 1px solid rgba(0, 33, 105, .22);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .95),
        inset 0 -1px 0 rgba(0, 23, 68, .10),
        0 1px 1px rgba(0, 23, 68, .10),
        0 3px 6px rgba(0, 23, 68, .18);
    transition: transform .12s ease, box-shadow .12s ease;
}

/* Vệt bóng nhựa phủ nửa trên. `inherit` bán kính để không lòi góc; không bắt chuột. */
.the-bang-ten::before {
    content: "";
    position: absolute;
    inset: 0 0 50% 0;
    border-radius: inherit;
    background: linear-gradient(180deg, rgba(255, 255, 255, .55), transparent);
    pointer-events: none;
}

.the-bang-ten:hover {
    transform: translateY(-1px);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .95),
        inset 0 -1px 0 rgba(0, 23, 68, .10),
        0 2px 2px rgba(0, 23, 68, .12),
        0 6px 12px rgba(0, 23, 68, .24);
}

.the-bang-ten:active {
    transform: translateY(1px);
    box-shadow:
        inset 0 1px 2px rgba(0, 23, 68, .22),
        0 1px 1px rgba(0, 23, 68, .10);
}

/* Thẻ là thẻ <a> (🔔 Thông báo · 👤 tên) thì Bootstrap không cho padding — tự cấp cho bằng nút. */
.top-row a.the-bang-ten {
    display: inline-flex;
    align-items: center;
    gap: .25rem;
    padding: .25rem .55rem;
}

/* Cụm 4 nút đổi giao diện là MỘT dãy liền (btn-group) — bo góc chỉ ở hai đầu, giữa phẳng, nếu không
   4 thẻ rời rạc trông vụn. Bóng đổ để nguyên từng nút cho ra khối. */
.top-row .btn-group .the-bang-ten { border-radius: 0; }
.top-row .btn-group .the-bang-ten:first-child { border-radius: 7px 0 0 7px; }
.top-row .btn-group .the-bang-ten:last-child { border-radius: 0 7px 7px 0; }

/* Chip LOGO giữ nền TRẮNG ở MỌI giao diện (luật đợt 373: logo navy+cam cần nền trắng mới nổi) ⇒
   biến thể này KHÔNG đổi theo nền tối.
   ⭐ ĐỢT 887 hoàn nguyên đợt 880 (chip trong suốt ở nền sáng) — anh Tiến chốt 19/08 tối: "anh cần lấy
   lại thanh nav và logo 3d cũ, phiên bản hiện tại quá xấu". Câu này MỚI HƠN câu "logo không trùng màu
   thanh nav" sáng 19/08 ⇒ ⛔ đừng áp lại chip trong suốt khi chưa có chỉ đạo mới. */
.the-bang-ten-sang {
    background: linear-gradient(180deg, #fff 0%, #fbfcfe 55%, #f2f5f9 100%) !important;
    border-color: rgba(0, 33, 105, .26) !important;
}

/* ── Nền TỐI: lật công thức (nền tối dần, gờ sáng mờ hơn, bóng đổ đậm hơn) ── */
:root[data-theme="dark"] .the-bang-ten,
:root[data-theme="co-dien-toi"] .the-bang-ten {
    background: linear-gradient(180deg, #3a4048 0%, #333941 55%, #2c3138 100%);
    border-color: rgba(255, 255, 255, .20);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .12),
        inset 0 -1px 0 rgba(0, 0, 0, .35),
        0 1px 1px rgba(0, 0, 0, .35),
        0 3px 8px rgba(0, 0, 0, .45);
}

:root[data-theme="dark"] .the-bang-ten::before,
:root[data-theme="co-dien-toi"] .the-bang-ten::before {
    background: linear-gradient(180deg, rgba(255, 255, 255, .10), transparent);
}

:root[data-theme="dark"] .the-bang-ten:hover,
:root[data-theme="co-dien-toi"] .the-bang-ten:hover {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .14),
        inset 0 -1px 0 rgba(0, 0, 0, .35),
        0 2px 3px rgba(0, 0, 0, .38),
        0 7px 14px rgba(0, 0, 0, .5);
}

:root[data-theme="dark"] .the-bang-ten:active,
:root[data-theme="co-dien-toi"] .the-bang-ten:active {
    box-shadow:
        inset 0 1px 3px rgba(0, 0, 0, .55),
        0 1px 1px rgba(0, 0, 0, .3);
}

/* ── Hai giao diện CỔ ĐIỂN: nổi TIẾT CHẾ ──
   Cổ điển cố ý mang dáng "phần mềm cũ, nút vuông" (luật module CoDien) — bóng bẩy quá là trái tông.
   Giữ đủ gờ sáng + viền để hết cảm giác lõm, nhưng bo góc gần vuông và bóng đổ mỏng. */
:root[data-theme^="co-dien"] .the-bang-ten {
    border-radius: 3px;
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .85),
        inset 0 -1px 0 rgba(0, 23, 68, .12),
        0 1px 2px rgba(0, 23, 68, .18);
}

:root[data-theme^="co-dien"] .the-bang-ten::before { inset: 0 0 60% 0; opacity: .7; }
:root[data-theme^="co-dien"] .top-row .btn-group .the-bang-ten:first-child { border-radius: 3px 0 0 3px; }
:root[data-theme^="co-dien"] .top-row .btn-group .the-bang-ten:last-child { border-radius: 0 3px 3px 0; }

:root[data-theme="co-dien-toi"] .the-bang-ten {
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .12),
        inset 0 -1px 0 rgba(0, 0, 0, .4),
        0 1px 2px rgba(0, 0, 0, .45);
}

/* Chữ trên thẻ: nền sáng ⇒ chữ NAVY ĐẬM (13,1:1) thay vì xám nhạt của nút Bootstrap — đúng việc
   chính của đợt này. Nền tối đã có màu chữ sáng sẵn từ các luật giao diện. */
:root[data-theme="light"] .top-row .the-bang-ten,
:root[data-theme="co-dien"] .top-row .the-bang-ten {
    color: #14284a;
}

/* Mũi ▾ của nút Menu: nền nút nay là THẺ nổi (#3a4048) chứ không còn là mặt thanh, nên #9aa0a8 tụt
   xuống 3,97:1 — đo sau khi gom thanh. Đậm lên cho khớp nền mới. */
:root[data-theme="dark"] .the-bang-ten .mcd-mui,
:root[data-theme="co-dien-toi"] .the-bang-ten .mcd-mui {
    color: #c7ccd2;
}

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   ĐỢT 386c — HAI NGUỒN "NỀN SÁNG CỐ ĐỊNH" CÒN SÓT Ở GIAO DIỆN TỐI
   Tìm ra khi quét TỰ ĐỘNG 18 trang × 4 giao diện (trước đó chỉ đo 7 trang bằng tay).
   ══════════════════════════════════════════════════════════════════════════════════════════════ */

/* ───── 12. BIẾN `--bs-body-bg` PHẢI ĐỔI THEO GIAO DIỆN ─────
   Bootstrap 5.1 khai `--bs-body-bg: #fff` CỐ ĐỊNH (mãi 5.3 mới đổi theo theme). Nhiều bảng dài dùng
   đầu bảng DÍNH và phải tự tô nền để hàng cuộn không chui qua chữ — cách viết tự nhiên nhất là
   `style="background: var(--bs-body-bg)"` ngay trong trang. Ở giao diện tối, biến đó vẫn trả #fff
   ⇒ đầu bảng TRẮNG mà chữ vẫn #dde1e6: đo thật **1,24:1** ở `/quan-tri/nguoi-dung` và `/quy-doi`.
   Vì nền đặt bằng style INLINE nên không luật CSS nào đè được — phải sửa từ gốc là chính cái biến.
   ⇒ Khai lại biến theo giao diện: mọi chỗ (kể cả inline, kể cả trang viết sau này) tự đúng.
   Đây là cách vá rộng nhất mà vẫn không đụng file trang nào. */
:root[data-theme="dark"] {
    --bs-body-bg: #1c1f24;
    --bs-body-color: #dde1e6;
}

/* ───── 12b. TOKEN `--bs-border-color` KHÔNG HỀ TỒN TẠI Ở BOOTSTRAP 5.1 (đợt 766, việc o41) ─────
   Token này mãi **Bootstrap 5.2** mới có; app đang nạp **v5.1.0** (`wwwroot/bootstrap/bootstrap.min.css`
   — đo: `grep -c -- "--bs-border-color"` ra **0**). Vậy mà app viết `var(--bs-border-color)` **trần,
   không giá trị lui** ở **9 chỗ**. Var không phân giải được nằm trong `border` (shorthand) thì cả khai
   báo bị vứt — "invalid at computed-value time" — ⇒ `border-style` rơi về `none`, **không một câu lỗi
   nào**, build sạch, test sạch.
   📏 ĐO TẬN NƠI trước khi sửa (bàn đo thuần CSS: nhúng đủ 6 tệp đúng thứ tự App.razor vào MỘT tài liệu
   cấp cao nhất — trình duyệt đo đạc chặn tệp con của localhost, quy tắc #6; chốt ngược `d-flex` phải ra
   `display:flex` mới tin số; khung nhìn 1400px; đo cả sáng lẫn tối):
       TRƯỚC: 4/4 chỗ đo được → `border-top-style: none · width: 0px`   (token đọc ra RỖNG)
       SAU  : 4/4 chỗ         → `solid · 1px · rgb(222,226,230)` (sáng) · `rgb(58,63,71)` (tối)
   ⇒ Cái "viền" mắt thường vẫn thấy ở nút 3D là **bóng inset** của khuôn đợt 447, KHÔNG phải viền —
   viền ấy chết ngay từ đợt 447 mà không ai biết. Cùng bệnh với mục 12 ngay trên (token thiếu chứ luật
   không sai) ⇒ vá đúng chỗ là **khai lại token**, không đi sửa 9 chỗ dùng.
   ⚠ Ai nâng Bootstrap lên ≥ 5.2 thì **bỏ khối này** — bản mới tự khai token theo `data-bs-theme`, giữ
   lại là đè lên bảng màu của chính nó.
   ⓘ Khai cho cả hai nền CỔ ĐIỂN dù chúng đang ĐÓNG BĂNG (quy tắc #20): đây là **chữa biến chết**, không
   phải phát triển thêm — 5 trong 9 chỗ dùng (dải tab xuống hàng · hộp chú thích chuột · 2 khung sa bàn ·
   ảnh trình chiếu) áp cho MỌI giao diện, khai thiếu là cổ điển tối lãnh viền sáng chói. Vẫn chỉ ĐO hai
   giao diện hiện đại.

   ✅ **ĐỢT 831 (việc o73) — BA DÒNG KHAI TOKEN ĐÃ BỎ, đúng câu dặn ngay trên.** App nay nạp
   **Bootstrap 5.3.8**, bản này TỰ khai `--bs-border-color` (đo: 36 lượt trong tệp) theo trục
   `data-bs-theme` mà đợt 831 đã bắc cầu. Giữ lại ba dòng đó là **đè lên bảng màu của chính bản
   mới** — và vì `:root` trần có độ đặc hiệu (0,1,0) **BẰNG** `[data-bs-theme=dark]` của Bootstrap
   mà `app.css` nạp SAU, dòng `:root` trần sẽ thắng cả ở nền tối, tức ghim token ở giá trị nền sáng.
   ⚠ Giữ nguyên khối chú thích này làm **lịch sử**: nó là bằng chứng đo được của lỗi viền chết đợt
   447→766, và là lời giải thích vì sao đừng khai lại token `--bs-*` bằng tay lần nữa. */

/* ───── 13b. HỘP CẢNH BÁO `.alert` Ở GIAO DIỆN TỐI ─────
   Bootstrap 5.1 cho `.alert-*` nền PASTEL SÁNG cố định. Ở giao diện tối, chữ bên trong lại lấy màu
   của giao diện ⇒ chữ sáng trên nền sáng:
       `.alert-light` + `.text-muted` ....... 2,61:1 (`/danh-gia-thang`, `/danh-gia-quy`)
       nút 🚨 trong `.alert-warning` ........ 1,65:1 (`/quan-tri/nguoi-dung`)
   ⚠ Cái 1,65:1 là HỒI QUY do chính mục 13 của đợt này gây ra: đổi `.btn-outline-danger` sang hồng
   nhạt cho nền tối, nhưng nút đó lại đang nằm trên nền vàng nhạt của alert. Bài học: đổi màu chữ
   theo giao diện thì phải hỏi "chữ này có nằm trên NỀN CỐ ĐỊNH nào không" — lần thứ ba trong đợt.
   ⇒ Chọn cách gốc: cho `.alert` nền TỐI luôn (đúng như Bootstrap 5.3 làm ở dark mode), thay vì đi
   chừa `.alert *` ở từng luật màu chữ — chừa kiểu đó chắc chắn sót chỗ. Cặp màu lấy đúng bảng 5.3. */
:root[data-theme="dark"] .alert-primary   { background-color: #031633; border-color: #084298; color: #9ec5fe; }
:root[data-theme="dark"] .alert-secondary { background-color: #161719; border-color: #41464b; color: #c4c8cb; }
:root[data-theme="dark"] .alert-success   { background-color: #051b11; border-color: #0f5132; color: #a3cfbb; }
:root[data-theme="dark"] .alert-danger    { background-color: #2c0b0e; border-color: #842029; color: #f1aeb5; }
:root[data-theme="dark"] .alert-warning   { background-color: #332701; border-color: #997404; color: #ffe69c; }
:root[data-theme="dark"] .alert-info      { background-color: #032830; border-color: #087990; color: #9eeaf9; }
:root[data-theme="dark"] .alert-light     { background-color: #2b3035; border-color: #495057; color: #f8f9fa; }
:root[data-theme="dark"] .alert-dark      { background-color: #1a1d20; border-color: #343a40; color: #dee2e6; }

/* ══════════════════════════════════════════════════════════════════════════════════════════
   ───── 13c. ĐỢT 588 — NHỮNG MẢNG NỀN SÁNG CỦA BOOTSTRAP CÒN SÓT Ở GIAO DIỆN TỐI ─────

   Cùng họ với mục 13b (alert) và đợt 504 (bản cổ điển tối): Bootstrap 5.1 gắn CỨNG nền sáng cho một
   loạt thành phần, mãi 5.3 mới đổi theo giao diện. Giao diện tối của app đã đè `card` · `table` ·
   `alert` · `form-control` · `--bs-body-bg`, nhưng còn sót đúng NĂM nhóm dưới đây.

   📏 ĐO BẰNG SỐ (công thức tương phản WCAG 2.1, luminance tương đối, không mở trình duyệt):
       `.bg-light`   chữ #dde1e6 trên #f8f9fa ... **1,25:1** → sau: trên #2d323a ... **9,81:1**
       `[readonly]`  chữ #dde1e6 trên #e9ecef ... **1,11:1** → sau: #c7ccd2 trên #343a41 ... **7,11:1**
       `.btn-close`  nét đen opacity .5 hoà ra #131518 trên #262a31 ... **1,27:1**
                     → đảo màu: nét trắng hoà ra #929498 trên #262a31 ... **4,74:1**
       `.page-link`  #0d6efd trên #ffffff ... 4,50:1 (vừa đủ, nhưng là **mảng TRẮNG** giữa nền tối)
                     → sau: #6bb3ff trên #262a31 ... **6,53:1**
   Hai cái đầu là chữ SÁNG trên nền SÁNG — mất chữ hẳn, đúng kiểu lỗi đã dính ở đợt 228 và 430.

   ⛔ **CỐ Ý KHÔNG ĐỤNG `.progress`** dù máng #e9ecef là mảng sáng rõ nhất còn lại. Làm máng tối đi thì
   thanh màu bên trong tụt xuống dưới ngưỡng 3:1 của chỉ báo PHI VĂN BẢN — đo cả bộ trên máng #343a41:
   primary 2,55 · success 2,54 · danger 2,54 · warning 2,66 (máng #e9ecef hiện tại: 3,80 · 3,82 · 3,82 ·
   3,65). Tức là "hết loá" đổi lấy "không biết thanh chạy tới đâu", mà chính đợt 519 vừa sửa đúng chuyện
   đó. Muốn vừa tối vừa đủ tương phản thì phải sáng lại CẢ BỘ màu thanh — đổi chỗ dùng chung ở mức đó
   phải đi đợt riêng có đo sống (luật #18). ⚠ Đợt 588 chạy ở nhánh phụ và KHÔNG được đụng `docs/` nên
   việc này bàn giao MIỆNG cho phiên chính ghi vào VIEC-SAP-LAM — chưa có dòng nào trong sổ.

   ⚠ Ba chỗ CỐ Ý CHỪA RA, chừa sai là hỏng chỗ đang đúng:
     · `:not(.badge)` — huy hiệu `bg-light` có nền sáng CỐ ĐỊNH nên chữ phải đen (app.css mục 10 khoá
       `color:#000`). Đổi nền của chúng mà giữ chữ đen là ra 1,43:1 — đúng lỗi đợt 504 đã trả giá.
     · `:not(.top-row *)` — thẻ trắng bọc logo Petrolimex trên thanh nav là nền cố định có chủ đích
       (đợt 362y): nav vốn đã tối, thẻ trắng để logo màu nổi lên. Đổi nó thành tối là mất luôn ý đồ.
     · `.page-item.active .page-link` giữ nguyên xanh Bootstrap — trang đang đứng phải khác hẳn các trang
       còn lại, đó là thông tin chứ không phải trang trí.
   ⚠ `:not(...)` nâng độ ưu tiên lên (0,5,0) — nhớ điều này nếu sau này viết luật đè lên nhóm này.
   ══════════════════════════════════════════════════════════════════════════════════════════ */

/* (1) Nền xám/trắng của Bootstrap dùng làm nền khối phụ — bản hiện đại tối (đối xứng với đợt 504 của
       bản cổ điển tối). Đang dùng thật ở: hộp thoại ngày của Chấm công · thẻ hạng mục Chi tiết dự án ·
       hai đầu thẻ của trang Nạp đơn phép. */
:root[data-theme="dark"] .bg-light:not(.badge):not(.top-row *),
:root[data-theme="dark"] .bg-white:not(.badge):not(.top-row *) {
    background-color: #2d323a !important;
    color: #dde1e6;
}

/* (1b) ĐỢT 634 — Ô NHẬP NẰM TRONG KHỐI PHỤ VỪA LÀM TỐI Ở TRÊN.
   Hệ quả của luật (1): khối phụ nay là #2d323a — ĐÚNG BẰNG nền ô nhập của giao diện tối
   (`.form-control`/`.form-select` ở mục trên cũng #2d323a) ⇒ lòng ô và khối chứa nó **cùng một màu**,
   chỉ còn đường viền để nhận ra "chỗ để gõ".
   📏 Đo (WCAG 2.1, ngưỡng 3:1 cho chỉ báo PHI VĂN BẢN — đường viền của ô nhập):
       trước: viền #4a5058 trên #2d323a ... **1,58:1** (lòng ô so với khối: 1,00:1 — không có gì)
       sau  : viền #797f88 trên #2d323a ... **3,19:1**, đủ ở CẢ HAI phía (khối và lòng ô cùng màu).
   ⇒ Chữa bằng ĐÚNG MỘT kênh: đường viền. Cố ý KHÔNG đổi màu lòng ô — mũi tên của `.form-select` là
   ảnh SVG nét ĐEN nhúng sẵn của Bootstrap, không luật `color` nào với tới; làm lòng ô tối thêm là
   ép cái mũi tên đó xuống thấp hơn nữa. Đổi mũi tên phải đi đợt riêng, có đo sống (luật #18).
   ⓘ Đây đúng chuyện anh Tiến báo ở hộp thoại chấm công 12/08 (*"viền mờ, chữ mờ"*):
   `HopThoaiNgay.razor.css` đã chữa riêng cho một màn hình bằng cách phân tầng (khối lõm #1d2127 +
   viền #5c6673); luật này lo cho những khối `bg-light` còn lại — nhiều nhất ở **chấm công · nghỉ
   phép · nước** (14 tệp trong ba phân hệ này dùng `bg-light`, riêng Nạp đơn phép 8 chỗ).
   ⚠ CHỪA ô KHOÁ / CHỈ-ĐỌC: mục (3) ngay dưới đã dành cho chúng cặp #343a41/#c7ccd2 để trông rõ là
   "không gõ được". Luật này cùng độ ưu tiên (0,4,0) và nằm TRƯỚC, nhưng phần chừa cho chắc chắn:
   ô khoá mà mang viền sáng của ô gõ được là mời người dùng bấm vào chỗ không bấm được.

   🔴 ĐỢT 840 (việc o95) — `border-color` → `--bs-border-color`, và **ĐÍNH CHÍNH chú thích của mục ĐỢT
   659**: mục ấy gọi luật này là *"thừa nhưng VÔ HẠI (cùng giá trị)"*. Đo lại thì **thừa thì đúng, vô hại
   thì SAI**:
   · **Thừa**: bộ chọn ở đây là **tập con** của mục ĐỢT 659 (`[data-theme="dark"] .form-control`), mà đợt
     659 đã nâng luật gốc lên **đúng cùng một màu** `#797f88`. Bốn nhánh `:not([readonly]):not(:disabled)`
     cũng vô nghĩa từ đó: mục (3) chỉ đặt nền + chữ cho ô khoá, **không** đặt viền, nên ô khoá vốn đã lấy
     `#797f88` của luật gốc dù có luật này hay không. 📏 Đo sống để chắc: bỏ hẳn luật này rồi đo lại
     **6/6 ô** trong `.bg-light`/`.bg-white` (ô nhập · ô chọn · `[readonly]` · `:disabled`) — cả 6 vẫn ra
     `rgb(121,127,136)`, **không đổi một pixel nào**.
   · **KHÔNG vô hại**: nó viết `border-color` **longhand** ở (0,4,0) ⇒ nuốt viền đỏ `.is-invalid` (0,2,0)
     của Bootstrap **nặng hơn cả** mục ĐỢT 659, và nuốt ở đúng chỗ đông ô nhập nhất (khối `bg-light`:
     chấm công · nghỉ phép · nước). Chữa mục 659 mà để nguyên luật này là **vá đúng một nửa** — lặp lại y
     nguyên cái lỗi mà chính mục 659 đi chữa của đợt 634.
   ⇒ **VÌ SAO GIỮ LUẬT THỪA thay vì xoá** (đã thử xoá rồi hoàn lại): hàng rào `NenSangSotOGiaoDienToiTests`
     khai bộ chọn của luật NGAY DƯỚI ĐÂY là **BẮT BUỘC PHẢI CÓ** trong app.css — đo thật: xoá đi thì
     chuỗi ấy còn **0** lần trong tệp và bài đó **ĐỎ**. Sổ khai kia canh đúng một hiểm hoạ thật (khối phụ
     trùng màu lòng ô ⇒ chỉ còn đường viền chỉ chỗ để gõ), nên đường đúng là **viết lại cho đúng khuôn**,
     ⛔ không phải xoá luật rồi đi sửa sổ khai của người khác cho hết đỏ.
   ⚠ Chú thích này **cố ý KHÔNG gõ lại nguyên văn bộ chọn của luật ngay dưới**: bài kia dò bằng `Contains`
     trên tệp **CHƯA bóc chú thích**, nên một câu kể có chứa bộ chọn cũng đủ làm nó xanh — bằng chứng của
     nó hoá ra là lời kể chứ không phải luật. Đã ghi thành việc riêng để vá phép dò ấy. */
:root[data-theme="dark"] .bg-light .form-control:not([readonly]):not(:disabled),
:root[data-theme="dark"] .bg-light .form-select:not([readonly]):not(:disabled),
:root[data-theme="dark"] .bg-white .form-control:not([readonly]):not(:disabled),
:root[data-theme="dark"] .bg-white .form-select:not([readonly]):not(:disabled) {
    --bs-border-color: #797f88;
}

/* (2) Nút ✕ đóng: Bootstrap vẽ bằng ảnh SVG nét ĐEN nhúng sẵn, không phải màu chữ nên không luật
       `color` nào với tới được. Bootstrap 5.1 không có cách nào ⇒ app mượn công thức đảo màu của 5.3.
   ✅ **ĐỢT 831 (việc o73) — LUẬT NÀY ĐÃ BỎ, bản 5.3.8 tự lo.** Bootstrap 5.3 khai
   `--bs-btn-close-filter` theo trục `data-bs-theme`; đo trên trang thăm dò sau khi bắc cầu:
   token ra đúng `invert(1) grayscale(100%) brightness(200%)` — **trùng khít công thức app đang chép**.
   ⚠ **THỨ TỰ LÀ BẮT BUỘC, không phải cho gọn**: chỉ được bỏ luật này SAU KHI cầu nối `data-bs-theme`
   đã sống thật trong `App.razor`. Bỏ trước là `.btn-close` ở nền tối **mất hẳn phép đảo** ⇒ nét ✕ đen
   trên nền đen (**1,27:1**), ở **29 tệp**: mọi hộp thoại + mọi dải thông báo đóng được.
   ⓘ Trước khi bỏ đã kiểm nghi vấn "đảo hai lần": hai luật đặt **cùng thuộc tính `filter` trên cùng
   phần tử** nên đây là **cascade chứ không cộng dồn** (bộ lọc chỉ cộng dồn khi nằm trên cha và con
   khác nhau) ⇒ trước khi bỏ vẫn chỉ đảo MỘT lượt, bỏ đi không làm sáng thêm. */

/* (3) Ô nhập KHOÁ / TẮT: Bootstrap chỉ đổi nền sang #e9ecef mà không đụng màu chữ, nên chữ vẫn lấy
       #dde1e6 của giao diện tối ⇒ 1,11:1, coi như ô trống. Phải đặt CẢ nền lẫn chữ. */
:root[data-theme="dark"] .form-control:disabled,
:root[data-theme="dark"] .form-control[readonly],
:root[data-theme="dark"] .form-select:disabled {
    background-color: #343a41;
    color: #c7ccd2;
}

/* (3b) 🔴 ĐỢT 831 (việc o73) — Ô `[readonly]` Ở GIAO DIỆN SÁNG: BOOTSTRAP 5.3 THÔI TÔ NỀN XÁM.
   Bootstrap 5.1 cho `.form-control[readonly]` nền xám `#e9ecef` **sẵn trong bản gốc**, nên app chưa
   bao giờ phải khai cho nền sáng — mục (3) ngay trên CHỈ khai cho giao diện TỐI. Bản 5.3 rút nền xám
   về **chỉ còn `:disabled`**, `[readonly]` dùng chung nền với ô nhập bình thường.
   📏 ĐO trên trang thăm dò (116 phần tử · khung 1400px · chốt ngược `d-flex` ✓):
       5.1.0 : `.form-control[readonly]` nền `rgb(233,236,239)` — xám, phân biệt được
       5.3.8 : nền `rgb(255,255,255)` — **TRẮNG y hệt ô nhập được**
   ⇒ Mất sạch dấu hiệu "ô này chỉ đọc" ở **43 chỗ** dùng `readonly` trong app. Đây là loại hỏng
   **im lặng**: không câu lỗi nào, người dùng chỉ thấy một ô trông-như-nhập-được mà gõ không vào.
   ⇒ Khai lại nền xám cho giao diện sáng, lấy đúng giá trị `#e9ecef` mà 5.1 vốn dùng ⇒ **giữ nguyên
   diện mạo trước khi nâng**, không phải phát minh màu mới.
   ⚠ ⛔ ĐỪNG dùng `:root:not([data-theme="dark"])` — mục (3) ở trên nhắm `[data-theme="dark"]` với độ
   đặc hiệu (0,2,1); viết `:not(...)` cũng ra (0,2,1) rồi thắng/thua theo THỨ TỰ, dễ vỡ khi ai chèn
   luật vào giữa. Nhắm thẳng `[data-theme="light"]` cho hai luật rời nhau hẳn, không chồng lấn. */
:root[data-theme="light"] .form-control[readonly],
:root[data-theme="light"] .form-control:disabled,
:root[data-theme="light"] .form-select:disabled {
    background-color: #e9ecef;
}

/* (3c) 🔴 ĐỢT 838 (việc o89) — VIỀN Ô NHẬP GIAO DIỆN SÁNG KHÔNG ĐẠT 3:1, VÀ **KHÔNG PHẢI DO LƯỢT NÂNG
   BOOTSTRAP**. Đo trong đợt 823 nhưng cố ý để lại thành việc riêng, để khỏi dựng lại đúng cái bệnh mà
   đợt ấy đi chữa (app chép tay bảng màu Bootstrap rồi lệch dần).
   📏 SỐ ĐO (đợt 823, trang thăm dò 116 phần tử · khung 1400px · chốt ngược `d-flex` ✓):
       5.1.0 : viền `.form-control`/`.form-select` = `#ced4da` → **1,47:1** với lòng ô trắng
       5.3.8 : `#dee2e6` (= token `--bs-border-color`)  → **1,30:1**
   Ngưỡng WCAG 2.1 của chỉ báo **PHI VĂN BẢN** là **3:1** ⇒ **CẢ HAI bản đều hụt xa**. Đây là lỗ có sẵn
   của Bootstrap, ⛔ KHÔNG phải hồi quy của đợt 823 — nên ⛔ đừng "hoàn nguyên về `#ced4da`": bản cũ
   cũng trượt, chép lại chỉ là giữ nguyên cái sai với một con số khác.
   📏 `#7f858e` đo trên CẢ BA nền mà viền ô có thể nằm cạnh — phải đủ cả ba, không chỉ nền trắng:
       lòng ô trắng `#ffffff` **3,72:1** · ô `[readonly]`/`:disabled` `#e9ecef` (mục 3b ngay trên)
       **3,14:1** · thân trang `#f8f9fa` **3,53:1**.
       ⓘ Chốt ngược của phép đo: cùng bộ tính ấy chạy lại ba cặp màu nền tối của đợt 659 ra
         **3,19 · 3,57 · 4,10** — khớp bộ số `3,19 · 3,58 · 4,11` mà mục ĐỢT 659 đã ghi, **lệch 0,01 ở
         chữ số cuối** của hai cặp sau. ⛔ Đừng "sửa cho khớp" theo hướng nào: cả hai bộ đều nói cùng
         một chuyện (đạt 3:1), lệch chỉ là làm tròn của lượt đo cũ. Ghi ra đây để đời sau khỏi tưởng
         mình vừa tìm được một hồi quy — và để hàng rào đo bằng KHOẢNG chứ không so bằng chuỗi.
   🎨 `#7f858e` cố ý là **anh em cùng tông với `#797f88` của nền tối** (mục ĐỢT 659) — hai giao diện
      dùng một họ màu viền, đọc cạnh nhau là thấy ngay quan hệ.

   🔑 **VÌ SAO ĐẶT `--bs-border-color` NGAY TRÊN Ô NHẬP, KHÔNG viết thẳng `border-color`** — chỗ dễ làm
   ngược nhất của mục này. Bootstrap 5.3 dựng viền ô bằng **shorthand**
   `border: var(--bs-border-width) solid var(--bs-border-color)` ở `.form-control` (0,1,0); còn trạng
   thái lỗi `.form-control.is-invalid` đặt **longhand** `border-color` ở (0,2,0). Nếu ở đây viết
   `border-color` thẳng với bộ chọn (0,3,0) thì nó **thắng cả viền đỏ báo lỗi** ⇒ ô sai vẫn viền xám,
   hỏng LẶNG LẼ đúng kiểu dự án sợ nhất. Đặt **biến** thì longhand của mọi trạng thái vẫn thắng như cũ,
   viền thường mới đổi màu.
   ⓘ Hôm nay app **chưa dùng** `.is-invalid` của Bootstrap (đo 17/08: 0 chỗ trong `.razor`/`.cs`; xác
     thực của Blazor đi bằng `.valid`/`.invalid` + `outline`, xem đầu tệp) — nên đây là chốt **PHÒNG**,
     không phải đang cắn. Chính vì chưa cắn nên viết sai ở đây sẽ không ai phát hiện.
   ⛔ Đây KHÔNG phải kiểu "chép tay lại bảng màu `--bs-*`" mà quy tắc #5 cấm: cái bị cấm là khai đè
     token của Bootstrap trên `:root` **trần** cho cả app (8 đợt đã làm thế rồi lệch dần, đợt 823 đi
     dọn). Ở đây token đặt **trên chính phần tử ô nhập**, phạm vi đúng hai bộ chọn — đó là đường tùy
     biến chính thức của 5.3.
   ⛔ ĐỪNG nâng độ đặc hiệu bằng `:not(.is-invalid)`: thêm một lớp là (0,5,0), lúc ấy nó **vượt** luật
     thẻ đăng nhập `.dn-nen .dn-the .form-control` (0,4,0) và cướp mất màu viền của màn hình đó.
   ➕ `.input-group-text` đi kèm vì nó **dính liền một khung viền** với ô nhập (nhãn "m³", nút lịch…).
     Bỏ nó ra thì cùng một khung có hai nửa viền hai màu — nửa đậm nửa nhạt, trông như lỗi vẽ. Nó cũng
     dựng viền bằng đúng shorthand `border: … var(--bs-border-color)` nên đi chung được một luật. */
:root[data-theme] {
    --tknb-vien-o-nhap: #7f858e;
}
:root[data-theme="light"] .form-control,
:root[data-theme="light"] .form-select,
:root[data-theme="light"] .input-group-text {
    --bs-border-color: var(--tknb-vien-o-nhap);
}

/* (4) Nhãn dính đầu/cuối ô nhập (`.input-group-text`) — bản cổ điển tối đã có từ trước, đây là bản
       hiện đại tương ứng. Dùng ở ô ngưỡng của phân hệ nước.
   🔴 ĐỢT 838 (việc o89) — `#4a5058` → `#797f88`. Đo sống trên bản tĩnh nền tối: nhãn này còn giữ
   `#4a5058`, tức **đúng con số 1,58:1** mà mục ĐỢT 659 gọi là *"trước khi vá"* — đợt ấy nâng viền ô
   nhập nhưng **bỏ sót cái nhãn dán liền nó**. Hậu quả nhìn thấy được: `.input-group` là MỘT khung
   viền, mà nửa nhãn `#4a5058` còn nửa ô `#797f88` ⇒ vừa hụt ngưỡng vừa lộ vệt hai màu giữa khung.
   ⇒ Lấy đúng `#797f88` của ô nhập (3,19:1 với lòng ô `#2d323a`) để hai nửa liền một màu.
   ⛔ Ở đây KHÔNG dùng biến `--tknb-vien-o-nhap`: biến ấy mang màu của nền SÁNG (`#7f858e`), đắp vào
     nền tối là dựng lại đúng vệt hai màu vừa dọn. Nền tối giữ giá trị của mục ĐỢT 659.
   🔴 ĐỢT 840 (việc o95) — đổi sang `--bs-border-color` cho **cùng một khuôn** với ô nhập ngay trên (và
     với luật nền sáng ở mục 3c, nơi nhãn dính đi chung một luật với ô nhập). Bootstrap dựng viền nhãn
     bằng **đúng shorthand** `border: var(--bs-border-width) solid var(--bs-border-color)` nên màu ra
     **không đổi** — đo sống: `rgb(121,127,136)` trước và sau.
     ⓘ Riêng nhãn dính thì Bootstrap **không** có trạng thái `.is-invalid` nào (đã soi
     `bootstrap.min.css`: `.input-group > .form-control.is-invalid` chỉ đổi `z-index`), nên đổi ở đây là
     để **một khung một khuôn viết** — người sau đọc hai dòng cạnh nhau không phải hỏi vì sao khác nhau. */
:root[data-theme="dark"] .input-group-text {
    background-color: #2d323a;
    color: #dde1e6;
    --bs-border-color: #797f88;
}

/* (4b) ⭐ ĐỢT 652 — MŨI TÊN XỔ CỦA Ô CHỌN: ảnh SVG nét ĐEN dán cứng trong Bootstrap.
   Cùng họ với nút ✕ ở mục (2): không phải màu chữ nên KHÔNG luật `color` nào với tới được. Vẽ lại đúng
   hình đó bằng nét sáng — chỉ đổi `background-image`, còn vị trí/kích thước vẫn để `.form-select` của
   Bootstrap lo.

   📏 ĐO (WCAG 2.1, ngưỡng 3:1 cho chỉ báo PHI VĂN BẢN) — nét #343a40 của Bootstrap trên nền ô tối:
       ô chọn thường  #343a40 / #2d323a … **1,12:1**  →  #dde1e6 … **9,81:1**
       ô KHOÁ         #343a40 / #343a41 … **1,00:1**  →  #dde1e6 … **8,75:1**
       ô trong hộp thoại chấm công (#2f353e) … **1,07:1** → **9,41:1**
   ⚠ Con số **1,00** ở ô khoá không phải làm tròn: mũi tên trùng KHÍT màu nền — tàng hình hoàn toàn.
   ⓘ Vì sao tới giờ mới sửa: đợt 634 đã thấy nhưng cố ý hoãn (*"đổi mũi tên phải đi đợt riêng, có đo
     sống"*) vì nó đụng `app.css` — tệp dùng chung rủi ro cao nhất (quy tắc #18).

   ⛔ PHẢI CHỪA TRANG ĐĂNG NHẬP: thẻ đăng nhập ép ô nhập về nền TRẮNG ở MỌI giao diện (mục
     `:root[data-theme] .dn-nen .dn-the .form-control`) ⇒ nét sáng trên nền trắng là mất hẳn mũi tên.
     Dùng đúng khuôn `:not(.dn-nen *)` mà mấy luật `:focus` phía trên đã dùng.
     ⓘ Hôm nay thẻ đăng nhập CHƯA có ô chọn nào — chừa sẵn để mai kia thêm vào thì không hỏng lặng lẽ.
   📌 Hộp thoại chấm công từng tự vá riêng cho mình (`HopThoaiNgay.razor.css`, cùng đúng hai mã màu này);
     đợt 652 gỡ bản riêng đó đi vì luật chung đã phủ — hai nguồn cho cùng một hình là kiểu hỏng dự án này
     dính nhiều lần nhất.
   🧱 Hàng rào: `TKNB.Tests/MuiTenOChonNenToiTests.cs` (tự đọc CSS, tự tính lại tương phản). */
:root[data-theme="dark"] .form-select:not(.dn-nen *) {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dde1e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
}

/* (5) Thanh phân trang (`ThanhPhanTrang.razor` — dùng lại ở nhiều danh sách): nền trắng #fff. */
:root[data-theme="dark"] .page-link {
    background-color: #262a31;
    border-color: #3a3f47;
    color: #6bb3ff;
}
:root[data-theme="dark"] .page-link:hover,
:root[data-theme="dark"] .page-link:focus {
    background-color: #343a41;
    border-color: #3a3f47;
    color: #9ec5fe;
}
:root[data-theme="dark"] .page-item.disabled .page-link {
    background-color: #262a31;
    border-color: #3a3f47;
    color: #9aa0a8;
}
/* ⚠ BẪY ĐÃ SUÝT DÍNH khi viết khối này: luật `.page-link` ở trên có độ ưu tiên (0,3,0) — ĐÚNG BẰNG luật
   `.page-item.active .page-link` của Bootstrap. Bằng nhau thì bên VIẾT SAU thắng, mà `app.css` nạp sau
   `bootstrap.min.css` ⇒ trang đang đứng MẤT nền xanh, cả thanh trở thành một dãy số giống hệt nhau và
   người dùng không còn biết mình ở trang nào. Khai lại tường minh ở (0,4,0). Cặp #fff/#0d6efd giữ đúng
   của Bootstrap (4,56:1) — cố ý KHÔNG đổi: đây là chỉ báo vị trí, phải khác hẳn các trang còn lại. */
:root[data-theme="dark"] .page-item.active .page-link {
    background-color: #0d6efd;
    border-color: #0d6efd;
    color: #fff;
}

/* ───── 13. NÚT VIỀN MÀU Ở GIAO DIỆN TỐI (hiện đại) ─────
   Khối đợt 381 chỉ chỉnh `.btn-outline-primary` cho nền tối; 4 màu còn lại giữ nguyên màu chọn cho
   nền TRẮNG của Bootstrap ⇒ nút "▶ Chiếu" (.btn-outline-success) chỉ đạt **3,24:1** trên nền tối.
   (Bản cổ điển tối đã vá ở co-dien.css cùng đợt — đây là bản hiện đại tương ứng.) */
:root[data-theme="dark"] .btn-outline-success { color: #a3cfbb; border-color: #3d8c68; }
:root[data-theme="dark"] .btn-outline-danger  { color: #f1aeb5; border-color: #b5535e; }
:root[data-theme="dark"] .btn-outline-warning { color: #ffe69c; border-color: #c19a1f; }
:root[data-theme="dark"] .btn-outline-info    { color: #9eeaf9; border-color: #3a9fb5; }

/* ───── 10. HUY HIỆU DÙNG `.bg-*` TRỰC TIẾP (phần còn sót của bản vá đợt 205) ─────
   Đợt 205 vá `.text-bg-*` (lớp của Bootstrap 5.2+ mà bản 5.1 chưa có). Nhưng nhiều chỗ trong app
   viết `class="badge bg-warning"` — tức nền của Bootstrap 5.1 CÓ, còn màu chữ thì phó mặc. Trên nền
   VÀNG/XANH LƠ/SÁNG mà chữ lại sáng theo giao diện tối thì mất chữ:
       badge bg-warning "Chờ duyệt" ... 1,24:1
       badge bg-info    "Mới giao"  ... 1,49:1
   ⇒ Huy hiệu có nền CỐ ĐỊNH nên màu chữ cũng phải cố định — khai cho mọi giao diện, dùng đúng cặp
   của Bootstrap 5.3 (nền sáng → chữ đen, nền đậm → chữ trắng).
   ⚠ Đúng ra chỗ này KHÔNG hỏng: mã trang đã viết `class="badge bg-warning text-dark"` — chữ đen trên
   nền vàng là chuẩn. Thủ phạm là bản vá đợt 228 (`[dark] .text-dark { color:#dde1e6 !important }`,
   sinh ra để cứu chữ đen trên nền ĐEN) quét luôn cả `.text-dark` đang nằm trên nền SÁNG của huy
   hiệu. ⇒ Phải khai với `:root[data-theme]` để đạt (0,4,0), cao hơn (0,3,0) của luật 228.
   Bài học chung: luật "đổi màu chữ theo giao diện" luôn phải hỏi "chữ này có nằm trên nền CỐ ĐỊNH
   nào không?" — cùng đúng một câu hỏi đã sinh ra mục 1 (trang đăng nhập) của đợt này. */
:root[data-theme] .badge.bg-warning,
:root[data-theme] .badge.bg-info,
:root[data-theme] .badge.bg-light {
    color: #000 !important;
}

:root[data-theme] .badge.bg-primary, :root[data-theme] .badge.bg-success,
:root[data-theme] .badge.bg-danger, :root[data-theme] .badge.bg-secondary,
:root[data-theme] .badge.bg-dark {
    color: #fff !important;
}

/* ═══ ĐỢT 634 — GỠ LUẬT TRÙNG CỦA ĐỢT 558 (nền khối phụ ở chế độ tối) ═══
   Ở đây từng có luật thứ HAI cho cùng nhóm nền khối phụ của giao diện tối (đợt 558, màu #1d2127),
   trong khi khối 13c (đợt 588) đã khai đúng nhóm đó ở trên với màu #2d323a. Hai đợt chạy ở HAI NHÁNH
   SONG SONG, gộp vào không ai thấy trùng vì chúng nằm cách nhau ~100 dòng.
   📏 Đo trên chính hai luật đó: luật của 588 mang thêm hai phần chừa nên độ ưu tiên (0,5,0) > (0,4,0)
   của 558 ⇒ **588 luôn thắng**, luật 558 là mã CHẾT ở mọi màn hình thường.
   🐞 Nhưng nó KHÔNG chết hẳn, và đó mới là chỗ nguy hiểm: đúng những phần tử mà 588 CỐ Ý chừa ra
   (`.top-row *` — thẻ trắng bọc logo Petrolimex, đợt 362y) thì luật 588 không khớp, nên luật 558
   **quét trúng** và làm tối chúng. Tức phần chừa của 588 bị vô hiệu **đúng ở chỗ nó sinh ra để bảo vệ**,
   mà hàng rào `NenSangSotOGiaoDienToiTests` chỉ soi lần xuất hiện ĐẦU TIÊN nên vẫn xanh.
   ⇒ Giữ MỘT luật duy nhất (khối 13c ở trên). Hàng rào nay đếm số lần khai để không tái diễn.
   ⓘ Hộp thoại chấm công — màn hình sinh ra đợt 558 — KHÔNG bị ảnh hưởng: từ đợt 558 nó đã bỏ lớp
   `bg-light` và tự mang lớp riêng `.htn-khoi-cham` trong `HopThoaiNgay.razor.css`. */

/* ───── 11. TAB DẠNG NÚT NGOÀI THANH NAV Ở GIAO DIỆN TỐI ─────
   Luật màu tab của đợt 381 chỉ nhắm `.menu-co-dien .nav-link` (hàng tab đã dời lên thanh). Tab render
   TRONG TRANG bằng `<button class="nav-link">` (trang Công việc) không được phủ ⇒ giữ #0d6efd của
   Bootstrap: 3,67:1 trên nền tối. Chừa `.active` (đã có nền viên riêng) và ngăn kéo menu (mục 8). */
:root[data-theme="dark"] .nav-link:not(.active):not(.menu-co-dien *):not(.nav-scrollable *),
:root[data-theme="co-dien-toi"] .nav-link:not(.active):not(.menu-co-dien *):not(.nav-scrollable *) {
    color: #9ec5fe;
}

/* ───── 9. THƯƠNG HIỆU "TKNB" Ở ĐẦU NGĂN KÉO MENU — chữ trắng trên nền trắng ─────
   Có HAI thanh mang lớp `.top-row`: thanh công cụ của trang (MainLayout, nền sáng) và thanh tiêu đề
   của ngăn kéo menu (NavMenu, `class="top-row ps-3 navbar navbar-dark"`, nằm trên dải NAVY của
   `.sidebar` nên chữ thương hiệu để TRẮNG). Khối kính mờ đợt 381 phủ nền `rgba(255,255,255,.88)`
   cho MỌI `.top-row` ⇒ thanh tiêu đề ngăn kéo hoá trắng mà chữ vẫn trắng: **1,26:1**, mất hẳn chữ
   "TKNB" ở giao diện sáng. (Nền tối thoát vì lớp kính bên đó vốn đã tối.)
   ⇒ Trả nền NAVY cho đúng thanh đó — cùng màu đầu dải gradient của .sidebar nên nối liền mạch. */
:root[data-theme="light"] .top-row.navbar-dark,
:root[data-theme="co-dien"] .top-row.navbar-dark {
    background-color: rgb(5, 39, 103);   /* chữ trắng: 14,6:1 */
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

/* ───── 8. CHỮ TRONG NGĂN KÉO MENU — nền NAVY cố định nên màu chữ cũng phải cố định ─────
   `.sidebar` luôn là dải NAVY (#052767 → tím) ở CẢ BỐN giao diện, và NavMenu.razor.css cho mục
   thường #d7d7d7 / mục đang chọn TRẮNG — cặp màu đúng. Nhưng mỗi giao diện lại khai màu LIÊN KẾT
   riêng cho `a:not(.btn)` với trọng số (0,3,0) — bằng luật scoped mà nạp SAU nên THẮNG, và thế là
   màu link đè lên màu chữ thanh bên. Đo thật:
       cổ điển sáng  · #0645ad trên navy ... 1,25:1  ← tệ nhất, xanh đậm chìm hẳn vào navy
       hiện đại sáng · #006bb7 trên navy ... 1,66:1
       hai bản tối   · #6bb3ff / #7fb6ff ... 1,83:1 (mục đang chọn, ngay chỗ người dùng đứng)
   ⇒ Khoá màu chữ cho cả 4 giao diện. Trọng số (0,4,0) cố ý cao hơn luật màu link (0,3,0).
   Đặt ở app.css (không sửa NavMenu.razor.css) để chỗ vá nằm cạnh nguyên nhân — luật màu link theo
   giao diện — thay vì rải !important vào file scoped. */
:root[data-theme] .nav-scrollable a:not(.btn):not(.active) {
    color: #d7d7d7;   /* 9,7:1 trên navy */
}

:root[data-theme] .nav-scrollable a.active {
    color: #fff;      /* 14,6:1 — xem thêm NavMenu.razor.css: nền mục đang chọn hạ .37 → .25 */
}

/* ───── 6. CHỮ GỢI Ý TRONG Ô NHẬP Ở GIAO DIỆN TỐI ─────
   #8a9099 trên nền ô #2d323a chỉ được 3,9:1 — chữ gợi ý là thứ người dùng đọc để biết phải gõ gì,
   không phải trang trí. */
:root[data-theme="dark"] .form-control::placeholder {
    color: #a1a8b0;   /* 5,1:1 */
}

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   ĐỢT 412 — KHUNG CHỜ ĐỘNG CÓ HOẠT HOẠ  (trang /dang-xep-hang + <KhungChoViecNang> / <NutMotLuot>)

   Anh Tiến chỉ đạo 09/08/2026: máy đến ngưỡng thì xếp hàng, báo "lưu lượng tăng đột biến, xin vui
   lòng đợi trong giây lát" và hiện KHUNG ĐẸP ĐỘNG CÓ HOẠT HOẠ để người dùng "đỡ nhấn nút enter vài
   chục lần".

   ⚠ RÀNG BUỘC ĐẶC BIỆT CỦA KHỐI NÀY: nó chạy đúng lúc máy đang QUÁ TẢI. Nên mọi hoạt hoạ ở đây chỉ
   được động vào `transform` và `opacity` — hai tính chất trình duyệt chạy thẳng trên card đồ hoạ,
   KHÔNG bắt tính lại bố cục (layout/paint). Cố ý KHÔNG animate width/height/top/left, không bóng đổ
   chuyển động, không blur chạy, không ảnh, không thư viện ngoài.

   Mọi con số trong khối này là HẰNG SỐ VIẾT THẲNG VÀO CSS (không nội suy từ Razor) ⇒ đứng ngoài
   bẫy dấu phẩy thập phân của vi-VN ở quy tắc cứng #5. Chỗ DUY NHẤT có số từ mã là bề rộng thanh
   tiến trình, và chỗ đó đi qua InvariantCulture ngay tại trang.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */

/* ───── 1. THẺ CHỜ TOÀN MÀN HÌNH (trang /dang-xep-hang) ─────
   Trang này dùng LayoutTrong nên nền là dải navy động của bộ nhận diện; thẻ chỉ cần nổi lên trên. */
.xh-the {
    position: relative;
    z-index: 1;
    width: min(560px, calc(100vw - 2rem));
    border-radius: 18px;
    overflow: hidden;
    background: rgba(255, 255, 255, .96);
    box-shadow: 0 24px 70px rgba(0, 10, 40, .55);
    animation: xh-hien .5s ease both;
}

@keyframes xh-hien {
    from { opacity: 0; transform: translateY(18px) scale(.985); }
    to   { opacity: 1; transform: none; }
}

/* Vệt cam thương hiệu chạy ngang đầu thẻ — dấu hiệu "máy vẫn đang chạy" rẻ nhất có thể. */
.xh-vet {
    height: 4px;
    background: linear-gradient(90deg, #F47B20, #ffb26b 45%, #F47B20);
    background-size: 200% 100%;
    animation: xh-truot 2.4s linear infinite;
}

@keyframes xh-truot {
    from { background-position: 0% 50%; }
    to   { background-position: 200% 50%; }
}

.xh-than { padding: 1.6rem 1.5rem 1.3rem; text-align: center; }

/* ───── 2. BA CHẤM NẢY — hoạt hoạ chính ─────
   Độ trễ từng chấm khai CỐ ĐỊNH ở đây (không truyền từ Razor) nên vừa nhẹ vừa khỏi dính quy tắc #5. */
.xh-cham-hang { display: flex; gap: .45rem; justify-content: center; margin-bottom: .9rem; }

.xh-cham {
    width: 11px; height: 11px; border-radius: 50%;
    background: #0a3d91;
    animation: xh-nay 1.2s ease-in-out infinite;
}

.xh-cham:nth-child(2) { animation-delay: .15s; }
.xh-cham:nth-child(3) { animation-delay: .3s; }

@keyframes xh-nay {
    0%, 80%, 100% { transform: translateY(0);     opacity: .45; }
    40%           { transform: translateY(-9px);  opacity: 1; }
}

/* ───── 3. CHỮ TRÊN THẺ CHỜ ─────
   Đây là CẢNH BÁO + câu giải thích các con số đang hiện ⇒ được phép in thường trực ra màn hình
   (đúng hai ngoại lệ mà CLAUDE.md chừa cho luật "hướng dẫn nằm trong title"). */
.xh-tieu-de { font-size: 1.12rem; font-weight: 600; line-height: 1.45; color: #14181d; margin: 0 0 .4rem; }
.xh-phu     { font-size: .9rem;  color: #5b636d; margin: 0 0 1.1rem; }

/* ───── 4. HAI CON SỐ (vị trí trong hàng · tổng đang chờ) ─────
   ⚠ Anh Tiến chốt 1000 thiết bị là CÙNG LÚC THẬT ⇒ số có thể lên 3–4 chữ số. Lưới hai cột đều nhau
   với minmax(0,…) để ô không bao giờ đẩy nhau vỡ; tabular-nums giữ bề ngang chữ số ổn định khi số
   đang đếm lùi. */
.xh-so {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .6rem;
    margin-bottom: .9rem;
}

.xh-o {
    min-width: 0;
    padding: .6rem .4rem;
    border-radius: 12px;
    background: rgba(10, 61, 145, .07);
}

.xh-con-so {
    display: block;
    font-size: 1.6rem;
    font-weight: 700;
    line-height: 1.15;
    color: #0a3d91;
    font-variant-numeric: tabular-nums;
    overflow-wrap: anywhere;
}

.xh-nhan { display: block; font-size: .78rem; color: #5b636d; margin-top: .15rem; }

/* ───── 5. THANH TIẾN TRÌNH ─────
   Bề rộng do mã đặt (số nguyên, qua InvariantCulture). Vệt sáng chạy trên mặt thanh là một
   pseudo-element trượt bằng transform — không đụng tới bề rộng thật nên không gây tính lại bố cục. */
.xh-thanh {
    position: relative;
    height: 8px;
    border-radius: 99px;
    background: rgba(10, 61, 145, .13);
    overflow: hidden;
    margin-bottom: .9rem;
}

.xh-thanh-day {
    position: relative;
    height: 100%;
    border-radius: 99px;
    background: linear-gradient(90deg, #0a3d91, #2f7ae5);
    transition: width .6s ease;
}

.xh-thanh-day::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 99px;
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .55), transparent);
    animation: xh-loe 1.8s ease-in-out infinite;
}

@keyframes xh-loe {
    from { transform: translateX(-100%); }
    to   { transform: translateX(100%); }
}

.xh-con-lai  { font-size: .92rem; color: #14181d; margin: 0 0 .5rem; }
.xh-tran-an  { font-size: .86rem; color: #5b636d; margin: 0 0 1rem; }
.xh-tu-vao   { font-size: .78rem; color: #7b838d; margin: .7rem 0 0; }

/* Lối thoát cho người sốt ruột — là LIÊN KẾT chứ không phải nút gửi việc, nên bấm mấy lần cũng
   không đẻ thêm lượt việc nào cho máy chủ. */
.xh-nut {
    display: inline-block;
    padding: .55rem 1.4rem;
    border-radius: 10px;
    background: linear-gradient(120deg, #0a3d91, #002169);
    color: #fff;
    font-weight: 600;
    text-decoration: none;
    transition: transform .15s, box-shadow .15s;
}

.xh-nut:hover  { color: #fff; transform: translateY(-1px); box-shadow: 0 10px 24px rgba(0, 23, 68, .4); }
.xh-nut:focus-visible { outline: 3px solid #ffb26b; outline-offset: 2px; }

/* ⚠ ĐÚNG CÁI BẪY ĐÃ GHI Ở MỤC 8 CUỐI FILE — và đợt 412 dính lại y hệt, phát hiện khi CHỤP ẢNH thật ở
   giao diện tối: mỗi giao diện khai màu LIÊN KẾT riêng cho `a:not(.btn)` với trọng số (0,3,1), thắng
   `.xh-nut` (0,1,0) ⇒ chữ trên nút bị kéo về xanh nhạt #6bb3ff, nằm trên nền navy của chính nút thì
   chỉ còn ~2,4:1 — dưới ngưỡng AA. Nút này CỐ Ý không mang lớp .btn (nó là liên kết, không phải nút
   gửi việc), nên phải khai lại bằng `a.xh-nut` cho đủ trọng số (0,3,1) + đứng sau trong file.
   📌 Bài học: nút/liên kết tự vẽ màu nền riêng thì PHẢI tự khoá luôn màu chữ cho cả 4 giao diện. */
:root[data-theme] a.xh-nut,
:root[data-theme] a.xh-nut:hover,
:root[data-theme] a.xh-nut:focus {
    color: #fff;   /* 12,6:1 trên nền navy của nút */
}

/* ───── 6. KHUNG CHỜ TẠI CHỖ (<KhungChoViecNang>) + VÒNG QUAY TRONG NÚT (<NutMotLuot>) ─────
   Khối này nằm trong các trang BÌNH THƯỜNG nên phải theo cả 4 giao diện — xem mục 7 bên dưới. */
.xh-tai-cho { display: inline-block; }

.xh-cho-hop {
    display: flex;
    align-items: center;
    gap: .7rem;
    margin-top: .6rem;
    padding: .6rem .8rem;
    border-radius: 12px;
    border: 1px solid rgba(10, 61, 145, .18);
    background: rgba(10, 61, 145, .05);
    animation: xh-hien .35s ease both;
}

.xh-cham-nho { margin-bottom: 0; gap: .3rem; }
.xh-cham-nho .xh-cham { width: 7px; height: 7px; }

.xh-cho-chu      { text-align: left; min-width: 0; }
.xh-cho-tieu-de  { font-weight: 600; font-size: .92rem; }
.xh-cho-phu      { font-size: .8rem; color: #5b636d; }
.xh-nut-huy      { margin-left: auto; flex: none; }

/* Vòng quay nhỏ trong nút, thay cho chữ "Đang xử lý…" đứng trơ. Chỉ animate transform. */
.xh-quay {
    display: inline-block;
    width: .85em; height: .85em;
    margin-right: .4em;
    vertical-align: -.1em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: xh-xoay .7s linear infinite;
}

@keyframes xh-xoay { to { transform: rotate(360deg); } }

/* ───── 7. BỐN GIAO DIỆN ─────
   Trang /dang-xep-hang nằm trên nền navy của LayoutTrong ở mọi giao diện, nên thẻ chỉ đổi giữa
   "thẻ sáng" và "thẻ tối" cho hợp mắt người đang để máy ở chế độ tối. */
:root[data-theme="dark"] .xh-the,
:root[data-theme="co-dien-toi"] .xh-the {
    background: rgba(28, 32, 38, .96);
}

:root[data-theme="dark"] .xh-tieu-de,
:root[data-theme="co-dien-toi"] .xh-tieu-de,
:root[data-theme="dark"] .xh-con-lai,
:root[data-theme="co-dien-toi"] .xh-con-lai {
    color: #eef1f5;
}

:root[data-theme="dark"] .xh-phu,
:root[data-theme="co-dien-toi"] .xh-phu,
:root[data-theme="dark"] .xh-nhan,
:root[data-theme="co-dien-toi"] .xh-nhan,
:root[data-theme="dark"] .xh-tran-an,
:root[data-theme="co-dien-toi"] .xh-tran-an,
:root[data-theme="dark"] .xh-cho-phu,
:root[data-theme="co-dien-toi"] .xh-cho-phu {
    color: #aab2bc;   /* 6,5:1 trên nền thẻ tối */
}

:root[data-theme="dark"] .xh-tu-vao,
:root[data-theme="co-dien-toi"] .xh-tu-vao {
    color: #949ca6;
}

:root[data-theme="dark"] .xh-cham,
:root[data-theme="co-dien-toi"] .xh-cham {
    background: #6bb3ff;
}

:root[data-theme="dark"] .xh-o,
:root[data-theme="co-dien-toi"] .xh-o {
    background: rgba(107, 179, 255, .12);
}

:root[data-theme="dark"] .xh-con-so,
:root[data-theme="co-dien-toi"] .xh-con-so {
    color: #8cc4ff;   /* 7,3:1 trên nền ô tối */
}

:root[data-theme="dark"] .xh-thanh,
:root[data-theme="co-dien-toi"] .xh-thanh {
    background: rgba(255, 255, 255, .14);
}

:root[data-theme="dark"] .xh-cho-hop,
:root[data-theme="co-dien-toi"] .xh-cho-hop {
    border-color: rgba(107, 179, 255, .3);
    background: rgba(107, 179, 255, .09);
}

/* ───── 8. KHỔ ĐIỆN THOẠI ─────
   Cố ý KHÔNG đụng luật cuộn bảng của đợt 400 (selector khác hẳn, ngưỡng khác hẳn). */
@media screen and (max-width: 480px) {
    .xh-than    { padding: 1.2rem 1rem 1rem; }
    .xh-tieu-de { font-size: 1rem; }
    .xh-con-so  { font-size: 1.35rem; }

    /* Khung chờ tại chỗ: xuống hàng để nút Huỷ không ép chữ vỡ trên màn hình hẹp. */
    .xh-tai-cho { display: block; }
    .xh-cho-hop { flex-wrap: wrap; }
    .xh-nut-huy { margin-left: 0; }
}

/* ───── 9. TÔN TRỌNG "GIẢM CHUYỂN ĐỘNG" ─────
   ⚠ ĐỌC KỸ TRƯỚC KHI THÊM `animation: none` VÀO ĐÂY. Việc TẮT chuyển động đã có khối TOÀN CỤC của
   đợt 381 lo (mục 10 phía trên: `*, *::before, *::after { animation-duration: .01ms !important }`),
   và đợt đó cố ý chọn `.01ms` CHỨ KHÔNG PHẢI `none` — vì `none` làm phần tử có `fill: both` kẹt ở
   khung hình ĐẦU (thẻ đăng nhập từng ở lại trong suốt), còn `.01ms` thì hoạt hoạ "chạy xong" tức
   thì nên phần tử về đúng trạng thái CUỐI. Viết đè `animation: none` ở đây là lặng lẽ phá lại đúng
   quyết định đó.

   Khối này vì vậy CHỈ sửa HÌNH DÁNG TĨNH của hai thứ mà khi đứng yên trông như đang hỏng:
     · ba chấm — khi hoạt hoạ dừng, nền `.xh-cham` không khai opacity nên chúng về mức đậm đầy đủ;
       khai lại 1 ở đây cho chắc, vì "đứng yên" không được phép biến thành "biến mất";
     · vòng quay trong nút — đứng yên ở dạng vòng CỤT một góc thì nhìn như lỗi vẽ, nên nối kín vành
       và hạ độ đậm để nó đọc thành "dấu hiệu đang bận" tĩnh. */
@media (prefers-reduced-motion: reduce) {
    .xh-cham { opacity: 1; }
    .xh-quay { border-right-color: currentColor; opacity: .55; }
}

/* ═══════════════ BẢNG TIN VIỆC CHẠY NỀN (đợt 423d, sửa 423e) ═══════════════
   Anh Tiến 10/08/2026 (lần 2, kèm ảnh khối tin che mất nội dung): *"em làm nền TRONG SUỐT và CHẠY LÊN
   MỜ DẦN rồi biến mất NHƯ KHUNG CHAT TRONG CÁC PHIÊN LIVE STREAM"*.

   ⇒ Ba điểm khác bản đầu: (1) BỎ hẳn nền hộp + đổ bóng — chỉ còn chữ, đọc được nhờ viền chữ chứ không
   nhờ nền; (2) dòng TRÔI LÊN suốt thời gian sống (transform translateY) thay vì đứng im rồi tắt;
   (3) chồng từ DƯỚI LÊN như khung chat.

   ⚠ `pointer-events: none` cho cả khối — tin để LIẾC, không để bấm; nó nổi trên nội dung nên nuốt cú
     bấm là hỏng đúng nút nằm dưới. */
.bang-tin-nen {
    position: fixed;
    right: .75rem;
    bottom: .75rem;
    /* ⚠ 900 = DƯỚI mọi lớp nổi của Bootstrap (dropdown 1000 · sticky 1020 · modal 1055).
       Anh Tiến 10/08: *"nút menu bị lỗi, nhấn vào bị nền chèn mất"* — khối tin nổi cao hơn
       menu xổ là nuốt mất menu. Tin nền là thứ ÍT quan trọng nhất trên màn hình, phải nằm dưới cùng. */
    z-index: 900;
    display: flex;
    flex-direction: column;
    /* ⚠ ĐỢT 504 — anh Tiến 11/08/2026: *"các tin nhắn log thông báo hiển thị rồi mờ dần cần được
       CĂN ĐỀU ĐẸP, CĂN TRÁI"*.
       Bản cũ để `align-items: flex-end` + khối tự co theo nội dung ⇒ mỗi dòng một bề rộng, tất cả
       dán mép PHẢI nên mép TRÁI răng cưa: mắt phải dò lại điểm bắt đầu ở từng dòng.
       Nay khối có bề rộng CỐ ĐỊNH và các dòng kéo căng bằng nhau (`stretch`) ⇒ mọi dòng bắt đầu ở
       cùng một mép trái. Phần "căn đều" nằm ở lưới 3 cột của `.btn-dong` ngay dưới.
       ⚠ GIỮ NGUYÊN chỗ đứng (góc phải dưới) và `pointer-events: none`: dời sang trái là chồng lên
         menu/rail bên trái, còn bỏ pointer-events là nuốt cú bấm của nút nằm dưới. */
    align-items: stretch;
    justify-content: flex-end;
    gap: .1rem;
    width: min(34rem, 92vw);
    pointer-events: none;
}

.bang-tin-nen .btn-dong {
    /* Lưới 3 cột thay cho flex: cột GIỜ và cột TÊN VIỆC rộng bằng nhau ở MỌI dòng nên ba cột thẳng
       hàng dọc — đó là phần "căn đều" anh Tiến yêu cầu. Cột cuối `1fr` nhận phần còn lại.
       ⚠ Chỉ dùng bề rộng CỐ ĐỊNH mới thẳng hàng được: mỗi dòng là một lưới RIÊNG (không phải subgrid),
         để `auto` là mỗi dòng tự tính một kiểu, thẳng hàng chỉ còn là may rủi.
       ⚠ Tên việc dài nhất trong LuatBangTinNen.TenViec khoảng 20 ký tự ("Kiểm kết nối switch",
         "Chuyển kho quản trị mạng") ⇒ 9,5rem đủ chứa; dài hơn thì XUỐNG DÒNG trong cột chứ KHÔNG
         cắt cụt — cắt tên việc là mất đúng thứ người xem cần biết. */
    display: grid;
    grid-template-columns: 4.6rem 9.5rem 1fr;
    gap: 0 .5rem;
    align-items: baseline;
    padding: .1rem .2rem;
    text-align: left;
    font-size: .78rem;
    line-height: 1.35;
    background: none;           /* KHÔNG nền — đúng chữ "nền trong suốt" của anh Tiến */
    /* Viền chữ mỏng: giữ đọc được trên cả nền sáng lẫn ảnh vệ tinh của sa bàn, mà không cần hộp nền.
       Đây là cách khung chat livestream vẫn dùng. */
    text-shadow:
         0 0 3px var(--bs-body-bg, #fff),
         0 0 6px var(--bs-body-bg, #fff),
         0 1px 2px rgba(0, 0, 0, .35);
    animation: btn-troi-len 9s linear forwards;
}

.bang-tin-nen .btn-loi .btn-viec { color: var(--bs-danger, #dc3545); }

/* ⚠ ĐỢT 430 — đỏ Bootstrap #dc3545 trên nền bảng tin TỐI (#1b1e22) chỉ cho **3,69:1**. Đo trên dòng
   "Đồng bộ ra vào" của bảng tin nền, hiện ở MỌI trang nên đây là chỗ dùng chung.
   Dùng nấc đỏ dành cho nền tối của Bootstrap 5.3 (#ea868f) ⇒ 6,62:1. Áp cho CẢ HAI nền tối. */
:root[data-theme="dark"] .bang-tin-nen .btn-loi .btn-viec,
:root[data-theme="co-dien-toi"] .bang-tin-nen .btn-loi .btn-viec {
    color: #ea868f;
}

/* ⚠ ĐỢT 504 — nửa còn lại của cùng câu chuyện: đợt 430 chỉ vá hai nền TỐI. Đo sống 11/08 trên
   /cham-cong: cùng dòng "Đồng bộ ra vào" ở bản CỔ ĐIỂN SÁNG (nền #fbfbfb) chỉ được **4,38:1**, còn
   bản sáng hiện đại (nền #fff) được 4,53 — tức là đang đứng ngay mép ngưỡng, chỉ cần nền nhạt đi
   một chút là rơi. Hạ một nấc đỏ (#dc3545 → #b02a37) ⇒ 6,31 và 6,53. */
:root[data-theme="light"] .bang-tin-nen .btn-loi .btn-viec,
:root[data-theme="co-dien"] .bang-tin-nen .btn-loi .btn-viec,
:root:not([data-theme]) .bang-tin-nen .btn-loi .btn-viec {
    color: #b02a37;
}

/* ⚠ ĐỢT 504 — `opacity: .75` cũ hạ chữ giờ xuống dưới ngưỡng đọc: ở nền tối, chữ #dde1e6 mờ 75%
   trên nền #1c1f24 chỉ còn khoảng 6,6:1 → vẫn đạt, NHƯNG opacity còn nhân vào cả VIỀN CHỮ
   (text-shadow) nên trên ảnh vệ tinh của sa bàn thì cột giờ nhòe hẳn. Nay dùng MÀU nhạt hơn thay cho
   độ mờ: viền chữ giữ nguyên độ đậm, giờ vẫn đọc được trên mọi nền.
   tabular-nums giữ cột giờ không nhảy qua nhảy lại giữa các dòng. */
.bang-tin-nen .btn-gio {
    font-variant-numeric: tabular-nums;
    color: #4d5560;
}

:root[data-theme="dark"] .bang-tin-nen .btn-gio,
:root[data-theme="co-dien-toi"] .bang-tin-nen .btn-gio {
    color: #aeb6bf;
}

.bang-tin-nen .btn-viec {
    font-weight: 700;
    overflow-wrap: anywhere;
}

.bang-tin-nen .btn-cau { overflow-wrap: anywhere; }

/* Hiện nhanh → trôi lên đều → nhạt dần rồi tắt. Thời lượng phải khớp LuatBangTinNen.GiayHienThi (9s):
   lệch thì dòng biến mất lúc còn rõ, hoặc nằm lại một khoảng trong suốt vô hình. */
@keyframes btn-troi-len {
    0%   { opacity: 0; transform: translateY(1rem); }
    4%   { opacity: 1; transform: translateY(.8rem); }
    70%  { opacity: 1; transform: translateY(.25rem); }
    100% { opacity: 0; transform: translateY(-.5rem); }
}

/* Người tắt hiệu ứng chuyển động: bỏ trôi + bỏ nhạt dần, để bộ đếm dọn đi. */
@media (prefers-reduced-motion: reduce) {
    .bang-tin-nen .btn-dong { animation: none; opacity: 1; transform: none; }
}

/* ═════ ĐỢT 430 — NÚT VIỀN ĐỎ TRÊN HÀNG DANH SÁCH ĐÃ TÔ MÀU (giao diện SÁNG) ═════
   Đo thật: nút "✖ Từ chối" và 🗑 của /nghi-phep và /thong-bao nằm trong `list-group-item-warning`
   (nền #fff3cd). Màu đỏ Bootstrap #dc3545 trên nền vàng nhạt chỉ cho **4,09:1** — hụt ngưỡng 4,5.
   Đây là lỗi CÓ SẴN (không phải hồi quy), lộ ra khi quét đủ 4 giao diện chứ không chỉ nền tối.
   Hạ MỘT NẤC theo thang Bootstrap 5.3 (#dc3545 → #b02a37) ⇒ 5,86:1. Cùng cách đã dùng cho bản cổ điển
   sáng ở đợt 386c.
   ⚠ Khai HẸP — chỉ trong hàng/ô ĐÃ TÔ MÀU, không đụng nút đỏ trên nền trắng (vốn đã 4,5+): sửa màu
   nút toàn cục là đúng kiểu "đổi chỗ dùng chung" mà luật #18 dặn tránh. */
:root[data-theme="light"] [class*="list-group-item-"] .btn-outline-danger,
:root:not([data-theme]) [class*="list-group-item-"] .btn-outline-danger,
:root[data-theme="light"] .table [class*="table-"] .btn-outline-danger,
:root:not([data-theme]) .table [class*="table-"] .btn-outline-danger {
    color: #b02a37;
    border-color: #b02a37;
}

/* ═════ ĐỢT 504 — NÚT VIỀN MÀU Ở GIAO DIỆN SÁNG HIỆN ĐẠI: LỖ DUY NHẤT CÒN LẠI ═════
   Đo sống 11/08/2026 (4 giao diện × 8 trang của 4 phân hệ anh Tiến nêu). Ba giao diện kia ĐÃ được
   vá từ lâu — cổ điển sáng ở đợt 386c, hai nền tối ở đợt 386c/430 — riêng **sáng hiện đại** thì
   chưa ai đụng, nên nó giữ nguyên màu gốc của Bootstrap 5.1:
     · `.btn-outline-warning` #ffc107 "🔑 MK mặc định" (/quan-tri/nguoi-dung) … **1,63:1** trên nền
       trắng, **1,54:1** trên thẻ #f8f8f8 — đo được **425 nút** trên một màn hình. Đây là chỗ nặng
       nhất cả lượt rà: chữ vàng trên nền trắng gần như không có nét.
     · `.btn-outline-danger` #dc3545 "🗑 / Khóa" … 4,27:1 trên #f8f8f8 (trên nền trắng 4,53 — vừa đủ,
       nên lỗi chỉ lộ ra ở nút nằm trong thẻ/bảng có nền xám nhạt: /nuoc · /quan-tri/nguoi-dung ·
       /quan-tri/don-vi, tổng hơn 450 nút).
     · `.btn-outline-success` #198754 "Mở khóa / Đánh dấu xử lý" … 3,53:1 trên #e2e3e5.
   ⇒ Dùng ĐÚNG bộ màu mà bản CỔ ĐIỂN SÁNG đã dùng từ đợt 386c (co-dien.css mục 17) — không đặt bảng
     màu thứ hai. Đo lại: 8,91 · 9,46 · 9,36 (chữ) và ≥ 4,3 cho viền (ngưỡng viền là 3:1).
   ⚠ CHỈ đổi MÀU CHỮ + MÀU VIỀN, giữ nguyên trạng thái :hover của Bootstrap (nền đặc + chữ tương
     phản) — đổi cả hover là đổi hình dáng nút, việc khác hẳn việc chữa tương phản.
   ⛔ KHÔNG đảo nghĩa màu: đỏ vẫn là đỏ (bất lợi), xanh vẫn là xanh (có lợi) — chỉ hạ độ sáng. */
:root[data-theme="light"] .btn-outline-success,
:root:not([data-theme]) .btn-outline-success { color: #0f5132; border-color: #146c43; }

:root[data-theme="light"] .btn-outline-danger,
:root:not([data-theme]) .btn-outline-danger { color: #842029; border-color: #b02a37; }

:root[data-theme="light"] .btn-outline-warning,
:root:not([data-theme]) .btn-outline-warning { color: #664d03; border-color: #997404; }
/* 🔴 ĐỢT 850 (18/08/2026) — DÒNG THỨ TƯ BỊ ĐÁNH RƠI CỦA CHÍNH KHỐI ĐỢT 504 NÀY.
   Khối trên chép 3/4 dòng từ bảng cổ điển sáng (success · danger · warning) rồi **quên `info`** ⇒
   `.btn-outline-info` ở nền sáng vẫn là #0dcaf0 của Bootstrap, đo được **1,96:1** trên nền trắng —
   dưới xa ngưỡng 4,5:1 của chữ, và cũng dưới ngưỡng 3:1 của viền.
   ⚠ Chú thích ở dòng ~2262 (*"4 màu còn lại giữ nguyên"*) là chữ của đợt 381 và ĐÃ LẠC HẬU từ đợt
     504 — đọc nó rồi tưởng đây là "một họ nợ 4 màu" là SAI: đo đủ 10 ô (5 màu × 2 giao diện) ra
     **9 ĐẠT, đúng MỘT ô hỏng**.
   🎨 Vẫn theo đúng luật của khối 504: dùng ĐÚNG cặp màu bản CỔ ĐIỂN SÁNG đã chạy từ đợt 386c
     (co-dien.css mục 17) — ⛔ không đặt bảng màu thứ hai, không chọn mã màu mới.
     Đo cặp #055160 / #087990: nền trắng **8,93 / 5,06** · nền thẻ #f8f8f9 **8,41 / 4,77** ·
     hàng bảng đã khoá #e2e3e5 **6,95 / 3,94** ⇒ đạt cả ba nền, cả chữ lẫn viền.
   📍 Sáu chỗ dùng thật đã đo từng chỗ: TrangKeHoachDauTu.razor:245 (nặng nhất — nút nằm trong hàng
     `table-secondary` nên nền thật là #e2e3e5, chỉ **1,52:1**) · TrangTongHopPhepNam.razor:102
     (nút đôi, mặc định ở trạng thái outline ngay khi mở trang) · ChanDoanAD.razor:196 (ruột nút là
     thẻ <code> có luật màu riêng ⇒ chữ KHÔNG hỏng, chỉ viền hỏng) · BangTaiKhoan.razor:143 ·
     ChiTietBuoi.razor:119.
   ⛔ KHÔNG đổi lớp ở từng chỗ thay cho luật này: 2/5 chỗ là nút ĐÔI (outline = "chưa chọn"), đổi
     lớp là phải đổi luôn lớp đặc đi kèm, và `info` đang mang NGHĨA phân biệt hai nguồn số liệu.
   ✅ Đây là THÊM MỚI, không sửa cũ (quy tắc #18): trước đợt này `light/info` KHÔNG có luật nào của
     app, nên bán kính ảnh hưởng đúng bằng 6 chỗ dùng ở trên — không thể làm hỏng nút nào đang đúng.
   🚧 Hàng rào `TuongPhanGiaoDienTests` đã nới mảng lên 5 lớp CÙNG NHỊP với luật này (trước đó nó
     chép tay đúng 3 lớp nên **xanh trơn** trong khi lỗ mở toang — thừa hưởng đúng danh sách đã
     đánh rơi `info`). Đổi CSS thì sửa hàng rào đọc CSS cùng nhịp — bài học đợt 840. */
:root[data-theme="light"] .btn-outline-info,
:root:not([data-theme]) .btn-outline-info { color: #055160; border-color: #087990; }

/* ⚠ Thẻ <code> ở giao diện SÁNG: màu hồng #d63384 của Bootstrap đo được **4,50:1** trên nền trắng
   (⛔ đính chính 14/08/2026 — chú thích cũ ghi 4,31:1, tức ghi là TRƯỢT trong khi thực tế **vừa đúng
   ngưỡng**; lý do đổi màu vẫn giữ vì code hay in chữ nhỏ, nhưng số phải đúng) và
   4,06:1 trên nền cảnh báo #fff3cd — đo thật ở /quan-tri/ban-do-phan-quyen (33 chỗ, mỗi chỗ là một
   đường dẫn trang, tức là thứ người đọc PHẢI đọc từng ký tự). Hạ một nấc trong cùng họ hồng ⇒ 6,48:1
   trên trắng, 5,86:1 trên nền vàng nhạt. Nền TỐI đã có luật riêng (#ff9bb3) từ trước. */
:root[data-theme="light"] code,
:root:not([data-theme]) code { color: #ab296a; }

/* ═════ ĐỢT 504 — CHẤM CHÚ GIẢI CỦA BIỂU ĐỒ (dấu ● ▬ — mang màu của đường) ═════
   Bảng màu tuyến (#2bb673 xanh ngọc · #e8923a cam · #3b82c4 lam · #8a63d2 tím) chọn để PHÂN BIỆT
   NHAU trên biểu đồ, không phải để tương phản với nền. Đo sống 11/08 ở /nuoc tab "Phân tích số liệu":
   dấu ● đạt 2,44–4,38:1 trên nền sáng và 3,29–3,62:1 trên hai nền tối — hụt cả ngưỡng 3:1 của hình
   khối mang thông tin.
   ⛔ KHÔNG đổi bảng màu: dấu chú giải phải trùng màu đường, lệch là chú giải chỉ sai đường. Thay vào
     đó VIỀN quanh dấu bằng màu nền đối lập — mắt bắt được mép hình dù ruột nhạt. Cùng cách đợt 463
     đã dùng cho nhãn % của biểu đồ tròn (viền đục thay vì đổi màu lát).
   ⚠ Dùng `text-shadow` bốn hướng chứ không dùng `-webkit-text-stroke`: stroke vẽ ĐÈ VÀO RUỘT chữ nên
     dấu ● cỡ nhỏ bị ăn mất gần hết màu — đúng thứ đang cần giữ. */
.cham-chu-giai {
    text-shadow: 0 0 1px #0b1620, 1px 0 0 #0b1620, -1px 0 0 #0b1620, 0 1px 0 #0b1620, 0 -1px 0 #0b1620;
}

:root[data-theme="dark"] .cham-chu-giai,
:root[data-theme="co-dien-toi"] .cham-chu-giai {
    text-shadow: 0 0 1px #e6e9ed, 1px 0 0 #e6e9ed, -1px 0 0 #e6e9ed, 0 1px 0 #e6e9ed, 0 -1px 0 #e6e9ed;
}

/* ═════ ĐỢT 504 — DẤU • ĐỎ NẰM TRÊN TAB ĐANG MỞ CỦA BẢN CỔ ĐIỂN ═════
   Đo sống 11/08 ở /nuoc tab "Ghi chỉ số" và "Hóa đơn": dấu • báo "còn đồng hồ chưa nhập chỉ số"
   (TabKyNuoc.razor) nằm TRONG tab đang mở, mà tab đang mở của bản cổ điển tô VÀNG (#8a6a0b ở bản
   tối). Hồng đỏ #f1aeb5 của nền tối trên vàng chỉ được **2,76:1**.
   ⛔ Chỗ này KHÔNG chữa được bằng đổi màu chữ: giải ngược bất phương trình WCAG cho nền vàng đó ra
     "chữ phải sáng tới mức gần TRẮNG (#fff0f1) mới đạt 4,5" — lúc ấy nó hết là màu đỏ, tức là phá
     luật màu của dự án (số bất lợi = ĐỎ).
   ⇒ Giữ nguyên MÀU, viền quanh dấu. Dấu • là HÌNH KHỐI mang thông tin nên ngưỡng tính trên MÉP
     hình: viền #0b1620 trên nền vàng #8a6a0b được 3,60:1 — đạt ngưỡng 3:1.
   ⚠ Khai HẸP (chỉ trong `.nav-link.active` của hai bản cổ điển): mọi `.text-danger` khác đều nằm
     trên nền thường và đã đạt ngưỡng, viền thêm chỉ làm chữ dày lên vô cớ. */
:root[data-theme^="co-dien"] .nav-link.active .text-danger {
    text-shadow: 0 0 1px #0b1620, 1px 0 0 #0b1620, -1px 0 0 #0b1620, 0 1px 0 #0b1620, 0 -1px 0 #0b1620;
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   THẺ SỐ KỸ THUẬT — bộ đồ họa DÙNG CHUNG cho các phân hệ (đợt 435)
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Anh Tiến giao 10/08/2026: *"nâng cấp bố cục và đồ hoạ"* · *"đồ hoạ lại cho đẹp mắt, có hiệu ứng"* ·
   *"áp các đồ hoạ trend, có tính công nghệ và thời đại"* · *"em cũng làm thế với phân hệ quản lý
   nghỉ phép"*.

   ⭐ VÌ SAO ĐẶT Ở ĐÂY chứ không dùng CSS isolation từng trang: anh vừa bảo áp cho phân hệ THỨ HAI, và
   chắc chắn còn phân hệ thứ ba. Chép bộ luật này sang mỗi `.razor.css` là ba bản sẽ lệch nhau sau vài
   đợt — đúng luật số 3 của dự án: dùng tới chỗ thứ 2 thì tách ra dùng chung.

   CÁCH DÙNG (chỉ gắn lớp, không viết CSS mới):
       <div class="row g-2 hang-the-so">
         <div class="col-6 col-xl-3">
           <div class="card h-100 border-start border-4 border-primary shadow-sm the-so the-primary">…
   Biến thể màu: the-primary · the-info · the-success · the-warning · the-danger

   ⛔ CHỈ TRANG TRÍ Ở HAI GIAO DIỆN HIỆN ĐẠI (light/dark). Hai bản CỔ ĐIỂN cố ý mang dáng "phần mềm cũ"
      (chữ nhỏ, bảng kẻ ô, nút vuông) — dán kính mờ + phát sáng lên đó là phá đúng thứ người dùng quen
      tay. Vì vậy mọi luật trang trí đều nằm trong :root:not([data-theme^="co-dien"]).
   ⚠ Chỉ đụng NỀN (alpha thấp) · VIỀN · BÓNG — KHÔNG đổi màu chữ ⇒ giữ nguyên kết quả đo tương phản
      của đợt 430 (4 giao diện × 6 trang = 0 lỗi).
   ⚠ Mọi chuyển động tắt khi máy khai prefers-reduced-motion (cuối khối). */

/* ── Số liệu: font đều cột. Áp cho CẢ 4 giao diện — đây là chuyện ĐỌC SỐ, không phải trang trí.
   tabular-nums bắt mọi chữ số rộng BẰNG NHAU (không có nó thì số 1 hẹp hơn số 0, cột số nhảy qua nhảy
   lại mỗi lần đổi kỳ); slashed-zero để 0 không lẫn chữ O. */
.the-so .fs-2,
.the-so .fs-3,
.the-so .fs-4,
/* ⭐ ĐỢT 1295 (việc o484) — nhận thêm ba lớp TIÊU ĐỀ. Bootstrap có HAI cách gọi cùng một cỡ chữ:
   .fs-4 (chỉ đặt cỡ) và .h4 (cỡ + đậm + lề). Trang /tong-quan-nhiem-vu dựng con số bằng .h4, nên
   trước đợt này nó nằm trong .the-so mà KHÔNG hưởng chữ số đều cột — hỏng LẶNG LẼ: cột số nhảy qua
   nhảy lại mỗi lần đổi kỳ, mà không luật nào kêu.
   ⛔ Đây là chuyện ĐỌC SỐ, không phải trang trí, nên nó nằm ngoài mọi chốt :root — áp cho cả bốn
   giao diện, y như ba lớp .fs-* ở trên. */
.the-so .h2,
.the-so .h3,
.the-so .h4 {
    font-variant-numeric: tabular-nums slashed-zero;
    letter-spacing: -.025em;
    line-height: 1.1;
}

.the-so .small strong,
.the-so .thanh-tien-do-nhan { font-variant-numeric: tabular-nums; }

/* ── Thẻ: kính mờ + viền phát sáng (chỉ giao diện hiện đại) ── */
:root:not([data-theme^="co-dien"]) .the-so {
    position: relative;
    overflow: hidden;
    border-radius: .85rem;
    /* Kính mờ: nền bán trong suốt + làm nhòe thứ phía sau. Trình duyệt không hỗ trợ thì rơi về nền
       phẳng của Bootstrap — không vỡ bố cục. */
    backdrop-filter: blur(10px) saturate(1.15);
    -webkit-backdrop-filter: blur(10px) saturate(1.15);
    transition: transform .18s cubic-bezier(.2, .8, .3, 1), box-shadow .18s ease;
}

/* Vệt sáng loang từ góc trên-trái theo VAI của thẻ (vạch màu bên trái đã phân vai sẵn) */
:root:not([data-theme^="co-dien"]) .the-so.the-primary { background-image: linear-gradient(135deg, rgba(13, 110, 253, .12), transparent 58%); }
:root:not([data-theme^="co-dien"]) .the-so.the-info    { background-image: linear-gradient(135deg, rgba(13, 202, 240, .14), transparent 58%); }
:root:not([data-theme^="co-dien"]) .the-so.the-success { background-image: linear-gradient(135deg, rgba(25, 135, 84, .12), transparent 58%); }
:root:not([data-theme^="co-dien"]) .the-so.the-warning { background-image: linear-gradient(135deg, rgba(255, 193, 7, .15), transparent 58%); }
:root:not([data-theme^="co-dien"]) .the-so.the-danger  { background-image: linear-gradient(135deg, rgba(220, 53, 69, .12), transparent 58%); }

/* Đường sáng mảnh chạy dọc mép trên — thứ tạo cảm giác "bảng điều khiển".
   ::after không bắt chuột nên không cản cú bấm nào. */
:root:not([data-theme^="co-dien"]) .the-so::after {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 1px;
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .55), transparent);
    pointer-events: none;
}

/* Nền TỐI: vệt sáng mép trên phải mờ hơn, không thì thành gạch trắng chói */
:root[data-theme="dark"] .the-so::after {
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .22), transparent);
}

/* ── Rê chuột: nhấc thẻ + quầng sáng theo vai. Nhấc 3px; cao hơn là chữ rung khi rê ngang nhiều thẻ ── */
:root:not([data-theme^="co-dien"]) .the-so:hover { transform: translateY(-3px); }
:root:not([data-theme^="co-dien"]) .the-so.the-primary:hover { box-shadow: 0 .6rem 1.4rem rgba(13, 110, 253, .28) !important; }
:root:not([data-theme^="co-dien"]) .the-so.the-info:hover    { box-shadow: 0 .6rem 1.4rem rgba(13, 202, 240, .28) !important; }
:root:not([data-theme^="co-dien"]) .the-so.the-success:hover { box-shadow: 0 .6rem 1.4rem rgba(25, 135, 84, .28) !important; }
:root:not([data-theme^="co-dien"]) .the-so.the-warning:hover { box-shadow: 0 .6rem 1.4rem rgba(255, 193, 7, .30) !important; }
:root:not([data-theme^="co-dien"]) .the-so.the-danger:hover  { box-shadow: 0 .6rem 1.4rem rgba(220, 53, 69, .28) !important; }

/* ── Xuất hiện SO LE: mắt đọc trái→phải nên thẻ sau trễ hơn 70ms, thành một nhịp quét chứ không phải
   cả hàng bật cùng lúc. Tổng ~0,5s: đủ thấy có chuyển động, chưa đủ để phải chờ.
   Áp cho cả cổ điển (chỉ là trôi lên, không phá dáng). */
.hang-the-so > div:nth-child(1) .the-so { animation: the-so-hien .38s cubic-bezier(.2, .8, .3, 1) both; }
.hang-the-so > div:nth-child(2) .the-so { animation: the-so-hien .38s cubic-bezier(.2, .8, .3, 1) .07s both; }
.hang-the-so > div:nth-child(3) .the-so { animation: the-so-hien .38s cubic-bezier(.2, .8, .3, 1) .14s both; }
.hang-the-so > div:nth-child(4) .the-so { animation: the-so-hien .38s cubic-bezier(.2, .8, .3, 1) .21s both; }
.hang-the-so > div:nth-child(5) .the-so { animation: the-so-hien .38s cubic-bezier(.2, .8, .3, 1) .28s both; }
/* ⭐ ĐỢT 1295 (việc o484) — nấc THỨ SÁU cho hàng thẻ của /tong-quan-nhiem-vu.
   ⚠ Thiếu nấc này thì thẻ thứ sáu KHÔNG có `animation` nào ⇒ nó hiện ra NGAY trong khi năm thẻ trước
     còn đang trôi lên: cả hàng trông như hỏng dở, mà build xanh test xanh. Đây là cái bẫy của kiểu
     khai theo `nth-child` — nó im lặng với mọi thẻ vượt số nấc đã khai.
   ⛔ Đừng đổi sang `:nth-child(n)` tính bằng biến: mỗi nấc phải là một độ trễ CỐ ĐỊNH đọc được, và
     dải này cố ý dừng ở sáu — hàng dài hơn thì thẻ cuối phải chờ quá nửa giây mới hiện. */
.hang-the-so > div:nth-child(6) .the-so { animation: the-so-hien .38s cubic-bezier(.2, .8, .3, 1) .35s both; }

@keyframes the-so-hien {
    from { opacity: 0; transform: translateY(10px) scale(.985); }
    to   { opacity: 1; transform: none; }
}

/* ── Viên thuốc cho số so sánh (▲ ▼): tô NỀN theo màu chữ sẵn có, KHÔNG đổi màu chữ ⇒ giữ nguyên luật
   màu theo chiều lợi/bất lợi và giữ nguyên tỷ số tương phản đã đo. */
.the-so .small.text-danger,
.the-so .small.text-success {
    display: inline-block;
    padding: .08rem .45rem;
    border-radius: 999px;
    background-color: color-mix(in srgb, currentColor 13%, transparent);
    font-variant-numeric: tabular-nums;
}

/* ── THANH TIẾN ĐỘ (phép năm đã dùng / còn lại…) ──
   Nền là rãnh mờ, phần đã dùng là dải màu có vệt sáng chạy. Chiều rộng do Razor tính và ghi vào
   style="width:…%" — ⚠ số đó BẮT BUỘC dùng InvariantCulture (quy tắc #5: app chạy vi-VN, dấu phẩy
   thập phân trong CSS là dấu NGĂN nên thanh sẽ vỡ mà build vẫn sạch). */
.thanh-tien-do {
    height: .5rem;
    border-radius: 999px;
    background-color: color-mix(in srgb, currentColor 14%, transparent);
    overflow: hidden;
}

.thanh-tien-do > i {
    display: block;
    height: 100%;
    border-radius: inherit;
    background-color: currentColor;
    transition: width .5s cubic-bezier(.2, .8, .3, 1);
}

/* Vệt sáng chạy ngang phần đã dùng — chỉ giao diện hiện đại, và chỉ khi thanh CHƯA đầy
   (đầy rồi mà còn chạy thì trông như đang tải mãi không xong). */
:root:not([data-theme^="co-dien"]) .thanh-tien-do > i {
    background-image: linear-gradient(90deg, transparent, rgba(255, 255, 255, .45), transparent);
    background-size: 40% 100%;
    background-repeat: no-repeat;
    animation: thanh-tien-do-loang 1.8s ease-in-out infinite;
}

@keyframes thanh-tien-do-loang {
    from { background-position: -40% 0; }
    to   { background-position: 140% 0; }
}

/* ── Người tắt hiệu ứng chuyển động: bỏ trôi lên + nhấc + vệt sáng; GIỮ kính mờ và màu (tĩnh) ── */
@media (prefers-reduced-motion: reduce) {
    .hang-the-so > div .the-so { animation: none; }
    :root:not([data-theme^="co-dien"]) .the-so { transition: none; }
    :root:not([data-theme^="co-dien"]) .the-so:hover { transform: none; }
    .thanh-tien-do > i { transition: none; animation: none; background-image: none; }
}
/* ⛔ ĐỢT 435 — VÁ NGAY TRONG CÙNG ĐỢT: nền loang alpha 12–15% ở trên là QUÁ MẠNH trên NỀN TỐI.
   Đo thật ngay sau khi áp: 8 chỗ tụt xuống 3,89:1 (cần 4,5) — chữ phụ `.text-muted` màu #9aa0a8 nằm
   trên nền vàng đã bị đẩy sáng thành rgb(71,65,43). Chữ phụ ở nền tối vốn đã sát ngưỡng, cộng thêm
   một lớp màu là rơi ngay.
   ⇒ Nền tối dùng alpha ~5% và tắt loang sớm hơn (42% thay vì 58%). Vẫn thấy rõ vai của thẻ, nhưng
     không ăn vào tương phản. Đây là lý do luật đo là ĐO SAU KHI SỬA, không phải đo trước.
   ⚠ Đặt SAU khối trên để thắng theo thứ tự; cùng độ ưu tiên nên không cần !important. */
:root[data-theme="dark"] .the-so.the-primary { background-image: linear-gradient(135deg, rgba(13, 110, 253, .05), transparent 42%); }
:root[data-theme="dark"] .the-so.the-info    { background-image: linear-gradient(135deg, rgba(13, 202, 240, .05), transparent 42%); }
:root[data-theme="dark"] .the-so.the-success { background-image: linear-gradient(135deg, rgba(25, 135, 84, .05), transparent 42%); }
:root[data-theme="dark"] .the-so.the-warning { background-image: linear-gradient(135deg, rgba(255, 193, 7, .05), transparent 42%); }
:root[data-theme="dark"] .the-so.the-danger  { background-image: linear-gradient(135deg, rgba(220, 53, 69, .05), transparent 42%); }
/* ═══════════════ ĐỢT 447 — NÚT NỔI DẠNG 3D (anh Tiến 11/08/2026) ═══════════════
   Nguyên văn: *"trên nav đầu trang, các tab cũng phải được đồ hoạ dạng nút 3d tương đồng"* và
   *"nút xin nghỉ phép ở trang chủ cũng cần tân trang lại"*.
   "Tương đồng" = cùng ngôn ngữ với lát biểu đồ tròn nhô ra ở đợt 441: NHÔ LÊN khi rê chuộtnhau,
   LÚN XUỐNG khi bấm, có mặt sáng ở trên và bóng đổ ở dưới.

   ⛔ CHỈ ÁP CHO HAI GIAO DIỆN HIỆN ĐẠI (`:root:not([data-theme^="co-dien"])`). Hai giao diện cổ điển
      CỐ Ý trông như phần mềm đời cũ — anh chọn cổ điển là muốn vậy; tô 3D vào đó là phá đúng thứ anh
      chọn. Cùng luật với khối `.the-so` ở trên.
   ⚠ Dùng `box-shadow` + `translateY`, KHÔNG dùng `filter`/`backdrop-filter`: dải tab nằm trong ô có
     `overflow-x: auto`, mà filter tạo NGỮ CẢNH XẾP CHỒNG mới ⇒ nút bị cắt khi cuộn ngang.
   ⚠ Chỉ chuyển động `transform` + `box-shadow` (hai tính chất trình duyệt tăng tốc được); đổi
     `padding`/`border-width` khi hover là làm cả dải tab XÊ DỊCH theo, chữ nhảy. */

:root:not([data-theme^="co-dien"]) .top-row .mcd-dai-tab .nav-link,
:root:not([data-theme^="co-dien"]) .nut-noi {
    border: 1px solid var(--bs-border-color);
    border-radius: .45rem;
    background-image: linear-gradient(180deg, rgba(255, 255, 255, .16), rgba(0, 0, 0, .05));
    box-shadow: 0 1px 0 rgba(255, 255, 255, .22) inset, 0 2px 3px rgba(0, 0, 0, .16);
    transition: transform .14s ease-out, box-shadow .14s ease-out, filter .14s ease-out;
}

:root:not([data-theme^="co-dien"]) .top-row .mcd-dai-tab .nav-link:hover,
:root:not([data-theme^="co-dien"]) .nut-noi:hover {
    transform: translateY(-2px);
    box-shadow: 0 1px 0 rgba(255, 255, 255, .3) inset, 0 5px 10px rgba(0, 0, 0, .24);
    filter: brightness(1.06);
}

/* LÚN XUỐNG khi bấm — thứ làm nút "có thật". Cả lúc giữ chuột lẫn tab ĐANG MỞ. */
:root:not([data-theme^="co-dien"]) .top-row .mcd-dai-tab .nav-link:active,
:root:not([data-theme^="co-dien"]) .nut-noi:active {
    transform: translateY(1px);
    box-shadow: 0 1px 2px rgba(0, 0, 0, .3) inset;
}

/* ⭐ ĐỢT 680 — TAB NỔI LÊN, KHÔNG LÚN XUỐNG NỮA. Anh Tiến 15/08/2026: *"các nút tab nhìn như LÕM XUỐNG,
   anh muốn nó LỒI 3D lên và có ĐỘ BÓNG, hiệu ứng khi nhấn"*.
   ⛔ ĐẢO CHIỀU quyết định của đợt 447 — câu cũ ở đây ghi *"tab ĐANG MỞ lún hẳn vào trong như phím đang
     bị nhấn giữ"*. Ý đồ hồi đó (nhìn là biết đang ở đâu) VẪN GIỮ, chỉ đổi CÁCH nói: nay tab đang mở
     **nhô cao nhất dải** + bóng đổ dày + vệt sáng trên đỉnh, thay vì thụt vào.
   ⓘ Vẫn hai kênh chứ không chỉ màu (độ nổi + gạch chân dày), nên người phân biệt màu kém vẫn thấy được.
   ⚠ Hiệu ứng KHI NHẤN chuyển hẳn về `:active` ngay dưới — lún chỉ xảy ra trong lúc giữ chuột, đúng cảm
     giác một phím bấm thật. */
:root:not([data-theme^="co-dien"]) .top-row .mcd-dai-tab .nav-link.active {
    transform: translateY(-2px);
    background-image: linear-gradient(180deg, rgba(255, 255, 255, .26), rgba(0, 0, 0, .07));
    box-shadow: 0 1px 0 rgba(255, 255, 255, .38) inset,
                0 6px 12px rgba(0, 0, 0, .3),
                0 2px 3px rgba(0, 0, 0, .2);
    font-weight: 600;
}

/* Tab THƯỜNG cũng nổi nhẹ (trước đây phẳng lì, chỉ hover mới có gì đó) — cả dải nhìn như hàng phím. */
:root:not([data-theme^="co-dien"]) .top-row .mcd-dai-tab .nav-link {
    border-radius: .45rem;
    background-image: linear-gradient(180deg, rgba(255, 255, 255, .12), rgba(0, 0, 0, .04));
    box-shadow: 0 1px 0 rgba(255, 255, 255, .18) inset, 0 2px 3px rgba(0, 0, 0, .14);
    transition: transform .14s ease-out, box-shadow .14s ease-out, filter .14s ease-out;
}

/* NHẤN GIỮ: lún xuống + bóng vào trong — hiệu ứng anh Tiến yêu cầu, nay là của riêng lúc bấm. */
:root:not([data-theme^="co-dien"]) .top-row .mcd-dai-tab .nav-link.active:active {
    transform: translateY(1px);
    box-shadow: 0 1px 3px rgba(0, 0, 0, .32) inset;
}

/* Bàn phím vẫn phải thấy rõ đang ở nút nào (viền sáng của trình duyệt bị bóng đổ nuốt mất). */
:root:not([data-theme^="co-dien"]) .top-row .mcd-dai-tab .nav-link:focus-visible,
:root:not([data-theme^="co-dien"]) .nut-noi:focus-visible {
    outline: 2px solid var(--bs-primary);
    outline-offset: 2px;
}

/* ĐỢT 519 — NỀN TỐI: đổi MÀU viền focus, giữ nguyên độ dày và khoảng chừa.
   Đo sống: `var(--bs-primary)` = #0d6efd trên dải tab nền tối rgb(45,50,58) chỉ được **2,88:1**, dưới
   ngưỡng 3:1 của WCAG 1.4.11 (chỉ báo PHI VĂN BẢN). Cùng màu ấy ở nền sáng lại đạt 4,22 — nên lỗi chỉ
   lộ ở một giao diện, đúng mẫu "viết và nhìn ở một phong cách" mà lượt rà 455 nói tới.
   #7fb6ff đo được **6,19:1**; đây là màu app ĐÃ dùng làm `outline-color` ở khối canvas phía dưới, không
   phải màu mới. Chỉ đụng trạng thái :focus-visible ⇒ lúc nghỉ mọi màn hình y nguyên. */
:root[data-theme="dark"] .top-row .mcd-dai-tab .nav-link:focus-visible,
:root[data-theme="dark"] .nut-noi:focus-visible {
    outline-color: #7fb6ff;
}

/* Ai đặt máy "giảm chuyển động" thì bỏ phần NHÚN, giữ nguyên phần nổi khối (đó là thông tin về hình
   dạng nút, không phải hiệu ứng). */
@media (prefers-reduced-motion: reduce) {
    :root:not([data-theme^="co-dien"]) .top-row .mcd-dai-tab .nav-link,
    :root:not([data-theme^="co-dien"]) .nut-noi {
        transition: none;
    }
    :root:not([data-theme^="co-dien"]) .top-row .mcd-dai-tab .nav-link:hover,
    :root:not([data-theme^="co-dien"]) .nut-noi:hover { transform: none; }
}

/* ⛔ ĐỢT 463 — VÁ TƯƠNG PHẢN DẢI TAB Ở NỀN TỐI (đo thật bằng công cụ WCAG ghép chồng lớp).
   Gradient trắng 16% ở khối trên nâng nền `.top-row` từ #2d323a lên rgb(79,83,90); chữ tab #9ec5fe trên
   nền đã bị đẩy sáng đó chỉ còn 4,41:1 — dưới ngưỡng 4,5 của chữ thường. Hỏng đúng ở TRẠNG THÁI NGHỈ,
   tức là trạng thái người dùng nhìn suốt ngày; :hover (4,88) và .active (8,62) đều đã đạt.
   ⇒ Nền tối hạ alpha trắng xuống 12% (đo lại 4,99). Đúng khuôn đợt 435: nền tối luôn cần alpha THẤP HƠN
     nền sáng vì mỗi lớp trắng cộng vào là một lần đẩy nền lên gần màu chữ.
   ⚠ GIỮ NGUYÊN màu chữ #9ec5fe — chữa bằng cách đổi màu chữ là kéo theo cả bảng màu xanh của giao diện.
   ⚠ Cùng độ ưu tiên với khối đợt 447 ở trên (đều 5 lớp hạng-class) ⇒ thắng nhau BẰNG THỨ TỰ DÒNG, phải
     đặt SAU. Đừng dời khối này lên trên, cũng đừng thêm !important (che mất luật thứ tự cho người sau). */
:root[data-theme="dark"] .top-row .mcd-dai-tab .nav-link {
    background-image: linear-gradient(180deg, rgba(255, 255, 255, .12), rgba(0, 0, 0, .05));
}

/* ═══ ĐỢT 448 — HỘP CHÚ THÍCH BÁM THEO CHUỘT (dùng chung cho biểu đồ) ═══
   Anh Tiến 11/08/2026: *"khi di chuột đến thì sẽ hiện chú thích ở chuột chứ không phải ở dưới đồ thị"*.
   ⚠ `pointer-events: none` là BẮT BUỘC: hộp bám sát con trỏ, nếu nó nhận chuột thì chính nó che mất
     lát/miền đang rê ⇒ sự kiện `mouseout` bắn liên tục, chú thích nháy tắt không đọc được.
   ⚠ Vị trí do wwwroot/chu-thich-chuot.js đặt (left/top theo khung cha có position:relative) — CSS chỉ
     lo hình thức. Đặt sẵn left/top = 0 để lần hiện đầu chưa kịp có toạ độ thì nó nằm trong khung chứ
     không nhảy ra góc màn hình. */
.ct-chuot {
    position: absolute;
    left: 0;
    top: 0;
    z-index: 30;
    pointer-events: none;
    max-width: 17rem;
    padding: .4rem .55rem;
    border: 1px solid var(--bs-border-color);
    border-radius: .4rem;
    background: var(--bs-body-bg);
    color: var(--bs-body-color);
    box-shadow: 0 4px 14px rgba(0, 0, 0, .28);
    font-size: .78rem;
    line-height: 1.35;
    white-space: normal;
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   Ô BÁO MẤT KẾT NỐI — 4 GIAO DIỆN (đợt 504, anh Tiến báo 11/08/2026)
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Nguyên văn: *"ô báo mất kết nối ở chế độ darkmode đọc không được"*.

   GỐC LỖI: app CHƯA từng khai ô này ở đâu ⇒ đang xài ô MẶC ĐỊNH của Blazor. Từ .NET 10 ô mặc định
   nằm trong SHADOW DOM và mang sẵn thẻ <style> riêng, nên nó KHÔNG biết gì về 4 giao diện của app:
   nền trắng + chữ đen dán thẳng lên màn hình tối. Viết CSS đè cũng vô ích — shadow DOM chặn hết.
   ⇒ Cách chữa: khai sẵn phần tử id="components-reconnect-modal" trong Components/App.razor (Blazor
   thấy có sẵn thì dùng phần tử đó và chỉ gắn/gỡ LỚP), rồi tự vẽ toàn bộ ở đây.

   ⚠ ĐÂY LÀ MÀN HÌNH NGƯỜI DÙNG GẶP LÚC ĐANG HOẢNG — mất mạng giữa lúc nhập liệu. Ba đòi hỏi:
     (1) đọc được ngay ở CẢ 4 giao diện · (2) nói rõ ĐANG GÌ và PHẢI LÀM GÌ · (3) không bắt ai xoay xở.
   ⚠ Mọi cặp màu dưới đây khai TƯỜNG MINH (không thừa kế, không dùng biến Bootstrap) — ô này đè lên
     toàn màn hình nên không được để màu chữ ăn theo trang phía dưới. Tỉ số WCAG đo thật ghi ngay cạnh.
   ⚠ z-index 2000 = TRÊN cả modal Bootstrap (1055): mất mạng lúc đang mở hộp thoại thì lời báo vẫn
     phải nằm trên cùng. Đây là NGOẠI LỆ có chủ đích của luật "khối nổi phải nằm dưới" ở bảng tin nền —
     bảng tin là thứ để liếc, ô này là thứ chặn hẳn công việc.
   ⛔ ĐỪNG đổi id/tên lớp components-reconnect-* — đó là hợp đồng với blazor.web.js.

   ═══ ĐỢT 511 (anh Tiến 11/08/2026) — "căn đều đẹp có hiệu ứng và công nghệ, có 1 app để chơi ngay
       trên đó … bỏ hết nút đi" ═══
   · HAI NÚT ĐÃ GỠ. Việc của chúng do wwwroot/tro-choi-mat-ket-noi.js làm tự động (đếm ngược rồi gọi
     `Blazor.reconnect()` hoặc tải lại trang). ⇒ Mọi luật `.mkn-nut*` cũng đã gỡ khỏi file này —
     thấy lớp `mkn-nut` ở đâu nữa là mã chết, xoá đi.
   · Thay vào đó có dòng `.mkn-tu-dong` (hiện SỐ GIÂY đếm ngược) + khối `.mkn-tro-choi` (trò chơi chờ).
   · Hiệu ứng đều là CSS thuần (`mkn-vao` · `mkn-tho` · `mkn-song-nhip`) — mạng nội bộ không có
     Internet nên tuyệt đối không thư viện ngoài. Có nhánh `prefers-reduced-motion` ở cuối mục. */

#components-reconnect-modal.mkn {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 2000;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    /* ĐỢT 523 (anh Tiến 11/08/2026: *"đồ hoạ ô mất kết nối cho mềm mại đẹp và có hiệu ứng"*):
       nền phủ nay là gradient toả từ giữa + làm mờ hậu cảnh, thay mảng đen phẳng. `background-color`
       vẫn khai NGUYÊN (dòng dưới) làm lớp lót: trình duyệt cũ không hiểu `radial-gradient` hai chặng
       hoặc `backdrop-filter` thì vẫn còn nền tối để chữ nổi — ⛔ đừng gỡ nó đi cho gọn. */
    background-color: rgba(12, 17, 24, .62);
    background-image: radial-gradient(120% 100% at 50% 0%, rgba(27, 110, 194, .22) 0%, rgba(12, 17, 24, .66) 55%, rgba(8, 12, 18, .78) 100%);
    backdrop-filter: blur(3px) saturate(1.05);
    animation: mkn-hien .28s ease-out both;
}

/* Blazor gắn MỘT trong các lớp này khi mạch đứt. `-retrying` được gắn CHỒNG lên `-show`. */
#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-retrying,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected,
#components-reconnect-modal.components-reconnect-paused,
#components-reconnect-modal.components-reconnect-resume-failed {
    display: flex;
}

/* Đặt SAU cùng nhóm trên: nối lại được thì phải tắt hẳn, không được để sót một lớp nào giữ nó lại. */
#components-reconnect-modal.components-reconnect-hide {
    display: none;
}

/* CĂN ĐỀU: thẻ là một CỘT FLEX có `gap` chung, thay cho việc mỗi đoạn tự khai `margin-bottom` một
   kiểu. Đây mới là chỗ "căn đều" thật sự — `gap` chỉ tính giữa những phần tử ĐANG hiện, còn margin
   thì để lại khoảng trống của cả những câu đang `display:none` (ô này lúc nào cũng giấu 4/5 câu).
   `max-height` + `overflow-y` để màn hình thấp (điện thoại nằm ngang) vẫn đọc được hết. */
.mkn-the {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .5rem;
    width: 100%;
    max-width: 34rem;
    max-height: calc(100vh - 2rem);
    max-height: calc(100dvh - 2rem);   /* ⭐ 1500 — dòng lui cho DI ĐỘNG, xem LuatDonViKhungNhin */
    overflow-y: auto;
    padding: 1.35rem 1.4rem 1.4rem;
    border: 1px solid #d0d5db;
    border-radius: 1.1rem;              /* đợt 523 — bo mềm hẳn, .6rem cũ vẫn còn dáng hộp thoại cứng */
    background-color: #fff;
    /* Nền hơi ngả sáng ở đỉnh cho có chiều sâu. ⚠ Chặng nhạt nhất vẫn là #fff và chặng đậm nhất là
       #f4f8fd (tỉ số của chữ #212529 trên nó vẫn 15,3:1) — đổi hai chặng này phải đo lại tương phản. */
    background-image: linear-gradient(180deg, #ffffff 0%, #f9fbfe 62%, #f4f8fd 100%);
    color: #212529;                     /* 16,1:1 trên nền trắng */
    /* Bóng NHIỀU LỚP thay một lớp cứng: một lớp sát mép cho gờ, một lớp xa cho độ nổi, một quầng
       xanh rất nhạt cho "đang chờ máy chủ". Mềm mắt hơn hẳn `0 1rem 2.5rem rgba(0,0,0,.35)`. */
    box-shadow: 0 .05rem .15rem rgba(16, 24, 40, .10),
                0 1.1rem 2.6rem rgba(16, 24, 40, .30),
                0 0 0 .35rem rgba(27, 110, 194, .10);
    text-align: center;
    animation: mkn-vao .34s cubic-bezier(.22, 1, .36, 1) both;
}

.mkn-dau {
    display: flex;
    gap: .5rem;
    align-items: baseline;
    justify-content: center;
}

/* inline-block để `transform` của hiệu ứng thở ăn được — span thường không nhận transform. */
.mkn-icon {
    display: inline-block;
    font-size: 1.45rem;
    line-height: 1;
    /* Đợt 523 — "thở" cũ đổi cỡ đột ngột (scale 1 → 1,18). Nay nhịp mềm hơn và có quầng sáng ấm
       toả theo nhịp: mắt bắt được là "phần mềm vẫn đang sống", không phải hình đứng im. */
    animation: mkn-tho 2.4s ease-in-out infinite;
    filter: drop-shadow(0 0 .35rem rgba(176, 42, 55, .35));
}

/* ───────────── SÓNG NHỊP — hiệu ứng "đang chờ", thuần CSS ─────────────
   Năm vạch chạy lệch pha nhau bằng `animation-delay`; không tốn JS, không tốn một lượt đi mạng nào
   (mà lúc này thì cũng chẳng còn mạng). */
.mkn-song {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    gap: .28rem;
    height: 1.35rem;
}

.mkn-song span {
    width: .34rem;
    height: 100%;
    border-radius: .17rem;
    background-color: #1b6ec2;          /* 5,18:1 trên nền trắng — vạch đồ hoạ, ngưỡng 3:1 */
    /* Đợt 523 — vạch nay chuyển sắc từ đỉnh xuống chân + có quầng nhẹ; chân vạch nhạt dần nên
       không còn cảm giác năm que gỗ cắm thẳng. ⚠ Chặng đậm giữ đúng #1b6ec2 đã đo. */
    background-image: linear-gradient(180deg, #3d8bdc 0%, #1b6ec2 60%, #185ea6 100%);
    box-shadow: 0 0 .4rem rgba(27, 110, 194, .45);
    transform: scaleY(.22);
    transform-origin: bottom;
    animation: mkn-song-nhip 1.25s cubic-bezier(.45, 0, .55, 1) infinite;
}

.mkn-song span:nth-child(2) { animation-delay: .13s; }
.mkn-song span:nth-child(3) { animation-delay: .26s; }
.mkn-song span:nth-child(4) { animation-delay: .39s; }
.mkn-song span:nth-child(5) { animation-delay: .52s; }

.mkn-tieu-de {
    font-size: 1.15rem;
    font-weight: 700;
    color: #b02a37;                     /* 5,86:1 trên nền trắng — nấc đỏ đậm đã dùng từ đợt 430 */
}

/* Mỗi trạng thái một câu. Giấu hết, rồi mở đúng một câu theo lớp Blazor đang gắn.
   ⚠ `margin: 0` — khoảng cách do `gap` của `.mkn-the` lo (xem chú thích ở khối đó). */
#components-reconnect-modal .mkn-cau {
    display: none;
    margin: 0;
    line-height: 1.5;
}

#components-reconnect-modal.components-reconnect-show:not(.components-reconnect-retrying) .mkn-cau-dang-noi,
#components-reconnect-modal.components-reconnect-retrying .mkn-cau-thu-lai,
#components-reconnect-modal.components-reconnect-failed .mkn-cau-hong,
#components-reconnect-modal.components-reconnect-resume-failed .mkn-cau-hong,
#components-reconnect-modal.components-reconnect-rejected .mkn-cau-tu-choi,
#components-reconnect-modal.components-reconnect-paused .mkn-cau-tam-dung {
    display: block;
}

.mkn-tran-an,
.mkn-tro-choi-chu {
    margin: 0;
    font-size: .87rem;
    line-height: 1.45;
    color: #495057;                     /* 8,59:1 trên nền trắng */
}

/* ───────────── DÒNG "MÁY TỰ LÀM" — thay cho hai cái nút đã bỏ ─────────────
   Chỉ hiện ở hai (ba) trạng thái Blazor đã NGỪNG tự thử lại. Ở `-show`/`-retrying` thì Blazor đã có
   sẵn bộ đếm riêng, thêm dòng này nữa là hai con số đếm ngược cạnh nhau — rối.
   ⚠ Chữ trong dòng này do JS ghi đè. HTML để sẵn câu dự phòng nhắc phím F5 phòng khi tệp JS không
   nạp được: không có nút thì đó là đường ra cuối cùng, không được bỏ trống. */
#components-reconnect-modal .mkn-tu-dong {
    display: none;
    margin: 0;
    font-size: .92rem;
    font-weight: 600;
    line-height: 1.45;
    color: #0b5ed7;                     /* 5,82:1 trên nền trắng */
}

#components-reconnect-modal.components-reconnect-failed .mkn-tu-dong,
#components-reconnect-modal.components-reconnect-resume-failed .mkn-tu-dong,
#components-reconnect-modal.components-reconnect-rejected .mkn-tu-dong {
    display: block;
}

/* ───────────── TRÒ CHƠI CHỜ (đợt 511) ─────────────
   Chỉ bày ra khi JS gắn lớp `mkn-co-tro-choi` — tức là sau ~2,5 giây vẫn chưa nối lại được. Chớp
   mạng một cái rồi nối lại ngay thì không hiện gì cả.
   ⚠ `aspect-ratio` khai TƯỜNG MINH chứ không để canvas tự lấy tỉ lệ từ thuộc tính width/height:
   JS đặt lại `canvas.width/height` theo tỉ lệ điểm ảnh màn hình mỗi lần khớp cỡ khung; để canvas tự
   suy tỉ lệ là cỡ CSS đổi theo → lại kích hoạt khớp cỡ → vòng lặp không dứt. */
.mkn-tro-choi {
    display: none;
    width: 100%;
}

#components-reconnect-modal.mkn-co-tro-choi .mkn-tro-choi {
    display: flex;
    flex-direction: column;
    gap: .4rem;
    animation: mkn-vao .22s ease-out both;
}

/* Bốn biến màu bên dưới là BẢNG MÀU CỦA TRÒ CHƠI — JS đọc bằng getComputedStyle rồi vẽ lên canvas.
   ⛔ Khai màu thẳng trong JS là mất luôn 4 giao diện: canvas không thừa kế màu như HTML. */
.mkn-canvas {
    --mkn-tc-nen: #e3f0fb;
    --mkn-tc-dat: #5c7048;
    --mkn-tc-cot: #2e7d32;
    --mkn-tc-heo: #d6336c;
    --mkn-tc-may: #ffffff;
    --mkn-tc-chu: #212529;
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 5 / 2;
    border: 1px solid #b7c4d2;          /* đợt 523 — viền dịu hơn #6c757d, hợp với thẻ đã bo mềm */
    border-radius: .75rem;
    background-color: #e3f0fb;          /* khớp --mkn-tc-nen: canvas chưa vẽ kịp thì vẫn đúng màu */
    box-shadow: inset 0 .1rem .5rem rgba(16, 24, 40, .10);
    cursor: pointer;
    touch-action: manipulation;         /* chạm để heo bay, không phóng to trang */
}

.mkn-canvas:focus-visible {
    outline: 2px solid #1b6ec2;
    outline-offset: 2px;
}

/* Màn hình quá thấp thì bỏ trò chơi đi — câu báo kết nối mới là phần chính, không được đẩy nó ra
   ngoài tầm nhìn để nhường chỗ cho trò chơi. */
@media (max-height: 460px) {
    #components-reconnect-modal.mkn-co-tro-choi .mkn-tro-choi { display: none; }
}

/* ───────────── NỀN TỐI (hiện đại) ───────────── */
:root[data-theme="dark"] #components-reconnect-modal.mkn { background-color: rgba(0, 0, 0, .72); }

:root[data-theme="dark"] .mkn-the {
    border-color: #4a515a;
    background-color: #23272e;
    /* Đợt 523 — cùng cách làm mềm như nền sáng, nhưng nền tối thì gradient phải NGƯỢC (sáng ở đỉnh
       chỉ nhích một chút): nền tối mà chênh nhiều là thành vệt loang. Chữ #e6e9ed trên chặng sáng
       nhất #2a2f37 vẫn 11,1:1. */
    background-image: linear-gradient(180deg, #2a2f37 0%, #23272e 60%, #1f232a 100%);
    box-shadow: 0 .05rem .15rem rgba(0, 0, 0, .35),
                0 1.1rem 2.6rem rgba(0, 0, 0, .55),
                0 0 0 .35rem rgba(77, 155, 234, .12);
    color: #e6e9ed;                     /* 12,3:1 */
}

:root[data-theme="dark"] .mkn-tieu-de { color: #ea868f; }   /* 5,94:1 — nấc đỏ dành cho nền tối */

:root[data-theme="dark"] .mkn-tran-an,
:root[data-theme="dark"] .mkn-tro-choi-chu { color: #b6bcc4; }   /* 7,78:1 */

:root[data-theme="dark"] .mkn-song span {
    background-color: #4d9bea;          /* 5,13:1 trên #23272e */
    background-image: linear-gradient(180deg, #7fb6ff 0%, #4d9bea 60%, #3b83cc 100%);
    box-shadow: 0 0 .45rem rgba(77, 155, 234, .5);
}

:root[data-theme="dark"] .mkn-icon { filter: drop-shadow(0 0 .35rem rgba(234, 134, 143, .4)); }
:root[data-theme="dark"] #components-reconnect-modal .mkn-tu-dong { color: #7fb6ff; }   /* 7,16:1 */

:root[data-theme="dark"] .mkn-canvas {
    border-color: #495159;
    outline-color: #7fb6ff;
    box-shadow: inset 0 .1rem .5rem rgba(0, 0, 0, .45);
}

/* ───────────── CỔ ĐIỂN (sáng) — vuông góc như phần mềm đời cũ ─────────────
   ⚠ Nấc xanh của bản cổ điển là #2166ad, KHÔNG phải nấc xanh sáng #2f86d8 quen thuộc: trên nền
   #fbfbfb thì #2f86d8 chỉ được ~2,7:1 — hụt cả ngưỡng đồ hoạ. */
/* ⛔ ĐỢT 523 — bản CỔ ĐIỂN CỐ Ý KHÔNG NHẬN phần làm mềm: gỡ hết gradient · bóng nhiều lớp · bo góc ·
   quầng sáng · làm mờ hậu cảnh. Giao diện này dựng ra để giống phần mềm đời cũ (vuông góc, phẳng);
   làm mềm nó là xoá mất chính lý do nó tồn tại. Ai thêm hiệu ứng mới cho ô này thì nhớ gỡ ở đây. */
:root[data-theme^="co-dien"] #components-reconnect-modal.mkn {
    background-image: none;
    backdrop-filter: none;
}

:root[data-theme^="co-dien"] .mkn-the {
    border-radius: 2px;
    background-image: none;
    box-shadow: 0 8px 20px rgba(0, 0, 0, .4);
}

:root[data-theme^="co-dien"] .mkn-icon { filter: none; }

:root[data-theme^="co-dien"] .mkn-song span {
    background-image: none;
    box-shadow: none;
}

:root[data-theme^="co-dien"] .mkn-canvas {
    border-radius: 0;
    box-shadow: none;
}

:root[data-theme="co-dien"] .mkn-the {
    border-color: #7f9db9;
    background-color: #fbfbfb;
    color: #1a1a1a;                     /* 17,4:1 */
}

:root[data-theme="co-dien"] .mkn-song span { background-color: #2166ad; }   /* 5,75:1 trên #fbfbfb */
:root[data-theme="co-dien"] .mkn-canvas { border-color: #7f9db9; }

/* ───────────── CỔ ĐIỂN TỐI — bảng màu mục 15 của co-dien.css ───────────── */
:root[data-theme="co-dien-toi"] #components-reconnect-modal.mkn { background-color: rgba(0, 0, 0, .75); }

:root[data-theme="co-dien-toi"] .mkn-the {
    border-color: #6a727b;
    background-color: #24292e;
    color: #e6e9ed;                     /* 12,0:1 */
}

:root[data-theme="co-dien-toi"] .mkn-tieu-de { color: #ff9b9b; }   /* 7,24:1 — đỏ lưu ý của bản cổ điển tối */

:root[data-theme="co-dien-toi"] .mkn-tran-an,
:root[data-theme="co-dien-toi"] .mkn-tro-choi-chu { color: #a6adb5; }   /* 6,47:1 */

:root[data-theme="co-dien-toi"] .mkn-song span { background-color: #7fb6ff; }   /* 7,01:1 trên #24292e */
:root[data-theme="co-dien-toi"] #components-reconnect-modal .mkn-tu-dong { color: #7fb6ff; }   /* 7,01:1 */

:root[data-theme="co-dien-toi"] .mkn-canvas {
    border-color: #7b838d;
    outline-color: #7fb6ff;
}

/* ───────────── BẢNG MÀU TRÒ CHƠI CHO HAI GIAO DIỆN TỐI ─────────────
   ⛔ PHẢI ĐẶT SAU khối `.mkn-canvas` gốc: hai bộ chọn có độ ưu tiên khác nhau nên chỗ này thì an
   toàn, nhưng vẫn giữ đúng nếp của đợt 504 (nút bản cổ điển tối từng bị đè vì viết sai thứ tự) —
   luật riêng của giao diện luôn nằm DƯỚI luật chung. */
:root[data-theme="dark"] .mkn-canvas,
:root[data-theme="co-dien-toi"] .mkn-canvas {
    --mkn-tc-nen: #161b22;
    --mkn-tc-dat: #667d4e;
    --mkn-tc-cot: #3fa34d;
    --mkn-tc-heo: #ff9ecf;
    --mkn-tc-may: #2b3a4d;
    --mkn-tc-chu: #e6e9ed;
    background-color: #161b22;
}

/* ───────────── HIỆU ỨNG (thuần CSS) ───────────── */
/* Đợt 523 — nền phủ hiện ra bằng mờ dần, KHÔNG kèm chuyển động: ô này bật lên đúng lúc người dùng
   đang hoảng, nền mà trượt theo nữa là rối mắt. Chỉ cái thẻ mới có chuyển động. */
@keyframes mkn-hien {
    from { opacity: 0; }
    to   { opacity: 1; }
}

@keyframes mkn-vao {
    from { opacity: 0; transform: translateY(1.1rem) scale(.94); }
    to   { opacity: 1; transform: none; }
}

@keyframes mkn-tho {
    0%, 100% { transform: scale(1)    translateY(0);       opacity: .85; }
    50%      { transform: scale(1.12) translateY(-.12rem); opacity: 1; }
}

@keyframes mkn-song-nhip {
    0%, 100% { transform: scaleY(.22); }
    50%      { transform: scaleY(1); }
}

/* Máy nào người dùng đã khai "hạn chế chuyển động" thì tắt hết — hoạt hoạ liên tục làm một số người
   chóng mặt, mà đây lại đúng lúc họ đang căng thẳng vì mất việc dở. Trò chơi vẫn chơi được bình
   thường: nó chỉ chạy khi người dùng tự bấm. */
@media (prefers-reduced-motion: reduce) {
    #components-reconnect-modal.mkn,
    #components-reconnect-modal .mkn-the,
    #components-reconnect-modal.mkn-co-tro-choi .mkn-tro-choi,
    #components-reconnect-modal .mkn-icon,
    #components-reconnect-modal .mkn-song span { animation: none; }

    #components-reconnect-modal .mkn-song span { transform: scaleY(.6); }
}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   ───── 13d. ĐỢT 602 — HÀNG DANH SÁCH TÔ MÀU (`list-group-item-*`) Ở GIAO DIỆN TỐI ─────

   Cùng họ với đợt 430 (hàng BẢNG tô màu) và khối 13c của đợt 588 — nhưng đây là lỗi NGƯỢC CHIỀU với
   hai đợt đó: chỗ này KHÔNG loá, mà **mất hẳn ý nghĩa của màu**.

   📐 VÌ SAO: luật nền tối `:root[data-theme="dark"] .list-group-item` có độ ưu tiên **(0,3,0)**
   (`:root` + `[data-theme]` + một lớp), còn các biến thể `.list-group-item-warning` … của Bootstrap
   chỉ **(0,1,0)** ⇒ nền tối ĐÈ SẠCH cả bộ. Mọi hàng — đã đọc hay chưa đọc, đạt hay không đạt — đều
   ra đúng một màu #262a31. Chữ vẫn 10,97:1 nên không ai báo "khó đọc"; thứ mất đi là **thông tin
   đang được mã hoá bằng màu**, và kiểu hỏng đó không bao giờ tự kêu.

   📊 ĐANG DÙNG THẬT Ở ĐÂU (quét toàn bộ `.razor`): **đúng MỘT chỗ** — `TrangThongBao.razor` dùng
   `list-group-item-warning` để đánh dấu thông báo **CHƯA ĐỌC**. Tức là ở giao diện tối, thông báo
   chưa đọc và đã đọc hiện đang trông y hệt nhau.
   ⚠ Vẫn khai ĐỦ 8 BIẾN THỂ chứ không riêng `warning` — đúng bài học đợt 430: *"liệt kê biến thể màu
   thì phải liệt ĐỦ BỘ, thiếu một cái là nó nằm im chờ trang nào dùng tới"*.

   🎨 BẢNG MÀU = **ĐÚNG BỘ CỦA HÀNG BẢNG TÔ MÀU** (đợt 430, ngay đầu file này) — cố ý KHÔNG đặt bảng
   màu thứ hai: hàng bảng vàng và hàng danh sách vàng phải là cùng một sắc vàng, không thì người dùng
   phải học hai bảng màu. Đo bằng công thức tương phản WCAG 2.1 (luminance tương đối):
       success #1f3b2c + chữ #dde1e6 …  9,29:1   ·  danger    #4a2126 + chữ #f3d3d7 …  9,83:1
       warning #4a3c12 + chữ #f1e4c0 …  8,53:1   ·  info      #123a44 + chữ #cfe9ef …  9,66:1
       primary #17314f + chữ #cfe0f5 …  9,84:1   ·  secondary #2f343c + chữ #c7ccd2 …  7,75:1
       light   #2d323a + chữ #dde1e6 …  9,81:1   ·  dark      #1a1d20 + chữ #dee2e6 … 13,00:1
   ⚠ **CHƯA NHÌN BẰNG MẮT** — đợt 602 chạy ở nhánh phụ, bị cấm mở trình duyệt (anh Tiến đang dùng bản
   vừa dựng). Mọi con số trên đây là TÍNH theo công thức, không phải đo sống.

   ⚠ ĐỘ PHÂN BIỆT GIỮA HAI HÀNG KỀ NHAU chỉ **1,05–1,33:1** (hàng tô màu so với hàng thường #262a31).
   Nghe thấp, nhưng đây đúng là mức mà app đang chấp nhận cho hàng BẢNG tô màu, và bản thân Bootstrap
   ở giao diện SÁNG còn thấp hơn (#fff3cd trên #fff = **1,11:1**). Muốn mạnh hơn thì phải đổi kiểu
   đánh dấu (vd thêm vệt màu bên trái) — đó là đổi THIẾT KẾ, phải đo sống và đi đợt riêng (luật #18).

   ⛔ BA CÁI BẪY ĐÃ TÍNH TRƯỚC, đừng gỡ:
     · **(0,4,0) chứ không (0,3,0)** — khai `.list-group-item.list-group-item-warning` (hai lớp) thay vì
       `.list-group-item-warning` một mình. Viết một lớp thì bằng điểm đúng với luật `.list-group-item`
       ở trên và chỉ thắng nhờ NẰM SAU trong file — đúng cái bẫy khối 13c suýt dính với `.page-link`.
       Neo bằng độ ưu tiên thì ai dời khối này đi đâu cũng không hỏng.
     · **`.active` phải khai lại** — xem khối riêng ngay dưới.
     · **KHÔNG dùng `[class*="list-group-item-"]`** cho hai luật "bên trong hàng" ở cuối khối, dù đợt 430
       đã dùng cách đó cho giao diện sáng: chuỗi đó khớp luôn cả **`list-group-item-action`** (8 chỗ
       trong app, toàn hàng KHÔNG tô màu) ⇒ quét nhầm sang hàng thường. Ở đây liệt kê tường minh.
   ══════════════════════════════════════════════════════════════════════════════════════════ */

:root[data-theme="dark"] .list-group-item.list-group-item-success   { background-color: #1f3b2c; color: #dde1e6; }
:root[data-theme="dark"] .list-group-item.list-group-item-warning   { background-color: #4a3c12; color: #f1e4c0; }
:root[data-theme="dark"] .list-group-item.list-group-item-danger    { background-color: #4a2126; color: #f3d3d7; }
:root[data-theme="dark"] .list-group-item.list-group-item-info      { background-color: #123a44; color: #cfe9ef; }
:root[data-theme="dark"] .list-group-item.list-group-item-primary   { background-color: #17314f; color: #cfe0f5; }
:root[data-theme="dark"] .list-group-item.list-group-item-secondary { background-color: #2f343c; color: #c7ccd2; }
:root[data-theme="dark"] .list-group-item.list-group-item-light     { background-color: #2d323a; color: #dde1e6; }
:root[data-theme="dark"] .list-group-item.list-group-item-dark      { background-color: #1a1d20; color: #dee2e6; }

/* ⚠ HÀNG ĐANG CHỌN — cùng bài học `.page-item.active .page-link` của khối 13c, nhưng lần này KHÔNG
   phải "suýt": nó đã hỏng SẴN từ trước đợt 602. Luật `.list-group-item` của nền tối là (0,3,0), còn
   `.list-group-item.active` của Bootstrap chỉ (0,2,0) ⇒ ở giao diện tối, hàng ĐANG CHỌN mất nền xanh
   và trông y hệt các hàng còn lại. Đang dùng thật ở `/cong-cu-dev/csdl` (chọn bảng để xem).
   Khai lại ở (0,4,0) và đặt SAU 8 luật biến thể ở trên (cùng điểm ⇒ bên sau thắng) để hàng vừa tô màu
   vừa đang chọn thì "đang chọn" vẫn thắng — trạng thái bao giờ cũng phải nổi hơn phân loại.
   Cặp #fff / #0d6efd giữ đúng của Bootstrap: 4,50:1.
   ⚠ CHƯA NHÌN BẰNG MẮT — đây là chỗ đổi hình ảnh rõ nhất của khối này, nhìn một lượt trước khi tin. */
:root[data-theme="dark"] .list-group-item.active {
    background-color: #0d6efd;
    border-color: #0d6efd;
    color: #fff;
}

/* Chữ phụ NẰM TRONG hàng đã tô màu: lấy màu của hàng (kế thừa) thay vì màu xám nhạt toàn cục.
   Bản sao đúng khuôn của luật `.table tr[class*="table-"] .text-muted` ở đầu file (đợt 430).
   ⛔ BẮT BUỘC, không phải làm cho đẹp: `/thong-bao` in CẢ dấu thời gian LẪN nội dung bằng `.text-muted`,
   mà luật toàn cục `:root[data-theme="dark"] .text-muted { color:#9aa0a8 !important }` đặt trên nền
   vàng #4a3c12 chỉ được **4,09:1** — hụt ngưỡng 4,5. Kế thừa + `opacity:.75` ⇒ màu thật #c7ba94,
   đo lại **5,59:1** (thấp nhất trong 8 biến thể là secondary 5,11:1). */
:root[data-theme="dark"] .list-group-item-success   .text-muted,
:root[data-theme="dark"] .list-group-item-success   .text-body-secondary,
:root[data-theme="dark"] .list-group-item-warning   .text-muted,
:root[data-theme="dark"] .list-group-item-warning   .text-body-secondary,
:root[data-theme="dark"] .list-group-item-danger    .text-muted,
:root[data-theme="dark"] .list-group-item-danger    .text-body-secondary,
:root[data-theme="dark"] .list-group-item-info      .text-muted,
:root[data-theme="dark"] .list-group-item-info      .text-body-secondary,
:root[data-theme="dark"] .list-group-item-primary   .text-muted,
:root[data-theme="dark"] .list-group-item-primary   .text-body-secondary,
:root[data-theme="dark"] .list-group-item-secondary .text-muted,
:root[data-theme="dark"] .list-group-item-secondary .text-body-secondary,
:root[data-theme="dark"] .list-group-item-light     .text-muted,
:root[data-theme="dark"] .list-group-item-light     .text-body-secondary,
:root[data-theme="dark"] .list-group-item-dark      .text-muted,
:root[data-theme="dark"] .list-group-item-dark      .text-body-secondary {
    color: inherit !important;
    opacity: .75;
}

/* ⛔ VIỀN NÚT ĐỎ TRONG HÀNG ĐÃ TÔ MÀU — luật này SỬA CHÍNH CÁI MÌNH VỪA LÀM HỎNG, không phải tiện tay.
   Nút 🗑 của `/thong-bao` (`.btn-outline-danger`) nằm ngay trong hàng chưa đọc. Viền #b5535e của nền
   tối vốn đã sát ngưỡng — **2,99:1** trên hàng thường — và tô hàng thành vàng #4a3c12 kéo nó xuống
   **2,24:1**. Tức là nếu chỉ làm phần tô màu ở trên rồi dừng thì đợt 602 tự tay đẩy một nút đang gần
   đạt xuống dưới ngưỡng 3:1 của chỉ báo phi văn bản.
   Sáng viền lên #ea868f (đỏ nấc 300 của Bootstrap 5.3, app đã dùng sẵn ở `.mkn-tieu-de` nền tối) ⇒
   thấp nhất **4,27:1** (trên nền vàng), cao nhất 6,69:1. Màu CHỮ #f1aeb5 giữ nguyên: thấp nhất 5,89:1.
   ⚠ Đây đúng bài học đợt 386c/430 lặp lại lần thứ ba: *cùng một nút, đặt trên nền khác là ra kết quả
   khác*. Ai thêm biến thể màu mới cho hàng danh sách thì phải đo lại cả nút nằm bên trong.
   ⓘ Hàng BẢNG tô màu ở nền tối cũng có đúng lỗ này (viền #b5535e trên `table-warning` #4a3c12) —
   nhưng đó là lỗi CÓ SẴN, không do đợt 602 gây ra, nên để lại cho đợt rà riêng chứ không nới bộ chọn
   ra cho "gọn" (luật #18: chỉ đụng đúng thứ mình làm hỏng). */
:root[data-theme="dark"] .list-group-item-success   .btn-outline-danger,
:root[data-theme="dark"] .list-group-item-warning   .btn-outline-danger,
:root[data-theme="dark"] .list-group-item-danger    .btn-outline-danger,
:root[data-theme="dark"] .list-group-item-info      .btn-outline-danger,
:root[data-theme="dark"] .list-group-item-primary   .btn-outline-danger,
:root[data-theme="dark"] .list-group-item-secondary .btn-outline-danger,
:root[data-theme="dark"] .list-group-item-light     .btn-outline-danger,
:root[data-theme="dark"] .list-group-item-dark      .btn-outline-danger {
    border-color: #ea868f;
}

/* ═══ ĐỢT 602 — THANH TIẾN ĐỘ: **CỐ Ý KHÔNG ĐỘNG VÀO**, và đây là số đo để lần sau khỏi tính lại ═══

   Đề bài của đợt này có nêu: máng `.progress` #e9ecef là mảng sáng rõ nhất còn lại ở giao diện tối,
   làm tối được không? Điều kiện kèm theo (khối 13c đợt 588 đặt ra) là **phải sáng lại CẢ BỘ màu thanh
   rồi mới làm tối máng**, và chứng minh bằng số cho TỪNG màu. Đã tính đủ, kết quả: **KHÔNG LÀM.**

   Bước 1 — sáng lại bộ màu thì phần "thấy thanh chạy tới đâu" ĐẠT: lấy nấc 300 của Bootstrap 5.3
   (#6ea8fe · #75b798 · #ea868f · #ffda6a) đặt trên máng tối #343a41 ⇒ 4,76 · 4,92 · 4,54 · 8,48 —
   qua ngưỡng 3:1 cả bộ, so với 2,55 · 2,54 · 2,54 · 2,66 nếu giữ nguyên màu thanh cũ.
   Bước 2 — nhưng **NHÃN CHỮ NẰM TRÊN THANH thì sập**: `.progress-bar` của Bootstrap có `color:#fff`
   gắn cứng, chữ trắng trên bộ màu sáng đó chỉ còn **2,42 · 2,34 · 2,53 · 1,36** (hiện đang là
   4,50 · 4,53 · 4,53 · 4,33). Mà nhãn này CÓ THẬT, không phải giả định: `/quan-tri/dung-luong` in
   "KHÔNG ĐO ĐƯỢC — kiểm kết nối kho này" ngay trong thanh, `DanhSachCongViec` và `ChiTietDuAn` in số
   phần trăm, `SoiMayNguon` in `<span class="small">`. Đợt 519 sinh ra chính là để cứu cái nhãn đó.
   Bước 3 — vá tiếp bằng cách đổi nhãn sang chữ đen (8,31–15,49:1) thì về mặt SỐ là xong, nhưng khi đó
   một lượt sửa đụng **BA mặt dùng chung cùng lúc** (bảng màu thanh · màu máng · màu nhãn) trên 11 trang,
   trong một chế độ mà đợt này **không được phép mở trình duyệt để nhìn**. Luật #18 gọi đúng tên việc
   đó: đổi chỗ dùng chung phải đo lại TẤT CẢ chỗ đang dùng, không chỉ chỗ đang sửa.

   ⇒ Giữ nguyên. Tình trạng hiện tại KHÔNG có lỗi để chữa: bốn màu thanh app dùng thật (mặc định
   primary · success · warning · danger) đo trên máng sáng được 3,80 · 3,82 · 3,65 · 3,82 — đạt cả bộ.
   (Bộ Bootstrap còn `info` #0dcaf0 chỉ 1,65:1 và `secondary` 3,95:1, nhưng đã quét: **không thanh tiến
   độ nào trong app dùng hai màu đó** — nên đó là bẫy chờ chứ chưa phải lỗi đang cắn.)
   Hàng rào `NenSangSotOGiaoDienToiTests.Mang_thanh_tien_do_co_y_KHONG_bi_lam_toi_o_nen_toi` (đợt 588)
   GIỮ NGUYÊN, không gỡ — nay có thêm bảng số ở `HangDanhSachToMauNenToiTests` ghi lại vì sao. */

/* ═══════════════ ĐỢT 665 — DÒNG DẤU NHỎ TRONG Ô NGÀY (phân hệ CHẤM CÔNG) ═══════════════

   Ô ngày của lịch/bảng công có HAI tầng chữ: số ngày (cỡ thường) và một dòng phụ mang
   **thứ + ký hiệu ngày + dấu đơn phép + dấu đã quyết**. Dòng phụ đó trước đây được gõ thẳng
   `style="font-size:.55rem;line-height:1;opacity:.85;"` — **8 lần, ở 5 tệp**.

   Ba thứ hỏng cùng một chỗ, sửa gọn trong một lớp:
   ① **.55rem = 8,8px** — dưới ngưỡng đọc được. Mà đây KHÔNG phải chữ trang trí: nó là **ký hiệu
      mang nghĩa** (có tờ đơn phép · đã có người quyết · vắng chưa lý do). Ký hiệu không đọc ra
      thì bằng không có, mà bảng chú giải bên dưới vẫn đang nói về nó.
   ② **`opacity: .85`** — làm TỤT TƯƠNG PHẢN của chính dòng chữ đã nhỏ nhất màn hình. Bỏ hẳn:
      chữ vẫn nhỏ hơn số ngày nên vai phụ vẫn rõ, không cần làm mờ thêm.
   ③ **8 bản chép tay** — sửa một chỗ quên bảy chỗ; ba trong tám bản nằm ở TRANG CHỦ, nơi anh
      Tiến nhìn mỗi ngày.

   📏 ĐO ĐỂ KHÔNG TRÀN Ô (ô ngày trang chủ rộng cố định **2.1rem = 33,6px**):
      chuỗi dài nhất là "T2" + ký hiệu + 2 dấu ≈ **5 ký tự**.
      · cỡ cũ .55rem → ≈ 24px      · cỡ mới .65rem (10,4px) → ≈ 28,5px  ⇒ vẫn dư ~5px.
      `letter-spacing: -.01em` bù thêm phần tăng cỡ. Chiều cao cũng đủ: số ngày ~16px +
      dòng phụ 10,4px (line-height 1) = 26,4px < 33,6px.

   ⛔ **KHÔNG áp lớp này cho chữ "thứ" ở ĐẦU CỘT lưới 31 ngày** (`OCotNgayLich.razor.css`, .58rem):
      đó là chiều RỘNG CỘT của cả bảng công — nới cỡ chữ ở đó là nới cả bảng, phải đo trên màn
      hình thật trước. Miễn trừ này khai tường minh trong hàng rào `CoChuONgayTests`. */
.dau-o-ngay {
    font-size: .65rem;
    line-height: 1;
    letter-spacing: -.01em;
}

/* ═══════════ ĐỢT 676 — DẢI ẢNH TỔNG KHO Ở TRANG CHỦ ═══════════
   Anh Tiến 14/08/2026 gửi ảnh thật của Tổng kho Nhà Bè: *"em thử thêm vào các màn hình xem sao nhé"*.

   ⚡ Ba ảnh đã thu nhỏ còn 800px / 50–68 KB (bản gốc 2560px / 616–920 KB). Trang chủ là trang 433 tài
      khoản đều mở — đưa ảnh gốc vào là mỗi lượt mở tốn gần 1 MB (nguyên tắc X: cắt việc thừa).
   ⚠ Chiều cao CỐ ĐỊNH + `object-fit: cover`: khung không nhảy khi ảnh về muộn, và ảnh không méo dù tỉ lệ
      khung khác tỉ lệ ảnh.
   ⓘ Khổ hẹp (dưới 768px) chỉ giữ MỘT ảnh: ba ảnh trên màn hình điện thoại thì mỗi ảnh còn một mẩu, vừa
      xấu vừa tốn ba lượt tải cho thứ chỉ để nhìn. */
.dai-anh-kho {
    display: flex;
    gap: .5rem;
    margin-bottom: 1rem;
}

.dai-anh-kho img {
    flex: 1 1 0;
    min-width: 0;
    height: 110px;
    object-fit: cover;
    border-radius: .6rem;
    /* Ảnh là NỀN CẢNH, không phải nội dung cần đọc — làm dịu để nó không tranh mắt với bàn làm việc. */
    filter: saturate(.92);
}

@media (max-width: 768px) {
    .dai-anh-kho img:not(:first-child) { display: none; }
    .dai-anh-kho img { height: 90px; }
}

/* ═══ ĐỢT 677 — TRANG ĐỔI MẬT KHẨU: KHUNG RA GIỮA MÀN HÌNH ═══
   Anh Tiến 14/08/2026: *"trang đổi mật khẩu đưa khung ra giữa màn hình nhé"*.
   Trang chỉ có MỘT việc và một khung hẹp — dán sát mép trái thì khoảng trống bên phải thành thừa.
   ⚠ Căn giữa bằng lớp bọc riêng, KHÔNG đụng layout chung (sửa layout là đổi bố cục mọi trang). */
.dmk-giua {
    /* ⭐ ĐỢT 1569 (việc o876) — nới 460 → 640px. Trang nay đặt thẻ kính trên một SÂN nền navy
       (`.dn-nen.dn-san`, xem the-dang-nhap.css); sân bó đúng bằng bề ngang thẻ thì dải nền chỉ còn
       ở trên và dưới, trông như thẻ bị viền chứ ⛔ không ra một cái nền. Vẫn giữ nguyên điều anh
       Tiến chốt ở đợt 677: khối căn GIỮA vùng nội dung. */
    max-width: 640px;
    margin-left: auto;
    margin-right: auto;
    /* Đẩy xuống một chút cho khối nằm cân giữa vùng nội dung, không dính ngay dưới thanh nav. */
    padding-top: 2vh;
}

/* ═══ ĐỢT 679 — DẢI ẢNH TỔNG KHO: NỔI 3D + BÓNG ═══
   Anh Tiến 15/08/2026: *"có hiệu ứng gì cho hình thì thêm vào, cho hình nổi 3D và bóng lên"*.
   ⚠ Chỉ dùng `transform` + `box-shadow` ⇒ trình duyệt xử ở tầng hợp ảnh, không dựng lại bố cục.
   ⓘ Ảnh giữa là NHÀ ĐIỀU HÀNH (anh chốt đưa tòa nhà vào giữa) — thứ tự khai ở TongQuan.razor. */
.dai-anh-kho img {
    box-shadow: 0 .5rem 1.1rem rgba(0, 0, 0, .28), 0 .12rem .3rem rgba(0, 0, 0, .18);
    transition: transform .18s ease, box-shadow .18s ease;
}

.dai-anh-kho img:hover {
    /* Nhấc lên + nghiêng nhẹ = cảm giác nổi khỏi mặt phẳng; bóng dày hơn cho khớp độ cao mới. */
    transform: translateY(-4px) perspective(600px) rotateX(2deg) scale(1.012);
    box-shadow: 0 1rem 1.8rem rgba(0, 0, 0, .36), 0 .2rem .5rem rgba(0, 0, 0, .22);
}

@media (prefers-reduced-motion: reduce) {
    .dai-anh-kho img { transition: none; }
    .dai-anh-kho img:hover { transform: none; }
}

/* ═══ ĐỢT 707 — NÚT 👁 HIỆN/ẨN MẬT KHẨU (component ChungTu/ONhapMatKhau.razor) ═══
   Anh Tiến 15/08/2026: *"nhập mật khẩu không cho xem thì không biết đúng không"*.
   ⛔ Icon đổi bằng CSS theo thuộc tính `data-hien` mà JS đặt lên khung — KHÔNG viết lại chữ của nút.
      Chữ của nút nằm trong cây dựng của Blazor: JS ghi đè vào đó là hai bên tranh nhau một chỗ, và
      lượt Blazor vẽ lại sẽ trả icon về 👁 trong khi ô vẫn đang hiện chữ ⇒ icon nói dối. */
.o-mat-khau .o-mat-khau-hien,
.o-mat-khau[data-hien] .o-mat-khau-an {
    display: none;
}

.o-mat-khau[data-hien] .o-mat-khau-hien {
    display: inline;
}

/* Nút phụ, không được to hơn ô nhập nó đứng cạnh. */
.o-mat-khau .o-mat-khau-nut {
    line-height: 1;
}

/* ═══ ⭐ ĐỢT 1562 (việc o854) — NÚT 👁 CHỈ HIỆN KHI Ô ĐÃ CÓ CHỮ ═══
   Anh Tiến 30/08/2026: *"Tại ô mật khẩu cho phép xem mật khẩu khi nhập chữ vào rồi thì bỏ icon mắt
   đi, các ô nhập mật khẩu khác cũng vậy"* — ô còn TRỐNG thì ⛔ chẳng có gì để xem, nút chỉ là một
   viên xám ngồi cạnh mọi ô mật khẩu của app.

   🔑 ĐO BẰNG `:placeholder-shown`, ⛔ KHÔNG BẰNG JAVASCRIPT ĐỌC GIÁ TRỊ Ô. Đây ⛔ không phải chuyện
     gọn mã: `ONhapMatKhauTests` cấm tệp `hien-an-mat-khau.js` chạm tới thứ người ta vừa gõ (luật đợt
     707 — mật khẩu ⛔ không đọc, ⛔ không ghi đi đâu), và cấm đúng chỗ. Bộ chọn này trả lời **đúng câu
     hỏi cần hỏi** (ô có rỗng không) mà ⛔ không ai phải đọc mật khẩu cả.
     ⇒ Phần JavaScript chỉ còn một việc: ĐẶT một `placeholder` trắng cho ô nào chưa có, để bộ chọn có
     cái mà bám. Nó GHI một thuộc tính trình bày, ⛔ không ĐỌC gì.

   ⛔ CHIỀU FAIL PHẢI LÀ "NÚT HIỆN", ⛔ đừng đảo: luật chỉ ẨN khi có ĐỦ hai điều kiện (trình duyệt hiểu
     `:has`, và ô đang rỗng). Trình duyệt cũ ⛔ không hiểu `:has` ⇒ bỏ qua cả luật ⇒ nút hiện y như
     trước đợt này. Viết ngược (ẩn mặc định, hiện khi có chữ) là ở những máy ấy **mất hẳn nút xem mật
     khẩu**, mà lỗi đó ⛔ không có câu báo nào.

   ⚠ TRẢ LẠI GÓC BO cho ô nhập: `display: none` ⛔ không đổi `:last-child` nên Bootstrap vẫn coi ô là
     "phần tử không đứng cuối" và cắt phẳng hai góc phải ⇒ ô hoá ra có một cạnh vuông lơ lửng. */
.o-mat-khau:has(input:placeholder-shown) > .o-mat-khau-nut {
    display: none;
}

.o-mat-khau:has(input:placeholder-shown) > .form-control {
    border-top-right-radius: var(--bs-border-radius);
    border-bottom-right-radius: var(--bs-border-radius);
}

/* ═══════════════ ĐỢT 760 (việc f28) — ĐỒNG NHẤT DẠNG NÚT TAB + THU NHỎ MỘT NẤC ═══════════════
   Anh Tiến 17/08/2026 (kèm 2 ảnh /cham-cong và /cai-dat-cham-cong): *"các loại nút khác nhau không
   đồng nhất, em đồng nhất theo dạng nút ở trang chấm công theo tháng"* + *"scale nút nhỏ lại 1 tí
   vì thấy có vẻ đang khá to"*.

   CHUẨN = dạng nút của dải tab /cham-cong trên thanh nav (ảnh 1): viên bo góc .45rem, viền
   var(--bs-border-color), nổi 3D nhẹ (khuôn đợt 447/680 ngay trên), đang mở = navy gradient chữ
   trắng (khuôn mục 4 — cặp màu trắng/navy đã đo ≥ 8:1). Hai chỗ LỆCH chuẩn anh chỉ ra:
   · tab NAV-PILLS render TRONG THÂN TRANG (/cai-dat-cham-cong, /cong-viec…): trước đây là Bootstrap
     mặc định (chữ trần + viên xanh #0d6efd) — khối dưới đây phủ;
   · dải "các trang của app" (.tta-tab): sửa ở TabTrangCuaApp.razor(.css) bằng lớp dùng chung
     `nut-noi`, KHÔNG có luật nào ở đây.

   ⛔ CHỈ HAI GIAO DIỆN HIỆN ĐẠI (`:not(co-dien)`): cổ điển có ngôn ngữ thẻ-hồ-sơ-vàng riêng
     (co-dien.css mục 10) và từ 17/08 đã ĐÓNG BĂNG phát triển (quy tắc #20 CLAUDE.md).
   ⚠ `:not(.nav-scrollable *)` chừa ngăn kéo ☰ Menu — cùng dấu chừa với luật mục 11 nền tối.
   ⚠ KHÔNG khai `color` cho trạng thái thường: màu chữ đã có luật đo tương phản riêng từng nền
     (`.menu-co-dien .nav-link` #0a58ca · mục 11 nền tối #9ec5fe) — khai ở đây là đè lên số đã đo.
   📏 Thu nhỏ: Bootstrap .5rem/1rem + 1rem → .3rem/.65rem + .875rem. Dải tab trên thanh nav cũng là
     `ul.nav.nav-pills` nên MỘT luật ăn cả thanh nav lẫn trong trang — cùng cỡ, hết lệch. */
:root:not([data-theme^="co-dien"]) .nav-pills .nav-link:not(.nav-scrollable *) {
    border: 1px solid var(--bs-border-color);
    border-radius: .45rem;
    background-image: linear-gradient(180deg, rgba(255, 255, 255, .12), rgba(0, 0, 0, .04));
    box-shadow: 0 1px 0 rgba(255, 255, 255, .18) inset, 0 2px 3px rgba(0, 0, 0, .14);
    transition: transform .14s ease-out, box-shadow .14s ease-out, filter .14s ease-out;
    padding: .3rem .65rem;
    font-size: .875rem;
}

:root:not([data-theme^="co-dien"]) .nav-pills .nav-link:not(.nav-scrollable *):hover {
    transform: translateY(-2px);
    box-shadow: 0 1px 0 rgba(255, 255, 255, .3) inset, 0 5px 10px rgba(0, 0, 0, .24);
    filter: brightness(1.06);
}

/* NHẤN GIỮ: lún xuống — cùng cảm giác phím bấm với dải thanh nav (đợt 680). */
:root:not([data-theme^="co-dien"]) .nav-pills .nav-link:not(.nav-scrollable *):active {
    transform: translateY(1px);
    box-shadow: 0 1px 2px rgba(0, 0, 0, .3) inset;
}

/* ĐANG MỞ = navy gradient chữ trắng — đúng cặp màu của tab thanh nav (mục 4, trắng/navy ≥ 8:1). */
:root:not([data-theme^="co-dien"]) .nav-pills .nav-link.active:not(.nav-scrollable *) {
    background-color: var(--plx-xanh);
    background-image: linear-gradient(120deg, #0a3d91, #002169);
    color: #fff;
    font-weight: 600;
}

/* Máy "giảm chuyển động": bỏ phần nhún, giữ hình khối — cùng luật với khối đợt 447. */
@media (prefers-reduced-motion: reduce) {
    :root:not([data-theme^="co-dien"]) .nav-pills .nav-link:not(.nav-scrollable *) { transition: none; }
    :root:not([data-theme^="co-dien"]) .nav-pills .nav-link:not(.nav-scrollable *):hover { transform: none; }
}

/* ═══════════ ĐỢT 931 (việc o150) — THẺ 3D CỦA TRANG CHỦ: BỐN Ô THỐNG KÊ + HAI NÚT KHAI ĐƠN ═══════════

   Anh Tiến 21/08/2026, bốn tin liền nhau: *"việc chờ tôi duyệt đổi tên thành Chờ duyệt, em đồ hoạ 3D nổi
   bật có hiệu ứng cho 4 ô này, đổi màu ô màu xanh lá cây và ô xám dự án cho đúng chủ đề"* · *"em xem lại
   cả 4 màu các ô, giao việc, chờ duyệt... màu tươi sáng đúng chủ đề có hiệu ứng và hiện đại"* · *"tại 4 ô
   này, chỉ có ô giao việc là có icon, em kiểm tra và bổ sung icon cho phù hợp"* · *"bổ sung 3d và hiệu
   ứng cho cả 2 nút: lý do đi trễ... và xin nghỉ phép"*.

   🔑 VÌ SAO ĐẶT Ở ĐÂY chứ không rải vào từng tệp `.razor.css`: sáu chỗ nằm ở BỐN component khác nhau
     (`OViecChoToiDuyet` · `OGiaoViec` · ô "Việc quá hạn" dựng thẳng trong `KhoiTheThongKe` ·
     `ONhiemVuDuAn` · hai nút đầu thẻ của `KhoiBanLamViec`), mà CSS scoped của Blazor không dùng chung
     được giữa các component. Chép khối này làm bốn bản là bốn chỗ phải sửa mỗi lần anh đổi ý một câu.

   🔴 ĐỘ ĐẶC HIỆU — CHỖ ĐÃ ĐO VÀ SUÝT HỎNG LẶNG LẼ. Bản đầu viết bộ chọn `.tt3d` trần **(0,1,0)**, đo
     sống trên bản tĩnh thì màu ra đúng nhưng **`box-shadow` và `border` bị nuốt sạch**: mục 2 của khối
     hiệu ứng (dòng ~1361) khai `:root[data-theme="light"] .card:not(.trang-chu *)` ở **(0,4,0)** với
     bóng `0 6px 22px rgba(0,23,68,.07)` và viền 1px — tức đúng hai thứ làm nên khối 3D. Build xanh,
     test xanh, màu vẫn tươi, chỉ **hết nổi**. ⇒ Bộ chọn dưới đây cố ý mang **cùng (0,4,0)** rồi thắng
     nhờ **đứng SAU trong cùng tệp**. ⛔ Đừng rút gọn lại thành `.tt3d` cho "sạch mắt".

   📐 BA LUẬT SỐNG CÒN CỦA KHỐI HIỆU ỨNG (chép từ đầu mục, dòng ~1210) — khối này tuân đủ:
     · Bọc trong `@media screen`: không thì bóng mềm + gradient **lem vào tờ giấy** lúc `window.print()`.
       ⓘ Khối `@media screen` của đợt 381 đóng ở dòng ~1634 nên khối này nằm NGOÀI — phải tự bọc.
     · Gate theo `[data-theme="light"] / "dark"`: hai giao diện cổ điển là bản PHẲNG có chủ đích.
     · **KHÔNG `transform` lên phần tử có con `position: fixed`** — transform biến nó thành khối chứa
       của con ấy (bẫy đã trả giá đợt 649, và là lý do mục 2 cấm transform trên `.card:hover`).
       🔑 Vì luật này mà HAI NHÓM ở đây cư xử KHÁC NHAU, ⛔ đừng gộp làm một cho gọn:
       · **Bốn ô thẻ** (`.card.tt3d`) — nhấc lên bằng `transform` khi rê chuột. An toàn vì hộp thoại
         `HopThoai` (thứ mang nền phủ `fixed`) được dựng **NGOÀI** thẻ, ở cấp gốc của component.
       · **Hai nút khai đơn** (`.blv-dau.tt3d`) — ⛔ KHÔNG nhấc, chỉ sáng lên. Hai lý do, mỗi lý do đủ
         để cấm: ① thẻ cha `.blv-the` có `overflow: hidden` nên phần nhấc lên **bị cắt cụt**;
         ② `.blv-the.mo .blv-than` là `position: fixed` — đặt transform lên tổ tiên của nó là kéo hộp
         khai đơn về neo theo thẻ thay vì theo màn hình.

   🎨 MÀU KHÔNG LẤY TỪ `text-bg-*` CỦA BOOTSTRAP NỮA — bốn ô trước đây mượn warning/primary/secondary,
     nên hai ô mang đúng thứ anh chê: `text-bg-success` (XANH LÁ CÂY, ô "Việc quá hạn" lúc không còn
     việc nào) và `text-bg-secondary` (XÁM CHẾT, ô "Nhiệm vụ/Dự án"). Nay mỗi ô một cặp màu tự khai nói
     đúng chủ đề của nó, và vì tự khai nên nó giống nhau ở cả nền sáng lẫn nền tối.

   ⚖ TƯƠNG PHẢN CHỮ: nền tươi thì chữ trắng dễ đuối, nên khối có lớp phủ tối dần về ĐÁY — đúng chỗ dòng
     chữ nhỏ nằm. Con số cỡ lớn (`fs-2`, in đậm) ở nửa trên chỉ cần 3:1 theo WCAG chữ lớn; dòng `small`
     ở đáy được lớp phủ đưa lên trên 4,5:1. ⛔ Đừng gỡ lớp phủ ấy đi cho "màu tươi hơn" — gỡ là dòng chữ
     nhỏ mất đọc trên hai ô sáng nhất (cam và ngọc). */
@media screen {

:root:is([data-theme="light"], [data-theme="dark"]) .card.tt3d,
:root:is([data-theme="light"], [data-theme="dark"]) .blv-dau.tt3d,
:root:is([data-theme="light"], [data-theme="dark"]) .nut-mo-bang.tt3d {
    position: relative;
    overflow: hidden;
    border: 1px solid rgba(255, 255, 255, .28);
    color: #fff;
    text-shadow: 0 1px 2px rgba(0, 0, 0, .35);
    background-color: var(--tt3d-dam, #334155);
    background-image:
        linear-gradient(180deg, rgba(0, 0, 0, .10) 0%, rgba(0, 0, 0, .34) 100%),
        linear-gradient(145deg, var(--tt3d-sang, #64748b) 0%, var(--tt3d-dam, #334155) 70%);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .5),
        inset 0 -3px 0 rgba(0, 0, 0, .18),
        0 10px 20px -10px var(--tt3d-bong, rgba(15, 23, 42, .55));
    transition: transform .18s ease, box-shadow .18s ease, filter .18s ease;
}

/* Vệt sáng góc trên-trái — thứ làm khối "phồng" lên thay vì phẳng lì. Đặt bằng lớp giả để không phải
   thêm thẻ rỗng nào vào markup của sáu chỗ. */
:root:is([data-theme="light"], [data-theme="dark"]) .card.tt3d::before,
:root:is([data-theme="light"], [data-theme="dark"]) .blv-dau.tt3d::before,
:root:is([data-theme="light"], [data-theme="dark"]) .nut-mo-bang.tt3d::before {
    content: "";
    position: absolute;
    inset: 0;
    background: radial-gradient(115% 85% at 12% -18%, rgba(255, 255, 255, .42), rgba(255, 255, 255, 0) 62%);
    pointer-events: none;
}

/* Ánh sáng QUÉT NGANG khi rê chuột — hiệu ứng anh xin. Nằm ngoài khung lúc nghỉ, chỉ chạy khi hover. */
:root:is([data-theme="light"], [data-theme="dark"]) .card.tt3d::after,
:root:is([data-theme="light"], [data-theme="dark"]) .blv-dau.tt3d::after,
:root:is([data-theme="light"], [data-theme="dark"]) .nut-mo-bang.tt3d::after {
    content: "";
    position: absolute;
    top: -20%;
    bottom: -20%;
    left: -60%;
    width: 42%;
    background: linear-gradient(100deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, .38), rgba(255, 255, 255, 0));
    transform: skewX(-18deg);
    opacity: 0;
    pointer-events: none;
}

:root:is([data-theme="light"], [data-theme="dark"]) .card.tt3d:hover::after,
:root:is([data-theme="light"], [data-theme="dark"]) .card.tt3d:focus-visible::after,
:root:is([data-theme="light"], [data-theme="dark"]) .blv-dau.tt3d:hover::after,
:root:is([data-theme="light"], [data-theme="dark"]) .blv-dau.tt3d:focus-visible::after,
:root:is([data-theme="light"], [data-theme="dark"]) .nut-mo-bang.tt3d:hover:not(:disabled)::after,
:root:is([data-theme="light"], [data-theme="dark"]) .nut-mo-bang.tt3d:focus-visible::after {
    animation: tt3d-quet .8s ease forwards;
}

@keyframes tt3d-quet {
    0%   { opacity: 0; left: -60%; }
    35%  { opacity: 1; }
    100% { opacity: 0; left: 115%; }
}

/* Chữ phải nằm TRÊN hai lớp giả, không thì vệt sáng phủ mờ con số. */
:root:is([data-theme="light"], [data-theme="dark"]) .card.tt3d > *,
:root:is([data-theme="light"], [data-theme="dark"]) .blv-dau.tt3d > *,
:root:is([data-theme="light"], [data-theme="dark"]) .nut-mo-bang.tt3d > * {
    position: relative;
    z-index: 1;
}

/* Con số là thứ mắt tìm đầu tiên — cho nó dày dặn hơn một chút. */
:root:is([data-theme="light"], [data-theme="dark"]) .card.tt3d .fs-2 {
    letter-spacing: -.02em;
    text-shadow: 0 2px 6px rgba(0, 0, 0, .38);
}

/* ═══ ⭐ ĐỢT 1577 (việc o882) — MẶT CHỮ ĐỒ HOẠ CHO CON SỐ CỦA BỐN Ô THẺ ═══

   Anh Tiến 31/08/2026: *"chữ cũng cần đồ hoạ"*. Con số là chữ được nhìn nhiều nhất trang chủ.

   🔴 DẢI MÀU ĐI TRỌN TRONG VÙNG RẤT SÁNG (`#ffffff` → `#e9f3ff`), ⛔ đừng thả một mốc sẫm vào cho
     "có chiều sâu": nền bốn ô là bốn dải màu ĐẬM khác nhau (cam · lục · lam · tím), nên tương phản
     ở đây do **độ sáng của mặt chữ** giữ. Mốc tối nhất của dải đo được **L = 0,87**, tức chênh dưới
     6% so với trắng đặc ⇒ tương phản giữ nguyên mức của bản trước đợt này trên cả bốn nền.

   🔴 `text-shadow: none` LÀ BẮT BUỘC. Mặt chữ ở đây TRONG SUỐT, mà `text-shadow` khai ở luật `.fs-2`
     ngay trên là thuộc tính **KẾ THỪA** ⇒ không tắt thì bóng đen lộ nguyên qua lòng nét, thành vệt
     bẩn giữa con số. Bóng thay bằng `drop-shadow` — bộ lọc áp lên HÌNH ĐÃ VẼ nên ôm đúng đường viền.
     Đây là bài học đã trả giá ở khối đồng hồ màn tiếp khách (đợt 1562).

   🔑 Bọc `@supports` để chiều fail là **con số trắng đặc y như trước đợt này** — trình duyệt không
     hiểu luật cắt nền thì bỏ qua cả khối. ⛔ Đừng gỡ lớp bọc rồi khai `color: transparent` ở ngoài:
     máy nào không hiểu sẽ cho ra **ô thống kê không còn con số**, mà build xanh và test xanh.
   ⚠ Bộ chọn cố ý bám `.tt3d-so` — thẻ bọc RIÊNG con số. Đặt luật này lên `.fs-2` là cắt luôn biểu
     tượng cảm xúc đứng cạnh (xem chú thích ở `ONhiemVu.razor`).
   ⚠ ⛔ KHÔNG hoạt hoạ dải màu ở đây, khác hẳn hai màn hình đăng nhập: đây là trang người ta mở hàng
     chục lượt mỗi ngày để LÀM VIỆC, một vệt sáng chạy vô tận ngay trên con số là thứ kéo mắt suốt
     tám tiếng. Mặt chữ ở đây là HÌNH tĩnh. */
@supports (background-clip: text) or (-webkit-background-clip: text) {
    :root:is([data-theme="light"], [data-theme="dark"]) .card.tt3d .tt3d-so {
        background-image: linear-gradient(160deg, #ffffff 0%, #ffffff 42%, #e9f3ff 100%);
        -webkit-background-clip: text; background-clip: text;
        -webkit-text-fill-color: transparent; color: transparent;
        text-shadow: none;
        filter: drop-shadow(0 2px 5px rgba(0, 0, 0, .42));
    }
}

/* ICON ĐỨNG TRƯỚC CON SỐ (anh Tiến: *"chỉ có ô giao việc là có icon"*). Cỡ nhỏ hơn con số để số vẫn là
   thứ đọc trước; ô "Giao việc" không có số nên icon của nó đứng một mình, đúng chỗ ấy. */
:root:is([data-theme="light"], [data-theme="dark"]) .card.tt3d .tt3d-icon {
    margin-right: .4rem;
    font-size: .78em;
    filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .38));
}

/* ═══ ⭐ ĐỢT 1963 (việc o1227) — BẬC CHỮ BA TẦNG TRONG THẺ, VÀ CÂU PHỤ NEO Ở ĐÁY ═══

   Anh Tiến 04/09/2026, nguyên văn: *"form chữ và cách trình bày chưa đẹp cần kiểm tra lại"* — anh gửi
   kèm ảnh chụp đúng ô ② 🎯 Mục tiêu / Giao/tạo việc.

   📏 ĐO SỐNG Chrome thật TRƯỚC khi sửa (`/tong-quan`, tài khoản thật, ba khổ màn hình):
     · **BẬC CHỮ PHẲNG** — cả ba dòng trong thẻ đều ra `rgb(255, 255, 255)`, và DÒNG TÊN Ô lẫn DÒNG
       PHỤ cùng `font-weight: 400`, chỉ chênh nhau **2px** cỡ chữ (16 so với 14) ⇒ tên của ô ⛔ không
       đọc ra thành tiêu đề, ba dòng dính lại thành một khối chữ trắng phẳng.
     · **CÂU PHỤ GÃY DÒNG KÉO CẢ DÃY CAO LÊN** — câu phụ dài nhất (ô ②, *"đăng ký mục tiêu · giao hoặc
       tạo việc nhanh"*) đo **274px** trong lòng ô **280px** ở khổ 1400, tức chỉ còn **6px** dư. Tụt
       xuống khổ 1280 là nó gãy hai dòng, mà bốn thẻ đều `h-100` nên **CẢ BỐN** cao từ 129 lên
       **150px** — ba thẻ kia lãnh **21px trống ở đáy**, đúng chỗ mắt đọc thành "thẻ bị hụt".

   🔑 HAI PHÉP, GIẢI HAI VIỆC KHÁC NHAU — ⛔ đừng bỏ một cái vì tưởng cái kia đã đủ:
     ① **Bậc chữ** — tên ô lên `600`, câu phụ xuống `.8125rem` + `rgba(255, 255, 255, .82)`. Hạ cỡ câu
       phụ đồng thời kéo câu dài nhất từ 274px về **≈254px**, tức nó thôi gãy dòng ở khổ 1280 (lòng ô
       264px). ⛔ Đây là hệ quả kèm theo, ⛔ KHÔNG phải chỗ dựa: khổ 1024 lòng ô chỉ còn 186px thì nó
       vẫn gãy — và phép ② mới là thứ đỡ ca ấy.
     ② **Câu phụ neo ĐÁY thẻ** (`margin-top: auto`) — thẻ nào tên hoặc câu phụ gãy hai dòng thì bốn câu
       phụ vẫn **thẳng một hàng**. Chỗ dôi ra rơi vào GIỮA thẻ (đọc thành khoảng thở) thay vì nằm dưới
       dòng chữ cuối (đọc thành thẻ hụt). Đây mới là thứ chữa "21px trống"; bậc chữ ⛔ không chữa nó.

   ⚖ TƯƠNG PHẢN — ⛔ ĐỪNG HẠ `.82` XUỐNG NỮA. Câu phụ nằm ở nửa DƯỚI thẻ, nơi dải màu đã về mốc
     `--tt3d-dam`. Nền SÁNG NHẤT trong bảy chủ đề là `#166534` (lục, độ chói `L = 0,0952`); trắng ở
     `.82` hoà trên nền ấy đo được **5,39:1** (đo TRÊN BẢN CHẠY, Chrome thật — bốn ô lần lượt 5,44 ·
     5,39 · 6,41 · 6,51), vẫn trên ngưỡng AA (4,5:1). Và đó là **SÀN DƯỚI**: câu phụ nằm dưới lớp phủ
     tối dần về đáy của khối 3D, tức nền thật còn sẫm hơn mốc `--tt3d-dam` vừa tính. Hạ tiếp là rơi
     xuống dưới ngưỡng, mà ⛔ không một câu báo nào — build xanh, test xanh, chữ chỉ mờ đi.

   ⛔ BỘ CHỌN CỐ Ý BÁM `.card.tt3d`, ⛔ KHÔNG `.tt3d` trần: `.blv-dau.tt3d` (hai nút khai đơn) và
     `.nut-mo-bang.tt3d` ⛔ KHÔNG có cấu trúc ba dòng này — nới bộ chọn là đi đặt `margin-top: auto`
     lên ruột của hai thứ khác hẳn, và cả hai đều ⛔ không có `.card-body` để mà đỡ.
   ⚠ DÒNG TÊN nhận diện bằng **phép LOẠI TRỪ** (`:not(.fs-2):not(.small)`), ⛔ KHÔNG bằng
     `:nth-child(2)`: bốn ô do BỐN component khác nhau dựng, mà `nth-child` gãy **lặng lẽ** ngay lượt
     đầu tiên có ai chèn thêm một dòng vào giữa — build xanh, test xanh, chỉ một ô thôi đậm chữ. */
:root:is([data-theme="light"], [data-theme="dark"]) .card.tt3d .card-body {
    display: flex;
    flex-direction: column;
}

/* TẦNG GIỮA — TÊN Ô. Nó là tiêu đề của thẻ, phải đọc ra được như tiêu đề. */
:root:is([data-theme="light"], [data-theme="dark"]) .card.tt3d .card-body > div:not(.fs-2):not(.small) {
    font-weight: 600;
    letter-spacing: -.005em;
    line-height: 1.3;
}

/* TẦNG ĐÁY — CÂU PHỤ: nhỏ hơn, nhạt hơn, và NEO xuống đáy thẻ (xem phép ② ở khối chú thích trên). */
:root:is([data-theme="light"], [data-theme="dark"]) .card.tt3d .card-body > .small {
    margin-top: auto;
    padding-top: .2rem;
    font-size: .8125rem;
    line-height: 1.3;
    color: rgba(255, 255, 255, .82);
}

/* RÊ CHUỘT — bốn ô thẻ NHẤC LÊN (xem luật transform ở đầu khối). */
:root:is([data-theme="light"], [data-theme="dark"]) .card.tt3d:hover {
    transform: translateY(-4px);
    filter: saturate(1.07);
    border-color: rgba(255, 255, 255, .42);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .55),
        inset 0 -3px 0 rgba(0, 0, 0, .18),
        0 18px 28px -12px var(--tt3d-bong, rgba(15, 23, 42, .6));
}

/* RÊ CHUỘT — hai nút khai đơn chỉ SÁNG LÊN, ⛔ không nhấc (thẻ cha cắt, và nó có con `fixed`). */
:root:is([data-theme="light"], [data-theme="dark"]) .blv-dau.tt3d:hover {
    filter: brightness(1.09) saturate(1.05);
    border-color: rgba(255, 255, 255, .45);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .6),
        inset 0 -3px 0 rgba(0, 0, 0, .2),
        0 12px 22px -12px var(--tt3d-bong, rgba(15, 23, 42, .6));
}

/* ⭐ ĐỢT 957 (anh Tiến 21/08/2026: *"nút bảng chấm công 3d nổi lên ở trang chủ"*) — RÊ CHUỘT: nút
   "Bảng chấm công" NHẤC LÊN như bốn ô thẻ, ⛔ không phải chỉ sáng lên như hai nút khai đơn.
   🔑 Vì sao nó được phép nhấc trong khi `.blv-dau` bị cấm (đọc luật transform ở đầu khối): hộp thoại
     bảng công (`HopThoai`, thứ mang nền phủ `position: fixed`) dựng ở NHÁNH KHÁC của component —
     nút và hộp là hai anh em, hộp KHÔNG phải con của nút; và cha `.khung-nut-bang` chỉ là một
     `display: flex` căn giữa, không `overflow: hidden` nên phần nhấc lên không bị cắt.
   ⚠ Chừa `:not(:disabled)`: lúc "Đang tải bảng công…" nút phải đứng yên — nhấc một nút không bấm
     được là hứa hẹn sai. */
:root:is([data-theme="light"], [data-theme="dark"]) .nut-mo-bang.tt3d:hover:not(:disabled) {
    transform: translateY(-4px);
    filter: saturate(1.07);
    border-color: rgba(255, 255, 255, .42);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, .55),
        inset 0 -3px 0 rgba(0, 0, 0, .18),
        0 18px 28px -12px var(--tt3d-bong, rgba(15, 23, 42, .6));
}

/* NHẤN GIỮ: lún xuống — cùng cảm giác phím bấm với dải thanh nav (đợt 680). */
:root:is([data-theme="light"], [data-theme="dark"]) .card.tt3d:active { transform: translateY(-1px); }
:root:is([data-theme="light"], [data-theme="dark"]) .blv-dau.tt3d:active {
    box-shadow: inset 0 2px 5px rgba(0, 0, 0, .3);
}
:root:is([data-theme="light"], [data-theme="dark"]) .nut-mo-bang.tt3d:active:not(:disabled) {
    transform: translateY(0);
    box-shadow: inset 0 2px 6px rgba(0, 0, 0, .28);
}

:root:is([data-theme="light"], [data-theme="dark"]) .card.tt3d:focus-visible,
:root:is([data-theme="light"], [data-theme="dark"]) .blv-dau.tt3d:focus-visible,
:root:is([data-theme="light"], [data-theme="dark"]) .nut-mo-bang.tt3d:focus-visible {
    outline: 3px solid rgba(255, 255, 255, .9);
    outline-offset: 2px;
}

/* ── BẢY CHỦ ĐỀ MÀU (bộ thứ bảy `.tt3d-muc-tieu` thêm ở đợt 1253, ghi lý do ngay tại dòng khai) ──
   · CHỜ DUYỆT   — hổ phách/cam: việc đang NẰM CHỜ tay người, cùng họ màu với mọi dấu "cần xử lý".
   · GIAO VIỆC   — xanh dương: màu điều hành, và là màu thương hiệu navy của app dịch sáng lên.
   · QUÁ HẠN     — hai trạng thái CÙNG HỌ THỜI HẠN: đỏ hồng khi còn việc trễ, xanh NGỌC khi sạch.
     ⛔ Không quay lại xanh lá `success`: anh Tiến gọi đích danh nó ngày 21/08 là màu không đúng chủ đề.
   · NHIỆM VỤ/DỰ ÁN — tím: sắc riêng cho khối "kho việc dài hạn", thay ô xám không nói lên điều gì.
   · BA NÚT "CỦA TÔI" — xanh ngọc SÁNG, dùng CHUNG một bộ màu `.tt3d-cong`: "Lý do đi trễ / về sớm" ·
     "Xin nghỉ phép" · "Bảng chấm công".
     ⭐ ĐỢT 957 — anh Tiến 21/08/2026, hai tin liền nhau: *"nút xin nghỉ phép và nút lý do đi trễ về sớm
     để màu sáng và cùng màu"* · *"nút bảng chấm công cũng cùng màu"*. Trước đó ba nút ba màu và hai màu
     đầu đều TỐI: chàm `#6366f1→#3730a3` · xanh biển `#06b6d4→#155e75` · navy thương hiệu
     `#002169→#1565d8` (nút bảng công tự khai màu trong tệp scoped của nó).
     🔑 Vì sao chúng đáng CHUNG một màu: cả ba là việc CỦA CHÍNH NGƯỜI ĐANG XEM (khai đơn của tôi · xem
       bảng công của tôi), khác hẳn dãy thẻ phía trên — thứ nói về việc của người KHÁC đang chờ mình.
       Màu ở đây vì thế là màu NHÓM, ⛔ không phải màu nghĩa (không dính luật đỏ/xanh của CLAUDE.md).
     ⛔ Đừng tách lại thành ba bộ màu "cho đúng chủ đề từng nút": đó đúng là cái anh vừa bảo bỏ.
     ⚠ Cyan-400 → cyan-700 cố ý KHÁC `.tt3d-han-ok` (teal `#14b8a6→#115e59`) và `.tt3d-giao`
       (`#0ea5e9→#1e40af`) ở nửa ĐẬM — nửa đậm mới là phần chiếm diện tích lớn của khối. */
.tt3d-duyet   { --tt3d-sang: #f97316; --tt3d-dam: #9a3412; --tt3d-bong: rgba(154, 52, 18, .55); }
.tt3d-giao    { --tt3d-sang: #0ea5e9; --tt3d-dam: #1e40af; --tt3d-bong: rgba(30, 64, 175, .55); }
.tt3d-han     { --tt3d-sang: #f43f5e; --tt3d-dam: #9f1239; --tt3d-bong: rgba(159, 18, 57, .55); }
.tt3d-han-ok  { --tt3d-sang: #14b8a6; --tt3d-dam: #115e59; --tt3d-bong: rgba(17, 94, 89, .55); }
.tt3d-duan    { --tt3d-sang: #a855f7; --tt3d-dam: #5b21b6; --tt3d-bong: rgba(91, 33, 182, .55); }
.tt3d-cong    { --tt3d-sang: #22d3ee; --tt3d-dam: #0e7490; --tt3d-bong: rgba(14, 116, 144, .5); }
/* ⭐ ĐỢT 1253 — BỘ THỨ BẢY: MỤC TIÊU, hổ phách. Anh Tiến 25/08/2026 gom khối 🎯 Mục tiêu thành một nút
   đứng cạnh nút "Bảng chấm công" ⇒ ba nút lớn nằm sát nhau trên cùng một dải.
   🔑 Vì sao KHÔNG cho nút mới mượn `.tt3d-cong`: ba nút cạnh nhau mà cùng một màu thì mắt phải ĐỌC CHỮ
     mới biết bấm cái nào — đúng thứ dải nút sinh ra để khỏi phải làm. Màu ở đây là màu PHÂN BIỆT trong
     một dải, ⛔ không mâu thuẫn với câu "ba nút của tôi cùng màu" (đợt 957): câu ấy nói về ba nút NẰM
     RỜI ở ba khối khác nhau, chỉ nút 📆 trong số đó có mặt ở dải này.
   ⚠ Câu cũ ở đây (*"nửa ĐẬM `#92400e` amber-800 cố ý khác hẳn `.tt3d-duyet` `#9a3412` orange-800"*) đã
     ĐO LẠI VÀ BÁC BỎ ở đợt 1299 — xem ngay dưới.
   ⓘ Nút 🗃 Công việc dùng lại `.tt3d-duan` (tím) — CÙNG chủ đề với ô hồ sơ của dãy thẻ. */

/* 🔴 ĐỢT 1299 (việc o490) — ĐỔI TỪ HỔ PHÁCH SANG LỤC, VÌ HAI Ô NAY ĐỨNG SÁT NHAU.
   Anh Tiến xếp lại bốn ô trang chủ ⇒ khối 🎯 Mục tiêu thành ô ② nằm NGAY CẠNH ô ① Phê duyệt / Nghiệm
   thu (bộ `.tt3d-duyet`, cam). Ở dải nút đời trước hai thứ ấy không kề nhau nên chưa ai thấy vướng.
   📏 Đo trên bản tĩnh nền TỐI (25/08/2026, khung 1600px): nền hai ô ra `rgb(154,52,18)` và
     `rgb(146,64,14)` — lệch sắc **8°**, tương phản độ sáng **1,03:1**, tức mắt đọc thành MỘT màu.
     ⇒ Cùng loại khiếm khuyết vừa chữa cho ô ③/④ (hai ô tím trùng khít) — chữa nốt cho cả dãy.
   🔑 Lục hợp nghĩa: mục tiêu là ĐÍCH ĐẾN, và nó tách hẳn khỏi ba ô còn lại (cam · xanh biển · tím).
   ⚠ Bộ này chỉ dùng ở MỘT chỗ (ô ②) nên đổi giá trị ⛔ không lan sang màn hình nào khác — đã grep. */
.tt3d-muc-tieu { --tt3d-sang: #22c55e; --tt3d-dam: #166534; --tt3d-bong: rgba(22, 101, 52, .55); }

/* ⭐ ĐỢT 2272 (việc o1786) — BỘ THỨ TÁM: 📅 LỊCH CÔNG TÁC TUẦN, chàm.
   Anh Tiến 10/09/2026: *"lịch công tác tuần tạo thành nút đưa lên hàng bảng chấm công (đồ họa nút
   tương tự)"* ⇒ nút mới đứng NGAY CẠNH nút 📆 Bảng chấm công ở hàng bàn làm việc.
   🔑 Vì sao ⛔ KHÔNG cho nó mượn `.tt3d-cong` (xanh ngọc) của nút 📆: hai nút sát nhau mà cùng một màu
     thì mắt phải ĐỌC CHỮ mới biết bấm cái nào — đúng thứ dải nút sinh ra để khỏi phải làm (cùng lẽ
     với luật đợt 1253). Câu *"ba nút CỦA TÔI cùng màu"* (đợt 957) nói về việc của chính người đang
     xem (đơn của tôi · bảng công của tôi); tờ lịch công tác là việc CHUNG của cơ quan nên ⛔ không
     thuộc nhóm ấy.
   ⚠ Chàm `#6366f1 → #3730a3` chính là bộ màu nút "Lý do đi trễ / về sớm" đã bỏ ở đợt 957; đo 10/09
     bằng grep toàn `src/TKNB.Web`: ⛔ KHÔNG nút nào còn dùng hai mã màu ấy ⇒ nhận lại ⛔ không đụng
     màn hình nào khác. Nó tách khỏi `.tt3d-giao` (`#0ea5e9 → #1e40af`) ở nửa ĐẬM — phần chiếm diện
     tích lớn của khối, đúng phép so mà đợt 957 đã dùng. */
.tt3d-lich    { --tt3d-sang: #6366f1; --tt3d-dam: #3730a3; --tt3d-bong: rgba(55, 48, 163, .55); }

/* Máy "giảm chuyển động": bỏ phần nhún và vệt quét, GIỮ nguyên hình khối 3D — cùng luật với khối
   đợt 447 và dải nav đợt 680. */
@media (prefers-reduced-motion: reduce) {
    :root:is([data-theme="light"], [data-theme="dark"]) .card.tt3d,
    :root:is([data-theme="light"], [data-theme="dark"]) .blv-dau.tt3d,
    :root:is([data-theme="light"], [data-theme="dark"]) .nut-mo-bang.tt3d { transition: none; }
    :root:is([data-theme="light"], [data-theme="dark"]) .card.tt3d:hover,
    :root:is([data-theme="light"], [data-theme="dark"]) .nut-mo-bang.tt3d:hover:not(:disabled) { transform: none; }
    :root:is([data-theme="light"], [data-theme="dark"]) .card.tt3d:hover::after,
    :root:is([data-theme="light"], [data-theme="dark"]) .card.tt3d:focus-visible::after,
    :root:is([data-theme="light"], [data-theme="dark"]) .blv-dau.tt3d:hover::after,
    :root:is([data-theme="light"], [data-theme="dark"]) .blv-dau.tt3d:focus-visible::after,
    :root:is([data-theme="light"], [data-theme="dark"]) .nut-mo-bang.tt3d:hover:not(:disabled)::after,
    :root:is([data-theme="light"], [data-theme="dark"]) .nut-mo-bang.tt3d:focus-visible::after { animation: none; }
}

} /* hết @media screen — khối thẻ 3D đợt 931 */

/* ═══════════ ⭐ ĐỢT 1253 — DẢI BA NÚT LỚN CỦA TRANG CHỦ ═══════════

   Anh Tiến 25/08/2026: *"ở trang chủ, mục tiêu gom thành 1 nút đặt kế nút bảng chấm công và đồ họa nút
   tương tự, khi nhấn vào sẽ ra hộp thoại xử lý, cho phép tạo nhanh từ màn hình này, em cũng làm tương
   tự với khung quản lý công việc"*.

   ⇒ Ba nút cùng một dải: 📆 Bảng chấm công · 🎯 Mục tiêu · 🗃 Công việc. Trước đợt này hai khối 🎯 và 🗃
     là hai thẻ `card` BÀY SẴN ba cột giữa trang chủ; nay chúng thu về một nút, nội dung cũ chuyển
     nguyên vẹn vào hộp thoại.

   🔑 VÌ SAO HÌNH DÁNG NÚT DỜI TỪ TỆP SCOPED SANG ĐÂY: `.nut-mo-bang` vốn khai trong
     `KhoiBangCongPhongTrangChu.razor.css`, mà **CSS scoped của Blazor không dùng chung được giữa các
     component** — hai nút mới nằm ở hai component khác nên sẽ không hưởng một dòng nào. Hai đường ra:
     chép công thức sang hai tệp scoped nữa (ba bản phải sửa cùng lúc mỗi lần anh đổi một câu — đúng
     thứ khối `.tt3d` đợt 931 sinh ra để tránh), hoặc dời về một chỗ. Chọn đường thứ hai.
   ⛔ ĐỪNG chép các luật dưới đây ngược trở lại tệp scoped nào: luật ở đây là (0,1,0) nên bản scoped
     (0,2,0) sẽ THẮNG trong im lặng, và phiên sau sẽ sửa vào bản không có tác dụng.
   ⓘ Cái ÁO 3D (nền, bóng, vệt quét, nhún) vẫn nằm ở khối `.tt3d` ngay trên — phần dưới đây CHỈ là
     HÌNH DÁNG (viên thuốc, cỡ chữ, khoảng cách). Hai thứ tách nhau có chủ ý: đổi màu thì sửa bảng màu,
     đổi dáng thì sửa chỗ này.
   ⚠ Cố ý đặt NGOÀI `@media screen`: đây là bố cục, không phải hiệu ứng — bản in cũng cần nút đọc được. */

/* ⛔ ĐỢT 1299 (việc o490) — LUẬT `.dai-nut-lon` ĐÃ GỠ CÙNG VỚI CHÍNH CÁI DẢI.
   Anh Tiến 25/08/2026: *"lược bớt nút mục tiêu và công việc, đưa nút bảng chấm công lên cùng hàng với
   lý do đi trễ về sớm và đưa ra bên trái ngoài cùng"* ⇒ dải ba nút không còn: hai nút thành ô ② và ô ④
   của dãy thẻ, nút 📆 xuống bàn làm việc (hàng ấy đã có `.blv-day` lo bố cục).
   ⛔ Đừng khai lại luật này "cho chắc": một luật không nơi nào dùng là chỗ phiên sau đọc rồi tưởng dải
     vẫn còn, rồi đi tìm xem nó hỏng ở đâu. Hàng rào `DaiNutLonTrangChuTests` canh cả hai chiều.
   ⓘ Phần HÌNH DÁNG `.nut-mo-bang` + `.nmb-*` ngay dưới thì GIỮ NGUYÊN — nút 📆 vẫn dùng. */

/* HÌNH DÁNG của nút lớn. ⛔ Không nền, không bóng, không hiệu ứng — xem khối chú thích ở trên. */
.nut-mo-bang {
    display: inline-flex;
    align-items: center;
    gap: .85rem;
    max-width: 100%;
    padding: .8rem 1.6rem;
    border-radius: 999px;
    text-align: left;
    cursor: pointer;
    /* Lưới an toàn: nếu vì lý do gì đó thẻ `<html>` chưa mang `data-theme` thì khối `.tt3d` không khớp,
       và một nút chữ trắng trên nền trong suốt là nút VÔ HÌNH. Hai dòng này chỉ để không bao giờ có
       cảnh đó; khối `.tt3d` (0,4,0) đè lên ngay khi giao diện đã xác định. */
    color: #fff;
    background-color: #0e7490;
    border: 0;
}

.nut-mo-bang:disabled {
    cursor: progress;
    filter: saturate(.5);
    opacity: .85;
}

.nmb-icon {
    flex: none;
    font-size: 1.6rem;
    line-height: 1;
    filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .35));
}

.nmb-chu {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.nmb-ten {
    font-weight: 700;
    font-size: 1.02rem;
    line-height: 1.2;
}

/* ⭐ ĐỢT 1253 — HUY HIỆU SỐ trên nút (vd "3/7 mục tiêu", "5 hồ sơ").
   ⓘ Đây là THÔNG TIN đang hiện chứ không phải hướng dẫn ⇒ được in thường trực ra màn hình, đúng ngoại
     lệ ghi trong luật giao diện của dự án. Câu giải thích vẫn nằm trong `title` của nút.
   ⛔ Con số này CHỈ hiện khi đã đọc được số thật; ⛔ đừng in "0" lúc chưa nạp — đúng bẫy "0 khi không có
     số liệu" (quy tắc cứng #10): nút sẽ khai là *không có mục tiêu nào* trong khi sự thật là *chưa hỏi*. */
.nmb-phu {
    font-size: .82rem;
    line-height: 1.2;
    opacity: .9;
}

/* ── Khổ hẹp: nút xuống hai dòng chữ vẫn đọc được, không tràn ── */
@media (max-width: 576px) {
    .nut-mo-bang { padding: .65rem 1rem; gap: .6rem; }
    .nmb-ten { font-size: .95rem; }
}

/* ═══════════ ⭐ ĐỢT 1303 — BA NÚT CỦA HÀNG BÀN LÀM VIỆC PHẢI ĐỀU NHAU ═══════════

   Anh Tiến 25/08/2026: *"3 nút hàng bảng chấm công phải đều nhau và chung format"*.

   📏 ĐO TRƯỚC KHI SỬA (bản tĩnh khối `.ban-lam-viec`, khung 1400px, giao diện sáng, chốt ngược
     `d-flex` ĐẠT — `node cong-cu/dung-ban-tinh-trang.mjs --trang "" --lop ban-lam-viec --rong 1400`):
       · 📆 Bảng chấm công : 233,4 × 53,2 · bo 999px · viền 1px · đệm 12,8/25,6 · chữ đậm 400
       · ⏰ Lý do đi trễ    : 571,3 × 50,8 · bo  12px · viền 2px · đệm 10,4/13,6 · chữ đậm 700
       · 🏖 Xin nghỉ phép   : 571,3 × 50,8 · (y hệt thẻ trên)
     ⇒ Lệch **2,45 lần** bề ngang, và ⛔ KHÔNG một thuộc tính hình nào trùng nhau. Đây là hệ quả của
       đợt 1299: nút 📆 vốn thuộc `.dai-nut-lon` (viên thuốc, co theo chữ) rồi được dời sang đứng
       chung hàng với hai thẻ khai đơn (`flex: 1 1 20rem`) mà ⛔ không ai đổi cái áo của nó.

   🔑 VÌ SAO SỬA Ở ĐÂY, ⛔ không ở tệp `.razor.css` nào: nút 📆 do `KhoiBangCongPhongTrangChu` dựng,
     còn hàng `.blv-day` do `KhoiBanLamViec` dựng — **HAI component**, mà CSS scoped của Blazor ⛔
     không dùng chung được giữa các component. Đây đúng đường đợt 1253 đã đi khi dời `.nut-mo-bang`
     về đây.
   ⛔ ĐỪNG sửa thẳng khối `.nut-mo-bang` ở trên thành 12px + `flex`: hàng rào `DaiNutLonTrangChuTests`
     khai `border-radius: 999px` là HÌNH DÁNG NỀN của lớp ấy. Ở đây chỉ nói thêm một câu —
     *"khi đứng TRONG hàng bàn làm việc thì mặc đồng phục của hàng"* — và (0,2,0) đè (0,1,0).

   ⚠ `align-self: stretch` chứ ⛔ KHÔNG gõ một chiều cao cố định: hai thẻ kia dày hơn đúng **4px** vì
     viền 2px của `.blv-the` bọc NGOÀI nút, mà con số ấy đổi ngay ngày ai đó chỉnh viền. Kéo giãn
     theo hàng thì tự đúng mãi; số gõ tay thì lệch trong im lặng.
     ⓘ Hàng khai `align-items: flex-start`, nên phải nói `align-self` ở CHÍNH nút — ⛔ đừng đổi
       `align-items` của cả hàng: đó là đụng vào bố cục của hai thẻ kia cho một việc chỉ liên quan
       tới nút thứ ba (quy tắc #18 — kiểm soát rủi ro lan truyền).

   ⚠ Viền 2px navy khai bằng **LONGHAND** ở (0,5,0): `.tt3d` đặt `border` bằng **SHORTHAND** ở (0,4,0)
     (`1px solid rgba(255,255,255,.28)`), nên viết shorthand ở đây là **thua**, còn longhand thì thắng
     đúng hai thuộc tính cần đổi và giữ nguyên kiểu viền + trọn cái áo 3D. Đúng bài học đợt 838/850:
     hỏi *"Bootstrap/khối chung dựng viền ở trạng thái này bằng shorthand hay longhand?"* trước khi
     chép khuôn. */
.blv-day > .nut-mo-bang {
    flex: 1 1 20rem;
    min-width: 0;
    align-self: stretch;
    border-radius: .75rem;
    padding: .65rem .85rem;
    gap: .6rem;
    font-weight: 700;
}

/* Chữ và biểu tượng lấy ĐÚNG cỡ của `.blv-dau` (khai trong `KhoiBanLamViec.razor.css`).
   ⚠ ⛔ Đừng hạ dưới .65rem — ngưỡng của `CoChuONgayTests`; đây là hạ từ 1,02rem xuống .98rem nên an toàn. */
.blv-day > .nut-mo-bang .nmb-icon { font-size: 1.45rem; }
.blv-day > .nut-mo-bang .nmb-ten  { font-size: .98rem; line-height: 1.5; }

/* Viền của thẻ `.blv-the` — mỗi giao diện một màu, nên ở đây cũng phải hai dòng.
   ⚠ Dòng TỐI đứng SAU và cùng (0,5,0) ⇒ thắng nhờ THỨ TỰ trong tệp. Đảo lên trên là nền tối mang
     viền navy của nền sáng — hỏng lặng lẽ, chỉ mắt người mới thấy. */
:root:is([data-theme="light"], [data-theme="dark"]) .blv-day > .nut-mo-bang.tt3d {
    border-width: 2px;
    border-color: rgba(10, 61, 145, .30);
}

:root[data-theme="dark"] .blv-day > .nut-mo-bang.tt3d {
    border-color: rgba(120, 180, 255, .34);
}

/* ═══════════ ⭐ ĐỢT 2362e (việc o1967) — MỌI HỘP THOẠI CAO CỐ ĐỊNH THEO CỠ, THÂN HỘP TỰ CUỘN ═══════════

   Anh Tiến 13/09/2026, nguyên văn: *"anh cần tất cả khung dialogue cố định kích thước, nhảy kích thước
   theo nội dung rất khó chịu"*. Ảnh anh gửi: hộp 📊 Quản lý ngày phép (cỡ Rong) lọc 19 người thì cao kín
   màn 900px, lọc còn 2 người thì co lại còn ~640px — mỗi lần gõ ô lọc là cả khung nhảy.

   🔑 GỐC BỆNH — cùng chỗ khối đợt 1266 ngay dưới đã chỉ ra, nhưng đợt ấy chỉ vá cho 4 hộp trang chủ
     bằng lớp tự khai `hop-cao-co-dinh`: `modal-dialog-scrollable` của Bootstrap đặt chiều cao cho
     `.modal-dialog` (khung ngoài = 100% màn trừ hai lề) nhưng để `.modal-content` ở `max-height: 100%`,
     tức thẻ hộp vẫn CO THEO NỘI DUNG. Khối này là luật CHUNG cho mọi hộp dựng bằng `<HopThoai>`
     (đo 13/09/2026: 91 tệp .razor).

   ── MỐC CHIỀU CAO — ĐÚNG MỘT CHỖ, theo cỡ khai ở enum `CoHopThoai` (HopThoai.razor.cs) ──
     · Nho  (`modal-sm`)              → 50vh — hộp xác nhận một câu, một hai ô nhập.
     · Vua  (mặc định, ⛔ không lớp)  → 70vh — biểu mẫu ngắn.
     · Lon  (`modal-lg`) · Rong (`modal-xl`) · RatRong (`modal-xl hop-rat-rong`) → 100% = KÍN MÀN TRỪ LỀ.
       `100%` ở đây là chiều cao `.modal-dialog`, thứ Bootstrap đã trừ sẵn hai lề `--bs-modal-margin`
       (1,75rem từ 576px trở lên · 0,5rem dưới đó) ⇒ trên máy tính đúng bằng calc(100vh - 3.5rem) mà
       ⛔ không phải chép lại con số lề của Bootstrap vào đây.
     · Khổ điện thoại (dưới 576px): MỌI cỡ = 100% — màn nhỏ thì hộp nào cũng cần hết chỗ.

   🔑 VÌ SAO MỐC ĐI BẰNG BIẾN `--hop-thoai-cao` ĐẶT TRÊN `.modal-dialog`, ⛔ không viết ba luật `height`
     cho `.modal-content`: nhờ vậy cả khối chỉ có ĐÚNG MỘT luật `height` (độ đặc hiệu (0,2,0)), và nó
     đứng TRƯỚC khối đợt 1266 ⇒ `.modal-content.hop-cao-co-dinh` (cũng (0,2,0), đứng SAU) vẫn THẮNG.
     Bốn hộp trang chủ giữ nguyên mốc riêng anh đã chốt (72vh/700px · 94vh/1200px) và cả luật
     `height: auto` ở khổ hẹp của chúng. Viết `.modal-dialog-scrollable.modal-xl .modal-content` là
     (0,3,0) — nó đè lên đợt 1266 mà ⛔ không ai kêu.

   ⚠ CHỈ ÁP KHI THÂN HỘP CUỘN (`modal-dialog-scrollable`, tức tham số `CuonTrongThan` — mặc định BẬT).
     Hộp tắt nó (KhoiSoanToTrinhChuTruong: hai cột tự cuộn) ⛔ không có chỗ nào cho nội dung thừa đi
     ⇒ khóa chiều cao là chữ tràn ra ngoài hộp. Cố định kích thước và thân cuộn là MỘT CẶP.

   ⚠ `min-height: 0` trên `.modal-body` là dòng dễ bị dọn nhất: phần tử flex mặc định ⛔ co nhỏ hơn nội
     dung của nó, thiếu dòng này thì thân hộp ⛔ KHÔNG cuộn mà đẩy dải chân ra khỏi hộp. Hai dòng còn lại
     của thân hộp nhắc lại giá trị Bootstrap để hàng rào đọc được từ chính tệp này.

   ⚠ Bảng gợi ý sổ ngược lên của ONhieuNguoi/OGoTim (đợt 1670) bám vào đúng hai lớp
     `.modal-dialog-scrollable .modal-body` — khối này giữ nguyên cả hai tên lớp, ⛔ đừng đổi.

   🚧 Hàng rào: TKNB.Tests/HopThoaiCoDinhKichThuocTests.cs (đọc khối này theo tiêu đề, ⛔ đừng đổi tên). */
.modal-dialog-scrollable {
    --hop-thoai-cao-nho: 50vh;
    --hop-thoai-cao-vua: 70vh;
    --hop-thoai-cao-lon: 100%;
    --hop-thoai-cao: var(--hop-thoai-cao-vua);
}

.modal-dialog-scrollable.modal-sm {
    --hop-thoai-cao: var(--hop-thoai-cao-nho);
}

.modal-dialog-scrollable.modal-lg,
.modal-dialog-scrollable.modal-xl {
    --hop-thoai-cao: var(--hop-thoai-cao-lon);
}

/* Thẻ hộp: chiều cao CỐ ĐỊNH theo mốc — ⛔ không `max-height`, ⛔ không `auto`: cả hai đều là "trôi theo
   nội dung", đúng thứ anh bảo khó chịu. */
.modal-dialog-scrollable .modal-content {
    height: var(--hop-thoai-cao);
}

/* Thân hộp nhận phần còn lại và tự cuộn; đầu hộp (tiêu đề + ✕) và dải chân đứng yên. */
.modal-dialog-scrollable .modal-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

/* Khổ điện thoại: mọi cỡ kín màn trừ lề. Ngưỡng 575.98px là mốc `sm` của Bootstrap, cũng là chỗ
   `--bs-modal-margin` đổi từ 0,5rem lên 1,75rem. Đổi MỐC chứ ⛔ không thêm luật `height` thứ hai — để
   luật `height` ở trên vẫn là luật duy nhất của khối. */
@media (max-width: 575.98px) {
    .modal-dialog-scrollable {
        --hop-thoai-cao-nho: 100%;
        --hop-thoai-cao-vua: 100%;
    }
}

/* ═══════════ ⭐ ĐỢT 1266 — HỘP THOẠI CAO CỐ ĐỊNH, MỖI CỘT TỰ CUỘN ═══════════

   Anh Tiến 25/08/2026: *"ở hộp thoại nút mục tiêu và công việc hiển thị kích thước hộp thoại CỐ ĐỊNH
   và hợp lý, nội dung trong từng cột cho phép cuộn xuống xem khi vượt ngoài, nội dung trong cột có thể
   thu nhỏ lại một ít cho vừa khung xem"*.

   🔑 VÌ SAO `modal-dialog-scrollable` CỦA BOOTSTRAP CHƯA ĐỦ — chỗ dễ hiểu ngược nhất: nó đặt chiều cao
     cho `.modal-dialog` (khung ngoài) nhưng để `.modal-content` ở `max-height: 100%`, tức thẻ hộp vẫn
     CO THEO NỘI DUNG. Hộp mục tiêu 1 dòng thì lùn, thêm vài dòng là cao vọt lên, tick một ô cũng nhảy
     — ba cột hai bên xê dịch theo, mắt phải đi tìm lại chỗ vừa đứng. Một dòng `height` là hết.

   🔑 VÌ SAO KHÔNG THÊM THAM SỐ VÀO `HopThoai` — component dùng chung là chỗ rủi ro lan truyền cao nhất
     (quy tắc #18) và `ThamSoComponentCoThatTests` dặn thẳng điều đó. Khung đã sẵn tham số `LopThan`
     (lớp thêm cho `.modal-content`) và `LopThanHop` (lớp thêm cho `.modal-body`) ⇒ hai hộp này chỉ
     KHAI THÊM một lớp, ⛔ không sửa một dòng nào của khung.

   ⛔ ĐỪNG khai luật này trong `.razor.css` của hai khối: `.modal-content` do component `HopThoai` sinh
     ra nên KHÔNG mang mã scope của khối gọi, mà trong hai khối ấy thẻ `<HopThoai>` cũng không nằm
     trong thẻ bọc nào ⇒ `::deep` không có gốc để bám. Bộ chọn sẽ khớp 0 phần tử và hỏng LẶNG LẼ. */

/* Chiều cao CỐ ĐỊNH của thẻ hộp — ⛔ không đổi theo số dòng bên trong.
   ⚠ Ba con số, mỗi con một việc, ⛔ đừng rút còn một:
     · `72vh`  — phần màn hình hộp được chiếm; chừa lại nền hai đầu để còn chỗ nhấn ra ngoài mà đóng.
     · `700px` — TRẦN cho màn hình cao (4K dựng đứng): 72vh ở đó là gần 1.100px, ba cột kéo dài như
                 thế thì mắt phải quét dọc cả màn hình mới đọc hết một cột.
     · `100%`  — SÀN AN TOÀN cho màn hình thấp (laptop 768px, cửa sổ chia đôi): `.modal-dialog` đã trừ
                 sẵn lề nên 100% ở đây là *"kịch khung, không tràn ra ngoài tầm nhìn"*. Thiếu nó là
                 đầu hộp và dải nút bị đẩy khỏi màn hình, người dùng mất luôn đường bấm ✕. */
.modal-content.hop-cao-co-dinh {
    height: min(72vh, 700px);
    max-height: 100%;
}

/* 📏 ĐỢT 2273 (việc o1804) — HỘP CHỨA MỘT LƯỚI DÀI THÌ ĐƯỢC CAO HƠN NẤC CHUNG.
   Anh Tiến 10/09/2026, gửi ảnh hộp 📆 Bảng chấm công tháng 8/2026: *"trên dưới còn dư rất nhiều, tăng
   không gian hiển thị nội dung bảng chấm công, số dòng hiển thị bảng chấm công đang ít quá"*.
   📏 Đo sống trước khi sửa (khung cao 721px): hộp 519px = đúng 72vh, bên trong còn dải «Kỳ xem» 72px +
     chân hộp 55px ⇒ khối bảng chỉ 302px, mà mỗi dòng cao 42px nên **thấy 7 dòng trên tổng 13**.
   🔑 ⛔ KHÔNG nới `hop-cao-co-dinh` — lớp ấy đang đỡ 4 hộp khác (hồ sơ · mục tiêu · công việc · bảng
     công), mỗi hộp một loại nội dung; nới chung là đổi bố cục cả bốn để phục vụ đúng một cái. Lớp
     riêng này CỘNG THÊM vào lớp kia (chuỗi flex bên trong ⛔ không phải sửa gì).
     · `94vh`   — gần kịch màn hình, vẫn chừa 3% mỗi đầu làm chỗ nhấn ra ngoài để đóng.
     · `1200px` — TRẦN cho màn hình dựng đứng, cùng lý do với `700px` của luật ngay trên.
   🔴 BỌC TRONG `@media (min-width: 992px)` LÀ BẮT BUỘC, ⛔ đừng gỡ ra cho gọn: luật `height: auto` của
     khổ nhỏ (ngay dưới) chỉ mang độ đặc hiệu (0,2,1), còn chuỗi hai lớp ở đây là (0,3,1) ⇒ để ngoài
     media thì nó THẮNG cả ở điện thoại, và hộp bị ghim cứng 94vh đúng chỗ luật kia cố ý thả tự do.
     Mốc 992px lấy đúng của `hop-cao-co-dinh`, ⛔ đừng khai một mốc thứ hai. */
@media (min-width: 992px) {
    .modal-content.hop-cao-co-dinh.hop-bang-cong-cao {
        height: min(94vh, 1200px);
    }
}

/* ═══ ⭐ ĐỢT 2277b (việc o1797) — HỘP THOẠI GẦN TRÀN MÀN HÌNH (`CoHopThoai.RatRong`) ═══
   Anh Tiến 10/09/2026 về hộp 📅 Lịch công tác tuần: *"hộp thoại lịch công tác tuần hiển thị to ra,
   hiển thị tất cả các cột"*. Trần `modal-xl` của Bootstrap là 1140px — tờ lịch bảy ngày kèm đủ địa
   điểm, chủ trì, chuẩn bị, thành phần thì ở bề ngang ấy chữ xuống dòng từng chữ.
   · `96vw` — chừa lại 2% mỗi bên: dải nền ấy là chỗ người dùng nhấn ra ngoài để đóng, ⛔ không được
              xóa hẳn (mất nó là hộp trông như một trang mới, ⛔ không phải lớp phủ).
   · `1720px` — TRẦN cho màn hình siêu rộng (21:9, 4K nằm ngang): 96vw ở đó là hơn 3.600px, một dòng
                chữ dài thế thì mắt lạc dòng khi quét ngang.
   ⚠ Đặt `!important` là CỐ Ý và là chỗ dễ hiểu ngược nhất: Bootstrap khai `.modal-xl { max-width }`
     trong một `@media (min-width: 1200px)`, tức (0,1,0) NHƯNG nằm sau tệp này trong thứ tự nạp — bỏ
     `!important` thì luật dưới đây câm ở đúng khổ màn hình lớn, mà ở khổ nhỏ lại chạy, nên đo trên
     cửa sổ hẹp sẽ thấy "đã chạy tốt". */
.modal-dialog.hop-rat-rong {
    max-width: min(96vw, 1720px) !important;
}

/* Khổ hẹp (điện thoại, cửa sổ nhỏ): ba cột XẾP CHỒNG nên hộp cao cố định là ba khung cuộn tí hon
   chồng lên nhau — tệ hơn hẳn cách cũ. Trả hộp về co theo nội dung, thân hộp cuộn chung như trước.
   ⚠ Ngưỡng phải khớp `col-lg-4` của hai khối gọi (992px) — hai chỗ nói hai ngưỡng là hộp cao cố định
     mà cột lại đang xếp chồng. */
@media (max-width: 991.98px) {
    .modal-content.hop-cao-co-dinh {
        height: auto;
    }
}

/* ═══════════ ĐỢT 976 — MỘT KHUÔN NÚT DUY NHẤT CHO CẢ THANH NAV ═══════════
   Anh Tiến 22/08/2026, nguyên văn: "em rà lại và làm đồng nhất tất cả các nút trên thanh nav, mỗi
   app lại có một kiểu nút khác nhau".

   📏 ĐO THẬT TRƯỚC KHI SỬA (bản tĩnh 5 trang dựng bằng `cong-cu/dung-ban-tinh-trang.mjs`, khung
      1500px, giao diện sáng; chốt ngược `d-flex` đạt ở cả 5 lượt):
     · dải TAB TRANG CỦA APP (`.tta-tab`, thấy ở /quan-tri/nguoi-dung) — cao 26,8 · chữ 13,44 · đệm 4 / 8,8
     · dải TAB TRONG TRANG (`.mcd-dai-tab .nav-link` — /cham-cong · /nuoc · /nghi-phep) — cao 32,6 ·
       chữ 14 · đệm 4,8 / 10,4
     · dải TAB KHO của /cong-nghe/so-do (cùng loại, có gắn thêm `py-1 px-2`) — cao 31,2 · đệm 4 / 8
     · nút chuông 🔔 (`.tb-nut`) — cao 30,4 · đệm 4 / 8,8
   ⇒ BỐN cỡ nút chữ trên cùng một thanh, đổi theo app đang mở — đúng thứ anh nói.

   🔑 VÌ SAO ĐỢT 760 CHƯA ĐỦ (nó cũng làm đúng việc này, cùng một câu chỉ đạo dạng khác): đợt đó
     chuẩn hóa `.nav-pills .nav-link` nên với tới mọi dải tab viết bằng `ul.nav.nav-pills`, nhưng KHÔNG
     với tới hai chỗ:
       (a) dải `.tta-tab` là component có CSS CÁCH LY, luật của nó nạp SAU app.css nên thắng ở mọi
           thuộc tính app.css khai bằng bộ chọn cùng độ đặc hiệu;
       (b) LỚP CỠ TIỆN DỤNG của Bootstrap (`py-1` · `px-2` · `small`) mang sẵn `!important`, nên trang
           nào lỡ gắn vào là lệch khuôn — build xanh, test xanh, chỉ mắt người mới thấy.
   ⇒ Khuôn nay đặt ở MỘT chỗ (bộ biến ngay dưới), và phần CỠ ép bằng `!important`. Đây là ca chính
     đáng hiếm hoi của `!important`: chỉ `!important` mới thắng được `!important` của lớp tiện dụng.
     ⛔ Đừng chép con số cỡ ra từng tệp — sửa cỡ thì sửa BIẾN ở đây.
   ⓘ CỐ Ý áp cho MỌI giao diện (không chừa cổ điển như khối 3D đợt 447): đây là KÍCH THƯỚC, không
     phải ngôn ngữ đồ họa; và từ đợt 808 giao diện cổ điển không còn chọn được nữa (quy tắc #20).
   ⚠ Hai nút TRÒN hai đầu thanh (lưới 9 chấm 40×40 · ảnh đại diện 34×34) CỐ Ý đứng ngoài khuôn này:
     vùng bấm 40px của lưới là anh Tiến chốt 13/08/2026, còn ảnh đại diện là ô tròn danh tính chứ
     không phải nút chữ. Khuôn dưới chỉ gom NÚT CHỮ về một cỡ.
   🚧 Hàng rào canh: `TKNB.Tests/NutThanhNavDongNhatTests.cs`. */
:root {
    --nav-nut-cao: 2.05rem;
    --nav-nut-dem-doc: .3rem;
    --nav-nut-dem-ngang: .65rem;
    --nav-nut-chu: .875rem;
    --nav-nut-bo: .45rem;
}

.top-row .mcd-dai-tab .nav-link,
.top-row .tta-tab,
.top-row .tb-nut {
    display: inline-flex;
    align-items: center;
    min-height: var(--nav-nut-cao);
    padding: var(--nav-nut-dem-doc) var(--nav-nut-dem-ngang) !important;
    font-size: var(--nav-nut-chu) !important;
    border-radius: var(--nav-nut-bo);
    line-height: 1.25;
}

/* 📱 ĐỢT 2285 (việc o1811) — TAB CỦA DẢI ⛔ KHÔNG ĐƯỢC CO VÀ ⛔ KHÔNG ĐƯỢC GÃY CHỮ.

   Anh Tiến 10/09/2026 gửi ảnh chụp điện thoại: *"nút thì cái to cái nhỏ, thanh nav các icon cũng
   cái to cái nhỏ sắp xếp lộn xộn không đều và đẹp"*. 📏 Đo sống 390×844 trên bản chạy 5270
   (`/cham-cong`): tab ĐANG MỞ ra **99 × 61px** trong khi bốn tab anh em cùng dải ra **44 × 144–176px**
   ⇒ nó gãy chữ thành **4 dòng** («Bảng / công / theo / tháng») và cao gấp **2,25 lần** hàng xóm.

   🔑 GỐC BỆNH LÀ KIỂU THẺ, ⛔ KHÔNG PHẢI THIẾU CHỖ — chỗ dễ đi chữa nhầm nhất. Dải này **đã tự cuộn
     ngang** từ đợt 422 (`overflow-x: auto` ở `.mcd-dai-tab` ngay trên), nên tab ⛔ không cần co một
     pixel nào. Nhưng tab ĐANG MỞ được dựng bằng `<button>` còn các tab kia là `<a>`, mà `<button>`
     làm phần tử flex thì **cỡ tối thiểu tự động ⛔ không bảo vệ nó** (trình duyệt cho `min-width: auto`
     về 0, khác hẳn thẻ khối) ⇒ một mình nó bị bóp lại rồi xuống dòng, còn thẻ `<a>` thì không.
     ⇒ Hỏng **LẶNG LẼ và chỉ ở khổ hẹp**: máy tính còn thừa chỗ nên ⛔ không ai bị co, build xanh,
     test xanh, và `do-song-khung-hep.mjs` cũng báo **sạch** — nó đo tràn ngang / vùng chạm / cỡ chữ,
     ⛔ không đo *"nút có cùng cỡ với hàng xóm không"* (đúng bài học ⑭ đợt 2197: **đo CÁI ĐANG NGHI**).

   ⚠ PHẢI CÓ CẢ HAI VẾ: chỉ `white-space: nowrap` thì thẻ vẫn co được và chữ bị **CẮT** thay vì gãy —
     đổi một kiểu xấu lấy một kiểu xấu khác. `flex: none` mới là vế giữ nguyên bề ngang nội dung.
   ⛔ ĐỪNG khai cho cả ba nhóm nút của khối ngay trên: `.tta-tab` đã có đủ hai dòng này trong CSS cách
     ly của nó (đợt 650), còn `.tb-nut` (chuông) là nút ở cụm bên phải — ghim `flex` cho nó là đụng
     một cụm ⛔ không liên quan tới lượt đo này.
   🔑 Tiền tố `:root` là CỐ Ý, ⛔ đừng gỡ cho gọn: bộ dò `LuatNutThanhNav.KhoiLuat` tìm khối luật bằng
     chuỗi *"xuống dòng + bộ chọn + ` {`"*. Mở dòng bằng `.top-row .mcd-dai-tab .nav-link {` trần là
     dựng thêm một khối CÙNG đầu dòng với khuôn đợt 976 ⇒ bộ dò nào hỏi bộ chọn ấy sẽ trúng khối
     này (⛔ không có biến `--nav-nut-*` nào) — đúng bẫy *"tìm bằng chuỗi con là trúng nhầm khối
     khác"* đã ghi ở luật thanh nav. Áp cho MỌI giao diện là theo tiền lệ khối kích thước đợt 976. */
:root .top-row .mcd-dai-tab .nav-link {
    white-space: nowrap;
    flex: none;
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   LỐI "Chi tiết →" CỦA MỌI DASHBOARD — MỘT KHUÔN NÚT DÙNG CHUNG (đợt 1050, việc o237)

   Anh Tiến 22/08/2026 (đợt 1004): *"nút xem thêm cảnh báo đồ hoạ dạng nút, làm tương tự với các chữ
   dạng link"* → 23/08 anh nhắc **LẦN HAI**: *"các link chi tiết em đổi thành dạng nút"*.
   🔴 Vì sao phải nhắc lại: đợt 1004 chỉ sửa trang NƯỚC, và sửa ngay trong CSS **cách ly** của
   `TabTongQuanNuoc.razor.css` ⇒ ba trang còn lại không với tới được. Đo 23/08 trước lượt này:
   nước **2** nút viền · mạng **2** nút viền XÁM (khác kiểu) · công nghệ **6** chỗ còn `btn btn-link
   p-0` — tức TRÔNG y hệt một dòng chữ, người xem không biết là bấm được.

   ⇒ Khuôn nay nằm ở ĐÂY, dùng chung mọi trang. Thêm lối "Chi tiết" mới thì khoác đúng bộ lớp:
       class="btn btn-sm btn-outline-primary py-0 px-2 small nut-chi-tiet"
   ⛔ ĐỪNG khai lại `.nut-chi-tiet` trong tệp `.razor.css` của từng trang — CSS cách ly nạp SAU và có
      độ đặc hiệu cao hơn, nên bản chép tay sẽ THẮNG lặng lẽ và hai chỗ trôi khỏi nhau.
   ⛔ ĐỪNG đổi sang nút NỀN ĐẶC: dashboard đã nhiều mảng màu, nút đặc tranh mắt với chính con số phải
      đọc trước. Nút đặc để dành cho việc PHẢI LÀM NGAY (vd "Nhập chỉ số kỳ …"), không phải lối xem thêm.
   🚧 Hàng rào canh: `TKNB.Tests/LoiChiTietDangNutTests.cs`.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */
.nut-chi-tiet {
    font-size: .78rem;
    /* Bóng mảnh cho nút nổi khỏi mặt thẻ — dấu hiệu "bấm được" mà không tốn thêm chiều cao nào. */
    box-shadow: 0 .08rem .18rem rgba(0, 0, 0, .16);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   HOẠ TIẾT MẶT VẢI DÙNG CHUNG CHO HAI TẤM BĂNG RÔN — đợt 1426 (việc o621)
   ═══════════════════════════════════════════════════════════════════════════════════════════
   ⭐ Anh Tiến 27/08/2026: *"băng rôn đồ hoạ như cuộn vải có hoạ tiết đẹp, có các hiệu ứng lung linh"*.

   🔑 VÌ SAO HOẠ TIẾT NẰM Ở ĐÂY MÀ KHÔNG NẰM TRONG HAI TỆP SCOPED. Hai tấm vải là băng rôn ngang trên
     thanh nav (`BangRonChay.razor.css`) và hai biểu ngữ dọc hai bên hông (`BieuNguHong.razor.css`) —
     CSS cách ly nên chúng ⛔ không chia sẻ được luật cho nhau. Chép hoạ tiết ra hai bản là hai tấm vải
     của CÙNG một dịp lễ, treo CÙNG một màn hình, dệt hai kiểu khác nhau sau lượt sửa đầu tiên — hỏng
     LẶNG LẼ, build xanh test xanh. Đây đúng khuôn bộ biến `--nav-nut-*` ngay trên.

   🩸 ĐO TRƯỚC KHI DỆT — VÀ SỐ ĐO LẬT NGƯỢC TRỰC GIÁC. Nghĩ tới "hoạ tiết gấm" là nghĩ tới CHỈ VÀNG,
     nhưng chỉ vàng là lớp SÁNG phủ lên vải ⇒ nó làm NHẠT mặt vải ⇒ kéo TỤT tương phản với chữ vàng.
     Nấc mỏng nhất của cặp màu này (đo ở đợt 1367) vốn đã sát ngưỡng: chân nét `#f7b02e` trên vải tươi
     nhất `#96131a` = 4,66:1 so với ngưỡng 4,5:1. Đo 27/08/2026 (công thức WCAG, tính trên màu ĐÃ TRỘN):
       · sợi vàng `#ffd67c` 5% ....... 4,32:1  ⛔  ← "hoạ tiết cho đẹp" làm HỎNG chữ ngay ở nấc mảnh nhất
       · sợi vàng 2,5% ............... 4,49:1  ⛔  ← trần thật của sợi sáng là **2%** (4,53)
       · sợi TỐI đen 5% .............. 4,96:1  ✅
       · sợi TỐI đen 10% ............. 5,28:1  ✅  ← nấc đang dùng
       · sợi TỐI đen 18% ............. 5,82:1  ✅
     ⇒ Hoạ tiết dệt bằng **SỢI CHÌM** (đen mờ), đúng lối gấm thật: hoa văn hiện ra bằng BÓNG chứ không
       bằng ánh kim. Nó ⛔ không những không phá tương phản mà còn NÂNG nấc mỏng nhất từ 4,66 lên 5,28.
     ⛔ ĐỪNG đổi sợi chìm thành chỉ vàng cho "rực rỡ hơn" — đó đúng là phép đo vừa bác bỏ. Sợi vàng chỉ
       được dùng ở mức ≤2% (dải ánh lụa dưới đây) hoặc ở chỗ chữ ⛔ KHÔNG đi qua (trục cuộn hai đầu).

   ⚠ DÙNG BẰNG LONGHAND `background-image`, ⛔ ĐỪNG NHÉT VÀO SHORTHAND `background`. Quy tắc #5: biến
     ⛔ không phân giải được mà nằm trong shorthand thì CẢ khai báo bị vứt ⇒ mất luôn mặt vải, ⛔ không
     một câu lỗi nào. Hai tệp scoped vì thế phủ hoạ tiết lên một LỚP RIÊNG (thẻ rỗng `*-hoa-tiet`):
     biến hỏng thì lớp ấy trong suốt, mặt vải còn nguyên — hỏng về phía rẻ.
   ⚠ Ba lớp dưới đây CỐ Ý chỉ dùng `repeating-linear-gradient` — loại này mang sẵn hình học của nó nên
     ⛔ KHÔNG cần `background-size`. Dùng `radial-gradient` là phải khai thêm một token cỡ thứ hai, mà
     hai token phải khớp nhau là một chỗ nữa để quên. */
:root {
    /* Gấm chìm: lưới quả trám 14px dệt chéo hai chiều + sợi ngang mảnh (bóng) + ánh lụa dọc (2%). */
    --vai-hoa-tiet:
        repeating-linear-gradient(45deg,
            rgba(0, 0, 0, .10) 0 1px, rgba(0, 0, 0, 0) 1px 14px),
        repeating-linear-gradient(-45deg,
            rgba(0, 0, 0, .10) 0 1px, rgba(0, 0, 0, 0) 1px 14px),
        repeating-linear-gradient(0deg,
            rgba(0, 0, 0, .05) 0 1px, rgba(0, 0, 0, 0) 1px 4px),
        repeating-linear-gradient(90deg,
            rgba(255, 214, 124, .02) 0 1px, rgba(255, 214, 124, 0) 1px 3px);

    /* Đốm lấp lánh: bán kính khai TƯỜNG MINH (`circle 1.6px`) + vị trí theo phần trăm ⇒ ⛔ không cần
       `background-size`, và đốm giữ nguyên cỡ dù tấm vải to nhỏ thế nào. Hai chùm lệch pha nhau để
       mắt thấy "lấp lánh" chứ ⛔ không thấy cả tấm cùng nháy một nhịp. */
    --vai-lap-lanh-a:
        radial-gradient(circle 1.6px at 12% 22%, rgba(255, 249, 224, .95), rgba(255, 249, 224, 0) 70%),
        radial-gradient(circle 1.2px at 37% 74%, rgba(255, 240, 190, .85), rgba(255, 240, 190, 0) 70%),
        radial-gradient(circle 1.8px at 63% 33%, rgba(255, 253, 243, .90), rgba(255, 253, 243, 0) 70%),
        radial-gradient(circle 1.3px at 86% 61%, rgba(255, 244, 205, .85), rgba(255, 244, 205, 0) 70%);
    --vai-lap-lanh-b:
        radial-gradient(circle 1.3px at 24% 58%, rgba(255, 245, 210, .85), rgba(255, 245, 210, 0) 70%),
        radial-gradient(circle 1.7px at 49% 17%, rgba(255, 253, 243, .90), rgba(255, 253, 243, 0) 70%),
        radial-gradient(circle 1.2px at 72% 82%, rgba(255, 240, 190, .80), rgba(255, 240, 190, 0) 70%),
        radial-gradient(circle 1.6px at 94% 29%, rgba(255, 249, 224, .90), rgba(255, 249, 224, 0) 70%);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   📏 TRẦN BỀ NGANG CỘT NỘI DUNG — MỘT CON SỐ, NHIỀU NƠI ĐỌC (đợt 1481, việc o684)
   ═══════════════════════════════════════════════════════════════════════════════════════════
   ⭐ Anh Tiến 29/08/2026: *"băng rôn 2 bên bị vỡ to với màn hình lớn hơn 27 inch"*.

   🔑 VÌ SAO PHẢI LÀ MỘT BIẾN. Bề rộng cột nội dung là con số mà **hai thứ khác nhau cùng phải biết**:
     `.content` của layout (để chừa chỗ) và hai tấm biểu ngữ dọc hai bên hông (để biết dải trống còn
     bao nhiêu). Tới đợt 1481 chúng là **hai con số chép tay**, và chúng đã lệch nhau thật: trang chủ
     nới cột lên `min(96vw, 1760px)` ở đợt 684d mà phép tính của biểu ngữ vẫn đóng đinh 1400px ⇒ hai
     tấm ĐÈ LÊN nội dung ở mọi khổ 1.528–2.288px (đo 29/08 ở khổ 1920: đè **164px** mỗi bên). Hỏng
     **LẶNG LẼ** — build xanh, test xanh, chỉ người ngồi trước màn hình mới thấy.
   ⚠ Ai đặt lại biến này thì đặt trên `:root` (vd `:root:has(.trang-chu)` ở `TongQuan.razor`), ⛔ ĐỪNG
     đặt trên `.content`: hai tấm biểu ngữ là `position: fixed` nằm NGOÀI cây con của `.content` nên
     chúng ⛔ không bao giờ thấy một biến khai ở đó — mà biến "không thấy" thì rơi về giá trị lui và
     ⛔ không một câu lỗi nào, tức dựng lại đúng cái lỗ vừa bịt.
   ⚠ Dùng bằng LONGHAND (`max-width: var(--tran-cot-noi-dung, 1400px)`) và LUÔN kèm giá trị lui —
     quy tắc #5: biến ⛔ không phân giải được mà nằm trong shorthand thì cả khai báo bị vứt.
   🚧 Hàng rào `LuatBieuNguHongTests` đối chiếu con số này với `LuatBieuNguHong.RongCotNoiDungPx` và
     quét MỌI chỗ trong `src/TKNB.Web` đặt trần cho `.content` — thêm một chỗ nới cột mà quên khai là
     build ĐỎ ngay, ⛔ không chờ tới lúc anh nhìn thấy hai tấm đè lên nội dung. */
:root {
    --tran-cot-noi-dung: 1400px;
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   📐 TRẦN BỀ NGANG CỦA PHIẾU LẬP NHIỆM VỤ (đợt 2156, việc o1595)

   🩸 Anh Tiến 07/09/2026, kèm ảnh trang /cong-viec/giao: "màn hình điền thì rất rộng nhưng em
     chưa tối ưu các khung điền". Đo tận nơi: trang khai `style="max-width: 680px"` ở TÁM chỗ
     (thẻ phiếu + 7 dải cảnh báo) — một con số gõ tay từ hồi phiếu chỉ có bốn ô. Cột nội dung
     của app cho tới 1400px, nên trên màn 1920 thì hơn một nửa bề ngang bỏ trống trong khi ô
     "Người làm" (chia chỗ với nút "Giao cho tôi") chỉ còn ~180px.

   🔑 VÌ SAO LÀ BIẾN, ⛔ KHÔNG PHẢI SỬA TÁM SỐ: tám chỗ ấy phải BẰNG NHAU thì dải cảnh báo mới
     thẳng mép với thẻ phiếu. Sửa tay là ngày ai đó chỉnh một chỗ, bảy chỗ kia lệch đi mà build
     xanh, test xanh — đúng bệnh mà biến --tran-cot-noi-dung ở ngay trên sinh ra để tránh.

   ⚠ Dùng bằng LONGHAND kèm GIÁ TRỊ LUI (`max-width: var(--tran-phieu-nhiem-vu, 1180px)`) — quy
     tắc cứng #5: biến ⛔ không phân giải được mà nằm trong shorthand thì CẢ khai báo bị vứt.

   ⚠ `min(100%, …)` chứ ⛔ không phải số trần trơn: phiếu còn phải sống ở khổ điện thoại 390px,
     và ⛔ đừng thay bằng `@media` — một con số đọc được ở mọi khổ thì rẻ hơn một câu điều kiện.

   🔒 Trần 1180px CỐ Ý THẤP HƠN --tran-cot-noi-dung (1400px): phiếu là một cột ô nhập, kéo dài
     hết 1400px thì mắt phải quét ngang quá xa giữa nhãn và ô — thứ cần rộng là TỪNG Ô, và bố
     cục hai cột `col-md-6` đã lo việc ấy. ⛔ Đừng nới cho bằng cột nội dung. */
:root {
    --tran-phieu-nhiem-vu: min(100%, 1180px);
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   📐 TRẦN BỀ NGANG CỦA BẢN VẼ CÔNG NGHỆ (đợt 1550, việc o686 — anh Tiến báo 29/08/2026:
   "màn hình trên 27 inch: bản vẽ công nghệ phóng to và vỡ")

   🔑 VÌ SAO CẦN TRẦN THỨ HAI, TRONG KHI ĐÃ CÓ --tran-cot-noi-dung Ở NGAY TRÊN: hai biến trả lời
     HAI câu khác nhau, ⛔ đừng gộp. Biến trên hỏi "cột nội dung rộng bao nhiêu"; trang có bản vẽ
     CỐ Ý gỡ hẳn trần ấy (`.content.content-rong { max-width: none }`, đợt 1268 — anh chốt "cần tận
     dụng hiệu quả chiều ngang màn hình"). Biến này hỏi "riêng TỜ BẢN VẼ được phóng tới đâu".
     Gộp lại là lùi chỉ đạo 25/08 của anh ở đúng những khổ màn hình anh dùng hằng ngày.

   📏 Số suy ra từ ĐO, ⛔ không chọn cho tròn (Chrome thật, /cong-nghe/so-do, 30/08/2026):
       khổ cửa sổ 1400 → bản vẽ 1337px · nhãn 21px      khổ 2560 → 2497px · nhãn 38px
       khổ 1920      → 1857px · nhãn 28px               khổ 3840 → 3777px · nhãn 58px
     Anh viết "TRÊN 27 inch", tức ≤27 inch anh ⛔ không kêu ⇒ trần đặt ngay TRÊN số đo của khổ
     2560 (2497px) để mọi khổ tới 27 inch ⛔ KHÔNG đổi một pixel nào.

   🔴 CHỮ "VỠ" LÀ VỠ BỐ CỤC, ⛔ không phải vỡ hạt: SVG là hình véc-tơ, phóng bao nhiêu cũng nét.
     Thứ vỡ là phần điều khiển của trang (dải tab · chip lọc · nút · chú giải) ⛔ KHÔNG phóng theo,
     chữ chúng đứng nguyên ~13px ở mọi khổ. 1400px: nhãn bản vẽ 21px = 1,5 lần chữ giao diện, còn
     cùng một trang. 3840px: 58px = 4,5 lần — một tấm áp phích dán đè lên giao diện tí hon.

   ⚠ Dùng bằng LONGHAND kèm GIÁ TRỊ LUI (`max-width: var(--tran-be-ngang-ban-ve, 2500px)`) — quy
     tắc cứng #5: biến ⛔ không phân giải được mà nằm trong shorthand thì cả khai báo bị vứt, và bản
     vẽ lặng lẽ mất trần trở lại.
   🚧 Luật thuần + bảng chân lý: LuatTranBeNgangBanVe · LuatTranBeNgangBanVeTests — hàng rào đối
     chiếu con số ở ĐÂY với hằng TranPx và với dòng CSS trong KhoiSoDoCongNghe.razor, để ba chỗ
     ⛔ không bao giờ nói ba chuyện. Sửa trần thì sửa CẢ BA, hàng rào sẽ gọi tên chỗ còn sót. */
:root {
    --tran-be-ngang-ban-ve: 2500px;
}


/* ═══════════════════════════════════════════════════════════════════════════════════════════
   👆 VÙNG CHẠM CHO NGÓN TAY — CHỈ ÁP TRÊN THIẾT BỊ CẢM ỨNG (đợt 1518, việc o688)
   ═══════════════════════════════════════════════════════════════════════════════════════════
   ⭐ Anh Tiến chọn 29/08/2026 khi được hỏi bước kế tiếp của app điện thoại: **gọn màn hình cho
     ngón tay**. Đây là phần bù cho vỏ PWA đã dựng từ đợt 396/405 — app cài được vào màn hình
     chính từ lâu, nhưng bên trong vẫn là những ô cao 31px vốn vẽ cho con trỏ chuột.

   📏 SỐ ĐO NỀN, đo sống trên Chrome thật ở khung 390×844 ngày 29/08/2026 (10 trang đích của màn
     hình điện thoại, `node cong-cu/do-song-khung-hep.mjs`):
       · **0 trang** tràn ngang  ← phần này ĐÃ LÀNH từ đợt 400, ⛔ đừng đi vá lại
       · **99 vùng bấm** nhỏ hơn 44px — đông nhất là ô nhập/ô chọn **31px** và dải tab **32,6px**
       · **90 chỗ chữ** nhỏ hơn 12px — đông nhất là huy hiệu chuông **10,5px** (có ở MỌI trang)

   🔴 VÌ SAO NẰM TRONG `@media (pointer: coarse)` — ⛔ ĐỪNG GỠ CÂU MEDIA NÀY RA:
     `app.css` là chỗ dùng chung của **mọi** màn hình, mà 433 tài khoản phần lớn ngồi máy tính.
     Nâng chiều cao ô nhập lên 44px cho cả máy tính là **đổi bố cục toàn app** để đi giải một bài
     toán của điện thoại — đúng loại rủi ro lan truyền quy tắc #18 dặn phải tránh. Câu media này
     là ranh giới: máy chuột **⛔ không đổi một pixel nào** (đã đo hai chiều, xem hàng rào).
   ⚠ ⛔ Đừng đổi sang `@media (max-width: …)`: **khổ hẹp ⛔ không có nghĩa là ngón tay** (cửa sổ
     thu nhỏ trên máy tính vẫn dùng chuột), và **màn hình rộng vẫn có thể là cảm ứng** (máy bảng
     nằm ngang, laptop cảm ứng). Thứ quyết định vùng bấm là **CÁI CHẠM VÀO**, ⛔ không phải bề ngang.

   🔑 `min-height` chứ ⛔ không phải `height`: ô nào vốn đã cao hơn (vùng gõ nhiều dòng, nút có hai
     hàng chữ) thì **giữ nguyên**. Đặt `height` là bóp chúng lại — sửa một lỗi, đẻ một lỗi.
   🔑 Cỡ chữ dùng `max(<tỉ lệ cũ>, 12px)`: giữ nguyên **quan hệ tỉ lệ** mà Bootstrap dựng (chữ phụ
     vẫn nhỏ hơn chữ chính) và chỉ **nâng sàn**. Đặt thẳng `font-size: 12px` là san phẳng mọi cấp
     chữ phụ về một cỡ, mất hẳn tầng bậc thị giác.

   ⛔ HAI THỨ CỐ Ý ĐỨNG NGOÀI, ⛔ đừng "bổ sung cho đủ" — là ② và ④ (đợt 1521 đã gỡ ① và ③
      theo chỉ đạo anh Tiến 29/08/2026 *"nới cả hai lên 44px"*):
     ✅ ① `.ma-nut` (lưới 9 chấm · nút quay lại) — ĐÃ NỚI 40→44px, nhưng ⛔ **KHÔNG phải ở đây**:
        luật của nó nằm trong CSS **cách ly** `MenuApp.razor.css` nên bộ chọn ở đây (0,1,0) ⛔ không
        thắng nổi `.ma-nut[b-…]` (0,2,0) — sửa từ đây là viết một luật **⛔ không bao giờ chạy**.
        Máy tính vẫn giữ **40×40 anh chốt 13/08/2026** — nới chỉ trong `@media (pointer: coarse)`.
     ② `.form-check-input` (ô tick, cao 24px) — nâng cả ô tick lên 44px là ô vuông to đúng bằng
        dòng chữ bên cạnh, xấu và ⛔ không giải đúng bài: cái cần nới là **vùng chạm quanh nó**,
        phải sửa ở từng màn hình. Đã ghi việc.
     ✅ ③ `.mcd-logo` (42px) — ĐÃ NỚI lên 44px bằng `min-height`, xem luật ở cuối khối này.
     ④ Trang tĩnh `/nap-lieu/` — ⛔ không nạp `app.css`, nó có CSS riêng (đợt 1493).
   🚧 Hàng rào canh: `TKNB.Tests/VungChamNgonTayTests.cs`.
   📏 Đo lại: `node cong-cu/do-song-khung-hep.mjs --cong <cổng>` */
:root {
    /* 44 CSS px — chuẩn Apple HIG và WCAG 2.5.5 (mức AAA). ⛔ Đừng hạ: dưới mức này ngón cái bấm
       trượt sang ô bên cạnh, mà ô bên cạnh trong app này thường là một nút GHI. */
    --cham-toi-thieu: 44px;
    /* 12px — dưới mức này người 45 tuổi trở lên phải đưa máy lại gần mới đọc được. */
    --cham-chu-toi-thieu: 12px;
}

@media (pointer: coarse) {

    /* ① Ô NHẬP · Ô CHỌN · VÙNG GÕ — nhóm đông nhất trong lượt đo (31px). */
    .form-control,
    .form-select,
    .form-control-sm,
    .form-select-sm {
        min-height: var(--cham-toi-thieu);
    }

    /* ② NÚT CHỮ và MỤC ĐIỀU HƯỚNG.
       ⚠ `.nav-link` dùng `flex` chứ ⛔ KHÔNG `inline-flex`: mục menu trong dải dọc bên trái vốn là
         `block` (chiếm trọn dòng); đổi sang `inline-flex` là chúng co lại bằng bề ngang con chữ,
         tức vùng bấm HẸP ĐI — đúng ngược thứ khối này đi làm. `.btn` thì ngược lại: nó vốn
         `inline-block`, nên `inline-flex` mới giữ đúng chỗ đứng của nó trong dòng. */
    .btn {
        min-height: var(--cham-toi-thieu);
        min-width: var(--cham-toi-thieu);
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }

    .nav-link {
        min-height: var(--cham-toi-thieu);
        display: flex;
        align-items: center;
    }

    /* ③ NÚT LỊCH của ô ngày — nhỏ nhất cả app (27,2×29px) và nằm đúng chỗ hay bấm nhất.
       ⚠ Nới bề ngang nút thì PHẢI nới `padding-right` của ô gõ theo, không thì chữ ngày chui
         xuống dưới nút (chính chú thích của luật gốc đã dặn thế). Hai dòng này đi liền một cặp.
       ⚠ `top/bottom: 0` chứ ⛔ không giữ `1px` của luật gốc: chừa 1px mỗi đầu là nút cao **42px**
         trong một ô đã cao 44 — thiếu đúng 2px và vẫn trượt ngưỡng, tức sửa nửa vời. */
    .o-ngay-boc .nut-lich {
        width: var(--cham-toi-thieu);
        top: 0;
        bottom: 0;
    }

    .o-ngay-boc .o-ngay-go {
        padding-right: calc(var(--cham-toi-thieu) + .2rem);
    }

    /* ④ SÀN CỠ CHỮ. Giữ tỉ lệ cũ của Bootstrap, chỉ nâng sàn — xem chú thích `max()` ở trên. */
    small,
    .small {
        font-size: max(.875em, var(--cham-chu-toi-thieu));
    }

    .badge {
        font-size: max(.75em, var(--cham-chu-toi-thieu));
    }

    /* ⑤ THẺ MỞ RỘNG `<details>` — vùng bấm của nó là dòng tiêu đề, đo được 24px.
       ⛔ CỐ Ý chỉ đặt `min-height`, ⛔ KHÔNG đổi `display`: `summary` là `list-item`, chuyển sang
         `flex` làm **mất dấu tam giác đóng/mở** ở một số trình duyệt — gỡ mất chính dấu hiệu cho
         người ta biết chỗ này bấm được. */
    summary {
        min-height: var(--cham-toi-thieu);
    }

    /* ⭐ ĐỢT 1521 (việc o723 + o724) — Ô LOGO / LỐI VỀ TRANG CHỦ, đo được 111,3×42px.
       Anh Tiến chốt 29/08/2026: *"nới cả hai lên 44px"* — hai thứ ấy là `.ma-nut` và ô này, đúng
       hai vùng bấm dưới ngưỡng còn sót lại trên màn hình `/dien-thoai` (đo lần đầu ở đợt 1521).
       ⇒ Mục ③ của danh sách *"cố ý đứng ngoài"* ở đầu khối này đã được gỡ theo chỉ đạo ấy.

       ⚠ `min-height` chứ ⛔ không `height`: ô này là `inline-flex` bọc một chip ảnh cao 30px cộng đệm
         — đặt `height` là khoá cứng chiều cao, ngày chip đổi cỡ là ảnh bị cắt mà ⛔ không câu lỗi nào.
       ⚠ Độ đặc hiệu phải **bằng** luật gốc `.menu-co-dien .mcd-logo` (0,2,0) và đứng SAU nó trong
         cùng tệp ⇒ thắng theo thứ tự. Viết `.mcd-logo` trần (0,1,0) là luật ⛔ không bao giờ chạy. */
    .menu-co-dien .mcd-logo {
        min-height: var(--cham-toi-thieu);
    }
}

/* ═══ NÚT CHỮ TRÊN THANH NAV — NỚI QUA ĐÚNG BỘ BIẾN CỦA ĐỢT 976, ⛔ KHÔNG gõ số đè lên nó ═══
   Khối `--nav-nut-*` ở trên gom mọi nút chữ của thanh nav về một cỡ (2,05rem ≈ 32,8px) và dặn
   *"sửa cỡ thì sửa BIẾN"*. Trên thiết bị chạm, 32,8px là dưới ngưỡng ngón tay ⇒ đặt lại **đúng
   cái biến ấy**. Làm thế thì cả ba nhóm nút (dải TRANG của app · dải tab trong trang · chuông) đi
   cùng một nhịp, và hai tệp CSS **cách ly** vốn trỏ vào biến cũng tự đúng theo — ⛔ không tệp nào
   phải sửa. Gõ một luật `min-height` mới ở đây thì hai tệp cách ly ấy vẫn giữ 2,05rem và **thắng**
   (chúng nạp SAU app.css), tức bản vá trông có mà ⛔ không ăn.
   🚧 `NutThanhNavDongNhatTests` canh việc các tệp cách ly phải TRỎ VÀO biến — nó ⛔ không khoá giá
     trị, nên khối này hợp khuôn. */
@media (pointer: coarse) {
    :root {
        --nav-nut-cao: var(--cham-toi-thieu);
    }
}

/* ═══════════ ⭐ ĐỢT 1831 (việc o1090) — HUY HIỆU DÀI PHẢI XUỐNG DÒNG Ở KHUNG HẸP ═══════════

   📏 ĐO SỐNG TRƯỚC KHI SỬA (Chrome thật, khung 390×844, chế độ cảm ứng, đăng nhập tienvm, có chốt
     tiền đề `d-flex` nên CSS đã nạp thật — `node cong-cu/do-song-khung-hep.mjs --trang
     /nghi-phep/tong-hop-nam`): trang `/nghi-phep/tong-hop-nam` ra `scrollWidth` **489 trên khung
     390 ⇒ TRÀN NGANG 99px**, tức người dùng điện thoại phải kéo ngang CẢ TRANG để đọc.
     Thủ phạm đo được đích danh là **MỘT thẻ**: `span.badge.text-bg-primary.fs-6` rộng **477px**,
     mép phải đúng 489 — huy hiệu «Lưới 12 tháng đang hiển thị theo: BẢN GỐC phòng Tổ chức».
     ⛔ ⛔ KHÔNG phải lỗi của bảng 20 cột: bảng đã nằm trong khung cuộn ngang riêng và góp **0 pixel**
     vào `scrollWidth` của trang.

   🔑 GỐC BỆNH — `.badge` của Bootstrap 5.3 đặt `white-space: nowrap` ở **(0,1,0)** (đọc tận tay
     trong `wwwroot/bootstrap/bootstrap.min.css`). Huy hiệu vì thế **⛔ không xuống dòng được dù
     hết chỗ**, mà câu chữ trong nó lại do dữ liệu sinh ra nên dài ngắn tuỳ ngày.

   🔴 VÌ SAO ĐẶT Ở ĐÂY, MỘT LUẬT CHUNG — ⛔ đừng đi vá từng trang: huy hiệu kiểu này có ở khắp các
     phân hệ, và câu chữ trong chúng **SINH RA THEO DỮ LIỆU** (số phòng, số người, tên phạm vi).
     Vá một trang là để nguyên quả mìn ở mọi trang còn lại, và nó nổ vào đúng ngày dữ liệu dài thêm
     một chữ — ⛔ không câu báo nào, chỉ là một trang phải kéo ngang.

   ⚠ VÌ SAO ĐIỀU KIỆN LÀ **BỀ NGANG**, ⛔ không phải `(pointer: coarse)`: đây là bài toán **BỐ CỤC**
     (hết chỗ nên tràn), ⛔ không phải bài toán ngón tay. Cửa sổ thu nhỏ trên máy tính cũng tràn y
     hệt, mà ở đó `pointer` vẫn là `fine` ⇒ dùng câu media của vùng chạm là vá trượt đúng nửa số ca.
     ⓘ Đây là chiều NGƯỢC của luật vùng chạm đợt 1518 ở khối trên — ⛔ đừng đọc hai khối thành mâu thuẫn:
     vùng chạm hỏi *"cái gì chạm vào"*, tràn ngang hỏi *"còn bao nhiêu chỗ"*.

   🔒 CHỐT NGƯỢC BẮT BUỘC: mốc 991.98px chọn để **máy tính để bàn ⛔ KHÔNG đổi một pixel** — 433 tài
     khoản phần lớn ngồi màn ≥ 1200px (đúng lời hứa của quy tắc #18 về rủi ro lan truyền). Đổi mốc
     này thì phải đo lại cả hai phía.

   ⚠ `max-width: 100%` đi KÈM, ⛔ đừng bỏ: cho xuống dòng mà ⛔ không chặn trần thì một chuỗi LIỀN
     ⛔ không có dấu cách (mã hồ sơ, tên tệp) vẫn đẩy huy hiệu rộng hơn cha nó — tràn y như cũ.
     Cặp `white-space: normal` + `max-width: 100%` mới đóng kín được cả hai ca. */
@media (max-width: 991.98px) {
    .badge {
        white-space: normal;
        max-width: 100%;
    }
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   ⭐ ĐỢT 1868 (việc o1124) — TIÊU ĐỀ TRANG: MỘT KHUÔN ĐỒ HỌA DÙNG CHUNG CHO TOÀN APP
   ═══════════════════════════════════════════════════════════════════════════════════════════════

   Anh Tiến 06/09/2026: *"tiếp tục phát triển đồ họa, chủ đề của toàn app"*. Đợt 1577 (việc o882)
   dựng ngôn ngữ đồ họa cho TRANG CHỦ + hai màn đăng nhập rồi dừng ở đó; khối này đưa nó ra phần
   còn lại của phần mềm, qua thứ mọi trang đều có: TIÊU ĐỀ.

   📏 HIỆN TRẠNG ĐO SỐNG 06/09/2026 (Chrome thật, khổ 1400×900, 14 trang, đã đăng nhập `tienvm`):
     cùng một thứ — tên của trang — đang in ra ở **BA cỡ chữ**: 28px ở 48 trang (`.h3`), 24px ở 7
     trang (`.h4`), **40px** ở 5 trang (thẻ `h1` trần). Độ đậm 500, màu #212529 phẳng, ⛔ không một
     nét đồ họa nào. Đây đúng loại lệch anh Tiến đã bắt ở nút thanh nav (đợt 976: *"mỗi app lại có
     một kiểu nút khác nhau"*).

   🔴 ⛔ TUYỆT ĐỐI KHÔNG DÙNG MẶT CHỮ CHUYỂN SẮC (`background-clip: text`) Ở ĐÂY — ĐÃ ĐO, ĐÃ BÁC BỎ.
     Đó là thứ đợt 1577 dùng cho `.dn-tieu-de` và cho con số ở trang chủ, nên chép sang đây là
     phản xạ đầu tiên. Nhưng **77/98 tiêu đề trang mở đầu bằng một EMOJI** (`🗺 Mục lục trang` ·
     `⚙ Cài đặt chấm công` · `🚪 Quản lý ra vào`…), mà luật cắt nền **cắt luôn glyph ấy**: đo sống
     trên `/muc-luc` ngày 06/09 — chữ ra dải màu đúng ý, còn con emoji thành **một khối đen đặc**.
     ⓘ Đã thử lối gỡ *"trả `::first-letter` về mặt chữ thường"* và **ĐO LẠI: ⛔ KHÔNG cứu được** —
       emoji vẫn là khối đen. ⇒ Đừng thử lại lối đó, nó đã được trả giá bằng một lượt đo.
     ⇒ Chính chú thích của đợt 1577 đã dặn trước điều này (*"bọc RIÊNG con số vì thẻ ngoài còn
       chứa biểu tượng cảm xúc mà `background-clip: text` sẽ cắt cả glyph ấy"*) — ở đây ⛔ không có
       thẻ bọc riêng nào để mà bọc, vì chữ nằm thẳng trong `<h1>` của 98 trang.
     🚧 Hàng rào `TieuDeTrangDongNhatTests` + bộ đo sống canh **cả chiều này**: bật mặt chữ chuyển
       sắc lên tiêu đề trang là ĐỎ, ⛔ không phải "thêm cho đẹp".

   🔑 VÌ SAO ⛔ KHÔNG ĐẶT `color`: hôm nay tiêu đề **thừa kế** màu chữ của thân trang, mà màu ấy đã
     đúng sẵn ở **cả bốn** giao diện lẫn mọi chủ đề. Gõ một `color` vào đây là tự nhận trách nhiệm
     khai lại bốn nhánh, và quên một nhánh là chữ sẫm trên nền sẫm — ⛔ không câu lỗi nào (đúng ca
     `data-bs-theme="dark"` mà đợt 1562 đã bác bỏ ở thẻ đăng nhập). ⇒ Đồ họa ở đây nằm ở **DÁNG
     CHỮ và VẠCH NHẤN**, ⛔ không ở màu chữ; nhờ vậy khối này ⛔ không đổi một con số tương phản nào.

   🔴 ⛔ KHÔNG HOẠT HỌA. Đây là luật của đợt 1577 và nó áp cho mọi trang LÀM VIỆC: trang chủ và các
     trang nghiệp vụ được mở hàng chục lượt mỗi ngày, suốt tám tiếng, cho 433 tài khoản — một vệt
     sáng chạy vô tận ngay trên tiêu đề là thứ kéo mắt cả ngày. Hiệu ứng dựng lên chỉ dành cho hai
     màn ĐĂNG NHẬP, nơi người ta nhìn vài giây rồi đi.

   ⚠ BA NGOẠI LỆ, VIẾT ĐÍCH DANH — ⛔ đừng đổi sang mẫu dò kiểu `[class*="-tieu-de"]`: đó là đoán
     theo cách đặt tên, mà cách đặt tên thì đổi được trong im lặng (bài học 740 · 804).
       · `.dn-tieu-de`  — tiêu đề thẻ đăng nhập, ĐÃ có mặt chữ chuyển sắc riêng ở đợt 1577;
       · `.gbm-tieu-de` — màn gửi biểu mẫu từ xa, tự khai khuôn riêng;
       · `.text-truncate` — tên tệp đang xem ở `/trinh-chieu` (`XemTep.razor`), nằm trong một dải
         chật có `max-width: 60vw`; ép nó to lên là đẩy dải vỡ hàng.
     ⓘ `.xh-tieu-de` (màn xếp hàng) ⛔ không cần khai: trang ấy dùng `LayoutTrong` nên ⛔ không nằm
       trong `.content` — nhưng ⛔ đừng vì thế mà đổi bộ chọn gốc sang `body h1`.

   🔴 VẠCH NHẤN CHỈ MỌC Ở TIÊU ĐỀ **ĐỨNG RIÊNG** (`.content > h1`), ⛔ KHÔNG ở tiêu đề nằm trong một
     dải flex cùng hàng với nút bấm (`.content > div > h1`, ví dụ `/cong-viec`). Vạch cần chỗ ở
     dưới ⇒ thẻ `h1` cao thêm ⇒ cả dải cao theo và nút bên cạnh lệch trục. Đo 06/09: 60 tiêu đề
     đứng riêng · 37 nằm trong dải. Bộ đo sống canh **chiều ngược** này, ⛔ không phải "càng nhiều
     chỗ có vạch càng tốt".

   ⚠ Sửa cỡ / độ đậm / vạch thì **SỬA BIẾN**, ⛔ đừng gõ số vào từng chỗ — đúng khuôn `--nav-nut-*`
     của đợt 976. Đây là con đường DUY NHẤT để 98 trang thôi trôi mỗi trang một kiểu. */
:root {
    --tdt-co: 1.75rem;          /* 28px — đúng cỡ `.h3`, tức cỡ 48/60 trang đang dùng ⇒ đa số trang
                                   ⛔ không đổi một pixel; chỉ 12 trang lệch được kéo về hàng */
    --tdt-dam: 600;
    --tdt-dan-chu: -.015em;     /* siết nhẹ — cỡ càng lớn thì khe chữ mặc định càng rộng ra */
    --tdt-vach-cao: 3px;
    --tdt-vach-rong: 2.75rem;
    --tdt-vach-cach: .45rem;
}

/* ① KHUÔN CHỮ — áp cho MỌI tiêu đề trang, kể cả tiêu đề nằm trong dải flex. */
.content h1:not(.dn-tieu-de):not(.gbm-tieu-de):not(.text-truncate) {
    font-size: var(--tdt-co);
    font-weight: var(--tdt-dam);
    letter-spacing: var(--tdt-dan-chu);
}

/* ② VẠCH NHẤN THƯƠNG HIỆU — chỉ tiêu đề ĐỨNG RIÊNG (xem khối 🔴 ở trên).
   Cam `--bm-phu-nen` khai GIỐNG NHAU ở cả bốn giao diện của bộ mặc định nên giá trị lui #f47b20
   là đúng tuyệt đối, ⛔ không phải một phỏng đoán. Vạch là TRANG TRÍ ⇒ ⛔ không có ngưỡng tương
   phản nào phải đạt, cùng lẽ với `--bm-vach-dai` của thanh nav (đợt 1340). */
.content > h1:not(.dn-tieu-de):not(.gbm-tieu-de):not(.text-truncate) {
    position: relative;
    padding-bottom: calc(var(--tdt-vach-cach) + var(--tdt-vach-cao));
}

.content > h1:not(.dn-tieu-de):not(.gbm-tieu-de):not(.text-truncate)::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: 0;
    width: var(--tdt-vach-rong);
    height: var(--tdt-vach-cao);
    border-radius: calc(var(--tdt-vach-cao) / 2);
    background-image: linear-gradient(90deg,
        var(--bm-phu-nen, #f47b20) 0%,
        var(--bm-phu-nen, #f47b20) 58%,
        var(--bm-chinh, #0a3d91) 100%);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   📱 ĐỢT 2197 (việc o1639) — DẢI NÚT `.btn-group` PHẢI XUỐNG DÒNG Ở KHỔ ĐIỆN THOẠI (luật CHUNG)

   📏 ĐO SỐNG 375×812 trên bản chạy 5270 trước khi sửa (`node cong-cu/do-song-dai-nut-nhom.mjs
      --trang du-an`): dải lọc trạng thái của `/du-an` đo **357px trong khung cha 351px** ⇒
      **1 nút bị cắt mất** ở mép phải, sáu nút bị ép còn 44px, chữ gãy **2 dòng** nên nút cao
      **54,4px**. Dải lọc loại ngay trên cũng gãy chữ 2 dòng.
      ⚠ Bề ngang TÀI LIỆU vẫn đúng 375px ⇒ bộ đo `do-song-khung-hep.mjs` báo *"0 tràn ngang"* mà
      màn hình vẫn mất một nút: `.btn-group` là `inline-flex` **KHÔNG cho xuống dòng**, phần vượt
      mép bị khối cha cắt đi chứ ⛔ không đẩy trang rộng ra. ⇒ Đo dải nút, ⛔ đừng đo bề ngang trang.

   🔑 CHO XUỐNG DÒNG chứ ⛔ KHÔNG cho cuộn ngang: cuộn thì các nút cuối biến mất mà ⛔ không có dấu
      hiệu nào bảo người ta còn nút phía sau (khuôn đợt 2194 cho `/bao-cao/nang-suat`).

   🔴 VÌ SAO ĐẶT Ở ĐÂY CHỨ ⛔ KHÔNG CHÉP VÀO TỪNG TRANG: đo 08/09/2026 — app đang có **80 dải
      `.btn-group` ở 34 tệp `.razor`**. Chép khuôn vào từng tệp là 34 bản sao cùng một luật, mà
      mỗi bản lại thiếu đúng nửa dưới (xem ngay dưới đây).

   🩸 SỐ ĐO BÁC BỎ MỘT NỬA BẢN VÁ ĐỢT 2194 — CHỖ DỄ CHÉP NHẦM NHẤT. Khuôn ở
      `BaoCaoNangSuat.razor.css` viết `margin-left: 0` + `border-radius` trong CSS **cách ly**;
      đo sống trang ấy ngày 08/09 cho thấy `flex-wrap: wrap` CHẠY (6 nút, 2 hàng) nhưng
      **5/6 nút vẫn lề âm và vẫn thiếu bo góc** — tức nửa dưới của bản vá ⛔ chưa bao giờ chạy.
      🔑 Gốc: Bootstrap 5.3 đặt hai thứ ấy ở **(0,4,0)**
      (`.btn-group > :not(.btn-check:first-child) + .btn` · `.btn-group > .btn:not(:last-child)
      :not(.dropdown-toggle)`), còn CSS cách ly biên dịch ra `.btn-group > .btn[b-…]` chỉ
      **(0,3,0)** ⇒ THUA. Build xanh · test xanh · dải vẫn xuống dòng nên trông như đã vá xong.
      ⇒ Ở đây ghi đè **ĐÚNG BỘ CHỌN CỦA BOOTSTRAP** (cùng (0,4,0), mà `app.css` nạp SAU
      `bootstrap.min.css` nên thắng theo thứ tự) — ⛔ không dùng `!important`: Bootstrap ⛔ không
      cầm `!important` ở chỗ này, nên đôn độ đặc hiệu là đủ và ⛔ không chặn đường trang nào muốn
      khai riêng.

   🔒 Chỉ trong `max-width: 576px` — mốc điện thoại chung của dự án
      (`LuatHaiCotDanhSachTrangChu.KhoDienThoaiToiDaPx`). Máy tính ⛔ không đổi một pixel.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */
@media (max-width: 576px) {
    .btn-group {
        flex-wrap: wrap;
        gap: .35rem;
    }

    /* Đã tách các nút bằng `gap` thì mỗi nút là một viên RIÊNG ⇒ phải bo đủ bốn góc.
       Bộ chọn chép đúng của Bootstrap để đứng cùng (0,4,0) — xem khối chú thích trên. */
    .btn-group > .btn:not(:last-child):not(.dropdown-toggle),
    .btn-group > .btn.dropdown-toggle-split:first-child,
    .btn-group > .btn:nth-child(n+3),
    .btn-group > :not(.btn-check) + .btn,
    .btn-group > .btn-group:not(:last-child) > .btn,
    .btn-group > .btn-group:not(:first-child) > .btn {
        border-radius: var(--bs-border-radius);
    }

    /* Lề âm là cách Bootstrap cho hai nút chồng viền lên nhau; có khe rồi thì nó thành
       các nút chồng mép. */
    .btn-group > :not(.btn-check:first-child) + .btn,
    .btn-group > .btn-group:not(:first-child) {
        margin-left: 0;
    }
}

/* ═══ ĐỢT 2432 (việc o1994) — Ô CHỌN LỌC MANG TÊN Ô Ở DÒNG RỖNG ĐẦU TIÊN, TÔ XÁM NHƯ CHỮ CHÌM ═══
   Anh Tiến 13/09/2026: «ở nhóm quản trị, các ô lọc đưa hết label vào chữ chìm của ô lọc, gom tất cả ô lọc
   thành 1 hàng». Ô gõ thì có placeholder; ô CHỌN thì không, nên tên ô («Phòng — tất cả») nằm ở dòng
   value rỗng đầu tiên. Lớp o-loc-chim tô dòng ấy XÁM giống chữ chìm của ô gõ, CHỈ lúc chưa chọn gì;
   chọn một phòng thì chữ về màu thường. Luật thứ hai giữ màu thường cho danh sách xổ xuống: Chrome và
   Edge trên Windows cho thẻ option thừa kế màu của ô chọn, thiếu luật ấy là cả danh sách xám theo.
   Chiều fail đúng: trình duyệt không hiểu :has thì bỏ cả luật, ô chọn giữ màu chữ thường. */
.form-select.o-loc-chim:has(> option[value=""]:checked) {
    color: var(--bs-secondary-color, #6c757d);
}

.form-select.o-loc-chim option {
    color: var(--bs-body-color, #212529);
}
