/* ============================================================================
   layout.css —— 骨架与组件样式
   尺寸取值来自线上产物：
     .sidebar-container 200px（折叠 54px） / .navbar 50px / .tags-view 34px
     .sidebar-logo 58px / .app-container padding 20px / 菜单项 44px
     navbar box-shadow 0 1px 4px rgba(0,21,41,.08)
   ========================================================================= */

*, *::before, *::after { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
  font-size: 14px;
  line-height: 1.5;
  color: var(--jxc-text-primary);
  background: var(--jxc-bg);
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }
button { font: inherit; }
h1, h2, h3, h4, p, dl, dd, dt, ol, ul { margin: 0; padding: 0; }
ol, ul { list-style: none; }

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

.sidebar {
  width: var(--jxc-sidebar-width);
  flex: 0 0 var(--jxc-sidebar-width);
  background: var(--jxc-sidebar-bg);
  color: var(--jxc-sidebar-text);
  display: flex;
  flex-direction: column;
  transition: width var(--jxc-duration) var(--jxc-ease), flex-basis var(--jxc-duration) var(--jxc-ease);
  border-right: var(--jxc-sidebar-border, none);
  z-index: 30;
}
body.is-sidebar-collapsed .sidebar { width: 54px; flex-basis: 54px; }

.sidebar__logo {
  height: var(--jxc-sidebar-logo-height);
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 14px;
  background: var(--jxc-menu-bg);
  overflow: hidden;
  white-space: nowrap;
  flex: 0 0 auto;
}
.sidebar__logo-mark {
  width: 26px; height: 26px; flex: 0 0 26px;
  border-radius: 6px;
  background: var(--current-color);
  color: #fff;
  display: grid; place-items: center;
  font-weight: 700; font-size: 14px;
}
.sidebar__logo-text { font-weight: 600; font-size: 14px; letter-spacing: .2px; }
body.is-sidebar-collapsed .sidebar__logo-text { display: none; }

.sidebar__menu { flex: 1 1 auto; overflow-y: auto; padding: 8px 0 24px; }
.sidebar__menu::-webkit-scrollbar { width: 6px; }
.sidebar__menu::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .14); border-radius: 3px; }

.menu-group { margin-bottom: 4px; }
.menu-group__title {
  padding: 10px 20px 6px;
  font-size: 12px;
  color: var(--jxc-text-secondary);
  opacity: .72;
  letter-spacing: .5px;
  white-space: nowrap;
}
body.is-sidebar-collapsed .menu-group__title { padding-left: 0; text-align: center; font-size: 0; }
body.is-sidebar-collapsed .menu-group__title::after { content: '···'; font-size: 12px; }

.menu-item {
  height: 44px;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 20px;
  font-size: 14px;
  color: var(--jxc-menu-text);
  cursor: pointer;
  transition: background .2s, color .2s;
  white-space: nowrap;
  overflow: hidden;
}
.menu-item:hover { background: var(--jxc-menu-hover); }
/* 激活态：文字用主色 + 整行浅色底（线上 --current-color / --current-color-dark-bg） */
.menu-item.is-active {
  color: var(--current-color);
  background: var(--current-color-dark-bg);
}
.menu-item__icon { display: grid; place-items: center; flex: 0 0 17px; }
.menu-item__text { flex: 1 1 auto; }
.menu-item__tag {
  font-size: 11px;
  padding: 1px 6px;
  border-radius: 8px;
  background: rgba(230, 162, 60, .18);
  color: #e6a23c;
}
body.is-sidebar-collapsed .menu-item { padding: 0; justify-content: center; }
body.is-sidebar-collapsed .menu-item__text,
body.is-sidebar-collapsed .menu-item__tag { display: none; }

.sidebar-mask { display: none; }

.main-container {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--jxc-bg);
}

/* ---------- 顶栏 ---------- */
.navbar {
  height: var(--jxc-navbar-height);
  flex: 0 0 var(--jxc-navbar-height);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 16px;
  background: var(--jxc-navbar-bg);
  color: var(--jxc-navbar-text);
  box-shadow: var(--jxc-navbar-shadow);
  position: relative;
  z-index: 20;
}
body.is-fixed-header .navbar { position: sticky; top: 0; }
.navbar__left, .navbar__right { display: flex; align-items: center; gap: 10px; }
.navbar__hamburger, .navbar__icon {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border: 0; border-radius: var(--jxc-radius-sm);
  background: transparent; color: inherit; cursor: pointer;
}
.navbar__hamburger:hover, .navbar__icon:hover { background: var(--jxc-fill); }

.breadcrumb { display: flex; align-items: center; gap: 6px; font-size: 13px; }
.breadcrumb__item { display: flex; align-items: center; gap: 6px; color: var(--jxc-text-secondary); }
.breadcrumb__item a:hover { color: var(--current-color); }
.breadcrumb__item:last-child { color: var(--jxc-text-primary); font-weight: 500; }
.breadcrumb__sep { color: var(--jxc-text-placeholder); font-style: normal; }

.navbar__user-select {
  display: flex; align-items: center; gap: 6px;
  padding: 0 8px; height: 30px;
  border: 1px solid var(--jxc-border-light);
  border-radius: var(--jxc-radius-sm);
  color: var(--jxc-text-regular);
}
.navbar__user-select .input { border: 0; box-shadow: none; height: 26px; padding: 0; width: 150px; background: transparent; }
.navbar__user { display: flex; align-items: center; gap: 8px; padding-left: 8px; }
.navbar__avatar {
  width: 30px; height: 30px; flex: 0 0 30px;
  border-radius: 50%;
  background: var(--current-color);
  color: #fff;
  display: grid; place-items: center;
  font-weight: 600; font-size: 13px;
}
.navbar__user-meta { display: flex; flex-direction: column; line-height: 1.2; }
.navbar__user-meta strong { font-size: 13px; }
.navbar__user-meta span { font-size: 11px; color: var(--jxc-text-secondary); }

/* ---------- 页签 ---------- */
.tags-view { height: var(--jxc-tags-height); flex: 0 0 var(--jxc-tags-height); background: var(--jxc-tags-bg, var(--jxc-surface)); border-bottom: 1px solid var(--jxc-border-lighter); }
.tags-view__inner { display: flex; align-items: center; gap: 6px; height: 100%; padding: 0 12px; overflow-x: auto; }
.tags-view__actions { margin-left: auto; display: flex; gap: 6px; flex: 0 0 auto; padding-left: 10px; }
.tag-item {
  display: inline-flex; align-items: center; gap: 6px;
  height: 24px; padding: 0 8px;
  font-size: 12px;
  border: 1px solid var(--jxc-tags-item-border, var(--jxc-border-light));
  background: var(--jxc-tags-item-bg, #fff);
  color: var(--jxc-tags-item-text, var(--jxc-text-regular));
  border-radius: var(--jxc-radius-sm);
  white-space: nowrap;
}
.tag-item:hover { background: var(--jxc-tags-item-hover, var(--jxc-fill)); }
.tag-item.is-active {
  background: var(--current-color-dark-bg);
  border-color: var(--current-color);
  color: var(--current-color);
}
.tags-view__inner.is-chrome .tag-item { border-radius: 12px; }
.tag-item__close {
  border: 0; background: transparent; cursor: pointer; padding: 0;
  display: grid; place-items: center;
  color: inherit; opacity: .6;
}
.tag-item__close:hover { opacity: 1; }

/* ---------- 内容区 ---------- */
.app-main { flex: 1 1 auto; min-width: 0; }
.app-container { padding: var(--jxc-main-gutter); }

/* ---------- 移动端常驻底部导航栏（dock） ----------
   只在手机上显示：桌面上左侧导航一直在，再加一条底栏只是重复占地方。
   所以基础规则是 display: none，移动端媒体查询里才打开 ——
   这样即使 JS 在桌面宽度下误渲染了它，也不会露出半条空栏。 */
.dock { display: none; }

@media (max-width: 768px) {
  .dock {
    display: flex;
    position: fixed; left: 0; right: 0; bottom: 0;
    z-index: 45;                       /* 低于弹窗遮罩(2200)、高于抽屉遮罩(25) */
    background: var(--jxc-surface, #fff);   /* 跟随主题，深色下不该是白板 */
    border-top: 1px solid var(--jxc-border-lighter);
    box-shadow: 0 -1px 6px rgba(0, 21, 41, .06);
    /* 全面屏底部横条：留出安全区，否则最后一行文字会被系统手势条压住 */
    padding-bottom: env(safe-area-inset-bottom);
    /* 高度写死，不让子项把它撑开：底栏是靠 bottom:0 定位的，
       一旦自身高度超出预期就会把内容顶出屏幕下沿（实测手机上
       「发货批次」那格文字被裁掉）。 */
    height: 58px;
    box-sizing: content-box;           /* 58px 之上再加安全区 */
  }
  /* ★★ 按路由收起底栏（route.meta.hideDock，见 shell/app.js 的说明）。
     用于「新建/编辑采购单」这类自带固定页脚的表单页 ——
     两条 58px 的横条摞在一起既挤又分不清点哪条。
     ⚠ 选择器要带 .app-container 前缀：这个类挂在容器上，
     不限定的话任何带同名类的元素都会命中。
     ⚠ 用 display:none 而不是 visibility/高度 0：底栏是 fixed，
     留高度会继续占位；而它下面的页面元素各有一套 bottom 偏移，
     要一起挪（见 purchase-mobile.css 里的配套规则）。 */
  .app-wrapper.is-dock-hidden .dock,
  .app-container.is-dock-hidden .dock { display: none; }
  /* ★★ 内联规格编辑器展开时也收起底栏（views/purchase.js 的 setEditing）。
     实测事故：内联表单很长，它的「确认添加」被排在底栏**下面** ——
     手指点下去实际点到的是底栏的「出售记录」，直接跳走。
     ⚠ 与 is-dock-hidden 分开写：一个来自路由（表单页整页收起），
     一个来自"正在编辑"，各自独立，恢复时互不影响。 */
  .app-wrapper.is-inline-editing .dock { display: none; }
  .dock__item {
    flex: 1 1 0;
    /* 文字比格子窄很多（最长 4 字），所以宁可让它溢出也不要压缩 ——
       min-width 兜住「发货批次」这类宽标签，避免被挤到换行或裁切。 */
    min-width: 0;
    position: relative;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 2px;
    height: 100%;
    text-decoration: none;
    color: var(--jxc-text-secondary);
    -webkit-tap-highlight-color: transparent;
    /* 手机上浏览器会给窄容器里的文字做「字体放大补偿」，
       放大后文字超出格子高度就被裁掉。版面已经按移动端调过，不需要它。 */
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
    overflow: visible;
  }
  .dock__item.is-active { color: var(--jxc-color-primary); }
  /* 选中项在图标上方加一小段主色横条：比只改文字颜色更容易一眼看到 */
  .dock__item.is-active::before {
    content: '';
    position: absolute; top: 0; left: 50%; transform: translateX(-50%);
    width: 22px; height: 2px; border-radius: 0 0 2px 2px;
    background: var(--jxc-color-primary);
  }
  .dock__icon {
    display: flex; align-items: center; justify-content: center;
    flex: 0 0 auto; width: 24px; height: 24px;   /* 固定占位，不让图标行高影响文字位置 */
  }
  .dock__text {
    flex: 0 0 auto;
    font-size: 11px; line-height: 1.2;
    white-space: nowrap;
    max-width: 100%;
    overflow: hidden; text-overflow: ellipsis;
    /* 手机上浏览器会对窄容器里的文字做「字体放大补偿」，
       放大后文字超出格子高度就被裁掉（实测「发货批次」那一格被切）。
       整个模块的 body 上关了一次，这里再对底栏单独关一次更保险。 */
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
  }
  /* 「已停用」这类标记：底栏空间窄，只做一个小圆点，细节看侧边菜单。
     贴右上角内侧（而不是往外推），避免挤到旁边那一格。 */
  .dock__tag {
    position: absolute; top: 5px; right: 6px;
    width: 6px; height: 6px; border-radius: 50%;
    background: var(--jxc-color-warning);
  }
}

/* ---------- 页面头 ---------- */
.page-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 16px; flex-wrap: wrap; }
.page-header__title { font-size: 20px; font-weight: 600; }
.page-header__sub { margin-top: 4px; font-size: 13px; color: var(--jxc-text-secondary); }
.page-header__actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* ---------- 卡片 ---------- */
.panel {
  background: var(--jxc-surface);
  border: 1px solid rgba(0, 83, 207, .06);
  border-radius: var(--jxc-card-radius);
  box-shadow: var(--jxc-card-shadow);
  margin-bottom: 16px;
  overflow: hidden;
}
.panel__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--jxc-border-lighter);
}
.panel__title { font-size: 15px; font-weight: 600; }
.panel__body { padding: 16px; }
.panel__body.is-tight { padding: 0; }

.stack { display: flex; flex-direction: column; gap: 14px; }
.sub-title { font-size: 14px; font-weight: 600; margin-top: 4px; }
.grid-2-1 { display: grid; grid-template-columns: 2fr 1fr; gap: 16px; align-items: start; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; align-items: start; }
.grid-1 { display: grid; grid-template-columns: 1fr; }
.split-2 { display: grid; grid-template-columns: 320px 1fr; gap: 16px; align-items: start; }
.split-2__side, .split-2__main { min-width: 0; }

/* ---------- 指标卡 ---------- */
.stat-grid { display: grid; gap: 16px; margin-bottom: 16px; }
.stat-grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
.stat-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
/* 五列：退回管理用它（件数 / 运费 / 待寄回 / 运输中 / 已签收）。
   1400px 以下降到 3 列 —— 5 列在窄屏每张只剩约 200px，金额会被挤到换行。 */
.stat-grid.cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
@media (max-width: 1400px) { .stat-grid.cols-5 { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 820px) { .stat-grid.cols-5 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* 首页 KPI 一行 6 张。窄屏逐级降到 3 / 2 列 ——
   6 列在 1100px 以下每张只剩 ~160px，金额会被挤到换行。 */
.stat-grid.cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
@media (max-width: 1400px) { .stat-grid.cols-6 { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 820px) { .stat-grid.cols-6 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* 首页 KPI 加密到 7 张（加了「在库瑕疵成本」）。
   ⚠ 复用了 .cols-6 的降级断点：宽屏 7 列，≤1400px 落 4 列（7 张排两行）、
   ≤820px 落 2 列。**不能只写一句 repeat(7,1fr)** ——
   窄屏下 7 列每张不到 100px，金额会被压成竖排（这个坑在
   「手机端卡片文字一字一行」那次已经踩过一次）。 */
.stat-grid.cols-7 { grid-template-columns: repeat(7, minmax(0, 1fr)); }
@media (max-width: 1400px) { .stat-grid.cols-7 { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 820px) { .stat-grid.cols-7 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* 首页 KPI 到 8 张（又加了「返红包」）。
   ⚠ 同样必须带窄屏降级：8 列在 1440px 下每张只剩 ~150px，
   「¥166,263.00」这种金额会被挤到换行；窄屏更会变成竖排。
   ⚠ 宽屏 8 列时金额字号要收一档（见下面 .cols-8 .stat__value）——
   17px 的金额在 150px 宽的卡里放不下「¥166,263.00」共 11 个字符。 */
.stat-grid.cols-8 { grid-template-columns: repeat(8, minmax(0, 1fr)); }
@media (max-width: 1400px) { .stat-grid.cols-8 { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 820px) { .stat-grid.cols-8 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1401px) {
  .stat-grid--dense.cols-8 .stat__value { font-size: 15px; }
  .stat-grid--dense.cols-8 .stat { min-height: 44px; }
}
.stat {
  background: var(--jxc-surface);
  border: 1px solid rgba(0, 83, 207, .06);
  border-radius: var(--jxc-card-radius);
  box-shadow: var(--jxc-card-shadow);
  padding: 14px 16px;
  min-height: 88px;
  display: flex; flex-direction: column; gap: 4px;
}
.stat__top { display: flex; align-items: center; justify-content: space-between; }
.stat__label { font-size: 13px; color: var(--jxc-text-secondary); }
.stat__icon { color: var(--jxc-text-placeholder); }
.stat__value { font-size: 22px; font-weight: 600; letter-spacing: -.3px; }
.stat__sub { font-size: 12px; color: var(--jxc-text-secondary); }
.stat--blue { border-left: 3px solid var(--current-color); }
.stat--green { border-left: 3px solid #30b08f; }
.stat--purple { border-left: 3px solid #7c5cff; }
.stat--amber { border-left: 3px solid #e6a23c; }
.stat--red { border-left: 3px solid #f56c6c; }

/* 可点的指标卡（点进去能查到明细的那种，如首页「待领返红包」）。
   ⚠ 必须给出**可点的视觉线索**，否则用户不知道这张卡能点 ——
   而"看起来能点、点了没反应"比不能点更让人恼火，反之亦然。
   拟态界面里可点元素的惯例是"凸起 → 按下时内凹"，与按钮同一套语言。
   同时把 label 换成主色、鼠标变手型、加一个不易察觉的上浮。 */
.stat.is-clickable { cursor: pointer; transition: box-shadow var(--jxc-duration) var(--jxc-ease), transform var(--jxc-duration) var(--jxc-ease); }
.stat.is-clickable .stat__label { color: var(--current-color); }
.stat.is-clickable:hover { box-shadow: var(--jxc-neu-out-lg); transform: translateY(-1px); }
.stat.is-clickable:active { box-shadow: var(--jxc-neu-press); transform: translateY(1px); }
/* 键盘可达：只有 onclick 的 div 键盘走不到，所以 statCard 给了 tabindex，
   这里把焦点画出来（不然 tab 过去看不出停在哪） */
.stat.is-clickable:focus-visible {
  outline: 2px solid var(--current-color);
  outline-offset: 2px;
}

/* ---------- 指标卡：紧凑档（.stat-grid--dense） ----------
   列表页（出售记录 13 列）上面挂 4 张卡，标准档每张 88px 高 +
   16px 间距 + 16px 下边距 ≈ 占掉 120px 垂直空间，把真正要看的表格压到很低。

   这里不只是「把留白收小」，而是**换一种排法**：标签、数值、补充说明
   排成一行（数值加粗放大、其余压小），卡高从 88px 掉到 44px 左右 ——
   比单纯收留白多省一倍空间。
   数值仍是绝对主角（+2px、700 字重），其余降为陪衬。
   ⚠ 只影响列表页的观感，不改「指标卡长什么样」这个组件本身。 */
.stat-grid--dense { gap: 8px; margin-bottom: 10px; }
.stat-grid--dense .stat {
  padding: 7px 12px;
  min-height: 44px;
  /* 一行排：标签 · 数值 · 说明 */
  flex-direction: row;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}
/* 图标在单行排法里没地方站（会顶开标签），紧凑档直接不显示 ——
   标签本身已经说明了这是什么指标。 */
.stat-grid--dense .stat__icon { display: none; }
.stat-grid--dense .stat__top { display: contents; }
.stat-grid--dense .stat__label { font-size: 12px; flex: 0 0 auto; }
.stat-grid--dense .stat__value { font-size: 17px; font-weight: 700; flex: 0 0 auto; }
.stat-grid--dense .stat__sub {
  font-size: 11px; flex: 1 1 auto; min-width: 0;
  /* 说明太长时省略，不要把卡片撑成两行 */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  text-align: right;
}

/* ---------- 出售记录页：整页收紧 ----------
   这一页有四层（页面头 / 4 张指标卡 / 筛选卡 / 记录卡），标准留白叠起来
   两百多 px，13 列的表格被挤到要滚动才看得全。
   所有规则都挂在 .sellpage 下 —— 不能改全局 .panel / .filter-bar，
   那会把首页、采购、商品档案的卡片一并改瘦。
   （.input 本来就是 32px 高，这里只收紧外层留白与字段间距。） */
.sellpage .panel { margin-bottom: 12px; }
.sellpage .panel__head { padding: 10px 14px; }
.sellpage .panel__title { font-size: 14px; }
.sellpage .filter-bar { padding: 10px 14px; gap: 8px 10px; }

/* ---------- 仪表盘头部 ---------- */
.dashboard-hero {
  background: linear-gradient(135deg, #003fa3 0%, var(--current-color) 55%, #226bf7 100%);
  color: #fff;
  border-radius: var(--jxc-card-radius);
  padding: 20px 22px;
  margin-bottom: 16px;
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  flex-wrap: wrap;
}
.dashboard-hero__title { font-size: 20px; font-weight: 600; }
.dashboard-hero__sub { margin-top: 6px; font-size: 13px; opacity: .88; }
.dashboard-hero__stats { display: flex; gap: 28px; flex-wrap: wrap; }
.dashboard-hero__stat { display: flex; flex-direction: column; gap: 2px; }
.dashboard-hero__stat span { font-size: 12px; opacity: .82; }
.dashboard-hero__stat strong { font-size: 18px; font-weight: 600; }

/* 期间筛选条：默认吸顶（订单/统计这类长列表页有用 ——
   滑到下面还能直接切时间范围，不用滚回顶部）。
   ⚠ top 要把导航栏和标签栏都算进去，否则会被它们盖住。 */
.period-bar {
  position: sticky; top: calc(var(--jxc-navbar-height) + var(--jxc-tags-height));
  z-index: 10;
  background: var(--jxc-bg);
  padding: 8px 0 12px;
  margin-bottom: 4px;
}
/* 筛选卡：左侧筛选、右侧放低频操作（如仪表盘的「恢复出厂设置」）。
   用 space-between + 允许换行，窄屏时按钮自然落到下一行。 */
.period-bar__filters {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
  background: var(--jxc-surface);
  border: 1px solid rgba(0, 83, 207, .06);
  border-radius: var(--jxc-card-radius);
  padding: 10px 12px;
}
.period-bar__ops { display: flex; gap: 8px; align-items: center; padding-bottom: 1px; }

/* ---------- 首页顶部条 ----------
   原来是「你好，XX」的渐变头部 + 期间选择条两块，
   打招呼占一整块却只有一句话。现在合成一条：左侧一句话数据基准，
   右侧筛选与低频操作。第一屏直接就是数据。 */
.dashboard-bar {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
  /* ⚠ 首页这条**不吸顶**（覆盖 .period-bar 的 sticky）。
     它是 .period-bar 的一个变体，高度有 225px 左右 —— 手机上占屏幕近四分之一。
     吸顶之后往下滑它会一直压在 KPI 卡上面，第一屏几乎被它吃掉，
     所以用户要求「跟随滑动而滑动」。
     其它页面（订单 / 统计）的期间条仍然是吸顶的：那边内容长，
     滑到下面就切不了时间范围了。这里只关掉首页这一条。 */
  position: static;
}
.dashboard-bar__id { display: flex; flex-direction: column; gap: 2px; padding-bottom: 6px; min-width: 0; }
.dashboard-bar__who { font-size: 15px; font-weight: 600; }
.dashboard-bar__meta { font-size: 12px; color: var(--jxc-text-secondary); }

/* ---------- 发货批次：整页时间范围条 ----------
   和首页 .dashboard-bar 同一个套路：左侧写"现在看的是哪段时间 + 这条管到哪"，
   右侧放月份下拉。
   ⚠ 它管的是整页（6 张指标卡 + 收货仓 + 批次列表都跟随），
   所以不能塞进下面的筛选条和「状态 / 承运类型 / 承运商」并列 ——
   那三个只筛列表，混在一起会让人以为月份也只筛列表。
   ⚠ 也**不吸顶**：这一条只有一行高，吸顶会在滑动时压住表格；
   而发货页内容不长，滚回顶部很容易。 */
.ship-period {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
  position: static;
  padding: 0 0 10px;
}
.ship-period__id { display: flex; flex-direction: column; gap: 2px; padding-bottom: 6px; min-width: 0; }
.ship-period__label {
  font-size: 12px; color: var(--jxc-text-secondary);
  letter-spacing: .3px;
}
.ship-period__value { font-size: 15px; font-weight: 600; }
.ship-period__meta { font-size: 12px; color: var(--jxc-text-secondary); }
/* ★★ 不吸顶的期间条：发货批次页的行级筛选条用这个（见 shipment.js 的说明）。
   ----------------------------------------------------------------------------
   用户要求「发货批次状态卡片不要固定，跟随滑动而滑动」。
   实测（390×844，页面高 4716px）滚到 400px 时：
     时间范围条   top -225  static  ← 跟着滚走了
     6 张指标卡   top  -82  static  ← 也滚走了
     这一条筛选条 top  142  sticky  ← **只有它**钉在屏幕上
   所以"固定不动的那个"就是它。
   .period-bar 通用是吸顶（订单/统计这类长列表页滑到下面还要切时间范围），
   但发货页这条只有行级筛选，真正的时间维度在上面那条 .ship-period 里。
   一条行级筛选钉在屏幕上挡住列表，收益很小、挡住的却是要看的批次。
   ⚠ 用专门的标记类，**不用** :has() —— 本项目明确不用 :has()
   （老浏览器里整条规则失效且不报错，见下面 dashboard 那段注释）。
   ⚠ 用 `position: static` 而不是 `initial`：这里要表达的是"按普通文档流排"，
   不是"重置成浏览器默认值"，后者读代码的人得去查 initial 对 position 是什么。 */
.period-bar--nofix { position: static; }

/* 批次列表状态列：已送达但关联采购单还没入仓时的缺口提示。
   与 badge 并排，用橙色小字 —— 它是个"该处理但没坏"的提醒，
   不是错误（所以不用红色），但必须显眼到能被看见：
   库存少一层却没有任何提示，是这次实测踩到的真实问题。 */
.ship-status { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; }
.ship-status__warn {
  font-size: 11px; color: #e6a23c;
  border-bottom: 1px dashed rgba(230, 162, 60, .5);
  cursor: help; white-space: nowrap;
}
/* 这条里的筛选卡不再左右撑开（外层已经在做 space-between），
   并且去掉卡片的边框与底色 —— 它是整页的时间维度，不是一个"筛选框"，
   画成卡片会和下面真正的筛选条长得一样、分不出层级。 */
.ship-period .period-bar__filters {
  background: transparent;
  border: 0;
  box-shadow: none;
  padding: 0;
}
/* 窄屏：左侧说明与右侧下拉各占一行 */
@media (max-width: 820px) {
  .ship-period { flex-direction: column; align-items: stretch; gap: 6px; }
  .ship-period .period-bar__filters { justify-content: flex-start; }
}
/* 自定义月份：紧挨着月份下拉，与它是"或"的关系。
   用一条竖线把它和下拉隔开 —— 不隔开的话看起来像两个并列的筛选，
   用户不知道填了输入框之后下拉还算不算数。 */
.ship-custom-month {
  display: flex; align-items: center; gap: 6px;
  padding-left: 10px;
  border-left: 1px solid rgba(0, 83, 207, .12);
}
.ship-custom-month__label { font-size: 12px; color: var(--jxc-text-secondary); white-space: nowrap; }
.ship-custom-month__input { width: 150px; }
.ship-custom-month__clear {
  border: 0; background: transparent; cursor: pointer;
  font-size: 12px; color: var(--jxc-text-secondary);
  padding: 2px 4px; border-radius: 4px;
}
.ship-custom-month__clear:hover { color: var(--current-color); background: rgba(0, 83, 207, .06); }
@media (max-width: 820px) {
  /* 窄屏下左侧竖线会贴着屏幕边，去掉它改成整行 */
  .ship-custom-month { padding-left: 0; border-left: 0; }
  .ship-custom-month__input { flex: 1 1 auto; width: auto; min-width: 0; }
}
/* 顶部条里的筛选卡不再需要自己的左右撑开（外层已经在做 space-between） */
.dashboard-bar .period-bar__filters { flex: 1 1 420px; }

/* ---------- KPI 卡内迷你趋势 ----------
   sparkline 画在卡片底部，高度很小，不占额外一行。
   overflow:hidden 是为了让折线的顶部不越出卡片圆角。 */
.stat__spark { margin-top: auto; padding-top: 6px; overflow: hidden; line-height: 0; }
.stat__spark .spark { width: 100%; height: 26px; }
/* 紧凑档的卡是横向单行排（display:row），sparkline 要独占一行 ——
   用 flex-basis:100% 让它换行到下面，不跟标签/数值挤在一起。
   带趋势的卡因此比纯数字卡高一些（44px 放不下两行），
   所以下面用 :has() 的**替代写法**：由 dashboard.js 给带 spark 的卡
   额外加类不现实（statCard 不感知页面），改成直接放宽 min-height。
   ⚠ 不用 :has() —— 老浏览器里会整条规则失效且不报错。
   44px 是纯数字卡的下限；带 spark 的卡靠内容自然撑到 ~70px，
   两者混排时用 align-items:stretch 让同行卡片等高。 */
.stat-grid--dense .stat__spark { flex: 0 0 100%; padding-top: 4px; }
.stat-grid--dense.cols-6 { align-items: stretch; }
.stat-grid--dense.cols-6 .stat { min-height: 44px; }

/* ---------- 筛选栏 ---------- */
.filter-bar { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end; }
.filter-bar__field { display: flex; flex-direction: column; gap: 4px; }
.filter-bar__label { font-size: 12px; color: var(--jxc-text-secondary); }
.filter-bar__actions { display: flex; gap: 8px; margin-left: auto; }

/* ---------- 自定义日期区间（筛选条下方展开的一行） ----------
   只在时间档位选「自定义」时出现。与筛选条同一张卡，用虚线分隔，
   避免看起来像另一个独立控件。 */
.range-custom {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px; padding-top: 10px;
  border-top: 1px dashed var(--jxc-border-lighter);
}
.range-custom__label { flex: 0 0 auto; font-size: 12px; color: var(--jxc-text-secondary); }
.range-custom__date { flex: 0 0 auto; width: 150px; }
.range-custom__sep { flex: 0 0 auto; font-size: 12px; color: var(--jxc-text-secondary); }
.range-custom__hint { flex: 1 1 auto; min-width: 0; font-size: 12px; color: var(--jxc-text-placeholder); }
/* 开始晚于结束：只提示、不生效。用警告色而非错误色 ——
   这不是「填错了不能提交」，而是「这一刻先不筛」。 */
.range-custom__warn { flex: 1 1 auto; min-width: 0; font-size: 12px; color: var(--jxc-color-warning); }
.range-custom.is-invalid .range-custom__date { border-color: var(--jxc-color-warning); }
.range-custom__clear {
  flex: 0 0 auto;
  border: 0; background: transparent; padding: 4px 2px;
  font-size: 12px; color: var(--current-color);
  text-decoration: underline; text-underline-offset: 3px;
  cursor: pointer;
}
/* 移动端：两个 150px 的日期框挤在一行会溢出，各占一行 */
@media (max-width: 768px) {
  .range-custom__label { flex: 1 1 100%; }
  .range-custom__date { flex: 1 1 100%; width: auto; }
  .range-custom__sep { flex: 0 0 auto; }
  .range-custom__hint, .range-custom__warn { flex: 1 1 100%; }
}

/* ---------- 表单控件 ---------- */
.input {
  height: 32px;
  padding: 0 10px;
  font-size: 13px;
  color: var(--jxc-text-primary);
  background: var(--jxc-surface);
  border: 0;
  border-radius: var(--jxc-radius-sm);
  /* 线上用 inset box-shadow 代替 border，聚焦时改为主色 */
  box-shadow: 0 0 0 1px var(--jxc-border) inset;
  outline: none;
  min-width: 140px;
  max-width: 100%;
  transition: box-shadow .2s;
}
.input:focus { box-shadow: 0 0 0 1px var(--current-color) inset; }
.input--area { height: auto; padding: 8px 10px; min-height: 90px; resize: vertical; font-family: inherit; width: 100%; }
select.input { cursor: pointer; }

.field { display: flex; flex-direction: column; gap: 5px; }
.field__label { font-size: 13px; color: var(--jxc-text-regular); }
.field__req { color: #f56c6c; font-style: normal; margin-left: 3px; }
.field__hint { font-size: 12px; color: var(--jxc-text-secondary); }
.form-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }

.switch-row { display: flex; align-items: center; justify-content: space-between; padding: 7px 0; font-size: 13px; }

/* ---------- 按钮 ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  height: 32px; padding: 0 15px;
  font-size: 13px; font-weight: 500;
  border: 1px solid var(--jxc-border);
  border-radius: var(--jxc-radius-sm);
  background: var(--jxc-surface);
  color: var(--jxc-text-regular);
  cursor: pointer;
  transition: all .2s;
  white-space: nowrap;
}
.btn:hover { color: var(--current-color); border-color: var(--el-color-primary-light-5, var(--current-color)); background: var(--el-color-primary-light-9, #f2f7ff); }
.btn--primary { background: var(--current-color); border-color: var(--current-color); color: #fff; }
/* ⚠ 实心按钮的 hover **必须往深走，不能往浅走**。
   原来这里用的是 light-3（派生色阶里 light-N 是往白混，见 util.js 的 shade()），
   算出 #4d87dd，白字对比只有 3.61:1；而 .btn:hover 那个 light-9 (#e6eefa)
   更是只有 1.17:1 —— 白字基本消失。用户报的「亮色背景看不见」就是这一类。
   dark-N 走的是 tint()（往黑混），越深对比越高，所以实心底用 dark-3。 */
.btn--primary:hover { background: var(--el-color-primary-dark-3, #0043a6); border-color: var(--el-color-primary-dark-3, #0043a6); color: #fff; }
.btn--primary:active { background: var(--el-color-primary-dark-5, #00377f); border-color: var(--el-color-primary-dark-5, #00377f); color: #fff; }
/* 危险色：原来的 #f56c6c 上白字只有 **2.90:1**（WCAG AA 要 4.5），
   属于"看着还行、实际读不清"的那一档。往下压到 #cf3535 得 4.98:1，
   hover / active 再深一档，越按越深。
   （这个色值同时是 tokens.css 的 --jxc-color-danger，但那里不改 ——
   徽标、提示文字用它做**前景色**，压深会连带影响那些地方。
   所以只改按钮自己的底色，语义色保持不变。） */
.btn--danger { background: #cf3535; border-color: #cf3535; color: #fff; }
.btn--danger:hover { background: #bb2b2b; border-color: #bb2b2b; color: #fff; }
.btn--danger:active { background: #a82121; border-color: #a82121; color: #fff; }
.btn--sm { height: 24px; padding: 0 9px; font-size: 12px; }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn-row { display: flex; gap: 10px; flex-wrap: wrap; }
.link { color: var(--current-color); cursor: pointer; font-size: 12px; }
.link:hover { text-decoration: underline; }
.row-ops { display: flex; gap: 8px; justify-content: flex-end; align-items: center; }
/* 单元格里「徽标 + 说明文字」并排（如平台名后面跟「内置 / 自定义」） */
.row-flex { display: flex; gap: 7px; align-items: center; }
/* 允许换行的变体：承运类型分组这类个数不定的横排内容用 */
.row-flex--wrap { flex-wrap: wrap; }

/* 发货批次：按承运类型分组的统计块 */
.carrier-chip {
  display: flex; flex-direction: column; gap: 2px;
  min-width: 132px;
  padding: 10px 14px;
  background: var(--jxc-fill-light);
  border: 1px solid var(--jxc-border-lighter);
  border-radius: 8px;
}
.carrier-chip__type { font-size: 12px; color: var(--jxc-text-secondary); }
.carrier-chip__v { font-size: 12px; color: var(--jxc-text-secondary); }
.carrier-chip__cost { font-size: 15px; color: var(--jxc-text); }

/* ---------- 收货仓卡片 ----------
   得物这类平台是寄仓模式：建包时要把仓的收货人 / 电话 / 地址照抄到平台后台。
   卡片存在的唯一理由就是「照着抄」，所以电话与地址各带一个复制键，
   地址用等宽字体（数字与门牌号不容易看错行）。 */
.wh-cards {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 12px;
}
.wh-card {
  display: flex; flex-direction: column; gap: 8px;
  padding: 12px 14px;
  background: var(--jxc-fill-light);
  border: 1px solid var(--jxc-border-lighter);
  border-radius: var(--jxc-radius-sm);
  min-width: 0;
}
.wh-card--unknown { border-style: dashed; }
.wh-card__head { display: flex; align-items: center; gap: 7px; min-width: 0; }
.wh-card__head svg { flex: 0 0 auto; color: var(--jxc-text-placeholder); }
.wh-card__name { font-size: 14px; flex: 0 0 auto; }
.wh-card__contact {
  font-size: 13px; color: var(--jxc-text-regular);
  font-variant-numeric: tabular-nums;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.wh-card__addr { display: flex; align-items: flex-start; gap: 7px; min-width: 0; }
.wh-card__addr-text {
  flex: 1 1 auto; min-width: 0;
  font-size: 13px; line-height: 1.5; color: var(--jxc-text-regular);
  overflow-wrap: anywhere;
  /* 地址是照着抄的，数字错一位就发错地方 —— 用等宽字体减少看错 */
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.wh-card__meta { font-size: 12px; color: var(--jxc-text-secondary); }
.wh-card__copy {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px;
  border: 1px solid var(--jxc-border-lighter);
  border-radius: 5px;
  background: var(--jxc-surface);
  color: var(--jxc-text-secondary);
  cursor: pointer;
  transition: color .18s, border-color .18s;
}
.wh-card__copy:hover { color: var(--current-color); border-color: var(--current-color); }
/* 列表里的仓名小标签 */
.wh-tag {
  display: inline-block;
  padding: 2px 7px;
  font-size: 12px;
  border-radius: 4px;
  background: var(--jxc-fill);
  color: var(--jxc-text-regular);
  white-space: nowrap;
}

/* ---------- 表格里的「可复制」单元格（得物仓管理用） ----------
   收货电话与地址是这页最常被复制的东西（要往平台后台照抄），
   所以把复制键**贴在内容旁边**而不是藏进操作列：
   操作列在右边，让人先横向找再竖向找是多余的一步。 */
.cell-copy { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.cell-copy--wide { max-width: 320px; }
.cell-copy__text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 复制键平时淡一点，免得整列看着都是按钮；悬停才实心 */
.cell-copy .wh-card__copy { opacity: .55; }
.cell-copy:hover .wh-card__copy { opacity: 1; }

/* 弹窗里的行内错误提示（如「仓名重复」「电话格式不对」）。
   :empty 时不占位 —— 否则弹窗里会多出一块空白。
   ⚠ 底色用 --jxc-fill-light 而不是某个「danger 浅色」令牌：
   tokens.css 里并没有 --jxc-color-danger-light-9 这类派生变量，
   引一个不存在的变量不会报错，只会让底色静默消失（看起来像没样式）。 */
.form-err:not(:empty) {
  grid-column: 1 / -1;
  padding: 8px 10px;
  font-size: 13px;
  border-radius: var(--jxc-radius-sm);
  border: 1px solid var(--jxc-color-danger);
  background: var(--jxc-fill-light);
  color: var(--jxc-color-danger);
}
/* 弹窗里「收货仓」那一格整行铺开（它后面要挂地址卡 / 新建仓表单） */
.field--wide { grid-column: 1 / -1; }
.form-grid__full { grid-column: 1 / -1; display: flex; flex-direction: column; gap: 10px; }

/* ---------- 发货批次：勾选采购单 ----------
   建包时从系统里勾「已验货」的采购单，件数自动汇总。
   列表限高 + 滚动：演示账套里能勾的单有二三十张，不限高会把弹窗撑到屏幕外，
   而下面的承运商 / 运费 / 发货日期才是每次都要填的字段，不能被挤下去。 */
.ship-po { display: flex; flex-direction: column; gap: 8px; }
.ship-po__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; flex-wrap: wrap;
  font-size: 13px; font-weight: 500; color: var(--jxc-text);
}
.ship-po__count { font-weight: 400; color: var(--jxc-text-secondary); font-variant-numeric: tabular-nums; }
.ship-po__list {
  display: flex; flex-direction: column; gap: 6px;
  /* ⚠ 限高带上限 `min(46vh, 440px)`：每张单现在带商品明细（图 + 单价 + 小计），
     一张就 200px 出头。写死 260px 时连一张都看不全（实测：只露出大半张），
     勾选时得来回滚，很容易勾错。
     用 vh 而不是固定 px，是因为手机横屏 / 小屏时固定值会把弹层顶出屏幕；
     46vh 在常见手机上约等于 1.5~2 张单的高度，够看又留得住下面的字段。 */
  max-height: min(46vh, 440px); overflow-y: auto;
  padding: 2px;                       /* 给勾选态的描边留出不被裁掉的空间 */
  border: 1px solid var(--jxc-border-lighter); border-radius: 8px;
  background: var(--jxc-fill-light);
}
.ship-po__row {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 9px 10px; border-radius: 6px; cursor: pointer;
  background: var(--jxc-surface);
  border: 1px solid transparent;
}
.ship-po__row.is-on { border-color: var(--jxc-primary); background: var(--jxc-primary-lightest, #f2f7ff); }
.ship-po__row.is-blocked { cursor: not-allowed; opacity: .55; }
.ship-po__row input[type="checkbox"] { width: 16px; height: 16px; margin-top: 2px; flex: 0 0 auto; cursor: inherit; }
.ship-po__main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.ship-po__top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ship-po__no { font-family: var(--jxc-font-mono, ui-monospace, monospace); font-size: 13px; font-weight: 600; }
/* 平台订单号：和得物后台对单靠它，所以和系统单号并列在首行 */
.ship-po__order {
  font-size: 12px; color: var(--jxc-text-secondary);
  font-variant-numeric: tabular-nums;
}
.ship-po__qty { font-size: 13px; font-weight: 600; color: var(--jxc-primary); }
.ship-po__sub { font-size: 12px; color: var(--jxc-text-secondary); }
/* 采购价格：本批货值（主）+ 整单实付（次）。
   两个数含义不同，用不同字号与颜色明确区分，避免被当成同一个 ——
   与首页 KPI 里「期间利润 / 今日利润」的处理同理。 */
.ship-po__money { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-top: 2px; }
.ship-po__money-main { font-size: 13px; font-weight: 600; color: var(--jxc-text); font-variant-numeric: tabular-nums; }
.ship-po__money-sub { font-size: 12px; color: var(--jxc-text-secondary); font-variant-numeric: tabular-nums; }

/* ---------- 商品明细 ----------
   勾选时唯一能确认"寄的是不是这批货"的线索就是**图 + 货号**，
   光看单号勾错了要到对账才发现，所以每行都带缩略图。
   每行四列：[图 40px] [货号/规格 自适应] [件数] [单价/小计] */
.ship-po__lines {
  display: flex; flex-direction: column;
  margin-top: 8px; gap: 6px;
  border-top: 1px dashed var(--jxc-border-lighter); padding-top: 8px;
}
.ship-po__line { display: flex; align-items: center; gap: 9px; }
.ship-po__thumb {
  flex: 0 0 auto; width: 40px; height: 40px;
  border-radius: 5px; overflow: hidden;
  background: var(--jxc-fill-light);
  display: grid; place-items: center;
}
.ship-po__thumb .pimg { width: 100%; height: 100%; border-radius: 0; object-fit: cover; }
.ship-po__linemain { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.ship-po__code {
  font-family: var(--jxc-font-mono, ui-monospace, monospace);
  font-size: 12px; font-weight: 600; color: var(--jxc-text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ship-po__spec {
  font-size: 11px; color: var(--jxc-text-secondary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ship-po__lineqty {
  flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-end;
  font-size: 12px; font-variant-numeric: tabular-nums;
}
.ship-po__n { font-weight: 600; color: var(--jxc-text); }
.ship-po__sub2 { font-size: 11px; color: var(--jxc-text-secondary); }
.ship-po__lineamt {
  flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-end;
  min-width: 74px; font-variant-numeric: tabular-nums;
}
.ship-po__unit { font-size: 11px; color: var(--jxc-text-secondary); }
.ship-po__sum { font-size: 13px; font-weight: 600; color: var(--jxc-primary); }
.ship-po__empty {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 22px 12px; text-align: center;
  color: var(--jxc-text-secondary); font-size: 13px;
  border: 1px dashed var(--jxc-border-lighter); border-radius: 8px;
}
.ship-po__hint { font-size: 12px; opacity: .85; }
/* 件数：只读输入框 + 下面一行说明为什么不能改 */
.ship-qty { display: flex; flex-direction: column; gap: 4px; }
.ship-qty .field__hint { display: block; margin: 0; }
.input.is-readonly { background: var(--jxc-fill-light); color: var(--jxc-text-secondary); cursor: default; }

/* ---------- 退回明细编辑器（登记退回弹窗内） ----------
   一张退回单可以退多个规格多件，每件各有数量 / 问题 / 图片。
   每个规格一行；行内字段横排（弹窗 720px，横排比「标签在上」省高度）。 */
.ret-items { display: flex; flex-direction: column; gap: 10px; }
.ret-items__head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.ret-items__title { font-size: 13px; font-weight: 600; }
.ret-items__hint { font-size: 12px; color: var(--jxc-text-secondary); }
.ret-items__list { display: flex; flex-direction: column; gap: 10px; }
.ret-items__foot { display: flex; gap: 8px; }

.ret-item {
  position: relative;
  display: flex; flex-direction: column; gap: 8px;
  padding: 10px 34px 10px 12px;      /* 右侧留给删除键 */
  background: var(--jxc-fill-light);
  border: 1px solid var(--jxc-border-lighter);
  border-radius: var(--jxc-radius-sm);
}
.ret-item__no { font-size: 12px; font-weight: 600; color: var(--jxc-text-secondary); }
.ret-item__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 8px;
}
.ret-item__field { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.ret-item__field.is-wide { grid-column: 1 / -1; }
.ret-item__label { font-size: 11px; color: var(--jxc-text-secondary); }
.ret-item__grid .input { height: 30px; font-size: 13px; }

/* 图片：缩略图 + 上传 / 移除。有图时把缩略图画出来 ——
   用户要能确认「附对了没有」，只显示一个文件名是不够的。 */
.ret-item__img { display: flex; align-items: center; gap: 10px; }
.ret-item__thumb {
  flex: 0 0 auto;
  width: 52px; height: 52px;
  border-radius: 5px;
  object-fit: cover;
  border: 1px solid var(--jxc-border-lighter);
}
.ret-item__thumb--ph {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--jxc-fill);
  color: var(--jxc-text-placeholder);
}
.ret-item__imgops { display: flex; gap: 6px; flex-wrap: wrap; }
.ret-item__file { display: none; }
.ret-item__del {
  position: absolute; top: 8px; right: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px;
  border: 0; border-radius: 5px;
  background: transparent; color: var(--jxc-text-secondary);
  cursor: pointer;
}
.ret-item__del:hover { background: var(--jxc-fill); color: var(--jxc-color-danger); }

/* 列表里的「多规格 / 多问题」小标签组 */
.ret-goods { display: flex; flex-wrap: wrap; gap: 4px; }
.ret-goods__tag {
  padding: 1px 6px;
  font-size: 11px;
  border-radius: 4px;
  background: var(--jxc-fill);
  color: var(--jxc-text-regular);
  white-space: nowrap;
}
.wh-new {
  padding: 10px 12px;
  background: var(--jxc-fill-light);
  border: 1px dashed var(--jxc-border);
  border-radius: var(--jxc-radius-sm);
}

/* ---------- 发货批次：按承运类型的一条紧凑统计 ----------
   原来这是一整张 card（标题 + 一排 132px 宽的竖排 chip），
   只为看四组数字就占掉一整块。改成筛选条下方的一行横排 chip：
   信息一样，省掉一张卡的高度。 */
.ship-typebar {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 8px;
  padding: 0 0 12px;
}
.ship-typebar__label { flex: 0 0 auto; font-size: 12px; color: var(--jxc-text-secondary); }
.ship-typebar__hint { flex: 0 0 auto; font-size: 12px; color: var(--jxc-text-placeholder); margin-left: auto; }
/* 横排变体：类型名与金额同一行，宽度自适应 */
.carrier-chip--sm {
  flex: 0 0 auto;
  flex-direction: row; align-items: baseline; gap: 8px;
  min-width: 0; padding: 5px 10px;
}
.carrier-chip--sm .carrier-chip__type { font-size: 12px; color: var(--jxc-text); }
.carrier-chip--sm .carrier-chip__v { font-size: 11px; }
.carrier-chip--sm .carrier-chip__cost { font-size: 13px; }
/* 可点击的分组块（店铺管理里用来按平台筛选）：按钮语义、保留原视觉 */
.carrier-chip--btn { cursor: pointer; text-align: left; font: inherit; transition: border-color .18s, background .18s; }
.carrier-chip--btn:hover { border-color: var(--current-color); }
.carrier-chip--btn.is-active {
  border-color: var(--current-color);
  background: var(--el-color-primary-light-9, #ecf3ff);
  box-shadow: 0 0 0 1px var(--current-color) inset;
}

/* ---------- 品牌管理（弹窗内的列表） ---------- */
.brand-list { display: flex; flex-direction: column; gap: 6px; max-height: 44vh; max-height: 44dvh; overflow-y: auto; }
.brand-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--jxc-border-lighter); border-radius: 8px;
  background: var(--jxc-surface);
}
.brand-row__main { display: flex; align-items: baseline; gap: 8px; flex: 1 1 auto; min-width: 0; flex-wrap: wrap; }
.brand-row__main strong { font-size: 14px; }
.brand-add { display: flex; gap: 8px; align-items: center; padding-top: 4px; }
.brand-add .input, .brand-add input { flex: 1 1 auto; min-width: 0; }

/* 首页「今日售出订单」的汇总条 */
.today-bar {
  display: flex; flex-wrap: wrap; gap: 10px 28px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--jxc-border-lighter);
  background: var(--jxc-fill-light);
}
.today-bar__item { display: flex; align-items: baseline; gap: 8px; }
.today-bar__item span { font-size: 12px; color: var(--jxc-text-secondary); }
.today-bar__item strong { font-size: 16px; color: var(--jxc-text); }
.today-bar__item strong.is-up { color: #30b08f; }
.today-bar__item strong.is-down { color: #f56c6c; }

/* ---------- 表格勾选（批量管理） ---------- */
.table__check input[type="checkbox"] { width: 15px; height: 15px; cursor: pointer; accent-color: var(--current-color); }
.table__check input[type="checkbox"]:disabled { cursor: not-allowed; opacity: .45; }
/* 勾选框禁用的行：业务上不允许操作。整行压暗，但勾选框自身不变（要让用户看清是禁用的） */
.table tbody tr.is-locked > td { opacity: .55; }
.table tbody tr.is-locked > td.table__check { opacity: 1; }

/* ---------- 出售记录的批量操作栏（嵌在卡片内，不做固定定位） ---------- */
.sell-selbar {
  display: flex; flex-direction: column; gap: 10px;
  padding: 12px 14px;
  background: var(--jxc-fill-light);
  border-bottom: 1px solid var(--jxc-border-lighter);
}
.sell-selbar__info { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.sell-selbar__count { font-size: 14px; font-weight: 600; }
.sell-selbar__hint { font-size: 13px; color: var(--jxc-text-secondary); }
.sell-selbar__warn { font-size: 13px; color: #b7791f; }
.sell-selbar__ops { display: flex; gap: 8px; flex-wrap: wrap; }
.sell-selbar__ops .pm-btn { height: 36px; font-size: 14px; min-width: 108px; }
/* 选择动作弱化成描边，别和「删除选中」抢注意力 */
.sell-selbar__ops--select .pm-btn {
  background: transparent; color: var(--jxc-text-secondary); border-color: var(--jxc-border);
}

/* ---------- 标签 ---------- */
.tag {
  display: inline-flex; align-items: center;
  height: 24px; padding: 0 9px;
  font-size: 12px; line-height: 1;
  border-radius: var(--jxc-radius-sm);
  background: var(--jxc-fill);
  color: var(--jxc-text-regular);
  white-space: nowrap;
}
.tag--primary { background: var(--el-color-primary-light-9, #ecf3ff); color: var(--current-color); }
.tag--success { background: #eef9e8; color: #529b2e; }
.tag--warning { background: #fdf3e6; color: #b88230; }
.tag--danger { background: #fdeeee; color: #c45656; }
.tag--info { background: var(--jxc-fill); color: var(--jxc-text-secondary); }

/* ---------- 表格 ---------- */
.table-wrap { width: 100%; overflow-x: auto; }
.table { width: 100%; border-collapse: collapse; font-size: 13px; }
.table__th {
  height: var(--jxc-table-header-height);
  padding: 8px 12px;
  text-align: left;
  font-weight: 600;
  font-size: 13px;
  color: var(--jxc-table-header-text);
  background: var(--jxc-table-header-bg);
  border-bottom: 1px solid var(--jxc-table-border);
  white-space: nowrap;
}
.table__td {
  padding: 8px 12px;
  border-bottom: 1px solid var(--jxc-table-border);
  color: var(--jxc-text-regular);
  vertical-align: middle;
}
.table tbody tr:hover { background: var(--jxc-table-row-hover); }
.table tbody tr.is-clickable { cursor: pointer; }
.table__th.is-right, .table__td.is-right { text-align: right; }
.table__th.is-center, .table__td.is-center { text-align: center; }
.table--compact .table__td { padding: 6px 10px; }
/* 紧凑表的表头也跟着收：默认表头 40px 高，正文收紧了表头却不变会很割裂 */
.table--compact .table__th { height: 34px; padding: 5px 10px; }
.table__empty { padding: 0 !important; border-bottom: 0; }
.table__check { width: 36px; text-align: center; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; }
.muted { color: var(--jxc-text-secondary); }
.xs { font-size: 11px; }
.amount { font-variant-numeric: tabular-nums; }
.amount.is-pos, .is-pos { color: #30b08f; }
.amount.is-neg, .is-neg { color: #f56c6c; }

/* ---------- 「实际利润」单元格 ----------
   已登记到账 → 只显示金额；还没登记 → 金额后面挂一个小小的「估」。
   不标的话，两种数混在同一列里看不出来哪个是实数、哪个是估的。 */
.profit-cell { display: inline-flex; align-items: baseline; justify-content: flex-end; gap: 4px; }
.profit-cell__est {
  flex: 0 0 auto;
  font-size: 10px; line-height: 1;
  padding: 2px 3px; border-radius: 3px;
  color: var(--jxc-text-secondary);
  background: var(--jxc-fill);
}
.user-cell { display: flex; align-items: center; gap: 8px; }

.pager { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 16px; border-top: 1px solid var(--jxc-border-lighter); }
.pager__info { font-size: 12px; color: var(--jxc-text-secondary); }
.pager__btns { display: flex; gap: 8px; }

/* ---------- 页签 ---------- */
.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--jxc-border-lighter); margin-bottom: 16px; overflow-x: auto; }
.tabs__item {
  height: 40px; padding: 0 16px;
  font-size: 14px; font-weight: 500;
  background: transparent; border: 0;
  border-bottom: 2px solid transparent;
  color: var(--jxc-text-regular);
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
  white-space: nowrap;
}
.tabs__item:hover { color: var(--current-color); }
.tabs__item.is-active { color: var(--current-color); border-bottom-color: var(--current-color); }
.tabs__count { font-size: 11px; background: var(--jxc-fill); padding: 1px 6px; border-radius: 8px; }

/* ---------- 分段控件 ---------- */
.seg { display: inline-flex; border: 1px solid var(--jxc-border); border-radius: var(--jxc-radius-sm); overflow: hidden; }
.seg__btn { height: 28px; padding: 0 12px; font-size: 12px; background: var(--jxc-surface); border: 0; color: var(--jxc-text-regular); cursor: pointer; }
.seg__btn + .seg__btn { border-left: 1px solid var(--jxc-border); }
.seg__btn.is-active { background: var(--current-color); color: #fff; }
.seg--sm .seg__btn { height: 24px; padding: 0 9px; }

/* ---------- 图表 ---------- */
.chart-box { width: 100%; }
.chart { width: 100%; height: auto; display: block; }
.chart__grid { stroke: var(--jxc-border-lighter); stroke-width: 1; }
.chart__tick { fill: var(--jxc-text-secondary); font-size: 11px; }
.chart-legend { display: flex; gap: 16px; justify-content: center; margin-top: 6px; flex-wrap: wrap; }
.chart-legend__item { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--jxc-text-secondary); }
.chart-legend__dot { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }
.spark-row { padding: 4px 0; }
.spark { width: 100%; height: auto; }

.barlist { display: flex; flex-direction: column; gap: 10px; }
.barlist__row { display: grid; grid-template-columns: 130px 1fr 90px auto; align-items: center; gap: 8px; }
.barlist__label { font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.barlist__track { height: 8px; background: var(--jxc-fill); border-radius: 100px; overflow: hidden; }
.barlist__fill { display: block; height: 100%; border-radius: 100px; }
.barlist__value { font-size: 13px; text-align: right; font-variant-numeric: tabular-nums; }
.barlist__extra { font-size: 12px; color: var(--jxc-text-secondary); }

.donut-box { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.donut { flex: 0 0 auto; }
.donut__total { font-size: 15px; font-weight: 600; fill: var(--jxc-text-primary); }
.donut__cap { font-size: 11px; fill: var(--jxc-text-secondary); }
.donut-legend { flex: 1 1 160px; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.donut-legend__row { display: grid; grid-template-columns: 10px 1fr auto auto; align-items: center; gap: 7px; font-size: 12px; }
.donut-legend__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.donut-legend__value { font-variant-numeric: tabular-nums; }
.donut-legend__pct { color: var(--jxc-text-secondary); min-width: 42px; text-align: right; }

/* ---------- 列表小件 ---------- */
.todo-list { display: flex; flex-direction: column; }
.todo-row { display: grid; grid-template-columns: 1fr auto; gap: 4px 10px; padding: 10px 0; border-bottom: 1px dashed var(--jxc-border-lighter); }
.todo-row:last-child { border-bottom: 0; }
.todo-row__name { font-size: 13px; }
.todo-row__count { font-size: 16px; font-weight: 600; color: var(--current-color); }
.todo-row__note { grid-column: 1 / -1; font-size: 12px; color: var(--jxc-text-secondary); }
.todo-row:hover .todo-row__name { color: var(--current-color); }

.warn-list { display: flex; flex-direction: column; gap: 10px; }
.warn-row { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 9px; }
.warn-row__main { min-width: 0; display: flex; flex-direction: column; }
.warn-row__main strong { font-size: 13px; }
.warn-row__qty { font-size: 13px; font-variant-numeric: tabular-nums; }

.mini-summary { display: flex; flex-direction: column; gap: 10px; }
.mini-summary__row { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 13px; }
.mini-summary__row span { color: var(--jxc-text-secondary); }

.total-row { display: flex; flex-wrap: wrap; gap: 18px; align-items: center; font-size: 13px; }

.empty { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 34px 16px; color: var(--jxc-text-secondary); }
.empty svg { opacity: .35; }
.empty__text { font-size: 14px; }
.empty__hint { font-size: 12px; }
.empty-mini { padding: 18px; text-align: center; color: var(--jxc-text-secondary); font-size: 13px; }

.desc { display: grid; gap: 10px 18px; }
.desc--1 { grid-template-columns: 1fr; }
.desc--2 { grid-template-columns: repeat(2, 1fr); }
.desc--3 { grid-template-columns: repeat(3, 1fr); }
.desc__row { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.desc__label { font-size: 12px; color: var(--jxc-text-secondary); }
.desc__value { font-size: 13px; word-break: break-all; }

.timeline { position: relative; padding-left: 18px; }
.timeline__item { position: relative; padding: 0 0 16px 8px; border-left: 1px solid var(--jxc-border-lighter); }
.timeline__item.is-last { border-left-color: transparent; padding-bottom: 0; }
.timeline__dot { position: absolute; left: -5px; top: 4px; width: 9px; height: 9px; border-radius: 50%; background: var(--current-color); }
.timeline__item.is-pending .timeline__dot { background: var(--jxc-border); }
.timeline__body { display: flex; flex-direction: column; gap: 2px; }
.timeline__time { font-size: 12px; color: var(--jxc-text-secondary); }
.timeline__note { font-size: 12px; color: var(--jxc-text-secondary); }

.bullet { list-style: disc; padding-left: 20px; display: flex; flex-direction: column; gap: 5px; font-size: 13px; color: var(--jxc-text-regular); }

.notice { padding: 11px 15px; border-radius: var(--jxc-radius-sm); font-size: 13px; }
.notice--warn { background: #fdf6ec; color: #b88230; }
/* 信息型提示（发货批次停用说明等）：带图标、标题与操作区 */
.notice--info {
  display: flex; align-items: flex-start; gap: 12px;
  background: var(--el-color-primary-light-9, #f2f7ff);
  color: var(--jxc-text-regular);
  border: 1px solid rgba(0, 83, 207, .12);
  padding: 16px;
}
.notice__icon { flex: 0 0 auto; color: var(--current-color); display: grid; place-items: center; }
.notice__body { flex: 1 1 auto; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.notice__title { font-size: 14px; font-weight: 600; color: var(--jxc-text-primary); }
.notice__actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
.notice p { margin: 0; line-height: 1.7; }

/* 商品档案「修改历史」的字段级 before → after */
.change-list { display: flex; flex-direction: column; gap: 8px; }
.change-row {
  display: grid; grid-template-columns: 120px 1fr 16px 1fr;
  align-items: center; gap: 10px; font-size: 13px;
  padding: 7px 10px;
  background: var(--jxc-fill-light);
  border-radius: var(--jxc-radius-sm);
}
.change-row__field { color: var(--jxc-text-secondary); font-size: 12px; }
.change-row__arrow { color: var(--jxc-text-placeholder); text-align: center; }

/* 行内操作区。product/consignment/aftersale 用 .row-actions，
   purchase/sell 用 .row-ops，两者同款，保留两个名字以免改动各页标记。 */
.row-actions { display: flex; gap: 8px; justify-content: flex-end; align-items: center; flex-wrap: wrap; }
.dashboard-hero__main { min-width: 0; }

/* ---------- 弹窗 ---------- */
.modal-mask {
  position: fixed; inset: 0;
  background: rgba(0, 0, 0, .5);
  z-index: 2000;
  display: flex; align-items: flex-start; justify-content: center;
  padding: 6vh 16px 16px;              /* 线上把弹窗上边距压到 6vh */
  overflow-y: auto;
}
.modal {
  background: var(--jxc-surface);
  border-radius: var(--jxc-radius-sm);
  box-shadow: 0 12px 32px rgba(0, 0, 0, .18);
  max-width: 100%;
  display: flex; flex-direction: column;
  animation: modal-in .2s var(--jxc-ease);
}
@keyframes modal-in { from { transform: translateY(-12px); opacity: 0; } to { transform: none; opacity: 1; } }
.modal__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 16px 16px 10px; }
.modal__title { font-size: 18px; font-weight: 600; }
.modal__close { border: 0; background: transparent; cursor: pointer; color: var(--jxc-text-secondary); display: grid; place-items: center; }
.modal__close:hover { color: var(--current-color); }
.modal__body { padding: 0 16px 8px; }
.modal__foot { padding: 12px 16px 16px; border-top: 1px solid var(--jxc-border-lighter); margin-top: 8px; }
.modal__actions { display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap; }
.confirm__text { font-size: 14px; color: var(--jxc-text-regular); line-height: 1.7; }

/* ---------- 轻提示 ---------- */
.toast-host { position: fixed; top: 16px; left: 50%; transform: translateX(-50%); z-index: 3000; display: flex; flex-direction: column; gap: 8px; align-items: center; }
.toast {
  display: flex; align-items: center; gap: 8px;
  padding: 11px 15px;
  border-radius: var(--jxc-radius-sm);
  font-size: 13px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .14);
  animation: toast-in .22s var(--jxc-ease);
}
@keyframes toast-in { from { transform: translateY(-10px); opacity: 0; } to { transform: none; opacity: 1; } }
.toast.is-out { opacity: 0; transition: opacity .24s; }
.toast--success { background: #f0f9eb; color: #529b2e; border: 1px solid #e1f3d8; }
.toast--error { background: #fef0f0; color: #c45656; border: 1px solid #fde2e2; }
.toast--info { background: var(--jxc-fill); color: var(--jxc-text-regular); border: 1px solid var(--jxc-border-lighter); }

/* ---------- 抽屉 ---------- */
.drawer-mask { position: fixed; inset: 0; background: rgba(0, 0, 0, .35); z-index: 2100; display: flex; justify-content: flex-end; }
.drawer { width: 320px; max-width: 84vw; height: 100%; background: var(--jxc-surface); display: flex; flex-direction: column; animation: drawer-in .26s var(--jxc-ease); }
@keyframes drawer-in { from { transform: translateX(100%); } to { transform: none; } }
.drawer__head { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; border-bottom: 1px solid var(--jxc-border-lighter); }
.drawer__head h3 { font-size: 15px; font-weight: 600; }
.drawer__close { border: 0; background: transparent; cursor: pointer; color: var(--jxc-text-secondary); }
.drawer__body { flex: 1 1 auto; overflow-y: auto; padding: 14px 16px; }
.drawer__foot { display: flex; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--jxc-border-lighter); }
.drawer__foot .btn { flex: 1 1 0; }
.setting-block { margin-bottom: 20px; }
.setting-block__title { font-size: 13px; font-weight: 600; margin-bottom: 9px; }
.setting-block__note { font-size: 12px; color: var(--jxc-text-secondary); margin-top: 6px; }
.swatches { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; }
.swatch { width: 100%; aspect-ratio: 1; border-radius: var(--jxc-radius-sm); border: 2px solid transparent; cursor: pointer; }
.swatch.is-active { border-color: var(--jxc-text-primary); box-shadow: 0 0 0 2px var(--jxc-surface) inset; }

/* ---------- 出售工作台 ---------- */
.sell-search { padding: 10px 12px; border-bottom: 1px solid var(--jxc-border-lighter); }
.sell-search .input { width: 100%; min-width: 0; }
.sell-list { max-height: 560px; overflow-y: auto; padding: 8px; display: flex; flex-direction: column; gap: 6px; }
.sell-card {
  text-align: left; border: 1px solid var(--jxc-border-lighter);
  background: var(--jxc-surface); border-radius: var(--jxc-radius-sm);
  padding: 9px 11px; cursor: pointer; display: flex; flex-direction: column; gap: 3px;
  transition: all .18s;
}
.sell-card:hover { border-color: var(--current-color); background: var(--el-color-primary-light-9, #f5f9ff); }
.sell-card.is-active { border-color: var(--current-color); background: var(--current-color-dark-bg); }
.sell-card__top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.sell-card__qty { font-size: 12px; font-weight: 600; color: var(--current-color); }
.sell-card__name { font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sell-card__meta { display: flex; justify-content: space-between; gap: 8px; font-size: 11px; color: var(--jxc-text-secondary); }

.split-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; flex-wrap: wrap; margin-bottom: 4px; }
.split-head__title { font-size: 16px; font-weight: 600; margin-bottom: 3px; }

.layers { display: flex; flex-direction: column; gap: 8px; }
.layer { display: grid; grid-template-columns: 66px 1fr auto; align-items: center; gap: 10px; padding: 9px 11px; background: var(--jxc-fill-light); border-radius: var(--jxc-radius-sm); }
.layer__idx { font-size: 11px; color: var(--current-color); background: var(--el-color-primary-light-9, #ecf3ff); padding: 2px 7px; border-radius: 9px; text-align: center; }
.layer__main { display: flex; flex-direction: column; min-width: 0; }
.layer__main strong { font-size: 13px; }
.layer__cost { font-size: 13px; font-variant-numeric: tabular-nums; }

/* ---------- 采购明细编辑器 ---------- */
.items-editor { display: flex; flex-direction: column; gap: 6px; }
.items-editor__head, .items-editor__row {
  display: grid;
  grid-template-columns: minmax(180px, 2fr) 90px 70px 90px 90px 70px 40px;
  gap: 8px; align-items: center;
}
.items-editor__head { font-size: 12px; color: var(--jxc-text-secondary); padding: 0 2px; }
.items-editor__sub { font-size: 13px; text-align: right; font-variant-numeric: tabular-nums; }
.items-editor .input { min-width: 0; width: 100%; }
.cost-summary-wrap { display: flex; justify-content: flex-end; }
.cost-summary { min-width: 260px; display: flex; flex-direction: column; gap: 7px; background: var(--jxc-fill-light); padding: 12px 14px; border-radius: var(--jxc-radius-sm); }
.cost-summary__row { display: flex; align-items: center; justify-content: space-between; gap: 16px; font-size: 13px; }
.cost-summary__row span { color: var(--jxc-text-secondary); }
.cost-summary__row.is-total { border-top: 1px solid var(--jxc-border-lighter); padding-top: 8px; font-size: 15px; }

.import-preview { min-height: 60px; }
.mono-block { display: none; }

/* ---------- 商品图片（全系统统一） ----------
   productImage() 要么渲染 <img>，要么渲染占位插画的 <span.pimg>。
   两者必须同尺寸同圆角，否则列表会出现高度跳动。 */
.pimg {
  display: inline-block;
  border-radius: 6px;
  object-fit: cover;
  background: var(--jxc-fill-light);
  vertical-align: middle;
}
.pimg--ph { display: inline-grid; place-items: center; overflow: hidden; }
.pimg--ph > svg { display: block; border-radius: 6px; }
.cell-img { display: inline-flex; align-items: center; }
.cell-img .pimg { display: block; }
/* 缩略图列收窄用：单元格不留多余内边距 —— 否则 28px 的图仍会被
   10px 上下内边距撑出一行高度（图本身的小尺寸由 productImage 的 size 参数给，
   它会渲染 width/height 属性；这里只管单元格）。
   ⚠ 用「单元格自身带 cell-img--xs」而不是 `:has(> .cell-img--xs)`：
   :has() 在老浏览器里会整条规则失效、且失效时**不报错**，很难发现。 */
.cell-img--xs { line-height: 0; }
.table__td.cell-img--xs { padding-top: 2px; padding-bottom: 2px; }

/* 表格里的「图 + 名称」组合单元格 */
.goods-cell { display: flex; align-items: center; gap: 9px; min-width: 0; }
.goods-cell__main { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.goods-cell__name { font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------- 图片上传控件 ---------- */
.imgfield { display: flex; flex-direction: column; gap: 10px; }
.imgfield__file { display: none; }
.imgfield__row { display: flex; gap: 14px; align-items: flex-start; flex-wrap: wrap; }
.imgfield__preview {
  position: relative;
  flex: 0 0 auto;
  border: 1px dashed var(--jxc-border);
  border-radius: 8px;
  overflow: hidden;
  display: grid;
  place-items: center;
  background: var(--jxc-fill-light);
}
.imgfield__preview.is-drag,
.imgfield.is-drag .imgfield__preview { border-color: var(--current-color); background: var(--el-color-primary-light-9, #f2f7ff); }
.imgfield__preview--ro { border-style: solid; }
.imgfield__img { width: 100%; height: 100%; object-fit: cover; display: block; }
.imgfield__preview .pimg,
.imgfield__preview .pimg--ph,
.imgfield__preview svg { width: 100%; height: 100%; }
.imgfield__ph {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  color: var(--jxc-text-placeholder); font-size: 12px;
}
.imgfield__busy {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  background: rgba(255, 255, 255, .78);
  font-size: 12px; color: var(--jxc-text-regular);
}
.imgfield__meta { flex: 1 1 240px; min-width: 0; display: flex; flex-direction: column; gap: 7px; }
.imgfield__title { font-size: 13px; font-weight: 600; }
.imgfield__hint { margin: 0; font-size: 12px; color: var(--jxc-text-secondary); line-height: 1.7; }
.imgfield__tip { margin: 0; font-size: 12px; color: var(--jxc-text-placeholder); }
.imgfield__actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* ---------- 错误面板 ---------- */
.error-panel { background: #fef0f0; border: 1px solid #fde2e2; border-radius: var(--jxc-radius-sm); padding: 20px; color: #c45656; }
.error-panel h2 { font-size: 16px; margin-bottom: 8px; }
.error-panel__stack { margin-top: 10px; font-size: 11px; white-space: pre-wrap; max-height: 240px; overflow: auto; color: #a33; }

/* ---------- 启动占位 ---------- */
.boot-splash { position: fixed; inset: 0; display: grid; place-items: center; background: var(--jxc-bg); color: var(--jxc-text-secondary); gap: 10px; grid-auto-flow: row; }
.boot-splash__box { display: flex; flex-direction: column; align-items: center; gap: 10px; }
.spinner { width: 26px; height: 26px; border: 2px solid var(--jxc-border); border-top-color: var(--current-color); border-radius: 50%; animation: spin .8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
/* 脚本被禁用时的纯 CSS 兜底提示（不会无限转圈） */
.boot-splash__fallback { display: none; font-size: 13px; text-align: center; max-width: 460px; line-height: 1.7; }
.app-ready .boot-splash { display: none; }
body:not(.js-on) .boot-splash .spinner { display: none; }
body:not(.js-on) .boot-splash__loading { display: none; }
body:not(.js-on) .boot-splash__fallback { display: block; animation: fade-in 0s 4s both; }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

/* ============================================================================
   移动端（断点 768px，与线上一致：JS 给 body 打 .is-mobile）
   ========================================================================= */
@media (max-width: 768px) {
  .sidebar {
    position: fixed; top: 0; bottom: 0; left: 0;
    transform: translateX(-100%);
    transition: transform var(--jxc-duration) var(--jxc-ease);
    width: 210px; flex-basis: 210px;
    box-shadow: 2px 0 12px rgba(0, 0, 0, .18);
  }
  body.is-drawer-open .sidebar { transform: none; }
  body.is-drawer-open .sidebar-mask { display: block; position: fixed; inset: 0; background: rgba(0, 0, 0, .4); z-index: 25; }
  body.is-sidebar-collapsed .sidebar { width: 210px; flex-basis: 210px; }
  body.is-sidebar-collapsed .menu-item__text,
  body.is-sidebar-collapsed .menu-item__tag,
  body.is-sidebar-collapsed .sidebar__logo-text { display: initial; }
  body.is-sidebar-collapsed .menu-item { padding: 0 20px; justify-content: flex-start; }
  body.is-sidebar-collapsed .menu-group__title { padding-left: 20px; text-align: left; font-size: 12px; }
  body.is-sidebar-collapsed .menu-group__title::after { content: none; }

  .tags-view { display: none; }
  .period-bar { top: var(--jxc-navbar-height); }
  .navbar { padding: 0 10px; }
  .navbar__user-meta, .navbar__user-select { display: none; }
  .breadcrumb__item:not(:last-child) { display: none; }
  .app-container { padding: 12px; }
  .page-header__title { font-size: 17px; }
  .stat-grid.cols-4 { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .stat-grid.cols-3 { grid-template-columns: 1fr; }
  .grid-2-1, .grid-3 { grid-template-columns: 1fr; }
  .split-2 { grid-template-columns: 1fr; }
  .split-2__side .sell-list { max-height: 300px; }
  .form-grid { grid-template-columns: 1fr; }
  .filter-bar { flex-direction: column; align-items: stretch; }
  .filter-bar__field .input { width: 100%; min-width: 0; }
  .filter-bar__actions { margin-left: 0; }
  .panel__body { padding: 12px; }
  .desc--2, .desc--3 { grid-template-columns: 1fr; }

  /* 表格重排为卡片行：表头隐藏，每行一张卡，单元格变成「标签 / 值」两栏 */
  .table-wrap { overflow-x: visible; }
  .table thead { display: none; }
  .table, .table tbody, .table tr, .table td { display: block; width: 100%; }
  .table tbody tr {
    background: var(--jxc-surface);
    border: 1px solid var(--jxc-border-lighter);
    border-radius: var(--jxc-radius-sm);
    margin-bottom: 10px;
    padding: 6px 10px;
  }
  .table tbody tr:hover { background: var(--jxc-surface); }
  .table__td {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 6px 0; border-bottom: 1px dashed var(--jxc-border-lighter);
    text-align: left !important;
  }
  .table__td:last-child { border-bottom: 0; }
  .table__td::before {
    content: attr(data-label);
    flex: 0 0 auto;
    font-size: 12px; color: var(--jxc-text-secondary);
  }
  /* 图片列在卡片化后单独占一行并居中：挤在「图片 / 缩略图」两栏里很难看 */
  .table__td[data-label='图片'] { flex-direction: column; align-items: flex-start; gap: 6px; }
  .table__td[data-label='图片'] .cell-img .pimg { width: 64px; height: 64px; }
  .table__check { width: auto; }
  .table__empty::before { content: none; }
  .table__empty { display: block; }
  .pager { flex-direction: column; gap: 8px; }
  .barlist__row { grid-template-columns: 96px 1fr 80px; }
  .barlist__extra { grid-column: 2 / -1; }
  .items-editor__head { display: none; }
  .items-editor__row { grid-template-columns: 1fr 1fr; gap: 6px; border: 1px solid var(--jxc-border-lighter); border-radius: var(--jxc-radius-sm); padding: 8px; }
  .items-editor__row > *:first-child { grid-column: 1 / -1; }
  .cost-summary { width: 100%; min-width: 0; }
  .modal-mask { padding: 3vh 8px 8px; }
  .modal { width: 100% !important; }
  .drawer { width: 84vw; }
  .dashboard-hero { padding: 16px; }
  .dashboard-hero__stats { gap: 18px; }
  .dashboard-hero__stat strong { font-size: 16px; }
}

/* ---------- 同步状态徽标（仅服务器模式） ----------
   加后端之后「我的改动到底存哪了」变成用户必须知道的事：
   本地写是**同步**完成的（界面立刻更新），服务端写是**异步**的 ——
   不显示状态的话，用户会以为已经存好了，其实请求还在队列里或已经失败。

   四种色调：绿=已同步 / 蓝=进行中 / 橙=离线待发 / 红=冲突或失败。
   冲突那一种必须显眼，因为它是**唯一需要用户动手**的状态（点一下刷新）。 */
.navbar__sync:empty { display: none; }
.sync-badge {
  display: inline-flex; align-items: center; gap: 6px;
  height: 26px; padding: 0 10px; margin-right: 6px;
  border: 1px solid var(--jxc-border-light);
  border-radius: 13px;
  background: var(--jxc-surface);
  color: var(--jxc-text-secondary);
  font-size: 12px; cursor: pointer;
  transition: background .16s, border-color .16s;
}
.sync-badge:hover { background: var(--jxc-fill); }
.sync-badge__dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: currentColor; flex: none;
}
.sync-badge--ok { color: #2f9e6f; border-color: rgba(47, 158, 111, .32); }
.sync-badge--busy { color: var(--current-color); border-color: var(--jxc-border-light); }
.sync-badge--warn { color: #b7791f; border-color: rgba(183, 121, 31, .36); }
.sync-badge--bad { color: #c93030; border-color: rgba(201, 48, 48, .38); }
/* 冲突时的圆点做一个呼吸提示：这是唯一需要用户动手的状态 */
.sync-badge--bad .sync-badge__dot { animation: sync-pulse 1.6s ease-in-out infinite; }
@keyframes sync-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) {
  .sync-badge--bad .sync-badge__dot { animation: none; }
}
/* 手机上导航栏本来就挤：只留圆点，文字藏起来（title 里仍有完整说明） */
@media (max-width: 768px) {
  .sync-badge { padding: 0 8px; }
  .sync-badge span { display: none; }
}
