/*
 * Inter có đủ bộ chữ tiếng Việt (U+1EA0–1EF9, ơ, ư, ă, đ và ký hiệu ₫) — lý do TL3 §5.1 đổi từ Poppins, phông từng vẽ
 * ồ, ơ, ự bằng phông có chân của hệ điều hành ngay giữa chữ. Hai tệp dưới khai báo unicode-range theo từng bộ chữ nên
 * trình duyệt chỉ tải bộ đang dùng.
 */
@import '../fonts/inter.css';
@import '../fonts/jetbrains-mono.css';
@import './tokens.css';

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body,
#app {
  height: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--tx);
  font-family: var(--font-sans);
  font-size: var(--fs-md);
  line-height: var(--lh-base);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizelegibility;
}

h1,
h2,
h3,
h4 {
  margin: 0 0 0.5em;
  font-weight: 600;
  line-height: var(--lh-tight);
  letter-spacing: -0.011em;
  color: var(--tx);
}

h1 {
  font-size: var(--fs-xl);
}

h2 {
  font-size: var(--fs-lg);
}

p {
  margin: 0 0 0.8em;
}

a {
  color: var(--brand);
  text-decoration: none;
  transition: color var(--dur-1) var(--ease);
}

a:hover {
  text-decoration: underline;
}

code,
kbd,
.mono {
  font-family: var(--font-mono);
  font-size: 0.92em;
}

/* Các con số thẳng hàng theo cột: mã nhân viên, lương, ngày. */
.tabular {
  font-variant-numeric: tabular-nums;
}

/* Số kèm đơn vị, ngày, tỷ lệ không bị ngắt giữa chừng trong ô bảng hẹp. */
.nowrap {
  white-space: nowrap;
}

/* Ô trống (CellText): gạch ngang nhạt để mắt lướt qua; bảng dạng thẻ trên điện thoại bỏ hẳn dòng đó. */
.noValue {
  color: var(--tx3);
}

/* Không áp cho tiêu đề: FocusOnNavigate (App.razor) đưa tiêu điểm tới h1 của trang mỗi lần đổi route
   cho trình đọc màn hình, và tiêu điểm do mã đặt đó không được vẽ viền. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:not([tabindex='-1']):focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

/* Chắc thêm một lớp: vài bản Chromium tự vẽ viền mặc định cho phần tử được mã đặt tiêu điểm, dù nó
   không khớp :focus-visible ở trên, nên tắt hẳn theo tabindex. */
[tabindex='-1']:focus {
  outline: none;
}

::selection {
  background: var(--brand-lt);
  color: var(--tx);
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

::-webkit-scrollbar-thumb {
  border: 3px solid transparent;
  border-radius: 999px;
  background: var(--bd2);
  background-clip: content-box;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--tx3);
  background-clip: content-box;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/*
 * Hiệu ứng vào màn dùng chung cho mọi màn hình, dùng lần đầu ở thẻ đăng nhập: nội dung nổi lên đúng
 * chỗ theo thứ tự đọc. Đặt --i cho các phần tử anh em (style="--i:2") để chúng xuất hiện lần lượt.
 *
 * Hai animation với hai đường cong: dịch chuyển giảm tốc (nhanh lúc đầu, dừng từ từ), còn độ mờ bắt
 * đầu nhẹ. Một đường cong cho cả hai làm khối hiện rõ ngay trong vài khung hình đầu — chính là
 * cảm giác cứng. Chỉ dùng cho khối — không bao giờ gắn cho từng dòng của bảng hay danh sách.
 *
 * Ngắn để không bắt chờ: độ mờ xong trong 200ms, dịch chuyển 8px xong trong 320ms, mỗi khối sau chậm 40ms — màn sáu khối
 * đứng yên sau 520ms. Đây là hiệu ứng vào màn duy nhất: khung trang và vùng nội dung hiện ngay, không mờ dần chồng lên
 * (đo 03/10/2026: khung mờ dần 480ms sau đăng nhập cộng vùng nội dung mờ dần 320ms làm màn trắng gần nửa giây, xong hẳn sau 1 giây).
 */
.rise {
  animation:
    riseMove var(--dur-3) var(--ease-out) backwards,
    riseFade var(--dur-2) var(--ease) backwards;
  animation-delay: calc(var(--i, 0) * 40ms);
}

/*
 * Mọi hiệu ứng vào màn ở đây chỉ giữ khung đầu (backwards): giữ khung đầu trong lúc chờ lượt, rồi
 * nhả ra khi xong. Giữ cả khung cuối (forwards) sẽ để animation sống trên từng khối suốt thời gian
 * mở màn hình, cho một trạng thái cuối giống hệt style gốc của phần tử.
 */

@keyframes riseMove {
  from {
    transform: translateY(calc(8px * var(--travel)));
  }
}

@keyframes riseFade {
  from {
    opacity: 0;
  }
}

/*
 * Thay nội dung: ô giữ chỗ rời đi và dữ liệu thật vào thế chỗ. Chỉ đổi độ mờ — các dòng không được
 * trượt dưới mắt người đang nhìn vào bảng.
 */
.fade {
  animation: fade var(--dur-3) var(--ease) backwards;
}

@keyframes fade {
  from {
    opacity: 0;
  }
}

/* Chỉ báo chờ khi giảm chuyển động (con dấu chờ, vạch chờ của bảng): không di chuyển, mờ dần rồi rõ dần. */
@keyframes pulse {
  0%,
  100% {
    opacity: 0.3;
  }

  50% {
    opacity: 1;
  }
}

/*
 * Pop-up mở từ một nút (menu, danh sách của ComboBox, popover của MudBlazor, bộ lọc gập trên điện thoại): hiện dần và trượt
 * 6px từ phía nút ra — mở xuống thì trượt xuống, lật lên trên nút thì trượt lên (interop.js placeDropdown đặt data-drop="up").
 * Nhanh như đáp lại một cú bấm (--dur-2), chỉ đổi opacity và transform nên không tính lại bố cục ở khung hình nào, và mục bấm
 * được ngay từ khung hình đầu. Đóng thì mất ngay, không bắt người dùng chờ hiệu ứng.
 */
:root {
  --drop-shift: -6px;
}

[data-drop='up'] {
  --drop-shift: 6px;
}

@keyframes dropIn {
  from {
    opacity: 0;
    transform: translateY(calc(var(--drop-shift) * var(--travel)));
  }
}

/*
 * Sáng/tối: interop.js đổi giao diện bên trong document.startViewTransition, nên trình duyệt hòa hai
 * ảnh chụp của trang. Một lớp ảnh hòa trộn dù màn hình có bao nhiêu dòng — còn chuyển màu từng phần
 * tử thì phải vẽ lại tất cả ở mọi khung hình.
 */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: var(--dur-3);
  animation-timing-function: var(--ease);
}

:root[data-theme-switching] *,
:root[data-theme-switching] *::before,
:root[data-theme-switching] *::after {
  transition: none !important;
}

/*
 * MudBlazor (TL3 §5.2: MudDataGrid, MudChart) khoác cùng token thay cho bảng màu riêng của nó.
 * MudThemeProvider ghi biến :root ở cuối tài liệu, nên :root:root thắng; chế độ tối tự theo vì mọi
 * giá trị đều trỏ về tokens.css.
 */
:root:root {
  --mud-palette-primary: var(--brand);
  --mud-palette-primary-rgb: var(--brand-rgb);
  --mud-palette-primary-text: var(--on-brand);
  --mud-palette-primary-darken: var(--brand-dk);
  --mud-palette-primary-lighten: var(--brand-lt);
  --mud-palette-primary-hover: rgba(var(--brand-rgb), 0.06);
  --mud-palette-secondary: var(--tx2);
  --mud-palette-success: var(--ok);
  --mud-palette-success-rgb: var(--ok-rgb);
  --mud-palette-info: var(--info);
  --mud-palette-info-rgb: var(--info-rgb);
  --mud-palette-warning: var(--warn);
  --mud-palette-warning-rgb: var(--warn-rgb);
  --mud-palette-error: var(--dan);
  --mud-palette-error-rgb: var(--dan-rgb);
  --mud-palette-text-primary: var(--tx);
  --mud-palette-text-primary-rgb: var(--tx-rgb);
  --mud-palette-text-secondary: var(--tx3);
  --mud-palette-text-disabled: var(--tx3);
  --mud-palette-action-default: var(--tx2);
  --mud-palette-action-default-hover: var(--soft);
  --mud-palette-action-disabled: var(--tx3);
  --mud-palette-action-disabled-background: var(--soft);
  --mud-palette-background: var(--bg);
  --mud-palette-background-gray: var(--soft);
  --mud-palette-surface: var(--card);
  --mud-palette-divider: var(--bd);
  --mud-palette-divider-light: var(--bd);
  --mud-palette-lines-default: var(--bd);
  --mud-palette-lines-inputs: var(--bd2);
  --mud-palette-table-lines: var(--bd);
  --mud-palette-table-hover: var(--soft);
  --mud-palette-table-striped: var(--soft);
  --mud-palette-skeleton: var(--skeleton);
  --mud-palette-overlay-dark: var(--backdrop);
  --mud-default-borderradius: var(--radius);
  --mud-typography-default-family: var(--font-sans);
  --mud-typography-body1-family: var(--font-sans);
  --mud-typography-body2-family: var(--font-sans);
  --mud-typography-button-family: var(--font-sans);
  --mud-typography-button-text-transform: none;
  --mud-typography-caption-family: var(--font-sans);
  --mud-typography-subtitle2-family: var(--font-sans);
}

/*
 * Mọi MudDataGrid một diện mạo: tiêu đề cột kiểu nhãn nhỏ, chữ thường, viền theo token, không đổ bóng kiểu Material. Đặt toàn cục
 * vì CSS cô lập của từng trang không chạm được phần tử MudBlazor dựng ra.
 */
.mud-table {
  background: transparent;
  box-shadow: none;
}

.mud-table-head .mud-table-cell {
  border-bottom: 1px solid var(--bd);
  background: var(--soft);
  color: var(--tx3);
  font-size: var(--fs-sm);
  font-weight: 600;
  white-space: nowrap;
}

.mud-table-cell {
  padding: 10px 16px;
  border-bottom-color: var(--bd);
  color: var(--tx);
  font-family: var(--font-sans);
  font-size: var(--fs-md);
}

.mud-table-row:last-child .mud-table-cell {
  border-bottom: 0;
}

.mud-table-pagination {
  border-top: 1px solid var(--bd);
  color: var(--tx3);
  font-size: var(--fs-sm);
}

/* Cột thao tác ghim ở mép phải: bảng rộng hơn card (tiêu đề tiếng Anh dài hơn, bảng nhiều cột) thì phần còn lại cuộn ngang
   còn nút Sửa luôn thấy và bấm được. Nền đặc theo màu dòng (đổi khi rê chuột) để chữ cuộn qua không lộ bên dưới. */
.mud-table .rowActions {
  --row-bg: var(--card);
  position: sticky;
  right: 0;
  width: 1%;
  background: var(--row-bg);
  text-align: right;
  white-space: nowrap;
}

/* Chữ cuộn tới cột ghim thì mờ dần thay vì bị cắt thẳng; bảng vừa card thì dải mờ nằm trên phần đệm trống, không thấy. */
.mud-table .rowActions::before {
  content: '';
  position: absolute;
  inset: 0 100% 0 auto;
  width: 16px;
  background: linear-gradient(to right, transparent, var(--row-bg));
  pointer-events: none;
}

.mud-table-hover .mud-table-row:hover > .rowActions {
  --row-bg: var(--mud-palette-table-hover);
}

/*
 * Dòng vừa thêm hay vừa sửa (SavedRow): một lớp màu thương hiệu mờ phủ lên rồi nhạt dần — chỉ đổi độ mờ của lớp phủ, không tô lại
 * nền ở mọi khung hình. Dòng bảng phủ từng ô (ô thao tác đã là sticky, định vị sẵn, nên không đặt lại position cho nó); phần tử
 * khác (mục của một danh sách) phủ cả phần tử.
 */
.mud-table-row.justSaved > .mud-table-cell:not(.rowActions),
.justSaved:not(.mud-table-row) {
  position: relative;
}

.mud-table-row.justSaved > .mud-table-cell::after,
.justSaved:not(.mud-table-row)::after {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(var(--brand-rgb), 0.14);
  opacity: 0;
  pointer-events: none;
  animation: savedFlash 1.8s var(--ease);
}

/* Dải mờ của cột ghim tô màu nền ô lên lớp phủ của ô bên cạnh thành một vệt sáng: tạm bỏ trong lúc dòng sáng lên. */
.mud-table-row.justSaved > .rowActions::before {
  content: none;
}

@keyframes savedFlash {
  0%,
  35% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

/*
 * Bảng dài (nhật ký; danh sách nhân viên từ GĐ2): bảng cuộn trong khung cao vừa màn hình và tiêu đề cột dính trên đầu khung.
 * Tiêu đề không dính theo trang được vì khung bảng cuộn ngang (cột tiếng Anh dài, cột thao tác ghim) — CSS không cho dính theo
 * trang bên trong một khung cuộn. Gắn Class="viewportGrid" và FixedHeader="true" cho MudDataGrid; 136px chừa thanh trên cùng,
 * lề và dải phân trang bên dưới để cuộn trang tới đầu bảng là thấy trọn bảng lẫn nút sang trang.
 */
.viewportGrid .mud-table-container {
  max-height: calc(100dvh - var(--topbar-h) - 136px);
}

/* Tiêu đề dính của MudBlazor đổi nền sang màu card; giữ nền nhạt như mọi bảng khác. */
.mud-table-sticky-header .mud-table-root .mud-table-head .mud-table-cell {
  background: var(--soft);
}

/* position: sticky cho mỗi ô một lớp xếp chồng riêng, ô dòng sau vẽ đè lên ô dòng trước: menu đang mở bị nền ô dòng dưới che, và
   lớp phủ "bấm ra ngoài là đóng" của nó cũng bị che nên nút "⋯" dòng khác vẫn bấm được, mở chồng menu. Ô đang mở menu nổi lên trên
   hết — trên cả thanh trên cùng, như lớp phủ của menu khi không nằm trong ô ghim. */
.mud-table .rowActions:has([role='menu']) {
  z-index: 70;
}

/*
 * Bảng dạng thẻ trên điện thoại. MudBlazor luôn gắn lớp mud-xs-table nhưng chỉ đổi sang dạng thẻ dưới 600px (media query của
 * chính nó), nên mọi quy tắc dạng thẻ phải nằm trong cùng mốc — đặt ngoài là áp cho cả bảng trên máy tính.
 */
@media (width <= 600px) {
  /* Ô thao tác không có nhãn, trải hết thẻ và đẩy nút sang phải (width 1% ở trên làm viền dưới của thẻ chỉ dài bằng nút);
     thẻ không cuộn ngang nên không ghim, nền theo cả thẻ. */
  .mud-xs-table .rowActions,
  .mud-table-hover .mud-table-row:hover > .rowActions {
    position: static;
    width: auto;
    justify-content: flex-end;
    background: none;
  }

  .mud-xs-table .rowActions::before {
    content: none;
  }

  /* Mỗi ô là nhãn (::before) và giá trị cạnh nhau trong một hàng flex. Giá trị dài không có dấu cách (số quyết định
     0001/2026/QĐ-BN-…) không xuống dòng được thì đẩy cả thẻ rộng hơn màn, mọi giá trị và nút bên phải bị cắt — cho giá trị
     xuống dòng ở bất kỳ chỗ nào, nhãn giữ nguyên. .nowrap giữ số, ngày liền một dòng trong cột hẹp của bảng; ở dạng thẻ giá
     trị có cả dòng riêng nên chỉ xuống dòng khi dài hơn thẻ. */
  .mud-xs-table .mud-table-cell {
    overflow-wrap: anywhere;
  }

  .mud-xs-table .mud-table-cell::before {
    flex: none;
    overflow-wrap: normal;
  }

  .mud-xs-table .mud-table-cell.nowrap {
    white-space: normal;
  }

  /*
   * Thẻ gọn: mỗi dòng là một hàng flex xuống dòng — ô mang CellClass="cardTitle" (tên) đứng đầu thẻ, không nhãn, chữ đậm; nút
   * thao tác cùng hàng bên phải; ô trống (<CellText /> ra gạch ngang) bỏ hẳn; các ô còn lại sát nhau hơn. Thẻ thấp đi khoảng 40% so
   * với dạng mặc định của MudBlazor (mỗi ô một hàng cao 50px, nút thao tác một hàng riêng ở cuối).
   */
  .mud-xs-table .mud-table-body .mud-table-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    padding: 6px 0;
    border-bottom: 1px solid var(--bd);
  }

  .mud-xs-table .mud-table-body .mud-table-row:last-child {
    border-bottom: 0;
  }

  .mud-xs-table .mud-table-body .mud-table-row .mud-table-cell {
    flex: 1 1 100%;
    padding: 4px 16px;
    border-bottom: 0;
  }

  .mud-xs-table .mud-table-cell::before {
    color: var(--tx3);
    font-size: var(--fs-sm);
  }

  .mud-xs-table .mud-table-body .mud-table-row .cardTitle {
    order: -2;
    flex: 1 1 0;
    min-width: 0;
    font-weight: 600;
  }

  .mud-xs-table .mud-table-body .cardTitle::before {
    content: none;
  }

  .mud-xs-table .mud-table-body .mud-table-row .rowActions {
    order: -1;
    flex: none;
    padding-block: 0;
  }

  .mud-xs-table .mud-table-cell:has(> .noValue) {
    display: none;
  }

  /* Dạng thẻ không có tiêu đề cột để giữ: cuộn theo trang như mọi màn khác trên điện thoại. */
  .viewportGrid .mud-table-container {
    max-height: none;
  }
}

/*
 * Popover của MudBlazor (ô số dòng mỗi trang của MudDataGrid) cùng họ với ActionMenu và danh sách của ComboBox: viền,
 * bóng, mục cao --item-h thẳng lề 12px, mở bằng dropIn. MudBlazor đặt vị trí bằng top/left nên transform của dropIn không đụng
 * tới; khi lật lên trên nút nó đổi lớp sang mud-popover-bottom-*.
 */
.mud-popover.mud-paper {
  border: 1px solid var(--bd2);
  box-shadow: var(--shadow-lg);
}

.mud-popover-open {
  animation: dropIn var(--dur-2) var(--ease-out);
}

.mud-popover[class*='mud-popover-bottom-'] {
  --drop-shift: 6px;
}

.mud-popover .mud-list {
  padding: 4px;
}

.mud-popover .mud-list-item {
  min-height: var(--item-h);
  padding-block: 0;
  padding-inline: 12px;
  border-radius: var(--radius-sm);
}

/*
 * Vạch chờ của MudDataGrid khi tải trang dữ liệu từ máy chủ (ServerData, mẫu /ui/list): MudBlazor kéo hai vạch bằng left/right
 * — tính lại bố cục ở mọi khung hình, giật khi luồng chính đang dựng bảng (đo 03/10/2026). Thay bằng một vạch trượt bằng
 * transform, trình duyệt chạy trên GPU; giảm chuyển động thì vạch phủ ngang, mờ dần rồi rõ dần.
 */
.mud-progress-linear.mud-progress-indeterminate.horizontal .mud-progress-linear-bar:first-child {
  right: auto;
  left: 0;
  width: 40%;
  animation: barSlide 1.4s var(--ease) infinite;
}

.mud-progress-linear.mud-progress-indeterminate.horizontal .mud-progress-linear-bar:last-child {
  display: none;
}

@keyframes barSlide {
  from {
    transform: translateX(-100%);
  }

  to {
    transform: translateX(250%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .mud-progress-linear.mud-progress-indeterminate.horizontal .mud-progress-linear-bar:first-child {
    width: 100%;
    animation: pulse 1.6s ease-in-out infinite;
  }
}

/* Phần giữ chỗ #app của index.html trong lúc tải runtime WASM, và dải báo lỗi chưa xử lý của
   framework — cả hai là markup do template Blazor cấp, định kiểu cho khớp thương hiệu thay vì để
   mặc định trơn của trình duyệt. */
.boot {
  display: grid;
  gap: 14px;
  place-content: center;
  min-height: 100%;
  padding: 24px;
  /* Chờ một nhịp rồi mới hiện: khi đã có cache, runtime thường sẵn sàng trước, và chỉ báo chờ nháy vài
     khung hình trông giống lỗi hơn là đang chờ. */
  animation: fade var(--dur-3) var(--ease) 150ms backwards;
}

.bootLabel {
  margin: 0;
  color: var(--tx3);
  font-size: var(--fs-sm);
  text-align: center;
}

/*
 * Hiệu ứng chờ bằng con dấu K-Market — màn tải runtime (index.html) và PageLoader dùng chung markup này, nên đặt toàn cục
 * thay vì CSS cô lập. Nền là đĩa con dấu nhạt; màu xanh dâng từ đáy lên như giỏ hàng đầy dần, giữ một nhịp rồi mờ đi, lặp
 * lại; nét trắng của con dấu đứng yên phía trên.
 *
 * Chỉ một phần tử chuyển động: lớp màu .sealFill dịch chuyển và đổi độ mờ trong khung tròn đứng yên — trình duyệt chạy trên
 * GPU, không tính lại style hay vẽ lại khung nào, nên không giành luồng chính với Blazor đang khởi động. Không dùng cặp hiệu
 * ứng ngược chiều (khung trượt lên, con dấu bên trong trượt xuống bù lại): Chrome có GPU không bảo đảm hai hiệu ứng bắt đầu
 * cùng một khung hình — đo 03/10/2026 (Chrome 154, tỉ lệ màn hình 150%), con dấu lệch tới 6px, bị cắt mép và giật theo nhịp.
 */
.symbols {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.sealLoader {
  position: relative;
  display: block;
  width: 56px;
  height: 56px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 50%;
  /* Đĩa nhạt: màu con dấu pha với trong suốt nên đúng trên mọi nền (trang, card, sáng, tối). */
  background: color-mix(in srgb, var(--seal) var(--seal-ghost), transparent);
}

.sealFill {
  position: absolute;
  inset: 0;
  background: var(--seal);
  animation: sealRise 1.8s var(--ease) infinite;
}

/* Nét con dấu trên cùng, đĩa trong suốt: khe trong nét là lỗ thật (mask #kk-seal-cut) nên lộ lớp màu hay đĩa nhạt bên dưới. */
.sealMarks {
  --seal-disc: transparent;
  --seal-mark: var(--on-seal);

  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

@keyframes sealRise {
  0% {
    opacity: 1;
    transform: translateY(100%);
  }

  55%,
  78% {
    opacity: 1;
    transform: translateY(0);
  }

  100% {
    opacity: 0;
    transform: translateY(0);
  }
}

/* Tắt hiệu ứng hoạt ảnh: không dịch chuyển, lớp màu phủ kín đĩa, mờ dần rồi rõ dần — vẫn cho biết đang làm việc. */
@media (prefers-reduced-motion: reduce) {
  .sealFill {
    animation-name: pulse;
    animation-duration: 1.6s;
    animation-timing-function: ease-in-out;
  }
}

#blazor-error-ui {
  position: fixed;
  inset: auto 0 0;
  z-index: 1000;
  display: none;
  padding: 14px 3.5rem 14px 20px;
  background: var(--dan);
  color: var(--on-brand);
  box-shadow: var(--shadow-lg);
}

#blazor-error-ui .reload {
  margin-left: 8px;
  color: inherit;
  font-weight: 600;
  text-decoration: underline;
}

#blazor-error-ui .dismiss {
  position: absolute;
  inset: 10px 20px auto auto;
  cursor: pointer;
}
