/* ============================================================
   BTR印尼品质部检测中心 · 实验室耗材管理系统 — 样式表 v2
   ------------------------------------------------------------
   相对 v1 的改动（结构与 class 名完全兼容，JS 无需改动）：
   · 色彩：从「Material 2014 纯灰蓝」升级为有层次的品牌色阶，
           语义色改用现代色板并保证 WCAG AA 对比度。
   · 圆角：2dp 直角 → 4/6/10/14/20px 分级。
   · 阴影：MD2014 双硬阴影 → 多层柔和投影。
   · 排版：建立 9 级字阶 + 行高体系，标题/正文/辅助三级分明。
   · 状态：新增 :focus-visible 键盘焦点环（v1 完全缺失），
           hover 态补齐过渡与按压反馈。
   · 暗色：新增完整 prefers-color-scheme: dark 主题。
   · 无障碍：prefers-reduced-motion / prefers-contrast / print。

   变量分层：
     1) 原始色阶（--brand-* / --teal-* / --n-*）
     2) 语义别名（--primary / --accent / --text / --border …）
     3) 尺寸令牌（字阶 / 间距 / 圆角 / 阴影 / 动效）
   ============================================================ */

/* ============================================================
   1. 设计令牌
   ============================================================ */
:root {
  /* ---------- 品牌色阶：深海军蓝（实验室/工业的沉稳感） ---------- */
  --brand-50:  #EFF4F7;
  --brand-100: #DCE6EC;
  --brand-200: #B8CAD6;
  --brand-300: #8CA8BA;
  --brand-400: #62869C;
  --brand-500: #466A80;
  --brand-600: #365567;
  --brand-700: #2A4150;   /* ← 主色 */
  --brand-800: #1F313D;
  --brand-900: #16242D;

  /* ---------- 强调色：Teal（沿用品牌基因，提亮一档） ---------- */
  --teal-50:  #E6FBF7;
  --teal-100: #C3F5EB;
  --teal-200: #8FEADC;
  --teal-300: #4FD9C6;
  --teal-400: #22C3AE;
  --teal-500: #0FA795;   /* ← 强调色 */
  --teal-600: #0C8778;
  --teal-700: #0D6B60;

  /* ---------- 中性阶 ---------- */
  --n-0:   #FFFFFF;
  --n-25:  #FBFCFD;
  --n-50:  #F4F7F9;
  --n-100: #EDF1F4;
  --n-150: #E3E9EE;
  --n-200: #D5DEE5;
  --n-300: #B9C7D1;
  --n-400: #8FA2AF;
  --n-500: #6B7F8D;
  --n-600: #51646F;
  --n-700: #3D4E58;
  --n-800: #2A3841;
  --n-900: #1A242B;

  /* ---------- 语义别名（组件只使用这一层） ---------- */
  --bg:            var(--n-50);
  --bg-elevated:   var(--n-0);
  --surface:       var(--n-0);
  --surface-2:     var(--n-25);
  --surface-3:     var(--n-100);

  --text:          #16242D;   /* 15.8:1 */
  --text-2:        #445A67;   /*  7.4:1 */
  --text-3:        #5E7280;   /*  5.2:1 */
  --text-4:        #93A3AE;   /*  2.9:1，仅装饰 */

  --border:        #E1E8ED;
  --border-strong: #C3D0D9;

  --primary:       var(--brand-700);
  --primary-dark:  var(--brand-800);
  --primary-deep:  var(--brand-900);
  --primary-soft:  var(--brand-50);
  --primary-line:  var(--brand-200);

  --accent:        var(--teal-500);
  --accent-dark:   var(--teal-600);
  --accent-light:  var(--teal-300);
  --accent-soft:   var(--teal-50);

  /* ---------- 语义色（AA 达标） ---------- */
  --success:      #047857;
  --success-text: #065F46;
  --success-soft: #ECFDF5;
  --warn:         #B45309;
  --warn-text:    #92400E;
  --warn-soft:    #FFFBEB;
  --danger:       #DC2626;
  --danger-text:  #991B1B;
  --danger-soft:  #FEF2F2;
  --info:         #1D4ED8;
  --info-text:    #1E40AF;
  --info-soft:    #EFF6FF;

  /* ---------- 预警等级 ---------- */
  --lvl-urgent:        var(--danger);
  --lvl-urgent-soft:   var(--danger-soft);
  --lvl-warning:       var(--warn);
  --lvl-warning-soft:  var(--warn-soft);
  --lvl-watch:         var(--brand-500);
  --lvl-watch-soft:    var(--brand-50);

  /* ---------- 图表序列色（与 chart.js PALETTE 对齐） ---------- */
  --c1: #2A4150; --c2: #0FA795; --c3: #7C3AED; --c4: #D97706;
  --c5: #DC2626; --c6: #1D4ED8; --c7: #4D7C0F; --c8: #A21CAF;
  --c9: #0E7490; --c10: #EA580C;

  /* ---------- 字阶 ----------
     2026-10-08：下限整体抬高一级（10.5/11.5/12.5/13.5 → 11.5/12.5/13/14）。
     无头探针实测原字阶下目录页有 579 个元素是 10.5px、752 个是 11.5px ——
     正文主体低于 12px 是「字体拥挤」观感的根源，印尼语拼写又比中文长，更吃亏。
     改这一组数会全站生效，动之前先用 tools/smoke-test.js + 探针复核一遍。 */
  --fs-2xs: 11.5px;
  --fs-xs:  12.5px;
  --fs-sm:  13px;
  --fs-md:  14px;
  --fs-base:14px;
  --fs-lg:  16px;
  --fs-xl:  18px;
  --fs-2xl: 21px;
  --fs-3xl: 26px;
  --fs-4xl: 32px;

  --lh-tight: 1.25;
  --lh-snug:  1.4;
  --lh-base:  1.55;
  --lh-loose: 1.7;

  --fw-normal: 400;
  --fw-medium: 500;
  --fw-semi:   600;
  --fw-bold:   700;

  /* ---------- 间距（4px 基准） ---------- */
  --sp-1:  4px;  --sp-2:  8px;  --sp-3:  12px; --sp-4:  16px;
  --sp-5:  20px; --sp-6:  24px; --sp-7:  32px; --sp-8:  40px;
  --sp-9:  48px; --sp-10: 64px;

  /* ---------- 圆角 ---------- */
  --r-xs:   4px;
  --r-sm:   6px;
  --r-md:   10px;
  --r-lg:   14px;
  --r-xl:   20px;
  --r-full: 999px;

  /* v1 兼容别名 */
  --radius-s: var(--r-sm);
  --radius:   var(--r-md);
  --radius-l: var(--r-lg);

  /* ---------- 柔和投影 ---------- */
  --sh-xs: 0 1px 2px rgba(16, 36, 45, .05);
  --sh-sm: 0 1px 3px rgba(16, 36, 45, .07), 0 1px 2px rgba(16, 36, 45, .04);
  --sh-md: 0 4px 10px -2px rgba(16, 36, 45, .08), 0 2px 5px -2px rgba(16, 36, 45, .05);
  --sh-lg: 0 12px 22px -6px rgba(16, 36, 45, .10), 0 5px 10px -5px rgba(16, 36, 45, .05);
  --sh-xl: 0 24px 44px -12px rgba(16, 36, 45, .16), 0 10px 18px -10px rgba(16, 36, 45, .07);

  /* v1 兼容别名 */
  --elev-1: var(--sh-sm);
  --elev-2: var(--sh-md);
  --elev-3: var(--sh-lg);
  --elev-4: var(--sh-lg);
  --elev-6: var(--sh-xl);
  --shadow-1: var(--sh-sm);
  --shadow-2: var(--sh-md);
  --shadow-3: var(--sh-xl);

  /* ---------- 动效 ---------- */
  --ease:         cubic-bezier(.4, 0, .2, 1);
  --ease-out:     cubic-bezier(0, 0, .2, 1);
  --ease-in:      cubic-bezier(.4, 0, 1, 1);
  --ease-spring:  cubic-bezier(.34, 1.4, .64, 1);
  --dur-fast:  .14s;
  --dur:       .2s;
  --dur-slow:  .32s;

  --appbar-h: 64px;
  --nav-h:    64px;
  --maxw:     1280px;

  /* 图片占位底色 */
  --img-bg: linear-gradient(160deg, #F7FAFB 0%, #EAF0F4 100%);
}

/* ============================================================
   2. 暗色主题
   ============================================================ */
@media (prefers-color-scheme: dark) {
  :root {
    --bg:          #0E161B;
    --bg-elevated: #16212A;
    --surface:     #16212A;
    --surface-2:   #1B2831;
    --surface-3:   #223039;

    --text:        #E8EEF2;
    --text-2:      #B6C4CE;
    --text-3:      #8B9BA7;
    --text-4:      #64757F;

    --border:        #26343E;
    --border-strong: #354753;

    --primary:       #7FA6BC;
    --primary-dark:  #A3C0D2;
    --primary-deep:  #C4D9E5;
    --primary-soft:  #1D2C36;
    --primary-line:  #3A5265;

    --accent:      #2DD4BF;
    --accent-dark: #5EE7D4;
    --accent-light:#8FF0E2;
    --accent-soft: #12332F;

    --success:      #34D399; --success-text: #6EE7B7; --success-soft: #0D2E24;
    --warn:         #FBBF24; --warn-text:    #FCD34D; --warn-soft:    #33240A;
    --danger:       #F87171; --danger-text:  #FCA5A5; --danger-soft:  #3A1616;
    --info:         #60A5FA; --info-text:    #93C5FD; --info-soft:    #12233F;

    --lvl-urgent-soft:  #3A1616;
    --lvl-warning-soft: #33240A;
    --lvl-watch-soft:   #1D2C36;

    --c1:#7FA6BC; --c2:#2DD4BF; --c3:#A78BFA; --c4:#FBBF24;
    --c5:#F87171; --c6:#60A5FA; --c7:#A3E635; --c8:#E879F9;
    --c9:#22D3EE; --c10:#FB923C;

    --sh-xs: 0 1px 2px rgba(0, 0, 0, .34);
    --sh-sm: 0 1px 3px rgba(0, 0, 0, .40), 0 1px 2px rgba(0, 0, 0, .28);
    --sh-md: 0 4px 10px -2px rgba(0, 0, 0, .46), 0 2px 5px -2px rgba(0, 0, 0, .32);
    --sh-lg: 0 12px 22px -6px rgba(0, 0, 0, .52), 0 5px 10px -5px rgba(0, 0, 0, .36);
    --sh-xl: 0 24px 44px -12px rgba(0, 0, 0, .60), 0 10px 18px -10px rgba(0, 0, 0, .40);

    --img-bg: linear-gradient(160deg, #1A252D 0%, #131C23 100%);
    color-scheme: dark;
  }
}

/* ============================================================
   3. 基础重置
   ============================================================ */
* { box-sizing: border-box; }

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

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
               "Helvetica Neue", "PingFang SC", "Hiragino Sans GB",
               "Microsoft YaHei", "Noto Sans SC", sans-serif;
  font-size: var(--fs-base);
  line-height: var(--lh-base);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-tap-highlight-color: transparent;
}

body {
  min-height: 100vh; min-height: 100dvh;
  overflow-x: clip;
}

/* 数字统一等宽，表格/价格/统计不再跳动 */
table, .price, .stat-num, .mono, .qty, .amt, .cnt,
.delta, .rank-val, .val, .nav-badge {
  font-variant-numeric: tabular-nums;
}

button {
  font-family: inherit; font-size: inherit; cursor: pointer;
  touch-action: manipulation;
  border: 0; background: none; color: inherit;
}
input, select, textarea { font-family: inherit; font-size: inherit; color: inherit; }
a { color: var(--info); text-decoration: none; }
[hidden] { display: none !important; }
::selection { background: var(--accent-soft); color: var(--accent-dark); }

/* 滚动条 */
* { scrollbar-width: thin; scrollbar-color: var(--n-300) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--n-300); border-radius: var(--r-full);
  border: 3px solid transparent; background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: var(--n-400); background-clip: content-box; }

/* 键盘焦点环 —— v1 完全没有，键盘用户等于瞎子 */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}
.topbar :focus-visible { outline-color: #fff; }

.only-mobile { display: none !important; }

/* ============================================================
   4. 通用原子
   ============================================================ */
/* ---------- 图标（单色线性 SVG，图形见 js/icons.js） ----------
   全站不再用 emoji 当图标：emoji 的字形/字重/基线由系统字体决定，
   Windows / Android / iOS 三端各不相同；彩色 emoji 压在深色顶栏上也很跳。 */
.ico-wrap {
  display: inline-flex; align-items: center; justify-content: center;
  flex: none; vertical-align: -.18em;
}
.ico-wrap > svg { display: block; }
.ico-wrap:not(:only-child) { margin-right: 5px; }
/* 下面这些容器自己就有 gap，图标不用再补间距（否则会出现双倍空隙） */
.btn > .ico-wrap, .nav-item > .ico-wrap, .tab > .ico-wrap,
.chip > .ico-wrap, .seg-btn > .ico-wrap, .list-row > .ico-wrap,
.badge > .ico-wrap, .stepper button > .ico-wrap, .pcard-actions > .ico-wrap { margin-right: 0; }

.icon-btn {
  width: 40px; height: 40px; flex-shrink: 0; padding: 0;
  border-radius: var(--r-full);
  color: var(--text-2);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 15px; line-height: 1;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease),
              transform var(--dur-fast) var(--ease);
  position: relative; overflow: hidden;
}
.icon-btn:hover { background: rgba(15, 167, 149, .10); color: var(--accent-dark); }
.icon-btn:active { transform: scale(.92); }

.topbar-menu::before {
  content: ""; width: 18px; height: 2px; border-radius: 2px; background: currentColor;
  box-shadow: 0 -6px 0 currentColor, 0 6px 0 currentColor;
}
.drawer-close { display: none; width: 36px; height: 36px; font-size: 14px; margin-left: auto; }

.scrim {
  position: fixed; inset: 0; z-index: 120; display: none;
  background: rgba(8, 18, 24, .55);
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
  opacity: 0; visibility: hidden;
  transition: opacity var(--dur) var(--ease), visibility var(--dur) var(--ease);
}
.scrim.is-on { opacity: 1; visibility: visible; }

.spin {
  width: 15px; height: 15px; flex-shrink: 0;
  border: 2px solid rgba(255, 255, 255, .38);
  border-top-color: currentColor; border-radius: 50%;
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ============================================================
   5. 启动页
   ============================================================ */
.boot {
  position: fixed; inset: 0; z-index: 900;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: var(--sp-5); background: var(--surface);
}
.boot-mark {
  width: 68px; height: 68px; border-radius: var(--r-lg);
  background: linear-gradient(145deg, var(--brand-600), var(--brand-800));
  color: #fff; font-size: 19px; font-weight: var(--fw-semi); letter-spacing: .6px;
  display: flex; align-items: center; justify-content: center;
  box-shadow: var(--sh-lg);
  animation: bootPulse 2s var(--ease) infinite;
}
@keyframes bootPulse {
  0%, 100% { transform: scale(1);    box-shadow: var(--sh-lg); }
  50%      { transform: scale(1.04); box-shadow: var(--sh-xl); }
}
.boot-text { color: var(--text-3); font-size: var(--fs-md); }
.boot-bar {
  width: 208px; height: 4px; border-radius: var(--r-full);
  background: var(--surface-3); overflow: hidden;
}
.boot-bar i {
  display: block; height: 100%; width: 40%; border-radius: var(--r-full);
  background: linear-gradient(90deg, var(--accent), var(--accent-light));
  animation: bootSlide 1.15s var(--ease) infinite;
}
@keyframes bootSlide { 0% { transform: translateX(-110%); } 100% { transform: translateX(280%); } }

.boot.is-error .boot-bar { display: none; }
/* 启动失败时会把诊断信息（HTTP 状态码 / 接口路径 / 服务端原话）一起显示出来，
   所以这里要放宽宽度、允许换行 —— 否则被 460px 卡断，等于又看不到原因。 */
.boot.is-error .boot-text {
  color: var(--danger); max-width: 460px; text-align: center;
  max-width: min(680px, 88vw);
  line-height: 1.65;
  overflow-wrap: anywhere;
}

/* ============================================================
   6. 消息 / 提示条
   ============================================================ */
.auth-msg {
  margin: 0; padding: 11px 14px; border-radius: var(--r-sm);
  font-size: var(--fs-md);
  background: var(--danger-soft); color: var(--danger-text);
  border-left: 3px solid var(--danger);
}
.auth-msg.is-ok { background: var(--success-soft); color: var(--success-text); border-left-color: var(--success); }

.notice {
  display: flex; gap: var(--sp-3); align-items: flex-start;
  padding: 13px 16px; border-radius: var(--r-md);
  font-size: var(--fs-md); margin-bottom: var(--sp-4);
  line-height: var(--lh-base);
  border: 1px solid transparent;
}
.notice i { font-style: normal; flex-shrink: 0; line-height: 1.5; display: inline-flex; margin-top: 2px; }
.notice-amber { background: var(--warn-soft);    color: var(--warn-text);    border-color: rgba(180, 83, 9, .16); }
.notice-red   { background: var(--danger-soft);  color: var(--danger-text);  border-color: rgba(220, 38, 38, .16); }
.notice-blue  { background: var(--info-soft);    color: var(--info-text);    border-color: rgba(29, 78, 216, .14); }
.notice-green { background: var(--success-soft); color: var(--success-text); border-color: rgba(4, 120, 87, .16); }

/* ============================================================
   7. 表单
   ============================================================ */
.field { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.field-label {
  font-size: var(--fs-xs); color: var(--text-3);
  font-weight: var(--fw-medium); letter-spacing: .3px;
}
/* 字段下方的补充说明（如价格字段的「≈ ¥N 人民币」实时换算）。
   空内容时它不占位：:empty 直接收缩，避免表单里到处多出 4px 的空白行。 */
.field-hint {
  font-size: var(--fs-2xs); color: var(--text-4); line-height: 1.45;
}
.field-hint:empty { display: none; }
/* 三种控件统一成同一套「浅框盒」范式。
   以前 input/select 是裸下划线、textarea 却是有底有框的盒子 ——
   同一张表单里两种范式，看着就像没做完。 */
.field input, .field select, .field textarea {
  width: 100%;
  padding: 8px 11px; min-height: 38px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  background: var(--surface); color: var(--text); outline: none;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              background-color var(--dur) var(--ease);
}
.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--n-400); }
.field input:focus, .field select:focus, .field textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(15, 167, 149, .14);
}
.field select {
  cursor: pointer;
  appearance: none; -webkit-appearance: none;
  padding-right: 30px;
  background-image: linear-gradient(45deg, transparent 50%, var(--text-3) 50%),
                    linear-gradient(135deg, var(--text-3) 50%, transparent 50%);
  background-position: calc(100% - 16px) 52%, calc(100% - 11px) 52%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.field textarea { resize: vertical; min-height: 78px; line-height: var(--lh-base); }
.field input::placeholder, .field textarea::placeholder { color: var(--text-4); }

.field input[type="checkbox"], .field input[type="radio"] {
  width: 18px; height: 18px; min-height: 0; flex: none; padding: 0; margin: 0;
  border: 0; border-radius: 0; box-shadow: none;
  accent-color: var(--accent); cursor: pointer;
}
.field-row { display: flex; gap: var(--sp-4); }
.field-row input { flex: 1; min-width: 0; }
.field-row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }

.fold-head {
  display: flex; align-items: center; gap: var(--sp-2); width: 100%;
  margin: var(--sp-5) 0 var(--sp-2); padding: 12px 0 0;
  border: 0; border-top: 1px solid var(--border); background: none;
  font: inherit; color: var(--text); text-align: left; cursor: pointer;
  transition: color var(--dur) var(--ease);
}
.fold-head:hover .fold-title { color: var(--accent-dark); }
.fold-title { font-size: var(--fs-md); font-weight: var(--fw-semi); letter-spacing: .2px; }
.fold-hint { margin-left: auto; font-size: var(--fs-xs); color: var(--text-3); white-space: nowrap; }
.fold-arrow { font-size: var(--fs-xs); color: var(--text-3); flex: none; transition: transform var(--dur) var(--ease); }
.fold-head.is-open .fold-arrow { transform: rotate(90deg); }

/* ============================================================
   8. 按钮
   ============================================================ */
.btn {
  border-radius: var(--r-sm);
  padding: 0 var(--sp-4);
  font-size: var(--fs-md); font-weight: var(--fw-medium); letter-spacing: .2px;
  white-space: nowrap;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 38px;
  transition: background-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              color var(--dur) var(--ease), transform var(--dur-fast) var(--ease),
              box-shadow var(--dur) var(--ease);
  position: relative; overflow: hidden;
}
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn:disabled { opacity: .45; cursor: not-allowed; box-shadow: none; }

.btn-primary { background: var(--primary); color: #fff; box-shadow: var(--sh-sm); }
.btn-primary:hover:not(:disabled) { background: var(--primary-dark); box-shadow: var(--sh-md); }

.btn-ghost { background: transparent; color: var(--primary); }
.btn-ghost:hover:not(:disabled) { background: var(--primary-soft); color: var(--primary-dark); }

.btn-line {
  background: var(--surface); color: var(--primary);
  box-shadow: inset 0 0 0 1px var(--border-strong);
}
.btn-line:hover:not(:disabled) {
  background: var(--primary-soft); color: var(--primary-dark);
  box-shadow: inset 0 0 0 1px var(--primary-line);
}
/* 线框按钮的「已开启」态（如首页的「仅看预警」筛选） */
.btn-line.is-on,
.btn-line.is-on:hover:not(:disabled) {
  background: var(--warn-soft); color: var(--warn-text);
  box-shadow: inset 0 0 0 1px var(--warn);
}

.btn-danger {
  background: transparent; color: var(--danger);
  box-shadow: inset 0 0 0 1px rgba(220, 38, 38, .38);
}
.btn-danger:hover:not(:disabled) { background: var(--danger-soft); box-shadow: inset 0 0 0 1px var(--danger); }

.btn-block { width: 100%; padding: 12px; font-size: var(--fs-base); }
.btn-sm { padding: 0 13px; font-size: var(--fs-sm); min-height: 34px; }
.btn-xs { padding: 0 11px; font-size: var(--fs-xs); min-height: 28px; }

.seg {
  display: flex; gap: 2px; padding: 3px;
  background: var(--surface-3); border-radius: var(--r-sm);
  /* 宽度一律由内容决定，上限只在这里收；不要在 JS 里写死 width（会跟文字长度脱钩）。
     窄到放不下时整条横滑，而不是把按钮压扁把标签裁掉。 */
  max-width: 100%; overflow-x: auto;
  scrollbar-width: none;
}
.seg::-webkit-scrollbar { display: none; }
.seg-btn {
  /* flex-shrink 必须为 0：下面有 overflow:hidden，而 flex 项的 min-width:auto
     在 overflow 非 visible 时会退化成 0，于是「全部耗材合计」这种长标签会被压到
     比文字还窄、直接裁字（实测 1338px 视口下仍越界 15px）。
     flex:1 0 auto 之后，每个按钮最窄也是「文字 + 左右内边距」，有富余再等分。 */
  flex: 1 0 auto; padding: 6px 12px; border-radius: var(--r-xs);
  font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--text-2);
  min-height: 30px; white-space: nowrap;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
  position: relative; overflow: hidden;
}
.seg-btn:hover { color: var(--text); }
.seg-btn.is-active {
  background: var(--surface); color: var(--primary);
  box-shadow: var(--sh-xs); font-weight: var(--fw-semi);
}
.seg.is-sm { padding: 3px; }
.seg.is-sm .seg-btn { padding: 5px 10px; font-size: var(--fs-xs); min-height: 26px; }

/* 语言切换段：宽度由这里统一控制，不要在 JS 里写死（窄屏要能收缩，否则只能被藏掉） */
.seg.lang-seg { min-width: 132px; }
/* 这里不再覆盖 .seg-btn 的 flex —— 继承 flex:1 0 auto，按钮最窄也是「文字+内边距」，
   ≤640 时 min-width 归零，整段自然收到内容宽（正是上面那句注释想要的行为）。 */

/* ============================================================
   9. 外壳布局
   ============================================================ */
.shell { min-height: 100vh; min-height: 100dvh; display: flex; }

.sidebar {
  width: 248px; flex-shrink: 0;
  background: var(--surface);
  border-right: 1px solid var(--border);
  display: flex; flex-direction: column;
  position: sticky; top: 0;
  height: 100vh; height: 100dvh;
  z-index: 50;
}

.brand {
  height: var(--appbar-h); flex-shrink: 0;
  display: flex; align-items: center; gap: 11px;
  padding: 0 var(--sp-4);
  background: linear-gradient(135deg, var(--brand-700) 0%, var(--brand-800) 100%);
  color: #fff;
  position: relative;
}
.brand::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(120% 100% at 0% 0%, rgba(255,255,255,.10), transparent 58%);
}
.brand-mark {
  width: 38px; height: 38px; border-radius: var(--r-sm); flex-shrink: 0;
  background: rgba(255, 255, 255, .16);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .20);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 12.5px; font-weight: var(--fw-bold); letter-spacing: .4px;
  position: relative; z-index: 1;
}
.brand-text {
  display: flex; flex-direction: column; line-height: 1.3;
  min-width: 0; position: relative; z-index: 1;
}
.brand-text b {
  font-size: var(--fs-base); font-weight: var(--fw-semi); letter-spacing: .2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* 品牌副行（英文名）允许折成两行：印尼语全名 35 个字符，在 167px 里一行放不下，
   原来只能被省略号截成「Pusat Pengujian Mutu BTR Ind…」，与上一行内容几乎重复又都读不全。 */
.brand-text i {
  font-style: normal; font-size: var(--fs-2xs); line-height: 1.25;
  color: rgba(255, 255, 255, .70); letter-spacing: .3px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

.nav {
  flex: 1; padding: var(--sp-3) var(--sp-3) var(--sp-2);
  display: flex; flex-direction: column; gap: 2px;
  overflow-y: auto; overscroll-behavior: contain;
}
.nav-item {
  display: flex; align-items: center; gap: 11px;
  padding: 0 11px; border-radius: var(--r-sm);
  color: var(--text-2);
  font-size: var(--fs-md); font-weight: var(--fw-medium);
  text-align: left; width: 100%; min-height: 44px;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
  position: relative; overflow: hidden;
}
.nav-item:hover { background: var(--primary-soft); color: var(--primary-dark); }
.nav-item.is-active {
  background: var(--primary-soft);
  color: var(--primary); font-weight: var(--fw-semi);
}
.nav-item.is-active::before {
  content: ""; position: absolute; left: 0; top: 9px; bottom: 9px;
  width: 3px; border-radius: 0 3px 3px 0; background: var(--accent);
}
/* 图标尺寸由 Icons.el(name, 19) 决定，这里只管占位宽度与对齐。
   以前悬停会把 emoji 放大 1.12 倍 —— 那是个很「廉价」的效果，已去掉，
   悬停时让图标跟着文字一起变色即可（颜色继承自 .nav-item）。 */
.nav-item .nav-ico { width: 22px; justify-content: center; }
.nav-item .nav-badge {
  margin-left: auto;
  background: var(--danger); color: #fff;
  font-size: var(--fs-2xs); font-weight: var(--fw-semi);
  min-width: 19px; height: 19px; border-radius: var(--r-full);
  padding: 0 6px;
  display: inline-flex; align-items: center; justify-content: center;
  box-shadow: 0 0 0 2px var(--surface);
}
.nav-group-label {
  font-size: var(--fs-2xs); color: var(--text-4);
  font-weight: var(--fw-semi); letter-spacing: .1em; text-transform: uppercase;
  padding: var(--sp-5) 11px 6px;
}

.sidebar-user { border-top: 1px solid var(--border); padding: var(--sp-3); }
.user-chip {
  display: flex; align-items: center; gap: 11px;
  padding: 8px; border-radius: var(--r-md);
  min-height: 50px; width: 100%; text-align: left;
  transition: background-color var(--dur) var(--ease);
  position: relative; overflow: hidden;
}
.user-chip:hover { background: var(--primary-soft); }
.user-chip.is-active { background: var(--primary-soft); }
.avatar {
  width: 38px; height: 38px; border-radius: var(--r-full); flex-shrink: 0;
  background: linear-gradient(140deg, var(--brand-400), var(--brand-600));
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-weight: var(--fw-semi); font-size: var(--fs-md);
  box-shadow: var(--sh-xs);
}
.avatar.is-admin    { background: linear-gradient(140deg, var(--brand-500), var(--brand-700)); }
.avatar.is-super    { background: linear-gradient(140deg, #7C3AED, #4C1D95); }
.avatar.is-sampling { background: linear-gradient(140deg, var(--teal-400), var(--teal-600)); }
.user-meta { min-width: 0; display: flex; flex-direction: column; line-height: 1.35; }
.user-meta b {
  font-size: var(--fs-md); font-weight: var(--fw-semi);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.user-meta span {
  font-size: var(--fs-xs); color: var(--text-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.content { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.topbar {
  position: sticky; top: 0; z-index: 40;
  background: linear-gradient(120deg, var(--brand-700) 0%, var(--brand-600) 55%, var(--brand-700) 100%);
  color: #fff;
  box-shadow: var(--sh-md);
  min-height: var(--appbar-h);
  padding: 9px var(--sp-6);
  display: flex; align-items: center; gap: var(--sp-4);
}
.topbar::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(180deg, rgba(255,255,255,.07), transparent 42%);
}
.topbar > * { position: relative; z-index: 1; }
.topbar-left { min-width: 0; }
.page-title {
  margin: 0; font-size: var(--fs-xl);
  font-weight: var(--fw-semi); letter-spacing: .1px;
  color: #fff; line-height: var(--lh-tight);
}
.page-desc {
  margin: 2px 0 0; font-size: var(--fs-xs);
  color: rgba(255, 255, 255, .76); line-height: var(--lh-snug);
}
.topbar-right {
  margin-left: auto;
  display: flex; align-items: center; gap: var(--sp-2);
  flex-wrap: wrap;
}

.topbar .btn-line,
.topbar .btn-ghost {
  background: rgba(255, 255, 255, .10); color: #fff;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .30);
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
}
.topbar .btn-ghost { background: transparent; box-shadow: none; }
.topbar .btn-line:hover { background: rgba(255, 255, 255, .20); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .48); }
.topbar .btn-ghost:hover { background: rgba(255, 255, 255, .16); }
.topbar .btn-primary { background: #fff; color: var(--primary); box-shadow: var(--sh-sm); }
.topbar .btn-primary:hover { background: rgba(255, 255, 255, .90); }
.topbar .icon-btn { color: #fff; }
.topbar .icon-btn:hover { background: rgba(255, 255, 255, .18); color: #fff; }
.topbar .seg { background: rgba(255, 255, 255, .16); }
.topbar .seg-btn { color: rgba(255, 255, 255, .84); }
.topbar .seg-btn:hover { color: #fff; }
.topbar .seg-btn.is-active { background: #fff; color: var(--primary); box-shadow: var(--sh-xs); }

.view {
  flex: 1;
  padding: var(--sp-6) var(--sp-6) 120px;
  max-width: var(--maxw); width: 100%;
}
body.has-cart .view { padding-bottom: 152px; }

.tabbar { display: none; }

/* ============================================================
   10. 卡片 / 工具条 / 搜索 / 筛选
   ============================================================ */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-sm);
}
.card-pad { padding: var(--sp-5); }
.card-head {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 15px var(--sp-5);
  border-bottom: 1px solid var(--border);
  /* 标题旁边的说明文字太长时另起一行（否则会被挤成一行窄窄的多行小字） */
  flex-wrap: wrap;
}
.card-head h3 {
  margin: 0; font-size: var(--fs-base);
  font-weight: var(--fw-semi); letter-spacing: .1px;
  flex: 0 1 auto; min-width: 0;
}
.card-head .spacer { margin-left: auto; }
.card-head .hint { font-size: var(--fs-xs); color: var(--text-3); flex: 1 1 260px; min-width: 0; }

.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); align-items: start; }

.toolbar {
  display: flex; align-items: center; gap: 10px;
  flex-wrap: wrap; margin-bottom: var(--sp-4);
}
.toolbar .spacer { margin-left: auto; }
/* 状态 chip 并进工具栏时，去掉它给「横向滚动」留的下内边距 ——
   否则 chip 比右侧按钮高出一截，整行看起来是歪的。 */
.toolbar > .chip-row { padding-bottom: 3px; }

/* ---- 后台分区条：按用途分组 ----
   10 个区块平铺时，光看名字分不出「耗材移除」和「耗材验收」谁跟谁一类事。
   组名是小号灰字，前面一条细分隔线；chip 本身不变，所以点击次数一点没多。 */
.nav-glab {
  flex-shrink: 0; align-self: center;
  padding-right: 2px;
  font-size: var(--fs-xs); font-weight: var(--fw-semi);
  color: var(--text-3); letter-spacing: .3px; white-space: nowrap;
}
.nav-sep {
  flex-shrink: 0; align-self: center;
  width: 1px; height: 16px; margin: 0 3px;
  background: var(--border-strong, var(--border));
}
/* 组名前那一格不留 gap 之外的空档，紧凑一点 */
.nav-grouped > .nav-glab + .chip { margin-left: -2px; }

.search { position: relative; flex: 1; min-width: 200px; max-width: 440px; }
.search input {
  width: 100%;
  padding: 10px 14px 10px 40px;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text); outline: none;
  box-shadow: var(--sh-xs);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              background-color var(--dur) var(--ease);
}
.search input::placeholder { color: var(--text-4); }
.search input:hover { border-color: var(--border-strong); }
.search input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(15, 167, 149, .14);
}
.search::before {
  content: ""; position: absolute; left: 14px; top: 50%;
  width: 11px; height: 11px; margin-top: -7px;
  border: 2px solid var(--text-4); border-radius: 50%;
  transition: border-color var(--dur) var(--ease);
  pointer-events: none;
}
.search::after {
  content: ""; position: absolute; left: 24px; top: 50%;
  width: 6px; height: 2px; margin-top: 4px;
  background: var(--text-4); transform: rotate(45deg);
  border-radius: 2px;
  transition: background-color var(--dur) var(--ease);
  pointer-events: none;
}
.search:focus-within::before { border-color: var(--accent); }
.search:focus-within::after  { background: var(--accent); }

.select-sm {
  padding: 10px 32px 10px 13px;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-2); font-weight: var(--fw-medium);
  font-size: var(--fs-sm); outline: none;
  box-shadow: var(--sh-xs);
  cursor: pointer;
  appearance: none; -webkit-appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--text-3) 50%),
                    linear-gradient(135deg, var(--text-3) 50%, transparent 50%);
  background-position: calc(100% - 15px) 54%, calc(100% - 10px) 54%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.select-sm:hover { border-color: var(--border-strong); color: var(--text); }
.select-sm:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(15, 167, 149, .14); }

.chip-row {
  display: flex; gap: 7px;
  overflow-x: auto; padding: 3px 0 10px;
  scrollbar-width: none;
}
.chip-row::-webkit-scrollbar { display: none; }
.chip {
  flex-shrink: 0;
  padding: 0 14px; border-radius: var(--r-full);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-2);
  font-size: var(--fs-sm); font-weight: var(--fw-medium);
  min-height: 34px;
  display: inline-flex; align-items: center;
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease),
              color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              transform var(--dur-fast) var(--ease);
  position: relative; overflow: hidden;
}
.chip:hover {
  border-color: var(--primary-line); color: var(--primary-dark);
  background: var(--primary-soft);
}
.chip:active { transform: scale(.96); }
.chip.is-active {
  background: var(--primary); border-color: var(--primary);
  color: #fff; box-shadow: var(--sh-sm);
}
.chip.is-active:hover { background: var(--primary-dark); border-color: var(--primary-dark); color: #fff; }
.chip .chip-n {
  opacity: .62; font-weight: var(--fw-normal);
  margin-left: 5px; font-size: var(--fs-xs);
}
.chip.is-active .chip-n { opacity: .82; }

/* ============================================================
   11. 商品网格
   ============================================================ */
.grid {
  display: grid; gap: var(--sp-4);
  grid-template-columns: repeat(auto-fill, minmax(228px, 1fr));
}

.pcard {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
  display: flex; flex-direction: column;
  box-shadow: var(--sh-sm);
  /* 让卡片自己能作为容器查询的基准：卡面宽窄由栅格决定，不能靠视口断点猜 */
  container-type: inline-size;
  transition: box-shadow var(--dur-slow) var(--ease),
              transform var(--dur-slow) var(--ease),
              border-color var(--dur) var(--ease);
}
/* 桌面端不再整卡上浮：满屏卡片一起「飘」是廉价感的经典来源。
   改成边框提亮 + 投影加深，静止时反而更沉、更稳。 */
.pcard:hover {
  box-shadow: var(--sh-md);
  border-color: var(--primary-line);
}

.pcard-img {
  position: relative;
  aspect-ratio: 4 / 3;
  background: var(--img-bg);
  cursor: pointer; overflow: hidden;
}
.pcard-img img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform var(--dur-slow) var(--ease), opacity var(--dur) var(--ease);
}
.pcard:hover .pcard-img img { transform: scale(1.06); }

/* 状态标签：玻璃拟态胶囊。
   用户反馈「库存预警 / 缺货不够醒目」—— 原来三档一样大、都用 11.5px：
   商品图本来颜色就杂，一个 11.5px 的小胶囊压在上面几乎看不见。
   现在**按轻重分两档**：
     · 正常（success/muted）：保持小尺寸 —— 绝大多数卡片都是它，
       每张都放大反而会把真正该注意的那几张淹掉；
     · 预警 / 缺货（warn/danger）：字号 +1 档、内距加大、字重加粗、圆点变大，
       再补一圈半透明白描边（半透明的胶囊压在照片上，边界会糊掉，
       白色描边是让它从任何底色上「跳出来」最省事的办法）。 */
.pcard-tag {
  position: absolute; top: 10px; left: 10px;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px 4px 8px;
  border-radius: var(--r-full);
  font-size: var(--fs-2xs); font-weight: var(--fw-semi);
  color: #fff; background: var(--success);
  box-shadow: 0 2px 8px rgba(0, 0, 0, .18);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  letter-spacing: .2px;
}
.pcard-tag::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: currentColor; opacity: .9;
}
.pcard-tag.warn   { background: var(--warn); }
.pcard-tag.danger { background: var(--danger); }
.pcard-tag.muted  { background: rgba(22, 36, 45, .72); }

/* 预警 / 缺货：放大一档并加白描边，从照片上「跳」出来 */
.pcard-tag.warn,
.pcard-tag.danger {
  padding: 6px 13px 6px 10px;
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  letter-spacing: .3px;
  box-shadow: 0 3px 12px rgba(0, 0, 0, .3), inset 0 0 0 1.5px rgba(255, 255, 255, .6);
}
.pcard-tag.warn::before,
.pcard-tag.danger::before { width: 7px; height: 7px; opacity: 1; }
/* 缺货再加一圈脉冲，把「这个现在拿不到」从静止的色块变成会动的信号。
   只用 opacity —— transform 会把这个绝对定位的胶囊顶出图片框。 */
@keyframes pcardOutPulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: .72; }
}
.pcard-tag.danger { animation: pcardOutPulse 2.2s var(--ease) infinite; }
@media (prefers-reduced-motion: reduce) {
  .pcard-tag.danger { animation: none; }
}

.pcard-body {
  padding: 14px 15px 15px;
  display: flex; flex-direction: column; gap: 8px;
  flex: 1;
}
/* 名字与规格：都用 em 定高，跟着字号走。
   原来写死 40px / 32px 是「按 14px 字号算的魔法数」，字号一改就对不上；
   而且规格行被强制撑到两行，与上面的名字行贴在一起、又没有视觉分隔，
   读起来像一整段 —— 这是卡面「字挤在一起」最直接的一处。
   现在：名字锁 2 行（保证各行等高），规格锁 1 行 + 省略号（品牌规格在名字里通常已出现，
   重复的那部分信息价值最低，先让位）。 */
.pcard-name {
  font-size: var(--fs-base); font-weight: var(--fw-semi);
  line-height: var(--lh-snug); cursor: pointer; color: var(--text);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; min-height: 2.8em;
  transition: color var(--dur) var(--ease);
}
.pcard-name:hover { color: var(--accent-dark); }
.pcard-spec {
  font-size: var(--fs-xs); color: var(--text-3); line-height: var(--lh-snug);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 编码 + 归属小组：合成一行（原来各占一行，两行加起来 40px+，
   而「每个卡都挂一个同样的 A组」本身几乎不携带信息）。 */
.pcard-meta {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap; row-gap: 4px;
}
.pcard-code {
  font-family: "SFMono-Regular", ui-monospace, "JetBrains Mono", Menlo, Consolas, monospace;
  font-size: var(--fs-2xs); color: var(--text-3);
  background: var(--surface-3); border-radius: var(--r-xs);
  padding: 1px 6px;
  letter-spacing: .3px;
}

.pcard-foot {
  display: flex; align-items: baseline; justify-content: space-between;
  /* 允许折行：窄卡片里「剩余 + 安全库存」和单价并排是塞不下的（实测需要 178px / 只有 138px）。
     让单价自动掉到第二行并靠右，比两段文字互相叠字体面得多，而且不依赖断点猜测。 */
  flex-wrap: wrap; row-gap: 2px;
  gap: var(--sp-2); margin-top: auto; padding-top: 11px;
  font-variant-numeric: tabular-nums;
}
/* 剩余量是卡面的主角，价格降为等价信息。
   原写法把「台账单价」排成 fs-xl + bold（全卡最重的元素），可它对领取人没有决策价值。 */
.stock-line {
  display: flex; align-items: baseline; gap: 5px; min-width: 0;
  flex-wrap: wrap; row-gap: 1px; line-height: 1.3;
}
.stock-line .stock-lab { font-size: var(--fs-xs); color: var(--text-3); white-space: nowrap; }
.stock-line b { font-size: var(--fs-lg); font-weight: var(--fw-semi); color: var(--text); letter-spacing: -.2px; }
/* 「· 安全库存 N」是补充信息，必须能被压缩：
   给它 min-width:0 + ellipsis，否则两段 nowrap 挤不进卡片宽度时会直接压到价格上叠字
   （实测 154px 宽的卡片里 stockLine 内容 118px / 可用 82px）。 */
.stock-line .stock-safe {
  font-size: var(--fs-xs); color: var(--text-3); white-space: nowrap;
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
}
.stock-line.low b { color: var(--warn-text); }
.stock-line.out b { color: var(--danger-text); }
.price-min {
  flex: none; margin-left: auto; font-size: var(--fs-sm); color: var(--text-3);
  white-space: nowrap; font-variant-numeric: tabular-nums;
}
/* 「另一种货币」的参考值：只在详情面板 / 购物车合计这种空间宽裕的地方出现。
   卡片上**不再**并排两种币别 —— 那是卡面拥挤的主要来源，而且中方同事看「≈¥」、
   印尼同事看「≈Rp」时各有一个人要被迫读无关的数字。 */
.money-ref { font-style: normal; color: var(--text-4); font-size: var(--fs-2xs); }
/* 合计行：主币种 + 另一币种的小字参考（如「¥ 12.34　≈ Rp 32.950」）
   两者基线对齐、允许换行 —— 窄屏时参考值会整体掉到第二行，不会把数字挤断。 */
.money-line { display: flex; align-items: baseline; gap: 4px; flex-wrap: wrap; }
.pcard .stockbar { margin-top: 7px; }

/* 窄卡（手机上的三分列 ≈154px、四分列 ≈196px）：
   「剩余 N」+「· 安全库存 N」+ 单价三样塞不进一行，会挤成三行、卡面松垮。
   按信息可替代性取舍 —— 安全库存已经由下面的进度条和「库存预警」胶囊表达了，
   数值隐去损失最小；单价隐去就彻底查不到了，所以留价格、让安全库存数值退场。
   用容器查询而不是视口断点：卡面宽度由栅格列数决定，跟视口不是一回事。 */
@container (max-width: 227px) {
  .pcard-foot .stock-safe { display: none; }
}

/* 购物车弹层里的单价仍用大号字（那是「结算金额」，本来就该突出） */
.price {
  color: var(--text); font-weight: var(--fw-bold);
  font-size: var(--fs-xl); line-height: 1.15; letter-spacing: -.2px;
  font-variant-numeric: tabular-nums;
}
.price small { font-size: var(--fs-xs); font-weight: var(--fw-medium); margin-right: 1px; }

.pcard-actions { display: flex; gap: 7px; margin-top: 11px; }
/* 商品卡底栏的两颗按钮：文字一律不折行。
   .btn-add 原来没写 white-space，卡片一窄「加入领取清单」就被折成两行
   （最后一个「单」孤零零掉到第二行）。
   nowrap + ellipsis 只是兜底：真正的空间由下面那条容器查询给 ——
   窄卡改成上下堆叠、各占整行，所以正常不会走到省略号。 */
.btn-add, .btn-fav { white-space: nowrap; text-overflow: ellipsis; }
.btn-add {
  flex: 1;
  border-radius: var(--r-sm);
  padding: 9px 0; font-size: var(--fs-md); font-weight: var(--fw-semi);
  background: var(--primary); color: #fff;
  box-shadow: var(--sh-xs);
  transition: background-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              transform var(--dur-fast) var(--ease);
  position: relative; overflow: hidden;
}
.btn-add:hover:not(:disabled) { background: var(--primary-dark); box-shadow: var(--sh-md); }
.btn-add:active:not(:disabled) { transform: translateY(1px); }
/* 缺货时这颗按钮的文案是「已缺货」，属于**要看得见**的信息，不是普通禁用态。
   原来用 --surface-3 + --text-4（浅灰底浅灰字），在卡片里几乎和背景融成一片，
   用户反馈「缺货不够醒目」说的就是它。改成红色浅底 + 红色粗字：
   一眼能看出「这颗现在拿不到」，同时保持 disabled 的语义（无阴影、光标 not-allowed）。 */
.btn-add:disabled {
  background: var(--danger-soft); color: var(--danger-text);
  font-weight: var(--fw-bold);
  cursor: not-allowed;
}
.btn-fav {
  border-radius: var(--r-sm);
  background: var(--surface); color: var(--text-2);
  padding: 9px 13px; font-size: var(--fs-sm); font-weight: var(--fw-medium);
  box-shadow: inset 0 0 0 1px var(--border);
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
  position: relative; overflow: hidden;
}
.btn-fav:hover {
  background: var(--accent-soft); color: var(--accent-dark);
  box-shadow: inset 0 0 0 1px var(--accent-light);
}

/* 窄卡（手机是 3 列、一列只有 150–210px）：两颗按钮改上下堆叠，各占整行。
   为什么不是截断或缩字号：一行要塞下的是「加入领取清单」+「详情」，
   印尼语那边是「Tambah ke daftar」+「Detail」—— 实测一行要 216px 卡宽，
   165px 的卡怎么缩都会折行或截成「加入领取…」。堆叠让主按钮拿满整行，
   中英印尼三种文案都不折行、不截断，代价只是卡片高一点。 */
@container (max-width: 224px) {
  .pcard-actions { flex-direction: column; }
  .btn-add { flex: none; width: 100%; }
  .btn-fav { width: 100%; }
}

.stockbar {
  height: 5px; border-radius: var(--r-full);
  background: var(--surface-3); overflow: hidden;
}
.stockbar i {
  display: block; height: 100%; border-radius: var(--r-full);
  background: var(--success);
  transition: width var(--dur-slow) var(--ease);
}
.stockbar.warn   i { background: var(--warn); }
.stockbar.danger i { background: var(--danger); }

/* ============================================================
   12. 徽标 / 标签
   ============================================================ */
.badge {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 9px; border-radius: var(--r-full);
  font-size: var(--fs-xs); font-weight: var(--fw-medium);
  line-height: 1.7; white-space: nowrap;
}
.badge-green  { background: var(--success-soft); color: var(--success-text); }
.badge-amber  { background: var(--warn-soft);    color: var(--warn-text); }
.badge-red    { background: var(--danger-soft);  color: var(--danger-text); }
.badge-blue   { background: var(--info-soft);    color: var(--info-text); }
.badge-gray   { background: var(--surface-3);    color: var(--text-2); }
.badge-orange { background: var(--warn-soft);    color: var(--warn-text); }

.tag {
  padding: 3px 10px; border-radius: var(--r-sm);
  background: var(--surface-3); color: var(--text-2);
  font-size: var(--fs-xs); font-weight: var(--fw-normal);
}
.tag-list { display: flex; gap: 6px; flex-wrap: wrap; }

/* ============================================================
   13. 统计卡
   ============================================================ */
.stats {
  display: grid; gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.stat {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 17px var(--sp-5);
  box-shadow: var(--sh-sm);
  position: relative; overflow: hidden;
  transition: box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.stat:hover { box-shadow: var(--sh-md); border-color: var(--border-strong); }
.stat::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: 3px; background: var(--primary);
}
.stat::after {
  content: ""; position: absolute; right: -34px; top: -34px;
  width: 96px; height: 96px; border-radius: 50%;
  background: var(--primary-soft); opacity: .8;
}
.stat.is-green::before { background: var(--success); }
.stat.is-green::after  { background: var(--success-soft); }
.stat.is-amber::before { background: var(--warn); }
.stat.is-amber::after  { background: var(--warn-soft); }
.stat.is-blue::before  { background: var(--info); }
.stat.is-blue::after   { background: var(--info-soft); }

.stat-ico {
  position: relative; z-index: 1;
  width: 34px; height: 34px; border-radius: var(--r-sm);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-2);
}
.stat-num {
  font-size: var(--fs-3xl); font-weight: var(--fw-bold);
  line-height: 1.12; margin-top: 10px;
  letter-spacing: -.6px; position: relative; z-index: 1;
}
.stat-lab {
  font-size: var(--fs-md); color: var(--text-2);
  font-weight: var(--fw-medium); margin-top: 3px;
  position: relative; z-index: 1;
}
.stat-sub {
  font-size: var(--fs-xs); color: var(--text-3);
  margin-top: 5px; position: relative; z-index: 1;
}

/* ============================================================
   13b. 单页看板（用量统计）
   ------------------------------------------------------------
   一页五区：顶部锚点条吸附在顶栏下方；每区一个带编号的区标题；
   分组用「对比卡」，预警用「一行一款耗材」的清单行。
   锚点条只在宽屏吸附 —— 窄屏工具栏会挤成两行，吸附反而挡内容。
   ============================================================ */
.anchors {
  position: sticky; top: var(--appbar-h); z-index: 30;
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 10px 0 12px;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--sp-4);
}
.anchors-lab {
  flex-shrink: 0;
  font-size: var(--fs-xs); color: var(--text-3);
  font-weight: var(--fw-medium); letter-spacing: .3px;
}
.anchors .chip-row { flex: 1; min-width: 0; }

.sec-block { margin-bottom: var(--sp-6); }
.sec-head { display: flex; align-items: center; gap: 9px; margin: 0 0 12px; }
.sec-no {
  width: 21px; height: 21px; flex-shrink: 0;
  border-radius: var(--r-xs);
  background: var(--primary); color: #fff;
  font-size: var(--fs-xs); font-weight: var(--fw-bold);
  line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}
.sec-t {
  margin: 0; color: var(--text);
  font-size: var(--fs-lg); font-weight: var(--fw-bold); letter-spacing: .2px;
}
.sec-head .hint {
  margin-left: auto; text-align: right;
  font-size: var(--fs-xs); color: var(--text-3);
}

/* ---- 分组汇总卡（同时是二级菜单的按钮） ---- */
/* auto-fit（而不是 auto-fill）：6 张卡正好铺满一行，右侧不会留空轨道。
   minmax 取 160px —— 在约 1050px 的内容区刚好排出 6 列。
   本轮起卡片是可点按钮：点开 → 下方展开该组明细。所以 button 化（font/color 继承、
   去掉浏览器默认外观）+ hover/选中反馈，并在状态脚右侧挂一个展开箭头。 */
.gcard-grid {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  margin-bottom: var(--sp-3);
}
.gcard {
  display: flex; flex-direction: column; gap: 5px;
  width: 100%; cursor: pointer; text-align: left;
  font: inherit; color: inherit;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-sm);
  padding: 10px 12px;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.gcard:hover { border-color: var(--border-strong); box-shadow: var(--sh-md); }
.gcard:active { transform: scale(.985); }
/* 「打开组内明细」的箭头（朝右）。没有选中态、也不翻转 ——
   组内明细是弹窗，只有打开与关闭，不存在「展开/收起」这个来回。 */
.gcard-cue { margin-left: auto; display: inline-flex; align-items: center; color: currentColor; opacity: .75; }
.gcard-top { display: flex; align-items: center; gap: var(--sp-2); }
.gcard-name { font-size: var(--fs-md); font-weight: var(--fw-semi); color: var(--text); }
.gcard-top .badge { margin-left: auto; }
.gcard-sub {
  min-height: 15px; line-height: 1.35;
  font-size: var(--fs-xs); color: var(--text-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 组卡副标题里的「品类」胶囊。
   注意：用户明确说过「要的是卡片有颜色，不是字体有颜色」，所以这里是**中性**的 ——
   品类胶囊只负责把品类排整齐、并保住末尾的「+N」，颜色一律交给卡片本身（见下面的分组身份色）。 */
.gcard-sub .gcat {
  display: inline-flex; align-items: center;
  padding: 1px 8px; border-radius: var(--r-full);
  background: var(--surface-2);
  font-size: var(--fs-2xs); font-weight: var(--fw-medium);
  color: var(--text-2); white-space: nowrap;
}
/* 副标题那一行改成 flex：胶囊之间要有间隙，溢出时裁掉而不是把「+N」挤没。
   ⚠ 实测：胶囊比原来的纯文字宽，卡片 169px / 副标题可视只有 143px 时，
   6 张卡有 3 张的内容宽到 154~177px —— 用 nowrap 会把末位的「+N」裁掉，
   而「+N」正是「这里还有别的品类」的信号（顶部那个「N 种」数的是耗材种类，不是品类数，
   两回事）。所以这里**允许折行**：宁可多占一行，也不要丢信息。
   折行后同排的卡片会被 grid 拉伸到同一高度，视觉上仍然是齐的。 */
.gcard-sub.is-chips {
  display: flex; align-items: center; gap: 4px; row-gap: 3px;
  flex-wrap: wrap;
}
.gcard-sub .gcat-more {
  font-size: var(--fs-2xs); font-weight: var(--fw-semi);
  color: var(--text-3); white-space: nowrap; flex: none;
}

/* ---------- 组卡的分组身份色 ----------
   用户要求「按组查看下面的几个分类（= 六张组卡）用颜色区分，太素了」，
   且明确「是卡片增加颜色，不是字体增加颜色」—— 所以颜色落在**卡面**上：
   顶部 4px 实心色条 + 一层极浅的底纹，字体保持原来的中性色（保证对比度不受影响）。

   色值取自设计系统预留的 --c1…--c10（浅色/深色主题各一套，自动适配），
   六组挑 6 个色相分得开的，并**刻意避开 --c5(红) 与 --c4(琥珀)** ——
   这两个在本页是「缺货 / 预警」的语义色，拿来当组身份色会让人误读。
   深色主题下这 10 个变量会自动换成高亮版本，所以底纹浓度不必分主题写。 */
.gcard { position: relative; isolation: isolate; overflow: hidden; }
.gcard::before {          /* 顶部色条：最主要的身份信号 */
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 4px;
  background: var(--gc, transparent);
}
.gcard::after {           /* 极浅底纹：单独一层用 opacity 控制浓度，不必 color-mix */
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: var(--gc, transparent); opacity: .075;
}
.gcard:hover { border-color: var(--gc, var(--border-strong)); }
.gcard.g-blue    { --gc: var(--c6); }
.gcard.g-teal    { --gc: var(--c2); }
.gcard.g-purple  { --gc: var(--c3); }
.gcard.g-magenta { --gc: var(--c8); }
.gcard.g-orange  { --gc: var(--c10); }
.gcard.g-lime    { --gc: var(--c7); }
.gcard-kv { display: flex; align-items: baseline; gap: var(--sp-2); }
.gcard-lab { flex: 1; min-width: 0; font-size: var(--fs-xs); color: var(--text-3); }
.gcard-num {
  color: var(--text); font-variant-numeric: tabular-nums;
  font-size: var(--fs-xl); font-weight: var(--fw-bold); letter-spacing: -.4px;
}
.gcard-meta {
  font-size: var(--fs-xs); color: var(--text-3);
  font-variant-numeric: tabular-nums;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gcard-foot {
  display: flex; align-items: center; gap: 5px;
  margin-top: 2px; padding-top: 6px;
  border-top: 1px dashed var(--border);
  font-size: var(--fs-xs); font-weight: var(--fw-medium);
}
.gcard-foot.is-alert { color: var(--danger-text); }
.gcard-foot.is-ok    { color: var(--success-text); }
.gcard-tip { margin: 0 0 var(--sp-4); font-size: var(--fs-xs); color: var(--text-3); }

/* ---- 组内明细弹窗（.sheet）里的内容 ----
   结构：顶部一句结论（.gp-lead）→「需要关注」区块（琥珀底、行左竖色条）
        →「库存充足」区块（安静）→ 底栏「一键加入提报单」。
   突出重点靠的是「顺序 + 着色 + 只留能点出动作的数字」，不是把一堆数字摊平。
   （原来这些区块是摊在页面里的 .gpanel 内联面板，已改成弹窗，.gpanel 一并删除。） */

/* 顶部一句结论 + 该组的两个汇总数（弹窗把卡片盖住了，汇总要在这里补一眼） */
.gp-lead { margin-bottom: var(--sp-4); }
.gp-lead-t { display: block; font-size: var(--fs-md); font-weight: var(--fw-semi); color: var(--text); }
.gp-lead-n {
  display: block; margin-top: 3px;
  font-size: var(--fs-xs); color: var(--text-3);
  font-variant-numeric: tabular-nums;
}

/* 区块：各自成块（描边 + 圆角）—— 弹窗有内边距，区块再满宽铺就不是「一块块」了 */
.gp-sec {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
}
.gp-sec + .gp-sec { margin-top: var(--sp-3); }
.gp-sec-head {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 11px var(--sp-5); font-size: var(--fs-xs);
}
.gp-sec-head .ico-wrap { flex-shrink: 0; }
.gp-sec-t { font-size: var(--fs-sm); font-weight: var(--fw-semi); color: var(--text); }
.gp-sec-note { color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 「需要关注」区块：琥珀底 + 行左侧竖色条 —— 打开弹窗第一眼就该落在这里 */
.gp-sec.is-warn { background: var(--warn-soft); border-color: rgba(180, 83, 9, .22); }
.gp-sec.is-warn .gp-sec-head,
.gp-sec.is-warn .gp-sec-t { color: var(--warn-text); }
.gp-sec.is-warn .gp-sec-note { color: var(--warn-text); opacity: .85; }
.gp-sec.is-warn .list-row {
  border-left: 3px solid var(--warn);
  padding-left: calc(var(--sp-5) - 3px);
}
.gp-sec.is-warn .list-row:hover { background: rgba(180, 83, 9, .07); }

/* 「库存充足」区块：安静下来，不跟预警抢视线 */
.gp-sec-head.is-quiet .gp-sec-t { color: var(--text-2); font-weight: var(--fw-medium); }

/* 「建议备货 N」小药丸 —— 清单里唯一一个「看到了就要动手」的数字。
   编码 / 规格 / 库位都是灰字元数据，它必须是另一种颜色，否则重点全被摊平。 */
.ask-chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 9px 2px; border-radius: var(--r-full);
  background: var(--primary-soft); color: var(--primary-dark);
  font-size: var(--fs-xs); font-weight: var(--fw-semi);
  white-space: nowrap;
}
.ask-chip > .ico-wrap { margin-right: 0; }

/* ---- 清单折叠：「查看全部 N 条」作为列表的最后一行 ---- */
.more-btn {
  display: block; width: 100%; cursor: pointer;
  padding: 11px var(--sp-4);
  background: transparent; border: 0; border-top: 1px dashed var(--border);
  font: inherit; font-size: var(--fs-sm); font-weight: var(--fw-medium);
  color: var(--primary); text-align: center;
}
.more-btn:hover { background: var(--surface-2); }

/* ---- 清单行：图标位 + 副信息（本轮起副信息横排一行，不再竖着堆） ---- */
.ico-line { display: flex; align-items: center; gap: 5px; min-width: 0; max-width: 100%; }
.ico-line > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- 横幅里的行动按钮 ---- */
/* 提示条正文。min-width:0 让它能在 flex 行里被压缩（否则 flex 项的
   min-width:auto 会拒绝收缩，整行被撑破）；break-word 是兜底：
   印尼语有 kedaluwarsa / pertimbangan 这类长词，空间实在不够时宁可断词，
   也不要整词溢出被裁（375px 下实测正文被挤到 58px，长词直接顶出去 20px）。 */
.notice-txt { flex: 1 1 auto; min-width: 0; overflow-wrap: break-word; word-break: break-word; }
.notice .btn { flex-shrink: 0; align-self: center; }

/* 到期预警：起算日 + 来源标签（入库流水 / 建档日），避免把建档日当成入库日 */
.base-src { display: inline-flex; align-items: center; gap: 6px; justify-content: flex-end; }
.base-tag {
  font-style: normal; font-size: var(--fs-xs); line-height: 1.5;
  color: var(--text-3); border: 1px solid var(--border);
  border-radius: 4px; padding: 0 4px; white-space: nowrap;
}

/* 窄屏：锚点条不再吸附（顶栏 + 工具栏已经占了两行） */
@media (max-width: 719px) {
  .anchors { position: static; }
  .gcard-grid { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 8px; }
  .gcard-num { font-size: var(--fs-lg); }
}

/* ============================================================
   14. 列表 / 表格
   ============================================================ */
.list { display: flex; flex-direction: column; }
.list-row {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 13px var(--sp-5);
  border-bottom: 1px solid var(--border);
  position: relative; overflow: hidden;
  transition: background-color var(--dur) var(--ease);
}
.list-row:last-child { border-bottom: 0; }
.list-row:hover { background: var(--surface-2); }
.list-thumb {
  width: 50px; height: 50px; border-radius: var(--r-md);
  flex-shrink: 0; overflow: hidden; background: var(--img-bg);
}
.list-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.list-thumb.is-dim { opacity: .5; filter: grayscale(1); }
.list-main { flex: 1; min-width: 0; }
.list-title {
  font-size: var(--fs-md); font-weight: var(--fw-semi);
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  line-height: var(--lh-snug);
}
.list-sub {
  font-size: var(--fs-xs); color: var(--text-3); margin-top: 3px;
  display: flex; gap: 11px; flex-wrap: wrap;
}
.list-side {
  text-align: right; display: flex; flex-direction: column;
  align-items: flex-end; gap: 6px; flex-shrink: 0;
}
.list-side .qty { font-size: var(--fs-lg); font-weight: var(--fw-semi); color: var(--text); }
.list-side .qty span { font-size: var(--fs-xs); color: var(--text-3); font-weight: var(--fw-normal); }

.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ---------- 表格区域：表头吸附 + 区域内滚动（≥701px） ----------
   台账这类表近 200 行。原先 .table-wrap 只有 overflow-x、**没有高度上限**，
   表格就被撑到 2 万多像素高，而横向滚动条是贴在滚动容器**底部**的 ——
   等于要滚完 193 行才拖得到它。后果：在 1300~1500px 的常见窗口下，
   右边的「单价 (IDR) / 状态 / 操作」整列既看不见也基本够不着
   （实测可视容器 965px vs 表格 1480px，末列右缘 1753、可视右缘只有 1238；
    操作列里是 193 行 × 编辑/删除按钮，全都点不到）。
   给容器一个高度上限 + 表头吸附之后，两个滚动条都留在视野里，列随时拖得到，
   顺便把整页从 2 万多像素收成一屏多一点。
   ≤700px 不加：那时 table.tbl 已经降级成卡片，没有横向滚动，加了反而多余。 */
@media (min-width: 701px) {
  .table-wrap {
    max-height: min(72vh, 780px);
    overflow-y: auto;
  }
  /* 吸附的表头必须自带不透明底色 —— 否则下面滚过去的行会从字缝里透上来。
     .tbl-wide 的卡片态没有 thead（display:none），这里只对真表格生效。 */
  .table-wrap > table.tbl > thead th {
    position: sticky; top: 0; z-index: 3;
    background: var(--surface-2);
    box-shadow: inset 0 -1px 0 var(--border);
  }
}
table.tbl {
  width: 100%; border-collapse: separate; border-spacing: 0;
  font-size: var(--fs-md); min-width: 720px;
}
table.tbl th {
  text-align: left; padding: 11px var(--sp-4);
  background: var(--surface-2);
  color: var(--text-3);
  font-weight: var(--fw-semi); font-size: var(--fs-xs);
  letter-spacing: .4px; text-transform: uppercase;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
table.tbl td {
  padding: 10px var(--sp-4);
  border-bottom: 1px solid var(--border);
  vertical-align: middle; color: var(--text-2);
}
table.tbl td:first-child { color: var(--text); font-weight: var(--fw-medium); }
table.tbl tr:last-child td { border-bottom: 0; }
table.tbl tbody tr { transition: background-color var(--dur) var(--ease); }
table.tbl tbody tr:hover { background: var(--primary-soft); }
/* 数字列必须「表头 + 单元格」一起右对齐。
   只写 td.num 会让表头留在 th 的 left 上，数字看着跑到了表头右边一个列宽的位置
   —— 全站每一张带数字列的表格都会「错位」。
   （打印样式里 .print-area table.tbl th.num / td.num 一直是两者都写的，这里补齐同一个意图。） */
table.tbl th.num,
table.tbl td.num { text-align: right; }
/* ---- 表格密度（后台「耗材台账」尤其需要） ----
   改之前一行 80px，一屏只看得到 15 行。三个来源，逐个压掉：
     ① 操作列三颗按钮原本 flex-wrap: wrap，列一被挤窄就折成两行 → .ops-row 不换行
     ② 库存「值」与「安全 N」各占一行                          → 复用 .stock-line 并成一行
     ③ 缩略图 50px 把单元格撑高                                → 表内缩到 36px
   行内文字的两行（名称 / 品牌·规格）用 line-height 收紧，不再各带一段默认行高。
   注：.list-thumb 在列表行里仍是 50px，只有表格里缩 —— 它是共用的。 */
table.tbl td.td-block .list-thumb { width: 34px; height: 34px; }
/* 名称两行，行距收紧一点点（不改折行规则）。
   ⚠ 别给这一列加 min-width、也别把两行都设成 nowrap：
   9 列的「最小内容宽度」加起来会超过内容区，浏览器只好把「分类 / 状态」等列压到折行，
   实测行高从 63px 直接涨到 103px（比不改还差）。折行就让它折，行高让位给列宽。 */
td.td-name { line-height: 1.3; }
td.td-name .cell-name { display: block; font-weight: var(--fw-semi); color: var(--text); }
td.td-name .cell-sub { display: block; font-size: var(--fs-xs); color: var(--text-3); }
/* 存放位置。
   以前写的是 nowrap：中印尼双语库位名（"Ruang sementara sebelah lift lantai 2 /
   二楼电梯旁临时房间"）被硬撑成 **537px** —— 单这一列就吃掉整表 1/3 的宽度，
   是「右边几列被挤出视野、够不着」的头号原因（实测 1263px 视口下可视容器 965px、
   表格 1480px，末列右缘 1753 而可视右缘只有 1238）。
   改成可换行后整表收进常见窗口。但**必须给 min-width**：不给的话这一列会被压到
   95px 左右，「Gudang / PMC D1 / PMC D1 仓 / 库」竖成一串，行高反而更难看
   （踩过一次）。180px 是「两行放下大多数库位名」的下限。
   只用 overflow-wrap，不用 word-break —— 后者会把 Gudang 这种短词也劈开。 */
td.td-loc { white-space: normal; min-width: 180px; overflow-wrap: break-word; }
/* 表格里的库存行：右对齐 + 绝不换行（表头是 num，两边必须都靠右）。
   ⚠ 必须把 .stock-safe 的 min-width:0 / overflow:hidden 撤销 ——
   那两条是给 154px 宽的耗材卡写的「允许被压缩」，到了表格里会让浏览器
   把这一列的「最小内容宽度」算成 0，分到的宽度不够 →「42 盒 · 安全 20」被截成「42 盒…」。
   撤销后该列会按真实内容要宽度，不够就整表横向滚动（.tbl-wide 给了 min-width）。 */
table.tbl .stock-line { flex-wrap: nowrap; justify-content: flex-end; }
table.tbl .stock-line .stock-safe { min-width: auto; overflow: visible; text-overflow: clip; }
/* 「42 盒」内部也不能断行 —— 不然「盒」会被折到第二行，看着像坏掉 */
table.tbl .stock-line b { white-space: nowrap; }
/* 9 列 + 三颗操作按钮：给一个 1000px 下限，避免被挤到看不全；
   但**不要**设成 1080 —— 实测内容区在 1360 屏上是 1040px，1080 会凭空多出 40px 横向滚动。 */
table.tbl.tbl-wide { min-width: 1000px; }
/* 列多，横向内边距从 16px 收到 12px、纵向从 10px 收到 9px —— 9 列两边一共腾出约 72px */
table.tbl.tbl-wide th,
table.tbl.tbl-wide td { padding: 9px 12px; }
.ops-row {
  display: flex; gap: 6px; flex-wrap: nowrap;
  justify-content: flex-end; white-space: nowrap;
}
/* 提报单详情里的「上季度对比」行：跟上面那行档案数据贴紧成一组，作为核准参考 */
.citem-meta.prev-qtr { margin-top: 2px; }

.mono {
  font-family: "SFMono-Regular", ui-monospace, "JetBrains Mono", Menlo, Consolas, monospace;
  font-size: var(--fs-xs); letter-spacing: .2px;
}

.delta { font-weight: var(--fw-semi); }
.delta.up   { color: var(--danger); }
.delta.down { color: var(--success); }
.delta.flat { color: var(--text-3); }

/* 「15. 耗材预警瀑布流」整段样式已随独立页删除一并移除
   （.waterfall / .wf-card / .wf-thumb / .wf-life / .wf-bar / .wf-suggest …）。
   原来挂在 .wf-* 上的 --lvl-urgent / --lvl-warning / --lvl-watch 三档等级色
   现在没有使用方，但令牌定义保留 —— 库存预警的红色横幅仍按 --warn/--danger 取色。 */

/* ============================================================
   16. 图表 / 排行
   ============================================================ */
.chart-box { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.chart-svg { display: block; width: 100%; height: auto; max-width: 100%; overflow: visible; }
.chart-grid { stroke: var(--border); stroke-width: 1; }
.chart-base { stroke: var(--border-strong); stroke-width: 1.2; }
.chart-axis { fill: var(--text-3); font-size: 11px; font-family: inherit; }
.chart-bar { transition: opacity var(--dur-fast) var(--ease); }
.chart-bar:hover { opacity: .78; }
.chart-line { fill: none; }
.chart-dot { transition: opacity var(--dur-fast) var(--ease); }
.chart-dot:hover { opacity: .75; }
.chart-legend {
  display: flex; gap: var(--sp-3); flex-wrap: wrap; margin-top: var(--sp-3);
  padding-top: 11px; border-top: 1px solid var(--border);
}
.chart-legend-item {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-xs); color: var(--text-2);
}
.chart-legend-item i { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0; }

.rank-list { display: flex; flex-direction: column; gap: var(--sp-3); }
.rank-head { display: flex; align-items: center; gap: 9px; font-size: var(--fs-sm); margin-bottom: 6px; }
.rank-idx {
  width: 21px; height: 21px; border-radius: var(--r-xs); flex-shrink: 0;
  background: var(--surface-3); color: var(--text-2);
  font-size: var(--fs-2xs); font-weight: var(--fw-semi);
  display: inline-flex; align-items: center; justify-content: center;
}
.rank-idx.is-top { background: var(--accent); color: #fff; box-shadow: var(--sh-xs); }
.rank-name {
  flex: 1; min-width: 0; font-weight: var(--fw-normal); color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rank-val { font-weight: var(--fw-semi); color: var(--text-2); flex-shrink: 0; }
.rank-track {
  height: 7px; border-radius: var(--r-full);
  background: var(--surface-3); overflow: hidden;
}
.rank-track i {
  display: block; height: 100%; border-radius: var(--r-full);
  transition: width var(--dur-slow) var(--ease);
}
.rank-sub { font-size: var(--fs-xs); color: var(--text-3); margin-top: 5px; }

.ctrl-row { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.ctrl-lab { font-size: var(--fs-md); color: var(--text-2); font-weight: var(--fw-medium); flex-shrink: 0; }
.ctrl-hint {
  font-size: var(--fs-xs); color: var(--text-3);
  margin-top: var(--sp-3); padding-top: 11px;
  border-top: 1px solid var(--border);
}

/* ============================================================
   17. 空状态 / 骨架屏
   ============================================================ */
.empty { text-align: center; padding: 64px var(--sp-5); color: var(--text-3); }
/* 空状态图标：原来是 44px 的 emoji + opacity .35，换成单色 SVG 后用文字色就能压住，
   不再需要整体降透明度（那样会连线条一起糊掉）。 */
.empty-ico { display: flex; justify-content: center; margin-bottom: var(--sp-3); color: var(--text-4); line-height: 1; }
.empty b {
  display: block; color: var(--text-2);
  font-size: var(--fs-base); font-weight: var(--fw-semi);
  margin-bottom: 5px;
}
.empty p { margin: 0; font-size: var(--fs-md); }

.skeleton-grid {
  display: grid; gap: var(--sp-4);
  grid-template-columns: repeat(auto-fill, minmax(228px, 1fr));
}
.skel {
  border-radius: var(--r-lg);
  background: linear-gradient(100deg, var(--surface-3) 28%, var(--surface-2) 50%, var(--surface-3) 72%);
  background-size: 220% 100%;
  animation: shimmer 1.4s linear infinite;
  height: 300px;
}
@keyframes shimmer { 0% { background-position: 130% 0; } 100% { background-position: -30% 0; } }

/* ============================================================
   18. 底部结算栏
   ============================================================ */
.cart-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  padding: 11px var(--sp-5) calc(11px + env(safe-area-inset-bottom));
  background: var(--surface);
  border-top: 1px solid var(--border);
  box-shadow: 0 -4px 20px rgba(16, 36, 45, .10);
  display: flex; align-items: center; gap: var(--sp-3);
  animation: cartUp var(--dur-slow) var(--ease-out);
}
@keyframes cartUp { from { transform: translateY(100%); } to { transform: none; } }
.cart-bar .cart-info { display: flex; align-items: center; gap: var(--sp-3); flex: 1; min-width: 0; }
.cart-badge {
  position: relative; width: 48px; height: 48px;
  border-radius: var(--r-full); flex-shrink: 0;
  background: linear-gradient(140deg, var(--brand-600), var(--brand-800));
  color: #fff; display: flex; align-items: center; justify-content: center;
  font-size: 21px; box-shadow: var(--sh-md); border: 0;
  transition: transform var(--dur) var(--ease-spring);
}
.cart-badge:hover { transform: scale(1.05); }
.cart-badge em {
  position: absolute; top: -3px; right: -3px;
  background: var(--danger); color: #fff;
  border-radius: var(--r-full); min-width: 22px; height: 22px;
  font-style: normal; font-size: var(--fs-xs); font-weight: var(--fw-semi);
  display: flex; align-items: center; justify-content: center; padding: 0 5px;
  box-shadow: 0 0 0 2px var(--surface), var(--sh-xs);
}
.cart-text { min-width: 0; line-height: 1.4; }
.cart-text b { font-size: var(--fs-md); font-weight: var(--fw-semi); display: block; }
.cart-text span {
  font-size: var(--fs-xs); color: var(--text-3); display: block;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cart-total { text-align: right; line-height: 1.3; flex-shrink: 0; }
.cart-total .amt {
  font-size: var(--fs-2xl); font-weight: var(--fw-bold);
  color: var(--text); letter-spacing: -.4px;
}
.cart-total .amt small { font-size: var(--fs-sm); font-weight: var(--fw-medium); }
.cart-total .cnt { font-size: var(--fs-xs); color: var(--text-3); }

/* ============================================================
   19. 弹层 / 抽屉
   ============================================================ */
.overlay {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(8, 18, 24, .55);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  display: flex; align-items: center; justify-content: center;
  padding: var(--sp-5);
  animation: fadeIn var(--dur-fast) var(--ease-out);
}
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

.sheet {
  position: relative;
  background: var(--surface);
  border-radius: var(--r-xl);
  width: 100%; max-width: 580px;
  max-height: 88vh; max-height: 88dvh;
  display: flex; flex-direction: column; overflow: hidden;
  box-shadow: var(--sh-xl);
  border: 1px solid var(--border);
  animation: popIn var(--dur-slow) var(--ease-spring);
}
.sheet.is-wide { max-width: 880px; }
@keyframes popIn {
  from { opacity: 0; transform: scale(.95) translateY(10px); }
  to   { opacity: 1; transform: none; }
}
.sheet-head {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-5) var(--sp-5) 15px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0; background: var(--surface);
}
.sheet-head h3 {
  margin: 0; font-size: var(--fs-xl);
  font-weight: var(--fw-semi); letter-spacing: .1px;
}
.sheet-head .spacer { margin-left: auto; }
.sheet-close {
  width: 38px; height: 38px; border-radius: var(--r-full);
  color: var(--text-2); font-size: 15px; line-height: 1;
  flex-shrink: 0;
  transition: background-color var(--dur) var(--ease), transform var(--dur-fast) var(--ease);
}
.sheet-close:hover { background: var(--surface-3); color: var(--text); }
.sheet-close:active { transform: scale(.9); }
.sheet-body {
  padding: var(--sp-5); overflow-y: auto; flex: 1;
  -webkit-overflow-scrolling: touch;
}
.sheet-foot {
  padding: 15px var(--sp-5) calc(15px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--border);
  display: flex; gap: 10px; align-items: center;
  flex-shrink: 0; background: var(--surface-2);
}
.sheet-foot .spacer { margin-left: auto; }

/* ---- 详情 hero：图 + 身份 + 状态 + 三个数 ----
   详情页里唯一「不成块」的一段 —— 它是这张卡的身份头，视觉权重要高于下面的分区。 */
.dhero { display: grid; grid-template-columns: 232px 1fr; gap: var(--sp-5); align-items: start; }
.detail-img {
  border-radius: var(--r-lg); overflow: hidden;
  background: var(--img-bg);
  aspect-ratio: 1/1;
  border: 1px solid var(--border);
  position: relative;
}
.detail-img img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---------- 图片放大查看（灯箱） ----------
   用户要求「详情页点图片能看原图 / 更大的清晰图」。
   详情页那个方框只有 232~360px，而库里存的是 800px —— 不点开就白白浪费掉。
   灯箱恒用深底白字：它是覆盖层，不跟随浅色/深色主题，
   否则浅色主题下一张浅底照片会糊在页面里看不出边界。 */
.detail-img.is-zoomable { cursor: zoom-in; }
.detail-zoom-hint {
  position: absolute; right: 8px; bottom: 8px;
  display: inline-flex; align-items: center; gap: 4px;
  padding: 4px 10px; border-radius: var(--r-full);
  background: rgba(22, 36, 45, .74); color: #fff;
  font-size: var(--fs-2xs); font-weight: var(--fw-medium);
  pointer-events: none;
}
.imgviewer {
  /* 300：全站层级是 弹层.overlay=200 / toast=400 / 启动页=900。
     灯箱是在「详情弹层之上」再开一层，必须高于 200，否则会被弹层盖住 ——
     而 getBoundingClientRect 照样量得到尺寸，光看尺寸根本发现不了（踩过一次）。
     放在 400 之下是有意的：toast 应该始终看得见。 */
  position: fixed; inset: 0; z-index: 300;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 16px; padding: 26px 20px;
  background: rgba(8, 14, 18, .93);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  cursor: zoom-out;
  animation: imgviewerIn .16s var(--ease);
}
@keyframes imgviewerIn { from { opacity: 0; } to { opacity: 1; } }
.imgviewer img {
  /* 不设固定宽高：让图片撑到「能放下的最大」，由 max-* 兜住。
     800px 的原图在大屏上就能按 800px 显示，不再被 232px 的方框限制。 */
  max-width: 100%; max-height: calc(100% - 92px);
  width: auto; height: auto;
  object-fit: contain;
  border-radius: var(--r-md);
  background: #fff;
  box-shadow: 0 18px 60px rgba(0, 0, 0, .55);
}
.imgviewer-cap {
  color: #fff; text-align: center; line-height: 1.5; max-width: 100%;
}
.imgviewer-cap b {
  display: block; font-size: var(--fs-md); font-weight: var(--fw-semi);
  margin-bottom: 4px; overflow-wrap: break-word;
}
.imgviewer-cap span { color: rgba(255, 255, 255, .72); font-size: var(--fs-xs); }
.imgviewer-x {
  position: absolute; top: 14px; right: 14px;
  width: 44px; height: 44px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255, 255, 255, .16); color: #fff;
  font-size: 18px; line-height: 1; cursor: pointer;
  transition: background-color var(--dur) var(--ease);
}
.imgviewer-x:hover { background: rgba(255, 255, 255, .3); }
.dhero-main { min-width: 0; }
.dhero-top { display: flex; align-items: flex-start; gap: var(--sp-3); }
.dhero-top .badge { flex-shrink: 0; margin-top: 4px; }
.detail-name {
  font-size: var(--fs-2xl); font-weight: var(--fw-bold);
  line-height: var(--lh-tight); margin: 0;
  letter-spacing: -.3px;
}
.detail-sub { font-size: var(--fs-md); color: var(--text-3); margin: 4px 0 0; }
/* 身份摘要：编码 · 品牌 · 规格 —— 一行扫完，详细值在下面的「档案信息」里 */
.dhero-meta {
  margin: 7px 0 var(--sp-4);
  font-size: var(--fs-sm); color: var(--text-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dhero .notice { margin-bottom: var(--sp-4); }

.kv {
  display: flex; flex-direction: column;
  border-top: 1px solid var(--border);
  margin-bottom: var(--sp-4);
}
.kv-row {
  display: flex; gap: var(--sp-3);
  padding: 11px 0; border-bottom: 1px solid var(--border);
  font-size: var(--fs-md);
}
.kv-row dt { width: 100px; flex-shrink: 0; color: var(--text-3); }
.kv-row dd { margin: 0; flex: 1; min-width: 0; color: var(--text); word-break: break-word; }

.stock-detail {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 10px; margin-bottom: var(--sp-4);
}
.stock-cell {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 12px 14px;
}
.stock-cell .lab { font-size: var(--fs-xs); color: var(--text-3); }
.stock-cell .val {
  font-size: var(--fs-xl); font-weight: var(--fw-bold);
  line-height: 1.3; letter-spacing: -.3px;
}
.stock-cell .val small { font-size: var(--fs-xs); font-weight: var(--fw-normal); color: var(--text-3); }
.stock-cell.is-low { background: var(--warn-soft); border-color: rgba(180, 83, 9, .2); }
.stock-cell.is-low .val { color: var(--warn-text); }

/* ============================================================
   13c. 详情面板：测试项目 / 存放位置 / 剩余明细（按批次）/ 领取人记录
   ------------------------------------------------------------
   设计稿把详情下半部分拆成四个带「竖条标题」的分区，这里统一成
   .dsec 外壳，右侧 .dsec-hint 放计数，内容区 .dsec-body 负责异步替换。
   ============================================================ */
.detail-extra { margin-top: var(--sp-6); }

.dsec { margin-bottom: var(--sp-5); }
.dsec:last-child { margin-bottom: 0; }
.dsec-head { display: flex; align-items: center; gap: 8px; margin-bottom: 9px; }
.dsec-t {
  margin: 0; display: flex; align-items: center; gap: 6px;
  font-size: var(--fs-md); font-weight: var(--fw-semi);
  color: var(--text); letter-spacing: .1px;
}
.dsec-t .ico-wrap { color: var(--text-3); }
.dsec-hint { margin-left: auto; font-size: var(--fs-xs); color: var(--text-3); white-space: nowrap; }

/* 「档案信息」原来这里有一个折叠开关（.dsec-fold 胶囊 + .darch-body[hidden]）。
   用户要求常驻展开后整体删除 —— 不给已经不再存在的元素留样式，
   否则以后有人看见这条规则会以为还有个折叠功能。 */

.dsec-none { font-size: var(--fs-md); color: var(--text-3); }

/* ---- 统一内容容器：所有分区的内容都装进 .dbox ----
   浅底 + 圆角 + 描边，内容行之间用 1px 分隔线 —— 全站只有这一种「块」。 */
.dbox {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
}
.dbox-pad { padding: 12px 14px; }

/* ---- 统一内容行：批次与领取记录共用 ----
   左圆形标记（头像字母 / 图标）+ 主信息 + 右侧数值与徽标。 */
.drow {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 11px 14px;
}
.drow + .drow { border-top: 1px solid var(--border); }
.drow-lead {
  width: 32px; height: 32px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-full);
  background: var(--surface-3); color: var(--text-3);
  font-size: var(--fs-xs); font-weight: var(--fw-bold); letter-spacing: .2px;
}
.drow-lead.is-ava { background: var(--primary-soft); color: var(--primary); }
.drow-main { flex: 1; min-width: 0; }
.drow-top { display: flex; align-items: baseline; gap: var(--sp-2); }
.drow-t {
  flex: 1; min-width: 0;
  font-size: var(--fs-md); font-weight: var(--fw-semi); color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.drow-t.is-lg { font-size: var(--fs-lg); }
.drow-sub { flex-shrink: 0; font-size: var(--fs-xs); color: var(--text-3); }
.drow-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px;
  margin-top: 4px; font-size: var(--fs-sm); color: var(--text-3);
}
.drow-note { color: var(--text-3); }
.drow-side {
  display: flex; flex-direction: column; align-items: flex-end; gap: 5px;
  flex-shrink: 0; text-align: right;
}
.drow-num {
  font-size: var(--fs-md); font-weight: var(--fw-semi); color: var(--text);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

.who-tag {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 8px; border-radius: var(--r-full);
  background: var(--accent-soft); color: var(--accent-dark);
  font-size: var(--fs-xs); font-weight: var(--fw-medium); line-height: 1.7;
}

/* 芯片：一排可扫读的标签（适用测试项目） */
.chip-line { display: flex; flex-wrap: wrap; gap: 8px; }
.chip-tag {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 11px; border-radius: var(--r-sm);
  background: var(--surface); color: var(--text-2);
  border: 1px solid var(--border-strong);
  font-size: var(--fs-sm); font-weight: var(--fw-medium); line-height: 1.6;
}
.chip-tag .ico-wrap { color: var(--text-3); }

/* ---- 档案信息：展开后的两列网格 ----
   11 项参考数据，两列排开比 11 行满宽少一半高度，也不再把下面的内容顶走。 */
.darch-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0;
}
.darch-cell {
  display: flex; align-items: baseline; gap: var(--sp-3);
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  min-width: 0;
}
.darch-cell:nth-child(odd):not(.is-wide) { border-right: 1px solid var(--border); }
/* 「备注」通栏：11 项是奇数，让最长的备注独占一行收尾，不留半格空白 */
.darch-cell.is-wide { grid-column: 1 / -1; border-right: 0; }
.darch-cell:last-child { border-bottom: 0; }
.darch-lab { flex: 0 0 84px; font-size: var(--fs-xs); color: var(--text-3); }
.darch-val {
  flex: 1; min-width: 0; font-size: var(--fs-md); color: var(--text);
  word-break: break-word;
}
.darch-tags { display: inline-flex; flex-wrap: wrap; gap: 5px; }

/* 口径脚注：写明「剩余」是推算出来的，不假装有逐批扣减 */
.dnote {
  display: flex; flex-wrap: wrap; gap: 3px 8px;
  margin-top: 10px; font-size: var(--fs-xs);
  color: var(--text-3); line-height: 1.6;
}
.dnote b { color: var(--text-2); font-weight: var(--fw-semi); }

/* 迷你空态：比页面级 .empty 矮，不占满整屏 */
.dmini {
  text-align: center; padding: 24px var(--sp-4);
  background: var(--surface-2);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-md);
  color: var(--text-3);
}
.dmini-ico { display: flex; justify-content: center; margin-bottom: 8px; color: var(--text-4); line-height: 1; }
.dmini b { display: block; color: var(--text-2); font-size: var(--fs-md); font-weight: var(--fw-semi); margin-bottom: 3px; }
.dmini p { margin: 0; font-size: var(--fs-sm); line-height: 1.6; }

/* 异步取数时的骨架（复用 13 段的 shimmer 动画） */
.dskel { padding: 2px 0; }
.dskel-line {
  height: 13px; border-radius: var(--r-full); margin: 12px 0;
  background: linear-gradient(100deg, var(--surface-3) 28%, var(--surface-2) 50%, var(--surface-3) 72%);
  background-size: 220% 100%;
  animation: shimmer 1.4s linear infinite;
}
.dskel-line:first-child { width: 58%; }
.dskel-line:nth-child(2) { width: 86%; }
.dskel-line:nth-child(3) { width: 44%; }

.stepper {
  display: inline-flex; align-items: center;
  border-radius: var(--r-sm); overflow: hidden;
  background: var(--surface-2);
  border: 1px solid var(--border);
}
.stepper button {
  width: 38px; height: 38px;
  background: transparent; color: var(--text-2);
  font-size: 17px; line-height: 1;
  border-radius: var(--r-full);
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.stepper button:hover { background: var(--accent-soft); color: var(--accent-dark); }
.stepper input {
  width: 60px; height: 38px;
  text-align: center; font-size: var(--fs-md); font-weight: var(--fw-semi);
  outline: none; background: transparent;
  border-left: 1px solid var(--border);
  border-right: 1px solid var(--border);
  -moz-appearance: textfield;
}
.stepper input::-webkit-outer-spin-button,
.stepper input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.stepper.is-lg button { width: 44px; height: 44px; font-size: 19px; }
.stepper.is-lg input  { width: 68px; height: 44px; font-size: var(--fs-base); }

.citem {
  display: flex; gap: var(--sp-3);
  padding: 14px 0; border-bottom: 1px solid var(--border);
}
.citem:last-child { border-bottom: 0; }
.citem-img {
  width: 68px; height: 68px; border-radius: var(--r-md);
  overflow: hidden; flex-shrink: 0; background: var(--img-bg);
}
.citem-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.citem-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.citem-name { font-size: var(--fs-md); font-weight: var(--fw-semi); line-height: var(--lh-snug); }
.citem-meta { font-size: var(--fs-xs); color: var(--text-3); }
.citem-row { display: flex; align-items: center; gap: 10px; margin-top: auto; }
.citem-row .spacer { margin-left: auto; }
.citem-remove {
  color: var(--text-3); font-size: var(--fs-xs);
  padding: 8px 6px; border-radius: var(--r-xs);
  transition: color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.citem-remove:hover { color: var(--danger); background: var(--danger-soft); }

/* ============================================================
   20. Toast
   ============================================================ */
.toast-root {
  position: fixed; z-index: 400;
  left: var(--sp-6); right: auto; bottom: var(--sp-6); top: auto;
  transform: none;
  display: flex; flex-direction: column; gap: 10px;
  align-items: flex-start; pointer-events: none;
}
.toast {
  background: #26353E; color: #fff;
  padding: 13px 20px; border-radius: var(--r-md);
  font-size: var(--fs-md); font-weight: var(--fw-normal);
  box-shadow: var(--sh-lg);
  min-width: 300px; max-width: 580px;
  display: flex; align-items: center; gap: 10px;
  border-left: 3px solid #6B7F8D;
  animation: toastIn var(--dur-slow) var(--ease-spring);
}
.toast.is-ok   { background: #065F46; border-left-color: #34D399; }
.toast.is-err  { background: #7F1D1D; border-left-color: #F87171; }
.toast.is-warn { background: #78350F; border-left-color: #FBBF24; }
@keyframes toastIn {
  from { opacity: 0; transform: translateY(18px) scale(.96); }
  to   { opacity: 1; transform: none; }
}

/* ============================================================
   21. 上传 / 拖拽 / 图库
   ============================================================ */
.uploader { display: flex; gap: var(--sp-3); align-items: flex-start; }
.upload-preview {
  width: 112px; height: 112px; border-radius: var(--r-md);
  border: 1.5px dashed var(--border-strong);
  background: var(--surface-2);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; flex-shrink: 0; cursor: pointer;
  color: var(--text-3); font-size: var(--fs-xs);
  text-align: center; padding: 8px;
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease),
              color var(--dur) var(--ease);
}
.upload-preview img { width: 100%; height: 100%; object-fit: cover; }
.upload-preview:hover {
  border-color: var(--accent); color: var(--accent-dark); background: var(--accent-soft);
}

.dropzone {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 6px; padding: var(--sp-7) var(--sp-4);
  text-align: center; cursor: pointer;
  border: 2px dashed var(--border-strong);
  border-radius: var(--r-lg);
  background: var(--surface-2); color: var(--text-2);
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease),
              color var(--dur) var(--ease);
}
.dropzone b { font-size: var(--fs-base); font-weight: var(--fw-semi); color: var(--text); }
.dropzone span { font-size: var(--fs-xs); color: var(--text-3); }
.dropzone .dropzone-ico { line-height: 1; color: var(--text-3); }
.dropzone:hover { border-color: var(--primary-line); background: var(--primary-soft); color: var(--primary); }
.dropzone.is-over { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-dark); }
.dropzone-img { padding: var(--sp-4); }
.dropzone-img .dropzone-ico { color: var(--text-3); }

.imp-sep {
  display: flex; align-items: center; gap: 10px;
  margin: 15px 0 11px; color: var(--text-3); font-size: var(--fs-xs);
}
.imp-sep::before, .imp-sep::after { content: ''; flex: 1; height: 1px; background: var(--border); }
.imp-sep-lab { font-weight: var(--fw-semi); color: var(--text-2); white-space: nowrap; }

.img-library {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(116px, 1fr));
  gap: var(--sp-3);
}
.lib-item {
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: var(--r-md);
  padding: 7px;
  display: flex; flex-direction: column; gap: 7px;
  align-items: center;
  transition: box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease),
              transform var(--dur) var(--ease);
  overflow: hidden;
}
.lib-item img {
  width: 100%; aspect-ratio: 1/1; object-fit: cover;
  border-radius: var(--r-sm); display: block;
}
.lib-item span {
  font-size: var(--fs-2xs); color: var(--text-3);
  text-align: center; line-height: 1.35;
}
.lib-item:hover { border-color: var(--primary-line); box-shadow: var(--sh-sm); }
.lib-item.is-active {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-light), var(--sh-sm);
}

/* ============================================================
   22. 响应式
   ============================================================ */

/* ---------- 触屏：取消粘滞 hover ---------- */
@media (hover: none), (pointer: coarse) {
  .pcard:hover { box-shadow: var(--sh-sm); transform: none; border-color: var(--border); }
  .pcard:hover .pcard-img img { transform: none; }
  .stat:hover { box-shadow: var(--sh-sm); transform: none; }
  .lib-item:hover { border-color: var(--border); box-shadow: none; transform: none; }
  .list-row:hover, .user-chip:hover { background: none; }
  .nav-item:hover { background: none; color: var(--text-2); }
  .nav-item.is-active:hover { background: var(--primary-soft); color: var(--primary); }
  .chip:hover { border-color: var(--border); color: var(--text-2); background: var(--surface); }
  .chip.is-active:hover { background: var(--primary); color: #fff; }
  .upload-preview:hover { border-color: var(--border-strong); color: var(--text-3); background: var(--surface-2); }
  .btn-primary:hover { background: var(--primary); box-shadow: var(--sh-sm); }
  .btn-add:hover { background: var(--primary); box-shadow: var(--sh-xs); }
  .btn-ghost:hover, .btn-line:hover { background: transparent; }
  .btn-fav:hover { background: var(--surface); color: var(--text-2); box-shadow: inset 0 0 0 1px var(--border); }
  .btn-danger:hover { background: transparent; }
  .sheet-close:hover { background: transparent; }
  .stepper button:hover { background: transparent; color: var(--text-2); }
  .citem-remove:hover { color: var(--text-3); background: transparent; }
  .chart-bar:hover, .chart-dot:hover { opacity: 1; }
  .icon-btn:hover { background: transparent; color: var(--text-2); }
  .topbar .btn-line:hover, .topbar .btn-ghost:hover { background: transparent; }
  .topbar .btn-primary:hover { background: #fff; }
  .topbar .icon-btn:hover { background: transparent; }
  .search:hover input { border-color: var(--border); }
  .select-sm:hover { border-color: var(--border); color: var(--text-2); }
  .table-wrap table.tbl tbody tr:hover { background: none; }
  .fold-head:hover .fold-title { color: inherit; }
  .cart-badge:hover { transform: none; }
  .pcard-name:hover { color: inherit; }
}

/* 通用按压反馈 */
.btn:active:not(:disabled),
.chip:active, .pcard:active, .btn-add:active,
.nav-item:active, .tab:active { transform: scale(.97); }
.pcard-img:active img { opacity: .85; }

/* ---------- ≤1240 ---------- */
@media (max-width: 1240px) {
  .sidebar { width: 224px; }
  .topbar { padding: 9px var(--sp-5); }
  .view { padding: var(--sp-5) var(--sp-5) 120px; }
}

/* ---------- ≤1100 ---------- */
@media (max-width: 1100px) {
  .sidebar { width: 208px; }
  .topbar { padding: 9px var(--sp-4); }
  .view { padding: var(--sp-4) var(--sp-4) 120px; }
  .grid, .skeleton-grid { grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }
}

/* ---------- ≤1024：平板横屏 ---------- */
@media (max-width: 1024px) {
  .dhero { grid-template-columns: 200px 1fr; }
  .detail-img { max-width: 360px; }
  /* 窄屏档案改单列：去掉中间那条竖分隔线，靠逐行下边框分隔 */
  .darch-grid { grid-template-columns: minmax(0, 1fr); }
  .darch-cell:nth-child(odd):not(.is-wide) { border-right: 0; }
  .two-col { grid-template-columns: 1fr; }
  .stats { grid-template-columns: repeat(3, 1fr); }
}

/* ---------- ≤880：抽屉 + 底部导航 + 触控尺寸 ---------- */
@media (max-width: 880px) {
  .only-mobile { display: inline-flex !important; }
  .only-desktop { display: none !important; }

  :root { --appbar-h: 60px; }

  .sidebar {
    display: flex;
    position: fixed; left: 0; top: 0; bottom: 0; z-index: 130;
    width: min(86vw, 306px);
    height: 100vh; height: 100dvh;
    border-right: 0; box-shadow: var(--sh-xl);
    transform: translateX(-103%);
    transition: transform .28s var(--ease);
    padding-left: env(safe-area-inset-left);
    will-change: transform;
  }
  .sidebar.is-open { transform: none; }
  .brand {
    height: auto;
    padding: calc(15px + env(safe-area-inset-top)) var(--sp-4) var(--sp-4);
  }
  .drawer-close { display: inline-flex; color: #fff; }
  .drawer-close:hover { background: rgba(255, 255, 255, .18); }
  body.drawer-open { overflow: hidden; }
  .scrim { display: block; }

  .topbar {
    padding: calc(8px + env(safe-area-inset-top)) 14px 8px;
    gap: 8px 10px; flex-wrap: wrap;
  }
  .topbar-left { flex: 1 1 auto; min-width: 0; }
  .page-title {
    font-size: var(--fs-lg);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .page-desc {
    font-size: var(--fs-2xs); margin-top: 0;
    display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden;
  }
  .topbar-right { gap: 7px; flex-wrap: wrap; justify-content: flex-end; }

  .view { padding: 14px 14px calc(var(--nav-h) + 92px); }
  body.has-cart .view { padding-bottom: calc(var(--nav-h) + 158px); }

  /* 触控目标 ≥44px */
  .btn { min-height: 44px; padding: 0 var(--sp-4); }
  .btn-sm { min-height: 40px; padding: 0 14px; font-size: var(--fs-md); }
  .btn-xs { min-height: 36px; padding: 0 12px; font-size: var(--fs-sm); }
  .btn-add { min-height: 44px; font-size: var(--fs-base); }
  .btn-fav { min-height: 44px; padding: 9px 16px; }
  .icon-btn { width: 44px; height: 44px; }
  .drawer-close { width: 40px; height: 40px; margin-left: auto; }
  .seg-btn { min-height: 42px; font-size: var(--fs-md); }
  .seg.is-sm .seg-btn { min-height: 34px; }
  .sheet-close { width: 44px; height: 44px; font-size: 16px; }
  .chip { padding: 0 17px; min-height: 40px; }
  .nav-item { min-height: 50px; padding: 0 11px; font-size: var(--fs-base); }
  .nav-item .nav-ico { width: 24px; }
  .user-chip { min-height: 54px; padding: 9px 10px; }
  .stepper button { width: 44px; height: 44px; font-size: 19px; }
  .stepper input { width: 64px; height: 44px; }
  .stepper.is-lg button { width: 48px; height: 48px; }
  .stepper.is-lg input { width: 72px; height: 48px; }
  .select-sm { padding: 11px 34px 11px 14px; min-height: 44px; }
  .citem-remove { padding: 10px 8px; font-size: var(--fs-sm); }
  .fold-head { min-height: 44px; }

  /* iOS 聚焦缩放：输入类 <16px 会自动放大页面 */
  input, select, textarea { font-size: 16px; }
  .search input, .select-sm, .field input, .field select,
  .field textarea, .stepper input { font-size: 16px; }
  .seg-btn, .seg.is-sm .seg-btn { font-size: 14px; }

  .grid, .skeleton-grid {
    grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
    gap: var(--sp-3);
  }
  .pcard-body { padding: 12px 12px 13px; }
  .pcard-name { font-size: var(--fs-md); min-height: 38px; }
  .price { font-size: var(--fs-lg); }
  .stats { gap: var(--sp-3); }
  .stat { padding: 15px var(--sp-4); }
  .stat-num { font-size: var(--fs-2xl); }
  .card-pad { padding: var(--sp-4); }
  .card-head { padding: 14px var(--sp-4); }
  .list-row { padding: 13px var(--sp-4); gap: var(--sp-3); }
  .kv-row { padding: 11px 0; }
  .kv-row dt { width: 86px; }
  .field-row-2 { grid-template-columns: 1fr; }
  .field-row { flex-wrap: wrap; }
  .field-row input { flex: 1 1 140px; }
  .stock-detail { grid-template-columns: 1fr 1fr; }

  .toolbar { gap: 8px; margin-bottom: var(--sp-3); }
  .toolbar .search { flex: 1 1 100%; max-width: none; }
  .toolbar .select-sm { flex: 1 1 auto; }
  .search input { padding: 12px 14px 12px 40px; }

  .overlay { padding: 0; align-items: flex-end; }
  .sheet {
    max-width: 100%; max-height: 94vh; max-height: 94dvh;
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    border-bottom: 0;
    animation: sheetUp .3s var(--ease-out);
  }
  @keyframes sheetUp { from { transform: translateY(100%); } to { transform: none; } }
  .sheet::before {
    content: ""; position: absolute; top: 8px; left: 50%;
    transform: translateX(-50%);
    width: 44px; height: 4px; border-radius: var(--r-full);
    background: var(--border-strong);
  }
  .sheet-head { padding: var(--sp-5) var(--sp-4) 13px; touch-action: none; }
  .sheet-body { padding: var(--sp-4); overscroll-behavior: contain; }
  .sheet-foot {
    padding: 13px var(--sp-4) calc(13px + env(safe-area-inset-bottom));
    flex-wrap: wrap; gap: 9px;
  }
  .sheet-foot .btn { flex: 1 1 auto; }

  .tabbar {
    display: flex;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 80;
    height: calc(var(--nav-h) + env(safe-area-inset-bottom));
    padding-bottom: env(safe-area-inset-bottom);
    padding-left: calc(4px + env(safe-area-inset-left));
    padding-right: calc(4px + env(safe-area-inset-right));
    background: var(--surface);
    border-top: 1px solid var(--border);
    box-shadow: 0 -3px 16px rgba(16, 36, 45, .08);
    overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none;
    overscroll-behavior-x: contain;
  }
  .tabbar::-webkit-scrollbar { display: none; }
  .tab {
    flex: 0 0 auto; min-width: 62px; min-height: 54px;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    gap: 3px; color: var(--text-3);
    font-size: var(--fs-2xs); font-weight: var(--fw-medium);
    position: relative; padding: 0 6px;
    overflow: hidden;
    transition: color var(--dur) var(--ease);
  }
  /* 尺寸由 Icons.el(name, 21) 决定，不再用 font-size 控图标大小 */
  .tab .tab-ico { line-height: 1; }
  .tab.is-active { color: var(--accent); font-weight: var(--fw-semi); }
  .tab.is-active::before {
    content: ""; position: absolute; top: 0; left: 50%;
    transform: translateX(-50%);
    width: 26px; height: 3px;
    border-radius: 0 0 3px 3px;
    background: var(--accent);
  }
  .tab .nav-badge {
    position: absolute; top: 6px; left: 50%; margin-left: 4px;
    background: var(--danger); color: #fff;
    font-size: var(--fs-2xs); font-weight: var(--fw-semi);
    min-width: 17px; height: 17px; border-radius: var(--r-full);
    padding: 0 4px;
    display: inline-flex; align-items: center; justify-content: center;
  }

  .cart-bar {
    bottom: calc(var(--nav-h) + env(safe-area-inset-bottom));
    max-width: 100%;
    padding: 9px 14px calc(9px + env(safe-area-inset-bottom));
    gap: 11px;
  }
  .cart-badge { width: 44px; height: 44px; font-size: 19px; }
  .cart-bar .btn { min-height: 44px; }
  .cart-total .amt { font-size: var(--fs-xl); }
  .cart-text b { font-size: var(--fs-md); }

  .toast-root {
    left: 12px; right: 12px;
    bottom: calc(var(--nav-h) + env(safe-area-inset-bottom) + 12px);
    align-items: stretch;
  }
  body.has-cart .toast-root { bottom: calc(var(--nav-h) + env(safe-area-inset-bottom) + 78px); }
  .toast { min-width: 0; }

  .uploader { flex-wrap: wrap; }
  .upload-preview { width: 104px; height: 104px; }
}

@media (min-width: 881px) {
  .cart-bar {
    max-width: var(--maxw); margin: 0 auto;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    border: 1px solid var(--border); border-bottom: 0;
  }
}

/* ---------- ≤700：宽表格降级为卡片 ---------- */
@media (max-width: 700px) {
  .table-wrap { overflow-x: visible; }
  /* min-width 必须连 .tbl-wide 一起清掉。
     .tbl-wide 写的是 table.tbl.tbl-wide { min-width: 1000px }，特异度 (0,2,1)，
     **高于**这条 table.tbl (0,1,1) —— 只写 table.tbl 的话，手机上台账表虽然已经
     变成卡片布局（display:block 生效），盒子却仍然被钉在 1000px 宽，
     于是整页布局视口被撑到 1012px（实测 innerWidth=1012 而视觉视口只有 375），
     连 position:fixed 的 scrim / 底部标签栏都跟着拉成 1012 ——
     手机上只看得到左边 375px，「操作」列（编辑/入库/移除）正好在看不见的那一侧，
     点开后的弹层也被按 1012px 布局，显示不完整。 */
  table.tbl, table.tbl.tbl-wide { min-width: 0; display: block; }
  table.tbl thead { display: none; }
  table.tbl tbody { display: block; }
  table.tbl tr {
    display: block; padding: 13px var(--sp-4);
    border-bottom: 1px solid var(--border);
  }
  table.tbl tbody tr:last-child { border-bottom: 0; }
  table.tbl tbody tr:hover { background: none; }
  table.tbl td {
    display: flex; align-items: flex-start; gap: 10px;
    padding: 5px 0; border: 0; color: var(--text-2);
  }
  table.tbl td::before {
    content: attr(data-label);
    flex: 0 0 84px; min-width: 84px;
    font-size: var(--fs-xs); font-weight: var(--fw-medium);
    color: var(--text-3); line-height: 1.7;
  }
  table.tbl td.num { text-align: left; justify-content: flex-start; }
  table.tbl td.td-block { padding: 0 0 4px; display: block; }
  table.tbl td.td-block::before { content: none; }
  table.tbl td.td-block .list-thumb { width: 58px; height: 58px; }
  table.tbl td.td-ops { padding-top: 10px; }
  table.tbl td[colspan] { display: block; padding: 0; }
  table.tbl td[colspan]::before { content: none; }
  /* 表格态为了让行高降到 ~56px，把三种「一行排开」写成了 nowrap。
     到了卡片态单元格是满宽、空间宽松，全部还原成可换行 ——
     否则长库位名、多一位数字或印尼语长词会把卡片顶出横向滚动条。
     （这三条必须在媒体查询里，不能写回 inline style：inline 会盖掉媒体查询。） */
  .ops-row { flex-wrap: wrap; justify-content: flex-start; white-space: normal; }
  table.tbl .stock-line { flex-wrap: wrap; justify-content: flex-start; }
  td.td-loc { white-space: normal; }

  /* 卡片态下「名称 / 品牌·规格」这两行是 **flex 项**，而 flex 项的 min-width 默认是 auto
     （= min-content）。耗材名里大量不带空格的长串 ——「NCS206034,400g.1D」、
     「136/25802/00,19.1mm,」—— min-content 就等于整串宽，于是卡片被顶出屏幕：
     实测 td 自身溢出 351>319、子元素 span.cell-sub 右溢 44px，
     在 375px 手机上表现为整页多出 4px 横向滚动、文字被切。
     给 min-width:0 + break-word，长串只在真放不下时才断开。 */
  table.tbl td.td-name .cell-name,
  table.tbl td.td-name .cell-sub { min-width: 0; overflow-wrap: break-word; }
}

/* ---------- ≤640：手机 ---------- */
@media (max-width: 640px) {
  /* 语言切换不再藏起来（原先 display:none，手机用户只剩「我的」里那一个入口）。
     顶栏右侧整体 flex-wrap，放不下会自动换到第二行；这里只把它收到内容宽。 */
  .seg.lang-seg { min-width: 0; }
  .seg.lang-seg .seg-btn { padding: 5px 11px; font-size: var(--fs-sm); min-height: 38px; }

  .grid, .skeleton-grid {
    grid-template-columns: repeat(auto-fill, minmax(152px, 1fr));
    gap: 11px;
  }

  /* 分组卡 / 二级菜单：窄屏降一档内距，仍保持 6 张一行能放下最小宽度 */
  .gcard { padding: 9px 10px; gap: 4px; }
  .gp-sec-head, .gp-sec.is-warn .list-row { padding-left: var(--sp-4); }
  .gp-sec.is-warn .list-row { padding-left: calc(var(--sp-4) - 3px); }

  .detail-extra { margin-top: var(--sp-5); }
  /* 手机：hero 改「小图 + 文字」横排（竖排会把库存三个数挤到首屏之外） */
  .dhero { grid-template-columns: 104px 1fr; gap: var(--sp-4); }
  .detail-name { font-size: var(--fs-xl); }
  .stock-detail { grid-template-columns: repeat(3, 1fr); gap: 7px; }
  .stock-cell { padding: 9px 10px; }
  .stock-cell .val { font-size: var(--fs-lg); }
  .drow-top { flex-wrap: wrap; gap: 2px 10px; }
  .drow-t { white-space: normal; overflow: visible; text-overflow: clip; }
  .drow { padding: 10px 12px; gap: var(--sp-2); }
  .drow-side { gap: 4px; }
  .dsec-head { flex-wrap: wrap; }
  .darch-cell { padding: 9px 11px; }
  .darch-lab { flex-basis: 74px; }
  .sheet-body { padding: 14px; }
  .sheet-head { padding: var(--sp-5) 14px 12px; }
  .sheet-foot { padding: 12px 14px calc(12px + env(safe-area-inset-bottom)); }
  .img-library { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 9px; }
  .chart-legend { gap: 9px 13px; }
  .notice { padding: 11px 13px; }
  .empty { padding: 48px var(--sp-4); }
  .toast { max-width: none; padding: 13px 17px; font-size: var(--fs-md); }
  .badge { font-size: var(--fs-xs); }
  .two-col { gap: var(--sp-3); }

  /* 手机上再把最小两档字阶抬一级。
     桌面 11.5px 还能看，手机不一样：11.5px 在 390pt 屏上物理高度只有 3mm 上下，
     中文笔画密的字（「验收量」「核定量」）几乎糊成一团 —— 用户说的「字体拥挤」
     主要就是这个观感，而不是元素互相压住（探针实测裁切 / 重叠 / 溢出都是 0）。
     只动最小的两档，--fs-sm 及以上保持不变，避免整页重排。
     印尼语同长度的词更多字母，抬这一档对它的收益更大。
     注：放在本块末尾是有意的 —— smoke-test 里有一条「窄屏语言段规则须在
     @media (max-width: 640px) 之后 400 字符内」的断言，插在前面会把它挤出去。 */
  :root {
    --fs-2xs: 12.5px;
    --fs-xs:  13px;
  }

  /* 带行动按钮的提示条（库存预警 / 效期预警的横幅）改成上下堆叠。
     横排时右侧的「一键加入提报单」是 nowrap 按钮，印尼语标签比中文长得多，
     会把正文挤到 58px 宽 —— 连 kedaluwarsa 这种词都放不下、溢出被裁。
     改成按钮独占一行后，正文拿到整行宽度，中印尼两端都不会再挤。 */
  .notice { flex-wrap: wrap; row-gap: 10px; }
  .notice > .btn { flex: 1 1 100%; }
}

/* ---------- ≤420：小屏手机 ---------- */
@media (max-width: 420px) {
  .topbar { padding-left: 11px; padding-right: 11px; gap: 8px; }
  .view { padding-left: 11px; padding-right: 11px; }
  .grid, .skeleton-grid {
    grid-template-columns: repeat(auto-fill, minmax(146px, 1fr));
    gap: 9px;
  }
  .pcard-name { font-size: var(--fs-md); }
  .btn-add { font-size: var(--fs-md); }
  .tab { min-width: 58px; padding: 0 5px; }
  .list-row { padding: 12px; gap: 10px; }
  .list-thumb { width: 46px; height: 46px; }
  .list-side .qty { font-size: var(--fs-base); }
  .stat { padding: 13px; }
  .stat-num { font-size: var(--fs-xl); }
  .stat-lab { font-size: var(--fs-sm); }
  .stat-sub { font-size: var(--fs-2xs); }
  .stock-detail { grid-template-columns: 1fr; }
  .citem { gap: 10px; }
  .citem-img { width: 62px; height: 62px; }
  .page-title { font-size: var(--fs-base); }
  .cart-text span { max-width: 88px; }
  table.tbl td::before { flex-basis: 76px; min-width: 76px; font-size: var(--fs-xs); }
  .kv-row dt { width: 78px; }
}

/* ---------- ≤360：超小屏 ---------- */
@media (max-width: 360px) {
  .tab { min-width: 54px; font-size: var(--fs-2xs); }
  .tab .tab-ico > svg { width: 18px; height: 18px; }
  .btn { padding: 0 13px; }
  .cart-badge { width: 42px; height: 42px; font-size: 18px; }
  .cart-total .amt { font-size: var(--fs-lg); }
  .price { font-size: var(--fs-base); }
}

/* ---------- 手机横屏 ---------- */
@media (max-width: 880px) and (orientation: landscape) and (max-height: 540px) {
  :root { --nav-h: 50px; --appbar-h: 50px; }
  .topbar { padding-top: 6px; padding-bottom: 6px; }
  .view { padding-top: 10px; }
  .sheet { max-height: 100vh; max-height: 100dvh; border-radius: 0; }
  .sidebar { width: min(62vw, 306px); }
  .tab { min-height: 46px; font-size: 11px; }
  .tab .tab-ico > svg { width: 17px; height: 17px; }
}

/* ============================================================
   23. 等宽数字
   ------------------------------------------------------------
   全站数字统一用 tabular-nums：比例字形下每个数字宽度不同，
   库存 / 金额 / 统计数字一变，整列就会左右抖一下。这是最容易被
   忽略、也最影响「精修感」的一条。
   ============================================================ */
.stat-num, .price, .price-min, .stock-line, .stock-cell .val,
.cart-total .amt, .cart-badge em, .nav-badge, .chip-n, .mono,
.badge, .list-sub, .kv-row dd, .rank-n, .chart-axis,
table.tbl th, table.tbl td { font-variant-numeric: tabular-nums; }

/* ============================================================
   24. 入场动效
   ------------------------------------------------------------
   两层，互相独立：
     · #view.is-enter  —— 切换标签页时整页淡入 + 上浮 8px。
                          只由 app.js 在「真的换了页」时加类，同页重绘不播。
     · .is-stagger     —— 网格 / 列表子项错峰入场，节奏在 CSS，JS 只写 --i。
   两者都只在「进页面」那一次播，筛选 / 搜索 / 排序不重播。
   ============================================================ */
@keyframes viewIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
#view.is-enter { animation: viewIn .28s var(--ease-out) both; }

@keyframes itemIn {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
.is-stagger > * {
  animation: itemIn .3s var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 28ms);   /* JS 写 --i，上限 8 → 最长 224ms */
}

/* 数字滚动的节拍感：滚动中不要因为字宽变化而抖动 */
.is-counting { font-variant-numeric: tabular-nums; }

/* 角标数值变化时弹一下，而不是静默替换 */
@keyframes badgePop {
  0%   { transform: scale(1); }
  45%  { transform: scale(1.28); }
  100% { transform: scale(1); }
}
.nav-badge.is-pop, .cart-badge em.is-pop { animation: badgePop .34s var(--ease-spring) both; }

/* ============================================================
   25. 我的权限清单
   —— 「我的」页按 perms.js 的能力矩阵列出当前身份能做什么。
      列数自适应：手机 1 列、平板 2 列、桌面 3 列（不写死断点，靠 auto-fill）。
   ============================================================ */
.perm-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(172px, 1fr));
  gap: 8px 14px;
}
.perm-item {
  display: flex; align-items: center; gap: 7px;
  min-width: 0; font-size: var(--fs-sm); color: var(--text-2);
}
.perm-item > svg, .perm-item > .ico-wrap {
  flex: none; color: var(--success);
}
.perm-item > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* ============================================================
   25.5 采购闭环账目（js/pipeline.js）
   ------------------------------------------------------------
   为什么单开一套：一个季度通常有 100 个品种要提报，从「提报」走到
   「验收入库」跨好几个月。原来的表格只有一个状态列，看不出积压在哪一段。
   这里给两件东西：
     · 分布条 + 图例 —— 按「当前停在哪一步」着色，长度即品种数占比，
       图例本身是筛选器（点一下只看该阶段）
     · 账目表 —— 每推进一步多一格数字 + 该步的日期，一格一行读下去
   颜色一律用语义色变量（--info / --warn / --success / --danger / --c10 …），
   不新增色，也不写死十六进制 —— 深浅色主题都跟着走。
   ============================================================ */
.pipe-block {
  display: flex; flex-direction: column; gap: 10px;
  padding: 14px var(--sp-4);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.pipe-sum { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.pipe-kpi { display: flex; align-items: baseline; gap: 5px; margin-right: var(--sp-1); }
.pipe-kpi b {
  font-size: var(--fs-2xl); font-weight: var(--fw-bold); color: var(--text);
  font-variant-numeric: tabular-nums; line-height: 1.1;
}
.pipe-kpi span { font-size: var(--fs-xs); color: var(--text-3); }

/* 分布条：空阶段不渲染节点，所以这里只管容器与两端圆角 */
.pipe-bar {
  display: flex; gap: 2px; height: 12px;
  background: var(--surface-3); border-radius: var(--r-full);
  overflow: hidden;
}
.pipe-bar i { display: block; min-width: 3px; border-radius: 2px; }
.pipe-bar i:first-child {
  border-top-left-radius: var(--r-full); border-bottom-left-radius: var(--r-full);
}
.pipe-bar i:last-child {
  border-top-right-radius: var(--r-full); border-bottom-right-radius: var(--r-full);
}

/* 图例 = 筛选器（触控目标 28px，手机上也点得中） */
.pipe-legend { display: flex; flex-wrap: wrap; gap: 6px; }
.pipe-lg {
  display: inline-flex; align-items: center; gap: 6px;
  /* 32px 是触摸目标的下限：图例这几个小胶囊在手机上是要点着筛的，
     28px 点起来偏窄（探针实测 28px 会进「<32px」名单）。 */
  min-height: 32px; padding: 4px 11px;
  border: 1px solid var(--border); border-radius: var(--r-full);
  background: var(--surface); color: var(--text-2);
  font-family: inherit; font-size: var(--fs-xs); cursor: pointer;
  transition: border-color var(--dur), background-color var(--dur), color var(--dur);
}
.pipe-lg b { font-weight: var(--fw-semi); color: var(--text); font-variant-numeric: tabular-nums; }
.pipe-lg:hover { border-color: var(--n-300); background: var(--surface-2); }
.pipe-lg.is-active { border-color: var(--text-3); background: var(--surface-3); color: var(--text); }
.pipe-dot { width: 8px; height: 8px; border-radius: var(--r-full); flex: none; }

/* 五步流程条：纯展示。箭头用字符而不是伪元素 —— 便于随语言/字体缩放一起走 */
.pipe-flow {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  font-size: var(--fs-xs); color: var(--text-2);
}
.pipe-step { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.pipe-arrow { color: var(--text-4); }

/* 列表行里的迷你阶段条（提报单列表用） */
.pipe-bar-sm { height: 6px; }
.pipe-mini { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.pipe-mini .pipe-bar { flex: 1 1 auto; max-width: 220px; }

/* 账目表：11 列，列多但每列窄 —— 数量格是「数字 + 小字日期」两行，
   比并排一行更好读，也不会在窄列里折成三行 */
table.tbl.tbl-pipe { min-width: 1060px; }
table.tbl.tbl-pipe th,
table.tbl.tbl-pipe td { padding: 8px 10px; }
table.tbl.tbl-pipe th { text-transform: none; letter-spacing: 0; }
table.tbl.tbl-pipe th.pfirst,
table.tbl.tbl-pipe td.pfirst { white-space: nowrap; }
.phash { font-size: var(--fs-2xs); color: var(--text-4); margin-left: 5px; }
.pfirst input[type="checkbox"],
.pipe-check input[type="checkbox"] {
  width: 16px; height: 16px; vertical-align: middle;
  accent-color: var(--brand-600); cursor: pointer;
}

.pitem { display: flex; align-items: center; gap: 9px; min-width: 0; }
.pitem .list-thumb { width: 34px; height: 34px; flex: none; }
.pitem-txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.pitem-txt b {
  font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 24ch;
}
.pitem-txt i {
  font-style: normal; font-size: var(--fs-2xs); color: var(--text-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 跨提报单账目里的「来自哪张提报单」徽标 */
.pitem-src { flex: none; font-size: var(--fs-2xs); padding: 1px 7px; }

.pcell { display: flex; flex-direction: column; align-items: flex-end; gap: 0; }
.pcell b {
  font-size: var(--fs-sm); font-weight: var(--fw-semi); color: var(--text);
  font-variant-numeric: tabular-nums;
}
.pcell i { font-style: normal; font-size: var(--fs-2xs); color: var(--text-4); white-space: nowrap; }
.pcell .pcell-extra { color: var(--text-3); font-weight: var(--fw-medium); }

.pipe-empty {
  padding: 22px var(--sp-4) !important;
  text-align: center; color: var(--text-3); font-size: var(--fs-sm);
}
.pipe-tools { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.pipe-check {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: var(--fs-sm); color: var(--text-2); cursor: pointer;
}
.pipe-note { font-size: var(--fs-xs); color: var(--text-3); line-height: var(--lh-base); }

/* ≤700px：表格降级成卡片（见 24 节），这里只把「右对齐的数字格」掰回左对齐，
   否则卡片里数字贴着右边、标签贴着左边，读起来要来回跳。 */
@media (max-width: 700px) {
  .pcell { align-items: flex-start; }
  .pitem-txt b { max-width: none; white-space: normal; }
  .pitem-txt i { white-space: normal; }
  .pipe-block { padding: 12px var(--sp-3); }
  table.tbl.tbl-pipe { min-width: 0; }
}

/* ============================================================
   26. 无障碍偏好适配
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    /* 错峰入场的延迟也要清零：否则子项会「先隐身、等 224ms 才出现」 */
    animation-delay: 0s !important;
  }
  .boot-mark, .skel { animation: none; }
}

@media (prefers-contrast: more) {
  :root {
    --border: #94A3B8;
    --border-strong: #64748B;
    --text-3: #43586A;
  }
  .card, .pcard, .stat { border-width: 1.5px; }
}

/* 打印：去装饰，留数据 */
@media print {
  .sidebar, .topbar, .tabbar, .cart-bar, .toolbar, .chip-row,
  .pcard-actions, .toast-root, .scrim { display: none !important; }
  body, html { background: #fff; color: #000; }
  .view { padding: 0; max-width: none; }
  .card, .pcard, .stat { box-shadow: none; border: 1px solid #999; }
  table.tbl th { background: #eee !important; color: #000; }
}

/* ============================================================
   27. 打印区（.print-area）
   ------------------------------------------------------------
   领料清单 / 提报清单用：平时 display:none，只有打印时才显示，
   并把 body 下其它内容（应用界面、弹层、toast）全部排除在输出之外。
   入口是 util.js 的 UI.printSheet()。
   ============================================================ */
.print-area { display: none; }

@media print {
  /* 打印区独占整页 */
  body > *:not(.print-area) { display: none !important; }
  .print-area { display: block !important; }

  .print-area .pr-head h1 { font-size: 17pt; margin: 0 0 4pt; color: #000; }
  .print-area .pr-head .pr-sub { font-size: 10.5pt; color: #333; margin-bottom: 10pt; }
  .print-area .pr-meta { font-size: 10pt; color: #333; line-height: 1.7; }
  .print-area .pr-rule { border: 0; border-top: 1.5pt solid #333; margin: 6pt 0 0; }

  .print-area table.tbl {
    width: 100%; min-width: 0; margin-top: 8pt;
    border-collapse: collapse; border-spacing: 0; font-size: 10pt;
  }
  .print-area table.tbl th,
  .print-area table.tbl td {
    border: .6pt solid #555 !important;
    padding: 4pt 6pt !important;
    font-size: 10pt !important;
    color: #000 !important;
    background: #fff !important;
    display: table-cell !important;      /* 覆盖 ≤700 的卡片式降级 */
    text-align: left;
  }
  .print-area table.tbl th { background: #eaeaea !important; font-weight: 700; }
  .print-area table.tbl td.num,
  .print-area table.tbl th.num { text-align: right; }
  .print-area table.tbl td::before { content: none !important; }   /* 去掉 data-label 伪元素 */
  .print-area table.tbl tr { display: table-row !important; padding: 0 !important; border: 0 !important; }
  .print-area table.tbl thead { display: table-header-group !important; }  /* 跨页重复表头 */
  .print-area table.tbl tbody { display: table-row-group !important; }

  .print-area .pr-total { margin-top: 6pt; font-size: 10.5pt; font-weight: 700; }
  .print-area .table-wrap { overflow: visible !important; }
  .print-area .pr-foot {
    margin-top: 10pt; display: flex; justify-content: space-between;
    font-size: 9.5pt; color: #333;
  }
  .print-area .pr-sign { margin-top: 26pt; display: flex; gap: 30pt; font-size: 10pt; color: #333; }
  .print-area .pr-sign span { flex: 1; border-bottom: .8pt solid #555; padding-bottom: 16pt; }

  /* 万一浏览器把打印页当窄屏处理，也不要把应用界面的骨架露出来 */
  .skel, .boot, .overlay { display: none !important; }
}

/* ============================================================
   28. 人员登录（js/staff.js）
   ------------------------------------------------------------
   人员名单为空时这一段用不到 —— 系统保持免登录。
   导入第一批人之后，登录卡会盖在启动页之上（z-index 950 > .boot 的 900），
   登录成功才放行首屏，避免未登录状态下先闪一下主界面。

   2026-10-14 重做：卡顶加一条品牌带（机构名 + 语言切换），关键文字中印尼并排。
   语言切换必须留在卡上 —— 「登录页没有双语」的根因就是它没有切换入口。
   ============================================================ */
.staff-login {
  position: fixed; inset: 0; z-index: 950;
  display: flex; align-items: center; justify-content: center;
  padding: var(--sp-4);
  background:
    radial-gradient(130% 90% at 50% -10%, var(--primary-soft) 0%, transparent 62%),
    var(--bg);
  overflow-y: auto;
}
.staff-login-card {
  width: 100%; max-width: 396px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  overflow: hidden;                    /* 品牌带的直角交给卡片圆角裁 */
  box-shadow: 0 24px 60px -26px rgba(16, 36, 45, .38);
}

/* ---- 品牌带：机构名（双语）+ 语言切换 ---- */
.staff-login-hero {
  display: flex; align-items: center; gap: 12px;
  padding: 15px var(--sp-5);
  background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
}
.staff-login-brand { display: flex; align-items: center; gap: 11px; min-width: 0; flex: 1; }
.staff-login-names { min-width: 0; }
.staff-login-names b {
  display: block; font-size: var(--fs-sm); color: #fff;
  font-weight: var(--fw-semi); letter-spacing: .2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.staff-login-names i {
  display: block; font-style: normal; margin-top: 2px;
  font-size: var(--fs-2xs); color: rgba(255, 255, 255, .70);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 语言切换压在品牌色上：底用半透明白，选中态反过来用白底 + 品牌字 */
.staff-login-lang { flex-shrink: 0; background: rgba(255, 255, 255, .13); }
.staff-login-lang .seg-btn { color: rgba(255, 255, 255, .84); }
.staff-login-lang .seg-btn:hover { color: #fff; }
.staff-login-lang .seg-btn.is-active {
  background: #fff; color: var(--primary-dark);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .18);
}

/* ---- 卡身 ---- */
.staff-login-body { padding: var(--sp-5); }
.staff-login-title {
  margin: 0; font-size: var(--fs-xl); font-weight: var(--fw-bold);
  color: var(--text); letter-spacing: .2px;
}
.staff-login-titlesub { margin: 4px 0 0; font-size: var(--fs-xs); color: var(--text-3); }
.staff-login-sub { margin: 16px 0 0; font-size: var(--fs-sm); color: var(--text-2); }
/* 「请用姓名或工号登录」的另一语言那行：印尼同事最需要读懂的就是这句操作指引 */
.staff-login-subalt { margin: 3px 0 16px; font-size: var(--fs-2xs); color: var(--text-4); }
/* 两个输入框之间的留白（原先靠 JS 塞一个空 div，现在交给 CSS） */
.staff-login-body .field + .field { margin-top: 13px; }
/* label 后面跟另一种语言的小字：中印尼并排，切换之前两边的人都能读 */
.field-alt { font-style: normal; margin-left: 6px; font-size: var(--fs-2xs); color: var(--text-4); }
.staff-login-submit { margin-top: 18px; }
.staff-login-foot { margin: var(--sp-5) 0 0; text-align: center; }
.staff-login-foot span {
  display: block; font-size: var(--fs-2xs); color: var(--text-3); line-height: var(--lh-base);
}
.staff-login-foot i {
  display: block; margin-top: 3px; font-style: normal;
  font-size: var(--fs-2xs); color: var(--text-4); line-height: var(--lh-base);
}
/* 登录 / 改密码的即时错误提示（两处共用） */
.staff-login-err {
  font-size: var(--fs-xs); color: var(--danger-text);
  margin: 11px 0 0; line-height: var(--lh-base);
}
