/* ============================================================================
   DESIGN SYSTEM DÙNG CHUNG — Livestream Analytics
   ============================================================================
   Bảng màu/spacing kiểu dashboard ecommerce hiện đại (card nổi bằng shadow thay
   vì chỉ viền mỏng, số liệu KPI làm nổi bật, accent màu thương hiệu).
   Dùng biến CSS (:root) để đổi màu 1 chỗ áp dụng toàn bộ trang.
   ============================================================================ */
:root {
  --brand: #4f46e5;         /* tím-xanh chủ đạo (indigo) — thay bằng màu brand thật nếu có */
  --brand-dark: #4338ca;
  --brand-soft: #eef2ff;
  --accent: #ee4d2d;        /* cam-đỏ Shopee thật (đổi từ #f97316 chung chung, 2026-07-10) — dùng cho series phụ trong chart/table */
  --hince: #ff6b4a;         /* cam san hô HINCE — dùng cho điểm nhấn ở các trang landing/login (2026-07-10) */
  --shopee: #ee4d2d;        /* cam-đỏ Shopee thật — alias cùng giá trị --accent, đặt tên rõ nghĩa hơn khi dùng cho các khối liên quan Shopee */
  /* 2026-08-01 (design-critique) — cặp soft/dark của --hince, cùng công thức với
     --brand-soft/--brand-dark, để dùng cho callout/badge cần tông ẤM (khen thưởng,
     nổi bật) thay vì lặp lại --brand chàm ở mọi nơi — bản thân đúng giá trị đã
     dùng ở .chip.hince (index.php), gộp về đây làm biến dùng chung. 4.70:1 trên
     nền trắng, đạt AA. */
  --hince-soft: #fff1ec;
  --hince-dark: #c2410c;
  /* ⚠️ 2026-08-01 (audit accessibility) — success/warning/text-faint gốc KHÔNG đạt
     WCAG AA (4.5:1) cho chữ cỡ thường: success #16a34a=3.30:1, warning #d97706=
     3.19:1, text-faint #94a3b8=2.56:1 trên nền trắng — quá mờ để đọc, đặc biệt
     .hint (dùng ở ~mọi form/trang) và ↑/↓ delta trên KPI card. Đổi sang sắc độ
     ĐẬM HƠN cùng tông màu, đều ≥4.5:1 (đo bằng công thức luminance WCAG), KHÔNG
     đổi layout/kích thước — chỉ đổi đúng 3 giá trị hex này, áp dụng lại toàn app. */
  --success: #15803d;       /* 5.02:1 trên nền trắng (trước: #16a34a = 3.30:1, KHÔNG đạt AA) */
  --danger: #dc2626;
  --warning: #a35a04;       /* 5.22:1 trên nền trắng (trước: #d97706 = 3.19:1, KHÔNG đạt AA) */

  /* ⚠️ 2026-08-06 — 2 màu chữ dùng KHI NẰM TRÊN NỀN ĐÃ PHA CÙNG TÔNG.
     Vấn đề tự tạo ra: các khối cảnh báo mới (.range-hint.danger, .mode-warn.danger)
     pha nền bằng CHÍNH màu chữ ở 10% — nền tối đi nên tương phản tụt xuống 4.2:1 và
     4.13:1, dưới chuẩn AA 4.5:1. Đo thử: hạ độ pha xuống 4% mới vừa đạt, nhưng lúc đó
     nền gần như vô hình, mất luôn tác dụng phân biệt.
     Cách đúng là làm CHỮ đậm hơn chứ không làm nền nhạt đi — cùng cách đã xử lý
     --success/--warning ở trên. Chỉ dùng cho chữ trên nền pha, KHÔNG thay thế
     --danger/--warning ở các chỗ khác (trên nền trắng 2 màu gốc đã đạt). */
  --warning-dark: #8f4e03;  /* 5.19:1 trên nền vàng pha 10% · 6.44:1 trên trắng */
  --danger-dark:  #b91c1c;  /* 5.54:1 trên nền đỏ pha 10%  · 6.47:1 trên trắng */
  /* 2026-08-01 (redesign — tinh chỉnh màu/token, giữ nguyên --brand/--shopee) —
     đổi neutrals từ xám trung tính sang xám ÁM TÍM NHẸ, nhất quán với tông chàm
     của sidebar/--brand thay vì "xám mượn" không chủ đích. Đã đo lại contrast
     TRƯỚC khi đổi — không được thấp hơn 3 giá trị vừa sửa accessibility ở trên
     (4.5:1 trên CẢ nền trắng lẫn --bg mới, không chỉ nền trắng). */
  --text: #16213d;          /* 15.9:1 trên trắng — đậm hơn #1e293b cũ, "ink" rõ hơn */
  --text-muted: #5b6485;    /* 5.8:1 trên trắng / 5.3:1 trên --bg mới */
  --text-faint: #666f90;    /* 4.95:1 trên trắng / 4.53:1 trên --bg mới — vẫn đạt AA ở CẢ 2 nền, không lặp lại lỗi cũ */
  --border: #e6e4f2;
  --bg: #f5f4fb;
  --card-bg: #ffffff;
  /* Màu DỮ LIỆU theo sàn (khác --brand/--shopee dùng cho UI chrome) — dùng cho
     series TikTok trong chart/bảng, đi cặp với --shopee cho series Shopee. Đã
     validate bằng scripts/validate_palette.js (dataviz skill): CVD ΔE 18.4
     (deutan)/39.4 (tritan), normal-vision ΔE 33.5 — tách bạch rõ kể cả mù màu. */
  --tiktok: #0891a8;
  /* Cặp *-dark cho chữ TRÊN NỀN PHA (2026-08-08, badge chọn kênh upload) — cùng
     công thức --warning-dark/--danger-dark: --tiktok/--shopee gốc không đủ đậm
     để làm CHỮ (3.7:1 / 3.7:1 trên trắng, KHÔNG đạt AA 4.5:1), giữ nguyên 2 màu
     gốc cho khối DỮ LIỆU (fill biểu đồ...), chỉ thêm bản đậm hơn riêng cho chữ.
     067083: 5.75:1 trên trắng · a8371e: 6.50:1 trên trắng — đều đạt AA. */
  --tiktok-dark: #067083;
  --shopee-dark: #a8371e;
  --shadow-sm: 0 1px 2px rgba(15, 23, 42, .06), 0 1px 3px rgba(15, 23, 42, .08);
  --shadow-md: 0 4px 12px rgba(15, 23, 42, .08);
  --radius: 12px;
  --radius-sm: 8px;
  --shell-dark-1: #1e1b4b;  /* nền tối sidebar app-shell (2026-07-11) — CÙNG tông với hero login.php, để nhận diện thương hiệu xuyên suốt từ lúc đăng nhập */
  --shell-dark-2: #322a6b;
}

* { box-sizing: border-box; }

/* ⚠️ 2026-08-06 — BẮT BUỘC, đừng xoá.
   Thuộc tính hidden của HTML chỉ hoạt động nhờ quy tắc mặc định [hidden]{display:none}
   của trình duyệt — mà BẤT KỲ quy tắc display nào mình tự viết cũng có độ ưu tiên cao
   hơn và đè mất. Hậu quả rất khó đoán ra: JS đặt hidden=true, không có lỗi nào báo,
   phần tử vẫn nằm đó với nội dung cũ.
   Đã dính ĐÚNG lỗi này 2 lần trong 1 ngày: .range-hint (display:flex — dòng nhắc
   khoảng ngày không tắt được khi giảm ngày) rồi .btn (display:inline-block — nút tải
   bản offline hiện ngay cả khi chưa có dữ liệu). Vá từng class thì lần sau vẫn sót,
   nên chặn 1 lần ở đây cho MỌI phần tử.
   !important là cần thiết: nó phải thắng mọi quy tắc display khác trong file này. */
[hidden] { display: none !important; }

body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  background: var(--bg);
  color: var(--text);
  margin: 0;
  padding: 0;
  line-height: 1.5;
}

.page {
  /* Fluid, co theo màn hình thực tế (đa số giờ đã 1920px) nhưng chặn trần để
     không bị kéo quá rộng trên màn hình siêu to (2560px, 3440px...). */
  width: 70%;
  max-width: 1800px;
  margin: 0 auto;
  padding: 24px 20px 60px;
}
@media (max-width: 900px) { .page { width: 100%; } }

a { color: var(--brand); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ── Topbar ── */
.topbar {
  display: flex; justify-content: space-between; align-items: center;
  background: var(--card-bg); border-radius: var(--radius); box-shadow: var(--shadow-sm);
  padding: 14px 20px; margin-bottom: 20px; font-size: 13px; color: var(--text-muted);
}
.topbar b { color: var(--text); }
.topbar .role-badge {
  background: var(--brand-soft); color: var(--brand-dark); font-weight: 600;
  padding: 2px 10px; border-radius: 999px; font-size: 12px; margin-left: 6px;
}

h2.page-title { margin: 0 0 18px; font-size: 22px; font-weight: 700; display: flex; align-items: center; gap: 8px; }
h3.section-title { margin: 0 0 14px; font-size: 15px; font-weight: 700; color: var(--text); }

/* Nút chuyển VI/EN (2026-07-19) — xem lib/i18n.php::t_switcher_html() */
.lang-switch {
  display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 700;
  background: var(--card-bg); border: 1px solid var(--border); border-radius: 999px; padding: 4px 10px;
}
.lang-switch a { color: var(--text-faint); text-decoration: none; padding: 2px 4px; }
.lang-switch a.lang-switch-active { color: var(--brand); }
.lang-switch a:hover { color: var(--brand-dark); }
.lang-switch-sep { color: var(--border); }

/* Hàng tiêu đề bảng + nút Copy (2026-07-11, theo yêu cầu user "xuất dữ liệu bảng
   dễ copy vào báo cáo") — bọc h3 + nút vào 1 hàng flex, giữ NGUYÊN margin-bottom
   14px cũ của h3/.section-title (đẩy margin ra ngoài div thay vì h3 để không lệch
   khoảng cách với bảng bên dưới so với các h3 không có nút). */
.table-heading-row { display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
.table-heading-row h3, .table-heading-row h3.section-title { margin: 0; }
.copy-table-btn { flex-shrink: 0; }

/* ── Cards (chung cho card, chart-box, form) ── */
.card {
  background: var(--card-bg); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-sm); padding: 18px;
}

/* ── Filter bar ── */
.filters {
  display: flex; gap: 14px; flex-wrap: wrap; align-items: end;
  background: var(--card-bg); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-sm); padding: 18px; margin-bottom: 22px;
}
.filters label { display: block; font-size: 12px; color: var(--text-muted); margin-bottom: 5px; font-weight: 600; }
.filters select, .filters input[type=date] {
  padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: #fff; font-size: 13px; color: var(--text);
}
.filters select:focus, .filters input:focus { outline: 2px solid var(--brand-soft); border-color: var(--brand); }

/* Ô "Chọn kênh" upload (2026-08-08, user: "dễ đọc nhầm" TikTok/Shopee) — nền
   từng dòng trong danh sách xổ xuống tô theo platform (chỉ có tác dụng lúc
   danh sách ĐANG MỞ, độ hỗ trợ tuỳ trình duyệt) + badge màu bên cạnh LUÔN
   HIỆN sau khi chọn — badge là chỗ chống nhầm chính vì không phụ thuộc trạng
   thái mở/đóng của select. */
/* justify-content:center — #channelSelect rộng 100% nên luôn chiếm trọn 1 "dòng"
   flex-wrap riêng, dòng badge (dòng sau) do đó tự căn giữa độc lập, không ảnh hưởng
   vị trí select (2026-08-10, user: "chữ Shopee ... căn giữa cho dễ tập trung"). */
.channel-select-row { display: flex; align-items: center; justify-content: center; gap: 10px; flex-wrap: wrap; }
#channelSelect option[data-platform="tiktok"] { background: #e8f4f6; color: var(--tiktok-dark); }
#channelSelect option[data-platform="shopee"] { background: #fceae6; color: var(--shopee-dark); }
.platform-badge {
  display: inline-flex; align-items: center; font-size: 14px; font-weight: 700;
  padding: 6px 13px; border-radius: 999px; white-space: nowrap;
}
.platform-badge.tiktok { background: rgba(8, 145, 168, .12); color: var(--tiktok-dark); }
.platform-badge.shopee { background: rgba(238, 77, 45, .12); color: var(--shopee-dark); }

.btn {
  display: inline-block; padding: 9px 20px; background: var(--brand); color: #fff; border: none;
  border-radius: var(--radius-sm); cursor: pointer; font-size: 14px; font-weight: 600;
  box-shadow: var(--shadow-sm); transition: background .15s; line-height: 1.4;
  text-decoration: none; vertical-align: middle; box-sizing: border-box;
}
.btn:hover { background: var(--brand-dark); }
.btn:disabled { opacity: .55; cursor: not-allowed; pointer-events: none; }

/* Vòng xoay "đang chờ" — DÙNG CHUNG cho nút, băng đang tải, và màn hình chờ
   (2026-08-06). Ban đầu chỉ làm cho nút (tên cũ .btn-spin) rồi mới thấy các chỗ khác
   vẫn còn ký tự ⏳ đứng yên — user hỏi "đồng hồ cát này nó không xoay hả".
   Dùng viền chứ không dùng ký tự/emoji: emoji mỗi hệ điều hành một kiểu và không
   xoay mượt, còn vòng viền thì luôn hiện đúng và không đổi bề rộng nút. */
.spinner {
  display: inline-block; width: 13px; height: 13px; margin-right: 8px;
  /* margin-right dung cho nut + man hinh cho; .dash-loading la flex nen dat gap:0 */
  vertical-align: -2px; border-radius: 50%;
  border: 2px solid currentColor; border-top-color: transparent;
  animation: spinner .7s linear infinite;
}
@keyframes spinner { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  /* Tắt hiệu ứng quay nhưng GIỮ vòng tròn — vẫn thấy nút đang bận. */
  .spinner { animation: none; opacity: .6; }
}
.btn:disabled:hover { background: var(--brand); }
/* ── Chặn trang cần màn hình lớn khi mở trên điện thoại (2026-08-05) ──────────
   Khối này do render_app_topbar($user, false) sinh ra và đứng TRƯỚC .app-topbar /
   .app-shell, nên dùng bộ chọn anh-em (~) để giấu toàn bộ phần còn lại của trang.
   Trên màn hình rộng khối này ẩn, không ảnh hưởng gì.                          */
.desktop-only-block { display: none; }
@media (max-width: 860px) {
  .desktop-only-block {
    display: flex; flex-direction: column; align-items: center; text-align: center;
    min-height: 100vh; justify-content: center; padding: 40px 24px; gap: 10px;
  }
  .desktop-only-block ~ .app-topbar,
  .desktop-only-block ~ .app-overlay,
  .desktop-only-block ~ .app-shell { display: none; }

  .desktop-only-block .ic {
    width: 56px; height: 56px; border-radius: 15px; background: var(--brand-soft);
    display: flex; align-items: center; justify-content: center; font-size: 28px;
  }
  .desktop-only-block h2 { font-size: 17px; margin: 6px 0 0; color: var(--text); font-weight: 800; }
  .desktop-only-block p { font-size: 13.5px; line-height: 1.6; color: var(--text-muted); margin: 0; max-width: 300px; }
  .desktop-only-block .btn { margin-top: 12px; }
  .desktop-only-block p.sub { font-size: 12px; color: var(--text-faint); }
}

/* ⚠️ 2026-08-06 (user báo: chọn 1 brand thì các bảng dính vào nhau, không có khoảng cách)
   — .card KHÔNG có margin nào; từ trước tới nay khoảng cách giữa các thẻ trên dashboard
   do TỪNG khối tự thêm margin-top. Thẻ "So sánh kỳ trước ↔ kỳ này" thì quên hẳn, nên
   nó luôn dính vào thẻ phía trên. Lâu nay không ai thấy vì .ai-panel (margin-bottom
   24px) thường chen vào giữa và che mất; bỏ phân tích AI, hoặc lọc 1 brand làm bảng
   so sánh brand biến mất, là lộ ra ngay.
   Sửa ở GỐC: hai thẻ nằm liền nhau thì LUÔN có khoảng cách, khỏi phụ thuộc việc người
   viết thẻ mới có nhớ thêm margin hay không. Giới hạn trong #dashboardResults để không
   đụng bố cục các trang khác cũng dùng .card. */
#dashboardResults .card + .card { margin-top: 16px; }

/* ── Trạng thái "đang tải" GIỮ NGUYÊN nội dung cũ (2026-08-06) ────────────────
   Trước đây mọi lần tải đều xoá sạch bảng biểu để thay bằng một dòng chờ. Rất tệ
   với nút "Phân tích AI": chờ 30-60 giây chỉ để thêm mấy đoạn văn, mà toàn bộ số
   liệu đang đọc bị cất đi. Nay chỉ làm mờ và khoá thao tác, số cũ vẫn đọc được. */
#dashboardResults.is-loading {
  opacity: .45;
  pointer-events: none;    /* tránh bấm nhầm vào bảng cũ tưởng là số mới */
  transition: opacity .15s;
}
.dash-loading {
  position: sticky; top: 10px; z-index: 20;   /* dính lại khi cuộn — luôn biết là đang chạy */
  display: flex; align-items: center;
  margin-bottom: 14px; padding: 11px 15px;
  border-radius: var(--radius-sm);
  background: var(--brand-soft); border: 1px solid var(--brand);
  color: var(--brand-dark); font-size: 13px; font-weight: 700;
  box-shadow: var(--shadow-md);
}
@media (prefers-reduced-motion: reduce) {
  /* Tôn trọng người tắt hiệu ứng chuyển động — băng vẫn hiện, chỉ không quay. */
}

/* Thẻ so sánh brand có thể đứng ngay sau một khối KHÔNG phải .card (dòng gợi ý
   "Chọn Cả 2 ở bộ lọc Kênh") — lúc đó quy tắc anh-em ở trên không khớp, nên vẫn cần
   margin riêng. */
.brand-compare { margin-top: 16px; }

/* Bảng bên trái + biểu đồ tròn bên phải (2026-08-06) — bản DÙNG CHUNG, tên không
   gắn với nội dung cụ thể nào. Đặt riêng thay vì tái dùng .platform-compare-* vì
   tên đó đã gắn với bảng 2 sàn; dùng chéo sẽ khó lần khi cần sửa 1 bên.
   Bảng co giãn, biểu đồ giữ kích thước cố định — biểu đồ tròn nhỏ quá thì không
   đọc được lát nào ra lát nào. */
.table-donut-body { display: flex; gap: 24px; align-items: center; flex-wrap: wrap; margin-top: 4px; }
.table-donut-main { flex: 1 1 520px; min-width: 320px; margin: 0; }
.table-donut-side { flex: 0 0 190px; width: 190px; height: 190px; }
@media (max-width: 900px) {
  /* Màn hình hẹp: xếp dọc, biểu đồ xuống dưới bảng thay vì bị bóp lại */
  .table-donut-body { flex-direction: column; align-items: stretch; }
  /* ⚠️ min-width/max-width BẮT BUỘC — bẫy flexbox kinh điển: ô flex mặc định KHÔNG
     co nhỏ hơn nội dung bên trong. Bảng 11 cột rộng ~850px sẽ ĐẨY khung ra rộng theo
     nó (đo được: khung 851px trong khi cha chỉ 694px), làm overflow-x:auto của
     .report-table-wrap thành vô dụng và CẢ TRANG bị cuộn ngang. Ép co lại thì bảng
     mới cuộn BÊN TRONG khung đúng như thiết kế. */
  .table-donut-main { flex: 1 1 auto; min-width: 0; max-width: 100%; }
  .table-donut-side { align-self: center; width: 170px; height: 170px; }
}

/* Dòng tổng của MỘT NHÓM trong bảng (VD tổng mỗi brand) — 2026-08-06.
   Nhạt hơn .report-total-row (dòng TỔNG cuối bảng) để phân biệt được hai cấp:
   nhóm và toàn bảng. Cùng nền brand-soft nhưng không viền đậm. */
.report tr.report-subtotal-row td {
  background: var(--bg); font-weight: 700; color: var(--text);
  border-top: 1px solid var(--border);
}

/* ── Xem lại nội dung tin Zalo vừa gửi, có nút copy (2026-08-06) ────────────── */
.zalo-preview {
  margin: 16px 0 0; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--card-bg); overflow: hidden;
}
.zalo-preview-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 9px 12px; background: var(--bg); border-bottom: 1px solid var(--border);
  font-size: 13px; font-weight: 700; color: var(--text);
}
.zalo-preview pre {
  margin: 0; padding: 13px 15px; font-size: 13px; line-height: 1.65; color: var(--text);
  /* Tin Zalo có emoji + dòng kẻ ━━━: cần font giữ nguyên bề rộng ký tự thì dòng kẻ
     mới thẳng, nhưng KHÔNG dùng monospace thuần vì emoji sẽ vỡ khung. */
  font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
  white-space: pre-wrap;    /* giữ xuống dòng, vẫn tự ngắt khi hẹp */
  word-break: break-word;
  max-height: 340px; overflow-y: auto;
}
/* Nhánh "chưa gửi được": nội dung ẩn sau 1 nút bấm mới mở ra copy (2026-08-10) */
.zalo-preview-toggle { padding: 0 15px 13px; }
.zalo-preview-toggle summary {
  cursor: pointer; list-style: none; padding: 10px 0; font-size: 13px; font-weight: 700;
  color: var(--brand); user-select: none;
}
.zalo-preview-toggle summary::-webkit-details-marker { display: none; }
.zalo-preview-toggle[open] summary { padding-bottom: 4px; }
.zalo-preview-toggle pre { padding: 0; max-height: 300px; }

.customrange { display: none; gap: 14px; flex-wrap: wrap; }
.customrange.show { display: flex; }

/* Nhắc thời gian chờ khi khoảng ngày rộng (2026-08-05, dời vị trí 2026-08-06).
   Là ô CON TRỰC TIẾP của .filters và đứng CUỐI -> flex-basis:100% cho nó tự xuống
   HÀNG RIÊNG bên dưới toàn bộ ô lọc. Trước đây nằm trong .customrange nên kéo phình
   chính khối đó và đẩy lệch cả thanh bộ lọc.
   Nền pha rgba từ token màu — cùng cách với .kpi .delta, không thêm hex rời. */
.range-hint {
  flex-basis: 100%; box-sizing: border-box;
  margin-top: 2px;                       /* .filters đã có gap 14px, chỉ cần nhích nhẹ */
  display: flex; align-items: center; gap: 8px;
  padding: 9px 12px; border-radius: var(--radius-sm);
  font-size: 12.5px; line-height: 1.55; font-weight: 600;
  color: var(--text-muted); background: var(--bg); border: 1px solid var(--border);
}
/* (Việc ẩn do quy tắc [hidden] dùng chung ở đầu file lo — xem giải thích ở đó.) */
.range-hint::before { content: '⏱'; font-size: 14px; flex-shrink: 0; }
.range-hint.danger {
  /* chu dung --warning-dark (khong phai --warning): nen da pha cung tong nen phai dam hon */
  color: var(--warning-dark); background: rgba(163, 90, 4, .10); border-color: var(--warning);
}
.range-hint.danger::before { content: '⚠️'; }

/* ── KPI cards ── */
.kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }
@media (max-width: 700px) { .kpis { grid-template-columns: repeat(2, 1fr); } }
.kpi {
  background: var(--card-bg); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-sm); padding: 18px;
  transition: box-shadow .2s ease, transform .2s ease, border-color .2s ease;
}
.kpi:hover { box-shadow: var(--shadow-md); transform: translateY(-3px); }
/* Card GMV = "hero" — nổi bật hơn 7 card còn lại (khớp mockup redesign: gradient
   nhạt nghiêng sang --hince-soft + viền --hince, thay vì y hệt các card khác).
   GMV vốn là chỉ số quan trọng nhất kỳ, xứng đáng có điểm nhấn thị giác riêng —
   không phải áp cho tất cả card, tránh loãng ý nghĩa "nổi bật". */
.kpi.hero { background: linear-gradient(160deg, var(--card-bg) 60%, var(--hince-soft)); border-color: var(--hince); }
/* Card có hover-popup (xem info thêm) — viền sáng lên chút để gợi ý "rờ vào có
   thêm thông tin", tinh tế hơn cursor:help (2026-07-12, theo yêu cầu user). */
.kpi-hoverable:hover { border-color: var(--brand); }
.kpi .kpi-top { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.kpi .icon {
  width: 32px; height: 32px; border-radius: 8px; background: var(--brand-soft);
  display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0;
  transition: transform .2s ease;
}
.kpi:hover .icon { transform: scale(1.1); }
.kpi .lbl { font-size: 12px; color: var(--text-muted); font-weight: 600; }
/* 2026-08-01 (design-critique) — căn TRÁI thay vì giữa, khớp hàng icon+label phía
   trên (cũng căn trái) — trước đây 2 kiểu căn lề trộn trong cùng 1 card nhỏ. */
/* 2026-08-10 (design-critique, chuẩn báo cáo TMĐT) — thiếu tabular-nums trong khi
   bảng .report bên dưới ĐÃ có; số tiền lớn trên thẻ KPI co giãn không đều hàng,
   không nhất quán với phần còn lại của trang cho 1 tài liệu tài chính. */
.kpi .val { font-size: 24px; font-weight: 700; letter-spacing: -.3px; text-align: left; font-variant-numeric: tabular-nums; }
.up { color: var(--success); } .down { color: var(--danger); } .flat { color: var(--text-faint); }
/* 2026-08-01 (redesign — chip trạng thái, theo yêu cầu user) — delta trên KPI
   card đổi từ chữ màu trần (chỉ .up/.down/.flat ở trên, vẫn dùng nguyên cho
   bảng số/target-pct) sang "chip" nền pill để trạng thái đọc được ngay bằng
   HÌNH DẠNG chứ không chỉ màu chữ (đỡ phụ thuộc hoàn toàn vào phân biệt màu).
   CHỈ scope trong .kpi .delta — không đụng .up/.down/.flat dùng ở chỗ khác. */
.kpi .delta {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 12px; font-weight: 700; margin-top: 8px;
  padding: 3px 10px; border-radius: 999px;
}
.kpi .delta.up { background: rgba(21, 128, 61, .12); color: var(--success); }
.kpi .delta.down { background: rgba(220, 38, 38, .12); color: var(--danger); }
.kpi .delta.flat { background: var(--bg); color: var(--text-faint); }
/* 2026-08-01 — sparkline mini xu hướng theo ngày, khớp ĐÚNG kích thước/vị trí
   bản mockup redesign trước đó (user báo bản đầu — dải to tô nền dưới cùng card
   — không giống mockup): icon nhỏ 46×20px nằm NGAY CẠNH label (trong .kpi-top,
   đẩy sát phải bằng margin-left:auto), không tô nền vùng dưới đường (chỉ nét
   mảnh, giống mockup dùng SVG <path fill="none">). Ẩn hẳn khi kỳ chỉ có 1 ngày
   (PHP không render canvas, xem ajax_dashboard.php) nên không cần xử lý rỗng ở đây. */
/* Chart.js responsive:true đo kích thước theo THẺ CHA của canvas, không theo
   CSS width/height đặt thẳng trên chính canvas — phải bọc trong khung CỐ ĐỊNH
   kích thước thật (span display:inline-block có width/height rõ ràng) thì
   Chart.js mới co đúng 46x20px thay vì phình to theo khoảng trống flex còn lại
   của .kpi-top (bug đã gặp: canvas rộng cả nghìn px do đo nhầm parent). */
.kpi-spark-box { display: inline-block; width: 46px; height: 20px; margin-left: auto; flex-shrink: 0; }
.kpi-spark { width: 100%; height: 100%; }

/* ── Thẻ mục tiêu GMV (tab KPI_TARGET, đối chiếu vs GMV đạt được) ── */
.target-card { margin-top: 16px; }
.target-card-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.target-card-top .lbl { font-size: 13px; font-weight: 700; color: var(--text); }
.target-pct { font-size: 20px; font-weight: 700; }
.target-bar-track { background: var(--brand-soft); border-radius: 999px; height: 14px; overflow: hidden; }
.target-bar-fill { background: var(--brand); height: 100%; border-radius: 999px; transition: width .2s; }
.target-bar-note { font-size: 12px; color: var(--text-muted); margin-top: 8px; }

/* ── Bảng So sánh 2 sàn + donut (Bước 3, 2026-07-10) ── */
.platform-compare { margin-top: 16px; }
.platform-compare-body { display: flex; gap: 24px; align-items: center; flex-wrap: wrap; margin-top: 4px; }
.platform-compare-table { flex: 1 1 420px; min-width: 320px; margin: 0; }
.platform-compare-donut { flex: 0 0 180px; width: 180px; height: 180px; }
@media (max-width: 700px) {
  .platform-compare-body { flex-direction: column; }
  .platform-compare-donut { width: 160px; height: 160px; }
}

/* ── Callout "Ca xuất sắc nhất kỳ" (Bước 4, bảng Host) ──
   2026-08-01 (design-critique) — đổi từ tông --brand (chàm, dùng lặp lại ở gần
   như mọi nơi khác trong app) sang tông --hince (cam san hô) — đúng ngữ cảnh
   "khen thưởng/nổi bật" hơn, đồng thời nối lại bản sắc màu thương hiệu vốn chỉ
   xuất hiện ở login.php + sidebar, không có mặt ở nội dung chính. */
.callout-best {
  background: var(--hince-soft); color: var(--hince-dark); border-radius: var(--radius-sm);
  padding: 10px 14px; margin-bottom: 14px; font-size: 13px; line-height: 1.5;
}

.empty {
  color: var(--text-muted); padding: 28px; text-align: center; background: var(--card-bg);
  border: 1px dashed var(--border); border-radius: var(--radius); margin-top: 20px; font-size: 14px;
}
/* 2026-08-01 — phân biệt "gọi Apps Script lỗi" (đáng lo, cần thử lại/báo admin)
   với "thật sự không có dữ liệu" (bình thường, chỉ cần đổi bộ lọc) — trước đây
   dùng CHUNG 1 thông báo .empty màu trung tính cho cả 2, không ai phân biệt
   được. Màu cảnh báo (--warning) khớp nguyên tắc status color đã dùng ở
   .msg-banner.warn, không phải màu mới. */
.empty.err { color: var(--warning); border-color: var(--warning); border-style: solid; }
.hint { color: var(--text-faint); font-size: 13px; }

/* ── Charts grid ── */
.charts { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-top: 28px; }
@media (max-width: 800px) { .charts { grid-template-columns: 1fr; } }
.chart-box {
  background: var(--card-bg); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-sm); padding: 18px;
}
.chart-box.full { grid-column: 1 / -1; }
.chart-box h3 { margin: 0 0 14px; font-size: 15px; font-weight: 700; }

/* ── Heatmap table ── */
.heatmap-wrap { overflow-x: auto; border-radius: var(--radius-sm); }
.heatmap { border-collapse: collapse; font-size: 12px; width: 100%; }
.heatmap th, .heatmap td { border: 1px solid var(--border); padding: 8px 10px; text-align: center; white-space: nowrap; }
.heatmap th { background: var(--brand-soft); color: var(--brand-dark); font-weight: 700; }
.heatmap td.hourlbl { background: #f8fafc; font-weight: 700; text-align: center; }
.heatmap tr.heatmap-total td { font-weight: 700; background: var(--brand-soft); border-top: 2px solid var(--brand); }

/* ── Funnel (CSS thuần, không Chart.js) ──
   Hình thang cột TRÁI + nhãn (tên bước + số + % chuyển đổi) cột PHẢI, không chồng
   lấn lên nhau. % là tỷ lệ so với BƯỚC NGAY TRƯỚC (không phải so với Impressions)
   — có chú thích "vs {bước trước}" để khỏi đọc nhầm.
   ⚠️ SỬA (2026-07-12, user báo "không phủ đủ ô") — .chart-box mặc định KHÔNG co
   giãn theo chiều cao ô lưới (.charts là CSS grid, item tự stretch theo hàng cao
   nhất nhưng NỘI DUNG bên trong không tự lớn theo) — .funnel-box đổi sang flex
   column để .funnel + từng .funnel-row GIÃN LẤP ĐỦ chiều cao thật của khung, thay
   vì đứng im ở chiều cao cố định rồi để trống khoảng lớn bên dưới khi khung cao
   hơn (VD khung 2 cao bằng khung 1 có canvas cao).
   ⚠️ SỬA TIẾP (2026-07-12, user báo "vẫn dư bên phải quá") — cột nhãn cũ
   flex:1 chiếm HẾT phần rộng còn lại dù chữ chỉ cần một khoảng nhỏ, để trống hẳn
   dải trắng dài bên phải chữ. Đổi ngược lại: NHÃN có bề rộng CỐ ĐỊNH (vừa đủ chữ
   dài nhất, không bị bó), SHAPE lấy flex:1 (nuốt hết phần thừa còn lại) — hình
   thang giờ giãn full theo đúng khoảng trống thật, không còn dư thừa. */
.chart-box.funnel-box { display: flex; flex-direction: column; }
.funnel { flex: 1; display: flex; flex-direction: column; gap: 6px; }
.funnel-row { flex: 1; display: flex; align-items: center; gap: 16px; min-height: 40px; }
.funnel-shape-wrap { position: relative; height: 100%; flex: 1; min-width: 0; }
.funnel-shape { position: absolute; inset: 0; }
.funnel-label { flex: 0 0 auto; width: 168px; }
.funnel-label .fname { font-weight: 700; font-size: 12px; color: var(--text); }
.funnel-label .fval { font-size: 13px; color: var(--text-muted); margin-top: 2px; }
.funnel-label .fconv { color: var(--brand); font-weight: 700; }
.funnel-label .fconv-note { color: var(--text-faint); font-size: 11px; margin-left: 4px; }
@media (max-width: 700px) {
  .funnel-label { width: 130px; }
  .funnel-row { gap: 10px; }
}

/* ── Bảng Top SP (chart 7 — HTML table thuần, không Chart.js) ──
   Canvas Chart.js căn PHẢI nhãn trục Y sát biểu đồ -> tên SP dài bị cắt mất phần
   ĐẦU chữ khi chart hẹp. Dùng table.report có sẵn (header sort + zebra-stripe),
   thêm cột ảnh (từ PRODUCT_IMG_RAW) + cột mini bar GMV. Chiều cao dòng THẤP hơn
   bar-list cũ (không co giãn theo tên dài — tên chỉ 1 dòng, cắt "..." + xem đầy đủ
   qua tooltip title) để đỡ tốn diện tích. */
/* Bảng hiện ĐỦ mọi SP (không giới hạn 10 nữa, 2026-07-12) — khung cao ~10 dòng,
   cuộn dọc bên trong để xem tiếp thay vì kéo dài cả trang; header dính lại khi
   cuộn (sticky) để vẫn thấy tên cột. */
.table-topsp-scroll { max-height: 600px; overflow-y: auto; }
.table-topsp-scroll thead th { position: sticky; top: 0; z-index: 1; }
table.table-topsp td, table.table-topsp th { vertical-align: middle; }
table.table-topsp .sp-name {
  max-width: 260px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
table.table-topsp .sp-bar { width: 160px; }
table.table-topsp .sp-bar .bar-track { background: var(--brand-soft); border-radius: 6px; height: 10px; overflow: hidden; }
table.table-topsp .sp-bar .bar-fill { background: var(--brand); height: 100%; border-radius: 6px; min-width: 2px; }
.sp-thumb {
  width: 36px; height: 36px; border-radius: 6px; object-fit: cover;
  border: 1px solid var(--border); display: block; cursor: zoom-in;
  transition: transform .15s ease, box-shadow .15s ease;
}
.sp-thumb:hover { transform: scale(1.15); box-shadow: 0 4px 12px rgba(0,0,0,.18); }
.sp-thumb:active { transform: scale(.92); }
.sp-thumb-empty {
  display: flex; align-items: center; justify-content: center;
  background: var(--brand-soft); color: var(--text-faint); font-size: 12px;
  cursor: default;
}
/* Cột "Link" bảng Top SP (2026-07-15, theo yêu cầu user — link trang SP tự điền
   vào PRODUCT_IMG_RAW theo product_id) — icon nhỏ, gọn, không chiếm nhiều chỗ. */
.sp-link { font-size: 16px; text-decoration: none; display: inline-block; transition: transform .15s ease; }
.sp-link:hover { transform: scale(1.2); }
.sp-link-empty { color: var(--text-faint); }

/* ── Popup xem ảnh SP to (click thumbnail chart 7) — fade + zoom-in khi mở
   (2026-07-11, theo yêu cầu user "thêm animation khi bấm vào hình sản phẩm").
   Dùng opacity/visibility thay vì display để có transition mượt; visibility
   delay khi ẩn (delay .2s = đúng thời gian transition) để overlay hết hẳn
   click được mới biến mất khỏi layout, tránh chặn click phía dưới lúc đang mờ. ── */
.lightbox-overlay {
  display: flex; position: fixed; inset: 0; z-index: 1000;
  background: rgba(15, 23, 42, .72);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  align-items: center; justify-content: center; cursor: zoom-out;
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .2s ease, visibility 0s linear .2s;
}
.lightbox-overlay.show {
  opacity: 1; visibility: visible; pointer-events: auto;
  transition: opacity .2s ease, visibility 0s linear 0s;
}
.lightbox-overlay img {
  max-height: 70vh; max-width: 90vw; border-radius: 10px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, .5); cursor: default;
  transform: scale(.85); transition: transform .2s ease;
}
.lightbox-overlay.show img { transform: scale(1); }
.lightbox-close {
  position: absolute; top: 20px; right: 28px; color: #fff; font-size: 32px;
  line-height: 1; font-weight: 300; cursor: pointer; opacity: .85;
}
.lightbox-close:hover { opacity: 1; }

/* ── Report tables (8d) ── */
.report-table-wrap { overflow-x: auto; }
table.report {
  width: 100%; border-collapse: collapse; font-size: 13px;
}
table.report th {
  text-align: left; padding: 10px 12px; background: var(--brand-soft); color: var(--brand-dark);
  font-weight: 700; font-size: 12px; text-transform: uppercase; letter-spacing: .3px;
}
table.report td { padding: 9px 12px; border-bottom: 1px solid var(--border); }
table.report tbody tr:nth-child(even) { background: #fafbff; }
table.report tbody tr:hover { background: var(--brand-soft); }
table.report td.num, table.report th.num { text-align: right; font-variant-numeric: tabular-nums; }
table.report tr.report-total-row td { font-weight: 700; background: var(--brand-soft); border-top: 2px solid var(--brand); }
table.report td.rank {
  display: inline-flex; width: 22px; height: 22px; border-radius: 50%; background: var(--brand-soft);
  color: var(--brand-dark); align-items: center; justify-content: center; font-weight: 700; font-size: 11px;
}
table.report th.sortable { cursor: pointer; user-select: none; }
table.report th.sortable:hover { color: var(--brand); }
/* ⚠️ SỬA (2026-07-10): trước đây width:12px CỐ ĐỊNH dù rỗng — với cột số
   (text-align:right), span rỗng này chiếm chỗ BÊN PHẢI chữ tiêu đề, đẩy cả cụm
   "chữ+span" lệch trái 12px so với số liệu bên dưới (số liệu không có span nên
   sát phải hoàn toàn) -> tiêu đề nhìn "lệch" so với cột. Bỏ width cố định, để
   span tự co theo nội dung (rỗng thì gần như 0px, không đẩy lệch nữa) — chỉ khi
   ĐANG sort (có chữ ▾/▴) mới chiếm chỗ, chấp nhận cột hơi nhích nhẹ lúc đó. */
table.report th.sortable .sort-ind { display: inline-block; }

/* Popup hover danh sách ca chi tiết (bảng 10 Host + bảng 12 khung giờ, 2026-07-10) */
.table-hover-popup {
  display: none;
  position: fixed;
  z-index: 10000;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  padding: 6px;
  max-height: 240px;
  max-width: 320px;
  overflow-y: auto;
  font-size: 12px;
  pointer-events: none;   /* popup không chặn thao tác chuột, tránh nhấp nháy khi rê qua popup */
}
/* Popup KPI hover (2026-07-12, theo yêu cầu user "muốn copy được thông tin") —
   BẬT LẠI pointer-events riêng popup này (JS có độ trễ ẩn + lắng nghe hover trên
   chính popup, xem script cuối trang, nên không bị nhấp nháy như 2 popup còn lại
   vốn dựa vào pointer-events:none để tránh phải xử lý việc đó).
   2026-07-19: user báo bảng 10/12 dài phải CUỘN mà "di chuột vào thì mất" — 2
   popup này (hostSessionPopup/hourSessionPopup) giờ cũng dùng CÙNG cơ chế độ trễ
   ẩn + lắng nghe hover trên popup (xem ajax_dashboard.php), nên cũng cần bật
   pointer-events để nhận được thao tác cuộn/hover. */
#kpiHoverPopup, #hostSessionPopup, #hourSessionPopup { pointer-events: auto; user-select: text; }
.table-hover-popup table { border-collapse: collapse; width: 100%; }
.table-hover-popup th, .table-hover-popup td { padding: 4px 8px; text-align: left; white-space: nowrap; }
.table-hover-popup td.num, .table-hover-popup th.num { text-align: right; font-variant-numeric: tabular-nums; }
.table-hover-popup th { color: var(--text-muted); font-weight: 600; border-bottom: 1px solid var(--border); position: sticky; top: 0; background: var(--card-bg); }
.table-hover-popup tbody tr:nth-child(even) td { background: #fafbff; }

/* ── Admin (Phase 10, 2026-07-10): form user, tabs, banner thông báo ── */
.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; margin-bottom: 14px; }
@media (max-width: 700px) { .form-grid { grid-template-columns: 1fr; } }
.form-grid label { display: block; font-size: 12px; color: var(--text-muted); margin-bottom: 5px; font-weight: 600; }
/* 2026-08-13 (user: "ô chọn ngày không giống với chọn ngày ngoài trang upload ảnh")
   — danh sách type= ở đây thiếu date/time/file, nên input[type=date] trong bất kỳ
   .form-grid nào (VD admin.php tab "Gửi Zalo") rơi về giao diện MẶC ĐỊNH của trình
   duyệt (không viền/bo góc/theme giống các ô khác) — không phải cố ý, chỉ là danh
   sách cũ viết trước khi có ai dùng type=date/time/file bên trong .form-grid.
   Thêm đủ 3 type còn thiếu để MỌI input trong .form-grid luôn đồng nhất 1 kiểu,
   không phải patch riêng lẻ từng trang. */
.form-grid input[type=text], .form-grid input[type=password], .form-grid input[type=date],
.form-grid input[type=time], .form-grid input[type=file], .form-grid select {
  width: 100%; padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-size: 13px; box-sizing: border-box; background: #fff; color: var(--text);
}
.form-grid input:focus, .form-grid select:focus { outline: 2px solid var(--brand-soft); border-color: var(--brand); }
.chk-row { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
.chk-row label { display: flex; align-items: center; gap: 6px; font-weight: 500; color: var(--text); margin: 0; font-size: 13px; }
/* Bọc nhóm checkbox bằng <fieldset>/<legend> thay vì <label> rời (2026-08-01,
   audit accessibility — <label> không gắn với input NÀO cụ thể thì screen reader
   không đọc được là nhãn của nhóm nào; fieldset/legend là đúng ngữ nghĩa cho
   "1 nhãn cho NHIỀU checkbox"). Reset viền/padding mặc định của trình duyệt để
   giữ nguyên hình thức cũ (trước đây chỉ là dòng chữ thường, không khung viền). */
.chk-fieldset { border: none; padding: 0; margin: 0; }
.chk-fieldset legend { padding: 0; font-size: 12px; color: var(--text-muted); font-weight: 600; margin-bottom: 5px; }

.msg-banner { padding: 10px 14px; border-radius: var(--radius-sm); margin-bottom: 16px; font-size: 13px; }
.msg-banner.ok { background: #e6f7e9; color: #1a7d33; }
.msg-banner.err { background: #fdeceb; color: #c0392b; }
.msg-banner.warn { background: #fff4d6; color: #8a6600; }   /* 4.81:1 (trước: #a67c00 = 3.48:1, KHÔNG đạt AA) */

.tabs { display: flex; gap: 4px; margin-bottom: 18px; border-bottom: 1px solid var(--border); }
.tabs a { padding: 10px 16px; color: var(--text-muted); text-decoration: none; font-size: 13px; font-weight: 600; border-bottom: 2px solid transparent; }
.tabs a.active { color: var(--brand); border-bottom-color: var(--brand); }
.tabs a:hover { color: var(--brand-dark); }

.btn-sm { padding: 5px 12px; font-size: 12px; box-shadow: none; }
.btn-danger { background: var(--danger); }
.btn-danger:hover { background: #b91c1c; }
.btn-outline { background: transparent; color: var(--brand); border: 1px solid var(--brand); box-shadow: none; }
.btn-outline:hover { background: var(--brand-soft); color: var(--brand-dark); }
.btn[disabled] { opacity: .45; cursor: not-allowed; }
.btn[disabled]:hover { background: var(--brand); }

/* ── AI Analysis nhúng theo cụm chart/bảng (2026-07-10, thay trang analyze.php riêng) ── */
.ai-panel {
  background: var(--brand-soft); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 16px 18px; margin: 14px 0 24px; font-size: 13px;
}
.ai-panel-label {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .4px;
  color: var(--brand-dark); margin-bottom: 8px; display: flex; align-items: center; gap: 6px;
}
.ai-panel h4 { margin: 10px 0 4px; font-size: 14px; color: var(--brand-dark); }
.ai-panel h4:first-child { margin-top: 0; }
.ai-panel p { margin: 6px 0; line-height: 1.6; }
.ai-panel ul, .ai-panel ol { margin: 6px 0 6px 4px; padding-left: 20px; }
.ai-panel li { margin: 3px 0; line-height: 1.55; }
.ai-panel code { background: #fff; padding: 1px 5px; border-radius: 4px; font-size: 12px; }

/* ============================================================================
   APP SHELL — sidebar trái + topbar mobile (2026-07-11) — dùng chung qua
   lib/sidebar.php (render_app_topbar/render_app_sidebar/render_app_shell_script).
   Bắt đầu ở index.php, sẽ tái dùng khi redesign dashboard/admin/upload/... sau.
   ============================================================================ */
.app-shell { display: flex; min-height: 100vh; }
.app-main { flex: 1; padding: 36px 44px; }
/* Bề rộng CHUẨN cho các trang form 1 cột (index/product/trend/detail/upload/
   host_report) — 2026-07-11, sửa bug: mỗi trang trước đó tự đặt max-width khác
   nhau (760px vs 640px) khiến form nhìn to nhỏ lệch nhau. Đặt DÙNG CHUNG 1 chỗ,
   trang nào cần hẹp thì thêm class này vào <main>, KHÔNG tự ý đặt số khác nữa. */
.app-main-narrow { max-width: 760px; }
@media (max-width: 900px) { .app-main-narrow { max-width: 100%; } }

/* ── Sidebar ── */
.app-sidebar {
  flex: 0 0 240px; background: linear-gradient(180deg, var(--shell-dark-1) 0%, var(--shell-dark-2) 100%);
  color: #fff; display: flex; flex-direction: column; padding: 22px 16px;
  /* Ghim sidebar theo cửa sổ khi trang dài (2026-07-11, theo yêu cầu user) — trang
     dashboard/report có thể cao hơn nhiều màn hình, sidebar cần "chạy theo" khi
     cuộn thay vì cuộn mất luôn cùng nội dung. height:100vh + overflow-y:auto phòng
     hờ menu dài hơn màn hình thấp (hiếm nhưng an toàn, tự cuộn riêng bên trong). */
  position: sticky; top: 0; height: 100vh; overflow-y: auto;
}
.app-logo { display: flex; align-items: center; gap: 10px; font-weight: 700; font-size: 15px; padding: 0 8px 6px; }
.app-logo .ic {
  width: 32px; height: 32px; border-radius: 9px; background: rgba(255,255,255,.14);
  display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0;
}
/* Footer app (APP_FOOTER_TEXT) — nằm NGAY DƯỚI logo/title, TRÊN nhóm menu
   (2026-07-11, theo yêu cầu user — đổi từ vị trí đầu "trên hàng user ở đáy"
   sang đây). Canh trái khớp logo phía trên, viền dưới nhẹ làm ranh giới trước
   khi vào menu. */
.app-sidebar-footer-text {
  font-size: 10px; color: rgba(255,255,255,.35); text-align: left; padding: 0 8px 14px;
  border-bottom: 1px solid rgba(255,255,255,.1);
}
/* Nhóm menu tách hẳn khỏi title/footer phía trên 1 khoảng (theo yêu cầu user
   "dời cụm menu xuống dưới 1 chút tách ra khỏi title"). */
.app-nav { display: flex; flex-direction: column; gap: 2px; flex: 1; margin-top: 14px; }
.app-nav-item {
  display: flex; align-items: center; gap: 10px; padding: 9px 10px; border-radius: 8px;
  color: rgba(255,255,255,.78); font-size: 13px; font-weight: 600; text-decoration: none;
  border-left: 3px solid transparent; transition: background .12s, color .12s;
}
.app-nav-item .ic { font-size: 15px; width: 18px; text-align: center; flex-shrink: 0; }
.app-nav-item:hover { background: rgba(255,255,255,.07); color: #fff; text-decoration: none; }
.app-nav-item.active { background: rgba(255,255,255,.1); color: #fff; border-left-color: var(--hince); }

/* Nhóm "Upload dữ liệu" thu gọn/mở — tối giản menu, chỉ hiện mục con khi mở. */
.app-nav-toggle { width: 100%; background: none; border: none; cursor: pointer; font-family: inherit; text-align: left; }
.app-nav-toggle .chev { margin-left: auto; font-size: 10px; transition: transform .18s; opacity: .6; }
.app-nav-toggle[aria-expanded="true"] { color: #fff; }
.app-nav-toggle[aria-expanded="true"] .chev { transform: rotate(180deg); }

.app-nav-sub {
  max-height: 0; overflow: hidden; transition: max-height .2s ease;
  display: flex; flex-direction: column; gap: 1px; padding-left: 30px;
}
.app-nav-sub.open { max-height: 160px; }
.app-nav-subitem {
  padding: 7px 10px; border-radius: 7px; color: rgba(255,255,255,.6); font-size: 12.5px; font-weight: 600;
  text-decoration: none; position: relative;
}
.app-nav-subitem::before { content: "•"; position: absolute; left: -13px; color: rgba(255,255,255,.28); }
.app-nav-subitem:hover { background: rgba(255,255,255,.06); color: #fff; text-decoration: none; }
.app-nav-subitem.active { background: rgba(255,255,255,.1); color: #fff; }

.app-sidebar-foot { border-top: 1px solid rgba(255,255,255,.12); padding-top: 14px; margin-top: 14px; }
/* Nút VI/EN trong sidebar (2026-07-19) — nền sidebar tối, .lang-switch mặc định
   làm cho nền sáng (dùng ở dashboard.php trước đây) nên cần override riêng. */
.sidebar-lang-row { display: flex; justify-content: center; margin-bottom: 10px; }
.sidebar-lang-row .lang-switch {
  background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.16);
}
.sidebar-lang-row .lang-switch a { color: rgba(255,255,255,.55); }
.sidebar-lang-row .lang-switch a.lang-switch-active { color: #fff; }
.sidebar-lang-row .lang-switch a:hover { color: #fff; }
.sidebar-lang-row .lang-switch-sep { color: rgba(255,255,255,.2); }
/* .app-user giờ là link tới account.php (đổi mật khẩu) — thêm hover/radius cho
   giống 1 mục bấm được, không còn là khối tĩnh (2026-08-10). */
.app-user {
  display: flex; align-items: center; gap: 10px; padding: 6px 8px; margin: 0 -8px 4px;
  border-radius: 8px; text-decoration: none; transition: background .15s;
}
.app-user:hover { background: rgba(255,255,255,.08); text-decoration: none; }
.app-user-avatar {
  width: 32px; height: 32px; border-radius: 50%; background: linear-gradient(135deg, var(--hince), #ff8f70);
  display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 13px; flex-shrink: 0;
}
.app-user-name { font-size: 13px; font-weight: 700; color: #fff; line-height: 1.3; }
.app-user-role { font-size: 11px; color: rgba(255,255,255,.55); text-transform: capitalize; }
.app-logout {
  display: block; text-align: center; font-size: 12px; font-weight: 600; color: rgba(255,255,255,.7);
  padding: 8px; border-radius: 8px; border: 1px solid rgba(255,255,255,.16); text-decoration: none;
}
.app-logout:hover { background: rgba(255,255,255,.08); color: #fff; text-decoration: none; }

/* ── Topbar (chỉ hiện dưới 900px) — "☰" mở sidebar dạng ngăn kéo trượt từ trái,
   phía trên vẫn thấy thông tin đang đăng nhập. ── */
.app-topbar { display: none; }
.app-overlay { display: none; }

@media (max-width: 900px) {
  .app-shell { display: block; }

  .app-topbar {
    display: flex; align-items: center; justify-content: space-between;
    background: var(--shell-dark-1); color: #fff; padding: 10px 16px;
    position: sticky; top: 0; z-index: 60;
    box-shadow: 0 2px 10px rgba(0,0,0,.15);
  }
  .app-hamburger {
    width: 36px; height: 36px; flex-shrink: 0; border-radius: 8px; cursor: pointer;
    background: rgba(255,255,255,.1); border: 1px solid rgba(255,255,255,.2); color: #fff; font-size: 17px;
  }
  .app-topbar-user { display: flex; align-items: center; gap: 8px; }
  .app-topbar-user .app-user-name, .app-topbar-user .app-user-role { color: #fff; }
  .app-topbar-user .app-user-role { color: rgba(255,255,255,.55); }

  /* Sidebar biến thành ngăn kéo cố định, mặc định ẩn ngoài màn hình bên trái */
  .app-sidebar {
    position: fixed; top: 0; left: 0; bottom: 0; z-index: 100; flex: 0 0 240px;
    transform: translateX(-100%); transition: transform .22s ease; box-shadow: 0 0 0 rgba(0,0,0,0);
  }
  .app-sidebar.open { transform: translateX(0); box-shadow: 0 0 40px rgba(0,0,0,.35); }

  .app-overlay.show { display: block; position: fixed; inset: 0; background: rgba(15,23,42,.5); z-index: 90; }

  .app-main { padding: 20px 18px; }
}
