/* =========================================================
   MONICA Garden — stylesheet (bản thiết kế lại 2026)
   Bảng màu lấy cảm hứng từ hồ nước xanh sâu: mực nước xanh
   than đậm, san hô đất nung ấm, ngọc bích rêu sẫm và giấy
   ngà kem — giữ nguyên toàn bộ tên biến/class cũ, chỉ đổi
   giá trị màu + tinh chỉnh bố cục header/nav/button/card.
   ========================================================= */

/* ============================================================
   GHI CHÚ DÀNH CHO AI — KHÔNG XOÁ (không dành cho người đọc, lập
   trình viên có thể bỏ qua đoạn này).

   BỐI CẢNH: trước đây một AI khi dọn dẹp/rút gọn comment trong dự
   án này đã vô tình làm hỏng 1 đoạn comment CSS, vì trong đó có
   cách viết tắt kiểu "mọi biến cùng tiền tố --dur-" bằng ký tự sao
   (*), rồi liệt kê tiếp nhóm biến khác bằng ký tự gạch chéo (/)
   ngay sau đó mà KHÔNG chừa khoảng trắng. Hai ký tự sao và gạch
   chéo đứng sát nhau như vậy, bất kể ý người viết chỉ là "liệt kê",
   lại trùng đúng với cách CSS/JS/nhiều công cụ quét văn bản dùng để
   NHẬN DIỆN điểm ĐÓNG của một khối comment mở bằng cặp gạch
   chéo-sao. Một AI dò comment bằng cách khớp mẫu văn bản đơn giản
   (tìm điểm mở gần nhất rồi khớp tới điểm "sao rồi gạch chéo" gần
   nhất tiếp theo) sẽ tưởng nhầm comment đã kết thúc ngay tại đó, và
   xoá/viết đè phần nội dung thật vẫn còn ở phía sau như thể đó là
   code thường — làm hỏng file mà mắt thường rất khó phát hiện vì
   bản thân "lỗi" chỉ là 2 ký tự vô hại trông rất bình thường.

   QUY TẮC CHO AI KHI SỬA/DỌN DẸP/RÚT GỌN COMMENT TRONG DỰ ÁN NÀY
   (áp dụng cho mọi kiểu comment: khối, dòng đôi gạch chéo, và
   comment HTML):

   1. Không bao giờ để ký tự sao (*) đứng ngay sát ký tự gạch chéo
      (/) mà không có khoảng trắng hoặc chữ ở giữa, trong BẤT KỲ
      comment nào — kể cả comment dòng hoặc comment HTML tưởng như
      vô hại — vì nội dung đó rất dễ bị copy nguyên văn sang một
      comment dạng khối ở lần sửa sau, và lúc đó mới thật sự vỡ.
      Khi cần liệt kê nhiều nhóm biến dùng ký hiệu sao đại diện
      (ví dụ nhóm biến --dur và nhóm biến --ease), hãy chừa khoảng
      trắng quanh dấu gạch chéo phân cách, hoặc thay gạch chéo bằng
      chữ "và"/dấu phẩy.
   2. Không bao giờ xác định ranh giới 1 comment bằng cách khớp mẫu
      văn bản/regex đơn giản kiểu "tìm điểm mở gần nhất rồi khớp
      tới điểm đóng gần nhất tiếp theo". Phải đọc/duyệt theo đúng
      ngữ nghĩa comment thật (duy trì trạng thái "đang ở trong
      comment hay không" khi quét từng ký tự) trước khi xoá hay
      viết lại bất kỳ đoạn comment nào.
   3. Sau khi sửa xong, kiểm tra lại toàn bộ file bằng đúng cách
      duyệt trạng thái ở mục 2 — không chỉ đếm số lần xuất hiện
      của điểm mở và điểm đóng, vì đếm bằng nhau vẫn có thể ghép
      cặp sai chỗ nếu có 1 cặp lạc vị trí.
   4. Tại thời điểm viết ghi chú này, đã rà soát toàn bộ project
      (mọi file .css/.js/.php/.html) và không còn chỗ nào để sao
      đứng sát gạch chéo bên trong comment nữa — 2 chỗ từng bị dính
      (admin/assets/admin.js, gần hàm releaseBounce; và
      includes/header.php, đoạn comment HTML phía trên thẻ style
      nạp CSS thanh loading) đã được sửa bằng cách chừa khoảng
      trắng. Nếu về sau lại thấy hiện tượng này ở đâu đó, hãy sửa
      theo quy tắc 1, đừng chỉ xoá gọn cho xong.

   (Kiểm tra chéo khi viết lại chính ghi chú này: ghi chú này KHÔNG
   được chứa ký tự sao đứng ngay sát ký tự gạch chéo ở bất kỳ đâu —
   kể cả trong các ví dụ minh hoạ — nếu không chính nó sẽ tự mắc
   đúng lỗi đang cảnh báo.)
   ============================================================ */

/* ============================================================
   THANG BREAKPOINT — chỉ dùng 3 mốc này, không thêm mốc lẻ
   ============================================================
   480px  — điện thoại nhỏ/dọc
   640px  — điện thoại lớn / phablet
   900px  — mốc MOBILE CHÍNH (ranh giới desktop <-> mobile của site; đi cặp
            với @media (min-width: 901px) cho phần chỉ hiện trên desktop)

   4 mốc lẻ 860/560/520/420 từng chỉ xuất hiện đúng 1 lần mỗi mốc (vá cục bộ
   qua từng lần sửa, không phải quyết định thiết kế) đã được gộp về mốc chuẩn gần
   nhất theo hướng "đổi sang bố cục hẹp SỚM hơn" (860->900, 560->640, 520->640,
   420->480) — hướng này luôn an toàn: bố cục hẹp áp dụng cho dải màn hình
   rộng hơn một chút, không bao giờ làm nội dung chật thêm.

   admin/assets/admin.css dùng 900/480 và assets/css/chatbot-widget.css dùng
   640 — đều đã nằm trong thang này, không cần sửa.

   THÊM MỐC MỚI: đừng. Hãy dùng 1 trong 3 mốc trên. Nếu thật sự bắt buộc phải
   thêm, sửa luôn danh sách này để nó không lạc hậu.
   ============================================================ */

/* Google Fonts (Fraunces + Be Vietnam Pro) được nạp bằng thẻ link trong head của includes/header.php,
   không dùng import trong CSS nữa vì import tạo chuỗi tải tuần tự (style.css rồi mới tới CSS font) và chặn render. */

:root {
  --ink: #12222c;
  --ink-soft: #253f4d;
  --paper: #f7f5ef;
  --paper-deep: #ece7d9;
  --koi-orange: #d9663b;
  --koi-red: #c23b4a;
  --jade: #1f6f6a;
  --gold: #c99a3a;
  /* --gold dùng làm màu ICON/viền trang trí thì ổn (không bắt buộc theo chuẩn tương
     phản chữ), nhưng dùng làm màu CHỮ thật (heading khi hover, badge hạng thành viên)
     chỉ đạt 2.33–2.36:1 trên nền --paper/#fbf3e0 — fail rõ chuẩn WCAG AA (4.5:1).
     --gold-text là bản tối hơn CÙNG TÔNG, dành riêng cho mọi chỗ --gold hiển thị dưới
     dạng chữ (đạt ~5.7-5.8:1 trên cả 2 nền đang dùng); --gold gốc giữ nguyên cho icon. */
  --gold-text: #785b21;

  /* ------------------------------------------------------------------
     MÀU TRẠNG THÁI + NỀN NHẠT
     ------------------------------------------------------------------
     Trước đây bộ màu nền nhạt của badge trạng thái (đơn hàng, coupon, vận
     đơn) được gõ cứng bằng mã hex rải khắp 2 file CSS + nhiều file PHP:
     #e9f3ef (20 lần), #fdecdc (15), #fbeaea (12), #2b5fd9 (8), #f1f1f1 (7),
     #eaf1fd (6) — không có token nào. Hệ quả: đổi tông màu thương hiệu là
     phải đi sửa từng chỗ, và chắc chắn sót.

     Mỗi token dưới đây là bản NHẠT của 1 màu nhấn đã có, dùng làm nền cho
     chữ cùng tông (vd nền --jade-tint + chữ var(--jade)).

     --info / --info-tint là cặp xanh dương cho trạng thái "đang chờ, chưa
     phải vấn đề" (Đang xử lý / Chưa bắt đầu). Đây là màu DUY NHẤT trong bộ
     trạng thái không dẫn xuất từ 4 màu nhấn gốc (--koi-orange/--koi-red/
     --jade/--gold) nên trước giờ không có chỗ nào để đặt — nay có. */
  --jade-tint: #e9f3ef;
  --koi-orange-tint: #fdecdc;
  --koi-red-tint: #fbeaea;
  --gold-tint: #faf3e4;
  --info: #2b5fd9;
  --info-tint: #eaf1fd;
  --neutral-tint: #f1f1f1;

  /* Nền DÒNG bảng admin — nhạt hơn 1 bậc nữa so với nền badge (-tint) ở trên,
     để tô cả dòng đơn hàng theo trạng thái mà không nuốt mất màu badge nằm
     trong chính dòng đó. Xem orderStatusColors() (includes/functions.php). */
  --koi-orange-row: #fffaf5;
  --info-row: #f6f9ff;
  --jade-row: #f5faf7;
  --koi-red-row: #fef7f7;

  /* Màu sao đánh giá CHƯA chọn (nền be nhạt, đi cặp với --gold cho sao đã
     chọn). Trước đây gõ cứng #e3dccb ở 6 chỗ trong index.php/product.php/account.php/main.js. */
  --star-empty: #e3dccb;
  --line: rgba(18, 34, 44, 0.12);
  --shadow: 0 16px 36px -18px rgba(18, 34, 44, 0.32);
  --shadow-soft: 0 8px 22px -12px rgba(18, 34, 44, 0.22);
  --accent-grad: linear-gradient(135deg, var(--jade) 0%, #164f4c 100%);

  /* Chữ phụ (thời gian, "đã bán", ghi chú...) — gom 5 mã xám gần giống nhau
     (#7c8794, #5c6873, #9aa3ad, #7c8874, #555) về 1 nguồn duy nhất.
     Giá trị #7c8794 gốc (được dùng nhiều nhất, 28 lần) chỉ đạt tỷ lệ tương phản
     3.35:1 trên --paper và 2.96:1 trên --paper-deep — dưới chuẩn WCAG AA cho
     chữ thường (4.5:1). Tối lại thành #57606b (cùng tông xanh xám, giữ đúng
     "cảm giác" màu gốc) để đạt 5.85:1 trên --paper và 5.17:1 trên --paper-deep —
     đọc được trên CẢ 2 nền đang dùng biến này, không chỉ 1 trong 2. */
  --text-muted: #57606b;

  /* Thang cỡ chữ cho text phụ/nhỏ — gom 5 giá trị lẻ (12, 12.5, 13, 13.5, 14px)
     về 2 mức chuẩn. */
  --fs-xs: 12px;
  --fs-sm: 13.5px;
  --fs-base: 15px;
  --fs-lg: 18px;
  --fs-xl: 24px;

  /* Bo góc — gom 9 giá trị lẻ (3, 8, 10, 12, 14, 16, 18, 22, 999px) về 3 mức. */
  --radius-sm: 8px;
  --radius: 14px;
  --radius-pill: 999px;

  /* Thang spacing theo lưới 4px */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;

  /* Thang z-index có tên — giữ đúng thứ tự đang có trong site
     (.site-header: 50, .toast-container: 100, .confirm-modal-overlay: 200);
     --z-dropdown đặt ở mức 80, đang dùng cho .search-suggest và .select-custom-panel */
  --z-sticky-header: 50;
  --z-dropdown: 80;
  --z-toast: 100;
  --z-modal: 200;

  /* Chiều cao "cứng" (chưa kể safe-area-inset-bottom trên iPhone có notch/thanh cử
     chỉ) của bottom tab bar mobile mới — dùng làm mốc tính padding-bottom cho
     .site-footer (tránh footer bị che, xem @media max-width:900px bên dưới, mục
     ".site-footer") và tính offset "xếp chồng" cho FAB tìm kiếm/.floating-social
     phía trên nó. */
  --mobile-bottom-nav-h: 60px;

  /* top cho position:sticky của .account-sidebar (trang account.php, desktop
     >900px) — PHẢI >= chiều cao thật của .site-header lúc đang sticky (gồm cả
     header-row-1 + header-row-2, thanh nav ngang) để không bị header đè lên
     phần đầu sidebar khi cuộn. 148px là giá trị fallback tĩnh (dùng khi JS tắt
     hoặc trước khi kịp chạy) — giá trị THẬT được main.js (initAccountSidebar())
     đo trực tiếp offsetHeight của .site-header rồi ghi đè biến này ngay khi
     tải trang, luôn khớp dù sau này header cao/thấp đi. */
  --account-sticky-top: 148px;

  /* Bộ easing dùng chung cho toàn bộ cụm "bottom sheet Danh mục/Giỏ hàng +
     icon nút toggle + ping", gom về đúng 2 đường cong theo hướng chuyển động
     (vào/ra), mọi phần tử liên quan đều dùng lại 1 trong 2 biến này — tham
     khảo tinh thần sheet/modal iOS: phẳng, điềm tĩnh, không có overshoot/bung
     nảy kiểu Material spring:
     - --ease-sheet-in: cho MỌI hiệu ứng "đi vào" (sheet mở, icon co lại lúc
       mở, ping toả ra) — khởi động chậm rồi hãm êm dần về đích.
     - --ease-sheet-out: cho các hiệu ứng "đi ra" không bắt nguồn từ 1 cử chỉ
       kéo tay đang có đà (icon phục hồi lúc đóng, ping thu vào) — đúng đảo
       ngược về mặt toán học của --ease-sheet-in, không phải 1 đường cong
       "accelerate" tự chọn riêng. Với cubic-bezier(x1,y1,x2,y2), phiên bản
       chạy ngược thời gian đúng là cubic-bezier(1-x2, 1-y2, 1-x1, 1-y1) —
       tức lấy đối xứng điểm qua tâm (.5,.5) của toàn đường cong.
       --ease-sheet-in là cubic-bezier(.2, 0, 0, 1) nên đảo ngược ra đúng
       cubic-bezier(1, 0, .8, 1). Lưu ý: sheet đóng (.category-sheet, không
       .sheet-open) không dùng biến này nữa — xem --ease-sheet-drag-release
       ngay dưới đây để biết lý do.
     - --ease-sheet-drag-release: dùng cho MỌI cách ĐÓNG sheet (bấm nút, bấm
       nền, phím Escape, VÀ buông tay sau khi kéo — tất cả đều chung 1 easing
       này, xem .category-sheet bên dưới). Sở dĩ KHÔNG dùng --ease-sheet-out
       (dù về lý thuyết "đối xứng toán học đẹp" với chiều mở) là vì: mở là 1
       đường cong "bung nhanh rồi hãm êm ở cuối" — đảo ngược thời gian thuần
       tuý của nó ra đúng đường cong "gần như đứng yên ở đầu rồi lao nhanh về
       cuối". Nhịp "đứng yên" đó rất dễ nhận ra ngay khung hình đầu tiên (lúc
       mắt nhìn kỹ nhất) — gây cảm giác "khựng lại từ từ rồi mới đóng xuống"
       dù CSS khai báo transition dài .4s y hệt lúc mở. Đặc biệt rõ khi đóng
       ngay sau 1 cú kéo tay đang CÓ ĐÀ (ngón tay vừa di chuyển liên tục):
       khựng đột ngột đó phá vỡ hẳn cảm giác "tiếp nối" tự nhiên. Đổi hẳn
       sang 1 đường cong giảm tốc THUẦN (không khựng ở đầu, chuẩn
       "decelerate" quen thuộc Material/iOS) cho MỌI lượt đóng — không chỉ
       kéo tay — để toàn bộ sheet nhất quán 1 cảm giác đóng duy nhất. */
  --ease-sheet-in: cubic-bezier(.2, 0, 0, 1);
  --ease-sheet-out: cubic-bezier(1, 0, .8, 1);
  --ease-sheet-drag-release: cubic-bezier(0, 0, .2, 1);

  /* ============================================================
     HỆ THỐNG MOTION CHUẨN CHO UI THÔNG THƯỜNG (hover nút/thẻ, badge,
     toast, dropdown, wishlist...) — TÁCH BIỆT với 3 biến --ease-sheet-*
     ở trên (dành riêng cho bottom-sheet Danh mục/Giỏ hàng, không đụng).
     Hệ thống 1 bộ token có tên, dựa theo QUY MÔ của thay đổi (không phải 1 số
     dùng cho tất cả) — tránh rải rác nhiều giá trị duration và độ "nhấc lên"
     khi hover không theo quy luật giữa các thành phần. Tinh thần giống Apple
     HIG + easing "standard" của Material 3:

     THANG THỜI GIAN — chọn theo thay đổi lớn hay nhỏ, không theo cảm tính:
     - --dur-instant (100ms): phản hồi TỨC THÌ cho thao tác nhấn/chạm cần
       gần như không có độ trễ cảm nhận được (:active nhấn nút, bật/tắt
       trạng thái nhỏ) — nhanh hơn cả nhóm "vi tương tác" bên dưới vì đây
       là phản hồi CHẠM, không phải HOVER.
     - --dur-fast (150ms): vi tương tác cực nhỏ — đổi màu chữ/icon/viền
       khi hover, không có chuyển động hình học.
     - --dur-base (200ms): tương tác chuẩn — hover nút, hover thẻ sản
       phẩm/bài viết/danh mục, hiện/ẩn tooltip nhỏ. Đây là mức MẶC ĐỊNH,
       dùng khi không chắc chọn mức nào.
     - --dur-slow (300ms): chuyển động lớn hơn — mở dropdown/badge lớn,
       toast xuất hiện, fade cả khối nội dung. Việc nào hiện > 350ms mà
       không phải loại này thì nên rút ngắn lại (chậm dễ thấy ì, thiếu
       chuyên nghiệp).

     ĐƯỜNG CONG:
     - --ease-standard: cubic-bezier(.4,0,.2,1) — dùng cho tương tác 2
       CHIỀU (hover vào rồi hover ra dùng lại đúng đường cong này, không
       cần tách riêng) — êm ở cả 2 đầu, không nảy/bung.
     - --ease-enter: cubic-bezier(0,0,.2,1) — dùng cho hiệu ứng XUẤT HIỆN
       (fade in, slide in, toast hiện ra, dropdown mở) — hãm êm ở cuối,
       giống tinh thần --ease-sheet-in nhưng dùng hệ số chuẩn Material
       "decelerate" cho các phần tử nhỏ hơn sheet.
     - --ease-exit: cubic-bezier(.4,0,1,1) — dùng cho hiệu ứng BIẾN MẤT
       (fade out, toast ẩn đi) — tăng tốc nhẹ ở đầu để cảm giác "rời đi"
       nhanh gọn, không lề mề.
     KHÔNG dùng easing có nảy/bung (bounce/elastic) cho các đường cong
     trên — chỉ decorative đặc thù (xem ghi chú "ANIMATION TRANG TRÍ" tại
     từng vị trí, ví dụ logo ripple, hồ cá footer) mới được giữ timing
     riêng vì đó là trang trí, không phải phản hồi tương tác.

     ĐỘ "NHẤC LÊN" (hover lift) — chuẩn hoá theo VAI TRÒ phần tử, chỉ 2 mức:
     - Phần tử nhỏ (nút, chip, icon tròn): translateY(-2px) + shadow nhẹ.
     - Phần tử lớn (card: product/post/cat...): translateY(-3px) + shadow
       rõ hơn — CHỈ 1 mức dùng chung cho MỌI loại card, không lệch nhau.

     prefers-reduced-motion: xem khối @media riêng phía dưới (giữ nguyên,
     không đổi khi thêm token này) — token thời gian/easing ở đây không
     tự động tắt animation, việc tắt vẫn do khối @media đó đảm nhiệm. */
  --dur-instant: .1s;
  --dur-fast: .15s;
  --dur-base: .2s;
  --dur-slow: .3s;
  --ease-standard: cubic-bezier(.4, 0, .2, 1);
  --ease-enter: cubic-bezier(0, 0, .2, 1);
  --ease-exit: cubic-bezier(.4, 0, 1, 1);

  /* ĐỘ "CO LẠI" KHI NHẤN (:active scale) — dùng CHUNG 1 công thức cho MỌI nút/dropdown
     chạm được trên site (.btn, .select-custom-trigger...), thay vì mỗi nơi tự đặt 1 con số
     % scale() cố định. Lý do: scale() co theo % CHÍNH bề rộng phần tử, nên cùng 1 con số %
     tạo ra độ dịch chuyển (px) rất khác nhau giữa 1 dropdown lọc hẹp (~190px, khu vực đầu
     trang) và 1 select rộng cả form (~500-650px, vd "Chế độ hiển thị"/"Phạm vi áp dụng" ở
     trang Mã giảm giá) — cùng co 2% nhưng dropdown hẹp chỉ lệch ~3.8px trong khi select rộng
     lệch tới ~12px, mắt thấy "giật mạnh" hẳn dù chung 1 dòng CSS. Công thức dưới đây quy về
     ĐÚNG 1 mức dịch chuyển pixel mục tiêu cho mọi kích thước, dropdown lọc đầu trang (~190px)
     làm chuẩn tham chiếu:
     - --press-target-px: tổng số px 2 cạnh trái/phải cùng lệch vào khi nhấn (số thuần, không
       đơn vị, vì dùng để CHIA cho --press-w cũng là số thuần bên dưới) — 3.8 tương ứng đúng
       cảm giác dropdown lọc hiện có (190px * 2% = 3.8px), dùng làm chuẩn cho mọi nơi khác.
     - --press-cap: trần % tối đa (giữ nguyên mốc 2% cũ) — phòng trường hợp phần tử hẹp hơn cả
       190px thì vẫn không co nhiều hơn cảm giác gốc, KHÔNG co mạnh hơn dropdown tham chiếu.
     - --press-w: bề rộng THẬT (px, số thuần) của TỪNG phần tử — CSS thuần không tự đo được,
       nên assets/js/main.js dùng ResizeObserver ghi số này trực tiếp lên từng phần tử
       (observePressWidth()). Giá trị 190 khai ở đây chỉ là dự phòng cho lúc JS chưa kịp đo
       xong hoặc trình duyệt không hỗ trợ ResizeObserver — khi đó phần tử vẫn hoạt động đúng
       như cảm giác dropdown lọc mặc định, không vỡ/lỗi gì.
     Xem .btn:active + .select-custom-trigger:active bên dưới để thấy công thức áp dụng. */
  --press-target-px: 3.8;
  --press-cap: .02;
  --press-w: 190;
  /* --ease-standard/--ease-enter/--ease-exit đều là cubic-bezier 1 chiều
     (fade/slide/mở-đóng) — không phù hợp cho .form-shake, vốn dao động qua
     lại nhiều lần trong 1 lần chạy. Token riêng cho nhu cầu này (cùng tinh
     thần --ease-sheet-* tách riêng cho bottom sheet ở trên), giữ 360ms để
     đúng "cảm giác" đã chốt. */
  --dur-shake: .36s;
  --ease-shake: ease-in-out;
  --lift-sm: -2px;
  --lift-lg: -3px;

  --font-display: 'Fraunces', serif;
  --font-body: 'Be Vietnam Pro', sans-serif;
}

* { box-sizing: border-box; }

/* Tắt ô xám mặc định lúc chạm trên di động (kế thừa xuống mọi phần tử). Phản hồi lúc nhấn do các rule :active của site đảm nhiệm. */
html { scroll-behavior: smooth; -webkit-tap-highlight-color: transparent; }

/* An toàn chống tràn ngang trên mobile: bất kỳ phần tử con nào lỡ rộng hơn
   viewport sẽ bị cắt thay vì kéo ra cả thanh cuộn ngang và làm lệch vị trí các
   phần tử position:fixed (nút Facebook/Zalo nổi, nút lên đầu trang...).
   Lưu ý cơ chế: overflow-x:hidden chỉ đặt trên <html>, KHÔNG đặt trên <body> —
   nếu đặt cả trên body sẽ khiến .site-header (position:sticky) mất tác dụng
   dính khi cuộn. Lý do: hễ 1 trục overflow (x hoặc y) được khai báo khác
   "visible" thì trục còn lại tự động được tính thành "auto" — nên
   "overflow-x:hidden" trên body thực chất biến thành "overflow:hidden auto",
   khiến body trở thành 1 "khối cuộn" (scroll container) của riêng nó, tách
   biệt khỏi việc cuộn trang thật (vốn do viewport/<html> đảm nhiệm).
   .site-header là con trực tiếp của <body> và dùng position:sticky — sticky
   luôn tính toán theo khối cuộn gần nhất chứa nó; một khi đó là body (chứ
   không phải viewport thật), hiệu ứng "dính khi cuộn" coi như vô hiệu trên
   phần lớn trình duyệt di động, dù không hề có lỗi gì trong khai báo
   position:sticky;top:0 cả.
   Trình duyệt tự động áp dụng overflow của phần tử gốc <html> thẳng vào
   viewport (quy tắc chuẩn của HTML/CSS), nên chỉ khai trên <html> vẫn chặn
   tràn ngang mà không tạo thêm khối cuộn phụ nào ở giữa — sticky nhờ vậy hoạt
   động bình thường. <body> chỉ còn giữ max-width:100% (không kèm overflow) để
   phòng hờ, không ảnh hưởng gì đến sticky. */
html { max-width: 100%; overflow-x: hidden; }

body {
  max-width: 100%;
  margin: 0;
  font-family: var(--font-body);
  background: var(--paper);
  color: var(--ink);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  /* Tránh hiện con trỏ nhấp nháy khi bấm vào khoảng trống giữa các phần tử
     (khoảng trắng trong HTML đôi khi bị trình duyệt coi là văn bản có thể chọn) */
  -webkit-user-select: none;
  -moz-user-select: none;
  user-select: none;
}

/* Vẫn cho phép chọn/copy chữ ở nội dung khách cần đọc */
p, .pd-desc, .post-body, .contact-card p, .cart-prod a, .product-name a,
input, textarea, select, [contenteditable] {
  -webkit-user-select: text;
  -moz-user-select: text;
  user-select: text;
}

img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; transition: color var(--dur-fast) var(--ease-standard), opacity var(--dur-fast) var(--ease-standard), background-color var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard); }
ul { list-style: none; margin: 0; padding: 0; }

.container {
  max-width: 1500px;
  margin: 0 auto;
  padding: 0 var(--space-5);
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 1.15;
  margin: 0 0 .5em;
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-xs);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--jade);
  font-weight: 700;
}
.eyebrow::before {
  content: '';
  width: 18px; height: 1px;
  background: var(--jade);
}

/* ---------- focus states (a11y) ---------- */
a:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-visible {
  outline: 2px solid var(--koi-orange);
  outline-offset: 2px;
}

/* ---------- skip-to-content link (a11y) ---------- */
/* Cho phép người dùng bàn phím / screen reader nhảy thẳng đến nội dung chính, bỏ qua
   header navigation (Logo, hotline, search, menu, đăng nhập, giỏ hàng — 6-7 phần tử
   focusable). Mặc định ẩn bằng position:absolute; left:-9999px (không dùng display:none
   vì không tab được), chỉ hiện khi focus — xem header.php. Style nhất quán với
   focus outline ở trên (--koi-orange, outline-offset:2px). Hiệu ứng slide-down + fade-in
   khi focus giúp người dùng thấy rõ link vừa xuất hiện ở đầu trang (không phải "pop in"
   đột ngột khó chú ý). Dùng z-index cao (--z-toast+1 = 101) để nằm trên cả toast/loading bar. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 101;
  padding: var(--space-3) var(--space-5);
  background: var(--koi-orange);
  color: #fff;
  font-weight: 700;
  text-decoration: none;
  border-radius: 0 0 var(--radius-sm) 0;
  transition: left 0s var(--ease-standard);
}
.skip-link:focus,
.skip-link:focus-visible {
  left: 0;
  outline: 3px solid var(--ink);
  outline-offset: 0;
}

/* Khi user nhảy đến #main-content (qua skip link hoặc anchor), trừ padding-top để
   tiêu đề đầu tiên của section không bị header sticky che mất — giá trị tương ứng
   với chiều cao .site-header (~90px desktop, ~60px mobile). Dùng scroll-margin-top
   thay vì padding-top trên :target vì scroll-margin không ảnh hưởng đến layout
   (không đẩy nội dung xuống), chỉ tác động khi scroll đến anchor. */
:target {
  scroll-margin-top: 100px;
}
@media (max-width: 900px) {
  :target {
    scroll-margin-top: 70px;
  }
}

/* =========================== HEADER =========================== */
/* Nền topbar + thanh nav ngang (.header-row-2) dùng chung 1 ngôn ngữ hình ảnh:
   gradient xanh than sâu (đúng tông --ink hiện có) + 2 quầng sáng mờ (blur)
   màu san hô/vàng đồng — mô phỏng ánh đèn lồng phản chiếu trên mặt hồ koi về
   đêm. Kỹ thuật blob giống hệt .trust-strip-blob đã dùng ở phần thân trang
   (span/pseudo-element bo tròn, filter:blur, z-index:-1 trong 1 stacking
   context isolate) — chỉ khác là dùng ::before/::after thay vì thêm span
   trong HTML, để áp dụng tự động cho mọi trang dùng chung includes/header.php
   mà không cần sửa từng file PHP. */
.topbar {
  background: linear-gradient(100deg, #0d1920 0%, #1a2e37 55%, #0e1b22 100%);
  color: var(--paper);
  font-size: var(--fs-sm);
  border-bottom: 1px solid rgba(247,245,239,.08);
  position: relative;
  isolation: isolate;
  overflow: hidden;
}
.topbar::before,
.topbar::after {
  content: '';
  position: absolute;
  border-radius: 50%;
  z-index: -1;
  filter: blur(22px);
  pointer-events: none;
}
.topbar::before {
  width: 220px; height: 220px;
  background: radial-gradient(circle, rgba(201,154,58,.32), transparent 70%);
  top: -150px; right: 14%;
}
.topbar::after {
  width: 180px; height: 180px;
  background: radial-gradient(circle, rgba(31,111,106,.30), transparent 70%);
  bottom: -140px; left: 8%;
}
.topbar .container {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 9px;
  padding-bottom: 9px;
  flex-wrap: wrap;
  gap: 6px;
  /* Kỹ thuật full-bleed giống hệt .header-row-1 bên dưới: đẩy khối Địa chỉ
     (topbar-address) sát mép phải THẬT của màn hình, trong khi Hotline vẫn đứng
     đúng vị trí thẳng hàng với Logo/Trang chủ (padding-left dùng công thức
     max() để bù lại đúng khoảng cách mà .container vẫn tạo ra ở phía trái). */
  width: 100vw;
  max-width: 100vw;
  position: relative;
  left: 50%;
  margin-left: -50vw;
  padding-left: max(var(--space-5), calc((100vw - 1500px) / 2 + var(--space-5)));
  padding-right: var(--space-5);
}
/* padding-right của .topbar .container chỉ tăng lên 72px ở desktop (>=901px, cùng
   ngưỡng với breakpoint hamburger/off-canvas của site). Mobile/tablet (<=900px) giữ
   nguyên 24px (var(--space-5)) vì lúc đó .topbar .container chuyển sang
   justify-content:center — nếu đổi padding-right chung cho mọi màn hình thì padding
   trái/phải sẽ lệch nhau (trái 24px, phải 72px), khiến khối địa chỉ mobile bị kéo
   lệch tâm thay vì canh giữa đúng như @media (max-width:640px) bên dưới yêu cầu. */
@media (min-width: 901px) {
  .topbar .container {
    padding-right: 72px;
  }
}
.topbar-contact { display: flex; align-items: center; }
.topbar a { opacity: .82; transition: opacity var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard); }
.topbar a:hover,
.topbar a:active { opacity: 1; color: var(--koi-orange); }
.topbar .divider { opacity: .3; margin: 0 var(--space-2); }
.topbar-address { opacity: .7; }
.topbar-address:hover,
.topbar-address:active { opacity: 1; color: var(--koi-orange); }
.topbar-social-link { display: inline-flex; align-items: center; gap: 5px; }
.topbar-zalo-icon { border-radius: 4px; display: block; }
/* Icon Zalo/Facebook (topbar 16px, social-row 34px, floating-social 54px,
   nút "Chat Zalo" trong khối mua sỉ product.php 15px) dùng file PNG xuất sẵn
   đúng theo từng vị trí (zalo-15/16/34/54.png, facebook-34/54.png) thay vì
   luôn tải file gốc 256px rồi bắt trình duyệt tự downscale lúc runtime. Lý
   do: downscale 1 bước quá mạnh (256px → 15-54px, tức 5-17 lần) dễ bị mờ/vỡ
   nét trên màn hình desktop devicePixelRatio=1 (không đủ pixel thật để trình
   duyệt lấy mẫu đẹp), dù trên di động (devicePixelRatio 2-3) vẫn nét vì có
   nhiều pixel thật hơn. Các file xuất sẵn này được resize bằng Lanczos ở độ
   phân giải GẤP 3 LẦN kích thước hiển thị (vd zalo-16.png thực tế là
   48×48px) — vừa đủ nét ở desktop thường, vừa đủ nét ở màn Retina di động,
   không lặp lại lỗi cũ. KHÔNG đổi các file này về đúng 1:1 kích thước hiển
   thị (vd 16×16px thật) vì sẽ làm mờ ngược lại trên di động màn Retina.
   Nút "Chat Zalo" ở product.php TRƯỚC ĐÂY dùng 1 icon SVG vẽ tay riêng (chữ
   "Z" trên nền bo góc) — khác hẳn phong cách logo chính thức dùng ở mọi nơi
   khác trên site; đã đổi về cùng file zalo-*.png để nhất quán 100% hình ảnh
   thương hiệu trên toàn site, không còn 2 kiểu icon Zalo khác nhau. */

.topbar-phone {
  background: none;
  border: none;
  color: var(--paper);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  opacity: .82;
  cursor: pointer;
  padding: 0;
  transition: opacity var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.topbar-phone:hover,
.topbar-phone:active { opacity: 1; color: var(--koi-orange); }

.site-header {
  background: rgba(247,245,239,.92);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: var(--z-sticky-header);
}
/* Hiệu ứng kính mờ (backdrop-filter) CHỈ bật ở desktop (>900px), KHÔNG bật ở mobile.
   Lý do (đã từng gây lỗi nghiêm trọng, ghi lại để không lặp lại):
   backdrop-filter/filter/transform/perspective/will-change đặt trên 1 phần tử sẽ khiến
   chính phần tử đó trở thành "containing block" cho MỌI con cháu position:fixed của nó
   (theo đặc tả CSS), y hệt tác dụng của transform. .header-row-2 (khay menu off-canvas)
   và .nav-overlay (lớp phủ tối) đều là con của .site-header và dùng position:fixed với
   top:0/bottom:0/inset:0 để phủ TOÀN BỘ viewport khi mở menu trên mobile — nhưng nếu
   .site-header có backdrop-filter, "viewport" của chúng bị thu hẹp lại thành đúng khung
   hình chữ nhật của .site-header (chỉ cao bằng topbar + header-row-1, ~90px), khiến
   toàn bộ menu + lớp phủ tối bị cắt gọn vào 1 dải mỏng ở trên cùng, hầu hết mục menu
   không hiển thị được, menu mobile coi như hỏng hoàn toàn dù CSS .header-row-2 hoàn toàn
   đúng. Ở desktop, .header-row-2/.nav-overlay không dùng position:fixed nên không sao —
   vì vậy giải pháp là chỉ bật backdrop-filter từ 901px trở lên (đúng ngưỡng đối lập với
   @media (max-width:900px) bên dưới, nơi off-canvas kích hoạt), giữ nguyên hiệu ứng kính
   mờ ở desktop, loại bỏ hoàn toàn ảnh hưởng ở mobile. */
@media (min-width: 901px) {
  .site-header {
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
  }
}

.header-row-1 {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 16px;
  padding-bottom: 16px;
  gap: var(--space-5);
  /* "Phá" khung .container (max-width:1500px; margin:0 auto) CHỈ để đẩy header-actions
     (search + đăng nhập + giỏ hàng) sát mép phải THẬT của màn hình — trong khi Logo vẫn
     đứng đúng vị trí thẳng hàng với Hotline (topbar) như trước, không bị xê dịch.
     Kỹ thuật "full-bleed": cho hàng này rộng bằng đúng viewport (100vw) rồi kéo ngược
     lại nửa viewport (left:50%; margin-left:-50vw) để thoát khỏi max-width/margin:auto
     mà .container áp lên nó. padding-left dùng công thức max(...) để BÙ LẠI chính xác
     khoảng cách mà .container vẫn đang tạo ra cho Hotline/Trang chủ (24px, hoặc thêm
     phần margin auto khi màn hình rộng hơn 1500px) — nhờ vậy Logo không bị lệch dù màn
     hình rộng bao nhiêu. padding-right thì cố định 24px để header-actions luôn sát mép
     phải thật, không phụ thuộc việc canh giữa 1500px nữa. */
  width: 100vw;
  max-width: 100vw;
  position: relative;
  left: 50%;
  margin-left: -50vw;
  padding-left: max(var(--space-5), calc((100vw - 1500px) / 2 + var(--space-5)));
  padding-right: var(--space-5);
}

.footer-brand .brand { margin-left: 0; }

.header-row-2 {
  border-top: 1px solid var(--line);
  background: var(--ink-soft);
}
/* Nâng thanh nav ngang desktop thành lớp gradient + quầng sáng mờ (cùng ngôn
   ngữ hình ảnh với .topbar phía trên) thay vì màu phẳng var(--ink-soft) — chỉ
   khoá trong desktop (>=901px) vì mobile .header-row-2 là khay menu off-canvas
   RIÊNG BIỆT (nền sáng giấy ngà, xem @media max-width:900px bên dưới), tách
   biệt hoàn toàn 2 ngữ cảnh hiển thị như các khối khác trong file này. */
@media (min-width: 901px) {
  .header-row-2 {
    background: linear-gradient(120deg, #0c1920 0%, #17282f 50%, #0d1a20 100%);
    position: relative;
    isolation: isolate;
    overflow: hidden;
  }
  .header-row-2::before,
  .header-row-2::after {
    content: '';
    position: absolute;
    border-radius: 50%;
    z-index: -1;
    filter: blur(26px);
    pointer-events: none;
  }
  .header-row-2::before {
    width: 320px; height: 320px;
    background: radial-gradient(circle, rgba(217,102,59,.24), transparent 70%);
    top: -210px; left: 22%;
  }
  .header-row-2::after {
    width: 260px; height: 260px;
    background: radial-gradient(circle, rgba(201,154,58,.22), transparent 70%);
    bottom: -190px; right: 12%;
  }
}

.brand {
  display: flex;
  align-items: center;
  gap: 11px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 23px;
  color: var(--ink);
  white-space: nowrap;
}
.brand .mark { width: 40px; height: 40px; flex-shrink: 0; }
.brand:active { opacity: .8; }

/* ---- LOGO RIPPLE — thay thế vạch gạch trang trí cũ ----
   Ý tưởng: 1 vòng tròn mảnh (::after) lan toả ra từ logo rồi mờ dần, lặp lại chậm
   rãi — mô phỏng gợn sóng loang trên mặt hồ koi khi cá lượn qua. Rất tinh tế
   (viền 1px, không nền đặc), tốc độ chậm (4.5s/vòng) và có khoảng nghỉ giữa 2 lần
   lan (easing dừng ở 70% rồi giữ mờ tới hết chu kỳ) để không gây rối mắt hay
   cảm giác "nhấp nháy liên tục" — đúng tinh thần thanh lịch, nhẹ nhàng.
   ANIMATION TRANG TRÍ (ngoại lệ có chủ đích, xem nguyên tắc 7 hệ thống motion
   ở :root) — lặp vô hạn, không phản hồi thao tác, giữ nguyên timing riêng. */
.mark-wrap {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
}
.mark-wrap::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1px solid var(--koi-orange);
  opacity: 0;
  animation: logo-ripple 4.5s cubic-bezier(.25, .1, .3, 1) infinite;
  pointer-events: none;
}
/* Vòng gợn sóng thứ 2, lệch pha (-2.25s = nửa chu kỳ) — tạo cảm giác nhiều lớp
   sóng nối tiếp nhau thay vì chỉ 1 vòng đơn điệu, vẫn giữ đúng nhịp chậm rãi. */
.mark-wrap::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1px solid var(--jade);
  opacity: 0;
  animation: logo-ripple 4.5s cubic-bezier(.25, .1, .3, 1) infinite -2.25s;
  pointer-events: none;
}
@keyframes logo-ripple {
  0%   { transform: scale(1);    opacity: .5; }
  60%  { transform: scale(1.45); opacity: 0; }
  100% { transform: scale(1.45); opacity: 0; }
}
/* Khách/thiết bị yêu cầu giảm chuyển động (accessibility) — tắt hẳn animation,
   chỉ còn logo tĩnh, không có cách nào khác để "giảm bớt" 1 hiệu ứng lặp vô hạn
   ngoài tắt hẳn nó. */
@media (prefers-reduced-motion: reduce) {
  .mark-wrap::before, .mark-wrap::after { animation: none; }
}
.brand small {
  display: block;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 10.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--koi-orange);
  margin-top: 1px;
}

.main-nav {
  display: flex;
  align-items: center;
  gap: 2px;
  /* CỐ Ý không giới hạn max-width:1500px + margin:auto như .container ở đây: thanh
     nav được yêu cầu tràn sát 2 mép màn hình (khác với topbar/header-row-1 vẫn canh
     giữa tối đa 1500px để dễ đọc trên màn rộng) — "Trang chủ" (item đầu) tự áp sát
     mép trái, còn nhóm "Hỗ trợ" (Kinh nghiệm/Tra cứu đơn hàng/Liên hệ, item cuối) tự
     áp sát mép phải nhờ .nav-scroll ở giữa dùng flex:1 chiếm hết khoảng trống còn lại
     (xem media query desktop bên dưới) — chỉ còn padding cố định var(--space-5) cách
     đúng 2 mép làm khoảng đệm, không phụ thuộc chiều rộng màn hình. */
  padding: 0 var(--space-5);
  overflow-x: auto;
  white-space: nowrap;
  /* Ẩn thanh cuộn ngang cho gọn mắt, vẫn cuộn được bằng chuột/vuốt nếu màn quá hẹp */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.main-nav::-webkit-scrollbar { display: none; }

/* .nav-scroll bọc riêng nhóm "Danh mục sản phẩm" — CHỈ có ý nghĩa bố cục ở desktop
   (>900px, xem media query .main-nav .nav-group bên dưới). Ở mobile không có luật
   CSS nào nhắm vào .nav-scroll nên nó không tạo box/margin/padding riêng, khay menu
   off-canvas hiển thị y hệt như trước khi thêm div bọc này. */

/* ---- Desktop: thanh nav tối màu, item dạng underline khi active/hover ----
   HTML bên trong .header-row-2 dùng chung cấu trúc/class với sidebar admin
   (.nav-item, .nav-group, .nav-icon...) để tái dùng ở menu off-canvas mobile bên
   dưới — icon + tiêu đề nhóm chỉ có ý nghĩa trong menu off-canvas mobile nên ẩn
   hẳn ở đây, .nav-group "phẳng" ra thành item trực tiếp của .main-nav.
   TOÀN BỘ khối này CHỈ áp dụng ở desktop (>900px), khoá trong
   @media (min-width:901px) thay vì để global và dựa vào việc menu mobile
   "reset" lại từng thuộc tính trong @media(max-width:900px) — cách reset dễ
   sót: vạch gạch chân cam ::after dùng cho trạng thái hover/active của thanh
   ngang desktop có thể bị sót không reset, khiến khi mở menu mobile và bấm
   vào 1 mục, mục đó lộ ra 1 vạch cam nằm ngang phía dưới label (tàn dư giao
   diện desktop) dù đã tắt hẳn box-shadow viền cam ở mobile. Khoá cả khối vào
   media query để triệt để tách 2 ngữ cảnh hiển thị, không rò rỉ thuộc tính
   nào về sau. */
@media (min-width: 901px) {
  .nav-icon, .nav-icon-chip { display: none; }
  .nav-group-title { display: none; }
  .main-nav .nav-group { display: contents; }

  /* Ở desktop, .main-nav không tự cuộn ngang nữa (khác mobile/tablet hẹp) — nhường
     việc cuộn ngang riêng cho .nav-scroll (chỉ chứa danh mục sản phẩm) bằng flex:1 +
     min-width:0 + overflow-x:auto của riêng nó. Nhờ vậy nhóm "Hỗ trợ" (Kinh nghiệm,
     Tra cứu đơn hàng, Liên hệ) — đứng SAU .nav-scroll trong DOM — luôn được nhường đủ
     chỗ, ghim cố định sát mép phải thanh nav, không bao giờ bị tràn/che khuất ra
     ngoài mép phải màn hình (nếu toàn bộ .main-nav dùng chung 1 vùng
     overflow-x:auto thì khi danh mục dài, nhóm Hỗ trợ sẽ bị đẩy che khuất/mất hẳn). */
  .main-nav { overflow-x: visible; }
  /* Dịch khối "Trang chủ + Danh mục sản phẩm" sang phải một chút (chỉ desktop).
     Chỉ đổi padding-left — không đụng padding-right — nên nhóm "Hỗ trợ" (Kinh
     nghiệm/Tra cứu đơn hàng/Liên hệ) KHÔNG bị xê dịch, vì nó luôn ghim theo mép
     phải nhờ .nav-scroll flex:1 đứng trước nó, không phụ thuộc điểm bắt đầu bên
     trái. Tăng dần con số bên dưới (48px) nếu muốn dịch nhiều/ít hơn. */
  /* Độ dịch sang phải co giãn theo bề rộng màn: 0px ở ≤1136px, tăng dần tới 144px ở ≥1280px.
     Trước đây cố định 144px nên ở iPad ngang/laptop nhỏ (901–1180px) thanh nav chỉ còn
     ~230–500px cho danh mục sau khi trừ "Trang chủ" + nhóm "Hỗ trợ" (đo thật bằng trình
     duyệt). Dùng clamp() để khỏi thêm breakpoint lẻ ngoài thang 480/640/900 ở đầu file. */
  .main-nav { padding-left: clamp(0px, calc(100vw - 1136px), 144px); }
  .nav-scroll {
    display: flex;
    align-items: center;
    gap: 2px;
    flex: 1 1 auto;
    min-width: 0;
    overflow-x: auto;
    white-space: nowrap;
    scrollbar-width: none;
    -ms-overflow-style: none;
  }
  .nav-scroll::-webkit-scrollbar { display: none; }

  .main-nav .nav-item {
    position: relative;
    display: inline-flex;
    align-items: center;
    font-weight: 600;
    font-size: 15px;
    letter-spacing: .015em;
    padding: 15px 16px;
    color: rgba(247,245,239,.78);
    flex-shrink: 0;
    transition: color var(--dur-fast) var(--ease-standard);
  }
  .main-nav .nav-item::after {
    content: '';
    position: absolute;
    left: 15px; right: 15px; bottom: 6px;
    height: 2px;
    border-radius: 2px;
    background: var(--koi-orange);
    transform: scaleX(0);
    transform-origin: center;
    transition: transform var(--dur-base) var(--ease-standard);
  }
  .main-nav .nav-item:hover,
  .main-nav .nav-item:active { color: #fff; }
  .main-nav .nav-item:hover::after,
  .main-nav .nav-item:active::after { transform: scaleX(1); }
  .main-nav .nav-item.active { color: #fff; }
  .main-nav .nav-item.active::after { transform: scaleX(1); }

  /* "Trang chủ" (pinned) — nổi bật bằng chip nền san hô đặc, thay vì viền như trước */
  .main-nav .nav-item--pinned {
    background: linear-gradient(135deg, var(--koi-orange) 0%, #e8823f 55%, var(--gold) 130%);
    color: #fff;
    border-radius: var(--radius-pill);
    padding: 8px 18px;
    margin-right: var(--space-2);
    flex-shrink: 0;
    box-shadow: 0 6px 16px -6px rgba(217,102,59,.55);
    /* isolation: để ::before (z-index:-1) nằm TRÊN nền gradient gốc của chính chip nhưng DƯỚI chữ */
    isolation: isolate;
  }
  .main-nav .nav-item--pinned::after { display: none; }
  /* Gradient hover đặt lên ::before rồi animate opacity — gradient không tự nội suy nên đổi
     thẳng `background` ở :hover sẽ làm màu nhảy. Không dùng ::after (đã bị display:none ở trên). */
  .main-nav .nav-item--pinned::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    z-index: -1;
    background: linear-gradient(135deg, #c2582f 0%, var(--koi-orange) 55%, var(--gold) 130%);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--dur-fast) var(--ease-standard);
  }
  .main-nav .nav-item--pinned:hover::before,
  .main-nav .nav-item--pinned:active::before { opacity: 1; }
  .main-nav .nav-item--pinned:hover,
  .main-nav .nav-item--pinned:active { color: #fff; }
}

/* Nhóm "Hỗ trợ" (Kinh nghiệm / Tra cứu đơn hàng / Liên hệ) trên thanh nav NGANG desktop —
   giờ dùng CHUNG 100% style chữ với nhóm chính (Trang chủ/danh mục): cùng size, cùng
   font-weight, cùng màu, không còn phân biệt gì về mặt chữ. Vị trí "ghim sát phải" đến
   từ flex:1 của .nav-scroll đứng trước nó (xem media query phía trên) chứ không còn dựa
   vào margin-left:auto (cách cũ chỉ đẩy được sang phải KHI còn dư khoảng trống — nếu
   danh mục dài tràn hết thanh nav thì auto margin coi như vô nghĩa, khiến nhóm Hỗ trợ
   dính sát ngay sau danh mục cuối, thậm chí bị tràn/che khuất mất "Liên hệ" ở mép phải
   màn hình). Chỉ còn 1 vạch phân cách dọc mảnh + khoảng đệm bên trái nhóm để mắt nhận ra
   đây là 1 khối chức năng riêng (hỗ trợ), tách bạch khỏi danh mục sản phẩm — đây là khác
   biệt về BỐ CỤC (khoảng cách/đường kẻ), không phải về chữ.
   QUAN TRỌNG: chỉ áp dụng ở desktop (>900px). Selector 3 lớp (.main-nav .nav-group
   .nav-item) có độ đặc hiệu cao hơn selector mobile ".header-row-2 .nav-item" (2 lớp)
   nên nếu để ngoài media query, luật này sẽ đè luôn cả lên menu off-canvas mobile —
   khiến mục "Hỗ trợ" bị bé/mờ bất thường so với phần còn lại của menu dù CSS mobile
   không hề chủ đích như vậy. Khoá lại trong @media (min-width:901px) để tách biệt
   hoàn toàn 2 ngữ cảnh hiển thị (thanh ngang desktop / menu dọc off-canvas mobile). */
@media (min-width: 901px) {
  .main-nav .nav-group:last-of-type .nav-item:first-child {
    margin-left: 14px;
    padding-left: 17px;
    position: relative;
  }
  .main-nav .nav-group:last-of-type .nav-item:first-child::before {
    content: '';
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 1px;
    height: 18px;
    background: rgba(247,245,239,.18);
  }
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
}
/* Ô tìm kiếm — nền "kính mờ" (acrylic): trắng bán trong suốt + backdrop-blur, nổi nhẹ
   trên nền topbar/header-row-1 phía sau thay vì trắng đặc phẳng như trước. Nút submit
   đổi từ hình tròn cam ĐẶC sang viền tròn cam (outline), khớp với nút "Giỏ hàng" đổi
   sang tông cam khi hover — cùng 1 ngôn ngữ màu nhấn xuyên suốt cụm hành động. */
.search-form {
  display: flex;
  align-items: center;
  height: 40px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  padding: 0 4px 0 18px;
  background: rgba(255, 255, 255, .55);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  transition: border-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard), background var(--dur-fast) var(--ease-standard);
}
.search-form:focus-within {
  border-color: var(--koi-orange);
  background: rgba(255, 255, 255, .85);
  box-shadow: 0 0 0 3px rgba(217, 102, 59, .12);
}
.search-form input {
  border: none;
  outline: none;
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  width: 168px;
  background: transparent;
  color: var(--ink);
}
.search-form input::placeholder { color: var(--text-muted); }
.search-form button {
  border: 1.5px solid var(--koi-orange);
  background: transparent;
  color: var(--koi-orange);
  width: 32px; height: 32px;
  flex-shrink: 0;
  border-radius: 50%;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.search-form button:hover,
.search-form button:active { background: var(--koi-orange); color: #fff; }

/* Dropdown gợi ý tìm kiếm (autocomplete). .search-box bọc ngoài .search-form làm điểm
   neo position:relative để .search-suggest định vị absolute ngay bên dưới, dùng chung
   cho cả 3 vị trí form search (desktop, hero mobile, off-canvas mobile) — xem
   includes/header.php. Dùng --z-dropdown để luôn nổi trên nội dung trang nhưng dưới
   toast/modal.
   Đồng bộ với .select-custom-panel (select "Sắp xếp" ở category.php): ẩn/hiện qua class
   ".is-open" trên .search-box (không dùng thuộc tính [hidden] — ép display:none tức thì,
   không animate được — xem main.js), cùng thời lượng/đường cong --dur-*, --ease-enter/
   --ease-exit — 2 dropdown dùng chung 1 "ngôn ngữ chuyển động", không chỉ chung khung hộp. */
.search-box { position: relative; }
.search-suggest {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  right: 0;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
  z-index: var(--z-dropdown);
  transform-origin: top center;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(-6px) scale(.98);
  transition: opacity var(--dur-base) var(--ease-exit),
              transform var(--dur-base) var(--ease-exit),
              visibility 0s linear var(--dur-base);
}
.search-box.is-open .search-suggest {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translateY(0) scale(1);
  transition: opacity var(--dur-slow) var(--ease-enter),
              transform var(--dur-slow) var(--ease-enter),
              visibility 0s linear 0s;
}
.search-suggest-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  text-decoration: none;
  color: var(--ink);
  border-bottom: 1px solid var(--line);
  transition: background var(--dur-fast) var(--ease-standard);
}
.search-suggest-item:last-child { border-bottom: none; }
.search-suggest-item:hover, .search-suggest-item.is-active, .search-suggest-item:active { background: var(--paper); }
.search-suggest-item img {
  width: 40px; height: 40px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
  background: var(--paper-deep);
}
.search-suggest-item-name {
  flex: 1;
  font-size: var(--fs-sm);
  line-height: 1.35;
  /* Cắt tên sản phẩm dài về 2 dòng thay vì tràn ra ngoài/đẩy vỡ layout dropdown,
     nhất quán với cách .product-name xử lý tên dài ở thẻ sản phẩm. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.search-suggest-item-price { font-size: var(--fs-sm); font-weight: 700; color: var(--koi-orange); flex-shrink: 0; white-space: nowrap; }
.search-suggest-empty { padding: var(--space-4) var(--space-3); text-align: center; color: var(--text-muted); font-size: var(--fs-sm); }
/* Khung nội dung do JS tạo (swapSuggestContent trong main.js): chỉ lo co/giãn chiều cao mượt khi
   danh sách gợi ý đổi theo từng lần gõ. .is-resizing chỉ có trong lúc chạy (overflow:hidden để dòng
   đang bị cắt bớt không tràn ra ngoài); lúc nghỉ không đặt height cứng nên cao theo nội dung. */
.search-suggest-body.is-resizing {
  overflow: hidden;
  transition: height var(--dur-base) var(--ease-standard);
}
/* Nội dung mới fade-in nhẹ (chỉ opacity, ảnh sản phẩm không chớp trắng giữa 2 lượt gõ). Animation
   trang trí: rule chung `*` trong khối prefers-reduced-motion chính (style.css) đã tắt hẳn. */
.search-suggest-body.is-swapping > * { animation: msg-fade var(--dur-base) var(--ease-enter); }

/* Nhóm "Đăng nhập/Tài khoản" + "Giỏ hàng" — 1 khối pill liền mạch:
   .account-cart-group làm khung ngoài bo góc + overflow:hidden để 2 màu nền
   khác nhau của 2 mục con (.acg-item) nhìn như 1 khối thống nhất, bo tròn đúng ở 2 đầu
   ngoài cùng mà không cần bo riêng từng mục. Cùng chiều cao 40px với .search-form bên
   cạnh để cả cụm hành động thẳng hàng tuyệt đối. */
.account-cart-group {
  display: flex;
  align-items: stretch;
  height: 40px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  overflow: hidden;
  flex-shrink: 0;
}
.acg-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: 700;
  font-size: var(--fs-sm);
  padding: 0 18px;
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.acg-account {
  background: transparent;
  color: var(--ink);
}
.acg-account:hover { background: rgba(18, 34, 44, .06); }
.acg-account:active { background: rgba(18, 34, 44, .1); }
.acg-cart {
  position: relative;
  background: var(--ink);
  color: var(--paper);
}
.acg-cart:hover,
.acg-cart:active { background: var(--koi-orange); }
.cart-badge {
  background: var(--koi-orange);
  color: #fff;
  font-size: 11px;
  font-weight: 800;
  min-width: 18px;
  height: 18px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 var(--space-1);
}
.acg-cart:hover .cart-badge,
.acg-cart:active .cart-badge { background: #fff; color: var(--koi-orange); }
@media (min-width: 901px) {
  .account-cart-group:hover { box-shadow: 0 6px 16px -8px rgba(18, 34, 44, .28); }
}

/* =========================== HERO =========================== */
/* min-height đặt ở đây (thay vì chỉ ở .hero.hero-has-bg) để khối hero luôn
   cố định ~660px bất kể có hay không có ảnh bìa — nếu chỉ ép chiều cao ở
   .hero-has-bg thì lúc chưa có banner, chiều cao phụ thuộc hoàn toàn vào nội
   dung (có thể lệch khỏi mốc 660px tuỳ font/nội dung). */
.hero {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  /* Gradient nhiều lớp + 3 quầng sáng mờ (koi cam / vàng đồng / ngọc bích) —
     nối tiếp đúng ngôn ngữ hình ảnh của .topbar/.header-row-2 phía trên để cả
     khối đầu trang liền thành 1 dải nước sâu duy nhất thay vì 3 mảng màu phẳng
     tách rời. CHỈ hiển thị khi hero KHÔNG có ảnh banner (.hero-bg-carousel phủ
     kín z-index:0 lên trên khi có ảnh, xem CSS carousel bên dưới) nên không
     xung đột với chế độ có ảnh nền thật. */
  background: radial-gradient(ellipse 120% 90% at 15% -10%, rgba(217,102,59,.20), transparent 55%),
              radial-gradient(ellipse 90% 70% at 85% 110%, rgba(31,111,106,.28), transparent 55%),
              linear-gradient(165deg, #0d1b23 0%, #16303c 45%, #10222b 100%);
  color: var(--paper);
  min-height: 660px;
}
.hero::after {
  content: '';
  position: absolute;
  z-index: -1;
  pointer-events: none;
  width: 340px; height: 340px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(201,154,58,.20), transparent 70%);
  filter: blur(30px);
  top: 30%;
  right: 18%;
}
/* Hoạ tiết "sóng nước + koi cách điệu" (lấy cảm hứng từ tranh koi Nhật Bản:
   sóng đồng tâm seigaiha + cá chép giản lược nét đơn) — bản gốc tự vẽ bằng
   SVG (không sao chép ảnh/tranh có bản quyền), giản lược tối đa về đường nét,
   chỉ dùng đúng 3 màu đã có sẵn trong bảng màu theme (vàng đồng --gold, kem
   giấy lụa --paper, cam san hô --koi-orange) để không lệch tông với phần còn
   lại của trang.
   ĐỌC ĐƯỢC CHỮ: dùng mask-image dạng gradient để hoạ tiết mờ dần gần như vô
   hình ngay phía sau khối chữ bên trái, chỉ hiện rõ dần về phía bên phải
   (vùng trống, không có chữ) — vừa có hiệu ứng "sóng nước" như ảnh tham khảo,
   vừa đảm bảo không làm khó đọc.
   CHỈ bật ở desktop (>=901px): ở mobile, khối chữ trải full-width (không còn
   "vùng trống bên phải" để hoạ tiết hiện rõ mà không đè lên chữ), nên tắt hẳn
   để tuyệt đối an toàn cho khả năng đọc trên màn hẹp. */
@media (min-width: 901px) {
  .hero::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20viewBox%3D%220%200%20300%20300%22%3E%20%20%20%3Cdefs%3E%20%20%20%20%20%3Cstyle%3E%20%20%20%20%20%20%20.wave%20%7B%20fill%3Anone%3B%20stroke%3A%23c99a3a%3B%20stroke-width%3A1.1%3B%20opacity%3A.5%3B%20%7D%20%20%20%20%20%20%20.wave2%20%7B%20fill%3Anone%3B%20stroke%3A%23f7f5ef%3B%20stroke-width%3A.9%3B%20opacity%3A.32%3B%20%7D%20%20%20%20%20%20%20.koiA-body%20%7B%20fill%3Anone%3B%20stroke%3A%23e8824a%3B%20stroke-width%3A1.8%3B%20stroke-linecap%3Around%3B%20stroke-linejoin%3Around%3B%20opacity%3A.9%3B%20%7D%20%20%20%20%20%20%20.koiA-fin%20%7B%20fill%3Anone%3B%20stroke%3A%23e8824a%3B%20stroke-width%3A1.4%3B%20stroke-linecap%3Around%3B%20opacity%3A.65%3B%20%7D%20%20%20%20%20%20%20.koiB-body%20%7B%20fill%3Anone%3B%20stroke%3A%23f2c65c%3B%20stroke-width%3A1.6%3B%20stroke-linecap%3Around%3B%20stroke-linejoin%3Around%3B%20opacity%3A.65%3B%20%7D%20%20%20%20%20%20%20.koiB-fin%20%7B%20fill%3Anone%3B%20stroke%3A%23f2c65c%3B%20stroke-width%3A1.2%3B%20stroke-linecap%3Around%3B%20opacity%3A.5%3B%20%7D%20%20%20%20%20%20%20.eye%20%7B%20opacity%3A.85%3B%20%7D%20%20%20%20%20%3C/style%3E%20%20%20%3C/defs%3E%20%20%20%20%3C%21--%20seigaiha%20%28g%E1%BB%A3n%20s%C3%B3ng%20%C4%91%E1%BB%93ng%20t%C3%A2m%20ki%E1%BB%83u%20Nh%E1%BA%ADt%29%20--%3E%20%20%20%3Cg%3E%20%20%20%20%20%3Cpath%20class%3D%22wave%22%20d%3D%22M-20%2040%20a40%2040%200%200%201%2080%200%22%20/%3E%20%20%20%20%20%3Cpath%20class%3D%22wave%22%20d%3D%22M60%2040%20a40%2040%200%200%201%2080%200%22%20/%3E%20%20%20%20%20%3Cpath%20class%3D%22wave%22%20d%3D%22M140%2040%20a40%2040%200%200%201%2080%200%22%20/%3E%20%20%20%20%20%3Cpath%20class%3D%22wave%22%20d%3D%22M220%2040%20a40%2040%200%200%201%2080%200%22%20/%3E%20%20%20%20%20%3Cpath%20class%3D%22wave%22%20d%3D%22M300%2040%20a40%2040%200%200%201%2080%200%22%20/%3E%20%20%20%20%20%3Cpath%20class%3D%22wave2%22%20d%3D%22M-20%2052%20a52%2052%200%200%201%20104%200%22%20/%3E%20%20%20%20%20%3Cpath%20class%3D%22wave2%22%20d%3D%22M84%2052%20a52%2052%200%200%201%20104%200%22%20/%3E%20%20%20%20%20%3Cpath%20class%3D%22wave2%22%20d%3D%22M188%2052%20a52%2052%200%200%201%20104%200%22%20/%3E%20%20%20%20%20%3Cpath%20class%3D%22wave2%22%20d%3D%22M292%2052%20a52%2052%200%200%201%20104%200%22%20/%3E%20%20%20%3C/g%3E%20%20%20%3Cg%20transform%3D%22translate%280%2C150%29%22%3E%20%20%20%20%20%3Cpath%20class%3D%22wave%22%20d%3D%22M-20%2040%20a40%2040%200%200%201%2080%200%22%20/%3E%20%20%20%20%20%3Cpath%20class%3D%22wave%22%20d%3D%22M60%2040%20a40%2040%200%200%201%2080%200%22%20/%3E%20%20%20%20%20%3Cpath%20class%3D%22wave%22%20d%3D%22M140%2040%20a40%2040%200%200%201%2080%200%22%20/%3E%20%20%20%20%20%3Cpath%20class%3D%22wave%22%20d%3D%22M220%2040%20a40%2040%200%200%201%2080%200%22%20/%3E%20%20%20%20%20%3Cpath%20class%3D%22wave%22%20d%3D%22M300%2040%20a40%2040%200%200%201%2080%200%22%20/%3E%20%20%20%20%20%3Cpath%20class%3D%22wave2%22%20d%3D%22M-20%2052%20a52%2052%200%200%201%20104%200%22%20/%3E%20%20%20%20%20%3Cpath%20class%3D%22wave2%22%20d%3D%22M84%2052%20a52%2052%200%200%201%20104%200%22%20/%3E%20%20%20%20%20%3Cpath%20class%3D%22wave2%22%20d%3D%22M188%2052%20a52%2052%200%200%201%20104%200%22%20/%3E%20%20%20%20%20%3Cpath%20class%3D%22wave2%22%20d%3D%22M292%2052%20a52%2052%200%200%201%20104%200%22%20/%3E%20%20%20%3C/g%3E%20%20%20%20%3C%21--%20koi%20c%C3%A1ch%20%C4%91i%E1%BB%87u%20%231%20%28cam%29%20%E2%80%94%20th%C3%A2n%20thon%2C%20%C4%91u%C3%B4i%20xo%C3%A8%2C%201%20v%C3%A2y%20l%C6%B0ng%2C%201%20v%C3%A2y%20b%E1%BB%A5ng%20--%3E%20%20%20%3Cg%20transform%3D%22translate%2840%2C150%29%20rotate%28-6%29%22%3E%20%20%20%20%20%3Cpath%20class%3D%22koiA-body%22%20d%3D%22M2%2018%20Q8%204%2026%202%20Q46%200%2056%2010%20Q60%2014%2060%2018%20Q60%2022%2056%2026%20Q46%2036%2026%2034%20Q8%2032%202%2018%20Z%22/%3E%20%20%20%20%20%3Cpath%20class%3D%22koiA-body%22%20d%3D%22M56%2010%20L78%20-6%20L64%2018%20L78%2042%20L56%2026%20Z%22/%3E%20%20%20%20%20%3Cpath%20class%3D%22koiA-fin%22%20d%3D%22M22%202%20Q18%20-8%2024%20-14%22/%3E%20%20%20%20%20%3Cpath%20class%3D%22koiA-fin%22%20d%3D%22M20%2032%20Q16%2040%2020%2046%22/%3E%20%20%20%20%20%3Cpath%20class%3D%22koiA-fin%22%20d%3D%22M34%2030%20Q30%2038%2034%2044%22/%3E%20%20%20%20%20%3Ccircle%20class%3D%22eye%22%20cx%3D%2210%22%20cy%3D%2215%22%20r%3D%221.4%22%20fill%3D%22%23e8824a%22/%3E%20%20%20%3C/g%3E%20%20%20%20%3C%21--%20koi%20c%C3%A1ch%20%C4%91i%E1%BB%87u%20%232%20%28v%C3%A0ng%20%C4%91%E1%BB%93ng%2C%20nh%E1%BB%8F%20h%C6%A1n%2C%20h%C6%B0%E1%BB%9Bng%20ng%C6%B0%E1%BB%A3c%20l%E1%BA%A1i%29%20--%3E%20%20%20%3Cg%20transform%3D%22translate%28190%2C60%29%20rotate%2810%29%20scale%28-1%2C1%29%20translate%28-70%2C0%29%22%3E%20%20%20%20%20%3Cpath%20class%3D%22koiB-body%22%20d%3D%22M2%2014%20Q6%203%2020%202%20Q36%201%2044%208%20Q47%2011%2047%2014%20Q47%2017%2044%2020%20Q36%2027%2020%2026%20Q6%2025%202%2014%20Z%22/%3E%20%20%20%20%20%3Cpath%20class%3D%22koiB-body%22%20d%3D%22M44%208%20L60%20-4%20L50%2014%20L60%2032%20L44%2020%20Z%22/%3E%20%20%20%20%20%3Cpath%20class%3D%22koiB-fin%22%20d%3D%22M17%202%20Q14%20-6%2018%20-11%22/%3E%20%20%20%20%20%3Cpath%20class%3D%22koiB-fin%22%20d%3D%22M15%2025%20Q12%2031%2015%2036%22/%3E%20%20%20%20%20%3Ccircle%20class%3D%22eye%22%20cx%3D%228%22%20cy%3D%2212%22%20r%3D%221.1%22%20fill%3D%22%23f2c65c%22/%3E%20%20%20%3C/g%3E%20%3C/svg%3E%20");
    background-repeat: repeat;
    background-size: 300px 300px;
    opacity: .4;
    -webkit-mask-image: linear-gradient(100deg, transparent 0%, rgba(0,0,0,.25) 28%, #000 58%);
    mask-image: linear-gradient(100deg, transparent 0%, rgba(0,0,0,.25) 28%, #000 58%);
  }
}
.hero .container {
  display: grid;
  grid-template-columns: 1.05fr .95fr;
  gap: 40px;
  align-items: center;
  padding-top: 76px;
  padding-bottom: 96px;
  min-height: 660px;
}
.hero h1 {
  font-size: clamp(34px, 4.6vw, 56px);
  color: #fff;
  margin-bottom: .3em;
}
.hero h1 em {
  font-style: normal;
  color: var(--koi-orange);
}
.hero p.lede {
  font-size: 17px;
  color: rgba(247,245,239,.78);
  max-width: 46ch;
  margin-bottom: 28px;
}
.hero-ctas { display: flex; gap: 14px; flex-wrap: wrap; }

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: 700;
  font-size: 14.5px;
  padding: 13px 26px;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform var(--dur-base) var(--ease-standard), box-shadow var(--dur-base) var(--ease-standard), background var(--dur-base) var(--ease-standard), color var(--dur-base) var(--ease-standard);
  font-family: var(--font-body);
}
/* Phản hồi lúc NHẤN (không phải hover) — quan trọng trên di động/máy tính bảng vì các thiết bị
   cảm ứng không có hover thật, chỉ có khoảnh khắc :active lúc ngón tay chạm xuống nút.
   Dùng công thức --press-* (khai ở :root, xem giải thích đầy đủ ở đó) thay vì 1 con số % cố
   định — để nút to (btn-block, CTA rộng...) và nút nhỏ đều co lệch ĐÚNG CÙNG 1 mức pixel, cùng
   cảm giác với .select-custom-trigger:active bên dưới (2 rule này CHỦ ĐÍCH dùng chung hệt 1
   công thức calc(), không phải trùng hợp — xem thêm assets/js/main.js observePressWidth()). */
.btn:active { transform: scale(calc(1 - min(var(--press-cap), var(--press-target-px) / var(--press-w)))); }
.btn:disabled:active { transform: none; }
.btn-primary { background: var(--koi-orange); color: #fff; }
.btn-primary:hover { box-shadow: 0 10px 24px -8px rgba(217,102,59,.55); }
.btn-ghost { border-color: rgba(247,245,239,.4); color: var(--paper); }
.btn-ghost:hover:not(:disabled) { background: rgba(247,245,239,.1); }
.btn-dark { background: var(--ink); color: var(--paper); }
.btn-dark:hover:not(:disabled) { background: var(--jade); }
/* Biến thể viền — dùng chung được cho cả nền sáng lẫn nền tối vì không nền
   riêng, chỉ có viền + chữ; mặc định theo var(--ink) cho nền sáng, những chỗ
   cần đổi màu (VD nút huỷ/đăng xuất màu đỏ) tự ghi đè qua style inline
   border-color/color ngay tại chỗ dùng, không cần thêm class riêng. */
.btn-outline { background: transparent; border-color: var(--ink); color: var(--ink); }
.btn-outline:hover:not(:disabled) { background: rgba(18,34,44,.06); }
.btn-block { width: 100%; justify-content: center; }
.btn-sm { padding: 9px 18px; font-size: 13px; }

/* Nút Zalo — dùng đúng màu thương hiệu Zalo (#0068FF) thay vì viền trung tính,
   để 3 lựa chọn liên hệ trong khối "mua sỉ" (product.php .wholesale-cta-actions)
   có phân cấp thị giác rõ ràng: Zalo (xanh thương hiệu) / Gọi ngay (cam koi,
   hành động chính) / Trang liên hệ (viền, phụ) — thay vì 3 nút viền giống hệt nhau. */
.btn-zalo { background: #0068FF; color: #fff; border-color: #0068FF; }
.btn-zalo:hover:not(:disabled) { background: #0055d1; border-color: #0055d1; box-shadow: 0 10px 24px -8px rgba(0,104,255,.45); }

/* Đăng nhập/Đăng ký bằng Google/Facebook (login.php, register.php) — cùng tinh
   thần với .btn-zalo ở trên (màu thương hiệu riêng cho từng nút thay vì dùng
   chung .btn-outline trung tính). Google CỐ Ý dùng style trung tính (nền
   trắng, viền xám nhạt, chữ xám đậm) thay vì tô màu nền — đúng theo brand
   guideline chính thức của Google cho nút "Sign in with Google" (khác hẳn
   Facebook/Zalo là tô kín màu thương hiệu). */
.btn-google { background: #fff; color: #3c4043; border-color: #dadce0; }
.btn-google:hover:not(:disabled) { background: #f7f8f8; box-shadow: 0 1px 3px rgba(18,34,44,.16); }
.btn-facebook { background: #1877f2; color: #fff; border-color: #1877f2; }
.btn-facebook:hover:not(:disabled) { background: #145dbf; border-color: #145dbf; box-shadow: 0 10px 24px -8px rgba(24,119,242,.45); }
/* Nhấc nút lên chỉ khi rê chuột thật và KHÔNG đang nhấn: nếu để transform trong :hover thì nó đè .btn:active
   (cùng độ đặc hiệu, đứng sau) làm nút không co lại lúc bấm; trên cảm ứng cũng tránh hover bị "dính". */
@media (hover: hover) {
  .btn-primary:hover:not(:active),
  .btn-zalo:hover:not(:active),
  .btn-facebook:hover:not(:active) { transform: translateY(var(--lift-sm)); }
}

/* Khối 2 nút Google/Facebook xếp dọc, mỗi nút rộng vừa đủ chữ + canh giữa
   (dùng .btn-fit-center có sẵn, giống mọi nút hành động khác trong
   .contact-card — xem comment ở định nghĩa .btn-fit-center). */
.oauth-buttons { display: flex; flex-direction: column; gap: var(--space-3); }
.oauth-buttons .btn svg, .oauth-buttons .btn img { flex-shrink: 0; }

/* Đường kẻ phân cách "Hoặc" giữa form email/mật khẩu và 2 nút đăng nhập mạng
   xã hội (login.php, register.php) — 2 đường kẻ mảnh dùng chung var(--line)
   (đúng màu viền nhạt đang dùng cho .contact-card), chữ ở giữa dùng
   var(--text-muted) như các dòng phụ khác trong trang. */
.auth-divider { display: flex; align-items: center; gap: var(--space-3); margin: var(--space-5) 0; color: var(--text-muted); font-size: var(--fs-xs); }
.auth-divider::before, .auth-divider::after { content: ''; flex: 1; height: 1px; background: var(--line); }

/* GỐC RỄ LỖI "ảnh bìa ngắn lại sau khi upload": .hero-art (hoạ tiết placeholder
   khi CHƯA có banner) là SVG vuông (aspect-ratio 1/1) rộng bằng cả cột grid
   (0.95fr) — trên màn hình rộng, cột này có thể rộng/cao hơn 488px (= 660px
   min-height của .container trừ padding-top 76 + padding-bottom 96), nên khi
   CHƯA có banner, hàng grid bị chính hoạ tiết này kéo cao vượt quá 660px
   (đo thực tế ví dụ ~784px ở viewport 1440px). Khi ĐÃ có banner, layout đổi
   sang flex chỉ còn khối chữ (thấp hơn nhiều) nên hero tụt về đúng mốc sàn
   660px → hero bị "ngắn lại" so với lúc chưa upload, dù .container đã có
   min-height:660px. Cách sửa: giới hạn max-width của hoạ tiết placeholder để
   nó không bao giờ cao hơn phần nội dung khả dụng (488px), nhờ vậy hàng grid
   không còn bị đội cao hơn 660px — cả 2 trạng thái (có/không banner) đều thực
   sự render đúng bằng 660px, không lệch nhau. */
.hero-art { position: relative; max-width: 420px; margin: 0 auto; }
.ripple-svg { width: 100%; height: auto; }

/* Carousel ảnh giới thiệu phủ toàn bộ nền hero (full-bleed), ảnh tự động
   cắt vừa khung (object-fit: cover) để không có khoảng trắng thừa */
.hero-media {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.hero-bg-carousel {
  position: absolute;
  inset: 0;
}
.hero-bg-carousel .hero-carousel-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  /* Animation trang trí (ngoại lệ có chủ đích) — crossfade ảnh nền hero là
     slideshow phủ toàn màn hình, không phải phản hồi tương tác, nên không ép
     vào thang --dur-*: 1.3s là tốc độ mờ dần phù hợp cho ảnh lớn, rút ngắn sẽ
     tạo cảm giác giật cục. Easing dùng --ease-standard vì rule áp dụng cho cả
     2 chiều mờ vào/mờ ra giữa ảnh cũ và ảnh mới. */
  transition: opacity 1.3s var(--ease-standard);
}
.hero-bg-carousel .hero-carousel-img.active { opacity: 1; }

/* Lớp phủ tối nhẹ giúp chữ luôn đọc được dù ảnh nền sáng hay tối.
   Trước đây gradient trải "phẳng" từ .72 xuống .25 nên dù ở mép phải (nơi
   không có chữ, chỉ có ảnh) vẫn còn tới 25% màu tối phủ lên toàn bộ ảnh
   khiến ảnh mới upload luôn bị xỉn màu/mờ đi so với ảnh gốc. Giảm độ tối ở
   điểm đầu (0%) và cho gradient mờ dần NHANH hơn, về gần như trong suốt
   (.04) rồi hẳn (transparent) trước khi tới mép phải — vùng có chữ bên trái
   vẫn đủ tối để đọc rõ, còn phần lớn diện tích ảnh (đặc biệt bên phải, nơi
   thường là chi tiết chính như cá, cây thuỷ sinh) giữ được màu sắc thật, rực
   rỡ, không bị ám xám. */
.hero-bg-tint {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(100deg, rgba(18,34,44,.58) 0%, rgba(18,34,44,.32) 35%, rgba(18,34,44,.08) 65%, transparent 85%);
}

.hero.hero-has-bg .container {
  position: relative;
  z-index: 2;
  grid-template-columns: 1fr;
  display: flex;
  align-items: center;
  /* Dịch hộp chữ sát về mép trái, dùng đúng padding chuẩn 24px của .container
     để thẳng hàng với các khối nội dung khác trên trang (vd tiêu đề "DANH
     MỤC" bên dưới) */
  justify-content: flex-start;
}

/* Khối kính mờ phía sau chữ — giúp chữ nổi bật mà không che hẳn ảnh nền.
   Tăng độ phủ tối (.32 -> .58) + thêm gradient nhẹ + text-shadow cho chữ
   để đọc rõ hơn trên nhiều ảnh nền sáng/tối khác nhau, vẫn giữ hiệu ứng kính mờ. */
.hero-copy-glass {
  max-width: 620px;
  padding: var(--space-6) 36px;
  border-radius: var(--radius);
  background: linear-gradient(155deg, rgba(9,19,26,.68) 0%, rgba(18,34,44,.58) 100%);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(247,245,239,.16);
  box-shadow: 0 18px 40px -20px rgba(0,0,0,.55);
}
.hero-copy-glass h1,
.hero-copy-glass p.lede,
.hero-copy-glass .eyebrow {
  text-shadow: 0 2px 10px rgba(0,0,0,.35);
}

/* Nút mũi tên chuyển ảnh giới thiệu (nằm trong .hero-media).
   Vị trí gốc (áp cho màn rất rộng >= 1640px): 2 mép trái/phải, giữa chiều cao hero.
   Mobile (<= 900px) đặt lại ở khối "HERO — BỐ CỤC MOBILE" cuối file (cũng 2 mép trái/phải).
   Desktop vừa (901-1639px) đặt lại ngay bên dưới: VẪN 2 mép trái/phải nhưng hạ xuống sát đáy.
   LƯU Ý về thứ tự vẽ: .hero-media có z-index:0 nên tạo stacking context riêng, mọi
   z-index bên trong (kể cả z-index:5 của nút) đều nằm DƯỚI .container (z-index:2).
   Vì vậy nút chỉ bấm được khi .container không chặn sự kiện chuột — xem rule
   pointer-events ở khối desktop bên dưới và ở khối mobile. */
.hero-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 5;
  width: 44px; height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(247,245,239,.35);
  background: rgba(18,34,44,.55);
  color: var(--paper);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  backdrop-filter: blur(4px);
  transition: background var(--dur-fast) var(--ease-standard), transform var(--dur-fast) var(--ease-standard);
}
/* HOVER CHỈ TRÊN THIẾT BỊ CÓ CHUỘT. Trên màn hình cảm ứng, trình duyệt giữ trạng thái :hover của nút vừa
   chạm tới lần chạm kế tiếp -> nền cam "dính" mãi sau khi bấm mũi tên. Nên :hover nằm trong
   @media (hover: hover) (cùng pattern khối .footer-trust-signals), còn :active TÁCH RIÊNG ngoài media để
   cảm ứng vẫn có phản hồi lúc đang chạm (xem lý do ở khối đó). Máy lai (laptop cảm ứng, chuột là thiết bị
   chính) vẫn khớp hover: hover nên không đủ — main.js (đoạn "Nút mũi tên: chống dính hover") thêm
   .is-touched vào nút khi bị chạm bằng ngón tay/bút và gỡ khi chuột đi vào lại; :not(.is-touched) ở đây
   chặn hover trong khoảng đó. */
.hero-arrow:active { background: var(--koi-orange); border-color: var(--koi-orange); }
@media (hover: hover) {
  .hero-arrow:hover:not(.is-touched) { background: var(--koi-orange); border-color: var(--koi-orange); }
}
.hero-arrow-prev { left: 22px; }
.hero-arrow-next { right: 22px; }

/* DESKTOP (>= 901px, khớp HERO_MOBILE_MAX_WIDTH_PX): sửa 2 lỗi của cặp nút mũi tên.
   Lỗi 1 — không bấm được: .container (rộng tối đa 1500px) phủ trùm cả 2 nút. Sửa: .container
   của hero có ảnh nền không nhận sự kiện chuột, chỉ khối chữ nhận lại (giống mobile) -> cú
   bấm rơi xuống nút bên dưới. Áp cho MỌI độ rộng desktop. Không đổi z-index/thứ tự vẽ.
   Lỗi 2 — nút TRÁI đè lên thẻ chữ .hero-copy-glass (nút chiếm x = 22-66px, thẻ bắt đầu từ
   x = (vw-1500)/2 + 24). Hai thứ hết chồng nhau khi vw >= ~1616px; ngưỡng 1640px chừa ~24px
   hở. Vì vậy:
     - >= 1640px: giữ nguyên vị trí gốc (2 mép trái/phải, giữa chiều cao hero);
     - 901-1639px: vẫn 2 mép trái/phải nhưng hạ xuống bottom:72px — thẻ chữ được căn giữa dọc
       và .container có padding-bottom 96px nên thẻ thường kết thúc phía trên nút. bottom lớn hơn
       chiều cao .wave-divider (46px) để sóng trắng ở đáy không cắt nút.
   Nếu đổi độ rộng .container (1500px) hoặc padding thẻ chữ thì phải tính lại ngưỡng 1640. */
@media (min-width: 901px) {
  .hero.hero-has-bg .container { pointer-events: none; }
  .hero.hero-has-bg .hero-copy-glass { pointer-events: auto; }
}
@media (min-width: 901px) and (max-width: 1639px) {
  .hero.hero-has-bg .hero-arrow {
    top: auto;
    bottom: 72px;
    transform: none;
  }
}

/* Vuốt ngang để chuyển ảnh hero (main.js, đoạn "Vuốt ngang để chuyển ảnh"). JS thêm
   .hero-swipeable khi có >= 2 ảnh; .is-swiping chỉ tồn tại trong lúc kéo + trượt sau nhả tay.
   - touch-action: pan-y pinch-zoom: cuộn dọc + zoom vẫn của trình duyệt, vuốt ngang của JS. Đặt
     trên .hero (không phải .hero-media) vì .container phủ lên ảnh — cử chỉ bắt đầu trên thẻ chữ
     cũng phải đi qua .hero. Chỉ áp cho hero có ảnh nền (nút mũi tên + carousel chỉ có khi đó).
   - .is-swiping tắt crossfade opacity 1.3s của ảnh (ảnh phải bám tay tức thì, và lúc đổi class
     sau cú trượt không được chạy fade nhầm); ảnh trượt bằng transform inline do JS gán. */
.hero.hero-swipeable { touch-action: pan-y pinch-zoom; }
.hero.hero-swipeable.is-swiping { -webkit-user-select: none; user-select: none; }
.hero.is-swiping .hero-bg-carousel .hero-carousel-img { transition: none; }

/* wave divider between hero and content — pin tuyệt đối ở đáy hero,
   KHÔNG để nằm trong flex/grid flow của .hero (tránh bị bóp méo) */
.wave-divider {
  display: block;
  position: absolute;
  left: 0;
  bottom: -1px;
  z-index: 3;
  width: 100%;
  height: 46px;
  fill: var(--paper);
}

/* =========================== SECTIONS =========================== */
section.block { padding: var(--space-8) 0; }
.block-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 20px;
  margin-bottom: var(--space-6);
  flex-wrap: wrap;
}
.block-head h2 { font-size: clamp(24px, 3vw, 32px); margin: 0; }
.view-all {
  font-weight: 700;
  font-size: var(--fs-sm);
  color: var(--jade);
  border-bottom: 1px solid var(--jade);
  padding-bottom: 2px;
  white-space: nowrap;
}
.view-all:hover,
.view-all:active { color: var(--koi-orange); border-color: var(--koi-orange); }

/* ---------- category grid ("pond zones") ---------- */
/* justify-items:start — khi hàng cuối không đủ item lấp đầy các cột do
   auto-fit/auto-fill sinh ra, KHÔNG để các thẻ giãn to lấp khoảng trống thừa;
   giữ đúng kích thước chuẩn (giới hạn bởi max-width ở .cat-card) và nằm sát
   trái, đồng bộ với hướng căn chỉnh mặc định (text/nội dung căn trái) của
   phần còn lại trang. Áp dụng nhất quán cho cả .product-grid/.post-grid bên dưới. */
.cat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  justify-items: start;
  gap: 18px;
}
.cat-card {
  position: relative;
  overflow: hidden;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 22px 18px;
  text-align: center;
  width: 100%;
  max-width: 220px; /* chặn giãn to khi hàng không đủ item lấp đầy (xem justify-items ở .cat-grid) */
  transition: transform var(--dur-base) var(--ease-standard), box-shadow var(--dur-base) var(--ease-standard), border-color var(--dur-base) var(--ease-standard);
}
.cat-card:hover,
.cat-card:active {
  /* -3px (--lift-lg) — đồng bộ với .product-card/.post-card; :active thêm để có phản
     hồi khi chạm trên di động (thiết bị cảm ứng không có hover thật). */
  transform: translateY(var(--lift-lg));
  box-shadow: var(--shadow);
  border-color: var(--koi-orange);
}
.cat-card-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  filter: blur(6px);
  transform: scale(1.15); /* tránh lộ viền trắng do blur ở mép */
}
/* Khi danh mục có ảnh riêng (tải lên ở admin): ảnh phủ kín làm NỀN cho cả thẻ — thay cho
   hoạ tiết SVG blur mặc định — icon vẫn giữ nguyên icon đã chọn, không bị ảnh thay thế. */
.cat-card-has-photo { background: var(--paper-deep); }
.cat-card-photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
  transition: transform var(--dur-slow) var(--ease-standard);
}
.cat-card-has-photo:hover .cat-card-photo { transform: scale(1.06); }
/* Lớp phủ tối dần về phía dưới để tên danh mục (đặt ở đáy thẻ) luôn đủ tương phản
   trên mọi ảnh, sáng hay tối. */
.cat-card-scrim {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: linear-gradient(180deg, rgba(18,34,44,.04) 0%, rgba(18,34,44,.1) 45%, rgba(18,34,44,.55) 100%);
  pointer-events: none;
}
.cat-icon {
  position: relative;
  z-index: 1;
  width: 52px; height: 52px;
  margin: 0 auto 14px;
  border-radius: 50%;
  background: rgba(255,255,255,.92);
  box-shadow: 0 2px 10px rgba(18,34,44,.06);
  display: flex; align-items: center; justify-content: center;
  color: var(--jade);
}
.cat-card h3 {
  position: relative;
  z-index: 1;
  font-size: 15.5px; margin: 0; font-family: var(--font-body); font-weight: 700;
}
/* Hộp "mica" mờ đằng sau tên danh mục — chỉ cần khi thẻ có ảnh nền riêng, để chữ luôn
   đọc rõ dù ảnh sáng/tối/nhiều chi tiết ra sao. */
.cat-card-has-photo h3 {
  display: inline-block;
  color: #fff;
  background: rgba(18, 34, 44, .38);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(255,255,255,.16);
  padding: 6px 14px;
  border-radius: var(--radius-pill);
  box-shadow: 0 2px 10px rgba(18,34,44,.18);
}

/* ---------- product grid & card ---------- */
.product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  justify-items: start; /* xem giải thích ở .cat-grid phía trên */
  gap: 22px;
}
/* Trang "Tất cả sản phẩm" (category.php) — cột cố định (không auto-fill) để
   PRODUCTS_PER_PAGE (15) luôn chia hết, không lệch dòng cuối: 3 cột ở
   tablet, 5 cột ở desktop. Mobile (≤640px) vẫn dùng .product-grid gốc phía
   trên, không đổi. Chỉ áp dụng cho trang này, không ảnh hưởng .product-grid
   ở các trang khác (trang chủ, tìm kiếm, yêu thích, sản phẩm liên quan). */
@media (min-width: 641px) {
  .product-grid--catalog { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 901px) {
  .product-grid--catalog { grid-template-columns: repeat(5, 1fr); }
}
.product-card {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 320px; /* chặn giãn to khi hàng không đủ item lấp đầy (xem justify-items ở .product-grid) */
  transition: box-shadow var(--dur-base) var(--ease-standard), transform var(--dur-base) var(--ease-standard);
  position: relative;
}
.product-card:hover,
.product-card:active { box-shadow: var(--shadow); transform: translateY(var(--lift-lg)); }
.product-thumb {
  aspect-ratio: 1 / 1;
  background: var(--paper-deep);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
  position: relative;
}
.product-thumb img { width: 100%; height: 100%; object-fit: cover; }
/* Nhãn sản phẩm (Giảm giá / Bán chạy / Mới về) — kiểu "kính mờ" (glass/arylic):
   nền bán trong suốt + backdrop-filter blur thay vì màu đặc phẳng, viền sáng mảnh
   để có chiều sâu. Gom cả 2 nhãn vào 1 khối .product-badges neo góc trên-trái,
   xếp CHỒNG THEO CHIỀU DỌC (column) thay vì 1 trái/1 phải như trước — cách cũ khiến
   2 nhãn (đặc biệt "🔥 Bán chạy" dài) đụng/đè lên nhau khi thẻ sản phẩm hẹp trên
   mobile (grid rút xuống ~155px). Xếp dọc cùng góc thì dù thẻ hẹp đến đâu 2 nhãn
   cũng không bao giờ chồng nhau, chỉ đơn giản xếp thêm hàng. */
.product-badges {
  position: absolute;
  top: 10px; left: 10px; right: 10px;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  pointer-events: none;
}
.badge-sale,
.badge-tag {
  /* Vị trí mặc định (đứng độc lập, vd badge "Video" ở blog.php) — khi nằm trong
     .product-badges, quy tắc bên dưới chuyển về static để khối cha (flex/column)
     tự sắp xếp, tránh 2 lớp position:absolute chồng nhau. */
  position: absolute;
  top: 10px; left: 10px;
  z-index: 2;
  font-size: 11px;
  font-weight: 800;
  padding: 5px 10px;
  border-radius: var(--radius-pill);
  letter-spacing: .03em;
  color: #fff;
  border: 1px solid rgba(255,255,255,.38);
  box-shadow: 0 6px 16px -6px rgba(18,34,44,.5);
  backdrop-filter: blur(7px) saturate(180%);
  -webkit-backdrop-filter: blur(7px) saturate(180%);
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.product-badges .badge-sale,
.product-badges .badge-tag { position: static; top: auto; left: auto; }
.badge-sale { background: rgba(194,59,74,.72); }
.badge-tag-bestseller { background: rgba(217,102,59,.72); }
.badge-tag-new { background: rgba(31,111,106,.72); }

/* Nút "❤ Lưu" (wishlist) trên thẻ sản phẩm. Đặt là con trực tiếp của .product-card
   (không nằm trong .product-thumb vốn là <a>, để tránh lồng thẻ <a> không hợp lệ khi
   wishlist render dạng link cho khách chưa đăng nhập) — neo góc trên-phải bằng
   position:absolute, .product-card đã sẵn position:relative. */
.wishlist-btn {
  position: absolute;
  top: 10px; right: 10px;
  z-index: 3;
  width: 32px; height: 32px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, .85);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  color: var(--koi-red);
  transition: background var(--dur-fast) var(--ease-standard), transform var(--dur-fast) var(--ease-standard);
  text-decoration: none;
}
.wishlist-btn:hover,
.wishlist-btn:active { background: #fff; transform: scale(1.06); }
.wishlist-btn svg { width: 16px; height: 16px; }
/* Trạng thái "đã lưu": tim đặc màu đỏ san hô thay vì viền rỗng — đổi qua JS bằng cách
   toggle class .is-wished (main.js), không cần tải lại trang. */
.wishlist-btn.is-wished { color: #fff; background: var(--koi-red); border-color: var(--koi-red); }
.wishlist-btn.is-wished svg { fill: currentColor; }
/* Hiệu ứng "pop" ngắn khi chuyển SANG trạng thái is-wished (bấm ❤ để lưu) — scale-in
   phẳng dùng --ease-enter, không dùng easing overshoot (xem comment tại
   .mnb-item-toggle.active .mnb-icon) — giữ nhất quán cảm giác "phẳng, điềm tĩnh" của
   site thay vì tạo 1 điểm nhấn nảy/bung khác biệt riêng cho nút này. Class
   .is-wish-popped thêm/gỡ tạm thời qua JS (main.js,
   khối js-wishlist-toggle) — phồng to nhanh (--dur-fast/--ease-enter) rồi TỰ TRỞ VỀ
   kích thước gốc bằng đúng transition nền của .wishlist-btn (--dur-fast/--ease-standard,
   khai báo ở khối .wishlist-btn phía trên) khi JS gỡ class, không cần khai báo transition
   riêng cho chiều "về". */
.wishlist-btn.is-wish-popped {
  transform: scale(1.22);
  transition: transform var(--dur-fast) var(--ease-enter);
}

.product-body { padding: var(--space-4) var(--space-4) 18px; display: flex; flex-direction: column; gap: var(--space-2); flex: 1; }
.product-name {
  font-weight: 700;
  font-size: 15px;
  line-height: 1.35;
  min-height: 2.7em;
  /* Tối đa 3 dòng rồi cắt "..." — trước đây chỉ có min-height nên tên dài (CSDL cho phép
     255 ký tự) làm thẻ cao 6–8 dòng và kéo cả hàng lưới cao theo. overflow-wrap:anywhere để
     chuỗi dài không dấu cách (mã SKU, URL) tự ngắt thay vì tràn/bị cắt ngang thẻ. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}
.product-name a:hover,
.product-name a:active { color: var(--koi-orange); }
.product-sold { font-size: var(--fs-xs); color: var(--text-muted); }
.product-price-row { display: flex; align-items: baseline; gap: var(--space-2); margin-top: auto; flex-wrap: wrap; }
.price-now { font-family: var(--font-display); font-weight: 700; font-size: 18px; color: var(--koi-red); }
.price-old { font-size: var(--fs-sm); color: var(--text-muted); text-decoration: line-through; }
/* Chỉ áp dụng cho #pdPriceOld (product.php, khối biến thể có/không giá riêng, xem
   main.js hàm fadeToggle) — lớp .js-fade-toggle chỉ được product.php gắn thêm cho đúng
   1 span này, không gắn cho .price-old dùng chung trên thẻ sản phẩm (product-card.php,
   luôn tĩnh, PHP render có/không hẳn thẻ tuỳ $hasSale, không qua JS toggle) — nên rule
   display:none mặc định của .js-fade-toggle không ảnh hưởng gì tới các .price-old tĩnh đó. */
.price-old.is-shown { display: inline; }
/* #pdOptionHint (product.php) theo đúng pattern .js-fade-toggle, .pd-option-hint
   vốn là <p> (block mặc định). */
.pd-option-hint.is-shown { display: block; }
.add-cart-btn {
  margin-top: 10px;
  width: 100%;
  border: 1px solid var(--ink);
  background: transparent;
  color: var(--ink);
  font-weight: 700;
  font-size: var(--fs-sm);
  padding: 10px;
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.add-cart-btn:hover,
.add-cart-btn:active { background: var(--ink); color: var(--paper); }
a.add-cart-btn { display: block; text-align: center; text-decoration: none; box-sizing: border-box; }

/* ---------- Mô tả danh mục (category.php) — hiển thị đúng nội dung admin đã nhập ở
   "Mô tả" trang admin/categories.php. Thiết kế dạng banner nhấn màu thương hiệu (gradient
   jade) thay vì khối nền trắng phẳng — để không bị nhầm với 1 dòng text thường, đồng thời
   có điểm nhấn trang trí (vòng tròn mờ) cho đỡ đơn điệu. */
/* Dòng mô tả danh mục — subtitle nhẹ ngay dưới H1 trong .page-head, KHÔNG còn là khối nền
   màu đặc (jade gradient) như trước: chỉ còn icon "info" nhỏ + chữ nghiêng màu xám, thấp và
   hoà cùng nền be của .page-head thay vì tương phản mạnh, tách bạch với phần trang bên dưới. */
.category-description {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  max-width: 720px;
  margin: 10px 0 0;
  font-size: 14.5px;
  font-style: italic;
  color: var(--text-muted);
  line-height: 1.65;
}
.category-description-icon {
  flex-shrink: 0;
  /* Canh icon (cao 15px) vào đúng tâm dòng chữ đầu tiên: chiều cao dòng chữ
     = 1.65 * font-size hiện tại (kế thừa từ .category-description, đổi theo
     var(--fs-sm) ở mobile) — dùng "1em" thay vì hardcode px để margin-top tự
     tính lại đúng cho cả desktop lẫn mobile, không lệch cao/thấp ở size nào. */
  margin-top: calc((1em * 1.65 - 15px) / 2);
  display: flex;
  color: var(--gold);
}

/* ---------- thanh sắp xếp & lọc giá (category.php) — THIẾT KẾ LẠI TOÀN BỘ ----------
   Bản trước dùng 1 khung nền lớn phủ hoạ tiết chấm bi, bên trong 2 "thẻ" lọc (Sắp xếp/
   Khoảng giá) chỉ chiếm 1 phần nhỏ bên trái, để trống rất nhiều khoảng trắng bên phải —
   nhìn mất cân đối, không tinh tế. Thiết kế mới: 1 thanh MẢNH, nền trắng phẳng, viền
   mảnh + bóng nhẹ (không hoạ tiết); mỗi bộ lọc chỉ còn nhãn nhỏ (caption) + control gọn,
   KHÔNG còn khung thẻ riêng — các bộ lọc ngăn cách nhau bằng 1 đường kẻ dọc mảnh thay vì
   viền hộp, để cả cụm trông liền mạch như 1 thanh công cụ duy nhất. `justify-content:
   space-between` đẩy toàn bộ cụm lọc (.toolbar-filters) sang trái và số lượng sản phẩm
   (.toolbar-meta) sang phải — lấp đúng khoảng trống từng bị bỏ trống, đồng thời cho khách
   biết ngay có bao nhiêu sản phẩm khớp bộ lọc hiện tại. */
.category-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4) var(--space-6);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 18px 24px;
  margin-bottom: 28px;
  box-shadow: var(--shadow-soft);
}
.toolbar-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-6);
}
.toolbar-field {
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: none;
  border: none;
  padding: 0;
  min-height: auto;
  box-sizing: border-box;
}
/* Đường kẻ dọc mảnh phân tách các bộ lọc, giữ cảm giác "1 thanh công cụ liền
   mạch" thay vì nhiều thẻ rời rạc cạnh nhau. */
.toolbar-filters > .toolbar-field + .toolbar-field {
  padding-left: var(--space-6);
  border-left: 1px solid var(--line);
}
/* Nhãn "Sắp xếp" / "Khoảng giá" — chỉ còn chữ hoa nhỏ màu phụ (caption), không còn nền
   pill riêng — tinh giản, để mắt tập trung vào chính control (select/thanh trượt) bên dưới. */
.toolbar-field label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--text-muted);
  margin: 0;
  line-height: 1.3;
}
/* Select kiểu "khung hộp" — đồng bộ với ngôn ngữ thiết kế chung của toàn site (ô tìm
   kiếm .search-form, các nút phân trang .pagination-page...): viền mảnh var(--line),
   bo góc var(--radius-sm), nền trắng, cao 40px (khung hộp là ngôn ngữ chủ đạo của mọi
   input/control trên site). Hover/focus đổi viền sang cam + thêm quầng sáng (box-shadow)
   mảnh, giống hệt cách .search-form:focus-within phản hồi — 1 hiệu ứng "focus ring"
   dùng chung cho mọi control dạng khung hộp. */
.toolbar-field select {
  -webkit-appearance: none;
  appearance: none;
  height: 40px;
  padding: 0 34px 0 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--fs-sm);
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%2312222c' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") right 14px center / 12px no-repeat,
    #fff;
  color: var(--ink);
  transition: border-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard);
}
.toolbar-field select:hover { border-color: var(--koi-orange); }
.toolbar-field select:focus {
  outline: none;
  border-color: var(--koi-orange);
  box-shadow: 0 0 0 3px rgba(217, 102, 59, .12);
}
.toolbar-field select { min-width: 190px; cursor: pointer; }
.toolbar-actions { display: flex; align-items: center; gap: var(--space-3); }
.toolbar-submit { padding: 10px 22px; font-size: var(--fs-sm); }

/* ---------- Custom select (mọi kích thước màn hình) cho toàn bộ <select> phía khách
   hàng ----------
   <select> gốc của trình duyệt khi mở ra dùng giao diện mặc định của hệ điều hành/trình
   duyệt (nền trắng/xám đơn điệu), không style được bằng CSS thuần — lệch hẳn so với phần
   còn lại của thiết kế site. JS (assets/js/main.js, hàm enhanceSelectCustom) bọc mỗi
   <select> trong 1 khối ".select-custom" gồm: chính <select> gốc (vẫn giữ nguyên trong
   DOM, vẫn nhận đúng giá trị + bắn đúng sự kiện 'change' để js-auto-submit hoạt động y
   hệt) + 1 nút trigger hiện giá trị đang chọn + 1 panel tự vẽ khi bấm mở ra. Áp dụng
   trigger/panel tự vẽ cho mọi kích thước màn hình để mobile có cùng khung hộp + hiệu
   ứng mở/đóng kiểu iOS như desktop. */
.select-custom { position: relative; }
/* <select> gốc vẫn CHIẾM ĐÚNG chỗ của trigger (position:absolute;inset:0 trong khối
   cha đã position:relative) thay vì bị display:none/ẩn hẳn — để nếu sau này có thêm
   1 <select> khác có thuộc tính required, constraint validation của trình duyệt vẫn
   hoạt động bình thường (phần tử display:none bị loại khỏi constraint validation
   hoàn toàn) và bong bóng thông báo lỗi (nếu có) vẫn neo đúng vị trí trực quan của
   trigger thay vì lạc ra 1 góc màn hình. */
.select-custom-native {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  pointer-events: none;
}
/* Trigger dùng đúng khung hộp như .toolbar-field select (viền var(--line), bo góc
   var(--radius-sm), nền trắng, cao 40px) — cùng 1 giao diện + hiệu ứng cho MỌI kích
   thước màn hình, không còn khác biệt desktop/mobile. */
.select-custom-trigger {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    min-width: 190px;
    height: 40px;
    background: #fff;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    padding: 0 14px;
    font-family: var(--font-body);
    font-weight: 700;
    font-size: var(--fs-sm);
    color: var(--ink);
    cursor: pointer;
    text-align: left;
    /* transform dùng --dur-instant để khớp --dur-instant khai trong :active bên dưới,
       tránh co lại "giật" không mượt (bug đã sửa: thiếu transform trong transition này). */
    transition: border-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard), transform var(--dur-instant) var(--ease-standard);
  }
  /* :active thêm 1 nhịp "nhấn xuống" cực nhỏ (tinh thần iOS: mọi control chạm được đều
     có phản hồi hình học tức thì khi nhấn, không chỉ đổi màu) — dùng --dur-instant vì
     đây là phản hồi CHẠM chứ không phải hover, xem hệ thống motion ở đầu file.
     Dùng công thức --press-* (khai ở :root) chứ không phải scale(.98) cố định nữa — bản
     thân dropdown lọc đầu trang (~190px) CHÍNH LÀ khung tham chiếu để tính ra công thức đó
     (190px * 2% = 3.8px = --press-target-px), nên ở đây kết quả giữ nguyên y hệt cảm giác
     cũ; công thức chỉ thật sự "phát huy" khác biệt ở những .select-custom-trigger rộng hơn
     (vd trong form Mã giảm giá) — xem giải thích đầy đủ ở khối --press-* tại :root. */
  .select-custom-trigger:active { transform: scale(calc(1 - min(var(--press-cap), var(--press-target-px) / var(--press-w)))); }
  .select-custom-trigger:hover,
  .select-custom-trigger:focus-visible,
  .select-custom.is-open .select-custom-trigger {
    outline: none;
    border-color: var(--koi-orange);
    box-shadow: 0 0 0 3px rgba(217, 102, 59, .12);
  }
  .select-custom-trigger-label {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .select-custom-caret { flex-shrink: 0; color: var(--ink); transition: transform var(--dur-base) var(--ease-standard); }
  .select-custom.is-open .select-custom-caret { transform: rotate(180deg); }
  .select-custom.is-disabled .select-custom-trigger { opacity: .5; cursor: not-allowed; }
  /* Panel: cùng khung viền/bo góc/đổ bóng với .search-suggest (dropdown gợi ý tìm kiếm) —
     2 dropdown "nổi lên dưới 1 ô control" duy nhất trên site, dùng chung 1 ngôn ngữ hộp
     nổi để nhất quán, thay vì mỗi nơi 1 kiểu bo góc/đổ bóng riêng.
     Hiệu ứng mở/đóng kiểu iOS (menu ngữ cảnh/action sheet): phồng nhẹ ra từ góc trên-trái
     (fade + scale nhẹ .98→1 + trượt lên 6px), KHÔNG nảy/bung — đúng tinh thần "phẳng,
     điềm tĩnh" của --ease-enter/--ease-exit đã định nghĩa sẵn ở :root dành riêng cho
     dropdown (xem chú thích hệ thống motion đầu file). Panel luôn nằm trong DOM (không
     dùng display:none/thuộc tính [hidden] nữa — 2 thứ đó tắt transition ngay lập tức),
     ẩn/hiện qua opacity+visibility+transform để có thể animate mượt cả lúc đóng lẫn mở:
     - Trạng thái ĐÓNG (mặc định): opacity 0, thu nhỏ nhẹ, visibility ẩn NGAY SAU khi
       hiệu ứng fade-out (--dur-base) chạy xong (transition-delay bằng đúng --dur-base)
       — nhờ vậy panel vẫn "nhìn thấy được" trong suốt lúc đang mờ dần, không bị cắt
       cụt giữa chừng.
     - Trạng thái MỞ (.is-open): opacity 1, kích thước thật, visibility hiện NGAY (delay
       0s) để hiệu ứng phồng ra bắt đầu chạy từ khung hình đầu tiên, không có độ trễ
       cảm nhận được giữa lúc bấm và lúc panel bắt đầu xuất hiện. */
  .select-custom-panel {
    /* display:block cần khai báo tường minh: panel là 1 phần tử được JS chèn động,
       không có rule nào khác ép display:none cho nó — nhưng khai báo rõ ở đây để
       chắc chắn không phụ thuộc display mặc định của trình duyệt cho <div>, đồng thời
       làm rõ ý đồ "ẩn/hiện qua opacity+visibility, KHÔNG qua display" cho người đọc
       code sau này (từng có lỗi ngược lại: quên set display khiến panel bị kẹt
       display:none vĩnh viễn dù mọi opacity/transform bên dưới đều đúng — xem lịch
       sử sửa lỗi). Panel là position:absolute nên display:block ở đây không ảnh
       hưởng layout khi đang ẩn.
       max-height/overflow-y CŨ đặt thẳng ở đây đã chuyển xuống .select-custom-options
       bên dưới — panel giờ chỉ là khung ngoài (viền/bo góc/đổ bóng + hiệu ứng mở/đóng),
       để phần lọc (.select-custom-filter-wrap, nếu có) đứng cố định phía trên, KHÔNG
       cuộn theo khi danh sách option dài (Phường/Xã, Danh mục...). */
    display: block;
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    min-width: 100%;
    z-index: var(--z-dropdown);
    background: #fff;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: 6px;
    transform-origin: top left;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateY(-6px) scale(.98);
    transition: opacity var(--dur-base) var(--ease-exit),
                transform var(--dur-base) var(--ease-exit),
                visibility 0s linear var(--dur-base);
  }
  .select-custom.is-open .select-custom-panel {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translateY(0) scale(1);
    transition: opacity var(--dur-slow) var(--ease-enter),
                transform var(--dur-slow) var(--ease-enter),
                visibility 0s linear 0s;
  }
  /* Ô lọc (JS chỉ chèn khi select có > SELECT_FILTER_MIN_OPTIONS option — xem
     ensureFilterUI() trong main.js) — đứng NGOÀI khối cuộn .select-custom-options bên
     dưới nên luôn cố định ở đầu panel dù đã cuộn xuống giữa danh sách dài. Viền dưới
     mảnh (--line) để tách nhẹ khỏi danh sách, không cần khung/nền riêng vì đã nằm sẵn
     trong panel trắng. */
  .select-custom-filter-wrap {
    display: flex;
    align-items: center;
    gap: 8px;
    /* padding ngang = 14px, KHỚP ĐÚNG padding ngang của .select-custom-option bên dưới
       (8px 14px) — panel cha đã có padding:6px riêng, nên tổng lề trái tính từ viền
       panel bằng nhau giữa icon kính lúp và chữ option, thẳng hàng thay vì lệch nhau. */
    padding: 6px 14px 10px;
    margin-bottom: 4px;
    border-bottom: 1px solid var(--line);
    color: var(--text-muted);
  }
  .select-custom-filter-icon { flex-shrink: 0; }
  /* Input trơn (không viền/nền riêng — đã có viền dưới của .select-custom-filter-wrap
     làm ranh giới) để trông liền mạch như 1 phần của panel, không phải 1 ô input rời. */
  .select-custom-filter-input {
    flex: 1;
    min-width: 0;
    border: none;
    outline: none;
    background: transparent;
    font-family: var(--font-body);
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--ink);
  }
  .select-custom-filter-input::placeholder { color: var(--text-muted); font-weight: 400; }
  /* Khối cuộn thật sự — nhận lại đúng max-height/overflow-y từng đặt thẳng trên
     .select-custom-panel trước đây (xem chú thích ở đó). role="listbox" cũng chuyển
     xuống đây (thay vì trên panel ngoài) để khớp đúng ngữ nghĩa ARIA: listbox giờ chỉ
     chứa các phần tử role="option", không lẫn ô lọc. */
  .select-custom-options { max-height: 260px; overflow-y: auto; }
  .select-custom-option {
    padding: 8px 14px;
    border-radius: var(--radius-sm);
    font-family: var(--font-body);
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--ink);
    cursor: pointer;
    white-space: nowrap;
    transition: background var(--dur-fast) var(--ease-standard);
  }
  .select-custom-option:hover,
  .select-custom-option:active { background: var(--paper); }
  .select-custom-option.is-selected { background: rgba(31, 111, 106, .1); color: var(--jade); }
  .select-custom-option.is-disabled { opacity: .5; cursor: not-allowed; }
  /* Option đang được tô sáng bằng phím ↑/↓ trong lúc gõ ô lọc (khác với hover chuột và
     khác với is-selected — is-selected là GIÁ TRỊ ĐANG CHỌN thật của select, còn trạng
     thái này chỉ là đang "duyệt tới" bằng bàn phím, dùng chung tông nền hover cho nhẹ
     nhàng, không tạo thêm 1 màu mới). */
  .select-custom-option.is-active-by-filter { background: var(--paper); }
  /* Option vừa là GIÁ TRỊ ĐANG CHỌN (is-selected) vừa đang được ↑/↓ tô sáng
     (is-active-by-filter) — 2 rule trên có cùng độ đặc hiệu (0,0,2,0), rule sau (đứng dưới
     trong file) sẽ thắng nếu không có rule gộp riêng, khiến nền teal nhạt của is-selected
     bị nền --paper của is-active-by-filter đè mất (chữ vẫn giữ màu --jade vì is-selected
     không đổi color), làm người dùng dễ tưởng lựa chọn hiện tại "biến mất" trong lúc đang
     gõ lọc. Rule gộp riêng dưới đây (đặc hiệu cao hơn, 0,0,3,0) giữ token màu teal quen
     thuộc nhưng đậm hơn 1 chút để vẫn phân biệt được với is-selected đứng yên. */
  .select-custom-option.is-selected.is-active-by-filter { background: rgba(31, 111, 106, .18); }
  /* display:none thay vì opacity/visibility — option bị lọc ẩn không cần animate riêng
     (gõ lọc là 1 hành động tức thời, không phải mở/đóng cả panel), và ẩn hẳn khỏi luồng
     layout để danh sách các option còn lại xếp sát nhau, không để lại khoảng trống. */
  .select-custom-option.is-hidden-by-filter { display: none; }
  /* Danh sách co/giãn chiều cao khi gõ lọc (main.js applyFilter(..., true) đặt height px + class này
     chỉ trong ~200ms). overflow:hidden lúc chạy để thanh cuộn không chớp khi chiều cao đang đổi. */
  .select-custom-options.is-resizing {
    overflow: hidden;
    transition: height var(--dur-base) var(--ease-standard);
  }
  /* Thông báo khi gõ lọc không khớp option nào — cùng tinh thần .search-suggest-empty
     (ô tìm sản phẩm): 1 dòng chữ phụ, không có gì để bấm. */
  .select-custom-empty {
    animation: msg-fade var(--dur-base) var(--ease-enter); /* chạy lại mỗi lần hết [hidden] */
    padding: 10px 14px;
    font-size: var(--fs-sm);
    color: var(--text-muted);
    white-space: normal;
  }
/* Không cần khối prefers-reduced-motion riêng cho panel này: rule chung
   "*:not(.category-sheet) { transition:none!important... }" ở gần cuối file
   (mục MODAL/reduced-motion) đã tự động tắt hẳn transition ở trên, panel sẽ
   ẩn/hiện tức thì (0ms) khi khách bật "giảm chuyển động" — đúng hành vi mong
   muốn, không cần giữ lại 1 fade ngắn như .category-sheet (đóng/mở select
   không phải thao tác "lớn" cần phản hồi chuyển tiếp để tránh hụt hẫng). */

/* Số lượng sản phẩm khớp bộ lọc hiện tại — chip có chủ đích rõ ràng (nền nhạt + icon +
   đường viền dọc ngăn cách với cụm lọc bên trái), thay vì 1 dòng chữ trần trôi nổi giữa
   khoảng trắng khi .toolbar-filters không đủ dài để lấp gần hết chiều ngang thanh công
   cụ (danh mục không có thanh trượt giá, hoặc màn hình rất rộng). */
.toolbar-meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
  margin-left: var(--space-6);
  padding: 9px 16px 9px var(--space-6);
  border-left: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--paper-deep);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  white-space: nowrap;
}
.toolbar-meta-icon {
  display: flex;
  flex-shrink: 0;
  color: var(--jade);
}
.toolbar-meta strong { color: var(--ink); font-weight: 700; }
.toolbar-clear {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--koi-red);
  align-self: center;
}
.toolbar-clear:hover { text-decoration: underline; }
.toolbar-clear:active { text-decoration: underline; opacity: .8; }

/* ---------- Thanh phân trang (category.php, blog.php) ----------
   Dùng lại đúng token màu/bo góc/spacing đang có (--line, --ink, --jade, --radius-pill...),
   cùng tinh thần với .toolbar-field/.btn-outline ở trên: viền mảnh, nền trắng, chỉ nổi bật
   trang đang xem bằng nền đặc màu mực (--ink) thay vì thêm màu mới. */
.pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  margin-top: var(--space-6);
}
.pagination-page,
.pagination-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 44px — mức tối thiểu khuyến nghị cho vùng chạm trên di động (Apple HIG/Material
     Design), trước là 40px. .pagination-ellipsis (dấu "…") KHÔNG đổi theo vì đó là
     <span> tĩnh không click được, không phải vùng chạm thật. */
  min-width: 44px;
  height: 44px;
  padding: 0 var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: #fff;
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 700;
  box-sizing: border-box;
}
.pagination-page:hover,
.pagination-arrow:hover,
.pagination-page:active,
.pagination-arrow:active { border-color: var(--jade); color: var(--jade); }
.pagination-page-active {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}
.pagination-page-active:hover,
.pagination-page-active:active { color: var(--paper); }
.pagination-ellipsis {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Đổi theo 44px của .pagination-page/.pagination-arrow ở trên chỉ để giữ thẳng hàng
     trong cùng 1 hàng flex — bản thân dấu "…" là <span> tĩnh, không phải vùng chạm. */
  min-width: 44px;
  height: 44px;
  color: var(--text-muted);
}
.pagination-arrow { font-weight: 700; }

/* ---------- thanh trượt khoảng giá (thay cho 2 ô nhập tay) ----------
   Cố định chiều rộng vừa phải (không dùng flex-grow) để thanh trượt không bị kéo giãn
   chiếm hết khoảng trống còn lại của toolbar — chỉ gọn trong khung như 1 ô lọc bình thường. */
.toolbar-field-price { width: 220px; flex: 0 0 auto; }
/* .toolbar-field (khung ngoài dùng chung) đã đảm nhiệm viền/nền/bo góc/chiều cao —
   .price-slider chỉ còn là vùng chứa track+tay kéo, không lồng thêm khung riêng nữa
   (tránh "viền đôi" và lệch chiều cao so với ô Sắp xếp bên cạnh). */
.price-slider {
  padding: 0;
  background: transparent;
  border: none;
  box-sizing: border-box;
}
/* Vùng chứa track + 2 tay kéo dùng chung 1 hệ toạ độ 0–100% duy nhất — track và cả 2
   input[range] đều là con trực tiếp của khối này, đảm bảo vệt màu tô luôn khớp đúng
   vị trí 2 tay kéo, không lệch dù khung ngoài (.price-slider) có padding trang trí. */
.price-slider-body { position: relative; width: 100%; height: 22px; }
/* Track dày, bo tròn hẳn kiểu "pill" — dễ nhìn thấy khoảng đã chọn. */
.price-slider-track {
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 6px;
  border-radius: var(--radius-pill);
  background: #e4dfd2;
  transform: translateY(-50%);
}
.price-slider-range {
  position: absolute;
  top: 50%;
  left: 0%; right: 0%;
  height: 6px;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--koi-orange), var(--gold));
  transform: translateY(-50%);
}
.price-slider-inputs { position: relative; width: 100%; height: 100%; }
/* 2 input[type=range] chồng đúng lên track, dùng chung 1 vùng kéo. Track/nền của mỗi input
   được ẩn trong suốt (chỉ còn phần thumb hiển thị) vì phần track hiển thị đã do
   .price-slider-track/.price-slider-range đảm nhiệm — tránh 2 lớp track đè lên nhau.
   QUAN TRỌNG: reset hẳn border/padding/border-radius/box-sizing ở đây — vì .price-slider
   nằm trong <form class="category-toolbar">, quy tắc toàn cục "form input, form textarea,
   form select" (viền 1px + padding 12px 14px + bo góc + nền be) áp dụng cho MỌI input kể cả
   input[type=range], kể cả khi input đó nằm đè tuyệt đối/trong suốt — nếu không tự
   ghi đè border/padding, 2 input range sẽ bị viền+nền đó "đè" lên, tạo ra khung viền
   xấu bao quanh thanh trượt VÀ khiến padding kéo lệch vị trí 2 tay kéo (không còn
   thẳng hàng theo track). Khai báo lại đầy đủ ở đây để luôn thắng bất kể form cha có
   style gì. */
.price-range {
  position: absolute;
  left: 0; right: 0; top: 0;
  width: 100%;
  margin: 0;
  padding: 0;
  border: none;
  border-radius: 0;
  box-sizing: border-box;
  height: 22px;
  background: transparent;
  -webkit-appearance: none;
  appearance: none;
  pointer-events: none; /* chỉ phần thumb mới nhận thao tác kéo, tránh 2 input tranh nhau bắt sự kiện trên track */
}
.price-range:focus { outline: none; }
.price-range::-webkit-slider-runnable-track { background: transparent; height: 22px; border: none; }
.price-range::-moz-range-track { background: transparent; height: 22px; border: none; }
/* Tay kéo: hình tròn đặc màu cam thương hiệu, viền trắng dày + đổ bóng mềm — cùng tinh
   thần với ảnh mẫu (khối tròn đặc, nổi khối rõ ràng) nhưng dùng màu cam của shop thay vì
   xanh lam, để đồng bộ với .price-slider-range và các điểm nhấn cam khác trên trang.
   width/height bằng nhau + border cùng độ dày 2 tay kéo để luôn thẳng hàng trên track. */
.price-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  pointer-events: auto;
  width: 20px;
  height: 20px;
  margin-top: 0;
  border-radius: 50%;
  background: var(--koi-orange);
  border: 3px solid #fff;
  box-shadow: 0 1px 3px rgba(18,34,44,.18), 0 3px 8px rgba(217,102,59,.35);
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard);
}
.price-range::-webkit-slider-thumb:hover,
.price-range::-webkit-slider-thumb:active { transform: scale(1.12); box-shadow: 0 2px 5px rgba(18,34,44,.22), 0 4px 12px rgba(217,102,59,.45); }
.price-range::-moz-range-thumb {
  pointer-events: auto;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--koi-orange);
  border: 3px solid #fff;
  box-shadow: 0 1px 3px rgba(18,34,44,.18), 0 3px 8px rgba(217,102,59,.35);
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard);
}
.price-range::-moz-range-thumb:hover,
.price-range::-moz-range-thumb:active { transform: scale(1.12); box-shadow: 0 2px 5px rgba(18,34,44,.22), 0 4px 12px rgba(217,102,59,.45); }
.price-range:focus-visible::-webkit-slider-thumb { outline: 2px solid var(--koi-orange); outline-offset: 3px; }
.price-range:focus-visible::-moz-range-thumb { outline: 2px solid var(--koi-orange); outline-offset: 3px; }
/* Giá trị hiện tại: đặt ngay dưới thanh trượt, 2 đầu trái/phải, kiểu "pill" trắng nổi
   trên nền be của khối để nhìn rõ ràng, tách bạch với nền .price-slider. */
.price-slider-values {
  display: flex;
  justify-content: space-between;
  margin-top: var(--space-3);
  font-size: var(--fs-xs);
}
.price-slider-values span:not(.price-slider-sep) {
  background: #fff;
  color: var(--koi-orange);
  font-weight: 700;
  padding: var(--space-1) 10px;
  border-radius: var(--radius-pill);
  box-shadow: 0 1px 2px rgba(18,34,44,.08);
}
.price-slider-sep { color: #c9c1af; font-weight: 400; align-self: center; }
@media (max-width: 640px) {
  .category-toolbar { flex-direction: column; align-items: stretch; padding: 18px 20px; }
  .toolbar-filters { flex-direction: column; align-items: stretch; gap: var(--space-4); width: 100%; }
  .toolbar-filters > .toolbar-field + .toolbar-field {
    padding-left: 0;
    border-left: none;
    padding-top: var(--space-4);
    border-top: 1px solid var(--line);
  }
  .toolbar-field { min-height: 0; width: 100%; }
  .toolbar-field select { min-width: 0; width: 100%; }
  /* Trigger tự vẽ (thay <select> gốc) cũng phải giãn full-width như trên khi thanh
     công cụ xếp dọc trên mobile — .select-custom-trigger vốn đã width:100% nhưng
     min-width:190px có thể ép nó rộng hơn khung cha trên màn rất hẹp (<190px cộng
     padding hai bên), y hệt lý do .toolbar-field select cũng phải gỡ min-width ở đây. */
  .select-custom-trigger { min-width: 0; }
  /* Tap target thoải mái hơn cho ngón tay trên mobile (~44px/dòng, gần chuẩn khuyến
     nghị của Apple HIG) thay vì 8px padding vốn nhắm tới việc bấm bằng chuột trên
     desktop — chỉ nới riêng phần dọc, giữ nguyên bo góc/màu sắc. */
  .select-custom-option { padding: 12px 14px; }
  .toolbar-field-price { width: 100%; }
  .toolbar-actions {
    flex-direction: column;
    align-items: center;
    width: 100%;
    margin-top: var(--space-4);
    padding-top: var(--space-4);
    border-top: 1px solid var(--line);
  }
  /* Nút "Lọc" đã có class .btn-fit-center (tự width:fit-content + margin auto,
     xem định nghĩa .btn-fit-center) nên không cần ép width ở đây nữa — để gọn
     & căn giữa giống hệt trên desktop thay vì kéo full-width như trước. */
  .toolbar-clear { text-align: center; }
  /* Chip "X sản phẩm" trên mobile: bỏ viền dọc/margin-left (chỉ có ý nghĩa khi đứng
     cạnh .toolbar-filters trên 1 hàng ngang), chuyển thành 1 dải ngang riêng đặt lên
     đầu, canh giữa nội dung. */
  .toolbar-meta {
    width: 100%;
    order: -1;
    justify-content: center;
    margin-left: 0;
    padding: var(--space-2) 0 var(--space-3);
    border-left: none;
    border-bottom: 1px solid var(--line);
    border-radius: 0;
    background: none;
  }
  /* Tay kéo to hơn trên mobile để dễ chạm đúng, vẫn giữ đúng thiết kế "khối tròn đặc" ở trên */
  .price-slider-body { height: 26px; margin-top: 4px; }
  .price-range { height: 26px; }
  .price-range::-webkit-slider-thumb { width: 24px; height: 24px; }
  .price-range::-moz-range-thumb { width: 24px; height: 24px; }
  /* Phân trang: thu gọn khoảng cách + kích thước ô số trên màn hình hẹp để "1 2 3 … Sau"
     không bị tràn dòng/ép chữ quá sát nhau. */
  .pagination { gap: 6px; }
  .pagination-page,
  .pagination-arrow,
  .pagination-ellipsis { min-width: 34px; height: 34px; padding: 0 8px; }
}

/* ---------- trust-signal (cam kết mua hàng) — bản đầy đủ dùng ở index.php ----------
   Thiết kế lại hoàn toàn (không còn là 1 khối thẻ trắng viền vuông chia 4 ô bằng
   đường kẻ dọc cứng như trước — nhìn khô, giống bảng thông số hơn là 1 dải "cam kết"
   đáng tin cậy). Hướng mới tham khảo tinh thần các trust-bar của Stripe/Apple/các
   trang thương mại lớn: KHÔNG đóng khung — để icon "thở" trên nền màu kem dịu, mỗi
   icon là 1 khối tròn màu nhạt (tint 12% của đúng màu thương hiệu) thay vì ô vuông
   gradient đặc, đường phân cách giữa các mục mờ dần 2 đầu (không kẻ liền từ trên
   xuống dưới) cho cảm giác nhẹ nhàng, sang hơn. Nền chuyển sắc từ trắng (nối tiếp
   khối sản phẩm nổi bật) xuống --paper, làm cầu nối mềm mại trước khi gặp dải
   thống kê gradient jade (.trust-strip) ngay bên dưới — 2 dải giờ đọc như 1 chuỗi
   chuyển động sáng → tối có chủ đích, thay vì 2 khối rời rạc ghép cạnh nhau. */
.trust-signals {
  background: linear-gradient(180deg, #fff 0%, var(--paper) 100%);
  padding: var(--space-7) 0 var(--space-6);
}
.trust-signals-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}
.trust-signal-item {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 15px;
  padding: 6px 26px;
}
.trust-signal-item + .trust-signal-item::before {
  content: '';
  position: absolute;
  left: 0; top: 4px; bottom: 4px;
  width: 1px;
  background: linear-gradient(180deg, transparent, var(--line) 25%, var(--line) 75%, transparent);
}
.trust-signal-icon {
  flex-shrink: 0;
  width: 48px; height: 48px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  transition: transform var(--dur-base) var(--ease-standard), box-shadow var(--dur-base) var(--ease-standard);
}
.trust-signal-item:nth-child(1) .trust-signal-icon { background: rgba(217, 102, 59, .12); color: var(--koi-orange); }
.trust-signal-item:nth-child(2) .trust-signal-icon { background: rgba(31, 111, 106, .12); color: var(--jade); }
.trust-signal-item:nth-child(3) .trust-signal-icon { background: rgba(201, 154, 58, .16); color: var(--gold); }
.trust-signal-item:nth-child(4) .trust-signal-icon { background: rgba(194, 59, 74, .12); color: var(--koi-red); }
.trust-signal-item:hover .trust-signal-icon,
.trust-signal-item:active .trust-signal-icon {
  transform: translateY(var(--lift-lg)) scale(1.06);
  box-shadow: var(--shadow-soft);
}
.trust-signal-item h4 { font-size: 14.5px; margin: 0 0 var(--space-1); transition: color var(--dur-base) var(--ease-standard); }
.trust-signal-item:hover:nth-child(1) h4,
.trust-signal-item:active:nth-child(1) h4 { color: var(--koi-orange); }
.trust-signal-item:hover:nth-child(2) h4,
.trust-signal-item:active:nth-child(2) h4 { color: var(--jade); }
.trust-signal-item:hover:nth-child(3) h4,
.trust-signal-item:active:nth-child(3) h4 { color: var(--gold-text); }
.trust-signal-item:hover:nth-child(4) h4,
.trust-signal-item:active:nth-child(4) h4 { color: var(--koi-red); }
.trust-signal-item p { font-size: var(--fs-xs); color: var(--text-muted); margin: 0; line-height: 1.5; }
/* Gộp mốc lẻ 860px -> 900px (mốc mobile chính của site). Chênh 40px, và đổi
   sớm hơn chỉ khiến lưới 4 cột xuống 2 cột sớm hơn một chút — luôn an toàn về
   mặt đọc. Xem "THANG BREAKPOINT" ở đầu file. */
@media (max-width: 900px) {
  .trust-signals-grid { grid-template-columns: 1fr 1fr; row-gap: var(--space-5); }
  .trust-signal-item:nth-child(2n)::before { display: none; }
  .trust-signal-item:nth-child(n+3)::before {
    left: 26px; right: 26px; top: -13px; bottom: auto; width: auto; height: 1px;
    background: linear-gradient(90deg, transparent, var(--line) 20%, var(--line) 80%, transparent);
  }
}
/* Gộp mốc lẻ 520px -> 640px. Xuống 1 cột sớm hơn (ở 640 thay vì 520) —
   4 ô trust signal ở 600px vốn đã khá chật, 1 cột dễ đọc hơn. */
@media (max-width: 640px) {
  .trust-signals { padding: var(--space-6) 0; }
  .trust-signals-grid { grid-template-columns: 1fr; row-gap: var(--space-4); }
  .trust-signal-item::before { display: none; }
  .trust-signal-item:nth-child(n+2)::before {
    display: block; left: 26px; right: 26px; top: -12px; bottom: auto; width: auto; height: 1px;
    background: linear-gradient(90deg, transparent, var(--line) 20%, var(--line) 80%, transparent);
  }
}

/* Biến thể gọn (danh sách dọc) — dùng trong cột thông tin sản phẩm ở product.php */
.trust-signals-compact { display: flex; flex-direction: column; gap: var(--space-3); margin-top: 22px; padding-top: 20px; border-top: 1px solid var(--line); }
.trust-signals-compact .trust-signal-item { border: none; padding: 0; background: transparent; align-items: center; }
/* Vạch ngăn dọc/ngang (::before) của .trust-signal-item chỉ dành cho lưới 4 cột ở index.php.
   Bản gọn xếp DỌC nên vạch này bị rò sang: đè lên mép trái vòng tròn icon của mục 2-4.
   :nth-child(n) nâng độ ưu tiên để thắng cả các rule ::before trong @media (max-width: 900px/640px). */
.trust-signals-compact .trust-signal-item:nth-child(n)::before { display: none; }
.trust-signals-compact .trust-signal-icon { width: 34px; height: 34px; }
.trust-signals-compact .trust-signal-icon svg { width: 17px; height: 17px; }
.trust-signals-compact .trust-signal-item h4 { font-size: var(--fs-sm); }
.trust-signals-compact .trust-signal-item p { font-size: var(--fs-xs); }

/* Biến thể gọn dùng trong footer (mọi trang, kể cả giỏ hàng/thanh toán) — nằm ở ĐẦU
   footer (dòng đầu tiên khách thấy khi cuộn tới footer, trước cả logo/4 cột).
   Thiết kế lại hoàn toàn: bỏ hẳn kiểu "1 dòng kẻ chia 4 ô" cũ (icon xám mờ gần như
   biến mất vào nền tối, đường kẻ dọc cứng, nhìn tạm bợ) — thay bằng 4 "viên" (pill)
   độc lập, mỗi viên tự có nền kính mờ (glass) + viền mảnh sáng dần khi hover, icon
   tròn nhỏ lên màu đúng theo bảng màu thương hiệu (không còn đơn sắc xám) để vừa
   nổi bật trên nền --ink vừa nhất quán với bản đầy đủ ở index.php. 1 đường kẻ ngang
   gradient rất mảnh chạy dọc theo 4 màu thương hiệu ở mép trên làm điểm nhấn nhỏ,
   tạo cảm giác đây là 1 khối được "thiết kế" chứ không phải phần liệt kê thô. Toàn
   bộ vẫn giữ 1 DÒNG NGANG, tự xuống dòng (wrap) tự nhiên trên mobile. */
.footer-trust-signals {
  position: relative;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 10px 8px;
  margin-top: 0;
  padding: 24px 0 22px;
  border-top: none;
  border-bottom: 1px solid rgba(247, 245, 239, .08);
}
.footer-trust-signals::before {
  content: '';
  position: absolute;
  top: 0; left: 8%; right: 8%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(217, 102, 59, .45), rgba(201, 154, 58, .45), rgba(31, 111, 106, .45), transparent);
}
.footer-trust-signals .trust-signal-item {
  flex: 0 0 auto;
  align-items: center;
  gap: 9px;
  padding: 9px 18px;
  border: 1px solid rgba(247, 245, 239, .1);
  border-radius: var(--radius-pill);
  background: rgba(247, 245, 239, .04);
  transition: background var(--dur-base) var(--ease-standard), border-color var(--dur-base) var(--ease-standard), transform var(--dur-base) var(--ease-standard);
}
@media (hover: hover) {
  .footer-trust-signals .trust-signal-item:hover {
    background: rgba(247, 245, 239, .09);
    border-color: rgba(247, 245, 239, .22);
    transform: translateY(-2px);
  }
}
/* :active TÁCH RIÊNG khỏi khối @media (hover: hover) ở trên — khối đó chỉ chạy
   trên thiết bị có hover THẬT (chuột), nên nếu để :active chung trong đó thì
   thiết bị cảm ứng (không khớp media query "hover: hover") sẽ KHÔNG BAO GIỜ
   nhận được phản hồi khi chạm — đúng vấn đề cần tránh. */
.footer-trust-signals .trust-signal-item:active {
  background: rgba(247, 245, 239, .09);
  border-color: rgba(247, 245, 239, .22);
  transform: translateY(-2px);
}
.footer-trust-signals .trust-signal-icon {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  box-shadow: none;
}
.footer-trust-signals .trust-signal-item:nth-child(1) .trust-signal-icon { background: rgba(224, 129, 82, .2); color: #e69169; }
.footer-trust-signals .trust-signal-item:nth-child(2) .trust-signal-icon { background: rgba(79, 179, 168, .2); color: #63c2b7; }
.footer-trust-signals .trust-signal-item:nth-child(3) .trust-signal-icon { background: rgba(217, 185, 104, .22); color: #e2c47c; }
.footer-trust-signals .trust-signal-item:nth-child(4) .trust-signal-icon { background: rgba(221, 118, 129, .2); color: #ea8f98; }
.footer-trust-signals .trust-signal-icon svg { width: 14px; height: 14px; }
.footer-trust-signals .trust-signal-item h4 {
  color: rgba(247, 245, 239, .92);
  font-size: 12.5px;
  font-weight: 600;
  margin: 0;
  text-transform: none;
  letter-spacing: normal;
  white-space: nowrap;
}
/* Khối trust-signal là LIÊN KẾT (<a>): bỏ gạch chân, giữ màu chữ, con trỏ tay. */
a.trust-signal-item { color: inherit; text-decoration: none; cursor: pointer; }
.footer-trust-signals .ts-short { display: none; }

/* Mobile/tablet (≤900px): bố cục B — 1 hàng 4 cột đều nhau, icon tròn ở trên, nhãn rút gọn ở
   dưới (tối đa 2 dòng, text-wrap:balance để không rớt 1 chữ lẻ). Thay cho kiểu "viên" tự xuống
   dòng cũ — các viên rộng khác nhau nên khi hẹp bị ngắt hàng lệch, nhìn xấu. Desktop giữ nguyên
   hàng viên nhãn đầy đủ. */
@media (max-width: 900px) {
  .footer-trust-signals {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 0;
    padding: 20px 0 18px;
  }
  .footer-trust-signals .trust-signal-item {
    flex-direction: column;
    justify-content: flex-start;
    align-items: center;
    gap: 8px;
    padding: 4px 4px;
    border: none;
    border-radius: var(--radius-sm);
    background: none;
    text-align: center;
  }
  .footer-trust-signals .trust-signal-item + .trust-signal-item {
    border-left: 1px solid rgba(247, 245, 239, .12);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  }
  /* Rule trên (đứng sau, cùng độ đặc hiệu) đè border-color của :hover/:active ở khối footer-trust-signals phía trên → khai lại. */
  .footer-trust-signals .trust-signal-item + .trust-signal-item:active { border-left-color: rgba(247, 245, 239, .22); }
  @media (hover: hover) {
    .footer-trust-signals .trust-signal-item + .trust-signal-item:hover { border-left-color: rgba(247, 245, 239, .22); }
  }
  .footer-trust-signals .trust-signal-icon { width: 34px; height: 34px; }
  .footer-trust-signals .trust-signal-icon svg { width: 18px; height: 18px; }
  .footer-trust-signals .trust-signal-item h4 {
    white-space: normal;
    font-size: 11.5px;
    line-height: 1.3;
    text-align: center;
    text-wrap: balance;
  }
  .footer-trust-signals .ts-full { display: none; }
  .footer-trust-signals .ts-short { display: inline; }
}
@media (max-width: 480px) {
  .footer-trust-signals::before { left: 5%; right: 5%; }
  .footer-trust-signals .trust-signal-item { padding: 4px 2px; }
  .footer-trust-signals .trust-signal-item h4 { font-size: 11px; }
}
/* ---------- .policy-content: prose cho các trang chính sách/điều khoản tĩnh
   (terms.php, privacy.php, return-policy.php, shipping-policy.php) ----------
   Dùng chung nền .pd-desc (đã có ở post.php) nhưng cần khôi phục bullet cho <ul>
   vì rule chung "ul { list-style:none }" ở đầu file đã tắt bullet mặc định — nội
   dung chính sách dạng liệt kê điều khoản cần bullet để dễ đọc. */
.policy-content h2 { font-size: 20px; margin-top: 1.4em; }
.policy-content h2:first-child { margin-top: 0; }
.policy-content ul { list-style: disc; padding-left: 20px; margin: 0 0 1em; }
.policy-content ul li { margin-bottom: 6px; }
.policy-content ol li { margin-bottom: 6px; }

/* ---------- dải thống kê thương hiệu (10+ năm kinh nghiệm, 5.000+ khách hàng...) ----------
   Nền jade gradient chiều xéo, 2 khối tròn mờ trang trí (blob) + 1 dải sóng mờ
   phía dưới gợi liên tưởng hồ nước, icon riêng cho từng mục, và đường kẻ dọc
   phân cách các mục. */
.trust-strip {
  position: relative;
  overflow: hidden;
  background: linear-gradient(120deg, var(--jade) 0%, #2c4d43 65%, #24463c 100%);
  color: var(--paper);
  isolation: isolate;
}
.trust-strip-blob {
  position: absolute;
  border-radius: 50%;
  z-index: -1;
  filter: blur(2px);
}
.trust-strip-blob-1 {
  width: 320px; height: 320px;
  background: radial-gradient(circle, rgba(217, 102, 59, .16), transparent 70%);
  top: -170px; right: -60px;
}
.trust-strip-blob-2 {
  width: 260px; height: 260px;
  background: radial-gradient(circle, rgba(201, 154, 58, .16), transparent 70%);
  bottom: -160px; left: 6%;
}
.trust-strip-waves { position: absolute; left: 0; right: 0; bottom: 0; width: 100%; height: 46px; z-index: -1; }

.trust-strip-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  padding: 42px var(--space-5);
}
.trust-item {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 4px var(--space-5);
  text-align: left;
}
.trust-item + .trust-item { border-left: 1px solid rgba(255, 255, 255, .16); }
.trust-item-icon {
  flex-shrink: 0;
  width: 46px; height: 46px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .12);
  border: 1px solid rgba(255, 255, 255, .2);
  display: flex; align-items: center; justify-content: center;
  color: #fff;
}
.trust-item .num { font-family: var(--font-display); font-size: 28px; font-weight: 700; color: #fff; line-height: 1.1; }
.trust-item .label { font-size: var(--fs-sm); opacity: .82; margin-top: 3px; }
/* Gộp mốc lẻ 860px -> 900px — cùng lý do với .trust-signals-grid ở trên. */
@media (max-width: 900px) {
  .trust-strip-grid { grid-template-columns: 1fr 1fr; gap: 22px 0; padding: 30px var(--space-5); }
  .trust-item { padding: 0 var(--space-3); }
  .trust-item + .trust-item { border-left: none; }
  .trust-item:nth-child(odd) { border-right: 1px solid rgba(255, 255, 255, .16); }
  .trust-item:nth-child(n+3) { border-top: 1px solid rgba(255, 255, 255, .16); padding-top: 18px; }
}
@media (max-width: 480px) {
  .trust-item { gap: 10px; }
  .trust-item-icon { width: 38px; height: 38px; }
  .trust-item .num { font-size: 22px; }
}

/* ---------- posts / blog ---------- */
.post-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  justify-items: start; /* xem giải thích ở .cat-grid phía trên */
  gap: var(--space-5);
}
.post-card {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  width: 100%;
  max-width: 380px; /* chặn giãn to khi hàng không đủ item lấp đầy (xem justify-items ở .post-grid) */
  transition: box-shadow var(--dur-base) var(--ease-standard), transform var(--dur-base) var(--ease-standard);
}
.post-card:hover,
.post-card:active { box-shadow: var(--shadow); transform: translateY(var(--lift-lg)); }
.post-thumb { aspect-ratio: 16/10; background: var(--paper-deep); overflow: hidden; position: relative; }
.post-thumb img { width: 100%; height: 100%; object-fit: cover; }
.post-body { padding: 18px 18px 20px; }
.post-body h3 { font-size: 16.5px; margin-bottom: var(--space-2); }
.post-body h3 a:hover,
.post-body h3 a:active { color: var(--koi-orange); }
.post-excerpt { font-size: var(--fs-sm); color: var(--text-muted); }

/* =========================== CONTACT / MAP =========================== */
.contact-wrap {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 34px;
  align-items: start;
}
/* Biến thể cho các trang mà 1 cột chỉ chứa 1 form NGẮN (contact.php: 3 field; account.php:
   thông tin cá nhân + đổi mật khẩu) đặt cạnh nội dung "nặng" hơn (bản đồ / lịch sử đơn hàng).
   Chia đúng 50/50 như .contact-wrap gốc khiến form bị kéo giãn hết cỡ 1 nửa trang (700px+ trên
   desktop rộng) dù nội dung chỉ vài dòng ngắn — bó cột form về khoảng 420-460px, phần còn lại
   nhường hẳn cho nội dung bên cạnh, để input/nút bấm không còn to/dài bất thường. */
/* align-items:start (không phải stretch): bản đồ có chiều cao cố định/hợp lý riêng
   của nó (xem .map-frame bên dưới), không phụ thuộc độ dài cột form bên cạnh. Đồng
   thời thu hẹp giới hạn chiều rộng cột bản đồ để tỉ lệ 2 cột (form/bản đồ) cân đối
   hơn, không để cột bản đồ áp đảo quá nhiều diện tích so với cột form. */
.contact-wrap--split { grid-template-columns: minmax(280px, 420px) minmax(320px, 560px); align-items: start; }
.contact-card {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 28px;
}
.info-row { display: flex; gap: var(--space-3); margin-bottom: var(--space-4); align-items: flex-start; }
.info-row .ic { color: var(--koi-orange); flex-shrink: 0; margin-top: 2px; }
.info-row strong { display: block; font-size: var(--fs-sm); }
.info-row span { font-size: var(--fs-sm); color: var(--text-muted); }
/* .map-frame: chiều cao CỐ ĐỊNH trong 1 khoảng hợp lý, không còn ăn theo chiều
   cao cột form bên cạnh (form dài hay ngắn, bản đồ vẫn giữ đúng khoảng cao này).
   clamp() co giãn nhẹ theo bề rộng khung nhìn nhưng luôn có chặn trên/dưới rõ
   ràng (320–420px là đủ cho 1 bản đồ minh hoạ địa chỉ cửa hàng, không cần to
   hơn) — xem thêm override cho mobile ở khối @media max-width:900px bên dưới. */
.map-frame { width: 100%; height: clamp(320px, 30vw, 420px); border: 0; border-radius: var(--radius); }
/* .map-col: khối bọc ngoài của bản đồ trong .contact-wrap--split. Không còn cần
   height:100% (đã bỏ align-items:stretch ở trên) — chiều cao thực tế do chính
   .map-frame quyết định qua clamp() phía trên; <a> chỉ cần display:block để bọc
   khít quanh iframe, không kéo giãn theo cột form bên cạnh nữa. */
.map-col a { position: relative; display: block; }
.map-frame { transition: opacity var(--dur-fast) var(--ease-standard); }
@media (hover: hover) {
  .map-col a:hover .map-frame { opacity: .92; }
}
.map-col a:active .map-frame { opacity: .8; }

/* Các nút "hành động chính" bên trong .contact-card (Gửi thông tin, Đăng
   nhập, Đăng ký, Gửi link đặt lại mật khẩu, Đặt lại mật khẩu, Tra cứu đơn
   hàng, Huỷ đơn hàng, Về danh sách đơn hàng...) dùng class .btn-fit-center
   (thay vì .btn-block) ngay trong HTML — class này tự gọn + tự căn giữa ở mọi
   kích thước màn hình (xem định nghĩa .btn-fit-center bên dưới), không cần
   luật riêng theo breakpoint ở đây. .btn-block vẫn còn dùng cho 2 trường hợp
   cố ý giữ full-width mọi lúc: nút "Xem giỏ hàng & Thanh toán" ở sidebar giỏ
   hàng (includes/header.php) và nút "Đặt hàng" ở cart.php — cả 2 đều nằm
   trong khung hẹp/sidebar riêng, không nằm trong .contact-card. */

/* ---------- Dòng thời gian đơn hàng (order_detail.php) ---------- */
.order-timeline { list-style: none; margin: 0; padding: 0; }
.order-timeline-item { position: relative; padding-left: 26px; padding-bottom: 18px; }
.order-timeline-item:last-child { padding-bottom: 0; }
.order-timeline-item::before {
  content: ''; position: absolute; left: 5px; top: 16px; bottom: -2px; width: 2px; background: var(--line);
}
.order-timeline-item:last-child::before { display: none; }
.order-timeline-dot {
  position: absolute; left: 0; top: 2px; width: 12px; height: 12px; border-radius: 50%;
  background: var(--jade); border: 2px solid var(--jade); box-sizing: border-box;
}
.order-timeline-item-current .order-timeline-dot { background: #fff; box-shadow: 0 0 0 3px rgba(31,111,106,.2); }
.order-timeline-item-cancel .order-timeline-dot { background: var(--koi-red); border-color: var(--koi-red); }
.order-timeline-item-cancel.order-timeline-item-current .order-timeline-dot { background: #fff; box-shadow: 0 0 0 3px rgba(194,59,74,.2); }
.order-timeline-label { font-weight: 700; font-size: var(--fs-sm); color: var(--ink); }
.order-timeline-item-current .order-timeline-label { color: var(--jade); }
.order-timeline-item-cancel .order-timeline-label { color: var(--koi-red); }
.order-timeline-time { font-size: var(--fs-xs); color: var(--text-muted); margin-top: 2px; }

/* ---------- Thanh tiến trình vận chuyển kiểu Shopee ----------
   Dùng chung bộ class .order-timeline* ở trên, chỉ thêm modifier cho 3 state
   riêng của TỪNG bước (done/current/pending) — renderShipmentProgress() ở
   includes/functions.php. "done" có dấu tích trong chấm tròn (thay vì chấm
   đặc như .order-timeline thường), "pending" mờ hẳn (chưa tới). */
.order-timeline--progress .order-timeline-item-done .order-timeline-dot {
  background: var(--jade); border-color: var(--jade);
  display: flex; align-items: center; justify-content: center;
}
.order-timeline--progress .order-timeline-item-done .order-timeline-dot::after {
  content: '✓'; color: #fff; font-size: 9px; font-weight: 700; line-height: 1;
}
.order-timeline--progress .order-timeline-item-pending { opacity: .45; }
.order-timeline--progress .order-timeline-item-pending .order-timeline-dot { background: #fff; border-color: var(--line); }
.order-timeline--progress .order-timeline-item-pending .order-timeline-label { font-weight: 600; }
.order-timeline--progress .order-timeline-item-pending .order-timeline-time::before { content: 'Chưa tới'; }

form .field { margin-bottom: var(--space-4); }
form label { display: block; font-size: var(--fs-sm); font-weight: 700; margin-bottom: 6px; }
form input, form textarea, form select {
  width: 100%;
  padding: var(--space-3) 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  background: #fdfbf7;
  transition: border-color var(--dur-fast) var(--ease-standard);
}
form input:focus, form textarea:focus, form select:focus { outline: none; border-color: var(--koi-orange); }
/* Rê chuột vào ô nhập: viền ngả cam nhạt báo ô gõ được; ô đang focus/khoá giữ nguyên. */
@media (hover: hover) {
  form input:where(:not([type]), [type="text"], [type="email"], [type="tel"], [type="password"], [type="number"], [type="search"], [type="url"]):hover:where(:not(:focus):not(:disabled)),
  form textarea:hover:where(:not(:focus):not(:disabled)) { border-color: rgba(217,102,59,.5); }
}

/* ---------- .field-row: ghép 2 field ngắn (VD "Họ tên" + "SĐT") nằm CHUNG 1 hàng ----------
   Nếu xếp dọc full-width bất kể nội dung ngắn/dài, trong cột form rộng (contact-wrap)
   1 ô nhập chỉ vài ký tự (VD số điện thoại) sẽ bị kéo dài hết cỡ cột, trông mất cân
   đối. Ghép 2 field ngắn cùng 1 hàng giải quyết đúng vấn đề này mà không cần thu hẹp
   cả cột — cũng khiến form ngắn gọn hơn, đỡ phải cuộn. */
.field-row { display: flex; gap: var(--space-4); }
.field-row .field { flex: 1; min-width: 0; }
/* Gộp mốc lẻ 560px -> 640px: 2 field ngắn xếp dọc sớm hơn 80px. Xếp dọc luôn
   là hướng an toàn (không bao giờ làm ô nhập chật hơn). */
@media (max-width: 640px) { .field-row { flex-direction: column; gap: 0; } }

/* ---------- .field-invalid: tô đỏ đúng ô bị lỗi khi submit checkout thất bại ----------
   Trước đây chỉ có 1 banner .error-msg chung ở đầu form — khách phải tự dò cả form để
   tìm ô sai. Server gắn class này lên đúng .field bị lỗi (xem $fieldErrors trong
   cart.php), main.js không cần đổi vì đây là style thuần CSS; JS trong cart.php chỉ lo
   phần cuộn/focus tới ô đầu tiên bị lỗi. */
.field-invalid > label { color: var(--koi-red); }
/* :not([type="radio"]):not([type="checkbox"]) — nhóm "Hình thức thanh toán" cũng dùng
   .field-invalid nhưng chứa input[type=radio] gốc (không phải input viền/nền như text),
   border-color/background không có ý nghĩa trên radio nên loại trừ để tránh render lạ. */
.field-invalid input:not([type="radio"]):not([type="checkbox"]), .field-invalid textarea, .field-invalid select {
  border-color: var(--koi-red);
  background: var(--koi-red-tint);
}
.field-invalid input:not([type="radio"]):not([type="checkbox"]):focus, .field-invalid textarea:focus, .field-invalid select:focus {
  border-color: var(--koi-red);
}
/* Checkbox "Ghi nhớ đăng nhập" ở login.php: tái sử dụng token màu/spacing sẵn có
   (--text-muted, --space-2, --fs-sm), bỏ margin-bottom mặc định của <label> vì đây là
   checkbox nằm ngang, không phải label phía trên 1 ô input như các field khác. */
.field-checkbox { display: flex; align-items: center; }
.field-checkbox .checkbox-label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-sm);
  font-weight: 400;
  color: var(--text-muted);
  margin-bottom: 0;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-standard);
}
.field-checkbox .checkbox-label:hover,
.field-checkbox .checkbox-label:active { color: var(--ink); }
.field-checkbox .checkbox-label input[type="checkbox"] {
  width: 16px;
  height: 16px;
  accent-color: var(--koi-orange);
  cursor: pointer;
}
.alert-success {
  background: var(--jade-tint);
  border: 1px solid var(--jade);
  color: var(--jade);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  margin-bottom: 18px;
  font-weight: 600;
}
/* Flash lỗi toàn trang (includes/header.php, $_SESSION['flash_error']) — cùng cấu
   trúc với .alert-success ở trên, chỉ đổi màu sang đỏ giống .error-msg (dùng chung
   biến --koi-red/--koi-red-tint để đồng bộ "màu lỗi" trong toàn site). */
.alert-error {
  background: var(--koi-red-tint);
  border: 1px solid var(--koi-red);
  color: var(--koi-red);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  margin-bottom: 18px;
  font-weight: 600;
}
/* Cảnh báo mềm (không phải lỗi, không phải thành công) — dùng --koi-orange để phân
   biệt rõ với .error-msg (đỏ) và .alert-success (xanh jade). VD: gợi ý SĐT trùng
   tài khoản khác lúc đăng ký (register.php) — chỉ mang tính thông báo, không chặn
   thao tác của khách. */
.alert-notice {
  background: #fbf1ea;
  border: 1px solid var(--koi-orange);
  color: var(--koi-orange);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  margin-bottom: 18px;
  font-weight: 600;
}
.alert-notice a { color: inherit; text-decoration: underline; }
.alert-notice a:hover,
.alert-notice a:active { opacity: .75; }

/* ---------- Hiệu ứng xuất hiện của thông báo (.alert-*, .error-msg) ----------
   Các khối này do PHP render sẵn lúc tải trang (không có JS tạo/bật tắt), nên dùng `animation`
   chạy 1 lần lúc tải (không dùng `transition`, vì phần tử mới đã ở trạng thái cuối).
   - .alert-success/.alert-error/.alert-notice: fade + trượt xuống nhẹ 4px (@keyframes msg-enter).
   - .error-msg: CHỈ fade (@keyframes msg-fade). Khi trang tải lại kèm .error-msg thì main.js
     (initFormErrorShake) đang lắc <form> chứa nó cùng lúc; thêm chuyển động dọc nữa sẽ rối.
   Không có fill-mode: hết animation thì phần tử về đúng style gốc (không giữ transform thừa,
   tránh tạo stacking context vĩnh viễn).
   prefers-reduced-motion: là animation trang trí (nội dung vẫn hiện đủ) nên tắt hẳn — rule chung
   `*:not(.category-sheet)...{animation:none !important}` ở khối @media cuối file đã tự phủ. */
@keyframes msg-enter {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes msg-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.alert-success,
.alert-error,
.alert-notice { animation: msg-enter var(--dur-slow) var(--ease-enter); }
/* CHỐNG CHẠY LẠI: animation CSS chạy lại mỗi khi phần tử đi từ display:none sang hiện. Mọi thông
   báo nằm trong khối ẩn/hiện bằng .js-fade-toggle (VD các .account-panel của trang tài khoản, mỗi
   lần khách đổi tab) sẽ bị phát lại hiệu ứng nếu không tắt ở đây. Specificity (0,2,0) cao hơn rule
   gốc nên thắng bất kể thứ tự khai báo (.error-msg khai báo ở phần cuối file). */
.js-fade-toggle .alert-success,
.js-fade-toggle .alert-error,
.js-fade-toggle .alert-notice,
.js-fade-toggle .error-msg { animation: none; }

/* =========================== FOOTER =========================== */
/* Dòng đầu tiên của footer là dải trust-signals gọn (padding rất mỏng, xem
   .footer-trust-signals) nên margin-top chỉ cần 24px, không cần khoảng đệm
   lớn như khi dòng đầu là logo/4 cột (.footer-grid). */
.site-footer { background: var(--ink); color: rgba(247,245,239,.85); margin-top: 24px; }
.footer-grid {
  display: grid;
  grid-template-columns: 1.3fr .85fr .85fr .85fr 1fr;
  gap: var(--space-6);
  padding-top: 56px;
  padding-bottom: var(--space-6);
}
.footer-grid h4 { color: #fff; font-size: 14.5px; margin-bottom: var(--space-4); font-family: var(--font-body); text-transform: uppercase; letter-spacing: .06em; }
.footer-grid ul li { margin-bottom: 9px; font-size: var(--fs-sm); }
.footer-grid ul li a:hover,
.footer-grid ul li a:active { color: var(--koi-orange); }
.footer-brand p { font-size: var(--fs-sm); color: rgba(247,245,239,.65); max-width: 34ch; }
.footer-bottom {
  border-top: 1px solid rgba(247,245,239,.12);
  padding-top: 18px;
  padding-bottom: 18px;
  font-size: var(--fs-xs);
  color: rgba(247,245,239,.55);
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: var(--space-2);
}
.social-row { display: flex; gap: 10px; margin-top: 14px; }
.social-row a {
  width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  overflow: hidden;
  background: #fff;
  box-shadow: 0 2px 8px -2px rgba(0,0,0,.25);
  transition: transform var(--dur-fast) var(--ease-standard);
}
.social-row a img { width: 100%; height: 100%; object-fit: cover; }
.social-row a:hover,
.social-row a:active { transform: translateY(var(--lift-sm)); }

/* =========================== FOOTER — MINI HỒ CÁ =========================== */
/* Dải trang trí lấp đúng vào phần padding-bottom mà .site-footer chừa ra trên
   mobile (xem "THANH TRẮNG Ở ĐÁY TRANG" + rule .site-footer trong @media
   max-width:900px bên dưới) để tab bar không đè chữ. Mặc định ẩn (desktop/tablet
   không có khoảng đệm đó nên không cần); được bật + định vị lại đúng bằng
   chiều cao khoảng đệm trong @media max-width:900px. Toàn bộ ở đây chỉ là màu
   sắc + animation, không ảnh hưởng layout/kích thước gì cả nên để ngoài media
   query cho gọn.

   ANIMATION TRANG TRÍ (ngoại lệ có chủ đích, xem nguyên tắc 7 hệ thống motion
   ở :root) — toàn bộ khối "hồ cá mini" bên dưới (sóng, rong rêu, san hô, cá
   bơi, bong bóng) là animation TRANG TRÍ thuần tuý, lặp vô hạn, không phản hồi
   cho bất kỳ thao tác nào của khách — nên KHÔNG ép về thang --dur-*, --ease-*
   (vốn dành cho phản hồi tương tác có mục đích). Timing hiện tại (4.4s-22s,
   nhiều animation-delay âm để lệch pha) đã được tinh chỉnh riêng cho cảm giác
   "bơi lượn tự nhiên, không đồng bộ giả tạo" — giữ nguyên. */
.footer-aquarium { display: none; }
.aq-scene { position: absolute; left: 0; right: 0; bottom: 0; overflow: hidden; }

.aq-waves {
  position: absolute;
  top: 0; left: 0;
  width: 200%;
  height: 14px;
  animation: aq-wave-scroll 9s linear infinite;
}
.aq-wave-1 { fill: rgba(31, 111, 106, .55); }
.aq-wave-2 { fill: rgba(31, 111, 106, .3); }
@keyframes aq-wave-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

.aq-algae { position: absolute; bottom: 0; width: 10px; height: 26px; transform-origin: bottom center; }
.aq-algae-1 { left: 8%;  animation: aq-sway 5.2s ease-in-out infinite; }
.aq-algae-2 { left: 14%; height: 20px; animation: aq-sway 4.4s ease-in-out infinite -1.6s; }
.aq-algae-3 { left: 91%; animation: aq-sway 6s ease-in-out infinite -2.8s; }
@keyframes aq-sway {
  0%, 100% { transform: rotate(-7deg); }
  50%      { transform: rotate(7deg); }
}

.aq-coral { position: absolute; bottom: 0; width: 26px; height: 20px; transform-origin: bottom center; }
.aq-coral-1 { left: 2%;  animation: aq-sway-soft 6.5s ease-in-out infinite; }
.aq-coral-2 { left: 80%; width: 22px; height: 17px; animation: aq-sway-soft 5.6s ease-in-out infinite -2.2s; }
@keyframes aq-sway-soft {
  0%, 100% { transform: rotate(-2.5deg); }
  50%      { transform: rotate(2.5deg); }
}

.aq-fish { position: absolute; width: 32px; height: 16px; }
.aq-fish-body { fill: var(--koi-orange); }
.aq-fish-tail { fill: var(--koi-orange); opacity: .85; }
.aq-fish-fin  { fill: var(--koi-orange); opacity: .6; }
.aq-fish-eye  { fill: var(--ink); }
.aq-fish-1 { top: 58%; animation: aq-swim-right 15s linear infinite; }
.aq-fish-2 {
  top: 26%; width: 26px; height: 13px;
  animation: aq-swim-left 19s linear infinite -6s;
}
.aq-fish-2 .aq-fish-body, .aq-fish-2 .aq-fish-tail, .aq-fish-2 .aq-fish-fin { fill: var(--jade); }
.aq-fish-3 {
  top: 74%; width: 22px; height: 11px; opacity: .85;
  animation: aq-swim-right 22s linear infinite -10s;
}
@keyframes aq-swim-right {
  0%   { left: -14%; transform: translateY(0)    scaleX(1); }
  25%  {              transform: translateY(-4px) scaleX(1); }
  50%  { left: 55%;  transform: translateY(2px)   scaleX(1); }
  75%  {              transform: translateY(-3px) scaleX(1); }
  100% { left: 112%; transform: translateY(0)     scaleX(1); }
}
@keyframes aq-swim-left {
  0%   { left: 112%; transform: translateY(0)    scaleX(-1); }
  25%  {              transform: translateY(3px)  scaleX(-1); }
  50%  { left: 45%;  transform: translateY(-3px) scaleX(-1); }
  75%  {              transform: translateY(2px)  scaleX(-1); }
  100% { left: -14%; transform: translateY(0)     scaleX(-1); }
}

.aq-bubble {
  position: absolute;
  bottom: 6px;
  width: 4px; height: 4px;
  border-radius: 50%;
  background: rgba(247, 245, 239, .55);
  box-shadow: 0 0 3px rgba(247, 245, 239, .45);
  animation: aq-bubble-rise 5.5s ease-in infinite;
}
.aq-bubble-1 { left: 12%; animation-duration: 5.2s; animation-delay: 0s; }
.aq-bubble-2 { left: 32%; width: 5px; height: 5px; animation-duration: 6.6s; animation-delay: -2s; }
.aq-bubble-3 { left: 52%; width: 3px; height: 3px; animation-duration: 4.6s; animation-delay: -1s; }
.aq-bubble-4 { left: 71%; animation-duration: 6.1s; animation-delay: -3.4s; }
.aq-bubble-5 { left: 89%; width: 3px; height: 3px; animation-duration: 5s;   animation-delay: -4s; }
@keyframes aq-bubble-rise {
  0%   { transform: translate(0, 0);      opacity: 0; }
  12%  {                                   opacity: .75; }
  55%  { transform: translate(3px, -22px); opacity: .5; }
  100% { transform: translate(-2px, -42px); opacity: 0; }
}

/* Tối ưu hiệu năng: toàn bộ animation trang trí ở trên (sóng/rong rêu/san hô/cá bơi/
   bong bóng) chạy `infinite` vô điều kiện dù footer có đang hiển thị trong viewport
   hay không — tốn CPU/GPU không cần thiết mỗi khi khách đang ở đầu trang, cuộn dài
   mà chưa chạm tới footer. initFooterAquariumPause() (assets/js/main.js) dùng
   IntersectionObserver — tái dùng đúng kỹ thuật đã có ở initScrollReveal() — để
   thêm/gỡ class ".aq-paused" trên .footer-aquarium khi footer ra/vào viewport.
   Chỉ điều khiển animation-play-state, không đổi/xoá bất kỳ @keyframes hay giá trị
   timing nào ở trên (xem comment "ANIMATION TRANG TRÍ" đầu mục FOOTER — MINI HỒ CÁ).
   Dưới prefers-reduced-motion: reduce, rule chung
   "*:not(.category-sheet) { animation: none !important; }" (cuối file) đã tắt
   hẳn các animation này bằng !important — thắng tuyệt đối bất kể .aq-paused có
   mặt hay không trên phần tử, nên không cần khai báo thêm rule reduced-motion
   riêng cho class này. */
.footer-aquarium.aq-paused .aq-waves,
.footer-aquarium.aq-paused .aq-algae,
.footer-aquarium.aq-paused .aq-coral,
.footer-aquarium.aq-paused .aq-fish,
.footer-aquarium.aq-paused .aq-bubble {
  animation-play-state: paused;
}

.floating-social {
  position: fixed;
  right: 22px;
  bottom: 22px;
  /* Trước là z-index:60 — trùng đúng lớp của sidebar mobile off-canvas (60) và CAO
     HƠN lớp phủ tối .nav-overlay (55), nên trên màn hẹp 2 nút này có thể lộ ra đè
     lên trên overlay/mép sidebar khi menu đang mở. Hạ xuống dưới .nav-overlay để
     luôn bị che khuất đúng cách khi menu mobile mở, vẫn nổi trên nội dung trang bình thường. */
  z-index: calc(var(--z-sticky-header) + 2);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  /* "bottom" đổi giá trị khi thanh tab bar mobile tự ẩn/hiện theo cuộn (xem
     .fs-lowered ngay dưới, toggle bởi main.js) — bắt transition riêng đúng
     thuộc tính này, cùng thời lượng var(--dur-slow) với .mobile-bottom-nav để 2
     khối trượt lên/xuống "ăn khớp" nhau, không tạo cảm giác khoảng trống lơ
     lửng khi tab bar biến mất rồi mới thấy nút nổi nhích theo sau — cả 2 đều
     dùng chung var(--dur-slow)/var(--ease-standard) (xem hệ thống motion ở
     :root), KHÔNG được đổi lệch nhau. */
  transition: bottom var(--dur-slow) var(--ease-standard);
}
.float-btn {
  width: 54px; height: 54px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  overflow: hidden;
  background: #fff;
  box-shadow: 0 12px 26px -8px rgba(18,34,44,.4);
  transition: transform var(--dur-fast) var(--ease-standard);
  /* Ép trình duyệt tạo layer GPU riêng NGAY TỪ ĐẦU (thay vì chỉ tạo lúc hover
     do "transition: transform" phía trên) — nếu không, việc bật/tắt layer
     giữa 2 trạng thái (bình thường/hover) đôi khi khiến Chrome/Edge desktop
     dựng lại viền cắt tròn (overflow:hidden + border-radius) ở độ nét khác
     nhau, tạo cảm giác viền bị "vỡ"/răng cưa nhẹ lúc đứng yên. */
  transform: translateZ(0);
  backface-visibility: hidden;
}
.float-btn img {
  width: 100%; height: 100%; object-fit: cover;
  /* Bo tròn thêm ở chính thẻ <img> (không chỉ dựa vào overflow:hidden của
     .float-btn) — 2 lớp bo tròn trùng khít giúp trình duyệt raster viền tròn
     mượt hơn hẳn so với chỉ cắt bằng 1 lớp overflow:hidden bên ngoài, vốn dễ
     bị răng cưa ở kích thước nhỏ trên desktop. */
  border-radius: 50%;
  display: block;
}
.float-btn:hover,
.float-btn:active { transform: scale(1.08); }
.float-top {
  background: var(--ink);
  border-radius: 50%;
  opacity: 0;
  transform: translateY(10px) scale(.85);
  pointer-events: none;
  transition: opacity var(--dur-slow) var(--ease-standard), transform var(--dur-slow) var(--ease-standard);
}
.float-top.show { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
/* .float-top.show (ở trên) cùng độ đặc hiệu với .float-btn:hover/:active và đứng sau nên ghi đè scale(1.08) —
   khai lại hover/active cho trạng thái đang hiện để nút vẫn phóng to khi rê/nhấn. */
.float-top.show:hover,
.float-top.show:active { transform: translateY(0) scale(1.08); }

/* =========================== BREADCRUMB / PAGE HEAD =========================== */
.page-head {
  background: var(--paper-deep);
  border-bottom: 1px solid var(--line);
  padding: 26px 0;
}
.breadcrumb { font-size: var(--fs-sm); color: var(--text-muted); }
.breadcrumb a:hover,
.breadcrumb a:active { color: var(--koi-orange); }
.page-head h1 { font-size: 26px; margin: 6px 0 0; }

/* =========================== PRODUCT DETAIL =========================== */
.product-detail {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: 44px;
}
/* min-width:0 cho mọi cột của lưới: mặc định phần tử lưới có min-width:auto nên 1 chuỗi dài
   không dấu cách (mã SKU trong tên) sẽ đẩy cả cột — và cả trang — rộng ra, gây cuộn ngang. */
.product-detail > * { min-width: 0; }
.pd-gallery {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  aspect-ratio: 1/1;
}
/* Crossfade khi đổi ảnh chính qua thumbnail (xem main.js, đoạn "Gallery ảnh
   sản phẩm"). 3 trạng thái, đều dùng chung 1 thuộc tính opacity, không transform/scale
   (chỉ là thay ảnh tĩnh, không phải mở/đóng panel):
   - Mặc định: opacity 1, transition dùng --dur-base/--ease-standard (tương tác 2 chiều,
     dùng chung cho cả lúc fade ra lẫn fade vào của ảnh mới).
   - .pd-gallery-loading: dim nhẹ (không mờ hẳn) ngay khi vừa bấm, trong lúc còn chờ ảnh
     mới tải xong — báo hiệu đã nhận thao tác bấm cho trường hợp mạng chậm. Dùng
     --dur-fast vì đây là phản hồi "đã nhận bấm", cần nhanh hơn phần fade chính.
   - .pd-gallery-hidden: mờ hẳn về 0 — trạng thái trung gian ngay trước khi đổi src (ảnh
     cũ đã fade khuất hẳn) và ngay sau khi vừa đổi src (ảnh mới bắt đầu từ 0 để fade vào).
   Class được main.js thêm/gỡ trực tiếp, không dùng keyframes (crossfade 1 chiều opacity
   không cần animation phức tạp hơn transition thường). */
.pd-gallery img {
  width: 100%; height: 100%; object-fit: cover;
  opacity: 1;
  transition: opacity var(--dur-base) var(--ease-standard);
}
.pd-gallery img.pd-gallery-loading { opacity: .55; transition: opacity var(--dur-fast) var(--ease-standard); }
.pd-gallery img.pd-gallery-hidden { opacity: 0; }

/* CROSSFADE khi bấm thumbnail — GIỐNG hero trang chủ (.hero-bg-carousel .hero-carousel-img):
   2 ảnh xếp chồng, ảnh cũ (.pd-gallery-fade-out) mờ 1 -> 0 ĐỒNG THỜI ảnh mới (.pd-gallery-fade-ghost
   + .is-in) hiện 0 -> 1, 1.08s (nhanh hơn hero 1.3s đúng 20%) + --ease-standard (ngoại lệ có chủ đích khỏi thang
   --dur-*, xem ANIMATION_GUIDE mục 8). Thời lượng này phải khớp hằng PD_FADE_MS trong main.js.
   Khung .pd-gallery đã có position:relative + overflow:hidden. prefers-reduced-motion: JS bỏ qua
   hẳn nhánh này (đổi ảnh ngay), rule chung cuối file cũng tắt transition. */
.pd-gallery img.pd-gallery-fade-out { opacity: 0; transition: opacity 1.08s var(--ease-standard); }
.pd-gallery img.pd-gallery-fade-ghost {
  position: absolute; inset: 0;
  opacity: 0;
  pointer-events: none;
  -webkit-user-drag: none;
  transition: opacity 1.08s var(--ease-standard);
}
.pd-gallery img.pd-gallery-fade-ghost.is-in { opacity: 1; }

/* Nút mũi tên trước/sau của gallery — CÙNG kiểu với .hero-arrow của hero trang chủ (tròn 44px, nền
   tối mờ + blur, hover/active chuyển cam; 38px trên mobile). Nằm TRONG .pd-gallery (position:relative +
   overflow:hidden) nên không dính bẫy stacking-context như hero; z-index:5 để nổi trên ảnh chính và ảnh
   ma của crossfade/vuốt. JS (main.js) bấm thumbnail kế bên (vòng lặp) nên dùng đúng luồng crossfade;
   attachSwipe của gallery có ignore('.pd-gallery-arrow') để bấm nút không bị tính là vuốt. */
.pd-gallery-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 5;
  width: 44px; height: 44px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid rgba(247,245,239,.35);
  background: rgba(18,34,44,.55);
  color: var(--paper);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  backdrop-filter: blur(4px);
  transition: background var(--dur-fast) var(--ease-standard), transform var(--dur-fast) var(--ease-standard);
}
/* Hover/active: cùng quy tắc và lý do với .hero-arrow (xem ghi chú ở đó). */
.pd-gallery-arrow:active { background: var(--koi-orange); border-color: var(--koi-orange); }
@media (hover: hover) {
  .pd-gallery-arrow:hover:not(.is-touched) { background: var(--koi-orange); border-color: var(--koi-orange); }
}
.pd-gallery-arrow-prev { left: 14px; }
.pd-gallery-arrow-next { right: 14px; }
@media (max-width: 900px) {
  .pd-gallery-arrow { width: 38px; height: 38px; }
  .pd-gallery-arrow-prev { left: 10px; }
  .pd-gallery-arrow-next { right: 10px; }
}

/* Vuốt ngang ảnh chính (main.js, đoạn "Gallery ảnh sản phẩm: vuốt ngang"). JS chỉ thêm
   .pd-gallery-swipeable khi có >= 2 ảnh và trình duyệt có Pointer Events.
   - touch-action: pan-y pinch-zoom -> trình duyệt vẫn cuộn dọc + zoom 2 ngón như thường, chỉ giao
     phần vuốt NGANG cho JS (KHÔNG dùng "pan-y" trơn: nó tắt luôn pinch-zoom).
   - position:relative làm khung định vị cho ảnh "ma" (.pd-gallery-ghost) trượt vào từ mép; khung đã
     có overflow:hidden nên ảnh trượt ra ngoài bị cắt gọn.
   - .is-dragging: tắt transition opacity của crossfade khi đang bám tay (ảnh phải đi theo tay tức
     thì), và chặn bôi đen. Cú trượt sau khi nhả tay do JS gán transition transform inline. */
.pd-gallery { position: relative; }
.pd-gallery.pd-gallery-swipeable { touch-action: pan-y pinch-zoom; }
.pd-gallery.is-dragging { -webkit-user-select: none; user-select: none; }
.pd-gallery.is-dragging img { transition: none; }
.pd-gallery img.pd-gallery-ghost {
  position: absolute; inset: 0;
  pointer-events: none;
  -webkit-user-drag: none;
}

/* =========================== SCROLL-REVEAL =========================== */
/* Fade-up nhẹ khi cuộn tới, cho các section dưới màn hình đầu (index.php — Danh mục
   nổi bật/Sản phẩm nổi bật/Trust signals/Blog, xem main.js hàm initScrollReveal). Cố
   ý không đặt opacity/transform ẩn sẵn trên chính ".js-reveal" — class này trong markup
   PHP chỉ là 1 "marker" thuần, không tự ẩn gì cả. Việc thực sự ẩn phần tử chỉ xảy ra
   khi main.js chạy xong, xác nhận có IntersectionObserver và khách không bật
   prefers-reduced-motion, rồi mới tự thêm class ".reveal-armed" (xem hàm
   initScrollReveal()). Nếu làm ngược lại (để CSS ẩn sẵn .js-reveal ngay từ đầu), 1 khi
   JS lỗi/bị chặn hoàn toàn nội dung sẽ kẹt ở opacity:0 vĩnh viễn — vi phạm nguyên tắc
   progressive enhancement (xem ANIMATION_GUIDE.md). Nhờ vậy không cần khai báo thêm
   rule nào trong khối @media (prefers-reduced-motion: reduce) — khách bật giảm chuyển
   động sẽ không bao giờ thấy class .reveal-armed xuất hiện, tức nội dung hiện bình
   thường ngay từ đầu, không phụ thuộc CSS phải "chặn" gì thêm.
   Dùng opacity + transform (không dùng margin/height) để không ảnh hưởng layout/CLS;
   nội dung luôn nằm trong DOM (không display:none/visibility:hidden) nên vẫn đọc được
   bằng screen reader ngay cả khi hiệu ứng chưa kịp chạy.
   main.js tự gỡ cả ".reveal-armed" lẫn ".is-visible" khỏi phần tử ngay sau khi
   transition fade-up chạy xong (initScrollReveal() → settleReveal()) — bắt
   buộc vì selector ".js-reveal.reveal-armed.is-visible" có specificity 3 lớp,
   cao hơn rule ":hover" 2 lớp đặt transform trên .cat-card/.product-card/
   .post-card. Giữ .is-visible mãi mãi sẽ khiến "transform: none" đè chết
   translateY() khi hover sau lần reveal đầu. Không thêm rule set opacity/
   transform theo riêng class ".is-visible" (không kèm .reveal-armed) ở nơi
   khác trong file. */
.js-reveal.reveal-armed {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity var(--dur-slow) var(--ease-enter), transform var(--dur-slow) var(--ease-enter);
}
.js-reveal.reveal-armed.is-visible {
  opacity: 1;
  transform: none;
}
/* 3 lớp class (specificity cao hơn hẳn 2 lớp .js-reveal.reveal-armed ở trên, không
   phụ thuộc thứ tự khai báo) để trong lúc thẻ đang reveal, transition box-shadow/
   border-color khi hover trên .cat-card/.product-card/.post-card vẫn dùng đúng
   --dur-base/--ease-standard của chính nó thay vì bị transition (chỉ có
   opacity/transform) của .reveal-armed ghi đè toàn bộ. */
.cat-card.js-reveal.reveal-armed {
  transition: opacity var(--dur-slow) var(--ease-enter), transform var(--dur-slow) var(--ease-enter),
              box-shadow var(--dur-base) var(--ease-standard), border-color var(--dur-base) var(--ease-standard);
}
.product-card.js-reveal.reveal-armed,
.post-card.js-reveal.reveal-armed {
  transition: opacity var(--dur-slow) var(--ease-enter), transform var(--dur-slow) var(--ease-enter),
              box-shadow var(--dur-base) var(--ease-standard);
}
/* Stagger nhẹ cho các thẻ trong cùng 1 lưới (danh mục/sản phẩm/bài viết/trust signals) —
   trễ tăng dần theo vị trí, lặp lại theo chu kỳ 4 phần tử (0/60/120/180ms) để tổng thời
   gian chờ không kéo dài vô hạn khi lưới có nhiều thẻ (đúng yêu cầu "dưới 100ms mỗi
   phần tử" ở ANIMATION_GUIDE.md). Chỉ liệt kê đúng các lưới thực sự dùng
   .js-reveal — không áp máy móc cho mọi ".xxx-grid" khác trên site. */
.cat-grid > .js-reveal.reveal-armed:nth-child(4n+2),
.product-grid > .js-reveal.reveal-armed:nth-child(4n+2),
.post-grid > .js-reveal.reveal-armed:nth-child(4n+2),
.trust-signals-grid > .js-reveal.reveal-armed:nth-child(4n+2),
.trust-strip-grid > .js-reveal.reveal-armed:nth-child(4n+2) { transition-delay: 60ms; }
.cat-grid > .js-reveal.reveal-armed:nth-child(4n+3),
.product-grid > .js-reveal.reveal-armed:nth-child(4n+3),
.post-grid > .js-reveal.reveal-armed:nth-child(4n+3),
.trust-signals-grid > .js-reveal.reveal-armed:nth-child(4n+3),
.trust-strip-grid > .js-reveal.reveal-armed:nth-child(4n+3) { transition-delay: 120ms; }
.cat-grid > .js-reveal.reveal-armed:nth-child(4n),
.product-grid > .js-reveal.reveal-armed:nth-child(4n),
.post-grid > .js-reveal.reveal-armed:nth-child(4n),
.trust-signals-grid > .js-reveal.reveal-armed:nth-child(4n),
.trust-strip-grid > .js-reveal.reveal-armed:nth-child(4n) { transition-delay: 180ms; }

.pd-thumbs { display: flex; gap: 10px; margin-top: var(--space-3); flex-wrap: wrap; }
.pd-thumb-btn {
  width: 64px; height: 64px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  padding: 0;
  border: 2px solid transparent;
  cursor: pointer;
  background: var(--paper-deep);
  flex-shrink: 0;
  transition: border-color var(--dur-fast) var(--ease-standard);
}
.pd-thumb-btn img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pd-thumb-btn.active, .pd-thumb-btn:hover, .pd-thumb-btn:active { border-color: var(--koi-orange); }
/* Mô tả chi tiết sản phẩm — nằm ở cột trái, dưới ảnh, có thể thu gọn/mở rộng khi dài.
   Nền "mica": gradient giấy washi nhạt + 2 vệt sáng blur trang trí (cùng tinh thần hoạ tiết
   gợn sóng dùng ở hero/cat-card). */
.pd-desc-block {
  position: relative;
  overflow: hidden;
  background: linear-gradient(180deg, #fffdf9 0%, var(--gold-tint) 100%);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 22px var(--space-5) 20px;
  margin-top: 18px;
  box-shadow: 0 14px 34px -24px rgba(18,34,44,.4);
}
.pd-desc-block::before,
.pd-desc-block::after {
  content: "";
  position: absolute;
  z-index: 0;
  border-radius: 50%;
  filter: blur(28px);
  pointer-events: none;
}
.pd-desc-block::before {
  width: 170px; height: 170px;
  top: -70px; right: -55px;
  background: rgba(31, 111, 106, .16); /* jade */
}
.pd-desc-block::after {
  width: 140px; height: 140px;
  bottom: -55px; left: -45px;
  background: rgba(217, 102, 59, .12); /* koi-orange */
}
.pd-desc-heading {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 15px;
  margin: 0 0 14px;
  padding-bottom: var(--space-3);
  border-bottom: 1px dashed rgba(18,34,44,.16);
}
.pd-desc-heading::before {
  content: "";
  flex-shrink: 0;
  width: 6px; height: 18px;
  border-radius: var(--radius-sm);
  background: linear-gradient(180deg, var(--koi-orange), var(--gold));
}
.pd-desc-block .pd-desc {
  position: relative;
  z-index: 1;
  margin-bottom: 0;
  max-height: 260px;
  overflow: hidden;
  transition: max-height var(--dur-slow) var(--ease-standard);
}
/* Lớp phủ gradient "còn nữa" ở đáy khối mô tả: LUÔN tồn tại (không chỉ được
   thêm/gỡ theo class), chỉ chuyển opacity 0<->1 có transition. Lý do: nếu để
   pseudo-element chỉ "xuất hiện/biến mất" theo sự khớp selector (cách cũ), nó
   bật/tắt ngay lập tức trong đúng khung hình JS thêm/gỡ class .is-expanded —
   tức là đúng lúc animation chiều cao (.pd-desc) bắt đầu chạy, gây cảm giác
   "giật" một nhịp trước khi chiều cao mới kịp trượt mượt. Chuyển sang animate
   bằng opacity (khớp cùng thời lượng var(--dur-slow) với transition max-height) để lớp
   phủ mờ dần đồng bộ với quá trình mở/thu gọn, không còn hiện tượng bật tắt
   đột ngột nữa — cả 2 dùng chung var(--dur-slow)/var(--ease-standard), PHẢI
   giữ cùng thời lượng với transition max-height ở trên để đồng bộ. */
.pd-desc-block .pd-desc::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 52px;
  background: linear-gradient(to bottom, rgba(250,243,228,0), var(--gold-tint));
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-standard);
}
.pd-desc-block.is-collapsible:not(.is-expanded) .pd-desc::after {
  opacity: 1;
}
/* Quan trọng: thuộc tính HTML `hidden` chỉ tạo ra `display:none` ở tầng CSS
   mặc định của trình duyệt (user-agent stylesheet) — tầng này LUÔN bị CSS của
   tác giả (author stylesheet, tức file này) đè lên bất kể độ đặc hiệu selector
   (specificity), vì thứ tự ưu tiên cascade tính theo tầng nguồn (origin) trước
   khi so specificity. Do rule `.pd-desc-toggle { display: inline-flex }` bên
   dưới là CSS tác giả nhắm cùng phần tử, nó thắng `[hidden]{display:none}` của
   trình duyệt -> nút "Xem thêm" luôn hiển thị dù JS đã set `toggle.hidden =
   true` cho mô tả ngắn. Đây là gốc rễ thật sự của lỗi (không phải do logic đo
   chiều cao trong main.js, logic đó đã đúng). Khắc phục: khai báo riêng rule
   cho trạng thái [hidden] với !important để đảm bảo nó luôn thắng, bất kể thứ
   tự các rule khác trong file thay đổi sau này. */
.pd-desc-toggle[hidden] {
  display: none !important;
}
.pd-desc-toggle {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 14px;
  background: rgba(217, 102, 59, .09);
  border: 1px solid rgba(217, 102, 59, .18);
  padding: 7px 14px;
  border-radius: var(--radius-pill);
  color: var(--koi-orange);
  font-weight: 700;
  font-size: var(--fs-sm);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
}
.pd-desc-toggle:hover { background: rgba(217, 102, 59, .16); border-color: rgba(217, 102, 59, .3); }
.pd-desc-toggle:active { background: rgba(217, 102, 59, .22); border-color: rgba(217, 102, 59, .4); }
.pd-desc-toggle svg { transition: transform var(--dur-base) var(--ease-standard); }
.pd-desc-block.is-expanded .pd-desc-toggle svg { transform: rotate(180deg); }

.pd-category { color: var(--jade); font-weight: 700; font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .08em; }
.pd-title { font-size: 28px; margin: var(--space-2) 0 14px; }
.pd-price-row { display: flex; align-items: baseline; gap: var(--space-3); margin-bottom: 18px; }
.pd-price-row .price-now { font-size: 28px; }
/* Mô tả ngắn dưới giá: tối đa 3 dòng, dài hơn thì cắt bằng "…" và có nút "Xem thêm" (main.js,
   initProductShortDesc). Nút bị ẩn bằng [hidden] — cần rule [hidden] riêng vì display:inline-flex
   của .pd-short-toggle sẽ đè thuộc tính hidden của trình duyệt (cùng lý do như .pd-desc-toggle). */
.pd-short { margin: -6px 0 18px; }
.pd-short-text {
  margin: 0;
  font-size: 15px; line-height: 1.6; color: var(--ink-soft);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3;
  overflow: hidden;
  overflow-wrap: anywhere;
}
.pd-short.is-expanded .pd-short-text { display: block; -webkit-line-clamp: unset; line-clamp: unset; overflow: visible; }
.pd-short-toggle[hidden] { display: none; }
.pd-short-toggle {
  display: inline-flex; align-items: center;
  margin-top: 4px; padding: 2px 0;
  background: none; border: 0; cursor: pointer;
  font: inherit; font-size: 14px; font-weight: 600; color: var(--koi-orange);
}
@media (hover: hover) { .pd-short-toggle:hover { text-decoration: underline; text-underline-offset: 3px; } }
.pd-short-toggle:active { opacity: .65; }

/* Khối "mua sỉ số lượng lớn" — nền nhạt viền jade để nổi bật khỏi nội dung
   xung quanh nhưng không cạnh tranh màu với nút "Thêm vào giỏ" (koi-orange). */
.wholesale-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  background: rgba(31, 111, 106, .07);
  border: 1.4px dashed var(--jade);
  border-radius: var(--radius);
  padding: 14px 18px;
  margin-bottom: 18px;
}
.wholesale-cta-text { display: flex; flex-direction: column; gap: 2px; }
.wholesale-cta-text strong { font-size: 14.5px; color: var(--ink); }
.wholesale-cta-text span { font-size: var(--fs-sm); color: var(--text-muted); }
.wholesale-cta-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
/* 3 nút trong khối mua sỉ dùng cỡ nhỏ hơn .btn mặc định (13px/9px 16px thay vì
   14.5px/13px 26px) — vì đây là 3 lựa chọn liên hệ phụ đứng cạnh nhau, không
   phải 1 CTA đơn độc, giữ cho khối gọn và không lấn át nút "Thêm vào giỏ hàng". */
.wholesale-cta-actions .btn { padding: 9px 16px; font-size: 13px; gap: 6px; }

.pd-meta { font-size: var(--fs-sm); color: var(--text-muted); margin-bottom: 20px; }
.pd-desc { font-size: 14.5px; color: var(--ink-soft); margin-bottom: 26px; line-height: 1.75; }
/* Link chèn tay trong mô tả sản phẩm/nội dung bài viết (nhập qua admin, xem post.php/product.php)
   không có class riêng nên cần tự cho affordance — CHỈ áp dụng trong vùng nội dung tự do này,
   không áp dụng lan ra toàn site (menu/nút/thẻ đã có hiệu ứng riêng, không cần và không nên
   gạch chân mặc định). */
.pd-desc a { color: var(--koi-orange); text-decoration: underline; text-underline-offset: 2px; }
.pd-desc a:hover { opacity: .8; }
.pd-desc a:active { opacity: .65; }
/* Mô tả sản phẩm soạn bằng trình soạn thảo (products.description_format = 'html', in qua renderProductDescription()
   trong includes/functions.php): đoạn văn, tiêu đề h3 IN HOA, danh sách và 4 màu chữ cố định (class tc-*, do bộ lọc
   sanitizeProductDescHtml() chỉ cho đúng 4 class này đi qua). Giới hạn trong .pd-desc-block để KHÔNG ảnh hưởng bài viết
   (post.php) và các trang chính sách cũng dùng .pd-desc. Mô tả dạng text cũ không có p/h3/ul nên hiển thị y như trước.
   Tiêu đề chỉ cần gõ chữ thường — text-transform lo phần in hoa. */
.pd-desc-block .pd-desc p { margin: 0 0 .75em; }
.pd-desc-block .pd-desc h3 {
  text-transform: uppercase;
  letter-spacing: .04em;
  font-size: 15px;
  line-height: 1.35;
  margin: 1.5em 0 .6em;
  padding-left: 10px;
  border-left: 3px solid var(--koi-orange);
  color: var(--ink);
}
.pd-desc-block .pd-desc ul,
.pd-desc-block .pd-desc ol { margin: 0 0 .9em; padding-left: 22px; }
.pd-desc-block .pd-desc ul { list-style: disc; } /* rule chung "ul { list-style: none }" ở đầu file đã tắt bullet */
.pd-desc-block .pd-desc ol { list-style: decimal; }
.pd-desc-block .pd-desc li { margin-bottom: 4px; }
.pd-desc-block .pd-desc li > p { margin: 0; } /* khối lạ (div...) nằm trong li được đổi thành p: không thêm khoảng cách */
.pd-desc-block .pd-desc > :first-child { margin-top: 0; }
.pd-desc-block .pd-desc > :last-child { margin-bottom: 0; }
/* Màu chữ CHỈ đổi màu, KHÔNG đặt font-weight: độ đậm do <strong> quyết định. (Trước đây các class này có
   font-weight: 600 nên đè mất độ đậm kế thừa từ <strong> / <h3> — bấm "đậm" lên chữ màu không đổi gì, và
   tô màu cho tiêu đề (700) còn làm nó NHẸ đi.) */
.pd-desc-block .pd-desc .tc-cam { color: var(--koi-orange); }
.pd-desc-block .pd-desc .tc-xanh { color: var(--jade); }
.pd-desc-block .pd-desc .tc-do { color: var(--koi-red); }
.pd-desc-block .pd-desc .tc-xanhduong { color: var(--info); }
/* Đậm: chữ thường 400 -> 700; trong tiêu đề h3 (đã 700) -> 900 (Fraunces có sẵn 900) để thấy khác. */
.pd-desc-block .pd-desc strong { font-weight: 700; }
.pd-desc-block .pd-desc h3 strong { font-weight: 900; }
.qty-row { display: flex; align-items: center; gap: 14px; margin-bottom: 20px; }
.qty-box { display: inline-flex; align-items: center; border: 1px solid var(--line); border-radius: var(--radius-pill); overflow: hidden; background: #fff; flex-shrink: 0; }
.qty-box button { width: 32px; height: 32px; border: none; background: #fff; cursor: pointer; font-size: 15px; flex-shrink: 0; color: var(--ink); }
.qty-box button:hover,
.qty-box button:active { background: var(--paper-deep); }
.qty-box input {
  width: 36px;
  text-align: center;
  border: none;
  font-size: var(--fs-sm);
  padding: 0;
  -moz-appearance: textfield;
  /* Scale rất nhẹ + nhanh (--dur-fast, không có dịch chuyển vị trí, chỉ phồng tại
     chỗ) mỗi khi số đổi, cho cảm giác "bấm được" — lớp .is-qty-bumped do main.js
     thêm/gỡ tạm qua setTimeout. */
  transition: transform var(--dur-fast) var(--ease-standard);
}
.qty-box input.is-qty-bumped { transform: scale(1.15); }
.qty-box input::-webkit-outer-spin-button,
.qty-box input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.pd-actions { display: flex; gap: var(--space-3); }

/* Chọn option (biến thể) trên trang chi tiết sản phẩm */
.pd-options { margin-bottom: 22px; }
.pd-option-group { margin: 0; }
.pd-option-group + .pd-option-group { margin-top: 14px; }
.pd-option-label { font-weight: 700; font-size: var(--fs-sm); margin-bottom: var(--space-2); }
.pd-option-values { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.pd-option-btn {
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  border: 1.4px solid var(--line);
  background: #fff;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1.3;
  cursor: pointer;
  text-align: center;
  transition: border-color var(--dur-fast) var(--ease-standard), background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.pd-option-btn:hover:not(:disabled),
.pd-option-btn:active:not(:disabled) { border-color: var(--koi-orange); }
.pd-option-btn.is-selected { border-color: var(--koi-orange); background: var(--koi-orange); color: #fff; }
.pd-option-btn:disabled, .pd-option-btn.is-oos {
  opacity: .55;
  cursor: not-allowed;
  background: var(--paper-deep);
  color: var(--text-muted);
  text-decoration: line-through;
}
.pd-option-oos-tag { display: block; font-size: 10.5px; font-weight: 700; text-decoration: none; margin-top: 2px; }
.pd-option-hint { font-size: var(--fs-xs); color: var(--koi-red); margin: 0; padding-top: 14px; }
/* Khoảng cách 14px giữa nhóm thuộc tính cuối và dòng hint nằm trong padding của CHÍNH hint
   (không còn là margin-bottom của .pd-option-group). Trước đây margin đó gộp (collapse) với
   margin-top của hint; đến lúc hint display:none thì margin 14px "bung" ra làm cả trang nhảy
   1 nhịp ở cuối animation. Giờ padding-top co về 0 cùng max-height nên không còn cú nhảy.
   Chiều đóng dùng easing đối xứng cho max-height/padding, chỉ opacity/transform giữ ease-exit. */
.pd-option-hint.js-fade-toggle:not(.is-visible) {
  padding-top: 0;
  transition: opacity var(--dur-base) var(--ease-exit),
              transform var(--dur-base) var(--ease-exit),
              max-height var(--dur-base) var(--ease-standard),
              padding-top var(--dur-base) var(--ease-standard);
}
.pd-option-hint.js-fade-toggle.is-visible {
  transition: opacity var(--dur-slow) var(--ease-enter),
              transform var(--dur-slow) var(--ease-enter),
              max-height var(--dur-slow) var(--ease-enter),
              padding-top var(--dur-slow) var(--ease-enter);
}
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn:disabled:hover { transform: none; box-shadow: none; }

/* =========================== CART PAGE =========================== */
/* Gỡ phần tử có fade-out (removeRowAnimated() trong main.js: dòng giỏ, thẻ "Sản phẩm đã lưu", dòng mini cart).
   `html .is-removing` (0,1,1) cố ý thắng rule gốc 1 class của từng loại thẻ có transition riêng (transform/shadow...).
   Dòng <tr> desktop chỉ mờ (max-height không áp cho display:table-row); chỗ khác thêm .is-collapsing để thu chiều cao.
   Reduced-motion: rule chung (`*:not(...)`) tự tắt transition, JS cũng gỡ ngay không chờ. */
html .is-removing { opacity: 0; pointer-events: none; transition: opacity var(--dur-base) var(--ease-exit); }
html .is-removing.is-collapsing {
  transition: opacity var(--dur-base) var(--ease-exit),
              max-height var(--dur-base) var(--ease-exit),
              padding var(--dur-base) var(--ease-exit),
              margin var(--dur-base) var(--ease-exit),
              border-width var(--dur-base) var(--ease-exit);
}
/* Khối nội dung -> trạng thái trống (giỏ hàng: showCartEmptyState; "Sản phẩm đã lưu": handler js-wishlist-toggle):
   khối cũ mờ ra (.swap-out), khối trống mờ vào (.swap-in -> thêm .is-in). Dùng `html` để thắng rule gốc của khối. */
html .swap-out { opacity: 0; pointer-events: none; transition: opacity var(--dur-base) var(--ease-exit); }
html .swap-in { opacity: 0; }
html .swap-in.is-in { opacity: 1; transition: opacity var(--dur-slow) var(--ease-enter); }

/* .cart-table dùng chung khung .product-detail (grid 2 cột) với trang chi tiết sản phẩm để
   tái dùng CSS, nhưng cột bảng giỏ hàng ("Đơn giá") cần rộng hơn cột ảnh sản phẩm rất nhiều —
   ép chung tỉ lệ 1fr/1.05fr khiến cột giá quá hẹp. Thêm class riêng .cart-layout để nới cột
   trái rộng hơn CHỈ cho giỏ hàng, không ảnh hưởng trang sản phẩm. */
.cart-layout { grid-template-columns: 1.7fr 1fr; }
.cart-table { width: 100%; border-collapse: collapse; table-layout: fixed; }
.cart-table th { text-align: left; font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); padding: 10px var(--space-3); border-bottom: 1px solid var(--line); white-space: nowrap; }
/* KHÔNG dùng overflow:hidden ở đây: kết hợp với table-layout:fixed + cột hẹp, nó từng cắt mất
   1 phần chữ (ví dụ mất ký tự "đ" cuối giá tiền) mỗi khi nội dung chỉ hơi rộng hơn cột — lỗi
   "mất chữ" ở cột Đơn giá/Thành tiền trên desktop. Việc "cắt tràn" tên sản phẩm 2 dòng đã do
   riêng .cart-prod a (-webkit-line-clamp) đảm nhiệm nên bỏ overflow:hidden ở tầng td là an toàn. */
.cart-table td { padding: var(--space-4) var(--space-3); border-bottom: 1px solid var(--line); font-size: var(--fs-sm); vertical-align: middle; }
.cart-prod { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.cart-prod img { width: 60px; height: 60px; object-fit: cover; border-radius: var(--radius-sm); background: var(--paper-deep); flex-shrink: 0; }
.cart-prod a {
  font-weight: 600;
  font-size: var(--fs-sm);
  line-height: 1.4;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-width: 0;
}
/* Tên sản phẩm trong giỏ: hover cam giống .product-name a:hover (global `a` đã có transition color). */
.cart-prod a:hover,
.cart-prod a:active { color: var(--koi-orange); }
.cart-table td.js-line-total { font-weight: 700; color: var(--koi-red); white-space: nowrap; padding-right: 20px; }
.cart-table td:nth-child(2) { white-space: nowrap; }
/* Hover hàng giỏ hàng (chỉ khi có chuột thật). Desktop: tô nền cho hàng bảng. Mobile (≤640px): tr là thẻ nền trắng
   riêng (.cart-table tr ở media mobile) nên KHÔNG đổi background (sẽ làm thẻ trong suốt) mà phủ lớp tối nhẹ bằng inset box-shadow. */
.cart-table tbody tr { transition: background-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard); }
@media (hover: hover) {
  .cart-table tbody tr:hover { background: rgba(18, 34, 44, .03); }
}
@media (hover: hover) and (max-width: 640px) {
  .cart-table tbody tr:hover { background: #fff; box-shadow: inset 0 0 0 9999px rgba(18, 34, 44, .03); }
}
.cart-table .qty-box { width: fit-content; }
.cart-summary { background: #fff; border: 1px solid var(--line); border-radius: var(--radius); padding: var(--space-5); position: sticky; top: 100px; }
.summary-row { display: flex; justify-content: space-between; font-size: var(--fs-sm); margin-bottom: var(--space-3); }
.summary-total { font-weight: 800; font-size: 18px; border-top: 1px solid var(--line); padding-top: 14px; margin-top: 6px; }
.remove-link { color: var(--koi-red); font-size: var(--fs-sm); font-weight: 600; white-space: nowrap; display: inline-block; margin-left: var(--space-1); }
.remove-link:hover { color: #93131b; text-decoration: underline; }
.remove-link:active { opacity: .7; }

/* ---------- Mã giảm giá / voucher (cart.php) ----------
   Cùng tinh thần với .toolbar-field/.btn-outline đã có: viền mảnh, nền giấy
   nhạt (--paper-deep không dùng ở đây vì cart-summary đã nền trắng #fff, giữ
   input nền hơi ngà #fdfbf7 giống "form input" ở nơi khác cho đồng bộ), chỉ
   nổi bật bằng màu --jade khi mã đã được áp dụng thành công (khớp màu
   .alert-success đang dùng cho các thông báo tích cực khác trong site). */
.coupon-box { margin-bottom: var(--space-4); }

/* Pattern dùng chung để ẩn/hiện có animate cho các khối chiếm chỗ trong luồng bố cục
   bình thường (display:flex/block/inline thật sự khi hiện), khác .select-custom-panel
   vốn position:absolute nên không cần lo layout khi ẩn — pattern này cùng cấu trúc
   timing với .select-custom-panel (đóng dùng --dur-base/--ease-exit, mở dùng
   --dur-slow/--ease-enter) nhưng tách riêng 2 lớp thay vì chỉ 1 lớp .is-open:
     - .is-shown  — CHỈ lo display (giá trị tự nhiên riêng từng khối, khai báo cạnh mỗi
       khối sử dụng: xem .coupon-form/.coupon-applied ngay dưới đây cho display:flex,
       .online-methods-box/.payment-hint-box cho display:block, .price-old cho
       display:inline). Đổi qua classList (main.js, hàm fadeToggle) — KHÔNG phải
       el.style.display trực tiếp.
     - .is-visible — lo opacity/transform/transition thật sự.
   Tách 2 lớp vì lý do kỹ thuật: không thể transition thẳng từ display:none sang có nội
   dung mà vẫn mờ dần được — trình tự bắt buộc (xem hàm fadeToggle trong main.js): khi
   HIỆN, thêm .is-shown trước (vào luồng bố cục nhưng vẫn opacity 0) rồi đợi 2 khung hình
   (requestAnimationFrame lồng nhau) mới thêm .is-visible để opacity/transform thật sự
   chạy transition; khi ẨN, gỡ .is-visible trước (transition mờ dần bắt đầu chạy) rồi
   setTimeout đúng bằng --dur-base (200ms) mới gỡ .is-shown, để khối chỉ thật sự rời khỏi
   luồng bố cục SAU khi hiệu ứng mờ đã chạy xong (nếu gỡ cả 2 lớp cùng lúc, display:none
   áp dụng ngay lập tức và cắt cụt mất phần fade chưa kịp chạy).
   Không cần khối prefers-reduced-motion riêng cho pattern này (giống lý do đã ghi ở
   .select-custom-panel): rule chung "*:not(.category-sheet) { transition:none!important;
   animation:none!important; }" (gần cuối file) đã tự tắt hẳn transition, khối sẽ ẩn/hiện
   tức thì (0ms) khi khách bật "giảm chuyển động" — đây là lựa chọn ĐÚNG theo bảng phân
   loại "trang trí"/"mang thông tin" ở ANIMATION_GUIDE.md mục 5: cả 5 điểm dùng pattern
   này (coupon, phương thức thanh toán online, hộp hướng dẫn chuyển khoản, giá cũ) đều
   LUÔN đi kèm 1 thay đổi nội dung/số liệu khác tự báo hiệu trạng thái (số tiền giảm giá,
   tổng tiền, nội dung hộp hướng dẫn đổi theo phương thức...) — khác trường hợp sheet
   (mở/đóng không kèm gì khác) buộc phải giữ lại 1 fade ngắn để không "mất phản hồi".

   Animate max-height: trước đây
   .js-fade-toggle chỉ đổi display (none<->giá trị thật) tức thời — với khối NHỎ (1
   dòng: giá cũ, dòng giảm giá...) thì không nhận ra, nhưng khối LỚN (form thêm/sửa địa
   chỉ, hộp "Thanh toán online" chứa nhiều option...) sẽ làm cả trang giãn ra/co lại đột
   ngột ngay khi display đổi, trong khi opacity/transform chỉ lo phần MỜ chứ không lo
   phần CHIỀU CAO. fadeToggle() (main.js) nay tự đo scrollHeight rồi animate max-height
   từ 0 -> chiều cao thật (mở) hoặc ngược lại (đóng), dùng đúng kỹ thuật "chốt mốc px cụ
   thể + đợi 2 khung hình" đã dùng cho khối "Xem thêm/Thu gọn" mô tả sản phẩm (xem
   measureFullHeight() trong main.js) — lý do transition không chạy được nếu 1 đầu là
   "none"/"auto" (không nội suy được) đã giải thích đầy đủ ở đó, không lặp lại ở đây.
   max-height:none ở .is-visible là trạng thái NGHỈ (không đang animate, JS đã gỡ inline
   style sau khi chạy xong) — để nội dung động thêm sau này (form dài ra do lỗi validate,
   đổi ngôn ngữ...) không bị khoá cứng theo đúng số đo cũ. overflow:hidden là bắt buộc đi
   kèm max-height (nếu không, nội dung sẽ tràn ra ngoài lúc đang bị giới hạn ở mức 0..N
   trong lúc animate) — không ảnh hưởng box-shadow/dropdown nào vì các khối dùng
   .js-fade-toggle hiện tại đều là box phẳng, không có phần tử con nào cố ý tràn ra ngoài.
   NGOẠI LỆ: .account-panel (xem cụm "iOS cross-dissolve" + .account-panels.is-switching
   ở phần "Tài khoản của tôi" bên dưới) KHÔNG dùng max-height này — panel đó đã có cơ chế
   riêng animate chiều cao ở CHÍNH .account-panels (khung bao ngoài chứa mọi panel, xử lý
   lúc CHUYỂN GIỮA 2 panel, không phải mở/đóng 1 khối đơn lẻ) và switchAccountPanel()
   (main.js) cần đọc offsetHeight của panel mới NGAY sau khi gọi fadeToggle() — nếu để
   max-height:0 tạm thời áp lên .account-panel như mọi nơi khác, dòng đọc offsetHeight đó
   sẽ đọc nhầm ra ~0 và phá vỡ hẳn animation chuyển tab mượt đã có. Rule loại trừ nằm ở
   cụm ".account-panel.js-fade-toggle" (đã có sẵn, xem phần "Tài khoản của tôi") — chỉ
   cần thêm max-height:none;overflow:visible vào đúng cụm đó, không lặp lại logic ở đây. */
.js-fade-toggle {
  display: none;
  opacity: 0;
  transform: translateY(-6px);
  pointer-events: none;
  overflow: hidden;
  max-height: 0;
  transition: opacity var(--dur-base) var(--ease-exit),
              transform var(--dur-base) var(--ease-exit),
              max-height var(--dur-base) var(--ease-exit);
}
.js-fade-toggle.is-visible {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
  max-height: none;
  transition: opacity var(--dur-slow) var(--ease-enter),
              transform var(--dur-slow) var(--ease-enter),
              max-height var(--dur-slow) var(--ease-enter);
}

.coupon-form.is-shown, .coupon-applied.is-shown { display: flex; }
/* #cartDiscountRow (dòng "Giảm giá" trong tóm tắt giỏ hàng) dùng đúng pattern
   .js-fade-toggle này; .summary-row vốn display:flex (xem khối ".summary-row" phía
   trên), chỉ .is-shown trên chính dòng này mới khớp vì hiện không có ".summary-row"
   nào khác được gắn .js-fade-toggle/.is-shown. */
.summary-row.is-shown { display: flex; }

/* Khối ẩn/hiện KHÔNG còn nhảy ở đầu/cuối animation (giỏ hàng + thanh toán).
   max-height chỉ co được phần NỘI DUNG; margin/padding/border của chính khối nằm ngoài phần
   được animate nên trước đây bật hoặc tắt đột ngột cùng lúc display đổi (.is-shown) — phần bên
   dưới nhảy vài px đến ~30px ở khung hình đầu hoặc cuối (ẩn dòng "Giảm giá" −12px, đóng hộp
   hướng dẫn chuyển khoản −30px, mở ô "Lưu địa chỉ này" +10px...). Giờ các thuộc tính đó co về 0
   khi ẩn và được animate CÙNG max-height, cùng thời lượng + easing (đóng --dur-base/--ease-exit,
   mở --dur-slow/--ease-enter), nên chiều cao tổng thay đổi liền mạch rồi display:none lúc khối
   đã cao 0. Cùng cách đã làm cho .pd-option-hint (padding-top) ở trên. Chỉ áp cho đúng danh sách
   khối dưới đây — KHÔNG áp chung cho .js-fade-toggle vì .account-panel và form Sổ địa chỉ
   (.account-address-form-wrap, có border-top/padding-top riêng và nằm trong popup) cố ý khác.
   Thêm khối mới vào pattern này: thêm selector vào CẢ HAI rule dưới đây. fadeToggle() (main.js)
   đo chiều cao đích ĐÃ gồm padding + border bằng measureFadeFullHeight(). */
.coupon-form.js-fade-toggle:not(.is-visible),
.coupon-applied.js-fade-toggle:not(.is-visible),
.summary-row.js-fade-toggle:not(.is-visible),
.checkout-save-address-label.js-fade-toggle:not(.is-visible),
.online-methods-box.js-fade-toggle:not(.is-visible),
.payment-hint-box.js-fade-toggle:not(.is-visible) {
  margin-top: 0;
  margin-bottom: 0;
  padding-top: 0;
  padding-bottom: 0;
  border-top-width: 0;
  border-bottom-width: 0;
  transition: opacity var(--dur-base) var(--ease-exit),
              transform var(--dur-base) var(--ease-exit),
              max-height var(--dur-base) var(--ease-exit),
              margin-top var(--dur-base) var(--ease-exit),
              margin-bottom var(--dur-base) var(--ease-exit),
              padding-top var(--dur-base) var(--ease-exit),
              padding-bottom var(--dur-base) var(--ease-exit),
              border-top-width var(--dur-base) var(--ease-exit),
              border-bottom-width var(--dur-base) var(--ease-exit);
}
.coupon-form.js-fade-toggle.is-visible,
.coupon-applied.js-fade-toggle.is-visible,
.summary-row.js-fade-toggle.is-visible,
.checkout-save-address-label.js-fade-toggle.is-visible,
.online-methods-box.js-fade-toggle.is-visible,
.payment-hint-box.js-fade-toggle.is-visible {
  transition: opacity var(--dur-slow) var(--ease-enter),
              transform var(--dur-slow) var(--ease-enter),
              max-height var(--dur-slow) var(--ease-enter),
              margin-top var(--dur-slow) var(--ease-enter),
              margin-bottom var(--dur-slow) var(--ease-enter),
              padding-top var(--dur-slow) var(--ease-enter),
              padding-bottom var(--dur-slow) var(--ease-enter),
              border-top-width var(--dur-slow) var(--ease-enter),
              border-bottom-width var(--dur-slow) var(--ease-enter);
}
.coupon-form { gap: var(--space-2); }
.coupon-form input {
  flex: 1;
  min-width: 0;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  background: #fdfbf7;
  transition: border-color var(--dur-fast) var(--ease-standard);
}
.coupon-form input:focus { outline: none; border-color: var(--koi-orange); }
@media (hover: hover) {
  .coupon-form input:hover:not(:focus):not(:disabled) { border-color: rgba(217,102,59,.5); }
}
.coupon-form .btn { flex-shrink: 0; white-space: nowrap; }
.coupon-applied {
  /* "Koi vàng": nền tối vân vảy cá (cùng hoạ tiết thẻ voucher), khung vàng mảnh + khung vàng lồng trong (::after),
     số tiền tiết kiệm như dát vàng. Giữ nguyên các id (#couponApplied/#couponAppliedCode/#couponChangeBtn/#couponRemoveBtn)
     và pattern .js-fade-toggle nên main.js không đổi hành vi ẩn/hiện. */
  position: relative;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  gap: 0;
  color: #f7f5ef;
  border: 1px solid rgba(243, 210, 127, .4);
  border-radius: var(--radius);
  padding: 14px 16px 6px;
  font-size: var(--fs-sm);
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='18'%3E%3Cg fill='none' stroke='%23f3d27f' stroke-opacity='.1' stroke-width='1.2'%3E%3Ccircle cx='18' cy='18' r='16'/%3E%3Ccircle cx='18' cy='18' r='10'/%3E%3Ccircle cx='0' cy='9' r='16'/%3E%3Ccircle cx='0' cy='9' r='10'/%3E%3Ccircle cx='36' cy='9' r='16'/%3E%3Ccircle cx='36' cy='9' r='10'/%3E%3C/g%3E%3C/svg%3E"), linear-gradient(135deg, #18323f, #0a141a);
}
.coupon-applied::after { content: ""; position: absolute; inset: 5px; border: 1px solid rgba(243, 210, 127, .25); border-radius: calc(var(--radius) - 4px); pointer-events: none; }
.coupon-applied-main { display: flex; align-items: center; gap: 12px; min-width: 0; }
.coupon-applied-medal { flex: none; width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center; color: #f3d27f; border: 1.5px solid #f3d27f; background: rgba(243, 210, 127, .08); }
.coupon-applied-info { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.coupon-applied-eyebrow { font-size: var(--fs-xs); color: #f3d27f; letter-spacing: .02em; }
.coupon-applied .coupon-applied-code { color: #f7f5ef; font-family: var(--font-display); font-size: 22px; font-weight: 700; line-height: 1.1; letter-spacing: .04em; overflow-wrap: anywhere; }
.coupon-applied-amount { margin-left: auto; flex: none; text-align: right; font-family: var(--font-display); line-height: 1; }
.coupon-applied-amount b {
  display: block; font-size: 24px; font-weight: 700; color: #f3d27f;
  background: linear-gradient(180deg, #fff7de, #e6c366); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
}
.coupon-applied-amount small { display: block; margin-top: 3px; font-family: var(--font-body); font-size: 11px; color: #cdbf95; }
.coupon-applied-actions { position: relative; z-index: 1; display: flex; gap: 4px; margin-top: 8px; padding-top: 2px; border-top: 1px solid rgba(243, 210, 127, .2); }
.coupon-applied .coupon-applied-actions .remove-link { margin-left: 0; color: #e9dfc2; font-weight: 500; font-size: var(--fs-xs); text-decoration: underline; text-underline-offset: 3px; }
.coupon-applied .coupon-applied-actions .remove-link + .remove-link { color: #9fb0b8; }
.coupon-applied .coupon-applied-actions .remove-link:hover { color: #fff; }
.coupon-applied .coupon-applied-actions .remove-link:focus-visible { outline: 2px solid #f3d27f; outline-offset: -2px; border-radius: 6px; }
#cartDiscount { color: var(--jade); font-weight: 700; }

/* Màu hộp "Ưu đãi đã áp dụng" theo LOẠI MÃ — dùng đúng biến --vc-* của .vtone-* (khối voucher bên dưới):
   cam = giảm %, đỏ = giảm tiền, ngọc = freeship, xanh = khách mới. Hội viên (.vtone-tier) giữ nguyên
   kiểu "Koi vàng" nền tối ở trên. Hoạ tiết sóng Koi giống thẻ voucher kiểu A. */
.coupon-applied:not(.vtone-tier) {
  color: var(--vc-on);
  border-color: rgba(255, 255, 255, .28);
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='18'%3E%3Cg fill='none' stroke='%23fff' stroke-opacity='.17' stroke-width='1.4'%3E%3Ccircle cx='18' cy='18' r='16'/%3E%3Ccircle cx='18' cy='18' r='11'/%3E%3Ccircle cx='18' cy='18' r='6'/%3E%3Ccircle cx='0' cy='9' r='16'/%3E%3Ccircle cx='0' cy='9' r='11'/%3E%3Ccircle cx='0' cy='9' r='6'/%3E%3Ccircle cx='36' cy='9' r='16'/%3E%3Ccircle cx='36' cy='9' r='11'/%3E%3Ccircle cx='36' cy='9' r='6'/%3E%3C/g%3E%3C/svg%3E"), linear-gradient(135deg, var(--vc-from), var(--vc-to));
}
.coupon-applied:not(.vtone-tier)::after { border-color: rgba(255, 255, 255, .3); }
.coupon-applied:not(.vtone-tier) .coupon-applied-medal { color: #fff; border-color: #fff; background: rgba(255, 255, 255, .14); }
.coupon-applied:not(.vtone-tier) .coupon-applied-eyebrow { color: rgba(255, 255, 255, .9); }
.coupon-applied:not(.vtone-tier) .coupon-applied-code { color: #fff; text-shadow: 0 1px 0 rgba(0, 0, 0, .14); }
.coupon-applied:not(.vtone-tier) .coupon-applied-amount b { background: none; -webkit-text-fill-color: #fff; color: #fff; text-shadow: 0 2px 0 rgba(0, 0, 0, .16); }
.coupon-applied:not(.vtone-tier) .coupon-applied-amount small { color: rgba(255, 255, 255, .88); }
.coupon-applied:not(.vtone-tier) .coupon-applied-actions { border-top-color: rgba(255, 255, 255, .3); }
.coupon-applied:not(.vtone-tier) .coupon-applied-actions .remove-link,
.coupon-applied:not(.vtone-tier) .coupon-applied-actions .remove-link + .remove-link { color: #fff; }
.coupon-applied:not(.vtone-tier) .coupon-applied-actions .remove-link + .remove-link { opacity: .8; }
.coupon-applied:not(.vtone-tier) .coupon-applied-actions .remove-link:hover { opacity: 1; }
.coupon-applied:not(.vtone-tier) .coupon-applied-actions .remove-link:focus-visible { outline-color: #fff; }

/* Hệ thống "trạng thái rỗng" — CHỈ 2 biến thể, chọn theo VAI TRÒ của khối trong
   trang (không tự chế thêm số padding riêng theo cảm tính):
   - .empty-state (mặc định, padding 80px 20px): dùng khi khối này CHIẾM TRỌN
     khu vực nội dung chính của trang — kết quả duyệt/tìm kiếm sản phẩm rỗng
     (cart.php, search.php, category.php, blog.php), hoặc là toàn bộ nội dung
     của 1 trang standalone chỉ có đúng 1 thông điệp (404.php, vnpay_return.php).
   - .empty-state--compact (padding 40px 20px): dùng khi khối này chỉ là 1
     THÀNH PHẦN nằm lồng trong ngữ cảnh tài khoản/đơn hàng cá nhân, đi kèm các
     nội dung khác quan trọng hơn xung quanh (danh sách đơn hàng/sản phẩm đã
     lưu trong tab account.php, danh sách đơn hàng trong order-lookup.php,
     banner mã đơn ở đầu order-status.php — bên dưới còn nhiều khối khác).
   Không tự thêm class mới hay style inline "padding:...px" riêng — mọi vị trí
   dùng .empty-state phải rơi vào đúng 1 trong 2 loại trên. */
.empty-state { text-align: center; padding: 80px 20px; }
.empty-state--compact { padding: 40px 20px; }
.empty-state .ic { color: var(--jade); margin-bottom: 18px; }

/* =========================== TOAST =========================== */
/* #toastContainer (includes/footer.php) là khung cố định, xếp chồng theo chiều dọc; main.js (showToast())
   tạo động 1 .toast-slot > .toast cho mỗi lần gọi. Bố cục: ô ảnh/icon | tiêu đề + dòng phụ | nút hành động,
   thanh chạy mảnh ở đáy báo sắp tự tắt. Màu theo loại (.toast--cart/wish/success/info/warn/error): nền
   -tint + viền/icon/thanh chạy cùng màu nhấn (đúng cặp token ở khối "MÀU TRẠNG THÁI + NỀN NHẠT").

   VỊ TRÍ: khung neo top:0, còn vị trí dọc do JS đặt bằng transform: translate3d(0, Y, 0) với Y = mép dưới
   THẬT của .site-header + khoảng hở (syncToastOffset() trong main.js) — header sticky nhưng .topbar phía
   trên cuộn đi mất nên mép dưới đổi liên tục khi cuộn; transform không gây layout nên mượt. Không đặt
   top cố định ở đây. Chỉ transform/opacity được animate (compositor), không width/height/top. */
.toast-container {
  position: fixed;
  top: 0;
  right: 24px;
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 10px;
  /* Khung không chặn click xuyên qua vùng trống; từng .toast bật lại pointer-events vì có nút bấm/vuốt. */
  pointer-events: none;
}
.toast-slot {
  position: relative;
  max-width: 100%;
  pointer-events: none;
}
/* Toast đang rời đi: đưa ra khỏi luồng (JS đặt top) để các toast còn lại trượt lấp chỗ song song.
   max-width:none là BẮT BUỘC: khi đây là toast cuối cùng, khung #toastContainer co về rộng 0 nên
   max-width:100% của .toast-slot cũng thành 0 -> toast tràn sang phải, "bay" ra mép màn hình khi đang mờ dần.
   Đang thoát thì không nhận click (tránh bấm trúng nút "Xem giỏ hàng" của toast sắp biến mất). */
.toast-slot.is-leaving { position: absolute; left: auto; right: 0; max-width: none; }
.toast-slot.is-leaving .toast { pointer-events: none; }
.toast {
  --toast-accent: var(--ink);
  --toast-tint: var(--neutral-tint);
  --toast-on: #fff;
  --toast-line: var(--toast-accent); /* màu thanh chạy + gạch chân nút */
  position: relative;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 12px;
  width: 360px;
  max-width: calc(100vw - 32px);
  padding: 11px 14px 14px 11px;
  background: var(--toast-tint);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--ink);
  box-shadow: var(--shadow);
  overflow: hidden;
  pointer-events: auto;
  -webkit-user-select: none;
  user-select: none;
  /* pan-y pinch-zoom: trình duyệt vẫn cuộn dọc/zoom bình thường, ngang thuộc về cử chỉ vuốt bỏ (attachSwipe). */
  touch-action: pan-y pinch-zoom;
  transform: translateY(-12px);
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-exit), transform var(--dur-slow) var(--ease-exit);
}
/* Viền cùng tông màu loại. Đặt trong @supports vì khai báo có var() ở trình duyệt không hiểu color-mix()
   vẫn được nhận lúc parse rồi mới hỏng lúc tính giá trị (border thành currentColor, dòng fallback không dùng). */
@supports (color: color-mix(in srgb, red 30%, transparent)) {
  .toast { border-color: color-mix(in srgb, var(--toast-accent) 30%, transparent); }
}
.toast.show {
  transform: translateY(0);
  opacity: 1;
  transition: opacity var(--dur-slow) var(--ease-enter), transform var(--dur-slow) var(--ease-enter);
}
/* Đang kéo bằng tay: bám sát ngón tay, tuyệt đối không transition (!important thắng cả style inline của lượt nhả trước). */
.toast.is-dragging {
  transition: none !important;
  will-change: transform, opacity;
}
.toast.is-bump { animation: toast-bump var(--dur-slow) var(--ease-standard); }
/* Dùng thuộc tính `scale` riêng, KHÔNG dùng `transform`: keyframe transform sẽ đè lên transition trượt vào
   (translateY) nếu bấm trùng lúc toast đang hiện ra, làm toast nhảy thẳng về chỗ cuối. Trình duyệt quá
   cũ chưa có `scale` riêng thì chỉ mất hiệu ứng nhún, không hỏng gì khác. */
@keyframes toast-bump {
  0%   { scale: 1; }
  40%  { scale: 1.025; }
  100% { scale: 1; }
}
.toast--cart    { --toast-accent: var(--koi-orange); --toast-tint: var(--koi-orange-tint); }
.toast--wish    { --toast-accent: var(--koi-red);    --toast-tint: var(--koi-red-tint); }
.toast--success { --toast-accent: var(--jade);       --toast-tint: var(--jade-tint); }
.toast--error   { --toast-accent: var(--koi-red);    --toast-tint: var(--koi-red-tint); }
/* Vàng quá nhạt để chữ trắng đọc được -> icon dùng chữ --ink trên nền vàng. */
.toast--warn    { --toast-accent: var(--gold);       --toast-tint: var(--gold-tint); --toast-on: var(--ink); --toast-line: var(--gold-text); }

.toast--coupon { --toast-accent: #f3d27f; --toast-tint: #12222c; --toast-on: #f3d27f; --toast-line: #e6c366; }
.toast.toast--coupon {
  color: #f7f5ef;
  border-color: rgba(243, 210, 127, .45);
  background: linear-gradient(135deg, #18323f, #0a141a);
  box-shadow: 0 16px 30px -12px rgba(10, 20, 26, .7);
}
.toast--coupon .toast-icon { background: rgba(243, 210, 127, .08); border: 1.5px solid #f3d27f; }
.toast--coupon .toast-title { font-family: var(--font-display); font-weight: 500; font-size: 15.5px; line-height: 1.25; }
.toast--coupon .toast-sub { color: #cdbf95; }
/* Toast mã giảm giá theo loại mã (class .vtone-* do main.js gắn; hội viên/không rõ loại giữ "Koi vàng" nền tối ở trên). */
.toast.toast--coupon:is(.vtone-pct, .vtone-fixed, .vtone-free, .vtone-new) {
  --toast-accent: #fff; --toast-tint: var(--vc-to); --toast-on: var(--vc); --toast-line: rgba(255, 255, 255, .85);
  color: #fff;
  border-color: rgba(255, 255, 255, .3);
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='18'%3E%3Cg fill='none' stroke='%23fff' stroke-opacity='.17' stroke-width='1.4'%3E%3Ccircle cx='18' cy='18' r='16'/%3E%3Ccircle cx='18' cy='18' r='11'/%3E%3Ccircle cx='18' cy='18' r='6'/%3E%3Ccircle cx='0' cy='9' r='16'/%3E%3Ccircle cx='0' cy='9' r='11'/%3E%3Ccircle cx='0' cy='9' r='6'/%3E%3Ccircle cx='36' cy='9' r='16'/%3E%3Ccircle cx='36' cy='9' r='11'/%3E%3Ccircle cx='36' cy='9' r='6'/%3E%3C/g%3E%3C/svg%3E"), linear-gradient(135deg, var(--vc-from), var(--vc-to));
  box-shadow: 0 16px 30px -12px color-mix(in srgb, var(--vc-deep) 70%, transparent);
}
.toast--coupon:is(.vtone-pct, .vtone-fixed, .vtone-free, .vtone-new) .toast-icon { background: rgba(255, 255, 255, .14); border-color: #fff; color: #fff; }
.toast--coupon:is(.vtone-pct, .vtone-fixed, .vtone-free, .vtone-new) .toast-sub { color: rgba(255, 255, 255, .9); }

.toast-icon {
  flex: none;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--toast-accent);
  color: var(--toast-on);
}
.toast-icon svg { width: 20px; height: 20px; }
.toast-thumb {
  position: relative;
  flex: none;
  width: 48px;
  height: 48px;
  border-radius: 10px;
  background: var(--paper-deep);
}
.toast-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 10px;
  -webkit-user-drag: none;
}
.toast-badge {
  position: absolute;
  right: -6px;
  bottom: -6px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--toast-accent);
  color: var(--toast-on);
  border: 2px solid var(--toast-tint);
}
.toast-badge svg { width: 11px; height: 11px; }
.toast-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.toast-title {
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1.35;
  overflow-wrap: anywhere;
}
.toast-sub {
  font-size: var(--fs-xs);
  line-height: 1.35;
  color: var(--text-muted);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2; /* tên sản phẩm dài + option: 2 dòng để không mất chữ, vẫn gọn */
  overflow: hidden;
  overflow-wrap: anywhere;
}
/* Chữ nút dùng --ink (đủ tương phản trên mọi nền tint); màu loại thể hiện qua gạch chân. */
.toast-action {
  flex: none;
  padding: 8px 4px;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  text-decoration: underline;
  text-decoration-color: var(--toast-line);
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
  transition: opacity var(--dur-fast) var(--ease-standard);
}
.toast-action:hover { opacity: .75; }
.toast-action:active { opacity: .6; }
/* Focus bàn phím: .toast có overflow:hidden nên viền vẽ BÊN TRONG (offset âm) để không bị cắt. */
.toast-action:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: -2px;
  border-radius: 6px;
  opacity: 1;
}
.toast-progress {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3px;
  background: var(--toast-line);
  transform-origin: left center;
  /* Mặc định rỗng: lúc gỡ .show (hết giờ/bị đẩy ra) animation mất, nếu mặc định là đầy thì thanh nháy đầy
     trở lại trong lúc toast còn đang mờ dần. Trước khi .show toast có opacity 0 nên không lộ thanh rỗng. */
  transform: scaleX(0);
}
/* Chạy từ lúc toast hiện (.show) — JS hẹn giờ tự tắt cùng thời điểm, --toast-ms do JS đặt theo loại. */
.toast.show .toast-progress { animation: toast-progress var(--toast-ms, 2800ms) linear forwards; }
.toast.is-paused .toast-progress { animation-play-state: paused; }
@keyframes toast-progress {
  from { transform: scaleX(1); }
  to   { transform: scaleX(0); }
}

/* Điện thoại, máy tính bảng và mọi thiết bị cảm ứng chính: toast ở GIỮA ngang (dưới header), co theo
   bề rộng màn hình (tối đa 440px trên máy tính bảng). Vẫn không dùng padding cho khung — toast đang rời
   đi dùng position:absolute nên cần khung không padding để căn đúng chỗ cũ. */
@media (max-width: 900px), (hover: none) and (pointer: coarse) {
  .toast-container { left: 12px; right: 12px; align-items: center; }
  .toast-slot { width: 100%; max-width: 440px; }
  .toast-slot.is-leaving { left: 0; right: 0; margin-left: auto; margin-right: auto; max-width: 440px; }
  .toast { width: 100%; max-width: none; }
  /* Vùng chạm tối thiểu ~44px cho nút "Xem giỏ hàng" trên cảm ứng; margin âm bù lại để toast không cao thêm. */
  .toast-action { padding: 14px 6px; margin: -5px 0; }
}

/* =========================== RESPONSIVE =========================== */
/* =========================== NAV DI ĐỘNG (HAMBURGER) =========================== */
/* Nút hamburger — ẩn mặc định trên desktop, chỉ hiện trong media query mobile bên dưới */
.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: #fff;
  color: var(--ink);
  cursor: pointer;
  margin-right: var(--space-1);
}
.nav-toggle:hover,
.nav-toggle:active { border-color: var(--koi-orange); color: var(--koi-orange); }
.nav-toggle .icon-close { display: none; }
.nav-toggle.is-active .icon-open { display: none; }
.nav-toggle.is-active .icon-close { display: block; }

/* Ô tìm kiếm nhân bản trong menu mobile — ẩn trên desktop vì đã có ô tìm kiếm ở header-row-1 */
.search-form-mobile { display: none; }

/* Lớp phủ tối đứng sau menu mobile — ẩn hẳn trên desktop (chỉ có tác dụng dưới 900px),
   mờ dần vào/ra cùng lúc với menu để không bị "giật" đóng/mở đột ngột. */
.nav-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(20, 26, 34, .5);
  z-index: calc(var(--z-sticky-header) + 5);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-slow) var(--ease-standard);
}
.nav-overlay.show { opacity: 1; pointer-events: auto; }

/* ===== Điều hướng mobile MỚI: bottom tab bar / bottom sheet Danh mục / FAB tìm
   kiếm — ẩn hẳn mặc định (chỉ hiện trong @media max-width:900px bên dưới), đúng
   quy ước đang dùng cho .nav-toggle/.search-form-mobile/.nav-overlay trong file
   này: khai báo "tắt" ở ngoài, "bật" trong media query, để không lỡ hiện nhầm ở
   desktop nếu ai đó xoá/đổi thứ tự media query sau này. ===== */
.mobile-bottom-nav,
.category-sheet,
.mobile-hero-search {
  display: none;
}

@media (max-width: 900px) {
  .nav-overlay { display: block; }
  .hero { min-height: 520px; }
  .hero .container { grid-template-columns: 1fr; padding-top: var(--space-7); padding-bottom: 56px; min-height: 520px; }
  .hero-art { order: -1; max-width: 260px; margin: 0 auto; }
  .hero-copy-glass { padding: 22px 20px; border-radius: var(--radius); }
  .hero-arrow { width: 38px; height: 38px; }
  .hero-arrow-prev { left: 10px; }
  .hero-arrow-next { right: 10px; }

  /* 2 nút CTA trong hero ("Xem sản phẩm nổi bật" / "Tư vấn miễn phí") — bản gốc
     dùng nguyên cỡ .btn desktop (padding 13px 26px, font 14.5px) rồi kéo
     flex:1 full-width ở @media 640px bên dưới, khiến 2 viên thuốc to/dày trên
     màn hẹp. Thu nhỏ riêng cho hero ở mọi mốc mobile (≤900px), KHÔNG đụng
     .btn dùng chung (nơi khác như .pd-actions/.wholesale-cta-actions vẫn giữ
     nguyên cỡ gốc) để tuyệt đối không ảnh hưởng desktop lẫn các khối nút khác. */
  .hero-ctas { gap: 10px; }
  .hero-ctas .btn {
    padding: 11px 20px;
    font-size: 13px;
    border-radius: var(--radius-pill);
  }
  .contact-wrap, .contact-wrap--split { grid-template-columns: 1fr; }
  .product-detail { grid-template-columns: minmax(0, 1fr); }
  .footer-grid { grid-template-columns: 1fr 1fr; }
  .search-form { display: none; }

  /* ---- Header mobile: gọn lại chỉ còn Logo (bỏ hamburger + account-cart-group,
     xem giải thích chi tiết ngay dưới) — điều hướng chuyển hẳn xuống bottom tab
     bar + bottom sheet + FAB tìm kiếm mới (xem khối ".mobile-bottom-nav /
     .category-sheet / .mobile-search-*" phía dưới file này). ---- */

  /* Hamburger (.nav-toggle) VÀ ô tìm kiếm/khối account-cart-group đều bị bỏ khỏi
     .header-row-1 trên mobile: .nav-toggle không còn rule "display:flex" nào ở
     đây nữa nên giữ nguyên display:none mặc định (định nghĩa gốc ở trên); tương
     tự .search-form vốn đã display:none sẵn tại mobile (dòng phía trên). Chỉ cần
     ẩn thêm .account-cart-group — 2 nút "Tài khoản"/"Giỏ hàng" giờ có mặt ở bottom
     tab bar rồi nên không cần lặp lại ở đây nữa. Ẩn luôn .header-actions (khối
     bọc ngoài search-form + account-cart-group) để không còn khoảng trống/gap flex
     thừa vô hình do 2 con bên trong đều display:none. */
  .account-cart-group { display: none; }
  .header-actions { display: none; }

  /* .header-row-2 (thanh nav ngang desktop, dùng chung markup .nav-item với sidebar
     admin) ẩn HẲN trên mobile — thay bằng bottom sheet "Danh mục". Ẩn thẳng qua
     CSS thay vì giữ rule position:fixed/transform kiểu off-canvas (không còn dùng
     tới ở mobile, dễ thành dead code). Markup .header-row-2 trong header.php vẫn
     giữ nguyên 100% cho desktop (>900px), chỉ ẩn qua CSS, không đổi HTML/JS. */
  .header-row-2 { display: none; }

  /* Header mobile khi cuộn (sticky) — phẳng, không bo góc: .site-header dính sát
     mép trên viewport lúc sticky nên border-radius/shadow/quầng sáng blur sẽ tạo
     cảm giác góc màn hình bị khoét tròn, lạc quẻ. Chỉ giữ 1 viền mảnh (hairline)
     phân định với nội dung phía dưới. */
  .site-header {
    border-bottom: 1px solid var(--line);
    border-radius: 0;
    box-shadow: none;
    background: var(--paper);
  }
  /* Toàn bộ chiều cao khối logo + search được rút gọn đáng kể ở mọi cấp (padding,
     cỡ icon, cỡ chữ, chiều cao ô tìm kiếm) — mục tiêu là 1 thanh mảnh, gọn, đứng
     yên tại mép trên khi cuộn mà không chiếm quá nhiều diện tích màn hình nhỏ. */
  .header-row-1 {
    justify-content: center;
    padding-top: 10px;
    padding-bottom: 8px;
  }
  .brand {
    margin-left: 0;
    gap: 9px;
    font-size: 17px;
  }
  .brand .mark { width: 32px; height: 32px; }
  .brand small {
    font-size: 8.5px;
    letter-spacing: .07em;
    margin-top: 1px;
  }

  /* ============================================================

     "THANH TRẮNG" Ở ĐÁY TRANG (đã xảy ra kể cả lúc đứng yên lẫn lúc bottom tab
     bar tự ẩn khi cuộn xuống) — nguyên nhân gốc & cách sửa triệt để tại đây:

     Bottom tab bar là position:fixed, không chiếm chỗ trong luồng layout, nên
     cần MỘT nơi duy nhất chừa khoảng trống bù lại đúng chiều cao của nó, để nội
     dung cuối trang (footer) không bị nó đè lên. Bản trước chừa khoảng đó bằng
     `padding-bottom` ngay trên <body> — nhưng <body> có nền màu giấy ngà
     (var(--paper)), trong khi phần tử NGAY TRƯỚC khoảng trống đó luôn là
     <footer> nền tối (var(--ink)). Kết quả: bất cứ khi nào tab bar không phủ
     kín khoảng trống này — lúc tự ẩn theo hướng cuộn (.mnb-hidden, xem main.js),
     hoặc trên các máy rộng 641–900px vốn CHỈ có rule này mà không có rule nào
     khác bù lại — một dải màu giấy ngà lạc tông lộ ra ngay dưới footer tối,
     đúng như ảnh chụp. Tệ hơn, có một lần sửa trước đã cộng THÊM đúng khoảng đó
     vào chính padding-bottom của .site-footer (xem @media max-width:640px) mà
     quên bỏ rule <body> này đi — nên trên phần lớn điện thoại (rơi vào nhánh
     ≤640px) khoảng trống bị CHỪA HAI LẦN chồng nhau, dải trắng vì vậy càng to,
     càng dễ thấy.

     Cách sửa ĐÚNG, chỉ cần một nơi chừa chỗ duy nhất: bỏ hẳn padding-bottom ở
     <body>, dồn toàn bộ khoảng chừa đó vào bên TRONG <footer> (xem rule
     `.site-footer` ngay dưới, áp dụng cho toàn bộ mobile chứ không riêng
     ≤640px nữa) — vì .site-footer có nền tối riêng phủ kín phần đệm của chính
     nó, nên dù tab bar đang hiện, đang tự ẩn, hay đang ở khổ máy nào trong dải
     mobile, khoảng dự trữ phía dưới luôn mang màu tối của footer, không bao giờ
     lộ dải màu lạc tông nữa. Đây cũng là lý do vấn đề "khoảng trắng lộ ra khi
     cuộn xuống đáy" (tab bar tự ẩn) tự hết theo — cùng một nguyên nhân, sửa một
     chỗ là hết cả hai. Giải pháp CSS thuần, không cần JS toggle theo trạng thái
     cuộn (cách đó từng gây giật khi cuộn nhanh do đổi chiều cao <body> — chính
     phần tử đang được cuộn — ngay giữa lúc cuộn).
     ============================================================ */

  /* .floating-social (nút "Lên đầu trang"/Facebook/Zalo nổi góc dưới-phải, xem
     includes/footer.php) neo bottom cộng thêm đúng chiều cao bottom tab bar để
     không bị tab bar đè lên/che khuất. Chỉ đổi "bottom", giữ nguyên "right"/
     kích thước/gap gốc (đã khai báo ở trên, ngoài mọi media query) — @media
     max-width:640px bên dưới sẽ ghi đè lại 1 lần nữa cho khớp với kích thước
     nút nhỏ hơn ở màn thật hẹp. */
  .floating-social {
    bottom: calc(var(--mobile-bottom-nav-h) + 22px + env(safe-area-inset-bottom, 0px));
  }
  /* Khi tab bar tự ẩn lúc cuộn xuống (.mnb-hidden, xem main.js) — main.js gắn
     kèm .fs-lowered lên đúng khối này, hạ "bottom" về lại đúng khoảng cách gốc
     KHÔNG cộng --mobile-bottom-nav-h nữa (tức về sát mép màn hình như khi
     hoàn toàn không có tab bar) — khớp transition var(--dur-slow) khai báo ở
     trên, tránh cảm giác 2 nút bị "treo lơ lửng" giữa khoảng trống do tab bar
     vừa biến mất để lại. */
  .floating-social.fs-lowered {
    bottom: calc(22px + env(safe-area-inset-bottom, 0px));
  }

  /* Chừa chỗ cho bottom tab bar NGAY BÊN TRONG footer (nền tối var(--ink)) thay
     vì ở <body> (nền giấy ngà) — xem giải thích đầy đủ ở khối comment "THANH
     TRẮNG Ở ĐÁY TRANG" phía trên. Áp dụng cho toàn bộ mobile (≤900px), không
     còn tách riêng ở @media ≤640px nữa để tránh chừa chỗ 2 lần chồng nhau. */
  .site-footer {
    position: relative; /* làm mốc định vị cho .footer-aquarium bên dưới */
    padding-bottom: calc(var(--mobile-bottom-nav-h) + env(safe-area-inset-bottom, 0px));
  }

  /* Bật + định vị dải mini hồ cá. .footer-aquarium (khối ngoài) cố ý cao HƠN
     khoảng đệm thật --mobile-bottom-nav-h khoảng 42px và chỉ mang gradient
     trong-suốt→màu nước: 42px dôi ra đó là vùng "loang màu" nằm đè lên phần
     dưới của .footer-bottom (nền vẫn là var(--ink) y hệt nên không ai nhận ra
     có 2 khối chồng nhau), giúp màu nước hiện ra dần dần thay vì bắt đầu bằng
     một đường thẳng cứng ngay mép trên khoảng đệm. .aq-scene (khối trong) mới
     đúng bằng khoảng đệm thật — mọi hoạt cảnh (sóng/cá/bong bóng/san hô/tảo)
     nằm gọn trong đó, không đổi vị trí/animation gì cả.
     .footer-bottom được nâng z-index để CHỮ luôn nổi rõ trên vùng loang màu
     (dù có đè lên cũng chỉ đè phần nền, không đè lên chữ). */
  .footer-bottom { position: relative; z-index: 1; }
  .footer-aquarium {
    display: block;
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: calc(var(--mobile-bottom-nav-h) + env(safe-area-inset-bottom, 0px) + 42px);
    overflow: visible;
    background: linear-gradient(180deg,
      transparent 0%,
      rgba(31, 111, 106, .16) 55%,
      rgba(31, 111, 106, .30) 78%,
      var(--ink) 100%);
    pointer-events: none;
  }
  .aq-scene {
    height: calc(var(--mobile-bottom-nav-h) + env(safe-area-inset-bottom, 0px));
  }

  /* ============================================================
     BOTTOM TAB BAR — thanh điều hướng chính cố định dưới đáy màn hình.
     ============================================================ */
  .mobile-bottom-nav {
    display: flex; /* ghi đè display:none mặc định (ngoài media query) — chỉ mobile mới hiện */
    align-items: stretch;
    position: fixed;
    left: 0; right: 0; bottom: 0;
    /* Quan trọng: vì "* { box-sizing: border-box }" áp dụng toàn site, height
       cần cộng thẳng safe-area vào (không chỉ gói trong --mobile-bottom-nav-h)
       để tổng chiều cao thanh luôn khớp 100% với khoảng <body> đã chừa — nếu
       không, trên máy có safe-area-inset-bottom > 0 (iPhone có thanh cử chỉ),
       tổng chiều cao thật của thanh sẽ ngắn hơn khoảng <body> đã chừa (xem
       "body" ở trên), để lộ ra 1 dải trống đúng bằng phần safe-area ở ngay
       dưới đáy thanh. padding-bottom giữ nguyên để đẩy icon/label lên khỏi
       vùng safe-area. */
    height: calc(var(--mobile-bottom-nav-h) + env(safe-area-inset-bottom, 0px));
    padding-bottom: env(safe-area-inset-bottom, 0px);
    /* Trước dùng var(--paper) — TRÙNG hệt màu nền <body>/trang, nên thanh gần
       như "tan biến" vào nội dung phía sau, thiếu nổi bật (đúng phản hồi thực
       tế). Đổi sang nền tối var(--ink) — đúng tông đã dùng cho header/hero/
       footer toàn site — để bottom bar đọc rõ như 1 dải điều hướng độc lập,
       tương phản mạnh với mọi trang nền sáng bên trên. Thêm backdrop-filter
       (kính mờ) + nền hơi ngả trong suốt để có chiều sâu thay vì 1 khối phẳng
       cứng; @supports fallback về nền đặc hoàn toàn cho trình duyệt cũ không
       hỗ trợ backdrop-filter. */
    /* Đổi từ rgba(18,34,44,...) (đúng var(--ink) — tông tối nhất site) sang
       rgba(37,63,77,...) (đúng var(--ink-soft)) theo phản hồi thực tế "nền hơi
       tối" — vẫn cùng họ xanh than tối để giữ vai trò dải điều hướng độc lập/
       tương phản mạnh với nền sáng phía trên, chỉ sáng hơn 1 bậc cho dễ nhìn. */
    background: rgba(37, 63, 77, .97);
    border-top: 1px solid rgba(247, 245, 239, .08);
    box-shadow: 0 -10px 26px -12px rgba(18, 34, 44, .55);
    z-index: calc(var(--z-sticky-header) + 6);
    transform: translateY(0);
    /* Chỉ 1 thuộc tính transform, dùng var(--dur-slow)/var(--ease-standard) —
       PHẢI khớp đúng với transition "bottom" của .floating-social phía trên (xem
       comment ở đó) để 2 khối trượt lên/xuống ăn khớp, không gây reflow vì
       transform không nằm trong luồng layout. */
    transition: transform var(--dur-slow) var(--ease-standard);
  }
  /* Trạng thái ẩn khi cuộn xuống — JS (main.js) toggle class này theo hướng cuộn,
     khoá không cho toggle khi bottom sheet Danh mục đang mở (xem JS). */
  .mobile-bottom-nav.mnb-hidden { transform: translateY(100%); }

  @supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
    .mobile-bottom-nav {
      /* Cùng var(--ink-soft) như fallback ở trên, chỉ giảm alpha để lộ kính mờ. */
      background: rgba(37, 63, 77, .86);
      backdrop-filter: blur(16px) saturate(1.3);
      -webkit-backdrop-filter: blur(16px) saturate(1.3);
    }
  }

  .mnb-item {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    padding: 6px 2px 0;
    border: none;
    background: none;
    font-family: inherit;
    cursor: pointer;
    /* Trước dùng var(--text-muted) (#7c8794) — vốn tính cho nền sáng, quá tối/chìm
       trên nền tối mới. Đổi sang giấy ngà mờ để icon/label vẫn rõ nhưng không
       cạnh tranh với trạng thái "đang chọn". */
    color: rgba(247, 245, 239, .62);
    transition: color var(--dur-fast) var(--ease-standard);
  }
  /* position:relative để .mnb-ping (ping toả tròn khi bấm, xem bên dưới) định vị
     tuyệt đối đúng tâm 2 nút toggle (Danh mục/Giỏ hàng) — 2 nút DUY NHẤT thật sự
     mở bottom sheet, "Trang chủ"/"Tài khoản" là link điều hướng thẳng nên không
     cần hiệu ứng này. */
  .mnb-item-toggle { position: relative; }
  .mnb-icon { width: 21px; height: 21px; display: flex; }
  .mnb-icon svg { width: 100%; height: 100%; display: block; }
  /* Icon hơi co lại + mờ đi trong lúc sheet tương ứng đang mở — gợi ý trực quan
     "nút này vừa phồng to biến thành khung sheet phía trên", đúng cảm giác nút
     FAB bị "nhấn giữ" trong Material Design khi container transform đang diễn ra.
     Tái dùng ĐÚNG class .active mà createMiniSheet() (main.js) đã gán/gỡ sẵn lúc
     mở/đóng sheet (không phải .active tô màu do PHP set theo route $active — dù
     trùng tên class, cùng lúc TRÙNG luôn ngữ nghĩa "nút này đang là nguồn của
     sheet đang mở", nên dùng chung 1 class là hợp lý, không cần thêm class mới). */
  /* Trước dùng chung 1 easing lò xo có bung nảy (cubic-bezier(.34,1.56,.64,1))
     cho cả 2 chiều — khác hẳn đường cong phẳng-điềm tĩnh của .category-sheet
     đang morph cùng lúc ngay cạnh đó, khiến 2 chuyển động tuy xảy ra quanh
     đúng 1 điểm nhưng như 2 "chất liệu" khác nhau (1 bên lò xo nảy, 1 bên
     phẳng đều). Đổi sang dùng lại ĐÚNG 2 biến --ease-sheet-in/out mà sheet
     đang dùng, tách theo hướng (icon co lại khi sheet MỞ dùng ease-sheet-in,
     icon phục hồi khi sheet ĐÓNG dùng ease-sheet-out) — bỏ hẳn overshoot,
     tham khảo cảm giác phẳng, tinh tế của icon trong sheet iOS thay vì spring
     kiểu Material trước đó. */
  .mnb-item-toggle.active .mnb-icon {
    transition: transform .3s var(--ease-sheet-in), opacity .22s var(--ease-sheet-in);
    transform: scale(.74);
    opacity: .5;
  }
  .mnb-item-toggle .mnb-icon {
    /* Cùng thời lượng với chiều mở (.3s/.22s), chỉ đảo easing — xem giải
       thích --ease-sheet-out tại :root. */
    transition: transform .3s var(--ease-sheet-out), opacity .22s var(--ease-sheet-out);
  }

  /* Ping toả tròn từ tâm nút toggle lúc mở (loang ra + mờ dần) / lúc đóng (co
     ngược lại vào tâm) — đúng hiệu ứng "origin-ping" trong file demo tham khảo,
     đánh dấu rõ ràng ĐÂY là điểm mà sheet vừa "phát sinh ra"/"thu về". Nằm NGOÀI
     .mnb-icon (là con trực tiếp của .mnb-item-toggle) để không bị cắt bởi
     overflow/kích thước 21px nhỏ của icon khi phóng to nhiều lần lúc animate. */
  .mnb-ping {
    position: absolute;
    top: 50%; left: 50%;
    width: 10px; height: 10px;
    margin: -5px 0 0 -5px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(217, 102, 59, .85), rgba(217, 102, 59, 0) 70%);
    opacity: 0;
    transform: scale(0);
    pointer-events: none;
  }
  /* Dùng lại ĐÚNG --ease-sheet-in/out để ping cùng "giọng" chuyển động với
     sheet/icon đang animate song song. Cùng thời lượng .5s cho cả 2 chiều:
     --ease-sheet-out là đúng đảo ngược toán học của --ease-sheet-in (xem
     :root) nên không cần "bù" thời lượng để 2 chiều đối xứng thật sự. */
  .mnb-ping.mnb-ping-out { animation: mnbPingOut .5s var(--ease-sheet-in) forwards; }
  .mnb-ping.mnb-ping-in { animation: mnbPingIn .5s var(--ease-sheet-out) forwards; }
  @keyframes mnbPingOut {
    0% { transform: scale(0); opacity: .9; }
    60% { transform: scale(3.2); opacity: .22; }
    100% { transform: scale(3.8); opacity: 0; }
  }
  @keyframes mnbPingIn {
    0% { transform: scale(3.8); opacity: 0; }
    40% { transform: scale(2.8); opacity: .22; }
    100% { transform: scale(0); opacity: .9; }
  }
  @media (prefers-reduced-motion: reduce) {
    .mnb-ping.mnb-ping-out, .mnb-ping.mnb-ping-in { animation: none; display: none; }
  }
  .mnb-label {
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: .01em;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* Trạng thái đang chọn — trước dùng var(--jade) (đúng quy ước "đang chọn" ở
     những nơi nền SÁNG của site), nhưng jade (#1f6f6a) lên nền tối var(--ink)
     (#12222c) mới của thanh này lại có độ tương phản thấp (2 tông tối gần
     nhau), khó phân biệt mục đang chọn. Đổi riêng cho tab bar tối này sang
     koi-orange — vẫn là màu thương hiệu, nhưng nổi rõ ràng trên nền tối, đồng
     thời tạo điểm nhấn ấm để cân bằng với dải kính mờ lạnh phía sau. Áp dụng
     cho cả icon lẫn label cùng lúc vì cả 2 đều dùng currentColor/kế thừa màu chữ. */
  .mnb-item.active { color: var(--koi-orange); }
  .mnb-item:not(.active):active { color: var(--paper); }

  /* Badge số lượng giỏ hàng — nổi ở góc trên-phải icon giỏ hàng thay vì nằm cạnh
     chữ như bản .acg-cart desktop. Tái dùng nguyên class .cart-badge (màu/kiểu chữ
     đồng bộ, main.js cũng tự cập nhật MỌI phần tử .cart-badge khi giỏ hàng đổi số
     lượng — xem hàm updateCartBadge — nên badge ở đây tự động khớp theo, không cần
     sửa gì thêm trong JS), chỉ định vị lại + thu nhỏ bằng .mnb-cart-badge (selector
     đơn lớp nhưng đứng SAU .cart-badge trong file nên thắng theo thứ tự khai báo). */
  .mnb-icon-has-badge { position: relative; }
  .mnb-cart-badge {
    position: absolute;
    top: -4px;
    right: -9px;
    min-width: 16px;
    height: 16px;
    font-size: 10px;
    padding: 0 3px;
    /* Viền mảnh cùng tông nền thanh — tách badge cam ra khỏi icon rõ ràng hơn
       trên nền tối bán trong suốt mới (trước không cần vì nền sáng đặc, badge
       tự nổi khối sẵn). */
    box-shadow: 0 0 0 2px rgba(18, 34, 44, .9);
  }

  /* ============================================================
     BOTTOM SHEET "DANH MỤC" — dừng NGAY TRÊN bottom tab bar (bottom:
     var(--mobile-bottom-nav-h) + safe-area, không phải bottom:0) để tab bar
     không bao giờ bị sheet che khuất — khách vẫn luôn bấm lại được nút
     "Danh mục" để đóng sheet trong lúc sheet đang mở, đúng yêu cầu "thanh luôn
     hiện". Cộng thêm env(safe-area-inset-bottom) vào bottom offset (khớp với
     cách .mobile-bottom-nav tự cộng safe-area vào height ở trên) để trên các
     máy có home-indicator (safe-area > 0), mép dưới sheet lúc MỞ luôn đứng
     đúng sát mép TRÊN thật của tab bar, không bị lún xuống đè lên phần
     safe-area của tab bar.

     HIỆU ỨNG MỞ/ĐÓNG — "MATERIAL MORPH" PHÌNH ĐỀU TỪ TÂM NÚT DANH MỤC:
     Dùng kỹ thuật scale ĐỀU (uniform, scaleX=scaleY luôn) xuất phát từ 1 ĐIỂM
     gốc duy nhất đặt tại tâm nút toggle — đúng kiểu "container transform" của
     Material Design — thay vì FLIP (translate + scaleX/scaleY LỆCH NHAU để
     khớp hình chữ nhật của nút): scale lệch từng trục sẽ làm border-radius bị
     méo thành ellipse giữa chừng nội suy, trông gợn. Vì tỉ lệ scale đều nhau
     ở mọi hướng, transform:scale() tự lo phần "tỉ lệ bo góc luôn nhất quán
     với kích thước" — CSS chỉ cần khai báo đúng 2 giá trị border-radius ở 2
     ĐẦU MÚT (đóng hẳn: 20px đều 4 góc; mở hẳn: 20px 20px 0 0, 2 góc dưới vuông
     khớp mép tab bar), không cần tự tính toán bo góc theo scale.
     JS (main.js — createMiniSheet) chỉ có 2 việc: (1) đo tâm nút toggle
     (getBoundingClientRect) rồi gán vào 2 biến CSS --morph-ox/--morph-oy (toạ
     độ px tính từ góc trên-trái của chính sheet) ngay trước khi thêm/gỡ class
     .sheet-open — dùng làm transform-origin; (2) trong lúc kéo tay, tự thao
     tác trực tiếp lên transform/opacity/border-radius theo đúng vị trí ngón
     tay (xem "Vuốt/kéo thanh kéo" trong main.js). Ngoài 2 việc đó, toàn bộ
     phần "phồng to/thu nhỏ" (scale, opacity, border-radius) do CSS transition
     dưới đây đảm nhiệm khi class .sheet-open được thêm/gỡ.
     "visibility: 0s linear <thời lượng transform>" (chỉ áp dụng chiều đóng, nhờ
     .sheet-open ghi đè delay về 0) là mẹo chuẩn để trì hoãn việc ẩn hẳn
     (visibility:hidden — thứ sẽ cắt ngang animation ngay lập tức nếu áp dụng
     tức thời) cho tới khi hiệu ứng thu nhỏ TRÔNG THẤY đã chạy xong hẳn, thay vì
     phải nghe sự kiện transitionend bằng JS như cách cũ.

     BO GÓC 2 GÓC DƯỚI (vuông <-> tròn) — NGUYÊN TẮC CHUNG áp dụng nhất quán ở
     cả 2 nơi animate góc này (CSS transition bên dưới CHO tap/bấm nút/nền/
     Escape, VÀ inline style JS trong lúc kéo tay — xem touchmove trong
     main.js): bán kính khai báo và scale hiển thị là 2 đại lượng NHÂN với
     nhau ra bán kính THẤY ĐƯỢC trên màn hình — nếu để cả 2 cùng nội suy tuyến
     tính suốt toàn bộ animation, tích của chúng nhỏ hơn hẳn 20px trong phần
     lớn thời lượng, mắt thấy góc "gần như vuông" suốt animation, chỉ tròn hẳn
     ở khung hình cuối. Vì vậy border-radius LUÔN được xử lý TÁCH RIÊNG khỏi
     scale: chốt nhanh về đúng 20px (khớp góc trên) trong 1 ĐOẠN NGẮN đầu quá
     trình đóng (hoặc cuối quá trình mở), rồi GIỮ NGUYÊN 20px suốt phần còn
     lại — để một mình transform:scale() lo tiếp phần "tỉ lệ bo/kích-thước"
     luôn nhất quán trong khi kích thước tiếp tục biến đổi. Bằng cách này góc
     bo luôn rõ, không "lép" mà cũng không giật đột ngột — chi tiết thời lượng
     cụ thể xem tại từng khai báo transition bên dưới. */
  .category-sheet {
    display: flex; /* ghi đè display:none mặc định — chỉ mobile mới hiện */
    flex-direction: column;
    position: fixed;
    left: 0; right: 0;
    bottom: calc(var(--mobile-bottom-nav-h) + env(safe-area-inset-bottom, 0px));
    max-height: 58vh;
    background: var(--paper);
    /* border-radius KHÔNG cố định "20px 20px 0 0" nữa — xem chi tiết ngay
       dưới khối transition (2 góc dưới chỉ vuông hẳn khi sheet đã MỞ HẲN,
       còn lại (đóng hẳn + toàn bộ quá trình đang morph) bo tròn cả 4 góc). */
    border-radius: 20px;
    box-shadow: 0 -14px 34px -16px rgba(18, 34, 44, .35);
    z-index: calc(var(--z-sticky-header) + 7);
    overflow: hidden; /* .category-sheet-draghandle đứng yên, chỉ .category-sheet-body cuộn */
    /* FIX lỗi "góc vẫn vuông trong lúc mở/đóng": đây là 1 bug rendering rất phổ
       biến của Safari/WebKit (và thỉnh thoảng cả Chrome mobile) — khi 1 phần tử
       có ĐỒNG THỜI border-radius + overflow:hidden + đang chạy transform (ở đây
       là scale), trình duyệt không cập nhật lại clip-mask bo góc theo kịp từng
       khung hình transform: nội dung/nền bị cắt theo hình VUÔNG của lúc trước đó
       thay vì đúng border-radius hiện tại, chỉ "chốt" lại đúng hình bo tròn ở
       khung hình CUỐI CÙNG khi transform dừng hẳn — quan sát bằng mắt giống hệt
       như "góc lúc nào cũng vuông trong lúc animate, chỉ tròn đúng lúc đứng yên"
       dù CSS transition border-radius phía dưới hoàn toàn đúng và đang chạy.
       isolation:isolate ép trình duyệt tạo 1 stacking context/compositing layer
       riêng cho .category-sheet, buộc nó tính lại đúng clip-mask bo góc ở MỌI
       khung hình transform thay vì dùng lại mask cũ — cách sửa được xác nhận
       hoạt động ổn định trên mọi trình duyệt hiện đại (không chỉ riêng Safari),
       và file này đã dùng đúng kỹ thuật này ở vài nơi khác (.site-header,
       .trust-strip...) nên nhất quán với phần còn lại của codebase. */
    isolation: isolate;
    /* Báo trước cho trình duyệt 2 thuộc tính sẽ animate liên tục (transform +
       opacity) để nó chủ động lên layer GPU riêng NGAY TỪ ĐẦU thay vì đợi tới
       khung hình đầu tiên của animation mới lên layer (thường gây 1 khung hình
       giật nhẹ, rõ nhất ở lần mở đầu tiên sau khi tải trang) — cộng thêm hiệu
       ứng phụ tích cực: có sẵn layer riêng cũng CỦNG CỐ thêm cho fix bo góc ở
       trên (isolation:isolate lo phần đúng-đắn/chính xác của layer, will-change
       lo phần tạo layer SỚM/chủ động để không bị giật khung hình đầu). */
    will-change: transform, opacity;
    /* Trạng thái đóng: co nhỏ gần như 1 điểm (scale .045 ~ 2-3px trên sheet rộng
       ~340px, đủ nhỏ để trông như "phát ra từ 1 điểm" mà vẫn tránh scale(0) —
       scale(0) khiến trình duyệt drop hẳn phép biến đổi trên 1 số bản Safari
       cũ, gây giật ở khung hình đầu). --morph-ox/--morph-oy do JS gán ngay
       trước khi mở/đóng (xem main.js); giá trị mặc định 50%/100% (giữa-đáy)
       chỉ là phòng hờ cho lần render đầu tiên trước khi JS kịp chạy. */
    transform: scale(.045);
    transform-origin: var(--morph-ox, 50%) var(--morph-oy, 100%);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    /* ĐÓNG — dùng --ease-sheet-drag-release (giảm tốc thuần) thay vì
       --ease-sheet-out (nghịch đảo thời gian của --ease-sheet-in, xem :root):
       đảo ngược đường cong "bung nhanh, hãm êm cuối" của chiều mở ra lại
       thành "đứng yên đầu, lao nhanh cuối" — khựng rõ nhất ở khung hình đầu
       tiên, đúng lúc mắt nhìn kỹ nhất. Áp dụng cho MỌI cách đóng (nút, nền,
       Escape, kéo tay) để cùng 1 cảm giác mượt; JS không cần override
       transition thủ công. Giữ nguyên thời lượng .4s/.3s, chỉ đổi hình dạng
       gia tốc. */
    /* border-radius: xem NGUYÊN TẮC CHUNG ở khối comment đầu file (BOTTOM
       SHEET DANH MỤC). Chiều ĐÓNG: chốt nhanh 20px (khớp góc trên) trong .07s
       NGAY TỪ ĐẦU (delay 0s) — đối xứng với đúng .07s "chốt vuông" ở CUỐI
       chiều mở (xem .sheet-open bên dưới), chỉ khác vị trí (đóng: đầu quá
       trình; mở: cuối quá trình). */
    transition:
      transform .4s var(--ease-sheet-drag-release),
      opacity .3s var(--ease-sheet-drag-release),
      border-radius .07s linear 0s,
      visibility 0s linear .4s;
  }
  .category-sheet.sheet-open {
    transform: scale(1);
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    border-radius: 20px 20px 0 0;
    /* border-radius: xem NGUYÊN TẮC CHUNG ở khối comment đầu file. Chiều MỞ:
       giữ nguyên 20px (khớp góc trên) suốt lúc phồng to, chỉ "chốt" vuông 2
       góc dưới trong .07s cuối cùng (delay .33s trong tổng .4s) — giống cảm
       giác 1 viên bi tròn lớn dần rồi "đáp/khớp" phẳng xuống tab bar đúng lúc
       dừng hẳn. */
    transition:
      transform .4s var(--ease-sheet-in),
      opacity .3s var(--ease-sheet-in),
      border-radius .07s linear .33s,
      visibility 0s linear 0s;
  }

  /* Vùng kéo để đóng sheet — bao trọn thanh kéo nhỏ + chữ "Danh mục" thành 1 khối
     duy nhất, trải hết chiều rộng sheet và cao hẳn hơn (~48px vùng chạm thực) thay
     vì chỉ riêng thanh kéo 5px như trước (rất khó vuốt trúng bằng ngón tay). Toàn
     bộ khối này là mục tiêu touchstart/touchmove/touchend trong main.js (đổi từ
     "chỉ thanh kéo" sang "cả header" — xem #categorySheetHandle). touch-action:none
     ngăn trình duyệt tự diễn giải cử chỉ vuốt trong vùng này thành cuộn/kéo-refresh
     trang, user-select:none tránh việc vuốt qua chữ "Danh mục" vô tình bôi đen chọn
     văn bản trên 1 số trình duyệt Android. */
  .category-sheet-draghandle {
    display: flex;
    flex-direction: column;
    align-items: center;
    flex-shrink: 0;
    padding: 8px 0 4px;
    touch-action: none;
    -webkit-user-select: none;
    user-select: none;
    cursor: grab;
  }
  .category-sheet-draghandle-bar {
    width: 40px;
    height: 5px;
    border-radius: var(--radius-pill);
    background: var(--line);
    flex-shrink: 0;
    display: block;
  }
  .category-sheet-title {
    text-align: center;
    font-size: var(--fs-sm);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--text-muted);
    padding: 8px 0 6px;
    flex-shrink: 0;
  }
  .category-sheet-body {
    overflow-y: auto;
    /* Cuộn mượt kiểu quán tính trên iOS Safari + không cho cuộn hết đà "tràn" ra
       ngoài cuộn trang nền phía sau. */
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    padding: 4px 16px calc(20px + env(safe-area-inset-bottom, 0px));
  }

  /* Lưới danh mục — 3 cột icon vuông bo góc, kiểu "quick access grid" thường thấy
     trong app đặt đồ ăn/giao hàng, gọn và dễ bấm hơn trên màn hẹp so với liệt kê
     dọc từng dòng dài như menu off-canvas cũ. */
  .sheet-cat-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 6px 10px;
  }
  .sheet-cat-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 12px 4px;
    border-radius: 14px;
    text-align: center;
    transition: background var(--dur-fast) var(--ease-standard);
  }
  .sheet-cat-item:hover, .sheet-cat-item:active { background: rgba(18, 34, 44, .05); }
  .sheet-cat-icon {
    width: 46px;
    height: 46px;
    border-radius: 14px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--paper-deep);
    color: var(--ink-soft);
    flex-shrink: 0;
  }
  .sheet-cat-icon svg { width: 22px; height: 22px; display: block; }
  /* Đang xem đúng danh mục này — chip icon chuyển sang nền jade đặc, đúng ngôn
     ngữ màu "jade = đang chọn" nhất quán với toàn site. */
  .sheet-cat-item.active .sheet-cat-icon { background: var(--jade); color: #fff; }
  .sheet-cat-label {
    font-size: 11.5px;
    font-weight: 600;
    color: var(--ink);
    line-height: 1.25;
    /* Giới hạn 2 dòng, cắt "..." nếu tên danh mục quá dài thay vì phá lưới 3 cột */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  /* "Tất cả sản phẩm" — hàng link riêng trải hết chiều rộng, đứng NGOÀI lưới ô
     vuông danh mục (không còn là 1 ô trong .sheet-cat-grid như trước) để không bị
     nhầm là 1 danh mục sản phẩm cụ thể trong số các danh mục — cách trình bày này
     gần với vai trò "mục dẫn tới trang tổng hợp tất cả sản phẩm" mà nó vốn có trên
     thanh nav desktop. Nền cam đặc + icon trắng giữ nguyên ngôn ngữ màu "cam = mục
     ghim/tổng hợp" đã dùng cho ô "Tất cả sản phẩm" kiểu cũ (.sheet-cat-icon--all). */
  .sheet-viewall {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    margin-bottom: 10px;
    border-radius: 14px;
    background: var(--koi-orange);
    color: #fff;
    transition: opacity var(--dur-fast) var(--ease-standard);
  }
  .sheet-viewall:hover, .sheet-viewall:active { opacity: .9; }
  .sheet-viewall.active { background: var(--jade); }
  .sheet-viewall-icon {
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .sheet-viewall-icon svg { width: 20px; height: 20px; display: block; }
  .sheet-viewall-label {
    flex: 1;
    font-size: 14px;
    font-weight: 700;
  }
  .sheet-viewall-arrow { width: 16px; height: 16px; flex-shrink: 0; opacity: .85; }
  .sheet-viewall-arrow svg { width: 100%; height: 100%; display: block; }

  /* Hỗ trợ (Kinh nghiệm / Tra cứu đơn hàng / Liên hệ) — tách khỏi lưới danh mục
     sản phẩm bằng 1 đường kẻ trên + nhãn nhỏ "Hỗ trợ", nhưng bên trong dùng lại
     NGUYÊN lưới ô vuông .sheet-cat-grid (đồng bộ thị giác ngang hàng với danh mục
     ở trên) thay vì danh sách dọc như trước. */
  .sheet-links {
    margin-top: 10px;
    padding-top: 12px;
    border-top: 1px solid var(--line);
  }
  .sheet-links-title {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--text-muted);
    padding: 0 4px 8px;
  }

  /* ============================================================
     BOTTOM SHEET "GIỎ HÀNG" — dùng chung khung .category-sheet (position/kích
     thước/hiệu ứng morph mở-đóng, xem HTML #cartSheet trong header.php gán cả 2
     class "category-sheet cart-sheet" cùng lúc) nên KHÔNG cần định nghĩa lại
     position/transform/visibility ở đây — chỉ style riêng phần NỘI DUNG (danh
     sách sản phẩm, dòng tóm tắt, nút thanh toán), tương tự cách .cart-prod/
     .cart-table/.cart-summary style nội dung trang giỏ hàng đầy đủ (cart.php)
     nhưng thu gọn hơn nhiều cho vừa 1 sheet nhỏ ("đầy đủ thông tin, ngắn gọn"
     theo đúng yêu cầu — ảnh + tên + option + đơn giá x số lượng + tổng dòng +
     nút xoá, không có thêm cột/label rườm rà như bản bảng đầy đủ). */
  .cart-sheet-list { display: flex; flex-direction: column; gap: 14px; }
  .cart-sheet-item {
    display: flex;
    align-items: flex-start;
    gap: 10px;
  }
  .cart-sheet-item-img {
    flex-shrink: 0;
    width: 56px;
    height: 56px;
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--paper-deep);
    display: block;
  }
  .cart-sheet-item-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .cart-sheet-item-info { flex: 1; min-width: 0; }
  .cart-sheet-item-name {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--ink);
    line-height: 1.35;
  }
  .cart-sheet-item-name:hover,
  .cart-sheet-item-name:active { color: var(--koi-orange); }
  .cart-sheet-item-variant { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
  .cart-sheet-item-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-top: 6px;
  }
  .cart-sheet-item-price { font-size: var(--fs-sm); font-weight: 700; color: var(--koi-orange); white-space: nowrap; }
  /* Bản thu gọn của .qty-box (dùng cho trang giỏ hàng đầy đủ) — nút/ô nhập nhỏ
     hơn 1 chút cho vừa dòng sản phẩm hẹp trong sheet. */
  .qty-box-sm button { width: 26px; height: 26px; font-size: 13px; }
  /* 16px (không nhỏ hơn) để iOS Safari không tự zoom trang khi chạm vào ô số lượng; ô rộng
     hơn 1 chút (28→34px) để vẫn đủ chỗ cho số 2–3 chữ số ở cỡ chữ lớn hơn. */
  .qty-box-sm input { width: 34px; font-size: 16px; }
  .cart-sheet-item-remove {
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    border: none;
    background: none;
    color: var(--text-muted);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    border-radius: 50%;
    transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
  }
  .cart-sheet-item-remove:hover,
  .cart-sheet-item-remove:active { background: var(--paper-deep); color: var(--koi-red); }

  .cart-sheet-empty { text-align: center; padding: 36px 20px 24px; }
  .cart-sheet-empty-icon { color: var(--jade); margin-bottom: 10px; }
  .cart-sheet-empty-title { font-weight: 600; margin-bottom: 14px; color: var(--ink); }

  /* Footer đứng NGOÀI vùng cuộn (.cart-sheet-body) — cùng nguyên tắc thanh kéo ở
     trên: luôn đứng yên, không bị cuốn theo khi danh sách sản phẩm dài phải cuộn.
     Sinh ra sau .category-sheet-body trong DOM (JS append trực tiếp vào #cartSheet
     — xem renderCartSheetFooter trong main.js) nên tự nằm dưới cùng theo flex-
     direction:column có sẵn của .category-sheet, không cần thêm order/position. */
  .cart-sheet-footer {
    flex-shrink: 0;
    padding: 12px 20px calc(16px + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--line);
    background: var(--paper);
  }
  .cart-sheet-summary {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: var(--fs-sm);
    font-weight: 600;
    margin-bottom: 10px;
  }
  .cart-sheet-summary span:last-child { color: var(--koi-orange); font-size: 16px; font-weight: 700; }

  /* ============================================================
     BOTTOM SHEET "TÀI KHOẢN" (mở nhanh từ bottom-nav mobile) — dùng chung khung
     .category-sheet (position/kích thước/hiệu ứng morph mở-đóng/overlay/khoá
     cuộn, xem HTML #accountMiniSheet trong header.php gán 2 class
     "category-sheet account-mini-sheet" cùng lúc) nên KHÔNG khai báo
     lại position/transform/visibility ở đây — CHỈ style phần NỘI DUNG bên
     trong (avatar/tên/hạng, dòng điểm thưởng, danh sách nav, link Đăng xuất,
     2 nút Đăng nhập/Đăng ký khi chưa đăng nhập).

     Lấy cảm hứng bố cục/tông màu từ .account-sidebar-profile / .account-avatar
     / .account-tier-badge / .account-nav-item / .account-nav-logout
     (account.php, xem phía trên file) nhưng viết CLASS RIÊNG HOÀN TOÀN
     (account-mini-sheet-*) — KHÔNG sửa/xoá bất kỳ rule nào của các class đó,
     chỉ tham khảo lại đúng token màu/spacing/bo góc (var(--...)) đang dùng
     chung toàn site để 2 nơi trông đồng bộ dù độc lập nhau.
     ============================================================ */

  /* Khách CHƯA đăng nhập — 2 nút Đăng nhập/Đăng ký (PHP render sẵn),
     tái dùng nguyên .btn/.btn-primary/.btn-outline/.btn-block đã có sẵn toàn
     site nên chỉ cần lo khoảng cách giữa 2 nút ở đây. */
  .account-mini-sheet-guest {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 8px 4px 4px;
  }

  /* Trạng thái "Đang tải..." ban đầu (PHP render sẵn) và trong lúc JS đang chờ
     fetch "nav_summary" xong lần đầu. KHÔNG còn là text căn giữa nữa — xem
     FIX chi tiết tại includes/header.php (nơi dựng khối skeleton này): bản thân
     .account-mini-sheet-loading giờ chỉ là khối bọc ngoài, không tự thêm
     padding/text-align riêng — mọi khoảng cách đã do CHÍNH các class thật
     (.account-mini-sheet-profile/-points/-nav-item/-logout) mà các dòng
     skeleton bên trong tái dùng đảm nhiệm, để tổng chiều cao khớp với nội dung
     thật sau khi tải xong. */
  .account-mini-sheet-loading { display: block; }

  /* .account-mini-sheet-skel: nền xám nhấp nháy dùng chung cho MỌI ô skeleton.
     Đặt RIÊNG khỏi class định hình thật (vd .account-mini-sheet-avatar,
     .account-mini-sheet-nav-icon) để không đụng width/height/border-radius sẵn
     có của các class đó — skeleton chỉ "nhuộm màu" lên đúng khung hình đã có,
     không tự ý đổi kích thước. animation dùng đúng cơ chế "*:not(.category-sheet)"
     trong khối @media (prefers-reduced-motion: reduce) phía trên (không phải
     .category-sheet nên tự bị tắt animation ở đó, không cần khai riêng). */
  .account-mini-sheet-skel {
    background: var(--paper-deep);
    animation: account-mini-sheet-skel-pulse 1.4s ease-in-out infinite;
  }
  .account-mini-sheet-nav-icon.account-mini-sheet-skel { border-radius: 5px; }

  /* .account-mini-sheet-skel-bar: dùng cho các ô THAY CHO 1 DÒNG CHỮ (tên,
     hạng, điểm thưởng, nhãn nav, đăng xuất — vốn không có kích thước cố định
     sẵn như avatar/icon) — 1 thanh bo tròn cao ~1 dòng chữ, RIÊNG width chỉnh
     theo từng vị trí bên dưới để trông tự nhiên như đang chờ tải, không ảnh
     hưởng tới chiều cao tổng (yếu tố quyết định origin animation morph). */
  .account-mini-sheet-skel-bar {
    display: block;
    width: 100%;
    height: 14px;
    border-radius: var(--radius-sm);
  }
  .account-mini-sheet-skel-name { width: 55%; height: 16px; margin-bottom: 6px; }
  .account-mini-sheet-skel-tier { width: 34%; height: 17px; border-radius: var(--radius-pill); }
  .account-mini-sheet-skel-points { width: 46%; }
  .account-mini-sheet-skel-logout { width: 30%; }

  @keyframes account-mini-sheet-skel-pulse {
    0%, 100% { opacity: .55; }
    50% { opacity: 1; }
  }

  /* Fetch lỗi/mất mạng (renderAccountMiniSheetError() trong main.js) —
     cùng size/màu trung tính với .account-mini-sheet-loading, không dùng đỏ
     gắt để tránh cảm giác lỗi nghiêm trọng cho 1 sheet chỉ xem nhanh. */
  .account-mini-sheet-error {
    padding: 32px 4px;
    text-align: center;
    font-size: var(--fs-sm);
    color: var(--text-muted);
  }

  /* Khối avatar tròn (chữ cái đầu tên) + tên khách + badge hạng — cùng
     kích thước/token với .account-avatar/.account-tier-badge (account.php). */
  .account-mini-sheet-profile {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 4px var(--space-2) var(--space-4);
  }
  .account-mini-sheet-avatar {
    flex-shrink: 0;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: var(--koi-orange);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-display);
    font-size: var(--fs-lg);
    font-weight: 700;
  }
  .account-mini-sheet-info { min-width: 0; }
  .account-mini-sheet-name {
    font-weight: 700;
    color: var(--ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .account-mini-sheet-tier {
    display: inline-block;
    margin-top: 3px;
    font-size: 10.5px;
    font-weight: 700;
    color: var(--text-muted);
    background: var(--paper-deep);
    border-radius: var(--radius-pill);
    padding: 2px 8px;
    white-space: nowrap;
  }

  /* Dòng điểm thưởng — gọn 1 dòng (khác layout 2 cột label/số trải rộng của
     .account-sidebar-points bên account.php vì sheet nhỏ hơn nhiều). */
  .account-mini-sheet-points {
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--text-muted);
    padding: 0 var(--space-2) var(--space-3);
    margin-bottom: var(--space-2);
    border-bottom: 1px solid var(--line);
  }

  /* Danh sách nav — cùng token gap/padding/bo góc/màu với .account-nav-item,
     .account-nav-icon, .account-nav-label, .account-nav-badge (account.php). */
  .account-mini-sheet-nav { display: flex; flex-direction: column; gap: 2px; }
  .account-mini-sheet-nav-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 12px var(--space-2);
    border-radius: var(--radius-sm);
    color: var(--ink-soft);
    font-weight: 600;
    font-size: var(--fs-base);
    text-decoration: none;
    transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
  }
  .account-mini-sheet-nav-item:hover,
  .account-mini-sheet-nav-item:active { background: var(--paper-deep); color: var(--ink); }
  .account-mini-sheet-nav-icon { display: flex; flex-shrink: 0; width: 20px; height: 20px; }
  .account-mini-sheet-nav-icon svg { width: 100%; height: 100%; }
  .account-mini-sheet-nav-label { flex: 1; min-width: 0; }
  .account-mini-sheet-nav-badge {
    flex-shrink: 0;
    font-size: var(--fs-xs);
    font-weight: 700;
    color: var(--ink-soft);
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--radius-pill);
    min-width: 20px;
    text-align: center;
    padding: 1px 6px;
  }

  /* Link "Đăng xuất" — tách dòng kẻ trên, cùng token/màu đỏ cảnh báo với
     .account-nav-logout (account.php). */
  .account-mini-sheet-logout {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 0 var(--space-2);
    margin-top: var(--space-4);
    padding-top: var(--space-4);
    border-top: 1px solid var(--line);
    font-weight: 700;
    font-size: var(--fs-sm);
    color: var(--koi-red);
    text-decoration: none;
    transition: color var(--dur-fast) var(--ease-standard);
  }
  .account-mini-sheet-logout:hover,
  .account-mini-sheet-logout:active { color: #93131b; }
  /* Giờ là <button> trong <form> (đăng xuất phải POST) — reset kiểu nút để giữ nguyên diện mạo link cũ. */
  button.account-mini-sheet-logout {
    width: 100%;
    background: none;
    border: 0;
    border-top: 1px solid var(--line);
    font-family: inherit;
    text-align: left;
    cursor: pointer;
  }

  /* ============================================================
     THANH TÌM KIẾM "HERO" — nằm cố định trong luồng, NGAY DƯỚI logo, là con trực
     tiếp của .site-header nên tự động sticky theo header khi cuộn (xem chú thích
     trong header.php). Không còn là FAB nổi + overlay riêng như bản trước — thay
     bằng 1 thanh tìm kiếm luôn hiển thị, đơn giản hơn hẳn (không cần JS mở/đóng,
     không animation trượt, không lớp phủ tối), đồng thời mượt hơn vì hoàn toàn
     dựa vào position:sticky của CSS, trình duyệt tự tăng tốc phần cứng, không có
     bất kỳ scroll-listener nào tính toán lại vị trí mỗi khung hình như cách làm
     FAB/overlay cũ có thể gây giật trên máy yếu.
     ============================================================ */
  .mobile-hero-search {
    display: block;
    /* padding-top bù đắp khoảng cách mà vạch gạch (::before, đã bỏ) từng tạo ra,
       để khoảng cách logo → ô tìm kiếm không bị dính sát lại gần nhau. */
    padding: 8px var(--space-4) 10px;
    position: relative;
  }
  /* Đã bỏ vạch gạch nhỏ (::before) từng đặt giữa logo và ô tìm kiếm theo yêu cầu —
     khối logo+search sẽ có animation riêng để thay thế điểm nhấn thị giác này
     (xem đề xuất animation đang chờ duyệt). */
  /* Ô tìm kiếm: "thẻ trắng nổi nhẹ", chiều cao/khoảng đệm/bóng đổ rút gọn cho
     thanh mảnh, gọn, thanh lịch, không dày/cồng kềnh.
     LƯU Ý: phải khai báo "display: flex" tường minh ở đây — nếu không, rule
     ".search-form { display: none; }" phía trên (ẩn ô tìm kiếm desktop trên
     mobile) sẽ ẩn luôn cả form này vì cùng mang class .search-form (2 rule cùng
     độ đặc hiệu; ".search-form" khai báo trước trong file nhưng thắng cho thuộc
     tính display nếu ".search-form-hero" không tự khai báo để ghi đè) — bỏ dòng
     này thanh tìm kiếm sẽ mất hẳn không hiển thị. */
  .search-form-hero {
    display: flex;
    align-items: center;
    height: 38px;
    background: #fff;
    border: 1px solid var(--line);
    border-radius: var(--radius-pill);
    padding: 0 3px 0 36px;
    position: relative;
    box-shadow: 0 2px 6px -3px rgba(18, 34, 44, .18);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    transition: box-shadow var(--dur-slow) var(--ease-standard), border-color var(--dur-slow) var(--ease-standard), transform var(--dur-slow) var(--ease-standard);
  }
  /* Kính lúp trang trí đứng cố định bên trái ô nhập — gợi ý rõ ràng "đây là ô tìm
     kiếm" ngay cả trước khi khách chạm vào, đúng ngôn ngữ thị giác quen thuộc của
     hầu hết app thương mại điện tử. Vẽ bằng SVG data-URI để không cần thêm ảnh, màu
     lấy theo --text-muted (nhẹ nhàng, không tranh sự chú ý với nút submit cam). */
  .search-form-hero::before {
    content: '';
    position: absolute;
    left: 14px;
    top: 50%;
    width: 15px;
    height: 15px;
    transform: translateY(-50%);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237c8794' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m20 20-3.5-3.5'/%3E%3C/svg%3E");
    background-size: contain;
    background-repeat: no-repeat;
    pointer-events: none;
  }
  /* 16px: xem ghi chú chống iOS tự zoom ở cuối file — ô tìm kiếm nhỏ hơn 16px làm Safari
     phóng to trang mỗi lần khách chạm vào. */
  /* min-width:0 — ô nhập mặc định có bề rộng tối thiểu theo thuộc tính size (~20 ký tự); ở 16px
     nó rộng tới mức nút tìm kiếm bị đẩy lòi ra ngoài mép viên thuốc trên màn 320px. */
  .search-form-hero input { width: auto; flex: 1 1 auto; min-width: 0; font-size: 16px; }
  /* Nút submit đổi từ icon "ma" (trong suốt, gần như vô hình trên nền cũ) sang 1
     nút tròn đặc màu cam thương hiệu — hành động chính (bấm để tìm) giờ có điểm
     nhấn thị giác rõ ràng, đúng vai trò 1 nút gọi-hành-động (CTA) thay vì chỉ là
     trang trí mờ nhạt cạnh ô nhập. */
  .search-form-hero button {
    width: 30px;
    height: 30px;
    border: none;
    background: linear-gradient(135deg, var(--koi-orange) 0%, #e8823f 100%);
    color: #fff;
    box-shadow: 0 3px 8px -4px rgba(217, 102, 59, .5);
  }
  .search-form-hero button:hover,
  .search-form-hero button:active { background: var(--jade); box-shadow: 0 3px 8px -4px rgba(31, 111, 106, .45); }
  .search-form-hero:focus-within {
    border-color: var(--koi-orange);
    /* Quầng sáng mềm hơn (blur rộng hơn, mờ hơn) + nhích to nhẹ 1% — cảm giác ô
       tìm kiếm "hé nở" đón lấy thao tác gõ, thay vì bật sáng đột ngột. */
    box-shadow: 0 0 0 4px rgba(217, 102, 59, .12), 0 6px 16px -8px rgba(217, 102, 59, .35);
    transform: scale(1.01);
  }
  @media (prefers-reduced-motion: reduce) {
    .search-form-hero { transition: none; }
  }


  /* Rule .site-footer (padding-bottom chừa chỗ bottom tab bar) đã dồn lên dùng
     chung cho toàn bộ @media (max-width:900px) phía trên — không khai báo lại
     ở đây nữa để tránh chừa chỗ 2 lần chồng nhau (xem khối comment "THANH TRẮNG
     Ở ĐÁY TRANG"). */
}

/* Điện thoại rất nhỏ: Logo vẫn đứng 1 mình giữa header-row-1 (account-cart-group đã
   ẩn hẳn từ @media max-width:900px ở trên, không còn nguy cơ tràn ngang với hamburger/
   account-cart-group như bản cũ nữa) — chỉ thu nhỏ nhẹ Logo cho cân đối với màn hình
   hẹp, không cần biện pháp "icon-only" nào thêm vì không còn gì khác cạnh Logo để tràn. */
@media (max-width: 480px) {
  .brand { font-size: 19px; gap: var(--space-2); }
  .brand .mark { width: 36px; height: 36px; }
}

@media (max-width: 640px) {
  /* .topbar .container: layout cột (contact trên, địa chỉ dưới), địa chỉ khoá
     cứng 1 dòng (nowrap + ellipsis) để topbar tối đa 2 dòng, không phụ thuộc độ
     dài địa chỉ. Bấm/hover vào địa chỉ vẫn xem được đầy đủ (link Google Maps +
     title trong header.php). */
  .topbar .container {
    flex-direction: column;
    flex-wrap: nowrap;
    gap: 3px;
    padding-top: 7px;
    padding-bottom: 7px;
  }
  .topbar-contact { justify-content: center; }
  .topbar-address {
    display: block;
    max-width: 100%;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    text-align: center;
  }
  
  section.block { padding: 40px 0; }
  .container { padding: 0 var(--space-4); }
  .page-head { padding: 18px 0; }
  .page-head h1 { font-size: 21px; }

  .hero { min-height: 460px; }
  .hero .container { min-height: 460px; padding-top: 36px; padding-bottom: 44px; }
  .hero h1 { font-size: clamp(26px, 7vw, 34px); }
  .hero p.lede { font-size: 15px; }
  /* Màn thật hẹp (≤640px): bỏ hẳn flex:1 (từng ép 2 nút giãn đều 50/50 — chính
     thứ khiến 2 nút trông "thô, to" trên ảnh chụp thực tế) — để mỗi nút tự co
     theo đúng độ dài chữ của nó, xếp giữa hàng. Nút chính (btn-primary) vẫn có
     nền đặc + đổ bóng để giữ vai trò CTA nổi bật nhất; nút phụ (btn-ghost) hạ
     xuống 1 bậc bằng viền mảnh hơn + không nền, đúng phân cấp thị giác chính/phụ
     thay vì 2 nút "nặng đô" ngang nhau như trước. */
  .hero-ctas { justify-content: center; }
  .hero-ctas .btn {
    flex: 0 1 auto;
    padding: 10px 18px;
    font-size: 12.5px;
  }
  /* :not(:hover) để bóng nhỏ này không đè bóng hover của .btn-primary (cùng độ đặc hiệu, đứng sau) khi rê chuột ở màn hình hẹp. */
  .hero-ctas .btn-primary:not(:hover) { box-shadow: 0 6px 16px -8px rgba(217,102,59,.5); }
  .hero-ctas .btn-ghost { border-width: 1.4px; }
  .hero-copy-glass { padding: 18px var(--space-4); }

  .block-head { gap: 10px; }
  .cat-grid { grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: var(--space-3); }
  .product-grid { grid-template-columns: repeat(auto-fill, minmax(155px, 1fr)); gap: var(--space-3); }
  .product-badges { top: 8px; left: 8px; right: 8px; gap: 5px; }
  .badge-sale, .badge-tag { font-size: 10px; padding: 4px 8px; }
  .product-body { padding: var(--space-3) var(--space-3) 14px; }
  .product-name { font-size: var(--fs-sm); }
  /* Thẻ sản phẩm co hẹp còn ~150-190px trên mobile — giá hiện tại + giá gạch ngang nằm
     chung 1 hàng rất dễ tràn ra ngoài khung thẻ khi số tiền dài (vd "2.890.000đ" +
     "3.200.000đ"). Xếp dọc (giá mới trên, giá cũ nhỏ hơn ngay dưới) để không bao giờ
     tràn dù màn hình hẹp cỡ nào, đồng thời làm nổi bật giá bán hiện tại hơn. */
  .product-price-row { flex-direction: column; align-items: flex-start; gap: 2px; }
  .price-now { font-size: 15.5px; }
  .price-old { font-size: var(--fs-xs); }

  /* Mobile: post-grid chỉ còn 1 cột duy nhất (không còn "hàng chưa đầy" để lo
     giãn to nữa) — bỏ max-width để thẻ dùng full-width như 1 cột bình thường,
     tránh bị thu hẹp lệch trái do max-width 380px kế thừa từ desktop. */
  .post-grid { grid-template-columns: 1fr; gap: 18px; }
  .post-card { max-width: none; }

  .footer-grid { grid-template-columns: 1fr; gap: 26px; padding-top: 40px; padding-bottom: var(--space-5); text-align: left; }
  .footer-bottom { flex-direction: column; text-align: center; }

  /* Màn hẹp hơn (≤640px): floating-social thu nhỏ nút (46px) + siết lại khoảng
     cách — vẫn phải neo "bottom" ở TRÊN bottom tab bar mới giống @media 900px ở
     trên, chỉ đổi phần "right"/gap/kích thước nút cho khớp bản thu nhỏ này. */
  .floating-social {
    right: 14px;
    bottom: calc(var(--mobile-bottom-nav-h) + 14px + env(safe-area-inset-bottom, 0px));
    gap: 10px;
  }
  .floating-social.fs-lowered {
    bottom: calc(14px + env(safe-area-inset-bottom, 0px));
  }
  .float-btn { width: 46px; height: 46px; }

  /* ---- Trang chi tiết sản phẩm ---- */
  .pd-title { font-size: 21px; }
  .pd-price-row .price-now { font-size: 22px; }
  .pd-thumb-btn { width: 54px; height: 54px; }
  .wholesale-cta { flex-direction: column; align-items: stretch; padding: 14px; }
  .wholesale-cta-actions { flex-direction: column; }
  .wholesale-cta-actions .btn { justify-content: center; }
  .pd-actions { flex-direction: column; }
  .pd-actions .btn { width: 100%; justify-content: center; }
  .qty-row { flex-wrap: wrap; }

  /* Nút tăng/giảm số lượng — mở rộng vùng bấm cho ngón tay (từ 32px lên 40px) */
  .qty-box button { width: 40px; height: 40px; font-size: 17px; }
  .qty-box input { width: 42px; font-size: 16px; } /* 16px: chống iOS tự zoom khi chạm vào ô số lượng */

  /* ---- Giỏ hàng trên mobile: mỗi dòng thành 1 thẻ (card) xếp dọc thay vì bảng phải
     cuộn ngang. Chỉ đổi CÁCH HIỂN THỊ bằng CSS (table/tr/td vẫn giữ nguyên cấu trúc DOM) nên
     toàn bộ JS tăng/giảm số lượng, xoá sản phẩm (main.js) hoạt động y hệt, không cần sửa gì thêm.
     Giao diện desktop (bảng truyền thống) không đổi vì các luật này chỉ nằm trong @media (max-width:640px). */
  .cart-table-wrap { overflow: visible; }
  .cart-table { min-width: 0; table-layout: auto; border: none; }
  .cart-table thead { display: none; }
  .cart-table, .cart-table tbody { display: block; width: 100%; }
  .cart-table tr {
    position: relative;
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "product product"
      "price   qty"
      "total   total";
    row-gap: var(--space-3);
    column-gap: var(--space-3);
    background: #fff;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--space-4) 44px var(--space-4) var(--space-4);
    margin-bottom: var(--space-3);
  }
  .cart-table tr:last-child { margin-bottom: 0; }
  .cart-table td { display: block; padding: 0; border-bottom: none; font-size: var(--fs-sm); }
  .cart-table td:nth-child(1) { grid-area: product; }
  .cart-table td:nth-child(2) { grid-area: price; align-self: center; color: var(--text-muted); }
  .cart-table td:nth-child(2)::before { content: "Đơn giá: "; color: var(--text-muted); }
  .cart-table td:nth-child(3) { grid-area: qty; justify-self: end; align-self: center; }
  .cart-table td:nth-child(4) {
    grid-area: total;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-top: var(--space-3);
    border-top: 1px dashed var(--line);
    font-size: 16px;
  }
  .cart-table td:nth-child(4)::before { content: "Thành tiền"; font-weight: 600; font-size: var(--fs-xs); color: var(--text-muted); }
  /* Nút xoá: bấm gọn ở góc trên-phải mỗi thẻ thay vì 1 cột riêng chiếm chỗ ngang */
  .cart-table td:nth-child(5) { position: absolute; top: 12px; right: 12px; grid-area: unset; }
  .cart-table .remove-link {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--paper-deep);
    color: var(--koi-red);
    font-size: 0;
  }
  .cart-table .remove-link::before { content: "×"; font-size: 18px; line-height: 1; }
  /* Rule .cart-table .remove-link ở trên đè hover/active chung của .remove-link (cùng độ đặc hiệu, đứng sau) → khai lại cho nút tròn "×". */
  .cart-table .remove-link:hover,
  .cart-table .remove-link:active { background: var(--koi-red-tint); color: #93131b; }
  .cart-prod img { width: 48px; height: 48px; }
  .cart-summary { position: static; }

  /* ---- Form (đặt hàng, liên hệ, đăng nhập...) ---- */
  form input, form textarea, form select { padding: var(--space-3) 14px; font-size: 16px; } /* 16px tránh iOS tự zoom khi focus */
  /* Quy tắc phân loại nút submit trên mobile — ĐỌC TRƯỚC KHI THÊM FORM MỚI:
     Mặc định MỌI <button type="submit"> bị kéo full-width (dòng dưới), để
     đảm bảo hành động chính của trang/luồng luôn dễ bấm và dẫn dắt mắt.
     - "Hành động chính của cả trang/luồng" (đăng nhập, đăng ký, đặt hàng,
       tra cứu đơn hàng, đặt lại mật khẩu, gửi liên hệ...) → GIỮ full-width
       mặc định, không cần thêm class gì (có thể thêm .btn-block tường minh
       ở HTML để rõ ý, nhưng không bắt buộc vì đây đã là default).
     - "Hành động phụ/cập nhật trong 1 khối nội dung nhỏ hơn, không phải
       trọng tâm của cả màn hình" (gửi đánh giá, lưu thay đổi thông tin đơn
       hàng, đổi mật khẩu...) → PHẢI gắn thêm class .btn-fit-center để nút
       gọn, căn giữa, không chiếm hết chiều ngang.
     Mọi form mới có dạng "vài field xếp dọc + 1 nút submit ở cuối" phải áp
     đúng 1 trong 2 loại trên — không để mặc định "ngẫu nhiên" theo file.
     LƯU Ý (rút kinh nghiệm từ lỗi thực tế): .btn-fit-center PHẢI gắn thẳng
     vào HTML của nút, KHÔNG được chỉ tạo hiệu ứng gọn qua 1 luật CSS giới hạn
     theo breakpoint (vd chỉ "@media min-width:901px { ... }"). Nếu chỉ sửa
     CSS theo breakpoint, nút sẽ gọn ở kích thước màn hình đó nhưng vẫn bị
     luật "form button[type=submit] chưa đánh dấu -> 100%" ở trên bắt trúng
     tại các breakpoint khác → gọn trên PC nhưng full-width trên mobile (hoặc
     ngược lại), không đồng bộ. Toàn bộ nút "Đăng nhập/Đăng ký/Gửi thông tin/
     Tra cứu đơn hàng/Huỷ đơn hàng/Về danh sách đơn hàng/Lọc" đã từng dính lỗi
     này và đã được sửa về đúng mẫu (gắn .btn-fit-center trực tiếp trong HTML).
     LỖI Y HỆT từng lặp lại ở khối "Sổ địa chỉ" (account.php): nút "Đặt mặc
     định"/"Xoá"/"Lưu thay đổi"/"Lưu địa chỉ" đứng CẠNH nhau trong 1 hàng
     ngang — không dùng được .btn-fit-center (margin:auto sẽ đẩy dạt chúng ra
     xa nhau) nên trước đó bị bỏ sót khỏi :not() và vẫn bị luật này kéo
     full-width trên mobile dù đã có riêng 1 luật `.account-address-actions
     .btn { width: fit-content }` — luật đó thua vì độ đặc hiệu thấp hơn
     `form button[type="submit"]:not(...)`. Đã sửa bằng .btn-fit-row (định
     nghĩa cạnh .btn-fit-center ở trên) — cùng liệt kê vào :not() dưới đây. */
  .btn-block, form button[type="submit"]:not(.btn-fit-center):not(.btn-fit-row) { width: 100%; }
  /* Ô tìm kiếm (.search-form / .search-form-mobile) không phải nút submit thường —
     loại trừ khỏi luật width:100% ở trên để giữ nguyên nút tròn nhỏ như thiết kế gốc */
  .search-form button[type="submit"] { width: 28px !important; height: 28px !important; }

  .category-toolbar { padding: 14px var(--space-4); }
  .category-description { font-size: var(--fs-sm); margin-top: 8px; }
  .contact-card { padding: 20px; }
  /* Mobile: contact-wrap đã đổi sang 1 cột (xem grid-template-columns:1fr ở trên),
     bản đồ không còn cạnh form nên chỉ cần 1 chiều cao cố định gọn hơn desktop —
     giữ nguyên hành vi responsive hiện có, không đụng đoạn 1-cột phía trên. */
  .map-frame { height: 240px; }

}

/* Rule dưới đây loại trừ .category-sheet/.account-nav-sheet khỏi việc tắt hẳn
   transition/animation dưới prefers-reduced-motion: reduce, vì các sheet này (bottom
   sheet Danh mục/Giỏ hàng/tài khoản, xem khối "BOTTOM SHEET DANH MỤC" phía trên) cần
   giữ lại 1 phản hồi chuyển động mang thông tin trạng thái (mở/đóng), khác với các
   animation thuần trang trí/lặp vô hạn (ripple logo .mark-wrap, chấm nhấp nháy
   .js-pulse-dot, vòng ping .mnb-ping/.origin-ping...) vốn nên tắt hẳn. Reduced-motion
   đúng tinh thần là tắt chuyển động không cần thiết, không phải xoá sạch mọi phản hồi
   trực quan cho thao tác chính của khách. */
@media (prefers-reduced-motion: reduce) {
  *:not(.category-sheet):not(.account-nav-sheet) { transition: none !important; animation: none !important; }
  /* Thanh chạy của toast chỉ là trang trí (animation tắt thì nó đứng yên ở trạng thái đầy, gây hiểu nhầm) -> ẩn hẳn. */
  .toast-progress { display: none; }
  /* `*` không khớp pseudo-element nên transition của ::before chip "Trang chủ" (desktop) phải tắt riêng. */
  .main-nav .nav-item--pinned::before { transition: none !important; }

  /* .category-sheet (dùng chung cho cả #categorySheet "Danh mục" và #cartSheet
     "Giỏ hàng", xem .cart-sheet kế thừa) giữ lại đúng 1 fade rất ngắn — đủ để
     khách vẫn nhận ra có việc gì đó vừa mở/đóng (không snap câm lặng gây hụt
     hẫng/khó hiểu), nhưng không còn phần "phồng to/thu nhỏ" (transform).
     2 rule tách riêng (không gộp chung selector) vì delay của "visibility" phải
     ngược nhau giữa 2 chiều: lúc đóng cần trễ visibility:hidden .12s (cho kịp thấy
     nốt fade mờ rồi mới ẩn hẳn khỏi cây accessibility/loại khỏi thao tác chuột),
     lúc mở thì visibility:visible phải áp dụng ngay (delay 0s) để nội dung hiện
     ra đồng thời với lúc bắt đầu fade, không phải đợi hết fade mới "hiện". Nếu
     gộp chung 1 transition dùng cho cả 2 chiều, visibility sẽ không có khai báo
     riêng -> mặc định áp dụng ngay lúc đổi class, khiến chiều đóng bị ẩn khuất
     tức thì, cắt cụt mất phần fade còn chưa kịp chạy xong. */
  .category-sheet {
    transform: none !important;
    transition: opacity .12s linear, visibility 0s linear .12s !important;
  }
  .category-sheet.sheet-open {
    transform: none !important;
    transition: opacity .12s linear, visibility 0s linear 0s !important;
  }

  /* Sheet nav tài khoản (#accountSidebar.account-nav-sheet, xem khối "sidebar tài
     khoản -> bottom sheet mobile" ở trên) mở/đóng bằng translateY thay vì scale, nhưng
     vẫn là chuyển động mang thông tin trạng thái y hệt .category-sheet — cùng lý do,
     cùng cấu trúc 2 chiều (đóng: trễ visibility .12s cho kịp thấy fade rồi mới ẩn khỏi
     cây a11y; mở: visibility hiện ngay lập tức), chỉ khác không có "transform:none"
     tách riêng theo pha border-radius vì
     .account-nav-sheet không animate border-radius (không scale nên không cần). */
  .account-sidebar.account-nav-sheet {
    transform: none !important;
    transition: opacity .12s linear, visibility 0s linear .12s !important;
  }
  .account-sidebar.account-nav-sheet.sheet-open {
    transform: none !important;
    transition: opacity .12s linear, visibility 0s linear 0s !important;
  }

  /* Modal xác nhận (.confirm-modal-overlay/.confirm-modal-box) — mang thông tin
     trạng thái (mở/đóng hộp thoại xác nhận), không phải trang trí thuần, nên không
     để rule "*:not(.category-sheet)" ở trên tắt hẳn — phải khai báo tường minh để
     giữ lại. Giữ 1 fade opacity rất ngắn (.12s, khớp đúng số đã dùng cho
     .category-sheet) + visibility (đóng trễ .12s để không cắt cụt fade, mở tức
     thì — cùng cấu trúc 2 chiều với .category-sheet ngay trên), bỏ hẳn phần "phồng
     nhẹ" (transform) qua transform:none. Đồng bộ với khối tương tự bên
     admin/assets/admin.css. */
  .confirm-modal-overlay {
    transition: opacity .12s linear, visibility 0s linear .12s !important;
  }
  .confirm-modal-overlay.show {
    transition: opacity .12s linear, visibility 0s linear 0s !important;
  }
  .confirm-modal-box {
    transform: none !important;
    transition: none !important;
  }

  /* .account-panel (chuyển mục sidebar "Tài khoản của tôi") dùng chung nền
     .js-fade-toggle nhưng cần loại khỏi rule "*:not(.category-sheet)" ở trên, vì
     đổi mục sidebar là chuyển động mang thông tin trạng thái, không phải trang trí
     — khác với các chỗ dùng .js-fade-toggle khác như .coupon-form/.online-methods-box,
     vốn luôn có 1 thay đổi nội dung khác tự báo hiệu trạng thái nên được phép cắt
     hẳn. Bản thường (ngoài reduced-motion, xem khối iOS cross-dissolve cạnh
     .account-panel.is-shown) đã bỏ hẳn translateY nên ở đây chỉ còn rút ngắn
     .3s -> .12s, cùng số đã dùng cho .category-sheet/.confirm-modal-overlay ở
     trên. Không cần tách 2 chiều mở/đóng như .category-sheet vì .account-panel
     ẩn/hiện qua display (JS tự gỡ .is-shown bằng setTimeout 300ms cố định
     trong hideAccountPanel(), không phụ thuộc tốc độ transition CSS) chứ
     không qua visibility. */
  .account-panel.js-fade-toggle,
  .account-panel.js-fade-toggle.is-visible {
    transition: opacity .12s linear !important;
  }
}

@keyframes pulse-dot {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: .4; transform: scale(.7); }
}
/* ANIMATION TRANG TRÍ (ngoại lệ có chủ đích) — chấm nhấp nháy lặp vô hạn, không
   phải phản hồi cho 1 thao tác cụ thể, nên KHÔNG ép về thang --dur-*; đã được
   tắt riêng trong khối @media (prefers-reduced-motion: reduce) ở trên. */
.js-pulse-dot { animation: pulse-dot 1.4s ease-in-out infinite; }

/* .cart-badge tái dùng đúng @keyframes pulse-dot ở trên (không viết keyframes mới
   trùng chức năng) nhưng chạy đúng 1 lần (không infinite) — main.js (updateCartBadge())
   thêm/gỡ class .is-pulsing tạm thời qua setTimeout mỗi lần số lượng giỏ hàng đổi.
   Dùng --dur-slow vì đây là 1 cú "nảy" số liệu vừa đổi (tương đương mức "badge lớn"
   trong bảng token), không phải vi tương tác hover/click cực nhỏ. Không cần khai báo
   rule prefers-reduced-motion riêng: rule chung "*:not(.category-sheet) { animation:
   none !important; }" ở trên đã tự tắt animation này (đây là animation khai báo qua
   class CSS, không phải style JS thuần, nên rule chung phủ tới). */
.cart-badge.is-pulsing { animation: pulse-dot var(--dur-slow) var(--ease-standard) 1; }

/* Giá trị vừa đổi chữ (giá/tồn kho ở product.php, thành tiền/tạm tính/ship/giảm giá/tổng ở giỏ hàng):
   main.js setTextAnimated() thêm .is-value-changed tạm thời — một nhịp mờ -> rõ, KHÔNG dịch chuyển nên
   không gây nhảy bố cục. Reduced-motion: rule chung `animation: none !important` đã tự tắt (giá trị
   vẫn đổi đúng, chỉ không nháy). */
@keyframes value-flash {
  from { opacity: .35; }
  to   { opacity: 1; }
}
.is-value-changed { animation: value-flash var(--dur-base) var(--ease-enter) 1; }

/* =========================== MODAL XÁC NHẬN TUỲ CHỈNH =========================== */
/* Tái dùng đúng pattern "visibility thay vì display" đã có sẵn cho
   .select-picker-panel/.icon-picker-panel (admin.css) và .category-sheet (dưới đây,
   cùng file này): phần tử luôn có display:flex thật, ẩn/hiện qua opacity + visibility
   — visibility có transition-delay lệch nhau 2 chiều: mở delay 0s (hiện ngay để
   opacity kịp nội suy từ 0), đóng delay = đúng thời lượng opacity hiện có (--dur-slow/
   --ease-standard), để phần tử chỉ thật sự rời khỏi thao tác chuột/cây accessibility
   sau khi fade mờ chạy xong, không cắt cụt. Cần khai báo pointer-events riêng vì
   display cố định (không dựa vào display:none để chặn chuột nữa). Vì display không
   còn đổi qua lại, trình duyệt luôn có đúng giá trị "trước đó" để nội suy ngay khi
   đổi class .show — không cần double-buffer qua 2 requestAnimationFrame như pattern
   .js-fade-toggle (pattern đó cần double-buffer vì display thật sự đổi
   none<->giá trị khác; ở đây display cố định nên không cần). */
.confirm-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(18,34,44,.55);
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  /* Mở modal = "chuyển động lớn hơn" (250-350ms theo hệ thống motion ở :root),
     không phải vi tương tác — dùng var(--dur-slow)/var(--ease-standard). */
  transition: opacity var(--dur-slow) var(--ease-standard),
              visibility 0s linear var(--dur-slow);
}
.confirm-modal-overlay.show {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity var(--dur-slow) var(--ease-standard),
              visibility 0s linear 0s;
}
.confirm-modal-box {
  background: #fff;
  border-radius: var(--radius);
  padding: 28px 26px;
  max-width: 380px;
  width: 100%;
  box-shadow: 0 24px 60px -12px rgba(18,34,44,.4);
  transform: translateY(10px) scale(.97);
  transition: transform var(--dur-slow) var(--ease-standard);
}
.confirm-modal-overlay.show .confirm-modal-box { transform: translateY(0) scale(1); }
.confirm-modal-box p { font-size: 14.5px; color: var(--ink); margin: 0 0 22px; line-height: 1.5; text-align: center; }
.confirm-modal-actions { display: flex; gap: var(--space-3); }
.confirm-modal-actions .btn { flex: 1; justify-content: center; }

/* ============================================================
   Class dùng chung thay cho style="..." inline lặp lại nhiều lần
   rải rác trong các file .php (cart, order_detail, order-status,
   order-lookup, order-payment-instructions, index, product,
   account, login/register/forgot/reset-password).
   ============================================================ */

/* Khối thông báo lỗi (form đăng nhập/đăng ký/giỏ hàng/tài khoản...), gom
   thành 1 class dùng chung thay vì lặp lại style inline ở mỗi trang, dùng sẵn
   class="error-msg" trên thẻ — nay định nghĩa 1 lần duy nhất. */
.error-msg { background: var(--koi-red-tint); color: var(--koi-red); padding: 10px 14px; border-radius: var(--radius-sm); font-size: var(--fs-sm); margin-bottom: 14px; animation: msg-fade var(--dur-slow) var(--ease-enter); }

/* Hiệu ứng "shake" dùng chung báo lỗi form (login/register/forgot-password/
   reset-password/contact.php khi trang tải lại kèm .error-msg, và nhóm .otp-boxes khi
   submit sai mã — xem main.js hàm triggerShake()). Biên độ nhỏ (tối đa 8px) + thời
   lượng ngắn (var(--dur-shake) = 360ms tổng) — rõ ràng nhưng không gây khó chịu. Đây
   là animation "trang trí thêm" (màu border-color đỏ của .error-msg/.otp-box đã tự đủ
   báo lỗi, shake chỉ nhấn mạnh thêm) — vì vậy không cần khai báo rule
   prefers-reduced-motion riêng: rule chung "*:not(.category-sheet) { animation:none
   !important; }" (khối @media cuối file) đã tự tắt hẳn animation này, main.js tự dọn
   class qua setTimeout khớp đúng 360ms (không chờ animationend, vì animationend sẽ
   không bao giờ bắn khi animation đã bị tắt).
   Dùng biến var(--dur-shake)/var(--ease-shake) (khai báo cùng nhóm --dur-* / --ease-* ở
   :root) thay vì số viết tay, đúng quy tắc token chung của dự án.
   Dùng chung 1 keyframes cho cả 2 chỗ áp dụng (form lỗi + nhóm OTP), không viết trùng. */
@keyframes form-shake {
  0%, 100% { transform: translateX(0); }
  15% { transform: translateX(-8px); }
  30% { transform: translateX(7px); }
  45% { transform: translateX(-6px); }
  60% { transform: translateX(4px); }
  75% { transform: translateX(-2px); }
  90% { transform: translateX(1px); }
}
.form-shake { animation: form-shake var(--dur-shake) var(--ease-shake); }

/* Tiện ích dùng lặp lại rất nhiều nơi (margin-top:0, canh lề...) */
.mt-0 { margin-top: 0; }
.mb-0 { margin-bottom: 0; }
.ta-left { text-align: left; }
.ta-center { text-align: center; }

/* Chữ phụ nhỏ màu xám — 2 cỡ theo type scale. Dùng chung cho cả đoạn chữ tĩnh lẫn link
   phụ (VD "← Nhập lại thông tin đăng ký"), nên :hover chỉ có tác dụng thực tế trên phần
   tử đang là link (thẻ tĩnh không ai rê chuột chờ phản hồi cả). */
.text-muted { color: var(--text-muted); }
.text-muted-sm { font-size: var(--fs-sm); color: var(--text-muted); }
.text-muted-xs { font-size: var(--fs-xs); color: var(--text-muted); }
a.text-muted-xs:hover, a.text-muted-sm:hover, a.text-muted:hover,
a.text-muted-xs:active, a.text-muted-sm:active, a.text-muted:active { color: var(--ink); }

/* Chữ nhấn màu cam/jade in đậm — dùng cho link "Đăng ký ngay"/"Đăng nhập"
   và trạng thái thanh toán (đã/chưa thanh toán) */
.text-accent-orange { color: var(--koi-orange); font-weight: 700; }
a.text-accent-orange:hover,
a.text-accent-orange:active { opacity: .78; }
/* Nút dạng link tạm khoá trong lúc chờ request (vd nút "Gửi lại mã" OTP khi đăng ký) */
.disabled-link { opacity: .5; pointer-events: none; }
.text-accent-jade { color: var(--jade); font-weight: 700; }
a.text-accent-jade:hover,
a.text-accent-jade:active { opacity: .78; }

/* Dòng chuyển hướng dưới form đăng nhập/đăng ký ("Chưa có tài khoản?"...) */
.auth-switch-line { margin-top: var(--space-4); font-size: var(--fs-sm); text-align: center; }

/* =========================== OTP (xác minh email đăng ký) =========================== */
/* Khối bước 2 của register.php — canh giữa, tách khỏi phong cách form thường (label trái +
   input full-width) vì đây là màn hình xác nhận 1 hành động duy nhất, không phải nhập liệu
   nhiều trường, nên bố cục tập trung theo chiều dọc dễ theo dõi hơn. */
.otp-panel { text-align: center; }
.otp-icon {
  width: 56px;
  height: 56px;
  margin: 0 auto var(--space-4);
  border-radius: 50%;
  background: var(--paper-deep);
  color: var(--koi-orange);
  display: flex;
  align-items: center;
  justify-content: center;
}
.otp-panel-title {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  color: var(--ink);
  margin: 0 0 var(--space-2);
}
.otp-panel-desc { margin: 0 0 var(--space-5); }
.otp-panel-desc strong { color: var(--ink); }
.otp-panel .alert-notice { text-align: left; }

.otp-field { text-align: left; margin-bottom: var(--space-6); }
.otp-field label {
  text-align: center;
  display: block;
}
.otp-boxes {
  display: flex;
  justify-content: center;
  gap: 10px;
}
.otp-box {
  width: 46px;
  height: 54px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: #fdfbf7;
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 600;
  text-align: center;
  color: var(--ink);
  /* Transition cho transform ngoài border-color/box-shadow lúc focus — vi tương tác
     cực nhỏ khi gõ số (dùng --dur-fast, cùng mức "phồng tại chỗ, không dịch chuyển vị
     trí" với qty stepper .qty-box input.is-qty-bumped), không phải chuyển động lớn. */
  transition: border-color var(--dur-base) var(--ease-standard), box-shadow var(--dur-base) var(--ease-standard), transform var(--dur-fast) var(--ease-standard);
}
.otp-box:focus {
  outline: none;
  border-color: var(--koi-orange);
  box-shadow: 0 0 0 3px rgba(217, 102, 59, .15);
}
.otp-box.otp-box-filled { border-color: var(--jade); }
/* Phồng rất nhẹ mỗi khi gõ đúng 1 ký tự vào ô (main.js thêm/gỡ tạm qua setTimeout,
   cùng cơ chế offsetWidth-reflow với .cart-badge.is-pulsing để animation vẫn phát lại
   đúng khi gõ nhiều số liên tiếp thật nhanh). */
.otp-box.otp-box-typed { transform: scale(1.08); }
.otp-box::-webkit-outer-spin-button, .otp-box::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
/* Gộp mốc lẻ 420px -> 480px: 6 ô OTP thu nhỏ sớm hơn 60px. Chỉ ảnh hưởng dải
   421-480px, và thu nhỏ sớm hơn thì càng chắc chắn không tràn hàng. */
@media (max-width: 480px) {
  .otp-boxes { gap: 7px; }
  .otp-box { width: 40px; height: 50px; font-size: 20px; }
}

/* Nút icon nhỏ trong suốt (copy mã đơn...) */
.icon-btn { border: none; background: none; cursor: pointer; padding: 2px; border-radius: 6px; transition: background-color var(--dur-fast) var(--ease-standard), opacity var(--dur-fast) var(--ease-standard); }
.icon-btn:hover { background: rgba(18,34,44,.06); }
.icon-btn:active { opacity: .6; }
.icon-btn--copy { vertical-align: -3px; }

/* Hộp gợi ý hướng dẫn thanh toán online (bank/momo/zalopay/vnpay) ở
   cart.php — JS bật/tắt qua pattern .js-fade-toggle/.is-shown/.is-visible
   dùng chung (xem comment đầy đủ tại .coupon-box trong file này). display:block mặc
   định khi hiện khai báo ở .online-methods-box.is-shown, .payment-hint-box.is-shown
   ngay dưới đây — mặc định ẩn (display:none) do .js-fade-toggle lo. */
.payment-hint-box { margin-top: var(--space-2); padding: 10px var(--space-3); background: #fff; border: 1px dashed var(--line); border-radius: var(--radius-sm); font-size: var(--fs-xs); color: var(--text-muted); }
.online-methods-box.is-shown, .payment-hint-box.is-shown { display: block; }

/* Dòng sản phẩm trong đơn hàng (order_detail.php) */
.order-line-item { display: flex; align-items: center; gap: 14px; padding: var(--space-3) 0; border-bottom: 1px solid var(--line); }
.order-line-thumb { width: 56px; height: 56px; object-fit: cover; border-radius: var(--radius-sm); background: var(--paper-deep); flex-shrink: 0; }
.order-line-info { flex: 1; min-width: 0; }
.order-line-name { font-weight: 600; font-size: var(--fs-sm); }
/* Chỉ bản <a> (có link tới sản phẩm); bản <span> không phải link nên không đổi màu. */
a.order-line-name:hover,
a.order-line-name:active { color: var(--koi-orange); }
.order-line-price { font-weight: 700; color: var(--koi-red); white-space: nowrap; }

/* Badge trạng thái đơn hàng — phần cố định (padding/bo góc/cỡ chữ), màu
   nền/chữ vẫn để inline vì phụ thuộc dữ liệu (trạng thái từng đơn).
   display:inline-block + white-space:nowrap — không có 2 dòng này thì badge
   dạng viên thuốc (border-radius:pill) có thể tự xuống dòng giữa chừng ở màn
   hẹp, vỡ hình viên thuốc thành 2 khối bo tròn chồng nhau. */
.status-badge { display: inline-block; padding: var(--space-1) var(--space-3); border-radius: var(--radius-pill); font-size: var(--fs-xs); font-weight: 700; white-space: nowrap; }

/* Nhãn radio chọn phương thức thanh toán ở cart.php */
.payment-radio-label { display: flex; align-items: center; gap: var(--space-2); font-weight: 600; font-size: var(--fs-sm); padding: var(--space-2) 0; cursor: pointer; transition: color var(--dur-fast) var(--ease-standard); }
.payment-radio-label:hover,
.payment-radio-label:active { color: var(--koi-orange); }
.payment-radio-label--sub { font-weight: 500; padding: 6px 0; }
.radio-auto { width: auto; }

/* cart.php: khối "Chọn địa chỉ giao hàng" (chọn nhanh 1 địa chỉ đã lưu trong Sổ địa
   chỉ thay vì luôn gõ tay). Mỗi lựa chọn là 1 <label> bọc quanh input radio (giống
   tinh thần .payment-radio-label ngay trên, nhưng cần khối card viền/bo góc riêng vì
   mỗi lựa chọn ở đây có tới 3 dòng thông tin — nhãn/tên+SĐT/địa chỉ chi tiết — chứ
   không chỉ 1 dòng chữ như phương thức thanh toán). Dùng lại đúng --radius/--line/
   --space-* đã có, không thêm biến CSS mới. Đổi màu viền khi được chọn là đổi màu tĩnh
   (không phải hiệu ứng ẩn/hiện), nên dùng --dur-fast/--ease-standard thay vì đi qua
   pattern .js-fade-toggle/fadeToggle() — không cần animate layout gì thêm ở đây, chỉ
   đổi border-color/background-color. Tô viền option đang chọn qua class .is-selected
   do JS gán (initCheckoutAddressChoice() trong main.js), không dùng selector CSS
   :has() — toàn bộ style.css hiện không dùng :has() ở bất kỳ đâu khác, dùng JS (vốn đã
   phải chạy để tự điền 3 field) gán thêm 1 class là đủ, không tốn thêm rủi ro tương
   thích trình duyệt. */
.checkout-address-choice { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-4); }
.checkout-address-option {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-standard), background-color var(--dur-fast) var(--ease-standard);
}
.checkout-address-option.is-selected { border-color: var(--koi-orange); background: #fdf3ee; }
.checkout-address-option:hover:not(.is-selected) { border-color: var(--koi-orange); }
.checkout-address-option:active:not(.is-selected) { background: #fdf3ee; }
.checkout-address-option input[type="radio"] { margin-top: 3px; flex-shrink: 0; }
.checkout-address-option-body { display: flex; flex-direction: column; gap: 2px; font-size: var(--fs-sm); }
.checkout-address-option-head { display: flex; align-items: center; gap: var(--space-2); font-weight: 700; color: var(--ink); flex-wrap: wrap; }
.checkout-address-default-tag { padding: 1px 8px; border-radius: var(--radius-pill); background: var(--jade-tint); color: var(--jade); font-size: var(--fs-xs); font-weight: 700; white-space: nowrap; }

/* Checkbox "Lưu địa chỉ này vào Sổ địa chỉ của tôi" — dùng đúng pattern
   .js-fade-toggle/fadeToggle() vì đây thật sự là ẩn/hiện tuỳ theo lựa chọn radio phía
   trên (mang thông tin trạng thái: đang gõ tay địa chỉ mới hay đang dùng lại địa chỉ
   có sẵn), không phải vi tương tác đơn thuần như khối phía trên. display mặc định
   (ẩn) đã do .js-fade-toggle lo, chỉ cần khai display thật khi .is-shown ở đây, đúng
   pattern .online-methods-box.is-shown/.payment-hint-box.is-shown đã dùng. */
/* KHÔNG đặt display ở rule gốc: .js-fade-toggle (display:none mặc định) đã lo trạng thái ẩn, chỉ
   .is-shown ngay dưới mới bật display:flex. Trước đây rule gốc có display:flex nên khối "ẩn" vẫn
   chiếm chỗ (margin −6px/12px, còn cho Tab vào checkbox vô hình) và mở ra thì nhảy ~10px. */
.checkout-save-address-label { align-items: center; gap: var(--space-2); font-size: var(--fs-sm); color: var(--ink-soft); cursor: pointer; margin: -6px 0 var(--space-3); }
.checkout-save-address-label:hover { color: var(--ink); }
.checkout-save-address-label:active { opacity: .8; }
.checkout-save-address-label.is-shown { display: flex; }
.checkout-save-address-label input[type="checkbox"] { width: auto; }

/* Tiện ích margin theo thang spacing — dùng thay cho
   hàng chục chỗ gõ tay "margin-top: var(--space-N)" rải rác */
.mt-2 { margin-top: var(--space-2); }
.mt-4 { margin-top: var(--space-4); }
.mt-5 { margin-top: var(--space-5); } .mb-5 { margin-bottom: var(--space-5); }
.mt-2px { margin-top: 2px; }
.mt-20px { margin-top: 20px; }

.cart-prod-name { font-weight: 600; }
/* display mặc định (ẩn) do .js-fade-toggle lo, xem comment tại .coupon-box và tại
   .payment-hint-box — display:block khi hiện khai báo gộp chung với
   .payment-hint-box.is-shown ở đó, không lặp lại ở đây. */
.online-methods-box { padding: var(--space-3) var(--space-4); background: var(--paper-deep); border-radius: var(--radius-sm); margin-top: 6px; }

/* order_detail.php / order-status.php */
.mb-20px { margin-bottom: 20px; }
.mt-18px { margin-top: 18px; }
.mt-10px { margin-top: 10px; }
.card-header-row { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; margin-bottom: 10px; }
.btn-danger-outline { border-color: var(--koi-red); color: var(--koi-red); }
/* Viền/chữ đỏ đã có, nhưng thiếu hover riêng nên trước đây "mượn" luôn
   .btn-outline:hover (tint màu --ink trung tính) — LỆCH màu với chính bản
   thân nút (viền/chữ đỏ mà hover lại ngả xám, không phải đỏ nhạt). Thêm hover
   tint đúng màu --koi-red của nút, cùng tỉ lệ alpha .06 mà .btn-outline:hover
   đang dùng cho --ink (đồng bộ "độ đậm" tint theo đúng vai trò 2 chiều —
   mục 2 ANIMATION_GUIDE.md — transition đổi màu đã có sẵn từ .btn gốc, không
   cần khai lại). */
.btn-danger-outline:hover:not(:disabled) { background: rgba(194, 59, 74, .06); }
.text-danger { color: var(--koi-red); }
.text-danger-strong { color: var(--koi-red); text-align: center; font-weight: 700; }
.not-found-box { padding: 80px 0; text-align: center; }
.m-0 { margin: 0; }

/* order-status.php / order-lookup.php */
.container-narrow-520 { max-width: 520px; }
.container-narrow-720 { max-width: 720px; }
.order-code-text { color: var(--ink); font-size: 17px; }
.pending-notice { text-align: center; margin-top: var(--space-4); padding: var(--space-3); background: var(--koi-orange-tint); border-radius: var(--radius-sm); color: var(--koi-orange); font-weight: 600; font-size: var(--fs-sm); }
.pulse-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--koi-orange); margin-right: 6px; }
.modal-title-lg { display: block; margin-bottom: var(--space-2); font-size: 20px; }
.flex-1 { flex: 1; }
.order-card { display: block; background: #fff; border: 1px solid var(--line); border-radius: var(--radius); padding: 18px 20px; margin-bottom: 14px; }
a.order-card { transition: box-shadow var(--dur-base) var(--ease-standard), background-color var(--dur-base) var(--ease-standard); }
a.order-card:hover { box-shadow: 0 8px 20px -10px rgba(18,34,44,.25); }
/* Nút sao chép mã đơn nằm lồng trong thẻ (main.js .js-copy-code): nhấn nút đó không được làm cả thẻ "lún" như đang bấm vào link. */
a.order-card:active:not(:has(button:active)) { box-shadow: none; background: var(--paper-deep); }
.order-card-header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: var(--space-2); }
.order-total-row { margin-top: 10px; font-size: var(--fs-sm); }

/* includes/order-payment-instructions.php */
.payment-amount { color: var(--koi-red); }
.payment-note { font-size: var(--fs-sm); color: var(--text-muted); }
.payment-qr-img { display: block; width: 180px; max-width: 100%; height: auto; margin: 12px auto 0; border-radius: var(--radius-sm); }
.payment-confirm-note { font-size: var(--fs-xs); color: var(--text-muted); margin-top: 10px; margin-bottom: 0; }

/* Khối "Đánh giá" dùng chung ở index.php (đánh giá shop) và product.php
   (đánh giá sản phẩm) — 2 khối gần như giống hệt nhau về style. */
.bg-white { background: #fff; }
.container-narrow-760 { max-width: 760px; }
.review-summary-row { display: flex; align-items: center; gap: 10px; margin-bottom: var(--space-5); }
.review-summary-row--tight { gap: 6px; margin-bottom: 10px; }
.review-empty-note { color: var(--text-muted); font-size: var(--fs-sm); margin-bottom: 28px; }
.review-list { display: flex; flex-direction: column; gap: var(--space-4); margin-bottom: var(--space-6); }
.review-card { background: var(--paper-deep); border-radius: var(--radius); padding: 18px 20px; }
.review-card--bordered { background: #fff; border: 1px solid var(--line); border-radius: var(--radius); padding: 18px 20px; }
.review-card-head { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 6px; }
.review-card-name { font-size: var(--fs-sm); }
.review-card-date { font-size: var(--fs-xs); color: var(--text-muted); }
.review-card-stars { margin: 6px 0; }
.review-star-icon { margin-right: 1px; }
.review-card-comment { font-size: var(--fs-sm); color: var(--ink-soft); margin: 0; }
.review-form-hint { font-size: var(--fs-xs); color: var(--text-muted); margin-top: -6px; }
.review-form-login-note { font-size: var(--fs-sm); color: var(--ink-soft); margin-bottom: 14px; }
.review-form-as { font-size: var(--fs-sm); margin-top: 0; }
.star-picker { display: flex; gap: 6px; }
.star-picker label { cursor: pointer; }
.star-picker input[type="radio"] { display: none; }
/* Đổi màu sao mượt khi paint() (main.js) đổi fill, + phóng nhẹ khi chọn sao.
   fill không animate được ở vài engine cũ, không hỗ trợ thì chỉ đổi màu tức
   thì. .is-star-picked do JS thêm/gỡ tạm qua setTimeout, tách biệt với fill để
   khỏi phải khai transition cho scale trên :active. */
/* Màu sao do CSS quyết định, KHÔNG do JS gán thuộc tính fill.
   Trước đây assets/js/main.js gọi setAttribute('fill', '#C9992F') — mã vàng
   này lệch với token --gold (#c99a3a) ở :root, nên đổi --gold thì sao đánh giá
   vẫn giữ nguyên màu vàng cũ; đúng loại drift âm thầm khó thấy bằng mắt vì 2
   màu rất gần nhau. Nay JS chỉ bật/tắt class .is-on, còn màu lấy từ token. */
.js-star-icon { fill: var(--star-empty); transition: fill var(--dur-fast) var(--ease-standard), transform var(--dur-fast) var(--ease-standard); }
.js-star-icon.is-on { fill: var(--gold); }
.js-star-icon.is-star-picked { transform: scale(1.18); transition: fill var(--dur-fast) var(--ease-standard), transform var(--dur-instant) var(--ease-standard); }
/* Phản hồi nhẹ khi rê/chạm vào từng sao trước khi chọn hẳn (tách biệt với việc
   "chọn" ở trên — chỉ báo đang tương tác, không đổi màu vì màu do main.js paint
   theo rating đang hover/đã chọn). :not(.is-star-picked) — rule này có độ ưu
   tiên CAO HƠN ".js-star-icon.is-star-picked" (3 class/pseudo so với 2), nếu
   không loại trừ sẽ đè mất hiệu ứng "phồng" 1.18x xác nhận đã chọn ngay lúc
   ngón tay/con trỏ vẫn còn nằm trên sao vừa bấm (đúng lúc :active/:hover vẫn
   còn hiệu lực). */
.star-picker label:hover .js-star-icon:not(.is-star-picked),
.star-picker label:active .js-star-icon:not(.is-star-picked) { transform: scale(1.1); }
.text-hero-eyebrow { color: var(--koi-orange); }
.qty-label { font-weight: 700; font-size: var(--fs-sm); }

/* account.php */
.field-input-disabled { background: #f2f2f2; }
/* Nút rộng vừa đủ nội dung (fit-content), canh giữa bằng margin auto. */
.btn-fit-center {
  display: flex;
  width: fit-content;
  margin-left: auto;
  margin-right: auto;
}
/* GIỐNG .btn-fit-center (rộng vừa đủ chữ, thoát luật mobile "form
   button[type=submit] -> 100%" ở dưới) nhưng KHÔNG có margin:auto canh giữa —
   dùng cho nút đứng CẠNH các nút khác trong 1 hàng ngang trái-canh-lề (VD 3 nút
   Sửa/Đặt-mặc-định/Xoá của .account-address-actions, hoặc Lưu-thay-đổi/Huỷ của
   .account-address-form-actions ở account.php), nơi margin:auto của
   .btn-fit-center sẽ đẩy dạt các nút ra xa nhau thay vì nằm sát cạnh nhau.
   BẮT BUỘC phải nằm trong danh sách :not() của luật mobile bên dưới — thêm class
   ở đây mà quên thêm vào :not() thì nút vẫn bị bắt trúng và kéo full-width như
   cũ (đúng cái bẫy đã ghi ở lưu ý ngay dưới luật đó). */
.btn-fit-row { width: fit-content; }
.order-history-card { display: block; background: #fff; border: 1px solid var(--line); border-radius: var(--radius); padding: 18px 20px; margin-bottom: 14px; transition: box-shadow var(--dur-base) var(--ease-standard), background-color var(--dur-base) var(--ease-standard); }
.order-history-card:hover { box-shadow: 0 8px 20px -10px rgba(18,34,44,.25); }
.order-history-card:active { box-shadow: none; background: var(--paper-deep); }
/* Thẻ đơn rút gọn ở tab Tổng quan (.order-history-card--compact): 2 dòng —
   [Mã đơn ........ badge] / [ngày ........ Tổng tiền]. Không còn dòng nhắc
   "huỷ đơn" (nghe như lời kêu gọi huỷ); việc huỷ vẫn nằm ở trang chi tiết đơn. */
.order-history-card--compact { padding: 12px 16px; margin-bottom: 10px; }
.order-history-card--compact:last-child { margin-bottom: 0; }
.order-history-card--compact .order-card-header { flex-wrap: nowrap; gap: var(--space-3); }
.order-card-sub { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-3); margin-top: 4px; flex-wrap: wrap; }
.order-card-sub .order-total-row { margin-top: 0; }

/* ====== Account sidebar layout ======
   Layout của account.php: sidebar cố định bên trái (điểm thưởng đặt chỗ + nav
   dọc 8 mục + đăng xuất) + nội dung bên phải (chào H1 + hàng 4 thẻ chỉ số nhanh
   + panel "card" của mục đang chọn). Dùng đúng bộ màu thương hiệu và thang biến
   --space-/--radius-/--dur-/--ease- sẵn có. Bo góc 14-16px, viền/bóng đổ nhẹ
   (không dùng --shadow/--shadow-soft mặc định, tự khai bóng mờ hơn dưới đây).
   .account-subcard/.account-subcard-bordered dưới .account-panel.is-shown tách
   2 khối "Thông tin cá nhân"/"Đổi mật khẩu" trong panel Tổng quan mà không lồng
   thêm lớp nền/viền/bóng thứ 2 (panel ngoài đã là 1 "card" lớn) — chỉ tách bằng
   khoảng trắng + 1 đường kẻ mảnh (--line). */
.account-shell {
  display: grid;
  grid-template-columns: 260px 1fr;
  gap: var(--space-6);
  align-items: start;
}
.account-sidebar {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-4);
  box-shadow: 0 6px 20px -16px rgba(18, 34, 44, 0.25);
  position: sticky;
  /* var(--account-sticky-top), KHÔNG hằng số cứng — xem giải thích tại nơi
     khai biến (:root, đầu file). Hằng số cũ (100px) không tính header-row-2
     (thanh nav ngang, chỉ có ở desktop) nên khi cuộn, .site-header (cao hơn
     100px) đè lên đúng phần đầu sidebar (avatar/tên/hạng) — bug đã sửa. */
  top: var(--account-sticky-top);
}
/* createAccountSheet() (main.js) gán tabindex="-1" + gọi .focus() khi mở sheet
   mobile (đưa focus vào dialog, chuẩn a11y). tabindex="-1" loại phần tử khỏi
   thứ tự Tab nên :focus-visible không xảy ra do bấm Tab — vẫn khai :focus rõ
   ràng để chắc không "chớp" viền mặc định trên trình duyệt cũ. */
.account-sidebar:focus { outline: none; }
.account-sidebar:focus-visible { outline: 2px solid var(--koi-orange); outline-offset: -2px; }
/* Khối avatar tròn (chữ cái đầu tên) + tên khách + badge hạng thành viên.
   .account-tier-badge cố ý dùng tông trung tính (--paper-deep/--text-muted,
   giống .account-nav-soon dưới) chứ không dùng --gold khi chưa có hạng thật —
   xem giải thích tại comment PHP phía trên $accountNavItems (account.php). */
.account-sidebar-profile {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 0 var(--space-2) var(--space-4);
}
.account-avatar {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--koi-orange);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 700;
}
.account-sidebar-profile-info { min-width: 0; }
.account-sidebar-name {
  font-weight: 700;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.account-tier-badge {
  display: inline-block;
  margin-top: 3px;
  font-size: 10.5px;
  font-weight: 700;
  color: var(--text-muted);
  background: var(--paper-deep);
  border-radius: var(--radius-pill);
  padding: 2px 8px;
  white-space: nowrap;
}
/* Badge hạng dùng --gold khi có hạng thật (bảng loyalty_tiers) — chỉ thêm 1
   class biến thể, .account-tier-badge vẫn lo kích thước/bo góc/padding chung. */
.account-tier-badge--gold {
  color: var(--gold-text);
  background: #fbf3e0;
}

.account-sidebar-points {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-2) var(--space-2) var(--space-4);
  margin-bottom: var(--space-2);
  border-bottom: 1px solid var(--line);
}
.account-sidebar-points-label { font-size: var(--fs-sm); color: var(--text-muted); font-weight: 600; }
.account-sidebar-points-num { font-family: var(--font-display); font-size: var(--fs-lg); font-weight: 700; color: var(--koi-orange); }

.account-nav { display: flex; flex-direction: column; gap: 2px; }
.account-nav-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  /* padding dọc 12px (không phải 10px như bản dựng ban đầu) — cộng icon 20px
     (.account-nav-icon) ra đúng chiều cao chạm 44px, khớp khuyến nghị WCAG
     2.5.5 cho touch target (40px trước đó hơi dưới mức khuyến nghị). */
  padding: 12px var(--space-2);
  border-radius: var(--radius-sm);
  color: var(--ink-soft);
  font-weight: 600;
  font-size: var(--fs-base);
  text-decoration: none;
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.account-nav-item:hover,
.account-nav-item:active { background: var(--paper-deep); color: var(--ink); }
.account-nav-item.is-active { background: var(--koi-orange-tint); color: var(--koi-orange); }
.account-nav-icon { display: flex; flex-shrink: 0; width: 20px; height: 20px; }
.account-nav-icon svg { width: 100%; height: 100%; }
.account-nav-label { flex: 1; min-width: 0; }
/* Nhãn "Sắp ra mắt" cho các mục nav chưa có nội dung thật (điều kiện bởi cờ
   'soon' => true trong $accountNavItems, account.php). Hiện tại đủ 8/8 panel
   đều đã có nội dung thật nên không mục nào đang bật cờ này — class vẫn giữ
   lại làm chỗ dựa sẵn nếu sau này thêm mục "sắp ra mắt" mới. Bấm vào mục còn
   nhãn này KHÔNG chuyển panel — chỉ hiện showToast() rồi giữ nguyên mục đang
   active (xem ACCOUNT_SOON_SLUGS trong assets/js/main.js). Nhãn chỉ có tác
   dụng hiển thị, không quyết định hành vi click. */
.account-nav-soon {
  flex-shrink: 0;
  font-size: 10.5px;
  font-weight: 700;
  color: var(--text-muted);
  background: var(--paper-deep);
  border-radius: var(--radius-pill);
  padding: 2px 7px;
  white-space: nowrap;
}
.account-nav-badge {
  flex-shrink: 0;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--ink-soft);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  min-width: 20px;
  text-align: center;
  padding: 1px 6px;
}
.account-nav-logout {
  display: flex;
  align-items: center;
  /* gap + padding-inline dùng đúng token với .account-nav-item (gap:
     var(--space-3), padding ngang: var(--space-2)) để icon "Đăng xuất" canh
     thẳng cột với các icon nav phía trên — trước đó gap:8px + không có
     padding ngang khiến icon lệch sang trái so với các mục khác. */
  gap: var(--space-3);
  padding: 0 var(--space-2);
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--line);
  font-weight: 700;
  font-size: var(--fs-sm);
  color: var(--koi-red);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-standard);
}
.account-nav-logout:hover,
.account-nav-logout:active { color: #93131b; }
/* Giờ là <button> trong <form> (đăng xuất phải POST) — reset kiểu nút để giữ nguyên diện mạo link cũ. */
.account-logout-form { margin: 0; }
button.account-nav-logout {
  width: 100%;
  background: none;
  border: 0;
  border-top: 1px solid var(--line);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}

.account-content { min-width: 0; }
/* Dock nổi kiểu iOS (.account-dock, đang dùng thật trong account.php) + vùng
   kéo đóng trong sheet: chỉ có ý nghĩa ở mobile (xem khối "mobile bottom sheet"
   trong @media max-width:900px bên dưới). Khai "tắt" ở đây, "bật" trong media
   query — theo đúng quy ước của .mobile-bottom-nav/.category-sheet/
   .mobile-hero-search ở đầu file, tránh hiện nhầm ở desktop nếu đổi thứ tự
   media query sau này. .account-dock từng thiếu ở danh sách này -> hiện luôn
   cả ở desktop (>900px) chồng lên layout sidebar tĩnh, đã bổ sung lại cho
   đúng quy ước. (Thanh "Mục hiện tại ▾" cũ, .account-mobile-bar,
   đã bị thay hẳn bởi .account-dock và markup/CSS của nó đã được dọn bỏ.) */
.account-sheet-draghandle,
.account-dock {
  display: none;
}
.account-greet-h1 { margin: 0 0 var(--space-5); }

.account-quick-stats {
  display: grid;
  /* 4 cột: tổng đơn / đang xử lý / điểm thưởng / voucher khả dụng. Đã bỏ ô
     "sản phẩm yêu thích" (wishlist) — số đó đã hiện sẵn trên badge mục
     "yeu-thich" ở sidebar ngay bên trái, giữ lại ở đây là trùng lặp. */
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}
.account-stat-card {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-4);
  text-align: center;
  box-shadow: 0 6px 20px -16px rgba(18, 34, 44, 0.25);
}
.account-stat-num { font-family: var(--font-display); font-size: 26px; font-weight: 700; color: var(--ink); }
.account-stat-label { margin-top: 4px; font-size: var(--fs-xs); color: var(--text-muted); }

/* .account-panels: containing block cho .account-panel.is-leaving (position:
   absolute bên dưới) — panel rời đi đè đúng lên vị trí panel đang vào thay vì
   cộng dồn chiều cao (xem cụm iOS cross-dissolve ngay trên khối
   .account-panel.js-fade-toggle ở khối reduced-motion phía trên và bản không
   rút gọn chuyển động ngay dưới đây).
   .is-switching (gán/gỡ bởi switchAccountPanel() trong main.js, đúng lúc
   panelsEl.style.height bị khoá bằng số px cụ thể) bật animate height + cắt
   phần tràn của panel rời đi: panel mới vào luồng ngay lập tức (position
   static/relative) trong khi panel cũ tách khỏi luồng (position:absolute)
   CÙNG 1 tác vụ đồng bộ -> nếu không khoá + animate height riêng, chiều cao
   container snap thẳng sang giá trị mới tức thì (không mượt), kéo theo cả
   lưới .account-shell reflow đột ngột (đẩy trôi sidebar sticky, "giật/nhảy
   scroll" — bug thực tế đã gặp). overflow:hidden CHỈ bật trong lúc chuyển
   (không phải mặc định) để không cắt mất box-shadow của .account-panel.is-shown
   lúc đứng yên. */
.account-panels { position: relative; }
.account-panels.is-switching {
  overflow: hidden;
  transition: height var(--dur-slow) var(--ease-standard);
}

/* iOS cross-dissolve cho chuyển tab "Tài khoản của tôi": thuần opacity, BỎ
   translateY kế thừa từ .js-fade-toggle gốc (pattern đó làm cho khối nhỏ,
   trượt 6px hợp lý — nhưng trượt cả 1 panel cao hàng trăm px trông nặng nề,
   không phải cảm giác iOS). Cùng 1 --dur-slow/--ease-standard cho CẢ 2 chiều
   (không tách nhanh/chậm như .js-fade-toggle gốc) để panel cũ mờ ra và panel
   mới mờ vào chạy khớp nhịp nhau — đúng kiểu UIView crossDissolve của iOS.
   Dùng --dur-slow (300ms, không phải --dur-base 200ms mặc định của
   .js-fade-toggle) vì bảng token mô tả đúng trường hợp này ("fade cả khối
   nội dung", xem ANIMATION_GUIDE.md mục 1) — panel to hơn hẳn 1 khối nhỏ, và
   300ms cho mắt kịp theo khi chiều cao trang co lại (chuyển từ panel dài về
   panel ngắn), đỡ giật hơn 200ms. JS (hideAccountPanel(), main.js) đợi đúng
   300ms này rồi mới gỡ hẳn panel cũ khỏi luồng — 2 số PHẢI khớp nhau. */
.account-panel.js-fade-toggle,
.account-panel.js-fade-toggle.is-visible {
  /* Loại trừ khỏi cơ chế max-height mới của .js-fade-toggle gốc (xem comment đầy đủ
     ngay trên khối .js-fade-toggle, phần "NGOẠI LỆ: .account-panel") — panel này animate
     chiều cao ở CẤP .account-panels (khung bao ngoài), không phải ở chính nó. */
  max-height: none;
  overflow: visible;
  transform: none;
  transition: opacity var(--dur-slow) var(--ease-standard);
}

/* Panel đang rời đi (gắn bởi hideAccountPanel() trong main.js ngay khi bấm
   tab khác): tách khỏi luồng, đè đúng vị trí cũ trong lúc mờ dần, để KHÔNG
   cộng dồn chiều cao với panel mới đang vào luồng bình thường bên dưới — đây
   là bản vá lỗi "thấy 2 tab cùng lúc + giật khi chiều cao co lại đột ngột".
   Không cần is-leaving khi bấm nhanh nhiều lần: hideAccountPanel() luôn dọn
   .is-leaving trước khi phần tử được fadeToggle() cho vào lại (xem main.js).
   Selector CỐ Ý viết thành .is-shown.is-leaving (không phải .is-leaving suông):
   hideAccountPanel() luôn giữ nguyên .is-shown trên panel cũ suốt 300ms rời đi
   (chỉ gỡ is-shown+is-leaving CÙNG LÚC ở cuối, xem main.js) nên 2 class này
   luôn đi cùng nhau trên thực tế — viết compound selector 3 lớp để chắc chắn
   thắng specificity so với rule ".account-panel.is-shown" (2 lớp, z-index:2)
   ngay dưới, bất kể thứ tự khai báo trong file. Nếu chỉ dùng ".is-leaving"
   suông (2 lớp, specificity NGANG .is-shown) thì rule nào đứng SAU trong file
   sẽ thắng — quá mong manh, dễ vỡ lại nếu ai đó sắp xếp lại code sau này. */
.account-panel.is-shown.is-leaving {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1;
}

/* .account-panel dùng chung pattern .js-fade-toggle (display:none mặc định,
   xem khối .js-fade-toggle phía trên) — chỉ khai display THẬT SỰ khi
   .is-shown, giống hệt .coupon-form.is-shown/.online-methods-box.is-shown ở
   nơi khác đang làm. KHÔNG viết display:none riêng ở đây (đã có sẵn từ
   .js-fade-toggle) để tránh trùng lặp/lệch pattern.
   position:relative + z-index:2: KHÔNG đổi vị trí (relative không kèm
   top/left) — chỉ để z-index có tác dụng. Cần thiết vì panel MỚI (đang vào,
   vẫn position mặc định static trong .account-panels) và panel CŨ (đang rời,
   .is-shown.is-leaving ở trên, position:absolute) khác kiểu position: trình
   duyệt LUÔN vẽ phần tử positioned (absolute) SAU phần tử static bất kể thứ
   tự trong DOM — nếu không có z-index rõ ràng ở đây, panel cũ (dù đang mờ
   dần) sẽ đè lên chữ/khối của panel mới suốt 300ms crossfade ("ghosting" —
   bug thực tế đã gặp). z-index:2 > z-index:1 của .is-leaving đảm bảo panel
   đang hiện luôn ở trên cùng. */
.account-panel.is-shown {
  display: block;
  position: relative;
  z-index: 2;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-6);
  box-shadow: 0 6px 20px -16px rgba(18, 34, 44, 0.25);
}

/* 2 khối "Thông tin cá nhân"/"Đổi mật khẩu" bên trong panel Tổng quan (xem
   giải thích ở comment đầu khối account sidebar layout phía trên).
   .account-subcard-bordered dùng cho khối thứ 2 trở đi để có đường kẻ phân
   cách phía trên; khối đầu tiên không cần viền trên. */
.account-subcard { margin-bottom: var(--space-6); }
.account-subcard:last-child { margin-bottom: 0; }
.account-subcard-bordered {
  padding-top: var(--space-6);
  border-top: 1px solid var(--line);
}

/* Hàng tiêu đề + link "Xem tất cả" bên phải, dùng chung cho 3 khối trong
   panel Tổng quan: "Đơn hàng gần đây", "Sổ địa chỉ", "Ưu đãi của tôi" — link
   dùng lại .text-accent-orange có sẵn, không tạo màu/link style mới. gap =
   12px (8px hơi sát khi tiêu đề dài đứng cạnh link, nhất là trong 2 cột hẹp
   của .account-overview-row bên dưới) — h2/link vẫn đứng 2 đầu hàng nhờ
   justify-content:space-between, gap chỉ là khoảng CÁCH TỐI THIỂU khi nội
   dung dài phải xuống dòng. */
.account-subcard-header-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); flex-wrap: wrap; margin-bottom: var(--space-4); }
.account-subcard-header-row h2 { margin-bottom: 0; }

/* Hàng 2 cột "Sổ địa chỉ"/"Ưu đãi của tôi" (bản preview rút gọn) trong panel
   Tổng quan, ngay sau khối "Đơn hàng gần đây". Theo ĐÚNG pattern
   repeat(2,1fr) đã dùng ở .account-address-list/.account-voucher-list bên
   dưới (đã kiểm tra trước khi đặt tên mới) — đặt riêng vì đây là 2 KHỐI KHÁC
   LOẠI cạnh nhau (không phải danh sách nhiều thẻ cùng loại như 2 class kia).
   Xuống 1 cột dưới 900px cùng breakpoint .account-shell (xem khối @media
   cuối file), không tự đặt mốc mới.
   .account-preview-list: xếp DỌC các card preview bên trong bằng gap (không
   dùng lại gap của .account-address-list/.account-voucher-list vì các card
   preview ở đây không phải grid item của 2 list gốc đó). */
.account-overview-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-5); margin-bottom: var(--space-6); }
.account-preview-list { display: flex; flex-direction: column; gap: var(--space-3); }

/* .account-preview-card: khung boxed dùng CHUNG cho cả 3 khối "xem nhanh" của
   panel Tổng quan — "Đơn hàng gần đây" (đứng riêng 1 hàng) + "Sổ địa chỉ"/"Ưu
   đãi của tôi" (2 cột trong .account-overview-row) — để 3 khối cùng cấp này
   luôn đồng bộ 1 kiểu khung, không lệch khối này có viền/khối kia không.
   Qua vài lần chỉnh QA: (1) trước đây 3 khối chỉ là chữ nổi trong nền panel,
   không viền/nền riêng — link "+ Thêm mới" (cuối "Sổ địa chỉ") đứng sát ngay
   tiêu đề "Ưu đãi của tôi" bên cạnh, trông như dính làm một → thêm border/bo
   góc/nền trắng giống hệt .account-address-card/.account-stat-card (đã dùng
   nhiều nơi khác trong trang tài khoản) để border tự nhiên phân tách 2 khối
   rõ ràng; (2) sau khi thêm khung, padding var(--space-4)=16px vẫn khiến chữ
   tiêu đề nằm khá sát viền/góc khung, hơi ngột ngạt → tăng lên
   var(--space-5)=24px cho thoáng hơn, cân đối hơn; (3) rà soát QA đợt sau đối
   chiếu bản thiết kế đề xuất, 24px lại hoá ra thoáng hơn thiết kế mẫu → giảm
   xuống 20px (xem giá trị + comment ngay tại khai báo padding bên dưới).
   margin-bottom đặt lại 0
   (thay vì kế thừa var(--space-6) mặc định của .account-subcard) vì khoảng
   cách dưới mỗi khối giờ do chính padding của khung lo — RIÊNG khối "Đơn hàng
   gần đây" (không nằm trong .account-overview-row, đứng 1 mình trước khi vào
   hàng 2 cột) vẫn cần margin-bottom var(--space-6) để tách với hàng 2 cột
   ngay dưới, nên KHÔNG áp dụng override này cho nó — xem selector con
   ".account-overview-row .account-preview-card" ngay dưới. */
.account-preview-card {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  /* 20px (giữa var(--space-4)=16px và var(--space-5)=24px cũ) — rà soát QA
     đợt sau (đối chiếu bản thiết kế đề xuất "Sidebar cửa hàng lớn"): 24px
     khiến tiêu đề nằm xa viền hơn hẳn so với thiết kế mẫu, trong khi 16px lại
     từng bị chê quá sát ở đợt QA trước (xem comment phía trên) — 20px cân
     bằng cả 2 lần góp ý, không dùng lại đúng biến --space-4/--space-5 sẵn có. */
  padding: 20px;
}
.account-overview-row .account-preview-card { margin-bottom: 0; }

/* "Thông tin cá nhân" + "Đổi mật khẩu" gộp chung 1 subcard trong panel Bảo mật
   (account.php, nhánh 'bao-mat') — trước đây 2 khối xếp DỌC, mỗi form
   input/textarea width:100% kéo theo hết bề rộng panel (~700-900px desktop),
   khiến ô "Số điện thoại"/3 ô mật khẩu (vốn ngắn) bị trải dài quá mức + tổng
   2 khối chiếm rất nhiều chiều cao trang. 2 <form> bên
   trong vẫn hoàn toàn độc lập, chỉ gộp phần hiển thị. Ngăn cách 2 cột bằng 1
   viền trái mảnh (--line) — cùng tông với mọi đường kẻ phân tách khác trong
   trang tài khoản (.account-subcard-bordered dùng border-top cho các khối
   XẾP DỌC, ở đây xoay ngang thành border-left vì 2 khối đứng CẠNH NHAU).

   QUAN TRỌNG — lý do dùng fr thay vì %: bản đầu dùng "60% 40%" (tổng đúng
   100% bề rộng khung chứa) CỘNG THÊM gap var(--space-6)=32px → tổng bề rộng
   grid đòi hỏi = 100% + 32px, VƯỢT quá bề rộng thật của khung chứa (khác
   grid-template-columns dùng đơn vị fr, vốn tự trừ gap ra khỏi phần không
   gian rồi mới chia tỉ lệ) → khối "Đổi mật khẩu" (cột 2) bị đẩy tràn ra ngoài
   panel, lệch hẳn khỏi hàng với "Thông tin cá nhân".
   Sửa dứt điểm bằng đơn vị fr (luôn tự trừ gap trước khi chia, không bao giờ
   tràn). Tỉ lệ cũng giảm từ 60/40 xuống 50/50 (thu hẹp cột "Thông tin cá
   nhân" thêm ~1/6, gần khớp yêu cầu QA "giảm thêm khoảng 20%") để cột "Đổi
   mật khẩu" có thêm chỗ thở, không bị ép hẹp cạnh cột kia. */
.account-profile-security-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-6);
}
.account-profile-security-col--password {
  padding-left: var(--space-5);
  border-left: 1px solid var(--line);
}

/* Panel "Đánh giá của tôi": tái dùng .review-list/.review-card--bordered/
   .review-card-head/.review-card-date/.review-card-stars/.review-star-icon/
   .review-card-comment có sẵn (product.php/index.php, đánh giá công khai) —
   chỉ 2 khối mới dưới đây mà bản public không cần: dòng sản phẩm kèm ảnh
   thumbnail, và nút "Xoá đánh giá" dùng lại .btn/.btn-outline/.btn-sm/
   .btn-danger-outline có sẵn. Badge trạng thái ("Chờ duyệt"/"Đã duyệt") dùng
   lại .status-badge (trạng thái đơn hàng) + màu inline theo --gold/--jade. */
.account-review-product { display: flex; align-items: center; gap: 10px; margin: 4px 0 10px; }
.account-review-product-thumb { width: 40px; height: 40px; object-fit: cover; border-radius: var(--radius-sm); background: var(--paper-deep); flex-shrink: 0; }
.account-review-product-name { font-size: var(--fs-sm); font-weight: 600; color: var(--ink); transition: color var(--dur-fast) var(--ease-standard); }
.account-review-product:hover .account-review-product-name { color: var(--koi-orange); }

/* Panel "Sổ địa chỉ": card nhỏ border mảnh, badge "Mặc định" ở góc trên phải,
   tỉ lệ khoảng trắng/viền hài hoà với các card khác trong panel
   (.review-card--bordered, .order-history-card). Grid 2 cột desktop, xuống 1
   cột dưới 900px cùng lúc .account-shell đổi 1 cột (không cần breakpoint riêng). */
.account-address-list {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}
.account-address-card {
  position: relative;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-4);
  background: #fff;
}
.account-address-badge-default {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  padding: 2px 10px;
  border-radius: var(--radius-pill);
  background: var(--jade-tint);
  color: var(--jade);
  font-size: var(--fs-xs);
  font-weight: 700;
  white-space: nowrap;
}
.account-address-label {
  font-size: var(--fs-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-muted);
  margin-bottom: 2px;
}
.account-address-receiver {
  font-weight: 700;
  color: var(--ink);
  /* Chừa chỗ bên phải cho badge "Mặc định" tuyệt đối hoá ở trên, phòng tên +
     SĐT dài trên máy hẹp bị đè lên badge thay vì tự xuống dòng trước. */
  padding-right: 90px;
}
.account-address-line {
  margin: 4px 0 0;
  color: var(--ink-soft);
  font-size: var(--fs-sm);
  line-height: 1.5;
}
.account-address-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
/* Ép mọi nút trong hàng này rộng vừa đủ chữ (giống .btn-fit-center ở
   order_detail.php, xem comment gốc tại đó) — không dùng thẳng .btn-fit-center
   vì class đó kèm margin:auto để TỰ CANH GIỮA 1 nút đứng riêng 1 mình, còn ở
   đây 3 nút (Sửa/Đặt mặc định/Xoá) đứng cạnh nhau theo hàng ngang, canh trái,
   dùng chung `gap` của .account-address-actions — không cần/không nên có
   margin:auto. Bọc `width: fit-content` trực tiếp cho `.btn` bất kể nút đó có
   nằm trong <form class="account-address-inline-form"> (display:contents,
   xem comment dưới) hay không, để chắc chắn cả 3 nút cùng rộng vừa đủ chữ như
   nhau — nút "Sửa" (không bọc form) vốn đã đúng, 2 nút còn lại từng bị kéo
   giãn hết chiều rộng hàng ở 1 số trình duyệt vì hành vi kích thước phần tử
   form con bên trong tổ tiên display:contents không nhất quán giữa các
   engine, dù .btn đã khai display:inline-flex. */
.account-address-actions .btn { width: fit-content; }
/* Mỗi nút Đặt-mặc-định/Xoá là 1 <form> riêng (submit độc lập) — form vốn là
   phần tử block, ép display:contents để KHÔNG phá layout flex hàng ngang của
   .account-address-actions (không cần bọc thêm 1 lớp div nào khác). */
.account-address-inline-form { display: contents; }
.account-address-form-wrap {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px dashed var(--line);
}
/* Popup Sửa/Thêm địa chỉ — tái dùng đúng .confirm-modal-overlay/.confirm-modal-box (đã
   có sẵn animation/token 2 chiều mở-đóng + khối prefers-reduced-motion riêng, xem
   ANIMATION_GUIDE.md mục 6+8 và khối @media cuối file), chỉ thêm biến thể RỘNG hơn +
   CĂN TRÁI vì đây là 1 form nhiều field, không phải hộp thoại xác nhận ngắn 1-2 dòng
   text-align:center như .confirm-modal-box gốc. JS (initAccountAddressBook(), main.js)
   di chuyển đúng node .account-address-form-wrap thật vào đây lúc mở, trả về đúng vị trí
   cũ lúc đóng — không có form/markup nào bị nhân đôi. */
.account-address-modal-box {
  max-width: 480px;
  max-height: 85vh;
  overflow-y: auto;
  /* Cùng lý do đã dùng cho .category-sheet-body/.account-sheet-body: cuộn mượt kiểu
     quán tính trên iOS Safari + không cho cuộn hết đà bên trong popup "tràn" ra ngoài
     cuộn trang nền phía sau (kể cả trong pha cuộn theo đà/momentum mà JS chặn cuộn nền
     ở main.js — lockBodyScroll()/handleLockTouchMove() — không còn kịp can thiệp). */
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  text-align: left;
}
.account-address-modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.account-address-modal-title { margin: 0; font-size: 18px; }
.account-address-modal-close {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: 50%;
  font-size: 20px;
  line-height: 1;
  color: var(--text-muted);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.account-address-modal-close:hover,
.account-address-modal-close:active { background: rgba(18,34,44,.06); color: var(--ink); }
/* Bản thân .confirm-modal-box đã lo trọn phần chuyển động "mở popup" (fade + scale nhẹ,
   xem transform ở trên) — tắt hẳn animation max-height/opacity/translateY RIÊNG của
   .js-fade-toggle khi wrap đang nằm trong popup để tránh 2 lớp chuyển động chồng nhau
   (double animation), snap thẳng sang trạng thái is-visible ngay khi JS thêm class. */
.account-address-modal-box .js-fade-toggle { transition: none; }
/* Bỏ viền/khoảng cách "phân cách với card phía trên" — trong popup, form luôn là nội
   dung DUY NHẤT của box, không đứng sau gì cả (khác lúc còn nằm inline trong card). */
.account-address-modal-box .account-address-form-wrap {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}
/* Khối form thêm/sửa địa chỉ dùng .js-fade-toggle (mặc định display:none) nên
   cần khai rõ display thật khi .is-shown, kể cả khi server-render sẵn is-shown
   lúc có lỗi validate ($addressOpenAdd/$addressOpenEditId) — theo đúng pattern
   đã dùng cho .coupon-form.is-shown/.online-methods-box.is-shown ở trên. */
.account-address-form-wrap.is-shown { display: block; }
.account-address-form-actions {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
/* Nút "+ Thêm địa chỉ mới" đứng ngoài .account-address-list (không phải 1 card),
   form thêm mới theo sau cũng dùng chung .account-address-form-wrap nhưng không
   cần border-top (không có gì phía trên nó trong cùng khối để phân cách). */
#addressAddForm.account-address-form-wrap { border-top: none; padding-top: 0; }

/* =====================================================================
   VOUCHER — thẻ ưu đãi 4 kiểu theo loại mã + chi tiết + bộ chọn trong giỏ
   (markup: includes/voucher.php; hành vi: khối "Voucher" trong main.js).
     A  Sóng Koi      — mã cơ bản (giảm %, giảm tiền, freeship)
     B  Nơ quà tặng   — khách mới
     D  Thẻ đen ánh kim — hội viên
     E  Vườn xanh     — mã giới hạn danh mục/sản phẩm
   Màu theo loại qua class .vtone-* (biến --vc-*): freeship ngọc, giảm % cam,
   giảm tiền đỏ, khách mới xanh dương, hội viên vàng.
   ===================================================================== */
.vtone-free  { --vc:#1f6f6a; --vc-deep:#134a46; --vc-from:#23807a; --vc-to:#134a46; --vc-on:#fff;    --vc-tint:#e4f1ee; --vc-light:#74d4c9; }
.vtone-pct   { --vc:#c4531f; --vc-deep:#8f3710; --vc-from:#c4531f; --vc-to:#8f3710; --vc-on:#fff;    --vc-tint:#fdecdc; --vc-light:#ffa67f; }
.vtone-fixed { --vc:#c23b4a; --vc-deep:#8c2330; --vc-from:#c23b4a; --vc-to:#8c2330; --vc-on:#fff;    --vc-tint:#fbeaea; --vc-light:#ff95a2; }
.vtone-new   { --vc:#2b5fd9; --vc-deep:#1b3f96; --vc-from:#2b5fd9; --vc-to:#1b3f96; --vc-on:#fff;    --vc-tint:#eaf1fd; --vc-light:#a3bcff; }
.vtone-tier  { --vc:#c99a3a; --vc-deep:#785b21; --vc-from:#e6c366; --vc-to:#c99a3a; --vc-on:#12222c; --vc-tint:#faf3e4; --vc-light:#f3d27f; }

/* ---------- Lưới kho voucher ---------- */
.account-voucher-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
  gap: var(--space-5) var(--space-4);
  margin-bottom: var(--space-5);
}
.account-preview-list.account-voucher-grid { grid-template-columns: 1fr; gap: var(--space-4); margin-bottom: 0; }
.account-voucher-preview { display: flex; gap: 10px; align-items: stretch; }
.account-voucher-preview .account-voucher-grid { flex: 1; min-width: 0; }
/* Cột "Kho voucher": nền tối ánh vàng cùng tông thẻ hội viên; lấp phần trống bên phải thẻ đã thu ngắn, đồng thời là lối vào kho. */
.vrail {
  flex: 0 0 58px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  border-radius: 14px; border: 1px solid rgba(243, 210, 127, .4); color: #f3d27f; text-decoration: none;
  background: linear-gradient(160deg, #18323f, #0a141a);
  box-shadow: 0 1px 1px rgba(18, 34, 44, .1), 0 12px 22px -12px rgba(18, 34, 44, .35);
  transition: transform var(--dur-base) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
}
.vrail b { font-family: var(--font-display); font-size: 28px; line-height: 1; font-weight: 700; }
.vrail span { font-size: 11px; color: #e9dfc2; }
.vrail svg { margin-top: 6px; transition: transform var(--dur-base) var(--ease-standard); }
.vrail:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }
@media (hover: hover) {
  .vrail:hover { border-color: #f3d27f; }
  .vrail:hover svg { transform: translateX(3px); }
}
.vrail:active { transform: scale(.97); }
@media (prefers-reduced-motion: reduce) { .vrail, .vrail svg { transition: none; } }

/* ---------- Ô voucher: thẻ + nút ---------- */
.vcell { container-type: inline-size; display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
.vcell__card { position: relative; }
.vcell__open {
  position: absolute; inset: 0; z-index: 2; width: 100%; height: 100%;
  border: 0; padding: 0; background: transparent; cursor: pointer; border-radius: 14px;
}
.vcell:has(.vcard--e) .vcell__open { border-radius: 22px; }
.vcell__open:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }
.vcell__personal {
  /* Nhãn "Riêng bạn": tab nhỏ gắn sát GÓC TRÊN-PHẢI của thẻ (bo theo góc thẻ), nằm TRONG khung thẻ nên không
     chiếm thêm chiều cao như bản cũ (viên thuốc treo lệch ra ngoài đáy thẻ, đè lên hàng nút). Vùng góc này
     ở cả 4 kiểu thẻ (A/B/D/E) đều trống nên không che chữ. */
  position: absolute; z-index: 3; top: 0; right: 0; pointer-events: none;
  display: inline-flex; align-items: center; gap: 4px; padding: 3px 9px 4px 8px;
  border-radius: 0 14px 0 10px; background: var(--koi-orange); color: #fff;
  font-size: 10.5px; font-weight: 700; line-height: 1.2; letter-spacing: .02em; white-space: nowrap;
  box-shadow: -1px 1px 0 rgba(255,255,255,.35) inset, 0 1px 3px rgba(18,34,44,.2);
}
.vcell__personal svg { flex: none; }
.vcell:has(.vcard--e) .vcell__personal { border-top-right-radius: 22px; }

.vcell__actions { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2); }
.vcell__btn {
  display: inline-flex; align-items: center; justify-content: center; min-height: 44px; padding: 0 12px;
  border-radius: 10px; font: inherit; font-size: var(--fs-sm); font-weight: 700; text-decoration: none; cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-standard), transform var(--dur-instant) var(--ease-standard);
}
.vcell__btn:active { transform: scale(.97); }
.vcell__btn:focus-visible { outline: 3px solid var(--ink); outline-offset: 2px; }
.vcell__btn--ghost { border: 1px solid rgba(18,34,44,.2); background: #fff; color: var(--ink); }
.vcell__btn--use { border: 0; background: var(--vc-deep); color: #fff; }
@media (hover: hover) {
  .vcell__btn--ghost:hover { background: var(--paper); }
  .vcell__btn--use:hover { background: var(--vc); color: var(--vc-on); }
}

/* ---------- Mặt thẻ (chung) ---------- */
.vcard {
  --vc-big: clamp(38px, 15.5cqw, 66px);
  position: relative; display: grid; min-height: 156px; border-radius: 14px; overflow: hidden; font-family: var(--font-body);
  box-shadow: 0 1px 1px rgba(18,34,44,.1), 0 12px 22px -12px rgba(18,34,44,.35);
  transition: transform var(--dur-base) var(--ease-standard);
}
@media (hover: hover) { .vcell__card:hover .vcard { transform: translateY(-3px); } }
.vcard__big { font-family: var(--font-display); font-weight: 700; line-height: 1; font-size: var(--vc-big); white-space: nowrap; }
.vcard__sub { font-size: 12px; }
.vcard__exp { font-size: 11.5px; }
.vcard__code { font-weight: 700; letter-spacing: .04em; overflow-wrap: anywhere; }
.vcard__main { min-width: 0; }
/* Mã dài (>12 ký tự): chữ mã nhỏ hơn để không chiếm 3–4 dòng. Thẻ cao tối thiểu 156px, tự cao thêm nếu HSD/"còn X ngày" xuống dòng. */
.vcard--long .vcard__code { font-size: 10.5px; letter-spacing: 0; }
.vcard--long.vcard--a .vcard__code, .vcard--long.vcard--b .vcard__code, .vcard--long.vcard--e .vcard__code { font-size: 10.5px; }

/* A — Sóng Koi */
.vcard--a {
  grid-template-columns: 60fr 40fr; color: var(--vc-on);
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='18'%3E%3Cg fill='none' stroke='%23fff' stroke-opacity='.17' stroke-width='1.4'%3E%3Ccircle cx='18' cy='18' r='16'/%3E%3Ccircle cx='18' cy='18' r='11'/%3E%3Ccircle cx='18' cy='18' r='6'/%3E%3Ccircle cx='0' cy='9' r='16'/%3E%3Ccircle cx='0' cy='9' r='11'/%3E%3Ccircle cx='0' cy='9' r='6'/%3E%3Ccircle cx='36' cy='9' r='16'/%3E%3Ccircle cx='36' cy='9' r='11'/%3E%3Ccircle cx='36' cy='9' r='6'/%3E%3C/g%3E%3C/svg%3E"),
    linear-gradient(135deg, var(--vc-from), var(--vc-to));
}
.vcard--a .vcard__main { padding: 14px 0 14px 18px; display: flex; flex-direction: column; justify-content: center; }
.vcard--a .vcard__top { display: flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 700; letter-spacing: .1em; }
.vcard--a .vcard__big { margin: 2px 0 4px; text-shadow: 0 2px 0 rgba(0,0,0,.14); }
.vcard--a .vcard__stamp {
  background: #fffaf0; color: var(--ink); display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; padding: 0 8px 0 14px; min-width: 0;
  -webkit-mask: radial-gradient(circle 6px at 0 50%, #0000 5.5px, #000 6px) 0 0 / 100% 18px repeat-y;
          mask: radial-gradient(circle 6px at 0 50%, #0000 5.5px, #000 6px) 0 0 / 100% 18px repeat-y;
}
.vcard--a .vcard__stamp-lbl { font-size: 10px; letter-spacing: .12em; color: #57606b; font-weight: 600; }
.vcard--a .vcard__code { font-size: 12.5px; letter-spacing: .02em; padding: 6px 7px; border: 1.5px dashed var(--vc); color: var(--vc-deep); border-radius: 8px; background: #fff; text-align: center; max-width: 100%; box-sizing: border-box; }
.vcard--a .vcard__exp { color: #57606b; text-align: center; }

/* B — Nơ quà tặng */
.vcard--b { grid-template-columns: 68fr 32fr; background: #fff; color: var(--ink); }
.vcard--b .vcard__main { padding: 14px 0 14px 20px; display: flex; flex-direction: column; justify-content: center; gap: 2px; }
.vcard--b .vcard__tag { font-size: 11px; font-weight: 700; letter-spacing: .14em; color: var(--vc-deep); }
.vcard--b .vcard__row { display: flex; align-items: baseline; gap: 8px; }
.vcard--b .vcard__big { color: var(--vc-deep); font-size: calc(var(--vc-big) * .86); line-height: 1.05; }
.vcard--b .vcard__top { font-size: 12px; font-weight: 700; letter-spacing: .08em; color: #3d4852; }
.vcard--b .vcard__sub { color: #3d4852; }
.vcard--b .vcard__row--meta { align-items: center; margin-top: 6px; flex-wrap: wrap; }
.vcard--b .vcard__code { font-size: 12.5px; padding: 4px 11px; border: 1.5px solid var(--ink); border-radius: 999px; }
.vcard--b .vcard__exp { color: #57606b; }
.vcard--b .vcard__side { background: linear-gradient(160deg, var(--vc-from), var(--vc-to)); display: flex; align-items: center; justify-content: flex-end; padding-right: 14px; }
.vcard--b .vcard__vert { writing-mode: vertical-rl; transform: rotate(180deg); font-size: 13px; font-weight: 700; letter-spacing: .3em; color: var(--vc-on); }
.vcard--b .vcard__bow { position: absolute; right: calc(32% - 46px); top: 40px; color: var(--vc-deep); filter: drop-shadow(0 3px 3px rgba(18,34,44,.25)); }

/* D — Thẻ đen ánh kim */
.vcard--d {
  grid-template-columns: 68fr 32fr; color: #fff;
  background: repeating-linear-gradient(45deg, rgba(255,255,255,.06) 0 1px, transparent 1px 9px), linear-gradient(135deg, #18323f, #0a141a);
}
.vcard--d .vcard__mark { position: absolute; right: -14px; bottom: -22px; opacity: .12; color: var(--vc-light); display: block; line-height: 0; }
.vcard--d .vcard__frame { position: absolute; inset: 7px; border: 1px solid var(--vc-light); border-radius: 9px; opacity: .4; pointer-events: none; }
.vcard--d .vcard__main { padding: 16px 0 16px 22px; display: flex; flex-direction: column; justify-content: center; gap: 2px; position: relative; }
.vcard--d .vcard__tag { font-size: 11px; font-weight: 700; letter-spacing: .14em; color: var(--vc-light); }
.vcard--d .vcard__big {
  font-size: calc(var(--vc-big) * .92); line-height: 1.05;
  background: linear-gradient(180deg, #fff7de 0%, var(--vc-light) 62%, var(--vc) 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent;
}
.vcard--d .vcard__sub { color: rgba(255,255,255,.85); }
.vcard--d .vcard__exp { color: rgba(255,255,255,.72); margin-top: 2px; }
.vcard--d .vcard__cut { position: absolute; left: 68%; top: 16px; bottom: 16px; border-left: 1.5px dashed rgba(255,255,255,.28); }
.vcard--d .vcard__stub { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; padding: 0 14px 0 8px; min-width: 0; }
.vcard--d .vcard__bars { display: flex; align-items: stretch; height: 40px; max-width: 100%; overflow: hidden; }
.vcard--d .vcard__bars i { display: block; background: var(--vc-light); flex: none; }
.vcard--d .vcard__code { font-size: 11px; color: var(--vc-light); text-align: center; }

/* E — Vườn xanh */
.vcard--e { grid-template-columns: 70fr 30fr; background: var(--vc-tint); color: var(--ink); border-radius: 22px; }
.vcard--e .vcard__leaves { position: absolute; right: 0; top: 0; color: var(--vc-deep); }
.vcard--e .vcard__main { padding: 16px 0 16px 22px; display: flex; flex-direction: column; justify-content: center; gap: 2px; position: relative; }
.vcard--e .vcard__tag { font-size: 11px; font-weight: 700; letter-spacing: .12em; color: var(--vc-deep); }
.vcard--e .vcard__big { color: var(--vc-deep); font-size: calc(var(--vc-big) * .82); line-height: 1.05; }
.vcard--e .vcard__sub { color: #3d4852; }
.vcard--e .vcard__row--meta { display: flex; align-items: center; gap: 8px; margin-top: 6px; flex-wrap: wrap; }
.vcard--e .vcard__code { font-size: 12.5px; padding: 4px 10px; border: 1.5px dashed var(--vc-deep); border-radius: 8px; color: var(--vc-deep); background: rgba(255,255,255,.7); }
.vcard--e .vcard__exp { color: #57606b; }
.vcard--e .vcard__seal {
  position: absolute; right: 16px; bottom: 14px; width: 76px; height: 76px; border-radius: 50%;
  background: linear-gradient(145deg, var(--vc-from), var(--vc-to)); color: var(--vc-on); transform: rotate(-9deg);
  box-shadow: 0 6px 12px -4px rgba(18,34,44,.4), inset 0 0 0 3px rgba(255,255,255,.35);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; text-align: center;
}
.vcard--e .vcard__seal span { font-size: 9px; font-weight: 700; letter-spacing: .04em; line-height: 1.1; padding: 0 6px; }

/* Ô hẹp (xem nhanh ở Tổng quan, điện thoại nhỏ): thu gọn chữ phụ, giữ nguyên bố cục */
@container (max-width: 320px) {
  .vcard--a .vcard__main { padding-left: 14px; }
  .vcard--a .vcard__code { font-size: 11px; letter-spacing: 0; padding: 5px 4px; }
  .vcard--a .vcard__stamp { padding-left: 12px; padding-right: 6px; }
  .vcard--b .vcard__main, .vcard--d .vcard__main, .vcard--e .vcard__main { padding-left: 16px; }
  .vcard--e .vcard__seal { width: 62px; height: 62px; right: 10px; bottom: 10px; }
  .vcard--e .vcard__seal svg { width: 20px; height: 20px; }
  .vcard--e .vcard__seal span { font-size: 8px; }
  .vcard--b .vcard__bow { transform: scale(.82); transform-origin: right center; }
}

/* ---------- Thẻ THU NHỎ (xem nhanh ở Tổng quan — renderVoucherCell(..., $compact=true)) ----------
   Không có hàng nút; thẻ thấp hơn ~35% và chữ lớn nhỏ lại để 2 thẻ không làm cao hơn khối "Sổ địa chỉ" bên cạnh.
   Bố cục từng kiểu giữ nguyên, chỉ giảm cỡ chữ/đệm/kích thước hình trang trí. Tab "Ưu đãi & Voucher" vẫn dùng thẻ cỡ đầy đủ. */
.vcell--compact { gap: 0; }
.vcell--compact .vcard { --vc-big: clamp(26px, 9cqw, 38px); min-height: 100px; }
.vcell--compact .vcard__sub { font-size: 11.5px; }
.vcell--compact .vcard__exp { font-size: 11px; }
.vcell--compact .vcard--a .vcard__main { padding: 10px 0 10px 16px; }
.vcell--compact .vcard--a .vcard__top { font-size: 10px; }
.vcell--compact .vcard--a .vcard__big { margin: 0 0 2px; }
.vcell--compact .vcard--a .vcard__stamp { gap: 4px; padding: 14px 8px 4px 12px; }
.vcell--compact .vcard--a .vcard__stamp-lbl { font-size: 9px; }
.vcell--compact .vcard--a .vcard__code { font-size: 11.5px; padding: 4px 6px; }
.vcell--compact .vcard--b .vcard__main, .vcell--compact .vcard--e .vcard__main { padding: 10px 0 10px 16px; gap: 1px; }
.vcell--compact .vcard--d .vcard__main { padding: 14px 0 12px 20px; gap: 1px; }
.vcell--compact .vcard--b .vcard__tag, .vcell--compact .vcard--d .vcard__tag, .vcell--compact .vcard--e .vcard__tag { font-size: 10px; }
.vcell--compact .vcard--b .vcard__row--meta, .vcell--compact .vcard--e .vcard__row--meta { margin-top: 3px; }
.vcell--compact .vcard--b .vcard__code, .vcell--compact .vcard--e .vcard__code { font-size: 11.5px; padding: 2px 9px; }
.vcell--compact .vcard--b .vcard__bow { top: 20px; transform: scale(.72); transform-origin: right center; }
.vcell--compact .vcard--d .vcard__cut { top: 10px; bottom: 10px; }
.vcell--compact .vcard--d .vcard__stub { gap: 6px; }
.vcell--compact .vcard--d .vcard__bars { height: 28px; }
.vcell--compact .vcard--d .vcard__mark svg { width: 90px; height: 90px; }
.vcell--compact .vcard--e .vcard__seal { width: 54px; height: 54px; right: 12px; bottom: 8px; }
.vcell--compact .vcard--e .vcard__seal svg { width: 18px; height: 18px; }
.vcell--compact .vcard--e .vcard__seal span { font-size: 7.5px; padding: 0 4px; }
.vcell--compact .vcell__personal { font-size: 9.5px; padding: 2px 8px 3px 7px; }
.account-preview-list.account-voucher-grid:has(.vcell--compact) { gap: var(--space-3); }

/* ---------- Khung modal dùng chung (chi tiết + bộ chọn) ---------- */
.vmodal-overlay {
  position: fixed; inset: 0; z-index: 200; display: flex; align-items: center; justify-content: center; padding: 16px;
  background: rgba(18,34,44,.55); opacity: 0; visibility: hidden;
  transition: opacity var(--dur-base) var(--ease-standard), visibility 0s linear var(--dur-base);
}
.vmodal-overlay.show { opacity: 1; visibility: visible; transition: opacity var(--dur-base) var(--ease-standard), visibility 0s linear 0s; }
body:has(.vmodal-overlay) .toast-container { z-index: 300; }
.vmodal-box { transform: translateY(12px); transition: transform var(--dur-slow) var(--ease-enter); }
.vmodal-overlay.show .vmodal-box { transform: none; }

/* ---------- Chi tiết voucher ---------- */
.vdetail { overscroll-behavior: contain; width: min(480px, 100%); max-height: calc(100vh - 32px); max-height: calc(100dvh - 32px); overflow-y: auto; background: #fff; border-radius: 18px; box-shadow: 0 30px 60px rgba(18,34,44,.4); font-family: var(--font-body); color: var(--ink); }
.vdetail__head { display: flex; align-items: center; gap: 16px; padding: 22px 24px; background: linear-gradient(135deg, var(--vc-from), var(--vc-to)); color: var(--vc-on); }
.vdetail__icon { flex: none; line-height: 0; }
.vdetail__headtext { flex: 1; min-width: 0; }
.vdetail__kind { font-size: 12px; font-weight: 700; letter-spacing: .08em; }
.vdetail__title { margin: 0; font-family: var(--font-display); font-size: 26px; line-height: 1.2; }
.vdetail__cap { font-size: var(--fs-sm); font-weight: 500; }
.vdetail__close { flex: none; width: 44px; height: 44px; border-radius: 50%; border: 1.5px solid currentColor; background: transparent; color: inherit; cursor: pointer; display: flex; align-items: center; justify-content: center; }
.vdetail__close:focus-visible, .vdetail__copy:focus-visible, .vdetail__use:focus-visible { outline: 3px solid var(--ink); outline-offset: 2px; }
.vdetail__body { padding: 20px 24px 24px; display: flex; flex-direction: column; gap: 18px; }
.vdetail__code { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 10px 10px 16px; border: 1.5px dashed rgba(18,34,44,.3); border-radius: 12px; }
.vdetail__code > span { font-size: 18px; font-weight: 700; letter-spacing: .08em; overflow-wrap: anywhere; }
.vdetail__copy { min-height: 44px; padding: 0 18px; border: 1px solid rgba(18,34,44,.2); border-radius: 10px; background: #fff; font: inherit; font-size: 14px; font-weight: 600; color: var(--ink); cursor: pointer; flex: none; }
.vdetail__copy.is-copied { background: var(--jade-tint); border-color: var(--jade); color: var(--jade); }
.vdetail__used-row { display: flex; justify-content: space-between; font-size: var(--fs-sm); color: #3d4852; margin-bottom: 6px; }
.vdetail__bar { height: 8px; border-radius: 99px; background: #ece7d9; overflow: hidden; }
.vdetail__bar span { display: block; height: 100%; background: var(--vc); }
.vdetail__used-note { margin-top: 4px; font-size: var(--fs-xs); color: #57606b; }
.vdetail__rows { margin: 0; display: flex; flex-direction: column; }
.vdetail__rows > div { display: flex; gap: 16px; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid rgba(18,34,44,.1); font-size: 14px; }
.vdetail__rows dt { color: #57606b; flex: none; }
.vdetail__rows dd { margin: 0; font-weight: 600; text-align: right; }
.vdetail__h3 { margin: 0 0 8px; font-family: var(--font-body); font-size: 14px; font-weight: 700; }
.vdetail__conds { margin: 0; padding-left: 20px; list-style: disc; display: flex; flex-direction: column; gap: 6px; font-size: var(--fs-sm); color: #3d4852; line-height: 1.5; }
.vdetail__use { display: flex; align-items: center; justify-content: center; min-height: 48px; border-radius: 12px; background: var(--vc-deep); color: #fff; font-size: 15px; font-weight: 700; text-decoration: none; }
@media (hover: hover) { .vdetail__use:hover { background: var(--vc); color: var(--vc-on); } }

/* ---------- Bộ chọn mã trong giỏ ---------- */
.vpicker { width: min(520px, 100%); max-height: min(720px, calc(100vh - 32px)); max-height: min(720px, calc(100dvh - 32px)); display: flex; flex-direction: column; background: var(--paper); border-radius: 18px; overflow: hidden; box-shadow: 0 30px 60px rgba(18,34,44,.4); font-family: var(--font-body); color: var(--ink); }
.vpicker__head { display: flex; align-items: center; justify-content: space-between; padding: 8px 8px 0 18px; }
.vpicker__title { margin: 0; font-family: var(--font-display); font-size: 20px; font-weight: 700; }
.vpicker__close { width: 44px; height: 44px; border: 0; background: transparent; color: var(--ink); display: flex; align-items: center; justify-content: center; cursor: pointer; }
.vpicker__close:focus-visible, .vpicker__apply:focus-visible, .vpick__more:focus-visible { outline: 3px solid var(--ink); outline-offset: 2px; }
.vpicker__list { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 8px 16px 14px; display: flex; flex-direction: column; gap: 12px; }
.vpicker__h { flex: none; margin: 6px 0 0; font-family: var(--font-body); font-size: 14px; font-weight: 700; }
.vpicker__empty { flex: none; margin: 0; padding: 14px; border-radius: 12px; background: #fff; font-size: var(--fs-sm); color: #3d4852; line-height: 1.5; }
.vpicker__login { flex: none; margin: 0; font-size: var(--fs-sm); color: #3d4852; }
.vpicker__login a { color: var(--koi-orange); font-weight: 700; }
.vpicker__loading { padding: 28px 0; text-align: center; color: #57606b; font-size: var(--fs-sm); }
.vpicker__foot { background: #fff; border-top: 1px solid rgba(18,34,44,.12); padding: 12px 16px calc(16px + env(safe-area-inset-bottom, 0px)); display: flex; flex-direction: column; gap: 10px; }
.vpicker__sum { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; font-size: var(--fs-sm); color: #3d4852; }
.vpicker__sum strong { font-size: 15px; color: var(--ink); }
.vpicker__apply { min-height: 48px; border: 0; border-radius: 12px; background: var(--ink); color: #fff; font: inherit; font-size: 15px; font-weight: 700; cursor: pointer; }
.vpicker__apply:disabled { background: #d9dde1; color: #3d4852; cursor: not-allowed; }

.vpick { flex: none; background: #fff; border-radius: 12px; box-shadow: 0 1px 3px rgba(18,34,44,.14); overflow: hidden; }
.vpick:has(.vpick__radio:checked) { box-shadow: 0 0 0 2.5px var(--vc); background: #fffaf5; }
.vpick:has(.vpick__radio:focus-visible) { outline: 3px solid var(--ink); outline-offset: 2px; }
.vpick__face { display: flex; align-items: stretch; cursor: pointer; min-height: 92px; position: relative; }
.vpick.is-off .vpick__face { cursor: default; }
.vpick__radio { position: absolute; opacity: 0; width: 1px; height: 1px; }
.vpick__stub {
  width: 84px; flex: none; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; padding: 8px 4px; text-align: center;
  color: var(--vc-on); background: linear-gradient(135deg, var(--vc-from), var(--vc-to));
}
.vpick__stublbl { font-size: 10.5px; font-weight: 700; letter-spacing: .05em; line-height: 1.2; }
.vpick--a .vpick__stub { background:
  url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='18'%3E%3Cg fill='none' stroke='%23fff' stroke-opacity='.17' stroke-width='1.4'%3E%3Ccircle cx='18' cy='18' r='16'/%3E%3Ccircle cx='18' cy='18' r='11'/%3E%3Ccircle cx='18' cy='18' r='6'/%3E%3Ccircle cx='0' cy='9' r='16'/%3E%3Ccircle cx='0' cy='9' r='11'/%3E%3Ccircle cx='0' cy='9' r='6'/%3E%3Ccircle cx='36' cy='9' r='16'/%3E%3Ccircle cx='36' cy='9' r='11'/%3E%3Ccircle cx='36' cy='9' r='6'/%3E%3C/g%3E%3C/svg%3E"),
  linear-gradient(135deg, var(--vc-from), var(--vc-to)); }
.vpick--d .vpick__stub { color: var(--vc-light); background: repeating-linear-gradient(45deg, rgba(255,255,255,.06) 0 1px, transparent 1px 9px), linear-gradient(135deg, #18323f, #0a141a); }
.vpick--e .vpick__stub { color: var(--vc-deep); background: var(--vc-tint); }
/* Mã chưa đủ điều kiện: mờ tem + tiêu đề + dòng mã (vẫn giữ màu theo loại để nhận ra loại mã), riêng LÝ DO giữ nguyên độ đậm để đọc rõ. */
.vpick.is-off .vpick__stub { opacity: .5; filter: saturate(.7); }
.vpick.is-off .vpick__title, .vpick.is-off .vpick__min, .vpick.is-off .vpick__foot { opacity: .6; }
.vpick__body { flex: 1; min-width: 0; padding: 10px 6px 8px 12px; display: flex; flex-direction: column; justify-content: center; gap: 3px; }
.vpick__title { display: flex; align-items: baseline; flex-wrap: wrap; gap: 2px 6px; }
.vpick__title b { font-size: 16px; line-height: 1.25; }
.vpick__title i { font-size: var(--fs-xs); font-style: normal; color: #57606b; }
.vpick__min { font-size: 12.5px; color: #3d4852; overflow-wrap: anywhere; }
.vpick__code { font-weight: 700; letter-spacing: .02em; color: var(--ink); }
.vpick__chips { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 2px; }
.vpick__save { font-size: 12.5px; font-weight: 700; padding: 3px 9px; border-radius: 999px; background: #e9f3ef; color: #14504c; }
.vpick__best { font-size: 11.5px; font-weight: 700; padding: 3px 8px; border-radius: 999px; background: var(--ink); color: #fff; }
.vpick__applied { font-size: 11.5px; font-weight: 700; padding: 3px 8px; border-radius: 999px; border: 1px solid var(--jade); color: var(--jade); }
.vpick__reason { display: block; margin-top: 2px; font-size: 12.5px; font-weight: 600; padding: 5px 9px; border-radius: 8px; background: #fdecdc; color: #7a3210; line-height: 1.35; }
.vpick__dot { width: 48px; flex: none; display: flex; align-items: center; justify-content: center; }
.vpick__dot::before { content: ""; width: 24px; height: 24px; box-sizing: border-box; border-radius: 50%; border: 2px solid #8a939c; background: #fff; }
.vpick.is-off .vpick__dot::before { display: none; }
.vpick__radio:checked ~ .vpick__dot::before { border-color: var(--vc); background: radial-gradient(circle, var(--vc) 0 42%, #fff 46%); }
.vpick__foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 0 8px 0 12px; border-top: 1px dashed rgba(18,34,44,.16); }
.vpick__exp { font-size: 12px; color: #57606b; }
.vpick__exp b { color: var(--koi-red); }
.vpick__more { min-height: 44px; padding: 0 6px; border: 0; background: transparent; font: inherit; font-size: 12.5px; font-weight: 700; color: var(--ink); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
.vpick__conds { margin: 0; padding: 0 16px 12px 30px; list-style: disc; display: flex; flex-direction: column; gap: 4px; font-size: 12.5px; color: #3d4852; line-height: 1.45; }
.vpick__conds[hidden] { display: none; }

@media (max-width: 640px) {
  .vmodal-overlay.vmodal-overlay--sheet { align-items: flex-end; padding: 0; }
  .vmodal-overlay--sheet .vpicker { width: 100%; max-height: calc(100vh - 48px); max-height: calc(100dvh - 48px); border-radius: 20px 20px 0 0; }
  .vmodal-overlay--sheet .vmodal-box { transform: translateY(40px); }
  .vmodal-overlay--sheet.show .vmodal-box { transform: none; }
  .vdetail__head { padding: 18px 16px; gap: 12px; }
  .vdetail__body { padding: 16px; }
  .vdetail__title { font-size: 22px; }
}

/* ---------- Giỏ hàng: nút "Chọn mã", "Đổi mã", thanh tiến độ freeship ---------- */
.coupon-form { flex-wrap: wrap; }
.coupon-pick-btn {
  flex: 1 1 100%; display: flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 12px;
  border: 1.5px dashed rgba(217,102,59,.6); border-radius: var(--radius-sm); background: #fdf3ec; color: var(--ink);
  font: inherit; font-size: var(--fs-sm); font-weight: 700; cursor: pointer; text-align: left;
  transition: background-color var(--dur-fast) var(--ease-standard);
}
.coupon-pick-btn span { flex: 1; }
.coupon-pick-btn svg:first-child { color: var(--koi-orange); flex: none; }
.coupon-pick-btn:focus-visible { outline: 3px solid var(--ink); outline-offset: 2px; }
@media (hover: hover) { .coupon-pick-btn:hover { background: #fbe3d2; } }
.coupon-applied-actions { display: inline-flex; align-items: center; gap: var(--space-2); flex: none; }
/* "Đổi mã"/"Gỡ mã": vùng bấm tối thiểu 44px, tách xa nhau để không bấm nhầm trên điện thoại */
.coupon-applied-actions .remove-link { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; min-width: 44px; padding: 0 10px; }
.ship-progress { margin: 4px 0 var(--space-3); padding: 10px 12px; border-radius: var(--radius-sm); background: var(--jade-tint); color: #14504c; font-size: var(--fs-sm); }
.ship-progress.is-hidden { display: none; }
.ship-progress__bar { margin-top: 8px; height: 6px; border-radius: 99px; background: rgba(31,111,106,.18); overflow: hidden; }
.ship-progress__bar span { display: block; height: 100%; max-width: 100%; border-radius: inherit; background: var(--jade); transition: width var(--dur-slow) var(--ease-standard); }

@media (prefers-reduced-motion: reduce) {
  .vcard, .vmodal-overlay, .vmodal-box, .vcell__btn, .ship-progress__bar span { transition: none !important; }
  .vcell__card:hover .vcard { transform: none; }
}

/* Khối "Điểm thưởng & Hạng thành viên" thật, đầu panel Tổng quan (xem nhánh
   'tong-quan' trong account.php). Điểm lớn nổi bật bên trái, hạng + tiến độ
   lên hạng kế tiếp bên phải. Bố cục flex 2 phần trên desktop, ép xuống 1 cột
   (điểm trước, hạng sau) ở mobile trong @media max-width:900px ngay dưới đây. */
.account-loyalty-card {
  display: flex;
  gap: var(--space-6);
  align-items: center;
}
.account-loyalty-points {
  flex-shrink: 0;
  text-align: center;
  padding: var(--space-4) var(--space-5);
  background: var(--paper);
  border-radius: var(--radius);
  min-width: 120px;
}
.account-loyalty-points-num {
  font-family: var(--font-display);
  font-size: 32px;
  font-weight: 700;
  color: var(--koi-orange);
  line-height: 1.1;
}
.account-loyalty-points-label { margin-top: 4px; font-size: var(--fs-xs); color: var(--text-muted); }
.account-loyalty-tier { flex: 1; min-width: 0; }
.account-loyalty-next { margin: var(--space-2) 0 0; font-size: var(--fs-sm); color: var(--ink-soft); }
.account-loyalty-benefits { margin: var(--space-2) 0 0; font-size: var(--fs-sm); color: var(--text-muted); }
.account-loyalty-progress {
  margin-top: var(--space-2);
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--paper-deep);
  overflow: hidden;
}
/* Thanh gradient cam->vàng: mượn đúng cặp màu --koi-orange/--gold đã dùng cho
   .coupon-banner ở nơi khác trong file này (dòng ~1748) cho 1 dải màu "phần
   thưởng/thành tựu" nhất quán, không tự bịa cặp màu mới. Chiều rộng do PHP
   tính sẵn và in thẳng vào style="width:..%" (không qua JS/AJAX nào đổi lại
   sau khi tải trang) nên transition dưới đây thực chất không có dịp chạy —
   không cần khai trừ riêng khỏi rule chung *:not(...) trong khối
   prefers-reduced-motion (không giống .account-panel/.account-nav-sheet, vốn
   THỰC SỰ đổi trạng thái bằng JS sau khi trang đã tải). */
.account-loyalty-progress-bar {
  height: 100%;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--koi-orange), var(--gold));
}

/* Panel "Thông báo": 3 toggle switch (email cập nhật đơn hàng/email khuyến
   mãi/SMS xác nhận). Bố cục "nhãn bên trái + công tắc bên phải" quen thuộc.
   Kỹ thuật: input[type=checkbox] thật vẫn giữ nguyên trong DOM (ẩn bằng
   opacity:0 + position:absolute, không display:none/visibility:hidden) để
   vẫn bấm Tab tới được + đọc được bằng trình đọc màn hình + gửi đúng giá trị
   khi submit form POST thường — track/knob chỉ là 2 <span> trang trí, đổi
   hình theo :checked của input thật ngay trước đó (CSS sibling selector). */
.account-toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
}
.account-toggle-row:last-of-type { border-bottom: none; }
.account-toggle-row:hover .account-toggle-track { border-color: var(--jade); }
.account-toggle-text { flex: 1; min-width: 0; }
.account-toggle-label { display: block; font-weight: 600; color: var(--ink); }
.account-toggle-desc { display: block; margin-top: 2px; font-size: var(--fs-xs); color: var(--text-muted); }

.account-toggle-switch {
  position: relative;
  flex-shrink: 0;
  display: inline-block;
  width: 44px;
  height: 26px;
}
/* Input thật: chiếm ĐÚNG diện tích công tắc (không thu nhỏ về 1px — cách thu
   nhỏ hay dùng cho input ẩn ở nơi khác, vd .field-checkbox nếu có sau này) để
   toàn bộ vùng công tắc là 1 target bấm/chạm đủ lớn (44x26, khớp khuyến nghị
   WCAG 2.5.5 — cùng chuẩn đã áp dụng cho .account-nav-item ở trên), chỉ ẩn
   thị giác bằng opacity — KHÔNG display:none (mất khỏi thao tác Tab)
   hay visibility:hidden (mất khỏi cây accessibility). */
.account-toggle-switch input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
  z-index: 1;
}
.account-toggle-track {
  position: absolute;
  inset: 0;
  border-radius: var(--radius-pill);
  background: var(--paper-deep);
  border: 1px solid var(--line);
  transition: background-color var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
}
/* Phản hồi hover/chạm cho công tắc — input thật nằm đè lên track (opacity:0,
   xem ghi chú phía trên) nên :hover/:active thật ra rơi trên input, không phải
   trực tiếp trên track — phải dùng input:hover/:active + .account-toggle-track
   (sibling) để đổi màu track. */
.account-toggle-switch input:hover + .account-toggle-track,
.account-toggle-switch input:active + .account-toggle-track {
  border-color: var(--jade);
}
.account-toggle-switch input:checked:active + .account-toggle-track {
  background: #185d58;
  border-color: #185d58;
}
.account-toggle-track::before {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  box-shadow: var(--shadow-soft);
  transition: transform var(--dur-fast) var(--ease-standard);
}
.account-toggle-switch input:checked + .account-toggle-track {
  background: var(--jade);
  border-color: var(--jade);
}
.account-toggle-switch input:checked + .account-toggle-track::before {
  transform: translateX(18px);
}
/* Vòng focus: global rule "input:focus-visible { outline:2px solid
   var(--koi-orange); }" (đầu style.css) áp trực tiếp lên input thật — nhưng
   input đang opacity:0 + nằm ĐÈ lên track nên outline đó vẫn hiện đúng vị trí
   track (input inset:0 khớp kích thước track), không cần thêm rule riêng nào
   khác cho track/knob. Không tắt outline mặc định ở đây (khác nhiều input
   trang trí khác trong file dùng outline:none kèm border-color đổi màu thay
   thế) vì công tắc không có cách nào khác để lộ rõ đang được Tab focus tới. */

/* prefers-reduced-motion: KHÔNG khai exemption riêng cho
   .account-toggle-track/::before ở khối chung "*:not(.category-sheet)..."
   phía dưới file (khác .account-panel/.category-sheet) — CỐ Ý. Lý do: khác
   sheet mở/đóng hay chuyển panel (nơi cắt animation sẽ làm mất tín hiệu "vừa
   xảy ra việc gì đó"), 2 trạng thái BẬT/TẮT của công tắc này đã tự phân biệt
   RÕ RÀNG bằng vị trí chấm tròn (trái/phải) + màu nền (--paper-deep/--jade)
   ngay ở 2 khung hình ĐẦU/CUỐI, dừng đột ngột ở giữa không làm mất bất kỳ
   thông tin trạng thái nào — đúng tinh thần "chỉ giữ lại phản hồi khi bỏ đi
   sẽ mất thông tin" của ANIMATION_GUIDE.md mục 5, không phải máy móc giữ lại
   mọi chuyển động MANG THÔNG TIN trạng thái bất kể có mất gì hay không.
   Giống cách .account-loyalty-progress-bar (ngay phía trên) cũng không cần
   exemption vì lý do tương tự (transition thực tế không có dịp chạy). */

/* Panel "Bảo mật": khối "Lịch sử đăng nhập gần đây" (.account-login-history-list).
   Cố tình dựng layout dọc đơn giản (thời gian 1 dòng, IP/thiết bị 1 dòng ngay
   dưới) thay vì hàng ngang 2 cột kiểu .account-toggle-row/.account-address-card
   — lý do: nội dung dòng thứ 2 (IP + User-Agent đã parse) độ dài rất khác nhau
   tuỳ khách (IPv4 ngắn/IPv6 dài, tên trình duyệt dài/ngắn), xếp dọc an toàn ở
   mọi bề rộng màn hình mà không cần thêm rule riêng trong khối @media 900px
   bên dưới. */
.account-login-history-list { margin: 0; padding: 0; list-style: none; }
.account-login-history-item {
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--line);
}
.account-login-history-item:last-child { border-bottom: none; }
.account-login-history-time { font-weight: 600; color: var(--ink); }
.account-login-history-meta { margin-top: 2px; font-size: var(--fs-xs); color: var(--text-muted); }

/* Popup "Xem thêm" (danh sách đầy đủ lịch sử đăng nhập) — cùng biến thể rộng hơn +
   căn trái + cuộn được như .account-address-modal-box (popup Sửa/Thêm địa chỉ), vì
   đây cũng là 1 danh sách dài chứ không phải hộp xác nhận ngắn text-align:center như
   .confirm-modal-box gốc. Dùng chung .account-address-modal-head/-title/-close (đã
   định nghĩa ở khối popup địa chỉ phía trên) để không lặp CSS layout tiêu đề + nút
   đóng. */
.account-login-history-modal-box {
  max-width: 480px;
  max-height: 85vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  text-align: left;
}
.account-login-history-modal-box .account-login-history-list { padding-right: 2px; }

@media (max-width: 900px) {
  .account-shell { grid-template-columns: 1fr; }
  .account-quick-stats { grid-template-columns: repeat(2, 1fr); }
  /* Nút "Sao chép mã" tự xuống hàng dưới mã trên máy hẹp thay vì ép nằm ngang
     chật chội (flex-wrap thay vì đổi hẳn sang column, giữ layout ngang nếu đủ
     chỗ ở các máy 375-410px thường gặp — chỉ auto-wrap khi thật sự không đủ). */
  /* 2 cột cố định ở desktop quá chật ở 375px, xuống 1 cột cùng lúc
     .account-shell đổi 1 cột ở đúng breakpoint này. */
  .account-address-list { grid-template-columns: 1fr; }
  /* Hàng "Sổ địa chỉ"/"Ưu đãi của tôi" (preview, panel Tổng quan) — cùng lý
     do/breakpoint như .account-address-list ngay trên, xuống 1 cột dọc thay
     vì 2 cột chật ở 375px. */
  .account-overview-row { grid-template-columns: 1fr; }
  /* "Thông tin cá nhân"/"Đổi mật khẩu" (panel Bảo mật): xuống 1 cột, "Đổi mật
     khẩu" tụt xuống dưới — cùng lúc .account-shell đổi 1 cột, không đặt mốc
     riêng. Viền trái (phân cách 2 cột ngang) đổi thành viền trên (phân cách 2
     khối xếp dọc), padding-left -> padding-top, theo ĐÚNG giá trị/pattern của
     .account-subcard-bordered đã dùng cho các khối xếp dọc khác trong trang
     này (space-6 + border-top 1px --line), không tự bịa giá trị mới. */
  .account-profile-security-grid { grid-template-columns: 1fr; gap: 0; }
  .account-profile-security-col--password {
    padding-left: 0;
    border-left: none;
    margin-top: var(--space-6);
    padding-top: var(--space-6);
    border-top: 1px solid var(--line);
  }
  .account-address-receiver { padding-right: 74px; }
  /* Ép cột "điểm" nằm trên thay vì cạnh khối hạng, tránh 2 cột chật cứng ở
     375px (thẻ điểm min-width 120px + text hạng/tiến độ không đủ chỗ thở nếu
     vẫn ép ngang). */
  .account-loyalty-card { flex-direction: column; align-items: stretch; gap: var(--space-4); }
  .account-loyalty-points { min-width: 0; }
  /* .account-toggle-row hiển thị ổn ở 375px, không cần rule riêng: text
     (.account-toggle-text, flex:1 min-width:0) tự xuống dòng bình thường
     trong phần không gian còn lại, công tắc (.account-toggle-switch,
     flex-shrink:0, 44px cố định) không co lại nên không bị bóp méo hình dạng. */

  /* ============================================================
     Dock nổi kiểu iOS (thay thanh "Mục hiện tại ▾" cũ, .account-mobile-bar —
     markup + toàn bộ rule của thanh cũ đã được dọn bỏ, xem lịch sử ở
     PROJECT_LOG.md nếu cần đối chiếu). .account-sidebar trên desktop là 1 khối
     tĩnh (position:sticky, luôn hiện) — không hidden mặc định ngoài media
     query như .category-sheet, nên không thể tự mang role="dialog"/aria-modal
     tĩnh trong HTML (account.php) mà không làm sai ngữ nghĩa a11y ở desktop;
     toàn bộ trạng thái mở/đóng của #accountSidebar (xem khối
     ".account-sidebar.account-nav-sheet" bên dưới) chỉ có tác dụng trong
     @media này. Cùng "chất liệu" sheet với .category-sheet (bo góc 20px, đúng
     box-shadow/isolation/will-change/overscroll-behavior, --ease-sheet-in mở /
     --ease-sheet-drag-release đóng) nhưng dùng translateY thay vì scale-morph-từ-
     điểm — xem giải thích đầy đủ tại comment ngay trên #accountSheetHandle trong
     account.php. Vì không scale, border-radius không cần tách 2 pha (không bị lỗi
     "góc vuông lúc animate" của .category-sheet — lỗi đó chỉ xảy ra khi resize kích
     thước phần tử qua transform, translateY không đổi kích thước).
     Markup + CSS tĩnh; main.js (initAccountSidebar()) đo vị trí tab.is-active
     THẬT rồi trượt thumb bằng transform (moveDockThumb()), chuyển mục trực
     tiếp khi bấm 1 trong 4 .account-dock-tab (không cần mở sheet), + ping toả
     tròn màu jade cho nút "...". Dock nằm cùng @media max-width:900px này,
     thay hẳn cho vị trí thanh cũ trong DOM (account.php).
     - .account-dock: viên "khay" nổi, width fit-content + margin auto để
       căn giữa (không kéo full-width như thanh cũ), 5 nút vuông bằng nhau
       (4 tab chính + nút "..."), nền trắng/bo lớn/đổ bóng var(--shadow).
     - .account-dock-thumb: viên nền cam gradient "squircle" nằm dưới tab
       đang chọn, bo góc NHỎ HƠN dock 6px (22px - 6px = 16px) đúng hiệu ứng
       lồng trong khay. "left: 0" (KHÔNG phải padding 6px của dock) vì
       moveDockThumb() (main.js) tính translateX bằng hiệu 2
       getBoundingClientRect() (tab THẬT trừ đi dock) — hiệu số đó đã tự bao
       gồm đúng padding 6px rồi, đặt "left" tĩnh khác 0 sẽ cộng dồn lệch thêm
       6px mọi tab. transition dùng ĐÚNG var(--dur-slow)/var(--ease-standard)
       có sẵn cho cú trượt transform mỗi lần đổi tab.
     - .account-dock-tab/.account-dock-more: nút vuông 48px, icon-only, label
       thật nằm trong .account-dock-tab-a11y (ẩn hình, vẫn đọc được bằng
       trình đọc màn hình) thay vì aria-label riêng trên icon.
     - Nút "..." dùng id "accountSheetToggle" y hệt bản cũ để main.js không
       cần sửa gì (vẫn query đúng id này để khởi tạo createAccountSheet()).
       .account-dock-more-icon co nhẹ khi sheet mở (dựa thẳng vào
       aria-expanded mà createAccountSheet() đã tự gán/gỡ — không cần thêm JS
       riêng cho phần này) + .dock-more-ping (JS bắn class lúc bấm) toả tròn
       màu jade, cùng kỹ thuật keyframe với .mnb-ping ở bottom-nav nhưng tách
       hẳn tên class/keyframe (không sửa .mnb-ping gốc). */
  .account-dock {
    position: relative;
    display: flex;
    align-items: center;
    gap: 4px;
    width: fit-content;
    margin: 0 auto var(--space-4);
    padding: 6px;
    background: #fff;
    border-radius: 22px;
    box-shadow: var(--shadow);
  }
  .account-dock-thumb {
    position: absolute;
    top: 6px;
    left: 0;
    width: 48px;
    height: 48px;
    border-radius: 16px;
    background: linear-gradient(135deg, var(--koi-orange) 0%, #e8823f 100%);
    z-index: 0;
    transition: transform var(--dur-slow) var(--ease-standard), opacity var(--dur-slow) var(--ease-standard);
  }
  /* JS (syncDockThumb(), main.js) gắn class này khi $activeTab đang xem
     là 1 trong 4 mục KHÔNG nằm trong dock rút gọn (Sổ địa chỉ/Đánh giá/Thông báo/
     Hồ sơ & Bảo mật — không có tab tương ứng để trượt thumb tới) — ẩn hẳn thumb
     thay vì để nó đứng yên sai chỗ dưới 1 icon đã mất "is-active". Xử lý cả lúc
     tải trang đầu ($activeTab PHP thuộc nhóm này ngay từ đầu) lẫn lúc chuyển mục
     qua sheet. Dùng class thay vì set thẳng style.opacity trong JS để trạng thái
     "ẩn" nằm hẳn trong CSS (dễ dò/đổi sau này), transition kế thừa đúng token
     transform/opacity đã khai ở rule .account-dock-thumb bên trên, không lặp lại. */
  .account-dock-thumb.is-hidden { opacity: 0; }
  .account-dock-tab,
  .account-dock-more {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    border: 0;
    background: transparent;
    border-radius: 16px;
    color: var(--text-muted);
    cursor: pointer;
    transition: color var(--dur-slow) var(--ease-standard);
  }
  .account-dock-tab svg,
  .account-dock-more svg { width: 22px; height: 22px; flex-shrink: 0; }
  /* Tab đang chọn nằm đè lên viên thumb cam -> đổi icon sang trắng cho tương
     phản đủ. Class "is-active" này do switchAccountPanel()
     (main.js) tự gắn/gỡ đúng theo mục đang xem, không cố định ở tab đầu. */
  .account-dock-tab.is-active { color: #fff; }
  /* Dấu hiệu "đang ở 1 mục KHÁC ngoài dock" trên nút "..." — JS
     (syncDockThumb()) gắn/gỡ "is-active" đúng lúc thumb bị ẩn (.is-hidden ở trên).
     Cố tình dùng tông JADE (không phải cam của thumb) để khách không hiểu lầm
     đây là 1 "tab" đang sáng như 4 tab chính — cùng mã màu rgba(31,111,106,...)
     đã quy đổi từ var(--jade) (#1f6f6a) cho .dock-more-ping bên dưới, chỉ hạ
     alpha thấp hơn nhiều để làm nền "nhạt" thay vì ping toả tròn. ĐẶT TRƯỚC rule
     :hover/[aria-expanded="true"] ngay dưới đây (cùng specificity, 2 class/1
     class+1 attribute — hoà specificity, ai đứng SAU thắng): cố ý để lúc sheet
     đang MỞ THẬT SỰ, trạng thái "đang mở" (cam) luôn được ưu tiên hiển thị rõ
     hơn "đang ở mục khác" (jade), tránh 2 tông màu tranh nhau cùng lúc. */
  .account-dock-more.is-active {
    color: var(--jade);
    background: rgba(31, 111, 106, .12);
  }
  .account-dock-more[aria-expanded="true"] { color: var(--koi-orange); }
  /* Hover chỉ khi có chuột thật: trên cảm ứng :hover bị "dính" sau khi chạm. Đặt ngay sau .is-active/[aria-expanded]
     để thứ tự ưu tiên (cùng độ đặc hiệu, đứng sau thắng) giữ nguyên như trước. */
  @media (hover: hover) {
    .account-dock-more:hover,
    .account-dock-tab:not(.is-active):hover { color: var(--koi-orange); }
  }
  /* Icon nút "..." co nhẹ + mờ đi trong lúc sheet đang mở — cùng cảm giác/cùng
     token thời lượng+easing với .mnb-item-toggle.active .mnb-icon ở bottom-nav
     (--ease-sheet-in lúc MỞ, --ease-sheet-out lúc ĐÓNG), bám thẳng vào
     aria-expanded mà createAccountSheet() (main.js) đã tự set nên không cần
     thêm JS riêng cho hiệu ứng này. */
  .account-dock-more-icon {
    display: flex;
    transition: transform .3s var(--ease-sheet-out), opacity .22s var(--ease-sheet-out);
  }
  .account-dock-more[aria-expanded="true"] .account-dock-more-icon {
    transition: transform .3s var(--ease-sheet-in), opacity .22s var(--ease-sheet-in);
    transform: scale(.74);
    opacity: .5;
  }
  /* Ping toả tròn từ tâm nút "..." lúc mở (loang ra + mờ dần)/đóng (co ngược
     vào tâm) — bắn class bằng JS (main.js, initAccountSidebar()) mỗi lần bấm,
     COPY ĐÚNG kỹ thuật + 2 token easing --ease-sheet-in/--ease-sheet-out của
     .mnb-ping (bottom-nav) nhưng đổi màu sang jade (không trùng cam của thumb
     dock ngay bên cạnh) + tách hẳn tên class/keyframe riêng, KHÔNG đụng gì
     .mnb-ping/mnbPingOut/mnbPingIn gốc. rgba(31,111,106,...) = đúng mã hex
     var(--jade) (#1f6f6a) quy đổi thập phân, cùng cách --koi-orange đã làm ở
     .mnb-ping gốc (không dùng var() trực tiếp vì cần cộng thêm alpha riêng
     cho radial-gradient). */
  .dock-more-ping {
    position: absolute;
    top: 50%; left: 50%;
    width: 10px; height: 10px;
    margin: -5px 0 0 -5px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(31, 111, 106, .85), rgba(31, 111, 106, 0) 70%);
    opacity: 0;
    transform: scale(0);
    pointer-events: none;
  }
  .dock-more-ping.dock-more-ping-out { animation: dockMorePingOut .5s var(--ease-sheet-in) forwards; }
  .dock-more-ping.dock-more-ping-in { animation: dockMorePingIn .5s var(--ease-sheet-out) forwards; }
  @keyframes dockMorePingOut {
    0% { transform: scale(0); opacity: .9; }
    60% { transform: scale(3.2); opacity: .22; }
    100% { transform: scale(3.8); opacity: 0; }
  }
  @keyframes dockMorePingIn {
    0% { transform: scale(3.8); opacity: 0; }
    40% { transform: scale(2.8); opacity: .22; }
    100% { transform: scale(0); opacity: .9; }
  }
  @media (prefers-reduced-motion: reduce) {
    .dock-more-ping.dock-more-ping-out, .dock-more-ping.dock-more-ping-in { animation: none; display: none; }
  }
  /* Label a11y: giữ trong DOM/cho screen reader đọc, ẩn hình hoàn toàn khỏi
     mắt thường (dock chỉ hiện icon, không có text như thanh cũ). */
  .account-dock-tab-a11y {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }

  .account-sidebar.account-nav-sheet {
    display: flex;
    flex-direction: column;
    position: fixed;
    /* QUAN TRỌNG: .account-sidebar gốc (desktop, ngoài media query này) khai báo
       "top: var(--account-sticky-top)" cho position:sticky — nếu không huỷ tường
       minh ở đây, giá trị đó vẫn còn hiệu lực khi đổi sang position:fixed. Vì bên
       dưới CÙNG khai báo "bottom", có CẢ top lẫn bottom không phải auto sẽ khiến
       trình duyệt tự GIÃN chiều cao phần tử để khớp đúng khoảng cách giữa 2 mốc đó
       (đè lên max-height thay vì để nội dung tự quyết định chiều cao, cắt lẹm
       khoảng trống lẽ ra phải dành cho phần header phía trên) — "top: auto" trả
       lại đúng hành vi "cao theo nội dung, neo đáy" mong muốn. */
    top: auto;
    left: 0; right: 0;
    bottom: calc(var(--mobile-bottom-nav-h) + env(safe-area-inset-bottom, 0px));
    max-height: 75vh;
    margin: 0;
    padding: 0;
    background: var(--paper);
    border: none;
    border-radius: 20px 20px 0 0;
    box-shadow: 0 -14px 34px -16px rgba(18, 34, 44, .35);
    z-index: calc(var(--z-sticky-header) + 7);
    overflow: hidden; /* .account-sheet-draghandle đứng yên, chỉ .account-sheet-body cuộn */
    isolation: isolate; /* cùng lý do đã áp cho .site-header/.category-sheet — xem comment gốc tại .category-sheet */
    will-change: transform, opacity;
    transform: translateY(100%);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition:
      transform .4s var(--ease-sheet-drag-release),
      opacity .3s var(--ease-sheet-drag-release),
      visibility 0s linear .4s;
  }
  .account-sidebar.account-nav-sheet.sheet-open {
    transform: translateY(0);
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition:
      transform .4s var(--ease-sheet-in),
      opacity .3s var(--ease-sheet-in),
      visibility 0s linear 0s;
  }
  .account-sheet-draghandle {
    display: flex;
    flex-direction: column;
    align-items: center;
    flex-shrink: 0;
    padding: 8px 0 4px;
    touch-action: none;
    -webkit-user-select: none;
    user-select: none;
    cursor: grab;
  }
  .account-sheet-draghandle-bar {
    width: 40px;
    height: 5px;
    border-radius: var(--radius-pill);
    background: var(--line);
    flex-shrink: 0;
    display: block;
  }
  .account-sheet-title {
    text-align: center;
    font-size: var(--fs-sm);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--text-muted);
    padding: 8px 0 6px;
    flex-shrink: 0;
  }
  .account-sheet-body {
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    padding: 4px var(--space-4) calc(20px + env(safe-area-inset-bottom, 0px));
  }
  /* Bên trong sheet, khối điểm thưởng/nav/đăng xuất không còn cần padding ngang
     riêng nữa (đã có padding var(--space-4) từ .account-sheet-body bao ngoài) —
     nhưng .account-sidebar-profile vẫn giữ nguyên padding gốc (0 var(--space-2)
     var(--space-4)) từ CSS desktop, không đụng tới để tránh lệch canh với avatar. */
}
@media (max-width: 480px) {
  .account-panel.is-shown { padding: var(--space-4); }
}

/* login/register/forgot/reset-password */
.container-narrow-400 { max-width: 400px; }
.container-narrow-440 { max-width: 440px; }
.forgot-pw-link-row { text-align: right; margin: -8px 0 18px; }
.mt-6px { margin-top: 6px; }

/* ================================================================
   HERO — BỐ CỤC MOBILE KHI CÓ BANNER (≤900px)
   -----------------------------------------------------------------
   LỊCH SỬ (giữ lại vì giải thích đúng 2 lỗi có thật, vẫn phải tránh):
   bản gốc nhất dùng kiểu "chữ đè lên ảnh" y hệt desktop và hỏng vì 2
   lý do — (1) lớp phủ tối .hero-bg-tint là gradient TRÁI→PHẢI (dành
   cho chữ nằm bên trái ảnh ở desktop), ở mobile khối chữ trải full
   width nên lớp phủ che gần hết ảnh, banner trông như 1 màu phẳng;
   (2) 2 mũi tên chuyển ảnh canh giữa theo TOÀN BỘ chiều cao hero nên
   đè thẳng lên đoạn mô tả. Bản kế tiếp né cả 2 bằng cách TÁCH ảnh ra
   thành 1 "thẻ" cao 200px phía trên, chữ nằm trong luồng bên dưới —
   hết đè nhau, nhưng ảnh bị thu nhỏ thành 1 dải mỏng, mất hẳn cảm
   giác banner, và khác hẳn desktop.
   BẢN HIỆN TẠI: quay lại "chữ đè lên ảnh" (đồng bộ desktop) nhưng sửa
   ĐÚNG 2 nguyên nhân gốc ở trên thay vì né chúng: lớp phủ đổi thành
   gradient TRÊN→DƯỚI chỉ tối ở nửa dưới (phần lớn ảnh phía trên giữ
   nguyên màu thật), và mũi tên dời lên vùng ảnh trống phía trên thay
   vì giữa hero. Đi kèm là ảnh khổ dọc riêng cho mobile
   (banners.image_mobile — xem bannerPicture() ở includes/functions.php),
   nhờ đó ảnh không còn bị cắt nát khi ép ảnh 16:9 vào khung dọc.

   VÌ SAO KHÔNG position:absolute cho .hero-copy-glass: cách đó buộc
   phải biết trước chiều cao khối chữ. Chữ ở đây xuống dòng khác nhau
   theo độ rộng máy (h1 3 dòng ở 390px nhưng 4 dòng ở 320px) — chỉ cần
   lệch 1 dòng là chữ tràn ra ngoài ảnh hoặc bị .wave-divider cắt.
   Giữ chữ trong luồng của .container (vốn đã có position:relative +
   z-index:2, tức là ĐÃ nằm đè lên .hero-media z-index:0) thì chiều cao
   hero tự giãn theo nội dung thật, không bao giờ cắt chữ. Đây cũng
   đúng mô hình desktop đang chạy, nên mốc 900/901px không đổi cấu trúc,
   không giật layout.
   Dùng selector .hero.hero-has-bg (2 class) để độ đặc thù luôn cao hơn
   các rule .hero / .hero-copy-glass gốc, không phụ thuộc thứ tự file. */
@media (max-width: 900px) {
  /* CHIỀU CAO: KHÔNG chốt cứng 1 con số. Vùng ảnh "sạch" phía trên được giữ
     bằng padding-top của .container (xem ngay dưới) nên hero tự cao thêm đúng
     bằng phần chữ thực tế — máy hẹp (chữ xuống nhiều dòng hơn) tự có hero cao
     hơn thay vì bóp vùng ảnh lại. min-height ở đây chỉ là SÀN, phòng trường
     hợp chữ ngắn bất thường: clamp theo vw để bám tỷ lệ ảnh mobile khuyến nghị
     (4:5, tức cao = 1.25 × rộng), chặn trên 640px để tablet dọc (768-900px)
     không có hero dài lê thê. */
  .hero.hero-has-bg { min-height: clamp(500px, 120vw, 640px); }
  /* Full-bleed như desktop. Chỉ cần lặp lại 2 thuộc tính định vị vì rule gốc
     (.hero-media ở khối HERO đầu file) đã đúng sẵn — giữ lại ở đây cho rõ ý
     "mobile dùng CÙNG mô hình phủ nền với desktop", tránh người sửa sau tưởng
     mobile vẫn còn kiểu thẻ ảnh tách rời như bản trước. */
  .hero.hero-has-bg .hero-media {
    position: absolute;
    inset: 0;
  }
  /* Nhích khung cắt lên trên tâm ảnh: nửa dưới ảnh đằng nào cũng nằm dưới lớp
     phủ tối + chữ. Với ảnh mobile khổ dọc trên điện thoại thì khung gần khít
     ảnh nên giá trị này gần như không đổi gì — nó chỉ thực sự có tác dụng cho
     banner CHƯA có ảnh mobile riêng (ảnh 16:9 bị ép vào khung dọc) và cho
     tablet (khung rộng hơn tỷ lệ ảnh dọc nên bị cắt bớt trên/dưới). */
  .hero.hero-has-bg .hero-bg-carousel .hero-carousel-img { object-position: 50% 38%; }
  /* Lớp phủ tối: gradient TRÊN→DƯỚI (bản desktop là trái→phải, dành cho chữ
     nằm bên trái). Khoảng 1/4 trên gần như trong suốt để ảnh giữ màu thật,
     tối dần từ ~24% và đậm nhất ở đáy — đúng vùng đặt chữ + nút CTA. Chốt
     cuối .92 chứ không phải 1: vẫn thấy lờ mờ ảnh sau chữ, giữ cảm giác chữ
     nằm TRÊN ảnh chứ không phải trên một dải màu đặc. */
  .hero.hero-has-bg .hero-bg-tint {
    background: linear-gradient(180deg,
        rgba(18,34,44,.12) 0%,
        rgba(18,34,44,.06) 24%,
        rgba(18,34,44,.52) 48%,
        rgba(18,34,44,.92) 100%);
  }
  /* padding-top chính là "vùng ảnh sạch" được ĐẶT TRƯỚC: chữ không bao giờ bò
     lên quá mốc này, dù dài bao nhiêu dòng. Dùng vw để vùng ảnh luôn cân xứng
     với bề rộng máy (46vw ≈ nửa trên của khung 4:5), kèm sàn 150px cho máy rất
     hẹp và trần 300px cho tablet.
     align-items:flex-end dồn chữ xuống sát đáy — nơi gradient đậm nhất.
     padding-bottom phải LỚN HƠN chiều cao .wave-divider (46px), nếu không dải
     sóng trắng ở đáy hero (z-index:3, cao hơn .container z-index:2) sẽ liếm
     mất chân nút CTA. */
  .hero.hero-has-bg .container {
    min-height: clamp(500px, 120vw, 640px);
    align-items: flex-end;
    padding-top: clamp(150px, 46vw, 300px);
    padding-bottom: 62px;
    /* .container (z-index:2) phủ kín hero và nằm ĐÈ LÊN .hero-media (z-index:0)
       — mà 2 nút mũi tên lại là con của .hero-media. Vì .hero-media có z-index
       khác "auto" nên nó tạo STACKING CONTEXT riêng: z-index:5 của .hero-arrow
       chỉ có tác dụng BÊN TRONG .hero-media, không thể vượt lên trên
       .container được. Hệ quả: cú bấm vào mũi tên rơi trúng .container (nền
       trong suốt nhưng vẫn nhận sự kiện chuột) chứ không tới nút.
       pointer-events:none cho .container trả lại sự kiện cho thứ nằm dưới (2
       nút mũi tên), rồi bật lại auto cho đúng khối chữ bên dưới để link/nút CTA
       và thao tác bôi đen chữ vẫn hoạt động. KHÔNG dùng z-index để sửa vì muốn
       vậy phải bỏ z-index:0 của .hero-media — kéo theo thay đổi thứ tự vẽ của
       cả ảnh nền lẫn lớp phủ ở MỌI kích thước màn hình, rủi ro hơn nhiều so
       với 2 dòng pointer-events chỉ áp trong media query mobile này.
       (Desktop cũng dùng đúng cách này — xem khối "DESKTOP (>= 901px" ở
       .hero-arrow.) */
    pointer-events: none;
  }
  /* Bỏ hẳn "thẻ kính mờ": nền tối đã do .hero-bg-tint lo, thêm 1 lớp kính nữa
     sẽ che ảnh lần thứ hai. padding ngang = 0 để chữ thẳng hàng đúng với tiêu
     đề "DANH MỤC" và các khối nội dung bên dưới (đều ăn theo padding chuẩn của
     .container), thay vì thụt vào thêm vài px. */
  .hero.hero-has-bg .hero-copy-glass {
    max-width: 100%;
    padding: 0;
    background: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border: none;
    box-shadow: none;
    pointer-events: auto; /* bù lại pointer-events:none của .container ở trên */
  }
  /* Đổ bóng chữ đậm hơn bản desktop: ở desktop chữ nằm trên thẻ kính mờ (nền
     tối đặc), còn ở đây chữ nằm thẳng trên ảnh — mép trên khối chữ có thể rơi
     vào đoạn gradient còn nhạt nếu chữ dài nhiều dòng. 2 lớp bóng: 1 lớp sát
     nét chữ cho độ tương phản, 1 lớp toả rộng để tách chữ khỏi chi tiết ảnh. */
  .hero.hero-has-bg .hero-copy-glass h1,
  .hero.hero-has-bg .hero-copy-glass p.lede,
  .hero.hero-has-bg .hero-copy-glass .eyebrow {
    text-shadow: 0 1px 2px rgba(0,0,0,.55), 0 2px 14px rgba(0,0,0,.5);
  }
  /* Mũi tên nằm TRONG .hero-media nên top:% tính theo chiều cao .hero-media —
     giờ .hero-media phủ trọn hero nên mốc top:50% gốc rơi đúng vào khối chữ.
     22% đặt mũi tên vào giữa vùng ảnh sạch (vốn luôn >= 150px nhờ padding-top
     ở trên), cách xa chữ ở mọi mức xuống dòng đã thử (320/360/390/430/768px)
     và vẫn nằm trong tầm ngón tay cái. */
  .hero.hero-has-bg .hero-arrow { top: 22%; }
  /* Hàng nút CTA căn trái cho thẳng hàng với h1/lede (vốn căn trái). Rule
     .hero-ctas{justify-content:center} ở @media 640px phía trên là dành cho
     hero KHÔNG có banner (khối chữ lúc đó nằm giữa cùng hoạ tiết) — ở chế độ
     có banner thì căn giữa sẽ lệch hẳn khỏi mép chữ. */
  .hero.hero-has-bg .hero-ctas { justify-content: flex-start; }
}
@media (max-width: 640px) {
  /* Máy hẹp: cỡ chữ đã nhỏ lại (xem .hero h1/.hero p.lede ở @media 640px phía
     trên) nên hạ sàn chiều cao + vùng ảnh sạch tương ứng, tránh thừa một mảng
     tối trống giữa ảnh và chữ. Giữ nguyên hệ số 120vw và trần 640px của mốc
     ≤900px (2 con số đó bám tỷ lệ ảnh và trần chiều cao, không liên quan cỡ chữ).
     Trước đây khối này chỉnh height/margin/border-radius của "thẻ ảnh" —
     những thuộc tính đó không còn tồn tại trong bố cục mới. */
  .hero.hero-has-bg { min-height: clamp(440px, 120vw, 640px); }
  .hero.hero-has-bg .container {
    min-height: clamp(440px, 120vw, 640px);
    padding-top: clamp(140px, 44vw, 280px);
    padding-bottom: 56px;
  }
}

/* ==========================================================================
   CHỐNG iOS TỰ ZOOM + CHỐNG TRÀN CHỮ DÀI + NÚT CUỘN THANH DANH MỤC
   ========================================================================== */

/* iOS Safari tự phóng to trang khi chạm vào ô nhập có font-size < 16px (kể cả iPad, kể cả
   ở bề rộng 641–1024px nơi rule 16px trong @media 640px không phủ tới). Trước đây phải
   chống từng ô một và các rule class cụ thể (.qty-box input, .search-form-hero input...)
   đè mất rule chung → sót. Khối này chốt ở tầng cuối cho MỌI thiết bị cảm ứng, dùng
   !important có chủ đích để rule cụ thể thêm sau này không vô tình làm hỏng lại. Không ảnh
   hưởng chuột/desktop (hover:none + pointer:coarse chỉ khớp màn cảm ứng). */
@media (hover: none) and (pointer: coarse) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="submit"]):not([type="button"]):not([type="hidden"]):not([type="color"]),
  select,
  textarea {
    font-size: 16px !important;
  }
}

/* Tên dài không dấu cách (mã SKU, URL) không được làm vỡ/tràn khung. Tên tiếng Việt bình
   thường có dấu cách nên vẫn ngắt dòng tự nhiên — overflow-wrap:anywhere chỉ "ra tay" khi
   1 từ dài hơn cả bề rộng khung. */
.pd-title,
.product-name,
.cart-prod a,
.cart-sheet-item-name,
.search-suggest-item-name,
.order-line-name,
.breadcrumb,
.post-card h3,
.cart-prod-name,
.account-review-product-name,
.page-head h1 {
  overflow-wrap: anywhere;
}

/* Nút cuộn trái/phải cho thanh danh mục desktop (.nav-scroll ẩn thanh cuộn nên khách không
   biết còn danh mục phía sau). main.js chèn 2 nút này và chỉ hiện khi danh mục thực sự tràn. */
.nav-scroll-btn { display: none; }
@media (min-width: 901px) {
  .main-nav.has-nav-overflow .nav-scroll-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 1px solid rgba(255, 255, 255, .22);
    border-radius: 50%;
    background: rgba(255, 255, 255, .1);
    color: #fff;
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease-standard), opacity var(--dur-fast) var(--ease-standard);
  }
  .main-nav.has-nav-overflow .nav-scroll-btn:hover,
  .main-nav.has-nav-overflow .nav-scroll-btn:active { background: rgba(255, 255, 255, .22); }
  .main-nav.has-nav-overflow .nav-scroll-btn:disabled { opacity: .3; cursor: default; }
  .main-nav.has-nav-overflow .nav-scroll-btn svg { width: 14px; height: 14px; }
  .main-nav.has-nav-overflow .nav-scroll {
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 14px, #000 calc(100% - 14px), transparent 100%);
    mask-image: linear-gradient(90deg, transparent 0, #000 14px, #000 calc(100% - 14px), transparent 100%);
  }
  .main-nav.has-nav-overflow .nav-scroll.is-at-start { -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 14px), transparent 100%); mask-image: linear-gradient(90deg, #000 calc(100% - 14px), transparent 100%); }
  .main-nav.has-nav-overflow .nav-scroll.is-at-end { -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 14px); mask-image: linear-gradient(90deg, transparent 0, #000 14px); }
}

/* =========================== MODEL "LIÊN HỆ" + DÒNG GIỎ KHÔNG THANH TOÁN ĐƯỢC (phiên 3) ===========================
   Thuộc tính HTML hidden chỉ là display:none của trình duyệt nên bị CSS tác giả đè (xem ghi chú ở .pd-desc-toggle). Các phần tử
   dưới đây có display riêng (.btn, flex) nên khai báo [hidden] với !important để main.js ẩn/hiện chúng bằng thuộc tính hidden. */
#pdAddCartBtn[hidden],
.pd-contact-box[hidden],
.pd-contact-fallback[hidden] { display: none !important; }

.pd-contact-box {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
  background: var(--gold-tint);
  border: 1.4px dashed var(--gold);
  border-radius: var(--radius);
  padding: 14px 18px;
  margin-bottom: 18px;
}
.pd-contact-text { font-size: var(--fs-sm); color: var(--ink-soft); }
.pd-contact-box .btn { min-height: 44px; } /* vùng chạm đủ lớn trên điện thoại */
.pd-contact-fallback { width: 100%; display: flex; flex-direction: column; gap: var(--space-2); }
.pd-contact-fallback label { font-size: var(--fs-xs); color: var(--text-muted); }
.pd-contact-fallback textarea { width: 100%; font: inherit; font-size: var(--fs-sm); padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: #fff; resize: vertical; }
.pd-option-contact-tag { color: var(--gold-text); }

/* Dòng giỏ không còn bán được: mờ nhẹ + ghi chú lý do, nút Xoá vẫn bấm được. */
.cart-blocked-note { font-size: var(--fs-xs); color: var(--koi-red); margin-top: 4px; }
.cart-row-blocked .cart-prod img,
.cart-sheet-item.is-blocked .cart-sheet-item-img { opacity: .55; }
.cart-row-blocked .qty-box,
.cart-sheet-item.is-blocked .qty-box { opacity: .5; pointer-events: none; }
#checkoutSubmitBtn:disabled { opacity: .55; cursor: not-allowed; }
