/* ⚠️ 生成物，不要手改：`python -m scripts.build_user_page_assets` 重新生成。
   来源（按顺序）：vibe/shared/theme/workbench.tokens.css（含它 @import 的 tokens.primitives.css）
                 + frontend/src/style.css
                 + frontend/src/studio.css
   所有 @import 都已**展开**：产物自包含，页面不再发第二次 CSS 请求。
   守卫：tests/live/test_user_page.py::test_committed_page_assets_are_up_to_date */
/* ============================================================================
   workbench.tokens.css —— 暗色工作台令牌（S 层 + T 层）
   ============================================================================
   服务两个消费方（原先各抄一份、色值逐条相同）：
     - vibe/hp-video/server/frontend/src/style.css        （口播工作台）
     - vibe/hp-video/client/mix/renderer/src/style.css        （混剪渲染层）

   Figma Standard Pattern 的 "Color  modes:['Light','Dark']" 里的 **Dark 模式**：
     S 层 `--s-*`   = 语义，别名指向 tokens.primitives.css
     T 层 `--bg/--panel/...` = 兼容别名，名字与两处既有样式**完全一致**（消费方零改动）

   为什么不直接把两处的 `:root` 合并了事：两份文件各自还有壳专属增量
   （sip 的 `--safe-b/--safe-t` 安全区、mix 的 `.modal-overlay` 等），
   只有**调色板**是共用的，所以共用层只收调色板。
   ============================================================================ */

/* ============================================================================
   tokens.primitives.css —— P 层：原始值（Primitives）
   ============================================================================
   分层依据：Figma 官方 agent skill《Working with design systems: Variables》与
   《token-creation.md》的 Standard Pattern ——

     Collection "Primitives"  modes:["Value"]      ← 原始值，无模式（本文件）
     Collection "Color"       modes:["Light","Dark"] ← 语义，别名指向原始值
                                                       （tokens.semantic.css / workbench.tokens.css）

   方法论原文两条决定本文件形态：
   1.「If the source data has two tiers (primitives + semantics), create all primitives first,
      then create semantic variables that alias into them.」
   2.「Aliasing … Common example is pointing a semantic variable to a primitive variable.」

   对应到 CSS：本层只放 `--p-*` 字面量，**不放任何语义判断**；
   语义层用 `var(--p-*)` 别名。改一个色值只改这里一处。

   为什么需要它：sip（口播工作台）与 mix（混剪渲染层）原先**各自抄了一份完全相同的暗色色值**，
   改一处要记得改两处。本层 + 语义层把这份重复消掉。
   ============================================================================ */

:root {
  /* ---------------------------------------------------------------- 中性 */
  --p-white: #ffffff;
  --p-gray-50: #f9fafb;
  --p-gray-100: #f3f4f6;
  --p-gray-200: #e5e7eb;
  --p-gray-150: #f5f5f5;
  --p-gray-300: #d1d5db;
  --p-gray-950: #0b1120;   /* 暗色顶栏 */
  --p-green-300: #6ee7b7;
  --p-green-900: #064e3b;
  --p-red-300: #fca5a5;
  --p-red-900: #7f1d1d;
  --p-amber-300: #fcd34d;
  --p-amber-950: #78350f;
  --p-blue-300: #93c5fd;
  --p-blue-900: #1e3a8a;
  --p-violet-300: #c4b5fd;
  --p-violet-900: #4c1d95;   /* 控制台页面底色（writer base.html:28 的取值） */
  --p-gray-400: #9ca3af;
  --p-gray-500: #6b7280;
  --p-gray-600: #4b5563;
  --p-gray-700: #374151;
  --p-gray-800: #1f2937;
  --p-gray-900: #111827;

  /* ------------------------------------------- 亮色控制台（L1）调色板 */
  --p-blue-600: #2563eb;
  --p-blue-700: #1d4ed8;
  --p-blue-50: #eff6ff;
  --p-blue-100: #dbeafe;
  --p-blue-800: #1e40af;
  --p-ios-blue: #0071e3;   /* 控制台 --c-info 的既有取值 */
  --p-green-600: #059669;
  --p-green-50: #d1fae5;
  --p-green-800: #065f46;
  --p-red-600: #dc2626;
  --p-red-700: #b91c1c;
  --p-red-50: #fee2e2;
  --p-red-800: #991b1b;
  --p-amber-600: #d97706;
  --p-amber-50: #fef3c7;
  --p-amber-800: #92400e;
  --p-violet-600: #5b21b6;
  --p-violet-50: #ede9fe;

  /* ------------------------------------------- 电商控制台（hh-ecom MPA）补充色 */
  --p-leaf-600: #16a34a;
  --p-leaf-50: #dcfce7;
  --p-leaf-800: #166534;

  /* ------------------------------------------- 暗色工作台（L2）调色板 */
  --p-ink-1000: #0b0e1a;
  --p-ink-950: #0f1220;
  --p-ink-900: #181c30;
  --p-ink-800: #1f2440;
  --p-ink-700: #2a3050;
  --p-ink-600: #2a3341;
  --p-ink-500: #3a3f5c;
  --p-paper-50: #e8eaf6;
  --p-paper-400: #8b90b0;
  --p-paper-500: #777777;
  --p-violet-500: #6c5ce7;
  --p-violet-400: #a29bfe;
  --p-amber-400: #fdcb6e;
  --p-teal-500: #00b894;
  --p-teal-900: #0d3b30;
  --p-coral-500: #e17055;
  --p-coral-900: #4a1f18;
  --p-amber-900: #4a3a12;
}

/* 租户可注入的前台兜底值（hh-ecom L3）引用的同族原始值见上；租户主题白名单只放行语义层，
   原始层不允许外部注入 —— 见《UI交互设计专项-设计规范》§4.11。 */

:root {
  color-scheme: dark;

  /* ---------------------------------------------------------------- S 层：语义 */
  --s-color-bg: var(--p-ink-950);
  --s-color-surface: var(--p-ink-900);
  --s-color-surface-2: var(--p-ink-800);
  --s-color-sunken: var(--p-ink-1000);
  --s-color-border: var(--p-ink-700);
  --s-color-border-strong: var(--p-ink-600);
  --s-color-disabled-bg: var(--p-ink-500);
  --s-color-disabled-text: var(--p-paper-500);
  --s-color-text: var(--p-paper-50);
  --s-color-text-muted: var(--p-paper-400);
  --s-color-primary: var(--p-violet-500);
  --s-color-primary-soft: var(--p-violet-400);
  --s-color-accent: var(--p-amber-400);
  --s-color-success: var(--p-teal-500);
  --s-color-success-deep: var(--p-teal-900);
  --s-color-danger: var(--p-coral-500);
  --s-color-danger-deep: var(--p-coral-900);
  --s-color-warn-deep: var(--p-amber-900);

  /* ---------------------------------------------------------------- T 层：兼容别名
     名字与原 sip / mix 的 :root 一致 —— 消费方一个类名、一条规则都不用改。 */
  --bg: var(--s-color-bg);
  --panel: var(--s-color-surface);
  --panel2: var(--s-color-surface-2);
  --line: var(--s-color-border);
  --text: var(--s-color-text);
  --muted: var(--s-color-text-muted);
  --primary: var(--s-color-primary);
  --accent: var(--s-color-accent);
  --ok: var(--s-color-success);
  --err: var(--s-color-danger);

  /* ---------------------------------------------------------------- 焦点环
     暗底上 --accent 的对比度实测 11.18，主色只有 4.86 —— 焦点环用前者。 */
  --s-focus-ring: var(--s-color-accent);

  /* ================================================================ 结构层（W 层）
     原先这里**只有颜色**：间距/圆角/字号/时长全靠各处口算（8px、10px、12px、14px 混用），
     于是"层级"只能靠卡片 + 阴影表达 —— 这正是 SaaS 卡片套件的来源。

     结构层是**纯增量**（不改任何既有令牌），并且只定义"节奏"、不定义色相：
     色相仍由租户注入的 --s-color-* 决定，换肤不塌。
     命名用 `--w-`（workbench），与 `--p-`（primitive）/`--s-`（semantic）区分。 */

  /* 间距：4 的倍数，只留 6 档 —— 档位太多等于没有体系 */
  --w-sp-1: 4px;
  --w-sp-2: 8px;
  --w-sp-3: 12px;
  --w-sp-4: 16px;
  --w-sp-5: 24px;
  --w-sp-6: 32px;

  /* 圆角：**分三级**（原先所有东西共用一个 12px = 没有层级）。
     输入/按钮用 sm，面板用 md，只给"浮层"用 lg。 */
  --w-r-sm: 8px;
  --w-r-md: 12px;
  --w-r-lg: 18px;

  /* 字号：语义命名，避免再写 12/13/14/16 的口算值 */
  --w-fs-display: 22px;   /* 舞台稿件的字号档（提词器） */
  --w-fs-title: 16px;
  --w-fs-body: 14px;
  --w-fs-meta: 12px;

  /* 动效：只给"回应用户动作"用；非用户触发的动效全仓只在主动作上出现一次 */
  --w-dur-fast: 120ms;
  --w-dur: 200ms;
  --w-ease: cubic-bezier(.2, .7, .3, 1);
}

/* 布局稳定性（2026-11 全仓普查）：竖向滚动条出现/消失不得改变布局。
   scrollbar-gutter: stable 永远预留滚动条槽位；老浏览器回退为滚动条常显。
   另外：**不要用「视口宽度单位」做宽度**（它含滚动条宽度，滚动条一出现就溢出/抖动）。 */
/* 暗色工作台调色板 = 与另一处工作台**共用同一份**（原先两处各抄一份、色值逐条相同，
   改一处必须记得改两处）。分层照 Figma 官方 agent skill 的 Standard Pattern：
   Primitives（原始值）→ Semantic（语义）→ T 层兼容别名。
   ⚠ @import 必须位于所有规则之前（CSS 规范），故放在 scrollbar-gutter 规则上面。 */
/* ============================================================================
   workbench.tokens.css —— 暗色工作台令牌（S 层 + T 层）
   ============================================================================
   服务两个消费方（原先各抄一份、色值逐条相同）：
     - vibe/hp-video/server/frontend/src/style.css        （口播工作台）
     - vibe/hp-video/client/mix/renderer/src/style.css        （混剪渲染层）

   Figma Standard Pattern 的 "Color  modes:['Light','Dark']" 里的 **Dark 模式**：
     S 层 `--s-*`   = 语义，别名指向 tokens.primitives.css
     T 层 `--bg/--panel/...` = 兼容别名，名字与两处既有样式**完全一致**（消费方零改动）

   为什么不直接把两处的 `:root` 合并了事：两份文件各自还有壳专属增量
   （sip 的 `--safe-b/--safe-t` 安全区、mix 的 `.modal-overlay` 等），
   只有**调色板**是共用的，所以共用层只收调色板。
   ============================================================================ */

/* ============================================================================
   tokens.primitives.css —— P 层：原始值（Primitives）
   ============================================================================
   分层依据：Figma 官方 agent skill《Working with design systems: Variables》与
   《token-creation.md》的 Standard Pattern ——

     Collection "Primitives"  modes:["Value"]      ← 原始值，无模式（本文件）
     Collection "Color"       modes:["Light","Dark"] ← 语义，别名指向原始值
                                                       （tokens.semantic.css / workbench.tokens.css）

   方法论原文两条决定本文件形态：
   1.「If the source data has two tiers (primitives + semantics), create all primitives first,
      then create semantic variables that alias into them.」
   2.「Aliasing … Common example is pointing a semantic variable to a primitive variable.」

   对应到 CSS：本层只放 `--p-*` 字面量，**不放任何语义判断**；
   语义层用 `var(--p-*)` 别名。改一个色值只改这里一处。

   为什么需要它：sip（口播工作台）与 mix（混剪渲染层）原先**各自抄了一份完全相同的暗色色值**，
   改一处要记得改两处。本层 + 语义层把这份重复消掉。
   ============================================================================ */

:root {
  /* ---------------------------------------------------------------- 中性 */
  --p-white: #ffffff;
  --p-gray-50: #f9fafb;
  --p-gray-100: #f3f4f6;
  --p-gray-200: #e5e7eb;
  --p-gray-150: #f5f5f5;
  --p-gray-300: #d1d5db;
  --p-gray-950: #0b1120;   /* 暗色顶栏 */
  --p-green-300: #6ee7b7;
  --p-green-900: #064e3b;
  --p-red-300: #fca5a5;
  --p-red-900: #7f1d1d;
  --p-amber-300: #fcd34d;
  --p-amber-950: #78350f;
  --p-blue-300: #93c5fd;
  --p-blue-900: #1e3a8a;
  --p-violet-300: #c4b5fd;
  --p-violet-900: #4c1d95;   /* 控制台页面底色（writer base.html:28 的取值） */
  --p-gray-400: #9ca3af;
  --p-gray-500: #6b7280;
  --p-gray-600: #4b5563;
  --p-gray-700: #374151;
  --p-gray-800: #1f2937;
  --p-gray-900: #111827;

  /* ------------------------------------------- 亮色控制台（L1）调色板 */
  --p-blue-600: #2563eb;
  --p-blue-700: #1d4ed8;
  --p-blue-50: #eff6ff;
  --p-blue-100: #dbeafe;
  --p-blue-800: #1e40af;
  --p-ios-blue: #0071e3;   /* 控制台 --c-info 的既有取值 */
  --p-green-600: #059669;
  --p-green-50: #d1fae5;
  --p-green-800: #065f46;
  --p-red-600: #dc2626;
  --p-red-700: #b91c1c;
  --p-red-50: #fee2e2;
  --p-red-800: #991b1b;
  --p-amber-600: #d97706;
  --p-amber-50: #fef3c7;
  --p-amber-800: #92400e;
  --p-violet-600: #5b21b6;
  --p-violet-50: #ede9fe;

  /* ------------------------------------------- 电商控制台（hh-ecom MPA）补充色 */
  --p-leaf-600: #16a34a;
  --p-leaf-50: #dcfce7;
  --p-leaf-800: #166534;

  /* ------------------------------------------- 暗色工作台（L2）调色板 */
  --p-ink-1000: #0b0e1a;
  --p-ink-950: #0f1220;
  --p-ink-900: #181c30;
  --p-ink-800: #1f2440;
  --p-ink-700: #2a3050;
  --p-ink-600: #2a3341;
  --p-ink-500: #3a3f5c;
  --p-paper-50: #e8eaf6;
  --p-paper-400: #8b90b0;
  --p-paper-500: #777777;
  --p-violet-500: #6c5ce7;
  --p-violet-400: #a29bfe;
  --p-amber-400: #fdcb6e;
  --p-teal-500: #00b894;
  --p-teal-900: #0d3b30;
  --p-coral-500: #e17055;
  --p-coral-900: #4a1f18;
  --p-amber-900: #4a3a12;
}

/* 租户可注入的前台兜底值（hh-ecom L3）引用的同族原始值见上；租户主题白名单只放行语义层，
   原始层不允许外部注入 —— 见《UI交互设计专项-设计规范》§4.11。 */

:root {
  color-scheme: dark;

  /* ---------------------------------------------------------------- S 层：语义 */
  --s-color-bg: var(--p-ink-950);
  --s-color-surface: var(--p-ink-900);
  --s-color-surface-2: var(--p-ink-800);
  --s-color-sunken: var(--p-ink-1000);
  --s-color-border: var(--p-ink-700);
  --s-color-border-strong: var(--p-ink-600);
  --s-color-disabled-bg: var(--p-ink-500);
  --s-color-disabled-text: var(--p-paper-500);
  --s-color-text: var(--p-paper-50);
  --s-color-text-muted: var(--p-paper-400);
  --s-color-primary: var(--p-violet-500);
  --s-color-primary-soft: var(--p-violet-400);
  --s-color-accent: var(--p-amber-400);
  --s-color-success: var(--p-teal-500);
  --s-color-success-deep: var(--p-teal-900);
  --s-color-danger: var(--p-coral-500);
  --s-color-danger-deep: var(--p-coral-900);
  --s-color-warn-deep: var(--p-amber-900);

  /* ---------------------------------------------------------------- T 层：兼容别名
     名字与原 sip / mix 的 :root 一致 —— 消费方一个类名、一条规则都不用改。 */
  --bg: var(--s-color-bg);
  --panel: var(--s-color-surface);
  --panel2: var(--s-color-surface-2);
  --line: var(--s-color-border);
  --text: var(--s-color-text);
  --muted: var(--s-color-text-muted);
  --primary: var(--s-color-primary);
  --accent: var(--s-color-accent);
  --ok: var(--s-color-success);
  --err: var(--s-color-danger);

  /* ---------------------------------------------------------------- 焦点环
     暗底上 --accent 的对比度实测 11.18，主色只有 4.86 —— 焦点环用前者。 */
  --s-focus-ring: var(--s-color-accent);

  /* ================================================================ 结构层（W 层）
     原先这里**只有颜色**：间距/圆角/字号/时长全靠各处口算（8px、10px、12px、14px 混用），
     于是"层级"只能靠卡片 + 阴影表达 —— 这正是 SaaS 卡片套件的来源。

     结构层是**纯增量**（不改任何既有令牌），并且只定义"节奏"、不定义色相：
     色相仍由租户注入的 --s-color-* 决定，换肤不塌。
     命名用 `--w-`（workbench），与 `--p-`（primitive）/`--s-`（semantic）区分。 */

  /* 间距：4 的倍数，只留 6 档 —— 档位太多等于没有体系 */
  --w-sp-1: 4px;
  --w-sp-2: 8px;
  --w-sp-3: 12px;
  --w-sp-4: 16px;
  --w-sp-5: 24px;
  --w-sp-6: 32px;

  /* 圆角：**分三级**（原先所有东西共用一个 12px = 没有层级）。
     输入/按钮用 sm，面板用 md，只给"浮层"用 lg。 */
  --w-r-sm: 8px;
  --w-r-md: 12px;
  --w-r-lg: 18px;

  /* 字号：语义命名，避免再写 12/13/14/16 的口算值 */
  --w-fs-display: 22px;   /* 舞台稿件的字号档（提词器） */
  --w-fs-title: 16px;
  --w-fs-body: 14px;
  --w-fs-meta: 12px;

  /* 动效：只给"回应用户动作"用；非用户触发的动效全仓只在主动作上出现一次 */
  --w-dur-fast: 120ms;
  --w-dur: 200ms;
  --w-ease: cubic-bezier(.2, .7, .3, 1);
}

html { scrollbar-gutter: stable; }
@supports not (scrollbar-gutter: stable) {
  html { overflow-y: scroll; }
}
* { box-sizing: border-box; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
  /* 工具界面里的数字都是**表格数据**（时长/单价/tokens/版本号）——等宽数字让它们不对齐跳动。
     用字体特性而不是换一套 mono 字体：标题、正文、数字共用一套族，层级靠字重与字号。 */
  font-variant-numeric: tabular-nums;
}
button {
  background: linear-gradient(135deg, var(--s-color-primary), var(--s-color-primary-soft));
  color: #fff;
  border: 0;
  border-radius: var(--w-r-sm);
  padding: var(--w-sp-2) var(--w-sp-4);
  cursor: pointer;
  font-size: var(--w-fs-body);
}
button.secondary { background: var(--accent); color: #333; }
button:disabled { background: var(--s-color-disabled-bg); color: var(--s-color-disabled-text);
                 cursor: not-allowed; }
input, select, textarea {
  background: var(--panel2);
  border: 1px solid var(--line);
  border-radius: var(--w-r-sm);
  color: var(--text);
  padding: var(--w-sp-2) var(--w-sp-3);
  font-size: var(--w-fs-body);
  width: 100%;
}
textarea { resize: vertical; min-height: 64px; }
/* range 的 UA `margin: 2px` 会让它比容器宽 4px（窄卡片里就顶出 2px）——2026-09-23 审查在 390 档实测到 */
input[type="range"] { margin: 0; box-sizing: border-box; }
/* 复选框/单选框**不是文本框**：上面那条 `input { width:100%; padding; border; background }` 是给
   文本类控件的，套到 checkbox 上会把它们拉成"整行宽的有底方框"。后果有两层（2026-09-23 实测）：
   ① 同一屏里出现两副面孔（有的 13px 原生小框、有的 25×27 带底方框）；
   ② 在**有确定宽度**的容器里（如 `.check-note`）会被拉到容器宽度 —— 那个 379px 宽的勾选框
      把移动端整页顶出 28px 横向滚动条（④成片 在 390 宽下）。
   统一回原生尺寸与外观；与文字的间距交给 `.check-line/.check-note` 的 `gap`。 */
input[type="checkbox"], input[type="radio"] {
  width: auto; height: auto; padding: 0; margin: 0;
  background: none; border: 0; border-radius: 0;
  accent-color: var(--s-color-primary);
}
label { font-size: var(--w-fs-meta); color: var(--muted); display: block;
        margin: var(--w-sp-2) 0 var(--w-sp-1); }
.muted { color: var(--muted); }
.err { color: var(--err); font-size: 13px; }
.ok { color: var(--ok); font-size: 13px; }
.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
/* `auto-fit`（不是 `auto-fill`）：**空轨道会被折叠**，于是"这一步只有一张卡"时卡片吃掉整行。
   2026-09-23 实测：`/pc` 无检查器的步骤舞台宽 1024，`auto-fill` 会铺 2 条 505px 轨道，
   而每步只渲染一张可见卡 → 卡片只占左半边，右边 500 多像素空着（用户报"样式乱"的一处）。
   有 2 张卡时仍是 2 列；手机档本来就只有 1 条轨道，不受影响。 */
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); gap: 14px; }
/* 每一屏的**第一个内容元素**与顶栏之间留同一份白（2026-09-23 用户报"左侧菜单点击后右侧内容有的太靠顶部、
   margin 不一致"）。`/pc` 那三条（小步进条 / 舞台 / 账户首卡）在 `.wrap.pc` 段里；这里覆盖
   "宽屏浏览器 /app"这种非 /pc 的形态：舞台与"账户向"的首张卡都不能贴着顶栏。 */
main.grid { margin-top: var(--w-sp-4); }
.wrap > section.card:first-of-type { margin-top: var(--w-sp-4); }
.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--w-r-md);
  padding: 16px;
}
.card h3 { margin: 0 0 10px; font-size: 16px; display: flex; justify-content: space-between; }
.card .placeholder { color: var(--muted); font-size: 12px; border: 1px dashed var(--line);
  border-radius: var(--w-r-sm); padding: 12px; text-align: center; margin-top: 8px; }
.card .hint { color: var(--muted); font-size: 12px; margin: 4px 0 2px; }
.preview { margin-top: 8px; }
.preview video, .preview audio { max-width: 100%; border-radius: var(--w-r-sm); background: #000; }
.chip { font-size: 12px; border-radius: 999px; padding: 2px 10px; background: var(--panel2); }
.chip.ok { background: var(--s-color-success-deep); }

/* 复选框 + 紧随其后的文字（平台上架勾选、AI 角标、配乐说明…）。
   **为什么必须单独成类**：中文没有空格 → 一个 flex 项的 `min-content` 只有**一个字**宽，
   于是"抖音 / 视频号"这类短标签会被压成**一字一行**的竖排（2026-09-23 样式审查在 /pc
   账户屏的「发布伴侣 → 平台」实测到：抖音被拆成"抖/音"、视频号拆成"视/频/号"）。
   · `.check-line`：短标签 → 按内容占位、不收缩、不换行；
   · `.check-note`：长说明 → 允许换行，但给足最小宽度，避免"一字一行"。 */
.check-line { display: inline-flex; align-items: center; gap: var(--w-sp-1);
  flex: 0 0 auto; white-space: nowrap; }
.check-note { display: inline-flex; align-items: flex-start; gap: var(--w-sp-1);
  flex: 1 1 22em; min-width: 14em; }
.check-note > input { flex: 0 0 auto; margin-top: 3px; }

/* 「联系客服」统一形态（可点跳转）；主题没配客服信息时渲染成纯文本 .link 不会出现 */
.link { color: var(--accent); cursor: pointer; text-decoration: underline; }

/* ================================================================ 表单紧凑化（2026-09-23 用户口径）
   用户原话：「不要一个选项占用一行」「两个下拉放同一行」「留下很大的 textArea」。
   做法：字段成对进 `.field-grid`（宽屏 2 列 / 手机 1 列），每个字段自带小标题与可选说明。
   ⚠ 不用 `flex`：grid 的列宽由容器决定，输入框不会把标签挤成竖排（中文 min-content 只有一个字宽）。 */
.field-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--w-sp-3) var(--w-sp-4); align-items: start; }
.field-grid.one { grid-template-columns: minmax(0, 1fr); }
.field { min-width: 0; }
.field > label { margin: 0 0 var(--w-sp-1); }
.field > .row > * { min-width: 0; }
/* `.check-note` 自己有 `min-width: 14em`（防"一字一行"）——放进窄字段里会顶出卡片右界，这里允许收缩 */
.field .check-note { min-width: 0; flex: 1 1 auto; }
/* 字段下方的"这一项现在是什么效果"（参考说明）——服务端下发的 desc/tone/ratio/bgm 直接渲染在这里 */
.field-hint { color: var(--muted); font-size: 12px; line-height: 1.5; margin-top: 4px; }
.field-hint b { color: var(--text); font-weight: 600; }
@media (max-width: 767px) { .field-grid { grid-template-columns: minmax(0, 1fr); gap: var(--w-sp-2); } }

/* 大编辑区（改写结果）：**写字的地方就该大**。工具条给出最基本的编辑动作（撤销/重做/复制/清空/还原）+ 实时字数。 */
.editor-wrap { margin-top: var(--w-sp-2); }
.editor-bar { display: flex; align-items: center; gap: var(--w-sp-1); flex-wrap: wrap;
  margin-bottom: var(--w-sp-1); }
/* 工具条按钮**看起来紧凑但要能点**：高度不得低于 28px（small-control 判据在 390/768/1280 三档都会报） */
.editor-bar button { padding: 2px var(--w-sp-2); font-size: var(--w-fs-meta); min-height: 28px; }
.editor-bar .count { margin-left: auto; color: var(--muted); font-size: var(--w-fs-meta); }
.editor { width: 100%; min-height: 300px; line-height: 1.7; font-size: var(--w-fs-body);
  resize: vertical; }
@media (max-width: 767px) { .editor { min-height: 220px; } }

/* 每屏开头的"这一步是干什么的、产出是什么"（用户 2026-09-23：门槛太高 → 每屏先说人话，再给控件）。
   `b` 用来点出关键词（授权码 / 充值码 / 槽位），整段保持 muted 以免和正文抢注意力。 */
.lead { color: var(--muted); font-size: var(--w-fs-body); line-height: 1.7;
  margin: 0 0 var(--w-sp-2); }
.lead b { color: var(--text); font-weight: 600; }
/* 关键数字（余额 / 配对码）单独放大，一眼能看到 */
.big-num { font-size: 24px; font-weight: 700; color: var(--text); line-height: 1.4; }
.big-num .muted { font-size: var(--w-fs-meta); font-weight: 400; margin-left: var(--w-sp-1); }

/* 生成状态（流式）：贴着工具条，别抢字数的位置 */
.stream-note { font-size: var(--w-fs-meta); color: var(--muted); }
/* 历史版本：一行一版；对比是**两栏**（只在点开时展开） */
.versions { margin-top: var(--w-sp-3); }
.version-row { margin-top: 6px; }
.version-row .chip { flex: 0 0 auto; }
.version-row button.on { outline: 1px solid var(--s-color-primary); }
.compare { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--w-sp-3); margin-top: var(--w-sp-2);
  border: 1px solid var(--line); border-radius: var(--w-r-md); padding: var(--w-sp-3); }
.compare-col p { margin: 0 0 6px; line-height: 1.7; font-size: var(--w-fs-body); }
.compare-col p.only-a { color: var(--s-color-warn); text-decoration: line-through; }
.compare-col p.only-b { color: var(--ok); }
@media (max-width: 767px) { .compare { grid-template-columns: minmax(0, 1fr); } }

/* "路径卡"（①取源三条路 / ③配音两条路）：把"这一步有几种做法"直接画出来，
   而不是让用户从一个下拉 + 一个裸 file 控件里猜。`auto-fit` 让 2 张与 3 张都排得开。 */
.choice-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--w-sp-2); margin: var(--w-sp-3) 0; }
.choice { text-align: left; background: var(--panel2); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--w-r-md);
  padding: var(--w-sp-3); display: flex; flex-direction: column; gap: 4px; height: auto; }
.choice strong { font-size: var(--w-fs-body); }
.choice .muted { font-size: var(--w-fs-meta); line-height: 1.5; }
.choice.on { border-color: var(--group-voice); box-shadow: inset 0 0 0 1px var(--group-voice);
  background: color-mix(in srgb, var(--group-voice) 12%, var(--panel2)); }
@media (max-width: 767px) { .choice-row { grid-template-columns: minmax(0, 1fr); } }

/* 操作引导（③克隆：先做什么、再做什么）。带序号是因为这里**顺序**本身是信息。 */
.guide { margin: var(--w-sp-2) 0; padding-left: 18px; color: var(--muted);
  font-size: var(--w-fs-meta); line-height: 1.7; }
.guide b { color: var(--text); font-weight: 600; }
/* 朗读参考文本（音色参考用）：明确是"照着念的一段话"，与"本次文案"区分开 */
.ref-script { background: var(--panel2); border: 1px dashed var(--line);
  border-radius: var(--w-r-sm); padding: var(--w-sp-3); margin: var(--w-sp-2) 0; }
.ref-script p { margin: var(--w-sp-1) 0; line-height: 1.8; font-size: var(--w-fs-body); }
.ref-script .meta { color: var(--muted); font-size: var(--w-fs-meta); }

/* 预设（④视频剪辑）：把"一堆参数"收敛成三个**有结果的档位**，每档一句话说清产物长什么样。 */
.preset-row { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--w-sp-2); margin: var(--w-sp-2) 0; }
.preset { text-align: left; background: var(--panel2); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--w-r-md);
  padding: var(--w-sp-2) var(--w-sp-3); display: flex; flex-direction: column; gap: 3px; height: auto; }
.preset small { color: var(--muted); font-size: 11px; line-height: 1.5; }
.preset.on { border-color: var(--group-voice); box-shadow: inset 0 0 0 1px var(--group-voice); }
@media (max-width: 767px) { .preset-row { grid-template-columns: minmax(0, 1fr); } }

/* 「这一项到底改了画面什么」的**参考对象**：纯 CSS 画的示意（不依赖素材，也就不会假装有样片）。
   画幅用真实比例的方框；字幕用三条横线的位置示意。 */
.ref-visual { display: flex; gap: var(--w-sp-2); align-items: flex-end; margin-top: 6px; }
.ref-visual .ratio-box { width: 30px; border: 1px solid var(--line); border-radius: 3px;
  display: flex; align-items: flex-end; justify-content: center; font-size: 9px;
  color: var(--muted); background: var(--panel2); padding-bottom: 1px; }
.ref-visual .ratio-box.on { border-color: var(--group-voice); color: var(--text); }
.r916 { height: 53px; } .r34 { height: 40px; } .r11 { height: 30px; }
.sub-visual { display: flex; flex-direction: column; justify-content: flex-end;
  width: 44px; height: 30px; border: 1px solid var(--line); border-radius: 3px;
  background: var(--panel2); padding: 2px; gap: 2px; }
.sub-visual i { display: block; height: 3px; border-radius: 2px; background: var(--muted); }
.sub-visual.center { align-items: center; }
.sub-visual.center i { width: 60%; height: 5px; background: var(--text); }
.sub-visual.off { opacity: .45; }
/* 名词解释：默认收起（不占地方），点开才给术语对照 */
.glossary { margin-top: var(--w-sp-3); }
.glossary summary { cursor: pointer; color: var(--muted); font-size: var(--w-fs-meta); }
.glossary dl { margin: var(--w-sp-2) 0 0; font-size: var(--w-fs-meta); line-height: 1.7; }
.glossary dt { color: var(--text); font-weight: 600; margin-top: var(--w-sp-1); }
.glossary dd { margin: 0; color: var(--muted); }

/* 客服二维码：**元素无关**的方框（`<img>` 与兜底文字块共用同一个尺寸/圆角/不收缩）。
   事故（2026-09-23 用户报「联系客服样式乱了」）：这里原先只有 `img.qr` ——
   而 `/static/wechat-qr.png` 未落盘（404）时渲染的是 `div.qr.qr-fallback`，
   它**一条规则都匹配不到** → 没有尺寸、没有边框，左边一坨裸文字、右边文案列被挤到逐字换行。 */
.qr { flex: 0 0 auto; width: 148px; height: 148px; box-sizing: border-box;
      border-radius: var(--w-r-md); }
img.qr { object-fit: contain; background: #fff; }
/* 资产缺失时的兜底：明确是"占位框"，不是坏掉的图片（虚框 + 居中提示 + 不抢注意力） */
.qr-fallback { display: flex; flex-direction: column; align-items: center; justify-content: center;
      gap: 4px; padding: 10px; text-align: center; font-size: 12px; line-height: 1.5;
      color: var(--muted); background: var(--panel2); border: 1px dashed var(--line); }
/* 客服卡的行：二维码固定、文案列吃剩余宽度；`min-width:0` + `overflow-wrap` 让长客服号可折行而不撑破 */
.contact-row { align-items: flex-start; }
.contact-row > .contact-text { flex: 1 1 220px; min-width: 0; overflow-wrap: anywhere; }
@media (max-width: 480px) {
  /* 窄屏给文案让位：148px 的方框 + 文案在 390 宽下只剩 ~200px，会把「客服微信：superip-service」挤成多行 */
  .qr { width: 116px; height: 116px; }
}

/* 「下一步」提示一键跳转后的定位高亮（`gotoAnchor` 会临时给目标卡加 .flash）。
   没有这条规则时"跳过去"没有任何视觉反馈 —— 用户不知道落在了哪张卡上（2026-10 审查实测）。 */
.card.flash { animation: card-flash 1.2s ease-out 1; }
@keyframes card-flash {
  0%   { box-shadow: 0 0 0 2px var(--accent); }
  60%  { box-shadow: 0 0 0 2px var(--accent); }
  100% { box-shadow: 0 0 0 2px rgba(0, 0, 0, 0); }
}

/* 危险操作二次确认（解绑 / 删除）：最小对话框，不用 window.confirm */
.modal-mask {
  position: fixed;
  inset: 0;
  background: rgba(8, 10, 20, 0.68);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  z-index: 100;
}
.modal { max-width: 420px; width: 100%; }
.modal h3 { margin-bottom: 8px; }


/* ============================================================
   r10-mobile（D-37）：手机/平板断点 + 步骤向导 + 安全区
   规则：端差异只出现在这里（布局）与端能力矩阵（能力），业务组件不含 isMobile 分支。
   ============================================================ */
:root {
  /* 壳内/全面屏：底部导航避让 */
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-t: env(safe-area-inset-top, 0px);
}
/* Android 15（targetSdk 35）**强制 edge-to-edge**：`StatusBar.overlaysWebView=false` 被系统忽略，
   而 **Android WebView 不提供 `env(safe-area-inset-*)`**（那是 iOS 的能力）→ `--safe-t` 恒为 0，
   状态栏会压住顶栏。壳入口在 Android 上给 `<html>` 加 `.plat-android`，这里补一个兜底高度。
   口径：**只覆盖变量**，消费方（`.topbar` 等）不动；真实高度需要原生探针，见 D-84 待办。 */
.plat-android { --safe-t: 24px; }

/* 触摸细节：iOS 输入聚焦不缩放（font-size < 16px 会触发自动放大）；点按目标 ≥44px */
@media (max-width: 767px) {
  input, select, textarea { font-size: 16px; }
  button { min-height: 44px; }
  .topbar { padding-top: calc(8px + var(--safe-t)); }
  /* 单列：桌面网格在手机上一屏一卡（配合向导 v-show） */
  .grid { display: block; }

  /* 移动优先重构：**不再把每一步装进一张同款圆角卡片** —— 那套"SaaS 卡片套件"
     （相同圆角 + 相同软阴影 + 每块都是白底矩形）是模板感最重的来源。
     改为**结构线 + 明度层级**：分区靠一条发丝线，层级靠底色的细微差别。 */
  .grid > .card {
    background: transparent;
    border: 0;
    border-top: 1px solid var(--line);
    border-radius: 0;
    padding: var(--w-sp-4) 0 0;
    margin-bottom: 0;
  }
  .grid > .card:first-of-type { border-top: 0; padding-top: var(--w-sp-1); }
  .card h3 { font-size: var(--w-fs-title); margin-bottom: var(--w-sp-3); }
  .wrap { padding-bottom: calc(84px + var(--safe-b)); }   /* 给底部动作栏留位 */
}

/* 平板：两列（保持桌面语义，仅收紧间距） */
@media (min-width: 768px) and (max-width: 1179px) {
  .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .wrap { padding-bottom: calc(24px + var(--safe-b)); }
}

/* ---------------------------------------------------------------- 主流程外壳
   两个固定的"框"：**顶部进度轨**（在哪儿）+ **底部动作栏**（下一步做什么）。
   移走原先的底部步骤条 —— 进度属于"位置信息"，钉在顶上才不会随手势跑掉；
   底部留给动作。这是本轮交互上的主要改动。 */

/* 顶部进度轨：吸顶、可横滑；**每一步都带名字**（只有数字要用户去记，名字不用） */
.wizard-nav {
  position: sticky;
  top: 0;
  z-index: 50;
  margin: 0 0 var(--w-sp-3);
  padding: var(--w-sp-2) 0;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}
/* 轨道**分组**（2026-09-23 用户要求：明确分成「口播视频」与「发布伴侣」两组，默认展开）。
   组头是**可点的折叠标题**（caret 旋转 90° 表示展开）；移动档上它退化成一行小标签，
   整条轨道仍然横向滚动（`.steps` 已有 overflow-x）。 */
.nav-group { display: flex; flex-direction: column; }
.nav-group + .nav-group { margin-top: var(--w-sp-3); }
/* 两组**用不同的配色**做区分（2026-09-23 用户要求）：口播视频、发布伴侣各自一色。
   色值只在这里声明一次，组头/色条/当前项三处共用。
   ⚠ 2026-09-23 用户反馈「紫色太暗」→ 口播组改用**亮蓝**（下方 `--group-voice` 一处可改）。
   账户 = 琥珀（`--accent`），三色（亮蓝 / 青绿 / 琥珀）在暗底上互不混淆。 */
:root { --group-voice: #4dabf7; --group-companion: var(--ok); --group-account: var(--accent); }
.nav-group[data-group="voice"] { --group-color: var(--group-voice); }
.nav-group[data-group="companion"] { --group-color: var(--group-companion); }
/* 组头：**比步条目（14px）大一号**（16px 标题号）+ 加粗 + 分组色 + 左侧色条。
   `-soft` 用 color-mix 派生一层淡色底，避免再手抄一遍色值。 */
.nav-group-head {
  display: flex; align-items: center; gap: 6px; width: 100%;
  background: color-mix(in srgb, var(--group-color, var(--muted)) 14%, transparent);
  border: 0; border-left: 3px solid var(--group-color, var(--muted));
  border-radius: var(--w-r-sm);
  cursor: pointer;
  color: var(--group-color, var(--muted));
  font-size: var(--w-fs-title); font-weight: 700; letter-spacing: .01em;
  /* 组头是**可点控件** → 点按目标不能只有 25px（审查判据 small-control 实测报过） */
  min-height: 36px;
  padding: var(--w-sp-2) var(--w-sp-3); text-align: left;
}
.nav-group-head:hover { filter: brightness(1.15); }
.nav-group-head .caret { display: inline-block; font-size: 11px; transition: transform .15s ease; }
.nav-group-head .caret.open { transform: rotate(90deg); }
/* 组内条目跟随分组色：当前项的左强调条用**本组颜色**（口播紫 / 伴侣绿），
   悬停时也染一点分组色 —— 一眼能看出"这一项属于哪一组"。 */
/* ⚠ 必须带 `.wrap.pc` 前缀：`.wrap.pc .step-btn.active` 在后面（同特异度、后写者胜），
   不抬特异度的话当前项的强调条仍然是全局的 accent 黄（实测过） */
.wrap.pc .nav-group .step-btn.active { border-left-color: var(--group-color); }
.nav-group .step-btn:hover { color: var(--text); border-left-color: color-mix(in srgb, var(--group-color, var(--muted)) 55%, transparent); }
@media (prefers-reduced-motion: reduce) { .nav-group-head .caret { transition: none; } }

.wizard-nav .steps {
  display: flex; gap: var(--w-sp-1); align-items: center;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.wizard-nav .steps::-webkit-scrollbar { display: none; }
.wizard-nav .step-btn {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: 6px;
  min-width: 36px; height: 36px;
  border-radius: 999px;
  background: var(--panel2);
  color: var(--muted);
  padding: 0 10px;
  font-size: var(--w-fs-meta);
}
/* 只有**当前步**展开显示名字，其余留数字 —— 宽 390 放不下 5 个中文名，
   而"当前这一步叫什么"才是真正要回答的问题。 */
.wizard-nav .step-btn .lb { display: none; white-space: nowrap; }
/* 当前项用**本组颜色**（口播紫 / 伴侣绿）：分组配色的意义就是"一眼看出这一项属于哪一组"。
   移动档的当前步是**实心胶囊**（桌面档另行覆盖成 `--panel` 底 + 左色条）。
   渐变第二段由本组色 `color-mix` 出浅色（而不是写死 `--s-color-primary-soft`）——
   写死的话"伴侣组"的当前步会是"绿→紫"的错色。 */
.wizard-nav .step-btn.active {
  background: linear-gradient(135deg, var(--group-color, var(--s-color-primary)),
              color-mix(in srgb, var(--group-color, var(--s-color-primary)) 55%, #fff));
  color: #fff;
}
.wizard-nav .step-btn.active .lb { display: inline; }
/* D-82：主流程只有 5 个编号步（`STEPS` 单一数据源）；「账户」（计费说明/软件授权）**不在**这一步号
   序列里 —— 靠一个词而非数字、虚线分隔、自己的配色，一眼能看出它不是"第 0 步"。

   ⚠ 这套配色（16px/700 + 琥珀 + 3px 左色条 + 淡色底）**必须写在这里、不能只写在 `.wrap.pc` 里**：
   桌面档先写了一份 `.wrap.pc .step-btn.step-btn-account`，移动档只落到旧的 12px 灰白虚线小胶囊
   —— 三色体系（口播紫 / 伴侣绿 / 账户琥珀）在手机上看不到（2026-09-23 用户要求"移动端也要同步"）。
   两端共用这一条，布局差异（靠右 / 独占一行）留给各自的断点。 */
.wizard-nav .step-btn.step-btn-account {
  --group-color: var(--group-account);
  justify-content: flex-start;
  margin-left: auto;
  padding: var(--w-sp-2) var(--w-sp-3);
  border: 0; border-left: 3px solid var(--group-account);
  border-radius: var(--w-r-sm);
  background: color-mix(in srgb, var(--group-account) 12%, transparent);
  color: var(--group-account);
  font-size: var(--w-fs-title); font-weight: 700;
  min-height: 36px;
}
.wizard-nav .step-btn.step-btn-account.active {
  color: var(--text);
  background: color-mix(in srgb, var(--group-account) 22%, transparent);
  border-left-color: var(--group-account);
}
.wizard-nav .step-btn.step-btn-account:hover { filter: brightness(1.15); }

/* 底部动作栏：一步只有**一个**主要动作 */
.actionbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 50;
  display: flex; align-items: center; gap: var(--w-sp-2);
  padding: var(--w-sp-2) var(--w-sp-3) calc(var(--w-sp-2) + var(--safe-b));
  background: var(--bg);
  border-top: 1px solid var(--line);
}
.actionbar .step-name {
  flex: 1; text-align: center;
  font-size: var(--w-fs-meta); color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ---------------------------------------------------------------- 舞台（本步的主角）
   口播这一行，主角是**稿子**：所以稿件用提词器字号占据主屏，空的时候它自己就是一句邀请。
   它不是"输入框"而是一页稿纸 —— 去掉盒子，只留一条左侧竖线；**键盘聚焦**时竖线亮起
   （保留全局 `:focus-visible` 焦点环，不做 outline:none —— 那会毁掉键盘可见性）。 */
.stage-script {
  margin-top: var(--w-sp-3);
  min-height: 38vh;
  background: transparent;
  border: 0;
  border-left: 2px solid var(--line);
  border-radius: 0;
  padding: var(--w-sp-1) var(--w-sp-3);
  font-size: var(--w-fs-display);
  line-height: 1.7;
  letter-spacing: -.01em;
}
.stage-script:focus-visible { border-left-color: var(--accent); }
/* 空状态是**一句邀请**，不是正文：占位文字用小一号的正文大小 + muted。
   否则提词器字号会把"还没有稿子"这件事喊得比稿子本身还响。 */
.stage-script::placeholder { font-size: var(--w-fs-body); color: var(--muted); }
.stage-tools { margin-top: var(--w-sp-2); }
/* 取源行：一行搞定（输入占满剩余宽度；按钮**不许被挤到换行** —— "粘贴"曾竖成两行） */
.source-row { flex-wrap: nowrap; }
.source-row input { flex: 1 1 auto; min-width: 0; }
.source-row button { flex: 0 0 auto; white-space: nowrap; }
/* 动作栏主按钮：占满剩余宽度（拇指区的唯一重音） */
.actionbar-main { flex: 1 1 auto; }

@media (max-width: 767px) {
  /* 手机上没有"拖拽改高度"这回事，露出 resize 手柄只会像渲染瑕疵 */
  .stage-script { resize: none; }
  /* 给稿纸左侧那条竖线留一点呼吸位，否则它贴在屏幕边缘像画歪了 */
  .grid > .card { padding-left: var(--w-sp-1); padding-right: var(--w-sp-1); }
  /* 移动档顶栏的「余额」chip 是**账户屏的入口**（手机上唯一能进账户的方式之一）：
     原来继承 `button.chip { padding: 2px 10px }` → 高约 20px，点按目标偏小
     （2026-09-23 样式审查实测 < 28px）。这里把它撑到 32px，外观仍是 chip。
     ⚠ 必须写成 `.topbar button.chip`：基础规则 `button.chip` 在本文件里**更靠后**，
     同特异度下后写者胜 —— 早先写成 `button.chip` 时这条被静默覆盖（复测仍报 21px）。 */
  .topbar button.chip { min-height: 32px; padding: 6px 12px; }
  /* 「账户」在移动档**独占一行**（与两个分组头同级）：12px 的靠右小胶囊在 390 宽上像个"漏掉的按钮"。
     虚线分隔沿用桌面语义 —— 它不属于上面那两条链。 */
  .wizard-nav .step-btn.step-btn-account {
    width: 100%;
    margin: var(--w-sp-2) 0 0;
    border-top: 1px dashed var(--line);
  }
  /* 账户屏的「入口与模式」chips：中文的 `min-content` 只有一个字宽 → 不锁住就被压成"半句换行"
     （2026-09-23 实测：`…当前可联系` / `客服获取` 断成两行，看着像标签框没包住文字）。
     锁住后由容器 `flex-wrap` 在**chip 之间**换行。 */
  .account-bar .modes > .chip { flex: 0 0 auto; white-space: nowrap; }
}

/* 顶栏：**极简一行**（品牌 + 余额）。它此前完全没有样式 —— 几个块级 div 自然换行成三行，
   把最上面 ~120px 花在了"Excel 助手 / 手动 / 自动：需在电脑上…（尚未开放）"这类说明文字上，
   而每一步真正要用的东西被挤到屏幕中间。入口与模式说明移进「账户」屏（那里才是它们的位置）。 */
.topbar {
  display: flex;
  align-items: center;
  gap: var(--w-sp-2);
  /* ⚠ 这里**必须**带上 --safe-t：本规则定义在下面的移动断点之后，会把断点里的
     `padding-top` 整条盖掉。2026-09-22 Android 真机实测：漏掉它 → 状态栏直接压住品牌与余额
     （截图 /tmp/and_r3_step1.png），返工一次。
     左右内边距同样不能是 0（2026-09-23 用户报"品牌文字太靠左边、没有 margin"）：
     取 `--w-sp-3` 让品牌与左侧轨道的**文字**起点对齐（轨道按钮自身也是 12px 内边距）。 */
  padding: calc(var(--w-sp-2) + var(--safe-t)) var(--w-sp-3) var(--w-sp-2);
  border-bottom: 1px solid var(--line);
}
.topbar .brand { font-size: var(--w-fs-body); font-weight: 600; white-space: nowrap; }
.topbar .modes {
  display: flex; align-items: center; gap: var(--w-sp-1);
  flex: 1 1 auto; min-width: 0;
  overflow-x: auto; scrollbar-width: none;
}
.topbar .modes::-webkit-scrollbar { display: none; }
/* 这一行是**横向滚动**的（上面 `overflow-x:auto`），所以 chip 不许收缩、不许换行：
   中文的 `min-content` 只有**一个字**宽 → 不锁的话那条长说明 chip 会被压成两行，
   第二行从整行最左边开始（看起来像串到别的 chip 下面），而滚动条又不会出现（2026-09-23
   样式审查在移动档顶栏实测）。锁住后：单行、超出部分靠横向滚动看。 */
.topbar .modes > .chip { flex: 0 0 auto; white-space: nowrap; }
.topbar .acct { display: flex; align-items: center; gap: var(--w-sp-1); margin-left: auto; }
/* 右侧这几项都是**短标签**（昵称 / 配额 / 余额 / 退出），不许被压窄：
   中文的 `min-content` 只有一个字宽 → 768 宽实测昵称「用户5600」被压成 38×44px（逐字换行、两行高）。
   让位交给左侧可滚动的 `.modes`（它有 `overflow-x:auto`）。 */
.topbar .acct > span, .topbar .acct > button { flex: 0 0 auto; white-space: nowrap; }
/* 余额做成可点的 chip（移动档 → 账户屏）；`.chip` 是类选择器，会盖掉 `button` 的渐变底 */
button.chip { min-height: 0; height: auto; padding: 2px 10px; font-size: var(--w-fs-meta); }

/* 账户屏的账号区：身份 + 退出 + 入口与模式（移动档） */
.account-bar { padding: var(--w-sp-1) 0 var(--w-sp-2); }
.account-bar .modes { flex-wrap: wrap; overflow: visible; margin-top: var(--w-sp-1); }

/* ---------------------------------------------------------------- 焦点可见性 + 动效降级
   设计规范专项全仓普查（6 份全局样式入口）：原先 **0 处** `:focus-visible`、
   **0 处** `prefers-reduced-motion` —— 键盘用户没有任何焦点反馈，前庭敏感用户关不掉动画。
   焦点环用 --s-focus-ring（暗底上是 --accent，对比度实测 11.18；主色只有 4.86）。 */
:focus-visible { outline: 2px solid var(--s-focus-ring); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    /* 用极短时长而不是 animation:none —— 后者会让依赖 animationend 的逻辑收不到事件 */
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------------------------------------------------------------- `/pc` 桌面外壳
   `/app` 与 `/pc` **共用同一套业务与步骤内容**，只换外壳（这是拆入口的全部意义）：
     · `/app` = 顶部进度轨 + 底部动作栏（拇指优先）
     · `/pc`  = **左侧竖排轨道 + 内容区**（鼠标/大屏优先；第三栏"检查器"要等把参数从
       各步内容里抽出来，属下一轮）
   做法上**只动 CSS 的排布**，不重排 DOM —— DOM 顺序一致，两端才不容易漂移。 */
.wrap.pc {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr);
  gap: 0 var(--w-sp-5);
  align-items: start;
}
.wrap.pc .topbar { grid-column: 1 / -1; }
.wrap.pc .wizard-nav {
  grid-column: 1;
  position: sticky;
  top: 0;
  margin: 0;
  padding: var(--w-sp-3) 0;
  border-bottom: 0;
  border-right: 1px solid var(--line);
  background: transparent;
}
/* 除顶栏、轨道、**检查器**外，其余内容一律进第二栏（顺序不变）。
   把 `.inspector` 显式排除，好过靠拼特异度去压它 —— 后者极易算错
   （2026-09-22 实测：`.wrap.pc > aside.inspector` 是 (0,3,1)，**输给**本规则的 (0,4,0)，
    结果检查器被按进第二栏、**压在舞台上**）。 */
/* ⚠ `:not(.segbar)` 不能省：本条是 (0,4,0)，比下面 `.wrap.pc > .segbar` 的 (0,3,0) 更高 ——
   漏掉它，小步进条会被压回单列（实测：有检查器时 tab 只有 665px 宽，正是用户报的"占比会变"）。
   这与本文件里 `.inspector` 当初被压进第二栏是同一个坑（注释在下面那条规则上方）。 */
.wrap.pc > *:not(.topbar):not(.wizard-nav):not(.inspector):not(.segbar) { grid-column: 2; }
/* 竖排轨道：桌面有地方 → **每一步都显示名字**（不是只有当前步） */
.wrap.pc .wizard-nav .steps {
  flex-direction: column; align-items: stretch; gap: 2px; overflow: visible;
}
.wrap.pc .step-btn {
  justify-content: flex-start;
  height: 40px; min-width: 0;
  background: transparent; border-radius: var(--w-r-sm);
  padding: 0 var(--w-sp-3);
  font-size: var(--w-fs-body);
}
.wrap.pc .step-btn .lb { display: inline; }
.wrap.pc .step-btn.active { background: var(--panel); color: var(--text); }
/* 「账户」= **序列之外的第三个入口**（计费/授权），与两个分组头同级：更大字号 + 自己的配色（琥珀 accent）。
   底部那条虚线分隔表示"它不属于上面两条链"（口播紫 / 伴侣绿 / 账户琥珀，三色一眼可分）。 */
.wrap.pc .step-btn.step-btn-account {
  --group-color: var(--group-account);
  margin: var(--w-sp-3) 0 0;
  border: 0; border-top: 1px dashed var(--line);
  border-left: 3px solid var(--group-account);
  border-radius: var(--w-r-sm);
  padding: var(--w-sp-2) var(--w-sp-3);
  font-size: var(--w-fs-title); font-weight: 700;
  color: var(--group-account);
  min-height: 36px;
  background: color-mix(in srgb, var(--group-account) 12%, transparent);
}
.wrap.pc .step-btn.step-btn-account:hover { filter: brightness(1.15); }
.wrap.pc .step-btn.step-btn-account.active {
  color: var(--text);
  background: color-mix(in srgb, var(--group-account) 22%, transparent);
  border-left-color: var(--group-account);
  /* ⚠ 必须重申 700：`.wrap.pc .step-btn.active`（同特异度、在本文件更靠后）会把字重压成 600 ——
     账户入口一旦是当前项就"瘦"一圈（把配色提到共用规则后实测到）。 */
  font-weight: 700;
}
/* **舞台进内容栏第一位**：DOM 顺序是"伴侣卡 → 计费/授权 → 主流程"（在 /app 下无所谓，
   那些卡被步骤门控藏着），但 /pc 全同屏就暴露了 —— 真正要看的当前步骤被挤到下面。
   ⚠ 这里**不能用 `order`**：`order` 作用于整个网格的放置序列，会把顶栏也挤到舞台下面
   （2026-09-22 实测：顶栏跑到页面中间）。改用**显式行列**定位，意图更明确也不会误伤。 */
/* 行分配（2026-09-23，用户要求"tab 宽度不变 + 配置项落在 tab 之下"）：
   ① 顶栏 ｜ ② ④ 的小步进条（横跨舞台+检查器）｜ ③ 舞台 + 检查器 + **左侧轨道**。
   轨道放第 3 行（与舞台**同排**）是关键：轨道有 282px 高，若它独占第 2 行，就会把第 3 行
   的内容顶到 y=335（顶栏只有 53px）—— 实测过一次"②③⑤ 步全被推下去 282px"的空白带。 */
.wrap.pc .topbar { grid-row: 1; }
/* 轨道**跨第 ②③ 行**（`span 2`）：它的顶边因此永远落在第 2 行的起点（顶栏正下方），
   **不会因为"这一步有没有小步进条"而整体上下移动** —— 用户报的"左侧菜单有一次整体下移的跳动"
   就是它此前只占第 3 行：④ 成片出现小步进条（第 2 行）时，整条轨道被推到 y=117；切回别的步又跳回 y=53。
   跨行后：轨道始终从 y=53 开始；小步进条仍占第 2 行、舞台与检查器在第 3 行（tab 之下）。
   ⚠ 用 `span 2` 而不是 `2 / -1`：没有显式行模板时 `-1` 指向显式网格的末线（只有 1 行），语义不明确。 */
.wrap.pc .wizard-nav { grid-row: 2; }
/* 账户屏没有小步进条：轨道回到第 2 行，账户卡也从第 2 行开始（否则首卡会被推到轨道下面） */
/* 账户屏：卡片是自动排行的多行（第 2..N 行）→ 轨道要**跨过这些行**，否则它的高度会把第 2 张卡
   顶到它下面（实测过 282px 的空白）；跨行后高度由所有卡片共同吸收 */
.wrap.pc.acct .wizard-nav { grid-row: 2 / span 8; }
/* ④ 的小步进条：**横跨「舞台 + 检查器」两列**、单独占一行（第 2 行），于是
   ① 它的宽度不随"本段有没有检查器"变化（有/无检查器都是整条内容宽）；
   ② 右侧检查器被排到它**下面**（第 3 行），而不是并排。
   （用户 2026-09-23 明确要求：tab 占比不要变、配置项落在 tab 之下。） */
/* （小步进条已移入 `main.grid` 的子栅格，见上面的 `.wrap.pc main.grid > .segbar`；
   旧的外层规则删除：外层定位会让它只占舞台列 / 或把轨道所在的行推下去。） */
/* 舞台与检查器都放到第 3 行：没有 segbar 的步骤里第 2 行是**空行**（高 0、行间距 0）→
   不会像账户屏那样顶出一段空白（那条是"空 main 占行 + 轨道 282px 撑高"，另一回事）。 */
/* 舞台顶部的留白与 `④ 的小步进条` **同一份**（`--w-sp-4`）：每一屏的第一个内容元素落在同一条
   水平线上（否则"有 tab 的屏"首元素在 69px、"没 tab 的屏"直接贴顶栏 53px —— 用户报"有的太靠顶部"）。 */
.wrap.pc main.grid {
  grid-column: 2; grid-row: 2;
  margin-top: var(--w-sp-4);
  /* 舞台自己是一块**子栅格**：小步进条（第 1 行，跨两列）+ 舞台/检查器（第 2 行，各占一列）。
     这样 tab 的宽度恒为"整块内容宽"，检查器落在 tab **下面**，而外层行不受影响（轨道不跳）。 */
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  /* 三行：① 小步进条 ② 提示横幅（自动放置，落在 ①/② 行） ③ 舞台 + 检查器。
     舞台卡**显式钉在第 3 行**：不钉的话（auto 行）没有小步进条时它会落到第 1 行，
     而检查器钉在第 2 行 → 检查器被顶到卡片**下面**（实测 y=334，本会话踩过）。 */
  grid-template-rows: auto auto auto;
  row-gap: 0;                 /* 继承来的 14px 行距会让空行也占位（+28px 偏移） */
  align-content: start;
}
.wrap.pc.has-inspector main.grid { grid-template-columns: minmax(0, 1fr) 320px; column-gap: var(--w-sp-5); }
.wrap.pc main.grid > .segbar { grid-column: 1 / -1; grid-row: 1; }
/* 步骤卡与"提示横幅"都在**舞台列**；横幅横跨整块（第 2 行），不与检查器抢同一格 */
.wrap.pc main.grid > section.card, .wrap.pc main.grid > div.card { grid-column: 1; grid-row: 3; }
.wrap.pc main.grid > .span-stage { grid-column: 1 / -1; }
/* 非 /pc（宽屏浏览器 /app）的横幅仍横跨整行 */
.span-stage { grid-column: 1 / -1; }
/* 账户屏没有 main（`display:none`），首张卡自己带上同样的留白 */
.wrap.pc.acct > section.card:first-of-type { margin-top: var(--w-sp-4); }
/* 账户屏（step 0）没有步骤内容：`main.grid` 里的每一步卡片都是 `v-show` 隐藏的（DOM 仍在），
   于是这个 h=0 的 main 仍占着第 2 行，而第 2 行高度由左侧轨道（282px）决定
   → **账户卡被整块推下去，头顶留一大片空白**（2026-09-23 实测：首卡 y=335，顶栏只有 53px）。
   账户屏直接让空 main 不参与布局（`.acct` 由模板按 `step === 0` 打上）。
   ⚠ 不用 `:has(> *)` 判断"空"：里面的卡片是 `v-show`，DOM 里**有**元素，只是 display:none。 */
.wrap.pc.acct main.grid { display: none; }
/* 计费/授权这类"账户向"卡片自然排在主流程之后的行里（grid-column 由上面的通配规则给） */

/* 动作栏留在内容栏（不横跨整窗），并吸在内容底部。
   ⚠ 必须给**显式行号**（第 4 行 = 舞台那一行之后）：它 `v-if` 在 DOM 里排在 `main.grid` 之后，
   但 `main.grid` 有显式行号 → 自动放置会把动作栏塞进**第 2 行**（实测 y=53），
   于是第 2 行被撑高 53px，舞台整体下移（③⑤ 步内容从 y=53 掉到 y=106）。 */
.wrap.pc .actionbar {
  grid-column: 2;
  grid-row: 4;
  position: sticky;
  bottom: 0;
}
/* 账户屏的卡片是自动排行的（占了 2..N 行）→ 动作栏不能钉在第 4 行，否则会与某张卡重叠 */
.wrap.pc.acct .actionbar { grid-row: auto; }
/* `/pc` 轨道的**当前步**必须一眼可辨（5 步流程里"我在哪"是首要信息）。
   用左侧强调条 + 底色 + 字重三重区分；强调条在未选中时占位为透明，避免选中时抖动。
   颜色取 `--accent`（暗底对比度 11.18，见令牌注释里的实测值），不用主色（只有 4.86）。 */
.wrap.pc .step-btn { border-left: 2px solid transparent; }
.wrap.pc .step-btn.active {
  background: var(--panel);
  color: var(--text);
  border-left-color: var(--accent);
  font-weight: 600;
}

/* ---------------------------------------------------------------- 底部抽屉
   手机上一屏只留 1–2 个关键参数；其余进抽屉。抽屉是**用户触发**的动效（回应用户动作），
   非用户触发的动效全仓只有主动作那一处。`prefers-reduced-motion` 由全局规则统一降级。 */
.sheet-mask {
  position: fixed; inset: 0; z-index: 90;
  background: rgba(8, 10, 20, .55);
  display: flex; align-items: flex-end;
}
.sheet {
  width: 100%;
  background: var(--panel);
  border-top: 1px solid var(--line);
  border-radius: var(--w-r-lg) var(--w-r-lg) 0 0;
  padding: var(--w-sp-4) var(--w-sp-4) calc(var(--w-sp-4) + var(--safe-b));
  animation: sheet-up var(--w-dur) var(--w-ease) 1;
}
@keyframes sheet-up {
  from { transform: translateY(12px); opacity: .6; }
  to   { transform: none; opacity: 1; }
}

/* ---------------------------------------------------------------- `/pc` 第三栏：检查器
   三栏 = 轨道 ｜ 舞台 ｜ **本步参数**。只在有重参数的步骤出现（③ 配音 / ④ 剪辑），
   所以列数由 `has-inspector` 决定 —— 没有参数的步骤不留一条空列。
   ⚠ 上方通配规则已**显式排除** `.inspector`；这里不再依赖特异度高低（曾算错并把检查器压到舞台上）。 */
/* ⚠ 检查器**不再由外层栅格开第三列**（2026-09-23 改为 `main.grid` 内部的子栅格）：
   这样"舞台 + 检查器"是一块整体，它和左侧轨道**同处第 2 行** —— 小步进条出现/消失只改这块内部的行，
   不会把轨道整体推下去（用户报的"左侧菜单整体下移的跳动"）。
   旧写法（外层三列 + 检查器占第 3 列）会让轨道所在的行随 tab 变化而移动。 */

/* ⚠ 选择器跟着**位置**走：检查器现在在 `main.grid` 的子栅格里（第 2 列、与舞台同一行），
   不再是 `.wrap` 的直接子元素 —— 选择器不改的话这整块样式会静默失效（实测：左分隔线、
   右内边距、视觉中线补正全丢，标题比舞台标题高 19px）。 */
.wrap.pc main.grid > aside.inspector {
  grid-column: 2;
  grid-row: 3;
  position: sticky;
  top: 0;
  /* 顶部与舞台**同一份留白 + 同一份内边距**（2026-09-23 用户报"配音参数顶部 margin 不一致"）：
     实测改前检查器盒顶在 y=53（紧贴顶栏），而舞台卡片在 y=69、卡内标题在 y=86 ——
     检查器比舞台高 16px、标题高 33px。现在：外边距取舞台同一份 `--w-sp-4`，再补一份内边距
     对齐卡片的 `padding`（卡片 16px + 1px 边框），使**两栏第一行文字落在同一水平线**。 */
  /* +3px 不是魔法数：舞台卡有 1px 上边框、且它的标题是 16px（行盒 22px）而这里是 12px（行盒 17px）
     —— 只把**盒顶**对齐（各 69px）时，两栏首行的**视觉中线**仍差 ~3px（实测 94 vs 97、158 vs 161），
     用户看得出来"配音参数/剪辑参数顶部 margin 不一致"。这里补上这 3px，让**中线**对齐。 */
  padding-top: calc(var(--w-sp-4) + 3px);
  /* 左右都留内边距：**右内边距不是装饰** —— 检查器贴着窗口右缘时，内容盒正好顶到 1280，
     任何自带 2px 外边距的控件（`input[type=range]` 的 UA 边距）都会把整页顶出横向滚动条
     （2026-09-23 实测 ③ 配音：`scrollWidth=1284 > 1280`，右侧滑块被切掉 2px）。 */
  padding-left: var(--w-sp-4);
  padding-right: var(--w-sp-4);
  border-left: 1px solid var(--line);
}
/* 表单控件统一到"盒模型一致"：range 的 UA 外边距（2px）是上面那条溢出的直接来源 */
.wrap.pc .inspector input[type="range"] { margin: 0; }
.wrap.pc .inspector-title {
  font-size: var(--w-fs-meta);
  color: var(--muted);
  margin-bottom: var(--w-sp-2);
}

/* `/pc` 上「更多参数」是**多余**的：那批参数已经常驻在第三栏（检查器）里，
   再点开一个内容相同的抽屉只会让人以为还有别的设置。它是移动档的交互，桌面隐藏。
   （注意：移动档的 `upload-direct` e2e 依赖这个按钮，而它跑在 `/index.html`（无 .pc）→ 不受影响。） */
.wrap.pc [data-testid="open-voice-sheet"],
.wrap.pc [data-testid="open-clip-sheet"] { display: none; }


/* ④ 成片的小步进切换条：一次只给一段（生成 / 剪辑 / 封面）。
   它属于"大步内的小步"，所以**不改进度轨**（仍是 5 步，D-82 不动）。
   ⚠ 它是 `.wrap` 的直接子元素（不是 `.grid` 的子元素）—— 旧的 `.grid > .segbar { grid-column: 1/-1 }`
   已失效并删除：那写法只能横跨**舞台列**，于是"这一段有没有检查器"会让 tab 忽宽忽窄
   （用户 2026-09-23 报：tab 宽度占比会变；/pc 上的定位见上方的 `.wrap.pc > .segbar`）。 */
.segbar { display: flex; gap: var(--w-sp-1); margin-bottom: var(--w-sp-3); }
.seg {
  flex: 1 1 auto; min-height: 36px;
  background: var(--panel2); color: var(--muted);
  border-radius: var(--w-r-sm); padding: 0 var(--w-sp-3);
  font-size: var(--w-fs-meta);
}
.seg.active {
  background: var(--panel); color: var(--text); font-weight: 600;
  box-shadow: inset 0 -2px 0 var(--accent);
}

/* 原生 file input 的标签会被省略号截断 —— 实测移动端**常态**显示「未选...何文件」
   （2026-09-22：8 次连拍里 6 次如此；被右侧按钮挤窄导致原生 label 触发 ellipsis）。
   窄屏让它**独占整行**（`.row` 本就 flex-wrap），标签就有足够宽度。 */

/* ③ 样本上传框：原生 file 控件的标签在 220px 下会被 Chrome 省略号截断
   （实测「未选...何文件」）。窄屏让它独占整行 → 标签有足够宽度。
   注意：该 input 原先带内联 `max-width:220px`，会压住本条 —— 已移除。 */
@media (max-width: 767px) {
  input[type="file"] { flex: 1 1 100%; min-width: 0; }
}

/* 登录/激活卡：**居中**展示（水平居中 + 上方留白），描述文案放**中下部**并给足行距。
   起因（2026-09-22 使用者反馈）：登录窗口没有居中、描述类文字位置局促。 */
.auth {
  max-width: 560px;
  margin: 0 auto;          /* 水平居中；垂直居中由 .auth-gate 的 flex + margin:auto 完成 */
  padding: var(--w-sp-5);
}
/* 门禁态（登录/启动）：让卡片在**顶栏以下的剩余空间**里垂直+水平居中。
   之前只写了 `margin: 32px auto 0` → 卡片顶在上方，使用者反馈"位置不够居中"。 */
/* 门禁态：卡片**在视口正中**（不依赖容器布局）。
   为什么不用"容器内居中"：卡片居中于 `.wrap` 时，顶栏、`.pc` 栅格残留、滚动条留白都会让
   观感偏一点（使用者两次反馈"没居中"）。这里让顶栏脱离文档流，卡片用 grid 居中于**视口** ——
   无论窗口多宽、容器有什么，卡片都严格居中。 */
.wrap.auth-gate { display: grid; place-items: center; min-height: 100vh; }
.wrap.auth-gate > .topbar { position: absolute; top: 0; left: 0; right: 0; }
.wrap.auth-gate > .auth { margin: 0; }
/* ⚠ 关键：`.wrap.pc > *:not(...) { grid-column: 2 }` 那条老规则会把登录卡塞进**第 2 列**
   （左侧留出 232px 轨道列）→ 卡片**右偏 121px**，看起来就是"没有水平居中"。
   登录态本来没有轨道，所以这里把 `.pc` 的两列结构一并取消。 */
/* 用 flex + align-items:center 做水平居中（grid 在 `.pc` 残留规则下不可靠，实测会贴右）。
   这一组选择器特异度 (0,4,0) 且位置在最后 → 稳定压掉 `.wrap.pc{display:grid}` 与
   `.wrap.pc > *:not(...) { grid-column: 2 }`。 */
.wrap.pc.auth-gate { display: flex; flex-direction: column; align-items: center;
                     justify-content: center; min-height: 100vh; grid-template-columns: none; }
.wrap.pc.auth-gate > .topbar { position: absolute; top: 0; left: 0; right: 0; }
.wrap.pc.auth-gate > .auth { grid-column: auto; margin: 0; width: 100%; max-width: 560px; }
/* 标题与副标题**上下分行**：`justify-content:center` 会让两者粘成一行
   （实测渲染成「登录 / 注册授权制软件：…」）—— 副标题落在标题下方，正合"描述类在中下部"。 */
.auth h3 { display: block; text-align: center; }
.auth h3 > .muted { display: block; margin-top: var(--w-sp-1); font-weight: 400; }
.auth .row { justify-content: center; }
.auth .row input { max-width: 200px; }
/* 描述：与动作拉开距离、落在卡片中下部；单独成块（不是紧贴按钮的小字） */
.auth-desc {
  margin-top: var(--w-sp-5);
  padding-top: var(--w-sp-4);
  border-top: 1px dashed var(--line);
  color: var(--muted);
  font-size: var(--w-fs-meta);
  line-height: 1.9;
  text-align: center;
}
.auth-actions { margin-top: var(--w-sp-4); display: flex; justify-content: center; }

/* 卡片标题里的副标题（`.muted`）在窄屏会把标题挤到折行（实测「计费说明」被折成
   「计费说」/「明」）。窄屏让它独占一行 —— 标题一行、描述在下一行，也更符合
   "描述类应放在中下部"的阅读顺序。 */
@media (max-width: 767px) {
  .card h3 { flex-wrap: wrap; }
  .card h3 > .muted { flex: 1 1 100%; text-align: left; margin-top: 2px; }
}

/* 7 步导航（D-172）：借鉴对标产品左栏的编号步骤 —— 长页面里"我在第几步 / 还要做什么"一眼可见。
   完成态=实心勾，当前步=描边高亮；点一下滚到那张卡。 */
.step-rail { position: sticky; top: 0; z-index: 5; display: flex; flex-wrap: wrap;
  gap: var(--w-sp-2, 8px); padding: var(--w-sp-2, 8px) 0; margin: 0 0 var(--w-sp-3, 12px);
  background: var(--bg, transparent); border-bottom: 1px solid var(--line); }
.step-chip { display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  padding: 4px 10px; border: 1px solid var(--line); border-radius: 999px;
  background: transparent; color: var(--muted); font-size: var(--w-fs-meta); }
.step-chip b { font-weight: 700; }
.step-chip i { font-style: normal; color: var(--ok); font-weight: 700; }
.step-chip.on { border-color: var(--ok); color: var(--text); }
.step-chip.cur { border-color: var(--s-color-primary); color: var(--text);
  box-shadow: 0 0 0 1px var(--s-color-primary) inset; }
.step-chip:hover { border-color: var(--s-color-primary); }

/* 法务审核报告（D-172）：对标产品的双栏报告，但把"建议"做成**可点的动作**。 */
.audit { margin-top: var(--w-sp-3, 12px); border: 1px solid var(--line);
  border-radius: var(--w-r-md, 8px); padding: var(--w-sp-3, 12px); }
.audit-rows { margin-top: var(--w-sp-2, 8px); }
.audit-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
.audit-bad { color: var(--s-color-warn); text-decoration: line-through; }
.audit-arrow { color: var(--muted); }
.audit-good { color: var(--ok); }

/* studio.css — 简化版工作台（Studio）的**紧凑卡片**皮肤（对标用户给的参考截图）。
 *
 * 与 `style.css` 的分工：`style.css` 是设计 token + 旧向导外壳的历史样式；
 * 这一层只负责"一屏卡片工作台"的布局与密度 —— 卡片网格、左图标轨、紧凑字段行、表格。
 * 全部颜色走既有 token（`--panel/--line/--text/--muted/--accent/--ok`），换肤不用改这里。
 */

.studio {
  display: grid;
  grid-template-columns: 52px minmax(0, 1fr);
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
  /* **应用默认宽度**（用户 2026-09-24："默认应用宽度要设定一个值，让下拉都能正常，且所有内容在一个屏幕内显示"）：
     不设这个值时，主区会随窗口无限拉宽 —— 1920 屏上卡片被拉成两块"宽扁条"，下拉宽度全靠运气，
     而 1366 屏上又挤成三列、下拉只剩 100px（"中年男/正常"这种四字选项都贴边）。
     定成 1360：两列时每列卡片 ≈ 675px，一行放得下 2 个下拉 + 1 个输入框；
     比它窄的屏按窗口走（不产生横向滚动），比它宽的屏左右留白居中（不再继续拉宽）。 */
  --studio-w: 1360px;
  --studio-gap: 8px;
}
.studio.mobile { grid-template-columns: 44px minmax(0, 1fr); }

/* ── 左轨：只有图标（参考图那种） ── */
.studio .rail {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 10px 6px; border-right: 1px solid var(--line); background: var(--panel);
}
.studio .rail-btn {
  width: 34px; height: 34px; padding: 0; border-radius: 9px; font-size: 15px; line-height: 1;
  background: transparent; border: 1px solid transparent; color: var(--muted); cursor: pointer;
}
.studio .rail-btn:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.studio .rail-btn.on {
  color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}
.studio .rail-gap { flex: 1; }
.studio .rail-ver {
  writing-mode: vertical-rl; font-size: 10px; color: var(--muted); opacity: .7;
  max-height: 96px; overflow: hidden;
}

/* ── 主区 ── */
/* `max-width` + `margin-inline: auto`：把内容按**应用默认宽度**居中，窗口再宽也不继续拉宽。 */
.studio .main {
  min-width: 0; width: 100%; max-width: calc(var(--studio-w) + 28px); margin-inline: auto;
  padding: 8px 14px 16px; display: flex; flex-direction: column; gap: var(--studio-gap);
}

/* 顶栏**吸顶**：滚动时也一直可见 —— 「刷新」按钮在它最右侧，所以是"右侧顶部固定"。
   注意不要给 `.main` 加 overflow:hidden（会让 sticky 失效）。 */
.studio .bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  position: sticky; top: 0; z-index: 40;
  background: var(--bg); padding-bottom: 6px;
}
.studio .bar .refresh { white-space: nowrap; }
.studio .bar .brand { font-weight: 700; font-size: 15px; }
.studio .bar .grow { flex: 1; }
.studio .bar .who { font-size: 12px; }
.studio .bar-tabs, .studio .tabs { display: flex; gap: 6px; flex-wrap: wrap; }
.studio .tab {
  padding: 5px 12px; border-radius: 999px; font-size: 12px; cursor: pointer;
  background: transparent; border: 1px solid var(--line); color: var(--muted); text-decoration: none;
}
.studio .tab.on {
  color: var(--text); border-color: color-mix(in srgb, var(--accent) 50%, transparent);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
}
.studio .chip {
  font-size: 11px; padding: 2px 8px; border-radius: 999px; color: var(--muted);
  border: 1px solid var(--line); white-space: nowrap;
}
.studio .chip.ok { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 45%, transparent); }
.studio .ghost {
  padding: 5px 10px; font-size: 12px; border-radius: 8px; cursor: pointer;
  background: transparent; border: 1px solid var(--line); color: var(--text);
}
.studio .ghost:disabled { opacity: .45; cursor: not-allowed; }
.studio .link {
  background: none; border: none; color: var(--accent); cursor: pointer; padding: 0; font-size: 12px;
}
.studio .bar-note {
  font-size: 12px; padding: 6px 10px; border-radius: 8px; border: 1px solid var(--line);
  background: var(--panel); display: flex; align-items: center; gap: 8px;
}
.studio .bar-note.ok { border-color: color-mix(in srgb, var(--ok) 45%, transparent); color: var(--ok); }
.studio .bar-note.err { border-color: color-mix(in srgb, var(--err, #ff6b6b) 45%, transparent); color: var(--err, #ff6b6b); }
.studio .bar-note.warn { border-color: color-mix(in srgb, var(--accent) 45%, transparent); }

/* ── 工作台：一屏卡片（**两列**；900 以下一列） ── */
/* 用户 2026-09-24："三列改成两列，下拉要保证宽度……所有内容在一个屏幕内显示"。
   三列时每张卡只有 447px，一行塞 3–4 个字段 → 下拉被压到 100px（"中年男/正常"这种四字选项贴边、
   "默认模板_居中大字"这种九字选项直接被裁）。两列后每列 ≈ 675px，字段行宽松、下拉按下面那条保证宽度。
   用**多列**而不是 grid：卡片高度差很大（有的带预览/音频），多列能紧密堆叠，
   grid 会在每行按最高卡片对齐、留出大片空白。 */
.studio .board { columns: 2; column-gap: var(--studio-gap); }
.studio .board .card { break-inside: avoid; margin-bottom: var(--studio-gap); }
@media (max-width: 899px) { .studio .board { columns: 1; } }

.studio .card {
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
  padding: 8px 11px; margin: 0; display: flex; flex-direction: column; gap: 6px; min-width: 0;
}
.studio .ch { display: flex; align-items: center; gap: 8px; }
/* 卡片内的小节标题（如「修改密码」「换台手机」）：与上一块拉开一点，但不再加边框
   （`.sub` 那条左边线是"缩进层级"的意思，这里只是分段）。 */
.studio .ch.gap { margin-top: 8px; }
/* 步骤编号底色 = **暗绿**（用户 2026-09-24："步骤标识的数字背景，不要用黄色，要用暗绿色"）。
   原来用的是 `--accent`（amber-400 黄）—— 暖色在暗底上最抢眼，7 张卡的编号本该是**路标**而不是警示，
   一眼扫过去满屏黄点，反而把卡片标题和状态徽章压下去了。
   取 `--p-green-900`（#064e3b）：暗绿、白字对比度约 9:1，比它更深的一档（teal-900 #0d3b30）
   贴着面板底色几乎看不出块面，故不取。 */
.studio { --studio-step-bg: var(--p-green-900); }
.studio .ch .num {
  width: 20px; height: 20px; border-radius: 6px; display: grid; place-items: center;
  font-size: 11px; font-weight: 700; color: #fff; background: var(--studio-step-bg);
}
.studio .ch b { font-size: 13px; }
.studio .ch .chip { margin-left: auto; }

/* 紧凑字段行：一行放 1–4 个字段，放不下就换行（手机上也不挤爆卡片） */
.studio .srow { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.studio .srow > input, .studio .srow > select, .studio .srow > textarea { flex: 1 1 96px; min-width: 0; }
/* **下拉要保证宽度**（用户 2026-09-24）：`min-width` 是"关着的时候也读得全"的底线 ——
   选中的选项文本（如「默认模板_居中大字」9 字 ≈ 120px）不许被裁。140px 是实测够用的最小值；
   `flex-basis 150px` 让它在还有余量时优先长一点，同排的输入框按比例分剩下的宽度。 */
.studio .srow > select { flex: 1 1 150px; min-width: 140px; }

/* **矮屏紧凑模式**（用户 2026-09-24："所有内容在一个屏幕内显示"）：
   两列布局下 7 张卡总高约 780px —— 1440×900 及以上能一屏放完；但 1366×768 / 1280×800
   这类笔记本屏会被顶出一百来像素。矮屏上把"可滚动的多行文本框"默认压低（**内容不丢，框内可滚**），
   再收一点内边距，就能一屏看完；移动档（≤899px）不受影响，写稿仍然给足高度。 */
@media (max-height: 899px) and (min-width: 900px) {
  .studio { --studio-gap: 5px; }
  .studio .card { padding: 6px 10px; gap: 3px; }
  /* 矮屏上底部不留白：省下的正是"最后一排卡被顶出屏幕"的那十几像素 */
  .studio .main { padding-top: 5px; padding-bottom: 0; }
  .studio .bar { gap: 8px; }
  .studio .bar .brand { font-size: 13px; }
  .studio .tab { padding: 4px 10px; }
  .studio .srow { gap: 5px; }
  .studio .srow > textarea { height: 42px; }
  .studio .primary.big { padding: 7px 18px; }
  .studio .hint, .studio .ok, .studio .err { line-height: 1.3; }
}
.studio .srow > input[type="range"] { flex: 0 0 96px; }
.studio .srow > input[type="file"] { flex: 1 1 150px; font-size: 11px; }
.studio .rng { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); white-space: nowrap; }
.studio .chk { display: flex; align-items: center; gap: 5px; font-size: 12px; color: var(--muted); white-space: nowrap; }
.studio .cands { display: flex; gap: 6px; flex-wrap: wrap; }
.studio .sub {
  border-left: 2px solid color-mix(in srgb, var(--accent) 35%, transparent);
  padding-left: 8px; display: flex; flex-direction: column; gap: 6px;
}
.studio .hint { font-size: 11px; color: var(--muted); line-height: 1.5; }
/* 「还差一个浏览器」：这是**阻塞整条本机取源**的缺件，值得一块醒目的引导（含下载入口） */
.studio .need-browser {
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  border-radius: 8px; padding: 8px 10px; display: flex; flex-direction: column; gap: 6px;
}
.studio .need-browser .srow { gap: 8px; }
.studio .ok { font-size: 11px; color: var(--ok); }
.studio .err { font-size: 11px; color: var(--err, #ff6b6b); }
.studio textarea, .studio input, .studio select { font-size: 12px; }
.studio textarea { resize: vertical; }
.studio audio, .studio video { width: 100%; border-radius: 8px; }
.studio .cover { max-width: 160px; border-radius: 8px; }
.studio .pair { font-size: 17px; letter-spacing: 3px; color: var(--accent); }

/* 出片预览：视频/封面占满卡片宽，避免"缩略图看不清" */
.studio .card video { max-height: 260px; background: #000; }
.studio .button-ish {
  padding: 5px 10px; font-size: 12px; border-radius: 8px; border: 1px solid var(--line);
  color: var(--text); text-decoration: none; white-space: nowrap;
}

/* ── 系统与账户：四个页签 + 面板 ── */
.studio .page { display: flex; flex-direction: column; gap: 10px; }
.studio .panel {
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px;
  display: flex; flex-direction: column; gap: 8px; max-width: 1000px;
}
.studio .panel h3 { margin: 0; font-size: 14px; display: flex; align-items: center; gap: 8px; }
.studio .panel.login { max-width: 620px; }
.studio .kv { display: flex; align-items: center; gap: 10px; font-size: 12px; }
.studio .kv > span:first-child { color: var(--muted); min-width: 96px; }
.studio .kv .big { font-size: 18px; }
.studio .tbl { width: 100%; border-collapse: collapse; font-size: 12px; }
.studio .tbl th { text-align: left; color: var(--muted); font-weight: 500; padding: 4px 6px; }
.studio .tbl td { padding: 4px 6px; border-top: 1px solid var(--line); white-space: nowrap; }
.studio .tbl td.ok { color: var(--ok); }

/* ── 弹窗 ── */
.studio .mask {
  position: fixed; inset: 0; background: rgba(0, 0, 0, .55); display: grid; place-items: center; z-index: 40;
}
.studio .dialog {
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
  padding: 14px; min-width: 320px; max-width: min(560px, 92vw); display: flex; flex-direction: column; gap: 8px;
}

/* 「多平台发布」：平台勾选一行 + 一个大按钮（对标参考图） */
.studio .platforms { gap: 10px 14px; }
.studio .platforms .chk { font-size: 12px; color: var(--text); }
.studio .platforms input[type="checkbox"] { width: 14px; height: 14px; accent-color: var(--accent); }
.studio .primary.big { padding: 9px 18px; font-size: 14px; font-weight: 600; flex: 1 1 100%; }
.studio .fold > summary {
  cursor: pointer; display: flex; align-items: center; gap: 8px;
  font-size: 12px; color: var(--muted); padding: 4px 0;
}
.studio .fold > summary::-webkit-details-marker { display: none; }
.studio .fold[open] > summary { color: var(--text); }

/* ── C 端网页：**多端登录确认弹窗**（2026-10-20；参考竞品那张「该账号已在其他网页端登录」）
   为什么要单独一组：`.modal` 只有 max-width 420px，而登录卡是 `.studio .panel`（max-width
   1000px）—— 两条规则撞在一起时后者胜，弹窗会拉成一整条横带（实测截图就是这样）。
   所以这里给弹窗自己的类，把宽度、标题层级、按钮靠右一次说清。
   ⚠️ 选择器必须是 **`.studio .login-conflict`**（与 `.studio .panel` 同权重、且排在后面）——
   只写 `.login-conflict` 会被 `.studio .panel` 压住，弹窗还是一条横带（实测过一次）。 */
.studio .login-conflict { max-width: 420px; }
.login-conflict h3 { font-size: 16px; margin: 0 0 12px; }
.login-conflict .hint { margin: 0 0 8px; line-height: 1.7; }
/* 决策依据那一行：不用 `.hint`（那条 30 字规则管的是页面常驻说明，见模板里的注释） */
.login-conflict .dlg-line { margin: 0 0 8px; line-height: 1.75; font-size: 12.5px; }
.login-conflict .srow { justify-content: flex-end; margin-top: 14px; }

/* ── C 端「直播平台配置」卡片墙（2026-10-20，默认页；对照竞品登录后首页）─────────
   为什么是卡片而不是表格：这一页要回答"这台机器去哪个平台开播、配到哪一步"，
   13 个平台按系分组，每张卡写入口与状态 —— 平铺成表格之后"哪几个是一家的"
   要读的人自己在脑子里分组。 */
.pnote { margin: 0 0 10px; padding: 10px 12px; border-radius: 8px; font-size: 12.5px;
         line-height: 1.7; }
.pnote.info { background: var(--s-color-info-weak, #eff6ff); color: var(--s-color-text, #111); }
.pnote.warn { background: var(--s-color-warn-weak, #fef3c7); color: var(--s-color-text, #111); }
.pgroup { margin: 0 0 16px; }
.pgroup-title { margin: 0 0 8px; font-size: 12.5px; font-weight: 600; color: var(--muted); }
.pcards { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }
.pcard { border: 1px solid var(--line); border-radius: 10px; padding: 12px;
         display: flex; flex-direction: column; gap: 6px; background: var(--panel); }
/* 未接入的平台**不装成"配一下就好"**：底色压灰，原因照写 */
.pcard.st-unsupported { background: var(--panel2); }
.pcard-top { display: flex; align-items: center; gap: 9px; }
.pcard-ico { width: 28px; height: 28px; border-radius: 8px; flex: 0 0 auto; font-size: 13px;
             font-weight: 700; display: inline-flex; align-items: center; justify-content: center;
             background: var(--panel2); color: var(--text); }
.pcard-name { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pcard-name b { font-size: 13.5px; }
.ptag { align-self: flex-start; font-size: 10.5px; padding: 0 5px; border-radius: 4px;
        background: var(--panel2); color: var(--muted); }
.pcard-entry { margin: 0; font-size: 11.5px; color: var(--muted); line-height: 1.55; }
.pcard-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 11.5px; }
.pstate { font-size: 11px; padding: 2px 8px; border-radius: 999px; font-weight: 600; }
.pstate.st-configured { background: var(--s-color-success-weak, #d1fae5);
                        color: var(--s-color-success-ink, #065f46); }
.pstate.st-unconfigured { background: var(--s-color-info-weak, #eff6ff);
                          color: var(--s-color-primary-ink, #1e40af); }
.pstate.st-trial { background: var(--s-color-warn-weak, #fef3c7);
                   color: var(--s-color-warn-ink, #92400e); }
.pstate.st-unsupported { background: var(--panel2); color: var(--muted); }
.pcard-dev { margin: 0; font-size: 11.5px; line-height: 1.6; }
.pcard-note { margin: 0; font-size: 11.5px; color: var(--muted); line-height: 1.6;
              border-top: 1px dashed var(--line); padding-top: 6px; }

/* ── C 端「一个平台的配置详情页」（2026-10-20，点卡片进来；对照竞品同名页）────────
   为什么是"一行一格"的清单而不是卡片网格：这一页的操作是**逐个确认开关**，
   横排卡片会把"哪个开关属于哪一组"切开（竞品也是分组竖直排列）。 */
.pcard.is-open { cursor: pointer; }
.pcard.is-open:hover { border-color: var(--accent, #6c5ce7); }
.pfs { display: flex; flex-direction: column; gap: 8px; margin: 0 0 14px; }
.pfs-title { margin: 18px 0 8px; font-size: 12.5px; font-weight: 600; color: var(--muted);
             border-top: 1px dashed var(--line); padding-top: 12px; }
.pfs-title:first-of-type { border-top: 0; padding-top: 0; margin-top: 4px; }
.pfs .chk { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.pfs .chk input[type="checkbox"] { width: 16px; height: 16px; flex: 0 0 auto; }
.pfs .chk .muted { font-size: 11.5px; }
.pfs-row { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.pfs-row .num { width: 76px; padding: 4px 8px; font-size: 13px; }
.pfs-links { display: flex; flex-wrap: wrap; gap: 8px; }

/* ── 平台详情页：**吸顶的保存条**（用户口径 2026-10-20：「保存按钮浮动留在顶部」）───
   这一页会很长（内容块 + 一排开关），滚到中段时"保存"必须还在手边。
   `top: 0` 贴着内容区顶；底色不能透明，否则滚过去的内容会从按钮下面透出来。 */
.pf-card { padding-top: 0; }
.pf-head { position: sticky; top: 0; z-index: 20; display: flex; align-items: center;
           gap: 8px; padding: 12px 0; margin: 0 0 6px; background: var(--panel);
           border-bottom: 1px solid var(--line); }
.pf-head-main { display: flex; align-items: baseline; gap: 8px; flex: 1; min-width: 0; }
.pf-head-main b { font-size: 15px; }
.pf-head-main .chip { font-size: 11.5px; }

/* 平台详情页里的多行输入（人设提示词 / 违禁词）：等宽感不重要，**行高与留白**重要 ——
   这两格是长文本，挤成一团就看不清自己在写什么。 */
.pf-text { width: 100%; box-sizing: border-box; padding: 8px 10px; font-size: 12.5px;
           line-height: 1.7; border: 1px solid var(--line); border-radius: 8px;
           background: var(--panel2); color: var(--text); resize: vertical; }
.pf-help { margin: 0; font-size: 11.5px; color: var(--muted); line-height: 1.6; }

/* 两个选择器（改写用话术 / 改写用音色）：列表要能滚，条目要一眼看出"选中了没有" */
.modal.pick { max-width: 460px; display: flex; flex-direction: column; gap: 8px; }
.pick-list { max-height: 46vh; overflow: auto; display: flex; flex-direction: column; gap: 6px;
             margin: 4px 0; }
.pick-item { display: flex; align-items: center; gap: 8px; text-align: left; width: 100%;
             padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px;
             background: var(--panel2); color: var(--text); cursor: pointer; font-size: 12.5px; }
.pick-item b { flex: 1; font-weight: 600; }
.pick-item.on { border-color: var(--accent, #6c5ce7); background: var(--panel); }

/* 选择器抽屉（照竞品截图重做 2026-10-20）：话术两行预览、音色分组+标签 */
.pick { max-width: 520px; max-height: 84vh; }
.pick .pf-help { margin: 0 0 6px; }
.pick-search { margin: 4px 0 8px; padding: 6px 10px; font-size: 13px;
               border: 1px solid var(--line); border-radius: 8px; background: var(--panel2);
               color: var(--text); }
.pick-item.script { display: block; padding: 10px 12px; }
.pick-item.script .pick-h b { font-size: 13.5px; }
.pick-sub { display: flex; gap: 6px; align-items: center; font-size: 11.5px;
            color: var(--muted); margin-top: 2px; }
.pick-preview { margin: 4px 0 0; font-size: 11.5px; line-height: 1.6; color: var(--muted);
                display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
                overflow: hidden; }
.pick-item.script.on { border-color: var(--accent, #6c5ce7);
                       box-shadow: inset 0 0 0 1px var(--accent, #6c5ce7); }
.pick-group { font-size: 11.5px; color: var(--muted); margin: 6px 0 2px; }
.pick-item.voice { padding: 8px 10px; }
.pick-item.voice.off { opacity: .55; }
.tag-pill { font-size: 10.5px; padding: 1px 6px; border-radius: 999px;
            background: var(--panel2); color: var(--muted); }
.tag-pill.mine { background: var(--accent, #6c5ce7); color: #fff; }
.tag-pill.pub { background: var(--panel2); }
.tag-pill.def { background: var(--panel2); color: var(--text); }
.tag-pill.bad { background: #fee2e2; color: #991b1b; }
.pick-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 6px; }

/* 触发关键词的输入与标签（竞品那一排「购物车 ×」的样式） */
.kw-input { flex: 1; min-width: 0; padding: 6px 10px; font-size: 12.5px;
            border: 1px solid var(--line); border-radius: 8px; background: var(--panel2);
            color: var(--text); }
.kw-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.kw-tag { display: inline-flex; align-items: center; gap: 4px; font-size: 11.5px;
          padding: 2px 8px; border-radius: 999px; background: var(--panel2);
          color: var(--text); }
.kw-x { border: 0; background: transparent; color: var(--muted); cursor: pointer;
        font-size: 13px; line-height: 1; padding: 0; }

/* 数值项：标签 + 当前值一行，滑杆一行（照竞品「AI 自动回复频率」那张图） */
.pfs-slider { display: flex; flex-direction: column; gap: 4px; margin: 0 0 10px; }
.pfs-row-h { display: flex; align-items: baseline; justify-content: space-between;
             font-size: 13px; }
.pfs-row-h b { font-size: 12.5px; font-weight: 600; }
.pfs-slider input[type="range"] { width: 100%; accent-color: var(--accent, #6c5ce7); }

/* 详情页右上角的**实时保存状态**（用户口径：右上角有状态，表示确实有实时保存） */
.pf-status { font-size: 12px; color: var(--muted); white-space: nowrap; }
.pf-status.st-pending { color: #b45309; }        /* 待保存：黄 */
.pf-status.st-saving { color: var(--muted); }    /* 保存中 */
.pf-status.st-saved { color: #047857; }          /* 已保存：绿 */
.pf-status.st-failed { color: #b91c1c; }         /* 失败：红 */

/* 报时格式 chips / 副播列表 / 自动发言文案列表（照竞品截图 2026-10-20） */
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip-btn { padding: 4px 12px; border-radius: 999px; border: 1px solid var(--line);
            background: var(--panel2); color: var(--muted); font-size: 12px; cursor: pointer; }
.chip-btn.on { border-color: var(--accent, #6c5ce7); color: var(--text); }
.t-ipt { width: 64px; padding: 2px 6px; font-size: 12px; border: 1px solid var(--line);
         border-radius: 6px; background: var(--panel2); color: var(--text); }
.pfs-preview { margin-top: 8px; padding: 10px 12px; border-radius: 8px; font-size: 12.5px;
               background: var(--s-color-success-weak, #d1fae5);
               color: var(--s-color-success-ink, #065f46); }
.pfs-warn { margin: 8px 0 0; padding: 10px 12px; border-radius: 8px; font-size: 12.5px;
            background: var(--s-color-warn-weak, #fef3c7);
            color: var(--s-color-warn-ink, #92400e); }
.sub-voices, .post-lines { display: flex; flex-direction: column; gap: 6px; }
.sub-voice, .post-line { display: flex; align-items: center; justify-content: space-between;
                         gap: 8px; padding: 8px 10px; border: 1px solid var(--line);
                         border-radius: 8px; background: var(--panel2); font-size: 12.5px; }
.sub-voice-main { display: flex; align-items: center; gap: 8px; }
.post-line span { flex: 1; }

/* ── 平台详情页：**每一块配置 = 一张有阴影的卡片**（用户口径 2026-10-20：
      "每一个配置，要像一个有阴影的卡片，看起来才不会那么乱"）─────────────────
   为什么外层不再是卡片：一整页一张大卡片时，块与块之间只靠一条虚线分隔，
   十几块连成一片、看不清边界。现在外层透明、每块自己一张卡。 */
.pf-card { background: transparent; border: 0; box-shadow: none; padding: 0; }
.cfg-card { background: var(--panel); border-radius: 12px; padding: 14px 16px;
            margin: 0 0 14px; box-shadow: 0 2px 12px rgba(16, 24, 40, .08); }
.cfg-card .pfs-title { margin: 0 0 10px; padding: 0; border-top: 0; font-size: 13px;
                       font-weight: 700; color: var(--text); }
.cfg-card .pfs { margin: 0; }
.cfg-card .pfs + .pfs { margin-top: 8px; }

/* 一行里既有开关又有输入框：开关单独一个 label，输入框在外面（避免点输入框点到开关） */
.chk-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 13px; }
.chk-row .chk { margin: 0; }

/* 直播间互动：四张事件子卡（比外层卡浅一档，避免"卡里套卡"看着更乱） */
.ev-card { border: 1px solid var(--line); border-radius: 10px; padding: 12px;
           margin: 10px 0 0; background: var(--panel2); display: flex;
           flex-direction: column; gap: 8px; }
.ev-head { display: flex; align-items: center; justify-content: space-between; }
.ev-head b { font-size: 13px; }

/* 事件卡里的**细节区**（如「点赞后 AI 主动提问」）——比事件卡再深一档，用左侧竖线分段 */
.ev-detail { border-left: 3px solid var(--accent, #6c5ce7); padding-left: 10px;
             margin-top: 4px; display: flex; flex-direction: column; gap: 8px; }

/* 双柄区间（停顿时长上限/下限）：两条滑杆叠在一行，看着像"一个区间" */
.range-dual { width: 100%; accent-color: var(--accent, #6c5ce7); margin: 0 0 2px; }
