/* ═══════════════════════════════════════════════════════════════════════
   微信AI助手 · 授权后台样式（v2，2026-10-21 改版）

   ## 与 Android 端的关系
   语义色同源（品牌绿 #07C160 / 红 #FA5151），但**底色不同**：后台是长时间盯的
   桌面界面，用 iOS 系统灰阶（#F5F5F7 底 / #FFFFFF 卡），不做装饰性阴影。
   旧版底色 #F3F3F4 的注释写着"暖灰，冷灰盯久了发青"—— 这条保留，只是把暖度调回来。

   ## 两套壳并存，这是刻意的
   - **新壳** `.app > .side + .main`：左侧固定导航 + 主区（授权码 / 批量生成已改版）
   - **旧壳 `.wrap > .top + .card`**：v0.4.92 起**已全部移除** —— 七个页面统一到 `.app`，
     登录页改用 `body.auth`（无侧栏，品牌区居中）。旧壳的 `.row / .field / .note /
     .mini / .dev / .label / .filters / .stats` 因新壳复用而保留，就是下面「通用组件」一节。
   两套共用同一批 token 与组件，因此视觉不会割裂；
   等其余四页迁完，旧壳可以整段删掉。

   ## 响应式三档
   ≥1000 侧栏常驻 · 760~1000 侧栏收成顶栏 · <760 表格变卡片。
   ⚠ 表格只有一套 DOM（8 个子元素），切移动端靠 `grid-column` 重排，
     不要为两种布局各写一套 —— 会重复内容，且必然有一天忘了同步其中一套。
   ═══════════════════════════════════════════════════════════════════════ */

* { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  /* v0.4.80：**色板对齐「相册管家」后台**（火山方舟 Agent Plan 风格）。
     它的灰阶比 iOS 那套更冷、文字更深 —— 同为桌面后台，长时看更稳。
     主色仍用品牌绿，只借它的层级关系，不借它的紫。 */
  --bg:        #f6f8fa;   /* 页面（比卡片更灰一档） */
  --panel:     #ffffff;
  --line:      #e1e3ea;   /* 卡 border */
  --line-soft: #edeef4;   /* 行内浅分割 */
  --text:      #0b0b0f;
  --text2:     #3f3f51;
  --text3:     #737a87;
  --text4:     #afb0c0;
  --green:     #07C160;
  --green-ink: #067A3D;
  --green-soft:#E8F8EF;
  --amber:     #a6661f;
  --amber-soft:#fef3e2;
  --red:       #f53f3f;
  --red-soft:  #fff5f6;
  /* v0.4.91：信息色从相册管家的紫改成**深绿**。
     紫色是它那套的品牌色，本站主色是绿 —— 链接/提示条用紫会像"另一个产品"。
     用比主绿更深的绿，既能与按钮区分，又不跑出绿色主题。 */
  --blue:      #067a3d;
  --blue-soft: #e8f8ef;
  --r: 10px;               /* 卡片圆角。相册管家是 12，本站整体收小一档（用户 2026-10-21）*/
  --shadow: 0 2px 6px rgba(0, 0, 0, .04);
}

html, body { height: 100%; }
body {
  font: 14px/1.55 -apple-system, "Segoe UI", "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--blue); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ══════════════════ 新壳：侧栏 + 主区 ══════════════════ */
.app { display: flex; min-height: 100vh; }

.side {
  width: 216px; flex: none;
  background: var(--panel);
  border-right: 1px solid var(--line);
  display: flex; flex-direction: column;
  position: sticky; top: 0; height: 100vh;
}
/* 品牌块可点回概览（v0.1.10）：做成 <a> 后要去掉链接的下划线与颜色继承 */
.side a.brand {
  padding: 22px 20px 18px; display: flex; align-items: center; gap: 10px;
  text-decoration: none; color: inherit; border-radius: 0;
}
.side a.brand:hover { background: #FAFAFB; text-decoration: none; }
.side a.brand:hover b { color: var(--text); }

.brand { padding: 22px 20px 18px; display: flex; align-items: center; gap: 10px; }
.brand .logo {
  width: 28px; height: 28px; border-radius: 6px; flex: none;
  background: var(--green); color: #fff;
  display: grid; place-items: center;
  font-size: 13px; font-weight: 700; letter-spacing: -.5px;
}
.brand b { font-size: 14px; font-weight: 600; letter-spacing: -.2px; }
.brand small { display: block; font-size: 11px; color: var(--text3); font-weight: 400; margin-top: 1px; }

.nav { padding: 6px 10px; display: flex; flex-direction: column; gap: 2px; }
.nav a {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border-radius: 6px;
  font-size: 13.5px; color: var(--text2); text-decoration: none;
  transition: background .12s, color .12s;
}
.nav a:hover { background: #F7F7F9; color: var(--text); text-decoration: none; }
.nav a.on { background: var(--green-soft); color: var(--green-ink); font-weight: 500; }
.nav a .ic { width: 16px; height: 16px; opacity: .75; flex: none; }
.nav a.on .ic { opacity: 1; }
.nav .grouplabel {
  font-size: 11px; color: var(--text3); letter-spacing: .4px;
  padding: 14px 10px 6px; text-transform: uppercase;
}
.side .foot { margin-top: auto; padding: 14px 16px; border-top: 1px solid var(--line-soft); }
.side .foot button {
  width: 100%; padding: 7px; border-radius: 6px; border: 1px solid var(--line);
  background: #fff; color: var(--text2); font-size: 13px; cursor: pointer;
  font-family: inherit;
}
.side .foot button:hover { background: #FAFAFB; color: var(--text); }

.main { flex: 1; min-width: 0; padding: 26px 32px 60px; }

.head { display: flex; align-items: flex-start; gap: 16px; margin-bottom: 22px; flex-wrap: wrap; }
.head h1 { font-size: 22px; font-weight: 600; letter-spacing: -.4px; }
.head p { font-size: 13px; color: var(--text2); margin-top: 3px; }
.head .right { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* ══════════════════ 通用组件 ══════════════════ */
.btn {
  height: 36px; padding: 0 16px; border-radius: 6px; border: 1px solid var(--line);
  background: #fff; font-size: 13px; cursor: pointer; color: var(--text2);
  white-space: nowrap; font-family: inherit; text-decoration: none;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  transition: background 150ms, border-color 150ms, opacity 150ms;
}
.btn:hover { background: #fbfbfc; border-color: var(--text4); text-decoration: none; }
.btn.primary {
  background: var(--green); border-color: var(--green); color: #fff; font-weight: 500;
}
/* 主按钮的 hover 用**透明度**而不是换色 —— 相册管家就是这样（.amc-btn-primary:hover），
   换色会让"绿色主题"在 hover 时漂到另一个绿。 */
.btn.primary:hover { background: var(--green); border-color: var(--green); opacity: .92; }
.btn.danger { color: var(--red); border-color: #F3C9C9; }
.btn.danger:hover { background: var(--red-soft); }

.search {
  width: 220px; padding: 7px 12px; border-radius: 6px;
  border: 1px solid var(--line); background: #fff; font-size: 13px; color: var(--text);
  font-family: inherit;
}
.search::placeholder { color: var(--text3); }
.search:focus { outline: 2px solid var(--green-soft); border-color: var(--green); }

/* KPI */
.kpis { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; margin-bottom: 20px; }
.kpi {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r); padding: 14px 16px 13px;
  box-shadow: var(--shadow);
}
.kpi .k { font-size: 12px; color: var(--text2); display: flex; align-items: center; gap: 6px; }
.kpi .v {
  font-size: 26px; font-weight: 600; letter-spacing: -.8px; margin-top: 6px;
  font-variant-numeric: tabular-nums;
}
.kpi .s { font-size: 11.5px; color: var(--text3); margin-top: 2px; }
.kpi .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--text3); }
.kpi.warn .v { color: var(--amber); }
.kpi.warn .dot { background: var(--amber); }
.kpi.bad .v { color: var(--red); }
.kpi.bad .dot { background: var(--red); }
.kpi.ok .dot { background: var(--green); }

/* 筛选 chips */
.toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
.chips { display: flex; gap: 6px; flex-wrap: wrap; }
.chip {
  padding: 5px 11px; border-radius: 6px; font-size: 12.5px;
  border: 1px solid var(--line); background: #fff; color: var(--text2);
  cursor: pointer; text-decoration: none; white-space: nowrap;
}
.chip:hover { border-color: #D8D8DE; color: var(--text); text-decoration: none; }
.chip.on { background: var(--text); border-color: var(--text); color: #fff; font-weight: 500; }
.toolbar .sp { flex: 1; }
.toolbar .count { font-size: 12.5px; color: var(--text3); }

/* 标签 */
.tag {
  display: inline-flex; align-items: center; height: 21px; padding: 0 7px;
  border-radius: 5px; font-size: 11.5px; font-weight: 500; white-space: nowrap;
  background: #F2F2F5; color: var(--text2);
}
.tag.gray { background: #F2F2F5; color: var(--text2); }
.tag.green, .tag.on { background: var(--green-soft); color: var(--green-ink); }
.tag.amber, .tag.warn { background: var(--amber-soft); color: var(--amber); }
.tag.red, .tag.off, .tag.danger, .tag.expired { background: var(--red-soft); color: var(--red); }
.tag.blue { background: var(--blue-soft); color: var(--blue); }

/* ══════════════════ 列表（新壳） ══════════════════ */
.panel {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r); overflow: hidden;
  box-shadow: var(--shadow);
}
/* 8 列：勾选 / 码 / 状态 / 批次 / 到期 / 剩余 / 设备 / 操作 */
/* ⚠ 必须带 `.panel` 前缀：旧壳的表单行也用 `.row` 类（`<form class="row">`），
   裸 `.row` 会把它们套成 8 列 grid —— 2026-10-21 就是这么把「远程开关 / 回填订单 /
   批次 / 登录」四页弄坏的：label 被挤进 20px 列 → 一字一行竖排。 */
.panel .thead, .panel .row {
  display: grid;
  /* 最后一列是操作区：三枚按钮（复制/详情/吊销）各 ~44px + 两道 4px 间隙 = 140px，
     原来的 132px 装不下，「吊销」被压扁 / 贴到卡片边（2026-10-21 截图）。留到 160。 */
  grid-template-columns: 20px 186px 104px minmax(140px, 1fr) 100px 76px 66px 160px;
  align-items: center; gap: 12px; padding: 0 16px;
}
.panel .thead {
  height: 40px; font-size: 12px; color: var(--text3);
  background: var(--bg); border-bottom: 1px solid var(--line);
}
.panel .row { min-height: 52px; border-bottom: 1px solid var(--line-soft); }
.panel .row:last-child { border-bottom: 0; }
.panel .row:hover { background: #FBFBFC; }

.code {
  font: 600 13px/1 ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  letter-spacing: 1.2px; color: var(--text); white-space: nowrap;
}
.code .dim { color: var(--text3); font-weight: 500; }

.muted { font-size: 12.5px; color: var(--text2); }
.tiny { font-size: 11.5px; color: var(--text3); }
.left { font-size: 12.5px; color: var(--text2); font-variant-numeric: tabular-nums; white-space: nowrap; }
.left b { font-weight: 600; color: var(--text); }
.left.warn b { color: var(--amber); }
.left.bad b { color: var(--red); }

.bar { height: 3px; border-radius: 2px; background: #F0F0F3; overflow: hidden; margin-top: 4px; }
.bar i { display: block; height: 100%; background: var(--green); border-radius: 2px; }
.bar.warn i { background: #FF9500; }
.bar.danger i { background: var(--red); }

.acts { display: flex; gap: 4px; justify-content: flex-end; opacity: 0; transition: opacity .12s; }
.row:hover .acts { opacity: 1; }
/* 操作列的三枚按钮：统一 26px。`.xs danger` 与 `.xs` 走同一条规则 ——
   之前「吊销」带 class 走这条、另两枚不带 class 走进了已删除的兜底规则，所以一大一小。 */
.acts button, .acts button.xs {
  height: 26px; padding: 0 9px; border-radius: 5px; font-size: 12px;
  border: 1px solid var(--line); background: #fff; color: var(--text2);
  cursor: pointer; font-family: inherit;
  /* ⚠ white-space + flex:none 都必要：只设其中一个，按钮仍会被 flex 压到
     「最窄内容宽度」（一个汉字），文字随即折成两行 —— 真机上就是「复/制」竖排。 */
  white-space: nowrap;
  flex: none;
}
.acts button:hover { background: #F7F7F9; color: var(--text); }
.acts button.danger:hover { background: var(--red-soft); border-color: #F3C9C9; color: var(--red); }

/* 详情行：与 .row 成对出现，默认折叠。
   ⚠ 不能放进 .row 里 —— .row 是 8 列 grid，塞进去会挤成第 9 格并错位。 */
.subrow { background: #FAFAFB; border-bottom: 1px solid var(--line-soft); }
.subrow.hide { display: none; }
/* 详情行：**两列 grid** —— 设备在左、订单回填在右，第三段（生成时间 + 删除）跨满一行。
   原来是 flex-wrap：三段宽度不一时第三段会被挤到下一行最左边，与上面对不齐。 */
.subrow .inner {
  padding: 14px 16px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  column-gap: 32px; row-gap: 14px;
  /* ⚠ **不能写 `align-items: start`**：grid 子项默认 stretch 才会撑满列宽，
     写成 start 后 `.subgroup` 只剩内容宽度 —— 里面的 `.row` 因此被压到 ~200px，
     两个 `min-width:150px` 的输入框装不下，直接**互相重叠**（2026-10-21 截图）。 */
  align-items: stretch;
}
.subrow .subgroup { display: flex; flex-direction: column; min-width: 0; width: 100%; }
/* 第三段（生成 / 有效期 / 删除）横排占满整行 */
.subrow .subgroup.foot {
  grid-column: 1 / -1;
  flex-direction: row; align-items: center; flex-wrap: wrap; gap: 12px;
  padding-top: 12px; border-top: 1px solid var(--line-soft);
}
/* ⚠ 详情行里的「订单回填」表单：不加这几条，`.field` 会被 flex 压到比 label 还窄，
   「订单号 / 买家」就被挤成一字一行（2026-10-21 真机截图）。 */
/* 订单回填那一行：**显式底对齐**。
   截图里的错位是「保存」按钮跑到了 label 那一行的右侧 —— 它没有跟着 `.field` 贴底。
   `align-items` 写在 `.row` 上，按钮再显式 `align-self` 兜一次：两者任一失效都不会错位。 */
/* ★ 订单回填那一行用 **grid 而不是 flex**。
   flex 方案（`flex:1 1 150px` + `min-width:150px`）在这一层嵌套里试过两次都不生效：
   两个输入框被压到 30px 并互相重叠（2026-10-21 截图 + 调试边框实测）。
   grid 的列宽由 `1fr` 直接决定，不需要 min-width 与各层 flex 规则搏斗。 */
.subrow .row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
  gap: 12px; align-items: end;
  width: 100%;
}
.subrow .field { display: flex; flex-direction: column; min-width: 0; }
.subrow .field input { width: 100%; min-width: 0; }
.subrow .row button { align-self: end; }
.subrow .subtitle { font-size: 12px; color: var(--text2); font-weight: 500; margin-bottom: 6px; }

.empty { padding: 44px 16px; text-align: center; font-size: 13px; color: var(--text3); }

.footbar {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 16px; border-top: 1px solid var(--line); background: #FAFAFB;
  font-size: 12.5px; color: var(--text2); flex-wrap: wrap;
}
.footbar b { color: var(--text); font-variant-numeric: tabular-nums; }

/* 分页 */
.pager { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.pager .range { font-size: 12.5px; color: var(--text3); margin-right: 6px; font-variant-numeric: tabular-nums; }
.pager a, .pager span.pg {
  min-width: 28px; height: 28px; padding: 0 8px; border-radius: 5px;
  border: 1px solid var(--line); background: #fff;
  font-size: 12.5px; color: var(--text2); cursor: pointer; text-decoration: none;
  font-variant-numeric: tabular-nums; white-space: nowrap; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
}
.pager a:hover { background: #F7F7F9; color: var(--text); text-decoration: none; }
.pager .on { background: var(--text); border-color: var(--text); color: #fff; font-weight: 500; }
.pager .on:hover { background: var(--text); color: #fff; }
.pager .dis { opacity: .45; pointer-events: none; }
.pager .gap { color: var(--text3); font-size: 12px; padding: 0 2px; }

/* ══════════════════ 表单 ══════════════════ */
.form {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r); padding: 20px; box-shadow: var(--shadow);
}
.form h2 { font-size: 15px; font-weight: 600; margin-bottom: 4px; }
.form .hint { font-size: 12.5px; color: var(--text2); margin-bottom: 18px; }
.fields { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px 16px; }
.fields .full { grid-column: 1 / -1; }
.field label { display: block; font-size: 12.5px; color: var(--text2); margin-bottom: 5px; }
.field input, .field select {
  width: 100%; padding: 8px 11px; border-radius: 6px;
  border: 1px solid var(--line); background: #fff;
  font-size: 13.5px; color: var(--text); font-family: inherit;
}
.field input:focus, .field select:focus { outline: 2px solid var(--green-soft); border-color: var(--green); }
.field .tip { font-size: 11.5px; color: var(--text3); margin-top: 4px; }
.submit-row {
  margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--line-soft);
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.submit-row .sp { flex: 1; }
.submit-row .sum { font-size: 12.5px; color: var(--text2); }
.submit-row .sum b { color: var(--text); font-variant-numeric: tabular-nums; }

/* 批量生成：左参数 / 右预览 */
.gen { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 16px; align-items: start; }
.preview {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r); padding: 16px; box-shadow: var(--shadow);
}
.preview h3 { font-size: 13px; font-weight: 600; margin-bottom: 10px; }
.preview .kv { display: flex; justify-content: space-between; padding: 7px 0; border-bottom: 1px solid var(--line-soft); font-size: 12.5px; }
.preview .kv:last-of-type { border-bottom: 0; }
.preview .kv span { color: var(--text2); }
.preview .kv b { font-weight: 600; font-variant-numeric: tabular-nums; }
.preview .sample {
  margin-top: 12px; padding: 10px; border-radius: 6px; background: #FAFAFB;
  font: 600 12.5px/1.9 ui-monospace, Menlo, monospace; letter-spacing: 1.2px; color: var(--text);
}
.preview .sample .dim { color: var(--text3); }

/* 已载入上次参数 */
.loadbar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 9px 12px; border-radius: 6px; background: var(--blue-soft);
  font-size: 12.5px; color: var(--text2); margin-bottom: 16px;
}
.loadbar b { color: var(--text); font-variant-numeric: tabular-nums; }
.loadbar a { margin-left: auto; white-space: nowrap; }


/* 概览页底部说明（v0.1.10） */
.hintbar {
  margin-top: 14px; padding: 11px 14px; border-radius: 6px;
  background: #fff; border: 1px solid var(--line);
  font-size: 12.5px; color: var(--text2); line-height: 1.7;
}


/* 回填订单页的粘贴框（v0.4.78 迁新壳时补）：等宽字体，行对齐才好核对「码 + 订单号」 */
.paste {
  width: 100%; min-height: 220px; padding: 12px;
  border-radius: 6px; border: 1px solid var(--line);
  background: #fff; color: var(--text);
  font: 13px/1.75 ui-monospace, "SF Mono", Menlo, monospace;
  resize: vertical;
}
.paste:focus { outline: 2px solid var(--green-soft); border-color: var(--green); }
.paste::placeholder { color: var(--text3); }

/* 导入结果里"库里不存在"的码清单 */
.missed { font: 12px/1.9 ui-monospace, Menlo, monospace; color: var(--red); margin-top: 6px; }


/* ── 系统设置页的三张列表（v0.4.81）──
   原来三处都用一个未定义的 `.ev`：没有 display，5 个 span 直接连成一串
   （`10-21 11:28:58 heartbeat_fail 5KQB… 223.104… revoked` 读不出列）。 */
.ev-bak {
  display: grid; grid-template-columns: minmax(0, 1fr) 90px; gap: 12px;
  /* ⚠ 左右必须有内边距：原来写 0，最后一列（撤回）直接顶到卡片 border 上，
     被 .panel 的 overflow:hidden 切掉半边（用户 2026-10-21 截图）。 */
  padding: 8px 16px; border-bottom: 1px solid var(--line-soft);
  font-size: 12.5px; color: var(--text2); align-items: baseline;
}
.ev-bak:last-child { border-bottom: 0; }

.ev-ver {
  /* v0.5.10：grid 改成 **flex + 固定列宽**。
     grid 版本（`140px 70px 1fr 94px 62px`）在容器偏窄时，最后一列会被挤出右边界 ——
     真机/截图里「撤回」按钮都压到卡片外面（用户两次指同一处）。
     flex 每个子项显式给宽、说明列 `flex:1 + min-width:0` 负责收窄，不会再溢出。 */
  display: flex; gap: 12px; align-items: center;
  padding: 8px 16px; border-bottom: 1px solid var(--line-soft);
  font-size: 12.5px; color: var(--text2);
}
.ev-ver > span:nth-child(1) { flex: none; width: 130px; }
.ev-ver > span:nth-child(2) { flex: none; width: 64px; }
.ev-ver > span:nth-child(3) {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ev-ver > span:nth-child(4) { flex: none; width: 88px; text-align: right; }
.ev-ver > span:nth-child(5) { flex: none; width: 78px; text-align: right; }
/* 「撤回」按钮本体：给它一个明确最小宽度 —— 在 flex 里按钮默认 min-width:auto，
   但父 span 只有 58px 时它会缩到 18px，两个字直接溢出框外（截图可见）。 */
.ev-ver .xs { flex: none; min-width: 54px; height: 24px; }
.ev-ver:last-child { border-bottom: 0; }
.ev-ver .head { font-size: 11.5px; color: var(--text3); }

.ev-log {
  display: grid; grid-template-columns: 176px 130px 150px 130px minmax(0, 1fr);
  gap: 12px; padding: 7px 0; border-bottom: 1px solid var(--line-soft);
  font-size: 12.5px; color: var(--text2); align-items: baseline;
}
.ev-log:last-child { border-bottom: 0; }
.ev-log .t { font-variant-numeric: tabular-nums; color: var(--text3); white-space: nowrap; }
/* 合并后的重复次数：用琥珀色标出来，一眼能看出"这是刷屏而不是多个事件" */
.dup { color: var(--amber); font-weight: 600; margin-left: 6px; font-variant-numeric: tabular-nums; }

/* 版本说明只要 3 行，不该跟回填订单的粘贴框一样高 */
.paste.short { min-height: 88px; }


/* 文件选择框（v0.4.83）：原生控件是一块 90 年代灰方块，与新壳完全两个时代。
   `::file-selector-button` 只在真正需要的地方定制按钮，文件名部分保持浏览器默认排版。 */
input[type=file] { font-size: 13px; color: var(--text2); }
input[type=file]::file-selector-button {
  height: 32px; padding: 0 14px; margin-right: 10px;
  border-radius: 6px; border: 1px solid var(--line); background: #fff;
  font-size: 13px; color: var(--text2); font-family: inherit; cursor: pointer;
  transition: background 150ms, border-color 150ms;
}
input[type=file]::file-selector-button:hover {
  background: #fbfbfc; border-color: var(--text4);
}

/* ⚠ **这条兜底规则删掉了**（v0.4.95）。
 *
 * 它本意是"忘了写 class 的裸 button 至少别露浏览器默认样式"，但它选择器优先级 (0,2,1)
 * 高于 `.acts button` (0,1,1) —— 于是操作列里**没写 class 的「复制」「详情」被它放大成 36px**，
 * 而写了 `class="danger"` 的「吊销」仍是 `.acts button` 的 26px，
 * 三个按钮大小不一致（用户 2026-10-21：「这种最简单的你也修不好」）。
 *
 * 教训：**兜底规则不要跟组件规则抢同一批元素** —— 要么给元素补 class，
 * 要么让兜底用 `:where()` 把优先级降到 0。这里选了前者：纽扣全都写 class。 */


/* 操作列里的「吊销」被一层 <form> 包着。`.acts` 是 flex 容器，form 作为 flex 子项
   会按内容收缩，里面的 button 因此被压得比「复制/详情」小一圈（2026-10-21 截图）。
   ⚠ 不能用 `display: contents` —— 那样 form 从布局里消失、button 直接进 flex，
   但操作列是固定宽度的 grid 列，三个按钮会溢出并被 `.panel` 的 `overflow:hidden` 裁掉。
   让 form 自身也成为 flex 容器即可：它按内容撑开，内部 button 保持同一套尺寸。 */
.acts form { display: flex; margin: 0; }

/* 浏览器自动填充会把输入框刷成淡紫/淡黄底，且有 !important 级的优先级；
   唯一可靠的办法是用 inset box-shadow 盖住它。登录页首屏最常见（账号密码都填过）。 */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 1000px #fff inset;
  -webkit-text-fill-color: var(--text);
  transition: background-color 9999s ease-in-out 0s;
}



/* ── 表单纵向节奏（v0.4.96）──
   症状：输入框下方的「强制更新」和「发布版本」按钮**贴着上面的元素**。
   根因：`.field` 从来没有 `margin-bottom`（字段间距全靠模板里的内联 `margin-top`，
   而那几条又是给上一个元素加的）；`.chk` 更是**根本没定义** —— `<label>` 默认 inline，
   **垂直方向的 margin 对 inline 元素无效**，所以模板里写的 `margin-top:10px` 一点没生效。
   这里把它定义成 flex 行并给出上下间距，字段间距也统一到 14px。 */
.form .field { margin-bottom: 14px; }
.form .field:last-of-type { margin-bottom: 0; }
.chk {
  display: flex; align-items: center; gap: 7px;
  margin: 14px 0; font-size: 13px; color: var(--text2);
  cursor: pointer; line-height: 1.5;
}
.chk input[type="checkbox"] { width: 15px; height: 15px; accent-color: var(--green); margin: 0; }
.form > form > .row, .form .submit-row { margin-top: 16px; }


/* ── 日志页筛选条（v0.4.99）──
   动作类型有十几种，全铺成 chip 会挤成一条并贴住表格；改成下拉 + 右侧计数。 */
/* 并入表格卡片后：与表格共用左边界（thead 是 padding 0 16px），下面一条分隔线 */
.panel .logbar {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 16px; border-bottom: 1px solid var(--line);
  background: var(--bg);
}
.logbar select {
  height: 34px; min-width: 190px; padding: 0 30px 0 12px;
  border-radius: 6px; border: 1px solid var(--line); background: #fff;
  font-size: 13px; font-family: inherit; color: var(--text); cursor: pointer;
  appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 16 16' fill='none' stroke='%23737a87' stroke-width='1.6'><path d='M4 6l4 4 4-4'/></svg>");
  background-repeat: no-repeat; background-position: right 10px center;
}
.logbar select:focus { outline: none; border-color: var(--green); box-shadow: 0 0 0 3px var(--green-soft); }
.logbar .count { margin-left: auto; font-size: 12.5px; color: var(--text3); }
.logbar .count b { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }

/* 合并区间放第二行：原来挤在一行里，「时间」列宽到 176px，把详情挤得只剩几个字 */
.ev-log .t em {
  display: block; font-style: normal; font-size: 11px;
  color: var(--text4); margin-top: 1px;
}
/* 日志行留一点纵向呼吸：14 行铺满一屏时贴在一起很难扫 */
.ev-log { padding-top: 8px; padding-bottom: 8px; }


/* ── 表单里并排的字段（v0.5.0）──
   症状：versionCode / versionName 两个字段**上下错位**，右边那个整体低 25px。
   根因：`.row` 是 `display:flex; align-items:flex-end`（底部对齐）——
   两个字段内容高度一致时看不出来，但只要其中一个多一行字（或其内部空隙不同），
   底部一对齐，上面的标签就高低不齐。
   两个同构字段应该**顶部对齐**，所以这里改用 grid，`align-items: start`。 */
.form .row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 12px;
  align-items: start;
}
/* 按钮行（没有 .field 的）仍按 flex 排、靠右 */
.form .row:not(:has(.field)) {
  display: flex; align-items: center; justify-content: flex-end;
}


/* ── App 版本管理块（v0.5.2）── */
.blockhead { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
.blockhead h2 { margin: 0 0 2px; }

/* 空状态：只占 70px 左右。截图那版做到 160px，比内容还大，看着像"这里坏了" */
.empty-state {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; padding: 20px 16px; margin-bottom: 16px;
  border: 1px dashed var(--line); border-radius: 8px; background: #fbfbfc;
  text-align: center;
}
.empty-state .ico { width: 26px; height: 26px; color: var(--text4); }
.empty-state .t { font-size: 13.5px; font-weight: 500; color: var(--text2); }
.empty-state .s { font-size: 12px; color: var(--text3); }

.subcard { border: 1px solid var(--line); border-radius: 8px; padding: 16px; background: #fff; }
.subcard h3 {
  display: flex; align-items: center; gap: 7px;
  margin: 0 0 14px; font-size: 13.5px; font-weight: 600; color: var(--text);
}
.subcard h3 .ic { width: 15px; height: 15px; color: var(--green-ink); }

.dropzone {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; padding: 20px 16px;
  border: 1.5px dashed var(--line); border-radius: 8px;
  background: #fcfcfd; cursor: pointer;
  transition: border-color .15s, background .15s;
}
.dropzone:hover, .dropzone.over { border-color: var(--green); background: var(--green-soft); }
.dropzone input[type="file"] { display: none; }
.dropzone .ico { width: 24px; height: 24px; color: var(--text3); transition: color .15s; }
.dropzone .t { font-size: 13px; color: var(--text2); }
.dropzone .t b { color: var(--green-ink); font-weight: 500; }
.dropzone .s { font-size: 11.5px; color: var(--text4); }
.dropzone.hasfile { border-color: var(--green); background: var(--green-soft); }
.dropzone.hasfile .ico { color: var(--green); }
.dropzone.hasfile .t { color: var(--green-ink); font-weight: 500; }

.changelog {
  width: 100%; min-height: 96px; padding: 10px 12px; box-sizing: border-box;
  border-radius: 6px; border: 1px solid var(--line);
  font: 13px/1.7 inherit; color: var(--text); background: #fff; resize: vertical;
}
.changelog:focus { outline: none; border-color: var(--green); box-shadow: 0 0 0 3px var(--green-soft); }
.changelog::placeholder { color: var(--text4); }

.danger-text { color: var(--red); font-weight: 500; }
/* 按钮在未选文件时是禁用态：灰底、不可点，但仍占位（避免布局跳动） */
.btn:disabled { opacity: .5; cursor: not-allowed; }


/* 日志页底部：左侧说明 + 右侧分页（v0.5.4） */
.logfoot {
  display: flex; align-items: center; gap: 12px;
  margin-top: 14px; font-size: 12.5px; color: var(--text3);
}
.logfoot .hint { flex: 1; }
.logfoot .pager { display: flex; align-items: center; gap: 10px; white-space: nowrap; }
.logfoot .pager a {
  padding: 3px 9px; border-radius: 5px; border: 1px solid var(--line);
  background: #fff; color: var(--text2); text-decoration: none;
}
.logfoot .pager a:hover { border-color: var(--text4); color: var(--text); }
.logfoot .pager span { font-variant-numeric: tabular-nums; }


/* ── 日志页左右留白（v0.5.6）──
   用户反复反馈"还是贴着左侧"。上一轮我把筛选条并进了卡片、给了 16px 内边距，
   但 `thead` 的 `padding: 0 16px` 与它是同一档 —— 8px 视觉差在窄屏上看不出来，
   观感仍是"贴边"。
   这一轮只作用于日志页（`.logs-panel`），把左右留白统一提到 **20px**：
   筛选条、表头、每一行三者用同一个值，才会读作"一致"。
   ⚠ 不要改全局 `.panel .thead` —— 授权码页有 8 列，再收窄会挤。 */
.logs-panel .logbar { padding: 12px 20px; }
.logs-panel .thead { padding: 0 20px; }
.logs-panel .row, .logs-panel .ev-log { padding-left: 20px; padding-right: 20px; }


/* 版本列表：表头与数据行之间留一点呼吸；发布列右对齐（它是最右一列，左对齐会飘在中间） */
.ev-ver .head { padding-bottom: 2px; }
.ev-ver .right, .ev-ver .head.right { text-align: right; }

/* 上传进度（v0.5.8）
   原生 form 提交只知道"开始"和"结束"，中间没有任何进度事件 ——
   必须用 XHR 的 upload.onprogress 才能拿到已发送字节数。 */
.uprogress { margin: 12px 0 4px; }
.uprogress .ubar {
  height: 4px; border-radius: 2px; background: var(--line-soft); overflow: hidden;
}
.uprogress .ubar i {
  display: block; height: 100%; width: 0; border-radius: 2px;
  background: var(--green); transition: width .12s linear;
}
.uprogress .utext { margin-top: 6px; font-size: 12px; color: var(--text3); }

/* ══════════════════ 登录页（v0.4.92）══════════════════
   参照「相册管家」登录页的规格：**品牌区居中** + 卡片 400 宽 / 16 圆角 / 32 内边距，
   标题 20 + 说明 13 居中，字段 13 号 medium，按钮全宽。
   它没有侧栏 —— 未登录时导航无处可去，所以这一页不套 `.app`。 */
body.auth {
  min-height: 100vh;
  display: grid; place-items: center;
  background: var(--bg); padding: 16px;
  margin: 0;
}
.auth-card {
  width: 100%; max-width: 400px;
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  padding: 32px;
  box-shadow: 0 2px 12px rgba(11, 11, 15, .04);
}
.auth-brand {
  display: flex; flex-direction: column; align-items: center;
  gap: 10px; margin-bottom: 28px;
}
.auth-logo {
  width: 40px; height: 40px; border-radius: 10px;
  display: grid; place-items: center;
  background: var(--green); color: #fff;
  font-size: 15px; font-weight: 600; letter-spacing: .02em;
}
.auth-brandname { font-size: 15px; font-weight: 500; color: var(--text); }
.auth-brandsub {
  font-size: 11px; letter-spacing: .08em; color: var(--text3);
  margin-top: -6px; text-transform: uppercase;
}
.auth-card h1 {
  font-size: 20px; font-weight: 500; letter-spacing: -.01em;
  text-align: center; margin: 0 0 4px; color: var(--text);
}
.auth-desc {
  font-size: 13px; color: var(--text3); text-align: center;
  margin: 0 0 24px;
}
.auth-field { display: block; margin-bottom: 16px; }
.auth-field .lbl {
  display: block; font-size: 13px; font-weight: 500;
  color: var(--text); margin-bottom: 6px;
}
.auth-field input {
  width: 100%; height: 40px; padding: 0 12px;
  border-radius: 6px; border: 1px solid var(--line);
  font-size: 13px; font-family: inherit; color: var(--text);
  background: #fff; box-sizing: border-box;
}
.auth-field input:focus {
  outline: none; border-color: var(--green);
  box-shadow: 0 0 0 3px var(--green-soft);
}
.auth-submit { width: 100%; height: 42px; margin-top: 4px; font-size: 14px; }
.auth-foot {
  text-align: center; font-size: 12px; color: var(--text4);
  margin: 16px 0 0;
}

/* ══════════════════ 旧壳兼容（订单 / 批次 / 远程开关 / 登录） ══════════════════

   这四页还没迁到新壳，仍用 `.wrap > .top + .card` 结构，且用的是**裸 `input` / `button`**
   （不带 class）。为了不污染新壳的 `.btn`，裸元素规则一律限定在 `.wrap` 内。

   ⚠ 2026-10-21 的教训：上一版把 `.row` 定义成列表行的 8 列 grid，而这四页的表单行
     也叫 `<form class="row">` ⇒ label 被挤进 20px 列、一字一行竖排，四页全坏。
     现在列表行一律 `.panel .row`，这里保留 `.row` 的 flex 语义（表单行）。
   等四页迁完，这一段可以整段删掉。 */


/* 登录页：外层只负责定位，卡片由模板里的 .card 承担 —— 两层都画边框会变成"卡中卡" */


.stats { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; margin-bottom: 20px; }
.stats > div {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r); padding: 14px 16px 13px;
}
.stats .k { font-size: 12px; color: var(--text2); }
.stats .v {
  font-size: 26px; font-weight: 600; letter-spacing: -.8px; margin-top: 6px;
  font-variant-numeric: tabular-nums;
}
.stats .v.warn { color: var(--amber); }
.stats .v.bad { color: var(--red); }

.label { font-size: 12px; color: var(--text2); margin-bottom: 12px; }

/* 表单行：flex（**不是** grid —— 见文件顶部那条教训） */
.row { display: flex; gap: 12px; flex-wrap: wrap; align-items: flex-end; }
.field { display: flex; flex-direction: column; gap: 5px; }
.field label { font-size: 12.5px; color: var(--text2); }

/* 裸元素只在旧壳内生效，避免和新壳的 .btn / .field input 打架 */


/* .mini 是通用小按钮：新壳的详情行里也在用，故不加 .wrap 前缀 */
.mini {
  height: 30px; padding: 0 12px; border-radius: 6px;
  border: 1px solid var(--line); background: #fff; color: var(--text2);
  font-size: 12px; font-weight: 500; font-family: inherit;
  cursor: pointer; white-space: nowrap; flex: none;
}
.mini:hover { background: #F7F7F9; color: var(--text); }
.mini.sec { color: var(--text2); }
.mini.danger { color: var(--red); border-color: #F3C9C9; }
.mini.danger:hover { background: var(--red-soft); }

/* 旧筛选条（部分页面仍在用） */
.filters {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line);
}
.filters a {
  font-size: 13px; line-height: 1; padding: 7px 12px; border-radius: 6px;
  color: var(--text2); text-decoration: none; background: #F2F2F5;
}
.filters a:hover { background: #E9E9EB; color: var(--text); }
.filters a.on { background: var(--green-soft); color: var(--green-ink); font-weight: 600; }

/* 旧壳进度条：限宽，避免在宽卡片里拉成一整条 */

/* 提示条 */
.note { padding: 10px 13px; border-radius: 6px; font-size: 13px; line-height: 1.55; background: var(--amber-soft); color: var(--amber); }
.note.ok { background: var(--green-soft); color: var(--green-ink); }
.note.err { background: var(--red-soft); color: var(--red); }

.meta { font-size: 12.5px; color: var(--text2); margin-top: 6px; }
.dev { font-size: 12px; color: var(--text3); margin-top: 6px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
details.sold { margin-top: 8px; font-size: 12.5px; }
details.sold summary { cursor: pointer; color: var(--text2); }

/* ══════════════════ 响应式 ══════════════════ */

/* 中屏：侧栏收成顶栏 */
@media (max-width: 1000px) {
  .app { flex-direction: column; }
  .side {
    width: auto; height: auto; position: sticky; top: 0; z-index: 10;
    flex-direction: row; align-items: center; gap: 4px;
    border-right: 0; border-bottom: 1px solid var(--line);
    padding: 0 12px; overflow-x: auto;
  }
  .brand { padding: 12px 10px 12px 0; flex: none; }
  .brand small { display: none; }
  .nav { flex-direction: row; padding: 0; gap: 2px; flex: 1; }
  .nav .grouplabel, .nav a .ic { display: none; }
  .nav a { padding: 6px 10px; font-size: 13px; white-space: nowrap; }
  .side .foot { margin: 0 0 0 8px; padding: 0; border: 0; }
  .side .foot button { width: auto; padding: 5px 12px; font-size: 12.5px; }

  .main { padding: 18px 16px 48px; }
  .kpis, .stats { grid-template-columns: repeat(3, 1fr); }
  .gen { grid-template-columns: 1fr; }
  .panel .thead, .panel .row { grid-template-columns: 20px 170px 96px minmax(90px, 1fr) 92px 70px 60px 120px; gap: 10px; }
}

/* 窄屏：表格变卡片 */
@media (max-width: 760px) {
  .head h1, .top h1 { font-size: 19px; }
  .head .right { width: 100%; }
  .search { width: 100%; }
  .kpis, .stats { grid-template-columns: repeat(2, 1fr); gap: 8px; }
  .kpi, .stats > div { padding: 12px 13px; }
  .kpi .v, .stats .v { font-size: 22px; }

  .panel .thead { display: none; }

  .panel .row {
    grid-template-columns: minmax(0, 1fr) auto auto;
    gap: 5px 12px; padding: 12px 14px; min-height: 0;
    align-items: center;
  }
  .panel .row > .c-check { display: none; }
  .panel .row > .c-code   { grid-column: 1; }
  .panel .row > .c-status { grid-column: 3; justify-self: end; }
  .panel .row > .c-batch  { grid-column: 1 / 4; }
  .panel .row > .c-due    { grid-column: 1; }
  .panel .row > .c-left   { grid-column: 2; }
  .panel .row > .c-dev    { grid-column: 3; justify-self: end; }
  .panel .row > .c-acts   { grid-column: 1 / 4; }

  /* 每项前补标签：只剩裸数字与日期读不出含义 */
  .c-due::before  { content: "到期 "; font-size: 11px; color: var(--text3); }
  .c-left::before { content: "剩余 "; font-size: 11px; color: var(--text3); }
  .c-dev::before  { content: "设备 "; font-size: 11px; color: var(--text3); }

  /* 移动端没有 hover，操作按钮必须常显 —— 隐藏等于不可用 */
  .acts { opacity: 1; justify-content: flex-start; margin-top: 2px; flex-wrap: wrap; }
  .acts button { height: 28px; padding: 0 11px; }

  .footbar { padding: 10px 14px; }
  .footbar .btn { padding: 6px 10px; font-size: 12.5px; }
  .fields { grid-template-columns: 1fr; }
  .subrow .inner { grid-template-columns: 1fr; row-gap: 16px; padding: 12px 14px; }
}

@media (max-width: 420px) {
  .kpis, .stats { grid-template-columns: 1fr 1fr; }
  .c-code { font-size: 12px; letter-spacing: .6px; }
}

/* ── 开关行（v0.3.3）──
   原来两个 0/1 开关是文本框：要记语义、容易输错，而且看不出当前是开是关。
   改成勾选式后，状态一眼可见，也不用记"1 是什么意思"。 */
.sw {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 0;
  cursor: pointer;
  border-bottom: 1px solid var(--line);
}
.sw:last-of-type { border-bottom: none; }
.sw input { position: absolute; opacity: 0; pointer-events: none; }
.sw-track {
  position: relative;
  flex: 0 0 auto;
  width: 42px;
  height: 26px;
  border-radius: 13px;
  /* 关闭态用 --line(#D1D1D6) 而不是 --fill(#E9E9EB)：后者压在白色卡片上
     几乎等于看不见，用户反馈"关的时候纯白"。同时加一圈极淡的描边，
     保证任何主题下都能看出"这里有个开关、现在是关的"。 */
  background: #D1D1D6;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.06);
  transition: background 0.18s ease-out;
}
.sw-track i {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
  transition: transform 0.18s ease-out;
}
.sw input:checked + .sw-track { background: var(--green); }
.sw input:checked + .sw-track i { transform: translateX(16px); }
.sw input:focus-visible + .sw-track { box-shadow: 0 0 0 3px var(--green-soft); }
.sw-main { display: flex; flex-direction: column; gap: 2px; }
.sw-title { font-size: 13.5px; font-weight: 500; color: var(--text); }
.sw-sub { font-size: 12px; color: var(--text3); }

/* 字段下方的一句说明：把这句从 label 括号里挪出来，label 才能短到一眼扫完。 */
.hint { display: block; margin-top: 4px; font-size: 11.5px; color: var(--text3); }

/* 页面底部的版本条（v0.3.4）。刻意做得很轻：它是"需要时能一眼看到"的信息，
   不该和上面的操作区抢注意力。 */
.vercard {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 13px 18px;
  margin: 4px 0 10px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  font-size: 12.5px;
  color: var(--text3);
}
.vercard svg { width: 15px; height: 15px; flex: 0 0 auto; }
.vercard b { color: var(--text); font-weight: 700; }
