/* ==========================================================================
   公开前台样式 v4 —— 卡片分区 + 天蓝只做焦点（客户 2026-09-19 二次评审：v3「更丑了」）

   v2 的「工业合规风」把页面压成了一堵 4000px 的白墙：7 个板块共用同一条淡绿标题
   条，标题条之下不是 12 行平铺的 label/value，就是一张表 —— 没有任何节奏变化，
   概要卡也只是个灰盒子。v3 的解法是**卡片分区**：

     页面底 → 中性冷灰（--color-bg #f2f5f9）
     每个板块 → 一张白色圆角卡片（.detail-section）
     标题 → 深天蓝短下划线压在一条通长细线上（不是整条色块）
     厂商两组 → ≥900px 并排，不再串成 12 行

   但 v3 把天蓝**当底色用**了：页底、每一张表头、徽标描边、所有分隔线上都是蓝。
   低饱和的蓝铺满全屏 → 满眼淡蓝却没有任何重点。v4 把结构性表面全部收回中性灰
   （--color-neutral-soft / -faint），天蓝只留四个落点：**概要卡实底**、分区标题
   下划线、主按钮、正文链接。其中概要卡是唯一的强焦点 —— 也见 tokens.css 文件头。

   断点以本行为准，共 3 档，不新增：640px / 900px / 1200px（F13）。
   其中 640 与 900 是 media query；1200 档由 `--max-width: 1120px` 实现 ——
   内容到 1120px 即封顶居中，不再需要查询。CSS 变量不能用于 media query，
   故此处硬编码；tokens.css 文件末有同名标注。

   纪律（F14 不变）：零 JS（唯一例外是 base.html 语言选择器的内联 onchange）；
   无渐变 / 无图标圆圈 / 无 emoji / 无在线字体；动效仅 hover/focus 的颜色与边框
   过渡，120–180ms，且尊重 prefers-reduced-motion。
   v3 放宽的两条（见 tokens.css 文件头）：允许两级克制阴影；圆角 2px → 10px。
   移动优先：基础规则面向 390px 竖屏（首要设备），向上用 min-width 升级。
   ========================================================================== */

/* --- 1. 基础 ------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-body);
  color: var(--color-ink);
  background: var(--color-bg);
  /* 短页面（列表页搜索结果、符合性声明）内容不足一屏时，页脚会浮在半空、
     下面露出一截页面底色。撑满视口 + main 吃掉剩余高度 → 页脚永远贴底。
     dvh 优先（移动端地址栏收放不跳），vh 作旧浏览器兜底。 */
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

h1, h2, h3 { line-height: var(--leading-heading); }
img { max-width: 100%; height: auto; }

a { color: var(--color-link); text-underline-offset: 2px; }
a:visited { color: var(--color-link-visited); }
a:hover { color: var(--color-link-hover); }

/* 键盘焦点可见（鼠标点击不显示环，避免视觉噪音） */
:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

.container {
  width: 100%; max-width: var(--max-width);
  margin-inline: auto; padding-inline: var(--space-4);
}
@media (min-width: 640px) { .container { padding-inline: var(--space-6); } }

main { flex: 1 0 auto; padding-block: var(--space-6) var(--space-12); }

/* 跳到主要内容（键盘用户第一个 Tab 落点） */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--color-primary); color: var(--color-on-primary);
  padding: var(--space-3) var(--space-4); text-decoration: none;
}
.skip-link:focus { left: var(--space-4); top: var(--space-2); }

/* --- 2. 按钮 ------------------------------------------------------------- */
.button {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; padding: 0 var(--space-5);
  background: var(--color-primary); color: var(--color-on-primary);
  border: 1px solid var(--color-primary); border-radius: var(--radius-sm);
  font-size: var(--text-base); font-weight: 600; text-decoration: none;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.button:visited { color: var(--color-on-primary); }
.button:hover {
  background: var(--color-primary-strong); border-color: var(--color-primary-strong);
  color: var(--color-on-primary);
}

/* --- 3. 页头（全站复用） ------------------------------------------------- */
/* 页头实测高度 —— sticky 目录的 `top` 与锚点 `scroll-margin-top` 的基准。
   不进 tokens.css：变量值要在 media query 里改，本文件是断点唯一落点。
   两个值都是浏览器实测（2026-09-19，1440/768/390 三档），不是估算：
     移动端 = 内边距 8×2 + 品牌行 44 + 行间距 8 + 动作组 96（站点导航 44 + 间距 8 +
              语言选择器 44）+ 下边框 1 = 165；动作组整行折到品牌下方，故取**有两行**的
              最坏值 —— scroll-margin 宁可多留白，也不能少留（少了标题会钻到页头底下）。
     ≥640px = 内边距 8×2 + 单行 44 + 下边框 1 = 61。
   改动页头内边距、控件最小高度或增删动作项后，这两个数要同步。 */
/* 页头实测高度：**两档都是 61px**（2026-09-19 起窄屏动作组也压成一行，
   原先窄屏是 165px —— 品牌行 + 符合性声明行 + 语言选择器行）。
   这个标量被 scroll-margin-top（详情页锚点跳转）与目录 sticky 的 top 引用：
   写小了锚点跳转后标题会钻进 sticky 页头底下，写大了每跳一次都多一段空白。
   窄屏 61px 的前提是动作组不折行，实测下限 **360px**：320px 下品牌(70) + 动作组(220)
   超过 288px 可用宽，动作组折到第二行、页头变 113px。320 已在项目验证尺寸
   （375/390/768/1024/1280/1440）之下，不为它增加断点；真要支持再压 .brand-logo 高度。 */
:root { --header-h: 61px; }

.site-header {
  position: sticky; top: 0; z-index: var(--z-header);
  background: var(--color-surface); border-bottom: var(--border);
}

/* 品牌 / 动作组两区（F5）：动作组整行折到下一行，不挤压品牌。
   2026-09-19：动作组内部不再竖排 —— 窄屏也是**一行**（符合性声明 · 语言选择器），
   放不下时由这里的 flex-wrap 整组折行。 */
.header-inner {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: var(--space-2) var(--space-4);
  padding-block: var(--space-2);
}

.brand { display: inline-flex; align-items: center; text-decoration: none; min-height: 44px; }
.brand span { color: var(--color-primary); font-weight: 700; font-size: var(--text-lg); }
.brand-logo { display: block; height: 32px; width: auto; }

.header-actions {
  display: flex; align-items: center;
  gap: var(--space-3);
}

.site-nav a {
  display: inline-flex; align-items: center; min-height: 44px;
  color: var(--color-link); font-weight: 600; text-decoration: none; white-space: nowrap;
}
.site-nav a:hover { color: var(--color-link-hover); text-decoration: underline; }

.lang-switch { display: flex; align-items: center; gap: var(--space-2); }
/* <640px：语言标签「语言/Language」转视觉隐藏 —— 它几乎是全页最长的文案，
   却只是给一个下拉框做注脚（注记 2026-09-19，客户：「选页面语言不该占这么大的空间」）。
   声明与 .sr-only 一致（沿用 .qr-text 的既有做法：不引入新工具类，就地写明）。 */
.lang-switch label {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}
.lang-select {
  /* max-width 是页头高度的**去风险阀**：原生 select 的 auto 宽度按「最宽的选项」算，
     语言清单（D29 待客户定）一旦出现「Português (Brasil)」这类长名，select 就会把
     动作组顶到第二行、页头从 61px 涨到 112px —— 而 --header-h 是个标量，跟不上。
     钳到 8rem 后 320px 宽也不折行，超长语言名在收起态被裁（展开的下拉仍显示全名）。 */
  flex: none; min-width: 0; max-width: 8rem; min-height: 44px;
  padding: 0 var(--space-2); font-family: inherit; font-size: var(--text-base);
  color: var(--color-ink); background: var(--color-surface);
  border: var(--border); border-radius: var(--radius-sm);
  transition: border-color var(--dur-fast) var(--ease-out);
}
.lang-select:hover { border-color: var(--color-border-strong); }
.lang-links { display: flex; flex-wrap: wrap; gap: var(--space-3); list-style: none; margin: 0; padding: 0; }

@media (min-width: 640px) {
  .brand-logo { height: 40px; }
  .header-actions { gap: var(--space-4); }
  .lang-select { flex: 1 1 auto; max-width: none; }
  /* 单行页头里语言标签放回来：横排空间富余，它是下拉框的正常注脚 */
  .lang-switch label {
    position: static; width: auto; height: auto;
    margin: 0; overflow: visible; clip-path: none;
  }
}

/* --- 4. 页脚 ------------------------------------------------------------- */
/* 2026-09-20 客户：「底部占用太多空间」→ 三处收窄：
  ① 上下留白 48/32 → 40/24；② 标签与**同行**（原来 dt 一行、dd 一行 → 6 行变 3 行）；
  ③ 整块弱化一档（标签 ink-muted、值 ink-soft、字号 xs/sm）—— 页脚是落款，不是内容。
  2026-09-30 客户第 4 条**换版**：改两行居中落款（参照客户提供的底部图）——
  第一行 © 公司名 | 地址，第二行 网址：… | 邮箱：…（必要时续 电话 / 欧盟授权代表）。
  仍是落款：字号最小档、中性灰，只是不再是一条条 dt/dd 行。 */
.site-footer {
  margin-top: var(--space-10); padding-block: var(--space-6);
  background: var(--color-surface); border-top: var(--border);
  font-size: var(--text-sm);
}
/* 两行居中落款（客户 2026-09-30 第 4 条）。每行是 flex 行：项多时**按项**换行
   （「网址：https://…」不会被从中间截断），整行居中；项间竖线由相邻兄弟选择器生成
   —— 上一项没值就不出现（模板不手写分隔符，否则会留下孤立的「|」）。 */
.company-footer {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--space-2); margin: 0; text-align: center;
}
.footer-line {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: baseline;
  gap: var(--space-2) var(--space-3); margin: 0;
  font-size: var(--text-xs); color: var(--color-ink-soft);
  word-break: break-word;
}
.footer-line .footer-item + .footer-item::before {
  content: "|"; margin-right: var(--space-3); color: var(--color-ink-muted);
}
/* 第一行（© 公司名 | 地址）是落款主体，比第二行的联系方式略重一档。 */
.footer-line:first-child { color: var(--color-ink-muted); }
.company-footer a { color: inherit; }
/* 窄屏逐项堆叠、去掉竖线：换行时竖线会留在**行首**（「| 广东省…」），
   一行一项比一行一个「| 项」干净，也更合 D20 mobile-first（落款逐行读）。 */
@media (max-width: 639px) {
  .footer-line { flex-direction: column; align-items: center; gap: var(--space-1); }
  .footer-line .footer-item + .footer-item::before { content: none; }
}
/* `.footer-nav`（页脚的「符合性声明」入口，2026-09-20 从页头移来）**已退役**
   —— 客户 2026-10-02 第 3 条明确不要这一条（他手动删过、被 2026-10-02 前的
   一次修复改了回来）。型号级声明的入口只剩详情页页尾「相关文档」里的那一条。 */

/* --- 5. 列表页 ----------------------------------------------------------- */
/* 行动带：列表页唯一的强焦点。与详情页概要卡共用同一套令牌语言
   （实心 primary 底 + 亮蓝左色条 + --color-on-primary 系列文字），
   这样两个页面的「强调块」是同一个东西，不是两种风格。 */
.page-band {
  margin-bottom: var(--space-5); padding: var(--space-4);
  background: var(--color-primary);
  border-left: 4px solid var(--color-primary-bright);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  color: var(--color-on-primary);
}
.page-intro { margin-bottom: var(--space-4); }
/* 窄屏用 text-xl：390px 下 22px 正好让 13 字标题落在**一行**，
   28px 会折成两行、白占 38px —— 标题折行在窄屏是纯损失，不换来任何信息。 */
.page-intro h1 { margin: 0 0 var(--space-2); font-size: var(--text-xl); }
.page-band .page-lede { max-width: none; color: var(--color-on-primary-soft); }

/* 搜索：不换行 —— 输入框吃掉剩余宽度，按钮永不被挤到下一行 */
.search { display: flex; flex-wrap: nowrap; align-items: center; gap: var(--space-2); margin-bottom: 0; }
.search input[type="search"] {
  flex: 1 1 auto; min-width: 0; min-height: 44px; padding: 0 var(--space-3);
  font-family: inherit; font-size: var(--text-base); color: var(--color-ink);
  background: var(--color-surface); border: var(--border); border-radius: var(--radius-sm);
  transition: border-color var(--dur-fast) var(--ease-out);
}
.search input[type="search"]:hover { border-color: var(--color-border-strong); }
/* 带内的搜索按钮**反转**：实底 primary 上再放实底 primary 会互相吞掉，
   改成白底 + primary-strong 文字（7.6:1 ✓ AA），hover 落回 on-primary-soft。 */
.search button {
  flex: none; min-height: 44px; padding: 0 var(--space-5);
  font-family: inherit; font-size: var(--text-base); font-weight: 600;
  color: var(--color-on-primary); background: var(--color-primary);
  border: 1px solid var(--color-primary); border-radius: var(--radius-sm); cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out);
}
.search button:hover { background: var(--color-primary-strong); }
.page-band .search button {
  color: var(--color-primary-strong);
  background: var(--color-on-primary); border-color: var(--color-on-primary);
}
.page-band .search button:hover {
  color: var(--color-primary-strong);
  background: var(--color-on-primary-soft); border-color: var(--color-on-primary-soft);
}
.search-clear { flex: none; display: inline-flex; align-items: center; min-height: 44px; padding: 0 var(--space-3); color: var(--color-on-primary); }

/* 计数行：<640px 整体收起。窄屏行动带竖着堆（标题 + 说明 + 搜索框）已经占满头部，
   再挂一行计数是纯负担 —— 它要说明的事，紧跟其后的表格当场就演示了。 */
.band-meta {
  display: none;
  margin: var(--space-4) 0 0; padding-top: var(--space-3);
  border-top: 1px solid var(--color-on-primary-rule);
  font-size: var(--text-sm); color: var(--color-on-primary-soft);
}
@media (min-width: 640px) {
  .page-band { padding: var(--space-5) var(--space-5) var(--space-4); }
  .page-intro h1 { font-size: var(--text-2xl); }
  .band-meta { display: block; }
}
@media (min-width: 900px) {
  .page-intro h1 { font-size: var(--text-4xl); }
  .page-intro { margin-bottom: var(--space-5); }
}

.result-summary { margin: 0 0 var(--space-4); color: var(--color-ink-soft); font-size: var(--text-sm); }

/* 表格容器卡片（列表页专用修饰符）。**不用 overflow: hidden 裁圆角** ——
   二维码浮层是绝对定位且朝下越出本行，一旦被裁剪就永远显示不全；改用内边距
   把表格从圆角边拉开，视觉上同样是「面板里的一张表」，且零裁剪风险。 */
.table-wrap--card { padding: var(--space-2); background: var(--color-surface); border: var(--border); border-radius: var(--radius); }
@media (min-width: 640px) { .table-wrap--card { box-shadow: var(--shadow-card); } }

/* 型号表 —— 移动端降级为卡片行（<640px），≥640px 恢复真表格 */
.battery-table { width: 100%; border-collapse: collapse; }
.battery-table thead { display: none; }
.battery-table, .battery-table tbody, .battery-table tr, .battery-table td { display: block; }

.battery-table tr {
  position: relative;
  margin-bottom: var(--space-3); padding: var(--space-2) var(--space-4);
  background: var(--color-surface); border: var(--border); border-radius: var(--radius-sm);
}
.battery-table tr:last-child { margin-bottom: 0; }
.battery-table td {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4); padding: var(--space-3) 0;
}
.battery-table td + td { border-top: var(--border); }
.battery-table td[data-label]::before {
  content: attr(data-label); flex: none;
  font-size: var(--text-sm); color: var(--color-ink-muted);
}
/* 卡片标题不需要「型号」标签。特异性必须**严格高于**上面的 td[data-label]::before
   （0,2,2）：只写 `.battery-table .cell-model::before` 是 (0,2,1)，压不住，
   卡片标题仍会渲染成「型号  CR2032」。这里补上 td 与属性选择器 → (0,3,2)。 */
.battery-table td.cell-model[data-label]::before { content: none; }

.row-link {
  color: var(--color-ink); font-family: var(--font-mono); font-size: var(--text-lg);
  font-weight: 700; font-variant-numeric: tabular-nums; text-decoration: none;
}
.row-link:visited { color: var(--color-ink); }
.row-link:hover { color: var(--color-link); text-decoration: underline; }
/* stretched link：型号锚点的 ::after 铺满整行 → 整行可点（行内定位基准是 tr） */
.row-link::after { content: ""; position: absolute; inset: 0; }

.row-chevron { width: 18px; height: 18px; flex: none; align-self: center; color: var(--color-ink-muted); pointer-events: none; }
.cell-chem { color: var(--color-ink-soft); }

/* 二维码单元格：抬 z-index 保住自己的点击（其余区域归 stretched link） */
.cell-qr { position: relative; z-index: 1; }

.qr-trigger {
  display: inline-flex; align-items: center; gap: var(--space-2);
  min-height: 44px; padding: var(--space-2) var(--space-3);
  color: var(--color-link); text-decoration: none;
  border: var(--border); border-radius: var(--radius-sm);
  transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.qr-trigger:visited { color: var(--color-link); }
/* hover 底色是淡天蓝 → 文字必须同步换成 primary-strong（#0369a1 压在 #e0f2fe 上只有
   3.6:1，见 test_frontend_tokens.py 的 TEXT_PAIRS 注释） */
.qr-trigger:hover { border-color: var(--color-border-strong); background: var(--color-neutral-faint); color: var(--color-primary-strong); }
.qr-icon { display: none; width: 20px; height: 20px; flex: none; }
.qr-pop { display: none; }

/* 指针设备：图标代替文字 + 浮层预览。
   触屏保持可见的「下载二维码」文字链接 —— 首要设备是扫码进入的手机，
   hover-only 会让二维码在手机上不可下载（design §4.2 偏离说明）。 */
@media (hover: hover) and (pointer: fine) {
  .qr-icon { display: block; }
  /* 文字改为视觉隐藏而非 display:none —— 图标是 aria-hidden，链接仍需可访问名 */
  .qr-text {
    position: absolute; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap; border: 0;
  }
  .qr-pop {
    display: block; position: absolute; right: 0; top: 100%; z-index: var(--z-overlay);
    /* max-content：二维码单元格是 1% 窄列，浮层若按包含块收缩-fit 会被压到 ~80px */
    width: max-content;
    padding: var(--space-3); text-align: center;
    background: var(--color-surface-raised); border: var(--border);
    border-radius: var(--radius-lg); box-shadow: var(--shadow-overlay);
    opacity: 0; visibility: hidden; transform: translateY(-4px);
    pointer-events: none;   /* 链接本身已是下载目标，浮层不截获点击 */
    transition: opacity var(--dur-base) var(--ease-out),
                transform var(--dur-base) var(--ease-out),
                visibility var(--dur-base) var(--ease-out);
  }
  /* 浮层向下越出本行、压到下一行上；下一行的 .cell-qr 同样 z-index:1，
     且 DOM 更靠后 → 必须在本单元格 hover 时把自己的层级抬到它之上 */
  .cell-qr:hover, .cell-qr:focus-within { z-index: calc(var(--z-overlay) + 1); }
  .cell-qr:hover .qr-pop,
  .cell-qr:focus-within .qr-pop { opacity: 1; visibility: visible; transform: translateY(0); }
  .qr-pop img { display: block; width: 120px; height: 120px; max-width: none; }
  .qr-pop-hint { display: block; margin-top: var(--space-2); font-size: var(--text-xs); color: var(--color-ink-soft); }
}

@media (min-width: 640px) {
  .battery-table { display: table; }
  .battery-table thead { display: table-header-group; }
  .battery-table tbody { display: table-row-group; }
  .battery-table tr {
    display: table-row; position: relative;
    margin: 0; padding: 0; border: 0; border-radius: 0; background: transparent;
  }
  .battery-table th, .battery-table td {
    display: table-cell; padding: var(--space-3) var(--space-4);
    text-align: left; vertical-align: middle;
  }
  .battery-table td + td { border-top: 0; }
  .battery-table td[data-label]::before { content: none; }
  .battery-table th {
    background: var(--color-neutral-soft); color: var(--color-ink);
    font-size: var(--text-xs); font-weight: 700; white-space: nowrap;
  }
  /* 型号与二维码列收缩到内容宽，化学体系吃掉剩余空间 —— 否则 auto 布局把
     主键列撑得过宽，二维码被推到离化学体系很远的右边 */
  .battery-table th:first-child, .battery-table .cell-model { width: 1%; white-space: nowrap; }
  .battery-table .cell-qr { width: 1%; white-space: nowrap; }
  .battery-table tbody tr { border-top: var(--border); }
  .battery-table tbody tr:nth-child(even) { background: var(--color-surface-subtle); }
  /* hover 用 primary-faint 而非 subtle，否则与斑马纹同色 → 偶数行无反馈 */
  .battery-table tbody tr:hover { background: var(--color-neutral-faint); }
  .cell-chem { color: var(--color-ink-soft); }
  .row-chevron { margin-left: var(--space-2); }
}

/* 分页 */
.pagination {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  margin-top: var(--space-6); font-size: var(--text-sm); color: var(--color-ink-soft);
}
.pagination a {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 var(--space-4);
  text-decoration: none; background: var(--color-surface);
  border: var(--border); border-radius: var(--radius-sm);
  transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.pagination a:hover { border-color: var(--color-border-strong); background: var(--color-neutral-faint); color: var(--color-primary-strong); }

/* 空态 / 错误页 */
.empty-state {
  padding: var(--space-8) var(--space-4); text-align: center;
  background: var(--color-surface); border: var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-card);
}
.empty-state h1, .empty-state h2 { margin: 0 0 var(--space-3); font-size: var(--text-xl); }
.empty-state p { max-width: var(--measure); margin: 0 auto var(--space-3); color: var(--color-ink-soft); }
.empty-state p:last-child { margin-bottom: 0; }

/* --- 6. 详情页 ----------------------------------------------------------- */
/* 页面大标题（客户 2026-09-20）：详情页自己的名字，与型号无关。
   两个落点由 presentation.DETAIL_TITLE_IN_HERO 决定（卡外 .detail-title /
   卡内 .hero-page-title），字号同档 —— 换落点不该顺带换体量。 */
.detail-title {
  margin: 0 0 var(--space-4);
  font-size: var(--text-2xl); letter-spacing: -.01em;
}
@media (min-width: 640px) { .detail-title { font-size: var(--text-3xl); } }

/* 面包屑（.breadcrumb）2026-09-20 整体移除：本页基本由包装二维码直接打开，
   没有「从列表点进来」这条路径 —— 规则随模板一起删，不留死样式。 */

/* 概要卡：全页唯一的强焦点，也是唯一一处大面积天蓝（v4 的核心决定）。
   它是本页唯一一次「这是什么」的第一印象 —— 用**实心主色 + 白字**把它钉住，
   其余所有表面退回中性灰。蓝因此从「到处都有的底色」变成「一个明确的重点」。
   卡内两层文字：主文字纯白，次级文字用 on-primary-soft（白会与主文字抢层级）。 */
.hero-card {
  position: relative;
  display: flex; flex-wrap: wrap; align-items: flex-start;
  justify-content: space-between; gap: var(--space-4);
  margin-bottom: var(--space-4); padding: var(--space-4) var(--space-3);
  background: var(--color-primary);
  border: 1px solid var(--color-primary);
  border-left: 4px solid var(--color-primary-bright);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  color: var(--color-on-primary);
}
/* 抬头与 DoC 页的 `.doc-head` 是**同一版面**（客户 2026-09-29）：文字在左、图片在右。
   两处刻意保持同一组声明值 —— 同一站点里这两块该长得一样；改一处请同步另一处。 */
.hero-main { flex: 1 1 12rem; min-width: 0; }
/* 卡内大标题（DETAIL_TITLE_IN_HERO = True 时渲染）：实心蓝上的白字，抬头的主文字层。 */
.hero-page-title {
  margin: 0;
  font-size: var(--text-xl); font-weight: 700; letter-spacing: -.01em;
  color: var(--color-on-primary);
}
@media (min-width: 640px) { .hero-page-title { font-size: var(--text-2xl); } }
/* 型号：抬头的第二行（DoC 页 h1 下的副标题同位）。等宽 + 数字等宽是型号的视觉签名，
   但它不再与页面名抢层级 —— 抬头的最大字号永远是页面名。 */
.hero-model {
  margin: var(--space-1) 0 0;
  font-family: var(--font-mono); font-size: var(--text-lg); font-weight: 600;
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
  color: var(--color-on-primary-soft);
}
/* 概要卡身份行（客户 2026-10-16）：型号下方「类别（类型）」一行。
   走 on-primary-soft（与型号抢层级会读成两个主文字）；此前的「字段标签 + 值徽标」
   两段式随本轮改版退役。 */
.hero-identity { margin: var(--space-2) 0 0; font-size: var(--text-sm); color: var(--color-on-primary-soft); }

/* 右栏是**该型号详情页链接的二维码**（客户 2026-09-21 换成二维码，2026-09-29 换到右栏）。
   二维码必须落在**浅底**上（深色底上扫不出来），故白底 + 内边距当静区；
   它在卡上的作用是「把这个页面带走」，不是装饰。104px 与 DoC 页抬头同一档。 */
.hero-qr { flex: none; text-align: center; }
.hero-image {
  display: block; width: 104px; height: 104px; object-fit: contain;
  padding: var(--space-1);
  background: var(--color-surface); border: 1px solid var(--color-on-primary-rule);
  border-radius: var(--radius-sm);
}
.hero-qr-label { display: block; margin-top: var(--space-1); font-size: var(--text-xs); color: var(--color-on-primary-soft); }
/* 无产品图时的占位格（.hero-image--empty）随产品图一起退役：二维码恒有内容，
   右栏不再有「空档」这一状态。 */

/* 概要卡内可能出现语义色徽标（如「一次电池（不可充电）」）。它们是**不透明**的
   琥珀/灰底，压在实心蓝上依然成立 —— 不要为了统一而抹掉语义色。 */

@media (min-width: 640px) {
  /* 卡片更宽时给足内边距（与 `.doc-card` / DoC 抬头的 640 档同一值）。 */
  .hero-card { padding: var(--space-5); }
}
@media (min-width: 1280px) { .hero-card { padding: var(--space-6); } }

/* 页内目录（F9 引入）：**<900px 整体隐藏**（客户 2026-09-19：「页宽缩小之后隐藏导航，
   不要影响正文布局」）。原本窄屏是概要卡下方的两列 chip 盒，实测占 261px 竖向空间，
   把正文整块顶下去 —— 窄屏的竖向空间比可导航性更稀缺，目录在这个宽度下是净负担。
   用 `display: none`（而非 visibility/clip）：目录在窄屏没有可用替代形态，
   留在无障碍树里只会让 AT 用户多走一段走不通的路。
   **将来若要给窄屏补导航，正确形态是折叠抽屉，不是把这块加回来。** */
.toc { display: none; }
.toc-title { margin: 0 0 var(--space-3); font-size: var(--text-xs); font-weight: 700; letter-spacing: .08em; color: var(--color-ink-muted); }
.toc-list { margin: 0; padding: 0; list-style: none; }
.toc-list a {
  display: block; padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm); line-height: var(--leading-data); text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}

/* 详情页容器放宽（main_class = container--detail）：让出 `--toc-w` + 列间距给目录，
   正文列因此仍是完整的 --max-width 内容宽（1072）—— 目录落在 main 内容列**之外**，
   不再从正文里切走 240px。≥1368px 视口正文回到满宽，更窄时正文照常先变窄。 */
.container.container--detail { max-width: calc(var(--max-width) + var(--toc-w) + var(--space-6)); }
/* 页头共用普通容器宽时，品牌位会卡在「目录」与「正文」之间成为第三条左边界；
   详情页把页头也拉到同一容器宽 —— 品牌与目录左缘齐平、动作组与正文右缘齐平。
   仅 ≥900px（有目录列时）生效，窄屏页头与其他页保持一致。 */
@media (min-width: 900px) {
  .page-detail .header-inner { max-width: calc(var(--max-width) + var(--toc-w) + var(--space-6)); }
}

/* ≥900px：目录转左侧固定悬停轨道（**推翻 F9「不 sticky」**，2026-09-19 客户要求「悬浮在 main 外」）。
   两行显式网格：行 1 = 导语（下架提示/面包屑/概要卡），行 2 = 正文。
   目录放左列并**显式跨行 1–3** —— 跨行是 sticky 的行程来源（网格区域高 = 两行高之和）；
   注意 `grid-row: 1 / -1` 在这里**无效**（无显式行时 -1 == 1，实测跨度为 1，sticky 无行程），
   所以行号必须写死，改结构时同步。
   目录项沿用「左侧 3px 轨道」语言：每一项是一条连接成整条的浅色轨道，hover 只把当前段
   染成亮天蓝 —— 结构信息，非装饰。 */
.detail-body { display: block; }
@media (min-width: 900px) {
  .detail-body {
    display: grid; grid-template-columns: var(--toc-w) minmax(0, 1fr);
    column-gap: var(--space-6); row-gap: var(--space-8);
    align-items: start;
  }
  .detail-intro { grid-column: 2; grid-row: 1; }
  .detail-flow { grid-column: 2; grid-row: 2; }
  /* 行间距已由 row-gap 给，概要卡自带的 20px 下边距在栅格项内不会外溢，会叠上去 */
  .detail-intro > :last-child { margin-bottom: 0; }
  .toc {
    display: block; grid-column: 1; grid-row: 1 / 3;
    position: sticky; top: calc(var(--header-h) + var(--space-4));
    padding: var(--space-4) var(--space-2);
    background: var(--color-surface); border: var(--border); border-radius: var(--radius);
    box-shadow: var(--shadow-card);
    /* 兜底：译文字段比中文长，目录总高可能超出视口 —— sticky 元素一旦吸顶就不再移动，
       矮视口下末尾几项会永远够不着。给它自己的滚动条，只在真溢出时出现。 */
    max-height: calc(100vh - var(--header-h) - var(--space-8));
    overflow-y: auto;
  }
  .toc-list a {
    display: block; min-height: 0; padding: var(--space-2) var(--space-3);
    font-size: var(--text-sm); line-height: var(--leading-data);
    background: none; border: 0; border-left: 3px solid var(--color-border);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  }
  .toc-list a:hover {
    background: var(--color-neutral-faint);
    border-left-color: var(--color-primary-bright); color: var(--color-primary-strong);
  }
}

/* 板块 = 一张白卡片（v3 的核心改动）。v2 是「淡绿整条色块标题」压在连续白底上，
   7 个板块连成 4000px 的一堵墙；卡片把每一块变成独立对象，同时给页面底一个存在理由。
   scroll-margin-top：页头是 sticky 的，锚点跳转过去时标题会钻到页头底下 ——
   卡片与标题各留一份页头高度 + 呼吸位（#qr-heading 挂在 h2 上，故 h2 也要）。 */
.detail-section {
  margin-bottom: var(--space-5);
  padding: var(--space-5) var(--space-4) var(--space-6);
  background: var(--color-surface);
  border: var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  scroll-margin-top: calc(var(--header-h) + var(--space-4));
}
/* 内边距与 DoC 页的 `.doc-card` **同一组值**（客户 2026-09-29：两页同一套卡片语言）——
   改这里请同步 `.doc-card`，否则两页的卡片观感会悄悄分叉。 */
@media (min-width: 640px) { .detail-section { padding: var(--space-6); } }

/* 分区标题：一条通长细线 + 左端 40px 深天蓝短杠（与 `.doc-card > h2` 同规格 ——
   两页的板块标题长得一样，客户 2026-09-29 要求详情页对齐 DoC 页的版面语言）。 */
.detail-section > h2 {
  position: relative;
  margin: 0 0 var(--space-4); padding: 0 0 var(--space-3);
  font-size: var(--text-lg);
  border-bottom: 1px solid var(--color-border);
  scroll-margin-top: calc(var(--header-h) + var(--space-4));
}
.detail-section > h2::after {
  content: ""; position: absolute; left: 0; bottom: -1px;
  width: 40px; height: 3px; border-radius: 0 3px 3px 0;
  background: var(--color-primary);
}

/* 空卡占位（客户 2026-10-14：没有内容的卡片也要展示，卡内提示「暂无相关信息」）。
   详情页 `.detail-section` 与 DoC 页 `.doc-card` 的**空卡共用**这一条（文案键
   `site.under_construction`）—— 与列表页的 `.empty-state`（整页空态：居中 + 大标题）
   刻意不同：这里只是**单张卡**里的一行说明，套居中大块会让空卡比有内容的卡还显眼。
   色用 --color-ink-soft（登记在 test_frontend_tokens 的 AA 对里，压卡片底 4.5:1 以上）。 */
.section-placeholder {
  margin: 0;
  font-size: var(--text-base);
  color: var(--color-ink-soft);
}

/* 组标题（「基本信息」「制造商」…）：短竖标 + 文字。层级低于 h2、高于字段标签。 */
.group-heading {
  display: flex; align-items: center; gap: var(--space-2);
  margin: var(--space-6) 0 var(--space-2);
  font-size: var(--text-sm); font-weight: 700; letter-spacing: .04em;
  color: var(--color-ink-soft);
}
.group-heading::before { content: ""; flex: none; width: 3px; height: 14px; background: var(--color-primary-bright); border-radius: 2px; }
/* 卡片标题之下第一个组标题不再多要一段上边距（h2 的 margin-bottom 已经给过） */
.detail-section > h2 + .group-heading { margin-top: 0; }

/* `.company-groups` / `.company-group`（一组卡内并排的两组，1280px 起两列）**已退役**
   —— 客户 2026-09-30 第 1 条把制造商与欧盟授权代表拆成两张卡（`DETAIL_BLOCKS` 两条
   `kind == "company"` 条目），卡片之间的分界由卡片自身承担，不再需要组间分隔线与
   两列栅格；「值列被压成一字一行」的那条阈值推导（1280px）随之对本页失效 ——
   `test_frontend_layout.py` 的两列不变式改为「守空的座位」：详情页正文列里
   再出现两列，就必须挂在 1280px 档上。 */

/* 厂商卡并排（客户 2026-10-02 第 1 条：「按照响应式的布局。行宽就分两列，窄就单独一个」）。
   用 flex 行而不是 `grid-template-columns: repeat(2, …)`：**只有一张厂商卡**时
   （例如只录入了制造商）flex-grow 会让它自己铺满整行，grid 会留半行空档。
   阈值仍是 1280px（`test_frontend_layout.py` 的实测表）：900–1199px 正文列只有
   588–888px，两张卡各自的字段值列会被压成一字一行 —— 那时 flex-wrap 自动退回单列，
   这正是「窄就单独一个」。竖直间距仍由各卡的 margin-bottom 提供（不设 row-gap），
   页面的竖向节奏与单列形态完全一致。 */
@media (min-width: 1280px) {
  .detail-flow { display: flex; flex-wrap: wrap; column-gap: var(--space-5); }
  .detail-flow > * { flex: 1 1 100%; min-width: 0; }
  .detail-flow > .detail-section--company { flex: 1 1 22rem; }
}

/* 字段表（客户 2026-09-20）：产品信息与「制造商 / 欧盟授权代表」两块都是
   **一张表**，字段标题走 `<th scope="row">`（此前是 `<dl>` + muted 小字 dt）。
   两个要求合在一起驱动了这次改动：
     ①「内容改为表格布局」—— 语义上就是一张行标题表（读屏也会读成「行标题」）；
     ②「凸显字段标题」—— 加粗 + 主文本色 + 浅底标签栏，标签不再是灰注脚。
   标签列仍是**固定 7em**（不是按内容自适应）：两块表各自独立算列宽，自适应会让
   「制造商」与「欧盟授权代表」两张表的标签/值基线错开；固定列宽让所有行、所有块
   共用同一条基线（与客户 2026-09-20「label 对齐、内容对齐」同一取舍）。
   标签/值间距由 td 的 padding-left（--space-5 = 20px）提供，行分隔线仍通长。 */
.kv-table { width: 100%; border-collapse: collapse; }
.kv-table tr { border-bottom: 1px solid var(--color-border); }
.kv-table tr:last-child { border-bottom: 0; }
.kv-table th, .kv-table td { padding: var(--space-3) 0; text-align: left; vertical-align: top; }
.kv-table th[scope="row"] {
  width: 7em;                       /* 固定标签列（见上：两块表基线对齐） */
  /* 客户 2026-09-21：字段标题**垂直居中、水平靠左**。此前 vertical-align: top ——
     值长的行（产品概述、关键原材料）里标题贴着上沿，看起来像「掉在行外」。 */
  vertical-align: middle; text-align: left;
  font-size: var(--text-sm); font-weight: 700; color: var(--color-ink);
  background: var(--color-surface-subtle);   /* 浅底标签栏：标题从灰注脚升为行标题 */
  overflow-wrap: anywhere;
}
.kv-table td {
  padding-left: var(--space-5);     /* 标签/值间距（上一轮 8px 太挤） */
  font-size: var(--text-base); color: var(--color-ink); overflow-wrap: anywhere;
}
/* markdown 单元格（产品概述 / 关键原材料）在表内不要撑出多余行距 */
.kv-table td p { margin: 0 0 var(--space-2); }
.kv-table td p:last-child { margin-bottom: 0; }

/* 仍在使用 kv 行语言的两处：符合性声明页的签署日期行（conformity.html 独占一行
   的 `<dl class="kv-list">`）与页脚（base.html 的 `.company-footer` 覆盖 display）。
   详情页正文已全部改走 `.kv-table`，这两处保原样 —— 客户 2026-09-20 的反馈只
   针对详情页，别顺手改掉已验收的页面。 */
.kv-list { margin: 0; }
.kv-row { padding: var(--space-3) 0; border-bottom: 1px solid var(--color-border); }
.kv-row:last-child { border-bottom: 0; padding-bottom: 0; }
.kv-row dt { font-size: var(--text-sm); font-weight: 500; color: var(--color-ink-muted); }
.kv-row dd { margin: var(--space-2) 0 0; font-size: var(--text-base); color: var(--color-ink); overflow-wrap: anywhere; }
.kv-row dd:has(> .badge) { margin-top: var(--space-2); }
/* 值里是图片时不要 baseline 对齐（标签会被推到图片底边，看起来像错位） */
.kv-row:has(.zoom-link) { align-items: start; }
@media (min-width: 640px) {
  .kv-row { display: grid; grid-template-columns: 7em minmax(0, 1fr); align-items: baseline; }
  .kv-row dd { margin: 0; padding-left: var(--space-5); }
}
/* 字段表里的 image 字段（如「电池标签图片」）：**小缩略图** + 页内看图组件。
   客户 2026-09-21：按**最大高度**限制（≈120px）而不是按宽度 —— 电池标签照片是竖版
   （演示数据 1080×1440），按宽度限 200px 会渲染成 200×267，仍是半屏高的一列。
   `max-height` + `max-width: 100%` 两个上限同时生效、按比例缩放（object-fit 不需要：
   两轴都留 `auto`，浏览器自己保比例），故窄屏自动变矮变窄 —— 这就是「适配响应式」。
   链接本身改成 `inline-block` 收缩包裹：边框与放大镜角标要贴住**图片**，而不是贴住
   一个 200px 的固定框（否则竖版图两侧会留一条空白边框）。 */
/* 缩略图链接：`position: relative` 必须挂在**所有** `.zoom-link` 上 —— 表格里的图标
   （label_icons）不在 `.kv-list` 内，漏掉它时 `.zoom-hint` 会以 body 为定位祖先、
   角标飘到页面右缘（实测就这么飘了）。 */
.zoom-link { position: relative; display: block; }
.kv-table .zoom-link {
  display: inline-block; width: auto; max-width: 100%;
  border: var(--border); border-radius: var(--radius-sm);
  background: var(--color-surface-subtle); overflow: hidden;
}
.kv-table .zoom-link img {
  display: block; width: auto; height: auto;
  max-width: 100%; max-height: 104px;   /* 窄屏再收一档 */
}
@media (min-width: 640px) { .kv-table .zoom-link img { max-height: 120px; } }
/* 图片组（客户 2026-10-02：「图片都展示在第二列就可以，不要在新行」）：多张缩略图
   在**同一个值单元格**里并排、按需换行 —— 字段名仍是行标题，不另起一张表。
   每张仍是上面的 `.kv-table .zoom-link`（同一档 104/120px、同样可点开看图）。 */
.gallery { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-2); }
/* 放大镜角标：「点得开」这件事必须自己说出来，缩略图本身不表达可点。装饰，不进 a11y 树。 */
.zoom-hint {
  position: absolute; right: 0; bottom: 0;
  display: flex; align-items: center; justify-content: center;
  width: 22px; height: 22px;
  color: var(--color-on-primary); background: var(--color-primary-strong);
  border-top-left-radius: var(--radius-sm);
}
.zoom-hint svg { width: 13px; height: 13px; }
/* 表格单元格里的图标（96px）：行内尺寸，别让 .zoom-link 撑宽「图标」列 */
.data-table .zoom-link { display: inline-block; }
/* 看图组件：**CSS `:target` 是基线，JS 是增强**（客户 2026-09-20 建，2026-09-29 加缩放）。
   缩略图指向 `#<zoom_id>`，本块由 `:target` 打开；close/backdrop 指向不存在的
   `#zoom-closed` → 关闭且**不滚动**（无匹配元素时浏览器不动滚动位置）。
   装了 `frontend/lightbox.js` 时改走 `.is-open`（不再动 URL hash），并补上
   滚轮/双指缩放、拖拽平移、双击复原、Esc 关闭与焦点回收（见该文件头注释）。 */
.lightbox { display: none; }
.lightbox:target, .lightbox.is-open {
  display: flex; align-items: center; justify-content: center;
  position: fixed; inset: 0; z-index: var(--z-overlay);
  padding: var(--space-4);
  background: rgba(9, 20, 30, .88);
}
/* 背景整片可点关闭；图片与关闭键在它之上（后者 position: relative）。 */
.lightbox-backdrop { position: absolute; inset: 0; }
/* 舞台：图片的定位容器，也是缩放/拖拽的事件面。
   `touch-action: none` 把双指手势交给脚本（否则浏览器会去滚页面 / 缩放页面）。 */
.lightbox-stage {
  position: relative; display: flex; align-items: center; justify-content: center;
  width: 100%; height: 100%; overflow: hidden; touch-action: none; cursor: zoom-in;
}
.lightbox-img {
  position: relative;
  max-width: min(94vw, 1200px); max-height: 86vh; width: auto; height: auto;
  padding: var(--space-2); background: var(--color-surface);
  border-radius: var(--radius-sm); box-shadow: var(--shadow-overlay);
  /* 缩放/平移由脚本写 transform；**不设 transition** —— 跟手比「平滑」重要，
     也顺手满足 prefers-reduced-motion（无动画可降）。 */
  transform-origin: center center; will-change: transform;
  -webkit-user-select: none; user-select: none; -webkit-user-drag: none;
}
/* 光标状态：可缩放 / 可拖拽中 */
.lightbox.is-zoomed .lightbox-stage { cursor: grab; }
.lightbox.is-dragging .lightbox-stage { cursor: grabbing; }
/* 工具条（+ / −）：只有装到脚本时才会从 `hidden` 摘下来（无 JS 不留两个空按钮）。
   位置居中压在弹层底部 —— 拇指区，窄屏也好按；观感与关闭键同源。 */
.lightbox-tools {
  position: absolute; left: 50%; bottom: var(--space-4); transform: translateX(-50%);
  display: flex; gap: var(--space-2);
}
.lightbox-tools[hidden] { display: none; }
.lightbox-btn {
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 0;                     /* 触控目标 ≥44px */
  color: var(--color-on-primary); background: rgba(255, 255, 255, .16);
  border: 1px solid var(--color-on-primary-rule); border-radius: 999px;
  cursor: pointer;
}
.lightbox-btn svg { width: 22px; height: 22px; }
.lightbox-btn:hover { background: rgba(255, 255, 255, .28); }
/* 弹层打开时锁住页面滚动（`:target` 基线不锁 —— 那条路径没有脚本参与） */
html.lightbox-open, html.lightbox-open body { overflow: hidden; }
.lightbox-close {
  position: absolute; top: var(--space-4); right: var(--space-4);
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;                       /* 触控目标 ≥44px（移动优先） */
  font-size: var(--text-xl); line-height: 1; text-decoration: none;
  color: var(--color-on-primary); background: rgba(255, 255, 255, .16);
  border: 1px solid var(--color-on-primary-rule); border-radius: 999px;
}
.lightbox-close:hover { background: rgba(255, 255, 255, .28); }

/* 数据表：与型号表同一套移动端卡片降级 */
.table-wrap { width: 100%; }
.data-table { width: 100%; border-collapse: collapse; font-size: var(--text-base); }
.data-table thead { display: none; }
.data-table, .data-table tbody, .data-table tr, .data-table td { display: block; }
.data-table tr {
  margin-bottom: var(--space-3); padding: var(--space-2) var(--space-4);
  background: var(--color-surface-subtle); border: var(--border); border-radius: var(--radius-sm);
}
.data-table tr:last-child { margin-bottom: 0; }
.data-table td { padding: var(--space-2) 0; overflow-wrap: anywhere; }
.data-table td + td { border-top: var(--border); }
.data-table td[data-label]::before { content: attr(data-label); display: block; font-size: var(--text-xs); color: var(--color-ink-muted); }
@media (min-width: 640px) {
  .data-table { display: table; }
  .data-table thead { display: table-header-group; }
  .data-table tbody { display: table-row-group; }
  .data-table tr { display: table-row; margin: 0; padding: 0; border: 0; border-radius: 0; background: transparent; }
  .data-table th, .data-table td { display: table-cell; padding: var(--space-3) var(--space-4); text-align: left; vertical-align: top; }
  .data-table td + td { border-top: 0; }
  .data-table td[data-label]::before { content: none; }
  .data-table th { background: var(--color-neutral-soft); color: var(--color-ink); font-size: var(--text-xs); font-weight: 700; }
  .data-table tbody tr { border-top: var(--border); }
  .data-table tbody tr:nth-child(even) { background: var(--color-surface-subtle); }
}

/* 标签图标说明表的图标列（E4）：缩略图尺寸，避免大图撑破表格 */
.data-table img { max-width: 96px; height: auto; }

/* 数组表的可见表名（2026-09-20 复核）：`<caption>` 是表格语义上的标题，此前只给
   sr-only 版本，明眼用户在正文里看到一张没有名字的表（如「认证证书」）。
   字号/颜色与 `.group-heading` 同级但更轻 —— 它是表的题目，不是新分区。 */
.data-table .table-caption {
  padding: var(--space-2) 0; text-align: left;
  font-size: var(--text-sm); font-weight: 700; color: var(--color-ink-soft);
}
/* 移动端降级：`.data-table` 是 display:block 的卡片，但 `<caption>` 默认仍是
   table-caption —— 宽度由匿名表格盒决定，实测 390px 下被压成**一字一行**
   （「有害物质」「认证证书」竖排；2026-09-27 无头 Chromium 实测）。故卡片模式下
   让 caption 一起降级为 block，≥640px 转回真表格时再还原 table-caption。 */
.data-table .table-caption { display: block; }
@media (min-width: 640px) { .data-table .table-caption { display: table-caption; } }

/* 有害物质卡片（客户 2026-10-04：单字段成卡，照参考图）
   四段：法规标题行 → 检测方法行 → 5 列表格 → 备注行；表格本体复用 `.data-table`
   （表头 检测项目/限值/单位/MDL/A），末行「结论 / 符合」由 `<tfoot>` 跨列渲染。
   文案是**页面固定文案**（词汇表 `hazardous.*`，可中英翻译），与型号内容无关。 */
.field-card-caption {
  margin: 0 0 var(--space-3);
  font-size: var(--text-lg); font-weight: 700; color: var(--color-ink);
}
.field-card-method { margin: 0 0 var(--space-4); color: var(--color-ink-soft); }
.field-card-note {
  margin: var(--space-4) 0 0;
  font-size: var(--text-sm); color: var(--color-ink-soft);
}
/* 结论行：标签格跨前几列、值格在末列（与表头同款底色，读成表格的一部分） */
.data-table tfoot th, .data-table tfoot td {
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-base); font-weight: 700; color: var(--color-ink);
  text-align: left; background: var(--color-neutral-soft);
}
.data-table tfoot td { border-left: var(--border); }
/* 窄屏：表格降级成卡片，结论行跟着降成「标签在左、值在右」的一行（不竖排、不串格） */
@media (max-width: 639px) {
  .data-table tfoot { display: block; }
  .data-table tfoot tr {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--space-3);
  }
  .data-table tfoot th, .data-table tfoot td {
    display: block; padding: 0; background: transparent; border: 0;
  }
}

/* 长文（markdown 正文，D27 净化后的唯一 |safe 产物） */
.global-intro {  line-height: var(--leading-body); color: var(--color-ink); }
.global-intro p { margin: 0 0 var(--space-4); }
.global-intro p:last-child { margin-bottom: 0; }
.global-intro a { overflow-wrap: anywhere; }

/* 外部文档 URL：等宽 + 可断行（裸 URL 在 390px 下会撑破容器） */
.ext-link { display: inline-flex; align-items: baseline; gap: var(--space-1); }
.ext-link-text { font-family: var(--font-mono); font-size: var(--text-sm); overflow-wrap: anywhere; }
.ext-link-icon { flex: none; width: 14px; height: 14px; align-self: center; }
/* 上传的合规文档（document 类型，客户 2026-10-16）：链接文字是固定文案（「查看文件」），
   不像 ext-link 那样把整条 URL 摊开 —— 服务端 uuid 文件名不该出现在正文里。 */
.doc-link { color: var(--color-primary-strong); text-decoration: underline; }

/* 警示卡（F11）：三重编码 —— 左侧 rail 竖条 + SVG 图标 + 文字徽标。
   卡片底色保持中性，语义色只落在徽标与竖条上，避免整块色wash。 */
.notice, .alert {
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: var(--space-4); border-radius: var(--radius-sm);
  background: var(--color-surface-subtle); border: var(--border);
}
/* 下架提示：信息语义已让位给品牌蓝，走中性 slate（见 tokens.css） */
.notice { margin-bottom: var(--space-5); border-left: 4px solid var(--color-info-rail); color: var(--color-info-text); background: var(--color-info-bg); }
.notice-icon { flex: none; width: 20px; height: 20px; align-self: center; }

.alert-list { display: grid; gap: var(--space-3); margin: 0; padding: 0; list-style: none; }
.alert--warning { border-left: 4px solid var(--color-warn-rail); }
.alert--caution { border-left: 4px solid var(--color-caution-rail); }
.alert--info { border-left: 4px solid var(--color-info-rail); }
.alert-icon { flex: none; width: 20px; height: 20px; align-self: center; }
.alert--warning .alert-icon { color: var(--color-warn-rail); }
.alert--caution .alert-icon { color: var(--color-caution-rail); }
.alert--info .alert-icon { color: var(--color-info-rail); }
.alert-icon svg { display: block; width: 100%; height: 100%; }
.alert-body { flex: 1 1 auto; min-width: 0; }
.alert-badge-row { margin: 0 0 var(--space-2); }
.alert-text { color: var(--color-ink); line-height: var(--leading-body); }

/* 枚举徽标（F12 的 tone 落点）；仅警示类枚举带 tone，其余走纯文本 */
.badge {
  display: inline-block; padding: var(--space-1) var(--space-3);
  font-size: var(--text-xs); font-weight: 700;
  border: 1px solid currentColor; border-radius: 999px;
}
.badge--warning { color: var(--color-warn-text); background: var(--color-warn-bg); border-color: var(--color-warn-border); }
.badge--caution { color: var(--color-caution-text); background: var(--color-caution-bg); border-color: var(--color-caution-border); }
.badge--info { color: var(--color-info-text); background: var(--color-info-bg); border-color: var(--color-info-border); }

/* 页尾二维码块（D2 保留在页尾；在 v3 里它也是一张卡片，由 .detail-section 给） */
.qr-block { scroll-margin-top: calc(var(--header-h) + var(--space-4)); }
.qr-inner { display: flex; flex-direction: column; align-items: center; gap: var(--space-4); text-align: center; }
.qr-image {
  display: block; width: 180px; height: 180px; padding: var(--space-2);
  background: var(--color-surface-subtle); border: var(--border); border-radius: var(--radius-sm);
}
.qr-actions { max-width: var(--measure); }
.qr-actions p { margin: 0 0 var(--space-3); color: var(--color-ink-soft); }
@media (min-width: 640px) {
  .qr-inner { flex-direction: row; align-items: center; text-align: left; }
}

/* --- 7. 符合性声明页 ----------------------------------------------------- */
/* 该页没有 h2 分区，正文直接挂在 main 上；在淡蓝页面底上给它一张卡片，
   否则文字会「浮」在底色里，与详情页的观感断层。 */
.page-conformity > h1 { margin: 0 0 var(--space-4); font-size: var(--text-2xl); }
.conformity-card {
  padding: var(--space-5) var(--space-4);
  background: var(--color-surface); border: var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-card);
}
@media (min-width: 640px) { .conformity-card { padding: var(--space-8); } .page-conformity > h1 { font-size: var(--text-4xl); } }

.model-list { display: grid; gap: 0; margin: var(--space-2) 0 0; padding: 0; list-style: none; }
.model-list li + li { border-top: var(--border); }
/* 每行两个出口（型号详情 / 本型号 DoC）：左右分列，窄屏各自仍占满 44px 触控高 */
.model-list li { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-3); }
.model-list a {
  display: flex; align-items: center; min-height: 44px;
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
}
/* DoC 链接不是型号标识，不穿等宽字体 —— 等宽在本页是「型号」的视觉签名 */
.model-list a.model-doc-link { font-family: var(--font-sans); font-size: var(--text-sm); }
@media (min-width: 640px) { .model-list { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--space-6); } }

/* --- 8. 型号级符合性声明（DoC）页 ---------------------------------------- */
/* 客户 2026-09-21 第 7 条，参照 https://www.tmmq.top/doc.html?type=6lr61 。
   版面语言与详情页一致（同一套令牌 / 同一张 kv-table），只加 DoC 特有的三件：
   抬头卡（实心天蓝 = 全页唯一强焦点）、10 段两栏网格、签名卡。
   `.doc-card` 的卡片观感与 `.detail-section` **刻意保持同一份声明值** ——
   两页上一眼是同一个站点；改一处请同步另一处（无共享类，见本段注释）。 */
.doc-head {
  display: flex; flex-wrap: wrap; gap: var(--space-4);
  align-items: flex-start; justify-content: space-between;
  margin-bottom: var(--space-4); padding: var(--space-5);
  background: var(--color-primary); color: var(--color-on-primary);
  border-left: 4px solid var(--color-primary-bright);
  border-radius: var(--radius); box-shadow: var(--shadow-card);
}
/* 文字块与详情页概要卡的 `.hero-main` 同一 flex 基准（`1 1 12rem`）——
   两处抬头在窄屏都保持「左字右图」一行，不因内容长短各自动换行（客户 2026-09-29）。 */
.doc-head-main { flex: 1 1 12rem; min-width: 0; }
.doc-title { margin: 0; font-size: var(--text-xl); color: var(--color-on-primary); }
.doc-title-sub { display: block; margin-top: var(--space-1); font-size: var(--text-sm); font-weight: 500; color: var(--color-on-primary-soft); }
.doc-subhead { margin: var(--space-3) 0 0; font-size: var(--text-sm); color: var(--color-on-primary-soft); }
.doc-qr { text-align: center; }
.doc-qr img { display: block; width: 104px; height: 104px; padding: var(--space-1); background: #fff; border-radius: var(--radius-sm); }
.doc-qr span { display: block; margin-top: var(--space-1); font-size: var(--text-xs); color: var(--color-on-primary-soft); }
.doc-meta { margin: 0 0 var(--space-4); font-size: var(--text-sm); color: var(--color-ink-soft); }
.doc-meta strong { font-family: var(--font-mono); color: var(--color-ink); }
/* 文档编号 = Annex IX 标题里的 `No*`（2026-10-16 收敛到法定模板） */
.doc-number { margin: 0 0 var(--space-4); font-size: var(--text-sm); color: var(--color-ink-soft); }
.doc-number strong { font-family: var(--font-mono); color: var(--color-ink); }
/* Annex IX 的两句法定固定文案（第 3 点责任声明 / 第 7 点模块 A） */
.doc-sole-responsibility,
.doc-module-statement { margin: 0; font-size: var(--text-sm); color: var(--color-ink); }

.doc-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-5); }
.doc-card {
  padding: var(--space-5) var(--space-4) var(--space-6);
  background: var(--color-surface); border: var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-card);
}
.doc-company-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-4); }
.doc-company-card {
  min-width: 0; padding: var(--space-4);
  background: var(--color-surface-subtle); border: var(--border);
  border-radius: var(--radius-sm);
}
.doc-company-card > .group-heading:first-child { margin-top: 0; }
.doc-card > h2 {
  position: relative; margin: 0 0 var(--space-4); padding: 0 0 var(--space-3);
  font-size: var(--text-lg); border-bottom: 1px solid var(--color-border);
}
.doc-card > h2::after {
  content: ""; position: absolute; left: 0; bottom: -1px;
  width: 40px; height: 3px; border-radius: 0 3px 3px 0; background: var(--color-primary);
}
@media (min-width: 640px) {
  .doc-card { padding: var(--space-6); }
  .doc-title { font-size: var(--text-2xl); }
}
/* ≥900px 两栏：段 3/4（制造商 / 授权代表）是天然的并列对；其余段通栏。
   段 6 是四~五列表格，半栏会把编号与日期压成逐字换行（视觉稿实测），故也走整行。 */
@media (min-width: 900px) {
  .doc-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .doc-full { grid-column: 1 / -1; }
}
@media (min-width: 1280px) {
  .doc-company-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.doc-related-title { margin: var(--space-8) 0 var(--space-3); font-size: var(--text-lg); }
/* 「相关文档」链接行 —— DoC 页与详情页页尾**同一套** UI（客户 2026-09-30 第 3 条：
   详情页此前把这一行套在 `.detail-section` 卡片里，现已去掉卡片、与 DoC 页逐字一致）：
   白底描边 + 44px 触控高，一行可换行。 */
.related-links { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.related-links a {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 var(--space-4);
  background: var(--color-surface); border: var(--border); border-radius: var(--radius-sm);
  text-decoration: none; font-size: var(--text-sm);
}
.related-links a:hover { border-color: var(--color-border-strong); background: var(--color-neutral-faint); }
/* 链接行下原有一行说明（`.related-hint`，详情页「相关文档」块用）**已退役**
   —— 客户 2026-09-30 第 3 条要求「和 doc 页类似」，DoC 页的链接行没有说明行，
   2026-10-02 顺手清掉模板里的空 `<p>` 与本规则（模板没有就删死键/死样式）。 */

/* 签名卡（参照站的两栏签名区）：浅底卡片 + 一条签名线，留白即手签/盖章位 */
.sig-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-4); }
.sig-card { padding: var(--space-4); background: var(--color-surface-subtle); border: var(--border); border-radius: var(--radius-sm); }
/* 卡头（客户 2026-10-02 参照图）：「签署代表：」一行小字 + 下方加粗的当事方名称
   —— 名称是这一卡的「谁签」，比字段行高一级。 */
.sig-party { margin: 0 0 var(--space-3); }
.sig-party-label { display: block; font-size: var(--text-sm); color: var(--color-ink-soft); }
.sig-party-name { display: block; font-size: var(--text-base); color: var(--color-ink); }
.sig-card .kv-table th, .sig-card .kv-table td { padding: var(--space-2) 0; }
/* 签名线：短横线（参照图约卡片宽度的三成）+ 线下的小字标签，留白即手签位。 */
.sig-line {
  width: 11em; max-width: 60%; margin: var(--space-8) 0 0;
  padding-top: var(--space-2); border-top: 1px solid var(--color-ink-muted);
  font-size: var(--text-xs); color: var(--color-ink-muted);
}
@media (min-width: 640px) { .sig-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* 附件单元格（证书附件一列）：一列多文件，竖排链接 */
.link-list { margin: 0; padding: 0; list-style: none; }
.link-list li + li { margin-top: var(--space-1); }

/* 打印（客户实际用法：浏览器打印成 PDF 存档/发进口商）：去掉站点外壳与导航，
   卡片改描边防灰底吃墨，标题不与正文分页、表格行不跨页断开。 */
@media print {
  body { background: #fff; }
  /* 站点外壳与「相关文档」是屏幕上的导航，纸上无用；**抬头里的二维码保留** ——
     打印出来的声明同样要能被扫码回跳到型号页。 */
  .site-header, .site-footer, .skip-link, .doc-related-title, .related-links { display: none; }
  .container { padding-inline: 0; }
  .doc-head { border: 1px solid var(--color-border); background: #fff; color: var(--color-ink); box-shadow: none; }
  .doc-title, .doc-title-sub, .doc-subhead, .doc-qr span { color: var(--color-ink); }
  .doc-grid { display: block; }
  .doc-card { margin-bottom: var(--space-4); border: 1px solid var(--color-ink-muted); box-shadow: none; break-inside: avoid; }
  .doc-card > h2 { break-after: avoid; }
  .data-table tr, .kv-table tr, .sig-card { break-inside: avoid; }
  a { color: inherit; text-decoration: none; }
}

/* --- 9. 动效纪律（F14） -------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}
