/* ============================================================================
   组件样式 Components
   ----------------------------------------------------------------------------
   规则：页面只允许用这里的类，禁止在页面里写自定义样式。
   需要新样式 → 在这里加组件，不要在页面里临时写。
   ========================================================================== */

/* ==========================================================================
   1. 应用骨架
   ========================================================================== */
.app-shell {
  display: flex;
  min-height: 100vh;
}

/* 侧边导航（桌面 / 平板横屏 / 收银机） */
.side-nav {
  width: var(--sidebar-w);
  height: 100vh;              /* 显式高度，避免 inset 在某些环境失效 */
  flex-shrink: 0;
  background: #121a2b;
  color: #c8d2e4;
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 0;
  left: 0;
  z-index: var(--z-nav);
  overflow: hidden;
  transition: width var(--t-base) var(--ease);
}
.side-nav__brand {
  padding: var(--sp-md);
  font-size: var(--fs-lg);
  font-weight: 600;
  color: #fff;
  border-bottom: 1px solid rgba(255,255,255,.08);
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  min-height: 64px;
}
.side-nav__items { flex: 1; padding: var(--sp-sm); overflow-y: auto; }
.side-nav__item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 12px;
  border-radius: var(--r-md);
  color: #a9b6cc;
  font-size: var(--fs-base);
  cursor: pointer;
  margin-bottom: 2px;
  transition: background var(--t-fast);
  min-height: var(--touch-min);
}
.side-nav__item:hover { background: rgba(255,255,255,.06); color: #fff; }
.side-nav__item.is-active { background: var(--c-primary); color: #fff; font-weight: 500; }
.side-nav__icon { width: 20px; text-align: center; flex-shrink: 0; }
/* 日常入口和「手册 / 系统」之间拉一条线。不分组的话，
   十三个入口平铺下来，找「盘点」得从头扫一遍。 */
.side-nav__sep {
  height: 1px;
  margin: 8px 12px;
  background: rgba(255, 255, 255, .09);
}

/* 主内容区 */
.app-main {
  flex: 1;
  margin-left: var(--sidebar-w);
  padding: var(--page-pad);
  min-width: 0;              /* 防止 flex 子项撑破 */
  max-width: var(--content-max);
  transition: margin-left var(--t-base) var(--ease);
}

/* 手机：隐藏侧栏，改底部导航 */
@media (max-width: 639px) {
  .side-nav { display: none; }
  .app-main { margin-left: 0; padding-bottom: 72px; }
}

/* 设备显隐（双视图页面用：同一份数据，两套 DOM） */
.show-mobile { display: none !important; }
@media (max-width: 639px) {
  .show-desktop { display: none !important; }
  .show-mobile  { display: block !important; }
}

/* 平板（640~1023）：侧栏收成图标，只留符号。
   具体规则写在 .side-nav__* 组件定义之后（文件靠后位置）——媒体查询不加特异度，
   同特异度比的是先后，写在这里会被后面的 .side-nav__logout{display:flex} 盖掉。
   这坑踩过一次：1000px 宽下登出按钮没藏住，挤在 64px 侧栏底部。 */

/* 横屏手机：左栏收成图标 */
@media (max-height: 500px) and (orientation: landscape) {
  .side-nav { width: var(--sidebar-w-icon); }
  .side-nav__brand span:not(.side-nav__icon),
  .side-nav__item span:not(.side-nav__icon) { display: none; }
  .app-main { margin-left: var(--sidebar-w-icon); }
}

/* 底部导航（只在手机） */
.bottom-nav {
  display: none;
  position: fixed;
  inset: auto 0 0 0;
  height: 60px;
  background: var(--c-card);
  border-top: 1px solid var(--c-border);
  padding-bottom: env(safe-area-inset-bottom);   /* iPhone 底部安全区 */
  z-index: var(--z-nav);
}
@media (max-width: 639px) {
  .bottom-nav { display: flex; }
  .bottom-nav__item {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    font-size: var(--fs-xs);
    color: var(--c-text-3);
    min-height: var(--touch-min);
  }
  .bottom-nav__item.is-active { color: var(--c-primary); }
  .bottom-nav__icon { font-size: 20px; line-height: 1; }
}

/* ==========================================================================
   2. 页面结构
   ========================================================================== */
.page-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-md);
  margin-bottom: var(--sp-md);
  flex-wrap: wrap;
}
.page-head__title { font-size: var(--fs-title); font-weight: 600; }
.page-head__desc { font-size: var(--fs-xs); color: var(--c-text-3); margin-top: 2px; }
.page-head__actions { display: flex; gap: var(--sp-sm); flex-wrap: wrap; }

/* 工具栏：搜索 + 筛选 + 操作 */
.toolbar {
  display: flex;
  gap: var(--sp-sm);
  align-items: center;
  margin-bottom: var(--sp-md);
  flex-wrap: wrap;
}
.toolbar__search { flex: 1; min-width: 200px; max-width: 360px; }
.toolbar__spacer { flex: 1; }

/* 平板及更窄：搜索框独占一行。
   800 宽时侧栏 64 + 三个筛选控件挤在一起，输入框只剩 200px，
   占位文字被截成「名称 / 品牌 / 型号 / 编……」，看着像坏了。 */
@media (max-width: 1023px) {
  .toolbar__search { max-width: none; flex-basis: 100%; }
}
@media (max-width: 639px) {
  .page-head { margin-bottom: var(--sp-sm); }
}

/* 有固定底部操作条的页面，内容要给操作条让位，否则最后一行被压住 */
@media (max-width: 639px) {
  .has-action-bar { padding-bottom: 76px; }
}

/* ==========================================================================
   3. 卡片
   ========================================================================== */
.card {
  background: var(--c-card);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--sp-md);
  margin-bottom: var(--sp-md);
}
.card--flush { padding: 0; overflow: hidden; }
.card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-sm);
  padding: var(--sp-md);
  border-bottom: 1px solid var(--c-border);
  font-weight: 600;
}
.card__body { padding: var(--sp-md); }

/* 统计卡（首页看板） */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--sp-md);
  margin-bottom: var(--sp-md);
}
@media (max-width: 639px) {
  .stat-grid { grid-template-columns: repeat(2, 1fr); gap: var(--sp-sm); }
  /* 28px 的数字在 184px 格子里放不下 ¥391,280.00，会把整页顶宽 4~8px */
  .stat__value { font-size: 22px; }
}
/* 平板竖屏 800px：auto-fit 会排出 3 列 + 1 个孤儿，四张卡排成 2×2 才齐 */
@media (min-width: 640px) and (max-width: 1023px) {
  .stat-grid { grid-template-columns: repeat(2, 1fr); }
}
.stat {
  /* grid 子项默认 min-width:auto，内容（比如 ¥391,280.00）会把列撑破，
     整页跟着横向溢出。允许收缩，数字自己再想别的办法。 */
  min-width: 0;
  background: var(--c-card);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  padding: var(--sp-md);
  border-left: 3px solid var(--c-border);
}
.stat--primary { border-left-color: var(--c-primary); }
.stat--success { border-left-color: var(--c-success); }
.stat--warning { border-left-color: var(--c-warning); }
.stat--danger  { border-left-color: var(--c-danger); }
.stat__label { font-size: var(--fs-xs); color: var(--c-text-2); margin-bottom: var(--sp-xs); }
.stat__value { font-size: var(--fs-stat); font-weight: 700; letter-spacing: -.5px; }
.stat__value small { font-size: var(--fs-head); font-weight: 500; color: var(--c-text-3); margin-left: 4px; }
.stat__hint { font-size: var(--fs-xs); color: var(--c-text-3); margin-top: 2px; }

/* ==========================================================================
   4. 按钮
   ========================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: var(--h-btn);
  padding: 0 var(--sp-md);
  border-radius: var(--r-md);
  border: 1px solid var(--c-border);
  background: var(--c-card);
  color: var(--c-text);
  font-size: var(--fs-lg);
  font-weight: 500;
  white-space: nowrap;
  transition: background var(--t-fast), border-color var(--t-fast), opacity var(--t-fast);
  min-height: var(--touch-min);
  user-select: none;
}
.btn:hover:not(:disabled) { background: var(--c-hover); border-color: var(--c-border-strong); }
.btn:active:not(:disabled) { transform: translateY(1px); }

.btn--primary {
  background: var(--c-primary);
  border-color: var(--c-primary);
  color: #fff;
}
.btn--primary:hover:not(:disabled) { background: var(--c-primary-dark); border-color: var(--c-primary-dark); }

.btn--danger { background: var(--c-danger); border-color: var(--c-danger); color: #fff; }
.btn--danger:hover:not(:disabled) { opacity: .9; }

.btn--ghost { background: transparent; border-color: transparent; color: var(--c-text-2); }
.btn--ghost:hover:not(:disabled) { background: var(--c-hover); color: var(--c-primary); }

.btn--sm {
  height: var(--h-btn-sm);
  padding: 0 12px;
  font-size: var(--fs-table);
  border-radius: var(--r-sm);
}

.btn--block { width: 100%; }
.btn:disabled { opacity: .45; }

/* 手机：主操作按钮固定底部（不随内容滚动） */
.action-bar {
  display: none;
}
@media (max-width: 639px) {
  .action-bar {
    display: flex;
    gap: var(--sp-sm);
    position: fixed;
    inset: auto 0 60px 0;
    padding: var(--sp-sm) var(--page-pad);
    background: var(--c-card);
    border-top: 1px solid var(--c-border);
    padding-bottom: calc(var(--sp-sm) + env(safe-area-inset-bottom));
    z-index: var(--z-sticky);
  }
  .action-bar .btn { flex: 1; }
}

/* ==========================================================================
   5. 表单
   ========================================================================== */
.field { margin-bottom: var(--sp-md); }
.field__label {
  display: block;
  font-size: var(--fs-head);
  color: var(--c-text-2);
  font-weight: 500;
  margin-bottom: 6px;
}
.field__label .req { color: var(--c-danger); margin-left: 2px; }
.field__hint { font-size: var(--fs-xs); color: var(--c-text-3); margin-top: 4px; }

/* 错误提示：预留固定高度，出现时不推挤下方元素（防布局跳动） */
.field__error {
  font-size: var(--fs-xs);
  color: var(--c-danger);
  margin-top: 4px;
  min-height: 17px;
  line-height: 17px;
  opacity: 0;
  transition: opacity var(--t-fast);
}
.field__error.show { opacity: 1; }
/* 紧凑表单可关掉预留高度 */
.field--compact .field__error { min-height: 0; }

.input, .select, .textarea {
  width: 100%;
  height: var(--h-input);
  padding: 0 12px;
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-card);
  font-size: var(--fs-lg);
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
  min-height: var(--touch-min);
}
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--c-primary);
  box-shadow: 0 0 0 3px rgba(30,94,255,.12);
}
.input:disabled, .select:disabled {
  background: var(--c-bg);
  color: var(--c-text-3);
}
.input.is-error { border-color: var(--c-danger); }
.input.is-error:focus { box-shadow: 0 0 0 3px rgba(232,64,42,.12); }

.textarea { height: auto; min-height: 80px; padding: 10px 12px; resize: vertical; }

/* 表单行：桌面多列，手机单列 */
.form-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--sp-md);
}
@media (max-width: 639px) {
  .form-row { grid-template-columns: 1fr; gap: 0; }
}

/* 数字输入（进销存专用：− 数字 +） */
.qty-input {
  display: inline-flex;
  align-items: stretch;
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  overflow: hidden;
  height: var(--h-input);
  min-height: var(--touch-min);
}
.qty-input__btn {
  width: var(--touch-min);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  color: var(--c-text-2);
  background: var(--c-bg);
  transition: background var(--t-fast);
}
.qty-input__btn:hover { background: var(--c-border); }
.qty-input__btn:disabled { color: var(--c-border-strong); }
.qty-input__field {
  width: 80px;
  text-align: center;
  border: none;
  border-left: 1px solid var(--c-border);
  border-right: 1px solid var(--c-border);
  font-size: var(--fs-lg);
  font-variant-numeric: tabular-nums;
  background: var(--c-card);
}
.qty-input__field:focus { outline: none; background: var(--c-hover); }

/* 搜索框 */
.search-box { position: relative; }
.search-box .input { padding-left: 36px; }
.search-box__icon {
  position: absolute;
  left: 12px; top: 50%;
  transform: translateY(-50%);
  color: var(--c-text-3);
  pointer-events: none;
}

/* ==========================================================================
   6. 表格（桌面 / 平板）
   ========================================================================== */
.table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  /* 底部渐变遮罩：暗示"下面还有内容" */
  position: relative;
}
.table-wrap--scrollable::after {
  content: '';
  position: absolute;
  inset: auto 0 0 0;
  height: 24px;
  background: linear-gradient(to bottom, rgba(255,255,255,0), var(--c-card));
  pointer-events: none;
}

.data-table {
  width: 100%;
  font-size: var(--fs-table);
}
.data-table th {
  text-align: left;
  font-size: var(--fs-head);
  font-weight: 500;
  color: var(--c-text-2);
  background: var(--c-hover);
  padding: 10px var(--sp-sm);
  border-bottom: 1px solid var(--c-border);
  white-space: nowrap;
  position: sticky;
  top: 0;
  z-index: 1;
}
.data-table td {
  /* 上下留 6px：名称下面还有一行规格，不留白两行字会贴在一起 */
  padding: 6px var(--sp-sm);
  height: var(--h-row);
  border-bottom: 1px solid var(--c-border);
  vertical-align: middle;
  line-height: 1.4;
}
.data-table tbody tr { transition: background var(--t-fast); }
.data-table tbody tr:hover { background: var(--c-hover); }
.data-table tbody tr.is-clickable { cursor: pointer; }
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table .num { text-align: right; font-variant-numeric: tabular-nums; }

/* 平板：隐藏次要列（由页面给列加 .col-secondary） */
@media (max-width: 1279px) {
  .data-table .col-secondary { display: none; }
}

/* ==========================================================================
   7. 移动卡片列表（表格的手机形态）
   ========================================================================== */
.card-list { display: flex; flex-direction: column; gap: var(--sp-sm); }

.list-item {
  background: var(--c-card);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  padding: var(--sp-md);
  transition: background var(--t-fast);
}
.list-item:active { background: var(--c-hover); }
.list-item__title {
  font-size: var(--fs-lg);
  font-weight: 600;
  margin-bottom: 2px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-sm);
}
.list-item__sub {
  font-size: var(--fs-xs);
  color: var(--c-text-3);
  margin-bottom: var(--sp-sm);
}
.list-item__body {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-md);
  font-size: var(--fs-table);
  padding-top: var(--sp-sm);
  border-top: 1px dashed var(--c-border);
}
.list-item__meta { color: var(--c-text-2); }
.list-item__meta strong { color: var(--c-text); font-weight: 600; }
.list-item__actions {
  display: flex;
  gap: var(--sp-sm);
  margin-top: var(--sp-sm);
  padding-top: var(--sp-sm);
  border-top: 1px solid var(--c-border);
}

/* ==========================================================================
   8. 徽章 / 状态
   ========================================================================== */
.badge {
  display: inline-flex;
  align-items: center;
  padding: 1px 8px;
  border-radius: var(--r-full);
  font-size: var(--fs-xs);
  font-weight: 500;
  line-height: 1.7;
  white-space: nowrap;
}
/* 语义固定：同一状态全站同一个颜色，不许改 */
.badge--danger  { background: var(--c-danger-light);  color: var(--c-danger); }
.badge--warning { background: var(--c-warning-light); color: var(--c-warning); }
.badge--success { background: var(--c-success-light); color: var(--c-success); }
.badge--info    { background: var(--c-info-light);    color: var(--c-primary); }
.badge--muted   { background: var(--c-bg);            color: var(--c-text-2); }

/* 状态点 */
.dot {
  display: inline-block;
  width: 6px; height: 6px;
  border-radius: 50%;
  margin-right: 6px;
  vertical-align: middle;
}
.dot--danger  { background: var(--c-danger); }
.dot--warning { background: var(--c-warning); }
.dot--success { background: var(--c-success); }
.dot--muted   { background: var(--c-text-3); }

/* ==========================================================================
   9. 弹窗
   ========================================================================== */
.modal-mask {
  position: fixed;
  inset: 0;
  background: var(--c-mask);
  z-index: var(--z-mask);
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--sp-md);
}
.modal-mask.is-open { display: flex; }

.modal {
  background: var(--c-card);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-mask);
  width: 100%;
  max-width: 560px;
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  animation: modal-in var(--t-base) var(--ease);
}
.modal--wide { max-width: 900px; }

.modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-md);
  border-bottom: 1px solid var(--c-border);
  font-size: var(--fs-lg);
  font-weight: 600;
  flex-shrink: 0;
}
.modal__close {
  width: var(--touch-min);
  height: var(--touch-min);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--c-text-3);
  font-size: 22px;
  border-radius: var(--r-md);
}
.modal__close:hover { background: var(--c-hover); color: var(--c-text); }
.modal__body { padding: var(--sp-md); overflow-y: auto; flex: 1; }
.modal__foot {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-sm);
  padding: var(--sp-md);
  border-top: 1px solid var(--c-border);
  flex-shrink: 0;
}

@keyframes modal-in {
  from { opacity: 0; transform: translateY(8px) scale(.98); }
  to   { opacity: 1; transform: none; }
}

/* 手机：底部抽屉，拇指够得到 */
@media (max-width: 639px) {
  .modal-mask { align-items: flex-end; padding: 0; }
  .modal {
    max-width: none;
    max-height: 90vh;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    padding-bottom: env(safe-area-inset-bottom);
    animation: sheet-in var(--t-base) var(--ease);
  }
  .modal__foot .btn { flex: 1; }
  @keyframes sheet-in {
    from { transform: translateY(100%); }
    to   { transform: none; }
  }
}

/* ==========================================================================
   10. 提示 Toast
   ========================================================================== */
.toast-wrap {
  position: fixed;
  /* 右下角，不是右上角：右上角是「新建商品」这类主操作的位置，
     提示盖上去正好挡住要去点的地方。 */
  bottom: calc(var(--sp-md) + 8px);
  right: var(--sp-md);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--sp-sm);
  pointer-events: none;
}
@media (max-width: 639px) {
  .toast-wrap { top: auto; bottom: 80px; left: var(--page-pad); right: var(--page-pad); }
  /* 页面底部还有一条快捷操作条时（新建商品/保存/确认导入），提示要让开它，
     否则「保存成功」的提示正好盖在「保存」按钮上，下一次就点错了。 */
  body:has(.action-bar) .toast-wrap { bottom: calc(80px + 64px); }
  /* 开单页的提交条也是吸底的，同样要让开 */
  body:has(.submit-bar) .toast-wrap { bottom: calc(80px + 76px); }
}
/* 桌面和平板上提交条一样在底部，提示要抬到它上面 */
@media (min-width: 640px) {
  body:has(.submit-bar) .toast-wrap { bottom: 104px; }
}
.toast {
  padding: 10px var(--sp-md);
  border-radius: var(--r-md);
  color: #fff;
  font-size: var(--fs-table);
  box-shadow: var(--shadow-lg);
  max-width: 380px;
  animation: toast-in var(--t-base) var(--ease);
}
.toast--success { background: var(--c-success); }
.toast--error   { background: var(--c-danger); }
.toast--info    { background: var(--c-primary); }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: none; }
}

/* ==========================================================================
   11. 空状态 / 加载
   ========================================================================== */
.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 56px var(--sp-md);
  color: var(--c-text-3);
  text-align: center;
}
.empty__icon { font-size: 40px; opacity: .4; margin-bottom: var(--sp-sm); }
.empty__text { font-size: var(--fs-table); margin-bottom: var(--sp-md); }

/* 骨架屏（不要用转圈） */
.skeleton {
  background: linear-gradient(90deg, var(--c-bg) 25%, #e9eef5 37%, var(--c-bg) 63%);
  background-size: 400% 100%;
  animation: skeleton 1.4s ease infinite;
  border-radius: var(--r-sm);
  height: 16px;
}
@keyframes skeleton {
  from { background-position: 100% 50%; }
  to   { background-position: 0 50%; }
}

/* ==========================================================================
   12. 分页
   ========================================================================== */
.pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-md);
  padding: var(--sp-md) 0 0;
  font-size: var(--fs-table);
  color: var(--c-text-2);
}
.pager__pages { display: flex; align-items: center; gap: var(--sp-xs); }
.pager__btn {
  min-width: 36px;
  height: 36px;
  padding: 0 8px;
  border-radius: var(--r-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--c-text-2);
}
.pager__btn:hover { background: var(--c-hover); }
.pager__btn.is-active { background: var(--c-primary); color: #fff; }
.pager__btn:disabled { color: var(--c-border-strong); }

@media (max-width: 639px) {
  .pager { justify-content: center; }
  .pager__total { display: none; }
}

/* ==========================================================================
   13. 列表内滚动提示（电脑端专用）
   ========================================================================== */
/* 只在鼠标设备显示"共 N 条"的提示，触摸设备靠直觉滑动 */
.scroll-hint {
  display: none;
  font-size: var(--fs-xs);
  color: var(--c-text-3);
  text-align: center;
  padding: var(--sp-xs);
}
@media (pointer: fine) {
  .scroll-hint { display: block; }
}

/* ==========================================================================
   14. 输入校验状态
   --------------------------------------------------------------------------
   三态：idle 无痕 / valid 轻量确认 / invalid 红框+文案
   设计取舍：valid 不放大面积绿色，只用细边框+对勾，避免满屏花花绿绿
   ========================================================================== */

/* 通过：细绿边 + 右侧对勾 */
.input.is-valid {
  border-color: #86d0a4;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%2316a34a' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='M3 8.5l3.5 3.5L13 5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 16px;
  padding-right: 34px;
}
.input.is-valid:focus {
  border-color: var(--c-success);
  box-shadow: 0 0 0 3px rgba(22,163,74,.12);
}

/* 错误：红边加粗一点，误差更醒目 */
.input.is-error,
.select.is-error {
  border-color: var(--c-danger);
  background-color: #fffafa;
}
.input.is-error:focus,
.select.is-error:focus {
  border-color: var(--c-danger);
  box-shadow: 0 0 0 3px rgba(232,64,42,.14);
}

/* 校验中的输入框：统一去掉浏览器自带校验气泡 */
.input:invalid { box-shadow: none; }

/* ==========================================================================
   15. 输入框前缀 / 后缀（币种、单位）
   --------------------------------------------------------------------------
   关键：¥ 是视觉装饰，不是 value 的一部分。
   用户复制走的是纯数字，不会把符号带进数据库。
   ========================================================================== */
.affix-wrap {
  position: relative;
  display: flex;
  align-items: center;
}
.affix-wrap .input { padding-left: 30px; }
.affix-wrap__prefix {
  position: absolute;
  left: 12px;
  font-size: var(--fs-lg);
  color: var(--c-text-2);
  pointer-events: none;      /* 点击穿透到 input */
  user-select: none;
}
.affix-wrap--suffix .input {
  padding-left: 12px;
  padding-right: 44px;
}
.affix-wrap__suffix {
  position: absolute;
  right: 12px;
  font-size: var(--fs-head);
  color: var(--c-text-3);
  pointer-events: none;
  user-select: none;
}
/* 数字类输入统一等宽数字，位数对齐好核对 */
.input--num {
  font-variant-numeric: tabular-nums;
  text-align: right;
  letter-spacing: .2px;
}

/* ==========================================================================
   16. 快捷值按钮（能点击就不打字）
   --------------------------------------------------------------------------
   用在：数量 1/5/10、日期 今天/昨天、付款方式、常见规格
   规则：一行最多 5 个，超出换行；点击即填，再点取消
   ========================================================================== */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-sm);
}
.chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 36px;
  padding: 0 14px;
  border: 1px solid var(--c-border);
  border-radius: 999px;
  background: var(--c-card);
  font-size: var(--fs-table);
  color: var(--c-text-2);
  cursor: pointer;
  transition: all var(--t-fast);
  white-space: nowrap;
  font-family: inherit;
}
.chip:hover { border-color: var(--c-border-strong); background: var(--c-hover); }
.chip.is-active {
  border-color: var(--c-primary);
  background: var(--c-primary-light);
  color: var(--c-primary);
  font-weight: 500;
}
@media (pointer: coarse), (any-pointer: coarse) {
  .chip { min-height: 44px; padding: 0 18px; }
}

/* ==========================================================================
   17. 联想下拉（打字时实时检索）
   --------------------------------------------------------------------------
   用法：输入 ≥1 字触发，200ms 防抖，支持拼音首字母与错别字纠错
   ========================================================================== */
.suggest {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  max-height: 280px;
  overflow-y: auto;
  background: var(--c-card);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg, 0 8px 24px rgba(20,32,60,.14));
  z-index: var(--z-sticky);
  display: none;
  /* 容器不拦截点击：点空白处可穿透到下方控件，避免遮挡操作 */
  pointer-events: none;
}
.suggest.show { display: block; }
.suggest__item,
.suggest__correct,
.suggest__empty { pointer-events: auto; }
.suggest__item {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  padding: 10px var(--sp-md);
  cursor: pointer;
  font-size: var(--fs-table);
  border-bottom: 1px solid #f2f5fa;
}
.suggest__item:last-child { border-bottom: none; }
.suggest__item:hover,
.suggest__item.is-active { background: var(--c-primary-light); }
.suggest__main { flex: 1; min-width: 0; }
.suggest__title {
  color: var(--c-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.suggest__title mark {
  background: #fff2b8;
  color: inherit;
  padding: 0 1px;
  border-radius: 2px;
}
.suggest__meta {
  font-size: var(--fs-xs);
  color: var(--c-text-3);
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.suggest__side {
  font-size: var(--fs-xs);
  color: var(--c-text-2);
  text-align: right;
  white-space: nowrap;
}
.suggest__empty {
  padding: 18px var(--sp-md);
  text-align: center;
  font-size: var(--fs-xs);
  color: var(--c-text-3);
}
/* 纠错提示条：输入"健盘"时提示"您是不是想找：键盘" */
.suggest__correct {
  padding: 8px var(--sp-md);
  background: var(--c-warning-light);
  font-size: var(--fs-xs);
  color: #8a5a00;
  border-bottom: 1px solid #f5e2c0;
}
.suggest__correct b { color: var(--c-warning); }

/* ==========================================================================
   18. 步进器（数量 −/+）
   --------------------------------------------------------------------------
   手机上调数量最难，两侧按钮 48×48，中间可直接输入
   ========================================================================== */
.stepper {
  display: flex;
  align-items: stretch;
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--c-card);
}
.stepper__btn {
  width: var(--h-input);
  flex-shrink: 0;
  border: none;
  background: var(--c-hover);
  color: var(--c-text-2);
  font-size: 20px;
  cursor: pointer;
  transition: background var(--t-fast);
  font-family: inherit;
  line-height: 1;
}
.stepper__btn:hover { background: #eef2f8; color: var(--c-primary); }
.stepper__btn:active { background: #e4eaf4; }
.stepper__btn:disabled { color: var(--c-text-3); cursor: not-allowed; opacity: .5; }
.stepper .input {
  border: none;
  border-radius: 0;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.stepper .input:focus { box-shadow: none; }

/* ==========================================================================
   19. 只读展示值（详情页用，区别于可编辑输入框）
   ========================================================================== */
.readonly-value {
  min-height: var(--h-input);
  display: flex;
  align-items: center;
  padding: 0 2px;
  font-size: var(--fs-lg);
  color: var(--c-text);
  font-variant-numeric: tabular-nums;
}
.readonly-value--money { font-weight: 500; }

/* 下方空间不足时向上展开（列表底部、弹窗内常见） */
.suggest--up {
  top: auto;
  bottom: calc(100% + 4px);
}
.suggest__correct { cursor: pointer; }
.suggest__correct:hover { background: #fdf0d8; }
.suggest__fix {
  margin-left: 6px;
  color: var(--c-primary);
  text-decoration: underline;
  text-underline-offset: 2px;
}
/* 步进器内不显示对勾：两边按钮已提供足够的操作反馈 */
.stepper .input.is-valid {
  background-image: none;
  padding-right: 0;
}

/* ==========================================================================
   18. 登录页 / 详情页 / 库位网格 / 导入（0.4 新增）
   --------------------------------------------------------------------------
   只加这一节。页面里仍然不许写自定义样式。
   ========================================================================== */

/* ---------- 登录 ---------- */
.login-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-md);
  background:
    radial-gradient(900px 500px at 15% 0%, #e9f0ff 0%, transparent 60%),
    radial-gradient(700px 500px at 100% 100%, #eef3f9 0%, transparent 55%),
    var(--c-bg);
}
.login-card {
  width: 100%;
  max-width: 400px;
  background: var(--c-card);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-md);
  padding: var(--sp-xl);
}
.login-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--fs-title);
  font-weight: 700;
  letter-spacing: -.3px;
}
.login-brand__mark {
  width: 34px; height: 34px;
  border-radius: 9px;
  background: var(--c-primary);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 16px; font-weight: 700;
  flex-shrink: 0;
}
.login-sub {
  font-size: var(--fs-xs);
  color: var(--c-text-3);
  margin: 6px 0 var(--sp-lg);
}
.login-foot {
  font-size: var(--fs-xs);
  color: var(--c-text-3);
  margin-top: var(--sp-md);
  line-height: var(--lh-base);
}
@media (max-width: 639px) {
  .login-page { align-items: flex-start; padding-top: 12vh; }
  .login-card { padding: var(--sp-lg) var(--sp-md); box-shadow: none; }
}

/* ---------- 侧栏底部账号 ---------- */
.side-nav__user {
  border-top: 1px solid rgba(255,255,255,.08);
  padding: var(--sp-sm) var(--sp-md);
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--fs-head);
  color: #a9b6cc;
  min-height: 56px;
}
.side-nav__avatar {
  width: 30px; height: 30px;
  border-radius: 50%;
  background: rgba(255,255,255,.12);
  display: flex; align-items: center; justify-content: center;
  color: #fff; font-size: 13px; font-weight: 600;
  flex-shrink: 0;
}
/* 名字和角色要上下排。它们都是 span，不显式改成列会挤成一行
   「管理员管理员」——看不出哪个是名字哪个是角色。 */
.side-nav__user-info {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.side-nav__user-name {
  color: #fff; font-size: 13px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.side-nav__user-role {
  font-size: var(--fs-xs); color: #7e8ca3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.side-nav__logout {
  margin-left: auto;
  color: #7e8ca3;
  padding: 6px;
  border-radius: var(--r-sm);
  display: flex;
}
.side-nav__logout:hover { background: rgba(255,255,255,.08); color: #fff; }

/* 侧栏收成 64px 图标栏：文字必须显式藏掉，否则中文一字一行竖着排下来。
   放在组件定义之后是有意的，见文件前面对应的说明。 */
@media (min-width: 640px) and (max-width: 1023px) {
  .side-nav__brand,
  .side-nav__item { justify-content: center; padding-left: 0; padding-right: 0; }
  .side-nav__brand span:not(.side-nav__icon),
  .side-nav__item span:not(.side-nav__icon),
  .side-nav__user-info,
  .side-nav__logout { display: none; }
  /* 只留头像，居中；gap 归零，不然头像会被 gap 顶偏 */
  .side-nav__user { justify-content: center; gap: 0; padding: var(--sp-sm) 0; }
}
@media (max-height: 500px) and (orientation: landscape) {
  .side-nav__user-info, .side-nav__logout { display: none; }
  .side-nav__user { justify-content: center; gap: 0; padding: var(--sp-sm) 0; }
}

/* ---------- 键值列表（详情页） ---------- */
/* 键值列表限宽：桌面整卡 1200 宽时，键在左值在右，中间空一大片反而难对 */
.kv { display: flex; flex-direction: column; max-width: 680px; }
.kv__row {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-md);
  padding: 10px 0;
  border-bottom: 1px dashed var(--c-border);
  font-size: var(--fs-table);
}
.kv__row:last-child { border-bottom: none; }
.kv__k { color: var(--c-text-2); flex-shrink: 0; }
.kv__v { color: var(--c-text); text-align: right; word-break: break-all; }
.kv__v.num { font-variant-numeric: tabular-nums; }
/* 值是一整句话时右对齐会把句子拆得七零八落，改成键上值下 */
.kv--text .kv__row { flex-direction: column; gap: 2px; }
.kv--text .kv__v { text-align: left; color: var(--c-text-2); }

/* ---------- 库位网格 ---------- */
.loc-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: var(--sp-sm);
}
.loc-cell {
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  padding: 10px;
  text-align: center;
  background: var(--c-card);
  transition: border-color var(--t-fast), background var(--t-fast);
}
.loc-cell__code {
  font-size: var(--fs-table);
  font-weight: 600;
  font-family: var(--font-mono, ui-monospace, monospace);
  letter-spacing: .2px;
}
.loc-cell__meta { font-size: var(--fs-xs); color: var(--c-text-3); margin-top: 3px; }
.loc-cell.is-empty { border-style: dashed; }
.loc-cell.is-empty .loc-cell__code { color: var(--c-text-3); }
.loc-cell.is-used { border-color: #bcd2ff; background: #f5f9ff; }
@media (max-width: 639px) {
  .loc-grid { grid-template-columns: repeat(auto-fill, minmax(86px, 1fr)); }
  .loc-cell { padding: 8px 6px; }
}

/* ---------- 提示条（导入页/表单说明） ---------- */
.notice {
  border-radius: var(--r-md);
  padding: 10px var(--sp-md);
  font-size: var(--fs-table);
  line-height: var(--lh-base);
  border: 1px solid var(--c-border);
  background: var(--c-hover);
  color: var(--c-text-2);
}
.notice--warn { background: var(--c-warning-light); border-color: #f3ddb5; color: #8a5a05; }
.notice--danger { background: var(--c-danger-light); border-color: #f6c9c2; color: #a12b1c; }
.notice--info { background: var(--c-primary-light); border-color: #cfdcff; color: #17408f; }
.notice b { color: inherit; }

/* ---------- 上传区 ---------- */
.upload-box {
  border: 2px dashed var(--c-border-strong);
  border-radius: var(--r-lg);
  padding: var(--sp-xl) var(--sp-md);
  text-align: center;
  background: var(--c-card);
  cursor: pointer;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.upload-box:hover, .upload-box.is-drag {
  border-color: var(--c-primary);
  background: var(--c-primary-light);
}
.upload-box__title { font-size: var(--fs-lg); font-weight: 600; margin-bottom: 4px; }
.upload-box__hint { font-size: var(--fs-xs); color: var(--c-text-3); }
.upload-box input[type=file] { display: none; }

/* ---------- 表格内的行状态 ---------- */
.data-table tbody tr.is-error { background: #fff7f6; }
.data-table tbody tr.is-error:hover { background: #feeff0; }
.cell-err { color: var(--c-danger); font-size: var(--fs-xs); }
.cell-warn { color: var(--c-warning); font-size: var(--fs-xs); }

/* ---------- 移动端页面返回 ---------- */
.page-back {
  display: none;
  align-items: center;
  gap: 4px;
  color: var(--c-text-2);
  font-size: var(--fs-table);
  margin-bottom: var(--sp-sm);
  min-height: var(--touch-min);
}
@media (max-width: 639px) {
  .page-back { display: inline-flex; }
}

/* 卡片列表底部操作条（手机） */
.list-foot { margin-top: var(--sp-md); }

/* 底部操作条占位：内容末尾垫一下，最后一行不会被固定条压住 */
.action-bar-spacer { display: none; }
@media (max-width: 639px) {
  .action-bar-spacer { display: block; height: 76px; }
}

/* 手机：分类下拉可压缩，两个筛选 chip 和它挤在一行里 */
@media (max-width: 639px) {
  .toolbar .select { flex: 1 1 96px; min-width: 0; }
  .toolbar .chip { padding: 0 12px; }
}

/* 分页按钮在触摸设备上要够点：36px 是鼠标尺寸，手指点不准 */
@media (pointer: coarse), (any-pointer: coarse) {
  .pager__btn { min-width: 44px; height: 44px; }
}

/* ==========================================================================
   19. 开单与单据
   ========================================================================== */

/* 单据类型：六个 chip 会换行，行距别贴着 */
.order-types { margin-bottom: var(--sp-md); }
.order-types .chip:disabled { opacity: .45; cursor: not-allowed; }

.form-row--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 1023px) { .form-row--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 639px) { .form-row--3 { grid-template-columns: 1fr; gap: 0; } }

/* ---------- 扫码 / 搜索条 ---------- */
.scan-bar {
  position: relative;             /* 搜索下拉挂在这上面 */
  display: flex;
  gap: var(--sp-sm);
  padding: 0 var(--sp-md) var(--sp-md);
}
@media (max-width: 639px) {
  .scan-bar { padding: 0 var(--sp-md) var(--sp-sm); }
  .scan-bar .btn { padding: 0 12px; }
}

.search-pop {
  position: absolute;
  top: calc(100% - 8px);
  left: var(--sp-md);
  right: var(--sp-md);
  z-index: 30;
  background: var(--c-card);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
  max-height: 316px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.search-pop__row {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  padding: 10px var(--sp-md);
  border-bottom: 1px solid var(--c-border);
  cursor: pointer;
  min-height: var(--touch-min);
}
.search-pop__row:last-child { border-bottom: none; }
.search-pop__row.is-active { background: var(--c-primary-light); }
.search-pop__main { flex: 1; min-width: 0; }
.search-pop__name { font-weight: 500; }
.search-pop__side { text-align: right; flex-shrink: 0; }
.search-pop__side .num { font-weight: 600; }

/* ---------- 明细行 ---------- */
.line-head,
.line {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 116px 128px 104px 104px 34px;
  gap: var(--sp-sm);
  align-items: center;
}
.line-head--transfer,
.line--transfer {
  grid-template-columns: minmax(0, 1fr) 100px 100px 120px 92px 96px 34px;
}
.line-head {
  padding: 8px var(--sp-md);
  border-top: 1px solid var(--c-border);
  border-bottom: 1px solid var(--c-border);
  background: var(--c-bg);
  font-size: var(--fs-head);
  color: var(--c-text-2);
}
.line {
  padding: 10px var(--sp-md);
  border-bottom: 1px solid var(--c-border);
}
.line:last-of-type { border-bottom: none; }
.line__name { min-width: 0; }
.line__title { font-weight: 500; word-break: break-word; }
.line__lb { display: none; font-size: var(--fs-xs); color: var(--c-text-3); margin-bottom: 2px; }
.line__amount { text-align: right; font-weight: 600; white-space: nowrap; }
.line__del {
  width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--r-sm);
  color: var(--c-text-3);
  background: transparent;
}
.line__del:hover { background: var(--c-danger-light); color: var(--c-danger); }

/* 序列号：横跨整行，单独一块 */
.line__serials {
  grid-column: 1 / -1;
  margin-top: 4px;
  padding: var(--sp-sm);
  background: var(--c-bg);
  border-radius: var(--r-md);
}
.line__serials-head {
  display: flex; align-items: center; gap: var(--sp-sm);
  font-size: var(--fs-table);
  color: var(--c-text-2);
  margin-bottom: var(--sp-sm);
  flex-wrap: wrap;
}
.line__serials .chips { margin-bottom: var(--sp-sm); }
.line__serials .input { width: 100%; }

/* 数量：− 数字 ＋，比裸输入框好点，仓管一只手也能改 */
.qty-step {
  display: flex;
  align-items: stretch;
  height: 36px;
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--c-card);
}
.qty-step__btn {
  width: 32px;
  display: flex; align-items: center; justify-content: center;
  color: var(--c-text-2);
  background: var(--c-bg);
  flex-shrink: 0;
}
.qty-step__btn:hover { background: var(--c-border); }
.qty-step .input {
  flex: 1;
  min-width: 0;
  width: 100%;
  height: 100%;
  border: none;
  border-left: 1px solid var(--c-border);
  border-right: 1px solid var(--c-border);
  border-radius: 0;
  text-align: center;
  padding: 0 4px;
  font-variant-numeric: tabular-nums;
}

/* 可删的标签（序列号） */
.chip--tag { gap: 4px; padding-right: 6px; }
.chip__x {
  width: 18px; height: 18px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px;
  color: var(--c-text-3);
  font-size: 14px;
  line-height: 1;
}
.chip__x:hover { background: var(--c-danger-light); color: var(--c-danger); }

/* ---------- 提交条：所有尺寸都吸底，不只手机 ---------- */
.submit-bar {
  position: sticky;
  bottom: 0;
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;
  gap: var(--sp-md);
  margin-top: var(--sp-md);
  padding: var(--sp-sm) var(--sp-md);
  background: var(--c-card);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  box-shadow: 0 -4px 18px rgba(20, 32, 60, .08);
}
.submit-bar__sum { flex: 1; min-width: 0; }
.submit-bar__count { font-size: var(--fs-xs); color: var(--c-text-3); }
.submit-bar__money { font-size: 22px; font-weight: 700; letter-spacing: -.4px; }
.submit-bar__actions { display: flex; gap: var(--sp-sm); }
@media (max-width: 639px) {
  .submit-bar {
    bottom: calc(60px + env(safe-area-inset-bottom));
    border-radius: var(--r-md);
    padding: 10px var(--sp-md);
  }
  .submit-bar__money { font-size: 18px; }
  .submit-bar__actions .btn { padding: 0 14px; }
}

/* 明细行在窄屏改成两列堆叠。860 是关键线：
   再窄下去，六个固定列会把商品名压成一条缝。 */
@media (max-width: 860px) {
  .line-head { display: none; }
  .line,
  .line--transfer {
    grid-template-columns: 1fr 1fr;
    grid-template-areas:
      "name name"
      "loc loc"
      "qty price"
      "amt del";
    gap: var(--sp-sm) var(--sp-md);
    padding: var(--sp-md);
    align-items: end;
  }
  .line--transfer {
    grid-template-areas:
      "name name"
      "loc loc"
      "to to"
      "qty price"
      "amt del";
  }
  .line__name { grid-area: name; }
  .line__loc { grid-area: loc; }
  .line__to { grid-area: to; }
  .line__qty { grid-area: qty; }
  .line__price { grid-area: price; }
  .line__amount { grid-area: amt; text-align: left; }
  .line__del { grid-area: del; justify-self: end; width: var(--touch-min); height: var(--touch-min); }
  .line__lb { display: block; }
}

/* ---------- 扫码弹层 ---------- */
.modal--scan { max-width: 460px; width: calc(100% - 32px); }
.scan-stage {
  position: relative;
  background: #000;
  overflow: hidden;
  height: 260px;
}
.scan-video { width: 100%; height: 100%; object-fit: cover; display: block; }
.scan-frame {
  position: absolute;
  inset: 22% 10%;
  border: 2px solid rgba(255, 255, 255, .9);
  border-radius: 8px;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, .38);
  pointer-events: none;
}
.scan-tip {
  position: absolute;
  left: 0; right: 0; bottom: 8px;
  padding: 6px 12px;
  font-size: var(--fs-xs);
  color: #fff;
  text-align: center;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .8);
  line-height: 1.5;
}
.scan-tip--err {
  top: 0; bottom: auto;
  background: rgba(232, 64, 42, .92);
  text-align: left;
  padding: 10px 12px;
  font-size: var(--fs-table);
  text-shadow: none;
  min-height: 100%;
  display: flex;
  align-items: center;
}

/* ---------- 打印页工具条 ---------- */
.print-toolbar {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  margin-bottom: var(--sp-md);
  padding: var(--sp-sm) var(--sp-md);
  background: var(--c-card);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  flex-wrap: wrap;            /* 手机上份数框 + 打印按钮会顶出去 */
}
.print-toolbar__spacer { flex: 1; }
.print-toolbar__lbl {
  display: flex; align-items: center; gap: 6px;
  font-size: var(--fs-table); color: var(--c-text-2);
}
@media (max-width: 639px) {
  .print-toolbar__spacer { display: none; }
  .print-toolbar__lbl { flex: 1; }
  .print-toolbar__lbl .select { flex: 1; min-width: 0; }
}

/* 打印页这类「裸页面」：不留侧栏位置，让 A4 在正中间 */
.app-main--bare { margin-left: 0; max-width: none; }

/* ==========================================================================
   20. 盘点
   现场是站着单手操作的：一行一件事，输入框要大，差异要显眼。
   ========================================================================== */

/* 选货架：48 个库位排成一片，比下拉框快得多——人知道自己要盘 A-01，
   下拉框里找 48 项要滑半天。 */
.loc-pick {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
  gap: var(--sp-sm);
  max-height: 260px;
  overflow: auto;
  padding: var(--sp-sm);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-bg);
}
.loc-pick__item {
  padding: 10px 6px;
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: var(--fs-table);
  color: var(--c-text-2);
  background: var(--c-card);
  border: 1px solid var(--c-border);
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: .12s;
}
.loc-pick__item:hover { border-color: var(--c-primary); }
.loc-pick__item.is-active {
  color: #fff;
  background: var(--c-primary);
  border-color: var(--c-primary);
}

/* 实盘输入框窄一点：数字短，留宽了手要伸更远 */
.check-table .js-real { text-align: right; font-variant-numeric: tabular-nums; }
.check-table tr.is-diff td { background: var(--c-warning-light); }
.check-table .js-reason { font-size: var(--fs-table); }

/* 扫码定位后闪一下，告诉人"就是这一行"。
   只闪背景不闪边框：边框动会带着整行抖。 */
@keyframes flash-row {
  0%   { background: var(--c-primary-light); }
  100% { background: transparent; }
}
.is-flash { animation: flash-row 1.2s ease-out; }

/* 手机上的盘点卡：一眼看到货、三个数并排 */
.check-card {
  padding: var(--sp-md);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-card);
}
.check-card + .check-card { margin-top: var(--sp-sm); }
.check-card.is-diff { border-color: var(--c-warning); background: var(--c-warning-light); }
.check-card__head { display: flex; justify-content: space-between; align-items: center; }
.check-card__name { margin-top: 4px; font-size: var(--fs-base); font-weight: 500; }
.check-card__spec { margin-top: 2px; }
.check-card__grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: var(--sp-sm);
  margin-top: var(--sp-sm);
  align-items: end;
}
.check-card__label { font-size: var(--fs-xs); color: var(--c-text-3); }
.check-card__val {
  margin-top: 2px;
  font-variant-numeric: tabular-nums;
  font-weight: 500;
}

/* 收付款行的右侧是「金额 + 作废按钮」，手机上并排放不下，
   会把整页顶宽 46px。窄屏改成上下两行。 */
@media (max-width: 639px) {
  .pay-row { flex-direction: column; align-items: flex-start; gap: 4px; }
  .pay-row .kv__v { text-align: left; }
}


/* ==========================================================================
   21. 项目与报价（0.7）
   ========================================================================== */

/* 三列信息格。桌面一行三个，窄屏一列——项目名长，两列会把名字挤成竖排。 */
.grid-3 {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0 var(--sp-md);
}
@media (max-width: 860px) {
  .grid-3 { grid-template-columns: 1fr; }
}

.row { display: flex; align-items: center; }

/* 页签。横向可滚，不换行——换行会让内容整体往下跳一格。 */
.tabs {
  display: flex;
  gap: 2px;
  padding: 0 var(--sp-sm);
  border-bottom: 1px solid var(--c-border);
  overflow-x: auto;
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  position: relative;
  flex: none;
  min-height: var(--touch-min);
  padding: 0 14px;
  border: 0;
  background: none;
  font-size: var(--fs-base);
  color: var(--c-text-2);
  cursor: pointer;
  white-space: nowrap;
}
.tab:hover { color: var(--c-text); }
.tab.is-active { color: var(--c-primary); font-weight: 500; }
.tab.is-active::after {
  content: "";
  position: absolute;
  left: 12px; right: 12px; bottom: 0;
  height: 2px;
  border-radius: 2px;
  background: var(--c-primary);
}
.tab__count {
  display: inline-block;
  min-width: 18px;
  margin-left: 4px;
  padding: 0 5px;
  border-radius: var(--r-full);
  background: var(--c-bg);
  color: var(--c-text-3);
  font-size: var(--fs-xs);
  line-height: 18px;
  text-align: center;
}

/* 成本构成条。三块钱挤在一条上，比饼图好读——饼图在小屏上
   分不清哪块是人工，而人工恰恰是小项目亏钱的主因。 */
.cost-bar {
  display: flex;
  height: 10px;
  margin: var(--sp-md) 0 var(--sp-sm);
  border-radius: var(--r-full);
  overflow: hidden;
  background: var(--c-bg);
}
.cost-bar__seg { display: block; height: 100%; }
.cost-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-sm) var(--sp-lg);
  font-size: var(--fs-sm);
  color: var(--c-text-2);
}
.cost-legend__item { display: inline-flex; align-items: center; gap: 6px; }
.cost-legend__dot {
  width: 8px; height: 8px;
  border-radius: 2px;
  flex: none;
}

/* 报价单明细：手机上一行一条，桌面走表格 */
.quote-line {
  display: grid;
  grid-template-columns: 1fr 88px 96px 96px 32px;
  gap: var(--sp-sm);
  align-items: center;
  padding: var(--sp-sm) 0;
  border-bottom: 1px solid var(--c-border);
}
.quote-line__amount { text-align: right; font-variant-numeric: tabular-nums; }
@media (max-width: 860px) {
  .quote-line {
    grid-template-columns: 1fr 1fr;
    gap: 6px;
    padding: var(--sp-md) 0;
  }
  .quote-line__amount { text-align: left; }
}

/* 打印页复用 A4 三分的尺寸，报价单只印一份 */
.quote-print-line td { padding: 3pt 4pt; }

/* ==========================================================================
   22. 操作手册与备份（0.7）
   ========================================================================== */

.manual-wrap {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  gap: var(--sp-lg);
  align-items: start;
}
@media (max-width: 1100px) {
  .manual-wrap { grid-template-columns: 1fr; }
}

/* 目录吸在顶部。手册很长，没有目录只能靠浏览器搜索。 */
.manual-toc {
  position: sticky;
  top: var(--sp-md);
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--sp-sm);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-card);
  max-height: calc(100vh - 120px);
  overflow-y: auto;
}
.manual-toc__item {
  display: flex;
  align-items: center;
  min-height: var(--touch-min);
  padding: 8px 10px;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--c-text-2);
  font-size: var(--fs-table);
  text-align: left;
  cursor: pointer;
  line-height: 1.4;
}
.manual-toc__item:hover { background: var(--c-hover); color: var(--c-text); }
.manual-toc__item.is-active {
  background: var(--c-primary-light);
  color: var(--c-primary);
  font-weight: 500;
}

.manual__mobile-toc { margin-bottom: var(--sp-md); }

/* 手册正文里有宽表格，grid 子项必须能收缩，否则整页横向滚 */
.manual { min-width: 0; }

.manual__body {
  padding: var(--sp-lg) var(--sp-xl);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-card);
  color: var(--c-text);
  font-size: var(--fs-base);
  line-height: var(--lh-loose);
}
@media (max-width: 639px) {
  .manual__body { padding: var(--sp-md); }
}

.manual__body h1 {
  margin: 0 0 var(--sp-md);
  font-size: var(--fs-title);
  font-weight: 600;
}
.manual__body h2 {
  margin: var(--sp-xl) 0 var(--sp-sm);
  padding-top: var(--sp-md);
  border-top: 1px solid var(--c-border);
  font-size: var(--fs-lg);
  font-weight: 600;
  scroll-margin-top: 72px;      /* 顶部工具条高度，跳转后标题不被盖住 */
}
.manual__body h2:first-of-type { border-top: 0; padding-top: 0; }
.manual__body h3 { margin: var(--sp-lg) 0 var(--sp-sm); font-size: var(--fs-base); }
.manual__body p { margin: var(--sp-sm) 0; }
.manual__body ul, .manual__body ol { margin: var(--sp-sm) 0; padding-left: 1.4em; }
.manual__body li { margin: 4px 0; }
.manual__body strong { font-weight: 600; }
.manual__body a { color: var(--c-primary); }
.manual__body hr {
  margin: var(--sp-lg) 0;
  border: 0;
  border-top: 1px solid var(--c-border);
}
.manual__body code {
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--c-bg);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .92em;
}
.manual__body pre {
  margin: var(--sp-sm) 0;
  padding: var(--sp-md);
  border-radius: var(--r-md);
  background: var(--c-bg);
  overflow-x: auto;
}
.manual__body pre code { padding: 0; background: none; }
.manual__body blockquote {
  margin: var(--sp-sm) 0;
  padding: 6px var(--sp-md);
  border-left: 3px solid var(--c-border-strong);
  color: var(--c-text-2);
}

/* 手册里的表格：小屏允许横向滚，不要压成两列 */
.manual__body table {
  display: block;
  width: 100%;
  max-width: 100%;
  margin: var(--sp-md) 0;
  border-collapse: collapse;
  overflow-x: auto;
  font-size: var(--fs-table);
}
.manual__body th, .manual__body td {
  padding: 8px 12px;
  border: 1px solid var(--c-border);
  text-align: left;
  vertical-align: top;
  white-space: nowrap;
}
.manual__body th { background: var(--c-bg); font-weight: 600; }

/* ---------- 备份列表 ---------- */
.backup-list { border-top: 1px solid var(--c-border); }
.backup-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-md);
  padding: 12px 0;
  border-bottom: 1px solid var(--c-border);
}

.code-block {
  padding: 10px 12px;
  border-radius: var(--r-sm);
  background: rgba(20, 32, 60, .06);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--fs-sm);
  overflow-x: auto;
  white-space: pre;
}

/* 组合配置一行：名称 / 用量 / 金额 / 删除 */
.bom-line {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 88px 96px 34px;
  gap: var(--sp-sm);
  align-items: center;
  padding: 10px 0;
  border-bottom: 1px solid var(--c-border);
}
@media (max-width: 639px) {
  .bom-line { grid-template-columns: minmax(0, 1fr) 76px 34px; }
  .bom-line > span.num { display: none; }
}

/* ==========================================================================
   23. 报表与待办（0.8）
   ========================================================================== */

/* 三列统计：报表页固定三张（销售额 / 单数 / 单值），
   用 auto-fit 在 700px 会掉成 2+1，不如直接按断点写死。 */
.stat-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 1023px) {
  .stat-grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 639px) {
  .stat-grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* 第三张单独占一行，否则右边空半格 */
  .stat-grid--3 > .stat:last-child:nth-child(odd) { grid-column: 1 / -1; }
}

/* 横条：表格里比大小用它，比柱状图省地方，也不用引图表库。
   宽度由行内 style 给，这里只管高度和圆角。 */
.bar {
  height: 5px;
  border-radius: 3px;
  background: var(--c-bg);
  overflow: hidden;
}
.bar__fill {
  display: block;
  height: 100%;
  min-width: 2px;               /* 金额极小时也留一点，别看不见 */
  border-radius: 3px;
  background: var(--c-primary);
  transition: width .25s ease;
}
.bar__fill--danger { background: var(--c-danger); }

/* 待办：两张一排，最后一张奇数就自己占满 */
.todo-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-md);
}
@media (max-width: 1023px) {
  .todo-grid { grid-template-columns: minmax(0, 1fr); }
}
.todo-card { min-width: 0; }
/* 五张卡排两列会剩下一个孤零零的半张，让它自己占满一行 */
.todo-grid > .todo-card:last-child:nth-child(odd) { grid-column: 1 / -1; }
/* 空的那张淡下去，一眼看到"这里没事" */
.todo-card--empty { opacity: .62; }

.todo-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-sm);
  padding: 7px 0;
  border-bottom: 1px solid var(--c-border);
  font-size: var(--fs-sm);
}
.todo-line:last-of-type { border-bottom: 0; }
.todo-line > .ellipsis { min-width: 0; }
.todo-line > .num { flex: 0 0 auto; font-variant-numeric: tabular-nums; }
/* 只有能点进去的行才有手型，纯数据行别给假的可点感 */
.todo-line.is-link { cursor: pointer; border-radius: var(--r-sm); }
.todo-line.is-link:hover { background: var(--c-bg); }

.todo-more {
  margin-top: var(--sp-sm);
  padding-top: var(--sp-sm);
  border-top: 1px solid var(--c-border);
  font-size: var(--fs-sm);
}
.todo-more a { color: var(--c-primary); }

/* ==========================================================================
   24. 全局搜索（0.8）
   ========================================================================== */

/* 侧栏里的入口。做成"长得像输入框的按钮"——
   看起来能打字，点下去真的能打字，比一个小放大镜图标好找。 */
.side-nav__search {
  display: flex;
  align-items: center;
  gap: 8px;
  width: calc(100% - 16px);
  margin: 4px 8px 8px;
  padding: 8px 10px;
  /* 触摸目标：42px 的高度用手指点会点空，按 44 来 */
  min-height: var(--touch-min);
  border: 0;
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, .1);
  color: rgba(255, 255, 255, .78);
  font-size: var(--fs-sm);
  text-align: left;
  cursor: pointer;
}
.side-nav__search:hover { background: rgba(255, 255, 255, .17); color: #fff; }
.side-nav__search > span { flex: 1; }
.side-nav__search kbd {
  padding: 1px 5px;
  border-radius: 4px;
  background: rgba(255, 255, 255, .16);
  font-family: inherit;
  font-size: 12px;
  line-height: 1.5;
}
/* 平板侧栏只剩图标，搜索按钮跟着收窄 */
@media (min-width: 640px) and (max-width: 1023px) {
  .side-nav__search { justify-content: center; padding: 9px 0; }
  .side-nav__search { min-height: var(--touch-min); }
  .side-nav__search > span, .side-nav__search kbd { display: none; }
}

/* 手机上侧栏没有了，用右下角浮动按钮。位置避开底部导航（56px） */
.fab-search {
  position: fixed;
  right: 16px;
  bottom: calc(56px + var(--sp-md) + env(safe-area-inset-bottom, 0px));
  z-index: 60;
  display: none;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border: 0;
  border-radius: 50%;
  background: var(--c-primary);
  color: #fff;
  box-shadow: var(--shadow-lg);
  cursor: pointer;
}
@media (max-width: 639px) { .fab-search { display: flex; } }
.fab-search:active { transform: scale(.94); }

/* ---------- 弹层 ---------- */
.search-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding: 8vh var(--sp-md) var(--sp-md);
  background: rgba(20, 32, 60, .42);
}
.search-modal__panel {
  width: 100%;
  max-width: 560px;
  max-height: 76vh;
  display: flex;
  flex-direction: column;
  border-radius: var(--r-lg);
  background: var(--c-surface);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.search-modal__bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px var(--sp-md);
  border-bottom: 1px solid var(--c-border);
  color: var(--c-text-3);
}
.search-modal__input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: none;
  background: none;
  font-size: var(--fs-lg);
  color: var(--c-text-1);
}
.search-modal__input::placeholder { color: var(--c-text-3); }
.search-modal__esc {
  flex: 0 0 auto;
  padding: 2px 7px;
  border: 1px solid var(--c-border-strong);
  border-radius: 5px;
  background: var(--c-surface);
  color: var(--c-text-3);
  font-size: 11px;
  cursor: pointer;
}
.search-modal__body { overflow-y: auto; padding: 6px 0 var(--sp-sm); }
.search-modal__hint {
  padding: var(--sp-lg) var(--sp-md);
  color: var(--c-text-3);
  font-size: var(--fs-sm);
  text-align: center;
}
.search-group { padding-top: 6px; }
.search-group__title {
  padding: 6px var(--sp-md);
  color: var(--c-text-3);
  font-size: 11px;
  letter-spacing: .04em;
}
.search-hit {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 9px var(--sp-md);
  cursor: pointer;
}
.search-hit:hover, .search-hit:focus { background: var(--c-bg); }
.search-hit__main {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 500;
}
.search-hit__sub {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--c-text-3);
  font-size: var(--fs-sm);
}
.search-hit__val {
  flex: 0 0 auto;
  color: var(--c-text-2);
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
}
/* 手机上弹层贴着顶部，键盘弹起时不至于被推到看不见的地方 */
@media (max-width: 639px) {
  .search-modal { padding: 0; align-items: stretch; }
  .search-modal__panel {
    max-width: none;
    max-height: none;
    border-radius: 0;
  }
  .search-hit { padding: 12px var(--sp-md); }   /* 手指点得准 */
}

/* ==========================================================================
   25. 系统设置与向导（0.9）
   ========================================================================== */

/* 提示条补两个语义色。原来只有 warn，写页面时很自然会敲 --warning */
.notice--warning { background: var(--c-warning-light); border-color: #f3ddb5; color: #8a5a05; }
.notice--success { background: var(--c-success-light); border-color: #bfe3cd; color: #16653a; }

/* ---------- 权限矩阵 ---------- */
.perm-table th, .perm-table td { padding-top: 8px; padding-bottom: 8px; }
/* 分组行：整行灰底，把十六个页面切成四块，不然一屏全是勾 */
.perm-group td {
  background: var(--c-bg);
  color: var(--c-text-2);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: .04em;
}
.perm-cell {
  width: 34px;
  height: 34px;
  border: 1px solid var(--c-border-strong);
  border-radius: var(--r-sm);
  background: var(--c-surface);
  color: var(--c-text-3);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.perm-cell.is-on {
  border-color: var(--c-primary);
  background: var(--c-primary-light);
  color: var(--c-primary);
  font-weight: 700;
}
/* 管理员那一列不给点：灰掉，点上去也没反应，别让人以为是坏了 */
.perm-cell.is-locked { cursor: not-allowed; opacity: .55; }
.perm-cell.is-locked.is-on { background: var(--c-bg); border-color: var(--c-border); color: var(--c-text-2); }

/* 手机上矩阵会挤成一条线。允许横滑，比拆成四张卡好读—— 
   横着看能一眼比出"这个人比那个人多看了哪几页" */
/* 手指点的设备要 44px：34px 的勾选框点十次能中三次。
   按宽度判断不够——12 寸 Windows 平板 1368 宽但照样用手指点，
   所以用 pointer: coarse，宽度那条留着兜老浏览器。 */
@media (pointer: coarse), (max-width: 1023px) {
  .perm-cell { width: var(--touch-min); height: var(--touch-min); }
}
@media (max-width: 639px) {
  .perm-table { min-width: 420px; }
}

/* ---------- 初始化向导 ---------- */
.step-list { padding: 0; }
.step {
  display: flex;
  align-items: center;
  gap: var(--sp-md);
  padding: var(--sp-md);
  border-bottom: 1px solid var(--c-border);
}
.step:last-child { border-bottom: 0; }
.step__no {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--c-bg);
  color: var(--c-text-2);
  font-size: var(--fs-sm);
  font-weight: 600;
}
.step.is-done .step__no { background: var(--c-success-light); color: var(--c-success); }
.step__body { flex: 1; min-width: 0; }
.step__title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 2px;
  font-weight: 600;
}
/* 手机上按钮不换行会把这行顶宽，让它自己掉到下一行 */
@media (max-width: 639px) {
  .step { flex-wrap: wrap; }
  .step__body { flex: 1 1 calc(100% - 30px - var(--sp-md)); }
  .step > .btn { margin-left: calc(30px + var(--sp-md)); }
}
