/* ============================================================================
   工作台 SPA · 页面级样式
   依赖：tokens.css（令牌）+ icons.css（图标）+ icons.svg.html（图纸）
   规则：本文件只写工作台独有布局，颜色/圆角/时间一律用 token 变量。
   ============================================================================ */

/* ---------- 应用骨架：侧栏 + 主区 ---------- */
.app{
  display:grid;
  grid-template-columns:auto minmax(0,1fr);
  min-height:100dvh;
  background:var(--bg);
}

/* 侧栏：浮动玻璃态面板（不贴边、不贴顶） */
.sidebar{
  position:sticky;top:0;
  height:100dvh;
  width:246px;
  flex-shrink:0;
  padding:var(--sp-4) var(--sp-3);
  display:flex;flex-direction:column;
  gap:var(--sp-4);
  background:var(--bg-sunken);
  border-right:1px solid var(--line);
  z-index:var(--z-sticky);
  transition:width var(--dur-slow) var(--ease-soft);
}

.logo{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-2) var(--sp-2) var(--sp-1)}
.logo-badge{
  width:34px;height:34px;border-radius:var(--r-md);
  background:var(--accent);
  display:flex;align-items:center;justify-content:center;
  color:var(--accent-ink);
  flex-shrink:0;
  box-shadow:var(--sh-inset-hi), 0 6px 14px -8px var(--accent-ring);
}
.logo-badge svg{width:17px;height:17px;stroke-width:1.7}
.logo-text{min-width:0}
.logo-text b{
  display:block;font-size:var(--fs-13);
  font-weight:var(--fw-semibold);
  letter-spacing:-.01em;
  white-space:nowrap;
}
.logo-text small{
  display:block;color:var(--sub-2);
  font-size:var(--fs-11);
  letter-spacing:var(--ls-label);
  text-transform:uppercase;
  white-space:nowrap;
}

/* 导航 */
.nav{display:flex;flex-direction:column;gap:2px;flex:1;overflow-y:auto}
.nav button{
  display:flex;align-items:center;gap:var(--sp-3);
  padding:9px var(--sp-3);
  border-radius:var(--r-md);
  color:var(--sub);
  font-size:var(--fs-13);
  font-weight:var(--fw-medium);
  text-align:left;
  position:relative;
  transition:
    background-color var(--dur-fast) var(--ease-in-out-quint),
    color var(--dur-fast) var(--ease-in-out-quint);
}
.nav button:hover{background:var(--surface-hover);color:var(--ink)}
.nav button.on{background:var(--surface);color:var(--ink);font-weight:var(--fw-semibold);box-shadow:var(--sh-sm), var(--sh-inset-hi)}
/* 当前页指示：左侧细条，不用彩色圆点 */
.nav button.on::before{
  content:"";position:absolute;left:-var(--sp-3);left:-12px;top:50%;
  transform:translateY(-50%);
  width:3px;height:16px;border-radius:var(--r-full);
  background:var(--accent);
}
.nav button .badge{
  margin-left:auto;
  font-size:var(--fs-11);font-weight:var(--fw-semibold);
  color:var(--sub-2);
  font-variant-numeric:tabular-nums;
}

/* 侧栏底部：余额 + 账号 */
.side-foot{
  padding-top:var(--sp-3);
  border-top:1px solid var(--line);
  display:flex;flex-direction:column;gap:var(--sp-2);
}
.balance{
  display:flex;align-items:center;gap:var(--sp-3);
  padding:var(--sp-3);
  border-radius:var(--r-md);
  background:var(--surface);
  border:1px solid var(--line);
  box-shadow:var(--sh-inset-hi);
  transition:border-color var(--dur-fast) var(--ease-in-out-quint);
  width:100%;text-align:left;
}
.balance:hover{border-color:var(--accent)}
.balance .b-ico{color:var(--accent);display:flex}
.balance .b-ico svg{width:16px;height:16px;stroke-width:1.7}
.balance .b-body{flex:1;min-width:0}
.balance .b-label{font-size:var(--fs-11);color:var(--sub);letter-spacing:var(--ls-label)}
.balance .b-val{
  font-size:var(--fs-16);font-weight:var(--fw-bold);
  letter-spacing:-.02em;color:var(--ink);
  font-variant-numeric:tabular-nums;
}
.balance .b-val em{font-size:var(--fs-11);font-weight:var(--fw-medium);color:var(--sub);font-style:normal;margin-left:2px}

.who{
  display:flex;align-items:center;gap:var(--sp-2);
  padding:0 var(--sp-2);
  color:var(--sub);font-size:var(--fs-12);
}
.who svg{width:14px;height:14px;stroke-width:1.6;flex-shrink:0}
.who b{color:var(--ink-2);font-weight:var(--fw-medium);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.who .logout{
  margin-left:auto;color:var(--sub-2);font-size:var(--fs-11);
  display:inline-flex;align-items:center;gap:4px;
  transition:color var(--dur-fast) var(--ease-in-out-quint);
}
.who .logout:hover{color:var(--bad)}
.who .logout svg{width:13px;height:13px}

/* 主题切换：放在账户行下方，整宽一行（不做悬浮按钮，避免遮挡内容） */
.theme-toggle{
  display:flex;align-items:center;gap:var(--sp-3);
  width:100%;text-align:left;
  padding:var(--sp-2) var(--sp-2);
  border-radius:var(--r-sm);
  color:var(--sub);font-size:var(--fs-12);
  transition:background-color var(--dur-fast) var(--ease-in-out-quint),
             color var(--dur-fast) var(--ease-in-out-quint);
}
.theme-toggle:hover{background:var(--surface-hover);color:var(--ink)}
.theme-toggle svg{stroke-width:1.6;flex-shrink:0}

/* ---------- 主内容区 ---------- */
.main{
  min-width:0;
  padding:var(--sp-8) var(--sp-10) var(--sp-24);
  position:relative;
  /* ⚠️ boot() 用 inline 样式把 #appView 写成 display:flex 来显示主界面，
     而那会盖掉本文件 .app 的 display:grid —— 实际跑的是 flex 布局，
     于是主区按 flex:0 1 auto 收缩到「内容宽度」，内容越窄右边空得越多。
     实测 1440 视口：出片任务 598px、视频高清化 679px、工作台 879px、
     复盘 1136px（可用宽度本该是 1194px）。
     这里显式让它吃掉剩余宽度，和 grid 版 minmax(0,1fr) 的意图对齐。 */
  flex:1 1 auto;
}
.main > .page-wrap{max-width:var(--wrap-app);margin:0 auto}
/* 版心：主区铺满后，宽屏上内容不能无限拉宽（--wrap-app = 1240px）。
   原来只有 .page-wrap 这一条，但 HTML 里并没有包这一层，
   所以版心从来没生效过 —— 主区一撑满，2560 的屏上正文会到 2230px。 */
.main > .page{max-width:var(--wrap-app);margin:0 auto}

.page{display:none}
.page.on{display:block;animation:page-in var(--dur-slow) var(--ease-out-quint)}
@keyframes page-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}

/* 页头 */
.page-head{
  display:flex;align-items:flex-end;justify-content:space-between;
  gap:var(--sp-4);flex-wrap:wrap;
  margin-bottom:var(--sp-8);
}
.page-head h1{
  font-size:var(--fs-24);
  font-weight:var(--fw-semibold);
  letter-spacing:var(--ls-title);
  line-height:var(--lh-title);
}
.page-head p{color:var(--sub);font-size:var(--fs-13);margin-top:var(--sp-1)}

/* ---------- 统计卡组：不对称网格（破三等分） ---------- */
.stat-grid{
  display:grid;
  grid-template-columns:repeat(4,minmax(0,1fr));
  gap:var(--sp-3);
}
.stat-grid .stat.wide{grid-column:span 2}
.stat{
  padding:var(--sp-4) var(--sp-5);
  position:relative;
  overflow:hidden;
}
.stat .s-label{
  display:flex;align-items:center;gap:var(--sp-2);
  font-size:var(--fs-11);color:var(--sub);
  letter-spacing:var(--ls-label);
  font-weight:var(--fw-medium);
}
.stat .s-label svg{width:13px;height:13px;stroke-width:1.6;opacity:.8}
.stat .s-val{
  display:flex;align-items:baseline;gap:var(--sp-2);
  margin-top:var(--sp-3);
}
.stat .s-val b{
  font-size:var(--fs-28);
  font-weight:var(--fw-bold);
  letter-spacing:-.035em;
  line-height:1;
  font-variant-numeric:tabular-nums;
}
.stat .s-val em{
  font-size:var(--fs-12);color:var(--sub);
  font-style:normal;font-weight:var(--fw-medium);
}
.stat .s-note{font-size:var(--fs-11);color:var(--sub-2);margin-top:var(--sp-2)}

/* ---------- 通用栅格 ---------- */
.grid2{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--sp-4)}
.grid3{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--sp-4)}

/* 卡片内边距 */
.pad{padding:var(--sp-5) var(--sp-6)}

/* ---------- 生成器布局 ---------- */
.gen-layout{
  display:grid;
  grid-template-columns:minmax(440px,520px) minmax(0,1fr);
  gap:var(--sp-4);
  align-items:start;
}

/* 工具栏：操作条 */
.toolbar{
  display:flex;gap:var(--sp-2);
  margin-bottom:var(--sp-3);
  align-items:center;flex-wrap:wrap;
}
.toolbar .grow{flex:1;min-width:170px}
.toolbar select{
  width:100%;
  border:1px solid var(--line);
  border-radius:var(--r-md);
  padding:8px var(--sp-3);
  background:var(--surface);
  font-size:var(--fs-12);
  outline:none;
  color:var(--ink);
  transition:border-color var(--dur-fast) var(--ease-in-out-quint);
}
.toolbar select:hover{border-color:var(--line-strong)}
.toolbar select:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-ring)}

/* 时长预设：两段式切换 */
.presets{
  display:grid;grid-template-columns:1fr 1fr;
  gap:var(--sp-2);
  margin-bottom:var(--sp-4);
}
.preset{
  border:1px solid var(--line);
  border-radius:var(--r-md);
  padding:var(--sp-3) var(--sp-4);
  background:var(--surface);
  text-align:left;
  box-shadow:var(--sh-hairline);
  transition:
    border-color var(--dur-fast) var(--ease-in-out-quint),
    background-color var(--dur-fast) var(--ease-in-out-quint),
    box-shadow var(--dur-base) var(--ease-soft);
}
.preset:hover{border-color:var(--line-strong)}
.preset.on{
  border-color:var(--accent);
  background:var(--accent-soft);
  box-shadow:0 0 0 3px var(--accent-ring);
}
.preset .p-top{display:flex;align-items:center;gap:var(--sp-2);font-size:var(--fs-13);font-weight:var(--fw-semibold)}
.preset .p-top svg{width:15px;height:15px;stroke-width:1.7;color:var(--accent)}
.preset small{display:block;color:var(--sub);font-size:var(--fs-11);margin-top:3px}

/* 创意面板（ideas / hook 变体） */
.idea-panel{
  border:1px solid var(--line);
  border-radius:var(--r-lg);
  background:var(--surface);
  padding:var(--sp-4);
  margin-bottom:var(--sp-3);
  box-shadow:var(--sh-sm);
  display:none;
}
.idea-panel.show{display:block;animation:page-in var(--dur-base) var(--ease-out-quint)}
.idea-panel .ip-head{
  display:flex;align-items:center;justify-content:space-between;
  gap:var(--sp-3);margin-bottom:var(--sp-3);
}
.idea-panel .ip-head b{font-size:var(--fs-12);font-weight:var(--fw-semibold)}
.idea-panel .ip-head span{font-size:var(--fs-11);color:var(--sub-2)}

.idea-item{
  padding:var(--sp-3);
  border-radius:var(--r-md);
  cursor:pointer;
  border:1px solid transparent;
  text-align:left;width:100%;
  transition:
    background-color var(--dur-fast) var(--ease-in-out-quint),
    border-color var(--dur-fast) var(--ease-in-out-quint);
}
.idea-item:hover{background:var(--surface-hover);border-color:var(--line)}
.idea-item b{font-size:var(--fs-12);display:block;font-weight:var(--fw-semibold);line-height:var(--lh-tight)}
.idea-item span{font-size:var(--fs-11);color:var(--sub);display:block;margin-top:3px;line-height:var(--lh-tight)}

/* 数据推荐条 */
.insight-bar{
  display:flex;align-items:center;gap:var(--sp-4);
  padding:var(--sp-3) var(--sp-4);
  border-radius:var(--r-lg);
  background:var(--accent-soft);
  border:1px solid var(--accent-ring);
  font-size:var(--fs-12);
  margin-bottom:var(--sp-4);
  flex-wrap:wrap;
}
.insight-bar .ib-ico{color:var(--accent);display:flex}
.insight-bar .ib-ico svg{width:16px;height:16px;stroke-width:1.7}
.insight-bar .ib-main{flex:1;min-width:200px;line-height:var(--lh-tight)}
.insight-bar .ib-main b{color:var(--accent);font-weight:var(--fw-semibold)}
.insight-bar .ib-sub{font-size:var(--fs-11);color:var(--sub);margin-top:2px}

/* ---------- 选择器口径说明 ----------
 * 图库页按「主肖像 + 变体」计数（每角色 4 张），而这里只列变体（每角色 3 张）。
 * 两处对照会让人以为「少图」，所以把口径写在这行，顺带说明主肖像为何不作首帧：
 * 它是米灰墙前的大头照（生成指令写死「肩膀以上构图」），当首帧会让背景与提示词的
 * 场景直接打架，且人货同框演示缺身体。定位是生成变体时的身份锚点，不是成片素材。 */
.pick-head{
  display:flex;align-items:baseline;justify-content:space-between;
  gap:var(--sp-3);flex-wrap:wrap;
  margin-bottom:var(--sp-2);
}
.pick-head b{font-size:var(--fs-12);font-weight:var(--fw-medium);color:var(--ink-2)}
.pick-head span{font-size:var(--fs-11);color:var(--sub);line-height:var(--lh-tight)}

/* ---------- 角色选择：按角色分组 ----------
 * 结构：.char-pick(滚动容器) > .cp-group(一位角色) > [.cp-head 组头 + .cp-row 缩略图行]
 * 为什么分组而不是平铺 12 张：适配品类是**角色级**属性，平铺会把同一份品类
 * 重复 3 遍（每个角色 3 张图），既看不到「谁适合我这件货」，也没地方放组头信息。 */
.char-pick{
  /* ⚠️ 缩略图高度写**固定值**，不交给 aspect-ratio。
   *    aspect-ratio 会让行高随容器宽度变化（104px 宽 → 139px 高），max-height 就永远对不齐
   *    「整行」，于是滚动区域会在第 n+1 行的图片中间拦腰裁断，看上去就是「图片坏了 / 缺图」。
   *    （早先 max-height:250px 只够 1.2 行，第二行整排只剩一条头顶，就是这个原因。）
   *    固定高度后：行高 = 缩略图 139 + 标签 38 + 边框 3 = 180px，与宽度无关。 */
  --thumb-h:139px;
  --row-h:180px;
  /* 组头高度也必须固定，否则 --grp-h 算不准，滚动区会在组边界之外露出半张图。
   *   公式值：组头 44(第一行 26 + 行距 2 + 品类行 16) + 组头下边距 8 + 缩略图行 180 = 232。
   *   实测值：组高 231、组间距 16、容器内边距 2（缩略图行实测 179，比公式少 1px，
   *          属边框的次像素取整；多出的 1px 只当余量用，不影响判定）。
   *   ⚠️ 徽标在组头里被压到 20px 高（.cp-head .chip），否则它自己的上下内边距
   *      会把第一行撑到 27px，--grp-h 立刻失准。
   *   ⚠️ 组高与宽度无关（缩略图 height 写死 139，标签写死 38），所以窄屏换 3 列也不会
   *      改变组高。前提是「每位角色的变体不超过列数」——也就是 3 张，这正是图库的约定。 */
  --head-h:44px;
  --head-gap:var(--sp-2);
  --grp-h:calc(var(--head-h) + var(--head-gap) + var(--row-h));
  display:block;
  /* max-height 必须落在「第 2 组底边」与「第 3 组顶边」之间那个空隙里，否则滚动区
   * 一定会在整组之外露出半张图。按公式值算这个窗口是 [482, 498)：
   *   第 2 组底边 = 内边距 2 + 组高 232×2 + 间距 16      = 482
   *   第 3 组顶边 = 内边距 2 + 组高 232×2 + 间距 16×2    = 498
   * 取窗口中偏上的 492（= 232×2 + 16 + 12），两侧各留 ~8px 余量。
   * ⚠️ 别把余量删掉换成「刚好贴住 2 组」：那样任何 1~2px 的字体/边框变化都会让第 3 组
   *    露出标题那一小条，比多留 12px 空白难看得多。 */
  max-height:calc(var(--grp-h) * 2 + var(--sp-4) + var(--sp-3));
  overflow-y:auto;
  padding:2px;
  scrollbar-gutter:stable;
}
.cp-group + .cp-group{margin-top:var(--sp-4)}
.cp-head{
  display:flex;flex-direction:column;gap:2px;
  height:var(--head-h);
  margin-bottom:var(--head-gap);
}
.cp-l1{display:flex;align-items:center;gap:var(--sp-2);height:26px;min-width:0}
.cp-l1 b{
  font-size:var(--fs-12);font-weight:var(--fw-semibold);color:var(--ink);
  flex-shrink:0;font-variant-numeric:tabular-nums;
}
.cp-persona{
  font-size:var(--fs-11);color:var(--sub);flex:1 1 auto;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
/* 徽标在组头里必须瘦身：默认 .chip 的内边距会把这一行从 26px 撑到 33px，
   --grp-h 随之失准，滚动区就会在图片中间裁断。用 border-box 反推：
   20 = 边框 2 + 内容 18，所以 line-height 写 18px。 */
.cp-head .chip{height:20px;padding:0 var(--sp-2);line-height:18px;flex-shrink:0}
.cp-cats{
  height:16px;line-height:16px;font-size:var(--fs-11);color:var(--sub);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
/* 适配品类是用户最需要一眼看到的信息，用强调色而不是灰字 */
.cp-cats b{color:var(--accent);font-weight:var(--fw-medium)}
.cp-forms{color:var(--sub);opacity:.8}
/* 一列 3 张：图库约定每位角色就是 3 张场景变体，所以 3 列正好排满一行。
 * 用 4 列会每行右边空一格（看起来像缺图），用 3 列则每张更宽、人脸侧裁更少。
 * ⚠️ 别在窄屏把列数降到 2：3 张变体会折成 2 行，组高翻倍，--grp-h 立刻失准，
 *    max-height 就会在图片中间拦腰截断。所有宽度都用 3 列，靠缩略图自适应变窄即可。 */
.cp-row{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--sp-2)}
/* ---------- 选人区的筛选口径条 ---------- */
.pick-filter{
  display:flex;align-items:center;gap:var(--sp-2);flex-wrap:wrap;
  margin-top:var(--sp-2);padding:var(--sp-2) var(--sp-3);
  border-radius:var(--r-sm);
  background:var(--accent-soft);border:1px solid var(--accent-ring);
}
.pick-filter .i-icon{width:14px;height:14px;color:var(--accent);flex-shrink:0}
.pick-filter .pf-txt{font-size:var(--fs-11);color:var(--ink-2);line-height:var(--lh-tight);flex:1 1 auto;min-width:0}
.pick-filter .pf-txt b{color:var(--accent);font-weight:var(--fw-semibold)}
.pick-filter .btn{margin-left:auto;flex-shrink:0}
/* 字段下方的补充说明（当前用于「场景由首帧图决定」） */
.field-note{
  font-size:var(--fs-11);color:var(--sub);line-height:var(--lh-tight);
  margin-top:calc(-1 * var(--sp-1));
}
.char-item{
  border:1.5px solid var(--line);
  border-radius:var(--r-md);
  overflow:hidden;
  cursor:pointer;
  background:var(--surface);
  padding:0;
  text-align:left;
  transition:
    border-color var(--dur-fast) var(--ease-in-out-quint),
    box-shadow var(--dur-base) var(--ease-soft),
    transform var(--dur-base) var(--ease-soft);
}
.char-item:hover{border-color:var(--line-strong);transform:translateY(-2px)}
.char-item img{
  width:100%;height:var(--thumb-h);object-fit:cover;display:block;
  background:var(--surface-hover);
}
/* ⚠️ 标记必须是 small（渲染时写的就是 <small>）。早先这条选择器写成 .c-name 属于**死规则**，
 * 于是「单行 + 省略号 + 内边距」从未生效，长标签会折成 2~3 行，把整行卡高在 188 / 211 之间乱跳。
 * 现在夹到固定两行：行高稳定，同时比单行省略能看到更多字
 * （卡宽仅约 106px，单行只放得下 9 个字，「char_001 · 浅灰色宽松连帽卫衣」会被截成半句）。
 * ⚠️ 全局 `box-sizing:border-box`，height 必须把 8px 上下内边距**算进去**：
 *    38 = 内边距 8 + 行高 15 × 2。写成 32 会让内容区只剩 24px，第二行被纵向切掉半截。 */
.char-item small{
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  height:38px;
  padding:var(--sp-1) var(--sp-2);
  font-size:var(--fs-11);line-height:15px;color:var(--sub);
  overflow:hidden;
}
.char-item.on{
  border-color:var(--accent);
  box-shadow:0 0 0 3px var(--accent-ring);
}

/* ---------- 输出区 ---------- */
.output-box{
  background:var(--bg-sunken);
  color:var(--ink-2);
  border:1px solid var(--line);
  border-radius:var(--r-lg);
  padding:var(--sp-4) var(--sp-5);
  font-family:var(--font-mono);
  font-size:var(--fs-12);
  line-height:1.82;
  white-space:pre-wrap;
  word-break:break-word;
  max-height:620px;
  overflow:auto;
  box-shadow:var(--sh-inset-lo);
}
.output-box .out-placeholder{color:var(--sub-2);font-family:var(--font-sans)}

/* 输出工具条 */
.out-tools{display:flex;gap:var(--sp-2);flex-wrap:wrap}

/* 质检结果 */
.qc{display:flex;flex-wrap:wrap;gap:var(--sp-2);margin-top:var(--sp-4)}
.qc .chip svg{width:12px;height:12px;stroke-width:2}

/* ---------- 上传区 ---------- */
.up-drop{
  border:1.5px dashed var(--line-strong);
  border-radius:var(--r-lg);
  padding:var(--sp-6) var(--sp-4);
  text-align:center;
  cursor:pointer;
  color:var(--sub);
  font-size:var(--fs-12);
  line-height:1.7;
  background:var(--surface-2);
  transition:
    border-color var(--dur-fast) var(--ease-in-out-quint),
    background-color var(--dur-fast) var(--ease-in-out-quint),
    color var(--dur-fast) var(--ease-in-out-quint);
  display:flex;flex-direction:column;align-items:center;gap:var(--sp-2);
}
.up-drop:hover{border-color:var(--accent);color:var(--ink);background:var(--accent-soft)}
.up-drop .d-ico{color:var(--accent);display:flex;margin-bottom:2px}
.up-drop .d-ico svg{width:22px;height:22px;stroke-width:1.5}
.up-drop b{color:var(--ink);font-size:var(--fs-13);font-weight:var(--fw-semibold);display:block}
.up-drop .d-hint{font-size:var(--fs-11);color:var(--sub-2);max-width:340px}

.up-thumbs{
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  gap:var(--sp-2);margin-top:var(--sp-3);
}
.up-thumb{
  position:relative;border:1.5px solid var(--line);
  border-radius:var(--r-md);overflow:hidden;
  aspect-ratio:3/4;background:var(--surface-2);
}
.up-thumb.main{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-ring)}
.up-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.up-thumb .tag{
  position:absolute;left:5px;top:5px;
  background:var(--accent);color:var(--accent-ink);
  font-size:9.5px;font-weight:var(--fw-bold);
  padding:2px var(--sp-2);border-radius:var(--r-sm);
  letter-spacing:.02em;
}
.up-thumb .del{
  position:absolute;right:5px;top:5px;
  width:22px;height:22px;border-radius:var(--r-full);
  background:var(--scrim);color:#fff;
  display:flex;align-items:center;justify-content:center;
  backdrop-filter:blur(6px);
  transition:background-color var(--dur-fast) var(--ease-in-out-quint);
}
.up-thumb .del:hover{background:var(--bad)}
.up-thumb .del svg{width:11px;height:11px;stroke-width:2.2}
/* 已上传图片管理（个保法删除权入口）。
   用 <details> 而不是自绘折叠：展开收起是浏览器原生的，键盘与读屏一并自带，
   省掉一套 JS 状态。summary 要手动去掉默认三角，换成与全站一致的细箭头。 */
.myimgs{
  margin-top:var(--sp-4);
  border:1px solid var(--line);border-radius:var(--r-md);
  background:var(--surface-2);
}
.myimgs summary{
  cursor:pointer;list-style:none;
  padding:var(--sp-3) var(--sp-4);
  font-size:var(--fs-12);font-weight:var(--fw-semibold);color:var(--ink-2);
  display:flex;align-items:center;gap:var(--sp-2);
}
.myimgs summary::-webkit-details-marker{display:none}
.myimgs summary::after{
  content:"";margin-left:auto;
  width:6px;height:6px;
  border-right:1.5px solid var(--sub);border-bottom:1.5px solid var(--sub);
  transform:rotate(45deg);
  transition:transform var(--dur-fast) var(--ease-in-out-quint);
}
.myimgs[open] summary::after{transform:rotate(-135deg)}
.myimgs .mi-hint{font-weight:var(--fw-regular);color:var(--sub-2);font-size:var(--fs-11)}
.myimgs-grid{
  display:grid;grid-template-columns:repeat(auto-fill,minmax(56px,1fr));
  gap:var(--sp-2);padding:0 var(--sp-4) var(--sp-4);
}
.mi-item{position:relative;border-radius:var(--r-sm);overflow:hidden;aspect-ratio:3/4;background:var(--surface)}
.mi-item img{width:100%;height:100%;object-fit:cover;display:block}
.mi-del{
  position:absolute;right:3px;top:3px;
  width:20px;height:20px;border-radius:var(--r-full);
  background:var(--scrim);color:#fff;
  display:flex;align-items:center;justify-content:center;
  transition:background-color var(--dur-fast) var(--ease-in-out-quint);
}
.mi-del:hover{background:var(--bad)}
.mi-del svg{width:11px;height:11px;stroke-width:2.2}
.mi-empty{padding:var(--sp-4);font-size:var(--fs-12);color:var(--sub-2);text-align:center}
/* 去标识申请（《标识办法》第九条）。
   刻意做成"收起 + 默认不勾"：这一档是例外路径，不该在视觉上与正常出片等重；
   但也不能藏进设置页深处，它改变的是这条片子的合规属性，必须按下去之前看得见。 */
.stripbox{
  margin-top:var(--sp-4);
  border:1px solid var(--line);border-radius:var(--r-md);
  background:var(--surface-2);
}
.stripbox summary{
  cursor:pointer;list-style:none;
  padding:var(--sp-3) var(--sp-4);
  font-size:var(--fs-12);color:var(--ink-2);
  display:flex;align-items:center;gap:var(--sp-2);
}
.stripbox summary::-webkit-details-marker{display:none}
.stripbox summary::after{
  content:"";margin-left:auto;
  width:6px;height:6px;
  border-right:1.5px solid var(--sub);border-bottom:1.5px solid var(--sub);
  transform:rotate(45deg);
  transition:transform var(--dur-fast) var(--ease-in-out-quint);
}
.stripbox[open] summary::after{transform:rotate(-135deg)}
.sb-body{padding:0 var(--sp-4) var(--sp-4);display:flex;flex-direction:column;gap:var(--sp-3)}
.sb-note{
  margin:0;font-size:var(--fs-11);line-height:var(--lh-body);
  color:var(--sub);background:var(--surface);
  border-radius:var(--r-sm);padding:var(--sp-3);
}
.sb-note strong{color:var(--ink-2)}
.sb-check{
  display:flex;align-items:flex-start;gap:var(--sp-2);
  font-size:var(--fs-11);color:var(--ink-2);line-height:var(--lh-body);cursor:pointer;
}
.sb-check input{margin-top:2px;flex:0 0 auto}
.up-thumb .fname{
  position:absolute;left:0;right:0;bottom:0;
  background:var(--glass-bg);
  backdrop-filter:blur(10px);
  font-size:9.5px;color:var(--ink-2);
  padding:3px var(--sp-2);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}

/* 复选框组 */
.checks{display:flex;gap:var(--sp-5);flex-wrap:wrap}
.checks label{
  display:flex;align-items:center;gap:var(--sp-2);
  font-size:var(--fs-12);color:var(--ink-2);
  cursor:pointer;user-select:none;
}
.checks input[type=checkbox]{
  appearance:none;
  width:16px;height:16px;border-radius:5px;
  border:1.5px solid var(--line-strong);
  background:var(--surface);
  display:grid;place-content:center;
  transition:
    background-color var(--dur-fast) var(--ease-in-out-quint),
    border-color var(--dur-fast) var(--ease-in-out-quint);
  flex-shrink:0;
}
.checks input[type=checkbox]::after{
  content:"";width:9px;height:9px;
  transform:scale(0);
  transition:transform var(--dur-fast) var(--ease-soft);
  background:var(--accent-ink);
  clip-path:polygon(14% 44%,0 65%,50% 100%,100% 16%,80% 0%,43% 62%);
}
.checks input[type=checkbox]:checked{background:var(--accent);border-color:var(--accent)}
.checks input[type=checkbox]:checked::after{transform:scale(1)}

/* ---------- 出片设置 ---------- */
.price-note{
  font-size:var(--fs-12);color:var(--sub);
  margin-top:var(--sp-3);
  padding:var(--sp-3);
  background:var(--surface-2);
  border-radius:var(--r-md);
  border:1px solid var(--line);
  line-height:var(--lh-tight);
}
.price-note b{color:var(--ink);font-weight:var(--fw-semibold);font-variant-numeric:tabular-nums}

/* 出片按钮的「积分不足」态：直接把按钮本身变成充值入口。
 * 目的：不让用户填完 8 个字段、点提交才被告知要付钱 —— 那是流失点，不是付费点。 */
.btn.btn-lack{
  background:var(--bad-soft);color:var(--bad);
  border:1px solid var(--bad);box-shadow:none;
}
.btn.btn-lack:hover{background:var(--bad-soft);border-color:var(--bad);box-shadow:none}

/* ---------- 图库 ---------- */
.lib-group{margin-bottom:var(--sp-8)}
.lib-group-head{
  display:flex;align-items:center;gap:var(--sp-3);
  margin-bottom:var(--sp-4);
}
.lib-group-head h2{
  font-size:var(--fs-14);font-weight:var(--fw-semibold);
  letter-spacing:-.01em;
}
.lib-group-head .count{
  font-size:var(--fs-11);color:var(--sub-2);
  font-variant-numeric:tabular-nums;
}
.lib-group-head .rule{flex:1;height:1px;background:var(--line)}
/* 适配品类行：图库页的「这位能带哪些货」。与选人区组头同一份数据（meta.json 的 categories
 * 与 forms），措辞也保持一致，避免两处说法不同让人以为是两套标准。 */
.lib-cats{
  font-size:var(--fs-12);color:var(--sub);line-height:var(--lh-tight);
  margin:-8px 0 12px;max-width:var(--measure);
}
.lib-cats b{color:var(--accent);font-weight:var(--fw-medium)}
.lib-forms{color:var(--sub);opacity:.85}
.lib-grid{
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(122px,1fr));
  gap:var(--sp-3);
}
.lib-card{
  border-radius:var(--r-md);
  overflow:hidden;
  background:var(--surface);
  border:1px solid var(--line);
  box-shadow:var(--sh-hairline);
  transition:
    transform var(--dur-base) var(--ease-soft),
    box-shadow var(--dur-base) var(--ease-soft),
    border-color var(--dur-base) var(--ease-in-out-quint);
}
.lib-card:hover{transform:translateY(-3px);box-shadow:var(--sh-md);border-color:var(--line-strong)}
.lib-card img{width:100%;aspect-ratio:3/4;object-fit:cover;display:block;background:var(--surface-hover)}
.lib-card .lc-body{padding:var(--sp-2) var(--sp-3) var(--sp-3)}
.lib-card .lc-id{
  font-family:var(--font-mono);
  font-size:var(--fs-11);font-weight:var(--fw-semibold);
  color:var(--ink);
  letter-spacing:.01em;
}
.lib-card .lc-meta{font-size:var(--fs-11);color:var(--sub);margin-top:1px;line-height:var(--lh-tight)}
.lib-card .lc-vars{display:flex;gap:3px;margin-top:var(--sp-2)}
.lib-card .lc-vars i{
  width:100%;height:3px;border-radius:var(--r-full);
  background:var(--line-strong);
}
.lib-card .lc-vars i.on{background:var(--accent)}

/* ---------- 出片任务队列 ---------- */
.job-row{
  display:grid;
  grid-template-columns:auto minmax(0,1fr) auto;
  gap:var(--sp-4);
  align-items:center;
  padding:var(--sp-4) 0;
  border-bottom:1px solid var(--line);
}
.job-row:last-child{border-bottom:none}
.job-thumb{
  width:64px;aspect-ratio:9/16;
  border-radius:var(--r-sm);
  background:var(--bg-sunken);
  border:1px solid var(--line);
  display:flex;align-items:center;justify-content:center;
  color:var(--sub-2);
  overflow:hidden;
  flex-shrink:0;
}
.job-thumb svg{width:16px;height:16px;stroke-width:1.5}
.job-thumb video{width:100%;height:100%;object-fit:cover;border-radius:0;background:none;max-width:none}
/* 有片子的缩略图是可点的封面，点开放大播放。
   64px 的小窗只够看「有没有画面」，「镜头是否拍歪、产品是否糊」必须放大才判得出来，
   所以列表里只当封面用，真看细节走播放层。 */
.job-thumb.playable{position:relative;padding:0;cursor:pointer}
.job-thumb.playable video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;background:#08080A}
.job-thumb .thumb-play{position:absolute;inset:0;z-index:1;display:grid;place-items:center;pointer-events:none}
.job-thumb .thumb-play svg{
  width:15px;height:15px;padding:7px;box-sizing:content-box;
  border-radius:var(--r-full);
  background:rgba(8,8,12,.55);color:#fff;stroke-width:2;
  transition:transform var(--dur-fast) var(--ease-in-out-quint), background var(--dur-fast) var(--ease-in-out-quint);
}
.job-thumb.playable:hover .thumb-play svg{transform:scale(1.08);background:rgba(8,8,12,.78)}
.job-thumb.playable:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.job-main{min-width:0}
.job-main .j-title{
  font-size:var(--fs-13);font-weight:var(--fw-medium);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.job-main .j-meta{
  display:flex;gap:var(--sp-3);flex-wrap:wrap;
  font-size:var(--fs-11);color:var(--sub);margin-top:var(--sp-1);
  font-variant-numeric:tabular-nums;
}
.job-side{display:flex;align-items:center;gap:var(--sp-2);flex-shrink:0}
/* 任务备注行。颜色由任务所处阶段决定，不是固定报错色：
 * 一个正在自动重试的任务用红色报错会让人以为已经失败、钱白花了。 */
.job-note{
  font-size:var(--fs-11);line-height:var(--lh-tight);
  margin-top:var(--sp-1);color:var(--sub-2);
}
.job-note.bad{color:var(--bad)}
.job-note.warn{color:var(--warn)}

/* 进度条：无填充底轨的克制做法 */
.progress{
  height:3px;border-radius:var(--r-full);
  background:var(--line);overflow:hidden;
  margin-top:var(--sp-2);
}
.progress i{
  display:block;height:100%;
  background:var(--accent);border-radius:var(--r-full);
  transition:width var(--dur-slow) var(--ease-out-quint);
}

/* ---------- 订单 / 流水 ---------- */
.order-tabs{display:flex;gap:var(--sp-2);margin-bottom:var(--sp-4);flex-wrap:wrap}
.order-tabs button{
  padding:5px var(--sp-3);
  border-radius:var(--r-full);
  font-size:var(--fs-12);font-weight:var(--fw-medium);
  color:var(--sub);
  border:1px solid var(--line);
  background:var(--surface);
  transition:all var(--dur-fast) var(--ease-in-out-quint);
}
.order-tabs button:hover{border-color:var(--line-strong);color:var(--ink)}
.order-tabs button.on{background:var(--ink);border-color:var(--ink);color:var(--bg);font-weight:var(--fw-semibold)}

/* ---------- 充值卡 ---------- */
.plan-row{
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(148px,1fr));
  gap:var(--sp-3);
}
.plan{
  position:relative;
  border:1.5px solid var(--line);
  border-radius:var(--r-lg);
  background:var(--surface);
  padding:var(--sp-4) var(--sp-4) var(--sp-3);
  cursor:pointer;
  text-align:left;
  font:inherit;
  transition:
    border-color var(--dur-fast) var(--ease-in-out-quint),
    transform var(--dur-base) var(--ease-soft),
    box-shadow var(--dur-base) var(--ease-soft);
}
.plan:hover{border-color:var(--line-strong);transform:translateY(-2px);box-shadow:var(--sh-md)}
.plan.on{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-ring)}
.plan .p-price{
  font-size:var(--fs-24);font-weight:var(--fw-bold);
  letter-spacing:-.04em;line-height:1;
  font-variant-numeric:tabular-nums;
}
.plan .p-price sup{font-size:var(--fs-13);font-weight:var(--fw-medium);margin-right:1px;vertical-align:top;line-height:1.6}
.plan .p-points{
  font-size:var(--fs-12);color:var(--sub);margin-top:var(--sp-2);
  font-variant-numeric:tabular-nums;
}
.plan .p-points b{color:var(--accent);font-weight:var(--fw-semibold)}
.plan .p-unit{
  font-size:var(--fs-11);color:var(--sub-2);margin-top:var(--sp-2);
  padding-top:var(--sp-2);border-top:1px solid var(--line);
}
.plan .p-badge{
  position:absolute;top:-9px;left:var(--sp-3);
  background:var(--accent);color:var(--accent-ink);
  font-size:9.5px;font-weight:var(--fw-bold);
  padding:2px var(--sp-2);border-radius:var(--r-sm);
  letter-spacing:.03em;
}
.plan.dis{opacity:.4;cursor:not-allowed}
.plan.dis:hover{transform:none;border-color:var(--line);box-shadow:none}

/* 折算说明 */
.conv{
  display:flex;flex-wrap:wrap;gap:var(--sp-2) var(--sp-6);
  font-size:var(--fs-12);color:var(--sub);
  margin-top:var(--sp-4);padding-top:var(--sp-4);
  border-top:1px solid var(--line);
}
.conv b{color:var(--ink);font-weight:var(--fw-semibold);font-variant-numeric:tabular-nums}

/* ---------- 收银弹层 ---------- */
.modal{
  position:fixed;inset:0;
  background:var(--scrim);
  backdrop-filter:blur(6px);
  display:none;
  align-items:center;justify-content:center;
  z-index:var(--z-modal);
  padding:var(--sp-4);
}
.modal.show{display:flex;animation:fade-in var(--dur-base) var(--ease-out-quint)}
@keyframes fade-in{from{opacity:0}to{opacity:1}}

.modal .box{
  width:360px;max-width:100%;
  border-radius:var(--r-xl);
  background:var(--surface);
  border:1px solid var(--line);
  box-shadow:var(--sh-lg), var(--sh-inset-hi);
  padding:var(--sp-6);
  text-align:center;
  position:relative;
  animation:modal-in var(--dur-slow) var(--ease-soft);
}
@keyframes modal-in{
  from{opacity:0;transform:translateY(16px) scale(.97)}
  to{opacity:1;transform:none}
}
.modal .x{
  position:absolute;top:var(--sp-3);right:var(--sp-3);
  width:30px;height:30px;border-radius:var(--r-sm);
  display:flex;align-items:center;justify-content:center;
  color:var(--sub);
  transition:all var(--dur-fast) var(--ease-in-out-quint);
}
.modal .x:hover{background:var(--surface-hover);color:var(--ink)}
.modal .x svg{width:15px;height:15px;stroke-width:1.8}
.modal h3{
  font-size:var(--fs-16);font-weight:var(--fw-semibold);
  letter-spacing:-.015em;margin-bottom:var(--sp-1);
}
.modal .m-sub{font-size:var(--fs-11);color:var(--sub-2);font-family:var(--font-mono)}

.modal .qr{
  width:196px;height:196px;
  margin:var(--sp-5) auto var(--sp-4);
  border:1px solid var(--line);
  border-radius:var(--r-lg);
  overflow:hidden;
  background:#fff;
  display:flex;align-items:center;justify-content:center;
  padding:var(--sp-3);
}
.modal .qr svg{width:100%;height:100%;display:block}
.modal .qr .ph{font-size:var(--fs-11);color:var(--sub-2);font-family:var(--font-sans)}

.modal .amt{
  font-size:var(--fs-28);font-weight:var(--fw-bold);
  letter-spacing:-.04em;color:var(--ink);
  font-variant-numeric:tabular-nums;
}
.modal .amt sup{font-size:var(--fs-16);font-weight:var(--fw-medium);margin-right:2px;vertical-align:top;line-height:1.5}
.modal .meta{font-size:var(--fs-12);color:var(--sub);margin-top:var(--sp-1)}
.modal .meta b{color:var(--ink);font-weight:var(--fw-semibold);font-variant-numeric:tabular-nums}
.modal .row{display:flex;gap:var(--sp-2);margin-top:var(--sp-5)}
.modal .row>*{flex:1}
.modal .m-hint{
  font-size:var(--fs-11);color:var(--sub-2);
  margin-top:var(--sp-4);line-height:var(--lh-tight);
}

/* ---------- 收银弹层：人工通道（PAY_CHANNEL=manual） ---------- */
/* 把「扫码」换成「订单号 + 一键复制 + 三步指引」。
   注意下面两条 [hidden] 规则是**必须显式写**的，不能只靠 hidden 属性：
   .modal .qr 是类选择器给的 display:flex，specificity 高于 [hidden] 的 UA 规则，
   属性设了元素照样显示（同样踩过 .seg-item 那个坑）。
   .m-manual 默认 display:block，所以它也要自己写一条 [hidden]。 */
.modal .qr[hidden]{display:none}
.modal .m-manual{display:block;margin:var(--sp-5) 0 var(--sp-4)}
.modal .m-manual[hidden]{display:none}
.modal .mm-tip{font-size:var(--fs-11);color:var(--sub-2);margin-bottom:var(--sp-2)}
.modal .mm-tip b{color:var(--ink);font-weight:var(--fw-semibold)}
.modal .mm-no{
  font-family:var(--font-mono);
  font-size:var(--fs-16);font-weight:var(--fw-semibold);letter-spacing:.01em;
  color:var(--ink);word-break:break-all;
  background:var(--surface-hover);border:1px dashed var(--line-strong);
  border-radius:var(--r-md);padding:var(--sp-3) var(--sp-2);
  user-select:all;
}
.modal .mm-copy{width:100%;margin-top:var(--sp-3);justify-content:center}
.modal .mm-steps{
  text-align:left;margin:var(--sp-5) 0 0;padding-left:1.2em;
  font-size:var(--fs-12);color:var(--sub);line-height:var(--lh-body);
}
.modal .mm-steps li{margin-bottom:var(--sp-1)}
.modal .mm-steps b{color:var(--ink);font-weight:var(--fw-semibold)}

/* 后台订单对账：按订单号查找 */
.ord-search{display:flex;gap:var(--sp-2);margin-bottom:var(--sp-4);flex-wrap:wrap}
.ord-search input{flex:1;min-width:180px}

/* ---------- 成片播放层 ---------- */
/* 点任务缩略图放大播放。
   为什么另起一套而不用收银弹层 .modal：收银台是「360px 白卡片 + 居中文字」，
   成片要「黑底舞台 + 点遮罩关闭」，两者版式诉求相反，共用只会互相牵制。
   舞台比例由 JS 按任务实际画幅写进内联 style，这里 9/16 只是兜底
   （画幅已有 6 档，写死比例会让 21:9 的片子站在竖屏框里）。
   src 只在打开时挂上、关闭时摘掉（removeAttribute + load）：preload="none"
   只保证不预拉，摘掉才是真正掐断下载，否则关掉后仍在后台拉流。 */
.player{
  position:fixed;inset:0;z-index:var(--z-modal);
  display:none;align-items:center;justify-content:center;
  padding:var(--sp-4);
}
.player.show{display:flex;animation:fade-in var(--dur-base) var(--ease-out-quint)}
.player-mask{
  position:absolute;inset:0;
  background:rgba(8,8,12,.76);
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
}
.player-card{
  position:relative;display:flex;flex-direction:column;
  width:min(92vw,400px);max-height:92vh;
  border-radius:var(--r-xl);overflow:hidden;
  background:var(--surface);
  border:1px solid var(--line);
  box-shadow:var(--sh-lg), var(--sh-inset-hi);
  animation:modal-in var(--dur-slow) var(--ease-soft);
}
.player-stage{
  position:relative;
  aspect-ratio:9/16;max-height:70vh;
  background:#08080A center/cover no-repeat;
}
.player-stage video{display:block;width:100%;height:100%;object-fit:contain}
.player-x{
  position:absolute;top:var(--sp-3);right:var(--sp-3);z-index:2;
  width:34px;height:34px;border-radius:var(--r-full);
  display:grid;place-items:center;
  background:rgba(8,8,12,.62);border:1px solid rgba(255,255,255,.2);
  color:#fff;
  transition:all var(--dur-fast) var(--ease-in-out-quint);
}
.player-x:hover{background:rgba(8,8,12,.85)}
.player-x svg{width:15px;height:15px;stroke-width:2}
.player-cap{padding:var(--sp-4);border-top:1px solid var(--line)}
.player-cap b{display:block;font-size:var(--fs-14);font-weight:var(--fw-semibold);color:var(--ink)}
.player-cap span{display:block;font-size:var(--fs-11);color:var(--sub);margin-top:2px}
body.player-open{overflow:hidden}

/* ---------- 视频高清化：把 crisp 内嵌进这一页 ----------
 * crisp 是另一个进程（Python + FastAPI，端口 8765），不是同源资源，
 * 所以这里用 iframe 装载，而不是同源反向代理。
 *
 * 为什么不能反代：crisp 前端写了绝对路径（/api/jobs、/assets/app.js），
 * 而工作台自己的 /api 与它同名 —— 挂在同源前缀下，浏览器照样打到站根，
 * /api/* 必然撞车。iframe 是**另一个源**，绝对路径原样解析回 8765，不撞；
 * crisp 也没发 X-Frame-Options / CSP frame-ancestors（已实测响应头）。
 *
 * 高度必须写死：本页没有自己的滚动容器（文档整体滚），
 * iframe 不给高度就塌成默认 150px，看起来像"没加载出来"。
 * 260px ≈ 主区上内边距 + 页头 + 状态条 + 脚注，保证整块框进首屏。 */
.crisp-tools{display:flex;gap:var(--sp-2);flex-wrap:wrap}
.crisp-frame-wrap{position:relative;padding:0;overflow:hidden;background:var(--bg-sunken)}
.crisp-frame{
  display:block;width:100%;border:0;
  height:calc(100dvh - 260px);min-height:560px;
  background:var(--surface);
}
/* 加载遮罩：crisp 首屏要先探一次 AI 设备（约 1~3 秒），
   不给反馈的话用户会以为内嵌失败又去点"新标签页打开"。 */
.crisp-loading{
  position:absolute;inset:0;z-index:var(--z-base);
  display:grid;place-items:center;
  background:var(--surface);color:var(--sub);
  font-size:var(--fs-13);
}
.crisp-foot{margin-top:var(--sp-3);font-size:var(--fs-11);color:var(--sub-2);line-height:var(--lh-tight)}

/* ---------- 爆款重制 ----------
 * 这一页的骨架是「原片 / 复刻片」左右对照，所以左右两栏必须**等宽同高**：
 * 原片多是 9:16 竖屏，两栏宽度不等时两块播放器高度差得很明显，
 * 看上去像其中一块没加载出来。 */
.rmk-pair{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--sp-4)}
@media(max-width:980px){.rmk-pair{grid-template-columns:1fr}}
.rmk-vid{
  display:block;width:100%;
  aspect-ratio:9/16;max-height:56vh;
  background:#08080A;border-radius:var(--r-md);
  object-fit:contain;
}
/* 占位块与播放器同尺寸：出片前后这块区域不能跳高，否则用户记不住位置。
 * 注意：类名是 rmk-phbox 而不是 rmk-ph。节奏行用的是 .rmk-ph（4 列网格），
 *   两者共用同一个名字时 `.rmk-ph` 的 grid-template-columns 会**泄漏**进来，
 *   占位块里的「提示文案 + 进度条」就被排成两列（曾经真出现过）。
 *   同一块界面上的两个不同东西，不要共用类名。 */
.rmk-vid.rmk-phbox{
  display:grid;place-items:center;align-content:center;gap:var(--sp-4);
  border:1px dashed var(--line);
  background:var(--bg-sunken);color:var(--sub-2);
  font-size:var(--fs-12);text-align:center;padding:var(--sp-5);
}
.rmk-ph-mid{display:grid;justify-items:center;gap:var(--sp-3);padding:0 var(--sp-4)}
/* 出片进度就长在复刻片这块占位区里：这块区域本来就是为了「出片完成后在这里左右对照」
 * 而留的，进度放在这里，用户盯着的和产出出现的是同一个位置，视线不用来回找。 */
.rmk-outprog{display:grid;gap:var(--sp-2);width:min(100%,320px);text-align:left}
.rmk-outprog .rmk-progress{margin-top:0}
.rmk-foot{margin-top:var(--sp-3);font-size:var(--fs-11);color:var(--sub-2);line-height:var(--lh-tight)}
/* 拆解进度条：拿不到服务端的真实百分比（它是 fire and forget），
 * 所以按实测的 117 秒做一条**当次预估时长线性推进**的动画。
 * 宁可让它慢一点走到头，也不要给一个来回跳的假进度。 */
.rmk-progress{margin-top:var(--sp-4);height:6px;border-radius:999px;background:var(--bg-sunken);overflow:hidden}
.rmk-progress i{display:block;height:100%;width:0;background:var(--accent);border-radius:999px;animation:rmk-grow 150s linear forwards}
@keyframes rmk-grow{from{width:2%}to{width:96%}}
@media(prefers-reduced-motion:reduce){.rmk-progress i{animation:none;width:96%}}
/* 节奏编辑：一行一个相位，秒数输入与百分比同排。 */
.rmk-phases{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:var(--sp-3)}
.rmk-ph{
  display:grid;grid-template-columns:minmax(0,1fr) 68px auto auto;
  align-items:center;gap:var(--sp-2);
  padding:var(--sp-3) var(--sp-4);
  border:1px solid var(--line);border-radius:var(--r-md);
  background:var(--surface);
}
.rmk-ph-n{font-size:var(--fs-12);color:var(--ink-2);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rmk-ph input{padding:var(--sp-2);text-align:center;font-size:var(--fs-12)}
.rmk-ph-u{font-size:var(--fs-11);color:var(--sub-2);font-style:normal}
.rmk-ph-p{font-size:var(--fs-11);color:var(--accent);font-weight:var(--fw-medium);min-width:34px;text-align:right}
/* 口播节拍：一排一条，只读。 */
.rmk-beat{
  font-size:var(--fs-12);color:var(--ink-2);line-height:var(--lh-tight);
  padding:var(--sp-2) 0;border-bottom:1px solid var(--line);
}
.rmk-beat:last-child{border-bottom:none}
/* 历史列表：复用任务行的排布，但第一列没有缩略图，所以两列即可。 */
.rmk-row{
  display:grid;grid-template-columns:minmax(0,1fr) auto;
  gap:var(--sp-4);align-items:center;
  padding:var(--sp-4) 0;border-bottom:1px solid var(--line);
}
.rmk-row:last-child{border-bottom:none}
/* 本页的配置项比工作台多（拆解结果 + 你的产品两卡加起来十几项），而它用的是**整幅主区宽度**
 * （没有 .gen-layout 那条 440~520px 的左栏），照抄单列会让每个输入框被拉到 900px 宽、
 * 整页拉到五千多像素。所以只在这一页把 form-grid 铺成两列，跨列的项由 HTML 里的
 * grid-column:1/-1 指定。不改共享的 .form-grid，工作台那边一点不受影响。 */
#page-remake .form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--sp-4)}
#page-remake .form-grid .form-grid{gap:var(--sp-3)}
/* 产品图缩略图必须限列宽：生成器那边左栏只有 440px，4 列 1fr 每格约 100px；
   本页是整幅主区宽，沿用 1fr 会把每张撑到 230px 宽、310px 高（3:4），
   看着像一张大图而不是缩略图。改成固定宽度的自适应列，两边观感一致。 */
#page-remake .up-thumbs{grid-template-columns:repeat(auto-fill,124px)}
@media(max-width:900px){#page-remake .form-grid{grid-template-columns:minmax(0,1fr)}}

/* ---------- 登录 / 注册 ---------- */
.auth-wrap{
  min-height:100dvh;
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,.92fr);
  background:var(--bg);
}
@media(max-width:900px){.auth-wrap{grid-template-columns:1fr}}

/* 左侧品牌面板 */
.auth-brand{
  position:relative;
  padding:var(--sp-12) var(--sp-16);
  display:flex;flex-direction:column;
  justify-content:space-between;
  background:var(--bg-sunken);
  border-right:1px solid var(--line);
  overflow:hidden;
}
@media(max-width:900px){.auth-brand{display:none}}
.auth-brand::before{
  content:"";
  position:absolute;inset:0;
  background:
    radial-gradient(620px 380px at 78% 6%, var(--accent-soft), transparent 62%),
    radial-gradient(520px 340px at 8% 92%, var(--accent-soft), transparent 58%);
  pointer-events:none;
}
.auth-brand > *{position:relative}
.auth-brand .ab-quote{
  margin-top:auto;
  max-width:400px;
}
.auth-brand .ab-quote p{
  font-size:var(--fs-20);
  font-weight:var(--fw-semibold);
  letter-spacing:var(--ls-title);
  line-height:var(--lh-tight);
  text-wrap:balance;
}
.auth-brand .ab-quote p em{
  color:var(--accent);font-style:normal;
}
.auth-brand .ab-meta{
  display:flex;gap:var(--sp-8);
  margin-top:var(--sp-8);
  padding-top:var(--sp-6);
  border-top:1px solid var(--line);
}
.auth-brand .ab-meta div small{
  display:block;font-size:var(--fs-11);
  color:var(--sub-2);letter-spacing:var(--ls-label);
  margin-top:2px;
}
.auth-brand .ab-meta div b{
  font-size:var(--fs-18px);font-weight:var(--fw-bold);
  letter-spacing:-.03em;font-variant-numeric:tabular-nums;
}
.auth-brand .ab-feats{
  display:flex;flex-direction:column;gap:var(--sp-3);
  margin-top:var(--sp-6);
}
.auth-brand .ab-feats div{
  display:flex;align-items:center;gap:var(--sp-3);
  font-size:var(--fs-13);color:var(--ink-2);
}
.auth-brand .ab-feats svg{
  width:15px;height:15px;stroke-width:1.7;
  color:var(--accent);flex-shrink:0;
}

/* 右侧表单 */
.auth-form-wrap{
  display:flex;align-items:center;justify-content:center;
  padding:var(--sp-8) var(--sp-6);
}
.auth-card{width:100%;max-width:376px}
.auth-card h1{
  font-size:var(--fs-24);font-weight:var(--fw-semibold);
  letter-spacing:var(--ls-title);margin-bottom:var(--sp-2);
}
.auth-card .sub{
  color:var(--sub);font-size:var(--fs-13);
  margin-bottom:var(--sp-6);line-height:var(--lh-tight);
}
.auth-tabs{
  display:flex;gap:2px;
  background:var(--surface-hover);
  border-radius:var(--r-md);
  padding:3px;margin-bottom:var(--sp-5);
}
.auth-tabs button{
  flex:1;padding:7px;border-radius:calc(var(--r-md) - 3px);
  font-size:var(--fs-12);font-weight:var(--fw-medium);
  color:var(--sub);
  transition:all var(--dur-fast) var(--ease-in-out-quint);
}
.auth-tabs button.on{
  background:var(--surface);color:var(--ink);
  font-weight:var(--fw-semibold);
  box-shadow:var(--sh-sm);
}
.auth-card .field{margin-bottom:var(--sp-3)}
/* 表单错误行：恒占一行高度（min-height），否则报错出现时会把下面的按钮顶下去，
 * 用户刚要点的那一下正好落空 —— 这是登录页最经典的可用性事故。 */
.auth-err{
  min-height:18px;margin-top:var(--sp-3);
  font-size:var(--fs-12);color:var(--bad);
  font-weight:var(--fw-medium);line-height:var(--lh-tight);
}
.auth-card .btn-primary{width:100%;padding:12px;margin-top:var(--sp-2)}
.auth-tip{
  margin-top:var(--sp-5);
  font-size:var(--fs-11);color:var(--sub-2);
  text-align:center;line-height:var(--lh-tight);
}
/* 字段级提示（目前用于邀请码预校验）。
 * ⚠️ 同样**恒占一行高度**：否则"已确认：妇联一期"这行出现时会把
 *    下面的密码框与按钮整体推下去，正在敲键盘的用户会点错位置。 */
.hint{
  min-height:16px;margin-top:var(--sp-2);
  font-size:var(--fs-11);color:var(--sub-2);line-height:var(--lh-tight);
}
.hint-ok{color:var(--ok)}
.hint-bad{color:var(--bad)}
/* 协议勾选：复选框与文字同一行对齐，文字可换行但复选框不缩进。
 * 三个链接必须能点开看到全文 —— 同意的前提是可读，否则同意无效。 */
.auth-terms{
  display:flex;gap:var(--sp-2);align-items:flex-start;
  margin-top:var(--sp-3);
  font-size:var(--fs-12);color:var(--sub);line-height:var(--lh-tight);
  cursor:pointer;
}
.auth-terms input{
  flex:none;margin-top:2px;width:14px;height:14px;
  accent-color:var(--accent);cursor:pointer;
}
.auth-terms a{color:var(--accent);text-decoration:none}
.auth-terms a:hover{text-decoration:underline}

/* 协议改版后的强制同意覆盖层：铺满视口、不在顶部显示关闭按钮。
 * 用 fixed 而不是弹窗组件：此刻页面内容对用户没有意义
 * （他必须同意才能继续），所以直接盖住，减少"关掉它接着用"的错觉。 */
.gate{
  position:fixed;inset:0;z-index:var(--z-modal);
  display:flex;align-items:center;justify-content:center;
  padding:var(--sp-5);
  background:color-mix(in srgb,var(--ink) 42%,transparent);
  backdrop-filter:blur(6px);
}
.gate[hidden]{display:none}
.gate-card{
  width:100%;max-width:420px;
  padding:var(--sp-8);
  background:var(--surface);border-radius:var(--r-xl);
  box-shadow:var(--sh-lg);
}
.gate-card h2{
  margin:0 0 var(--sp-2);
  font-size:var(--fs-18px);font-weight:var(--fw-bold);
  letter-spacing:var(--ls-title);color:var(--ink);
}
.gate-sub{margin:0 0 var(--sp-2);font-size:var(--fs-13);color:var(--sub);line-height:var(--lh-body)}
.gate-actions{display:flex;gap:var(--sp-3);margin-top:var(--sp-6)}
.gate-actions .btn{flex:1;padding:10px}

/* ---------- 响应式：窄屏 ---------- */
@media(max-width:1180px){
  .gen-layout{grid-template-columns:minmax(0,1fr)}
  .main{padding:var(--sp-6) var(--sp-6) var(--sp-16)}
}
@media(max-width:1024px){
  .stat-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .stat-grid .stat.wide{grid-column:span 2}
}
@media(max-width:900px){
  .app{grid-template-columns:minmax(0,1fr)}
  .sidebar{
    position:fixed;left:0;top:0;
    width:64px;
    padding:var(--sp-4) var(--sp-2);
    transform:none;
  }
  /* [!] 这里隐藏的是「文字」而不是「按钮」：64px 条里只剩图标，图标必须留着。
   * 选择器曾漏掉两处，后果是文字在 64px 条里**竖排折行并溢出色条**，整条侧栏看着像坏了：
   *   · `.who span` 盖不住 `.logout` 里的「退出」—— 那是**裸文本节点**，不是元素；
   *   · `.nav .txt` 盖不住 `.theme-toggle .txt` —— 它在 `.side-foot`，根本不在 `.nav` 下。
   * 所以：改成**按 class 收口** `.sidebar .txt`（覆盖导航、深色模式、退出三处），
   * 并给「退出」的文字也套一层 `<span class="txt">`（见 index.html）。
   * 以后侧栏再加文字，只要记得带 `class="txt"` 就会被一起收掉。 */
  .sidebar .logo-text,
  .sidebar .txt,
  .sidebar .who span,
  .sidebar .balance .b-body{display:none}
  /* 触控目标：手指点得准的下限约 40px。原来是 47x37 / 28x37，高度不够。 */
  .sidebar .nav button{justify-content:center;padding:10px;min-height:40px}
  .sidebar .nav button.on::before{left:-8px}
  .sidebar .logo{justify-content:center;padding:var(--sp-1) 0}
  .sidebar .balance{justify-content:center;padding:var(--sp-2);min-height:40px}
  .sidebar .who .logout{display:flex;align-items:center;justify-content:center;min-width:40px;min-height:40px}
  .sidebar .theme-toggle{min-height:40px}
  .main{padding:var(--sp-6) var(--sp-4) var(--sp-16) 80px}
  .grid2,.grid3{grid-template-columns:minmax(0,1fr)}
}

/* ============================================================================
   窄屏下表格的横向滚动
   ----------------------------------------------------------------------------
   页面里的表格全是 innerHTML 现拼的**裸表**（订单 7 列 / 会员 6 列 / 流水 3 列…）。
   当表的 min-content 比容器还宽，表会撑破卡片并越过视口 ⇒ **整页能左右晃**。
   [!] `table{width:100%}`（tokens.css）拦不住它：自动表格布局下，
       指定的 100% 只是**下限**，内容更宽时表会自己变宽。
   [!] 滚动容器要放在**表格的父元素**上，不能放 table 上：给 table 设
       `display:block;overflow-x:auto` 会让它退化成匿名表格盒、宽度变 shrink-to-fit，
       宽屏下表头反而不铺满了。放父元素上则宽屏零变化、窄屏才出滚动条。
   [!] 新增表格容器时把 id 补进下面这行，或直接给容器套 class="tbl-wrap"。
       漏掉的表在窄屏会再次撑破页面。
   ============================================================================ */
#priceTable,#ledgerList,#orderList,#reviewRows,#engineStatus,
#adminStats,#adminUsers,#adminOrders,
.tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
@media(max-width:640px){
  .stat-grid{grid-template-columns:minmax(0,1fr)}
  .stat-grid .stat.wide{grid-column:span 1}
  .page-head{margin-bottom:var(--sp-6)}
  .page-head h1{font-size:var(--fs-20)}
  .pad{padding:var(--sp-4)}
  .up-thumbs{grid-template-columns:repeat(3,minmax(0,1fr))}
  .toolbar .grow{min-width:100%}
  .presets{grid-template-columns:1fr}
}

/* ============================================================================
   断线提示条（#netBar）
   这是 SPA，标签页打开后页面一直留在内存里。服务被关掉或被回收时，界面
   看上去**完全正常**，点任何按钮都只弹出浏览器原话（Failed to fetch），
   用户会以为是自己传的文件有问题。真实原因只有一个：请求根本没发出去。
   放底部（顶部居中已被 toast 占用），常驻、不自动消失，带一个重新连接。
   ============================================================================ */
.net-bar{
  position:fixed;left:50%;bottom:var(--sp-5);transform:translateX(-50%);
  z-index:var(--z-toast);
  display:flex;align-items:center;gap:var(--sp-3);
  max-width:min(760px,calc(100vw - var(--sp-8)));
  padding:var(--sp-3) var(--sp-4);
  background:var(--bad);color:#fff;
  border-radius:var(--r-md);
  box-shadow:var(--sh-lg);
  font-size:var(--fs-13);line-height:var(--lh-body);
}
.net-bar[hidden]{display:none}
.net-bar span{min-width:0}
.net-bar button{
  flex:none;font:inherit;color:inherit;background:transparent;cursor:pointer;
  border:1px solid currentColor;border-radius:var(--r-full);
  padding:var(--sp-1) var(--sp-3);
}
.net-bar button:hover{background:rgba(255,255,255,.16)}
@media(max-width:640px){
  .net-bar{flex-direction:column;align-items:stretch;gap:var(--sp-2);text-align:left}
  .net-bar button{align-self:flex-start}
}
