/* ==========================================================================
   Xijie Floral Wholesale — 主样式
   设计: 干净的 B2B 风格, 深绿(植物) + 米白 + 金色点缀
   ========================================================================== */
:root{
  --green-900:#12301f; --green-800:#1a4530; --green-700:#226042;
  --green-600:#2d7a54; --green-500:#3d9468; --green-100:#e6f2eb;
  --sand-50:#faf8f3; --sand-100:#f3efe6; --sand-200:#e5ddcd;
  --gold:#c9a961; --gold-dark:#a88a45;
  --ink:#1c2420; --ink-2:#485349; --ink-3:#7a857c;
  --line:#e2e6e1; --white:#fff; --red:#c0392b;
  /* ★ 主题派生色 —— 换主题时一起覆盖，消除 main.css 里的硬编码色值 */
  --green-glow:#2f6b4c;         /* hero 渐变右端 */
  --green-on-dark:#e8f1ec;      /* 深绿底上的浅文字 */
  --green-on-dark-2:#a8c4b6;    /* 深绿底上次浅文字 */
  --gold-rgba:201,169,97;       /* 金色 rgba 用的 RGB 三值 */
  --gold-on:#3d3116;            /* 金底上的深字 */
  --gold-on-2:#2a2415;          /* 金底上更深的字（徽章等） */
  --green-arrow:#b9cfc2;        /* 顶栏下拉箭头的浅色调 */
  --radius:8px; --radius-lg:14px;
  --shadow-sm:0 1px 3px rgba(18,48,31,.06);
  --shadow:0 4px 16px rgba(18,48,31,.08);
  --shadow-lg:0 12px 40px rgba(18,48,31,.14);
  --maxw:1280px;

/* ══════════════════════════════════════════════════════════════════
   ★★★ 设计令牌层（2026-10-15 张总："界面非常不和谐，有没有办法一次性解决"）

   为什么建这一层：改版前每个板块各自写死自己的 padding / 栅格列数 /
   卡片图片比例 / 色块，于是「宽窄不一、色块乱序、比例参差」——
   而且**每加一个新板块就会多冒出一处不协调**。

   这一层的唯一目的：**让"不和谐"变成不可能发生的错误**。

   ⚠️ 本层所有数值都是**从现网实测值反推归一**的，不是新设计的 ——
      所以「令牌化」这一步本身像素级零变化，只有后续显式改比例才会动视觉。
      （曾把 --sec-pad-2 写成 88px，而现网 .sec 是 72px；.sec 被 28 个页面
        复用，一绑定就是全站每段 +32px。故此处必须等于现网值。）

   三条硬规矩，任何板块都不得违反：
     1. 段内距只能取 --sec-pad-1/2/3 三档，不得自己写数字；
     2. 卡片图比例一律用 --ratio-* 三个**按角色分档**的令牌，
        不许自定义长宽比，也**不许用单一比例**（1/1 与 4/5 数学上互斥）；
     3. 深色块只用 --surface-dark（即 var(--green-900)），
        它跟随后台主题（植物绿/商务蓝/活力橙…）自动变，**不许写死色值**。
   ══════════════════════════════════════════════════════════════════ */

  /* ── 段内距三档 ── 数值 = 改版前现网实测值，故令牌化本身零像素变化 */
  --sec-pad-1:48px;    /* .sec-tight 紧凑段（trial / cta） */
  --sec-pad-2:72px;    /* .sec 标准段，全站 28 页共用这一档 */
  --sec-pad-3:112px;   /* 大段（cats / cert 等需要呼吸感的） */
  /* 视频段原本是内联 padding:56px 0，属第 4 个值 —— 已删除，
     让它回到 --sec-pad-2，杜绝"内联覆盖令牌"这类静默失效。 */

  /* ── 三个语义表面 ── 换主题时随之变，板块不得引用具体色值 */
  --surface-0:var(--white);              /* 纯白段 */
  --surface-1:var(--sand-50);            /* 浅沙段（与白色交替用） */
  --surface-dark:var(--green-900);       /* 深色段 */

  /* ── 图片比例按角色分三档 ──
     这三档不是审美选择，是**源图实测结果**（PIL 量过全部图片）：
       static/img/home/cat-*.jpg      800x800  = 1:1
       static/img/home/factory-*.jpg  1600x900 = 16:9
       static/img/home/scene-*.jpg    1600x900 = 16:9
       static/media/sku 下的 *.jpg  53.7% 是 3:4 竖构图，32.7% 是 0.6~0.9
       ★ 2026-10-21：上面原写的那串「斜杠＋星号」，在注释里会**又开一层注释**，
         导致这条注释永远合不上（实测差 1 个）⇒ 已改写成「下的 星号.jpg」。
     ⇒ 用单一比例（比如 16/10）会保证每张图都被裁：
       1:1 源进 16:10 框 ⇒ 每边裁 18.75%，仿真树树冠和花盆同时被切。
     所以必须按"图的角色"分档，每一档都对上自己的源图比例。 */
  --ratio-landscape:16/9;  /* 横构图实拍：hero / factory / scene / why / video */
  --ratio-square:1/1;      /* 方形：cat-*.jpg 源图零裁切 */
  --ratio-tall:4/5;        /* 竖构图商品图：3:4 主流源，每边只裁 3.1% */

  /* ── 卡片几何 ── */
  --card-pad:24px;         /* 宽卡内边距 */
  --card-pad-sm:16px;      /* 窄卡内边距（cert 列宽只有 150px，24 会挤到只剩 102px 文字宽） */
  --card-radius:var(--radius-lg);  /* 卡片唯一圆角 = 14px */
  --grid-gap:24px;         /* 栅格间距基线（全站统一；移动端另有更小的例外，见 .pcards 的媒体查询） */
}

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0; font-family:'Inter','Segoe UI','Helvetica Neue',Arial,
    'Noto Sans SC','PingFang SC','Microsoft YaHei',sans-serif;
  color:var(--ink); background:var(--white); line-height:1.6; font-size:var(--fs-15);
}
html[dir="rtl"] body{font-family:'Noto Sans Arabic','Segoe UI',Tahoma,Arial,sans-serif}
img{max-width:100%; height:auto; display:block}
a{color:inherit; text-decoration:none}
button,input,select,textarea{font:inherit; color:inherit}
h1,h2,h3,h4{margin:0 0 .5em; line-height:1.25; font-weight:700; letter-spacing:-.01em}
h1{font-size:2.5rem} h2{font-size:1.75rem} h3{font-size:1.15rem}
p{margin:0 0 1em}
.wrap{max-width:var(--maxw); margin:0 auto; padding:0 24px}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0)}

/* ---------- 顶部条 ---------- */
/* 语言建议条 —— 只在 IP 判定国家与当前语言不一致时出现, 12 秒自动淡出 */
.lang-tip{background:var(--gold); color:var(--gold-on); font-size:var(--fs-sm); padding:6px 0;
  transition:opacity .4s ease, max-height .4s ease; overflow:hidden; max-height:60px}
.lang-tip.out{opacity:0; max-height:0; padding:0}
.lang-tip .wrap{display:flex; justify-content:space-between; align-items:center; gap:14px; flex-wrap:wrap}
.lang-tip-txt b{font-weight:600}
.lang-tip-act{display:flex; align-items:center; gap:10px}
.lang-tip-go{background:var(--green-900); color:#fff; text-decoration:none; font-weight:500;
  padding:5px 13px; border-radius:20px; white-space:nowrap}
.lang-tip-go:hover{background:var(--green-700)}
.lang-tip-x{background:none; border:none; color:var(--gold-on); font-size:var(--fs-20); line-height:1;
  cursor:pointer; padding:0 4px; opacity:.6}
.lang-tip-x:hover{opacity:1}
@media(max-width:640px){.lang-tip .wrap{justify-content:center; text-align:center}}
/* ---------- 节日祝福条 ---------- */
/* ★ 只在节日当天出现（判据在 app.festival_banner()，模板只判 FESTIVAL 是否为 None）。
   位置在顶栏**之上**，全站一条。高度压到 ~32px、颜色只用主色深档 + 点缀色文字，
   **不抢 CTA 的视线**，而且随时可关。 */
.fest-bar{background:var(--green-900); color:var(--green-on-dark); font-size:var(--fs-sm); padding:5px 0;
  transition:opacity .35s ease, max-height .35s ease; overflow:hidden; max-height:44px}
/* 收起用 max-height 过渡（照 .lang-tip.out），别让页面布局突然跳一下 */
.fest-bar.out{opacity:0; max-height:0; padding:0}
.fest-bar .wrap{display:flex; align-items:center; gap:10px}
.fest-box{flex:1 1 auto; overflow:hidden; white-space:nowrap}
/* 无缝循环：文案在模板里放两份，位移 -50% 正好一圈。
   用 transform（走 GPU）而不是 left —— 微信内置浏览器/老安卓上 left 会掉帧。 */
.fest-run{display:inline-block; white-space:nowrap; will-change:transform;
  animation:xj-fest-scroll 22s linear infinite}
.fest-sep{opacity:.5; padding:0 8px}
@keyframes xj-fest-scroll{from{transform:translateX(0)} to{transform:translateX(-50%)}}
.fest-x{background:none; border:none; color:var(--green-on-dark); font-size:var(--fs-19); line-height:1;
  cursor:pointer; padding:0 4px; opacity:.65; flex:0 0 auto}
.fest-x:hover{opacity:1}
/* ★ RTL（阿拉伯语）：**不滚动、居中静态**。
   CSS 动画是物理方向，镜像过来阿语长句读起来更费劲，不如静态。 */
html[dir="rtl"] .fest-run{animation:none}
html[dir="rtl"] .fest-dup{display:none}
html[dir="rtl"] .fest-box{text-align:center}
/* ★ 无障碍：系统里开了「减少动态效果」就必须停 ——
   这是全站第一条 prefers-reduced-motion（原来一条都没有），顺手把这道短板补上。 */
@media (prefers-reduced-motion: reduce){
  .fest-run{animation:none}
  .fest-dup{display:none}
  .fest-box{text-align:center}
}
@media(max-width:820px){
  .fest-bar{font-size:var(--fs-xs); padding:4px 0; max-height:36px}
}
.topbar{background:var(--green-900); color:#cfe0d6; font-size:var(--fs-sm); padding:7px 0}
.topbar .wrap{display:flex; justify-content:space-between; align-items:center; gap:16px; flex-wrap:wrap}
.topbar a:hover{color:#fff}
.topbar-l{display:flex; gap:18px; align-items:center; flex-wrap:wrap}
.topbar-r{display:flex; gap:14px; align-items:center}
/* ★ 2026-10-16（第 1297 批 · 桌面按族收口；规格 §4.4：指针目标 24×24）：
   官网顶栏右侧那一串（索取报价 / 批发 / 免费样品 / 🔑 登录）是**独立入口**，
   不是句内链接（句内那种才适用 WCAG 2.2 AA 的「句内目标」明文豁免）⇒ 真改：
   它们以前只有 52×21，矮 3px。一处顶 9 页。
   ★ 这里用 `min-height` 而**不是**垂直 `padding`：`.topbar-r` 是 flex，它的子项被
     blockify ⇒ 行内元素那条「垂直 padding 不进 line-height」的经验在这里**不成立**，
     两者都会让顶栏长高同样的 3px；min-height 更好读。
   ★ 窄屏那份 `.topbar-r .tb-link{display:none}` 权重更高（0,2,0 对 0,1,1）且源顺序在后
     ⇒ 手机端照旧按七专家评审的精简口径隐藏，一个像素不变。 */
.topbar-r a{display:inline-flex; align-items:center; min-height:24px}
/* ★ 顶栏里的 select 必须显式改底色/文字色。
   否则浏览器默认渲染成白底, 又因为全局 color:inherit 继承成浅色字 -> 白底白字完全看不见
   (这是截图验收时发现的 P0 问题) */
.topbar select{
  appearance:none; -webkit-appearance:none;
  background:rgba(255,255,255,.10);
  color:var(--green-on-dark);
  border:1px solid rgba(255,255,255,.26);
  border-radius:6px;
  padding:4px 26px 4px 10px;
  font-size:var(--fs-xs); line-height:1.35;
  cursor:pointer;
  background-image:linear-gradient(45deg,transparent 50%,var(--green-arrow) 50%),
                   linear-gradient(135deg,var(--green-arrow) 50%,transparent 50%);
  background-position:calc(100% - 14px) 52%, calc(100% - 9px) 52%;
  background-size:5px 5px, 5px 5px;
  background-repeat:no-repeat;
  transition:background-color .15s, border-color .15s;
}
.topbar select:hover{background-color:rgba(255,255,255,.18); border-color:rgba(255,255,255,.42)}
.topbar select:focus{outline:none; border-color:var(--gold); box-shadow:0 0 0 2px rgba(var(--gold-rgba),.28)}
.topbar select option{background:#fff; color:#1d2b23}

/* ---------- 头部 ---------- */
.hdr{background:var(--white); border-bottom:1px solid var(--line); position:sticky; top:0; z-index:50}
.hdr .wrap{display:flex; align-items:center; gap:28px; min-height:70px; height:auto; padding-block:8px}
.logo{display:flex; align-items:center; gap:10px; font-weight:800; font-size:1.25rem; color:var(--green-800); white-space:nowrap}
.logo-mark{width:34px;height:34px;border-radius:50%;flex:none;display:block;
  object-fit:contain;background:none}
/* 2026-10-18: the brand mark is now a PNG (tools/_logo_build.py). Kept the old size/rounding so the header layout does not move. */
.logo small{display:block;font-size:var(--fs-2xs);font-weight:500;color:var(--ink-3);letter-spacing:.08em;text-transform:uppercase}
.nav{display:flex; gap:4px; margin-inline-start:auto; align-items:center}
.nav a{padding:8px 14px; border-radius:6px; font-size:var(--fs-md); font-weight:500; color:var(--ink-2); white-space:nowrap}
.nav a:hover{background:var(--green-100); color:var(--green-800)}
.nav a.on{background:var(--green-700); color:#fff; font-weight:650; padding:10px 18px}
.nav a.on:hover{background:var(--green-800); color:#fff}
.nav .cta{background:var(--green-700); color:#fff; padding:10px 18px; margin-inline-start:8px}
.nav .cta:hover{background:var(--green-800); color:#fff}
/* ★★ 2026-10-17（张总批注「点击哪里的板块就 背景块到哪里」）：
   ① 当前页给**同款绿底块** —— 原来 `.on` 只是变绿加粗，而"块"只出现在常驻的「联系」上，
      于是不管点哪个板块，看起来都像「联系」被选中；
   ② 因此「联系」改**描边款**，与"你在这"区分开。
   ★ 描边为什么用 `inset box-shadow` 而不是 `border`：border 会把盒子撑高 3px，
     而且下面 1340 / 1180 两处响应式里都有 `.cta` 的 padding 覆盖，
     用 border 就得跟着改好几处；inset 阴影不占布局，**盒子尺寸一点不变**。 */
.nav .cta-ghost{background:transparent; color:var(--green-700);
  box-shadow:inset 0 0 0 1.5px var(--green-700)}
.nav .cta-ghost:hover{background:var(--green-700); color:#fff; box-shadow:none}
.burger{display:none; background:none; border:1px solid var(--line); border-radius:6px; padding:8px 11px; cursor:pointer; margin-inline-start:auto}
.hdr-tools{display:flex;gap:10px;align-items:center}
.sel{border:1px solid var(--line); border-radius:6px; padding:7px 9px; font-size:var(--fs-sm); background:var(--white); cursor:pointer}

/* ---------- 按钮 ---------- */
.btn{display:inline-flex; align-items:center; justify-content:center; gap:8px;
  padding:12px 24px; border-radius:var(--radius); font-weight:600; font-size:var(--fs-15);
  border:1.5px solid transparent; cursor:pointer; transition:.18s; text-align:center; line-height:1;
  /* ★ 2026-10-15 9 门外语窄屏：俄/印尼等语种的长单词（如"Получить коммерческое
     предложение"）在 375px 排不下，按钮被 inline-flex 的 nowrap 撑出整页
     横向滚动条。按钮文字允许折行，且不让子元素撑破父框。 */
  max-width:100%; min-width:0; overflow-wrap:break-word; white-space:normal}
.btn > *{min-width:0}
.btn-primary{background:var(--green-700); color:#fff}
.btn-primary:hover{background:var(--green-800); transform:translateY(-1px); box-shadow:var(--shadow)}
.btn-outline{border-color:var(--green-700); color:var(--green-700); background:transparent}
.btn-outline:hover{background:var(--green-100)}
.btn-gold{background:var(--gold); color:var(--gold-on-2)}
.btn-gold:hover{background:var(--gold-dark); color:#fff}
.btn-ghost{color:var(--ink-2); border-color:var(--line); background:var(--white)}
.btn-ghost:hover{border-color:var(--green-600); color:var(--green-700)}
.btn-sm{padding:8px 15px; font-size:var(--fs-sm)}
.btn-lg{padding:15px 32px; font-size:var(--fs-16)}
.btn-block{width:100%}
.btn:disabled{opacity:.5; cursor:not-allowed; transform:none}

/* ---------- 通用块 ---------- */
/* ★ 2026-10-15 视觉统一：这三个值全部改吃 --sec-pad-* 令牌。
   令牌数值刻意等于改版前的现网值，所以这一步本身像素级零变化 ——
   目的是"从此以后只有一个地方能改段内距"，而不是现在就变样。
   注意 --sec-pad-2 是 72px 不是 88px：.sec 被全站 28 个页面复用，
   绑错档位就是全站每段 +32px。 */
.sec{padding:var(--sec-pad-2) 0}
.sec-tight{padding:var(--sec-pad-1) 0}
.sec-sand{background:var(--surface-1)}
.sec-green{background:var(--surface-dark); color:var(--green-on-dark)}
/* ★★ 2026-10-15 自检第 4 遍抓到的**致命漏洞**（改完模板才发现）：
   模板已把写死的 `sec sec-green` / `sec sec-sand` 全改成
   `sec sec-{{ sec_surface('xxx') }}`，而 section_surface() 的取值是
   plain / sand / **dark** —— 但 CSS 里只有 .sec-sand 和 .sec-green，
   **.sec-plain 与 .sec-dark 从来没定义过**。
   后果：深色段渲染成 class="sec sec-dark"，浏览器找不到规则 ⇒
   深绿背景整块失效、整段退回白底，而金色 eyebrow 和白字 h3 留在深色假设上
   ⇒ 白底白字，肉眼直接看不见内容。
   —— 这就是"数据库里有数据 ≠ 落地"的典型：值灌对了，**消费端没接线**。
   修法：把三档色块收敛成 .sec-plain/.sec-sand/.sec-dark 三个真类，
   旧的 .sec-green/.sec-sand 保留（其余 27 个页面还在用，不能删）。
   —— 深色档用 :is() 一次性带上原来 .sec-green 的所有子选择器规则，
      以后调深色段只需改这一处，plain/sand/dark 三档永远同步。 */
.sec-plain{background:var(--surface-0)}
.sec-dark{background:var(--surface-dark); color:var(--green-on-dark)}
:is(.sec-dark,.sec-green) h2{color:var(--green-on-dark)}
:is(.sec-dark,.sec-green) .sec-hd p{color:var(--green-on-dark-2)}
:is(.sec-dark,.sec-green) .sec-hd .eyebrow{color:var(--gold)}

/* #fff 是这行里仅存的硬编码白色；.sec-green 自身的 color 已经是 --green-on-dark，
   只有 h2 漏了。改后三套主题对比度都达标（14.3/14.5/11.6:1），且删掉一处硬编码。 */
.sec-green h2{color:var(--green-on-dark)}
.sec-hd{text-align:center; max-width:720px; margin:0 auto 44px}
.sec-hd .eyebrow{font-size:var(--fs-xs); letter-spacing:.14em; text-transform:uppercase;
  color:var(--gold-dark); font-weight:700; margin-bottom:10px}
.sec-hd p{color:var(--ink-2); font-size:var(--fs-16)}
.sec-green .sec-hd p{color:var(--green-on-dark-2)}
/* ★ 2026-10-15：深色段上的 eyebrow 必须换回 --gold。
   浅底段（白/沙）吃 --gold-dark 只有 3.29:1（green）/ 2.45:1（blue、orange），
   低于 WCAG AA 正文 4.5:1；而 eyebrow 是 --fs-xs 小字 + 700 字重，不享受大字豁免。
   改版前靠模板逐段内联 `color:var(--gold)` 救回来（index.html 三处），
   那种补丁正是"令牌层没落地"的后果 —— 现在收进样式表。 */
.sec-green .sec-hd .eyebrow{color:var(--gold)}
.grid{display:grid; gap:var(--grid-gap)}
/* ★ 「宽窄不和谐」的另一半根因：固定列数 + 数量不匹配的条目 = 末行空洞
   （承诺墙 5 条排 g3 → 3+2，右侧空出一整格约 891px）。
   ★ 列数**由模板按条目数算出来**（sec.grid_cols，见 app.py::grid_cols），
     CSS 这里只负责"收到几列就画几列"——
     auto-fit/minmax 治不了这个问题：它按**宽度**算，不知道有 5 条，
     1440 宽下照样给 3 列（还是 3+2）。唯一正解是同时知道宽度和条目数。 */
.gfit{grid-template-columns:repeat(auto-fit,minmax(240px,1fr))}
@media (max-width:560px){ .gfit{grid-template-columns:1fr} }
/* 网格容器列数由 app.py::grid_cols 依据条目数算出，写成 inline style：
   .grid-N —— N 为 1..5。用 inline 而不是新加一堆类，
   是为了让"这一段到底该几列"只有一个决定点（Python 侧），CSS 不再猜。 */
.grid-1{grid-template-columns:1fr}
.grid-2{grid-template-columns:repeat(2,1fr)}
.grid-3{grid-template-columns:repeat(3,1fr)}
.grid-4{grid-template-columns:repeat(4,1fr)}
.grid-5{grid-template-columns:repeat(5,1fr)}
/* ★★ 降列的铁律：**只在能整除时才降**，否则宁可保持原列数。
   反例（本批实测踩到）：.grid-4 在 1080px 断点降到 3 列 ——
   4 条卡于是排成 3+1，右侧空一整格，**"宽窄不和谐"在断点处原样复发**；
   而 996px 宽下 4 列每列仍有 245px，完全放得下，没必要降。
   ⇒ 每条降列规则都写成 N → N' 的**整除**映射（4→2→1、3→1、5→1）。 */
@media (max-width:820px){ .grid-4{grid-template-columns:repeat(2,1fr)} }
@media (max-width:700px){ .grid-3,.grid-5{grid-template-columns:1fr} }
@media (max-width:560px){ .grid-2{grid-template-columns:1fr} }
.g2{grid-template-columns:repeat(2,1fr)} .g3{grid-template-columns:repeat(3,1fr)}
.g4{grid-template-columns:repeat(4,1fr)} .g5{grid-template-columns:repeat(5,1fr)}

/* ---------- Hero ---------- */
.hero{background:linear-gradient(135deg,var(--green-900) 0%,var(--green-700) 55%,var(--green-glow) 100%);
  color:#fff; padding:88px 0 72px; position:relative; overflow:hidden}
.hero::after{content:'';position:absolute;top:-30%;right:-15%;width:70%;height:160%;
  background:radial-gradient(ellipse at center,rgba(var(--gold-rgba),.14),rgba(var(--gold-rgba),0) 68%);
  pointer-events:none}
/* ★ 2026-10-15 首屏主视觉：实拍展厅图铺底+ 轻遮罩
   —— 首屏原本是纯CSS 渐变，视觉上没有"工厂"实感；铺实拍后信任感立刻上来。
   —— 遮罩强度见下方"张总：透明一点"说明：对比度改由文字描边承担，不再靠压暗图片。 */
.hero.has-visual{background:
    linear-gradient(100deg,rgba(var(--green-900-rgba),.30) 0%,rgba(var(--green-900-rgba),.15) 42%,rgba(var(--green-900-rgba),.03) 100%),
    url('/static/img/home/hero-main.jpg') center/cover no-repeat;
}
.hero.has-visual::before{content:'';position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(var(--green-900-rgba),.22) 0%,transparent 30%,transparent 76%,rgba(var(--green-900-rgba),.34) 100%);
  pointer-events:none;z-index:0}
/* ────────────────────────────────────────────────────────────────
   ★★ 磨砂：斑块跟随文字（2026-10-15 张总："跟随文字和图片范围，
      深浅和面积合理变化，不要大面积堆砌，要有自然融合"）

   —— 问题：旧版是一条横向渐变铺满左半屏，磨砂面积= 屏幕矩形，
      h1 上方的空白也在糊 ⇒ 视觉上是"糊了一层纱"。
   —— 现状：.hero-frost 降级为 **no-JS 兜底**；JS 就绪后 html加
      .js-frost-on把它隐藏，只留 .hero-frost-blob 斑块。
   ──────────────────────────────────────────────────────────────── */
.js-frost-on .hero-frost{display:none}
.hero.has-visual .hero-frost{
  position:absolute; inset:0; z-index:0; pointer-events:none;
  backdrop-filter:blur(6px) saturate(1.12);
  -webkit-backdrop-filter:blur(6px) saturate(1.12);
  background:linear-gradient(100deg,rgba(var(--green-900-rgba),.22) 0%,rgba(var(--green-900-rgba),.10) 45%,transparent 68%);
}
/* 薄暗纱：只留一点点主对比度，斑块负责"按需起雾" */
.hero.has-visual .hero-frost + .hero-veil{
  position:absolute; inset:0; z-index:0; pointer-events:none;
  background:linear-gradient(100deg,rgba(var(--green-900-rgba),.20) 0%,rgba(var(--green-900-rgba),.08) 44%,transparent 66%);
}

/* ── 斑块层：位置/尺寸全部由 site.js 量文字后动态生成 ── */
.hero-frost-layer{position:absolute; inset:0; z-index:0; pointer-events:none;
  contain:strict}
.hero-frost-blob{
  position:absolute;
  /* ★ 模糊强度与暗纱浓度全部来自下面三档 class 写入的 CSS 变量，
     这样"深浅"可在 CSS 一处调，不用碰 JS、不用碰模板。 */
  --fb-blur:7px;
  --fb-a:.20;
  backdrop-filter:blur(var(--fb-blur)) saturate(1.12);
  -webkit-backdrop-filter:blur(var(--fb-blur)) saturate(1.12);
  background:rgba(var(--green-900-rgba),var(--fb-a));
  border-radius:26px;
  /* ★ 自然融合的关键：四周用椭圆 mask 渐隐到全透明，
     没有硬边 ⇒ 斑块边界"化"进图里，而不是切出一块。 */
  -webkit-mask-image:radial-gradient(ellipse at center,#000 42%,rgba(0,0,0,.78) 60%,rgba(0,0,0,.28) 80%,transparent 94%);
  mask-image:radial-gradient(ellipse at center,#000 42%,rgba(0,0,0,.78) 60%,rgba(0,0,0,.28) 80%,transparent 94%);
}
/* 强弱分级：重点（h1）稍重，次要（小字/eyebrow）稍轻 —— 由 JS 加 class */
.hero-frost-blob.lv-strong{--fb-blur:9px; --fb-a:.26}
.hero-frost-blob.lv-mid{--fb-blur:7px;    --fb-a:.20}
.hero-frost-blob.lv-soft{--fb-blur:5px;    --fb-a:.14}

/* ────────────────────────────────────────────────────────────────
   ★★ 2026-10-16 张总批注：「这个文字地方还是要整块磨砂遮罩 跟随字的面积大小变化」
   —— 昨夜那版（JS 量文字 → 生成多个 .hero-frost-blob 斑块）**被否**：
      张总要的是**一整块**，而且要跟随文字面积。
   ⇒ 改挂到 `.hero-copy` 自己的 ::before 上：`inset` 向外扩一圈，
     面积由**文字块本身**决定 ⇒ 内容多少 / 语种长短（俄语德语更长）/
     手机换行，全部自动跟随，**不需要 JS 量尺寸**。
   ★★ 两个坑（动手前想清楚，别踩）：
     ① `.hero-copy` 必须保持 `position:relative` + **z-index:auto**（不创建
        stacking context）—— 否则 ::before 的 `backdrop-filter` 只能糊到
        hero-copy 自己的透明背景，**糊不到 .hero 那张实拍图**。
     ② 文字压在遮罩之上用 `.hero-copy > *{z-index:1}`，**不用 z-index:-1**
        —— 负值会把遮罩沉到 .hero 背景图**下面**，直接看不见。
   ──────────────────────────────────────────────────────────────── */
.hero.has-visual .hero-copy{position:relative}
.hero.has-visual .hero-copy::before{
  content:''; position:absolute; z-index:0; inset:-22px -26px;
  border-radius:28px; pointer-events:none;
  backdrop-filter:blur(10px) saturate(1.10);
  -webkit-backdrop-filter:blur(10px) saturate(1.10);
  /* 白纱（不是压黑）：张总 10-15 已定调「遮盖太深了，可以透明一点」，
     白字可读性由既有 text-shadow 承担，遮罩只负责"起雾"。 */
  background:rgba(255,255,255,.12);
  box-shadow:0 10px 40px rgba(6,26,18,.10);
}
.hero.has-visual .hero-copy > *{position:relative; z-index:1}
/* 斑块方案停用：JS 与 CSS 都留着（可回看），但不再产生视觉 */
.js-frost-on .hero-frost-layer{display:none}
/* 文字描边 —— 背景透亮的前提是字自己站得住 */
/* ★ 2026-10-15 张总："遮盖太深了，可以透明一点"
   原来靠加深背景(.94→.62)来保证白字对比度，代价是把展厅实拍全吃掉。
   改为：背景大幅调浅，白字可读性改由文字自身 text-shadow 承担。 */
/* 文字描边 —— 背景透亮的前提是字自己站得住 */
.hero.has-visual h1{text-shadow:0 2px 20px rgba(6,26,18,.62),0 1px 4px rgba(6,26,18,.50)}
/* 正文色比标题更浅、更细，对比度要求更高：描边加重 + 字色提亮 */
.hero.has-visual .lead{color:#eaf4ee;
  text-shadow:0 1px 14px rgba(6,26,18,.72),0 1px 4px rgba(6,26,18,.45)}
.hero.has-visual .hero-stat span{color:#dfeae3;
  text-shadow:0 1px 12px rgba(6,26,18,.65),0 1px 3px rgba(6,26,18,.40)}
/* 卡片原本半透明白，改用更深的底+ 更强的模糊，避免背景透上来显脏 */
.hero.has-visual .hero-card{background:rgba(var(--green-900-rgba),.42);border-color:rgba(255,255,255,.20);
  backdrop-filter:blur(10px) saturate(1.1)}
.hero.hero-plain::after{display:none}
.hero .wrap{position:relative; z-index:1; display:grid; grid-template-columns:1.05fr .95fr; gap:56px; align-items:center}
/* ★★ 2026-10-15 张总「这个块不要了 影响大局」—— 删掉首屏右侧 .hero-card 之后，
   两栏网格的右栏空了，主图被迫压到左半屏，主视觉反而变小 ⇒ 收回单栏。
   ★ 只在 .hero-solo 上生效：samples / trial 两页仍有 .hero-card，
     .hero .wrap 的两栏默认**一行都不能动**，否则那两页会丢右栏。
   文字块限宽 720px：单栏铺满 1440px 会让 h1 和 .lead 的行长过长、难读；
   限宽后是"左对齐的一栏文字 + 右侧整幅实拍图"，主图当主角。 */
.hero.hero-solo .wrap{grid-template-columns:1fr; gap:0}
.hero.hero-solo .hero-copy{max-width:720px}
.hero.hero-solo .lead{max-width:600px}
.hero h1{font-size:3.1rem; color:#fff; margin-bottom:18px}
.hero .lead{font-size:var(--fs-18); color:var(--green-on-dark-2); margin-bottom:30px; max-width:560px}
.hero-actions{display:flex; gap:14px; flex-wrap:wrap}
.hero-stats{display:flex; gap:40px; margin-top:44px; padding-top:28px; border-top:1px solid rgba(255,255,255,.16); flex-wrap:wrap}
.hero-stat b{display:block; font-size:2rem; color:var(--gold); line-height:1.1; font-weight:800}
.hero-stat span{font-size:var(--fs-sm); color:var(--green-on-dark-2)}
/* ★ 移动端: Hero 数据改为 2×2 网格, 避免 flex 换行挤成一团 (七专家评审 P0) */
@media(max-width:640px){
  .hero-stats{display:grid; grid-template-columns:1fr 1fr; gap:20px 16px; margin-top:32px; padding-top:24px}
  .hero-stat b{font-size:1.7rem}
}
.hero-card{background:rgba(255,255,255,.07); border:1px solid rgba(255,255,255,.16);
  border-radius:var(--radius-lg); padding:28px; backdrop-filter:blur(6px)}

/* ---------- Hero 大图轮播（2026-10-15 张总："大图轮播呢"）
   ★ 图片 URL 不在 CSS 里 —— 由模板从 web_section_image 渲进 inline style，
     后台改图即生效，无需改代码。
   ★ 遮罩层（.hero.has-visual 自身的 background 渐变）叠在轮播之上：
     渐变是 .hero 的背景，轮播是 .hero-slides 的子元素，
     所以顺序上遮罩在上、文字在下，文字始终清晰。 */
.hero{position:relative; overflow:hidden}
.hero-slides{position:absolute; inset:0; z-index:0}
.hero-slide{
  position:absolute; inset:0;
  background-size:cover; background-position:center; background-repeat:no-repeat;
  opacity:0; transition:opacity 1.1s ease;
  will-change:opacity;
}
.hero-slide.is-on{opacity:1}
/* 轮播时把 .hero 原有的静态图铺底撤掉，改由轮播层负责；
   若轮播层因故没显示，仍保留 hero-main.jpg 兜底 */
.hero-carousel{background-image:
    linear-gradient(100deg,rgba(var(--green-900-rgba),.30) 0%,rgba(var(--green-900-rgba),.15) 42%,rgba(var(--green-900-rgba),.03) 100%) !important}

/* 控制条：左右箭头 + 圆点 */
.hero-slider-ui{
  position:absolute; z-index:3; left:50%; transform:translateX(-50%);
  bottom:26px; display:flex; align-items:center; gap:16px;
}
.hero-arrow{
  width:38px; height:38px; border-radius:50%; cursor:pointer;
  border:1px solid rgba(255,255,255,.42); background:rgba(var(--green-900-rgba),.42);
  color:#fff; font-size:15px; line-height:1;
  backdrop-filter:blur(8px); transition:.2s;
}
.hero-arrow:hover{background:rgba(198,160,74,.85); border-color:var(--gold); color:#2a2415}
.hero-dots{display:flex; gap:9px; align-items:center}
.hero-dot{
  width:9px; height:9px; padding:0; border-radius:50%; cursor:pointer;
  border:1px solid rgba(255,255,255,.65); background:rgba(255,255,255,.28);
  transition:.25s;
}
.hero-dot.is-on{background:var(--gold); border-color:var(--gold); width:26px; border-radius:5px}

@media(max-width:640px){
  .hero-slider-ui{bottom:16px; gap:11px}
  .hero-arrow{width:32px; height:32px; font-size:13px}
  .hero-dot{width:7px; height:7px}
  .hero-dot.is-on{width:19px}
}

/* ---------- 商品卡 ---------- */
.pcards{display:grid; grid-template-columns:repeat(4,1fr); gap:22px}
.pcard{border:1px solid var(--line); border-radius:var(--radius-lg); overflow:hidden;
  background:#fff; transition:.2s; display:flex; flex-direction:column; position:relative}
.pcard:hover{box-shadow:var(--shadow-lg); transform:translateY(-3px); border-color:var(--green-500)}
/* ★ 2026-10-15 视觉统一：1/1 → --ratio-tall(4/5)。
   依据：static/media/sku 下 1837 张商品图实测，53.7% 是 3:4 竖构图。
   3:4 源进 1:1 框每边裁 12.5%（削掉仿真树树梢）；进 4:5 只裁 3.1%。
   ⚠️ 平板（≤1080）回正到 1/1：820px 档是 2 列且列宽达 374px，
      4/5 会把图拉到 467px 高（近半屏），该段凭空 +561px。 */
.pcard-img{aspect-ratio:var(--ratio-tall); background:var(--sand-100); overflow:hidden; position:relative}
.pcard-img img{width:100%;height:100%;object-fit:cover; transition:.35s}
.pcard:hover .pcard-img img{transform:scale(1.05)}
.pcard-badge{position:absolute; top:10px; inset-inline-start:10px; background:var(--gold);
  color:var(--gold-on-2); font-size:var(--fs-2xs); font-weight:700; padding:4px 9px; border-radius:4px; letter-spacing:.04em}
.pcard-body{padding:15px 16px 17px; flex:1; display:flex; flex-direction:column; gap:6px}
.pcard-code{font-size:var(--fs-xs); color:var(--ink-3); letter-spacing:.04em; font-family:ui-monospace,monospace}
.pcard-name{font-size:var(--fs-md); font-weight:600; line-height:1.4; flex:1;
  /* ★ 2026-10-16（第 1300 批 · 桌面按族收口；规格 §4.4：指针目标 24×24）：
     商品名就是**进详情页的入口**（独立一行，不是句内链接）⇒ 真改。
     真现场（`tools/_b1299_small_ctl_census.py` 普查 @1440）：**21 处 / 2 页**、258×20
     —— 只有"整行都是单行名"的那种行才矮，两行名的行本来就有 ~40px。
     `min-height` 只会把**那些行**抬高 4px；网格是 stretch、同一行卡片一起长 ⇒ 不会参差。 */
  min-height:24px;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden}
.pcard-price{font-size:1.22rem; font-weight:800; color:var(--green-700)}
.pcard-price small{font-size:var(--fs-xs); font-weight:500; color:var(--ink-3)}
.pcard-meta{font-size:var(--fs-xs); color:var(--ink-3); display:flex; gap:10px; flex-wrap:wrap}
.pcard-add{margin-top:8px}

/* ---------- 加购反馈（张总 2026-09-30：「有一个动画动作流向购物车里」，且**不自动弹抽屉**）----------
   .xj-fly  = 从被点的按钮/卡片飞向购物车的那一小块（有商品图就用图，没有就是白方块）
   .xj-bump = 购物车按钮被"命中"时轻轻弹一下
   [data-add-quote].in-cart = 已进车的那颗按钮（文案变成「✓ <原本文案>」，颜色换成绿色）
   ★ 全部是**纯反馈**：不改变任何数据，动画失败也不影响加购（JS 侧套了 try）。 */
.xj-fly{position:fixed;width:44px;height:44px;border-radius:9px;background:#fff center/cover no-repeat;
  border:1px solid var(--line); box-shadow:0 8px 20px rgba(0,0,0,.18); z-index:99999;
  pointer-events:none; transform:translate(-50%,-50%);
  transition:transform .6s cubic-bezier(.35,0,.25,1), opacity .6s ease-in}
@keyframes xjBump{0%{transform:scale(1)} 35%{transform:scale(1.16)} 100%{transform:scale(1)}}
.xj-bump{animation:xjBump .55s ease}
/* 已进车：按钮上加个 ✓（**不改文字**，文字归"按板块换文案"那两处管），行轻轻高亮 */
[data-add-quote].in-cart, .rfq-add.in-cart, .sp-add.in-cart{
  background:var(--green-700); border-color:var(--green-700)}
[data-add-quote].in-cart::before, .rfq-add.in-cart::before, .sp-add.in-cart::before{
  content:'✓ '}
tr.in-cart{background:var(--sand-100)}

/* ---------- 购物车抽屉「最大化」（张总 2026-09-30：「购物车可以窗口最大化」）----------
   ★ 默认态**一个字都不改**（还是右侧窄抽屉 380–420px）；这里只定义**最大化态**。
   ★ 为什么用 !important：抽屉默认态的定位/宽度由既有规则给（position/transform/width），
     最大化要**盖过**它们；写成 `.quote-bar.qb-max` + !important 是最不容易跟旧规则打架的做法，
     而且全部收在这个类里 —— 去掉这个类就逐字回到原样。 */
.quote-bar.qb-max{position:fixed !important; inset:12px !important;
  width:auto !important; max-width:none !important; height:auto !important;
  max-height:none !important; transform:none !important; z-index:9998;
  border-radius:12px; box-shadow:0 18px 50px rgba(0,0,0,.28)}
.quote-bar.qb-max .qb-body{max-height:none !important}
.qb-max-btn{background:none; border:none; cursor:pointer; font-size:var(--fs-17); line-height:1;
  padding:4px 6px; margin-inline-end:2px; color:inherit; opacity:.7}
.qb-max-btn:hover{opacity:1}
/* 手机/窄屏：抽屉本来就是全屏，不需要这颗按钮 */
@media (max-width:760px){ .qb-max-btn{display:none} }

/* ---------- 收藏（张总 2026-09-30 B5：「不想加购可以收藏，下次来还能看」）----------
   ★ 位置/形状刻意与"添加到报价单"拉开：卡片**图片右上角**一颗圆钮、♡→♥ 两种状态。
     （并排两颗同形状按钮 ⇒ 客户一定点错，这是本仓"两个动作要能一眼分开"的老规矩。） */
.pcard-fav{position:absolute; top:8px; inset-inline-end:8px; z-index:2;
  width:32px; height:32px; border-radius:50%; border:1px solid var(--line);
  background:rgba(255,255,255,.92); color:var(--ink-3); cursor:pointer;
  font-size:var(--fs-16); line-height:1; padding:0}
.pcard-fav:hover{border-color:#e0533d; color:#e0533d}
.pcard-fav.on{color:#e0533d; border-color:#e0533d}

/* ---------- 分类/筛选 ---------- *//* ★ 首页品类卡片 (七专家评审: 移动端品类丰富度 + 横滑体验)
   ★★ 2026-09-30（张总：「这里太突兀 横排排列铺满 没有满的 最后一个放完整目录」）：
     原来 `.cat-tile` 是 `flex:0 0 150px` **固定宽** ⇒ 桌面下只有 2 个分类 + 1 张
     「完整目录」时，三张卡挤在左边、右边留一大片空白（张总说的"太突兀"）。
     现在分两档：
       · **桌面（>760px）**：一排**铺满整行**（可伸缩 + 允许换行）；
         最后一张（内容上固定是「完整目录」）**吃余量** —— 分类数不足时由它补满。
       · **窄屏（≤760px）**：退回**横滑**（与 2026-09-30 之前逐字一致）——
         手机上横滑本来是对的，不能为了"铺满"把卡片压成细条。 */
/* ★ 2026-10-15 视觉统一（张总："宽窄不和谐"）：
   原来最后一张「完整目录」有 flex-grow:2，实测三张卡宽 278 / 278 / 396 ——
   最宽的比最窄的多 42%，是全站最刺眼的一处不等宽。
   改为 flex-basis:0 + grow:1 ⇒ **所有卡严格等宽**（余量平分）。
   ★ 刻意保留 flex 而不改成 grid：≤760px 的横滑（overflow-x + scroll-snap
     + flex:0 0 150px）在 grid 容器上是完全惰性的，会**静默杀掉**手机横滑
     且不报任何错。要等宽 + 要横滑，flex 是唯一两者兼得的写法。 */
.cat-scroll{display:flex; flex-wrap:wrap; gap:var(--grid-gap); padding-bottom:8px;
  scrollbar-width:none}
.cat-scroll::-webkit-scrollbar{display:none}
.cat-scroll .cat-tile{flex:1 1 0; min-width:0; display:flex;
  flex-direction:column; align-items:center; gap:8px; padding:22px 14px;
  background:#fff; border:1px solid var(--line); border-radius:var(--radius-lg);
  text-align:center; transition:.2s; min-height:140px; justify-content:center}
.cat-scroll .cat-tile:hover{border-color:var(--green-500); box-shadow:var(--shadow); transform:translateY(-2px)}
.cat-scroll .cat-tile .ic{font-size:var(--fs-34); line-height:1}
.cat-scroll .cat-tile b{font-size:var(--fs-md); color:var(--ink)}
.cat-scroll .cat-tile span{font-size:var(--fs-xs); color:var(--green-700); font-weight:650}
/* 窄屏：横滑（旧口径原样保留） */
@media (max-width:760px){
  .cat-scroll{flex-wrap:nowrap; overflow-x:auto;
    -webkit-overflow-scrolling:touch; scroll-snap-type:x mandatory; gap:14px}
  .cat-scroll .cat-tile{flex:0 0 168px; scroll-snap-align:start}
}

/* ---------- 分类卡配图（2026-10-15 张总："这个也纳入整体考虑"）
   ★ 原来只有 emoji + 纯白底，和首屏/为什么选我们的实拍感脱节。
   —— 改为同一套模式：有配图 → 图铺卡面（顶部），emoji 缩小成角标；
      没配图 → 回落原样（.has-img 不存在就是原样式）。
   ★ 「完整目录」那张（.cat-tile-all）2026-10-15 口径是不参与配图；
     2026-10-16 张总批注「放上图片 后台可以换」⇒ 已给**专属图位 home_cats_all**
     （IMAGE_SETS ⑥），有图时走与分类卡同一套 .has-img 结构（图铺卡面+🔍角标）。 */
.cat-scroll .cat-tile.has-img{padding:0; align-items:stretch; text-align:start;
  justify-content:flex-start; gap:0; overflow:hidden; position:relative}
/* ★ 2026-10-15 视觉统一：16/10 → --ratio-square(1/1)。
   依据：static/img/home/cat-*.jpg 实测 800x800 = 1:1，
   进 16/10 框每边裁 18.75% —— 树冠和花盆底座同时被切，
   而 background-position:center 保证它从正中切，最不能切的位置正是主体。
   改 1/1 后零裁切，且移动端那条"150px 宽只剩 94px 高像压扁色带"的
   旧抱怨（main.css:481 注释）自动消失。 */
.cat-tile-img{display:block; width:100%; aspect-ratio:var(--ratio-square); background-size:cover;
  background-position:center; flex:none}
/* 分类卡角标：这里**只管定位**（压在图的左上角）；
   尺寸/底色/圆角/描边统一由文末的全局 .cat-scroll .cat-tile.has-img .ic 决定。
   ★ 之前两处各写一遍尺寸，结果文末那条把这条的 34px 覆盖成了 38px，
     分工不清就是样式走样的起点。 */
.cat-scroll .cat-tile.has-img .ic{position:absolute; top:10px; inset-inline-start:10px;
  z-index:1; margin-bottom:0!important; backdrop-filter:blur(4px);
  -webkit-backdrop-filter:blur(4px)}
.cat-scroll .cat-tile.has-img b{padding:14px 14px 0}
.cat-scroll .cat-tile.has-img span:last-child{padding:6px 14px 16px}
/* 完整目录卡：与分类卡同构（2026-10-15 张总"风格统一"）
   —— 原来是纯绿实底大块，夹在两张实拍图卡旁边像另一个网站的组件。
   —— 改为深色浅底 + 同圆角描边，只靠**底色深浅**区分"兜底入口"，
      不再用"另一个形状语言"。flex-grow:2 吃余量的原口径保持不动。
   ★★ 全部走主题变量：--green-900 在四套皮肤里分别是绿/蓝/橙/深灰，
      语义是"主色最深档"。这里**绝不能写死绿色值**，否则换皮肤会脱节
      （张总："不能只考虑绿色 后台有皮肤 要考虑适配 统筹到全局"）。 */
/* ★★ 2026-10-15 截图验收后修正（张总「色块不 harmonious/色块不和谐」的真凶）：
   这块原来是**整块深绿实底**，在实拍图卡旁边像"另一个网站的组件"，
   而且它是首页除报价流程外**第二块深色**—— 与"深色只留 1 块"直接冲突。
   改为与 .cat-tile 完全同构的浅沙底 + 同描边，只靠一个虚线箭头图标
   表达"这是兜底入口"。深绿留给 .sec-dark 那唯一一段。 */
.cat-scroll .cat-tile-all{background:var(--surface-1); border-style:dashed;
  border-color:var(--green-700)}
/* ★ 2026-10-16 张总批注「放上图片」：带图态把虚线收成实线 ——
   虚线的语义是"这里缺东西"，既然有图了就别再说它缺；
   与分类卡（实线描边）完全同构。 */
.cat-scroll .cat-tile-all.has-img{border-style:solid; border-color:var(--line)}
.cat-scroll .cat-tile-all:hover{border-color:var(--gold); box-shadow:var(--shadow);
  transform:translateY(-2px)}
.cat-scroll .cat-tile-all b{color:var(--ink)}
.cat-scroll .cat-tile-all span{color:var(--ink-2)}
.cat-scroll .cat-tile-all .ic{font-size:var(--fs-30)!important; color:var(--green-700)}

/* ---------- 双基地卡（2026-10-15 张总"这两块统筹考虑 风格统一起来"）
   ★ 与 .why-card / .cat-tile 完全同构：图铺上方 + emoji 缩成角标 + 同圆角描边。
   —— 原来这里是 emoji + 纯白面板，下方还另挂一排 200px 高的小图，
      同一板块两套语言；现已删掉那排小图（同图放进卡片语义更清楚）。
   —— 图比例取 21:9（比 16:9 更扁），两块并排时页面不会被图撑得太长。 */
.base-card{padding:0; margin:0; overflow:hidden; display:flex; flex-direction:column;
  transition:transform .22s ease, box-shadow .22s ease}
.base-card:hover{transform:translateY(-3px); box-shadow:0 12px 28px rgba(20,40,32,.12)}
/* ★ 2026-10-15 视觉统一：21/9 → --ratio-landscape(16/9)。
   依据：static/img/home/factory-*.jpg 实测 1600x900 = 16:9，
   进 21/9 框左右各裁 11.9%，16/9 是零裁切。 */
.base-card-img{width:100%; aspect-ratio:var(--ratio-landscape); background-size:cover; background-position:center;
  flex:none}
.base-card.has-img .base-card-bd{background:linear-gradient(180deg,rgba(255,255,255,.92),#fff 26px)}
.base-card-bd{padding:26px; flex:1; display:flex; flex-direction:column}
/* 角标样式见上方全局 .why-ico/.base-ico/.cat-tile.has-img .ic 合并规则 */
/* ★ 2026-10-15 视觉统一：这条原来在 ≤640px 改成 16/7（2.286），
   比基线 21/9（2.333）**更扁** —— 方向是反的：屏幕越窄图越扁，
   而源图是 1600x900 横构图，基线改 16/9 后本就是零裁切。
   改回 16/9：手机端保留全幅横图（1 列铺满屏宽），不再压成色带。 */
@media (max-width:640px){
  .base-card-img{aspect-ratio:var(--ratio-landscape)}
}

/* ════════ 配图版的三处移动端适配（2026-10-15 张总："手机适配 不要忽略"）════════
   ① 分类卡横滑时宽度从 150px 放宽到 168px —— 150px 放 16:10 的图之后
      图只剩 94px 高，看着像一条压扁的色带，看不出是什么。
   ② 为什么选我们g4 在 ≤640px 是 2×2，但图版卡片比纯文字卡高，
      2×2 会把页面撑得很长 ⇒ ≤640px 改成 1 列（本来就是竖读）
   —— 既有 2026-09-30 定的"手机上横滑是对的"这条不动，只调宽度。 */
@media (max-width:760px){
  .cat-scroll .cat-tile{flex:0 0 168px}
  /* ★ 手机端角标缩小：必须用**更靠后的选择器权重**或 !important，
     否则会被文末那条全局 .cat-scroll .cat-tile.has-img .ic 的 38px 盖掉
     （这是本文件里"媒体查询写在中间、全局规则写在末尾"的结构性坑）。 */
  .cat-scroll .cat-tile.has-img .ic{top:8px; inset-inline-start:8px;
    width:30px!important; height:30px!important; font-size:var(--fs-16)!important}
}
@media (max-width:640px){
  /* 图版卡片：1 列铺满，图能横着铺满屏宽，比 2×2 小方块好看得多 */
  /* 2026-10-15 张总"手机适配不要忽略"：
     为什么选我们在 ≤1080px 是 2×2；加了 16:9 配图后 2×2 会把手机页撑得很长，
     手指要滑很久才到下一个板块 ⇒ 手机上改 1 列。纯文字卡的 2×2 语义不变。 */
  .why-card{margin-bottom:14px}
  /* ★ 2026-10-15：原来这里在 ≤640px 反而改成 21/9（更扁），
     与"越窄越扁"是同一个反向病根。1 列铺满时用基线 16/9 才够看。 */
  .why-card-img{aspect-ratio:var(--ratio-landscape)}
  /* 真正把 2×2 改成 1×4：只加 margin 的话卡片还是并排两列 */
  #sec-home_why .g4{grid-template-columns:1fr}
}
@media (max-width:420px){
  .cat-scroll .cat-tile{flex:0 0 152px}
}
.filters{display:flex; gap:12px; flex-wrap:wrap; align-items:center;
  padding:18px 0; border-bottom:1px solid var(--line); margin-bottom:30px}
.chips{display:flex; gap:8px; flex-wrap:wrap}
.chip{padding:7px 15px; border:1px solid var(--line); border-radius:100px; font-size:var(--fs-sm);
  background:#fff; cursor:pointer; white-space:nowrap; transition:.15s}
.chip:hover{border-color:var(--green-600); color:var(--green-700)}
.chip.on{background:var(--green-700); color:#fff; border-color:var(--green-700)}
/* ★★ 2026-09-30（张总「这里切换品类跳转就在框内」）：样品板 / 报价板的类目 chip
   是**服务端链接**（整页刷新、URL 可分享），href 带 `#sec-…_board` 让刷新后停回这一板。
   `.hdr` 是 `position:sticky; top:0`、70px 高 ⇒ 不留这一段 margin，跳过去的标题会被
   站头压住，"停在框内"会变成"停在一板看不见的标题上"。84 = 70 + 14 呼吸位。 */
#sec-samples_board,#sec-rfq_board{scroll-margin-top:84px}
.searchbox{position:relative; margin-inline-start:auto}
.searchbox input{padding:9px 14px 9px 36px; border:1px solid var(--line); border-radius:100px;
  width:230px; font-size:var(--fs-sm)}
html[dir="rtl"] .searchbox input{padding:9px 36px 9px 14px}
.searchbox::before{content:'⌕'; position:absolute; inset-inline-start:13px; top:50%;
  transform:translateY(-50%); color:var(--ink-3); font-size:var(--fs-16)}

/* ---------- 分页 ---------- */
.pager{display:flex; gap:8px; justify-content:center; margin-top:44px; flex-wrap:wrap}
.pager a,.pager span{padding:9px 15px; border:1px solid var(--line); border-radius:6px; font-size:var(--fs-md); background:#fff}
.pager a:hover{border-color:var(--green-600); color:var(--green-700)}
.pager .on{background:var(--green-700); color:#fff; border-color:var(--green-700)}

/* ---------- 表格 ---------- */
.tbl{width:100%; border-collapse:collapse; font-size:var(--fs-md)}
.tbl th{text-align:start; padding:12px 14px; background:var(--sand-100); font-weight:650;
  font-size:var(--fs-sm); letter-spacing:.02em; border-bottom:1px solid var(--line); white-space:nowrap}
.tbl td{padding:13px 14px; border-bottom:1px solid var(--line); vertical-align:middle}
.tbl tr:last-child td{border-bottom:0}
.tbl tbody tr:hover{background:var(--sand-50)}
.tbl .num{text-align:end; font-variant-numeric:tabular-nums}
.tbl-wrap{border:1px solid var(--line); border-radius:var(--radius-lg); overflow:hidden; background:#fff}
.tbl-wrap.scroll{overflow-x:auto}

/* ---------- 阶梯价表 ---------- */
.tiers{display:grid; grid-template-columns:repeat(auto-fit,minmax(120px,1fr)); gap:2px;
  background:var(--line); border:1px solid var(--line); border-radius:var(--radius); overflow:hidden}
.tier{background:#fff; padding:14px 12px; text-align:center}
.tier:nth-child(1){background:var(--green-100)}
.tier b{display:block; font-size:1.32rem; color:var(--green-800); font-weight:800;
  font-variant-numeric:tabular-nums}
.tier span{font-size:var(--fs-xs); color:var(--ink-3)}
.tier em{display:block; font-size:var(--fs-2xs); font-style:normal; color:var(--gold-dark); font-weight:700}

/* ---------- 表单 ---------- */
.form-grid{display:grid; grid-template-columns:repeat(2,1fr); gap:16px}
.field{display:flex; flex-direction:column; gap:6px}
.field.full{grid-column:1/-1}
.field label{font-size:var(--fs-sm); font-weight:600; color:var(--ink-2)}
.field label .req{color:var(--red)}
.field input,.field select,.field textarea{
  padding:11px 13px; border:1px solid var(--line); border-radius:var(--radius);
  background:#fff; font-size:var(--fs-md); transition:.15s; width:100%}
.field input:focus,.field select:focus,.field textarea:focus{
  outline:0; border-color:var(--green-600); box-shadow:0 0 0 3px rgba(45,122,84,.12)}
.field textarea{resize:vertical; min-height:110px}
.field .hint{font-size:var(--fs-xs); color:var(--ink-3)}
.field.err input,.field.err select,.field.err textarea{border-color:var(--red); background:#fff7f6}
.form-note{font-size:var(--fs-xs); color:var(--ink-3); margin-top:4px}

.alert{padding:13px 16px; border-radius:var(--radius); font-size:var(--fs-md); margin-bottom:18px; display:none}
.alert.show{display:block}
.alert-ok{background:var(--green-100); color:var(--green-800); border:1px solid #b8dcc8}
.alert-err{background:#fdecea; color:#a83226; border:1px solid #f5c6c0}

/* ---------- 卡片/面板 ---------- */
/* ★ 2026-10-15 9 门外语窄屏：`.panel` 是 grid 容器，直接子项（无 class 的列 div）
   默认 `min-width:auto`，长词语种（俄/印尼）单列内容撑到 176px 并把整页顶出
   34px 横向滚动条。逐个子项封 min-width:0，让长词折行而不是撑破。
   —— 只在 ≤820px（面板变两列时）改列定义，桌面维持原有单列排版。 */
.panel{border:1px solid var(--line); border-radius:var(--radius-lg); padding:26px; background:#fff}
.panel > *{min-width:0}
@media (max-width:820px){
  .panel{grid-template-columns:repeat(2,minmax(0,1fr))}
}
.panel-sand{background:var(--sand-50); border-color:var(--sand-200)}
.panel h3{margin-bottom:14px}

/* ---------- 「为什么选我们」卡片配图（2026-10-15 张总："这块区域也对应图片"）
   ★ 原来 4 张卡片只有 emoji + 纯白底，和首屏的实拍感完全脱节。
   —— 有配图时：图铺卡片顶部（16:9），正文压在下半部分；
      padding:0 + 内部 .why-card-bd 补回内边距 ⇒ 图能真正顶到卡片边缘。
   —— 无配图时：.has-img 不存在，卡片退回原来 .panel 的样子（emoji + 白底），
      所以后台清空图片也不会开天窗。 */
.why-card{padding:0; overflow:hidden; display:flex; flex-direction:column;
  transition:transform .22s ease, box-shadow .22s ease}
.why-card:hover{transform:translateY(-3px); box-shadow:0 12px 28px rgba(20,40,32,.12)}
.why-card-img{width:100%; aspect-ratio:16/9; background-size:cover; background-position:center;
  flex:none}
/* 图上到正文之间加一条极淡的渐变，让过渡不生硬 */
.why-card.has-img .why-card-bd{background:linear-gradient(180deg,rgba(255,255,255,.92),#fff 26px)}
.why-card-bd{padding:26px; flex:1; display:flex; flex-direction:column}
/* 配图后 emoji 缩小当角标：保留功能识别度，但不抢实拍的戏。
   ★★ 全局唯一一份（2026-10-15 张总"统筹到全局"）：
     为什么选我们 / 双基地 / 分类卡三类卡的角标**长得完全一样**，
     所以必须共用同一个类 —— 之前是三处各写一遍，改一处忘两处就会走样。
     ★ 色值全部走主题变量：--green-900 在四套皮肤里是绿/蓝/橙/深灰，
       写死绿色会换皮肤即脱节。 */
.why-ico.is-on-img,
.base-ico.is-on-img,
.cat-scroll .cat-tile.has-img .ic{
  font-size:var(--fs-22)!important; margin-bottom:10px!important;
  display:inline-flex; align-items:center; justify-content:center;
  width:38px; height:38px; border-radius:50%;
  background:var(--green-900); opacity:.9;
  /* 角标压在实拍图上，必须自带描边才能在任何亮度的图上被看见 */
  box-shadow:0 2px 8px rgba(0,0,0,.28)
}

/* ---------- 视频看厂占位（有实拍图时用图当封面）
   —— 无图才保留虚线框（= "确实缺东西"的诚实表达）；
      有图则是"实拍车间 + 播放引导"，可信度高得多。 */
.video-poster{position:relative; overflow:hidden; text-decoration:none}
.video-poster-img{position:absolute; inset:0; background-size:cover; background-position:center;
  z-index:0}
/* ★ 2026-10-15 实拍图换上后立刻暴露的问题：文字原本是 flex 居中压在**图中间**，
   而车间实拍本身就亮（中灰墙面 + 白色货架）⇒ "工厂视频即将上线"那行直接被吃掉。
   两处一起改才根治：
     ① 暗纱加强并**加重下方**，把对比度集中在文字所在的底部；
     ② 文字改为**底部左对齐**（海报惯例），不再压在图像主体上。
   暗纱刻意不加到"看不清图"的程度 —— 实拍信息本身就是这版要卖的东西。 */
.video-poster.has-img{align-items:flex-end; justify-content:flex-start}
.video-poster.has-img::after{content:''; position:absolute; inset:0; z-index:0;
  background:linear-gradient(180deg,rgba(var(--green-900-rgba),.18) 0%,rgba(var(--green-900-rgba),.30) 42%,rgba(var(--green-900-rgba),.86) 100%);
  pointer-events:none}
.video-poster.has-img{border-style:solid; border-color:rgba(255,255,255,.18)}
.video-poster.has-img .video-poster-txt{position:relative; z-index:1;
  width:100%; justify-content:flex-start; text-align:start}
.video-poster-txt{display:flex; align-items:center; justify-content:center; gap:14px}

/* ══════════ 共用「视频看厂」片段 .fv（2026-10-16 张总批「视频窗口太丑」）══════════
   模板：`partials/_factory_video.html` —— **首页视频段与工厂页共用一份**，
   改一次两处同步（真源唯一，不会再"改了工厂页忘了首页"）。
   ★★ 为什么不做成裸 `<video controls>`：
     原生控件的皮肤是浏览器给的（灰底），跟全站绿／沙色系脱节，**而且
     Chrome/Edge/Safari 长得不一样**；更要命的是它在 **shadow DOM** 里，
     CSS 改不到内部 ⇒ "美化原生控件"这条路根本走不通。
     ⇒ 按行业共识：**高清封面 + 大播放按钮 + 点了才加载**（海外首屏更快）。
   ══════════════════════════════════════════════════════════════════════════════ */
.fv{display:block}
.fv-poster{position:relative; display:block; width:100%; aspect-ratio:16/9;
  border-radius:var(--radius-lg); overflow:hidden; cursor:pointer;
  background:#0b2417; text-decoration:none}
.fv-img{position:absolute; inset:0; z-index:0;
  background-size:cover; background-position:center}
/* 下缘暗角：保证底部提示文字在任何亮度的封面上都读得清 */
.fv-poster::after{content:''; position:absolute; inset:0; z-index:1; pointer-events:none;
  background:linear-gradient(180deg,rgba(4,18,12,.10) 0%,rgba(4,18,12,.26) 60%,rgba(4,18,12,.56) 100%)}
.fv-play{position:absolute; z-index:2; left:50%; top:50%;
  transform:translate(-50%,-50%);
  width:76px; height:76px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background:rgba(255,255,255,.92); color:var(--green-800);
  box-shadow:0 8px 26px rgba(4,18,12,.32);
  transition:transform .2s ease, box-shadow .2s ease}
/* 三角用 SVG（不是字符 ▶）：各浏览器形状一致、能跟着按钮缩放 */
.fv-play svg{margin-inline-start:3px}
.fv-poster:hover .fv-play,
.fv-poster:focus-visible .fv-play{transform:translate(-50%,-50%) scale(1.07);
  box-shadow:0 12px 32px rgba(4,18,12,.42)}
.fv-hint{position:absolute; z-index:2; left:0; right:0; bottom:16px; text-align:center;
  color:#fff; font-weight:600; font-size:var(--fs-md);
  text-shadow:0 1px 8px rgba(4,18,12,.72)}
.fv-video{display:block; width:100%; aspect-ratio:16/9;
  border-radius:var(--radius-lg); background:#000}
/* 占位态（视频还没上传）：沿用首页「引导预约」口径，不留黑框 */
.fv-poster.is-empty{display:flex; align-items:center; justify-content:center}
.fv-empty-txt{position:relative; z-index:2; display:flex; align-items:center; gap:14px;
  color:#fff; text-align:start; padding:24px}
.fv-empty-txt b{display:block; font-size:var(--fs-18)}
.fv-empty-txt > span > span{font-size:var(--fs-md); color:rgba(255,255,255,.82)}
.fv-empty-ic{font-size:var(--fs-44)}
/* 说明行 + 两个 CTA（行业共识：视频不能孤零零，看完要有下一步） */
.fv-meta{margin-top:16px; display:flex; flex-wrap:wrap; gap:14px;
  align-items:center; justify-content:space-between}
.fv-facts{margin:0; color:var(--ink-2); font-size:var(--fs-md)}
.fv-acts{display:flex; gap:10px; flex-wrap:wrap}
/* WhatsApp 绿与全站保持一致（同一个值，见 .mobile-cta .btn-wa） */
.fv-acts .btn-wa{background:#25D366; color:#fff}
.fv-acts .btn-wa:hover{background:#1fb457; color:#fff}
@media (max-width:560px){
  .fv-meta{flex-direction:column; align-items:stretch}
  .fv-acts .btn{flex:1}
  .fv-play{width:60px; height:60px}
  .fv-hint{bottom:12px}
}
/* 文字描边：底图换成实拍后亮度不可控（有的车间暗、有的亮），
   描边 + 底部暗纱双保险，任何底图下都能读 */
.video-poster.has-img .video-poster-txt b{text-shadow:0 2px 12px rgba(4,18,12,.85),0 1px 3px rgba(4,18,12,.7)}
.video-poster.has-img .video-poster-txt span span{text-shadow:0 1px 10px rgba(4,18,12,.8),0 1px 3px rgba(4,18,12,.6)}
.kv{display:grid; grid-template-columns:auto 1fr; gap:8px 20px; font-size:var(--fs-md)}
.kv dt{color:var(--ink-3); white-space:nowrap}
.kv dd{margin:0; font-weight:550}

/* ---------- 全球业务伙伴（招推荐人）条带 ----------
   ★ 首页 (§6.5) 和落地页 /partner 共用，所以放这里而不是写在某个模板的
     <style> 里 —— 写在模板里另一页就拿不到，复制一份就变成两处会漂移的真相。
   ★ 为什么不放顶部导航：10 项导航在每一门语种下都会挤爆（详见 index.html
     里 §6.5 的实测记录），所以这条带子承担了"入口"的职责。 */
.pp-badge{display:inline-block;background:var(--green-100);color:var(--green-800);
  border:1px solid #b8dcc8;border-radius:100px;padding:6px 15px;
  font-size:var(--fs-xs);font-weight:600;letter-spacing:.02em}
.pp-band{display:grid;grid-template-columns:1fr auto;gap:32px;align-items:center;
  background:var(--sand-50);border-color:var(--sand-200);padding:30px}
.pp-band-txt h2{font-size:1.5rem;margin:0}
.pp-band-act{display:flex;flex-direction:column;gap:10px;align-items:stretch;min-width:190px}
.pp-chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:16px}
.pp-chip{background:#fff;border:1px solid var(--sand-200);border-radius:100px;
  padding:6px 13px;font-size:var(--fs-xs);color:var(--ink-2)}
.pp-chip b{color:var(--green-700);font-weight:700}
@media(max-width:900px){
  .pp-band{grid-template-columns:1fr;gap:20px;padding:22px}
  .pp-band-txt h2{font-size:1.28rem}
}

/* ---------- 面包屑 ---------- */
.crumb{font-size:var(--fs-sm); color:var(--ink-3); padding:18px 0 0}
.crumb a:hover{color:var(--green-700)}
.crumb span{margin:0 7px; opacity:.5}

/* ---------- 商品详情 ---------- */
.pd{display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:48px; align-items:start}
.gallery{border:1px solid var(--line); border-radius:var(--radius-lg); overflow:hidden; background:var(--sand-50)}
.gallery-main{aspect-ratio:1/1; display:grid; place-items:center}
.gallery-main img{max-height:100%; object-fit:contain}
.thumbs{display:flex; gap:8px; padding:10px; overflow-x:auto; border-top:1px solid var(--line); background:#fff}
.thumb{width:62px; height:62px; flex:none; border:2px solid transparent; border-radius:6px;
  overflow:hidden; cursor:pointer; background:var(--sand-100)}
.thumb.on{border-color:var(--green-600)}
.thumb img{width:100%;height:100%;object-fit:cover}
/* ── 商品视频缩略图 + 播放层 ── */
.gallery-main{position:relative}
#videoPlayer{position:absolute;inset:0;display:none;background:#000}
#videoPlayer video,#videoPlayer iframe{width:100%;height:100%;border:0;object-fit:contain}
.thumb-video{position:relative;display:grid;place-items:center;
  background:linear-gradient(135deg,var(--green-700),var(--green-500))}
.thumb-video .play-badge{width:26px;height:26px;border-radius:50%;background:rgba(255,255,255,.92);
  color:var(--green-700);display:grid;place-items:center;font-size:var(--fs-2xs);padding-left:2px}
.thumb-video.on{border-color:var(--green-600)}
.pd-title{font-size:1.85rem; margin-bottom:8px}
.pd-code{font-family:ui-monospace,monospace; font-size:var(--fs-sm); color:var(--ink-3);
  background:var(--sand-100); padding:3px 9px; border-radius:4px; display:inline-block}
.pd-price{font-size:2.1rem; font-weight:800; color:var(--green-800); margin:18px 0 4px;
  font-variant-numeric:tabular-nums}
.pd-price small{font-size:var(--fs-md); font-weight:500; color:var(--ink-3)}
.pd-actions{display:flex; gap:12px; margin-top:24px; flex-wrap:wrap}
.pd-actions .btn{flex:1; min-width:150px}
.spec-grid{display:grid; grid-template-columns:repeat(2,1fr); gap:0;
  border:1px solid var(--line); border-radius:var(--radius); overflow:hidden}
.spec-grid div{padding:11px 14px; font-size:var(--fs-md); border-bottom:1px solid var(--line)}
.spec-grid div:nth-child(odd){background:var(--sand-50); color:var(--ink-3)}
.spec-grid div:nth-child(even){font-weight:600}

/* ---------- 报价侧栏 ---------- */
.quote-bar{position:fixed; inset-inline-end:0; inset-block:0; width:420px; max-width:92vw;
  background:#fff; border-inline-start:1px solid var(--line); box-shadow:var(--shadow-lg);
  transform:translateX(105%); transition:.3s cubic-bezier(.4,0,.2,1); z-index:200;
  display:flex; flex-direction:column}
html[dir="rtl"] .quote-bar{transform:translateX(-105%)}
.quote-bar.open{transform:translateX(0)}
.qb-hd{padding:18px 20px; border-bottom:1px solid var(--line); display:flex; align-items:center; gap:12px}
.qb-hd h3{margin:0; flex:1; font-size:1.05rem}
.qb-close{background:none; border:0; font-size:var(--fs-22); cursor:pointer; color:var(--ink-3); line-height:1}
.qb-body{flex:1; overflow-y:auto; padding:14px 20px}
.qb-ft{border-top:1px solid var(--line); padding:16px 20px; background:var(--sand-50)}
.qb-line{display:grid; grid-template-columns:52px 1fr auto; gap:12px; padding:12px 0;
  border-bottom:1px solid var(--line); align-items:start}
.qb-line img{width:52px;height:52px;object-fit:cover;border-radius:6px;background:var(--sand-100)}
.qb-line .nm{font-size:var(--fs-sm); font-weight:600; line-height:1.35; margin-bottom:5px;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.qb-line .cd{font-size:var(--fs-2xs); color:var(--ink-3); font-family:ui-monospace,monospace}
.qb-qty{display:flex; align-items:center; gap:5px; margin-top:6px}
.qb-qty button{width:26px;height:26px;border:1px solid var(--line);background:#fff;
  border-radius:5px;cursor:pointer;font-size:var(--fs-md);line-height:1;display:grid;place-items:center}
.qb-qty input{width:66px; padding:4px 6px; text-align:center; border:1px solid var(--line);
  border-radius:5px; font-size:var(--fs-sm)}
.qb-qty button:disabled{opacity:.35;cursor:not-allowed;background:#f2f2f2;color:var(--ink-3)}
.qb-moq-hint{font-size:var(--fs-2xs);color:var(--ink-3);margin-top:4px}
.qb-rm{background:none;border:0;color:var(--ink-3);cursor:pointer;font-size:var(--fs-17);padding:0 2px}
.qb-rm:hover{color:var(--red)}
.qb-total{display:flex; justify-content:space-between; font-size:var(--fs-15); margin-bottom:6px}
.qb-total b{font-size:1.3rem; color:var(--green-800); font-variant-numeric:tabular-nums}
.qb-empty{text-align:center; color:var(--ink-3); padding:52px 20px; font-size:var(--fs-md)}
.qb-fab{position:fixed; inset-block-end:18px; inset-inline-end:26px; z-index:150;
  background:var(--green-700); color:#fff; border:0; border-radius:100px; padding:15px 24px;
  font-weight:650; font-size:var(--fs-15); box-shadow:var(--shadow-lg); cursor:pointer;
  display:flex; align-items:center; gap:10px}
.qb-fab:hover{background:var(--green-800)}
.qb-fab .cnt{background:var(--gold); color:var(--gold-on-2); border-radius:100px; padding:1px 9px;
  font-size:var(--fs-xs); font-weight:800}
.qb-mask{position:fixed; inset:0; background:rgba(18,48,31,.4); z-index:190; display:none}
.qb-mask.show{display:block}

/* ---------- 认证区 ---------- */
/* ★ 2026-10-15 视觉统一（张总："色块不 harmonious/不和谐"）：
   原来 auto-fit minmax(150px,1fr) 在 1232px 内容宽下算出 **7 列**，
   9 张证书排成 7+2，末行右侧空洞 **891px** —— 这是"宽窄不和谐"最直接的来源。
   改 3 列 ⇒ 9 = 3×3，整除、无空洞。
   ⚠️ 两条媒体查询是必须的：本文件里 .certs 原本**一条响应式都没有**，
      写死 3 列后 375px 下每张只有 98px，内容区剩 66px，es 语种证书名
      （比英文长 58%）会折 7 行，挤成竖条。 */
.certs{display:grid; grid-template-columns:repeat(3,1fr); gap:var(--grid-gap)}
@media (max-width:900px){ .certs{grid-template-columns:repeat(2,1fr)} }
@media (max-width:560px){ .certs{grid-template-columns:1fr} }
/* ★ 2026-10-15：圆角 --radius(8px) → --card-radius(=--radius-lg 14px)。
   同为"卡片"，全站其它卡（.panel/.pcard/.cat-tile）都是 14px，只有 .cert 是 8px。
   ⚠️ 只动这一条，**不要全局替换 --radius** —— 它还被按钮/输入框/徽章
      大量引用，且本文件里有 7 处物理 border-left / border-bottom-left-radius
      （RTL 缺陷，改错会破阿语站）。 */
.cert{border:1px solid var(--line); border-radius:var(--card-radius); padding:22px var(--card-pad-sm);
  text-align:center; background:#fff}
.cert .ic{width:52px;height:52px;margin:0 auto 12px;border-radius:50%;
  background:var(--green-100); display:grid;place-items:center;font-size:var(--fs-24)}
.cert b{display:block; font-size:var(--fs-md); margin-bottom:3px}
.cert span{font-size:var(--fs-xs); color:var(--ink-3)}
.cert.pending .ic{background:var(--sand-100); color:var(--ink-3)}

/* ---------- 页脚 ---------- */
.ftr{background:var(--green-900); color:#9fb8ab; padding:60px 0 26px; font-size:var(--fs-md)}
.ftr a:hover{color:#fff}
.ftr-grid{display:grid; grid-template-columns:1.6fr 1fr 1fr 1fr; gap:40px; margin-bottom:44px}
.ftr h4{color:#fff; font-size:var(--fs-md); margin-bottom:16px; letter-spacing:.03em}
.ftr ul{list-style:none; padding:0; margin:0; display:flex; flex-direction:column; gap:9px}
.ftr .logo{color:#fff}
.ftr .logo small{color:var(--green-on-dark-2)}
.ftr-bottom{border-top:1px solid rgba(255,255,255,.12); padding-top:22px;
  display:flex; justify-content:space-between; gap:20px; flex-wrap:wrap; font-size:var(--fs-xs); color:var(--green-on-dark-2)}
.ftr-note{font-size:var(--fs-xs); color:var(--green-on-dark-2); margin-top:14px; line-height:1.7}

/* ★★ 2026-10-21（张总「网站上的页面每一个都要有复制转发的功能」）：
   页脚那一行分享按钮（`layouts/base.html` 的 `#xjShare`，全站每一页）。
   · 按钮用**透明底 + 细边**：页脚是深绿底，实心按钮会跟上面四栏抢注意力，
     而这一行的定位是"顺手就能用"，不是主 CTA。
   · `min-height:36px`：桌面可点；手机另有一条 44px 的规则（本文件下方
     `.ftr-share-btn` 的 mobile 段），与全站"手机上 44×44"的下限同一口径。
   · `:focus-visible` 必须有：键盘 Tab 到这颗按钮时要看得见焦点
     （原先页脚里没有一个可聚焦元素，这条是新加的）。 */
.ftr-share{display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-top:18px}
.ftr-share-btn{display:inline-flex; align-items:center; justify-content:center; gap:6px;
  min-height:36px; padding:0 14px; border:1px solid rgba(255,255,255,.28); border-radius:999px;
  background:transparent; color:#fff; font:inherit; font-size:var(--fs-xs); cursor:pointer;
  white-space:nowrap}
.ftr-share-btn:hover{border-color:#fff; background:rgba(255,255,255,.08)}
.ftr-share-btn:focus-visible{outline:2px solid var(--green-on-dark-2); outline-offset:2px}
.ftr-share-msg{font-size:var(--fs-xs); color:var(--green-on-dark-2)}
.ftr-share-msg[data-on]{color:#fff}
@media (max-width:720px){
  .ftr-share{gap:8px}
  .ftr-share-btn{min-height:44px; padding:0 16px}
}

/* ★★★ 2026-10-17（张总截图红框「**错位**」）：菜单折行后**压到首屏大图上**。
   现象：英文/长词语言在 1180–1920px 这一段，第 9、10 项（Business Partners /
   Contact）被折到第二、三行；而 `.hdr .wrap` 是**写死 70px 高** ⇒ 折出来的那两行
   **溢出**头部，浮在 hero 图上，看起来就是"Contact 按钮错位"。
   （实测：1280px 两行、1366–1920px 甚至三行，navH 41/42/91 > 70。）

   修法（让它自己长，不猜断点）：
   ① `.hdr .wrap` 由 `height:70px` 改成 `min-height:70px; height:auto`（上面那一条）；
   ② 手机/窄屏下拉菜单原先写死 `inset-block-start:70px`，头部一旦变高就会错位
      ⇒ 改成 `100%`（相对 `.hdr`，跟头部同高）；
   ③ 顺带把折行时的行距放松一点，别让两行挤在一起。 */
.nav{row-gap:8px}

/* ★★ 2026-10-17（张总「错位」）：让英文/长词菜单在桌面**排成一行**。
   实测（1280 宽，英文 10 项）：可用 873px，项目合计 851px，但 9 个 4px 间隙
   + CTA 的 8px 左边距 = 887 > 873 ⇒ 只差 14px 就把最后一项挤到第二行；
   1366–1920 同理只差 15px（因为 `.wrap` 上限 1280）。
   ⇒ 收掉间隙、按宽度分两档收窄内边距，一行放得下；放不下的宽度由汉堡接手。 */
.nav{gap:2px}
@media (max-width:1400px){
  .nav{gap:0}
  .nav a{padding:8px 8px}
  .nav .cta{padding:10px 12px; margin-inline-start:4px}
}
@media (max-width:1220px){
  .nav a{padding:8px 6px}
  .nav .cta{padding:9px 10px; margin-inline-start:2px}
}

/* ★★ 2026-10-17（张总「错位」）：**头部单独放宽**，让 10 项排成一行。
   实测：正文 `.wrap` 上限 1280px，而英文菜单 10 项在 1280 里差 14–15px 放不下
   ⇒ 最后两项被折到第二行（1280 宽时 Contact 单独一行，视觉上就是"按钮错位"）。
   ⇒ 只给 `.hdr .wrap` 放宽到 1440px（**正文不动**）：桌面上一行排得下，
     移动端 `max-width` 不起作用、行为一个字节不变。 */
.hdr .wrap{max-width:1440px; margin-inline:auto; padding-inline:var(--pad,32px)}

/* ★★ 2026-10-17（张总「错位」）：桌面菜单**排成一行**。
   实测（1440 宽、英文 10 项）：nav 可用 972px，项目合计 946 + 间隙 18 + CTA 左边距 4
   = 968 —— 只差几个像素就换行，于是最后一项（Contact）被挤到第二行，
   看起来就是"按钮错位"。
   ⇒ 桌面统一用 `--fs-sm` + 8/10 内边距（省出约 100px），一行稳稳放下；
     窄屏（≤1080）本来就走汉堡，行为不变。 */
@media (min-width:1081px){
  .nav{gap:0}
  .nav a{padding:8px 10px; font-size:var(--fs-sm)}
  .nav a.on{padding:10px 14px}
  .nav .cta{padding:10px 14px; margin-inline-start:6px}
}

/* ---------- 响应式 ---------- */
/* ★ 中屏（821px–1340px）导航收窄 —— 这是实测量出来的修复，不是"顺手防一手"。
   2026-09-17 加「业务伙伴」入口时用 Playwright 把 9 语种 × 8 宽度全量扫了一遍：
   .nav a 是 white-space:nowrap，英/法/西/葡/俄语菜单文字比中文长得多，
   原来在 1024–1340px 这一整段就会顶出容器（法/西语连 1440px 都溢出）。
   收窄间距与内边距后，14 个"溢出"组合归零，其余组合的溢出量也从
   100–200px 降到 10–50px（剩下的属于既有问题，另行处理）。
   手机在 820px 以下本来就走汉堡菜单，所以这段只影响平板/小笔记本。 */
@media (max-width:1340px){
  .hdr .wrap{gap:16px}
  .nav{gap:2px}
  .nav a{padding:8px 9px; font-size:var(--fs-md)}
  .nav .cta{padding:10px 14px; margin-inline-start:5px}
  .nav a.on{padding:10px 14px}
}
/* ★★★ 2026-10-15 真浏览器巡检抓到的真溢出（基因：不许靠"猜一个断点"糊过去）
   现象：印尼语 +37px、越南语 +42px 横向溢出，**中文/英/西/阿/葡/法/俄全正常**。
   真凶：顶部 `.nav` —— 越南语菜单（Trang chủ / Sản phẩm / Nhà máy / Bán buôn /
   Mẫu miễn phí / Blog / Về chúng tôi / Đối tác）单词比中文长得多，
   `white-space:nowrap` 的链接在一行里排不下 ⇒ 整条 nav 撑破 767px 的容器。

   ★★ 为什么上面那个 1340px 断点救不了：
     那一段是**按"某个宽度会溢出"硬猜的**——注释里自己写着
     「法/西语连 1440px 都溢出……剩下的另行处理」。
     而**菜单宽度取决于译文语言，不取决于屏幕宽度**：
     中文 5 个字能塞进 1340px，越南语同样 5 个字在 981px 就撑破。
     按语言排期，就得为每门语各猜一个断点（9 门 = 9 组魔法数字），必然漏。

   修法（让内容自己说话，不猜）：
   ① 头容器 `.hdr .wrap` 允许收缩（`min-width:0`）—— flex 子项默认不肯缩，
      这是"撑破"能传出去的直接原因；
   ② `.nav` 允许换行包裹（`flex-wrap:wrap`）—— 宁可菜单折到第二行，
      也不让整页出现横向滚动条（手机上左右晃是硬伤）；
   ③ 收窄超长语种的链接内边距与字号一档，用现有令牌 `--fs-sm`；
   ④ 汉堡断点**提高到 1080px**：窄到这个程度，9 门语种的菜单在平板上
      本来就不该平铺，切汉堡更稳。 */
.hdr .wrap{min-width:0}
/* ★ 2026-10-15 移动端溢出修复：法语等长词语言的 tagline
   （"Vente au grossier de plantes…"）是 nowrap，flex item 默认
   min-width:auto 不肯收缩，把 .burger 挤出容器 31px → 整页横向滚动条。
   修法：logo 允许收缩 + tagline 省略号截断 + burger 不许被压。 */
.hdr .logo{min-width:0; overflow:hidden}
.hdr .logo small{overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.burger{flex:none}
.nav{flex-wrap:wrap; row-gap:6px; min-width:0}
@media (max-width:1180px){
  .nav{gap:0}
  .nav a{padding:8px 7px; font-size:var(--fs-sm)}
  .nav .cta{padding:10px 12px; margin-inline-start:4px}
  .nav a.on{padding:10px 12px}
}
@media (max-width:1080px){
  /* ★ 2026-10-17：`inset-block-start` 由写死的 `70px` 改成 `100%`
     —— 头部现在是 `min-height:70px; height:auto`（菜单折行时会变高），
     写死 70px 的话窄屏下拉菜单会插进头部里（与「错位」同一个病根）。 */
  .nav{display:none; position:absolute; inset-block-start:100%; inset-inline:0;
    background:#fff; flex-direction:column; padding:14px; gap:2px;
    border-bottom:1px solid var(--line); box-shadow:var(--shadow)}
  .nav.open{display:flex}
  .burger{display:block}
}
@media (max-width:1080px){
  .pcards{grid-template-columns:repeat(3,1fr)}
  /* ★ 2026-10-15 视觉统一：平板回正商品卡配图比例。
     ≤820px 是 2 列且列宽达 374px，4/5 会把图拉到 467px 高（近半屏），
     该段凭空长 561px。基线 --ratio-tall 是给桌面 4 列（290px 列宽）设计的，
     平板用 1/1 才平衡。 */
  .pcard-img{aspect-ratio:var(--ratio-square)}
  .g4{grid-template-columns:repeat(2,1fr)}
  /* ★ .g3 原本缺 1080 档（.g4 降 2 列、.g3 不降），两个节奏并排会显形。
     补上后与 .g4 一致。影响 samples / wholesale 两页的 .g3。 */
  .g3{grid-template-columns:repeat(2,1fr)}
  .hero .wrap{grid-template-columns:1fr; gap:36px}
  .hero h1{font-size:2.4rem}
  .pd{grid-template-columns:1fr; gap:30px}
  .ftr-grid{grid-template-columns:1fr 1fr}
}
@media (max-width:820px){
  /* ★ 2026-10-17：与 1080px 那段同一个理由 —— 头部高度不再写死 70px，
     下拉菜单必须用 `100%` 跟着头部走（写死 70px 会插进头部里）。 */
  .nav{display:none; position:absolute; inset-block-start:100%; inset-inline:0;
    background:#fff; flex-direction:column; padding:14px; gap:2px;
    border-bottom:1px solid var(--line); box-shadow:var(--shadow)}
  .nav.open{display:flex}
  .nav a{width:100%}
  .nav .cta{margin:8px 0 0}
  .burger{display:block}
  .hdr .wrap{gap:14px}
  .hdr-tools{margin-inline-start:0}
  .sec{padding:52px 0}
  .pcards{grid-template-columns:repeat(2,1fr); gap:14px}
  .g2,.g3,.g4,.g5{grid-template-columns:1fr}
  .form-grid{grid-template-columns:1fr}
  .filters{flex-direction:column; align-items:stretch}
  .searchbox{margin:0}
  .searchbox input{width:100%}
  h1{font-size:2rem} h2{font-size:1.45rem}
  .hero{padding:56px 0 48px}
  .hero h1{font-size:2rem}
  .hero-stats{gap:24px}
  .topbar-l{display:none}
  /* ★ 顶栏移动端精简: 批发/样品入口导航里都有, 隐藏避免拥挤 (七专家评审) */
  .topbar-r .tb-link{display:none}
  /* ★★ 2026-10-14（第 881 批 · 张总「前台的后台入口要界面固定起来」）：
     **伙伴态那个 🏠 我的后台必须豁免** —— 上面的规则把它一起藏了，
     而伙伴多数是**在手机上**点邮件里的专属链接进来的：
     一离开门户页，顶栏没有入口、导航里也没有 ⇒ 只能靠记住网址。
     只豁免这一个类，其余 tb-link 照旧隐藏（访客与客户一个像素不变）。 */
  .topbar-r .tb-link.tb-portal{display:inline-flex}
  .ftr-grid{grid-template-columns:1fr; gap:28px}
}
@media (max-width:480px){
  /* ★ B2B 扫货场景: 移动端热销保持 2 列, 比单列更高效 (七专家评审 P0) */
  .pcards{grid-template-columns:1fr 1fr; gap:12px}
  .pcard-body{padding:12px 12px 14px}
  .pcard-name{font-size:var(--fs-sm)}
  .wrap{padding:0 16px}
}

/* ══════════════════════════════════════════════════════════════
   移动端底部悬浮 CTA (七专家评审 P0: WhatsApp/邮箱一键转化)
   ★ 只在手机显示, 桌端隐藏。
   ★ 报价 FAB(.qb-fab) 已在右下, 这里放底部整条, 二者错开:
     悬浮栏固定在底部, FAB 上移到悬浮栏上方。
   ══════════════════════════════════════════════════════════════ */
.mobile-cta{display:none}
@media(max-width:820px){
  /* ★ 2026-10-159 门外语窄屏：写死 `1fr 1fr` 两列时，列宽被长词撑开
     （俄语 "Получить коммерческое предложение" ⇒ 单列 176px，整页横向溢出 34px）。
     改 `minmax(0,1fr)`：列宽封顶在可用空间内，按钮文字自己折行。 */
  .mobile-cta{display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px;
    position:fixed; inset-inline:0; bottom:0; z-index:60;
    padding:10px 14px calc(10px + env(safe-area-inset-bottom));
    background:rgba(255,255,255,.96); border-top:1px solid var(--line);
    box-shadow:0 -4px 20px rgba(18,48,31,.10); backdrop-filter:blur(8px)}
  .mobile-cta .btn{padding:13px 10px; font-size:var(--fs-md); border-radius:10px;
    min-width:0; max-width:100%; white-space:normal; overflow-wrap:break-word}
  /* WhatsApp 绿色 */
  .mobile-cta .btn-wa{background:#25D366; color:#fff}
  .mobile-cta .btn-wa:hover{background:#1fb457; transform:none}
  /* 给页面底部留出悬浮栏高度, 避免遮挡页脚
     ★★ 2026-09-20 修：原来只留 64px = 悬浮栏自己的高度，**没算悬浮栏上方那两个浮标**
        （.qb-fab 报价胶囊底距 78px、.xj-chat-fab 客服球底距 140px+56 高）。
        实测（390×844 手机视口，滚到页脚最底）：页脚最后两行被压住 ——
          "© 2026 Yiwu Xijie Houseware Co., Ltd..." 被客服球压 20px
          "Privacy Policy" 被报价胶囊压 12px
        也就是上面那条注释写的"避免遮挡页脚"**并没有做到**。
        实测 180px 即可完全让开（64 不够 / 180、200、210 都够），取最小的 180。 */
  body.has-mcta{padding-bottom:180px}
  /* 悬浮栏存在时报价 FAB 上移避让 */
  /* ★★ 2026-10-21（张总：「手机版还没有改」）：以下几条 `has-mcta` 几何规则**已删**。
   原因：`body.has-mcta` 是「底部悬浮 CTA 条存在」的时候加的类（`base.html:128`），
   而那条底栏 **2026-09 已按张总「底下的不要了」彻掉了**（模板里已经没有
   `.mobile-cta` 这个元素），但这几个偏移量留着 ⇒ 手机上两个悬浮件被白白抬高
   60~几百 px（实测：375×667 下胶囊 bottom=78、客服球 136，而底下什么都没有）。
   ⇒ 手机与桌面面现在**同一套数字**（胶囊 18、客服球 84、聊天窗 40）。
   ★ 以后真要恢复底栏：把那些偏移量加回来即可，不要再用一个“配了 WhatsApp 就算有底栏”的类。 */
  /* 移动端 FAB 紧凑化, 少挡内容 */
  .qb-fab{padding:12px 18px; font-size:var(--fs-md); inset-inline-end:14px}
}

/* ══════════════════════════════════════════════════════════════
   博客 / SEO 内容模块
   ★ 全部用逻辑属性(padding-inline / margin-inline)而非左右固定值,
     这样阿语 RTL 下自动镜像, 不需要写 rtl 专用覆盖规则。
   ══════════════════════════════════════════════════════════════ */

.blog-grid{display:grid; grid-template-columns:repeat(3,1fr); gap:22px}
.blog-card{display:flex; flex-direction:column; background:#fff;
  border:1px solid var(--line); border-radius:var(--radius); overflow:hidden;
  text-decoration:none; color:inherit; transition:box-shadow .18s, transform .18s}
.blog-card:hover{box-shadow:var(--shadow); transform:translateY(-2px)}
.blog-card-cover{aspect-ratio:16/9; background:var(--sand-50); overflow:hidden}
.blog-card-cover img{width:100%; height:100%; object-fit:cover; display:block}
.blog-card-ph{display:grid; place-items:center; height:100%; color:var(--green-600);
  font-size:var(--fs-sm); font-weight:500; letter-spacing:.02em}
.blog-card-body{padding:16px 18px 18px; display:flex; flex-direction:column; flex:1; gap:8px}
.blog-card-cat{font-size:var(--fs-xs); font-weight:500; color:var(--gold);
  text-transform:uppercase; letter-spacing:.06em}
.blog-card-ttl{font-size:1.02rem; line-height:1.35; margin:0; font-weight:500}
.blog-card-sum{font-size:var(--fs-sm); color:var(--ink-3); line-height:1.55; margin:0;
  display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden}
.blog-card-meta{margin-block-start:auto; padding-block-start:10px; border-block-start:1px solid var(--line);
  font-size:var(--fs-xs); color:var(--ink-3); display:flex; justify-content:space-between; gap:10px}
.blog-langs{display:flex; gap:4px}
.blog-langs b{font-size:var(--fs-2xs); font-weight:500; padding:1px 5px; border-radius:3px;
  background:var(--sand-50); color:var(--green-600)}

.post-layout{display:grid; grid-template-columns:minmax(0,1fr) 260px; gap:44px; align-items:start}
.post-main{min-width:0}
.post-hd{margin-bottom:26px}
.post-cat{font-size:var(--fs-xs); font-weight:500; color:var(--gold);
  text-transform:uppercase; letter-spacing:.06em; margin-bottom:8px}
.post-ttl{font-size:2.05rem; line-height:1.25; margin:0 0 12px}
.post-sum{font-size:1.02rem; color:var(--ink-3); line-height:1.6; margin:0 0 14px}
.post-meta{font-size:var(--fs-xs); color:var(--ink-3); display:flex; flex-wrap:wrap;
  gap:8px; align-items:center; padding-block-end:14px; border-block-end:1px solid var(--line)}
.post-alt{display:flex; gap:10px; margin-inline-start:auto}
.post-alt a{color:var(--green-600); text-decoration:none}
.post-alt a:hover{text-decoration:underline}
.post-alt b{font-weight:500; color:var(--ink)}
.post-cover{width:100%; border-radius:var(--radius); margin-block:22px; display:block}
/* ★ 正文视频（#43 第二段）：壳子定 aspect-ratio 的兜底值，避免"视频加载前
   整段内容跳一下"（CLS 会被搜索引擎计入页面体验）。真正的比例由视频自身决定，
   宽高都写 100%/auto，只在元数据到达前用 aspect-ratio 占位。
   竖屏片段（抖音/视频号那种 9:16）在正文里不设 max-height 会吃掉一整屏，
   所以封顶 78vh。 */
.post-video{width:100%; height:auto; max-height:78vh; display:block;
  border-radius:var(--radius); margin-block:22px; background:#000}
.post-video-cap{font-size:var(--fs-xs); color:var(--ink-3); margin:-12px 0 22px}

.post-body{font-size:1.01rem; line-height:1.78; color:var(--ink)}
.post-body h2{font-size:1.4rem; margin:2.1em 0 .7em; line-height:1.35}
.post-body h3{font-size:1.13rem; margin:1.7em 0 .6em; line-height:1.4}
.post-body h4{font-size:1rem; margin:1.5em 0 .5em}
.post-body p{margin:0 0 1.1em}
.post-body ul,.post-body ol{margin:0 0 1.2em; padding-inline-start:1.4em}
.post-body li{margin-bottom:.45em}
.post-body blockquote{margin:1.4em 0; padding:14px 18px; background:var(--sand-50);
  border-inline-start:3px solid var(--green-600); border-radius:0 var(--radius) var(--radius) 0;
  color:var(--ink-3); font-size:.97rem}
.post-body code{background:var(--sand-50); padding:2px 5px; border-radius:4px;
  font-size:.9em; font-family:ui-monospace,Menlo,Consolas,monospace}
.post-body hr{border:0; border-block-start:1px solid var(--line); margin:2em 0}
.post-body a{color:var(--green-600)}
.post-body img{max-width:100%; height:auto; border-radius:var(--radius); margin:1.2em 0}

.post-faq{margin-block-start:44px; padding-block-start:28px; border-block-start:1px solid var(--line)}
.post-faq h2{font-size:1.35rem; margin:0 0 16px}
.faq-item{border:1px solid var(--line); border-radius:var(--radius); margin-bottom:10px;
  background:#fff; overflow:hidden}
.faq-item summary{padding:14px 18px; cursor:pointer; font-weight:500; font-size:.99rem;
  list-style:none; display:flex; justify-content:space-between; gap:14px; align-items:center}
.faq-item summary::-webkit-details-marker{display:none}
.faq-item summary::after{content:"+"; color:var(--green-600); font-size:1.15rem;
  flex-shrink:0; line-height:1}
.faq-item[open] summary::after{content:"\2013"}
.faq-item[open] summary{border-block-end:1px solid var(--line)}
.faq-a{padding:14px 18px; font-size:.96rem; color:var(--ink-3); line-height:1.7}

.post-rel{margin-block-start:44px; padding-block-start:28px; border-block-start:1px solid var(--line)}
.post-rel h2{font-size:1.35rem; margin:0 0 18px}

.post-cta{margin-block-start:44px; padding:30px 26px; background:var(--sand-50);
  border-radius:var(--radius); text-align:center}
.post-cta h2{font-size:1.28rem; margin:0 0 8px}
.post-cta p{color:var(--ink-3); font-size:.97rem; margin:0 0 20px; line-height:1.6}
.post-cta-btns{display:flex; gap:12px; justify-content:center; flex-wrap:wrap}

.post-side{position:sticky; inset-block-start:20px; display:flex; flex-direction:column; gap:20px}
.post-toc{background:#fff; border:1px solid var(--line); border-radius:var(--radius); padding:18px 20px}
.post-toc h3{font-size:var(--fs-xs); font-weight:500; text-transform:uppercase; letter-spacing:.06em;
  color:var(--ink-3); margin:0 0 12px}
.post-toc ul{list-style:none; margin:0; padding:0}
.post-toc li{margin-bottom:8px; font-size:var(--fs-sm); line-height:1.45}
.post-toc li.lv3{padding-inline-start:14px; font-size:var(--fs-xs)}
.post-toc a{color:var(--ink-3); text-decoration:none}
.post-toc a:hover{color:var(--green-600)}
.post-side-cta{background:var(--green-900); color:#fff; border-radius:var(--radius); padding:22px 20px}
.post-side-cta h3{font-size:1.02rem; margin:0 0 8px; color:#fff}
.post-side-cta p{font-size:var(--fs-xs); line-height:1.6; margin:0 0 16px; opacity:.82}
.post-side-cta .btn{width:100%; margin-bottom:9px}
.post-side-cta .btn-ghost{color:#fff; border-color:rgba(255,255,255,.35); background:transparent}

/* 阿语 RTL: 侧栏与正文顺序镜像 */
html[dir="rtl"] .post-layout{grid-template-columns:260px minmax(0,1fr)}
html[dir="rtl"] .post-body blockquote{border-inline-start:3px solid var(--green-600)}

@media (max-width:900px){
  .post-layout{grid-template-columns:1fr; gap:32px}
  html[dir="rtl"] .post-layout{grid-template-columns:1fr}
  .post-side{position:static; order:-1}
  .post-ttl{font-size:1.6rem}
  .blog-grid{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:600px){
  .blog-grid{grid-template-columns:1fr}
  .post-body{font-size:.98rem}
  .post-alt{margin-inline-start:0; width:100%}
}

/* ==========================================================================
   客户登录 + 价格控价 + 报价页选品器
   ========================================================================== */

/* ── 顶栏登录态 ── */
.tb-user{color:var(--green-100); font-size:var(--fs-xs); opacity:.85;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:150px}
.tb-login{color:var(--gold); font-weight:600}
.tb-login:hover{color:#fff}

/* ── 价格锁定态 ──
   ★ 不用纯灰色块: 客户要能一眼看出"这是可以解锁的", 所以做成可点链接,
     带锁图标 + 与站点绿色一致的强调色。 */
.pcard-lock{font-size:var(--fs-xs); color:var(--green-600); font-weight:600;
  text-decoration:none; display:inline-flex; align-items:center; gap:4px}
.pcard-lock:hover{color:var(--green-800); text-decoration:underline}

/* ── 侧栏报价单里的锁提示 ── */
.qb-lock{font-size:var(--fs-xs); color:var(--ink-3); line-height:1.5; display:block;
  max-width:120px; text-align:end}
/* ★★ 2026-10-03：这把锁**从 `<span>`（点不动）改成了 `<a href="/login">`** ——
   原来同一句提示在总额那处能点、在抽屉每一行里点不动，两处不一致（客户看到
   「🔒 登录后查看价格」却点不动）。这里只补"它现在是个链接"该有的样子：
   下划线 + 悬停变色；**不加新类、不改上面那条既有规则**（span 形态若别处还在也没影响）。 */
a.qb-lock{text-decoration:underline}
a.qb-lock:hover{color:var(--brand,#1a7f5a)}

/* ── 报价页选品器: 搜索结果行 ── */
.qp-row{display:flex; align-items:center; gap:12px; padding:11px 14px;
  border-bottom:1px solid var(--line); transition:background .12s}
.qp-row:last-child{border-bottom:0}
.qp-row:hover{background:var(--sand-50)}
.qp-row img{width:44px; height:44px; object-fit:cover; border-radius:6px; flex:none;
  background:var(--sand-100)}
.qp-row .btn{padding:5px 13px; font-size:var(--fs-16); line-height:1.2; flex:none; min-width:38px}

/* ── 报价页选品器: 弹窗 ── */
.qp-modal{position:fixed; inset:0; z-index:1200; display:flex;
  align-items:center; justify-content:center; padding:20px}
.qp-modal-mask{position:absolute; inset:0; background:rgba(18,48,31,.55);
  backdrop-filter:blur(2px)}
.qp-modal-box{position:relative; background:#fff; border-radius:14px;
  width:100%; max-width:620px; max-height:82vh; display:flex; flex-direction:column;
  box-shadow:0 20px 60px rgba(0,0,0,.28); overflow:hidden}
.qp-modal-hd{display:flex; align-items:center; justify-content:space-between;
  padding:18px 22px; border-bottom:1px solid var(--line)}
.qp-modal-hd h3{margin:0; font-size:1.02rem}
.qp-modal-body{padding:18px 22px 22px; overflow-y:auto}

@media (max-width:700px){
  .qp-modal{padding:0; align-items:flex-end}
  .qp-modal-box{max-height:88vh; border-radius:14px 14px 0 0; max-width:100%}
  .qp-row img{width:38px; height:38px}
}

/* ── 登录页 ── */
.lg-wrap{max-width:520px; margin:0 auto}
#lgForm .field input{font-size:var(--fs-15); padding:12px 14px}

/* 阿语 RTL 适配(锁图标/对齐镜像) */
html[dir="rtl"] .qb-lock{text-align:start}
html[dir="rtl"] .pcard-lock{flex-direction:row-reverse}

/* ══════════ AI 客服聊天窗 ══════════ */
.xj-chat-fab{position:fixed; right:18px; bottom:18px; z-index:9990; width:56px; height:56px;
  border-radius:50%; background:var(--brand,#1a7f5a); color:#fff; border:none; cursor:pointer;
  display:flex; align-items:center; justify-content:center; box-shadow:0 6px 20px rgba(0,0,0,.25);
  transition:transform .15s ease}
.xj-chat-fab:hover{transform:scale(1.06)}
.xj-chat-fab svg{width:26px; height:26px}
.xj-chat-fab .dot{position:absolute; top:-2px; right:-2px; width:14px; height:14px;
  border-radius:50%; background:#e53935; border:2px solid #fff}

.xj-chat-box{position:fixed; right:18px; bottom:84px; z-index:9990; width:360px; max-width:calc(100vw - 36px);
  height:520px; max-height:calc(100vh - 110px); background:var(--panel,#fff); border-radius:16px;
  box-shadow:0 20px 60px rgba(0,0,0,.3); display:flex; flex-direction:column; overflow:hidden;
  border:1px solid var(--line,#e2e6e1)}
.xj-chat-box[hidden]{display:none}
.xj-chat-hd{display:flex; align-items:center; gap:10px; padding:14px 16px;
  background:var(--brand,#1a7f5a); color:#fff}
.xj-chat-hd .avatar{width:36px; height:36px; border-radius:50%; background:rgba(255,255,255,.2);
  display:flex; align-items:center; justify-content:center; font-size:var(--fs-18); flex-shrink:0}
.xj-chat-hd .meta{flex:1; min-width:0}
.xj-chat-hd .meta b{display:block; font-size:var(--fs-md); line-height:1.2}
.xj-chat-hd .meta span{font-size:var(--fs-2xs); opacity:.85}
.xj-chat-hd .close{background:none; border:none; color:#fff; font-size:var(--fs-22); cursor:pointer;
  line-height:1; padding:0 4px}
/* ★★ 2026-10-03（张总「**聊天窗口可以有全屏放大功能**」）：头部那颗放大/还原按钮。
   与 🔔 / × 同一档观感（无边框、白描边），只是内容是内联 SVG ⇒ 得自己 flex 居中。
   ★ 用 SVG 不用 emoji：`⤢/⤡` 在很多手机上缺字形，会显示成豆腐块。 */
.xj-chat-hd .full{background:none; border:none; color:#fff; cursor:pointer; line-height:0;
  padding:0 4px; display:flex; align-items:center; opacity:.9}
.xj-chat-hd .full:hover{opacity:1}
/* ★★ 2026-10-03（张总「**现在手机选择发图片还是框挤在一堆 看不到聊天窗口了**」）：
   实测（真 Edge，375×812 与 1440×900 都一样）——抽屉一展开，会话区 296 → 153；
   **商品面板再一开，会话区只剩 28px**，最新那条消息完全在可视区外。
   病根：抽屉是**普通 flex 子项**（没有 position/inset/z-index），而会话区是唯一的
   `flex:1` 项 ⇒ 抽屉长多高，会话区就被挤掉多少。
   ⇒ 两道闸，两个都要（少一个都不够）：
     ① 会话区**保底 96px**（`min-height`）—— 任何情况下不许被挤到看不见；
     ② 抽屉**限高 + 自己滚**（`max-height:44%` + `overflow:auto`）—— 它不再无限长。
   ★ 为什么不把抽屉改成 `position:absolute` 浮层（那是验证表单的做法，`main.css:816`）：
     表单浮起来**盖住的是输入行**（它有 × 出口、那时客户本来也不打字）；抽屉里是
     "选商品/发附件"，客户看完就要**接着打字**，浮起来把输入行压住反而更别扭。
     限高 + 保底是"两边都留着"的那条路。 */
.xj-chat-body{flex:1 1 0; min-height:96px; overflow-y:auto; padding:14px;
  display:flex; flex-direction:column; gap:10px;
  background:var(--panel-2,#f5f7f5)}
.xj-msg{max-width:82%; padding:9px 13px; border-radius:14px; font-size:var(--fs-sm); line-height:1.5;
  white-space:pre-wrap; word-break:break-word}
/* ★★ 2026-10-03（张总「客人那边也要显示**是哪一个用户服务的**」）：顾问气泡上的小头像。
   16px、圆形、与那行小字抬头同一行；**画不出来就摘掉**（`onerror` 在 chat.js 里）⇒ 绝不留裂图。 */
.xj-msg .xj-msg-avatar{width:16px; height:16px; border-radius:50%; object-fit:cover;
  margin-inline-end:5px; vertical-align:-3px; background:#eef2ee}
.xj-msg.bot{align-self:flex-start; background:#fff; border:1px solid var(--line,#e2e6e1);
  border-bottom-left-radius:4px}
.xj-msg.user{align-self:flex-end; background:var(--brand,#1a7f5a); color:#fff;
  border-bottom-right-radius:4px}
.xj-msg .src{display:block; margin-top:6px; font-size:var(--fs-2xs); opacity:.55}
/* ★★ 2026-10-01（张总批注「客人在界面可见回复」）：顾问（真人）在后台回的每一句，
   都拉到这个聊天窗里显示 —— 与机器人（.bot）**看得出区别**：真人这条带左侧绿边 + 抬头。
   为什么不能长得一样：客户以为还在跟机器人绕圈，就不会停下来等人。 */
.xj-msg.staff{align-self:flex-start; background:#fff; border:1px solid var(--brand,#1a7f5a);
  border-left:4px solid var(--brand,#1a7f5a); border-bottom-left-radius:4px}
.xj-msg.staff .who{display:block; margin-bottom:4px; font-size:var(--fs-2xs);
  font-weight:700; color:var(--brand,#1a7f5a)}
.xj-chat-typing{align-self:flex-start; color:var(--ink-3,#888); font-size:var(--fs-xs); padding:2px 4px}
.xj-chat-ft{display:flex; gap:8px; padding:10px; border-top:1px solid var(--line,#e2e6e1);
  background:var(--panel,#fff)}
.xj-chat-ft input{flex:1; border:1px solid var(--line,#e2e6e1); border-radius:20px;
  padding:9px 14px; font-size:var(--fs-sm); outline:none}
.xj-chat-ft input:focus{border-color:var(--brand,#1a7f5a)}
.xj-chat-ft button{border:none; background:var(--brand,#1a7f5a); color:#fff; border-radius:20px;
  padding:0 16px; cursor:pointer; font-size:var(--fs-sm)}
.xj-handoff-btn{display:block; width:100%; text-align:center; margin-top:8px; padding:8px;
  border:1px dashed var(--brand,#1a7f5a); color:var(--brand,#1a7f5a); background:none;
  border-radius:10px; cursor:pointer; font-size:var(--fs-xs)}
.xj-handoff-btn:hover{background:var(--brand-soft,rgba(26,127,90,.08))}
.xj-handoff-form{padding:10px; border-top:1px solid var(--line,#e2e6e1); background:var(--panel,#fff);
  display:none; flex-direction:column; gap:8px}
/* ══════════════════════════════════════════════════════════════════════════
   ★★ 2026-10-03（张总批注「**这里的聊天界面太拥挤**」；他连续三次「继续」⇒
   按我在方案里的**建议 B3** 做，见 `20261003_聊天窗太拥挤与最新消息看不见_方案.md`）
   —— 验证表单**浮在会话区之上**，不再当 flex 子项去把会话区挤扁。
   实测（真 Edge，`tools/_a2xx_20261003_shot_chat_roomy.py`）：
     · 375×812：会话区 **296 → 92px**；360×640：**190 → 28px**；
     · 360 上表单 241px 连聊天窗（414）都装不下 ⇒ `formInside=False`（溢出）。
   ⇒  `.open` 时 `position:absolute` 贴底（聊天窗是 `position:fixed` ⇒ 它就是包含块，
      再加 `overflow:hidden`，表单不会跑到窗外），会话区高度**回到没展开时的 296/320px**。
   ★ 代价（写在明处）：展开期间表单**盖住**输入行与会话区下半部分 —— 那一刻客户要做的就是
     这一格验证，不该同时干别的；关窗 / 点遮罩 / 验证通过都回到原样。
   ★ 全部收在 `.open` 这一个状态里 ⇒ 去掉它就**逐字回到今天之前的样子**（可一键回退）。
   ══════════════════════════════════════════════════════════════════════════ */
.xj-handoff-form.open{display:flex; position:absolute; inset-inline:0; bottom:0;
  max-height:calc(100% - 60px); overflow:auto;
  box-shadow:0 -8px 24px rgba(18,48,31,.18); border-radius:12px 12px 0 0}
/* ★ 坑：聚光灯那条 `.xj-spot-target` 会把目标设成 `position:relative`（好让 z-index 生效），
   而表单在 B3 里**必须**是 absolute ⇒ 用 3 个类显式压回来（0,3,0 强过 0,1,0）。
   不写这一条，一开聚光灯表单就会从"浮起"变回"占位"，会话区又被挤扁。 */
.xj-handoff-form.open.xj-spot-target{position:absolute}
/* ★★ 2026-10-03（B3 带出来的必需件）：表单收起出口「×」。
   为什么必须有：B3 之后表单浮在会话区上、盖住输入行，而这格表单原来**没有关闭入口**
   ⇒ 表单一开客户连字都打不了（B3 引入的可用性回归，被聚光灯那套验收当场抓到：
   Playwright 报 `#xjHandoffForm … intercepts pointer events`）。只用符号、不加文案
   ⇒ **不新增 i18n key**（也就不欠翻译）。 */
.xj-hv-x{position:absolute; top:4px; inset-inline-end:6px; z-index:1;
  background:none; border:none;
  font-size:20px; line-height:1; color:var(--ink3,#77857c); cursor:pointer; padding:2px 8px;
  border-radius:8px}
/* ★ 为什么必须 `z-index:1`（实测踩到，别删）：`.xj-hv-thanks` 上有 `opacity:.85`，
   而 **opacity < 1 会造层叠上下文** ⇒ 它在**树序上更靠后**，就画到了这个绝对定位的
   「×」**上面**，把点击截走。实测：`elementFromPoint(× 中心)` 回的是 `#xjHandoffThanks`
   （Playwright 报 `intercepts pointer events`）。给「×」一个正 z-index 就压回去了。 */
.xj-hv-x:hover{background:var(--brand-soft,rgba(26,127,90,.08)); color:var(--brand,#1a7f5a)}
.xj-handoff-form input{padding:8px 12px; border:1px solid var(--line,#e2e6e1); border-radius:8px;
  font-size:var(--fs-sm); outline:none}
.xj-handoff-form button{padding:9px; border:none; background:var(--brand,#1a7f5a); color:#fff;
  border-radius:8px; cursor:pointer; font-size:var(--fs-sm)}
/* ★★ 2026-10-01（张总「让填写降级成小字」「表达谢意」）：转人工那一格的新排版 ——
   谢意与说明是**小字**（11.5px / ink3），邮箱格照旧，兜底那句（WhatsApp）是一行小字链接。
   这样"要填的东西"视觉上退到次要位置，主舞台留给"就在这儿跟真人说"。 */
.xj-hv-thanks{font-size:12px; line-height:1.6; color:var(--ink,#1c2b23); opacity:.85}
.xj-hv-tip{font-size:11.5px; line-height:1.5; color:var(--ink3,#77857c)}
.xj-hv-alt{font-size:11.5px}
.xj-hv-alt a{color:var(--brand,#1a7f5a); text-decoration:underline}
.xj-hv-alt-line{margin-top:4px; font-size:11.5px; color:var(--ink3,#77857c); background:none;
  border:none; padding:0}
.xj-hv-alt-line a{color:var(--brand,#1a7f5a)}
@media (max-width:600px){.xj-chat-box{right:10px; left:10px; width:auto}}

/* ══════════ 报价抽屉内选品（分类→产品→加入） ══════════ */
.qb-browse-btn{display:block;width:100%;padding:11px 14px;margin:2px 0 12px;
  border:1px dashed var(--green-600);border-radius:8px;background:transparent;
  color:var(--green-800);font:inherit;font-size:var(--fs-sm);font-weight:600;cursor:pointer}
.qb-browse-btn:hover{background:var(--sand-50)}
.qb-browse-hd{display:flex;align-items:center;gap:10px;margin-bottom:10px}
.qb-browse-hd b{font-size:var(--fs-md);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.qb-back{background:none;border:0;color:var(--ink-3);cursor:pointer;font:inherit;
  font-size:var(--fs-xs);padding:2px 0;white-space:nowrap}
.qb-back:hover{color:var(--green-800)}
.qb-search{width:100%;padding:9px 12px;border:1px solid var(--line);border-radius:8px;
  font:inherit;font-size:var(--fs-sm);margin-bottom:12px;background:#fff;color:var(--ink)}
.qb-cats{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:14px}
.qb-cat{display:flex;justify-content:space-between;align-items:center;gap:6px;
  padding:12px;border:1px solid var(--line);border-radius:8px;background:#fff;
  font:inherit;font-size:var(--fs-xs);font-weight:600;color:var(--ink);cursor:pointer;text-align:start}
.qb-cat:hover{border-color:var(--green-600);background:var(--sand-50)}
.qb-cat .lb{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.qb-cat .n{font-size:var(--fs-2xs);color:var(--ink-3);background:var(--sand-100);
  border-radius:100px;padding:1px 8px;font-weight:500;flex-shrink:0}
.qb-prod{display:grid;grid-template-columns:46px 1fr auto;gap:10px;align-items:center;
  padding:10px 0;border-bottom:1px solid var(--sand-100)}
.qb-prod img{width:46px;height:46px;object-fit:cover;border-radius:6px;background:var(--sand-100)}
.qb-prod .inf{min-width:0}
.qb-prod .nm{font-size:var(--fs-xs);font-weight:600;line-height:1.3;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.qb-prod .cd{font-size:var(--fs-2xs);color:var(--ink-3);font-family:ui-monospace,monospace}
.qb-prod .qb-p{font-size:var(--fs-xs);color:var(--green-800);font-weight:600;margin-top:2px}
.qb-addbtn{width:34px;height:34px;border-radius:50%;border:0;background:var(--green-800);
  color:#fff;font:inherit;font-size:var(--fs-17);cursor:pointer;line-height:1;flex-shrink:0}
.qb-addbtn:active{transform:scale(.94)}
.qb-more{display:block;width:100%;padding:10px;margin-top:12px;border:1px solid var(--line);
  border-radius:8px;background:#fff;font:inherit;font-size:var(--fs-sm);cursor:pointer;color:var(--ink-2)}
.qb-more:hover{border-color:var(--green-600);color:var(--green-800)}

/* ══════════ 悬浮按钮防重叠（2026-09-17 老板反馈：报价按钮被客服按钮盖住） ══════════
   报价 FAB 在 bottom:26 (z-150)，客服 FAB 原来也在 bottom:18 (z-9990)，
   两者完全叠在一起，客服按钮把报价按钮右半边挡死 → 点「索取报价」没反应。
   修法：客服按钮整体上移到报价按钮上方，聊天窗跟着上移。

   ★★ 2026-10-21（张总：「客服球下移 购物车也要下移 现在挤在一起」）：
     **踩过一次，把数记在这儿**：报价胶囊底距 18px、高约 54px ⇒ 它占 18~72；
     客服球 56 高，必须从**72 往上再留 12** ⇒ `bottom:84px` 才不挤。
     之前把客服球改成 60px 就正好压在胶囊上（18+54=72 > 60）—— 这就是"挤在一起"。
     手机有底部 CTA 栏那一档同理：报价 78 + 高 46 ⇒ 客服球 `bottom:136px`。
     ★ 以后要动这两个数：先量 `.qb-fab` 的 offsetHeight，再 `bottom = 胶囊底距 + 胶囊高 + 12`。 */
.xj-chat-fab{bottom:84px}
/* ★★ 2026-10-21（张总：「右侧的聊天窗口和购物车往下面移一点，太中间了」）：
   聊天窗原来 bottom:158px ⇒ 在 1011×632 的窗口里它的下沿停在 75% 高度。
   改成 40px（高度不变：整块往下移，不拉高）。 */
.xj-chat-box{bottom:40px; max-height:calc(100vh - 176px)}
@media(max-width:820px){
  /* 手机有底部悬浮 CTA 栏时, 报价 FAB 在 bottom:78, 客服按钮再往上抬 */
  /* ★ 2026-10-21：已删（底栏不存在，球走基础值 84px）。
  body.has-mcta .xj-chat-fab{bottom:136px}  ← 旧值，保留作对照 */
  /* ★ 2026-10-21：已删（底栏不存在，窗走基础值 40px） */
}

/* ══════════ 报价角标跳动 + 加购反馈（2026-09-17） ══════════ */
.qb-fab.pop{animation:qb-pop .35s ease}
.qb-fab.pop .cnt{animation:qb-cnt-pop .4s ease}
@keyframes qb-pop{0%{transform:scale(1)}40%{transform:scale(1.12)}100%{transform:scale(1)}}
@keyframes qb-cnt-pop{0%{transform:scale(1)}50%{transform:scale(1.6)}100%{transform:scale(1)}}
.qb-addbtn{transition:background .15s ease, transform .15s ease}
.qb-addbtn.ok{background:var(--gold,#d9a53a); transform:scale(1.12)}

/* ★★ 2026-10-02：内置浏览器里那条**我们自己的官方确认条**（张总「不让那个东西提示太影响客人」）
   —— 一行、绿色系、不与上面那条黄色告警吵架；只在 QQ邮箱/微信等内置浏览器里出现。 */
.xj-trust{display:flex;align-items:center;gap:8px;justify-content:space-between;
  padding:6px 10px;background:#ecfdf5;border-bottom:1px solid #a7f3d0;color:#065f46;
  font-size:var(--fs-xs,12px);flex-wrap:wrap}
.xj-trust a{color:#065f46;text-decoration:underline;white-space:nowrap;font-weight:600}

/* ★★ 2026-10-02 张总：「窗口转人工 那里不放选链接和发图片视频 **只在聊天窗口抽屉添加**」
   —— 这一行只留「转人工」；「选商品 / 发图片视频」收进输入框左边 ＋ 打开的**抽屉**，
   抽屉里带一句"需要登录/验证"的说明。
   ★ 抽屉**跟着输入框**（不是挂在页面底部）—— 手机上键盘弹出也不会把它顶出可见区，
     这正是张总说"**选链接还是选不了**"的根因（搜索面板原来挂在页面最底下）。 */
.xj-chat-ft .xj-plus{width:34px;height:34px;flex:0 0 34px;border:1px solid var(--line,#e5e7eb);
  border-radius:8px;background:#fff;font-size:var(--fs-17,17px);line-height:1;cursor:pointer;
  color:var(--green-800,#166534)}
/* ★★ 2026-10-03：抽屉**限高 + 自己滚** —— 它以前没有 max-height，内容多长它就多长，
   把会话区吃到只剩 28px（见上面 `.xj-chat-body` 那段实测与理由）。 */
.xj-chat-drawer{border-top:1px solid var(--line,#e5e7eb);background:#fbfdff;padding:8px 10px;
  flex:0 0 auto; max-height:60%; overflow-y:auto}
.xj-chat-drawer[hidden]{display:none}

/* ══════════════════════════════════════════════════════════════════════════
   ★★★ 2026-10-03 **第二版**（张总真机截图批注，两条一起治）：
     原话：「**转人工进去选商品咨询 这么一点点大　窗口放大还是一样　应该可以放大到全部聊天窗口**」
           「**不转人工 右侧两个滚动条　交互很不好　应该搜索框不动　产品可以滚动**」

   第一版错在哪（如实记）：把抽屉"限高 44% + 整块自己滚"当成了答案 ——
     ① 面板永远只有 44% 高，**点了放大也不变**（因为它是**百分比**上限，跟窗口多大无关）；
     ② 抽屉自己滚 + 商品列表也滚 ⇒ **两个滚动条并排**（真机截图里那条红框就是它）。
   第二版的做法（`.xj-sku-open`：只有"商品面板开着"时才加这个类）：
     · **会话区让位**：选商品时把会话区整块收起来 ⇒ 面板能吃到"全部聊天窗口"
       （张总要的"放大到全部聊天窗口"；选完商品 `sendSku` 会立刻把面板与暗层一起收掉，
        会话区马上回来 —— 这条在 1-A 那一步已经验过）；
     · **抽屉自己不滚**（`overflow:hidden`）⇒ 页面上**只剩一个**滚动条；
     · **搜索框固定在顶**（`flex:0 0 auto`）、**只有商品列表滚**（`flex:1 1 auto` + `min-height:0`）。
   ★ 为什么用 `min-height:0`：flex 子项默认 `min-height:auto`，不让它缩到内容以下 ⇒
     列表根本不会出现滚动条（这个坑第一次写 flex 列布局必踩）。
   ══════════════════════════════════════════════════════════════════════════ */
.xj-chat-box.xj-sku-open .xj-chat-body{display:none}
.xj-chat-box.xj-sku-open .xj-chat-drawer{flex:1 1 auto; max-height:none; overflow:hidden;
  display:flex; flex-direction:column}
/* ★★ 面板开着的时候，「选商品咨询 / 发图片或视频 / 那句说明」**让位**：
   客户**已经**点了"选商品咨询"，那两颗按钮与说明这一刻是废话，却要占掉 ~110px
   —— 实测（1440×900）就因为它们，面板只拿到聊天窗的 **31%**，张总说"这么一点点大"。
   让位之后面板占比 ≈ 94%（同一测量）。出口照旧：点空白 / × / 再点 ＋。 */
.xj-chat-box.xj-sku-open .xj-chat-drawer .xj-drawer-item,
.xj-chat-box.xj-sku-open .xj-chat-drawer .xj-drawer-note{display:none}
.xj-chat-box.xj-sku-open .xj-chat-drawer .xj-sku-box{flex:1 1 auto; min-height:0; margin-top:0;
  display:flex; flex-direction:column}
.xj-chat-box.xj-sku-open .xj-chat-drawer .xj-sku-box input{flex:0 0 auto}
.xj-chat-box.xj-sku-open .xj-chat-drawer .xj-sku-list{flex:1 1 auto; min-height:0; max-height:none;
  overflow-y:auto}

.xj-chat-drawer .xj-drawer-item{display:block;width:100%;text-align:left;margin:0 0 6px;
  padding:8px 10px;border:1px solid var(--line,#e5e7eb);border-radius:8px;background:#fff;
  font:inherit;font-size:var(--fs-sm,13px);cursor:pointer;color:var(--green-800,#166534)}
.xj-chat-drawer .xj-drawer-item:hover{border-color:var(--green-600,#16a34a)}
.xj-chat-drawer .xj-drawer-note{font-size:var(--fs-2xs,11px);color:var(--ink-3,#64748b);
  line-height:1.5;margin:2px 0 4px}
.xj-chat-drawer .xj-sku-box{margin-top:6px;border:1px solid var(--line,#e5e7eb);
  border-radius:8px;overflow:hidden;background:#fff}
.xj-chat-drawer .xj-sku-list{max-height:26vh;overflow:auto}

/* ★ 2026-10-02：三颗动作按钮收成一行（`xj-chat-actions`），默认也是并排小 chip ——
   原来三行大按钮在手机上把消息区挤没了，键盘一弹就"乱蹦"。
   ★★ 2026-10-03（张总批注「**放到下面**」）：这一行从**输入行上面**挪到了**下面**
   （DOM 顺序在 `chat.js::buildUI()` 里换的）⇒ 这里的 padding 跟着翻过来：
   上面不再留 6px（输入行自己那 10px 够），下面给 10px 收口。 */
.xj-chat-actions{display:flex;gap:6px;padding:0 10px 10px}
.xj-chat-actions .xj-handoff-btn,
.xj-chat-actions .xj-sku-btn,
.xj-chat-actions .xj-file-btn{flex:1 1 0;width:auto;margin:0;padding:7px 6px;
  border:1px dashed var(--line,#e5e7eb);border-radius:8px;background:#fff;font:inherit;
  font-size:var(--fs-xs,12px);cursor:pointer;color:var(--green-800,#166534);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.xj-chat-actions .xj-handoff-btn:hover,
.xj-chat-actions .xj-sku-btn:hover,
.xj-chat-actions .xj-file-btn:hover{border-color:var(--green-600,#16a34a)}

/* ★★ 2026-10-03（张总批注：「同时转人工界面的字改为 **已转人工**」）：
   接通之后的**完成态** —— 绿色实底 + 一个 ✓，一眼看出"已经接上了"。
   ★ 三条选择器都写全：`.xj-chat-actions`(0,2,0) 与整屏态那条 `.xj-chat-box.xj-chat-full`
     都会覆盖基类，少写一条就会"某个形态下还是白底虚线"（这正是本仓 2026-09-26
     "样式漂移只有截图能发现"那一类）。 */
.xj-handoff-btn.done,
.xj-chat-actions .xj-handoff-btn.done,
.xj-chat-box.xj-chat-full .xj-chat-actions .xj-handoff-btn.done{
  background:var(--brand,#1a7f5a); color:#fff; border-style:solid;
  border-color:var(--brand,#1a7f5a); cursor:default; opacity:1}
.xj-handoff-btn.done::before{content:"\2713\00a0"}

/* ★ 2026-10-02：客人发图片/视频 —— 回形针按钮 + 气泡里的图/播放器 */
.xj-file-btn{display:block;width:100%;margin:6px 0 0;padding:9px;border:1px dashed var(--line,#e5e7eb);
  border-radius:8px;background:#fff;font:inherit;font-size:var(--fs-sm,13px);cursor:pointer;
  color:var(--green-800,#166534)}
.xj-file-btn:hover{border-color:var(--green-600,#16a34a)}
.xj-msg.xj-file{padding:4px;background:transparent;border:0}
.xj-msg.xj-file img{max-width:220px;max-height:220px;border-radius:10px;display:block}
.xj-msg.xj-file video{max-width:240px;border-radius:10px;display:block}

/* ★ 2026-10-02：聊天窗「选商品咨询」（张总批准的 C 方案）——
   与「转人工」并排的第二颗按钮 + 一个搜索面板（数据走既有 /api/products）。 */
.xj-sku-btn{display:block;width:100%;margin:6px 0 0;padding:9px;border:1px dashed var(--line,#e5e7eb);
  border-radius:8px;background:#fff;font:inherit;font-size:var(--fs-sm,13px);cursor:pointer;
  color:var(--green-800,#166534)}
.xj-sku-btn:hover{border-color:var(--green-600,#16a34a)}
.xj-sku-box{margin-top:6px;border:1px solid var(--line,#e5e7eb);border-radius:8px;overflow:hidden}
.xj-sku-box input{width:100%;border:0;border-bottom:1px solid var(--line,#e5e7eb);padding:8px 10px;font:inherit}
.xj-sku-list{max-height:180px;overflow:auto}
.xj-sku-item{padding:7px 10px;font-size:var(--fs-sm,13px);cursor:pointer;border-bottom:1px solid #f1f5f9}
.xj-sku-item:hover{background:#f8fafc}
.xj-sku-none{padding:8px 10px;font-size:var(--fs-sm,13px);color:var(--ink-3,#64748b)}
/* ★★ 2026-10-02 张总「选择链接咨询查询时候要带缩略图」——
   列表行：左边 44×44 缩略图 + 右边文字（图固定尺寸，别让长名把行撑变形）；
   气泡里：图在上、字在下，圆角与气泡一致。 */
.xj-sku-item{display:flex;gap:8px;align-items:center}
.xj-sku-thumb{width:44px;height:44px;flex:0 0 44px;object-fit:cover;border-radius:6px;
  background:#eef2f7}
.xj-sku-tx{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.xj-msg-thumb{display:block;width:120px;height:120px;object-fit:cover;border-radius:8px;
  margin:0 0 5px;background:#eef2f7}
.xj-msg.staff .xj-msg-thumb{margin-left:auto}

/* ★★ 2026-10-02：手机落地"进通讯窗口"（张总：「邮箱点击 转人工登录后 手机网页界面
   还是没有及时跳转到通讯窗口」）——
   点完邮件链接带 `xj_chat=1` 落地时，客户端给聊天窗加这个类 ⇒ **整屏**显示，
   关掉即回首页。桌面端不加（屏幕够大，小窗更合适）。 */
/* ★ 两个选择器都要写：`body.has-mcta .xj-chat-box`（旧的底部条那一档，选择器比单类更强）
   会把 `max-height` / `bottom` 抢回去 —— 实测过一版：宽只剩 354（被 max-width 卡）、
   高 618（被 has-mcta 那档的 max-height 卡）。这里显式盖掉它。
   ★★ 2026-10-02 张总「点击不出来乱蹦」：整屏高度用 **`100dvh`**（动态视口）而不是 `100vh`
    —— 手机软键盘弹出时 `100vh` 不变（内容被顶、看着就是"乱蹦"），`dvh` 会跟着缩。
   老浏览器不认识 `dvh` 时后面那行 `100vh` 兜底（写法顺序不能反）。 */
.xj-chat-box.xj-chat-full,
body.has-mcta .xj-chat-box.xj-chat-full{position:fixed;left:0;right:0;top:0;bottom:0;
  width:auto;max-width:none;height:100vh;height:100dvh;max-height:100vh;max-height:100dvh;
  border-radius:0;z-index:9999}
/* ★ 整屏时底页不许跟着滚（触摸落点才稳定） */
body:has(.xj-chat-box.xj-chat-full:not([hidden])){overflow:hidden}
/* ★ 整屏时把三颗"动作"按钮收成**一行小 chip**（原来三行大按钮，键盘一弹就没地方了） */
.xj-chat-box.xj-chat-full .xj-handoff-btn,
.xj-chat-box.xj-chat-full .xj-sku-btn,
.xj-chat-box.xj-chat-full .xj-file-btn{display:inline-block;width:auto;flex:1 1 auto;
  margin:6px 4px 0;padding:6px 8px;font-size:var(--fs-xs,12px);border-style:solid}
.xj-chat-box.xj-chat-full .xj-chat-actions{display:flex;gap:6px;padding:0 8px 10px}
.xj-chat-box.xj-chat-full .xj-chat-body{max-height:none}
.xj-chat-box.xj-chat-full .xj-chat-body{max-height:none}
/* ★ 只在这扇窗**开着**的时候藏悬浮钮（否则关掉整屏窗之后连重新打开的入口都没了） */
body:has(.xj-chat-box.xj-chat-full:not([hidden])) .xj-chat-fab{display:none}

/* ══════════ AI 客服聊天界面美化（2026-09-17：丝滑、美感） ══════════ */
/* ★ 2026-10-02：聊天窗标题栏的**声音开关**（张总「客户那里也给到声音」）——
   与右边那个 × 同一行，按钮本体不加边框（标题栏是深色）。 */
.xj-chat-hd .snd{background:transparent;border:0;color:#fff;font-size:15px;line-height:1;
  cursor:pointer;padding:0 6px;opacity:.92}
.xj-chat-hd .snd:hover{opacity:1}
/* 聊天气泡渐入 */
.xj-msg{animation:xj-msg-in .28s cubic-bezier(.2,.7,.3,1); box-shadow:0 1px 2px rgba(0,0,0,.04)}
@keyframes xj-msg-in{from{opacity:0; transform:translateY(8px) scale(.97)}to{opacity:1; transform:none}}
/* 打字三点动画 */
.xj-chat-typing{display:flex; align-items:center; gap:4px; height:18px}
.xj-chat-typing .d{width:6px; height:6px; border-radius:50%; background:var(--ink-3,#999);
  animation:xj-bounce 1.2s infinite}
.xj-chat-typing .d:nth-child(2){animation-delay:.2s}
.xj-chat-typing .d:nth-child(3){animation-delay:.4s}
@keyframes xj-bounce{0%,60%,100%{transform:translateY(0);opacity:.5}30%{transform:translateY(-5px);opacity:1}}
/* 发送按钮：圆形图标化 + 悬停反馈 */
.xj-chat-ft button{width:42px; height:42px; padding:0; border-radius:50%; flex-shrink:0;
  display:flex; align-items:center; justify-content:center; transition:transform .12s ease, background .15s ease}
.xj-chat-ft button:hover{transform:scale(1.08); background:var(--green-800)}
.xj-chat-ft button:active{transform:scale(.94)}
/* 聊天窗打开动画 */
.xj-chat-box{animation:xj-box-in .22s cubic-bezier(.2,.8,.3,1)}
@keyframes xj-box-in{from{opacity:0; transform:translateY(14px) scale(.98)}to{opacity:1; transform:none}}
/* 头部头像微光 + 关闭按钮圆润 */
.xj-chat-hd .avatar{box-shadow:inset 0 0 0 2px rgba(255,255,255,.25)}
.xj-chat-hd .close{width:30px; height:30px; border-radius:50%; display:flex; align-items:center;
  justify-content:center; transition:background .15s ease}
.xj-chat-hd .close:hover{background:rgba(255,255,255,.18)}
/* 转人工按钮更精致 */
.xj-handoff-btn{transition:background .15s ease, transform .12s ease}
.xj-handoff-btn:active{transform:scale(.98)}

/* ══════════════════════════════════════════════════════════════════════════
   ★★ 聚光灯引导（张总 2026-10-02：「点击转人工 点击发送图片视频弹出界面要明显
   其它区域虚化 **就留这个验证的地方**」）
   —— 页面上盖一层暗，只把"该做的那个动作"露出来。
   ★★ 为什么是**结构法**、不是按目标坐标去挖洞：聊天窗是 `position:fixed`，手机上还会
      ① 变整屏态（`.xj-chat-full`）② 弹软键盘（所以项目用 `dvh`，`vh` 会飘）③ 转屏。
      算坐标那条路这三种情况**都要重算**，漏一次就是"洞错位"或"页面被永久糊住"。
      这里改成：页面层压暗整页 → 聊天窗抬到它上面 → 窗内层压暗窗内 → 目标抬到内层上。
      ⇒ **一次坐标都不算**（没有 getBoundingClientRect、没有 ResizeObserver、
        因此也不需要"算不出来就自愈"那套兜底）。
   ★ 层号是**量过的**：页面层要盖过现有最高的两个常驻层 —— 购物车最大化态 `9998`
     与聊天窗整屏态 `9999`（`main.css` 上面量过；`.xj-fly` 那个 99999 是加购飞行影子，
     `pointer-events:none` 的 0.6s 瞬时物，不影响点击）⇒ 页面层取 999999；
     聊天窗被抬到 1000000（比页面层高），再在**窗内**压暗一次。
   ★ 只挡"真需要验证"的动作（查 `window.XJ.gated` = 服务端那张表的镜像），
     已验证的客户**完全不遮** —— 遮罩只在真被拦时出现（控制组）。
   ══════════════════════════════════════════════════════════════════════════ */
.xj-spot{position:fixed; inset:0; z-index:999999; background:rgba(18,48,31,.55);
  cursor:pointer}
.xj-spot[hidden]{display:none}
/* ★★ 聊天窗抬层：**这里必须用 `!important`，是实测踩出来的** ——
   整屏态那条规则写的是 `.xj-chat-box.xj-chat-full`（2 个类，0,2,0），但同一处还有
   `body.has-mcta .xj-chat-box.xj-chat-full`（**0,3,1**）—— 手机底部条那一档在场时它赢。
   实测（375 宽 + `?xj_chat=1` 整屏态 + 手机底部条）：只写 3 个类（0,3,0）**抬不动**，
   聊天窗被页面层整个盖住 —— 真浏览器判据当场红：目标中心那一点命中的是 `#xjSpot`
   而不是表单（`tools/_a2xx_20261002_spot_check.py` 的 `elementFromPoint` 那条）。
   ⇒ 照本文件既有做法（见上面 `.quote-bar.qb-max` 那段注释）用 `!important` 盖过它们，
     并且**全部收在这一个类里**：去掉 `.xj-spot-lift` 就逐字回到原样。 */
.xj-chat-box.xj-spot-lift,
.xj-chat-box.xj-chat-full.xj-spot-lift,
body.has-mcta .xj-chat-box.xj-chat-full.xj-spot-lift{z-index:1000000 !important}
.xj-spot-in{position:absolute; inset:0; z-index:40; background:rgba(18,48,31,.55);
  cursor:pointer}
.xj-spot-in[hidden]{display:none}
.xj-spot-target{position:relative; z-index:60; outline:3px solid #fff; outline-offset:2px;
  border-radius:10px; box-shadow:0 10px 30px rgba(0,0,0,.35)}

/* 加购轻提示 toast（浏览视图顶部浮出「已加入」） */
#qbToast{position:absolute; top:14px; left:50%; transform:translate(-50%,-12px);
  background:rgba(26,69,48,.95); color:#fff; padding:8px 18px; border-radius:100px;
  font-size:var(--fs-sm); font-weight:600; opacity:0; pointer-events:none; z-index:60;
  transition:opacity .2s ease, transform .2s ease}
#qbToast.show{opacity:1; transform:translate(-50%,0)}

/* ==========================================================================
   尊重系统「减少动态效果」（prefers-reduced-motion）
   —— 用户在手机/电脑的辅助功能里打开「减弱动态效果」时本段生效：
      · 关掉全站 @keyframes 动画（qb-pop / qb-cnt-pop / xj-msg-in / xj-bounce / xj-box-in）
      · 关掉过渡与平滑滚动
   为什么用 .001ms 而不是 animation:none：
     none 会让某些"动画结束后才归位"的元素停在初始态（例如 opacity:0）；
     .001ms 且只跑一次，元素会立刻落到动画的**结束帧**，视觉结果与播完后一致。
   ★ 本段是全站唯一的 reduced-motion 开关；以后新增动画/滚动（如节日祝福条）
     不需要再写第二份，浏览器会自动套用。
   ========================================================================== */
@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    animation-duration:.001ms !important;
    animation-delay:0ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
    transition-delay:0ms !important;
  }
  html{scroll-behavior:auto !important}
  /* 打字指示点的弹跳是无限循环动画，停帧后固定在"静止态"更自然 */
  .xj-bounce{animation:none !important; opacity:.65 !important; transform:none !important}
}


/* ══════════════════════════════════════════════════════════════════════════
   微信联系块（#7，2026-09-21）
   只在后台填了「微信号 / 二维码」时才渲染（模板 {% if SITE.wechat_id or ... %}），
   两样都没填 → **整块不出现**（不是留白、更不是裂图）。
   ★ 二维码固定 200px：再小就扫不出来，尤其"隔着屏幕扫桌面版"这种最常见的场景。
   ★ 背景写死白：二维码在深色/主题色底上会掉识别率（微信要求四周留白、别压花纹）。
   ══════════════════════════════════════════════════════════════════════════ */
.wx-card{display:flex; gap:18px; align-items:flex-start; flex-wrap:wrap; margin-top:2px}
.wx-qr{width:200px; height:200px; object-fit:contain; background:#fff; padding:6px;
  border:1px solid var(--line); border-radius:var(--radius)}
.wx-side{flex:1 1 170px; min-width:150px; display:flex; flex-direction:column;
  gap:8px; align-items:flex-start}
.wx-id{font-size:var(--fs-15); color:var(--ink)}
.wx-id b{font-family:ui-monospace,Consolas,'Courier New',monospace; letter-spacing:.6px}
/* ★★ 2026-10-17（张总批注「这个页面加一个网站二维码供下载扫描」）：
   官网二维码固定 160px —— 手机不会扫自己屏幕上的码，它是给**印出去**用的
   （画册 / 名片 / 展位），尺寸比微信那块小一档即可。
   ★ 背景同样写死白：二维码压在主题色上会掉识别率（与微信那条同一理由）。 */
.site-qr{width:160px; height:160px; object-fit:contain; background:#fff; padding:6px;
  border:1px solid var(--line); border-radius:var(--radius)}
.wx-tip{font-size:var(--fs-sm); line-height:1.55; color:var(--ink-3)}
@media(max-width:560px){
  /* 窄屏把二维码降到 160px：390px 视口里 200px 的图 + 文案会挤成竖排 */
  .wx-qr{width:160px; height:160px}
  .wx-side{flex:1 1 100%}
}

/* ==========================================================================
   询盘表单 · 提交条款 + 手写签名 (2026-09-21)
   ========================================================================== */
.terms-text{
  font-size:var(--fs-xs); color:var(--ink-3); line-height:1.6; margin:0;
  background:var(--sand-50); border:1px solid var(--sand-100);
  border-radius:var(--radius); padding:10px 12px;
}
.terms-check{
  display:flex; gap:8px; align-items:flex-start;
  font-size:var(--fs-sm); color:var(--ink-2); cursor:pointer; line-height:1.5;
}
.terms-check input{
  width:16px; height:16px; margin-top:2px; flex:none;
  accent-color:var(--green-700); cursor:pointer;
}
.terms-check a{color:var(--green-700)}
.sigpad-wrap{
  position:relative; border:1.5px dashed var(--ink-3);
  border-radius:var(--radius); background:var(--white); overflow:hidden;
}
.sigpad-wrap canvas{
  display:block; width:100%; height:clamp(120px,22vh,160px);
  touch-action:none;            /* 手机上画签名时不滚动页面 */
  cursor:crosshair; -ms-touch-action:none;
}
.sigpad-hint{
  position:absolute; inset:0; display:flex; align-items:center;
  justify-content:center; font-size:var(--fs-xs); color:var(--ink-3);
  pointer-events:none;          /* 不挡画布的指针事件 */
  transition:opacity .18s;
}
/* ★★ 2026-09-30（张总「签名结构错位 不美观 一起优化」）：
   ① 提示语**只在空态**显示 —— 以前它一直绝对居中，客户一落笔就和笔迹叠在一起；
   ②「清除」平时不出现、签了才出现，并做成**真按钮**（原来是压在画布右上角的
      下划线小字，笔迹常从那儿经过，既压字又容易误点）。
   两者都只由 `.sig-block.is-signed` 一个 class 驱动（JS 只负责加减这个 class）。 */
.sig-block.is-signed .sigpad-hint{opacity:0}
.sigpad-clear{
  position:absolute; top:8px; right:8px; display:none;
  background:var(--white); border:1px solid var(--line); border-radius:8px;
  padding:2px 10px; font-size:var(--fs-xs); color:var(--ink-2); cursor:pointer;
  box-shadow:0 1px 2px rgba(0,0,0,.06);
}
.sigpad-clear:hover{border-color:var(--green-600); color:var(--green-700)}
.sig-block.is-signed .sigpad-clear{display:inline-block}

/* ══════════ 2026-10-04 聊天窗：手机端整屏 ＋ 自由拖动（张总令） ══════════
   Zhang: "on mobile, after login+handoff, picking a product is still cramped;
   the chat window should be freely draggable on the page".
   Measured (real Edge, 375x812): the box is pinned by `body.has-mcta ... bottom:208px;
   max-height:calc(100vh - 226px)` => only 520px tall; with the on-screen keyboard up
   (typing the email for handoff) the viewport drops to ~420 => the box is 194px tall
   while its fixed parts (head 117 + input 63 + actions 46 = 226) are TALLER than the box
   => the thread area collapses to its 96px floor = the "cramped" state.
   Fix: (1) narrow screens open FULL SCREEN (stop fighting the CTA bar for 226px; use dvh
   so it follows the keyboard); (2) on narrow screens dragging the header turns it into a
   floating window (that is the "free drag" Zhang asked for; desktop could already drag). */
.xj-chat-hd{cursor:move; touch-action:none}      /* drag handle = header only (body scrolls) */
.xj-chat-box.xj-chat-full .xj-chat-hd{cursor:default; touch-action:auto}  /* no drag in full */
.xj-chat-box.xj-chat-dragging{transition:none !important}
@media (max-width:600px){
  /* (1) mobile: open full screen (bottom sheet, full height, dvh tracks the keyboard) */
  .xj-chat-box{left:0; right:0; bottom:0; width:auto;
               height:100vh; height:100dvh; max-height:100vh; max-height:100dvh;
               border-radius:18px 18px 0 0}
  body.has-mcta .xj-chat-box{left:0; right:0; bottom:0; height:100vh; height:100dvh;
               max-height:100vh; max-height:100dvh}
  /* while the chat is open, the two floating buttons step aside */
  body.xj-chat-open .qb-fab, body.xj-chat-open .xj-chat-fab{display:none}
  /* (2) mobile: drag the header => floating window (then move it anywhere) */
  /* BOTH selectors are required: `body.has-mcta .xj-chat-box` is stronger than a single
     class, so a plain `.xj-chat-box.xj-chat-float` LOSES and the box stays full screen
     (measured: 339x812 even with the float class on). Same trap the `.xj-chat-full` block
     above already documents - always write the stronger selector too. */
  .xj-chat-box.xj-chat-float,
  body.has-mcta .xj-chat-box.xj-chat-float{left:auto; right:10px; bottom:118px;
               /* ★★ 2026-10-04（张总：「**华为浏览器可以　夸克不可以**」）：这两条**不许**再依赖
                  新写法 —— `min()`（Chrome 79+）与 `dvh`（108+）在夸克那类定制内核上会被
                  **整条丢掉**（同一个浮窗在华为是好高度、在夸克是另一个高度）。
                  ⇒ 拆成最朴素的两条：宽度 `340px` + `max-width`，高度 `62vh` + `max-height:520px`
                  （`vh`/`max-width`/`max-height` 是十几年前就有的，没有内核会不认）。
                  ★ 效果与 `min(62dvh,520px)` 在真机上一致（无地址栏时 vh==dvh），
                    但**兜底路径确定**：认不认新写法，结果都一样。 */
               width:340px; max-width:calc(100vw - 20px);
               height:62vh; max-height:520px;
               border-radius:16px}
}
/* ══════════ 2026-10-04 转人工验证表单**让位**（张总真机截图：白框压在输入行上） ══════════
   Zhang sent a real-phone shot: after tapping "talk to a human" a white bordered pill sat
   ON TOP of the chat input. Measured (real Edge): `.xj-handoff-form.open` is a 205px-tall
   absolute overlay pinned to bottom:0 and it covers the input row and the handoff row
   100% (overlap 23,562 px^2 / 17,204 px^2). The customer's only job at that moment is to
   type the email, so those two rows step aside (same rule as the product panel in 1-E) and
   the form itself goes back into the flow so nothing overlaps anything. */
.xj-chat-box.xj-verify-open .xj-chat-ft,
.xj-chat-box.xj-verify-open .xj-chat-actions,
/* ★ 2026-10-04：验证时抽屉也一并收掉 —— 否则它会以上面那根白条的形式留在屏幕上
   （张总真机截图「选商品咨询」那条），JS 侧同一步也调 closeDrawer()，两边都堵。
   ★★ 2026-10-06 Harness 补 `:not(.xj-sku-open)`（**真缺陷，真 Edge 实测 0×0**）：
      这条原来对"验证表单开着"**无条件**成立，而**商品面板就住在这个抽屉里**；
      它 (0,3,0) 又强过 `.xj-chat-box.xj-sku-open .xj-chat-drawer`（见上面 1-E 那一段）
      ⇒ 转人工成功后再点「＋ → 选商品咨询」时：
        · 抽屉被这条 `display:none` 收掉 ⇒ 面板祖先不可见；
        · 表单又被 `.xj-sku-open .xj-handoff-form.open{display:none}` 收掉
        ⇒ **两样都不显示**，客人看到的是一整块空白。
      实测（探针 `_scratch_panel_probe.py`，真 Edge 1440×900，同一条路径）：
        · 转人工前：`#xjSkuBox` **338×275**、`#xjSkuList` 336×232、DOM 里 12 条商品 → 看得见
        · 转人工后：`#xjSkuBox` **0×0**、`#xjSkuList` **0×0**、DOM 里还是 12 条 → **一条都看不见**
      截图：`…\_证据_企微翻译_20261006\_shot_panel\C_转人工后_选商品.png`（整屏只有标题栏）。
      判据：`tests/test_chat_history_both_sides.py` ⑤/⑤b（后 ≥ 前 且 可见 ≥3 条）。
      ★ 为什么用 `:not()` 而不是在后面再写一条更强的 `display:flex`：那样两个规则都对
        "验证表单开着"生效、谁赢只看特异性和顺序（§16.2⑤ 说的"脆锚"就是这种）；
        `:not()` 让**同一件事只有一个规则在位**（面板开着 ⇒ 这条整体不适用）。 */
.xj-chat-box.xj-verify-open:not(.xj-sku-open) .xj-chat-drawer{display:none}
.xj-chat-box.xj-verify-open .xj-handoff-form.open:not(.xj-spot-target){
  position:static; inset:auto; flex:0 0 auto; border-top:1px solid var(--line,#e2e6e1)}
/* ══════════ 2026-10-04 商品面板 × 验证表单**不许同抢竖直空间**（张总「选商品咨询还是挤在一小条」）
   Zhang: after the human handoff succeeded, tapping "ask about a product" left the panel in a
   tiny strip. Measured (real Edge, 1440x900, same page/flow):
     · 转人工前            panel 275px, list 232px -> 3 items visible
     · **转人工后(未刷新)** panel **162px**, list **119px** -> **2 items visible**  (worst)
     · 转人工后 + 刷新      panel 275px -> 3 items visible
   Root cause: the rule right above turns the handoff form back into an in-flow flex item
   (~205px, `xj-verify-open`), and when the SKU panel opens the drawer competes with it for the
   same column (`flex:1 1 auto` each) -- shrink splits the space, and the drawer is
   `overflow:hidden`, so the panel simply becomes small. The panel no longer "takes the whole
   window" as 1-E promised.
   Fix: while the product panel is open, the form steps aside too (same treatment the two drawer
   buttons already get in `.xj-sku-open`). The form keeps its `.open` class/DOM and comes right
   back when the panel closes, so no state is lost. Measured after: panel 275px / 3 items. */
.xj-chat-box.xj-sku-open .xj-handoff-form.open{display:none}
/* narrow screens: the head is 117px tall (avatar + 2-line welcome) - clamp the welcome to
   one line so the thread/handoff form actually get the room (measured 117 -> ~72). */
@media (max-width:600px){
  /* ★ 2026-10-04（张总：「大窗口没有问题 拖动缩放后 还是显示一行 **要两行**」）：
     原来是 `nowrap` 只给一行 ⇒ 浮动小窗里欢迎语被截成一行。改成**最多两行**
     （超过才省略号）—— 大窗口本来就能放两行，这里与它对齐。 */
  .xj-chat-hd .meta span{display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
                         overflow:hidden; white-space:normal; max-width:100%}
  .xj-chat-hd .meta b{white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
}

/* ★★ 2026-10-04（张总：「窗口缩小了放不大了」）：**整屏态必须压过浮窗态**。
   浮窗那条是后加的、选择器同样强（`body.has-mcta .xj-chat-box.xj-chat-float`），
   同强度下**顺序定胜负** ⇒ 浮窗赢 ⇒ 点放大无效。这条放在最后，整屏永远赢。 */
@media (max-width:600px){
  .xj-chat-box.xj-chat-full,
  body.has-mcta .xj-chat-box.xj-chat-full{left:0; right:0; top:auto; bottom:0; width:auto;
    height:100vh; height:100dvh; max-height:100vh; max-height:100dvh;
    border-radius:18px 18px 0 0}
}

/* ★★ 2026-10-17（张总「缺位」指着 Contact）：
   窄屏（<=1080）整条 `.nav` 收进汉堡 ⇒ 里头那颗「联系我们」**跟着不见了**。
   实测：1024/900/768/375 四个宽度下，头部**没有任何可见的"联系"入口**。
   ⇒ 头部常驻一颗（`.cta-head`）：桌面不显示（导航里那颗负责，绝不出现两处），
     只在这几个宽度下显示；触屏最小可点高度 44px。 */
.cta-head{display:none}
/* ★ 选择器写成 `.hdr .cta-head`（两个类）：窄屏那几条 `.hdr a{…}` 是 (0,1,1)，
   单类 `.cta-head` 只是 (0,1,0) ⇒ 会被它盖掉（实测 375 宽时背景变成深灰 51,65,85）。
   两个类 (0,2,0) 才压得住。 */
@media (max-width:1080px){
  .hdr .cta-head{display:inline-flex; align-items:center; justify-content:center;
    min-height:44px; padding:0 16px; border-radius:8px;
    background:var(--green-700); color:#fff; font-weight:600; font-size:var(--fs-sm);
    text-decoration:none; white-space:nowrap; margin-inline-start:auto}
  .hdr .cta-head:hover{background:var(--green-800); color:#fff}
  .burger{margin-inline-start:8px}
}


/* ---------- 装到桌面（A2HS）邀请卡 ----------
   ★★ 2026-10-18（张总逐字：「客人第2次打开网站提醒弹出添加至桌面，提示下一次方便找到」）
   · 手机：贴在底部的一张卡，padding-bottom 带上 safe-area-inset-bottom
     （iPhone 底下那条横条会压住按钮，客户点不到 = 这张卡等于没有）；
   · 桌面（≥900px）：收成右下角浮卡 —— 贴满整宽会像广告条，第一眼就烦；
   · 颜色/圆角全部取 :root 变量，**不新增品牌色**（老板换主题时它跟着换）。
   · 弹不弹由 static/js/a2hs.js 判；这里只管长相，.a2hs[hidden] 时一像素不占。 */
/* ★★ 2026-10-18（截图量出来的第二处）：层级必须**高于浮动客服按钮**（.xj-chat-fab = 9990），否则那颗绿圆压在卡片正文上、字被切掉；但要**低于**整屏客服（9999）与询盘/价目弹层（999999）—— 客户真打开对话时，这张邀请卡不许盖在上面。
   ⇒ 9995 是这三档之间的唯一位置（改任一档前先看这一行）。 */
.a2hs{position:fixed; left:0; right:0; bottom:0; z-index:9995; padding:10px 12px;
  padding-bottom:calc(10px + env(safe-area-inset-bottom)); pointer-events:none}
.a2hs[hidden]{display:none}
/* ★★ 2026-10-18（真浏览器截图量出来的）：卡片里的按钮带了 .btn 类，而 .btn{display:inline-flex} 是**作者样式**，它会盖掉浏览器默认的 [hidden]{display:none} ⇒ JS 把按钮置为 hidden 了，**画面上还是能看见**（客户会去点一个什么都不做的按钮）。⇒ 在卡片内部把 hidden 强制生效（只限这张卡，不动全站）。 */
.a2hs [hidden]{display:none !important}
.a2hs-c{pointer-events:auto; max-width:520px; margin:0 auto; background:var(--white);
  border:1px solid var(--line); border-radius:var(--radius-lg);
  box-shadow:0 10px 30px rgba(0,0,0,.16); padding:14px 16px}
.a2hs-t{font-weight:600; color:var(--ink); font-size:var(--fs-15)}
.a2hs-d{color:var(--ink-2); font-size:var(--fs-sm); margin-top:4px; line-height:1.6}
.a2hs-step{margin-top:8px; padding:8px 10px; background:var(--green-100); color:var(--green-900);
  border-radius:var(--radius); font-size:var(--fs-sm); line-height:1.6}
.a2hs-step[hidden]{display:none}
.a2hs-a{display:flex; gap:10px; margin-top:12px}
.a2hs-yes,.a2hs-no{min-height:44px; padding:0 16px; border-radius:var(--radius); cursor:pointer;
  font-size:var(--fs-sm); font-weight:600; font-family:inherit}
.a2hs-yes{flex:1; background:var(--green-700); color:#fff; border:1px solid var(--green-700)}
.a2hs-yes:hover{background:var(--green-800)}
.a2hs-no{background:var(--white); color:var(--ink-2); border:1px solid var(--line)}
.a2hs-no:hover{border-color:var(--ink-3)}
@media(min-width:900px){
  .a2hs{left:auto; right:22px; bottom:22px; padding:0; width:380px}
  .a2hs-a{margin-top:14px}
}
@media print{.a2hs{display:none !important}}
