/* GEO 智能写作工作台 · 基础样式（MVP 单入口，无左侧导航） */
/* UI-019（2026-09-17）：全站「简洁商务风」微调 —— 基础令牌去饱和 + 背景中性化。
   主色由「高饱和电商蓝」改为「沉静藏蓝」（白字对比度 5.17 → 8.32）。
   顺带修掉三处**既有的对比度不达标**（均低于 WCAG AA 4.5:1）：--ok / --warn / --muted。
   改后 --ok 5.02 / --warn 5.02 / --muted 5.84，全部达标。
   P1（v1.126）：上述令牌的**色值本体已迁至 css/tokens.css**（唯一令牌文件，判据 A1）；
   历史色值字面量不再留在本文件，否则 A1 计数归不了零。
   前后色值与判据全过程：docs/UI_OPTIMIZATION_LOG.md UI-019。 */

* { box-sizing: border-box; }

/* 全局隐藏守卫：作者样式的 display 会压过浏览器 UA 的 [hidden]{display:none}，
   曾导致 .overlay{display:flex} 让带 hidden 的 API 设置遮罩从加载起就常驻显示、点关闭无效。
   统一用 !important 兜底，所有 hidden 切换（路由视图/遮罩/组件）都必须真正隐藏。 */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
  font-size: 14px;
}

/* ── 内容宽度分两档（P1 返工-1）────────────────────────────────────────────
   契约 §2.1 写的是「内容宽度 960 → 1240~1440（**含侧栏**）」——「含侧栏」三个字限定了
   这一档的适用对象：**带侧栏的主界面**（侧栏 200 + gap 20 + 内容区），不是所有页面。
     · 带侧栏（渲染出 .dash-wrap 的 dashboard）→ --content-max = 1400
     · 无侧栏（队列 / 设置 / 文章库 / 发布 / 详情…）→ 维持 960
   ⚠ 无侧栏页**必须**维持 960，这不是保守、是防版式被改写：
     队列页那六格表单是按 960 容器逐格算出来的（v1.91 UI-046 / v1.95 UI-050 两次迭代
     才定下"6+3"折行）。实测：容器 960 时 needW7(996px) > availW(894px) ⇒ 必然折行；
     放宽到 1400 后 availW≈1334，996 < 1334 ⇒ **不再折，九格挤进同一行**，
     当年的版式结论当场作废（verify_phase5_p01_queue 的 A2-04 会红，且红得有理）。
   ⚠ 单一来源：两处宽度共用 --page-max，`.topbar-inner` 自动与 `.page` 同档
     ⇒ 无论哪一档，顶栏内容与页面内容的左边缘都严格对齐。
   ⚠ 用 :has() 而不是给每个页面挂 class：渲染出 .dash-wrap 就是"有侧栏"的充要条件，
     多一处 class 就多一处要同步维护的东西（纪律 D6：单一来源）。 */
body:has(.dash-wrap) { --page-max: var(--content-max); }
.page { max-width: var(--page-max); margin: 0 auto; padding: 20px 16px 40px; }

/* ── 通栏深色顶栏（P1 返工-1 · 契约 §4-P1 交付物②「深色顶栏」+ §2.1 外壳令牌）──────
   返工令 §1.1 的根因是「令牌定义了但**没有任何 CSS 属性引用它**」—— 故本规则体必须出现
   var(--shell-*)，判据 A10-1 按**选择器逐条**断言（防"写在注释里也能过"）。
   通栏原理见 index.html 同位置注释：header 已移出 .page，是 body 直接子元素。 */
.topbar {
  background: var(--shell-grad);
  border-bottom: 1px solid var(--shell-border);
  color: var(--shell-fg);
}
.topbar-inner {
  max-width: var(--page-max);   /* 与 .page 同档 ⇒ 顶栏内容与页面内容左右对齐（见 .page 处注释） */
  margin: 0 auto;
  padding: 12px 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.topbar .title { font-size: var(--fs-title); font-weight: 600; letter-spacing: -.01em; color: var(--shell-fg); }

/* 顶栏内的幽灵按钮（P1 返工-1）：默认 `button` 是「白底深字」，为浅色内容区设计，
   直接搬进深色外壳会变成一排亮块。这里换成描边幽灵，悬停用 --shell-hover。
   ⚠ 刻意限定 `.ghost` 而**不写** `.topbar button`：.llm-pill 也是 button，且靠
     `button.llm-pill { border:0; padding:0; background:none }` 抹掉默认样式来伪装成文本；
     用元素通配会以更高特异性把那套重置顶掉、凭空长出一圈边框和一个内边距。 */
.topbar button.ghost {
  background: transparent;
  border-color: var(--shell-border);
  color: var(--shell-fg);
}
.topbar button.ghost:hover {
  background: var(--shell-hover);
  border-color: var(--shell-accent-bar);
  color: var(--shell-fg);
}
/* 顶栏状态条：--muted 是按**浅底**调的音量，深底上读不清，换外壳次级文字色 */
.topbar .llm-pill { color: var(--shell-fg-muted); }

/* OEM 品牌位（PHASE_4 P0-3）：顶部品牌名 + Logo，品牌肉见即可 */
.topbar .title .brand-logo { height: 22px; width: 22px; object-fit: contain; vertical-align: -4px; margin-right: 6px; border-radius: var(--radius-sm); }
.topbar .title .brand-name { display: inline-block; max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; }

.topbar-right { display: flex; align-items: center; gap: 12px; }

.llm-pill { font-size: 12px; color: var(--muted); max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* UI-010：状态条由 span 改为 button（可点直达设置页）。抹掉浏览器默认按钮样式，
   保持与原来 span 完全一致的观感，只多「手型光标 + hover/聚焦下划线」提示可点。 */
button.llm-pill {
  background: none; border: 0; padding: 0; margin: 0;
  font-family: inherit; cursor: pointer;
}
button.llm-pill:hover, button.llm-pill:focus-visible { text-decoration: underline; }
.llm-pill.ok { color: var(--ok); }

/* 顶栏当前用户 + 角色 badge（P2-1 三级代理最低配置版；仅展示，不参与权限判定） */
.user-pill {
  font-size: 12px; padding: 2px 10px; border-radius: var(--radius-pill); white-space: nowrap;
  background: var(--primary-soft, var(--c-blue-06)); color: var(--text, var(--c-blue-22));
  border: 1px solid var(--border, var(--c-blue-11));
}
.user-pill.role-super_admin { background: var(--c-orange-02); color: var(--c-orange-10); border-color: var(--c-orange-05); }
.user-pill.role-agent_l1     { background: var(--c-blue-07); color: var(--c-blue-21); border-color: var(--c-blue-14); }
.user-pill.role-agent_l2     { background: var(--c-green-04); color: var(--c-green-10); border-color: var(--c-green-06); }
.user-pill.role-customer     { background: var(--c-orange-03); color: var(--c-gray-05); border-color: var(--c-orange-06); }
/* v1.137：终端客户胶囊两行制——上行 名字（角色）、下行 到期日（纯日期，无解释文字） */
.user-pill.has-expiry { display: inline-flex; flex-direction: column; align-items: center;
  gap: 0; padding: 3px 12px; line-height: 1.25; white-space: nowrap; }
.user-pill .pill-expiry { font-size: 11px; opacity: .85; }

/* ===== 登录 / 注册页 ===== */
/* P1 返工-1/2（契约 §4-P1 交付物⑤「登录页深色科技版」）：
   首轮把这里**整个漏了** —— 仍是浅灰底 + 白卡片 + 蓝按钮，与 before 无实质差别（返工令 §1.2）。
   现在与深色外壳 / 落地页同一套语言：深底 + 顶部径向光晕 + 深色品牌卡。
   色值全部走令牌（纪律 D6）；深底专用语义色见 tokens.css 的 --shell-ok / --shell-danger。 */
.auth-body {
  background-color: var(--shell-bg);
  background-image: radial-gradient(1100px 520px at 50% -12%, var(--shell-glow), transparent 62%);
  background-repeat: no-repeat;
  min-height: 100vh;
  /* 让**浏览器原生控件**跟着走深色：未选中的复选框、滚动条、autofill 底色。
     accent-color 只管「选中态」，未选中态是 UA 默认的白底方块 —— 深色卡上会是一块突兀的白。
     仅影响 UA 默认渲染，已显式定色的 input/按钮不受影响。 */
  color-scheme: dark;
}
.auth-card {
  max-width: 360px; margin: 12vh auto 0; background: var(--shell-bg-2);
  border: 1px solid var(--shell-border); border-radius: var(--radius-lg); padding: 24px 22px;
  box-shadow: var(--shadow-3); color: var(--shell-fg);
}
.auth-title { font-size: 20px; font-weight: 700; text-align: center; color: var(--shell-fg); }
.auth-sub { color: var(--shell-fg-muted); font-size: 13px; text-align: center; margin: 6px 0 20px; }
.auth-form { display: flex; flex-direction: column; gap: 14px; }
/* 字段标签用 --shell-fg（与输入文字同级），**不能**用 --shell-fg-muted：
   后者与 input::placeholder 同色，标签与提示文字层次不分，扫视时分不清
   "这是字段名"还是"这是示例"。深色 UI 里标签必须一眼可见（实测 15.40:1）。 */
.auth-field { display: flex; flex-direction: column; gap: 6px; font-size: 13px; color: var(--shell-fg); }
.auth-field input {
  padding: 9px 12px; border: 1px solid var(--shell-border); border-radius: var(--radius-sm);
  font-size: 14px; background: var(--shell-hover); color: var(--shell-fg);
}
/* 深色底上的 placeholder：不写这条会继承浏览器默认的深灰，深底上几乎不可见 */
.auth-field input::placeholder { color: var(--shell-fg-muted); opacity: 1; }
.auth-field input:focus { outline: none; border-color: var(--shell-accent-bar); }
.auth-error { color: var(--shell-danger); font-size: 13px; min-height: 18px; }
/* v1.61「记住登录情况」（CONTRACT §7.10）：复选框 + 它的说明。
   沿用既有主题变量（--text / --muted / --primary），不引入新配色；
   说明文字左缩进与复选框对齐（勾选框 15px + gap 8px = 23px）。 */
.auth-remember { display: flex; flex-direction: column; gap: 4px; }
.auth-remember-label { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--shell-fg); cursor: pointer; }
.auth-remember-label input { width: 15px; height: 15px; margin: 0; accent-color: var(--shell-accent-bar); cursor: pointer; }
.auth-remember-hint { font-size: 12px; line-height: 1.5; color: var(--shell-fg-muted); padding-left: 23px; }
/* 说明由 JS 按闲置窗口渲染；万一 JS 未执行则整行不占位（不留一条空白） */
.auth-remember-hint:empty { display: none; }
/* 主按钮：渐变两端对白字分别是 **4.88 / 8.32** ⇒ 渐变上任意一点都 ≥4.5（A4 达标）。
   不直接用纯 --primary 是因为它压在深色卡上偏闷、缺「科技版」的层次。
   ⛔ 注释里**不要写色值字面量**：判据 A1 数的是 src/** 里的色值写法，**注释也算进去**。
      本节已因此红过两次 —— 第二次更讽刺：是在"提醒不要写色值"的这句注释里，
      顺手把令牌的取值复述了一遍。解释规则的注释本身也得守这条规则。 */
.auth-submit {
  padding: 10px; border: none; border-radius: var(--radius-md); background: var(--shell-active-grad);
  color: var(--white); font-size: 15px; font-weight: 600; cursor: pointer;
}
.auth-submit:disabled { opacity: .6; cursor: default; }
.auth-links { text-align: center; margin-top: 16px; font-size: 13px; color: var(--shell-fg-muted); }
.auth-links a { color: var(--shell-accent-bar); text-decoration: none; }

/* ── v1.218 §7.130：登录框下方的「阅读并同意《软件系统使用协议》」+ 勾选框 ──────
   刻意**复用** `.auth-links` 作容器（不新建一个同位置的块）：
   ① 它的居中/字号/前景色正是这一行要的；② 新建容器会让 `.auth-links` 变成死样式
   （本项目对"死样式"有守卫先例，见下方 v1.202 那段）。
   ⛔ 复选框**不是闸门**：按用户口径「点击登录即同意」，未勾也能登录，
      点「登录」时由 auth_page.js 自动把它打上。不许改成"未勾就拦住"。 */
.auth-agree { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; }
.auth-agree input { width: 15px; height: 15px; margin: 0; flex: none; cursor: pointer; accent-color: var(--shell-accent-bar); }

/* ── v1.218 §7.130：《软件系统使用协议》站内页 ─────────────────────────────
   与登录页同一套深色外壳（都挂在 .auth-body 下），内容卡片用**浅色令牌**自持前景，
   不继承外壳的近白正文 —— 与 §7.70「横幅浅色面前景自持」同一道理。 */
.agr-body { padding: 34px 16px 56px; }
.agr-card {
  max-width: 820px; margin: 0 auto; padding: clamp(20px, 4vw, 40px);
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-2); color: var(--text);
}
.agr-title { margin: 0 0 6px; text-align: center; font-size: clamp(20px, 3vw, 26px); color: var(--text); }
.agr-meta { margin: 0 0 22px; text-align: center; font-size: 12.5px; color: var(--muted); }
.agr-h2 { margin: 26px 0 10px; font-size: 16px; color: var(--text); }
.agr-h3 { margin: 16px 0 8px; font-size: 14px; color: var(--text); }
.agr-p { margin: 0 0 10px; font-size: 13.5px; line-height: 1.85; color: var(--muted); }
.agr-list { margin: 0 0 10px; padding-left: 22px; }
.agr-list li { margin: 0 0 8px; font-size: 13.5px; line-height: 1.85; color: var(--muted); }
.agr-foot { margin-top: 28px; display: flex; justify-content: center; }
.agr-ok {
  display: inline-block; padding: 10px 28px; border-radius: var(--radius-md);
  background: var(--shell-active-grad); color: var(--white);
  font-size: 15px; font-weight: 600; text-decoration: none;
}
.agr-ok:hover { filter: brightness(1.06); }

/* ★★ v1.202 §7.115：这里原本是登录 / 注册页「安装到桌面」入口的样式（`.auth-install`），
   已随那枚按钮一并删除（用户 2026-10-04 要求移除界面入口、只保留可安装能力）。
   ⛔ 不要把按钮与这段样式加回来；需要恢复见 `_rollback/v1.202-remove-install-entries-pre/`。 */

/* ===== 数据迁移横幅（阶段2） ===== */
#migration-banner {
  margin: 14px 0; padding: 14px 16px; border: 1px solid var(--border);
  border-radius: var(--radius-md); background: var(--card);
  display: flex; justify-content: space-between; align-items: center;
  gap: 16px; flex-wrap: wrap;
}
#migration-banner .mb-text { font-size: 13px; color: var(--text); flex: 1; min-width: 240px; }
#migration-banner .mb-actions { display: flex; gap: 10px; align-items: center; }
.mb-busy { color: var(--muted); font-size: 13px; }
.mb-err { color: var(--c-red-11); font-size: 13px; }

/* ===== 步骤5：工作台主视图（4 步卡片 + 一键生成） ===== */
.ws-lead { color: var(--muted); font-size: 13px; margin: 0 0 14px; }

/* 细节打磨#5：写文章页头部——左侧说明文案 + 右侧「← 返回首页」按钮（窄屏换行铺满） */
.ws-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px 16px; flex-wrap: wrap; }
.ws-head .ws-lead { flex: 1 1 260px; margin-bottom: 14px; }
.ws-back-home { flex: none; }
@media (max-width: 560px) { .ws-back-home { width: 100%; } }

.steps { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }

.wcard {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--card);
  overflow: hidden;
  transition: box-shadow .15s, border-color .15s;
}
.wcard:hover { border-color: var(--primary); box-shadow: var(--shadow-2); }

.wcard-link { display: block; padding: 12px 16px 10px; text-decoration: none; color: inherit; }

.wcard-head { display: flex; align-items: center; gap: 10px; }
.wcard-no {
  width: 28px; height: 28px; border-radius: 50%; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--primary-soft); color: var(--primary);
  font-weight: 700; font-size: 14px;
}
.wcard-title { font-size: 15px; font-weight: 600; }

.wcard-badge { margin-left: auto; font-size: 12px; padding: 2px 10px; border-radius: var(--radius-pill); }
.wcard-badge.done { background: var(--c-green-01); color: var(--ok); }
.wcard-badge.todo { background: var(--c-gray-01); color: var(--muted); }

.wcard-body { padding-top: 10px; }
.wcard-sum { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; min-height: 20px; }
.wsum-chip {
  font-size: 12px; padding: 2px 8px; border-radius: var(--radius-sm);
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
}
.wsum-chip b { color: var(--muted); font-weight: 500; margin-right: 2px; }
.wsum-empty { color: var(--muted); font-size: 13px; }

.wcard-thumbs { display: flex; gap: 8px; margin-top: 8px; flex-wrap: wrap; }
.wcard-thumbs img {
  width: 52px; height: 52px; object-fit: cover;
  border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--neutral-soft);
}

.wcard-foot { margin-top: 12px; display: flex; justify-content: flex-end; }
.wcard-btn {
  font-size: 13px; font-weight: 500; color: var(--primary);
  padding: 4px 14px; border-radius: var(--radius-sm); border: 1px solid var(--primary);
  background: var(--white); line-height: 1.5;
}
.wcard-link:hover .wcard-btn { background: var(--primary-soft); }

/* —— 一键生成面板 —— */
/* v1.93（UI-048）：`.gen-panel` / `.gen-panel .msg` 两条规则**随工作台的一键生成面板一并删除**
   （删前实测：全项目已无任何元素带 `gen-panel` 类 —— 唯一的使用点就是被客户要求删掉的那块）。
   ⚠️ `.gen-btn` / `.gen-hint` **必须保留**：写作队列页「✍️ 本次写作」仍在用它们（同一个按钮外形）。
   ⚠️ `.gen-panel .msg` 原本还兼着"队列页生成消息的上下边距"——故把那一条边距**原样搬到队列页
   自己的容器上**（`.qw-gen .msg`），避免"删了工作台的规则，顺手把队列页的间距也改了"。 */
.gen-btn { font-size: 16px; padding: 11px 34px; }
.gen-hint { color: var(--muted); font-size: 12px; margin: 10px 0 0; }
.qw-gen .msg { margin-top: 8px; }

/* —— 工作台「写作与生成都在写作队列」指引（v1.93 / UI-048）—— */
/* 这块占的是原来一键生成面板的位置。它**不是**生成入口，是导航（避免"配完 3 步不知下一步去哪"）。*/
.ws-next {
  margin-top: 22px; border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--card); padding: 18px 16px; text-align: center;
}
.ws-next-title { font-weight: 600; }
.ws-next-hint { color: var(--muted); font-size: 12px; margin: 8px 0 12px; }
.ws-next .msg { margin-top: 10px; }

/* 工作台「文章库」入口（刷新后也能找到已生成文章） */
.ws-lib-row { margin-top: 12px; text-align: center; }
.ws-lib-row [data-act="library"] { font-size: 13px; padding: 7px 18px; }

/* —— 第二页（步骤详情） —— */
.step-page-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.step-page-title { font-size: 17px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
.step-page-title .wcard-no { width: 30px; height: 30px; font-size: 15px; }

/* 第二页内的面板通栏展示（base .panel 为 640px 弹窗宽，此处覆盖） */
.step-page > .panel,
.step-page .panel { width: 100%; box-shadow: none; border: 1px solid var(--border); margin-bottom: 16px; }

/* 步骤1 详情页：企业资料表单 */
.s1-form .panel-body { display: grid; gap: 14px; }
.s1-actions { display: flex; justify-content: flex-end; gap: 10px; }
.s1-form .req { color: var(--danger); font-style: normal; font-weight: 500; }
.s1-kn { margin-top: 2px; }

/* 步骤1 字段级「确定」键（企业名称/行业/地域/简介，可确认可修改） */
.field-with-confirm { display: flex; align-items: flex-start; gap: 8px; width: 100%; }
.field-with-confirm > input,
.field-with-confirm > textarea { flex: 1 1 auto; min-width: 0; }
.field-with-confirm > .mini-confirm {
  flex: 0 0 auto; padding: 8px 14px; border: 1px solid var(--border);
  background: var(--primary); color: var(--white); border-radius: var(--radius-sm); white-space: nowrap;
}
.field-with-confirm > .mini-confirm:hover { filter: brightness(1.05); }
.field-with-confirm > textarea + .mini-confirm { align-self: stretch; }

/* —— 开发组件宿主面板（折叠，步骤6-9 迁移后移除） —— */
.dev-panel {
  margin-top: 30px; border-top: 1px dashed var(--border); padding-top: 10px;
  font-size: 12px; color: var(--muted);
  /* UI-019：开发期自检面板从界面上隐藏（**DOM 原样保留**）。
     6 个套件断言 index.html 必须存在 details.dev-panel 与 #selftest
     （verify_step4 / 5 / 6 / 7 / 8 / 9），故只能 CSS 隐藏、不能删 DOM。 */
  display: none;
}
.dev-panel summary { cursor: pointer; user-select: none; padding: 4px 0; }
.dev-panel[open] summary { margin-bottom: 10px; }

button {
  font: inherit;
  cursor: pointer;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text);
  padding: 5px 12px;
}

button.ghost:hover { background: var(--primary-soft); border-color: var(--primary); color: var(--primary); }

.content { padding-top: 16px; }

.placeholder { color: var(--muted); }

.selftest {
  margin-top: 16px;
  padding: 12px 16px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--muted);
  white-space: pre-line;
  line-height: 1.8;
}

.selftest.ok { border-color: var(--ok); color: var(--ok); }
.selftest-title { font-weight: 500; margin-bottom: 6px; color: var(--text); }
.selftest.ok .selftest-title { color: var(--ok); }
.selftest-list { margin: 0; padding-left: 18px; }
.selftest-list li { font-size: 13px; line-height: 1.7; }
.selftest-list li.ok { color: var(--ok); }
.selftest-list li.err { color: var(--danger); }

/* API 设置面板 */
.overlay {
  position: fixed;
  inset: 0;
  /* 细节打磨#2（2026-09-14）：遮罩必须有全站最高层级。
     根因：.overlay 此前无 z-index，而页面内粘性表头(th, z-index:1)、
     图片放大层(z-index:50)等正层级元素会压在遮罩之上——
     表现为「弹层底下透出高亮度表头重影」。60 > 全站最大值 50。 */
  z-index: 60;
  background: rgba(15, 23, 42, .45);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 48px 16px 32px;
  /* 视口过矮时整层可滚，保证面板头部/底部操作永不越出屏幕不可达 */
  overflow-y: auto;
  /* 细节打磨#1（2026-09-14）：滚到头不把滚轮链给底层页面，
     修「API 配置弹层里滚动 → 顶栏名称/角色与角色首页跟着动、遮罩下内容透出似重影」 */
  overscroll-behavior: contain;
}

/* 弹层打开期间锁死底层页面滚动（细节打磨#1，2026-09-14）。
   根因：弹层未锁 body 滚动，滚轮穿透(scroll chaining)到底层页面。
   :has() 现代浏览器全支持；纯 CSS 方案，不动任何 JS，撤回=删这两条规则。 */
body:has(.overlay:not([hidden])) { overflow: hidden; }

/* 遮罩内的弹窗面板：约束在视口高度内，头部与底部固定、中部表单区内部滚动 */
.overlay .panel {
  max-height: calc(100vh - 80px);
  display: flex;
  flex-direction: column;
}
.overlay .panel-head,
.overlay .panel-foot { flex: none; }
.overlay .panel-body { overflow-y: auto; overscroll-behavior: contain; }

.panel {
  width: 640px;
  max-width: calc(100vw - 32px);
  background: var(--card);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-3);
  overflow: hidden;
}

.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
}

.panel-title { font-size: 15px; font-weight: 600; }

.panel-body { padding: 14px 16px; display: grid; gap: 10px; }

.field { display: grid; gap: 5px; }
.field > span { color: var(--muted); font-size: 13px; }

.field input,
.field select {
  font: inherit;
  padding: 7px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--card);
  color: var(--text);
}

.field input:focus,
.field select:focus { outline: 2px solid var(--primary-soft); border-color: var(--primary); }

.field .hint { color: var(--muted); font-size: 12px; font-style: normal; }
/* ★ v1.220 §7.132：`.field .hint.warn` 与 `.model-line`（下拉候选那一行的布局）已随
   「模型名改手动填写」一并删除 —— 它们同时失去唯一使用点，留着就是**死样式**
   （`--warn` 令牌本身仍被别处使用，故只删规则、不删令牌）。 */

/* 忙碌态（保存/测试中）：禁用 + 明确视觉反馈，避免被误认为"点了没反应" */
.panel-foot button[disabled] { opacity: 0.55; cursor: not-allowed; }
.panel-foot button.is-busy { opacity: 0.85; }

.field-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }

.panel-foot {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  padding: 12px 16px;
  border-top: 1px solid var(--border);
  background: var(--surface-3);
}

button.primary { background: var(--primary); border-color: var(--primary); color: var(--white); }
button.primary:hover { background: var(--primary-hover); }
button.danger { color: var(--danger); border-color: var(--danger-border); }
button.danger:hover { background: var(--c-red-01); border-color: var(--danger); }

.msg { font-size: 13px; min-height: 18px; }
.msg.ok { color: var(--ok); }
.msg.err { color: var(--c-red-11); }
/* P-65 / P-67（2026-09-21）：中性知情提示（"本次未使用写作队列"、平台名近似提醒）——
   既不是成功也不是错误，但必须**看得见**：此前这两件事都是静默的。 */
.msg.warn { color: var(--warn-text); }

/* ★ v1.224b：企微机器人配置面板的实时连接状态条（让用户直接看到 chatid 是否已获取，
   避免"发了消息没反应"的误判）。仅作用于本面板内的 .conn-status，不改任何共享规则。 */
.conn-status { font-size: 12.5px; line-height: 1.6; padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface-2, rgba(127,127,127,0.06)); color: var(--muted); }
.conn-status code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: var(--text); background: var(--surface-1, rgba(127,127,127,0.1)); padding: 1px 5px; border-radius: 4px; word-break: break-all; }
.conn-status .st { font-weight: 600; }
.conn-status .st.ok { color: var(--ok); }
.conn-status .st.off { color: var(--c-red-11); }

/* ===== 步骤3：企业图库 + 企业知识库（content 内的宿主面板）===== */
.content > .panel {
  width: 100%;
  box-shadow: none;
  border: 1px solid var(--border);
  margin-bottom: 20px;
}

.panel-sub { color: var(--muted); font-size: 12px; margin-top: 2px; }

textarea {
  font: inherit;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--white);
  color: var(--text);
  resize: vertical;
}

/* ===== 蒸馏提示词配置面板（CONTRACT v1.31 可插拔） ===== */
.panel-desc { color: var(--text); font-size: 13px; line-height: 1.6; margin: 0; }
.panel-desc.muted { color: var(--muted); font-size: 12px; }
.field textarea { font: inherit; padding: 7px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--card); color: var(--text); resize: vertical; min-height: 130px; line-height: 1.6; }
.field textarea:focus { outline: 2px solid var(--primary-soft); border-color: var(--primary); }
.slot-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
.slot-grid .slot-field span { color: var(--muted); font-size: 13px; }

/* —— 图库 —— */
.lib-toolbar { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
.lib-toolbar select { font: inherit; padding: 6px 8px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--white); }
.lib-count { color: var(--muted); font-size: 13px; margin-left: auto; }

/* ★ v1.178：上传规则**常驻**一行（客户 2026-10-02：「在上传到图库的时候，就要告知和禁止」）。
   原先规则只写在"库为空"的提示里 —— 库里有图就再也看不到，只能等被拒才知情，属"事后告知"。
   放在工具栏正下方，点「上传图片」之前就在视线内。字号弱化，不与主信息争。 */
.lib-rules { margin: 8px 16px 0; font-size: 12px; color: var(--muted); line-height: 1.6; }
.lib-rules b { color: var(--text); font-weight: 600; }

.lib-msg { margin: 10px 16px 0; padding: 8px 10px; border-radius: var(--radius-sm); font-size: 13px; white-space: pre-line; }
.lib-msg.ok { background: var(--c-green-01); color: var(--ok); }
.lib-msg.err { background: var(--c-red-01); color: var(--danger); }

.lib-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 14px; padding: 16px; }
.lib-card { border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; background: var(--white); display: flex; flex-direction: column; }
.lib-thumb { width: 100%; height: 130px; object-fit: cover; display: block; cursor: zoom-in; background: var(--c-gray-01); }
.lib-meta { padding: 8px 10px 10px; display: grid; gap: 4px; }
.lib-name { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lib-line { display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: 12px; flex-wrap: wrap; }
.lib-url a { font-size: 12px; color: var(--primary); text-decoration: none; }
.lib-url a:hover { text-decoration: underline; }
.lib-card .lib-del { margin: 0 10px 10px; align-self: flex-start; padding: 3px 10px; font-size: 12px; }

.lib-preview {
  position: fixed; inset: 0; background: rgba(15, 23, 42, .78);
  display: flex; align-items: center; justify-content: center; cursor: zoom-out; z-index: 50;
}
.lib-preview img { max-width: 86vw; max-height: 86vh; border-radius: var(--radius-sm); box-shadow: 0 20px 60px rgba(0, 0, 0, .5); }

/* —— 通用小件 —— */
.tag { display: inline-block; padding: 1px 8px; border-radius: var(--radius-pill); background: var(--primary-soft); color: var(--primary); font-size: 12px; }
.empty { color: var(--muted); text-align: center; padding: 28px 12px; font-size: 13px; }
.grid-2col { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

/* —— 知识库 —— */
.kn-form { padding: 14px 16px; border-bottom: 1px solid var(--border); display: grid; gap: 10px; }
.kn-form .field-row { grid-template-columns: 1fr 1.2fr 1fr; }
.kn-actions { display: flex; align-items: center; gap: 10px; }
.kn-msg { font-size: 13px; }
.kn-msg.ok { color: var(--ok); }
.kn-msg.err { color: var(--danger); }

.kn-list { padding: 6px 16px 16px; display: grid; gap: 10px; }
.kn-card { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px 12px; background: var(--white); display: grid; gap: 6px; }
.kn-card-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.kn-card-title { font-weight: 600; }
.kn-card-ent { color: var(--muted); font-size: 12px; }
.kn-card-content { color: var(--c-gray-06); line-height: 1.7; font-size: 13px; white-space: pre-line; max-height: 96px; overflow: hidden; }
.kn-card-actions { display: flex; gap: 8px; justify-content: flex-end; }
.kn-card-actions button { padding: 3px 10px; font-size: 12px; }

/* ===== 步骤4：图库选择器形态（勾选/随机/已选排序条）===== */
.lib-card { position: relative; }
.lib-card.picked { border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary) inset; }
.pk-badge {
  position: absolute; top: 6px; left: 6px; z-index: 2;
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--white); border: 1px solid var(--border);
  color: var(--primary); font-weight: 700; font-size: 13px; line-height: 18px;
  text-align: center; pointer-events: none;
}
.lib-card.picked .pk-badge { background: var(--primary); border-color: var(--primary); color: var(--white); }
.pk-random { white-space: nowrap; }
.pk-count { font-size: 12px; color: var(--muted); margin-left: auto; }
.pk-count.over { color: var(--danger); font-weight: 600; }

.pk-picked-bar { padding: 10px 16px 14px; border-top: 1px solid var(--border); background: var(--c-blue-01); }
.pk-picked-title { font-size: 12px; color: var(--muted); margin-bottom: 8px; }
.pk-picked { display: flex; gap: 8px; flex-wrap: wrap; min-height: 40px; }
.pk-chip {
  position: relative; width: 64px; height: 64px; border-radius: var(--radius-sm);
  overflow: hidden; border: 1px solid var(--border); background: var(--white);
  cursor: grab;
}
.pk-chip.dragging { opacity: .45; }
.pk-chip img { width: 100%; height: 100%; object-fit: cover; display: block; pointer-events: none; }
.pk-chip-x {
  position: absolute; top: 2px; right: 2px;
  width: 18px; height: 18px; padding: 0; line-height: 1;
  border-radius: 50%; border: none; background: rgba(15, 23, 42, .62); color: var(--white);
  font-size: 13px; cursor: pointer;
}
.pk-chip-x:hover { background: var(--danger); }

/* ===== 步骤4：提示词编辑器（变量高亮 overlay）===== */
.pe-body { padding: 14px 16px; display: grid; gap: 10px; }
.pe-wrap { position: relative; }
.pe-input,
.pe-highlight {
  width: 100%; margin: 0; padding: 10px 12px;
  font: inherit; font-family: inherit; font-size: 14px; line-height: 1.7;
  white-space: pre-wrap; word-break: break-all; overflow-wrap: anywhere;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}
/* 叠层修复（复盘 B1）：白底/外框必须画在下层 .pe-highlight 上，
   上层 textarea 背景透明 + 文字透明 + caret 可见，才能透出高亮文字。
   若 textarea 设不透明背景，会把高亮层整层盖住（此前真实浏览器中文字不可见）。 */
.pe-highlight {
  position: absolute; inset: 0; pointer-events: none; overflow: hidden;
  background: var(--white); color: var(--text); border-color: var(--border);
}
.pe-input {
  position: relative; z-index: 1; background: transparent; color: transparent;
  caret-color: var(--text); resize: vertical; min-height: 160px;
}
.pe-input::placeholder { color: var(--muted); opacity: .6; }
.pe-input:focus { outline: 2px solid var(--primary-soft); border-color: var(--primary); }

/* 变量高亮 span（5 色轮换，主题 light） */
.pv { border-radius: var(--radius-sm); padding: 0 2px; font-weight: 500; }
.pv-0 { color: var(--c-violet-02); background: var(--c-violet-01); } /* 紫 */
.pv-1 { color: var(--c-teal-03); background: var(--c-teal-01); } /* 青 */
.pv-2 { color: var(--c-blue-20); background: var(--c-blue-09); } /* 蓝 */
.pv-3 { color: var(--c-orange-08); background: var(--c-orange-04); } /* 橙 */
.pv-4 { color: var(--c-red-11); background: var(--c-red-04); } /* 红 */

/* P1（用户 2026-09-19）：可用变量默认收起 —— 一行开关 + 说明，chips 藏进 .pe-vars[hidden]。
   开关只切换 hidden 与 aria-expanded，不动提示词文本（纯展示层）。 */
.pe-vars-head {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 12px; color: var(--muted);
}
.pe-vars-toggle {
  font: inherit; font-size: 12px; line-height: 1.5; cursor: pointer;
  display: inline-flex; align-items: center; gap: 5px;
  border: none; background: none; color: var(--muted); padding: 0;
}
.pe-vars-toggle:hover { color: var(--primary); }
.pe-vars-caret {
  display: inline-block; width: 0; height: 0; margin-top: 1px;
  border-left: 4px solid currentColor;
  border-top: 4px solid transparent; border-bottom: 4px solid transparent;
  transition: transform .15s ease;
}
.pe-vars-toggle[aria-expanded="true"] .pe-vars-caret { transform: rotate(90deg); }
.pe-vars-tip-open { display: none; }
.pe-vars-toggle[aria-expanded="true"] ~ .pe-vars-tip-idle { display: none; }
.pe-vars-toggle[aria-expanded="true"] ~ .pe-vars-tip-open { display: inline; }
.pe-vars { display: flex; flex-wrap: wrap; gap: 6px; }
.pe-var {
  font: inherit; font-size: 12px; cursor: pointer;
  border: none; border-radius: var(--radius-pill); padding: 3px 10px;
}
.pe-var:hover { filter: brightness(.94); }
.pe-msg { font-size: 13px; min-height: 18px; }
.pe-msg.err { color: var(--danger); }

/* —— 通用小件补充 —— */
.muted { color: var(--muted); }
.small { font-size: 12px; }

/* ===== 步骤7：步骤2 详情页（关键词与长尾词）===== */
.s2-form .panel-body { display: grid; gap: 14px; }
.s2-kw-row { grid-template-columns: 1fr 1fr; }
.s2-kw-row .kw-input,
.lt-manual .kw-input { width: 100%; }

/* 标签输入行：输入框 + 添加按钮 */
.tag-input { display: flex; gap: 8px; align-items: stretch; }
.tag-input .kw-input { flex: 1; min-width: 0; }
.tag-input .kw-add { white-space: nowrap; }

/* 标签容器（已添加词 chips） */
.tag-box { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; min-height: 20px; }
.tag-box:empty { display: none; }

/* 可删除 tag（通用覆盖：纯文字徽标/图库分类不受 inline-flex 影响；chip 场景叠 gap） */
.tag { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border-radius: var(--radius-pill); background: var(--primary-soft); color: var(--primary); font-size: 12px; }
.tag-x { cursor: pointer; font-style: normal; font-size: 11px; opacity: .62; padding: 0 2px; margin-left: -2px; border-radius: 50%; line-height: 1.2; }
.tag-x:hover { opacity: 1; background: rgba(37, 99, 235, .14); }

/* 已选长尾词 tag（含来源徽标） */
.sel-tag { background: var(--white); border: 1px solid var(--primary); color: var(--text); }
.sel-tag .tag-x { color: var(--muted); }
.sel-tag .tag-x:hover { color: var(--danger); background: var(--c-red-04); }
.src { font-style: normal; font-size: 11px; border-radius: var(--radius-pill); padding: 0 6px; line-height: 1.5; }
.src-manual { background: var(--c-blue-05); color: var(--muted); }
.src-distilled { background: var(--c-green-02); color: var(--c-green-08); }

/* ★ v1.176：已选长尾词改为**横向 3 列网格**（用户原话「也采用横向 3 排、纵向排列的形式：
   横向只排 3 排，竖向排列」）。
   原来它用通用的 `.tag-box { display:flex; flex-wrap:wrap }` —— chip 宽度按词长走，
   横向能挤几个挤几个，排出来参差不齐；词一多就是一大片乱麻。
   这里固定 3 列**等宽**：不足 3 个时按实际数量占位（grid 不会凭空造列），
   超过 3 个自动往下开新行 —— 即「横向只排 3 个、竖向排列」。
   特异性 (0,2,0) 稳赢第 631 行通用的 `.tag-box` (0,1,0)：**只影响这一处**，
   同一页的「训练词」「转化词」两个 `.tag-box` 不受牵连（它们仍是流式换行）。 */
.tag-box.sel-box {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px 10px;
}
/* 长词在列内换行，不撑破列宽（否则一个长词会把 3 列顶成「一宽两窄」）。
   注意 chip 的文本节点没有自己的包裹元素（模板是 `✕ + 词 + 徽标`），
   所以 `overflow-wrap` 只能落在 chip 自身上。 */
.tag-box.sel-box .sel-tag { min-width: 0; overflow-wrap: anywhere; }

/* AI 蒸馏区 */
.lt-distill { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 12px; }
.lt-distill .kn-msg { min-height: 18px; width: 100%; }
.lt-distill-hint { font-size: 12px; color: var(--muted); }
.lt-distill-btn:disabled { opacity: .6; cursor: wait; }

/* 蒸馏候选区 */
.cand { margin-top: 12px; border: 1px dashed var(--border); border-radius: var(--radius-sm); padding: 10px 12px; background: var(--c-blue-01); }
.cand-head { font-size: 12px; color: var(--muted); margin-bottom: 8px; }
/* ★ v1.175：候选区改为**网格 3 列 × 每列 10 行**（用户原话「一次蒸馏刚好 30 个，
   可以分三排横向，每排竖向 10 个，刚好 30 个」）。
   实现要点：`grid-auto-flow: column` + `grid-template-rows: repeat(10, …)` ⇒
   按**列优先**填充：先竖着放满第 1 列的 10 个，再开第 2 列 —— 正好是"每排竖向 10 个"。
   数量不足 30 时自动减少列数（≤10 个 = 1 列、11~20 = 2 列），不会留空列。
   同时**取消**原来的 `max-height: 260px` + 滚动：30 个一屏排得下，不需要滚动条
   （原来 30 个挤在一列 926px 里、只能看到最上面 9 个 —— 用户说的"用不着这样排列"）。 */
.cand-list {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(10, minmax(0, auto));
  grid-auto-columns: minmax(0, 1fr);
  gap: 4px 14px;
  max-height: none;
  overflow: visible;
}
.cand-item { display: flex; align-items: center; gap: 8px; padding: 4px 6px; border-radius: var(--radius-sm); cursor: pointer; min-width: 0; }
/* 长词在窄列里换行，不撑破列宽（否则 3 列会被一个长词顶成不等宽的 2 列） */
.cand-item > span { min-width: 0; overflow-wrap: anywhere; }
.cand-item:hover { background: var(--white); }
.cand-item input { margin: 0; }
.cand-item .src { margin-left: auto; }

/* 已选长尾词区 */
.lt-sel { margin-top: 14px; }
.lt-sel-title { font-size: 12px; color: var(--muted); margin-bottom: 8px; }

/* 步骤页底部操作行（保存并返回 / 取消） */
.step-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 4px; }

/* ===== 步骤8：步骤3 详情页（提示词输入——纯粹框架）===== */
.s3-form { display: grid; gap: 16px; }
.s3-editor .panel { margin-bottom: 0; }

/* 收录策略单选（卡片式，点标签即选中） */
.s3-options .panel-body { display: grid; gap: 14px; }
.s3-strategy { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.s3-opt {
  display: flex; gap: 8px; align-items: flex-start;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 10px 12px; cursor: pointer; background: var(--white);
}
.s3-opt:hover { border-color: var(--primary); }
.s3-opt input[type="radio"] { margin-top: 2px; accent-color: var(--primary); }
.s3-opt-main { display: grid; gap: 2px; min-width: 0; }
.s3-opt-label { font-size: 13px; font-weight: 600; color: var(--text); }
.s3-opt-desc { line-height: 1.5; }
.s3-opt:has(input:checked) { border-color: var(--primary); background: var(--primary-soft); }

/* 生成篇数 */
.s3-count input[type="number"] { width: 140px; }

/* ===== 步骤9：步骤4 详情页（配图）===== */
.s4-intro .panel-body { display: grid; gap: 6px; }
/* ★ v1.176（客户实测"这排字看不清楚"）：原值 `var(--c-gray-06)` 是**写死的浅色语义灰**，
   而终端客户页是 `body.neon-home` 深色外壳（见 neon-home.css 的重定向），该令牌
   **不在重定向清单里** ⇒ 深灰字直接压在深底上，真实浏览器量得对比度 **1.98:1**。
   改走 `var(--muted)`：与全站"次级说明文字"同一个口径，深色主题下自动转浅、浅色主题下转深，
   一个令牌同时满足两种外壳（这是本项目"颜色唯一真源"同一条路的延伸，不是为这一页另开色号）。 */
.s4-intro-text { margin: 0; line-height: 1.7; color: var(--muted); }
.s4-intro-text b { color: var(--primary); }
.s4-actions .msg { min-height: 18px; }

/* ===== 写作队列（q-form / q-field，PHASE_5 P0-1；细节打磨#4 放大输入框 → 已被 UI-044 收成小框）=====
   UI-044（客户 2026-09-21）：新建/编辑队列的**六项并排成一行**，输入框收成
   「刚好放得下 8 个汉字」的小框；字段说明改挂标签旁的悬停圆点 `.q-tip`；
   提示词区**面积不变**（客户原话"暂时不变"）。

   UI-045（客户 2026-09-21 第五轮）：**第 7 格「收录策略」加入这一排** —— 客户原话
   "以并列或者下一列的形式排列"，它由「写作文章④页」移来（契约 §7.28），形态是**下拉框**，
   并且折到**第二行**（折行位置怎么来的见下面第 3 条 ★）。

   ★ 格宽 132px 的算式（改字号/内边距/边框时**必须同步重算**，否则"8 个字"不成立）：
       132 − 9×2（内边距） − 1×2（边框） = 112px  =  14px 字号下的 8 个汉字
     （中日韩字形是全角：1 个字 = 1em = 14px。故字号必须是 var(--fs-base) = 14px。）
     ⚠ 此算式只管**六个输入框**：第 7 格是下拉（选，不是填），不参与"8 个字"这条口径。

   ★ 为什么**显式声明 border**（此前这里没有 border 规则，靠浏览器 UA 默认值）：
     真实浏览器量取（`_probe/_queue_row_layout.mjs`）显示 UA 默认边框是 **2px + 2px**，
     而 UA 值**各浏览器不一样** ⇒ 不声明的话，"刚好 8 个字"只是"在 Chrome 上碰巧 8 个字"，
     换浏览器就可能变成 7 个字。声明成 1px 同时让它与全站 `.field input` 同款
     （P0-4 验收标准①「表单全站一致」本来就要求这个）。

   ★ 折行位置是**算出来的，不是碰巧**（UI-045 补算）：
       第一行 6×132 + 5×12(gap) = 852 ≤ 表单可用宽 894（实测）⇒ 六项放得下；
       再加第 7 格 852 + 12 + 132 = 996 > 894 ⇒ **必然**折行，且第二行只有它一格。
     两侧余量分别是 42px / 102px，字号与字体的正常波动不会改变折行位置。
     窄屏靠 flex-wrap 自然折成更多行，不挤成一团。
   ★ `.q-input-lg` / `.q-input-sm` 随 UI-044 **一并删除**：横排后已无任何调用点，
     留着就是"让断言去守死代码"（同 UI-043 删 `platformFilterOptionsHtml` 的先例）。 */
.q-form .q-field { display: grid; gap: 6px; margin-bottom: 14px; }
.q-form .q-label { font-weight: 600; display: flex; align-items: center; gap: 4px; }
/* ⚠ 原有一条 `.q-form input[type="text"], .q-form input[type="number"]`（整行宽 15px 字）
   随本轮**一并删除**：删掉 `.q-input-lg` / `.q-input-sm` 之后，本表单里再没有第二个
   text/number 输入框（提示词区由 createPromptEditor 承载，组件内零 input）
   ⇒ 那条规则已**无任何生效对象**，留着就是"让断言去守死代码"。 */
/* 悬停说明圆点：没有说明的字段不生成它（不给"点开是空的"那种失望）。
   cursor: help 是唯一的可发现性线索 —— 客户看到问号就知道能悬停。 */
.q-form .q-tip {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 15px; height: 15px; border-radius: var(--radius-pill);
  background: var(--neutral-soft); color: var(--muted);
  font-size: 11px; font-weight: 700; line-height: 1; cursor: help;
  -webkit-user-select: none; user-select: none;
}
/* 并排行容器。align-items: flex-end 是**安全网**：万一将来某个标签折成两行，
   输入框仍按**底边**对齐（而不是各自错开一个标签高度）。 */
.q-form .q-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 4px var(--sp-3); margin-bottom: 14px; }
.q-form .q-row > .q-field { flex: 0 0 auto; width: 132px; margin-bottom: 0; }
.q-form .q-row input[type="text"], .q-form .q-row input[type="number"], .q-form .q-row select {
  width: 100%; font-size: var(--fs-base); padding: 7px 9px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  /* UI-046：**显式等高**（32px）。依据（真实浏览器实测，`_probe/_queue_row_layout.mjs`）：
       input 渲染高 32px、select 渲染高 **34px** —— 同一套 padding/font-size 下，
       select 的 UA 内部行盒比 input 高 2px。
       而 `.q-row` 是 `align-items: flex-end`（底对齐）⇒ 第 2 行里「收录策略」与「生成篇数」
       两格的**上沿差 2px**（实测 top=182 vs 184），客户看到两个框一高一矮。
       ⚠ 这不是新增要求：v1.90 那轮就在下面写了"同一排里不许一高一矮"，
         但**声明没被兑现**（只统一了 width/padding/font-size，没管 UA 的高度差异）。
         本轮第 2 行从 1 格变 2 格，这 2px 才第一次显形 ⇒ 一并落实。
       ⚠ 为什么不用 `padding-block: 6px` 去凑：那是把"2px"当常数硬编码，
         UA 一升级就失效；显式 `height` 与 A2-03「边框必须显式声明、不靠 UA 默认」同一条路子。 */
  height: 32px;
  /* ★ v1.176（客户实测"输入框里的字不够醒目"）：补**显式底色 + 字色**。
     理由与下面 select 那条 ① 同源 —— 不声明就会走 UA 默认皮肤，出现两件坏事：
       ① **同一套代码两副面孔**：深色外壳里是 UA 的系统灰底 + 系统白字（实测背景 rgb(59,59,59)），
          浅色外壳里又是白底 + 黑字；而终端客户页正是深色外壳 ⇒ 客户看到的就是那块"系统灰"。
       ② **同一排两种底**：select 早就显式声明了 background/color（走 --card / --text），
          input 却还在吃 UA 皮肤 ⇒ 一排尿得下、一排灰，比"都灰"更难看。
     声明之后两控件同底色、同字色，"变白"由外壳令牌自己决定（深壳转浅、浅壳转深），
     **不为这一页新增色号** —— 与全体 A 组判据同一条路。 */
  background: var(--card); color: var(--text);
}
/* v1.90（UI-045）下拉专属三条：与上面两个输入框共用尺寸规则（**同一排里不许一高一矮**），
   这里只补 select 与 input 的 UA 差异 ——
   ① background/color：select 的 UA 皮肤是灰底系统色，不声明就会在白色表单里显成一块异色；
   ② cursor: pointer：让"这一格是选不是填"有可发现性；
   ③ 色值全走令牌（`--card` / `--text`）—— MARK 之后的十六进制色值会被 P0-4 颜色守卫判红。 */
.q-form .q-row select { background: var(--card); color: var(--text); cursor: pointer; }
.q-form .q-row input::placeholder { color: var(--muted); opacity: 1; }
/* ★ v1.176：placeholder 也要显式声明 —— 空输入框里客户真正看到的就是它（如「点选或自定义」）。
   UA 会**再叠一层默认不透明度**把 placeholder 压暗，光给 color 还不够 ⇒ 显式 `opacity: 1`
   把 UA 那层去掉，只留 --muted 一个变量说话（真实浏览器实测差这 1 层，见契约 §7.74 证据段）。
   为什么颜色走 --muted 而不是 --text：placeholder 是"还没填的示例"，视觉层级必须低于已填的值，
   但仍要够看清（深色外壳下 --muted 实测约 7.5:1）。 */
.q-form .q-row input:focus, .q-form .q-row select:focus { outline: 2px solid var(--primary-soft); border-color: var(--primary); }
/* 提示词粘贴区：面积**不变**（大幅增高 ≈ 20 行可视，纵向可拖拽调整）。
   v1.79：本字段改由通用组件 createPromptEditor 承载（队列不再自造裸 textarea，
   见 CONTRACT 5.2「同一功能只有一套实现」）。★ 叠层不变式：.pe-input 与 .pe-highlight
   必须同字号同内边距，否则高亮层与文字错位（复盘 B1），故尺寸类同时挂在两层上。 */
.q-form .pe-body { padding: 0; }
.q-form .q-prompt-lg { font-size: 15px; padding: 12px 14px; }
/* ★ v1.169（用户 2026-10-02）：提示词区**压缩** ——
   用户原话「粘贴提示词下方有一个较大的空白区域，包含『可用变量』以及一句
   『粘贴该队列使用的提示词全文』的提示。将这个区域的内容压缩，变得更紧凑」。
   真机量测（`_probe/shot_queue.mjs`）：原 textarea 渲染高 **420px** —— 空表时望去就是一大片白；
   其下再叠「可用变量」行(18px) + 提示句(19px) + 组件内边距。三处一起收紧：
     ① 高度 420 → **200**（≈8~9 行，够写；`resize: vertical` 仍在，客户可自行拉高，不锁死）；
     ② 组件内 **行距** 收紧（`gap: 10px → 6px`）。⚠ 这里**只动 gap**：
        本页早有一条 `.q-form .pe-body { padding: 0; }`（就在上面几行），
        **内边距本来就是 0**（最紧凑）—— 若在此处补 padding，反而是把它**撑开**；
     ③ 提示句降到 12px 并与上方贴合（原先它按 14px 渲染、且与组件各占一档间距）。
   ⚠ 不要把 420 改回来 —— 那正是「空表一大片空白」的成因。 */
.q-form textarea.q-prompt-lg { min-height: 420px; }
.q-form .pe-body { gap: 6px; }
.q-form .q-field > .hint { font-size: 12px; line-height: 1.5; }
.q-form .hint { margin: 0; }

/* ★ v1.170（用户 2026-10-02 二次指令，纠正上一轮）：
   用户原话「我说的不是提示词框，而是提示词框下方一直到『创建队列』这里。
   从提示词框下方到『创建队列』这里，只有三行字都没有填满，但是占的空间很大。
   我的意思是把这一段缩小，变得更紧凑，**提示词框还是可以保留那么大**」。
   ⇒ 上一轮把 textarea 压到 200px 是**改错了对象**，已还原为 420px（见 `.q-form textarea.q-prompt-lg`）。
   真机量测（`_probe/measure_gap.mjs`）：从 textarea 底(795) 到「创建队列」按钮顶(971) 共 **176px**，
   而其中**实际文字只有 98px**（还含一个空的消息区），**纯间距吃掉 62px + 一个 44px 的异常行**。
   三处浪费，逐条收掉： */
/* ① 「启用」那行 44px —— 根因：`.q-field` 是 `display:grid`（竖排），而 `.q-inline` 全项目
   **没有任何 CSS**，复选框与说明文字只能上下叠。改为并排（复选框贴左、文字其后）⇒ 44 → ~22px。 */
.q-form .q-inline { grid-template-columns: auto 1fr; align-items: center; gap: 6px; margin-bottom: 4px; }
/* ② 空消息区白占 18px（`.msg` 的 `min-height: 18px` 是为"消息出现时不跳动"，
   但在这一段里它就是一条恒在的空档）。队列表单内改为不占位；消息出现时才撑开。 */
.q-form .msg { min-height: 0; }
/* ③ 提示词那格的 `margin-bottom` 14 → 4（`.q-row > .q-field` 自带 `margin-bottom: 0`，
   特异性更高，横排小框不受影响）。 */
.q-form .q-field { margin-bottom: 4px; }
/* ④ 按钮区上方 4px 也收掉（`.step-actions` 的 `margin-top` 是通用值，这里限定作用域覆盖）。 */
.q-form .step-actions { margin-top: 0; }

/* ===== v1.171 / v1.172 写作队列：「弹出—收起」折叠机制 ===== */
/* ① 说明与新建入口合并成一行：文字在左、按钮在右
      （原来"说明一段 + 整张 420px 表单"上下叠，客户进页先看到一大片空表）。
      特异性 (0,3,0) 是为了稳赢上面的 `.s4-intro .panel-body { display:grid; gap:6px }`。 */
.s4-intro .panel-body.q-new-bar { grid-template-columns: 1fr auto; align-items: center; gap: var(--sp-3); }
/* ② 折叠开关 —— **一条规则管全部折叠块**（v1.172 抽成通用机制）。
      元素只要同时具备「q-collapsible 类 + data-collapsed 属性」即可折叠；
      这样再加第三个折叠块时不必再复制一条选择器（复制出来的规则迟早会漏改一处）。
   ★ 用**自定义属性 + !important**，刻意不复用全局 `[hidden]` 守卫：
     那条守卫被 CSS 语法错误吞掉过三次（CONTRACT §7.76 有完整复盘），
     而这里一旦失效，420px 表单会常驻展开、把「已有队列」整张表顶出首屏 ——
     这段的可靠性不值得挂在别处。 */
.q-collapsible[data-collapsed="1"] { display: none !important; }
/* ③ v1.172：抬头上的条数（收起后只剩这一行，它是"里面有几条"的唯一来源）。 */
.q-list-count { color: var(--muted); font-weight: 400; }

/* ===== 步骤10：一键生成（进度 / 结果 / 卡片高亮）===== */
.wcard.hl {
  border-color: var(--danger, var(--c-red-10));
  box-shadow: 0 0 0 3px rgba(217, 48, 37, 0.12);
}
.gen-progress { margin-top: 10px; display: grid; gap: 6px; }
.gen-bar { height: 6px; border-radius: var(--radius-pill); background: var(--border); overflow: hidden; }
.gen-bar > i { display: block; height: 100%; width: 0; background: var(--primary); transition: width .25s ease; }
.gen-progress-text { font-size: 12px; color: var(--muted); }
.gen-result { margin-top: 10px; display: grid; gap: 8px; justify-items: start; }
.gen-result-head { font-size: 14px; font-weight: 600; color: var(--c-green-09); }
.gen-result-head.has-fail { color: var(--c-red-13); }
.gen-fail-list { margin: 0; padding: 0; list-style: none; display: grid; gap: 4px; width: 100%; }
.gen-fail-item {
  display: grid; grid-template-columns: 64px 120px 1fr; gap: 8px; align-items: baseline;
  font-size: 12px; padding: 6px 8px; border-radius: var(--radius-sm); background: var(--c-red-02); color: var(--c-red-16);
}
.gen-fail-no { font-weight: 600; }
.gen-fail-word { color: var(--c-red-16); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gen-fail-err { color: var(--c-red-16); line-height: 1.5; }

/* ===== 步骤11：生成结果页（P0-18）===== */
.res-page { display: grid; gap: 14px; }
.res-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.res-h2 { margin: 0; font-size: 20px; }
.res-sub { margin: 4px 0 0; color: var(--muted); font-size: 13px; }
.res-head-right { display: flex; gap: 8px; }
.res-list { margin: 0; padding: 0; list-style: none; display: grid; gap: 10px; }
.res-item {
  display: grid; grid-template-columns: 96px 1fr auto; gap: 12px; align-items: center;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-md); padding: 10px 12px;
}
.res-item.fail { background: var(--c-red-02); border-color: var(--c-red-06); }
.res-thumb, .res-nothumb {
  width: 96px; height: 64px; border-radius: var(--radius-sm); overflow: hidden; background: var(--c-blue-08);
  display: flex; align-items: center; justify-content: center; color: var(--muted); font-size: 12px;
}
.res-thumb img { width: 100%; height: 100%; object-fit: cover; }
.res-title { font-size: 15px; font-weight: 600; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.res-badge { font-size: 11px; font-weight: 500; padding: 1px 6px; border-radius: var(--radius-pill); }
.res-badge.ok { background: var(--c-green-05); color: var(--c-green-09); }
.res-badge.fail { background: var(--c-red-02); color: var(--c-red-13); }
.res-meta { margin-top: 4px; display: flex; gap: 12px; flex-wrap: wrap; font-size: 12px; color: var(--muted); }
.res-error { margin-top: 6px; font-size: 12px; color: var(--c-red-13); line-height: 1.6; }
.res-ops { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.res-foot { display: flex; justify-content: center; }
/* 阶段3 序3-3：生成后校验摘要 */
.res-quality { margin-top: 6px; font-size: 12px; display: flex; gap: 8px; flex-wrap: wrap; align-items: baseline; }
.res-quality.ok { color: var(--c-green-09); }
.res-quality.warn { color: var(--c-orange-09); }
.res-quality.err { color: var(--c-red-13); }
.res-quality-detail { color: var(--muted); font-size: 11px; line-height: 1.6; }
.res-sim { margin: 8px 0; font-size: 12px; }
.res-sim .ok { color: var(--c-green-09); }
.res-sim .warn { color: var(--c-orange-09); }
.res-sim-detail { color: var(--muted); font-size: 11px; margin-left: 8px; }

/* ===== 步骤11：文章详情页（P0-19/20/21）===== */
.ad-page { display: grid; gap: 14px; }
/* ★ v1.113：发布任务页把旧「桌面发文助手」面板折叠为 details.panel —— summary 是整条可点，
   去掉面板默认内边距对 summary 的挤压，视觉上与其它 panel 的标题区对齐。 */
.ad-legacy-panel { padding: 0; }
.ad-legacy-panel > summary {
  cursor: pointer; user-select: none;
  padding: 12px 16px; font-size: 13px; color: var(--muted);
}
.ad-legacy-panel[open] > summary { border-bottom: 1px solid var(--border); margin-bottom: 12px; }
.ad-legacy-panel > :not(summary) { padding-left: 16px; padding-right: 16px; }
/* 头部基础形态：返回键在左。
   ⛔ 这一条**不要动**：它虽是"默认形态"，但正因为有它，"某个页面把返回键挪到右上角"才是一件
      **局部**的事（给那一页加个类即可）；改它等于一次性改掉所有 `.ad-head` 页面，
      而且将来新增页面会连带变形、却看不出是谁改的。
   ★ v1.119 / UI-059 现状（注释须与实现一致，漂移过就是债）：
     `.ad-head` 的**两个**使用方（article_detail.js、publish_view.js）现在**都**改用下面的
     `ad-head-back-right` —— 客户 2026-09-27 点名「将返回键安放在右上角，不要安放在左上角」，
     发布任务页由此从"返回键在左"改为"在右"（v1.96/UI-051 时客户只点了文章详情页与预览页）。
     故本规则**当前无使用方**，但**保留**：它是"返回键在左"的默认形态，删掉就等于强制全站
     返回键在右 —— 那是一次范围更大的行为变更，客户没要求（沿用 UI-048 已立口径：移除入口 ≠ 销毁能力）。 */
.ad-head { display: grid; grid-template-columns: auto 1fr; gap: 10px; align-items: center; }
/* v1.96 / UI-051 立、v1.119 / UI-059 增第二使用方：**标题在左、返回键贴右上角**。
   · v1.96 用于「文章详情页」（客户点名那一页）；
   · v1.119 增用于「发布任务页 #/publish」（客户 2026-09-27 第二轮点名："将返回键安放在右上角"）。
   为什么要单开一条而不是改上面那条：见上（`.ad-head` 是共享规则，改它会波及所有使用方）。
   为什么写成 `.ad-head.ad-head-back-right` 双类而不是单类：两者特异度相同（0,1,0）时
   靠"谁写在后面"决定胜负 —— 那是个**看不见的依赖**，将来有人把这条往上挪就会静默失效。
   双类把特异度提到 (0,2,0)，与书写位置无关。
   配合调用方的 DOM 顺序「标题在前、返回键在后」：1fr 列给标题、auto 列给返回键。 */
.ad-head.ad-head-back-right { grid-template-columns: 1fr auto; }
.ad-title { margin: 0; font-size: 22px; line-height: 1.4; }
.ad-info { display: flex; gap: 10px; flex-wrap: wrap; }
.ad-chip {
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-pill);
  padding: 4px 10px; font-size: 12px; color: var(--muted);
}
.ad-chip b { color: var(--text); }
.ad-info-detail { font-size: 12px; color: var(--muted); line-height: 1.8; }
.ad-ops { display: flex; gap: 8px; flex-wrap: wrap; }

/* ★ v1.124（客户 2026-09-27 第二十一轮）：v1.123 那套「📤 导出 ▾」菜单样式
   （.ad-export / .ad-export-btn / .ad-export-menu 共 6 条规则）已**整块删除** ——
   菜单本身从详情页移除了（导出收敛到「👁 排版预览」页），样式留着就是死代码：
   既没有选择器匹配的对象，也会让下一个人误以为这套交互还在用。
   删除依据：「没有对应 DOM 的 CSS 就是负债」，不是"占地方"。 */
.ad-content { padding: 4px 2px 12px; font-size: 15px; line-height: 1.9; color: var(--c-gray-07); }
.ad-content p { margin: 0 0 12px; }
.ad-content img.article-img {
  display: block; max-width: 100%; height: auto; margin: 14px 0;
  border-radius: var(--radius-sm); border: 1px solid var(--border);
}
.ad-images-ops { display: flex; align-items: center; gap: 10px; padding: 0 0 10px; }
.ad-images-hint { font-size: 12px; color: var(--muted); }
.ad-img-list { margin: 0; padding: 0; list-style: none; display: grid; gap: 8px; }
.ad-img {
  display: grid; grid-template-columns: 80px 1fr auto; gap: 12px; align-items: center;
  border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 8px; background: var(--card);
  cursor: grab;
}
.ad-img.dragging { opacity: .5; }
.ad-img-thumb {
  width: 80px; height: 56px; border-radius: var(--radius-sm); overflow: hidden; background: var(--c-blue-08);
  display: flex; align-items: center; justify-content: center;
}
.ad-img-thumb img { width: 100%; height: 100%; object-fit: cover; }
.ad-img-name { font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ad-img-pos { font-size: 11px; color: var(--muted); font-weight: 400; }
.ad-img-alt { display: flex; align-items: center; gap: 6px; margin-top: 6px; font-size: 12px; color: var(--muted); }
.ad-img-alt input { flex: 1; }
.ad-img-ops { display: flex; gap: 6px; }
.ad-picker { border: 1px dashed var(--primary); border-radius: var(--radius-md); padding: 12px; display: grid; gap: 10px; }
.ad-picker-ops { display: flex; gap: 8px; justify-content: flex-end; }
/* 阶段3 序3-2：完整提示词查看面板（P-12 方案A） */
.ad-prompt-panel { margin-top: 12px; }
.ad-prompt-ops { display: flex; gap: 8px; margin-bottom: 10px; }
.ad-prompt-text {
  margin: 0;
  padding: 12px;
  max-height: 420px;
  overflow: auto;
  background: var(--bg, var(--c-blue-02));
  border: 1px solid var(--border, var(--c-blue-12));
  border-radius: var(--radius-sm);
  font-family: ui-monospace, "SFMono-Regular", Consolas, Menlo, monospace;
  font-size: 13px;
  line-height: 1.7;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--text, var(--text));
}
/* 阶段3 序3-3：质量校验清单 */
.ad-quality-panel { margin-top: 12px; }
.ad-quality-ops { display: flex; gap: 8px; margin-bottom: 10px; }
.ad-quality-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.ad-quality-item {
  padding: 8px 12px;
  border: 1px solid var(--border, var(--c-blue-12));
  border-radius: var(--radius-sm);
  font-size: 13px;
  line-height: 1.6;
  background: var(--bg, var(--c-blue-02));
}
.ad-quality-item.ok { color: var(--c-green-09); background: var(--c-green-05); border-color: var(--c-green-07); }
.ad-quality-item.warn { color: var(--c-orange-09); background: var(--c-amber-02); border-color: var(--c-amber-04); }
.ad-quality-item.err { color: var(--c-red-13); background: var(--c-red-02); border-color: var(--c-red-07); }
/* 阶段3 序3-5：相似度检测结果 */
.ad-sim-panel { margin-top: 12px; }
.ad-sim-ops { display: flex; gap: 8px; margin-bottom: 10px; }
.ad-sim-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.ad-sim-item {
  padding: 8px 12px;
  border: 1px solid var(--border, var(--c-blue-12));
  border-radius: var(--radius-sm);
  font-size: 13px;
  line-height: 1.6;
  background: var(--bg, var(--c-blue-02));
}
.ad-sim-item.ok { color: var(--c-green-09); background: var(--c-green-05); border-color: var(--c-green-07); }
.ad-sim-item.warn { color: var(--c-orange-09); background: var(--c-amber-02); border-color: var(--c-amber-04); }
.ad-sim-item.err { color: var(--c-red-14); background: var(--c-red-03); border-color: var(--c-red-08); }
.ad-sim-detail { color: inherit; opacity: .85; }

/* ===== 步骤12：文章库 / 历史列表页（his-*） ===== */
.his-page { max-width: 960px; margin: 0 auto; padding: 18px 16px 40px; }
.his-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 4px; }
.his-head-ops { display: flex; align-items: center; gap: 8px; }
.his-page .pv-help { margin-bottom: 12px; }
.his-h2 { font-size: 20px; margin: 0; }
.his-sub { color: var(--muted); font-size: 13px; margin: 2px 0 14px; }
.his-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
/* UI-052（v1.97）：终端客户文章库卡片紧凑化
   - 7 个元信息字段（字数/训练词命中/转化词命中/长尾词/平台/类型/来源队列）+ 状态 + 时间
     强制单行 nowrap 横向排列；过长字段单独省略号，不撑破换行。
   - 卡片面积较旧版缩小约一半（列宽 34/84→22/34、缩略图 84×60→34×22、内边距 12→6/8、字号收紧）。
   - 状态/时间已并入 .his-meta（history_view.js 单行渲染），不再用独立 .his-sub 行。
   - 注意：.his-sub 为全站共用类（11 个视图使用），本页已不再渲染它，但规则必须原样保留，勿动。 */
.his-item {
  display: grid;
  grid-template-columns: 22px 34px 1fr auto;
  align-items: center;
  gap: 6px;
  padding: 5px 8px;
  border: 1px solid var(--border, var(--c-gray-03));
  border-radius: var(--radius-md);
  background: var(--card, var(--white));
}
.his-no { font-size: 13px; font-weight: 700; color: var(--primary, var(--c-blue-20)); text-align: center; }
.his-thumb { width: 34px; height: 22px; border-radius: var(--radius-sm); overflow: hidden; background: var(--c-blue-04); display: flex; align-items: center; justify-content: center; }
.his-thumb img { width: 100%; height: 100%; object-fit: cover; }
.his-nothumb { font-size: 10px; color: var(--muted); }
.his-main { min-width: 0; display: flex; flex-direction: column; }
.his-title { font-size: 13px; font-weight: 600; margin: 0 0 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.his-meta { font-size: 11px; color: var(--muted); display: flex; flex-wrap: nowrap; white-space: nowrap; align-items: baseline; gap: 8px; overflow: hidden; }
.his-meta > span { overflow: hidden; text-overflow: ellipsis; max-width: 160px; flex: 0 1 auto; min-width: 0; }
.his-meta b { color: var(--text, var(--c-gray-09)); }
.his-sub { font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 10px; margin-top: 4px; }
.his-status { padding: 1px 6px; border-radius: var(--radius-pill); font-size: 10px; }
.his-status.ok { background: var(--c-green-03); color: var(--c-green-11); }
.his-status.fail { background: var(--c-red-04); color: var(--c-red-15); }
/* ★ v1.103（P-96 · 用户 2026-09-24 第四次裁定）：「📦 已发布」档内的**投放去处标注**
   （"已送至百家号草稿箱"）。它不是状态标签 —— 状态仍由 .his-status 说「已发布」；
   它只是把**已有信息**（`publish_tasks.platform`）如实透出（契约 §10.13：不新增状态、不扩写「已发布」）。
   ⚠️ 颜色走令牌而非字面色：本区块**不在** P0-4 守区内（该判据从区块标记扫到文件末尾），
      但"没有人扫的地方"正是最容易长出不一致的地方 —— 新代码不复制历史包袱。
   ⚠️ 宽度：祖先 .his-meta > span 有 max-width，超长会省略号截断；完整去处由 title 提供。 */
.his-placement {
  font-size: 10px; color: var(--muted);
  background: var(--ok-soft); border: 1px solid var(--ok-border);
  border-radius: var(--radius-pill); padding: 1px 6px;
  overflow: hidden; text-overflow: ellipsis;
}
.his-ops { display: flex; flex-wrap: nowrap; gap: 4px; justify-content: flex-end; overflow: hidden; }
.his-ops button { font-size: 11px; padding: 3px 7px; }
.his-ops .danger { color: var(--c-red-12); border-color: var(--c-red-05); }
.his-ops .danger:hover { background: var(--c-red-04); }
.his-pager { display: flex; align-items: center; justify-content: center; gap: 16px; margin-top: 18px; }
.his-pager [data-el="pinfo"] { font-size: 13px; color: var(--muted); }
/* UI-021（v1.69 §7.16）：翻页器。4 个列表（文章库 / 我的下级 / 我的终端客户 / 认证列表）共用
   src/js/ui/pager.js 一套实现，此处只为新增的「跳到第 N 页」补样式——它只在页数多时出现
   （见 pager.js PAGE_JUMP_MIN_PAGES），页数少时整条翻页器都不渲染。 */
.his-pager-jump { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--muted); }
.his-pager-jump input[data-el="pgoto"] { width: 62px; padding: 5px 6px; text-align: center; }
.his-pager-jump button { padding: 5px 10px; }
.his-empty { text-align: center; color: var(--muted); padding: 28px 0; }

/* 发布任务页（阶段6） */
.ad-pub-form { display: flex; gap: 14px; align-items: flex-end; flex-wrap: wrap; margin-top: 10px; }
.ad-pub-field { display: flex; flex-direction: column; gap: 4px; }
.ad-pub-field > span { font-size: 12px; color: var(--muted); }
.ad-pub-field select { padding: 7px 10px; border: 1px solid var(--border, var(--c-gray-04)); border-radius: var(--radius-sm); background: var(--bg, var(--white)); color: var(--fg, var(--c-gray-08)); min-width: 180px; }
.ad-pub-table { width: 100%; border-collapse: collapse; margin-top: 10px; }
.ad-pub-table th, .ad-pub-table td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border, var(--c-gray-02)); font-size: 14px; vertical-align: middle; }
.ad-pub-table th { color: var(--muted); font-weight: 600; }
.ad-pub-table a { color: var(--accent, var(--c-blue-19)); cursor: pointer; text-decoration: none; }
.ad-pub-table a:hover { text-decoration: underline; }
.ad-chip.ok { background: var(--c-green-03); color: var(--c-green-11); }
.ad-chip.err { background: var(--c-red-04); color: var(--c-red-15); }
.ad-chip.warn { background: var(--c-amber-03); color: var(--c-orange-11); }

/* ★ v1.117 平台勾选面板 + 一键「发布今日文章」（发布页第 2 步）
   设计目标（用户口径「有效、快速、简便、美观」）：
   · 31 个平台分 5 组，**每组一行多列**（不像表单那样一列到底）——扫一眼就能对比勾选状态；
   · 分组内固定列宽，标签长短不一也不会把布局拉得高低不齐；
   · 勾选框与文字同一行、整块可点（label 包住 input），点错率低；
   · 强提示（"需先配站点"）就地贴着平台名，不另开一列导致视线跳。
   主题：跟随 light 主题（浅底深字），与全站一致。 */
.ad-pick-bar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin: 10px 0 6px; }
/* ★ v1.119 / UI-059：去掉内层滚动条。分组改成可折叠（下面 `.ad-pick-group` 是 details）之后，
   长度交给"客户自己展开哪几组"控制，不再需要 320px 的固定滚动窗 ——
   双层滚动（列表内一层 + 页面一层）最容易"滑不动 / 滑错层"。 */
.ad-pick-list { border: 1px solid var(--border, var(--c-blue-10)); border-radius: var(--radius-sm); padding: 4px 10px; }
.ad-pick-group { padding: 6px 0; }
.ad-pick-group + .ad-pick-group { border-top: 1px dashed var(--border, var(--c-gray-02)); }
.ad-pick-group-head { font-size: 12px; font-weight: 600; color: var(--muted); padding: 4px 0 6px; display: flex; gap: 8px; align-items: center; }
.ad-pick-group-n { font-weight: 400; color: var(--muted); }
/* ★ v1.119 / UI-059：计数（`n/total`）留在 summary 上 —— 收起时也看得见"这组勾了几个"。
   收起 ≠ 把状态藏起来（"做对了也没人看见"是本项目已立的反面判据）。 */
.ad-pick-group[open] > .ad-pick-group-head { color: var(--text); }
.ad-pick-group-body { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 4px 12px; }
.ad-pick-item { display: flex; align-items: center; gap: 6px; font-size: 14px; padding: 3px 0; cursor: pointer; }
.ad-pick-item > input { flex: none; }
.ad-pick-item > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ad-pick-actions { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-top: 12px; }
.ad-pick-hint { font-size: 12px; color: var(--muted); }

/* ★ v1.125 / UI-063：「一键打开草稿页」工具条（第 3 步·回执面板顶部）。
   与 .ad-pick-bar 同形态（发布页同族视觉），但**不复用其类名**：`.ad-pick-*` 属于"勾选平台"那块，
   语义不同、将来各自演化（复用会让两处被同一条规则绑死）。
   配色一律走 :root 令牌 —— P0-4 之后不许新增零散硬编码颜色。 */
.ad-draft-bar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin: 10px 0 6px; }
.ad-draft-hint { font-size: 12px; color: var(--muted); }
.ad-draft-hint.warn { color: var(--warn-text, var(--warn-text)); }

/* ═══════════════════════════════════════════════════════════════════════════
   ★ v1.119 / UI-059：发布页「三步一条线」的版式收口（客户 2026-09-27 第十九轮）
   ───────────────────────────────────────────────────────────────────────────
   客户原话（逐字）：「整个页面和上下失衡，只居左侧 2/3，这个要解决。」
                    「将返回键安放在右上角，不要安放在左上角。」
                    「各篇文章所占的页面太大了，完全可以缩小；平台 ID 号等信息改成隐藏式。」
                    「要简单、有效、有价值」——面向的是普通操作者，不是技术人员。
   ═══════════════════════════════════════════════════════════════════════════ */

/* ① 满栏（"只居左侧 2/3"的根因）─────────────────────────────────────────────
   `.panel` 的基础规则是 **640px 的弹窗壳**（见 `.panel { width: 640px }`），
   发布页却把它当**内容卡**用 ⇒ 在 960px 的应用外壳里只铺左 640px，
   640 / 960 = 66.7% —— 正好是客户看到的"只居左侧 2/3"，右边 1/3 全空。
   本页按"内容卡"定位，整栏铺满（先例：`.content > .panel { width: 100% }`，步骤页同样处理过）。
   ⛔ 只作用于 `#view-publish`，**不许**改全局 `.panel`：它是 overlay 弹窗的壳体，
      改它会让全站弹窗一起变形。也⛔不许写成 `.ad-page > .panel` ——
      `.ad-page` 的另一个使用方是 `article_detail.js`（文章详情页），那样会顺手改掉
      一个客户没点名的页面（同一类"顺手改到别页"的教训见 `.ad-head` 那次）。 */
#view-publish .ad-page > .panel { width: 100%; max-width: 100%; }

/* ② 可折叠块的**公共形态**（发布页两处沿用同一套写法：步骤面板 / 平台分组）────────
   一处实现、两处使用 —— 免得两条折叠各写一套箭头与字号，改了一处忘了另一处。 */
.ad-fold > summary {
  cursor: pointer; user-select: none; list-style: none;
  display: flex; align-items: center; gap: 8px;
}
.ad-fold > summary::-webkit-details-marker { display: none; }   /* Safari / 旧 Chromium 的三角 */
.ad-fold > summary::before {
  content: '▸'; flex: none; font-size: 11px; color: var(--muted); line-height: 1;
  transition: transform var(--dur, .15s) var(--ease, ease);
}
.ad-fold[open] > summary::before { transform: rotate(90deg); }

/* ③ 「第 1 步 · 绑定浏览器扩展」折叠面板（客户：「这个操作只做一次，是不是能改成收缩式」）
   收起的代价是"里面的东西看不见"，故**状态芯片挂在 summary 上**（见 `.ad-step-state`），
   而不是塞在折叠体里 —— 否则收起就等于把"绑没绑"藏了。 */
.ad-step-panel > summary { padding: 12px 16px; }
.ad-step-panel[open] > summary { border-bottom: 1px solid var(--border); }
.ad-step-panel > .ad-step-body { padding: 12px 16px 14px; }
.ad-step-state { margin-left: auto; font-size: 12px; color: var(--muted); }

/* ④ 顶部一行"今天要做的唯一一件事"（面向普通操作者：先说清要干什么，再让他往下滚） */
.ad-lead {
  margin: 0; padding: 10px 14px; border-radius: var(--radius-md, 8px);
  background: var(--surface-2, var(--c-blue-03)); color: var(--text);
  font-size: 13px; line-height: 1.7;
}
.ad-lead b { color: var(--primary, var(--c-blue-19)); }

/* ⑤ 回执行收紧（客户：「各篇文章所占的页面太大了，完全可以缩小」）──────────────
   P0-4 的统一表格规则把 td 内边距定成 `--sp-2`（8px）、行高 1.6 —— 那对"长表"合适，
   但本页是**回执流水**（一天一条、累积很快），一屏只能看几条。
   这里按 id 提特异度压紧（(1,1,1) > (0,1,1)，与书写位置无关，不会因规则搬家而静默失效），
   **只动内边距与行高**：颜色 / 边框 / 斑马纹 / sticky 表头一律沿用统一规则。 */
#view-publish .ad-pub-table th,
#view-publish .ad-pub-table td { padding: 5px 10px; line-height: 1.4; }
#view-publish .ad-pub-table td { vertical-align: middle; }

/* ⑥ 平台 ID / 任务 ID 收进「详情」（客户：「平台 ID 号等信息改成隐藏式」）────────
   可见面只留**可执行**的东西：去平台草稿箱的链接、失败原因。
   排查故障时要能拿到 ID ⇒ **折叠**而不是删除。
   ⛔ `平台ID：<值>` 必须连续出现（tests/verify_ext_bridge_m2.js 的 R3 按字面断言）。
   ⛔★ 这里**只放图标**，绝不能再放一遍"详情"二字：`<summary>` 自己的文字就是「详情」，
      `::before` 再加一遍会渲染成「ⓘ 详情详情」—— 而**静态断言一条都不会红**
      （它们只判 `<details class="ad-res-detail">` 存在，不判**用户看到几个字**）。
      本批首版就是这么错的，是**真机截图**当场看出来的（《探针 `_probe/_pub_table_probe.mjs`》）；
      现已补 D11 断言把"图标 + 摘要文字"拼起来判**不得重复**。 */
.ad-res-detail { display: inline; }
.ad-res-detail > summary {
  display: inline; cursor: pointer; list-style: none;
  color: var(--muted); font-size: 12px; margin-left: 6px;
}
.ad-res-detail > summary::-webkit-details-marker { display: none; }
.ad-res-detail > summary::before { content: 'ⓘ '; }
.ad-res-detail[open] > summary::before { content: '▾ '; }
.ad-res-id {
  display: block; margin-top: 4px; font-size: 12px; color: var(--muted);
  font-family: ui-monospace, Menlo, Consolas, monospace; word-break: break-all;
}

/* ⑥b 回执行里**不许换行**的两样东西（真机截图看出来的）───────────────────────
   · 状态芯片：`已发布` 在窄列里被折成「已发 / 布」，读起来像两个字两个意思；
   · 「删除」按钮：被折成「删 / 除」，是个点了会误触的窄条。
   两者都是"字都看得见、但看着像坏了"，对普通操作者尤其刺眼。
   ⛔ 只给这两样加 nowrap，**不给整张表加** —— 「结果」列里的失败原因是长句，
      必须允许换行（否则会把表格顶宽、横向滚动条就出来了）。 */
#view-publish .ad-pub-table .ad-chip,
#view-publish .ad-pub-table button { white-space: nowrap; }

/* ⑦ 自建站分组的"怎么配"说明（客户：「这里没有说明怎么配置，点击配置后打个勾就可以了吗？」）
   答案是不能 —— 这句话必须**写进面板让客户看见**，不能只留在代码注释/文档里。 */
.ad-pick-note {
  grid-column: 1 / -1; margin: 4px 0 2px; padding: 8px 10px;
  border-left: 3px solid var(--warn, var(--c-amber-05)); border-radius: var(--radius-sm);
  background: var(--warn-soft, var(--c-amber-01)); color: var(--text);
  font-size: 12px; line-height: 1.7; white-space: normal;
}
.ad-pick-note b { color: var(--warn-text, var(--c-orange-11)); }

/* ⑧ 面板头改为**上下堆叠**（标题一行、说明一行）───────────────────────────────
   `.panel-head` 的基础规则是 `flex + space-between`（标题左、说明右）—— 那是给
   **640px 弹窗**定的：弹窗里两层各是一句短话，左右并排正好。
   本页把它当内容卡用（满栏 928px），说明句变长 ⇒ 左右并排会把**标题挤成两行**
   （"…一键发布今 / 日文章"），说明也被压成窄柱 —— 字都看得见，但读起来费劲。
   客户对普通操作者的要求是「简单、有效、有价值」⇒ 宽面板下改成上下堆叠：
   标题独占一行（15px 粗体，一眼扫到），说明在下一行（12px 灰，想细看再看）。
   ⛔ 只作用于 `#view-publish` 的**直接**内容卡：`.panel-head` 是共享规则，
      改它会让全站弹窗的头部一起变形。 */
#view-publish .ad-page > .panel > .panel-head {
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}

/* 发布预览页（PHASE_2_REVISED P0-3 / P1-2）
   纯净区 .pv-pure 只呈现「标题 + 正文 + 图片」，供 Wechatsync（文章同步助手）等扩展抓取；
   工具条 / 提示 / 说明面板一律在纯净区之外，且工具条带 [data-pv-ignore] 便于断言排除。 */
#view-preview { display: grid; gap: 12px; }

/* ★ v1.98 / UI-053：「干净页」外壳隐藏 —— 客户实测扩展抓到了产品名「GEO 智能写作工作台」，
   根因是单入口契约下顶栏与自检区跟预览页**同屏共存**（路由只切 #view-* 段，从不碰外壳）。
   进入预览页时 preview_view 给 body 挂 pv-mode，这里把外壳整块藏掉；离开时由 teardown 摘类还原。
   ⛔ 只允许在 `body.pv-mode` 下生效：不加限定会把顶栏在**全站**藏掉（那就是把预览页的补丁
      打成了"全站没有导航"的事故）。断言见 tests/verify_preview.js [D] 组。 */
body.pv-mode .topbar,
body.pv-mode .dev-panel,
body.pv-mode #migration-banner,
body.pv-mode #broadcast-banner { display: none !important; }

/* ★ v1.98 / UI-053：纯净区在 **DOM 中排首位**（让抓取器先遇到 <h1> 文章标题，见 preview_view.js），
   视觉上靠 grid item 的 `order` 压回工具条**下方**。`order` 只改绘制次序、不改 DOM 次序，
   所以 `querySelector` / 抓取器看到的仍是"正文在前"。
   ⛔ 不要删这条，否则页面会变成"正文在最上面、工具条在文章末尾"（返回键跑到页尾）。 */
#view-preview > .pv-pure { order: 1; }

.pv-toolbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 10px 12px;
}
/* ★ v1.98 收口：工具条按钮的内边距**在这里收窄**（5px 12px → 5px 9px）。
   为什么必须收：本工具条是「标题 + 操作组 + 返回键」同排布局，靠 `margin-left:auto`
   把返回键推到右上角。一旦总宽超出，返回键会**掉到第二行**（`verify_ui` 的真机几何断言
   会红：backL=646 < opsR=667）。
   实测（`_probe/_probe_pv_toolbar.mjs`，2026-09-23，视口 758px）：
     5 个操作按钮 533.46 + 4 处 8px 间距 32 = 操作组 565.47
     标题 65 + 10 + 操作组 565.47 + 10 + 返回键 69.92 = 720.39，而可用内宽只有 706
     ⇒ **差 14.39px**（有滚动条时可用宽度还会再少约 15px）⇒ 必须腾出余量。
   ⛔ 不要把这条删掉或改回 12px：加了第 5 个按钮（「🚀 一键预填」）之后，本工具条的
      **同排容量只剩约 40px 余量**，正是靠这条撑住的。再要加按钮请先跑一次上面的探针。 */
.pv-toolbar button { padding: 5px 9px; }
.pv-toolbar-title { font-size: 13px; color: var(--muted); }
.pv-toolbar-ops { display: flex; gap: 8px; flex-wrap: wrap; }
/* v1.96 / UI-051：把「靠右」的着力点从操作组**挪到返回键**上 ——
   标题贴左，操作组与返回键一起被推到右边，返回键在最右（右上角）。
   ⛔ 不要两头都写 margin-left:auto：flex 会把剩余空间**均分**给两个 auto 边距，
      操作组与返回键之间会凭空多出一道空档。
   ⛔ 也不要把它留在 .pv-toolbar-ops 上：窄屏（≤720px）操作组整行换行后，
      返回键会**独自落到下一行的左端**，与"右上角"直接矛盾。 */
.pv-toolbar > [data-act="back"] { margin-left: auto; }

/* ★ P1 返工-4：导出下拉的**浮层**（返工令 v1.5 §二-返工4 · 修法①）。
   为什么必须用绝对定位、不能内联展开：本工具条是 flex-wrap 布局，
   内联展开会把"标题 + 操作组 + 返回键"重新挤到第二行 —— 那正是本次要修的病。
   绝对定位让浮层**不参与同排宽度计算** ⇒ 返回键稳定留在右上角，与按钮多少无关。
   z-index: 30 要盖过下方 .pv-pure 白纸区（后者未设 z-index，自然在下）。
   ⚠ 收起靠 [hidden]，而 [hidden] 的全局守卫带 !important ⇒ 能压过这里的 display:flex。 */
.pv-export { position: relative; display: flex; align-items: center; }
.pv-export-list {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 30;
  display: flex; flex-direction: column; gap: 4px;
  min-width: 152px; padding: 6px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-md); box-shadow: var(--shadow-3);
}
.pv-export-list button { text-align: left; width: 100%; }

/* 纯净区：白纸式阅读版式，居中限宽、无阴影、无边框 */
.pv-pure {
  background: var(--card); border-radius: var(--radius-md);
  max-width: 760px; width: 100%; margin: 0 auto;
  padding: 40px 44px 56px;
}
.pv-title {
  margin: 0 0 22px; font-size: 26px; line-height: 1.45; font-weight: 700;
  color: var(--text); text-align: center;
}
.pv-content { font-size: 16px; line-height: 2; color: var(--c-gray-07); }
.pv-content p { margin: 0 0 14px; }
.pv-content img.article-img {
  display: block; max-width: 100%; height: auto; margin: 16px auto;
  border-radius: var(--radius-sm);
}
.pv-pure .empty { color: var(--muted); text-align: center; padding: 60px 12px; font-size: 14px; }

/* 使用说明面板（P1-2） */
.pv-help { max-width: 760px; width: 100%; margin: 0 auto; box-shadow: none; border: 1px solid var(--border); }
.pv-help-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 12px 16px; border-bottom: 1px solid var(--border); }
.pv-help-list { margin: 0; padding: 14px 20px 6px 38px; display: grid; gap: 12px; font-size: 14px; line-height: 1.7; }
.pv-help-list li b { color: var(--text); }
.pv-help-list li div { color: var(--muted); margin-top: 2px; }
.pv-help-foot { padding: 10px 16px 14px; font-size: 12px; color: var(--muted); border-top: 1px dashed var(--border); }

@media (max-width: 720px) {
  .pv-pure { padding: 24px 18px 36px; }
  .pv-title { font-size: 21px; text-align: left; }
  /* v1.96 / UI-051：`margin-left: 0` 已删 —— 那条是"操作组自己持有 auto 靠右"时代的遗留，
     现在 auto 归返回键所有（见上），留着它会让人误以为操作组仍是靠右的着力点。
     这里只保留 `width: 100%` 的**真实作用**：窄屏把操作组整体换到独立一行。 */
  .pv-toolbar-ops { width: 100%; }
}

/* ============================================================
 * 差异参数展示（P0-4 防重复核心）
 * 目的：让客户一眼看到这一篇和别的篇到底哪里不一样，
 * 不需要理解池子大小或组合编号即可确认系统确实做了区分。
 * ========================================================== */
.ad-div-panel .ad-div-list { margin: 0; padding: 14px 18px 6px; list-style: none; display: grid; gap: 10px; }
.ad-div-panel .ad-div-list li { display: flex; gap: 12px; align-items: flex-start; font-size: 14px; line-height: 1.6; }
.ad-div-panel .ad-div-list li span { flex: 0 0 88px; color: var(--muted); }
.ad-div-panel .ad-div-list li b { flex: 1; color: var(--text); font-weight: 600; }
.ad-div-faq { padding: 0 18px 16px; }
.ad-div-faq-t { font-size: 13px; color: var(--muted); margin-bottom: 6px; }
.ad-div-faq-list { margin: 0; padding-left: 22px; display: grid; gap: 4px; font-size: 14px; line-height: 1.6; color: var(--text); }

/* 结果页差异摘要行 */
.res-div { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; margin-top: 6px; font-size: 12.5px; color: var(--muted); }
.res-div-tag {
  display: inline-block; padding: 1px 8px; border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--accent, var(--c-blue-18)) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent, var(--c-blue-18)) 35%, transparent);
  color: var(--accent, var(--c-blue-18)); font-weight: 600; letter-spacing: .2px;
}

/* ============================================================
 * 统计报表页（PHASE_3 P0-1）
 * 数字卡网格 + 朴素表格。只展示后端给的确定数字，不做图表。
 * ========================================================== */
.stat-page .stat-body { margin-top: 4px; }
.stat-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 12px; margin-bottom: 16px;
}
.stat-card {
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 16px 18px; display: flex; flex-direction: column; gap: 6px;
}
.stat-label { font-size: 13px; color: var(--muted); }
.stat-value { font-size: 26px; font-weight: 700; color: var(--text); line-height: 1.2; }
/* v1.59：数字卡的副说明行。额度类数字光给一个数说不清来历（上限多少、已用多少）——
   副说明必须能放在卡片里，否则只能塞进标题，标题会变得又长又难扫。 */
.stat-hint { font-size: 12px; color: var(--muted); line-height: 1.5; }
.stat-list { padding: 4px 0; }
.stat-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.stat-table th, .stat-table td {
  text-align: left; padding: 9px 12px; border-bottom: 1px solid var(--border);
}
.stat-table th { color: var(--muted); font-weight: 600; background: var(--bg); }
.stat-table tbody tr:hover { background: var(--primary-soft); }
.stat-no { color: var(--muted); width: 34px; }
.stat-t { max-width: 340px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ============================================================
 * 角色首页（PHASE_4 P0-1）
 * 左侧菜单 + 右侧内容，菜单项按角色显隐（逐级授权，前端只隐藏、
 * 判定权在后端）。复用 stat-grid/stat-card/stat-table/panel 等既有样式。
 * ========================================================== */
.dash-wrap { display: flex; gap: 20px; align-items: flex-start; }
/* ── 深色侧栏（P1 返工-1 · 契约 §4-P1 交付物② + §2.1 外壳令牌）────────────────
   全角色共用同一套（菜单项按角色显隐，外壳不分支）—— 返工令 §二-返工1 明确要求
   "不得只在 customer 分支生效"。 */
.dash-sidebar {
  width: 200px; flex: none; align-self: stretch;
  background: var(--shell-grad); border: 1px solid var(--shell-border); border-radius: var(--radius-md);
  padding: 12px 8px;
}
.side-menu { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
/* 给选中项左侧那条 3px 高亮条做定位基准（::before 要相对每个菜单项定位） */
.side-menu li { position: relative; }
.side-link {
  display: block; width: 100%; text-align: left; padding: 10px 14px;
  background: none; border: none; border-radius: var(--radius-sm); cursor: pointer;
  font-size: 14px; color: var(--shell-fg-dim);
  text-decoration: none; /* 客户侧功能键是 <a>，去掉锚点默认下划线（文字下方的白横条） */
}
/* 三态（契约 §2.1：「当前菜单项 = 主色渐变 + 左侧 3px 高亮条」）
   柔白 --shell-fg-dim ｜ 悬停 --shell-hover 底 + 纯白 ｜ 选中 --shell-active-grad + --shell-accent-bar
   ⚠ 常态**不能**用 --shell-fg-muted：A10-2 要求 .side-link 文字亮度 > 0.6，muted 只有 0.364
     （详见 tokens.css 里 --shell-fg-dim 的说明）。 */
.side-link:hover { background: var(--shell-hover); color: var(--shell-fg); }
.side-menu li.active .side-link { background: var(--shell-active-grad); color: var(--shell-fg); font-weight: 600; }
.side-menu li.active .side-link::before {
  content: ''; position: absolute; left: 0; top: 6px; bottom: 6px; width: 3px;
  background: var(--shell-accent-bar); border-radius: 0 2px 2px 0;
}
.dash-main { flex: 1; min-width: 0; }
.dash-main .his-head { margin-bottom: 12px; }
.dash-main .stat-body { margin-top: 4px; }

@media (max-width: 720px) {
  .dash-wrap { flex-direction: column; }
  .dash-sidebar { width: 100%; }
  .side-menu { flex-direction: row; flex-wrap: wrap; gap: 4px; }
  .side-link { width: auto; }
}

/* ============================================================
 * 防塌缩监控页（PHASE_3 P1-1）
 * 三项指标卡 + 告警时可执行引导。复用 stat-grid 网格，卡片加告警态，
 * 处置指引用 panel（与工作台一致），不自动改池、只提示引导。
 * ========================================================== */
.monitoring-page .stat-grid.monitor-grid { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.metric-card {
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 16px 18px; display: flex; flex-direction: column; gap: 8px;
}
.metric-card.is-alert { border-color: color-mix(in srgb, var(--c-red-09) 55%, transparent); }
.metric-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.metric-label { font-size: 13px; color: var(--muted); }
.metric-badge {
  font-size: 11.5px; font-weight: 600; padding: 2px 9px; border-radius: var(--radius-pill); white-space: nowrap;
}
.metric-badge.ok { background: var(--primary-soft); color: var(--primary); }
.metric-badge.alert { background: color-mix(in srgb, var(--c-red-09) 14%, transparent); color: var(--c-red-09); }
.metric-value { font-size: 26px; font-weight: 700; color: var(--text); line-height: 1.2; }
.metric-extra { font-size: 12.5px; color: var(--muted); }
.metric-alert-text {
  font-size: 12.5px; line-height: 1.6; color: var(--text);
  background: color-mix(in srgb, var(--c-red-09) 8%, transparent);
  border-left: 3px solid var(--c-red-09); border-radius: var(--radius-sm); padding: 9px 11px;
}
.monitor-guide .panel-body { padding: 12px 16px; }
.monitor-guide-line { margin: 0; font-size: 13.5px; line-height: 1.7; color: var(--text); }

/* v1.76（P-52）：监控页「下级客户异常明细」—— 超管 / 一级代理可见，逐客户可定位。
   颜色一律走令牌（--danger / --danger-soft / --muted / --border / --card），不写死色值。 */
.mon-sub { margin-top: 16px; }
.mon-sub-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.mon-sub-table th {
  text-align: left; font-weight: 500; color: var(--muted); font-size: 12.5px;
  padding: 8px 10px; border-bottom: 1px solid var(--border);
}
.mon-sub-table td { padding: 9px 10px; border-bottom: 1px solid var(--border); color: var(--text); }
.mon-sub-table tr:last-child td { border-bottom: none; }
.mon-sub-table tr.is-alert td { background: var(--danger-soft); }
.mon-sub-name { font-weight: 500; }
.mon-sub-num { font-variant-numeric: tabular-nums; }
.mon-sub-num.is-alert { color: var(--danger); font-weight: 600; }
.mon-sub-extra { display: block; font-size: 11.5px; color: var(--muted); font-weight: 400; }
.mon-sub-hint {
  margin-top: 12px; padding: 10px 12px; border-radius: var(--radius-sm);
  background: var(--danger-soft); border-left: 3px solid var(--danger);
}
.mon-sub-hint-name { font-size: 13px; font-weight: 500; color: var(--text); margin-bottom: 4px; }
.mon-sub-hint-line { margin: 0 0 4px; font-size: 12.5px; line-height: 1.65; color: var(--text); }
.mon-sub-hint-line:last-child { margin-bottom: 0; }
.mon-sub-ops { margin-top: 12px; }

/* ============================================================
 * 代理管理页（PHASE_3 P0-3）
 * 创建下级表单 + 我的下级列表 + 充值。朴素表单 + 列表，不强依赖 JS 框架。
 * ========================================================== */
.ag-page .ag-panel { margin-bottom: 16px; }
.ag-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px 14px; }
.ag-field { display: flex; flex-direction: column; gap: 5px; }
.ag-field label { font-size: 12.5px; color: var(--muted); }
.ag-field input, .ag-field select {
  height: 36px; padding: 0 10px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--card); color: var(--text); font-size: 14px; outline: none;
}
.ag-field input:focus, .ag-field select:focus { border-color: var(--primary); }
.ag-form-ops { grid-column: 1 / -1; display: flex; justify-content: flex-end; }
.ag-list { margin: 0; padding: 0; list-style: none; }
.ag-list .his-empty { padding: 18px 16px; color: var(--muted); font-size: 14px; }
.ag-child {
  display: flex; align-items: center; gap: 12px; padding: 12px 14px;
  border-bottom: 1px solid var(--border);
}
.ag-child:last-child { border-bottom: none; }
.ag-c-main { flex: 1; min-width: 0; }
.ag-c-name { font-size: 15px; font-weight: 600; color: var(--text); }
.ag-c-meta { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 4px; font-size: 12.5px; color: var(--muted); }
/* v1.58：额度「已用/上限」与创建表单上方的余量提示 */
.ag-c-quota b { color: var(--primary); }
.ag-quota-info { font-size: 12.5px; color: var(--muted); margin: 0 0 10px; }
.ag-field-hint { font-size: 11.5px; color: var(--muted); margin-top: 2px; }
.ag-c-role {
  display: inline-block; padding: 0 8px; border-radius: var(--radius-pill); font-weight: 600;
  background: var(--primary-soft); color: var(--primary);
}
.ag-c-pts { color: var(--text); font-weight: 600; }
.ag-c-ops { flex: 0 0 auto; }
.ag-count { margin-left: 8px; font-size: 12.5px; color: var(--muted); font-weight: 400; }

/* ── v1.186 §7.97：终端客户「查询」栏 ─────────────────────────────────────
   列表是**全量在内存**的（Phase3Service.customers() 一次拉全），查询是纯前端过滤
   （见 customers_view.js 的 filterItems）—— 所以这里只要一排朴素控件，
   不引入任何"搜索组件"、也不需要 loading 态。颜色一律走令牌（不写死色号）。 */
.ag-search { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
.ag-search-input {
  flex: 0 1 300px; min-width: 180px;
  padding: 7px 10px; font-size: 13.5px; font-family: inherit;
  color: var(--text); background: var(--bg);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.ag-search-input:focus { border-color: var(--primary); }
.ag-search-clear { flex: 0 0 auto; }
.ag-search-hit { font-size: 12.5px; color: var(--muted); }

/* ── v1.186 §7.97：客户权益修改面板（增加点数 / 改变有效期）──────────────
   面板复用页面里已有的 .panel / .panel-head / .panel-body 外壳与 .primary / .ghost 按钮，
   这里只补给"两块可编辑区"自己的排版，不另起一套视觉语言。 */
.rt-block { padding: 12px 0; border-bottom: 1px solid var(--border); }
.rt-block:last-of-type { border-bottom: none; }
.rt-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.rt-title { font-size: 14px; font-weight: 600; color: var(--text); }
.rt-cur { font-size: 12.5px; color: var(--muted); }
.rt-form { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rt-lab { font-size: 13px; color: var(--muted); }
.rt-input {
  width: 150px; padding: 7px 10px; font-size: 13.5px; font-family: inherit;
  color: var(--text); background: var(--bg);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.rt-input:focus { border-color: var(--primary); }
.rt-input:disabled { opacity: .55; cursor: not-allowed; }
.rt-hint { margin-top: 6px; font-size: 12px; line-height: 1.6; color: var(--muted); }
.rt-close-row { margin-top: 10px; text-align: right; }

/* ── 重置下级账号面板（v1.48 / UI-004）─────────────────────────────
 * 「👥 代理管理」与「👤 终端客户」两页共用同一模块（child_credentials_panel.js），
 * 故样式也只写这一份。配色刻意与「创建下级」面板区分：创建是常规操作，
 * 重置会立刻改变对方的登录凭据，用主色描边让它在页面上「跳出来」。
 * 全部走设计令牌，深浅主题自动跟随（禁止硬编码色值）。 */
.ag-reset-panel { border-color: var(--primary); }
.ag-reset-panel .panel-title b { color: var(--text); font-weight: 600; }
/* v1.70：「新账号名」右侧的「随机生成」按钮连同它的横排容器 `.ag-reset-inline` 一并删除
 * （用户 2026-09-17 拍板）。输入框回到与「新密码」同款的整行宽度 —— 样式与功能同步下线，
 * 不留死样式（否则下次有人看到这三条会以为"是不是漏了按钮"）。 */
.ag-reset-hint {
  grid-column: 1 / -1;
  padding: 9px 12px; border-radius: var(--radius-sm);
  background: var(--primary-soft); color: var(--muted);
  font-size: 12.5px; line-height: 1.75;
}
.ag-reset-hint strong { color: var(--text); }

/* ============================================================
 * 发布队列页（PHASE_3 P0-4）
 * 左栏：队列列表（缩略图 + 标题 + 摘要，当前项高亮）；
 * 右栏：详情（标题 / 摘要 / 标签 / 正文，附复制按钮）；
 * 底部：上一篇 / 下一篇 + 标记已发布。
 * ========================================================== */
.pq-page { max-width: 1080px; }

.pq-layout {
  display: grid;
  grid-template-columns: 320px 1fr;
  gap: 14px;
  align-items: start;
  margin-top: 8px;
}

/* —— 左栏 · 列表 —— */
.pq-left { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden; }
.pq-list { margin: 0; padding: 0; list-style: none; max-height: 62vh; overflow-y: auto; }
.pq-item {
  display: flex; gap: 12px; align-items: flex-start; padding: 12px 14px;
  border-bottom: 1px solid var(--border); cursor: pointer; transition: background .12s;
}
.pq-item:last-child { border-bottom: none; }
.pq-item:hover { background: var(--primary-soft); }
.pq-item.active { background: var(--primary-soft); box-shadow: inset 3px 0 0 var(--primary); }
.pq-thumb {
  flex: 0 0 64px; width: 64px; height: 48px; border-radius: var(--radius-sm); overflow: hidden;
  background: var(--bg); display: flex; align-items: center; justify-content: center;
}
.pq-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pq-thumb .his-nothumb { font-size: 12px; color: var(--muted); }
.pq-item-main { flex: 1; min-width: 0; }
.pq-item-title { font-size: 14px; font-weight: 600; color: var(--text); line-height: 1.45; }
.pq-item.active .pq-item-title { color: var(--primary); }
.pq-item-summary {
  font-size: 12.5px; color: var(--muted); margin-top: 4px; line-height: 1.55;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.pq-empty-item { padding: 22px 16px; color: var(--muted); font-size: 14px; }

/* —— 右栏 · 详情 —— */
.pq-right { min-width: 0; }
.pq-detail { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-md); padding: 18px 20px; }
.pq-empty { color: var(--muted); text-align: center; padding: 60px 12px; font-size: 14px; }
.pq-d-title { font-size: 19px; font-weight: 700; color: var(--text); line-height: 1.4; margin-bottom: 6px; }
.pq-copy-title { margin-bottom: 14px; display: flex; gap: 8px; flex-wrap: wrap; }
.pq-d-block { margin-top: 14px; padding-top: 14px; border-top: 1px dashed var(--border); }
.pq-d-label { font-size: 12.5px; color: var(--muted); margin-bottom: 6px; }
.pq-summary { font-size: 14px; line-height: 1.7; color: var(--text); }
.pq-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.pq-tag {
  display: inline-block; padding: 2px 10px; border-radius: var(--radius-pill); font-size: 12.5px;
  background: var(--primary-soft); color: var(--primary); font-weight: 600;
}
.pq-muted { color: var(--muted); font-size: 13px; }
.pq-copy-line { margin-top: 8px; display: flex; gap: 8px; flex-wrap: wrap; }
.pq-body {
  margin-top: 10px; padding: 14px 16px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg); font-size: 14px; line-height: 1.8; color: var(--text);
  max-height: 44vh; overflow-y: auto;
}
.pq-body img { max-width: 100%; border-radius: var(--radius-sm); display: block; margin: 6px 0; }

/* —— 底部操作条 —— */
.pq-footer {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  margin-top: 14px; flex-wrap: wrap;
}

/* 复制成功态 */
button.copied { background: color-mix(in srgb, var(--primary) 18%, transparent); color: var(--primary); border-color: var(--primary); }
button.copied:hover { background: color-mix(in srgb, var(--primary) 22%, transparent); }

@media (max-width: 720px) {
  .pq-layout { grid-template-columns: 1fr; }
  .pq-list { max-height: none; }
}

/* ============================================================
 * 差异参数池页（PHASE_3 P1-3）
 * 按行业展示五维素材（叙事切入点 / 个人锚点 / 数据视角 / 语气 / FAQ 池）。
 * 可编辑角色（超管/一级代理）渲染 textarea + 保存；其余只读列表。
 * 五维不足下限时计数徽标标红提醒（仅提示，判定权在后端 POOL_MIN）。
 * ========================================================== */
.dp-page { max-width: 1080px; }
.dp-add { margin-bottom: 16px; }
.dp-add-form { display: flex; flex-direction: column; gap: 8px; }
.dp-add-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.dp-add-row label { font-size: 13px; color: var(--muted); }
.dp-add-row input {
  flex: 1; min-width: 180px; height: 36px; padding: 0 10px; border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: var(--card); color: var(--text); font-size: 14px; outline: none;
}
.dp-add-row input:focus { border-color: var(--primary); }
.dp-add-hint { margin: 0; font-size: 12.5px; color: var(--muted); line-height: 1.6; }

.dp-card { margin-bottom: 16px; }
.dp-card .panel-head { display: flex; align-items: center; gap: 10px; }
.dp-time { margin-left: auto; font-size: 12px; color: var(--muted); font-weight: 400; }
.dp-dims { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 14px; }
.dp-dim { display: flex; flex-direction: column; gap: 6px; }
.dp-dim-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.dp-dim-label { font-size: 13.5px; font-weight: 600; color: var(--text); }
/* v1.149：维度开关内联到每维标签旁（平台池=全局 / 自有池=自身）；不拉伸、紧随标签 */
.dp-dim-head .dp-switch { flex-shrink: 0; margin-left: 2px; }
.dp-dim-hint { font-size: 12px; color: var(--muted); line-height: 1.5; }
.dp-count { font-size: 12px; color: var(--muted); }
.dp-count.short { color: var(--c-red-09); font-weight: 700; }
.dp-items {
  margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 4px;
}
.dp-items li {
  font-size: 13px; color: var(--text); line-height: 1.55; padding-left: 14px; position: relative;
}
.dp-items li::before {
  content: '·'; position: absolute; left: 2px; color: var(--primary); font-weight: 700;
}
.dp-empty { font-size: 12.5px; color: var(--muted); }
.dp-textarea {
  width: 100%; box-sizing: border-box; min-height: 90px; padding: 8px 10px;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg);
  color: var(--text); font-size: 13px; line-height: 1.6; resize: vertical; outline: none;
  font-family: inherit;
}
.dp-textarea:focus { border-color: var(--primary); }
.dp-edit-bar { margin-top: 14px; display: flex; justify-content: flex-end; }
.dp-guide .panel-body { padding: 12px 16px; }
.dp-guide-line { margin: 0 0 6px; font-size: 13.5px; line-height: 1.7; color: var(--text); }
.dp-guide-line:last-child { margin-bottom: 0; }

/* ── v1.148 §7.60 维度开关（开放 / 关闭），v1.149 改为内联到维度输入框旁 ── */
.dp-dim-switch-section { margin-top: 18px; padding-top: 16px; border-top: 1px dashed var(--border); }
.dp-section-title { font-size: 13.5px; font-weight: 700; color: var(--text); margin-bottom: 12px; }
.dp-switch-list { display: flex; flex-direction: column; gap: 10px; }
.dp-switch-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.dp-switch-label { font-size: 13.5px; color: var(--text); }
.dp-switch {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 3px 10px 3px 4px; min-width: 78px; justify-content: space-between;
  border: 1px solid var(--border); border-radius: 999px; background: var(--switch-off);
  cursor: pointer; font-family: inherit; font-size: 12.5px; color: var(--muted);
  transition: background .15s, border-color .15s, color .15s;
}
.dp-switch .dp-switch-knob {
  width: 16px; height: 16px; border-radius: 50%; background: var(--white);
  box-shadow: 0 1px 2px rgba(0,0,0,.2); flex-shrink: 0; transition: transform .15s;
}
.dp-switch .dp-switch-text { line-height: 1; }
.dp-switch.on {
  background: var(--primary); border-color: var(--primary); color: var(--on-primary);
}
.dp-switch.off { background: var(--switch-off); border-color: var(--border); color: var(--muted); }
.dp-switch:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* ── PHASE_4 P0-2 授权管理页（permissions_view.js） ── */
.pm-page { max-width: 1080px; }
.pm-panel { margin-bottom: 16px; }
.pm-child-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px;
}
.pm-child {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--card); color: var(--text); font-size: 13.5px; cursor: pointer;
  transition: border-color .15s, box-shadow .15s; text-align: left; font-family: inherit;
}
.pm-child:hover { border-color: var(--primary); }
.pm-child.selected { border-color: var(--primary); background: var(--primary-soft); box-shadow: 0 0 0 1px var(--primary); }
.pm-child-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pm-child-role { font-size: 12px; color: var(--muted); flex-shrink: 0; }
.pm-features { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 8px; }
.pm-feature {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 14px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--card);
}
.pm-feature.on { border-color: var(--primary); background: var(--primary-soft); }
.pm-f-info { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pm-f-label { font-weight: 600; font-size: 13.5px; }
.pm-f-hint { font-size: 12px; color: var(--muted); }
/* 功能权限开关（朴素 toggle，纯 CSS 轨道 + 滑块） */
.pm-switch { position: relative; display: inline-block; width: 42px; height: 24px; flex-shrink: 0; cursor: pointer; }
.pm-switch input { opacity: 0; width: 0; height: 0; position: absolute; }
.pm-track {
  position: absolute; inset: 0; border-radius: var(--radius-pill); background: var(--c-blue-15); transition: background .2s;
}
.pm-track::before {
  content: ''; position: absolute; left: 3px; top: 3px; width: 18px; height: 18px;
  border-radius: 50%; background: var(--white); transition: transform .2s; box-shadow: 0 1px 2px rgba(0,0,0,.2);
}
.pm-switch input:checked + .pm-track { background: var(--primary); }
.pm-switch input:checked + .pm-track::before { transform: translateX(18px); }
.pm-switch.disabled { cursor: not-allowed; opacity: .5; }

/* ── 品牌设置页（OEM 贴牌，CONTRACT v1.38）─────────────────────────────
   与授权管理页同族（.his-page 壳体）；右侧实时预览顶栏品牌位的最终效果，
   让「下级自动继承什么样」在保存前肉眼可见（不靠想象）。 */
.bv-page { max-width: 1080px; }
.bv-panel { margin-bottom: 16px; }
.bv-grid { display: grid; grid-template-columns: minmax(0, 1fr) 260px; gap: 20px; align-items: start; }
@media (max-width: 880px) { .bv-grid { grid-template-columns: 1fr; } }
.bv-form { display: grid; gap: 14px; }
.bv-ops { display: flex; gap: 10px; align-items: center; }
/* 细节打磨#3：品牌素材 URL 输入框 + 上传按钮同行（窄屏自动换行，按钮不被挤扁） */
.bv-url-row { display: flex; gap: 8px; align-items: stretch; }
.bv-url-row input[type="text"] { flex: 1; min-width: 0; }
.bv-url-row button { flex: none; white-space: nowrap; }
@media (max-width: 560px) { .bv-url-row { flex-wrap: wrap; } .bv-url-row input[type="text"] { flex-basis: 100%; } }
.bv-preview { display: grid; gap: 8px; }
.bv-preview-tip { font-size: 12px; color: var(--muted); }
.bv-preview-box {
  display: flex; align-items: center; gap: 8px;
  min-height: 60px; padding: 16px;
  border: 1px dashed var(--border); border-radius: var(--radius-md); background: var(--card);
}
.bv-preview-box img { height: 22px; width: 22px; object-fit: contain; border-radius: var(--radius-sm); }
.bv-preview-box .brand-name { font-size: 17px; font-weight: 600; color: var(--text); }
.bv-scope { font-size: 12px; color: var(--muted); }


/* ── 消息广播（PHASE_5 P0-2，CONTRACT v1.39）────────────────────────────
   两部分：
     ① #broadcast-banner 客户侧横幅 —— 常驻内容区顶部，按级别配色，逐条可关；
     ② .bc-* 广播管理页表单/表格 —— 与写作队列页同族（.step-page 壳体 + .stat-table）。
   本阶段先给「可见、可读、可关闭」的最小形态；统一设计令牌与更细的间距/反馈
   在 P0-4 深度界面美化里收口（不在此处另起一套配色，沿用 :root 变量）。 */
.bc-stack { display: grid; gap: 10px; margin-bottom: 16px; }
.bc-item {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 12px 14px;
  border: 1px solid var(--border); border-left-width: 4px; border-radius: var(--radius-sm);
  background: var(--bc-banner-info);
  /* 前景色自己定（tokens.css --bc-banner-fg）：横幅是浅色面，未设文字色时必须是深字；
     否则在深色主题下会继承近白的页面正文 → 浅底白字「看不见」（2026-10-02 修）。 */
  color: var(--bc-banner-fg);
}
.bc-item .bc-badge {
  flex: 0 0 auto; font-size: 12px; font-weight: 600; line-height: 20px;
  padding: 0 8px; border-radius: var(--radius-pill); background: var(--primary-soft); color: var(--primary);
}
/* 流动容器：文字/图片/按钮同排流动，空间不足自动换行到下一排（用户 2026-09-30 要求） */
.bc-flow { flex: 1 1 auto; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; min-width: 0; }
.bc-txt { font-size: 13.5px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; }
.bc-item .bc-close {
  flex: 0 0 auto; border: none; background: transparent; cursor: pointer;
  color: var(--bc-banner-fg); opacity: .6; font-size: 14px; line-height: 1; padding: 4px 6px; border-radius: var(--radius-sm);
}
.bc-item .bc-close:hover { background: var(--bg); color: var(--text); }
/* 级别配色：自 2026-10-01 起级别【不再强制整条染色】，仅保留左侧 4px 描边作细微语义提示；
   整条底色完全由发送端「横幅背景色」取色器自由控制（banner_color 以 inline style 覆盖）。
   级别只决定徽标文字与徽标实色（信息蓝 / 警告橙 / 成功绿）。背景默认中性浅蓝（--bc-banner-info），
   由下方 .bc-item / .bc-canvas 基础规则提供；旧消息无 banner_color 时统一显示该中性色。 */
.bc-banner--info { border-left-color: var(--primary); }
.bc-banner--warn { border-left-color: var(--c-orange-07); }
.bc-banner--success { border-left-color: var(--ok); }
.bc-banner--info .bc-badge { background: var(--primary); color: var(--on-primary); }
.bc-banner--warn .bc-badge { background: var(--warn); color: var(--on-primary); }
.bc-banner--success .bc-badge { background: var(--ok); color: var(--on-primary); }
/* 文字大小（与后端 FONT_SIZES 同源）：medium 用 .bc-txt 默认，故只定义 small/large。
   双类选择器压过 .bc-txt 的默认 font-size（同特异性、靠后出现）。 */
.bc-txt.bc-fs-sm { font-size: 13px; line-height: 1.5; }
.bc-txt.bc-fs-lg { font-size: 18px; line-height: 1.7; }

/* ── 发送端：统一「所见即所得」编辑器（文字/图片/链接放进同一条流，编辑区即客户最终样子） ── */
.bc-composer { display: grid; gap: 10px; }
.bc-composer-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.bc-composer-bar .ghost { font-size: 12.5px; padding: 5px 12px; }
.bc-composer-label { font-size: 12.5px; color: var(--muted); }
/* 同排内联控件（文字大小/文字颜色与「+ 文字/+ 图片/+ 链接」同一排）：紧凑、不占满宽度 */
.bc-composer-bar .bc-composer-sep { width: 1px; height: 18px; background: var(--border); margin: 0 2px; }
.bc-composer-bar .bc-inline-ctl { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--muted); }
.bc-composer-bar .bc-inline-ctl select {
  width: auto; font-size: 12.5px; padding: 4px 8px; font-family: inherit;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--card); color: var(--text);
}
/* ── 取色色板（取代原生 <input type="color">，用户 2026-10-02 拍板）────────────
   原生取色器有 色相滑条 + 饱和度/明度方块 + RGB 数字框 多套控件：使用者"拉了滑条、
   数字没变、颜色也没变"，且方块停在发白位置时会产出**淡粉**（实测被存下的
   R253/G211 这一类的浅粉即此来路）。⇒ 改为固定色板，**点一下就定色**：
   无滑条、无数字、无二次确认。色块底色由 JS 从 BROADCAST_COLOR_PALETTE 内联下发
   （色值的唯一来源在 models/constants.js，本文件不写任何色值）。 */
.bc-swatch-row { display: inline-flex; align-items: center; gap: 5px; }
.bc-swatch {
  width: 20px; height: 20px; padding: 0; cursor: pointer;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  /* 双描边：内白 + 外黑。保证**纯黑**色块在深色主题面板上、
     **纯白**色块在浅色主题面板上，都始终有可见轮廓（单靠边框无法同时满足两端）。
     用 rgba 而非 #hex —— 判据 A1 只数 #hex。 */
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .5), 0 0 0 1px rgba(0, 0, 0, .35);
}
.bc-swatch:hover { border-color: var(--primary); }
.bc-swatch.is-on { outline: 2px solid var(--primary); outline-offset: 1px; border-color: var(--primary); }
/* ── 「更多选择」开关 + 更多档（用户 2026-10-04 指令）────────────────────────────
   基础档 6 色照旧摊在外面（用户认熟的那一排）；更多档放在 .bc-swatch-extra 里，**默认折叠**。
   ⛔ 折叠为什么用 :not(.is-open) 而不是给元素加 hidden 属性：
      .bc-swatch-extra 需要 display:inline-flex 才能横排，而 CSS 的 display **优先级高于**
      UA 样式表里的 [hidden]{display:none} ⇒ 加了 hidden 也照样显示。
      症状是"还没点开关就已经展开"，且这种 bug 只在浏览器里看得见、单测看不见 ——
      故折叠条件写成一条**可被断言**的类选择器，默认态 = 没有 is-open。 */
.bc-swatch-more {
  height: 20px; padding: 0 7px; cursor: pointer; white-space: nowrap;
  font-size: 12px; line-height: 18px; font-family: inherit;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--card); color: var(--muted);
}
.bc-swatch-more:hover { border-color: var(--primary); color: var(--primary); }
.bc-swatch-row .bc-swatch-extra {
  display: inline-flex; align-items: center; gap: 5px;
  margin-left: 3px; padding-left: 8px; border-left: 1px solid var(--border);
}
.bc-swatch-row:not(.is-open) .bc-swatch-extra { display: none; }

/* ── 配色可读性提示（用户 2026-10-04 选定的 A 案：不推翻"显式色优先"，只实时告知代价）──
   三档语义色走浅色主题的既有令牌；深色外壳下的提亮由 neon-home.css 第 16 节覆盖
   （与第 12~15 节同一手法：本文件写浅色主题的正确值，外壳只做重定向）。 */
.bc-contrast-hint {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin: 8px 0 0; font-size: 12.5px; line-height: 1.6; color: var(--muted);
}
.bc-contrast-hint.is-ok { color: var(--ok); }
.bc-contrast-hint.is-warn { color: var(--warn-text); }
.bc-contrast-hint.is-bad { color: var(--danger); }
/* 修复键：只给一个动作、不做自动改写。hover 用**与主题无关**的半透明灰，
   而不是某个 tint 令牌 —— 免得又踩"浅色 tint 漏进深色外壳"那一类（本文件顶部第 12~15 节）。 */
.bc-contrast-fix {
  font-size: 12px; line-height: 18px; padding: 2px 10px; cursor: pointer; white-space: nowrap;
  font-family: inherit; border: 1px solid currentColor; border-radius: var(--radius-pill);
  background: transparent; color: inherit;
}
.bc-contrast-fix:hover { background: rgba(127, 127, 127, .18); }
/* 画布：外观与客户横幅 .bc-item 同源（边框/圆角/底色/级别描边），编辑时即所见 */
.bc-canvas {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 12px 14px;
  border: 1px solid var(--border); border-left-width: 4px; border-radius: var(--radius-sm);
  background: var(--bc-banner-info);
  /* 与 .bc-item 同源：画布也是浅色面，前景自己定，不继承深色主题的正文色 */
  color: var(--bc-banner-fg);
}
.bc-canvas .bc-badge {
  flex: 0 0 auto; font-size: 12px; font-weight: 600; line-height: 20px;
  padding: 0 8px; border-radius: var(--radius-pill); background: var(--primary-soft); color: var(--primary);
}
.bc-canvas .bc-flow { flex: 1 1 auto; min-width: 0; }
.bc-canvas-empty { margin: 0; font-size: 12.5px; color: var(--bc-banner-fg); opacity: .65; }
/* 画布内单个可编辑块；工具条（上移/下移/删除）平时半透明，hover/聚焦显形 */
.bc-cblock { position: relative; display: inline-flex; align-items: center; gap: 6px; }
.bc-cblock-tools { display: inline-flex; gap: 3px; opacity: .25; transition: opacity .12s; }
.bc-cblock:hover > .bc-cblock-tools, .bc-cblock:focus-within > .bc-cblock-tools { opacity: 1; }
.bc-mini { border: 1px solid var(--border); background: var(--card); color: var(--text); border-radius: var(--radius-sm); cursor: pointer; font-size: 11px; line-height: 1; padding: 2px 6px; }
.bc-mini:hover { background: var(--bg); }
.bc-mini:disabled { opacity: .4; cursor: not-allowed; }
.bc-block-x { border: none; background: transparent; color: var(--muted); cursor: pointer; font-size: 12px; padding: 2px 5px; border-radius: var(--radius-sm); }
.bc-block-x:hover { background: var(--danger-soft); color: var(--danger); }
/* 文字块：直接内联可编辑，外观等同客户 .bc-txt */
.bc-cblock--text .bc-txt { outline: 1px dashed transparent; border-radius: 4px; padding: 1px 3px; min-width: 2ch; }
.bc-cblock--text .bc-txt:focus { outline: 2px dashed var(--primary); background: transparent; }
.bc-cblock--text .bc-txt:empty:before { content: attr(data-placeholder); color: var(--muted); }
/* 图片块：未选图显示「选择图片」按钮；选图后显示图片（点图可重选）+ 右上角删除叉（复用 .bc-img-x） */
.bc-img-pick { border: 1px dashed var(--border); background: var(--bg); color: var(--muted); border-radius: var(--radius-sm); cursor: pointer; font-size: 12.5px; padding: 6px 12px; }
.bc-img-pick:hover { border-color: var(--primary); color: var(--primary); }
.bc-cblock--image .bc-img-x { top: -8px; right: -8px; }
/* 链接(按钮)块：外观等同客户 .bc-consult，下方留一个编辑用的链接输入框 */
.bc-cblock--btn { flex-direction: column; align-items: flex-start; gap: 4px; }
.bc-cblock--btn .bc-consult { outline: 1px dashed transparent; }
.bc-cblock--btn .bc-consult:focus { outline-color: var(--primary); }
.bc-cblock--btn .bc-consult:empty:before { content: attr(data-placeholder); color: var(--muted); }
.bc-edit-url { width: 100%; max-width: 260px; font-size: 12px; padding: 4px 8px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--card); color: var(--muted); }
.bc-composer-hint { margin: 0; font-size: 12.5px; color: var(--muted); }

.bc-form { display: grid; gap: 14px; }
.bc-field { display: grid; gap: 6px; }
.bc-label { font-size: 12.5px; color: var(--muted); }
.bc-field input[type="text"], .bc-field input[type="url"], .bc-field textarea, .bc-field select, .bc-field input[type="datetime-local"] {
  width: 100%; padding: 8px 10px; font-size: 13.5px; font-family: inherit;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--card); color: var(--text);
}
.bc-field input[type="file"] { width: 100%; font-size: 12.5px; color: var(--muted); }
.bc-field input[type="file"]::file-selector-button {
  margin-right: 8px; padding: 5px 12px; font-size: 12.5px; font-family: inherit; cursor: pointer;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg); color: var(--text);
}
.bc-field textarea { resize: vertical; }
.bc-field.bc-inline { grid-auto-flow: column; justify-content: start; align-items: center; gap: 8px; }
.bc-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 700px) { .bc-grid2 { grid-template-columns: 1fr; } }
.bc-audience-tip { margin: 0; font-size: 12.5px; color: var(--muted); }
/* 图片预览右上角的删除叉：点一下清掉这张图，保留图片块可重选 */
.bc-img-x {
  position: absolute; top: 5px; right: 5px;
  width: 22px; height: 22px; padding: 0;
  border: none; border-radius: 50%;
  background: rgba(0,0,0,0.55); color: var(--on-primary); cursor: pointer;
  font-size: 13px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}
.bc-img-x:hover { background: rgba(0,0,0,0.82); }
.bc-tags { font-size: 11.5px; color: var(--primary); background: var(--primary-soft); padding: 1px 6px; border-radius: var(--radius-pill); white-space: nowrap; }

/* ── 消息栏与顶栏紧挨：抵消 .page 顶部 20px + .content 顶部 16px 内边距 ──
   ★ 2026-10-02（用户第八报「横幅颜色变灰、变浅、变淡」的**结构性根治**）：
   横幅被上面那行负 margin 顶到**顶栏正下方**，而横幅是「所见即所得」的颜色展示位 ——
   **不允许被任何半透明层覆盖**。历史真事故：`body.neon-home .topbar` 曾带一条
   「10px 偏移 + 30px 模糊」的深色向下投影，那层半透明深色整片压在横幅上，
   把发送端选的纯色按「上深下浅」梯度压暗、去饱和（实测横幅自顶约六成亮度渐变回满亮），
   用户观感就是「红的不是纯红、黑的不够黑」。
   故这里给横幅**自建层叠上下文并抬到 z-index:1**：此后任何 in-flow 兄弟的背景/投影
   只能画在它**下面**，从结构上不可能再洗它的色（比"删掉某条投影"更稳，不会再复发）。
   ⛔ 不要把 position/z-index 删掉或改成 0 —— 那等于取消这条保护，该类缺陷会以同样方式复发。 */
#broadcast-banner { margin-top: -36px; position: relative; z-index: 1; }
#broadcast-banner[hidden] { margin-top: 0; }
.bc-image { max-width: 100%; max-height: 180px; border-radius: var(--radius-sm); border: 1px solid var(--border); display: block; }
.bc-consult {
  display: inline-flex; align-items: center; justify-content: center; text-decoration: none; cursor: pointer;
  padding: 7px 16px; font-size: 13px; font-weight: 600; white-space: nowrap;
  border-radius: var(--radius-sm); background: var(--primary); color: var(--on-primary);
}
.bc-consult:hover { filter: brightness(0.95); }
.bc-cell-text { max-width: 420px; }
.bc-cell-window { white-space: nowrap; color: var(--muted); }
.bc-cell-ops { white-space: nowrap; }

/* ── OEM 深度定制（PHASE_5 P0-3，CONTRACT v1.39）────────────────────────
   两部分：
     ① 登录页（login.html / register.html）—— 背景图 + 品牌 Logo；
     ② 品牌设置页的「访客视角」登录预览（.lp-*），与真实登录页外观对齐。
   说明：此处只做「可见、可读」的最小形态；统一设计令牌、间距与响应式
   在 P0-4 深度界面美化里收口（不在此处另起一套配色，沿用 :root 变量）。 */
.auth-body.auth-has-bg {
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  background-attachment: fixed;
  /* 有背景图 = 白卡模式，原生控件回到浅色（与上面 color-scheme: dark 互斥） */
  color-scheme: light;
}
/* 有背景图时给卡片加一层近乎不透明的底 + 描边，保证表单在任何照片上都能读清 */
.auth-body.auth-has-bg .auth-card {
  background: var(--card);
  border-color: rgba(255,255,255,.7);
  box-shadow: 0 10px 40px rgba(0,0,0,.28);
}
/* ⚠ 有背景图时**整卡必须回到浅色语义**（P1 返工-1/2 补）：
   上面这条把卡片底改成了不透明白色，而深色科技版把文字定成了 --shell-fg（近白）——
   浅字压白底 = 完全不可读。故此处按"白卡"重新配一遍文字与控件色，
   与深色版**互斥**、各自自洽：无背景图 → 深色科技版；有背景图 → 浅色白卡（原观感不变）。
   判据 A10-3 只断言"无背景图"的默认态，故不冲突。 */
.auth-body.auth-has-bg .auth-card,
.auth-body.auth-has-bg .auth-title { color: var(--text); }
.auth-body.auth-has-bg .auth-sub,
.auth-body.auth-has-bg .auth-field,
.auth-body.auth-has-bg .auth-remember-hint,
.auth-body.auth-has-bg .auth-links { color: var(--muted); }
.auth-body.auth-has-bg .auth-field input {
  background: var(--card); border-color: var(--border); color: var(--text);
}
.auth-body.auth-has-bg .auth-field input::placeholder { color: var(--muted); }
.auth-body.auth-has-bg .auth-field input:focus { border-color: var(--primary); }
.auth-body.auth-has-bg .auth-remember-label { color: var(--text); }
.auth-body.auth-has-bg .auth-remember-label input { accent-color: var(--primary); }
.auth-body.auth-has-bg .auth-links a { color: var(--primary); }
.auth-body.auth-has-bg .auth-error { color: var(--c-red-11); }
.auth-title { display: flex; align-items: center; justify-content: center; gap: 8px; }
.auth-brand-logo { width: 24px; height: 24px; border-radius: var(--radius-sm); object-fit: cover; }

/* 品牌设置页 · 登录页预览（等比缩小的示意卡，不引入 iframe） */
.bv-login-preview { margin: 8px 0 12px; }
.lp-card {
  position: relative; border-radius: var(--radius-md); border: 1px solid var(--border);
  overflow: hidden; background: var(--bg); padding: 18px 16px;
}
.lp-card.lp-has-bg {
  background-size: cover; background-position: center; background-repeat: no-repeat;
}
/* 有背景图时给内容加一层浅色蒙版，保证预览里的文字依然可读（与真实登录页同理） */
.lp-card.lp-has-bg::before {
  content: ''; position: absolute; inset: 0; background: rgba(255,255,255,.72);
}
.lp-inner { position: relative; display: grid; gap: 8px; justify-items: center; }
.lp-title { display: flex; align-items: center; gap: 6px; font-size: 15px; font-weight: 700; }
.lp-logo { width: 20px; height: 20px; border-radius: var(--radius-sm); object-fit: cover; }
.lp-sub { font-size: 12.5px; color: var(--muted); margin-bottom: 6px; text-align: center; }
.lp-fake-field {
  width: 100%; padding: 7px 10px; font-size: 12.5px; color: var(--muted);
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--card);
}
.lp-fake-btn {
  width: 100%; margin-top: 4px; padding: 8px; text-align: center; font-size: 13px; font-weight: 600;
  color: var(--white); background: var(--primary); border-radius: var(--radius-sm);
}

/* 云存储「生效范围」说明（PHASE_5 P0-3）：把「已生效」讲清楚，避免用户以为是预留功能 */
.oss-scope-hint {
  margin: 0 0 12px; padding: 8px 10px; font-size: 12.5px; line-height: 1.6; color: var(--muted);
  background: var(--bg); border-left: 3px solid var(--primary); border-radius: var(--radius-sm);
}
.oss-scope-hint strong { color: var(--text); }

/* ══════════════════════════════════════════════════════════════════════════
 * P0-4 深度界面美化 · 设计令牌层 + 通用组件层（PHASE_5 §6.2，CONTRACT v1.42）
 *
 * 目标：把「能用」推进到「好看、好用、手机也能看」。
 * 纪律（契约硬约束）：
 *   - 不改业务逻辑、不改后端、**不改任何 id / data-act**（避免破坏既有测试与接线）；
 *   - 新增样式一律走令牌（var(--…)），不在本区块散落硬编码颜色；
 *   - 既有类名（.stat-table / .field / .panel / .his-empty…）**继续有效**，
 *     本层只是「统一它们的观感」并新增通用类（.btn / .card / .badge / .toast /
 *     .empty-state / .data-table / .form-field），新旧可混用、渐进替换。
 * ══════════════════════════════════════════════════════════════════════════ */

/* ── 设计令牌：**本体已迁至 css/tokens.css**（唯一令牌文件，判据 A1）────
   本层及以下全部 `var(--…)` 都从那里取值；不要在本文件重新声明令牌。 */

/* ── 统一交互反馈（hover / active / focus-visible）───────────────────── */
button, .btn, .wcard-btn, a {
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
              color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              transform var(--dur) var(--ease);
}
/* 键盘可达性：只在键盘聚焦时出现焦点环，鼠标点击不出现（不打搅视觉） */
button:focus-visible, .btn:focus-visible, .wcard-btn:focus-visible,
input:focus-visible, select:focus-visible, textarea:focus-visible, a:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}
/* 点击下压 1px：给「按到了」的即时体感（不改变布局尺寸） */
button:not(:disabled):active, .btn:not(:disabled):active { transform: translateY(1px); }
button:disabled { opacity: .55; cursor: not-allowed; }

/* ── 通用按钮语义类（与既有 button.primary / .ghost / .danger 同皮肤）── */
.btn { display: inline-flex; align-items: center; gap: var(--sp-2); justify-content: center; }
.btn-primary { background: var(--primary); border-color: var(--primary); color: var(--on-primary); }
.btn-primary:hover { background: var(--primary-hover); }
.btn-danger { color: var(--danger); border-color: var(--danger-border); }
.btn-danger:hover { background: var(--danger-soft); border-color: var(--danger); }
.btn-sm { padding: var(--sp-1) 10px; font-size: var(--fs-xs); border-radius: var(--radius-sm); }
.btn-block { width: 100%; }

/* ── 卡片（全宽内容卡；.panel 是 640px 弹窗壳体，两者用途不同故分开）── */
.card {
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-1);
  padding: var(--sp-4);
}
.card + .card { margin-top: var(--sp-4); }
.card-title { font-size: var(--fs-lg); font-weight: 600; margin: 0 0 var(--sp-3); }

/* ── 徽标（状态 / 平台 / 角色统一观感）───────────────────────────────── */
.badge {
  display: inline-block; padding: 2px 10px; border-radius: var(--radius-pill);
  font-size: var(--fs-xs); line-height: 18px; white-space: nowrap;
  background: var(--neutral-soft); color: var(--muted);
}
.badge-ok { background: var(--ok-soft); color: var(--ok); }
.badge-warn { background: var(--warn-soft); color: var(--warn-text); }
.badge-err { background: var(--danger-soft); color: var(--danger); }
.badge-info { background: var(--info-soft); color: var(--info); }

/* ── 轻提示 toast（加载 / 成功 / 错误；含统一小转圈）──────────────────── */
@keyframes geo-spin { to { transform: rotate(360deg); } }
.spinner {
  width: 12px; height: 12px; flex: none; border-radius: 50%;
  border: 2px solid currentColor; border-top-color: transparent;
  animation: geo-spin .7s linear infinite;
}
.toast {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3); margin: var(--sp-2) 0;
  border: 1px solid var(--border); border-left-width: 3px; border-radius: var(--radius-md);
  background: var(--card); font-size: var(--fs-sm); color: var(--text);
  box-shadow: var(--shadow-1);
}
.toast-loading { border-left-color: var(--primary); color: var(--muted); }
.toast-ok { border-left-color: var(--ok); }
.toast-err { border-left-color: var(--danger); color: var(--danger); }

/* ── 空状态（统一全站「这里还没有东西」的表达：图标 + 说明 + 可选动作）── */
.empty-state {
  display: grid; gap: var(--sp-2); justify-items: center;
  padding: var(--sp-6) var(--sp-4); text-align: center;
  color: var(--muted); font-size: var(--fs-sm);
}
.empty-state .es-icon { font-size: 26px; line-height: 1; opacity: .75; }
.empty-state .es-text { max-width: 420px; line-height: 1.7; }
.empty-state .es-actions { display: flex; gap: var(--sp-2); margin-top: var(--sp-1); }
/* 既有各页私有的空状态类统一到同一观感（不必逐页改类名） */
.empty, .his-empty, .pq-empty, .dp-empty, .pq-empty-item {
  color: var(--muted); text-align: center; font-size: var(--fs-sm);
}

/* ── 统一表格（此前全站表格无任何样式，是「表格不一致」的根因）───────── */
.table-wrap { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; border-radius: var(--radius-md); }
.stat-table, .ad-pub-table, .data-table {
  width: 100%; border-collapse: separate; border-spacing: 0;
  background: var(--card); font-size: var(--fs-sm);
}
.stat-table th, .ad-pub-table th, .data-table th {
  position: sticky; top: 0; z-index: 1;
  padding: var(--sp-2) var(--sp-3); text-align: left; white-space: nowrap;
  background: var(--surface-2); color: var(--muted); font-weight: 600;
  border-bottom: 1px solid var(--border);
}
.stat-table td, .ad-pub-table td, .data-table td {
  padding: var(--sp-2) var(--sp-3); border-bottom: 1px solid var(--border);
  vertical-align: top; color: var(--text); line-height: 1.6;
}
/* 斑马纹 + 悬停高亮：长表格里追行不费眼 */
.stat-table tbody tr:nth-child(even), .data-table tbody tr:nth-child(even) { background: var(--surface-3); }
.stat-table tbody tr:hover, .ad-pub-table tbody tr:hover, .data-table tbody tr:hover { background: var(--info-soft); }
.stat-table tbody tr:last-child td, .data-table tbody tr:last-child td { border-bottom: none; }

/* ── 表单（.form-* 为通用别名，与既有 .field 同观感，便于新页统一）───── */
.form-field { display: grid; gap: 6px; }
.form-field > span { color: var(--muted); font-size: var(--fs-sm); }
.form-label { color: var(--muted); font-size: var(--fs-sm); }
.form-hint { color: var(--muted); font-size: var(--fs-xs); font-style: normal; line-height: 1.6; }
.form-error { color: var(--danger); font-size: var(--fs-sm); }

/* ── 响应式（PC 为主，手机可看）───────────────────────────────────────
   ≤768：内容单列、按钮组换行铺满、面板内边距收窄；
   ≤560：标题字号下调、顶栏允许换行，避免右侧信息被挤出屏幕。 */
@media (max-width: 768px) {
  .page { padding: var(--sp-4) var(--sp-3) var(--sp-6); }
  .panel { width: 100%; }
  .panel-body { padding: var(--sp-4); }
  .stat-table th, .stat-table td,
  .ad-pub-table th, .ad-pub-table td,
  .data-table th, .data-table td { padding: var(--sp-2); }
  /* 操作按钮组在窄屏换行铺满，避免挤成一列竖排 */
  .his-head, .panel-foot, .step-page-head, .his-head-ops { flex-wrap: wrap; gap: var(--sp-2); }
  .panel-foot button, .his-head-ops button { flex: 1 1 auto; }
  .bc-grid2, .bv-grid { grid-template-columns: 1fr; }
}
@media (max-width: 560px) {
  /* ★ P4 / 判据 D4-5（手机端顶栏可用性）——**修正一条从未生效过的规则**。
     这里原来写的是 `.topbar { flex-wrap: wrap; gap: var(--sp-2); }`，但 `.topbar` 是
     **块级**容器、不是 flex 容器（真正的 flex 容器是 `.topbar-inner`，见本文件上方该规则）。
     于是那两行**从来没有产生过任何效果**，窄屏顶栏一直没有换行余地：
     375 档实测品牌名被折成 **3 行**、「发布 / 登出」被逐字压成**竖排两行**
     （取证：`docs/p4-mobile-shots/*/`，机器判据见 `tests/verify_p4_mobile.js` 的 [D4-5]）。
     改为在 `.topbar-inner` 上换行，并把顶栏文字一律钉成**单行**（单行语义 + 省略号），
     窄屏呈「品牌一行、动作一行」两行式 —— 不再逐字断行。 */
  .topbar-inner { flex-wrap: wrap; row-gap: var(--sp-2); }
  .topbar .title { font-size: var(--fs-lg); flex: 1 1 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .topbar-right { flex: 1 1 100%; flex-wrap: wrap; justify-content: flex-end; }

  /* ★ P4 / 判据 D4-3+（触控目标下限，**本支自加口径**：工卡只写「主 CTA 可点」）。
     ≤560 实测三个主 CTA 只有 31~41px 高（发布页 110×31 / 文章库 133×31 / 首页 122×41）
     —— 手机上按不准。只抬**主行动按钮**，不碰 .ghost 等次级按钮的既有观感。 */
  button.primary, button.ck-cta { min-height: 44px; }

  .auth-card { margin-top: 6vh; padding: var(--sp-5) var(--sp-4); }
  .wcard-link { padding: var(--sp-3) var(--sp-4) var(--sp-3); }
}

/* ── 设置页（CONTRACT v1.45）─────────────────────────────────────────────
   超管 / 一级代理 = ⚙️ 系统设置（账号 + 我的资料 + 修改密码 + 平台配置）；
   二级代理 / 终端客户 = 🔑 我的设置（账号 + 我的资料 + 修改密码）。
   与品牌设置 / 授权管理页同族（.his-page 壳体），表单观感与全站一致。 */
.st-page { max-width: 720px; }
.st-page .panel { margin-bottom: 16px; }
.st-page form { display: grid; gap: 14px; }
.st-account { display: flex; flex-wrap: wrap; gap: 8px 32px; }
.st-acc-item { display: flex; align-items: baseline; gap: 8px; }
.st-acc-label { font-size: 13px; color: var(--muted); }
.st-acc-item b { font-size: 14px; font-weight: 600; color: var(--text); }
.st-ops { display: flex; gap: 10px; align-items: center; }
.st-links { display: flex; flex-wrap: wrap; gap: 10px; }
@media (max-width: 560px) {
  .st-ops button, .st-links button { flex: 1 1 auto; }
}

/* ============================================================
 * v1.46 发布信号标注（CONTRACT 2.2 边界澄清 / §3.2 / §3.3）
 * 工作台任务级标注条 + 发布队列行/详情的信号展示（纯展示，无修改控件）。
 * 颜色一律取自设计令牌，符合 v1.42 令牌层纪律（本层不出现字面色）。
 * ========================================================== */
.ws-signal {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--sp-2) var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-4);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.ws-signal-title { font-size: var(--fs-sm); font-weight: 600; color: var(--text); }
.ws-signal-field { display: inline-flex; align-items: center; gap: var(--sp-2); }
.ws-signal-lb { font-size: var(--fs-xs); color: var(--muted); white-space: nowrap; }
.ws-signal-field input {
  width: 170px; padding: 6px 10px;
  font-size: var(--fs-sm); color: var(--text);
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.ws-signal-field input:focus { border-color: var(--primary); }
.ws-signal-hint { font-size: var(--fs-xs); color: var(--muted); }

/* 发布队列：行内信号行 + 详情信号块（均只读展示） */
.pq-item-signal { margin-top: 2px; font-size: var(--fs-xs); color: var(--muted); }
.pq-signal-line { font-size: var(--fs-sm); color: var(--muted); }
.pq-signal-line b { color: var(--text); font-weight: 600; }

@media (max-width: 560px) {
  .ws-signal-field { flex: 1 1 100%; }
  .ws-signal-field input { width: 100%; }
}

/* ────────────────────────────────────────────────────────────────
   后端实例切换提示条（CONTRACT §7.6 · UI-005）

   场景：后端会在磁盘代码变化后**自动**切换到新代码，但浏览器里这个页面
   仍是旧代码下加载的 —— 后端是新的、页面是旧的，同一个功能两种版本。
   这里用固定定位的常驻提示条告诉用户「刷新一下」。

   交互纪律（用户 2026-09-10 定的底层逻辑：不打断、不重复弹、不闪）：
     · 不自动刷新（用户可能正在编辑，reload 会吃掉输入）
     · 出现后常驻，直到用户刷新或点关闭（绝不自己消失）
     · 复用同一节点，轮询再多次也只有一条
   ──────────────────────────────────────────────────────────────── */
[data-el="instance-stale-host"] { position: fixed; right: 16px; bottom: 16px; z-index: 60; }
.inst-stale {
  display: flex; align-items: center; gap: 10px;
  max-width: 420px; padding: 10px 12px;
  background: var(--card); color: var(--text);
  border: 1px solid var(--border); border-left: 4px solid var(--primary);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-2);
  font-size: var(--fs-sm);
}
.inst-stale-icon { color: var(--primary); font-size: 16px; line-height: 1; }
.inst-stale-text { display: flex; flex-direction: column; gap: 2px; }
.inst-stale-text strong { font-weight: 600; color: var(--text); }
.inst-stale-text span { color: var(--muted); font-size: var(--fs-xs); }
.inst-stale-btn {
  flex: none; cursor: pointer; white-space: nowrap;
  padding: 5px 12px; font-size: var(--fs-sm);
  color: var(--on-primary); background: var(--primary);
  border: 0; border-radius: var(--radius-sm);
}
.inst-stale-btn:hover { filter: brightness(1.06); }

/* 「待切换」变体（v1.51 §7.6.5）：磁盘代码已变，但后端在**等空闲**才切换。
   用更弱的视觉权重 —— 这不是需要用户处理的事，只是一个"正在发生"的状态。
   与「已切换」的区别：这条**会**随事实消失，那条一旦出现就常驻。 */
.inst-stale--pending { border-left-color: var(--border); }
.inst-stale--pending .inst-stale-icon { color: var(--muted); }

/* 「已重启」变体（v1.108 §7.6.3 第 7 条）：换了**进程**但没换代码。
   视觉权重介于两者之间 —— 它比「待切换」重要（在途操作可能已中断、值得知道），
   又比「已切换」轻（页面**没有**过期，代码是同一份）。用一个中性色而不是主色，
   免得用户把"进程重启了"误读成"代码换了、界面要过期了"。 */
.inst-stale--restart { border-left-color: var(--border); }
.inst-stale--restart .inst-stale-icon { color: var(--muted); }

@media (max-width: 560px) {
  [data-el="instance-stale-host"] { left: 12px; right: 12px; bottom: 12px; }
  .inst-stale { max-width: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   企业认证页（CONTRACT v1.66；一个菜单项、四角色分流）
   ══════════════════════════════════════════════════════════════════════════ */

/* ── 顶部状态提示：四种状态各给「下一步做什么」，不留"知道了但不知道干嘛"的死提示 ── */
.cert-notice {
  margin: 0 0 16px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-left: 4px solid var(--primary);
  border-radius: var(--radius-sm);
  background: var(--card);
}
.cert-notice-title { font-weight: 600; margin-bottom: 4px; }
.cert-notice-body { color: var(--muted); font-size: 13px; line-height: 1.6; }
.cert-notice-body b { color: var(--text); }
.cert-notice-ok    { border-left-color: var(--ok); }
.cert-notice-ok    .cert-notice-title { color: var(--ok); }
.cert-notice-info  { border-left-color: var(--primary); }
.cert-notice-info  .cert-notice-title { color: var(--primary); }
.cert-notice-warn  { border-left-color: var(--warn); }
.cert-notice-warn  .cert-notice-title { color: var(--warn-text); }
.cert-notice-err   { border-left-color: var(--danger); }
.cert-notice-err   .cert-notice-title { color: var(--danger); }

/* ── 客户侧表单：证件缩略图 ── */
.cert-thumb {
  margin-top: 8px;
  min-height: 96px;
  display: flex; align-items: center; justify-content: center;
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-3);
  overflow: hidden;
}
.cert-thumb img { max-width: 100%; max-height: 220px; display: block; }
.cert-doc-hint { color: var(--muted); font-size: 12px; }
.cert-doc-err { color: var(--danger); }

/* ── 代理/超管侧：筛选条 + 列表 ── */
.cert-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.cert-chip {
  cursor: pointer;
  padding: 4px 12px;
  font-size: 13px;
  color: var(--text);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
}
.cert-chip b { font-weight: 600; color: var(--muted); margin-left: 2px; }
.cert-chip:hover { border-color: var(--primary); }
.cert-chip.selected { color: var(--on-primary); background: var(--primary); border-color: var(--primary); }
.cert-chip.selected b { color: var(--on-primary); }

.cert-list { list-style: none; margin: 0; padding: 0; }
.cert-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 4px;
  border-bottom: 1px solid var(--border);
}
.cert-row:last-child { border-bottom: 0; }
.cert-row-main { min-width: 0; }
.cert-row-name { font-weight: 600; margin-bottom: 4px; }
.cert-row-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; font-size: 12px; color: var(--muted); }
.cert-row-ops { display: flex; flex-wrap: wrap; gap: 8px; flex: none; }
.cert-company { color: var(--text); }
.cert-muted { color: var(--muted); }
.cert-time { color: var(--muted); }
/* 存量宽限中的未提交客户：把截止日摆到列表上，代理一眼看出"这几家快到期了" */
.cert-grace { color: var(--warn-text); background: var(--warn-bg); border-radius: var(--radius-sm); padding: 1px 6px; }

/* UI-021（v1.69）· 归属行：**在列表上直接显示**该客户的上级一级 / 二级代理，
   不必点「查看资料」才知道归属（用户原话：「这个项不要在"查看资料"里才能看，
   应该直接在列表外面就显示出来，至少要显示他的一级代理是谁。」）。
   单独占一行，避免把 .cert-row-meta 挤成一团读不出层次。 */
.cert-row-owner { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 4px; font-size: 12px; color: var(--muted); }
.cert-owner b { color: var(--text); font-weight: 600; }
/* 链上没有一级代理 = P-48 那类账号（提交了也没人能审）。存量已清零，留着是为了万一复发时
   一眼看得见，而不是等客户被门禁锁死才发现。 */
.cert-owner-warn { color: var(--warn-text); background: var(--warn-bg); border-radius: var(--radius-sm); padding: 1px 6px; }

/* 状态徽标（四态；none = 从未提交，用于列表区分"没交"与"交了待审"） */
.cert-badge {
  display: inline-block;
  padding: 1px 8px;
  border-radius: var(--radius-pill);
  font-size: 12px;
  border: 1px solid transparent;
}
.cert-badge-none     { color: var(--muted);     background: var(--neutral-soft); border-color: var(--border); }
.cert-badge-pending  { color: var(--warn-text); background: var(--warn-bg);      border-color: var(--warn-border); }
.cert-badge-approved { color: var(--ok);        background: var(--ok-bg);        border-color: var(--ok-border); }
.cert-badge-rejected { color: var(--danger);    background: var(--danger-bg);    border-color: var(--danger-border); }

/* ── 资料详情面板 ── */
.cert-detail-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; }
.cert-reject {
  margin-top: 12px; padding: 10px 12px;
  color: var(--danger); background: var(--danger-soft);
  border: 1px solid var(--danger-border); border-radius: var(--radius-sm); font-size: 13px;
}
.cert-doc-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; margin-top: 16px; }
.cert-doc { margin: 0; }
.cert-doc figcaption { font-size: 13px; color: var(--muted); margin-bottom: 6px; }
.cert-doc-box {
  min-height: 140px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-3);
  overflow: hidden;
}
.cert-doc-box img { max-width: 100%; max-height: 360px; display: block; }

@media (max-width: 560px) {
  .cert-row { flex-direction: column; align-items: stretch; }
  .cert-row-ops { justify-content: flex-end; }
}

/* ── v1.77 §7.20：账号使用授权有效期 ──────────────────────────────────────────
   三个形态共用一套词汇（不另造色）：
     · .lic-badge     首页右上角「我的有效期」（用户 2026-09-19 要求：客户在自己主页面
                      右上角能看到有效期）
     · .ag-c-expiry   代理管理「我的下级」行内的有效期
     · .lic-expired   到期后的整屏页
   ★ 本段**没有新增任何颜色值**，全部复用既有令牌（--card / --border / --text / --muted /
     --danger / --danger-soft / --danger-border / --warn-soft / --warn-border / --warn-text /
     --bg），故不触发契约里「改色须出对比度实测值」那条纪律（没有改色，只是引用）。 */

.lic-badge {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px; border-radius: var(--radius-pill);
  background: var(--card); border: 1px solid var(--border);
  color: var(--text); font-size: var(--fs-xs); line-height: 1.4;
  white-space: nowrap; max-width: 440px; overflow: hidden; text-overflow: ellipsis;
}
/* 被上游压制：**不是**错误状态，但用户必须一眼看出"我填的日期 ≠ 实际能用的日期" */
.lic-badge-limited { background: var(--warn-soft); border-color: var(--warn-border); color: var(--warn-text); }
.lic-badge-expired { background: var(--danger-soft); border-color: var(--danger-border); color: var(--danger); }

.ag-c-expiry { color: var(--muted); }
.ag-c-expiry.upstream { color: var(--warn-text); }
.ag-c-expiry.upstream b { font-weight: 600; }

/* ── v1.83 §7.23 / UI-035：点数单价设置 ──────────────────────────────────────
   同款纪律：不新增颜色值，全部复用既有令牌。
     · .pts-row / .pts-stepper  超管「系统设置」单价步进器（− 输入 ＋）
     · .ag-c-prices             代理管理一级代理行内的「单价已覆盖」徽标 */
.pts-row { align-items: center; }
.pts-stepper { display: inline-flex; align-items: center; gap: 6px; }
.pts-stepper button { min-width: 34px; padding: 5px 10px; font-size: var(--fs-sm); line-height: 1.2; }
.pts-stepper input { width: 96px; text-align: center; }

/* v1.102（P-89）· 设置页「发布方式」两档单选：.pm-list / .pm-opt / .pm-body / .pm-badge
   契约硬要求「两档都必须可达，不得做成隐藏项或不可达」⇒ 两档**平铺可见、整行可点**，
   而不是"只露出当前档、另一档藏在折叠里"那种看着精简、实则把选择权收回的做法。

   ⚠️ 本区块位于 P0-4 组件层之内 ⇒ **颜色/圆角/间距/时长一律走令牌，不得出现字面值**
      （`tests/verify_phase5_p04_ui_polish.js`「组件层无散落硬编码颜色」在守这条）。
      首版这里写过带回退值的中性边框色和一个字面绿色 ⇒ **全量回归当场抓红**。
      补记一条更刁的：**判据是纯文本扫描，注释里的色值一样算** ——
      我把教训写进注释时又把两个字面值抄了一遍，于是"修完仍红"。
      ⇒ 教训：① 只跑"我关心的那几个套件"就宣布完成，会漏掉别的图层自己的纪律；
             ② 解释性文本里**不要复述被判据扫描的字面量**。 */
.pm-list { display: flex; flex-direction: column; gap: var(--sp-2); margin: var(--sp-1) 0 var(--sp-3); }
.pm-opt {
  display: flex; gap: var(--sp-3); align-items: flex-start;
  padding: var(--sp-3); border: 1px solid var(--border); border-radius: var(--radius-md);
  cursor: pointer; transition: background var(--dur) var(--ease);
}
.pm-opt:hover { background: var(--surface-2); }
.pm-opt input { margin: 3px 0 0; flex: 0 0 auto; }
.pm-body { display: flex; flex-direction: column; gap: 2px; line-height: 1.5; }
.pm-body .hint { font-style: normal; }
.pm-badge {
  font-style: normal; margin-left: 6px; padding: 1px 6px;
  font-size: var(--fs-xs); border-radius: var(--radius-pill);
  background: var(--ok-soft); color: var(--ok);
}
.pts-row em.hint { flex-basis: 100%; }

.ag-c-prices { color: var(--muted); }
.ag-c-prices b { font-weight: 600; }

.ag-expiry-quick { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.ag-expiry-quick button { font-size: var(--fs-xs); padding: 4px 9px; }

.lic-expired {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: 24px; background: var(--bg);
}
.lic-ex-card {
  width: min(560px, 100%); background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-md); padding: 28px 30px; text-align: left;
}
.lic-ex-icon { font-size: 34px; line-height: 1; margin-bottom: 10px; }
.lic-ex-h2 { margin: 0 0 10px; font-size: var(--fs-title); color: var(--text); }
.lic-ex-who { margin: 0 0 4px; font-size: var(--fs-sm); color: var(--text); }
.lic-ex-role { color: var(--muted); font-size: var(--fs-xs); margin-left: 6px; }
.lic-ex-deadline { margin: 10px 0; font-size: var(--fs-sm); color: var(--danger); }
.lic-ex-reason, .lic-ex-action { margin: 0 0 8px; font-size: var(--fs-sm); line-height: 1.75; color: var(--text); }
.lic-ex-action { font-weight: 500; }
.lic-ex-meta { margin: 12px 0 0; font-size: var(--fs-xs); color: var(--muted); }
.lic-ex-ops { margin-top: 18px; }


/* ============================================================
 * CONTRACT v1.85 §7.24 · 企业图库与配图选取（UI-037）
 * 1) .gal-*  企业图库页「各分类用量」表（每类上限 10 张，一眼看出哪个分类还有空位）
 * 2) .pk-mode* / .pk-random-box  配图页「自主选择 / 随机选择」两种模式
 * 3) .lib-edit* 卡片内联编辑（改名 / 改分类 / 改描述）
 * ============================================================ */

/* —— 步骤页头的动作区（配图页多了「管理企业图库」按钮）—— */
.step-page-head-acts { display: flex; align-items: center; gap: var(--sp-2, 8px); flex-wrap: wrap; }

/* —— 企业图库页：分类用量表 —— */
.gallery-wrap { display: block; }
.gal-usage { display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center; }
.gal-usage-item {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px; border: 1px solid var(--border); border-radius: var(--radius-pill);
  background: var(--card); font-size: var(--fs-sm, 13px);
}
.gal-usage-item.full { border-color: var(--danger); background: var(--danger-soft); }
.gal-usage-cat { color: var(--text); }
.gal-usage-num { color: var(--muted); font-variant-numeric: tabular-nums; }
.gal-usage-item.full .gal-usage-num { color: var(--danger); font-weight: 600; }
.gal-usage-tag { font-size: var(--fs-xs, 12px); color: var(--danger); font-weight: 500; }
.gal-usage-total { margin-left: auto; color: var(--muted); font-size: var(--fs-sm, 13px); }

/* —— 配图页：选取模式切换（自主 / 随机）—— */
.pk-mode { display: inline-flex; gap: 0; }
.pk-mode > .pk-mode-btn {
  border-radius: 0; border-right-width: 0; white-space: nowrap;
}
.pk-mode > .pk-mode-btn:first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.pk-mode > .pk-mode-btn:last-child { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; border-right-width: 1px; }
.pk-mode > .pk-mode-btn.on {
  background: var(--primary); border-color: var(--primary); color: var(--on-primary); font-weight: 500;
}
.pk-random-box { display: inline-flex; align-items: center; gap: 8px; }
.pk-num { display: inline-flex; align-items: center; gap: 4px; color: var(--muted); font-size: var(--fs-sm, 13px); }
.pk-count-input {
  width: 56px; font: inherit; padding: 6px 8px;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--card); text-align: center;
}

/* —— 图库卡片：动作区 + 内联编辑 —— */
.lib-card-acts { display: flex; gap: 6px; margin: 0 10px 10px; }
.lib-card-acts button { padding: 3px 10px; font-size: var(--fs-xs, 12px); }
.lib-edit { display: grid; gap: 8px; padding: 12px 12px 14px; }
.lib-edit-row { display: grid; gap: 4px; font-size: var(--fs-xs, 12px); color: var(--muted); }
.lib-edit-row input, .lib-edit-row select {
  font: inherit; font-size: var(--fs-sm, 13px); color: var(--text);
  padding: 6px 8px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--card);
}
.lib-edit-acts { display: flex; gap: 8px; margin-top: 2px; }

/* ══════════════════════════════════════════════════════════════════════════
   v1.88 §7.25.5：写作队列页的「✍️ 本次写作」面板
   （客户 2026-09-21 拍板：配图 / 提示词 / 一键生成搬到写作队列页）

   版面意图（不是装饰，是"简单"这条准则的落地）：
     主路径（状态 → 一键生成）在**上**，微调（③ 配图 / ④ 提示词）收在**下方的收起区**。
     队列已经把提示词与配图带好了，客户这一刻要做的是"生成"，不是"重配" ——
     把生成按钮垫在一屏图库下面，等于每次逼他滚过去，那正是"生产出一个东西来烦人"。
   ══════════════════════════════════════════════════════════════════════════ */
.qw { display: grid; gap: 16px; }
.qw-panel { margin-top: 0; }
.qw-status {
  display: block; padding: 10px 12px; border-radius: var(--radius-sm); line-height: 1.7;
  background: var(--surface-2); border: 1px solid var(--border);
}
/* ★ v1.88 修（由 `verify_phase5_p04_ui_polish.js` 的「组件层无散落硬编码颜色」抓到）：
   本区块位于 P0-4 组件层之内（该守卫的射程是"从 P0-4 标记到文件末尾"），
   而它要求**颜色只能来自令牌**。初版我这里抄了结果页 `.res-*` 的**五个字面色值**
   （绿色的字/边/底三色 + 暖黄的边/底两色，此处不再复述具体色值 —— 见下条），
   ⇒ 违反该层纪律（抄来的那几处之所以合规，是因为它们**不在**这一层里）。
   两条教训：
   ① **同一个色值在 A 层合规，不代表在 B 层合规** —— 判断依据是"这段代码属于哪一层"；
   ② **该守卫不剥注释** ⇒ 注释里也**不许出现字面色值**（我第一版把色值写进注释解释，
      结果"改完了"仍然红 —— 这正是"注释也是源码的一部分"这个坑的又一张脸）。
   现在一律走令牌：`--ok` / `--ok-border` / `--ok-soft`（成功）、
   `--warn-text` / `--warn-border` / `--warn-bg`（提醒）。 */
.qw-status.ok { color: var(--ok); border-color: var(--ok-border); background: var(--ok-soft); }
.qw-status.warn { color: var(--warn-text); border-color: var(--warn-border); background: var(--warn-bg); }
.qw-status code { font-size: 12px; background: var(--neutral-soft); padding: 1px 4px; border-radius: var(--radius-sm); }

.qw-gen {
  margin-top: 14px; border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--card); padding: 18px 16px; text-align: center;
}
.qw-gen .msg { margin-top: 8px; }
.qw-need {
  margin: 12px auto 0; max-width: 640px; text-align: left;
  padding: 10px 12px; border-radius: var(--radius-sm); font-size: 13px; line-height: 1.8;
  background: var(--warn-bg); border: 1px solid var(--warn-border); color: var(--warn-text);
}
.qw-need button { margin-left: 8px; font-size: 12px; padding: 3px 12px; }
.qw-result-acts { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.qw-result-acts > button { font-size: 13px; padding: 7px 18px; }

/* ═══ v1.91（UI-046）：此处原有「本次写作」面板的收起区样式（.qw-secs / .qw-sec /
   .qw-sec-body / .qw-sum / .qw-sum-hint 共 9 条规则）—— 随客户第七轮"删掉面板里的
   ③配图 / ④提示词 两块"一并删除（那些元素已不存在，留着就是死样式）。
   ⚠ 与 v1.90 删 .q-input-lg 同一先例：**功能删了，样式跟着删**，不静默留着。
   ⛔ 若将来又要在面板里放收起区，必须**重新设计**这一段（旧内容见 git 历史），
      不要依赖"这些类名还认得出来"。 */

/* 内嵌形态（image_step / prompt_step 以 embed:true 复用时）：把"整页扮演号"收起来。
   这里不藏任何功能 —— 只藏「页标题」与「← 返回工作台」这类在别人页面里说不通的东西
   （点「返回工作台」会把人从队列页带走，正是本轮要消灭的那个动作）。
   ⚠ v1.91（UI-046）：本段**当前没有消费方** —— 唯一的 embed 调用点（queue_writing_view）
     已随 ③④ 一起删除。两个步骤页的 `embed` 分支本身**保留**（不删），故这段样式也保留，
     以免将来再用时"功能回来了样式没回来"（那种缺陷只在界面上看得见，测试抓不到）。
     已登记在台账 UI-046「已知取舍」。 */
.step-page-embed { display: block; }
.step-page-embed > .step-page-head { justify-content: flex-start; }

/* ═══ v1.89 文章库 · 按平台自动分库导航（CONTRACT §7.26） ═══════════════════════
   颜色一律走设计令牌。⚠️ 本段落在 `verify_phase5_p04_ui_polish.js` 的"组件层"扫描范围内
   （扫描范围 = 设计令牌层锚点处截到**文件末尾**），写死色值会当场变红。
   而且那条守卫**不剥注释** ⇒ 连注释里都不能出现十六进制色值字样
   （v1.88 为此刻意返工两次，此处照此办理）。 */
.his-body { display: grid; grid-template-columns: 190px minmax(0, 1fr); gap: 14px; align-items: start; }
.his-libs {
  display: flex; flex-direction: column; gap: 4px;
  padding: 8px; border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface-2); position: sticky; top: 12px;
}
.his-lib {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  padding: 7px 9px; border: 1px solid transparent; border-radius: var(--radius-sm);
  background: transparent; color: var(--text); font-size: 13px; cursor: pointer;
  font-family: inherit;
}
.his-lib:hover { background: var(--surface-3); border-color: var(--border); }
.his-lib.is-on { background: var(--neutral-soft); border-color: var(--border); font-weight: 600; }
.his-lib-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.his-lib-count { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
.his-lib-badge {
  font-size: 10px; padding: 0 5px; border-radius: var(--radius-pill);
  background: var(--warn-bg); color: var(--warn-text); border: 1px solid var(--warn-border);
}
.his-content { min-width: 0; }
.his-libnote { margin-bottom: 10px; }
.his-note {
  padding: 10px 12px; border-radius: var(--radius-sm); font-size: 12.5px; line-height: 1.7;
  background: var(--warn-bg); border: 1px solid var(--warn-border); color: var(--warn-text);
}
.his-note p { margin: 0 0 6px; }
.his-note p:last-child { margin-bottom: 0; }
.his-note button { margin-left: 6px; }

/* 窄屏：左栏改成可横向滚动的条、正文占满（P0-4 要求"PC 为主、手机可看"） */
@media (max-width: 720px) {
  .his-body { grid-template-columns: minmax(0, 1fr); }
  .his-libs { flex-direction: row; overflow-x: auto; position: static; }
  .his-lib { width: auto; white-space: nowrap; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   §A5 通配兜底：`prefers-reduced-motion: reduce` 下动效时长一律为 0

   与 css/tokens.css 里那道 `:root { --dur: 0s }` **互补**，缺一不可：
     · 令牌那道管"读 --dur 的组件"（本文件里占绝大多数，`transition: … var(--dur) …`）；
     · 这道管"硬编码时长的组件"（本文件里仍有 .12s / .15s / .18s / .2s / .25s，
       用在进度条、卡片悬停这类极短反馈上），令牌层够不着它们。
   为什么不只用通配一道：`!important` 通配是**兜底**，不是**设计**。
   令牌层归零才是"用户不要动效"这个偏好的正确表达（单点、可被读取）。
   为什么要连 `animation-*` 一起管：本文件有加载指示器这类 `infinite` 动画，
   若只把时长压到 0s 而 iteration-count 仍是 infinite，浏览器会把"0 秒 × 无限次"
   排成永不停歇的任务队列（表现为空转吃 CPU）。故 iteration-count 一并压到 1。
   ═══════════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0s !important;
    scroll-behavior: auto !important;
  }
}

/* ============================================================
 * 侧边栏链接（CONTRACT sidebar_links）
 * 两处上下文：
 *  ① 客户侧边栏「功能键」区 —— 挂在 .dash-sidebar（深底）内，用 --shell-* 令牌；
 *  ② 管理端「侧边栏链接」编辑页 —— 普通浅色内容区，用基础令牌。
 *  ⚠ 判据 A1：本段不写任何 #hex，颜色一律 var(--…)。
 * ========================================================== */

/* —— ① 客户侧边栏「功能键」区（深底，与首页侧边栏菜单项完全同构） —— */
.sb-links {
  display: flex; flex-direction: column; gap: 2px;
  margin-top: 6px;
}
/* 复用首页菜单按钮形态（.side-link 已在 .dash-sidebar 深底定义）；长文案省略号防溢出 */
.sb-link-btn { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* —— ② 管理端链接编辑页（浅色内容区） —— */
.sl-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.sl-row {
  display: flex; align-items: center; gap: 8px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 8px 10px;
}
.sl-label { flex: 0 0 30%; min-width: 120px; }
.sl-url { flex: 1 1 auto; min-width: 0; }
.sl-label, .sl-url {
  height: 34px; padding: 0 10px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-size: 14px; color: var(--text); background: var(--surface-3);
}
.sl-label:focus, .sl-url:focus { outline: none; border-color: var(--primary); box-shadow: var(--focus-ring); }
.sl-tools { flex: none; display: inline-flex; gap: 4px; }
.sl-mini {
  width: 30px; height: 30px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); cursor: pointer; color: var(--muted); font-size: 14px;
}
.sl-mini:hover:not(:disabled) { background: var(--neutral-soft); color: var(--text); }
.sl-mini:disabled { opacity: .4; cursor: not-allowed; }
.sl-del:hover:not(:disabled) { color: var(--danger); border-color: var(--danger-border); background: var(--danger-soft); }
.sl-empty {
  list-style: none; padding: 10px 12px; color: var(--muted); font-style: italic;
  background: var(--surface-2); border: 1px dashed var(--border); border-radius: var(--radius-sm);
}

/* ============================================================
 * 客户首页布局（CONTRACT homepage_editor）
 * ① 客户侧「首页画布」——挂在 .dash-main .stat-body（浅色内容区），用基础令牌；
 * ② 超管「客户首页编辑」页——浅色内容区，用基础令牌。
 *  ⚠ 判据 A1：本段不写任何 #hex，颜色一律 var(--…)。
 * ========================================================== */

/* —— ① 客户侧首页画布（自由画布：块可绝对定位、上下左右拖动） —— */
.hp-canvas { position: relative; min-height: 420px; }
.hp-block { line-height: 1.6; }
.hp-image-block { margin: 0; }
.hp-block-abs { position: absolute; max-width: 100%; outline: 1px dashed transparent; }
.hp-text { white-space: pre-wrap; word-break: break-word; }
.hp-text-link { text-decoration: none; }                 /* 点击文字打开链接：去掉下划线，颜色由内联 style 决定 */
.hp-text-link:hover { opacity: .82; }
.hp-images-v { display: flex; flex-direction: column; gap: 12px; align-items: flex-start; }
.hp-images-h { display: flex; flex-direction: row; flex-wrap: wrap; gap: 12px; align-items: flex-start; }
.hp-images-h .hp-img, .hp-images-h .hp-img-link { flex: 0 0 auto; }
.hp-img { display: block; max-width: 100%; height: auto; border-radius: var(--radius-sm); }
.hp-img-link { display: inline-block; }
.hp-link-btn {
  display: inline-block; padding: 10px 18px; border-radius: var(--radius-pill);
  background: var(--primary); color: var(--on-primary); text-decoration: none;
  font-size: 15px; font-weight: 600;
}
.hp-link-btn:hover { background: var(--primary-hover); }
.hp-empty { padding: 28px 16px; color: var(--muted); font-style: italic; text-align: center;
  background: var(--surface-2); border: 1px dashed var(--border); border-radius: var(--radius-md); }

/* —— ② 超管编辑器 —— */
.hp-editor { display: flex; flex-direction: column; gap: 14px; }
.hp-toolbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 10px 12px; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-md); }
.hp-toolbar-label { color: var(--muted); font-size: 13px; }
.hp-spacer { flex: 1; }
.hp-col-title { font-size: 13px; font-weight: 600; color: var(--muted); }
.hp-preview { min-height: 240px; padding: 16px; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--radius-md); overflow: auto; position: relative; }
/* 编辑画布：块可拖拽；拖动中高亮 */
.hp-editor-canvas .hp-canvas { min-height: 560px; }
.hp-editor-canvas .hp-block-abs { cursor: grab; touch-action: none; }
.hp-editor-canvas .hp-block-abs.dragging { cursor: grabbing; outline-color: var(--primary); z-index: 50; opacity: .92; will-change: transform; user-select: none; }
.hp-editor-canvas .hp-block-abs:hover { outline-color: var(--border-strong); }

/* 客户首页：原驾驶舱置顶，超管「首页画布」追加其下（向下自动延伸） */
.hp-canvas-section { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border); }

/* 顶部属性区：按选中块显示颜色/字号/对齐/链接（替代原右侧独立编辑区，v1.136） */
.hp-toolbar-divider { width: 1px; height: 22px; background: var(--border); margin: 0 4px; }
.hp-props { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-width: 0; }
.hp-props-type { font-size: 13px; font-weight: 600; color: var(--ink); }
.hp-props-hint { font-size: 12px; color: var(--muted); }
.hp-prop { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); }
.hp-prop input, .hp-prop select { height: 30px; padding: 0 8px;
  border: 1px solid var(--border); border-radius: var(--radius-sm); font-size: 13px;
  color: var(--text); background: var(--surface-3); }
/* ── 自定义取色器（取代原生 <input type="color">，用户 2026-10-02 拍板）──────────
   原生那套 = 色相滑条 + 饱和度/明度方块 + RGB 数字框：用户「拉了滑条、数字没变、颜色也没变」，
   控件太多、反馈对不上。改为自绘取色器：**没有数字**、拖动实时生效、面积更大，
   并给出「板块式」预设色板让可选范围更多。色值只来自 tokens.css 的 --cp-*（本文件不写任何 #hex）。 */
.hp-prop > .cp { position: relative; display: inline-flex; align-items: center; }
.cp-chip { width: 46px; height: 28px; padding: 0; cursor: pointer; border: 1px solid var(--border);
  border-radius: var(--radius-sm); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .45), 0 0 0 1px rgba(0, 0, 0, .35); }
.cp-chip:hover { border-color: var(--primary); }
.cp-panel { position: absolute; left: 0; top: calc(100% + 8px); z-index: 40; width: 396px; max-width: 92vw;
  display: grid; gap: 12px; padding: 12px; background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-md); box-shadow: var(--shadow-2); }
.cp-boards { display: grid; gap: 8px; }
.cp-board { display: flex; align-items: flex-start; gap: 8px; }
.cp-board-label { flex: 0 0 40px; padding-top: 4px; font-size: 12px; color: var(--muted); }
.cp-sws { display: flex; flex-wrap: wrap; gap: 5px; flex: 1 1 auto; min-width: 0; }
.cp-sw { width: 24px; height: 24px; padding: 0; cursor: pointer; border: 1px solid var(--border);
  border-radius: 5px; box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .45), 0 0 0 1px rgba(0, 0, 0, .35); }
.cp-sw:hover { border-color: var(--primary); }
.cp-sliders { display: grid; gap: 10px; }
.cp-sv { position: relative; display: block; height: 168px; border-radius: var(--radius-sm); cursor: crosshair;
  border: 1px solid var(--border); touch-action: none; }
.cp-hue { position: relative; display: block; height: 26px; border-radius: var(--radius-sm); cursor: pointer;
  border: 1px solid var(--border); touch-action: none;
  background: linear-gradient(to right, hsl(0 100% 50%), hsl(60 100% 50%), hsl(120 100% 50%),
    hsl(180 100% 50%), hsl(240 100% 50%), hsl(300 100% 50%), hsl(360 100% 50%)); }
.cp-handle { position: absolute; width: 14px; height: 14px; border-radius: 50%; pointer-events: none;
  border: 2px solid rgb(255 255 255); box-shadow: 0 0 0 1px rgb(0 0 0 / 55%); transform: translate(-50%, -50%); }
.hp-prop input[type="number"] { width: 64px; }
.hp-prop input[type="url"] { width: 180px; }
.hp-prop-wide input { width: 220px; }
.hp-prop input:focus, .hp-prop select:focus { outline: none; border-color: var(--primary); box-shadow: var(--focus-ring); }

/* 块编辑件（仅编辑器注入）：⠿ 拖拽手柄（左上）与 ✕ 删除（右上），悬停/选中时出现 */
.hp-block-abs.hp-selected { outline-color: var(--primary); }
.hp-block-handle { position: absolute; top: -10px; left: -10px; width: 22px; height: 22px;
  border-radius: 50%; background: var(--card); border: 1px solid var(--border-strong);
  color: var(--muted); font-size: 12px; line-height: 20px; text-align: center; cursor: grab;
  display: none; z-index: 60; user-select: none; }
.hp-block-del { position: absolute; top: -10px; right: -10px; width: 22px; height: 22px;
  border-radius: 50%; background: var(--danger); color: var(--on-primary); border: none;
  font-size: 12px; line-height: 1; cursor: pointer; display: none; z-index: 60; }
.hp-editor-canvas .hp-block-abs:hover .hp-block-handle,
.hp-editor-canvas .hp-block-abs.hp-selected .hp-block-handle { display: block; }
.hp-editor-canvas .hp-block-abs:hover .hp-block-del,
.hp-editor-canvas .hp-block-abs.hp-selected .hp-block-del { display: block; }
.hp-editor-canvas .hp-block-abs.dragging .hp-block-handle { cursor: grabbing; }

/* 预览下半部分：客户首页固定内容（经营驾驶舱 · 示意数据）——不可编辑、不响应指针 */
.hp-preview-fixed { margin-top: 20px; }
.hp-preview-fixed-label { font-size: 12px; color: var(--muted); padding: 10px 0 8px;
  border-top: 1px dashed var(--border-strong); }
.hp-preview-dash { pointer-events: none; user-select: none; }

/* 图片选择器（图库 / 从电脑上传） */
.hp-img-modal-mask { position: fixed; inset: 0; background: var(--mask); display: flex; align-items: center; justify-content: center; z-index: 1000; }
.hp-img-modal { width: min(640px, 92vw); max-height: 84vh; display: flex; flex-direction: column;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); overflow: hidden; }
.hp-img-modal-head { display: flex; align-items: center; justify-content: space-between;
  padding: 12px 16px; border-bottom: 1px solid var(--border); font-weight: 600; color: var(--ink); }
.hp-img-modal-body { padding: 16px; display: flex; flex-direction: column; gap: 14px; overflow: auto; }
.hp-upload-row { display: flex; align-items: center; gap: 10px; font-size: 14px; color: var(--muted); }
.hp-img-gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 10px; padding: 12px;
  background: var(--surface-2); border: 1px dashed var(--border); border-radius: var(--radius-sm); min-height: 90px; }
.hp-img-cell { padding: 0; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; cursor: pointer; background: var(--card); }
.hp-img-cell img { display: block; width: 100%; height: 84px; object-fit: cover; }
.hp-img-cell:hover { border-color: var(--primary); }
.hp-gallery-empty, .hp-gallery-loading { grid-column: 1 / -1; color: var(--muted); font-style: italic; text-align: center; padding: 12px; }
