/* ==========================================================================
   角色创作工作台 — Apple-flavoured light theme
   No build step, no external assets. Everything here is self-contained.
   ========================================================================== */

:root {
  --font-ui: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display",
    "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
    system-ui, sans-serif;
  --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, "PingFang SC", monospace;

  --bg: #f2f2f7;
  --bg-deep: #e9e9ee;
  --surface: #ffffff;
  --surface-veil: rgba(255, 255, 255, 0.72);
  --surface-sunken: #f7f7f9;

  --text: #1d1d1f;
  /* 灰阶按 WCAG AA（≥ 4.5:1）定：text-3 以前是 #9a9aa0（白底 2.8:1），
     阶段标签 / 「· 2 个项目」/ 空态说明全在它上面。 */
  --text-2: #636368;
  --text-3: #6b6b71;

  /* #007aff 上的白字只有 4.02:1，13px 以下不达标；压深一档到 4.8:1 */
  --accent: #006ad6;
  --accent-hover: #005cbd;
  --accent-soft: rgba(0, 122, 255, 0.1);
  --green: #34c759;
  --orange: #ff9500;
  --red: #d70015;
  --purple: #af52de;
  --teal: #30b0c7;

  --hair: rgba(0, 0, 0, 0.08);
  --hair-strong: rgba(0, 0, 0, 0.14);

  --r-sm: 7px;
  --r-md: 10px;
  --r-lg: 14px;
  --r-xl: 20px;

  --sh-1: 0 1px 2px rgba(0, 0, 0, 0.05);
  --sh-2: 0 2px 8px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
  --sh-3: 0 12px 40px rgba(0, 0, 0, 0.14), 0 2px 8px rgba(0, 0, 0, 0.06);

  --ease: cubic-bezier(0.32, 0.72, 0, 1);
  --dur: 0.2s;

  /* ---- 布局协议变量（固定层之间靠这些互相让位，别各写各的数字）----
     --sheet-*   弹层基类 .sheet 的外边距 / 内边距（见「弹层视口约束」一节）
     --toolbar-h 项目工具栏实际高度，JS 用 ResizeObserver 写入（sticky 工具栏 +
                 ≤1024 右栏浮层的 top 都靠它）
     --dock-h / --dock-gap  右下角「对话」浮标的高度和贴边距；所有滚动面板的
                 padding-bottom 都为它留位
     --rail-w    右栏宽度：文案页签右栏在场时浮标往左让这么多
     --tab-h     手机底部 tab bar 高度，桌面为 0（手机块里改数字） */
  --sheet-pad: 24px;
  --sheet-pt: 24px;
  --sheet-px: 24px;
  --sheet-pb: 20px;
  --toolbar-h: 52px;
  --dock-h: 40px;
  --dock-gap: 22px;
  --rail-w: 400px;
  --tab-h: 0px;
  --clamp-lines: 3;
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
}

body {
  font-family: var(--font-ui);
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow: hidden;
}

button, input, textarea, select { font: inherit; color: inherit; }
button { cursor: pointer; }
.hidden { display: none !important; }

::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: rgba(0, 0, 0, 0.16);
  border-radius: 99px;
  border: 2px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: rgba(0, 0, 0, 0.28); background-clip: content-box; }

/* ---------------------------------------------------------------- icons */
.ico {
  width: 16px; height: 16px;
  flex: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
}
.ico-sm { width: 14px; height: 14px; }
.ico-lg { width: 20px; height: 20px; }

/* ---------------------------------------------------------------- buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 30px;
  padding: 0 13px;
  border-radius: 99px;
  border: 0.5px solid var(--hair-strong);
  background: var(--surface);
  color: var(--text);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.01em;
  box-shadow: var(--sh-1);
  transition: background var(--dur) var(--ease), transform var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease), opacity var(--dur) var(--ease);
  white-space: nowrap;
}
.btn:hover { background: #fafafa; }
.btn:active { transform: scale(0.97); }
.btn:disabled { opacity: 0.42; cursor: default; transform: none; }

.btn-primary {
  background: var(--accent);
  border-color: transparent;
  color: #fff;
  box-shadow: 0 1px 2px rgba(0, 122, 255, 0.32);
}
.btn-primary:hover { background: var(--accent-hover); }

.btn-danger { color: var(--red); }
.btn-danger:hover { background: rgba(255, 59, 48, 0.08); }

.btn-ghost {
  border-color: transparent;
  background: transparent;
  box-shadow: none;
  color: var(--text-2);
}
.btn-ghost:hover { background: rgba(0, 0, 0, 0.05); color: var(--text); }

/* quiet by default, red only on intent */
.btn-ghost.btn-danger { color: var(--text-3); }
.btn-ghost.btn-danger:hover { color: var(--red); background: rgba(255, 59, 48, 0.08); }

.btn-sm { height: 32px; padding: 0 11px; font-size: 12.5px; }
.btn-icon { width: 32px; height: 32px; padding: 0; border-radius: 8px; }
.btn-icon.btn-sm { width: 32px; height: 32px; }
.btn-wide { width: 100%; }

/* -------------------------------------------------------------- controls */
.input, .textarea, .select {
  width: 100%;
  background: var(--surface);
  border: 0.5px solid var(--hair-strong);
  border-radius: var(--r-md);
  padding: 7px 11px;
  color: var(--text);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
  outline: none;
  font-size: 13.5px;
}
.input:hover, .textarea:hover { border-color: rgba(0, 0, 0, 0.22); }
.input:focus, .textarea:focus, .select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3.5px var(--accent-soft);
}
.input:disabled, .textarea:disabled { background: var(--surface-sunken); color: var(--text-2); }
.textarea {
  resize: vertical;
  line-height: 1.7;
  font-size: 13.5px;
  min-height: 76px;
}
.textarea.mono { font-family: var(--font-mono); font-size: 12.5px; }
.input-sm { height: 28px; padding: 0 9px; font-size: 13px; }

/* segmented control (iOS style) */
.segmented {
  display: inline-flex;
  padding: 2px;
  gap: 2px;
  background: rgba(118, 118, 128, 0.12);
  border-radius: 9px;
}
.segmented button {
  border: 0;
  background: transparent;
  color: var(--text);
  font-size: 12.5px;
  font-weight: 500;
  padding: 4px 14px;
  border-radius: 7px;
  transition: background var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.segmented button[aria-selected="true"] {
  background: var(--surface);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

/* pill chips (multi-select) */
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  border: 0.5px solid var(--hair-strong);
  background: var(--surface);
  color: var(--text-2);
  border-radius: 99px;
  padding: 3px 12px;
  font-size: 12.5px;
  transition: all var(--dur) var(--ease);
}
.chip:hover { border-color: rgba(0, 0, 0, 0.22); }
.chip[aria-pressed="true"] {
  background: var(--accent);
  border-color: transparent;
  color: #fff;
  font-weight: 500;
}

/* --------------------------------------------------------------- 登录门 */
/* 两步（口令 → 你是谁）共用一张卡，data-step 决定谁在台上。做成两个页面会
   闪，而且第二步是第一步的延续、不是新的一道关。 */
.gate-card[data-step="pass"] .gate-step[data-step="who"],
.gate-card[data-step="who"] .gate-step[data-step="pass"] { display: none; }
/* 已有用户名那一排：人多了也不许把按钮顶出卡外，超过就内滚 */
.gate-users {
  display: flex; flex-wrap: wrap; gap: 6px; justify-content: center;
  margin-bottom: 10px; max-height: 108px; overflow-y: auto;
}
.gate-user {
  border: 0.5px solid var(--hair-strong); background: var(--surface);
  border-radius: 999px; padding: 4px 11px;
  font: inherit; font-size: 12.5px; color: var(--text-2); cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.gate-user:hover { color: var(--text); }
.gate-user[aria-pressed="true"] {
  background: var(--accent); border-color: transparent; color: #fff;
}

.gate {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(1000px 600px at 18% 8%, #dbe7ff 0%, transparent 62%),
    radial-gradient(900px 620px at 88% 92%, #ffe6f0 0%, transparent 60%),
    linear-gradient(180deg, #f7f7fa 0%, #ececf1 100%);
  /* 登录墙必须压过所有工作层：审批全屏(280)、抽屉(250)、弹窗(300)。
     以前是 100 —— 口令一失效，401 弹出的 gate 被审批层整个盖住，人对着
     一个看似正常的界面接着批，一条都没进库。只让 toast(400) 从它上面过。 */
  z-index: 350;
}
.gate-card {
  width: 356px;
  padding: 34px 32px 28px;
  border-radius: var(--r-xl);
  background: var(--surface-veil);
  -webkit-backdrop-filter: saturate(180%) blur(30px);
  backdrop-filter: saturate(180%) blur(30px);
  border: 0.5px solid rgba(255, 255, 255, 0.8);
  box-shadow: var(--sh-3);
  text-align: center;
  animation: sheet-in 0.45s var(--ease);
}
@supports not (backdrop-filter: blur(4px)) {
  .gate-card { background: rgba(255, 255, 255, 0.96); }
}
@keyframes sheet-in {
  from { opacity: 0; transform: translateY(14px) scale(0.97); }
  to { opacity: 1; transform: none; }
}
.gate-mark {
  width: 58px; height: 58px;
  margin: 0 auto 16px;
  border-radius: 15px;
  display: grid;
  place-items: center;
  color: #fff;
  background: linear-gradient(160deg, #3f9bff 0%, #007aff 55%, #0060df 100%);
  box-shadow: 0 6px 18px rgba(0, 122, 255, 0.34);
}
.gate-mark .ico { width: 27px; height: 27px; stroke-width: 1.5; }
.gate h1 { font-size: 18px; font-weight: 600; letter-spacing: -0.02em; margin: 0 0 4px; }
.gate p.sub { color: var(--text-2); font-size: 12.5px; margin: 0 0 22px; }
.gate .input { text-align: center; height: 36px; letter-spacing: 0.04em; }
.gate .btn { margin-top: 12px; height: 34px; }
.gate-msg {
  margin-top: 14px;
  font-size: 12.5px;
  color: var(--red);
  min-height: 18px;
  transition: opacity var(--dur);
}
.gate-foot { margin-top: 20px; font-size: 12px; color: var(--text-3); }

/* ------------------------------------------------------------- app shell */
/* 两列 + 一条可有可无的右栏。第三列写 auto：.rail 不显示时它自己塌成 0，
   不用为每个视图各写一份 grid-template-columns。
   所有主视图都显式 `grid-column: 2` —— display:none 会让自动定位重排，
   把某一屏塞进侧栏那一格（这个坑本文件踩过，别再靠源码顺序）。 */
.app {
  display: grid;
  grid-template-columns: var(--side-w, 268px) minmax(0, 1fr) auto;
  height: 100vh;
}
.dashboard, .project, .create, .places, .facelib, .metrics {
  display: none;
  grid-column: 2;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  background: var(--bg);
}
#app[data-view="dashboard"] .dashboard,
#app[data-view="project"] .project,
#app[data-view="create"] .create,
#app[data-view="metrics"] .metrics { display: flex; }
/* 右栏（测试聊天）只服务角色设定，所以只在「项目 + 文案页签」出现。
   选择器带上 #app 不是啰嗦：.rail 自己的 `display:flex` 写在本文件更靠后，
   同优先级下后写的赢，光写 `.rail{display:none}` 会被它静默盖掉。 */
#app .rail { display: none; }
#app[data-view="project"][data-ptab="text"] .rail { display: flex; }
/* 工具栏里那组只对文案有意义的按钮 */
.tb-text-only { display: none; align-items: center; gap: 8px; }
#app[data-view="project"][data-ptab="text"] .tb-text-only { display: flex; }
/* 项目还没绑角色档案：右栏和「版本 / 发布」没有作用对象，一起收掉。
   属性选择器要跟上面那条**一样多**才盖得住（同优先级、后写的赢）—— 少写一个
   [data-view] 就是 (1,1,1) 打不过 (1,2,1)，规则静默失效。 */
#app[data-view="project"][data-bound="0"] .rail,
#app[data-view="project"][data-bound="0"] .tb-text-only { display: none; }
.sidebar-toggle { display: none; }

/* ---------------------------------------------------------------- sidebar */
.sidebar {
  display: flex;
  flex-direction: column;
  min-height: 0;
  z-index: 60;
  background: rgba(246, 246, 249, 0.86);
  -webkit-backdrop-filter: saturate(180%) blur(24px);
  backdrop-filter: saturate(180%) blur(24px);
  border-right: 0.5px solid var(--hair);
}
.sidebar-head {
  padding: 16px 16px 10px;
  display: flex;
  align-items: center;
  gap: 9px;
}
.sidebar-mark {
  width: 26px; height: 26px;
  border-radius: 7px;
  display: grid; place-items: center;
  color: #fff;
  background: linear-gradient(160deg, #3f9bff, #007aff);
  box-shadow: 0 1px 4px rgba(0, 122, 255, 0.35);
}
.sidebar-mark .ico { width: 15px; height: 15px; }
.sidebar-title { font-size: 13.5px; font-weight: 600; letter-spacing: -0.01em; }

.sidebar-search { padding: 0 12px 8px; position: relative; }
.sidebar-search .ico {
  position: absolute; left: 21px; top: 50%;
  transform: translateY(-50%);
  color: var(--text-3);
  pointer-events: none;
  margin-top: -4px;
}
.sidebar-search .input {
  height: 28px;
  padding-left: 28px;
  background: rgba(118, 118, 128, 0.1);
  border-color: transparent;
  box-shadow: none;
  font-size: 13px;
}
.sidebar-search .input:focus { background: var(--surface); border-color: var(--accent); }

.sidebar-section {
  padding: 10px 18px 4px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-3);
  letter-spacing: 0.03em;
  text-transform: uppercase;
}
.char-list { flex: 1; overflow-y: auto; padding: 0 8px 8px; min-height: 0; }
.char-item {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  text-align: left;
  padding: 7px 9px;
  border: 0;
  background: transparent;
  border-radius: 8px;
  transition: background var(--dur) var(--ease);
}
.char-item:hover { background: rgba(0, 0, 0, 0.045); }
.char-item.active { background: var(--accent); color: #fff; }
.char-item.active .char-meta { color: rgba(255, 255, 255, 0.75); }
.char-item.active .avatar { background: rgba(255, 255, 255, 0.22); color: #fff; }
.avatar {
  width: 26px; height: 26px;
  border-radius: 99px;
  display: grid; place-items: center;
  background: rgba(0, 122, 255, 0.12);
  color: var(--accent);
  font-size: 12px;
  font-weight: 600;
  flex: none;
}
.char-body { min-width: 0; flex: 1; }
.char-name {
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  display: flex;
  align-items: center;
  gap: 5px;
}
.char-meta { font-size: 12px; color: var(--text-3); }
.dot-published {
  width: 6px; height: 6px; border-radius: 99px;
  background: var(--green);
  flex: none;
}
.char-item.active .dot-published { background: #fff; }

/* ---- 合一列表：一行 = 一个角色 = 一个项目 ---- */
.nav-list { flex: 1; overflow-y: auto; padding: 0 8px 8px; min-height: 0; }
.nav-item {
  width: 100%; display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border: 0; background: none; cursor: pointer;
  border-radius: 9px; text-align: left; font: inherit; color: var(--text);
}
.nav-item + .nav-item { margin-top: 1px; }
/* 关联人物子目录：主角色带折叠箭头，版本缩进一级 */
.nav-chev {
  flex: none; width: 14px; margin-left: -6px; margin-right: -4px; text-align: center;
  font-size: 14px; line-height: 1; color: var(--text-3); transition: transform var(--dur) var(--ease);
}
.nav-chev.is-open { transform: rotate(90deg); }
.nav-chev:hover { color: var(--text); }
.nav-item.active .nav-chev { color: rgba(255, 255, 255, 0.85); }
.nav-item.nav-sub { padding-left: 30px; }
.nav-item.nav-sub .avatar { transform: scale(0.85); }
.nav-kids { font-size: 11px; color: var(--text-3); margin-top: 1px; }
.nav-item.active .nav-kids { color: rgba(255, 255, 255, 0.75); }
.nav-item:hover { background: rgba(0, 0, 0, 0.045); }
.nav-item.active { background: var(--accent); color: #fff; }
.nav-item.active .char-meta { color: rgba(255, 255, 255, 0.75); }
.nav-item.active .avatar { background: rgba(255, 255, 255, 0.22); color: #fff; }

/* 侧栏改版（芦嘉怡 2026-10-29）：分三组、角色脸头像、海外角色挂国旗、第二行负责人、右边最近改动日。
   14:11 第二轮「质感」：照 macOS / Linear 侧栏的克制做法 —— 选中是浮起来的白底卡片，不再整行亮蓝；
   头像一律 40px 正圆 + 0.5px 内描边；名字 / 负责人 / 日期三档灰阶分开；去掉不解释的小圆点（14:26 起最右边什么都不放）。 */
.nav-list { padding: 2px 8px 10px; }
.nav-group {
  width: 100%; display: flex; align-items: center; gap: 5px;
  margin: 14px 0 3px; padding: 3px 8px 3px 4px; border: 0; background: none; cursor: pointer;
  font: inherit; font-size: 11.5px; font-weight: 600; letter-spacing: 0.02em; color: #8e8e93; border-radius: 6px;
}
.nav-group:first-child { margin-top: 4px; }
.nav-group:hover { color: #6e6e73; }
.nav-group-chev { display: inline-grid; place-items: center; width: 14px; height: 14px; flex: none; transition: transform var(--dur) var(--ease); transform: rotate(-90deg); }
.nav-group-chev .ico { width: 14px; height: 14px; }
.nav-group.is-open .nav-group-chev { transform: none; }
.nav-group-n { font-weight: 500; color: #aeaeb2; font-variant-numeric: tabular-nums; }

.nav-list .nav-item {
  padding: 6px 8px; gap: 10px; border-radius: 9px; min-height: 52px;
  transition: background var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.nav-list .nav-item + .nav-item { margin-top: 2px; }
.nav-list .nav-item:hover { background: rgba(0, 0, 0, 0.04); }
/* 选中：浮起来的白卡片 + 极淡的边和影子，字保持深色；不用整行亮蓝（嘉怡 14:11「质感好拉」） */
.nav-list .nav-item.active,
.nav-list .nav-item.active:hover {
  background: #fff; color: var(--text);
  box-shadow: 0 0 0 0.5px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.07);
}
.nav-list .nav-item.active .char-name { font-weight: 600; }
.nav-list .nav-item.active .nav-chev { color: var(--text-3); }
.nav-list .nav-item.active .nav-kids { color: #8e8e93; }
.nav-list .nav-item.active .ov-badge.is-look { background: var(--surface-sunken); color: var(--text-2); }

/* 头像：一律 40px 正圆。脸图绝对定位铺满再裁圆 —— 以前 img 用 height:100% 挂在 grid 里，
   竖图（3:4）算不出高度，长成 40×53 的胶囊、还溢出这一行，跟方图的正圆对不上（嘉怡 14:11 截图里何栩那个）。 */
.nav-avatar {
  position: relative; width: 40px; height: 40px; flex: none;
  display: grid; place-items: center; border-radius: 99px;
  background: #e9e9ee; color: #6e6e73; font-size: 15px; font-weight: 600;
}
.nav-list .nav-item.active .nav-avatar { background: #e9e9ee; color: #6e6e73; }
.nav-avatar > img:not(.nav-flag) {
  position: absolute; inset: 0; width: 100%; height: 100%;
  border-radius: 99px; object-fit: cover; object-position: 50% 22%; display: block;
}
/* 统一 0.5px 内描边：浅色照片边缘也不会糊进底色里，每个圆的轮廓看起来一样 */
.nav-avatar::after {
  content: ""; position: absolute; inset: 0; border-radius: 99px; pointer-events: none;
  box-shadow: inset 0 0 0 0.5px rgba(0, 0, 0, 0.12);
}
.nav-flag {
  position: absolute; right: -2px; bottom: -2px; z-index: 1; width: 16px; height: 16px;
  border-radius: 99px; object-fit: cover; background: #fff;
  box-shadow: 0 0 0 1.5px #fff, 0 0 0 2px rgba(0, 0, 0, 0.10);
}
.nav-item.nav-sub { padding-left: 26px; }
.nav-item.nav-sub .nav-avatar { transform: none; width: 32px; height: 32px; font-size: 13px; }
.nav-item.nav-sub .nav-flag { width: 14px; height: 14px; }
.nav-list .nav-item.nav-sub { min-height: 44px; }

.nav-line { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.nav-line .char-name { flex: 1; min-width: 0; font-size: 13px; font-weight: 500; color: #1d1d1f; display: block; line-height: 18px; }
.nav-line + .nav-line { margin-top: 1px; }
.nav-day { flex: none; font-size: 11px; color: #8e8e93; font-variant-numeric: tabular-nums; }
.nav-owner {
  flex: 1; min-width: 0; font-size: 12px; line-height: 16px; color: #6e6e73;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.nav-owner.is-empty { color: #aeaeb2; }
/* 单独成行的海外版 / 副角色：先写是谁的版本，跟负责人同字号，压暗的蓝稍作区分 */
.nav-rel {
  flex: none; max-width: 62%; font-size: 12px; line-height: 16px; color: #4f74a8; font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.nav-rel + .nav-owner::before { content: "· "; color: #aeaeb2; }
.nav-kids { font-size: 11px; color: #8e8e93; margin-top: 1px; }

/* ---- 工具区：次要入口，图标 + 小字，跟上面那份主列表拉开分量 ---- */
.sidebar-foot { padding: 8px 8px calc(8px + env(safe-area-inset-bottom, 0px)); border-top: 0.5px solid var(--hair); display: grid; gap: 1px; }
.tool-btn {
  display: flex; align-items: center; gap: 9px;
  width: 100%; padding: 7px 10px; border: 0; background: none; cursor: pointer;
  border-radius: 8px; font: inherit; font-size: 12.5px; color: var(--text-2);
  text-align: left;
}
.tool-btn:hover { background: rgba(0, 0, 0, 0.05); color: var(--text); }
.tool-btn .ico { flex: none; color: var(--text-3); }
.tool-btn:hover .ico { color: var(--accent); }

/* 文档弹层：那一列整块从 #docPark 搬进来 */
.docs-modal { max-width: 460px; text-align: left; }
.docs-modal .doc-list { max-height: 60vh; overflow-y: auto; display: grid; gap: 2px; }


/* 「生图工作台」是一个跟编辑区平级的视图，不是一个动作 —— 人在里面的时候侧栏
   得看得出来「你在这儿」，沿用角色/文档条目那套选中态（实心主色）。
   有了这个提示，「怎么回去」的答案就写在屏幕上：点上面任意一个角色。 */
.sidebar-foot .btn.active,
.sidebar-foot .btn.active:hover {
  background: var(--accent);
  border-color: transparent;
  color: #fff;
  box-shadow: 0 1px 2px rgba(0, 122, 255, 0.32);
}

/* ----------------------------------------------------------------- editor */
/* .editor 现在是「文案」页签里的一块，不再是 .app 的 grid 子项。内部一行没动。 */
.editor { display: flex; flex-direction: column; min-width: 0; min-height: 0; background: var(--bg); }

/* ---- 项目详情：阶段带 + 三个页签面板 ----
   头部 chrome 进滚动区（2026-08-28 审计 E）：以前工具栏 / 头卡 / 角色工具条 / 流程条
   四层全叠在 #editorScroll 外面，每档断点各压一次，1920 和手机文案页签两头都漏
   （375×667 可编辑区只剩 84px）。现在 main.project 自己是滚动容器：只有工具栏
   sticky 常驻（>880 角色工具条也 sticky 在它下面），头卡和流程条随内容滚走。
   三个页签面板不再各自内滚 —— 所有「滚到某张卡」的代码都以 #projectView 为准。 */
.project { overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
.project > .toolbar { position: sticky; top: 0; z-index: 20; }
.project-panes { flex: 1 0 auto; min-height: 0; display: flex; }
/* 横向 flex-basis 必须是 0：auto 会让面板按 max-content 撑宽（片架 width:max-content
   直接把视频页签撑到 2000px）。高度由内容决定，不再内滚。 */
.ppane { flex: 1 1 0%; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
#paneText > #paneTextBody { flex: 1 0 auto; min-height: 0; display: flex; flex-direction: column; }
/* 三个面板里的滚动容器都退化成普通块：滚的是 main.project */
.project .editor-scroll, .project .wb-lib { flex: none; overflow: visible; min-height: 0; }
/* 空态以前靠 flex:1 撑满面板来居中，现在面板按内容高：给个最小高度 */
.project .empty { min-height: 60vh; }
/* scrollIntoView / 红线跳转的目标别钻到 sticky 的工具栏 + 工具条底下去
   （--ct-h 由 JS 量 #ctRow 写在 #app 上，手机上它不 sticky、归零） */
.project .card, .project .field, .project .scene-card, .project .proj-sec,
.project .lint-target { scroll-margin-top: calc(var(--toolbar-h) + var(--ct-h, 0px) + 12px); }
@media (min-width: 881px) {
  .ct-row { position: sticky; top: var(--toolbar-h); z-index: 15; background: var(--surface); }
}
/* 阶段带在项目视图里是页签条，贴在工具栏下面不跟着内容滚 */
#app[data-view="project"] .pdetail-head {
  flex: none; margin: 12px 18px 0; padding: 10px 14px 8px;
}
.pstage-btn.is-on { background: var(--surface-sunken); }
.pstage-btn.is-on .pstage-label { color: var(--accent); font-weight: 600; }
.pstages-nav { position: relative; }

.toolbar {
  height: 52px;
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 18px;
  background: var(--surface-veil);
  -webkit-backdrop-filter: saturate(180%) blur(24px);
  backdrop-filter: saturate(180%) blur(24px);
  border-bottom: 0.5px solid var(--hair);
}
.toolbar-title {
  font-size: 15px; font-weight: 600; letter-spacing: -0.02em;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.toolbar-spacer { flex: 1; }
/* 返回按钮放在标题左边、带文字：阅读视图里最该一眼找到的就是「怎么回去」，
   以前它缩在右上角，还只写「返回」（回哪儿？）。 */
.btn-back { flex: none; margin-left: -8px; }
/* 风景与场地、人脸底图库没有 .toolbar：同一颗「‹ 总览」放在大标题上面一行（overview-back.js）。 */
.pl-header .btn-back, .fl-header .btn-back { margin-bottom: 6px; }

/* ---------------------------------------------------------- 角色工具条 */
/* 角色标题下面一条固定的横带。放在 .editor-scroll 外面 = 正文怎么滚它都在，
   不用 position:sticky（sticky 在 iOS 上和键盘弹起会打架）。
   左边那块永远写着作用对象：多人共用一个工作台，「这一下点在谁身上」不能靠猜。 */
/* 工具条 + 流程条的外壳。>1440 它就是个普通的块（两条各占一行，跟以前一样），
   ≤1440 才变 flex 把两条并进一行 —— 见文件末尾那节「笔记本纵向减负」。
   flex:none 是因为它现在是 #paneTextBody 这个列向 flex 的直接孩子，
   不钉住的话正文一长它就被压扁。 */
.ct-row { flex: none; min-width: 0; }
.chartools {
  flex: none;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 18px;
  background: var(--surface);
  border-bottom: 0.5px solid var(--hair);
}
.ct-scope {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
  flex: none;
  max-width: 42%;
}
.ct-label { font-size: 12px; color: var(--text-3); flex: none; }
.ct-name {
  font-size: 13px;
  font-weight: 590;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ct-scope.is-empty .ct-name { color: var(--text-3); font-weight: 400; }
.ct-btns {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  min-width: 0;
  justify-content: flex-end;
  overflow-x: auto;
  scrollbar-width: none;
}
.ct-btns::-webkit-scrollbar { display: none; }
.ct-btns > .btn { flex: none; }
.ct-sep {
  flex: none;
  width: 0.5px;
  align-self: stretch;
  background: var(--hair-strong);
  margin: 2px 2px;
}
/* 红线条数徽标：数字直接长在按钮里，不用打开面板就知道有没有问题 */
.ct-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 17px;
  height: 17px;
  padding: 0 5px;
  margin-left: 5px;
  border-radius: 99px;
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  background: var(--red);
  color: #fff;
}
/* 白字压在徽标上：亮绿 / 亮橙 / 亮红只有 2–3.4:1，用深一档的同色系 */
.ct-badge { background: #c9231a; }
.ct-badge.is-clean { background: #1e7a37; }
.ct-badge.is-warn { background: #a85e00; }

.save-state {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  color: var(--text-3);
  transition: color var(--dur);
  white-space: nowrap;
}
.save-state[data-state="saving"] { color: var(--accent); }
.save-state[data-state="error"] { color: var(--red); }
.save-state .ico { width: 13px; height: 13px; }
.spin { animation: spin 0.9s linear infinite; transform-origin: 50% 50%; }
@keyframes spin { to { transform: rotate(360deg); } }

.badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 20px;
  padding: 0 8px;
  border-radius: 99px;
  font-size: 12px;
  font-weight: 500;
  background: rgba(118, 118, 128, 0.12);
  color: var(--text-2);
}
.badge-green { background: rgba(52, 199, 89, 0.14); color: #1e7a37; }
.badge-blue { background: var(--accent-soft); color: var(--accent); }
.badge-orange { background: rgba(255, 149, 0, 0.16); color: #b26a00; }
.badge-purple { background: rgba(175, 82, 222, 0.14); color: #7d2fa8; }
.badge-red { background: rgba(255, 59, 48, 0.13); color: #c22a20; }

/* 所有滚动面板的底部 padding 都为右下角浮标（--dock-h + --dock-gap）留位：
   固定层之间的避让协议，别让「对话」浮标盖住最后一张卡的按钮。 */
.editor-scroll { flex: 1; overflow-y: auto; padding: 20px 22px calc(var(--dock-h) + var(--dock-gap) + 28px); min-height: 0; }
.editor-inner { max-width: 780px; margin: 0 auto; display: grid; gap: 16px; }

.card {
  background: var(--surface);
  border: 0.5px solid var(--hair);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-2);
  overflow: hidden;
}
.card-head {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 13px 18px;
  cursor: pointer;
  user-select: none;
  transition: background var(--dur) var(--ease);
}
.card-head:hover { background: rgba(0, 0, 0, 0.017); }
.card-head .chev { color: var(--text-3); transition: transform var(--dur) var(--ease); }
.card[data-open="false"] .chev { transform: rotate(-90deg); }
.card[data-open="false"] .card-body { display: none; }
.card-icon { color: var(--accent); display: grid; place-items: center; }
.card-title { font-size: 13.5px; font-weight: 600; letter-spacing: -0.01em; }
.card-hint { font-size: 12px; color: var(--text-3); font-weight: 400; }
.card-body { padding: 4px 18px 18px; display: grid; gap: 15px; }

.field { display: grid; gap: 5px; }
.field > label { font-size: 12px; font-weight: 500; color: var(--text-2); }
/* 字段下面那行「目的」是正文，不是脚注：--text-2 + 1.6 行距。
   （曾经是 11.5px 的 --text-3 浅灰，看着像装饰，没人读。） */
.field .help { font-size: 12.5px; color: var(--text-2); line-height: 1.6; }
/* 日常剧本字数条：红 = 不到最少 3000，橙 = 到了但没到建议 3500，绿 = 达标 */
.script-count { display: flex; align-items: center; gap: 8px; margin-top: 6px; font-size: 12.5px; color: var(--text-2); line-height: 1.6; }
.script-count b { font-variant-numeric: tabular-nums; font-weight: 600; }
.script-count .script-bar { flex: 0 0 120px; height: 4px; border-radius: 2px; background: var(--border, #e5e5e5); overflow: hidden; }
.script-count .script-bar i { display: block; height: 100%; width: 0; border-radius: 2px; background: currentColor; transition: width .2s; }
.script-count.is-short { color: var(--red, #d33); }
.script-count.is-near { color: var(--orange, #d08a00); }
.script-count.is-ok { color: var(--green, #2a8a4a); }
/* 空的 .help 是留给 ⓘ 展开面板的槽位（字段说明现在全在 ⓘ 里），本身不占一行。 */
.field .help:empty { display: none; }
/* 整卡就是一个字段（核心记忆 / 前传记忆 / 结构化事件）时，那个 .field 只是
   data-field 的挂钩，ⓘ 挂在卡头上。display:contents 让它不产生盒子、不吃
   .card-body 的 gap —— 但点开 ⓘ 时面板插进来照样显示。 */
.field.field-anchor { display: contents; }
.field .help strong { font-weight: 600; color: var(--text); }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; }
.divider { height: 0.5px; background: var(--hair); margin: 1px 0; }

/* repeatable item cards */
.item-card {
  border: 0.5px solid var(--hair-strong);
  border-radius: var(--r-md);
  background: var(--surface-sunken);
  padding: 11px 12px;
  display: grid;
  gap: 8px;
}
.item-head { display: flex; align-items: center; gap: 8px; }
.item-index {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-3);
  min-width: 18px;
}
.item-list { display: grid; gap: 9px; }
.empty-inline {
  font-size: 12.5px;
  color: var(--text-3);
  padding: 14px;
  text-align: center;
  border: 1px dashed var(--hair-strong);
  border-radius: var(--r-md);
}

/* --------------------------------------------------------- today_script */
.script-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.timeline { display: grid; gap: 0; position: relative; }
.tl-row {
  display: grid;
  grid-template-columns: 66px 1fr;
  gap: 12px;
  padding: 11px 0;
  border-top: 0.5px solid var(--hair);
}
.tl-row:first-child { border-top: 0; }
.tl-time {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
  padding-top: 1px;
  position: relative;
}
.tl-time::after {
  content: "";
  position: absolute;
  right: -6.5px; top: 6px;
  width: 6px; height: 6px;
  border-radius: 99px;
  background: var(--hair-strong);
}
.tl-row.is-trigger .tl-time::after { background: var(--accent); }
.tl-body { border-left: 0.5px solid var(--hair); padding-left: 14px; min-width: 0; }
.tl-desc { font-size: 13px; line-height: 1.65; white-space: pre-wrap; word-break: break-word; }
.tl-tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; }
.tl-outcome {
  margin-top: 6px;
  font-size: 12px;
  color: var(--text-2);
  background: var(--surface-sunken);
  border-radius: var(--r-sm);
  padding: 6px 9px;
  white-space: pre-wrap;
}

/* progress bar for extraction */
.progress {
  height: 4px;
  border-radius: 99px;
  background: rgba(0, 122, 255, 0.14);
  overflow: hidden;
  position: relative;
}
.progress::after {
  content: "";
  position: absolute;
  inset: 0;
  width: 38%;
  border-radius: 99px;
  background: var(--accent);
  animation: slide 1.5s var(--ease) infinite;
}
@keyframes slide {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(360%); }
}
.extract-status {
  display: flex; align-items: center; gap: 8px;
  font-size: 12.5px; color: var(--text-2);
}

/* -------------------------------------------------------------- right rail */
.rail {
  width: var(--rail-w);
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--surface);
  border-left: 0.5px solid var(--hair);
  transition: width var(--dur) var(--ease);
}
.rail.collapsed { width: 0; overflow: hidden; border-left: 0; }
.rail-head {
  height: 52px;
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px 0 14px;
  border-bottom: 0.5px solid var(--hair);
  background: var(--surface-veil);
  -webkit-backdrop-filter: saturate(180%) blur(24px);
  backdrop-filter: saturate(180%) blur(24px);
}
.rail-pane { flex: 1; display: flex; flex-direction: column; min-height: 0; }
.rail-subbar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 9px 12px;
  border-bottom: 0.5px solid var(--hair);
  background: var(--surface-sunken);
  flex-wrap: wrap;
}

/* --------------------------------------------------------------- chat */
.chat-log {
  flex: 1;
  overflow-y: auto;
  padding: 16px 14px 8px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-height: 0;
  background: var(--surface);
}
.bubble-row { display: flex; margin-top: 9px; }
.bubble-row.me { justify-content: flex-end; }
.bubble-row.tight { margin-top: 3px; }
.bubble {
  max-width: 78%;
  padding: 8px 13px;
  border-radius: 18px;
  font-size: 13.5px;
  line-height: 1.55;
  white-space: pre-wrap;
  word-break: break-word;
  background: rgba(118, 118, 128, 0.11);
  color: var(--text);
  animation: pop 0.28s var(--ease);
}
.bubble-row.me .bubble { background: var(--accent); color: #fff; }
@keyframes pop {
  from { opacity: 0; transform: translateY(6px) scale(0.98); }
  to { opacity: 1; transform: none; }
}
.bubble-time {
  font-size: 12px;
  color: var(--text-3);
  text-align: center;
  margin: 12px 0 2px;
  font-variant-numeric: tabular-nums;
}
.sys-note {
  align-self: center;
  font-size: 12px;
  color: var(--text-3);
  background: rgba(118, 118, 128, 0.08);
  border-radius: 99px;
  padding: 3px 12px;
  margin: 8px 0 2px;
  max-width: 90%;
  text-align: center;
}
.typing { display: inline-flex; gap: 4px; padding: 3px 2px; }
.typing i {
  width: 7px; height: 7px; border-radius: 99px;
  background: var(--text-3);
  animation: bounce 1.2s infinite var(--ease);
}
.typing i:nth-child(2) { animation-delay: 0.16s; }
.typing i:nth-child(3) { animation-delay: 0.32s; }
@keyframes bounce {
  0%, 60%, 100% { opacity: 0.35; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-3px); }
}

.composer {
  flex: none;
  padding: 10px 12px 12px;
  border-top: 0.5px solid var(--hair);
  display: flex;
  gap: 8px;
  align-items: flex-end;
  background: var(--surface-veil);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
}
.composer .textarea {
  min-height: 34px;
  max-height: 130px;
  border-radius: 17px;
  padding: 7px 14px;
  resize: none;
  line-height: 1.5;
}
.send-btn {
  width: 32px; height: 32px;
  flex: none;
  border-radius: 99px;
  border: 0;
  display: grid; place-items: center;
  background: var(--accent);
  color: #fff;
  transition: transform var(--dur) var(--ease), opacity var(--dur);
}
.send-btn:disabled { opacity: 0.35; }
.send-btn:active:not(:disabled) { transform: scale(0.92); }

/* ----------------------------------------------------------------- runs */
.runs-scroll { flex: 1; overflow-y: auto; padding: 12px 14px 20px; display: grid; gap: 10px; align-content: start; min-height: 0; }
.run-card {
  border: 0.5px solid var(--hair);
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow: var(--sh-1);
  padding: 11px 13px;
  display: grid;
  gap: 8px;
}
.run-top { display: flex; align-items: center; gap: 8px; }
.run-id { font-family: var(--font-mono); font-size: 12px; color: var(--text-3); }
.run-bar { height: 3px; border-radius: 99px; background: rgba(118, 118, 128, 0.14); overflow: hidden; }
.run-bar > i { display: block; height: 100%; background: var(--accent); border-radius: 99px; transition: width 0.4s var(--ease); }

/* ------------------------------------------------------- empty states */
.empty {
  flex: 1;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 8px;
  color: var(--text-3);
  padding: 40px 24px;
  text-align: center;
}
.empty .ico { width: 30px; height: 30px; stroke-width: 1.3; opacity: 0.5; }
.empty h3 { margin: 4px 0 0; font-size: 14px; font-weight: 600; color: var(--text-2); }
.empty p { margin: 0; font-size: 12.5px; max-width: 260px; line-height: 1.6; }

/* ---------------------------------------------------------------- toasts */
.toasts {
  position: fixed;
  top: 14px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 400;
  display: grid;
  gap: 8px;
  justify-items: center;
  pointer-events: none;
}
.toast {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  max-width: 520px;
  padding: 10px 15px;
  border-radius: var(--r-lg);
  background: rgba(28, 28, 30, 0.9);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  backdrop-filter: blur(20px) saturate(180%);
  color: #fff;
  font-size: 13px;
  line-height: 1.5;
  box-shadow: var(--sh-3);
  animation: toast-in 0.3s var(--ease);
  pointer-events: auto;
  word-break: break-word;
}
.toast.out { animation: toast-out 0.24s var(--ease) forwards; }
.toast .ico { margin-top: 2px; }
.toast.ok .ico { color: #4cd964; }
.toast.err .ico { color: #ff6b60; }
@keyframes toast-in { from { opacity: 0; transform: translateY(-14px) scale(0.96); } to { opacity: 1; transform: none; } }
@keyframes toast-out { to { opacity: 0; transform: translateY(-10px) scale(0.97); } }

/* ---------------------------------------------------------------- modal */
.scrim {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.22);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  z-index: 300;
  display: grid;
  place-items: center;
  padding: var(--sheet-pad);
  /* 兜底：万一某个弹层仍然比视口高（没吃到 .sheet），至少旁白能滚到它的底部。
     grid 的自动行只往下溢，不会把顶部裁掉。 */
  overflow: auto;
  animation: fade 0.2s var(--ease);
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
.modal {
  width: 100%;
  max-width: 440px;
  max-height: calc(100dvh - 2 * var(--sheet-pad));
  overflow: auto;
  background: rgba(252, 252, 253, 0.96);
  -webkit-backdrop-filter: saturate(180%) blur(30px);
  backdrop-filter: saturate(180%) blur(30px);
  border-radius: var(--r-xl);
  box-shadow: var(--sh-3);
  border: 0.5px solid rgba(255, 255, 255, 0.7);
  padding: var(--sheet-pt) var(--sheet-px) var(--sheet-pb);
  animation: sheet-in 0.32s var(--ease);
  text-align: center;
}
.modal.wide { max-width: 640px; text-align: left; }
.modal h2 { margin: 0 0 6px; font-size: 16px; font-weight: 600; letter-spacing: -0.02em; }
.modal p.sub { margin: 0 0 16px; font-size: 12.5px; color: var(--text-2); line-height: 1.6; overflow-wrap: anywhere; }
.modal-actions { display: flex; gap: 9px; justify-content: center; margin-top: 20px; }
.modal-actions .btn { min-width: 92px; height: 32px; }
.modal .field { text-align: left; }

/* ==========================================================================
   弹层视口约束基类 .sheet
   openOverlay 挂载的每一个框都自动带上它（builder 自己标 .bare 才例外）——
   以后再写一个新弹层，不写 max-height 也截不掉、滚得到底、关得掉。
   · 框本身是滚动容器：max-height 卡在视口内，overflow-y:auto，
     overscroll-behavior 防止滚到底把底下的页面带着滚
   · .sheet-head / .vinspect-head / .pdel-head 钉在顶部（sticky），
     负外边距抵掉框的 padding，滚动时正文从它底下过而不是从它上面露出来
   · 动作条（.sheet-actions，或框里最后一个 .modal-actions）钉在底部：
     3400px 的发布单首屏也看得见「取消 / 发布」，2000 字的报错确认框也一样
   · 宽度和内部布局各自保留：.modal / .vinspect / .wb-light / .plib / .split
     只是 .sheet 的变体，只改宽度和内部 grid
   · 写在基础规则里，不写在 @media 里：手机端修了桌面端漏掉的事故（2026-08-28
     检查器）不许再来一次，@media 只改数字。 */
.sheet {
  display: flex;
  flex-direction: column;
  min-height: 0;
  max-height: calc(100dvh - 2 * var(--sheet-pad));
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  --sheet-bg: var(--surface);
}
.sheet-body { flex: 1 1 auto; min-height: 0; }
.sheet-head,
.sheet .vinspect-head,
.sheet .pdel-head {
  position: sticky;
  /* Chrome 的 sticky 参照的是滚动容器的**内容盒**：top:0 会停在 padding 下面，
     露一条 padding 高的缝让正文从上面滚过。负 pt 才贴到框的上沿。 */
  top: calc(-1 * var(--sheet-pt));
  z-index: 3;
  background: var(--sheet-bg);
  margin: calc(-1 * var(--sheet-pt)) calc(-1 * var(--sheet-px)) 0;
  padding: var(--sheet-pt) var(--sheet-px) 8px;
}
.sheet-actions,
.sheet .modal-actions:last-child {
  position: sticky;
  bottom: calc(-1 * var(--sheet-pb));
  z-index: 3;
  background: var(--sheet-bg);
  margin-left: calc(-1 * var(--sheet-px));
  margin-right: calc(-1 * var(--sheet-px));
  margin-bottom: calc(-1 * var(--sheet-pb));
  padding: 12px var(--sheet-px) var(--sheet-pb);
}
/* 内容真的滚起来了（JS 用 ResizeObserver 标 is-scroll）：动作条上沿描一道影子，
   告诉人「上面还有」。不滚的时候一条线都不多画。 */
.sheet.is-scroll .sheet-actions,
.sheet.is-scroll .modal-actions:last-child { box-shadow: 0 -10px 14px -12px rgba(0, 0, 0, 0.22); }
/* 右上角常驻关闭 ×（sheetCloseX）：0 高的 sticky 壳 + 绝对定位的按钮，
   内容再长它都钉在框的右上角，不跟着滚走。 */
.sheet-x-wrap { position: sticky; top: 0; height: 0; z-index: 4; }
.sheet-x {
  position: absolute;
  top: calc(10px - var(--sheet-pt));
  right: calc(10px - var(--sheet-px));
  width: 32px; height: 32px;
}
.sheet.has-x h2 { padding-right: 40px; }
/* 可折叠长文本（clampText）：默认只露 --clamp-lines 行，被截才显示「展开」 */
.is-clamp {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--clamp-lines);
  overflow: hidden;
}
.is-clamp.is-open { display: block; -webkit-line-clamp: unset; overflow: visible; }
.clamp-more {
  display: none;
  border: 0; background: none; padding: 4px 10px; margin: 2px 0 0 -10px;
  min-height: 32px; min-width: 44px;
  color: var(--accent); font-size: 12.5px; font-weight: 500; cursor: pointer;
}
.clamp-more.is-on { display: inline-block; }
/* 导出预览：一个气泡一行，长行横向滚动而不是折行，条数才看得出来。
   用 div 不用 textarea —— 弹窗的自动聚焦会挑第一个可聚焦元素，
   textarea 抢到焦点会把预览滚走半截。 */
.export-preview {
  height: 236px;
  margin-top: 4px;
  padding: 10px 12px;
  border: 0.5px solid var(--hair);
  border-radius: var(--r-md);
  background: var(--surface-sunken);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.65;
  color: var(--text);
  white-space: pre;
  overflow: auto;
  user-select: text;
  -webkit-user-select: text;
}
.checklist { display: grid; gap: 9px; text-align: left; margin: 4px 0 0; padding: 0; list-style: none; }
.checklist li {
  display: flex;
  gap: 9px;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-2);
  background: var(--surface-sunken);
  border-radius: var(--r-md);
  padding: 9px 11px;
}
.checklist .ico { color: var(--accent); margin-top: 2px; }

/* dropzone */
.dropzone {
  border: 1.5px dashed var(--hair-strong);
  border-radius: var(--r-lg);
  padding: 30px 20px;
  display: grid;
  place-items: center;
  gap: 7px;
  color: var(--text-2);
  background: var(--surface-sunken);
  transition: all var(--dur) var(--ease);
  cursor: pointer;
  text-align: center;
}
.dropzone:hover, .dropzone.over {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}
.dropzone .ico { width: 26px; height: 26px; stroke-width: 1.4; }
.dropzone .dz-title { font-size: 13.5px; font-weight: 500; }
.dropzone .dz-sub { font-size: 12px; color: var(--text-3); }

/* ---------------------------------------------------------------- drawer */
.drawer-scrim {
  position: fixed; inset: 0;
  background: rgba(0, 0, 0, 0.18);
  z-index: 250;
  animation: fade 0.2s var(--ease);
}
.drawer {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  width: 392px;
  max-width: 92vw;
  background: rgba(250, 250, 252, 0.97);
  -webkit-backdrop-filter: saturate(180%) blur(30px);
  backdrop-filter: saturate(180%) blur(30px);
  border-left: 0.5px solid var(--hair);
  box-shadow: -14px 0 40px rgba(0, 0, 0, 0.1);
  z-index: 251;
  display: flex;
  flex-direction: column;
  animation: drawer-in 0.32s var(--ease);
}
@keyframes drawer-in { from { transform: translateX(24px); opacity: 0; } to { transform: none; opacity: 1; } }
.drawer-head {
  height: 52px; flex: none;
  display: flex; align-items: center; gap: 8px;
  padding: 0 12px 0 18px;
  border-bottom: 0.5px solid var(--hair);
}
.drawer-head h2 { font-size: 14px; font-weight: 600; margin: 0; letter-spacing: -0.01em; }
/* 标题下那行小字（「所有项目共用一条对话」）。行高压到 1.2，两行加起来仍在 52px 里 */
.drawer-title { min-width: 0; display: grid; gap: 1px; align-content: center; }
.drawer-sub {
  font-size: 11px; line-height: 1.3; color: var(--text-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.drawer-body { flex: 1; overflow-y: auto; padding: 12px 14px 24px; display: grid; gap: 8px; align-content: start; }
.ver-row {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px;
  border-radius: var(--r-md);
  background: var(--surface);
  border: 0.5px solid var(--hair);
  box-shadow: var(--sh-1);
  transition: background var(--dur) var(--ease);
}
.ver-row:hover { background: #fdfdfd; }
.ver-no { font-family: var(--font-mono); font-size: 12px; color: var(--text-3); min-width: 30px; }
.ver-main { flex: 1; min-width: 0; }
.ver-time { font-size: 12px; color: var(--text-3); }

/* transcript view inside modal */
.transcript {
  max-height: 46vh;
  overflow-y: auto;
  padding: 10px 4px 4px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  border-top: 0.5px solid var(--hair);
  margin-top: 6px;
}
.report-box {
  margin-top: 14px;
  background: var(--surface-sunken);
  border-radius: var(--r-md);
  padding: 12px 14px;
  font-size: 12.5px;
  line-height: 1.75;
  white-space: pre-wrap;
  color: var(--text);
  max-height: 34vh;
  overflow-y: auto;
}

/* ------------------------------------------------------------ 维度体检 */
/* 结果面板就长在人物设定卡里：读到「八成笔墨在考公上」时，要改的那段文字
   正好在上面几行，不用先关掉一个抽屉。 */
.dim-panel {
  display: grid;
  gap: 10px;
  background: var(--surface-sunken);
  border-radius: var(--r-md);
  padding: 13px 14px;
}
.dim-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.dim-head-title { font-size: 12.5px; font-weight: 590; color: var(--text); }
.dim-head-sub { font-size: 12px; color: var(--text-3); }

/* 告警在最顶上——坍缩是唯一一条「不看别的也得先看这个」的结论 */
.dim-alert {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  font-size: 12.5px;
  line-height: 1.6;
  border-radius: var(--r-sm);
  padding: 9px 11px;
}
.dim-alert .ico { flex: none; margin-top: 2px; }
.dim-alert-red { background: rgba(255, 59, 48, 0.1); color: #c22a20; }
.dim-alert-orange { background: rgba(255, 149, 0, 0.13); color: #b26a00; }
.dim-alert-info { background: rgba(120, 120, 128, 0.1); color: #5a5a60; }
.dim-alert-notes { margin: 4px 0 0; padding-left: 16px; }

.dim-rows { display: grid; gap: 7px; }
.dim-row { display: grid; grid-template-columns: 7.2em 1fr 3.4em; gap: 9px; align-items: center; }
.dim-name {
  font-size: 12.5px;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dim-track { height: 8px; border-radius: 99px; background: rgba(0, 0, 0, 0.06); overflow: hidden; }
.dim-fill { height: 100%; border-radius: 99px; background: var(--accent); transition: width var(--dur) var(--ease); }
/* >40% = md 里那条坍缩线，单条也要能一眼看出来 */
.dim-fill.is-hot { background: var(--red); }
.dim-pct { font-size: 12px; color: var(--text-2); text-align: right; font-variant-numeric: tabular-nums; }
.dim-evidence {
  grid-column: 2 / 4;
  font-size: 12px;
  line-height: 1.55;
  color: var(--text-3);
  margin-top: -3px;
}

.dim-suggest { display: grid; gap: 7px; padding-top: 10px; border-top: 1px solid rgba(0, 0, 0, 0.06); }
.dim-suggest-title { font-size: 12.5px; font-weight: 600; color: var(--text); }
.dim-suggest-sub { font-size: 12px; color: var(--text-3); margin-top: -3px; }
.dim-suggest-item { display: flex; align-items: baseline; gap: 8px; font-size: 12.5px; line-height: 1.55; }
.dim-suggest-item .badge { flex: none; }
.dim-suggest-idea { color: var(--text-2); }

@media (max-width: 880px) {
  /* 5.6em 在 390px 上把维度名切成「考公备考与自…」——名字才是这张表的主语。
     让它独占一行，进度条和百分比走第二行。 */
  .dim-row { grid-template-columns: minmax(0, 1fr) 3.4em; }
  .dim-name { grid-column: 1 / -1; white-space: normal; }
  .dim-evidence { grid-column: 1 / -1; margin-top: 0; }
}

/* ------------------------------------------------------ 角色体检面板 */
.chk-sec { display: grid; gap: 10px; }
.chk-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.chk-title { font-size: 13px; font-weight: 590; color: var(--text); }

/* ---- 红线检查 ---- */
.lint-summary {
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
  font-size: 12.5px;
  color: var(--text-2);
}
.lint-group { display: grid; gap: 6px; margin-top: 4px; }
.lint-group-title {
  font-size: 12px;
  font-weight: 590;
  color: var(--text-3);
  letter-spacing: 0.02em;
}
/* 一条问题 = 一个按钮：点了直接跳到那个字段并聚焦，别让人自己在长表单里找 */
.lint-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 3px 9px;
  width: 100%;
  text-align: left;
  padding: 9px 11px;
  border: 0.5px solid var(--hair);
  border-left: 2.5px solid var(--red);
  border-radius: var(--r-md);
  background: var(--surface-sunken);
  transition: background var(--dur) var(--ease);
}
.lint-item:hover { background: #f0f0f3; }
.lint-item.is-warn { border-left-color: var(--orange); }
.lint-item .ico { grid-row: 1 / 3; color: var(--red); flex: none; margin-top: 2px; }
.lint-item.is-warn .ico { color: var(--orange); }
.lint-msg { font-size: 12.5px; line-height: 1.55; color: var(--text); }
.lint-where {
  font-size: 12px;
  color: var(--text-3);
  font-family: var(--font-mono);
  word-break: break-all;
}
.lint-snippet {
  grid-column: 2;
  font-size: 12px;
  line-height: 1.55;
  color: var(--text-2);
  background: var(--surface);
  border-radius: var(--r-sm);
  padding: 5px 8px;
  margin-top: 3px;
  white-space: pre-wrap;
  word-break: break-word;
}
.lint-hit { background: rgba(255, 59, 48, 0.16); border-radius: 3px; padding: 0 2px; }
/* 没法机器判断的红线如实说，不硬凑一个「通过」 */
.lint-manual {
  display: grid;
  gap: 3px;
  padding: 8px 11px;
  border-radius: var(--r-md);
  background: var(--surface-sunken);
  font-size: 12px;
  line-height: 1.55;
  color: var(--text-2);
}
.lint-manual b { font-size: 12px; color: var(--text); font-weight: 590; }
/* 字段被问题条送过来时闪一下，否则跳过去了人也不知道该看哪儿 */
@keyframes lint-flash {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 149, 0, 0); }
  25% { box-shadow: 0 0 0 3px rgba(255, 149, 0, 0.4); }
}
.lint-target { animation: lint-flash 1.4s var(--ease); border-radius: var(--r-md); }

/* ------------------------------------------------------ 五步流程条 */
/* 角色名下面一条横带，五个步。当前步是人手动标的纯标记（draft.pipeline_step），
   没有任何自动推进——所以视觉上只区分「当前」和「其余」，不画已完成/未完成，
   免得看起来像个会自己走的进度条。 */
.pipeline {
  flex: none;
  display: flex;
  align-items: stretch;
  gap: 6px;
  padding: 8px 18px;
  background: var(--surface-veil);
  border-bottom: 0.5px solid var(--hair);
  overflow-x: auto;
  scrollbar-width: none;
}
.pipeline::-webkit-scrollbar { display: none; }
/* ≤880 折叠：只露当前步 + 一颗「全部 N 步」（renderPipeline 建）。桌面不折 ——
   折叠规则只写在手机块里。 */
.pl-fold { display: none; }
.pl-step {
  flex: 1 1 0;
  min-width: 116px;
  display: grid;
  gap: 1px;
  text-align: left;
  padding: 6px 9px;
  border: 0.5px solid transparent;
  border-radius: var(--r-md);
  background: transparent;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.pl-step:hover { background: var(--surface-sunken); }
.pl-step[aria-current="true"] {
  background: var(--accent-soft);
  border-color: rgba(0, 122, 255, 0.22);
}
.pl-title {
  display: flex;
  align-items: baseline;
  gap: 5px;
  font-size: 12.5px;
  font-weight: 560;
  color: var(--text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pl-step[aria-current="true"] .pl-title { color: var(--accent); }
.pl-num { font-size: 12.5px; flex: none; color: var(--text-3); }
.pl-step[aria-current="true"] .pl-num { color: var(--accent); }
.pl-sub {
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- 步详情弹窗 ---- */
.pl-detail { display: grid; gap: 14px; text-align: left; }
.pl-sec > b { display: block; font-size: 13px; font-weight: 600; color: var(--text-2); margin-bottom: 4px; }
.pl-sec p { margin: 0; font-size: 15px; line-height: 1.7; color: var(--text); }
.pl-sec ul { margin: 0; padding-left: 18px; display: grid; gap: 5px; }
.pl-sec li { font-size: 14.5px; line-height: 1.7; color: var(--text); }
/* 参考文档：站内可点的按钮，不是 repo 路径。实习生看不到代码仓，路径对他们是噪音。 */
.pl-docs { display: flex; flex-wrap: wrap; gap: 6px; }
.doc-link {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 13px;
  padding: 5px 10px;
  border: 0.5px solid var(--hair-strong);
  border-radius: 99px;
  background: var(--surface);
  color: var(--accent);
  cursor: pointer;
  transition: background var(--dur) var(--ease);
}
.doc-link:hover { background: var(--accent-soft); }
.doc-link .ico { flex: none; }

/* ------------------------------------------------------ 拆分助手 */
/* 左边贴原文，右边看建议。两栏必须同屏：采用与否的判断全靠「这句在原文里吗」，
   来回切页就没法比。 */
.modal.split {
  max-width: min(1180px, 96vw);
  width: 96vw;
  height: 88vh;
  max-height: 88vh;
  --sheet-pt: 18px; --sheet-px: 18px; --sheet-pb: 16px;
  text-align: left;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  gap: 12px;
}
.fs-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.fs-head h2 { margin: 0; font-size: 15px; font-weight: 600; letter-spacing: -0.02em; }
.fs-head .sub { margin: 0; font-size: 12px; color: var(--text-2); }
.fs-body { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); gap: 14px; min-height: 0; }
.fs-pane { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; gap: 8px; min-height: 0; }
.fs-pane-title { font-size: 12px; font-weight: 600; color: var(--text-2); }
.fs-src { resize: none; height: 100%; font-size: 12.5px; line-height: 1.6; }
.fs-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.fs-right { overflow-y: auto; display: grid; gap: 10px; align-content: start; padding-right: 2px; }

.fs-field {
  border: 0.5px solid var(--hair);
  border-radius: var(--r-md);
  background: var(--surface);
  padding: 10px 11px;
  display: grid;
  gap: 7px;
}
.fs-field-head { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.fs-field-name { font-size: 12.5px; font-weight: 600; }
.fs-field-key { font-family: var(--font-mono); font-size: 12px; color: var(--text-3); }
.fs-hint { font-size: 12px; line-height: 1.55; color: var(--text-3); }
.fs-field textarea { font-size: 12.5px; line-height: 1.6; }
.fs-slot { display: grid; gap: 6px; padding-top: 8px; border-top: 0.5px dashed var(--hair); }
.fs-slot:first-of-type { border-top: 0; padding-top: 0; }

/* 忠实度：低于阈值的句子在这里列出来。textarea 里没法就地标黄，所以标在下面 ——
   点名到句，比一个「可信度 72%」有用得多。 */
.fs-flags {
  background: rgba(255, 149, 0, 0.1);
  border-radius: var(--r-sm);
  padding: 8px 10px;
  display: grid;
  gap: 5px;
}
.fs-flags-title { font-size: 12px; font-weight: 600; color: #b26a00; }
.fs-flag {
  font-size: 12px;
  line-height: 1.55;
  color: #8a5200;
  border-left: 2px solid var(--orange);
  padding-left: 8px;
}
.fs-flag .fs-score { color: #b26a00; font-variant-numeric: tabular-nums; margin-left: 4px; }

@media (max-width: 900px) {
  .modal.split { height: 92vh; max-height: 92vh; }
  .fs-body { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(160px, 0.8fr) minmax(0, 1fr); }
}
@media (max-width: 880px) {
  /* 五步流程条：一步一张定宽卡，横向 swipe + 吸附。
     桌面的 flex:1 0 均分在 390px 上会让「标题 + 一句话」把一张卡撑到 400px+，
     屏幕上只看得见第一步的前半句、还不知道后面有几步——所以这里必须定宽，
     宽度定了 .pl-title / .pl-sub 上的 ellipsis 才会真的生效。 */
  .pipeline {
    padding: 7px 12px;
    gap: 8px;
    scroll-snap-type: x mandatory;
    scroll-padding-left: 12px;
  }
  .pl-step {
    flex: 0 0 auto;
    width: 74vw;
    min-width: 0;
    max-width: 300px;
    padding: 8px 11px;
    scroll-snap-align: start;
  }
  .pl-title { font-size: 13.5px; }
  .pl-num { font-size: 13.5px; }
  .pl-sub { font-size: 12px; }
  .pl-fold { display: inline-flex; flex: none; align-self: center; white-space: nowrap; }
  /* 没标当前步的角色露第 ① 步（.pl-first），别折成一颗光秃秃的按钮 */
  .pipeline.is-folded .pl-step:not([aria-current="true"]):not(.pl-first) { display: none; }
  .pipeline.is-folded { scroll-snap-type: none; }
  .pipeline.is-folded .pl-step[aria-current="true"],
  .pipeline.is-folded .pl-step.pl-first { flex: 1 1 auto; width: auto; min-width: 0; max-width: none; }

  /* 工具条在 390px 上放不下「角色名 + 五个按钮」：名字单独占一行（它是这条
     的主语，不能被 ellipsis 吃掉），按钮折成两行**全部可见**。
     以前这排是横滑的，而滚动条被 scrollbar-width:none 藏了 —— 屏幕上只看得见
     前两个按钮，后面几个存不存在全靠猜。折行多占 40px，换五个入口都在。 */
  .chartools {
    flex-direction: column;
    align-items: stretch;
    gap: 7px;
    padding: 8px 12px;
  }
  .ct-scope { max-width: none; }
  /* 一行横滑：五颗都在，「后面还有」由 .hscroll 的渐隐交代（以前折成三行，
     头部 chrome 吃掉 200px；现在头部随内容滚，但一行仍然更省） */
  .ct-btns { justify-content: flex-start; gap: 6px; }
  .ct-sep { display: none; }
}

/* ==========================================================================
   小屏 —— 老板的小电脑大概率落在 860–1100 这一段，这几档是照着那个区间调的
   ========================================================================== */
/* 1280–1440：侧栏收一点，dashboard 两列，卡内数字降一档 */
@media (max-width: 1440px) {
  :root { --rail-w: 340px; }
  .app { --side-w: 248px; }
  .rail { width: var(--rail-w); }
  .pgroup-grid { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 10px; }
  .editor-scroll { padding: 18px 16px calc(var(--dock-h) + var(--dock-gap) + 20px); }
  #app[data-view="project"] .pdetail-head { margin: 10px 14px 0; }
}
/* ≤1280：右栏再窄一点，卡片自适应；侧栏 232 */
@media (max-width: 1280px) {
  :root { --rail-w: 316px; }
  .app { --side-w: 232px; }
  .rail { width: var(--rail-w); }
  .pgroup-grid { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
  .scene-row { grid-template-columns: 110px minmax(0, 1fr) auto; gap: 12px; }
  .scene-thumb { width: 110px; height: 74px; }
}
/* ≤1024：侧栏彻底让位 —— 收成浮层，主区满宽。881–1024 这一段以前是「侧栏
   挤到 190px + 主区被右栏吃掉一半」，两头都不够用，正是小电脑最难受的地方。 */
@media (max-width: 1024px) {
  /* 右栏成了浮层，不再占列宽：浮标不用为它让位 */
  :root { --rail-w: 0px; }
  .app { --side-w: 0px; }
  .sidebar {
    position: fixed; top: 0; bottom: 0; left: 0;
    width: 260px;
    transform: translateX(-100%);
    transition: transform var(--dur) var(--ease);
    box-shadow: 12px 0 30px rgba(0, 0, 0, 0.08);
  }
  .app.side-open .sidebar { transform: none; }
  .sidebar-toggle { display: inline-flex; }
  .side-open-btn { display: inline-flex !important; }
  /* 右栏在这个宽度下跟主区抢不动了（1024 减掉 300 只剩 700 写设定，两头都憋屈）：
     改成从右边滑进来的浮层，默认收着，要用点工具栏那颗面板键。 */
  /* 浮层从工具栏**下面**开始（--toolbar-h 由 JS 量出来写在 #app 上）：关掉它的那颗
     面板键永远露在外面。再配一层 .rail-scrim（JS 建，走 pushOverlay 栈）：
     Esc / 点旁白 / 切视图三条路统一收。 */
  #app[data-view="project"][data-ptab="text"] .rail {
    position: fixed; top: calc(var(--vbanner, 0px) + var(--toolbar-h)); right: 0;
    bottom: calc(var(--tab-h) + var(--kb, 0px));
    width: min(380px, 92vw);
    transform: translateX(100%);
    transition: transform var(--dur) var(--ease);
    z-index: 55;
    box-shadow: -12px 0 30px rgba(0, 0, 0, 0.1);
  }
  #app[data-view="project"][data-ptab="text"] .rail.rail-open { transform: none; }
  .rail-scrim {
    position: fixed; left: 0; right: 0;
    top: calc(var(--vbanner, 0px) + var(--toolbar-h));
    bottom: calc(var(--tab-h) + var(--kb, 0px));
    z-index: 54;
    background: rgba(0, 0, 0, 0.18);
    animation: fade 0.2s var(--ease);
  }
  .rail.collapsed { width: min(380px, 92vw); overflow: visible; border-left: 0.5px solid var(--hair); }
  .grid-3 { grid-template-columns: 1fr 1fr; }
}

/* ---------------------------------------------------------- responsive */

/* Never `display:none` a grid child here — that would re-flow auto placement
   and drop the editor into the sidebar's track. Collapse the width instead. */

/* ≤880 交给下面「手机端」那一整块接管（一屏一视图 + 底部 tab bar）。
   这里以前把侧栏塌成 0 宽 —— 而 tab bar 要 ≤767 才出现，于是 768–880 这一段
   角色列表既看不见也打不开（老板说的「左边那个滑动我滑都看不到」）。 */
@media (max-width: 880px) {
  .grid-2, .grid-3 { grid-template-columns: 1fr; }
}

/* ========================================================= 自由创作 / 素材库 */
.wb-body {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: 380px minmax(0, 1fr);
  gap: 0;
}
.wb-form {
  border-right: 0.5px solid var(--hair);
  overflow-y: auto;
  padding: 16px;
  background: var(--bg);
}
.wb-form .card-body { display: grid; gap: 14px; }
.wb-gallery-wrap { overflow-y: auto; padding: 16px 18px 40px; display: flex; flex-direction: column; min-height: 0; }

/* --------------------------------------------------------- 参考图上传条 */
.wb-drop {
  border: 1px dashed var(--hair-strong);
  border-radius: var(--r-md);
  background: var(--surface-sunken);
  padding: 10px;
  display: grid;
  gap: 10px;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.wb-drop.over { border-color: var(--accent); background: var(--accent-soft); }
.wb-refs { display: flex; flex-wrap: wrap; gap: 8px; }
.wb-refs:empty { display: none; }
.wb-ref {
  position: relative;
  width: 62px; height: 62px;
  border-radius: var(--r-sm);
  overflow: hidden;
  border: 0.5px solid var(--hair-strong);
  background: var(--surface);
}
.wb-ref img { width: 100%; height: 100%; object-fit: cover; display: block; }
.wb-ref .wb-ref-x {
  position: absolute; top: 2px; right: 2px;
  width: 18px; height: 18px;
  border: 0; border-radius: 99px;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-size: 12px; line-height: 1;
  display: grid; place-content: center;
}
.wb-ref.over-cap { opacity: 0.45; }
/* 在传的那一张：一个转圈的空格子，占住它将来会落在的位置。
   没有它，点完到缩略图出现之间是一段纯黑箱，人会以为没点上再点一遍。 */
.wb-ref-busy {
  display: grid; place-items: center;
  color: var(--text-3);
  background: var(--surface-sunken);
  border-style: dashed;
}
.wb-drop-hint {
  display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; gap: 7px;
  color: var(--text-3); font-size: 12.5px;
  padding: 6px 0;
}
.wb-drop-or { color: var(--text-3); }
.linklike {
  border: 0; background: none; padding: 0 2px;
  color: var(--accent); font-size: 12.5px;
}

/* ------------------------------------------------ 提示词库面板 */
/* 固定块 + 服装库 + 环境库，七十多条：面板自己一条内滚，别让整个 modal 长到
   看不见过滤框。 */
.plib-open { margin-left: 8px; font-weight: 400; }
.modal.plib {
  max-width: min(720px, 96vw);
  height: 84vh;
  max-height: 84vh;
  text-align: left;
  display: grid;
  grid-template-rows: auto auto auto minmax(0, 1fr);
  gap: 10px;
  --sheet-pt: 18px; --sheet-px: 18px; --sheet-pb: 16px;
}
.modal.plib h2, .modal.plib p.sub { margin: 0; }
.plib-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.plib-body { overflow-y: auto; -webkit-overflow-scrolling: touch; min-height: 0; padding-right: 2px; }
.plib-sec + .plib-sec { margin-top: 16px; }
.plib-sec-title {
  font-size: 12px; font-weight: 600; color: var(--text-2);
  position: sticky; top: 0; z-index: 1;
  background: var(--surface); padding: 4px 0 6px;
}
.plib-group { font-size: 12px; color: var(--text-3); margin: 10px 0 4px; }
.plib-row {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 8px 0; border-top: 0.5px solid var(--hair);
}
.plib-row-main { flex: 1; min-width: 0; }
.plib-name { font-size: 13px; font-weight: 600; color: var(--text); }
.plib-desc { font-size: 12px; color: var(--text-2); line-height: 1.55; margin-top: 2px; }
.plib-prev {
  font-size: 12px; color: var(--text-3); line-height: 1.55; margin-top: 2px;
  overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
}
.plib-row-acts { display: flex; gap: 6px; flex: none; }

/* ------------------------------------------------ 按角色润色 */
.wb-enrich { display: flex; align-items: center; gap: 8px; }
.wb-enrich .select { flex: 1; min-width: 0; }
.wb-enrich .btn { flex: none; }
.wb-subjects { margin-top: 8px; }
.wb-subject-thumbs { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0; }
.wb-subject-thumbs img {
  width: 44px; height: 44px; object-fit: cover; display: block;
  border-radius: var(--r-sm);
  border: 0.5px solid var(--hair-strong);
}

.wb-ratio { display: flex; }
.wb-quality { display: flex; }
.wb-stepper {
  display: inline-flex; align-items: center; gap: 10px;
}
.wb-stepper span { min-width: 20px; text-align: center; font-weight: 600; font-variant-numeric: tabular-nums; }
/* ---------------------------------------------- 队列占位卡 / 错误卡 */
.wb-card.wb-job { display: flex; flex-direction: column; }
.wb-job .wb-job-head {
  display: flex; align-items: center; gap: 7px;
  aspect-ratio: 1 / 1;
  justify-content: center;
  font-size: 13px; color: var(--text-2);
  background: var(--surface-sunken);
  position: relative;
  overflow: hidden;
}
/* 极轻的一道光扫过，表示「还在动」而不是卡住了 */
.wb-job .wb-job-head::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(100deg,
    rgba(255, 255, 255, 0) 30%,
    rgba(255, 255, 255, 0.5) 50%,
    rgba(255, 255, 255, 0) 70%);
  transform: translateX(-100%);
  animation: wb-shimmer 1.8s var(--ease) infinite;
}
@keyframes wb-shimmer { to { transform: translateX(100%); } }
.wb-job.wb-job-err .wb-job-head {
  color: var(--red);
  background: color-mix(in srgb, var(--red) 8%, var(--surface-sunken));
}
.wb-job.wb-job-err .wb-job-head::after { display: none; }
.wb-job .wb-job-x {
  position: absolute; top: 6px; right: 8px;
  border: 0; background: transparent; cursor: pointer;
  color: var(--text-3); font-size: 17px; line-height: 1;
  padding: 2px 5px; border-radius: var(--r-sm);
  z-index: 1;
}
.wb-job .wb-job-x:hover { background: rgba(0, 0, 0, 0.06); color: var(--text); }
/* 折行数由 clampText 的 .is-clamp 管；点一下弹全文（title 里也有） */
.wb-job .wb-job-msg {
  font-size: 12px; color: var(--red);
  word-break: break-word; cursor: pointer;
}
.wb-job .wb-job-msg.is-clamped::after { content: " 看全文"; color: var(--accent); }
.wb-card.wb-job:hover { transform: none; }

/* -------------------------------------------------------------- 画廊 */
.wb-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 14px;
}
.wb-card {
  position: relative;
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--surface);
  border: 0.5px solid var(--hair);
  box-shadow: var(--sh-1);
  transition: box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.wb-card:hover { box-shadow: var(--sh-2); transform: translateY(-1px); }
.wb-card .wb-thumb {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  display: block;
  background: var(--surface-sunken);
  cursor: zoom-in;
}
.wb-meta { padding: 8px 10px 10px; display: grid; gap: 3px; }
.wb-meta .wb-prompt {
  font-size: 12.5px; color: var(--text);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  cursor: pointer;
}
/* 固定提示词是拼在结尾的，两行 clamp 正好把它吃掉——点一下看全文。
   截断态的「还有更多」由 clamp 自带的省略号交代，不额外加角标。 */
.wb-meta .wb-prompt.expanded {
  -webkit-line-clamp: unset;
  overflow: visible;
  white-space: pre-wrap;
}
.wb-meta .wb-sub { font-size: 12px; color: var(--text-3); }
.wb-actions {
  position: absolute; left: 0; right: 0; top: 0;
  display: flex; gap: 6px; justify-content: flex-end;
  padding: 8px;
  opacity: 0;
  /* opacity:0 只是看不见，照样吃点击 —— 这一条压在图上半部，没有 hover 的设备
     （触屏，以及 768–880 那一段用鼠标但卡片不 hover 的情形）点图放大会被它挡掉。 */
  pointer-events: none;
  transition: opacity var(--dur) var(--ease);
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.35), rgba(0, 0, 0, 0));
}
.wb-card:hover .wb-actions { opacity: 1; pointer-events: auto; }
.wb-actions .btn { height: 26px; padding: 0 10px; font-size: 12px; }

/* ------------------------------------------------------------ lightbox */
.wb-light {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 260px;
  /* 行必须 minmax(0,1fr)：默认 auto 行会随右栏内容无限长，右栏的内滚永远触发不了
     （10 颗按钮把「下载」推到白板外面，就是这么来的）。框本身 overflow:hidden，
     滚的是右栏。 */
  grid-template-rows: minmax(0, 1fr);
  overflow: hidden;
  gap: 18px;
  background: var(--surface);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-3);
  --sheet-pt: 16px; --sheet-px: 16px; --sheet-pb: 16px;
  padding: var(--sheet-pt) var(--sheet-px) var(--sheet-pb);
  max-width: min(1040px, 92vw);
  max-height: calc(100dvh - 2 * var(--sheet-pad));
}
.wb-light img {
  max-width: 100%;
  max-height: calc(100dvh - 2 * var(--sheet-pad) - 32px);
  min-height: 0;
  align-self: start;
  object-fit: contain;
  border-radius: var(--r-md);
  background: var(--surface-sunken);
}
/* grid 子项默认 min-height:auto 永远缩不下去 —— 不写 min-height:0 右栏就不会内滚 */
.wb-light .wb-light-side {
  display: grid; gap: 10px; align-content: start; overflow-y: auto; min-height: 0;
  --sheet-px: 0px; --sheet-pb: 0px;
}
.wb-light .wb-light-side .card-title { padding-right: 36px; }
/* 灯箱右栏只有 260px 宽，而 .modal-actions 默认 nowrap + 每颗按钮 min-width:92px：
   按钮排到七八颗就整排横着溢出到模态外面去（多出「设为形象参考图」之后一眼可见，
   但七颗的时候其实就已经溢了）。这一栏里让它换行、按钮按内容宽。
   只在 881px 以上生效：窄屏那套 sheet 里按钮是 flex:1 / 44px 高的大按钮，
   `.wb-light .modal-actions .btn` 权重比它高，不圈起来会把手机上那套压掉。 */
@media (min-width: 881px) {
  .wb-light .modal-actions { flex-wrap: wrap; justify-content: flex-start; }
  .wb-light .modal-actions .btn { min-width: 0; }
}
.wb-light .wb-light-prompt {
  font-size: 12.5px; white-space: pre-wrap; color: var(--text);
  background: var(--surface-sunken); border-radius: var(--r-md); padding: 10px;
  max-height: 34vh; overflow-y: auto;
}

@media (max-width: 1040px) {
  .wb-body { grid-template-columns: 320px minmax(0, 1fr); }
}
@media (max-width: 880px) {
  .wb-body { grid-template-columns: minmax(0, 1fr); }
  .wb-form { border-right: 0; border-bottom: 0.5px solid var(--hair); }
}

/* ------------------------------------------------- 测试聊天:连接状态点 */
.rail-subbar-sub { padding: 7px 12px; background: var(--surface); }
.conn {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--text-3);
}
.conn .conn-dot {
  width: 7px; height: 7px; border-radius: 99px;
  background: var(--orange);
  transition: background var(--dur) var(--ease);
}
.conn.online { color: var(--text-2); }
.conn.online .conn-dot { background: var(--green); }

/* ==========================================================================
   手机端（≤880px）
   桌面端一行不动：所有手机规则都关在下面这个 media query 里，
   桌面只多了两个默认隐藏的元素（.tabbar / .m-only）。
   布局思路：不再用 grid 三栏，而是「一屏一视图」——
   .app[data-mview] 决定哪个面板可见，底部 iOS 风格 tab bar 负责切换。
   ========================================================================== */
.tabbar { display: none; }
.m-only { display: none !important; }
/* ≤1024 才出现的控件（右栏浮层的关闭 ×） */
.n-only { display: none !important; }
/* 灯箱右上角的关闭按钮：桌面也画 —— 以前只靠 scrim / Esc，草稿灯箱的动作条里
   连「关闭」都没有，不知道 Esc 的人只能瞎点。 */
.wb-light { position: relative; }
.wb-light-x {
  display: inline-flex;
  position: absolute;
  top: 8px; right: 8px;
  z-index: 2;
  width: 34px; height: 34px;
  align-items: center; justify-content: center;
  border: 0;
  border-radius: 99px;
  background: rgba(0, 0, 0, 0.5);
  color: #fff;
  font-size: 20px; line-height: 1;
}

@media (max-width: 880px) {
  :root {
    /* tab bar 实际占位高度；键盘弹起时归零（见 body.kb-open） */
    --tab-h: calc(50px + env(safe-area-inset-bottom, 0px));
    /* 由 JS 的 visualViewport 监听写入的键盘高度 */
    --kb: 0px;
    --dock-h: 48px;
    --dock-gap: 14px;
    --toolbar-h: 48px;
  }
  body.kb-open { --tab-h: 0px; }

  .m-only { display: inline-flex !important; }
  .d-only { display: none; }

  /* ---- 一屏一视图 ----
     哪个视图在台上还是由 data-view 决定（跟桌面同一套），这里只把主视图从
     grid 格子改成铺满的固定层，并把侧栏变成从左边滑进来的浮层。 */
  .app {
    display: block;
    height: 100%;
    grid-template-columns: none;
  }
  .dashboard, .project, .create, .places, .facelib, .metrics, .rail {
    position: fixed;
    left: 0; right: 0; top: 0;
    bottom: calc(var(--tab-h) + var(--kb));
    width: auto;
    max-width: none;
    border: 0;
    grid-column: auto;
    z-index: 1;
  }
  /* 右栏（测试聊天）在手机端盖在项目详情上面（工具栏下面），靠面板键 / × / 旁白开关 */
  #app[data-view="project"][data-ptab="text"] .rail { display: none; }
  #app[data-view="project"][data-ptab="text"] .rail.rail-open { display: flex; }
  .rail.collapsed { width: auto; overflow: visible; }
  #app[data-view="project"] .pdetail-head { margin: 10px 12px 0; }

  /* ---- 底部 tab bar ---- */
  .tabbar {
    display: flex;
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 200;
    height: var(--tab-h);
    padding-bottom: env(safe-area-inset-bottom, 0px);
    background: var(--surface-veil);
    -webkit-backdrop-filter: saturate(180%) blur(24px);
    backdrop-filter: saturate(180%) blur(24px);
    border-top: 0.5px solid var(--hair);
  }
  body.kb-open .tabbar { display: none; }
  .tabbar button {
    flex: 1;
    border: 0;
    background: transparent;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    padding: 0;
    color: var(--text-3);
    font-size: 12px;
    font-weight: 500;
    transition: color var(--dur) var(--ease);
  }
  .tabbar button .ico { width: 22px; height: 22px; stroke-width: 1.5; }
  .tabbar button[aria-selected="true"] { color: var(--accent); }
  .tabbar button:active { opacity: 0.6; }

  /* ---- 触控尺寸：控件放大到 44px 级别，输入框 16px 免 iOS 自动缩放 ---- */
  body { font-size: 15px; }
  .btn { height: 38px; padding: 0 15px; font-size: 14.5px; }
  /* btn-sm 是工具栏/卡片里最常按的一档（版本、发布、采用、切格、下载……），
     32px 在触屏上偏小。之前抬到 36px，实测还是差一口气——角色工具条那排
     （拆分助手 / 角色体检 / 结构化事件 / 复制角色）是横滑的，滑的时候手指
     容易蹭到隔壁。再抬到 40px，视觉密度还在。 */
  .btn-sm { height: 40px; padding: 0 13px; font-size: 13.5px; }
  .btn-icon { width: 40px; height: 40px; }
  .btn-icon.btn-sm { width: 40px; height: 40px; }
  .input, .textarea, .select { font-size: 16px; padding: 9px 12px; }
  .input-sm { height: 36px; font-size: 16px; }
  .textarea { min-height: 88px; line-height: 1.65; }
  .chip { padding: 7px 14px; font-size: 14px; }
  .segmented button { padding: 8px 16px; font-size: 14px; }
  .field > label { font-size: 13px; }
  .field .help, .card-hint { font-size: 12.5px; }
  /* 纯文字按钮（提示词库 / 撤销润色 / 收起）：只加触达区，不改字号
     （ⓘ 和作者 chip 的同类规则在文件末尾——它们的基础样式排在本段之后） */
  .linklike { padding: 7px 6px; }

  /* ---- 侧栏（= 角色 tab，整屏） ---- */
  .sidebar {
    background: var(--bg);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .sidebar-head { padding: max(14px, env(safe-area-inset-top, 0px)) 16px 10px; }
  .sidebar-title { font-size: 17px; }
  .sidebar-mark { width: 30px; height: 30px; }
  .sidebar-search { padding: 0 14px 10px; }
  .sidebar-search .input { height: 38px; padding-left: 32px; }
  .sidebar-search .ico { left: 24px; margin-top: -3px; }
  .char-list { padding: 0 10px 10px; }
  .char-item { padding: 11px 12px; gap: 12px; border-radius: 10px; }
  .char-item + .char-item { margin-top: 2px; }
  .avatar { width: 34px; height: 34px; font-size: 14px; }
  .char-name { font-size: 15px; }
  .char-meta { font-size: 12px; }
  .sidebar-foot { padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px)); gap: 8px; }

  /* ---- 顶栏 ---- */
  .toolbar {
    height: auto;
    min-height: 52px;
    padding: max(8px, env(safe-area-inset-top, 0px)) 12px 8px;
    gap: 8px;
    flex-wrap: wrap;
  }
  .toolbar-title { font-size: 16px; max-width: 45%; }
  .save-state { font-size: 12px; }

  /* ---- 编辑区 ---- */
  .editor-scroll { padding: 12px 12px calc(var(--dock-h) + var(--dock-gap) + 16px); -webkit-overflow-scrolling: touch; }
  .editor-inner { gap: 12px; }
  .card { border-radius: 12px; }
  .card-head { padding: 14px 14px; }
  .card-title { font-size: 15px; }
  .card-body { padding: 4px 14px 16px; gap: 16px; }
  .grid-2, .grid-3 { grid-template-columns: 1fr; }
  .field[style*="max-width"] { max-width: none !important; }
  .tl-row { grid-template-columns: 56px 1fr; gap: 8px; }
  .tl-body { padding-left: 10px; }

  /* ---- 聊天（首要流程） ---- */
  /* 右栏现在从工具栏下面开始，安全区已经由工具栏让过了 */
  .rail-head { height: auto; min-height: 52px; padding: 8px 12px 8px; }
  .rail-subbar { padding: 8px 12px; gap: 8px; flex-wrap: nowrap; }
  /* 聊天那两条 subbar 已经整块搬进 .rail-head 的 ⋯ 菜单里了（2026-09-07），
     这儿剩下的 .rail-subbar-sub 只有「自动对话」的引导方向框 —— 它是那一屏
     的主要输入，不能再跟着以前那条 more-open 规则一起被藏起来。 */
  .rail-subbar-sub .toolbar-spacer { display: none; }
  .chat-log { padding: 12px 12px 6px; -webkit-overflow-scrolling: touch; }
  .bubble { max-width: 84%; font-size: 15px; padding: 9px 14px; }
  .composer {
    padding: 8px 10px 10px;
    padding-bottom: max(10px, env(safe-area-inset-bottom, 0px));
  }
  body.kb-open .composer { padding-bottom: 10px; }
  .composer .textarea { min-height: 40px; font-size: 16px; border-radius: 20px; padding: 9px 15px; }
  .send-btn { width: 40px; height: 40px; }
  .runs-scroll { padding: 12px 12px calc(20px + env(safe-area-inset-bottom, 0px)); }

  /* ---- 生图工作台 ---- */
  /* 顶栏在 390px 上挤成两行还把「创作 / 素材库」夹在标题和 badge 中间。
     分段控件是这一屏的主开关，单独占一行铺满；两个保留期 badge 的信息在
     空状态文案和每张卡的「N 天后清理」里都有，手机上不重复占位。 */
  .workbench .toolbar .segmented { flex: 1 1 100%; order: 2; }
  /* 四个 tab 之后 flex:1 均分就不成立了：「创作」两个字和「AI 辅助制作」六个字
     分到一样宽，长的那个折成两行，整条 tab 从 40px 长到 63px、字还错位。
     改成各自占自己的宽度、放不下就横滑——tab 数量以后还会变，这个不会再崩。 */
  .workbench .toolbar .segmented {
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .workbench .toolbar .segmented::-webkit-scrollbar { display: none; }
  .workbench .toolbar .segmented button {
    flex: none;
    white-space: nowrap;
    min-height: 40px;
    padding: 4px 16px;
    font-size: 14px;
  }
  #wbRetentionBadge { display: none; }
  /* 项目工具栏在 375px 上有六个按钮 + 标题，必折两行。砍两处：
     「？」在总览那一屏也有一颗（同一套导览），返回键只留箭头。 */
  #btnProjHelp { display: none; }
  .btn-back { padding: 0 8px; }
  .btn-back span, #btnProjBack { font-size: 0; }
  #btnProjBack .ico { font-size: initial; }
  /* 刷新在手机端是纯图标（文字用 .d-only 收了），按钮收成方的别留一截空白 */
  #btnWbRefresh { width: 40px; padding: 0; }
  .wb-lib { padding: 12px 12px calc(var(--dock-h) + var(--dock-gap) + 12px); }
  .wb-lib-head { gap: 10px; margin-bottom: 12px; }

  /* ---- 项目 ---- */
  /* dashboard 卡 / 场景板 / 草稿区的手机端覆盖**不在这里**：它们的桌面规则写在
     本文件更靠后的位置，同优先级下后写的赢，放这儿会被静默盖掉（实测踩过）。
     统一挪到那一段之后的 max-width:880px 块里，见「项目（手机端覆盖）」。 */
  .proj-job, .proj-event { flex-wrap: wrap; font-size: 13px; }
  .proj-job-prompt, .proj-event-msg { flex: 1 1 100%; order: 2; }
  .proj-live-when { order: 3; }

  .wb-body { display: block; overflow-y: auto; -webkit-overflow-scrolling: touch; }
  .wb-form {
    border-right: 0;
    border-bottom: 0.5px solid var(--hair);
    overflow: visible;
    padding: 12px;
  }
  .wb-gallery-wrap { overflow: visible; padding: 12px 12px 32px; display: block; }
  /* 比例/质量/provider 这类分段控件在窄屏横向滚动，不换行也不被压扁
     （provider 现在两家还塞得下，加第三家就不一定了——一起收进来） */
  .segmented.wb-ratio, .segmented.wb-quality, .segmented.wb-provider {
    display: flex;
    max-width: 100%;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .segmented.wb-ratio::-webkit-scrollbar,
  .segmented.wb-quality::-webkit-scrollbar,
  .segmented.wb-provider::-webkit-scrollbar { display: none; }
  /* 比例 / 宫格 / 质量 / provider 四排都是「点一下就改下次生成参数」的开关，
     38px 高在手机上偏小（尤其「无 / 2×2 / 3×3」三个挨得近，容易点错格式）。 */
  .segmented.wb-ratio button,
  .segmented.wb-quality button,
  .segmented.wb-provider button {
    flex: none;
    min-height: 40px;
    padding: 4px 16px;
    font-size: 14px;
  }
  /* 参考图横向滚动条 */
  .wb-refs { flex-wrap: nowrap; overflow-x: auto; padding-bottom: 2px; }
  .wb-ref { width: 72px; height: 72px; flex: none; }
  .wb-ref .wb-ref-x { width: 24px; height: 24px; font-size: 15px; }
  .wb-subject-thumbs { flex-wrap: nowrap; overflow-x: auto; }
  .wb-subject-thumbs img { width: 56px; height: 56px; flex: none; }
  .wb-enrich { flex-wrap: wrap; }
  .wb-enrich .select { flex: 1 1 100%; }
  .wb-enrich .btn { flex: 1 1 auto; }
  .wb-gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  /* 手机上没有 hover，操作按钮得常驻；但两列卡片只有 ~160px 宽，压在图上的
     那条覆盖层塞不下 3–5 个按钮（素材库的「切格」「删除」会被卡片裁掉，
     在屏幕上根本点不到）。改成图下面正常流里的按钮区。

     排布用两列网格，不用 flex 换行：按钮宽度不一（「下载」51px、
     「用作参考图」89px），flex 换行的结果是每张卡断行的位置都不一样——
     「切格」独占一行、下一张卡又是两个挤一行，一墙卡片高矮参差。项目页
     五个按钮更是排成四行、170px 的按钮区顶着 159px 的图，整张卡看着像
     按钮列表配了张缩略图。两列网格换来的是：断行位置固定、同排卡片一样高、
     每个按钮都是 44px 的触达区（挑图/入库是这一屏最要紧的两个动作）。 */
  .wb-actions {
    position: static;
    opacity: 1;
    pointer-events: auto;
    background: none;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 6px;
    padding: 8px 8px 0;
  }
  .wb-actions .btn {
    height: 44px;
    min-width: 0;
    padding: 0 8px;
    font-size: 13px;
  }
  /* 挑图是项目页唯一的人类判断，别让它混在另外四个按钮里长得一模一样：
     独占一行（顺便把五个按钮的 2+2+1 收整齐）。 */
  .wb-stepper { gap: 16px; }
  .wb-stepper span { font-size: 17px; }
  /* 失败卡右上角那个 × 是全站最小的可点区（21×27），手机上基本点不中 */
  .wb-job .wb-job-x { width: 40px; height: 40px; top: 2px; right: 2px; font-size: 20px; }

  /* ---- 字号：桌面上贴着屏幕看没问题的几档，拿在手里就偏小了 ---- */
  /* 这几行是页面上真正要读的字，不是装饰：wf-note 是每屏顶上那句「这一屏是
     干嘛的」，wb-prompt 是一张卡上最常被读的一行（认图全靠它）。给到 14px。 */
  .wb-wf-note { font-size: 14px; }
  .wb-form .help, .wb-lib .help { font-size: 14px; }
  .wb-meta .wb-prompt { font-size: 14px; }
  /* 卡片上的元信息尾巴（provider · 质量 · 比例 · 作者 / 倒计时）密度高，给 13px：
     11.5px 在手机上已经到了要眯眼的程度，但拉到 14px 会把整墙卡片撑高一截。
     只收 .wb-meta 里面这一处——项目卡/灯箱里那些 .wb-sub 没有自己的字号、
     继承的是 15px，写成裸选择器反而会把它们改小。 */
  .wb-meta .wb-sub { font-size: 13px; }

  /* ---- 灯箱：整屏、可点关闭 ---- */
  .wb-light {
    grid-template-columns: minmax(0, 1fr);
    max-width: 100vw;
    width: 100%;
    max-height: 100dvh;
    border-radius: 0;
    padding: 12px;
    overflow-y: auto;
    align-content: start;
  }
  .wb-light img { max-height: 52dvh; width: 100%; }
  .wb-light .wb-light-side { overflow: visible; }
  .wb-light .modal-actions { flex-wrap: wrap; }
  /* 灯箱内容会内滚，关闭按钮必须钉在屏幕上而不是跟着滚走 */
  .wb-light-x {
    display: inline-flex !important;
    position: fixed;
    top: max(10px, env(safe-area-inset-top, 0px));
    right: 10px;
    width: 40px; height: 40px;
  }

  /* ---- 弹窗 / 抽屉 → 全屏 sheet ---- */
  /* scrim 原来是 inset:0，不认 --kb：sheet 贴的是「布局视口」的底边，而键盘
     盖住的是底下那 300 多 px。结果是在「新建项目」里点一下项目名，弹起的
     键盘就把「取消 / 建项目」整排按钮压在下面，人只能先收键盘再点。
     底边跟着键盘抬起来，sheet 就永远停在键盘上方。 */
  .scrim { padding: 0; place-items: end stretch; bottom: var(--kb); }
  .modal {
    max-width: 100%;
    width: 100%;
    /* 键盘占掉高度后 92dvh 会把 sheet 顶出屏幕，用 scrim 的实际高度兜底 */
    max-height: min(92dvh, 100%);
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    --sheet-pt: 22px; --sheet-px: 18px; --sheet-pb: calc(18px + env(safe-area-inset-bottom, 0px));
    animation: sheet-up 0.3s var(--ease);
  }
  /* 键盘弹起时 sheet 已经贴着键盘顶边，底部就不用再让安全区了 */
  body.kb-open .modal { --sheet-pb: 18px; }
  @keyframes sheet-up { from { transform: translateY(40px); opacity: 0; } to { transform: none; opacity: 1; } }
  .modal-actions { flex-wrap: wrap; }
  .modal-actions .btn { flex: 1; min-width: 110px; height: 44px; }
  .transcript { max-height: 50dvh; }

  /* ---- 提示词库：全屏抽屉 ---- */
  /* 表单那一列在 375px 上只有一屏宽，面板挤进去等于两个都没法用：直接占满整屏，
     一次只干「挑一条提示词」这一件事。复制/插入两个按钮抬到 40px。 */
  .modal.plib {
    width: 100%;
    max-width: 100%;
    height: 100dvh;
    max-height: 100dvh;
    border-radius: 0;
    --sheet-pt: max(10px, env(safe-area-inset-top, 0px)); --sheet-px: 12px;
    --sheet-pb: calc(10px + env(safe-area-inset-bottom, 0px));
  }
  /* 标签行上那个入口在手机上是个 12.5px 的链接，点不着——撑到 40px */
  .plib-open { display: inline-flex; align-items: center; min-height: 40px; padding: 0 8px; font-size: 14px; }
  .modal.plib .input { height: 40px; }
  .plib-head .btn { min-height: 40px; }
  .plib-row { padding: 10px 0; flex-wrap: wrap; }
  .plib-row-acts { width: 100%; }
  .plib-row-acts .btn { flex: 1; min-height: 40px; }

  /* ---- 拆分助手：左右分栏 → 分步 ---- */
  /* 桌面靠「原文在左、建议在右」同屏对照来判断采不采用；390px 上没有这个可能，
     上下堆叠的结果是两个都只剩一条缝。改成一次只看一个 pane，顶部分段切换，
     拆完自动跳到「字段建议」——顺序本来就是先贴原文、再逐字段审阅。 */
  .modal.split {
    width: 100%;
    max-width: 100%;
    height: 100dvh;
    max-height: 100dvh;
    border-radius: 0;
    --sheet-pt: max(10px, env(safe-area-inset-top, 0px)); --sheet-px: 12px;
    --sheet-pb: calc(10px + env(safe-area-inset-bottom, 0px));
    gap: 10px;
  }
  .fs-head { gap: 8px; }
  .fs-head h2 { flex: 1; }
  .fs-head .sub { flex: 1 1 100%; order: 3; font-size: 12px; }
  .fs-switch { flex: 1 1 100%; order: 4; }
  .fs-switch button { flex: 1; }
  .modal.split .fs-body { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr); }
  .modal.split .fs-body > .fs-pane { grid-area: 1 / 1; }
  .modal.split[data-fsview="src"] .fs-pane-out,
  .modal.split[data-fsview="out"] .fs-pane-src { display: none; }
  .fs-src { min-height: 0; }
  .fs-field textarea { font-size: 16px; }
  .drawer {
    width: 100%;
    max-width: 100%;
    border-left: 0;
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  .drawer-head { padding: max(10px, env(safe-area-inset-top, 0px)) 12px 10px 16px; height: auto; min-height: 52px; }
  .drawer-body { padding: 12px 12px 24px; }
  .ver-row { padding: 12px; }

  /* ---- 口令页 ---- */
  .gate { padding: 20px; }
  .gate-card { width: 100%; max-width: 356px; padding: 30px 22px 24px; }
  .gate .input { height: 44px; font-size: 16px; }
  .gate .btn { height: 44px; }

  /* ---- toast 贴边 ---- */
  .toasts {
    left: 12px; right: 12px;
    transform: none;
    top: max(12px, env(safe-area-inset-top, 0px));
    justify-items: stretch;
  }
  .toast { max-width: none; }
}

/* ---- 生图工作台：作者筛选条 / 固定提示词 tips / 过期倒计时 ---- */
.wb-author-bar { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
.wb-chip {
  border: 1px solid var(--hair-strong); background: var(--surface-sunken); color: var(--text-2);
  border-radius: 999px; padding: 4px 12px; font-size: 13px; cursor: pointer;
}
.wb-chip:hover { color: var(--text); }
.wb-chip.on { background: var(--accent); border-color: var(--accent); color: #fff; }
.wb-tip {
  display: inline-block; margin-left: 4px; color: var(--text-3);
  cursor: help; font-style: normal;
}
.wb-expiry { color: var(--orange); }

/* ---- 可滚动的图片墙主体（总览、图片工作台、预制朋友圈、场景工作台共用） ---- */
.wb-lib {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: 16px 18px calc(var(--dock-h) + var(--dock-gap) + 16px);
  background: var(--bg);
}
.wb-lib-head {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 14px;
}
/* ---- AI 辅助制作：铺量产物按批次名（author）分组的一面墙 ---- */
.wb-wf-note { font-size: 12.5px; color: var(--text-2); line-height: 1.6; }
.wb-wf-batch { margin-bottom: 24px; }
.wb-wf-batch .card-title { margin-bottom: 10px; }
.wb-wf-batch .card-title span { font-weight: 400; color: var(--text-3); }

/* ==========================================================================
   项目总览 dashboard —— 一个角色一张卡，文案 → 图片 → 视频三段进度
   排版守则（老板原话「不要一堆小字堆叠」）：每段只有一个大数字（26px），
   标签和注释是小字，靠留白和一条细分隔线分组，颜色只有系统灰阶 + 一个强调色。
   ========================================================================== */
/* 按 owner 分区:.pdash 是一列分区,每个分区 = 一行标题(可折叠)+ 一片卡网格 */
.pdash { display: grid; gap: 18px; }
.pgroup { min-width: 0; }
.pgroup-grid {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
}
.pgroup-head {
  display: flex; align-items: center; gap: 8px; width: 100%;
  border: 0; background: none; padding: 0 2px 10px; font: inherit; text-align: left;
  color: var(--text); cursor: pointer;
}
.pgroup-head .author-av { cursor: inherit; }
.pgroup-name { font-size: 14px; font-weight: 600; letter-spacing: -0.01em; }
.pgroup-n { font-size: 12.5px; color: var(--text-3); font-variant-numeric: tabular-nums; }
.pgroup-chev { margin-left: auto; color: var(--text-3); transition: transform var(--dur) var(--ease); }
.pgroup-chev .ico { width: 16px; height: 16px; }
.pgroup.is-folded .pgroup-chev { transform: rotate(-90deg); }
.pgroup.is-folded .pgroup-grid { display: none; }
.pdash-owner-bar { margin-bottom: 16px; }
/* 关联人物：主人物 + 各版本占满一整行，列宽跟外面的网格对齐（subgrid）；
   底色往外铺 6px 的 box-shadow，不占布局，一眼看出这几张是一家。 */
.pfamily {
  grid-column: 1 / -1; display: grid; gap: inherit;
  grid-template-columns: subgrid;
  border-radius: var(--r-lg); background: var(--surface-sunken);
  box-shadow: 0 0 0 6px var(--surface-sunken); margin: 6px 0;
}
@supports not (grid-template-columns: subgrid) {
  .pfamily { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
}
.pcard-rel { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-2); margin-top: 1px; }
.pcard-rel.is-main { color: var(--accent); }
/* 一家人 = 一个项目：顶上一行写明「项目 · 主角色 + N 个副角色」 */
.pfamily-head { grid-column: 1 / -1; display: flex; align-items: baseline; gap: 8px; padding: 2px 4px 0; font-size: 12.5px; }
.pfamily-k { font-size: 11px; font-weight: 600; color: var(--accent); background: var(--accent-soft); padding: 1px 6px; border-radius: 6px; }
.pfamily-name { font-weight: 600; }
.pfamily-sub { color: var(--text-3); }
.pdetail-rel-role { font-size: 11px; font-weight: 600; padding: 1px 6px; border-radius: 6px; background: var(--surface-sunken); color: var(--text-2); }
.pdetail-rel-role.is-main { background: var(--accent-soft); color: var(--accent); }
.pdetail-rel { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin-top: 2px; font-size: 12.5px; }
.pdetail-rel-k { color: var(--text-3); }
.pdetail-rel-link, .pdetail-rel-act {
  border: 0; background: none; padding: 0; font: inherit; cursor: pointer;
}
.pdetail-rel-link { color: var(--accent); }
.pdetail-rel-link:hover { text-decoration: underline; }
.pdetail-rel-act { color: var(--text-3); }
.pdetail-rel-act:hover { color: var(--text); }
.pdetail-rel-list { display: flex; flex-wrap: wrap; gap: 6px; max-height: 320px; overflow: auto; margin-bottom: 8px; }
/* 卡头名字下面那句「他大概是谁」:手写 > 档案提炼;点一下改。空的是一句灰色邀请。 */
.pcard-tag {
  display: block; max-width: 100%; margin-top: 3px;
  font-size: 12px; line-height: 1.35; color: var(--text-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  border-bottom: 1px dashed transparent; cursor: text;
}
.pcard-tag:hover, .pcard-tag:focus-visible { border-bottom-color: var(--hair-strong); outline: none; }
.pcard-tag.is-empty { color: var(--text-3); }
.pcard {
  display: block; width: 100%; text-align: left; font: inherit; cursor: pointer;
  border: 0.5px solid var(--hair); border-radius: var(--r-lg);
  background: var(--surface); box-shadow: var(--sh-1);
  padding: 16px 18px 14px;
  transition: box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.pcard:hover { box-shadow: var(--sh-2); transform: translateY(-1px); }
/* ⋯ 的挂点。.pcard 自己是 <button>，按钮不能套按钮（Safari/Firefox 直接吞掉内层
   点击），所以 ⋯ 是它的**兄弟**，这一层 relative 只是给它一个定位参照。 */
.pcard-wrap { position: relative; }
.pcard-more {
  position: absolute; top: 10px; right: 10px;
  width: 26px; height: 26px; padding: 0; border: 0; cursor: pointer;
  display: grid; place-items: center;
  border-radius: 999px; background: var(--surface-sunken); color: var(--text-3);
  opacity: 0;
  /* opacity:0 只是看不见，照样吃点击 —— 不配 pointer-events:none 的话，卡片
     右上角会留一块点不进项目的死区（同 .wb-actions 上面那条注释）。 */
  pointer-events: none;
  transition: opacity var(--dur) var(--ease), background var(--dur) var(--ease);
}
.pcard-more .ico { width: 16px; height: 16px; }
.pcard-wrap:hover .pcard-more,
.pcard-wrap:focus-within .pcard-more { opacity: 1; pointer-events: auto; }
.pcard-more:hover { background: rgba(0, 0, 0, 0.08); color: var(--text-2); }
.pcard-more:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* 触屏根本没有 hover：不常驻就等于这个入口在手机上不存在。 */
@media (hover: none) {
  .pcard-more { opacity: 1; pointer-events: auto; }
}
.pcard-top { display: flex; align-items: center; gap: 12px; }
.pcard-face {
  width: 48px; height: 48px; flex: none; object-fit: cover;
  border-radius: 12px; border: 0.5px solid var(--hair); background: var(--surface-sunken);
}
.pcard-face.is-empty, .pdetail-face.is-empty {
  display: grid; place-items: center; color: var(--text-3);
}
.pcard-face.is-empty .ico { width: 22px; height: 22px; }
.pcard-id { min-width: 0; flex: 1; }
.pcard-name {
  font-size: 15px; font-weight: 600; letter-spacing: -0.01em; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pcard-char { font-size: 12px; color: var(--text-3); margin-top: 1px; }
/* 生成中：琥珀色小徽标，卡上唯一一处非灰非蓝的颜色 */
.pcard-run {
  flex: none; border-radius: 999px; padding: 2px 9px; font-size: 12px;
  background: rgba(255, 149, 0, 0.14); color: #a15c00;
}
/* 一行两端：左边更新时间，右边 owner 头像圈 */
.pcard-foot {
  margin-top: 10px; padding-top: 9px; border-top: 0.5px solid var(--hair);
  font-size: 12px; color: var(--text-3);
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.pcard-foot-time { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- 删除项目确认框：先摆一屏「这是什么」，再问密码 ---- */
.pdel-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.pdel-head h2 { margin: 0; }
.pdel-prev {
  margin-top: 12px; padding: 12px 14px;
  border: 0.5px solid var(--hair); border-radius: var(--r-lg);
  background: var(--surface-sunken);
}
.pdel-loading { font-size: 12.5px; color: var(--text-3); }
.pdel-err { font-size: 12.5px; color: var(--red); }
.pdel-top { display: flex; align-items: center; gap: 12px; }
.pdel-when { font-size: 12px; color: var(--text-3); margin-top: 2px; }
.pdel-stats {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px; margin-top: 12px;
}
.pdel-stat { min-width: 0; }
.pdel-stat-label { font-size: 12px; color: var(--text-3); }
.pdel-stat-big {
  font-size: 20px; font-weight: 600; letter-spacing: -0.02em;
  color: var(--text); font-variant-numeric: tabular-nums;
}
/* 分布小字允许折行：「成功 7 · 失败 1 · 生成中 1」在窄列里被 ellipsis 咬掉
   后半句的话，丢的正好是要看的那几个数（同 .pstage-sub 踩过的坑）。 */
.pdel-stat-sub { font-size: 12px; color: var(--text-3); line-height: 1.5; white-space: normal; }
.pdel-thumbs { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.pdel-thumb {
  width: 54px; height: 54px; flex: none; object-fit: cover;
  border-radius: var(--r-md); border: 0.5px solid var(--hair); background: var(--surface);
}
.pdel-warn { margin: 14px 0 0; font-size: 12.5px; color: var(--red); line-height: 1.6; }
.pdel .field { margin-top: 10px; }
.pdel-msg { margin-top: 8px; }
/* 密码打错抖一下：红字在输入框下面，而眼睛这会儿正盯着输入框本身，
   光一行小字容易整个漏掉。 */
.pdel-pw.shake { animation: pdel-shake 0.34s var(--ease); }
@keyframes pdel-shake {
  0%, 100% { transform: none; }
  20% { transform: translateX(-6px); }
  45% { transform: translateX(5px); }
  70% { transform: translateX(-3px); }
}
/* 小屏收法。必须写在上面那些基础规则**之后** —— 同优先级下后写的赢，塞进文件
   靠前那个 880 块里会被这里的基础规则静默盖掉（本文件为这个坑注释过两次）。 */
@media (max-width: 880px) {
  /* 四列在 375px 上每列只剩 60 来 px，「背景」两个字都要竖着排。压成两行两列。 */
  .pdel-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 10px; }
  .pdel-thumb { width: 48px; height: 48px; }
  .pdel-warn { font-size: 13px; }
  .pdel-prev { padding: 12px; }
}

/* ---- 三段进度：三个大数字块，块下面一条 3px 的细轨 ---- */
.pstages {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px; margin-top: 14px;
}
/* grid + 1fr 那一行：副标题折成两行时，三列的细轨还是齐的 */
.pstage { min-width: 0; display: grid; grid-template-rows: auto auto 1fr auto; }
.pstage-label {
  font-size: 12px; letter-spacing: 0.06em; color: var(--text-3);
}
.pstage-big {
  font-size: 26px; font-weight: 600; letter-spacing: -0.03em; line-height: 1.15;
  color: var(--text); font-variant-numeric: tabular-nums; margin-top: 1px;
}
.pstage.is-done .pstage-big { color: #1f7a3d; }
.pstage-sub {
  font-size: 12px; color: var(--text-3); line-height: 1.45; margin-top: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pstage-track {
  height: 3px; border-radius: 99px; margin-top: 8px; overflow: hidden;
  background: rgba(118, 118, 128, 0.16);
}
.pstage-fill {
  display: block; height: 100%; border-radius: 99px; background: var(--accent);
  transition: width var(--dur) var(--ease);
}
.pstage.is-done .pstage-fill { background: var(--green); }

/* ---- 详情头：同一套三段进度，只是压扁一档 ---- */
.pdetail-head {
  padding: 14px 16px 12px; margin-bottom: 16px;
  border: 0.5px solid var(--hair); border-radius: var(--r-lg);
  background: var(--surface); box-shadow: var(--sh-1);
}
.pdetail-row { display: flex; align-items: center; gap: 12px; }
/* 封面（face_ref_url）：2026-08 从 44px 小头像放大成一张看得清脸的图 ——
   它是整个项目的形象锚点，不是装饰。参考图库那条横在它下面。 */
.pdetail-face {
  width: 104px; height: 104px; flex: none; object-fit: cover;
  border-radius: 11px; border: 0.5px solid var(--hair); background: var(--surface-sunken);
}
.pdetail-face.is-empty .ico { width: 30px; height: 30px; }
/* ---- 详情头的脸 = 形象参考图的上传/更换入口 ----
   dashboard 卡上那张脸故意不给这个入口：一屏十几张脸，误触一次就换掉了。 */
.pdetail-face-wrap {
  flex: none; display: flex; flex-direction: column; align-items: center; gap: 3px;
}
.pdetail-face-btn {
  position: relative; display: block; padding: 0; border: 0; background: none;
  border-radius: 11px; line-height: 0; cursor: pointer;
}
.pdetail-face-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.pface-mask {
  position: absolute; inset: 0; display: grid; place-items: center;
  border-radius: 11px; background: rgba(0, 0, 0, 0.46); color: #fff;
  opacity: 0; transition: opacity var(--dur) var(--ease);
}
.pface-mask .ico { width: 18px; height: 18px; }
.pdetail-face-btn:hover .pface-mask { opacity: 1; }
.pface-busy {
  position: absolute; inset: 0; display: grid; place-items: center;
  border-radius: 11px; background: rgba(0, 0, 0, 0.5); color: #fff;
}
.pface-busy .ico { width: 18px; height: 18px; }
.pdetail-face-btn.is-busy { cursor: default; }
.pdetail-face-btn.is-busy .pface-mask { opacity: 0; }
/* 88px 是「上传形象参考图」这七个字在 11px 下的整行宽度 —— 掐得再窄一点就会
   断成「上传形象参 / 考图」，那个断法比多占十几像素难看得多。 */
.pface-cta {
  font-size: 12px; line-height: 1.25; text-align: center;
  max-width: 88px; color: var(--text-3);
}
/* 触屏没有 hover。遮罩不能焊成常亮（那就永远看不清这张脸），退化成右下角
   一枚常驻的小角标 —— 一眼能看出这块是能点的。 */
@media (hover: none) {
  .pface-mask {
    opacity: 1; inset: auto -3px -3px auto; width: 17px; height: 17px;
    border-radius: 999px; background: var(--accent); border: 1.5px solid var(--surface);
  }
  .pface-mask .ico { width: 10px; height: 10px; }
}
/* ---- 具名脸位（正脸 / 侧脸）：参考图库上面的两个格子 ----
   比池子的 52px 缩略图大一圈：这两张是要人**看清角度**再定的，
   52px 上根本分不出 60° 和 90°。空态是虚线框 + 「未标」，不是错误态。 */
.faceslots { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 10px; }
.faceslot { display: flex; flex-direction: column; gap: 4px; }
.faceslot-lab { font-size: 12px; color: var(--text-2); letter-spacing: -0.01em; }
.faceslot-box {
  width: 72px; height: 72px; border-radius: 10px; overflow: hidden;
  border: 0.5px solid var(--hair); background: var(--surface-sunken);
}
.faceslot-box.is-empty {
  border: 1px dashed var(--hair-strong);
  display: grid; place-items: center;
}
.faceslot-ph { font-size: 12px; color: var(--text-3); }
.faceslot-thumb { display: block; width: 100%; height: 100%; object-fit: cover; cursor: zoom-in; }
.faceslot-acts { display: flex; gap: 4px; }
.faceslot-acts .btn { padding: 2px 6px; font-size: 12px; }
/* ---- 「怎样算真实 · 对比」：参考图编辑器里的教学块 ----
   默认收起（<details>），展开才占地方 —— 老手每天开十次编辑器，不该每次都被
   一屏例图挡在挑图前面。红/绿两道左边线是这块唯一的颜色语义：哪张是反例
   得在扫一眼的时候就分得开，靠读「AI 味」三个字就太晚了。 */
.facereal { margin-top: 12px; border: 0.5px solid var(--hair); border-radius: 10px; background: var(--surface-sunken); }
.facereal-sum {
  display: flex; align-items: baseline; gap: 8px; cursor: pointer;
  padding: 8px 10px; font-size: 13px; font-weight: 600; color: var(--text-1);
  user-select: none;
}
.facereal-sub { font-size: 12px; font-weight: 400; color: var(--text-3); }
.facereal-body { padding: 0 10px 10px; }
.facereal-pairs { display: flex; flex-wrap: wrap; gap: 14px; }
.facereal-pair { flex: 1 1 300px; min-width: 260px; }
.facereal-pair-t { font-size: 12px; color: var(--text-2); margin-bottom: 4px; }
.facereal-row { display: flex; gap: 8px; }
.facereal-shot { flex: 1 1 0; margin: 0; min-width: 0; }
.facereal-shot img {
  display: block; width: 100%; aspect-ratio: 1; object-fit: cover;
  border-radius: 8px; cursor: zoom-in; background: var(--surface);
}
.facereal-shot figcaption {
  display: flex; flex-direction: column; gap: 1px;
  margin-top: 4px; padding-left: 6px; font-size: 12px; line-height: 1.35;
}
.facereal-shot figcaption b { font-weight: 600; }
.facereal-shot figcaption span { color: var(--text-3); }
.facereal-shot.is-fake figcaption { border-left: 2px solid var(--red); }
.facereal-shot.is-fake figcaption b { color: var(--red); }
.facereal-shot.is-real figcaption { border-left: 2px solid var(--green); }
.facereal-shot.is-real figcaption b { color: var(--green); }
.facereal-gate-t { margin-top: 12px; font-size: 12px; font-weight: 600; color: var(--text-1); }
.facereal-gate { margin: 4px 0 0; padding-left: 18px; font-size: 12px; color: var(--text-2); line-height: 1.6; }
.facereal-gate-note { margin-top: 4px; font-size: 12px; color: var(--text-3); }
.facereal-acts { display: flex; gap: 6px; align-items: center; margin-top: 10px; }
.facereal-acts .btn { text-decoration: none; }

/* 「从图库选」的弹窗网格：格子骨架直接复用发布单挑头像那套 .pub-ava */
.facepick { width: min(560px, 92vw); }
.facepick-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(76px, 1fr));
  gap: 8px; margin-top: 10px;
}
.facepick-grid .pub-ava.is-on { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }

/* ---- 参考图库（ref_images）：封面下面一条横向小图 ----
   后端没带这个字段时 JS 整条不渲染，这里不用管空态。 */
.refpool {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 10px;
}
.refpool-cell { position: relative; width: 52px; height: 52px; flex: none; }
.refpool-thumb {
  display: block; width: 100%; height: 100%; object-fit: cover; cursor: zoom-in;
  border-radius: 9px; border: 0.5px solid var(--hair); background: var(--surface-sunken);
}
.refpool-busy {
  display: grid; place-items: center; border-radius: 9px;
  border: 0.5px solid var(--hair); background: var(--surface-sunken); color: var(--text-3);
}
.refpool-busy .ico { width: 16px; height: 16px; }
/* 两个悬停动作：右上 ✕ 摘掉、底边「设为封面」。触屏没有 hover，
   下面 (hover:none) 里退化成常亮。 */
.refpool-act {
  position: absolute; border: 0; cursor: pointer; padding: 0; color: #fff;
  opacity: 0; transition: opacity var(--dur) var(--ease);
}
.refpool-x {
  top: -5px; right: -5px; width: 17px; height: 17px; border-radius: 999px;
  background: rgba(0, 0, 0, 0.62); font-size: 12px; line-height: 1;
  display: grid; place-items: center;
}
.refpool-cover {
  left: 0; right: 0; bottom: 0; padding: 2px 0; font-size: 12px; line-height: 1.3;
  background: rgba(0, 0, 0, 0.55);
  border-bottom-left-radius: 9px; border-bottom-right-radius: 9px;
}
.refpool-cell:hover .refpool-act,
.refpool-act:focus-visible { opacity: 1; }
.refpool-add {
  width: 52px; height: 52px; flex: none; border-radius: 9px;
  border: 1px dashed var(--hair-strong); background: none; color: var(--text-3);
  font-size: 20px; line-height: 1; display: grid; place-items: center;
  transition: all var(--dur) var(--ease);
}
.refpool-add:hover { color: var(--text-2); border-color: rgba(0, 0, 0, 0.3); }
.refpool-add:disabled { opacity: 0.45; cursor: default; }
@media (hover: none) {
  .refpool-act { opacity: 1; }
}

/* ---- 参考图库编辑格（人像图工作台「参考图库」页签复用这套骨架）------------------------------------------
   紧凑态那条 52px 缩略图是「知道有这么些图」，这一块是「真的在挑图」：
   格子按 220px 起步（老板：看不到就等于没有），动作全铺开不靠 hover。
   收起来以后这一整块不存在，头条还是原来的高度。 */
.refed-toggle { flex: none; align-self: center; margin-left: 4px; }
.faceslots > .refed-toggle { align-self: flex-end; margin-bottom: 2px; }

.refeditor {
  margin-top: 10px; padding: 10px 12px 12px;
  border: 0.5px solid var(--hair-strong); border-radius: 12px;
  background: var(--surface-sunken);
}
.refeditor-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.refeditor-head .wb-sub { flex: 1 1 240px; min-width: 0; }
.refed-title { font-size: 14px; font-weight: 600; letter-spacing: -0.02em; }
.refed-lab { font-size: 12.5px; font-weight: 600; color: var(--text-2); }

/* 概念三格和图库格子共用同一套骨架：两处长得不一样的话，人得学两遍 */
.refed-grid {
  display: grid; gap: 12px; margin-top: 10px;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}
.refed-tile { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.refed-shot {
  position: relative; width: 100%; aspect-ratio: 1 / 1;
  border: 0.5px solid var(--hair-strong); border-radius: 10px; overflow: hidden;
  background: var(--surface-sunken);
}
.refed-shot img { width: 100%; height: 100%; object-fit: cover; display: block; cursor: zoom-in; }
.refed-shot.is-empty { display: grid; place-items: center; border-style: dashed; }
/* 已经担任封面/正脸/侧脸的那张：角标常驻，不是 hover 才出来的装饰 */
.refed-role {
  position: absolute; left: 6px; top: 6px; max-width: calc(100% - 12px);
  padding: 2px 7px; border-radius: 999px; font-size: 11px; line-height: 1.5;
  background: rgba(0, 0, 0, 0.62); color: #fff;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.refed-acts { display: flex; gap: 4px; flex-wrap: wrap; }
.refed-acts .btn { padding: 3px 8px; font-size: 12px; }
.refed-tile-busy {
  aspect-ratio: 1 / 1; border: 1px dashed var(--hair-strong); border-radius: 10px;
  display: grid; place-items: center;
}

/* 例图：一张小图 + 一行要求。它是**说明**不是资产，所以刻意比上面的格子小
   一圈 —— 一样大的话人会以为这也是这个项目的图，点着点着就设错了。 */
.refed-ex { display: flex; gap: 8px; align-items: flex-start; margin-top: 2px; }
.refed-ex-shot {
  position: relative; flex: none; width: 56px; height: 56px; padding: 0;
  border: 1px dashed var(--hair-strong); border-radius: 8px; overflow: hidden;
  background: none; cursor: zoom-in;
}
.refed-ex-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.refed-ex-tag {
  position: absolute; left: 0; bottom: 0; right: 0;
  font-size: 10px; line-height: 1.6; text-align: center;
  background: rgba(0, 0, 0, 0.6); color: #fff;
}
.refed-ex-req { font-size: 11.5px; line-height: 1.45; color: var(--text-3); min-width: 0; }

.refed-pool { margin-top: 14px; padding-top: 12px; border-top: 0.5px solid var(--hair); }
.refed-pool-head { display: flex; align-items: center; gap: 10px; }
.refed-pool-head .btn { margin-left: auto; }

.pdetail-info { min-width: 0; }
.pdetail-info .wb-sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pdetail-name { font-size: 16px; font-weight: 600; letter-spacing: -0.02em; }
.pstages-slim { margin-top: 12px; gap: 12px; }
.pstages-slim .pstage-big { font-size: 22px; }

/* ---- 阶段带：跟 dashboard 卡是同一套三段，只是这三节能点 ----
   可点性只用「悬停有底 + 手型」表达，不加边框不加箭头：三节各加一个 chevron 的话
   这条带子就从「进度」变成「导航条」了，进度才是它的主职。 */
.pstages-nav { margin-left: -8px; margin-right: -8px; }
.pstage-btn {
  border: 0; background: none; font: inherit; text-align: left; cursor: pointer;
  padding: 6px 8px 7px; border-radius: var(--r-md); min-height: 32px;
  transition: background var(--dur) var(--ease);
}
.pstage-btn:hover { background: var(--surface-sunken); }
.pstage-btn:active { background: rgba(0, 0, 0, 0.05); }
.pstage-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.proj-busy { color: var(--orange); }

/* 小屏三段进度的收法。必须写在上面那些基础规则**之后** —— 同优先级下后写的赢，
   放在文件靠前的 responsive 块里会被 .pstage-sub 的 nowrap 静默盖掉（踩过两次）。 */
@media (max-width: 1440px) {
  .pcard { padding: 14px 15px 12px; }
  .pstage-big { font-size: 20px; }
  .pstages { gap: 10px; margin-top: 12px; }
  /* 卡一窄，「专注 11/20 · 招呼 7/12」就被 ellipsis 咬掉后半句 —— 那半句正是
     要读的数。让它折行：细轨靠 .pstage 的 1fr 行仍然三列对齐。 */
  .pstage-sub { white-space: normal; }
}

/* ---- 区块抬头：一条细分隔线 + 标题，没有卡片外框（外框太多会更吵） ---- */
.proj-sec { margin-bottom: 26px; }
.proj-sec-head {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding-bottom: 8px; margin-bottom: 12px; border-bottom: 0.5px solid var(--hair);
}
.proj-sec-title { font-size: 13.5px; font-weight: 600; letter-spacing: -0.01em; }
.proj-sec-sub { font-size: 12px; color: var(--text-3); }
/* ---- 可折叠：跟角色编辑那摞卡同一套（data-toggle 在头上、data-open 在节上）。
        没有高度动画 —— display:none 切换是瞬时的，一屏三十张缩略图做展开动画
        只会掉帧。chevron 那 0.16s 的转向已经够说明发生了什么。
        张数留在组头（.proj-sec-sub）上，所以折着也看得出里面有多少。 ---- */
.proj-sec-head { cursor: pointer; user-select: none; }
.proj-sec-head .chev {
  margin-left: auto; align-self: center;
  color: var(--text-3); transition: transform var(--dur) var(--ease);
}
.proj-sec[data-open="false"] .chev { transform: rotate(-90deg); }
.proj-sec[data-open="false"] .proj-sec-body { display: none; }
/* 收起来之后头底下那 12px 就没有作用对象了，留着等于凭空多一道空隙 */
.proj-sec[data-open="false"] .proj-sec-head { margin-bottom: 0; }

/* ==========================================================================
   场景板 —— 一个场景一张横卡
   ========================================================================== */
.proj-board { display: grid; gap: 10px; }
.scene-card {
  display: grid; gap: 12px;
  padding: 12px 14px;
  border: 0.5px solid var(--hair); border-radius: var(--r-lg);
  background: var(--surface); box-shadow: var(--sh-1);
}
.scene-card.flash { animation: scene-flash 1.2s var(--ease); }
@keyframes scene-flash {
  0% { box-shadow: 0 0 0 2px var(--accent); }
  100% { box-shadow: var(--sh-1); }
}
/* 第一行：这个场景是什么 + 往里加素材 */
.scene-row {
  min-width: 0;
  display: grid; grid-template-columns: 128px minmax(0, 1fr) auto;
  gap: 14px; align-items: center;
}
.scene-thumb {
  width: 128px; height: 84px; object-fit: cover; cursor: pointer;
  border-radius: var(--r-md); border: 0.5px solid var(--hair);
  background: var(--surface-sunken);
}
.scene-thumb.is-empty { cursor: default; }
.scene-mid { min-width: 0; display: grid; gap: 6px; }
.scene-name {
  justify-self: start; max-width: 100%;
  border: 0; background: none; padding: 0; font: inherit; cursor: text;
  font-size: 14px; font-weight: 600; letter-spacing: -0.01em; color: var(--text);
  text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  border-bottom: 1px dashed transparent;
}
.scene-name:hover { border-bottom-color: var(--hair-strong); }
.scene-prow {
  display: grid; grid-template-columns: 3.6em minmax(0, 1fr) 2.6em;
  gap: 8px; align-items: center;
  font-size: 12px; color: var(--text-2);
}
.scene-prow-label { color: var(--text-3); }
.scene-prow-n { text-align: right; font-variant-numeric: tabular-nums; }
.scene-mini { height: 4px; border-radius: 99px; background: rgba(118, 118, 128, 0.16); overflow: hidden; }
.scene-mini-fill {
  display: block; height: 100%; border-radius: 99px; background: var(--accent);
  transition: width var(--dur) var(--ease);
}
.scene-prow.is-done .scene-mini-fill { background: var(--green); }
.scene-prow.is-done .scene-prow-n { color: #1f7a3d; }
.scene-acts { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }

/* ==========================================================================
   片架 —— 第二行，一片一条素材
   以前这儿是一排小 chip：「怎么删、怎么单看、一共几条」三件事全看不出来，
   只能靠一个 ⋯ 菜单猜。片架用的是媒体架的心智：看得见有几条、每条什么样，
   点开是检查器，✕ 就在片上。
   ========================================================================== */
/* min-width:0 是必须的，不是保险：grid 子项默认 min-width:auto，而这一格里装着
   width:max-content 的片架 —— 不写这一行，片架的完整宽度会顶穿场景卡、把整页
   撑出横向滚动条（实测右边那组「+打招呼」按钮被推出视口外）。 */
.scene-shelf-zone { min-width: 0; border-top: 0.5px solid var(--hair); padding-top: 10px; }
.scene-shelf-bar {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; margin-bottom: 8px;
}
.scene-shelf-n { font-size: 12px; color: var(--text-3); font-variant-numeric: tabular-nums; }
/* 打招呼 / 专注各一排：小标题（色点跟片上的类型标签同色）+ 条数；空的那排给一行提示 */
.scene-shelf-group + .scene-shelf-group { margin-top: 10px; }
.scene-shelf-group .scene-shelf-bar { margin-bottom: 6px; }
.scene-shelf-label {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 600; color: var(--text-2);
}
.scene-shelf-dot { width: 7px; height: 7px; border-radius: 999px; background: rgba(0, 122, 255, 0.92); }
.scene-shelf-dot.is-focus { background: rgba(52, 199, 89, 0.92); }
.scene-shelf-empty { font-size: 12px; color: var(--text-3); padding: 2px 0 4px; }
.scene-shelf-scroll {
  min-width: 0;
  overflow-x: auto; overflow-y: hidden;
  -webkit-overflow-scrolling: touch; scrollbar-width: none;
  margin: 0 -2px; padding: 2px;
}
.scene-shelf-scroll::-webkit-scrollbar { display: none; }
/* 渐隐 / ‹ › 由 .hscroll 组件统一画（app.js hscroll()），这里不再各画各的 */
.scene-shelf { display: flex; gap: 8px; width: max-content; }

/* ---- 一片素材：148 宽 = 96 高的画面 + 底下两行小字（标题 / 时间） ---- */
.vtile {
  position: relative; flex: none;
  width: 148px;
  border-radius: var(--r-md); overflow: hidden;
  border: 0.5px solid var(--hair); background: var(--surface);
  cursor: pointer;
  transition: box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}
/* 只在真有 hover 的设备上抬起：触屏点完 :hover 会粘住，片就一直翘着 */
@media (hover: hover) {
  .vtile:hover { box-shadow: var(--sh-2); transform: translateY(-1px); }
  .vtile.is-running:hover { transform: none; }
}
.vtile.is-running { cursor: default; }
.vtile.is-failed { border-color: rgba(255, 59, 48, 0.5); }
/* 画面这一格是所有角标的定位基准（角标都是 absolute），标题行在它外面，不会被压住 */
.vtile-frame { position: relative; height: 96px; overflow: hidden; background: var(--surface-sunken); }
.vtile-cap { display: grid; gap: 1px; min-width: 0; padding: 5px 7px 6px; }
.vtile-title {
  font-size: 12px; line-height: 1.3; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.vtile-title.is-empty { color: var(--text-3); }
.vtile-when { font-size: 12px; line-height: 1.3; color: var(--text-3); font-variant-numeric: tabular-nums; }
/* NEW：小而扎眼，只在标题行开头，不盖画面；整片再描一圈淡红边，横滑扫一眼就能找到 */
.vtile-new {
  display: inline-block; vertical-align: 1px; margin-right: 4px;
  padding: 0 4px; border-radius: 4px;
  font-size: 12px; font-weight: 700; letter-spacing: 0.04em; line-height: 13px;
  color: #fff; background: #ff3b30;
}
.vtile.is-new { border-color: rgba(255, 59, 48, 0.55); }
.vtile-poster { width: 100%; height: 100%; object-fit: cover; display: block; }
.vtile-poster.is-empty { width: 100%; height: 100%; background: var(--bg-deep); }
/* 生成中/失败的片把海报压暗：那张图是「将来会长成这样」，不是「现在有这条」 */
.vtile.is-running .vtile-poster, .vtile.is-failed .vtile-poster { filter: grayscale(0.7) brightness(0.72); }

.vtile-play {
  position: absolute; inset: 0; display: grid; place-items: center;
  color: #fff; pointer-events: none;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.5);
}
.vtile-play .ico {
  width: 30px; height: 30px; stroke-width: 1.4;
  background: rgba(0, 0, 0, 0.38); border-radius: 999px; padding: 5px;
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
}
.vtile-kind {
  position: absolute; top: 6px; left: 6px;
  border-radius: 999px; padding: 1px 7px; font-size: 12px; color: #fff;
  background: rgba(0, 0, 0, 0.42);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
}
.vtile-kind.is-focus { background: #1e7a37; }
.vtile-kind.is-greet { background: var(--accent); }
/* 动作计划的档位（静 / 动 / 招呼）挨着类型标签；判废另给一个红角标 */
.vtile-tier {
  position: absolute; top: 6px; right: 26px;
  border-radius: 999px; padding: 1px 6px; font-size: 12px; color: #fff;
  background: rgba(0, 0, 0, 0.42);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
}
.vtile-tier.is-active { background: rgba(255, 149, 0, 0.92); }
/* 定夺角标（projVerdictBadge）：片架和发布单选片共用一套颜色语言。
   绿=通过 / 红=不通过 / 深红=AI判废 / 橙=待定 / 半透明灰=未审。 */
.v-verdict {
  position: absolute;
  border-radius: var(--r-sm); padding: 1px 6px; font-size: 12px; color: #fff;
  background: rgba(0, 0, 0, 0.45);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
}
.v-verdict.is-ok { background: rgba(48, 209, 88, 0.9); }
.v-verdict.is-no { background: rgba(255, 69, 58, 0.9); }
.v-verdict.is-aino { background: rgba(176, 42, 34, 0.92); }
.v-verdict.is-hold { background: rgba(255, 159, 10, 0.92); }
.vtile-frame .v-verdict { left: 6px; bottom: 6px; }
.vtile-dur {
  position: absolute; right: 6px; bottom: 6px;
  border-radius: var(--r-sm); padding: 1px 6px; font-size: 12px; color: #fff;
  background: rgba(0, 0, 0, 0.55); font-variant-numeric: tabular-nums;
}
/* 生成中 / 失败：一层盖住整片的幕，状态和下一步都写在上面 */
.vtile-veil {
  position: absolute; inset: 0;
  display: grid; place-items: center; align-content: center; gap: 3px;
  color: #fff; text-align: center; padding: 4px;
}
.vtile-veil .ico { width: 18px; height: 18px; }
.vtile-veil-text { font-size: 12px; }
.vtile.is-failed .vtile-veil { color: #ffd7d4; }
.vtile-retry { height: 22px; padding: 0 10px; font-size: 12px; margin-top: 2px; }
.vtile-x {
  position: absolute; top: 5px; right: 5px;
  width: 20px; height: 20px; padding: 0; border: 0; cursor: pointer;
  display: grid; place-items: center; line-height: 1; font-size: 14px;
  border-radius: 999px; background: rgba(0, 0, 0, 0.6); color: #fff;
  opacity: 0; transition: opacity var(--dur) var(--ease);
}
.vtile:hover .vtile-x, .vtile:focus-within .vtile-x { opacity: 1; }
.vtile-x:hover { background: rgba(255, 59, 48, 0.92); }

/* ==========================================================================
   检查器 —— 一条素材摊开看
   ========================================================================== */
.vinspect {
  width: min(760px, 94vw);
  display: flex; flex-direction: column; gap: 12px;
  --sheet-pt: 14px; --sheet-px: 16px; --sheet-pb: 16px;
  padding: var(--sheet-pt) var(--sheet-px) var(--sheet-pb);
  border-radius: var(--r-xl);
  background: var(--surface); box-shadow: var(--sh-3);
  text-align: left;
}
.vinspect-head { display: flex; align-items: center; gap: 10px; flex: none; }
.vinspect-stage, .vinspect-meta { flex: none; }
.vinspect-title { font-size: 14px; font-weight: 600; letter-spacing: -0.01em; }
.vinspect-count { font-size: 12px; color: var(--text-3); font-variant-numeric: tabular-nums; }
.vinspect-stage { position: relative; }
.vinspect-video {
  width: 100%; max-height: 52vh; display: block;
  /* min-height 不是为了好看：播放器矮到 150px 以下时，左右翻片的箭头（垂直居中）
     正好压在原生控件条上，点「下一条」会点到播放/进度。letterbox 一点无所谓。 */
  min-height: 220px; object-fit: contain;
  border-radius: var(--r-md); background: #000;
}
.vinspect-placeholder {
  display: grid; place-items: center; align-content: center; gap: 8px;
  min-height: 220px; padding: 20px;
  border-radius: var(--r-md); background: var(--surface-sunken);
  color: var(--text-2); text-align: center;
}
.vinspect-placeholder .ico { width: 26px; height: 26px; }
.vinspect-ph-text { font-size: 12.5px; line-height: 1.6; max-width: 420px; }
/* 左右翻片：压在播放器上而不是排在底下 —— 底下那一排按钮已经是「对这条做什么」，
   翻片是「看哪条」，两件事混在一行会互相偷点击。 */
.vinspect-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 34px; height: 46px; padding: 0; border: 0; cursor: pointer;
  display: grid; place-items: center; font-size: 22px; line-height: 1;
  border-radius: var(--r-md); background: rgba(0, 0, 0, 0.42); color: #fff;
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
}
.vinspect-nav:hover { background: rgba(0, 0, 0, 0.62); }
.vinspect-nav.prev { left: 8px; }
.vinspect-nav.next { right: 8px; }
.vinspect-meta {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px;
  padding-top: 2px;
}
.vinspect-field { min-width: 0; }
.vinspect-field.is-wide { grid-column: 1 / -1; }
.vinspect-field.is-wide .vinspect-v { white-space: pre-wrap; font-size: 12.5px; line-height: 1.45; }
/* 场景卡名字下面那一行：场景理解做没做、档位分布、缺口 */
.scene-plan-line { font-size: 12px; color: var(--text-3); margin: 1px 0 3px; }
.scene-plan-line.is-missing { color: #a85e00; }
.vinspect-k { font-size: 12px; color: var(--text-3); }
.vinspect-v { font-size: 13px; color: var(--text); margin-top: 1px; word-break: break-word; }
.vinspect-acts { margin-top: 2px; justify-content: flex-end; }
.vinspect-acts .btn { min-width: 84px; }
/* 下载是个 <a>：给它跟按钮一样的行高，别在一排 button 里矮半头 */
a.btn { text-decoration: none; }

/* ==========================================================================
   学习场景草稿区 —— 缩略图网格，hover 出操作条
   ========================================================================== */
.pdraft-grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); }
.pdraft-more { margin-top: 12px; }
.dcard {
  position: relative; overflow: hidden;
  border-radius: var(--r-md); border: 0.5px solid var(--hair);
  background: var(--surface); box-shadow: var(--sh-1);
  transition: box-shadow var(--dur) var(--ease);
}
.dcard:hover { box-shadow: var(--sh-2); }
.dcard.is-scene { outline: 2px solid var(--accent); outline-offset: -2px; }
.dcard-thumb {
  display: block; width: 100%; aspect-ratio: 1 / 1; object-fit: cover; cursor: zoom-in;
  background: var(--surface-sunken);
}
.dcard-mark {
  border-radius: 999px; padding: 2px 8px; font-size: 12px;
  background: var(--accent); color: #fff;
}

/* ---- 时间分隔：日期行 + 批次小节 ----
   「看不出哪张新哪张旧，全糊在一起」的解药。它们是**坐标**不是内容，所以做到
   刚好看得见为止：细字 + 一条发丝线拉到行尾，不加底色不加圆角。
   grid-column: 1/-1 是关键 —— 分隔行必须横跨整行，否则会被当成一张卡塞进格子。 */
.dgroup-head {
  grid-column: 1 / -1;
  display: flex; align-items: center; gap: 9px;
  margin: 10px 0 -2px;
  font-size: 12px; font-weight: 500; color: var(--text-2);
}
.dgroup-head:first-child { margin-top: 0; }
.dgroup-head::after {
  content: ""; flex: 1; height: 0; border-top: 0.5px solid var(--hair);
}
/* 批次小节比日期行再轻一档：同一天里的二级坐标，不该跟天平起平坐 */
.dbatch-head {
  grid-column: 1 / -1;
  display: flex; align-items: baseline; gap: 7px;
  margin: 4px 0 -4px; padding-left: 1px;
  font-size: 12px; color: var(--text-3);
}
.dgroup-n { color: var(--text-3); font-weight: 400; font-variant-numeric: tabular-nums; }
.dcard-acts {
  position: absolute; left: 0; right: 0; bottom: 0;
  /* 换行是必须的：一张 168px 的卡上现在最多四颗（定为场景 / 移到… / 精修 / 删除），
     不换行的话后两颗会被挤成几像素宽的色块，文字全看不见。 */
  display: flex; flex-wrap: wrap; gap: 5px; padding: 7px;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0));
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur) var(--ease);
}
.dcard:hover .dcard-acts, .dcard:focus-within .dcard-acts { opacity: 1; pointer-events: auto; }
.dcard-acts .btn {
  flex: 1 1 auto; min-width: 0; white-space: nowrap;
  height: 26px; padding: 0 6px; font-size: 12px;
}

/* ---- 「生成中」占位骨架：跟画廊那张 wb-job 同一套语言（同一道 shimmer），
        只是缩到草稿卡的尺寸，插在对应那一组的最前面。 ---- */
.dcard-job { cursor: default; }
.dcard-job:hover { box-shadow: var(--sh-1); }
.dcard-job-head {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  aspect-ratio: 1 / 1; position: relative; overflow: hidden;
  font-size: 12.5px; color: var(--text-2);
  background: var(--surface-sunken);
}
.dcard-job-head::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(100deg,
    rgba(255, 255, 255, 0) 30%,
    rgba(255, 255, 255, 0.5) 50%,
    rgba(255, 255, 255, 0) 70%);
  transform: translateX(-100%);
  animation: wb-shimmer 1.8s var(--ease) infinite;
}
.dcard-job-when {
  font-size: 12px; color: var(--text-3); font-variant-numeric: tabular-nums;
}

/* ---- 已定场景：图片页签顶上一条紧凑横排（完整场景板在视频页签，不重复） ---- */
.pinned-strip {
  display: flex; gap: 8px; overflow-x: auto; padding-bottom: 4px;
  -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
.pinned-strip::-webkit-scrollbar { display: none; }

/* ==========================================================================
   .hscroll —— 横向滚动容器的可供性（渐隐 + ‹ ›）
   JS hscroll(scroller) 把 scroller 包进 .hscroll，按 scrollLeft/scrollWidth 打
   can-left / can-right；渐隐用 mask 变量表达，两侧各自独立。触屏只留渐隐。
   片架 / 已定场景条 / 五步流程条 / 角色工具条共用。
   ========================================================================== */
.hscroll { position: relative; min-width: 0; --hs-l: 0px; --hs-r: 0px; }
.hscroll > .hs-scroller {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--hs-l), #000 calc(100% - var(--hs-r)), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 var(--hs-l), #000 calc(100% - var(--hs-r)), transparent 100%);
}
.hscroll.can-left { --hs-l: 36px; }
.hscroll.can-right { --hs-r: 36px; }
.hs-btn {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
  width: 32px; height: 32px; padding: 0; border-radius: 999px;
  border: 0.5px solid var(--hair); background: var(--surface); box-shadow: var(--sh-2);
  color: var(--text-2); font-size: 18px; line-height: 1; cursor: pointer;
  display: none; place-items: center;
}
.hs-btn:hover { color: var(--text); }
.hs-prev { left: 2px; }
.hs-next { right: 2px; }
.hscroll.can-left > .hs-prev, .hscroll.can-right > .hs-next { display: grid; }
@media (hover: none) { .hs-btn { display: none !important; } }
/* 被包起来的几处各自的外层布局 */
.chartools > .hscroll { flex: 1; min-width: 0; }
.ct-row > .hscroll { flex: 0 1 auto; min-width: 0; }
.pinned-item {
  flex: none; display: flex; align-items: center; gap: 8px;
  padding: 5px 12px 5px 5px; border-radius: 999px; cursor: pointer;
  border: 0.5px solid var(--hair); background: var(--surface); font: inherit;
  transition: box-shadow var(--dur) var(--ease);
}
.pinned-item:hover { box-shadow: var(--sh-2); }
.pinned-thumb {
  width: 34px; height: 34px; object-fit: cover; flex: none;
  border-radius: 999px; background: var(--surface-sunken);
}
.pinned-thumb.is-empty { display: block; }
.pinned-name {
  font-size: 12.5px; color: var(--text); max-width: 11em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pinned-n { font-size: 12px; color: var(--text-3); font-variant-numeric: tabular-nums; }

/* ---- 选图：卡上的「选中」按钮 + 勾角标 + 右下角浮条 ---- */
.dcard-pick {
  position: absolute; top: 6px; right: 6px; z-index: 2;
  height: 24px; padding: 0 9px; font-size: 12px;
  opacity: 0; transition: opacity var(--dur) var(--ease);
}
.dcard:hover .dcard-pick, .dcard.picked .dcard-pick { opacity: 1; }
.dcard.picked { outline: 2px solid var(--accent); outline-offset: -2px; }
/* 左上角的角标条：选中勾 · 新 · 已定场景，一条 flex 自己排队。
   以前每枚角标各自 absolute 抢同一个坐标，靠硬编码 left 偏移互相让位 ——
   加第三枚就得再算一遍偏移，所以这次直接换成排队。 */
.dcard-badges {
  position: absolute; top: 6px; left: 6px; z-index: 2;
  display: flex; align-items: center; gap: 4px;
}
.dcard-tick {
  width: 20px; height: 20px; border-radius: 999px; flex: none;
  display: none; place-items: center; font-size: 12px; line-height: 1;
  background: var(--accent); color: #fff;
}
.dcard.picked .dcard-tick { display: grid; }
/* 「新」= 24 小时内。跟「已定场景」同为强调色，靠这圈白边和更小的字号分开 ——
   两者同时出现是常态（刚生成就定成场景），不能靠颜色区分。 */
.dcard-new {
  flex: none;
  border-radius: 999px; padding: 2px 7px;
  font-size: 12px; font-weight: 600; line-height: 1.5;
  background: var(--accent); color: #fff;
  box-shadow: 0 0 0 1.5px var(--surface);
  transition: opacity var(--dur) var(--ease);
}
/* 卡只有 168px 宽，「✓ + 新 + 已定场景」再加上右上角那颗「选中」就撞上了。
   「新」是三者里最不重要的一枚：它只在**扫视**的时候有用，而 hover / 选中
   都意味着人已经盯上这一张了，那一刻它可以让位。手机上「选中」常显，所以
   靠 .picked 这条把三连的情况整个消掉，不是只治桌面。 */
.dcard:hover .dcard-new { opacity: 0; }
.dcard.picked .dcard-new { display: none; }

.pick-bar {
  position: fixed; right: var(--dock-gap); bottom: var(--dock-gap); z-index: 45;
  display: flex; align-items: center; gap: 10px;
  padding: 8px 12px 8px 16px; border-radius: 999px;
  background: var(--surface); box-shadow: var(--sh-3);
  border: 0.5px solid var(--hair);
}
.pick-n { font-size: 13px; font-weight: 500; font-variant-numeric: tabular-nums; }

/* ---- 图片墙的「多选」模式（老板 9/7）----
   进模式之后整张卡只剩一个行为：勾 / 取消勾。所以卡上原来那些按钮（悬停操作条、
   右上角「选中」）在这个模式下**整个消失**，不是变灰 —— 留着灰按钮只会让人一直
   去点。JS 那边也在捕获阶段把整张卡的点击吃掉了，两边同一件事说两遍是故意的：
   CSS 管「看不见」，JS 管「点不着」，少一边都会漏。 */
.pdraft-grid.sel-mode .dcard { cursor: pointer; user-select: none; }
.pdraft-grid.sel-mode .dcard-acts,
.pdraft-grid.sel-mode .dcard-pick { display: none; }
.pdraft-grid.sel-mode .dcard-thumb { pointer-events: none; }
.pdraft-grid.sel-mode .dcard.is-sel {
  outline: 2px solid var(--accent); outline-offset: -2px;
}
/* 没勾中的那些压暗一点：一屏 30 张里勾了 3 张，靠一圈细边框看不出来 */
.pdraft-grid.sel-mode .dcard:not(.is-sel) .dcard-thumb { opacity: 0.62; }
.pdraft-grid.sel-mode .dcard:hover .dcard-thumb { opacity: 1; }
/* 勾选框：常显（模式里它就是主角，不能靠 hover 才出来），排在角标条第一位 */
.dcard-cb {
  width: 22px; height: 22px; flex: none; border-radius: 6px;
  display: grid; place-items: center; font-size: 13px; line-height: 1;
  background: var(--surface); color: transparent;
  box-shadow: 0 0 0 1.5px var(--hair), var(--sh-1);
  transition: background var(--dur) var(--ease);
}
.dcard.is-sel .dcard-cb {
  background: var(--accent); color: #fff;
  box-shadow: 0 0 0 1.5px var(--surface), var(--sh-1);
}

/* 多选动作条。和 .pick-bar 同一个角、同一种壳，但它会被撑得比较宽，所以横向居中
   而不是贴右 —— 贴右的话一长条会盖住整个右下角。 */
.sel-bar {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: var(--dock-gap); z-index: 46;
  max-width: calc(100vw - 2 * var(--dock-gap));
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  justify-content: center;
  padding: 8px 12px 8px 16px; border-radius: 999px;
  background: var(--surface); box-shadow: var(--sh-3);
  border: 0.5px solid var(--hair);
}
.sel-n {
  font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.sel-sep { width: 1px; height: 18px; background: var(--hair); flex: none; }
.sel-bar.is-busy .sel-n { color: var(--text-2); font-weight: 500; }

/* 窄屏：动作条摊平成整条贴底（八颗按钮在 375px 上排不成一个胶囊），
   勾选框放大到 24px —— 它在这个模式里是唯一的命中区，指头点得着才算数。 */
@media (max-width: 880px) {
  /* 贴底不是贴视口的底：底下还站着 .tabbar（项目 / 创作 / 素材库），
     压在它上面的话「加入参考池 / 下载」整排会被盖掉，人只当那两颗不存在。 */
  .sel-bar {
    left: 0; right: 0; transform: none; max-width: none;
    bottom: calc(var(--tab-h) + env(safe-area-inset-bottom, 0px));
    border-radius: 14px 14px 0 0; padding: 10px 12px;
    gap: 6px 8px;
  }
  .sel-n { width: 100%; text-align: center; }
  .sel-sep { display: none; }
  .dcard-cb { width: 26px; height: 26px; font-size: 15px; border-radius: 8px; }
}

/* ==========================================================================
   浮动「对话」按钮 + 对话抽屉
   对话是旁路：主区不出现聊天历史和输入框，想说话点右下角这一颗。
   ========================================================================== */
.proj-dock {
  /* 必须是 fixed：给 .workbench 加一条裸的 position:relative 当定位上下文，会把
     手机端那条 `.sidebar,.editor,.rail,.workbench{position:fixed}` 以同优先级
     后写盖掉（实测整屏塌了）。贴视口反而两端都对。 */
  position: fixed; right: var(--dock-gap); bottom: var(--dock-gap); z-index: 40;
  display: inline-flex; align-items: center; gap: 7px;
  height: var(--dock-h); padding: 0 16px; border-radius: 999px;
  border: 0; cursor: pointer; font: inherit; font-size: 13.5px; font-weight: 500;
  background: var(--accent); color: #fff; box-shadow: var(--sh-3);
  transition: transform var(--dur) var(--ease);
}
.proj-dock:hover { background: var(--accent-hover); }
.proj-dock:active { transform: scale(0.97); }
.proj-dock .ico { width: 17px; height: 17px; }
/* 文案页签右栏在场（没收起）时，浮标和选图条往左让出右栏的宽度：
   以前它压在测试聊天的发送键上。≤1024 右栏是浮层、--rail-w 归零，不让。 */
#app:has(#rail.rail-open) .proj-dock { display: none; }
#app[data-view="project"][data-ptab="text"]:not([data-bound="0"]):has(#rail:not(.collapsed)) .proj-dock,
#app[data-view="project"][data-ptab="text"]:not([data-bound="0"]):has(#rail:not(.collapsed)) .pick-bar {
  right: calc(var(--rail-w) + var(--dock-gap));
}
.proj-dock-dot {
  position: absolute; top: 4px; right: 6px;
  width: 9px; height: 9px; border-radius: 99px;
  background: var(--red); border: 1.5px solid #fff;
}

.proj-drawer { width: 420px; }
/* 抽屉头上的执行者模型开关：.select 默认 100% 宽是给表单用的，
   这里是头部工具，收成内容宽的小件。 */
.proj-model-select {
  width: auto;
  padding: 4px 8px;
  font-size: 12.5px;
  color: var(--text-2);
}
/* 轨道钉成 minmax(0, 1fr)：grid 的 auto 轨道会被孩子的 min-content 顶宽，
   而「现场」和对话流里随时可能出现一个 <pre>（min-content = 整行代码宽），
   轨道一被顶宽，整个抽屉就横向溢出、气泡也不折行了。0 是这条链的第一道闸。 */
.proj-drawer-body { gap: 12px; grid-template-columns: minmax(0, 1fr); }

/* ---- 任务小卡：横向一条，点开才在下面展开那一件 ----
   这一区是**执行者拆出来的结构化视图**，不是入口（人在底下的输入框里说话）。 */
.proj-live-box { display: grid; gap: 6px; }
.proj-task-cards {
  display: flex; gap: 8px; overflow-x: auto; margin-top: 8px; padding-bottom: 4px;
  -webkit-overflow-scrolling: touch; scrollbar-width: thin;
}
.proj-task-chip {
  flex: none; width: 190px; text-align: left; cursor: pointer;
  display: grid; gap: 3px; padding: 8px 10px;
  border-radius: var(--r-md); border: 0.5px solid var(--hair);
  background: var(--surface); font: inherit;
}
.proj-task-chip:hover { box-shadow: var(--sh-1); }
.proj-task-chip.open { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.proj-task-chip-act {
  font-size: 12px; color: var(--text-2); line-height: 1.5;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.proj-task-chip-n { font-size: 12px; color: var(--text-3); font-variant-numeric: tabular-nums; }
.proj-task-detail {
  margin-top: 8px; padding: 10px 12px; display: grid; gap: 6px;
  border-radius: var(--r-md); border: 0.5px solid var(--hair); background: var(--surface-sunken);
}
.proj-task-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.proj-task-name {
  font-size: 13px; font-weight: 600; color: var(--text); flex: 1; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.proj-task-status {
  flex: none; border-radius: 999px; padding: 2px 9px; font-size: 12px;
  background: var(--surface-sunken); color: var(--text-2); border: 0.5px solid var(--hair);
}
.proj-task-status.assigned { background: rgba(0, 122, 255, 0.10); color: #0a63c9; border-color: rgba(0, 122, 255, 0.24); }
.proj-task-status.running { background: rgba(255, 149, 0, 0.12); color: #a15c00; border-color: rgba(255, 149, 0, 0.28); }
.proj-task-status.done { background: rgba(52, 199, 89, 0.12); color: #1f7a3d; border-color: rgba(52, 199, 89, 0.28); }
.proj-task-status.failed { background: rgba(255, 59, 48, 0.10); color: #b3261e; border-color: rgba(255, 59, 48, 0.26); }
.proj-task-goal { margin: 0; line-height: 1.6; }
.proj-task-action { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; font-size: 13px; }
.proj-task-events { margin-top: 0; }
.proj-tasks-empty { margin-bottom: 4px; }

/* ==========================================================================
   对话流 —— 抽屉里的一列（不再是主区的一段）
   老板打字、执行者回音，back and forth。所以是聊天不是日志：boss 右气泡、
   session 左气泡、system 居中灰小字。正序（最新在底部、紧挨着输入框）。
   ========================================================================== */
.proj-chat {
  display: grid; gap: 8px; padding-top: 12px; border-top: 0.5px solid var(--hair);
  /* 同上：轨道不许超过抽屉宽。不写 overflow-x: hidden —— 那会让 overflow-y 变成
     auto，对话流就自己成了滚动容器，抽屉的「一条竖滚动」会裂成两条。 */
  grid-template-columns: minmax(0, 1fr); min-width: 0; max-width: 100%;
}
.proj-chat-empty { line-height: 1.7; }
.proj-bubble-row { display: flex; min-width: 0; max-width: 100%; }
.proj-bubble-row.boss { justify-content: flex-end; }
.proj-bubble-row.session { justify-content: flex-start; }
.proj-bubble-row.system { justify-content: center; }
/* 中间步骤：靠左，但不是气泡 —— 一次任务能写十几条，画成气泡就把 AI 真正
   说的那句话淹了。一条竖线 + 一行灰小字，扫过去知道它在干什么就够了。 */
.proj-bubble-row.step { justify-content: flex-start; }
.proj-bubble {
  max-width: 80%; min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 3px;
  padding: 8px 12px; border-radius: var(--r-md);
  border: 0.5px solid var(--hair); background: var(--surface);
}
/* 自己说的那句用主色底：一屏来回十几条，靠左右分不够快，得靠颜色 */
.proj-bubble.boss { background: var(--accent-soft); border-color: rgba(0, 122, 255, 0.24); }
.proj-bubble.pending { opacity: 0.6; }
.proj-bubble-who {
  font-size: 12px; color: var(--text-3);
  display: flex; align-items: center; gap: 6px; min-width: 0; flex-wrap: wrap;
}
/* 别的项目那枚小标签。对话流是**一条**（under the hood 本来就是同一个 AI
   session），所以每一行得能一眼看出是替谁说的；当前这个项目的行不挂标签 ——
   自己家的话再报一遍名字只是噪音。 */
.proj-bubble-proj {
  flex: none; max-width: 12em;
  font-size: 11px; line-height: 1.5; color: var(--text-2);
  padding: 0 6px; border-radius: 999px;
  background: var(--surface-sunken); border: 0.5px solid var(--hair);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* 步骤行 / 系统行是一整行文字，标签走行内、跟正文之间留一个空 */
.proj-bubble-step .proj-bubble-proj,
.proj-bubble-sys .proj-bubble-proj,
.proj-working-line .proj-bubble-proj { display: inline-block; vertical-align: baseline; margin-right: 5px; }
/* 正文现在是 mdRender 渲染出来的真 DOM（<p> / <br> / 列表…），换行由标签负责，
   所以不再需要 pre-wrap —— 留着的话段落之间会多出一行空白。代码块自己保留空白。 */
.proj-bubble-text {
  font-size: 13.5px; line-height: 1.65; color: var(--text);
  /* min-width: 0 是这条链的关键一环：气泡是 grid，正文格默认 min-width: auto，
     里头一个 <pre>（white-space: pre）的 min-content 就是一整行代码的宽度，
     overflow-x: auto 也拦不住它往上顶 —— 实测能把这一列顶到 17000px。
     anywhere 而不是 break-word：只有 anywhere 会同时把 min-content 压下去。 */
  min-width: 0; overflow-wrap: anywhere; word-break: normal;
}
/* 气泡里的 markdown：一切都贴着 13.5/1.65 走。标题不放大字号（一个 <h1> 就能
   把一屏对话的节奏打乱），只加粗 —— 层级靠留白，不靠字号。 */
.proj-bubble-text > :first-child { margin-top: 0; }
.proj-bubble-text > :last-child { margin-bottom: 0; }
.proj-bubble-text .md-p, .proj-bubble-text .md-h,
.proj-bubble-text .md-list, .proj-bubble-text .md-quote { min-width: 0; }
.proj-bubble-text .md-p { margin: 0 0 7px; }
.proj-bubble-text .md-h { font-weight: 600; margin: 9px 0 3px; }
.proj-bubble-text .md-list { margin: 3px 0 7px; padding-left: 20px; }
.proj-bubble-text .md-list li { margin: 1px 0; }
.proj-bubble-text .md-list .md-list { margin: 2px 0 0; }
.proj-bubble-text .md-quote {
  margin: 4px 0 7px; padding: 1px 0 1px 10px;
  border-left: 2px solid var(--hair-strong); color: var(--text-2);
}
.proj-bubble-text .md-hr { height: 0; margin: 9px 0; border-top: 0.5px solid var(--hair); }
.proj-bubble-text .md-pre {
  margin: 6px 0 7px; padding: 8px 10px;
  border-radius: var(--r-sm); background: var(--surface-sunken);
  border: 0.5px solid var(--hair);
  /* 代码块在自己的框里横滚 —— 但 max-width/min-width 这两条只是把它自己关住，
     真正拦住它顶宽外层的是上面 .proj-bubble-text 的 min-width: 0。 */
  max-width: 100%; min-width: 0; overflow-x: auto; white-space: pre;
}
.proj-bubble-text .md-pre code {
  font-family: var(--font-mono); font-size: 12px; line-height: 1.6;
  background: none; padding: 0; white-space: pre;
}
/* 行内代码 / 链接：气泡、步骤行、任务卡事件列表三处同一套 */
.proj-bubble-text .md-code, .proj-bubble-step .md-code, .proj-event-msg .md-code {
  font-family: var(--font-mono); font-size: 0.88em;
  background: var(--surface-sunken); border-radius: 5px; padding: 1px 5px;
  /* anywhere 而不是 break-all：先在空格处断，实在放不下才拆词 —— break-all 会把
     `python3 -c '…'` 从 "pyth / on3" 中间劈开，白白难看一行 */
  overflow-wrap: anywhere;
}
.proj-bubble.boss .md-code, .proj-bubble.boss .md-pre { background: rgba(255, 255, 255, 0.62); }
.proj-bubble-text .md-link, .proj-bubble-step .md-link, .proj-event-msg .md-link {
  color: var(--accent); overflow-wrap: anywhere;
}
.proj-bubble-text .md-link:hover, .proj-bubble-step .md-link:hover, .proj-event-msg .md-link:hover {
  text-decoration: underline;
}
.proj-bubble-text strong, .proj-bubble-step strong, .proj-event-msg strong { font-weight: 600; color: var(--text); }
/* 拖着图悬在对话上：两块一起亮，人才知道这儿真的能松手 */
.proj-chat.proj-drop-over, .proj-composer.proj-drop-over {
  outline: 2px dashed var(--accent); outline-offset: -3px;
  background: var(--accent-soft); border-radius: var(--r-sm);
}
.proj-bubble-when { font-size: 12px; color: var(--text-3); font-variant-numeric: tabular-nums; }
/* 投递回执那类：居中一行灰小字，不占气泡的分量 */
.proj-bubble-sys { font-size: 12px; color: var(--text-3); text-align: center; line-height: 1.6; overflow-wrap: anywhere; }
.proj-bubble-step {
  font-size: 12px; line-height: 1.5; color: var(--text-3);
  padding: 2px 10px; border-left: 2px solid var(--hair-strong);
  max-width: 86%; min-width: 0; white-space: pre-wrap; overflow-wrap: anywhere; word-break: normal;
}
.proj-step-ico { margin-right: 5px; }
/* 「💭 …」那几条是它在想，不是在动手：斜体再淡一档，跟动作行分开 */
.proj-bubble-step.thinking { font-style: italic; color: var(--text-3); opacity: 0.82; }
/* 一段步骤太长时那颗开关。做成一行小字，别做成按钮的样子 —— 它不是操作，
   是「还有 N 条要不要看」。 */
.proj-step-more {
  border: 0; background: none; cursor: pointer;
  padding: 2px 10px; margin: 0;
  font: inherit; font-size: 12px; line-height: 1.5; color: var(--accent);
  border-left: 2px dashed var(--hair-strong);
}
.proj-step-more:hover { text-decoration: underline; }

/* 单条长步骤掐短后的「展开全文 / 收起」。行内小链接，不是按钮。 */
.proj-step-expand {
  border: 0; background: none; cursor: pointer; padding: 0; margin: 0;
  font: inherit; font-size: 12px; color: var(--accent); white-space: nowrap;
}
.proj-step-expand:hover { text-decoration: underline; }

/* 「本轮结束」分隔线：居中一行，两侧细线。回答的是「这轮停了没」。 */
.proj-round-end {
  display: flex; align-items: center; gap: 10px;
  width: 100%; padding: 4px 0;
}
.proj-round-end-line { flex: 1; height: 0; border-top: 0.5px solid var(--hair-strong); }
.proj-round-end-text { flex: none; font-size: 12px; color: var(--text-3); }

/* 执行者停着：对话流末尾灰点一行。和绿边的「正在动」相互排斥。 */
.proj-bubble-row.idle { justify-content: flex-start; }
.proj-idle {
  display: flex; align-items: center; gap: 8px;
  max-width: 86%; padding: 2px 10px; border-left: 2px solid var(--hair-strong);
}
.proj-idle-dot {
  flex: none; width: 7px; height: 7px; border-radius: 999px;
  background: var(--text-3); opacity: 0.5;
}
.proj-idle-line { font-size: 12px; line-height: 1.6; color: var(--text-3); }

/* 抬头的状态灯：不随对话滚动走的那一份答案 */
.proj-title-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
.proj-status-chip {
  flex: none; display: inline-flex; align-items: center; gap: 5px;
  font-size: 11.5px; line-height: 1; padding: 3px 8px; border-radius: 999px;
  color: var(--text-3); background: var(--surface-sunken); border: 0.5px solid var(--hair);
}
.proj-status-chip::before {
  content: ""; width: 6px; height: 6px; border-radius: 999px; background: var(--text-3);
}
.proj-status-chip[data-state="working"] { color: var(--green); }
.proj-status-chip[data-state="working"]::before {
  background: var(--green);
  animation: proj-chip-pulse 1.4s ease-in-out infinite;
}
.proj-status-chip[data-state="waiting"] { color: var(--orange); }
.proj-status-chip[data-state="waiting"]::before { background: var(--orange); }
@keyframes proj-chip-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

/* 执行者正在动：对话流最下面那一行。三点用的是试聊那边同一颗 .typing 动画。
   内容全部来自后端的 current_action，这里不会有一个字是编的。 */
.proj-bubble-row.working { justify-content: flex-start; }
.proj-working {
  display: flex; align-items: flex-start; gap: 8px;
  max-width: 86%; min-width: 0;
  padding: 2px 10px; border-left: 2px solid var(--green);
}
.proj-working .typing { flex: none; padding-top: 5px; }
.proj-working-line {
  min-width: 0; font-size: 12.5px; line-height: 1.6; color: var(--text-2);
  white-space: pre-wrap; overflow-wrap: anywhere; word-break: normal;
}
.proj-working-who { color: var(--text-3); margin-right: 6px; }
.proj-working-line .proj-bubble-proj { max-width: 10em; }
.proj-working.thinking { border-left-color: var(--hair-strong); }
.proj-working.thinking .proj-working-line { font-style: italic; color: var(--text-3); }

/* 附件条：选中的图以缩略图出现，角上 × 单张摘掉。多了横向滚。 */
.proj-attach { display: flex; align-items: center; gap: 8px; }
.proj-attach-thumbs {
  flex: 1; min-width: 0; display: flex; gap: 6px; overflow-x: auto; padding: 2px 0;
  -webkit-overflow-scrolling: touch; scrollbar-width: thin;
}
.proj-attach-cell { position: relative; flex: none; line-height: 0; }
.proj-attach-thumb {
  width: 52px; height: 52px; object-fit: cover;
  border-radius: var(--r-sm); border: 0.5px solid var(--hair); background: var(--surface-sunken);
}
.proj-attach-x {
  position: absolute; top: -5px; right: -5px;
  width: 18px; height: 18px; padding: 0; border: 0; cursor: pointer;
  display: grid; place-items: center;
  border-radius: 999px; background: rgba(0, 0, 0, 0.62); color: #fff;
  font-size: 13px; line-height: 1;
}
.proj-attach-x:hover { background: rgba(0, 0, 0, 0.82); }
.proj-attach-clear { flex: none; }
/* 正在传的附件：跟缩略图一样大的一个转圈占位 */
.proj-attach-busy {
  width: 52px; height: 52px;
  display: grid; place-items: center;
  border-radius: var(--r-sm);
  border: 0.5px dashed var(--hair-strong);
  background: var(--surface-sunken);
  color: var(--text-3);
}
/* 输入框左边那颗回形针位：图标按钮，跟发送键一样不参与伸缩 */
.proj-attach-add { flex: none; align-self: flex-end; }
/* 气泡里那条「这句话带过的图」 */
.proj-bubble-atts { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 2px; }
.proj-bubble-att {
  width: 44px; height: 44px; object-fit: cover;
  border-radius: var(--r-sm); border: 0.5px solid var(--hair); background: var(--surface-sunken);
}

/* ==========================================================================
   输入框 —— 对话抽屉的最后一格（flex: none，钉在抽屉底部）
   ========================================================================== */
.proj-composer {
  flex: none; display: grid; gap: 8px;
  padding: 10px 18px 12px;
  border-top: 0.5px solid var(--hair); background: var(--surface);
}
.proj-composer-row { display: flex; align-items: flex-end; gap: 8px; }
.proj-say {
  flex: 1; min-width: 0; resize: none; overflow-y: hidden;
  font-family: inherit; font-size: 13.5px; line-height: 1.6;
  padding: 8px 12px; min-height: 38px;
}
.proj-send { flex: none; }
/* 快捷插入：点了把文案填进输入框，人改完再发（.proj-chip 的圆角胶囊复用） */
.proj-chip {
  display:inline-flex; align-items:center; justify-content:center; gap:5px;
  min-height:32px; padding:5px 10px; border:1px solid var(--hair-strong);
  border-radius:8px; background:var(--surface); color:var(--text-2);
  font:inherit; font-size:12px; line-height:1.4; cursor:pointer;
}
.proj-chip:hover { background:var(--surface-sunken); border-color:var(--accent); }
.proj-chip:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
.proj-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.proj-say-chip { font-size: 12px; padding: 3px 10px; }
/* 点一下就发的那颗（同步知识）：描边区分开，别让人以为它跟别的一样只是填个模板 */
.proj-say-chip.is-send { border-color: rgba(0, 122, 255, 0.35); color: var(--accent); }
/* 画面模式标签（【标准通话帧】/【前置贴脸帧】）：插进光标处，不发送。
   跟 preset 那排隔开一点点就够，别再造一层卡。 */
.proj-mode-chips { margin-top: -2px; }
.proj-mode-hint { font-size: 12px; color: var(--text-3); margin-top: -4px; }
/* 引擎下拉：跟同排的 chip 一样高一样圆，但保留 select 的原生箭头 ——
   它是「选一个」，不是「按下去」，长得像按钮反而会被当成开关点。
   选中非默认时描边加重：这排里只有它是**会一直留着**的状态，得看得见。 */
.proj-engine-select {
  font-size: 12px;
  padding: 3px 6px 3px 10px;
  max-width: 190px;
  cursor: pointer;
}
.proj-engine-select:not([data-default="1"]) {
  border-color: rgba(0, 122, 255, 0.35);
  color: var(--accent);
}
/* 生图任务挂着时引擎下拉禁用：引擎是视频的事，这句话用不上 */
.proj-engine-select:disabled { opacity: 0.45; cursor: not-allowed; }
/* 「引擎 X ✕」常驻挂牌：非默认引擎的显眼提醒，点一下摘牌回默认 */
.proj-engine-chip {
  font-size: 12px;
  padding: 3px 10px;
  border-color: rgba(0, 122, 255, 0.35);
  color: var(--accent);
}
/* 「→ 发给 X」：发送键旁的小字，话要投给谁一直看得见 */
.proj-send-target {
  font-size: 12px;
  color: var(--text-3);
  white-space: nowrap;
  align-self: center;
}

/* 任务类型标记按钮的按下态：跟插文本的模式标签长得明显不一样 —— 它是个开关 */
.proj-kind-chip[aria-pressed="true"] {
  background: var(--accent);
  border-color: transparent;
  color: #fff;
  font-weight: 500;
}

/* 类型下面的子选项行（生图→画面模式 / 生视频→引擎）：左边一道细线挂在类型下面，
   看得出是「它的」选项，不是又一排平级按钮 */
.proj-sub-chips {
  margin-top: -2px;
  padding-left: 10px;
  border-left: 2px solid var(--line);
  align-items: center;
}
.proj-sub-label { font-size: 12px; color: var(--text-3); align-self: center; margin-right: 2px; }
/* 第二层子选项（首尾帧任务 → 画面模式）：再缩一格，看得出它挂在上一行下面 */
.proj-sub-chips-2 { margin-left: 12px; }
/* 三张身份参考不齐的红条：它不是 toast（一闪而过），是这句话发出去必然被拒的告示，
   要一直挂在输入框上方直到人去补齐 */
.proj-ref-warn {
  font-size: 12px;
  line-height: 1.5;
  color: var(--red);
  border: 1px solid var(--red);
  border-radius: 6px;
  padding: 6px 8px;
  background: rgba(255, 59, 48, 0.08);
}
/* 画面模式是单选：按下态描边 + 主色字，和「开关」式的类型按钮区分开 */
.proj-mode-chip[aria-pressed="true"], .proj-imgkind-chip[aria-pressed="true"] {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
  font-weight: 500;
}
/* 暂停：聊天框最右那颗独立按钮 */
.proj-pause { flex: none; color: var(--text-2); }
.proj-pause:hover { color: var(--accent); }


/* ---- 项目（手机端覆盖）----
   必须写在上面那些桌面规则**之后**：本文件靠前的那个 max-width:880px 块里写的
   同名规则会被后面的桌面规则以同优先级盖掉（后写的赢）。 */
@media (max-width: 880px) {
  /* dashboard 单列：两列在 375px 上大数字要缩到看不清，那这卡就白做了 */
  .pgroup-grid { grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .pgroup-head { padding-bottom: 6px; }
  .pcard { padding: 14px 14px 12px; border-radius: 12px; }
  .pcard-face { width: 44px; height: 44px; }
  .pstages { gap: 10px; margin-top: 12px; }
  .pstage-big { font-size: 24px; }
  /* 三段的小字在 375px 上放不下一行，让它折而不是被 ellipsis 吃掉 */
  .pstage-sub { white-space: normal; }

  .pdetail-head { padding: 12px 14px; margin-bottom: 14px; border-radius: 12px; }
  .pdetail-face { width: 72px; height: 72px; }
  .pstages-slim .pstage-big { font-size: 20px; }
  /* 阶段带在 375px 上是三个并排的触达区，padding 补足高度别让人点空 */
  .pstage-btn { padding: 8px 6px 10px; }
  .pstages-nav { margin-left: -6px; margin-right: -6px; }

  /* 场景卡：600–880（平板竖屏）仍然横排，只把缩略图收一档；<600 才纵向堆叠
     （缩略图上、进度中、按钮下，片架仍是第二行）。768×1024 之前走的是堆叠，
     缩略图被拉成 1358×290 的横条，只剩鼻子。 */
  .scene-card { gap: 10px; padding: 12px; }
  .scene-row { grid-template-columns: 110px minmax(0, 1fr) auto; gap: 12px; }
  .scene-thumb { width: 110px; height: 74px; }
  .scene-name { font-size: 15px; }
  .scene-prow { font-size: 13px; }
  @media (max-width: 599px) {
    .scene-row { grid-template-columns: minmax(0, 1fr); gap: 10px; }
    .scene-thumb { width: 100%; height: 150px; }
    .scene-acts { justify-content: stretch; }
    .scene-acts .btn { flex: 1; }
  }
  /* 片架瓦片尺寸不变照样横滑 —— 这里的横滑是对的，媒体架本来就该这么翻。
     ✕ 在没有 hover 的设备上常显，否则手机上根本没法删。 */
  .vtile-x { opacity: 1; width: 26px; height: 26px; font-size: 16px; }
  /* 检查器全屏：760px 的浮层在 375px 上只是一个带边距的小窗，白瞎屏幕 */
  .vinspect {
    width: 100vw; max-width: none; min-height: 100dvh; max-height: 100dvh;
    border-radius: 0;
    --sheet-pt: max(12px, env(safe-area-inset-top, 0px)); --sheet-px: 12px;
    --sheet-pb: calc(16px + env(safe-area-inset-bottom, 0px));
  }
  .vinspect-video { max-height: 46dvh; min-height: 200px; }
  .vinspect-meta { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .vinspect-acts { justify-content: stretch; }
  .vinspect-acts .btn { flex: 1; min-width: 0; }
  .vinspect-nav { width: 40px; height: 56px; }

  /* 草稿网格：两列，缩略图本来就是 w_360 的，两列刚好 */
  .pdraft-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  /* 没有 hover：操作条常显，别让人长按猜 */
  .dcard-acts { opacity: 1; pointer-events: auto; position: static; background: none; padding: 7px; }
  .dcard-acts .btn { height: 34px; font-size: 12.5px; }

  /* 任务小卡：375px 上一屏看得到一张半，剩下的横滑 */
  .proj-task-chip { width: 168px; }
  /* 气泡：80% 是上限不是目标，375px 上再宽就没有「谁说的」那点空白了 */
  .proj-bubble { max-width: 80%; }

  /* 浮动「对话」：贴视口，所以要自己让开底部 tab bar（--tab-h 已含安全区） */
  .proj-dock { right: var(--dock-gap); bottom: calc(var(--tab-h) + var(--dock-gap)); height: var(--dock-h); }
  /* 选图浮条同理，并且铺满宽度：三个元素挤在一颗胶囊里在 375px 上点不准 */
  .pick-bar {
    left: 12px; right: 12px; bottom: calc(var(--tab-h) + var(--dock-gap));
    border-radius: var(--r-lg); padding: 10px 12px;
  }
  .pick-bar .btn { flex: 1; min-height: 40px; }
  /* 触屏没有 hover：「选中」常显 */
  .dcard-pick { opacity: 1; height: 30px; }
  .pinned-item { padding: 6px 14px 6px 6px; }
  .pinned-thumb { width: 40px; height: 40px; }
  .proj-attach-thumb { width: 48px; height: 48px; }
  .proj-attach-busy { width: 48px; height: 48px; }
  /* 回形针跟发送键一样抬到 44px：这是手机上唯一的传图入口，点不准就等于没有 */
  .proj-attach-add { min-height: 44px; min-width: 44px; }
  .proj-attach-x { width: 22px; height: 22px; top: -6px; right: -6px; font-size: 14px; }

  /* 抽屉变全屏 bottom-sheet：右侧滑入在 375px 上等于盖掉整屏还带个假边 */
  .proj-drawer {
    width: 100%; max-width: none;
    top: auto; left: 0; right: 0; bottom: 0;
    height: 88dvh; border-left: 0;
    border-top-left-radius: var(--r-xl); border-top-right-radius: var(--r-xl);
    box-shadow: 0 -14px 40px rgba(0, 0, 0, 0.14);
    animation: sheet-in 0.3s var(--ease);
  }
  @keyframes sheet-in { from { transform: translateY(24px); opacity: 0; } to { transform: none; opacity: 1; } }

  /* 输入区：一句话没写完不该占掉半屏，chips 横滑成一行 */
  .proj-composer { padding: 8px 12px calc(8px + env(safe-area-inset-bottom, 0px)); gap: 6px; }
  .proj-chips {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .proj-chips::-webkit-scrollbar { display: none; }
  .proj-say-chip { flex: none; min-height: 32px; font-size: 12.5px; }
  /* 发送要点得准（写库），拇指够得着：44px */
  .proj-send { min-height: 44px; padding: 8px 16px; font-size: 14px; }
  .proj-say { font-size: 16px; min-height: 44px; }   /* < 16px 会让 iOS Safari 聚焦时自动放大整页 */
}
/* 分区抬头下面那句「这一区是干嘛的」（文案来自 /meta 的 output_use） */
.proj-group-note { font-size: 12.5px; color: var(--text-2); line-height: 1.6; margin: 0; }

/* ---- 图片墙的三个分区页签（2026-09-07）----
   不用 .segmented：那套是等宽小胶囊，装不下「标签 + 张数」两截，
   而张数正是老板要一眼看见的东西。这里走「下划线页签」，标签左对齐、
   数字跟在后面，窄屏能横向滚。 */
.zone-tabs {
  display: flex; gap: 4px; margin: 0 0 10px;
  border-bottom: 0.5px solid var(--hair);
  overflow-x: auto; scrollbar-width: none;
}
.zone-tabs::-webkit-scrollbar { display: none; }
.zone-tab {
  flex: none;
  display: flex; align-items: baseline; gap: 6px;
  border: 0; background: none; cursor: pointer;
  padding: 7px 12px 8px; margin-bottom: -0.5px;
  border-bottom: 2px solid transparent;
  color: var(--text-2); font-size: 13.5px; font-weight: 500;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.zone-tab:hover { color: var(--text); }
.zone-tab.on { color: var(--text); border-bottom-color: var(--accent); }
.zone-tab-n {
  font-size: 12px; font-weight: 500; color: var(--text-3);
  font-variant-numeric: tabular-nums;
}
.zone-tab.on .zone-tab-n { color: var(--text-2); }
/* 「+3」＝这一区还有 3 张在生成中。绿色跟占位卡上的「生成中」同一个语义色。 */
.zone-tab-live { font-size: 11.5px; color: var(--green); font-variant-numeric: tabular-nums; }

/* 说明 + 张数 + 「上传到本区」一条 */
.zone-bar {
  display: flex; align-items: center; gap: 10px;
  margin: 0 0 10px; min-height: 26px;
}
.zone-bar .proj-group-note { flex: 0 1 auto; min-width: 0; }

/* 「移到…」浮层：一行一个区，名字 + 那一区是干嘛的 */
.zone-move-pop { text-align: left; }
.zone-move-pop h2 { font-size: 13px; margin: 0 0 8px; padding: 0 6px; }
.zone-move-list { display: grid; gap: 2px; }
.zone-move-row {
  display: grid; gap: 2px; width: 100%; text-align: left;
  border: 0; background: none; font: inherit; cursor: pointer;
  padding: 7px 6px; border-radius: var(--r-md); color: var(--text);
  transition: background var(--dur) var(--ease);
}
.zone-move-row:hover { background: var(--surface-sunken); }
.zone-move-name { font-size: 13px; font-weight: 500; }
.zone-move-note { font-size: 11.5px; color: var(--text-3); line-height: 1.45; }

@media (max-width: 720px) {
  /* 窄屏说明单独占一行：一行里挤不下「说明 + 张数 + 按钮」 */
  .zone-bar { flex-wrap: wrap; }
  .zone-bar .proj-group-note { flex: 1 0 100%; }
}

/* ---- 现场：当前动作 / 在飞任务 / 活儿 —— 抽屉最上面那一段 ---- */
.proj-action { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; font-size: 13px; }
.proj-live-dot { color: var(--green); font-size: 12px; }
.proj-action-text { color: var(--text); }
.proj-live-idle { color: var(--text-3); font-size: 13px; }
.proj-live-when { color: var(--text-3); font-size: 12px; font-variant-numeric: tabular-nums; }
/* 可灵余额 chip：贴在当前动作那一行的右端。低于阈值变红；上游没打通时灰显（stale）。 */
.proj-balance {
  margin-left: auto; flex: none; align-self: center;
  border-radius: 999px; padding: 1px 8px; font-size: 12px; line-height: 1.6;
  border: 0.5px solid var(--hair-strong); background: var(--surface-sunken); color: var(--text-2);
  font-variant-numeric: tabular-nums; cursor: default; white-space: nowrap;
}
.proj-balance.is-low { color: var(--red); border-color: rgba(255, 59, 48, 0.35); background: rgba(255, 59, 48, 0.08); }
.proj-balance.is-stale { opacity: 0.55; }
/* 生图通道健康灯:绿=正常 黄=抖动;红(故障)复用 is-low,灰(闲置/stale)复用 is-stale */
.proj-balance.is-ok { color: var(--green); border-color: rgba(52, 199, 89, 0.35); background: rgba(52, 199, 89, 0.08); }
.proj-balance.is-warn { color: var(--orange); border-color: rgba(255, 149, 0, 0.35); background: rgba(255, 149, 0, 0.08); }
/* 全站顶部栏里的那枚(每个视图的 toolbar 各一枚,同一份数据画到所有枚上)。
   spacer 已经把它推到右边了,不再需要 margin-left:auto;首次拉到数据前 hidden。 */
.toolbar .kling-balance { margin-left: 0; }
.kling-balance[hidden], .kling4-balance[hidden] { display: none; }
.proj-jobs, .proj-events { margin-top: 8px; display: grid; gap: 4px; }
.proj-job, .proj-event {
  display: flex; align-items: baseline; gap: 8px;
  font-size: 12.5px; color: var(--text-2); line-height: 1.6;
  padding: 3px 0; border-top: 0.5px solid var(--hair);
}
.proj-job-status { flex: none; color: var(--orange); }
.proj-job-prompt, .proj-event-msg { flex: 1; min-width: 0; color: var(--text); }
.proj-event-msg { overflow-wrap: anywhere; }
.proj-job-prompt { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 谁干的：人 / AI / 系统 —— 时间线上一眼分得出来 */
.proj-actor {
  flex: none; min-width: 30px; text-align: center;
  border-radius: 999px; padding: 1px 7px; font-size: 12px;
  background: var(--surface-sunken); color: var(--text-2); border: 0.5px solid var(--hair);
}
.proj-actor.boss { background: rgba(0, 122, 255, 0.10); color: #0a63c9; border-color: rgba(0, 122, 255, 0.24); }
.proj-actor.session { background: rgba(52, 199, 89, 0.12); color: #1f7a3d; border-color: rgba(52, 199, 89, 0.28); }


/* ==========================================================================
   知识面 —— 字段说明 ⓘ / 新手导览 / 创作哲学
   同一份内容源（后端 /meta ← character_knowledge/fields.yaml）的三种密度：
   一行 .help → 点开 .help-more → 步进导览气泡。
   ========================================================================== */

/* ------------------------------------------------------------- ⓘ 字段说明 */
.tip-btn {
  /* 触达区 ≥24px，但视觉上只有一个 15px 的小圆圈 */
  width: 32px; height: 32px;
  margin: -10px 0 -10px 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text-3);
  display: inline-grid;
  place-items: center;
  vertical-align: middle;
  border-radius: 99px;
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.tip-btn:hover { color: var(--accent); background: var(--accent-soft); }
.tip-btn[aria-expanded="true"] { color: var(--accent); background: var(--accent-soft); }
.tip-btn .ico { width: 15px; height: 15px; }
/* .help 上的 ⓘ（item-list 卡片：一卡一个，不给每条 item 发） */
.help > .tip-btn { margin-left: 0; }

/* 展开面板：三段式（目的 / 写什么 / 建议）+ 边界两栏。
   正文用 --text 主色、13px、行距 1.75 —— 之前拿 --text-3 当正文，一片浅灰糊在一起，
   读的人抓不到重点。关键词的加粗由 <strong> 承担，别再靠字号分层。 */
.help-more {
  margin-top: 4px;
  padding: 13px 15px 14px;
  border: 0.5px solid var(--hair);
  border-radius: var(--r-md);
  background: var(--surface-sunken);
  font-size: 13px;
  line-height: 1.75;
  color: var(--text);
  display: grid;
  gap: 12px;
}
.help-more strong { font-weight: 600; color: var(--text); }
.hm-head { display: flex; align-items: center; gap: 8px; }
.hm-title { font-size: 12px; font-weight: 600; color: var(--text-2); letter-spacing: 0.02em; }
.hm-sec > b {
  display: block;
  font-size: 12px; font-weight: 600; letter-spacing: 0.06em;
  color: var(--text-3);
  margin-bottom: 3px;
}
.hm-sec p { margin: 0; color: var(--text); }
.hm-sec ul { margin: 0; padding-left: 17px; display: grid; gap: 4px; }
.hm-sec li { color: var(--text); }
.hm-cols {
  display: grid; grid-template-columns: 1fr 1fr; gap: 10px 18px;
  padding-top: 10px; border-top: 0.5px solid var(--hair);
}
.hm-col > b { display: block; font-size: 12px; font-weight: 600; letter-spacing: 0.06em; color: var(--text-3); margin-bottom: 3px; }
.hm-col ul { margin: 0; padding-left: 16px; display: grid; gap: 3px; }
.hm-col li { color: var(--text-2); font-size: 12px; line-height: 1.65; }
@media (max-width: 880px) { .hm-cols { grid-template-columns: 1fr; } }

/* ------------------------------------------------------------- 新手导览 */
/* 挖洞遮罩：一个贴在目标 rect 上的空 div + 9999px 的 box-shadow，
   全屏变暗、目标位置留亮。盾牌层吃掉遮罩期间的一切点击（只读参观）。 */
/* 遮罩和挖洞都只是**视觉**：pointer-events:none，点击照常落到底下的页面上。
   以前它们吃掉一切点击（「只读参观」），结果导览开着时点哪儿都没反应 ——
   包括这次真正想去的地方。现在点别处 = 先退导览再执行那一下（见 tourOutsideClick）。 */
.tour-shield, .tour-hole { pointer-events: none; }
.tour-bubble { pointer-events: auto; }
.tour-shield {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: transparent;
}
/* 没有高亮目标的步（开场/收尾）：盾牌自己变暗，气泡居中 */
.tour-shield.dim { background: rgba(0, 0, 0, 0.45); }
.tour-hole {
  position: fixed;
  z-index: 301;
  border-radius: var(--r-lg);
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.45);
  pointer-events: none;
  transition: top 0.22s var(--ease), left 0.22s var(--ease),
              width 0.22s var(--ease), height 0.22s var(--ease);
}
.tour-bubble {
  position: fixed;
  z-index: 302;
  width: 320px;
  max-width: calc(100vw - 24px);
  padding: 14px 15px 12px;
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--sh-3);
  display: grid;
  gap: 8px;
  max-height: calc(100vh - 24px);
  overflow-y: auto;
  animation: tour-in 0.22s var(--ease);
}
/* 目标比视口还高（挖洞占了 60%+）：气泡贴右下角缩小，别压在被照亮的东西上 */
.tour-bubble.is-edge { max-height: 40vh; }
@keyframes tour-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.tour-eyebrow {
  display: flex; align-items: center; gap: 8px;
  font-size: 12px; color: var(--text-3);
}
.tour-progress { margin-left: auto; font-variant-numeric: tabular-nums; }
.tour-title { font-size: 14px; font-weight: 600; letter-spacing: -0.01em; }
.tour-body { font-size: 13px; line-height: 1.75; color: var(--text); }
.tour-acts { display: flex; align-items: center; gap: 8px; margin-top: 2px; }
.tour-acts .toolbar-spacer { flex: 1; }
.tour-skip { border: 0; background: none; color: var(--text-3); font-size: 12px; padding: 4px 0; }
.tour-skip:hover { color: var(--text-2); }

/* ------------------------------------------------------------- 创作哲学 */
/* 文档那一列的条数是后端 /meta 给的、前端管不了。它以前没有任何高度约束，
   而 .char-list 是这一栏里唯一 flex:1 的块 —— 文档多几条就把角色列表压到零高，
   屏幕上「角色」标题下面空空如也。封顶 + 自己内滚，角色列表永远有地方站。 */
.doc-list {
  padding: 0 8px 4px;
  flex: 0 1 auto;
  max-height: 34vh;
  overflow-y: auto;
  min-height: 0;
}
.doc-list .char-item.active { background: var(--accent); color: #fff; }
.doc-list .avatar { background: rgba(0, 122, 255, 0.12); color: var(--accent); }
.doc-list .char-item.active .avatar { background: rgba(255, 255, 255, 0.22); color: #fff; }

/* wb-mode 同款：显式 grid-column，别让 display:none 的兄弟节点重排自动定位 */
.essence { display: none; }
#app[data-view="essence"] .doc-view { display: none; }
#app[data-view="essence"] .essence:not(.doc-view) {
  display: flex;
  grid-column: 2;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  background: var(--bg);
}
/* 文档阅读：和《创作哲学》同一套版式，只是只读。两者互斥，同时只会开一个。 */
#app[data-view="doc"] .doc-view {
  display: flex;
  grid-column: 2;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  background: var(--bg);
}

.essence-body { flex: 1; min-height: 0; overflow-y: auto; padding: 22px 22px 80px; }
.essence-sheet { max-width: 820px; margin: 0 auto; padding: 40px 40px 48px; }
.essence-input {
  width: 100%;
  min-height: 60vh;
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1.75;
}

/* 阅读态排版：庄重感来自字号、行高、留白和引用块，不换字体 */
.essay { max-width: 640px; margin: 0 auto; color: var(--text); }
.essay h1 { font-size: 22px; font-weight: 600; letter-spacing: -0.02em; margin: 0 0 18px; }
.essay h2 {
  font-size: 17px; font-weight: 600; letter-spacing: -0.01em;
  margin: 34px 0 12px;
}
.essay h3 { font-size: 14.5px; font-weight: 600; margin: 24px 0 8px; color: var(--text-2); }
.essay p { font-size: 15px; line-height: 1.9; margin: 0 0 1em; }
.essay ul, .essay ol { margin: 0 0 1em; padding-left: 22px; }
.essay li { font-size: 15px; line-height: 1.9; margin-bottom: 4px; }
.essay blockquote {
  margin: 0 0 1em;
  padding: 2px 0 2px 16px;
  border-left: 3px solid var(--accent);
  color: var(--text-2);
}
.essay blockquote p { margin: 0 0 0.4em; }
.essay hr { border: 0; border-top: 0.5px solid var(--hair); margin: 30px 0; }
.essay a { color: var(--accent); }
.essay strong { font-weight: 600; color: var(--text); }
.essay code {
  font-family: var(--font-mono);
  font-size: 0.88em;
  background: var(--surface-sunken);
  border-radius: 5px;
  padding: 1px 5px;
}
.essay .empty-inline { font-size: 13px; }
.essay pre {
  margin: 0 0 1em;
  padding: 12px 14px;
  background: var(--surface-sunken);
  border-radius: var(--r-md);
  overflow-x: auto;
}
.essay pre code { background: none; padding: 0; font-size: 12.5px; line-height: 1.7; }
/* 宽表格自己横向滚，别把整页撑出横向滚动条 */
.md-table { overflow-x: auto; margin: 0 0 1em; max-width: 100%; }
.md-fig { margin: 0 0 1.2em; }
.md-img { display: block; max-width: 100%; height: auto; border-radius: var(--r-md); }
.md-fig figcaption { font-size: 12.5px; color: var(--text-3); margin-top: 6px; }
/* 阅读页在 ≤880 是 flex 列：.essay 变成 flex item，min-width:auto 会按表头的
   nowrap 撑到 386px，再被 .card 的 overflow:hidden 裁掉每行末字。约束写在基础规则。 */
.essay { min-width: 0; max-width: 100%; }
.essay table { border-collapse: collapse; width: 100%; font-size: 14px; }
.essay th, .essay td {
  text-align: left;
  padding: 7px 10px;
  border-bottom: 0.5px solid var(--hair);
  line-height: 1.7;
  vertical-align: top;
}
.essay th { font-weight: 600; color: var(--text-2); white-space: nowrap; }


@media (max-width: 880px) {
  /* 全屏一视图，和 wb 一样不靠 grid 定位；essence 在最上层，返回键回角色 tab */
  .essence {
    position: fixed;
    left: 0; right: 0; top: 0;
    bottom: calc(var(--tab-h) + var(--kb));
    z-index: 2;
  }
  #app[data-view="essence"] .essence:not(.doc-view),
  #app[data-view="doc"] .doc-view { grid-column: auto; }
  .essence-body { padding: 14px 12px 40px; }
  .essence-sheet { padding: 22px 18px 28px; }
  .essay h1 { font-size: 20px; }
  .essay p, .essay li { font-size: 16px; line-height: 1.85; }
  /* 编辑态：输入框吃满剩下的高度。定死 50vh 的话，写长文时是「小窗口 +
     下面一大片空白」，每打几行就得滚一次。 */
  .essence-input { font-size: 16px; min-height: 240px; }
  #app[data-view="essence"] .essence-body { display: flex; flex-direction: column; }
  .essence-sheet { flex: 1 1 auto; display: flex; flex-direction: column; width: 100%; }
  .essence-sheet > .essay { flex: none; width: 100%; }
  .essay th { white-space: normal; }
  .essence-input:not(.hidden) { flex: 1 1 auto; }
  .app.essence-editing .essence-sheet { padding: 14px 12px 16px; }

  /* 导览气泡在手机上不跟随目标，固定成底部 sheet（挖洞照常照亮目标） */
  .tour-bubble {
    left: 10px !important;
    right: 10px;
    top: auto !important;
    bottom: calc(var(--tab-h) + 10px);
    width: auto;
    max-width: none;
    /* 带正反例的步能长到半屏以上；封顶后内滚（overflow 在基础规则里，这儿只改数字） */
    max-height: 62dvh;
  }
  .tour-skip { padding: 9px 4px; }

  /* 触达区补丁（这几条的基础样式写在上面那个 media query 之后，
     所以必须放在这里才压得住）：视觉尺寸一点没变，只是手指够得着了。 */
  .tip-btn { width: 34px; height: 34px; margin: -9px 0 -9px 2px; }
  .tip-btn .ico { width: 17px; height: 17px; }
  .help > .tip-btn { margin-left: 0; }
  .wb-chip { padding: 8px 15px; font-size: 14px; }
}

/* ==========================================================================
   项目 owner —— 首字头像圈 + 指派浮层
   署名不是权限，所以视觉上要轻：系统灰阶的小圈，一个强调色只留给打勾。
   圈前一个小字「作者」点明这是什么；空态是虚线框的「作者：未设置」，不是红色警告。
   ========================================================================== */
.authors {
  display: inline-flex; align-items: center; flex: none;
  cursor: pointer; border-radius: 999px;
  /* 22px 的圈太小：触达区补到 32px，视觉不变 */
  padding: 5px; margin: -5px;
}
.authors:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.author-av {
  width: 22px; height: 22px; flex: none;
  /* 负 margin 让圈叠着走，人多也不占更多横向；描边用卡面色把叠压处切开 */
  margin-left: -5px; border-radius: 999px; border: 1.5px solid var(--surface);
  background: var(--bg-deep); color: var(--text-2);
  font-size: 12px; font-weight: 590; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  transition: transform var(--dur) var(--ease);
}
.authors-label { font-size: 12px; color: var(--text-3); white-space: nowrap; margin-right: 6px; }
.authors-label + .author-av { margin-left: 0; }
.authors-names {
  margin-left: 6px; font-size: 12.5px; color: var(--text-2);
  max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.authors.is-empty { border: 1px dashed var(--hair-strong); padding: 2px 8px; margin: 0; }
.authors.is-empty .authors-label { margin-right: 0; }
.authors.is-empty:hover { border-color: var(--text-3); }
.author-av:first-child { margin-left: 0; }
.authors:hover .author-av { transform: translateY(-1px); }
.author-av.is-more { background: var(--surface-sunken); color: var(--text-3); font-size: 12px; }
.author-av.is-empty {
  border: 1px dashed var(--hair-strong); background: none;
  color: var(--text-3); font-size: 13px;
}
/* 详情头那一行是 face + info，靠 auto 把头像圈推到右端，不动既有两块的布局 */
.pdetail-row > .authors { margin-left: auto; }

/* ---- 浮层容器：位置由 JS 量出来写在 style 上，这里只管长相 ---- */
.scrim-bare {
  background: none; -webkit-backdrop-filter: none; backdrop-filter: none;
  display: block; padding: 0; animation: none;
}
.popover {
  position: fixed; width: 232px;
  background: var(--surface);
  border: 0.5px solid var(--hair); border-radius: var(--r-lg);
  box-shadow: var(--sh-3); padding: 10px;
  animation: sheet-in 0.16s var(--ease);
}

/* ---- 指派内容。窄屏走 openOverlay 的居中模态，所以要压住 .modal 的居中对齐 ---- */
.authors-pop { text-align: left; }
.authors-pop-title {
  font-size: 12px; letter-spacing: 0.06em; color: var(--text-3); padding: 0 6px 6px;
}
.authors-pop-list { display: grid; gap: 1px; max-height: 246px; overflow-y: auto; }
.authors-pop-hint { font-size: 12px; color: var(--text-3); padding: 6px; }
.authors-pop-title + .authors-pop-hint { padding: 0 6px 8px; line-height: 1.45; }
.authors-row {
  display: flex; align-items: center; gap: 8px; width: 100%;
  border: 0; background: none; font: inherit; text-align: left; cursor: pointer;
  padding: 6px; border-radius: var(--r-md); color: var(--text);
  transition: background var(--dur) var(--ease);
}
.authors-row:hover { background: var(--surface-sunken); }
.authors-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.authors-row-name {
  min-width: 0; font-size: 13px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 勾本身常在，没选中时是透明的 —— 这样选中/未选中两态尺寸完全一致，
   一排勾下来不会因为多了个图标而左右跳。 */
.authors-tick {
  width: 16px; height: 16px; flex: none; border-radius: 5px;
  border: 1px solid var(--hair-strong); background: var(--surface);
  display: grid; place-items: center; color: transparent;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.authors-tick .ico { width: 11px; height: 11px; stroke-width: 2.6; }
.authors-row[aria-pressed="true"] .authors-tick {
  background: var(--accent); border-color: var(--accent); color: #fff;
}
.authors-pop-acts {
  display: flex; justify-content: flex-end;
  margin-top: 8px; padding-top: 8px; border-top: 0.5px solid var(--hair);
}

/* ==========================================================================
   版本哨兵横幅
   单页应用永远不会自己重拉代码，标签页开一天就还是早上那一份界面。这条横幅是
   唯一的解药，所以**不给关闭按钮** —— 关掉就等于自愿留在旧世界。代价用体量抵
   消：只有 32px 高，而且整页跟着往下让同样的 32px（--vbanner），它是「多出来
   的一条」，不是「盖在顶栏上的一块」，顶栏每颗按钮照样点得到。
   z-index 240：高于侧栏(60) / 登录页(100)，低于抽屉(250/251) 和弹窗(300) ——
   正在填的弹窗永远压在它上面。
   ========================================================================== */
.ver-banner {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 32px;
  z-index: 240;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  border: 0;
  padding: 0 12px;
  background: var(--accent);
  color: #fff;
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: 0.01em;
  box-shadow: 0 1px 12px rgba(0, 0, 0, 0.16);
  transform: translateY(-100%);
  transition: transform 0.3s var(--ease);
}
.ver-banner.in { transform: none; }
.ver-banner:hover { filter: brightness(1.06); }

/* 让位。--vbanner 平时是 0px，所以这一整节在没横幅的时候等于不存在。
   整套东西**故意**全留在这一节里（用后写覆盖，而不是回去改上面那几处），
   将来要拆掉哨兵，删这一节就干净了。

   桌面：.app 是 100vh 的 grid，把它整体压矮 + 下移就行。 */
.app {
  height: calc(100vh - var(--vbanner, 0px));
  margin-top: var(--vbanner, 0px);
}
.gate { top: var(--vbanner, 0px); }
.toasts { top: calc(14px + var(--vbanner, 0px)); }

/* ≤1024：侧栏和右栏变成 position:fixed 的滑出浮层，不再跟着 .app 走，
   得自己让开这 32px，否则横幅会盖住它们的头部。 */
@media (max-width: 1024px) {
  .sidebar { top: var(--vbanner, 0px); }
  /* 必须写在 .n-only{display:none} 之后（同优先级、都 !important，后写的赢） */
  .n-only { display: inline-flex !important; }
}

/* ≤880：一屏一视图，主视图全是贴视口的 fixed 层，.app 只剩个空壳 ——
   压 .app 没有任何意义（还会把上面那条 height:100% 覆盖掉），改成逐层让位。 */
@media (max-width: 880px) {
  .app { height: 100%; margin-top: 0; }
  .dashboard, .project, .create, .places, .facelib, .metrics { top: var(--vbanner, 0px); }
  .toasts { top: calc(max(12px, env(safe-area-inset-top, 0px)) + var(--vbanner, 0px)); }
}

/* ==========================================================================
   笔记本纵向减负（881–1440）
   老板的截图：文案页签上，正文出现之前叠了四层 chrome —— 工具栏、项目头卡、
   角色工具条、五步流程条。1440 宽的笔记本上表单卡只剩一条缝。

   这一节只做「压扁」，不删任何入口：每一件东西都还在，点得到的还是点得到，
   只是不再各占一层。收掉的文字都换成了 title（hover 看得见）。

   下限 881px 是硬的 —— 手机端（≤880）有一整套自己的布局（一屏一视图、底部
   tab bar、工具条折行），这一节一个字都不碰它。上限 1440 之上维持原样。
   写在文件最末尾是**故意**的：要压住前面 1440 / 1280 两块里的同名规则。
   ========================================================================== */
@media (min-width: 881px) and (max-width: 1440px) {

  /* ---- ② 角色工具条 + 五步流程条并成一行 ---- */
  .ct-row {
    display: flex; align-items: center;
    background: var(--surface);
    border-bottom: 0.5px solid var(--hair);
  }
  .ct-row .chartools {
    flex: 1 1 auto; min-width: 0;
    padding: 5px 6px 5px 14px; gap: 10px;
    background: none; border-bottom: 0;
  }
  .ct-row .pipeline {
    flex: 0 1 auto; min-width: 0;
    align-items: center; gap: 4px;
    padding: 5px 12px 5px 0;
    background: none; border-bottom: 0;
  }
  /* 「角色工具」这四个字是标签不是信息，作用对象（角色名）留着 —— 多人共用一个
     工作台，「这一下点在谁身上」任何时候都不能靠猜。 */
  .ct-row .ct-label { display: none; }
  .ct-row .ct-scope { max-width: 26%; }
  /* 工具按钮收成图标；文字进 title。体检那颗的红点徽标**不收**，它是要一眼
     看见的数，收掉就等于把这颗按钮的全部价值收掉了。 */
  .ct-row .ct-t { display: none; }
  .ct-row .ct-btns { gap: 5px; }
  .ct-row .ct-btns > .btn { padding: 0 8px; }
  .ct-row .ct-badge { margin-left: 3px; }

  /* 五步流程条 → 紧凑 stepper：当前步带文字，其余四步只剩数字圆点。
     五步里只有一步是「现在在做的」，其余四步的标题在这条带子上不产生任何
     决策；点开还是完整的步详情弹窗，一个字都没少。 */
  .ct-row .pl-step { flex: 0 0 auto; min-width: 0; padding: 3px 8px; gap: 0; }
  .ct-row .pl-sub { display: none; }
  .ct-row .pl-step:not([aria-current="true"]) {
    width: 32px; height: 32px; padding: 0;
    display: grid; place-items: center; border-radius: 999px;
  }
  .ct-row .pl-step:not([aria-current="true"]) .pl-title { gap: 0; justify-content: center; }
  .ct-row .pl-step:not([aria-current="true"]) .pl-title > span:not(.pl-num) { display: none; }

  /* ---- ③ 卡片内边距 / 卡间距收一档（-25% 上下） ---- */
  .editor-scroll { padding: 14px 16px calc(var(--dock-h) + var(--dock-gap) + 16px); }
  .editor-inner { gap: 12px; }
  .card-head { padding: 10px 14px; }
  .card-body { padding: 3px 14px 13px; gap: 11px; }
}

/* ---------------------------------------------------------- 发布预检单
   工具栏徽章三态 + 一张弹层（必填 / 建议 / 视频素材 / 五步进度）。
   全部复用现有 modal / field / badge 语汇，只补这里独有的排版。 */
.pub-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 32px;
  padding: 0 9px;
  border: 0;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  background: rgba(52, 199, 89, 0.14);
  color: #1e7a37;
}
.pub-badge.is-stale { background: rgba(255, 204, 0, 0.2); color: #8a6d00; }
.pub-badge .ico { width: 12px; height: 12px; }

.modal.pub-sheet { max-width: 760px; --sheet-pt: 20px; --sheet-px: 22px; --sheet-pb: 18px; }
.pub-sec { margin-top: 16px; padding-top: 14px; border-top: 0.5px solid var(--hair); display: grid; gap: 10px; }
.pub-sec-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.pub-sec-head h3 { margin: 0; font-size: 13px; font-weight: 600; }
.pub-sec-sub { font-size: 12px; color: var(--text-3); line-height: 1.5; }
.pub-hint-err { font-size: 12px; color: #c22a20; }

/* 头像九宫格：中心正方形裁切预览（object-fit cover 就是 App 端的裁法） */
.pub-ava-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(76px, 1fr)); gap: 8px; }
.pub-ava {
  position: relative;
  padding: 0;
  border: 2px solid transparent;
  border-radius: var(--r-md);
  overflow: hidden;
  cursor: pointer;
  background: var(--surface-sunken);
  aspect-ratio: 1 / 1;
}
.pub-ava img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pub-ava[aria-pressed="true"] { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.pub-ava-tag {
  position: absolute;
  left: 4px;
  bottom: 4px;
  font-size: 12px;
  padding: 1px 5px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  pointer-events: none;
}
/* 形象参考格子的放大镜：格子本体是 button（选/取消），这颗是里面的 span。
   常亮不玩 hover —— 触屏没有 hover，而「能看大图」这件事得一眼可见。 */
.pub-zoom {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 22px;
  height: 22px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  display: grid;
  place-items: center;
  cursor: zoom-in;
}
.pub-zoom .ico { width: 13px; height: 13px; }

/* 素材池分页：网格下面那颗「更多」，和「含背景图」开关 */
.pub-ava-more { margin-top: 8px; }
.pub-bg-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
  font-size: 12.5px;
  color: var(--text-2);
  cursor: pointer;
}

/* 声线试听：下拉 + 试听按钮排一行 */
.pub-voice-row { display: flex; align-items: center; gap: 8px; }
.pub-voice-row .select { flex: 1; min-width: 0; }
.pub-voice-play { flex-shrink: 0; white-space: nowrap; }

/* 头像框选弹层：圆形选框靠一圈超大 box-shadow 压暗选区外，
   stage overflow hidden 把阴影兜在图里 */
.modal.crop-modal { max-width: 560px; }
.crop-stage {
  position: relative;
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  border-radius: var(--r-md);
  background: var(--surface-sunken);
  user-select: none;
  -webkit-user-select: none;
}
.crop-img { display: block; max-width: 100%; max-height: 62vh; }
.crop-sel {
  position: absolute;
  box-sizing: border-box;
  border-radius: 50%;
  border: 2px solid #fff;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.55);
  cursor: move;
  touch-action: none;
}
.crop-preview-row { display: flex; align-items: center; gap: 10px; margin-top: 12px; }
.crop-preview {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  border: 0.5px solid var(--hair);
  background: #000;
  flex-shrink: 0;
}
.crop-size-lab { display: flex; align-items: center; gap: 8px; flex: 1; font-size: 12px; color: var(--text-3); }
.crop-size { flex: 1; min-width: 0; }

.pub-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 12px; }
.pub-field.is-ai .input, .pub-field.is-ai .textarea { background: var(--accent-soft); }
/* 字段标签一行：名字 + 「AI 草稿」小标 + 靠右的单字段「AI」按钮 */
.pub-field-lab { display: flex; align-items: center; min-height: 22px; }
.pub-field-ai {
  margin-left: auto;
  height: 22px;
  padding: 0 7px;
  gap: 3px;
  font-size: 11.5px;
  color: var(--accent);
  border-radius: 999px;
}
.pub-field-ai:hover { color: var(--accent); background: var(--accent-soft); }
.pub-ai-tag {
  font-size: 12px;
  font-weight: 500;
  color: var(--accent);
  border: 0.5px solid currentColor;
  padding: 0 5px;
  border-radius: 999px;
  margin-left: 6px;
}

.pub-vis { align-items: start; }

/* 场景卡：名字 + 描述 + 打招呼 / 专注两列素材 */
.pub-scene { border: 0.5px solid var(--hair); border-radius: var(--r-md); padding: 10px 12px; display: grid; gap: 7px; }
.pub-scene-head { display: flex; align-items: center; gap: 8px; }
.pub-scene-head .badge { flex: 0 0 auto; }
.pub-scene-name { flex: 1; min-width: 0; }
.pub-scene-desc-lab { font-size: 12px; font-weight: 500; color: var(--text-2); }
.pub-clip-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.pub-clip-kind { font-size: 12px; font-weight: 600; color: var(--text-2); margin-bottom: 5px; }
.pub-clips { display: grid; grid-template-columns: repeat(auto-fill, minmax(72px, 1fr)); gap: 6px; }
.pub-clip {
  position: relative;
  display: block;
  border-radius: var(--r-sm);
  overflow: hidden;
  aspect-ratio: 4 / 3;
  background: var(--surface-sunken);
  cursor: pointer;
}
.pub-clip img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pub-clip.is-off { cursor: default; }
.pub-clip.is-off img { opacity: 0.35; filter: grayscale(1); }
.pub-clip-cb { position: absolute; top: 4px; right: 4px; margin: 0; accent-color: var(--accent); }
.pub-clip-dur {
  position: absolute;
  left: 4px;
  bottom: 4px;
  font-size: 12px;
  padding: 0 4px;
  border-radius: 4px;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
}
.pub-clip-st {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 12px;
  color: var(--text-2);
}
/* 选片格子小（72px 起），角标缩一号：勾在右上，定夺在左上，时长左下，▶ 右下 */
.pub-clip .v-verdict { left: 4px; top: 4px; font-size: 11px; padding: 0 5px; }
.pub-clip-play {
  position: absolute; right: 4px; bottom: 4px;
  width: 22px; height: 22px; padding: 0; border: 0; border-radius: 999px;
  background: rgba(0, 0, 0, 0.55); color: #fff; font-size: 11px; line-height: 22px;
  cursor: pointer;
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
}
.pub-clip-play:hover { background: rgba(0, 0, 0, 0.75); }

.pub-note { background: var(--surface-sunken); color: var(--text-2); font-size: 12.5px; padding: 8px 11px; border-radius: var(--r-md); }
.pub-warn { background: rgba(255, 204, 0, 0.14); color: #8a6d00; font-size: 12px; line-height: 1.6; padding: 8px 11px; border-radius: var(--r-md); margin-top: 14px; }

/* 操作中台：项目头下面一排常驻按钮（审批/发布单/对话），入口集中不靠人记路径 */
.proj-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }

/* 发布页（原发布弹窗）：常驻页签。内容宽度收在 980，长表单居中读着不飘 */
.pub-page { overflow-y: auto; padding: 18px 24px 60px; display: block; }
.pub-page > * { max-width: 980px; margin-left: auto; margin-right: auto; }
.pub-page-head { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
.pub-saved-note { color: var(--text-3); font-size: 11.5px; }
/* 详情页阶段带按页签数平分（文案 / 图片 / 预制朋友圈 / 场景 / 发布；列表卡还是三列，别动）*/
.pstages-nav { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); }

/* 发布体检：一项一行。红=hard 拦路、黄=soft 空着的决定、问号=manual 人工确认 */
.pub-check-list { display: grid; gap: 4px; margin-top: 8px; }
.pub-check-row {
  display: flex;
  gap: 9px;
  align-items: flex-start;
  padding: 6px 10px;
  border-radius: var(--r-md);
  font-size: 12.5px;
}
.pub-check-row .ico { margin-top: 2px; flex: 0 0 auto; }
.pub-check-row[data-check="ok"] .ico { color: #2e9e5b; }
.pub-check-row[data-check="hard"] { background: rgba(214, 60, 60, 0.08); }
.pub-check-row[data-check="hard"] .ico { color: #c33; }
.pub-check-row[data-check="soft"] { background: rgba(255, 204, 0, 0.1); }
.pub-check-row[data-check="soft"] .ico { color: #8a6d00; }
.pub-check-row[data-check="manual"] .ico,
.pub-check-row[data-check="skip"] .ico { color: var(--text-3); }
.pub-check-detail { color: var(--text-3); font-size: 12px; line-height: 1.5; }

/* 五步进度：一步一行，跑着=spinner、成=绿勾、败=红叉带原因、跳过=灰点 */
.pub-step-list { display: grid; gap: 6px; margin-top: 10px; }
.pub-step {
  display: flex;
  gap: 9px;
  align-items: flex-start;
  padding: 8px 11px;
  border-radius: var(--r-md);
  background: var(--surface-sunken);
  font-size: 12.5px;
  line-height: 1.6;
}
.pub-step .ico { margin-top: 3px; flex: 0 0 auto; }
.pub-step[data-state="running"] .ico { color: var(--accent); }
.pub-step[data-state="ok"] .ico { color: #1e7a37; }
.pub-step[data-state="err"] .ico { color: #c22a20; }
.pub-step[data-state="skip"] { color: var(--text-3); }
.pub-step-main { min-width: 0; }
.pub-step-err { font-size: 12px; color: #c22a20; word-break: break-all; }
.pub-step[data-state="skip"] .pub-step-err { color: var(--text-3); }

@media (max-width: 720px) {
  .pub-grid2, .pub-clip-cols { grid-template-columns: 1fr; }
}

/* ==========================================================================
   手机端补丁（2026-08-28 审计：检查器看不了视频 / 视频页签只剩一条缝 / 触达区）
   放在文件**最末尾**：同优先级下后写的赢，这里要压住上面所有桌面规则。
   桌面端一行不动 —— 只有 .vinspect-more 这一条是桌面规则（藏掉折叠键）。
   ========================================================================== */
.tb-more-menu h2 { margin: 0 0 12px; font-size: 16px; }
/* 设置菜单里的开关行：checkbox + 标题 + 一行说明 */
.settings-row { display: flex; gap: 10px; align-items: flex-start; cursor: pointer; max-width: 320px; }
.settings-row input { margin-top: 3px; flex: none; }
.settings-txt { font-size: 13.5px; }
.settings-sub { font-size: 12px; color: var(--text-3); margin-top: 2px; line-height: 1.5; }

@media (max-width: 880px) {
  /* ---- 视频检查器：整屏 sheet（max-height / overflow 在基类 .sheet 里，这儿只
     改数字），× 钉在屏幕上，提示词默认两行 ---- */
  :root { --sheet-pad: 0px; --clamp-lines: 2; }
  .vinspect-head { padding-right: 46px; min-height: 40px; }
  .vinspect-x {
    position: fixed;
    top: max(10px, env(safe-area-inset-top, 0px));
    right: 10px;
    z-index: 2;
    width: 40px; height: 40px;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.5);
    color: #fff;
  }
  .vinspect-x .ico { width: 18px; height: 18px; }
  /* openOverlay 会把焦点给第一颗按钮（就是它）：触屏上别画一圈键盘焦点环 */
  .vinspect-x:focus { outline: none; }
  .clamp-more { padding: 6px 0; min-height: 36px; font-size: 13px; }
  /* 翻片箭头不再压在 <video controls> 中央（跟原生控件抢触点）：挪到画面底下一行 */
  .vinspect-stage.has-nav { padding-bottom: 52px; }
  .vinspect-nav {
    top: auto; bottom: 0; transform: none;
    width: calc(50% - 4px); height: 44px; font-size: 24px;
    background: var(--surface-sunken); color: var(--text);
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
  .vinspect-nav.prev { left: 0; }
  .vinspect-nav.next { right: 0; }
  .vinspect-video { min-height: 160px; }
  .vinspect-k { font-size: 12px; }

  /* ---- P0-2 / P2-5 顶栏单行 ---- */
  .toolbar { flex-wrap: nowrap; gap: 6px; min-height: 48px; padding: max(6px, env(safe-area-inset-top, 0px)) 12px 6px; }
  .toolbar-title { flex: 0 1 auto; max-width: none; }
  .toolbar-spacer { min-width: 0; }
  .toolbar .btn-sm { height: 36px; padding: 0 10px; }
  .toolbar .btn-icon, .toolbar .btn-icon.btn-sm { width: 36px; height: 36px; padding: 0; }
  /* ？ / 刷新 收进 ⋯（#btnDashMore / #btnProjMore，JS toolbarMoreMenu） */
  #btnDashHelp, #btnDashRefresh, #btnProjHelp, #btnProjRefresh { display: none; }
  .proj-balance { font-size: 12px; padding: 1px 7px; }
  /* 文案页签自己就有五颗键，余额 chip 在这一页没意义 */
  #app[data-view="project"][data-ptab="text"] .toolbar .proj-balance { display: none; }
  /* 发布徽章：只留图标，32px 圆点（P2-8：原来 69×22） */
  .pub-badge { width: 32px; height: 32px; padding: 0; justify-content: center; font-size: 12px; }
  .pub-badge span { display: none; }
  .pub-badge .ico { width: 15px; height: 15px; }

  /* ---- P0-2 项目头卡压扁：脸 48、非文案页签不画参考图库、阶段带收紧 ----
     头卡设计上不跟内容滚（贴在工具栏下面），所以只能把它压矮：
     390×844 上原来固定 chrome 446px、可滚区只剩 348px。 */
  #app[data-view="project"] .pdetail-head { margin: 6px 12px 0; padding: 6px 12px 2px; }
  .pdetail-row { gap: 10px; }
  .pdetail-face { width: 48px; height: 48px; border-radius: 9px; }
  .pdetail-face.is-empty .ico { width: 20px; height: 20px; }
  .pdetail-name { font-size: 15px; }
  #app[data-view="project"]:not([data-ptab="text"]) .refpool { display: none; }
  .refpool { margin-top: 8px; }
  /* 展开的工作台跟紧凑条同一条规矩：手机上只在文案页签出现，别的页签这块头
     一露出来可滚区就只剩两指宽。出现时格子收到 140px 起步、自己滚。 */
  #app[data-view="project"]:not([data-ptab="text"]) .refeditor { display: none; }
  .refeditor { max-height: 52vh; overflow-y: auto; padding: 8px 10px 10px; }
  .refed-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; }
  /* 那句「封面给人看…」在 390 上要占三行，把标题行顶成一坨、可滚区又少一截。
     手机上它是**注解**不是操作，直接不显示；每个格子下面的一行要求还在。 */
  .refeditor-head .wb-sub { display: none; }
  .pstages-slim { margin-top: 2px; gap: 8px; }
  .pstage-btn { padding: 5px 6px 6px; }
  .pstages-slim .pstage-big { font-size: 18px; }
  .pstage-label { font-size: 12px; }
  .pstage-sub { font-size: 12px; }
  .pstage-track { margin-top: 4px; }
  /* 对话 FAB 缩成圆形图标：胶囊压着片架右下角 */
  .proj-dock { width: var(--dock-h); padding: 0; justify-content: center; }
  .proj-dock > span { display: none; }
  .proj-dock .ico { width: 22px; height: 22px; }

  /* ---- P1-3 参考图 × / 设为封面：17px 常亮又没确认，手机上不画。
     两个动作在点开缩略图的灯箱里（44px 按钮 + 确认框）。 */
  .refpool-act { display: none; }
  /* ---- P1-4 片架 ✕：成片 / 失败的片点开就是检查器（里面有带确认的「删除」），
     片上不放破坏性按钮；还在生成的片点不开检查器，✕ 留着但抬到 36px。 */
  .vtile-x { display: none; }
  .vtile.is-running .vtile-x { display: grid; width: 36px; height: 36px; top: 4px; right: 4px; font-size: 18px; }
  .vtile-tier { right: 6px; }
  .vtile.is-running .vtile-tier { right: 44px; }

  /* ---- P2-6 灯箱提示词不做嵌套滚动：手指落在框里也滚整张 sheet ---- */
  .wb-light .wb-light-prompt { max-height: none; overflow: visible; }

  /* ---- P2-9 场景卡的 ⋯ 别被 flex:1 拉成 1/3 宽 ---- */
  .scene-acts .btn-icon { flex: none; }

  /* ---- P2-7 字号：< 12px 的统一抬到 12–12.5；tile 标题允许两行 ---- */
  .tabbar button { font-size: 12px; }
  .vtile-title {
    font-size: 12.5px; white-space: normal;
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
  }
  .vtile-when, .vtile-kind, .vtile-tier, .vtile-dur, .v-verdict { font-size: 12px; }
  .vtile-veil-text, .vtile-retry { font-size: 12.5px; }
  .vtile-retry { height: 30px; }
  .scene-shelf-n, .scene-shelf-empty, .scene-plan-line { font-size: 12px; }

  /* ---- P2-8 触达区 ---- */
  .pcard-more { width: 36px; height: 36px; top: 6px; right: 6px; }
  .authors { padding: 7px; margin: -7px; }
  .tour-skip { min-height: 40px; padding: 9px 10px; }
}

/* ==========================================================================
   审批模式 —— 全屏暗场，一条一条过整面片墙
   --------------------------------------------------------------------------
   这一整节**只在 #reviewMode 里生效**。工作台是浅色主题，审批是看片子的地方，
   看片必须是暗的（浅底会把画面里的暗部吃掉，判不准）。两套主题挤在一份 CSS 里，
   唯一安全的办法是把暗色 token 关在 #reviewMode 这个作用域里 —— 全站的
   :root 一个字都不动，退出审批界面自己就白回来了。
   层级 280：高于抽屉(250/251)，低于弹窗 .scrim(300) 和 toast(400) ——
   审批里弹「确定删这 N 条吗」和报错气泡都要盖在它上面。
   ========================================================================== */
#reviewMode {
  --rv-bg: #0e0f12;
  --rv-panel: #17181c;
  --rv-line: rgba(255, 255, 255, 0.09);
  --rv-line2: rgba(255, 255, 255, 0.18);
  --rv-text: #f4f5f7;
  --rv-text2: #b9bcc4;
  --rv-text3: #7d8089;
  --rv-green: #30d158;
  --rv-red: #ff453a;
  --rv-grey: #98989f;
  --rv-accent: #0a84ff;
  --rv-r: 14px;
  /* 刘海机横过来的时候吃的是**左右**，竖着的时候吃的是上下。四条都留出来，
     没刘海的机器和桌面上 env() 全是 0，一个像素都不多花。 */
  --rv-safe-b: env(safe-area-inset-bottom, 0px);
  --rv-safe-t: env(safe-area-inset-top, 0px);
  --rv-safe-l: env(safe-area-inset-left, 0px);
  --rv-safe-r: env(safe-area-inset-right, 0px);

  position: fixed;
  inset: 0;
  z-index: 280;
  padding-left: var(--rv-safe-l);
  padding-right: var(--rv-safe-r);
  background: var(--rv-bg);
  color: var(--rv-text);
  font: 14px/1.5 var(--font-ui);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  -webkit-tap-highlight-color: transparent;
}
#reviewMode * { -webkit-tap-highlight-color: transparent; }
/* 这一屏所有能点的东西：manipulation 去掉双击缩放和那 300ms 的点击延迟。
   连判几十条的时候，每一下都慢三百毫秒是能感觉出来的。 */
#reviewMode .rv-x, #reviewMode .rv-chip, #reviewMode .rv-f, #reviewMode .rv-th,
#reviewMode .rv-grp, #reviewMode .rv-act, #reviewMode .rv-sbtn,
#reviewMode .rv-delbar, #reviewMode .rv-prompt { touch-action: manipulation; }

/* ---- 顶栏：退出 / 进度 / 倍速 / 原片 ---- */
#reviewMode .rv-top {
  display: flex; align-items: center; gap: 10px;
  padding: calc(10px + env(safe-area-inset-top, 0px)) 14px 10px; flex: none;
}
#reviewMode .rv-x {
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--rv-panel); border: 1px solid var(--rv-line);
  color: var(--rv-text2); font-size: 15px; display: grid; place-items: center; cursor: pointer;
}
#reviewMode .rv-crumb { min-width: 0; }
#reviewMode .rv-crumb b {
  font-size: 14px; font-weight: 600; display: block;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#reviewMode .rv-crumb span { font-size: 11.5px; color: var(--rv-text3); font-variant-numeric: tabular-nums; }
#reviewMode .rv-sp { flex: 1; }
#reviewMode .rv-chip {
  border: 1px solid var(--rv-line2); background: var(--rv-panel); color: var(--rv-text2);
  border-radius: 999px; padding: 5px 11px; font-size: 12px; cursor: pointer;
  user-select: none; flex: none; white-space: nowrap;
}
#reviewMode .rv-chip.is-on { color: #fff; border-color: var(--rv-accent); background: rgba(10, 132, 255, 0.18); }
/* 这条片子压根没有压缩预览：原片本来就在播，chip 是死的 */
#reviewMode .rv-chip.is-na { opacity: 0.35; cursor: default; }
/* 「未同步 N」：亮着 = 有结论还没写进后端（正在自动补送）。平时不占位。 */
#reviewMode .rv-sync { display: none; cursor: default; }
#reviewMode .rv-sync.is-on {
  display: block; color: #ffd60a; border-color: rgba(255, 214, 10, 0.55);
  background: rgba(255, 214, 10, 0.12);
}

/* ---- 这一条片的审核要点：常驻一行小字，判之前不用去翻文档 ----
   专注和打招呼的验收标准不是一回事，但队列是**一条连续的流**（专注一整段在前、
   打招呼一整段在后）。所以不给开关、不做筛选 —— 人一路往下滑就行；
   滑过两段交界的那一下，这行字黄一下（.pulse），提醒「标准换了」。
   一个 1.6s 的闪比一个要点的开关便宜得多，也不打断连判的节奏。 */
#reviewMode .rv-stdhint {
  flex: none; padding: 2px 16px 0; font-size: 11.5px; color: var(--rv-text3); transition: color 0.5s;
}
#reviewMode .rv-stdhint b { color: var(--rv-text2); font-weight: 600; transition: color 0.5s; }
#reviewMode .rv-stdhint.pulse, #reviewMode .rv-stdhint.pulse b { color: #ffd60a; }

/* ---- 场景内部那道小黄标：这个场景的专注看完了，下面是打招呼 ----
   同一个场景的专注和招呼是一起看的（一个场景一节），所以「换标准」这件事发生在
   节的中间。缩略墙上给它一道细标，跟上面那行会黄闪的要点是同一套颜色语言。 */
#reviewMode .rv-kdiv {
  flex: none; align-self: center; font-size: 10.5px; color: #ffd60a; opacity: 0.85;
  border-left: 2px solid rgba(255, 214, 10, 0.85); padding: 1px 6px; white-space: nowrap;
}

/* ---- 视频区：占满剩余高度，点按暂停 ---- */
#reviewMode .rv-stage {
  flex: 1; min-height: 0; position: relative; display: grid; place-items: center;
  background: #000; border-radius: 18px; margin: 0 10px; overflow: hidden;
  /* 片子上的横滑是「切下一条」、点是「暂停」、底下压着的是进度条 —— 这块地方
     的手势全归我们。不写 none 的话浏览器会先把横滑当成自己的平移/侧滑返回，
     滑到一半被接管；双击还会缩放。 */
  touch-action: none;
}
/* 视频绝对定位铺满 stage 再 contain：grid 居中项的百分比高度会塌成 auto，
   方形片在宽屏被放大裁切（只剩头顶）——绝对定位不参与轨道尺寸计算，杜绝此坑 */
#reviewMode .rv-stage video {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; background: #000;
}
#reviewMode .rv-pre { display: none; }
#reviewMode .rv-pausebadge { position: absolute; inset: 0; display: none; place-items: center; pointer-events: none; }
#reviewMode .rv-pausebadge i {
  width: 64px; height: 64px; border-radius: 50%; background: rgba(0, 0, 0, 0.45);
  display: grid; place-items: center; font-style: normal; font-size: 24px; color: #fff;
}
#reviewMode .rv-stage.is-paused .rv-pausebadge { display: grid; }
#reviewMode .rv-verdict { position: absolute; top: 12px; left: 12px; display: flex; gap: 6px; z-index: 2; }
#reviewMode .rv-badge {
  font-size: 11.5px; padding: 3px 9px; border-radius: 999px;
  background: rgba(0, 0, 0, 0.5); color: #fff; border: 1px solid rgba(255, 255, 255, 0.25);
}
#reviewMode .rv-badge.ok { background: rgba(48, 209, 88, 0.85); }
#reviewMode .rv-badge.no { background: rgba(255, 69, 58, 0.85); }
#reviewMode .rv-badge.hold { background: rgba(255, 159, 10, 0.92); }
#reviewMode .rv-flash {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-size: 64px; opacity: 0; pointer-events: none; transition: opacity 0.12s;
}
#reviewMode .rv-flash.is-on { opacity: 1; }

/* ---- 元信息：场景 · 类型 · 提示词一行，点开全文 ---- */
#reviewMode .rv-meta { flex: none; padding: 9px 16px 2px; display: flex; align-items: center; gap: 8px; min-width: 0; }
#reviewMode .rv-scname { font-weight: 600; font-size: 13.5px; white-space: nowrap; }
#reviewMode .rv-kind {
  font-size: 11px; padding: 2px 8px; border-radius: 6px;
  border: 1px solid var(--rv-line2); color: var(--rv-text2); flex: none;
}
#reviewMode .rv-kind.greet { color: #ffd60a; border-color: rgba(255, 214, 10, 0.4); }
#reviewMode .rv-prompt {
  color: var(--rv-text3); font-size: 12px; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; min-width: 0; cursor: pointer;
}
#reviewMode .rv-prompt.is-open { white-space: normal; max-height: 5.5em; overflow: auto; }

/* ---- 缩略帧带：当前片高亮，横向滚 ---- */
#reviewMode .rv-strip {
  flex: none; display: flex; gap: 6px; overflow-x: auto; padding: 8px 16px 2px; scrollbar-width: none;
  /* 滚到头再往前推，别把这一下传给底下的页面（iOS 会整页橡皮筋） */
  overscroll-behavior: contain;
}
#reviewMode .rv-strip::-webkit-scrollbar { display: none; }
#reviewMode .rv-th {
  flex: none; width: 46px; height: 62px; border-radius: 8px;
  background: #26272c center / cover no-repeat; position: relative;
  opacity: 0.55; border: 2px solid transparent; cursor: pointer;
}
#reviewMode .rv-th.is-cur { opacity: 1; border-color: #fff; }
#reviewMode .rv-th .rv-dot {
  position: absolute; right: 3px; top: 3px; width: 8px; height: 8px; border-radius: 50%; display: none;
}
#reviewMode .rv-th.ok .rv-dot { display: block; background: var(--rv-green); }
#reviewMode .rv-th.no .rv-dot { display: block; background: var(--rv-red); }
#reviewMode .rv-th.hold .rv-dot { display: block; background: var(--rv-grey); }
/* 招呼片底下那道黄边：一屏几十张缩略图，靠颜色一眼分出两种片，不用读字 */
#reviewMode .rv-th .rv-kd { position: absolute; left: 0; right: 0; bottom: 0; height: 3px; }
#reviewMode .rv-th.greet .rv-kd { background: rgba(255, 214, 10, 0.85); }

/* ---- 缩略墙分组表头：一个场景一节，点表头＝跳到该组第一条 ---- */
#reviewMode .rv-grp {
  flex: none; align-self: center; font-size: 10.5px; color: var(--rv-text3);
  border: 1px solid var(--rv-line); border-radius: 7px; padding: 2px 7px;
  max-width: 64px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer;
}

/* ---- 筛选行：默认「未审」队列 ---- */
#reviewMode .rv-filters {
  flex: none; display: flex; gap: 6px; padding: 8px 16px 0; overflow-x: auto; scrollbar-width: none;
  overscroll-behavior: contain;
}
#reviewMode .rv-filters::-webkit-scrollbar { display: none; }
#reviewMode .rv-f {
  font-size: 12px; color: var(--rv-text3); border: 1px solid var(--rv-line);
  border-radius: 999px; padding: 4px 10px; cursor: pointer; white-space: nowrap;
  font-variant-numeric: tabular-nums; background: transparent;
}
#reviewMode .rv-f.is-on { color: #fff; border-color: var(--rv-line2); background: var(--rv-panel); }
#reviewMode .rv-f b { font-weight: 600; }
#reviewMode .rv-delbar {
  flex: none; display: none; margin: 8px 16px 0; padding: 9px 12px; border-radius: 10px;
  background: rgba(255, 69, 58, 0.14); border: 1px solid rgba(255, 69, 58, 0.4);
  color: #ff8a80; font-size: 12.5px; text-align: center; cursor: pointer; width: auto;
}
#reviewMode .rv-delbar.is-on { display: block; }

/* ---- 底部判定区：拇指区三大键，通过在右 ---- */
#reviewMode .rv-acts { flex: none; display: flex; gap: 10px; padding: 12px 16px calc(14px + var(--rv-safe-b)); }
#reviewMode .rv-act {
  flex: 1; border: none; border-radius: var(--rv-r); padding: 15px 0 13px;
  font-size: 15px; font-weight: 600; color: #fff; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 2px; transition: transform 0.08s;
}
#reviewMode .rv-act:active { transform: scale(0.96); }
#reviewMode .rv-act small { font-weight: 400; font-size: 10px; opacity: 0.7; }
#reviewMode .rv-act.no { background: rgba(255, 69, 58, 0.16); color: #ff6f61; border: 1px solid rgba(255, 69, 58, 0.45); }
#reviewMode .rv-act.hold { background: var(--rv-panel); color: var(--rv-text2); border: 1px solid var(--rv-line2); flex: 0.72; }
#reviewMode .rv-act.ok { background: rgba(48, 209, 88, 0.16); color: #4cd964; border: 1px solid rgba(48, 209, 88, 0.5); }
#reviewMode .rv-act.is-picked.no { background: var(--rv-red); color: #fff; }
#reviewMode .rv-act.is-picked.ok { background: var(--rv-green); color: #fff; }
#reviewMode .rv-act.is-picked.hold { background: var(--rv-grey); color: #fff; }
#reviewMode .rv-keys {
  display: none; flex: none; justify-content: center; gap: 14px;
  color: var(--rv-text3); font-size: 11px; padding: 0 0 10px;
}
#reviewMode .rv-keys kbd { border: 1px solid var(--rv-line2); border-radius: 4px; padding: 0 5px; font: inherit; }
@media (min-width: 760px) { #reviewMode .rv-keys { display: flex; } }

/* ---- 进度条：贴在视频底部，可拖 ---- */
#reviewMode .rv-seek {
  position: absolute; left: 0; right: 0; bottom: 0; display: flex; align-items: center; gap: 8px;
  z-index: 3; background: linear-gradient(transparent, rgba(0, 0, 0, 0.6)); padding: 11px 14px 6px;
}
/* 轨道**看着**还是 4px（设计稿就是这么细的一根），但输入框本身给到 24px 高 ——
   4px 的命中区在手机上根本捏不住，手指一按不是拖进度而是点了下面的片子。
   轨道用居中的渐变画，元素背景留空：外观一像素没动，能按的地方大了六倍。 */
#reviewMode .rv-seek input {
  flex: 1; -webkit-appearance: none; appearance: none; height: 24px; border-radius: 2px;
  background: linear-gradient(rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.3))
    center / 100% 4px no-repeat;
  outline: none; cursor: pointer; padding: 0; border: none; touch-action: none;
}
#reviewMode .rv-seek input::-webkit-slider-thumb {
  -webkit-appearance: none; width: 14px; height: 14px; border-radius: 50%; background: #fff; border: none;
}
#reviewMode .rv-seek input::-moz-range-thumb { width: 14px; height: 14px; border-radius: 50%; background: #fff; border: none; }
#reviewMode .rv-seek span {
  font-size: 10.5px; color: #fff; opacity: 0.85; font-variant-numeric: tabular-nums;
  min-width: 32px; text-align: center;
}

/* ---- 看完总结页 ---- */
/* 总结页竖着排四颗按钮，横屏手机上高度是不够的：给它自己能滚，
   底下留出小白条的位置，别把「完成，退出审批」压在指示条底下按不着。 */
#reviewMode .rv-summary {
  position: absolute; inset: 0; background: var(--rv-bg); display: none; flex-direction: column;
  align-items: center; gap: 10px; text-align: center; z-index: 5;
  padding: 24px calc(24px + var(--rv-safe-r)) calc(24px + var(--rv-safe-b)) calc(24px + var(--rv-safe-l));
  overflow-y: auto; overscroll-behavior: contain;
  /* safe center：装得下就居中，装不下就从顶上开始排 —— 纯 center 溢出时会把
     顶上那截推到滚不到的地方（标题和统计直接消失） */
  justify-content: center;
  justify-content: safe center;
}
#reviewMode .rv-summary.is-on { display: flex; }
#reviewMode .rv-summary h2 { margin: 0; font-size: 20px; }
#reviewMode .rv-sumrow { display: flex; gap: 18px; color: var(--rv-text2); font-size: 13px; font-variant-numeric: tabular-nums; }
#reviewMode .rv-sumrow i { font-style: normal; display: block; font-size: 22px; font-weight: 700; color: var(--rv-text); }
/* 对账行：结算数字是不是已经等于库里的数。警告态（还有账没送达）要扎眼。 */
#reviewMode .rv-sumsync { font-size: 12.5px; color: var(--rv-text3); }
#reviewMode .rv-sumsync.warn { color: #ffd60a; }
#reviewMode .rv-sbtn {
  width: min(320px, 80vw); border-radius: 12px; padding: 13px; border: 1px solid var(--rv-line2);
  background: var(--rv-panel); color: var(--rv-text); font-size: 14px; cursor: pointer; margin-top: 4px;
}
#reviewMode .rv-sbtn.danger { color: #ff6f61; border-color: rgba(255, 69, 58, 0.45); }
#reviewMode .rv-sbtn.primary { background: var(--rv-accent); border-color: var(--rv-accent); color: #fff; }
#reviewMode .rv-empty { flex: 1; display: grid; place-items: center; color: var(--rv-text3); padding: 24px; text-align: center; }

/* ---- 桌面（≥900px）：片子居中含黑边，右侧栏放缩略墙 / 筛选，判定键不横跨全屏 ----
   横过来的手机（矮，但不窄）走的也是这一套：竖排一路码下来的话，顶栏＋要点＋
   元信息＋缩略带＋筛选＋三大键把 375px 高吃得一点不剩，片子只剩 30px
   （开着「不通过」那条批量删的横条时干脆是 0）—— 左右分栏是把高度还给片子的
   唯一办法，而且这个形态本来就在设计里，不是新造一个。 */
@media (min-width: 900px), (min-width: 560px) and (max-height: 500px) {
  #reviewMode {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    grid-template-rows: auto minmax(0, 1fr) auto auto auto auto;
    gap: 0 18px;
    padding: 0 calc(18px + var(--rv-safe-r)) 6px calc(8px + var(--rv-safe-l));
  }
  #reviewMode .rv-top { grid-column: 1 / 3; }
  #reviewMode .rv-stage { grid-row: 2; grid-column: 1; margin: 0; }
  /* 六行都写死：审核要点那一行不给它明确的格子，grid 的自动放置会把它甩到
     右栏最下面那个空格里 —— 那儿既不在片子边上，人也不会往那儿看。 */
  #reviewMode .rv-stdhint { grid-row: 3; grid-column: 1; padding: 4px 6px 0; }
  #reviewMode .rv-meta { grid-row: 4; grid-column: 1; padding-left: 6px; }
  #reviewMode .rv-acts { grid-row: 5; grid-column: 1; max-width: 640px; margin: 0 auto; width: 100%; }
  #reviewMode .rv-keys { grid-row: 6; grid-column: 1; }
  #reviewMode .rv-strip {
    grid-row: 2; grid-column: 2; flex-wrap: wrap; align-content: flex-start;
    overflow-y: auto; padding: 4px 2px; gap: 8px;
  }
  #reviewMode .rv-th { width: 76px; height: 101px; border-radius: 10px; }
  #reviewMode .rv-kdiv { width: 100%; align-self: stretch; padding: 6px 2px 0; }
  #reviewMode .rv-grp {
    width: 100%; max-width: none; align-self: stretch; border: none;
    border-top: 1px solid var(--rv-line); border-radius: 0; padding: 8px 2px 1px;
    font-size: 11.5px; display: flex; gap: 6px;
  }
  #reviewMode .rv-grp b { color: var(--rv-text2); }
  #reviewMode .rv-grp:hover b { color: #fff; }
  #reviewMode .rv-filters { grid-row: 3 / 5; grid-column: 2; padding: 8px 2px; flex-wrap: wrap; align-content: start; }
  #reviewMode .rv-delbar { grid-row: 5; grid-column: 2; margin: 8px 2px; align-self: start; }
  #reviewMode .rv-prompt { white-space: normal; max-height: 3.2em; }
}

/* ---- 矮屏（横过来的手机，≤500px 高）：每一行都往回收一点，省下来的全给片子 ----
   只动尺寸和留白，不动布局顺序、配色，也不动 1/2/3 那套键位。
   键盘提示整行去掉：横着拿手机的人手上没有键盘，那一行纯占地方。 */
@media (max-height: 500px) {
  #reviewMode .rv-keys { display: none; }
  /* 退出那颗圆按钮的 32px 不动 —— 省下来的 4px 不值一个更难点中的退出键 */
  #reviewMode .rv-top { padding: calc(6px + var(--rv-safe-t)) 12px 6px; gap: 8px; }
  #reviewMode .rv-crumb b { font-size: 13px; }
  /* 要点和提示词都压成一行：矮屏上它们换行一次就吃掉片子二十几个像素 */
  #reviewMode .rv-stdhint {
    padding: 2px 12px 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  #reviewMode .rv-meta { padding: 5px 12px 0; }
  #reviewMode .rv-prompt { white-space: nowrap; max-height: none; }
  #reviewMode .rv-acts { padding: 8px 12px calc(8px + var(--rv-safe-b)); gap: 8px; }
  #reviewMode .rv-act { padding: 9px 0 8px; font-size: 14px; border-radius: 11px; }
  #reviewMode .rv-summary { gap: 6px; padding-top: 12px; }
  #reviewMode .rv-summary h2 { font-size: 17px; }
  #reviewMode .rv-sumrow i { font-size: 19px; }
  #reviewMode .rv-sbtn { padding: 9px; margin-top: 0; }
}
/* 矮屏上的右栏比桌面窄：340px 的栏在 812 宽的屏上要走掉四成多的横向空间 */
@media (min-width: 560px) and (max-height: 500px) {
  #reviewMode {
    grid-template-columns: minmax(0, 1fr) min(240px, 32vw);
    gap: 0 10px; padding: 0 calc(10px + var(--rv-safe-r)) 4px calc(6px + var(--rv-safe-l));
  }
  #reviewMode .rv-stdhint { padding: 3px 4px 0; }
  #reviewMode .rv-meta { padding: 4px 4px 0; }
  #reviewMode .rv-th { width: 52px; height: 70px; border-radius: 8px; }
  #reviewMode .rv-strip { gap: 6px; }
  #reviewMode .rv-filters { padding: 6px 2px; gap: 5px; }
  #reviewMode .rv-delbar { margin: 6px 2px; padding: 7px 10px; }
}

/* ---- 场景板抬头那颗「审批」：.proj-sec-head 是 baseline 对齐的，
       按钮跟着基线走会比标题低半行，单独扶正一下 ---- */
#wbProjSecBoard .proj-sec-head #btnProjReview { align-self: center; }

/* ================================================================
   一人一 session（2026-08-31）
   抽屉头上的 session 选择器 + 那盏「Working 5m」的灯。
   状态灯只有两态：在跑（蓝点会呼吸）/ 空闲（灰点）。刻意不做第三态 ——
   「连不上」和「空闲」对使用者是同一件事：现在指望不上它。
   ================================================================ */
.sess-pick { display: flex; align-items: center; flex: none; }
.sess-btn { display: inline-flex; align-items: center; gap: 6px; max-width: 260px; }
.sess-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sess-dot {
  width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--text-3); display: inline-block;
}
.sess-dot.on { background: var(--accent); animation: sess-pulse 1.6s ease-in-out infinite; }
@keyframes sess-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
.sess-work { font-size: 11.5px; color: var(--accent); font-variant-numeric: tabular-nums; flex: none; }
.sess-idle { font-size: 11.5px; color: var(--text-3); flex: none; }
/* 「别人的」：切到同事的 session 上说话时，这枚角标是唯一的提醒 —— 没有它，
   一屏跟自己那条长得一模一样的对话很容易让人以为是在跟自己的 AI 说话。 */
.sess-badge {
  font-size: 10.5px; padding: 1px 5px; border-radius: 999px; flex: none;
  background: rgba(255, 149, 0, 0.16); color: #b26a00;
}

.sess-menu { min-width: 260px; max-width: 340px; padding: 6px; }
.sess-menu-head { font-size: 11.5px; color: var(--text-3); padding: 4px 8px 6px; }
.sess-item {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  padding: 7px 8px; border-radius: 8px; cursor: pointer;
}
.sess-item:hover { background: var(--surface-sunken); }
.sess-item.is-on { background: var(--accent-soft); }
/* 当前动作占满第二行：它常常是一整句话，挤在名字后面会把时长顶出去 */
.sess-act { flex-basis: 100%; font-size: 11.5px; color: var(--text-3); }

/* ==========================================================================
   Development / Production —— 角色的两段生命
   词只有这两个：Development = 发布出去、TestFlight 里能用；Production = 同步
   过去才算真正上线。颜色语言全局只有三档，别再引入第四种：
     · 蓝（--accent）= 有东西还没推过去，等着人动手；
     · 红（--red）   = Production 被绕过 Studio 改过，账对不上；
     · 灰           = 没事。
   ========================================================================== */

/* ---- 总览卡底那一行 ---- */
.pcard-life {
  flex: 0 1 auto; min-width: 0;
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11.5px; color: var(--text-3);
  font-variant-numeric: tabular-nums;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pcard-life::before {
  content: ""; flex: 0 0 auto;
  width: 7px; height: 7px; border-radius: 999px;
  background: rgba(118, 118, 128, 0.45);
}
.pcard-life.is-ahead::before { background: var(--accent); }
.pcard-life.is-drift::before { background: var(--red); }
.pcard-life.is-drift { color: var(--red); }

/* owner chip 和 Production chip 之间那道竖线：两个维度，不是一排同类 */
.wb-chip-sep {
  align-self: center; flex: 0 0 auto;
  width: 1px; height: 16px; margin: 0 4px;
  background: var(--hair-strong);
}

/* 垫底那一区的头像格：一个「上」字，蓝底 —— 跟人名首字母的灰底区分开 */
.author-av.is-prod {
  background: var(--accent-soft); color: var(--accent); font-weight: 600;
}
.pgroup-prod .pgroup-n { white-space: normal; }

/* 「已发布到 Development」这类长副标题：折行，别切成半个单词 */
.pstage-sub.is-wrap { white-space: normal; }
/* 满格但不绿：发出去了 ≠ 两个环境对齐了。绿只留给 in_sync */
.pstage.is-flat .pstage-fill { background: rgba(118, 118, 128, 0.45); }

/* ---- 发布页顶部状态条 ----
   一行灰字 + 一条发丝线。刻意不做成卡片：它是处境说明，不是这一页的主角。 */
.pub-life {
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  min-height: 36px; margin: 6px 0 14px;
  border-bottom: 0.5px solid var(--hair);
  font-size: 12px; color: var(--text-3);
  font-variant-numeric: tabular-nums;
}
.pub-life-seg { min-width: 0; }
.pub-life-seg.is-bad { color: var(--red); }
.pub-life-link {
  flex: 0 0 auto; border: 0; background: none; padding: 0;
  font: inherit; color: var(--accent); cursor: pointer;
}
.pub-life-link:hover { text-decoration: underline; }

/* ---- 设置项式的只读行（App 显示名） ----
   一个禁用的输入框是在假装这里能输入，然后当场拒绝人。 */
.pub-ro {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 10px 12px; border-radius: var(--r-md);
  background: var(--surface-sunken);
}
.pub-ro-main { min-width: 0; flex: 1; }
.pub-ro-lab { font-size: 12px; color: var(--text-2); }
.pub-ro-val { font-size: 14px; font-weight: 600; letter-spacing: -0.01em; margin-top: 1px; }
.pub-ro-help { font-size: 11.5px; color: var(--text-3); line-height: 1.5; margin-top: 2px; }
.pub-ro-link {
  flex: 0 0 auto; border: 0; background: none; padding: 2px 0;
  font: inherit; font-size: 12.5px; color: var(--accent); cursor: pointer;
}
.pub-ro-link:hover { text-decoration: underline; }

/* ---- 选片：勾选态画在格子上 ---- */
.pub-clip-cell { min-width: 0; }
/* 勾选框留着管行为（回填 / 收集全靠它），但不再是那颗系统小方块 */
.pub-clip-cb {
  position: absolute; top: 4px; right: 4px; margin: 0;
  opacity: 0; width: 1px; height: 1px; pointer-events: none;
}
.pub-clip.is-picked { outline: 2px solid var(--accent); outline-offset: -2px; }
.pub-clip-tick {
  position: absolute; right: 4px; bottom: 4px;
  width: 18px; height: 18px; border-radius: 999px;
  display: none; place-items: center;
  background: var(--accent); color: #fff;
}
.pub-clip-tick .ico { width: 12px; height: 12px; }
.pub-clip.is-picked .pub-clip-tick { display: grid; }
/* ▶ 和勾都在右下角：选中时把 ▶ 让到勾的左边，别叠在一起 */
.pub-clip.is-picked .pub-clip-play { right: 26px; }
/* 不通过的片子：压暗 + 不可选。发布闸本来就只放行已通过的，
   让它长得能点、点了又被拦，是把规则藏起来让人撞。 */
.pub-clip.is-blocked { opacity: 0.4; cursor: default; }
.pub-clip-note {
  margin-top: 3px; font-size: 11px; color: var(--text-3); line-height: 1.4;
}

/* 场景头右侧那句实时账：达标才变蓝带勾 */
.pub-scene-meter {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 4px;
  font-size: 12px; color: var(--text-3); font-variant-numeric: tabular-nums;
}
.pub-scene-meter.is-ok { color: var(--accent); }
.pub-sec-tally {
  flex: 0 0 auto; font-size: 12px; color: var(--text-3);
  font-variant-numeric: tabular-nums;
}
.pub-sec-tally.is-ok { color: var(--accent); }

/* ---- 底部动作条：跟工具栏同一种半透明，贴在面板底 ---- */
.pub-bar {
  position: sticky; bottom: 0; z-index: 3;
  display: flex; align-items: center; justify-content: flex-end; gap: 8px;
  min-height: 52px; margin-top: 18px;
  padding: 8px 0;
  background: var(--surface-veil);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border-top: 0.5px solid var(--hair);
}
.pub-bar .btn.pub-primary { border-radius: 999px; padding-left: 18px; padding-right: 18px; }

/* ---- 同步确认框：先看变化，再点确认 ---- */
.lc-sync-cap {
  margin-top: 12px; font-size: 12px; font-weight: 600; color: var(--text-2);
}
.lc-diff { display: grid; gap: 4px; margin-top: 6px; }
.lc-diff-row {
  display: grid; grid-template-columns: 120px minmax(0, 1fr); gap: 10px;
  align-items: center;
  padding: 6px 10px; border-radius: var(--r-md);
  background: var(--surface-sunken); font-size: 12.5px;
}
.lc-diff-f { color: var(--text-2); overflow: hidden; text-overflow: ellipsis; }
.lc-diff-v { display: flex; align-items: center; gap: 8px; min-width: 0; flex-wrap: wrap; }
.lc-diff-old { color: var(--text-3); text-decoration: line-through; }
.lc-diff-new { color: var(--text); font-weight: 500; }
.lc-diff-arrow { color: var(--text-3); flex: 0 0 auto; }
.lc-diff-img {
  flex: 0 0 auto; width: 40px; height: 40px; border-radius: var(--r-sm);
  overflow: hidden; background: var(--bg-deep); display: block;
}
.lc-diff-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.lc-foot { color: var(--text-3); }
/* 版本历史里「已在 Production」那颗 chip */
.badge.lc-chip-prod { background: var(--accent-soft); color: var(--accent); }

@media (max-width: 640px) {
  .lc-diff-row { grid-template-columns: 1fr; gap: 4px; }
  .pub-bar { flex-wrap: wrap; }
}

/* ==========================================================================
   批次卡片（run）—— 一单「带人采样 10 张」在对话里的样子
   气泡是「谁说了一句话」，卡片是「一个还活着的对象」：它会在原地一格一格变色。
   所以它满宽、不带尖角、不挂 who 行 —— 一眼就分得出这不是有人在说话。
   ========================================================================== */
.proj-run {
  border: 0.5px solid var(--hair-strong); border-radius: var(--r-md);
  background: var(--surface); padding: 10px 12px; display: grid; gap: 8px;
  min-width: 0; max-width: 100%; box-shadow: var(--sh-1);
}
.proj-run.s-failed { border-color: rgba(215, 0, 21, 0.35); }
.proj-run.s-done { border-color: rgba(52, 199, 89, 0.4); }
.proj-run.s-cancelled { opacity: 0.6; }
.proj-run-head { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.proj-run-title {
  font-weight: 600; font-size: 13px; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.proj-run-state { font-size: 11px; color: var(--text-2); flex: none; }
.proj-run-count {
  font-size: 11px; color: var(--text-2); margin-left: auto; flex: none;
  font-variant-numeric: tabular-nums;
}
.proj-run-bar {
  height: 3px; border-radius: 2px; background: var(--bg-deep); overflow: hidden;
}
.proj-run-bar-fill {
  height: 100%; background: var(--accent); transition: width var(--dur) var(--ease);
}
.proj-run.s-failed .proj-run-bar-fill { background: var(--orange); }
.proj-run.s-done .proj-run-bar-fill { background: var(--green); }
/* 格子铺满一行、自己换行：10 张和 3 张用同一套排版，不给"每行几个"配参数。 */
.proj-run-cells { display: flex; flex-wrap: wrap; gap: 4px; }
.proj-run-cell {
  width: 40px; height: 40px; border-radius: var(--r-sm); overflow: hidden;
  background: var(--surface-sunken); border: 0.5px solid var(--hair);
  display: flex; align-items: center; justify-content: center; flex: none;
}
.proj-run-cell img { width: 100%; height: 100%; object-fit: cover; cursor: zoom-in; }
.proj-run-cell-seq { font-size: 11px; color: var(--text-3); }
.proj-run-cell.gen { border-color: var(--accent); }
.proj-run-cell.pass { border-color: var(--green); border-width: 1.5px; }
.proj-run-cell.fail { border-color: var(--red); background: rgba(215, 0, 21, 0.08); }
.proj-run-cell.void { opacity: 0.35; }
/* Six paired candidates, with one explicit choice per pair. */
.face-pair-hint { margin:0; color:var(--text-2); font-size:12px; line-height:1.6; }
.face-pair-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr)); gap:12px; min-width:0; }
.face-pair-card { min-width:0; display:grid; gap:10px; padding:12px; border:1px solid var(--hair); border-radius:12px; background:var(--surface); }
.face-pair-card.is-selected { border-color:var(--accent); background:var(--accent-soft,var(--surface)); }
.face-pair-card h4 { margin:0; font-size:13px; font-weight:600; }
.face-pair-shots { display:grid; grid-template-columns:1fr 1fr; gap:8px; }
.face-pair-shot { display:grid; gap:5px; min-width:0; }
.face-pair-view { color:var(--text-2); font-size:11px; }
.face-pair-preview, .face-pair-empty { display:flex; align-items:center; justify-content:center; width:100%; aspect-ratio:1; padding:0; overflow:hidden; border:0; border-radius:8px; background:var(--surface-sunken); }
.face-pair-preview { cursor:zoom-in; }
.face-pair-preview img { width:100%; height:100%; object-fit:contain; }
.face-pair-empty { color:var(--text-3); font-size:12px; }
.face-pair-use { width:100%; min-height:36px; justify-content:center; }
.face-pair-use:disabled { opacity:.45; cursor:default; }
.face-pair-draw { justify-self:start; min-height:36px; }
.face-pair-preview:focus-visible, .face-pair-use:focus-visible { outline:3px solid var(--accent); outline-offset:3px; }

/* 气泡正文里贴出来的图（执行者把 url 写进话里时） */
.proj-bubble-shots { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 6px; }
.proj-bubble-shot {
  width: 96px; height: 96px; object-fit: cover; cursor: zoom-in;
  border-radius: var(--r-sm); border: 0.5px solid var(--hair); background: var(--surface-sunken);
}

/* 钉在输入框上方的那张：贴着输入框，所以只留上边的分隔线，不再自带外边距。 */
.proj-run-pin {
  padding: 8px 16px 0; border-top: 0.5px solid var(--hair); min-width: 0;
}
.proj-run.pinned { box-shadow: none; }

/* ==========================================================================
   测试聊天面板：可收起 + 单行工具栏（2026-09-07）
   老板的小电脑上这块占了半屏，工具栏还叠了三层，正文只剩一条缝。三件事：
     ① 面板能收成右边缘一条 44px 的窄轨，主区吃掉让出来的宽度；
     ② 三行工具栏压成一行，次要动作进 ⋯ 菜单（一个入口都没删）；
     ③ 浮动「对话」和面板同时在场时只留一个，别再压住输入框。
   写在文件最末尾是**故意**的：要压住前面几档 @media 里的同名规则。
   ========================================================================== */

/* 只在 >1024 出现的控件（桌面的「收起」键；≤1024 那档由 .n-only 的 × 接手） */
.w-only { display: inline-flex; }
@media (max-width: 1024px) { .w-only { display: none !important; } }

/* 「自动对话」页签上，聊天那几件工具没有作用对象 */
#rail[data-tab="runs"] .chat-only { display: none !important; }

/* ------------------------------------------------------- 单行工具栏 */
.rail-head { position: relative; }
/* 连接状态只剩一颗点，文字挂在 title 上（setConn 两处一起写） */
.conn-text { display: none; }
.conn { padding: 0 2px; }
#btnChatMore { font-size: 16px; }
/* 页签是这一行的锚：右栏最窄 316px，它一被 flex 压缩就把「测试聊天」
   四个字折成竖排（1440 上实测）。整条工具栏宁可挤掉别的，不挤它。 */
.rail-head .segmented { flex: none; }
.rail-head .segmented button { white-space: nowrap; }
.rail-head .btn { flex: none; }

/* ------------------------------------------------------------ ⋯ 菜单 */
.rail-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 8px;
  z-index: 70;
  width: 246px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
  border-radius: var(--r-md);
  border: 0.5px solid var(--hair);
  background: var(--surface);
  box-shadow: var(--sh-3);
  animation: pop 0.16s var(--ease);
}
.rail-menu-item {
  width: 100%;
  justify-content: flex-start;
  border: 0;
  background: transparent;
  box-shadow: none;
}
.rail-menu-item:hover:not(:disabled) { background: var(--surface-sunken); }
/* 一行装不下就折行，别让「设时间」被菜单裁掉（246px 上实测过） */
.rail-menu-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 4px 8px;
  font-size: 13px;
}
.rail-menu-row .select { width: auto; flex: 1; min-width: 0; }
.rail-menu-row .input { width: 104px; flex: none; }
.rail-menu-row .btn { flex: none; }
.rail-menu-label { color: var(--text-2); white-space: nowrap; }
.rail-menu-sep { height: 0.5px; background: var(--hair); margin: 4px 6px; }

/* --------------------------------------------------------- 收起 / 窄轨 */
/* 收起后 .rail 只剩 44px 的空槽，窄轨（fixed，在 .rail 外面）钉在那条槽里。
   槽是**真占位**的：让主区的右边缘停在窄轨左侧，正文不会钻到它底下去。 */
/* 只在 >1024 —— 再往下 .rail 是浮层，「收起」由 railOpen/railClose 那套管，
   这里再去改它的宽度会把浮层拍成一条 44px 的缝（前面那档 .rail.collapsed
   写在本规则之前，同优先级下后写的赢，必须自己把范围关住）。 */
@media (min-width: 1025px) {
  .rail.collapsed {
    width: 44px;
    overflow: hidden;
    border-left: 0.5px solid var(--hair);
  }
  .rail.collapsed > * { display: none; }
}

.rail-rail {
  display: none;
  position: fixed;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: 44;
  width: 40px;
  height: 104px;
  padding: 0;
  border: 0.5px solid var(--hair);
  border-right: 0;
  border-radius: 12px 0 0 12px;
  background: var(--surface);
  box-shadow: var(--sh-2, 0 2px 10px rgba(0, 0, 0, 0.08));
  color: var(--text-2);
  cursor: pointer;
  align-items: center;
  justify-content: center;
}
.rail-rail:hover { color: var(--accent); background: var(--surface-sunken); }
.rail-rail .ico { width: 19px; height: 19px; }
.rail-rail-dot {
  position: absolute;
  top: 26px; right: 9px;
  width: 8px; height: 8px;
  border-radius: 99px;
  background: var(--red);
  border: 1.5px solid var(--surface);
}
/* 只在「有作用对象、面板又没在场」的时候露出来 */
#app[data-view="project"][data-ptab="text"]:not([data-bound="0"]):not(.rail-shown) .rail-rail {
  display: flex;
}

/* --------------------------------------------- 浮动「对话」不再压输入框 */
/* 面板在场 = 对话已经有地方了，浮标退场（.rail-shown 由 railSync 维护，
   桌面看 .collapsed、≤1024 看浮层开没开，一个判据两档都对）。 */
.app.rail-shown .proj-dock,
.app.rail-shown .pick-bar { display: none; }

/* ------------------------------------------------------ ≤880：底部抽屉 */
@media (max-width: 880px) {
  /* 从右边滑进来的竖抽屉在 390px 上等于盖住整屏；改成从下面升起来的半屏，
     上半屏还看得见正在编辑的设定，人才知道自己在测什么。 */
  #app[data-view="project"][data-ptab="text"] .rail,
  #app[data-view="project"][data-ptab="text"] .rail.rail-open {
    display: flex;
    position: fixed;
    left: 0; right: 0;
    top: auto;
    bottom: calc(var(--tab-h) + var(--kb, 0px));
    width: auto;
    height: min(78vh, 620px);
    max-height: calc(100vh - var(--vbanner, 0px) - 52px);
    border: 0;
    border-radius: 16px 16px 0 0;
    box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.14);
    transform: translateY(110%);
    transition: transform var(--dur) var(--ease);
    z-index: 55;
    overflow: hidden;
  }
  #app[data-view="project"][data-ptab="text"] .rail.rail-open { transform: none; }
  /* 抽屉在下面，旁白就得铺满整屏，不能只铺工具栏以下那一块 */
  .rail-scrim { top: var(--vbanner, 0px); }
  /* 16px 侧留白：气泡和输入框以前是贴着屏幕边的 */
  .rail-head { padding: 8px 16px; }
  .chat-log { padding: 12px 16px 6px; }
  .composer { padding-left: 16px; padding-right: 16px; }
  .rail-menu { left: 12px; right: 12px; width: auto; }
  /* 窄轨在手机上是唯一的开关，收成一颗贴边的小把手 */
  .rail-rail { width: 34px; height: 84px; top: 42%; }
  .rail-rail .ico { width: 17px; height: 17px; }
}

/* Studio chat: readable at 1280 x 720 without crowding out the conversation. */
.proj-drawer { background:#fafafc; backdrop-filter:none; -webkit-backdrop-filter:none; }
@media (min-width:641px) {
  .proj-drawer { width:clamp(640px, 64vw, 1040px); max-width:100vw; }
}
.proj-drawer-body { min-height:0; padding:20px 24px 28px; gap:20px; }
.proj-drawer .conversation-controls { max-height:none; overflow:visible; }
.proj-drawer .conversation-controls > * { flex-shrink:0; }
.proj-drawer .proj-composer {
  display:flex; flex-direction:column; max-height:45dvh;
  padding:10px 16px 14px; gap:8px;
}
.proj-drawer .proj-composer-row, .proj-drawer .proj-attach { flex:none; }
.proj-drawer .proj-tools { flex:0 1 auto; min-height:24px; overflow-y:auto; overscroll-behavior:contain; }
.proj-tools > summary { display:flex; align-items:center; gap:8px; list-style:none; cursor:pointer; font-size:12px; color:var(--text-2); padding:3px 0; }
.proj-tools > summary::-webkit-details-marker { display:none; }
.proj-tools > summary::before { content:'＋'; font-size:16px; }
.proj-tools[open] > summary::before { content:'−'; }
.proj-tools .proj-send-target { margin-left:auto; font-size:11px; }
.proj-tools-body { display:grid; gap:8px; padding:10px 0 4px; }
.proj-run-pin { flex:none; padding:8px 16px; }
.proj-run.pinned { margin:0; padding:10px 12px; max-height:25vh; overflow:auto; }
.proj-run.pinned > summary { cursor:pointer; list-style:none; margin:0; }
.proj-run.pinned > summary::-webkit-details-marker { display:none; }
.proj-run.pinned > summary::after { content:'⌄'; color:var(--text-3); }
.proj-run.pinned[open] > summary::after { content:'⌃'; }
.proj-run.pinned[open] .proj-run-bar { margin-top:10px; }
.proj-drawer .proj-say { min-height:44px; max-height:140px; overflow-y:auto; }
.proj-drawer .proj-send, .proj-drawer .proj-pause, .proj-drawer .proj-attach-add { min-height:40px; }
/* Project header: identity, reference summary, then navigation. */
#app[data-view="project"] .pdetail-head {
  display: block; margin: 12px 18px 0; padding: 16px 18px 0;
  border-radius: 16px; box-shadow: none;
}
.pdetail-top { display: flex; align-items: center; gap: 20px; }
.pdetail-top .pdetail-row { flex: 1; min-width: 0; gap: 12px; }
.pdetail-top .pdetail-info { flex: 1; }
.pdetail-top .pdetail-face { width: 56px; height: 56px; border-radius: 10px; }
.pdetail-top .pdetail-face-wrap { flex-direction: row; }
.pdetail-top .pface-cta { max-width: 84px; }
.pdetail-top .pdetail-name { font-size: 18px; line-height: 1.4; }
.pdetail-top .wb-sub { font-size: 12px; line-height: 1.6; color: var(--text-2); }
.pdetail-top .authors { margin: 0 4px; }
.pdetail-top .proj-actions { margin: 0; flex: none; gap: 6px; }
.pdetail-top .proj-actions .btn { min-height: 34px; padding: 0 12px; box-shadow: none; }
.pref-summary {
  display: flex; align-items: center; flex-wrap: wrap; gap: 12px;
  margin-top: 14px; padding: 10px 0; border-top: 0.5px solid var(--hair);
}
.pref-heading, .pref-count { color: var(--text-2); font-size: 12px; white-space: nowrap; }
.pref-previews { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; }
.pref-preview {
  display: flex; align-items: center; gap: 7px; padding: 0;
  border: 0; background: none; font: inherit; font-size: 12px;
  color: var(--text-2); cursor: pointer; border-radius: 6px;
}
.pref-preview img, .pref-placeholder {
  width: 32px; height: 32px; object-fit: cover; border-radius: 6px;
  border: 0.5px solid var(--hair); background: var(--surface-sunken);
}
.pref-placeholder { display: grid; place-items: center; font-size: 18px; }
.pref-preview:hover { color: var(--accent); }
.pref-preview:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.pref-summary .refed-toggle { margin-left: auto; min-height: 32px; border: 0; box-shadow: none; color: var(--accent); background: none; }
#app[data-view="project"] .pdetail-head .refeditor {
  display: block; margin: 0 0 12px; max-height: 50vh; overflow-y: auto;
}
.pdetail-head .refeditor-head { flex-wrap: wrap; }
.pdetail-head .refeditor-head .wb-sub { white-space: normal; }
.pdetail-head .pstages {
  display: flex; gap: 6px; margin: 0; padding-top: 4px;
  border-top: 0.5px solid var(--hair); overflow-x: auto;
}
.pdetail-head .pstage {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  min-height: 42px; padding: 8px 14px; flex: none;
  border-radius: 8px 8px 0 0; border-bottom: 2px solid transparent;
}
.pdetail-head .pstage.is-on { background: none; border-bottom-color: var(--accent); }
.pdetail-head .pstage:hover { background: var(--surface-sunken); }
.pdetail-head .pstage-label { order: 1; font-size: 13px; letter-spacing: 0; }
.pdetail-head .pstage-big { order: 2; margin: 0; font-size: 12px; line-height: 1.4; color: var(--text-2); }
.pdetail-head .pstage-track, .pdetail-head .pstage-sub { display: none; }
@media (max-width: 1100px) {
  #app[data-view="project"] .pdetail-head { margin: 10px 14px 0; padding: 12px 14px 0; }
  .pdetail-top { gap: 12px; flex-wrap: wrap; }
  .pdetail-top .pdetail-row { flex-basis: 260px; }
  .pdetail-top .proj-actions { margin-left: auto; }
}
@media (max-width: 600px) {
  .pdetail-top .pdetail-name { font-size: 16px; }
  .pdetail-top .pdetail-face { width: 44px; height: 44px; }
  .pdetail-top .proj-actions { width: 100%; }
  .pdetail-top .proj-actions .btn { flex: 1; min-height: 36px; }
  .pref-summary { gap: 8px; margin-top: 12px; }
  .pref-heading { display: none; }
  .pref-previews { gap: 10px; }
  .pref-preview { gap: 5px; }
  .pref-count { font-size: 11px; }
  .pref-summary .refed-toggle { padding: 0 4px; }
  .pdetail-head .pstage { flex: 1; min-width: 0; padding: 8px 6px; }
}

/* Comparison opens above the conversation without crowding its composer. */
.face-review { width:min(920px,calc(100vw - 32px)); max-height:calc(100dvh - 32px); box-sizing:border-box; padding:0; border:1px solid var(--hair); border-radius:18px; background:var(--surface); color:var(--text); box-shadow:0 20px 80px #0003; }
.face-review[open] { display:flex; flex-direction:column; }
.face-review::backdrop { background:rgba(0,0,0,.35); backdrop-filter:blur(4px); }
.face-review-head { flex:none; display:flex; align-items:center; justify-content:space-between; gap:16px; padding:16px 20px; border-bottom:1px solid var(--hair); }
.face-review-head h3 { margin:0; font-size:17px; font-weight:600; }
.face-review-body { min-height:0; overflow:auto; overscroll-behavior:contain; padding:16px 20px 20px; display:grid; gap:16px; }
.face-review .face-pair-grid { grid-template-columns:repeat(3,minmax(0,1fr)); }
.face-pair-review { justify-self:start; margin-top:6px; min-height:36px; }
@media(max-width:700px) { .face-review .face-pair-grid { grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media(max-width:420px) { .face-review .face-pair-grid { grid-template-columns:1fr; } }

.face-preview { max-width:calc(100vw - 32px); max-height:calc(100dvh - 32px); padding:12px; border:0; border-radius:14px; background:var(--surface); }
.face-preview[open] { display:flex; flex-direction:column; gap:12px; }
.face-preview::backdrop { background:rgba(0,0,0,.6); }
.face-preview > button { align-self:flex-end; flex:none; }
.face-preview img { min-height:0; max-width:100%; max-height:calc(100dvh - 110px); object-fit:contain; }

/* Seed count remains a wrapping form on phones, not a scrolling chip strip. */
.proj-seed-options { display: flex; flex-wrap: wrap; gap: 8px; }
.proj-seed-options > .proj-sub-label { white-space: nowrap; }
#inpProjSeedCount { width: 150px; flex: 0 0 150px; }
#wbProjSeedHint { flex-basis: 100%; line-height: 1.5; margin-top: 0; }
/* 个人 skill 的数字参数（M 组 N 张这种）：跟 Seed 段数同一套样式。 */
#wbProjSkillParams .input { width: 84px; flex: 0 0 84px; }
#wbProjSkillParams .proj-mode-hint { flex-basis: 100%; line-height: 1.5; margin-top: 0; }

/* Draft application status stays beside the disabled composer. */
.chat-sync-status { white-space:normal; line-height:1.5; gap:8px; }
.chat-sync-status span { flex:1; }
.chat-sync-status[data-state="pending"],
.chat-sync-status[data-state="saving"] { background:#fff4d6; color:#754c00; border-top:1px solid #f0d392; }
.chat-sync-status[data-state="error"] { background:#fff0ef; color:#a32218; border-top:1px solid #efb5ad; }

/* Persistent authoring prompts stay visible after typing. */
.intent-hint { margin: 0; color: var(--text-2); font-size: 13px; line-height: 1.6; }

/* The assistant has one reading area and a stationary composer. */
.proj-drawer .drawer-head { padding:18px 24px; border-bottom:1px solid var(--hair); }
.proj-drawer .drawer-sub { margin-top:5px; line-height:1.5; }
.proj-drawer .proj-chat { gap:18px; padding-top:20px; }
.proj-drawer .proj-bubble { max-width:92%; padding:14px 16px; gap:8px; }
.proj-drawer .proj-bubble-text { font-size:14px; line-height:1.8; }
.proj-drawer .proj-bubble-step { line-height:1.65; color:var(--text-2); }
.proj-drawer .proj-composer { padding:14px 24px 20px; border-top:1px solid var(--hair); }
.proj-drawer .proj-say { min-height:64px; }
@media (max-width:640px) {
  .proj-drawer-body, .proj-drawer .drawer-head { padding:16px; }
  .proj-drawer .proj-composer { padding:12px 16px; }
  .proj-drawer .proj-bubble { max-width:96%; }
}

/* Independent entry points for the creation assistant and character preview. */
.studio-assistant-entry { display:flex; flex:none; align-items:center; gap:4px; padding:4px; border:1px solid var(--hair); border-radius:12px; background:var(--surface); }
.studio-assistant-entry .btn { border:0; box-shadow:none; white-space:nowrap; min-height:36px; }
.studio-assistant-entry .assistant-entry { color:var(--accent); background:var(--accent-soft); }
.studio-service-status { position:relative; flex:none; font-size:12px; color:var(--text-2); }
.studio-service-status > summary { cursor:pointer; padding:8px; }
.studio-service-status > div { position:absolute; right:0; top:100%; z-index:40; min-width:180px; padding:14px; display:grid; gap:10px; background:var(--surface); border:1px solid var(--hair); border-radius:12px; box-shadow:0 10px 30px #0001; }
#app[data-view="project"] .studio-service-status .proj-balance { display:inline-flex; }
#btnProjDock { display:none !important; }
.proj-drawer .drawer-head { height:auto; min-height:78px; }
.proj-drawer .drawer-head h2 { font-size:19px; }
.proj-drawer .drawer-sub { font-size:13px; }
.role-purpose-head { display:flex; align-items:center; justify-content:space-between; flex:none; gap:10px; padding:16px; border-bottom:1px solid var(--hair); }
.role-purpose-head > div { display:grid; gap:5px; }
.role-purpose-head strong { font-size:15px; }
.role-purpose-head span { color:var(--text-2); font-size:12px; }
.proj-drawer .proj-live-box:has(.proj-action:empty):has(.proj-task-cards:empty):has(.proj-jobs.hidden):has(.proj-task-detail.hidden) { display:none; }
@media (max-width:880px) {
  #projectView > .toolbar { flex-wrap:wrap; height:auto; min-height:56px; gap:6px; }
  .studio-assistant-entry { margin-left:auto; }
  .studio-assistant-entry .btn { font-size:12px; padding:0 9px; }
}

.proj-drawer.is-expanded { width:calc(100vw - 32px); max-width:none; }
.proj-drawer .drawer-title { flex:1; }
.proj-drawer .drawer-head > .toolbar-spacer { display:none; }
.proj-drawer .drawer-head > .btn { flex:none; min-height:36px; }
.role-quick-entry { display:flex; flex:none; align-items:center; gap:12px; padding:10px 16px; border-bottom:1px solid var(--hair); background:var(--surface); }
.role-quick-entry > span { font-size:12px; color:var(--text-2); }
#btnToggleRail { color:var(--accent); border-color:var(--accent); font-weight:600; }
#btnAssistantOpen { min-height:38px; }
@media (max-width:640px) {
  .proj-drawer, .proj-drawer.is-expanded { width:100vw; max-width:100vw; }
  .role-quick-entry > span { display:none; }
}

.style-options { display: grid; gap: 8px; padding: 12px 16px; }
.style-options .textarea, .style-options .select { width: 100%; min-width: 0; }

.style-settings { border-bottom: 1px solid var(--border); max-height: 55%; overflow: auto; flex-shrink: 0; }
.style-settings summary { cursor: pointer; padding: 12px 16px; font-weight: 600; }

/* 「场景定下来了」闸(scene_locked) */
.scene-gate { border: 1px solid var(--accent, #1a73e8); border-radius: 12px; padding: 16px 18px; margin: 8px 0 14px; background: color-mix(in srgb, var(--accent, #1a73e8) 6%, var(--panel, #fff)); display: flex; flex-direction: column; gap: 8px; }
.scene-gate.is-open { border-color: var(--hair, #e5e7eb); background: var(--panel, #fff); flex-direction: row; align-items: center; flex-wrap: wrap; padding: 10px 14px; }
.scene-gate-t { font-weight: 600; font-size: 15px; }
.scene-gate-s { font-size: 13px; color: var(--text-2, #555); line-height: 1.6; flex: 1; min-width: 0; }
.scene-gate-btn { font-size: 16px; padding: 14px 24px; align-self: flex-start; }
/* 满三组后多出来的那一组:一行大白话提醒 + 批准按钮,不是红色报错 */
.scene-extra { flex-basis: 100%; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 8px 10px; border-radius: 8px; background: color-mix(in srgb, #f5a623 10%, var(--panel, #fff)); }
.proj-kind-chip:disabled { opacity: .45; cursor: not-allowed; }
.modal .sel { width: 100%; font: inherit; padding: 8px 10px; margin: 8px 0; }

/* 场景回收站 */
.scene-trash-body { display: flex; flex-direction: column; gap: 8px; padding: 8px 0; }
.scene-trash-row { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border: .5px solid var(--hair, #e5e7eb); border-radius: 8px; }
.scene-trash-row img { height: 44px; width: auto; max-width: 80px; object-fit: cover; border-radius: 6px; opacity: .7; }
.scene-trash-mid { flex: 1; min-width: 0; }

/* 手动试聊的模型选择：一行，贴在输入框上方 */
.chat-model-bar { display:flex; align-items:center; gap:8px; padding:6px 16px 0; font-size:12px; color:var(--text-2); }
.chat-model-bar label { flex:none; }
.chat-model-bar .select { flex:1; min-width:0; min-height:30px; padding:4px 8px; font-size:12.5px; }
.char-model-hint { margin:4px 0 0; color:#a15c00; }
#paneChat > .char-model-hint { padding:0 16px; }

/* AI 制作助手（2026-10-07 老板：「连对话框都看不见」）
   对话是这块面板的主体：抬头压成一行标题，目标状态一行（conversation-controls.css），
   批次钉卡一行，额度收成输入框下的小胶囊。剩下的高度全给对话流（.proj-drawer-body 的 flex:1）。 */
.proj-drawer .drawer-head { min-height:0; padding:12px 24px; }
.proj-drawer .drawer-head h2 { font-size:17px; white-space:nowrap; }
.proj-drawer .drawer-sub { margin-top:2px; font-size:12px; }
.proj-drawer .proj-chat { padding-top:12px; }
.proj-drawer-body { padding-top:4px; }
.proj-run-pin { padding:6px 16px; }
.proj-run.pinned:not([open]) { padding:6px 12px; gap:0; }
.proj-run.pinned > summary { display:flex; align-items:center; white-space:nowrap; min-height:24px; }
.proj-run.pinned > summary .proj-run-title { flex:0 1 auto; }
.proj-drawer .proj-composer { padding:10px 24px 12px; gap:6px; }
.proj-drawer .proj-say { min-height:48px; }
/* 空输入框的高度跟着占位符走（Chrome 把占位符算进 scrollHeight），窄屏上它会把
   输入框撑成四行。占位符只占一行，截断处挂省略号。 */
.proj-say::placeholder { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
@media (max-width:640px) {
  .proj-drawer .drawer-head { padding:10px 12px 10px 16px; }
  .proj-drawer .drawer-sub { display:none; }
  .proj-drawer .drawer-head > .btn:not(:last-child) { display:none; }   /* 全宽 sheet 上「展开工作区」无事可做 */
  .proj-drawer-body { padding:4px 16px 16px; }
  .proj-drawer .proj-composer { padding:8px 12px calc(8px + env(safe-area-inset-bottom, 0px)); }
  .proj-drawer .proj-say { min-height:44px; }
  .proj-run-pin { padding:6px 12px; }
  .proj-run.pinned .proj-run-state { display:none; }
}

/* AI 制作助手 第二轮（2026-10-08 老板：「还是乱七八糟的」）：不是再缩，是让一屏只剩
   「对话 + 下一步」。抬头一行；正文去掉重复的东西（顶部那行「此刻在做」和对话流末尾
   那行是同一份数据、钉卡和对话里同一批的卡）；字号统一成三档：正文 14、标题 13/600、
   其余说明 12。 */
.proj-drawer .drawer-head { min-height:0; padding:10px 24px; align-items:center; }
.proj-drawer .drawer-head h2 { font-size:16px; }
.proj-drawer .drawer-sub { display:none; }
.proj-drawer .proj-status-chip { font-size:12px; }
.proj-drawer #wbProjAction { display:none; }
.proj-drawer .proj-task-cards:empty { display:none; }
.proj-drawer .proj-live-box { gap:0; }
.proj-drawer .proj-chat { border-top:0; padding-top:8px; gap:14px; }
.proj-drawer-body { gap:0; }
.proj-drawer .proj-bubble { padding:10px 14px; gap:4px; }
.proj-drawer .proj-bubble-text { line-height:1.7; }
.proj-drawer .proj-run-state, .proj-drawer .proj-run-count, .proj-drawer .proj-send-target,
.proj-drawer .proj-tools .proj-send-target { font-size:12px; }
.proj-run-pin.is-shadowed { display:none; }
.proj-drawer .proj-composer { gap:6px; }
@media (max-width:640px) {
  .proj-drawer .drawer-head { padding:8px 12px 8px 16px; }
  .proj-drawer .proj-chat { gap:12px; }
  /* 输入框是这块的主角：发送键收窄，停止键只留图标宽，别把输入框挤成半截 */
  .proj-drawer .proj-composer-row { gap:6px; }
  .proj-drawer .proj-send { padding:8px 14px; }
  .proj-drawer .proj-pause { min-width:36px; padding:0; }
}

/* 国家 · 母语（自动识别） */
.locale-field { grid-column: span 2; }
.locale-row { display: flex; align-items: center; gap: 8px; min-height: 34px; }
.locale-value { font-size: 15px; font-weight: 500; }
.locale-edit { display: flex; gap: 8px; margin-top: 6px; align-items: center; }
.locale-edit .select { max-width: 160px; }
.locale-note { font-size: 12px; color: var(--text-2); margin-top: 4px; }
.locale-note.warn { color: #b26a00; }
.locale-note button { margin-left: 6px; }

/* ==========================================================================
   Archetype 标签：角色页「人物设定」最上面可增删，总览卡上跟在名字后面。
   一两个词，轻量灰底小签，不抢名字。
   ========================================================================== */
.arch-tags {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  min-height: 36px; padding: 4px 6px;
  border: 1px solid var(--hair); border-radius: var(--r-md); background: var(--surface);
}
.arch-tag {
  display: inline-flex; align-items: center; gap: 2px; flex: none;
  padding: 2px 8px; border-radius: 999px;
  background: var(--surface-sunken); color: var(--text-2);
  font-size: 12.5px; line-height: 1.5; white-space: nowrap;
}
.arch-tag.is-small { font-size: 11.5px; padding: 0 7px; }
.arch-tag-x {
  border: 0; background: none; font: inherit; cursor: pointer;
  color: var(--text-3); padding: 0 0 0 4px; line-height: 1;
}
.arch-tag-x:hover { color: var(--text); }
.arch-tag-input {
  flex: 1; min-width: 160px; border: 0; outline: none; background: none;
  font: inherit; font-size: 13px; color: var(--text); padding: 4px 2px;
}
.pcard-name-row { display: flex; align-items: center; gap: 6px; min-width: 0; }
.pcard-name-row .pcard-name { min-width: 0; flex: 0 1 auto; }

/* ==========================================================================
   项目四页签（芦嘉怡 2026-10-26）：图片工作台 / 场景工作台
   ========================================================================== */
/* 图片工作台：左主区三段 + 右侧 sticky 的预制朋友圈收集区（栏内样式在 pyq-collect.css） */
.imgwb { display: grid; grid-template-columns: minmax(0, 1fr) 260px; align-items: start; }
/* 生活概念照 / 灵感工作区一次只显示一区，顶上两颗按钮切换（芦嘉怡 10/27 17:58，替掉 15:07 的左右分栏）；
   按钮样式沿用场景工作台的 .scene-steps / .scene-step（下面「场景工作台 v2」那段） */
#paneImageScroll[data-img-zone="picking"] > #zoneSecScratch,
#paneImageScroll[data-img-zone="scratch"] > #zoneSecPicking { display: none; }
.imgwb > .pyq-collect { margin: 16px 18px 16px 0; }
/* 图片工作台右边的预制朋友圈底图投放框（收集区本体在「预制朋友圈」页签）*/
.imgwb > .pyq-mini { margin: 16px 18px 16px 0; position: sticky; top: 12px; display: grid; gap: 10px;
  padding: 14px; border: 1px solid var(--line, #e5e7eb); border-radius: 12px; background: var(--panel, #fff); }
.pyq-mini-head { display: flex; justify-content: space-between; align-items: baseline; font-size: 13px; }
.pyq-mini-head span { color: var(--text-3); font-size: 12px; }
.pyq-mini-drop { border: 1.5px dashed var(--line, #d1d5db); border-radius: 10px; padding: 8px; text-align: center;
  color: var(--text-3); font-size: 12px; line-height: 1.6; transition: border-color .15s, background .15s; }
.pyq-mini-hint { margin: 0; padding: 10px 2px; }
.pyq-mini-drop:not(.is-empty) .pyq-mini-hint { display: none; }
.pyq-mini.is-armed .pyq-mini-drop { border-color: var(--accent, #2563eb); background: rgba(37, 99, 235, .05); }
.pyq-mini.is-over .pyq-mini-drop { border-color: var(--accent, #2563eb); background: rgba(37, 99, 235, .12); color: var(--text-1, #111); }
@media (max-width: 1100px) { .imgwb { grid-template-columns: minmax(0, 1fr) 220px; } }
@media (max-width: 880px) {
  .imgwb { grid-template-columns: minmax(0, 1fr); }
  .imgwb > .pyq-collect, .imgwb > .pyq-mini { margin: 0 12px 16px; order: -1; position: static; }
}
.empty-mini { font-size: 13px; color: var(--text-2); padding: 8px 0 12px; }

/* 照片卡整张可拖（载荷见 app.js projMakeDraggable） */
.zone-grid .dcard[draggable="true"] { cursor: grab; }
.zone-grid .dcard[draggable="true"]:active { cursor: grabbing; }
.dcard.is-dragging { opacity: 0.45; }

/* 场景工作台顶上的步骤条：① 背景 → ② 首尾帧 → ③ 场景视频 */
.scene-steps {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin: 0 0 18px;
}
.scene-step {
  display: inline-flex; align-items: center; gap: 7px;
  border: 0.5px solid var(--hair-strong); border-radius: 999px;
  background: var(--surface); color: var(--text); cursor: pointer;
  padding: 5px 12px 5px 5px; font: inherit; font-size: 13px; font-weight: 500;
}
.scene-step:hover { box-shadow: var(--sh-1); }
.scene-step b, .step-badge {
  display: inline-grid; place-items: center; flex: none;
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--surface-sunken); color: var(--text-2);
  font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums;
}
.scene-step.is-done b { background: var(--accent); color: #fff; }
.scene-step-n { font-style: normal; font-size: 12px; color: var(--text-3); font-weight: 400; }
.scene-step-n:empty { display: none; }
.scene-step-arrow { color: var(--text-3); font-size: 13px; }
.step-sec > .proj-sec-head { align-items: center; }
.step-badge { background: var(--accent); color: #fff; align-self: center; }

/* ① 背景：网格 + 右边一条 sticky 的投放栏（拖距离短：就在背景图旁边） */
.bg-split { display: grid; grid-template-columns: minmax(0, 1fr) 168px; gap: 14px; align-items: start; }
.bg-split-main { min-width: 0; }
.bg-drops {
  position: sticky; top: calc(var(--toolbar-h) + 12px);
  display: flex; flex-direction: column; gap: 10px;
}
.bg-drop {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  min-height: 92px; padding: 10px; text-align: center; cursor: pointer;
  border: 1.5px dashed var(--hair-strong); border-radius: var(--r-md);
  background: var(--surface); color: var(--text);
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.bg-drop:hover, .bg-drop:focus-visible { border-color: var(--accent); outline: none; }
.bg-drop-title { font-size: 13px; font-weight: 600; }
.bg-drop-hint { font-size: 12px; color: var(--text-3); }
/* 开始拖背景图：两块投放区一起亮，告诉人往哪放；拖到上面再亮一档 */
body.bg-dragging .bg-drop { border-color: var(--accent); background: var(--accent-soft); }
.bg-drop.is-over { border-style: solid; border-color: var(--accent); background: var(--accent-soft); box-shadow: 0 0 0 3px var(--accent-soft); }
.bg-drop.is-done { border-style: solid; border-color: var(--green); }
@media (max-width: 880px) {
  .bg-split { grid-template-columns: minmax(0, 1fr); }
  .bg-drops { position: static; flex-direction: row; order: -1; }
  .bg-drops .bg-drop { flex: 1; min-height: 64px; }
}

/* ② 首尾帧末尾：已定专注场景（③ 的入口） */
.pinned-wrap { margin-top: 16px; padding-top: 12px; border-top: 0.5px solid var(--hair); }
.pinned-head { font-size: 13px; font-weight: 600; margin-bottom: 8px; display: flex; gap: 8px; align-items: baseline; }

/* 场景工作台 v2（嘉怡 10-26 13:54）：三步一次只显示一步；步骤条 sticky，翻到哪都能切 */
#paneVideo[data-scene-step="1"] .step-sec:not([data-step="1"]),
#paneVideo[data-scene-step="2"] .step-sec:not([data-step="2"]),
#paneVideo[data-scene-step="3"] .step-sec:not([data-step="3"]) { display: none; }
#paneVideo .step-sec > .proj-sec-head .step-badge { display: none; }   /* 条上已经标了第几步 */
.scene-steps {
  position: sticky; top: var(--toolbar-h); z-index: 6;
  margin: 0 -4px 14px; padding: 8px 4px;
  background: var(--bg, #f5f5f7);
}
.scene-step { padding: 7px 16px 7px 7px; font-size: 14px; }
.scene-step.is-on { border-color: var(--accent); box-shadow: 0 0 0 1.5px var(--accent) inset; color: var(--accent); }
.scene-step.is-on b { background: var(--accent); color: #fff; }

/* 投放区做大；一开始拖背景图，整栏变成醒目的「松手 → …」 */
.bg-split { grid-template-columns: minmax(0, 1fr) 240px; }
.bg-drop { min-height: 150px; gap: 6px; border-width: 2px; }
.bg-drop-title { font-size: 15px; }
.bg-drop-release { display: none; font-size: 14px; font-weight: 600; color: var(--accent); }
body.bg-dragging .bg-drop { min-height: 190px; border-style: dashed; border-color: var(--accent); background: var(--accent-soft); }
body.bg-dragging .bg-drop .bg-drop-hint { display: none; }
body.bg-dragging .bg-drop .bg-drop-release { display: block; }
.bg-drop.is-over { transform: scale(1.02); box-shadow: 0 0 0 4px var(--accent-soft), var(--sh-2, 0 4px 14px rgba(0,0,0,.12)); }
.bg-drop.is-over .bg-drop-release { font-size: 15px; }

/* 松手之后：派了什么、派成没有 */
.bg-drop-log { display: flex; flex-direction: column; gap: 8px; }
.bg-drop-log:empty { display: none; }
.bg-drop-item {
  display: flex; gap: 8px; align-items: flex-start; padding: 8px;
  border-radius: var(--r-md); background: var(--surface); border: 0.5px solid var(--hair-strong);
  font-size: 12px; line-height: 1.45;
}
.bg-drop-item img { width: 44px; height: 44px; object-fit: cover; border-radius: 6px; flex: none; }
.bg-drop-item-body { min-width: 0; }
.bg-drop-item-title { font-weight: 600; font-size: 13px; }
.bg-drop-item-st { color: var(--text-2); }
.bg-drop-item.is-ok { border-color: var(--green); }
.bg-drop-item.is-ok .bg-drop-item-st { color: var(--green); }
.bg-drop-item.is-err { border-color: var(--red, #d33); }
.bg-drop-item.is-err .bg-drop-item-st { color: var(--red, #d33); }
.bg-drop-item-acts { display: flex; gap: 4px; margin-top: 4px; flex-wrap: wrap; }
.bg-drop-item-acts .btn { padding: 2px 8px; font-size: 12px; }

/* 空分组提示里的「制作助手」：看起来是文字链接，点了打开右侧对话侧栏（app.js projHintWithAssistant） */
.wb-hint-link { border: 0; background: none; padding: 0; font: inherit; color: var(--accent, #006ad6); cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
.wb-hint-link:hover { text-decoration-thickness: 2px; }

/* 新建角色弹窗：先选「全新角色 / 已有角色的海外版」（芦嘉怡 10/27 15:07 方案 B） */
.newchar { width: 520px; max-width: 92vw; text-align: left; }
.newchar-cards { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 14px; }
.newchar-card { display: grid; gap: 4px; text-align: left; padding: 12px; border: 1.5px solid var(--line, #e5e7eb); border-radius: 12px; background: #fff; font: inherit; font-size: 13px; line-height: 1.5; color: var(--text-2, #4b5563); cursor: pointer; }
.newchar-card b { font-size: 14px; color: var(--text, #111827); }
.newchar-card.on { border-color: #1d4ed8; background: #eff4ff; }
.newchar-body { display: grid; gap: 10px; }
/* 每一步顶上一句「这一步」（在昀 10/27） */
.step-what { margin: 2px 0 12px; font-size: 13px; line-height: 1.6; color: var(--text-2); }
.step-what b { color: var(--text); }

/* 旧路线（在昀 10/27 14:32）：背景 + 两条旧方法收在工作台最底下，灰色，默认收起，两步都看得到 */
#paneVideo .old-route { margin-top: 28px; border-top: 1px dashed var(--hair-strong); padding-top: 6px; color: var(--text-2); }
#paneVideo .old-route > .proj-sec-head .proj-sec-title { color: var(--text-3); }
#paneVideo .old-route > .proj-sec-head .proj-sec-sub { color: var(--text-3); }
#paneVideo .old-route[data-open="true"] > .proj-sec-body { background: var(--surface-sunken); border-radius: var(--r-md); padding: 12px; filter: saturate(.7); }
#paneVideo .old-route > .proj-sec-head [data-zone-sub] { display: none; }   /* 区名「背景」已经写在副标题里 */
