:root {
  --bg: #f4f5f7;
  --card: #ffffff;
  --line: #dfe2e8;
  --ink: #1c2027;
  --dim: #6b7280;
  --accent: #2f6fed;
  --accent2: #0f9d6b;
  --warn: #d97706;
  --bad: #dc2626;
  /* ★★ 「需要更新」单独一个色（品红），**不许再借 `--warn`**（用户 2026-09-28：
     "要在导航栏的步骤按钮上用颜色和标识体现是否需要更新"）。
     从前 `.stage.stale`（别人动了 ⇒ 你旧了）和 `.stage.part`（自己没做完）**同一个
     橙**，只差一个虚线描边和 18%/12% 的底色 —— 手机上根本分不出来，而这两件事的
     处置完全相反：前者"去重跑这一步"，后者"接着跑就行"。
     选品红是因为它离已有的三个状态色都远：完成=绿(`--accent2`)、进行中=蓝
     (`--accent`)、做了一半=橙(`--warn`)、失败=红(`--bad`)。 */
  --stale: #a21caf;
  --radius: 10px;
  /* 媒体格子的衬底。**不许是黑的** —— 图不是 16:9 时（竖屏 9:16 尤其明显）
     上下/左右会留出衬底，纯黑看着就是"一块黑框"，用户 2026-09-25 反复报这个。
     跟着页面底色走：亮色模式浅灰、暗色模式深灰，都不是黑。 */
  --mediabg: #e9ebef;
  /* 「一个动作一个方块」的衬底（`.opgrp`）—— 只比卡片深一丁点：方块靠**边界**分得开，
     不靠颜色抢眼（按钮本身还有自己的边）。 */
  --grpbg: #f7f8fa;
  /* 顶上导航的高度，`app.js:syncNav` 量出来写进这儿 —— 钉住的几行和
     批量条都靠它让位。写死数字的话换个字号就压在一起。 */
  --navH: 54px;
  /* tab 条实高（ 现量现写，这是量不到时的兑底）。字幕预览钉在它下面。 */
  --cfgTabH: 44px;
}
/* ★★★ 2026-10-12 用户（截图）：「图文作品的编辑界面怎么是黑色的」。
   真病根：你的 Windows 开着**深色模式**，而下面这一段 `prefers-color-scheme: dark`
   把整页刷成了深色。用户选了「**永远亮色**」（该段已删）
   —— 不管系统什么模式，页面都是白底。
   ⚠️ 不要把它加回来（要加得先问用户）—— 上面 `:root` 那一份就是全部配色。 */
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--bg); color: var(--ink);
  /* 字号整体再抬一档（用户 2026-09-25：还是小）。16 → 17。 */
  font: 17px/1.65 system-ui, -apple-system, "Microsoft YaHei", sans-serif;
  padding: 0 0 124px;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
button, .btn {
  font: inherit; cursor: pointer; border: 1px solid var(--line);
  background: var(--card); color: var(--ink);
  border-radius: 8px; padding: 7px 13px;
}
button:hover:not(:disabled), .btn:hover { border-color: var(--accent); }
button:disabled { opacity: .45; cursor: not-allowed; }
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
button.ok { background: var(--accent2); border-color: var(--accent2); color: #fff; }
button.danger { background: var(--bad); border-color: var(--bad); color: #fff; }
button.sm { padding: 4px 9px; font-size: 14.5px; }
/* ★★ 2026-10-09 用户：「试听和预览按钮都加个颜色，显眼一点」——
   全局配置页每格 tab 顶上那颗「▶ 试听 / ▶ 预览 / 🖼 预览」。
   ⚠️ 它是**要点的那一颗**，跟旁边一排灰键长得一样就没人按。
   ⚠️ 用 `.ok`（`--accent2` 那一种绿），不用 `.primary`：`.primary` 是「跑这一步」的
      颜色，那一步的键在别处成片地摆着，撞色会让人以为按了会起作业。 */
button.sm.loud { background: var(--accent2); border-color: var(--accent2); color: #fff; font-weight: 600; }
button.sm.loud:hover { filter: brightness(1.08); }
/* ★★ 2026-10-01 用户（附截图）：「这个按钮做成大按钮，有颜色」——
   「背景音乐设置」那颗是这一步**唯一**的入口，跟旁边那些 `sm` 的小键一样大，
   看着像"可点可不点"。给它自己的类：占满这一列的宽度、高一点、字大一点。
   ⚠️ 颜色不在这儿写 —— 用 `.primary`（主题色那一种，同「▶ 跑这一步」）。
   ⚠️ 只有 `.bgmcard` 里那一颗用它，别顺手往别处加（那是用户点名要的观感）。 */
button.bigbtn { width: 100%; padding: 11px 16px; font-size: 16.5px; }
input, select, textarea {
  font: inherit; color: var(--ink); background: var(--card);
  border: 1px solid var(--line); border-radius: 8px; padding: 6px 9px;
}
textarea { width: 100%; min-height: 130px; resize: vertical; font-family: ui-monospace, Consolas, monospace; font-size: 15px; }
/* ★ 旁白那个框（「重音/停顿/改文字」弹窗里）：默认**两行**就够（用户 2026-09-27：
   「编辑框太高了，用不了那么多文字，两行就够了，如果在输入，就根据输入动态扩展」）
   —— 高度按内容走（`growPrompt`，在 `oninput` 和每次重画后各量一次），
   所以内部不许再出滚动条、也不给人手动拽（拽过的高度下一次输入就被算掉了）。
   ★★ 2026-09-29（用户第 1 条）：字幕框 `#weNarTa` 与台词框 `#weTxTa` **同一条规则**
      —— 它们是一屏里并排的两行，长得不一样才是错的。
   ★ `flex:1; min-width:0`：这两个框现在和左边那个「字幕 / 台词」小标、右边那颗
      「保存字幕」**同在一行 flex 里**。少了 `min-width:0`，textarea 的默认宽度
      （约 20 列）会把那一行撑破、按钮被挤到窗外去（flex 子项不许比内容更窄）。 */
#weTxTa, #weNarTa { min-height: 0; overflow: hidden; resize: none; flex: 1; min-width: 0; }
label.inline { display: inline-flex; align-items: center; gap: 6px; }
.row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.row.tight { gap: 5px; }
/* ★★ `.col` = 竖着一列（用户第 4 条：「音频设置界面太混乱了，重新设计一下，功能分组」）。
   ⚠️ **从前 style.css 里根本没有这条规则** —— 四个页面板（`#bgmBox` 背景音乐 /
      `#subBox` 字幕样式 / `#kwBox` 关键词标注 / `#vcBox` 音色与语速）全都写着
      `class="col" style="gap:5px"`，而 `gap` 只对 flex/grid 容器生效 ⇒ 那 5px
      从来就没生效过，整屏就是一堆**无边距的块级 div 顺次堆叠**。这正是用户说的"混乱"。
   ⚠️ 别改成 `display:grid` 之类的：`.opgrp.col`（片头设置那屏在用）是
      `.opgrp` + `.col` 两个类，靠的就是"这里只补 flex 方向、别的由 `.opgrp` 说了算"。 */
.col { display: flex; flex-direction: column; gap: 5px; }
.spread { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
.wrap { max-width: 1500px; margin: 0 auto; padding: 0 20px; }
h1 { font-size: 26px; margin: 20px 0 10px; }
h2 { font-size: 20px; margin: 18px 0 8px; }
h3 { font-size: 16.5px; margin: 14px 0 6px; color: var(--dim); font-weight: 600; }
.muted { color: var(--dim); }
.tiny { font-size: 14px; }
.mono { font-family: ui-monospace, Consolas, monospace; font-size: 15px; }
.card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 14px 16px; margin: 12px 0;
}
.grid { display: grid; gap: 12px; }
.grid.cards { grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); }
/* ★★★ 2026-10-18 用户：「视频作品区只显示视频，图文作品区只显示图文作品，
   可以都显示在一个屏幕下」⇒ 作品页两栏并排；窄屏（手机）自动上下叠。
   ★★ 2026-10-18 用户：「1.视频作品/图文作品 醒目一点；2.两个作品区各自有自己的滚动条」
   ⇒ 两栏**各自限高 + 各自滚**（栏头粘在栏顶）；栏头做成有底色的一条，一眼分得开。 */
.wcols { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; align-items: start;
  max-height: calc(100vh - 150px); }
.wcol { max-height: calc(100vh - 150px); overflow-y: auto; overflow-x: hidden;
  padding: 10px 12px 12px; border: 1px solid var(--line); border-radius: 12px;
  background: var(--card, #fff); }
.wcol::-webkit-scrollbar { width: 10px; }
.wcol::-webkit-scrollbar-thumb { background: var(--line); border-radius: 6px; }
.wcolhead { position: sticky; top: -10px; z-index: 2; margin: -10px -12px 10px;
  padding: 10px 12px; font-size: 17px; font-weight: 800; letter-spacing: .5px;
  border-bottom: 2px solid var(--accent); background: var(--card, #fff);
  border-radius: 12px 12px 0 0; }
.wcolhead .tiny { font-weight: 500; }
@media (max-width: 900px) {
  .wcols { grid-template-columns: 1fr; max-height: none; }
  .wcol { max-height: 70vh; }
}
.grid.shots { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
/* 一场一行：表头占满整行 ⇒ 后面那一场的镜头一定从新行开始（app.js 里每场都打一条） */
.grid.shots .chapterhead {
  grid-column: 1 / -1; font-size: 14.5px; font-weight: 600; color: var(--dim);
  padding-top: 8px; border-top: 1px dashed var(--line);
}
.grid.shots .chapterhead:first-child { padding-top: 0; border-top: 0; }
/* ★ 2026-10-19 用户：「这几个按钮（视频配置 / 图文配置 / 作品展示）是一致保持在顶端显示的」
   ⇒ 合集标题 + 三颗键整条吸顶，页面怎么滚都点得到。 */
.collhead { position: sticky; top: var(--navH); z-index: 26; background: var(--bg);
  padding: 6px 0 2px; margin: -6px 0 0; }
.collhead h1 { margin: 0 0 4px; }
.collhead #collBack { display: none; }
.collhead #collBack.on { display: inline-block; }
.pill.on { border-color: var(--accent); color: var(--accent); font-weight: 600;
  background: color-mix(in srgb, var(--accent) 14%, var(--bg)); }
.pill {
  display: inline-block; padding: 1px 8px; border-radius: 999px;
  background: var(--bg); border: 1px solid var(--line); font-size: 13.5px; color: var(--dim);
}
/* 左上角那颗「实际状态」牌（`app.js:viewEp`）—— 三态，配色就三种，别再加：
     ✏️ 在编辑（灰） / ✅ 已发布（绿） / ✅ 已发布·待搬（黄，目录还在「编辑中」）。
   ⚠️ 它是**状态**不是目录名：真发出去了但没搬成，也必须显示"已发布"。 */
.pill.state-edit { color: var(--dim); }
.pill.state-pub { color: var(--accent2); border-color: var(--accent2); font-weight: 600; }
/* ★ 2026-10-24 用户：选题池那一行的「视频 / 图文」要醒目（从前是 tiny 小灰字）。 */
.lineTag { display:inline-block; width:34px; font-size:14px; font-weight:700; color:var(--ink); letter-spacing:1px; }

.pill.state-todo { color: var(--warn); border-color: var(--warn); font-weight: 600; }
nav.top {
  /* ⚠️ 33 这个数是**压着作业队列取的**：`#queueBar` 也是 sticky、也是 30，
     两兄弟同值时后出现的赢 ⇒ 作业一排上，「系列 ▾」弹出的面板就被它盖住
     （用户 2026-09-29 报的就是这个）。导航比队列高一层，面板（40，
     在导航这个层叠上下文里）才压得住队列。比 `.bar`（40，底下那条作业栏）
     低一层：面板不该盖住"停止"那颗键。 */
  position: sticky; top: 0; z-index: 33; background: var(--card);
  border-bottom: 1px solid var(--line); padding: 8px 0;
}
nav.top .wrap { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
nav.top .brand { font-weight: 700; }
nav.top a { color: var(--ink); }
nav.top a.brand:hover { color: var(--accent); text-decoration: none; }
nav.top a.on { color: var(--accent); font-weight: 600; }
/* ★★ 2026-09-29 用户：「系列按钮改成下拉菜单…」→ 当天又定：「我希望做成**菜单**那种方式」
   [书签栏那张图]：一排系列 → 底下「在编辑 / 已发布」两行、行尾 ▸ → 往右飞出题目。
   盒子**绝对定位**在按钮底下（导航自己 sticky + z-index 33 ⇒ 盒子给 40，压在内容和
   作业队列上面，不然它会钻到系列卡底下）。
   ⚠️ **不能 `overflow:auto`**（第一版是）：飞出去那一层在盒子外面（`left:100%`），
     盒子一滚就把自己那层裁没了。要滚的是**飞出层**自己（`.mfly` 的 `max-height`）。 */
nav.top .navdrop { position: relative; }
nav.top .dropbox {
  position: absolute; left: 0; top: calc(100% + 6px); z-index: 40;
  min-width: 264px; max-width: min(420px, 88vw); overflow: visible;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 12px 30px rgba(0, 0, 0, .18); padding: 6px;
}
/* ---- 菜单形态：① 一排系列（像书签栏那些文件夹）② 两档 ③ 飞出去的题目 -------- */
nav.top .mnew {
  padding: 8px 10px 4px;
  border-top: 1px solid var(--line, #e5e5e5);
  margin-top: 6px;
}
nav.top .mbar {
  display: flex; flex-wrap: wrap; gap: 2px;
  padding-bottom: 5px; border-bottom: 1px solid var(--line);
}
nav.top .mchip {
  display: inline-flex; align-items: center; gap: 5px; max-width: 100%;
  background: none; border: 0; border-radius: 7px; cursor: pointer;
  padding: 5px 8px; font: inherit; font-size: 15px; color: var(--ink);
}
nav.top .mchip:hover { background: var(--grpbg); }
nav.top .mchip.on { background: var(--grpbg); color: var(--accent); font-weight: 600; }
/* ★★ 2026-10-09 用户：「这个全局配置按钮做个边框，加个颜色」
   —— 它跟旁边「日志」「大模型调用」那些胶囊长得一样，看不出是**一整页**的入口
      （那几颗是弹窗）。给它一个**看得见的框 + 强调色**，一眼认得出。
   ⚠️ 形态参数（padding / radius / 字号）与 `.mchip` 对齐，免得那一栏高低参差。 */
nav.top a.navcfg {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 9px; border: 1px solid var(--accent); border-radius: 7px;
  color: var(--accent); font-size: 15px; font-weight: 600; text-decoration: none;
}
nav.top a.navcfg:hover { background: var(--accent); color: #fff; text-decoration: none; }
nav.top a.navcfg.on { background: var(--accent); color: #fff; }
/* ★★★ 2026-10-12 用户（[图146]）：「**这两个按钮改成带颜色的按钮，也加个边框**，
   另外这两个按钮**也要有呼吸灯，方便我知道谁有作业在跑**」
   + 「**呼吸灯和正常被选中时边框颜色不一样，以示区分**」。
   ⇒ 顶上「视频作品 / 图文作品」这两颗（`#navWorks` / `#navPic`）从**光秃秃的文字链**
      变成有框有色的胶囊，形态照抄 `.navcfg`（同一栏里长得齐）。
   ⚠️ **两套颜色必须分得开**（用户专门说了这一句）：
      · **选中**（`a.on`，你正停在这一页）= 强调色蓝框 + 淡蓝底 —— 静态的，不变。
      · **有作业在跑**（`.run`）= **绿框 + 绿底 + 呼吸**，跟选中蓝**同时出现也不混**。
      只看一眼就能知道"我人在图文页，但视频那条线在跑"。 */
nav.top a.navline {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 9px; border: 1px solid var(--line); border-radius: 7px;
  color: var(--ink); font-size: 15px; font-weight: 600; text-decoration: none;
}
/* ★ 2026-10-18 用户：「分不清哪个下拉框标识什么含义」⇒ 两个合集下拉各挂一个小标签。
   ⚠️ 光挂 title 没用（要悬停才看得见，截图上看不出来）—— 必须写进画面上。 */
/* ★ 2026-10-18 用户：「选题池/作品字体变大，和对应的下拉框按组放到一起，
   标签在左，下拉列表在右」⇒ 每组一个 `.navgrp`：标签（15px 加粗，与旁边 navline 同档）+ 下拉。 */
nav.top .navnew { margin-left: 4px; white-space: nowrap; }
nav.top .navgrp { display: inline-flex; align-items: center; gap: 6px; }
nav.top label.navlbl { font-size: 15px; font-weight: 600; color: var(--ink); white-space: nowrap; }
nav.top a.navline:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }
/* ⚠️ 选中那态**只改框色/底色，不动 `background` 的简写顺序** —— 免得把下面的 .run 底冲掉。 */
nav.top a.navline.on { border-color: var(--accent); color: var(--accent); background: var(--grpbg); }
/* ---- 有作业在跑：绿框 + 呼吸（`navpulse` 见文件末尾的 keyframes 段）----------
   ⚠️ 这一条**排在 `.on` 后面**（同权重、后写的赢）：两条同时挂上时**绿框压过蓝框**
      —— 那是故意的，跑着的那条线比"我在哪一页"更值得看。 */
nav.top a.navline.run {
  border-color: var(--ok, #16a34a); color: var(--ok, #16a34a);
  background: rgba(22, 163, 74, .10);
  animation: navpulse 1.1s ease-in-out infinite;
}
@keyframes navpulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(22, 163, 74, .55); background: rgba(22, 163, 74, .08); }
  50% { box-shadow: 0 0 0 7px rgba(22, 163, 74, 0); background: rgba(22, 163, 74, .30); }
}
nav.top .msub { padding-top: 4px; min-width: 236px; }
nav.top .mitem {
  position: relative; display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; border-radius: 7px; cursor: pointer; font-size: 15.5px;
}
nav.top .mitem:hover, nav.top .mitem.on { background: var(--grpbg); }
nav.top .mitem .mrule { flex: 1; }
nav.top .mitem .arw { color: var(--dim); font-size: 13px; }
nav.top .mfly {
  display: none; position: absolute; left: 100%; top: -6px; margin-left: 4px;
  min-width: 262px; max-width: min(420px, 52vw); max-height: 62vh; overflow: auto;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 12px 30px rgba(0, 0, 0, .18); padding: 6px;
}
nav.top .mitem:hover .mfly, nav.top .mitem.on .mfly { display: block; }
/* ⚠️ 窄屏（手机/竖屏）：飞出去那一层会跑到屏幕外面 ⇒ **就地展开**（同一个盒子、
   同一份内容，只是不飞）。点一下加 `.on` 那条路手机上照样管用 —— 手机没有 hover。 */
@media (max-width: 820px) {
  nav.top .mfly {
    position: static; left: auto; margin: 4px 0 0;
    max-width: none; box-shadow: none; border-radius: 8px;
  }
  nav.top .mitem { flex-wrap: wrap; }
  nav.top .mitem .mrule { flex: 1 0 100%; height: 0; }
}
nav.top .dropbox .dhead {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 6px 8px; border-bottom: 1px solid var(--line); margin-bottom: 5px;
}
nav.top .dropbox .dhead b { font-size: 15.5px; }
nav.top .dropbox .drow {
  display: flex; align-items: baseline; gap: 8px; width: 100%;
  text-align: left; background: none; border: 0; border-radius: 7px;
  padding: 7px 8px; font-size: 15.5px; color: var(--ink); text-decoration: none;
}
nav.top .dropbox .drow:hover { background: var(--grpbg); text-decoration: none; }
nav.top .dropbox .drow b { overflow-wrap: anywhere; }
nav.top .dropbox .drow .tiny { margin-left: auto; text-align: right; }
nav.top .dropbox .dempty { padding: 8px 9px; color: var(--dim); font-size: 14px; }

/* ---- 主界面（用户 2026-09-25：太丑 / 字小 / 只给数字看不出有啥）----------
   一条横幅说清这是什么 + 四个入口；底下每个系列一张卡，卡里
   **「编辑中」「已发布」两栏直接把片子列出来**（片名 + 进度 + 多久没动），
   点片名直接进作品 —— 不用先进列表页再找。选题池只在系列卡上。 */
.home { max-width: 1180px; margin: 0 auto; }      /* 收成一栏，两边留白，看着居中 */
.hero {
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  flex-wrap: wrap; margin: 20px 0 6px; padding: 22px 24px;
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--card);
}
.hero h1 { margin: 0 0 6px; font-size: 30px; letter-spacing: .5px; }
.hero .lead { margin: 0; color: var(--dim); max-width: 60ch; }
.hero .heroL { min-width: 280px; flex: 1 1 420px; }
.heroActs { display: flex; gap: 8px; flex-wrap: wrap; }
.sechead { display: flex; align-items: baseline; gap: 10px; margin: 26px 0 4px; }
.sechead h2 { margin: 0; }
.collectioncard { padding: 16px 18px; }
.collectioncard .sname { font-size: 21px; }
.zpanels { display: grid; grid-template-columns: repeat(auto-fit, minmax(330px, 1fr)); gap: 14px; margin-top: 14px; }
.zpanel { border: 1px solid var(--line); border-radius: 9px; background: var(--bg); padding: 10px 12px; }
.zpanel .zhead { display: flex; align-items: baseline; gap: 8px; padding-bottom: 8px; border-bottom: 1px solid var(--line); }
.zpanel .zhead b { font-size: 16.5px; }
.zpanel .zall { margin-left: auto; }
.zpanel.pub .zhead b { color: var(--accent2); }
/* 一个区一组批量键（用户 2026-09-26：「编辑中」「已发布」各管各的）：
   刷新 / 全选本页 / 删除选中 —— 就在这个区的头上。 */
.zpanel .zbar { display: flex; align-items: center; gap: 6px; padding: 7px 0 3px;
  flex-wrap: wrap; row-gap: 6px; }
.zpanel .zbar .zsel { white-space: nowrap; }
/* 弹簧：把批量键推到右边。手机上整组放不下时它先让位（键们自己换行）。 */
.zpanel .zbar .zspacer { flex: 1; }
@media (max-width: 720px) { .zpanel .zbar .zspacer { display: none; } }
/* 「这条正在跑」：左边一道竖线 + 一个会呼吸的标签（用户 2026-09-26 要的提示）。
   ★ 用**动效**是有意的：静止的标记在十几条里会被当成"这条有什么特别的"，
     动起来才一眼看出"它现在正在被写"。 */
.ehrow.run { background: color-mix(in srgb, var(--accent) 9%, transparent);
  box-shadow: inset 3px 0 0 var(--accent); }
.pill.run { border-color: var(--accent); color: var(--accent); font-weight: 600;
  animation: runpulse 1.6s ease-in-out infinite; }
@keyframes runpulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
@media (prefers-reduced-motion: reduce) { .pill.run { animation: none; } }
/* ★ 那个标签上的分数（「🏃 正在跑：配音 12/28」，见 app.js `runProgText`）：
   `tabular-nums` 让 8/28 → 12/28 时**宽度不变**，否则它自己一鼓一鼓地跳，
   在会呼吸的标签上看着像在闪。 */
.pill.run { font-variant-numeric: tabular-nums; }
.eplist { display: flex; flex-direction: column; }
.ehrow { display: flex; align-items: center; gap: 10px; padding: 7px 5px; color: var(--ink); border-radius: 6px; }
/* ★★ 2026-10-08 这一行**放不下了就折到第二行**（用户 [图]：「这里重新排列一下，
   显示不够宽了」—— 截图里「长平杀降 / 关羽之死」那两行的 断点续跑+下拉+重跑
   被压得错位、竖着排下来）。
   ⚠️ 为什么是 `wrap` 而不是继续缩：这一行现在有四样要摆（勾选 / 片名+进度 /
      「▶ 断点续跑」/ 下拉+「▶ 重跑」/「✎ 改片名」），窄面板里**横着塞不下**；
      硬缩的结果是片名被压到地板（`min-width: 5em`）还是不够，然后整行溢出。
   ⚠️ 折行后左边那一段（勾选 + 片名 + 进度）**整块**挪到第二行，不会拆散
      —— 它是 `.ehlink` 一个元素，`flex` 的折行以元素为界。
   ⚠️ 按钮那一组给 `margin-left: auto`：一行放得下时照旧靠右；折下去时它们
      跟着第二行走，不会孤零零留在第一行右端。 */
.ehrow { flex-wrap: wrap; row-gap: 6px; }
.ehrow > .archbtn, .ehrow > select.ehsel { flex: none; white-space: nowrap; }
.ehrow > select.ehsel { margin-left: auto; }
.ehrow > select.ehsel ~ .archbtn { margin-left: 0; }
/* 「▶ 重跑」左边那颗起点下拉（`app.js` `homeEpRow`）：**必须跟旁边那两颗键一样高**
   —— 它带 `class="sm"`，`.sm` 那套是给 button 写的（padding/border 不同），
   select 套上去比 button 矮一截，一横排看着参差。这里只对齐盒子的外形。 */
select.ehsel { -webkit-appearance: none; appearance: none; background: var(--card);
  color: var(--ink); border: 1px solid var(--line); border-radius: 7px;
  padding: 4px 22px 4px 8px; font: inherit; font-size: 13px; cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 13px) 50%, calc(100% - 8px) 50%;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; }
select.ehsel:hover { border-color: var(--accent); }
.ehrow + .ehrow { border-top: 1px solid var(--line); }
.ehrow:hover { background: var(--card); text-decoration: none; }
/* 片名那一行本身是链接，但**勾选框在链接外头** —— 搁在里头点一下就跳走了（见 app.js） */
.ehrow .ehlink { display: flex; align-items: center; gap: 10px; flex: 1 1 auto; min-width: 0; color: inherit; }
.ehrow .ehlink:hover { text-decoration: none; }
.ehck { flex: none; width: 16px; height: 16px; margin: 0; cursor: pointer; accent-color: var(--accent); }
/* 首页的批量条：钉在导航下面，滚动时一直在手边（勾了几条、能不能删都要看得见） */
.batchbar { position: sticky; top: var(--navH); z-index: 22; display: flex; align-items: center;
  gap: 8px; margin: 12px 0 0; padding: 8px 12px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--card); }
.ehname { flex: 1 1 auto; min-width: 5em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
/* ★★ 2026-10-08：**片名那一行**（勾选 + 片名 + 进度条 + 「6/8 阶段 · 3 分钟前」）
   也包一组。理由同 `.ehacts`：折行要**整块**挪，不能让进度条和阶段数被拆散
   （拆散了第一行会剩一个孤零零的进度条）。 */
.ehmain { display: flex; align-items: center; gap: 10px; flex: 1 1 auto; min-width: 0; }
/* 行右端那颗「📦 搬到已发布」（用户 2026-09-27：「改到每个需要搬运的题目后面」）——
   ★ 它**不缩、不折行**（`.sm` 没带 nowrap：一挤就折成三行"搬到已发/布"，同一天贴图
     「题目文字都显示不出来了」）。
   ★ 一行里谁先让位有次序：**片名最后让**（上面那条 `min-width: 5em` 就是地板），
     左边那块小字（`6/7 阶段 · ⚠ 待搬 · 11 分钟前`）先缩 —— 它是辅助信息，
     片名才是这一行唯一认得出是哪条的东西。小字缩了也还有 tooltip 兜着。 */
.ehrow > .archbtn { flex: none; white-space: nowrap; }
/* ★★ 2026-10-08：**操作那一整组**（「▶ 断点续跑」/ 下拉+「▶ 重跑」/「✎ 改片名」）
   折行时**整体挪到第二行**，别一行一颗地散着（实测窄面板下散成三行、160px 高）。
   ⚠️ 靠 `.ehlink` 的 `flex: 1 1 auto` 撑满 + 这一组 `flex: none`：
      片名那块占满第一行 ⇒ 后面这几颗**必然**一起被挤到下一行。 */
.ehrow > .archbtn, .ehrow > select.ehsel { flex: none; }
/* ★★ 2026-10-08：操作那一整组包一个 `.ehacts`（`app.js` `homeEpRow` 末尾）——
   折行时**整组一起**挪到第二行，不会一行一颗地散成三行。
   ⚠️ 它自己 `flex: none`，组里的键照旧各是各的 `flex: none`。 */
.ehacts { display: flex; align-items: center; gap: 8px; flex: none; margin-left: auto; }
/* ⚠️ 折行判据：片名那块（`.ehlink`）能压到 `5em` + 进度条 108px 那么窄 ⇒ 光靠
   `flex-wrap` 它永远不折（一直缩到地板），操作组就被挤出去了。给它一根**别再缩**的
   地板：`min-width` 一顶，第一行放不下操作组 ⇒ 那一组整块折到第二行。
   实测：面板 ≥ 620px 一行摆得下（操作组 347px），< 620px 折两行。 */
.ehrow .ehlink { flex: 0 1 auto; min-width: 10em; max-width: 100%; }
/* ⚠️ 片名那块还是允许收（`10em` 地板），但**进度条/阶段数**不许被压成渣：
   第一行 = 勾选 + 片名 + 进度 + 阶段数，操作组单独一行 —— 两行稳定，
   不会因为片名长短在这两行之间反复横跳。 */
/* 折到下一行时不再往右推（第二行的右端已经没东西了，`margin-left:auto` 会把它顶到最右，
   而片名在左边 ⇒ 中间一大片空）。 */
.ehlink + .ehacts { margin-left: 0; }
.ehbar { flex: 0 0 108px; height: 6px; border-radius: 99px; background: var(--line); overflow: hidden; }
.ehbar i { display: block; height: 100%; background: var(--accent); }
/* 真发出去了才用绿色那条 —— 编辑中再满也是蓝的（发布那格本来就没走完）。
   见 app.js `homeEpRow`：`11_发布/` 有文件只说明打过包，不代表发过。 */
.ehbar.pub i { background: var(--accent2); }
/* 「已发布·待搬」（2026-09-27）：片子已经发到抖音、目录还留在「编辑中」。
   用它和「没发」区分开 —— 用户要的就是"一眼看出该搬哪条"。 */
.ehmeta.warn { color: var(--warn); font-weight: 600; }
.ehmeta { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; font-size: 13.5px; color: var(--dim); }
.pill.bad { color: var(--bad); border-color: var(--bad); }

/* ---- 作业队列（界面最上面，钉住）----------------------------------------
   用户 2026-09-26：「如果任务正在开跑，再次点击用这条开跑按钮，任务进入队列，
   再界面顶端可查看当前任务队列」。排了队看不见 = 没排，所以这块钉在最上面。 */
#queueBar {
  position: sticky; top: var(--navH); z-index: 30; padding-top: 8px;
  background: var(--bg);
}
#queueBar .qcard {
  border: 1px solid var(--accent); background: var(--card);
  border-radius: var(--radius); padding: 8px 12px;
  box-shadow: 0 4px 14px rgba(0,0,0,.14);
}
/* ★ 整块折成一行（2026-09-26 用户：「顶栏视频队列也支持折叠」）。折起来**更要矮**，
   而且不许换行 —— 一行就是一行，否则它照样把画面顶下去。 */
#queueBar .qcard.qfold { padding: 4px 12px; }
#queueBar .qcard.qfold .qbusy {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 52vw;
}
.qrow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 2px 0; }
.qrow .qn {
  flex: none; width: 22px; height: 22px; border-radius: 999px; background: var(--accent);
  color: #fff; font-size: 13.5px; font-weight: 600;
  display: inline-flex; align-items: center; justify-content: center;
}
.qrow.run .qn { background: var(--accent2); }
/* ★ 「正在跑」那条右边的分数（「配音 12/28」，见 app.js `runProgText`）——
   从前这一栏只有"正在跑 + 谁的标题"，在首页开跑的人看不到任何进度。
   ⚠️ `flex: none` + `tabular-nums`：宽度别跟着数字变（一变就像在抖）；
     换行交给 `.qrow` 自己（它本来就有 `flex-wrap: wrap`），窄屏（平板）下
      宁可它掉到下一行，也不许把标题挤成省略号（`.ehname` 那条的教训）。 */
.qrow .qprog {
  flex: none; font-variant-numeric: tabular-nums;
  background: var(--grpbg); border: 1px solid var(--line); border-radius: 999px;
  padding: 1px 9px; font-size: 14px; font-weight: 600;
}
/* 每行右边那三个键（↑ ↓ 删除）—— 推到最右边，别跟着标题长短飘 */
.qrow .qbtns { margin-left: auto; display: inline-flex; gap: 6px; flex: none; }
.qrow .qbtns .sm { padding: 1px 8px; line-height: 1.5; }
.qrow .qbtns .sm:disabled { opacity: .35; cursor: default; }
/* ★★ 2026-09-29 用户：「任务队列就是一个任务，没有子任务，跑一整条流水线和跑一个配音
   都算一个任务，即使是同一个题目下的，因此都应该用同样的标题样式」——
   从前那两条（折起来的"另有 N 个单步" `.qsteps` 和点开后的灰行 `.qsub`）一并撤掉：
   队列里每一行都是**一条任务**，长相一模一样。
   ⚠️ `.qhold` 是**另一回事**（不是"更次一等的行"）：那是「抢跑」让路被冻住的那条，
      它必须露一面（否则用户以为那条流水线没了）。缩进一格 = "它跟上面那条正在跑的有
      关系"，不是在分等。 */
.qrow.qhold { padding-left: 26px; }
/* ★★ 2026-09-29 用户：「点击队列任务时，跳到队列对应的题目的编辑界面」——
   队列里每一条现在都点得开（跳那部片子 + 它正在跑/要跑的那一步）。
   两种行（正在跑 `.run` / 排队的一条）共用这一条：`cursor: pointer`
   + 悬停给点底色，让人**看得出能点**（不能点的那些——出选题、终端里手跑的——
   后端没给片名，`paintQueue` 就不挂 `.link`，长相与原样一致）。 */
.qrow.link { cursor: pointer; border-radius: 6px; }
.qrow.link:hover { background: var(--grpbg); }

/* 选材要求那两行（选题池）：上面是输入框，下面是已经记下的那几条 */
.reqrow { margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--line); }
.reqchips { row-gap: 6px; }
.reqchip {
  display: inline-flex; align-items: center; gap: 4px; padding: 2px 4px 2px 10px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--bg);
  font-size: 14px;
}
.reqchip .reqx {
  border: 0; background: none; padding: 0 4px; color: var(--dim);
  font-size: 15px; line-height: 1; border-radius: 999px;
}
.reqchip .reqx:hover { color: var(--bad); }

/* ---- 提示词面板（选题池）：这一批**实际发给模型的**那份提示词，逐条摆开 ----
   用户 2026-09-26：「选题池界面可查看当前选题所有提示词，按条目分解开
   （每条前面有-号可删除），新提的要求自动合并到提示词并展示出来」。
   每条前面那个「−」就是删 —— 删掉的记进 `_选题/提示词删减.md`，
   下一批就真的不带了（删掉的那条会灰掉并变成「＋」可恢复）。 */
.pitem {
  display: flex; gap: 8px; align-items: flex-start;
  padding: 6px 0; border-bottom: 1px dashed var(--line);
}
.pitem:last-child { border-bottom: 0; }
.pitem > .px {
  flex: none; width: 24px; height: 24px; padding: 0; line-height: 1;
  border-radius: 999px; color: var(--dim); font-size: 16px;
}
.pitem > .px:hover { color: var(--bad); border-color: var(--bad); }
.pitem.cut > .px { color: var(--accent2); }
.pitem.cut > .px:hover { color: var(--accent2); border-color: var(--accent2); }
.pitem.cut > .pbody { opacity: .42; text-decoration: line-through; }
.pitem > .pbody { flex: 1; min-width: 0; }
.pitem .plabel { font-size: 13px; color: var(--dim); }
.pitem .ptext { font-size: 14.5px; white-space: pre-wrap; word-break: break-word; }
.pitem.req .plabel { color: var(--accent); font-weight: 600; }
/* ---- 作品页顶上那块：**两行**（用户 2026-09-27 [图]：「这个导航栏是在「和珅」
   「在编辑」「横屏」几个按钮下面一行的，导航栏是单独一行的」）。
   第一行 = 片名 + 状态牌 + 平台牌；第二行 = 阶段条（整行**等宽**、放不下的裁掉）。
   · 简介那一句整段撤了（同一天：「这段不要了」）；
   · 原来那四颗按钮全撤了（同一天：「这部分按钮全都不要了」）；
   · `.eprow2` 那一行整个没了 —— 阶段条曾经搬进片名那一行，同日又被要求**单独一行**。
   ⚠️ 靠 `flex-wrap: wrap` + 阶段条 `flex-basis: 100%` 分行：不用多包一层壳，
      顺序（片名在前、阶段条在后）就是行的顺序。 */
.eprow { display: flex; align-items: center; gap: 10px; row-gap: 8px; flex-wrap: wrap; }
.ephead h1 { margin: 0; font-size: 21px; display: flex; align-items: center; gap: 8px;
  flex: 0 1 auto; min-width: 0; overflow: hidden; flex-wrap: nowrap; }
/* ★★ 简介那一句**整段撤了**（用户 2026-09-27 [图]：「这段不要了」）。 */
/* ★★ 顶上那块现在是**固定一行**（片名 + 平台牌 │ 阶段条）：搬家那颗按钮搬去列表页了
   （用户 2026-09-27：「放到（列表页）「全选本页」按钮左侧」），那颗 ▸ 三角标更早就撤了
   （同一天：「导航条右侧的三角按钮删除」）。 */
/* ★★ "展开"这一档**整个撤了**（用户 2026-09-27：「导航条右侧的三角按钮删除」）——
   阶段条不再有换行铺开的样子，就一直是上面那行等宽的；手机上仍由 `html.narrow .stages`
   换行铺开（那不是"展开"，是那一行本来就在窄屏放不下）。 */
/* ★★ 阶段条：**每一格一样宽**（用户 2026-09-27 [图]：「这块按钮做成等宽的」）——
   用 grid 的 `minmax(0, 1fr)`：`0` 那一头是关键（光写 `1fr` 会被内容撑开，等不了宽）。
   **自己占一整行**（同日：「导航栏是单独一行的」）：`flex-basis: 100%` 把它顶到第二行，
   一行等宽、放不下的在格子里省略号（用户说的"平时可以显示不完整"）；
   外面那个框是同日「导航条有个框包起来，不然看着有点散」。 */
.stages { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr);
  gap: 6px; flex: 1 1 100%; min-width: 0; overflow: hidden;
  border: 1px solid var(--line); border-radius: 10px; padding: 4px; background: var(--grpbg); }
/* ★★★ 「日志」和「大模型调用」那两颗**虚线格子撤了**（2026-09-28 用户第 31 条：
   「日志按钮和大模型放到最顶部吧，因为日志和大模型调用是全局的」）。
   它们从阶段条挪到了顶上那条导航（`index.html` 的 `<nav class="top">`，用 `.sm`
   那颗按钮的样式，和「↻ 重启服务」同一族）—— **别再画回阶段条里**：
   阶段条上每一格都是"这一部片子的第几步"，而这两件事跟片子、跟步骤都无关。 */

/* 阶段条一格**一行**（原来是名字一行、数字一行，白高一倍）；
   它现在在第一行里**等宽排**（见上面 `.stages` 的 grid），窄屏自动换行。 */
.stage {
  border: 1px solid var(--line); background: var(--card); border-radius: 8px;
  padding: 4px 9px; cursor: pointer; line-height: 1.35;
  display: flex; align-items: baseline; gap: 5px;
  /* ★★ 等宽之后格子会比分窄（手机上尤其）⇒ 允许它比内容还窄：名字在格里省略号，
     数字（`.n`）不缩 —— 一眼能看出是几件。 */
  min-width: 0; overflow: hidden; justify-content: center;
}
.stage > div:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* ★★ 2026-10-11：图文页（`paintPic`）那一格的名字用的是 `<span class="nm">` 而不是
   `<div>`（图文那一串是自己拼的，用 span 更顺）—— 规则必须**两样都覆盖**，
   否则图文页的名字不省略号、长标签会把格子顶宽，而同一排的格子宽度就不一致了。 */
.stage > .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  min-width: 0; }
.stage > .nm, .stage > div:first-child { flex: 0 1 auto; }
.stage .n { flex: 0 0 auto; }
/* ★★ 2026-10-11：`picGoStage` 点一格之后给对应那一块加 `.flash` —— 闪一下告诉人
   「这一步的产物/按钮在这一块」。2 秒后由 JS 摘掉（别做成永久高亮：那会跟 `.stage.on`
   的"当前这一步"混起来）。 */
.flash { animation: flashPulse 1.4s ease-out 1; }
@keyframes flashPulse {
  0% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 55%, transparent); }
  100% { box-shadow: 0 0 0 3px transparent; }
}
/* ★ 2026-09-26 用户：「这块功能电脑端和手机端都不要了」（截图：「从 [阶段] 到 [阶段]
   ☐ --force ▶开始」那一整条）—— 原来给那条写的两条样式跟着删了：HTML 里已经不画它，
   留着样式只会让下一个人以为还有这个块。
*/
/* 作品页里「这一步」那张卡：**就一行**（用户 2026-09-25：右边全是空的，太浪费）。
   名字 · ▶跑这一步 · 这一步的开关 · ✎提意见 │ 可改：… ➜ 右边目录信息 · 折叠。
   原来挤成三行（标题 / 选项 / 配置），三行加起来右边一大片空地。 */
.opcard { padding: 8px 14px; }
/* ★★ 窄屏那条**展开「这一步」的窄条**（用户 2026-09-26：「[这张卡]把屏幕几乎都占了，
   得想办法让这块区域隐藏掉，用的时候才弹出来」）—— 手机上卡默认不画，原地只留这一条。
   ⚠️ 宽屏 `display:none`（那时卡就在左栏里摆着，不需要这条）；窄屏那一支再打开它。 */
.opbar {
  display: none; align-items: center; gap: 8px; cursor: pointer;
  padding: 6px 12px; margin: 12px 0;
  /* ⚠️ 窄屏它钉在顶上（`.opbar` 那条媒体查询里），底下的内容会从它下面钻过去 ⇒
     必须给底色，不能透明。 */
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: 8px; background: var(--card);
}
.opbar > b { font-size: 15px; }
.opbar .opbarend { font-size: 14px; color: var(--accent); white-space: nowrap; }
/* ★★ 这一行改成**竖着一列**（用户 2026-09-26：「不是让你重新排版吗…这部分太丑了」）。
   这张卡永远坐在 350~420px 宽的地方（宽屏=左栏，手机=整屏）—— 几个方块横着摆一定折得
   七零八落：标题挤在第一个按钮旁边、每块各占一行、右边参差不齐、`可改` 那排还剩下一个
   孤零零的「全局·封面」。现在：**标题自己一行 → 每个动作一整块（占满宽度）→
   提意见 / 可改 各一块**，左边一条竖线齐到底。
   ⚠️ `.opbody` 不再是 `display:contents` —— 它自己就是一列（"折起来"那套 2026-09-26 已删，
      它现在就管"竖着排"这一件事）。 */
.opline { display: flex; flex-direction: column; align-items: stretch; gap: 7px; }
.opline > b { font-size: 17px; }
.opbody { display: flex; flex-direction: column; align-items: stretch; gap: 7px; }
/* ★ 复选框统一用主题色（用户 2026-09-26：「字体颜色…看着混乱」）——
   从前勾选框的 `accent-color` 谁都没设，跟着系统/浏览器走，同一行里几颗勾颜色都不一致。 */
input[type=checkbox], input[type=radio] { accent-color: var(--accent); }
/* 方块里那颗**说明性小标签**（「顺带」「可改」）—— 做成小胶囊，一眼看出"我在管右边这一组"，
   而不是一串跟按钮同级的灰字（用户 2026-09-26：「看着不醒目，有点混乱」）。
   ⚠️ 它只是**说明**，不是控件：不设 `onclick`、不给 hover，免得看着像能点。 */
.gcap {
  flex: none; font-size: 13px; line-height: 1.7; color: var(--dim);
  background: var(--bg); border: 1px solid var(--line); border-radius: 6px; padding: 0 7px;
}
/* ★ 不折的时候，`.opbody` 的孩子就是 `.opline` 的**直接成员**（不另起一行）；
   折起来（JS 设 display:none）整行只剩名字 + 目录信息。 */
.opbody { display: contents; }
/* ★★ 一个方块 = **一个动作 + 只有它才读的开关**（用户 2026-09-26：「每个按钮与后面相关的
   组件放到一起，比如，仅做封面是不影响视频的，所以后面不需要有复选框，重新整理每个步骤下的
   组件分组」）。竖排之后**每块占满整行**（`display:flex` 不再是 `inline-flex`）：一列方块，
   左边对齐成一条竖线，块里放几个控件由 `app.js` 的 `opGrp` 决定 —— 判据是"谁读这个控件"。
   ⚠️ 从前是 `inline-flex` + 整行 `flex-wrap`（横着摆、自动折行）：在 350~420px 宽的地方
      折出来就是一堆参差不齐的块（用户 2026-09-26：「这部分太丑了」）。 */
.opgrp {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 6px 10px; border: 1px solid var(--line); border-radius: 9px;
  background: var(--grpbg);
}
/* ★ 「▶跑这一步」那一格是**主角**，边框带一点主题色 —— 否则它跟「出联系表」那种
   顺手一按长得分不出轻重（用户 2026-09-26：「看着不醒目」）。⚠️ 只动边框色，
   不换底色：底色一重，里面那颗已经上色的按钮就被压住了。 */
.opgrp.main { border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }
/* ★★ 一格**明着竖着摆两排**（用户 2026-09-26：「重新排版，太丑了」）：`.grprow` = 一排。
   「▶ 做备选片头」那一格里按钮 + 那颗勾 + 「片头配音用第几版」+ 下拉，横着一条在手机上
   摆不下 ⇒ 从前靠 `flex-wrap` 在盒子里乱折，折出来是"长标签一行 / 下拉和勾又一行"，
   字和框对不齐。现在由 app.js 明着分成两排，这儿只管让它竖着排、每排自己再对齐。 */
.opgrp.col {
  flex-direction: column; align-items: stretch; gap: 5px; padding: 5px 10px;
}
/* ★ 台词表那一块（「重音/停顿」弹窗里）：一行两排 —— 上排是字，下排是参数。
   ★★ 2026-09-26 用户 图 #74：「每一行之间的分隔线太不明显了，最好每一行的相关组件
   加个组控件分组」⇒ **一行一个分组盒子**：浅底 + 一圈边 + 圆角，盒子之间留缝。
   从前行之间只一条 `border-top` 淡线，线色跟卡片底色差不离 —— 图里就是看不出
   "这几排控件属于第几行"。盒子里装的是**当前这一行**的全部：台词 + 语速·前置·停顿、
   🎚/▶ 两颗键、以及展开出来的那排在盒子里（tab 条上别的行只有一颗序号）。
   ★★ 2026-09-27 用户：「这个界面不需要编辑，就列出每句台词，然后点进去才编辑」
   ⇒ 上排那两个字输入框（`.wtl1`/`.wtl2`）和「✎ 润色」全撤了，改字在展开之后那个
     大框里（`weTxPane` 的 `#weTxTa`）。
   ★★ 同一天再改：「这个界面，做成 tab 页的形式」⇒ 那排台词换成 tab（见下面 `.wttab`），
     底下**只画当前这一行**那张卡。
   ⚠️ 类的名字还是 `.wtrow`（`app.js` 一个字不动，自检也认它）。 */
.wtbl .wtrow {
  border: 1px solid color-mix(in srgb, var(--dim) 34%, transparent);
  border-radius: var(--radius); background: var(--grpbg);
  padding: 8px 10px; margin: 0 0 9px;
}
.wtbl .wtrow:last-of-type { margin-bottom: 0; }
/* 行号 `#N` 做成小圆牌：盒子里第一眼看到的"这是第几行"的锚点
   （盒子靠边分得开，靠它认得出是哪一行） */
.wtn {
  font-size: 13.5px; font-weight: 600; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 13%, transparent);
  border-radius: 999px; padding: 1px 9px; min-width: 0;
}
/* ★★ 台词表那一条 **tab**（用户 2026-09-27 图：「这个界面，做成 tab 页的形式」）——
   一行一颗，上面**只有序号**（这一句的字在悬停提示里）；当前那颗实心、点别颗就切过去
   （底下那张卡整个换成它）。
   ⚠️ tab 上**不带删除键**（用户：「删除不要放在这里，太容易误触了，放在 tab 里，点击时
      需要确认」）—— 删这一句的键在当前那一句的编辑页里，按下去先问一句（`weTblDelAsk`）。 */
.wtbl .wttabs { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 2px 0 10px; }
/* ★★ 语速 / 试听 / 前置 / 后置 这一行 —— **提到 tab 条上面**（用户 2026-09-27 晚：
   「语速和前后置时间不属于某句话，提到前面」）。摆在句子的卡里，看着就像"这几个数
   是这一句的"，而「前置（整条片头）」「后置（片尾留白）」讲的明明是整条片头。
   ⚠️ 只是挪位置：**盘上照旧逐行**，切 tab 时这一行跟着换成那一句的数。
   底下那条线是"上面这几个数是整条片头的设置、下面才是逐句的"。 */
.wtbl .wtctrl {
  align-items: center; gap: 6px; padding: 6px 10px; margin: 2px 0 10px;
  border: 1px solid color-mix(in srgb, var(--dim) 34%, transparent);
  border-radius: var(--radius); background: var(--card);
}
.wtbl .wttab {
  display: inline-flex; align-items: center; gap: 5px; cursor: pointer;
  padding: 3px 9px; border: 1px solid color-mix(in srgb, var(--dim) 34%, transparent);
  border-radius: 999px; background: var(--card); font-size: 13px; line-height: 1.5;
}
.wtbl .wttab:hover { border-color: var(--accent); }
.wtbl .wttab.on { background: var(--accent); border-color: var(--accent); color: #fff; cursor: default; }
.wtbl .wts { width: 104px; }

/* ★★ 格子里那行说明（用户 2026-09-26 [图]：「这里加一行文字，配置片头配音的重音和停顿，
   只做片头视频前完成」）：比正文小一档、灰一档 —— 它是**注解**，不是又一个控件，
   别长得跟旁边的勾选标签一样重。不换行到天边：跟着格子宽度自己折。 */
.grphint { font-size: 13px; color: var(--dim); line-height: 1.5; }
/* 一格里的一排（`.opgrp.col` 用它分排）—— 排内自己再左对齐 */
.grprow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* 竖线：把**不同的功能组**隔开 —— 名字 │ 动作方块 │ ✎提意见 │ 可改：…
   （用户 2026-09-25：功能组之间要有线、还要有点距离，别扎堆） */
/* 竖线：把**不同的功能组**隔开（横排时用；选题池那一排还在用它） */
.opsep { width: 1px; height: 20px; flex: none; background: var(--line); margin: 0 6px; }
/* ★★ 预览那一条**钉住**（用户 2026-09-26 [图]：「这一行钉死」）——往下翻产物清单时，
   想换一份看不用再滚回顶上。
   ⚠️ 让开多少**分两种模式**：窄屏页面自己滚 ⇒ 让开导航 + 顶上那几行 + 那条手柄
      （和 `.pickbar` 同一个口径）；宽屏 `.maincol` 自己就是滚动容器（`html.lock` 把整页
      锁住了）⇒ sticky 的 top 是相对**右栏顶端**的，该让开的量是 0。
      `.stagegrid.two` 只在宽屏挂（窄屏 `syncPinOp` 会把它摘掉），拿它当分界就够，
      不用再去抄那个 1100 的断点数字。 */
.pvbar {
  position: sticky; top: calc(var(--navH) + var(--pinH, 0px) + var(--opH, 0px));
  z-index: 22; margin-bottom: 10px; box-shadow: 0 6px 14px -10px rgba(0,0,0,.5);
}
.stagegrid.two .pvbar { top: 0; }
/* ★★ 预览那一条**永远一行**（用户 2026-09-27 [图]：「这部分能不能让他显示在一行
   （无论手机还是平板）」）。分工：左边那句 `nowrap`（真放不下就省略号）、中间的
   `select` 自己缩（留 74px 底）、右边两颗键一个像素都不缩 —— 360px 的手机上也是一行。
   ⚠️ `.pvbar` 自己是 `.spread`（`flex`），不给 `nowrap` 的话窄屏它会折成两截。 */
.pvbar { flex-wrap: nowrap; }
.pvbar > h3 { flex: 0 1 auto; min-width: 0; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }
.pvbar > .row { flex: 1 1 auto; min-width: 0; flex-wrap: nowrap; justify-content: flex-end; }
.pvbar #pvSel { flex: 1 1 auto; min-width: 74px; }
.pvbar #pvRefresh, .pvbar #pvRaw { flex: 0 0 auto; white-space: nowrap; }
/* 手机（≤720）再挤一档：那半句"· 全量在下面的产物清单"让位（份数留着）、
   下拉框那个 74px 底降到 56、两颗键的衬距和字号各收一格 —— 360px 上也容得下这一行。 */
@media (max-width: 720px) {
  .pvbar { gap: 8px; padding-left: 12px; padding-right: 12px; }
  .pvbar .pvmore { display: none; }
  .pvbar > h3 { font-size: 15px; }
  .pvbar > h3 .tiny { font-size: 13px; }
  .pvbar > .row { gap: 6px; }
  .pvbar #pvSel { min-width: 56px; font-size: 13.5px; }
  .pvbar #pvRefresh, .pvbar #pvRaw { padding: 4px 7px; font-size: 13.5px; }
}
/* ★ 跳过去的那一条闪一下（不给反馈的话，"跳到了"和"本来就在这儿"长得一样） */
.fjump { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 9px; }

/* ★ 卡里那三根在竖排里改画**横线**（竖着一列时，1px 宽的竖线只会变成一个不起眼的小点） */
.opbody > .opsep { width: auto; height: 1px; margin: 1px 0; }
/* 「折叠」是卡上的按钮、不是"这一步跑什么"—— 竖排里别让它抻成一整行 */
#opFold { align-self: flex-start; }
/* 「这一步」那一行可以钉住（**默认钉**，用户 2026-09-25）—— 往下看产物时，
   跑这一步的按钮和开关一直在顶上，不用滚回去。让开上面钉住的导航和片头那几行。 */
.opcard.pin {
  position: sticky; top: calc(var(--navH) + var(--collH, 0px) + var(--pinH, 0px)); z-index: 24;
  background: var(--card); box-shadow: 0 8px 14px -10px rgba(0,0,0,.5);
}
/* ★★ 左右分栏（用户 2026-09-26：「把这一块和下面的内容做成左右结构的，因为这部分是
   不需要上下拉的，他应该是实时显示的，只有下面的预览区才需要上下拉」）。
   左栏 = 「这一步」那张卡，钉住；右栏 = 预览 / 逐场旁白 / 逐镜表 / 文件清单，跟着页面拉。
   两个 div 由 `app.js` 的 `splitStage()` 在渲染后搬节点生成，这里只管长相。
   ⚠️ 粘性挂在**栏**上、不挂在卡上：sticky 的包含块是**格子区域** —— 这一格有整栏
     那么高，粘得住；挂在卡上时包含块只有卡自己那么高，滚起来纹丝不动（等于没钉）。 */
.stagegrid {
  display: grid;
  /* ★ 2026-10-21 用户：「左右两栏一直显示到最下方，底下不要留那么大空白」——
     格子自己撑满 `--gridH`，两栏跟着拉到底。 */
  min-height: var(--gridH, 0);
  /* ⚠️ 左栏宽度写**死**（不再 `minmax(340px, 420px)` 跟着内容走）：卡一旦
     `position: fixed` 就脱离文档流，格子里的内容宽度变成 0 ⇒ 轨道会缩到 340。
     写死之后，卡在不在流里，右栏的位置都纹丝不动。 */
  grid-template-columns: clamp(340px, 30vw, 420px) minmax(0, 1fr);
  align-items: start;
  gap: 0 16px;
}
.stagegrid > .opcol, .stagegrid > .maincol { min-width: 0; }
/* ★★ 宽屏**锁成一屏高**（用户 2026-09-26：「左右没对齐，看着很难受，滚动条应该是
   位于右侧内部，只有这部分可以滚动」）。`--gridH` 由 `app.js` 的 `syncPinOp()` 量出来
   写；`.two` 也由它挂（量不下就不挂，见那边的注释）。
   ★ 锁住之后**页面这一趟不再滚**：两栏的上边界永远齐平（这就是"对齐"），左栏那张卡
   连 `position: fixed` 都不再需要 —— 不滚的东西本来就不会动。
   ★ 滚动的只有右栏（`overflow-y:auto`），滚动条就落在右栏自己的右边界内侧。 */
/* ★★ **整页不许滚**（用户 2026-09-26：「整个页面不要滚动条了，我无论在哪里移动滚轮，
   都是滚动这个区域」）。`html.lock` 由 `syncPinOp()` 挂/摘 —— 页面真的滚不动了，
   滚轮才不会"先把整页挪一下再轮到右栏"。`body` 那 124px 的下内距是留给底下那条
   固定作业栏的，锁屏时格子已经自己让过位，留着就是白白多出一屏的滚动量。 */
html.lock { overflow: hidden; }
html.lock body { padding-bottom: 0; }
.stagegrid.two { height: var(--gridH, auto); align-items: stretch; }
.stagegrid.two > .maincol {
  overflow-y: auto; overscroll-behavior: contain;
  /* 滚动条落在**右栏自己的右边界内侧**，而右栏的右边界跟上面的组件（`.wrap`）齐 ——
     所以不能再拿负外边距往外顶 10px，那样整条右栏比上面的卡片宽出 10px。 */
  padding-right: 10px;
}
/* ⚠️ 左栏**只留滚动的能力、不留滚动条**：卡比一屏高时它的下半截还得有地方去
   （页面已经不滚了）。滚轮先给右栏、右栏到头才轮到它（见 `wheelToMain`），
   所以这条"条"平时根本不出现，出现也只会是在右栏已经到底之后。
   ★★ 2026-09-28 用户第 28 条（平板跑任务时左栏一块空白）：**"平时根本不出现"
   正是问题所在** —— 它一旦被滚下去，屏上就是一块空白，人看不出"这东西被滚走了、
   还能滚回来"。改成 `thin` + 半透明、只在真溢出时占位：平时看着跟没有一样，
   真滚下去了右边就有一条细线，一眼知道是滚走了。
   ⚠️ 下面那条 `::-webkit-scrollbar { width: 0 }` 得跟着去掉，不然 Chrome/安卓平板
      （用户那块就是）照样一条都看不见。 */
.stagegrid.two > .opcol {
  overflow-y: auto; overscroll-behavior: contain;
  scrollbar-width: thin;
}
/* ★ 见上：那条"一点都看不见"的规矩撤了 —— 滚走了得看得出来（用户第 28 条）。 */
.stagegrid.two > .opcol::-webkit-scrollbar { width: 8px; }
.stagegrid.two > .opcol::-webkit-scrollbar-thumb {
  background: rgba(128, 128, 128, .45); border-radius: 4px;
}
/* 两栏**第一个盒子**的上边界对齐：`.card` 自带 `margin: 12px 0`，留在栏首就是
   左右差一格（用户看到的就是这个"没对齐"）。
   ⚠️ 必须写到**孙辈**：右栏第一个盒子是 `#pvCard` 这层壳（它自己没有外距），那 12px
   长在里面那张 `.card` 上；而 `#pvCard` 没边框没内距 ⇒ 外距**穿透**上来，量
   `#pvCard` 的上边界照样低 12px。只写一层 `:first-child` 是治不好的。 */
.stagegrid > .opcol > :first-child,
.stagegrid > .maincol > :first-child,
.stagegrid > .opcol > :first-child > :first-child,
.stagegrid > .maincol > :first-child > :first-child { margin-top: 0; }
/* ★ 2026-09-26：左栏第一个孩子现在常常是**那条窄条**（窄屏用；宽屏它是 `display:none`）
   ⇒ 卡不再是 `:first-child`，上面那条 `margin-top: 0` 就够不着它了，卡的上边界又低 12px、
   跟右栏对不齐（实测差 12px）。按 id 直接按住它，跟谁是第一个孩子无关。 */
.stagegrid > .opcol > #opCard { margin-top: 0; }
/* ★★ 「完全不动」（用户 2026-09-26：「滚动鼠标滚轮时，这个还是有点向上向下移动，
   能不能跟顶部那样做到完全不动」）。
   sticky 的**固有行程**是挡不住的：卡在文档里比导航低 280px，滚这 280px 它就一路往上
   爬 —— 只有 `position: fixed` 才是真的不动。left/width/top 由 `app.js` 的
   `syncPinOp()` 量出来写在内联样式上（量不到就不加这个类，见那边的注释）。
   ⚠️ 必须排在 `.opcard.pin` **后面**：两条同样两个类名，靠"后写的赢"盖掉它的 sticky。 */
.opcard.fixcard {
  position: fixed; margin: 0; z-index: 24;
  background: var(--card); box-shadow: 0 8px 14px -10px rgba(0,0,0,.5);
}
/* ⚠️ 这里**故意不给 `max-height`/`overflow`**（用户 2026-09-26：「这里不允许跟着鼠标
   滚轮动，不要横向滚动条了」）。给过一版"卡比屏幕高就自己滚"—— 结果是鼠标一悬到左栏，
   滚轮就滚这一格而不是页面，还多出一条横向滚动条。宁可让它矮下去也不许它自己滚。 */
.stagegrid > .opcol.pin {
  position: sticky; top: calc(var(--navH) + var(--collH, 0px) + var(--pinH, 0px)); z-index: 24;
}
/* 窄屏（手机、只开半个窗口）不分栏：三个 div 一起 `display:contents` ⇒ 父子关系等于
   不存在，`#stagePanel` 还是原来那个块容器，那张卡照旧横跨全宽。
   ⚠️ 断点改这里就够 —— `app.js` 的 `stageTwoCol()` 是去量 `display` 的，没抄数字。 */
@media (max-width: 1100px) {
  .stagegrid, .stagegrid > .opcol, .stagegrid > .maincol { display: contents; }
  /* ★★ 手柄 = 窄屏唯一常驻的那一条，钉在导航底下（`--pinH` 在窄屏是 0：顶上一整块
     不钉了，钉住的就这一条，见 `syncPin`）。 */
  .opbar { display: flex; position: sticky; top: var(--navH); z-index: 30; }
  /* ★★ 顶上那几行（片名/阶段条/从…到…/开始）和「这一步」那张卡**一起收、一起展**
     （用户 2026-09-26：「这两部分，一起展开一起隐藏吧，不要最上面的折叠按钮了，
     展开时是从上向下展开」）。两块共用一个 `.open`，所以不会一半在上一半在下。
     ★ 「从上向下」= 它们就在**文档流里**、紧着手柄按顺序摊开（不是从屏幕底下浮上来的
       弹层）—— 展完接着往下滚，它们自己就滚走了，预览照旧看得见。
     ⚠️ `.open` 那两条必须排在 `:not(.open)` **后面**（同样字号的选择器，靠后写的赢）。 */
  /* ★★ 2026-09-27 用户：「手机端，折叠仅折（那条横幅），这个导航条是要一直显示的」
     —— 从前窄屏是**整块一起收**（`.ephead:not(.open)` 直接不画）。现在：
     · 顶上那几行**一直在**（片名 + 阶段条要一直看得见 —— "折"不该把导航折走）；
     · 收/展只动那条**横幅**（在编辑·待确认 / 已发布·还没搬家 那种提示）；
     · 「这一步」那张卡照旧跟着收/展（窄屏它是"弹出来看的"，一展就占满一屏）。
     ⚠️ `.open` 那两条必须排在 `:not(.open)` **后面**（同样字号的选择器，靠后写的赢）。 */
  html.narrow .ephead { display: block; position: static; animation: none; box-shadow: none; }
  html.narrow .ephead:not(.open) .banner { display: none; }
  html.narrow .opcard:not(.open) { display: none; }
  html.narrow .opcard.open {
    display: block; position: static; animation: dropTop .18s ease-out; padding: 8px 14px;
  }
  /* ★★ 2026-09-30 用户（附宽屏截图）：「背景音乐设置，**手机端和平板端**页得做成这种格式，
     **单按钮 + 预览窗口**」。
     背景音乐这一步的卡**只有一颗按钮 + 一行"现在设的是什么"**（控件全在点开的那个框里）
     —— 它就是用户要的那颗按钮本身。窄屏却被上面那条 `:not(.open)` 一起折没了
     （实测 390 宽：只剩一个视频框；平板 834 同样）⇒ 这一步的卡**常驻**。
     ⚠️ 必须排在 `html.narrow .opcard:not(.open)` **后面** —— 两条同分（0,3,1），靠后写的赢。
     ⚠️ 只加在这张卡上（`.bgmcard`）：通用那条是别的步骤"弹出来看"的根，不许动。
     ★ 卡常驻了 ⇒ 这条手柄没东西可折（点它只会翻一张已经展着的卡，而上面还写着
       「▾ 展开设置」= 假话）⇒ 一并收掉。`syncPinOp` 那边本来就写着
       「`ob` 量到 `display:none` 就退回卡的实测高度」⇒ `.pickbar` 的 `--opH` 照样让得对。 */
  html.narrow .opcard.bgmcard {
    display: block; position: static; padding: 8px 14px;
  }
  html.narrow .opbar.bgmbar { display: none; }
  /* ★★★ 2026-10-12 用户：「图文作品编辑页，改成这种风格啊」—— 图文页左栏是
     **一列卡**（文案/分镜出图/压字/音乐/打包），而上面 `html.narrow .opcard:not(.open)`
     那条是视频页的口径（左栏**只有一张卡**，折了还有顶上那条手柄能点开）。
     图文页折掉就是**整列消失**，而它根本没有 `#opBar` 那条手柄
     （实测 900 宽：`.opcol` 里 5 张全 `display:none`）⇒ 这一类卡**常驻**。
     ⚠️ 必须排在 `html.narrow .opcard:not(.open)` **后面** —— 同样两个类名，靠后写的赢。
     ⚠️ 只加在 `.picop` 这一类上：通用那条是视频页的根，不许动。 */
  html.narrow .opcard.picop {
    display: block; position: static; padding: 8px 14px;
  }
  /* ★★ 「重配中把左栏整块收掉」那一条**撤了**（2026-09-28 用户第 30 条：「跑任务的过程中，
     平板的左侧区域**还是**不显示」—— 同一件事第三次报上来了，就是它干的）。
     ⚠️ 口径记在这儿，**别再往回加**：作业跑着的时候左栏（那张卡 + 那条手柄）**照旧在**。
        窄屏要把它收起来，是上面 `html.narrow .opcard:not(.open)` 那条既有规则的事
        （点了手柄展开就一直在）—— **不许**再按"S.runStage 是什么"另藏一次：
        那一藏连手柄一起没，人连展开的地方都找不到，整块空白就只能报"左侧不显示"。 */
  /* 卡上那颗「折叠」不画了（手机上那一整块由顶上那条手柄管） */
  #opFold { display: none; }
  /* ★★ 手机上阶段条那 10 格一排放不下（用户 2026-09-27：「导航条要一直显示」）⇒
     换 grid 的行流、格子最小 74px，铺成两排；`overflow: visible` 是因为窄屏下
     宁可让它多占一行，也不许把某一格裁掉。 */
  html.narrow .stages { grid-auto-flow: row; grid-template-columns: repeat(auto-fit, minmax(74px, 1fr));
    flex-basis: 100%; overflow: visible; }
}
/* 展开时从上往下落一下（用户 2026-09-26：「展开时是从上向下展开」） */
@keyframes dropTop { from { opacity: 0; transform: translateY(-10px); } }
/* ★ 这一步的**常驻小字**（三段：能改 / 怎么改 / 改完）—— 用户 2026-09-26：
   「在每个步骤上方，增加个简短的说明」。它是 `.opline` 的下一个兄弟，
   所以落在按钮那一行下面；又在 `#opCard` 里面，所以跟着「📌 钉住」一起钉住。
   ⚠️ 三段一定**都在**（后端 `STAGE_NOTES` 是定长的元组，缺一段就是改错了）。
   ★★ 2026-09-26 重做版式（用户：「排列布局（对齐）和字体颜色等重新设计一下，
   看着不醒目，有点混乱」）：**两列网格** —— 左边标签列（定宽、主题色、加粗），
   右边正文列。三行正文于是**对齐成一竖线**；原来每行行内加粗、正文起点各不相同，
   通篇一个灰、糊成一片。底色用 `--grpbg` + 左边一道主题色竖条，整块看得出是个"说明框"。 */
.stageNotes {
  margin-top: 10px; padding: 9px 12px 10px;
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: 8px; background: var(--grpbg);
  display: grid; grid-template-columns: auto 1fr; gap: 5px 10px;
  font-size: 14.5px; line-height: 1.6;
}
.stageNotes .k { color: var(--accent); font-weight: 600; white-space: nowrap; }
.stageNotes .v { color: var(--ink); min-width: 0; }
/* 备注（「「增强」和「核验」也在这一步里」那类）：同列、字色压一档，别跟三段正文抢 */
.stageNotes .v.note { color: var(--dim); }
/* ★ 2026-09-26 用户（手机）：「步骤卡默认折成一两行，点一下才展开，预览占满剩下的」——
   折起来只留**按钮那一行**（说明 + 怎么改.md + 目录一并收走）。那颗键在按钮行末尾，
   宽窄屏都有（宽屏在左栏里也够高，一样用得上）。 */
.opcard.fold .stageNotes,
.opcard.fold .howtorow { display: none; }
.stageNotes .arrow { color: var(--dim); margin: 0 5px; }
/* 「改完 → [配音] [出图] [成片]」里那几个步骤名：可点，点了跳过去 */
.nstep {
  font: inherit; font-size: 13.5px; line-height: 1.5; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 9%, var(--card));
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line));
  border-radius: 999px; padding: 1px 10px; margin-left: 5px; cursor: pointer;
}
.nstep:hover { background: color-mix(in srgb, var(--accent) 18%, var(--card)); }
/* 命令行版的操作说明（`怎么改.md`）—— 摆在常驻小字**正下方**（原来在面板最底部，
   手机用户根本翻不到）。展开才占地方，折着只留一行字。
   ★★ 2026-09-26（用户：「图中这一块移动到下面和怎么改.md同一行」）：目录信息不再挤在
   按钮那一行，改跟这行说明摆在一起。所以外距归 `howtorow`，`howtodoc` 自己不带 ——
   两处都带上就是双份留白。
   ★ 2026-09-26 再改：那一簇里的 🔄刷新 / 折叠 / 📌钉住 三样全去掉了（用户：「刷新按钮
   也去掉」「折叠、钉住按钮不要了，默认就是钉住」）⇒ 剩下的只有一行目录文字，
   flex 那一套没用了，改回**上下两块**（左栏只有 420px 宽，并排也是白挤）。 */
.howtorow { margin-top: 8px; }
.howtorow > .howtodoc { min-width: 0; }
.howtodoc > summary { font-size: 14px; color: var(--dim); cursor: pointer; }
.howtodoc > .mdview { margin-top: 6px; }

/* 「逐条让大模型润色」那块（片头台词 / 字幕清单 / 文案 的编辑弹窗里）。
   ★ 它**在文本框上面**、自己滚（`max-height`）—— 一份字幕清单有 79 条，
    不限高就会把文本框挤到屏幕外，用户改完一条不知道下面还有框要保存。 */
.linesHead { display: flex; align-items: baseline; gap: 8px; margin: 10px 0 2px; }
.lines { max-height: 30vh; overflow: auto; border: 1px solid var(--line);
  border-radius: 8px; padding: 4px 8px; background: var(--bg); }
/* ★★ 2026-09-28：润色那一块收进 `<details>`（长起来就是几十上百条，平铺着占掉 30vh，
   还把真正要改的文本框一路挤到折线以下 —— 用户：「现在不容易修改，方便修改」）。
   `summary` 上的 `display:flex` 会把浏览器默认那个三角吃掉，所以自己画一个
   （`.foldhint::before`，折着是 ▸、开着是 ▾）。 */
.linefold { margin: 2px 0 0; }
.linefold > summary { list-style: none; cursor: pointer; }
.linefold > summary::-webkit-details-marker { display: none; }
.linefold > summary .foldhint::before { content: '\25B8'; }
.linefold[open] > summary .foldhint::before { content: '\25BE'; }
.linefold > summary .foldhint { color: var(--dim); }
.lineitem { border-bottom: 1px dashed var(--line); }
.lineitem:last-child { border-bottom: 0; }
.linerow { display: flex; align-items: center; gap: 8px; padding: 4px 0; }
.lineno { flex: 0 0 auto; min-width: 74px; font-size: 13px; color: var(--dim); }
.linetext { flex: 1; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lineask { padding: 0 0 8px 82px; }
.lineask textarea { min-height: 60px; }
.cand { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 4px 0; border-top: 1px dashed var(--line); }
.candtext { flex: 1; min-width: 160px; color: var(--ink); }
.warnline { flex: 0 0 100%; color: var(--warn); }

/* 「只重做某几场」那个勾选表（弹窗里）—— 一场一行，场号 / 已出几张 / 旁白 */
.onlypick { max-height: 52vh; overflow: auto; border: 1px solid var(--line); border-radius: 8px; padding: 4px 10px; }
.onlyrow { display: flex; align-items: baseline; gap: 10px; padding: 4px 0; cursor: pointer; }
.onlyrow + .onlyrow { border-top: 1px dashed var(--line); }
.onlyno { flex: none; min-width: 76px; }
.onlysay { color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 这一步的目录（全路径挂在 title 上，鼠标一停看得全）。
   ★ 2026-09-26 用户：「超出的文字两行显示」—— 从前是 `nowrap` + 省略号，挤在左栏
   420px 里只剩「…/04_出图 · 3」，连文件数和体积都读不到。改成**最多两行**：
   第一行放得下就一行，放不下折行，再放不下才截断。`anywhere` 是给长英文目录名
   （`_备选`、`output`）用的 —— 不许它们把这一行顶宽、顶出横向滚动条。 */
.opdir {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere;
  font-size: 14px; line-height: 1.45; color: var(--dim);
}
/* 那一簇现在就剩这行目录了（🔄刷新 / 折叠 / 📌钉住 已按用户要求去掉）——
   壳留着是为了 `app.js` 里那句 `closest('.opside')` 和将来的加回。 */
.opside { margin-top: 4px; min-width: 0; }
.stage .n { font-size: 13px; color: var(--dim); }
/* 阶段条三态，一眼分得开：绿=已生成、红=有错、灰虚线=还没做 */
.stage.done {
  border-color: color-mix(in srgb, var(--accent2) 45%, var(--line));
  background: color-mix(in srgb, var(--accent2) 12%, var(--card));
}
.stage.done .n { color: var(--accent2); }
.stage.bad {
  border-color: var(--bad);
  background: color-mix(in srgb, var(--bad) 13%, var(--card));
}
.stage.bad > div:first-child, .stage.bad .n { color: var(--bad); font-weight: 600; }
.stage.empty { border-style: dashed; opacity: .62; }
/* 「做了一半」：黄。有产物但没做全（运镜 18/35 段）以前跟全做完一样是绿的 ——
   数字位置写 `18/35`，一眼看得出还差多少。 */
.stage.part {
  border-color: var(--warn);
  background: color-mix(in srgb, var(--warn) 12%, var(--card));
}
.stage.part > div:first-child, .stage.part .n { color: var(--warn); font-weight: 600; }
/* 「需要更新」：**品红 + 实线**。产物在盘上，但**上游变了**（换了图/改了字幕/重配了
   某几段音），它还是旧的 —— 与"做了一半"（黄、虚线、底色浅）**一眼分得开**：
   那个是自己没做完（接着跑就行），这个是别人动了（得回来重跑这一步）。
   用户 2026-09-25：「我重新生成了一张图片…运镜和成片和发布都得显示需要更新吧」；
   用户 2026-09-28：「要在导航栏的步骤按钮上用颜色和标识体现是否需要更新」。
   ⚠️ 描边必须**实线**、底色必须比 `.part` 更实 —— 两者同框时（一屏里既有没做完的
      又有旧了的）只靠色相区分是不够的，色觉障碍下会糊成一样。 */
.stage.stale {
  border-color: var(--stale);
  border-style: solid;
  border-width: 2px;
  background: color-mix(in srgb, var(--stale) 20%, var(--card));
}
.stage.stale > div:first-child, .stage.stale .n { color: var(--stale); font-weight: 700; }
/* 选中的那个：蓝框 + 光晕（放在三态后面，别被状态色盖掉） */
.stage.on { border-color: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 22%, transparent); }
/* 第四态：**正在进行**（蓝 + 呼吸），数字位置换成进度 `2/5`（`app.js:markStageRun`）。
   放最后 —— 它要盖过 .done 的绿和 .on 的选中色，否则跑起来那格看不出来。 */
.stage.run {
  border-color: var(--ok, #16a34a);
  background: color-mix(in srgb, var(--ok, #16a34a) 15%, var(--card));
  animation: stagepulse 1.1s ease-in-out infinite;
}
.stage.run > div:first-child, .stage.run .n { color: var(--ok, #16a34a); font-weight: 600; }
/* 第五态：**待办但还没成**（目前只有「发布」用）。用户 2026-09-25：
   "发布按钮绿色的前提是真发布，没发布就显示正在进行的图标"。
   `11_发布/` 里的文件是打包产物（预演也生成），有文件 ≠ 发出去了，
   所以没搬进 `_已发布/` 之前不许绿，用 ⏳ 的蓝格子表示"还等着发"。 */
.stage.todo {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, var(--card));
}
.stage.todo > div:first-child, .stage.todo .n { color: var(--accent); font-weight: 600; }
@keyframes stagepulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(22, 163, 74, .55); }
  50%      { box-shadow: 0 0 0 5px rgba(22, 163, 74, 0); }
}
/* ★★ 2026-09-28 用户：「电脑端页面，流水线在跑的过程中，没有呼吸灯，只有选中时外边框」
   —— 病根就是上面那条 `animation: none`：Windows 的「动画效果」关掉之后，浏览器报
   `prefers-reduced-motion: reduce`，于是**跑着那一格唯一的"正在跑"标记当场消失**，
   而平板（不报这个偏好）照旧在呼吸 —— 同一个界面两台设备两种样子，用户只会当成坏了。
   ⇒ 呼吸灯是**状态指示**（哪一步正在跑），不是装饰：这一条不再摘它。
   ⚠️ 幅度已经压得很小（4px 光晕、1.5s 一个来回），不会晃到人；真要照顾晕动，
      摘的该是别处那些位移/缩放动画（`dropTop` 那种），不是这个。 */

.shot {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden; display: flex; flex-direction: column;
}
.shot.sel { border-color: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 25%, transparent); }
.shot .imgbox { position: relative; background: var(--mediabg); }
.shot img { width: 100%; display: block; aspect-ratio: 16/9; object-fit: contain; cursor: zoom-in; }
/* 「还没出」的空格：**别留一块黑**（.imgbox 的底色是给图片做上下留边的纯黑，
   空格会像"黑框/坏图"—— 用户 2026-09-25 连着报了几回黑框）。这里盖成灰底 +
   虚线边 + 一行说得清的灰字，一眼就知道是"还没渲"。 */
.shot .noimg {
  aspect-ratio: 16/9; display: flex; align-items: center; justify-content: center;
  background: var(--bg); color: var(--dim); font-size: 14px;
  outline: 1px dashed var(--line); outline-offset: -5px; border-radius: 4px;
}
.shot .body { padding: 8px 10px; display: flex; flex-direction: column; gap: 6px; flex: 1; }
.shot .tag { position: absolute; left: 6px; top: 6px; background: rgba(0,0,0,.66); color: #fff; padding: 1px 6px; border-radius: 5px; font-size: 13.5px; }
.shot .pick { position: absolute; right: 6px; top: 6px; width: 22px; height: 22px; accent-color: var(--accent); }
/* ★★ 台词框：**固定两行高 + 滚动条**（用户 2026-09-27：「台词和提示词框都用固定高度，
   用滚动条上下拉，台词框高度是两行字体高度，提示词框是四行字体高度，台词和提示词
   字体大小改成一样大」）。
   ⚠️ 高度写成 `calc(2 * 1.65em)` 而不是一个像素数：`em` 只跟着上面那个 `font-size`
      走，而 `line-height` 是**纯数字 1.65**（从 `body` 继承，这里显式写死免得后面
      有人改 body 导致算式和实际对不上）⇒ 一个 `1.65em` 恰好就是一行的高度。
      改字号不用回来重算像素。
   ⚠️ 不加左右内距：`box-sizing: border-box`（style.css:30）下加了内距就等于凭空
      吃掉两行里的一条，短句也会冒出滚动条。 */
.shot .say {
  /* ★ 用户 2026-09-27/28（[Image #131]/[Image #132]/[Image #134]）：「运镜和出图预览区
     的台词字体变小点」「出图」和「合成成片」按钮下 台词和提示词的字体都变小点」——
     这一格是旁白、下面那格是提示词，两个原来都是 15px，一起降到 13.5px
     （高度按 em 算的，行数不变）。 */
  font-size: 13.5px; color: var(--dim);
  line-height: 1.65;
  height: calc(2 * 1.65em);
  overflow-y: auto;
}
/* ★★ 提示词框：**固定四行高 + 滚动条**，字号跟上面那句台词一模一样（用户同一条）。
   ⚠️ `+ 12px` 是上下内距和边框 —— `border-box` 下高度是连着 padding/border 一起
      算的，不补这一截就只露三行半。
   ⚠️ `resize: none` 是**故意的**：高度已经定死，能手拽就走样了。 */
.shot textarea {
  /* ★ 用户 2026-09-28（[Image #134]）：「我是让你把「出图」和「合成成片」按钮下 台词和
     提示词的字体都变小点」—— 上面那条只管了台词，这条是提示词框，一起降到 13.5px
     （高度按 4em 算的，行数不变；.shot 出图/运镜/合成三处共用这一条）。 */
  min-height: 0; font-size: 13.5px; line-height: 1.65;
  height: calc(4 * 1.65em + 12px);
  overflow-y: auto; resize: none;
}
/* ★★ 提示词那一栏**只看不写**了（用户 2026-09-28：「生图步骤预览区提示词改成不可编辑，
   增加编辑按钮」）—— 长相、高度、滚动条**照抄上面那个 textarea**（同一栏换了个元素而已，
   不该长高也不该变矮）。
   ⚠️ 它是个 `div`，得多两样：`white-space: pre-wrap`（不加的话提示词里的换行全挤成一行）
   、`word-break`（提示词里有长串英文/参数名，不折就横向溢出）。
   ⚠️ 底色压暗一档**是故意的**：长得和输入框一模一样，人会去点，点了没反应 ——
      "这是显示"必须一眼看得出来（旁边那颗「✎ 编辑提示词」才是能改的地方）。
   ⚠️ `.draft` = 改过、还没重出（见 app.js 的 `draftOf`）：描边换成 `--warn`，
      和格子下面那行小字配套，一眼看出来"哪几镜动过还没生效"。 */
.shot .promptro {
  min-height: 0; font-size: 13.5px; line-height: 1.65;
  height: calc(4 * 1.65em + 12px);
  overflow-y: auto; white-space: pre-wrap; word-break: break-word;
  background: var(--mediabg); border: 1px solid var(--line);
  border-radius: 8px; padding: 6px 9px; color: var(--dim);
}
.shot .promptro.draft { border-color: var(--warn); }
.shot .draftnote { color: var(--warn); margin-top: 3px; }
/* ★ 一格里那两个「版本」键：增强版 / 原图（`app.js:imgTabs`，用户 2026-09-26
   「预览图预览原图和增强图」）。⚠️ 类名**不能只用 `.tab`** —— 上面 `.tabs .tab`
   那套是**阶段页签**的长相（带下划线），挂在 `.imgtabs` 里会串味；这里自己一套。
   ⚠️ 当前那版要**一眼看得出**（`.on` 加底色），否则两版长得一样、点了像没反应。 */
.imgtabs { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.imgtabs button.tab {
  padding: 2px 9px; font-size: 13px; line-height: 1.5; cursor: pointer;
  background: var(--mediabg); color: var(--dim); border: 1px solid var(--line); border-radius: 999px;
}
.imgtabs button.tab.on { color: #fff; background: var(--accent); border-color: var(--accent); font-weight: 600; }
.imgtabs span.muted { font-size: 13px; }
/* ★★ 2026-10-10 候选条（用户：「重试过程中所有的图都保留，预览中可以轮播，
   然后我来选择用哪张」）。压在图片**底部**一条，别把格子撑高、别挡住画面。
   ⚠️ 只在有两张以上候选时才由 `candBar()` 生成 ⇒ 这里不必管"一张也显示"的情况。 */
.candbar {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; align-items: center; gap: 4px; flex-wrap: wrap;
  padding: 3px 5px; background: rgba(0, 0, 0, .55);
}
.candbar span.muted { color: #ddd; font-size: 13px; margin-right: 2px; }
.candbar button.cand {
  min-width: 22px; padding: 1px 6px; font-size: 13px; line-height: 1.5; cursor: pointer;
  background: rgba(255, 255, 255, .14); color: #eee; border: 1px solid rgba(255, 255, 255, .3);
  border-radius: 999px;
}
/* 正在看的那张：亮；**已选定用它的那张**：主色实心（两种状态长得不一样 ——
   "我在看"和"就要它"是两件事，用户要区分得开） */
.candbar button.cand.on { color: #fff; border-color: #fff; font-weight: 600; }
.candbar button.cand.picked { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
/* ⚠️ 候选条是 absolute 的 ⇒ 图片框得是它的定位父级。
   `.imgbox` 本来就有 `position: relative`（第 957 行的 `.shot .imgbox`）——
   **这里不再写第二条**：同一件事两处实现，改一处漏一处。
/* 配音那一步的逐场清单：一场一块，旁白 + 那一段的音 + 提意见 */
.scenelist { display: flex; flex-direction: column; margin-top: 6px; }
/* ★★ 用户 2026-09-27 [图]：「这个场x做的显眼一点，每一场之间的分割线也粗一点」。
   一行里那颗「场 N」是**主语**（这是第几场），别的 pill 是状态（灰的就够）——
   所以它实心浅底、主色描边、粗字，十几场里先认它。分割线从 1px 虚线加到 2px。 */
.pill.sc { font-size: 15px; font-weight: 700; color: var(--ink);
  background: var(--grpbg); border-color: var(--accent); padding: 2px 12px; }
.scenerow { border-top: 2px dashed var(--line); padding: 10px 0; }
.scenerow:first-child { border-top: 0; }
/* ★ 旁白原文是这一行的**正文**（用户 2026-09-27 [图]：「这个字体大一点，粗一点」）——
   上面那排是工具（小按钮）、下面是播放器，中间这句话才是要读的东西。 */
.scenerow .say { font-size: 17px; font-weight: 600; color: var(--ink);
  line-height: 1.55; margin: 5px 0 8px; }
/* ★ 预览片头上那条「改这一场」（用户 2026-09-27 [图]）—— 电脑和手机平板都要好按：
   一颗正常大小的按钮（不是 `sm`），窄屏就自己折行。 */
.pvsce { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 4px 0 8px; }
.scenerow audio { max-width: 420px; }
/* 这一段录音比整条旁白新（提意见/手改重配之后就是这样）：左边挂条竖线，
   好在十几场里一眼看出"刚动过的是哪几场"（用户 2026-09-25 要的提示） */
.scenerow.fresh { border-left: 3px solid var(--warn); padding-left: 10px; margin-left: -13px; }
/* 重音/停顿弹窗里那一句的字格子：一个字一格，点一下选中（见 app.js weRender）。
   `.on` = 现在选中的那几个字，`.has` = 这儿已经有标注了（省得标重了看不见）。 */
.wordgrid { display: flex; flex-wrap: wrap; gap: 2px; margin: 10px 0; line-height: 2; }
.wordgrid .ch { padding: 1px 5px; border: 1px solid transparent; border-radius: 6px;
  cursor: pointer; font-size: 15.5px; }
.wordgrid .ch:hover { background: rgba(127, 127, 127, .2); }
.wordgrid .ch.on { background: var(--accent); color: #fff; border-color: var(--accent); }
.wordgrid .ch.has { border-color: var(--warn); color: var(--warn); font-weight: 600; }
.wordgrid .ch.has.on { color: #fff; background: var(--warn); }
.wordgrid .ch.brk { margin-right: 9px; }
/* 格子右上角那两个字：`响` = 这个字上有重音，`停` = 这个字后面有停顿
   （用户 2026-09-26：「点击某个字的时候能不能把重音/停顿都显示出来，方便我参考」）。
   ⚠️ `vertical-align: super` 抬上去就够，别加 padding 撑高行高 —— 字格子是 flex 换行的，
      一格变高整片就跳。 */
.wordgrid .ch i.mk { font-style: normal; font-size: 13px; font-weight: 700; line-height: 1;
  padding: 0 2px; border-radius: 4px; margin-left: 2px; vertical-align: super; }
.wordgrid .ch i.mk.g { background: var(--accent2); color: #fff; }
.wordgrid .ch i.mk.p { background: var(--accent); color: #fff; }
.wordgrid .ch.on i.mk { background: rgba(255, 255, 255, .3); }
/* ★★ 「重音 / 停顿 / 改文字」这一屏原来是一墙同级文字（用户 2026-09-27 [图]：
   「主次要分明，不同的功能分组，分组有分组标题（说明分组功能）」）——
   一组一个框：一条实线分隔 ＋ 一条色底标题说清这组是干什么的 ＋ 一行小字说明。
   ⚠️ 标题**色底 ＋ 左侧粗色条**（`.wesel` 是"提醒"，这一条是"章节"，两种不能长一样）；
   ⚠️ 第一组不要顶上的线（它紧跟在贴顶的抬头底下，多一条线像被切开）。 */
.wgrp { border-top: 1px solid var(--line); padding-top: 10px; margin-top: 12px; }
.wgrp:first-child { border-top: 0; padding-top: 0; margin-top: 4px; }
.wgrp > .wt { font-size: 15px; font-weight: 700; color: var(--ink);
  background: var(--grpbg); border-left: 4px solid var(--accent);
  border-radius: 4px; padding: 4px 10px; margin-bottom: 6px; }
.wgrp > .wd { font-size: 13px; color: var(--dim); line-height: 1.6; margin-bottom: 7px; }
.wgrp > .wd b { color: var(--ink); }

/* 「选中：第 N~M 字『…』」+「这个字上有：…」那一块（用户 2026-09-26：「这里起码标注
   深色或者亮色」）。原来是 `.muted` 灰字，跟周围的说明混在一起看不出选了谁 ⇒
   给整块**彩色底 + 左边一道色条**，字用正文色；选中的那几个字**反白**成实心块。
   亮色模式：浅蓝底 + 深字；暗色模式：深蓝底 + 亮字。 */
.wesel {
  margin: 8px 0;
  padding: 6px 10px;
  border-left: 3px solid var(--accent);
  border-radius: 4px;
  background: rgba(47, 111, 237, .12);
  color: var(--ink);
  line-height: 1.8;
}
.wesel b.hl {
  background: var(--accent);
  color: #fff;
  border-radius: 3px;
  padding: 0 4px;
  font-weight: 600;
}
@media (prefers-color-scheme: dark) {
  .wesel { background: rgba(47, 111, 237, .26); }
}
/* 播放器右下角那个「📺 画中画」+ 手机上的 ⏪10s / 10s⏩（app.js 动态挂上去的）——
   浮在画面上、一眼看得见（用户 2026-09-26：放到右下角显眼位置）。父容器要是没有定位，
   JS 会给它临时加一个 position:relative（只给绝对定位当参照，不改布局）。 */
button.pipbtn, button.jumpbtn { position: absolute; bottom: 8px; z-index: 3;
  padding: 5px 11px; border-radius: 999px; font-size: 13px; line-height: 1.6;
  color: #fff; background: rgba(16, 18, 22, .68); border: 1px solid rgba(255, 255, 255, .34);
  backdrop-filter: blur(4px); opacity: .9; }
button.pipbtn { right: 8px; }
button.jumpbtn { left: 8px; display: none; }        /* 只有触屏设备才露出来（看下面那条 media） */
button.jumpbtn.b { left: 84px; }
button.pipbtn:hover, button.jumpbtn:hover { background: var(--accent); border-color: var(--accent); opacity: 1; }
button.pipbtn.on { background: var(--accent); border-color: var(--accent); opacity: 1; color: #fff; }
/* 手机上浏览器原生那排控件就贴在视频底部 —— 我们的浮层整体往上抬一档，别压住它。
   52px 时 ⏪5s 正好压在播放键上（用户 2026-09-26：「位置再往上一点，现在把播放按钮盖住了」），
   抬到 96px：够高过整条控件（含 iOS 那条），又不至于飘到画面正中。 */
@media (hover: none) {
  button.pipbtn, button.jumpbtn { bottom: 96px; }
  button.jumpbtn { display: inline-block; }
}
/* 长按 2 倍速 / 双击 ±10 秒 / 拖动进度时，画面中间闪一下的那个提示 */
.mediatip { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  padding: 6px 13px; border-radius: 10px; background: rgba(0, 0, 0, .72); color: #fff;
  font-size: 15px; font-weight: 600; white-space: nowrap; pointer-events: none;
  opacity: 0; transition: opacity .15s; z-index: 4; }
.mediatip.on { opacity: 1; }
.thumbcover { width: 100%; aspect-ratio: 16/9; object-fit: cover; border-radius: 8px; background: var(--mediabg); }
.bar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 40; background: var(--card); border-top: 1px solid var(--line); }
/* ★★ 2026-10-12：底下那条作业栏（`#jobbar`）的显隐**只走这一个类**，不再由各处
   零散地写 `style.display`（用户：「下边的状态栏显示在跑任务，但是上面的任务队列没显示
   ……他们应该是同时出现同时消失才对」）。
   ⚠️ `#queueBar`（顶栏）也是 `display:none` 收摊的，两栏的收摊/冒出统一由
      `syncBars()` 一个判据决定，见 app.js 那段注。 */
.hidden { display: none !important; }
.bar .wrap { display: flex; gap: 12px; align-items: flex-start; padding-top: 8px; padding-bottom: 8px; }
/* ★ 折起来的作业栏（2026-09-26 用户：「下面的日志显示支持折叠」）：整条压成**一行** ——
   片名 + 阶段 + 小字 + 那几个键并排，不再压着页面底下一块。
   ⚠️ 只是画法：作业照跑，`停止` 键**必须还在这一行里**（折了日志却叫不停 = 出事没法收）。
   类名由 `paintLog()` 挂（`S.showLog`，存浏览器本地）。 */
.bar.collapsed .wrap { align-items: center; }
.bar.collapsed .wrap > div {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  min-width: 0 !important;          /* 盖掉那一列写死的 250px（它原本是给竖排留的宽度） */
}
.bar.collapsed .wrap > div > .row,
.bar.collapsed .wrap > div > .tiny { margin: 0 !important; }
.bar.collapsed #jobMeta { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

pre.log {
  flex: 1; max-height: 150px; overflow: auto; margin: 0; padding: 8px;
  background: #0f1115; color: #d6dae2; border-radius: 8px;
  font: 13px/1.45 ui-monospace, Consolas, monospace; white-space: pre-wrap; word-break: break-all;
}
pre.log .bad { color: #ff8a8a; }
pre.log .head { color: #8fd0ff; font-weight: 600; }
pre.txt {
  max-height: 60vh; overflow: auto; background: var(--bg); border: 1px solid var(--line);
  border-radius: 8px; padding: 10px; white-space: pre-wrap; word-break: break-word;
  font: 14px/1.6 ui-monospace, Consolas, monospace;
}
/* ★★ 用户 2026-09-27：「这个片头台词是显示纯台词，就是为了方便看的，不可修改，
   字体大一点，粗一点，但是可以复制」——「大一点、粗一点」就是这条；「可以复制」
   靠 `user-select: text`（他要是想把字拖出来复制，得能选中）。 */
pre.txt.big {
  font: 700 20px/2 ui-monospace, Consolas, monospace;
  user-select: text; -webkit-user-select: text; -webkit-touch-callout: default;
  cursor: text;
}
/* .md 的预览（app.js 的 mdToHtml）—— 看着像文档，不像源码 */
.mdview {
  max-height: 62vh; overflow: auto; background: var(--card); border: 1px solid var(--line);
  border-radius: 8px; padding: 12px 16px; font-size: 15px; line-height: 1.75;
  word-break: break-word;
}
.mdview > :first-child { margin-top: 0; }
.mdview > :last-child { margin-bottom: 0; }
/* ★★ 2026-09-27（文案这一步，`app.js:showPreview` 挂的）：**去掉内框那道封顶**。
   上面两条 `max-height + overflow` 让正文自成一个滚动区 ⇒ 鼠标停在正文上滚的是内框、
   停在别处滚的是右栏，同一个滚轮两种结果；而"停在别处"那一下**正文根本没动**。
   用户要的是「在这个页面鼠标无论在哪个位置滚动都是滚动文案」⇒ 放开高度，
   全页只剩右栏一个滚动区，滚到哪儿都是文案在动。
   ⚠️ 别把这条挪成全局 —— 别的步骤（核验报告、字幕清单）是一次要看好几份的，
      放开就变成"翻两屏才看到第二份"。 */
.mdview.full, pre.txt.full { max-height: none; overflow: visible; }

.mdview h1, .mdview h2, .mdview h3, .mdview h4, .mdview h5, .mdview h6 {
  margin: 14px 0 6px; line-height: 1.35;
}
.mdview h1 { font-size: 21px; } .mdview h2 { font-size: 19px; }
.mdview h3 { font-size: 17px; } .mdview h4, .mdview h5, .mdview h6 { font-size: 15.5px; color: var(--dim); }
.mdview p { margin: 7px 0; }
/* ★★ 2026-10-08 用户：「预览窗口字体放大一点」
   （配合同一天的「只留章节标题和段落内容」）。
   ⚠️ 只放大**文案这一步** —— `.mdview.full` / `pre.txt.full`
     只有 script 用（app.js 的 `S.stage === 'script'`）。核验报告、
     字幕清单那些是一次要看好几份的，放大就变成“翻三屏才看到第二份”。
   ⚠️ **必须摆在上面那组 `.mdview h1..h6` 之后** —— 同优先级下
     后写的赢，摆前面会被 `21px` 覆盖，标题反而比正文小。
     （2026-10-08 实测栽过：第一版只写了 `.mdview.full {font-size}` 没写标题那几条，
      结果正文 19px、h3 还是 17px，**标题比正文小**。）
   ⚠️ 正文 19px / 行高 1.95：同一屏高度放得下的行数几乎没变
     （旧 15/1.75），但每行少了一截字 —— **这是有意的**，用户读得慢。 */
.mdview.full { font-size: 19px; line-height: 1.95; }
.mdview.full h1 { font-size: 26px; } .mdview.full h2 { font-size: 23px; }
.mdview.full h3 { font-size: 21px; }
.mdview.full h4, .mdview.full h5, .mdview.full h6 { font-size: 19px; }
/* ★ 2026-10-08 用户：「预览窗口章节标题加粗加黑」。
   章标题在这一页上是 `####`（`preview._demote` 降过级），所以落点是 h4。
   ⚠️ **加粗 + 加黑是两条**：上面那组通用规则给 h4/h5/h6 挂了 `color: var(--dim)`
      （灰的），只加 `font-weight` 的话标题会比正文**更淡**，看着像注解不像标题。
   ⚠️ 颜色用 `var(--ink)`（深浅两套主题各自的正文字色），**不写死** `#000`：
     写死的话深色主题下是黑字压深底，等于把标题弄没了。 */
.mdview.full h4, .mdview.full h5, .mdview.full h6 {
  font-weight: 800; color: var(--ink);
}
.mdview.full p { margin: 11px 0; }
pre.txt.full { font-size: 17px; line-height: 1.9; }
.mdview ul, .mdview ol { margin: 7px 0; padding-left: 22px; }
.mdview li { margin: 2px 0; }
.mdview blockquote {
  margin: 8px 0; padding: 4px 10px; border-left: 3px solid var(--line); color: var(--dim);
}
.mdview hr { border: 0; border-top: 1px dashed var(--line); margin: 14px 0; }
.mdview code {
  background: var(--bg); border: 1px solid var(--line); border-radius: 4px;
  padding: 0 4px; font: 14px/1.5 ui-monospace, Consolas, monospace;
}
.mdview pre.mdcode {
  background: var(--bg); border: 1px solid var(--line); border-radius: 8px; padding: 10px;
  overflow: auto; white-space: pre-wrap; word-break: break-word;
  font: 14px/1.6 ui-monospace, Consolas, monospace;
}
.mdview table.mdtab { border-collapse: collapse; margin: 8px 0; font-size: 14px; }
.mdview table.mdtab th, .mdview table.mdtab td {
  border: 1px solid var(--line); padding: 4px 8px; text-align: left; vertical-align: top;
}
.mdview table.mdtab th { background: var(--bg); }
.modal { position: fixed; inset: 0; background: rgba(0,0,0,.55); z-index: 60; display: flex; align-items: center; justify-content: center; padding: 18px; }
.modal .box { background: var(--card); border-radius: var(--radius); max-width: 1100px; width: 100%; max-height: 92vh; overflow: auto; padding: 16px;
  /* ★ 滚动条那条槽**一直留着**：不留的话"面板一开、滚动条冒出来"会让内容宽度
     整个缩 15px（用户 2026-09-27：「下拉框点击时宽度会变，不要变」）。
     ⚠️ 与 `weBgOpen` 那句"放不下就往上开"是同一件事的两道保险。 */
  scrollbar-gutter: stable; }
.modal .box > .spread { cursor: move; }         /* 按住这条能把弹窗拖开（挡着图写不了意见） */
.modal .box > .spread:active { cursor: grabbing; }
/* ★★ 「改稿」那一类弹窗（`app.js` 的 `bigModal`，用户 2026-09-28：「修改文案对话框
   重新设计，现在不容易修改，方便修改」）：**占满整屏 + 三段钉住** —— 抬头钉顶、
   文本框撑满中间、底下那排「保存 / 取消」钉底，滚的只有文本框自己、和里面那条
   润色清单自己那一条。
   为什么非要有：居中的那张卡片（`max-width:1100px` + 整张卡片自己滚）装不下
   「润色清单 + 文本框」，真正要改的那块被挤到折线以下 —— 得先滚过几十条清单才够得着，
   框还只有 340px 高。
   ⚠️ 这个类**只由 `bigModal` 挂**，而 `modal()` 每开一屏都会先摘掉它（同一屏接着
     开别的框就不会跟着满屏）。 */
.modal .box.bigbox { max-width: min(1500px, 97vw); height: 92vh; display: flex;
  flex-direction: column; overflow: hidden; }
.modal .box.bigbox > .spread { flex: 0 0 auto; }
/* 正文那个框**撑满剩下的一整块**（`min-height` 是给矮屏兜底的：真放不下时整张卡片
   自己滚，不至于把「保存」顶没）。`resize` 关掉 —— 它跟这套 flex 高度互相打架。
   ★★ 2026-09-28 用户：「字体加粗，字号加大，看不清楚」⇒ 改稿这框的字**要比全站
   默认大一号、加粗**（`--fs` 那套是给界面读的，这里是**读稿**，一屏少几行没关系；
   `line-height` 跟着放大到 1.75，不然加粗后行挤在一起更难认）。 */
.modal .box.bigbox > #epFileText { flex: 1 1 auto; min-height: 160px; resize: none;
  font-size: 19px; font-weight: 600; line-height: 1.75; letter-spacing: .2px; }
/* ★★ 弹窗**抬头不动**（用户 2026-09-27 图 #5：「**下拉时这里不动**」）——
   「重音 / 停顿 · 片头第 N 段」＋「关」滚上去就没了，表一长想关掉得先滚回顶上。
   ⚠️ 负的**左右**外边距 ＋ 自己的内边距：把 `.box` 那 16px 内边距**铺到抬头身上**，
      这样它贴顶时盖住的是整幅宽 —— 不然左右各留一条 16px 的缝，底下的字从缝里穿过去。
   ★★ 2026-09-27 用户 [图51]/[图54]：「文字又被盖住了」「为什么新作的对话框顶部都会
      盖住文字」—— 就是这块：`top: 0` 贴的是 `.box` **内容框**的顶（见下一条注释），
      而负的**上**外边距把抬头自己摆在**内边距框**的顶 —— 差着 `.box` 那 16px 内边距
      ⇒ 一开（`scrollTop: 0`）抬头就整体下移 16px，它那块不透明的底正好压住下面
      8px 外边距 ＋ 约 5px 行距 ＋ **3px 字顶**（无头 Edge 量的：头一行字高 11px、
      别的行 15px；把底改成透明、或去掉负的上外边距，字就全长回来了）。
      ⇒ **上**外边距去掉（左右留着）：这样它的正常位置跟贴顶那条线**正好重合**，
      不再有那个凭空的 16px 下移；头顶那 16px 缝由下面 `.spread:first-child::before`
      那块同色顶盖照旧填掉（滚起来时不漏字，已渲染验过）。
   ⚠️ 它是 `dragModal` 认的把手（`:scope > .spread`，见 app.js），只能改样式、别换元素。
   ⚠️ 背景必须是**不透明**的（`--card` 与 `.box` 同色）：透明的话它贴顶时下面的字透上来。
   ⚠️ `z-index` 比正文高、比弹窗里那些浮层低（`.ddpanel` 是 25、`.bgtlh` 是 2）——
      下拉面板该盖在抬头上面（盖不住就等于打不开）。 */
.modal .box > .spread {
  position: sticky; top: 0; z-index: 6;
  background: var(--card);
  margin: 0 -16px; padding: 16px 16px 10px;
}
/* ★★ 抬头上面那块**同色顶盖**（用户 2026-09-27 [图]×2：「下拉滚动条时，顶部怎么还能
   看到文字，标题栏应该是延申到顶部的，不应该漏字」＋「这里也一样」＋
   「还有其他滚动的地方应该也要修改」）。
   为什么需要它：`sticky; top:0` 贴的是 `.box` **内容框**的顶，而 `.box` 自己有 16px
   内边距 —— 抬头就停在离可见顶还差一截的地方，滚起来的正文从那一条缝里漏出来。
   这块盖子是抬头的伪元素（跟抬头同一个层叠层，盖得住下面的正文），从抬头往上长 240px：
   · 滚起来时 —— 它正好填掉那条缝，多出来的部分被 `.box` 的 `overflow` 裁在可见顶上，
     看上去就是"标题栏一直延到顶"；
   · 没滚（内容没超、或者刚打开）—— 它整块都在框外被裁掉，一个像素都不挡。
   ⚠️ 只能挂**第一个** `.spread`（`:first-child`）：弹窗里还有第二条 `.spread` 时，
      它的顶盖会盖住上面的内容。
   ⚠️ `background` 必须跟抬头一样（`--card`）：不一样就是顶上贴一条色差。 */
.modal .box > .spread:first-child::before {
  content: ''; position: absolute; left: 0; right: 0; bottom: 100%;
  height: 240px; background: var(--card);
}
/* ★★ 片头那三页的 tab 条（台词表 / 关键词 / 底图）**钉在抬头底下**（用户 2026-09-27：
   「下拉滚动条时，这三个 tab 按钮钉死」）—— 表一长，往下滚就看不见切页的入口了。
   ⚠️ `top` 不写死：抬头自己是 `top:0` 贴顶的，而它多高随窗口宽度变（标题会不会折行、
      三颗键会不会换行）⇒ 由 app.js 的 `weTabStick()` 量完抬头的高度写进来（变量默认 0，
      量不到时退化成"跟抬头一样贴顶"，压在上面总比飞走好）。
   ⚠️ 左右跟抬头一样铺满（负外边距 + 自己的内边距）：不铺的话左右各留一条 16px 的缝，
      底下的字从缝里穿上来。
   ⚠️ `z-index` 比抬头（6）**低**：抬头贴顶时不能被 tab 条压住；比正文高就行。 */
.wetabs {
  position: sticky; top: var(--wehead, 0px); z-index: 5;
  background: var(--card);
  margin: 10px -16px; padding: 4px 16px 0;
}
/* ★★ 「配音设置」那一屏的四页 tab（音色/语速 · 背景音乐 · 字幕样式 · 关键词标注）
   **同样钉在抬头底下**（用户 2026-09-28 [Image #138]：「这行tab钉死」）——
   六十多行的标注清单、一屏的样式控件，往下滚就看不见切页的入口了。
   ⚠️ `top` 由 app.js 的 `vcStick()` 量抬头的高度写进来（同 `.wetabs` 不写死一个数：
      抬头折不折行随窗口宽度变）；钉住的那块预览（`#subPrevWrap`）得**让开这一条**
      （它的 `top` = 抬头高 + 这条的高，见 `vcStick`），不然两块叠在一起、
      切页的入口被预览压住点不到。
   ⚠️ 左右铺满（负外边距 + 自己的内边距）、背景不透明：不铺的话两边各留一条 16px 的缝，
      底下的字从缝里穿上来。 */
#vcTabs {
  position: sticky; top: 0; z-index: 5;
  background: var(--card);
  margin: 10px -16px; padding: 4px 16px 0;
}
.modal img { max-width: 100%; display: block; margin: 0 auto; }
/* ★★ 跑着"整条片头"那几条作业的时候（生成预览 / 应用预览 / 重做片头 / 恢复初始值），
   **这一屏点不动**（用户 2026-09-27 [图]：「生成预览时，界面不允许操作，结束后释放」）：
   生成预览要跑几分钟，这中间手一抖再按一颗，就是两条作业叠着写同一批文件。
   见 app.js 的 `weBusySync`（判据只有 `S.we.reBusy` 一处，只挂/摘 `welock` 这个类）。
   ★★ **不是**罩一层大字遮罩（2026-09-27 晚用户 [图]：「你不要做成这样，我是说在生成
   预览过程中下拉列表框，和上面的节奏条都不允许操作（点击了都没反应）」）—— 那块
   "⏳ 正在生成预览…"横在屏幕正中，把要看的图、要读的字全盖住了。现在只掐能点的东西：
   ⚠️ 不能写成 `.box { pointer-events: none }` 一整块 —— 那连滚轮一起掐了，
   人等在几分钟里连往下翻看都不能，过头了。
   ⚠️ 也不能只给 `<button>` 加 `disabled`：这一屏能点的东西不只有按钮 ——
   「点台词那一句」（`.bgvo` 那个 div）、节奏条上拖的**把手**（`.bgtlh`）、下拉列表里
   每一项，全是挂在 div 上的 `onclick`/`onpointerdown`，`disabled` 一个都盖不住。
   ⇒ 按**能点的几块**点名拦：`.bgrows`（每一段那一整行，含下拉按钮/列表/台词/拖点）、
   `.bgtl`（节奏条）、`.bgaxes`（两条轴），再＋所有表单控件（保底：抬头那几颗按钮、
   别处漏进来的）。灰只给表单控件和节奏条（"看着就点不动"），列表那边不灰 ——
   图还得看得清，只是点不动。 */
#modal.welock .box button,
#modal.welock .box select,
#modal.welock .box input,
#modal.welock .box textarea,
#modal.welock .box .bgrows,
#modal.welock .box .bgtl,
#modal.welock .box .bgaxes { pointer-events: none; }
#modal.welock .box button,
#modal.welock .box select,
#modal.welock .box input,
#modal.welock .box textarea,
#modal.welock .box .bgtl { opacity: .45; cursor: not-allowed; }
/* ★★ 2026-09-30 用户：「在片头设置界面，点击生成预览或者应用预览的过程中，右上角的
   关闭按钮应该做成可以关闭的」—— 那几条作业要跑几分钟，人整个被锁在这一屏里，
   连关窗都点不动，想走只能干等。⇒ 那一颗「关」**照旧能按**：它是出路，不是"再开一条
   作业"（按了只是这一屏收起来，作业照跑 —— 底层进度框会自己立回来，见 `jobFloatOn`）。
   ⚠️ 只放这一颗：抬头里还挂着那几颗片头键（`#weHeadBtns`），它们**必须**继续锁着 ——
      跑着的时候再按一颗就是两条作业叠着写同一批文件，正是 `welock` 要拦的那件事。
   ⚠️ 写成 `.box button.weclose` 是**刻意比上面那条多一个类**：特律度更高，谁先谁后都赢。 */
#modal.welock .box button.weclose { pointer-events: auto; opacity: 1; cursor: pointer; }
/* ★ 第二层窗口（2026-09-26：日志正文也是弹窗）。只多一件事：**叠在第一层上面**。
   长相/内边距/能拖都照旧（`.modal .box` 那几条对它一样生效）。 */
.modal.dlg { z-index: 70; }
/* 第三层：压在「重音/停顿」那一屏上面的问句（`#ask`，见 app.js 的 `askLayer`）。
   ⚠️ 夹在第二层（70）和 toast / 悬浮进度框（80）之间 —— 提示必须仍然浮在最上面。 */
#ask { z-index: 75; }
.banner { border-radius: 8px; padding: 8px 12px; margin: 8px 0; border: 1px solid var(--line); }
.banner.warn { background: color-mix(in srgb, var(--warn) 12%, var(--card)); border-color: var(--warn); }
/* ★★ 「需要更新」那条横幅（`staleBanner`）—— 与阶段条那颗**同色**（`--stale` 品红）。
   同一条信息在两处两个颜色，人会当成两件事。从前这条跟 `.banner.warn`（做了一半）同橙。 */
.banner.stale { background: color-mix(in srgb, var(--stale) 12%, var(--card)); border-color: var(--stale); }
.banner.stale b { color: var(--stale); }
/* ★★ 这条横幅**常驻三态**（2026-09-28 用户第 3 条）—— 干净和"判断不了"也各有一个样子，
   不能只在脏的时候冒出来（那等于"没消息 = 没事"，而"没消息"也可能是"没记录"）。
   · `.fresh` 绿：判据说"没发现要重做的输入"；
   · `.plain` 灰：判据这次说不了话（老片子没记录、文件读不出来）。
   ⚠️ 这两态都**不许**用 `--stale`／`--warn` 的底色 —— 一上色就等于在喊"要更新"，
      跟真正脏的那态分不出来了。 */
.banner.fresh { background: color-mix(in srgb, var(--accent2) 8%, var(--card)); border-color: var(--line); color: var(--dim); }
.banner.plain { background: transparent; border-style: dashed; color: var(--dim); }
.banner.plain b { color: var(--fg); }
/* ★★ 阶段条底下那条「哪几步要更新」的汇总（`.impbar`，2026-09-28 第 3 条当天加的）
   **撤了** —— 用户：「[图] 这块文字区域不要了，那里需要更新，显示到每个步骤的下面」。
   理由：同一件事在"这一步"那一屏顶上（`.banner.stale`，`staleBanner`）本来就说全了
   （why / 要重做的场次 / 文案逐句对照表），汇总那条只是压成一行再念一遍。
   ⚠️ 别再往回加：要体现"该更新了"，用**阶段条那格的颜色**（`.stage.stale`）+ 点进去
      那一屏顶上那条横幅，两处够了。 */
/* ★★ 文案逐句对照表（`staleBanner` 画的，数据来自后端 `_narr_map`）。
   行底色按改动种类分：改写=黄、新增=绿、删除=红、小改=不着色（只是顺手改字）。
   ⚠️ 手机上一行放不下三列长句 ⇒ 表格自己横向滚，**不许**把字缩到 13px 以下
   （`_webui_check.py` 有一条"没有漏在 13px 以下的小字"）。 */
.narrtbl { width: 100%; margin-top: 6px; border-collapse: collapse; font-size: 14px;
  display: block; overflow-x: auto; }
.narrtbl th { text-align: left; color: var(--dim); font-weight: 600; white-space: nowrap; }
.narrtbl td { padding: 3px 8px 3px 0; vertical-align: top; border-top: 1px solid var(--line); }
.narrtbl td:first-child { white-space: nowrap; color: var(--dim); }
.narrtbl td:last-child { white-space: nowrap; color: var(--dim); }
.narrtbl tr.k-rw { background: color-mix(in srgb, var(--warn) 16%, transparent); }
.narrtbl tr.k-add { background: color-mix(in srgb, var(--accent2) 16%, transparent); }
.narrtbl tr.k-del { background: color-mix(in srgb, var(--bad) 14%, transparent); }
.narrtbl tr.k-del td:nth-child(2) { text-decoration: line-through; color: var(--dim); }
.banner.bad { background: color-mix(in srgb, var(--bad) 10%, var(--card)); border-color: var(--bad); }
.banner.info { background: color-mix(in srgb, var(--accent) 9%, var(--card)); border-color: var(--accent); }
/* 横幅里的**说明句**自己占一行（用户 2026-09-26：状态 + 说明 + 提示缝成一片糊字）。
   状态留在抬头上，说明从下一行起，按钮跟在后面。 */
.banner .sub { display: block; margin-top: 3px; }
.knob { display: grid; grid-template-columns: 190px 1fr; gap: 8px 12px; align-items: start; padding: 8px 0; border-bottom: 1px dashed var(--line); }
.knob .nm { font-weight: 600; }
.knob .note { color: var(--dim); font-size: 14px; }
.knob .ctl { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.knob input[type=text], .knob input[type=number] { width: 220px; }
.knob.ro { opacity: .62; }
.noteitem { border-left: 3px solid var(--accent); padding: 6px 10px; margin: 8px 0; background: var(--bg); border-radius: 0 8px 8px 0; }
.noteitem.done { border-left-color: var(--accent2); opacity: .65; }
.pill.hot { border-color: var(--warn); color: var(--warn); font-weight: 600; }
.pill.bad { border-color: var(--bad); color: var(--bad); }
/* ★ 2026-10-24 选题池「已开跑 / 已排队」那颗标 + 「↗ 去编辑界面」那颗键 */
.pill.ok { border-color: var(--ok, #16a34a); color: var(--ok, #16a34a); font-weight: 600; }
/* ⚠️ 手机窄屏下那颗键得看得见字 —— `.sm` 自带的底色会把 `color` 吃掉 */
button.sm.ok { border: 1px solid var(--ok, #16a34a); color: var(--ok, #16a34a);
  background: var(--card); font-weight: 600; }
.card.topic.running { border-left: 3px solid var(--ok, #16a34a); }
.pickbar { position: sticky; top: calc(var(--navH) + var(--collH, 0px) + var(--pinH, 0px) + var(--opH, 0px)); z-index: 20; border-color: var(--accent);
  box-shadow: 0 4px 14px rgba(0,0,0,.14); }
/* 顶上那几行（片名/简介/按钮/阶段条）**永远钉住** —— 用户 2026-09-25：往下拉看镜头时，
   阶段条上的进度数字得一直看得见。（2026-09-26 起不再有"钉不钉"那颗勾，见下面的 .fold。）
   `top: 54px` 是让开上面那条 sticky 的导航；下面 .pickbar 靠 --pinH 再往下让。 */
/* ★★ 2026-09-27 用户：「折叠按钮就不要了」＋「这里（阶段条）不参与折叠，永远显示」——
   `.ephead.fold` 这一支**整个撤了**：那颗勾不在页面上，`headFoldNow` 也删了。
   顶上那块现在只有一种"矮"的法子：那颗 ▸ 三角标（收着 = 一行，展着 = 阶段条换行铺开）。 */
.ephead.pin {
  position: sticky; top: calc(var(--navH) + var(--collH, 0px)); z-index: 25;
  background: var(--bg); padding: 4px 0 10px;
  box-shadow: 0 8px 14px -10px rgba(0,0,0,.5);
}
.topic details summary { cursor: pointer; }
.topic ol { margin: 4px 0 0 18px; padding: 0; }
/* ★★ 2026-10-09 用户（附截图）：「全局配置页面，这个 tab 表头是钉死的，不随滚动条滚动」
   —— 三格 tab 条做成 sticky（与全项目同一个口径 `top: var(--navH)`，见 `.batchbar`）。
   ⚠️ 背景要跟上：不然滚上来的卡片会从字缝里透出来。 */
.tabs { display: flex; gap: 6px; border-bottom: 1px solid var(--line); margin: 10px 0; flex-wrap: wrap; }
.tabs .tab { padding: 6px 12px; cursor: pointer; border-bottom: 2px solid transparent; color: var(--dim); }
.tabs .tab.on { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
/* 发布那张弹窗里的图（扫码 / 发布进度）。**白底**：二维码反色扫不出来，
   截图上深色主题下也得看得清。 */
.pubshot { margin-top: 10px; background: #fff; border: 1px solid var(--line);
  border-radius: 8px; padding: 8px; max-height: 72vh; overflow: auto; }
.pubshot img { display: block; width: 100%; border-radius: 6px;
  /* 长按要弹「保存图片」：不挡选字的话，手机弹出来的是「选择文字/复制」那个菜单
     （用户 2026-09-26：「点击保存到相册没反应」，长按再被选字抢走就真没路了）。 */
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: default; }

.toast { position: fixed; right: 16px; bottom: 108px; z-index: 80; display: flex; flex-direction: column; gap: 8px; align-items: flex-end; }
/* ★ `white-space: pre-line`：一条 toast 里可以**写两行**（片头那一屏就是这么用的：
   第一行"谁在跑 + 干什么"，第二行"跑完会怎样"，见 app.js 的 `jobToastOff` 那段）。 */
.toast div { background: var(--ink); color: var(--bg); padding: 8px 14px; border-radius: 8px; font-size: 14.5px; max-width: 460px; white-space: pre-line; }
/* ★ 重念那个悬浮进度框（见 app.js `jobFloat*`）：**抬到 `#toast` 上面那一层**
   ——两条都钉在右下角，同一个高度上必然叠在一起。两行字（进度 / 说明）用 pre-line 换行。 */
#jobFloat { bottom: 168px; }
#jobFloat div { white-space: pre-line; text-align: left; }
/* ★★ 「让大模型找新音乐 / 自动标注关键词」那两件活的进度框（见 app.js `aiFloat*`）：
   和 `#jobFloat` 同一套长相，再高一层（底下那条作业栏、`#jobFloat`、普通 toast
   三者都钉在右下角，位置错开才不叠）。**多行**：一行一步，`pre-line` 认 `\n`。 */
#aiFloat { bottom: 232px; }
#aiFloat div { white-space: pre-line; text-align: left; }
/* ★★ 2026-10-09 用户：「点击自动标注，**不要退出对话框**，给个进度提示」。
   `#aiFloat` 被挪进 `#modalBox` / `#askBox` 里面时（`setAiFloatIn(1)`）——
   那两条是 `.modal .box`（半透明卡面、能滚），fixed 定位会对齐**视口**而不是卡面，
   就飘到弹窗边上去了 ⇒ 改成钉在卡面内部的左下角。 */
#aiFloat.inbox { position: absolute; right: 14px; bottom: 14px; z-index: 5;
  max-width: min(420px, 60%); }
audio { width: 100%; height: 34px; }
video { width: 100%; border-radius: 8px; background: var(--mediabg);
  touch-action: manipulation; }   /* 手机上双击是"快退/快进"，别再触发浏览器的双击放大 */
/* ★★ 2026-10-13 用户（图）：「发布和贴纸的预览区域太小了，显示不完整预览视频」。
   根因：上面那句只管宽度（`width:100%`）**没有高度上限**，而右栏是
   `overflow-y:auto` + 整页 `html.lock` 不滚 ⇒ 16:9 按右栏宽度撑出来的高度
   超过可视区 ⇒ 上下被裁。同一屏里的 `.pvimg`（预览大图）早就写了
   `max-height: 72vh`，视频这一路一直漏着。
   ⚠️ **只加 `#pvBody` 这一支**，不动上面那条全站规则 ——
      `<video>` 这个项目里还有另外 5 处用途（出图网格、运镜段、
      片头预览、文件清单、备选预览），那些是格子里的小图，
      跟全站规则走才对。
   ★ 同样把 `audio` 一并算上：配音那一步预览的就是音频，
      它原本只有一条全站 `audio { height: 34px }`，这里不能反而把它拉高。
   ⚠️ `max-height` + `width:auto`：高度封顶后宽度跟着比例收、居中，
      竖片/方片时不会横向溢出（`max-width:100%` 兜底）。 */
#pvBody video {
  width: 100%; height: auto; max-height: 72vh;
  object-fit: contain; display: block;
}
/* ★ 音频另算：全站那条没给它高度上限，但它本来就只有 `height: 34px`，
   拿不到这里的 `max-height` 反而更安全 —— 不要把它拉高。 */
#pvBody audio { max-height: 72vh; }
/* 预览里的大图：整幅居中、点一下进放大镜（跟出图网格一个手势） */
.pvimg { max-width: 100%; max-height: 72vh; display: block; margin: 0 auto;
  border-radius: 8px; cursor: zoom-in; }
#pvSel { max-width: 58vw; }
/* 预览大图翻页（用户 2026-09-25：点左边上一张、点右边下一张）。
   三格轨道横着排：左=上一张、中=现在这张、右=下一张，靠 -100% 把中间那格停在正位。
   翻页时推一格 ⇒ 是"推拉"，不是原来的"啪一下换掉"（那样一闪一闪的很难看）。 */
.pvview { position: relative; overflow: hidden; }
/* ⚠ 轨道必须**显式 width:300%**，格子才是"三分之一 = 一个视图宽"。
   光靠 `flex: 0 0 100%` 的话容器自己还是 1 个视图宽（子项只会溢出、不撑大容器），
   于是 `calc(-100% / 3)` 只挪了三分之一格 —— 屏幕上就是"两张各露一半"。 */
.pvtrack { display: flex; width: 300%; transform: translateX(calc(-100% / 3)); }
.pvpane { flex: 0 0 calc(100% / 3); text-align: center; }
/* 这一格是哪一场哪个镜（用户 2026-09-25）。放在图**上面**、跟着格子一起滑 ——
   盖在图上会挡住画面，固定在轨道外面又跟不上推拉。没标签的格子留同样高度不跳。 */
.pvpane .pvtagname {
  font-size: 14px; color: var(--dim); padding: 0 8px 6px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 三块**各占三分之一**（用户 2026-09-25）：左 1/3 上一张、中间 1/3 放大、
   右 1/3 下一张。以前两块翻页区各只占 15%，中间那一大条全是"放大"，
   想点翻页得瞄边缘；现在整幅图从左到右就是 上一张 / 放大 / 下一张 三段，不用瞄。 */
.pvzone {
  position: absolute; top: 0; bottom: 0; width: 33.3333%; z-index: 2;
  display: flex; align-items: center; font-size: 34px; line-height: 1;
  color: #fff; opacity: 0; cursor: pointer; user-select: none;
  text-shadow: 0 2px 10px rgba(0,0,0,.85); transition: opacity .12s;
}
/* 渐变淡一点：一块占三分之一，压太深会把画面盖掉 */
.pvzone.l { left: 0; justify-content: flex-start; padding-left: 14px;
  background: linear-gradient(90deg, rgba(0,0,0,.3), transparent); }
.pvzone.r { right: 0; justify-content: flex-end; padding-right: 14px;
  background: linear-gradient(270deg, rgba(0,0,0,.3), transparent); }
.pvview:hover .pvzone { opacity: .9; }
.pvzone:hover { opacity: 1; color: var(--accent); }
.pvcount {
  position: absolute; right: 10px; bottom: 10px; z-index: 3;
  background: rgba(0,0,0,.62); color: #fff; border-radius: 999px;
  padding: 1px 10px; font-size: 13.5px; pointer-events: none;
}
@media (prefers-reduced-motion: reduce) { .pvtrack { transition: none !important; } }
.sep { height: 1px; background: var(--line); margin: 12px 0; }
@media (max-width: 720px) {
  .knob { grid-template-columns: 1fr; }
  .knob input[type=text], .knob input[type=number] { width: 100%; }
  .bar .wrap { flex-direction: column; }
  pre.log { max-height: 110px; width: 100%; }
}

/* ══ 片头**关键词** / 片头**底图**（换行弹窗里加的那两块） ═══════════════════
   ⚠️ 缩略图走的是**现成的**取图口 `/api/file?thumb=1`（`fileUrl()`），
     没新开取图路由。⚠️ 字号全走 `.tiny`/组件默认，别在 JS 里写死 px。 */
.ktags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.ktag {
  display: inline-flex; align-items: center; gap: 4px;
  background: var(--grpbg); border: 1px solid var(--line);
  border-radius: 999px; padding: 2px 3px 2px 10px; font-size: 14.5px;
}
.ktag b {
  cursor: pointer; color: var(--dim); font-weight: 700;
  padding: 0 5px; border-radius: 999px;
}
.ktag b:hover { color: #fff; background: var(--bad); }
/* ══ 「一屏几个词」（2026-09-28）═════════════════════════════════════════════
   用户：「这里支持指定多个关键词可以显示到同一屏幕」「多个关键词显示到同一屏幕，
   中间通过空格分开显示」「每个词加一颗『⤴ 同上屏』」「一屏最多 3~4 个」。
   · `.kscr` = **一屏** —— 并起来的那几个词包在同一个框里（外面还有一圈虚线底），
     一眼看出"这几个是一起出场的"；屏与屏之间自然由 `.ktags` 的 `gap` 分开。
   · `.kup` = 每颗词身上那颗「⤴」：**亮着** = 这个词并进了上一个词那一屏；
     灰掉（`.off`）= 上一屏已经并满 `per_screen` 个，点不动。
   ⚠️ 只加长相，判据和落盘全在 `app.js` 的 `weKeyToGroups` / 后端 `intro_key_groups`。 */
.kscr {
  display: inline-flex; align-items: center; gap: 4px;
  border: 1px dashed var(--line); border-radius: 12px; padding: 2px 6px;
}
/* ★ 2026-09-28：**正被点着的那一屏**（右边预览图画的就是它）。 */
.kscr.on { border-color: var(--accent); background: var(--grpbg); }
/* ★★ 2026-09-28 用户第 13 条：「平板无法选中关键词，**点击没反应**」—— 触屏上
   `:hover` 会**粘住**（点过的那个词一直亮着像是选中了），而且这上面的 × / ⤴
   也要"点一下就有反应"。`manipulation` 把双击缩放的 300ms 等待去掉，
   `-webkit-tap-highlight-color` 关掉那层灰flash（自己那套高亮才看得清）。 */
.ktag { cursor: pointer; touch-action: manipulation;
        -webkit-tap-highlight-color: transparent; }
.ktag.pk { border-color: var(--accent); }
.kscr .ktag { background: transparent; border-color: transparent; padding-left: 4px; }
.kscr .ktag.pk { background: transparent; }
.ktag b.kup {
  font-weight: 700; font-size: 13px; padding: 0 4px;
  color: var(--dim); opacity: .45;
}
.ktag b.kup.on { opacity: 1; color: var(--accent); background: transparent; }
.ktag b.kup.on:hover { background: var(--grpbg); color: var(--accent); }
.ktag b.kup:not(.on):hover { background: var(--grpbg); color: var(--dim); }
.ktag b.kup.off { opacity: .18; cursor: not-allowed; }
.ktag b.kup.off:hover { background: transparent; color: var(--dim); }
/* 「合并时加空格」那颗勾（2026-09-28，用户：「多关键词合并时是否加空格默认是加空格」）
   —— 挨着「＋ 加」「清空」那一排，长得像一句话里的一个词，不是一颗按钮：
   不换行、跟着那一排的基线走，勾本身吃掉点鼠标热区。 */
.kchk {
  display: inline-flex; align-items: center; gap: 5px;
  white-space: nowrap; font-size: 13px; color: var(--dim); cursor: pointer;
}
.kchk input { margin: 0; cursor: pointer; }
.kchk:hover { color: var(--fg); }
/* ══ 底图：**一段一格的下拉** + 悬停出图（用户 2026-09-27：「底图选择就是通过
   下拉框实现，但是鼠标悬停在某一项时动态显示图像预览（要快速）」） ═══════════
   ⚠️ 不用原生 `<select>`：选项是系统画的，收不到 hover ⇒ 只能自己搭一个。
   （原来是"几十张缩略图铺一排、点一下编号"—— 用户要的是下拉 ＋ 悬停出图，
     那套 `.bggrid/.bgcell/.bgn` 已经没人用了，**别留着**。） */
/* ══ 底图那条**能拖的时间条**（用户 2026-09-27：「每张画面的显示时间可以调整，
   用一个类似播放进度条，每两个画面之间拖动点，左右滑动就能改变相邻的画面的
   显示时长」）═══════════════════════════════════════════════════════════
   ⚠️ 把手要**够宽**（14px）且 `touch-action:none`：窄了拖不住，手机上更是一拖
      就变成"滚动整个弹窗"（那一下拖到的是滚动条，不是切点）。 */
/* ★★ 两条横轴**各自在左边写上名字**（用户 2026-09-27 [图]：「这里左侧写清楚，上面是
   画面轴，下面是旁白轴」）—— 两条轴长得一样（都是"块＋时间"），不标就得靠猜。
   ⚠️ 左列 64px ＝ `.bgk` 那一列 ⇒ 和下面每一行的「第 N 段」对齐（窄屏 52px，见
      `@media (max-width:720px)`）。⚠️ 标签那两格由 app.js **按需**给（哪条轴算不出来
      就没有那一行，见 `weBgHtml` 的 `axes`）—— 这里是两列网格，行数由内容定。 */
.bgaxes {
  display: grid; grid-template-columns: 64px 1fr; gap: 3px 8px;
  align-items: center; margin: 6px 0 4px;
}
.bgax {
  font-size: 13.5px; font-weight: 700; color: var(--dim); white-space: nowrap;
}
.bgaxes .bgtl, .bgaxes .bgtlkw { margin: 0; }
.bgtl {
  position: relative; height: 34px; margin: 8px 0 4px; border-radius: 8px;
  background: var(--mediabg); border: 1px solid var(--line);
}
.bgtli {
  position: absolute; top: 0; bottom: 0; background: var(--card);
  border-right: 1px solid var(--line); display: flex; align-items: center;
  justify-content: center; overflow: hidden; white-space: nowrap;
}
.bgtli.alt { background: var(--grpbg); }
.bgtlt { font-size: 13.5px; font-weight: 700; color: var(--ink); }
.bgtlt i { font-style: normal; font-weight: 400; color: var(--dim); font-size: 13px; }
.bgtlh {
  position: absolute; top: -3px; bottom: -3px; width: 14px; margin-left: -7px;
  cursor: ew-resize; touch-action: none; z-index: 2;
}
.bgtlh::after {
  content: ''; position: absolute; left: 5px; top: 4px; bottom: 4px; width: 4px;
  border-radius: 2px; background: var(--accent);
}
.bgtlh:hover::after { background: var(--bad); }
/* ★★ 2026-09-30 用户：「每段音乐左右两侧都是可以拖动的，没被音乐条覆盖的部分无背景
   音乐」⇒ 每一段**两个**把手。两条 4px 亮条**分列接缝两侧**（各在自己那一段里面）：
   没空档时它们贴在一起，看着就是一根粗一点的分界线；拖出空档之后左手那条跟着左沿、
   右手那条跟着右沿，各回各的条边 —— 一眼能看出"这一段从哪儿到哪儿"。
   ⚠️ 基准那格（`.bgtlh::after { left: 5px }`）**一个字不许动**：片头设置里那条拖动条
      （`weBgTlHtml`）用的是同一个类名，改基准就是把它一起改了。 */
.bgtlh.l::after { left: 7px; }
.bgtlh.r::after { left: 3px; }
/* 没被音乐条盖住的那一截（"这一段没有背景音乐"）。
   ⚠️ 不接鼠标（`pointer-events:none`）：挡在块或把手上就拖不动了。
   ⚠️ 字**从头排**（`flex-start`），不是居中 —— 空档只有十几像素宽时居中会把两头一起
      裁掉、只剩下中间那几个字（实测 0.4% 宽的那种显示成「背景音」，看着像另一句话）。
      从头排就永远读得出"无背景…"，宽的时候照样是完整一句。
   ⚠️ 它没有底色（那截深色是 `.bgtl` 自己的），所以左边那 3px 内边距不显形；
      宽度为 0 的那种（前后两段严丝合缝）连字一起被 `overflow:hidden` 裁掉。 */
.bgtlgap {
  position: absolute; top: 0; bottom: 0; display: flex; align-items: center;
  justify-content: flex-start; padding-left: 3px;
  overflow: hidden; white-space: nowrap;
  font-size: 13px; color: var(--dim); pointer-events: none;
}
/* ★★ 2026-09-30 用户第 3 条（背景音乐这一步）：节奏条上那根**播放头** ——
   横轴与上面的块**一模一样**（left 就是当前播放时间占全片的比例）。
   ⚠️ z-index 必须压过 `.bgtlh`（那是 2）：不然拖到哪、放到哪都看不见。
   ⚠️ 它**不接鼠标**（pointer-events:none）—— 挡在分界把手上就拖不动了。 */
.bgtlp {
  position: absolute; top: -4px; bottom: -4px; width: 2px; margin-left: -1px;
  background: var(--bad); border-radius: 1px; z-index: 3; pointer-events: none;
}
/* ══ 时间条底下那一排**词块**（用户 2026-09-27：「②，维持只能拖，但是在节奏条下面
   标记关键词的显示时间区间」）═══════════════════════════════════════════════
   ⚠️ 横轴必须和上面那条**一模一样**（两块都是 `left/width` 的百分比 ⇒ 同一个
      包含块）。上面那条 `.bgtl` 有 1px 边框 ⇒ 它的百分比是拿"减去 2px"算的；
      这一块要**照样画一圈透明的边框**（不是去掉边框、更不是加内边距），两排才
      逐像素对齐 —— 差那 2px 的后果是"词块看着比它实际所在的画面段偏一点"，
      而那正是这一排唯一要回答的问题。
   ⚠️ 类名**不能叫 `.bgkw`**：那是每段那一行**右边那一列**（`flex: 0 0 230px`，
      见下面），撞名的话这一排会被套上 230px 固定宽 + `pointer-events:none`。
      所以叫 `.bgtlkw`（跟上面那条 `.bgtl` 成对）。
   ⚠️ 词块**不可拖、不可点**（他选的就是"只能拖"）：`pointer-events:none` 让
      点/拖穿过去，不会误当成把手（免得拖词块变成拖整个弹窗）。
   ⚠️ 窄块里的字**必须能掉**（`overflow:hidden` ＋ `text-overflow:ellipsis`）：
      0.6 秒的词只有几个像素宽，字撑出去会盖住邻块（看着像"两块重叠"）。
      完整内容在 `title` 里（鼠标停上去有）。 */
.bgtlkw {
  position: relative; height: 22px; margin: 0 0 4px;
  border: 1px solid transparent; pointer-events: none;
}
.bgtlkw .kwi {
  position: absolute; top: 0; bottom: 0; overflow: hidden; white-space: nowrap;
  text-overflow: ellipsis; border-radius: 5px; border: 1px solid var(--line);
  background: var(--card); color: var(--ink); font-size: 13px;
  line-height: 20px; padding: 0 5px;
}
.bgtlkw .kwi.alt { background: var(--grpbg); }
.bgtlkw .kwi b { font-weight: 700; }
.bgtlkw .kwi i {
  font-style: normal; color: var(--dim); margin-left: 5px; font-size: 13px;
}
/* ★★ 2026-09-28 用户第 24 条：这块**落在第几段里**（拖节奏条时当场跟着变，见
   `weBgTlPaint`）—— 是这两排轴存在的全部意义，所以给它一点分量（不是灰字小注）。 */
.bgtlkw .kwi em {
  font-style: normal; color: var(--accent); margin-left: 6px; font-size: 13px;
  font-weight: 600;
}
/* 旁白轴那一排：与关键词轴**同一个横轴**，只是换一套底色/边框 —— 上下两排要是长得
   一模一样，就再也分不清哪块是"词"、哪块是"这句话"。 */
.bgtlsent .kwi {
  border-style: dashed; background: transparent; color: var(--dim);
}
.bgtlsent .kwi.alt { background: transparent; }
.bgtlsent .kwi b { font-weight: 400; }
.bgpick { display: flex; gap: 12px; align-items: flex-start; margin-top: 6px; }
.bgrows { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.bgrow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.bgk { flex: 0 0 auto; width: 62px; color: var(--dim); font-weight: 600; font-size: 14.5px; }
/* ★★ 每段那一行的**三列各自定宽**（用户 2026-09-27 [图]：「宽度对齐」）——
   只要有一列宽度跟着它自己的内容变（`第 1 段` vs `第 10 段`、`0.0~2.7s` vs
   `6.3~11.3s`、下拉里名字长短不同），下面每一行的按钮和旁白就都错开一格，
   一整列看着是歪的。定了宽 ⇒ 每行的下拉框、旁白、预览**上下对齐成一列**。
   ⚠️ `.bgvo` 拿剩下的宽（`flex:1 1 auto`）——它是这三列之外唯一会伸缩的那一列。 */
.bgk { flex: 0 0 64px; }
.bgt { flex: 0 0 78px; white-space: nowrap; }
.ddwrap { position: relative; flex: 0 0 360px; min-width: 0; }
.ddbtn {
  width: 100%; text-align: left; display: flex; justify-content: space-between;
  align-items: center; gap: 8px; background: var(--card); color: var(--ink);
  border: 1px solid var(--line); border-radius: 8px; padding: 5px 9px;
  font-size: 14.5px; cursor: pointer; font-family: inherit;
}
.ddbtn:hover { border-color: var(--accent); }
.ddbtn i { font-style: normal; color: var(--dim); }
/* ⚠️ 面板是**绝对定位**的（弹窗那个框 `overflow:auto`，跟着滚就行）——
   它一展开就把下面顶下去的话，鼠标还没移到选项上，选项已经跑掉了。 */
.ddpanel {
  position: absolute; left: 0; top: calc(100% + 3px); z-index: 25;
  /* ★★ 宽度**不许跟着那颗按钮走**（用户 2026-09-27 [图]：「打开下拉列表，看不到
     图片名称了」）—— 每行加上那一格预览（`.bgpv` 168px）之后，按钮那一格被挤窄，
     面板 `left:0;right:0` 跟着窄 ⇒ 名字全成了 `scene_0...`，那排小图就白做了。
     改成"**按内容要宽度**，再给上下限"：名字多长就多宽，最窄 380px（短名字的
     那几行也别窄成一条），最宽 `min(94vw, 620px)`（不许捅出屏幕 —— 它冒出去会撑大
     弹窗那个框的滚动区，横向滚动条一出来页面就横着晃）。
     ⚠️ 窄屏那一档另说（见文件末尾的 `@media`）：手机上钉着 `right:0` 更稳，
        `620px` 在 390px 的屏上根本放不下。 */
  width: max-content; min-width: 380px; max-width: min(94vw, 620px);
  max-height: 330px; overflow: auto; background: var(--card);
  border: 1px solid var(--line); border-radius: 8px; padding: 4px;
  box-shadow: 0 8px 22px rgba(0, 0, 0, .18);
}
.ddi {
  padding: 4px 8px; border-radius: 6px; cursor: pointer;
  font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* ★★ 候选那一项现在是**一行三样**（小图 + 名字 + 「👁 看」）—— 用户 2026-09-27：
   「底图下拉列表还是设置一个预览方式吧，（电脑端和手机端平板端都可以用的），
    不然我不知道应该选那张啊」。手机平板没有 hover，所以"选之前先看见"必须落在
    **列表本身**上：左边一张小图（`loading="lazy"`，滚到哪下到哪）。
   ⚠️ 名字那一段自己 `ellipsis`（`flex:1 1 auto; min-width:0`）—— 不这么写，
      flex 子项的最小宽度是内容宽，长名字会把那颗「看」挤出面板。 */
.ddi { display: flex; align-items: center; gap: 8px; }
.ddt {
  flex: 0 0 auto; width: 64px; height: 36px; object-fit: cover;
  border: 1px solid var(--line); border-radius: 4px; background: var(--mediabg);
}
.ddt[src=""] { visibility: hidden; }   /* 这一段没有自动匹配图时占位，别把名字挤歪 */
.ddn { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* 「👁 看」= 只在右边预览区出大图、**不选中**（手机上点得到）。字号不许小于 13px。 */
button.dds { flex: 0 0 auto; padding: 1px 6px; font-size: 13px; }
.ddi:hover { background: var(--grpbg); }
.ddi.on { background: var(--grpbg); font-weight: 700; }
/* ★ 面板**往上开**那一档（最后那几行下面放不下 250px 时，`weBgOpen` 给 `b.upK`）——
   绝对定位往**下**溢会把弹窗那个框的滚动区撑大（滚动条冒出来 ⇒ 宽度变）。 */
.ddpanel.up { top: auto; bottom: calc(100% + 3px); }
/* 每段那一行**最右边**那一列：这一段画面上砸的是哪个词 + 词自己的起止秒
   （用户 2026-09-27：「那个画面显示那句关键词」）。
   ⚠️ 宽度固定：不固定的话每个词长短不同 ⇒ 每行的下拉宽度都不一样，一列看着是歪的。 */
.bgkw { flex: 0 0 230px; font-size: 14.5px; white-space: nowrap;
        overflow: hidden; text-overflow: ellipsis; }
/* ★★ 每段那一行右边那一列（**现在装的是旁白**，用户 2026-09-27：「这里后面应该是
   显示的旁白，不是关键词，**我是根据旁白选底图的**」）——「砸的那个词」挪进
   `title`（挑图看念什么，但"这一段砸哪个词"还是要知道的事）。
   ⚠️ 旁白比一个词长得多 ⇒ 这一列**吃掉剩下的宽**（`flex:1 1 auto; min-width:0`）。
      ★★ 2026-09-27 用户：「可以把文本缩短点，**多行显示**」⇒ 从"一行 + 省略号"改成
      **最多 3 行**（`-webkit-line-clamp`，超了才在第三行末尾打省略号）：一整句念白
      一行只看得见前 15 个字，挑图看的就是这句话，看不全等于白给。
      ⚠️ 两边都留着：`overflow:hidden` 是 `line-clamp` 的前提（少了它就不夹）。
      ★★ 基准尺寸必须是 **0**（`flex: 1 1 0`，不是 `1 1 auto`）：`.bgrow` 是
      `flex-wrap: wrap`，换行是按**基准尺寸**判的 —— `auto` 的基准是这句话的
      "一行放不下"的宽度 ⇒ 一长句就把右边那格预览**挤到下一行**去（用户 2026-09-27
      [图]：「得限制一下台词的宽度，把预览窗口挤到下一行去了」）。基准 0 ⇒ 它只吃
      剩下的宽，永远不参与"放不放得下"的判断。 */
.bgvo { flex: 1 1 0; min-width: 0; font-size: 14.5px; color: var(--dim);
        display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3;
        overflow: hidden; }
/* ★★ 每一段**自己那一格**预览（用户 2026-09-27：「我想同时看多句话的预览，每句话一个
   预览，点击某一句台词或对话框时对应预览**外边框加粗**（被选择）」）—— 从前只有一块
   跟着鼠标走的预览，同一时刻只看得见一段，要比对"第 2 段和第 3 段会不会撞脸"就得来回点。
   ⚠️ 选中就是**边框真的变粗**（2px → 4px）＋换色，但 `box-sizing:border-box` + 尺寸写死
      ⇒ 外框一个像素都不动（不写死的话整列图会跟着挪，鼠标点一下满屏跳）。
   ⚠️ 高宽都**先占住**：这一段没出图时也得占位，不然那一格是 0 高、整列上下跳。 */
.bgpv { flex: 0 0 auto; width: 168px; cursor: pointer; }
.bgpv img {
  display: block; box-sizing: border-box; width: 100%; height: 95px; object-fit: cover;
  border: 2px solid var(--line); border-radius: 7px; background: var(--mediabg);
}
.bgpv img[src=""] { visibility: hidden; }
.bgpv:hover img { border-color: var(--dim); }
/* ⚠️ 这两条**必须排在 `:hover` 后面**：同一个元素上两边都命中时，后写的赢 ——
   排在前面的话，鼠标一压上去加粗的那一格就褪成灰边，"选中"当场看不出来。 */
.bgpv.on img { border-width: 4px; border-color: var(--accent); }
.pvn { display: block; max-width: 100%; white-space: nowrap; overflow: hidden;
       text-overflow: ellipsis; }
/* ★★ 每一行最右边那两颗「＋ / ✕」（2026-09-28 用户：「底图可以增加/删除」——
   画面张数可增删，见 app.js 的 weBgAddSeg / weBgDelSeg）。
   ⚠️ `flex: 0 0 auto` ＋ `nowrap`：这两颗是**固定宽**的一小格，不给它伸缩的话
      窄屏上会被右边的旁白挤成竖排（一个字一行）。
   ⚠️ 它们**紧跟在缩略图后面**（`.bgpv` 之后），中间不留 `margin-left:auto`
      —— 靠右推的话窄屏一换行就飘到屏幕最右边，看着像不属于这一行。 */
.bgseg { flex: 0 0 auto; display: flex; gap: 4px; }
.bgseg .sm { padding: 2px 7px; line-height: 1.35; }
/* ★★ 下拉里悬停那张图**悬空**跟着鼠标（用户 2026-09-27 贴图 #46：「下拉列表的预览
   不在这里，**应该是悬空的**（不好做的话再增加一个预览区）」）—— 右边那块固定的预览区
   归"点一句旁白看那一段"。两个问题两个出口，挤在一格里必然互相骗。
   ⚠️ `position:fixed`：弹窗那个框是 `overflow:auto`，`absolute` 会被它裁掉、还会跟着滚。
   ⚠️ `pointer-events:none` 是**必须的**：鼠标压上去时它不能变成事件目标 —— 一变，
      底下那一项就收到 `mouseleave` ⇒ 图刚出来就被自己收掉，一路闪。
   ⚠️ `z-index` 比弹窗（60）高：它就出在弹窗上面。 */
.bgfloat {
  position: fixed; z-index: 90; display: none; pointer-events: none;
  background: var(--card); border: 1px solid var(--line); border-radius: 8px;
  padding: 4px; box-shadow: 0 10px 26px rgba(0, 0, 0, .3);
}
.bgfloat img {
  display: block; width: 240px; height: 135px; object-fit: cover;
  border-radius: 6px; background: var(--mediabg);
}
.bgfloat #bgFloatName {
  max-width: 240px; margin-top: 3px; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
/* ★★ 那一句旁白**能点**（用户 2026-09-27 贴图 #46：「这里点击那句话显示对应那一段的
   底图在预览区」）—— 能点就得看得出来能点。 */
.bgvo { cursor: pointer; }
.bgvo:hover { color: var(--ink); }
/* 现在预览的是哪一段：这一行**亮着**（点了旁白/挑了图那一段）—— 不亮的话点完不知道
   自己看的是哪一格。 */
.bgrow.on .bgvo, .bgvo.on { color: var(--ink); font-weight: 700; }
.bgrow.on .bgk { color: var(--ink); }
@media (max-width: 720px) {
  .bgpick { flex-direction: column; }
  /* 窄屏：预览那一格窄一点（168px 在一行里挤掉下拉和旁白），一行放不下就换行
     —— `.bgrow` 已经 `flex-wrap: wrap`，它会自己掉到下一行，不许把别的挤没。 */
  .bgpv { width: 132px; }
  .bgpv img { height: 74px; }
  /* 手机上 `380~620px` 那套宽度放不下（390px 的屏）：**钉回按钮那一格**（`right:0`），
     横向绝不冒出去 —— 手机上名字短一点也得忍着，冒出去就是整页横着晃。 */
  .ddpanel { width: auto; min-width: 0; max-width: none; right: 0; }
  /* 窄屏：360px 的下拉框在 390px 的屏上放不下 ⇒ 让它**自己占一行**（换行是
     `.bgrow` 的 `flex-wrap` 干的），下面那行摆旁白和预览格。 */
  .bgk { flex: 0 0 52px; }
  .bgt { flex: 0 0 64px; }
  /* 两条轴左边那列跟着 `.bgk` 一起窄（对齐是这一列存在的全部意义） */
  .bgaxes { grid-template-columns: 52px 1fr; }
  .ddwrap { flex: 1 1 100%; }
  .bgkw { flex: 1 1 auto; }        /* 窄屏那一列不再固定宽（一行放不下三样） */
  .bgvo { flex: 1 1 0; }
}

/* ══ 关键词：那句旁白**可划选**（划出来的就是关键词） ═══════════════════════ */
.spok {
  background: var(--mediabg); border: 1px solid var(--line); border-radius: 8px;
  padding: 8px 10px; margin: 2px 0 0; font-size: 15.5px; line-height: 1.9;
  user-select: text; -webkit-user-select: text;
  /* ★★ 2026-09-28 用户：「片头设置…关键词 tab，平板无法选中关键词，点击没反应，
     平板没有鼠标」—— 触屏上**划选**要先长按、再拖手柄，而浏览器默认还会把这一下
     当成"滚动/双击缩放"抢走（`touch-action` 默认 `auto`）⇒ 划不动。
     `manipulation` = 只留滚动和捏合，双击缩放和长按菜单都交给我们
     （和下面 `video` / `.vsl` 那两处同一个值）。
     ⚠️ 真正给平板用的那条路是「✎ 挑词」（点开头点结尾，不用划），这条只是兜底。 */
  touch-action: manipulation;
  -webkit-touch-callout: none;
}
.khl {
  background: var(--accent); color: #fff; border-radius: 5px;
  padding: 1px 2px 1px 4px; white-space: nowrap;
}
.khl b {
  cursor: pointer; font-weight: 700; padding: 0 4px; margin-left: 2px;
  border-radius: 999px; opacity: .8;
}
.khl b:hover { opacity: 1; background: var(--bad); }
/* ★★ 「关键词加上之后，**立即**在句子里用不同颜色标记」（用户 2026-09-27）——
   "刚加的、还没存"的那几段**另给一个颜色**（琥珀 `--warn`），存过的那批仍是蓝的
   ⇒ 一眼看出刚才那一下落在句子的哪儿，不用去底下那排词里对。
   ⚠️ 只换底色，`padding`/圆角/那个 × 全跟 `.khl` 一样 —— 只是换个颜色，不是换个长相。 */
.khl.knew { background: var(--warn); color: #3a2b00; }
/* 底下那排词跟着同一套颜色（两处必须一致：句子里的色和词上的色对不上就等于没说清）。 */
.ktag.new { border-color: var(--warn); }
.ktag.bad { border-color: var(--bad); color: var(--bad); }
/* ★★ 2026-09-28：**改过字的词**（用户贴图 [图7]：「我只想要一具尸体这四个字，
   然后可对关键词编辑，将其他词删除」）—— 画面上印的是改完的字，时间按**原来那个
   词**定位。所以它既不是"新加的"也不是"坏的"：边框用蓝、左边加一颗 ✎ 记号，
   一眼看出"这个字的秒数是照原文来的"（`.ktag.fx::before` 那个笔尖就是它）。
   ⚠️ 不许和 `.bad` 同色：改过字的词是**能砸出来**的（红框会让用户以为它存不住）。 */
.ktag.fx { border-color: var(--accent); }
.ktag.fx::before { content: '✎'; color: var(--accent); margin-right: 2px; font-size: 13px; }
/* 双击进来改字时的那个输入框（`weKeyEdit`）。宽度跟着内容给个下限就好 ——
   改短是常态（「一具被五个人分掉的尸体」→「一具尸体」）。 */
.ktag.ed { border-color: var(--accent); background: var(--grpbg); padding: 1px 3px; }
.ked {
  font: inherit; color: var(--fg); background: var(--bg);
  border: 1px solid var(--accent); border-radius: 5px;
  padding: 1px 4px; min-width: 60px; width: 8em;
}
.kselbar {
  margin-top: 6px; padding: 6px 9px; border-radius: 8px;
  background: var(--grpbg); border: 1px solid var(--line); font-size: 14.5px;
}
.kselbar button { margin-left: 6px; }
/* ★★ 「服务还是旧的」那颗红条（用户 2026-09-27：「加一句，因为我总是忘记重启服务」）
   —— 后端改过没重启时钉在页面顶上，**不自己消失**（要按提示重启，不是看一眼就没）。
   颜色走 `--warn`：这不是出错，是"该重启了"。 */
/* ★★ 顶栏那一行里的红字：后端改过、服务没重启（用户 2026-09-27：「如果后端修改了，
   没重启，在（顶上那一栏）这里用红色字体提示一下用户重启」）。
   ⚠️ 本来是一条**单独的整行红条**（摆在导航下面）—— 用户没看见 ⇒ 挪进 `nav.top` 里，
     只留一行红字；详细的那几句（哪几个文件、服务启动于几点）挂在 `title` 上。 */
#staleBar {
  color: var(--warn); font-size: 13px; font-weight: 700;
  margin-left: 14px;
  display: inline-flex; align-items: center; gap: 8px;
}
/* ★★ 那句红字**后面跟的那颗「↻ 重启服务」**（用户 2026-09-27：「直接在提示旁白有个
   重启按钮，重启后消失」）。⚠️ 这一格原来是整条可点的（`cursor:pointer` + 悬停下划线，
   留给"点整条也能重启"那版）—— 现在真正能点的是里面那颗键，整条再挂下划线就会**连
   按键一起划线**，看着像坏掉的链接 ⇒ 撤掉。红字本身不再是点击目标。 */
#staleBar button { font-weight: 700; }

/* ── 关键词「显示样式」面板（2026-09-27 晚第十五趟）────────────────────────────
   左边那几格控件、右边预览图 + 竖着的「显示位置」滑杆。
   ⚠️ 预览图的衬底走 `--mediabg`（**不许是黑的**）—— 字的外发光是往外的，纯黑底
     看着就是"一块黑框"（同上面那条媒体格子的规矩）。
   ⚠️ 竖滑杆是自己搭的：原生 `range` 竖排要走 `writing-mode` / `slider-vertical`
     那套各家不一样的写法。轨道 + 填充 + 把手三个 div，拖的算法在
     `weVslDown`（`setPointerCapture` ⇒ 划出滑杆外面也不丢）。 */
.stpane { display: flex; gap: 14px; align-items: flex-start; flex-wrap: wrap; }
.stctrl { flex: 1 1 320px; min-width: 280px; }
.stctrl > label, .stctrl > .lb {
  display: flex; gap: 8px; align-items: center; margin: 6px 0;
  font-size: 14px; color: var(--dim);
}
.stctrl > label select, .stctrl > label input[type=range],
.stctrl > .lb input[type=range] { flex: 1; min-width: 90px; }
/* ⚠️ `.lb`（取色器那一排）**故意不是 `<label>`**（用户 2026-09-27 晚贴图：「这怎么
   点击空白区域也弹出颜色选择器，点击位置精准一点」）：`<label>` 会把点在它上面**任何
   地方**（连那行字）的点击转给里面第一个可点控件 = 第一个取色器 —— 点到旁边一点就
   弹色板。`.lb` 只是个装样子的盒子，**只有色块本身**点得开。
   ⚠️ `:hover` 也只给色块，别整排变色（那会让人以为整排可点）。 */
.stctrl > label input[type=color], .stctrl > .lb input[type=color] {
  width: 46px; height: 28px; padding: 0; cursor: pointer;
  border: 1px solid var(--line); border-radius: 6px; background: var(--card);
}
.stprev { display: flex; gap: 8px; align-items: stretch; }
.stprev img {
  width: 256px; height: 144px; display: block; border-radius: 8px;
  border: 1px solid var(--line); background: var(--mediabg);
}
.vsl {
  position: relative; width: 24px; cursor: pointer; touch-action: none;
  user-select: none; border: 1px solid var(--line); border-radius: 6px;
  background: var(--grpbg);
}
.vsl .vslfill {
  position: absolute; left: 0; right: 0; bottom: 0;
  background: var(--accent); opacity: .32; border-radius: 0 0 5px 5px;
}
.vsl .vslknob {
  position: absolute; left: -4px; right: -4px; height: 8px; margin-bottom: -4px;
  border-radius: 4px; background: var(--accent); box-shadow: 0 1px 3px rgba(0,0,0,.35);
}

/* 压字页（图文配置 · 顶层「压字」）：预览图 + 右侧竖条，左边钉住、下面配置自己滚。 */
#picTxtPin { flex-direction: row; align-items: flex-start; justify-content: center; gap: 10px; padding: 0; }
#picTxtPin .stprev { align-items: flex-start; justify-content: center; width: 100%; }
/* 2026-10-19：`.stprev` 那圈底把整张卡（含下端 `#picTxtBody` 里的画风下拉和
   「▶ 预览」）也套了进去 ⇒ 那一截一点就点在图圈上，按钮点不到（实测）。只套图+竖条。 */
#inlineCfg #picTxtPin .stprev { display: inline-flex !important; width: auto !important;
  flex: 0 0 auto; align-self: center; }
#picTxtPin .stprev img {
  width: auto; height: min(46vh, 520px); aspect-ratio: 3 / 4;
  object-fit: contain; cursor: zoom-in;
}
#picTxtPin .vsl { height: min(46vh, 520px); flex: none; }
#picTxtBody .opgrp { margin-bottom: 6px; }

/* ══ 关键词标注（配音设置 · 「字幕样式 / 关键词」那一页里的清单）═════════════════
   ★★ 2026-09-27 用户（贴了整页截图）：「这块字幕样式/关键词标注，手机端很不好操作啊，
   包括选关键词和看预览；另外点击哪一行文字选关键词，预览就自动跳到哪一个画面」。
   两件事各自的落点：
   · **预览**（`.subprev`）—— 窄屏**钉在抬头底下**（`top` 由 app.js 的 `vcStick()`
     量抬头高度写进来，写死对不上）：六十多行的清单，不钉住就是"看一句、滚回顶上、
     再滚下来"，而这一页存在的意义就是一边看画面一边改；
   · **清单**（`.mkrow`）—— 窄屏那一行拆成上下两截（原文独占一行、输入格铺满），
     手指点得着、划得准；词牌加大一号（手机上原来那 0.5 行高的小牌点不中）。
   ⚠️ 词牌的长相从 `app.js:mkChip` 的**行内样式**搬到了这儿 —— 行内样式媒体查询盖不住
     （手机上想放大一号就得 `!important`）。
   ⚠️ `.mkrow.on` = **现在预览的就是这一行**（`mkPick` 打的记号）；右边那个 👁 是 CSS
     伪元素，不改 DOM。 */
.subprev { background: var(--card); }
.mkrow.on { background: var(--grpbg); box-shadow: inset 3px 0 0 var(--accent); }
.mkrow.on .mkkey::after { content: ' 👁'; }
.mkchip {
  display: inline-flex; align-items: center; gap: 2px;
  border: 1px solid var(--line); border-radius: 9px; padding: 0 5px;
  white-space: nowrap;
}
/* ★★ 2026-09-28：**大模型写稿时标的那几个词**（虚线框 = 还没经你手）。
   ⚠️ 虚线不是装饰，是"这一条还没归你"：人一改（或按保存）就变成实线。
   ⚠️ 只改边框样式、**不改颜色/字号** —— 那两样在这个面板上已经被"对不上原文"
   那个红字占着，再借一个颜色就没法一眼分辨了。 */
.mkchip.mkauto { border-style: dashed; }
/* ★★ 行尾那张小图（用户 2026-09-27：「关键词每行后面一个缩略图预览，可以单击打开看
   大图」）—— 后端按这一行的原文 + 标过的词现画（和上面那张大图、和成片同一份画法）。
   ⚠️ 横竖两个方向**只封顶、不拉伸**（`max-width` / `max-height` 一起给，宽高都 auto）
     —— 写死宽高会把竖屏 9:16 那张压成扁的，看着就不像片子里的字幕。
   ⚠️ 衬底 `--mediabg`（**不许是黑的** —— 同上面那条媒体格子的规矩）。 */
.mkthumb {
  display: block; width: auto; height: auto;
  max-width: 118px; max-height: 66px;
  border: 1px solid var(--line); border-radius: 4px;
  background: var(--mediabg); cursor: zoom-in;
}
/* ══ 「✎ 挑词」那一层：**点着挑**（手机 / 平板用这条）═══════════════════════════
   ★★ 2026-09-28 用户：「配音中的关键词标注功能，在平板和手机上无法选择关键词
   （好像不可编辑一样）」「我需要那种可以点开头，然后点结尾，选范围，不然平板没有
   鼠标，不好精准的选择范围」。所以点行尾那颗「✎ 挑词」，把这一句切成一块一块摆
   出来：**点一下定开头、再点一下定结尾**，中间整段拼成一个词。
   ⚠️ 每一块**最小 34px 高**（手指点得着）—— 手机上原来那些 0.5 行高的小牌
     就是"点了没反应"的来源。
   ⚠️ 标上的（`.on`）用**实底强调色**；选段的**起点**（`.from`）是一圈描边 ——
     两样必须一眼分得开（一个是"已经标了"，一个是"还等着点结尾"）。 */
.mkpickwrap { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
.mkpick {
  display: inline-flex; align-items: center; min-height: 32px; padding: 2px 10px;
  border: 1px solid var(--line); border-radius: 10px; cursor: pointer;
  user-select: none; -webkit-user-select: none; -webkit-tap-highlight-color: transparent;
}
.mkpick.on {
  background: var(--accent); border-color: var(--accent); color: #fff;
}
/* 选段的起点：还等着点结尾 */
.mkpick.from {
  border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent);
}
/* 切不动的块（标点 / 空格）：**摆着但不给点** —— 这一层要能照着原文念一遍，
   丢字就没人知道"这一句到底长什么样"（见 subtitles.cand_toks 那段）。 */
.mkpickoff { display: inline-flex; align-items: center; min-height: 32px; color: var(--dim); }
.mkpickbtn { flex: 0 0 auto; }
/* 「现在标着」那一行（去词只有这儿和清单里的词牌两处） */
.mknow { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; margin-bottom: 6px; }
.mkpicked {
  display: inline-flex; align-items: center; gap: 2px;
  border: 1px solid var(--line); border-radius: 9px; padding: 1px 6px;
  background: var(--grpbg);
}
.mkpicked b { cursor: pointer; color: var(--bad); }
@media (max-width: 720px) {
  /* 钉住的那块只吃 1/3 屏：竖屏预览图缩到 390px 宽时能有 690px 高，整屏都给它就没法
     改字了（`max-height` + 原比例 ⇒ 宽度自己跟着收）。 */
  .subprev { position: sticky; z-index: 5; padding-bottom: 4px; }
  .subprev img { max-height: 30vh; margin: 0 auto; }
  .mkrow .mkline { flex-wrap: wrap; }
  .mkrow .mktext { flex: 1 1 100%; font-size: 16px; line-height: 1.8; }
  .mkrow .mkin { flex: 1 1 auto; width: auto; min-width: 0; height: 34px; }
  .mkrow .mkadd { flex: 0 0 auto; height: 34px; padding: 0 10px; }
  .mkrow .mkthumb { flex: 0 0 auto; max-width: 96px; max-height: 54px; }
  .mkrow .mkwrap { padding-left: 0; }
  .mkchip { padding: 3px 9px; font-size: 15.5px; }
  /* 挑词那颗按钮：手指点得着，跟输入格 / ＋ 一样高 */
  .mkrow .mkpickbtn { flex: 0 0 auto; height: 34px; padding: 0 10px; }
  .mkpick, .mkpickoff { min-height: 38px; font-size: 16.5px; padding: 3px 12px; }
  .mkpicked { padding: 3px 9px; font-size: 15.5px; }
}

/* ---- 只有一部手机时那条路（验证码登录）那两格（用户 2026-09-28）----
   手机上要按得着：给个够宽的输入框（默认的 `input` 宽度在手机上太挤），
   红字给"后端回的坏消息"（滑块 / 验证码错了）。 */
.smsin { width: 132px; }
.tiny.bad { color: var(--bad); }

/* ★★ 2026-10-09 用户：「字幕的预览应该显示在最上面，而且，滚动只有配置选项滚动，
   预览界面是一直能显示在屏幕上的」
   ⇒ 字幕那一页的预览块**摆在最上面 + 钉住**：底下的配置滚，图不动。
   ⚠️ `top: var(--navH)` 用的是全项目同一个口径（导航自己 sticky 在 0、高 `--navH`，
      见 `.batchbar` / `#queueBar`）—— 不跟它对齐就会被导航盖住半截。
   ⚠️ 背景必须**不透明**（`var(--card)`）：它浮在滚过去的内容上面，透明的话字会透过来。
   ⚠️ `display:flex` 是 `.opgrp` 本来就有的，这里只加定位，不动它的盒模型。 */
/* ★★ 2026-10-09 用户（附截图）：「字幕预览在字幕 tab 页最上方，且钉死，不能滚动，
   只有下方的配置选项可以滚动」
   ⇒ 它要钉在**tab 条下面**（tab 条自己已经钉在 `var(--navH)`），
     不然两块会叠在一起。*/
/* ★★ 2026-10-09 用户：「字幕预览放到 tab 页顶部，也钉死」+
   「从预览窗口往上是锁死的，只有下面的配置选项可以滚动」
   ⇒ 它现在长在 `#cfgSubCard` 里的 `.cfgPin`（与声音页的试听行同一个盒子）——
     钉住由 `.cfgPin` 负责，它自己**不用**再 sticky（嵌两层会互相打架）。 */
.subpreview {
  background: var(--card);
  margin: 0 0 8px;
}
.subpreview #subImg { max-height: 34vh; width: auto; max-width: 100%;
  display: block; margin: 0 auto; }
/* ★★ 2026-10-09 用户（附截图）：「这个 tab 表头是钉死的，不随滚动条滚动」+「上面这些
   控件还是没钉死啊，还在滚动」+「字幕预览放到 tab 页顶部，也钉死」
   ⇒ 这一页的规矩是：**表头 + 那张卡片一起钉在 `var(--navH)` 下面**，只有卡片里面的
     `.opgrp` 盒子随页面滚。
   ⚠️ 三条一起才成立：
     ① `.tabbar` 钉在 `var(--navH)`；
     ② `.knobgroup` 钉在 `calc(var(--navH) + var(--cfgTabH))`（= tab 条底下），
        高度不够一屏就不钉（`max-height` + `overflow:auto` 之内自己滚）；
     ③ `#view` 里 tab 条**上面**那一坨（`h1`）要收干净 —— 钉住的元素只在**它的容器**
        够长时才停得住，前面若有 100px 的标题，卡片进视口前那段照样滚。
   ⚠️ 只钉这一页（`#cfgSubCard` / `.knobgroup` 只在 `viewConfig` 里出现），
      `.tabbar` 这个类也只在这一页用。 */
.tabbar { position: sticky; top: var(--navH); z-index: 21; background: var(--bg);
  padding: 4px 0 0; margin: 0 0 6px; }
#view > h1 + .tabbar { margin-top: 4px; }
/* ── 全局配置页：**上面钉死、下面滚**（2026-10-09 用户两次强调）─────────────
   原话：「声音 tab 钉死到这里，试听按钮和进度条一直能显示，不被滚动出窗口」
         「预览窗口要放到字幕 tab 页的**最前面**，从预览窗口往上是锁死的，
           **只有下面的配置选项**在下面可以滚动」
   ⇒ 每张卡片里：
     · `.cfgPin`   = 试听行 / 字幕预览 —— 钉住，永远看得见；
     · `.cfgScroll`= 配置选项 —— 唯一滚动的东西。
   ⚠️ 页面**自己**也锁死（`html.lock`，一点不许滚）—— 这是"最上面"那一句的前提。
   ⚠️ 卡片 `overflow: visible`：卡片若自己也滚，里面两个 box 的 sticky 就各自为政，
      滚起来一半钉一半跑。 */
html.lock #view .cfgCols { height: var(--cfgColsH, calc(100vh - 96px)); }
.tabbar { position: sticky; top: var(--navH); z-index: 21; background: var(--bg);
  padding: 4px 0 0; margin: 0 0 6px; }
#view > h1 + .tabbar { margin-top: 4px; }
.cfgSaveRow { margin: 0 0 6px; }
.knobgroup {
  height: 100%; box-sizing: border-box;
  overflow: visible; margin: 0;
  display: flex; flex-direction: column;
}
/* 配置选项：唯一的滚动容器。`min-height:0` 必须给 —— flex 子项默认不肯缩到内容高以下。 */
.cfgScroll { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
/* ★ 2026-10-19 用户：「配置页面滚动条，只有鼠标右侧靠近滚动条的空白区域时才可以滚动，
   应该是在任意位置都可以滚动」⇒ 内嵌面板（合集页标题下面那块）不锁整页、卡片也没有定高，
   `.cfgScroll` 永远和内容一样高 ⇒ 它根本滚不动。鼠标在卡片判定的"可滚祖先"就是它，
   滚轮事件被它吞掉，页面也不动。
   ⇒ 内嵌时让 `.cfgScroll` 不自己滚（`overflow: visible`），滚轮冒泡到页面，
      整页在**任意位置**都滚得动。卡片里的 `.cfgPin`（试听/预览）改成 `sticky` 跟着页面停。
   ⚠️ 只认内嵌那颗盒子 `#inlineCfg`：独立配置页（`html.lock`）还是"上面钉死、下面自己滚"。 */
#inlineCfg .cfgScroll { overflow: visible; }
#inlineCfg .knobgroup { height: auto; }
/* 钉住的那一块。⚠️ 卡片自带 `padding: 14px 16px` ⇒ sticky 的 `top:0` 是**内边**
   不是卡片顶；往上拉 14px 盖住那段 padding，钉住时才贴着卡片上沿。 */
.cfgPin {
  flex: 0 0 auto;
  position: sticky; top: 0; z-index: 18;
  background: var(--card); padding-bottom: 4px;
  margin-top: -14px; padding-top: 14px;
}
.cfgTryBar { margin: 4px 0 0; }
.cfgTryBar #cfgTryTime { flex: 0 0 auto; min-width: 74px; text-align: right; }

/* ★★ 2026-10-09 用户：「试听那里要有播放进度条」—— 试听那一行下面那根。
   ⚠️ 它跟着一起隐现的只有声音那一页（ 只画在  那一页）。 */
/* ★★ 2026-10-09 用户（附截图）：「声音 tab 钉死到这里，试听按钮和进度条一直能显示，
   不被滚动出窗口」⇒ 试听那两行（键 + 进度条）包在 `.cfgPin` 里，钉在**卡片内部顶**。
   ⚠️ 卡片自己就是滚动容器（`overflow:auto`）⇒ 这里 `top: 0` 正好贴卡片内边。
   ⚠️ 卡片要留出它的高度：`padding-top` 由 `viewConfig` 量了写进 `--cfgPinH`
      （不写死，按钮文案换行时高度会变）。 */
.cfgPin {
  position: sticky; top: 0; z-index: 18;
  background: var(--card); padding-bottom: 4px;
  /* 卡片自带 `padding: 14px 16px` ⇒ sticky 的 `top:0` 是**内边**不是卡片顶。
     往上拉 14px 盖住那段 padding，钉住时才贴着卡片上沿、不露出下面滚上来的格子。 */
  margin-top: -14px; padding-top: 14px;
}
.cfgTryBar { margin: 4px 0 0; }
.cfgTryBar #cfgTryTime { flex: 0 0 auto; min-width: 74px; text-align: right; }

/* ================================================================ 图文线
   ★★ 2026-10-11 加（`#/pic`）。class 名一律 `p` 打头，**别撞** `.stage` /
      `.shots` / `.tabbar` / `.plist` —— 撞了的话视频线那几页会跟着变样，
      而"变样"没人会当成 bug 报上来。
   ⚠️ 用 `.pstage` 不用 `.stage`：后者是视频线阶段条那一格。
   ⚠️ `.pbig` 是点开大图那个浮层，`z-index` 要压住作业栏。 */
.pbar { gap: 6px; flex-wrap: wrap; }
.pstage {
  border: 1px solid var(--line); border-radius: 999px;
  padding: 2px 10px; font-size: 13px; color: var(--dim); background: var(--card);
}
.pstage.ok { border-color: var(--accent2); color: var(--accent2); }
.pstage.part { border-color: var(--warn); color: var(--warn); }
.plist { width: 100%; border-collapse: collapse; }
.plist th, .plist td {
  text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--line);
}
.psec { border-top: 1px dashed var(--line); padding-top: 8px; margin-top: 8px; }
.psec textarea { width: 100%; }
/* ★★★ 2026-10-15 用户：「第一个 tab 和第二个 tab 内的文字都改一下字体大小和加粗」。
   ⇒ 正文那两块**都比别处大一档、加粗**：
      · `.pictabs` 管的范围里，一段的正文 textarea（`#picPane_edit .psec textarea`）
      · 「纯文案」那一页的段落 `.ppara`
   ⚠️ 只管**正文那几段** —— 别一路 `#picPane_edit textarea` 打下去，那会把
      「画面」那句提示词框（`.psec > textarea` 的第二个）也一起放大变粗，
      而它是给模型看的白描、不是给人读的正文（用户没要它变）。
      判据：正文框是 `.psec` 的**直接**子元素里的**第一个** textarea。 */
#picPane_edit .psec > textarea:first-of-type {
  font-size: 17px; font-weight: 600; line-height: 1.75;
}
.ppara {
  font-size: 17px; font-weight: 600; line-height: 1.9;
  margin: 0 0 1em; color: var(--ink); user-select: text;
}
.ppara.muted { font-weight: 400; }
.pgrid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 12px; margin-top: 10px;
}
.pimgbox {
  /* 竖图 3:4。衬底用浅灰，不许纯黑（图不是 16:9 时留白会看着像黑框）。 */
  background: #eef0f4; border-radius: 8px; aspect-ratio: 3 / 4;
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
.pimgbox img { width: 100%; height: 100%; object-fit: contain; cursor: zoom-in; }
/* ★★ 2026-10-11：格子里现在显示的是**成图**（压过字的、发出去的那份），
   角上标一个「成图 / 底图（没压字）」—— 看不出区别正是"发出去没字"那类事故的温床。 */
.pimgbox { position: relative; }
.ptag {
  position: absolute; left: 6px; top: 6px; font-size: 13px;
  padding: 1px 6px; border-radius: 4px; color: #fff;
  background: rgba(0, 0, 0, .55); pointer-events: none;
}
/* ★★ 2026-10-21 用户：「每个图片预览下面增加一个刷新按钮，可以刷新原图和压字图」。
   挂在 `.ptag` 里 ⇒ 要单独开 `pointer-events:auto`（父元素是 none）。
   ⚠️ 它**只重拉图**，不跑作业 —— 与旁边「↻ 重画底图」那颗分清。 */
.ptag .pv-refresh {
  pointer-events: auto; cursor: pointer; margin-left: 8px;
  padding: 0 4px; border-radius: 3px; background: rgba(255, 255, 255, .18);
}
.ptag .pv-refresh:hover { background: rgba(255, 255, 255, .35); }
/* ★★★ 2026-10-12 用户：「出图那个预览应该是有两个按钮，原图和压字图，
   分别在原图和压字图切换」⇒ 一屏两张图都画在 DOM 里（`.pv-base` / `.pv-fin`），
   只让带 `.on` 的那张显示。**不切换 `src`** —— 重设 src 会把已下好的图丢掉、
   当场闪一下白（这也是"两张都画"唯一站得住的理由）。
   ⚠️ `position:absolute` 两张叠在同一格里，翻 `.on` 就是换一张、不会把格子撑开。 */
.pimgbox img.pv-base, .pimgbox img.pv-fin {
  position: absolute; inset: 0; display: none;
}
.pimgbox img.pv-base, .pimgbox img.pv-fin, .pimgbox img.off { display: none; }
.pimgbox img.pv-base.on, .pimgbox img.pv-fin.on { display: block; }
/* ★ 2026-10-12 用户 [图140]：点了哪一份就只认哪一份，缺的那份换成空态那句话。
   `.dim` = 这一份还没出，键留着（点了会告诉你"还没有出图"），但看着就知道是灰的。 */
.seg2 button.dim { opacity: .45; }
/* 「原图 / 压字图」那一对键（同 `.seg` 那套的窄版：两颗挨着，选中的那颗上色）。 */
.seg2 { display: inline-flex; }
.seg2 button {
  border-radius: 0; margin-left: -1px; background: var(--card, #fff);
}
.seg2 button:first-child { border-radius: 4px 0 0 4px; margin-left: 0; }
.seg2 button:last-child { border-radius: 0 4px 4px 0; }
.seg2 button.on {
  background: var(--accent); color: #fff; border-color: var(--accent);
  position: relative; z-index: 1;
}
/* ★★★ 2026-10-14 用户（[图76]）：「这个图重画重压字，怎么只有退啊」。
   ⚠️ 根因不在按钮，在**这一格不肯缩**：`.pimg` 是 `.pgrid` 的子元素，而 grid item
      默认 `min-width: auto`（= 内容的最小宽度）。`.pgrid` 的轨道是
      `minmax(260px, 1fr)`，可这一行里那两颗键（「↻ 重画底图 + 重压字」+
      「⟳ 刷新」）撑出来的最小内容宽度**大于** 260px ⇒ 格子被内容顶宽、溢出轨道，
      而 `.row` 的 `flex-wrap: wrap` 算的是这个被顶宽后的宽度 ⇒ **不换行**，
      两颗键叠在一起、后半截被裁掉（用户看到的就是"只有「退」"—— 那是
      「重压字」的尾巴）。
   ⇒ ① 给 `.pimg` 补 `min-width: 0`（grid item 缩得下去的唯一写法）；
      ② 那一行里的长键允许**在文字里断行**（`white-space: normal`），
         缩到 260px 时它自己折两行，而不是把整行顶出去。 */
.pimg { min-width: 0; }
.pimg > .row { min-width: 0; }
.pimg > .row > button { white-space: normal; height: auto; line-height: 1.35; }
/* 分镜里那一屏的字：**只读**（文案不许在网页上改，见 paintPic 里那段说明）。 */
.pshottext {
  font-size: 13px; line-height: 1.5; color: var(--dim);
  border-left: 2px solid var(--line); padding: 2px 0 2px 8px;
  margin: 4px 0; user-select: text;
}
.pimg details summary { cursor: pointer; }
.pimg details pre {
  margin: 4px 0 0; padding: 6px; background: var(--card);
  border-radius: 6px; max-height: 160px; overflow: auto;
}
.pimg textarea { width: 100%; font-size: 13px; }
.pbig {
  position: fixed; inset: 0; background: rgba(0, 0, 0, .78);
  display: flex; align-items: center; justify-content: center;
  z-index: 9000; cursor: zoom-out;
}
.pbig img { max-width: 94vw; max-height: 94vh; }

/* ★★ 2026-10-11：清单页卡片（`workCard`）左上角那颗勾选框 ——
   清单页改成卡片版式之后勾选框丢了，顶上那排「▶ 重跑本片 / 🗑 删除选中」
   就永远灰着（它们认 `S.selEps` 里勾中的键）。绝对定位压在封面角上，
   不占卡片高度（卡片本身没地方再塞一行）。 */
.card.wcard { position: relative; }
.wck {
  position: absolute; left: 6px; top: 6px; z-index: 2;
  background: rgba(255, 255, 255, .88); border-radius: 6px;
  padding: 3px 5px; display: flex; align-items: center; cursor: pointer;
}
.wck input { margin: 0; cursor: pointer; }

/* ★★★ 2026-10-12 用户：「视频和图文按钮做得**显眼一点**」——
   「⚙ 视频配置」/「⚙ 图文配置」这两颗（`workBar` 里画的）用强调色描边 + 淡底，
   在一排灰按钮里一眼能挑出来，但不至于抢过主要内容。
   ⚠️ 只给配置入口用（`.cfgbig`）；别把它加到「全选」「刷新」那些日常键上。 */
/* ★★★ 2026-10-12 用户：「**视频配置和图文配置按钮放到网页顶部，与视频作品和图文作品
   按钮放到一起吧，分组**」。
   ⇒ 导航上这四颗连成一排：`视频作品 · 图文作品 │ 视频配置 · 图文配置`。
   `.navsep` 就是中间那道竖线 —— 左边是"看作品"，右边是"改配置"，一眼分得开。
   ⚠️ 用**一根竖线**而不是"留白"：纯留白在这条导航上跟别处的间距没差（都是 `gap`），
      分组是分不出来的。
   ⚠️ `.navcell` 给 `<span>` 用（导航里别处是 `<a>`/`<button>`，这两个默认有 padding
      和最小高度，纯 `<span>` 没有 —— 不补的话它比两边矮一截，竖线跟着塌）。 */
.navsep {
  display: inline-block; width: 1px; height: 20px; margin: 0 7px;
  background: var(--line); vertical-align: middle; flex: 0 0 auto;
  opacity: .9;
}

.cfgbig {
  border-color: var(--accent) !important;
  color: var(--accent) !important;
  background: color-mix(in srgb, var(--accent) 12%, transparent) !important;
  font-weight: 600;
}
.cfgbig:hover { background: color-mix(in srgb, var(--accent) 22%, transparent) !important; }

/* ★★★ 2026-10-12 用户：「图文也要有个预览界面，最好是能做成跟抖音类似的预览效果」
   ⇒ 抖音「图文」那条的架子：**一屏一张竖图铺满**、右边一列竖排按钮、
      右下角文案区（@ + 标题 + 正文 + 曲子名）、顶上一排「图文」页签。
   ⚠️ 手机比例写死 **9:16**（`aspect-ratio`），高度封顶 `72vh` ——
      不封顶的话横屏笔记本上这个手机框会顶出屏幕，底部那颗「关」就够不着了。
   ⚠️ 文案区压在图上（绝对定位 + 从下往上的黑渐变）：抖音就是这么压的，
      分开摆就不像了。但**图不被压掉**：图是 `object-fit:contain`，
      四周留黑，字压在留出来的黑边上。 */
.pvpvWrap { display: flex; justify-content: center; margin-top: 10px; }

.pvbig {
  border-color: var(--accent) !important;
  color: var(--accent) !important;
  background: color-mix(in srgb, var(--accent) 12%, transparent) !important;
  font-weight: 600;
}
.pvbig:hover { background: color-mix(in srgb, var(--accent) 22%, transparent) !important; }

.pvpvPhone {
  position: relative;
  width: min(360px, 92vw);
  aspect-ratio: 9 / 16;
  max-height: 72vh;
  background: #000;
  border-radius: 14px;
  overflow: hidden;
  box-shadow: 0 6px 28px #0006;
  touch-action: pan-y;
  user-select: none;
}
.pvpvPhone img {
  width: 100%; height: 100%;
  object-fit: contain;
  display: block;
}
.pvpvTop {
  position: absolute; top: 8px; left: 0; right: 0;
  display: flex; justify-content: center; gap: 14px;
  pointer-events: none;
}
.pvpvTab {
  font-size: 13px; color: #fff9; padding: 3px 10px; border-radius: 20px;
}
.pvpvTab.on { color: #fff; font-weight: 600; background: #fff2; }
/* 底部文案区：从下往上的黑渐变，字一律白 —— 图上什么颜色都压得住。 */
.pvpvMeta {
  position: absolute; left: 0; right: 56px; bottom: 0;
  padding: 40px 10px 12px;
  background: linear-gradient(to top, #000c 12%, #0008 55%, #0000);
  color: #fff; pointer-events: none;
}
.pvpvAt { font-size: 13px; opacity: .85; margin-bottom: 4px; }
.pvpvTitle { font-size: 14px; font-weight: 600; line-height: 1.35;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; }
.pvpvText { font-size: 13px; line-height: 1.5; opacity: .92; margin-top: 4px;
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical;
  overflow: hidden; white-space: pre-wrap; }
.pvpvBgm { font-size: 13px; opacity: .85; margin-top: 6px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 右边那一列竖排按钮 —— 抖音的点赞/评论/分享那一竖列的位置。 */
.pvpvSide {
  position: absolute; right: 8px; bottom: 16px;
  display: flex; flex-direction: column; gap: 8px;
}
.pvpvBtn {
  width: 38px; height: 38px; border-radius: 50%;
  border: 0; background: #fff2; color: #fff; font-size: 17px; line-height: 1;
  cursor: pointer; backdrop-filter: blur(2px);
}
.pvpvBtn:hover:not(:disabled) { background: #fff4; }
.pvpvBtn:disabled { opacity: .3; cursor: default; }
/* 张数点：抖音那种一排小点，点多的地方能直接跳过去。 */
.pvpvDots {
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  display: flex; flex-direction: column; gap: 3px; max-height: 52%; overflow: hidden;
}
.pvpvDots i {
  display: block; width: 4px; height: 4px; border-radius: 50%;
  background: #fff5; cursor: pointer;
}
.pvpvDots i.on { background: #fff; height: 10px; border-radius: 2px; }

/* ★★★ 2026-10-12 用户：「每个作品**右下角**（打开按钮旁边）设置三个点，点击弹出菜单，
   可选择功能（删除，重跑，编辑等等）」。
   ⇒ 卡片右下角那颗「⋯」+ 挂在 body 上的浮层菜单（`.wmenu`）。
   ⚠️ 浮层不是弹窗：连着一部一部处理时不用每次重开一屏（见 `workMenu` 抬头）。
   ⚠️ `position:fixed` + JS 算坐标（`workMenu`）：`absolute` 会被卡片的
      `overflow:hidden`（缩略图那圈圆角要它）裁掉半边。
   ⚠️ z-index 要压过 `.modal`（1000）—— 从列表页开菜单时不算弹窗，但要是哪天
      在别处复用，压在下面会点不到。 */
.wmore {
  padding: 2px 9px;
  font-size: 17px;
  line-height: 1.1;
  font-weight: 700;
}
.wmenu {
  position: fixed;
  z-index: 1200;
  min-width: 176px;
  padding: 4px;
  background: var(--card, #fff);
  border: 1px solid var(--line, #d5d9e0);
  border-radius: 10px;
  box-shadow: 0 8px 26px #0003;
  display: flex;
  flex-direction: column;
}
.wmitem {
  border: 0;
  background: transparent;
  text-align: left;
  padding: 7px 10px;
  border-radius: 7px;
  font: inherit;
  font-size: 14px;
  cursor: pointer;
  color: inherit;
}
.wmitem:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }

/* ★ 2026-10-19 用户：「点击图文配置，下面就只显示图文配置的内容，不要显示作品列表」
   ⇒ 面板开着时，两栏整块藏起来（面板自己住 `#collCfgSlot`），一行 CSS 兜底——
     不用等 JS 重画那一趟把两栏去掉（上一版就卡在"盒子里攥着两栏"上）。 */
body:has(#inlineCfg) #collOpen { display: none !important; }
/* 2026-10-19 用户：配置界面与作品工具栏互斥 —— 面板在 DOM 里时这条工具栏让位（不管谁重画了它）。 */
body:has(#inlineCfg) #workbar { display: none !important; }

/* ★★ 2026-11-01 真人配音「录全文」卡拉OK字幕卡 */
.karkbox { max-height: 300px; overflow: auto; border: 1px solid var(--line);
  border-radius: 8px; padding: 10px; }
.karknow { margin-bottom: 10px; }
.karksay { font-size: 20px; font-weight: 700; color: var(--ink);
  line-height: 1.6; margin-top: 6px; }
.karklist { display: flex; flex-direction: column; gap: 4px; }
.karkcue { color: var(--muted, #888); padding: 2px 4px; border-radius: 5px;
  transition: color .15s, background .15s; }
.karkcue.on { color: #fff; background: var(--accent, #c0392b); font-weight: 600; }
.karkcue.done { color: var(--ink); opacity: .55; }
