/* ====== 全局样式 ====== */
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
  --primary: #4A6CF7;
  --primary-dark: #3b5de7;
  --bg: #f5f6f8;
  --bg-card: #ffffff;
  --text: #1a1a2e;
  --text-secondary: #6b7280;
  --border: #e5e7eb;
  --success: #10b981;
  --warning: #eab308;
  --danger: #ef4444;
  --radius: 10px;
  --shadow: 0 2px 8px rgba(0,0,0,0.06);
}

body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  color: var(--text);
  font-size: 18px;
  line-height: 1.5;
  /* 2026-10-01 用户要求：平板端铺满整屏、两边不要留白。
     ⚠️ 项目刻意维持「全文 0 条 @media」（不靠断点判设备），所以这里用 100% 而不是加媒体查询：
     手机上本来就到不了 480px，改了等于没改；平板/电脑才会真的铺开。 */
  max-width: 100%;
  margin: 0 auto;
  min-height: 100vh;
  position: relative;
  padding-bottom: 56px;
}

/* ====== 电脑端收窄（2026-10-01）======
   用户先要「平板端铺满整屏、两边不要留白」，于是上面这个 body（连同 .fullpage / .profile-page /
   .bottombar）的 max-width 都写成了 100%；紧接着又说「电脑端是全屏了，改回去」。
   所以这里把**电脑**单独收窄回 480 居中，手机和平板照旧铺满。

   ⚠️ 判据是「电脑」不是「屏幕有多宽」：靠 html 上的 .ua-desktop（脚本写在 index.html 的 </head> 之前，
      必须在那儿 —— 再晚就会先铺满再收窄，肉眼看到闪一下）。项目刻意维持「全文 0 条 @media」，
      所以用类名而不是媒体查询，电脑上把窗口拖窄仍然是电脑版。
   ⚠️ 四个容器一个都不能漏：body 管普通页面、.fullpage 管图包/文案/讲解视频那些整页浮层、
      .profile-page 管个人中心（它是 fixed + inset:0 撑满的）、.bottombar 是底部条
      （目前没有元素用它，是死 CSS，但一起收着，免得将来谁用上又漏掉）。 */
html.ua-desktop body,
html.ua-desktop .fullpage,
html.ua-desktop .profile-page,
html.ua-desktop .bottombar {
  max-width: 480px;
  margin: 0 auto;
}

/* ====== 顶部导航 ====== */
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  background: var(--bg-card);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  /* ⚠️ 10 = 只在首页 / 预览并生成页可见。个人中心及其子页、结果页(1000)、弹窗(2000)照旧盖住它 ——
     用户 2026-09-18 明确要求"顶部栏只在主页和预览并生成显示，不要所有页面都有"，
     所以**不要**为了"主页键随处可点"把它抬到 1100（改过一次，被否掉了）。 */
  z-index: 10;
}
/* 顶栏左侧：logo 图形 + 品牌名（2026-10-09 加的图形）。
   ⚠️ .topbar-left 必须是 flex：img 默认跟文字按【基线】对齐，会浮在文字上方半截。 */
.topbar-left { display: flex; align-items: center; gap: 8px; }
.topbar-logo { width: 30px; height: 30px; display: block; }
.topbar-title { font-size: 21px; font-weight: 700; }
.topbar-right { display: flex; gap: 8px; }
.topbar-btn {
  background: none;
  border: none;
  font-size: 21px;
  padding: 4px 8px;
  cursor: pointer;
  border-radius: 6px;
}
.topbar-btn:active { background: var(--bg); }

/* 单页滚动的小节分隔（配合 step-header 使用） */
.section-divider {
  margin-top: 24px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}

/* 区块标题点击切换 */
.step-header.clickable {
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  background: var(--success);
  border-radius: 12px;
  padding: 12px 14px;
  color: #fff;
}
.step-header.clickable:active { opacity: 0.85; }
.step-header.clickable .step-num {
  background: rgba(255,255,255,0.28);
}
.step-header.clickable .step-title { color: #fff; }
.step-header.clickable .step-hint { color: rgba(255,255,255,0.85); }
.step-header.active-sec .step-num {
  box-shadow: 0 0 0 3px rgba(255,255,255,0.35);
}
#infoSection.hidden, #uploadSection.hidden { display: none; }

/* ====== 主内容区 ====== */
.main-content {
  padding: 16px;
}

/* ====== 步骤切换 ====== */
.step { display: none; }
.step.active { display: block; }

.step-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 20px;
}
.step-num {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--primary);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 17px;
  font-weight: 600;
  flex-shrink: 0;
}
/* 2026-10-01：全局字号 +3 之后「上传车辆照片」被右边的提示挤成了「上传车辆照 / 片」两行。
   标题必须**永远一行**——这是页面的大标题，折行看着像坏了。
   加 nowrap 后标题宽度固定，宽度不够时改成右边的提示折行（提示折行无所谓，它本来就是句说明）。 */
.step-title { font-size: 20px; font-weight: 600; white-space: nowrap; }
.step-hint {
  font-size: 15px;
  color: var(--text-secondary);
  margin-left: 8px;
  font-weight: 400;
}

/* ====== 表单 ====== */
.form-group {
  margin-bottom: 14px;
}
.form-label {
  display: block;
  font-size: 17px;
  font-weight: 500;
  margin-bottom: 6px;
  color: var(--text);
}
.label-tip {
  font-size: 15px;
  color: var(--text-secondary);
  font-weight: 400;
  margin-left: 6px;
}
.form-input, .form-textarea {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 18px;
  outline: none;
  transition: border-color 0.2s;
  background: var(--bg-card);
  color: var(--text); /* 车商输入的文字：正常深色 */
}
/* 系统提示词（placeholder）：灰白色，与用户输入的深色明显区分开 */
.form-input::placeholder, .form-textarea::placeholder {
  color: #b0b6bd;
  opacity: 1; /* 某些浏览器默认降低 placeholder 透明度，统一为完整灰白色 */
}
.form-input:focus, .form-textarea:focus {
  border-color: var(--primary);
}
.form-textarea {
  resize: vertical;
  min-height: 80px;
  font-family: inherit;
}
/* 描述框右下角压着两颗按钮（🎤 和「识别」），文字必须给它们让位。
   ⚠️ 不留这段底部内边距的话，文字会一直流到按钮底下被盖住 —— 用户 2026-10-08 报的就是这个
      （「文字多的时候按键没有自动下移、会盖住文字」）。
      文字多时框自己会长高（见 app.js 的 syncDescHeight），但**长高只解决"要不要滚动"**，
      不让位的话最后几行照样钻到按钮底下 —— 两件事都得做，缺一个都还是盖着。
   ⚠️ 48 = 按钮高 36 + 距底 8 + 4 余量。这两个数在 .btn-extract / .btn-voice 上，改它们时这里要跟着改。
   ⚠️ max-height 到了就转框内滚动；滚到底时最后一行仍在按钮上方，因为上面的底边距已经让开了。
   ⚠️ resize 必须关掉：高度归 app.js 管，留着手柄会和它打架（车商拉高一下、再打一个字就弹回去）。 */
.textarea-wrap .form-textarea {
  padding-bottom: 48px;
  max-height: 320px;
  overflow-y: auto;
  resize: none;
}
.form-row-2col {
  display: grid;
  /* ⚠️⚠️ 这三处（minmax(0,1fr) ×2 + 下面两条 min-width:0）是**同一个 iOS 问题的双保险**，别只删一处。
     根因：`1fr` 等价于 `minmax(auto, 1fr)`，而 grid 子项默认 `min-width: auto` = **自动最小尺寸**
     = 内容的 min-content。iOS 上原生月份控件（上牌时间 / 保险到期）的 min-content 远大于列宽，
     于是左轨道被顶到 840px、右轨道挤成 26px，「里程」「颜色」连同输入框被压成一条缝并顶出卡片
     （2026-09-23 用户报「输入框超长、跟里程和颜色重叠了」）。
     ⚠️ 轨道写 `minmax(0, 1fr)` 是把 auto 最小值从**轨道**这层拿掉；
       子项写 `min-width: 0` 是把 auto 最小值从**元素**这层拿掉。**两层都要**才最稳：
       只改轨道的话，子项仍会按自己的自动最小尺寸撑出轨道外。
     `min-width: 0` 也是项目既有的防溢出惯例（见 .profile-mid / .pi-main / .link-info）。
     ⚠️ 不要为此把 type 改成 text：会丢掉系统日期选择器。 */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 10px;
}
.form-row-2col > * { min-width: 0; }
.form-row-2col .form-input { min-width: 0; }

/* ⚠️⚠️ 第三层（2026-09-23 补）：iOS 原生月份控件**自成一套宽度算法**，上面那两层管不到它。
   症状：只有「上牌时间 / 保险到期」两个框比列宽出一截，把「里程 / 颜色」压住、两框之间
   那 10px 空隙被吃光；**安卓和电脑完全正常**。
   实测（用户三张真机截图逐像素量的）：列宽 230 → 框 258；列宽 219 → 框 241。
   → **框宽 = 列宽 + 26**，26 正好 = 我们的 padding 12×2 + border 1×2。
   → 即 iOS 把这个控件的 width 当成「内容宽」，算完**又把 padding/border 加了一遍**。
   根因是 WebKit 的 iOS 专属 bug：「带 padding 的 date/time 类输入框，width:100% 会被算错」
   （WebKit bug 301648 —— 只影响 iOS，同族类型 date/time/datetime-local/week 同样中招；
   桌面 Safari 正常）。Safari 把这类控件渲染成 inline-flex、内部影子字段自带内边距，
   于是它不认标准的 border-box。

   ✅ 解法：**把 box-sizing 明确写成 content-box，再用 calc 把 padding+border 减掉**。
      于是「按 content-box 算」这件事不再是 bug 而是我们写死的规则，
      **有 bug 的 iOS 和正常的安卓/电脑会算出完全相同的结果**：
        content = 列宽 − 26，加上 padding 24 + border 2 → border-box 正好 = 列宽。
   ⚠️ 26 = 12(padding-left/right) × 2 + 1(border) × 2。**改 .form-input 的 padding 或 border
      就必须同步改这个数**，否则这两个框会比别的框宽/窄一截。
   ⚠️ max-width:100% 之类的封顶写法**对这个 bug 无效**：它是「宽度算完之后再加 padding」，
      封顶封的是算之前那个值，加完照样超出去（验证脚本里量过）。
   ⚠️⚠️ 不要改成 type="text"、也不要加 -webkit-appearance:none —— 会丢掉系统日期选择器，
      而这两个字段的价值就在那个滑动选择器上。 */
.form-row-2col input[type="month"] {
  box-sizing: content-box;
  width: calc(100% - 26px);
}

/* 「上牌时间 / 保险到期」框里的格式提示（2026-09-23 用户要求：提示语要在框里，不是在名称后面）
   ⚠️⚠️ 为什么必须自绘：`type="month"` **不认 placeholder** —— 实测给控件加上 placeholder 后
   截图逐字节完全一样（等于没加）。而苹果手机/平板上的浏览器**不认 month 类型**，会把它退化成
   一个纯文本框 → 框里原本一片空白，车商不知道该填什么格式。
   ✅ 做法：把提示字绝对定位**盖在输入框里**，但**默认隐藏**，只有 app.js 探测到
   「浏览器不认 month」时才给 .mo-wrap 加 .fallback 打开它 —— 认这个类型的（电脑 Chrome 会自己
   画 `----年--月`、有的安卓自带小三角）**一律不盖**，免得糊住它自己的原生控件。
   ⚠️ 左边距 13px = .form-input 的 border 1px + padding-left 12px，跟别的框里 placeholder 对齐；
      改 .form-input 的 padding/border 要同步改这里。 */
.mo-wrap {
  position: relative;
}
.mo-hint {
  display: none;
}
.mo-wrap.fallback .mo-hint {
  display: block;
  position: absolute;
  left: 13px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 18px;
  color: #b0b6bd;            /* 与 .form-input::placeholder 同一个灰 */
  pointer-events: none;      /* 别挡住点框 */
  max-width: calc(100% - 26px);
  overflow: hidden;
  white-space: nowrap;
}
/* 填了值就把提示字收起来，由 app.js 的 syncMonthHints() 打 .filled。
   ⚠️⚠️ 别改回 CSS 的 `:placeholder-shown` 自动跟——实测（Chromium）：
   `type="month"` 的框上 `:placeholder-shown` **永远不匹配**（浏览器压根不认它的 placeholder），
   于是 `:not(:placeholder-shown)` 恒为真，这条会把提示字**永久藏住**（本机跑出来就是 display:none）。
   走 JS 还顺带解决另一件事：识别回填 / 清空表单 / 记录回填那三处都是**直接赋值**、不触发事件。 */
.mo-wrap.fallback.filled .mo-hint {
  display: none;
}

/* 安卓（用户 2026-09-23：「能不能把小三角改大一点点」）：放大浏览器自带的小三角。
   它长在输入框的 shadow DOM 里，普通选择器够不着，只能改 Blink 专有的这个伪元素。
   ⚠️ 只对安卓生效 —— `html.ua-android` 是 app.js 按 UA 打的标记，电脑上的日历图标不受影响。
   ⚠️ 用 `transform: scale` 而不是写 width/height：后者会把图标本身重新拉伸出锯齿。
   ⚠️ 万一某台安卓上这点没反应，说明那个小三角不是这个伪元素画的（那就改不动），别在这儿死磕。
   ⚠️ 同时把提示字的活动范围收窄，免得它在窄屏上顶到三角身上。 */
html.ua-android input[type="month"]::-webkit-calendar-picker-indicator {
  transform: scale(1.5);
  cursor: pointer;
}
html.ua-android .mo-hint {
  max-width: calc(100% - 58px);
}

/* 车况描述 + 识别/语音按钮 */
.textarea-wrap {
  position: relative;
}
/* 灰白 #e5e7eb 是两个按钮的"未激活"底色。原来用的 #9ca3af 压在白色描述框上太重、太突兀 */
.btn-extract {
  position: absolute;
  right: 8px;               /* 最靠右（语音键在它左边） */
  bottom: 8px;
  height: 36px;
  width: 74px;              /* 固定宽，不用 min-width：宽度会变的话下方语音键的 right 就算不死、会重叠 */
  border-radius: 18px;
  border: none;
  background: var(--primary);
  color: #fff;
  font-size: 16px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  white-space: nowrap;
}
.btn-extract:active { background: var(--primary-dark); }
/* 描述为空 / 识别中：灰白底 + 中灰字（灰白底上再配白字就看不清了） */
.btn-extract:disabled { background: #e5e7eb; color: #6b7280; cursor: not-allowed; }
.btn-voice {
  position: absolute;
  right: 90px;              /* = 8(右边距) + 74(识别键宽) + 8(两键间距) */
  bottom: 8px;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: none;
  background: #e5e7eb;      /* 默认灰白；录音中由 .recording 置红 */
  color: #6b7280;
  font-size: 19px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.btn-voice:active { background: #d1d5db; }
.btn-voice.recording { background: #ef4444; color: #fff; }            /* 录音中：红底白图标 */
.btn-voice:disabled { opacity: .5; cursor: not-allowed; }             /* 识别中不可点 */

/* 识别中把描述框锁成只读，背景压浅一档做视觉提示 */
.form-textarea:read-only { background: #f3f4f6; }

/* ====== 按钮 ====== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 10px 20px;
  border: none;
  border-radius: var(--radius);
  font-size: 18px;
  font-weight: 500;
  cursor: pointer;
  transition: opacity 0.2s;
  /* 2026-10-01：全局字号 +3 之后，「生成重置」「← 返回修改」这两个小键被挤成了
     「生成重 / 置」两行。按钮上的字**永远不该折行**——折了就不像个键了。
     宽度不够时该换的是旁边的东西，不是把按钮掰成两半。 */
  white-space: nowrap;
}
.btn:active { opacity: 0.8; }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-primary { background: var(--primary); color: #fff; }
.btn-outline { background: none; border: 1px solid var(--border); color: var(--text); }
.btn-block { width: 100%; padding: 12px; margin-top: 8px; }
.btn-sm { padding: 8px 14px; font-size: 16px; }
.btn-group {
  display: flex;
  gap: 10px;
  margin-top: 16px;
}
.btn-group .btn { flex: 1; }
.btn-group-sm {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}
/* 文案模块生成后的按钮组：右对齐，查看无背景、开始生成蓝色，两个高度一致 */
/* .ro-btn-group = 只读预览态下单个「查看」按钮的组（车商从「生成记录」看关联账号的记录时）。
   与 owner 端 [查看][开始生成] 同为右对齐小按钮，只是没有第二个按钮。 */
.copy-btn-group, .video-btn-group, .ro-btn-group {
  justify-content: flex-end;
  align-items: center;
  margin-top: 12px;
}
.copy-btn-group .btn, .video-btn-group .btn, .ro-btn-group .btn {
  height: 36px;
  padding: 0 14px;
  border: 1px solid;
  line-height: 1;
}
.copy-btn-group .btn-copy-view, .video-btn-group .btn-copy-view {
  background: none;
  color: var(--text);
  border-color: var(--border);
  transform: translateY(4px);  /* 文案区「查看」下移4px */
}
/* 视频区生成后「查看」与「开始生成」水平对齐（不高不低） */
.video-btn-group .btn-copy-view {
  transform: translateY(0);
}
.copy-btn-group .btn-copy-go, .video-btn-group .btn-copy-go {
  border-color: transparent;
}
/* 视频区生成后按钮组内「开始生成」不继承 module-btn 的 margin-top/align-self，保证与「查看」水平对齐 */
.module-item[data-module="video"] .video-btn-group .btn-copy-go {
  margin-top: 0;
  align-self: auto;
}

/* ====== 上传照片 ====== */
.upload-section { margin-bottom: 12px; }
.upload-category { margin-bottom: 16px; }
.upload-label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 17px;
  font-weight: 500;
  margin-bottom: 6px;
}
.upload-count {
  font-size: 15px;
  font-weight: 400;
  /* ⚠️ 颜色**必须写在外壳上**：斜杠 `/` 是夹在两个 <b> 之间的**裸文本节点**，
     它没有自己的选择器、只认这条。写在 .pc-now 上是管不到它的。 */
  color: #9ca3af;
}
/* 环绕视频那格显示的是「未上传」三个字（不是 0/20），**故意保持蓝色**、不跟着变灰。
   ⚠️ 它必须单独写一条 id 规则，**别把蓝色挪回 .upload-count** ——
      那会让照片那 6 个分类的斜杠也跟着变蓝。
   ⚠️ 顺带：id 优先级高于 .upload-count.full，所以这里也挡住了"万一它被误加 .full"。 */
#videoStatus {
  color: var(--primary);
  font-weight: 600;
}
/* 分类标题后面的「已传/上限」计数。
   ⚠️ 这条 2026-10-07 改过三版，**现在这版是用户后来重新定的**：
     平时三个字符（两个数 + 斜杠）**都是灰白色、细体**（安静不抢眼），**满了才整格变红**。
     ⚠️ 别改回「斜杠后面那个上限一直红」—— 那是第一版，用户明确否掉了。
     ⚠️ 也别改回「数字 700 深灰 + 斜杠蓝色」—— 那是第二版，用户当天再次否掉：
        「字体再细一点，颜色也灰白点，包括 / 也和字体一样」。
   ⚠️ 变色的类挂在**外壳 .upload-count 上**（由 updatePhotoCount() 打），
     不是挂在斜杠前那个数身上 —— 因为要**含斜杠在内整格一起**变色。
   ⚠️ 数字的 font-weight **必须显式写 400**：它们标签是 <b>，浏览器默认值是 `bolder`，
     父级 400 会被它算成 700 —— 不写这条就白改（颜色反倒可以靠继承，不用重复写）。 */
.upload-count .pc-now,
.upload-count .pc-max { font-weight: 400; }
.upload-count.full { color: var(--danger); }
.upload-tip2 {
  font-size: 14px;
  color: var(--primary);
  font-weight: 400;
  margin-left: 4px;
}
.upload-tag {
  font-size: 14px;
  color: var(--text-secondary);
  font-weight: 400;
  padding: 1px 6px;
  border-radius: 4px;
  background: var(--bg);
}
.upload-area {
  border: 2px dashed var(--border);
  border-radius: var(--radius);
  padding: 20px;
  text-align: center;
  cursor: pointer;
  transition: border-color 0.2s;
  position: relative;
}
.upload-area:active { border-color: var(--primary); }
.upload-area input { display: none; }
/* 上传框里的说明句（「点击选择多张照片（最多20张）」这类）。
   ⚠️ 2026-10-07 用户要求「再灰白点」：从 --text-secondary(#6b7280) 调淡到 #9ca3af。
   ⚠️ 这里**刻意不跟着 --text-secondary 走** —— 那个变量管全站几十处正文说明，
      动它会连累一大片；这条只针对上传框这一处。
   ⚠️ 框里的 .hint-req / .hint-fail 自带颜色和底色，不受这条影响。 */
.upload-hint { font-size: 16px; color: #9ca3af; }
/* 上传框提示里的「硬要求」强调（目前只有环绕视频的「横屏拍摄」用）：
   那四个字写的是会被服务端**直接拦截**的条件，不该和普通灰色说明一个色。
   ⚠️⚠️ 2026-09-29 用户否掉了第一版「纯红加粗」：「横屏拍摄这四个红字有点太突兀了」。
      原因是 13px 灰句子中间突然蹦出四个高饱和红字，**没有容器**、像颜色出了故障。
      改法**跟积分到期那套同源**（见 .points-tr.exp-danger 那段的注释）：
      颜色**不上字、上底色**，文字用同色系深色 —— 一个淡红小标签，醒目但不再刺眼。
   ⚠️ 色号**直接沿用积分那对** `#fee2e2` / `#991b1b`，别自己另调一套：
      同一个产品里"浅红底+深红字"只该有一种说法。
   ⚠️ 字重从 700 降到 600：底色已经在做强调，再加 700 就又回到"突兀"。
   ⚠️ 用红不用黄 —— 项目踩过「黄色做白底文字」的坑，白底上根本看不清。
   ⚠️ 选择器**限定在 .upload-hint 内**：拍摄规范弹窗里那句「横屏拍摄」本来就是加粗的，
      不该被这条染色（用户只要求改上传框）。 */
.upload-hint .hint-req {
  color: #991b1b;          /* 深红字（与积分到期同色） */
  background: #fee2e2;     /* 淡红底 —— 强调靠它，不靠刺眼的高饱和红字 */
  border-radius: 4px;
  padding: 1px 6px;
  font-weight: 600;
}
/* 视频压缩失败时框里那句话（2026-09-30）。
   ⚠️ 与上面 .hint-req **同一套色**（淡红底 + 深红字 + 600），别再另调一套：
      同一个产品里"浅红底+深红字"只该有一种说法，而且纯红加粗已经被否过一次。
   ⚠️ 它**顶掉**原来那句上传要求（不是并排、不是追加在下面）—— 失败时框里只剩这一句，
      车商点一下框重传，原来那句自己回来。见 app.js 的 showVideoFail / clearVideoFail。
   ⚠️ 选择器同样**限定在 .upload-hint 内**：别写成全局 .hint-fail。 */
.upload-hint .hint-fail {
  color: #991b1b;
  background: #fee2e2;
  border-radius: 4px;
  padding: 1px 6px;
  font-weight: 600;
}
.upload-preview {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 8px;
  margin-top: 8px;
}
.upload-thumb {
  position: relative;
}
.upload-thumb img {
  width: 100%;
  aspect-ratio: 1/1;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid var(--border);
  display: block;
}
.thumb-del {
  position: absolute;
  top: -6px;
  right: -6px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: rgba(0,0,0,0.65);
  color: #fff;
  border: none;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2;
}
.thumb-del:active { background: var(--danger); }
/* 检测报告 PDF 缩略图（首页上传区，网格内自适应方形文件卡片） */
.upload-thumb.thumb-pdf {
  width: 100%;
  aspect-ratio: 1/1;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-card);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  cursor: pointer;
}
.upload-thumb.thumb-pdf .pdf-ico { font-size: 25px; line-height: 1; }
.upload-thumb.thumb-pdf .pdf-name {
  font-size: 12px;
  color: var(--text-secondary);
  max-width: 62px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 上传照片后的"继续添加"小框（与缩略图同尺寸，虚线加号） */
.upload-add {
  width: 100%;
  aspect-ratio: 1/1;
  border: 2px dashed var(--border);
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-size: 31px;
  line-height: 1;
  color: var(--text-secondary);
  background: var(--bg-card);
  transition: border-color 0.2s, color 0.2s;
}
.upload-add:active { border-color: var(--primary); color: var(--primary); }
.upload-tip {
  font-size: 16px;
  color: var(--text-secondary);
  text-align: center;
  margin-bottom: 8px;
}

/* 视频上传预览 */
.video-preview video {
  width: 100%;
  border-radius: 8px;
  max-height: 200px;
  background: #000;
}
/* 视频容器：右上角 × 删除按钮 */
.video-wrap { position: relative; }
.video-del {
  position: absolute;
  top: -6px;
  right: -6px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: rgba(0,0,0,0.65);
  color: #fff;
  font-size: 16px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  z-index: 2;
}

/* 环绕视频「上传 + 压缩」进度圈：盖在视频画面上，从 12 点方向顺时针走一圈。
   ⚠️ 弧线用 var(--success)=#10b981（**状态绿**），不是对话气泡那个微信绿 #07c160 ——
      项目里这两个绿是分开的（见联系后台页那套），别看到两个绿就想"统一"。
   ⚠️ 只转圈 + 中间一个百分数，**不出现任何句子**（用户明确要求不要提示语）。
   ⚠️ 这里不许写 @media：全项目 style.css 刻意保持 0 条断点，判设备走 isMobileDevice()。 */
.vc-ring {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 72px;
  height: 72px;
  z-index: 3;              /* 高于 .video-del 的 2 */
  pointer-events: none;    /* 不挡播放控件、不挡右上角 × */
}
.vc-ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }  /* -90° = 从 12 点起步 */
.vc-track { fill: none; stroke: rgba(255,255,255,0.30); stroke-width: 8; }
.vc-arc {
  fill: none;
  stroke: var(--success);
  stroke-width: 8;
  stroke-linecap: round;
  stroke-dasharray: 276.46;    /* 2πr，r=44 */
  stroke-dashoffset: 276.46;   /* 276.46=空、0=满 */
  transition: stroke-dashoffset .25s linear;
}
.vc-num {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 19px;
  font-weight: 600;
  text-shadow: 0 1px 3px rgba(0,0,0,0.7);   /* 压在任意视频画面上都要看得清 */
}

/* ====== 预览卡片 ====== */
.preview-card {
  background: var(--bg-card);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin-bottom: 12px;
  box-shadow: var(--shadow);
}
.preview-card-title {
  font-size: 17px;
  font-weight: 600;
  margin-bottom: 6px;
}
.preview-card-body { font-size: 17px; color: var(--text-secondary); }
/* 上传统计（视频 / 照片含 PDF）：黑色加粗放大 */
.preview-photo-count {
  font-size: 18px;
  color: var(--text);
  font-weight: 600;
}
.preview-card-body .info-line {
  display: flex;
  justify-content: space-between;
  padding: 4px 0;
  border-bottom: 1px solid var(--bg);
  color: var(--text);
}
.preview-card-body .info-line .label {
  color: var(--text-secondary);
}
/* 车辆信息预览：双列网格（节省垂直空间） */
.preview-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px 14px;
}
.preview-item {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  font-size: 18px;
  padding: 5px 0;
  border-bottom: 1px solid var(--bg);
  color: var(--text);
}
.preview-item .label { color: var(--text-secondary); flex-shrink: 0; }
.preview-item .val { color: var(--text); text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ====== 功能模块列表 ====== */
.module-list { margin-top: 8px; }
.module-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: var(--bg-card);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin-bottom: 10px;
  box-shadow: var(--shadow);
}
.module-name { font-size: 18px; font-weight: 600; margin-bottom: 2px; }
/* 功能名称后面的价签：括号里的数字 = 点「开始生成」实际要扣的积分（不是单价，选了平台/音色就是总数）。
   比名称小一号、不加粗，免得抢标题；积分不够时整段变红（车商不用点就知道不够）。 */
.pts-tag { font-size: 16px; font-weight: 400; color: var(--text-secondary); margin-left: 2px; }
/* 积分够/不够两种色（用户 2026-10-01 定：够=绿 / 不够=红 / 没登录=灰）。
   ⚠️ 两个类都由 renderPriceTags 挂，必要条件是「**已登录**」——没登录时两个类都**不挂**，
      自动落回上面那条的灰色。别照着色号反推逻辑。
   .pack-opt-price = 图包弹窗里「选择平台」后面那个单价。用户要求它「和前面的一样颜色」，
   所以并进**同一条规则**，别给它另写一份色值 —— 两份早晚会漂成两个颜色。 */
.pts-tag.insufficient, .pack-opt-price.insufficient { color: #ef4444; }
.pts-tag.sufficient,   .pack-opt-price.sufficient   { color: var(--success); }
.module-desc { font-size: 15px; color: var(--text-secondary); }
.interior-toggle { display: flex; align-items: center; gap: 6px; font-size: 15px; color: var(--text-secondary); margin-top: 6px; cursor: pointer; }
.interior-toggle input { width: 16px; height: 16px; accent-color: #10b981; cursor: pointer; }

/* ====== 底部栏 ====== */
.bottombar {
  position: fixed;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  max-width: 100%;
  background: var(--bg-card);
  border-top: 1px solid var(--border);
  padding: 10px 16px;
  display: flex;
  align-items: center;
  gap: 8px;
  z-index: 10;
}
.balance-label { font-size: 17px; color: var(--text-secondary); }
.balance-amount {
  font-size: 21px;
  font-weight: 700;
  color: var(--primary);
  flex: 1;
}
.btn-recharge {
  background: var(--primary);
  color: #fff;
  border: none;
  border-radius: 20px;
  padding: 6px 16px;
  font-size: 16px;
  cursor: pointer;
}
.btn-recharge:active { background: var(--primary-dark); }

/* ====== 结果弹窗/进度条 ====== */
.modal-overlay {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(0,0,0,0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2000;   /* 高于个人中心全屏页(1000)和结果页(1000)，弹窗永远在最上层 */
}
/* ====== 环绕视频拍摄规范弹窗 ====== */
.guide-box {
  background: #fff;
  border-radius: 16px;
  width: 92%;
  max-width: 480px;
  max-height: 82vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.guide-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.guide-title { font-size: 19px; font-weight: 600; }
.guide-body {
  flex: 1;
  overflow-y: auto;
  padding: 14px 16px 18px;
  font-size: 16px;
  line-height: 1.7;
  color: var(--text);
  -webkit-overflow-scrolling: touch;
}
.guide-body h4 { font-size: 17px; margin: 12px 0 4px; }
.guide-body h4:first-child { margin-top: 0; }
.guide-body ol { padding-left: 20px; margin: 4px 0 0; }
.guide-body li { margin: 3px 0; }
.guide-remark {
  background: #fff8e6;
  border-radius: 8px;
  padding: 8px 10px;
  color: #8a6d1a;
  font-size: 15px;
  line-height: 1.6;
  margin-top: 2px;
}
.guide-agree {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
  font-size: 16px;
  font-weight: 500;
}
.guide-agree input { width: 16px; height: 16px; flex-shrink: 0; }
/* 「7天内不再提醒」（用户 2026-10-08 加的）：排在「本人已知晓并理解」**上面**，
   是个偶尔用一次的免打扰（位置用户拍板，当天挪了两回，别再动）。
   ⚠️ **颜色跟「本人已知晓并理解」那条一模一样**（用户要求：「字体颜色和本人已知晓并理解一样」）。
      这里**不写 color** 就是继承，等于跟 .guide-agree 同色；写死一个色号反而会在换主题时对不上。
   ⚠️ 字号/字重仍比那条轻一档（16px/500 → 15px/400）：这是**有意的**，别一起"统一"掉 ——
      两条都是 16px + 500 的话看着一样重，车商会以为两个都得勾、两个都不敢漏。 */
.guide-snooze { font-size: 15px; font-weight: 400; }
#guideConfirm { margin-top: 12px; }
.modal-box {
  background: var(--bg-card);
  border-radius: 16px;
  padding: 24px;
  width: 85%;
  max-width: 360px;
  text-align: center;
  /* 2026-10-01 全局字号 +3 之后，小屏横屏（667×375）下登录弹窗高 387px、比屏幕还高 12px，上下两头被切。
     根因是这个基类**从来没有高度上限** —— 同文件的 .image-modal-box / .refer-modal-box 都写了 80vh，
     唯独最常用来放文字的这个没有，内容一长就直接顶出屏幕，而且没有任何滚动。
     ⚠️ 必须用 auto 不能用 hidden：hidden 会把超出的部分**直接切掉**，
        而超出的往往正是最下面那颗「登录」键 —— 那才是真的点不到。
     ⚠️ 内容不超高时这两个属性完全不生效，对现有弹窗是零影响。 */
  max-height: 90vh;
  overflow-y: auto;
}

/* ====== 账户冻结提示弹窗 ====== */
/* 文案与两组清单由 app.js 决定何时弹（登录后 / 已登录进入时各一次），样式只管好看。
   .frozen-li 要单独设 text-align:left：.modal-box 是中心对齐的，清单居中就完全没法读 */
.frozen-head { font-size: 20px; font-weight: 600; color: #ef4444; margin-bottom: 10px; }
.frozen-desc { font-size: 16px; line-height: 1.65; color: var(--text-secondary); text-align: left; margin-bottom: 14px; }
.frozen-group { background: #f9fafb; border-radius: 10px; padding: 10px 12px; margin-bottom: 10px; text-align: left; }
.frozen-h { font-size: 15px; font-weight: 600; margin-bottom: 6px; }
.frozen-h.off { color: #ef4444; }
.frozen-h.on { color: #10b981; }
.frozen-li { font-size: 15.5px; line-height: 1.75; color: var(--text-secondary); }
/* 冻结账户的「充值 / 提现」按钮：置灰 + 按钮文字直接写着原因，不用点也知道为什么 */
.frozen-locked, .frozen-locked:disabled {
  background: #e5e7eb !important;
  color: #9ca3af !important;
  border-color: #e5e7eb !important;
  cursor: not-allowed !important;
}

/* ====== 会话被终止弹窗（2026-10-07「单设备登录」）====== */
/* 「账号已在其他设备登录」/「账号已被停用」两个框的外壳。何时弹、文案是什么全在 app.js 里定
   （服务端给的 reason 二选一），这里只管好看。触发链见 index.html 那两段注释。 */
/* ⚠️ 标题**刻意不用** .frozen-head / .genFailTitle 那个红（#ef4444）：那两个说的是「出故障了」，
   而这两个说的是「你被退出了，去重登」。红字在这类白底框里会像颜色出错 ——
   项目里为"红色强调"栽过两次（见本文件「白底上强调文字」那段的正解）。所以走深灰。 */
.kicked-box { position: relative; }   /* 右上角 × 的定位基准；.modal-box 本身没有 position */
.kicked-head {
  font-size: 20px; font-weight: 600; color: #1f2937; margin-bottom: 10px;
  /* 左右各留出 × 的位置：标题居中，窄屏（320 宽）下会折成两行 —— 那也比压在 × 底下强。
     ⚠️ 这两个 30px 和下面 × 的宽度是一对，改 × 尺寸要回来看这里。 */
  padding: 0 30px;
}
.kicked-desc { font-size: 16px; line-height: 1.65; color: var(--text-secondary); margin-bottom: 18px; }
/* 右上角 ×（用户 2026-10-07 指定：「点×关闭提示框，页面自动回到程序首页」）。
   ⚠️ 40×40 是**手机上手指能点准的下限**，不是美观值 —— 别为了好看缩小，验证脚本按实际像素量着。
   ⚠️ 偏移量是按 **.modal-box 的 24px 内边距**单独算的，**不能照抄 .pts-expire-close**：
      那个框是另一个外壳（.help-modal-box，内边距不同），抄过来 × 会飘。 */
.kicked-x {
  position: absolute; top: 2px; right: 2px;
  width: 40px; height: 40px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  background: none; border: none; padding: 0; cursor: pointer;
  font-size: 23px; color: var(--text-secondary);
}
.kicked-x:active { opacity: .6; }

/* ====== 生成中：状态键 + 取消键（2026-10-01）====== */
/* 生成改成后台任务后，「正在生成中…」右边多一个独立的「取消」键（用户拍板的位置）。
   ⚠️ 取消键**不是 disabled**，哪怕今天 3 次取消已经用完：手机上 title 看不见，
     点一下弹「今天的取消次数已用完（每天 3 次）」比一个点了没反应的灰键清楚得多。
     .gen-cancel-off 只负责画出"这个键现在不灵"的样子。 */
.gen-running-group { display: flex; gap: 8px; align-items: center; }
/* 「正在生成中…」要看得出来程序还在跑（用户 2026-10-08：静止的文字盯久了看不出是卡死还是在跑）。
   做成整段文字轻微上下跳。
   ⚠️ 只跳 3px：这个键在手机上高 36px，跳多了会顶到键的上下边框，看着像整个按钮在抖。
   ⚠️ 动的是**按钮本身**（transform 直接落在 .gen-status 上），不是往文字外面套一层 span ——
      那样得把 genRunningText 的返回值从纯文本改成 HTML，为了一个动画不值当。 */
@keyframes gen-status-bounce {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-3px); }
}
.gen-status { cursor: default; animation: gen-status-bounce .9s ease-in-out infinite; }
.gen-cancel-off { opacity: .55; }

/* 讲解视频生成中的「正在生成中… + 取消」这一组，也要跟「开始生成」一样**靠右**、并跟音色卡拉开距离。
   ⚠️ 上面 `.module-item[data-module="video"] .module-btn` 那条 align-self/margin-top **管不到它**：
      生成时那颗按钮被整组换成了 .btn-group-sm.gen-running-group（见 app.js 的 paintGenRunning），
      它身上**没有** .module-btn → 掉回 .module-item 的默认左对齐；而 .btn-group-sm 又不带任何外边距
      → 结果既贴左边、又紧贴着上面那排音色卡。用户 2026-10-08 报的「一点生成按钮就跑到左边、
      还贴着音色卡」就是这么来的。
   ⚠️ margin-top 取 10px 是**跟「开始生成」那颗一模一样**：这样点下去的一瞬间按钮不会往下跳一下。
   ⚠️ 3D 转台（h5）没有这个毛病，那边由 `.module-item[data-module="h5"] > .btn-group-sm` 命中，别顺手改它。 */
.module-item[data-module="video"] > .gen-running-group {
  align-self: flex-end;
  margin-top: 10px;
}

/* ====== 登录/注册弹窗 ====== */
/* ====== 精修图选图 ====== */
.image-modal-box, .result-modal-box {
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  text-align: left;
}
.image-select-grid {
  flex: 1;
  overflow-y: auto;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  padding-right: 4px;
}
.image-cat-title {
  grid-column: 1 / -1;
  font-size: 16px;
  font-weight: 600;
  color: var(--text-secondary);
  padding: 6px 0 2px;
  border-bottom: 1px solid var(--bg);
}
.image-select-item {
  position: relative;
  border-radius: 8px;
  overflow: hidden;
  border: 2px solid var(--border);
  cursor: pointer;
  aspect-ratio: 1;
}
.image-select-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.image-select-item .check {
  position: absolute;
  top: 4px;
  right: 4px;
  font-size: 17px;
  opacity: 0;
}
.image-select-item.selected {
  border-color: #10b981;
}
.image-select-item.selected .check {
  opacity: 1;
}

/* ====== 平台图包 ====== */
/* 选图弹窗：平铺网格 + 最前添加照片框 */
.pack-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 8px;
}
.pack-add {
  aspect-ratio: 1;
  border: 2px dashed #3b82f6;
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: #3b82f6;
  background: var(--bg);
}
.pack-add:active { opacity: 0.7; }
.pack-add-plus { font-size: 29px; line-height: 1; }
.pack-add-txt { font-size: 15px; margin-top: 4px; }
.pack-empty {
  grid-column: 1 / -1;
  text-align: center;
  color: var(--text-secondary);
  font-size: 16px;
  padding: 20px 0;
}
/* 绿色选中特效框（点击照片后出现）——纯外描边无水晕，避免低端机重绘卡顿，点击即时生效 */
.image-select-item.selected {
  border-color: #10b981;
  box-shadow: 0 0 0 2px #10b981;
  z-index: 1;
}
.image-select-item .check {
  top: 4px;
  right: 4px;
  width: 18px;
  height: 18px;
  background: #10b981;
  color: #fff;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  font-weight: 700;
  opacity: 0;
  transition: none; /* 去掉 0.2s 淡入过渡，点击立即显示 */
}
.image-select-item.selected .check { opacity: 1; }
/* 选项区：平台 + 水印复选框 */
.pack-options {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.pack-opt-title {
  font-size: 16px;
  font-weight: 600;
  margin-bottom: 8px;
}
.pack-opt-title:not(:first-child) { margin-top: 12px; }
/* 「选择平台」后面的单价（用户 2026-10-01 要求：这里也显示积分定价）。
   比标题小一号、不加粗、灰色，跟主页那四个价签是同一套视觉。文字由 renderPackPlats() 填。 */
.pack-opt-price { font-size: 15px; font-weight: 400; color: var(--text-secondary); margin-left: 4px; }
.pack-opt-hint {
  font-size: 15px;
  color: var(--text-secondary);
  line-height: 1.5;
  margin: -4px 0 8px;
}
.pack-plats {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.pack-check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 16px;
  padding: 6px 10px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  user-select: none;
}
.pack-check input { accent-color: #10b981; width: 15px; height: 15px; }
.pack-check:has(input:checked) {
  border-color: #10b981;
  color: #10b981;
  background: rgba(16,185,129,0.06);
}
/* 结果：按平台分组 */
.pack-result-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 21px;
  font-weight: 600;
  margin: 14px 0 8px;
  padding-left: 8px;
  border-left: 3px solid #10b981;
}
.pack-result-title:first-child { margin-top: 0; }
.pack-result-name { flex: 0 0 auto; }
.pack-result-count { flex: 1; font-size: 16px; font-weight: 400; color: var(--text-secondary); }
.pack-result-save { flex: 0 0 auto; padding: 4px 14px; font-size: 16px; }
.pack-result-row {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 6px;
}
.pack-result-row .result-item {
  flex: 0 0 auto;
  width: 96px;
}
.pack-result-row .result-item img { cursor: pointer; }
/* 生成结果独立页面：固定全屏、纯背景、无遮罩（非悬浮） */
#packResultPage {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  z-index: 1000;
  background: var(--bg-card);
}
#packResultPage .fullpage { height: 100%; }

/* ====== 图包大图查看器（点击缩略图看原图，仅当前平台内切换） ====== */
#packViewer {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  z-index: 2000;
  background: rgba(0, 0, 0, 0.93);
  display: flex;
  flex-direction: column;
}
.pack-viewer-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  color: #fff;
  font-size: 19px;
  flex-shrink: 0;
}
.pack-viewer-close {
  background: none;
  border: none;
  color: #fff;
  font-size: 25px;
  cursor: pointer;
  padding: 4px 10px;
}
.pack-viewer-img-wrap {
  flex: 1;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  padding: 0 12px;
}
.pack-viewer-img-wrap img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 4px;
}
.pack-viewer-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 64px;
  background: rgba(255, 255, 255, 0.15);
  border: none;
  color: #fff;
  font-size: 35px;
  border-radius: 8px;
  cursor: pointer;
  z-index: 2;
}
.pack-viewer-prev { left: 6px; }
.pack-viewer-next { right: 6px; }

/* 生成完成后的"查看"按钮：位于"选择照片"左侧，组内 gap 控制间距 */
.btn-view-pack {
  margin: 0;
}
/* 图包模块右侧按钮组：两个按钮靠右，间距与车辆展示按钮组(.btn-group-sm)一致 */
.pack-btn-group {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
/* 图包模块：右侧的「选择照片」/生成后的「查看·开始生成」靠右对齐。
   ⚠️ 这条是老注释的残留描述（原话是「按钮下移，底部与…行底部齐平」）——那是**并排时代**的
   口径：靠负边距把按钮上下挪、去跟左侧某行文字对齐。全项目那种负边距写法 2026-10-01 已清干净
   （车辆展示那条 `margin-top:-33px` 是最后一处）。这里没有负边距，就是单纯的 flex 靠右，
   别再照老注释去给它补"对齐"代码。 */
.module-item[data-module="image"] .module-btn,
.module-item[data-module="image"] .pack-btn-group {
  align-self: flex-end;
}

/* 车辆展示的布局规则在文件下方「车辆展示模块：纵向布局」那一段（**只此一处**）——
   别在这儿再写一份：同特异度的规则谁在后面谁赢，两处并存时下面那份会静默盖掉这里的。 */

/* ====== 精修结果（水印） ====== */
.result-grid {
  flex: 1;
  overflow-y: auto;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
  padding-right: 4px;
}
.result-item {
  position: relative;
  border-radius: 8px;
  overflow: hidden;
  background: #000;
}
.result-item img {
  width: 100%;
  display: block;
}
.watermark-wrap {
  position: relative;
  overflow: hidden;
  border-radius: 8px;
}
.watermark-wrap img {
  width: 100%;
  display: block;
}
.wm-text {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: rgba(255,255,255,0.5);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 2px;
  text-shadow: 0 0 6px rgba(0,0,0,0.6);
  background: repeating-linear-gradient(
    45deg,
    transparent 0 60px,
    rgba(0,0,0,0.12) 60px 120px
  );
  pointer-events: none;
  z-index: 2;
}

/* ====== 各平台文案：卡片内平台按键 ====== */
.module-item.module-copy {
  flex-direction: column;
  align-items: stretch;
}
.module-item.module-copy .module-name {
  display: flex;
  align-items: center;
}
.module-item.module-copy .module-tip {
  font-size: 15px;
  color: var(--text-secondary);
  margin-top: 4px;
}
.module-item.module-copy .module-btn {
  align-self: flex-end;
  margin-top: 10px;
}
.platform-mini {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 10px;
}
.pf-item {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 4px 9px;
  font-size: 14px;
  border: 1px solid var(--border);
  border-radius: 14px;
  cursor: pointer;
  color: var(--text-secondary);
  background: var(--bg-card);
  transition: all 0.2s;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.pf-item:active { opacity: 0.7; }
.pf-item.selected {
  border-color: #10b981;
  color: #10b981;
  background: rgba(16,185,129,0.08);
}
.pf-item .pf-check {
  font-size: 13px;
  opacity: 0;
  transition: opacity 0.2s;
}
.pf-item.selected .pf-check { opacity: 1; }

/* ====== 各平台文案：结果全屏页面 ====== */
.fullpage {
  width: 100%;
  height: 100%;
  max-width: 100%;
  margin: 0 auto;
  background: var(--bg-card);
  display: flex;
  flex-direction: column;
}
.fullpage-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-card);
  flex-shrink: 0;
}
.fullpage-title {
  font-size: 20px;
  font-weight: 600;
}
.fullpage-close {
  width: 30px;
  height: 30px;
  border: none;
  background: var(--bg);
  border-radius: 50%;
  font-size: 19px;
  cursor: pointer;
  color: var(--text-secondary);
}
.fullpage-close:active { opacity: 0.7; }
.fullpage-tip {
  font-size: 15px;
  color: var(--text-secondary);
  padding: 8px 16px;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
/* 责任声明（2026-09-17）：文案 / 讲解视频「已生成」页顶端常驻。
   ⚠️ 它是**配套的免责说明、不是装饰**，别删也别改成可关闭的 ——
   用户拍板「车况描述不再由系统过滤（原版原漆、无事故这些一律放开），真实性由车商自负」，
   这句提示正是那个决定的另一半：既然系统不判断真假，就必须把"判断责任在谁"讲明白。
   橙色系是故意的：比 .fullpage-tip 的灰底显眼（要让人看见），但不像红色报错那样吓人（它不是错误）。 */
.risk-note {
  font-size: 15px;
  line-height: 1.6;
  color: #8a5a00;
  background: #fff8e6;
  border-bottom: 1px solid #f0d9a0;
  padding: 9px 16px;
  flex-shrink: 0;
}
.fullpage-body {
  flex: 1;
  overflow-y: auto;
  padding: 12px 16px;
}
.fullpage-footer {
  padding: 10px 16px;
  border-top: 1px solid var(--border);
  background: var(--bg-card);
  display: flex;
  gap: 8px;
  flex-shrink: 0;
}

/* 讲解视频「已生成」页的发布入口（2026-09-18 用户拍板）：**紧跟在视频下面**。
   ⚠️ 它在 .fullpage-body **内部**（那个容器是 flex:1，放外面会被顶到屏幕最底下、离视频一大截空白），
      所以这里不要左右 padding —— 外层已经给了 12px 16px，再加会双重缩进。
   ⚠️ 交互是**两步**（用户要求）：点平台名 → 展开那一家的网址；**再点网址才真跳转**。
      网址默认全隐藏，一次只展开一个（点第二个收起第一个，点同一个则收起）。
   ⚠️ 我们只给**入口**，登录/选文件/写标题/发布都在平台上由车商自己完成，拿不到回执 ——
      所以这里只写「先保存视频」，**绝不能**出现"已发布"这类状态字眼。 */
.publish-entry { margin-top: 14px; }
.publish-tip {
  font-size: 15px;
  color: var(--text-secondary);
  margin-bottom: 8px;
}
.publish-links { display: flex; gap: 8px; }
.publish-link {
  flex: 1;
  text-align: center;
  font-size: 16px;
  font-family: inherit;      /* 它是 <button>，不写会掉回系统默认字体 */
  padding: 7px 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  cursor: pointer;
}
.publish-link:active { opacity: .7; }
/* 展开中的那个平台：描边和文字变主题色，让人一眼看出「网址是哪一家的」 */
.publish-link.active {
  border-color: var(--primary);
  background: #eef1fe;
  color: var(--primary);
  font-weight: 600;
}
/* 网址行：默认藏着，点平台名才展开一个。⚠️ 网址很长，word-break 必须留，否则会撑破容器 */
.publish-url {
  display: none;
  margin-top: 8px;
  padding: 8px 10px;
  font-size: 15px;
  line-height: 1.5;
  color: var(--primary);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  word-break: break-all;
}
.publish-url.show { display: block; }

/* ===== 手机 / 平板上的发布入口（2026-09-28 用户拍板「手机、电脑分开显示」）=====
   来龙去脉：车商在手机上点那三个网址，得到的是平台自己的「请在电脑端访问」页 ——
   因为抖音/快手/小红书的创作者中心**都是 PC 端网页**，我们既改不了、也没有手机版地址可换。
   所以手机上干脆**一个网址都不摆**，只留一句指路 App 的提示。
   ⚠️ 判定走 <html> 上的 .ua-mobile（app.js 的 isMobileDevice 打的），**不许改成 @media**
      —— 本项目全文 0 条断点，而且按宽度判会把「电脑上把窗口拖窄」的车商误判成手机、把网址藏掉。
   ⚠️⚠️ 下面两条**必须**写成 .ua-mobile .publish-entry .xxx 的复合形式：
      上面 `.publish-url.show` 是 (0,2,0)，只写 `.ua-mobile .publish-url` 同样是 (0,2,0)，
      同权重下就看谁写在后面 —— 那是"改了没效果也不报错"的经典坑。复合形式是 (0,3,0)，稳赢。
   ⚠️ 平板跟手机同一侧：iPad 打开创作者中心同样是那页「不适配」，跟屏幕大小无关。 */
.publish-tip-mobile { display: none; }
.ua-mobile .publish-entry .publish-tip { display: none; }
.ua-mobile .publish-entry .publish-tip-mobile {
  display: block;
  font-size: 15px;
  color: var(--text-secondary);
  margin-bottom: 8px;
}
.ua-mobile .publish-entry .publish-links,
.ua-mobile .publish-entry .publish-url { display: none; }

/* 每段文案（页面式段落）：平台名+复制 → 文案正文 */
.copy-result-list {
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.copy-item {
  /* 段落式，无卡片边框 */
}
.copy-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}
.copy-platform {
  font-size: 19px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 7px;
}
.copy-platform svg { display: block; flex-shrink: 0; }
.btn-copy-one {
  border: none;
  background: var(--primary);
  color: #fff;
  font-size: 16px;
  padding: 6px 16px;
  border-radius: 16px;
  cursor: pointer;
}
.btn-copy-one:active { opacity: 0.85; }
.copy-text {
  width: 100%;
  border: none;
  background: var(--bg);
  border-radius: 10px;
  padding: 12px 14px;
  font-size: 18px;
  line-height: 1.7;
  resize: vertical;
  outline: none;
  min-height: 90px;
  color: var(--text);
  font-family: inherit;
}
.copy-text:focus { box-shadow: inset 0 0 0 1.5px var(--primary); }

/* ====== 推荐记录 / 提现 ====== */
.refer-modal-box { max-height: 80vh; display: flex; flex-direction: column; text-align: left; }
.refer-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.refer-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 10px;
  background: var(--bg);
  border-radius: 8px;
  font-size: 16px;
}
.refer-item .r-reward { color: #10b981; font-weight: 600; }
.refer-item .r-status {
  font-size: 14px;
  padding: 2px 8px;
  border-radius: 10px;
}
.refer-item .r-status.pending { background: #eab30822; color: #eab308; }
.refer-item .r-status.done { background: #10b98122; color: #10b981; }
.refer-empty { font-size: 16px; color: var(--text-secondary); padding: 8px 0; text-align: center; }
.wd-type.active {
  border-color: var(--primary);
  color: var(--primary);
  background: rgba(74,108,247,0.08);
}

.auth-box { text-align: left; }
.auth-tab {
  flex: 1; padding: 10px; border: none; background: none;
  font-size: 18px; font-weight: 500; cursor: pointer; color: var(--text-secondary);
  border-bottom: 2px solid transparent;
}
.auth-tab.active { color: var(--primary); border-bottom-color: var(--primary); }
.auth-form { }
.auth-error { font-size: 16px; color: var(--danger); margin-top: 6px; min-height: 20px; }
/* ====== 推荐码的「框内」反馈（注册页 #regCode / 账户页 #accRefCode 共用）======
   车商 2026-09-26 拍板的形态：
     有效 → **框里的字变绿**，**不出任何文字**（原来框下的绿字「推荐码有效」已删）
     错误 → **框里的字变红** + 红字**后面**、**仍在框内**跟一句「推荐码错误」
   ⚠️ 所以框下面**没有**提示行了（原 `#regCodeMsg` 已删）。好处是间距回到和别的字段
   一样的 10px（改前为了给提示行占位是 30px，车商提过一次「间距缩小」），
   且提示出现/消失时下面**永远不会跳** —— 因为它压根不占下面的位置。
   ⚠️ `.code-inmsg` 的 `left` 是 **app.js 按已输入文字的像素实宽算出来再写进来的**
   （提示要贴在那串码后面），不是靠 CSS 定位。所以别删 `.code-wrap` 这层包裹：
   它是绝对定位的基准。 */
.code-wrap { position: relative; overflow: hidden; }
.code-inmsg {
  position: absolute; top: 0; bottom: 0; left: 0;
  display: none; align-items: center;
  font-size: 18px;              /* ⚠️ 必须和 .form-input 的 font-size 一致，否则和前面的码对不齐 */
  color: var(--danger);
  white-space: nowrap;
  pointer-events: none;         /* ⚠️ 必须：否则这一段会吃掉点击，输入框点不进去 */
}
/* 有文字才显示。空的时候连盒子都不存在，所以不会影响任何布局。 */
.code-inmsg:not(:empty) { display: flex; }
/* 推荐码填错：**框变红 + 框里的字也变红**（车商 2026-09-26 原话「在推荐码框里提示推荐码错误，
   字体也是红色的」）。
   ⚠️ 不给 :focus 也留个红边的话，用户一点进框想改，红边会消失 —— 那正是他最需要看到提示的时候。
   所以下面这条把 :focus 的红边也钉住了，别删。 */
.form-input.code-bad, .form-input.code-bad:focus { border-color: var(--danger); color: var(--danger); }
/* 校验通过：**绿框 + 框里的字也变绿**，且不出现任何文字。
   车商 2026-09-26 原话：「推荐码有效时，XF8588 字体是绿色」（所以这里的 color 不能省）。 */
.form-input.code-ok, .form-input.code-ok:focus { border-color: var(--success); color: var(--success); }
.auth-toggle { margin: 6px 0 0; font-size: 16px; color: var(--primary); cursor: pointer; text-align: center; }
.auth-toggle:hover { text-decoration: underline; }
.toast {
  position: fixed;
  top: 20px;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(0,0,0,0.8);
  color: #fff;
  padding: 10px 20px;
  border-radius: 8px;
  font-size: 17px;
  z-index: 3000;
  animation: fadeInToast 0.25s ease;  /* 只淡入，显示时长由 JS 控制，避免固定时长把提示提前淡出 */
}
@keyframes fadeInToast {
  from { opacity: 0; transform: translateX(-50%) translateY(-8px); }
  to { opacity: 1; transform: translateX(-50%) translateY(0); }
}

/* ====== 个人中心独立页面（弹窗改为页面） ====== */
.profile-page {
  position: fixed;
  top: 0; left: 0; right: 0; bottom: 0;
  z-index: 1000;
  background: var(--bg);
  max-width: 100%;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
}
/* ⚠️ `position: sticky` 是**必须**的（车商 2026-09-23：「进入个人中心后无论打开哪个功能或哪个页面，
   向下滑动时顶部的导航栏都是固定的悬浮的，这样方便返回」）。
   滚动容器是外层 `.profile-page` 自己（它 `overflow-y:auto`、高度撑满屏幕），
   所以 sticky 的参照系就是它，`top:0` 一贴到底 —— **9 个子页共用这一个类，改一处全改**。
   别改成 `fixed`：fixed 会脱离 flex 流、下面内容要自己补 padding，
   而且 `.profile-back`（position:absolute）的定位基准也会跟着变。 */
.profile-header {
  display: flex;
  align-items: center;
  justify-content: center;
  position: sticky;
  top: 0;
  z-index: 5;          /* 必须压住下面的内容，否则滚动时文字会从标题栏底下透出来 */
  padding: 14px 16px;
  background: var(--bg-card);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
/* 返回键：看到的还是那个 ‹，但**点击区做到 44×44**（9 个子页共用这一个类，一处改全改）。
   ⚠️ 尺寸必须靠 width/height + flex 居中，**不能靠 padding 撑** —— padding 撑出来的大小跟着
      字形宽度跑，"‹" 这种窄标点永远凑不满 44（原来是 29×34，宽度差一半）。
   ⚠️ 用 left:0 + top:50% + translateY(-50%) 把**字形中心钉在原处**：原来 29×34 的盒子
      靠 flex 静态定位正好居中在标题栏里，字形中心在 x=22.5、垂直居中；现在换成
      left:0 宽 44 → 中心 x=22，只差 0.5px，肉眼看不出来。改了尺寸却不动 left 的话会右飘 7px。 */
.profile-back {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  /* 2026-10-01 用户要求这个返回箭头「多改大一点」：全站统一 +3 后是 29，这个单独拨到 36。
     ⚠️ 盒子还是 44×44（左边缘靠 left:0 钉住，见上面那段注释），字形比盒子小一点放得下，别顺手把盒子也改大。 */
  font-size: 36px;
  line-height: 1;
  color: var(--text);
  cursor: pointer;
  padding: 0;
}
.profile-title { font-size: 20px; font-weight: 600; }
.profile-body { padding: 14px 12px 24px; }
/* 用户概览卡 */
.profile-overview {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--bg-card);
  border-radius: var(--radius);
  padding: 16px;
  box-shadow: var(--shadow);
  margin-bottom: 14px;
}
.profile-avatar {
  width: 52px; height: 52px;
  border-radius: 50%;
  background: rgba(74,108,247,0.12);
  display: flex; align-items: center; justify-content: center;
  font-size: 29px;
  flex-shrink: 0;
}
.profile-mid { flex: 1; min-width: 0; }
.profile-phone { font-size: 19px; font-weight: 600; }
/* 车商名称：2026-09-21 用户要求「大一点点」（原 12px）。三条截断属性别删 ——
   这行紧挨着右边的积分键，名称一长就会顶过去，靠 ellipsis 收尾。 */
.profile-shop { font-size: 16px; color: var(--text-secondary); margin-top: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 右侧一栏：2026-09-21 用户要求「积分和推荐码上下居中对齐」→ 两行**各自水平居中**再叠起来，
   （原来是 text-align:right，右边缘对齐；积分是胶囊有内边距、推荐码是纯文字，右对齐时两行看着是歪的）。
   ⚠️ 要改回右对齐得连 .profile-pts-btn 的 justify-content 一起改 —— 只改这里，胶囊仍贴着右边。 */
.profile-side { text-align: center; flex-shrink: 0; }
/* 积分：做成一颗**灰白按键**（2026-09-21 用户第二版要求：「把积分添加个灰白色的按键，字体大一点点」）。
   第一版是纯蓝字 + 后面跟个 ⓘ 角标提示"能点"，用户把角标去掉了 —— 现在靠按键外观本身表达可点性。
   底色调得比卡片白底深一档（--bg 那档太淡、贴着白卡片看不出来），胶囊圆角。
   ⚠️ 底色必须是**正灰**（三通道相等）。第一版用的是 #eef0f4，蓝通道比红高 6 —— 配上蓝色的
      积分文字，截图拿识图模型一看就把这颗键描述成"浅蓝紫色的按钮"，跟"灰白色"对不上。
      要改也只改亮度（#eee ↔ #e8e8e8），**别往底色里掺蓝**。 */
.profile-side-vip {
  display: inline-block;
  font-size: 17px; font-weight: 700; color: var(--primary);
  background: #eeeeee; border-radius: 999px; padding: 4px 12px; white-space: nowrap;
}
/* 推荐码：2026-09-21 用户先要「字体大一点、颜色黑一点」（原 11px / --text-secondary 太淡，
   车商要念给朋友听时看不清），同一天又追加「再小一点点」→ 定在 13px。
   ⚠️ 「再小一点点」说的是**字号**，颜色保持 --text 近黑别退回灰色。 */
.profile-side-code { font-size: 16px; color: var(--text); margin-top: 5px; }

/* ====== 个人中心概览卡：积分可点 + 积分到期弹层（2026-09-21 用户需求） ====== */
/* 外层 <button> 只管"能点"和键盘可达，外观全交给里面的 .profile-side-vip（那颗灰白按键）——
   所以这里把浏览器给的默认外观复位掉：不复位的话会在按键外面再套一圈灰底+边框，像两层壳。
   justify-content: flex-end —— .profile-side 是靠右的一栏，按钮要跟着右对齐。 */
.profile-pts-btn {
  display: flex; align-items: center; justify-content: center; width: 100%;
  background: none; border: none; padding: 0; cursor: pointer;
  font-family: inherit;
}
.profile-pts-btn:active { opacity: .6; }
.pts-expire-box { position: relative; }
/* 右上角 × ：用户指定的关闭方式。28px 见方只是手能点准的**下限**，手机上仍偏小 → 放到 40×40。
   ⚠️ top/right 必须同步从 (6,8) 改成 (0,2)：6+14=20、8+14=22 是原来字形中心到上/右的距离，
      减掉新的半个盒子(20) 就是新值。**只放大盒子不动这两个值，× 会整体往右下飘 6px。**
   ⚠️ 盒子变大后会探进标题行右上角，但标题「积分信息」靠左、横向够不着（实测间距 >180px），不会压字。 */
.pts-expire-close {
  position: absolute; top: 0; right: 2px;
  width: 40px; height: 40px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  background: none; border: none; padding: 0; cursor: pointer;
  font-size: 23px; color: var(--text-secondary);
}
.pts-expire-close:active { opacity: .6; }
/* 摘要行（2026-09-21 用户要求：整行字体大一点、**数字比汉字再大一点**、并且颜色区分）。
   数字用主题蓝，和上面那颗积分胶囊同色 —— 一眼看到的是「还剩多少」，汉字只是标签。
   ⚠️ 数字必须包在 .pts-sum-num 里（app.js 用 innerHTML 拼），别再退回纯 textContent ——
      纯文本没法给数字单独上色上字号。 */
.pts-expire-sum { font-size: 17px; color: var(--text-secondary); margin: 0 0 8px; }
.pts-sum-num { font-size: 20px; font-weight: 700; color: var(--primary); margin-left: 2px; }
/* 高度上限 + 可滚动：积分批次独立到期，充过几档、几档又是不同有效期时会有五六行，
   不封顶的话弹层会长到屏幕外面去（× 也就跟着看不见了）。 */
.pts-expire-list { max-height: 46vh; overflow-y: auto; }
/* 规则说明：2026-09-21 用户要求从弹层底部**挪到标题下面**（所以外边距是下 10px，不是上 10px）。
   它排在「当前可用积分」之前 —— 先说清口径，再看数字。 */
.pts-expire-tip { font-size: 15px; color: var(--text-secondary); line-height: 1.6; margin: 0 0 10px; }

/* ====== 积分到期提醒弹窗（2026-09-21 用户需求，当天来回改了三版）======
   ⚠️⚠️ 形态在这里被用户来回拧了三次，别再自作主张"优化"：
     ① 第一版：**黑遮罩 + 居中卡片**（`.modal-overlay` 原样）→ 用户否：「我想要的不是这样的，
        就像别的网站有什么重要通知那样的」（嫌黑遮罩太重、像报错弹窗）。
     ② 第二版：照他挑的做**无遮罩、页面还能点**（pointer-events 穿透）→ 用户又改：
        「页面变淡淡的灰，不点击确定，桌面不能操作」。
     ③ 现在这版 = ②的卡片 + ①的"挡住操作"，只把黑的换成**淡灰**。
   ⚠️ 所以结论是：**要压灰、要挡住操作，但不要黑**。三样缺一不可，别只改其中一样。
   ⚠️ 挡住操作**不是**靠把 pointer-events 设回去就行——它本来就是 `.modal-overlay` 的默认行为，
      现在是**默认态**，不用额外写。②那版加的 `pointer-events:none/auto` 已经删掉了，
      加回来会让页面又能点（用户这次明确要"桌面不能操作"）。
   ⚠️ 光有遮罩**管不住滚动**：手指在遮罩上划，滚动链会把动作交给背景 body，桌面照样跟着滚
      （用户 2026-09-22 明确要求「后面桌面不能滑动」）。那半件事不在这里，在 app.js 的
      checkPtsNotice() 里锁 body overflow —— 别在 CSS 里找它，也别以为写了遮罩就完事。
   ⚠️ 1900 **低于** .modal-overlay 的 2000，这是有意的：账户被冻时冻结弹窗要压在上面
      （冻号是更紧急的告知），车商点掉冻结那个，下面这个自然露出来。两个弹窗因此互不感知，
      也就不存在"关了一个忘了开另一个"的漏法。
   ⚠️ 没有 × 、也没有点外面关闭：用户明确要求「车商必须点击确定按键」。
      别顺手照抄 ptsExpireModal 的写法给它加关闭方式。 */
#ptsNoticeModal {
  z-index: 1900;
  /* 「淡淡的灰」：中性灰 + 低透明度。用纯黑(0,0,0)低透明度会发闷发脏，"洗掉一层"的灰更贴用户说的。
     ⚠️ 必须**三通道相等**的纯灰。第一版写的 gray-500 `#6b7280` 蓝通道比红高 21 ——
        那种"灰"在截图里会被读成偏蓝（同款教训：积分按键的 `#eef0f4` 被识图模型读成"浅蓝紫"）。
        验证脚本里钉了「三通道最大差 ≤ 20」。 */
  /* 2026-09-22 用户要「后面的桌面再暗一点点」：只提 alpha，**不动 RGB** ——
     灰的色相不变，只是压得更实。⚠️ 不能顶到 0.40：验证脚本钉了「白底合成亮度 ≥ 200」，
     0.40 正好压在 200.2，护栏贴着实测线跑（这是本项目反复踩过的坑）。
     0.32 合成亮度 211，离护栏还有余量。 */
  background: rgba(118, 118, 118, 0.32);
}

/* 标题区（2026-09-22 用户要求：文案改黄 + 前面加提示图标，原话「登录后直接显示，
   看不出来是提示作用」）。
   ⚠️ 黄色在白底上先天对比度低：纯黄 #FFFF00 只有 1.07:1（≈看不见），
      #EAB308 也只有 2.1:1。这里取的是**在白底上还看得清的最黄的那个值**（约 3.5:1）。
      要更亮可以改，但会更看不清 —— 这条路在积分档位标签上走过一次，
      结论是「黄色做白底文字无解」，最后靠换底色块才解决（见 .points-tr 那段注释）。 */
.pts-notice-ico { font-size: 20px; line-height: 1; flex-shrink: 0; }
.pts-notice-title { font-size: 20px; font-weight: 700; color: #ca8a04; }
/* 卡片：2026-09-22 用户要求「提醒卡设置淡黄色」—— 整张卡刷成淡黄。
   这比只把标题染黄更贴"提示"的视觉习惯：黄底 = 提示条/便利贴，扫一眼就知道是要读的东西。
   ⚠️⚠️ 选择器**必须**写成 `.help-modal-box.pts-notice-box` 这个复合形式，不能只写 `.pts-notice-box`：
      单独的 `.pts-notice-box` 优先级 (0,1,0) 和 `.help-modal-box` (0,1,0) 打平，而后者在第 1477 行、
      **排在这条后面**，会把这个淡黄底原样覆盖回白色 —— 改了没效果，还不报错，最难查。
      复合选择器 (0,2,0) 直接赢，跟顺序无关。
   ⚠️ 取 yellow-100 `#fef9c3`：更淡的（amber-50 `#fffbeb`）在手机上几乎就是白，看不出"黄"；
      再深一档就发橙了。标题那个黄字压在这个底上仍有约 2.7:1（白底上是 2.9:1，几乎没损失），
      所以标题色**不用**跟着加深 —— 别看到"黄字压黄底"就条件反射去改它。
   投影：压灰之后其实不那么必要了，但留着能让卡片和灰底再分开一点。 */
.help-modal-box.pts-notice-box {
  position: relative;
  background: #fef9c3;
  box-shadow: 0 12px 32px rgba(0,0,0,.18);
}
.pts-notice-tip { font-size: 15.5px; color: var(--text-secondary); line-height: 1.6; margin: 0 0 10px; }
/* 封顶可滚：批次多、又都挤在同一档时（比如同一天充的几笔）会排出好几行。
   ⚠️ 下面的 margin-bottom 是**补**出来的：列表和「确定」之间原本靠合计行的 margin 撑开，
      2026-09-22 合计行被用户去掉后，按钮就贴着列表了，所以间距挪到这里。 */
.pts-notice-list { max-height: 42vh; overflow-y: auto; margin-bottom: 14px; }
/* 每批次一个底块，样式与积分信息弹层的 .points-tr 同源观感，但**不带档位色**：
   这框里列出来的每一行本身就是"要到期了"，全染色等于没有重点。
   底色 2026-09-22 从浅灰 #f4f4f5 改成**白**：卡片变淡黄之后，灰块压在黄底上会发脏
   （灰黄一混就是脏绿味），白块浮在黄底上才干净、有层次。
   ⚠️ 别看到"白"就以为是漏设了 —— 它和卡片的黄底是一组，动一个就得看另一个。 */
.pts-notice-row {
  display: flex; justify-content: space-between; align-items: center;
  padding: 10px 12px; background: #fff; border-radius: 10px;
}
.pts-notice-row + .pts-notice-row { margin-top: 8px; }
.pts-notice-date { display: block; font-size: 17px; color: var(--text); }
.pts-notice-date em { display: block; font-style: normal; font-size: 14px; color: var(--text-secondary); margin-top: 2px; }
.pts-notice-num { font-size: 17px; font-weight: 700; color: var(--text); }
/* ⚠️ 这里原有 `.pts-notice-total`（深红合计行「共 N 积分将清零」），2026-09-22 用户要求**去掉**，
   样式和 DOM（index.html 的 #ptsNoticeTotal）一起删干净了。别只恢复样式不恢复结构、也别反过来。 */

/* 功能区列表 */
.profile-menu { background: var(--bg-card); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; }
.profile-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 14px;
  background: none;
  border: none;
  border-bottom: 1px solid var(--border);
  text-align: left;
  cursor: pointer;
  font-size: 18px;
  color: var(--text);
}
.profile-item:last-child { border-bottom: none; }
.profile-item:active { background: rgba(74,108,247,0.05); }
.pi-icon { font-size: 23px; flex-shrink: 0; }
.pi-main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.pi-main em { font-style: normal; font-size: 15px; color: var(--text-secondary); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pi-arrow { font-size: 23px; color: #c0c4cc; flex-shrink: 0; }
/* 菜单红点（待处理关联申请数） */
.pi-badge {
  min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px;
  background: #ef4444; color: #fff; font-size: 14px; font-weight: 600;
  line-height: 18px; text-align: center; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
/* 「联系后台」有新回复的红点：只显示一个点、不带数字。
   与上面带数字的 .pi-badge 是两种东西——这里不关心有几条，只回答「有没有新消息」，
   所以做成固定尺寸的圆点，不用胶囊。 */
.pi-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: #ef4444; flex-shrink: 0;
  display: inline-block;
}
/* 退出账号（红色，独立） */
.profile-logout {
  width: 100%;
  margin-top: 18px;
  padding: 13px;
  background: var(--bg-card);
  border: 1px solid var(--danger);
  color: var(--danger);
  border-radius: var(--radius);
  font-size: 18px;
  font-weight: 600;
  cursor: pointer;
}
.profile-logout:active { background: rgba(239,68,68,0.06); }
/* 功能区占位页 */
.profile-sub-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 40px 20px;
  text-align: center;
}
.profile-sub-icon { font-size: 51px; }
.profile-sub-text { font-size: 18px; color: var(--text); }
.profile-sub-tip { font-size: 15px; color: var(--text-secondary); }
/* ====== 系统说明页（个人中心 → 系统说明）======
   纯静态长文，阅读节奏照搬「车辆环绕视频拍摄规范」弹窗（.guide-*）：大标题 + 小标题 + 列表。
   ⚠️ 正文取 13px 是下限考量：手机端体检把「字号 <12px」列为一类问题，
      13px 与 .records-tip / .refer-tip 一致，别往下压到 12。 */
.sys-h { font-size: 18px; font-weight: 700; color: var(--text); margin: 20px 0 8px; }
.sys-h:first-child { margin-top: 0; }   /* 抵消 .profile-body 的 padding-top，首个标题不悬空 */
.sys-sub { font-size: 17px; font-weight: 600; color: var(--text); margin: 16px 0 6px; }
.sys-list { margin: 0; padding-left: 20px; }
.sys-list > li { font-size: 16px; line-height: 1.75; color: var(--text-secondary); margin-bottom: 6px; }
.sys-list > li:last-child { margin-bottom: 0; }
.sys-sublist { margin: 6px 0 0; padding-left: 18px; }
.sys-sublist > li { font-size: 16px; line-height: 1.7; color: var(--text-secondary); margin-bottom: 4px; }
.sys-sublist > li:last-child { margin-bottom: 0; }
.sys-fn { font-size: 16px; line-height: 1.75; color: var(--text-secondary); margin: 0 0 10px; }
.sys-fn:last-of-type { margin-bottom: 0; }
.sys-thanks { font-size: 16px; line-height: 1.8; color: var(--text-secondary); margin: 22px 0 0; padding-top: 16px; border-top: 1px solid var(--border); }
/* 功能名加粗拉层次。**不上主色蓝**：蓝色在本项目里是「可点击」的信号（按钮都是蓝底/蓝字），
   这里点不动，上蓝等于骗车商去点（用户 2026-09-24「跳转这个先不弄了」）。
   将来真做跳转时，再连同蓝色一起加。 */
#profileSysPage b { color: var(--text); font-weight: 600; }
/* 文案末尾「联系后台」—— 全页唯一可点的一处（用户 2026-09-24 点名要的）。
   ⚠️ 用 <button> 不用 <a>：没有 href 的 <a> 键盘不可达、也不该假装是链接
      （项目里平台名那几个也是 <button>，见 car-tool-publish-entry）。
   ⚠️ display:inline 是刻意的：它要长在句子中间跟着文字走，inline-block 会带自己的行盒、
      基线也可能对不齐。inline 元素的上下 padding **不影响行高**，所以下面那对 padding
      纯粹是在撑点击区（正文 13px + 上下各 9px ≈ 41px，够手机点），不会把这一段撑开。
   ⚠️ 主色蓝 = 本项目「可点」的信号，这里正好名实相符。下划线是给正文里的小字链接补辨识度，
      不想要的话删掉 text-decoration 那一行即可，其余不用动。 */
.sys-link {
  display: inline;
  background: none;
  border: 0;
  padding: 9px 2px;
  font: inherit;
  color: var(--primary);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.sys-link:active { opacity: .6; }
/* 账户信息功能区：只读账户 / 改密码 / 车商信息 */
/* 帮助说明小图标：黄色圆圈 + 白色问号，点开看该项功能说明 */
.help-dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #f59e0b;
  color: #fff;
  font-size: 14px;
  font-weight: 700;
  line-height: 1;
  flex-shrink: 0;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
}
.help-dot:active { background: #d97706; }
.help-dot-lg { width: 22px; height: 22px; font-size: 17px; }
.help-modal-box {
  background: #fff;
  border-radius: 16px;
  padding: 20px 22px;
  width: 82%;
  max-width: 340px;
  text-align: left;
}
.help-modal-head { display: flex; align-items: center; gap: 9px; margin-bottom: 10px; }
.account-card { background: var(--bg-card); border-radius: var(--radius); box-shadow: var(--shadow); padding: 0 14px; margin-bottom: 14px; }
.account-card-title { font-size: 16px; font-weight: 600; color: var(--text-secondary); padding: 14px 0 4px; }
.account-card-tip { font-size: 15px; color: var(--text-secondary); line-height: 1.5; margin: 0 0 8px; }
.account-row { display: flex; justify-content: space-between; align-items: center; padding: 13px 0; font-size: 17px; }
.account-row + .account-row { border-top: 1px solid var(--border); }
.a-label { color: var(--text-secondary); }
.a-value { color: var(--text); font-weight: 500; word-break: break-all; text-align: right; }
/* 「积分到期」这种要多行明细的行：.account-row 是单行两端对齐，装不下竖排列表
   （明细会被挤到标签右边、右对齐成一坨）。改成上下堆叠，明细占满整行宽度。 */
.account-row-block { flex-direction: column; align-items: stretch; gap: 6px; }
/* 账户信息页的紧凑版。⚠️ 原来首/末块各有 padding 归零（那时行没有底色，归零是为了贴合卡边）——
   现在每行都有灰白底色，归零会让第一块、最后一块比中间的扁一截，所以这两条**已删**。 */
.points-mini .points-tr { padding: 7px 10px; font-size: 16px; }
.points-mini .pt-left em { font-size: 14px; }
.account-card .form-input { margin-top: 6px; }
/* ====== 佣金提现功能区：概览 5 项 / 提现记录 / 提现弹窗 ====== */
.withdraw-summary { display: flex; background: var(--bg-card); border-radius: var(--radius); box-shadow: var(--shadow); margin-bottom: 12px; flex-wrap: wrap; }
.wd-sum-item { flex: 1 1 0; min-width: 20%; text-align: center; padding: 12px 2px; }
.wd-sum-item + .wd-sum-item { border-left: 1px solid var(--border); }
.wd-sum-num { display: block; font-size: 20px; font-weight: 700; color: var(--primary); white-space: nowrap; }
.wd-sum-label { font-size: 13px; color: var(--text-secondary); white-space: nowrap; }
.wd-sum-item.wd-avail .wd-sum-num { color: #10b981; }
.wd-record { background: var(--bg-card); border-radius: var(--radius); box-shadow: var(--shadow); padding: 12px 14px; margin-bottom: 10px; }
.wd-record-main { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.wd-record-amt { font-size: 18px; font-weight: 700; color: var(--text); }
.wd-record-time { font-size: 15px; color: var(--text-secondary); }
.wd-status { font-size: 14px; padding: 2px 8px; border-radius: 20px; margin-left: auto; }
.wd-status.done { color: #10b981; background: rgba(16,185,129,0.12); }
.wd-status.pending { color: #eab308; background: rgba(234,179,8,0.14); }
.wd-status.failed { color: #ef4444; background: rgba(239,68,68,0.12); }
.wd-fail-reason { margin-top: 8px; font-size: 15px; color: #ef4444; background: rgba(239,68,68,0.06); border-radius: 6px; padding: 6px 8px; }
/* 打款成功那句话：和失败原因同一形态、只换色（绿）。
   ⚠️ 色值跟 `.wd-status.done` 一致（#10b981）—— 卡片右边那块状态标就是这个绿，别改用 var(--success)。 */
.wd-done-msg { margin-top: 8px; font-size: 15px; color: #10b981; background: rgba(16,185,129,0.06); border-radius: 6px; padding: 6px 8px; }
/* 提现弹窗：金额 + 全部 / 微信支付宝双账户 */
.wd-amount-row { display: flex; gap: 8px; margin-bottom: 10px; }
.wd-amount-row .form-input { flex: 1; margin-bottom: 0; }
.btn-all { flex-shrink: 0; padding: 10px 16px; border: none; border-radius: var(--radius); background: var(--primary); color: #fff; font-size: 16px; cursor: pointer; }
.wd-acc-row { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
/* 一个账户 = 账号行 + 姓名行，两行成组；组与组之间留大一点的间距，
   「姓名」标签对齐上面那个账户，视觉上归属清楚，不会被误读成共用一个姓名。 */
.wd-acc-block { margin-bottom: 12px; }
.wd-acc-block .wd-acc-row:last-child { margin-bottom: 0; }
.wd-acc-tag { flex-shrink: 0; width: 52px; font-size: 16px; color: var(--text); font-weight: 600; }
.wd-acc-row .form-input { flex: 1; margin-bottom: 0; }
.wd-hint { font-size: 14px; color: var(--text-secondary); line-height: 1.6; margin-bottom: 4px; }
/* 二次确认里列出的收款账户行（可能一行、也可能两个账号两行）。
   ⚠️ 字号比摘要正文（#wdConfirmSummary 的 13px）**大一档**：这是要车商逐字核对账号和姓名的地方，
   和旁边那句风险提示不是一个用途，不能一个字号。 */
.wd-cacc { font-size: 18px; line-height: 1.9; display: inline-block; }
.wd-notice-title { font-size: 15px; font-weight: 600; color: var(--text-secondary); margin: 0 0 4px; }
.wd-warning { font-size: 15px; color: #ef4444; line-height: 1.7; background: rgba(239,68,68,0.06); border-radius: 8px; padding: 8px 10px; margin-bottom: 10px; }
.account-card .btn { margin-top: 12px; }
/* 推荐信息功能区：被推荐人列表（手机号脱敏）+ 备注 + 会员记录下拉 */
/* 顶端两个汇总框 = **可切换的页签按键**（2026-09-23 车商要求：点哪个，下面显示哪个列表）。
   ⚠️ 必须是 <button>（原来是 <div>）：车商要"点它"，div 在手机上不给点击反馈、读屏也读不出是按键。
   ⚠️ 未选中 = 白底 + 灰字数字，选中 = 浅蓝底 + 蓝边 + 数字变主色。**两层差异都要有**：
      只改数字颜色的话（20px 数字 vs 14px 标签），一眼看上去两个框几乎一样，等于没有选中态。
   ⚠️ 边框常驻（未选中也占位，只是透明）：不占位的话选中时整块会跳 1px。 */
.refer-summary { display: flex; gap: 8px; margin-bottom: 12px; }
.refer-sum-item { flex: 1; display: flex; align-items: baseline; justify-content: center; gap: 4px; font-family: inherit; background: var(--bg-card); border: 1.5px solid transparent; border-radius: 10px; box-shadow: var(--shadow); padding: 8px 6px; cursor: pointer; }
.refer-sum-item:active { opacity: .85; }
.refer-sum-item.active { background: rgba(74,108,247,0.10); border-color: var(--primary); }
/* ⚠️ 两个框里的数字**恒为同一个颜色**（先蓝后绿，2026-09-23 车商当天改的色）——
   别再加回 `.active` 才变色的那条：那样未选中的那个数字是灰的，车商要的是两个都亮着。
   「哪个页签选中」靠底色 + 边框表达（见上面 .refer-sum-item.active），不靠数字颜色。
   换色只改这一行；⚠️ 验证脚本里钉着具体色值（`BLUE` 常量），换色要同步改那一行。 */
.refer-sum-num { font-size: 23px; font-weight: 700; color: var(--success); line-height: 1.1; }
.refer-sum-label { font-size: 17px; color: #000; line-height: 1.1; }
.refer-sum-unit { font-size: 16px; color: #000; line-height: 1.1; }
.refer-tip { font-size: 15px; color: var(--text-secondary); margin: 0 0 10px; }
.refer-item { display: block; background: var(--bg-card); border-radius: var(--radius); box-shadow: var(--shadow); padding: 8px 14px; margin-bottom: 8px; }
.refer-item-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.refer-phone { font-size: 19px; font-weight: 700; color: var(--text); }
.refer-name { font-size: 19px; font-weight: 700; color: var(--text); }
/* 金额（两个列表的右上角）：margin-left:auto 顶到行尾，绿色与「提现记录」里的佣金色一致 */
.refer-item .refer-money { margin-left: auto; font-size: 20px; font-weight: 700; color: #10b981; white-space: nowrap; }
.refer-item .refer-money i { font-style: normal; font-size: 15px; font-weight: 400; margin-left: 2px; }
/* 从没充过值的推荐人：与其空着不如写「未充值」，否则那行看着像坏了 */
.refer-none { color: var(--text-secondary); font-weight: 400; }
/* 「已冲销」标签：好友那笔充值退了款，这笔佣金已被冲回。灰色而非红色 —— 不是错误，是既成事实 */
.refer-rev { font-size: 14px; color: #b45309; background: rgba(245,158,11,0.12); padding: 2px 8px; border-radius: 20px; }
.refer-remark-name { font-size: 15px; color: #b45309; background: rgba(245,158,11,0.12); padding: 1px 8px; border-radius: 20px; }
.refer-regtime { font-size: 15px; color: var(--text-secondary); }
.refer-item-meta { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 10px; }
.refer-actions { display: flex; align-items: center; gap: 8px; }
.refer-btn { font-size: 16px; border: 1px solid var(--border); background: #fff; color: var(--primary); border-radius: 7px; padding: 6px 14px; cursor: pointer; }
.refer-btn:active { background: rgba(74,108,247,0.08); }
.refer-records { margin-top: 10px; background: var(--bg); border-radius: 8px; padding: 8px 12px; }
.refer-rec-row { font-size: 16px; color: var(--text); padding: 4px 0; }
.refer-rec-row + .refer-rec-row { border-top: 1px dashed var(--border); }
.refer-rec-row.empty { color: var(--text-secondary); }
.refer-empty { font-size: 16px; color: var(--text-secondary); text-align: center; padding: 24px 0; }
/* ====== 生成记录：保留时长提示条 + 记录列表（名称/日期/4 小方框/×删除） ====== */
.records-tip { font-size: 16px; color: #b45309; line-height: 1.7; background: rgba(245,158,11,0.12); border-radius: 8px; padding: 8px 10px; margin-bottom: 12px; }
.records-list { display: flex; flex-direction: column; gap: 10px; }
/* cursor:pointer 是配「整卡可点」的（2026-09-23 起卡片任意位置都进预览页，见 app.js renderRecordRow）。
   卡内 .record-del / .record-name 自己也声明了 pointer，不冲突。 */
.record-item { position: relative; background: var(--bg-card); border-radius: var(--radius); box-shadow: var(--shadow); padding: 12px 14px; cursor: pointer; }
.record-del { position: absolute; top: 6px; right: 8px; width: 26px; height: 26px; border: none; background: transparent; color: var(--text-secondary); font-size: 21px; line-height: 1; cursor: pointer; border-radius: 6px; }
.record-del:hover { color: var(--danger, #ef4444); background: rgba(239,68,68,0.08); }
.record-name { font-size: 19px; font-weight: 700; color: var(--text); text-align: left; border: none; background: transparent; padding: 0; margin: 0 0 4px; cursor: pointer; line-height: 1.4; max-width: calc(100% - 34px); }
.record-name:active { color: var(--primary); }
/* 关联账号记录的绿标（记录条内标记；该记录只读，不能删除/生成/编辑） */
.record-tag-linked {
  display: inline-flex; align-items: center; font-size: 14px; color: #10b981;
  border: 1px solid rgba(16,185,129,0.45); background: rgba(16,185,129,0.08);
  border-radius: 6px; padding: 1px 6px; flex-shrink: 0; margin-right: 2px;
  vertical-align: 2px;
}
/* ⚠️ `flex-wrap` 是修「手机上 4 个功能名上下排、时间被挤成两行」的**唯一机制**，别删。
   没有它时 flex 默认 nowrap：窄屏放不下就把子项**压缩**，中文在各自盒内断行 ——
   那才是病根（不是"换行"本身）。
   为什么只在手机上出问题：卡片内容区 = min(视口,480) − 左右 padding，
   电脑约 428px > 内容 358px（放得下、不换行），手机 390px 只剩约 338px（放不下、被压断）。
   ⚠️ 不要改成 @media 断点：电脑端可用宽也只有 428px，断点会判错。 */
.record-prods { display: flex; gap: 6px 10px; flex-wrap: wrap; }
/* nowrap 让功能名**自己**不断行；换不换行交给父级的 flex-wrap 决定 */
.record-prod { display: inline-flex; align-items: center; gap: 4px; font-size: 15px; color: var(--text-secondary); white-space: nowrap; }
/* margin-left:auto 让它贴右；放不下时被 flex-wrap 推到第二行，自然右对齐 */
.record-meta { display: inline-flex; align-items: center; font-size: 15px; color: var(--text-secondary); margin-left: auto; white-space: nowrap; }
.record-prod .rp-box { width: 14px; height: 14px; border: 1.5px solid var(--text-secondary); border-radius: 3px; display: inline-flex; align-items: center; justify-content: center; font-size: 14px; line-height: 1; color: #fff; flex-shrink: 0; }
.record-prod.done { color: var(--text); }
.record-prod.done .rp-box { background: var(--primary); border-color: var(--primary); }
.record-prod.pending .rp-box { background: transparent; }
.records-empty { text-align: center; padding: 40px 10px; }
.records-empty p { font-size: 18px; color: var(--text-secondary); }
.records-empty .records-empty-sub { font-size: 15px; color: var(--text-secondary); opacity: .8; margin-top: 6px; line-height: 1.6; }
/* ====== 联系后台：留言对话（气泡式） ====== */
#profileContactPage { overflow: hidden; }
/* 对话区改成**后台聊天窗那一套**（车商 2026-09-23：「把下面的对话页面改和后台的对话页面一样，
   灰背景，绿色和白色对话框，再把下面输入框条和对话框连接起来，和后台一样」）。
   三件事：
     ① `.msg-box` 不再是一张"卡片"（去掉白底/圆角/描边/阴影），换成后台 `.chat-body` 的**灰底**；
     ② 灰色区域**通到屏幕两边**（负 margin 抵掉 .profile-body 的左右 12px 内边距），
        这样它和下面的输入框条同宽、直接相接 —— 就是后台那种"上面灰、下面白、一条分隔线"的形态；
     ③ `.contact-body` 原来的 padding-bottom:12px 也去掉，不然灰色区和输入框条之间会空出一条缝。
   ⚠️ 滚动容器仍然是 `.msg-box` 自己（app.js 里 `closest('.msg-box')` 找它设 scrollTop），
      只改外观、**别把这层壳换掉**。 */
.contact-body { flex: 1; display: flex; flex-direction: column; overflow: hidden; padding-bottom: 0; }
.msg-box {
  flex: 1; min-height: 0;
  display: flex; flex-direction: column;
  /* 和后台 .chat-body 同一个灰，别的灰都会让两边看起来不是一套 */
  background: #f0f2f5;
  margin: 0 -12px;          /* 抵掉 .profile-body 的左右内边距 → 通到屏幕两边 */
  padding: 14px;            /* 和后台 .chat-body 一样 */
  overflow-y: auto;
  overflow-x: hidden;
}
.wx-entry {
  display: flex; align-items: center; gap: 12px;
  width: 100%; background: var(--bg-card);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 14px 16px; margin-bottom: 14px; cursor: pointer;
  box-shadow: var(--shadow); text-align: left; font: inherit;
}
/* 联系后台入口行的客服头像。⚠️ 2026-09-16 曾整块删掉（去人型图标那次误删），
   同日按用户更正还原 —— 要去的是二维码**弹窗标题**里的图标，不是这一处。
   ⚠️ 2026-10-09 用户：「把客服微信头像改为 logo」——里头换成站标。
      绿色圆底（#07c160）撤了：logo 本身是橙底圆角方块，压在绿圆上会像贴纸。
      占位仍是 44px 见方、仍是 flex-shrink:0，入口行的排版一个像素都不动。
      ⚠️⚠️ 里面那张图是**竖版锁版**（标在上、字在下，用户点名要"带字那张"），
      长宽比不是 1:1 —— 所以 img 只能按**高**对齐，写成 width:44px 会把它压扁。
      横向 44px 的格子放 35px 宽的图，左右自然留白，行高不被撑。 */
.wx-entry-icon {
  width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.wx-entry-icon img { height: 44px; width: auto; max-width: 44px; display: block; }
.wx-entry-text { display: flex; flex-direction: column; gap: 2px; flex: 1; }
.wx-entry-text b { font-size: 18px; color: var(--text); }
.wx-entry-text em { font-style: normal; font-size: 15px; color: var(--text-secondary); }
.wx-entry-arrow { color: #bbb; font-size: 23px; }
.msg-title { font-size: 16px; color: var(--text-secondary); margin: 2px 0 10px; padding-left: 2px; }
.msg-list { display: flex; flex-direction: column; gap: 8px; min-height: 60px; }
.msg-empty { text-align: center; color: var(--text-secondary); font-size: 16px; padding: 30px 0; }
.msg-row { display: flex; flex-direction: column; align-items: flex-start; }
.msg-row.mine { align-items: flex-end; }
/* 留言发送失败：气泡左侧一个红色感叹号，点它重发（2026-09-29）。
   ⚠️ .msg-row 本体是 column 方向（竖排的是**气泡里面**的 meta/正文/时间），
      所以感叹号要是直接塞进去会落在气泡**上面**。这里单独把失败那一行掰成 row
      + justify-content:flex-end，感叹号才会落到气泡**左边**（「前面」）。
   ⚠️ 别改成给 .msg-row 全体换 row —— 正常气泡会跟着错位。 */
.msg-row.mine.has-fail { flex-direction: row; align-items: center; justify-content: flex-end; gap: 6px; }
.msg-fail {
  position: relative;
  flex: none;
  width: 22px; height: 22px; border-radius: 50%;
  background: #ef4444; color: #fff;
  font-size: 19px; font-weight: 700; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; user-select: none;
}
/* 视觉 22px、点击区 46px（22 + 12×2）—— 22px 在手机上仍不算大，靠这层透明的
   ::after 把可点范围外扩出去。
   ⚠️ 改上面的 width/height 时，这里的 inset 要一起重算（后台端那份也一样，见 admin.html）。 */
.msg-fail::after { content: ''; position: absolute; inset: -12px; }
/* 气泡：**我的绿底白字、客服的白底描边**，尺寸/圆角对齐后台的 .bubble
   （后台：.bubble.admin = #07c160 白字，.bubble.user = 白底 + 1px #e5e7eb 描边）。
   ⚠️ 字号**故意不是**「和后台一样」：2026-10-01 车商端全局字号 +3，气泡跟着 15 → 18
      （时间戳同理，13 vs 后台的 11）。后台是电脑上看的，车商在手机上看，不能一个字号。
      所以这句原来写的「字号全部对齐后台」是句假话，2026-10-09 改掉了 —— 别照它改回 15。
   ⚠️ 绿色沿用 #07c160（微信那个绿），别换成 var(--success)（#10b981 偏青）。
      ⚠️ 2026-10-09 起入口那个绿色圆形图标已换成 logo，本页**没有**第二个绿跟它作对照了 ——
         但 #07c160 别动：微信用户一眼认的就是这个绿，凭「反正没人比」把它改掉是错的。 */
.msg-bubble { max-width: 78%; padding: 9px 12px; border-radius: 10px; font-size: 18px; line-height: 1.45; word-break: break-word; }
.msg-bubble.mine { background: #07c160; color: #fff; border-top-right-radius: 2px; }
.msg-bubble.theirs { background: var(--bg-card); color: var(--text); border: 1px solid var(--border); border-top-left-radius: 2px; }
.msg-meta { display: flex; align-items: center; gap: 6px; margin-bottom: 3px; }
.msg-name { font-size: 14px; opacity: .85; }
.msg-time { font-size: 13px; opacity: .75; margin-top: 3px; }
/* ⚠️ 这三条是**绿气泡上**的配色（底色一改绿，原来那套按白底挑的颜色就全不成立了）：
   时间跟后台一样走右对齐 + 半透明白；「未读」小标原来是粉底红字，压在绿上又跳又脏，
   改成半透明白底白字 —— 它要表达的是"还没被看到"，字本身写着「未读」，不需要靠红色喊。 */
.msg-bubble.mine .msg-time { opacity: 1; color: rgba(255,255,255,.85); text-align: right; }
.msg-status { font-size: 14px; padding: 1px 8px; border-radius: 10px; }
.msg-status.unread { background: rgba(254,226,226,0.9); color: #ef4444; }
.msg-bubble.mine .msg-status.unread { background: rgba(255,255,255,.25); color: #fff; }
.msg-text { white-space: pre-wrap; }
.msg-imgs { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.msg-imgs img { width: 72px; height: 72px; object-fit: cover; border-radius: 8px; cursor: zoom-in; }
/* 照片已被服务端 90 天清理（或文件早已不在，图加载失败）→ 原位一张灰片，不给碎图标。
   文案「照片已过期」是用户指定的。尺寸与 .msg-imgs img 严格一致，否则气泡会跳一下。 */
.msg-photo-expired { width: 72px; height: 72px; border-radius: 8px; background: #e9ecf1; color: #98a2b3; font-size: 14px; line-height: 1.25; display: flex; align-items: center; justify-content: center; text-align: center; padding: 0 4px; box-sizing: border-box; }
/* 我这边气泡是绿的，原来那圈灰描边在绿底上像没对齐的框；后台的图也没有描边 */
.msg-bubble.mine .msg-imgs img { border: none; }
.msg-input-bar {
  flex-shrink: 0;
  display: flex; align-items: flex-end; gap: 8px; flex-wrap: wrap;
  padding: 10px 12px calc(10px + env(safe-area-inset-bottom, 0px));
  background: var(--bg-card); border-top: 1px solid var(--border);
}
/* 输入框对齐后台的 #chatInput：圆角 10px（原来是 18px 的胶囊，和上面灰色方角区不搭）、
   白底、聚焦时绿边。⚠️ flex-wrap:wrap 和 .msg-photo-preview 的 order:-1 都**要留着** ——
   照片预览是靠 flex-basis:100% 换行到上面一整行的。 */
.msg-input-bar textarea {
  flex: 1; resize: none; border: 1px solid var(--border); border-radius: 10px;
  padding: 9px 11px; font: inherit; font-size: 18px; background: var(--bg-card);
  color: var(--text); max-height: 96px; min-height: 38px; outline: none;
}
.msg-input-bar textarea:focus { border-color: #07c160; }
/* 上传照片键：**显示效果对齐后台的 .chat-icon-btn**（透明底 + 8px 圆角方块 +
   按下/悬停时透出 #f0f0f0 底），原来是个 50% 圆的纯图标、只有按住才有一点底色。
   ⚠️ **位置不变**（还在输入框右边、发送键左边）—— 后台那边它在输入框**左边**，
      用户特意说了「上传照片按键位置不变」，别照后台挪过去。
   ⚠️ 尺寸保留 40×40（后台是 34×34）：后台那个数在手机上偏小，
      本机体检把「点击区 < 40px」列为一类问题，这里不跟着缩。视觉反馈是一样的。 */
.msg-photo-btn {
  width: 40px; height: 40px; flex-shrink: 0; border: none; background: transparent;
  border-radius: 8px; transition: background .15s;
  font-size: 21px; cursor: pointer; color: var(--text);
}
/* 后台只有 :hover（它主要在电脑上用）；这页车商在手机上点，触摸设备没有 hover，
   所以 :active 也要给同一个色，不然手机上按下去还是看不出反应。 */
.msg-photo-btn:hover, .msg-photo-btn:active { background: #f0f0f0; }

/* ====== 配音音色（会员专属福利）：布局与文案区「各平台文案」一致 ====== */
/* 视频模块与文案模块同构：纵向排列（标题→描述→音色卡→开始生成/查看按钮右下角） */
.module-item[data-module="video"] {
  flex-direction: column;
  align-items: stretch;
}
.module-item[data-module="video"] .module-btn {
  align-self: flex-end;
  margin-top: 10px;
}
/* 音色卡与文案区平台选择同结构（platform-mini 横向 wrap、胶囊+绿色选中），仅作用域限音色区 */
#voiceVoiceMini .pf-item {
  font-size: 16px;
  color: var(--text);
  justify-content: center;  /* 名称在胶囊内水平居中（颜色/边框沿用 .pf-item） */
  /* 360px 屏上 5 列每格只有 54.4px，默认 padding:4px 9px 装不下「AI语音」，
     文字会折成「AI语 / 音」两行、那张卡高 49px（其余 30px），整排参差。
     这是分两排之前就有的（旧 flex 布局实测同样 49/30/30/30/30），顺手修掉：
     收窄内边距 + 禁止折行。12px 内边距下 320px 屏也放得下，不影响 390px 观感。 */
  padding-left: 6px;
  padding-right: 6px;
  white-space: nowrap;
}
#voiceVoiceMini .pf-item.selected { color: #10b981; }
/* 2026-09-14 音色卡分两排，且两排共用同一套 5 列栅格：
     第一排 5 张各占 1 列（等宽铺满，外观与改前一致）
     第二排 猴哥|八戒|熊二 占第 1~3 列，「开始生成」占第 4~5 列
   用 grid 而不是 flex 的原因：flex 下两行各自算宽度，第二排的三张卡永远比第一排窄、
   且对不齐列（用户要的是「大小一样、上下对齐」）。1fr × 5 保证两行列宽完全相同，
   猴哥正对 AI语音、八戒正对小何、熊二正对薇薇。 */
.voice-row {
  flex: 1 1 100%;                    /* 在 .platform-mini 里独占一行 */
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 6px;
  align-items: center;
}
/* 第二排单独用 flex（第一排仍是 grid）：三张卡各占一列（宽度见下面那条 calc），
   右边空着，与第一排上下对齐。
   ⚠️ 这里原本还有一段 `#voiceVoiceMini .voice-row > .module-btn` 提权规则，是给**住在第二排里的**
      「开始生成」用的（不提权压不住 .module-item[data-module="video"] .module-btn 的
      align-self/margin-top —— 特异度 (0,3,0)，实测踩过）。2026-10-01 按钮已移出这一排、
      改住到 .module-item 下面单独一行，那段规则随之删掉。
      别再把按钮塞回 .voice-row：一旦塞回去，就得把那套提权规则一并加回来。 */
#voiceRowExtra { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
/* 第一排每列宽 = (容器宽 - 4×gap) / 5，这里用同一公式算死，保证与第一排同宽同列。
   ⚠️ 不能写成 flex:1 或百分比：那样第二排三张又会按自己的剩余宽度等分（就是之前对不齐的原因）。 */
#voiceRowExtra > .pf-item { flex: 0 0 calc((100% - 24px) / 5); }

/* ====== 车辆展示模块：纵向布局，按钮下移到内容下方单独一排、靠右 ====== */
/* 用户 2026-10-01 要求：按钮不要和左侧内容并排，做成「各平台文案」那样单独一排
   （按钮上现在还要写「开始生成（100 积分）」，横排会被挤窄）。
   ⚠️ 这里原来写的是 `margin-top: -33px` —— 那是**并排时代**的对齐方案（把按钮上提，
   让它的底部跟左侧最后一个勾选项「电话水印」的文字底部齐平）。按钮已经移到内容下方，
   负边距只会把它推回去压住文字。别改回负值。 */
.module-item[data-module="h5"] {
  flex-direction: column;
  align-items: stretch;
}
/* ⚠️ 用子选择器 `>` 是有意的：`.btn-group-sm` 那条是给**生成之后**的 [查看 3D][复制链接]
   用的（那个组没有 copy-btn-group/video-btn-group 的 justify-content，不一起管就会甩到左边），
   而只读预览态的同名组住在 .ro-h5-stack 里、另有 flex-start 对齐规则，不该被这里命中。 */
.module-item[data-module="h5"] > .module-btn,
.module-item[data-module="h5"] > .btn-group-sm {
  align-self: flex-end;
  margin-top: 10px;
}
/* 发送键：颜色和圆角都跟后台的 `.btn.green.chat-send` 对齐（2026-09-24 用户要求
   「发送键和后台一样」）。圆角从 19px 胶囊改成 10px —— 左边输入框上一轮已经改成
   10px 方角了，发送键留着胶囊形状会跟它不搭。
   ⚠️ 这个绿是 `var(--success)` = **#10b981**，和上面对话气泡的 `#07c160` **不是同一个绿**，
      是**故意的**：气泡用微信那个绿，而这里用户明确要的是"后台那个绿"。
      别看到两个绿就想"统一"成一个。（入口的绿圆 2026-10-09 已换成 logo，
      但这条口径不变 —— 当时定它本来就不是照入口取的色。） */
.msg-send-btn {
  flex-shrink: 0; height: 38px; padding: 0 18px; border: none; border-radius: 10px;
  background: var(--success); color: #fff; font-size: 17px; cursor: pointer;
}
.msg-send-btn:active { opacity: .85; }
.msg-photo-preview { display: flex; gap: 6px; padding: 0; flex-wrap: wrap; flex-basis: 100%; order: -1; }
.msg-photo-preview:empty { display: none; }
.msg-photo-item { position: relative; }
.msg-photo-item img { width: 56px; height: 56px; object-fit: cover; border-radius: 8px; display: block; }
.msg-photo-del {
  position: absolute; top: -6px; right: -6px; width: 18px; height: 18px;
  text-align: center; line-height: 18px; border-radius: 50%;
  background: rgba(0,0,0,0.65); color: #fff; font-size: 14px; cursor: pointer;
}
/* 客服微信二维码弹窗 */
.wx-qr-box { display: flex; flex-direction: column; align-items: center; padding: 4px 0 8px; }

/* ====== 聊天照片大图查看器（与后台 admin 同一套：右上×，左右切换，可捏合/双击/滚轮放大） ====== */
.chat-lightbox { position: fixed; inset: 0; z-index: 99999; display: none; align-items: center; justify-content: center; background: rgba(0,0,0,.88); overflow: hidden; }
.chat-lightbox.on { display: flex; }
/* 手势层（逻辑在 public/js/lightbox.js，两端共用）。手势全绑在这一层上。
   ⚠️ touch-action:none 是硬要求：车商端 viewport 带 user-scalable=no，但 iOS 仍有橡皮筋回弹，
      而且这套缩放要自己接管，不能让浏览器先把手势吃掉。 */
.lb-stage { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; overflow: hidden; touch-action: none; }
/* transform 由 lightbox.js 写（translate + scale）。
   ⚠️ 故意不加 will-change:transform —— 它是纯优化，但会让老安卓 WebView 多占一份图层内存；
      现代内核本来就会给 transform 自动提层，实测卡顿再说。 */
.lb-img { max-width: 92vw; max-height: 88vh; object-fit: contain; border-radius: 8px; box-shadow: 0 4px 30px rgba(0,0,0,.55); background: #000; transform-origin: center center; user-select: none; -webkit-user-drag: none; }
.lb-btn { position: absolute; border: none; cursor: pointer; z-index: 2; color: #fff; background: rgba(255,255,255,.16); border-radius: 50%; width: 42px; height: 42px; display: flex; align-items: center; justify-content: center; font-size: 23px; line-height: 1; }
.lb-btn:active { background: rgba(255,255,255,.35); }
.lb-close { top: 14px; right: 14px; font-size: 19px; }
.lb-prev { left: 12px; top: 50%; transform: translateY(-50%); font-size: 29px; }
.lb-next { right: 12px; top: 50%; transform: translateY(-50%); font-size: 29px; }
.lb-count { position: absolute; bottom: 18px; left: 50%; transform: translateX(-50%); color: #fff; background: rgba(0,0,0,.45); padding: 4px 14px; border-radius: 14px; font-size: 16px; }
/* .lb-tip（大图顶部常驻的"双指缩放/双击放大"说明行）2026-09-25 按用户要求整个删掉，
   连同 index.html / admin.html 里的 <div class="lb-tip">。别再加回来。 */
/* 大图内的临时提示（翻到头时用），元素由 lightbox.js 创建。
   ⚠️ 必须画在 .chat-lightbox **内部**：全局 .toast 的 z-index 只有 3000、大图是 99999，
   用 toast 会被压在黑遮罩底下 —— 用户按了 ▶ 什么都不出现，看着就是"点了没反应"。
   ⚠️ pointer-events:none：它是盖在图片上方的一层，不设的话会挡住"点空白关闭"。 */
.lb-hint { position: absolute; bottom: 56px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,.72); color: #fff; font-size: 16px; padding: 7px 16px; border-radius: 16px; white-space: nowrap; opacity: 0; transition: opacity .18s; pointer-events: none; }
.lb-hint.on { opacity: 1; }

/* ====== 账户关联（个人中心页） ====== */
.link-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; padding: 10px 0; border-bottom: 1px solid var(--border); }
.link-row:last-child { border-bottom: none; }
.link-info { flex: 1; min-width: 0; }
.link-name { font-size: 17px; font-weight: 600; color: var(--text); display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.link-phone { font-size: 15px; font-weight: 400; color: var(--text-secondary); }
.link-remark { font-size: 15px; color: var(--text-secondary); margin-top: 2px; word-break: break-all; }
.link-remark-empty { color: #c0c4cc; }
.link-time { font-size: 14px; color: #c0c4cc; margin-top: 2px; }
/* 行内操作按钮横排 + 底部对齐：竖排时两个按钮总高 98px 会把整行撑到 119px，而左侧文字只占 43px，
   「同意」就悬在日期下方 58px 的空白里。横排后行高交回左侧文字决定，按钮组底部对齐行底部，
   「同意」的底部正好与「申请时间」的底部齐平 */
.link-ops { display: flex; flex-direction: row; align-items: center; gap: 6px; flex-shrink: 0; align-self: flex-end; }
/* 卡片的通用规则 .account-card .btn 给了每个按钮 12px 上外边距（竖排表单用），
   行内按钮吃这个外边距会让按钮组凭空高 12px、把整行又顶开，这里清掉 */
.link-ops .btn { margin-top: 0; }
.link-search-hit { border: 1px solid var(--border); border-radius: 8px; padding: 10px; background: rgba(74,108,247,0.04); }
/* 搜索结果卡里的号码要跟店名一样看得清（列表卡里仍是 12px 的次要样式） */
.link-search-hit .link-phone { font-size: 17px; font-weight: 500; color: var(--text); }
.link-ok { color: #10b981; font-size: 16px; padding: 6px 0; }
/* 搜索结果区：只在有结果时撑开间距。空着时高度为 0，卡片的 padding-bottom 就成了按钮到卡片底的净留白，
   与顶部 6px 对称；若写死 margin-top，空态会多出一段，底部又被顶下去 */
#linkSearchResult:not(:empty) { margin-top: 10px; }

/* ====== 关联账号记录只读预览态（禁生成/禁编辑/禁增删照片） ====== */
body.link-readonly .upload-area,
body.link-readonly .upload-add,
body.link-readonly .thumb-del,
body.link-readonly #btnRefreshGen,
body.link-readonly .interior-toggle,
body.link-readonly #copyPlatformMini,
body.link-readonly #voiceVoiceMini .pf-item,   /* ⚠️ 藏的是音色卡，不是整个 #voiceVoiceMini：
                                                  视频模块的「查看」按钮就长在容器里的 #voiceRowExtra 上，
                                                  连容器一起藏 = 按钮跟着消失（2026-09-18 用户反馈「视频没有查看按键」） */
body.link-readonly .module-item .module-btn { display: none !important; }
/* 只读预览里四个模块都不显示积分价签（2026-09-18 用户要求）：
   这里只能查看/保存/复制，生成不了，标着价反而是误导（「点了会不会扣分？」）。 */
body.link-readonly .module-item .pts-tag { display: none !important; }
/* 四个「查看」按钮统一黑边框（2026-09-18 用户反馈边框不一致）：
   展示/图包走 .btn-outline 的 var(--border) 灰白，文案/视频走 .ro-btn-group .btn 的 `border:1px solid`
   （无色 = currentColor = 近黑）→ 同样的按钮两种颜色。只读态一律用 --text。
   ⚠️ 只框这四个，别写成 body.link-readonly .btn-outline —— 页头的「← 返回」也是 .btn-outline，
      跟着变黑会和它本身的次要按钮定位不符。 */
body.link-readonly .btn-view,
body.link-readonly .btn-view-pack,
body.link-readonly .ro-btn-group .btn { border-color: var(--text); }
/* 只读 h5 模块：水印按钮在上整行、查看/复制行在下靠左 */
.ro-h5-stack { display: flex; flex-direction: column; align-items: stretch; gap: 6px; margin-top: 6px; }
.ro-h5-stack .btn-group-sm { justify-content: flex-start; }
/* 只读 h5 卡里的「自动变更为我的电话水印」勾选框：与「账户关联」页那档同款（同一账号开关）。
   ⚠️ 类名不能用 .interior-toggle —— 那个类在只读态被整片藏掉（见上方 body.link-readonly 列表）。 */
.ro-auto-cover { display: flex; align-items: center; gap: 7px; cursor: pointer; padding: 2px 0; }
.ro-auto-cover input[type="checkbox"] { accent-color: #22c55e; width: 15px; height: 15px; flex-shrink: 0; margin: 0; }
.ro-auto-cover > span:not(.help-dot) { font-size: 16px; color: var(--text); }

/* ====== 积分充值功能区（个人中心 → 积分充值） ====== */
/* 不退款提示：车商指定常驻显示在页面顶端（灰色小字，不做勾选/弹窗——现在也没有在线支付） */
.points-notice { font-size: 15px; color: var(--text-secondary); line-height: 1.6; margin: 0 2px 12px; }
.points-balance { background: var(--bg-card); border-radius: var(--radius); box-shadow: var(--shadow); padding: 16px; text-align: center; margin-bottom: 16px; }
.pb-label { display: block; font-size: 15px; color: var(--text-secondary); }
.pb-num { display: block; font-size: 35px; font-weight: 700; color: var(--primary); margin-top: 4px; }
.points-sec-title { font-size: 16px; font-weight: 600; color: var(--text-secondary); margin: 16px 2px 8px; }
.points-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(88px, 1fr)); gap: 8px; }
.points-opt { background: var(--bg-card); border: 1.5px solid var(--border); border-radius: var(--radius); padding: 10px 6px; text-align: center; cursor: pointer; font-size: 17px; color: var(--text); }
/* min-height：1000 档没有赠送，那格是空的，不给最小高度四张卡的内容就对不齐 */
.points-opt em { display: block; font-style: normal; font-size: 14px; color: var(--text-secondary); margin-top: 2px; min-height: 15px; }
/* 「限时赠送」红色高亮。纯展示文案，数值来自后端 RECHARGE_TIERS.bonus，没有真实截止时间 */
.points-opt em.po-bonus { color: var(--danger); font-weight: 600; }
.points-opt.on { border-color: var(--primary); background: rgba(37,99,235,.06); }
.points-opt.on .po-main { color: var(--primary); font-weight: 600; }
.points-summary { background: var(--bg-card); border-radius: var(--radius); box-shadow: var(--shadow); padding: 14px; margin-top: 16px; font-size: 17px; line-height: 1.9; }
.points-summary b { color: var(--primary); font-size: 19px; }
.points-buy { width: 100%; margin-top: 12px; padding: 13px; border: none; border-radius: var(--radius); background: var(--primary); color: #fff; font-size: 18px; font-weight: 600; cursor: pointer; }
/* 积分到期时间表：按到期升序，快到期的在前；≤15 天整条黄、≤7 天整条红（见下面的 .exp-*） */
.points-table { background: var(--bg-card); border-radius: var(--radius); box-shadow: var(--shadow); padding: 2px 14px; }
/* 每条批次一个**灰白底小块**（2026-09-21 用户要求「把积分信息卡里显示的每批次区分改为灰白色底」）。
   原来是靠 `.points-tr + .points-tr` 的 1px 分隔线区分，现在改成色块 + 块间距。
   ⚠️ 底色用**正灰**（三通道差 ≤1）。偏蓝的灰（比如 `--bg` 的 #f5f6f8）配上蓝色的积分文字，
      截图拿识图模型一看会被描述成"浅蓝紫"，跟用户要的"灰白色"对不上 —— 积分那颗按键已经踩过一次。
   ⚠️ 这条规则是**三处共用**的（弹层 / 账户信息页 / 积分充值页都走 ptsBatchRowsHtml），
      改这里三处一起变，这是有意的，别为其中一处另开后门。 */
.points-tr {
  display: flex; justify-content: space-between; align-items: center;
  padding: 10px 12px; font-size: 17px;
  background: #f4f4f5; border-radius: 10px;
}
.points-tr + .points-tr { margin-top: 8px; }
.pt-left { color: var(--text); }
.pt-left em { display: block; font-style: normal; font-size: 14px; color: var(--text-secondary); margin-top: 2px; }
.pt-right { text-align: right; }
.pt-days { font-size: 15px; color: var(--text-secondary); }
/* ⚠️⚠️ 档位配色走过一条弯路，别再走回去（2026-09-21 一天之内）：
      最初「黄字」(#f59e0b / #d97706) → 用户觉得不像黄 → 一路提亮 #ca8a04 → #eab308 → #facc15
      → 用户回「亮的看不清晃眼睛、暗的也看不清，颜色不好」。
      **根因不是色号没调对，是"黄色做白底文字"这条路本身没有解**：黄在白底上对比度
      天然只有 1.4~3.2:1（正文要 4.5 才叫清楚），亮则晃眼、暗则发糊，两头都堵死。
      出路是**换赛道**：颜色不上字、上整块**底色**，文字用同色系深色 —— 对比度立刻上到 6~8:1。
      用户看对比页后拍板这套。⚠️ 再有人说"黄一点/亮一点"，**别动色号**，先回去看那条物理限制。
   ⚠️ 选择器不带 .points-mini 前缀是**故意的**：弹层和账户信息页共用 ptsBatchRowsHtml，
      同一批积分在两处必须同色，否则车商会以为两处数据不一样。 */
.points-tr.exp-ok { background: #dcfce7; }     /* 淡绿底：>15 天，2026-09-21 用户要求 */
.points-tr.exp-warn { background: #ffedd5; }   /* 浅橙底 */
.points-tr.exp-danger { background: #fee2e2; } /* 浅红底 */
/* 倒计时那格跟左边正文同色（原来是两档色：左边深、右边亮）。整块已经靠底色区分了，
   右边再单独用亮色反而脏。 */
.pt-days.warn { color: #9a3412; }
.pt-days.danger { color: #991b1b; font-weight: 600; }

/* 整条变色（2026-09-21 用户拍板：「≤15 天**整条信息**都显示黄色，≤7 天**整条信息**红色」）。
   类挂在 .points-tr 上，左边「N 积分 / 到期日」跟着一起变 —— 原来只有右边那个倒计时变色，用户要整条。
   现在演进成「整条连**底色**一起变」（2026-09-21 用户从 4 套方案里选的 B 方案，见上面的弯路记录）。
   ⚠️ 底色写在这两个单类选择器上，比 .pt-days 和 .points-tr 的灰白底（#f4f4f5）都更具体，能盖住；
      正常档不受影响，仍是灰白块。
   ⚠️ 深色系（#9a3412 深橙 / #991b1b 深红）是配浅底用的：底色一浅，文字就必须压深，
      不然又回到"糊"的老问题。 */
.points-tr.exp-warn .pt-left, .points-tr.exp-warn .pt-left em { color: #9a3412; }
.points-tr.exp-danger .pt-left, .points-tr.exp-danger .pt-left em { color: #991b1b; }
/* 正常档（>15 天）：2026-09-21 用户要求「把超过 15 天的改为淡绿色」。淡绿底 + 深绿字，
   跟橙/红同一套做法（颜色上底色、文字压深）。三档至此都有底色：绿=充裕 / 橙=将到期 / 红=快过期。
   ⚠️ 右边倒计时也一起染绿（.pt-right）—— 它自己那条 .pt-days 是灰的，不覆盖的话
      会变成"绿底上挂个灰字"，跟左边对不上。正常档的 .pt-days 没有 warn/danger 类，所以这条不会误伤另外两档。 */
.points-tr.exp-ok .pt-left, .points-tr.exp-ok .pt-left em, .points-tr.exp-ok .pt-right { color: #166534; }
.points-empty { text-align: center; color: var(--text-secondary); font-size: 16px; padding: 22px 0; }
/* 到期明细搬进余额卡之后：它自己的卡片底/阴影要去掉（否则卡中卡两层阴影很脏），
   改成一条分隔线 + 左对齐，跟上面的「当前可用积分」形成上下两块。 */
.points-balance .points-table {
  background: transparent; box-shadow: none; padding: 0; margin-top: 12px;
  border-top: 1px solid var(--border); text-align: left;
}
/* ⚠️ 原来这里还有 `.points-tr:first-child{padding-top:10px}` 和 `:last-child{padding-bottom:0}`
   （行没有底色时，靠归零让首末块贴合卡片边）。现在每行是独立的灰白块，归零会让首末两块扁一截，
   **两条都已删**。别照着旧版加回来。 */

/* 积分记录三个折叠区（充值 / 使用 / 到期）：默认全收起，点哪个开哪个 */
.pts-acc { margin-top: 18px; }
.pts-acc-head {
  display: flex; align-items: center; justify-content: space-between; cursor: pointer;
  padding: 13px 14px; margin-bottom: 8px;
  background: var(--bg-card); border-radius: var(--radius); box-shadow: var(--shadow);
  font-size: 17px; color: var(--text);
}
.pts-acc-head.on .pts-acc-title { font-weight: 600; color: var(--primary); }
.pts-acc-arrow { color: #c0c4cc; font-size: 21px; transition: transform .18s; }
.pts-acc-head.on .pts-acc-arrow { transform: rotate(90deg); }
.pts-acc-body {
  background: var(--bg-card); border-radius: var(--radius); box-shadow: var(--shadow);
  margin-bottom: 8px; padding: 2px 14px;
}
/* 记录行：左边「什么功能 / 什么时候」，右边「动了多少 / 动完还剩多少」 */
.pts-rec { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; }
.pts-rec + .pts-rec { border-top: 1px solid var(--border); }
.pts-rec-t { font-size: 16px; color: var(--text); }
.pts-rec-d { font-size: 14px; color: var(--text-secondary); margin-top: 2px; }
.pts-rec-r { text-align: right; flex-shrink: 0; margin-left: 10px; }
.pts-rec-amt { font-size: 17px; font-weight: 600; }
.pts-rec-amt.in { color: #10b981; }
.pts-rec-amt.out { color: #ef4444; }
/* delta=0 的行（后台改到期日）：不增不减，写个灰的「—」，别写成绿色的 +0 让人以为加了分 */
.pts-rec-amt.flat { color: #9ca3af; }
.pts-rec-r em { display: block; font-style: normal; font-size: 14px; color: var(--text-secondary); margin-top: 2px; }
/* 分页条（2026-09-23 车商：「每个记录里面的记录显示分页，每页15条信息」）。
   只有超过一页才渲染，所以这一行出现就说明确实有得翻，不用再写「共 N 条」以外的东西。
   ⚠️ 按钮按 40px 高做：手机上手指点得中（本机体检把「点击区 < 40px」列为一类问题，
      新加的可点元素就别再往那条老账上添了）。 */
.pts-pager {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; padding: 10px 0 12px; border-top: 1px solid var(--border);
}
.pts-page-btn {
  min-height: 40px; padding: 0 14px; font-size: 16px;
  font-family: inherit; color: var(--primary);
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius);
  cursor: pointer;
}
/* 到头了就置灰。⚠️ 置灰只是"看起来不能点"，真正拦住点击的是 ptsPagerHtml 里
   给到头那个键加的 `disabled` 属性 —— 只写这条 CSS 的话按钮照样能按下去。 */
.pts-page-btn:disabled { color: #c0c4cc; background: var(--bg-card); cursor: default; }
.pts-page-info { font-size: 15px; color: var(--text-secondary); text-align: center; flex: 1; }
