/* ==========================================================================
   一喵效期管家 · 真站样式表（唯一一份）
   本文件由 out/tmp/build-app-css.js 生成，别直接改：
     ① 上半部分 = out/桥build/v4-full.css（v4 设计系统，设计稿的真相源）
     ② 机制层   = out/tmp/v4-mechanics.css（真站机制：屏切换/兼容隐藏/逐屏对齐）
     ③ 组件片段 = out/tmp/v4-mech-*.css（设计稿没画到的零件：弹窗/导入向导/撤销条…）
   旧 css/styles.css（1445 条规则、845 个 !important）不进这份文件。
   生成时间：2026-09-28T07:42:40.644Z
   ========================================================================== */

/* 一喵效期管家 · v4 全站设计系统（v4.css 基线 + v4-extra.css 组件补充，自动合成，勿手改本文件） */

/* ============================================================
   米白小铺 · 库存首页 v4
   本轮只动两处：
   1) .batch-badge 撞车 .empty（既有空状态类）→ 改组合类 .batch-badge.is-empty
   2) .item-info 去掉重复的 28×28 图标块
   设计方向不动：米白底 / #B8627E / 2 色相 / 5 档字号 / 3 档圆角 / 左侧 3px 竖条
   ============================================================ */

/* 2026-10-08 · 定版配色「P7 奶霜玫瑰」（店主从 9 版候选里选定）。
   取值与关系规则见 out/tmp/palettes.js（p7）；改配色只改这一段 + 下面 §v94 里的换皮槽位。
   关系规则：一个强调色（干玫瑰）+ 四个只在状态位出现的语义色；正文/地面非纯黑非纯白；
   全部组合过 WCAG（正文 ≥4.5、次要文字 ≥4.5、按钮字 ≥4.5、状态色 ≥4.5）。 */
:root{
  --bg:              #F8F1EA;
  --bg-soft:         #F2E9E0;
  --card:            #FFFDFB;
  --card-2:          #FCF8F3;
  /* 2026-10-10 店主：这种不要纯黑的字 → 原来 #2B2422 暖但太深，看着像纯黑；
     换暖棕 #3C332F：对比度仍有约 11.7:1（远高于 4.5:1 的无障碍线），但不再是硬黑 */
  --text:            #3C332F;
  --text-2:          #6F6360;
  --border:          #EBE0D6;
  --border-strong:   #DCCEC2;

  --primary:         #A85F73;
  --primary-2:       #8B4A5D;
  --primary-soft:    #F6E4E8;
  --primary-line:    #E7C9D1;

  --st-expired:      #93394A;
  --st-expired-bg:   #F5DCE0;
  --st-urgent:       #A85F73;
  --st-soon:         #8A6118;
  --st-soon-bg:      #F5E9CF;
  --st-ok:           #3E6B54;
  --st-ok-bg:        #DEEADF;
  --st-unknown:      #6F6360;
  --st-unknown-bg:   #EFE8E1;

  --fs-hero: 26px;
  --fs-h1:   19px;
  --fs-h2:   16px;
  --fs-body: 15px;
  --fs-cap:  14px;
  --fs-badge: 12px;

  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;

  --r-sm:   10px;
  --r-md:   16px;
  --r-pill: 999px;

  --shadow-1: 0 1px 3px rgba(74,54,46,.06);
  --shadow-2: 0 6px 18px rgba(74,54,46,.08);

  --tabbar-h: 64px;
}

*{ box-sizing: border-box; }

html,body{
  margin:0; padding:0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Noto Sans CJK SC", "Noto Sans SC", "Microsoft YaHei", system-ui, sans-serif;
  font-size: var(--fs-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

.app{
  max-width: 640px;
  margin: 0 auto;
  padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
  min-height: 100vh;
}

/* —— 顶栏 —— */
.app-header{
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  min-height: 64px;
  padding: 0 var(--sp-4);
  background: rgba(250,247,242,.92);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border);
  border-radius: 0 0 var(--r-md) var(--r-md);
}
.app-title{
  font-size: 18px;
  font-weight: 700;
  letter-spacing: .2px;
  color: var(--text);
}

#btn-bell{
  position: relative;
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--card);
  color: var(--text-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: transform 140ms cubic-bezier(.2,.8,.25,1), background 140ms;
}
#btn-bell:active{ transform: scale(.96); background: var(--bg-soft); }
#btn-bell svg{ width: 22px; height: 22px; }

#bell-badge{
  position: absolute;
  top: -2px;
  right: -2px;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  border-radius: var(--r-pill);
  background: var(--primary-2);
  color: #FFFFFF;
  font-size: var(--fs-badge);
  font-weight: 700;
  line-height: 20px;
  text-align: center;
  box-shadow: 0 0 0 2px var(--bg);
}

/* —— 试用条 —— */
#trial-banner{
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: var(--sp-3) var(--sp-4) 0;
  padding: var(--sp-3) var(--sp-4);
  background: var(--primary-soft);
  border: 1px solid var(--primary-line);
  border-radius: var(--r-md);
  min-height: 56px;
  font-size: var(--fs-cap);
  color: var(--primary-2);
}
#trial-banner .trial-banner-text{ flex: 1; line-height: 1.4; }
#trial-banner .trial-banner-btn{
  /* 它是个 <a>，不是 <button>：不写 inline-flex 的话文字贴在 44px 胶囊的顶部（店主截图：字位置不对） */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  min-height: 44px;
  padding: 0 var(--sp-4);
  border: 0;
  border-radius: var(--r-pill);
  background: var(--primary);
  color: #FFFFFF;
  font-size: var(--fs-cap);
  font-weight: 700;
  cursor: pointer;
  transition: transform 140ms cubic-bezier(.2,.8,.25,1), background 140ms;
}
#trial-banner .trial-banner-btn:active{ transform: scale(.96); background: var(--primary-2); }

/* —— 问候区 —— */
.inv-hello{
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: var(--sp-3) var(--sp-4) 0;
  padding: var(--sp-4);
  min-height: 112px;
  background: var(--primary-soft);
  border: 1px solid var(--primary-line);
  border-radius: var(--r-md);
  overflow: hidden;
}
.inv-hello-txt{ flex: 1; min-width: 0; }
.inv-hello-k{
  font-size: var(--fs-cap);
  color: var(--primary-2);
  letter-spacing: .5px;
}
.inv-hello-h{
  margin-top: var(--sp-1);
  font-size: var(--fs-hero);
  font-weight: 700;
  line-height: 1.25;
  color: var(--text);
}
.inv-hello-s{
  margin-top: var(--sp-1);
  font-size: var(--fs-cap);
  color: var(--text-2);
}
.inv-hello-cat{
  flex: 0 0 auto;
  width: 72px;
  height: 72px;
  color: var(--primary);
  opacity: .9;
}
.inv-hello-cat svg{ width: 72px; height: 72px; }

/* —— 搜索框 —— */
.search-box{
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: var(--sp-3) var(--sp-4) 0;
  padding: 0 var(--sp-4);
  min-height: 48px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  transition: border-color 140ms;
}
.search-box:focus-within{ border-color: var(--primary-line); }
.search-box .search-ic{ color: var(--text-2); display: inline-flex; }
.search-box input{
  flex: 1;
  min-width: 0;
  border: 0;
  outline: 0;
  background: transparent;
  font-size: var(--fs-body);
  color: var(--text);
}
.search-box input::placeholder{ color: var(--text-2); }
.search-box .clear-btn{
  min-width: 44px;
  min-height: 44px;
  border: 0;
  background: transparent;
  color: var(--text-2);
  font-size: var(--fs-h2);
  cursor: pointer;
}
.search-box .clear-btn.hidden{ display: none; }

/* —— 筛选条 —— */
#inventory-chips{
  margin: var(--sp-3) var(--sp-4) 0;
  min-height: 48px;
  display: flex;
  align-items: center;
}
#inventory-chips .chips{
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  overflow-x: auto;
  padding-bottom: var(--sp-1);
  scrollbar-width: none;
}
#inventory-chips .chips::-webkit-scrollbar{ display: none; }
#inventory-chips .chip{
  flex: 0 0 auto;
  min-height: 44px;
  padding: 0 var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--card);
  color: var(--text);
  font-size: var(--fs-cap);
  font-weight: 700;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  transition: transform 140ms cubic-bezier(.2,.8,.25,1), background 140ms, color 140ms, border-color 140ms;
}
#inventory-chips .chip b{
  font-weight: 700;
  color: var(--text-2);
  font-size: var(--fs-cap);
}
#inventory-chips .chip.active{
  background: var(--primary);
  border-color: var(--primary);
  color: #FFFFFF;
}
#inventory-chips .chip.active b{ color: rgba(255,255,255,.8); }
#inventory-chips .chip:active{ transform: scale(.96); }
#inventory-chips .chip-act{
  background: transparent;
  border-style: dashed;
  color: var(--primary-2);
  border-color: var(--primary-line);
}

/* —— 列表 —— */
#inventory-list{
  margin: var(--sp-3) var(--sp-4) 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

/* ============================================================
   货卡 v4：两列 grid（不变）
   ============================================================ */
.item-card{
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: auto auto auto;
  column-gap: var(--sp-3);
  row-gap: var(--sp-1);
  align-items: start;
  padding: var(--sp-3) var(--sp-4) var(--sp-3) calc(var(--sp-4) + 6px);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-1);
  min-height: 112px;
  transition: transform 140ms cubic-bezier(.2,.8,.25,1);
}
.item-card:active{ transform: scale(.995); }

.item-card::before{
  content: "";
  position: absolute;
  left: 0;
  top: var(--sp-3);
  bottom: var(--sp-3);
  width: 3px;
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  background: var(--st-unknown);
}
.item-card.is-expired::before{ background: var(--st-expired); }
.item-card.is-urgent::before{ background: var(--st-urgent); }
.item-card.is-soon::before{ background: var(--st-soon); }
.item-card.is-ok::before{ background: var(--st-ok); }
.item-card.is-unknown::before{ background: var(--st-unknown); }

/* 左列第 1 行：图标 + 货名 */
.item-head{
  grid-column: 1;
  grid-row: 1;
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  min-width: 0;
}
.item-head .item-icon{
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--r-sm);
  background: var(--primary-soft);
  color: var(--primary-2);
  margin-top: 2px;
}
.item-head .item-icon svg{ width: 20px; height: 20px; }

.item-name{
  flex: 1;
  min-width: 0;
  font-size: var(--fs-h1);
  font-weight: 700;
  line-height: 1.3;
  color: var(--text);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
}

/* 左列第 2 行：批次 + 徽章 */
.item-sub{
  grid-column: 1;
  grid-row: 2;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  min-width: 0;
}
.item-sub > *{ flex: 0 0 auto; }

.batch-line{
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
}
/* 关键修复：类名从 `empty` 换成 `is-empty`，不再撞既有的 `.empty`（空状态大占位块）。
   padding 写全 2px 8px，min-height 22px，line-height 1，锁死 22–26px。 */
.batch-badge{
  display: inline-flex;
  align-items: center;
  align-self: center;
  flex: 0 0 auto;
  white-space: nowrap;
  padding: 2px 8px;
  min-height: 22px;
  line-height: 1;
  border-radius: var(--r-sm);
  background: var(--bg-soft);
  color: var(--text-2);
  font-size: var(--fs-cap);
  font-weight: 700;
}
.batch-badge.is-empty{
  background: var(--st-soon-bg);
  color: var(--st-soon);
}
.badge{
  display: inline-flex;
  align-items: center;
  align-self: center;
  flex: 0 0 auto;
  white-space: nowrap;
  padding: 2px 8px;
  min-height: 22px;
  line-height: 1;
  border-radius: var(--r-sm);
  font-size: var(--fs-cap);
  font-weight: 700;
}
.item-card.is-expired .badge{ background: var(--st-expired-bg); color: var(--st-expired); }
.item-card.is-urgent  .badge{ background: var(--primary-soft); color: var(--primary-2); }
.item-card.is-soon    .badge{ background: var(--st-soon-bg);   color: var(--st-soon); }
.item-card.is-ok      .badge{ background: var(--st-ok-bg);     color: var(--st-ok); }
.item-card.is-unknown .badge{ background: var(--st-unknown-bg); color: var(--st-unknown); }

/* 左列第 3 行：库位 / 数量（本轮去掉重复的图标块） */
.item-info{
  grid-column: 1;
  grid-row: 3;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-3);
  min-width: 0;
  font-size: var(--fs-cap);
  color: var(--text-2);
}
.item-info .info-line{
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  flex: 0 0 auto;
  white-space: nowrap;
}
.item-info .info-line span{ color: var(--text-2); }
.item-info .info-line b{ color: var(--text); font-weight: 700; }

/* 右列：天数（跨三行）+ 到期日 */
.item-days-row{
  grid-column: 2;
  /* 2026-10-10 店主：黄箭头是「移动位置」，要**左右两栏对齐到同一条水平线** ✓
     原来这里是 grid-row: 1 / span 3（跨三行 ✗）→ 右边那块被摊在整张卡的高度上，
     所以它永远和货名对不齐 ✗。改成只占第一行、顶端对齐 ✓ → 「天数」和「货名」同一条水平线，
     下面的「到期日」自然和「已过期」那排齐平 ✓。 */
  grid-row: 1;
  align-self: start;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--sp-1);
  text-align: right;
  min-width: 0;
}
.item-days{
  display: inline-flex;
  align-items: center;
  gap: 2px;
  white-space: nowrap;
  padding: 2px 10px;
  min-height: 26px;
  line-height: 1;
  border-radius: var(--r-pill);
  background: var(--primary-soft);
  color: var(--primary-2);
  font-size: var(--fs-h2);
  font-weight: 700;
}
.item-days b{ font-weight: 700; }
.item-days.expired{ background: var(--st-expired-bg); color: var(--st-expired); }
.item-days.soon{ background: var(--st-soon-bg); color: var(--st-soon); }
.item-days.ok{ background: var(--st-ok-bg); color: var(--st-ok); }
.item-months,
.item-exp{
  font-size: var(--fs-badge);
  color: var(--text-2);
  white-space: nowrap;
}

/* —— 加一件 —— */
.inv-add-row{
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  margin: var(--sp-3) 0 0;
  min-height: 58px;
  width: 100%;
  border: 2px dashed var(--primary-line);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--primary-2);
  font-size: var(--fs-h2);
  font-weight: 700;
  cursor: pointer;
  transition: background 140ms, transform 140ms cubic-bezier(.2,.8,.25,1);
}
.inv-add-row:active{ transform: scale(.98); background: var(--primary-soft); }

/* —— 底栏 —— */
.tabbar{
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 30;
  display: flex;
  align-items: stretch;
  height: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
  padding-bottom: env(safe-area-inset-bottom, 0px);
  max-width: 640px;
  margin: 0 auto;
  background: rgba(255,255,255,.94);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-top: 1px solid var(--border);
  border-radius: var(--r-md) var(--r-md) 0 0;
}
.tab-btn{
  flex: 1;
  min-height: var(--tabbar-h);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  border: 0;
  background: transparent;
  color: var(--text-2);
  font-size: var(--fs-badge);
  font-weight: 700;
  cursor: pointer;
  transition: color 140ms;
}
.tab-btn .tab-icon{ display: inline-flex; }
.tab-btn.active{ color: var(--primary-2); }
.tab-btn.active .tab-icon{
  background: var(--primary-soft);
  border-radius: var(--r-pill);
  padding: 4px 12px;
}
.tab-btn[hidden]{ display: none; }
.tab-btn .badge-dot{
  position: absolute;
  margin-left: 18px;
  margin-top: -18px;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  border-radius: var(--r-pill);
  background: var(--primary-2);
  color: #fff;
  font-size: var(--fs-badge);
  line-height: 18px;
  text-align: center;
}

/* —— 空状态（这是既有 `.empty`，本轮没动它）—— */
.empty{
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-6) var(--sp-4);
  color: var(--text-2);
  text-align: center;
}
.empty-cat-ic{ color: var(--primary); }

/* —— reduced-motion —— */
@media (prefers-reduced-motion: reduce){
  *,
  *::before,
  *::after{
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
  }
  .item-card:active,
  #btn-bell:active,
  #trial-banner .trial-banner-btn:active,
  #inventory-chips .chip:active,
  .inv-add-row:active{
    transform: none;
  }
}

/* ============================================================
   一喵效期管家 · v4 组件补充层（接在 v4.css 之后）
   用途：把「库存 / 添加 / 提醒 / 我的 / 登录」五屏要用到的组件，
        统一到 v4 那套令牌上（米白底 · #B8627E 主色 · 5 档字号 · 3 档圆角）。
   纪律：只用 v4.css 里已有的变量；正文不小于 14px；触控不小于 44×44；
        图标一律内联 SVG；不引外链字体、不用位图、不用 emoji。
   ============================================================ */

/* —— 通用 —— */
.hidden{ display: none !important; }
.muted{ color: var(--text-2); }
.icon-btn{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px; height: 44px; min-width: 44px; min-height: 44px;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--card);
  color: var(--text-2);
  cursor: pointer;
  transition: transform 140ms cubic-bezier(.2,.8,.25,1), background 140ms;
}
.icon-btn:active{ transform: scale(.96); background: var(--bg-soft); }
.icon-btn svg{ width: 22px; height: 22px; }

/* —— 卡片 / 区块标题 —— */
.card{
  margin: var(--sp-3) var(--sp-4) 0;
  padding: var(--sp-4);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-1);
}
.card-title{
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-cap);
  font-weight: 700;
  letter-spacing: .5px;
  color: var(--text-2);
}
.card-title:last-child{ margin-bottom: 0; }
.setting-row{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  min-height: 56px;
}
.setting-title{ font-size: var(--fs-body); font-weight: 700; color: var(--text); }
.setting-sub{ font-size: var(--fs-cap); color: var(--text-2); line-height: 1.5; }
.setting-sub b{ color: var(--text); font-weight: 700; }
.actions-row{ display: flex; flex-direction: column; gap: var(--sp-2); }
.safe-actions{ display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.summary-bar{
  margin: var(--sp-3) var(--sp-4) 0;
  padding: var(--sp-2) var(--sp-4);
  min-height: 40px;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  font-size: var(--fs-cap);
  color: var(--text-2);
}
.summary-bar b{ color: var(--text); font-weight: 700; }

/* —— 按钮 —— */
.btn{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 48px;
  padding: 0 var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--card);
  color: var(--text);
  font-size: var(--fs-body);
  font-weight: 700;
  cursor: pointer;
  text-decoration: none;
  transition: transform 140ms cubic-bezier(.2,.8,.25,1), background 140ms, border-color 140ms;
}
.btn:active{ transform: scale(.98); }
.btn-primary{ background: var(--primary); border-color: var(--primary); color: #FFFFFF; }
.btn-primary:active{ background: var(--primary-2); border-color: var(--primary-2); }
.btn-ghost{ background: var(--card); border-color: var(--primary-line); color: var(--primary-2); }
.btn-ghost:active{ background: var(--primary-soft); }
.btn-danger{ background: var(--st-expired-bg); border-color: var(--st-expired-bg); color: var(--st-expired); }
.btn-block{ width: 100%; }
.link-btn{
  min-height: 44px;
  border: 0;
  background: transparent;
  color: var(--primary-2);
  font-size: var(--fs-cap);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

/* —— 列表行（v4「我的」那种一行一世界） —— */
.list-item{
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  min-height: 56px;
  padding: 0 var(--sp-4);
  border: 0;
  border-top: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  text-align: left;
  font-size: var(--fs-body);
  cursor: pointer;
  text-decoration: none;
  transition: background 140ms;
}
.list-item:active{ background: var(--bg-soft); }
.card > .list-item:first-child,
.card-title + .list-item{ border-top: 0; }
.card > .list-item{ margin: 0 calc(0px - var(--sp-4)); width: auto; padding-left: var(--sp-4); padding-right: var(--sp-4); }
.card > .list-item:last-child{ border-radius: 0 0 var(--r-md) var(--r-md); }
.list-icon{
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px; height: 36px;
  border-radius: var(--r-sm);
  background: var(--primary-soft);
  color: var(--primary-2);
}
.list-icon svg{ width: 20px; height: 20px; }
.list-label{ flex: 0 0 auto; font-weight: 700; white-space: nowrap; }
.list-value{
  flex: 1 1 auto;
  min-width: 0;
  text-align: right;
  font-size: var(--fs-cap);
  color: var(--text-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.list-arrow{ flex: 0 0 auto; display: inline-flex; color: var(--border-strong); }
.list-arrow svg{ width: 16px; height: 16px; transition: transform 160ms; }
.list-item[aria-expanded="true"] .list-arrow svg{ transform: rotate(90deg); }

/* —— 折叠体（点开才看细节；不能用 display:none，滚轮要能保留 scrollTop） —— */
.row-panel{
  max-height: 0;
  overflow: hidden;
  visibility: hidden;
  border-top: 1px solid transparent;
  transition: max-height 200ms cubic-bezier(.2,.8,.25,1), visibility 200ms;
}
.row-panel.is-open{
  max-height: 900px;
  visibility: visible;
  border-top-color: var(--border);
  padding: var(--sp-3) 0 var(--sp-1);
}
.row-panel > .card{ margin: var(--sp-2) 0 0; box-shadow: none; border-color: var(--border); }
.row-panel .setting-sub{ margin-bottom: var(--sp-2); }

/* —— 会员卡（v4 样张） —— */
.member-card{
  position: relative;
  margin: var(--sp-3) var(--sp-4) 0;
  padding: var(--sp-4);
  background: linear-gradient(135deg, var(--primary-soft), var(--primary-line));
  border: 1.5px solid var(--primary);
  border-radius: 18px;
  overflow: hidden;
}
.member-card::after{
  content: "会员";
  position: absolute;
  right: -6px;
  bottom: -26px;
  font-size: 84px;
  font-weight: 800;
  color: rgba(255,255,255,.42);
  pointer-events: none;
}
.member-top{ display: flex; align-items: center; gap: var(--sp-2); }
.member-badge{
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 10px;
  min-height: 24px;
  border-radius: var(--r-pill);
  background: var(--primary-2);
  color: #FFFFFF;
  font-size: var(--fs-badge);
  font-weight: 700;
}
.member-badge svg{ width: 14px; height: 14px; }
.member-plan{ font-size: var(--fs-cap); color: var(--primary-2); font-weight: 700; }
.member-name{ margin-top: var(--sp-2); font-size: 17px; font-weight: 700; color: var(--text); }
.member-meta{ margin-top: 2px; font-size: 13px; color: var(--text-2); }
.member-meta b{ color: var(--primary-2); font-weight: 700; }
.member-actions{ display: flex; gap: var(--sp-2); margin-top: var(--sp-3); }
.member-actions .btn{ flex: 1; }

/* —— 危险区 —— */
.danger-zone{ margin: var(--sp-4) var(--sp-4) 0; }
.danger-zone .btn-danger{ width: 100%; min-height: 50px; }
.danger-hint{ margin-top: var(--sp-2); font-size: var(--fs-badge); color: var(--text-2); text-align: center; }

/* —— 开关 —— */
.switch-row{ display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); min-height: 56px; }
.switch{
  position: relative;
  flex: 0 0 auto;
  width: 52px; height: 32px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border-strong);
  background: var(--bg-soft);
  cursor: pointer;
  transition: background 160ms, border-color 160ms;
}
.switch::after{
  content: "";
  position: absolute;
  top: 2px; left: 2px;
  width: 26px; height: 26px;
  border-radius: var(--r-pill);
  background: #FFFFFF;
  box-shadow: var(--shadow-1);
  transition: transform 160ms cubic-bezier(.2,.8,.25,1);
}
.switch[aria-checked="true"]{ background: var(--primary); border-color: var(--primary); }
.switch[aria-checked="true"]::after{ transform: translateX(20px); }

/* —— 提醒：分组卡 —— */
.group-card{
  margin: var(--sp-3) var(--sp-4) 0;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
  box-shadow: var(--shadow-1);
}
.group-head{
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 52px;
  padding: 0 var(--sp-4);
  background: var(--card-2);
  border-bottom: 1px solid var(--border);
}
.group-dot{ width: 10px; height: 10px; border-radius: var(--r-pill); background: var(--st-unknown); flex: 0 0 auto; }
.group-dot.is-expired{ background: var(--st-expired); }
.group-dot.is-urgent{ background: var(--st-urgent); }
.group-dot.is-soon{ background: var(--st-soon); }
.group-dot.is-ok{ background: var(--st-ok); }
.group-dot.is-unknown{ background: var(--st-unknown); }
.group-title{ flex: 1; font-size: var(--fs-h2); font-weight: 700; color: var(--text); }
.group-count{ font-size: var(--fs-cap); color: var(--text-2); }
.group-list{ display: flex; flex-direction: column; }
.reminder-item{
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-top: 1px solid var(--border);
}
.reminder-item:first-child{ border-top: 0; }
.reminder-name{ flex: 1; min-width: 0; font-size: var(--fs-body); font-weight: 700; color: var(--text); }
.reminder-meta{ display: block; margin-top: 2px; font-size: var(--fs-badge); font-weight: 400; color: var(--text-2); }
.group-empty{ padding: var(--sp-4); font-size: var(--fs-cap); color: var(--text-2); text-align: center; }
.section-head{
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  min-height: 52px;
  margin: var(--sp-4) var(--sp-4) 0;
  padding: 0 var(--sp-2);
  border: 0;
  background: transparent;
  text-align: left;
  cursor: pointer;
}
.section-title{ font-size: var(--fs-h2); font-weight: 700; color: var(--text); }
.section-count{ font-size: var(--fs-cap); color: var(--text-2); }
.section-caret{ display: inline-flex; color: var(--text-2); transition: transform 160ms; }
.section-head[aria-expanded="false"] .section-caret{ transform: rotate(-90deg); }

/* —— 添加 / 传表 —— */
.upload-zone{
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  margin: var(--sp-3) var(--sp-4) 0;
  padding: var(--sp-5) var(--sp-4);
  border: 2px dashed var(--primary-line);
  border-radius: var(--r-md);
  background: var(--card);
  text-align: center;
  cursor: pointer;
}
.upload-zone:active{ background: var(--primary-soft); }
.upload-icon{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 72px; height: 72px;
  border-radius: var(--r-pill);
  background: var(--primary-soft);
  color: var(--primary-2);
}
.upload-icon svg{ width: 34px; height: 34px; }
.upload-title{ font-size: var(--fs-h1); font-weight: 700; color: var(--text); }
.upload-sub{ font-size: var(--fs-cap); color: var(--text-2); }
.upload-note{ margin-top: var(--sp-2); font-size: var(--fs-badge); color: var(--text-2); }
.inv-add-row + .upload-now{ margin-top: var(--sp-3); }
.upload-now-row{
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 44px;
  border-top: 1px solid var(--border);
  font-size: var(--fs-cap);
  color: var(--text-2);
}
.upload-now-row:first-child{ border-top: 0; }
.upload-now-row b{ color: var(--text); font-weight: 700; }
.upload-faq-row{
  display: flex;
  gap: var(--sp-3);
  padding: var(--sp-3) 0;
  border-top: 1px solid var(--border);
  font-size: var(--fs-cap);
}
.upload-faq-row:first-of-type{ border-top: 0; }
.upload-faq-row b{ flex: 0 0 auto; color: var(--text); }
.upload-faq-row span{ color: var(--text-2); }

/* —— 录入表单 —— */
.field{ margin-top: var(--sp-3); }
.field > label{ display: block; margin-bottom: var(--sp-1); font-size: var(--fs-cap); font-weight: 700; color: var(--text-2); }
.input{
  width: 100%;
  min-height: 48px;
  padding: 0 var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--text);
  font-size: var(--fs-body);
  font-family: inherit;
}
.input::placeholder{ color: var(--text-2); }
.input:focus{ outline: 0; border-color: var(--primary); }
.seg{ display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.seg .btn{ flex: 1 1 auto; min-width: 96px; }
.seg .btn.on{ background: var(--primary); border-color: var(--primary); color: #FFFFFF; }

/* —— 登录 —— */
.acc-wrap{ margin: var(--sp-4) var(--sp-4) 0; }
.acc-card{
  margin: 0;
  padding: var(--sp-4);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-1);
}
.acc-title{ margin: 0 0 var(--sp-3); font-size: var(--fs-h1); font-weight: 700; color: var(--text); }
.acc-label{ display: block; margin: var(--sp-3) 0 var(--sp-1); font-size: var(--fs-cap); font-weight: 700; color: var(--text-2); }
.acc-input{
  width: 100%;
  min-height: 48px;
  padding: 0 var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card-2);
  color: var(--text);
  font-size: var(--fs-body);
  font-family: inherit;
}
.acc-input::placeholder{ color: var(--text-2); }
.acc-input:focus{ outline: 0; border-color: var(--primary); background: var(--card); }
.acc-or{
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: var(--sp-4) 0 var(--sp-2);
  font-size: var(--fs-badge);
  color: var(--text-2);
}
.acc-or::before, .acc-or::after{ content: ""; flex: 1; height: 1px; background: var(--border); }
.acc-sub{ margin-top: var(--sp-3); font-size: var(--fs-cap); color: var(--text-2); text-align: center; }
.acc-link{ min-height: 44px; border: 0; background: transparent; color: var(--primary-2); font-size: var(--fs-cap); font-weight: 700; text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
.site-icp{ margin: var(--sp-4) var(--sp-4) 0; font-size: var(--fs-badge); color: var(--text-2); text-align: center; }
.site-icp a{ color: var(--text-2); }

/* —— 滚轮选择器（提醒设置） —— */
.wheel-wrap{ display: flex; align-items: center; justify-content: center; gap: var(--sp-3); }
.wheel{
  position: relative;
  width: 128px;
  height: 132px;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card);
}
.wheel-band{
  position: absolute;
  left: 0; right: 0; top: 44px;
  height: 44px;
  z-index: 0;
  background: var(--primary-soft);
  border-top: 1px solid var(--primary-line);
  border-bottom: 1px solid var(--primary-line);
}
.wheel-scroll{
  position: relative;
  z-index: 1;
  height: 100%;
  overflow-y: auto;
  scroll-snap-type: y mandatory;
  scrollbar-width: none;
}
.wheel-scroll::-webkit-scrollbar{ display: none; }
.wheel-item{
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  scroll-snap-align: center;
  font-size: var(--fs-body);
  color: var(--text-2);
}
.wheel-item.on{ color: var(--primary-2); font-weight: 700; font-size: var(--fs-h2); }
.wheel-pad{ height: 44px; }
.wheel-side{ font-size: var(--fs-cap); color: var(--text-2); }

/* —— 底部弹层 / 提示 —— */
.sheet-mask{
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(43,39,36,.42);
}
.sheet{
  width: 100%;
  max-width: 640px;
  padding: var(--sp-4);
  background: var(--card);
  border-radius: var(--r-md) var(--r-md) 0 0;
  box-shadow: var(--shadow-2);
}
.sheet-title{ font-size: var(--fs-h1); font-weight: 700; margin: 0 0 var(--sp-2); }
.toast{
  position: fixed;
  left: 50%;
  bottom: calc(var(--tabbar-h) + 24px);
  transform: translateX(-50%);
  z-index: 50;
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-pill);
  background: rgba(43,39,36,.9);
  color: #FFFFFF;
  font-size: var(--fs-cap);
}

/* —— 演示用：顶栏返回 + 页面小地图入口 —— */
.head-back{ display: inline-flex; align-items: center; gap: var(--sp-1); min-height: 44px; border: 0; background: transparent; color: var(--primary-2); font-size: var(--fs-cap); font-weight: 700; cursor: pointer; text-decoration: none; }
a.tab-btn{ text-decoration: none; }
.demo-map{
  margin: var(--sp-4) var(--sp-4) calc(var(--tabbar-h) + var(--sp-4));
  padding: var(--sp-4);
  background: var(--card);
  border: 1px dashed var(--primary-line);
  border-radius: var(--r-md);
}
.demo-map h2{ margin: 0 0 var(--sp-2); font-size: var(--fs-h2); }
.demo-map a{ display: flex; align-items: center; justify-content: space-between; min-height: 44px; color: var(--primary-2); font-size: var(--fs-cap); font-weight: 700; text-decoration: none; border-top: 1px solid var(--border); }
.demo-map a:first-of-type{ border-top: 0; }

@media (prefers-reduced-motion: reduce){
  .row-panel, .switch, .switch::after, .list-arrow svg, .section-caret{ transition-duration: .01ms !important; }
}


/* ============================================================================
   真站机制层 —— 最终产物 out/桥build/v4-app.css = v4-full.css（设计系统，原样）+ 本层
   ----------------------------------------------------------------------------
   为什么要这一层：v4-full.css 是「静态演示稿」的样式，演示稿没有真实应用的
   屏切换、老结构兼容、分页加载这些东西；真站需要，但**不许**在这里写外观规则。
   规矩：
     · 外观（颜色/字号/间距/圆角/阴影）一律回 v4-full.css 改，不写这儿；
     · 不写 !important（v4-full.css 自身只有 .hidden 那一条）；
     · 只补「显隐 / 切换 / 兼容」，每条都要写清为什么。
   施工记录：out/新前端-施工记录.md
   ============================================================================ */

/* ---- 1. 屏切换：真站每个屏是一个 <section class="view">，靠 .active 显示 ---- */
.view{ display: none; }
.view.active{ display: block; }

/* ---- 2. 老结构兼容：必须留在 DOM 里、只是视觉上不可见 ---- */

/* 旧版「数据统计」四宫格（index.html:150 `<div class="stats-grid hidden-compat" id="inventory-stats">`）。
   v4 首页没有这块（v4 用 .inv-hello 那一行说件数），但 js/ui-picker.js:139 与
   tools/verify-inventory.js:134、tools/verify-ui5.js:46 仍拿 .stats-grid .stat 当筛选控件用
   （JS 点击对隐藏元素照样有效），所以元素留着、只是不显示。
   旧 css/styles.css:1948 就是这么干的（.hidden-compat{display:none !important}），这边不用 !important。 */
.hidden-compat{ display: none; }

/* ---- 3. 库存屏底部 ---- */

/* 「导出表格」：v4 首页没有这颗按钮（v4 把导出放在「提醒 → 导出台账」和「我的 → 导出表格」里）。
   元素保留（#btn-export 仍被套件和托盘复用），只是首页不显示。 */
.inventory-actions{ display: none; }

/* 「＋ 加一件」行里的图标底座：v4 是行内 svg 22×22，真站外面包了一层 .inv-add-ic
   （实测原来 22×26，会把这一行顶得比 v4 高 4px）。 */
.inv-add-ic{ display: flex; align-items: center; justify-content: center; inline-size: 22px; block-size: 22px; }
.inv-add-ic svg{ display: block; inline-size: 22px; block-size: 22px; }

/* 「还有 N 条 · 点击加载更多」：演示稿没有这一行（它把 306 条一次全铺开），
   真站要分页加载（js/app.js 的 renderInventory 只渲染前 N 条）。按 v4 的胶囊语言给一颗低调的按钮。 */
.loadmore{ display: block; inline-size: 100%; min-height: 44px; margin: 12px 0 0;
  border: 1px solid var(--border); border-radius: 999px; background: var(--card);
  color: var(--text-2); font-size: var(--fs-cap); line-height: 22px; text-align: center; }

/* ---- 4. 批量勾选（v4 演示稿没有批量功能，真站有）----
   卡片左上角的圆圈；用 v4 的主色令牌，不做新配色。 */
.item-card .pick{ position: absolute; left: 12px; top: 12px; inline-size: 22px; block-size: 22px;
  border: 2px solid var(--border); border-radius: 50%; background: var(--card); }
.item-card .pick.on{ border-color: var(--primary); background: var(--primary); }
.item-card .pick.on::after{ content: ''; display: block; inline-size: 10px; block-size: 6px; margin: 4px auto 0;
  border-left: 2px solid #fff; border-bottom: 2px solid #fff; transform: rotate(-45deg); }

/* ---- 5. 添加 / 传表屏（index.html 的 #view-upload，2026-09-28 按 v4 形态重排）---- */

/* 顶部「加一件」那一行：v4 演示稿把它包在 .add-pad 里（.inv-add-row 自身是 width:100%，
   直插 .app 会通栏出血）。 */
.add-pad{ padding: 0 var(--sp-4); }

/* 上传块本体是个 <button>（v4 就是这么写的），而按钮的 width:auto 是「按内容收」、不是「撑满」。
   v4 演示稿在页面里自己打了补丁 `.add-upload-zone{width:calc(100% - 16px - 16px)}`（v4-add.html:13），
   真站在这一层按同一算法补上 —— .upload-zone 自带 `margin: 12px 16px 0`，所以减两个 --sp-4。
   漏了这条：整块会缩成 312px（实测），图标/文字跟着偏左。 */
.upload-zone{ width: calc(100% - var(--sp-4) - var(--sp-4)); }

/* v4 添加页没有「还有 N 件没处理完」那句人话（v4 的 .summary-bar 在提醒页）。
   元素留着（js/ui-chrome.js 照常写文案），只是这一屏不显示。 */
#view-upload .summary-bar{ display: none; }

/* 「店里现在」两行：行外壳用 v4 的 .upload-now-row（flex/44px/分隔线），
   行内是三段（图标 · 文字 · 数字），左侧图标底座真站有、v4 没有。 */
.upload-now-ic{ flex: 0 0 auto; display: inline-flex; color: var(--text-2); }
.upload-now-ic svg{ inline-size: 20px; block-size: 20px; }
.upload-now-name{ min-width: 0; overflow-wrap: anywhere; color: var(--text); font-weight: 700; }
.upload-now-name b{ font-variant-numeric: tabular-nums; }

/* 上传卡的三个机制态（照抄旧 css/styles.css:1068-1074 的行为，配色改走 v4 令牌）：
   · loading —— js/app.js 解析表格时给 #upload-card 挂 .loading，灰掉文字、显示 .upload-busy；
   · locked  —— 未注册时挂 .locked；
   · drag-over —— 拖文件进来时挂 .drag-over。
   tools/verify-skeleton.js:97-127 断言的就是 loading + .upload-busy + #upload-busy-text，别改语义。 */
.upload-card.loading{ pointer-events: none; }
.upload-card.loading > .upload-icon,
.upload-card.loading > .upload-title,
.upload-card.loading > .upload-sub,
.upload-card.loading > .upload-note{ opacity: .35; }
.upload-busy{ display: none; }
.upload-card.loading .upload-busy{
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-cap); color: var(--primary-2); }
.upload-lock{ display: none; font-size: var(--fs-cap); color: var(--text-2); }
.upload-lock:not(.hidden){ display: block; }
.upload-card.locked{ border-color: var(--border); background: var(--bg-soft); cursor: not-allowed; }
.upload-card.locked .upload-icon{ background: var(--bg-soft); color: var(--text-2); }
.upload-card.drag-over{ border-color: var(--primary); background: var(--primary-soft); }
.spin{ display: inline-block; inline-size: 16px; block-size: 16px; flex: 0 0 auto;
  border: 2px solid var(--primary-line); border-top-color: var(--primary); border-radius: 50%;
  animation: ymq-spin .8s linear infinite; }
@keyframes ymq-spin{ to{ transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce){ .spin{ animation: none; } }

/* ---- 6.「我的」屏（index.html 的 #view-account，2026-09-28 按 v4 形态重排）----
   目标样张 out/桥build/v4-me.html：会员卡 → 账号 → 功能 → 帮助 → 危险区。
   为什么这一屏要写机制层：会员卡/账号卡/功能行/帮助行/危险区 在演示稿里就有（v4-full.css 已覆盖），
   下面这些是「真站有、演示稿没有」的机器件。选择器带 #account-root / #view-account 前缀是有原因的：
   js/account.js 的 injectStyle() 把老 .acc-* 规则插在 <head> 末尾，同权重时后出现的赢，必须靠权重压过去。 */

/* 人话条：v4 的我的页没有人话条（那一句在提醒页）。 */
#view-account .summary-bar{ display: none; }

/* 容器：v4 让每张卡自己带 margin（.card = 12/16/0），容器再缩一层就会左右各多 32px。 */
#account-root .acc-wrap{ display: block; padding: 0; margin: 0; }

/* 会员卡：压过 .acc-card 的白底 + 1px 描边（js/account.js 给它挂了 acc-card 这个老钩子）。 */
#account-root .member-card{
  margin: var(--sp-3) var(--sp-4) 0;
  padding: var(--sp-4);
  background: linear-gradient(135deg, var(--primary-soft), var(--primary-line));
  border: 1.5px solid var(--primary);
  border-radius: 18px;
  box-shadow: none;
  overflow: hidden;
}
/* 胶囊：真站的胶囊同时挂着 acc-badge-*（老钩子，verify-trial-gate.js:534 靠它找人），
   注入样式会把它改成浅底彩字（--st-cool 在 v4 里没有定义），按设计系统盖回实底白字。 */
#account-root .member-badge.acc-badge{
  display: inline-flex; align-items: center; gap: 4px;
  margin-top: 0; padding: 2px 10px; min-height: 24px;
  border-radius: var(--r-pill);
  background: var(--primary-2); color: #FFFFFF;
  font-size: var(--fs-badge); font-weight: 700;
}
/* 「数据」那一行的值是 .acc-sub —— 设计系统里它是「居中说明」，进了列表行要右对齐；
   v4 的值样式是 14px / --text-2（注入样式把它压成 12px 淡紫灰 --muted）。 */
#account-root .list-item .acc-sub{ margin: 0; text-align: right; font-size: var(--fs-cap); color: var(--text-2); }
/* 账号名那一格是 .list-value.acc-uname：注入样式把它做成 18px 粗体深色，v4 的值是 14px 常规灰。 */
#account-root .list-item .acc-uname{ font-size: var(--fs-cap); font-weight: 400; color: var(--text-2); }
/* 账号卡自带 acc-card 钩子，v4 的 .acc-card{margin:0} 会吃掉卡片间距（实测与样张差 8px）。 */
#account-root .card.acc-card{ margin: var(--sp-3) var(--sp-4) 0; }
/* 会员卡下面那行灰字：v4 样张是 13px、margin-top 2px、颜色 --text-2（.member-meta）；
   真站那行带 acc-exp 钩子，被注入样式压成 12px / margin-top 6px / 淡紫灰（--muted = #ADA8C8，
   在渐变卡上对比度只有 2.0:1，是真实缺陷）。 */
#account-root .member-meta{ margin-top: 2px; font-size: 13px; color: var(--text-2); }
/* 帮助卡那三行也是 .list-item，注入样式把 .acc-doc 压成 min-height:44px；v4 的行高是 56px。 */
#view-account .list-item{ min-height: 56px; }

/* 折叠体（提醒设置 / 数据安全 / 检查数据 / 添加到手机桌面 / 关于）：默认收起，点行头展开。
   为什么不用 display:none —— js/ui-picker.js:78-134 的时间滚轮靠 scrollTop 定位，
   元素一旦 display:none，浏览器会把 scrollTop 丢掉，展开后滚轮会跳回第一项。
   开关逻辑在 js/account.js 的 bindMyRows()（点 [data-my-row] → 兄弟互斥地切 .is-open）。 */
.my-body{ max-height: 0; overflow: hidden; visibility: hidden; }
.my-body.is-open{ max-height: none; overflow: visible; visibility: visible; }
/* 折叠体里的旧卡片：它已经是「卡中卡」，去掉自己的白底描边，只留上面那条虚线分区。 */
#view-settings .my-body .card{ margin: var(--sp-3) 0 0; padding: 0; background: transparent; border: 0; box-shadow: none; }

/* 危险区里的第二条（注销账号）：样张自己的页面级样式（out/桥build/v4-me.html:11-20）。 */
.danger-zone .me-unsub{
  width: 100%; min-height: 48px; margin-top: var(--sp-2);
  background: transparent; border-color: transparent; box-shadow: none;
  color: var(--st-expired);
}
.danger-zone .me-unsub:active{ background: var(--st-expired-bg); }

/* 支付面板 / 兑换码块 / 未连接提示：真站的机器件（演示稿的「立即开通」是死链，没有这一块设计），
   把 js/account.js 注入样式里的深色兜底值换成设计系统令牌。 */
#account-root .acc-hint{ margin-top: var(--sp-2); font-size: var(--fs-cap); color: var(--text-2); line-height: 1.6; }
#account-root .acc-redeem{ margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: 1px dashed var(--border); }
#account-root .acc-redeem-row{ display: flex; gap: var(--sp-2); }
#account-root .acc-redeem-row .acc-input{ flex: 1; }
#account-root .acc-panel{
  margin-top: var(--sp-3); padding: var(--sp-3);
  border-radius: var(--r-md); background: var(--bg-soft); border: 1px solid var(--border);
}
#account-root .acc-panel-title{ margin: 0 0 var(--sp-1); font-size: var(--fs-body); font-weight: 700; color: var(--text); }
#account-root .acc-panel-amt{ margin-bottom: var(--sp-3); font-size: var(--fs-cap); color: var(--text-2); font-variant-numeric: tabular-nums; }
#account-root .acc-plan-grid{ display: flex; flex-direction: column; gap: var(--sp-2); margin-bottom: var(--sp-3); }
#account-root .acc-plan-opt{
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3); border: 1.5px solid var(--border); border-radius: var(--r-md);
  background: var(--card); cursor: pointer; text-align: left; font-family: inherit;
}
#account-root .acc-plan-opt.is-on{ border-color: var(--primary); box-shadow: 0 0 0 2px var(--primary-line); }
#account-root .acc-plan-radio{ position: relative; flex: none; width: 18px; height: 18px; border: 2px solid var(--border); border-radius: 50%; }
#account-root .acc-plan-opt.is-on .acc-plan-radio{ border-color: var(--primary); }
#account-root .acc-plan-opt.is-on .acc-plan-radio:after{
  content: ""; position: absolute; left: 3px; top: 3px; width: 8px; height: 8px;
  border-radius: 50%; background: var(--primary); }
#account-root .acc-plan-main{ flex: 1; min-width: 0; }
#account-root .acc-plan-name{ font-size: var(--fs-body); font-weight: 700; color: var(--text); }
#account-root .acc-plan-days{ margin-top: 2px; font-size: var(--fs-badge); color: var(--text-2); }
#account-root .acc-plan-price{ flex: none; font-size: var(--fs-h2); font-weight: 700; color: var(--primary-2); font-variant-numeric: tabular-nums; }
#account-root .acc-pay-amount{ font-size: var(--fs-body); font-weight: 700; color: var(--text); line-height: 1.5; }
#account-root .acc-pay-big{ margin: 2px 0 var(--sp-1); font-size: var(--fs-hero); font-weight: 800; color: var(--primary-2); font-variant-numeric: tabular-nums; }
#account-root .acc-qr-wrap{ display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-3); }
#account-root .acc-qr-imgs{ display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-3); margin-bottom: var(--sp-1); }
#account-root .acc-qr-item{ display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); width: 132px; }
#account-root .acc-qr-cap{ font-size: var(--fs-badge); color: var(--text-2); }
#account-root .acc-qr-box{
  width: 200px; height: 200px; display: flex; align-items: center; justify-content: center;
  overflow: hidden; border: 1px solid var(--border); border-radius: var(--r-md); background: #FFFFFF;
}
#account-root .acc-qr-box img{ display: block; width: 100%; height: 100%; }
#account-root .acc-qr-ph{
  width: 132px; height: 132px; padding: var(--sp-2); box-sizing: border-box;
  border: 1px dashed var(--border); border-radius: var(--r-md); background: #FFFFFF;
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-badge); color: var(--text-2); text-align: center; line-height: 1.5;
}
#account-root .acc-apply{ margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: 1px dashed var(--border); }
#account-root .acc-notified{
  margin-top: var(--sp-3); padding: var(--sp-3); border-radius: var(--r-md);
  background: var(--primary-soft); border: 1px solid var(--primary-line);
  text-align: center; font-size: var(--fs-body); color: var(--primary-2);
}
#account-root .acc-hint-list{ margin: var(--sp-1) 0 0; padding-left: 18px; font-size: var(--fs-badge); color: var(--text-2); line-height: 1.7; }
#account-root .acc-order-id{ font-size: var(--fs-badge); color: var(--text-2); word-break: break-all; font-variant-numeric: tabular-nums; }
#account-root .acc-spin{
  display: inline-block; width: 12px; height: 12px; margin-right: 6px; vertical-align: -1px;
  border: 2px solid var(--primary-line); border-top-color: var(--primary);
  border-radius: 50%; animation: acc-spin .8s linear infinite; }
#account-root .acc-spin-dark{ border-color: var(--primary-line); border-top-color: var(--primary); }
@media (prefers-reduced-motion: reduce){ #account-root .acc-spin{ animation: none; } }

/* ---- 7. 提醒屏（index.html 的 #view-reminders，2026-09-28 按 v4 形态重排）----
   样张：out/桥build/v4-alerts.html（它的页面级样式在 :9-92，这里做等价落地）。
   这一屏有两条来源：
     · 动态部分（分组卡 / 提醒条 / 天数胶囊）＝ js/app.js 的 renderReminders() 里的 UI_V4 分支；
     · 静态部分（顶栏返回、提醒设置卡、底部动作行）＝ out/tmp/make-app-v4.js 从 index.html 改出来。
   老前端（index.html + css/styles.css）一个字节都没动。 */

/* 顶栏：v4 的提醒屏是「← 库存 + 提醒」，其它屏仍是「一喵效期管家」（样张 .al-head/.al-head-title）。
   注意 v4-full.css:1093 已经给 .head-back 定了 display:inline-flex，所以这里用 id 把它压回隐藏。 */
.app-head-left{ display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
#btn-back, .head-view-title{ display: none; }
body[data-view="reminders"] #btn-back{ display: inline-flex; }
body[data-view="reminders"] .head-view-title{ display: inline-block; }
body[data-view="reminders"] .app-title{ display: none; }
.head-back svg{ width: 18px; height: 18px; }
.head-back .head-back-ic svg{ transform: rotate(180deg); }  /* 图标集的 chevron 朝右，v4 的返回箭头朝左 */
#btn-back.head-back{ padding: 0; }   /* 它是 <button>，浏览器默认左右各 ~6px 内边距，会把 67px 撑成 80px */
.head-view-title{ font-size: var(--fs-h1); font-weight: 700; color: var(--text); white-space: nowrap; }

/* 人话条：左侧猫头底座（样张 .al-sum-face）+ 那句话（.al-sum-txt）。
   真站的 markup 由 js/ui-chrome.js 生成，是 .cat-face/.sum-text —— 样式原来在 css/styles.css 里，
   新前端不加载它，所以在这里补齐。 */
#view-reminders .summary-bar .cat-face{
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: var(--r-sm);
  background: var(--primary-soft); color: var(--primary-2);
}
#view-reminders .summary-bar .cat-face svg{ width: 20px; height: 20px; }
#view-reminders .summary-bar .sum-text{ min-width: 0; font-size: var(--fs-cap); color: var(--text); }

/* 提醒设置卡（样张 .al-switch-card / .al-card-cap / .al-switch-row / .al-link-row） */
#view-reminders .al-switch-card{ padding-bottom: var(--sp-1); }
#view-reminders .al-card-cap{
  margin: 0 0 var(--sp-2); font-size: var(--fs-cap); font-weight: 700;
  letter-spacing: .5px; color: var(--text-2);
}
#view-reminders .al-switch-row{ align-items: flex-start; }
#view-reminders .al-switch-txt{ min-width: 0; }
/* 「手机提醒」这行标题的 line-height 跟着 --fs-body 走会算出 23px，比样张高 2px（样张 21px） */
#view-reminders .al-switch-txt .setting-title{ line-height: 1.4; }
/* 「同一样货每天最多提醒一次」+「试一下」：摆一行、不许折行 —— 折了的话那句里的
   .link-btn（v4-full.css:703 给了 min-height:44px）会另起一行，整张卡凭空高 23px。 */
#view-reminders .al-switch-sub{ display: flex; align-items: center; gap: var(--sp-2); flex-wrap: nowrap; margin-top: 0; }
#view-reminders .al-switch-sub .link-btn{ flex: 0 0 auto; min-height: 44px; white-space: nowrap; }
/* 开关：真站的 markup 是老式的 <label.switch><input><span.slider></label>（样张是
   <div.switch role="switch" aria-checked>）。v4-full.css 的 .switch 画的是 ::after 假滑块、
   状态认 [aria-checked] —— 这里一律关掉，改按 input:checked 画。几何与样张逐像素对齐：
   盒子 52×44，轨道 32 高、上下各留 6px，滑块 26 直径、位移 20px。 */
#view-reminders .al-switch-row .switch{
  position: relative; flex: 0 0 auto; width: 52px; height: 44px; min-height: 44px;
  margin-top: var(--sp-2); border: 0; border-radius: var(--r-pill); background: transparent;
}
#view-reminders .al-switch-row .switch::after{ content: none; }
#view-reminders .al-switch-row .switch input{
  position: absolute; left: 0; top: 0; width: 100%; height: 100%; margin: 0; opacity: 0;
}
#view-reminders .al-switch-row .switch .slider{
  position: absolute; left: 0; right: 0; top: 6px; height: 32px;
  border: 1px solid var(--border-strong); border-radius: var(--r-pill);
  background: var(--bg-soft); transition: background 160ms, border-color 160ms;
}
#view-reminders .al-switch-row .switch .slider::before{
  content: ""; position: absolute; top: 2px; left: 2px; width: 26px; height: 26px;
  border-radius: var(--r-pill); background: #FFFFFF; box-shadow: var(--shadow-1);
  transition: transform 160ms cubic-bezier(.2,.8,.25,1);
}
#view-reminders .al-switch-row .switch input:checked + .slider{ background: var(--primary); border-color: var(--primary); }
#view-reminders .al-switch-row .switch input:checked + .slider::before{ transform: translateX(20px); }
/* 「提前几天提醒」那一行：紧贴开关行下沿、左右各顶到卡的边框（356 = 内容 324 + 16×2）。
   注意它是 button —— v4-full.css:720 的 `.list-item{width:100%}` 只给到内容宽 324，
   负外边距会把右边留出 32px 的空，所以这里显式写宽度；不能再写 width:auto（button 会缩成 fit-content）。 */
#view-reminders .al-link-row{
  margin: 0 calc(0px - var(--sp-4)) 0;
  width: calc(100% + var(--sp-4) + var(--sp-4));
  border-top: 1px solid var(--border); border-radius: 0 0 var(--r-md) var(--r-md);
}

/* 天数胶囊（样张的 .al-days）：颜色走设计系统的 --st-* 令牌 */
.reminder-days{
  flex: 0 0 auto; display: inline-flex; align-items: center; white-space: nowrap;
  padding: 2px 10px; min-height: 26px; line-height: 1;
  border-radius: var(--r-pill); font-size: var(--fs-badge); font-weight: 700;
  background: var(--primary-soft); color: var(--primary-2);
}
.reminder-days b{ font-weight: 700; }
.reminder-days.is-expired{ background: var(--st-expired-bg); color: var(--st-expired); }
.reminder-days.is-soon{ background: var(--st-soon-bg); color: var(--st-soon); }
.reminder-days.is-unknown{ background: var(--st-unknown-bg); color: var(--st-unknown); }

/* 提醒条：长货名不许被胶囊挤爆（样张 .al-item） */
#view-reminders .reminder-item{ align-items: center; }
#view-reminders .reminder-item .reminder-name{ word-break: break-word; }

/* 收尾：脚注（样张 .al-foot）+ 动作行（样张是 16px 内缩、两个整宽按钮上下排） */
#view-reminders .last-check{
  margin: var(--sp-4) var(--sp-4) 0; font-size: var(--fs-badge);
  color: var(--text-2); text-align: center;
}
#view-reminders .actions-row{ margin: var(--sp-4) var(--sp-4) 0; }

/* ============================================================================
   8) 登录屏（#view-auth）—— 样张 out/桥build/v4-login.html
   ----------------------------------------------------------------------------
   真站的登录卡是 js/account.js 里 injectStyle() 那段注入样式画出来的：它是一个
   <style>，在文档里排在 v4 全站表之后，同特异度的类规则一律由它赢。而它写的是老
   深色主题的令牌（--muted / --surface-2 / --radius-ctl / --primary-ink / --amber…），
   v4 里根本没有这几个名字 → 全部掉进 var() 的兜底值（#ADA8C8 灰紫、#262348 深蓝），
   于是卡里出现深色输入框、灰紫小字、金色链接。所以这一节分两层：
     (a) 老令牌桥：只在 body[data-ui="v4"] 下，把老令牌指到 v4 的同义令牌（颜色类
         问题一次解决；老前端没有 data-ui 属性，一点不变）；
     (b) 登录屏的几何：注入样式与 v4 的排布本身就不同（按钮绝对定位、卡 18px 内边距、
         副行/备案在卡里…），只能用 #auth-slot 前缀逐条按回来。
   钩子纪律：#auth-slot / #account-root / #acc-* / .card.acc-* / .site-icp /
   [data-site-icp-*] 一个都没改名 —— 老前端与 46 套套件跑的是 index.html，走不到这里。
   ============================================================================ */

/* —— (a) 老令牌桥（只在 v4 下生效）——
   只用 v4 已有的令牌做同义映射，不新造颜色；--border / --text / --primary / --fs-cap
   这些 v4 本来就有的不写（写了会自我引用）。 */
body[data-ui="v4"]{
  --muted: var(--text-2);
  --text-3: var(--text-2);
  --surface: var(--card);
  --surface-2: var(--card-2);
  --radius: var(--r-md);
  --radius-ctl: var(--r-sm);
  --fs-h3: var(--fs-h1);
  --primary-ink: var(--primary-2);
  --amber: var(--primary-2);
  --amber-ink: var(--primary-2);
  --amber-soft: var(--primary-soft);
  --amber-glow: var(--primary-line);
  --st-safe: var(--st-ok);
  --st-safe-bg: var(--st-ok-bg);
  --st-cool: var(--st-unknown);
  --st-cool-bg: var(--st-unknown-bg);
  --st-neutral: var(--st-unknown);
  --st-neutral-bg: var(--st-unknown-bg);
  --c-expired: var(--st-expired);
}

/* —— (b1) 卡：样张 .acc-wrap > .acc-card —— */
#auth-slot .acc-wrap{ display: block; padding: 0; margin: 0; gap: 0; }
#auth-slot .card.acc-card{
  margin: var(--sp-4) var(--sp-4) 0; padding: var(--sp-4);
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--r-md); box-shadow: var(--shadow-1);
}
#auth-slot .acc-title{ margin: 0 0 var(--sp-3); font-size: var(--fs-h1); font-weight: 700; color: var(--text); }
/* 副行：样张放在卡顶（标题下面，style="margin:0 0 var(--sp-2)"），老前端放在卡底 */
#auth-slot .acc-sub{
  margin: 0 0 var(--sp-2); text-align: left;
  font-size: var(--fs-cap); color: var(--text-2); line-height: 1.5;
}
#auth-slot .acc-sub-warn{ color: var(--st-expired); }
#auth-slot .acc-hint{ font-size: var(--fs-cap); color: var(--text-2); line-height: 1.5; }
/* 字段：间距由 .acc-label 的上外边距给（v4-full.css:990 是 12px 0 4px），
   注入样式的 .acc-field{margin-bottom:12px} 会再加一层，抹掉 */
#auth-slot .acc-field{ margin-bottom: 0; }
#auth-slot .acc-label{
  margin: var(--sp-3) 0 var(--sp-1);
  font-size: var(--fs-cap); font-weight: 700; color: var(--text-2);
}
#auth-slot .acc-input{
  min-height: 48px; padding: 0 var(--sp-4);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--card); color: var(--text); font-size: var(--fs-body);
}
#auth-slot .acc-input:focus{ border-color: var(--primary); }
#auth-slot .acc-input::placeholder{ color: var(--text-2); }
/* 「显示密码」：注入样式把它绝对定位塞进输入框里（12px 小字、金色），
   样张是一颗 48 高的独立按钮（.lg-pwd-toggle：r-sm、b1、min-width 76）→ 改成同一行的 flex */
#auth-slot .acc-pwd-row{ display: flex; align-items: stretch; gap: var(--sp-2); position: static; }
#auth-slot .acc-pwd-row .acc-input{ flex: 1 1 auto; min-width: 0; padding-right: var(--sp-4); }
#auth-slot .acc-pwd-toggle{
  position: static; transform: none; flex: 0 0 auto;
  min-width: 76px; min-height: 48px; padding: 0 var(--sp-3);
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  background: var(--card-2); color: var(--text-2);
  font-size: var(--fs-cap); font-weight: 700;
}
#auth-slot .acc-pwd-toggle[aria-pressed="true"]{
  background: var(--primary-soft); border-color: var(--primary-line); color: var(--primary-2);
}
/* 登录按钮：样张是 style="margin-top:var(--sp-4)"（样式本身由 v4-full.css 的 .btn-primary 给） */
#auth-slot #acc-submit{ margin-top: var(--sp-4); }
/* 「或」：样张在登录按钮之后、注册链接之前；横线是伪元素（v4-full.css:1012 已有，这里只按颜色字号） */
#auth-slot .acc-or{
  gap: var(--sp-3); margin: var(--sp-4) 0 var(--sp-2);
  font-size: var(--fs-badge); color: var(--text-2);
}
#auth-slot .acc-or:before, #auth-slot .acc-or:after{ background: var(--border); }
/* 注册链接行：样张是 flex 居中、margin-top:-4px（.acc-link 自己 44 高） */
#auth-slot .acc-links{
  display: flex; flex-wrap: wrap; justify-content: center;
  margin-top: calc(-1 * var(--sp-1));
}
/* 注入样式把链接压成 13px 金色（--primary #B8627E 在白底只有 4.13:1）→ 按回 v4 的 14px --primary-2 */
#auth-slot .acc-link{
  min-height: 44px; border: 0; background: transparent;
  color: var(--primary-2); font-size: var(--fs-cap); font-weight: 700;
  text-decoration: underline; text-underline-offset: 3px;
  /* 样张里这两个链接是 <a>；真站是 <button>，浏览器默认给按钮 1px 6px 内边距与 line-height:normal
     （实测光这两项就差 12px：152 vs 140）→ 显式抹平 */
  font-family: inherit; padding: 0; line-height: 1.5;
}
#auth-slot .acc-err{ margin-top: var(--sp-2); font-size: var(--fs-cap); color: var(--st-expired); line-height: 1.5; }
/* —— (b2) 第三方登录块（样张 .lg-outer / .lg-btn-off / .lg-off-note）——
   样张把它的上外边距写成 0：卡自己还有 16px 下内边距，合起来正好是一档 --sp-4 */
#auth-slot .lg-outer{ margin: 0 var(--sp-4) 0; }
#auth-slot .lg-outer .btn + .btn{ margin-top: var(--sp-2); }
#auth-slot .acc-ali-btn{ margin: 0; font-weight: 700; background: var(--card); border: 1px solid var(--primary-line); color: var(--primary-2); }
#auth-slot .acc-ali-btn:active{ background: var(--primary-soft); }
#auth-slot .acc-ali-ic{ display: inline-flex; align-items: center; margin-right: var(--sp-2); }
#auth-slot .lg-btn-off, #auth-slot .lg-btn-off:active{
  background: var(--bg-soft); border-color: var(--border); color: var(--text-2);
  transform: none; cursor: not-allowed;
}
#auth-slot .lg-btn-off svg{ opacity: .55; }
#auth-slot .lg-off-note{ margin: var(--sp-2) 0 0; font-size: var(--fs-badge); color: var(--text-2); text-align: center; }
/* —— (b3) 服务器设置：一行 list-item + 折叠面板（样张 :115-131）—— */
#auth-slot .acc-server-toggle{
  padding: 0 var(--sp-4); gap: var(--sp-3);
  color: var(--text); font-size: var(--fs-body);
}
#auth-slot .acc-server-box{
  margin: 0; padding: var(--sp-3) 0 var(--sp-1);
  background: transparent; border: 0;
  border-top: 1px solid var(--border); border-radius: 0;
}
#auth-slot .acc-server-box .acc-label{ margin-top: 0; }
#auth-slot .acc-server-box .btn{ margin-top: var(--sp-3); }
#auth-slot .acc-server-note{ margin: var(--sp-2) 0 0; font-size: var(--fs-badge); color: var(--text-2); }
/* —— (b4) 备案号：样张在卡外（.lg-icp），12px 灰字 + 44 高的点区 —— */
#auth-slot .site-icp{
  margin: var(--sp-5) var(--sp-4) 0; font-size: var(--fs-badge);
  color: var(--text-2); text-align: center; line-height: 1.7;
}
#auth-slot .site-icp a{
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; padding: 0 var(--sp-3); color: var(--text-2);
}
/* 顶栏：登录屏不显示铃铛（未登录时提醒没意义，角标还会漏数据） */
body[data-view="auth"] #btn-bell{ display: none; }


/* ===== 片段：v4-mech-bars.css ===== */
/* ============================================================================
   v4-mech-bars.css —— 「撤销条 + 库存页批量/统计零件 + 批次多选筛选面板 + 同步提示条」的 v4 皮肤
   ----------------------------------------------------------------------------
   这一族在新表里**一条规则都没有**（v4-full.css 与 v4-mechanics.css 里都 grep 不到），
   浏览器里就是裸默认样式。改前的实测（探针 out/tmp/probe-bars.js，截图在 out/v4-audit-bars/）：
     .undo-bar  static 390×27、无底色无圆角无阴影 —— 它被 appendChild 到 body 末尾，
                所以「撤销条」是掉在整页**最底下**、要滚到底才看得到的一条文字；
     .bulk-bar  static 390×25，同样掉在文档流末尾（批量功能在 v4 下等于不可用）；
     .undo-btn / .bulk-btn 是浏览器原生灰按钮（rgb(240,240,240) + 2px outset，字号 13.33px）；
     .inv-bub / .stock-card / .stat-num / .stat-sub 全裸。
   本片段的取值一律只走 v4-full.css:11-58 的令牌，不碰老表的 --muted/--surface/--amber 等。

   【证据对照】（哪几条规则有截图为证）
     有截图（探针 out/v4-audit-bars/，改前 .png 与改后 .after.png 各一份）：
       .bulk-bar/.bulk-cnt/.bulk-btn（批量态-点了批量没选货 / 批量态-选了两件 / 批量态-长按进入）
       .btn-sm/.empty-acts/.empty-title/.empty-sub/.empty-cat/.inventory-summary（空态-搜不到货）
       .stats-grid/.stat-num/.stat-sub/.stat-label/.stat-bar（统计四宫格-人工揭开）
       .undo-bar 全族（撤销条-删完这一件）
       批次多选筛选面板 .batch-filter 全族（⑦ 节，js/app.js:4073-4095）—— 官方体检脚本的 15 个
         状态都走不到它（它只在「批次码查询命中、且该批次里 ≥2 个商品名」时才由 js 渲染出来），
         截图与几何来自我自己的探针 out/tmp/probe-batch2.js（改前/改后各一份）
     只有「按源码结构画 + 手工注入存档 markup 后截图」：
       .inv-bub（app-v4.html:107-114 与 .stock-card 一样是整块 HTML 注释，真实页面永不渲染）、
       .stock-card 及其内部（app-v4.html:142-166 同样在注释里）—— 截图里那三张卡是我把注释里的
       markup 原样注进 DOM 后拍的，不是真站会出现的状态。
     另有两条是官方体检脚本 out/tmp/audit-unstyled.js 走不到、只能靠探针的状态：
       批量选择态、空态（它只走「屏-库存 / 面板-筛选 / 撤销条-删完这一件」三个状态）。
     同步提示条 #sync-host/.sync-banner*（⑧ 节，js/sync.js:95-122）：**加载时页面里根本没有这个元素**
       （js/sync.js:82-92 首次 showBanner 才现造并 appendChild 到 body 末尾），官方脚本也拍不到；
       截图与几何来自探针 out/tmp/probe-suspects.js 真调一次 window.Sync.showBanner（改前
       out/shot-sync-banner.png，改后 out/shot-sync-banner-改后.png）—— 改前它 position:static，
       盒子是 0,10591,390,25，也就是掉在首屏下方一万像素处，用户看不见。

   【为什么是白底浮条，不是深底白字】
     v4 里唯一的深底浮条是 .toast（v4-full.css:1079，rgba(43,39,36,.9)），它是**不需要点的**
     一次性提示。撤销条上有必须点的「撤销」和「×」，深底会把 --text-2 的关闭按钮压到对比度不合格，
     而浅色浮层 + --shadow-2 正是 v4 的 .sheet/.card 语言。故：白底 + --border-strong 边 + --shadow-2。
   【为什么主按钮用 --primary-2 而不是 --primary】
     白字压在 --primary #B8627E 上实测对比度 4.13:1（不达 4.5:1）；--primary-2 #9A4D66 是 5.79:1。
     （v4-full 自己的 .btn-primary 就是 4.13:1 那种搭配，那是设计系统的既有问题，不在我这一片里改。）
   ========================================================================== */

/* ---------------------------------------------------------------- ① 撤销条 */
/* DOM（js/ui-chrome.js:57 undoBar()）：body > .undo-bar > [.undo-cat, .undo-text.undo-txt,
   .undo-btn.undo-act, .undo-x, .undo-timer>i]，五个扁平兄弟；.undo-x 是 span 不是 button。
   .show 与 .on 在同一个 requestAnimationFrame 里一起加（旧表只认 .show，后台只认 .on），
   所以两个类名都要给显隐态。hide() 是 remove('on')+remove('show') 后 240ms 删除节点。 */
.undo-bar{
  position: fixed;
  left: var(--sp-4);            /* 左右各 16px —— 用 left/right + max-width 居中，不写死 390 */
  right: var(--sp-4);
  bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px) + var(--sp-3));  /* 底栏上方 12px */
  max-width: 640px; margin: 0 auto;
  z-index: 45;                 /* > 底栏 30、> 筛选面板遮罩 40；< .toast 50（见文件末「已知取舍」） */
  display: flex; align-items: center; gap: var(--sp-2);
  min-height: 52px;
  padding: var(--sp-1) var(--sp-2) calc(var(--sp-1) + 3px) var(--sp-3);  /* 底部 3px 留给倒计时线 */
  background: var(--card);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-2);
  font-size: var(--fs-cap);    /* 14px 起 */
  overflow: hidden;            /* 让倒计时线贴着圆角走 */
  opacity: 0; transform: translateY(12px); pointer-events: none;
  transition: opacity 200ms ease, transform 200ms ease;
}
.undo-bar.show, .undo-bar.on{ opacity: 1; transform: translateY(0); pointer-events: auto; }

/* 左侧猫爪：js/ui-chrome.js:65 与 catSvg() 输出的 svg 都是 stroke="currentColor"，靠 color 上色 */
.undo-cat{
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  inline-size: 24px; block-size: 24px; color: var(--primary-2);
}
.undo-cat svg{ display: block; inline-size: 20px; block-size: 20px; }

/* 文案：超长一律省略号（不换行、不把浮条撑高） */
.undo-text, .undo-txt{
  flex: 1 1 auto; min-inline-size: 0;
  font-size: var(--fs-cap); line-height: 1.4; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* 「撤销」：≥44 高的胶囊；底 --primary-2（白字 5.79:1，达标），按下换成亮一点的 --primary */
.undo-btn, .undo-act{
  flex: 0 0 auto; min-height: 44px; min-width: 44px; padding: 0 var(--sp-3);
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--primary-2); border-radius: var(--r-pill);
  background: var(--primary-2); color: #FFFFFF;
  font-family: inherit; font-size: var(--fs-cap); font-weight: 700; line-height: 1;
  cursor: pointer; transition: background 140ms, border-color 140ms, transform 140ms;
}
.undo-btn:active, .undo-act:active{ background: var(--primary); border-color: var(--primary); transform: scale(.98); }

/* 「×」：span，命中区必须做到 44×44；颜色用 --text-2（白底 5.3:1） */
.undo-x{
  flex: 0 0 auto; inline-size: 44px; block-size: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-sm); background: transparent;
  color: var(--text-2); font-size: 20px; line-height: 1; cursor: pointer;
  user-select: none;
}
.undo-x:active{ background: var(--bg-soft); }

/* 倒计时线：.undo-timer 是绝对定位的底边条，里面的 i 从满到 0 缩。
   注意：老表那条 ymqUndoCount keyframes 在 css/styles.css:2120，**不在这份新表里**，必须自己定义。
   时长 5s 与 js/ui-chrome.js 的 secs 缺省 5 对齐（客户端不改，这里也不改）。 */
@keyframes ymqBarsUndoCount{ from{ transform: scaleX(1); } to{ transform: scaleX(0); } }
.undo-timer{
  position: absolute; left: 0; right: 0; bottom: 0; block-size: 3px;
  border-radius: 0 0 calc(var(--r-md) - 1px) calc(var(--r-md) - 1px);
  background: var(--primary-soft); overflow: hidden;
}
.undo-timer > i{
  display: block; inline-size: 100%; block-size: 100%;
  background: var(--primary); transform-origin: left center;
  animation: ymqBarsUndoCount 5s linear forwards;
}

/* ------------------------------------------------- ② 批量操作条（库存页） */
/* DOM（js/app.js:832-834 batchRenderBar()）：body > #bulk-bar.bulk-bar >
   [span.bulk-cnt, span.bulk-spacer, button.bulk-btn#bulk-edit, button.bulk-btn.danger#bulk-del,
    button.bulk-btn#bulk-cancel]；进批量时 body 会加 .bulk-on，退出时移除。
   它和撤销条不会同时出现（js/app.js 删之前先 batchExit()），所以两者贴同一个位置、同一套皮。 */
.bulk-bar{
  position: fixed;
  left: var(--sp-4); right: var(--sp-4);
  bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px) + var(--sp-3));
  max-width: 640px; margin: 0 auto;
  z-index: 60;
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2) var(--sp-1) var(--sp-3);
  background: var(--card);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-2);
  font-size: var(--fs-cap);
}
.bulk-cnt{ flex: 0 0 auto; font-size: var(--fs-cap); font-weight: 700; color: var(--text); white-space: nowrap; }
.bulk-spacer{ flex: 1 1 auto; }
.bulk-btn{
  flex: 0 0 auto; min-height: 44px; min-width: 44px; padding: 0 var(--sp-3);
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border-strong); border-radius: var(--r-pill);
  background: var(--card); color: var(--text);
  font-family: inherit; font-size: var(--fs-cap); font-weight: 700; line-height: 1;
  cursor: pointer; transition: background 140ms, border-color 140ms, transform 140ms;
}
.bulk-btn:active{ background: var(--bg-soft); transform: scale(.98); }
/* 「删」照抄 v4 的 .btn-danger 配方（--st-expired-bg 底 + --st-expired 字，5.86:1），
   按下反过来变实心，让人在按下去那一刻看清这是破坏性动作。 */
.bulk-btn.danger{
  background: var(--st-expired-bg); border-color: var(--st-expired-bg); color: var(--st-expired);
}
.bulk-btn.danger:active{ background: var(--st-expired); border-color: var(--st-expired); color: #FFFFFF; }
/* 批量态里「批量 / 退出批量」那颗 chip（v4-full 已有 .chip-act 常态样式，这里只补「按下去」的样子） */
body.bulk-on .chip-act{
  background: var(--primary-2); border-color: var(--primary-2); border-style: solid; color: #FFFFFF;
}
body.bulk-on .chip-act:active{ background: var(--primary); }

/* ------------------------------------------- ③ 空态那两颗小按钮（.btn-sm） */
/* .btn-sm 本身不算「裸样式」（.btn 兜住了它），但它没有任何自己的规则 ——
   量出来和普通 .btn 一模一样（94×48、15px 字），「小按钮」这层语义在新表里丢了。 */
.btn-sm{ min-height: 44px; padding: 0 var(--sp-3); font-size: var(--fs-cap); }
/* .empty-acts 包着那两颗 .btn-sm（js/app.js:1184-1197 / js/ui-chrome.js:133 emptyStateHtml），
   它也没有规则；不给它排一下，两颗按钮会挤在一起。 */
.empty-acts{ display: flex; flex-wrap: wrap; gap: var(--sp-2); justify-content: center; margin-top: var(--sp-4); }
.empty-acts .btn{ flex: 1 1 140px; min-width: 0; }
/* 空态的三行字：跟 .btn-sm 是同一个块（#inventory-empty），裸着的时候一眼能看出不搭 */
.empty-cat{ display: flex; justify-content: center; color: var(--primary-line); }
.empty-cat svg{ display: block; inline-size: 48px; block-size: 48px; }
.empty-title{ margin-top: var(--sp-2); font-size: var(--fs-h2); font-weight: 700; color: var(--text); }
.empty-sub{ margin-top: var(--sp-1); font-size: var(--fs-cap); color: var(--text-2); }

/* 「找到 N 条 / M 件」那行小字（#inventory-summary.inventory-summary，app-v4.html:188） */
.inventory-summary{
  margin: var(--sp-2) var(--sp-4) 0;
  font-size: var(--fs-cap); color: var(--text-2);
}

/* ------------------------------------------------ ④ 统计四宫格（兼容层） */
/* ★ 先说清楚：这一块在真实页面里**看不见** —— app-v4.html:172 的 #inventory-stats 带着
   .hidden-compat，v4-mechanics.css:24 把它 display:none。它留着是因为 js/ui-picker.js:139/145
   与 tools/verify-inventory.js、verify-bars.js 仍拿 .stats-grid .stat 和 #stat-bar-* 当控件/断言用。
   我照旧画它（万一哪天揭开就能直接用）。
   ⚠ 自己踩过的坑，记在这：我这条 .stats-grid{display:grid} 和机制层的
   .hidden-compat{display:none}（v4-mechanics.css:24）**特异性相同（都是 0,1,0）**，
   而 v4-mech-*.css 按文件名排在 v4-mechanics.css **之后** → 整块四宫格被顶出来，
   库存页刚进去就能看到（探针量到 .stats-grid 358×244、4 张 .stat 全部可见）。
   所以下面显式把「被藏起来的形态」钉回去：.stats-grid.hidden-compat 是 (0,2,0)，
   压得过机制层也压得过我自己的 display:grid。**任何往新片段里写 .hidden-compat
   元素样式的片段都会踩这个坑，谁写谁知道。**
   截图只能靠人工揭开后拍（探针里那一态就叫「统计四宫格-人工揭开」）。 */
.stats-grid.hidden-compat{ display: none; }
.stats-grid{
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3);
  margin: var(--sp-3) var(--sp-4) 0;
  /* js/ui-picker.js:145-166 会往 #stat-bar-expired/-soon/-long 写
     inline style.background='var(--c-expired)' 等三个 --c-* 变量 —— 它们只存在于已下线的
     css/styles.css，新表里没有。inline 值在 computed-value 阶段解析失败会整条作废、退成 transparent，
     我在这一层规则里赢不过 inline，所以只能把别名补在容器上，指到 v4 的状态色。
     这是我唯一一处替老零件补令牌，作用域只有这颗四宫格。 */
  --c-expired: var(--st-expired);
  --c-warning: var(--st-soon);
  --c-ok: var(--st-ok);
}
/* .stat 自带 .card（v4-full.css:635）—— .card 的 margin 是「通栏卡」用的，进了网格会把格子撑歪，清掉 */
.stats-grid .stat{
  margin: 0; inline-size: 100%; padding: var(--sp-3) var(--sp-4);
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  border: 1px solid var(--border); border-radius: var(--r-md); background: var(--card);
  box-shadow: var(--shadow-1); color: var(--text);
  font-family: inherit; text-align: left; cursor: pointer;
  transition: border-color 140ms, box-shadow 140ms, transform 140ms;
}
.stats-grid .stat:active{ transform: scale(.98); }
/* 选中态：js/ui-picker.js:137 syncStatActive() 给当前 data-filter 的卡加 .active */
.stats-grid .stat.active{ border-color: var(--primary); box-shadow: var(--shadow-2); }
.stat-num{
  display: flex; align-items: baseline; gap: 2px;
  font-size: var(--fs-hero); font-weight: 700; line-height: 1.15; color: var(--primary-2);
  font-variant-numeric: tabular-nums;
}
.stat-num.stat-danger{ color: var(--st-expired); }   /* 7.5:1 */
.stat-num.stat-warn{ color: var(--st-soon); }        /* 5.9:1 */
.stat-num.stat-ok{ color: var(--st-ok); }            /* 6.1:1 */
.stat-unit{ font-size: var(--fs-cap); font-weight: 700; color: var(--text-2); }
.stat-label{ font-size: var(--fs-cap); font-weight: 700; color: var(--text); }
.stat-sub{ font-size: var(--fs-cap); color: var(--text-2); }
.stat-bar{
  inline-size: 100%; block-size: 6px; margin-top: var(--sp-2);
  border-radius: var(--r-pill); background: var(--bg-soft); overflow: hidden;
}
/* 宽度由 js/ui-picker.js:164 写 inline style.width（≥2%）；底色走上面补的 --c-* 别名 */
.stat-bar > i{ display: block; block-size: 100%; inline-size: 0; border-radius: inherit; background: var(--primary); }

/* ---------------------------------------------------- ⑤ 三圆气泡（存档） */
/* app-v4.html:107-114 整块是 HTML 注释（2026-09-25「页面逻辑重建」批次 1 已删，理由是
   .inv-bubbles / #inventory-stock / .stock-total-line 是同一信息的三份重复）。
   真实页面永不渲染 —— 这里的规则是我按存档 markup 画的，截图是同结构手工注入后拍的。 */
.inv-bubbles{ display: flex; gap: var(--sp-3); margin: var(--sp-3) var(--sp-4) 0; }
.inv-bub{
  flex: 1 1 0; min-inline-size: 0; min-height: 44px;
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-1);
  padding: var(--sp-3) var(--sp-2);
  border: 1px solid var(--border); border-radius: var(--r-md); background: var(--card);
  box-shadow: var(--shadow-1); color: var(--text);
  font-family: inherit; font-size: var(--fs-cap); font-weight: 700; cursor: pointer;
  transition: background 140ms, transform 140ms;
}
.inv-bub:active{ background: var(--bg-soft); transform: scale(.98); }
/* 数字用小圆角标：--primary-2 底白字（5.79:1），不是 --primary（那是 4.13:1） */
.inv-bub b{
  display: inline-flex; align-items: center; justify-content: center;
  min-inline-size: 34px; block-size: 34px; padding: 0 var(--sp-1);
  border-radius: var(--r-pill); background: var(--primary-2); color: #FFFFFF;
  font-size: var(--fs-badge); font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums;
}

/* -------------------------------------------------- ⑥ 三张大卡（存档） */
/* app-v4.html:142-166 同样整块在注释里。状态类名是 data-state="danger|warn|ok"（不是老后台的
   expired/watch/safe）。左边那条 3px 竖线是照 v4 的 .item-card::before 抄的手感。 */
.stock-row{ display: flex; flex-direction: column; gap: var(--sp-3); margin: var(--sp-3) var(--sp-4) 0; }
.stock-card{
  position: relative; margin: 0; inline-size: 100%;
  display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-rows: auto auto auto;
  align-items: center; column-gap: var(--sp-3); row-gap: 2px;
  padding: var(--sp-3) var(--sp-4) var(--sp-3) calc(var(--sp-4) + 6px);
  background: var(--card); border: 1px solid var(--border); border-radius: var(--r-md);
  box-shadow: var(--shadow-1); color: var(--text);
  font-family: inherit; font-size: var(--fs-body); text-align: left; cursor: pointer;
  transition: transform 140ms, box-shadow 140ms;
}
.stock-card:active{ transform: scale(.98); box-shadow: var(--shadow-2); }
.stock-card::before{
  content: ''; position: absolute; left: 0; top: var(--sp-3); bottom: var(--sp-3);
  inline-size: 3px; border-radius: var(--r-pill); background: var(--border-strong);
}
.stock-card[data-state="danger"]::before{ background: var(--st-expired); }
.stock-card[data-state="warn"]::before{ background: var(--st-soon); }
.stock-card[data-state="ok"]::before{ background: var(--st-ok); }
.stock-name{ grid-column: 1; grid-row: 1; font-size: var(--fs-h2); font-weight: 700; color: var(--text); }
.stock-num{
  grid-column: 2; grid-row: 1 / span 2; justify-self: end; align-self: center;
  display: flex; align-items: baseline; gap: 2px;
  font-size: var(--fs-hero); font-weight: 700; line-height: 1.1; color: var(--primary-2);
  font-variant-numeric: tabular-nums;
}
.stock-num .stock-unit{ font-size: var(--fs-cap); font-weight: 700; font-style: normal; color: var(--text-2); }
.stock-card[data-state="danger"] .stock-num{ color: var(--st-expired); }
.stock-card[data-state="warn"] .stock-num{ color: var(--st-soon); }
.stock-card[data-state="ok"] .stock-num{ color: var(--st-ok); }
.stock-pct{ grid-column: 1; grid-row: 2; font-size: var(--fs-cap); color: var(--text-2); }
.stock-sub{ grid-column: 1; grid-row: 3; font-size: var(--fs-cap); color: var(--text-2); }
.stock-go{ grid-column: 2; grid-row: 3; justify-self: end; font-size: var(--fs-cap); font-weight: 700; color: var(--primary-2); }
.stock-go i{ font-style: normal; }

/* ------------------------------------- ⑦ 批次多选筛选面板（js/app.js:4073-4095） */
/* 这一族在新表里同样是 0 条规则（9 个类全 grep 不到），只有外层 .batch-filter.card 撑着。
   markup 由 js/app.js:4073-4095 renderBatchFilter() 生成，挂在 app-v4.html:187 的 #batch-filter；
   只有「批次码查询命中（js/app.js:1112 isBatchQuery）、并且该批次的货里 ≥2 个不同商品名」时才渲染
   （js/app.js:4074 `if (!groups || groups.length < 2) return ''`），所以官方体检脚本的 15 个状态
   一个都碰不到它 —— 它的改前/改后只能靠探针取证。
   改前实测（out/tmp/probe-batchfilter.js，截图 out/shot-batchfilter-real.png）：
     面板 358×82 白卡（是 .card 的功劳）；头部/说明字/按钮组/列表/每一项/商品名/条数**全是
     display:inline**，于是说明字和两个按钮挤在一行、两项货也挤在同一行「德国罐头12 条日本抹茶8 条」；
     两个按钮是浏览器原生灰按钮（13.33px / rgb(240,240,240) / 2px outset / radius 0）；
     勾选框 13×13（原生）；.selected **完全看不出来**（老表 css/styles.css:1282 那条用的是
     --amber-soft，这个令牌在新表里不存在 → 底色声明在计算值阶段失效）。 */
/* .card 自带 margin: var(--sp-3) var(--sp-4) 0（v4-full.css:636），下面紧跟 #inventory-summary，
   所以只补一个下边距；写成 .batch-filter.card 是为了稳稳压过 .card 的 margin 简写。 */
.batch-filter.card{ margin-bottom: var(--sp-3); }

/* 头部：说明字占一行，两个按钮换到第二行并靠右（窄屏 390 下 16 字说明 + 两颗 44 高按钮
   挤不进 324px 的一行，所以让它们分两行 —— 这也是店主在手机上真正看到的样子）。 */
.batch-filter-head{
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
}
.batch-filter-title{
  flex: 1 1 100%;
  min-width: 0;
  font-size: var(--fs-cap);
  font-weight: 600;
  line-height: 1.45;
  color: var(--text-2);
}
.batch-filter-actions{
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-inline-start: auto;
}
/* 两个文字按钮照 .btn-ghost（v4-full.css:699-700）的配方：白底 + --primary-line 边 + --primary-2 字。
   命中区硬指标 ≥44×44，所以「全选 / 清空」都得有 44 高（老表是 12px 字 + 4px 内边距，约 25 高）。 */
.batch-filter-btn{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0 var(--sp-3);
  border: 1px solid var(--primary-line);
  border-radius: var(--r-pill);
  background: var(--card);
  color: var(--primary-2);
  font-family: inherit;
  font-size: var(--fs-cap);
  font-weight: 600;
  cursor: pointer;
  transition: background-color .15s, border-color .15s;
}
.batch-filter-btn:active{
  border-color: var(--primary-2);
  background: var(--primary-soft);
}
.batch-filter-btn:focus-visible{
  outline: 2px solid var(--primary-2);
  outline-offset: 2px;
}

/* 列表：一个商品一行（勾选框 + 名字占满 + 条数靠右）。
   行底色用 --card-2 而不是白 —— 面板本身已经是白卡（.card），
   行再白就分不出「这是列表里的行」（v4 的嵌套面就是这么用的）。 */
.batch-filter-list{
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.batch-filter-item{
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 44px;
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card-2);
  font-size: var(--fs-cap);
  line-height: 1.45;
  color: var(--text);
  cursor: pointer;
  transition: background-color .15s, border-color .15s;
}
/* 选中态：--primary-soft 底 + --primary-line 边（v4 里 .chip.active / .card.active 用的就是这套），
   商品名加粗、条数转 --primary-2，让「选中」不靠颜色也看得出来（色盲友好）。 */
.batch-filter-item.selected{
  border-color: var(--primary-line);
  background: var(--primary-soft);
}
.batch-filter-item.selected .batch-filter-name{ font-weight: 700; }
.batch-filter-item.selected .batch-filter-count{ color: var(--primary-2); }
.batch-filter-item:focus-within{
  border-color: var(--primary-2);
  box-shadow: 0 0 0 3px var(--primary-soft);
}
/* 勾选框保留原生 input（键盘可操作、读屏能识别），只放大到 22px 并用 v4 主色填充
   —— 与 v4-mech-import.css:587-593 那个勾选框用同一个配方，免得同一个 App 里两种勾选框。
   注意：真正可点的区域是外层 label（≥44 高，点哪一格都切换），22px 只是它画出来的大小。 */
.batch-filter-item input[type="checkbox"]{
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  min-width: 22px;
  min-height: 22px;
  margin: 0;
  accent-color: var(--primary);
  cursor: pointer;
}
.batch-filter-name{
  flex: 1 1 auto;
  min-width: 0;
  word-break: break-all;
}
.batch-filter-count{
  flex: 0 0 auto;
  font-size: var(--fs-cap);
  font-weight: 400;
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}

/* 屏幕宽一点（≥480px，PWA 在桌面上）时说明字和两个按钮回到同一行：
   480 - 32(card 左右外边距) - 32(card 内边距) = 416px，放得下「说明字 225 + 8 + 按钮组 140」。 */
@media (min-width: 480px){
  .batch-filter-title{ flex: 1 1 auto; min-width: 0; }
}

/* ---------------------------------------------------------- ⑧ 同步提示条（js/sync.js:95-122 showBanner） */
/* 这一族「加载时并不存在」：js/sync.js:82-92 的 host() 现造 #sync-host 再 appendChild 到 body 末尾，
   首次 showBanner 才有。所以官方 out/tmp/audit-unstyled.js 的 15 个状态一个都拍不到它 ——
   它不在任何「屏」里。证据来自只读探针 out/tmp/probe-suspects.js（它真调了一次
   window.Sync.showBanner），改前实测（见 out/tmp/probe-suspects-out.json 的 ②_host / ②_banner / ②_btn）：
     宿主 div#sync-host 盒子 0,10591,390,25、position:static → 这条「提示」被挂在文档流最末尾，
     掉在首屏下方 10591px 处，用户根本看不见；横幅本体无底色/无圆角/无投影/无内边距；
     按钮是浏览器原生按钮（13.3333px、2px outset、底 rgb(240,240,240)、半径 0、43×25）。
   画法：宿主固定在视口顶部**但让开页头**，横幅落在 header 下面居中（老表 css/styles.css:1139-1162
   也是顶部固定，但那是深色底、且直接压住页头；按父的要求照 v4 令牌改写成白卡，并按父拍板把
   宿主整体下移一个页头高）。z-index 46 的依据：v4 的层级阶梯是 header 20 / tabbar 30 /
   sheet-mask 40 / toast 50，取 46 = 在弹窗遮罩之上、吐司之下：同步失败的提示不该盖住吐司，
   但也不能被弹窗遮罩吃掉（46 高于 header 的 20，是为了保证横幅本身永远可见）。
   只有 .sync-banner-warn 一种变体：js/sync.js:101 是
   el.className = 'sync-banner' + (o.kind === 'warn' ? ' sync-banner-warn' : '')
   —— 其它 kind（info 等）都走白卡底，所以这里不需要再写 is-ok / is-info。 */
#sync-host,
.sync-host{
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 46;
  display: flex;
  justify-content: center;
  /* 顶部 64px 让开页头：64 = .app-header 实测高（h64、position:sticky、z-index:20、padding:0 16px，
     未登录/已登录两态一致、四套 v4 稿每屏都有这个 header），见只读探针 out/tmp/probe-headerh.js。
     —— 这不是猜的，是一个实测值；页头以后若改高，这里要跟着改（旧表/新表都没有「页头高」令牌，
     v4-full.css 里只有 --tabbar-h: 64px 是底栏的）。--sp-2 = 8px，与上面「页头 + 8px」同义。 */
  padding: calc(64px + env(safe-area-inset-top, 0px) + var(--sp-2)) var(--sp-3) 0;
  /* 宿主自己不吃点击：否则这条横跨整屏的 fixed 层会把顶部的返回键/标题全挡死 */
  pointer-events: none;
}

.sync-banner{
  width: 100%;
  max-width: 560px;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 10px var(--sp-3);
  background: var(--card);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-2);
  /* 横幅本体要把点击收回来（里面的动作按钮得能点） */
  pointer-events: auto;
}

/* 警示态用 v4「快到期」那一组令牌（底 + 边都来自令牌，不用深色底） */
.sync-banner-warn{
  background: var(--st-soon-bg);
  border-color: var(--st-soon);
}

.sync-banner-text{
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--fs-cap);
  line-height: 1.45;
  color: var(--text);
}

.sync-banner-btn{
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;            /* 可达性硬线：命中区 ≥44×44（老表是 32 高） */
  padding: 0 14px;
  font-family: inherit;        /* 不继承的话按钮会掉回系统 UI 字体 */
  font-size: var(--fs-cap);
  font-weight: 700;
  background: #FFFFFF;
  border: 1px solid var(--primary-line);
  border-radius: var(--r-pill);
  color: var(--primary-2);     /* 白底上 5.79:1 */
  cursor: pointer;
  transition: transform 120ms, background-color 150ms, border-color 150ms;
}
.sync-banner-btn:active{
  transform: scale(0.97);
  background: var(--primary-soft);
  border-color: var(--primary-2);
}
.sync-banner-btn:focus-visible{
  outline: 2px solid var(--primary-2);
  outline-offset: 2px;
}

/* ------------------------------------------------------------------ 无障碍 */
/* v4-full.css:591 那条全局 reduced-motion 只压 transition/animation 的**时长**，
   这里把动画整个撤掉（老表 css/styles.css:2123 也是这么干的）。 */
@media (prefers-reduced-motion: reduce){
  .undo-timer > i{ animation: none; }
  .undo-bar{ transition: none; }
  .stats-grid .stat, .inv-bub, .stock-card, .bulk-btn, .undo-btn, .undo-act,
  .batch-filter-btn, .batch-filter-item, .sync-banner-btn{ transition: none; }
}

/* ================================ 已知取舍 / 要拍板的 ==================================
   1. 撤销条与 .toast 抢同一个位置：撤销条 bottom = 底栏 + 12px、高约 57px；.toast（v4-full.css:1079）
      在 底栏 + 24px、高约 34px，两者会重叠。我把撤销条 z-index 设成 45、低于 toast 的 50，
      真撞上时 toast 盖在上面（旧表里 toast 也是压着撤销条的）。删除流程本身不发 toast
      （js/safety.js:152 flashUndo 只调 chrome.undoBar），所以只在别的动作恰好同时提示时才会看到。
      如果想彻底不撞，可把撤销条抬到 calc(var(--tabbar-h) + 56px) —— 这是要店主拍板的一条。
   2. .undo-timer > i 的 5s 与 js/ui-chrome.js 里 secs 的缺省 5 写死对齐（老表同样是写死 5s）。
      哪天客户端改了秒数，进度条会不同步 —— 这需要 js 侧给 CSS 一个变量才治得了，我不改 js。
   3. .inv-bub 与 .stock-card 两组规则目前**没有任何页面在渲染**（元素在 HTML 注释里）。
      它们不影响真站，只是把存档零件补齐，避免哪天有人把注释块放回去时又裸一次。
      为了留证据，我用探针把注释里的 markup 原样注进 DOM 拍了一组截图。
   4. 撤销条横向空间比老表紧：× 要做到 44×44、撤销按钮 44 高，都比老表（28×28 / 34 高）大，
      文案可用宽度从约 214px 降到约 190px，长货名会多省略一两个字。这是无障碍换来的。
   5. 批次多选筛选面板（⑦ 节）在 390px 窄屏上必然占两行：说明字一行、[全选][清空] 一行。
      16 字说明 + 两颗 44 高的按钮在 324px 内容宽里挤不进一行；宽度 ≥480px 时自动回到一行。
      另外面板只在「批次码查询命中且该批次 ≥2 个商品名」时出现（js/app.js:4074），
      所以我在 test01 号上看不到它 —— 取证靠探针（真实路径 + 注入两条都试了）。
   6. 同步提示条（⑧ 节）已是「底部让开页头的顶部浮条」。改前它是 position:static 被挂在 body 末尾，
      盒子 0,10591,390,25 —— 谁也没见过它长什么样。**父已拍板**：不遮 header，宿主顶部留 64px
      （2026 年这次收尾按 out/tmp/probe-headerh.js 实测的 .app-header 高 64px 落的），所以现在
      .sync-banner 的 y ≈ 72（64 页头 + 8 间距）、header 的 bottom = 64、两者不重叠（见报告 §7.2）。
      附带保留两件事：宿主 pointer-events:none（只让横幅本体接收点击，否则这条横跨整屏的 fixed
      层会把返回键/标题挡死）；z-index 46（高于 header 的 20，保证横幅自身永远可见，低于 toast 的 50）。
      **残留风险**：64 不是令牌（v4-full.css 只有 --tabbar-h: 64px 是底栏的），哪天页头改高，
      这一行要跟着改，否则又会叠上去。（.sync-banner-warn 是 js 里唯一存在的变体，
      另外的 kind 都走白卡底。）
   ================================================================================== */


/* ===== 片段：v4-mech-import.css ===== */
/* ==========================================================================
   v4 机制层 · 导入向导族（.wz-* 全族 + 导入确认零件）
   只负责：out/桥build/v4-full.css 里一条规则都没有的「导入向导」零件。
   令牌全部取自 v4-full.css:11-58；不使用 !important；不使用图片/emoji/外链字体。
   作用域说明：
     · .wz-* 只出现在「导入表格」四步向导（js/app.js:2406-2541）里，可直接写裸类；
     · .imp-* 家族与旧的「导入对话」弹窗（js/app.js:2746 起 .imp-card）共用类名，
       凡是共用的（.imp-sr / .import-mode-list / .import-opt / .map-rows / #schema-picker …）
       都按 v4 统一画一份，两边都受益；只属于旧弹窗的（.imp-chat、.imp-fold、.dup-…、.preview-…）
       一行都不写，归另一位智能体。注意：注释里不要出现「星号紧跟斜杠」，会提前闭合注释。
   ========================================================================== */

/* —— 弹窗外壳：四步向导要「头固定 + 中间可滚 + 底固定」，本体是 .modal-card.imp-wiz-card —— */
.modal-card.imp-wiz-card{
  display: flex;
  flex-direction: column;
  max-height: min(92vh, 720px);
  overflow: hidden;
  padding: 0;
}
/* 旧「导入对话」弹窗（.imp-card）：内边距由弹窗壳的 .modal-body 提供
   （out/tmp/v4-mech-modal.css:116-124，padding: var(--sp-4)），这里显式归零，免双重内边距。 */
.modal-card.imp-card{ padding: 0; }

/* —— 头部：× 关闭 / 标题 / 第 N 步 —— */
.wz-head{
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex: 0 0 auto;
  padding: var(--sp-2) var(--sp-4) var(--sp-2) var(--sp-2);
}
button.wz-x{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--card);
  color: var(--text-2);
  font: inherit;
  font-size: var(--fs-h1);
  line-height: 1;
  cursor: pointer;
}
button.wz-x:active{ background: var(--bg-soft); transform: scale(.96); }
.wz-title{
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--fs-h1);
  font-weight: 700;
  color: var(--text);
}
.wz-step{
  flex: 0 0 auto;
  font-size: var(--fs-badge);
  line-height: 1.4;
  color: var(--text-2);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.wz-prog{
  flex: 0 0 auto;
  height: 3px;
  background: var(--bg-soft);
}
.wz-prog > i{
  display: block;
  height: 100%;
  width: 0;
  background: var(--primary);
  border-radius: 0 var(--r-pill) var(--r-pill) 0;
  transition: width 180ms ease;
}

/* —— 主体（可滚动）—— */
.wz-body{
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: var(--sp-4);
}
.wz-file{
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  flex-wrap: wrap;
  margin-bottom: var(--sp-4);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card-2);
}
.wz-fname{
  min-width: 0;
  font-size: var(--fs-cap);
  font-weight: 700;
  color: var(--text);
  word-break: break-all;
}
.wz-fmeta{
  margin-left: auto;
  font-size: var(--fs-badge);
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}
.wz-scr{ display: block; }
.wz-scr[hidden]{ display: none; }
.wz-h1{
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-h1);
  font-weight: 700;
  line-height: 1.4;
  color: var(--text);
}
.wz-sub{
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-cap);
  line-height: 1.6;
  color: var(--text-2);
}
.wz-label{
  margin: var(--sp-3) 0 var(--sp-2);
  font-size: var(--fs-badge);
  font-weight: 700;
  letter-spacing: .5px;
  color: var(--text-2);
}

/* —— 第 1 屏：计算方式可点卡片 —— */
.wz-opts{
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
}
.wz-opt{
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  min-height: 56px;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--card);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 140ms ease, border-color 140ms ease;
}
.wz-opt:active{ transform: scale(.99); }
.wz-opt.on{
  border-color: var(--primary-line);
  background: var(--primary-soft);
}
.wz-opt-txt{
  flex: 1 1 auto;
  min-width: 0;
}
.wz-opt-t{
  display: block;
  font-size: var(--fs-h2);
  font-weight: 700;
  color: var(--text);
}
.wz-opt.on .wz-opt-t{ color: var(--primary-2); }
.wz-opt-s{
  display: block;
  margin-top: 2px;
  font-size: var(--fs-cap);
  line-height: 1.5;
  color: var(--text-2);
}
.wz-rd{
  position: relative;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border: 2px solid var(--border-strong);
  border-radius: var(--r-pill);
  background: var(--card);
}
.wz-opt.on .wz-rd{ border-color: var(--primary); }
.wz-opt.on .wz-rd::after{
  content: "";
  position: absolute;
  inset: 3px;
  border-radius: var(--r-pill);
  background: var(--primary);
}

/* —— 第 2 屏：列的对应关系 —— */
.wz-maplist{
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.wz-maprow{
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 56px;
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card);
}
.wz-maprow.on{
  border-color: var(--primary-line);
  background: var(--primary-soft);
}
.wz-cn{
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--fs-cap);
  line-height: 1.5;
  color: var(--text);
  word-break: break-all;
}
.wz-mapsel{
  flex: 0 1 46%;
  min-width: 120px;
  max-width: 46%;
  min-height: 44px;
}
.wz-miss{
  margin-top: var(--sp-2);
  font-size: var(--fs-cap);
  line-height: 1.6;
  color: var(--text-2);
}
.wz-link{
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-top: var(--sp-2);
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--primary-2);
  font: inherit;
  font-size: var(--fs-cap);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.wz-link[hidden]{ display: none; }

/* —— 第 3 屏：保质期兜底（− 数字 + 步进器）—— */
.wz-stepper{
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.wz-pm{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 48px;
  height: 48px;
  min-width: 48px;
  min-height: 48px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  background: var(--card);
  color: var(--primary-2);
  font: inherit;
  font-size: var(--fs-h1);
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}
.wz-pm:active{ background: var(--primary-soft); transform: scale(.96); }
.wz-shelf-slot{
  flex: 1 1 auto;
  min-width: 0;
}
.imp-say-sub{
  margin: 0 0 var(--sp-1);
  font-size: var(--fs-badge);
  line-height: 1.5;
  color: var(--text-2);
}
.imp-shelf-row{
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  font-size: var(--fs-body);
  color: var(--text);
}
/* #fallback-shelf-value 自带 inline style="width:76px"（inline 优先），
   所以这里只给 min-width / 高度，不跟 inline 抢宽度。 */
#fallback-shelf-value{
  min-width: 76px;
  min-height: 48px;
  padding: 0 var(--sp-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-h1);
  font-weight: 700;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
#fallback-shelf-unit{
  min-height: 44px;
  padding: 0 var(--sp-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-body);
}
#fallback-shelf-count{
  flex: 1 1 100%;
  margin-top: var(--sp-1);
  font-size: var(--fs-badge);
  line-height: 1.5;
  color: var(--text-2);
}
.wz-codes{
  margin: 0 0 var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card-2);
  font-size: var(--fs-cap);
  line-height: 1.9;
  color: var(--text-2);
}
.wz-codes:empty{ display: none; }
.wz-code{
  display: inline-block;
  margin: 0 var(--sp-1);
  padding: 1px var(--sp-2);
  border: 1px solid var(--primary-line);
  border-radius: var(--r-sm);
  background: var(--primary-soft);
  color: var(--primary-2);
  font-size: var(--fs-badge);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* —— 第 4 屏：摘要 / 折叠 / 模式选择 —— */
.wz-sum,
.wz-card{
  margin: 0 0 var(--sp-3);
  padding: 0 var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--card-2);
}
.wz-sumrow{
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  flex-wrap: wrap;
  padding: var(--sp-2) 0;
  border-top: 1px solid var(--border);
}
.wz-sumrow:first-child{ border-top: 0; }
.wz-k{
  flex: 0 0 auto;
  min-width: 72px;
  font-size: var(--fs-cap);
  color: var(--text-2);
}
.wz-v{
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--fs-cap);
  font-weight: 700;
  color: var(--text);
  word-break: break-all;
}
/* .wz-x 一名两用：button.wz-x 是关闭按钮（上面），span.wz-x 是摘要行的补充说明 */
span.wz-x{
  flex: 1 1 100%;
  font-size: var(--fs-badge);
  line-height: 1.5;
  color: var(--text-2);
}
.wz-inline{
  margin-top: var(--sp-3);
  padding: var(--sp-3);
  border: 1px solid var(--primary-line);
  border-radius: var(--r-md);
  background: var(--primary-soft);
  color: var(--primary-2);
  font-size: var(--fs-cap);
  line-height: 1.6;
}
.wz-inline[hidden]{ display: none; }
.wz-btns2{
  display: flex;
  gap: var(--sp-2);
  flex-wrap: wrap;
  margin-top: var(--sp-3);
}
.wz-btns2 .btn{
  flex: 1 1 auto;
  min-width: 132px;
}
.wz-fold{
  margin-top: var(--sp-3);
  border-top: 1px solid var(--border);
}
.wz-fold-head{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  width: 100%;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--primary-2);
  font: inherit;
  font-size: var(--fs-cap);
  font-weight: 700;
  text-align: left;
  cursor: pointer;
}
.wz-chev{
  display: inline-block;   /* 行内元素吃不到 width，rotate 也不生效，必须显式块化 */
  flex: 0 0 auto;
  width: 18px;
  color: var(--primary-2);
  font-size: var(--fs-h2);
  line-height: 1;
  text-align: center;
  transition: transform 160ms ease;
}
.wz-fold-head.open .wz-chev{ transform: rotate(90deg); }
.wz-fold-body{ padding: var(--sp-2) 0 var(--sp-1); }
.wz-fold-body[hidden]{ display: none; }

/* —— 导入模式选择（.selected / .danger 两态）—— */
.import-mode-list{
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-3);
}
.import-opt{
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  min-height: 56px;
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--card);
  cursor: pointer;
  transition: background 140ms ease, border-color 140ms ease;
}
.import-opt.selected{
  border-color: var(--primary);
  background: var(--primary-soft);
}
.import-opt.danger.selected{
  border-color: var(--st-expired);
  background: var(--st-expired-bg);
}
.import-radio{
  position: relative;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  margin-top: 1px;
  border: 2px solid var(--border-strong);
  border-radius: var(--r-pill);
  background: var(--card);
}
.import-opt.selected .import-radio{ border-color: var(--primary); }
.import-opt.selected .import-radio::after{
  content: "";
  position: absolute;
  inset: 3px;
  border-radius: var(--r-pill);
  background: var(--primary);
}
.import-opt.danger.selected .import-radio{ border-color: var(--st-expired); }
.import-opt.danger.selected .import-radio::after{ background: var(--st-expired); }
.import-text{
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--fs-body);
  color: var(--text);
}
.import-text b{
  display: block;
  font-size: var(--fs-body);
  font-weight: 700;
  color: var(--text);
}
.import-text span{
  display: block;
  margin-top: 2px;
  font-size: var(--fs-cap);
  line-height: 1.5;
  color: var(--text-2);
}
.import-opt.danger .import-text b{ color: var(--st-expired); }
.import-opt.danger .import-text span{ color: var(--text-2); }

/* —— 屏幕阅读器容器 .imp-sr：
      v4 里保持可见 —— 它一处包 #required-warn（缺列警告 + #required-override 认可框，
      都是给人看/点的），一处包 #field-rows（js/app.js:3172 的 wzGoAndPoint 会
      scrollIntoView + focus 到里面的下拉），旧表的 sr-only 会让这些内容永远看不见。 —— */
.imp-sr{ display: block; margin-top: var(--sp-3); }
.imp-q{
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.imp-q select{ flex: 1 1 auto; min-width: 0; }

/* —— 原生控件统一 v4 外观（沿用 .input 的写法：48/44 高、--r-sm、白底、聚焦主色边）—— */
.wz-body select,
.wz-body input[type="number"],
.wz-body input[type="text"],
#schema-picker,
#unknown-picker{
  min-height: 44px;
  padding: 0 var(--sp-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-cap);
}
.wz-body select:focus,
.wz-body input[type="number"]:focus,
#schema-picker:focus,
#unknown-picker:focus{
  outline: 0;
  border-color: var(--primary);
}
#schema-picker{ width: 100%; max-width: 100%; }

/* —— 列的对应关系真控件（#field-rows，js/app.js:1892-1901）—— */
.map-rows{ margin-bottom: var(--sp-3); }
.map-title{
  margin: 0 0 var(--sp-1);
  font-size: var(--fs-badge);
  font-weight: 700;
  letter-spacing: .5px;
  color: var(--text-2);
}
.map-row{
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  padding: var(--sp-2) 0;
  border-top: 1px solid var(--border);
  font-size: var(--fs-cap);
  color: var(--text);
}
.map-row:first-child{ border-top: 0; }
.map-col{
  flex: 1 1 40%;
  min-width: 0;
  color: var(--text);
  word-break: break-all;
}
.map-select{ flex: 1 1 auto; min-width: 0; max-width: 100%; }
.map-rows .field-ok,
#required-warn input[type="checkbox"]{
  width: 22px;
  height: 22px;
  min-width: 22px;
  min-height: 22px;
  margin: 0;
  accent-color: var(--primary);
}
.map-rows label{ display: inline-flex; align-items: center; gap: var(--sp-1); min-height: 44px; }
#required-warn{
  font-size: var(--fs-cap);
  line-height: 1.7;
  color: var(--text-2);
}
#required-warn:empty{ display: none; }
#required-warn .muted{ color: var(--text-2); }

/* —— 这四行「列名对好了 / 都对一次确认 / 已确认 N / 小字提示」两套弹窗都用
      （向导的修复屏 #imp-scr-fix 与旧「导入对话」弹窗 .imp-card 的折叠体里），
      所以写成共用规则，两边都受益 —— */
.imp-map-line{
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  flex-wrap: wrap;
  margin-bottom: var(--sp-2);
  font-size: var(--fs-cap);
  color: var(--text-2);
}
.imp-map-sum{ font-weight: 700; color: var(--text); }
.imp-link{
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--primary-2);
  font: inherit;
  font-size: var(--fs-cap);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.imp-link[disabled]{ color: var(--text-2); text-decoration: none; cursor: default; }
.imp-count{ font-size: var(--fs-badge); color: var(--text-2); }
.imp-note{
  margin-top: var(--sp-2);
  font-size: var(--fs-cap);
  line-height: 1.6;
  color: var(--text-2);
}
.imp-note:empty{ display: none; }
.wz-scr#imp-scr-fix .map-title{ margin-top: var(--sp-3); }

/* —— 底部按钮排 —— */
.wz-foot{
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  flex: 0 0 auto;
  padding: var(--sp-3) var(--sp-4);
  border-top: 1px solid var(--border);
  background: var(--card);
}
.wz-foot .btn{ min-height: 44px; }
.wz-foot .btn:disabled{
  background: var(--bg-soft);
  border-color: var(--border);
  color: var(--text-2);
}
.wz-foot .wz-spacer{ flex: 1 1 auto; }
/* 提示自己占一行（order:-1 放在按钮上方），免得在窄屏把按钮挤变形 */
.wz-foot-hint{
  order: -1;
  flex: 1 1 100%;
  margin: 0;
  font-size: var(--fs-badge);
  line-height: 1.4;
  color: var(--st-expired);
}
.wz-foot-hint:empty{ display: none; }
.wz-foot [hidden]{ display: none; }
#mapping-cancel,
#imp-next,
#mapping-confirm{ min-width: 108px; }

/* ============================================================
   补漏：默认收在折叠里、上一轮体检扫不到的那批零件（这次只动这一个文件）
   来源同一份 markup：向导确认屏的两个折叠（有重复的怎么办 / 看一眼前 5 行，
   js/app.js:2500-2528）与旧「导入对话」弹窗（.imp-card，js/app.js:2746 起）。
   ============================================================ */

/* —— 折叠块 .imp-fold（旧弹窗那套；.wz-fold 已在上面画过）—— */
.imp-fold{
  margin-top: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card);
  overflow: hidden;
}
.imp-fold-head{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  width: 100%;
  min-height: 44px;
  padding: 0 var(--sp-3);
  border: 0;
  background: transparent;
  color: var(--primary-2);
  font: inherit;
  font-size: var(--fs-cap);
  font-weight: 700;
  text-align: left;
  cursor: pointer;
}
.imp-chev{
  display: inline-block;   /* 同上：不然 width 与 rotate 都不生效 */
  flex: 0 0 auto;
  width: 18px;
  color: var(--primary-2);
  font-size: var(--fs-h2);
  line-height: 1;
  text-align: center;
  transition: transform 160ms ease;
}
.imp-fold-head.open .imp-chev{ transform: rotate(90deg); }
.imp-fold-body{ padding: 0 var(--sp-3) var(--sp-3); }
/* 折叠体的开关是 hidden 属性（js/app.js:2894/2555），这条必须留 */
.imp-fold-body[hidden]{ display: none; }
.imp-fold .map-title{ margin-top: var(--sp-2); }
.imp-fold .map-rows{ margin-bottom: 0; }
.imp-fold .import-mode-list{ margin-bottom: var(--sp-2); }

/* —— 「同一批货重复出现时」三选一：.dup-opt（两张表都没写过，真缺口）——
   选中态由 js/app.js:2710/2976 的 classList.toggle('selected') 给 */
/* 这个块本身只是个容器（真正的间距在 .map-title 与 .dup-mode-list 上），
   但给它一条基底规则，免得它在「带类名却一条规则都没命中」的表里继续出现 */
.dup-mode-block{ display: block; margin: 0 0 var(--sp-2); }
.dup-mode-block.hidden{ display: none; }   /* js/app.js:2367/2968 用 .hidden 类切换 */
.dup-mode-list{
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.dup-opt{
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: 56px;
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--card);
  cursor: pointer;
}
.dup-opt.selected{
  border-color: var(--primary);
  background: var(--primary-soft);
}
.dup-opt b{
  display: block;
  font-size: var(--fs-body);
  font-weight: 700;
  color: var(--text);
}
.dup-opt.selected b{ color: var(--primary-2); }
.dup-opt span{
  display: block;
  font-size: var(--fs-cap);
  line-height: 1.5;
  color: var(--text-2);
}
/* 里面的真 radio 是 <input … hidden>，只给一条兜底，别让别的规则把它顶出来 */
.dup-opt input[type="radio"][hidden]{ display: none; }

/* —— 表格前几行预览：.preview-wrap + .preview-table ——
   （markup 自带 inline style="margin-bottom:0"，inline 优先，这里不抢） */
.preview-wrap{
  max-width: 100%;
  max-height: min(50vh, 320px);
  margin-bottom: var(--sp-3);
  overflow: auto;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card);
  -webkit-overflow-scrolling: touch;
}
.preview-table{
  width: 100%;
  min-width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-cap);
  color: var(--text);
}
.preview-table th,
.preview-table td{
  padding: var(--sp-1) var(--sp-3);
  border: 1px solid var(--border);
  white-space: nowrap;
  text-align: left;
}
.preview-table th{
  background: var(--card-2);
  font-weight: 700;
  color: var(--text-2);
}

/* —— 旧「导入对话」弹窗独有的零件（UI 上已打不开，见交付说明）—— */
.imp-file{
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
  margin-bottom: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card-2);
}
.imp-ico{ flex: 0 0 auto; font-size: var(--fs-body); }
.imp-name{
  min-width: 0;
  overflow: hidden;
  font-size: var(--fs-cap);
  font-weight: 700;
  color: var(--text);
  text-overflow: ellipsis;
  white-space: nowrap;
}
.imp-meta{
  flex: 0 0 auto;
  margin-left: auto;
  font-size: var(--fs-badge);
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}
.imp-chat{
  max-height: 56vh;
  overflow-y: auto;
  padding-right: 2px;
  overscroll-behavior: contain;
}
.imp-ask{ margin: 0 0 var(--sp-3); }
.imp-ask[hidden]{ display: none; }          /* js/app.js:2997 用 hidden 属性切问句 */
.imp-q-zone{
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.imp-say{
  margin: 0;
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-sm) var(--r-md) var(--r-md) var(--r-md);
  background: var(--card-2);
  color: var(--text);
  font-size: var(--fs-body);
  line-height: 1.7;
}
/* 用户那句回答：右对齐的浅粉气泡（用 --primary-soft 而不是 --primary，
   白字压 --primary 只有 4.16:1，达不到正文 4.5:1） */
.imp-me{
  width: fit-content;
  max-width: 78%;
  margin: 0 0 var(--sp-2) auto;
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--primary-line);
  border-radius: var(--r-md) var(--r-md) var(--r-sm) var(--r-md);
  background: var(--primary-soft);
  color: var(--text);
  font-size: var(--fs-cap);
  line-height: 1.6;
}
.imp-me[hidden]{ display: none; }
.imp-foot{
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  margin-top: var(--sp-1);
}
.imp-foot .btn{ flex: 0 1 auto; min-width: 104px; min-height: 44px; }
.imp-ask-line{
  margin: 0 0 var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card-2);
}
/* 这一行是「这张表里『保质期』是哪一列？」——是真问句，按正文 14px 画 */
.imp-ask-line .imp-say-sub{
  margin-top: 0;
  font-size: var(--fs-cap);
  color: var(--text);
}
.imp-fix-select{
  width: 100%;
  min-height: 44px;
  padding: 0 var(--sp-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-cap);
}
.imp-fix-select:focus{ outline: 0; border-color: var(--primary); }
#imp-more-zone[hidden]{ display: none; }

/* —— 无障碍：减弱动效时不播动画 —— */
@media (prefers-reduced-motion: reduce){
  .wz-prog > i,
  .wz-opt,
  .wz-chev,
  .imp-chev,
  .import-opt{ transition: none; }
  .wz-opt:active,
  button.wz-x:active,
  .wz-pm:active{ transform: none; }
}


/* ===== 片段：v4-mech-member.css ===== */
/* ============================================================================
   v4-mech-member.css —— 「会员卡 / 开通续费 / 兑换码」这一面在 v4 下真正缺/坏的部分
   ----------------------------------------------------------------------------
   这份片段由 `node out/tmp/build-app-css.js` 拼在 `out/tmp/v4-mechanics.css` 之后，
   所以同特异性的规则以这里为准。只补证据（out/v4-audit-member/probe-out*.json 的
   computed 数值）能证明「本来没有 / 本来就坏」的地方，不重画 v4 已有的东西。
   只用 v4 令牌；不用 !important；不用图片/emoji；不写死 390px；不加动画
   （因此不需要 prefers-reduced-motion 分支）。

   改前实测（probe-out.json）+ 改后复测（probe-out-改后.json）的数值都写在每条规则
   上方的注释里，体检报告见 out/会员支付面-v4体检.md。

   已确认「本来就正常、因此不写规则」的部分（避免后人重复劳动）：
   · 老令牌桥：--muted/--surface/--surface-2/--amber/--primary-ink/--st-cool/
     --st-safe/--st-neutral/--c-expired 在 v4 下全部落到真 v4 令牌（body 上实测
     #6B6259/#FFFFFF/#FBF8F4/#9A4D66/#9A4D66/#6B6259/#3E6B57/#6B6259/#8A3A52），
     没有掉兜底、没有残留老深色；这一面用到的老令牌对比度 5.79~7.48 全达标。
   · 会员徽章 .member-badge.acc-badge：白字压 --primary-2 = 5.79:1 ✓
     （v3 的粉色主题就是「统一实底 + 白字」，见 css/styles.css:3617-3630 的对比度定案，
     所以五种状态不做分色，这不是坏点）。
   · .member-card 渐变/圆角/水印、.acc-panel/.acc-panel-title/.acc-pay-big(5.02)/
     .acc-pay-amount/.acc-order-id/.acc-qr-cap/.acc-qr-ph 几何、试用提醒条
     #trial-banner(4.63:1、按钮 44 高) 都已由 v4-full + mechanics 画对。
   · .acc-qr-cap / .acc-order-id 的 12px 与 .btn-primary 白字 4.13:1 是 v3 一直如此
     的既有事实（前者是标签不是正文，后者是设计稿自带），只记录、不改。
   ============================================================================ */

/* ---------------------------------------------------------------------------
   1) 门禁墙 #gate-mask —— 改前整块是裸样式
   改前：`#gate-mask` 390×150、无任何规则命中（.gate-card/.gate-title/.gate-sub 同样裸），
   底色实测 rgb(250,247,242) = 页面 --bg，即它只是文档流里一个 150px 高的普通块，
   既没有遮罩也没有卡面：底栏、内容照常露在外面，文案只有 15px/400 没有 700。
   改前实测「挡不住」的硬证据（探针 elementFromPoint + 几何）：
   · 未登录态 pos=static、background rgba(0,0,0,0)、box 390×150@0,664、
     coversViewport=false、视口正中点打到的元素是 svg（不是门禁层）；
   · 试用到期态 box 390×127@0,10519（掉在长列表后面一万像素处）、
     视口正中点打到的是 H3.item-name（货物名）。
   改后：pos=fixed、inset 0、z-index 45、bg rgba(43,39,36,.42)、box 390×844@0,0、
   coversViewport=true，未登录态视口正中点=DIV.gate-card、到期态=BUTTON#gate-cta。
   （老表的 .gate-* 规则在 css/styles.css:1300-1322，不进 v4 页面。）
   遮罩色/层叠取自 v4 自己的弹层惯例：.sheet-mask(:1061-1069) 用 rgba(43,39,36,.42)；
   层叠顺序 v4-full 里是 顶栏20(.app-header:84) / 底栏30(.tabbar:526) /
   弹层40(.sheet-mask:1064) / toast50(:1084)，门禁墙取 45：压住弹层、让 toast 还能露头。
   .hidden 的显示开关继续由 v4-full:617 的 `.hidden{display:none!important}` 负责，
   不需要在这里重复（!important 一定压得住本规则里的 display:flex）。
--------------------------------------------------------------------------- */
#gate-mask{
  position: fixed;
  inset: 0;
  z-index: 45;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-4);
  background: rgba(43,39,36,.42);
}
body.gate-on{ overflow: hidden; }              /* 老表 styles.css 的同一意图，v4 下丢了 */
#gate-mask .gate-card{
  width: 100%;
  max-width: 340px;
  padding: var(--sp-5) var(--sp-4) var(--sp-4);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-2);
  text-align: center;
}
#gate-mask .gate-title{
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-h1);
  font-weight: 700;
  color: var(--text);
}
#gate-mask .gate-sub{
  margin: 0 0 var(--sp-4);
  font-size: var(--fs-cap);
  color: var(--text-2);
  line-height: 1.6;
}
/* 右上角关闭（2026-10-08 店主：这卡要有个关闭按钮）。只把卡片收掉，门禁不变。 */
#gate-mask .gate-card{ position: relative; }
#gate-mask .gate-close{
  position: absolute;
  top: 6px;
  right: 6px;
  width: 44px;
  height: 44px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--text-2);
  font-size: 22px;
  line-height: 1;
  font-family: inherit;
  cursor: pointer;
}
#gate-mask .gate-close:hover{ background: var(--surface-2, rgba(0,0,0,.05)); }

/* ---------------------------------------------------------------------------
   2) 会员卡上的小字：13px 低于「正文 ≥14px」的硬要求，且渐变底上对比度不够
   改前实测：.member-meta 324×20、13px/400、#6B6259（mechanics 写死 13px）；
   .member-plan 14/700 #9A4D66、.member-meta b 13/700 #9A4D66，三者都压在
   .member-card 的 linear-gradient(135deg,--primary-soft→--primary-line) 上。
   按渐变两端算出来的对比度：--primary-2 4.63(最亮) / 3.57(最暗)，
   --text-2 4.78 / 3.68 ⇒ 最暗端两侧都不达标（3.57、3.68 < 4.5）。
   根因是 v4-full 的卡片渐变（不许改），所以只把字换成 v4 里唯一的深玫瑰
   --st-expired #8A3A52：5.99(最亮) / 5.14(最暗) 两端都达标；加粗的到期日再用
   --text 压到 11.85 / 9.13，保住「小字不抢标题」的层次。
   13px→14px 用 --fs-cap（v4 正文字号阶梯），因为「试用到期时间…到期后数据会隐藏」
   是用户要读懂的句子，不是标签。
   改后实测：.member-plan 字 #8A3A52、在「剩余 N 天」那一行的真实底色
   rgb(235,214,221) 上 5.41:1；.member-meta 324×21、14px/400、#8A3A52，
   在它自己那一行的真实底色 rgb(228,203,211) 上 4.91:1（该采样点比文字实际
   落脚的位置更靠右、更暗，属保守值）；.member-meta b 14px/700 #2B2724 9.72:1。
   （改前同一批采样点：#9A4D66 4.19 / #6B6259 3.89，都低于 4.5。）
--------------------------------------------------------------------------- */
#account-root .member-meta{ font-size: var(--fs-cap); }
#account-root .member-plan,
#account-root .member-meta{ color: var(--st-expired); }
#account-root .member-meta b{ color: var(--text); }

/* ---------------------------------------------------------------------------
   3) 徽章里的小星星 .member-badge-ic —— 改前裸样式，14×18 比 svg 高 4px
   （span 是内联盒，svg 走基线排版，底下多出一条降部空隙）⇒ 徽章里的图标看着偏上。
   改成 inline-flex + 居中后实测 14×14（改前 14×18）；徽章本身有 min-height:24 撑着，
   高度仍是 24，不影响 .member-badge 已经达标的 5.79:1 白字对比度。
--------------------------------------------------------------------------- */
#account-root .member-badge-ic{
  display: inline-flex;
  align-items: center;
}

/* ---------------------------------------------------------------------------
   4) 支付面板里的按钮列 .acc-actions-col —— 改前裸样式（298×48，无规则命中）
   v3 靠 js/account.js:405 注入的 `display:flex;flex-direction:column;gap:10px`，
   v4 下 injectStyle() 在 :352 直接 return，于是「换一张新的支付码」+「我已付好了，
   检查一下」两个按钮会在需要同时出现时贴在一起（本机 mock 网关只有后者，
   但代码分支确实会同时 append 两个 .acc-actions-col 的按钮）。
   按钮自带 .btn-block（width:100%），列方向下默认 stretch 也够，无需再写宽度。
--------------------------------------------------------------------------- */
#account-root .acc-actions-col{
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
}

/* ---------------------------------------------------------------------------
   5) 套餐档 .acc-plan-opt —— 结构意图是两行，实际挤成一行，同行高度还不一致
   改前实测：`.acc-plan-name`(76×21) 与 `.acc-plan-days`(63×17) 都是内联 span
   （buildPlanPickerHtml 只给它们包了 .acc-plan-main，没有 display:block，
   v3 的 js/account.js:414 也一样），于是 mechanics 写的 `margin-top:2px` 对内联无效，
   一行里变成「会员 1 个月¥50 / 1 个月 …… ¥50」——金额印两遍；
   而且长标题把未选中档撑成 298×50、选中档 298×66，同一个列表里两档不一样高。
   给两个 span 补 display:block 后每档都是「套餐名 / 金额·时长 / 右侧价格」两行，
   改后实测：未选中档与选中档都是 298×66（改前 298×50 / 298×66）、
   .acc-plan-main 202×40（改前 202×21）、.acc-plan-name 202×21、.acc-plan-days 202×17、
   .acc-plan-grid 298×214（改前 198）。
   同时给按钮补上字号/字色：改前 .acc-plan-opt 继承的是浏览器按钮默认
   `13.3333px/400 字 rgb(0,0,0)`（mechanics 只写了 font-family:inherit），
   现在看不出问题只因为内部 span 各自带字号色，属隐患。
--------------------------------------------------------------------------- */
#account-root .acc-plan-opt{
  font-size: var(--fs-body);
  color: var(--text);
}
#account-root .acc-plan-name,
#account-root .acc-plan-days{ display: block; }

/* ---------------------------------------------------------------------------
   6) 支付码图溢出白盒 —— 改前 `#acc-gw-qr img` 200×200 且 ★裸样式（hit=false），
   父盒 .acc-qr-ph 只有 132×132（padding 8、border 1px ⇒ 内容区 114×114），
   探针实测 overflowsParent=34x34：上下左右各越出虚线框 34px（相对内容区则各 43px），
   把虚线框和下面的说明都压住
   （v4 的 .acc-qr-box img{width:100%} 管不到 .acc-qr-ph；v3 同样没管，属于一直存在的坑）。
   二维码库给的是 200×200 的图，没有 width/height 属性、只有内联 display:block，
   所以 max-width/max-height:100% 正好把它收成 114×114。
   mechanics 已经给 .acc-qr-box 加了 overflow:hidden，这里把同一套做法补齐到
   .acc-qr-ph，并把 200px 的 img/canvas 按 100% 收进盒内（QR 是正方形，
   max-width + max-height 同时生效不会拉变形）。老 Android 上二维码库会走 canvas 分支，
   一并覆盖。
--------------------------------------------------------------------------- */
#account-root .acc-qr-ph{ overflow: hidden; }
#account-root .acc-qr-ph img,
#account-root .acc-qr-ph canvas,
#account-root .acc-qr-box img,
#account-root .acc-qr-box canvas{
  display: block;
  max-width: 100%;
  max-height: 100%;
}

/* ---------------------------------------------------------------------------
   7) 二维码兜底块 .acc-qr-fallback —— 改前裸样式
   （本机 window.QRCode 存在所以没渲染，但 renderQr 的两个分支都会用它：
   没有 QRCode 库时会把整条支付链接原样打进 132px 的白盒。）
   v3 是 12px + --muted + word-break:break-all，v4 下 word-break 丢了，
   长链接会横向顶出盒子；这里补回换行，颜色/字号沿用 v4 的 --text-2/--fs-badge。
   盒子在第 6 条已经 overflow:hidden：宁可把超长的兜底文本裁掉，
   也不让它压在下面的「我已付好了」按钮上。
--------------------------------------------------------------------------- */
#account-root .acc-qr-fallback{
  font-size: var(--fs-badge);
  color: var(--text-2);
  line-height: 1.5;
  text-align: center;
  word-break: break-all;
}

/* ---------------------------------------------------------------------------
   8) 支付提示清单 .acc-hint-list —— 12px 低于「正文 ≥14px」的硬要求
   改前实测 298×41、12px、对比度 5.17 ✓（对比度没问题，只是字号）；
   改后 298×48、14px，对比度仍是 5.17 ✓。
   v3 的 js/account.js:422 也是 12px，属于两面都偏小；这两条是「付完点检查」
   之类必须读懂的说明文字，按 v4 正文阶梯提到 --fs-cap(14px)。
--------------------------------------------------------------------------- */
#account-root .acc-hint-list{ font-size: var(--fs-cap); }


/* ===== 片段：v4-mech-mine.css ===== */
/* ============================================================================
   片段：v4-mech-mine.css
   「我的」屏功能折叠卡里的小零件 + 两个全局小件。
   ----------------------------------------------------------------------------
   施工范围（本次只动这一个文件，其它一律不碰）：
     ① 数据自检卡  .check-row（.sev-high/.sev-mid/.sev-low）/.check-top/.check-n/.check-hint/.check-sample
     ② 安装说明    .install-box（DOM 上实为 .install-box.install-steps-box）/.install-box-title
                   /.install-steps（ol）/.install-note
     ③ 关于卡      .about-version/.about-list（ul）
     ④ 会员卡星标  .member-badge-ic（14×18 的内联 SVG 底座）
     ⑤ 提醒屏返回箭头 .head-back-ic（18×21 的内联 SVG 底座）
     ⑥ 全局 toast 文字 .toast-msg

   规矩（都是硬要求，逐条对着写）：
     · 只用 out/桥build/v4-full.css:11-58 的令牌；老表的 --muted/--surface/--surface-2/
       --amber/--primary-ink/--line 在这份新表里根本不存在，一条都不写。
     · 不写 !important；不用位图 / emoji / 外链字体（图标都是页面里的内联 SVG）；
       宽度只用 100% / calc()，不写死 390px；本片段不引入任何动画，所以无需
       prefers-reduced-motion 兜底。
     · 字号纪律：正文/列表项 ≥14px（--fs-body 15 / --fs-cap 14）；只有纯「说明性小字」
       （.check-hint / .check-sample / .install-note）降到 13px，且颜色用 --text-2。
     · 触控纪律：本片段不缩小任何可点控件的命中区（.head-back 的 44px、
       .install-box 里的 .btn 的 48px 都原样保留），只给它们留间距。

   为什么画成「卡中卡」：这些零件都在 .card.my-card 的折叠体 .my-body 里，
   同一张卡的邻居是 .list-item（56px 行 + border-top:1px solid var(--border)）。
   所以折叠体内部一律走 v4 的次级面语言 —— --card-2 软底 + 1px --border + --r-sm 圆角，
   悬在白色卡面上，既分得清层级，又不会和白卡抢眼。
   ============================================================================ */

/* ============================================================
   ① 数据自检卡（js/app.js:1487 生成）
   一条 issue = 一小块：左标题 + 右条数徽章 + 说明 + 举例。
   严重度只改三样东西（左侧竖条 / 徽章底色 / 徽章字色），用元素级局部变量集中写，
   免得三条 severity 各写一遍同样的盒子。
   ============================================================ */
.check-row{
  --chk-bar: var(--border-strong);      /* 左侧 3px 竖条 */
  --chk-bg:  var(--st-unknown-bg);      /* 条数徽章底色 */
  --chk-ink: var(--text-2);             /* 条数徽章字色 */
  margin-top: var(--sp-2);
  padding: var(--sp-3);
  padding-left: calc(var(--sp-3) + 1px); /* 竖条 3px 比边框粗 2px，补回来，四条左右对齐 */
  background: var(--card-2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--chk-bar);
  border-radius: var(--r-sm);
}
.check-row:first-child{ margin-top: 0; }
/* 需要核对 → 已过期红；还差东西 → 琥珀提醒；没大碍 → 完成绿。
   （和老表 css/styles.css:1104-1106 的 sev-high/sev-mid/sev-low 分级一一对应，
   只是把 --c-expired/--c-warning 换成 v4 的 --st-* ） */
.check-row.sev-high{ --chk-bar: var(--st-expired); --chk-bg: var(--st-expired-bg); --chk-ink: var(--st-expired); }
.check-row.sev-mid { --chk-bar: var(--st-soon);    --chk-bg: var(--st-soon-bg);    --chk-ink: var(--st-soon); }
.check-row.sev-low { --chk-bar: var(--st-ok);      --chk-bg: var(--st-ok-bg);      --chk-ink: var(--st-ok); }

/* 左标题 + 右条数：一行两端对齐（照老表 css/styles.css:1107 的基线对齐改居中，
   因为右边的条数现在是一枚有底色的胶囊，基线对齐会让胶囊悬空） */
.check-top{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
}
.check-top b{
  font-size: var(--fs-body);            /* 15px，正文下限之上 */
  font-weight: 700;
  color: var(--text);
}
/* 「21 条」：小徽章。--fs-badge 12px 是「非正文」的数字标签，允许；
   tabular-nums 让多条 issue 的条数竖着对得齐（老表同款细节） */
.check-n{
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  min-height: 22px;
  padding: 2px var(--sp-2);
  border-radius: var(--r-pill);
  background: var(--chk-bg);
  color: var(--chk-ink);
  font-size: var(--fs-badge);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  white-space: nowrap;
}
/* 说明 + 举例：说明性小字，13px 是允许的下限档；行距 1.6 让长中文段落好读 */
.check-hint{
  margin-top: var(--sp-1);
  font-size: 13px;
  line-height: 1.6;
  color: var(--text-2);
}
.check-sample{
  margin-top: var(--sp-1);
  font-size: 13px;
  line-height: 1.6;
  color: var(--text-2);
  overflow-wrap: anywhere;              /* 货名常是长串没有空格，别把盒子撑破 */
}

/* ============================================================
   ② 安装说明（js/install.js:68-120）
   .install-box 里是「标题 → ol 三步 → 底下一句备注」。
   标题 14px、步骤 14px（都是正文档），备注 13px 说明小字。
   ============================================================ */
.install-box{
  margin-top: var(--sp-3);
  padding: var(--sp-3);
  background: var(--card-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font-size: var(--fs-cap);             /* 14px */
  line-height: 1.6;
  color: var(--text);
}
/* 微信/QQ 里打开的那种「加不了桌面」的警告变体（js/install.js:72 arrowBox('install-warn')）：
   软琥珀底 + 标题转琥珀字，和自检卡的 sev-mid 同一档提醒语言 */
.install-box.install-warn{
  background: var(--st-soon-bg);
  border-color: var(--border);
}
.install-box.install-warn .install-box-title{ color: var(--st-soon); }

.install-box-title{
  margin-bottom: var(--sp-2);
  font-size: var(--fs-cap);             /* 14px */
  font-weight: 700;
  color: var(--text);
}
/* 有序列表：不用浏览器默认的 1. 2. 3.（字号/缩进都不受控），
   改用 counter + 自制序号徽章 —— --primary-soft 底 + --primary-2 字，和 .list-icon 同一套语言 */
.install-steps{
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: install-step;
  font-size: var(--fs-cap);             /* 14px */
  line-height: 1.6;
  color: var(--text);
}
.install-steps li{
  position: relative;
  counter-increment: install-step;
  padding-left: 30px;                   /* 20px 徽章 + 10px 间隙 */
  min-height: 24px;
}
/* 行距按店主说的「宽一点」：比段落行距 1.6 再松一档，三步之间一眼能分开 */
.install-steps li + li{ margin-top: var(--sp-3); }
.install-steps li::before{
  content: counter(install-step);
  position: absolute;
  left: 0;
  top: 1px;                             /* (行高 22.4 − 徽章 20) / 2 ≈ 1，徽章压着首行中线 */
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: var(--r-pill);
  background: var(--primary-soft);
  color: var(--primary-2);
  font-size: var(--fs-badge);           /* 12px，圆圈里的单个数字不算正文 */
  font-weight: 700;
  line-height: 1;
}
.install-steps li b{ color: var(--text); font-weight: 700; }

/* 备注：说明性小字 13px。上面压一条虚线，把它和「步骤」分开（老表是一条 margin-top，
   这里给一条更轻的分区线，因为 v4 的 .install-note 常常紧跟着长步骤） */
.install-note{
  margin-top: var(--sp-3);
  padding-top: var(--sp-2);
  border-top: 1px dashed var(--border);
  font-size: 13px;
  line-height: 1.6;
  color: var(--text-2);
}
.install-note b{ color: var(--text); font-weight: 700; }

/* 安卓那条「一键添加到桌面」的按钮（js/install.js:99/79）：按钮本身是 .btn（48px 命中区，
   不动），只补它和标题之间的间距，免得贴在一起 */
.install-box > .btn{ margin-top: var(--sp-2); }

/* ============================================================
   ③ 关于卡（app-v4.html:365-371）
   ============================================================ */
.about-version{
  font-size: var(--fs-body);            /* 15px */
  font-weight: 700;
  color: var(--text);
}
/* 三条说明 = 键值/条目列表：行间一条 border-top 细线，行内左侧一枚 6px 小圆点。
   用 flex 而不是 ::marker，是为了让圆点跟着首行中线走（多行换行时也不会飘到中间）。 */
.about-list{
  margin: var(--sp-2) 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--fs-cap);             /* 14px，正文档 */
  line-height: 1.6;
  color: var(--text-2);
}
.about-list li{
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-2) 0;
}
.about-list li + li{ border-top: 1px solid var(--border); }
.about-list li:first-child{ padding-top: 0; }
.about-list li:last-child{ padding-bottom: 0; }
.about-list li::before{
  content: "";
  flex: 0 0 auto;
  width: 6px;
  height: 6px;
  margin-top: 8px;                      /* (行高 22.4 − 圆点 6) / 2 ≈ 8，压在首行中线上 */
  border-radius: var(--r-pill);
  background: var(--primary-line);
}

/* ============================================================
   ④ 会员卡星标（js/account.js:536-540：.member-badge 里的内联 SVG）
   这一段只解决「底座盒子比图标高 4px」的问题：span 原来是 inline，
   行高把它撑成 14×18，SVG 只在 .member-badge svg 里定了 14×14。
   颜色不写死 —— SVG 自带 stroke="currentColor"，继承 .member-badge 的 #FFFFFF。
   ============================================================ */
.member-badge-ic{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  color: currentColor;
}
.member-badge-ic svg{
  display: block;                       /* 干掉 inline 基线下沉 */
  width: 14px;
  height: 14px;
}

/* ============================================================
   ⑤ 提醒屏返回箭头（app-v4.html:19 的 [data-ic="chevron"] 占位符）
   同样只是「底座高 3px」的问题：18×21 → 18×18。
   两条纪律：
     · 旋转不在这儿写 —— 图标集的 chevron 朝右、v4 的返回箭头朝左，
       out/tmp/v4-mechanics.css:236 已有 `.head-back .head-back-ic svg{transform:rotate(180deg)}`，
       那条选择器权重比这里高，保持原样即可（本片段排在机制层之后，但不会覆盖它）。
     · .head-back 自己的 44px 命中区一点不动，这里只收拾里面那颗图标。
   颜色继承 .head-back 的 --primary-2（SVG 用 stroke="currentColor"）。
   ============================================================ */
.head-back-ic{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  color: currentColor;
}
.head-back-ic svg{
  display: block;
  width: 18px;
  height: 18px;
}

/* ============================================================
   ⑥ 全局 toast 文字（js/app.js:308：<div class="toast-msg">）
   .toast 容器在 v4-full.css:1079-1090 已有样式（深底 #FFFFFF 字 / 圆角胶囊 / 固定在 tabbar 上方）。
   这里只补容器没定的那一条：正文用 15px，颜色和换行都继承容器，不另起一套色。
   ============================================================ */
.toast-msg{
  font-size: var(--fs-body);            /* 15px */
  line-height: 1.5;
  color: inherit;                       /* 跟随 .toast 的白字 */
  overflow-wrap: anywhere;              /* 长货名/长路径不要把胶囊撑出屏 */
}

/* ============================================================================
   追加块 R2（2026-09-28 追加）：五族「默认 hidden / 只在手机 UA 下出现」的零件。
   为什么要追加：15 态体检跑在桌面 UA + 默认折叠态上，下面这些零件在体检里
   永远不出现（`.hidden` 由 CSS 吃掉、手机提示条要安卓/iOS UA 才吐、占位卡要
   account.js 没加载才吐），但店里真机上会看到 —— 老表都有规则、新表一条没有。
   取材出处逐族写在每节开头；`.acc-row` 一族的结论在本片段末尾单列。
   ============================================================================ */

/* ============================================================
   ⑦ 数据安全卡里的两行：可恢复 / 回收站
      出处 app-v4.html:311-327（两个 `div.safe-row.hidden`，有货时由 JS 摘 `hidden`）；
      老表 css/styles.css:1090-1097（.safe-row 是 flex 两端对齐 + padding-top:12px +
      border-top:1px 分隔线，.safe-row-title 15px/600，.btn 被压成 8px 14px/13px）。
      新表 v4-full.css 里 `.safe-*` 只有一条 `.safe-actions`（:662），safe-row 一族 0 条
      → 现在两个 `.safe-row` 是块级堆叠：标题像正文、按钮掉到文字下面一行。
      画法：把老表那套「文本在左、按钮在右、行间一条 --border 分隔线」照搬，
      但按钮不再压小 —— 保留 v4 的胶囊语言，只把高度收到 44px（可点下限）、字号 14px。
   ============================================================ */
.safe-row{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  margin-top: var(--sp-3);              /* 12px，和 .setting-sub / .safe-actions 的节奏一致 */
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border);  /* 两行之间的分隔线 */
}
/* min-width:0 是老表的关键一笔：没有它，长说明文字会把右边的按钮挤出卡片 */
.safe-row-txt{ flex: 1 1 auto; min-width: 0; }
/* 标题要一眼看出是标题：15px + 700 + --text（改前实测 15px/400、和正文一个样） */
.safe-row-title{
  font-size: var(--fs-body);
  font-weight: 700;
  color: var(--text);
}
.safe-row-btns{
  display: flex;
  gap: var(--sp-2);
  flex: 0 0 auto;                       /* 按钮区不被文字压缩 */
}
/* 说明文字是 .setting-sub（v4-full.css:659）。给它在这里加一条断行兜底：
   说明里会拼进货名（js/app.js:3942「删除了「牛奶」 · 12 条 · 14:32」），
   货名可能是一长串没有空格的字 → 不兜底就会从文字列里溢出去（按钮因为有
   min-width:0 不会被顶走，但文字会压到卡外）。只作用于这两行，不碰全局 .setting-sub。 */
.safe-row-txt .setting-sub{ overflow-wrap: anywhere; }
/* `.safe-row` 里直接挂的按钮（#btn-rollback）和 .safe-row-btns 里的都吃这条 */
.safe-row .btn{
  flex: 0 0 auto;
  min-height: 44px;                     /* 可点下限；默认 .btn 是 48，这里收一档好排两列 */
  padding: 0 var(--sp-3);               /* 0 12px */
  font-size: var(--fs-cap);             /* 14px：比 .btn 的 15px 收一档，不抢正文的戏 */
  white-space: nowrap;                  /* 「全部还原」四个字不许折行 */
}
/* 「清空」是删动作：借 v4 现成的危险色语言（= .btn-danger 的配色，--st-expired 系），
   免得它和旁边的「全部还原」长得一模一样。（要撤就删这 5 行，不影响别的） */
#btn-trash-empty{
  background: var(--st-expired-bg);
  border-color: var(--st-expired-bg);
  color: var(--st-expired);
}
#btn-trash-empty:active{ background: var(--st-expired); border-color: var(--st-expired); color: #FFFFFF; }

/* ============================================================
   ⑧ 「加到手机桌面」提示条（手机浏览器里的那一窄条）
      出处 js/install.js:171-178（renderHint() 注入 #install-hint-host，宿主在
      app-v4.html:35，位于 <main> 里、所有 view 之前；桌面 UA 下 hintWanted() 为假 →
      连 DOM 都没有，体检永远看不到它）。结构：
        .install-hint = [.install-hint-ico(📱 emoji)] [.install-hint-text] [.btn.btn-primary.install-hint-btn] [.install-hint-x]
        .install-hint-panel（兄弟节点，展开「怎么加」时装 bodyHtml()）
      老表 css/styles.css:1365-1389（--primary-soft 粉底、12px 文字、× 只有 padding:0 2px）。
      新表 0 条。画法：改成 v4 的中性提示条（--card-2 底 + 1px --border + --r-sm），
      粉色只留给 28px 圆形图标底和「怎么加」按钮；文字回到 14px 正文档；
      × 做满 44×44 命中区（老表那个约 24×24，手指点不到）。
   ============================================================ */
.install-hint{
  display: flex;
  align-items: center;
  gap: var(--sp-2);                     /* 8px */
  margin: var(--sp-3) var(--sp-4) 0;    /* 12px 16px 0 —— 和 .card{margin:12px 16px 0} 左右对齐 */
  padding: var(--sp-2) var(--sp-3);     /* 8px 12px */
  background: var(--card-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);           /* 10px */
}
/* 图标底：28px 圆 + --primary-soft 底。里面是 markup 自带的 📱 emoji（不许动 markup），
   所以只控制字号和居中 —— 本片段没有引入任何图片/emoji/外链字体。 */
.install-hint-ico{
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--r-pill);
  background: var(--primary-soft);
  font-size: 16px;
  line-height: 1;
}
/* 一句话说明：14px 正文档 + --text（老表 12px，手机上太小） */
.install-hint-text{
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--fs-cap);             /* 14px */
  line-height: 1.5;
  color: var(--text);
}
/* 「怎么加」= 已有的 .btn.btn-primary（粉底白字），这里只把它收到 44px、字号 14px，
   别让这条提示被一颗大按钮带跑 */
.install-hint-btn{
  flex: 0 0 auto;
  min-height: 44px;
  padding: 0 var(--sp-3);
  font-size: var(--fs-cap);
}
/* ×：老表是 padding:0 2px 的裸字符 ≈24×24，手指点不中。做成 44×44 的透明命中区，
   视觉上还是一个小 ×（--text-2），按下去才出 --primary-soft 底。 */
.install-hint-x{
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
  margin-right: calc(0px - var(--sp-1)); /* -4px：44px 的透明区不把 × 推离右边缘 */
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--text-2);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.install-hint-x:active{ background: var(--primary-soft); color: var(--primary-2); }
/* 展开的面板：装的是 ② 那套 .install-box。外边距在这儿管，
   所以面板里的第一块 .install-box 不要再叠一层 margin-top（否则空 24px） */
.install-hint-panel{ margin: var(--sp-2) var(--sp-4) 0; }
.install-hint-panel > .install-box:first-child{ margin-top: 0; }

/* ============================================================
   ⑨ 「已经在手机桌面上了」（install-done）
      出处 js/install.js:125（isStandalone() 或已触发 installed 事件时的一行结论）。
      老表 css/styles.css:1363（14px/700/写死的 #15803d）。
      新表 0 条。画法：句意是「完成」→ 用 v4 的 --st-ok（写死色换成令牌），
      字号抬到正文档 15px。
   ============================================================ */
.install-done{
  font-size: var(--fs-body);            /* 15px */
  font-weight: 700;
  color: var(--st-ok);                  /* #3E6B57：白卡上 6.09:1，--card-2 上 5.79:1 */
}

/* ============================================================
   ⑩ 吐司里的问题清单（.toast-problems）
      出处 js/app.js:310-312（toast(msg, problems) 时拼进 `.toast`，最多 10 条 li）。
      老表 css/styles.css:1194-1202（padding-left:16px + 默认圆点 + 12px + opacity .85）。
      新表 0 条 → 深底吐司里会冒出 UA 的圆点和 16px 缩进，和旁边 15px 白字的
      .toast-msg 完全两个风格。
      画法：去掉 UA 的圆点与缩进，改成 flex 行 + 4px 同色小圆点，字号回到 14px，
      颜色直接继承吐司的白字（不另起一套色），行距 1.5。
      对比度：白字压在 .toast 的 rgba(43,39,36,.9) 上、底下再垫一层 --bg #FAF7F2，
      合成出约 #403C39 → 10.9:1（远高于 4.5:1；实测值见报告）。
   ============================================================ */
.toast-problems{
  margin: var(--sp-2) 0 0;              /* 8px：和上面的 .toast-msg 拉开 */
  padding: 0;                           /* 干掉 UA 的 padding-left:16px */
  list-style: none;                     /* 干掉默认圆点 */
  font-size: var(--fs-cap);             /* 14px：条目名是内容，不降到 12px 小字档 */
  line-height: 1.5;
  color: inherit;                       /* 跟随 .toast 的 #FFFFFF */
  max-height: 130px;                    /* 最多 10 条，别让吐司长成一屏（老表同款兜底） */
  overflow-y: auto;
}
/* min-width:0 + overflow-wrap:anywhere 是这个 ul 的**安全绳**，不是装饰：
   `.toast` 是 `position:fixed; left:50%; transform:translateX(-50%)` 且**没有 width**
   （v4-full.css:1079），所以吐司宽度是 shrink-to-fit —— 可用宽度只有半个视口。
   只要清单里出现一条「长货名连成一串、没有空格」的条目，它的 min-content 就会
   顶穿可用宽度，把整条吐司撑到屏外（实测 72 字符长串 → 吐司 506px 宽，右边出屏 116px）。
   li 是 flex 容器，匿名 flex item 默认 min-width:auto 不肯缩 → 必须显式 min-width:0。 */
.toast-problems li{ display: flex; align-items: flex-start; gap: var(--sp-2); min-width: 0; overflow-wrap: anywhere; }
.toast-problems li + li{ margin-top: var(--sp-1); }
/* 4px 小圆点代替 UA 的 disc：走 flex，所以换行的第二行会和第一行文字左对齐 */
.toast-problems li::before{
  content: "";
  flex: 0 0 auto;
  width: 4px;
  height: 4px;
  margin-top: 8px;                      /* (行高 21 − 圆点 4) / 2 ≈ 8.5，压首行中线 */
  border-radius: var(--r-pill);
  background: currentColor;
}

/* ============================================================
   ⑪ 账户占位卡的两行字（降级路径）
      出处 js/app.js:285-286（account.js 没加载/init 抛错时的降级占位：
        <div class="card"><div class="account-name">账户功能加载中</div>
        <div class="account-hint">请刷新页面重试。注册即送 7 天试用…</div></div>）
      老表 css/styles.css:804 / :851（#account-root 作用域下 16px/700 break-all、12px --muted）。
      新表 0 条。正常加载下永远不出现 —— 体检看不到，只能注入复现。
      画法：名字 16px/700 --text（保留 word-break:break-all，用户名可能是一长串邮箱），
      提示 14px --text-2（老表 12px 太小，且它其实是一句要人照做的话）。
   ============================================================ */
.account-name{
  font-size: var(--fs-h2);              /* 16px */
  font-weight: 700;
  color: var(--text);
  word-break: break-all;
}
.account-hint{
  margin-top: var(--sp-1);
  font-size: var(--fs-cap);             /* 14px */
  line-height: 1.5;
  color: var(--text-2);
}
/* 额外发现（不在追加清单里，但同属「我的」屏、新表同样 0 条）：
   js/account.js:898 —— 账号卡在服务器不可达时追加的一行 `.acc-hint`
   「未连接服务器（可点登录页的服务器设置）」。它没有 .list-item 之类的邻居帮忙兜底，
   在 v4 里是裸的（体检也看不到：要 serverError 才吐）。色用 --st-soon：这是「连不上」的提醒，
   不是错误终止，和自检卡 sev-mid 同一档语言。 */
.acc-hint{
  margin-top: var(--sp-2);
  font-size: var(--fs-cap);             /* 14px */
  line-height: 1.5;
  color: var(--st-soon);
}

/* ============================================================
   ⑫ `.acc-row` —— 本片段**故意不写规则**（结论，不是遗漏）
      js/account.js 在 v4 分支里吐 `.acc-row` 的地方只有两处：:879 / :886，
      两处都是 `<div class="list-item acc-row">` —— 老表那条
      `.acc-row{display:flex;gap:10px;align-items:center}`（:388）来自 account.js
      自己注入的 <style id="acc-style">，而 v4 下 injectStyle() 明确只建空标签
      （js/account.js:354-358：「v4 下照旧建这个标签、内容留空」）。
      也就是说：v4 的 .acc-row 永远和 .list-item 同时出现，行高/对齐/分隔线
      已由 v4-full.css:716-737 的 .list-item（56px 行、border-top:1px --border）接管，
      再写一条只会和 .list-item 打架。实测数值见报告（行高 56、display:flex）。
   ============================================================ */

/* ============================================================
   ⑬ 卡里的「行」是 <button>/<a> 时，顶部分隔线只画到一半（店主 2026-09-28 提的
      「这些横线为什么都只有一半呢 划到头吧」）
      根因：v4-full.css:746 的 `.card > .list-item{ margin: 0 -16px; width: auto; }`。
        width:auto 对块级元素（`<div class="list-item acc-row">`）＝撑满内容盒，
        行盒 606px（卡 608 − 两侧 1px 边框）+ 两侧 −16px 出血，正好从卡片左边缘画到右边缘；
        但对行内级元素（`<button>` / `<a>`）＝ shrink-to-fit，行盒只有内容那么宽。
      实测（675 布局宽，我的页）：同样 56px 高的一行，
        div.list-item.acc-row 606px  ✓ 满宽
        button/* 2026-10-10：标题旁的独立小链接（如「手机提醒 试一下」）留 8px 间距 */
.setting-title .link-btn{margin-left:8px;}

/* 2026-10-10 分组小标题（我的 → 功能卡）：提醒 / 数据 / 这个应用 */
.my-cap{font-size:13px;color:var(--text-2);margin:14px 2px 6px;letter-spacing:.02em;}
.my-cap:first-child{margin-top:2px;}
.my-item.list-item 176 / 213 / 218 / 220 / 248 / 339px  ✗ 只有内容宽
        button.list-item.acc-doc（怎么用/隐私政策/用户协议）105 / 120 / 120px  ✗
        button#acc-logout  180px（样张 v4-me.html:64-69 也是 180）      ✗
        ⇒ 这些行 `.list-item{border-top:1px solid var(--border)}`（v4-full.css:734）画出来的
          分隔线就只占卡片宽度的 17%~56%，而且每行长度都不一样，看着像画了一半。
      改法：给卡里的行内级行显式满宽 = 内容盒 + 两侧 16px 出血（`*{box-sizing:border-box}`，
        v4-full.css:70），算出来的盒与 div 行逐像素相同（x35 w606 right641）。
      只碰宽度：行高、padding、border-top、:first-child 去线、:last-child 圆角都不动。
      与样张的差异：样张（out/桥build/v4-me.html）自己也是半截线（它是 `<a>`，同一条 width:auto），
        所以这一处**故意**与样张不一致 —— 店主明确要「划到头」，记进施工记录的有意差异清单。
   ============================================================ */
.card > button.list-item,
.card > a.list-item{
  width: calc(100% + var(--sp-4) + var(--sp-4));   /* 574 + 32 = 606 = div 行的宽度 */
}
/* §㉑ 「添加」页卡片里的行分隔线也画到卡片边（店主口径：横线要划到头）
   根因：out/桥build/v4-full.css:937-955 里 .upload-now-row 与 .upload-faq-row 是块级 flex，
   横向宽度 = 卡片内容宽 324（卡片 358 减左右内边距 16 各一份），
   border-top 只能画 324 宽，到不了卡片内边缘，视觉上比卡片窄一圈。
   修法同 §⑬（我的页 .list-item）：负外边距出血 + 等量内边距把内容留在原位。
   这里只动左右两个方向，竖排间距与 :first-child 去线规则都不碰。 */
.card > .upload-now-row,
.card > .upload-faq-row{
  margin-left: calc(-1 * var(--sp-4));
  margin-right: calc(-1 * var(--sp-4));
  padding-left: var(--sp-4);
  padding-right: var(--sp-4);
}


/* ===== 片段：v4-mech-modal.css ===== */
/* ==========================================================================
   一喵效期管家 · v4 机制层片段：弹窗族
   文件：out/tmp/v4-mech-modal.css
   覆盖：弹窗外壳（#modal-root）/ 货物详情 / 改这一件 / 产品内确认框（#dlg-root）
   令牌全部取自 out/桥build/v4-full.css:11-58，不使用任何老表变量
   （--muted / --surface / --surface-2 / --line / --amber / --primary-ink / --st-cool
    在新表里都不存在，用了会掉到兜底值）。
   层级：本片段排在 out/tmp/v4-mechanics.css 之后、v4-mech-*.css 按文件名排序拼接，
        因此 .modal-card 的几何只写在类选择器（0,1,0）上，不绑 ID、不加 !important，
        好让后续片段（如导入向导 .imp-wiz-card）能用组合类覆盖。
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. 弹窗打开时锁住背后的页面滚动
   老表 css/styles.css:944 有这条，新表整条链路都没有 → 不补的话手指在遮罩上
   滑动仍然会滚动背后的列表（视觉上像"弹窗跟着动"）。
   -------------------------------------------------------------------------- */
body.modal-open{ overflow: hidden; }

/* --------------------------------------------------------------------------
   1. 遮罩：#modal-root（货物详情 / 改这一件 / 注销账号 / 说明页 / 导入向导共用）
   显隐走 v4-full 已有的 .hidden{display:none !important}：
   js/app.js:325-351 openModal() 只做 remove('hidden') / add('active')，
   而 js/delete-account.js:69-96 与 js/pages.js:100-128 只 remove('hidden')、
   根本不加 .active —— 所以遮罩必须"常驻 display:flex、靠 .hidden 收场"，
   不能只写 .modal.active{display:flex}（那样注销账号弹窗永远打不开）。
   -------------------------------------------------------------------------- */
.modal{
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 层级 45：v4-full 的 .toast 是 z-index:50，而"改这一件"保存时的校验提示
     （js/app.js:784-786 toast('货名不能空着')）必须盖在弹窗上方 —— 老站靠
     #toast-root{z-index:200} + body.modal-open 把 toast 挪到顶部实现。
     45 同时高过 .sheet-mask(40) 与 .tabbar(30)，低于 .toast(50)。 */
  z-index: 45;
  background: rgba(43,39,36,.45);
}

/* 兜底：容器空着（关窗后 innerHTML='' 且 .hidden 掉了）时别自己铺一层遮罩 */
#modal-root.modal:empty{ display: none; }

/* --------------------------------------------------------------------------
   2. 卡片外壳
   老站是底部抽屉（align-items:flex-end、只圆上两角）；这里按 v4 语言改成
   居中卡片：--r-md 四角 + --shadow-2（和 .card / .sheet 同一套手感）。
   -------------------------------------------------------------------------- */
.modal-card{
  display: flex;
  flex-direction: column;
  width: calc(100% - 32px);
  max-width: 520px;
  max-height: 86vh;
  /* 用 auto 而不是 hidden：导入向导也用 .modal-card（.modal-card.imp-wiz-card），
     它自己不管高度，万一内容超过 86vh，hidden 会把它的「下一步」裁掉再也点不到；
     我自己的弹窗靠 .modal-body{overflow-y:auto} 内部滚动，卡片本身不会溢出，
     所以这里 auto 对详情/编辑/确认框没有任何影响。 */
  overflow: auto;
  background: var(--card);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-2);
  animation: v4-modal-in 160ms cubic-bezier(.2,.8,.25,1);
}

/* 只做位移、不做淡入：审计脚本点开弹窗后立刻截图，半透明的中间帧会被拍进去 */
@keyframes v4-modal-in{
  from{ transform: translateY(10px); }
  to{ transform: none; }
}

/* --------------------------------------------------------------------------
   3. 头部 + 正文（详情 / 编辑 / 确认框 / 注销共用）
   -------------------------------------------------------------------------- */
.modal-head{
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  min-height: 56px;
  padding: 0 var(--sp-4);
  border-bottom: 1px solid var(--border);
}

.modal-title{
  font-size: var(--fs-h2);
  font-weight: 700;
  line-height: 1.35;
  color: var(--text);
}

/* 关闭按钮：44×44 命中区（老表是 40×40），外观照 .icon-btn（v4-full:619-632） */
.modal-close{
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--card);
  color: var(--text-2);
  font-family: inherit;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  transition: transform 140ms cubic-bezier(.2,.8,.25,1), background 140ms linear;
}
.modal-close:active{ transform: scale(.96); background: var(--bg-soft); }

/* 正文：卡片内部滚动的唯一容器（min-height:0 是 flex 子项能滚动的前提） */
.modal-body{
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: var(--sp-4);
  font-size: var(--fs-body);
  color: var(--text);
}

/* --------------------------------------------------------------------------
   4. 货物详情
   -------------------------------------------------------------------------- */
/* 货名独占一行，badge 与 .item-days 胶囊在下一行并排、放不下就换行 */
.detail-head{
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  padding-bottom: var(--sp-3);
  border-bottom: 1px solid var(--border);
}
.detail-name{
  flex: 1 0 100%;
  font-size: var(--fs-h1);
  font-weight: 700;
  line-height: 1.4;
  color: var(--text);
  overflow-wrap: anywhere;
}

/* 行清单本身：纯纵向堆叠容器，行距全部由 .detail-row 的 padding 与 border-top 给 */
.detail-rows{
  display: flex;
  flex-direction: column;
}

/* 两列：左标签（14px --text-2）、右取值（15px --text，右对齐）；行间 1px 线 */
.detail-row{
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-2) 0;
  border-top: 1px solid var(--border);
}
.detail-row:first-child{ border-top: 0; }

.detail-label{
  flex: 0 0 auto;
  font-size: var(--fs-cap);
  font-weight: 600;
  color: var(--text-2);
}
.detail-value{
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--fs-body);
  font-weight: 600;
  color: var(--text);
  text-align: right;
  overflow-wrap: anywhere;
}

/* 动作区：按钮整宽竖排（老表是横排两个 flex:1，窄屏上"删这一件"太窄） */
.detail-acts{
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-top: var(--sp-4);
}
.detail-acts .btn{ width: 100%; }

/* "删这一件"用的是 .btn.btn-ghost.btn-danger-text，涂成 .btn-danger 同款
   （--st-expired #8A3A52 落在 --st-expired-bg #F2DFE4 上 = 5.9:1，达标） */
.detail-acts .btn-danger-text{
  background: var(--st-expired-bg);
  border-color: var(--st-expired-bg);
  color: var(--st-expired);
}

/* --------------------------------------------------------------------------
   5. 改这一件（表单）
   -------------------------------------------------------------------------- */
.edit-field{ margin-top: var(--sp-3); }
.edit-field:first-child{ margin-top: 0; }

.edit-label{
  display: block;
  margin-bottom: var(--sp-1);
  font-size: var(--fs-cap);
  font-weight: 700;
  color: var(--text-2);
}

/* 与 v4-full 的 .input（962-974）同一手感：48px 高、--r-sm、白底 + --border 边 */
.edit-input{
  width: 100%;
  min-height: 48px;
  padding: 0 var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-body);
}
.edit-input::placeholder{ color: var(--text-2); }
.edit-input:focus{ outline: 0; border-color: var(--primary); }

.edit-qty{
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.qty-btn{
  flex: 0 0 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card-2);
  color: var(--primary-2);
  font-family: inherit;
  font-size: 20px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  transition: transform 140ms cubic-bezier(.2,.8,.25,1), background 140ms linear;
}
.qty-btn:active{ transform: scale(.96); background: var(--primary-soft); }

.edit-input.edit-qty-input{
  flex: 1 1 auto;
  width: auto;
  min-width: 0;
  text-align: center;
  font-size: var(--fs-h1);
  font-weight: 700;
}
/* 数量两边已经有 −/＋ 按钮，藏掉浏览器自带的数字微调箭头免得两套控件打架 */
.edit-input.edit-qty-input{ appearance: textfield; -moz-appearance: textfield; }
.edit-input.edit-qty-input::-webkit-outer-spin-button,
.edit-input.edit-qty-input::-webkit-inner-spin-button{
  -webkit-appearance: none;
  margin: 0;
}

.edit-hint{
  margin: var(--sp-2) 0 var(--sp-3);
  font-size: var(--fs-cap);   /* 老表是 12px，按无障碍要求提到 14px */
  line-height: 1.6;
  color: var(--text-2);
}

/* --------------------------------------------------------------------------
   6. 产品内确认框 #dlg-root（js/app.js:654-667 每次现建、关掉即 remove，
   所以可以常驻 display:flex；DOM 上是 #dlg-root > .modal-card.dlg-card）
   -------------------------------------------------------------------------- */
.dlg-root{
  position: fixed;
  inset: 0;
  z-index: 46;   /* 比 .modal(45) 高一层：确认框要压在详情弹窗上面 */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-4);
  background: rgba(43,39,36,.45);
}
/* 确认框比弹窗窄：0,2,0 压过 .modal-card(0,1,0) */
.dlg-root .modal-card{
  width: 100%;
  max-width: 400px;
}

.dlg-msg{
  font-size: var(--fs-body);
  font-weight: 700;
  line-height: 1.5;
  color: var(--text);
}
.dlg-detail{
  margin-top: var(--sp-2);
  font-size: var(--fs-cap);   /* 老表是 13px，提到 14px */
  line-height: 1.7;
  color: var(--text-2);
}
.dlg-acts{
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-top: var(--sp-4);
}
.dlg-acts .btn{ width: 100%; }

/* --------------------------------------------------------------------------
   7. 动效降级（v4-full 末尾 1106-1108 只压 transition-duration，压不住 animation）
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce){
  .modal-card{ animation: none; }
  .modal-close, .qty-btn{ transition: none; }
}

/* --------------------------------------------------------------------------
   8. 导入向导最后一步的按钮排 .modal-actions
   --------------------------------------------------------------------------
   js/app.js:2884 这段属于 js/app.js:2726-2727 的 function openMappingModalLegacy(ctx)，
   是留底代码：js/app.js:2171 的注释写着旧的「气泡式」实现「不再调用」，
   全仓 js/** grep openMappingModalLegacy 只有 2727 那一处定义、没有任何调用点
   —— 也就是说真站现在不会渲染 .modal-actions。
   真正渲染的底部按钮排是导入族的 .wz-foot（活路径 js/app.js:2391 openMappingWizard，
   由 js/app.js:2723 调用），人家已经在 out/tmp/v4-mech-import.css:644-674 写好了皮
   （display:flex + gap:var(--sp-2) + flex-wrap + border-top + 三个按钮 min-width:108px），
   所以这一节不是在补活缺陷，只是给留底代码顺手补皮（类名通用，无副作用）。
   它落在 .modal-card.imp-card > .modal-body 里：out/tmp/v4-mech-import.css:13-23 把
   .imp-card / .imp-wiz-card 的内边距归零、让我的 .modal-body 出 16px 内边距，
   所以这里不用再补左右/下边距。js/app.js:1760 / :1779 那两处同名的 .modal-actions 在
   showUnknownTableDialog 里，同样是死代码（js/app.js:1746-1747 自述 v55 起不再调用）。
   老表的意图：css/styles.css:1035-1036（display:flex;gap:10px + .btn{flex:1}）、
   css/styles.css:1503（.imp-card .modal-actions{margin-top:14px}）。
   改前实测 out/tmp/probe-suspects-out.json：display:block、gap:normal，
   两个按钮 64px / 94px 不等宽、间距 0（32+64=96 紧贴）；改后 158/158、间距 10px。
   10px / 14px 是老表的原值，v4 间距阶（4/8/12/16/24/32）上没有这两档，故按原值保留。
   -------------------------------------------------------------------------- */
.modal-actions{
  display: flex;
  align-items: center;
  gap: 10px;
}
.modal-actions .btn{
  flex: 1 1 0;
  min-width: 0;
}
.modal-card .modal-actions{ margin-top: 14px; }


/* ===== 片段：v4-mech-sheet.css ===== */
/* ============================================================================
   一喵效期管家 · v4 皮肤片段：筛选抽屉（点「≤ N」弹出来的那个滑选层）
   ----------------------------------------------------------------------------
   为什么单独一片：
   1) 真站的标记是「兄弟节点」——`#sheet-mask` 是个空的遮罩 div，`#inventory-sheet`
      是它的兄弟（app-v4.html:179-186；老表靠 `#inventory-sheet{position:fixed}` 自己贴底，
      见 css/styles.css 里的抽屉规则）。v4 的 `.sheet`（v4-full.css:1070）只是弹层
      **内部**的白卡（width:100%、static），贴底交给 `.sheet-mask{display:flex;align-items:flex-end}`
      —— 而我们的 mask 里没有 sheet，所以 v4 下这张卡会掉进文档流、挤在列表中间。
      这一片把 `#inventory-sheet` 自己做成贴底浮层（也保住 tools/verify-inventory.js:117
      「抽屉是真的底部浮层（position:fixed）」）。
   2) `.sheet-head` / `.sheet-foot` / `.sheet-done` 这三个零件在 v4-full.css 里**没有任何规则**
      （v4 只有 `.sheet-title`，而真站标记用的是 `.sheet-head b`）→ 不补就是裸控件。
   设计依据：v4 的标题一律左对齐块级标题（`.sheet-title{font-size:var(--fs-h1);font-weight:700;
   margin:0 0 var(--sp-2)}`，v4-full.css:1078），v4 的主按钮一律整宽胶囊（`.btn.btn-primary.btn-block`）。
   ========================================================================== */

#inventory-sheet{
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 41;                     /* 遮罩 40 < 抽屉 41 < 轻提示 50（toast 要能盖住） */
  max-width: 640px;
  margin: 0 auto;
  padding: var(--sp-4);
  padding-bottom: calc(var(--sp-4) + env(safe-area-inset-bottom, 0px));
  background: var(--card);
  border-radius: var(--r-md) var(--r-md) 0 0;
  box-shadow: var(--shadow-2);
  animation: v4-sheet-up .16s ease-out;
}

@keyframes v4-sheet-up{
  from{ transform: translateY(14px); }
  to{ transform: none; }
}

@media (prefers-reduced-motion: reduce){
  #inventory-sheet{ animation: none; }
}

/* 标题：v4 的块级左对齐标题（同 .sheet-title） */
#inventory-sheet .sheet-head{
  padding: 0 0 var(--sp-2);
}

#inventory-sheet .sheet-head b{
  font-size: var(--fs-h1);
  font-weight: 700;
  color: var(--text);
}

/* 底部动作区：v4 主按钮整宽 */
#inventory-sheet .sheet-foot{
  margin-top: var(--sp-4);
}

#inventory-sheet .sheet-done{
  width: 100%;
  min-height: 48px;
  padding: 0 var(--sp-4);
  border: 0;
  border-radius: var(--r-pill);
  background: var(--primary);
  color: #FFFFFF;
  font-family: inherit;
  font-size: var(--fs-body);
  font-weight: 700;
  cursor: pointer;
  transition: background .15s ease;
}

#inventory-sheet .sheet-done:active{
  background: var(--primary-2);
}

@media (prefers-reduced-motion: reduce){
  #inventory-sheet .sheet-done{ transition: none; }
}

/* ==========================================================================
   § v94 · 2026-10-08 「可爱，但要像个成熟产品」
   --------------------------------------------------------------------------
   口径（店主 2026-10-08）：「还是要可爱一点，感觉像很成熟的产品。」
   → 不换气质（米白纸感小铺 + 猫 + 玫瑰粉全部保留），换的是**精度**：
     ① 字号梯度从 2.2 倍拉到 3 倍以上，「还剩几天」变成真正的大字（等宽数字）
     ② 用色逻辑收敛：一个强调色（玫瑰）只在"要做的事/当前选中"上出现；
        四个状态色只在状态位出现，不做装饰性大面积填充
     ③ 间距回 8pt 网格；圆角分三档（18/14/10），不再到处 16px
     ④ 结构靠"白卡 × 米白地"的面差 + 发丝线，不再靠每张卡一个弥散阴影
     ⑤ 动效有显式时长/缓动，且全部尊重 reduced-motion
   纪律（一条都没破）：只追加覆盖、不改任何旧 id/class/data-*、不删旧规则。
   回退：把本段整体删掉即回到 v93 的观感。
   ========================================================================== */

/* —— ① 令牌 —— */
:root{
  /* 字号档位（**真实生效值**）：nano 11 / badge 12 / cap 14 / body 15 / h2 20 / h3=h1 23 / hero 27 / display 32
     梯度 32÷11 = 2.9x（旧表是 12/26 = 2.2x）。--fs-cap 保持 14px 不动：产品口径「正文 ≥14px」。
     ⚠️ 别再在这里写 `--fs-h3`：本文件 1464 行那条 `body[data-ui="v4"]` 兼容映射（特异性 0,1,1）
        把 `--fs-h3` 钉成了 `var(--fs-h1)` = 23px，写在 `:root`（0,1,0）里会被它盖掉 —— **加了等于没加**。
        2026-10-08 踩过：以为 .item-name 算的是 17px，实测浏览器算出来 23px。
        要用 17px 就写具体数值，别造一个会被钉住的别名。 */
  --fs-nano:    11px;
  --fs-h2:      20px;
  --fs-h1:      23px;
  --fs-hero:    27px;
  --fs-display: 32px;

  /* 圆角三档（旧表只有一个 --r-md:16px 通吃） */
  --r-xs: 6px;
  --r-sm: 10px;
  --r-md: 14px;
  --r-lg: 18px;

  /* 发丝线 + 台阶面（结构来源） */
  --line:      #EBE0D6;
  --line-2:    #DCCEC2;
  --surface-2: #F5EDE5;
  --ink-soft:  #8B7E78;
  --hairline:  1px solid var(--hairline-c);

  /* 阴影只留两级：卡片一律不用，只给"浮层"（会员卡 / 弹层 / 底栏 / toast） */
  --shadow-1: 0 1px 2px rgba(74,54,46,.05);
  --shadow-2: 0 14px 30px -16px rgba(74,54,46,.30);

  /* 动效参数（每个动画都要有显式时长 + 缓动） */
  --ease:    cubic-bezier(.2,.8,.25,1);
  --t-fast:  140ms;
  --t:       200ms;
  --t-slow:  280ms;

  /* —— 换皮槽位（2026-10-08：把原先写死的色收进 token，换配色=只改这一段） ——
     每个槽位的含义：
       --bg-glow       地面顶部那层暖光（第二层质感的第 1 层）
       --primary-soft-2 强调浅底的第二档（问候区渐变的中段）
       --member-a/-b   会员卡渐变的起止色
       --danger-soft/-line 危险按钮的软底与描边
       --hairline-c    发丝线颜色（深色皮要反过来用浅线）
       --header-bg / --tabbar-bg 顶栏与底栏的半透明底
       --on-primary    压在强调色上的文字色（浅色皮=白；深色皮=近黑）
       --primary-glow  焦点/选中态的柔光圈
       --primary-shadow 主按钮与选中胶囊的投影 */
  --bg-glow:           #FFFBF7;
  --primary-soft-2:    #FAEAEE;
  --member-a:          #F6E2E6;
  --member-b:          #E6C6CE;
  --danger-soft:       #F9E9E7;
  --danger-line:       #EDD1CD;
  --hairline-c:        rgba(43,36,34,.055);
  --header-bg:         rgba(248,241,234,.9);
  --tabbar-bg:         rgba(255,253,251,.95);
  --on-primary:        #FFFFFF;
  --primary-glow:      rgba(180,106,126,.12);
  --primary-shadow:    0 8px 18px -12px rgba(149,80,101,.85);
  --avatar-bg:         rgba(255,255,255,.74);
  --avatar-ring:       rgba(255,255,255,.9);
}

/* —— ② 地面：两层质感（顶部暖光 + 一层极淡的纸纹），不再是纯色平铺 —— */
html{ background: var(--bg); }
body{
  background-color: var(--bg);
  background-image:
    radial-gradient(120% 380px at 50% -140px, var(--bg-glow) 0%, rgba(255,255,255,0) 72%),
    repeating-linear-gradient(180deg, var(--hairline-c) 0 1px, rgba(43,39,36,0) 1px 4px);
  background-repeat: no-repeat, repeat;
  background-attachment: scroll;
}

/* —— ③ 顶栏：不再是"一条浮起来的空圆角条" —— */
.app-header{
  min-height: 56px;
  border-radius: 0;
  border-bottom: 1px solid var(--line);
  background: var(--header-bg);
}
.app-title{ font-size: 16px; font-weight: 800; letter-spacing: .01em; }
.app-header #btn-bell{ width: 40px; height: 40px; min-width: 40px; min-height: 40px; border-color: var(--line); }

/* —— ④ 问候区：主句是"人话"，不再是加载占位；猫缩小一档别抢戏 —— */
.inv-hello{
  min-height: 96px;
  padding: 14px var(--sp-4);
  border-radius: var(--r-md);
  background: linear-gradient(120deg, var(--primary-soft), var(--primary-soft-2) 62%, var(--primary-soft));
  border: 1px solid var(--primary-line);
}
.inv-hello-k{ font-size: var(--fs-badge); letter-spacing: .12em; text-transform: none; opacity: .85; }
.inv-hello-h{
  margin-top: 2px;
  font-size: var(--fs-hero);
  font-weight: 800;
  line-height: 1.22;
  letter-spacing: -.01em;
  text-wrap: pretty;
}
.inv-hello-s{ margin-top: 4px; font-size: var(--fs-cap); color: var(--text-2); }
/* 子行由 js 写成空串时不留一条空缝 */
.inv-hello-s:empty{ display: none; }
.inv-hello-cat{ width: 56px; height: 56px; opacity: .82; }
.inv-hello-cat svg{ width: 56px; height: 56px; }

/* —— ⑤ 搜索：干掉浏览器原生的第二个清除按钮（原来是蓝色 ✕ + 自己的 ×，两个并排） —— */
.search-box input[type="search"]::-webkit-search-cancel-button,
.search-box input[type="search"]::-webkit-search-decoration,
.search-box input[type="search"]::-webkit-search-results-button{
  -webkit-appearance: none;
  appearance: none;
  display: none;
}
.search-box{ min-height: 46px; border-radius: var(--r-md); border-color: var(--line-2); }
.search-box:focus-within{ border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-glow); }

/* —— ⑥ 筛选条：5 个胶囊在 390 宽放不下（第 4 个原来被切一半）→ 允许换行，全部看得见 —— */
#inventory-chips{ margin-top: var(--sp-3); }
#inventory-chips .chips{
  flex-wrap: wrap;
  overflow-x: visible;
  width: 100%;
  gap: var(--sp-2);
  padding-bottom: 0;
}
#inventory-chips .chip{
  min-height: 38px;
  padding: 0 var(--sp-3);
  font-size: var(--fs-cap);
  border-color: var(--line-2);
  font-variant-numeric: tabular-nums;
}
#inventory-chips .chip b{ font-size: var(--fs-badge); }
#inventory-chips .chip.active{ box-shadow: var(--primary-shadow); color: var(--on-primary); }
#inventory-chips .chip.active b{ color: var(--on-primary); opacity: .82; }
/* 「批量」是模式不是筛选：用虚位把它和四个筛选胶囊分开 */
#inventory-chips .chip-act{ margin-left: 2px; border-style: dotted; }

/* —— ⑦ 货卡：主角是"还剩几天"那三个数字 —— */
#inventory-list{ gap: var(--sp-2); }
.item-card{
  min-height: 108px;
  padding: var(--sp-3) var(--sp-4) var(--sp-3) calc(var(--sp-4) + 8px);
  border: var(--hairline);
  border-radius: var(--r-md);
  box-shadow: none;
}
.item-card::before{ top: 12px; bottom: 12px; width: 3px; border-radius: 0 3px 3px 0; }
.item-head .item-icon{ width: 30px; height: 30px; border-radius: var(--r-sm); }
.item-name{ font-size: var(--fs-h3); font-weight: 700; line-height: 1.32; letter-spacing: -.005em; text-wrap: pretty; }
.item-sub{ margin-top: 2px; }
.badge, .batch-badge{ border-radius: var(--r-xs); font-size: var(--fs-badge); font-weight: 700; letter-spacing: .01em; }
.item-info{ font-size: var(--fs-cap); gap: var(--sp-4); }
.item-info .info-line b{ font-variant-numeric: tabular-nums; }

/* 天数：把胶囊底去掉，改成"已过期 <大数字> 天"的价签式排版（文案一个字不动，
   仍是 Expiry.daysText 那一句，verify-months.js 量的 /剩余 1245 天/ 照样成立） */
.item-days{
  background: transparent;
  padding: 0;
  min-height: 0;
  border-radius: 0;
  gap: 3px;
  align-items: baseline;
  font-size: var(--fs-badge);
  font-weight: 700;
  letter-spacing: .01em;
}
.item-days b{
  font-size: var(--fs-display);
  font-weight: 800;
  line-height: .95;
  letter-spacing: -.035em;
  font-variant-numeric: tabular-nums;
}
.item-days.expired{ color: var(--st-expired); background: transparent; }
.item-days.soon{ color: var(--st-soon); background: transparent; }
.item-days.ok{ color: var(--st-ok); background: transparent; }
.item-exp{ font-size: var(--fs-badge); color: var(--text-2); font-variant-numeric: tabular-nums; }
.item-months{ font-size: var(--fs-badge); color: var(--text-2); }

/* —— ⑧ 「加一件」/「上传卡」：不再用虚线（虚线在本项目里已经滥用到第三处） —— */
.inv-add-row{
  min-height: 50px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  background: var(--card);
  color: var(--primary-2);
  font-size: var(--fs-cap);
  font-weight: 700;
}
.inv-add-row:active{ background: var(--primary-soft); border-color: var(--primary-line); }
.upload-zone{
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  background: var(--card);
  padding: var(--sp-5) var(--sp-4);
}
.upload-zone:active{ border-color: var(--primary-line); background: var(--primary-soft); }
.upload-icon{ width: 64px; height: 64px; border-radius: var(--r-md); }
.upload-icon svg{ width: 30px; height: 30px; }
.upload-title{ font-size: var(--fs-h1); font-weight: 800; letter-spacing: -.01em; }
.upload-note{ color: var(--ink-soft); }

/* —— ⑨ 卡片与列表行：去掉"每张卡一个弥散阴影"，结构交给发丝线 —— */
.card{
  border: var(--hairline);
  border-radius: var(--r-md);
  box-shadow: none;
}
.card-title{ font-size: var(--fs-badge); letter-spacing: .14em; color: var(--ink-soft); font-weight: 800; }
.list-item{ font-size: var(--fs-body); }
.list-value{ font-variant-numeric: tabular-nums; }
.list-arrow{ color: var(--line-2); }

/* —— ⑩ 按钮：补 hover / 按压 / 焦点环（键盘可达） —— */
.btn{ transition: transform var(--t-fast) var(--ease), background var(--t-fast), border-color var(--t-fast), box-shadow var(--t-fast); }
.btn:active{ transform: translateY(1px) scale(.985); }
.btn-primary{ color: var(--on-primary); box-shadow: var(--primary-shadow); }
.btn-primary:active{ color: var(--on-primary); }
.btn-ghost{ border-color: var(--line-2); color: var(--primary-2); }
.btn-danger{ background: var(--st-expired-bg); border-color: var(--st-expired-bg); }
@media (hover: hover){
  .btn-primary:hover{ background: var(--primary-2); border-color: var(--primary-2); }
  .btn-ghost:hover{ background: var(--primary-soft); border-color: var(--primary-line); }
  .item-card:hover{ border-color: var(--line-2); }
  .list-item:hover{ background: var(--surface-2); }
  .tab-btn:hover{ color: var(--primary-2); }
}
:focus-visible{ outline: 2px solid var(--primary); outline-offset: 2px; border-radius: var(--r-xs); }
.search-box input:focus-visible, .acc-input:focus-visible{ outline: 0; }

/* —— ⑪ 底栏：选中态收小一圈，别做成一颗大药丸 —— */
.tabbar{ border-radius: var(--r-lg) var(--r-lg) 0 0; border-top: 1px solid var(--line); background: var(--tabbar-bg); }
.tab-btn{ font-size: var(--fs-nano); font-weight: 700; letter-spacing: .04em; gap: 3px; }
.tab-btn.active .tab-icon{ padding: 3px 11px; border-radius: var(--r-sm); }
.tab-btn.active{ color: var(--primary-2); font-weight: 800; }
/* 压在强调色上的文字统一走 --on-primary（深色皮里强调色本身是浅的，白字会读不出来） */
#bell-badge{ color: var(--on-primary); }
#trial-banner .trial-banner-btn{ color: var(--on-primary); }
#account-root .member-badge.acc-badge{ color: var(--on-primary); }
.seg .btn.on{ color: var(--on-primary); }

/* —— ⑫ 会员卡：全站唯一"浮起来"的卡（头像圈修好 —— 注入样式给的是老深色主题的琥珀色，
      在浅粉渐变卡上等于看不见，店主看到的是一个飘着的"苏"字） —— */
#account-root .member-card{
  border-radius: var(--r-lg);
  border: 1px solid var(--primary-line);
  background: linear-gradient(135deg, var(--member-a) 0%, var(--primary-soft) 46%, var(--member-b) 100%);
  box-shadow: var(--shadow-2);
}
#account-root .acc-avatar{
  background: var(--avatar-bg);
  color: var(--primary-2);
  box-shadow: inset 0 0 0 1px var(--avatar-ring);
  font-size: 26px;
}
#account-root .member-name{ font-size: var(--fs-h3); font-weight: 800; letter-spacing: -.01em; }
#account-root .member-meta{ font-size: var(--fs-badge); }
#account-root .member-badge.acc-badge{ background: var(--primary-2); border-radius: var(--r-xs); }
.member-actions .btn{ min-height: 46px; }
/* 会员卡右上角那片装饰水印：旧规则在 ::after 里塞了一个 84px 的「会员」，
   浅色下很脏；而且试用版卡上印着"会员"本身自相矛盾 → 直接不画。 */
#account-root .member-card::after{ content: none; }
/* 头像与昵称同一行（旧 CSS 里 .member-head 没有规则，它是块级 div，
   后面那个 .member-name 也是块级 → 头像被挤到单独一行，就是店主看到的那个"飘着的苏字"） */
#account-root .member-head{ display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-2); }
#account-root .member-head .member-name{ margin-top: 0; }
#account-root .acc-avatar-wrap{ width: 48px; height: 48px; flex: none; }
#account-root .acc-avatar{ width: 48px; height: 48px; font-size: 20px; border-radius: 50%; }

/* —— ⑬ 危险区 / 帮助卡：危险动作要有"危险"的样子，帮助卡补上图标位 —— */
.danger-zone{ margin-top: var(--sp-5); }
.danger-zone .btn-danger{ min-height: 52px; background: var(--danger-soft); border-color: var(--danger-line); color: var(--st-expired); }
.danger-hint{ text-align: left; font-size: var(--fs-cap); color: var(--text-2); line-height: 1.6; padding: 0 var(--sp-1); margin-top: var(--sp-3); }
#view-account .list-item{ min-height: 54px; }

/* —— ⑭ 试用条 / 门禁卡 —— */
#trial-banner{ border-radius: var(--r-md); border-color: var(--primary-line); }
.gate-card{ border-radius: var(--r-lg); box-shadow: var(--shadow-2); }
.gate-sub-line{ display: block; margin-top: 3px; color: var(--primary-2); font-weight: 700; }
.gate-title{ letter-spacing: -.01em; }

/* —— ⑮ 登录 / 注册卡：字段等宽（原来密码框旁边那个按钮把它挤窄了，三格不对齐） —— */
#auth-slot .acc-card{ border-radius: var(--r-lg); padding: var(--sp-5) var(--sp-4); }
#auth-slot .acc-title{ font-size: var(--fs-h1); font-weight: 800; letter-spacing: -.01em; }
#auth-slot .acc-label{ font-size: var(--fs-cap); color: var(--text-2); }
#auth-slot .acc-input{ min-height: 48px; border-radius: var(--r-sm); border-color: var(--line-2); }
#auth-slot .acc-pwd-toggle{
  min-height: 48px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line-2);
  background: var(--surface-2);
  color: var(--text-2);
  font-size: var(--fs-cap);
  font-weight: 700;
}
#auth-slot .acc-or{ color: var(--ink-soft); font-size: var(--fs-badge); }
#auth-slot .acc-or:before, #auth-slot .acc-or:after{ background: var(--line); }
#auth-slot .acc-link{ font-weight: 700; }

/* —— ⑯ 导入向导：第 1 步的两个大卡与下拉是同一个选择 → 有卡就不画下拉（真控件仍在 DOM 里） —— */
#imp-scr-calc.has-algo-cards .imp-q-sr{ display: none; }
.wz-opt{ border-radius: var(--r-md); }
.wz-opt.on{ border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-glow); }
.wz-opt-s{ color: var(--text-2); }
.wz-fmeta, .imp-meta{ font-variant-numeric: tabular-nums; }
.modal-card{ border-radius: var(--r-lg) var(--r-lg) 0 0; }

/* —— ⑰ 商品详情：字号分层，别和标签一样大 —— */
.detail-name{ font-size: var(--fs-h2); font-weight: 800; letter-spacing: -.01em; }
.detail-label{ color: var(--text-2); }
.detail-value{ font-variant-numeric: tabular-nums; }
.modal-title{ font-size: var(--fs-h3); font-weight: 800; }
.modal-close{ border-color: var(--line); }
/* 2026-10-08：详情分两层 —— 上面 4 行是"她会问的"，源表原样收进这个折叠（默认收起）。
   折叠头做成一行低调的文字按钮（不是第三颗按钮），点开才展开。 */
.detail-more{
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  min-height: 48px;
  margin-top: var(--sp-1);
  padding: 0 2px;
  border: 0;
  border-top: 1px solid var(--line);
  background: transparent;
  color: var(--primary-2);
  font-size: var(--fs-cap);
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  text-align: left;
}
.detail-more-n{ color: var(--ink-soft); font-size: var(--fs-badge); font-weight: 400; }
.detail-more-chev{ margin-left: auto; color: var(--line-2); transition: transform var(--t-fast) var(--ease); }
.detail-more.open .detail-more-chev{ transform: rotate(90deg); }
.detail-raw[hidden]{ display: none; }
.detail-raw{
  margin-top: var(--sp-1);
  padding: 0 var(--sp-3);
  background: var(--surface-2);
  border-radius: var(--r-sm);
}
.detail-raw .detail-row{ font-size: var(--fs-cap); }
.detail-raw .detail-label{ color: var(--ink-soft); }

/* —— ⑱ 提醒页：名单优先，设置卡不再独占首屏 —— */
#view-reminders .summary-bar{ border-radius: var(--r-md); }
#view-reminders .al-switch-card{ margin-top: var(--sp-2); }
.group-card{ border-radius: var(--r-md); }
.group-dot{ width: 8px; height: 8px; }
.reminder-name{ font-size: var(--fs-h3); }
.reminder-meta{ font-size: var(--fs-badge); font-variant-numeric: tabular-nums; }
.reminder-days{ border-radius: var(--r-xs); font-size: var(--fs-badge); font-weight: 700; font-variant-numeric: tabular-nums; }
#view-reminders .actions-row{ display: flex; flex-direction: row; gap: var(--sp-2); }
#view-reminders .actions-row .btn{ flex: 1 1 0; min-width: 0; }

/* —— ⑲ 「我的」页各卡：行高与图标统一 —— */
.list-icon{ border-radius: var(--r-sm); }
.my-item .list-label{ font-weight: 700; }

/* —— ㉑ 同一个动作别开四扇门 ——
   实测（2026-10-08 截图）：空店时「传一张表」这一个动作，顾客能看见 4 个入口 ——
     库存页的「＋ 加一件」、空状态里的「上传一张库存表」、「添加」tab 里的「＋ 加一件」+ 大上传卡。
   每屏只留**一个**主入口：库存页空态留按钮（最显眼），添加页留大上传卡（本来就是唯一主角）。 */
#view-inventory:has(#inventory-empty:not(.hidden)) .inv-add-row{ display: none; }
#view-upload .add-pad{ display: none; }

/* —— ⑳ 动效：屏切换只做一次很短的淡入（160ms、只动 opacity，不碰几何，
       免得诊断脚本在动画中间量到错的数字）；卡片与浮层各自显式时长 —— */
@keyframes ymqViewIn{ from{ opacity: 0 } to{ opacity: 1 } }
.view.active{ animation: ymqViewIn 160ms var(--ease) both; }
@media (prefers-reduced-motion: reduce){
  .view.active{ animation: none; }
}
.toast{ border-radius: var(--r-md); }
.sheet{ border-radius: var(--r-lg) var(--r-lg) 0 0; }

/* ==========================================================================
   § v97 · 支付宝一键登录按钮（店主 2026-10-08 看线上截图后提的）
   「这个按钮还是要鲜艳一点也不要挨得太近了」
   --------------------------------------------------------------------------
   改之前：它是 `btn btn-ghost` —— 白底 + 1px 玫瑰描边 + 玫瑰字，是整屏最"安静"的元素；
          而且 `.lg-outer` 上外边距是 0，跟上面登录卡之间只隔了卡片自己的 16px 内边距，
          视觉上像贴在卡底边上（实测卡底边到按钮顶边 = 0px）。
   改之后：① 实心**支付宝蓝 #0B6BCB**（店主 2026-10-08 从四版里选定这一版；白字 5.28:1 达标）
              —— 一眼认得出"这是支付宝"，比玫瑰系更"鲜艳"；
          ② 52px 高 + 17px 字号（和上面「登录」按钮不打架）+ 一点蓝调投影；
          ③ `.lg-outer` 上边距 16px → 卡与按钮的视觉间距 16 → 32px；
          ④ 图标与文字间距 8 → 10px，图标 22 → 24px。
   为什么不是品牌蓝 #1677FF：它配白字只有 4.10:1（15–17px 粗体要 ≥4.5），压深到 #0B6BCB 才 5.28:1。
   四版对照图：out/tmp/shots/sheet-alipay-btn.png（`node out/tmp/preview-alipay-btn.js` 可重渲）。
   回退：把本段整体删掉即回到"白底描边 + 0 间距"。
   ========================================================================== */
#auth-slot .lg-outer{ margin: var(--sp-4) var(--sp-4) 0; }
#auth-slot .acc-ali-btn{
  min-height: 52px;
  background: #0B6BCB;
  border: 1px solid #0B6BCB;
  color: var(--on-primary);
  /* 写具体数值而不是 var(--fs-h3)：那个别名被 body[data-ui="v4"] 钉成 23px（见 §v94 ① 的说明），
     实测按钮字会变成 23px、比上面「登录」按钮(15px)大出一大截，两颗按钮就不像一家人了。 */
  font-size: 17px;
  box-shadow: 0 10px 22px -14px rgba(11,107,203,.95);
}
#auth-slot .acc-ali-btn:active{ background: #0959AA; border-color: #0959AA; }
#auth-slot .acc-ali-ic{ margin-right: 10px; }
#auth-slot .acc-ali-ic svg{ width: 24px; height: 24px; }


/* ============================================================
   2026-10-10 追加（生效位置修正）
   背景：js/account.js 的 injectStyle() 在 v4 模式下**故意不注入**那些 acc-* 老规则
        （见该函数注释），所以规则必须写在**这里**才生效。
   这三条是：
     ① 手机号+验证码那一块的分隔线
     ② 「发送验证码」按钮样式
     ③ 会员卡里「点头像换一个」——原来继承了大标题的 23px 且和昵称黏成一行 ✗ → 单独成行 12px
   ============================================================ */
.acc-sms{margin-bottom:14px;padding-bottom:14px;border-bottom:1px solid var(--border,#EBE0D6);}
.acc-sms-send{flex:0 0 auto;white-space:nowrap;background:transparent;border:1px solid var(--border-strong,#DCCEC2);color:var(--primary,#A85F73);border-radius:10px;padding:0 12px;font-size:13px;height:40px;font-weight:600;}
.acc-sms-send:disabled{opacity:.5;}
.acc-avatar-hint{display:block;font-size:12px;line-height:1.5;color:var(--text-2,#6F6360);margin-top:2px;font-weight:400;}
/* 2026-10-10：筛选条本该**横向滚动**（.chips 的滚动条样式本来就有），但实际会换行成两行 ✗。
   这里显式补 nowrap + overflow-x，让它永远一行、超出可横向滑。 */
#inventory-chips .chips{flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;}
/* ============================================================
   2026-10-10 店主定案 · 「我的」页排版修正
   根因：.my-item 是 <button>，默认不撑满卡片 → 行只有 220px 宽（卡片 382）
        ⇒ ① 行没到右边，"线"只有一小截  ② 右侧小字与箭头卡在 220px 处，到不了最右
   所以：先把行撑满，再把"小字 + 箭头"推到最右。
   ============================================================ */
#view-account .my-item{width:100%;box-sizing:border-box;display:flex;align-items:center;gap:10px;}
#view-account .my-item .my-label{flex:0 1 auto;margin-right:auto;text-align:left;}
#view-account .my-item .my-value{flex:0 0 auto;text-align:right;}
#view-account .my-item .my-arrow{flex:0 0 auto;}
/* 分组小标题下的线**画满整行** */
#view-account .my-cap{border-bottom:1px solid var(--border,#EBE0D6);padding-bottom:6px;margin:16px 0 8px;}
#view-account .my-cap:first-of-type{border-bottom:0;padding-bottom:0;}
/* 账号卡里的「改昵称 / 改密码」也推到最右 */
#view-account .acc-row .acc-link{margin-left:auto;flex:0 0 auto;}
/* 2026-10-10 补充：上面那组只写了 .my-item，"帮助"卡的行用的是别的类 → 统一到两者都覆盖。
   规则：行撑满卡片；标签占满中间（把右侧的小字/箭头/链接顶到最右）。 */
#view-account .card .list-item{width:100%;box-sizing:border-box;display:flex;align-items:center;gap:10px;}
#view-account .card .list-item .list-label{flex:0 1 auto;margin-right:auto;text-align:left;}
#view-account .card .list-item .list-value{flex:0 0 auto;text-align:right;}
#view-account .card .list-item .list-arrow{flex:0 0 auto;}
#view-account .card .list-item .acc-link{margin-left:auto;flex:0 0 auto;}
/* 2026-10-10 店主定案：**去掉分组小标题**（"数据""这个应用"被划掉 ✗），
   改用"间距 + 一条通到两边的细线"来分组 —— 更干净，也不占一行。 */
#view-account .my-item.my-grp{margin-top:14px;border-top:1px solid var(--border,#EBE0D6);}
/* 2026-10-10：「提前几天提醒」的档位按钮（原来是滚轮 ✗ 店主说太 low）
   3 列网格；未选=白底灰边，选中=实心玫瑰白字。 */
.th-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;padding:4px 0;}
.th-chip{appearance:none;border:1px solid var(--border-strong,#DCCEC2);background:#fff;color:var(--text,#2B2422);
  border-radius:12px;min-height:42px;font-size:15px;font-weight:600;font-family:inherit;cursor:pointer;}
.th-chip:active{transform:scale(.97);}
.th-chip.on{background:var(--primary,#A85F73);border-color:var(--primary,#A85F73);color:#fff;}
/* 2026-10-10 店主报「这个小图标，位置都没对齐」—— 实测头像圈里的猫头**往上偏 6px** ✗
   根因：svg 是 display:inline + vertical-align:baseline，被行盒按基线顶高。
   修法：圈改成 inline-flex 居中、行高 1；svg 改 block。
   （这个规则同时管"汉字首字"那种文字头像，文字也会跟着居中 ✓） */
.acc-avatar{display:inline-flex;align-items:center;justify-content:center;line-height:1;overflow:hidden;}
.acc-avatar svg{display:block;}
/* 2026-10-10 店主：「这个要跟到左侧」——
   值的默认位置改成**紧跟标签（左边）**（原来被推到最右、右边又没有东西陪它，看着"飘" ✓）。
   只有"带箭头（可展开）"的行，值才贴最右、和箭头作伴（iOS 那种"值+chevron 成对"的读法 ✓）。 */
#view-account .my-item .my-label,
#view-account .card .list-item .list-label{margin-right:0;}
#view-account .my-item .my-value,
#view-account .card .list-item .list-value{margin-left:8px;text-align:left;}
#view-account .my-item:has(.my-arrow) .my-value,
#view-account .card .list-item:has(.list-arrow) .list-value{margin-left:auto;text-align:right;}
#view-account .my-item .my-arrow,
#view-account .card .list-item .list-arrow{margin-left:auto;}
/* 2026-10-10b 钉住：上面那组被别的规则压住了（提醒设置的值没贴到最右，实测距右 109px ✗）。
   这两条用 !important 明确"带箭头的行 = 值贴右、箭头最右"，不含糊。 */
#view-account .my-item:has(.my-arrow) .my-value,
#view-account .card .list-item:has(.list-arrow) .list-value{margin-left:auto !important;text-align:right !important;}
#view-account .my-item .my-arrow,
#view-account .card .list-item .list-arrow{margin-left:auto !important;}
/* 2026-10-10c 店主：「这些小字放在最右侧」（指"提醒设置 6 个月"「关于 v1.0」那种带箭头的行）
   上一版用 margin 推不动 —— 值外面还套了一层，位置由外层决定。
   这次换成**让标签吃掉剩余空间**（flex:1）：[图标][标签(撑开)][小字][箭头] → 小字和箭头自然一起贴最右 ✓
   而且**只对带箭头的行**生效：不带的（昵称 / 导出表格）仍是"值紧贴标签" ✓ 两边都满足。 */
#view-account .my-item:has(.my-arrow) .my-label,
#view-account .card .list-item:has(.list-arrow) .list-label{flex:1 1 auto;margin-right:0;}
#view-account .my-item:not(:has(.my-arrow)) .my-label,
#view-account .card .list-item:not(:has(.list-arrow)) .list-label{flex:0 0 auto;}
#view-account .my-item:has(.my-arrow) .my-value,
#view-account .card .list-item:has(.list-arrow) .list-value{margin-left:auto;text-align:right;}
/* 2026-10-10d 店主画了三个黄箭头：「6 个月」「一份 Excel」「v1.0」**都要贴最右** ✓
   （上一版我把"一份 Excel"也放左边了 —— 那是按"昵称要左贴"推的，推错了 ✗）
   最终规则：**所有行的值都贴最右**；只有「昵称」那行例外（店主明确说"要跟到左侧" ✓）。 */
#view-account .my-item .my-label,
#view-account .card .list-item .list-label{flex:1 1 auto;margin-right:0;}
#view-account .my-item .my-value,
#view-account .card .list-item .list-value{margin-left:auto;text-align:right;}
/* 例外：昵称行（它带「改昵称」按钮）—— 值紧贴标签 */
#view-account .card .list-item:has(.acc-nick-edit) .list-label{flex:0 0 auto;}
#view-account .card .list-item:has(.acc-nick-edit) .list-value{margin-left:8px;text-align:left;}
/* 2026-10-10e 店主：顶部「一喵效期管家」要**居中显示** ✓
   注意：右边还有铃铛，不能靠 flex 平分（那样标题是在"左边空位"里居中、视觉偏左 ✗）。
   做法：标题绝对定位到整条头部的正中；铃铛用 margin-left:auto 顶到最右。 */
.app-header{position:relative;}
.app-header .app-title{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);white-space:nowrap;}
.app-header #btn-bell{margin-left:auto;}
/* 2026-10-10f 修正（上一条 10-10e 把标题自身绝对定位 → 容器塌成 0 宽、flex 只剩铃铛、铃铛跑到左边 ✗）
   正确做法：**移动标题的容器**，剩下的兄弟（铃铛）用 margin-left:auto 顶到最右。 */
.app-header .app-title{position:static;top:auto;left:auto;transform:none;}
.app-header .app-head-left{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);white-space:nowrap;}
.app-header>*:not(.app-head-left){margin-left:auto;}
/* 2026-10-10：登录卡上的「忘记密码？」入口与展开区（重建） */
.acc-forgot-entry{text-align:right;margin:6px 0 2px;}
.acc-forgot{margin-top:6px;padding:14px;border-radius:14px;background:var(--surface-2,#FBF6F2);border:1px solid var(--border,#EBE0D6);}
.acc-forgot .acc-hint{margin:0 0 10px;}
.acc-forgot .acc-forgot-tip{margin-top:12px;}
.acc-forgot .acc-err-bad{color:var(--st-expired,#93394A);}
/* 2026-10-10：补回「忘记密码？」展开区的样式（新类名 .acc-recover；旧类名 .acc-forgot* 的那几条是上一轮留下的） */
.acc-forgot-entry{text-align:right;margin:8px 0 2px;}
.acc-recover{margin-top:6px;padding:14px;border-radius:14px;background:var(--surface-2,#FBF6F2);border:1px solid var(--border,#EBE0D6);}
.acc-recover .acc-hint{margin:0 0 10px;}
.acc-recover .acc-recover-tip{margin-top:12px;}
/* 2026-10-10 续：光把右边改成"只占第一行"还不够 —— 实测「货名」和「2375 天」的顶边差 −23px ✗
   （因为「天数」是个 26px 高的胶囊，货名只有 20px 高，顶边对齐看着反而歪）。
   做法：让货名那一行的内容**和胶囊等高并垂直居中** → 两栏的视觉中线在同一条水平线上 ✓ */
.item-head{align-items:center;min-height:26px;}
.item-name{line-height:1.25;}
/* 2026-10-10 续2：上面那条 align-items:center 之所以没效果 —— .item-head 根本不是 flex ✗。
   补上 display:flex + 居中，让「货名」和右边「天数胶囊」的视觉中线在同一条水平线上 ✓ */
.item-card .item-head{display:flex;align-items:center;min-height:26px;gap:10px;}
/* 2026-10-10 续3：网格项默认会被**拉伸**（align-self:stretch）✗ ——
   所以 .item-head 虽然写了 align-items:center，它自己先被撑到整行高、里面的货名又被居中 → 还是差 23px。
   补 align-self:start：货名那一格和右边"天数胶囊"从同一条线起步 ✓ */
.item-card .item-head{align-self:start;align-items:center;min-height:26px;}
/* 2026-10-10 续4：真凶找到了 —— h3.item-name 带着浏览器默认的 23px 上下外边距 ✗
   （两栏盒子的顶边差其实是 0 ✓，是**文字**被这 23px 顶下去了）→ 清掉外边距：
   文字和右边胶囊齐平 ✓，顺带卡片也不会被白撑高 46px ✓ */
.item-card .item-name{margin:0;}
/* 2026-10-10 店主：「像这种重点提示要用红字」✗→✓
   导入向导里凡是"发现问题、要店主处理"的大标题（缺保质期 / 缺到期日子 / 列名没见过）用危险色 ✓；
   中性的步骤标题（列名对好了…）不上红 —— 全上红就等于没重点 ✗ */
.wz-h1.is-warn{color:var(--st-expired,#93394A);}
/* 2026-10-10 店主：「到期和库位对齐」✓
   做法：让 .item-days-row 用 display:contents —— 它里面的两个元素（天数 / 到期日）
   直接变成卡片的网格项 ✓，于是可以分开摆：
     · 天数   → 第 1 行（和货名同一条水平线 ✓，这是上一轮做的 ✓）
     · 到期日 → **第 3 行**（和「库位 / 数量」同一条水平线 ✓，就是这次要的 ✓）
   纯 CSS，不动任何标记 ✓ */
.item-card .item-days-row{display:contents;}
.item-card .item-days{grid-column:2;grid-row:1;justify-self:end;align-self:start;}
.item-card .item-exp{grid-column:2;grid-row:3;justify-self:end;align-self:center;}
/* 2026-10-10 店主：「批次和已过期往中间移动太低了」✓
   → 把「批次 + 已过期」那一行**往上提**：
     · 网格行距压到最小（原来行与行之间留白偏大）
     · 这一行再往上吸一点（负外边距），让它落在货名和底行**中间偏上**的位置 ✓ */
/* 2026-10-10 续：上一条提到 -10px 提过头了 —— 店主说「粘到一起了」✗
   松开：行距给 3px、上提改成 2px ✓（既不粘、也不像原来那么低 ✓） */
.item-card{row-gap:3px;}
.item-card .item-sub{align-self:center;margin-top:-2px;}
.item-card .item-head{align-self:start;}
/* 2026-10-10 店主：「春联 人喵共旺 这字体都可以小一点，又不是给老年人看呢」✓
   → 卡片里各档字号统一收一档（货名最明显）：
       货名 18 → 16，批次牌/徽章 13 → 12，库位/到期日 13 → 12 ✓
     天数（大数字）不动 —— 它是这张卡的"主信号"，再小就扫不到了 ✓ */
.item-card .item-name{font-size:16px;}
.item-card .batch-badge,.item-card .badge{font-size:12px;}
.item-card .info-line,.item-card .item-exp{font-size:12px;}
/* 2026-10-10 店主：「支付宝一键登录放在登录下面，做成快捷登录的感觉」✓ */
.acc-quick-label{font-size:12px;color:var(--text-2,#6F6360);margin:10px 0 6px;text-align:center;}
/* 2026-10-10 店主：「我们需要一个窗口」→ 我的页顶部的消息提示条 + 消息列表样式 */
.acc-msgbar{display:flex;align-items:center;gap:8px;width:100%;box-sizing:border-box;text-align:left;
  background:var(--primary-soft,#F6E3E8);border:1px solid var(--border,#EBE0D6);border-radius:14px;
  padding:12px 14px;margin:0 0 12px;font-family:inherit;font-size:14px;color:var(--text,#3C332F);}
.acc-msgbar-t{flex:1 1 auto;} .acc-msgbar-t b{color:var(--primary,#A85F73);font-size:16px;}
.acc-msgbar-go{color:var(--primary,#A85F73);font-weight:600;}
.msg-item{padding:12px 0;border-bottom:1px solid var(--border,#EBE0D6);}
.msg-item:last-child{border-bottom:0;}
.msg-item.is-new .msg-title:after{content:'新';margin-left:6px;font-size:11px;color:#fff;background:var(--primary,#A85F73);border-radius:999px;padding:1px 6px;vertical-align:2px;}
.msg-title{font-weight:700;font-size:15px;margin-bottom:4px;}
.msg-body{font-size:14px;color:var(--text-2,#6F6360);line-height:1.6;}
.msg-code{margin:8px 0;font-family:ui-monospace,Menlo,monospace;font-size:16px;letter-spacing:.06em;
  background:var(--surface-2,#FBF6F2);border:1px dashed var(--border-strong,#DCCEC2);border-radius:10px;padding:10px;text-align:center;}
.msg-when{font-size:12px;color:var(--text-3,#8B7E78);margin-top:6px;}
/* 2026-10-10 店主：「直接放在顶部让他闪烁之类的」→ 顶部消息条 + 轻微呼吸 + 看完收成细线 */
#acc-msg-slot{position:sticky;top:0;z-index:30;}
.acc-msgbar.is-flash{animation:accMsgPulse 1.8s ease-in-out infinite;}
@keyframes accMsgPulse{0%,100%{box-shadow:0 0 0 0 rgba(168,95,115,.00);transform:translateY(0);}50%{box-shadow:0 0 0 4px rgba(168,95,115,.10);transform:translateY(-1px);}}
.acc-msgbar-ic{font-size:15px;}
.acc-msgline{display:block;width:100%;box-sizing:border-box;background:transparent;border:0;border-bottom:1px solid var(--border,#EBE0D6);
  padding:6px 2px;margin:0 0 8px;font-family:inherit;font-size:12px;color:var(--text-3,#8B7E78);text-align:right;}
@media (prefers-reduced-motion: reduce){.acc-msgbar.is-flash{animation:none;}}