/* ============================================================
   蜜蜂设计 · AI出图工作台 —— 全局样式
   适配：企微侧边栏（窄至 320px）/ 桌面浏览器
   ============================================================ */
:root {
  --brand: #2f7cf6;
  --brand-dark: #1f63d0;
  --brand-light: #e8f1ff;
  --bg: #f5f7fb;
  --card: #ffffff;
  --border: #e4e8f0;
  --text: #1f2530;
  --text-2: #5a6472;
  --text-3: #8d97a6;
  --green: #16a34a;
  --red: #e5484d;
  --orange: #f59e0b;
  --radius: 10px;
  --shadow: 0 1px 3px rgba(20, 30, 50, .06), 0 6px 20px rgba(20, 30, 50, .05);
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0; padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", "Segoe UI", sans-serif;
  font-size: 14px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--brand); text-decoration: none; }
button { font-family: inherit; cursor: pointer; }
input, textarea, select { font-family: inherit; }

/* ---------- 布局 ---------- */
.wrap { max-width: 1180px; margin: 0 auto; padding: 16px; }
.wrap-narrow { max-width: 520px; margin: 0 auto; padding: 12px; }

.topbar {
  background: var(--card);
  border-bottom: 1px solid var(--border);
  padding: 10px 16px;
  display: flex; align-items: center; gap: 10px;
  position: sticky; top: 0; z-index: 50;
}
.topbar .logo {
  width: 28px; height: 28px; border-radius: 8px;
  background: linear-gradient(135deg, #2f7cf6, #6aa5ff);
  display: flex; align-items: center; justify-content: center;
  color: #fff; font-weight: 700; font-size: 14px; flex: 0 0 auto;
}
.topbar .title { font-weight: 600; font-size: 15px; }
.topbar .spacer { flex: 1; }
.topbar .who { font-size: 12px; color: var(--text-2); }
.topbar .who b { color: var(--text); font-weight: 600; }

/* 顶栏的「管理端」入口 —— 只对管理员显示（JS 按 IS_ADMIN 切换 hidden）。
   ⚠️ 2026-09-16 新增：之前这个入口**完全不存在** ——
      `IS_ADMIN` 变量算出来了却从没被用过，管理员登录后落在客服页、
      页面上找不到任何进管理端的路，只能手动敲 /admin（老板就这么卡住的）。 */
.topbar .admin-link {
  flex: 0 0 auto;
  font-size: 12px; font-weight: 600; line-height: 1;
  padding: 6px 11px; border-radius: 20px;
  background: var(--brand-light); color: var(--brand-dark);
  border: 1px solid #cfe0fb; text-decoration: none;
  transition: all .13s;
}
.topbar .admin-link:hover {
  background: var(--brand); border-color: var(--brand); color: #fff;
}

/* ⚠️ 老板的真实视口只有约 340px。加了「管理端」胶囊后，
   顶栏「logo + 标题 + 管理端 + 你是谁」会挤不下 ——
   窄屏先把**标题**藏掉（logo 的「蜜」字还担着品牌识别），
   而不是把入口藏掉：入口藏了就等于没做。 */
@media (max-width: 420px) {
  .topbar .title { display: none; }
}

/* ---------- 卡片 ---------- */
.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 16px;
  margin-bottom: 14px;
}
.card-title {
  font-size: 14px; font-weight: 600; margin: 0 0 12px;
  display: flex; align-items: center; gap: 8px;
}
.card-title .bar {
  width: 3px; height: 14px; border-radius: 2px; background: var(--brand);
}
.card-title .sub { font-weight: 400; font-size: 12px; color: var(--text-3); }

/* ---------- 表单 ---------- */
.field { margin-bottom: 12px; }
.field label {
  display: block; font-size: 12px; color: var(--text-2);
  margin-bottom: 5px; font-weight: 500;
}
.field label .req { color: var(--red); margin-left: 2px; }
/* 输入框上的「图片识别」提示条：醒目但不吵 */
.field label .label-badge {
  display: inline-flex; align-items: center; gap: 3px;
  margin-left: 6px; padding: 2px 7px; border-radius: 20px;
  background: var(--brand-light); color: #1a5bbf;
  font-size: 10.5px; font-weight: 600; vertical-align: 1px;
}
.input, .textarea, .select {
  width: 100%; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: 8px;
  background: #fbfcfe; color: var(--text); font-size: 14px;
  transition: border-color .15s, box-shadow .15s, background .15s;
  outline: none;
}
.input:focus, .textarea:focus, .select:focus {
  border-color: var(--brand); background: #fff;
  box-shadow: 0 0 0 3px rgba(47, 124, 246, .12);
}
.textarea { resize: vertical; min-height: 68px; line-height: 1.5; }
/* 客户要求是整个表单的重点，框子给大一些。
   老板反馈：太窄了看着压抑。默认 68px 在窄侧边栏里只露两行半。 */
.textarea-req { min-height: 118px; }
.field-hint { font-size: 11px; color: var(--text-3); margin-top: 4px; }

.amount-wrap { position: relative; }
.amount-wrap .prefix {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  color: var(--text-3); font-size: 14px; pointer-events: none;
}
.amount-wrap .input { padding-left: 28px; }

/* ---------- 按钮 ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 10px 16px; border-radius: 8px; border: 1px solid transparent;
  font-size: 14px; font-weight: 500; transition: all .15s; white-space: nowrap;
  user-select: none;
}
.btn:disabled { opacity: .55; cursor: not-allowed; }
.btn-primary { background: var(--brand); color: #fff; }
.btn-primary:hover:not(:disabled) { background: var(--brand-dark); }
.btn-ghost { background: #fff; border-color: var(--border); color: var(--text-2); }
.btn-ghost:hover:not(:disabled) { border-color: #c9d3e0; color: var(--text); }
.btn-green { background: var(--green); color: #fff; }
.btn-green:hover:not(:disabled) { background: #128a3e; }
.btn-red { background: #fff; border-color: #f3c6c8; color: var(--red); }
.btn-red:hover:not(:disabled) { background: #fff5f5; }
.btn-block { width: 100%; }
.btn-lg { padding: 13px 18px; font-size: 15px; font-weight: 600; }
.btn-sm { padding: 6px 11px; font-size: 12px; border-radius: 7px; }

/* ---------- 提示 ---------- */
.alert {
  padding: 10px 12px; border-radius: 8px; font-size: 13px;
  margin-bottom: 12px; display: flex; gap: 8px; align-items: flex-start;
}
.alert-info { background: var(--brand-light); color: #1a4f9e; }
.alert-warn { background: #fff8e6; color: #8a5a00; }
.alert-ok { background: #e9f9ef; color: #0e6b32; }
.alert-err { background: #fff0f0; color: #9b1c1f; }

/* ---------- 图片 ---------- */
.img-box {
  border: 1px solid var(--border); border-radius: 8px;
  background: repeating-conic-gradient(#f0f2f6 0% 25%, #fff 0% 50%) 50% / 16px 16px;
  overflow: hidden; position: relative;
}
.img-box img { display: block; width: 100%; height: auto; }
.img-tag {
  position: absolute; top: 8px; left: 8px;
  background: rgba(0, 0, 0, .62); color: #fff;
  font-size: 11px; padding: 2px 8px; border-radius: 20px; backdrop-filter: blur(4px);
}

/* ---------- 步骤条 ---------- */
.steps { display: flex; align-items: center; gap: 4px; margin-bottom: 14px; overflow-x: auto; }
.step {
  display: flex; align-items: center; gap: 5px; font-size: 11px;
  color: var(--text-3); flex: 0 0 auto;
}
.step .dot {
  width: 18px; height: 18px; border-radius: 50%; flex: 0 0 auto;
  background: #e8ecf3; color: var(--text-3);
  display: flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 600;
}
.step.on .dot { background: var(--brand); color: #fff; }
.step.on { color: var(--brand); font-weight: 600; }
.step.done .dot { background: var(--green); color: #fff; }
.step.done { color: var(--green); }
.step-line { flex: 1 0 10px; height: 1px; background: var(--border); min-width: 8px; }

/* ---------- 话术框 ---------- */
.script-box {
  background: #f8fafc; border: 1px dashed #c9d6e8; border-radius: 8px;
  padding: 11px 12px; font-size: 13px; line-height: 1.65;
  color: var(--text); white-space: pre-wrap; word-break: break-word;
  position: relative;
}
/* 水印档位：分段按钮。手机上比下拉框好点（目标大、一眼看清选中的是哪个） */
.wm-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.wm-row .wm-label { font-size: 12px; color: var(--text-2); white-space: nowrap; }
.wm-row .wm-tip { font-size: 11px; color: var(--text-3); }
.seg { display: inline-flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.seg button {
  padding: 8px 18px; font-size: 13px; background: #fff; color: var(--text-2);
  border: 0; border-right: 1px solid var(--border); cursor: pointer;
  font-family: inherit; transition: all .15s;
}
.seg button:last-child { border-right: 0; }
.seg button.on { background: var(--brand); color: #fff; font-weight: 600; }
.seg button:disabled { opacity: .5; cursor: not-allowed; }

/* 话术分类：选一类套一套措辞，免得把客户要求全文塞进话术里（会变成几百字） */
.script-kind {
  display: flex; align-items: center; gap: 8px; margin-bottom: 8px;
}
.script-kind label {
  font-size: 12px; color: var(--text-2); white-space: nowrap;
}
.script-kind .select { flex: 1; min-width: 0; }
/* 话术框现在是个可编辑 textarea（客服可以随手改），样式对齐原来的 .script-box */
#scriptBox {
  background: #f8fafc; border: 1px dashed #c9d6e8; border-radius: 8px;
  padding: 11px 12px; font-size: 13px; line-height: 1.65;
  color: var(--text); min-height: 92px; resize: vertical;
}
.script-actions { display: flex; gap: 8px; margin-top: 8px; flex-wrap: wrap; }

/* ---------- 列表 ---------- */
.list { display: flex; flex-direction: column; gap: 8px; }
.list-item {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border: 1px solid var(--border);
  border-radius: 8px; background: #fff; transition: all .15s;
}
.list-item:hover { border-color: #c9d6e8; background: #fbfcff; }
.list-item .li-main { flex: 1; min-width: 0; }
.list-item .li-title {
  font-size: 13px; font-weight: 500; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.list-item .li-sub { font-size: 11px; color: var(--text-3); margin-top: 2px; }

.badge {
  display: inline-block; padding: 2px 8px; border-radius: 20px;
  font-size: 11px; font-weight: 500; flex: 0 0 auto;
}
.badge-gray { background: #eef1f6; color: var(--text-2); }
.badge-blue { background: var(--brand-light); color: #1a4f9e; }
.badge-green { background: #e9f9ef; color: #0e6b32; }
.badge-orange { background: #fff4e0; color: #9a5c00; }
.badge-red { background: #fff0f0; color: #9b1c1f; }

/* ---------- 表格 ---------- */
.table { width: 100%; border-collapse: collapse; font-size: 13px; }
.table th {
  text-align: left; font-weight: 600; color: var(--text-2); font-size: 12px;
  padding: 8px 10px; border-bottom: 1px solid var(--border); white-space: nowrap;
}
.table td { padding: 9px 10px; border-bottom: 1px solid #f0f3f8; }
.table tr:last-child td { border-bottom: none; }
.table tr:hover td { background: #fafbfe; }
.table .num { text-align: right; font-variant-numeric: tabular-nums; }

/* ---------- Tab ---------- */
.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin-bottom: 14px; }
.tab {
  padding: 9px 14px; font-size: 13px; color: var(--text-2);
  border-bottom: 2px solid transparent; margin-bottom: -1px; cursor: pointer;
  font-weight: 500; transition: all .15s;
}
.tab:hover { color: var(--text); }
.tab.on { color: var(--brand); border-bottom-color: var(--brand); }

/* ---------- 加载 ---------- */
.spinner {
  width: 14px; height: 14px; border: 2px solid rgba(255, 255, 255, .35);
  border-top-color: #fff; border-radius: 50%;
  animation: spin .7s linear infinite; display: inline-block;
}
@keyframes spin { to { transform: rotate(360deg); } }
.loading-mask {
  position: fixed; inset: 0; background: rgba(255, 255, 255, .82);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 12px; z-index: 999; backdrop-filter: blur(2px);
}
.loading-mask .ring {
  width: 36px; height: 36px; border: 3px solid #dbe6f5;
  border-top-color: var(--brand); border-radius: 50%;
  animation: spin .8s linear infinite;
}
.loading-mask .txt { font-size: 13px; color: var(--text-2); }

/* ---------- 上传区 ---------- */
.upload-zone {
  border: 1.5px dashed #c9d6e8; border-radius: 8px;
  padding: 14px 10px; text-align: center; background: #fafcff;
  cursor: pointer; transition: all .15s;
  display: flex; flex-direction: column; justify-content: center;
}
.upload-zone:hover, .upload-zone:focus {
  border-color: var(--brand); background: var(--brand-light);
  outline: none;
}
.upload-zone .ico { font-size: 22px; margin-bottom: 4px; }
/* t2 = 卡片标题（"设计素材"/"风格参考"）。比 t1 的提示语更醒目，
   因为用户第一眼要认出"这格是干嘛的"。 */
.upload-zone .t2 { font-size: 11.5px; color: var(--text); font-weight: 600; line-height: 1.35; }
/* t1 = 操作提示（"点这里，再按 Ctrl+V"） */
.upload-zone .t1 { font-size: 13px; color: var(--text-2); font-weight: 600; margin-top: 1px; }
/* t3：第三行小字补充（如"实拍照片 / 产品图"），最淡 */
.upload-zone .t3 { font-size: 10px; color: var(--text-3); margin-top: 2px; line-height: 1.35; }

/* ⭐⭐⭐ v7：满意截图区的"已上传"态。
 *
 * ⚠️⚠️ v7 之前这段写的是 `.upload-zone.shot-uploaded` —— 那时 #shotZone 是
 *    "一个大虚线框 + 居中三行文字"。v7 把 #shotZone 换成了 `.upload-head`
 *    （标题行，和「设计素材」完全一致），所以选择器**必须跟着改**：
 *    旧选择器留着不会报错，但**永远不会再命中**，是纯死代码。
 *
 * 表达方式：绿边框 + 右上角"已上传"角标（角标走 ::after，不占布局）。
 *
 * ⚠️ `:not(.paste-ready)` 是必须的：
 *    `.upload-head.paste-ready::after` 也有一个角标（"✅ 按 Ctrl + V"）。
 *    "已上传之后又点一下准备粘贴"时两者会同时命中 —— 同优先级后写的胜，
 *    会盖掉粘贴提示，观感变成"点了准备粘贴，提示却不出来"。
 *    加 :not() 让**粘贴就绪优先**（那是当下更该说的事）。
 */
.upload-head.shot-uploaded {
  border-color: #9ddcb4; background: #f3fbf6;
}
.upload-head.shot-uploaded:not(.paste-ready)::after {
  content: '✅ 已上传';
  position: absolute; top: -8px; right: 8px;
  font-size: 9.5px; line-height: 1; padding: 3px 6px; border-radius: 20px;
  background: var(--green); color: #fff; font-weight: 600; letter-spacing: .2px;
}
/* 主入口（客户要求截图）：蓝色描边 + 淡蓝底 + 右上角角标，视觉上比参考图更突出 */
.upload-zone[data-primary] {
  border-style: solid; border-color: #bcd4f7; background: #f6faff;
  position: relative;
}
.upload-zone[data-primary] .t1 { color: #1a5bbf; }
.upload-zone[data-primary]::after {
  content: '可识别文字'; position: absolute; top: -8px; right: 8px;
  font-size: 9.5px; line-height: 1; padding: 3px 6px; border-radius: 20px;
  background: var(--brand); color: #fff; font-weight: 600; letter-spacing: .2px;
}
/* 上传区里的两个可点操作按钮 */
.zone-acts {
  display: flex; gap: 6px; margin-top: 9px; justify-content: center; flex-wrap: wrap;
}
/* ⚠️ 按钮不能被拉满整宽。
   .upload-zone 是 flex column，子项 align-items 默认 stretch，
   不写 align-self 的话「📁 选择文件」会横撑满卡片，看着很笨重。 */
.upload-zone .zone-btn { align-self: center; }
.zone-btn {
  display: inline-flex; align-items: center; gap: 3px;
  padding: 5px 9px; border-radius: 6px;
  background: #fff; border: 1px solid #c9d6e8;
  color: #3d5f8a; font-size: 11.5px; font-weight: 500;
  cursor: pointer; white-space: nowrap; user-select: none;
  transition: all .13s;
}
.zone-btn:hover {
  border-color: var(--brand); color: var(--brand);
  background: var(--brand-light);
}
.zone-btn:active { transform: translateY(1px); }
/* 粘贴按钮稍微突出一点，因为它是最省事的入口 */
.zone-btn.paste-btn {
  background: #eef4ff; border-color: #bcd4f7; color: #1a5bbf;
}
.zone-btn.paste-btn:hover {
  background: #e2edff; border-color: var(--brand);
}
/* 粘贴成功闪一下 */
.upload-zone.paste-flash {
  border-color: var(--green); background: #eefaf1;
  box-shadow: 0 0 0 3px rgba(21, 154, 74, .13);
}
/* 卡片被聚焦（准备接收粘贴）时的高亮 */
.upload-zone:focus {
  border-color: var(--brand); background: var(--brand-light);
  box-shadow: 0 0 0 3px rgba(47, 124, 246, .13);
  outline: none;
}

/* 两个平行上传入口：主次不等宽 —— 设计素材是要真出图的核心料（占大头），
   风格参考是辅助（占小头）。老板反馈"设计素材应该大一些"。

   ⭐⭐【永远并排，不再有竖排分支】⭐⭐
   老板的企微侧边栏实测只有 ~340px 宽，我前两版按断点猜（420 → 360），
   他两次都看到"上下两行、更大了"。教训：
   **并排的总高 = max(两块)；竖排的总高 = 两块之和** —— 竖排永远更占地方，
   在只有两块的场景下**没有"窄到必须竖排"这回事**。
   所以直接删掉竖排断点，用 minmax(0, Xfr) 保证窄到什么程度都并排、
   溢出的部分靠缩小内边距和文案来吸收，而不是靠换行。

   ⭐ `align-items: start`（不是 stretch）—— 关键！
   用 stretch 时两块被拉成等高，图片少的那个卡片下方会空出一大截，
   而预览缩略图在卡片**外面**、紧跟在下方 —— 观感变成"卡片空着、图片掉在下面"。
   改成 start 后每块按自己内容高，预览图紧贴卡片底，不再有空档。 */
.upload-pair {
  display: grid;
  /* ⚠️ 必须用 minmax(0, Xfr)。直接写 `1.6fr 1fr` 时，fr 的默认最小值是 auto，
     内容（"点这里，再按 Ctrl+V"）撑不下就会**顶破栅格**、把另一块挤扁甚至溢出。
     包一层 minmax(0, …) 才允许它真的缩小。 */
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: 8px;
  align-items: start;
}

/* ⭐⭐ upload-pair-even：让两块的卡片**一样高**。
 * 老板反馈（第 4、5 条）："这两个高低大小不同的，你看下 怪怪的"、"一个高一个低"。
 *
 * 为什么之前会一高一低：主位字号大（ico 20px / t2 11.5px / t1 12px / padding 10px），
 *   副位字号小（ico 17px / t2 11px / t1 9.5px / padding 9px），
 *   两块内容虽然一样多，**算出来的自然高度不一样**。实测 340px 下 123px vs 111px。
 *
 * ❌ 试过但**不成立**的做法（都留个记录，别再走一遍）：
 *   ① 给 .upload-zone 设 min-height（96px / 84px）——两块自然高都超过它，
 *      完全不生效，差值 12px 原样保留。要盖住两块得写 130px+，
 *      那在宽屏下会把卡片撑空，很丑。
 *   ② `.upload-slot { display: contents }` 把 zone / preview 提到栅格层 ——
 *      想法对，但**只声明了 2 列**，4 个子元素自动流成 2 行 × 2 列，
 *      结果两块跑到不同行上去，topDiff 反而从 0 变成 130px。**更糟。**
 *
 * ✅ 最终做法：**显式指定每个子元素落在哪一格**。
 *   栅格 2 列 × 2 行：
 *        col1        col2
 *   row1 [主位 zone] [副位 zone]     ← 这一行两个 zone 自动等高（取较大值）
 *   row2 [主位 prev] [副位 prev]     ← 各自内容高，互不影响
 *
 *   ⚠️ 预览行必须 `align-self: start`，否则会被 row2 的高度拉长。
 *   ⚠️ 不用 `align-items: stretch` 拉齐整个 slot（那是上上轮修掉的 old bug：
 *      zone 短的 slot 会在 zone 和 preview 之间多一段空白，
 *      表现是"图片掉到卡片下面去了"）。这里只拉齐 zone 那一行。 */
.upload-pair-even {
  grid-template-rows: 1fr auto;
  align-items: stretch;
}
/* ⚠️⚠️ 这里必须**显式指定格位**，不能靠自动放置。
 *   用 display:contents 把 4 个子元素提到栅格层后，DOM 顺序是
 *       [主zone] [主preview] [副zone] [副preview]
 *   自动放置会填成：
 *       row1 = 主zone, 主preview
 *       row2 = 副zone, 副preview     ← 两块卡片跑到了不同的行！实测 topDiff 0 → 130px
 *   所以给 zone 钉在第 1 行（column 各 1 / 2）、preview 钉在第 2 行。
 *   （这是实测踩过的坑，不要再简化成"靠 DOM 顺序自动落位"。） */
.upload-pair-even .upload-slot { display: contents; }
.upload-pair-even .upload-slot-main .upload-zone    { grid-area: 1 / 1; }
.upload-pair-even .upload-slot-side .upload-zone    { grid-area: 1 / 2; }
.upload-pair-even .upload-slot-main .ref-grid       { grid-area: 2 / 1; }
.upload-pair-even .upload-slot-side .ref-grid       { grid-area: 2 / 2; }
/* 卡片行：撑满该行（两块取较大的那个高度），内容垂直居中 */
.upload-pair-even .upload-slot .upload-zone { height: 100%; }
/* 预览行：跟着自己的卡片走，别被拉长 */
.upload-pair-even .upload-slot .ref-grid { align-self: start; }
/* 极窄屏：两块都变矮，等高由栅格保证，这里只把 padding 再收一点 */
@media (max-width: 330px) {
  .upload-pair-even .upload-slot .upload-zone { padding-top: 8px; padding-bottom: 8px; }
}
.upload-slot { display: flex; flex-direction: column; min-width: 0; }
.upload-slot .upload-zone { justify-content: center; }
.upload-slot .ref-grid { margin-top: 6px; }

/* ============================================================
   ⭐⭐ 上传卡片 v2：改成参考图那种「标题行 + 下面图片格」的样子

   老板原话："图片上传那边能不能参考类似这种，不然上面的那种真的怪怪的，再下面"
   （他给的参考图是常见的"资料上传"卡片：上面一行标题，图全在下面，
     末尾一个「＋」虚线空位，点它就选图。）

   v1 的问题（老板看到的"怪怪的"）：
     每块都是「一个空的大虚线框（内容只有「点这里，再按 Ctrl+V」+「📁 选择文件」）
     + 图在下面」。没图时那一片大空框很占地方；有图时它也不走，杵在图片上方，
     于是"上传按钮"和"已经传好的图"分成上下两截，看着很割裂。

   v2 的结构：
     .upload-card
       .upload-head   ← 一行：图标 + 名称 + 操作提示 + ⊕
       .ref-grid      ← 下面：缩略图… 最后跟一个「＋」空位
   两块（设计素材 / 风格参考）用 .upload-cards 栅格并排，主位宽、副位窄。

   ⚠️ 交互两条路都保留，不二选一：
     - 点标题行 → armPaste()（进入"准备粘贴"态，Ctrl+V 贴图）
       这是老板专门要过的快捷路，不能因为改样式就丢掉。
     - 点「＋」格 → 弹文件选择框（.zj-add / .ref-add 的 click 里 stopPropagation）
   ============================================================ */
.upload-cards {
  display: grid;
  /* ⭐⭐ 老板第二轮反馈："这个一个传两排，一个传一排，不太对，规划合理点"
     （截图：左边设计素材一行 2 个，右边风格参考一行 1 个，满图后
       左边 3 排、右边 5 排，高度差出一大截，右边一条长尾巴。）

     根因：① 格距 7px **写死**了 —— 两边可用宽都被吃掉同样的 7px；
           ② 横向 5px 内边距白白浪费；
           ③ 最关键：两列的**每行容量没对齐**。原来 1.6fr/1fr 在 340px 下算出
              主位 178px（2 格）、副位 112px（1 格），高度自然差一倍。

     ⭐⭐ v6 的核心要求（两条，都要满足）：
        ① 两块**等宽**   → 每行格数必然一致（v3 的核心诉求）
        ② 两块**撑满整行** → 右侧不留白

     📜 历史：v3 的算式模型（**v6 起已弃用，留作教训**）
        v3 曾把列宽按格宽**精确算死**：`列宽 = 10 + --u`，
        其中 `--u = 2×格子 + 2×3px margin`（"两个格子并排"那条内容线宽）。
        好处：每行严格 2 格。
        代价：必须再补一列弹性列吸收算不进的零头 → **右侧永远留白**
              （就是老板 v6 圈出来的那一大块）。
        ⚠️ 当时踩过的坑，记录以免重犯：
           `--u` 是"内容区宽"**不是**格子边长。第一版当边长写成
           `calc(var(--u) * 2 + 13px)`，340px 下列宽只剩 133px，
           而两个 60px 格子要 126px → 一行只放得下 1 格。

     ⭐⭐⭐ v6（2026-09-16 老板第二次反馈）：
       "我说的是那边空出来很多呀…然后外面最好加个虚线的框"

     原来第 3 列那个 `minmax(0, 1fr)` **弹性列在宽屏会吃掉剩余空间** ——
     两块只占左边一大半，右边空出一大块（老板截图圈的就是那块空白）。
     当时加它是为了"吸收算式算不进的零头、绝不横向溢出"，
     代价是右侧永远留白。

     ✅ 现在直接用 `minmax(0, 1fr)` **两块平分**：
        · 平分 → 两块**必然等宽** → 每行格数必然一致（v3 的核心诉求照样成立）
        · 撑满 → 右边不留白
        · `minmax(0, …)` 而不是素 `1fr`：素 fr 的最小值是 auto，
          内容装不下会**顶破栅格**（这条 v2 就踩过，别改回去）

     ⭐ 为什么"平分"比原来的"按格宽算死"更稳：
       窄到极限时两块会一起缩成"每行 1 格" —— 但**两块都是 1 格**，
       依然对称、依然等高。而 v3 那版是"一边 2 格一边 1 格"才难看。
       对称 > 绝对格数。 */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  /* ⭐ 两块之间留缝，否则两个虚线外框会贴在一起、糊成一个大框。
     原来这里是 gap:0（因算式要精确闭合，见下），改平分后不再需要闭合，可以留缝。 */
  gap: 8px;
  align-items: start;
}

/* ⭐⭐⭐ v6：每一块**整块**加虚线外框（老板："外面最好加个虚线的框"）。
   包住「标题行 + 下面所有图片格」，这一块的范围一眼可见 ——
   这正是老板说的"没有边界感觉"要解决的问题：
   原来只有「＋」格子自己有虚线，标题行是另一圈实线，
   两块之间没有"我这一块到哪儿为止"的表达。

   层次设计（别做成"框中框"打架）：
     · 外层（本规则）= 虚线 + **不加底色** → 它只是"范围"，不抢内容
     · 内层 .upload-head = 浅灰底 + 实线 → 它是"可点的操作区"
   一虚一实、一透一底，两层语义不重复。 */
.upload-card {
  min-width: 0;
  border: 1.5px dashed #a9bdd8;
  border-radius: 10px;
  /* 下边距比上/左右小一点：图片格自己带 3px 下 margin，否则底部会显得空 */
  padding: 7px 7px 4px;
}

/* ⭐ v7：满意截图区（老板："这个上传满意的能不能也改成类似的前面的那种样式"）。
   它复用 .upload-card 的外观，但**不在 .upload-cards 栅格里** ——
   是独立单块，块级元素宽度天然撑满整行，不需要任何列宽规则。

   ⚠️ 它的图片格用的是 `.ref-grid`（**不带** -lg），格子固定 68px
      （见下方 `.ref-grid:not(.ref-grid-lg) .zj-add`），**因此不依赖 --tu** ——
      外面没有 .upload-cards 也不会掉档位、不会缩成小格子。
      ⭐ 这正是当初选 68px 那套、而不是 --tu 那套的原因，**别顺手改成 -lg**：
      改成 -lg 后 --tu 取不到档位值，格子会退回 fallback 尺寸，和上面两块不一致。 */
.upload-card-solo { display: block; }

/* 标题行：一行放下「图标 名称 提示 ⊕」。
   用 flex + 右侧 auto 把 ⊕ 推到最右（参考图里那个 ⊕ 也在最右）。 */
.upload-head {
  display: flex; align-items: center; gap: 5px;
  padding: 7px 9px; border-radius: 8px;
  border: 1px solid var(--border); background: #fbfcfe;
  cursor: pointer; transition: all .15s;
  position: relative;
}
.upload-head:hover, .upload-head:focus {
  border-color: var(--brand); background: var(--brand-light); outline: none;
}
.upload-head .ico { font-size: 14px; line-height: 1; flex: 0 0 auto; }
.upload-head .nm { font-size: 12px; font-weight: 600; color: var(--text); flex: 0 0 auto; }
.upload-head .t1 {
  font-size: 10.5px; color: var(--text-3); flex: 1 1 auto;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ⊕：最右边的选文件入口。做小一点，别抢标题的注意力 */
.upload-head .go {
  flex: 0 0 auto; width: 20px; height: 20px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; line-height: 1; color: #3d5f8a;
  background: #eef4ff; border: 1px solid #c9d6e8;
  transition: all .13s;
}
.upload-head:hover .go { background: var(--brand); border-color: var(--brand); color: #fff; }
/* 准备粘贴态：整行变绿（原来是 .upload-zone.paste-ready，现在挂在这个 head 上）。
   ⚠️ v2 起 armPaste 不再替换 innerHTML（那会冲掉两档 <i> 结构），
   所以"已就绪"的提示改由 CSS 的 ::after 出。

   ⚠️⚠️ 踩过的坑（老板截图："我点击这个的时候文字重叠了"）：
      这里原来**只藏了 .t1**，但标题行里还有 .ico（🧩 / 🎨）和
      .nm（设计素材 / 风格参考）。而 ::after 是从 left:9px 起绝对定位的，
      正好压在图标和名称上 —— 于是
        "✅ 已就绪 —— 按 Ctrl + V"  和  "🎨 风格参考"
      叠成一团，两个都看不清。
      ⇒ 就绪态必须**整行替换**：.ico / .nm / .t1 一起藏。
        用 visibility（不是 display）——保留占位，这样整行高度和 ⊕ 的位置
        不会在"点一下 / 超时 20s 自动取消"之间跳动。

   ⚠️ 文案也收短了。::after 的可视宽度只有「容器 − 9(left) − 30(right)」：
      老板侧边栏 340px 时容器约 140px ⇒ 只剩约 101px。
      10.5px 字号下 "✅ 已就绪 —— 按 Ctrl + V" 要约 131px，**必然被省略号吃掉**；
      "✅ 按 Ctrl + V" 约 71px，从 300px 到 560px 各档都放得下。
      "已就绪"这层意思由**整行变绿**表达，不用再用文字说一遍。 */
.upload-head.paste-ready { border-color: #9ddcb4; background: #f3fbf6; }
.upload-head.paste-ready .ico,
.upload-head.paste-ready .nm,
.upload-head.paste-ready .t1 { visibility: hidden; }
.upload-head.paste-ready::after {
  content: '✅ 按 Ctrl + V';
  position: absolute; left: 9px; right: 30px; top: 50%;
  transform: translateY(-50%);
  font-size: 10.5px; font-weight: 600; color: #157a3e;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  pointer-events: none;
}
/* 就绪态时 ⊕ 不该继续引注意（这时应该去按 Ctrl+V） */
.upload-head.paste-ready .go { opacity: .45; }

/* 主位标题行稍大一点，视觉上仍是主角 */
.upload-card-main .upload-head .nm { font-size: 12.5px; }
/* 副位压小一档（它本来就比主位挤，名称字略小可省出提示位） */
.upload-card-side .upload-head { padding: 6px 7px; gap: 4px; }
.upload-card-side .upload-head .ico { font-size: 13px; }
.upload-card-side .upload-head .nm { font-size: 11.5px; }

/* ⚠️⚠️ v3 起格子缩到 62~76，标题行也跟着变窄，提示位被挤没了。
   实测账（diag_v3_hint.js，340px 窗口 / 容器 140px）：
     可用 140 − 左右 padding 18 = 122
     减去：ico 13 + gap 5 + 名称"设计素材"(12.5px ≈ 52) + gap 5 + ⊕ 18 + gap 5
     = 只剩 **约 16px**，「粘贴」2 字需 20px → 放不下。
   ⇒ 瓶颈在**名称**。所以名称收到 11.5px（省约 4px）、字间距压到 -0.2px，
     gap 收到 4px（省 3px），把提示位让到 ~23px，刚好放下 2 字。
   ⭐ 这是"宁可把提示显示出来"的选择：上传口的提示对客服是操作指引，
      藏掉等于把入口的说明抹了。
   ⚠️ 名称字号只动到 11.5px —— 再小（<11px）在手机上看不清，
      那时就该轮到**整条藏掉**的兜底档（见下面的 @container）。 */
.upload-head { gap: 4px; }
.upload-head .nm { letter-spacing: -0.2px; }
.upload-card-main .upload-head .nm { font-size: 11.5px; }
.upload-card-side .upload-head .nm { font-size: 11px; }

/* 提示文案：两档由 CSS 容器查询切换（阈值按实测定，见下） */
.upload-head .t1 { font-size: 10px; }
/* ⚠️ 用 CSS 容器查询（container query）而不是 JS —— 
    .t1 是 flex 子项，clientWidth 量的是"被容器夹住后的宽度"，
    永远 ≈ 容器宽，量不出"文字真实需要多宽"（这个坑记录过）。
   ⚠️ 宽屏要先把短档 display:none，否则两句话同时显示。
   ⚠️ i 标签默认斜体，要 normal。 */
.upload-head .t1 i { font-style: normal; }
.upload-head .t1 .t1-narrow { display: none; }
@supports (container-type: inline-size) {
  .upload-card { container-type: inline-size; }
  /* ① 默认（宽）：4 字「点头粘贴」——"头"= 这一行标题
     ② 中档：换 2 字「粘贴」
     ③ 兜底：整条藏掉

     ⚠️⚠️ 阈值全按**实测算过的余量**定，不是拍的。核算表：
        窗口  tu  容器  提示位余量   该显示
        300   56  128    22px      2 字（4 字需 42px）
        340   62  140    34px      2 字   ← 老板侧边栏常态
        380   62  140    34px      2 字
        420   70  156    50px      4 字 ✅
        560   76  168    62px      4 字 ✅
        （余量 = 容器 − 36padding − 13ico − 45名称 − 18⊕ − 12gap）
     ⇒ 4 字档门槛 = 容器 **150px**（≈420px 窗口），
       2 字档下限 = 容器 **120px**（低于此连"粘贴"都塞不下，会变竖杠）。
     我第一版把 4 字门槛写成 205px，结果 560px 这么宽也只在显示 2 字 ——
     因为格子放大时列宽跟着变大，容器并不会无上限增长，永远够不到 205。
     ⚠️ 兜底门槛实测定为 **132px**：容器 128px（280/300 窗口）时"设计素材"
        那格的 t1 只有 12px，2 字要 20px —— 还是差 8px，会被截成「粘…」。
        提到 132 就能把这一档（也是唯一会截断的一档）整体藏掉。 */
  @container (max-width: 149px) {
    .upload-head .t1 .t1-wide { display: none; }
    .upload-head .t1 .t1-narrow { display: inline; }
  }
  @container (max-width: 132px) {
    /* ⚠️⚠️ 容器 <=132px：**整条藏掉**，别留一小截竖杠 + 省略号。
       到这个宽度连「粘贴」2 字（需 20px）都放不下 ——
       实测 280/300px 窗口下"设计素材"格的 t1 只剩 12px，
       在截图里就是「粘…」这种半截字，比不显示还难看。
       "点标题可粘贴"靠 title 属性 + 点下去变绿的反馈表达即可 ——
       真正要紧的是**点下去有反应**，不是那行提示永远可见。 */
    .upload-head .t1 { display: none; }
  }
}
.upload-card-side .upload-head .go { width: 18px; height: 18px; font-size: 12px; }
/* ⊕ 单独当按钮使：点它 = 选文件（真正的 click 在 addTile/JS 里绑） */
.upload-head .go { cursor: pointer; }
.upload-head .go:hover { background: var(--brand); border-color: var(--brand); color: #fff; }
/* 空状态只有「＋」一格时，别让它跟标题行之间空太多（v1 那种大空框的后遗症） */
.upload-card .ref-grid:empty { display: none; }

/* ============================================================
   ⭐⭐ v4：截图预览框在"没图"时整块藏起来

   背景：老板反馈"修改要求那边能不能去掉那个识别文字的、上传图片那个按钮"，
   那个「📷 可传图识别」按钮删掉后，它下面留着一个**孤零零的「＋」虚线框**
   （就是客户要求截图的预览位）。没图的时候它白占一行，
   让「修改要求」和「设计素材」之间多出一块空白 —— 老板说"去掉多余的东西"。

   但**不能把框整个删掉**：贴了截图之后必须能看到"我贴了什么"（还要能点 × 删掉）。
   所以做成**有图才出现**：

     · 没图 → 框里只剩一个 .zj-add（「＋」）→ display:none，整块消失
     · 有图 → 框里有 .ref-thumb → 正常显示

   ⚠️ 为什么用 :has() 而不是 :empty？
      v3 起「＋」是**渲染函数产出**的（为了空状态也有上传口），
      所以框里**永远不为空** —— :empty 判不出来。
      必须用 :has(.ref-thumb) 反着判："有没有真图"。
   ⚠️ :has() 支持度：Chrome/Edge 105+、Safari 15.4+。
      企微侧边栏用的是系统 WebView，2026 年早够了。
      万一遇上老内核：@supports 兜底，退化成"一直显示"（跟改之前一样，不会坏）。

   适用范围：客户要求截图 / 改稿截图 这两个"OCR 预览框"（#reqShotPreview、
   #reviseReqShotPreview）。它们跟「设计素材」「风格参考」卡片**不一样** ——
   后两者即使空着也要保留「＋」（那是主要上传口），
   而截图框只是"贴图后的回显"，没图时不该占位。 */
@supports selector(:has(*)) {
  #reqShotPreview:not(:has(.ref-thumb)),
  #reviseReqShotPreview:not(:has(.ref-thumb)) {
    display: none;
  }
}

/* 图片格：v2 用大方格（参考图里就是大方块），不再是 68px 小图。
   v3 起 gap 归零、改由格子自己带 margin —— 目的是让"每行能放几个"只由
   列宽决定，不再受写死的 gap 影响（两边才对得齐）。
   ⚠️⚠️ gap 必须写 !important 或放到基础规则**后面**：
      .ref-grid 的基础规则在文件下方（约 940 行）定义了 gap: 8px，
      同优先级下**后写的胜** —— 我第一版就是在这里被自己坑了，
      诊断显示 gridGap 仍是 8px，格子被多撑出去 8px，一行只放得下 1 个。
   ⚠️ margin 写在**格子**上而不是靠 gap：这样最后一个格子的右边距也算进
      "内容宽"里，列宽算式（10 + 2×tu + 6）才能精确闭合。 */
.ref-grid-ct { gap: 0 !important; }
.ref-grid-ct > .ref-thumb,
.ref-grid-ct > .zj-add { margin: 0 3px 3px 0; }

/* ⛔ v6：这里曾经有一组「空状态下让「＋」居中」的规则
   （`.ref-grid-ct:not(:has(.ref-thumb)) { justify-content: center }`）。
   **已删除，不要再加回来。**

   背景：2026-09-16 我把老板那句"不要左边对齐"理解成了"把「＋」居中"，
   做了一版。老板当场纠正原话：
     "我说的是那边空出来很多呀…**没有说那个添加素材的那个按钮居中**…
      然后外面最好加个虚线的框"
   —— 他要的是**每一块外面套一个虚线框**（见上面 .upload-card 那条规则），
   不是让「＋」在框里居中。
   📌 教训：**用户描述一个"不对"的现象时，别替他猜解法。**
      他说"不要左对齐"，可能指"整块没有边界所以显得偏"，也可能真的指对齐方式 ——
      猜错就白做一轮，还会被当成"听不懂话"。不确定就先问一句。

   ⚠️ 它还有个技术隐患：`grid-template-columns: none` 会把 v3/v6
      好不容易定下来的列宽规则**整个推翻**，
      属于"为了一个没被要求的效果去动核心布局"，风险收益完全不划算。 */
/* 格子尺寸（跟随 --tu；--tu 由 .upload-cards 分档、经 inherit 传下来） */
.ref-grid-lg .ref-thumb {
  width: var(--tu, 76px); height: var(--tu, 76px); border-radius: 10px;
}

/* ⭐⭐ v3：格子尺寸提到 .upload-cards 上，两块共用。
   --tu = 单个格子的边长（档位值由下面 @media 分档决定）

   ⛔ **`--u` 已于 v6 废弃、已删除**（原来是"两个格子并排的内容宽"，
      专供 grid-template-columns 的列宽算式）。v6 把列宽改成
      `minmax(0,1fr) minmax(0,1fr)` 平分后，列宽不再需要按格宽算死，
      --u 就一个消费者都没有了 —— 留着只会让后来人以为列宽还依赖它。
      **别再把它加回来。**

   ⚠️ 为什么还保留 `--tu: inherit`：
      .upload-cards 上定义的 --tu 本来就会**天然继承**给子元素，
      这两行严格说是冗余的。保留是为了防"以后有人把分档规则挪到子元素上"
      那种改法（v3 第一版就是 --tu 写在子元素、列宽在父元素求值，
      结果两边对不上、行数不一致）。改动最小化，别删。 */
.upload-card-main { --tu: inherit; }
.upload-card-side { --tu: inherit; }
/* 「＋」空位：虚线格，点它选文件。跟在最后一张图后面，跟参考图一致。
   ⚠️ 尺寸规则必须写在基础规则**后面**：同优先级下后者胜，
      写在前面的话基础规则里的 font-size 会盖掉这里的（尺寸本身不受影响，
      因为基础规则没声明 width/height —— 但别留下这种"看起来能跑其实靠运气"的顺序）。 */
/* ⭐⭐⭐ 老板反馈（2026-09-16）：
     "这个能不能给个虚线或者虚线的框框…然后居中对齐…不要左边对齐。
      有点难看 而且没有边界感觉~~~"

   他指的是**没图时那两个「＋」空位**。原来的毛病：
     ① 虚线太淡 —— `#c9d6e8` 在浅色卡片上几乎看不见，
        而底色 `#fafcff` 又和卡片底色几乎同色 → 整格"没有边界感"，
        看着像"两个孤零零的加号飘在那儿"，不像一个能点的框。
     ② 左对齐 —— `.ref-grid` 用 grid + 固定列宽（v3 为了让两块每行容量对齐），
        没图时左列只有一个「＋」，它就紧贴容器左边；
        而上面的标题行（.upload-head）是**撑满整行**的，
        于是"标题占满一行、下面那个框缩在左上角" → 视觉上不平衡。

   修法分两块：
     a) 虚线加深（见下面 border-color）+ 填一点底色，让它"有边界"
     b) 让**没图时的单个「＋」居中**（见 .ref-grid-ct 的 justify-content 规则）*/

.zj-add {
  display: flex; align-items: center; justify-content: center;
  /* ⭐ 虚线加深一档：#c9d6e8 → #a9bdd8（对比够了但不刺眼）。
     原来 1.5px 的虚线在缩到 62px 的小格子上会糊成一片，
     这里保持 1.5px 但把颜色提上去，边界就出来了。 */
  border: 1.5px dashed #a9bdd8;
  /* 底色和卡片（#fff）拉开一点点，让框"浮"出来 */
  background: #f4f8fd;
  color: #7e97b8; font-size: 24px; font-weight: 300; line-height: 1;
  cursor: pointer; transition: all .15s; user-select: none;
}
.zj-add:hover { border-color: var(--brand); color: var(--brand); background: var(--brand-light); }
/* 主位/副位卡片里的大方格「＋」：边长跟着 --tu 走（v3 起两块一样大，
   避免"副位格子小一号"又造成两边行数不同步） */
.ref-grid-lg .zj-add {
  width: var(--tu, 76px); height: var(--tu, 76px); border-radius: 10px;
}
/* 客户要求截图区（.ref-grid 但没挂 -lg）：「＋」跟 68px 的图对齐 */
.ref-grid:not(.ref-grid-lg) .zj-add { width: 68px; height: 68px; border-radius: 8px; font-size: 20px; }

/* ============================================================
   ⭐⭐ v3：整行容量自适应 —— 窗口再窄也不会出现"主位 2 格、副位 1 格"

   老板第二轮反馈："这个一个传两排，一个传一排，不太对，规划合理点"

   上面已经用"两块一样宽 + 每行一样 2 格"把 340px 这个常态修平了。
   但那是**算死的**：窗口一窄（企微浮窗、分屏），列宽算式会被弹性列压缩，
   格子被挤小甚至换行 —— 又回到"左右行数不一样"的老毛病。

   所以这里再兜一层：按"整行宽度"分档，**两块共用一个 --tu**。

   ⚠️⚠️ 分档的关键是留足"容器内边距"：
      实测（diag_v3_rows.js，340px）：.upload-card 拿到的是 **319px** 不是 340 ——
      .card 的 padding + 边框吃掉了 21px。所以可用宽 ≈ **window - 21**。
      每档反推：2 × (10 + 2×tu + 6) + 一些余量 ≤ window - 21
        · 320 可用 → tu 最大 72（2×160=320，**刚好顶满，溢出 1px**）
        · 所以我 340px 档位选 **tu=70**（2×156=312，剩 8px 弹性）而不是 72。
      第一版我按 "window 减去一点点" 估的，结果 2×160=320 > 319，
      实测 hOverflow=1 —— 虽然只有 1px 不显眼，但会触发横向滚动条，
      在手机上表现为"页面能左右晃"，必须修掉。

     ① 500px+：tu=76（宽屏格子给足）
     ② 360~500：tu=70
     ③ 320~360：tu=62   ← 老板侧边栏 340px 落这一档
     ④ 280~320：tu=56
     ⑤ <280：tu=48

   ⚠️ 为什么不按"单列宽"分档（@container）？
      @container 是以**就近容器**为参照的 —— 落到哪一块就只改哪一块的变量，
      正好会制造出"左右格子大小不同 → 行数不同"这个老板要修的问题本身。
      只有"按整行宽"分档，才能保证两块永远同步。

   ⭐ _media 顺序要求：min-width 必须**从大到小**写、max-width **从小到大**写，
     否则后面的会覆盖前面的（同优先级靠顺序决胜）。这里按这个规则排好了。 */
.upload-cards { --tu: 56px; }
@media (min-width: 280px) { .upload-cards { --tu: 56px; } }
@media (min-width: 321px) { .upload-cards { --tu: 62px; } }
@media (min-width: 361px) { .upload-cards { --tu: 70px; } }
@media (min-width: 501px) { .upload-cards { --tu: 76px; } }
@media (max-width: 279px) { .upload-cards { --tu: 48px; } }

/* ⛔ v6：原来这里定义 `--u: calc(var(--tu) * 2 + 6px)` 供列宽算式用。
   v6 列宽改 `minmax(0,1fr)` 平分后已无消费者 —— **整行删除**。
   ⚠️ 别因为"看着像有用"又加回来：加回来也不会有任何效果，
      只会让人误以为列宽还依赖格子宽度。 */

/* 主素材卡片：高一点、文案大一点，视觉上就是"主角"。
   ⭐ 整体压扁了一轮（老板反馈"他很大…浪费了这么大的按钮"）：
   原 padding 15px + ico 26px + 按钮 5px9px，孤零零一块就 168px 高。
   现在收到 padding 10px、ico 20px、按钮更秀气。 */
.upload-slot-main .upload-zone { padding: 10px 8px; }
.upload-slot-main .upload-zone .ico { font-size: 20px; margin-bottom: 1px; }
.upload-slot-main .upload-zone .t2 { font-size: 11.5px; }
.upload-slot-main .upload-zone .t1 { font-size: 12px; }
/* 副卡片：整体压小，别跟主位抢注意力 */
.upload-slot-side .upload-zone { padding: 9px 6px; }
.upload-slot-side .upload-zone .ico { font-size: 17px; margin-bottom: 1px; }
.upload-slot-side .upload-zone .t2 { font-size: 11px; }
/* ⭐ 副位比主位窄得多，字号也要跟着小一档 —— 目标是让
   「点这里，再按 Ctrl+V」在 340px（老板侧边栏的真实宽度）也能**一行**放下。
   实测：11px 时需宽 ~106px > 可用 100px（折成两行）；降到 9.5px 后一行放得下。
   如果哪天字号又调大了，文案会自动退回短句（fitUploadHints 兜底），不会裁字。 */
.upload-slot-side .upload-zone .t1 { font-size: 9.5px; }
.upload-slot-side .zone-btn { font-size: 10px; padding: 4px 6px; }

/* ⭐⭐ 副位（风格参考）宽度只有主位 60% 左右，"点这里，再按 Ctrl+V" 必折两行。
   ❌ 不要用 white-space:nowrap 硬压 —— 实测 280~330px 下会**裁掉文案**
      （最多少 24px），还会把容器撑出 17px 横向溢出的假滚动条。
      我第一版就是这么写的，被 verify_round8b.js 当场抓到。
   ✅ 正确做法：由 JS 按可用宽度**切换短文案**（见 index.html 的
      fitUploadHints()）—— 窄了就说"点击或 Ctrl+V"，宽了才用完整句子。
      文字换行本身没有错，错的是"让一句话必须挤进两行"。 */
.upload-slot .upload-zone .t1 { text-wrap: balance; }

/* 两块共用：卡片里的按钮再收一圈。原 5px 9px 在窄侧边栏里显得很笨重。 */
.upload-slot .zone-btn { padding: 4px 8px; font-size: 11px; }
.upload-slot .zone-btn .f-ico { font-size: 10px; }

/* 改稿面板里的上传对，窄一点也无所谓 */
.revise-panel .upload-pair { gap: 8px; }

/* 改稿面板「修改要求」那行 label —— 跟主表单 .field > label 长一样。 */
.revise-panel .revise-label {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 12.5px; font-weight: 600; color: var(--text-2);
  margin: 0 0 6px;
}

/* ============================================================
   ⭐⭐ v4：「截图可直接 Ctrl+V 粘贴」提示文字

   背景：老板要求去掉「📷 可传图识别」按钮。那个按钮虽然多余，
   但它是**唯一**在视觉上告诉客服"这个框能收图"的东西 ——
   只删不补，客服就再也不知道能贴图了（老板之前抱怨过
   "无法截图客户的要求转换内容，我只能打字"，根因就是"没人告诉他能贴"）。

   所以补这句话。**它必须看起来不像按钮**：
     · 无边框、无底色、不可点（cursor 用 default 不用 pointer）
     · 字号比 label 小一档、颜色更淡（--text-3）
   否则客服会以为还要先点它一下，又回到"绕弯"的老问题。

   ⚠️ 别给它加 hover 效果、别加 tabindex、别绑 click —— 它是说明文字，不是控件。 */
.paste-hint {
  font-size: 10.5px; font-weight: 400; color: var(--text-3);
  cursor: default; user-select: none;
  letter-spacing: 0;
}
/* ⚠️ 主表单的 .field label 是 display:block（不是 flex），
   所以这里要把提示改成 inline + 左边留点距离，才能跟标题文字同一行。
   改稿面板那边 label 本身就是 flex，gap 会接管间距，不用重复留 margin。 */
.field label .paste-hint { display: inline; margin-left: 7px; }
/* 太窄时让它换行到 label 下面，别把「修改要求」挤变形 */
.paste-hint { flex: 0 1 auto; min-width: 0; }

/* ---------- 登录页 ---------- */
.login-page {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  background: linear-gradient(150deg, #eaf1fd 0%, #f5f7fb 45%, #f0f4fa 100%);
  padding: 20px;
}
.login-card {
  width: 100%; max-width: 380px; background: #fff;
  border-radius: 14px; box-shadow: 0 10px 40px rgba(30, 50, 90, .1);
  padding: 28px 26px;
}
.login-card .brand {
  display: flex; flex-direction: column; align-items: center; gap: 10px; margin-bottom: 22px;
}
.login-card .brand .ico {
  width: 48px; height: 48px; border-radius: 13px;
  background: linear-gradient(135deg, #2f7cf6, #6aa5ff);
  display: flex; align-items: center; justify-content: center;
  color: #fff; font-weight: 700; font-size: 20px;
  box-shadow: 0 6px 16px rgba(47, 124, 246, .3);
}
.login-card .brand .nm { font-size: 17px; font-weight: 600; }
.login-card .brand .ds { font-size: 12px; color: var(--text-3); }

/* ---------- 窄屏适配（企微侧边栏） ---------- */
/* ⛔ 这里原来有一个 @media (max-width: 360px) { .upload-pair { grid-template-columns: 1fr } }
   —— 让上传两块在窄屏竖排。**已彻底删除，不要再加回来。**

   为什么删：我按断点猜了两轮（420 → 360），老板两次看到的都是"上下两行、更大了"。
   根因是一个反直觉的数学事实：
       并排总高 = max(两块)  ；  竖排总高 = 两块 *之和*
   只要块数 ≥ 2，**竖排永远比并排高**。所谓"窄到装不下"并不存在——
   装不下应该靠缩小内边距/字号来吸收，而不是靠换行。
   （实测：并排 198px vs 竖排 338px，竖排高 71%。）

   顺便：老板的侧边栏实测只有 ~340px，比 360 还窄，
   所以那个断点在他那里**必定命中**，等于我亲手把布局改成了最差的那种。 */

/* 极窄兜底（< 330px）：不换行，只把文案再缩一档，保证仍在同一行 */
@media (max-width: 330px) {
  .upload-pair { gap: 6px; }
  .upload-slot-main .upload-zone,
  .upload-slot-side .upload-zone { padding: 8px 6px; }
  .upload-slot-main .upload-zone .t1,
  .upload-slot-side .upload-zone .t1 { font-size: 10.5px; }
  .upload-slot .zone-btn { padding: 3px 6px; font-size: 10px; }
  .upload-zone .t3 { display: none; }
}

@media (max-width: 420px) {
  body { font-size: 13px; }
  .wrap, .wrap-narrow { padding: 8px; }
  .card { padding: 12px; border-radius: 9px; margin-bottom: 10px; }
  .topbar { padding: 8px 10px; }
  .topbar .title { font-size: 14px; }
  .input, .textarea, .select { padding: 9px 10px; font-size: 13px; }
  .btn-lg { padding: 12px 16px; font-size: 14px; }
  .step .lbl { display: none; }
  .step-line { min-width: 5px; }
  .card-title { font-size: 13px; }
  .table th, .table td { padding: 7px 6px; font-size: 12px; }
  /* ⛔ 这里原来有一段 `.upload-pair { grid-template-columns: 1fr }` 让它竖排，
     后来又搬到 360px 断点 —— 现在**两处都已删除**。
     原因见上面「窄屏适配」第一段：竖排总高 = 两块之和，永远比并排更占地方。
     （老板原话："这个还是有点大么…上下两行了。。更大了~"）别搬回来。 */
  /* 侧边栏是窄屏主战场：压缩卡片高度，而不是靠换行。 */
  .upload-slot-main .upload-zone { padding: 9px 7px; }
  .upload-slot-side .upload-zone { padding: 8px 5px; }
  .upload-slot .ref-grid { margin-top: 5px; }
  .upload-zone .t1 { font-size: 11.5px; }
}

/* ---------- Toast ---------- */
#toast-wrap {
  /* 放底部：顶部会压住 topbar 和 Tab 栏，看起来像导航被遮了 */
  position: fixed; bottom: 26px; left: 50%; transform: translateX(-50%);
  z-index: 10000; display: flex; flex-direction: column-reverse; gap: 8px;
  align-items: center; pointer-events: none; width: max-content; max-width: 92vw;
}
.toast {
  background: rgba(28, 36, 50, .93); color: #fff;
  padding: 9px 16px; border-radius: 8px; font-size: 13px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, .2);
  animation: toastIn .22s ease-out; backdrop-filter: blur(6px);
}
.toast.ok { background: rgba(16, 140, 65, .95); }
.toast.err { background: rgba(210, 40, 45, .95); }
@keyframes toastIn {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ---------- 弹窗 ---------- */
.modal-mask {
  position: fixed; inset: 0; background: rgba(20, 28, 42, .45);
  display: flex; align-items: center; justify-content: center;
  z-index: 9000; padding: 16px; backdrop-filter: blur(2px);
}
.modal {
  background: #fff; border-radius: 12px; width: 100%; max-width: 440px;
  max-height: 88vh; overflow-y: auto; box-shadow: 0 20px 60px rgba(0, 0, 0, .22);
}
.modal-head {
  padding: 14px 16px; border-bottom: 1px solid var(--border);
  font-weight: 600; font-size: 14px; display: flex; align-items: center;
}
.modal-body { padding: 16px; }
.modal-foot {
  padding: 12px 16px; border-top: 1px solid var(--border);
  display: flex; gap: 8px; justify-content: flex-end;
}
.hidden { display: none !important; }
.muted { color: var(--text-3); }
.mt8 { margin-top: 8px; } .mt12 { margin-top: 12px; } .mt16 { margin-top: 16px; }
.mb8 { margin-bottom: 8px; } .mb12 { margin-bottom: 12px; }
.flex { display: flex; } .gap8 { gap: 8px; } .gap12 { gap: 12px; }
.items-center { align-items: center; } .justify-between { justify-content: space-between; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

/* ============================================================
   订单列表 / 筛选
   ============================================================ */
.filter-bar {
  display: flex; flex-wrap: wrap; gap: 6px; padding-bottom: 2px;
}
.chip {
  flex: 0 0 auto; padding: 5px 11px; border-radius: 20px;
  border: 1px solid var(--border); background: #fff; color: var(--text-2);
  font-size: 12px; transition: all .15s; white-space: nowrap;
}
.chip:hover { border-color: #c9d6e8; color: var(--text); }
.chip.on { background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 500; }
.chip b { font-weight: 600; opacity: .85; margin-left: 1px; }

/* ---------- 截图识别客户要求 ---------- */
/* 入口统一收进「客户要求截图」上传卡片（见 .upload-zone[data-primary]），
   不再单独放一个大横幅按钮，避免和卡片重复。 */

.ocr-status {
  margin-top: 8px; padding: 9px 11px; border-radius: 8px;
  font-size: 12px; line-height: 1.6;
  background: #f4f7fc; border: 1px solid var(--border); color: var(--text-2);
}
.ocr-status.loading {
  background: #f2f7ff; border-color: #cfe0fa; color: #2c6bd4;
}
.ocr-status.ok {
  background: #f0faf3; border-color: #bfe6cb; color: #1f7a3d;
}
.ocr-status.err {
  background: #fef4f3; border-color: #f3cdc8; color: #b42318;
}
.ocr-status .ocr-text {
  display: block; margin-top: 5px; padding: 7px 9px;
  background: #fff; border: 1px dashed var(--border); border-radius: 6px;
  color: var(--text); white-space: pre-wrap; word-break: break-all;
  max-height: 150px; overflow-y: auto;
}

/* 后台出图进度（生成中 / 失败）。
   出图一次要 1~2 分钟，必须让客服看得见「在跑」和「跑了多久」，
   否则会以为卡死了、反复点生成。 */
.gen-status {
  margin-bottom: 12px; padding: 12px 14px; border-radius: 10px;
  font-size: 13px; line-height: 1.75;
  background: #f2f7ff; border: 1px solid #cfe0fa; color: #2c6bd4;
}
.gen-status.err {
  background: #fef4f3; border-color: #f3cdc8; color: #b42318;
}
.gen-status .muted { color: var(--text-3); font-size: 12px; }
/* 生成中、且这个单子还没有任何样稿可看时，把结果区里的卡片整体藏起来，
   只留上面那条进度提示。
   为什么：这几张卡（样稿 / 发客户 / 传截图 / 出终稿）都得等出图才有内容，
   提前露出来时样稿卡里的 <img> 没有 src，浏览器会画一个「破图」图标，
   后面几张卡又全是空的 —— 客服看到会以为页面坏了（老板反馈过）。
   已经有样稿的单子（改稿重出、出终稿）不藏，让客服还能看着上一版。 */
#result.gen-running > .card { display: none; }
.spin {
  display: inline-block; width: 11px; height: 11px;
  border: 2px solid #cfe0fa; border-top-color: #2c6bd4; border-radius: 50%;
  animation: _spin .7s linear infinite; vertical-align: -1px; margin-right: 4px;
}
@keyframes _spin { to { transform: rotate(360deg); } }

.order-item {
  display: flex; align-items: center; gap: 10px;
  padding: 10px; border: 1px solid var(--border); border-radius: 9px;
  background: #fff; margin-bottom: 8px; cursor: pointer; transition: all .15s;
}
.order-item:hover { border-color: #b9cde8; background: #fbfcff; box-shadow: 0 2px 8px rgba(30,60,120,.06); }
.order-item .oi {
  width: 46px; height: 46px; border-radius: 7px; object-fit: cover;
  flex: 0 0 auto; background: #eef1f6; border: 1px solid var(--border);
}
.order-item .oi-ph {
  display: flex; align-items: center; justify-content: center;
  font-size: 10px; color: var(--text-3);
}
.order-item .oi-main { flex: 1; min-width: 0; }
.order-item .oi-top { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.order-item .oi-tb { font-size: 12px; font-weight: 600; color: var(--text); }
.order-item .oi-content {
  font-size: 12px; color: var(--text-2); margin: 3px 0 2px;
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical;
  overflow: hidden; word-break: break-all;
}
.order-item .oi-meta { font-size: 11px; color: var(--text-3); }
.tag-shop {
  display: inline-block; padding: 1px 6px; border-radius: 4px;
  background: #eef4ff; color: #2c6bd4; font-size: 10px; font-weight: 500;
  max-width: 90px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.empty-box {
  text-align: center; padding: 40px 16px; color: var(--text-2); font-size: 13px;
}
.empty-box .ico { font-size: 30px; margin-bottom: 8px; opacity: .5; }

/* ============================================================
   参考图
   ============================================================ */
.ref-grid { display: flex; flex-wrap: wrap; gap: 8px; }
.ref-thumb {
  position: relative; width: 68px; height: 68px; border-radius: 8px;
  overflow: hidden; border: 1px solid var(--border); background: #f4f6fa;
}
.ref-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* 客户要求截图区（.ref-grid 但没挂 -lg）里的「＋」要跟 68px 的图对齐 */
.ref-grid:not(.ref-grid-lg) .zj-add { width: 68px; height: 68px; border-radius: 8px; font-size: 20px; }
.ref-del {
  position: absolute; top: 2px; right: 2px; width: 17px; height: 17px;
  border-radius: 50%; background: rgba(0,0,0,.6); color: #fff;
  font-size: 12px; line-height: 17px; text-align: center; cursor: pointer;
  transition: background .15s;
}
.ref-del:hover { background: var(--red); }

/* ============================================================
   生成方式选择
   ============================================================ */
.mode-pick { display: flex; flex-direction: column; gap: 7px; }
.mode-opt {
  display: flex; align-items: flex-start; gap: 8px; padding: 9px 11px;
  border: 1px solid var(--border); border-radius: 8px; cursor: pointer;
  transition: all .15s; background: #fbfcfe;
}
.mode-opt:hover { border-color: #b9cde8; }
.mode-opt input { margin-top: 3px; flex: 0 0 auto; accent-color: var(--brand); }
.mode-opt div { display: flex; flex-direction: column; }
.mode-opt b { font-size: 13px; font-weight: 500; }
.mode-opt span { font-size: 11px; color: var(--text-3); margin-top: 1px; }
.mode-opt:has(input:checked) { border-color: var(--brand); background: var(--brand-light); }

.revise-panel {
  background: #f8fafc; border: 1px solid var(--border);
  border-radius: 9px; padding: 12px;
}

/* ============================================================
   客服选择（管理端）
   ============================================================ */
.agent-pick { display: flex; flex-wrap: wrap; gap: 7px; }
.agent-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 11px; border: 1px solid var(--border); border-radius: 20px;
  font-size: 12px; cursor: pointer; background: #fbfcfe; transition: all .15s;
  user-select: none;
}
.agent-chip:hover { border-color: #b9cde8; }
.agent-chip.on { border-color: var(--brand); background: var(--brand-light); color: #1a4f9e; font-weight: 500; }
.agent-chip input { accent-color: var(--brand); margin: 0; }

/* ============================================================
   管理端：各设计分类的订单量
   ============================================================ */
.design-stats { display: flex; flex-direction: column; gap: 6px; margin-top: 4px; }
.ds-row { display: flex; align-items: center; gap: 8px; font-size: 12px; }
.ds-name {
  flex: 0 0 96px; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ds-bar {
  flex: 1; height: 8px; border-radius: 6px; background: #eef2f7; overflow: hidden;
}
.ds-bar i {
  display: block; height: 100%; min-width: 3px;
  background: #3b82f6; border-radius: 6px;
}
.ds-n { flex: 0 0 52px; text-align: right; color: var(--text-3); }

@media (max-width: 420px) {
  .order-item .oi { width: 40px; height: 40px; }
  .order-item .oi-content { font-size: 11px; }
  /* 窄屏：chip 换成两列网格，状态名完整可见且更省竖向空间 */
  .filter-bar { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; }
  .chip { padding: 5px 6px; font-size: 11px; text-align: center; }
  .ref-thumb { width: 58px; height: 58px; }
}

/* ============================================================
   「传截图识别」小按钮（挂在「客户要求」的 label 上）
   ============================================================
   以前它是一个和「参考图」并列的大格子，白占半屏 ——
   而它干的事只是"给客户要求那个框供文字"，贴在框边上才对。
   （老板反馈：填写项看着太多，客服不愿意用） */
.upload-zone.upload-mini {
  display: inline-flex; flex-direction: row; align-items: center;
  padding: 3px 9px; margin-left: 6px; border-radius: 20px;
  border-width: 1px; border-style: solid; border-color: #bcd4f7;
  background: var(--brand-light); color: #1a5bbf;
  font-size: 11px; font-weight: 600; line-height: 1.5;
  vertical-align: 1px; text-align: left; white-space: nowrap;
}
.upload-zone.upload-mini:hover,
.upload-zone.upload-mini:focus {
  background: #dbe9ff; border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(47, 124, 246, .13);
}
/* 小按钮不要「主入口」那个角标（角标是给大格子用的） */
.upload-zone.upload-mini[data-primary]::after { display: none; }
.upload-zone.upload-mini.paste-flash {
  border-color: var(--green); background: #eefaf1;
  box-shadow: 0 0 0 3px rgba(21, 154, 74, .13);
}

/* 窄屏：分类/版式并排时字号收一点，别把 select 挤爆 */
@media (max-width: 420px) {
  .grid2 { gap: 8px; }
  .field > label .upload-mini { font-size: 10.5px; padding: 2px 7px; }
}

/* ============================================================
   上传框：一个按钮 + 点框即粘贴
   ============================================================
   老板反馈原来框里那个「📋 点此粘贴」按钮看不懂 ——
   现在整个框就是粘贴入口，框里只留一个明确的「选择文件」。 */
.zone-btn-file {
  margin-top: 9px; padding: 6px 13px; font-size: 12px;
  background: #fff; border-color: #bcd4f7; color: #1a5bbf; font-weight: 600;
}
.zone-btn-file:hover { background: #eaf2ff; border-color: var(--brand); }

/* 「已就绪，按 Ctrl+V」——点完框必须有明显反馈，
   不然客服看不出任何变化，会以为没点上 */
.upload-zone.paste-ready {
  border-style: solid; border-color: var(--green); background: #eefaf1;
  box-shadow: 0 0 0 3px rgba(21, 154, 74, .15);
}
.upload-zone.paste-ready .t1 { color: #157a3e; }

/* 已有订单的图片：左下角「↔」＝ 在「设计素材 / 风格参考」之间换一下 */
.ref-role {
  position: absolute; bottom: 2px; left: 2px;
  width: 17px; height: 17px; line-height: 17px; text-align: center;
  border-radius: 50%; background: rgba(47, 124, 246, .88); color: #fff;
  font-size: 11px; cursor: pointer; transition: background .15s;
  user-select: none;
}
.ref-role:hover { background: var(--brand); }
