/* =============================================================================
 * CỬA HÀNG › CHỮ KHÔNG BỊ CẮT · KHÔNG ĐÈ ICON · VÙNG CHẠM ≥ 44px  (build 847)
 * File: client/public/shop-textwrap-847.css     (nạp SAU 779/787/830/836/840/846)
 * worker 4/5 — SUBTASK 4 của vòng "shop điện thoại theo ảnh ngang mẫu".
 *
 * ────────────────────────────── PHẠM VI CHÍNH XÁC ───────────────────────────
 * ĐÂY LÀ LỚP TYPOGRAPHY + COMPONENT NÚT/CHIP. Việc của nó:
 *   • Bỏ `white-space: nowrap` + `text-overflow: ellipsis` ở NHÃN QUAN TRỌNG;
 *     cho xuống tối đa 2 dòng (`-webkit-line-clamp`) rồi mới cắt, và khi đã cắt
 *     thì có tooltip (shop-textwrap-847.js gắn `title` + `data-sw-clamped`).
 *   • Khung nút/chip tự giãn theo nội dung: `height` cố định → `min-height`.
 *   • Icon `flex-shrink: 0` + SÀN `gap` ⇒ chữ dài không bao giờ bò lên icon.
 *
 * ⚠ DÙNG `overflow-wrap: break-word`, TUYỆT ĐỐI KHÔNG `anywhere`.
 *   Hai giá trị này ngắt dòng giống hệt nhau, nhưng `anywhere` CÒN hạ
 *   MIN-CONTENT WIDTH của ô xuống 1 KÝ TỰ. Trong hàng flex (`.sf-gridhead`,
 *   `.sf-top`) điều đó cho phép ô bị ép hẹp hơn cả bề rộng một từ ⇒ đo được:
 *   nút "TẤT CẢ" tụt còn 41,5px THẬT (cần 44) và tiêu đề tiếng Tây Ban Nha vỡ
 *   giữa từ thành "INVENTA / RIO". `break-word` giữ min-content = từ dài nhất.
 *   • Mọi phần tử bấm được có sàn vùng chạm `var(--sf-tap)` ⇒ ≥ 44px THẬT.
 *
 * KHÔNG ĐỤNG THẺ SẢN PHẨM. `.sf-card` · `.sf-card-n` · `.sf-nm` · `.sf-art` ·
 * `.sf-price` · `.sc-sb-*` · `.sf-grid` · `.sf-scroll` · `.sf-new` · `.sf-rar` ·
 * `.sf-grp` là của `mobile-shop-846.css` (worker 1) — file đó đã tự lo phần chữ
 * trong thẻ theo hướng "cho khung CAO TỰ DO từ min-height, `-webkit-line-clamp:
 * none`, `text-overflow: clip`". Hai cách khác nhau nhưng cùng đạt "không cắt
 * chữ"; chồng lên nhau chỉ sinh ra cãi nhau về specificity. Vì vậy ở đây KHÔNG
 * có một rule nào nhắm các selector trên (có test canh: mục B0 của
 * src/ui/shoptextwrap.test.ts sẽ đỏ nếu ai thêm vào).
 *
 * Cũng KHÔNG đụng: khung ngoài/lề an toàn (shop-landscape-840.css), luật chơi,
 * API/DB, protocol.
 *
 * ────────────────────────── VÌ SAO PHẢI 6 LẦN #shopframe ────────────────────
 * Nút/chip cửa hàng trên bản CHẠY THẬT do các file CSS NGOÀI quyết định, không
 * phải khối <style> của shopframe.ts (1 lần id). Bậc thang hiện có:
 *     shop-responsive-779.css       #shopframe × 2   (nạp KHÔNG điều kiện)
 *     mobile-shop-bag-787.css       #shopframe × 2
 *     mobile-repair-830.css         #shopframe × 3 + !important
 *     shop-text-836.css             #shopframe × 3   (rot90: × 4 + !important)
 *     shop-landscape-840.css        #shopframe × 4
 *     mobile-shop-846.css           #shopframe × 5   (worker 1, vòng này)
 *   ⇒ file này × 6 (rot90: × 6 + !important) và PHẢI <link> SAU tất cả.
 *
 * ───────────────────── VÌ SAO KHÔNG DÙNG @media CHO VÙNG CHẠM ───────────────
 * Cửa hàng KHÔNG reflow: nó luôn là khổ 1280×720 rồi `transform: scale(--sf-fit)`.
 * Điện thoại khoá dọc được game xoay 90° bằng CSS (body.rot90) nên viewport BÁO
 * 896×414 ⇒ mọi ngưỡng `max-width` đều trượt đúng trên thiết bị cần bù nhất
 * (bài học đã ghi trong ui/shoptap.ts). Vì vậy sàn vùng chạm đi theo
 * `var(--sf-tap)` = clamp(44px, 44px/fit, 160px) — ở PC fit ≥ 1 nên --sf-tap =
 * 44px, và `max(giá-trị-cũ, 44px)` = ĐÚNG GIÁ TRỊ CŨ ⇒ PC KHÔNG ĐỔI MỘT PIXEL.
 * Chỉ rule đổi BỀ NGANG/max-width mới bị chặn trong @media (max-width:1023.98px).
 *
 * ──────────────────── NĂM LỖI THẬT FILE NÀY VÁ (đều đo được) ─────────────────
 * 1. `.sf-cta` (nút MUA / TRANG BỊ / NHẬN MIỄN PHÍ — nút quan trọng nhất).
 *    shopframe.ts đặt `min-height: max(44px·inv, var(--sf-tap))` bằng 1 lần id,
 *    nhưng 779 ghi `min-height: 48px` bằng 2 lần id ⇒ BÙ VÙNG CHẠM BỊ VÔ HIỆU:
 *    ở fit 0,49 (điện thoại 667×375 nằm ngang) nút chỉ còn 48 × 0,49 ≈ 23,5px
 *    THẬT, chưa tới nửa ngưỡng 44px của WCAG 2.5.5.
 * 2. `.sf-inv-tab` và `.sf-cat` — shop-text-836.css khoá `min-height: 36px` /
 *    `38px !important` bằng 4 lần id ⇒ cũng đè mất --sf-tap (≈ 17–19px THẬT).
 * 3. `.sf-plus` (nút + nạp tiền), `.sf-closebtn` (X đóng), `.sf-dlg-x` — 779
 *    khoá 44×44 px BỐ CỤC bằng 2 lần id, đè bù của shopframe.ts (viết bằng
 *    `#shopframe[data-nav="rail"]` = 1 id + 1 attr ⇒ yếu hơn) ⇒ ≈ 22px THẬT.
 * 4. `.sf-im-act` (Trang bị / Bán / Xoá trong menu ngữ cảnh túi đồ) chỉ có
 *    `padding: 10px 12px`, KHÔNG có sàn nào ⇒ ≈ 19px THẬT. Nút "Xoá" cao 19px
 *    nằm sát nút "Bán" là công thức bấm nhầm mất đồ.
 * 5. `.sf-cell-tag` (nhãn ĐANG DÙNG trong ô túi đồ) neo `left: 50%` +
 *    `translateX(-50%)` + `nowrap`, không có `max-width` nào ⇒ "EN POSESIÓN"
 *    (es) / "POSSUÍDO" (pt) tràn ĐỐI XỨNG ra ngoài cả hai mép ô.
 *
 * ────────────────────────── NGÔN NGỮ ĐƯỢC KIỂM ──────────────────────────────
 * Game hiện có ĐÚNG 7 tiếng (client/public/game_i18n.js › SUPPORTED):
 *   vi · en · zh · ja · ko · es · pt      — KHÔNG có de, KHÔNG có ru.
 * Chuỗi dài nhất thực tế là es/pt ("ORDENAR: MAIS BARATO", "EN POSESIÓN",
 * "FALTAN 1.200 GOLD") rồi tới vi. Luật ở đây không đọc mã tiếng nào cả (chỉ
 * wrap + clamp + sàn) nên thêm de/ru sau này cũng không phải sửa file này.
 * ========================================================================== */

/* ---------------------------------------------------------------------------
 * (0) BIẾN DÙNG CHUNG — chỉ KHAI BÁO, không vẽ gì ⇒ không đổi pixel nào ở đâu.
 * ------------------------------------------------------------------------- */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe {
  /* Sàn vùng chạm. NGUỒN SỐ THẬT là ui/shoptap.ts (--sf-tap); đây chỉ là tên
     ngắn + mặc định 44px cho lúc khung vừa dựng, chưa kịp đo fit. */
  --sw-tap: var(--sf-tap, 44px);
  /* Khe tối thiểu icon ↔ chữ. Hẹp hơn mức này thì dấu tiếng Việt chồng 2 tầng
     (Ậ, Ệ) và chữ CJK chạm vào icon — người chơi đọc ra là "chữ đè icon". */
  --sw-gap: 6px;
  /* Khoảng phải cần chừa cho nút X của hộp chi tiết: nút neo absolute
     `right: 8px` và rộng đúng sàn vùng chạm, nên chữ phải lùi bằng CHÍNH bề
     rộng đó + 8px mép + 8px khe. Bám biến ⇒ nút to lên thì lề tự theo. */
  --sw-xclear: 60px;      /* 44px nút X + 8px mép phải + 8px khe */
  /* Số dòng tối đa của nhãn dài. 2 dòng đủ cho es/pt/vi; dòng thứ 3 đẩy nút
     cao vô lý nên cắt + tooltip (shop-textwrap-847.js gắn title). */
  --sw-lines: 2;
}

/* ---------------------------------------------------------------------------
 * (1) TIỆN ÍCH CHUNG — dùng lại cho mọi nhãn để không phải chép luật clamp vào
 *     từng selector. Đặt class lên phần tử là xong.
 *       .sw-wrap2 = xuống tối đa 2 dòng rồi mới cắt (có tooltip).
 *       .sw-grow  = cho khung giãn, tuyệt đối không cắt.
 * ------------------------------------------------------------------------- */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sw-wrap2 {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--sw-lines);
  line-clamp: var(--sw-lines);
  white-space: normal;
  overflow: hidden;
  overflow-wrap: break-word;
  word-break: normal;             /* KHÔNG break-all: chữ CJK đã tự ngắt được */
  text-overflow: clip;            /* "…" làm người chơi tưởng mất chữ */
  min-width: 0;
}
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sw-grow {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  overflow-wrap: break-word;
  min-width: 0;
}

/* ---------------------------------------------------------------------------
 * (2) MENU TRÁI — DANH MỤC `.sf-cat`   ← LỖI 2
 *     779 (≤700px) và 836 (rot90) ép `white-space: nowrap` cho CẢ nút lẫn nhãn
 *     `.sf-cat-l`. Cột rail chỉ rộng 104–110px bố cục nên nhãn dài của es/pt
 *     ("Naves espaciales", "Efeitos visuais") bị đẩy hẳn ra ngoài cột.
 *     Nay: xuống 2 dòng TRONG cột, và nút cao theo sàn vùng chạm.
 * ------------------------------------------------------------------------- */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-cat {
  height: auto;
  min-height: max(46px, var(--sw-tap));
  white-space: normal;
  column-gap: max(var(--sw-gap), 4px);
  align-items: center;
  text-align: left;
}
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe:not([data-nav="rail"]) .sf-cat .sf-cat-l {
  flex: 1 1 auto;
  min-width: 0;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--sw-lines);
  line-clamp: var(--sw-lines);
  white-space: normal;
  overflow: hidden;
  overflow-wrap: break-word;
  word-break: normal;
  text-overflow: clip;
  line-height: 1.25;
}
/* BẮT BUỘC có `:not([data-nav="rail"])`. Ở chế độ rail (điện thoại nằm ngang)
   ui/shopnav.ts biến `.sf-cat-l` thành nhãn CHỈ-CHO-TRÌNH-ĐỌC-MÀN-HÌNH: 1×1px,
   `clip-path: inset(50%)`, `white-space: nowrap`. Đổi white-space/display của ô
   đó là phá đúng công thức visually-hidden (chữ xuống dòng trong hộp 1px) mà
   không ai NHÌN thấy khác gì — chỉ người dùng NVDA/VoiceOver gánh.
   Icon danh mục KHÔNG bao giờ bị chữ bóp: giữ đúng ô vuông của nó. */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-cat .sf-cat-ic,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-cat .sf-cat-ic svg {
  flex: 0 0 auto;
  flex-shrink: 0;
}
body.rot90 #shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-cat {
  height: auto !important;
  min-height: max(38px, var(--sw-tap)) !important;
  white-space: normal !important;
}
body.rot90 #shopframe#shopframe#shopframe#shopframe#shopframe#shopframe:not([data-nav="rail"]) .sf-cat .sf-cat-l {
  white-space: normal !important;
}

/* ---------------------------------------------------------------------------
 * (3) THANH LỌC — NÚT "TẤT CẢ" `.sf-allpill` + Ô SẮP XẾP `.sf-sort`
 *     shopframe.ts đặt `white-space: nowrap` + `> span {text-overflow: ellipsis}`
 *     và khoá `height: var(--fb-h)`. Nút này vẽ bằng khung 9-slice
 *     (btn_grid.webp) nên TUYỆT ĐỐI KHÔNG hạ `--fb-h` — hạ là nắp trái/phải méo
 *     ngay (bài học b729). Giữ nguyên --fb-h, chỉ đổi `height` → `min-height`:
 *     nội dung 2 dòng nới phần GIỮA (vùng `stretch` của border-image), nắp vẫn
 *     đúng tỉ lệ ảnh gốc.
 * ------------------------------------------------------------------------- */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-allpill {
  /* flex: 0 0 auto + sàn min-width ⇒ hàng lọc có chật đến đâu nút này cũng
     không bị bóp xuống dưới ngưỡng chạm (đo được 41,5px trước khi có sàn). */
  flex: 0 0 auto;
  height: auto;
  min-width: max(44px, var(--sw-tap));
  min-height: max(44px, var(--sw-tap));
  white-space: normal;
  line-height: 1.25;
  column-gap: max(var(--sw-gap), 5px);
  align-items: center;
  justify-content: center;
}
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-allpill > span {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--sw-lines);
  line-clamp: var(--sw-lines);
  min-width: 0;
  white-space: normal;
  overflow: hidden;
  overflow-wrap: break-word;
  word-break: normal;
  text-overflow: clip;
}
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-allpill i,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-allpill i svg {
  flex: 0 0 auto;
  flex-shrink: 0;
}

/* `<select>` KHÔNG xuống dòng được (trình duyệt luôn vẽ 1 dòng và tự cắt) ⇒ cách
   duy nhất để "không cắt chữ" là CHO KHUNG RỘNG RA. Trần cũ 240px (779) không đủ
   cho ja "並べ替え: 価格が高い順" hay pt "ORDENAR: MAIS BARATO". Chỉ nới ở khổ
   < 1024px để PC giữ nguyên 240px. */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-sort {
  height: auto;
  min-height: max(44px, var(--sw-tap));
}
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-sort select {
  height: auto;
  min-height: max(44px, var(--sw-tap));
  /* SÀN bề ngang, gói trong `min(100%, …)` để không bao giờ tràn khỏi ô cha.
     Đo được một khổ (360 dọc · tiếng Tây Ban Nha) ô này bị bóp còn 42,4px THẬT.
     KHÔNG cần @media: ở PC --sw-tap = 44px mà select vốn ~240px ⇒ vô hại. */
  min-width: min(100%, max(44px, var(--sw-tap)));
  text-overflow: clip;
}
@media (max-width: 1023.98px) {
  /* flex-basis cũ 175px (779 ≤700px) / 150px (779 ngang) bóp ô sắp xếp TRƯỚC CẢ
     KHI hết chỗ. Cho nó lấy bề ngang theo NỘI DUNG, nhưng không quá 68% thanh
     lọc để nút "TẤT CẢ" luôn còn chỗ đứng. */
  #shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-sort {
    flex: 0 1 auto;
    min-width: 0;
    /* Chừa ĐÚNG chỗ cho nút "TẤT CẢ" (sàn chạm) + khe 12px, thay vì một con số
       % đoán bừa: 68% từng ăn hết chỗ và bóp nút kia còn 41,5px THẬT. */
    max-width: calc(100% - max(44px, var(--sw-tap)) - 12px);
  }
  #shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-sort select {
    width: 100%;
    max-width: 100%;
  }
}
body.rot90 #shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-allpill,
body.rot90 #shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-sort,
body.rot90 #shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-sort select {
  height: auto !important;
  min-height: max(40px, var(--sw-tap)) !important;
}

/* ---------------------------------------------------------------------------
 * (4) NÚT HÀNH ĐỘNG HỘP CHI TIẾT `.sf-cta`   ← LỖI 1
 *     Nhãn dài nhất trong 7 tiếng là cta.short / cta.topup của es/pt
 *     ("FALTAN 1.200 GOLD" · "RECARREGAR 1.200") ⇒ phải cho 2 dòng.
 *     max() ⇒ PC (--sf-tap = 44px) giữ ĐÚNG 48px của 779; fit 0,49 cho 89,8px
 *     bố cục = 44px THẬT. KHÔNG dùng @media (body.rot90 báo 896px ⇒ trượt).
 * ------------------------------------------------------------------------- */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-cta {
  height: auto;
  min-height: max(48px, var(--sw-tap));
  white-space: normal;
  overflow: visible;
  overflow-wrap: break-word;
  word-break: normal;
  text-overflow: clip;
  line-height: 1.3;
  column-gap: max(var(--sw-gap), 6px);
  padding-block: 8px;
  text-align: center;
  align-items: center;
  justify-content: center;
}
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-cta.sell {
  min-height: max(44px, var(--sw-tap));
}
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-cta svg,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-cta img,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-cta i {
  flex: 0 0 auto;
  flex-shrink: 0;
}
body.rot90 #shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-cta {
  height: auto !important;
  min-height: max(44px, var(--sw-tap)) !important;
}

/* ---------------------------------------------------------------------------
 * (5) TÚI ĐỒ — TAB LỌC `.sf-inv-tab` · NÚT SẮP XẾP `.sf-sortbtn` ← LỖI 2
 *     min-WIDTH cũng cần: nhãn ngắn ("ALL", "すべて") chỉ rộng ~35px THẬT dù
 *     chiều cao đã đủ ⇒ vẫn dưới ngưỡng 44×44.
 * ------------------------------------------------------------------------- */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-inv-tab {
  height: auto;
  min-height: max(36px, var(--sw-tap));
  min-width: max(44px, var(--sw-tap));
  white-space: normal;
  overflow-wrap: break-word;
  word-break: normal;
  text-overflow: clip;
  line-height: 1.25;
  padding-block: 4px;
}
body.rot90 #shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-inv-tab {
  height: auto !important;
  min-height: max(36px, var(--sw-tap)) !important;
  min-width: max(44px, var(--sw-tap)) !important;
}
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-sortbtn,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-autoeq {
  height: auto;
  min-height: max(44px, var(--sw-tap));
  white-space: normal;
  overflow-wrap: break-word;
  word-break: normal;
  text-overflow: clip;
  line-height: 1.25;
  column-gap: max(var(--sw-gap), 6px);
  padding-block: 6px;
  align-items: center;
}
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-sortbtn svg,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-autoeq svg {
  flex: 0 0 auto;
  flex-shrink: 0;
}

/* Tên món trong ô túi đồ + tiêu đề kho.
   PHẢI kèm `.sf-inv-grid.list`: shopframe.ts đặt `.sf-cell-n {display:none}` và
   chỉ bật lại `display:block` ở KIỂU DANH SÁCH. Viết `.sf-cell-n` trơ ở 6 lần id
   là BẬT HIỆN tên trong MỌI ô của kiểu lưới — đổi hẳn giao diện túi đồ, việc
   không ai yêu cầu. */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-inv-grid.list .sf-cell-n,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-inv-title {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--sw-lines);
  line-clamp: var(--sw-lines);
  min-width: 0;
  white-space: normal;
  overflow: hidden;
  overflow-wrap: break-word;
  word-break: normal;
  text-overflow: clip;
  line-height: 1.25;
}

/* Nhãn ĐANG DÙNG trong ô túi đồ ← LỖI 5. Nó absolute + translateX(-50%) nên
   PHẢI có trần bề ngang, không thì tràn đối xứng ra hai bên ô. */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-cell-tag {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--sw-lines);
  line-clamp: var(--sw-lines);
  max-width: calc(100% - 10px);
  white-space: normal;
  overflow: hidden;
  overflow-wrap: break-word;
  word-break: normal;
  text-overflow: clip;
  text-align: center;
  line-height: 1.2;
}
/* Nhãn khoá phủ lên ô (skin chưa mở) — cho wrap, không cắt. */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-lock {
  max-width: 100%;
  white-space: normal;
  overflow-wrap: break-word;
  word-break: normal;
  text-overflow: clip;
}

/* ---------------------------------------------------------------------------
 * (6) MENU NGỮ CẢNH VẬT PHẨM `.sf-itemmenu` (Trang bị / Bán / Xoá)  ← LỖI 4
 * ------------------------------------------------------------------------- */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-im-act {
  height: auto;
  min-height: max(44px, var(--sw-tap));
  white-space: normal;
  overflow-wrap: break-word;
  word-break: normal;
  text-overflow: clip;
  line-height: 1.3;
  column-gap: max(var(--sw-gap), 8px);
  padding-block: 6px;
  align-items: center;
}
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-im-act svg,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-im-act i {
  flex: 0 0 auto;
  flex-shrink: 0;
}
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-im-name {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--sw-lines);
  line-clamp: var(--sw-lines);
  max-width: 100%;
  white-space: normal;
  overflow: hidden;
  overflow-wrap: break-word;
  word-break: normal;
  text-overflow: clip;
  line-height: 1.3;
}

/* ---------------------------------------------------------------------------
 * (7) THANH TRÊN — CHỈ NÚT + TIÊU ĐỀ (bố cục thanh là của worker 1/840)  ← LỖI 3
 * ------------------------------------------------------------------------- */
/* HIT-SLOP, KHÔNG phình hộp. Bản đầu của lớp này ép thẳng
   `min-width/min-height: var(--sw-tap)` lên bốn nút icon và ĐO RA HAI HẬU QUẢ:
     • `.sf-plus` (nút + nạp tiền) nằm trong khay ví CAO CỐ ĐỊNH 46px và rộng
       `max-width: 50% - 4px`. Ép nó thành 160×160px bố cục ở khổ 360 dọc làm nó
       tràn khỏi khay, và đo bằng `elementFromPoint` thì TÂM NÚT trả về `.sf-wal`
       chứ không phải nút ⇒ to ra mà bấm KHÔNG trúng: tệ hơn lúc đầu.
     • `.sf-dlg-x` phình ra đè lên tên món (đo được: đuôi "…Suprema 4" nằm ngay
       dưới nút X), buộc phải chừa lề cho nó, mà lề đó lại kéo phồng huy hiệu
       MIỄN PHÍ (huy hiệu là `width: max-content` + nền gradient).
   Cách đúng là GIỮ NGUYÊN hộp hiển thị và nới VÙNG BẮT CHẠM bằng một lớp phủ
   trong suốt — đây cũng là khuôn mẫu dự án đã dùng cho nút X trước đây. Sự kiện
   nổi bọt từ `::after` lên chính nút nên không cần thêm JS.
   `position: relative !important` vì shop-responsive-779.css ghim
   `position: static !important` cho `.sf-plus`. `relative` không đổi bố cục. */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-plus,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-closebtn,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-dlg-x,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-icobtn {
  position: relative !important;
  flex-shrink: 0;
}
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-plus::after,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-closebtn::after,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-dlg-x::after,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-icobtn::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: max(100%, var(--sw-tap));
  height: max(100%, var(--sw-tap));
  /* z-index để lớp phủ nằm TRÊN nền anh em bên cạnh; không nền, không viền nên
     người chơi không thấy gì đổi. */
  z-index: 2;
}

/* ---------------------------------------------------------------------------
 * (7b) SỬA 2026-10-08 — THANH VÍ: nút "+" tụt xuống dưới khay và BẤM KHÔNG TRÚNG
 * ---------------------------------------------------------------------------
 * ĐO ĐƯỢC (scripts/_qa_wallet_plus_w1.py, chuỗi <link> CSS thật của index.html):
 *
 *   hậu quả 1 — nút "+" nằm NGOÀI khay ví, đè lên hàng bộ lọc bên dưới
 *     360×640 / 375×667 / 414×896 / 390×844 dọc và PC 1366×768: hộp nút tụt
 *     xuống đúng 22px (PC 23px) dưới đáy `.sf-wal`; ở `body.rot90` lệch 17px
 *     theo trục ngang. Gỡ riêng lớp 847 ⇒ tràn 0px ở mọi khổ (lượt đối chứng).
 *   hậu quả 2 — nút "+" của ví THỨ HAI chết hẳn
 *     % diện tích phản hồi `elementFromPoint`: 0,0% ở 360/375, 4,3% ở 390,
 *     12,9% ở 414, 50–69% ở rot90; tâm nút trả về `button#sf-close.sf-closebtn`.
 *   hậu quả 3 — khay ví cuộn ngang
 *     `.sf-wallets` scrollWidth − clientWidth = +58px (360) … +22px (rot90).
 *
 * VÌ SAO. Hai nguyên nhân ĐỘC LẬP, phải vá cả hai:
 *
 *  (1) `position: relative` ở trên LÀM SỐNG LẠI hai lệch cũ. Ghi chú gốc nói
 *      "relative không đổi bố cục" — điều đó chỉ đúng khi phần tử không có
 *      `top/right/bottom/left`. Nhưng shopframe.ts:3241 đặt cho bản skin
 *      `.sf-wal .sf-plus { position: absolute; right: 4px; top: 50%;
 *      transform: translateY(-50%) }` và shopframe.ts:2878 đặt cho
 *      `.sf-inv-add { position: absolute; right: 8px; top: 50% }`.
 *      779/780 huỷ chúng bằng `position: static !important` + `transform:
 *      none !important` — `static` thì `top/right` BỊ BỎ QUA nên vô hại. Đổi
 *      sang `relative` thì `top: 50%` sống lại (= 50% chiều cao khay = 22px)
 *      mà `transform: translateY(-50%)` bù ngược thì VẪN bị 779 tắt ⇒ nút
 *      trôi thẳng xuống nửa khay. Khoá `inset: auto` ⇒ `relative` quay về
 *      đúng nghĩa "chỉ để làm khối chứa cho ::after", không dịch 1px nào.
 *
 *  (2) `--sw-tap` = `--sf-tap` = clamp(44px, 44px / --sf-fit, 160px). Phép chia
 *      cho `--sf-fit` là để bù ngược `transform: scale()` của `.sf-panel` —
 *      mà shop-responsive-779.css:4 đã bỏ `transform` từ lâu, nên px bố cục ĐÃ
 *      LÀ px thật. Ở 360 `--sf-fit` = 0,2750 ⇒ lớp phủ phồng thành 160×160:
 *      nút X (44px) có lớp phủ thò ra 58px mỗi bên, trùm hẳn lên nút "+" của ví
 *      kim cương bên cạnh (X đứng SAU trong DOM, cùng z-index: 2 nên thắng) và
 *      chính 58px đó là bề rộng cuộn ngang thừa của khay ví.
 *      Chốt trần 44px cho hai nút Ở TRONG KHAY VÍ: 44px đã là sàn WCAG 2.5.5,
 *      không cần hơn. CỐ Ý không đụng `--sw-tap` chung (`.sf-cta`, `.sf-dlg-x`,
 *      `.sf-icobtn` có lớp khác đang chốt riêng) để không nới phạm vi bản vá.
 *
 * Đo lại sau vá: scripts/_qa_wallet_plus_w1.py 116/118 PASS (2 FAIL còn lại là
 * phát hiện kèm, CÓ TRƯỚC bản vá: ô ví cắt âm thầm 2px với chuỗi 9 ký tự
 * "999,9 mil" của es/pt ở 360×640 — geodiff chứng minh giống hệt bản chưa vá).
 * Hồi quy hình học scripts/_qa_wallet_plus_geodiff_w1.py: 446 phần tử/khổ,
 * khổ DỌC và PC 1366×768 KHÔNG đổi 1px nào ngoài chính `.sf-plus`. */
/* KHÔNG cần !important: hai lệch gốc nằm ở khối <style> của shopframe.ts
   (1 id + 2 class) còn selector này 6 id ⇒ đã thắng. Giữ sạch !important cũng
   là điều kiện của cổng B8 trong src/ui/shoptextwrap.test.ts. */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-plus {
  inset: auto;
}
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-wallets .sf-plus::after,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-wallets .sf-closebtn::after {
  width: max(100%, 44px);
  height: max(100%, 44px);
}
/* rot90 (điện thoại khoá dọc, game tự xoay 90°): mobile-repair-830.css:115-116
   ép nút + và nút X còn 34×34 để thanh trên mỏng 38px. Lúc đó lớp phủ phải nở
   5px mỗi bên để đủ 44 — mà khe flex giữa ô ví và nút X chỉ 6px, nên hai lớp
   phủ CHỒNG NHAU 4px và nút X (đứng SAU trong DOM, cùng z-index: 2) ăn mất
   2px của nút +: đo được 44×42. Cách đúng KHÔNG phải nới khe (đẩy nút X tràn
   mép khung ở khổ 360) mà là cho nút ĐÚNG 44px THẬT theo CHIỀU DỌC THANH —
   chiều đó dư chỗ: hai ô ví là `flex: 1 1 0` dài 287px, nhường 10px mỗi nút
   không hề chật. Bề DÀY thanh giữ nguyên 34px, không cao thêm 1px nào. */
body.rot90 #shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-wallets .sf-plus {
  width: 44px !important;
  flex-basis: 44px !important;
}
body.rot90 #shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-wallets .sf-closebtn {
  width: 44px !important;
  flex: 0 0 44px !important;
}
/* Tiêu đề cửa hàng: shopframe.ts và mobile-shop-bag-787.css đang đặt
   `display: none` cho nó ở một số khổ (wordmark đã nằm trong ảnh logo). Vì vậy ở
   đây TUYỆT ĐỐI không đặt `display` — chỉ mở khoá xuống dòng. Nhãn này cũng chỉ
   là một từ ("LOJA" · "CỬA HÀNG" · "ショップ") nên không cần clamp 2 dòng. */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-title {
  white-space: normal;
  overflow-wrap: break-word;
  word-break: normal;
  hyphens: auto;          /* buộc phải ngắt thì còn dấu gạch nối để đọc được */
  text-overflow: clip;
  line-height: 1.2;
}
/* Ví: số tiền đã được util/moneyfmt.ts rút gọn theo chữ từng nước VÀ đã có
   tooltip số thật ⇒ CỐ Ý KHÔNG nới khung ví (nới là đẩy nút X ra khỏi thanh).
   Chỉ khoá icon đồng tiền lại để số dài không bóp méo nó. */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-wal-ic,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-wal-ic svg {
  flex: 0 0 auto;
  flex-shrink: 0;
}
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-wal {
  column-gap: max(var(--sw-gap), 4px);
}

/* ---------------------------------------------------------------------------
 * (8) TIÊU ĐỀ NHÓM + HỘP CHI TIẾT — chỉ cần wrap, có chỗ để giãn nên không clamp.
 *     (`.sf-grp` bản thân nó là của worker 1 — ở đây chỉ hai con `-n` / `-hint`.)
 * ------------------------------------------------------------------------- */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-grp-n,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-grp-hint,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-d-name,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-d-desc,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-d-freetag {
  white-space: normal;
  overflow: visible;
  overflow-wrap: break-word;
  word-break: normal;
  text-overflow: clip;
  min-width: 0;
}
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-grp-n {
  flex: 0 0 auto;
}

/* Nút X của hộp chi tiết neo absolute ĐÈ LÊN vùng chữ chứ không đẩy chữ. 779
   chừa 24px — thiếu ngay cả khi nút còn 44px (44 + 8 mép = 52), và thiếu hẳn khi
   nút to lên. KHÔNG áp cho `.sf-d-freetag`: huy hiệu đó là `width: max-content`
   + nền gradient nên `padding-right` kéo phồng nó thành một thanh vàng dài (đã
   nhìn thấy trong ảnh đối chứng); nó lại ngắn và canh TRÁI nên không với tới
   nút X ở bên phải. */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-d-name,
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-d-rar {
  padding-right: var(--sw-xclear);
}


/* ---------------------------------------------------------------------------
 * (9) CHỐT AN TOÀN — không một "…" nào sống sót ở khổ < 1024px.
 *     `:is()` để danh sách dài không phình specificity ngoài ý muốn. Chừa
 *     `<select>`/`<option>`/ô nhập (trình duyệt tự lo) và chừa thẻ sản phẩm.
 * ------------------------------------------------------------------------- */
@media (max-width: 1023.98px) {
  #shopframe#shopframe#shopframe#shopframe#shopframe#shopframe
    :is(.sf-cat-l, .sf-cell-n, .sf-cell-tag, .sf-inv-title, .sf-inv-tab,
        .sf-cta, .sf-sortbtn, .sf-autoeq, .sf-allpill, .sf-allpill > span,
        .sf-im-name, .sf-im-act, .sf-title, .sf-grp-n, .sf-grp-hint,
        .sf-d-name, .sf-d-desc, .sf-lock) {
    text-overflow: clip;
  }
}

/* ---------------------------------------------------------------------------
 * (10) TOOLTIP KHI VẪN TRÀN SAU 2 DÒNG
 *      shop-textwrap-847.js gắn `title` + `data-sw-clamped="1"`. Gạch chân mờ
 *      để người chơi biết "còn nữa, rê chuột / giữ ngón vào xem". KHÔNG đổi
 *      con trỏ thành `help` trên phần tử bấm được (làm tưởng không bấm được).
 * ------------------------------------------------------------------------- */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe [data-sw-clamped="1"] {
  text-decoration: underline dotted rgba(234, 242, 255, .35);
  text-underline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  #shopframe#shopframe#shopframe#shopframe#shopframe#shopframe [data-sw-clamped="1"] {
    text-decoration: none;
  }
}
/* =============================================================================
 * (862) NÚT X ĐÓNG CỬA HÀNG — Ô VUÔNG TRẮNG Ở GÓC PHẢI-TRÊN HÀNG VÍ
 * -----------------------------------------------------------------------------
 * KHÔNG phải ảnh 404. `shop-responsive-779.css` (dòng 17–18) vẽ HAI NÉT của chữ
 * X bằng chính `::before` / `::after` của `#sf-close`:
 *     ::before, ::after { left:12px; top:21px; width:18px; height:2px;
 *                         background:#e4edff; transform:rotate(±45deg) }
 * Khối hit-slop ở phía trên trong CHÍNH TỆP NÀY nạp sau và thắng (6 lần id) nên
 * nó biến `::after` thành lớp phủ `max(100%, var(--sw-tap))` = 44×44 với
 * `z-index: 2` — nhưng KHÔNG khai lại `background`, nên màu nét chữ #e4edff của
 * 779 còn nguyên và nở ra thành MẢNG ĐẶC phủ kín nút, nằm trên cả nét ::before.
 * Đo trên LIVE (scripts/_qa_w2_shopclose_white.py, 4 khổ màn hình):
 *     ::after  background-color = rgb(228,237,255)  44×44  (nút 44×44)  ⇒ trắng.
 *
 * Vá: trả `::after` về TRONG SUỐT (giữ nguyên vùng chạm 44px, không đổi bố cục)
 * và vẽ TRỌN chữ X bằng `::before` (hai gradient chéo) vì chỉ còn một pseudo.
 * Chỉ đụng nút đóng — không chạm hàng ví, ô vàng/ruby, nút "+" hay lưới hàng.
 * ========================================================================== */
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-closebtn::after {
  background: none transparent;
  border: 0;
  box-shadow: none;
}
#shopframe#shopframe#shopframe#shopframe#shopframe#shopframe .sf-closebtn::before {
  left: 50%;
  top: 50%;
  width: 18px;
  height: 18px;
  transform: translate(-50%, -50%);
  background-color: transparent;
  background-repeat: no-repeat;
  background-image:
    linear-gradient(45deg,
      transparent calc(50% - 1px), #e4edff calc(50% - 1px),
      #e4edff calc(50% + 1px), transparent calc(50% + 1px)),
    linear-gradient(-45deg,
      transparent calc(50% - 1px), #e4edff calc(50% - 1px),
      #e4edff calc(50% + 1px), transparent calc(50% + 1px));
  z-index: 1;
  pointer-events: none;
}
