/* =============================================================================
   HIỆU ỨNG MỞ/ĐÓNG CHUNG CHO MỌI LỚP PHỦ (N3, ADR-0051)

   Người dùng (23/09): «các popup, modal mở lên có hiệu ứng hết nhé» — kèm ảnh bảng Cảnh báo bật ra đột ngột.
   MỘT cơ chế cho cả site, không phải mỗi trang một kiểu: thời lượng, đường cong, độ trượt là BIẾN ở :root dưới
   đây; mỗi loại lớp phủ chỉ khai nó mở/đóng bằng cách nào (thuộc tính hidden, <dialog open>, lớp .show…).

   Cách làm: CSS thuần — @starting-style cho lượt MỞ (kể cả phần tử vừa chèn vào DOM), transition-behavior
   allow-discrete trên display/overlay cho lượt ĐÓNG (trình duyệt giữ phần tử hiện thêm đúng thời lượng đóng rồi
   mới display:none). Không đụng mã mở/đóng của từng trang: trang vẫn đặt el.hidden = true/false như cũ.
   Hai việc CSS không tự làm được (chờ đóng xong rồi mới gỡ khỏi DOM / trả phần tử mượn về chỗ cũ) nằm ở
   wwwroot/js/hieu-ung.js (HieuUng.sauKhiDong, HieuUng.go).

   Chỉ dùng translate/scale (thuộc tính RIÊNG), không dùng transform: lớp phủ nào đang tự định vị bằng transform
   (hộp chi tiết bảng điều khiển kéo dời bằng style.transform, hộp nổi giữa màn translate(-50%,-50%)) không bị
   đè. translate/scale không làm dịch bố cục (không tính vào CLS) — lớp phủ đều nằm ngoài luồng (absolute/fixed/
   top layer) nên bật display cũng không đẩy khối nào.

   Trình duyệt cũ (Firefox ESR 102/115) không hiểu @starting-style/allow-discrete: bỏ qua lặng lẽ, lớp phủ mở/đóng
   tức thì như trước — không vỡ, không nháy.

   THÊM LỚP PHỦ MỚI: (1) gắn data-hieu-ung (thả xuống từ trên; ="len" nếu mở lên trên nút) và mở/đóng bằng
   thuộc tính hidden; hoặc (2) dùng <dialog> + showModal()/close(); hoặc (3) thêm bộ chọn vào đúng nhóm bên dưới.
   Gỡ khỏi DOM khi đóng thì gọi HieuUng.go(el) thay vì removeChild. Không tự đặt thời lượng riêng.
   ========================================================================== */
:root {
  --hu-mo: 180ms;                              /* mở: 150–200 ms */
  --hu-dong: 140ms;                            /* đóng: 120–150 ms — đóng nhanh hơn mở, người dùng đã muốn đi */
  --hu-cong-mo: cubic-bezier(.16, 1, .3, 1);   /* giảm tốc: bật ra nhanh, đậu nhẹ */
  --hu-cong-dong: cubic-bezier(.4, 0, 1, 1);   /* tăng tốc: rời đi dứt khoát */
  --hu-truot: 6px;                             /* độ trượt của thực đơn thả xuống, danh sách chọn, thông báo */
  --hu-phong: .97;                             /* hộp thoại phóng 0,97 → 1 */
}
/* Giảm chuyển động: không trượt, không phóng — chỉ còn mờ rất nhanh. */
@media (prefers-reduced-motion: reduce) {
  :root { --hu-mo: 80ms; --hu-dong: 60ms; --hu-truot: 0px; --hu-phong: 1; }
}

/* ---------------------------------------------------------------- 1. Thả xuống mở/đóng bằng thuộc tính hidden
   --hu-huong: -1 trượt từ phía trên xuống (nút gọi ở trên); 1 trượt từ dưới lên (thực đơn mở lên trên nút).
   Nhóm menu đã bị chuyển vào bảng «Thêm» KHÔNG thuộc nhóm này: ở đó nó là ngăn gấp trượt chiều cao riêng
   (#danhSachMenuThem, dau-trang.css) — nên bộ chọn neo vào .menu-hang. */
.tai-khoan-menu,
.menu-them-danh-sach,
.menu-hang .menu-nhom-danh-sach,
.hop-cb-bang,
.dash-menu-o.dash-menu-noi,
.sd-thuc-don,
[data-hieu-ung] {
  --hu-huong: -1;
  transition:
    opacity var(--hu-mo) var(--hu-cong-mo),
    translate var(--hu-mo) var(--hu-cong-mo),
    display var(--hu-mo) allow-discrete;
}
#dash-menu-cai,
[data-hieu-ung="len"] { --hu-huong: 1; }
:is(.tai-khoan-menu, .menu-them-danh-sach, .menu-hang .menu-nhom-danh-sach, .hop-cb-bang,
    .dash-menu-o.dash-menu-noi, .sd-thuc-don, [data-hieu-ung])[hidden] {
  opacity: 0;
  translate: 0 calc(var(--hu-huong) * var(--hu-truot) * .67);
  pointer-events: none;
  transition-duration: var(--hu-dong);
  transition-timing-function: var(--hu-cong-dong);
}
@starting-style {
  :is(.tai-khoan-menu, .menu-them-danh-sach, .menu-hang .menu-nhom-danh-sach, .hop-cb-bang,
      .dash-menu-o.dash-menu-noi, .sd-thuc-don, [data-hieu-ung]):not([hidden]) {
    opacity: 0;
    translate: 0 calc(var(--hu-huong) * var(--hu-truot));
  }
}

/* ---------------------------------------------------------------- 2. Danh sách của ô chọn (Tom Select)
   Thư viện mở/đóng bằng style.display NGAY TRÊN THẺ (display:block / display:none) — trạng thái đóng nhận theo
   chính thuộc tính style đó. Cơ chế đính <body> và định vị của dung-control-trang.js giữ nguyên: thư viện đo
   offsetHeight (không tính translate) nên phép lật lên/kẹp chiều cao không lệch. */
.ts-dropdown {
  --hu-huong: -1;
  transition:
    opacity var(--hu-mo) var(--hu-cong-mo),
    translate var(--hu-mo) var(--hu-cong-mo),
    display var(--hu-mo) allow-discrete;
}
.ts-dropdown.ts-dropdown-flip-up { --hu-huong: 1; }
.ts-dropdown[style*="display: none"] {
  opacity: 0;
  translate: 0 calc(var(--hu-huong) * var(--hu-truot) * .67);
  pointer-events: none;
  transition-duration: var(--hu-dong);
  transition-timing-function: var(--hu-cong-dong);
}
@starting-style {
  .ts-dropdown { opacity: 0; translate: 0 calc(var(--hu-huong) * var(--hu-truot)); }
}

/* ---------------------------------------------------------------- 3. Lịch chọn ngày (flatpickr)
   Thư viện có sẵn hoạt cảnh fpFadeInDown 300 ms — dài quá khung 150–200 ms: thay bằng cơ chế chung.
   Mở/đóng bằng lớp .open (display + opacity + visibility cùng đổi). arrowBottom: lịch nằm TRÊN ô nhập. */
.flatpickr-calendar.flatpickr-calendar.animate.open { animation: none; }
.flatpickr-calendar:not(.inline) {
  --hu-huong: -1;
  transition:
    opacity var(--hu-mo) var(--hu-cong-mo),
    translate var(--hu-mo) var(--hu-cong-mo),
    visibility var(--hu-mo) allow-discrete,
    display var(--hu-mo) allow-discrete;
}
.flatpickr-calendar.arrowBottom:not(.inline) { --hu-huong: 1; }
.flatpickr-calendar:not(.inline):not(.open) {
  translate: 0 calc(var(--hu-huong) * var(--hu-truot) * .67);
  pointer-events: none;
  transition-duration: var(--hu-dong);
  transition-timing-function: var(--hu-cong-dong);
}
@starting-style {
  .flatpickr-calendar.open:not(.inline) { opacity: 0; translate: 0 calc(var(--hu-huong) * var(--hu-truot)); }
}

/* Thực đơn ⚙ của sơ đồ (<details>): lượt MỞ mờ dần/trượt tới (transition + @starting-style dưới đây, đã đo đúng —
   tools/giaodien/kiem-hieu-ung.js). Lượt ĐÓNG CỐ Ý không có hiệu ứng — "display" KHÔNG nằm trong transition (không
   allow-discrete): ĐÃ THỬ thêm để mờ dần cả lúc đóng (ADR-0051, Leader 24/09) nhưng đo được (dbg thủ công) rằng
   Chromium co nội dung của <details> theo cơ chế riêng, không cộng tác đúng với transition khai trên phần tử
   con — display/opacity có thể đứng yên ở giá trị "đang mở" HÀNG GIÂY sau khi [open] đã mất, rủi ro menu không
   bao giờ biến mất thật. Giữ [open] tự nhiên của trình duyệt cho lượt đóng: tắt tức thì, không có gì để kẹt. */
.sd-dock > .sd-dock-ds {
  transition:
    opacity var(--hu-mo) var(--hu-cong-mo),
    translate var(--hu-mo) var(--hu-cong-mo);
}
@starting-style {
  .sd-dock[open] > .sd-dock-ds { opacity: 0; translate: 0 calc(-1 * var(--hu-truot)); }
}

/* ---------------------------------------------------------------- 5. Hộp thoại <dialog> (site.js, Dựng báo cáo,
   Liên thông, hộp xem trọn ô của bộ control): phóng nhẹ 0,97 → 1 từ giữa, nền mờ dần.
   Lượt ĐÓNG cần giữ hộp trong top layer thêm một nhịp (overlay allow-discrete) — trình duyệt chưa có overlay
   (Firefox) mà vẫn chạy lượt đóng thì hộp rơi khỏi top layer và nhảy chỗ giữa chừng, nên lượt đóng chỉ bật
   trong @supports (overlay: auto); ở đó chỉ còn lượt mở. */
dialog {
  transition:
    opacity var(--hu-mo) var(--hu-cong-mo),
    scale var(--hu-mo) var(--hu-cong-mo);
}
dialog::backdrop { transition: opacity var(--hu-mo) linear; }
@starting-style {
  dialog[open] { opacity: 0; scale: var(--hu-phong); }
  dialog[open]::backdrop { opacity: 0; }
}
@supports (overlay: auto) {
  dialog {
    transition:
      opacity var(--hu-mo) var(--hu-cong-mo),
      scale var(--hu-mo) var(--hu-cong-mo),
      overlay var(--hu-mo) allow-discrete,
      display var(--hu-mo) allow-discrete;
  }
  dialog:not([open]) {
    opacity: 0;
    scale: var(--hu-phong);
    transition-duration: var(--hu-dong);
    transition-timing-function: var(--hu-cong-dong);
  }
  dialog::backdrop {
    transition:
      opacity var(--hu-mo) linear,
      overlay var(--hu-mo) allow-discrete,
      display var(--hu-mo) allow-discrete;
  }
  dialog:not([open])::backdrop { opacity: 0; transition-duration: var(--hu-dong); }
  /* Giữ HÌNH HỌC của hộp trong lúc mờ đi. close() gỡ [open] và :modal ngay tức khắc: không giữ thì hộp rơi về kiểu
     «dialog không modal» của trình duyệt (position:absolute, cao/rộng theo nội dung) và luật `dialog.hop-thoai[open]
     { flex-direction: column }` hết hiệu lực — đo được (kiem-hieu-ung.js): thân hộp «Lưu mẫu» nhảy từ 858 px xuống
     456 px, CLS 0,009 ngay giữa lượt đóng. :where() để mọi luật kích thước riêng của từng hộp vẫn thắng. */
  :where(dialog:not([open])) {
    position: fixed;
    inset-block: 0;
    max-width: calc(100% - 6px - 2em);
    max-height: calc(100% - 6px - 2em);
  }
  dialog.hop-thoai:not([open]) { flex-direction: column; }
}

/* ---------------------------------------------------------------- 6. Hộp thoại Bootstrap của bộ control (dialogDiv:
   chi tiết ô bảng điều khiển, chi tiết nút sơ đồ, thêm/sửa ô…). Bootstrap đã có lớp .fade/.show và tự chờ
   transitionend theo thời lượng đọc từ CSS — chỉ thay thời lượng/đường cong, và đổi «trượt 50px trong 300 ms»
   thành phóng nhẹ. Hộp chi tiết bảng điều khiển kéo dời bằng style.transform của .modal-dialog: phóng bằng
   thuộc tính scale riêng nên hai thứ cộng dồn, không đè nhau (và kéo dời không còn bị trễ theo transition cũ). */
.modal.fade { transition: opacity var(--hu-mo) var(--hu-cong-mo); }
.modal.fade:not(.show) { transition-duration: var(--hu-dong); transition-timing-function: var(--hu-cong-dong); }
.modal.fade .modal-dialog {
  transform: none;
  scale: var(--hu-phong);
  transition: scale var(--hu-mo) var(--hu-cong-mo);
}
.modal.fade.show .modal-dialog { scale: 1; }
.modal-backdrop.fade { transition: opacity var(--hu-mo) linear; }
.modal-backdrop.fade:not(.show) { transition-duration: var(--hu-dong); }

/* ---------------------------------------------------------------- 7. Thông báo nổi
   Thông báo của cơ chế AJAX chung (ajax-chung.js): trượt lên từ góc dưới; đóng/tự tắt qua HieuUng.go. */
.ajax-thong-bao .ajax-tb {
  transition:
    opacity var(--hu-mo) var(--hu-cong-mo),
    translate var(--hu-mo) var(--hu-cong-mo);
}
@starting-style {
  .ajax-thong-bao .ajax-tb { opacity: 0; translate: 0 var(--hu-truot); }
}
/* Trạng thái «đang rời đi» chung cho mọi phần tử gỡ bằng HieuUng.go (hieu-ung.js). !important có chủ ý: đây là trạng thái
   tạm vài chục mili giây trước khi phần tử bị gỡ, phải thắng luật hình thức riêng của phần tử (vd `.ajax-thong-bao
   .ajax-tb` khai transition đặc hiệu hơn) — hiệu ứng vẫn chạy vì transition đứng trên mọi khai báo, kể cả !important. */
[data-hu-dong] {
  opacity: 0 !important;
  translate: 0 calc(var(--hu-truot) * .67) !important;
  pointer-events: none !important;
  transition-duration: var(--hu-dong) !important;
  transition-timing-function: var(--hu-cong-dong) !important;
}
/* Hộp nổi và hộp khoá màn của thư viện His (05-dialog-button.js): hoạt cảnh riêng 300 ms vào / 220 ms ra — chỉ
   rút thời lượng về khung chung (bộ chọn lặp lớp: CSS thư viện chèn SAU tệp này, cùng độ ưu tiên thì nó thắng).
   Thư viện gỡ hộp nổi sau 180 ms kể từ lúc bắt đầu ra, nên lượt ra 140 ms chạy trọn. */
.his-toast.his-toast, .his-mdl.his-mdl { animation-duration: var(--hu-mo); }
.his-toast.his-toast.out { animation-duration: var(--hu-dong); }
.his-mdl-bd.his-mdl-bd { transition: opacity var(--hu-mo) linear; }
@starting-style {
  .his-mdl-bd.his-mdl-bd { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  /* Hoạt cảnh của thư viện có dịch chuyển trong khung hình — không tắt hẳn (thư viện chờ nó), rút gần bằng 0. */
  .his-toast.his-toast, .his-toast.his-toast.out, .his-mdl.his-mdl { animation-duration: 1ms; }
}

/* ---------------------------------------------------------------- 8. Lớp nổi khác của thư viện (thực đơn và
   bảng lọc của lưới, hộp xem trọn ô, chú giải Bootstrap): chỉ lượt MỞ. Thư viện tự ẩn/gỡ chúng — không thêm
   display vào transition để lượt đóng giữ nguyên hành vi của thư viện (không treo một lớp vô hình nhận bấm). */
.tabulator-menu,
.tabulator-popup-container,
.excel-filter-panel,
.exgrid-hdr-menu,
.exgrid-cc-popup,
.exgrid-panel-loc,
.benh-lkpop,
.grid-fullcell-box {
  transition:
    opacity var(--hu-mo) var(--hu-cong-mo),
    translate var(--hu-mo) var(--hu-cong-mo);
}
@starting-style {
  .tabulator-menu, .tabulator-popup-container, .excel-filter-panel, .exgrid-hdr-menu, .exgrid-cc-popup,
  .exgrid-panel-loc, .benh-lkpop, .grid-fullcell-box {
    opacity: 0;
    translate: 0 calc(-1 * var(--hu-truot));
  }
}
.tooltip.fade, .popover.fade { transition: opacity var(--hu-mo) var(--hu-cong-mo); }

@media print {
  /* Bản in chụp đúng trạng thái cuối, không bao giờ giữa chừng một hiệu ứng. */
  *, *::before, *::after, ::backdrop { transition: none !important; animation: none !important; }
}
