@font-face {
  font-family: 'Google Sans Flex'; font-style: normal; font-weight: 300 800; font-display: swap;
  src: url('/assets/fonts/qq-skkn-gsf-vn.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Google Sans Flex'; font-style: normal; font-weight: 300 800; font-display: swap;
  src: url('/assets/fonts/qq-skkn-gsf-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215;
}
@font-face {
  font-family: 'Newsreader'; font-style: italic; font-weight: 400 600; font-display: swap;
  src: url('/assets/fonts/qq-skkn-newsreader-it-vn.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Newsreader'; font-style: italic; font-weight: 400 600; font-display: swap;
  src: url('/assets/fonts/qq-skkn-newsreader-it-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122;
}
@font-face {
  font-family: 'Geist Mono'; font-style: normal; font-weight: 400; font-display: swap;
  src: url('/assets/fonts/qq-skkn-geistmono-regular.woff2') format('woff2');
}

:root {
  /* A1 — Bảng màu (Tone Xanh Học Thuật), nguồn 05.3.qq-design-system.md */
  --color-primary: #004780;
  --color-primary-rgb: 0, 71, 128;
  --color-primary-hover: #003660;
  --color-primary-fg: var(--color-primary); /* text/icon/viền — đổi riêng ở dark, --color-primary GIỮ NGUYÊN mọi theme (chỉ dùng làm nền nút, luôn kèm chữ trắng) */
  --color-heading: var(--color-primary); /* h1-h4 dùng biến này, KHÔNG dùng --color-text-primary trực tiếp */
  --color-secondary: #f0f4f8;
  --color-bg-primary: #ffffff;
  --color-bg-secondary: #f5f8fa;
  --color-bg-tertiary: #e6eff7;
  --color-sidebar-bg: #eef3f8;
  --color-bg-dark: #0a131c;
  --color-text-primary: #0a131c;
  --color-text-secondary: #202f3e;
  --color-text-muted: #506275;
  --color-text-min: #a0b2c6; /* CHỈ divider, KHÔNG BAO GIỜ dùng cho chữ ở bất kỳ theme nào — 2.17:1, fail AA */
  --color-border: #d0dfed;
  --color-accent-academic: #1d70b8;
  --color-accent-sky: #7eb1d6;
  --color-focus-ring: var(--color-accent-academic);
  --color-glass-bg: rgba(255, 255, 255, 0.82);
  --color-glass-border: rgba(var(--color-primary-rgb), 0.08);

  --color-warning-bg: #fbf3e2; --color-warning-text: #8a5a12; --color-warning-rgb: 138, 90, 18;
  --color-info-bg: #e3ecf5;    --color-info-text: #155a94;
  --color-danger-bg: #fdf2f2;  --color-danger-text: #c53030;
  --color-success-bg: #e5f3ec; --color-success-text: #1a7a4c;

  --shadow-card: 0 40px 90px -40px rgba(var(--color-primary-rgb), 0.03);
  --shadow-btn-primary: 0 10px 24px -10px rgba(var(--color-primary-rgb), 0.5);
  --radius-pill: 9999px;
  --radius-card-outer: 22px;
  --radius-card-inner: 14px;
  --radius-data: 10px; /* input/select/table — KHÔNG dùng pill */
}

@media (prefers-color-scheme: dark) {
  /* :not([data-theme="light"]) — nếu người dùng đã CHỦ ĐỘNG chọn theme sáng (nút toggle ghi
     data-theme="light" vào <html>), phải thắng tuyệt đối sở thích Dark Mode của hệ điều hành.
     Thiếu điều kiện loại trừ này, khối dưới vẫn khớp (chỉ dựa vào prefers-color-scheme, không
     biết gì về lựa chọn thủ công) và ĐÈ các biến chỉ được định nghĩa lại ở đây mà KHÔNG được
     định nghĩa lại trong :root[data-theme="light"] (điển hình: --color-heading) — hậu quả là
     toàn bộ h1-h4 và mọi chỗ dùng --color-heading mất màu xanh thương hiệu, ngả xám/đen dù
     giao diện đang hiển thị theme sáng, chỉ vì máy người dùng bật Dark Mode ở hệ điều hành. */
  :root:not([data-theme="light"]) {
    --color-primary-hover: #0a5a9c; --color-primary-fg: #9cc9e8; --color-primary-rgb: 126, 177, 214;
    --color-secondary: #16273a; --color-bg-primary: #0a131c; --color-bg-secondary: #101c29;
    --color-bg-tertiary: #16273a; --color-sidebar-bg: #142231; --color-bg-dark: #000000; --color-text-primary: #eef3f8;
    --color-heading: var(--color-text-primary); --color-text-secondary: #dbe7f0; --color-text-muted: #aec2d4;
    --color-text-min: #35516e; --color-border: #24374d; --color-accent-academic: #4d9bd6;
    --color-warning-bg: #3a2c10; --color-warning-text: #e8b559; --color-warning-rgb: 232, 181, 89;
    --color-info-bg: #142a3d;   --color-info-text: #9cc9e8;
    --color-danger-bg: #3a1616; --color-danger-text: #ef7272;
    --color-success-bg: #123625; --color-success-text: #4ad991;
    --shadow-card: 0 40px 90px -40px rgba(0, 0, 0, 0.5);
    --shadow-btn-primary: 0 10px 24px -10px rgba(0, 0, 0, 0.6);
    --color-glass-bg: rgba(10, 19, 28, 0.75); --color-glass-border: rgba(255, 255, 255, 0.07);
  }
}
:root[data-theme="dark"] { 
    --color-primary-hover: #0a5a9c; --color-primary-fg: #9cc9e8; --color-primary-rgb: 126, 177, 214;
    --color-secondary: #16273a; --color-bg-primary: #0a131c; --color-bg-secondary: #101c29;
    --color-bg-tertiary: #16273a; --color-sidebar-bg: #142231; --color-bg-dark: #000000; --color-text-primary: #eef3f8;
    --color-heading: var(--color-text-primary); --color-text-secondary: #dbe7f0; --color-text-muted: #aec2d4;
    --color-text-min: #35516e; --color-border: #24374d; --color-accent-academic: #4d9bd6;
    --color-warning-bg: #3a2c10; --color-warning-text: #e8b559; --color-warning-rgb: 232, 181, 89;
    --color-info-bg: #142a3d;   --color-info-text: #9cc9e8;
    --color-danger-bg: #3a1616; --color-danger-text: #ef7272;
    --color-success-bg: #123625; --color-success-text: #4ad991;
    --shadow-card: 0 40px 90px -40px rgba(0, 0, 0, 0.5);
    --shadow-btn-primary: 0 10px 24px -10px rgba(0, 0, 0, 0.6);
    --color-glass-bg: rgba(10, 19, 28, 0.75); --color-glass-border: rgba(255, 255, 255, 0.07);
}
:root[data-theme="light"] {
  --color-primary-hover: #003660; --color-primary-fg: var(--color-primary); --color-primary-rgb: 0, 71, 128;
  --color-secondary: #f0f4f8; --color-bg-primary: #ffffff; --color-bg-secondary: #f5f8fa;
  --color-bg-tertiary: #e6eff7; --color-sidebar-bg: #eef3f8; --color-bg-dark: #0a131c; --color-text-primary: #0a131c;
  --color-text-secondary: #202f3e; --color-text-muted: #506275; --color-text-min: #a0b2c6;
  --color-border: #d0dfed; --color-accent-academic: #1d70b8;
  --color-warning-bg: #fbf3e2; --color-warning-text: #8a5a12; --color-warning-rgb: 138, 90, 18;
  --color-info-bg: #e3ecf5;   --color-info-text: #155a94;
  --color-danger-bg: #fdf2f2; --color-danger-text: #c53030;
  --color-success-bg: #e5f3ec; --color-success-text: #1a7a4c;
  --shadow-card: 0 40px 90px -40px rgba(var(--color-primary-rgb), 0.03);
  --shadow-btn-primary: 0 10px 24px -10px rgba(var(--color-primary-rgb), 0.5);
  --color-glass-bg: rgba(255, 255, 255, 0.82); --color-glass-border: rgba(var(--color-primary-rgb), 0.08);
}

/* Base Reset & Typography */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
    font-family: 'Google Sans Flex', system-ui, -apple-system, sans-serif;
    background-color: var(--color-bg-secondary);
    color: var(--color-text-primary);
    line-height: 1.625;
    font-size: 16px;
    -webkit-font-smoothing: antialiased;
}
@media (max-width: 768px) {
    body { font-size: 14px; }
}

h1, h2, h3, h4, h5, h6 {
    color: var(--color-heading);
    margin-bottom: 0.5em;
}
h1 { font-size: clamp(24px, 2.4vw, 30px); font-weight: 700; line-height: 1.2; letter-spacing: -0.01em; }
h2 { font-size: clamp(20px, 2vw, 24px); font-weight: 700; line-height: 1.25; }
h3 { font-size: 18px; font-weight: 600; }
h4 { font-size: 16px; font-weight: 600; }
.qq-skkn-hero-title { font-size: clamp(38px, 6vw, 72px); font-weight: 800; line-height: 1.02; letter-spacing: -0.04em; }
.qq-skkn-accent-text {
    font-family: 'Newsreader', serif;
    font-style: italic;
    font-weight: 400;
}
.qq-skkn-numeric {
    font-family: 'Geist Mono', monospace;
    font-variant-numeric: tabular-nums;
}

/* Utilities */
.qq-skkn-text-center { text-align: center; }
.qq-skkn-text-muted { color: var(--color-text-muted); }
.qq-skkn-text-primary { color: var(--color-text-primary); }
.qq-skkn-mb-3 { margin-bottom: 1rem; }
.qq-skkn-mb-4 { margin-bottom: 1.5rem; }
.qq-skkn-mt-4 { margin-top: 1.5rem; }

/* SVG Icon */
.qq-skkn-icon {
    width: 24px;
    height: 24px;
    display: inline-block;
    vertical-align: middle;
}
.qq-skkn-icon-sm { width: 18px; height: 18px; }

/* Buttons */
.qq-skkn-btn {
    display: inline-flex; align-items: center; justify-content: center;
    gap: 8px;
    white-space: nowrap;   /* MỚI — chặn chữ trong nút xuống dòng */
    padding: 10px 20px;
    border-radius: var(--radius-pill);
    font-family: 'Google Sans Flex', sans-serif;
    font-weight: 600;
    font-size: 15px;
    cursor: pointer;
    border: none;
    text-decoration: none;
    transition: all 0.2s ease;
}
.qq-skkn-btn:active {
    transform: scale(0.96);
}
.qq-skkn-btn-primary {
    background-color: var(--color-primary);
    color: #ffffff;
    box-shadow: var(--shadow-btn-primary);
}
.qq-skkn-btn-primary:hover {
    background-color: var(--color-primary-hover);
    color: #ffffff;
}
.qq-skkn-btn-secondary {
    background-color: var(--color-bg-primary);
    color: var(--color-text-primary);
    border: 1px solid var(--color-border);
}
.qq-skkn-btn-secondary:hover {
    background-color: var(--color-secondary);
    transform: translateY(-1px);
}

@keyframes pulse-glow {
    0% { box-shadow: 0 0 0 0 rgba(var(--color-primary-rgb), 0.4); }
    70% { box-shadow: 0 0 0 10px rgba(var(--color-primary-rgb), 0); }
    100% { box-shadow: 0 0 0 0 rgba(var(--color-primary-rgb), 0); }
}
.qq-skkn-btn-pulse {
    animation: pulse-glow 2s infinite;
}

/* Biến thể CẢNH BÁO của qq-skkn-btn-pulse (nhẫn quầng màu warning thay vì primary) — dùng cho hành
   động BẮT BUỘC phải làm ngay vì bị Hội đồng/Ban thư ký yêu cầu chỉnh sửa (VD nút "Nộp bổ sung" ở
   HoSo/Views/detail.php khi trang_thai_tiep_nhan = 'can_bo_sung'), nổi bật hơn hẳn nút hành động
   thường — theo yêu cầu người dùng. Nền/viền solid màu warning (giống cách các nút màu riêng khác
   trong dự án dùng inline style qua biến --color-warning-*, xem "Tải file đính kèm" ở Dot/Views/
   detail.php) — không chỉ viền pulse mà cả khối nút phải nổi bật ngay từ màu nền. */
@keyframes pulse-glow-warning {
    0% { box-shadow: 0 0 0 0 rgba(var(--color-warning-rgb), 0.55); }
    70% { box-shadow: 0 0 0 12px rgba(var(--color-warning-rgb), 0); }
    100% { box-shadow: 0 0 0 0 rgba(var(--color-warning-rgb), 0); }
}
.qq-skkn-btn-pulse-warning {
    background-color: var(--color-warning-text);
    color: var(--color-warning-bg);
    animation: pulse-glow-warning 1.6s infinite;
}
.qq-skkn-btn-pulse-warning:hover {
    background-color: var(--color-warning-text);
    color: var(--color-warning-bg);
    filter: brightness(0.92);
}

/* Badges / Pills */
.qq-skkn-pill {
    display: inline-flex; align-items: center; gap: 6px;
    white-space: nowrap;   /* MỚI — chặn chữ trong pill xuống dòng */
    padding: 4px 12px;
    border-radius: var(--radius-pill);
    font-size: 13px;
    font-weight: 600;
}
.qq-skkn-pill-grade {
    min-width: 120px;
    justify-content: center;
}
.qq-skkn-pill-top {
    background-color: var(--color-primary);
    color: #ffffff;
}
.qq-skkn-pill-success {
    background-color: var(--color-success-bg);
    color: var(--color-success-text);
}
.qq-skkn-pill-warning {
    background-color: var(--color-warning-bg);
    color: var(--color-warning-text);
}
.qq-skkn-pill-danger {
    background-color: var(--color-danger-bg);
    color: var(--color-danger-text);
}
.qq-skkn-pill-info {
    background-color: var(--color-info-bg);
    color: var(--color-info-text);
}
.qq-skkn-pill-neutral {
    background-color: var(--color-bg-tertiary);
    color: var(--color-text-secondary);
}

/* Badge/pill tự đặt style riêng (không dùng class .qq-skkn-pill dùng chung) rải rác ở nhiều View — cùng
   thiếu white-space: nowrap nên chữ bị xuống dòng giữa chừng khi cột bảng hẹp. Sửa chung 1 lần bằng attribute
   selector thay vì sửa lẻ từng file. */
[style*="border-radius: var(--radius-pill)"] {
    white-space: nowrap;
}

/* Cards */
.qq-skkn-card {
    background-color: var(--color-bg-primary);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-card-outer);
    box-shadow: var(--shadow-card);
    padding: 24px;
    transition: all 0.25s ease;
}
.qq-skkn-card-hover:hover {
    transform: translateY(-3px);
    border-color: rgba(var(--color-primary-rgb), 0.2);
    box-shadow: 0 20px 40px -10px rgba(var(--color-primary-rgb), 0.1);
}

/* Navbar */
.qq-skkn-nav {
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    background-color: var(--color-glass-bg);
    border-bottom: 1px solid var(--color-glass-border);
    position: sticky;
    top: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 24px;
}
.qq-skkn-nav-logo {
    display: flex; align-items: center; gap: 12px;
    text-decoration: none;
    color: var(--color-heading);
    font-weight: 800;
    font-size: 1.1rem;
}
.qq-skkn-nav-logo img {
    height: 36px;
    width: auto;
}
.qq-skkn-nav-actions {
    display: flex; align-items: center; gap: 16px;
}
.qq-skkn-mobile-menu-btn {
    display: none;
    background: none; border: none; cursor: pointer; color: var(--color-text-primary);
}
/* TASK-46 MỤC 1b — mặc định (desktop lẫn mobile > 480px) hiện chữ đầy đủ "Đăng xuất", chỉ đổi thành
   "Thoát" ở @media (max-width: 480px) phía dưới. */
.qq-skkn-logout-text-short {
    display: none;
}
/* TASK-46 MỤC 1d — ẩn hẳn trên desktop mặc định, chỉ hiện lại ở @media (max-width: 900px) phía dưới
   (3 icon phụ tương ứng đã có sẵn ở topbar cho desktop, không lặp lại trong sidebar). */
.qq-skkn-sidebar-mobile-only {
    display: none;
}

/* Sidebar Layout */
.qq-skkn-layout {
    display: flex;
    min-height: 100vh;
}
.qq-skkn-sidebar {
    width: 260px;
    background-color: var(--color-sidebar-bg);
    border-right: 1px solid var(--color-border);
    transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 0;
    height: 100vh;
    overflow-y: auto;
    z-index: 90;
    overflow-x: hidden;
}
.qq-skkn-sidebar.collapsed {
    width: 72px;
}
.qq-skkn-sidebar-header {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding: 16px;
    border-bottom: 1px solid var(--color-border);
}
.qq-skkn-sidebar.collapsed .qq-skkn-sidebar-header {
    justify-content: center;
}
.qq-skkn-sidebar-content {
    padding: 16px 0;
}
.qq-skkn-sidebar-group {
    margin-bottom: 16px;
}
.qq-skkn-sidebar-title {
    font-size: 11px;
    text-transform: uppercase;
    font-weight: 700;
    color: var(--color-text-muted);
    letter-spacing: 0.5px;
    padding: 0 20px;
    margin-bottom: 8px;
    white-space: nowrap;
}
.qq-skkn-sidebar.collapsed .qq-skkn-sidebar-title {
    display: none;
}
.qq-skkn-sidebar-title-toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    cursor: pointer;
    user-select: none;
    /* To hơn + màu nổi bật hơn (--color-primary-fg — token đổi đúng theo theme sáng/tối, không
       dùng --color-primary thẳng vì biến đó giữ cố định mọi theme, dễ mất tương phản ở dark) so
       với 3 mục còn lại của .qq-skkn-sidebar-title dùng chung — chỉ áp cho 3 nhóm bấm được. */
    font-size: 13px;
    color: var(--color-primary-fg);
}
.qq-skkn-sidebar-title-toggle:hover {
    color: var(--color-primary-hover);
}
.qq-skkn-sidebar-chevron {
    font-size: 12px;
    line-height: 1;
    color: var(--color-primary-fg);
}
.qq-skkn-sidebar-link {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 20px;
    color: var(--color-text-secondary);
    text-decoration: none;
    font-weight: 500;
    transition: background 0.2s, color 0.2s;
    white-space: nowrap;
}
.qq-skkn-sidebar-link:hover {
    background-color: rgba(var(--color-primary-rgb), 0.05);
    color: var(--color-primary-fg);
}
.qq-skkn-sidebar-link.active {
    background-color: rgba(var(--color-primary-rgb), 0.1);
    color: var(--color-primary-fg);
    font-weight: 700;
    border-right: 3px solid var(--color-primary-fg);
}
.qq-skkn-sidebar-link .qq-skkn-icon {
    flex-shrink: 0;
}
.qq-skkn-sidebar-text {
    opacity: 1;
    transition: opacity 0.2s;
}
.qq-skkn-sidebar.collapsed .qq-skkn-sidebar-text {
    opacity: 0;
    display: none;
}
.qq-skkn-workspace {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
}
.qq-skkn-topbar {
    background-color: var(--color-bg-primary);
    border-bottom: 1px solid var(--color-border);
    position: sticky;
    top: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 24px;
    height: 57px;
}

/* Tables */
.qq-skkn-table-wrapper {
    overflow-x: auto;
    border-radius: var(--radius-data);
    border: 1px solid var(--color-border);
    background: var(--color-bg-primary);
}
.qq-skkn-table {
    width: 100%; border-collapse: collapse; text-align: left;
}
.qq-skkn-table th {
    background-color: var(--color-bg-secondary);
    color: var(--color-text-secondary);
    font-weight: 600;
    padding: 12px 16px;
    border-bottom: 1px solid var(--color-border);
}
.qq-skkn-table td {
    padding: 12px 16px;
    border-bottom: 1px solid var(--color-border);
    color: var(--color-text-primary);
    transition: background 0.2s ease;
}
.qq-skkn-table tr:last-child td { border-bottom: none; }
.qq-skkn-table tbody tr:hover td {
    background-color: var(--color-bg-secondary);
}

/* Forms */
.qq-skkn-field {
    width: 100%;
    padding: 10px 14px;
    border-radius: var(--radius-data);
    border: 1px solid var(--color-border);
    background-color: var(--color-bg-primary);
    color: var(--color-text-primary);
    font-family: inherit;
    font-size: 15px;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.qq-skkn-field:focus {
    outline: none;
    border-color: var(--color-primary-fg);
    box-shadow: 0 0 0 3px rgba(var(--color-primary-rgb), 0.15);
}

/* Toast */
.qq-skkn-toast-stack {
    position: fixed;
    top: 24px; right: 24px;
    z-index: 9999;
    display: flex; flex-direction: column; gap: 12px;
}
.qq-skkn-toast {
    background-color: var(--color-bg-primary);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-card-inner);
    box-shadow: var(--shadow-card);
    padding: 16px;
    width: 320px;
    transform: translateX(120%);
    transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    display: flex; gap: 12px; align-items: flex-start;
}
.qq-skkn-toast.show {
    transform: translateX(0);
}
.qq-skkn-toast-icon { color: var(--color-primary-fg); }
.qq-skkn-toast-content h4 { margin: 0 0 4px; font-size: 15px; }
.qq-skkn-toast-content p { margin: 0; font-size: 13px; color: var(--color-text-muted); }
.qq-skkn-toast-close {
    margin-left: auto; background: none; border: none; cursor: pointer; color: var(--color-text-muted);
}

/* Bells (Not used in Layout right now per rules, but prepared) */
@keyframes bell-ring {
    0% { transform: rotate(0); }
    10% { transform: rotate(15deg); }
    20% { transform: rotate(-15deg); }
    30% { transform: rotate(10deg); }
    40% { transform: rotate(-10deg); }
    50% { transform: rotate(5deg); }
    60% { transform: rotate(-5deg); }
    70% { transform: rotate(0); }
    100% { transform: rotate(0); }
}
.qq-skkn-bell-btn:hover .qq-skkn-icon { animation: bell-ring 1s ease; }

/* Grid icon cell from demo */
.qq-skkn-icon-cell {
    transition: all 0.2s ease;
    border-radius: var(--radius-card-inner);
    padding: 20px;
    text-align: center;
}
.qq-skkn-icon-cell:hover {
    transform: translateY(-2px);
    background-color: var(--color-bg-tertiary);
}

/* Animations */
@keyframes text-shimmer {
    0% { background-position: -200% center; }
    100% { background-position: 200% center; }
}
.text-shimmer {
    background: linear-gradient(90deg, var(--color-text-primary) 30%, var(--color-primary-fg) 50%, var(--color-text-primary) 70%);
    background-size: 200% auto;
    color: transparent;
    -webkit-background-clip: text;
    background-clip: text;
    animation: text-shimmer 3s infinite linear;
}

.reveal-on-scroll {
    opacity: 0; transform: translateY(20px);
    transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}
.reveal-on-scroll.revealed {
    opacity: 1; transform: translateY(0);
}

@keyframes skeleton-loading {
    0% { background-position: 100% 50%; }
    100% { background-position: 0 50%; }
}
.skeleton {
    background: linear-gradient(90deg, var(--color-bg-secondary) 25%, var(--color-border) 50%, var(--color-bg-secondary) 75%);
    background-size: 400% 100%;
    animation: skeleton-loading 1.5s ease-in-out infinite;
    border-radius: 4px;
}

/* Mobile Nav */
@media (max-width: 900px) {
    .qq-skkn-mobile-menu-btn { display: block; }
    .qq-skkn-layout {
        flex-direction: column;
    }
    .qq-skkn-sidebar {
        position: fixed;
        left: -260px;
        transition: left 0.3s ease;
        z-index: 1000;
        width: 260px; /* Force full width on mobile even if collapsed on desktop */
    }
    .qq-skkn-sidebar.collapsed {
        width: 260px;
    }
    .qq-skkn-sidebar.collapsed .qq-skkn-sidebar-text,
    .qq-skkn-sidebar.collapsed .qq-skkn-sidebar-title {
        display: block;
        opacity: 1;
    }
    .qq-skkn-sidebar.show {
        left: 0;
        box-shadow: 0 0 50px rgba(0,0,0,0.5);
    }
    .qq-skkn-workspace {
        width: 100%;
    }

    /* Chỉ giữ 1 nút mở menu duy nhất trên mobile (.qq-skkn-mobile-menu-btn ở topbar) —
       ẩn nút thu gọn sidebar (.qq-skkn-sidebar-toggle) vì nó chỉ có ý nghĩa trên desktop;
       để cả 2 cùng hiện gây rối mắt khi sidebar mở dạng overlay trên mobile. */
    .qq-skkn-sidebar-toggle {
        display: none;
    }

    /* Topbar/nav mobile: bớt phần tử phụ + siết khoảng cách/lề để hamburger và các nút
       thao tác chính (chuông, hồ sơ, đăng xuất) luôn có đủ chỗ hiển thị rõ ràng, không bị
       tràn/che khuất khi nhiều phần tử chen trong 1 hàng hẹp. Bắt buộc !important — TASK-38
       thêm style="display: inline-block" nội tuyến cho khối chữ 2 dòng ở Cổng công bố, mà
       style nội tuyến luôn thắng rule CSS ngoài nếu rule đó không có !important. */
    .qq-skkn-brand-text,
    .qq-skkn-user-name {
        display: none !important;
    }
    .qq-skkn-topbar,
    .qq-skkn-nav {
        padding-left: 12px;
        padding-right: 12px;
    }
    .qq-skkn-nav-actions {
        gap: 8px;
    }

        /* TASK-46 MỤC 1d — nhóm sidebar "Cổng công bố/Hướng dẫn/Sáng-Tối" chỉ dựng ra để thay thế 3 icon
       phụ bị ẩn ở topbar mobile — hiện đúng ở đây (≤900px, cùng breakpoint sidebar chuyển sang overlay),
       ẩn hẳn trên desktop (nơi 3 icon phụ đó đã có sẵn ở topbar, đừng lặp lại). */
    .qq-skkn-sidebar-mobile-only {
        display: block;
    }

/* TASK-46 MỤC 1c (L2, phiên kiểm thử 2026-08-29) — "lưới an toàn" bắt buộc kể cả khi MỤC 1b (ẩn
       icon phụ ở ≤480px) đã đủ chỗ: sau này ai thêm nút mới vào topbar thì nó XUỐNG DÒNG chứ không bao
       giờ trôi ra ngoài màn hình nữa (trước đây .qq-skkn-nav-actions không có flex-wrap, hàng luôn
       rộng ~390px bất kể viewport — nút ☰ bị đẩy ra ngoài viewport ở mọi máy < 375px, mất hẳn điều
       hướng mobile). .qq-skkn-topbar đổi height cố định 57px -> min-height, nếu không hàng thứ 2 (khi
       nav-actions xuống dòng) sẽ bị cắt mất vì topbar không tự cao lên theo nội dung. */
    .qq-skkn-nav-actions {
        flex-wrap: wrap;
        justify-content: flex-end;
    }
    .qq-skkn-topbar {
        height: auto;
        min-height: 57px;
    }

    /* Dropdown chuông thông báo định vị theo nút chuông (position: relative của thẻ cha) —
       trên màn hình hẹp, nút chuông không nằm sát mép phải nên hộp rộng 320px cố định dễ
       tràn ra ngoài viewport. Chuyển sang định vị cố định theo viewport để không bao giờ
       tràn, bất kể nút chuông nằm ở đâu trong topbar. */
    .qq-skkn-notif-dropdown {
        position: fixed;
        top: 60px;
        left: 12px;
        right: 12px;
        width: auto;
    }

    /* Các hàng flex nội tuyến (style="display: flex; gap: ...") dùng lặp lại ở ~42 view để xếp
       nhiều trường form/bộ lọc nằm ngang — không có flex-wrap, bị bóp méo trên mobile. Ép wrap
       bằng attribute selector khớp chuỗi trong thuộc tính style (bắt buộc dùng !important vì
       style nội tuyến luôn có độ ưu tiên cao hơn CSS ngoài, không có cách nào khác để ghi đè). */
    [style*="display: flex"] {
        flex-wrap: wrap !important;
    }
    [style*="display: flex"] > * {
        flex: 1 1 200px;
    }

    /* Riêng <form> bộ lọc/tìm kiếm (Đợt, Trạng thái, Lĩnh vực, ô tìm kiếm...) — 1 số form có
       RIÊNG từng field 1 giá trị flex/max-width khác nhau (VD input flex:2, select flex:1,
       div max-width:400px) khiến rule chung ngay trên (không !important) không thắng được
       style nội tuyến của chính field đó — trên mobile các field vẫn chen chung 1 hàng
       không đều nhau thay vì xuống hẳn 1 cột như mong muốn, thay vì bị đẩy wrap. Ép RIÊNG
       cho thẻ <form> (bộ lọc luôn dùng <form>, khác với `<div>` của thanh phân trang/nhóm
       pill không nên bị ép xuống 1 cột) để mỗi field xuống đúng 1 hàng, rộng bằng nhau. */
    form[style*="display: flex"] > * {
        flex: 1 1 100% !important;
        max-width: 100% !important;
    }

    /* Lớp loại trừ cho các khối flex nội tuyến không nên bóp méo/giãn chiều rộng con.
       Định nghĩa thêm biến thể `form.qq-skkn-no-wrap-mobile` cùng độ ưu tiên CSS (loại +
       lớp) với rule `form[style*="display: flex"] > *` ở trên để thắng được đúng theo thứ
       tự khai báo sau — dùng cho form hành động gọn trong 1 hàng bảng (VD nhan-rong.php)
       không nên bị ép xuống 1 cột như form bộ lọc đầu trang. */
    .qq-skkn-no-wrap-mobile {
        flex-wrap: nowrap !important;
    }
    .qq-skkn-no-wrap-mobile > *,
    form.qq-skkn-no-wrap-mobile > * {
        flex: initial !important;
        max-width: none !important;
    }

    /* Loại trừ TOÀN BỘ <label> khỏi 2 rule ép flex-basis/wrap ở trên — người dùng phát hiện qua ảnh
       chụp thật (trang Hội đồng phê duyệt): 1 <label style="display: flex; align-items: center;
       gap: 8px"> bọc 1 radio/checkbox + đoạn text ngắn (mẫu dùng LẶP LẠI hàng chục nơi toàn dự án —
       radio "Duyệt/Thay đổi điểm", "Đề xuất nhân rộng", mọi checkbox ở Cài đặt...) cũng khớp luôn
       `[style*="display: flex"] > *` — khiến chính input/span BÊN TRONG 1 label bị đẩy vỡ ra 2 dòng
       riêng (radio 1 dòng, chữ 1 dòng), không phải các label wrap với NHAU (hành vi đúng, vẫn giữ
       nguyên). 1 label kiểu này chỉ có 2-3 phần tử con ngắn, không bao giờ cần tự vỡ dòng bên trong
       chính nó — loại trừ hẳn thay vì phải rải class .qq-skkn-no-wrap-mobile vào từng label riêng lẻ. */
    label[style*="display: flex"] {
        flex-wrap: nowrap !important;
    }
    label[style*="display: flex"] > * {
        flex: initial !important;
        max-width: none !important;
    }
}

/* TASK-46 MỤC 1b (L2, lỗi nặng nhất phase, phiên kiểm thử 2026-08-29) — ☰ luôn nằm ở x=366→390 bất kể
   viewport (đo thật: còn 9px ở 375px, mất hẳn ở 360/320px — máy phổ biến nhất Galaxy/Pixel 360px không
   còn cách nào mở sidebar). Đặt SAU block 900px để thắng theo thứ tự khai báo cùng độ ưu tiên CSS. Ẩn
   3 icon phụ (Cổng công bố/Hướng dẫn/Sáng-Tối, đánh dấu .qq-skkn-nav-icon-phu ở Layout.php) — vẫn truy
   cập được qua 3 mục tương ứng thêm vào cuối sidebar (MỤC 1d), không mất chức năng. */
@media (max-width: 480px) {
    .qq-skkn-nav-icon-phu {
        display: none;
    }

    /* Không có icon logout sẵn trong qq-skkn-icons.svg — theo đúng phương án dự phòng ticket cho phép:
       giữ chữ nhưng rút còn "Thoát" + giảm padding, thay vì thêm icon mới ngoài phạm vi. */
    .qq-skkn-logout-text-short {
        display: inline;
    }
    .qq-skkn-logout-text-full {
        display: none;
    }
    .qq-skkn-logout-btn {
        padding: 6px 8px !important;
    }

    /* Vùng chạm tối thiểu 44×44px cho thao tác ngón tay (trước đây chỉ ~24px, khớp đúng kích thước
       icon bên trong không có đệm riêng). */
    .qq-skkn-mobile-menu-btn {
        min-width: 44px;
        min-height: 44px;
        display: flex;
        align-items: center;
        justify-content: center;
    }
}

/* Phân trang (Layout::renderPagination()) — căn giữa, 2 phiên bản chuyển đổi theo
   độ rộng màn hình: đầy đủ số trang trên desktop/tablet, rút gọn còn Trước/Sau +
   "Trang X / Y" trên mobile (liệt kê nhiều số trang trên màn hình hẹp dễ vỡ layout
   và khó bấm trúng bằng ngón tay — khuyến nghị UX phổ biến cho pagination mobile). */
.qq-skkn-pagination {
    display: flex;
    justify-content: center;
    margin-top: 20px;
}
.qq-skkn-pagination-numbers {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}
.qq-skkn-pagination-compact {
    display: none;
    justify-content: center;
    align-items: center;
    gap: 12px;
}
@media (max-width: 768px) {
    .qq-skkn-pagination-numbers { display: none; }
    .qq-skkn-pagination-compact { display: flex; }
}

/* Reduced Motion Override */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* Utilities for TASK-24 */
.qq-skkn-form-group { margin-bottom: 16px; }
.qq-skkn-form-label { display: block; font-weight: 600; margin-bottom: 6px; color: var(--color-heading); }

.qq-skkn-alert { padding: 12px 16px; margin-bottom: 20px; border-radius: var(--radius-card-inner); border: 1px solid; }
.qq-skkn-alert-success { background: var(--color-success-bg); color: var(--color-success-text); border-color: var(--color-success-text); }
.qq-skkn-alert-danger { background: var(--color-danger-bg); color: var(--color-danger-text); border-color: var(--color-danger-text); }
.qq-skkn-alert-warning { background: var(--color-warning-bg); color: var(--color-warning-text); border-color: var(--color-warning-text); }
.qq-skkn-alert-info { background: var(--color-info-bg); color: var(--color-info-text); border-color: var(--color-info-text); }

/* Popup cảnh báo giữa màn hình cho thông báo nhất thời sau submit (xem script cuối renderFooter()
   trong Layout.php — bắt các khối [qq-skkn-alert-flash], ẩn khung cũ, hiện popup này thay thế). */
.qq-skkn-modal-overlay {
    position: fixed; inset: 0; background: rgba(0, 0, 0, 0.55);
    display: flex; align-items: center; justify-content: center;
    z-index: 9999; padding: 20px;
}
.qq-skkn-modal-box {
    background: var(--color-bg-primary); border-radius: var(--radius-card);
    max-width: 440px; width: 100%; padding: 28px 24px 24px;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35);
    text-align: center; animation: qqSkknModalIn 0.18s ease-out;
}
@keyframes qqSkknModalIn {
    from { transform: scale(0.92); opacity: 0; }
    to { transform: scale(1); opacity: 1; }
}
.qq-skkn-modal-icon {
    width: 56px; height: 56px; border-radius: 50%; margin: 0 auto 16px;
    display: flex; align-items: center; justify-content: center;
    font-size: 26px; font-weight: 700;
}
.qq-skkn-modal-icon.success { background: var(--color-success-bg); color: var(--color-success-text); }
.qq-skkn-modal-icon.danger { background: var(--color-danger-bg); color: var(--color-danger-text); }
.qq-skkn-modal-icon.warning { background: var(--color-warning-bg); color: var(--color-warning-text); }
.qq-skkn-modal-icon.info { background: var(--color-info-bg); color: var(--color-info-text); }
.qq-skkn-modal-message {
    color: var(--color-text-primary); font-size: 15px; line-height: 1.6;
    white-space: pre-line; margin-bottom: 20px;
}
.qq-skkn-modal-btn { padding: 10px 32px; }

.qq-skkn-import-box { border: 2px dashed var(--color-border); border-radius: var(--radius-card-inner); padding: 30px; text-align: center; background: var(--color-bg-secondary); }

/* Utilities for Printing (van-ban.php) */
/* Lề theo đúng Nghị định 30/2020/NĐ-CP (thể thức văn bản hành chính): trên/dưới 20-25mm,
   trái 30-35mm (chừa chỗ đóng dấu/lưu hồ sơ), phải 15-20mm — trước đây 20mm đều 4 cạnh,
   lề trái thiếu so với mức tối thiểu quy định. */
.qq-skkn-print-container {
    width: 210mm;
    min-height: 297mm;
    margin: 0 auto;
    background: var(--color-bg-primary);
    padding: 20mm 20mm 20mm 30mm;
    box-shadow: var(--shadow-card);
    box-sizing: border-box;
    font-family: "Times New Roman", Times, serif;
    font-size: 14pt;
    line-height: 1.5;
    color: var(--color-text-primary);
}
/* TASK-46 MỤC 2 (L7, quét bổ sung 2026-08-29) — thẻ biểu đồ cột (bar-chart) ở Thống kê (index.php +
   detail.php, cả 4 chỗ "2. Phân bổ theo Xếp loại"/"3. Phân bổ theo Loại sáng kiến"/"Đề xuất Nhân
   rộng") có min-width: 400px cứng qua inline style — flex item không co được nhỏ hơn 400px dù
   container cha đã có flex-wrap: wrap, tràn ngang ở viewport < 400px (360px, 320px). Chuyển từ
   inline style sang class này để có thể override ở @media mobile bên dưới mà không cần !important. */
.qq-skkn-stat-chart-card {
    flex: 1;
    min-width: 400px;
}
/* TASK-46 MỤC 2 (L7, quét bổ sung 2026-08-29) — <select> lọc "Đợt sáng kiến" ở Tổng hợp Kết quả Chấm
   điểm/Hội đồng phê duyệt/Chấm điểm (giám khảo) có min-width: 300px cứng qua inline style. Dù form cha
   đã thêm flex-wrap: wrap, select tự nó (đứng 1 mình 1 dòng) vẫn rộng hơn phần nội dung còn lại trong
   .qq-skkn-card (padding 24px x2) ở viewport hẹp — 300px > 345px (viewport) - 48px (padding thẻ) = 297px,
   tràn ~3-4px. Chuyển sang class để override min-width ở mobile mà không cần !important. */
.qq-skkn-dot-filter-select {
    min-width: 300px;
}
@media print {
    /* Văn bản in ra giấy LUÔN nền trắng/chữ đen bất kể theme sáng/tối đang bật
       trên màn hình — dùng màu cứng có chủ đích ở đây (không phải vi phạm quy
       tắc token), vì giấy in vật lý không có khái niệm "theme". */
    /* FIX 2026-09-07 (phát hiện khi điều tra lỗi "in Giấy Chứng nhận tràn trang, mất chữ ký") — TẤT CẢ
       9 view "văn bản in" trong dự án (van-ban.php, van-ban-tong.php, bien-ban.php x3, diem-chi-tiet-
       bien-ban*.php, van-ban-sua-doi.php, thu-hoi-van-ban.php, bang-chi-tiet.php) đặt `padding: 20px`
       lên <body> bằng INLINE style (để có khoảng đệm xám khi XEM trên màn hình). Theo chuẩn CSS, inline
       style luôn thắng bất kỳ rule nào trong stylesheet — kể cả rule khớp @media print như rule
       `padding: 0` ngay dưới đây — trừ khi rule đó có `!important`. Vì thiếu `!important`, rule reset
       này CHƯA BAO GIỜ có hiệu lực lúc in: `.qq-skkn-print-container{width:100%}` (và tương tự
       `.cert-page` ở Giấy Chứng nhận, xem partials/chung-nhan-css.php) luôn bị tính hụt mất 40px
       (padding trái+phải của body) so với chiều rộng khổ giấy thật — sinh viền trắng thừa quanh mọi
       văn bản in, và ở Giấy Chứng nhận (nơi duy nhất ép `min-height:100vh` để canh chỉnh vừa đúng 1
       trang) còn khiến khối nội dung tràn hẳn sang trang in thứ 2 vì 2 cạnh rộng/cao lệch tỷ lệ A4
       thật. Thêm `!important` để rule reset LUÔN thắng bất kể inline style trên <body> — sửa dứt điểm
       ở 1 chỗ, không cần sửa lại 9 file trên (chung-nhan.php/chung-nhan-tong.php đã sửa riêng, bỏ hẳn
       inline style, xem partials/chung-nhan-css.php — thêm `!important` ở đây là lớp an toàn thứ 2). */
    body { background: #fff !important; padding: 0 !important; margin: 0 !important; }
    .qq-skkn-print-container { background: #fff; color: #000; box-shadow: none; margin: 0; padding: 0; width: 100%; min-height: auto; }
    .qq-skkn-print-title-danger { color: #000 !important; }
    .qq-skkn-print-hide { display: none !important; }
}
/* XEM (chưa in) trên màn hình hẹp hơn khổ A4 (~794px) — width:210mm cố định phía trên tràn
   ngang toàn trang trên điện thoại/tablet nhỏ (rà soát responsive phát hiện: viền khung 375px
   bị đẩy tràn hẳn ra ngoài, chữ đầu dòng bị cắt cụt). Co container về 100% + giảm lề (20-30mm
   quá lớn so với màn 375px, ăn gần nửa chiều rộng) — CHỈ áp dụng khi XEM trên màn hình, giữ
   nguyên @media print phía trên cho đúng khổ giấy A4 thật khi in ra. Dùng chung breakpoint
   768px với rule [style*="display: flex"] bên dưới (khối Quốc hiệu 2 cột tự wrap dọc đúng lúc
   container co lại, không lệch nhau giữa 2 rule) — không cần sửa từng file văn bản in riêng lẻ. */
@media screen and (max-width: 768px) {
    .qq-skkn-print-container {
        width: 100%;
        min-height: auto;
        padding: 16px;
    }

    /* TASK-46 MỤC 2 (L7, phiên kiểm thử 2026-08-29) — nút "Công nhận các Sáng kiến đã chọn" (và các nút
       nhãn dài khác trong hàng flex có sẵn rule mobile [style*="display: flex"] > * { flex: 1 1 200px;
       }) tràn khung: flex item mặc định min-width:auto KHÔNG co được nhỏ hơn nội dung, nhãn dài với
       white-space mặc định cho ra bề rộng nội tại lớn hơn cả container 278px trong viewport 360px, tràn
       41px ra ngoài. min-width:0 là mấu chốt cho phép co nhỏ hơn nội dung; white-space:normal cho nhãn
       dài xuống dòng thay vì đẩy ngang. Áp DIỆN RỘNG .qq-skkn-btn — đã rà lại các trang danh sách khác
       (Duyệt hồ sơ, Thẩm định điều kiện, Hội đồng phê duyệt, Quyết định, Sửa đổi & Thu hồi, Phân công
       Giám khảo, Quản lý Hội đồng, Đơn vị/Người dùng) không phát sinh vỡ chữ/cao bất thường nào. */
    .qq-skkn-btn {
        min-width: 0;
        max-width: 100%;
        white-space: normal;
    }

    /* TASK-46 MỤC 2 (L7, quét bổ sung 2026-08-29) — <code> inline (VD tên hàm/class dài không dấu
       cách như "QqSkkn\Core\AutoTasks::chayNeuDenLuot()" ở trang Trợ giúp & Vận hành) không tự xuống
       dòng được (không có khoảng trắng để wrap), tràn nhẹ ra ngoài viewport hẹp. overflow-wrap: anywhere
       cho phép ngắt dòng giữa từ khi cần — chỉ áp dụng mobile, không đụng hiển thị desktop. Không áp cho
       <pre><code> (khối lệnh cron) — khối đó đã có overflow-x: auto riêng, cố tình cuộn ngang để giữ
       nguyên định dạng lệnh, không nên bẻ dòng.
    */
    code {
        overflow-wrap: anywhere;
    }
    pre code {
        overflow-wrap: normal;
    }

    /* TASK-46 MỤC 2 (L7, quét bổ sung 2026-08-29) — override min-width: 400px của
       .qq-skkn-stat-chart-card (xem chú thích ở định nghĩa base phía trên @media print) cho phép co
       nhỏ hơn 400px, tự wrap dọc đúng như flex-wrap: wrap của container cha đã định. */
    .qq-skkn-stat-chart-card {
        min-width: 0;
    }

    /* TASK-46 MỤC 2 (L7, quét bổ sung 2026-08-29) — override min-width: 300px của
       .qq-skkn-dot-filter-select (xem chú thích ở định nghĩa base phía trên @media print) cho phép co
       vừa khung .qq-skkn-card trên viewport hẹp — 100% kế thừa từ .qq-skkn-field, không mất chức năng. */
    .qq-skkn-dot-filter-select {
        min-width: 0;
    }
}

/* Thông báo (Chuông) */
.qq-skkn-notif-badge {
    position: absolute; top: 2px; right: 2px;
    min-width: 16px; height: 16px; padding: 0 4px;
    background: var(--color-danger-text); color: #fff;
    border-radius: 999px; font-size: 10px; font-weight: 700;
    display: flex; align-items: center; justify-content: center;
    line-height: 1;
}
.qq-skkn-notif-dropdown {
    display: none;
    position: absolute; top: calc(100% + 8px); right: 0;
    width: 420px; max-height: 400px; overflow-y: auto;
    background: var(--color-bg-primary); border: 1px solid var(--color-border);
    border-radius: var(--radius-card-inner); box-shadow: var(--shadow-card);
    z-index: 1100;
}
.qq-skkn-notif-dropdown.show { display: block; }
/* Thanh công cụ trên cùng — "Xem tất cả" trái, "Đánh dấu tất cả đã đọc" phải, cùng 1 hàng. */
.qq-skkn-notif-toolbar {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 10px 16px; border-bottom: 1px solid var(--color-border);
    background: var(--color-bg-secondary);
}
.qq-skkn-notif-view-all {
    background: none; border: none; padding: 0;
    color: var(--color-primary-fg); font-size: 12.5px; font-weight: 600; text-decoration: none;
}
.qq-skkn-notif-view-all:hover { text-decoration: underline; }
.qq-skkn-notif-mark-all {
    background: none; border: none; padding: 0;
    color: var(--color-primary-fg); font-size: 12.5px; font-weight: 600;
    cursor: pointer; font-family: inherit;
}
.qq-skkn-notif-mark-all:hover { text-decoration: underline; }
/* "Xoá tất cả" — hàng riêng NGAY DƯỚI thanh công cụ, tách biệt rõ bằng màu danger vì đây là thao
   tác phá huỷ dữ liệu (khác 2 nút phía trên không mất dữ liệu gì). */
.qq-skkn-notif-clear-all {
    display: block; width: 100%; text-align: center;
    padding: 8px 16px; border: none; border-bottom: 1px solid var(--color-border);
    background: var(--color-danger-bg); color: var(--color-danger-text);
    font-size: 12.5px; font-weight: 600; cursor: pointer; font-family: inherit;
}
.qq-skkn-notif-clear-all:hover { text-decoration: underline; }
/* "Hiển thị thêm" — cuối danh sách item, chỉ xuất hiện khi còn thông báo cũ hơn chưa tải (xem
   qqSkknRenderShowMoreBtn() trong Layout.php). Nhạt hơn 2 nút hành động phía trên (không viền
   dưới đậm, nền trong suốt) vì đây chỉ là điều hướng tải thêm, không phải hành động dữ liệu. */
.qq-skkn-notif-show-more {
    display: block; width: 100%; text-align: center;
    padding: 10px 16px; border: none;
    background: none; color: var(--color-primary-fg);
    font-size: 12.5px; font-weight: 600; cursor: pointer; font-family: inherit;
}
.qq-skkn-notif-show-more:hover { background: var(--color-bg-secondary); text-decoration: underline; }
.qq-skkn-notif-show-more:disabled { cursor: default; opacity: 0.6; text-decoration: none; }
.qq-skkn-notif-item {
    display: block; padding: 12px 16px; text-decoration: none;
    border-bottom: 1px solid var(--color-border);
    color: var(--color-text-primary); font-size: 14px;
}
.qq-skkn-notif-item:last-child { border-bottom: none; }
.qq-skkn-notif-item:hover { background: var(--color-bg-secondary); }
.qq-skkn-notif-item.unread { font-weight: 700; background: var(--color-info-bg); }
.qq-skkn-notif-empty { padding: 24px 16px; text-align: center; color: var(--color-text-muted); font-size: 14px; }
/* Nội dung đầy đủ email thay thế khi SMTP tự động đang TẮT (xem QqSkkn\Core\Notifier) —
   luôn thường (không đậm) kể cả khi thông báo chưa đọc, để phân biệt rõ với tiêu đề. */
.qq-skkn-notif-body {
    font-weight: 400; font-size: 12.5px; color: var(--color-text-muted);
    margin-top: 6px; white-space: pre-line; line-height: 1.5;
}
/* Trang /thong-bao "Xem tất cả" — mỗi dòng dùng lại đúng tông màu unread của dropdown chuông. */
.qq-skkn-notif-page-item:hover { background: var(--color-bg-secondary) !important; }

/* "Xem thêm/Thu gọn" cho khối text dài (4 trường Mô tả bản chất Sáng kiến ở các trang chi tiết
   read-only) — cắt theo max-height ~6 dòng (line-height ép 1.5, max-height = 9em nên tự thích ứng
   theo font-size từng nơi, không phải số px cố định), JS ở Layout.php (qqSkknInitCollapsible) chỉ
   hiện nút khi nội dung thực sự tràn quá khung, nội dung ngắn không có nút thừa.
   KHÔNG dùng -webkit-line-clamp: nội dung ở đây đi qua HtmlSanitizer::clean() có thể chứa thẻ <br>
   (xuống dòng), mà line-clamp tính sai layout dòng cuối khi có phần tử khối/br bên trong, gây lỗi
   CHỮ CHỒNG LÊN NHAU ở dòng bị cắt (đã xác nhận qua ảnh chụp thật của người dùng) — max-height +
   overflow:hidden đơn thuần không có lỗi này vì chỉ cắt thô theo chiều cao, không tính lại dòng. */
.qq-skkn-collapsible-content {
    max-height: 9em; line-height: 1.5; overflow: hidden; position: relative;
}
.qq-skkn-collapsible-content:not(.qq-skkn-collapsible-expanded)::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1.6em;
    background: linear-gradient(to bottom, transparent, var(--color-bg-secondary));
    pointer-events: none;
}
.qq-skkn-collapsible-content.qq-skkn-collapsible-expanded {
    max-height: none; overflow: visible;
}
.qq-skkn-collapsible-toggle {
    display: none; margin-top: 8px; padding: 0; border: none; background: none;
    color: var(--color-primary-fg); font-size: 13px; font-weight: 600;
    cursor: pointer; font-family: inherit;
}
.qq-skkn-collapsible-toggle:hover { text-decoration: underline; }

/* Thanh tiến trình dạng stepper (Quy trình xét duyệt hồ sơ ở HoSo/Views/detail.php, Tiến trình hoạt
   động của Đợt ở Dot/Views/detail.php) — gộp về 1 định nghĩa DUY NHẤT ở đây, trước đây mỗi View tự
   viết 1 bản CSS gần giống nhau trong thẻ <style> riêng.
   Đường nối ngang là 1 THANH DUY NHẤT chạy full-width phía SAU cả hàng (::before của .stepper-wrapper,
   z-index thấp nhất trong toàn khối) — KHÔNG ghép từng đoạn ::before/::after theo từng ô như cách cũ
   (mỗi ô tự vẽ nửa đoạn nối sang ô bên cạnh, phụ thuộc z-index so sánh CHÉO giữa các stacking context
   của những phần tử anh em khác nhau — dễ bị trình duyệt tính sai khiến đường nối đè lên vòng tròn số,
   người dùng phản ánh trực tiếp qua ảnh chụp thật). Vòng tròn số luôn có position:relative + z-index
   cao hơn hẳn + nền đặc (không trong suốt) nên LUÔN nổi trên đường nối trong mọi trường hợp. */
/* padding-top: 8px — KHÔNG phải trang trí, đây là chỗ dành cho vòng halo (box-shadow spread 5px)
   của bước "active". overflow-x: auto khiến trình duyệt tự ép overflow-y cũng thành auto (quy tắc
   tương thích CSS2.1 cũ: 1 trục auto/scroll thì trục "visible" còn lại KHÔNG THỂ giữ visible, dù có
   khai overflow-y: visible tường minh cũng bị ép lại), tức là mọi thứ tràn ra ngoài khung đều bị cắt
   — halo của bước active tràn lên trên đúng 5px bị cắt cụt mất phần trên, người dùng phản ánh kèm
   ảnh chụp thật (vòng tròn "1" bị vạt phẳng phía trên). Cách sửa: không gỡ overflow-x (vẫn cần cho
   Đợt nhiều mốc cuộn ngang trên màn hẹp), mà CHỪA ĐỦ CHỖ trong chính khung bằng padding-top — halo
   nằm gọn trong padding, không còn chạm mép clip nữa. */
.stepper-wrapper {
    display: flex; justify-content: space-between; position: relative;
    margin-top: 30px; margin-bottom: 24px; overflow-x: auto; padding: 8px 0 4px;
}
.stepper-wrapper::before {
    content: ""; position: absolute; left: 0; right: 0; top: 23px; height: 3px;
    background-color: var(--color-border); z-index: 0;
}
.stepper-item {
    position: relative; z-index: 1;
    display: flex; flex-direction: column; align-items: center;
    flex: 1; min-width: 110px; text-align: center;
}
.step-counter {
    position: relative; z-index: 2;
    display: flex; align-items: center; justify-content: center;
    width: 30px; height: 30px; border-radius: 50%;
    background-color: var(--color-border); color: var(--color-bg-primary);
    font-weight: bold; font-size: 14px; margin-bottom: 5px;
}
.step-name { font-size: 13px; font-weight: bold; color: var(--color-text-muted); text-align: center; }
.step-date { margin-top: 4px; font-size: 12px; color: var(--color-text-muted); text-align: center; }

.stepper-item.completed .step-counter { background-color: var(--color-success-text); }
.stepper-item.completed .step-name { color: var(--color-success-text); }
.stepper-item.active .step-counter { background-color: var(--color-primary); box-shadow: 0 0 0 5px rgba(var(--color-primary-rgb), 0.3); }
.stepper-item.active .step-name { color: var(--color-primary); }
.stepper-item.error .step-counter { background-color: var(--color-danger-text); }
.stepper-item.error .step-name { color: var(--color-danger-text); }
.stepper-item.warning .step-counter { background-color: var(--color-warning-text); }
.stepper-item.warning .step-name { color: var(--color-warning-text); }

/* Thanh bộ lọc dạng "nhãn + ô chọn" lặp lại nhiều lần trên 1 hàng (VD HoSo/Views/index.php: Trạng
   thái đợt / Lọc theo đợt / Số dòng-trang) — trước đây mỗi label và mỗi select là 1 flex-item RIÊNG
   LẺ nằm thẳng trong <form>, nên khi flex-wrap phải xuống dòng (màn hẹp/nhiều đợt tên dài), nó ngắt
   NGAY GIỮA 1 cặp label-select thay vì ngắt giữa 2 cặp — label "Lọc theo đợt:" bị trôi lên 1 mình,
   ô chọn của nó rớt xuống dòng khác, nhìn rất rối (người dùng phản ánh kèm ảnh chụp thật). Gộp mỗi
   cặp label+select vào 1 .qq-skkn-filter-group riêng để chúng LUÔN xuống dòng CÙNG NHAU. Trên mobile
   (≤768px), xếp thành đúng 1 cột dọc theo yêu cầu người dùng thay vì tự do wrap nhiều dòng lởm chởm. */
.qq-skkn-filter-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; margin: 0; }
.qq-skkn-filter-group { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; }
.qq-skkn-filter-group > .qq-skkn-form-label { margin-bottom: 0; white-space: nowrap; }
/* Trần rộng cho ô chọn trong thanh lọc (TASK-48 MỤC 5) — <select> tự co giãn theo option DÀI NHẤT,
   nên chỉ cần 1 đợt tên dài ("D2601 - Đợt sáng kiến năm học 2026-2027 (QA)" = 381px) là ô đó nuốt quá
   nửa thanh lọc, đẩy /ho-so vỡ 3 hàng ở 1024px. Đây là lỗi CÓ SẴN, không phải do TASK-48 bỏ
   min-width: 250px — đã kiểm chứng: áp lại đúng min-width cũ thì select VẪN 381px, VẪN 3 hàng (sàn
   250px chưa bao giờ có tác dụng vì nội dung đã rộng hơn). Tên đợt dài bị cắt trong ô là chấp nhận
   được — bung dropdown ra vẫn đọc đủ. */
.qq-skkn-filter-group > select { max-width: 280px; }
@media (max-width: 768px) {
    .qq-skkn-filter-bar { flex-direction: column; align-items: stretch; }
    .qq-skkn-filter-group { width: 100%; }
    /* max-width: 100% BẮT BUỘC — gỡ trần 280px ở trên, nếu không ô chọn bị ghì lại 280px trong khi
       mobile cần rộng gần hết thẻ (và ở 320px thì thẻ còn hẹp hơn 280px). */
    .qq-skkn-filter-group > select { flex: 1; width: 100%; min-width: 0; max-width: 100%; }
}
