/* ============================================================
   common.css — Shell dùng chung cho toàn site Thanh Long Group
   Thứ tự: @font-face · :root · reset · layout · icon · button
           · overlay · header · mega-menu · header-mobile
           · footer · group-contact · [data-aos] · responsive
   Nạp TRƯỚC mọi file css của trang. Giữ nguyên tên class khi
   chuyển sang WordPress.
   ============================================================ */

/* ===== 1 · FONT (cục bộ, biến thiên 400–700) ===== */
/* Tách 3 subset để trang chỉ tải phần ký tự thật sự dùng.
   Bản vietnamese chỉ 10KB nhưng bắt buộc phải có: thiếu nó thì
   toàn bộ dấu tiếng Việt rơi về font hệ thống. */
@font-face {
  font-family: "Inter"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("../fonts/inter-vietnamese.woff2") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
                 U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
                 U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: "Inter"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("../fonts/inter-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Inter"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("../fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ===== 2 · TOKEN (trang 1 file thiết kế PA1-R6) ===== */
:root {
  --c-primary:      #006540;   /* CTA, tab active, viền active */
  --c-primary-dark: #004d30;   /* hover của primary */
  --c-dark:         #152e2b;   /* nền footer */
  --c-grey:         #c9c9c9;   /* dải nền gallery, ô ảnh chờ */
  --c-grey-light:   #eaeaea;   /* nền card, nền section, input */

  --c-ink:      #1a1a1a;       /* tiêu đề */
  --c-body:     #3d3d3d;       /* văn bản */
  /* #8a8a8a → #6a6a6a (pass "canh lề" 31/07/2026). Token này dùng ở 31 chỗ và
     gần như luôn ở cỡ 12–15px, tức là CHỮ THƯỜNG theo WCAG nên cần 4.5:1.
     #8a8a8a chỉ được 3.45:1 trên nền trắng và 2.87:1 trên --c-grey-light —
     trượt ở cả hai nền mà nó thực sự xuất hiện. Đây là lỗi tương phản lớn nhất
     của site: nó chạm breadcrumb, dòng đếm kết quả, nhãn thương hiệu trên card,
     ngày tháng tin tức, chữ gợi ý trong ô tìm kiếm, và cả nút "đặt lại bộ lọc".

     Vì sao là #6a6a6a chứ không phải #767676 (con số hay được nhắc tới):
     #767676 đạt 4.54:1 trên TRẮNG nhưng chỉ 3.78:1 trên #eaeaea — mà nền
     --c-grey-light chính là nền của card và ô nhập. #6a6a6a cho 5.41:1 trên
     trắng và đúng 4.50:1 trên #eaeaea: đạt AA trên CẢ HAI, không phải nhớ
     ngoại lệ. Sắc độ vẫn là xám trung tính nên cảm giác "chữ phụ" không đổi. */
  --c-muted:    #6a6a6a;       /* nhãn phụ, ngày tháng */
  --c-line:     #e3e3e3;       /* đường kẻ mảnh (chia ô, gạch ngang) */
  --c-line-strong: #c4c4c4;    /* viền nút / khung — đậm hơn để bằng thiết kế */
  --c-white:    #ffffff;

  --font-body: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  /* Thang chữ dùng chung (pass phản hồi client 29/07/2026).
     Trước pass này mọi heading tự viết clamp() riêng ở 20+ chỗ nên cả site
     lệch nhau và mảnh hơn thiết kế. Mọi tiêu đề PHẢI lấy từ đây. */
  /* Cận trên/dưới của clamp tính bằng `rem`, phần giữa vẫn `vw` (pass "canh lề"
     31/07/2026). Ở cỡ chữ gốc mặc định 16px thì 2rem = 32px, 3.25rem = 52px…
     nên KHÔNG có một pixel nào đổi so với bản client đã duyệt.

     Vì sao vẫn phải đổi: cỡ khai bằng `px` không nhúc nhích khi người dùng tăng
     cỡ chữ mặc định của trình duyệt, mà đó là cách chỉnh cỡ chữ phổ biến nhất
     của người mắt kém — WCAG 1.4.4 đòi chữ phóng được tới 200%. Đổi hai đầu
     clamp sang rem thì cả thang chữ giãn theo cài đặt đó ở hai đầu, còn đoạn
     giữa vẫn co theo bề rộng màn hình như thiết kế. */
  --fs-h1:   clamp(2rem, 4.6vw, 3.25rem);      /* 32 → 52px */
  --fs-h2:   clamp(1.75rem, 3.4vw, 2.5rem);    /* 28 → 40px */
  --fs-h3:   clamp(1.25rem, 2.2vw, 1.625rem);  /* 20 → 26px */
  /* Tiêu đề của MỘT MỤC trong lưới: tên gạch, tên bộ sưu tập, tên dự án, tên
     danh mục… Trước pass phản hồi 30/07/2026 mỗi loại card tự hard-code
     18px/19px nên tên sản phẩm mảnh hơn hẳn phần còn lại của trang — client
     góp ý "tên sản phẩm/tên bài viết cho to hơn". Một token, 6 loại card. */
  --fs-card:  clamp(1.1875rem, 1.5vw, 1.375rem);
  --fs-body: 1rem;   /* văn bản chính */
  --fs-sm:   .9375rem;   /* mô tả, spec */
  --fs-xs:   .875rem;   /* nhãn phụ, meta, breadcrumb */
  /* 600 → 700 (phản hồi 30/07/2026: "tăng font-weight tiêu đề, in đậm thêm").
     Inter đã nạp dải 400–700 nên 700 là cạnh trên, không bị trình duyệt giả đậm. */
  --fw-title:  700;  /* mọi heading + nhãn đang được chọn */
  /* Chrome nhỏ chữ HOA (nav header, nhãn nhóm mega, mục menu mobile) giữ 600:
     ở 12–13px + letter-spacing dương thì 700 bết nét, đọc khó hơn chứ không rõ
     hơn. Tách token để lần sau chỉnh --fw-title không kéo theo phần này. */
  --fw-ui:     600;
  --fw-medium: 500;

  --bw: 1.5px;       /* độ dày viền nút / khung */
  /* Ô nhập / ô lọc bo góc NHỎ HƠN card (phản hồi client 30/07/2026: "giảm
     border radius ở các field input"). Card to nên bo 12px trông mềm; ô nhập
     cao 46px mà bo 12px thì gần thành viên thuốc, mất cảm giác "gõ được vào". */
  --r-field: 8px;

  /* Bóng card mang SẮC XANH BRAND chứ không phải đen trung tính (phản hồi
     31/07/2026: "shadow khi hover đang có ánh đỏ"). Trong file không hề có giá
     trị đỏ nào — bản cũ là rgba(0,0,0,.13), đen tuyệt đối. Nhưng bóng xám trung
     tính nằm cạnh ảnh tông ẤM (gạch, vữa, gỗ — gần như mọi ảnh sản phẩm của
     site) thì mắt đọc nó ra là xám hồng: độ hoà sắc được cảm nhận theo TƯƠNG
     QUAN với vùng xung quanh, không theo giá trị tuyệt đối. Đen thì "trôi" theo
     ảnh, còn màu có sắc độ thật thì không.
     #003d26 là --c-primary (#006540) tối thêm một bậc — tối để vẫn đọc ra là
     bóng chứ không phải quầng sáng màu, và cùng gốc màu với viền/chữ hover nên
     cả trạng thái hover của card ra một khối. */
  --sh-card:  0 16px 36px rgba(0, 61, 38, .18);
  --sh-float: 0 6px 18px rgba(0,0,0,.2);
  /* Bìa e-book: đổ lệch phải-dưới. SÁT — ÍT TÁN — ĐẬM (phản hồi client
     31/07/2026). Bản trước (8px 10px 24px -6px / .30) đổ xa và tán rộng nên đọc
     ra "thẻ nổi trên mặt phẳng"; bóng của một cuốn sách đứng thì bám ngay mép
     bìa, gần như không nhoè, và tối hơn vì nguồn sáng ở gần. */
  --sh-book:  3px 4px 8px -2px rgba(0,0,0,.45);

  /* Pass "canh lề" 31/07/2026 — TRƯỚC pass này site có BỐN mép trái khác nhau ở
     cùng một màn hình 1440: chữ banner 40px (.container-sec), chữ header 64px
     (.container-sec + --header-pad), lưới mega 80px, còn body/breadcrumb/footer
     120px (.container). Chênh tới 80px, và độ chênh header↔body còn TRƯỢT theo
     bề rộng — 0px ở ≤1360, 80px ở ≥1520 — nên lỗi lúc thấy lúc không.
     Nguyên nhân không nằm ở CSS mà ở việc mỗi trang chọn nhầm một trong hai
     class. Chủ dự án chốt ép tất cả về 1440, nên hai token giờ BẰNG NHAU. */
  --maxw:      1440px;
  /* Giữ lại làm bí danh: 23 file .html đang viết `container-sec` ở khối shell,
     đổi tên token sẽ phải sửa cả 23 file mà không được gì. Đừng cho hai token
     này lệch nhau trở lại — đó chính là lỗi mà pass này đi chữa. */
  --maxw-wide: var(--maxw);
  /* Cột chữ dài. Nới body 1280 → 1440 làm đoạn văn dài thêm, mà hai đoạn đã quá
     dài sẵn: .about-infra__text / .about-eco__text đo được ~150 ký tự/dòng ở
     1440 (ngưỡng dễ đọc là 45–75). Không có token này thì việc canh lề đổi lấy
     một lỗi đọc hiểu. Dùng `ch` nên nó co theo cỡ chữ, không theo viewport. */
  --maxw-text: 68ch;

  /* ---- Thang khoảng cách (pass "canh lề" 31/07/2026) ----
     Trước pass này dự án KHÔNG có token khoảng cách nào: mọi padding/margin/gap
     là px trần, đếm được 38 giá trị rời rạc — kể cả 3, 5, 7, 9, 22, 26, 34, 38,
     46, 68, 76. Không ai chọn "26px" một cách có chủ đích cả; đó là dấu vết của
     việc mỗi lần cần khoảng hở lại gõ đại một số.

     Thang 4px. Các file CŨ chưa đổi hết — đừng đổi hàng loạt trong một pass, dễ
     lệch bố cục mà không ai soát nổi. Quy ước từ đây: giá trị MỚI lấy từ thang
     này; chạm vào rule cũ nào thì đổi rule đó sang token gần nhất. */
  --sp-1:  4px;   --sp-2:  8px;   --sp-3: 12px;   --sp-4: 16px;
  --sp-5: 20px;   --sp-6: 24px;   --sp-8: 32px;   --sp-10: 40px;
  --sp-12: 48px;  --sp-16: 64px;  --sp-20: 80px;

  /* Đệm dọc của một section. Con số này bị CHÉP TAY 15 chỗ trong 11 file — sửa
     nhịp dọc của site đang là việc phải rà 11 file và chắc chắn sót. */
  --sp-section: clamp(48px, 7vw, 88px);
  /* Chiều cao ảnh banner trang con. Trước pass phản hồi 30/07/2026 con số
     `clamp(360px, 58vh, 640px)` bị CHÉP TAY 3 chỗ (§15.3, biến thể
     .is-placeholder, và .about-banner__ph trong about.css) nên sửa một chỗ là
     lệch ngay hai chỗ kia.
     Client góp ý "tăng height banner trang con" HAI LẦN (30/07/2026):
     58vh → 66vh → 72vh. Cận dưới 430px giữ cho khổ điện thoại NẰM NGANG (cao
     ~390px) không bị banner nuốt hết màn hình — ở đó clamp rơi về cận dưới chứ
     không theo vh. Cận trên 780px chặn màn hình 4K khỏi có banner cao cả mét. */
  --h-page-banner: clamp(430px, 72vh, 780px);
  --pad:       clamp(16px, 4vw, 40px);
  /* Đệm mép cho nút điều hướng NẰM ĐÈ lên ảnh (mũi tên banner, mũi tên hero bộ
     sưu tập). Trước pass "canh lề" ba chỗ này dùng clamp(12px, 2.4vw, 34px) —
     một thang riêng, không ai biết vì sao — nên ở 1440 mũi tên rơi cách mép 34px
     trong khi cột chữ ngay dưới bắt đầu ở 40px: lệch 6px, đủ để mắt thấy "trồi
     ra". Cho ăn chung --pad thì mũi tên nằm đúng cột với nội dung. */
  --pad-overlay: var(--pad);
  /* Đáy chung của MỌI cụm nổi trên ảnh banner: khối chữ + nút CTA ở lề trái và
     cụm mũi tên / vòng đếm ở lề phải, cho cả banner trang chủ (.banner),
     banner trang con (.page-banner) và hero bộ sưu tập (.cd-hero).

     Trước pass 29/07/2026 mỗi bên tự mang một thang riêng — chữ trang chủ
     12vh, chữ trang con 7vh, mũi tên cả hai nơi 4.5vh — nên hai cụm cùng nằm ở
     dải dưới cùng của một tấm ảnh mà đáy lệch nhau 30–50px tuỳ chiều cao cửa
     sổ. Mắt đọc ra là lỗi canh lề chứ không phải nhịp có chủ ý.

     Số CỐ ĐỊNH chứ không clamp theo vh: đây là lề của một cụm nhỏ so với mép
     ảnh, không phải nhịp dọc của trang — nó không có lý do gì để co giãn theo
     chiều cao cửa sổ, mà co giãn thì hai cụm chỉ khớp khi CẢ HAI dùng đúng một
     công thức, tức là lại đúng cái bẫy vừa gỡ. Khổ ≤720px ghi đè token này
     xuống 18–32px (xem §17 và home.css) vì ở đó ảnh thấp hơn nhiều. */
  --banner-base: 48px;
  --header-h:  72px;
  --header-top: 14px;  /* header trôi nổi, cách mép trên bấy nhiêu */
  --header-pad: 24px;  /* đệm trong thanh header; §8b cộng lại đúng số này khi ghim */

  --r-sm:  12px;
  --r-md:  16px;
  --r-lg:  28px;
  --r-pill: 999px;

  --ease: cubic-bezier(.16, 1, .3, 1);
  --dur:  .35s;
  --dur-fast:  .18s;   /* phản hồi khi nhấn, đổi màu tức thì */
  --dur-media: .6s;    /* zoom ảnh khi hover — trước đây hard-code 8 chỗ */

  --reveal-ease: cubic-bezier(.22, 1, .36, 1);
  --reveal-dur:  .95s;
  --reveal-stagger: .13s;   /* nhịp so le phải đủ thưa mới ĐỌC RA được là so le */
  --reveal-shift: 56px;     /* quãng trượt của [data-aos]; xem §14 */

  /* Biến chính thức của Swiper 11. Swiper ghi `transition-duration` inline nhưng
     để timing-function cho CSS quyết định — một dòng ở đây đổi cảm giác của cả
     20 slider trên site, không phải sửa 13 file module. */
  --swiper-wrapper-transition-timing-function: var(--ease);
}

/* ===== 3 · RESET ===== */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.6;
  color: var(--c-body);
  background: var(--c-white);
  -webkit-font-smoothing: antialiased;
}
img, svg, video { max-width: 100%; display: block; }
ul, ol { margin: 0; padding: 0; list-style: none; }
h1, h2, h3, h4, h5, h6 { margin: 0; color: var(--c-ink); font-weight: var(--fw-title); line-height: 1.25; }
p { margin: 0; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; padding: 0; cursor: pointer; }
:focus-visible { outline: 2px solid var(--c-primary); outline-offset: 3px; }

/* `[hidden]{display:none}` chỉ nằm trong UA stylesheet nên THUA mọi khai báo của
   tác giả — kể cả `.grid-3{display:grid}` vốn chỉ có một class. Không có dòng này
   thì panel tab bị `main.js` gắn `hidden` vẫn hiện nguyên. Phải là !important vì
   phần tử bị ẩn có thể mang bất kỳ class layout nào. */
[hidden] { display: none !important; }

/* Liên kết "bỏ qua tới nội dung". Nằm ngoài màn hình cho tới khi được focus —
   dùng `translateY(-120%)` chứ không `display:none`/`visibility:hidden`, vì hai
   thứ đó gỡ luôn phần tử khỏi thứ tự Tab nên nó sẽ chẳng bao giờ nhận được
   focus để mà hiện ra. z-index phải cao hơn --header (100) vì header là
   position:fixed và sẽ che mất liên kết vừa hiện. */
.skip-link {
  position: fixed; top: 0; left: 50%; z-index: 200;
  padding: 12px 22px;
  background: var(--c-primary); color: #fff;
  font-size: var(--fs-sm); font-weight: var(--fw-ui);
  border-radius: 0 0 var(--r-sm) var(--r-sm);
  transform: translate(-50%, -120%);
  transition: transform var(--dur-fast) var(--ease);
}
.skip-link:focus-visible { transform: translate(-50%, 0); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ===== 4 · LAYOUT ===== */
.container      { width: 100%; max-width: var(--maxw);      margin-inline: auto; padding-inline: var(--pad); }
.container-sec  { width: 100%; max-width: var(--maxw-wide); margin-inline: auto; padding-inline: var(--pad); }
.section-pad    { padding-block: var(--sp-section); }

/* Tiêu đề section + link "Xem tất cả" nằm hai đầu — lặp ở 4/5 section trang chủ */
.sec-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px; margin-bottom: clamp(24px, 3vw, 36px);
}
.sec-head h2 { font-size: var(--fs-h2); font-weight: var(--fw-title); letter-spacing: -.01em; }

/* ===== 5 · ICON (SVG dùng làm mask, ăn theo currentColor) ===== */
.icon {
  display: inline-block; width: 18px; height: 18px; flex: none;
  background-color: currentColor;
  -webkit-mask: var(--ic) no-repeat center / contain;
          mask: var(--ic) no-repeat center / contain;
}
.icon--180 { transform: rotate(180deg); }
.icon--up  { transform: rotate(-90deg); }
.icon--sm  { width: 15px; height: 15px; }

/* ===== 6 · BUTTON ===== */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  height: 46px; padding: 0 24px; border-radius: var(--r-pill);
  font-size: var(--fs-xs); font-weight: var(--fw-title); white-space: nowrap;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease),
              border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.btn .icon { transition: transform var(--dur) var(--ease); }
.btn:hover .icon:not(.icon--180) { transform: translateX(3px); }

.btn-primary { background: var(--c-primary); color: #fff; }
.btn-primary:hover {
  background: var(--c-primary-dark);
  transform: translateY(-2px);
  box-shadow: 0 10px 24px rgba(0, 101, 64, .28);
}

/* Mặc định là nút TRẮNG viền đậm; hover mới tô xanh — client xác nhận ở pass
   phản hồi 29/07/2026 rằng nút xanh đặc trong file thiết kế (trang 26, card đầu
   trang Tuyển dụng) là ảnh chụp trạng thái hover chứ không phải trạng thái nghỉ. */
.btn-outline { border: var(--bw) solid var(--c-line-strong); background: #fff; color: var(--c-ink); }
.btn-outline:hover {
  border-color: var(--c-primary); color: #fff;
  background: var(--c-primary);
  transform: translateY(-2px);
  box-shadow: 0 10px 24px rgba(0, 101, 64, .28);
}

/* Nút trên ảnh nền tối (banner) */
.btn-ghost { border: var(--bw) solid rgba(255,255,255,.8); color: #fff; }
.btn-ghost:hover { background: #fff; color: var(--c-ink); border-color: #fff; }

/* ===== 6b · TRẠNG THÁI KHÔNG BẤM ĐƯỢC / ĐANG XỬ LÝ =====
   Trước pass "canh lề" toàn dự án KHÔNG có một khai báo :disabled nào — một nút
   bị khoá trông y hệt nút bấm được, người dùng bấm mãi mà không hiểu vì sao
   không có gì xảy ra.

   Dùng `opacity` chứ không đổi màu: nút có 4 biến thể nền khác nhau (primary,
   outline, ghost, nav) nên tô một màu xám cố định sẽ đúng với biến thể này và
   sai với ba biến thể kia. .55 là mức thấp nhất còn giữ được tương phản chữ
   trên nền primary ở ngưỡng AA (7.15 × .55 ≈ 3.9 trên nền trắng — vẫn đọc rõ,
   vẫn thấy ngay là đang mờ đi).

   Bắt cả [aria-disabled="true"]: thẻ <a> không nhận thuộc tính `disabled`, mà
   một số CTA phải ở dạng <a> để giữ đúng ngữ nghĩa liên kết. */
.btn:disabled, .btn[aria-disabled="true"],
.nav-btn:disabled, .nav-btn[aria-disabled="true"],
.pill:disabled, .pill[aria-disabled="true"] {
  opacity: .55;
  cursor: not-allowed;
  pointer-events: none;    /* chặn cả hover lẫn click, kể cả trên <a> vẫn còn href */
  box-shadow: none;
  transform: none;
}

/* Đang gửi: khoá nút và quay một vòng tròn ở đầu nhãn. Kích thước vòng bám theo
   `em` nên tự khớp mọi cỡ nút mà không cần khai riêng. */
.btn.is-loading { pointer-events: none; opacity: .8; }
.btn.is-loading::before {
  content: '';
  width: 1em; height: 1em; flex: none;
  border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%;
  animation: tl-spin .7s linear infinite;
}
@keyframes tl-spin { to { transform: rotate(360deg); } }
/* Tắt hiệu ứng thì vòng quay đứng yên sẽ thành một hình tròn hở vô nghĩa —
   thay bằng dấu chấm đặc, vẫn báo "đang bận" mà không chuyển động. */
@media (prefers-reduced-motion: reduce) {
  .btn.is-loading::before { animation: none; border-color: currentColor; }
}

/* Nút bật/tắt (nút tạm dừng banner trang chủ). banner.js đã gán aria-pressed từ
   trước nhưng chưa có CSS nào đọc nó, nên trạng thái "đang dừng" chỉ nhận ra
   được qua hình icon. */
[aria-pressed="true"] { color: var(--c-primary); }

/* Nút tròn điều hướng swiper */
.nav-btn {
  width: 44px; height: 44px; border-radius: 50%;
  border: var(--bw) solid var(--c-line-strong); color: var(--c-ink); background: #fff;
  display: inline-flex; align-items: center; justify-content: center;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease),
              background-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              opacity var(--dur) var(--ease),
              scale var(--dur-fast) var(--ease), translate var(--dur-fast) var(--ease);
}
/* Tô đặc thay vì chỉ đổi viền — nút điều hướng slider phải "gọi" được ngón tay.

   BẮT BUỘC dùng thuộc tính `scale` rời, KHÔNG dùng `transform: scale()`:
   .m-news__prev/.m-news__next (home.css) canh giữa bằng `transform: translateY(-50%)`,
   nên một khai báo `transform` ở đây sẽ ĐÈ MẤT phần canh giữa và nút nhảy tụt
   xuống nửa chiều cao ngay khi rê chuột. `scale`/`translate` rời được nhân thêm
   vào transform sẵn có thay vì thay thế nó. */
.nav-btn:hover {
  border-color: var(--c-primary); background: var(--c-primary); color: #fff;
  scale: 1.06;
  box-shadow: 0 8px 20px rgba(0, 101, 64, .26);
}
.nav-btn.swiper-button-disabled { opacity: .35; pointer-events: none; }
.nav-group { display: flex; align-items: center; justify-content: center; gap: 14px; }

/* Pill lọc / tab — dùng ở trang chủ, tin tức, thư viện, tìm kiếm */
.pill {
  height: 40px; padding: 0 22px; border-radius: var(--r-pill);
  background: var(--c-grey-light); color: var(--c-body);
  font-size: var(--fs-xs); font-weight: var(--fw-medium);
  display: inline-flex; align-items: center;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.pill:hover { background: #dedede; transform: translateY(-1px); }
.pill.is-active { background: var(--c-primary); color: #fff; font-weight: var(--fw-title); }

/* Cụm tab: CÁC NÚT RỜI, không có khung bọc.

   Phản hồi MONA R1 tr.10: "Đổi kiểu hiện thị các thanh khoanh đỏ sang kiểu nút
   riêng lẻ bên dưới" — ảnh tham chiếu vẽ đúng hai ví dụ: "Sản phẩm | Bộ sưu tập"
   thành hai nút tách bạch, và "Tất cả | Sản phẩm | Sự kiện | Cảm hứng" thành bốn
   viên rời. Kèm tr.15 cho bản điện thoại: các nút xuống dòng thay vì cuộn ngang.

   Việc này ĐẢO LẠI pass 29/07/2026 (khi đó gom mọi tab vào một khung xám nhạt vì
   file thiết kế trang 3/6/22 vẽ vậy). ĐỪNG khôi phục khung bọc — cụ thể là
   `background`, `padding` và `border-radius` trên .pill-group, cùng rule
   `.pill-group .pill { background: transparent }`. Ba thứ đó đi liền nhau: pill
   phải trong suốt CHỈ KHI có khung bọc, còn giờ mỗi pill tự mang nền xám của
   chính nó (§6 bên trên) nên phải để nguyên.

   .pill-group giờ chỉ còn là một hàng flex biết xuống dòng. `width: fit-content`
   giữ lại để `margin-inline: auto` của biến thể --center căn giữa được ngay trên
   chính phần tử — không phải bọc thêm một thẻ hàng ở 6 file HTML. */
/* Khe khai qua BIẾN chứ không viết thẳng vào `gap`: khổ hẹp còn dùng lại chính
   con số này để tính flex-basis "đúng hai nút một dòng" (§16/720). Trang nào
   muốn khe khác thì đổi --pill-gap, đừng đổi `gap` — đổi `gap` là hai chỗ lệch
   nhau và lưới hai cột vỡ thành 3+1. */
.pill-group {
  --pill-gap: 10px;
  display: flex; flex-wrap: wrap; width: fit-content; max-width: 100%;
  gap: var(--pill-gap);
}
.pill-group--center { margin-inline: auto; justify-content: center; }

/* Phản hồi khi nhấn — nhanh hơn hover để cảm giác "ăn tay". */
.btn:active, .pill:active {
  transform: translateY(1px) scale(.99);
  transition-duration: var(--dur-fast);
}
/* .nav-btn tách riêng vì cùng lý do với :hover bên trên — .m-news__prev/next đang
   giữ `transform: translateY(-50%)`, đè `transform` vào là nút nhảy khi bấm. */
.nav-btn:active {
  translate: 0 1px; scale: .99;
  transition-duration: var(--dur-fast);
}

/* Phản hồi khi bấm cho CARD (pass "canh lề" 31/07/2026).
   Trước đây chỉ .btn/.pill/.nav-btn có :active, còn card — thứ người dùng bấm
   nhiều nhất trên một site catalogue — thì không: bấm vào card gạch không có
   phản hồi nào cho tới khi trang sau tải xong. Trên mạng chậm khoảng lặng đó
   dài tới mức người ta bấm lại lần hai.

   Dùng `scale` (thuộc tính riêng) chứ KHÔNG dùng `transform`: khối ảnh trong
   card đang giữ transform riêng để nhấc lên khi hover (§15.6), ghi đè
   `transform` vào đây là hai hiệu ứng đá nhau. `scale` nằm ở một thuộc tính
   khác nên chồng lên được, đúng cách .nav-btn ngay trên đã làm. */
.tile-card, .collection-card, .news-card, .project-card,
.ebook-card, .job-card, .plain-card,
.l-gallery__item, .gallery__thumb {
  /* Khai transition ở TRẠNG THÁI NGHỈ chứ không trong :active — đặt trong
     :active thì lúc nhả chuột phần tử mất luôn khai báo và bật về nguyên cỡ
     không có nhịp nào. */
  transition: scale var(--dur-fast) var(--ease);
}
.tile-card:active, .collection-card:active, .news-card:active, .project-card:active,
.ebook-card:active, .job-card:active, .plain-card:active,
.l-gallery__item:active, .gallery__thumb:active {
  scale: .988;
}

/* ===== 7 · OVERLAY (nền mờ khi mở menu mobile) ===== */
/* z-index 105 — PHẢI trên .header (100). Bản cũ để 90 nên thanh header trắng nổi
   nguyên vẹn trên nền mờ, trông như menu chui xuống dưới header. Vẫn dưới
   .header-mobile (110) và .modal (120). */
.overlay {
  position: fixed; inset: 0; z-index: 105;
  background: rgba(0,0,0,.45);
  opacity: 0; visibility: hidden;
  transition: opacity var(--dur) var(--ease), visibility var(--dur);
}
.overlay.is-open { opacity: 1; visibility: visible; }

/* ===== 8 · HEADER DESKTOP ===== */
/* Thanh trắng bo góc trôi trên banner (đúng bản thiết kế trang 3), cuộn xuống thì
   trải dài ra sát hai mép và dính vào top:0 — xem §8b. */

/* Việc canh lề 1440px cố ý chuyển từ `max-width` sang `padding-inline`:
   `max-width: 1440px → none` KHÔNG nội suy được (`none` không phải giá trị số)
   nên header sẽ nhảy phắt sang full-bleed thay vì trải ra. Padding từ calc()
   về 0 là nội suy độ dài thuần tuý, mượt trên mọi engine.

   Bề rộng ở trạng thái nghỉ không đổi một pixel nào so với bản cũ:
   100% − 2·max(--pad, (100% − 1440px)/2 + --pad) = min(1440px, 100%) − 2·--pad. */
.header {
  position: fixed; top: var(--header-top); left: 0; right: 0; z-index: 100;
  transition: top var(--dur) var(--ease);
}
/* CỐ Ý animate `padding-inline` dù đây là thuộc tính gây layout. Hiệu ứng người
   dùng yêu cầu ĐÚNG LÀ một thay đổi layout — thanh trắng dài thật ra hai bên —
   nên transform không thay thế được: `scaleX` sẽ kéo méo luôn bo góc (thứ cũng
   đang chạy về 0) và bóp ngang cả logo, menu. Chi phí chấp nhận được vì:
   · .header là `position: fixed` → layout lại chỉ trong cây con của header,
     không đụng tới dòng chảy của cả trang;
   · đây là chuyển trạng thái 350ms bắn 2 lần mỗi phiên cuộn, không phải hoạt
     ảnh lặp hay bám theo từng frame cuộn. */
.header .container-sec {
  max-width: none;
  padding-inline: max(var(--pad), (100% - var(--maxw-wide)) / 2 + var(--pad));
  /* impeccable-disable-next-line layout-transition -- hiệu ứng bản chất LÀ layout, lý do đầy đủ ở khối chú thích trên */
  transition: padding-inline var(--dur) var(--ease);
}
.header__inner {
  position: relative;
  display: flex; align-items: center; gap: clamp(14px, 2vw, 30px);
  height: var(--header-h);
  padding: 0 var(--header-pad);
  background: #fff; border-radius: var(--r-md);
  box-shadow: 0 6px 24px rgba(0,0,0,.08);
  transition: box-shadow var(--dur) var(--ease),
              border-radius var(--dur) var(--ease),
              padding-inline var(--dur) var(--ease);
}
.header.is-scrolled .header__inner { box-shadow: 0 10px 30px rgba(0,0,0,.14); }

/* ===== 8b · HEADER KHI ĐÃ CUỘN =====
   Header KHÔNG bao giờ tự ẩn — trước đây có `.is-hidden` trượt header ra khỏi
   màn hình, đã gỡ (§12.4 BUILD.md).

   PHẢN HỒI MONA R1 (tr.5 bản máy tính + tr.11 bản điện thoại):
   "Lúc trượt trang web thì menu bar bị đổi thành thanh trắng ngang, mình muốn
   giữ nguyên khung chữ nhật nổi ban đầu xuyên suốt ạ."

   Trước pass này, main.js gắn thêm lớp `.is-pinned` từ 8px cuộn trở đi và nhóm
   rule ở đây biến viên pill trắng thành dải trắng full-bleed: `top: 0`,
   `.container-sec { padding-inline: 0 }`, `border-radius: 0` cho cả
   .header__inner, .scroll-progress và .mega. TOÀN BỘ nhóm đó đã được GỠ, kèm
   theo lớp `.is-pinned` trong main.js (initScrollUI) vì không còn ai dùng.

   Giờ header giữ nguyên `--header-top` và `--r-md` ở mọi vị trí cuộn; thứ duy
   nhất còn đổi theo trạng thái cuộn là đổ bóng (`.is-scrolled` bên trên) và độ
   mờ của thanh tiến độ đọc.

   ĐỪNG khôi phục `top: 0` / `padding-inline: 0` / `border-radius: 0` — đó chính
   là thứ client đã trả lại. Ghi chú "canh lề logo lúc ghim" của pass trước cũng
   theo đó mà hết hiệu lực: ở trạng thái nổi, mép viên pill luôn trùng mép cột
   nội dung nên không còn vấn đề lệch cột nào để cân. */

/* Bố cục 3 phần: logo trái · menu GIỮA · tìm kiếm + ngôn ngữ phải (thiết kế
   trang 3). Cách làm: hai đầu cùng `flex: 1 1 0` — cùng flex-basis 0 nên nhận
   phần trống chia đôi bằng nhau, đẩy cụm giữa (nav + badge) về đúng tâm mà
   KHÔNG cần bọc thêm thẻ hay dùng position:absolute (absolute sẽ làm nav thoát
   khỏi dòng chảy và chồng lên logo ở khổ hẹp).
   `min-width: max-content` giữ cho logo/actions không bị bóp méo khi nav dài. */
.header__logo { flex: 1 1 0; min-width: max-content; }
.header__logo img { height: 34px; width: auto; }

/* Khe giữa các mục menu: 20px ở 1280 · 23px ở 1440 · 26px từ ~1611px trở lên,
   tức ~1,4× bản trước (14 / 16 / 20). Theo yêu cầu client "tăng thêm khoảng cách
   giữa các menu trên header desktop" — nối tiếp phản hồi MONA R1 tr.6 ("các chữ
   đang hơi dính nhau quá"), lần đó mới chỉ bỏ bớt một mục chứ chưa nới khe.

   Bản đầu của pass này để 24 / 28 / 32; client rà lại thấy rộng quá ("giảm nhẹ
   khoảng cách giữa các menu") nên hạ về mức hiện tại. ĐỪNG đẩy ngược lên 32px —
   đó đúng là thứ vừa bị trả lại.

   Trần và sàn ĐỀU đo bằng thực nghiệm, không chọn cho tròn. Cách đo: tăng dần
   gap tới khi `.header__nav`, `.header__logo` hoặc `.header__actions` bắt đầu bị
   bóp (scrollWidth > clientWidth). Bản EN luôn là bản chặn vì nhãn dài hơn.
   Điểm CHẬT NHẤT của dải này KHÔNG phải mép trái 1201px mà là 1241px (EN chịu
   tối đa 34px) — đúng chỗ khối @media 1025–1240 nhả lại --header-pad, nav mất
   phần đệm vừa được vay. Sàn 20px chừa ~15px dự phòng ở đúng điểm đó.
     1201 → en≤37 · 1241 → en≤34 · 1280 → en≤40 · 1320 → en≤47 · 1440 → en≤65
   Từ ~1611px container chạm --maxw nên trần 26px là đủ, tăng nữa chỉ đẩy nav xa
   khỏi cụm giữa mà không lấy thêm được chỗ.

   Dải 1025–1200 có rule riêng bên dưới (§16) vì ở đó cỡ chữ nav nhỏ hơn. */
.header__nav { display: flex; align-items: center; gap: clamp(20px, 1.8vw - 3px, 26px); }
/* Cố ý để static: mega-menu trải hết chiều rộng .header__inner (khối chứa
   định vị gần nhất). Nếu đặt relative ở đây, panel co lại bằng bề rộng chữ
   "SẢN PHẨM" (~80px). */
.header__nav > li { position: static; }
.header__nav a {
  display: flex; align-items: center; gap: 6px;
  font-size: 13px; font-weight: var(--fw-ui); letter-spacing: .02em; text-transform: uppercase;
  color: var(--c-ink); white-space: nowrap;
  padding: 8px 0;
  transition: color var(--dur) var(--ease);
}
.header__nav a:hover,
.header__nav li.is-open > a { color: var(--c-primary); }

/* Mục "Sản phẩm" KHÔNG còn href (phản hồi client 01/08/2026 — bấm vào chỉ mở
   bảng mega, không dẫn đi đâu). <a> không href thì UA trả con trỏ về mũi tên
   thường, trông y như chữ chết trong khi nó vẫn bấm được; khai lại cho đúng.
   Giữ thẻ <a> thay vì đổi sang <button> để mọi rule .header__nav a ở §8/§9
   (cỡ chữ, hover, aria-current, breakpoint) không phải viết lại lần thứ hai. */
.header__nav .js-mega-trigger,
.header-mobile__nav .m-row__link { cursor: pointer; }

/* MỤC ĐANG XEM. QA-REPORT P2-6 đã thêm aria-current vào 19 file, nhưng KHÔNG có
   rule nào vẽ nó ra — trợ năng đọc được "trang hiện tại" còn người nhìn thì
   không thấy gì, mục đang xem trông y hệt 7 mục kia.

   Báo hiệu bằng HAI thứ chứ không riêng màu (WCAG 1.4.1): chữ đậm lên --fw-title
   và màu xanh thương hiệu. GẠCH CHÂN ĐÃ GỠ (phản hồi client 31/07/2026) — bản
   trước vẽ thêm một thanh 2px bằng ::after, client thấy rối và file thiết kế
   cũng không vẽ. Cặp màu + độ đậm vẫn là hai kênh phân biệt, nên 1.4.1 giữ nguyên.
   Menu mobile báo hiệu bằng chấm tròn (§ header-mobile) — không đụng tới.

   NHẬN CẢ HAI GIÁ TRỊ, và đó là chủ ý:
   · `page` — link trỏ ĐÚNG trang đang mở (Giới thiệu ở gioi-thieu.html).
   · `true` — link trỏ trang CHA của nhánh đang mở ("Sản phẩm" khi đang ở
     bo-suu-tap-con.html). Trước 31/07/2026 chỗ này cũng ghi `page`, sai spec:
     `aria-current="page"` nghĩa là "chính trang này", nên trình đọc màn hình
     thông báo nhầm hai trang khác nhau là cùng một trang.
   Về THỊ GIÁC hai trạng thái vẽ y hệt nhau — người nhìn chỉ cần biết "đang ở
   nhánh nào", phân biệt sâu hơn là việc của trợ năng. `:is()` lấy độ ưu tiên
   cao nhất trong ngoặc, mà cả hai đều là attribute (0,1,0), nên tổng vẫn đúng
   0,2,1 y như bản cũ — mọi tính toán đè nhau ở §9 giữ nguyên. */
.header__nav a:is([aria-current="page"], [aria-current="true"]) {
  color: var(--c-primary); font-weight: var(--fw-title);
}

.header__badge { flex: none; }
.header__badge img { height: 26px; width: auto; }

.header__actions {
  display: flex; align-items: center; justify-content: flex-end; gap: 14px;
  flex: 1 1 0; min-width: max-content;
}
.header__divider { width: 1px; height: 20px; background: var(--c-line); }
.header__search-btn { color: var(--c-ink); display: flex; transition: color var(--dur) var(--ease); }
.header__search-btn:hover { color: var(--c-primary); }
.header__burger { display: none; color: var(--c-ink); transition: color var(--dur) var(--ease); }
/* Ba nút này là những nút duy nhất còn thiếu phản hồi khi rê chuột — nút kính
   lúp ngay cạnh burger thì có, nên thiếu ở đây đọc ra là hỏng chứ không phải cố ý. */
.header__burger:hover,
.header-mobile__nav .m-toggle:hover { color: var(--c-primary); }
/* Chỉ khai `:hover`. `transition` của .check đã có sẵn ở §15.7 (pass accordion,
   §15c) với đúng cùng giá trị — khai lại ở đây là hai nguồn sự thật cho một
   thuộc tính, sửa một chỗ sẽ tưởng là xong. */
.check:hover { color: var(--c-ink); }

/* Vùng bấm cảm ứng cho các nút CHỈ CÓ ICON — icon chỉ 18×18px, tự nó là vùng
   bấm 18px. Nới ra 44×44 bằng ::after phủ lên nên kích thước HIỂN THỊ không đổi,
   bố cục header giữ nguyên. (WCAG 2.5.8 tối thiểu 24×24; Apple HIG 44×44.)

   Trước pass "canh lề" khối này nằm TRONG @media (max-width: 1024px) — tức là
   trên desktop nút kính lúp và nút đặt lại bộ lọc vẫn chỉ 18×18. Ngưỡng 2.5.8
   không phân biệt thiết bị: chuột cũng cần đích đủ lớn, và màn hình cảm ứng
   rộng hơn 1024px thì rất nhiều. Đã đưa lên đây để áp ở MỌI khổ. */
.header__burger, .header__search-btn, .js-menu-close, .filter__reset { position: relative; }
.header__burger::after, .header__search-btn::after,
.js-menu-close::after, .filter__reset::after {
  content: ''; position: absolute; top: 50%; left: 50%;
  width: 44px; height: 44px; transform: translate(-50%, -50%);
}

/* Ô tìm kiếm bung ra khi bấm icon kính lúp.

   `padding-inline: inherit` chứ KHÔNG phải một con số: ô này là `inset: 0` phủ
   lên .header__inner, nên muốn kính lúp thẳng hàng với logo và nút × thẳng hàng
   với cụm ngôn ngữ thì đệm ngang của nó phải LUÔN bằng đệm ngang của
   .header__inner — mà con số đó đổi theo ba trạng thái: 24px lúc nghỉ, 16px ở
   ≤1024px, và max(--pad, (100% - --maxw)/2 + --pad) lúc ghim (§8b).
   Bản cũ hard-code `padding: 0 24px` nên lúc header ghim (trải full-bleed), logo
   chạy ra mép cột nội dung (280px ở khổ 1920) còn ô search vẫn dính 24px — bấm
   kính lúp lúc đang cuộn là thấy nó thụt hẳn ra lề so với cả thanh header.
   `inherit` lấy giá trị ĐÃ TÍNH của cha nên tự bám cả ba trạng thái, và vì cha
   đang transition padding-inline nên nó chạy theo cùng nhịp, không cần khai
   thêm transition riêng. */
.header__search {
  position: absolute; inset: 0; z-index: 2;
  display: flex; align-items: center; gap: 12px;
  padding-block: 0; padding-inline: inherit;
  background: #fff; border-radius: var(--r-md);
  opacity: 0; visibility: hidden; transform: translateY(-6px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease), visibility var(--dur);
}
.header__search.is-open { opacity: 1; visibility: visible; transform: none; }
.header__search input {
  flex: 1; height: 44px; border: 0; background: none; font-size: var(--fs-body);
}
.header__search input::placeholder { color: var(--c-muted); }
/* Bỏ vòng focus xanh của §3 trên riêng ô nhập này: overlay bung ra đè hết header
   + con trỏ nhấp nháy đã là tín hiệu focus rõ ràng, thêm khung 2px nữa thì ô
   search trông như bị lỗi viền. Nút đóng (×) vẫn giữ outline nên người dùng bàn
   phím vẫn định vị được khi Tab. Cần độ đặc hiệu (0,2,1) mới ghi đè được
   `:focus-visible` toàn cục (0,1,0). */
.header__search input:is(:focus, :focus-visible) { outline: none; }

/* ===== 9 · MEGA MENU (trang 4 file thiết kế) ===== */
/* Panel DÍNH LIỀN header (thiết kế trang 4): trước pass phản hồi 29/07/2026 nó
   cách 10px nên nhìn như hai khối rời.
   `clip-path` cắt phần bóng hắt ngược LÊN TRÊN — không có nó, dải bóng 46px chạy
   vắt ngang chỗ nối và panel vẫn trông tách khỏi header dù đã sát 0px. */
.mega {
  position: absolute; top: 100%; left: 0; right: 0;
  background: #fff; border-radius: 0 0 var(--r-md) var(--r-md);
  box-shadow: 0 18px 46px rgba(0,0,0,.12);
  clip-path: inset(0 -80px -80px -80px);
  padding: 30px 40px 38px;
  opacity: 0; visibility: hidden; transform: translateY(-8px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease), visibility var(--dur);
}
.header__nav li.is-open .mega { opacity: 1; visibility: visible; transform: none; }
/* Thanh header nhả bo góc dưới khi panel mở → hai khối thành một mặt trắng liền */
.header__inner:has(.header__nav li.is-open) {
  border-radius: var(--r-md) var(--r-md) 0 0;
}

.mega__grid { display: grid; grid-template-columns: 4fr 2fr; gap: 46px; }
/* Tiêu đề nhóm + gạch ngang mảnh chạy hết chiều rộng nhóm */
.mega__group > :is(h3, p) {
  font-size: 13px; font-weight: var(--fw-ui); letter-spacing: .1em; text-transform: uppercase;
  color: var(--c-muted);
  padding-bottom: 12px; margin-bottom: 22px; border-bottom: 1px solid var(--c-line);
}
.mega__cols { display: grid; gap: 30px; }
.mega__group:first-child .mega__cols { grid-template-columns: repeat(4, 1fr); }
.mega__group:last-child  .mega__cols { grid-template-columns: repeat(2, 1fr); }
.mega__col :is(h4, p) {
  font-size: 13px; font-weight: var(--fw-ui); letter-spacing: .08em; text-transform: uppercase;
  color: var(--c-ink); margin-bottom: 12px;
}
.mega__col li + li { margin-top: 9px; }
.mega__col a {
  font-size: var(--fs-sm); font-weight: 400; text-transform: none; letter-spacing: 0;
  color: var(--c-body); padding: 0;
  position: relative; padding-left: 13px;
  transition: color var(--dur) var(--ease);
}
.mega__col a::before {
  content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%);
  width: 4px; height: 4px; border-radius: 50%; background: var(--c-primary);
}
/* Hover ĐỔI MÀU + IN ĐẬM, không chỉ đổi màu (phản hồi client 31/07/2026: "hover
   in đậm cho dễ nhìn").
   Bản cũ đi từ #3d3d3d sang #006540: so với nền trắng thì xanh này tương phản
   tốt (7,2:1), nhưng người dùng KHÔNG so mục hover với nền — họ so nó với 20 mục
   còn lại quanh đó, mà cả hai đều đọc ra là "chữ tối". Liếc qua rất khó thấy con
   trỏ đang nằm ở mục nào. Thêm độ đậm là thêm một kênh phân biệt không phụ thuộc
   màu — đúng cặp màu + weight mà nav header đang dùng cho mục đang chọn (§18.2
   mục 4), và thoả WCAG 1.4.1.
   Chỉ `color` được transition: cho weight chạy mượt (Inter là variable font nên
   400→600 nội suy được) sẽ dồn chữ lại từng frame trong suốt 240ms, mắt bắt
   ngay. Nhảy một nhịp thì không.
   Kèm `:focus-visible` để người dùng bàn phím thấy cùng một tín hiệu, không chỉ
   mỗi vòng outline. */
.mega__col a:is(:hover, :focus-visible) {
  color: var(--c-primary); font-weight: var(--fw-ui);
}

/* Tiêu đề nhóm cũng bấm được để sang trang danh mục (phản hồi client: "trong menu
   cho click xem được các trang như Sản phẩm khác, Thiết bị vệ sinh"). Link nằm
   BÊN TRONG h3/h4 nên phải huỷ lại quy tắc của .mega__col a ở trên — nhất là
   chấm tròn xanh ::before vốn chỉ dành cho mục con. */
.mega__group > :is(h3, p) a,
.mega__col :is(h4, p) a {
  font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
  padding: 0; position: static;
  transition: color var(--dur) var(--ease);
}
.mega__group > :is(h3, p) a::before,
.mega__col :is(h4, p) a::before { content: none; }
.mega__group > :is(h3, p) a:hover,
.mega__col :is(h4, p) a:hover { color: var(--c-primary); }

/* MỤC MEGA-MENU TRỎ VỀ CHÍNH TRANG ĐANG XEM. Cùng ngôn ngữ với nav cấp 1 ở §8:
   màu thương hiệu + `--fw-title`, hai kênh chứ không riêng màu (WCAG 1.4.1).

   Phải viết lại ở đây dù §8 đã có `.header__nav a[aria-current="page"]` (chọn
   trúng cả mục mega vì mega nằm trong `.header__nav`), vì hai quy tắc ĐỨNG SAU
   nó ghi đè mất:
   · `.mega__col a:is(:hover,…)` — cùng độ ưu tiên 0,2,1 nhưng đứng sau, nên rê
     chuột lên mục đang xem lại làm nó MỎNG ĐI (700 → 600). Tín hiệu ngược.
   · `.mega__group > :is(h3,p) a` — `font: inherit` xoá luôn `font-weight` của
     §8, nên tiêu đề nhóm đang xem chỉ còn màu.
   Khối này đứng cuối §9 và có 0,3,1 ở hai dòng dưới nên thắng cả hai.

   Mục đang xem KHÔNG cần trạng thái hover riêng: nó đã ở mức nhấn cao nhất, đổi
   thêm nữa chỉ làm nhiễu. */
.mega__col a[aria-current="page"],
.mega__group > :is(h3, p) a[aria-current="page"],
.mega__col :is(h4, p) a[aria-current="page"] {
  color: var(--c-primary); font-weight: var(--fw-title);
}
/* Bản mobile của trạng thái này nằm ở §11, ngay sau quy tắc gốc — đặt ở đây thì
   thua thứ tự nguồn. */

/* ===== 10 · SELECT TUỲ BIẾN (.re-select-main) ===== */
/* Dùng cho VN/EN ở header và 5 bộ lọc ở section "Bạn cần tìm sản phẩm gạch?" */
.re-select-main { position: relative; }
.re-select__btn {
  position: relative;
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 13px; font-weight: var(--fw-ui); color: var(--c-ink);
  transition: color var(--dur) var(--ease);
}
/* Nút chọn ngôn ngữ ở header chỉ cao ~21px (13px chữ, không đệm dọc) — DƯỚI
   ngưỡng 24×24 của WCAG 2.5.8. Nới vùng chạm theo chiều dọc bằng ::after, giữ
   nguyên kích thước hiển thị để cụm header không xô lệch. Chỉ cao 44px chứ
   không đặt bề rộng: nút này nằm trong flex row, ::after rộng cố định sẽ đè lên
   dấu gạch --header-divider bên cạnh. */
.re-select__btn::after {
  content: ''; position: absolute; top: 50%; left: 0; right: 0;
  height: 44px; transform: translateY(-50%);
}
.re-select__btn:hover { color: var(--c-primary); }
.re-select__btn .icon { width: 14px; height: 14px; transition: transform var(--dur) var(--ease); }
.re-select-main.is-open .re-select__btn .icon { transform: rotate(180deg); }

.re-select__list {
  position: absolute; right: 0; top: calc(100% + 8px); min-width: 150px; z-index: 5;
  /* Bo góc PHẢI BẰNG ô đóng, không phải --r-sm của card (phản hồi client
     31/07/2026: "field bộ lọc border radius nhỏ thôi, đồng bộ với radius khi mở").
     Ô đóng .re-select__btn dùng --r-field = 8px; bảng xổ ra mà bo 12px thì lúc
     mở ra hình dáng đổi ngay dưới con trỏ, đọc thành hai component khác nhau. */
  background: #fff; border: 1px solid var(--c-line); border-radius: var(--r-field);
  box-shadow: 0 12px 28px rgba(0,0,0,.12); padding: 6px;
  opacity: 0; visibility: hidden; transform: translateY(-6px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease), visibility var(--dur);
  max-height: 260px; overflow-y: auto;
}
.re-select-main.is-open .re-select__list { opacity: 1; visibility: visible; transform: none; }
/* Nhận cả <a>: trên WordPress, bộ chọn ngôn ngữ là LIÊN KẾT thật
   (`<a data-lang="en" href="/en/…">`) chứ không phải <button> như bản tĩnh —
   để mở tab mới được và để máy tìm kiếm lần ra bản dịch. Chỉ nhắm `button` thì
   hai lựa chọn rơi về `display:inline` mặc định của thẻ a, nằm dính vào nhau
   thành một dòng "Tiếng ViệtEnglish": không padding, không trạng thái chọn.
   Các ô lọc vẫn là <button>, nên gộp cả hai vào cùng bộ quy tắc. */
.re-select__list button,
.re-select__list a {
  display: block; width: 100%; text-align: left; padding: 9px 12px;
  /* 6px chứ không 8px: bo TRONG phải nhỏ hơn bo NGOÀI của panel (--r-field = 8px),
     bằng nhau thì góc option đội ra khỏi mép bảng ở đúng 4 góc. */
  border-radius: 6px; font-size: var(--fs-sm); color: var(--c-body);
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.re-select__list button:hover,
.re-select__list a:hover { background: var(--c-grey-light); color: var(--c-primary); }
/* Mục ĐANG được chọn trong danh sách: đậm + có dấu ✓, không chỉ đổi màu. Mở
   danh sách ra phải thấy ngay mình đang ở lựa chọn nào mà không cần so màu. */
.re-select__list button.is-active,
.re-select__list a.is-active {
  background: color-mix(in srgb, var(--c-primary) 10%, #fff);
  color: var(--c-primary); font-weight: var(--fw-ui);
}
/* Dấu tick VẼ BẰNG CSS chứ không dùng ký tự "✓" (U+2713): không subset Inter
   nào chứa ký tự đó — kể cả bản Google CDN phát — nên nó rơi về font hệ thống,
   lệch nét so với phần còn lại. Hai cạnh của một hình vuông xoay 45° cho đúng
   hình chữ V, ăn theo currentColor và tự đậm nhạt theo cỡ chữ. */
.re-select__list button.is-active::after,
.re-select__list a.is-active::after {
  content: ""; float: right;
  width: 5px; height: 9px; margin: 4px 2px 0 10px;
  border: solid currentColor; border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

/* ----- 10b · Ô LỌC (.re-select-main--field) — CHƯA CHỌN vs ĐÃ CHỌN -----
   Phản hồi client 30/07/2026: "thêm các state cho field input để biết cái nào
   chưa chọn, cái nào chọn dropdown rồi".

   Ba trạng thái phân biệt được bằng CẢ màu lẫn hình, không chỉ bằng màu (WCAG
   1.4.1 — người mù màu xanh-đỏ vẫn phải đọc ra):
     · CHƯA CHỌN  — nền trắng, chữ MỜ như placeholder, không có chấm
     · ĐANG MỞ    — viền xanh + quầng, mũi tên lật 180° (§10 lo)
     · ĐÃ CHỌN    — nền ngả xanh, viền xanh, chữ ĐẬM màu mực, CÓ chấm tròn

   CỐ Ý không tô nền xám cho ô rỗng dù đó là cách nhanh nhất: ở index.html cả
   cụm lọc nằm trong khung `.m-search__box` vốn đã là --c-grey-light, ô xám trên
   nền xám là biến mất. Chữ mờ + viền nhạt đọc ra "rỗng" trên MỌI nền.

   Khối này trước nằm ở catalog.css §4b nên chỉ sống trên 9 trang nạp file đó —
   index.html và he-thong-phan-phoi.html, tức chính hai trang có bộ lọc nổi bật
   nhất, lại KHÔNG có state nào. Chuyển lên đây để dùng chung.

   `data-value` là tín hiệu tin cậy: main.js gán khi người dùng chọn, catalog.js
   và product-list.js gỡ khi đặt lại hoặc khi tham số URL không khớp lựa chọn
   nào. Không cần thêm class nào trong JS. */
.re-select-main--field .re-select__btn {
  width: 100%; height: 46px; padding: 0 16px; justify-content: space-between;
  border: var(--bw) solid var(--c-line-strong); border-radius: var(--r-field);
  /* PHẢI khai lại font-size: rule gốc .re-select__btn ở §10 đặt 13px cho ô chọn
     ngôn ngữ bé xíu trên header. Biến thể "ô lọc" dùng lại rule đó nên thừa
     hưởng luôn 13px, trong khi mọi ô nhập khác (.field input) đã là 15px —
     client đọc ra thành "font bị lỗi ở chỗ field" (phản hồi 30/07/2026), thực
     chất là lệch CỠ chứ không phải lệch font: đo bằng CDP thì 100% glyph vẫn là
     Inter. */
  font-size: var(--fs-sm); font-weight: 400;
  /* Nghỉ = CHƯA CHỌN: chữ mờ đúng như placeholder của <input>. Trước đây nhãn
     "Chọn kích thước" và giá trị đã chọn cùng một màu nên nhìn cột 5 ô không
     biết ô nào mình đã đụng tới. */
  background: #fff; color: var(--c-muted);
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease),
              color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.re-select-main--field .re-select__btn:hover {
  border-color: var(--c-primary); color: var(--c-ink);
}
/* Đang mở: quầng mảnh thay cho việc làm dày viền — dày viền lên 2px sẽ đẩy chữ
   bên trong dịch nửa pixel, cả cột 5 ô rung khi bấm lần lượt. */
/* "Đang mở" và "đang được focus bằng bàn phím" TÁCH RIÊNG. Trước pass "canh lề"
   hai trạng thái này dùng chung một khối, mà khối đó có `outline: none` — nên ô
   lọc được focus bằng Tab chỉ còn quầng xanh 14% độ đục, quá nhạt để đạt 3:1.
   Giờ: mở = quầng nhạt (tín hiệu thẩm mỹ, đi kèm mũi tên lật); focus bàn phím =
   vòng focus toàn cục ở §3, không bị xoá nữa. Ô vừa mở vừa focus thì có cả hai. */
.re-select-main--field.is-open .re-select__btn {
  background: #fff; border-color: var(--c-primary); color: var(--c-ink);
  box-shadow: 0 0 0 3px rgba(0, 101, 64, .14);
}
.re-select-main--field .re-select__btn:focus-visible {
  background: #fff; border-color: var(--c-primary); color: var(--c-ink);
}

/* ĐÃ CHỌN */
.re-select-main--field[data-value] .re-select__btn {
  border-color: var(--c-primary);
  background: color-mix(in srgb, var(--c-primary) 6%, #fff);
  color: var(--c-ink);
  font-weight: var(--fw-medium);
}
/* Bản trước có thêm chấm tròn xanh trước nhãn làm chỉ dấu thứ hai; client bỏ
   (phản hồi 30/07/2026). Trạng thái ĐÃ CHỌN vẫn phân biệt được KHÔNG chỉ bằng
   màu: nền ngả xanh + viền xanh + chữ đậm hơn + nhãn đổi từ placeholder mờ sang
   giá trị thật màu mực — đủ tín hiệu cho cả người mù màu. */
.re-select-main--field[data-value] .re-select__btn .icon { color: var(--c-primary); }

.re-select-main--field .re-select__list { left: 0; right: 0; min-width: 0; }

/* Nhãn ô lọc GIỮ ĐÚNG MỘT HÀNG ở MỌI khổ, thừa thì cắt bằng "…".
   Quy tắc chung cho field input (phản hồi client 30/07/2026): ô lọc cao đúng một
   dòng; nhãn dài hơn khung thì cắt, KHÔNG BAO GIỜ xuống dòng.

   Trước đây rule này chỉ nằm trong @media (max-width: 640px) của distributor.css
   nên chỉ cứu he-thong-phan-phoi.html ở khổ điện thoại. Khổ tablet vẫn vỡ: hàng
   lọc là grid `1fr 1fr auto` giữ nguyên hai cột từ desktop, nên mỗi ô chỉ còn
   ~nửa khung — "Select a province or city" (bản EN dài hơn "Chọn tỉnh, thành
   phố") xuống hai dòng, đội .re-select__btn từ 46px lên ~66px và làm hai ô cạnh
   nhau lệch đáy. Đặt ở đây thì mọi ô lọc trên MỌI trang (index, dự án, tuyển
   dụng, danh mục sản phẩm) cùng theo một quy tắc.

   Ba khai báo PHẢI đi cùng nhau, thiếu một cái là quay về xuống dòng:
   · min-width: 0 — .re-select__current là flex item của .re-select__btn
     (inline-flex, §10) và .re-select-main--field là grid/flex item của hàng lọc.
     Cả hai mặc định có min-width: auto = min-content, tức BỀ RỘNG CẢ CHUỖI khi
     đã nowrap; không hạ sàn đó thì text-overflow không bao giờ được gọi tới.
   · white-space: nowrap — chặn xuống dòng.
   · overflow: hidden + text-overflow: ellipsis — cắt bằng "…".

   .icon để flex: none để phần bị cắt luôn là CHỮ chứ không phải mũi tên: caret
   biến mất thì ô mất luôn tín hiệu "bấm được để mở". */
.re-select-main--field { min-width: 0; }
.re-select-main--field .re-select__btn { min-width: 0; }
.re-select-main--field .re-select__current {
  min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.re-select-main--field .re-select__btn .icon { flex: none; }

/* ===== 11 · HEADER MOBILE ===== */
/* z-index 110 — PHẢI trên .header (100) để menu phủ kín cả thanh header, không
   thì header trắng vẽ đè lên .header-mobile__top và nuốt mất logo + nút đóng.
   Vẫn dưới .modal (120) để lightbox mở từ trong menu còn nổi lên trên được.

   width: rộng gần hết màn hình để khối thư mục con của "Sản phẩm" đủ chỗ chia
   hai cột (xem .header-mobile__sub bên dưới). min() giữ chặn trên 460px cho
   máy tính bảng, ở đó menu vẫn là tấm trượt chứ không phải toàn màn hình. */
.header-mobile {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 110;
  width: min(460px, 100vw);
  background: #fff; display: flex; flex-direction: column;
  transform: translateX(100%);
  transition: transform var(--dur) var(--ease);
  overflow-y: auto;
}
.header-mobile.is-open { transform: none; }
.header-mobile__top {
  display: flex; align-items: center; justify-content: space-between;
  padding: 18px 20px; border-bottom: 1px solid var(--c-line);
}
.header-mobile__top img { height: 30px; width: auto; }
.header-mobile__search {
  display: flex; align-items: center; gap: 10px; margin: 16px 20px 4px;
  height: 44px; padding: 0 14px;
  background: var(--c-grey-light); border-radius: var(--r-pill);
}
.header-mobile__search input { flex: 1; border: 0; background: none; font-size: var(--fs-sm); }
/* Cùng lý do với ô search ở header desktop (§8): bỏ vòng focus xanh của §3 vì
   viên pill nền xám đã tự viền lấy ô nhập, thêm outline nữa thành hai khung
   lồng nhau. Con trỏ nhấp nháy vẫn là chỉ báo focus; các mục menu bên dưới giữ
   nguyên outline nên Tab trong drawer vẫn theo dõi được. */
.header-mobile__search input:is(:focus, :focus-visible) { outline: none; }
.header-mobile__nav { padding: 10px 20px; }
.header-mobile__nav > li { border-bottom: 1px solid var(--c-line); }
.header-mobile__nav a,
.header-mobile__nav .m-toggle {
  display: flex; align-items: center; justify-content: space-between; width: 100%;
  padding: 14px 0; font-size: var(--fs-sm); font-weight: var(--fw-ui); text-transform: uppercase;
  letter-spacing: .02em; color: var(--c-ink); text-align: left;
}
/* Mục đang xem trong ngăn kéo mobile — cùng nguyên tắc với nav desktop: đậm +
   màu, thêm một chấm tròn đứng trước để không chỉ dựa vào màu. Ở đây dùng chấm
   thay gạch dưới vì mỗi mục đã có sẵn `border-bottom` phân cách, thêm gạch nữa
   là hai đường kẻ chồng nhau.
   Nhận cả `page` lẫn `true` — xem lý do đầy đủ ở §8. */
.header-mobile__nav a:is([aria-current="page"], [aria-current="true"]) {
  color: var(--c-primary); font-weight: var(--fw-title);
}
.header-mobile__nav a:is([aria-current="page"], [aria-current="true"])::before {
  content: ''; width: 6px; height: 6px; margin-right: 10px; border-radius: 50%;
  background: currentColor; flex: none;
}
/* ::before là con đầu tiên của flex `space-between`, nên nhãn chữ bị đẩy sang
   phải mất. Cho nhãn ăn hết phần trống còn lại để chữ vẫn bám mép trái. */
.header-mobile__nav a:is([aria-current="page"], [aria-current="true"]) { justify-content: flex-start; }

/* --- Mục ĐANG XEM nằm trong ngăn kéo con (mega-menu gập lại) --- */
/* Link nằm trong h4/p bị `font: inherit` của quy tắc "tiêu đề nhóm bấm được"
   bên dưới xoá mất cả màu lẫn độ đậm — trả lại. */
.header-mobile__sub :is(h4, p) a[aria-current="page"] {
  color: var(--c-primary); font-weight: var(--fw-title);
}
/* CHẤM TRÒN thì GỠ ở cấp này. Nó được vẽ cho các hàng điều hướng CẤP 1 — một
   cột, nên chấm đẩy nhãn sang phải cũng không lệch với ai. Danh sách con lại xếp
   HAI CỘT: chấm 6px + lề 10px đẩy đúng một mục thụt vào 16px so với các mục cùng
   cột, mép cột gãy hẳn ra. Đây là tác dụng phụ chứ không phải quyết định thiết
   kế — trước pass này chưa mục con nào mang aria-current nên chưa ai gặp.
   Bỏ chấm vẫn còn ĐỦ HAI KÊNH (màu + `--fw-title`), đúng như bản mega desktop
   vốn cũng không có chấm → WCAG 1.4.1 giữ nguyên.
   Bắt cả `true` dù hiện chưa mục con nào dùng: lý do gỡ chấm là BỐ CỤC hai cột,
   không liên quan giá trị nào của aria-current. */
.header-mobile__sub a:is([aria-current="page"], [aria-current="true"])::before { content: none; }

/* --- Hàng "Sản phẩm": nhãn + mũi tên, CẢ HAI đều gập/mở ---
   Đã đi một vòng: ban đầu cả hàng là <button> gập/mở; rồi tách nhãn thành <a>
   dẫn sang trang danh sách; nay client chốt (01/08/2026) mục "Sản phẩm" không
   dẫn đi đâu nữa, nên nhãn quay lại làm tay cầm gập/mở — nhưng GIỮ thẻ <a>
   (role="button") để không phải viết lại cả cụm rule .header-mobile__nav a.
   Đường vào danh sách đầy đủ nay nằm ở hai tiêu đề nhóm bên trong: "Gạch ốp
   lát" → toàn bộ gạch, "Sản phẩm khác" → toàn bộ sản phẩm khác.
   Vẫn để hai vùng bấm tách bạch: mũi tên 44px là đích ngón tay quen thuộc. */
.header-mobile__nav .m-row { display: flex; align-items: center; }
.header-mobile__nav .m-row__link { flex: 1; }
.header-mobile__nav .m-row .m-toggle {
  /* 44×44 để hai vùng bấm cạnh nhau vẫn tách bạch được bằng ngón tay. */
  width: 44px; flex: none; justify-content: center;
  margin-right: -10px;   /* mũi tên thẳng cột với mép phải của các mục khác */
}
.header-mobile__nav .m-toggle .icon { transition: transform var(--dur) var(--ease); }
.header-mobile__nav li.is-open .m-toggle .icon { transform: rotate(180deg); }
/* Accordion: mega-menu desktop gập lại thành danh sách trên mobile */
.header-mobile__sub {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur) var(--ease);
}
.header-mobile__nav li.is-open .header-mobile__sub { grid-template-rows: 1fr; }
/* Chia hai cột bằng GRID, không phải `columns: 2`.
   Bên trong là một dãy PHẲNG h4 + a xen kẽ (index.html), không có thẻ bọc từng
   nhóm. Với multi-column, dòng chảy cắt ngang giữa nhóm: đã đo được "Bộ sưu tập"
   bị xẻ đôi — tiêu đề cùng một mục nằm cuối cột trái, hai mục còn lại trôi lên
   đầu cột phải và mất tiêu đề.
   Grid giải quyết đúng vấn đề đó: cho h4 chiếm trọn hàng (`1 / -1`), các <a>
   sau nó tự xếp đôi vào hai cột của những hàng kế tiếp. Nhóm không bao giờ bị
   tách, mà danh sách ~20 mục vẫn ngắn lại còn một nửa. */
.header-mobile__sub > div {
  overflow: hidden;
  display: grid; grid-template-columns: 1fr 1fr;
  column-gap: 20px; align-content: start;
}
/* --- TIỆM CẬN (Gestalt) ---
   Mắt gom các phần tử GẦN NHAU thành một cụm. Muốn đọc ra "đây là 5 nhóm" thì
   khoảng hở GIỮA hai nhóm phải lớn hơn hẳn khoảng hở TRONG một nhóm.

   Bản trước hỏng đúng chỗ đó: giữa hai nhóm = margin-top 12px + padding link
   10px = 22px, mà trong nhóm hai link cạnh nhau cũng đã 10+10 = 20px. Chênh
   2px — không đủ để mắt tách cụm, nên cả khối đọc thành MỘT danh sách dài.

   Nay: trong nhóm 8px, giữa nhóm 16+2 = 18px → tỷ lệ ~2,2:1, ĐỦ để tách cụm.
   Bản trước đẩy lên 32px (tỷ lệ 2,7:1) thì phân nhóm rất rõ nhưng menu dài ra
   tới mức phải cuộn hai lần mới hết — mà đây là menu điều hướng, không phải bài
   đọc. Sự khác biệt được bù lại bằng đường kẻ mảnh trên mỗi tiêu đề: nguyên tắc
   "vùng chung" gánh phần việc mà khoảng trắng vừa nhường bớt, nên rút gần một
   nửa chiều cao mà mắt vẫn đọc ra 5 cụm. */
.header-mobile__sub :is(h4, p) {
  grid-column: 1 / -1;
  /* 12px chứ không 11px: 11px là dưới ngưỡng đọc thoải mái trên điện thoại, và
     hai nhãn nhóm "Bộ sưu tập" / "Sản phẩm khác" còn là LINK bấm được nên cỡ
     chữ của chúng cũng là cỡ của một mục điều hướng. */
  font-size: 12px; font-weight: var(--fw-ui); letter-spacing: .1em; text-transform: uppercase;
  color: var(--c-muted);
  margin: 16px 0 2px; padding-top: 10px;
  /* NÉT ĐỨT, khác hẳn nét liền của `.header-mobile__nav > li` phân cách các mục
     điều hướng chính. Hai cấp phân cách khác nhau thì phải trông khác nhau: nét
     liền = ranh giới giữa các trang, nét đứt = ranh giới mềm giữa các nhóm bên
     trong cùng một trang. */
  border-top: 1px dashed var(--c-line-strong);
}
/* Nhóm ĐẦU TIÊN không cần ranh giới: phía trên nó đã là hàng "Sản phẩm". */
.header-mobile__sub > div > :is(h4, p):first-child {
  margin-top: 2px; padding-top: 0; border-top: 0;
}
/* Nhãn nhóm thứ HAI cũng bỏ nét đứt. Nhãn đầu tiên là "Gạch ốp lát" — tiêu đề
   của cả nhánh, và "Ứng dụng" ngay dưới là nhóm con ĐẦU TIÊN của nó, không phải
   nhóm ngang cấp. Để nét đứt ở đây thì "Gạch ốp lát" trông như một nhãn đứng
   trơ không có mục nào, rồi bị một đường kẻ cắt khỏi chính danh sách của mình.
   Các nét đứt còn lại (Khu vực · Bộ sưu tập · Nhãn hiệu · Sản phẩm khác) giữ
   nguyên, nên vẫn đọc ra đúng số cụm. */
.header-mobile__sub > div > :is(h4, p):first-child + :is(h4, p) {
  margin-top: 4px; padding-top: 0; border-top: 0;
}
/* 7px → vùng chạm 31px. Bản trước để 4px (=22px) khi rút gọn chiều cao menu —
   gọn thật nhưng dưới ngưỡng WCAG 24px, mà đây là hai cột link sát nhau nên
   bấm nhầm sang mục bên cạnh rất dễ. 31px vẫn giữ menu ngắn hơn hẳn bản 10px
   ban đầu, chỉ dài thêm ~60px so với bản 4px. */
.header-mobile__sub a {
  padding: 7px 0; font-size: var(--fs-sm); font-weight: 400; text-transform: none; color: var(--c-body);
}
/* Tiêu đề nhóm bấm được — bản mobile của quy tắc ở §9 */
/* Nhãn nhóm bấm được: `inline-block` + padding để có vùng chạm thật (14px →
   28px). `inline` như trước thì padding dọc không nới được hộp — link cao đúng
   bằng dòng chữ, không bấm trúng nổi. */
.header-mobile__sub :is(h4, p) a {
  font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
  display: inline-block; padding: 7px 0;
}
.header-mobile__sub :is(h4, p) a:hover { color: var(--c-primary); }
.header-mobile__foot { margin-top: auto; padding: 20px; display: flex; align-items: center; gap: 14px; }
.header-mobile__social { display: flex; gap: 12px; margin-left: auto; color: var(--c-ink); }

/* ===== 12 · FOOTER ===== */
.footer { background: var(--c-dark); color: rgba(255,255,255,.78); padding-top: clamp(40px, 5vw, 60px); }
.footer a { transition: color var(--dur) var(--ease); }
.footer a:hover { color: #fff; }
.footer__cols {
  display: grid; grid-template-columns: 1fr 1fr 1fr 1.5fr; gap: 34px;
  padding-bottom: clamp(32px, 4vw, 46px);
}
/* Ô grid mặc định có min-width:auto = min-content. Ô thứ 4 chứa .footer__form
   (flex: input + nút); WebKit tính min-content của input theo bề rộng nội tại mặc
   định (~220px) nên track không co dưới 311px → tràn ngang 7px ở khổ 320.
   Chromium/Firefox không bị. min-width:0 cho track co đúng trên mọi engine. */
.footer__col { min-width: 0; }
.footer__col :is(h4, p):not(.footer__note) {
  font-size: 12px; font-weight: var(--fw-ui); letter-spacing: .08em; text-transform: uppercase;
  color: #fff; margin-bottom: 16px;
}
.footer__col li + li { margin-top: 9px; }
.footer__col a, .footer__col li { font-size: var(--fs-xs); }

.footer__form { display: flex; height: 46px; border-radius: var(--r-sm); overflow: hidden; background: #fff; }
/* `color` BẮT BUỘC phải khai ở đây. Reset §3 cho `input { color: inherit }`, mà
   .footer đặt color: rgba(255,255,255,.78) → không có dòng này thì chữ người dùng
   gõ vào là chữ TRẮNG trên nền trắng của .footer__form, hoàn toàn không đọc được.
   Placeholder trước giờ vẫn thấy chỉ vì có dòng ::placeholder riêng bên dưới. */
.footer__form input {
  flex: 1; min-width: 0; border: 0; padding: 0 14px;
  font-size: var(--fs-xs); color: var(--c-ink);
}
.footer__form input::placeholder { color: var(--c-muted); }
.footer__form button {
  padding: 0 20px; background: var(--c-primary); color: #fff;
  font-size: var(--fs-xs); font-weight: var(--fw-title);
  transition: background-color var(--dur) var(--ease);
}
.footer__form button:hover { background: var(--c-primary-dark); }
.footer__note { font-size: var(--fs-xs); margin-top: 9px; min-height: 19px; }

.footer__brand { text-align: center; padding-bottom: 26px; }
.footer__brand img { height: 62px; width: auto; margin: 0 auto 14px; }
.footer__brand strong {
  display: block; color: #fff; font-size: clamp(15px, 1.6vw, 18px);
  font-weight: var(--fw-title); letter-spacing: .01em; text-transform: uppercase;
}
.footer__brand span { display: block; font-size: var(--fs-xs); margin-top: 7px; }

.footer__bottom {
  display: flex; align-items: center; justify-content: space-between; gap: 18px; flex-wrap: wrap;
  padding: 18px 0 22px; border-top: 1px solid rgba(255,255,255,.16); font-size: var(--fs-xs);
}
.footer__contact { display: flex; align-items: center; gap: 26px; flex-wrap: wrap; }
.footer__contact a { display: inline-flex; align-items: center; gap: 8px; }

/* ===== 13 · GROUP CONTACT (nút nổi) ===== */
/* Không có trong file thiết kế PA1-R6 nhưng là khối bắt buộc của shell
   theo html-rules.md §2.2 — giữ tối giản để không phá bố cục. */
.group-contact {
  position: fixed; right: 16px; bottom: 16px; z-index: 60;
  display: flex; flex-direction: column; gap: 10px;
  transition: opacity var(--dur) var(--ease), visibility var(--dur);
}
/* --- Nhường chỗ khi đã cuộn tới footer ---
   Dải nút này neo cố định ở góc phải dưới, mà góc đó ở footer lại đúng chỗ nút
   "Đăng ký" của ô nhập email và cụm điện thoại/email — nghĩa là nó che mất hành
   động chính ngay tại nơi người dùng định thao tác. Khác với việc che tạm một
   tấm ảnh lúc lướt qua, đây là chồng lấn CỐ ĐỊNH vì cả hai cùng nằm ở đáy.
   Xuống tới footer thì bản thân footer đã có đủ số điện thoại và email, nút nổi
   không còn việc gì để làm nữa — ẩn đi (main.js: initContactFade). */
.group-contact.is-hidden { opacity: 0; visibility: hidden; pointer-events: none; }
.group-contact a, .group-contact button {
  width: 46px; height: 46px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--c-primary); color: #fff;
  box-shadow: var(--sh-float);
  /* opacity/visibility PHẢI có trong danh sách: .gc-top ẩn/hiện bằng đúng hai
     thuộc tính này, thiếu chúng thì nút "lên đầu trang" bật ra giật cục. */
  transition: transform var(--dur) var(--ease), background-color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease),
              opacity var(--dur) var(--ease), visibility var(--dur);
}
.group-contact a:hover, .group-contact button:hover {
  transform: translateY(-3px);
  box-shadow: 0 10px 26px rgba(0,0,0,.28);
}

/* ---- Gọi chú ý: sóng lan toả + rung chuông ----
   Bản trước chỉ xoay icon vài độ, đặt cạnh nền trang là chìm nghỉm. Nay ghép hai
   lớp: một vòng sóng bung ra khỏi nút (thấy được từ xa, ở ngoại vi thị giác) và
   cú rung mạnh hơn của icon (thấy được khi đã nhìn vào). Cả hai cùng chu kỳ 4s
   và cùng mốc thời gian nên đọc ra MỘT chuyển động, không phải hai thứ rời rạc. */
.group-contact a { position: relative; }

/* Sóng: vòng tròn bung ra rồi tắt. Nằm ở ::after nên không đụng tới transform
   của thẻ <a> (đang dùng cho hover) lẫn của .icon (đang dùng cho cú rung). */
@keyframes tl-ping {
  0%, 60%  { opacity: 0; transform: scale(1); }
  66%      { opacity: .65; transform: scale(1); }
  100%     { opacity: 0; transform: scale(2); }
}
.group-contact a::after {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  border: 2px solid var(--c-primary);
  animation: tl-ping 4s var(--ease) infinite;
  pointer-events: none;
}

/* Rung: animation đặt lên .icon BÊN TRONG chứ không lên thẻ <a> — thẻ <a> đang
   giữ `transform: translateY(-3px)` lúc hover, mà animation luôn thắng transition
   trên cùng một phần tử, gộp chung là hover chết hẳn. */
@keyframes tl-wiggle {
  0%, 60%, 92%, 100% { transform: rotate(0) scale(1); }
  64% { transform: rotate(-15deg) scale(1.14); }
  70% { transform: rotate(13deg)  scale(1.14); }
  76% { transform: rotate(-9deg)  scale(1.08); }
  82% { transform: rotate(6deg)   scale(1.04); }
  88% { transform: rotate(-3deg)  scale(1.02); }
}
/* Chọn `a` nên .gc-top (thẻ <button>) đứng yên — nút lên đầu trang không lắc. */
.group-contact a .icon { animation: tl-wiggle 4s var(--ease) infinite; }
.group-contact a:nth-of-type(2) .icon,
.group-contact a:nth-of-type(2)::after { animation-delay: .45s; }
.group-contact a:nth-of-type(3) .icon,
.group-contact a:nth-of-type(3)::after { animation-delay: .9s; }
/* Rê chuột vào là dừng cả hai — người dùng đã chú ý rồi, không cần gọi nữa. */
.group-contact a:hover .icon,
.group-contact a:hover::after { animation-play-state: paused; }

/* Chạy 6 vòng rồi NGHỈ, thay vì `infinite` (pass "canh lề" 31/07/2026).
   Cụm nút này nổi cố định trên MỌI trang, nên `infinite` nghĩa là có ba vòng
   sóng và ba cú lắc chạy suốt thời gian người dùng đọc trang — nó cạnh tranh
   sự chú ý với chính nội dung mà nó nằm cạnh, và trên laptop dùng pin thì
   compositor không bao giờ được nghỉ. Sáu vòng (~24 giây) đủ để một người mới
   vào trang nhìn thấy cụm nút ít nhất một lần; sau đó nút vẫn ở nguyên đó,
   vẫn bấm được, chỉ là thôi vẫy tay.
   `both` giữ khung cuối (rotate 0, scale 1) nên icon dừng ở đúng dáng nghỉ. */
.group-contact a::after { animation-iteration-count: 6; animation-fill-mode: both; }
.group-contact a .icon  { animation-iteration-count: 6; animation-fill-mode: both; }
.group-contact .gc-top { background: var(--c-dark); opacity: 0; visibility: hidden; }
.group-contact .gc-top.is-visible { opacity: 1; visibility: visible; }

/* ===== 14 · REVEAL ON SCROLL ===== */
/* Không dùng thư viện AOS. IntersectionObserver trong main.js gắn .is-inview.

   `data-aos` RỖNG = trượt lên (mặc định cũ, giữ nguyên cho ~213 attribute sẵn có).
   `data-aos="up|down|left|right|zoom|blur|mask"` = chọn hướng khác.

   Nhịp so le điều khiển bằng biến --aos-i (số nguyên >= 0):
   · main.js tự đặt --aos-i cho con của [data-aos-group] và .grid-2/3/4;
   · `data-aos-delay="1..5"` là cách viết tay cũ, nay chỉ set --aos-i. */
/* Biên độ đã tăng hai lần theo phản hồi: 14px → 30px → 56px + thu nhỏ nhẹ.
   Lý do quãng ngắn không ăn: Lenis cuộn có quán tính nên phần tử vẫn đang trôi
   lên trong lúc transition chạy — hai chuyển động cùng chiều triệt tiêu nhau,
   mắt chỉ thấy nội dung "đã ở đó sẵn".

   Ghép THÊM `scale(.94)` chứ không chỉ kéo dài quãng trượt: trượt là chuyển động
   cùng phương với cuộn nên dễ bị nuốt, còn phóng to thì vuông góc với nó — luôn
   đọc ra được dù người dùng đang cuộn nhanh cỡ nào. */
[data-aos] {
  opacity: 0;
  transform: translateY(var(--reveal-shift)) scale(.94);
  transition: opacity var(--reveal-dur) var(--reveal-ease),
              transform var(--reveal-dur) var(--reveal-ease),
              filter var(--reveal-dur) var(--reveal-ease),
              clip-path var(--reveal-dur) var(--reveal-ease),
              /* `scale` là thuộc tính RIÊNG, không nằm trong `transform` ở trên.
                 Gần như mọi card đều mang [data-aos], mà rule này đứng sau §6
                 nên `transition` của nó ghi đè — thiếu dòng này thì nhịp thu nhỏ
                 khi bấm card (§6) mất transition và giật cụp. Thời lượng lấy
                 --dur-fast chứ không --reveal-dur: phản hồi khi bấm phải tức
                 thì, 0.95s thì cảm giác như trang bị treo. */
              scale var(--dur-fast) var(--ease);
  /* Danh sách delay khớp 1-1 với danh sách property ở trên. Bốn property của
     reveal nhận nhịp so le; `scale` (property thứ 5) nhận 0s — một giá trị đơn
     sẽ áp cho CẢ NĂM, khiến cú bấm card ở cuối lưới phải chờ tới nửa giây mới
     thấy phản hồi. */
  transition-delay: calc(var(--reveal-stagger) * var(--aos-i, 0)),
                    calc(var(--reveal-stagger) * var(--aos-i, 0)),
                    calc(var(--reveal-stagger) * var(--aos-i, 0)),
                    calc(var(--reveal-stagger) * var(--aos-i, 0)),
                    0s;
}

/* Biến thể hướng — chỉ ghi đè trạng thái ĐẦU, trạng thái cuối dùng chung. */
[data-aos="down"]  { transform: translateY(calc(-1 * var(--reveal-shift))) scale(.94); }
[data-aos="left"]  { transform: translateX(calc(-1 * var(--reveal-shift))) scale(.94); }
[data-aos="right"] { transform: translateX(var(--reveal-shift)) scale(.94); }
[data-aos="zoom"]  { transform: scale(.86); }
[data-aos="blur"]  { transform: translateY(var(--reveal-shift)); filter: blur(12px); }
[data-aos="mask"]  { transform: none; clip-path: inset(0 0 100% 0); }

[data-aos].is-inview {
  opacity: 1; transform: none; filter: none;
}
[data-aos="mask"].is-inview { clip-path: inset(0 0 0 0); }

/* Bấm Back về đúng chỗ vừa dừng: main.js gắn .is-restoring lên <body> trong lúc
   cuộn về vị trí cũ, rồi hiện luôn mọi [data-aos] từ đó trở lên. Không tắt
   transition ở cửa sổ đó thì cả màn hình mờ vào một lượt — người dùng quay lại
   một trang MÌNH ĐÃ ĐỌC lại phải xem hiệu ứng chạy lần nữa.
   Đủ specificity để thắng §14d ở trên, không cần !important. */
body.is-restoring [data-aos] { transition: none; }

[data-aos-delay="1"] { --aos-i: 1; }
[data-aos-delay="2"] { --aos-i: 2; }
[data-aos-delay="3"] { --aos-i: 3; }
[data-aos-delay="4"] { --aos-i: 4; }
[data-aos-delay="5"] { --aos-i: 5; }

/* FOOTER KHÔNG CÓ HIỆU ỨNG HIỆN — phản hồi MONA R1 tr.13: "Khi vuốt xuống đến
   cuối footer thì phần chữ bên trên hiện lên bị chậm, bỏ hiệu ứng và để mặc
   định là đc".

   Con số đằng sau cảm giác "chậm": 4 cột footer mang data-aos-delay 0→3, tức
   cột cuối bắt đầu ở 3 × --reveal-stagger (.39s) rồi còn chạy hết --reveal-dur
   (.95s) ⇒ 1.34s. Cộng thêm `rootMargin: '0px 0px -10% 0px'` của initReveal()
   (main.js) bắt footer phải trồi lên qua 10% chiều cao khung nhìn mới kích hoạt
   — trên điện thoại thì lúc đó người dùng đã cuộn kịch đáy và đang chờ.

   Trung hoà bằng CSS thay vì gỡ thuộc tính `data-aos` khỏi 4 cột × 28 file HTML:
   markup header/footer chép cứng từng trang nên sửa hàng loạt là 28 cơ hội lệch
   nhau. JS vẫn gắn `.is-inview` như cũ, vô hại vì rule dưới đã khoá cả bốn
   thuộc tính mà [data-aos] động tới. Áp dụng cho MỌI khổ màn hình (client chốt),
   không riêng điện thoại. */
.footer [data-aos] {
  opacity: 1; transform: none; filter: none; clip-path: none; transition: none;
}

/* ===== 14e · CHUYỂN ĐỘNG KHI KÉO SLIDER =====
   `initSliderMotion()` trong main.js gắn `.is-shifting` lên gốc .swiper trong
   suốt thời gian slide đang chạy, rồi gỡ ra khi dừng.

   Điểm mấu chốt: ĐỨNG YÊN THÌ KHÔNG CÓ GÌ ĐỔI. Card chỉ thu lại lúc đang trôi
   rồi bung về đúng kích thước cũ — bố cục nghỉ giữ nguyên bản thiết kế.
   (Đã thử effect:'coverflow' của Swiper và phải gỡ: nó xoay + đẩy slide vào
   chiều sâu ngay cả lúc đứng yên, tức là đổi bố cục.)

   `:not(.swiper-fade):not(.swiper-3d)` — hai loại đó Swiper tự ghi transform
   thẳng lên slide, thêm scale vào là đá nhau. main.js cũng bỏ qua chúng, khai
   ở cả hai nơi để CSS không phụ thuộc việc JS có chạy hay không. */
.swiper:not(.swiper-fade):not(.swiper-3d) .swiper-slide {
  transition: transform var(--dur) var(--ease);
}
.swiper:not(.swiper-fade):not(.swiper-3d).is-shifting .swiper-slide {
  transform: scale(.94);
}
/* Card ở giữa thu ít hơn để nó vẫn là tâm điểm trong lúc dải card trôi qua. */
.swiper:not(.swiper-fade):not(.swiper-3d).is-shifting .swiper-slide-active {
  transform: scale(.97);
}

@media (prefers-reduced-motion: reduce) {
  .swiper .swiper-slide { transition: none; transform: none !important; }
}

/* ===== 14b · LENIS — CUỘN CÓ QUÁN TÍNH =====
   Lenis ghi scroll position THẬT (window.scrollTo) nên position:sticky và
   position:fixed vẫn chạy bình thường. Class `lenis*` do Lenis tự gắn vào <html>,
   vì vậy mọi rule dưới đây chỉ sống khi Lenis nạp được — hỏng thì site tự lùi
   về cuộn mặc định của trình duyệt. */
html.lenis, html.lenis body { height: auto; }
html.lenis { scroll-behavior: auto; }        /* nhường quyền điều khiển cho Lenis */
.lenis.lenis-stopped { overflow: hidden; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }

/* Header cố định che mất mục tiêu khi nhảy anchor — chừa sẵn khoảng bù. */
:where(section, [id], .sec-head, .page-head) {
  scroll-margin-top: calc(var(--header-h) + 30px);
}

/* ===== 14c · (ĐÃ GỠ) THANH TIẾN ĐỘ ĐỌC =====
   Vạch xanh 2px chạy dưới đáy header theo tỉ lệ cuộn đã được GỠ HẲN theo
   feedback R2 tr.3: "Bỏ thanh chạy màu xanh này đi (cả bản điện thoại nữa)".

   Gỡ cả phần tử trong main.js (initScrollUI) lẫn nhóm rule ở đây, không dùng
   `display:none`: node còn sống thì mỗi frame cuộn vẫn ghi `--p` vào inline
   style cho một thứ không ai nhìn thấy.

   Giữ mục này làm mốc để lần sau không ai "khôi phục" lại nhầm — client đã chốt
   bỏ, không phải bug hiển thị. Nếu sau này khách đổi ý, xem lại lịch sử git:
   bản cũ có HAI TẦNG (máng .scroll-progress overflow:hidden bo theo header,
   ruột vạch ở ::after) để vạch không lòi ra 2 góc bo của viên pill. */

/* ===== 14d · CHUYỂN TRANG =====
   Site tĩnh 24 trang nên mỗi cú click là một lần trắng màn. Fade-in là thuần
   CSS (không phụ thuộc JS) để không bao giờ có nguy cơ trang trắng vĩnh viễn.

   Cố ý CHỈ animate opacity, không animate transform: trình duyệt khôi phục vị trí
   cuộn khi F5, mà transform trên .main sẽ tạo containing block mới và làm
   position:sticky bên trong (.dist__map, .pl-filter) nhảy trong lúc chạy. */
@keyframes tl-page-in  { from { opacity: 0; } to { opacity: 1; } }
@keyframes tl-page-out { to { opacity: 0; } }
/* CỐ Ý không có fill-mode. `both` sẽ giữ .main ở trạng thái `from` (opacity:0)
   TRƯỚC khi animation chạy — môi trường nào không chạy animation là mất trắng
   toàn bộ nội dung trang. Không fill thì opacity giữ giá trị thường (1), hỏng
   animation cũng chỉ mất hiệu ứng chứ không mất nội dung. .main không có
   animation-delay nên bỏ fill cũng không gây nháy. */
.main { animation: tl-page-in var(--dur) var(--ease); }
/* Dùng animation thứ hai chứ không phải transition: .main đang có animation
   fill-mode `both` giữ opacity:1, một khai báo opacity thường sẽ bị nó đè. */
body.is-leaving .main { animation: tl-page-out var(--dur-fast) var(--ease) forwards; }

/* --- View Transitions xuyên tài liệu (pass "canh lề" 31/07/2026) ---
   Trình duyệt giữ nguyên ảnh trang cũ trong lúc tải trang mới rồi hoà tan sang,
   nên hết hẳn khoảng trắng giữa hai trang — thứ mà nhánh JS ở trên chỉ giấu đi
   được chứ không xoá được. Trình duyệt chưa hỗ trợ thì bỏ qua nguyên khối này
   và main.js tự quay về cách cũ (nó kiểm tra `startViewTransition`).

   Header giữ tên riêng để KHÔNG bị hoà tan cùng phần còn lại: nó nằm nguyên vị
   trí ở mọi trang, cho nó mờ đi rồi hiện lại là tự tạo ra một cú nháy không có
   lý do gì. `view-transition-name` phải là DUY NHẤT trên trang. */
@view-transition { navigation: auto; }
.header { view-transition-name: tl-header; }
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: var(--dur);
  animation-timing-function: var(--ease);
}
/* Header không đổi giữa các trang → không animate, tránh nháy. */
::view-transition-old(tl-header),
::view-transition-new(tl-header) { animation: none; mix-blend-mode: normal; }

@media (prefers-reduced-motion: reduce) {
  /* `navigation: auto` không tự tôn trọng reduced-motion, phải tự tắt. */
  @view-transition { navigation: none; }
}

/* ============================================================
   ===== 15 · DESIGN SYSTEM — component dùng lại giữa các trang
   Mọi trang con lấy từ đây. KHÔNG sửa file này khi dựng trang mới:
   style riêng của trang đặt trong assets/css/<ten-trang>.css.
   Xem design-system.html để đối chiếu trực quan.
   ============================================================ */

/* --- 15.1 Khung trang ---------------------------------------
   Header trôi nổi nên trang KHÔNG có banner phải tự chừa khoảng
   trên. Trang có banner tràn viền: thêm class `has-banner` vào <body>. */
.main { padding-top: calc(var(--header-h) + 30px); }
body.has-banner .main { padding-top: 0; }

/* --- 15.2 Breadcrumb (căn giữa, ngăn bằng dấu chấm tròn) ----- */
.breadcrumb {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 10px; padding: 26px 0; font-size: var(--fs-xs); color: var(--c-muted);
}
.breadcrumb li { display: flex; align-items: center; gap: 10px; }
.breadcrumb li + li::before {
  content: ""; width: 4px; height: 4px; border-radius: 50%; background: var(--c-muted);
}
.breadcrumb a:hover { color: var(--c-primary); }
.breadcrumb [aria-current="page"] { color: var(--c-ink); font-weight: var(--fw-ui); }

/* --- 15.3 Banner đầu trang con ------------------------------ */
.page-banner { position: relative; }
.page-banner__img { width: 100%; height: var(--h-page-banner); object-fit: cover; }

/* Zoom chậm (Ken Burns) — DÙNG CHUNG cho banner trang chủ (home.css §1) và banner
   chi tiết sản phẩm (product-detail.css §1). Đặt ở common.css vì từ lúc trang
   chi tiết sản phẩm cũng dùng, nó không còn là của riêng trang chủ nữa; chép
   sang file thứ hai thì hai bản số liệu sẽ trôi khỏi nhau.

   KHÔNG gắn animation theo class slide của Swiper — đã thử và đều lỗi:
   1. Gắn vào `.swiper-slide-active`: Swiper gỡ class ngay khi bắt đầu chuyển,
      animation biến mất và ảnh đang phóng bật ngược về scale(1) trong một frame.
   2. Lấy scale cuối làm trạng thái nghỉ: hết giật ở ảnh đi ra, nhưng ảnh đi VÀO
      vẫn khởi động lại từ scale(1) trong khi ảnh đi ra đang ở 1.07 — hai mức
      phóng khác nhau chồng nhau suốt lúc mờ chồng, mắt vẫn đọc ra là giật cỡ.
   Chạy MỘT animation vô hạn trên mọi ảnh thì mọi slide luôn cùng một mức phóng
   tại mọi thời điểm: không còn chênh lệch nào để giật, không có mốc nào để khởi
   động lại. Đổi slide trở thành thuần tuý mờ chồng.

   `alternate` để phóng vào rồi ra, không bao giờ nhảy về đầu chu kỳ. 16s một
   chiều — chậm hơn nhịp autoplay nên ảnh không "chạy đua" với slide. `linear`
   vì đây là chuyển động nền liên tục; `ease` sẽ lộ rõ điểm dừng ở hai đầu.
   `.swiper` có sẵn overflow:hidden nên ảnh phóng to không tràn ra ngoài.

   CỐ Ý không đặt `will-change: transform`: các ảnh full-screen giữ lớp
   compositor thường trực là quá tốn, mà scale đơn thuần compositor xử lý tốt
   không cần gợi ý.

   Không cần tắt riêng cho `prefers-reduced-motion`: khối `*` ở §16 đã ép
   animation-duration về .01ms và iteration-count về 1 cho mọi thứ. */
@keyframes tl-ken { from { transform: scale(1); } to { transform: scale(1.08); } }
/* Phủ tối dâng từ ĐÁY LÊN chứ không phải gradient chéo từ trái như bản đầu:
   chữ banner luôn neo ở đáy (.page-banner__body) nên phần cần tối là dải dưới.
   Bản chéo làm góc trái trên bị tối vô cớ mà chân chữ vẫn chìm vào ảnh sáng. */
/* HAI lớp, không phải một (phản hồi 31/07/2026: "thêm 1 lớp overlay đen nhẹ ở
   dưới ảnh để cho text nổi lên"). Gradient một mình tắt hẳn về 0 ở mốc 68%, nên
   nửa trên ảnh không có gì che: ở slide sáng như banner sân vườn (mảng tường đá
   be + trời sáng) chân chữ dòng thứ hai của tiêu đề rơi đúng vào vùng đó và
   chìm. Lớp phẳng .16 phủ ĐỀU cả khung dựng một sàn tương phản tối thiểu cho
   mọi ảnh, gradient vẫn lo phần đậm ở dải đáy nơi chữ neo vào.
   Cùng khuôn với .page-banner--center ngay dưới (gradient + lớp phẳng) — biến
   thể đó đã đi trước một bước, đây chỉ là kéo banner thường về cùng cấu trúc. */
.page-banner::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(to top,
      rgba(0,0,0,.62) 0%, rgba(0,0,0,.28) 34%, rgba(0,0,0,0) 68%),
    rgba(0,0,0,.16);
}
.page-banner__body {
  position: absolute; left: 0; right: 0; bottom: var(--banner-base); z-index: 2; color: #fff;
}
.page-banner__eyebrow { font-size: clamp(14px, 1.4vw, 17px); margin-bottom: 6px; }
/* 700 → 600 (phản hồi 31/07/2026: "text tiêu đề những chỗ banner như này giảm
   font-weight 1 chút"). Cùng lý do quang học đã ghi ở .banner__title trang chủ
   (home.css §2): tiêu đề banner nằm TRÊN ẢNH, và chữ trắng trên nền tối luôn
   trông dày hơn chữ đen trên nền sáng ở cùng font-weight — nét sáng loe ra vào
   nền tối. 700 ở 52px đọc ra là "béo" chứ không phải "mạnh", các ô kín trong
   chữ Việt có dấu (ạ, ê, ơ trong "Keo dán gạch chuyên dụng") bết lại.
   Chỉ ở banner. Tiêu đề section trên nền trắng vẫn --fw-title (700), vì ở đó
   không có hiện tượng loe nét và 700 mới đủ tách khỏi văn bản quanh nó. */
.page-banner__title {
  color: #fff; font-size: var(--fs-h1); font-weight: 600;
  letter-spacing: -.02em; line-height: 1.1; margin-bottom: 18px;
}
/* Biến thể căn giữa + phủ đậm hơn — dùng cho trang Giới thiệu */
/* Intro banner trang trong — thuần CSS, chạy ngay khi tải, không cần JS.
   Chỉ tác động lên chữ bên trong .page-banner__body (đã absolute) nên không
   ảnh hưởng layout ảnh hay breadcrumb. */
@keyframes tl-banner-in { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }
.page-banner__eyebrow,
.page-banner__title,
.page-banner__desc,
.page-banner__body .btn {
  animation: tl-banner-in var(--reveal-dur) var(--reveal-ease) both;
}
.page-banner__title       { animation-delay: calc(var(--reveal-stagger) * 1); }
.page-banner__desc        { animation-delay: calc(var(--reveal-stagger) * 2); }
.page-banner__body .btn   { animation-delay: calc(var(--reveal-stagger) * 3); }

/* Biến thể căn giữa: phủ phẳng cho chữ giữa ảnh, CỘNG dải tối dâng từ đáy để
   đồng nhịp với banner thường (thiết kế trang 16). */
.page-banner--center::after {
  /* Phủ tối màu xanh thương hiệu thay vì đen trung tính (phản hồi client
     10/08/2026: banner Giới thiệu, gradient tối dần từ dưới lên dùng xanh
     thương hiệu để chữ nổi rõ hơn). rgba(0,61,38) = #003d26, cùng tông tối
     một bậc của --c-primary (#006540) đã dùng cho --sh-card ở §token — giữ
     nhất quán sắc xanh thay vì đen thuần.

     ĐẬM HƠN + VỚI CAO HƠN (feedback R2 tr.6: "Chỉnh hiệu ứng gradient trên ảnh
     sao cho phần tối rõ hơn để dễ đọc chữ hơn" · "Trang giới thiệu thêm Gradient
     nhẹ nhẹ").

     Vấn đề của bản .68→0 tại 58% KHÔNG phải là chưa đủ đậm ở chân ảnh, mà là
     TẮT QUÁ SỚM: hộp chữ của biến thể này trải từ ~46% chiều cao banner trở
     xuống (tiêu đề + đoạn mô tả 6–8 dòng ở khổ điện thoại), nên 2–3 dòng đầu
     rơi đúng vào quãng gradient đã gần bằng 0 — trên ảnh Giới thiệu thì đó lại
     là mái tôn trắng sáng nhất tấm hình. Đẩy điểm tắt 58% → 88% để cả khối chữ
     nằm trong vùng có phủ, và chèn hai điểm dừng giữa để đoạn dốc trải đều thay
     vì đổ dồn ở chân.

     Lớp phẳng giữ ở .36 — nâng nó là tối đều cả tấm ảnh, đúng thứ "nhẹ nhẹ"
     loại trừ. Việc làm rõ chữ giao cho đoạn dốc, không giao cho lớp phẳng. */
  background:
    linear-gradient(to top,
      rgba(0,61,38,.80) 0%,
      rgba(0,61,38,.56) 34%,
      rgba(0,61,38,.24) 62%,
      rgba(0,61,38,0)   88%),
    rgba(0,61,38,.36);
}
/* Chữ dồn xuống 1/3 dưới chứ không giữa tuyệt đối: đó là nơi dải phủ tối dâng
   lên đủ đậm, nên chữ trắng luôn đọc được (thiết kế trang 16). */
.page-banner--center .page-banner__body {
  top: 0; bottom: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: flex-end; text-align: center;
  /* Biến thể này kéo hộp chữ full-height nên khoảng cách tới đáy ảnh do
     `padding-bottom` giữ, nhưng vẫn phải là ĐÚNG con số của biến thể thường —
     nếu không, hai kiểu banner trang con lại đặt chữ ở hai độ cao khác nhau. */
  padding-bottom: var(--banner-base);
}
/* 62ch → 84ch (phản hồi 30/07/2026: "tăng width cụm text mô tả dưới tiêu đề").
   Ở khổ desktop 62ch chỉ chiếm ~46% bề rộng .container nên đoạn mô tả trang
   Giới thiệu bị bẻ thành 5–6 dòng hẹp, trông như một cột báo lọt thỏm giữa ảnh
   banner rộng. 84ch vẫn dưới ngưỡng ~90ch mà mắt bắt đầu lạc dòng khi xuống hàng. */
.page-banner--center .page-banner__desc { max-width: 84ch; font-size: var(--fs-sm); }
/* Nút lùi/tiếp GOM THÀNH MỘT CỤM Ở GÓC PHẢI DƯỚI (phản hồi client 31/07/2026,
   quay lại đúng file thiết kế trang 7 và 15). Bản 30/07 tách hai nút ra hai mép
   trái–phải; client rà lại bản desktop và yêu cầu về đúng PDF.

   Neo bằng `inset: auto 0 0 auto` chứ không `top/left` cho từng nút: .nav-btn đã
   giữ `transform`/`scale` riêng lúc hover và lúc nhấn, nên mọi cách định vị dùng
   transform ở đây sẽ ghi đè lẫn nhau và làm nút nhảy khi rê chuột.

   Lề phải rộng hơn lề dưới để né dải nút liên hệ nổi (.group-contact) đang neo
   cố định bên phải màn hình — cùng giá trị với .cd-hero__nav ở collection.css.
   `pointer-events` chỉ mở lại trên chính hai nút nên phần ảnh còn lại vẫn kéo
   thả được để lật slide. */
/* Cụm neo `bottom: 0` rồi đẩy lên bằng `padding-bottom` — KHÔNG dùng `bottom`
   để đẩy. Cả hộp phủ hết dải đáy ảnh (nên `pointer-events: none`, chỉ con mới
   `auto`), và giữ đúng một cơ chế thì mọi breakpoint chỉ cần đổi --banner-base,
   không sợ cộng dồn `bottom` với `padding-bottom`. */
.page-banner__nav {
  position: absolute; inset: auto 0 0 auto; z-index: 3;
  display: flex; align-items: center; gap: 14px;
  /* Cận dưới 76px, KHÔNG phải 62px. Dải nút liên hệ nổi (.group-contact,
     position:fixed góc phải dưới) chiếm đúng 62px tính từ mép phải màn hình ở
     mọi khổ — đo trực tiếp, và con số này CỐ ĐỊNH theo px chứ không co theo bề
     rộng. Cận dưới 62px vì thế cho khe đúng 0: ở 768 và 1024px mép phải nút
     trùng khít mép trái dải liên hệ (đo được 706/706 và 962/962). Trước đây
     chưa lộ vì banner còn thấp nên hai khối không giao nhau theo CHIỀU DỌC;
     nâng banner chi tiết sản phẩm lên 88dvh là chúng gặp nhau.
     76 = 62 + 14px khe. 5.4vw chỉ vượt 76 từ ~1408px trở lên, nên thay đổi này
     chỉ chạm tới dải 721–1407px; từ 1440 trở lên giữ nguyên như cũ. */
  padding-right: clamp(76px, 5.4vw, 86px);
  padding-bottom: var(--banner-base);
  pointer-events: none;
}
.page-banner__nav .nav-btn { pointer-events: auto; }
.page-banner__nav .nav-btn { background: transparent; border-color: rgba(255,255,255,.7); color: #fff; }
.page-banner__nav .nav-btn:hover { background: #fff; color: var(--c-ink); border-color: #fff; }

/* --- 15.4 Tiêu đề trang (căn giữa) -------------------------- */
.page-head { text-align: center; padding-bottom: clamp(24px, 3.4vw, 40px); }
.page-head__eyebrow { font-size: var(--fs-body); color: var(--c-body); margin-bottom: 4px; }
.page-head h1 {
  font-size: var(--fs-h1); font-weight: var(--fw-title); letter-spacing: -.02em; line-height: 1.15;
}
.page-head__desc { max-width: 96ch; margin: 16px auto 0; font-size: var(--fs-sm); color: var(--c-body); }
.page-head__meta {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 22px; margin-top: 12px; font-size: var(--fs-xs); color: var(--c-muted);
}
.page-head__meta span { display: inline-flex; align-items: center; gap: 7px; }
.page-head__actions {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 14px; margin-top: 22px;
}

/* Tiêu đề section căn giữa + số lượng nằm phải (vd "100 sản phẩm") */
.sec-title { text-align: center; font-size: var(--fs-h2); font-weight: var(--fw-title); letter-spacing: -.01em; }
.count { display: block; text-align: right; font-size: var(--fs-xs); color: var(--c-muted); margin-bottom: 14px; }

/* --- 15.5 Lưới ---------------------------------------------- */
.grid-2, .grid-3, .grid-4 { display: grid; gap: clamp(18px, 2vw, 26px); }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }

/* Bố cục "sidebar lọc + lưới sản phẩm" của các trang danh sách */
.with-filter { display: grid; grid-template-columns: 232px 1fr; gap: clamp(24px, 3.2vw, 46px); align-items: start; }

/* --- 15.6 Card ---------------------------------------------- */
/* Khối ảnh của MỌI loại card nhấc lên + đổ bóng khi rê chuột. Cố ý đặt transform
   ở khối ảnh chứ không ở thẻ `img` bên trong: `img` đang giữ `scale()` riêng (biến
   --dur-media), hai transform trên hai phần tử khác nhau thì chồng lên nhau
   thay vì đá nhau — được cả nhấc lẫn zoom. */
.tile-card__media,
.collection-card__media,
.news-card__media,
.project-card__media,
.plain-card__media {
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.tile-card:hover .tile-card__media,
.collection-card:hover .collection-card__media,
.news-card:hover .news-card__media,
.project-card:hover .project-card__media,
.plain-card:hover .plain-card__media {
  transform: translateY(-5px);
  /* `var(--sh-card)` chứ không phải chép lại ba con số: bản cũ hard-code đúng
     giá trị của token nên sửa token KHÔNG chạm tới card nào — đây là chỗ sắc
     bóng lệch khỏi hệ màu mà không ai thấy. */
  box-shadow: var(--sh-card);
}

/* Card gạch: ảnh vuông + mã + nhãn hiệu. Dùng ở trang chủ, danh sách
   sản phẩm, sản phẩm liên quan, kết quả tìm kiếm, sản phẩm trong dự án.

   GÓC NHỌN, KHÔNG BO (phản hồi MONA R1 tr.7: "Các khung hiển thị hình sản phẩm
   gạch đổi sang khung vuông góc nhọn, k bo tròn ạ"). Góc nhọn còn đúng về mặt
   sản phẩm: viên gạch thật là hình vuông sắc cạnh, bo 12px làm ảnh chụp bề mặt
   gạch đọc ra như một tấm thẻ ứng dụng.

   Đặt `0` tại chỗ chứ KHÔNG đổi token `--r-sm` (12px vẫn đúng cho card dự án,
   card keo dán gạch, .modal__img…). Ba nơi phải sửa cùng nhau vì cùng là "khung
   ảnh gạch": .tile-card__media (lưới), .gallery__main + .gallery__thumb (trang
   chi tiết) — xem §15.10. `overflow: hidden` GIỮ NGUYÊN: nó phục vụ hover
   scale(1.05) và lớp .is-scene, không liên quan bo góc. */
.tile-card { display: block; }
.tile-card__media {
  display: block; position: relative; aspect-ratio: 1; border-radius: 0;
  overflow: hidden; background: var(--c-grey-light); margin-bottom: 14px;
}
.tile-card__media.is-placeholder { background: var(--c-grey); }
.tile-card__media img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--dur-media) var(--ease); }
.tile-card:hover .tile-card__media img { transform: scale(1.05); }
/* Ảnh phối cảnh hiện khi rê chuột (ghi chú đỏ trang 5 file thiết kế) */
.tile-card__media .is-scene {
  position: absolute; inset: 0; opacity: 0; transition: opacity var(--dur) var(--ease);
}
.tile-card:hover .tile-card__media .is-scene { opacity: 1; }
.tile-card__code {
  display: block; font-size: var(--fs-card); font-weight: var(--fw-title); color: var(--c-ink);
  transition: color var(--dur) var(--ease);
}
.tile-card:hover .tile-card__code { color: var(--c-primary); }
.tile-card__brand { display: block; font-size: var(--fs-xs); color: var(--c-muted); margin-top: 3px; }

/* Card bộ sưu tập: ảnh ngang lớn + tên + nhãn hiệu */
.collection-card { display: block; }
.collection-card__media {
  display: block; aspect-ratio: 16 / 11; border-radius: var(--r-sm);
  overflow: hidden; background: var(--c-grey-light); margin-bottom: 14px;
}
.collection-card__media img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--dur-media) var(--ease); }
.collection-card:hover .collection-card__media img { transform: scale(1.04); }
.collection-card__name { display: block; font-size: var(--fs-card); font-weight: var(--fw-title); color: var(--c-ink); }
.collection-card__brand { display: block; font-size: var(--fs-xs); color: var(--c-muted); margin-top: 3px; }

/* Card tin tức: ảnh + thẻ chuyên mục + tiêu đề + ngày */
.news-card__media {
  display: block; aspect-ratio: 4 / 3; border-radius: var(--r-md);
  overflow: hidden; background: var(--c-grey-light);
}
.news-card__media img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--dur-media) var(--ease); }
.news-card:hover .news-card__media img { transform: scale(1.04); }
.news-card__tag {
  display: inline-flex; align-items: center; height: 32px; padding: 0 16px;
  margin: 16px 0 10px; border: var(--bw) solid var(--c-line-strong); border-radius: var(--r-pill);
  font-size: var(--fs-xs); color: var(--c-body);
}
.news-card h3 { font-size: clamp(20px, 2.1vw, 24px); font-weight: var(--fw-title); line-height: 1.4; }
.news-card h3 a { transition: color var(--dur) var(--ease); }
.news-card h3 a:hover { color: var(--c-primary); }
.news-card time { display: block; margin-top: 8px; font-size: var(--fs-xs); color: var(--c-muted); }

/* Card dự án: ảnh dọc + tên + tỉnh thành */
.project-card { display: block; }
.project-card__media {
  display: block; aspect-ratio: 3 / 4; border-radius: var(--r-sm);
  overflow: hidden; background: var(--c-grey); margin-bottom: 14px;
}
.project-card__media img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--dur-media) var(--ease); }
.project-card:hover .project-card__media img { transform: scale(1.05); }
.project-card__name {
  display: block; font-size: var(--fs-card); font-weight: var(--fw-title); color: var(--c-ink);
  transition: color var(--dur) var(--ease);
}
.project-card:hover .project-card__name { color: var(--c-primary); }
.project-card__place { display: block; font-size: var(--fs-xs); color: var(--c-muted); margin-top: 3px; }

/* Card E-book: bìa catalog dọc + tên (trang Thư viện, thiết kế trang 22).
   Bìa cao hơn 3/4 một chút (≈0.73) và bóng ĐỔ LỆCH phải-dưới thay vì toả đều —
   đó là thứ tạo cảm giác quyển sách dựng đứng chứ không phải ảnh dán phẳng.
   Bo góc nhỏ hơn token chung: bìa sách thật gần như vuông góc. */
.ebook-card { display: block; text-align: center; }
.ebook-card__media {
  display: block; aspect-ratio: .73; border-radius: 4px; overflow: hidden;
  background: var(--c-grey-light); margin-bottom: 14px;
  box-shadow: var(--sh-book);
}
.ebook-card__media img { width: 100%; height: 100%; object-fit: cover; }
.ebook-card__name { font-size: 17px; font-weight: var(--fw-title); color: var(--c-ink); }

/* Card tuyển dụng: nền xám, thông tin trái + nút phải */
.job-card {
  display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap;
  background: var(--c-grey-light); border-radius: var(--r-md);
  padding: clamp(18px, 2.2vw, 26px) clamp(20px, 2.6vw, 30px);
}
.job-card h3 { font-size: clamp(22px, 2.4vw, 28px); font-weight: var(--fw-title); margin-bottom: 8px; }
.job-card__meta { display: flex; flex-wrap: wrap; gap: 20px; font-size: var(--fs-sm); color: var(--c-body); }
.job-card__meta span { display: inline-flex; align-items: center; gap: 7px; }
/* Nút "Xem chi tiết" nằm TRÊN nền xám của chính card → dùng công thức nút-trên-
   dải-xám, giống .m-project (home.css §5), .p-gallery__nav và .a-gallery. Phản
   hồi MONA R1 tr.9 khoanh đỏ đúng nút này cùng lượt với dải Dự án.
   Phải khai đủ ba thuộc tính ở CẢ HAI trạng thái: `.job-card .btn-outline` và
   `.btn-outline:hover` cùng độ đặc hiệu (0,2,0) mà rule này đứng sau trong file
   → nó thắng luôn cả lúc hover. */
.job-card .btn-outline {
  background: transparent; border-color: rgba(0, 0, 0, .45); color: var(--c-ink);
}
.job-card .btn-outline:hover {
  background: var(--c-primary); border-color: var(--c-primary); color: #fff;
}

/* Card đơn giản có ảnh + tên (Sản phẩm khác, danh mục) */
.plain-card__media {
  display: block; aspect-ratio: 16 / 10; border-radius: var(--r-sm);
  overflow: hidden; background: var(--c-grey-light); margin-bottom: 14px;
}
.plain-card__media img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--dur-media) var(--ease); }
.plain-card:hover .plain-card__media img { transform: scale(1.04); }
.plain-card__name { font-size: var(--fs-card); font-weight: var(--fw-title); color: var(--c-ink); transition: color var(--dur) var(--ease); }
.plain-card:hover .plain-card__name { color: var(--c-primary); }

/* --- 15.7 Sidebar bộ lọc ------------------------------------ */
.filter__head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 16px;
}
.filter__head h2 { font-size: 22px; font-weight: var(--fw-title); }
/* `inline-flex` + `align-items: center` KHÔNG phải để xếp hàng — nút này chỉ
   chứa đúng một <i>. Nó để canh giữa chính cái icon đó theo chiều DỌC.
   Mặc định <button> là khối văn bản, nên <i> (inline-block, 18px) đứng trên
   ĐƯỜNG CHÂN CHỮ của dòng cao 25.6px chứ không giữa dòng — đo được icon lệch lên
   3.8px so với chữ "Bộ lọc" và số "36 sản phẩm" hai bên. Ba cụm đều đã được
   .filter__head canh giữa đúng, chỉ phần ruột của cụm này trôi lên (phản hồi
   30/07/2026 "canh giữa 3 cụm này"). Flex bỏ hẳn đường chân chữ khỏi cuộc chơi. */
.filter__reset {
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--c-muted); transition: color var(--dur) var(--ease);
}
.filter__reset:hover { color: var(--c-primary); }
/* `align-items: start` bắt buộc: ở khổ hẹp .filter__fields xuống lưới nhiều cột
   (§16), mà mặc định của grid là `stretch` — mở một nhóm ra là CẢ HÀNG cao lên
   theo, ba viên thuốc bên cạnh bị kéo dài thành ba hộp rỗng. */
.filter__fields { display: grid; gap: 10px; align-items: start; }

/* --- Nhóm lọc = ACCORDION, KHÔNG phải dropdown ---------------
   Trang 5 file thiết kế vẽ nhóm đang ĐÓNG là viên thuốc có mũi tên; trang 13 vẽ
   nhóm đang MỞ là khung bo góc ôm trọn danh sách tick. Tức là danh sách nằm
   TRONG dòng chảy và đẩy các nhóm bên dưới xuống — khác hẳn .re-select__list
   (§10) vốn là bảng nổi che lên lưới sản phẩm.

   Khác biệt này không chỉ là hình thức. Sidebar có 7 tiêu chí và người dùng
   thường bật 2-3 cái một lúc; bảng nổi chỉ cho mở MỘT ô và che mất kết quả nên
   phải đóng-mở liên tục để đối chiếu. Accordion cho mở đồng thời và luôn nhìn
   thấy mình đã tick những gì. Đây cũng là lý do chuyển từ select một-giá-trị
   sang checkbox nhiều-giá-trị: catalog.js đã hiểu OR trong cùng một tiêu chí.

   Bo --r-sm ở CẢ hai trạng thái. Bản trước dùng viên thuốc khi đóng rồi rút về
   --r-sm khi mở: bảy nhóm đóng thành bảy viên thuốc xếp dọc, đọc ra như một cột
   nút bấm chứ không phải một bộ lọc, và khung tự đổi hình mỗi lần gập làm mắt
   phải bám theo cạnh thay vì bám theo nội dung vừa xổ ra. */
.filter__group {
  background: #fff;
  border: var(--bw) solid var(--c-line-strong); border-radius: var(--r-sm);
  transition: border-color var(--dur) var(--ease);
}
/* Trang Thiết bị vệ sinh xếp nhóm thẳng trong <aside class="filter">, không có
   .filter__fields bọc ngoài — margin lo khoảng cách cho trường hợp đó. Trong
   .filter__fields thì `gap` đã lo rồi, cộng thêm margin là thành 20px. */
.filter__group + .filter__group { margin-top: 10px; }
.filter__fields > .filter__group + .filter__group { margin-top: 0; }

.filter__toggle {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; height: 44px; padding: 0 18px;
  font-size: var(--fs-sm); font-weight: 400; color: var(--c-muted);
  transition: color var(--dur) var(--ease);
}
.filter__toggle .icon {
  width: 12px; height: 12px; flex: none;
  transition: transform var(--dur) var(--ease);
}
.filter__group.is-open .filter__toggle .icon { transform: rotate(180deg); }
.filter__toggle:hover, .filter__toggle:focus-visible { color: var(--c-primary); }

/* Đóng/mở bằng grid 0fr → 1fr chứ không phải max-height: max-height buộc phải
   ĐOÁN một con số lớn hơn nội dung — đoán thiếu thì nhóm "Khu vực" 5 dòng bị
   cắt mất dòng cuối, đoán thừa thì lúc đóng danh sách đứng yên một nhịp rồi
   mới trôi. 0fr→1fr đo đúng chiều cao thật, không cần con số nào. */
.filter__body { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--dur) var(--ease); }
.filter__group.is-open .filter__body { grid-template-rows: 1fr; }
/* visibility (chứ không chỉ overflow) để nhóm đang đóng ra khỏi cả thứ tự Tab
   lẫn cây trợ năng — overflow:hidden vẫn cho phép Tab lọt vào ô tick vô hình.
   Trễ đúng --dur khi đóng để chữ không biến mất trước lúc khung thu xong. */
.filter__list {
  overflow: hidden; visibility: hidden;
  transition: visibility 0s linear var(--dur);
}
.filter__group.is-open .filter__list { visibility: visible; transition-delay: 0s; }

.check {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 18px; font-size: var(--fs-xs); color: var(--c-body); cursor: pointer;
  transition: color var(--dur) var(--ease);
}
.check:last-child { padding-bottom: 14px; }
/* Ô tick vẽ tay: hộp tick mặc định của trình duyệt là hình vuông góc nhọn, đặt
   cạnh cả rừng viên thuốc bo tròn của trang này thì lạc hẳn. accent-color chỉ
   đổi được MÀU chứ không đổi được hình. */
.check input {
  appearance: none; -webkit-appearance: none;
  position: relative; flex: none; width: 16px; height: 16px;
  border: var(--bw) solid var(--c-line-strong); border-radius: 5px; background: #fff;
  cursor: pointer;
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.check input:checked { background: var(--c-primary); border-color: var(--c-primary); }
/* Dấu tick VẼ BẰNG CSS, cùng lý do đã ghi ở §10: không subset Inter nào chứa
   U+2713 nên ký tự "✓" rơi về font hệ thống và lệch nét. */
.check input:checked::after {
  content: ''; position: absolute; left: 4px; top: 1px;
  width: 4px; height: 8px;
  border: solid #fff; border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.check input:focus-visible { outline: 2px solid var(--c-primary); outline-offset: 2px; }

/* Trạng thái ĐÃ CHỌN đọc thẳng từ :checked — không chờ JS gắn class, nên đúng
   ngay cả khi bộ lọc được áp từ query string lúc mới nạp trang. */
.filter__group:has(input:checked) { border-color: var(--c-primary); }
.filter__group:has(input:checked) .filter__toggle { color: var(--c-primary); font-weight: var(--fw-ui); }
.check:has(input:checked) { color: var(--c-ink); font-weight: var(--fw-ui); }

/* --- 15.8 Phân trang ---------------------------------------- */
.pagination {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 10px;
  margin-top: clamp(30px, 4vw, 48px);
}
.pagination a, .pagination span {
  width: 38px; height: 38px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--fs-xs); font-weight: var(--fw-medium); color: var(--c-body);
  border: var(--bw) solid transparent;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.pagination a:hover { border-color: var(--c-line-strong); color: var(--c-ink); }
.pagination [aria-current="page"] { border-color: var(--c-primary); color: var(--c-primary); font-weight: var(--fw-title); }
.pagination .is-gap { border: 0; }

/* --- 15.9 Form ---------------------------------------------- */
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(18px, 2.4vw, 30px); }
.field { display: block; }
.field > label { display: block; font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--c-ink); margin-bottom: 8px; }
.field .req { color: var(--c-primary); }
.field input, .field textarea, .field select {
  width: 100%; border: var(--bw) solid var(--c-line-strong); border-radius: var(--r-field);
  background: #fff; padding: 12px 14px; font-size: var(--fs-sm);
  transition: border-color var(--dur) var(--ease);
}
.field input { height: 46px; padding-block: 0; }
.field textarea { min-height: 150px; resize: vertical; }
/* Đổi màu viền khi con trỏ vào ô — dùng cho CẢ chuột lẫn bàn phím, nên bám
   `:focus`. Trước pass "canh lề" dòng này còn kèm `outline: none`, và vì độ đặc
   hiệu (0,2,1) cao hơn `:focus-visible` toàn cục ở §3 (0,1,0) nên nó XOÁ luôn
   vòng focus của MỌI ô nhập trên site — người dùng bàn phím chỉ còn lại một
   viền 1.5px đổi màu, tức là báo hiệu CHỈ BẰNG MÀU (WCAG 1.4.1) và gần như
   không thấy ở form liên hệ / ứng tuyển. Đã bỏ `outline: none`; vòng focus toàn
   cục quay lại đúng chỗ, viền đổi màu giờ là tín hiệu thứ hai chứ không phải
   tín hiệu duy nhất. */
.field input:focus, .field textarea:focus { border-color: var(--c-primary); }
/* Viền ô nhập là ranh giới DUY NHẤT nhận ra ô — khác nút bấm vốn còn chữ bên
   trong để nhận diện — nên nó phải đạt 3:1 của WCAG 1.4.11. --c-line-strong
   (#c4c4c4) chỉ được 1.74:1. Giữ --c-line-strong cho viền nút (đúng bản client
   đã chốt), nâng riêng viền field lên #767676 = 4.54:1. */
.field input, .field textarea, .field select { border-color: #767676; }
.field__err { display: none; margin-top: 6px; font-size: var(--fs-xs); color: #c0392b; }
.field.is-invalid .field__err { display: block; }
.field.is-invalid input, .field.is-invalid textarea { border-color: #c0392b; }

/* Ô tải CV (trang Tuyển dụng) */
/* Ô tải file.

   Bản đầu chỉ là một mảng xám phẳng có dòng chữ "Tải lên CV" ở giữa: không viền,
   không icon, không nói được nhận định dạng gì — nhìn ra là chỗ CHỜ ẢNH chứ
   không phải chỗ BẤM ĐƯỢC (client góp ý 30/07/2026).

   Ba thứ làm nó đọc ra là "ô nhập": viền ĐỨT (quy ước chung cho vùng thả file),
   mũi tên hướng lên, và dòng gợi ý định dạng ngay bên dưới. Vẫn giữ nền
   --c-grey-light của design system, chỉ thêm viền chứ không đổi tông. */
.field--file { position: relative; }
.field--file .field__drop {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; padding: 24px 20px;
  min-height: 190px; border-radius: var(--r-field);
  background: var(--c-grey-light);
  /* Viền đứt phải ĐẬM HƠN --c-line-strong (#c4c4c4): nền ở đây là --c-grey-light
     (#eaeaea) chứ không phải trắng, hai màu sát nhau nên viền tan vào nền và ô
     lại trông như mảng xám phẳng — đúng thứ vừa đi sửa. */
  border: var(--bw) dashed #a5a5a5;
  color: var(--c-body); cursor: pointer; text-align: center;
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease),
              color var(--dur) var(--ease);
}
/* Mũi tên hướng lên = download.svg lật 180°, không phải thêm file icon mới */
.field--file .field__drop::before {
  content: ""; width: 34px; height: 34px; margin-bottom: 4px;
  background-color: currentColor; opacity: .62;
  -webkit-mask: url(../icons/download.svg) no-repeat center / contain;
          mask: url(../icons/download.svg) no-repeat center / contain;
  transform: rotate(180deg);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.field__drop-title { font-size: var(--fs-sm); font-weight: var(--fw-medium); }
.field__drop-hint  { font-size: var(--fs-xs); color: var(--c-muted); }

.field--file .field__drop:hover,
.field--file .field__drop:focus-visible {
  background: #e4e4e4; border-color: var(--c-primary); color: var(--c-ink);
}
.field--file .field__drop:hover::before { opacity: .8; transform: rotate(180deg) translateY(2px); }

/* Đang kéo file qua: đổi hẳn sang tông xanh để người dùng biết thả được ở đây */
.field--file .field__drop.is-dragover {
  background: color-mix(in srgb, var(--c-primary) 8%, #fff);
  border-color: var(--c-primary); color: var(--c-primary);
}
.field--file .field__drop.is-dragover::before { opacity: 1; transform: rotate(180deg) translateY(3px); }

/* ĐÃ CHỌN: viền liền + nền ngả xanh — khác hẳn trạng thái rỗng, không phải
   chỉ đổi màu chữ như bản cũ. */
.field--file .field__drop.is-filled {
  border-style: solid; border-color: var(--c-primary);
  background: color-mix(in srgb, var(--c-primary) 6%, #fff);
  color: var(--c-ink);
}
/* Đã chọn xong thì icon đổi thành DẤU TICK vẽ bằng CSS, không giữ mũi tên
   download: mũi tên xuống đứng cạnh tên file đọc ra là "bấm để tải file này
   về" — ngược hẳn việc vừa xảy ra. Cùng cách vẽ với dấu tick ở .re-select__list
   nên không phải thêm file icon nào. */
.field--file .field__drop.is-filled::before {
  -webkit-mask: none; mask: none;
  background: none;
  width: 13px; height: 24px; margin: 0 4px 12px 0;
  border: solid var(--c-primary); border-width: 0 3px 3px 0;
  transform: rotate(45deg); opacity: 1;
}
.field--file .field__drop.is-filled .field__drop-title { word-break: break-word; }

/* Nút gỡ file — ĐỨNG NGOÀI <label>, nếu nằm trong thì bấm nó cũng mở hộp thoại
   chọn file (label chuyển cú bấm sang input) và không bao giờ xoá được. */
.field__drop-clear {
  position: absolute; top: 44px; right: 12px; z-index: 2;
  width: 32px; height: 32px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: #fff; border: var(--bw) solid var(--c-line-strong); color: var(--c-body);
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.field__drop-clear:hover { border-color: #c0392b; color: #c0392b; }
.field__drop-clear .icon { width: 13px; height: 13px; }

.field.is-invalid .field__drop { border-color: #c0392b; }
/* input file thật bị ẩn nhưng KHÔNG dùng display:none — phần tử display:none
   không nhận được focus, mà validate lỗi thì JS gọi .focus() vào chính nó. */
.field--file input[type="file"] {
  position: absolute; width: 1px; height: 1px; opacity: 0;
  left: 20px; bottom: 20px; pointer-events: none;
}

/* --- 15.10 Thư viện ảnh sản phẩm (thumb dọc + ảnh chính) ----- */
.gallery { display: grid; grid-template-columns: 92px 1fr; gap: 14px; align-items: start; }
.gallery__thumbs { display: grid; gap: 10px; }
/* <button>, không phải <div>: trước pass "canh lề" đây là <div> chỉ có
   addEventListener('click') nên bàn phím KHÔNG tới được — cả dải chọn ảnh sản
   phẩm vô hình với người dùng không dùng chuột (WCAG 2.1.1). Mẫu đúng đã có sẵn
   ở thu-vien.html (.l-gallery__item là <button type="button">), đây chỉ là chép
   lại. `width: 100%` vì <button> không tự kéo giãn trong lưới như <div>. */
/* `border-radius: 0` — góc nhọn theo phản hồi MONA R1 tr.7, đồng bộ với
   .tile-card__media và .gallery__main. Lưu ý bán kính cũ ở đây là 8px CỨNG (không
   phải --r-sm), nên đổi token sẽ không chạm tới nó: phải sửa thẳng dòng dưới. */
.gallery__thumb {
  width: 100%; padding: 0;
  aspect-ratio: 1; border-radius: 0; overflow: hidden;
  background: var(--c-grey-light); border: 1px solid transparent;
  /* `scale` phải nằm ở đây chứ không chỉ ở rule chung §6: rule này đứng SAU
     trong file nên `transition` của nó ghi đè, thiếu `scale` là mất nhịp bấm. */
  transition: border-color var(--dur) var(--ease), scale var(--dur-fast) var(--ease);
}
.gallery__thumb img { width: 100%; height: 100%; object-fit: cover; }
.gallery__thumb.is-active {
  border-color: var(--c-primary);
  /* Viền 1px xanh nằm trên ảnh nhiều chi tiết thì gần như không đọc ra được
     thumb nào đang xem. Quầng ngoài dày thêm mà KHÔNG đổi border-width — đổi
     border-width là cả dải 4 thumb dịch 1px, nhìn ra ngay ở cột hẹp 92px. */
  box-shadow: 0 0 0 2px var(--c-primary);
}
/* VUÔNG là mặc định, không phải ngoại lệ (phản hồi 31/07/2026: "sao cho ảnh sản
   phẩm là hình vuông"). Bản cũ để 4/3 ở đây rồi CẢ HAI trang chi tiết còn lại
   tự ghi đè về `aspect-ratio: 1`, mỗi trang kèm một ghi chú "thiết kế vẽ ảnh
   chính gần vuông" (product-detail.css §, sanitary.css §3). Tức 4/3 chưa bao giờ
   là ý định thiết kế — nó chỉ sống sót ở trang Keo dán gạch vì trang này quên
   ghi đè, nên đúng một trong ba trang chi tiết lệch khỏi hai trang kia.
   Đảo mặc định thay vì thêm ghi đè thứ ba: hai ghi đè kia nay xoá được, và ảnh
   sản phẩm ở trang chi tiết khớp luôn với .tile-card__media (cũng vuông) — cùng
   một sản phẩm không đổi tỉ lệ khung khi bấm từ lưới vào trang chi tiết. */
.gallery__main {
  aspect-ratio: 1; border-radius: 0; overflow: hidden;
  background: var(--c-grey-light); cursor: zoom-in;
}
.gallery__main img { width: 100%; height: 100%; object-fit: cover; }

/* --- 15.11 Thẻ chuyên mục & thông số ------------------------ */
.tag {
  display: inline-flex; align-items: center; height: 30px; padding: 0 16px;
  border: var(--bw) solid var(--c-line-strong); border-radius: var(--r-pill);
  font-size: var(--fs-xs); color: var(--c-body);
}
.spec { display: grid; gap: 6px; font-size: var(--fs-body); color: var(--c-body); }

/* Khối số liệu (trang Giới thiệu) */
.stat { text-align: center; }
.stat b { display: block; font-size: clamp(30px, 3.8vw, 48px); font-weight: var(--fw-title); color: var(--c-primary); }
.stat span { font-size: var(--fs-sm); color: var(--c-muted); }

/* --- 15.12 Modal xem ảnh phóng to ---------------------------
   Phản hồi MONA R1 tr.2 + tr.3: "Các pop up ảnh (trang sản phẩm, trang thư
   viện,…) làm giống hệt của web Tasa với các chức năng như zoom, chạy tự động,
   bấm mũi tên để xem ảnh bên cạnh,…".

   Bản trước chỉ là MỘT ảnh + nút đóng. Nay là trình xem cả dải ảnh: thanh công
   cụ trên (bộ đếm · thu/phóng · chạy tự động · toàn màn hình · đóng), mũi tên
   hai bên, dải thumbnail dưới. Toàn bộ markup do initLightbox() trong main.js
   dựng MỘT LẦN rồi dùng lại — trang không khai gì thêm.

   Bố cục là grid ba hàng để dải thumbnail và thanh công cụ luôn chiếm chỗ thật,
   ảnh nhận đúng phần còn lại. Dùng flex + absolute thì ảnh cao 86vh sẽ chui
   xuống dưới dải thumbnail ở màn thấp. */
.modal {
  position: fixed; inset: 0; z-index: 120;
  display: grid; grid-template-rows: auto minmax(0, 1fr) auto;
  background: rgba(0,0,0,.92);
  opacity: 0; visibility: hidden; transition: opacity var(--dur) var(--ease), visibility var(--dur);
}
.modal.is-open { opacity: 1; visibility: visible; }

/* ---- Thanh công cụ ---- */
.modal__bar {
  display: flex; align-items: center; gap: 6px;
  padding: 14px 18px; color: #fff;
}
.modal__count {
  margin-right: auto;
  font-size: var(--fs-sm); font-variant-numeric: tabular-nums; color: rgba(255,255,255,.75);
}
.modal__tool {
  width: 40px; height: 40px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: transparent; color: #fff;
  transition: background-color var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.modal__tool:hover { background: rgba(255,255,255,.16); }
/* Nút thu nhỏ khi đã ở mức 1× (và phóng to khi đã kịch trần) không còn tác dụng
   — báo bằng mắt thay vì để người dùng bấm vào chỗ chết. */
.modal__tool[disabled] { opacity: .3; pointer-events: none; }

/* ---- Sân khấu ảnh ---- */
.modal__stage {
  position: relative; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  padding-inline: 68px;
  touch-action: none;          /* tự xử lý vuốt/kéo bằng pointer event */
}
.modal__img {
  max-width: 100%; max-height: 100%; object-fit: contain;
  /* Đổi transform khi zoom/kéo. `transition` chỉ bật lúc bấm nút hoặc nháy đúp
     (.is-eased); lúc kéo tay phải TẮT, nếu không ảnh chạy trễ sau ngón tay. */
  transform: translate(0, 0) scale(1);
  cursor: zoom-in;
  /* Đi CÙNG CẶP với draggable="false" trên thẻ <img> (main.js §lightbox), không
     thay thế nhau: thuộc tính HTML lo Chrome/Firefox, còn Safari chỉ chịu nghe
     -webkit-user-drag. Thiếu một trong hai thì kéo ảnh lúc zoom lại biến thành
     thao tác kéo-thả ảnh của trình duyệt (feedback R2 tr.2).
     user-select: none để cú rê không bôi đen phần tử quanh ảnh. */
  -webkit-user-drag: none;
  user-select: none; -webkit-user-select: none;
}
.modal__img.is-eased { transition: transform var(--dur) var(--ease); }
.modal__stage.is-zoomed .modal__img { cursor: grab; }
.modal__stage.is-zoomed.is-dragging .modal__img { cursor: grabbing; }

/* ---- Mũi tên trước / sau ---- */
.modal__nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 52px; height: 52px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,.10); color: #fff;
  transition: background-color var(--dur) var(--ease);
}
.modal__nav:hover { background: rgba(255,255,255,.26); }
.modal__prev { left: 10px; }
.modal__next { right: 10px; }
.modal__prev .icon { transform: rotate(180deg); }
/* Dải chỉ có một ảnh thì không có "ảnh bên cạnh" để đi tới. */
.modal.is-single .modal__nav,
.modal.is-single .modal__thumbs { display: none; }

/* ---- Dải thumbnail ---- */
.modal__thumbs {
  display: flex; justify-content: center; gap: 8px;
  padding: 14px 18px 18px;
  overflow-x: auto; scrollbar-width: none;
}
.modal__thumbs::-webkit-scrollbar { display: none; }
.modal__thumb {
  flex: none; width: 62px; height: 46px; padding: 0;
  border: 2px solid transparent; overflow: hidden;
  opacity: .5; transition: opacity var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.modal__thumb img { width: 100%; height: 100%; object-fit: cover; }
.modal__thumb:hover { opacity: .85; }
.modal__thumb[aria-current="true"] { opacity: 1; border-color: #fff; }

@media (max-width: 720px) {
  /* Mũi tên tròn to chiếm gần một phần ba bề ngang máy 375px và đè lên chính
     tấm ảnh. Trên cảm ứng thì VUỐT mới là thao tác chính (stage đã bắt pointer
     event), nên ẩn hẳn mũi tên và trả chỗ đó cho ảnh. */
  .modal__nav { display: none; }
  .modal__stage { padding-inline: 8px; }
  .modal__bar { padding: 10px 12px; gap: 2px; }
  .modal__tool { width: 38px; height: 38px; }
  .modal__thumbs { justify-content: flex-start; padding: 10px 12px 14px; }
  .modal__thumb { width: 52px; height: 40px; }
}

/* --- 15.12b Hộp thoại báo kết quả gửi form -------------------
   Trước đây kết quả gửi form chỉ là một dòng chữ nhỏ nằm dưới nút Gửi. Ở form
   Liên hệ, dòng đó nằm ngoài vùng nhìn sau khi người dùng vừa điền xong 6 ô —
   bấm Gửi rồi không thấy gì phản hồi, dễ bấm lại lần nữa. Hộp thoại thì không
   thể bỏ lỡ, và buộc phải bấm một cái để đóng nên chắc chắn đã được đọc.

   Dùng lại đúng lớp phủ của .modal (z-index 120) để không sinh thêm một tầng
   chồng lấn mới; main.js dựng markup nên 24 file HTML không phải khai gì. */
.dialog {
  position: fixed; inset: 0; z-index: 120;
  display: flex; align-items: center; justify-content: center; padding: 24px;
  background: rgba(0, 0, 0, .55);
  opacity: 0; visibility: hidden;
  transition: opacity var(--dur) var(--ease), visibility var(--dur);
}
.dialog.is-open { opacity: 1; visibility: visible; }

.dialog__box {
  position: relative;
  width: min(460px, 100%);
  background: #fff; border-radius: var(--r-md);
  padding: clamp(28px, 4vw, 40px);
  text-align: center;
  box-shadow: var(--sh-card);
  /* Trượt lên một nhịp ngắn khi mở — hộp thoại "đến" chứ không "hiện ra đột
     ngột". Ở trạng thái đóng vẫn giữ transform để lần mở sau lặp lại được. */
  transform: translateY(12px) scale(.98);
  transition: transform var(--dur) var(--ease);
}
.dialog.is-open .dialog__box { transform: none; }

/* Vòng tròn dấu tích. Dùng ::after vẽ hai nét chứ không nhúng thêm file SVG —
   một icon dùng đúng một chỗ không đáng thêm một lượt tải. */
.dialog__icon {
  width: 60px; height: 60px; border-radius: 50%;
  margin: 0 auto clamp(16px, 2vw, 22px);
  background: color-mix(in srgb, var(--c-primary) 12%, #fff);
  position: relative;
}
.dialog__icon::after {
  content: ''; position: absolute; left: 50%; top: 48%;
  width: 11px; height: 20px;
  border: solid var(--c-primary); border-width: 0 2.5px 2.5px 0;
  transform: translate(-50%, -60%) rotate(45deg);
}

/* --- Biến thể LỖI ---
   Đỏ + dấu chấm than thay cho xanh + dấu tích. Đổi CẢ HÌNH chứ không chỉ đổi
   màu: người mù màu đỏ–xanh lá (khoảng 8% nam giới) nhìn hai vòng tròn này gần
   như y hệt nhau nếu chỉ khác sắc độ. Dấu tích và dấu chấm than thì phân biệt
   được ở mọi kiểu nhìn màu (WCAG 1.4.1). */
.dialog--error .dialog__icon {
  background: color-mix(in srgb, #c0392b 12%, #fff);
}
/* Thân dấu chấm than — dựng lại ::after từ đầu, không kế thừa dấu tích. */
.dialog--error .dialog__icon::after {
  width: 3px; height: 12px;
  border: 0; border-radius: 2px;
  background: #c0392b;
  top: 30%;
  transform: translate(-50%, 0) rotate(0);
}
/* Chấm tròn dưới chân dấu chấm than */
.dialog--error .dialog__icon::before {
  content: ''; position: absolute; left: 50%; top: 66%;
  width: 3px; height: 3px; border-radius: 50%;
  background: #c0392b;
  transform: translate(-50%, 0);
}
.dialog__title { font-size: var(--fs-h3); margin-bottom: 10px; }
.dialog__text  { font-size: var(--fs-sm); line-height: 1.7; color: var(--c-body); }
.dialog__actions { margin-top: clamp(20px, 2.6vw, 28px); }
.dialog__actions .btn { min-width: 140px; }

@media (prefers-reduced-motion: reduce) {
  .dialog__box { transform: none; }
}

/* --- 15.13 Dải nền xám tràn viền (gallery dự án / bài viết) -- */
.band-grey { background: var(--c-grey); padding-block: clamp(28px, 4vw, 52px); }

/* --- 15.14 Ô CHỜ ẢNH (gom từ 20 trang) ----------------------
   Khi dựng song song, 3 nhóm đã tự viết ô chờ riêng vì §15 ban đầu chỉ có
   `.tile-card__media.is-placeholder`. Đây là bản dùng chung — trang mới dùng
   `.media-ph` thay vì tự chế. Tỷ lệ đặt qua biến `--ph-ratio`. */
.media-ph {
  display: block; width: 100%;
  aspect-ratio: var(--ph-ratio, 16 / 9);
  background: var(--c-grey); border-radius: var(--r-sm);
}
.media-ph--flat   { border-radius: 0; }           /* banner tràn viền */
.media-ph--soft   { background: var(--c-grey-light); }
.media-ph--label {                                 /* có chữ ở giữa, vd tên đối tác */
  display: flex; align-items: center; justify-content: center;
  text-align: center; padding: 12px;
  font-size: var(--fs-xs); color: var(--c-muted);
}

/* Biến thể is-placeholder cho các media còn lại */
.page-banner__img.is-placeholder {
  display: block; height: var(--h-page-banner); background: var(--c-grey);
}
.gallery__thumb.is-placeholder,
.gallery__main.is-placeholder { background: var(--c-grey); cursor: default; }
.ebook-card__media.is-placeholder { background: var(--c-grey); box-shadow: none; }
.collection-card__media.is-placeholder,
.plain-card__media.is-placeholder,
.project-card__media.is-placeholder,
.news-card__media.is-placeholder { background: var(--c-grey); }

/* --- 15.15 Biến thể trên nền tối ---------------------------- */
/* Breadcrumb đè lên banner tối (trang có `.page-banner`) */
.breadcrumb--on-banner { color: rgba(255,255,255,.8); }
.breadcrumb--on-banner a:hover { color: #fff; }
.breadcrumb--on-banner [aria-current="page"] { color: #fff; }
.breadcrumb--on-banner li + li::before { background: rgba(255,255,255,.6); }

/* Nút tròn điều hướng đặt trên nền tối (section Lịch sử hình thành…) */
.nav-btn--on-dark {
  background: transparent; border-color: rgba(255,255,255,.5); color: #fff;
}
.nav-btn--on-dark:hover { background: #fff; border-color: #fff; color: var(--c-ink); }

/* --- 15.16 Bổ sung nhỏ -------------------------------------- */
/* Dòng địa danh mờ dưới tiêu đề trang chi tiết (dự án, tuyển dụng…) */
.page-head__place { font-size: var(--fs-sm); color: var(--c-muted); margin-top: 6px; }

/* Hàng pill lọc căn giữa — lặp ở Tin tức, Thư viện, Tìm kiếm */
.pill-row {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 12px; margin-bottom: clamp(22px, 3vw, 34px);
}

/* --- 15.17 Bổ sung cho form (gom từ trang Liên hệ / Tuyển dụng) --- */
/* Một cột dọc các .field bên trong .form-grid */
.form-col { display: grid; gap: 18px; align-content: start; }

/* Hàng nút cuối form: trải hết chiều rộng .form-grid, căn phải */
.form-actions {
  grid-column: 1 / -1;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 14px;
}
/* Dòng trạng thái sau khi gửi (role="status") */
.form-note { font-size: var(--fs-xs); color: var(--c-primary); }
.form-note.is-error { color: #c0392b; }


/* ===== 15c · CHẤM TRANG SLIDER (chỉ dựng ở mobile — main.js initSliderDots) ===== */
.swiper-dots {
  display: flex; justify-content: center; align-items: center;
  /* gap 0: vùng chạm 24px của mỗi chấm đã tự chừa 8.5px đệm mỗi bên, nên hai
     chấm liền nhau vẫn cách nhau 17px khoảng trắng thị giác. Thêm gap nữa là
     dãy chấm rời rạc, đọc thành nhiều vật thể thay vì một thước đo. */
  gap: 0; margin-top: 10px;
}
/* Chấm hiển thị 7px nhưng vùng chạm 24×24 nhờ padding + background-clip: nút
   7px thì gần như không bấm trúng, mà phóng to chấm lên cho vừa tay lại thành
   một dãy nút to lấn át chính nội dung nó đang chỉ tới. */
.swiper-dots__dot {
  width: 24px; height: 24px; padding: 8.5px;
  border-radius: 50%;
  background: var(--c-line-strong);
  background-clip: content-box;
  /* CỐ Ý chỉ chuyển màu, KHÔNG chuyển `width`: animate width là animate layout —
     mỗi khung hình phải xếp lại cả hàng chấm. Bề rộng nhảy 16px trong khi màu
     đang đổi thì mắt không kịp bắt, nên bỏ đi chẳng mất gì. */
  transition: background-color var(--dur) var(--ease);
}
/* Chấm đang xem kéo dài thành gạch: phân biệt được bằng HÌNH chứ không chỉ bằng
   màu, nên vẫn đọc ra ở chế độ tương phản cao và với người mù màu. */
.swiper-dots__dot.is-active {
  width: 40px; border-radius: var(--r-pill);
  background: var(--c-primary);
  background-clip: content-box;
}
/* Trên nền tối (mục "Lịch sử hình thành"), chấm đang xem tô --c-primary là xanh
   đậm trên nền xanh đen — gần như tàng hình, nên nhìn vào tưởng slider không có
   chỉ báo nào. Đảo sang trắng cho cả hai trạng thái. */
.about-history .swiper-dots__dot { background: rgba(255, 255, 255, .32); background-clip: content-box; }
.about-history .swiper-dots__dot.is-active { background: #fff; background-clip: content-box; }

/* ===== 15b · BỘ LỌC GẬP — CHỈ trang danh sách sản phẩm =====
   main.js:initFilterToggle() chuyển chính <h2>Bộ lọc</h2> sẵn có vào trong một
   <button>, KHÔNG dựng thêm nút mang nhãn y hệt: bản trước làm vậy nên màn hình
   có hai chữ "Bộ lọc" nằm chồng nhau.
   Các trang chỉ có hai ô lọc trên một hàng ngang thì không gập gì cả — xem chú
   thích trong initFilterToggle. */
/* Tên KHÁC `.filter__toggle` (§15.7 — nút gập của từng nhóm lọc trong sidebar):
   trùng tên là thừa hưởng `width: 100%` + `justify-content: space-between` của
   nó, icon phễu bị đẩy sang một đầu còn nhãn sang đầu kia. */
.filter__head-btn {
  display: flex; align-items: center; gap: 8px;
  width: fit-content; min-width: 0; text-align: left;
  color: inherit;
}
/* Icon phễu tô màu thương hiệu — điểm neo màu duy nhất của hàng này, nên mắt
   bắt được "chỗ lọc" ngay cả khi lướt rất nhanh. */
.filter__head-icon { color: var(--c-primary); flex: none; }

/* Gập bằng grid-template-rows 0fr→1fr: cùng cách với accordion menu ở §11, cho
   chiều cao chuyển mượt mà KHÔNG cần JS đo pixel. */
.filter-collapse {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur) var(--ease);
}
.filter-collapse.is-open { grid-template-rows: 1fr; }
/* overflow:hidden phải nằm ở lớp TRONG, không phải ở .filter-collapse: đặt ở
   ngoài thì lúc mở xong danh sách xổ của ô lọc bị cắt mất. */
.filter-collapse__inner { overflow: hidden; }
.filter-collapse.is-open .filter-collapse__inner { overflow: visible; }

/* ===== 16 · RESPONSIVE (1240* / 1200 / 1024 / 720 / 640) =====
   *1240 chỉ dành riêng cho thanh header — xem §16a. */

/* ----- 16a · DẢI CHẬT NHẤT CỦA HEADER (1025–1240px) -----
   Thanh header là bốn khối cứng trên MỘT hàng: logo · nav 8 mục · badge Home
   Stylist · cụm tìm kiếm+ngôn ngữ. Không khối nào co được (nav dùng `nowrap`,
   logo/actions có `min-width: max-content`), nên khi khung hẹp lại thì phần thừa
   KHÔNG bị cắt mà TRÀN RA NGOÀI viên pill trắng.

   Đo trước khi sửa (khổ nghỉ, chưa ghim — khổ nghỉ chật hơn khổ ghim 48px vì
   viên pill còn thụt --pad hai bên):
     · 1025px: bản VN tràn 32px, bản EN tràn 42px khỏi mép pill
     · 1201px: bản EN tràn 10px  ← dải này TRƯỚC ĐÂY không có rule nào
   Bản EN luôn tệ hơn vì nhãn tiếng Anh dài hơn: "DISTRIBUTION NETWORK" một mình
   đã 151px, cả nav 545px so với 533px của VN.

   Hai việc, tách làm hai @media vì phạm vi khác nhau:

   1 · 1025–1240 — LẤY LẠI CHỖ. Thu đệm trong pill (24→16) và khe giữa bốn khối
       (2vw→12px, cùng con số với khổ ≤1024) trả lại ~42px, đủ để cả hai ngôn ngữ
       nằm gọn trong pill tới 1025px. Trần 1240px là chỗ bản EN tự đủ chỗ với rule
       gốc (đo: vừa khít ở 1241px), không phải con số tròn cho đẹp.
       --header-pad đổi qua BIẾN chứ không ghi đè padding: §8b cộng lại đúng biến
       này lúc ghim, sửa sai chỗ là logo nhảy khi header trải ra.

   2 · 1025–1200 — nav nhỏ hơn một chút (12px, letter-spacing 0, logo 30px) để
       chỗ vừa lấy lại được dồn vào KHE GIỮA CÁC MỤC, không phải vào chữ.

   Khe nav trước là 8px cứng — client đọc ra "quá gần" (phản hồi 30/07/2026) và
   đúng: 8px chỉ gấp đôi khoảng cách giữa hai TỪ trong cùng một nhãn (~4px), nên
   "ABOUT US DISTRIBUTION NETWORK" dính thành một khối chữ, mắt không tách được
   đâu là ranh giới hai mục. Bản kế nới thành clamp 8→13px theo bề rộng màn.

   PHẢN HỒI MONA R1 tr.6 vẫn báo "các chữ đang hơi dính nhau quá" và yêu cầu bỏ
   mục Tuyển dụng khỏi thanh menu. Bỏ xong thì nav còn BẢY mục thay vì tám, dôi
   ra ~60px (EN) đến ~80px (VN) — đủ để khe thành một con số cố định rộng rãi,
   không phải bò theo màn nữa.

   16px đo từ trường hợp CHẬT NHẤT (1025px, tiếng Anh, nhãn "Distribution
   network"): ở đó pill bắt đầu tràn từ khe 24px, nên 16px còn dư 8px an toàn;
   tiếng Việt rộng hơn, tràn từ 28px. Font vẫn 12px — thử 13px thì trần khe tụt
   về đúng 13px, tức đánh đổi chính thứ client đang phàn nàn để lấy 1px cỡ chữ.
   Đo lại khi đổi nhãn menu hoặc thêm mục mới. */
@media (min-width: 1025px) and (max-width: 1240px) {
  :root { --header-pad: 16px; }
  .header__inner { gap: 12px; }
  .header__actions { gap: 10px; }
}
@media (min-width: 1025px) and (max-width: 1200px) {
  /* Cũng nới theo yêu cầu "tăng khoảng cách giữa các menu", nhưng KHÔNG được
     dùng chung con số với §8: ở dải này nav đã bị thu còn 12px + letter-spacing 0
     nên chỗ trống ít hơn hẳn, và mép trái 1025px là điểm chật nhất của CẢ site
     (bản EN chỉ chịu tối đa 22px khe — đo được).
     Vì vậy sàn giữ nguyên 16px như trước ở đúng 1025px, rồi mới nới dần lên 20px
     khi màn rộng ra:  1025 → 16 (en≤22) · 1100 → 16 (en≤34) · 1200 → 20 (en≤51).
     Trần 20px hạ theo §8 sau khi client xin "giảm nhẹ khoảng cách". */
  .header__nav { gap: clamp(16px, 4.4vw - 32px, 20px); }
  .header__nav a { font-size: 12px; letter-spacing: 0; }
  .header__logo img { height: 30px; }
  .header__badge img { height: 22px; }
}

@media (max-width: 1024px) {
  /* --header-pad đổi ở ĐÂY chứ không ghi đè padding của .header__inner: §8b cộng
     lại đúng biến này lúc ghim, đặt sai chỗ là logo nhảy 8px khi header trải ra. */
  :root {
    --header-h: 62px; --header-top: 10px; --header-pad: 16px;
    /* Banner vẫn CAO HƠN bản cũ, nhưng theo đường cong mềm hơn desktop.
       Lý do phải tách khỏi con số của desktop: vh trên điện thoại dọc gần bằng
       vh trên màn hình máy tính (812 so với 810) nhưng Ý NGHĨA khác hẳn — cùng
       72vh, ở máy tính là một dải ngang, ở điện thoại là 3/4 màn hình dựng đứng
       và người dùng không đoán được bên dưới banner còn gì.
       Chặn trên 620px ≈ 76% màn hình iPhone dọc, vẫn chừa được một dải nội dung
       nhìn thấy dưới banner. */
    --h-page-banner: clamp(380px, 64vh, 620px);
  }
  .header__inner { gap: 12px; }
  .header__nav, .header__badge, .header__divider,
  .header__actions .re-select-main { display: none; }

  /* Thứ tự mobile: LOGO (trái) · TÌM KIẾM + MENU (phải).
     Phản hồi MONA R1 tr.11: "Để logo bên trái, cụm icon tìm kiếm và menu bên
     phải như hình". Trước pass này là TÌM KIẾM · LOGO(giữa) · MENU.

     `display: contents` xoá hộp của .header__actions nhưng giữ nguyên các con,
     nên nút tìm kiếm và nút menu trở thành flex item TRỰC TIẾP của
     .header__inner — cùng cấp với logo, sắp lại được bằng `order`. Không có nó
     thì logo và cụm nút nằm ở hai cấp DOM khác nhau, muốn xen kẽ phải sửa
     markup ở cả 28 file HTML (header được chép cứng vào từng trang).

     `margin-inline-start: auto` đặt trên phần tử order:2 (nút tìm kiếm) nuốt hết
     chỗ trống ở giữa, đẩy CẢ CỤM search + burger sang mép phải. Đặt `flex: 1`
     lên logo thay cho margin auto thì không được: logo sẽ giãn ra chiếm cả dải
     và vùng bấm của nó trùm sang giữa header. */
  .header__actions { display: contents; }
  .header__logo { order: 1; flex: none; display: flex; justify-content: flex-start; }
  .header__search-btn { order: 2; flex: none; margin-inline-start: auto; }
  .header__burger { order: 3; flex: none; display: flex; }

  /* Ô tìm kiếm bung ra vẫn phải phủ đúng .header__inner: nó là position:absolute
     nên đứng ngoài luồng flex, `order` không đụng tới. KHÔNG cần ghi đè padding
     ở đây nữa — .header__search đã dùng `padding-inline: inherit` (§8) nên tự
     lấy --header-pad 16px của khổ này. */

  /* Vùng bấm 44×44 của các nút icon đã chuyển lên §8 để áp ở MỌI khổ, không chỉ
     ≤1024px. Đừng khai lại ở đây. */

  /* Icon kính lúp + menu to hơn ở khổ cảm ứng (18 → 22px, theo yêu cầu client
     "tăng nhẹ size icon menu và search"). Chỉ đổi HÌNH THỨC — vùng bấm vẫn là
     44×44 do ::after ở §8 lo, nên không ảnh hưởng gì tới khả năng chạm.
     Trên máy tính giữ 18px: ở đó hai icon này đứng cạnh dải chữ nav 13px, to lên
     là lệch thứ bậc; còn ở mobile chúng là hai vật thể duy nhất bên phải header
     nên 18px đọc ra hơi nhỏ so với logo cao 30px. */
  .header__search-btn .icon,
  .header__burger .icon { width: 22px; height: 22px; }

  /* ---- Chọn ngôn ngữ trong menu: bày thẳng 2 lựa chọn ----
     Trên điện thoại, dropdown ở chân menu là mẫu tệ nhất có thể: nó bung XUỐNG
     (top: 100% + 8px) từ một khối đã bị `margin-top: auto` ghim sát đáy, nên
     danh sách đổ ra ngoài vùng nhìn thấy. Hai lựa chọn thì cũng không đáng phải
     giấu — bày cả hai ra thành cụm nút phân đoạn, bấm một lần là xong.

     Không cần đổi markup hay JS: .re-select__list đã sẵn có đúng 2 nút
     [data-lang]; ở đây chỉ gỡ position/opacity/visibility để nó thành khối tĩnh.
     Cả hai listener hiện có vẫn bắt được click — main.js (đổi nhãn) và
     i18n.js (đổi ngôn ngữ thật). */
  .header-mobile__foot .re-select__btn { display: none; }
  .header-mobile__foot .re-select__list {
    position: static; opacity: 1; visibility: visible; transform: none;
    display: flex; gap: 4px;
    min-width: 0; max-height: none; overflow: visible;
    padding: 4px; box-shadow: none;
    background: var(--c-grey-light); border: 0; border-radius: var(--r-pill);
  }
  .header-mobile__foot .re-select__list button,
  .header-mobile__foot .re-select__list a {
    width: auto; padding: 9px 16px; text-align: center; border-radius: var(--r-pill);
  }
  /* Trạng thái ĐANG CHỌN dùng đúng dáng .pill.is-active của cụm tab (§6): nền
     xanh đặc, chữ trắng, đậm. Bản trước dùng nền xanh 10% + chữ xanh — trên nền
     xám của khung nó gần như chìm, nhìn ra "hơi khác" chứ không ra "đang chọn".
     Cùng một ngôn ngữ thị giác với tab thì người dùng không phải học lần hai. */
  .header-mobile__foot .re-select__list button.is-active,
  .header-mobile__foot .re-select__list a.is-active {
    background: var(--c-primary); color: #fff; font-weight: var(--fw-title);
  }
  /* Dấu ✓ float:right chỉ hợp lý trong danh sách dọc; ở cụm nút ngang nó đẩy
     chữ lệch và thừa — nền xanh đặc đã là chỉ dấu đủ rõ. */
  .header-mobile__foot .re-select__list button.is-active::after,
  .header-mobile__foot .re-select__list a.is-active::after { display: none; }

  .footer__cols { grid-template-columns: 1fr 1fr; gap: 28px; }

  /* Sidebar lọc chuyển lên trên, xếp ngang thành nhiều cột */
  .with-filter { grid-template-columns: 1fr; }
  .filter__fields { grid-template-columns: repeat(3, 1fr); }
  .grid-4 { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 720px) {
  /* --- Địa chỉ · điện thoại · email là MỘT cụm thông tin liên hệ ---
     Markup tách chúng làm hai khối (.footer__brand giữ địa chỉ, .footer__bottom
     giữ điện thoại + email) và .footer__bottom có `border-top` — nên trên điện
     thoại, khi mọi thứ đã xếp dọc canh giữa, đường kẻ đó cắt ngang đúng giữa một
     cụm đáng lẽ phải liền mạch. Bỏ kẻ, thu khoảng cách hai khối lại còn bằng
     khoảng cách trong khối.
     Dòng bản quyền thì tách ra bằng khoảng trống rộng hơn — nó KHÔNG thuộc cụm
     liên hệ, và giờ nó là thứ duy nhất cần tách. */
  .footer__brand { padding-bottom: 10px; }
  .footer__bottom { border-top: 0; padding-top: 0; gap: 10px; }
  /* Đường kẻ chuyển XUỐNG trên dòng bản quyền. Ở vị trí cũ (đỉnh
     .footer__bottom) nó cắt ngang giữa địa chỉ và số điện thoại — tức là chia
     đôi một cụm liên hệ đáng lẽ liền mạch. Ở đây nó tách đúng thứ cần tách:
     cụm liên hệ ở trên, dòng bản quyền ở dưới. */
  .footer__bottom > span {
    width: 100%;
    margin-top: 18px; padding-top: 16px;
    border-top: 1px solid rgba(255, 255, 255, .16);
    opacity: .75;
  }

  /* Cụm địa chỉ · điện thoại · email canh giữa cho cân với .footer__brand ngay
     bên trên (vốn đã text-align:center) — trước đây dồn trái nên hai khối lệch
     trục nhau rõ rệt trên màn hẹp. */
  .footer__bottom { flex-direction: column; align-items: center; text-align: center; }
  .footer__contact { flex-direction: column; align-items: center; gap: 10px; }

  /* ---- Mũi tên trái/phải của MỌI slider ----
     Trên cảm ứng người dùng vuốt chứ không bấm; hai nút 44px chỉ chiếm chỗ và,
     với .m-news, còn nằm đè lên chính tấm card. Mọi slider đều bật touch/drag
     mặc định của Swiper nên không mất chức năng nào. */
  .nav-group,
  .m-news__prev, .m-news__next,
  .cd-hero__nav { display: none; }

  /* ---- Banner trang con ----
     Ngoại lệ của quy tắc "bỏ mũi tên ở mobile": banner là ảnh phủ kín khung,
     KHÔNG ló rìa slide kế tiếp nên nhìn vào không có gì báo là vuốt được. Ở đây
     mũi tên là chỉ báo duy nhất — nhưng gom về góc dưới phải thành một cặp nhỏ
     thay vì hai nút dí hai mép, để không cắt ngang tấm ảnh.
     Không dùng dot: initSliderDots đã loại .page-banner ra, hai nút này đã đủ.

     Chữ và mũi tên DÙNG CHUNG một mốc để cùng nằm trên lề dưới của tấm ảnh —
     giống hệt cách banner trang chủ ghép cụm chữ với vòng đếm. Trước đây chữ ở
     58–78px còn mũi tên ở 14–22px, thành hai hàng rời nhau lửng lơ giữa phần
     dưới tấm ảnh.

     Mốc chung ấy nay là --banner-base. Bản trước đặt `bottom` cho cụm mũi tên
     mà QUÊN mất `padding-bottom` của rule desktop vẫn còn hiệu lực, nên hai giá
     trị CỘNG VÀO NHAU và mũi tên vẫn cao hơn chữ ~36px — đúng thứ mà comment
     ngay trên tuyên bố là đã sửa. Giữ `bottom: 0` như desktop, chỉ đổi token. */
  .page-banner { --banner-base: clamp(18px, 3vh, 28px); }
  .page-banner__nav {
    justify-content: flex-end; gap: 10px;
    /* Lề PHẢI phải chừa chỗ cho dải nút liên hệ nổi (.group-contact, position:
       fixed ở góc phải dưới) — đúng thứ mà `padding-right: clamp(62px, 5.4vw,
       86px)` của rule desktop đang lo. Bản trước viết `padding-inline: var(--pad)`
       nên ĐÈ MẤT phần chừa đó ở cả hai phía, mũi tên tụt về sát mép 16px.
       Suốt thời gian qua chưa lộ vì banner còn thấp, cặp mũi tên nằm vừa đủ TRÊN
       đỉnh dải liên hệ. Đến lúc banner chi tiết sản phẩm nâng lên 88dvh thì mũi
       tên tụt xuống đúng vào dải và bị nó phủ lên (z-index của .group-contact
       cao hơn) — đo được ở 430/540/640/768/1024.
       76px = 62px dải chiếm thật tính từ mép phải (44px nút + lề, đo trực tiếp)
       + 14px khe. Con số cố định vì dải liên hệ cũng cố định theo px, không co
       theo bề rộng màn. */
    padding-inline: var(--pad) 76px;
  }

  /* CĂN TRÁI — CHỈ banner chi tiết sản phẩm (feedback R2 tr.5: "Nút mũi tên di
     chuyển cho về căn trái thẳng hàng với hàng nút màu xanh"; ảnh kèm là trang
     san-pham-chi-tiet).

     PHẢI giới hạn ở .pd-banner, TUYỆT ĐỐI không áp cho .page-banner__nav chung:
     hai trang còn lại có cặp mũi tên (bo-suu-tap, san-pham-gach) đặt chữ +
     nút "Khám phá ngay" trong .page-banner__body neo ở góc dưới TRÁI, đúng cùng
     hàng với mũi tên — chính vì thế rule ngay dưới mới phải chừa 102px bên phải
     cho chúng. Đẩy mũi tên sang trái ở đó là chồng thẳng lên nút CTA.
     Banner .pd-banner không có .page-banner__body nào nên góc trái trống hẳn.

     `inset` của rule desktop neo hộp vào mép phải (`auto 0 0 auto`) và hộp co
     theo nội dung, nên đổi mỗi justify-content là chưa đủ — phải nhả cạnh trái
     ra thì flex-start mới có chỗ căn.

     Sang trái cũng giải luôn chồng lấn với dải .group-contact bên phải, nên trả
     lề về `var(--pad)` cân hai bên thay vì giữ 76px chừa chỗ vô cớ. */
  .pd-banner .page-banner__nav {
    inset: auto 0 0 0;
    justify-content: flex-start;
    padding-inline: var(--pad);
  }

  /* Dải nút liên hệ nổi lên MỘT NẤC ở khổ điện thoại (feedback R2 tr.5: "Để
     tránh các nút đè nhau có thể đẩy hàng nút màu xanh dịch lên 1 nấc").

     Một nấc = đúng bước của chính dải này: 46px (chiều cao nút, §13) + 10px
     (gap) = 56px, nên 16 → 72px. Lấy theo bước thật thay vì một số tròn để dải
     vẫn nằm trên lưới của chính nó nếu sau này đổi cỡ nút.

     Không chỉ để né cặp mũi tên của .pd-banner (cặp đó vừa dời hẳn sang trái ở
     rule trên): trên TRANG CHỦ, vòng play/pause của banner neo `padding-right:
     var(--pad)` = 16px — đúng cột với dải này (`right: 16px`) — nên hai khối
     chồng lên nhau ở đáy màn, thấy rõ trong ảnh client gửi kèm. Nâng dải lên là
     chỗ sửa chung cho cả hai trang.

     Chỉ ở ≤720px: desktop còn nhiều chiều cao, hai khối không gặp nhau, và dải
     ở 16px là đúng thói quen nút nổi trên máy tính. */
  .group-contact { bottom: 72px; }
  .page-banner__nav .nav-btn { width: 38px; height: 38px; }

  /* Lớp phủ NHẸ đi: bản desktop tối tới .62 ở chân ảnh vì chữ ở đó rất lớn; ở
     khổ này chữ nhỏ hơn nên giữ nguyên độ đậm ấy thì nửa dưới tấm ảnh gần đen. */
  /* `bottom` không khai lại ở đây: rule desktop đã trỏ vào --banner-base, mà
     token vừa bị ghi đè ngay trên — chép số lần nữa là mở lại đúng cái khe làm
     hai cụm trôi khỏi nhau.
     Chừa chỗ cho cặp mũi tên (2×38 + khe 10 + lề) để tiêu đề dài không chui
     xuống dưới chúng.

     `:has(.page-banner__nav)` bắt buộc, KHÔNG đặt thẳng lên .page-banner__body:
     banner trang Giới thiệu (.page-banner--center) không có cặp mũi tên nào, nên
     102px ấy chỉ là chỗ trống — mà hộp chữ ở biến thể này CANH GIỮA, nên nó vừa
     kéo cả cụm lệch sang trái vừa bóp bề rộng còn 241/375px: đoạn mô tả dài bị
     bẻ thành 8 dòng ngắn ngủn (phản hồi 30/07/2026 "canh giữa, tăng width cụm
     text"). Bỏ đệm đi thì cụm chữ tự về giữa và rộng bằng cả container. */
  .page-banner:has(.page-banner__nav) .page-banner__body {
    padding-right: calc(var(--pad) + 102px);
  }
  /* `:not(.page-banner--center)` — thiếu vế loại trừ này thì rule đen ở đây
     (cùng specificity, đứng SAU trong file) đè mất gradient xanh thương hiệu
     của `.page-banner--center::after` (§ trên, dòng ~1577) trên mobile, dù
     desktop vẫn đúng. Banner "Về chúng tôi" dùng biến thể --center nên bị lộ
     lại nền đen ở ≤720px (phản hồi client 10/08/2026 "kể cả trên bản điện
     thoại" — mục 1 mới chỉ sửa desktop). */
  .page-banner:not(.page-banner--center)::after {
    background: linear-gradient(to top,
      rgba(0,0,0,.5) 0%, rgba(0,0,0,.18) 42%, rgba(0,0,0,0) 72%);
  }

  /* ================= VÙNG CHẠM =================
     Bộ soát tự động 23 trang ở 375px đo được một loạt link chỉ cao 17–23px —
     dưới ngưỡng 24px của WCAG 2.5.8, và xa chuẩn 44px mà chính file này đã đặt
     cho nút header ở §16/1024. Chữ nhỏ thì ngón tay không có gì để nhắm, hai
     link cạnh nhau cách 9px là bấm nhầm dòng.

     Nới bằng padding DỌC chứ không bằng cỡ chữ: giữ nguyên dáng chữ của thiết
     kế, chỉ mở rộng hộp. Ở những chỗ nới padding thì thu margin lại tương ứng
     để tổng chiều cao khối gần như không đổi. */

  /* Footer: link cột (Giới thiệu, Dự án…, Facebook, Youtube) 17px → 31px.
     Nhịp dòng cũ 17+9=26px, nhịp mới 31px — dài thêm 5px mỗi dòng mà bù lại
     không còn bấm nhầm sang dòng bên cạnh. */
  .footer__col a { display: block; padding-block: 7px; }
  .footer__col li + li { margin-top: 0; }
  /* Điện thoại và email ở chân trang: 22px → 34px. */
  .footer__contact a { padding-block: 6px; }

  /* Breadcrumb: 22px → 32px. Chọn `li > a, li > span` chứ không phải
     `a, [aria-current]` như bản trước: cấp cha KHÔNG có trang riêng (crumb
     "Sản phẩm") là <span> trần, không khớp cả hai vế cũ nên thấp hơn hàng xóm
     10px và tụt khỏi hàng khi breadcrumb xuống dòng. Con trực tiếp để span
     lồng bên trong (.js-search-term ở tim-kiem.html) không bị nhân đôi padding. */
  .breadcrumb li > a, .breadcrumb li > span { display: inline-block; padding-block: 5px; }

  /* ---- Hàng bộ lọc: [phễu Bộ lọc] [đặt lại] ········ [12 sản phẩm] ----
     main.js chuyển .count vào đây. `margin-right: auto` trên nút đặt lại gom
     cụm lọc về trái và đẩy số lượng sang phải — dùng nó thay cho
     `justify-content: space-between` vì với BA phần tử, space-between sẽ dàn
     đều và nút đặt lại rơi ra giữa hàng, tách khỏi nhãn nó thuộc về. */
  /* Nút "Bộ lọc" đo được 23px — thiếu đúng 1px so với ngưỡng. padding-block
     đưa lên 35px mà không đội chiều cao hàng, vì hàng đã cao bằng dòng đếm. */
  .filter__head { gap: 10px; margin-bottom: 12px; }
  .filter__head h2 { font-size: 18px; }
  .filter__head-btn { padding-block: 6px; }
  .filter__head .filter__reset { margin-right: auto; }
  .filter__head .count { margin: 0; white-space: nowrap; }

  /* ---- Canh giữa các cụm chữ ----
     Bố cục desktop đặt tiêu đề trái · nút phải; khi xuống một cột thì mọi thứ
     dồn trái tạo ra một mép trái dài lê thê không có điểm nhấn. Canh giữa cho
     tiêu đề và đoạn dẫn ngắn. */
  .sec-head { flex-direction: column; align-items: center; text-align: center; gap: 14px; }
  .sec-title { text-align: center; }

  /* ---- Thanh tab: XUỐNG DÒNG, KHÔNG CUỘN NGANG ----
     Phản hồi MONA R1 tr.15: "Đổi kiểu hiển thị của thanh trượt thành từng nút
     riêng, tương tự với trang sp, tin tức, thư viện catalog… (giống feedback bản
     máy tính)" — ảnh vẽ 4 tab xếp thành lưới 2×2 thay vì một dải cuộn ngang.

     Toàn bộ cơ chế cuộn ngang cũ đã GỠ, gồm ba mảnh đi liền nhau:
     · `flex-wrap: nowrap` + `overflow-x: auto` ở đây (đúng một dòng `nowrap` là
       thứ tạo ra thanh cuộn — desktop vốn đã `wrap`);
     · block `.pill-group.is-scrollable` kéo cụm tab tràn ra mép phải, bỏ bo góc
       phải và chừa 46px đường chạy né dải nút liên hệ nổi;
     · `initScrollableTabs()` trong main.js đo `scrollWidth > clientWidth` để gắn
       lớp đó — đã xoá cùng lượt, kể cả phần gọi lại khi đổi ngôn ngữ.
     Khôi phục một mảnh mà thiếu hai mảnh kia thì hỏng: ví dụ trả `nowrap` về mà
     không có `.is-scrollable` thì tab cuối bị dải nút Zalo che vĩnh viễn.

     ĐÚNG HAI NÚT MỘT DÒNG, khớp ảnh tr.15 (Gạch lát · Gạch ốp / Gạch trang trí ·
     Sản phẩm khác) và ảnh tr.10 (Sản phẩm · Bộ sưu tập).

     `flex-basis: calc(50% - var(--pill-gap))` là chỗ ép ra lưới hai cột, không
     phải `1 1 auto`: với basis auto, trình duyệt xếp theo bề rộng CHỮ nên hàng
     đầu nhét được ba nhãn ngắn rồi nhãn thứ tư rơi xuống một mình (3+1 lệch).
     Trừ nguyên một --pill-gap thay vì nửa để chắc chắn ba nút KHÔNG bao giờ lọt
     một dòng: 3 × (50% − g) + 2g = 150% − g > 100%.
     `flex-grow: 1` cho nút lẻ ở hàng cuối nở kín dòng, không đứng lệch trái.

     Phải ghi đè `width: fit-content` của rule gốc, nếu không cụm tab ngắn sẽ co
     lại ôm chữ và phần trăm ở trên tính trên một khung hẹp hơn màn hình. */
  .pill-group { width: 100%; margin-inline: 0; }
  .pill-group .pill {
    flex: 1 1 calc(50% - var(--pill-gap));
    justify-content: center;
  }

  /* Ảnh card dự án: 3/4 dựng đứng ở một cột trên điện thoại thành ~343×457px,
     gần trọn màn hình cho MỘT card. Đổi sang 4/3 còn ~257px, lướt thấy được
     nhiều dự án hơn. */
  .project-card__media { aspect-ratio: 4 / 3; }

  /* Banner trang con thấp lại. Mốc ≤1024 để 64vh/620px là tính cho máy tính
     bảng; trên điện thoại dọc, 620px ăn hết 76% màn hình cho một tấm ảnh kèm
     một dòng tiêu đề — người dùng phải cuộn một màn mới tới nội dung thật.
     46vh/420px vẫn đủ cho ảnh và tiêu đề mà chừa lại nửa màn cho nội dung.
     (Banner TRANG CHỦ giữ nguyên — nó là nội dung chính, không phải phần đầu.) */
  :root { --h-page-banner: clamp(280px, 46vh, 420px); }

  .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); }
  .filter__fields { grid-template-columns: repeat(2, 1fr); }
  .form-grid { grid-template-columns: 1fr; }
  .gallery { grid-template-columns: 1fr; }
  /* Thumb chuyển thành dải ngang cuộn được dưới ảnh chính */
  .gallery__thumbs { grid-auto-flow: column; grid-auto-columns: 72px; overflow-x: auto; order: 2; }
  .job-card { flex-direction: column; align-items: flex-start; }
}
@media (max-width: 640px) {
  /* Giữ 2 cột thay vì tụt về 1: hàng đầu thành VỀ CHÚNG TÔI | SẢN PHẨM đúng
     như bản thiết kế mobile. Cột 4 (form đăng ký email) phải chiếm trọn hàng —
     ô input + nút "Đăng ký" nhét vào nửa chiều rộng thì chữ bị cắt. */
  .footer__cols { grid-template-columns: 1fr 1fr; gap: 26px 20px; }
  .footer__col:nth-child(4) { grid-column: 1 / -1; }
  /* Giữ 44×44 — đây là ngưỡng chạm mà chính file này đã chuẩn hoá cho nút header
     ở §16/1024. Trước đây co xuống 38px là tự phá chuẩn của mình.

     `bottom: 64px` chứ KHÔNG phải 12px: khổ này có bộ số riêng (nút 44, gap 8,
     lề 12) nên phải tự tính lại "một nấc" của feedback R2 tr.5 — 44 + 8 = 52,
     12 + 52 = 64. Chép 72px của rule ≤720px sang đây là sai lưới, vì 72 được
     tính từ bộ số 46/10/16 của khổ kia.
     ĐỪNG bỏ dòng này đi rồi tưởng rule ≤720px lo hết: khối 640 đứng SAU và cùng
     specificity nên nó mới là bên quyết định ở mọi điện thoại dọc (≤640px). */
  .group-contact { right: 12px; bottom: 64px; gap: 8px; }
  .group-contact a, .group-contact button { width: 44px; height: 44px; }

  .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }

  /* ---- Số cột quyết định theo LOẠI THẺ, không theo class của lưới ----
     Cùng một `.grid-3` chứa lúc thì thẻ gạch vuông, lúc thì thẻ bộ sưu tập khổ
     lớn — nên vá theo từng trang là sai gốc và cứ sót mãi (đã sót đúng
     `san-pham-khac`, `thiet-bi-ve-sinh`, `keo-dan-gach` ở lượt trước).
     `:has()` cho phép hỏi ngược "lưới này đang chứa thẻ gì" và trả lời một lần
     cho mọi trang, kể cả trang thêm về sau.

     · thẻ gạch / e-book / thẻ phẳng: ảnh vuông, nhãn ngắn → 2 cột để lướt và
       so sánh được nhiều mẫu trong một màn.
     · thẻ bộ sưu tập: ảnh khổ lớn là nội dung chính, bóp một nửa thì không còn
       nhìn ra hoa văn → giữ 1 cột.

     (`:has()` đã được dùng sẵn trong repo — recruit.css §form ứng tuyển.) */
  :is(.grid-2, .grid-3, .grid-4):has(> .tile-card, > .ebook-card) {
    grid-template-columns: repeat(2, 1fr);
  }
  /* .plain-card giữ 1 cột: trang "Sản phẩm khác" chỉ có ĐÚNG HAI thẻ và chúng
     là hai NHÁNH HÀNG lớn (Keo dán gạch · Thiết bị vệ sinh), không phải hai mẫu
     để so sánh. Bóp còn nửa màn hình biến hai lối đi chính thành hai ô nhỏ. */
  :is(.grid-2, .grid-3, .grid-4):has(> .collection-card, > .plain-card) {
    grid-template-columns: 1fr;
  }

  .filter__fields { grid-template-columns: 1fr; }
  .breadcrumb { padding: 18px 0; font-size: var(--fs-xs); }
  .pagination a, .pagination span { width: 34px; height: 34px; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  /* Lenis không được khởi tạo ở chế độ này (main.js chặn), nên chỉ cần lo phần CSS. */
  [data-aos] {
    opacity: 1 !important; transform: none !important;
    filter: none !important; clip-path: none !important;
  }
  /* Header vẫn ghim bình thường, chỉ mất phần chuyển tiếp (khối `*` bên dưới đã
     ép transition-duration về .01ms). Không còn `.is-hidden` để phải vô hiệu hoá,
     và thanh tiến độ đọc đã gỡ hẳn ở §14c nên cũng không còn gì để ẩn ở đây. */
  /* Tắt hẳn, không để khối `*` bên dưới "chạy xong tức thì": tl-ping kết thúc ở
     opacity 0 nên vô hại, nhưng vòng sóng vẫn nhấp nháy một phát lúc tải. */
  .group-contact a .icon { animation: none; }
  .group-contact a::after { animation: none; opacity: 0; }
  .main, body.is-leaving .main { animation: none !important; opacity: 1 !important; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    transition-delay: 0s !important;
  }
}
