/* 拍照识别 / OCR 面板 */
/* 视觉隐藏但保持可点击（避免 display:none 导致部分安卓 WebView 不唤起文件选择器） */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
.scan-drop {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 14px;
  background: #f4f8fb;
  border: 1px dashed #b9cbe0;
  border-radius: 10px;
  margin-bottom: 14px;
}
.scan-preview-wrap { margin-bottom: 14px; }
.scan-preview {
  max-width: 100%;
  max-height: 320px;
  border-radius: 10px;
  border: 1px solid #e2e8f0;
  display: block;
  margin-bottom: 10px;
  background: #fff;
}
/* 🆕 v284：允许换行 —— 第二行放阶段名/计时（.progress-sub），不挤压进度轨道 */
.progress { display: flex; align-items: center; gap: 10px; margin: 12px 0; flex-wrap: wrap; }
/* v168: 修复进度条宽度不随进度变化——旧 .progress-bar 用 flex:1 会始终占满容器、
   style.width 被 flex-grow 覆盖（看上去永远满格）。改为外层 .progress-track 承担 flex:1
   固定轨道，内层 .progress-bar 的 width 按进度真实生效。 */
.progress-track {
  flex: 1;
  min-width: 0;
  height: 10px;
  background: #e8eef3;
  border-radius: 6px;
  overflow: hidden;
  position: relative;
}
/* 🆕 v284：进度条改为 `transform: scaleX()` 驱动。
   原因（实测坐实）：PaddleOCR 的 rec 在主线程同步跑 wasm（ORT.env.wasm.numThreads=1），
   主线程会被完全阻塞十几秒 —— 实测 13s 内 setInterval 只跑了 4 次，
   任何 JS 驱动的进度更新（rAF / setInterval / 改 width）在那期间都执行不到，
   这就是「进度条停在百分之一二十、然后突然跳到 100%」的真正原因。
   transform 动画由**合成器线程**驱动，主线程被卡住时仍然继续推进；
   配合 JS 在进入每个阶段时下达一次「长 transition」，条就能全程连续爬行。 */
.progress-bar {
  height: 100%;
  width: 100%;
  transform: scaleX(0);
  transform-origin: left center;
  will-change: transform;              /* 提升为合成层，确保动画走合成器线程 */
  background: linear-gradient(90deg, #0e7c86, #27ae60, #43c96b);
  background-size: 200% 100%;
  border-radius: 6px;
  transition: transform .3s linear;    /* duration 由 JS 按阶段预估耗时动态改写 */
  /* 流动光泽动画：进度条有变化感，进度前进时更醒目 */
  animation: cm-bar-sheen 2.5s linear infinite;
}
@keyframes cm-bar-sheen {
  0% { background-position: 0% 0; }
  100% { background-position: 200% 0; }
}
.progress-text { font-size: 12px; color: #6b7a8d; white-space: nowrap; }
/* 🆕 v284：第二行细分信息（当前阶段 + 本张已用时 + 总张数） */
.progress-sub { flex: 1 1 100%; font-size: 11.5px; color: #8a99a8; line-height: 1.4; }
html[data-theme="dark"] .progress-sub { color: #7f91a3; }
@media (max-width: 640px) {
  .progress-sub { font-size: 11px; }
}

.scan-result { margin-top: 8px; overflow-x: auto; }
.scan-table { width: 100%; border-collapse: collapse; font-size: 13px; min-width: 680px; }
.scan-table th, .scan-table td { padding: 6px 8px; border-bottom: 1px solid #eef2f6; text-align: left; vertical-align: middle; word-break: break-all; }
/* 🆕 v445：名称列（含「原识别」超长 OCR 串）限宽 + 断行 —— 否则超长名会把整行顶出页面宽度。
   对齐 .raw 列「max-width + break-all」的既有做法；正常中文名（<320px）不受影响。 */
.scan-table td:nth-child(2) { max-width: 320px; }
.scan-table th { background: #f4f8fb; color: #41566b; font-weight: 600; position: sticky; top: 0; }
.scan-table input[type=number] { width: 74px; }
/* 🆕 v521（需求5b）：参考范围编辑区宽度自适应 —— 上限/下限输入框随内容自动伸缩，
   不再固定 74px 或窄到截断「115 / 150」这类三位数；field-sizing 需 Chromium 123+，旧内核回退 min/max 兜底。 */
.entry-result .scan-table input.scan-low,
.entry-result .scan-table input.scan-high {
  width: auto; min-width: 40px; max-width: 130px; field-sizing: content;
}
.scan-table select { min-width: 150px; max-width: 220px; }
.scan-table .raw { color: #8593a3; font-size: 11px; max-width: 220px; word-break: break-all; }
.scan-table .seq-td { text-align: center; font-weight: 700; color: #27ae60; font-size: 13px; min-width: 32px; }
.scan-actions { display: flex; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
.scan-raw { margin-top: 10px; }
.scan-raw pre {
  white-space: pre-wrap;
  word-break: break-all;
  background: #f7fafc;
  border: 1px solid #eef2f6;
  border-radius: 8px;
  padding: 10px;
  max-height: 220px;
  overflow: auto;
  font-size: 12px;
  color: #41566b;
}

/* 引擎选择 */
.engine-sel { font-size: 13px; color: #41566b; display: inline-flex; align-items: center; gap: 6px; }
.engine-sel select { padding: 6px 8px; border-radius: 8px; border: 1px solid #cdd9e6; background: #fff; }

/* 识别汇总 */
.scan-summary { font-size: 13px; margin: 10px 0 6px; color: #41566b; }
.scan-summary .ok { color: #1f9d55; font-weight: 600; }
.scan-summary .warn { color: #d98324; font-weight: 600; }
.scan-summary .unk { color: #8a97a6; font-weight: 600; }
.scan-summary .abn { color: #c0392b; font-weight: 700; }
.scan-summary .rev { color: #c0392b; font-weight: 700; }

/* 校验徽章 */
.badge { display: inline-block; font-size: 11px; padding: 2px 7px; border-radius: 10px; font-weight: 600; margin-top: 4px; }
.badge.ok { background: #e6f7ee; color: #1f9d55; }
.badge.warn { background: #fdf0e0; color: #c9761a; }
.badge.unk { background: #eef1f5; color: #8a97a6; }
.badge.rev { background: #fde8e8; color: #c0392b; }
.orig { color: #9aa6b3; font-size: 11px; }

/* 文档识别（报告头/页脚/签名等非指标文本） */
.doc-block { border: 1px dashed #c3d2e0; background: #f7fafd; border-radius: 8px; padding: 10px 12px; margin: 10px 0; }
.doc-title { font-size: 12px; font-weight: 700; color: #5a6b7d; margin-bottom: 6px; }
.doc-meta { display: flex; flex-wrap: wrap; gap: 6px 10px; margin-bottom: 6px; }
.doc-chip { font-size: 12px; background: #eef4fa; border-radius: 6px; padding: 2px 8px; color: #35506b; }
.doc-chip b { color: #6a7d90; font-weight: 600; }
.doc-lines { font-size: 12px; color: #5a6b7d; line-height: 1.7; max-height: 180px; overflow: auto; white-space: pre-wrap; word-break: break-all; }
.doc-line { padding: 1px 0; border-bottom: 1px dotted #e3ebf2; }
.doc-star { color: #c0392b; font-weight: 700; }

/* 偏高/偏低/正常标记 */
.flag { font-size: 12px; font-weight: 600; }
.flag.high { color: #c0392b; }
.flag.low { color: #2f6fb0; }
.flag.ok { color: #1f9d55; }
.flag { color: #9aa6b3; }

/* 英文指标名标签 */
.en-label { display: inline-block; font-size: 11px; color: #0e7c86; font-weight: 700; background: #e8f5f6; border-radius: 4px; padding: 1px 6px; margin-left: 4px; vertical-align: middle; }

/* 原始 OCR 文本逐行打印（调试用） */
.raw-ocr-block { border: 1px solid #d5e2ec; background: #fafcfD; border-radius: 8px; margin: 10px 0; overflow: hidden; }
.raw-ocr-title { padding: 8px 12px; cursor: pointer; font-size: 13px; font-weight: 700; color: #35506b; background: #f0f4f8; user-select: none; }
.raw-ocr-title:hover { background: #e8eef4; }
.raw-ocr-lines { max-height: 300px; overflow-y: auto; padding: 4px 0; }
/* 🆕 v242：原始 OCR 紧凑文本块——按文本自然顺序 + 正常换行展示，不再逐行拆 flex 行 */
.raw-ocr-text { margin: 0; padding: 6px 12px; font-size: 12px; line-height: 1.55; white-space: pre-wrap; word-break: break-all; font-family: "SF Mono", "Consolas", "Liberation Mono", Menlo, monospace; color: #334155; max-height: 320px; overflow-y: auto; }
.raw-ocr-line { display: flex; align-items: flex-start; padding: 2px 12px; font-size: 12px; line-height: 1.7; border-bottom: 1px dotted #eef2f6; }
.raw-ocr-line:hover { background: #f0f7fa; }
.raw-line-no { display: inline-block; min-width: 32px; text-align: right; color: #9aa6b3; font-weight: 600; font-size: 11px; padding-right: 8px; user-select: none; flex-shrink: 0; }
.raw-line-en { display: inline-block; min-width: 56px; text-align: left; color: #0d7a68; font-weight: 700; font-size: 11px; padding: 0 6px; background: #e6f5f1; border: 1px solid #bce2d7; border-radius: 3px; margin-right: 8px; flex-shrink: 0; font-family: "SF Mono", "Consolas", monospace; }
.raw-line-txt { color: #334155; word-break: break-all; font-family: "SF Mono", "Consolas", "Liberation Mono", Menlo, monospace; }
.raw-line-suffix { margin-left: 10px; flex-shrink: 0; white-space: nowrap; }
.raw-line-unit { display: inline-block; color: #35506b; font-weight: 600; font-size: 11px; padding: 0 6px; background: #eef2f6; border: 1px solid #d5dde5; border-radius: 3px; margin-right: 6px; font-family: "SF Mono", "Consolas", monospace; }
.raw-line-range { display: inline-block; color: #8a5a00; font-weight: 600; font-size: 11px; padding: 0 6px; background: #fdf3dc; border: 1px solid #f0e0b0; border-radius: 3px; margin-right: 6px; font-family: "SF Mono", "Consolas", monospace; }
.raw-ocr-mapped { margin-left: 8px; font-size: 11px; font-weight: 600; white-space: nowrap; flex-shrink: 0; }
.raw-ocr-mapped.ok { color: #0d7a68; }
.raw-ocr-mapped.warn { color: #b45309; }
.raw-ocr-mapped.unk { color: #64748b; }
.raw-map-tag { display: inline-block; margin-top: 2px; color: #0d7a68; font-weight: 600; font-size: 11px; }

/* 版本/构建时间戳（让用户一眼确认是不是最新代码） */
.scan-build-info { padding: 6px 12px; font-size: 12px; color: #4a6c8a; background: #eef6fc; border-bottom: 1px solid #d5e2ec; font-family: "SF Mono", "Consolas", "Liberation Mono", Menlo, monospace; }

/* 识别历史对比区 */
.history-block { margin-top: 8px; background: #fffefb; border-color: #e9dcc0; }
.history-block > .raw-ocr-title { background: #f7f2e6; color: #6b5637; }
.history-list { max-height: 360px; overflow-y: auto; padding: 6px 0; }
.history-item { border: 1px solid #ede4d3; border-radius: 6px; margin: 6px 12px; background: #fff; }
.history-summary { padding: 6px 10px; cursor: pointer; font-size: 12px; color: #5c4b2f; user-select: none; }
.history-summary:hover { background: #faf6ed; }

/* ===== 历史检验结果（多图归档） ===== */
.scan-thumbs {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  padding: 4px 2px 10px;
  margin-bottom: 4px;
  /* 🆕 v477（用户）：缩略图条横向滑动找图时，页面不要跟着纵滚。
     touch-action: pan-x —— 该容器只响应横向手势，纵向手势直接交给页面滚动；
     overscroll-behavior-x: contain —— 横向滚到头后不再「链」到页面，避免页面被带着滚走。 */
  touch-action: pan-x;
  overscroll-behavior-x: contain;
}
.scan-thumb {
  position: relative;
  flex: 0 0 auto;
  width: 132px;
  background: #fff;
  border: 1px solid #dbe4ee;
  border-radius: 10px;
  padding: 6px;
  cursor: pointer;
  text-align: center;
  transition: border-color .15s, box-shadow .15s, transform .15s;
}
.scan-thumb:hover { border-color: #5b8def; box-shadow: 0 2px 8px rgba(91,141,239,.18); transform: translateY(-1px); }
.scan-thumb.active { border-color: #0e7c86; box-shadow: 0 0 0 2px rgba(14,124,134,.35); background: #eef9fa; }
html[data-theme="dark"] .scan-thumb.active { border-color: #2bb6c4; background: #142028; box-shadow: 0 0 0 2px rgba(43,182,196,.35); }
.scan-thumb img { width: 100%; height: 92px; object-fit: cover; border-radius: 6px; background: #f0f4f8; display: block; }
/* 🆕 v207：缩略图被配额兜底剥离后的占位块（指标与检验记录仍完整保留） */
.scan-thumb-nofp { width: 100%; height: 92px; border-radius: 6px; display: flex; align-items: center; justify-content: center;
  background: repeating-linear-gradient(45deg, #f0f4f8, #f0f4f8 8px, #e6ecf3 8px, #e6ecf3 16px);
  color: #7b8ca0; font-size: 12px; letter-spacing: .5px; }
html[data-theme="dark"] .scan-thumb-nofp { background: repeating-linear-gradient(45deg, #1b2530, #1b2530 8px, #212d3a 8px, #212d3a 16px); color: #8fa3b8; }
.scan-thumb-cap { display: block; margin-top: 5px; font-size: 11px; color: #41566b; line-height: 1.3; word-break: break-all; }
.scan-thumb-cap b { color: #c0392b; }
/* 缩略图上的删除按钮 */
.scan-thumb-del {
  position: absolute; top: 4px; right: 4px;
  width: 20px; height: 20px; line-height: 1;
  padding: 0; border: none; border-radius: 50%;
  background: rgba(192,57,43,.92); color: #fff;
  font-size: 15px; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.scan-thumb-del:hover { background: #a93226; }
html[data-theme="dark"] .scan-thumb-del { background: rgba(222,82,72,.95); }
html[data-theme="dark"] .scan-thumb-del:hover { background: #e0564a; }
/* 🆕 v462（用户）：缩略图右上角「正确识别数量/总指标数量」比值徽标，位于 ❌ 左侧。
   🆕 v500（用户第 1 项）：红底白字 → 透明底（只留文字）。
   🆕 v513（用户第 2 项）：口径改回并收紧 ——
     · warn（识别数 ≠ 总数 / 有待核对 / 同图同名值冲突）⇒ **红底白字**，写「已识别/总数」；
     · ok（识别数 === 总数）⇒ **白底黑字的圆圈钮，只显示指标总数**。
   数字用 tabular-nums，避免「1/3 → 10/13」时宽度跳动。 */
.scan-thumb-ratio {
  position: absolute; top: 4px; right: 28px; z-index: 3;
  box-sizing: border-box;
  min-width: 20px; height: 20px; line-height: 20px; padding: 0 5px;
  border-radius: 999px; text-align: center;
  font-size: 10px; font-weight: 700; font-variant-numeric: tabular-nums;
  color: #fff; background: rgba(192,57,43,.92);
  white-space: nowrap; pointer-events: none;
}
.scan-thumb-ratio.ok { background: #fff; color: #1f2b36; box-shadow: 0 1px 3px rgba(0,0,0,.28); }
html[data-theme="dark"] .scan-thumb-ratio { background: rgba(222,82,72,.95); }
html[data-theme="dark"] .scan-thumb-ratio.ok { background: #eef2f6; color: #16202a; }
/* 缩略图左上角勾选框（勾选后随顶部「导入选中」一并导入此图） */
.scan-thumb-checkwrap {
  position: absolute; top: 4px; left: 4px; z-index: 2;
  width: 22px; height: 22px; margin: 0; cursor: pointer;
}
.scan-thumb-check { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.scan-thumb-checkmark {
  position: absolute; inset: 0;
  width: 20px; height: 20px; border-radius: 50%;
  background: #0e7c86; color: #fff; font-size: 13px; line-height: 20px; text-align: center;
  box-shadow: 0 1px 3px rgba(0,0,0,.3); pointer-events: none;
}
.scan-thumb-checkmark.off {
  background: rgba(255,255,255,.82); color: transparent;
  border: 1px solid #9fb0c0; box-shadow: none;
}
html[data-theme="dark"] .scan-thumb-checkmark.off { background: rgba(35,46,57,.85); border-color: #41566b; }
/* 缩略图自动判定徽标：新增/更新=青色（会自动勾选）；已存在=灰色（不勾选）
   🆕 v500（用户第 1 项）：徽标原来水平居中，会被右上角的「比值数字 + ❌」压住 ⇒ 左移到左上角
   （左上勾选框当前未渲染，位置空闲），与右侧的比值 / 删除键彻底分开、不再重叠。 */
.scan-thumb-badge {
  position: absolute; top: 4px; left: 4px; z-index: 3;
  padding: 1px 7px; border-radius: 10px; font-size: 10px; font-weight: 600; line-height: 1.5;
  color: #fff; pointer-events: none; white-space: nowrap;
}
.scan-thumb-badge.fresh { background: #0e7c86; }
.scan-thumb-badge.dup { background: #9aa7b4; }
html[data-theme="dark"] .scan-thumb-badge.fresh { background: #2bb6c4; }
html[data-theme="dark"] .scan-thumb-badge.dup { background: #5a6675; }

.scan-entries { display: flex; flex-direction: column; gap: 18px; margin-top: 8px; }
/* 🆕 v357：`.scan-entry` 由「一张大卡片」改为**纯容器**，内部分成两张独立卡片：
     ① `.scan-card` × 1 = 「照片识别结果」（标题 + 大图）
     ② `.scan-card` × 1 = 「指标识别结果」（标题栏 + 指标表）
   用户原话：「图片识别的大窗口下，照片识别结果和指标识别结果分成2个窗口，不用合并在一个子窗口中了。」
   ⚠️ 之前是 `.scan-entry` 自己带 border/padding/background → 整块看起来是**一个**窗口，
      指标那半只是「套在里面的子卡片」。所以外包一层卡片是不够的，必须把外框**移走**。
   ⚠️ `.flash` 用 box-shadow 做高亮，不依赖 border ⇒ 外框移走后跳转定位高亮仍然有效。 */
.scan-entry {
  display: flex; flex-direction: column; gap: 12px;
  scroll-margin: 80px;
}
/* 两张卡片的公共外观（原来这套外观长在 .scan-entry 上） */
.scan-card {
  border: 1px solid #dbe4ee;
  border-radius: 12px;
  padding: 12px;
  background: #fbfdff;
}
html[data-theme="dark"] .scan-card { background: #1c242c; border-color: #33414f; }
/* 卡片内「指标识别结果」标题栏：不再是自带圆角外框的独立条，改为卡片的**内嵌标题行** */
.scan-card .entry-result-bar {
  padding: 0 0 10px; margin-top: 0;
  background: transparent; border: none; border-bottom: 1px solid var(--line); border-radius: 0;
}
.scan-card .entry-result-bar + .entry-result { border-top: none; border-radius: 0; }
/* 指标区已在卡片内 ⇒ 去掉自身外框，避免「卡片套卡片」的双层边框（手机端 v221 给它加过 border） */
.scan-card .entry-result { border: none; border-radius: 0; background: transparent; }
.scan-entry.flash { animation: scanFlash 1.1s ease; }
@keyframes scanFlash {
  0% { box-shadow: 0 0 0 0 rgba(91,141,239,.55); border-color: #5b8def; }
  100% { box-shadow: 0 0 0 10px rgba(91,141,239,0); border-color: #dbe4ee; }
}
.scan-entry-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; flex-wrap: wrap; }
.scan-entry-title { font-size: 13px; font-weight: 600; color: #41566b; margin-right: 2px; }
/* v268：可编辑日期控件已移除，其样式规则随之删除 */
/* v328：「删除本次」按钮已移除（浅色卡片上 .btn.ghost 白字白底看不见；删除统一走缩略图 ❌），其样式规则随之删除 */

/* 🆕 v269：「已识别 X / Y 项」计数徽标（Y 按抬头以下结构数出） */
.entry-result-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 10px 12px; background: var(--panel); border: 1px solid var(--line); border-bottom: none; border-radius: 12px 12px 0 0; margin-top: 10px; }
.entry-result-bar + .entry-result { border-top: none; border-radius: 0 0 12px 12px; }
.entry-result-title { flex: 0 1 auto; font-size: 14px; font-weight: 700; color: var(--ink); }
.entry-count-badge { flex: 0 0 auto; margin-left: auto; font-size: 12px; font-weight: 600; color: #2b6cb0; background: #e8f1fb; border: 1px solid #c5dcf5; border-radius: 999px; padding: 2px 10px; }
.entry-result-bar .raw-ocr-q,
.entry-result-bar .raw-ocr-help { flex: 0 0 auto; }
html[data-theme="dark"] .entry-count-badge { color: #8ec2ff; background: #14202e; border-color: #24425f; }

/* ===== 🆕 v355：标题栏右侧工具组（📄 → ＋ → ?），用户 2026-09-17 要求 =====
   ① 顺序：原始图片文本(📄) 在「＋」左边，「＋」(补录缺失指标) 在问号左边；
      「补录缺失指标」去掉文字只留一个加号，点「＋」才下拉出带文字的菜单项。
   ② ⚠️ **本栏内所有图标按钮一律不得用 `.btn.ghost`**：`.btn.ghost{color:#fff}` 是给**深色顶栏**
      设计的，而本栏底色是浅色 `var(--panel)` ⇒ 白字打在白底上 = **隐形**。
      v315 起那个「右上角问号」就是这样「存在但看不见」的（用户 2026-09-17 的截图里只有 📄 没有 ?，
      因为 📄 是 emoji 自带颜色、躲过了 color:#fff）。这是**真缺陷**，随本轮一并修掉。
      实测取证见 _e2e_v355_scan_ui.js 的 C2（对比度 ≥ 3.0）。 */
/* 🆕 v434（用户）：工具组（📄 ＋ ↻ 🌰）靠窗口右上角 —— 标题居左、工具组贴右
   🆕 v513（用户第 4 项）：组内最后一枚由问号 ? 换成 🌰 栗子（自绘 SVG），顺序不变其余。 */
.entry-bar-tools { display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto; margin-left: auto; }
/* 🆕 v357：本栏三个图标按钮（📄 / ＋ / ?）统一 26×26 圆形 —— 用户要求「软件中的问号都统一大小」，
   于是问号必须与卡片角落的 `.hint-q`（同 26×26）一致；为让同一组按钮不出现「一个大一个小」，
   📄 与 ＋ 一并改为同尺寸圆形（原来 34×30 圆角矩形）。
   ⚠️ 尺寸必须写在 **本文件** 里而不是 styles.css：本文件在 index.html 中于 styles.css **之后**加载，
      同为 (0,2,0) 特异性时靠后者胜 ⇒ 写在 styles.css 会被这条压掉。 */
.entry-bar-tools .entry-ico {
  box-sizing: border-box;
  /* 🆕 v548（用户九问 ①）：「指标识别结果图标偏了，没有在圆形图标中间」——
     本按钮此前只靠浏览器对 button 文本的默认居中，而内容里有 **display:block 的 SVG**
     （🌰 的 .nut-ico / ↻ 的内联 svg）。块级子元素不吃 text-align 居中，
     在 26×26 圆钮里会贴左（偏左约 5px）⇒ 用户看到「图标不在圆中间」。
     对齐全站 .act-ico 的口径（styles.css）：inline-flex 双轴居中，文本字形（＋/📄）同样受益。 */
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; min-width: 26px; min-height: 26px; padding: 0;
  font-size: 15px; line-height: 1; font-weight: 700;
  border-radius: 50%;
  color: var(--primary); background: #fff; border: 1px solid var(--line);
}
/* 🆕 v513（用户第 4 项）：本枚按钮的内容由「?」字形换成 `NUT_SVG`（自绘圆头板栗）。
   板栗 SVG 自带 width/height=16，收到 15px 与 📄 emoji / ↻ 的视觉高度对齐；
   display:block 去掉行内基线的下沉，避免在 26×26 圆钮里偏下 —— 按钮自身仍负责居中。 */
.entry-bar-tools .entry-ico.raw-ocr-help .nut-ico { width: 15px; height: 15px; display: block; }
/* 🆕 v545（用户第 1 条）：重新识别按钮 `↻` 换成内联 SVG 后，同样收进 26×26 圆钮居中 ——
   与 `.raw-ocr-help .nut-ico` 同源（15px + display:block 去行内基线下沉）。 */
.entry-bar-tools .entry-ico.scan-reparse svg { width: 15px; height: 15px; display: block; }
.entry-bar-tools .entry-ico:hover { border-color: var(--primary); }
html[data-theme="dark"] .entry-bar-tools .entry-ico { color: #7fdde4; background: #22303c; border-color: #3a4a5c; }
/* 「＋」旁的下拉菜单
   定位：`right:0` —— 菜单比「＋」按钮宽得多（127px vs 30px），必须让**右缘对齐「＋」**、向左铺开。
   ⚠️ 第一版写 `right:0` 却仍然溢出视口左缘（实测 left=-8.1），根因不在这里而是
      「工具组当时是左对齐的」（x=89）⇒ 右缘对齐等于贴着左边走。
      现在工具组已用 margin-left:auto 钉在右端（见 640px 断点内），`right:0` 才真正落在视口内。 */
.scan-add-wrap { position: relative; display: inline-flex; }
.scan-add-menu {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 40;
  /* 🆕 v513（UISKILL 一致性）：圆角 10 → 12，与同族的 #ind-filter-menu / #rec-filter-menu /
     #med-filter-menu（均 12）对齐 —— 同为「浮层表面」不该有第二种圆角。 */
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
  padding: 6px; box-shadow: 0 8px 20px rgba(15, 30, 45, .18); white-space: nowrap;
  max-width: calc(100vw - 20px);
}
/* ⚠️ 显式压回 [hidden]（v269 起本项目反复踩过：作者 CSS 的 display 会盖掉 UA 的 [hidden]） */
.scan-add-menu[hidden] { display: none; }
/* 🆕 v513（用户第 7 项 · UISKILL 一致性检查）：菜单项命中高度 34 → 40px。
   依据 UISKILL「Hit areas are physical — at least 40x40px, ideally 44x44px」。
   ⚠️ 这里**只能调高度、不能用伪元素扩命中区**：`.scan-add-menu` 的项是**纵向堆叠**的，
      伪元素外扩会与上下相邻项的命中区重叠（UISKILL 明确禁止 overlap）⇒ 必须靠真实高度。
   ⚠️ 本规则必须写在 css/scan.css：index.html 里 scan.css 在 styles.css **之后**加载，
      同特异度下后者胜 —— 放 styles.css 会被本文件覆盖（见 index.html 43/44 行的 link 顺序）。 */
.scan-add-menu .btn { min-height: 40px; font-size: 12.5px; }
/* 🆕 v513（用户第 3 项）：指标库 ＋ 的菜单从 1 项变 2 项 ⇒ 必须**纵向**排。
   横向会撑宽菜单、两枚按钮也没有分隔；纵向与 `.scan-add-menu` 的 padding(6px) 同心：
   菜单圆角 12 = 按钮圆角 8 + 内边距 6 - 2（UISKILL 的 outer = inner + padding，含边框视觉修正）。 */
.scan-add-menu.ind-add-menu { display: flex; flex-direction: column; gap: 4px; }
html[data-theme="dark"] .scan-add-menu { background: #22303c; border-color: #3a4a5c; box-shadow: 0 8px 20px rgba(0, 0, 0, .45); }

/* 🆕 v391（用户第 2 项）：图片识别卡「🔍」的「按时间排列的文件名」下拉。
   本体直接复用上面 `.scan-add-menu`（绝对定位 right:0 + 主题配色），这里只补两条：
   ① 可滚动（导入几十张时不能撑满整屏）；② 每项独占一行、文件名过长省略号。 */
.scan-find-menu { max-height: min(52vh, 320px); overflow-y: auto; padding: 4px; }
.scan-find-menu .scan-find-item {
  display: block; width: 100%; text-align: left;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.scan-find-menu .scan-find-item.is-cur { border-color: var(--primary); color: var(--primary); }
/* ⚠️ .scan-find-item 自带 display:block（特异度高于 UA 的 [hidden] 规则）
   ⇒ 搜索过滤靠 hidden 属性切换显隐时**必须**显式补这条，否则过滤失效。 */
.scan-find-menu .scan-find-item[hidden] { display: none; }
.scan-find-empty { padding: 8px 10px; font-size: 12.5px; color: var(--muted); white-space: nowrap; }
/* 🆕 v556（用户）：下拉顶部搜索框 —— 按「存储名称」（日期 + 分类名 + 原文件名）过滤列表。 */
.scan-find-menu .scan-find-search {
  display: block; width: calc(100% - 8px); margin: 2px 4px 6px;
  padding: 6px 9px; font-size: 12.5px; line-height: 1.5;
  border: 1px solid var(--line, #d7dee6); border-radius: 8px;
  background: var(--panel, #f1f4f8); color: var(--ink, #222);
  outline: none; min-width: 0;
}
.scan-find-menu .scan-find-search:focus { border-color: var(--primary, #0e7c86); }
.scan-find-nomatch { padding: 8px 10px; font-size: 12.5px; color: var(--muted); white-space: nowrap; }

/* ===== 🆕 v355：只读态「合并文本」与编辑窗口字段标签 =====
   ⚠️ 这三类元素**只服务于手机卡片布局**（见 @media(max-width:640px) 内的同名规则）；
      桌面端是真正的八列表格、每列有自己的表头，合并文本与字段标签在那里都是多余的。
      ⇒ 默认 display:none，只在 640px 断点内打开。 */
.scan-vu-ro, .scan-range-ro, .scan-range-lb, .scan-field-lb { display: none; }

/* 🆕 v315：指标行名称列「只读 / 编辑」切换 —— 非编辑态只读，编辑态（tr.editing）显示输入框 */
.scan-name-edit { display: none; flex-wrap: wrap; gap: 4px; }
.scan-table tr.editing .scan-name-readonly { display: none; }
.scan-table tr.editing .scan-name-edit { display: flex; }
.scan-table .scan-name-edit .scan-name { flex: 1 1 100%; min-width: 0; }
.scan-table .scan-name-edit .scan-en { flex: 1 1 100%; min-width: 0; }

/* 🆕 v269：应用内相册多选弹层（Android）——点按即勾选圆孔、可滑动、顶部全选/导入 */
body.gal-lock { overflow: hidden; }
.gal-modal[hidden] { display: none; } /* display:flex 会覆盖 hidden 的默认 none，必须显式压回（v269 实测坑：未加时整屏拦截点击，e2e 全挂） */
.gal-modal {
  position: fixed; inset: 0; z-index: 999;
  background: #f2f5f9; display: flex; flex-direction: column;
}
html[data-theme="dark"] .gal-modal { background: #10161d; }
.gal-head {
  display: flex; align-items: center; gap: 6px; flex-wrap: nowrap;
  padding: 10px 12px; background: #fff; border-bottom: 1px solid #e2e8f0;
  position: sticky; top: 0; z-index: 2;
}
html[data-theme="dark"] .gal-head { background: #18212b; border-color: #2a3644; }
.gal-head .btn { flex: none; white-space: nowrap; margin: 0; min-width: 34px; padding-left: 8px; padding-right: 8px; }
/* ⚠️ .btn.ghost 是白字（给深色顶栏用的），在浅色 gal-head 上会隐形（v269 实测：✕/全选 在但看不见）→ 这里覆盖成本地配色 */
.gal-head .btn.ghost { background: #eef3f8; color: #2c3e50; border-color: #c8d5e2; }
.gal-head .btn.ghost:active { background: #dde7f1; }
html[data-theme="dark"] .gal-head .btn.ghost { background: #243140; color: #dce7f2; border-color: #3a4a5c; }
.gal-title { font-size: 15px; font-weight: 700; color: #2c3e50; white-space: nowrap; }
html[data-theme="dark"] .gal-title { color: #dce7f2; }
.gal-count { font-size: 12.5px; color: #5b7186; margin-right: auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
html[data-theme="dark"] .gal-count { color: #9fb2c4; }
.gal-tip { padding: 8px 12px; font-size: 12.5px; color: #7a8a99; }
html[data-theme="dark"] .gal-tip { color: #8fa1b3; }
.gal-grid {
  flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; padding: 4px 8px 24px;
}
/* ⚠️ 不用 aspect-ratio（雷电老 WebView 不支持 → 格子塌缩成一团，v269 实测）；
   用 padding-bottom:100% 撑正方形，子元素绝对定位铺满。 */
.gal-item {
  position: relative; height: 0; padding-bottom: 100%; border-radius: 8px; overflow: hidden;
  background: #e6ebf1; cursor: pointer; -webkit-tap-highlight-color: transparent; box-sizing: border-box;
}
html[data-theme="dark"] .gal-item { background: #1c2630; }
.gal-item img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.gal-item.sel img { opacity: .82; }
.gal-item.sel { outline: 3px solid #2b7ad9; outline-offset: -3px; }
/* 可勾选圆孔：默认空心圆，选中填充蓝色打勾 */
.gal-check {
  position: absolute; top: 6px; right: 6px; width: 24px; height: 24px;
  border-radius: 50%; border: 2px solid #fff; background: rgba(0,0,0,.18);
  color: #fff; font-size: 15px; font-weight: 700; line-height: 20px; text-align: center;
  box-shadow: 0 1px 3px rgba(0,0,0,.35); z-index: 1;
}
.gal-item.sel .gal-check { background: #2b7ad9; border-color: #fff; }
.gal-date {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 2px 6px;
  font-size: 11px; color: #fff; background: linear-gradient(transparent, rgba(0,0,0,.55));
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; z-index: 1;
}
.entry-img-wrap {
  width: 100%; height: 420px; max-height: 60vh;
  overflow: auto;
  border: 1px solid #e2e8f0; border-radius: 8px;
  background: #f5f7fa;
  position: relative;
  margin-bottom: 10px;
  cursor: grab;
  -webkit-overflow-scrolling: touch;
}
.entry-img-wrap .entry-img {
  display: block;
  width: 100%; height: auto;
  border: none; border-radius: 0;
  background: #fff;
  transform-origin: top left;
  transition: width .2s ease;
  user-select: none; -webkit-user-drag: none;
}
/* 手机尺寸图片按“手机比例”显示（不顶满窗口），并居中 */
.entry-img-wrap.is-phone { text-align: center; }
.entry-img-wrap.is-phone .entry-img { display: inline-block; margin: 0 auto; }
.entry-img-wrap.is-shot .entry-img { max-width: 384px; }
/* 🆕 v453：手机截图分屏 tiles —— 仅大屏版（≥641px）启用；
   手机版（≤640px）.shot-tiles 恒隐藏、回退 .entry-img 直显（原有 384px 限宽 + 窗口内滚动）。
   🆕 v521（需求16）：has-tiles 态**恢复固定高度 + 内滚**（与指标识别结果窗口等高）——
   原 v453 的「自动高」会把分屏块全部展开、窗口随内容撑到极高，页面往下滑看不到窗口以下内容。 */
.shot-tiles { display: none; }
@media (min-width: 641px) {
  .entry-img-wrap.has-tiles { overflow: auto; cursor: default; }
  .entry-img-wrap.has-tiles .entry-img { display: none; }
  .entry-img-wrap.has-tiles .entry-img-hint { display: none; }
  .entry-img-wrap.has-tiles .shot-tiles { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-start; }
}
.shot-tile { flex: 0 0 auto; background-repeat: no-repeat; border: 1px solid #e2e8f0; border-radius: 4px; background-origin: padding-box; }
html[data-theme="dark"] .shot-tile { border-color: #33414f; }
/* 鼠标按压拖拽平移时的光标 */
.entry-img-wrap.dragging { cursor: grabbing; user-select: none; }
.entry-img-hint {
  position: absolute; bottom: 8px; right: 8px;
  background: rgba(0,0,0,.55); color: #fff;
  font-size: 11px; padding: 3px 8px; border-radius: 4px;
  pointer-events: none; user-select: none;
  transition: opacity .2s ease;
}
.entry-img-wrap:hover .entry-img-hint { opacity: 1; }
.entry-img-wrap:active .entry-img-hint { opacity: 0; }
html[data-theme="dark"] .entry-img-wrap { background: #1c242c; border-color: #33414f; }
html[data-theme="dark"] .entry-img-wrap .entry-img { background: #2a333d; }
.entry-result { overflow-x: auto; }
.entry-actions { margin-top: 12px; display: flex; gap: 10px; flex-wrap: wrap; }

/* 暗色主题 */
html[data-theme="dark"] .scan-thumb { background: #1f2730; border-color: #33414f; }
html[data-theme="dark"] .scan-thumb img { background: #2a333d; }
html[data-theme="dark"] .scan-thumb-cap { color: #c2ccd6; }
/* 🆕 v401：数值突变 / 疑似小数点丢失提醒条（.scan-outlier）
   —— 「指标识别结果」窗口顶部，统计行之后。默认琥珀色（一般突变）；
      `is-dot` 变红（疑似小数点丢失，最该核对）。纯只读提示，不写任何数据。 */
.scan-outlier { margin: 10px 0 0; padding: 10px 12px; border-radius: 10px;
  background: #fff7e6; border: 1px solid #f0c36e; }
.scan-outlier.is-dot { background: #fdecea; border-color: #f3c3bd; }
.scan-outlier-h { font-size: 13px; font-weight: 700; color: #8a5a00; margin-bottom: 4px; }
.scan-outlier.is-dot .scan-outlier-h { color: #b3261e; }
.scan-outlier ul { margin: 0; padding-left: 18px; }
.scan-outlier li { font-size: 12.5px; line-height: 1.55; color: #6b5a3a; }
.scan-outlier.is-dot li { color: #7a2b22; }
html[data-theme="dark"] .scan-outlier { background: #2f2a1c; border-color: #5a4a26; }
html[data-theme="dark"] .scan-outlier.is-dot { background: #2a1c1c; border-color: #4a2f2f; }
html[data-theme="dark"] .scan-outlier-h { color: #e0b96a; }
html[data-theme="dark"] .scan-outlier.is-dot .scan-outlier-h { color: #f0a0a0; }
html[data-theme="dark"] .scan-outlier li { color: #c9b98f; }
html[data-theme="dark"] .scan-outlier.is-dot li { color: #e0b0aa; }

/* 🆕 v357：夜间底色/边框已随外框一起移到 `.scan-card`（.scan-entry 只剩 flex 容器） */
html[data-theme="dark"] .scan-entry-title { color: #c2ccd6; }

/* 手机端：识别结果表格取消固定最小宽度，允许单元格换行，避免横向滚动 */
@media (max-width: 640px) {
  .scan-result { overflow-x: visible; }
  .scan-table { min-width: 0; display: block; }
  .scan-table thead, .scan-table tbody, .scan-table th, .scan-table td, .scan-table tr { display: block; }
  .scan-table thead { display: none; }
  .scan-table tr { margin-bottom: 10px; padding: 10px 12px; border: 1px solid #dbe4ee; border-radius: 10px; background: #fff; }
  /* 🆕 v221：异常指标整卡标色 */
  .scan-table tr.rec-abn { background: #fdecea; border-color: #f3c3bd; }
  html[data-theme="dark"] .scan-table tr.rec-abn { background: #2a1c1c; border-color: #4a2f2f; }
  .scan-table td { padding: 4px 0; border: none; }
  .scan-table td.seq-td { display: inline-block; min-width: 24px; padding: 2px 8px; background: #e8f5f6; border-radius: 12px; font-size: 12px; margin-bottom: 6px; }
  .scan-table td .badge { margin-top: 0; margin-right: 4px; }
  .scan-table input[type=number], .scan-table input[type=text], .scan-table select { width: 100%; min-width: 0; max-width: 100%; padding: 6px 8px; font-size: 14px; }
  .scan-table .raw { max-width: none; color: #7b8ca0; font-size: 12px; margin-top: 6px; padding-top: 6px; border-top: 1px dashed #e2e8f0; }
  .scan-table .en-label { margin-left: 0; margin-top: 4px; }
  .scan-table .orig { display: block; margin-top: 2px; }
  /* 🆕 v445：手机端名称列是块布局、宽度自适应（flex:1 1 auto），不受桌面 320px 限宽约束 */
  .scan-table td:nth-child(2) { max-width: none; }
  .scan-empty-name, .scan-miss-name { width: 100% !important; }
  .scan-add-row { flex-direction: column; align-items: flex-start; gap: 6px; }

  /* 🆕 v221：大图回退为「宽度自适应 + 窗口固定高 320px 纵向滚动」。
     v218 的正方形窗口 + 双指缩放被用户判定为「看图太小、操作繁琐」，手机端恢复
     最大宽度自适应显示，超高部分在固定高度窗口内上下滑动查看。 */
  .entry-img-wrap { width: 100%; height: 320px; max-height: 320px; overflow: auto;
    -webkit-overflow-scrolling: touch; touch-action: pan-x pan-y;
    border-radius: 12px; background: var(--panel); }
  .entry-img-wrap .entry-img { width: 100%; max-width: 100%; height: auto; display: block; margin: 0 auto; transform: none !important; }
  .entry-img-wrap.is-phone .entry-img { max-width: 100%; }
  .entry-img-hint { display: none; }

  .scan-thumb { width: 118px; }
  .scan-thumb img, .scan-thumb-nofp { height: 80px; }
}

/* ===== v216：拍照识别手机端精修（与 styles.css v216 同一套 4pt 基距 / 圆角 / 触控规则） ===== */
@media (max-width: 640px) {
  .scan-drop { padding: 16px; border-radius: 14px; gap: 10px; flex-direction: column; align-items: stretch; }
  .scan-drop .scan-drop-icon { flex: 0 0 auto; margin: 0 auto; }
  .scan-drop .btn { flex: 0 0 auto; width: 100%; min-height: 44px; max-width: 100%; box-sizing: border-box; white-space: normal; }
  .scan-drop .hint { width: 100%; display: block; font-size: 12.5px; line-height: 1.6; margin: 0; box-sizing: border-box; word-break: break-word; overflow-wrap: break-word; }
  /* 🆕 v357：圆角/内边距随卡片走（原长在 .scan-entry 上，v357 起 .scan-entry 只做容器） */
  .scan-card { border-radius: 14px; padding: 14px; }
  .scan-entry-title { font-size: 13.5px; }
  .scan-table tr { border-radius: 12px; margin-bottom: 10px; padding: 12px; }
  .scan-table td.seq-td { min-height: 26px; display: inline-flex; align-items: center; }
  .scan-table input[type=number], .scan-table input[type=text], .scan-table select { min-height: 40px; }
  .scan-actions .btn { min-height: 42px; padding: 8px 16px; }
  .scan-summary { font-size: 13.5px; }
  .engine-sel select { min-height: 38px; }
  .scan-thumb { width: 116px; border-radius: 12px; }
  .scan-thumb img, .scan-thumb-nofp { height: 78px; border-radius: 8px; }
  .history-item { border-radius: 10px; }

  /* ===== v218：识别结果区改成「只读卡片窗口 + 编辑按钮」 ===== */
  /* 🆕 v357：本栏现在是 `.scan-card` 的内嵌标题行（不是自带圆角外框的独立条）⇒ 去外框、留底部细分隔线，
     让它读起来像「指标识别结果」这张卡片的标题，而不是又一张卡片。 */
  .entry-result-bar { display: flex; align-items: center; justify-content: flex-start; gap: 8px; padding: 0 0 10px; background: transparent; border: none; border-bottom: 1px solid var(--line); border-radius: 0; margin-top: 0; }
  .entry-result-bar + .entry-result { border-top: none; border-radius: 0; }
  .entry-result-title { font-size: 14px; font-weight: 700; color: var(--ink); }
  .entry-result-bar .entry-edit { min-height: 30px; padding: 4px 12px; font-size: 12px; }
  /* 🆕 v221：指标区改为自适应高度（去掉 v218 的 58vh 固定窗口），
     指标全部铺开，跟随页面一起滚动；每屏能看到的指标数由内容决定。 */
  /* 🆕 v221：自适应高度 —— 显式覆盖桌面 `.entry-result { overflow-x: auto }`
     （overflow-x:auto 会让 overflow-y:visible 被计算成 auto，导致区域仍被裁成固定窗口）。 */
  /* 🆕 v357：v221 原来在这里给 `.entry-result` 又套了一层 border + radius + panel 底 ⇒
     v357 它已经在 `.scan-card` 里，会变成「卡片套卡片的双层边框」。外框交给 `.scan-card` 承担，
     这里只保留「自适应高度」这个 v221 的真实目的（overflow: visible）。 */
  .entry-result { overflow: visible; }

/* 🆕 v314：指标卡片紧凑 flex 排布
     第一行 = 序号(10) + 指标名称(20) + 状态徽标(30)
     第二行 = 数值(40) + 单位(50) + 参考范围(60)（无空格紧凑排列）
     改/删按钮(70) 在第二行末尾，靠右下
     🆕 v521（需求5a）：第三行「原始 OCR 文本(80)」列已删除，本表现为 7 列。
     注意：selector 特异性用 `:not(.scan-missing):not(.scan-empty)` 与 v295/v300 一致，避免被覆盖 */
.entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 2px 4px;
  padding: 8px 10px; margin: 0 8px 6px;
  border: 1px solid var(--line); border-radius: 10px;
  background: rgba(255,255,255,0.45);
}
.entry-result .scan-table tr:not(.scan-missing):not(.scan-empty):last-child { margin-bottom: 8px; }
/* 🆕 v221：异常指标整卡标色（不再只标数值字符），一眼扫出哪些项超标 */
/* 🆕 v337 修复：这条规则原先**被上面那条布局规则压住**——`tr:not(.scan-missing):not(.scan-empty)`
   的 `background: rgba(255,255,255,0.45)` 特异性 (0,4,1) > 本条的 (0,3,1)，所以「整卡粉底」其实
   从来没生效过；粉色的视觉全靠 styles.css 里那条未限定容器的 `.rec-abn > td` 逐格涂抹撑着，
   于是每格一个色块。这里补上 `:not(...)` 把特异性提到 (0,5,1)，让 v221 的本意真正生效。 */
.entry-result .scan-table tr.rec-abn,
.entry-result .scan-table tr.rec-abn:not(.scan-missing):not(.scan-empty) { background: #fdecea; border-color: #f3c3bd; }
html[data-theme="dark"] .entry-result .scan-table tr.rec-abn,
html[data-theme="dark"] .entry-result .scan-table tr.rec-abn:not(.scan-missing):not(.scan-empty) { background: #2a1c1c; border-color: #4a2f2f; }
/* 🆕 v344 补漏：**普通行**的夜间底色。
   上面第 445 行那条规则既是「布局」又顺手写了 `background: rgba(255,255,255,0.45)` —— 那在日间是
   给白卡片加深一点点的浅灰，没问题；但在夜间，0.45 白叠在深色卡片上会合成出 **rgb(128,133,141) 中灰**，
   而文字是 #e6ebf1 ⇒ 实测对比度只有 **3.10**（WCAG AA 要求 4.5）。这就是用户报的「夜间模式问题」，
   只不过 v339/v344 修的是 #record-list / #indicator-list，**唯独漏了「照片识别结果」这张表**。
   实测取证：真机（模拟器 390 宽 / 夜间）截图上该表普通行就是一块中灰；
   同一时刻 #record-list 普通行 = rgb(38,47,60)、对比度 5.45（说明修复本身有效，只是没覆盖到这里）。
   特异性：(0,5,2) > 布局规则的 (0,4,1)，且 < 上面 rec-abn 那条的 (0,6,2) ⇒ 异常行仍保持 #2a1c1c。
   ⚠️ 1100px 桌面下该布局规则不生效（实测 rowBgRaw 为 transparent、合成色 rgb(28,36,44)、对比度 13.09），
      所以本修复刻意留在同一个 `@media (max-width: 640px)` 内，不越界。 */
html[data-theme="dark"] .entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) {
  background: rgba(255,255,255,0.06);
}
/* 🆕 v337：异常只由「整卡」着色，td 一律透明。手机端 td 是 inline-block + gap，任何 td 级背景
   都会被看成独立色块。`:not(.seq-td)` 是为了不碰序号胶囊自己的底色；特异性 (0,4,1) 足以压过
   任何裸 `.rec-abn > td` 规则（含暗色主题那条）。 */
.entry-result .scan-table tr.rec-abn > td:not(.seq-td) { background: transparent; }
.entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) > td {
  display: inline-block; padding: 0; font-size: 13px; border: none; vertical-align: middle;
}
/* 序号 —— v315 去掉色块，改为纯文字灰色序号（与 v221 色块化「异常」冲突：seq 不应抢戏） */
.entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) > td.seq-td {
  order: 10; display: inline-flex; align-items: center; justify-content: center;
  min-width: 22px; min-height: 22px; padding: 0;
  background: transparent; color: var(--muted); font-size: 12px; font-weight: 500;
  border: 0; border-radius: 0; margin: 0;
}
/* 指标名称：占据第一行剩余空间，长名称自动截断换行 */
.entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) > td:nth-child(2) {
  order: 20; flex: 1 1 auto; min-width: 0; font-weight: 600; font-size: 14px;
}
/* 状态徽标：推到第一行右侧 */
.entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) > td:nth-child(6) {
  order: 30; flex: 0 0 auto; min-width: 0; white-space: nowrap; margin-left: auto;
}
/* 换行：第一行 → 第二行 */
.entry-result .scan-table tr:not(.scan-missing):not(.scan-empty)::before {
  content: ''; order: 35; flex: 1 1 100%; height: 0;
}
/* 数值 + 单位 + 参考范围：紧凑排列，无额外空格 */
.entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) > td:nth-child(3) {
  order: 40; flex: 0 0 auto; min-width: 0; white-space: nowrap;
}
.entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) > td:nth-child(4) {
  order: 50; flex: 0 0 auto; min-width: 0; white-space: nowrap;
}
.entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) > td:nth-child(5) {
  order: 60; flex: 1 1 auto; min-width: 0; white-space: nowrap;
}
/* 改/删按钮：第二行末尾，靠右下 */
.entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) > td:nth-child(7) {
  order: 70; flex: 0 0 auto; min-width: 0; white-space: nowrap;
  margin-left: auto; align-self: flex-end;
}
.entry-result .scan-table .scan-actions { display: inline-flex; gap: 6px; align-items: center; }
/* 🆕 v533（用户需求 7）：识别结果行内四颗钮（✏ ❌ ↑ ↓）里，红 ❌ 是**文字 ×**、
   其余三颗是块级 SVG —— 混排时红 ❌ 整体比两边低一截（用户截图实证）。
   容器是 flex 行 ⇒ 用 align-items:center 让四颗钮沿交叉轴居中对齐
   （子项是 flex item，vertical-align 不参与布局，别走那条路）。 */
.entry-result .scan-table .scan-actions > .act-ico { align-self: center; }
/* ⛔ v357：原来这里把 改/删 染成绿底白字（`background:#2fbf83`），用户要求「不要青绿色了」⇒ 整组撤除。
   改/删 现在由 `.act-ico.act-edit`（纯笔形、无底色）/ `.act-ico.act-del`（红底白叉 ❌）承担，
   定义在 css/styles.css 文件末尾的「v357 全软件行内图标统一」段。 */
/* 🆕 v521（需求5a）：原「原始 OCR 文本」第 8 列已删除（见 buildEntryTableHTML），
   此处的 td:nth-child(8) 排版规则随列一并作废，仅保留徽标字号基础规则。 */
.entry-result .scan-table .badge { font-size: 11px; padding: 2px 6px; }
.entry-result .scan-table .en-label { display: inline-block; font-size: 11px; color: var(--muted); margin-left: 4px; font-weight: 400; }
.entry-result .scan-table .orig { display: block; font-size: 11px; color: var(--muted); margin-top: 2px; }
  /* 🆕 v221：手机端隐去「✓匹配」徽标 —— 已匹配无需提示，它会在名称后强制多占一行；
     ⚠近似 / ?未知 / 需核对 等需要人工介入的徽标保留。 */
  .entry-result .scan-table td:nth-child(2) .badge.ok { display: none; }
  .entry-result .scan-table .raw-map-tag { font-size: 11px; color: var(--muted); }

  /* 默认只读：数值/单位/范围编辑框无框透明 */
  .entry-result .scan-table input.scan-val,
  .entry-result .scan-table input.scan-unit,
  .entry-result .scan-table input.scan-low,
  .entry-result .scan-table input.scan-high,
  .entry-result .scan-table input.scan-empty-name,
  .entry-result .scan-table input.scan-empty-val,
  .entry-result .scan-table input.scan-miss-name,
  .entry-result .scan-table input.scan-miss-val,
  .entry-result .scan-table input.scan-miss-unit,
  .entry-result .scan-table input.scan-miss-low,
  .entry-result .scan-table input.scan-miss-high {
    border: none; background: transparent; -webkit-appearance: none; -moz-appearance: textfield;
    padding: 0; margin: 0; font-size: 13px; color: var(--ink); width: auto; min-width: 32px; min-height: 0; height: auto;
    pointer-events: none; user-select: none; text-align: left; border-radius: 0;
  }
  .entry-result .scan-table input::-webkit-inner-spin-button,
  .entry-result .scan-table input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }

  .entry-result .scan-table input.scan-val { max-width: 70px; }
  .entry-result .scan-table input.scan-unit { max-width: 75px; }
  .entry-result .scan-table input.scan-low,
  .entry-result .scan-table input.scan-high { max-width: 55px; }

  /* 🆕 v346：★「待填行」的输入框必须**常驻可编辑**（它没有「改」按钮，就是让你直接填的）。
     缺陷形态：上面那条只读规则的列表里**含** `.scan-empty-*` / `.scan-miss-*`（pointer-events:none），
     而唯一的解除规则是下面的 `.entry-result.editing` —— 那是**死代码**（全项目没有任何地方给
     .entry-result 容器加 editing，只有给 tr 加 .editing）。
     ⇒「＋ 补录缺失指标 → 填写 → 保存为记录」这条流程**根本填不进去**。
     实测证据（_probe_v346_empty_row.js，真实鼠标点击，非推断）：
       5 个输入框全部 pointer-events:none ／ border-width:0px ／ appearance:none ／ 高 15~17px
       elementFromPoint 命中的是 TD / SPAN.scan-range-sep（= 点不到输入框本身，hitSelf:false）
       Playwright 真实点击 4s 超时（locator.click: Timeout 4000ms exceeded），activeElement = (none)
       紧接着点「保存为记录」→ toast「请先填写数值」 ⇒ **流程不可用**
       （`fill()` 却能写进去 —— 它直接设值+派发事件、绕过指针 ⇒ 「看着能填」是假象）
     修法：特异性抬到 **(0,4,2)** 压过只读规则 (0,3,1)；配色沿用 v346 建立的「可输入 = 青色」这套
       （日 `#0e7c86` / 夜 `#14a0a8`），与单行编辑态的输入框视觉完全一致。
     ⚠️ 用「**整行** input」而不是逐个类名 —— 实测踩到过：待填行的名称/数值是 `.scan-empty-name`
       /`.scan-empty-val`，但单位/范围用的是**朴素类名** `.scan-unit/.scan-low/.scan-high`
       （见 js/app.js 空行模板）。只按 `.scan-empty-*` 列类名会漏掉后三个，
       表现就是「名称能填、单位范围仍点不进去」。整行选择器一次覆盖、也不会随模板加字段而漏。
     ⚠️ `.scan-empty-val` / `.scan-miss-val` 同时带 `.scan-val` 类，只读规则两条都命中
       ⇒ 必须用带 `tr.scan-row` 的更强选择器才压得住。 */
  .entry-result .scan-table tr.scan-row.scan-empty input,
  .entry-result .scan-table tr.scan-row.scan-missing input {
    border: 1px solid #0e7c86; background: #fff; -webkit-appearance: auto; pointer-events: auto; user-select: auto;
    padding: 4px 6px; border-radius: 6px; min-height: 30px; text-align: left; max-width: none;
  }
  html[data-theme="dark"] .entry-result .scan-table tr.scan-row.scan-empty input,
  html[data-theme="dark"] .entry-result .scan-table tr.scan-row.scan-missing input {
    background: #243140; border-color: #14a0a8; color: var(--ink);
  }

  /* 🆕 v391（用户第 4 项）：手机端「数值」框必须能**完整**显示 placeholder。
     被截断的 placeholder 会让人误以为框里已经有内容 —— 用户 2026-09-22 说的
     「名称框里有图片漏检·手填名称、数值框里填的是未识别」就是这类观感：
     原 placeholder「未识别·手填」在那点宽度里只露得出「未识别」三个字。
     实测（_probe_v391_scanui.js，canvas 按真字体量文本宽 vs 可用宽 clientWidth-内边距）：
       手机 390px：.scan-empty-val「数值或结果」need=65px / avail=58px ⇒ **短 7px，屏上显示「数值或结」**；
                  改文案后的 .scan-miss-val「数值」need=26 / avail=32 已够；桌面 1100px 两处都够。
     修法：两个「数值」框同一下限 88px（= need 65 + 左右内边距 12 + 余量 11），
       顺带让两张卡（漏检行 / 待填行）的数值框在手机端**等宽** —— 第 4 项要的就是这两种卡同构。
     ⚠️ 只抬 min-width、不动 width：不影响桌面端表格列宽（桌面两个数值框本来就都 ≥72px）。 */
  .entry-result .scan-table tr.scan-row.scan-empty input.scan-empty-val,
  .entry-result .scan-table tr.scan-row.scan-missing input.scan-miss-val { min-width: 88px; }

  /* 编辑模式：恢复输入框样式 */
  .entry-result.editing .scan-table input.scan-val,
  .entry-result.editing .scan-table input.scan-unit,
  .entry-result.editing .scan-table input.scan-low,
  .entry-result.editing .scan-table input.scan-high,
  .entry-result.editing .scan-table input.scan-empty-name,
  .entry-result.editing .scan-table input.scan-empty-val,
  .entry-result.editing .scan-table input.scan-miss-name,
  .entry-result.editing .scan-table input.scan-miss-val,
  .entry-result.editing .scan-table input.scan-miss-unit,
  .entry-result.editing .scan-table input.scan-miss-low,
  .entry-result.editing .scan-table input.scan-miss-high {
    border: 1px solid var(--line); background: #fff; -webkit-appearance: auto; pointer-events: auto; user-select: auto;
    padding: 4px 6px; border-radius: 6px; min-height: 30px; text-align: left; max-width: none;
  }
  .entry-result.editing .scan-table input.scan-val { min-width: 80px; }
  .entry-result.editing .scan-table input.scan-unit { min-width: 70px; }
  .entry-result.editing .scan-table input.scan-low,
  .entry-result.editing .scan-table input.scan-high { min-width: 60px; }
  .entry-result.editing .scan-table input.scan-empty-name,
  .entry-result.editing .scan-table input.scan-miss-name { width: 100%; min-width: 0; }
  html[data-theme="dark"] .entry-result.editing .scan-table input { background: #243140; border-color: var(--line); color: var(--ink); }

  /* 🆕 v302：单行编辑模式（点击「改」进入），比全局 .entry-result.editing 更轻量 */
  /* 🆕 v346 ★ 选择器必须带 `.entry-result .scan-table tr` 前缀 + `input` 元素，否则被上面 L534 的**只读规则压死**：
       只读规则 `.entry-result .scan-table input.scan-val` 特异性 = **(0,3,1)**
       v302 原写法 `.scan-row.editing .scan-val`         特异性 = **(0,3,0)**（同为 3 个类，但只读那条多一个 `input` 元素）
       ⇒ 同分靠源顺序也救不回来（何况只读规则在**前面**，编辑规则在后面，却依然输）。
     实测（真实点击「改」进编辑态后读 computed，v345 版）：
       border-width:0px ／ border-style:none ／ pointer-events:**none** ／ appearance:none
       ⇒ 数值 / 单位 / 参考范围看着就是**纯只读文字**、连点都点不进去
       ⇒ 这正是用户报的「点改后为什么还是只读啊？」
       （而同行的 `.scan-name/.scan-en` 正常可编辑——因为只读规则列表里**不含**它们，所以只有名称列看着像输入框）
     现写法 `.entry-result .scan-table tr.scan-row.editing input.scan-val` = **(0,5,2)** > (0,3,1) ✅ 真正生效。
     ⚠️ 本条同时改变**日间**表现（此前日间与夜间一样都是不可编辑的纯文字），属功能修复，需日间一并回归。
     🎨 边框用项目既定的「编辑态」配色对：日间 `#0e7c86` / 夜间 `#14a0a8`
        （照 css/styles.css L114/L115 `.rec-editing`，那里就是这一对）。
        实测对比度：`#0e7c86` vs 白底 = **4.94**；`#14a0a8` vs 夜间行底 rgb(38,47,60) = **4.26**
        ⇒ 两边都过 WCAG 1.4.11「非文本 UI 组件 ≥ 3.0」。
        （对比：原来日间用 var(--line) #e2e8f0 vs 白底只有 **1.23** ⇒ 白底白框等于没有框。） */
  .entry-result .scan-table tr.scan-row.editing input.scan-val,
  .entry-result .scan-table tr.scan-row.editing input.scan-unit,
  .entry-result .scan-table tr.scan-row.editing input.scan-low,
  .entry-result .scan-table tr.scan-row.editing input.scan-high {
    border: 1px solid #0e7c86; background: #fff; -webkit-appearance: auto; pointer-events: auto; user-select: auto;
    padding: 4px 6px; border-radius: 6px; min-height: 30px; text-align: left; max-width: none;
  }
  /* 夜间：底色/边框色沿用 v345 的**醒目青色**（可输入区域标识，与 css/styles.css L115 `.rec-editing` 同惯例）。
     必须写在这条更强选择器里 —— 若只留在下面 (0,4,1) 的弱规则里，会被本条的 `border:1px solid …` 反压回去。 */
  html[data-theme="dark"] .entry-result .scan-table tr.scan-row.editing input.scan-val,
  html[data-theme="dark"] .entry-result .scan-table tr.scan-row.editing input.scan-unit,
  html[data-theme="dark"] .entry-result .scan-table tr.scan-row.editing input.scan-low,
  html[data-theme="dark"] .entry-result .scan-table tr.scan-row.editing input.scan-high {
    background: #243140; border-color: #14a0a8; color: var(--ink);
  }
  .scan-row.editing .scan-actions .btn[data-scan-edit] { background: #0e7c86; }
  /* 🆕 v315：指标名称列 —— 只读显示与可编辑输入的两组元素互斥显隐，
     点「改」进入行内编辑态后，原本只读的指标名称列变为一组可改的输入框（标准名 + 英文名） */
  .scan-row.editing .scan-name-readonly { display: none; }
  .scan-row .scan-name-edit { display: none; }
  .scan-row.editing .scan-name-edit { display: flex; flex-direction: column; gap: 4px; min-width: 0; width: 100%; }
  .scan-row.editing .scan-name-edit input { border: 1px solid #0e7c86; background: #fff; -webkit-appearance: auto; pointer-events: auto; user-select: auto; padding: 4px 6px; border-radius: 6px; font-size: 13px; }
  /* 🆕 v346：日间边框同样改青色 —— 原来两边都是 var(--line)（日间 vs 白底仅 1.23 对比度）
     ⇒ 整行所有可输入框（名称/英文名/数值/单位/范围）统一用 `.rec-editing` 那对配色，一眼看出「哪些能改」。 */
  html[data-theme="dark"] .scan-row.editing .scan-name-edit input { background: #243140; border-color: #14a0a8; color: var(--ink); }

  /* 🆕 v344：夜间「编辑态输入框边界」——与上面那条**普通行底色**修复是同一次改动的连带面。
     ── 为什么必须一起修（实测数据，不是推断）──
     单行编辑态（点「改」给 tr 加 .editing）里真正渲染边框的只有 `.scan-name-edit input`
     （指标名/英文名，边框来自本文件 L598 的 `border: 1px solid var(--line)`；只读规则列表里
      **不含** `.scan-name/.scan-en`，所以它没被压住）。
       修复前：行底是中灰 rgb(128,133,141) ⇒ 边框 var(--line) vs 行底 = **3.26**、框底 #243140 vs 行底 = **3.56**
               （能看出「这里有两个可输入的框」，代价是整行变成一块中灰 —— 那正是用户报的缺陷本体）
       修底后：行底 rgb(38,47,60) ⇒ 边框 vs 行底掉到 **1.12**、框底 vs 行底掉到 **1.02** ⇒ 边框完全看不见
     修法照项目既有惯例：编辑态用**醒目青色**标识可输入区域
       （css/styles.css L115 `html[data-theme="dark"] .rec-editing { border-color: #14a0a8 !important }`）。
     加青色后实测（390 宽 / 夜间，_tmp_shot_edit.js 截图与探针双重取证）：
       #14a0a8 vs 普通行底 rgb(38,47,60) = **4.26**、vs 异常行底 #2a1c1c = **5.17**
       ⇒ 高于 WCAG 1.4.11 对非文本 UI 组件的 3.0。
     ⚠️ 只覆盖夜间：日间输入框是白底 + 深色边框，本来就清晰，不动。
       ❌ **v346 订正**：上面这句是**错的** —— 日间边框 var(--line) #e2e8f0 vs 白底只有 **1.23** 对比度，
         同样是「白底白框 = 看不见」。v346 已把日间也改成 `#0e7c86`（见 L618 与上面那条强规则）。
     ⚠️ **v346 订正**：本块原先还带着 `.scan-val/.scan-unit/.scan-low/.scan-high` 四条，当时是为
       「一旦下面那条既存缺陷被修好，青色立即生效」。该缺陷已在 v346 修好（见上方 L580 那条
       强选择器规则），**但青色不能留在这里**——这里的选择器只有 (0,4,1)，会被 (0,5,2) 的
       `border: 1px solid #0e7c86` 反压回去。故青色已上移到那条强规则内。
     ⚠️ 因为基线规则（L617 / L600 强规则）现在**双主题都是青色**，本块的两支看起来已是多余
       —— 但 ⚠️ **不要据此认定它是 no-op，见下面那条 🔴 v346 订正（它其实是活的层叠后备，已被删除）**。
       本块现在**只保留下面这段注释**（规则已删），目的是「一个地方能读到 v344 的完整证据链」。
     ⚠️ `.entry-result.editing` 这一支是**死代码**（全项目 grep 只有 L14457 给 tr 加 editing，
       没有任何地方给 .entry-result 容器加 editing），将来若启用「全局编辑模式」需重新启用它。
     🔴 **v346 订正（受控实验当场抓出来的）**：上面这段「本块两支在 v346 之后都已是 no-op」的说法**是错的**。
       实测：受控实验第 3 轮把**基础规则**的青色改回 var(--line)（模拟还原命名列的青色修复），
       C5 应该变红 —— 结果 **RC=0（锁没抓住）**，读 computed 发现夜间边框**仍是 rgb(20,160,168)**：
       正是本块 `html[data-theme="dark"] .scan-row.editing .scan-name-edit input { border-color:#14a0a8 }`
       这一支把它顶了回去。⇒ 它不是 no-op，而是**活的层叠后备**。
       危害：同一个视觉有了**两处定义**，改一处另一处默默兜底 ⇒「改了不生效」且套件保持全绿，
         正是本项目反复栽的「grep 到 ≠ 生效」同类坑。
       ⇒ v346 处置：**删掉本块的两条 CSS 规则**（下面只留注释，不留规则）。
         青色现在每主题**只有一处定义**：日间 L617（名称列）/ L600（数值列），夜间 L621 / L606。
         想再验锁就重跑 `_probe_v337_guard.sh` 第 3/4 轮，必须能红。
       （`.entry-result.editing` 那支若要复活，请连同上面那段说明一起重新启用，别只贴一行。） */
  /* 📌 v344 取证留档 → **v346 已修**（保留原始证据，便于回溯，勿删）：
     缺陷：单行编辑态下 `.scan-val/.scan-unit/.scan-low/.scan-high` 被只读规则彻底压死。
     实测（真实点击「改」后读 computed）：
       border-width: 0px ／ border-style: none ／ pointer-events: **none** ／ appearance: none
       ⇒ 用 mouse.click 点输入框中心，activeElement 仍是 BODY ⇒ **点不进去、改不了数值**。
     根因：只读规则 `.entry-result .scan-table input.scan-val…` 特异性 (0,3,1) >
           `.scan-row.editing .scan-val…` 的 (0,3,0)，仅凭源顺序救不回来。
     影响：**日间与夜间完全一样**（已实测两种主题，故与夜间无关）；
           点「改」后能改的其实只有指标名/英文名那两组框 ⇒ 用户报「点改后为什么还是只读啊？」。
     与 L13538 处注释（「编辑态 tr.editing 下输入框可见、可改」）明确矛盾 ⇒ v302 的实现被级联打败。
     ✅ **v346 修法（已落地）**：L580-591 两条选择器改为
       `.entry-result .scan-table tr.scan-row.editing input.scan-val…`（(0,5,2) / (0,6,3)）压过只读规则。
     为什么 v344 没一起修：它会改变**日间**单行编辑态的外观与交互（白底+边框+30px 高），
       属于功能行为改动，超出「夜间模式补漏」的范围，且需单独回归 ⇒ 用户 v346 直接点名要求，故本轮修。 */

  /* 操作按钮在只读/编辑模式下都保持可点 */
  .entry-result .scan-table .scan-empty-save,
  .entry-result .scan-table .scan-miss-save { min-height: 28px; font-size: 12px; padding: 4px 10px; }
  /* 🆕 v355：`.scan-add-row` 已废弃 —— 「＋ 补录缺失指标」整组上移到标题栏 .entry-result-bar
     （说明文字也收进 ? 弹窗）。下面三条只对已经不存在的老 DOM 生效，保留仅为回溯，
     新代码不要再引用 .scan-add-row。 */
  .entry-result .scan-add-row { display: flex; align-items: center; gap: 8px; padding: 8px 12px; margin: 0; border-bottom: 1px solid var(--line); }
  .entry-result .scan-add-row .btn { min-height: 32px; }
  .entry-result .scan-add-row .muted { font-size: 12px; line-height: 1.4; }
  .entry-result .scan-summary { padding: 8px 12px; font-size: 12.5px; line-height: 1.45; }
  .entry-result .scan-missing-hint { margin: 0; padding: 8px 12px; font-size: 12px; border-radius: 0; border-left: none; border-right: none; border-top: 1px solid #f0c36e; }
  /* 🆕 v379：底色与「待填行」（.scan-empty 的 #fffbe8）拉平 —— 原来这里是 #fffaf0，
     两者肉眼几乎分不出、但确实不是同一个值；既然本轮要把两类卡合并成一种，就统一到一个值上
     （留两个近似色只会让以后的人再犯"到底该跟哪个"的犹豫）。 */
  .entry-result .scan-missing-body tr { border-color: #f0c36e; background: #fffbe8; }

  /* ===== 🆕 v355：标题栏工具组在窄屏收紧（保证「已识别 X/Y 项」与 📄 ＋ ? 尽量同排） =====
     用户原话是「窗口**右上角**的问号」⇒ 工具组必须钉在右侧。
     做法：① 工具组吃 `margin-left:auto`（同排时被推到最右；窄屏换行时**仍能把第二行推到右端**）；
          ② 计数徽标在手机端让出它的 auto 外边距 —— 否则两个 auto 会平分剩余空间，
             徽标被顶到中间、工具组仍留在左端（实测第一版就是这样：工具组 top 与徽标差 30px 且 left=51）。 */
  .entry-result-bar { gap: 6px; }
  .entry-result-bar .entry-count-badge { margin-left: 0; }
  .entry-result-bar .entry-bar-tools { margin-left: auto; gap: 5px; }
  /* 🆕 v357：这里原来把工具按钮压成 30×28 圆角矩形 ⇒ 与卡片角落 26×26 的问号**肉眼可见不一样大**
     （用户报的「问号大小不统一」正是这一处）。现在三处统一 26×26，本断点不再改尺寸。 */
  /* 🆕 v513（UISKILL 命中区）：窄屏同样保持 40px，不再压到 32px —— 手指比鼠标更需要命中高度。 */
  .scan-add-menu .btn { min-height: 40px; font-size: 12px; }

  /* ===== 🆕 v355：普通指标窗口「只读态 = 合并文本」 =====
     用户原话：「数值、单位要合并成一个文本，不要留空格，参考范围前保留"参考范围"的文字，
              参考范围的2个文本框要合并成一个文本，不要留空格」。
     为什么必须换元素而不是收紧 CSS：`<input>` **永远不会按内容自适应宽度**
     （旧 Android WebView 没有 field-sizing:content），只靠 max-width 收紧，
     短值（12.8）照样把 70px 的框撑满、后面留一大块空白 ⇒ 只能改成纯文本 span。
     判据：只读态下 `.scan-vu-ro` / `.scan-range-ro` 可见、四个输入框不可见；
           点「改」进入 tr.editing 后**反过来**（这是 C9 反向段要锁的）。 */
  .entry-result .scan-table tr:not(.scan-missing):not(.scan-empty):not(.editing) > td:nth-child(3) > input.scan-val,
  .entry-result .scan-table tr:not(.scan-missing):not(.scan-empty):not(.editing) > td:nth-child(5) > input.scan-low,
  .entry-result .scan-table tr:not(.scan-missing):not(.scan-empty):not(.editing) > td:nth-child(5) > input.scan-high,
  .entry-result .scan-table tr:not(.scan-missing):not(.scan-empty):not(.editing) > td:nth-child(5) > span.scan-range-sep { display: none; }
  /* 单位单独占一格（td4）——只读态整格收起，单位已并进「数值」文本里 */
  .entry-result .scan-table tr:not(.scan-missing):not(.scan-empty):not(.editing) > td:nth-child(4) { display: none; }
  .entry-result .scan-table tr:not(.scan-missing):not(.scan-empty):not(.editing) > td:nth-child(3) > .scan-vu-ro,
  .entry-result .scan-table tr:not(.scan-missing):not(.scan-empty):not(.editing) > td:nth-child(5) > .scan-range-ro { display: inline; }
  .entry-result .scan-table .scan-range-lb { display: inline; color: var(--muted); font-weight: 500; margin-right: 1px; }

  /* ===== ⛔ v357：v355 的「字段标签 + 可编辑序号」已按用户要求撤回 =====
     用户原话（第 4 条）：「…不需要修改序号了，在没有填文本和数值之前，把指标名称、数值、单位、
     参考范围这几个文本放到编辑框内部去」。
     ⇒ ① `.scan-field-lb`（框外标签）**不再渲染**，其样式规则一并删除；
        ② `.scan-empty-seq`（序号输入框）**不再渲染**，序号改回只读 `(idx+1)`；
        ③ 对应替代规则见本文件末尾「v357：待填行改按 image#1 版式」段。
     ⚠️ 保留一条：`.scan-field-lb` 仍在「默认隐藏」列表里（见本文件顶部
        `.scan-vu-ro, .scan-range-ro, .scan-range-lb, .scan-field-lb { display:none }`）——
        那是**只读态合并文本**那一组的隐藏名单，留着无害（元素已不存在），
        但**不要**据此认为它还在用；也不要为了「清理干净」去动那一行（它同时管着 3 个在用的类）。 */
  /* ===== 🆕 v357：待填「编辑窗口」的输入框宽度（**取代 v355 的 130px / 96px**）=====
     v355 是照「标签在框外、每行一个字段」定的宽（数值 130 / 单位 130 / 下限·上限 96）；
     v357 改成 image#1 版式后，数值 / 单位 / 下限 ~ 上限 要**并排放在同一行**，
     360px 视口下这一行的可用宽度约 330px ⇒ 四框收到 72 / 72 / 62 / 62（含 `~` 与 6px 间隙）才放得下，
     仍用 130px 会整行溢出被裁。宽度只做「够用」不追求撑满，与 image#1 的小方框一致。
     ⚠️ 特异性必须 ≥ v355 的 (0,6,2)：照抄带 `:nth-child()` 与 `input` 元素的写法。
     ⚠️ 顺带记一段沿革：v355 这条是为了修「框塌缩到 15px」——
        根因是只读规则 `.entry-result .scan-table input.scan-val…` (0,3,1) 的 `width:auto; min-width:32px`
        压过 `.scan-table input[type=number]{width:100%}` (0,2,1)，再叠加 v346 的 (0,4,2) `max-width:none`。
        这条结论仍然成立，v357 只是把数值改小，**父级仍然要压住只读规则**。 */
  .entry-result .scan-table tr.scan-row.scan-empty > td:nth-child(3) input.scan-val { width: 72px; max-width: none; }
  .entry-result .scan-table tr.scan-row.scan-empty > td:nth-child(4) input.scan-unit { width: 72px; max-width: none; }
  .entry-result .scan-table tr.scan-row.scan-empty > td:nth-child(5) input.scan-low,
  .entry-result .scan-table tr.scan-row.scan-empty > td:nth-child(5) input.scan-high { width: 62px; max-width: none; }
  /* 🆕 v357：英文名输入框与指标名称同列纵向排布（image#1 的两个全宽框） */
  /* 🆕 v379：漏检行补齐英文名框（`.scan-miss-en`）后，与待填行的 `.scan-empty-en` **共用同一条规则** ——
     「两张卡合并」在 CSS 层的落地就是这种「同一组选择器同时匹配两者」，
     而不是再抄一份（抄一份就会出现两处取值各自漂移的老毛病）。
     ⚠️ 漏检行的模板里同时也写了内联 `width:100%`（与 `.scan-miss-name` 保持一致）；
        这条 CSS 是**兜底**：万一将来有人去掉内联，宽度也不会塌回只读规则的 `min-width:32px`。 */
  .entry-result .scan-table tr.scan-row.scan-empty input.scan-empty-en,
  .entry-result .scan-table tr.scan-row.scan-missing input.scan-miss-en { width: 100%; min-width: 0; font-weight: 400; }
  /* 🆕 v379：漏检行的「单位 / 参考范围」两个框此前是**完全空白**的输入框 ——
     用户原话「应该把英文名称、单位、参考范围所在的文本框中用文本标记出来」。
     placeholder 已加在模板里；这里只统一字体粗细，与名称框的常规字重对齐
     （placeholder 本身在浏览器里默认就是浅色，日/夜主题各自继承，无需另设颜色）。 */
  .entry-result .scan-table tr.scan-row.scan-missing input.scan-miss-unit,
  .entry-result .scan-table tr.scan-row.scan-missing input.scan-miss-low,
  .entry-result .scan-table tr.scan-row.scan-missing input.scan-miss-high { font-weight: 400; }
  /* 🆕 v355：同族缺陷 —— 「图片漏识别」行（tr.scan-missing）的**数值**框没有内联宽度，
     落进只读规则后同样塌缩（它的单位/范围三个框有内联 width 所以看起来正常）。
     同一个表里两处同病，一并修，避免「补录行能填、漏检行不能填」的不一致。 */
  .entry-result .scan-table tr.scan-row.scan-missing input.scan-miss-val { width: 110px; max-width: 42%; }
}

/* ==========================================================================
   🆕 v283：疑似同人合并确认对话框（取代 window.confirm，支持「本次剩余照片都按此规则」）
   ⚠️ display:flex 会覆盖 [hidden] 的默认 none，必须显式压回（v269 同款坑）
   ========================================================================== */
.merge-dlg[hidden] { display: none; }
.merge-dlg {
  position: fixed; inset: 0; z-index: 1000;
  background: rgba(15, 30, 45, .45);
  display: flex; align-items: center; justify-content: center; padding: 18px;
}
.md-box {
  width: 100%; max-width: 420px; max-height: 86vh; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--line); border-radius: 14px;
  padding: 16px 16px 12px; box-shadow: 0 12px 32px rgba(15, 30, 45, .25);
}
.md-title { font-size: 16px; font-weight: 700; margin: 0 0 8px; }
.md-desc { margin: 0 0 12px; font-size: 13.5px; line-height: 1.6; color: var(--ink); white-space: pre-line; }
.md-desc b { color: var(--primary); }
/* 「记住本次选择」勾选行 */
.md-remember {
  display: flex; align-items: flex-start; gap: 8px; margin: 0 0 14px;
  padding: 9px 10px; border-radius: 10px;
  background: rgba(14, 124, 134, .07); border: 1px solid rgba(14, 124, 134, .2);
  font-size: 13px; line-height: 1.5; cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.md-remember input { width: 18px; height: 18px; margin: 1px 0 0; flex: 0 0 auto; accent-color: #0e7c86; }
.md-remember .md-rm-sub { display: block; font-size: 11.5px; color: var(--muted); margin-top: 2px; }
html[data-theme="dark"] .md-remember { background: rgba(95, 208, 219, .10); border-color: rgba(95, 208, 219, .24); }
.md-actions { display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap; }
.md-actions .btn { flex: 1 1 auto; min-width: 116px; }
/* ⚠️ .btn.ghost 是白字（深色顶栏专用），在浅色对话框里会隐形（已命中 3 次）→ 这里覆盖成本地配色 */
.md-actions .btn.ghost { background: #eef3f8; color: #2c3e50; border-color: #c8d5e2; }
.md-actions .btn.ghost:active { background: #dde7f1; }
html[data-theme="dark"] .md-actions .btn.ghost { background: #243140; color: #dce7f2; border-color: #3a4a5c; }

/* ===== v295：指标卡片紧凑化（用户 2026-09-09 七条 UI 反馈之三/四） =====
   ① 状态徽标（正常/偏高…）钉到卡片右上角，不再单独占一行；
   ② 参考范围与单位之间的大空格收紧；
   ③ 虚线下方「原始文本 → 标准名」与卡片内「原识别」重复 → 普通行整块隐藏
     （漏检/待填行保留原始文本供对照补填）；
   ④ 大图窗口标题栏「照片识别结果」与「图片识别指标」标题栏同款边框。 */
@media (max-width: 640px) {
  .entry-result .scan-table tr { position: relative; }
  /* 🆕 v314：状态徽标(30)与改/删按钮(70)的 order 改在 v314 块（line 458/477）统一管理，
     此处仅保留 v303 的 layout 兜底（position: static / margin 推到右侧）。v303 原 order:40/41 已删除。 */
  .entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) > td:nth-child(6) {
    position: static; flex: 0 0 auto; margin: 0 0 0 auto; max-width: none;
  }
  .entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) > td:nth-child(7) {
    flex: 0 0 auto; margin: 0 0 0 auto;
  }
  /* 指标名不再给绝对定位徽标让位 */
  .entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) > td:nth-child(2) { padding-right: 0; }
  /* 参考范围 ~ 上限 收紧，单位紧跟其后 */
  /* 参考范围/单位输入框随内容自适应（field-sizing 需 Chromium 123+；旧 WebView 回退到固定宽） */
  .entry-result .scan-table input.scan-low,
  .entry-result .scan-table input.scan-high,
  .entry-result .scan-table input.scan-val,
  .entry-result .scan-table input.scan-unit { field-sizing: content; width: auto; max-width: 72px; min-width: 20px; }
  .entry-result .scan-table input.scan-low,
  .entry-result .scan-table input.scan-high { width: 38px; max-width: 38px; min-width: 24px; }
  /* Chromium 123+：输入框宽度随内容自适应（旧 WebView 由上面固定宽兜底） */
  @supports (field-sizing: content) {
    .entry-result .scan-table input.scan-low,
    .entry-result .scan-table input.scan-high,
    .entry-result .scan-table input.scan-val,
    .entry-result .scan-table input.scan-unit { field-sizing: content; width: auto; max-width: 90px; min-width: 0; }
  }
  .entry-result .scan-table td:nth-child(5) { column-gap: 0; }
  .entry-result .scan-table td:nth-child(4) { margin-left: 3px; }
  /* 🆕 v521（需求5a）：原「原始文本」第 8 列已删，隐藏/保留该列的规则一并作废。 */

  /* 「照片识别结果」标题栏 = 「图片识别指标」标题栏同款样式 */
  .scan-entry-head {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    padding: 10px 12px; margin-top: 10px; margin-bottom: 0;
    background: var(--panel); border: 1px solid var(--line); border-bottom: none;
    border-radius: 12px 12px 0 0;
  }
  .scan-entry-head + .entry-img-wrap { border-radius: 0 0 12px 12px; }
}

/* ===== 🆕 v300：数值/单位/参考范围合并到一行（用户 2026-09-09 晚间反馈之三） =====
   v221 老布局：第一行=序号+名称+数值+徽标，tr::before 强制换行后第二行=参考+单位（间距大）。
   v299 首轮：只调 order/flex 没设 input 宽，结果 number 输入框默认 size 20 → 每个 input
   160px，flex 容器装不下又换行，截图里 46.7 与 U/L 之间、参考 low~high 之间仍有很大空格。
   v300 二轮：①td3/4/5 order 改为 55/56/57（排在换行伪元素之后）+ 显式 input 宽，但旧
     WebView 不支持 field-sizing:content，会把 max-width 当成 width 全占满 → 截图里 input 内
     留大片空白。
   v300 末轮（本块）：input 改 `width:auto; max-width:Xpx` + `field-sizing:content`（Chromium
     123+ / 新 WebView 自适应），旧 WebView 回退时按 max-width 截断不溢出；统一覆盖 row 间距
     `gap: 2px 4px`；去掉 "参考 " 字样（用户：「数值、单位、参考范围把空格去掉」）。
   排版目标：`46.7  U/L  0 ~ 5` 一行紧凑，徽标钉行尾。漏检/待填行保持 v221 原布局不动。 */
/* 🆕 v295/v300 旧 CSS 已由 v314 接管（line 428+ 同一特异性 + 整 tr 重排），
   这里只保留「参考范围 ::before content:none」和 input 宽度控制两项基础规则
   （这些是非 v314 专属，任何 .entry-result 都适用） */
.entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) { gap: 2px 4px; }
/* 数值/单位/参考范围：去掉 "参考 " 字样 */
.entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) > td:nth-child(5)::before { content: ''; }
/* 显式小宽度：input 默认 size 20 → ~160px，会撑开 flex 行。新 WebView 自适应收缩，旧 WebView 按 max-width 截断 */
.entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) > td:nth-child(3) input.scan-val { width: auto; max-width: 64px; min-width: 20px; field-sizing: content; }
.entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) > td:nth-child(4) input.scan-unit { width: auto; max-width: 48px; min-width: 20px; field-sizing: content; }
.entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) > td:nth-child(5) input.scan-low,
.entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) > td:nth-child(5) input.scan-high { width: auto; max-width: 36px; min-width: 18px; field-sizing: content; }

/* ===== 🆕 v303：数据管理左右两个正方形窗口 =====
   关键点：正方形=高度=宽度本身（不是百分比），由 JS 在布局阶段把每个 .data-import-square
   的 height 设为 clientWidth，回调 _pairSquaresAlign() 处理窗口缩放/标签切换。 */
.data-import-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}
.data-import-square {
  /* 内容如 img/img-list 不要撑高容器，最小高度由 JS 设置保证正方形 */
  min-height: 0;
  display: flex;
  flex-direction: column;
  /* overflow 限制在本方块内（缩略图条 / 大图 viewer 自己再内部滚动） */
  overflow: hidden;
  /* v303：JS 把 height 设为内容区宽度，需 border-box 才能对得齐像素（高度含 border） */
  box-sizing: border-box;
}
/* 左右两边的内容块（缩略图 / viewer）按需滚动，不要把方块撑破 */
.data-import-square .scan-drop,
.data-import-square .scan-thumbs,
.data-import-square .scan-entries,
.data-import-square .file-thumbs,
.data-import-square .file-viewer { flex: 0 0 auto; }
.data-import-square .file-thumbs {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(78px, 1fr));
  gap: 6px;
  max-height: 100px;
  overflow: auto;
  margin: 8px 0;
  padding: 4px;
  background: rgba(255, 255, 255, 0.04);
  border-radius: 8px;
}
.data-import-square .file-thumbs:empty { display: none; }
.data-import-square .file-thumbs .file-thumb {
  position: relative;
  aspect-ratio: 1 / 1;
  border: 1px solid var(--line);
  border-radius: 6px;
  overflow: hidden;
  cursor: pointer;
  background: var(--panel);
}
.data-import-square .file-thumbs .file-thumb img {
  width: 100%; height: 100%;
  object-fit: cover; display: block;
}
.data-import-square .file-thumbs .file-thumb .file-thumb-name {
  position: absolute; left: 0; right: 0; bottom: 0;
  font-size: 10px; line-height: 1.1; padding: 2px 4px;
  background: rgba(0, 0, 0, 0.55); color: #fff;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.data-import-square .file-thumbs .file-thumb.active {
  outline: 2px solid var(--brand, #198754);
  outline-offset: -2px;
}

/* 文件查看器（Excel/Word 式：拖动平移 + 滚轮缩放） */
.file-viewer {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 200px;
  margin-top: 8px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: #fafafa;
  overflow: hidden;
}
.file-viewer[hidden], [hidden] { display: none !important; }
html[data-theme="dark"] .file-viewer { background: #1c1f23; }
.file-viewer-head {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 10px;
  border-bottom: 1px solid var(--line);
  background: var(--panel);
  flex: 0 0 auto;
}
.file-viewer-name {
  font-weight: 600; font-size: 13px;
  max-width: 50%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.file-viewer-meta { font-size: 11px; color: var(--muted); }
.file-viewer-tools { margin-left: auto; display: flex; align-items: center; gap: 4px; }
.file-viewer-zoom {
  display: inline-block; min-width: 46px; text-align: center;
  font-size: 11px; color: var(--muted); padding: 0 4px;
}
.file-viewer-stage {
  flex: 1 1 auto;
  position: relative;
  overflow: auto;
  background: repeating-linear-gradient(
    45deg, transparent 0 10px,
    rgba(0,0,0,0.025) 10px 11px
  );
  /* 移动端用 overfow-scrolling 提升平移手感 */
  -webkit-overflow-scrolling: touch;
}
.file-viewer-canvas {
  position: relative;
  transform-origin: 0 0;
  width: fit-content;
  min-width: 100%;
  min-height: 100%;
  display: flex; align-items: flex-start; justify-content: center;
  cursor: grab;
}
.file-viewer-canvas.dragging { cursor: grabbing; user-select: none; }
.file-viewer-canvas img {
  display: block;
  max-width: none; /* 缩放时不希望被图片 max-width 限制 */
  pointer-events: none;
}
.file-viewer-canvas canvas {
  display: block;
  pointer-events: none;
}

/* 文件导入按钮组 */
.file-import-actions {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  margin: 6px 0 4px;
}
.data-import-square .hint {
  max-width: 100%;
  word-break: break-word;
  box-sizing: border-box;
}
.data-import-square .file-import-actions .btn {
  max-width: 100%;
  white-space: normal;
}
/* 🆕 v303：文件识别空状态 inline 提示（避开 hintsToCorner 扫描） */
.data-import-square .file-import-hint {
  margin: 6px 0 8px;
  padding: 8px 10px;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--muted);
  background: rgba(127, 180, 220, 0.08);
  border-left: 3px solid rgba(127, 180, 220, 0.45);
  border-radius: 4px;
  word-break: break-word;
  overflow-wrap: break-word;
  box-sizing: border-box;
}
.data-import-square .file-import-hint[style*="display: none"] { display: none !important; }

/* 🆕 v303 A 版：图片识别窗口底部的「数据管理收容」辅助按钮（导入 / 导入CSV / 示例） */
.data-import-square .scan-aux-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  margin: 10px 0 4px;
  padding-top: 8px;
  border-top: 1px dashed var(--line);
}
.data-import-square .scan-aux-actions .btn {
  flex: 1 1 auto;
  min-width: 80px;
  max-width: 100%;
  white-space: normal;
  text-align: center;
}

/* 手机端：左右两个窗口自然堆成上下两个正方形 */
@media (max-width: 720px) {
  .data-import-pair {
    grid-template-columns: 1fr;
    gap: 12px;
  }
}
/* 横向紧凑布局：左右两个窗口更窄时也保持并排 */
@media (min-width: 721px) and (max-width: 900px) {
  .data-import-pair { grid-template-columns: 1fr 1fr; gap: 10px; }
}

/* ===== v303 B 版：检验记录页入口对（推翻 v303 A 自包含窗口方案）=====
   第一行 = 左右 2 个正方形「入口卡」（.lab-entry），仅含动图+标题+简介；
   点击切换下方子内容容器（.lab-entry-content）。默认选中图片识别。 */
.lab-entry-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  margin-bottom: 14px;
}
.lab-entry {
  /* 复用 .home-win 的视觉语言（动图+标题+简介），但去掉「点击跳转别的 tab」语义 */
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  text-align: left;
  cursor: pointer;
  user-select: none;
  padding: 12px;
  min-height: 0;
  /* 🆕 v341 修既有深色缺陷：原为 `var(--card-bg, #fff)`，但 `--card-bg` **全项目从未定义**
     （只有这一处引用），于是永远落到 #fff 兜底 ⇒ 未选中的那张入口卡在深色主题下是**硬白**。
     选中态由下面的 `.lab-entry.on` 覆盖成半透明绿，所以只有未选中那张看得出来——正是用户截图上
     「图片识别=深色、文件识别=死白」的成因。项目通用面板色令牌是 `--panel`（浅 #ffffff / 深 #182230）。 */
  background: var(--panel, #fff);
  border: 2px solid var(--line, #e2e8f0);
  border-radius: 10px;
  box-sizing: border-box;
  font: inherit;
  color: inherit;
  transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
  overflow: hidden;
}
.lab-entry:hover {
  border-color: var(--brand, #198754);
  box-shadow: 0 2px 8px rgba(25, 135, 84, 0.12);
}
.lab-entry.on {
  border-color: var(--brand, #198754);
  background: rgba(25, 135, 84, 0.04);
  box-shadow: 0 2px 12px rgba(25, 135, 84, 0.18);
}
/* 正方形：高度 = 自身宽度的一半（与原 .data-import-square 同样由 JS 同步） */
.lab-entry-pair .lab-entry {
  aspect-ratio: 1 / 1;
  max-height: 280px;
}
/* 标题头（图标+名称+badge） */
.lab-entry .hw-head {
  display: flex; align-items: center; gap: 6px;
  font-size: 1em; font-weight: 600;
  margin-bottom: 6px;
}
.lab-entry .hw-ico { font-size: 1.1em; }
.lab-entry .hw-badge {
  margin-left: auto; font-style: normal;
  font-size: 11px; font-weight: 500;
  padding: 2px 8px; border-radius: 999px;
  background: var(--brand-soft, rgba(25, 135, 84, 0.12));
  color: var(--brand, #198754);
}
.lab-entry.on .hw-badge { background: var(--brand, #198754); color: #fff; }
/* 动图区（占满中间，留出 8px 给底部简介） */
.lab-entry .hw-demo {
  flex: 1 1 auto;
  display: flex; align-items: center; justify-content: center;
  min-height: 0;
  padding: 4px 0;
  overflow: hidden;
}
.lab-entry .hw-mock {
  width: 78%; height: 100%;
  display: block;
  position: relative;
  background: rgba(127, 180, 220, 0.06);
  border-radius: 8px;
  border: 1px solid rgba(127, 180, 220, 0.18);
  overflow: hidden;
}
/* 文件识别专用动图：三张错位文档卡（沿用首页 .hw-mock-* 视觉语言） */
.lab-entry .hw-mock-file {
  display: flex; align-items: center; justify-content: center;
  gap: 4%; padding: 8% 6%;
}
.lab-entry .hw-mock-file .hw-doc {
  flex: 0 0 auto;
  width: 24%; height: 78%;
  background: #fff;
  border: 1px solid rgba(127, 180, 220, 0.35);
  border-radius: 4px;
  display: flex; flex-direction: column;
  justify-content: flex-start;
  padding: 8% 8% 4%;
  gap: 18%;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
  position: relative;
}
.lab-entry .hw-mock-file .hw-doc s {
  display: block; height: 6%;
  background: rgba(127, 180, 220, 0.35);
  border-radius: 1px;
}
.lab-entry .hw-mock-file .hw-doc s.short { width: 60%; }
.lab-entry .hw-mock-file .d1 { transform: translateY(-4%) rotate(-2deg); animation: hw-doc-float 3.2s ease-in-out infinite; }
.lab-entry .hw-mock-file .d2 { transform: translateY(0)   rotate(0deg);  animation: hw-doc-float 3.2s ease-in-out 0.4s infinite; }
.lab-entry .hw-mock-file .d3 { transform: translateY(2%)  rotate(2deg);  animation: hw-doc-float 3.2s ease-in-out 0.8s infinite; }
@keyframes hw-doc-float {
  0%, 100% { transform: translateY(0) rotate(var(--r, 0deg)); }
  50%      { transform: translateY(-3%) rotate(var(--r, 0deg)); }
}
.lab-entry .hw-mock-file .d1 { --r: -2deg; }
.lab-entry .hw-mock-file .d2 { --r: 0deg; }
.lab-entry .hw-mock-file .d3 { --r: 2deg; }
/* 简介行（底部一行） */
.lab-entry .hw-foot {
  font-size: 12.5px;
  color: var(--muted);
  line-height: 1.45;
  margin-top: 4px;
  word-break: break-word;
}
.lab-entry.on .hw-foot { color: var(--brand, #198754); }
/* 入口下方的子内容容器：默认显示图片识别，文件识别 hidden（按入口状态切换） */
.lab-entry-content[hidden] { display: none !important; }
.lab-entry-content { margin-top: 4px; scroll-margin: 76px; }   /* 🆕 v524（需求17）：scrollIntoView 落点让开 sticky 页签条 */

/* 手机端仍保持左右两个正方形（用户明确要求「左右不分」）——
   入口卡宽度 = 容器宽的一半减去 gap，高度由 aspect-ratio 1/1 自然等于宽度。
   小屏下文字通过缩小字号和隐藏简介多行避免溢出。 */
@media (max-width: 720px) {
  .lab-entry-pair {
    grid-template-columns: 1fr 1fr;
    gap: 10px;
  }
  .lab-entry-pair .lab-entry { padding: 8px; }
  .lab-entry .hw-head { font-size: 0.92em; }
  .lab-entry .hw-badge { display: none; }  /* 小屏省略 badge，省空间 */
  .lab-entry .hw-foot { font-size: 11px; }
}

/* 🆕 v314：图片识别入口的「相册 / 微信 / 拍照」3 按钮行
   三按钮始终横向并排；小屏时自动等分宽度、略收内边距。 */
.scan-actions-row {
  display: flex; flex-wrap: nowrap;
  gap: 8px;
  margin: 4px 0 8px;
}
.scan-actions-row .scan-act-btn {
  flex: 1 1 0;
  min-width: 0;
  padding: 8px 12px;
  font-size: 14px;
  line-height: 1.2;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
}
@media (max-width: 480px) {
  .scan-actions-row .scan-act-btn { padding: 8px 6px; font-size: 13px; }
}

/* ============================================================================
   🆕 v357：待填行（手动补录）改按用户 image#1 的「可编辑窗口」版式
   ----------------------------------------------------------------------------
   用户原话（第 4 条）：
     「添加指标的时候，指标窗口就按照已识别的窗口 @image#1 的可编辑样式来做，不需要修改序号了，
       在没有填文本和数值之前，把指标名称、数值、单位、参考范围这几个文本放到编辑框内部去，
       同样保留完成和删 2 个按钮。」
   版式（对齐 image#1）：
     ┌ [序号]  指标名称(整行输入框) ┐
     │         英文名  (整行输入框) │
     ├ 数值(框) 单位(框) 下限~(框) 上限(框) ┤
     │                     完成(青) ❌(红) ┘
   三条关键取舍（都有理由，别改回去）：
     ① **序号不再可编辑**：v355 曾给待填行加过 `input.scan-empty-seq`，用户明确否掉
        （「不需要修改序号了」）⇒ 改回与普通行一致的**只读序号**，序号仍会自动重排
        （保存时按 `splice` 插入后由 idx 重新生成，逻辑见 js/app.js 的保存分支）。
     ② **四个字段标签从框外移进框内**（placeholder）：v355 是框外 `<label class="scan-field-lb">`，
        用户要求「在没有填文本和数值之前，把…这几个文本放到编辑框内部去」。
        ⇒ 本版删掉 `.scan-field-lb`，改用 placeholder；参考范围是两个框 ⇒ 写「下限」「上限」
          （用户口中的「参考范围」由 `~` 分隔符 + 下限/上限两个词共同表达）。
     ③ **删按钮 = 红底白叉 ❌**（不是「与完成同色」）：用户追问后定稿
        「按照第7条，红底白叉，不要青绿色了」⇒ 第 4 条原话「"删"按钮的颜色保持与"完成"按钮一致」
        已被该口径覆盖。「完成」保留 primary 青绿（= image#1 里的深青底「完成」）。
   断点：只在 ≤640px（卡片化版式）生效；桌面端待填行走正常表格列，行为不变。
   ============================================================================ */
@media (max-width: 640px) {
  /* 🆕 v379：本块把「待填行(scan-empty)」与「漏检行(scan-missing)」的卡片化规则
     **合并成同一组选择器**（用户第 2/3 条原话「两张卡合并，应该都是按照 OCR 漏检行卡片的样式来做」）。
     ── 为什么现在能合并：v379 给漏检行补上英文名框后，两类行的 td 结构**完全同构**
        （序号 / 名称+英文名 / 数值 / 单位 / 范围 / 按钮 / 操作 / 原始文本，共 8 列）
        ⇒ 同一套 flex 卡片布局对两者都成立，不必各写一份（抄两份就会各自漂移）。
     ── 为什么不干脆合并类名：`tr:not(.scan-missing):not(.scan-empty)` 这个组合
        在 scan.css 里出现 40+ 次（普通行的布局全靠它把这两类特例排除掉）——
        改类名等于重做整张表的布局，风险与收益完全不成比例。
     ── 选择器全部**逐条并列**（不用 `:is()`）：本项目要兼容老 WebView，
        `:is()` 需要 Chrome/WebView 88+，并列写法没有兼容性风险。 */
  /* 两类行的 td 与普通行一样先全部打开（普通行的布局规则用 :not(...) 排除了它们，这里补齐） */
  .entry-result .scan-table tr.scan-row.scan-empty > td,
  .entry-result .scan-table tr.scan-row.scan-missing > td {
    display: inline-block; padding: 0; font-size: 13px; border: none; vertical-align: middle;
  }
  .entry-result .scan-table tr.scan-row.scan-empty,
  .entry-result .scan-table tr.scan-row.scan-missing { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; }
  /* ① 只读序号（不再是输入框） */
  .entry-result .scan-table tr.scan-row.scan-empty > td.seq-td,
  .entry-result .scan-table tr.scan-row.scan-missing > td.seq-td {
    order: 10; flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
    min-width: 22px; min-height: 22px; color: var(--muted); font-size: 12px; font-weight: 500;
  }
  /* ② 指标名称 + 英文名：同占第一行，纵向两格，各占满宽（对齐 image#1 的两个全宽框） */
  .entry-result .scan-table tr.scan-row.scan-empty > td:nth-child(2),
  .entry-result .scan-table tr.scan-row.scan-missing > td:nth-child(2) {
    order: 20; flex: 1 1 auto; min-width: 0; font-weight: 600;
    display: flex; flex-direction: column; gap: 4px;
  }
  .entry-result .scan-table tr.scan-row.scan-empty > td:nth-child(2) > input,
  .entry-result .scan-table tr.scan-row.scan-missing > td:nth-child(2) > input { width: 100%; min-width: 0; }
  /* 第一行结束 → 数值/单位/参考范围换行 */
  .entry-result .scan-table tr.scan-row.scan-empty::before,
  .entry-result .scan-table tr.scan-row.scan-missing::before {
    content: ''; order: 35; flex: 1 1 100%; height: 0;
  }
  /* ③ 数值 / 单位 / 下限 ~ 上限 —— 宽度见文件中部那条 (0,6,2) 的 v357 宽度规则
     （这里不再重复写 width：本段选择器比那条少一个 `:nth-child()`，写了也会被压掉，只会变成死代码） */
  .entry-result .scan-table tr.scan-row.scan-empty > td:nth-child(3),
  .entry-result .scan-table tr.scan-row.scan-missing > td:nth-child(3) { order: 40; flex: 0 0 auto; min-width: 0; }
  .entry-result .scan-table tr.scan-row.scan-empty > td:nth-child(4),
  .entry-result .scan-table tr.scan-row.scan-missing > td:nth-child(4) { order: 50; flex: 0 0 auto; min-width: 0; }
  .entry-result .scan-table tr.scan-row.scan-empty > td:nth-child(5),
  .entry-result .scan-table tr.scan-row.scan-missing > td:nth-child(5) { order: 60; flex: 0 0 auto; min-width: 0; white-space: nowrap; }
  /* 第二行结束 → 按钮换到右下角独占一行 */
  .entry-result .scan-table tr.scan-row.scan-empty::after,
  .entry-result .scan-table tr.scan-row.scan-missing::after {
    content: ''; order: 65; flex: 1 1 100%; height: 0;
  }
  /* ④ 完成（primary 青绿，= image#1 的「完成」）/ ❌（红底白叉，见 .act-del） */
  .entry-result .scan-table tr.scan-row.scan-empty > td:nth-child(6),
  .entry-result .scan-table tr.scan-row.scan-missing > td:nth-child(6) {
    order: 70; flex: 0 0 auto; margin-left: auto; align-self: flex-end;
  }
  .entry-result .scan-table tr.scan-row.scan-empty .scan-empty-save,
  .entry-result .scan-table tr.scan-row.scan-missing .scan-miss-save { min-height: 30px; padding: 4px 16px; font-size: 13px; }
  .entry-result .scan-table tr.scan-row.scan-empty > td.scan-actions,
  .entry-result .scan-table tr.scan-row.scan-missing > td.scan-actions {
    order: 75; flex: 0 0 auto; align-self: flex-end; display: inline-flex; gap: 6px;
  }
  /* ⑤ 末行的说明（「⚠ 图片漏识别」）在整行最下、虚线分隔，不挤占输入框 */
  .entry-result .scan-table tr.scan-row.scan-empty > td.raw,
  .entry-result .scan-table tr.scan-row.scan-missing > td.raw {
    order: 80; display: block; flex: 1 1 100%;
    font-size: 11.5px; line-height: 1.35; color: var(--muted);
    margin-top: 2px; padding-top: 4px; border-top: 1px dashed var(--line);
  }
  /* 待填行的输入框圆角跟 image#1 一致（原 6px 略方，image#1 是明显圆角） */
  .entry-result .scan-table tr.scan-row.scan-empty input,
  .entry-result .scan-table tr.scan-row.scan-missing input { border-radius: 8px; }

  /* 🆕 v379：关闭 × 在**手机端卡片**里移到右上角（用户点名的位置：「右上角需要有个白色的×」）。
     ── 桌面端不是卡片、没有"右上角"这个概念 ⇒ 只在 ≤640px 这一档移位；
        桌面端它留在操作列里，与 ❌ 并排（见 js/app.js 的两处模板）。
     ── 参照物必须是 **tr** 而不是 td：`td.scan-actions` 在卡片布局里是 flex item，
        若把它设为定位祖先，× 会贴着这一格而不是整张卡。
        故 tr 加 `position:relative`（flex 容器上加 relative 是合法的，绝对定位子元素仍以它为准），
        再把 td 显式置回 static，确保 × 的定位参照是**整张卡片**。
     ── z-index:2：× 要压在卡片里的输入框之上（否则输入框的 1px 边框会盖住它的一角）。 */
  .entry-result .scan-table tr.scan-row.scan-empty,
  .entry-result .scan-table tr.scan-row.scan-missing { position: relative; }
  .entry-result .scan-table tr.scan-row.scan-empty > td.scan-actions,
  .entry-result .scan-table tr.scan-row.scan-missing > td.scan-actions { position: static; }
  .entry-result .scan-table tr.scan-row.scan-empty > td.scan-actions .scan-dismiss,
  .entry-result .scan-table tr.scan-row.scan-missing > td.scan-actions .scan-dismiss {
    position: absolute; top: 6px; right: 6px; margin: 0; z-index: 2;
  }
}

/* ============================================================================
   🆕 v357：长按拖拽排序的视觉与触摸行为（用户第 3 条）
   ----------------------------------------------------------------------------
   ★ 触摸端为什么必须显式写 `-webkit-touch-callout` / `user-select:none`：
     长按 380ms 后 Android 默认会进入「文本选择」并弹上下文菜单，手指一动选中的文本跟着跑，
     视觉上完全看不出在拖行。二者只在**拖拽进行中**（`.drag-live`）生效，
     不影响平时长按选中表格里的文字（用户核对原始文本时要用）。
   ★ 为什么 `.dragging` 要高 z-index + 阴影 + outline：
     行的重排是**真移动 DOM**（不是浮层克隆），相邻行会立刻让位 ⇒ 必须让被拖的那一行
     明显「浮在上面」，否则用户分不清哪一行在跟着手指走。
   ============================================================================ */
.entry-ind-win.drag-live { user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }
.entry-result .scan-table tr.scan-row.dragging {
  position: relative; z-index: 3;
  background: #eaf3ff; border-color: var(--primary);
  outline: 2px solid var(--primary); outline-offset: -2px;
  box-shadow: 0 6px 18px rgba(20, 40, 60, .28);
  opacity: .97;
}
html[data-theme="dark"] .entry-result .scan-table tr.scan-row.dragging {
  background: #1d3346; border-color: #14a0a8; outline-color: #14a0a8;
}

/* ══ 🆕 v359：「指标识别结果」标题栏工具组（📄 / ＋ / ?）跟随全族缩到 20×20 ══
   用户原话：「软件中的❌太大，和缩略图上的❌要尺寸一致…设置、问号都要改成一样的大小」。
   ⚠️⚠️ 尺寸必须写在**本文件**里，不能只写在 css/styles.css —— index.html 里 scan.css 在
      styles.css **之后**加载，本文件的 `.entry-bar-tools .entry-ico`（0,2,0）会把 styles.css 里
      同特异性的 `.entry-bar-tools .raw-ocr-help`（0,2,0）压掉，`.raw-ocr-help`（0,1,0）更压不动。
      v359 第一版就只改了 styles.css ⇒ e2e E6 实测 `raw-ocr-help` 仍是 26×26/14px（假修）。
      这条与上面 v357 注释里写的坑是同一个，两个方向都踩过了。 */
.entry-bar-tools .entry-ico {
  /* 🆕 v417：按新统一口径回到 26×26（用户 2026-09-25「右上角图标统一大小」，见 styles.css v417 段） */
  width: 26px; height: 26px; min-width: 26px; min-height: 26px;
  font-size: 15px;   /* ＋ 的字形 */
}
.entry-bar-tools .entry-ico.raw-ocr-help { font-size: 14px; }   /* 「?」与全软件问号族同字号 */

/* ══════════════════════════════════════════════════════════════════════════
   🆕 v361（用户 2026-09-17 需求 1）：
     「照片识别结果窗口还有2层窗口，大窗口套着小窗口，可以去掉一层，把窗口自适应放大到合适的尺寸；
       指标识别结果原来是套了3层窗口的，现在独立成一个窗口以后，指标窗口也可以适当横向拉宽。」

   ── 先取证再动手（_probe_scan_nest.js，390×844 / 768×1024 / 1280×900 三档实测）──
   照片链路「可见窗口层」= 3：.entry-img-wrap（内层框） → .scan-card-photo（卡片） → 页面 .card
   指标链路「可见窗口层」= 2：.scan-card-ind（卡片） → 页面 .card
   ⇒ 用户数的「大窗口套着小窗口」= **.scan-card-photo 里又套了一层自带 border + 圆角的 .entry-img-wrap**。
     v357 已把外框从 .scan-entry 移到 .scan-card，这一层就成了纯叠加（卡片本身已经是窗口）。
   还有一条铁证：手机上这个内框的圆角实测是 `0px 0px 12px 12px`（平顶圆底）——
     来源是 styles.css:935 `.scan-entry-head + .entry-img-wrap{border-radius:0 0 12px 12px}`，
     v218 时代它是「标题条 + 图」两段式窗口的**下半截**，上两角故意切平去接标题条。
     v357 之后标题已在卡片头内、与图之间还有 8px 间距 ⇒ 平顶圆底的内框必然看着像「套娃」。

   ── 做法一：内层框只保留「滚动容器」职责，去掉窗口外观；高度改为自适应内容 ──
   `.entry-img-wrap` 原来固定高 320px（≤640）/ 420px（桌面）+ 内滚 —— 只看到 A4 检验单的上半截。
   用户要的「自适应放大到合适的尺寸」= 框高随图走，超高才在框内滚动（max-height 封顶）。 */
.entry-img-wrap {
  border: none;
  background: transparent;
  border-radius: 0;
  height: 420px;   /* 网页/exe 恢复固定高 420px（手机端在下方 @media 内保持自适应） */
  max-height: 60vh;   /* 封顶：超长图（手机长截图）仍可在框内滚动，不会把整页撑到失控 */
  overflow: auto;
  margin-bottom: 0;
}
/* 大图固定高仅对网页/exe（桌面）生效；手机端保持 v361 的自适应高（框高随图走） */
@media (max-width: 640px) {
  .entry-img-wrap { height: auto; }
}
/* 🆕 v499（用户第 9 项）：手机端「照片识别结果」窗口改为**正方形**（高 = 宽）。
   ⚠️ 这条必须写进本文件（scan.css 在 styles.css **之后**加载），不能只写在 styles.css：
      本文件 L532 有一条 `@media(max-width:640px){ .entry-img-wrap{ height:320px; max-height:320px } }`，
      与上面 L1580 一样是 (0,1,0)、同样在 ≤640 媒体查询里。只在 styles.css 写 `max-height:none`
      的话，本文件这条后加载的同特异性规则会把 `max-height` 抢回去 ⇒ 方框在宽度 >320 的手机上
      被削成 320 高（例如宽 384 ⇒ 384×320，肉眼仍是个“矮胖”矩形，不是正方形）。
      ⇒ 写在下面、media 块之后，靠“同特异性取后写”把 height 与 max-height 一起复位。
   `overflow:auto` 必须保留（不能跟 ≤767 的 `visible` 一样）：窗口变方后竖图必然超出，
      不滚动查看的话下半张照片会直接跑出窗口、看起来像被裁掉了。 */
@media (max-width: 640px) {
  .entry-img-wrap { aspect-ratio: 1 / 1; height: auto; max-height: none; overflow: auto; }
}
/* ⚠️ 注意两条**特异性不低**的旧规则，必须显式压掉（同特异性靠本文件在 styles.css 之后加载来决胜）：
      · styles.css:935  `.scan-entry-head + .entry-img-wrap`（0,2,0）→ 不改就会留下平顶圆底
      · styles.css:432  `html[data-theme="dark"] .entry-img-wrap`（0,2,0）→ 夜间会给内框补底色，等于又套一层 */
.scan-entry-head + .entry-img-wrap { border-radius: 0; }
html[data-theme="dark"] .entry-img-wrap { background: transparent; border-color: transparent; }
/* 图片本身仍是「一张纸」，保留自己的白底/深底，不然纸面会跟卡片底糊在一起 */
.entry-img-wrap .entry-img { border-radius: 0; }

/* ── 补修（★探针第二版挖出来的）：照片卡片的**标题行**才是「套娃」的另一半 ──
   第一版探针只沿 `.entry-img-wrap` 的**祖先链**往上走，漏掉了兄弟节点 ⇒ 只看到 3 层里的 2 层。
   改量整棵子树后暴露：移动端 scan.css:929（≤640 媒体查询内）
     `.scan-entry-head { padding:10px 12px; margin-top:10px; background:var(--panel);
                         border:1px solid var(--line); border-bottom:none; border-radius:12px 12px 0 0 }`
   —— 这是 v218「标题条 + 图」两段式窗口的**上半截**（上圆角、无下边框），
      与 `.entry-img-wrap` 当年配套的 `0 0 12px 12px`（下半截）严丝合缝地拼成**一个内层窗口**。
   v357 把外框移到 `.scan-card` 之后，这上下两半失去了存在意义，合起来正是用户说的
   「大窗口套着小窗口」。指标卡那侧 v357 已经处理过（`.scan-card .entry-result-bar`），
   照片这侧当时漏了 ⇒ 这里补齐，两卡的标题行外观从此完全一致。
   ⚠️ 实测佐证：修正前 `.entry-img-wrap` 的 top = 55（= 卡片 border 1 + 标题行 10+23.3+10 + margin-top 10）。 */
.scan-card.scan-card-photo > .scan-entry-head {
  padding: 12px 12px 10px;
  margin-top: 0; margin-bottom: 0;
  background: transparent; border: none; border-radius: 0;
}

/* ── 做法二：照片卡片让图**铺到卡片内缘**，这才是用户要的「放大」──
   原来图的可用宽 = 卡片宽 − 2×border − 2×padding(12/14) − 2×内框border ⇒ 手机端仅 314px（屏宽 390）。
   去掉内框只多 2px，所以必须把卡片自己的左右内边距让给图：
     标题行单独留内边距，图片区 padding 归零（+28px，手机端 314 → 342px，+8.9%）。
   ⚠️ 卡片圆角要裁住图片的直角，否则又变成需求 2③ 那个「四角被方底盖住」的毛病 ⇒ overflow:hidden。
      安全性已核：卡片里没有被绝对定位到边界外的东西（.entry-img-hint 在 .entry-img-wrap 内部）。 */
.scan-card.scan-card-photo { padding: 0; overflow: hidden; }

/* ── 做法三：指标识别结果横向拉宽（用户「指标窗口也可以适当横向拉宽」）──
   手机端指标表的横向内缩由四段组成：页面 .card padding 14 + 卡片 border 1 + .scan-card padding 14
   + 表格行自身 `margin: 0 8px 6px`。页面级 .card 不动（避免波及其它窗口），只收卡片内这两段：
     .scan-card-ind 左右内边距 14 → 10（+8px）
     表格行左右外边距    8 → 4（+8px）
   ⇒ 首行可用宽 298 → 314px（+5.4%）。「适当」= 不超过一档，不动换行规则与任何断点。 */
.scan-card.scan-card-ind { padding-left: 10px; padding-right: 10px; }
.entry-result .scan-table tr:not(.scan-missing):not(.scan-empty) { margin-left: 4px; margin-right: 4px; }

/* ============================================================================
   🆕 v379（用户第 2/3 条）：手填卡右上角的「关闭」×
   ----------------------------------------------------------------------------
   用户原话：「右上角需要有个白色的×、点击关闭窗口」+ 答复「永久关掉该条 (推荐)」。
   两类卡（待填行 tr.scan-empty / 漏检行 tr.scan-missing）**共用这一个按钮**，
   区别只由 `data-scan-dismiss` 的值承担（`empty:<候选下标>` / `miss:<原始行号>`）。
   ── ★ 为什么白色 × 必须配深色圆底（用户只说"白色"，但纯白单摆按不出来）：
      卡片底色是 #fffbe8（淡黄），纯白 × 叠上去对比度≈1.04 —— 等于看不见。
      故给半透明深色圆底：日间 rgba(31,45,61,.55)、夜间 rgba(255,255,255,.22)。
      等效背景（圆底叠卡底）：日间 ≈ #8b939e ⇒ 白 × 对比度≈3.3；夜间 ≈ #6b6f6f ⇒ ≈5.1。
      两边都过 WCAG 1.4.11「非文本 UI 组件 ≥ 3.0」。判据由 _e2e_v379_cards.js 实测钉住。
   ── ★ 为什么不用现成的红底白叉（.act-del）：红是本项目「危险 / 删除」的语义色
      （v357 第 7 条定的「删 = 红底白叉 ❌」）。关闭**不动任何数据**，用红底会让人以为点了会丢东西。
   ── 尺寸 20×20：与 v359 统一后的那一族圆钮（缩略图 ❌ / 改 / 删 / 设置 / 问号）保持一致。 */
.scan-dismiss {
  box-sizing: border-box;
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; min-width: 20px; min-height: 20px;
  padding: 0; border: none; border-radius: 50%;
  background: rgba(31, 45, 61, .55); color: #fff;
  font-size: 15px; line-height: 1; font-weight: 400;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.scan-dismiss:hover { background: rgba(31, 45, 61, .78); }
html[data-theme="dark"] .scan-dismiss { background: rgba(255, 255, 255, .22); color: #fff; }
html[data-theme="dark"] .scan-dismiss:hover { background: rgba(255, 255, 255, .34); }

/* ══════════ 🆕 v560（用户 2026-10-28 第 1 项）：卡头刷新键与「其它窗口的刷新键」统一 ══════════
   现状差异：指标趋势页的刷新是 `.chart-gear`（**实测 26×26 正圆**，font-size 14px，
   1px 描边 + 轻投影 + 内联 SVG 环形箭头），而图片识别卡头原来是 `.btn.tiny` 里的**字符** `↻`
   加「刷新」二字（矩形胶囊 + 字体依赖）。
   统一口径：几何/描边/投影/字号/图标全部对齐 `.chart-gear` 的**实测值**（不是 CSS 里写的 34px ——
   34px 被后面一条窄屏规则收到了 26px，两端真实渲染都是 26）；但**不沿用**它的
   `position:absolute; top/right` —— 卡头是 flex 行，本键必须留在流内（在 🔍 左侧）。
   ⚠️ 图标用 `<svg width/height:1em>` ⇒ 只要钉住 font-size 就能精确控制字形大小。 */
.card-head .btn.tiny.ico-refresh {
  width: 26px; height: 26px; min-width: 26px; min-height: 26px;
  padding: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 14px; line-height: 1;
  background: rgba(255, 255, 255, .92); color: var(--ink);
  border: 1px solid var(--line); box-shadow: 0 1px 4px rgba(0, 0, 0, .12);
  transition: border-color .15s ease, color .15s ease, background .15s ease;
}
.card-head .btn.tiny.ico-refresh:hover { border-color: var(--primary); color: var(--primary); }
.card-head .btn.tiny.ico-refresh:active { background: rgba(14, 124, 134, .10); }
.card-head .btn.tiny.ico-refresh > svg { display: block; }
/* UISKILL 触控目标 ≥40×40 的折中（与 hero 里那颗主题按钮同一手法）：
   可见几何 26px（= 指标趋势页那颗刷新键的实际渲染尺寸，用户要的「一致」以实测几何为准），
   多出的 14px 放在不参与布局的伪元素上 ⇒ 命中区 40×40。
   ⚠️ 左右各 -7px 合计 14px：相邻的 🔍 钮同为 26px、卡头 gap 6px ⇒ 两者命中区各让 7px 会重叠 8px，
   故左右收窄为 -4px（命中 34 宽，仍 ≥ 系统 32px 最小触控），上下仍作 -7px 补到 40 高。 */
.card-head .btn.tiny.ico-refresh { position: relative; }
.card-head .btn.tiny.ico-refresh::after {
  content: ''; position: absolute; left: -4px; right: -4px; top: -7px; bottom: -7px;
}
/* 刷新进行中：图标匀速旋转 —— 这颗键点击后要跑「重读缩略图 + 补写数据」，
   原实现没有任何进行中反馈（用户会连点）。`prefers-reduced-motion` 下取消旋转，改降不透明度。 */
.card-head .btn.tiny.ico-refresh.is-busy { pointer-events: none; opacity: .7; }
.card-head .btn.tiny.ico-refresh.is-busy > svg { animation: ico-spin .9s linear infinite; }
@keyframes ico-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .card-head .btn.tiny.ico-refresh.is-busy > svg { animation: none; opacity: .55; }
}
html[data-theme="dark"] .card-head .btn.tiny.ico-refresh {
  background: rgba(36, 49, 64, .92); color: #e6edf3; border-color: #3b4a5c;
}
html[data-theme="dark"] .card-head .btn.tiny.ico-refresh:hover { border-color: var(--primary); color: #5fd0db; }
/* 焦点可见（键盘 Tab 必须能看出停在哪颗键上） */
.card-head .btn.tiny.ico-refresh:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
