/* ============================================================================
   沐雪 AI · 超级智能获客操盘手 — 设计系统
   视觉语言：深空底 + 霓虹全息描边 + 玻璃拟态 + 动态光晕
   一句话：让界面看起来像一台正在运行的超级智能核心，而不是一张后台报表。
   ========================================================================== */
:root {
  /* ---------- 底层色板（深空） ---------- */
  --bg: #04060e;
  --bg-2: #080c18;
  --bg-3: #0d1224;
  --panel: rgba(14, 19, 36, .72);
  --panel-2: rgba(20, 26, 48, .58);
  --panel-solid: #0d1224;
  --line: rgba(120, 165, 255, .13);
  --line-2: rgba(120, 165, 255, .26);
  --line-3: rgba(120, 165, 255, .42);

  /* ---------- 文字 ---------- */
  --text: #e9eefb;
  --text-mid: #b5c1de;
  --muted: #8391b4;
  --muted-soft: #647192;

  /* ---------- 主色：青 → 紫 → 品红 的霓虹三段 ---------- */
  --brand: #22e0ff;
  --brand-strong: #4af0ff;
  --brand-deep: #0a9ec4;
  --violet: #7b5cff;
  --magenta: #ff4d9d;
  --brand-soft: rgba(34, 224, 255, .11);
  --brand-soft-2: rgba(123, 92, 255, .12);

  /* ---------- 语义色（在深底上都要提亮，否则发灰不可读） ---------- */
  --success: #22e6a0;
  --success-soft: rgba(34, 230, 160, .13);
  --warn: #ffb545;
  --warn-soft: rgba(255, 181, 69, .13);
  --warn-line: rgba(255, 181, 69, .38);
  --danger: #ff4d6d;
  --danger-soft: rgba(255, 77, 109, .13);
  --info: #46b8ff;
  --info-soft: rgba(70, 184, 255, .13);

  /* ---------- 排版 ---------- */
  --fs-xs: 12px;
  --fs-sm: 13px;
  --fs-base: 14px;
  --fs-md: 15px;
  --fs-lg: 18px;
  --fs-xl: 22px;
  --lh-tight: 1.35;
  --lh-base: 1.66;
  --font: "PingFang SC", "HarmonyOS Sans SC", "Microsoft YaHei",
          "Source Han Sans SC", "Helvetica Neue", Arial, sans-serif;
  --mono: "SF Mono", "JetBrains Mono", Consolas, "Liberation Mono", monospace;
  --emoji: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji";

  /* ---------- 形状与光效 ---------- */
  --radius: 12px;
  --radius-lg: 18px;
  --glow-brand: 0 0 0 1px rgba(34, 224, 255, .3), 0 8px 32px rgba(34, 224, 255, .13);
  --glow-soft: 0 8px 28px rgba(0, 0, 0, .5);
  --glow-card: 0 1px 0 rgba(255, 255, 255, .05) inset, 0 10px 34px rgba(0, 0, 0, .45);
  /* 侧栏卡片一套（预告片卡 .tr-card / 下载卡 .dl-card 共用）。
     抽出来的理由不是"整齐"：这两张卡的这 3 个值原本**逐字相同却各写了两遍**，
     而且都硬编码 —— 抽出后改一处两张卡自动一致，并且使用处不再有 rgba 字面量。 */
  --side-card-bg: linear-gradient(180deg, rgba(34, 224, 255, .07), rgba(10, 15, 30, .5));
  --side-card-line: rgba(34, 224, 255, .55);
  --side-card-glow: 0 6px 22px rgba(34, 224, 255, .14);
  --sidebar-w: 232px;
  --sidebar-w-collapsed: 74px;
  --topbar-h: 62px;
}

* { box-sizing: border-box; }
html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: var(--fs-base)/var(--lh-base) var(--font);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow: hidden;
}
/* 首帧淡入，避免开场白闪 */
body.booting .app { opacity: 0; }
.app { opacity: 1; transition: opacity .55s ease; }

h1, h2, h3, h4 { line-height: var(--lh-tight); letter-spacing: .2px; margin: 0; }
a { color: inherit; text-decoration: none; }
button { font: inherit; cursor: pointer; color: inherit; }
.hidden { display: none !important; }
.kpi .value, .fstep .val, .log .t, table.data td, .kv div:last-child,
.kpi .value, .gal-stat, .sow-run-stat {
  font-variant-numeric: tabular-nums;
}
:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 6px; }

/* 滚动条：细、暗、带霓虹滑块 */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: rgba(120, 165, 255, .16);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: rgba(120, 165, 255, .3); background-clip: content-box; }

/* ============================================================================
   动态氛围层（由 core/fx.js 驱动）
   ========================================================================== */
.fx { position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.fx canvas { position: absolute; inset: 0; opacity: .55; }

/* 透视网格：配合 JS 做极轻视差 */
.fx-grid {
  position: absolute; inset: -8% -12%;
  background-image:
    linear-gradient(rgba(90, 190, 255, .07) 1px, transparent 1px),
    linear-gradient(90deg, rgba(90, 190, 255, .07) 1px, transparent 1px);
  background-size: 46px 46px;
  mask-image: radial-gradient(ellipse 80% 70% at 50% 42%, #000 20%, transparent 78%);
  -webkit-mask-image: radial-gradient(ellipse 80% 70% at 50% 42%, #000 20%, transparent 78%);
  animation: fxGridDrift 34s linear infinite;
}
@keyframes fxGridDrift {
  to { background-position: 46px 46px, 46px 46px; }
}

/* 呼吸光晕：跟随鼠标（--mx/--my 由 fx.js 写入） */
.fx-glow {
  --mx: 50%; --my: 42%;
  position: absolute; inset: 0;
  background:
    radial-gradient(680px 460px at var(--mx) var(--my), rgba(34, 224, 255, .12), transparent 62%),
    radial-gradient(760px 520px at calc(var(--mx) - 18%) calc(var(--my) + 14%), rgba(123, 92, 255, .11), transparent 66%);
  transition: opacity .4s;
  animation: fxGlowPulse 11s ease-in-out infinite;
}
@keyframes fxGlowPulse { 0%, 100% { opacity: .85; } 50% { opacity: 1; } }

/* 扫描线：极缓慢下移的一层薄光，暗示"系统在持续运行" */
.fx-scan {
  position: absolute; left: 0; right: 0; height: 220px;
  background: linear-gradient(180deg, transparent, rgba(90, 220, 255, .05) 45%, transparent);
  animation: fxScan 14s linear infinite;
}
@keyframes fxScan {
  0% { transform: translateY(-260px); }
  100% { transform: translateY(100vh); }
}
@media (prefers-reduced-motion: reduce) {
  .fx-grid, .fx-glow, .fx-scan { animation: none; }
  .fx-scan { display: none; }
}

/* ============================================================================
   应用外壳
   ========================================================================== */
.app {
  position: relative; z-index: 1;
  display: flex; flex-direction: column; height: 100vh;
}
/* ★iOS Safari 的 `100vh` 把**地址栏也算进去** ⇒ 页面比可视区高一条地址栏，
   底部（手机端正是输入区）被压在地址栏后面，用户看到的是"打不了字"。
   `-webkit-fill-available` 是 WebKit 给出的"真正可用高度"，老 iOS 也认；
   手机端 `@media(max-width:900px)` 里的 `height:100dvh` 特异性更高，
   在 Safari 15.4+ 会赢过这里，只有 15.0~15.3 才用到这一条兜底。
   `-webkit-touch-callout` 只有 WebKit 认 ⇒ 其他内核整块跳过，零副作用。 */
@supports (-webkit-touch-callout: none) {
  .app { height: -webkit-fill-available; }
}

/* ---------- 顶栏（HUD 风格） ---------- */
.topbar {
  height: var(--topbar-h);
  flex: none;
  display: flex; align-items: center; gap: 16px;
  padding: 0 18px 0 14px;
  background: linear-gradient(180deg, rgba(12, 17, 32, .92), rgba(9, 13, 25, .72));
  border-bottom: 1px solid var(--line);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  position: relative;
  /* 必须显式抬层：backdrop-filter 会创建层叠上下文，导致下拉菜单被
     紧随其后的 .main（position:relative）盖住，用户点不到"退出登录"。 */
  z-index: 100;
}
/* 顶栏底部一条会呼吸的霓虹线 */
.topbar::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
  background: linear-gradient(90deg, transparent, var(--brand), var(--violet), transparent);
  opacity: .5;
  animation: hudLine 6s ease-in-out infinite;
}
@keyframes hudLine { 0%, 100% { opacity: .28; } 50% { opacity: .62; } }

.topbar-left { display: flex; align-items: center; gap: 12px; flex: none; }

/* 汉堡按钮（移动端 / 折叠） */
.nav-toggle {
  display: none; flex-direction: column; gap: 4px;
  width: 34px; height: 34px; align-items: center; justify-content: center;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 10px;
}
.nav-toggle i { display: block; width: 15px; height: 1.6px; background: var(--text-mid); border-radius: 2px; }
.nav-toggle:hover i { background: var(--brand); }

.brand { display: flex; align-items: center; gap: 11px; }
/* AI 核心：头像外套一圈旋转光环 */
.brand-core { position: relative; width: 40px; height: 40px; flex: none; }
.topbar-logo {
  width: 40px; height: 40px; border-radius: 50%;
  object-fit: cover; display: block;
  border: 1.5px solid rgba(34, 224, 255, .5);
  box-shadow: 0 0 18px rgba(34, 224, 255, .3), inset 0 0 12px rgba(34, 224, 255, .16);
}
.brand-ring {
  position: absolute; inset: -5px; border-radius: 50%;
  border: 1.5px solid transparent;
  border-top-color: var(--brand);
  border-right-color: rgba(123, 92, 255, .85);
  animation: coreSpin 5.5s linear infinite;
  pointer-events: none;
}
@keyframes coreSpin { to { transform: rotate(360deg); } }

.topbar-title { display: flex; flex-direction: column; line-height: 1.2; }
.brand-name { font-size: 17px; font-weight: 700; letter-spacing: 1.2px; }
.brand-name em {
  font-style: normal; margin-left: 4px;
  background: linear-gradient(92deg, var(--brand), var(--violet) 60%, var(--magenta));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.brand-sub {
  font-size: 10.5px; color: var(--muted); letter-spacing: 1.4px;
  font-family: var(--mono); text-transform: uppercase;
}
/* Beta 公测标识（v2.16）：纯文字徽标，挂在 brand-name 行内 —— 不给 .brand 增加
   额外的横向列（375px 顶栏宽度预算见下方「顶栏窄屏重排」注释）。
   P0 硬红线：禁 emoji 图标 / 禁紫粉渐变主视觉 ⇒ 用 warn 语义色的描边胶囊。 */
.beta-badge {
  display: inline-flex; align-items: center;
  margin-left: 8px; padding: 1px 7px;
  border-radius: 6px;
  font-size: 9.5px; font-weight: 700; letter-spacing: .7px;
  color: var(--warn); background: var(--warn-soft);
  border: 1px solid var(--warn-line);
  vertical-align: 2px; white-space: nowrap; flex: none;
}

/* 顶栏中央：系统状态条 */
.topbar-center { flex: 1; display: flex; justify-content: center; min-width: 0; }
.sysbar {
  display: flex; align-items: center; gap: 12px;
  padding: 6px 16px;
  background: rgba(10, 15, 28, .6);
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 11.5px; font-family: var(--mono); color: var(--muted);
  letter-spacing: .6px;
  max-width: 100%; overflow: hidden;
}
.sysbar-item { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.sysbar-sep { width: 1px; height: 11px; background: var(--line-2); flex: none; }
.sysbar .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--muted); }
/* 核心在线：双层脉冲光点 */
.sysbar .pulse {
  width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--success);
  box-shadow: 0 0 0 0 rgba(34, 230, 160, .6);
  animation: corePulse 2.2s ease-out infinite;
}
@keyframes corePulse {
  0% { box-shadow: 0 0 0 0 rgba(34, 230, 160, .55); }
  70% { box-shadow: 0 0 0 8px rgba(34, 230, 160, 0); }
  100% { box-shadow: 0 0 0 0 rgba(34, 230, 160, 0); }
}
@media (max-width: 1180px) { .topbar-center { display: none; } }

/* 顶栏是固定高度的**单行** flex：窄屏下必须显式放开 min-width。
   否则 `.topbar-left` + `.topbar-right` 的 min-content 会把顶栏（进而把 .app）
   撑到 ~583px，而移动端 Chrome 会把 **layout viewport 一起撑大** ——
   表现是整站可横向平移、右半截顶栏跑到屏外；偏偏 `scrollWidth - innerWidth`
   恒等于 0，用这条最常见的判据**完全看不出来**（本项真被它骗过一轮，
   记录在 .workbuddy/memory）。下面这 5 处 min-width:0 是这件事的唯一解，勿删。 */
.topbar, .topbar-left, .topbar-right, .brand, .topbar-title { min-width: 0; }
.topbar-right {
  display: flex; align-items: center; gap: 10px;
  flex: 0 1 auto; justify-content: flex-end;
}

.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 11px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600; letter-spacing: .3px;
  background: var(--brand-soft); color: var(--brand);
  border: 1px solid rgba(34, 224, 255, .28);
  white-space: nowrap;
}
.chip.ghost { background: rgba(255, 255, 255, .03); color: var(--muted); border-color: var(--line); font-weight: 500; }
.chip.danger { background: var(--danger-soft); color: var(--danger); border-color: rgba(255, 77, 109, .34); }
.chip .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--muted-soft); }
.chip .dot.on { background: var(--success); box-shadow: 0 0 8px var(--success); }
.chip .dot.off { background: var(--danger); }
@media (max-width: 900px) { .license-chip { display: none; } }

.user-menu {
  position: relative; display: flex; align-items: center; gap: 8px;
  padding: 5px 11px 5px 5px; border-radius: 999px;
  background: var(--panel-2); border: 1px solid var(--line);
  cursor: pointer; transition: .18s;
}
.user-menu:hover { border-color: var(--line-2); background: rgba(26, 34, 60, .7); }
.user-avatar {
  width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 12px; font-weight: 700; color: #04121a;
  background: linear-gradient(135deg, var(--brand), var(--violet));
  box-shadow: 0 0 12px rgba(34, 224, 255, .35);
}
.user-name { font-size: var(--fs-sm); font-weight: 600; white-space: nowrap; }
.user-caret { font-size: 9px; color: var(--muted-soft); }
.user-dropdown {
  position: absolute; top: calc(100% + 8px); right: 0; min-width: 148px;
  background: #101728; border: 1px solid var(--line-2);
  border-radius: 12px; padding: 5px; box-shadow: var(--glow-soft);
  z-index: 60;
}
.user-dropdown button {
  display: block; width: 100%; text-align: left;
  padding: 8px 11px; border: 0; background: transparent;
  border-radius: 8px; font-size: var(--fs-sm); color: var(--text-mid);
}
.user-dropdown button:hover { background: var(--brand-soft); color: var(--brand); }
.user-dropdown button[data-action="logout"] { color: var(--danger); }
.user-dropdown button[data-action="logout"]:hover { background: var(--danger-soft); color: var(--danger); }

/* ---------- 主体布局 ---------- */
.app-body { flex: 1; display: flex; min-height: 0; position: relative; }

/* ---------- 侧边栏 ---------- */
.sidebar {
  width: var(--sidebar-w); flex: none;
  padding: 14px 12px;
  display: flex; flex-direction: column;
  background: linear-gradient(180deg, rgba(10, 14, 27, .78), rgba(8, 11, 21, .58));
  border-right: 1px solid var(--line);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  overflow-y: auto; overflow-x: hidden;
  position: relative;
  transition: width .22s ease, flex-basis .22s ease;
}
/* 侧栏顶部一道竖向霓虹辉光 */
.sidebar-glow {
  position: absolute; top: 0; left: 0; right: 0; height: 180px;
  background: radial-gradient(120px 130px at 12% 0%, rgba(34, 224, 255, .12), transparent 70%);
  pointer-events: none;
}
.nav-group { display: flex; flex-direction: column; gap: 3px; position: relative; }
.nav-group + .nav-group { margin-top: 15px; }
.nav-group-title {
  font-size: 10.5px; color: var(--muted-soft); letter-spacing: 1.5px;
  padding: 0 10px 6px; text-transform: uppercase; font-family: var(--mono);
  transition: opacity .15s;
}

.nav-item {
  position: relative;
  display: flex; align-items: center; gap: 10px;
  padding: 9px 10px; border-radius: 10px;
  color: var(--text-mid); font-size: var(--fs-sm);
  cursor: pointer; transition: .16s;
}
.nav-item .ni {
  width: 28px; height: 28px; flex: none;
  display: grid; place-items: center;
  font-size: 14px; font-family: var(--emoji); font-style: normal;
  background: rgba(255, 255, 255, .04);
  border: 1px solid var(--line); border-radius: 8px;
  transition: .16s;
}
.nav-item .label { white-space: nowrap; transition: opacity .15s; flex: 1; }
/* 游客态锁标：默认隐藏，受限时才显示 */
.nav-item .lock {
  display: none; font-size: 9.5px; opacity: .5; font-family: var(--emoji);
}
.nav-item:hover { background: rgba(34, 224, 255, .06); color: var(--text); }
.nav-item:hover .ni { border-color: var(--line-3); background: rgba(34, 224, 255, .09); }
.nav-item.active {
  background: linear-gradient(90deg, rgba(34, 224, 255, .15), rgba(123, 92, 255, .07) 62%, transparent);
  color: var(--text); font-weight: 600;
}
.nav-item.active .ni {
  background: linear-gradient(135deg, rgba(34, 224, 255, .26), rgba(123, 92, 255, .24));
  border-color: rgba(34, 224, 255, .5);
  box-shadow: 0 0 16px rgba(34, 224, 255, .3);
}
/* 激活项左侧能量条 */
.nav-item.active::before {
  content: ""; position: absolute; left: -12px; top: 50%;
  transform: translateY(-50%);
  width: 3px; height: 22px; border-radius: 0 3px 3px 0;
  background: linear-gradient(180deg, var(--brand), var(--violet));
  box-shadow: 0 0 12px var(--brand);
}
/* 受限项：降低视觉权重，暗示"登录后可用" */
.nav-item.locked { color: var(--muted-soft); }
.nav-item.locked .lock { display: inline; }
.nav-item.locked:hover { color: var(--text-mid); }

/* 侧栏底部：预告片卡片 + 核心状态 + 收起
   ★底部粘性区：侧栏菜单在 1440×900 下比视口高约 35px，原实现里底部会整体滚出
     可视区 —— 开屏预告片"缩小到左下角"的落点因此看不见（实测复现）。
     改成 sticky 后：菜单区滚动，底部这一块常驻。 */
.nav-foot {
  margin-top: auto; padding-top: 14px; padding-bottom: 2px;
  position: sticky; bottom: 0; z-index: 2;
  background: linear-gradient(180deg, rgba(9, 12, 23, 0) 0%,
              rgba(9, 12, 23, .9) 22%, rgba(9, 12, 23, .97) 100%);
}
.nav-core {
  display: flex; align-items: center; gap: 10px;
  padding: 10px; margin-bottom: 9px;
  background: rgba(34, 224, 255, .05);
  border: 1px solid var(--line); border-radius: 12px;
  overflow: hidden;
}
.nav-core-ring {
  width: 26px; height: 26px; flex: none; border-radius: 50%;
  border: 2px solid transparent;
  border-top-color: var(--brand); border-bottom-color: var(--violet);
  animation: coreSpin 3.6s linear infinite;
}
.nav-core-txt { font-size: 10.5px; color: var(--muted); line-height: 1.4; transition: opacity .15s; }
.nav-core-txt b { color: var(--brand); font-weight: 600; }
.nav-collapse {
  display: flex; align-items: center; gap: 10px;
  width: 100%; padding: 9px 10px;
  background: transparent; border: 1px solid var(--line);
  border-radius: 10px; color: var(--muted); font-size: var(--fs-sm);
  transition: .16s;
}
.nav-collapse:hover { border-color: var(--line-2); color: var(--brand); background: rgba(34, 224, 255, .05); }
.nav-collapse .chev { font-style: normal; transition: transform .22s; }

/* 折叠态 */
body.nav-collapsed .sidebar {
  width: var(--sidebar-w-collapsed);
  flex-basis: var(--sidebar-w-collapsed);
  padding: 14px 10px;
}
body.nav-collapsed .nav-group-title,
body.nav-collapsed .nav-item .label,
body.nav-collapsed .nav-collapse .label,
body.nav-collapsed .nav-core-txt { opacity: 0; width: 0; overflow: hidden; pointer-events: none; }
body.nav-collapsed .nav-group-title { padding: 0; text-align: center; font-size: 9px; }
body.nav-collapsed .nav-group + .nav-group { margin-top: 10px; }
body.nav-collapsed .nav-item { justify-content: center; padding: 9px 0; }
body.nav-collapsed .nav-item::before { left: -10px; }
body.nav-collapsed .nav-collapse { justify-content: center; padding: 9px 0; }
body.nav-collapsed .nav-collapse .chev { transform: rotate(180deg); }
body.nav-collapsed .nav-core { justify-content: center; padding: 10px 0; }

/* ---------- 主内容区 ---------- */
.main {
  flex: 1; min-width: 0;
  overflow-y: auto; overflow-x: hidden;
  padding: 20px 22px 40px;
  position: relative;
}
.page-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin-bottom: 18px;
}
.page-head h2 {
  font-size: var(--fs-xl); font-weight: 700; letter-spacing: .6px;
  background: linear-gradient(96deg, #ffffff 20%, var(--brand) 130%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.page-head .desc { color: var(--muted); font-size: var(--fs-sm); margin-top: 4px; }
.page-actions { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
/* 页头主操作的**触达下限**（P1-H-5 ⑤ = PRD「主操作按钮高度 ≥ 44px」）。
   只作用于页头这一处（`.page-actions` 内的 primary），不动全局 `.btn` ——
   全局改会波及弹窗、表格行内按钮与紧凑区，那些地方 44px 反而把布局撑坏。
   工程依据：WCAG 2.5.5 / iOS HIG 的 44×44 触摸目标；手机上页头主操作
   是「进这一页第一件事要点的按钮」，矮一截就直接点不中。 */
.page-actions .btn.primary { min-height: 44px; }

/* ============================================================================
   按钮 / 表单
   ========================================================================== */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 8px 15px; border-radius: 10px;
  background: var(--panel-2); border: 1px solid var(--line);
  color: var(--text-mid); font-size: var(--fs-sm); font-weight: 500;
  transition: .16s; white-space: nowrap;
}
.btn:hover {
  border-color: var(--line-3); color: var(--text);
  background: rgba(34, 224, 255, .08);
  box-shadow: 0 0 16px rgba(34, 224, 255, .12);
}
.btn.primary {
  background: linear-gradient(120deg, var(--brand), var(--violet));
  border-color: transparent; color: #04121a; font-weight: 650;
  box-shadow: 0 4px 18px rgba(34, 224, 255, .26);
}
.btn.primary:hover { filter: brightness(1.12); box-shadow: 0 6px 26px rgba(34, 224, 255, .4); }
.btn.ghost { background: transparent; }
.btn.danger { color: var(--danger); border-color: rgba(255, 77, 109, .3); }
.btn.danger:hover { background: var(--danger-soft); border-color: var(--danger); }
.btn.sm { padding: 5px 11px; font-size: 12px; border-radius: 8px; }
.btn.block { width: 100%; padding: 11px; font-size: var(--fs-md); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.login-btn { padding: 7px 14px; font-size: var(--fs-sm); }

.field { display: grid; gap: 6px; }
.field span { font-size: var(--fs-sm); color: var(--muted); }
.field input, .field select, .field textarea, .select-sm {
  width: 100%; padding: 9px 12px;
  background: rgba(6, 10, 20, .7);
  border: 1px solid var(--line); border-radius: 10px;
  color: var(--text); font: inherit; font-size: var(--fs-sm);
  transition: .16s;
}
.field textarea { resize: vertical; min-height: 76px; line-height: 1.6; }
.field input::placeholder, .field textarea::placeholder { color: var(--muted-soft); }
.field input:focus, .field select:focus, .field textarea:focus, .select-sm:focus {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(34, 224, 255, .14);
}
.select-sm { width: auto; padding: 6px 10px; font-size: 12.5px; }
/* 原生下拉框的弹层（option）不继承 background/color，Windows Chrome 下会
   渲染成白底浅灰字，在深空霓虹主题里几乎看不见。这里显式指定弹层配色。 */
select option {
  background: #0d1526;
  color: var(--text);
}
select option:checked { background: rgba(34, 224, 255, .18); color: #fff; }
/* 收起状态的下拉框加个箭头，否则用户不知道它可以点开 */
select {
  appearance: none; -webkit-appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%),
linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 16px) calc(50% + 1px),
    calc(100% - 11px) calc(50% + 1px);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 32px; cursor: pointer;
}
select:focus {
  background-image: linear-gradient(45deg, transparent 50%, var(--brand) 50%),
linear-gradient(135deg, var(--brand) 50%, transparent 50%);
}
.form-grid { display: grid; gap: 13px; }
.switch-row {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-sm); color: var(--text-mid); cursor: pointer;
  user-select: none;
}
.switch-row input { accent-color: var(--brand); }

/* ============================================================================
   卡片 / 网格 / KPI
   ========================================================================== */
.grid { display: grid; gap: 14px; }
.grid.two { grid-template-columns: 1fr 1fr; }
.grid.kpi { grid-template-columns: repeat(6, 1fr); }
@media (max-width: 1500px) { .grid.kpi { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 1100px) {
  .grid.two { grid-template-columns: 1fr; }
  .grid.kpi { grid-template-columns: repeat(2, 1fr); }
}

.card {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 16px 18px;
  box-shadow: var(--glow-card);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  transition: border-color .2s, box-shadow .2s, transform .2s;
}
/* 顶部一道极淡的霓虹描边，卡片像一块通电的面板 */
.card::before {
  content: ""; position: absolute; left: 18px; right: 18px; top: -1px; height: 1px;
  background: linear-gradient(90deg, transparent, rgba(34, 224, 255, .45), transparent);
  opacity: .6;
}
.card:hover { border-color: var(--line-2); }
.card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 14px;
}
.card-head h3 { font-size: var(--fs-md); font-weight: 650; letter-spacing: .3px; }
.card-head .sub { font-size: 12px; color: var(--muted); }
.card-head .sub a { color: var(--brand); }
.card-head .sub a:hover { text-decoration: underline; }
.card + .card { margin-top: 0; }

/* ---- 卡片网格：平台接入页用它把各平台排成栅格 ----
   之前 matrix.js 用了 .grid-cards / .card.flat，但这两个类从未定义，
   结果平台卡片没有任何栅格约束，且嵌套 .card 继承父卡 padding 造成层层叠边框。 */
.grid-cards {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
@media (max-width: 640px) { .grid-cards { grid-template-columns: 1fr; } }
/* 嵌在另一张 .card 里的子卡片：去掉自身 margin，压低背景对比，避免嵌套发光 */
.card.flat {
  padding: 14px 15px;
  box-shadow: none;
  background: rgba(255, 255, 255, .022);
}
.card.flat::before { display: none; }
.card.flat:hover { transform: none; }

.kpi {
  position: relative; overflow: hidden;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 14px 15px;
  box-shadow: var(--glow-card);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  transition: transform .2s, border-color .2s, box-shadow .2s;
}
.kpi:hover { transform: translateY(-3px); border-color: var(--line-3); box-shadow: 0 14px 38px rgba(0, 0, 0, .5); }
/* 数字上方的图标水印 */
.kpi::after {
  content: ""; position: absolute; right: -26px; top: -26px;
  width: 88px; height: 88px; border-radius: 50%;
  background: radial-gradient(circle, rgba(34, 224, 255, .13), transparent 68%);
  pointer-events: none;
}
.kpi.accent {
  border-color: rgba(34, 224, 255, .34);
  box-shadow: 0 0 0 1px rgba(34, 224, 255, .1), 0 12px 34px rgba(34, 224, 255, .12);
}
.kpi .label { font-size: 11.5px; color: var(--muted); letter-spacing: .5px; }
.kpi .value {
  font-size: 27px; font-weight: 700; line-height: 1.2; margin: 5px 0 3px;
  background: linear-gradient(100deg, #ffffff, var(--brand) 130%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.kpi .value span { font-size: 14px; color: var(--muted); -webkit-text-fill-color: var(--muted); }
.kpi.accent .value {
  background: linear-gradient(100deg, var(--brand), var(--violet));
  -webkit-background-clip: text; background-clip: text;
}
.kpi .foot { font-size: 11.5px; color: var(--muted-soft); }

.empty {
  padding: 30px 16px; text-align: center;
  color: var(--muted-soft); font-size: var(--fs-sm);
  border: 1px dashed var(--line-2); border-radius: var(--radius);
  background: rgba(255, 255, 255, .015);
}

/* 访客引导卡 */
.guide { text-align: center; padding: 26px 18px; }
.guide-ic {
  width: 54px; height: 54px; margin: 0 auto 12px;
  display: grid; place-items: center; font-size: 25px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(34, 224, 255, .18), transparent 70%);
  border: 1px solid rgba(34, 224, 255, .3);
  animation: guideFloat 3.6s ease-in-out infinite;
}
@keyframes guideFloat {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-6px); }
}
.guide-t { font-size: var(--fs-md); font-weight: 600; color: var(--text); }
.guide-s { font-size: 12.5px; color: var(--muted); margin-top: 6px; }

.notice {
  padding: 10px 13px; border-radius: 10px;
  background: var(--brand-soft);
  border: 1px solid rgba(34, 224, 255, .22);
  color: var(--text-mid); font-size: 12.5px; line-height: 1.66;
  /* 窄屏：通知里常带长 URL / 长错误码（一连串无空格字符），必须允许任意处断行。
     否则整条的 min-content 被它撑大、把页面顶出视口（320px 实测溢出 152px，
     真因是 #ttsAlert 里回显的 wss://…Ocp-Apim-Subscription-Key=… 长 URL）。
     用 anywhere 而非 break-word —— 只有 anywhere 会同时收紧 min-content 尺寸。 */
  overflow-wrap: anywhere; min-width: 0;
}
.notice strong { color: var(--brand); }
.notice-flex { width: 100%; }

.tag {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 9px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600; white-space: nowrap;
  background: rgba(255, 255, 255, .05);
  border: 1px solid var(--line-2); color: var(--muted);
}
.tag.ok, .tag.a { color: var(--success); background: var(--success-soft); border-color: rgba(34, 230, 160, .3); }
.tag.info, .tag.b { color: var(--info); background: var(--info-soft); border-color: rgba(70, 184, 255, .3); }
.tag.warn, .tag.c { color: var(--warn); background: var(--warn-soft); border-color: rgba(255, 181, 69, .3); }
.tag.danger { color: var(--danger); background: var(--danger-soft); border-color: rgba(255, 77, 109, .34); }

/* ============================================================================
   表格 / 分页 / 日志
   ========================================================================== */
.table-wrap { overflow-x: auto; border-radius: var(--radius); }
table.data { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
table.data th {
  text-align: left; padding: 9px 12px;
  font-size: 11.5px; font-weight: 600; color: var(--muted);
  letter-spacing: .6px; white-space: nowrap;
  background: rgba(255, 255, 255, .028);
  border-bottom: 1px solid var(--line);
}
table.data td {
  padding: 10px 12px; border-bottom: 1px solid rgba(120, 165, 255, .07);
  color: var(--text-mid); vertical-align: middle;
}
table.data tbody tr { transition: background .14s; }
table.data tbody tr:hover { background: rgba(34, 224, 255, .045); }
table.data tbody tr:last-child td { border-bottom: 0; }
table.data td.wrap { white-space: normal; min-width: 200px; }

.out-table { width: 100%; border-collapse: collapse; font-size: 12.5px; margin: 6px 0; }
.out-table th, .out-table td {
  padding: 7px 10px; border: 1px solid var(--line);
  text-align: left; color: var(--text-mid);
}
.out-table th { background: rgba(255, 255, 255, .03); color: var(--muted); font-weight: 600; }

.bar {
  height: 6px; border-radius: 999px; overflow: hidden;
  background: rgba(255, 255, 255, .06);
}
.bar i {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--brand), var(--violet));
  box-shadow: 0 0 10px rgba(34, 224, 255, .5);
  transition: width .5s ease;
}
.bar.ok i { background: linear-gradient(90deg, var(--success), #7bf7c4); box-shadow: 0 0 10px rgba(34, 230, 160, .45); }

.pager { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 14px; }
.filters { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.filters input, .filters select {
  padding: 7px 11px; border-radius: 9px;
  background: rgba(6, 10, 20, .7); border: 1px solid var(--line);
  color: var(--text); font: inherit; font-size: 12.5px;
}
.filters input:focus, .filters select:focus {
  outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px rgba(34, 224, 255, .13);
}
.filters input { min-width: 180px; }

.logs { display: flex; flex-direction: column; gap: 5px; max-height: 320px; overflow-y: auto; }
.log {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 7px 10px; border-radius: 9px;
  background: rgba(255, 255, 255, .022);
  border-left: 2px solid var(--line-2);
  font-size: 12.5px; color: var(--text-mid);
}
.log .t { color: var(--muted-soft); font-family: var(--mono); font-size: 11.5px; flex: none; }
.log.warn { border-left-color: var(--warn); }
.log.error { border-left-color: var(--danger); }
.log.ok { border-left-color: var(--success); }

.kv { display: grid; grid-template-columns: auto 1fr; gap: 7px 14px; font-size: var(--fs-sm); }
.kv div:nth-child(odd) { color: var(--muted); }
.kv div:last-child { color: var(--text); }

.legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 12px; color: var(--muted); margin-top: 8px; }
.legend span { display: inline-flex; align-items: center; gap: 6px; }
.legend i { width: 11px; height: 3px; border-radius: 2px; display: inline-block; }

.chart { width: 100%; height: auto; display: block; overflow: visible; }
.chart .grid-line { stroke: rgba(120, 165, 255, .1); stroke-width: 1; }
.chart text { fill: var(--muted-soft); font-size: 11px; font-family: var(--font); }
.chart polyline, .chart polygon { filter: drop-shadow(0 2px 8px rgba(34, 224, 255, .3)); }

/* ============================================================================
   弹窗 / Toast
   ========================================================================== */
.modal {
  position: fixed; inset: 0; z-index: 200;
  display: none; align-items: center; justify-content: center;
  background: rgba(2, 4, 10, .74);
  backdrop-filter: blur(7px); -webkit-backdrop-filter: blur(7px);
  padding: 22px;
}
.modal.show { display: flex; animation: fadeIn .18s ease; }
@keyframes fadeIn { from { opacity: 0; } }
.modal-box {
  width: min(760px, 100%); max-height: 86vh;
  display: flex; flex-direction: column;
  background: linear-gradient(180deg, #111828, #0b1020);
  border: 1px solid var(--line-2); border-radius: var(--radius-lg);
  box-shadow: 0 30px 80px rgba(0, 0, 0, .7), 0 0 0 1px rgba(34, 224, 255, .1);
  animation: modalIn .22s cubic-bezier(.2, .9, .3, 1.2);
}
@keyframes modalIn { from { opacity: 0; transform: translateY(14px) scale(.97); } }
/* 宽版弹窗：扫码授权要并排放二维码 + 步骤说明，默认 760px 会把二维码压到看不清 */
.modal.wide .modal-box { width: min(880px, 100%); }

/* ---- 扫码授权弹窗 ---- */
.scan-wrap { display: flex; gap: 20px; align-items: flex-start; }
.scan-qr {
  flex: 0 0 auto; width: 240px; height: 240px;
  display: flex; align-items: center; justify-content: center;
  background: #fff; border-radius: 14px; padding: 8px;
  box-shadow: 0 0 0 1px var(--line-2), 0 8px 30px rgba(0, 0, 0, .45);
}
.scan-qr img { width: 100%; height: 100%; display: block; image-rendering: pixelated; }
.scan-fallback {
  background: rgba(255, 255, 255, .05); color: var(--muted);
  font-size: 13px; text-align: center; padding: 16px; line-height: 1.6;
}
.scan-info { flex: 1 1 auto; min-width: 0; }
.scan-row {
  display: flex; gap: 8px; font-size: 13.5px;
  padding: 5px 0; border-bottom: 1px dashed var(--line);
}
.scan-row b { color: var(--muted); flex: 0 0 56px; font-weight: 500; }
.scan-row span { color: var(--text); word-break: break-all; }
.scan-step {
  font-size: 13.5px; color: var(--text-mid);
  margin-top: 8px; padding-left: 2px; line-height: 1.6;
}
.scan-step::before { content: "· "; color: var(--brand); }
.scan-expire {
  margin-top: 14px; font-size: 12.5px; color: var(--muted-soft);
}
@media (max-width: 700px) {
  .scan-wrap { flex-direction: column; align-items: center; }
  .scan-qr { width: 200px; height: 200px; }
  .scan-info { width: 100%; }
}
.modal-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px; border-bottom: 1px solid var(--line);
  font-size: var(--fs-md); font-weight: 650;
}
.modal-head button {
  width: 28px; height: 28px; border-radius: 8px;
  background: transparent; border: 1px solid var(--line); color: var(--muted);
}
.modal-head button:hover { border-color: var(--danger); color: var(--danger); }
.modal-body { padding: 16px 18px; overflow-y: auto; flex: 1; font-size: var(--fs-sm); color: var(--text-mid); }
.modal-foot {
  display: flex; justify-content: flex-end; gap: 9px;
  padding: 13px 18px; border-top: 1px solid var(--line);
  /* 允许换行：按钮数量或文案长度不可控（弹窗 footer 由各处调用方拼 HTML），
     nowrap 时内容一旦超过容器宽度就会整块溢出到弹窗外面。 */
  flex-wrap: wrap; row-gap: 9px;
}
/* footer 里的按钮可以被压缩到容器宽度内，超出部分省略号收尾。
   注意：.modal / .modal-box / .modal-foot / .btn 这些 class 名被全站 JS
   依赖（209 处），只能改属性、不能改名。 */
.modal-foot > .btn {
  max-width: 100%; min-width: 0;
  overflow: hidden; text-overflow: ellipsis;
}
.modal-foot:empty { display: none; }
/* 窄屏：按钮各占一行，避免两个按钮挤成两半 */
@media (max-width: 520px) {
  .modal-foot { flex-direction: column-reverse; align-items: stretch; }
  .modal-foot > .btn { width: 100%; }
}

.toast-host {
  position: fixed; z-index: 300; right: 20px; bottom: 20px;
  display: flex; flex-direction: column; gap: 9px; align-items: flex-end;
}
.toast {
  padding: 10px 15px; border-radius: 11px;
  background: rgba(14, 20, 36, .95);
  border: 1px solid var(--line-2);
  color: var(--text); font-size: var(--fs-sm);
  box-shadow: var(--glow-soft);
  animation: toastIn .26s cubic-bezier(.2, .9, .3, 1.2);
  max-width: 380px;
}
@keyframes toastIn { from { opacity: 0; transform: translateX(24px); } }
.toast.ok { border-color: rgba(34, 230, 160, .42); color: var(--success); }
.toast.warn { border-color: rgba(255, 181, 69, .42); color: var(--warn); }
.toast.error { border-color: rgba(255, 77, 109, .42); color: var(--danger); }
.toast.info { border-color: rgba(70, 184, 255, .42); color: var(--info); }

/* ============================================================================
   登录 / 注册对话框（按需唤起，不再拦截首页）
   ========================================================================== */
.auth-gate {
  position: fixed; inset: 0; z-index: 250;
  display: none; align-items: center; justify-content: center;
  background: rgba(2, 4, 10, .72);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  padding: 22px;
}
.auth-gate.show { display: flex; animation: fadeIn .2s ease; }
.auth-card {
  position: relative; width: min(430px, 100%);
  padding: 26px 26px 22px;
  background: linear-gradient(180deg, #121a2e, #0a0f1e);
  border: 1px solid var(--line-2); border-radius: 22px;
  box-shadow: 0 30px 90px rgba(0, 0, 0, .75), 0 0 60px rgba(34, 224, 255, .1);
  animation: modalIn .26s cubic-bezier(.2, .9, .3, 1.2);
}
.auth-card::before {
  content: ""; position: absolute; left: 26px; right: 26px; top: 0; height: 1px;
  background: linear-gradient(90deg, transparent, var(--brand), var(--violet), transparent);
}
.auth-close {
  position: absolute; top: 14px; right: 14px;
  width: 28px; height: 28px; border-radius: 9px;
  background: transparent; border: 1px solid var(--line); color: var(--muted);
}
.auth-close:hover { border-color: var(--danger); color: var(--danger); }
.auth-head { display: flex; align-items: center; gap: 14px; margin-bottom: 18px; }
.auth-avatar {
  width: 50px; height: 50px; flex: none; border-radius: 50%;
  border: 1.5px solid rgba(34, 224, 255, .5);
  box-shadow: 0 0 24px rgba(34, 224, 255, .34);
  overflow: hidden;
}
.auth-avatar img { width: 100%; height: 100%; object-fit: cover; }
.auth-head h1 {
  font-size: 20px; font-weight: 700; letter-spacing: 1px;
  background: linear-gradient(92deg, #fff, var(--brand));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.login-sub { margin: 3px 0 0; color: var(--muted); font-size: 12.5px; }
.auth-tabs {
  display: flex; gap: 4px; padding: 4px; margin-bottom: 16px;
  background: rgba(0, 0, 0, .3); border-radius: 11px;
  border: 1px solid var(--line);
}
.auth-tab {
  flex: 1; padding: 8px; border-radius: 8px;
  background: transparent; border: 0; color: var(--muted); font-size: var(--fs-sm);
  transition: .16s;
}
.auth-tab.on {
  background: linear-gradient(120deg, var(--brand), var(--violet));
  color: #04121a; font-weight: 650;
}
.login-form { display: grid; gap: 13px; }
.login-error { color: var(--danger); font-size: 12.5px; min-height: 17px; }
.login-tip { color: var(--muted-soft); font-size: 11.5px; margin: 2px 0 0; line-height: 1.6; }
/* 会员协议 + 隐私条款勾选（注册 / 激活码开通两种建号路径必填） */
.agree-field { display: block; }
.agree-row { display: flex; align-items: flex-start; gap: 8px; line-height: 1.6; }
.agree-check { width: 15px; height: 15px; margin-top: 2px; accent-color: var(--brand, #22e0ff); flex: none; }
.agree-text { font-size: 12px; color: var(--muted); }
.agree-text a { color: var(--brand, #22e0ff); text-decoration: none; }
.agree-text a:hover { text-decoration: underline; }

/* ============================================================================
   智能体产出（弹窗内）
   ========================================================================== */
.out-summary {
  padding: 12px 14px; border-radius: 11px;
  background: var(--brand-soft); border: 1px solid rgba(34, 224, 255, .22);
  color: var(--text); margin-bottom: 14px; line-height: 1.7;
}
.out-section { margin-bottom: 16px; }
.out-section h4 {
  font-size: var(--fs-sm); color: var(--brand); margin-bottom: 7px;
  display: flex; align-items: center; gap: 7px;
}
.out-section h4::before {
  content: ""; width: 3px; height: 13px; border-radius: 2px;
  background: linear-gradient(180deg, var(--brand), var(--violet));
}
.out-section ul { margin: 0; padding-left: 19px; }
.out-section li { margin: 3px 0; line-height: 1.7; }
.out-note {
  margin-top: 8px; padding: 8px 11px; border-radius: 9px;
  background: var(--warn-soft); border: 1px solid rgba(255, 181, 69, .24);
  color: var(--warn); font-size: 12.5px;
}

/* ============================================================================
   沐雪对话页（系统首页）
   ========================================================================== */
.chat-layout {
  display: grid; grid-template-columns: 268px 1fr; gap: 14px;
  align-items: stretch;
}
@media (max-width: 1000px) { .chat-layout { grid-template-columns: 1fr; } }

/* 3D 形象舞台：像一个全息投影舱 */
.muxue-panel {
  display: flex; flex-direction: column; align-items: center; gap: 9px;
  text-align: center;
  background:
    radial-gradient(300px 220px at 50% 24%, rgba(34, 224, 255, .11), transparent 68%),
    var(--panel);
}
.muxue3d {
  position: relative;
  width: 100%; height: 300px;
  display: grid; place-items: center;
  /* 安全网：任何 canvas 尺寸异常都不会溢出到下方文案上 */
  overflow: hidden;
}
.muxue3d canvas {
  border-radius: 50%;
  filter: drop-shadow(0 0 26px rgba(34, 224, 255, .26));
}
/* 相框/舞台里的「通话」按钮 —— **3D 形象唯一的加载入口**（2026-10-30）。
   桌面（本段）44×44 贴在 300px 舞台的右下角；手机相框在 @media(max-width:900px)
   里覆盖成 38×38（`scripts/ui_mobile_check.js` 的触点判据是 `h<38 或 w<30` 即红，
   所以那个数字是**判据下限**，不是审美）。
   同一份视觉只写一次、只覆盖尺寸与偏移 —— 两处各写一份必然漂移（本仓的老教训）。 */
.muxue3d-call {
  position: absolute; right: 12px; bottom: 12px; z-index: 4;
  width: 44px; height: 44px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--brand);
  color: var(--brand-strong);
  background:
    radial-gradient(120% 120% at 30% 20%, var(--brand-soft), transparent 72%),
    var(--bg-3);
  box-shadow: 0 8px 20px -10px rgba(0, 0, 0, .9);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  /* 呼吸圈 = "这里能点"的唯一暗示。舞台本身就是一张静态图，没有它没人会去点。 */
  animation: mxCallPulse 2.6s ease-in-out infinite;
}
.muxue3d-call svg { width: 21px; height: 21px; }
.muxue3d-call:hover { border-color: var(--brand-strong); }
.muxue3d-call:active { transform: scale(.94); }
/* 加载中：脉冲加快。28MB 可能要等十几秒，界面必须让人看见"它在做事"。 */
.muxue3d-call[data-state="loading"] {
  animation-duration: .9s; opacity: .85; cursor: default;
}
/* 按钮随遮罩淡出后不必再烧动画帧（不可见但仍在跑，白耗电）。 */
.muxue3d .muxue-loading.hide .muxue3d-call { animation: none; }
@keyframes mxCallPulse {
  0%, 100% { box-shadow: 0 8px 20px -10px rgba(0, 0, 0, .9), 0 0 0 0 var(--brand-soft); }
  50%      { box-shadow: 0 8px 20px -10px rgba(0, 0, 0, .9), 0 0 0 7px var(--brand-soft); }
}
/* 舞台底部的一圈光晕，暗示"她站在这里" */
.muxue-panel::after {
  content: ""; position: absolute; bottom: 78px; left: 50%;
  width: 190px; height: 26px; transform: translateX(-50%);
  background: radial-gradient(ellipse, rgba(34, 224, 255, .3), transparent 70%);
  filter: blur(6px); pointer-events: none;
  animation: stageGlow 3.4s ease-in-out infinite;
}
@keyframes stageGlow { 0%, 100% { opacity: .55; } 50% { opacity: .95; } }
.muxue-loading {
  position: absolute; inset: 0; z-index: 2;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 9px; color: var(--muted); font-size: 12.5px;
  background: radial-gradient(circle at 50% 45%, rgba(34, 224, 255, .07), transparent 65%);
  transition: opacity .5s;
}
.muxue-loading img {
  width: 74px; height: 74px; border-radius: 50%; object-fit: cover;
  border: 1.5px solid rgba(34, 224, 255, .42);
  animation: stageGlow 2s ease-in-out infinite;
}
.muxue-loading.hide { opacity: 0; pointer-events: none; }
/* 降级态（WebGL 不可用 / VRM 加载失败 / 12s 超时）：
   把兜底头像放大成一张正经半身像。74px 的圆点摆在 300px 空舞台正中，
   用户只会说"3D 形象坏了"—— 降级态长得像故障，等于没降级。
   类名由 muxue3d.js::_degradeLoading() 加在 #muxue3d 上。 */
.muxue3d.muxue3d-fallback .muxue-loading { background: transparent; }
.muxue3d.muxue3d-fallback .muxue-loading img {
  /* ★2026-10-30：桌面从「一闪而过的加载占位」变成**长期可见的默认形态**
     （3D 改成点通话按钮才加载），所以尺寸从 160×200 提到 190×238，
     在 300px 舞台上更饱满；再用底部渐隐把白底图的硬边和深色舞台接起来，
     否则它看起来就是"贴了一张白卡在舞台上"。 */
  width: 190px; height: 238px; border-radius: 18px;
  object-fit: cover; object-position: 50% 16%;
  border: 1px solid rgba(34, 224, 255, .3);
  box-shadow: 0 12px 34px rgba(0, 0, 0, .42);
  -webkit-mask-image: linear-gradient(180deg, #000 86%, transparent);
  mask-image: linear-gradient(180deg, #000 86%, transparent);
  animation: none;
}
.muxue-id { font-size: 13.5px; font-weight: 650; letter-spacing: .5px; }
.muxue-id span { color: var(--muted); font-weight: 400; font-size: 12px; }

/* 聊天区 */
.chat-box {
  display: flex; flex-direction: column;
  min-height: 430px; max-height: 620px;
  padding: 0; overflow: hidden;
}
.messages {
  flex: 1; overflow-y: auto;
  padding: 18px 18px 8px;
  display: flex; flex-direction: column; gap: 14px;
  scroll-behavior: smooth;
}
.msg { display: flex; gap: 10px; align-items: flex-start; max-width: 100%; }
.msg.me { flex-direction: row-reverse; }
.avatar-mini {
  width: 32px; height: 32px; flex: none;
  border-radius: 50%; overflow: hidden;
  display: grid; place-items: center;
  font-size: 12px; font-weight: 700; color: #04121a;
  background: rgba(255, 255, 255, .07);
  border: 1px solid var(--line-2);
}
.avatar-mini img { width: 100%; height: 100%; object-fit: cover; }
.msg.me .avatar-mini {
  background: linear-gradient(135deg, var(--brand), var(--violet));
  border-color: transparent;
  box-shadow: 0 0 14px rgba(34, 224, 255, .3);
}
.bubble {
  position: relative;
  padding: 11px 15px; border-radius: 14px;
  background: rgba(255, 255, 255, .035);
  border: 1px solid var(--line);
  color: var(--text); font-size: var(--fs-sm); line-height: 1.78;
  white-space: pre-wrap; word-break: break-word;
  max-width: 78%;
  animation: bubbleIn .28s cubic-bezier(.2, .9, .3, 1.1);
}
@keyframes bubbleIn { from { opacity: 0; transform: translateY(8px); } }
.msg:not(.me) .bubble {
  border-bottom-left-radius: 4px;
  border-color: rgba(34, 224, 255, .22);
  background: linear-gradient(135deg, rgba(34, 224, 255, .07), rgba(123, 92, 255, .05));
}
.msg.me .bubble {
  border-bottom-right-radius: 4px;
  background: linear-gradient(135deg, rgba(34, 224, 255, .2), rgba(123, 92, 255, .16));
  border-color: rgba(34, 224, 255, .34);
}
/* 打字光标 */
.bubble.typing { color: var(--muted); }
.bubble.typing::after {
  content: "▋"; color: var(--brand);
  animation: caret .9s steps(2) infinite;
  margin-left: 2px;
}
@keyframes caret { 50% { opacity: 0; } }

/* ── 对话消耗行（2.6.8）：紧贴沐雪回复文字下方 —— 钱花在哪，就说在哪 ─────── */
/* 数值真源＝后端 messages.points_cost（云端账本 sellPoints 逐层回传），前端只显示、不自算 */
.msg-cost {
  margin-top: 9px; padding-top: 7px;
  border-top: 1px dashed var(--line-2);
  font-size: var(--fs-xs); line-height: 1.35;
  font-weight: 600; letter-spacing: .2px;
  color: var(--brand);
}
/* 0 也照实说：这条走的是内置模板回复，本来就没花钱。淡化成中性色，不装作报错 */
.msg-cost.zero { color: var(--muted-soft); font-weight: 500; border-top-color: var(--line); }
/* 计费被拦（账本不可用等）后端会捎原因过来，用警告色，不装作正常 */
.msg-cost.warn { color: var(--warn); border-top-color: var(--warn-soft); }

/* ── 思考过程块（2.12.0）：模型的真思考，从第 1 秒开始逐字流出来 ─────────── */
/* 为什么用 details：思考实测 1490~3307 字，全摊开会把对话区顶掉一整屏；
   默认收起又等于"没输出"。默认展开能读、读完自己收起、标题栏常驻可再展开。 */
.msg-think {
  margin-bottom: 10px; padding: 8px 10px 9px;
  border-radius: 10px;
  /* @cm-fallback: --violet */
  background: rgba(123, 92, 255, .07);
  background: color-mix(in srgb, var(--violet) 7%, transparent);
  /* @cm-fallback: --violet */
  border: 1px solid rgba(123, 92, 255, .22);
  border: 1px solid color-mix(in srgb, var(--violet) 22%, transparent);
  font-size: 12.5px; line-height: 1.72;
}
.msg-think-head {
  display: flex; align-items: center; gap: 6px;
  cursor: pointer; user-select: none; list-style: none;
  font-size: 11.5px; font-weight: 650; letter-spacing: .2px;
  color: var(--violet);
}
.msg-think-head::-webkit-details-marker { display: none; }
/* 自绘展开箭头：原生 marker 在深色主题里几乎看不见，且各浏览器位置不一致 */
.msg-think-head::before {
  content: ""; width: 0; height: 0; flex: none;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform .18s ease;
}
.msg-think[open] > .msg-think-head::before { transform: rotate(90deg); }
.msg-think-body {
  margin-top: 7px;
  color: var(--text-mid);
  white-space: pre-wrap; word-break: break-word;
  /* 思考再长也不许把整屏吃掉：超出 260px 自己在块内滚，
     这样"内容完整"和"对话还能往下翻"两件事同时成立。 */
  max-height: 260px; overflow-y: auto;
}
/* 流式未结束：虚线呼吸感 + 末尾光标，让"还在想"这件事一眼可见 */
.msg-think.live { border-style: dashed; }
.msg-think.live .msg-think-body::after {
  content: "▌";
  margin-left: 1px;
  color: var(--violet);
  animation: caret 1s step-end infinite;
}
.msg-think-hint {
  font-size: var(--fs-xs); color: var(--muted-soft); font-weight: 500;
}

/* 历史对话容器：必须与 .messages 同为纵向 flex、同 14px 间距，
   否则回读的历史消息会互相挤在一起，跟本会话新发的消息疏密不一致 */
#historySlot { display: flex; flex-direction: column; gap: 14px; }
/* 分页按钮居中（否则会被 flex 的 stretch 拉满整行） */
.history-more { align-self: center; margin: 2px 0; }
/* ★2.9.6 游客态提示：`#historySlot` 是 column flex，不设 align-self 会被拉满整行，
   而 `.notice` 自带外边距会把消息区撑出多余的缝；这里贴左、收窄、单行排布。
   ⚠️ 这个类在 `muxue.js::loadHistory()` 里生成，缺定义不会报错、只是"看起来没样式"。 */
#historySlot .history-guest-tip {
  align-self: flex-start; margin: 0; max-width: 100%;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}

/* 快捷词行 + 「清空聊天记录」**同一行**（2026-10-24 调整）。
   原先它们是两个块（.quick-row / .chat-tools）、各占一行、合计约 76px；
   .chat-box 又是固定高度（430~620px）+ .messages 用 flex:1
   ⇒ 底部每多占 1px，上方 8 个智能体卡片就少 1px，实测下半身被裁掉（用户报障）。
   合并后：align-items:center 让两类按钮**垂直居中对齐**（这就是"按钮不平齐"的根治点，
   不能靠给某一类加 margin-top 去凑）；清空按钮 margin-left:auto 靠右。 */
.quick-row {
  display: flex; gap: 7px; flex-wrap: wrap; align-items: center;
  padding: 0 18px 8px;
}
.quick-row > #clearChatBtn { margin-left: auto; color: var(--muted); }
.quick-row > #clearChatBtn:hover { color: var(--danger); border-color: rgba(255, 77, 109, .35); }
.quick-btn {
  padding: 6px 12px; border-radius: 999px;
  background: rgba(255, 255, 255, .03);
  border: 1px solid var(--line);
  color: var(--muted); font-size: 12px;
  transition: .16s;
}
.quick-btn:hover {
  border-color: var(--brand); color: var(--brand);
  background: var(--brand-soft);
  box-shadow: 0 0 14px rgba(34, 224, 255, .16);
}
.composer {
  display: flex; align-items: flex-end; gap: 9px;
  padding: 12px 14px;
  border-top: 1px solid var(--line);
  background: rgba(0, 0, 0, .22);
}
.composer textarea {
  flex: 1; resize: none;
  padding: 10px 14px; min-height: 42px; max-height: 110px;
  background: rgba(6, 10, 20, .72);
  border: 1px solid var(--line); border-radius: 12px;
  color: var(--text); font: inherit; font-size: var(--fs-sm); line-height: 1.6;
  transition: .16s;
}
.composer textarea:focus {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(34, 224, 255, .13), 0 0 20px rgba(34, 224, 255, .1);
}
.composer textarea::placeholder { color: var(--muted-soft); }
.composer .btn { height: 42px; padding: 0 20px; }
/* 输入区上方的工具条（清空聊天记录）。
   单独一行而不是塞进 `.composer`：composer 是 flex 单行，textarea 会分到更少的宽度，
   而这里只有一个低频危险按钮，不值得为它牺牲输入体验。 */
.chat-tools {
  display: flex; justify-content: flex-end; align-items: center;
  padding: 0 18px 8px;
}
.chat-tools .btn { color: var(--muted); }
.chat-tools .btn:hover { color: var(--danger); border-color: rgba(255, 77, 109, .35); }

/* ----------------------------------------------------------------------------
   输入区「文字 ↔ 语音长按」双模式（B 方案，2026-10-24）

   结构：`[切换] [composer-main: textarea | holdTalk] [发送]`
   · `.composer-main` 只是"同一块地方的两个互斥形态"的容器 —— 它必须 `flex:1`
     且 `min-width:0`（后者是 flex 子项能真正收窄的前提，漏了它 textarea 会把
     整行撑破，右侧「发送」被挤出可视区）；
   · 形态由 `.composer[data-mode="voice"]` 驱动，**不在 JS 里逐个 style.display**：
     状态只有一个真源，少一处能对不上的地方；
   · `#holdTalk` 必须够大（44px 高 + 占满宽度）——长按控件做小了，真机上按不住。
   ---------------------------------------------------------------------------- */
.composer-main { flex: 1; display: flex; align-items: stretch; min-width: 0; }
.composer[data-mode="voice"] #textInput { display: none; }
.composer[data-mode="voice"] .hold-talk { display: flex; }

.hold-talk {
  display: none;
  width: 100%; height: 44px;
  align-items: center; justify-content: center;
  padding: 0 14px;
  border-radius: 12px;
  border: 1px solid var(--line);
  /* 与 textarea 同色系：切换模式时视觉上"还是那块地方"，只是换了个形状。 */
  background: rgba(6, 10, 20, .72);
  color: var(--text-mid);
  font: inherit; font-size: var(--fs-sm); font-weight: 600;
  letter-spacing: .04em;
  cursor: pointer;
  transition: .16s;
  /* ★真机三条铁律，缺一条就录不上音：
     touch-action:none   手指按住时浏览器不许滚动/缩放抢手势（否则 pointercancel 立刻到）；
     user-select:none    长按不许选中文字（选中会打断 pointermove）；
     -webkit-touch-callout:none  苹果端长按不许弹"拷贝/查询"气泡。 */
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.hold-talk:hover { border-color: var(--line-2); color: var(--text); }
.hold-talk:disabled { cursor: wait; opacity: .85; }
/* 录音中：红色 + 脉冲（复用 TTS 那套 recPulse，全站"正在录音"只有一种语言）。 */
.hold-talk.is-recording {
  border-color: var(--danger); color: var(--danger);
  background: var(--danger-soft);
  animation: recPulse 1.1s ease-in-out infinite;
}
/* 上滑超过阈值：**实心红**，且停掉脉冲 —— 此时已经不可能误判成"正在录音"，
   继续闪反而让人以为还能松手发送。 */
.hold-talk.is-cancel {
  border-color: var(--danger); color: #fff;
  background: rgba(255, 77, 109, .34);
  animation: none;
}
/* 正在识别：中性灰 + 不可点（`disabled` 已经拦住事件，这里只管看起来像在忙）。 */
.hold-talk.is-busy {
  border-color: var(--line); color: var(--muted);
  background: rgba(6, 10, 20, .72);
  animation: none;
}
@keyframes recPulse { 50% { box-shadow: 0 0 0 5px rgba(255, 77, 109, .1); } }

/* ============================================================================
   智能体卡片 / 协同矩阵
   ========================================================================== */
.agent-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 10px; }
.agent-card {
  padding: 11px 13px; border-radius: 12px;
  background: rgba(255, 255, 255, .028);
  border: 1px solid var(--line);
  transition: .18s;
}
.agent-card .top {
  display: flex; align-items: center; gap: 8px;
  font-size: 12.5px; font-weight: 600; color: var(--text-mid);
}
.agent-card .top span:first-child { font-family: var(--emoji); font-size: 14px; }
.agent-card .top .status {
  margin-left: auto; font-size: 11px; color: var(--muted-soft); font-weight: 500;
}
.agent-card.running {
  border-color: rgba(34, 224, 255, .4);
  background: linear-gradient(135deg, rgba(34, 224, 255, .09), transparent);
  box-shadow: 0 0 22px rgba(34, 224, 255, .13);
  animation: agentBreathe 2.4s ease-in-out infinite;
}
@keyframes agentBreathe { 0%, 100% { box-shadow: 0 0 18px rgba(34, 224, 255, .1); } 50% { box-shadow: 0 0 30px rgba(34, 224, 255, .22); } }
.agent-card.running .status { color: var(--brand); }
.agent-card.done { border-color: rgba(34, 230, 160, .3); }
.agent-card.done .status { color: var(--success); }
.agent-card.failed { border-color: rgba(255, 77, 109, .34); }
.agent-card.failed .status { color: var(--danger); }
.agent-card.clickable { cursor: pointer; }
.agent-card.clickable:hover {
  border-color: var(--line-3); transform: translateY(-2px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, .4);
}

/* ============================================================================
   智能体思维链
   ========================================================================== */
.cot-tabs { display: flex; gap: 6px; margin-bottom: 13px; flex-wrap: wrap; }
.cot-tab {
  padding: 6px 15px; border-radius: 999px;
  background: transparent; border: 1px solid var(--line);
  color: var(--muted); font-size: var(--fs-sm); font-weight: 500; transition: .16s;
}
.cot-tab:hover { border-color: var(--line-3); color: var(--text); }
.cot-tab.on {
  background: linear-gradient(120deg, rgba(34, 224, 255, .2), rgba(123, 92, 255, .16));
  border-color: rgba(34, 224, 255, .45); color: var(--brand);
  box-shadow: 0 0 16px rgba(34, 224, 255, .16);
}
.cot-tab b {
  display: inline-block; margin-left: 5px; padding: 0 6px;
  border-radius: 999px; background: rgba(255, 255, 255, .07);
  font-size: 11px; font-weight: 600;
}
.cot-tab.on b { background: rgba(34, 224, 255, .2); }

/* ---------- 协作拓扑 ---------- */
.cot-topo {
  position: relative; width: 100%; aspect-ratio: 1000 / 580;
  min-height: 420px;
  background:
    radial-gradient(700px 340px at 50% 42%, rgba(34, 224, 255, .07), transparent 70%),
    rgba(0, 0, 0, .24);
  border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden;
}
.cot-edges { position: absolute; inset: 0; width: 100%; height: 100%; }
.cot-edge {
  stroke-width: 1.3; fill: none; opacity: .42; transition: opacity .25s;
}
.cot-edge.cmd { stroke: var(--brand); }
.cot-edge.data { stroke: var(--violet); }
.cot-edge.guard { stroke: var(--danger); }
.cot-edge.report { stroke: var(--muted); }
.cot-edge.live {
  opacity: 1; stroke-width: 2.1; stroke-dasharray: 7 5;
  animation: cotFlow 1.1s linear infinite;
  filter: drop-shadow(0 0 5px currentColor);
}
@keyframes cotFlow { to { stroke-dashoffset: -24; } }

.cot-node-layer { position: absolute; inset: 0; }
.cot-node {
  position: absolute; transform: translate(-50%, -50%);
  width: 118px; padding: 8px 6px;
  border-radius: 12px; text-align: center;
  background: rgba(12, 17, 32, .9);
  border: 1px solid var(--line);
  cursor: pointer; transition: .18s;
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
}
.cot-node:hover {
  border-color: var(--brand); transform: translate(-50%, -50%) scale(1.05);
  box-shadow: 0 0 26px rgba(34, 224, 255, .26);
}
.cot-node.orch {
  width: 130px;
  border-color: rgba(34, 224, 255, .42);
  background: linear-gradient(180deg, rgba(34, 224, 255, .14), rgba(12, 17, 32, .92));
  box-shadow: 0 0 26px rgba(34, 224, 255, .2);
}
.cot-node-av { font-size: 20px; line-height: 1.2; font-family: var(--emoji); }
.cot-node-name { font-size: 11.5px; font-weight: 600; color: var(--text); margin-top: 2px; line-height: 1.3; }
.cot-node-badge { font-size: 10px; color: var(--muted-soft); margin-top: 3px; }
.cot-node.has-thought .cot-node-badge { color: var(--brand); font-weight: 600; }
.cot-node.active { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(34, 224, 255, .16), 0 0 30px rgba(34, 224, 255, .3); }
.cot-pulse {
  position: absolute; inset: -4px; border-radius: 15px; pointer-events: none;
  border: 2px solid rgba(34, 224, 255, .55);
  animation: cotPulse 1.5s ease-out infinite;
}
@keyframes cotPulse {
  0% { transform: scale(.96); opacity: .85; }
  100% { transform: scale(1.12); opacity: 0; }
}
.cot-legend {
  position: absolute; left: 12px; bottom: 10px;
  display: flex; gap: 12px; flex-wrap: wrap;
  font-size: 11px; color: var(--muted);
}
.cot-lg { display: inline-flex; align-items: center; gap: 5px; }
.cot-lg i { width: 16px; height: 0; border-top: 2px solid; display: inline-block; }
.cot-lg.cmd i { border-color: var(--brand); }
.cot-lg.data i { border-color: var(--violet); }
.cot-lg.guard i { border-color: var(--danger); border-top-style: dashed; }
.cot-lg.report i { border-color: var(--muted); border-top-style: dotted; }
.cot-lg.assist i { border-color: var(--brand); border-top-style: dotted; opacity: .55; }

/* 按需调度边：辅助岗专用。刻意比 command 更淡、更碎 ——
   它表达的是"回调才动"，不该和"开工固定下达"一样抢眼。 */
.cot-edge.assist { stroke: var(--brand); opacity: .2; stroke-dasharray: 1 4; }
.cot-edge-glow.assist { stroke: var(--brand); }

/* ---------- 流动光带 ----------
   每条边画两层：底层静态关系线 + 这层短划线光带。
   只用一层的话静止时整张图是死的；只做流动则看不出"边还在"。
   光带 pointer-events:none，不能挡住节点的点击（否则点节点跳思维流会失效）。 */
.cot-edge-glow {
  fill: none; stroke-width: 3.4; stroke-linecap: round;
  stroke-dasharray: 2 62; opacity: 0; pointer-events: none;
  animation: cotRun 3.2s linear infinite;
}
.cot-edge-glow.cmd { stroke: var(--brand); }
.cot-edge-glow.data { stroke: var(--violet); }
.cot-edge-glow.guard { stroke: var(--danger); }
.cot-edge-glow.report { stroke: var(--muted); }
.cot-edge-glow.live { stroke-width: 5; animation-duration: 1.05s; }
@keyframes cotRun {
  0% { stroke-dashoffset: 64; opacity: 0; }
  14% { opacity: .9; }
  86% { opacity: .9; }
  100% { stroke-dashoffset: 0; opacity: 0; }
}

/* 节点入场：只动 opacity/filter，刻意**不碰 transform** ——
   transform 上的 translate(-50%,-50%) 是定位基准，动画一旦接管它，
   hover 的 scale(1.05) 就会被 animation-fill-mode:both 永久压住（点了没反应）。 */
@keyframes cotNodeIn { from { opacity: 0; filter: brightness(2.2); } }
.cot-node { animation: cotNodeIn .42s cubic-bezier(.22, .9, .32, 1) both; }

/* 辅助岗：独立的支撑带节点，虚线边 + 右上角「辅助」角标。
   视觉上必须与执行岗一眼可分 —— 否则用户仍会问"它怎么一直待命"。 */
.cot-node.assist {
  width: 108px; border-style: dashed; border-color: var(--line-2);
  background: rgba(10, 15, 28, .84);
}
.cot-node.assist.has-thought { border-style: solid; }
.cot-node-tag {
  position: absolute; top: -8px; right: -5px;
  padding: 0 6px; border-radius: 999px; line-height: 15px;
  font-size: 9.5px; color: var(--brand);
  background: rgba(34, 224, 255, .14);
  border: 1px solid rgba(34, 224, 255, .3);
}

/* ---------- 设计链条（执行流水线）----------
   与拓扑图分工：拓扑答「谁认识谁」（网状），链条答「活怎么一步步往下走」（线性）。 */
.cot-chain { display: flex; flex-direction: column; gap: 12px; }
.cot-chain-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.cot-chain-title { font-size: var(--fs-sm); font-weight: 650; color: var(--text); }
.cot-chain-sub { font-size: 11.5px; color: var(--muted-soft); }
.cot-chain-prog { margin-left: auto; font-size: 11.5px; color: var(--muted); }
.cot-chain-prog b { color: var(--brand); font-size: 14px; font-weight: 700; }
.cot-chain-track {
  display: flex; align-items: stretch;
  overflow-x: auto; padding: 6px 2px 8px;
  scrollbar-width: thin;
}
.cot-chain-stage {
  position: relative; flex: 1 1 0; min-width: 100px;
  display: flex; flex-direction: column;
  padding: 12px 7px 10px; border-radius: 12px; text-align: center;
  background: rgba(255, 255, 255, .026);
  border: 1px solid var(--line);
  animation: cotNodeIn .4s ease both;
  animation-delay: calc(var(--i) * 70ms);
}
.cot-chain-idx {
  position: absolute; top: 6px; left: 8px;
  font-size: 9.5px; letter-spacing: .5px;
  color: var(--muted-soft); font-variant-numeric: tabular-nums;
}
.cot-chain-avs { display: flex; justify-content: center; margin-bottom: 5px; }
.cot-chain-av { display: inline-grid; place-items: center; margin: 0 -4px; }
.cot-chain-av img {
  width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid var(--line-2); object-fit: cover;
}
/* name 吃掉剩余高度：段名一行 / 两行都不一样长，若不用 flex 撑开，
   状态徽标会跟着段名行数上下跳，7 段看着像没对齐。 */
.cot-chain-name {
  flex: 1 1 auto;
  font-size: 11.5px; font-weight: 600; color: var(--text); line-height: 1.35;
  word-break: break-word;
}
.cot-chain-state {
  display: inline-block; margin-top: 5px; padding: 1px 8px;
  border-radius: 999px; font-size: 10px;
  color: var(--muted-soft);
  background: rgba(255, 255, 255, .04); border: 1px solid var(--line);
}
.cot-chain-note {
  margin-top: 6px; font-size: 10px; line-height: 1.5; color: var(--muted-soft);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.cot-chain-stage.done {
  border-color: rgba(34, 230, 160, .34);
  background: rgba(34, 230, 160, .05);
}
.cot-chain-stage.done .cot-chain-state {
  color: var(--success); background: var(--success-soft);
  border-color: rgba(34, 230, 160, .3);
}
.cot-chain-stage.thinking { border-color: rgba(34, 224, 255, .34); }
.cot-chain-stage.thinking .cot-chain-state {
  color: var(--brand); background: var(--brand-soft);
  border-color: rgba(34, 224, 255, .3);
}
.cot-chain-stage.live { box-shadow: 0 0 22px rgba(34, 224, 255, .16); }
.cot-chain-link {
  position: relative; flex: 0 0 12px; align-self: center;
  height: 2px; border-radius: 2px; background: var(--line);
}
.cot-chain-link.on {
  background: linear-gradient(90deg, rgba(34, 224, 255, .12), rgba(34, 224, 255, .6));
}
.cot-chain-link.on::after {
  content: ""; position: absolute; top: -2px; left: -6px;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--brand); box-shadow: 0 0 8px var(--brand);
  animation: cotRunLine 1.6s linear infinite;
}
@keyframes cotRunLine {
  0% { left: -6px; opacity: 0; }
  20% { opacity: 1; }
  80% { opacity: 1; }
  100% { left: 12px; opacity: 0; }
}
.cot-chain-foot { font-size: 10.5px; line-height: 1.6; color: var(--muted-soft); }
.cot-chain-foot code {
  padding: 0 4px; border-radius: 4px; font-size: 10px;
  background: rgba(255, 255, 255, .06); color: var(--muted);
}

/* ---------- 思维流 ---------- */
.cot-filters { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; }
.cot-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 12px; border-radius: 999px;
  background: transparent; border: 1px solid var(--line);
  color: var(--muted); font-size: var(--fs-xs); transition: .16s;
}
.cot-chip:hover { border-color: var(--line-3); color: var(--text); }
.cot-chip.on {
  background: linear-gradient(120deg, rgba(34, 224, 255, .2), rgba(123, 92, 255, .14));
  border-color: rgba(34, 224, 255, .42); color: var(--brand);
}
.cot-chip b { padding: 0 5px; border-radius: 999px; background: rgba(255, 255, 255, .07); font-size: 10.5px; }

.cot-stream { display: flex; flex-direction: column; gap: 13px; }
.cot-card {
  padding: 13px 15px; border-radius: 13px;
  background: rgba(255, 255, 255, .026);
  border: 1px solid var(--line);
  transition: .2s;
}
.cot-card.active {
  border-color: rgba(34, 224, 255, .4);
  background: linear-gradient(135deg, rgba(34, 224, 255, .07), transparent);
  box-shadow: 0 0 24px rgba(34, 224, 255, .12);
}
.cot-card-head { display: flex; align-items: center; gap: 8px; margin-bottom: 11px; flex-wrap: wrap; }
.cot-card-av { font-size: 17px; font-family: var(--emoji); }
.cot-card-name { font-size: var(--fs-sm); font-weight: 650; color: var(--text); }
.cot-card-role {
  padding: 1px 8px; border-radius: 999px;
  font-size: 10.5px; color: var(--brand);
  background: var(--brand-soft); border: 1px solid rgba(34, 224, 255, .26);
}
.cot-card-count { font-size: 11px; color: var(--muted-soft); margin-left: auto; }
.cot-card-live {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 1px 8px; border-radius: 999px;
  font-size: 10.5px; color: var(--success);
  background: var(--success-soft); border: 1px solid rgba(34, 230, 160, .3);
}
.cot-card-live::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: var(--success); animation: cotBlink 1s ease-in-out infinite;
}
@keyframes cotBlink { 50% { opacity: .25; } }

.cot-th-list { display: flex; flex-direction: column; }
.cot-th { display: flex; gap: 10px; animation: cotIn .32s ease both; }
@keyframes cotIn { from { opacity: 0; transform: translateY(5px); } }
.cot-th-rail { flex: 0 0 24px; display: flex; flex-direction: column; align-items: center; }
.cot-th-dot {
  width: 24px; height: 24px; flex: none; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 12px; font-family: var(--emoji);
  /* @cm-fallback: --tone */
  background: rgba(34, 224, 255, .16);
  background: color-mix(in srgb, var(--tone) 16%, transparent);
  /* @cm-fallback: --tone */
  border: 1px solid rgba(34, 224, 255, .40);
  border: 1px solid color-mix(in srgb, var(--tone) 40%, transparent);
}
.cot-th-rail::after {
  content: ""; flex: 1; width: 1.5px; margin: 3px 0;
  /* @cm-fallback: --tone */
  background: linear-gradient(180deg, rgba(34, 224, 255, .34), transparent);
  background: linear-gradient(180deg, color-mix(in srgb, var(--tone) 34%, transparent), transparent);
}
.cot-th:last-child .cot-th-rail::after { display: none; }
.cot-th-body { flex: 1; min-width: 0; padding-bottom: 11px; }
.cot-th-head { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.cot-th-kind {
  padding: 1px 8px; border-radius: 999px;
  font-size: 10.5px; font-weight: 600;
  color: var(--tone);
  /* @cm-fallback: --tone */
  background: rgba(34, 224, 255, .13);
  background: color-mix(in srgb, var(--tone) 13%, transparent);
  /* @cm-fallback: --tone */
  border: 1px solid rgba(34, 224, 255, .30);
  border: 1px solid color-mix(in srgb, var(--tone) 30%, transparent);
}
.cot-th-title { font-size: var(--fs-sm); font-weight: 600; color: var(--text); }
.cot-th-ai {
  padding: 1px 7px; border-radius: 999px;
  font-size: 10px; color: var(--violet);
  background: rgba(123, 92, 255, .14); border: 1px solid rgba(123, 92, 255, .3);
}
.cot-th-detail {
  font-size: 12.5px; line-height: 1.78; color: var(--text-mid); margin-top: 4px;
  white-space: pre-wrap; word-break: break-word;
}

.cot-handoffs {
  padding: 12px 14px; border-radius: 12px;
  background: rgba(0, 0, 0, .22);
  border: 1px dashed var(--line-2);
}
.cot-sec-title { font-size: var(--fs-sm); font-weight: 650; color: var(--text); margin-bottom: 9px; }
.cot-ho {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  padding: 5px 0; border-bottom: 1px dashed rgba(120, 165, 255, .1);
  font-size: 11.5px; color: var(--text-mid);
}
.cot-ho:last-child { border-bottom: 0; }
.cot-ho-arrow { color: var(--muted-soft); }
.cot-ho-l {
  margin-left: auto; padding: 1px 8px; border-radius: 999px;
  font-size: 10.5px; color: var(--muted-soft);
  background: rgba(255, 255, 255, .04); border: 1px solid var(--line);
}
.cot-ho.guard .cot-ho-l { color: var(--danger); border-color: rgba(255, 77, 109, .3); background: var(--danger-soft); }
.cot-ho.cmd .cot-ho-l { color: var(--brand); border-color: rgba(34, 224, 255, .3); background: var(--brand-soft); }

/* ---------- AI 工作群（群聊时间线） ---------- */
.cot-chat { display: flex; flex-direction: column; gap: 14px; }
.cot-chat-head {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  padding: 9px 13px; border-radius: 11px;
  background: rgba(255, 255, 255, .03); border: 1px solid var(--line);
}
.cot-chat-title { font-size: var(--fs-sm); font-weight: 650; color: var(--text); }
.cot-chat-sub { font-size: 11px; color: var(--muted-soft); }
.cot-chat-members { display: flex; align-items: center; margin-left: auto; }
.cot-chat-m {
  width: 24px; height: 24px; border-radius: 50%; flex: none;
  display: grid; place-items: center; margin-left: -6px;
  font-size: 12px; font-family: var(--emoji); font-style: normal;
  background: rgba(255, 255, 255, .05); border: 1px solid var(--line-2);
}
.cot-chat-m img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; display: block; }

.cot-msg { display: flex; gap: 10px; animation: cotIn .32s ease both; }
.cot-msg-av {
  flex: 0 0 30px; width: 30px; height: 30px; border-radius: 50%; overflow: hidden;
  display: grid; place-items: center;
  font-size: 15px; font-family: var(--emoji);
  /* @cm-fallback: --tone */
  background: rgba(34, 224, 255, .16);
  background: color-mix(in srgb, var(--tone, #22e0ff) 16%, transparent);
  /* @cm-fallback: --tone */
  border: 1px solid rgba(34, 224, 255, .34);
  border: 1px solid color-mix(in srgb, var(--tone, #22e0ff) 34%, transparent);
}
.cot-msg-av img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cot-msg-wrap { flex: 1; min-width: 0; }
.cot-msg-meta { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-bottom: 4px; }
.cot-msg-meta b { font-size: var(--fs-sm); font-weight: 650; color: var(--text); }
.cot-msg-meta time { margin-left: auto; font-size: 10.5px; color: var(--muted-soft); }
.cot-msg-role {
  padding: 1px 7px; border-radius: 999px; font-size: 10px;
  color: var(--brand); background: var(--brand-soft);
  border: 1px solid rgba(34, 224, 255, .26);
}
.cot-msg-tag {
  padding: 1px 8px; border-radius: 999px; font-size: 10.5px; font-weight: 600;
  color: var(--tone, #22e0ff);
  /* @cm-fallback: --tone */
  background: rgba(34, 224, 255, .13);
  background: color-mix(in srgb, var(--tone, #22e0ff) 13%, transparent);
  /* @cm-fallback: --tone */
  border: 1px solid rgba(34, 224, 255, .30);
  border: 1px solid color-mix(in srgb, var(--tone, #22e0ff) 30%, transparent);
}
.cot-msg-ai {
  padding: 1px 7px; border-radius: 999px; font-size: 10px; color: var(--violet);
  background: rgba(123, 92, 255, .14); border: 1px solid rgba(123, 92, 255, .3);
}
.cot-msg-bubble {
  padding: 9px 13px; border-radius: 4px 13px 13px 13px;
  background: rgba(255, 255, 255, .032);
  border: 1px solid var(--line);
  /* @cm-fallback: --tone */
  border-left: 2px solid rgba(34, 224, 255, .46);
  border-left: 2px solid color-mix(in srgb, var(--tone, #22e0ff) 46%, transparent);
}
.cot-msg.live .cot-msg-bubble {
  border-color: rgba(34, 224, 255, .34);
  box-shadow: 0 0 18px rgba(34, 224, 255, .1);
}
.cot-msg-title { font-size: var(--fs-sm); font-weight: 600; color: var(--text); }
.cot-msg-text {
  font-size: 12.5px; line-height: 1.76; color: var(--text-mid); margin-top: 3px;
  white-space: pre-wrap; word-break: break-word;
}
/* 交接消息：@某人 + 交付物标签，读起来就是"我在群里把活交出去了" */
.cot-msg.ho .cot-msg-bubble {
  background: rgba(123, 92, 255, .07);
  border-left-color: rgba(123, 92, 255, .5);
}
.cot-msg-to { font-size: 12.5px; font-weight: 600; color: var(--violet); margin-right: 6px; }
.cot-msg-ho-label {
  padding: 1px 8px; border-radius: 999px; font-size: 10.5px; color: var(--muted-soft);
  background: rgba(255, 255, 255, .04); border: 1px solid var(--line);
}

@media (max-width: 900px) {
  .cot-topo { min-height: 300px; aspect-ratio: 1000 / 620; }
  .cot-node { width: 92px; }
  .cot-node.orch { width: 104px; }
  .cot-node.assist { width: 88px; }
  .cot-node-name { font-size: 10.5px; }
  .cot-msg-av { flex-basis: 26px; width: 26px; height: 26px; font-size: 13px; }
  .cot-msg-bubble { padding: 8px 11px; }
  .cot-chat-members { margin-left: 0; }
  /* 窄屏：链条横排会被挤成一字长条，改成两列换行并去掉连接线
     （连接线在换行后指向不明，留着反而误导读顺序）。 */
  .cot-chain-track { flex-wrap: wrap; gap: 8px; }
  .cot-chain-link { display: none; }
  .cot-chain-stage { flex: 1 1 46%; min-width: 0; }
}

/* ============================================================================
   内容工厂：标准运营工作流
   ========================================================================== */
.sow-flow {
  display: flex; gap: 6px; overflow-x: auto; padding-bottom: 8px;
}
.sow-step {
  position: relative; flex: 1; min-width: 108px;
  padding: 12px 12px 11px; border-radius: 12px;
  background: rgba(255, 255, 255, .028);
  border: 1px solid var(--line);
  transition: .2s;
}
.sow-step:hover { border-color: var(--line-3); transform: translateY(-2px); }
.sow-step.done {
  border-color: rgba(34, 230, 160, .34);
  background: linear-gradient(160deg, rgba(34, 230, 160, .09), transparent);
}
.sow-step.running {
  border-color: rgba(34, 224, 255, .5);
  background: linear-gradient(160deg, rgba(34, 224, 255, .13), transparent);
  box-shadow: 0 0 26px rgba(34, 224, 255, .18);
  animation: agentBreathe 2.2s ease-in-out infinite;
}
.sow-step-n {
  width: 21px; height: 21px; border-radius: 7px;
  display: grid; place-items: center;
  font-size: 11px; font-weight: 700; font-family: var(--mono);
  background: rgba(255, 255, 255, .07); color: var(--muted);
  margin-bottom: 7px;
}
.sow-step.done .sow-step-n { background: var(--success-soft); color: var(--success); border: 1px solid rgba(34, 230, 160, .32); }
.sow-step.running .sow-step-n {
  background: linear-gradient(135deg, var(--brand), var(--violet));
  color: #04121a; box-shadow: 0 0 14px rgba(34, 224, 255, .4);
}
.sow-step-t {
  font-size: 12.5px; font-weight: 650; color: var(--text); margin-bottom: 4px;
  /* 给左上角头像让位 */
  padding-left: 38px;
}
.sow-step-d { font-size: 11px; color: var(--muted); line-height: 1.55; }
.sow-step-c {
  position: absolute; top: 10px; right: 11px;
  font-size: 10.5px; color: var(--muted-soft); font-family: var(--mono);
}

/* 工作流运行详情 */
.sow-run { padding: 15px 0; }
.sow-run-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.sow-run-title { font-size: var(--fs-md); font-weight: 650; }
.sow-run-sub { font-size: 12px; color: var(--muted); }
.sow-run-stat { display: flex; gap: 16px; flex-wrap: wrap; font-size: 12px; color: var(--muted); }
.sow-run-stat b { color: var(--brand); font-size: 15px; font-weight: 700; margin-right: 3px; }

/* 任务时间线 */
.sow-timeline { position: relative; margin-top: 14px; padding-left: 22px; }
.sow-timeline::before {
  content: ""; position: absolute; left: 6px; top: 4px; bottom: 4px;
  width: 1px; background: linear-gradient(180deg, var(--brand), var(--violet), transparent);
  opacity: .4;
}
.sow-tl-row {
  position: relative; padding: 9px 12px; margin-bottom: 8px;
  border-radius: 11px;
  background: rgba(255, 255, 255, .024);
  border: 1px solid var(--line);
  transition: .18s;
}
.sow-tl-row:hover { border-color: var(--line-2); background: rgba(34, 224, 255, .04); }
.sow-tl-row::before {
  content: ""; position: absolute; left: -21px; top: 15px;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--bg-2); border: 2px solid var(--muted-soft);
}
.sow-tl-row.ok::before { border-color: var(--success); box-shadow: 0 0 10px var(--success); }
.sow-tl-row.running::before {
  border-color: var(--brand); box-shadow: 0 0 12px var(--brand);
  animation: cotPulse 1.5s ease-out infinite;
}
.sow-tl-row.failed::before { border-color: var(--danger); }
.sow-tl-who { font-size: 12.5px; font-weight: 650; color: var(--text); display: flex; align-items: center; gap: 7px; }
.sow-tl-ic { font-family: var(--emoji); font-size: 14px; }
.sow-tl-stage { font-size: 11px; color: var(--brand); background: var(--brand-soft); padding: 1px 7px; border-radius: 999px; }
.sow-tl-ms { margin-left: auto; font-size: 11px; color: var(--muted-soft); font-family: var(--mono); }
.sow-tl-sum { font-size: 12.5px; color: var(--text-mid); margin-top: 5px; line-height: 1.7; }

/* 任务看板 */
.sow-board {
  display: flex; flex-direction: column; gap: 7px;
  margin-top: 12px; padding: 13px;
  background: rgba(0, 0, 0, .22); border-radius: 13px;
  border: 1px solid var(--line);
}
.sow-board-row { display: flex; align-items: center; gap: 11px; font-size: 12.5px; }
.sow-board-n { flex: none; width: 20px; color: var(--muted-soft); font-family: var(--mono); font-size: 11px; }
.sow-board-b { flex: 1; }

/* ============================================================================
   AI 员工花名册
   ========================================================================== */
.crew-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; }
.crew-card {
  padding: 14px 15px; border-radius: 14px;
  background: var(--panel);
  border: 1px solid var(--line);
  transition: .2s;
  position: relative; overflow: hidden;
}
.crew-card:hover {
  border-color: rgba(34, 224, 255, .38);
  transform: translateY(-3px);
  box-shadow: 0 14px 38px rgba(0, 0, 0, .45), 0 0 26px rgba(34, 224, 255, .12);
}
.crew-top { display: flex; align-items: center; gap: 11px; margin-bottom: 10px; }
.crew-av {
  width: 42px; height: 42px; flex: none; border-radius: 13px;
  display: grid; place-items: center; font-size: 21px; font-family: var(--emoji);
  background: linear-gradient(135deg, rgba(34, 224, 255, .16), rgba(123, 92, 255, .14));
  border: 1px solid rgba(34, 224, 255, .26);
}
.crew-name { font-size: var(--fs-sm); font-weight: 650; color: var(--text); }
.crew-role { font-size: 11px; color: var(--brand); margin-top: 2px; }
.crew-id { font-size: 10px; color: var(--muted-soft); font-family: var(--mono); margin-top: 2px; }
.crew-duty { font-size: 12.5px; color: var(--text-mid); line-height: 1.7; }
.crew-out {
  margin-top: 9px; padding: 8px 11px; border-radius: 10px;
  background: rgba(0, 0, 0, .24); border: 1px solid var(--line);
  font-size: 12px; color: var(--text-mid); line-height: 1.65;
}
.crew-stat {
  display: flex; gap: 14px; flex-wrap: wrap;
  margin-top: 10px; font-size: 11.5px; color: var(--muted);
}
.crew-stat b { color: var(--brand); font-weight: 700; }
.crew-badge {
  position: absolute; top: 12px; right: 13px;
  padding: 1px 8px; border-radius: 999px;
  font-size: 10.5px; color: var(--success);
  background: var(--success-soft); border: 1px solid rgba(34, 230, 160, .3);
}

/* ============================================================================
   作品墙 / 内容卡片
   ========================================================================== */
.gal-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 14px; }
.gal-card {
  display: flex; flex-direction: column;
  border-radius: 15px; overflow: hidden;
  background: var(--panel);
  border: 1px solid var(--line);
  transition: .2s;
}
.gal-card:hover {
  border-color: var(--line-3);
  box-shadow: 0 14px 40px rgba(0, 0, 0, .5), 0 0 26px rgba(34, 224, 255, .1);
  transform: translateY(-2px);
}
.gal-card-top { padding: 12px 14px 10px; display: flex; align-items: flex-start; gap: 9px; }
.gal-card-title { font-size: var(--fs-sm); font-weight: 650; color: var(--text); line-height: 1.5; }
.gal-media {
  position: relative; background: rgba(0, 0, 0, .3);
  display: grid; place-items: center; overflow: hidden;
}
.gal-media img, .gal-media video {
  width: 100%; height: 100%; object-fit: contain; display: block;
}
.gal-media-empty { color: var(--muted-soft); font-size: 12px; padding: 34px 0; }
.gal-media-badge {
  position: absolute; top: 10px; left: 10px;
  padding: 2px 9px; border-radius: 999px;
  font-size: 10.5px; font-weight: 600;
  background: rgba(4, 8, 16, .8); color: var(--muted);
  border: 1px solid var(--line-2);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);   /* Safari/iOS 只认带前缀的（2026-10-30 补齐） */
}
.gal-media-badge.ready { color: var(--success); border-color: rgba(34, 230, 160, .4); }
.gal-media-badge.live { color: var(--brand); border-color: rgba(34, 224, 255, .44); animation: cotBlink 1.6s ease-in-out infinite; }
.gal-media-badge.fallback { color: var(--warn); border-color: rgba(255, 181, 69, .4); }
.gal-hook {
  padding: 10px 14px; font-size: 12px; color: var(--text-mid);
  line-height: 1.7; border-top: 1px solid var(--line);
  background: rgba(0, 0, 0, .18);
}
.gal-note { padding: 0 14px 10px; font-size: 11.5px; color: var(--muted); }
.gal-card-foot {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  padding: 11px 14px; margin-top: auto;
  border-top: 1px solid var(--line);
}
.gal-stat { font-size: 11px; color: var(--muted-soft); margin-left: auto; }
.gal-voice { font-size: 11.5px; color: var(--violet); }
.gal-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
.gal-title { font-size: var(--fs-md); font-weight: 650; }
.gal-sub { font-size: 12px; color: var(--muted); margin-top: 3px; }
.gal-list { display: flex; flex-direction: column; gap: 9px; }
.gal-items { display: flex; gap: 7px; flex-wrap: wrap; }
.gal-batch { display: flex; flex-direction: column; gap: 14px; }
.gal-block { border: 1px solid var(--line); border-radius: 14px; padding: 13px 15px; background: rgba(255, 255, 255, .022); }
.gal-block-t { font-size: var(--fs-sm); font-weight: 650; color: var(--brand); margin-bottom: 9px; }
.gal-block-b { font-size: 12.5px; color: var(--text-mid); line-height: 1.75; white-space: pre-wrap; }
.gal-score { font-size: 12px; color: var(--warn); }

.media-strip { display: flex; gap: 8px; flex-wrap: wrap; }
.media-thumb {
  position: relative; width: 96px; height: 128px;
  border-radius: 10px; overflow: hidden;
  border: 1px solid var(--line); background: rgba(0, 0, 0, .3);
}
.media-thumb img { width: 100%; height: 100%; object-fit: cover; }

/* ============================================================================
   平台人格卡 / 连接中心 / 能力卡
   ========================================================================== */
.plat-grid, .cap-grid, .conn-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(258px, 1fr)); gap: 12px;
}
.plat-card, .cap-card, .conn-card {
  padding: 14px 15px; border-radius: 14px;
  background: var(--panel);
  border: 1px solid var(--line);
  transition: .2s;
  position: relative; overflow: hidden;
}
.plat-card:hover, .cap-card:hover, .conn-card:hover {
  border-color: var(--line-3); transform: translateY(-2px);
  box-shadow: 0 12px 32px rgba(0, 0, 0, .42);
}
.plat-card.on, .conn-card.on { border-color: rgba(34, 224, 255, .45); box-shadow: 0 0 24px rgba(34, 224, 255, .14); }
.plat-top, .cap-top, .conn-top { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.plat-top .cap-icon, .cap-icon, .conn-icon {
  width: 36px; height: 36px; flex: none; border-radius: 11px;
  display: grid; place-items: center; font-size: 18px; font-family: var(--emoji);
  background: linear-gradient(135deg, rgba(34, 224, 255, .15), rgba(123, 92, 255, .13));
  border: 1px solid rgba(34, 224, 255, .24);
}
.plat-persona, .cap-desc {
  font-size: 12.5px; color: var(--text-mid); line-height: 1.72;
  padding: 9px 11px; border-radius: 10px;
  background: rgba(0, 0, 0, .22); border: 1px solid var(--line);
}
.plat-tags { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 9px; }
.plat-tags span {
  padding: 2px 9px; border-radius: 999px;
  font-size: 11px; color: var(--violet);
  background: rgba(123, 92, 255, .12); border: 1px solid rgba(123, 92, 255, .26);
}
.conn-name { font-size: var(--fs-sm); font-weight: 650; color: var(--text); }
.conn-sub { font-size: 11.5px; color: var(--muted); margin-top: 2px; }
.conn-ok {
  width: 8px; height: 8px; border-radius: 50%; background: var(--muted-soft);
  box-shadow: 0 0 8px currentColor;
}
.conn-ok.ready { background: var(--success); }
.conn-meta, .conn-cost { font-size: 11.5px; color: var(--muted); margin-top: 7px; }
.conn-cost b { color: var(--warn); }
.conn-btns { display: flex; gap: 7px; flex-wrap: wrap; margin-top: 11px; }
.conn-steps { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.conn-step {
  display: flex; gap: 8px; align-items: flex-start;
  font-size: 11.5px; color: var(--text-mid); line-height: 1.6;
}
.conn-step::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: var(--brand); margin-top: 6px; flex: none;
  box-shadow: 0 0 7px var(--brand);
}
.conn-scopes { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 9px; }
.conn-scopes span {
  padding: 1px 7px; border-radius: 6px; font-size: 10.5px;
  font-family: var(--mono); color: var(--muted);
  background: rgba(255, 255, 255, .04); border: 1px solid var(--line);
}
.conn-summary {
  padding: 12px 14px; border-radius: 12px; margin-bottom: 13px;
  background: var(--brand-soft); border: 1px solid rgba(34, 224, 255, .24);
}
.conn-summary-tip { font-size: 12px; color: var(--muted); margin-top: 4px; }
.conn-foot { font-size: 11.5px; color: var(--muted-soft); margin-top: 9px; }
.cap-flow {
  margin-top: 10px; padding-top: 10px;
  border-top: 1px dashed var(--line-2);
  font-size: 11.5px; color: var(--muted); line-height: 1.7;
}
.provider-pick { display: flex; gap: 7px; flex-wrap: wrap; margin-bottom: 12px; }
.provider-opt {
  padding: 6px 13px; border-radius: 999px;
  background: transparent; border: 1px solid var(--line);
  color: var(--muted); font-size: 12.5px; transition: .16s;
}
.provider-opt:hover { border-color: var(--line-3); color: var(--text); }
.provider-opt.on {
  background: linear-gradient(120deg, rgba(34, 224, 255, .2), rgba(123, 92, 255, .14));
  border-color: rgba(34, 224, 255, .45); color: var(--brand);
}
.provider-opt img { width: 16px; height: 16px; vertical-align: -3px; margin-right: 4px; }

/* ============================================================================
   漏斗 / 分步指标（任务详情）
   ========================================================================== */
.funnel { display: flex; flex-direction: column; gap: 8px; }
.fstep {
  display: flex; align-items: center; gap: 12px;
  font-size: 12.5px; color: var(--text-mid);
}
.fstep .val {
  flex: none; width: 76px; text-align: right;
  color: var(--brand); font-weight: 650;
}
.tiny { font-size: 11.5px; color: var(--muted-soft); }

/* ============================================================================
   响应式：窄屏折叠为抽屉式菜单
   ========================================================================== */
@media (max-width: 900px) {
  .nav-toggle { display: flex; }
  .sidebar {
    position: absolute; top: 0; bottom: 0; left: 0; z-index: 90;
    transform: translateX(-100%);
    transition: transform .24s ease;
    box-shadow: 20px 0 50px rgba(0, 0, 0, .55);
  }
  body.nav-expanded .sidebar { transform: translateX(0); }
  .main { padding: 16px 14px 32px; }
  .grid.kpi { grid-template-columns: repeat(2, 1fr); }
  .chat-box { min-height: 380px; }
}
@media (max-width: 560px) {
  .grid.kpi { grid-template-columns: 1fr; }
  .page-head h2 { font-size: 19px; }
  .brand-sub { display: none; }
}

/* ============================================================================
   手机端（第 9 条需求）：触控尺寸 / 可读字号 / 抽屉遮罩 / 紧凑顶栏
   只调属性，不改任何 class 名（既有约定：class 名被全站 JS 依赖）。
   ========================================================================== */
@media (max-width: 900px) {
  /* 触控目标：32px 高的按钮在手机上几乎点不准（iOS HIG 44pt / Material 48dp） */
  .btn.sm { min-height: 38px; padding: 8px 13px; }
  .cot-tab { padding: 9px 15px; }
  .quick-btn { padding: 9px 13px; }
  .nav-toggle { width: 40px; height: 40px; }
  /* label 包 input 的开关行：真实可点区是整行，22px 高太矮 */
  .switch-row { min-height: 38px; }
  /* 字号下限：10.5px 在 3x 屏上太小，读不清 */
  .nav-group-title, .nav-core-txt, .cot-node-name, .brand-sub,
  .sow-step-c, .gal-media-badge { font-size: 11.5px; }
  /* 下拉/输入统一到 ≥38px：手机上 35px 的 select 常常点不准 */
  .select-sm, .field input, .field select, .filters input, .filters select {
    min-height: 38px;
  }

  /* 抽屉遮罩：z-index 必须低于 .sidebar(90)，只挡内容不挡菜单 */
  .nav-backdrop {
    display: none; position: absolute; inset: 0; z-index: 85;
    background: rgba(2, 4, 10, .58);
    -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
  }
  body.nav-expanded .nav-backdrop { display: block; }
}
@media (max-width: 640px) {
  /* 紧凑顶栏：把右侧挤成「图标 + 头像」，否则 360px 宽仍放不下 */
  .topbar { gap: 8px; padding: 0 12px 0 10px; }
  .brand { gap: 8px; }
  .brand-core, .topbar-logo { width: 32px; height: 32px; }
  .topbar-right { gap: 6px; }
  #userMenu { padding: 4px; }
  #userMenu .user-name, #userMenu .user-caret { display: none; }
  /* 「实时」/「流云」只留图标：名字与文本在 title、抽屉抬头、下拉菜单里都还在 */
  .hide-on-narrow { display: none; }
  /* minmax(300px,1fr) 在 <300px 的容器里会溢出，手机上一律单列 */
  .gal-cards { grid-template-columns: 1fr; }
  .modal { padding: 14px; }
}
@media (max-width: 420px) {
  /* 极窄屏再让一步：实时状态条移出顶栏（同源信息在侧栏「核心在线」里） */
  #streamChip { display: none; }
}

/* ============================================================================
   语义色辅助类（替代原先散落在 JS 里的浅色内联样式）
   ========================================================================== */
.notice-ok {
  background: var(--success-soft);
  border-color: rgba(34, 230, 160, .3);
  color: var(--success);
}
.notice-danger {
  background: var(--danger-soft);
  border-color: rgba(255, 77, 109, .32);
  color: var(--danger);
}
.notice-warn {
  background: var(--warn-soft);
  border-color: rgba(255, 181, 69, .3);
  color: var(--warn);
}

/* ==========================================================================
   大模型接入状态横幅（沐雪对话页）
   未接上大模型时必须让用户一眼看到，否则会误以为在跟智能体自由对话，
   实际只匹配到内置规则引擎的固定话术。
   ========================================================================== */
.llm-banner {
  width: 100%;
  margin: 10px 0 2px;
  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid rgba(255, 181, 69, .38);
  background: var(--warn-soft);
  text-align: left;
  animation: lbIn .35s ease;
}
.llm-banner[hidden] { display: none; }
.llm-banner .lb-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--warn);
  margin-bottom: 6px;
}
.llm-banner .lb-reason {
  font-size: 12px;
  color: var(--text);
  line-height: 1.6;
  margin-bottom: 6px;
}
.llm-banner .lb-tip {
  font-size: 11px;
  color: var(--muted);
  line-height: 1.6;
  margin-bottom: 10px;
}
@keyframes lbIn {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: none; }
}
.kpi .value.tone-danger { background: none; -webkit-text-fill-color: var(--danger); color: var(--danger); }
.kpi .value.tone-warn { background: none; -webkit-text-fill-color: var(--warn); color: var(--warn); }
.kpi .value.tone-ok { background: none; -webkit-text-fill-color: var(--success); color: var(--success); }
.kpi .value.tone-muted { background: none; -webkit-text-fill-color: var(--muted); color: var(--muted); }
.kpi .value.tone-danger, .kpi .value.tone-warn, .kpi .value.tone-ok { text-shadow: 0 0 18px currentColor; }
.tab-tip { font-size: 12px; color: var(--muted); }
.grow { flex: 1; }
.arrow { color: var(--muted-soft); }

/* ============================================================================
   工作流阶段头像（身份色描边 + 淡染，深色底不发灰）
   ========================================================================== */
.sow-avatar {
  width: 30px; height: 30px; flex: none;
  display: grid; place-items: center;
  border-radius: 10px; border: 1px solid var(--line-2);
  font-size: 15px; font-family: var(--emoji);
  margin-bottom: 8px;
  transition: .2s;
}
.sow-step:hover .sow-avatar { transform: scale(1.08) rotate(-4deg); }
.crew-badge.busy {
  color: var(--brand); background: var(--brand-soft);
  border-color: rgba(34, 224, 255, .34);
  animation: cotBlink 1.6s ease-in-out infinite;
}
/* 头像在左上角占位，序号退到标题行，不再叠在一起 */
.sow-step { padding-top: 14px; }
.sow-step .sow-avatar { position: absolute; top: 11px; left: 12px; margin-bottom: 0; }
.sow-step .sow-step-n {
  position: absolute; top: 12px; right: 11px; margin-bottom: 0;
}

/* ============================================================================
   首屏能力速览（对话开始前的自我介绍）
   ========================================================================== */
.abilities {
  margin-top: 4px;
  padding: 16px 18px;
  border-radius: 16px;
  background: linear-gradient(150deg, rgba(34, 224, 255, .06), rgba(123, 92, 255, .05) 55%, transparent);
  border: 1px solid var(--line);
  animation: abIn .6s cubic-bezier(.2, .9, .3, 1.05) both;
}
@keyframes abIn { from { opacity: 0; transform: translateY(12px); } }
.ab-head { margin-bottom: 13px; }
.ab-title {
  display: block; font-size: var(--fs-md); font-weight: 650; color: var(--text);
  letter-spacing: .3px;
}
.ab-sub { display: block; font-size: 12.5px; color: var(--muted); margin-top: 3px; }
.ab-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(226px, 1fr)); gap: 9px;
}
.ab-card {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 12px; border-radius: 12px;
  background: rgba(255, 255, 255, .026);
  border: 1px solid var(--line);
  transition: .18s;
}
.ab-card:hover {
  border-color: rgba(34, 224, 255, .36);
  background: rgba(34, 224, 255, .05);
  transform: translateY(-2px);
  box-shadow: 0 8px 22px rgba(0, 0, 0, .38);
}
.ab-ic {
  width: 30px; height: 30px; flex: none;
  display: grid; place-items: center;
  border-radius: 9px; font-size: 15px; font-family: var(--emoji);
  background: linear-gradient(135deg, rgba(34, 224, 255, .15), rgba(123, 92, 255, .13));
  border: 1px solid rgba(34, 224, 255, .24);
}
.ab-name { font-size: 12.5px; font-weight: 650; color: var(--text); }
.ab-duty { font-size: 11.5px; color: var(--muted); line-height: 1.6; margin-top: 2px; }

/* ---------------- 腾讯会议接入与会后纪要 ---------------- */
/* 入会链接：可点击的会议号必须在暗底上明确可点 */
.link {
  color: var(--brand);
  text-decoration: none;
  /* @cm-fallback: --brand */
  border-bottom: 1px dashed rgba(34, 224, 255, .45);
  border-bottom: 1px dashed color-mix(in srgb, var(--brand) 45%, transparent);
  transition: color .15s, border-color .15s;
}
.link:hover { color: #7ef0ff; border-bottom-color: var(--brand); }

/* 纪要正文：长文本要限制行宽、提高行距，否则一整块字挤在一起没法读 */
.minutes-body {
  max-width: 68ch;
  max-height: 52vh;
  overflow-y: auto;
  padding: 16px 18px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, .026);
  font-size: var(--fs-sm);
  line-height: 2;
  color: var(--text);
  word-break: break-word;
}

/* 表格内的小字补充说明：与主内容拉开层级，别抢焦点 */
.table-wrap td .tiny { display: block; margin-top: 3px; line-height: 1.6; }
.table-wrap td .tiny.notice-danger { display: inline-block; margin-top: 4px; }

/* 接入横幅里的操作行 */
.notice .btn.sm { margin-top: 2px; }

/* ============================================================================
   AI 员工 / 智能体固定头像（半身像，web/img/characters/<id>.webp）
   ------------------------------------------------------------------------
   全部为**追加**规则：不改动任何既有 class 名与既有选择器。
   app.css 里有 200+ 个 class 被 JS 依赖，重命名会直接打断页面。

   为什么要这一段：头像位原来是纯 emoji（.sow-avatar / .crew-av 里的文字），
   emoji 在 30px 下糊成一团、且各平台字形不一致（Windows 的 🎬 与 macOS 的
   🎬 长得不一样），无法承担「每个员工一个专属形象」的辨识职责。
   中间曾用过程序化 SVG（128 矢量首字圆牌），但它只够当占位；现在是
   640x883 的动漫半身像，「固定」由 scripts/_avatar_golden.json 的 sha256
   快照守着（换图必须同批重新签入，不再靠"重新生成比对"）。
   ============================================================================ */

/* 通用：矢量头像填满头像位并保持圆形。
   emoji 兜底路径下是一个文本节点，本规则对它不生效，无需额外判断。 */
.av-img,
.agent-card .ag-av img,
.cot-node-av img,
.cot-card-av img,
.cot-ho-av img,
.out-av img {
  width: 100%; height: 100%;
  object-fit: cover;
  border-radius: 50%;
  display: block;
}

/* 流程条头像位：容器仍是圆角方（保持既有版式），图裁成圆形居中 */
.sow-avatar .av-img { width: 26px; height: 26px; border-radius: 50%; }
/* 头像位本体加一点内缩留白，避免矢量图的描边贴住边框显得挤 */
.sow-avatar { overflow: hidden; }

/* 花名册卡头像位 */
.crew-av { overflow: hidden; }
.crew-av .av-img { width: 38px; height: 38px; }

/* 任务看板里的智能体卡：原来的 emoji 直接放在 .top 的第一个 span 里，
   现在给它一个固定尺寸的圆形容器（.ag-av），避免 img 被 flex 压扁。 */
.agent-card .ag-av {
  width: 26px; height: 26px; flex: none;
  border-radius: 50%; overflow: hidden;
  display: inline-grid; place-items: center;
  font-family: var(--emoji); font-size: 14px;
  border: 1px solid var(--line-2);
  background: rgba(255, 255, 255, .05);
}

/* 思维链：拓扑节点 / 智能体卡 / 交接记录 */
.cot-node-av { display: grid; place-items: center; }
.cot-node-av img { width: 34px; height: 34px; }
.cot-card-av { display: inline-grid; place-items: center; vertical-align: -6px; }
.cot-card-av img { width: 24px; height: 24px; }
.cot-ho-av {
  display: inline-grid; place-items: center; vertical-align: -5px;
  width: 18px; height: 18px; margin-right: 3px;
  border-radius: 50%; overflow: hidden;
}
.cot-ho-av img { width: 18px; height: 18px; }

/* 智能体产出弹窗顶部的「谁产出的」 */
.out-who {
  display: flex; align-items: center; gap: 11px;
  margin-bottom: 12px; padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
}
.out-av {
  width: 40px; height: 40px; flex: none;
  border-radius: 50%; overflow: hidden;
  display: grid; place-items: center;
  background: rgba(255, 255, 255, .06);
  border: 1px solid var(--line-2);
}
.out-av img { width: 40px; height: 40px; }
.out-av-ico { font-family: var(--emoji); font-size: 20px; }
.out-who-n { font-size: var(--fs-sm); font-weight: 650; color: var(--text); }
.out-who-s { font-size: 11px; color: var(--muted-soft); margin-top: 2px; }

/* ============================================================================
   微信扫码进入 / 输入激活码 浮层（新增；不改动既有 class 名与配色变量）
   视觉沿用 .auth-gate 的深空霓虹语言，但独立成 .gate/.gate-card，互不影响。
   ========================================================================== */
.gate {
  position: fixed; inset: 0; z-index: 260;
  display: none; align-items: center; justify-content: center;
  background: rgba(2, 4, 10, .72);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  padding: 22px;
}
.gate.show { display: flex; animation: fadeIn .2s ease; }
.gate-card {
  position: relative; width: min(430px, 100%);
  padding: 24px 24px 20px;
  background: linear-gradient(180deg, #121a2e, #0a0f1e);
  border: 1px solid var(--line-2); border-radius: 22px;
  box-shadow: 0 30px 90px rgba(0, 0, 0, .75), 0 0 60px rgba(34, 224, 255, .1);
  animation: modalIn .26s cubic-bezier(.2, .9, .3, 1.2);
}
.gate-card::before {
  content: ""; position: absolute; left: 24px; right: 24px; top: 0; height: 1px;
  background: linear-gradient(90deg, transparent, var(--brand), var(--violet), transparent);
}
.gate-close {
  position: absolute; top: 14px; right: 14px;
  width: 28px; height: 28px; border-radius: 9px;
  background: transparent; border: 1px solid var(--line); color: var(--muted);
}
.gate-close:hover { border-color: var(--danger); color: var(--danger); }
.gate-head { display: flex; align-items: center; gap: 13px; margin-bottom: 16px; }
.gate-head-ic {
  width: 46px; height: 46px; flex: none; border-radius: 14px;
  display: grid; place-items: center; font-size: 22px;
  background: var(--brand-soft); border: 1px solid rgba(34, 224, 255, .3);
  box-shadow: 0 0 24px rgba(34, 224, 255, .22);
}
.gate-head h2 {
  font-size: 18px; font-weight: 700; letter-spacing: .5px;
  background: linear-gradient(92deg, #fff, var(--brand));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.gate-sub { margin: 3px 0 0; color: var(--muted); font-size: 12.5px; line-height: 1.6; }
.gate-error { color: var(--danger); font-size: 12.5px; min-height: 17px; margin-top: 4px; }
.gate-tip { color: var(--muted-soft); font-size: 11.5px; margin: 8px 0 0; line-height: 1.6; }

/* ============ 激活码浮层：两个选项卡（2.9.4） ============
   合并了原来「套餐激活码」与「体验码」两个浮层。这里只做"分段控件"的观感：
   外层一条内凹的槽，选中的那个 tab 用品牌青高亮。
   ⚠️ `.code-pane` 刻意**不写** `display` —— 面板显隐由 JS 切 `.hidden`
   （`.hidden` 是 `display:none !important`，见 :87）。若这里给 `.code-pane`
   写 `display:block`，两者特异性相同、靠先后顺序决胜，一旦有人调换顺序
   就会出现"两个面板同时显示"（本项目 `.nav-backdrop` 踩过同一族的坑）。 */
.code-gate { width: min(470px, 100%); }
.code-tabs {
  display: flex; gap: 6px; margin: 0 0 16px; padding: 4px;
  border-radius: 12px; border: 1px solid var(--line-2);
  background: rgba(10, 22, 38, .72);
}
.code-tab {
  flex: 1 1 0; min-width: 0; padding: 8px 10px;
  border: 1px solid transparent; border-radius: 9px; cursor: pointer;
  background: transparent; color: var(--muted);
  font: inherit; font-size: 13px; font-weight: 600; line-height: 1.2;
  transition: background .18s ease, color .18s ease, border-color .18s ease;
}
.code-tab:hover { color: var(--text); }
.code-tab:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.code-tab.is-active {
  background: var(--brand-soft); color: var(--brand);
  border-color: rgba(34, 224, 255, .32);
}
/* 面板本身只占满宽度；**显隐一律靠 `.hidden`**（理由见上一条注释）。 */
.code-pane { width: 100%; }

/* 扫码浮层：左码右引导 */
.qr-card { width: min(580px, 100%); }
.qr-body {
  display: grid; grid-template-columns: 216px 1fr; gap: 18px; align-items: center;
}
@media (max-width: 620px) {
  .qr-body { grid-template-columns: 1fr; justify-items: center; text-align: center; }
}
.qr-canvas-host {
  width: 216px; height: 216px; display: grid; place-items: center;
  background: #fff; border-radius: 14px; border: 1px solid var(--line-2);
  overflow: hidden; padding: 8px;
}
.qr-canvas-host img {
  width: 100%; height: 100%; object-fit: contain;
  image-rendering: pixelated; display: block;
}
/* ★过期/失效后必须让码"扫不动"（2026-10-28 线上实测）：旧实现只把倒计时那行改成
   "二维码已过期"，而二维码图片仍然完整可扫 ⇒ 用户直接扫了屏幕上这个已经不新鲜的码，
   手机上落到「这张二维码已过期或已失效」页，看起来像登录坏了。
   置灰 + 禁用指针，让"这个码不能用了"在视觉上成立，不靠用户去读那行小字。 */
.qr-canvas-host.is-expired {
  opacity: .28; filter: grayscale(1); pointer-events: none;
}
.qr-placeholder { color: #5b6788; font-size: 12.5px; text-align: center; padding: 0 12px; line-height: 1.6; }
.qr-side { display: flex; flex-direction: column; gap: 8px; }
.qr-step { color: var(--text-mid); font-size: 13px; }
.qr-state { margin-top: 4px; color: var(--brand); font-size: 13px; font-weight: 600; }
.qr-state.err { color: var(--danger); }
.qr-state.ok { color: var(--success); }
.qr-countdown { color: var(--muted); font-size: 12.5px; min-height: 16px; }
/* ★本地/内网地址提示（2026-10-28）：二维码指向 127.0.0.1 时，手机扫了必然打不开。
   这条不是"备注"，是**当前状态下唯一能解释白屏的那句话**，所以用警示色而不是灰色。 */
.qr-local-warn {
  margin-top: 10px; padding: 10px 12px; border-radius: 10px;
  border: 1px solid var(--warn); background: rgba(240, 180, 41, .12);
  color: var(--warn); font-size: 12.5px; line-height: 1.65;
}
.qr-countdown.warn { color: var(--warn); }
.qr-degrade {
  margin-top: 14px; padding: 9px 12px; border-radius: 10px;
  background: var(--warn-soft); border: 1px solid rgba(255, 181, 69, .28);
  color: var(--warn); font-size: 12.5px; line-height: 1.6;
}
.qr-error {
  margin-top: 14px; padding: 9px 12px; border-radius: 10px;
  background: var(--danger-soft); border: 1px solid rgba(255, 77, 109, .28);
  color: var(--danger); font-size: 12.5px; line-height: 1.6;
}
.qr-foot { display: flex; gap: 10px; margin-top: 18px; flex-wrap: wrap; }
.qr-foot .btn { flex: 1 1 auto; }

/* 扫码浮层内的「体验码」临时入口（测试期旁路，视觉上克制、次要：
   小字 + 次要色 + 一条极淡分隔线，不与主流程二维码抢注意力）。 */
.qr-exp {
  margin-top: 16px; padding-top: 14px;
  border-top: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 10px;
}
.qr-exp-toggle {
  align-self: flex-start;
  background: transparent; border: 0; padding: 0;
  color: var(--muted-soft); font-size: 12px; cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px;
  transition: color .16s;
}
.qr-exp-toggle:hover { color: var(--brand); }
.qr-exp-body { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.qr-exp-body input {
  flex: 1 1 160px; min-width: 0;
  padding: 9px 12px;
  background: rgba(6, 10, 20, .7);
  border: 1px solid var(--line); border-radius: 10px;
  color: var(--text); font: inherit; font-size: var(--fs-sm);
  transition: .16s;
}
.qr-exp-body input::placeholder { color: var(--muted-soft); }
.qr-exp-body input:focus {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(34, 224, 255, .14);
}
.qr-exp-body .btn { flex: 0 0 auto; }

/* 顶栏「输入激活码」弱化小入口 */
.activate-chip { cursor: pointer; }
.activate-chip:hover { border-color: var(--line-3); color: var(--text); }
/* 顶栏「体验码」常驻入口（#chipExp，游客 + 登录都可见 → #/exp 免密落地页）。
   刻意按 **id** 写而不是新造 class：`.chip` 本身不带 cursor（与 .activate-chip 同一缺口），
   而本项目铁律是"HTML/JS 里用了但 CSS 没定义的 class 不报错、不告警，只是静默没样式"
   —— 所以能不加新 class 名就不加，样式只补交互态。 */
#chipExp { cursor: pointer; }
#chipExp:hover { border-color: var(--line-3); color: var(--text); }
.scan-enter-btn { padding: 7px 14px; font-size: var(--fs-sm); }

/* 对话流中的「输入激活码」引导行（need_upgrade 分支） */
.need-upgrade { margin: 8px 0 0 40px; }
.need-upgrade .btn { font-size: 12.5px; padding: 7px 14px; }


/* ============================================================================
   智能体 / AI 员工 —— 卡牌模式（半身像 + 昵称 + 岗位 + 状态）
   ------------------------------------------------------------------------
   全部为**追加**规则：不改动任何既有 class 名与既有选择器。
   .cards 为新 modifier；.char / .char-* 为全新 class（app.css 中此前不存在），
   与既有规则零冲突。卡牌主色统一走内联 --tone。
   ============================================================================ */

/* 卡牌网格：比默认更紧凑，容纳竖版半身像 */
.agent-grid.cards,
.crew-grid.cards {
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 14px;
}

/* 卡牌本体：去掉内边距，让半身像贴顶铺满；圆角交由面板统一裁切 */
.agent-grid.cards .agent-card,
.crew-grid.cards .crew-card {
  padding: 0;
  overflow: hidden;
}

/* 卡牌纵向排布 + 悬浮上浮 + 以 --tone 描一层光晕 */
.char {
  display: flex; flex-direction: column;
  transition: .2s;
}
.char:hover {
  transform: translateY(-4px);
  /* @cm-fallback: --tone */
  box-shadow: 0 14px 38px rgba(0, 0, 0, .45), 0 0 22px rgba(34, 224, 255, .30);
  box-shadow: 0 14px 38px rgba(0, 0, 0, .45),
              0 0 22px color-mix(in srgb, var(--tone, #22e0ff) 30%, transparent);
}

/* 半身像：640×883 竖版比例，贴顶裁切 */
.char-portrait {
  position: relative;
  aspect-ratio: 640 / 883;
  overflow: hidden;
  border-radius: 12px 12px 0 0;
  background: rgba(255, 255, 255, .04);
}
.char-portrait .char-img {
  display: block; width: 100%; height: 100%;
  object-fit: cover; object-position: top center;
}
/* 缺图兜底：撑满头像位、居中放大 emoji */
.char-fallback {
  display: grid; place-items: center;
  width: 100%; height: 100%;
  font-size: 46px; font-family: var(--emoji);
  color: var(--muted);
  background: rgba(255, 255, 255, .04);
}

/* 头像与文字之间的身份色分隔光条 */
.char-glow {
  height: 3px; flex: none;
  background: linear-gradient(90deg, var(--tone, #22e0ff), transparent);
}

/* 文字区 */
.char-body { padding: 10px 13px; }
.char-nick {
  font-size: 17px; font-weight: 700;
  color: var(--tone, var(--brand));
  line-height: 1.25; letter-spacing: .3px;
  word-break: break-word;
}
.char-role { font-size: 12px; color: var(--muted); margin-top: 2px; }

/* 花名册卡：卡牌自身 padding 已归零，职责 / 统计 / 交付物改为贴边内缩 */
.crew-grid.cards .char-body { padding: 10px 13px 0; }
.crew-grid.cards .crew-duty { padding: 9px 13px 0; }
.crew-grid.cards .crew-stat { padding: 0 13px; margin-top: 9px; }
.crew-grid.cards .crew-out { margin: 9px 13px 13px; }

/* 智能体卡：状态行与进度条贴边内缩 */
.agent-grid.cards .agent-card .top { margin-top: 8px; }
.agent-grid.cards .agent-card .bar { margin-bottom: 12px; }

/* 状态色重新钉一遍：新规则未覆盖 border-color，这里显式保障（不改既有行） */
.agent-grid.cards .agent-card.running { border-color: rgba(34, 224, 255, .4); }
.agent-grid.cards .agent-card.done { border-color: rgba(34, 230, 160, .3); }
.agent-grid.cards .agent-card.failed { border-color: rgba(255, 77, 109, .34); }

/* 小尺寸头像位：半身像在 26px 圆里把脸对到上方（不覆盖既有宽高规则） */
.sow-avatar .av-img { object-position: center 16%; }
/* 对话气泡里的沐雪半身像（32px 圆）：同样把脸提到上方 */
.msg .avatar-mini img { object-position: center 16%; }


/* ============================================================================
   对话页首屏「能力速览」—— 数据驱动卡牌（/api/team 花名册）
   ------------------------------------------------------------------------
   全部为**追加**规则：不改动任何既有 class 名与既有选择器
   （.abilities/.ab-head/.ab-title/.ab-sub/.ab-grid/.ab-card/.ab-card:hover 一律不动）。
   只写 .ab-grid.cards / .ab-card.char 前缀的**紧凑覆写**：这是嵌在对话卡里的
   区域，要比花名册卡小一号。.char-* 基础规则见文件前面「卡牌模式」段。
   ============================================================================ */

/* 更紧凑的网格 */
.ab-grid.cards {
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 10px;
}

/* 覆盖 .ab-card 的横向 flex：卡牌须为纵向、贴边铺满半身像 */
.ab-grid.cards .ab-card.char {
  padding: 0;
  overflow: hidden;
  align-items: stretch;
  gap: 0;
}

/* 半身像比花名册矮一档（横向裁切、贴顶露脸） */
.ab-grid.cards .char-portrait { aspect-ratio: 4 / 5; }

/* 文字小一号 */
.ab-grid.cards .char-body { padding: 8px 10px 10px; }
.ab-grid.cards .char-nick { font-size: 14px; }
.ab-grid.cards .char-role { font-size: 11px; }
.ab-grid.cards .ab-duty {
  font-size: 11px; color: var(--muted);
  line-height: 1.55; margin-top: 3px;
}
.ab-grid.cards .char-fallback { font-size: 30px; }


/* ============================================================================
   设置中心 → AI 能力中心：「场景模型」卡（追加规则；不改动任何既有行）
   ------------------------------------------------------------------------
   每个 AI 场景一行，展示实际在用模型 + 一个小下拉切换该场景模型；
   卡头右上角一组「快速 / 均衡 / 极致」一键按钮。全部为新增 class，与既有规则零冲突。
   配色一律复用既有变量与 .tag / .notice 类，不自造颜色。
   ============================================================================ */

/* 卡头右上角的一键按钮组：靠右对齐（.card-head 已是 flex + wrap） */
.card-head .band-pick { display: flex; gap: 8px; margin-left: auto; }

/* 一键按钮：视觉语言对齐既有的 .btn.sm（小、圆角、hover 有反馈） */
.band-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 5px 12px; border-radius: 8px;
  background: var(--panel-2); border: 1px solid var(--line);
  color: var(--text-mid); font-size: 12.5px; font-weight: 500;
  white-space: nowrap; transition: .16s;
}
.band-btn:hover { border-color: var(--line-3); color: var(--text); background: rgba(34, 224, 255, .08); }
.band-btn:disabled { opacity: .55; cursor: not-allowed; }

/* 场景行：单元格垂直居中 */
.scene-row td { vertical-align: middle; }
/* 「切换模型」的小下拉：不铺满整列，够用即可 */
.scene-pick { min-width: 200px; max-width: 100%; }
/* 当前模型名与来源徽标之间留一点缝 */
.scene-now code { margin-right: 6px; }
/* 三档可达性单元格：居中、不换行 */
.band-cell { text-align: center; white-space: nowrap; }
/* 场景名下的说明文字与主名拉开一点层级 */
.scene-name .tiny { margin-top: 3px; }

/* ============================================================================
   设置中心 → AI 能力中心：「场景模型」卡 · 通道化扩展（追加规则；不改动任何既有行）
   ------------------------------------------------------------------------
   新增：通道（服务商）列 + 通道下拉、启用开关与停用灰化、通道密钥区。
   全部为新增 class，配色一律复用既有变量与 .tag / .notice / .switch-row 类。
   ============================================================================ */

/* 通道（服务商）单元格：通道名 + 通道下拉 */
.scene-channel-name { margin-bottom: 6px; font-size: 12.5px; color: var(--text-mid); }
.scene-channel-pick { min-width: 180px; max-width: 100%; }

/* 场景停用：行加淡红底、当前模型与三档灰化；「已停用」徽标与开关保持清晰可点 */
.scene-row.scene-off { background: rgba(255, 77, 109, .05); }
.scene-row.scene-off .scene-now,
.scene-row.scene-off .band-cell { opacity: .5; }

/* 未纳入成本表的候选说明：整行横跨，压扁 .notice 的上下留白 */
.scene-warn-row td { padding-top: 0; }
.scene-warn-row .notice { padding: 6px 12px; font-size: 12px; }

/* 启用开关单元格：居中、不换行 */
.scene-enable { text-align: center; white-space: nowrap; }

/* 通道密钥区块：与场景表拉开间距 */
.channels-box { margin-top: 16px; }
.channels-box .card-head { margin-bottom: 10px; }
.scene-key-row .tiny { margin-top: 3px; }
.scene-key-input {
  width: 100%; min-width: 200px; padding: 8px 11px;
  background: rgba(6, 10, 20, .7);
  border: 1px solid var(--line); border-radius: 10px;
  color: var(--text); font: inherit; font-size: var(--fs-sm);
  transition: .16s;
}
.scene-key-input::placeholder { color: var(--muted-soft); }
.scene-key-input:focus {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(34, 224, 255, .14);
}

/* ============================================================================
   设置中心美化（本次新增；**只追加，不修改任何既有规则**）
   ------------------------------------------------------------------------
   目标：把「AI 能力中心」与「大模型接入」两个 tab 的信息层级、通道关系、
   来源可见性做扎实提升。全部为新增 class，配色一律复用既有 CSS 变量
   （--brand/--violet/--line/--text-mid/--muted/--muted-soft/--brand-soft 等）
   与既有 .tag / .notice 类；不新增颜色常量、不改任何既有 class 名。
   ============================================================================ */

/* ---- 双通道说明条：把「主通道 / 补充通道」两枚徽标 + 一句话讲清 ---- */
.chan-explain { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-bottom: 12px; }
.chan-explain-note { font-size: 12px; color: var(--muted); line-height: 1.6; }
.chan-badge {
  display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600; white-space: nowrap;
  color: var(--brand); background: rgba(34, 224, 255, .12); border: 1px solid rgba(34, 224, 255, .28);
}
.chan-badge.alt { color: var(--violet); background: rgba(123, 92, 255, .12); border-color: rgba(123, 92, 255, .28); }
/* 「默认」徽标（CloudBase 通道）：绿色系，与「主/补充」区分；基于接口 is_default 渲染。 */
.chan-badge.default { color: var(--ok, #34d399); background: rgba(52, 211, 153, .14); border-color: rgba(52, 211, 153, .32); }
/* 能力卡右上角「当前通道」：靠右、更小 */
.chan-badge.mini { font-size: 10.5px; padding: 2px 8px; margin-left: auto; }
/* 通道密钥区行内徽标：不加 margin-left:auto，跟随通道名排布 */
.scene-key-row .chan-badge { font-size: 10.5px; padding: 2px 8px; vertical-align: 1px; }

/* ---- 服务商选择分组：在既有 .provider-pick（flex+wrap）里纵向分组 ---- */
.provider-group { flex: 1 1 100%; display: flex; flex-wrap: wrap; gap: 7px; align-items: center; }
.provider-group-title {
  flex: 1 1 100%; font-size: 11.5px; color: var(--muted-soft);
  margin: 8px 0 0 2px; letter-spacing: .3px;
}

/* ---- 选中服务商的只读回显（地址 / 默认模型 / 申请 Key） ---- */
.preset-hint {
  font-size: 11.5px; color: var(--text-mid); line-height: 1.8; margin-top: 12px;
  padding: 8px 11px; border-radius: 10px; background: rgba(0, 0, 0, .22); border: 1px solid var(--line);
}
.preset-hint code { font-size: 11px; word-break: break-all; }
.preset-hint a { color: var(--brand); }

/* ---- 大模型接入：顶部状态摘要卡 ---- */
.llm-summary {
  display: flex; gap: 18px; flex-wrap: wrap; align-items: center;
  padding: 11px 14px; border-radius: 12px; margin-top: 13px;
  background: var(--brand-soft); border: 1px solid rgba(34, 224, 255, .24);
}
.llm-summary-item { font-size: 12.5px; color: var(--text-mid); }
.llm-summary-item b { color: var(--brand); font-weight: 650; font-size: 13.5px; }

/* ---- 大模型接入：档位卡头部的紧凑状态徽标（靠右） ---- */
.tier-state {
  font-size: 11.5px; color: var(--muted); margin-left: auto;
  display: inline-flex; align-items: center; gap: 6px;
}

/* ---- 大模型接入：「一键填入服务商」步骤内联容器 ---- */
.preset-inline { display: block; }

/* ============================================================================
   设置中心 →「🎛 场景模型」表：修复首列竖排 + 三档列合并 + 消除横向滚动（本次新增）
   ------------------------------------------------------------------------
   背景（两轮演进）：
   ① 首轮：table.data 是 auto layout，列被压窄时中文默认逐字换行 → 首列「生文 / 读图 /
      生图 / 生视频」被挤成竖排。
   ② 本轮：用户拍板把「快速 / 均衡 / 极致」三列合并为 1 列（8 列 → 6 列），以消除右侧
      横向滚动。
   修法（在 auto layout 下「靠内容换行」始终无法可靠封顶，故对 #sceneTable 单独改用
   fixed layout + settings.js 里的 <colgroup> 显式定列宽）：列宽由 colgroup 百分比决定，
   与内容无关，因此表格宽度恒等于容器宽度（width:100%）→ 横向滚动被消除；表内长文本
   一律允许换行、下拉铺满本列，避免内容溢出格子。窄屏（< 720px）时仍由既有 .table-wrap
   （overflow-x:auto）横向滚动兜底。首列名 nowrap 保证「生文」等不再竖排。
   配色一律复用既有 CSS 变量（--brand/--line/--panel-2/--text/--text-mid/--muted/--muted-soft）。
   ============================================================================ */

/* 1) 表格：fixed layout + 占满容器；列宽交给 <colgroup>；窄屏给一个下限 */
#sceneTable { table-layout: fixed; width: 100%; min-width: 720px; }
#sceneTable th, #sceneTable td { vertical-align: middle; }
/* 合并列后同步收窄列间距，为「消除横向滚动」腾出空间 */
#sceneTable th, #sceneTable td { padding-left: 9px; padding-right: 9px; }

/* 2) 首列「AI 场景」：名不换行（不竖排）+ 描述换行 */
#sceneTable td.scene-name {
  white-space: nowrap; line-height: 1.5;
  font-size: 13.5px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis;
}
#sceneTable td.scene-name .tiny {
  white-space: normal; margin-top: 5px; line-height: 1.5; color: var(--muted);
}

/* 3) 通道列：通道名与下拉分层；两者都可换行，下拉铺满本列 */
#sceneTable .scene-channel-name {
  margin-bottom: 7px; white-space: normal; line-height: 1.4; overflow-wrap: anywhere;
}
#sceneTable .scene-channel-pick { width: 100%; min-width: 0; max-width: none; }

/* 4) 当前在用模型：型号代码块 + 来源徽标；整体可换行，长型号代码可断行 */
#sceneTable td.scene-now { white-space: normal; overflow-wrap: anywhere; }
#sceneTable td.scene-now code.scene-model {
  display: inline-block; padding: 3px 9px; border-radius: 8px; margin-right: 8px;
  background: var(--panel-2); border: 1px solid var(--line);
  color: var(--brand); font-size: 12px; overflow-wrap: anywhere; max-width: 100%;
}
#sceneTable td.scene-now .tiny { white-space: normal; margin-top: 6px; }

/* 5) 切换模型下拉：铺满本列，长选项不撑爆列 */
#sceneTable .scene-pick { width: 100%; min-width: 0; max-width: none; }

/* 6) 档位可达性（三档合并为一列）：左对齐；三个 chip 行内排列，必要时换行不溢出 */
#sceneTable th.scene-bands-h, #sceneTable td.scene-bands { text-align: left; }
#sceneTable td.scene-bands { white-space: normal; }
#sceneTable .scene-bands .band-cell {
  display: inline-flex; align-items: center; gap: 3px; margin-right: 7px;
}
#sceneTable .scene-bands .band-cell:last-child { margin-right: 0; }
#sceneTable .scene-bands .band-icon { font-size: 13px; opacity: .85; }

/* 7) 启用列：紧凑、居中 */
#sceneTable th:last-child, #sceneTable td.scene-enable { text-align: center; }

/* 8) 行高与层次：加大行内边距，行与行之间更透气 */
#sceneTable tbody td { padding-top: 13px; padding-bottom: 13px; }


/* ============================================================================
   9) 卡片顶栏折行修复（能力卡「生图 / 生视频」通道徽标被裁 + 卡名被压成竖排）

   缺陷形态（无头 Edge 实测）：
     .cap-top 是不换行的 flex 行，里面有「图标 + 卡名 + 已接入标签 + 通道徽标」。
     徽标 .chan-badge 是 inline-flex 且 white-space:nowrap ⇒ 它的 min-content 就是整串
     文字宽度，**作为 flex 项不可再缩**；而 .cap-card 有 overflow:hidden ⇒ 徽标被卡片
     右边界**直接裁掉**（实测卡片右边界 904px、徽标右边界 1013px，溢出 109px）。
     更糟的是：溢出把同一行里**可以换行**的卡名挤到 min-content，于是「生视频」被压成
     一字一行（实测 .cap-top 高度从 36px 涨到 70px）。

   修法（只加属性，不改任何既有 class 名 / 不动配色）：
     ① 顶栏允许折行 ⇒ 徽标换到第二行，第一行不再被挤压；
     ② 卡名禁止断字 ⇒ 即使极端窄也不会变竖排；
     ③ 徽标最宽不超过内容宽 + 省略号 ⇒ 真放不下时**优雅截断**而不是被 overflow 硬切；
        完整值仍在 title 提示与卡片描述文字里。
   ========================================================================== */
.plat-top, .cap-top, .conn-top { flex-wrap: wrap; }
.cap-name { white-space: nowrap; }
/* display:inline-block 是**有意的**：徽标基类是 inline-flex，文字会落在匿名 flex 项里，
   而 text-overflow 必须在**文字所在的块容器**上才生效 ⇒ inline-flex 下省略号渲染不出来
   （实测被硬切）。mini 徽标内容只有文字、不依赖 flex 对齐，改 inline-block 即可让省略号生效。 */
.chan-badge.mini { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }



/* ============================================================================
   帮助中心「流云」侧栏（T04 · 一期：只读动作清单，零执行入口）
   ----------------------------------------------------------------------------
   层级：z-index 150 —— 高于内容层（.app 的 1），低于 .modal(200)／.auth-gate(250)
        ／.gate(260)，故登录框、扫码浮层、激活码浮层都能盖住它；.toast(300) 仍最上。
   命名：.asst-* 前缀（assistant 在 web/ 下原本 0 命中，命名空间干净）；
        避让已占用的 .guide*（访客引导卡）／.toast*（提示）／.nav-*（侧边菜单）。
   配色：一律复用既有 CSS 变量（--panel-2/--line/--text/--muted/--warn 等），不硬编码。
   一期零执行入口：抽屉内只有展示节点，无任何按钮（除收起）。
   ============================================================================ */

.asst-toggle { font-size: var(--fs-sm); }
.asst-toggle.on { border-color: var(--line-3); color: var(--brand); background: var(--brand-soft); }
.asst-toggle[hidden] { display: none !important; }

.asst-drawer {
  position: fixed; top: var(--topbar-h); right: 0; bottom: 0;
  width: min(360px, 92vw); z-index: 150;
  display: flex; flex-direction: column;
  background: linear-gradient(180deg, rgba(13, 18, 36, .97), rgba(8, 12, 24, .97));
  border-left: 1px solid var(--line-2);
  box-shadow: -14px 0 40px rgba(0, 0, 0, .5);
  animation: asstSlide .22s ease;
}
.asst-drawer[hidden] { display: none !important; }
@keyframes asstSlide {
  from { transform: translateX(18px); opacity: 0; }
  to { transform: none; opacity: 1; }
}

.asst-head {
  display: flex; align-items: center; gap: 9px; flex: none;
  padding: 13px 15px; border-bottom: 1px solid var(--line);
}
.asst-head-ic { font-size: 16px; }
.asst-head-t { font-size: var(--fs-md); font-weight: 650; color: var(--text); }
.asst-head-n {
  font-size: 11.5px; color: var(--muted); padding: 1px 9px;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 999px;
}
.asst-close {
  margin-left: auto; width: 26px; height: 26px; border-radius: 8px;
  border: 1px solid var(--line); background: transparent; color: var(--muted);
}
.asst-close:hover { border-color: var(--danger); color: var(--danger); }
.asst-note {
  flex: none; padding: 9px 15px; font-size: 11.5px; line-height: 1.6;
  color: var(--muted-soft); border-bottom: 1px solid var(--line);
}
.asst-body { flex: 1; overflow-y: auto; padding: 12px 13px 22px; }

.asst-group { margin-bottom: 16px; }
.asst-group-head { display: flex; align-items: center; gap: 7px; }
.asst-group-ic { font-size: 13px; }
.asst-group-name { font-size: var(--fs-sm); font-weight: 650; color: var(--text-mid); }
.asst-group-n { margin-left: auto; font-size: 11px; color: var(--muted); }
.asst-group-hint { font-size: 11px; line-height: 1.5; color: var(--muted-soft); margin: 2px 0 8px 20px; }

.asst-row {
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 10px; padding: 9px 11px; margin-bottom: 7px;
}
.asst-row[data-cls="C"] { border-color: rgba(255, 181, 69, .24); }
.asst-row-top { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.asst-row-label { font-size: var(--fs-sm); font-weight: 550; color: var(--text); }
.asst-row-method {
  font-family: var(--mono); font-size: 10.5px; color: var(--brand);
  border: 1px solid var(--line-2); border-radius: 6px; padding: 0 6px;
}
.asst-badge.billed {
  font-size: 10.5px; color: var(--warn);
  border: 1px solid rgba(255, 181, 69, .42); border-radius: 6px; padding: 0 6px;
}
.asst-row-path {
  display: block; margin-top: 5px; font-family: var(--mono);
  font-size: 11px; color: var(--muted); overflow-wrap: anywhere;
}
.asst-why {
  margin-top: 7px; font-size: 11.5px; line-height: 1.62; color: var(--text-mid);
  background: var(--warn-soft); border-left: 2px solid var(--warn);
  border-radius: 0 8px 8px 0; padding: 7px 9px;
}
.asst-empty { color: var(--muted); font-size: var(--fs-sm); text-align: center; padding: 26px 10px; }


/* ============================================================================
   分步教程引擎（T04）：四块遮罩挖洞聚光灯 + 步骤卡
   ----------------------------------------------------------------------------
   层级：z-index 310（高于既有最高 .toast-host 300）。
   共存：门禁层 .auth-gate(250) / .gate(260) 出现时，tour.js 给本层加 .tour-yield
        整层隐藏让位，绝不叠在门禁之上。
   命名：.tour-* 前缀（web/ 下原本 0 命中）；**不复用** .guide*（访客引导卡）。
   挖洞：四块 .tour-mask 拼出「目标之外的区域」，目标处留空即为洞 ——
        只用最基础的固定定位，**不用** mix-blend-mode / clip-path（兼容性红线）。
   ============================================================================ */
.tour-entry { font-size: var(--fs-sm); }
.tour-entry[hidden] { display: none !important; }

.tour-layer { position: fixed; inset: 0; z-index: 310; }
.tour-layer[hidden] { display: none !important; }
.tour-layer.tour-yield { display: none !important; }

.tour-mask { position: fixed; background: rgba(3, 5, 12, .66); }

.tour-hole {
  position: fixed; pointer-events: none; border-radius: 12px;
  box-shadow: 0 0 0 2px var(--brand), 0 0 0 6px rgba(34, 224, 255, .18),
    0 0 34px rgba(34, 224, 255, .5);
  transition: all .25s ease;
}
.tour-hole[hidden] { display: none !important; }

.tour-card {
  position: fixed; width: min(340px, 92vw); z-index: 311;
  background: linear-gradient(180deg, rgba(14, 19, 36, .985), rgba(8, 12, 24, .985));
  border: 1px solid var(--line-2); border-radius: var(--radius-lg);
  box-shadow: var(--glow-card), var(--glow-brand);
  padding: 14px 15px 13px; animation: tourPop .22s ease;
}
@keyframes tourPop {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}
.tour-card-head { display: flex; align-items: center; gap: 8px; }
.tour-step-n {
  flex: none; font-size: 11px; font-weight: 700; color: var(--brand);
  border: 1px solid var(--line-3); border-radius: 999px; padding: 1px 8px;
  font-variant-numeric: tabular-nums;
}
.tour-card-t { font-size: var(--fs-md); font-weight: 650; color: var(--text); }
.tour-x {
  margin-left: auto; width: 24px; height: 24px; border-radius: 7px; flex: none;
  border: 1px solid var(--line); background: transparent; color: var(--muted);
}
.tour-x:hover { border-color: var(--danger); color: var(--danger); }
.tour-card-s { margin-top: 9px; font-size: var(--fs-sm); line-height: 1.72; color: var(--text-mid); }
.tour-card-foot { margin-top: 13px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.tour-card-foot .btn[disabled] { opacity: .45; cursor: not-allowed; }

/* ---- P1-4：step 契约新增字段的视觉（hint 徽标 + 定位闪烁） ------------------
   为什么单独一节、且只追加：本仓头号静默失效族是「JS 用了 class，CSS 没定义」
   ⇒ 界面看着能用、样式全丢。`scripts/check_css_class_coverage.py` 会把这几个
   class 记进「未定义欠账」，所以新增一个就必须在这里定义，缺一不可。
   颜色只用既有变量（--brand / --panel-2 / --text-mid / --line），不写任何字面色值：
   hint 有四种取值，若每种给一个色，一屏就会冒出四种强调色 —— 违反「每屏 ≤2 处强调」。
   只有 `input` 借用品牌色（那是最需要用户看见「轮到你打字」的一步），其余保持中性。 */
.tour-hint {
  display: inline-block;
  margin: 0 0 8px;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11.5px;
  line-height: 1.5;
  letter-spacing: .01em;
  color: var(--text-mid);
  background: var(--panel-2);
  border: 1px solid var(--line);
  /* 徽标是「说明」不是「按钮」：不给 hover/pointer 反馈，避免被误当成可点 */
  cursor: default;
  user-select: none;
}
.tour-hint[data-hint="input"] { color: var(--brand); border-color: var(--brand); }

/* 「定位到按钮」/「帮我填」的短暂高亮：让用户一眼看到目标在哪。
   outline 不占布局（用 box-shadow/边框会推移 1px，聚光灯洞就对不齐了）。 */
.tour-locate-flash {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  transition: outline-color .2s ease;
}


/* ============================================================================
   帮助中心「流云」侧栏（T05 · 二期：可执行 + 待确认卡片 + 撤销入口）
   ----------------------------------------------------------------------------
   本节只**追加**执行态样式；既有 .asst-* 只读样式一律不动（不改 class 名）。
   命名沿用 .asst-* 前缀；确认卡片用 .cf-* 子类，避免与既有样式冲突。
   ============================================================================ */
.asst-row-act { display: flex; align-items: center; margin-top: 8px; }
.asst-run {
  margin-left: auto; font-size: 11.5px; padding: 3px 12px; border-radius: 8px;
  border: 1px solid var(--line-2); background: var(--brand-soft); color: var(--brand);
}
.asst-run:hover { border-color: var(--line-3); }
.asst-run.confirm {
  background: var(--warn-soft); color: var(--warn);
  border-color: rgba(255, 181, 69, .42);
}

.asst-result {
  border: 1px solid var(--line-2); border-radius: 10px; padding: 10px 12px;
  margin-bottom: 13px; background: var(--panel-2);
}
.asst-result[data-status="undone_partial"] { border-color: rgba(255, 181, 69, .4); }
.asst-result-top { display: flex; align-items: center; gap: 8px; }
.asst-result-t { font-size: var(--fs-sm); font-weight: 600; color: var(--text); }
.asst-result-hint { margin-top: 6px; font-size: 11.5px; line-height: 1.6; color: var(--warn); }
.asst-undo { margin-left: auto; padding: 3px 11px; font-size: 11.5px; }

.asst-confirm .cf-row { display: flex; gap: 10px; padding: 7px 0; border-bottom: 1px solid var(--line); }
.asst-confirm .cf-k { flex: none; width: 74px; color: var(--muted); font-size: 12.5px; }
.asst-confirm .cf-v { color: var(--text); font-size: 13px; line-height: 1.6; overflow-wrap: anywhere; }
.asst-confirm .cf-why { margin-top: 12px; }
.asst-confirm .cf-why-t { font-size: 12px; color: var(--warn); font-weight: 600; margin-bottom: 5px; }
.asst-confirm .cf-why-b {
  font-size: 13px; line-height: 1.75; color: var(--text-mid); white-space: pre-wrap;
  background: var(--warn-soft); border-left: 2px solid var(--warn);
  border-radius: 0 8px 8px 0; padding: 9px 11px;
}

/* ============================================================================
   四期追加：对话式「流云」 + 智能体协同引导 + 平台卡授权行
   ----------------------------------------------------------------------------
   原则：只**追加**新 class，配色一律复用既有 CSS 变量，不新增颜色常量。
   唯一一处对既有 class 的覆写是 .asst-body（语义由"滚动动作清单"改为"对话容器"：
   自身不滚、改由 .asst-msgs 滚动），追加在文件末尾以保证生效。
   ============================================================================ */

/* --- ① 平台通道卡：授权信息行 ---------------------------------------------
   ⚠️ 这里曾误用 .conn-ok —— 那个 class 是「8px 状态圆点」，被当成文本行用之后，
   整行文字被塞进 8px 的圆点盒子里，溢出部分又被 .conn-card 的 overflow:hidden
   裁掉，用户只看到一个"已"字悬在按钮下面（截图里的红色圈）。故新起
   .conn-authline，不再征用状态点。 */
.conn-authline {
  margin-top: 9px; padding-top: 8px; border-top: 1px dashed var(--line);
  font-size: 11.5px; color: var(--muted); line-height: 1.6;
  overflow-wrap: anywhere;
}

/* --- ② 首屏能力卡可点 ----------------------------------------------------- */
.ab-clickable { cursor: pointer; }
.ab-clickable:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.ab-clickable:hover { border-color: rgba(34, 224, 255, .45); }
.ab-go {
  margin-top: 7px; font-size: 11px; color: var(--brand);
  display: inline-block; letter-spacing: .2px;
}

/* --- ③ 对话引擎（流云抽屉与智能体 dock 共用） ----------------------------- */
.asst-body {
  display: flex; flex-direction: column; gap: 8px;
  overflow: hidden; padding: 10px 12px 12px;
}
.asst-msgs {
  flex: 1; min-height: 120px; max-height: 46vh;
  overflow-y: auto; display: flex; flex-direction: column; gap: 10px;
  padding-right: 4px;
}
.asst-msg { display: flex; gap: 8px; align-items: flex-start; }
.asst-msg.me { flex-direction: row-reverse; }
.asst-msg-av {
  flex: none; width: 26px; height: 26px; border-radius: 9px; display: grid;
  place-items: center; font-size: 14px; font-family: var(--emoji);
  background: var(--panel-2); border: 1px solid var(--line); color: var(--text-mid);
}
.asst-msg.me .asst-msg-av {
  background: var(--brand-soft); border-color: rgba(34, 224, 255, .3); color: var(--brand);
  font-family: var(--font); font-size: 11.5px;
}
.asst-bubble {
  max-width: 88%; padding: 9px 11px; border-radius: 12px;
  font-size: var(--fs-sm); line-height: 1.72; color: var(--text);
  background: var(--panel-2); border: 1px solid var(--line);
  overflow-wrap: anywhere;
}
.asst-msg.me .asst-bubble {
  background: var(--brand-soft); border-color: rgba(34, 224, 255, .28);
}
.asst-engine { display: block; margin-top: 7px; font-size: 10.5px; color: var(--muted-soft); }

/* 快捷指令条 / 气泡内动作条 */
.asst-quick, .asst-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.asst-quick { flex: none; }
.asst-quick[hidden] { display: none !important; }
.asst-bubble .asst-chips { margin-top: 9px; }
.asst-chip {
  padding: 5px 10px; border-radius: 999px; font-size: 11.5px; text-align: left;
  background: transparent; border: 1px solid var(--line); color: var(--muted);
  transition: .15s;
}
.asst-chip:hover { border-color: var(--line-3); color: var(--text); }
.asst-chip.danger { color: var(--danger); border-color: rgba(255, 77, 109, .35); }
.asst-chip.danger:hover { border-color: var(--danger); }

/* 输入区（恒在底部：靠 .asst-body 的 flex 列 + 消息区滚动实现，非 sticky） */
.asst-input { flex: none; display: flex; gap: 7px; align-items: flex-end; }
.asst-input textarea {
  flex: 1; min-height: 38px; max-height: 96px; resize: none;
  padding: 9px 11px; border-radius: 11px; line-height: 1.6;
  background: var(--panel-2); border: 1px solid var(--line);
  color: var(--text); font-family: inherit; font-size: var(--fs-sm);
}
.asst-input textarea:focus { outline: none; border-color: var(--brand); }
.asst-input button { flex: none; }

/* 教学卡（智能体引导：分步说明 + 一键示例指令） */
.asst-guide { display: flex; flex-direction: column; gap: 9px; }
.asst-guide-step { display: flex; gap: 9px; align-items: flex-start; }
.asst-guide-n {
  flex: none; width: 20px; height: 20px; border-radius: 7px; display: grid;
  place-items: center; font-size: 11px; font-weight: 700;
  background: var(--brand-soft); color: var(--brand); border: 1px solid rgba(34, 224, 255, .28);
}
.asst-guide-t { font-size: var(--fs-sm); font-weight: 600; color: var(--text); }
.asst-guide-d { font-size: 12px; color: var(--muted); line-height: 1.65; margin-top: 2px; }
.asst-guide-tip { margin-top: 10px; font-size: 11.5px; color: var(--muted-soft); }
.asst-guide .asst-chips { margin-top: 6px; }

/* 待确认卡的按钮行 + 补参表单 */
.asst-confirm-act { display: flex; gap: 8px; margin-top: 11px; }
.asst-params { display: flex; flex-direction: column; gap: 8px; }
.asst-params .field { margin: 0; }
.asst-params .btn { align-self: flex-start; margin-top: 4px; }

/* 动作目录折叠区（保留三期能力，降级为参考目录） */
.asst-fold { flex: none; border-top: 1px solid var(--line); }
.asst-fold > summary {
  padding: 9px 13px; font-size: 11.5px; color: var(--muted); cursor: pointer; list-style: none;
}
.asst-fold > summary::-webkit-details-marker { display: none; }
.asst-fold > summary::before { content: "▸ "; }
.asst-fold[open] > summary::before { content: "▾ "; }
.asst-fold-n { color: var(--muted-soft); }
.asst-fold-body { max-height: 40vh; overflow-y: auto; padding: 4px 13px 16px; }

/* --- ④ 智能体协同引导 dock（右下浮动，跨页面存活） ------------------------- */
.agdock {
  position: fixed; right: 20px; bottom: 20px; z-index: 151;
  width: min(392px, 94vw);
  display: flex; flex-direction: column;
  background: linear-gradient(180deg, rgba(13, 18, 36, .98), rgba(8, 12, 24, .98));
  border: 1px solid var(--line-2); border-left: 2px solid var(--tone, var(--brand));
  border-radius: 16px; overflow: hidden;
  box-shadow: 0 22px 56px rgba(0, 0, 0, .55);
  animation: agdockIn .22s ease;
}
@keyframes agdockIn {
  from { transform: translateY(14px); opacity: 0; }
  to { transform: none; opacity: 1; }
}
.agdock-head {
  display: flex; align-items: center; gap: 10px; flex: none;
  padding: 11px 13px; border-bottom: 1px solid var(--line);
}
.agdock-av {
  flex: none; width: 38px; height: 38px; border-radius: 12px; overflow: hidden;
  display: grid; place-items: center; font-size: 18px; font-family: var(--emoji);
  background: var(--panel-2); border: 1px solid var(--line-2);
}
.agdock-av img { width: 100%; height: 100%; object-fit: cover; display: block; }
.agdock-who { min-width: 0; }
.agdock-name { font-size: var(--fs-sm); font-weight: 650; color: var(--text); }
.agdock-role {
  font-size: 11px; color: var(--muted); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 240px;
}
.agdock-close {
  margin-left: auto; flex: none; width: 26px; height: 26px; border-radius: 8px;
  border: 1px solid var(--line); background: transparent; color: var(--muted);
}
.agdock-close:hover { border-color: var(--danger); color: var(--danger); }
.agdock-banner {
  flex: none; padding: 8px 13px; font-size: 11.5px; line-height: 1.65;
  color: var(--muted); border-bottom: 1px solid var(--line);
}
.agdock-banner.ok { color: var(--brand); background: var(--brand-soft); }
.agdock-banner.warn { color: var(--warn); background: var(--warn-soft); }
.agdock-banner .btn { margin-top: 7px; }
.agdock-body { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px 12px; }

/* 小屏：dock 贴底铺满，别在手机上留一条缝 */
@media (max-width: 560px) {
  .agdock { right: 8px; left: 8px; bottom: 8px; width: auto; }
  .agdock-role { max-width: 150px; }
}

/* --- ⑤ 驻守气泡（每个功能区常驻入口，点开即 agdock） ---------------------
   为什么是气泡而不是进页面就弹大窗：用户进页面是有事要干，弹窗先把他的事挡住了；
   且 12 个页面之间切换会被弹 12 次。气泡 = "需要时我在"。
   z-index 150 —— **必须低于 .agdock(151)**：展开后 dock 盖住气泡所在区域，
   同时气泡会被 hideBubble() 收起，两者不会同时可见。 */
.agbubble {
  position: fixed; right: 20px; bottom: 20px; z-index: 150;
  width: 54px; height: 54px; padding: 0; border-radius: 50%;
  border: 2px solid var(--tone, var(--brand));
  background: var(--panel-2); cursor: pointer;
  box-shadow: 0 10px 28px rgba(0, 0, 0, .45);
  transition: transform .16s ease, box-shadow .16s ease;
  animation: agbubbleIn .22s ease;
}
@keyframes agbubbleIn {
  from { transform: scale(.7); opacity: 0; }
  to { transform: none; opacity: 1; }
}
.agbubble:hover {
  transform: translateY(-2px) scale(1.05);
  box-shadow: 0 14px 34px rgba(0, 0, 0, .55);
}
.agbubble-av {
  width: 100%; height: 100%; border-radius: 50%; overflow: hidden;
  font-size: 22px; font-family: var(--emoji);
  display: grid; place-items: center;
}
.agbubble-av img { width: 100%; height: 100%; object-fit: cover; display: block; }
.agbubble-dot {
  position: absolute; right: 1px; bottom: 1px;
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--tone, var(--brand));
  border: 2px solid var(--bg, #0a0e1a);
}
/* 手机端才出现（JS 控制 hidden）：一枚光有头像的圆点会被当成装饰/广告角标，
   加一句"可对话"用户才知道它是入口。PC 端不显示 —— 那里已经用自动展开说明过。
   位置在气泡左侧、垂直居中，不遮挡头像本身。 */
.agbubble-tip {
  position: absolute; right: calc(100% + 8px); top: 50%;
  transform: translateY(-50%);
  padding: 4px 8px; border-radius: 999px;
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--tone, var(--brand));
  font-size: var(--fs-xs); line-height: 1.2; white-space: nowrap;
  box-shadow: 0 6px 16px rgba(0, 0, 0, .35);
  pointer-events: none;           /* 不挡点击：点它等于点不到气泡 */
}
.agbubble-tip[hidden] { display: none; }
@media (max-width: 560px) {
  .agbubble { right: 12px; bottom: 12px; width: 48px; height: 48px; }
}

/* ==========================================================================
   智能体「亮相」居中弹框（agent-intro.js）
   --------------------------------------------------------------------------
   与 agdock 是两个形态、两个场景（详见 agent-intro.js 顶部注释）：
   这里是"看这是谁"的那几秒，居中、有仪式感；agdock 是随后的持续协同。

   ★ 主题色用 --tone（由 JS 按 agent.color 注入）：
     每个智能体有自己的专属色，亮相时脸部光晕跟着变色，用户一眼能认出
     "刚才说话的换人了"。不写死颜色，否则 18 个角色全一个色。
   ========================================================================== */
.aintro { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 6px 4px 2px; }
.aintro-face {
  position: relative; width: 108px; height: 108px; flex: none;
  border-radius: 26px; overflow: hidden; display: grid; place-items: center;
  background: var(--panel-2); border: 1px solid var(--line-2);
  /* 脸部光晕跟着该智能体的主题色走，换人即换色 */
  box-shadow: 0 0 0 1px var(--tone, var(--brand)), 0 12px 34px -14px var(--tone, var(--brand));
}
.aintro-face img { width: 100%; height: 100%; object-fit: cover; display: block; }
.aintro-emoji { font-size: 46px; font-family: var(--emoji); line-height: 1; }
/* 说话时的呼吸脉冲：只在 .aintro.on 时出现（JS 按真实播放状态挂类）。
   刻意不用 :has() —— 兼容性不稳，且在部分内核上静默失效会让动画永久不动。 */
.aintro-pulse {
  position: absolute; inset: -2px; border-radius: 28px; pointer-events: none;
  border: 2px solid var(--tone, var(--brand)); opacity: 0;
}
.aintro.on .aintro-pulse { opacity: .85; animation: aintroPulse 1.5s ease-in-out infinite; }
@keyframes aintroPulse {
  0%, 100% { transform: scale(1); opacity: .25; }
  50% { transform: scale(1.06); opacity: .75; }
}
.aintro-who { text-align: center; }
.aintro-nick { font-size: 17px; font-weight: 700; color: var(--text); letter-spacing: .3px; }
.aintro-role { font-size: var(--fs-xs); color: var(--muted); margin-top: 3px; }
.aintro-say {
  max-width: 520px; margin-top: 2px; padding: 12px 14px;
  font-size: var(--fs-sm); line-height: 1.85; color: var(--text-mid);
  background: var(--panel-2); border: 1px solid var(--line);
  border-left: 3px solid var(--tone, var(--brand)); border-radius: 12px;
  white-space: pre-wrap; text-align: left;
}
.aintro-voice {
  display: flex; align-items: center; gap: 8px; margin-top: 2px;
  font-size: 11.5px; color: var(--muted);
}
.aintro-voice.on { color: var(--brand); }
.aintro-voice.degraded { color: var(--warn); }
/* 播放中的均衡器动画：四根柱错峰跳动，直观表达"正在说话" */
.aintro-eq { display: none; align-items: flex-end; gap: 2px; height: 12px; }
.aintro-voice.on .aintro-eq { display: inline-flex; }
.aintro-eq i {
  width: 2.5px; height: 100%; border-radius: 2px; background: currentColor;
  animation: aintroEq .9s ease-in-out infinite;
}
.aintro-eq i:nth-child(2) { animation-delay: .15s; }
.aintro-eq i:nth-child(3) { animation-delay: .3s; }
.aintro-eq i:nth-child(4) { animation-delay: .45s; }
@keyframes aintroEq {
  0%, 100% { transform: scaleY(.35); }
  50% { transform: scaleY(1); }
}

/* 手机端：脸小一点、正文左对齐但留够触控间距，弹框别顶到屏幕边 */
@media (max-width: 560px) {
  .aintro-face { width: 84px; height: 84px; border-radius: 22px; }
  .aintro-emoji { font-size: 36px; }
  .aintro-say { font-size: var(--fs-sm); line-height: 1.8; padding: 11px 12px; }
  .aintro-nick { font-size: 16px; }
}

/* ============================================================================
   成本控制卡（设置中心 · 第 8 条需求）
   —— 全部为新增 class，配色/圆角一律复用既有变量，不改任何既有 class 的语义。
   ========================================================================== */
.budget-grid {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
/* 用量条：一条横向进度，左端是已用、右端是上限。
   没有上限（0=不限）时**不画条**，只显示"不限"——画一条永远走不完的条会让人以为坏了。 */
.budget-bar {
  position: relative; height: 8px; border-radius: 999px;
  background: var(--panel-2, rgba(255, 255, 255, .07)); overflow: hidden;
  border: 1px solid var(--line);
}
.budget-bar > i {
  display: block; height: 100%; border-radius: 999px;
  background: var(--brand); transition: width .35s ease;
}
/* 越限/逼近上限：换成告警色。判据由后端给定（percent），前端不做二次推断。 */
.budget-bar.warn > i { background: var(--warn, #f5a524); }
.budget-bar.danger > i { background: var(--danger, #f5455c); }
.budget-num {
  display: flex; align-items: baseline; gap: 6px;
  font-size: 12px; color: var(--muted); margin-top: 6px;
}
.budget-num b { font-size: 15px; color: var(--text); font-weight: 650; }
.budget-unknown { font-size: 12px; color: var(--warn, #f5a524); }
.budget-item { display: grid; gap: 4px; }
.budget-item > label { font-size: var(--fs-sm); color: var(--muted); }
.budget-foot { margin-top: 12px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* ==========================================================================
   自我迭代 · RSI / ASI 指数
   —— 全部为新增 class，配色/圆角一律复用既有变量，不改任何既有 class 的语义。
   进度条只在**知道分母**时才有人样；尚无数据的维度画成灰条 + 写明原因 ——
   拿 0% 充数会让人误读成"系统能力为零"，比不画更坏。
   ========================================================================== */
.evo-index {
  display: grid; gap: 14px; margin-bottom: 14px;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
.evo-index-card {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel-2); padding: 14px 16px;
}
.evo-index-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.evo-index-head .evo-name {
  font-size: var(--fs-lg); font-weight: 700; letter-spacing: .5px; color: var(--brand);
}
.evo-index-head .evo-title { font-size: var(--fs-sm); color: var(--muted); }
.evo-index-head .evo-pct {
  margin-left: auto; font-size: var(--fs-xl); font-weight: 700; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.evo-pct.good { color: var(--success); }
.evo-pct.mid { color: var(--brand); }
.evo-pct.low { color: var(--warn); }
.evo-pct.unknown { color: var(--muted-soft); }
.evo-bar {
  position: relative; height: 8px; border-radius: 999px; margin-top: 10px;
  background: var(--panel); overflow: hidden; border: 1px solid var(--line);
}
.evo-bar.big { height: 12px; }
.evo-bar > i {
  display: block; height: 100%; border-radius: 999px;
  background: var(--brand); transition: width .35s ease;
}
.evo-bar.good > i { background: var(--success); }
.evo-bar.mid > i { background: var(--brand); }
.evo-bar.low > i { background: var(--warn); }
.evo-bar.unknown > i { background: var(--muted-soft); }
.evo-formula {
  margin-top: 8px; font-size: var(--fs-xs); color: var(--muted-soft);
  line-height: 1.6; word-break: break-word;
}
.evo-dims { margin-top: 12px; display: grid; gap: 10px; }
.evo-dim-top {
  display: flex; align-items: baseline; gap: 8px;
  font-size: var(--fs-sm); color: var(--muted);
}
.evo-dim-top b {
  margin-left: auto; color: var(--text-mid); font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.evo-dim .evo-bar { margin-top: 5px; height: 6px; }
.evo-rule { margin-top: 4px; font-size: var(--fs-xs); color: var(--muted-soft); }
.evo-warn {
  margin-top: 10px; font-size: var(--fs-xs); color: var(--warn);
  background: var(--warn-soft); border-radius: 8px; padding: 6px 10px;
}
.evo-note { margin-bottom: 14px; }
@media (max-width: 560px) {
  .evo-index { grid-template-columns: 1fr; }
  .evo-index-head .evo-pct { margin-left: 0; }
}

/* ============================================================================
   内容工厂 · AI 运营群（第 6 条需求）：定时任务面板 + 团队群聊
   —— 群聊不是"第二个作品墙"，所以视觉上刻意做成聊天流：
      系统消息居中成一条胶囊，员工发言左侧头像 + 气泡，正在干活的头像呼吸。
   ========================================================================== */
.sow-group-head { display: flex; align-items: flex-start; gap: 14px; flex-wrap: wrap; margin-bottom: 12px; }
.sow-group-title { font-size: var(--fs-lg); font-weight: 700; }
.sow-group-title .desc { margin-left: 8px; font-weight: 400; }
.sow-group-avs { margin-left: auto; display: flex; gap: 6px; flex-wrap: wrap; }
.sow-group-av {
  width: 28px; height: 28px; border-radius: 50%;
  display: grid; place-items: center; font-size: 15px;
  /* @cm-fallback: --tone */
  background: rgba(34, 224, 255, .12);
  background: color-mix(in srgb, var(--tone, var(--brand)) 12%, transparent);
  /* @cm-fallback: --tone */
  border: 1px solid rgba(34, 224, 255, .42);
  border: 1px solid color-mix(in srgb, var(--tone, var(--brand)) 42%, transparent);
}

/* ---- 定时任务面板 ---- */
.sched-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; }
.sched-head .btn { margin-left: auto; }
.sched-list { display: grid; gap: 8px; }
.sched-row {
  display: flex; gap: 12px; align-items: flex-start; flex-wrap: wrap;
  padding: 10px 12px; border-radius: var(--radius);
  border: 1px solid var(--line); background: var(--panel-2);
}
.sched-row.off { opacity: .6; }
.sched-main { flex: 1; min-width: 240px; display: grid; gap: 4px; }
.sched-t { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sched-name { font-weight: 600; }
.sched-sub { display: flex; gap: 12px; flex-wrap: wrap; font-size: var(--fs-xs); color: var(--muted-soft); }
.sched-sub b { color: var(--text-mid); font-weight: 600; }
.sched-next { font-variant-numeric: tabular-nums; }
.sched-st.ok { color: var(--success); }
.sched-st.warn { color: var(--warn); }
.sched-st.danger { color: var(--danger); }
.sched-act { display: flex; gap: 6px; flex-wrap: wrap; }

/* ---- 群聊时间线 ---- */
#sowChatWrap { max-height: 620px; overflow-y: auto; }
.sow-chat { display: grid; gap: 10px; }
.sow-chat-more { text-align: center; font-size: var(--fs-xs); color: var(--muted-soft); padding: 4px 0; }
.sow-chat-sys {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  justify-content: center; text-align: center;
  font-size: var(--fs-xs); color: var(--muted);
  background: var(--panel-2); border: 1px dashed var(--line);
  border-radius: 999px; padding: 6px 12px; margin: 0 auto; max-width: 92%;
}
.sow-chat-sys-t { font-weight: 600; color: var(--text-mid); }
/* @cm-fallback: --brand */
.sow-chat-sys.live { border-color: rgba(34, 224, 255, .45); border-color: color-mix(in srgb, var(--brand) 45%, transparent); }
/* @cm-fallback: --success */
.sow-chat-sys.ok { border-color: rgba(34, 230, 160, .45); border-color: color-mix(in srgb, var(--success) 45%, transparent); }
/* @cm-fallback: --warn */
.sow-chat-sys.warn { border-color: rgba(255, 181, 69, .45); border-color: color-mix(in srgb, var(--warn) 45%, transparent); }

.sow-chat-run {
  display: flex; flex-direction: column; gap: 2px; align-items: center; text-align: center;
  font-size: var(--fs-xs); color: var(--muted);
  background: var(--panel-2); border: 1px dashed var(--line-2);
  border-radius: var(--radius); padding: 8px 12px; margin: 0 auto; max-width: 96%;
}
.sow-chat-run-t { font-weight: 600; color: var(--text-mid); }
.sow-chat-run-s { color: var(--muted-soft); }
.sow-chat-st.ok { color: var(--success); }
.sow-chat-st.live { color: var(--brand); }

.sow-chat-msg { display: flex; gap: 9px; align-items: flex-start; }
.sow-chat-av {
  width: 30px; height: 30px; border-radius: 9px; flex: 0 0 auto;
  display: grid; place-items: center; font-size: 16px;
  background: var(--panel-2); border: 1px solid var(--line);
}
.sow-chat-body { flex: 1; min-width: 0; }
.sow-chat-head {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  font-size: var(--fs-xs); color: var(--muted-soft);
}
.sow-chat-who { font-weight: 600; color: var(--text-mid); font-size: var(--fs-sm); }
.sow-chat-stage {
  padding: 1px 7px; border-radius: 999px; font-size: var(--fs-xs);
  /* @cm-fallback: --brand */
  background: rgba(34, 224, 255, .14); background: color-mix(in srgb, var(--brand) 14%, transparent); color: var(--brand);
}
.sow-chat-time { margin-left: auto; font-variant-numeric: tabular-nums; }
.sow-chat-bub {
  margin-top: 4px; padding: 8px 11px; border-radius: 4px 12px 12px 12px;
  background: var(--panel-2); border: 1px solid var(--line);
  font-size: var(--fs-sm); color: var(--text-mid); line-height: 1.6;
  word-break: break-word;
}
/* @cm-fallback: --brand */
.sow-chat-msg.live .sow-chat-bub { border-color: rgba(34, 224, 255, .45); border-color: color-mix(in srgb, var(--brand) 45%, transparent); }
/* @cm-fallback: --warn */
.sow-chat-msg.warn .sow-chat-bub { border-color: rgba(255, 181, 69, .45); border-color: color-mix(in srgb, var(--warn) 45%, transparent); }
.sow-chat-meta { margin-top: 3px; display: flex; gap: 10px; font-size: var(--fs-xs); color: var(--muted-soft); }
.sow-chat-msg.live .sow-chat-av {
  /* @cm-fallback: --brand */
  border-color: rgba(34, 224, 255, .50);
  border-color: color-mix(in srgb, var(--brand) 50%, transparent);
  animation: sowPulse 1.6s ease-in-out infinite;
}
@keyframes sowPulse {
  /* @cm-fallback: --brand */
  0%, 100% { box-shadow: 0 0 0 0 rgba(34, 224, 255, .30); box-shadow: 0 0 0 0 color-mix(in srgb, var(--brand) 30%, transparent); }
  50% { box-shadow: 0 0 0 5px transparent; }
}

/* ---- 星期多选（定时任务弹窗） ---- */
.sc-days { display: flex; gap: 6px; flex-wrap: wrap; }
.sc-day {
  display: inline-flex; align-items: center; gap: 4px; cursor: pointer;
  font-size: var(--fs-xs); color: var(--text-mid);
  padding: 4px 9px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--panel-2);
}
.sc-day:has(input:checked) {
  /* @cm-fallback: --brand */
  border-color: rgba(34, 224, 255, .55);
  border-color: color-mix(in srgb, var(--brand) 55%, transparent);
  color: var(--brand);
}
.sc-day input { margin: 0; }

@media (max-width: 900px) {
  #sowChatWrap { max-height: none; }
  .sow-group-avs { margin-left: 0; }
  .sched-act { width: 100%; }
  .sched-act .btn { flex: 1 1 auto; }
  .sow-chat-time { margin-left: 0; }
  .sow-chat-sys, .sow-chat-run { max-width: 100%; justify-content: flex-start; text-align: left; }
}

/* ============================================================================
   「我的仙贝」页（★2.6.5 补齐）
   ---------------------------------------------------------------------------
   【为什么之前是坏的】`web/js/pages/points.js` 一直在用 `points-hero` /
   `points-balance` / `points-num` / `points-unit` / `points-stats` / `points-stat`
   / `points-change` 这一整套类名，但本文件里**一个都没定义**（`grep "points-"`
   零命中）⇒ 它们全部按裸 div 渲染成"每行一个、左对齐、标签与数值粘连"
   （截图实测即为该形态）。本块是缺失的那一半，**新增类名，不改任何既有 class**。
   ========================================================================== */
.points-hero {
  display: flex; flex-wrap: wrap; align-items: center; gap: 26px;
}
/* 余额区：与右侧统计之间用一道竖线分隔，让"大数字"成为视觉锚点 */
.points-balance {
  display: flex; flex-direction: column; gap: 4px;
  min-width: 178px; padding-right: 26px;
  border-right: 1px solid var(--line);
}
.points-num {
  font-size: 46px; font-weight: 800; line-height: 1.04; letter-spacing: -1.2px;
  font-variant-numeric: tabular-nums;
  background: linear-gradient(96deg, #ffffff 6%, var(--brand) 58%, var(--violet) 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  filter: drop-shadow(0 0 22px rgba(34, 224, 255, .3));
}
.points-unit { font-size: 12.5px; color: var(--muted); letter-spacing: 2.5px; }
/* 统计三宫格：label 在上、value 在下 —— 修掉"累计消耗1"这种标签数值粘连 */
.points-stats { display: flex; flex-wrap: wrap; gap: 12px 30px; }
.points-stat { display: flex; flex-direction: column; gap: 5px; min-width: 82px; }
.points-stat .k { font-size: 12px; color: var(--muted); letter-spacing: .3px; white-space: nowrap; }
.points-stat .v {
  font-size: 17px; font-weight: 700; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.points-actions { margin-left: auto; display: flex; gap: 10px; align-items: center; }
/* 流水变动值：增加=绿（获得/充值），减少=红（消耗）。与"充值成功"的正向反馈同色系 */
.points-change { font-variant-numeric: tabular-nums; font-weight: 650; }
.points-change.up { color: var(--success); }
.points-change.down { color: var(--danger); }
/* 顶栏仙贝徽标里的数字：用品牌色，与侧栏菜单形成同一处颜色锚点 */
.points-chip { font-variant-numeric: tabular-nums; }
.points-chip b { color: var(--brand); font-weight: 700; }
.points-chip:hover b { color: var(--brand-strong); }

/* 流水行内的第二行（合并后的「结算明细」原文，如「预扣 12 / 实际 9 / 退回 3」）。
   为什么必须单独一个块级元素：那一行说明本来就长，跟在主说明后面用「·」连起来会
   变成一坨挤在表格末列 —— 而这一坨正是用户验算的唯一依据，不能糊掉。
   overflow-wrap:anywhere（不是 break-word）—— 说明里可能带 jobKey / token 数这种
   无空格长串，只有 anywhere 会同时收紧 min-content，否则窄屏会把表格顶出视口。 */
.points-sub {
  margin-top: 3px;
  font-size: 11.5px; line-height: 1.6;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/* 流水表上方的「怎么读这张表」机制说明（对账指引，不是免责声明）。
   与 .notice（品牌青）刻意区分：这里用中性描边 + 更暗的底，避免用户把它当成
   「出问题了」的告警条；它出现的位置在每一页流水上方，是常驻信息。 */
.points-flow-note {
  margin: 6px 0 12px;
  padding: 11px 13px;
  border-radius: 10px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  color: var(--text-mid);
  font-size: 12.5px; line-height: 1.7;
  overflow-wrap: anywhere;
}
.points-flow-note > b {
  display: block; margin-bottom: 5px;
  color: var(--text); font-size: 12.5px; font-weight: 700;
}
.points-flow-note > ul { margin: 0; padding-left: 18px; }
.points-flow-note > ul > li { margin: 3px 0; }
/* 窄屏：表格是 5 列，说明列本来就被挤到极窄，说明区的内边距与外边距同步收一点，
   并把列表缩进减小，避免每行只剩两三个字（实测 375px 下缩进 18px 时正文列宽 96px）。 */
@media (max-width: 640px) {
  .points-flow-note { padding: 9px 10px; font-size: 12px; }
  .points-flow-note > ul { padding-left: 15px; }
  .points-sub { font-size: 11px; }
}

/* ---- 充值弹层（复用 .gate / .gate-card / .field，仅补本页专属部件）---- */
.recharge-card { width: min(520px, 100%); }
.rc-tabs {
  display: flex; gap: 6px; padding: 4px; margin-bottom: 16px;
  background: rgba(6, 10, 20, .6);
  border: 1px solid var(--line); border-radius: 12px;
}
.rc-tab {
  flex: 1; padding: 8px 10px; border: none; border-radius: 9px;
  background: transparent; color: var(--muted);
  font-size: var(--fs-sm); font-weight: 600; transition: .16s;
}
.rc-tab:hover { color: var(--text); }
.rc-tab.on {
  background: linear-gradient(120deg, var(--brand), var(--violet));
  color: #04121a; font-weight: 650;
  box-shadow: 0 4px 16px rgba(34, 224, 255, .24);
}
.rc-pane { display: none; }
.rc-pane.on { display: block; animation: fadeIn .2s ease; }
/* 档位参考（**只读展示**：真正的可选档位在小程序内，PC 只是把换算口径讲清楚） */
.rc-tiers {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
  gap: 8px; margin-bottom: 14px;
}
.rc-tier {
  padding: 9px 10px; border-radius: 11px; text-align: center;
  background: rgba(6, 10, 20, .55); border: 1px solid var(--line);
}
.rc-tier b { display: block; font-size: 15px; font-weight: 700; color: var(--text); }
.rc-tier small { display: block; margin-top: 2px; font-size: 11.5px; color: var(--muted); }
/* 小程序码：**必须白底**（深色底上二维码对比度不足，多数手机会扫不出） */
.rc-qr {
  display: grid; place-items: center; min-height: 200px;
  padding: 14px; margin: 0 auto 4px; width: fit-content;
  background: #fff; border-radius: 14px;
}
.rc-qr img { width: 196px; height: 196px; display: block; }
/* 后端 SVG 版二维码（与「绑定小程序余额」同一条渲染链路，见 points.js renderRechargeQr）。
   尺寸与上面的 img 保持一致，换实现时弹层高度不跳；白底 + padding 是硬要求 ——
   深色底上扫码识别率会明显下降，这个码是给手机摄像头看的，不是装饰。 */
.rc-qr svg { width: 196px; height: 196px; display: block; background: #fff; padding: 10px; box-sizing: content-box; }
.rc-qr-ph { color: #6b7280; font-size: 12.5px; text-align: center; padding: 40px 18px; }
/* 「等待到账」状态条（points.js 的 startRcPoll 显示 / stopRcPoll 收起）。
   ★两条都必须这样写，缺一条就是静默失效：
     ① `display:flex` 会**覆盖** `[hidden]` 的默认 `display:none`（HTML 的 hidden 只有
        在没被 CSS 覆盖时才生效）⇒ 必须显式补 `.rc-qr-wait[hidden]{display:none}`，
        否则"隐藏"的等待条会一直占着一行（本项目 `.gate` 踩过同一个坑）。
     ② 那个脉冲圆点是**唯一的"还活着"信号** —— 只有静态文字时，用户分不清
        "正在等"和"页面卡住了"。颜色一律走变量，浅色/深色主题共用一份。
   动画只在透明度上做（不动尺寸/位置），避免二维码区域跟着抖。 */
.rc-qr-wait {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  margin: 10px auto 0; padding: 7px 12px; width: fit-content; max-width: 100%;
  border-radius: 999px; background: rgba(127, 127, 127, .12);
  color: var(--muted); font-size: 12.5px; line-height: 1.5; text-align: center;
}
.rc-qr-wait[hidden] { display: none; }
.rc-qr-wait::before {
  content: ""; flex: 0 0 auto; width: 6px; height: 6px; border-radius: 50%;
  background: var(--brand); animation: rcWaitPulse 1.4s ease-in-out infinite;
}
@keyframes rcWaitPulse { 0%, 100% { opacity: .25; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .rc-qr-wait::before { animation: none; opacity: 1; }
}
.rc-steps {
  margin: 12px 0 0; padding-left: 20px;
  color: var(--muted); font-size: 12.5px; line-height: 1.95;
}
.rc-steps li::marker { color: var(--brand); font-weight: 700; }

/* 扫码失败自救指引（2026-10-24）。
   与 .gate-tip 刻意分开：这句不是“备注”，是**当前状态下唯一的可用出路**，
   所以给底色 + 描边把它从周围灰字里拔出来（用户扫不开时视线正在二维码上，
   不这样做等于没写）。配色只借用品牌暖色 alpha，不引入新变量。 */
.rc-qr-fb {
  margin: 10px 0 0; padding: 9px 11px; border-radius: 8px;
  background: rgba(255, 176, 32, .10);
  border: 1px solid rgba(255, 176, 32, .30);
  color: var(--muted); font-size: 11.5px; line-height: 1.7;
}
.rc-qr-fb b { color: var(--text); font-weight: 600; }

@media (max-width: 720px) {
  .points-balance {
    border-right: none; border-bottom: 1px solid var(--line);
    padding: 0 0 14px; width: 100%;
  }
  .points-num { font-size: 38px; }
  .points-actions { margin-left: 0; width: 100%; }
  .points-actions .btn { flex: 1 1 auto; }
}


/* ── 客户资产表格副行（2.6.9）──────────────────────────────────────────────
   主行放"一眼要找的"（手机号、公司名），副行放"需要时才看的"（邮箱、职位）。
   邮箱/域名可能很长 ⇒ word-break 兜底，否则一个长邮箱会把整张表撑出横向滚动条。
   注意：**不要用 `.muted`** —— 那个 class 被 6 个页面引用但本项目 web/ 下从未定义
   （历史遗留，见 CHANGELOG 2.6.8 披露），挂上去等于没有样式。这里用变量才生效。 */
.cell-sub {
  margin-top: 2px;
  font-size: var(--fs-xs);
  line-height: 1.35;
  color: var(--muted);
  word-break: break-all;
}




/* ── 天眼查联系方式补全（v44）─────────────────────────────────────────────
   14 个 `.tyc-*` class：`.tyc-banner`(+ok/warn/bad) · `.tyc-cost` · `.tyc-cost-num`
   · `.tyc-preview` · `.tyc-preview-row` · `.tyc-skip` · `.tyc-progress`
   · `.tyc-progress-bar` · `.tyc-result-row` · `.tyc-badge` · `.tyc-hist`
   ★铁律：JS 里出现的 class 必须在这里有定义，否则页面上是裸文字且**不报错**
     （本项目曾因 `.muted` 未定义而"看起来没坏、其实没样式"）。新增 class 时同步本段。 */

.tyc-banner {
  display: block;
  padding: 10px 14px;
  margin: 0 0 12px;
  border-radius: 10px;
  border: 1px solid var(--line-2);
  background: rgba(255, 255, 255, .03);
  color: var(--text, #e8eefc);
  font-size: var(--fs-sm);
  line-height: 1.7;
  word-break: break-word;
}
.tyc-banner.ok {
  border-color: rgba(34, 224, 255, .35);
  background: var(--brand-soft);
  color: var(--brand-strong);
}
.tyc-banner.warn {
  border-color: rgba(255, 181, 69, .38);
  background: var(--warn-soft);
  color: var(--warn);
}
.tyc-banner.bad {
  border-color: rgba(255, 77, 109, .40);
  background: var(--danger-soft);
  color: var(--danger);
}
.tyc-banner code {
  font-family: var(--mono);
  font-size: var(--fs-xs);
  padding: 1px 4px;
  border-radius: 4px;
  background: rgba(0, 0, 0, .28);
}
/* 准入双闸横幅里的引导链接（去开通套餐 / 去充值仙贝）。
   为什么不能用默认 <a> 蓝：`.tyc-banner.warn` / `.bad` 把 color 设成警示色，
   默认蓝在暖橙/玫红底色上对比度不足；继承 + 下划线在三种 level 下都成立。 */
.tyc-banner a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  font-weight: 600;
}
.tyc-banner a:hover { opacity: .82; }

/* 费用预览：金额是这张弹窗的"主角"，字号明显大于正文（¥3.20 不能被看漏） */
.tyc-preview { margin: 0 0 4px; }
.tyc-cost {
  font-size: var(--fs-md);
  line-height: 1.8;
  margin-bottom: 6px;
  color: var(--text, #e8eefc);
}
.tyc-cost-num {
  font-family: var(--mono);
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--brand-strong);
  margin: 0 2px;
}
.tyc-preview-row {
  font-size: var(--fs-sm);
  line-height: 1.75;
  color: var(--muted);
  margin-bottom: 6px;
  word-break: break-word;
}
/* 跳过项：刻意用弱化色，避免把"跳过的 5 条"看得比"要花的 ¥3.20"更重 */
.tyc-skip {
  font-size: var(--fs-xs);
  line-height: 1.7;
  color: var(--muted-soft);
  background: rgba(255, 255, 255, .02);
  border-left: 2px solid var(--line-3);
  padding: 4px 10px;
  margin: 2px 0 8px;
  border-radius: 0 6px 6px 0;
  word-break: break-word;
}

/* 执行进度：条状进度 + 实时统计（"已调用 7/32 · 已花 ¥0.70 · 成功 6"） */
.tyc-progress {
  height: 6px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .07);
  overflow: hidden;
  margin: 4px 0 10px;
}
.tyc-progress-bar {
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--brand), var(--violet, #7b5cff));
  transition: width .25s ease;
}

/* 结果分组行：一行一条，状态徽标在前、人话在后 */
.tyc-result-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 10px;
  padding: 7px 10px;
  border-radius: 8px;
  background: rgba(255, 255, 255, .025);
  border: 1px solid var(--line);
  margin-bottom: 6px;
  font-size: var(--fs-sm);
  line-height: 1.6;
  word-break: break-word;
}
.tyc-badge {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
  color: var(--brand);
  background: var(--brand-soft);
  border: 1px solid rgba(34, 224, 255, .28);
}

/* 结果行的"这一条花了多少"。
   2026-10-22 真扣上线时新增：汇总行只给合计，用户对不上具体哪一条被扣了就会
   以为系统乱扣钱；每行单独标出净额（已回补的写"已回补"、没扣的写"未扣费"）。
   ⚠️ 新增 class 必须在这里定义 —— 否则 `class="tyc-row-cost"` 只是没有样式的
   span（不报错、不告警），本项目「class 缺失」已划过一次。 */
.tyc-row-cost {
  margin-left: auto;
  padding-left: 10px;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  color: var(--text-mid);
}

/* 采集历史抽屉条目 */
.tyc-hist {
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, .022);
  margin-bottom: 6px;
  font-size: var(--fs-sm);
  line-height: 1.7;
  word-break: break-all;
}

/* 窄屏（375px）：进度统计与结果行不横向溢出；徽标换行不挤压文字 */
@media (max-width: 720px) {
  .tyc-banner { padding: 9px 11px; font-size: var(--fs-xs); }
  .tyc-cost { font-size: var(--fs-base); }
  .tyc-cost-num { font-size: var(--fs-lg); }
  .tyc-result-row { padding: 6px 8px; gap: 4px 8px; }
  .tyc-badge { font-size: 11px; padding: 1px 6px; }
}


/* ══════════════════════════════════════════════════════════════════════════
 * P0 币种闭环 · 前端样式（T7 / M19）
 *
 * 覆盖三处新界面：
 *   ① 充值弹层新增的「在线支付」pane（`.rc-gift` / `.rc-caps` / `.rc-payhint` / `.rc-paygo`）；
 *   ② 新页面「套餐与授权」`#/plan`（`.plan-*`）；
 *   ③ 订单状态徽标（`.order-status`）。
 *
 * 三条约束（改这里之前先读）：
 *   · **只追加**，不改动任何既有选择器 —— 全站共用 `app.css`，
 *     改动既有规则会波及别的页面（本项目已因"顺手调一个通用类"塌过一次）。
 *   · 新 class 必须在这里有定义：`scripts/check_css_class_coverage.py` 会双向核对
 *     （JS 用了但 CSS 没有 ⇒ 红；名单里的欠账被补上却没删名单 ⇒ 也红）。
 *   · 不用 emoji 做**唯一**语义载体：状态同时有文字（`.order-status` 的内容是中文），
 *     颜色只是辅助（色弱用户与打印场景都靠文字）。
 * ══════════════════════════════════════════════════════════════════════════ */

/* ── ① 充值弹层：赠送角标 / 上限说明 / 支付不可用提示 / 替代通道按钮 ────── */

/* 赠送角标：`gift_pct > 0` 时才渲染（0 不渲染「+0%」，见 points.js）。 */
.rc-gift {
  display: inline-block; margin-top: 5px; padding: 1px 7px;
  border-radius: 999px; font-size: 10.5px; font-weight: 700; line-height: 1.7;
  color: #04121a; background: linear-gradient(120deg, var(--brand), var(--violet));
  white-space: nowrap;
}

/* 两个上限（卡密 1 万元 / 在线 2.2 万元）—— 铺满整行，不参与档位网格。 */
.rc-caps {
  grid-column: 1 / -1;
  margin-top: 2px; font-size: 11.5px; line-height: 1.7; color: var(--muted);
  text-align: center;
}
.rc-caps:empty { display: none; }

/* 在线支付可用性提示条：可用＝品牌色，不可用＝中性（配合 `.muted`）。 */
.rc-payhint {
  margin-bottom: 12px; padding: 9px 12px; border-radius: 10px;
  font-size: 12.5px; line-height: 1.75;
  color: var(--text-mid);
  background: var(--brand-soft); border: 1px solid var(--line-2);
}
.rc-payhint.muted {
  color: var(--muted);
  background: rgba(255, 255, 255, .025); border-color: var(--line);
}

/* 不可用时的两条替代通道（**当场把人送走**，不是只把他挡住）。 */
.rc-paygo {
  display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px;
}
.rc-paygo .btn { flex: 1 1 140px; }

/* ★P0 F2：在线支付**不可用**时的卡片内降级区（`#rcPayFallback`）。
   为什么要这块样式而不是复用 `.rc-payhint`：入口（tab）已从 DOM 移除，
   说明区从"pane 里"搬到了"tabs 下面"，需要它自己跟上方 tab 拉开距离，
   否则会贴着 tab 显得像一串标签文字。内层仍是 `.rc-payhint` + `.rc-paygo`，
   **不复制**它们的外观 —— 那会变成两套口径，改一处忘一处。 */
.rc-payfb {
  margin: 10px 0 2px; padding: 10px 12px; border-radius: 12px;
  background: rgba(255, 255, 255, .02); border: 1px solid var(--line);
}
.rc-payfb[hidden] { display: none; }
.rc-payfb .rc-payhint { margin-bottom: 8px; }
.rc-payfb .rc-paygo { margin-bottom: 0; }

/* ── ② 套餐与授权页（`#/plan`）────────────────────────────────────────── */

.plan-hero {
  display: flex; flex-wrap: wrap; align-items: center; gap: 14px;
  padding: 4px 2px 6px;
}
.plan-hero-main { min-width: 0; flex: 1 1 260px; }

.plan-name {
  font-size: 19px; font-weight: 700; color: var(--text);
  letter-spacing: .3px;
}

/* 副行（到期 / 剩余 / 席位 / 说明）—— 用 `<b>` 强调值，其余保持弱化。 */
.plan-meta {
  margin-top: 5px; font-size: 12.5px; line-height: 1.85; color: var(--muted);
}
.plan-meta b { color: var(--text-mid); font-weight: 650; }

/* 余额块：大数字 + 右侧操作。 */
.plan-balance {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 14px;
  padding: 2px 2px 8px;
}
.plan-balance-num {
  font-size: 34px; font-weight: 800; line-height: 1.1;
  color: var(--brand); font-variant-numeric: tabular-nums;
}
.plan-actions { margin-left: auto; display: flex; gap: 8px; }
.plan-actions .btn { text-decoration: none; }

/* 套餐卡网格：三列（窄屏自动降成单列）。 */
.plan-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
}
.plan-card {
  display: flex; flex-direction: column; gap: 10px;
  padding: 14px; border-radius: 14px;
  background: rgba(6, 10, 20, .55); border: 1px solid var(--line);
  transition: border-color .18s ease, transform .18s ease;
}
.plan-card:hover { border-color: var(--line-3); transform: translateY(-1px); }

/* 当前套餐卡：边框高亮 + 不倒影（避免"看起来是推荐购买"的误导）。 */
.plan-card-cur {
  border-color: var(--line-3);
  background: linear-gradient(180deg, var(--brand-soft), rgba(6, 10, 20, .55));
}

.plan-card-head {
  display: flex; align-items: center; gap: 8px; justify-content: space-between;
}
.plan-recommend {
  padding: 1px 8px; border-radius: 999px; font-size: 10.5px; font-weight: 700;
  color: #04121a; background: var(--brand); white-space: nowrap;
}

/* 价格：主价（元）+ 小字（仙贝）。金额一律由后端下发的整数分格式化而来。 */
.plan-price {
  font-size: 24px; font-weight: 800; line-height: 1.2; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.plan-price-unit {
  font-size: 12px; font-weight: 500; color: var(--muted);
  margin-left: 4px; white-space: nowrap;
}

/* 权益清单 */
.plan-feats {
  margin: 0; padding: 0; list-style: none;
  display: flex; flex-direction: column; gap: 5px;
  font-size: 12.5px; line-height: 1.7; color: var(--text-mid);
}
.plan-feats li { display: flex; gap: 6px; }
.plan-feats li::before {
  content: "·"; color: var(--brand); font-weight: 800; line-height: 1.7;
}

/* 输码入口 */
.plan-code-box {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px;
}
.plan-code-box .field { flex: 1 1 280px; margin: 0; }

/* ---------------------------------------------------------------------- *
 * P0 六态按钮：一张卡最多两个动作（扣仙贝 + 网页支付），并排一行。
 * 单按钮时仍然占满整行（`grid-template-columns: 1fr` 对单列自动生效）。
 * ---------------------------------------------------------------------- */
.plan-btn-row {
  margin-top: 2px;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 8px;
}

/* 「去小程序购买」指引弹层里的步骤列表（可照做，不是一句抱歉）。 */
.plan-steps {
  margin: 8px 0 0; padding-left: 20px;
  display: flex; flex-direction: column; gap: 4px;
  font-size: 12.5px; line-height: 1.8; color: var(--text-mid);
}

/* 网页支付二维码：白底 + 静区（扫码器需要），居中等宽。 */
.pay-qr {
  margin: 10px 0; padding: 10px; border-radius: 12px;
  background: #fff; align-self: center;
  display: flex; align-items: center; justify-content: center;
}
.pay-qr img { display: block; width: 240px; height: 240px; }

/* （此处曾定义 `.plan-mpfb`：通道未开通时的「去小程序购买」替代出口。
   已按主理人裁决撤掉 —— 本版没有可达的小程序跳转，挂它等于"必然失败的按钮"，
   P0-4.5③ 明令禁止。前置条件是小程序 URL Link，P1。） */

/* 充值档位里"刚好够待买套餐"的那一档（线索来自 `#/plan`，仅高亮不改价）。 */
.rc-tier-rec {
  border-color: var(--brand) !important;
  background: var(--brand-soft);
}

/* 明文激活码展示：等宽 + 可选 + 高对比（用户要抄/截图）。 */
.plan-code-reveal {
  margin-top: 6px; padding: 12px 14px; border-radius: 10px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 15px; font-weight: 700; letter-spacing: .6px; line-height: 1.7;
  color: var(--text); background: rgba(34, 224, 255, .08);
  border: 1px dashed var(--line-3);
  word-break: break-all; user-select: all;
}

/* 二次确认弹层正文 */
.plan-confirm { display: flex; flex-direction: column; gap: 2px; }

/* ── ③ 订单状态徽标 ────────────────────────────────────────────────────
 * 颜色只是辅助：徽标内始终有中文状态文案（`status_text` 由后端下发，
 * 前端不维护第二份映射 —— 见 plan.js 的注释）。
 */
.order-status {
  display: inline-block; padding: 1px 8px; border-radius: 999px;
  font-size: 11px; font-weight: 650; line-height: 1.75; white-space: nowrap;
  color: var(--muted); background: rgba(255, 255, 255, .045);
  border: 1px solid var(--line);
}
.order-status.ok     { color: #7ff0c2; background: rgba(46, 213, 148, .13); border-color: rgba(46, 213, 148, .32); }
.order-status.info   { color: var(--info); background: var(--info-soft); border-color: rgba(70, 184, 255, .32); }
.order-status.warn   { color: var(--warn); background: var(--warn-soft); border-color: rgba(255, 181, 69, .34); }
.order-status.danger { color: var(--danger); background: var(--danger-soft); border-color: rgba(255, 77, 109, .36); }

/* ── 通用小工具类（此前只以内联样式散落各处，这里给出唯一正式定义）────── */
.muted { color: var(--muted); }

/* ── P1-5 剪贴板降级框（web/js/core/clipboard.js）────────────────────────
 * 非 https / 老内核没有 navigator.clipboard 时插入的**可见**兜底：一段提示
 * + 一个已自动全选的只读 textarea。用户按 Ctrl+C 即可拿走全文。
 * 必须**可见**（不能 display:none 也不能 position:fixed —— 后者 offsetParent
 * 为 null，QA 判据 P1-I-4 的「offsetParent !== null」会假红）。
 * flex 容器里用 flex-basis:100% 换行独占一行，不挤爆卡片脚。
 */
.copy-fallback {
  flex-basis: 100%;
  margin: 6px 0 2px;
  padding: 8px 10px;
  border: 1px dashed rgba(255, 181, 69, .5);
  border-radius: 10px;
  background: var(--warn-soft);
}
.copy-fallback-tip {
  font-size: 12px;
  line-height: 1.6;
  color: var(--warn);
  margin-bottom: 4px;
}
.copy-fallback-text {
  width: 100%;
  min-height: 72px;
  box-sizing: border-box;
  resize: vertical;
  font: inherit;
  font-size: 12px;
  line-height: 1.6;
  color: var(--text);
  background: rgba(255, 255, 255, .05);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 6px 8px;
}

/* 窄屏：套餐卡单列、余额操作换行；避免 375px 横向溢出。 */
@media (max-width: 720px) {
  .plan-grid { grid-template-columns: 1fr; }
  .plan-balance { align-items: flex-start; }
  .plan-actions { margin-left: 0; width: 100%; }
  .plan-actions .btn { flex: 1 1 auto; }
  .plan-price { font-size: 21px; }
  .plan-confirm .plan-meta { font-size: 12px; }
}

/* ============================================================================
   P1-4 · T05：CSS 欠账销项（27 项逐项处置）
   ----------------------------------------------------------------------------
   这些 class 在 `web/index.html` / `web/js/**` 里早就在用，但 `app.css` 里
   **一条规则都没有** —— 浏览器不报错、单测不管、界面只是"没样式"。
   `scripts/check_css_class_coverage.py` 的 `_KNOWN_DEBT` 曾把它们登记为历史欠账；
   本节补齐真实样式后，名单已清空（守卫会双向校验：补了样式却还留在名单里=红）。
   写法纪律：只用既有语义变量（`--line` / `--panel-2` / `--text-mid` …），
   不写字面色值；不新增同名变体（禁 `xxx-2` 式镜像命名）。
   ========================================================================== */

/* ---- 帮助中心「流云」教学卡步骤体（兄弟类 .asst-guide-t / .asst-guide-d 已有）---- */
.asst-guide-b { min-width: 0; flex: 1; }

/* ---- 思维链（cot.js）：筛选条 / 容器 / 标题 / 视图 ----
   `.cot-filterbar` 与 `.cot-view` 另有 JS 显隐依赖（muxue.js），故补样式而非删 class。 */
.cot-filterbar { margin-bottom: 12px; }
.cot-panel { display: flex; flex-direction: column; }
.cot-view { position: relative; }
.cot-ho-n {
  display: inline-flex; align-items: center; gap: 6px;
  font-weight: 600; color: var(--text-mid);
}

/* ---- 内容工厂：作品墙空位里的图标（无图片资源时的占位）---- */
.ico { font-size: 26px; line-height: 1; margin-bottom: 6px; }

/* ---- 社媒矩阵：KPI 四列 ----
   `.kpi-row` 是 4 个 `.kpi-num/.kpi-label` 的栅格容器；窄屏折成两列，
   避免 375px 上四列各宽 < 80px 时数字被压成竖排。 */
.kpi-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.kpi-num { font-size: 24px; font-weight: 700; color: var(--text); }
.kpi-label { font-size: 11.5px; color: var(--muted); margin-top: 2px; }

/* ---- 设置中心：大模型接入的提供商选项（名称 / 说明 / 模型）---- */
.p-name { display: block; font-weight: 600; color: var(--text); }
.p-note { display: block; font-size: 12px; color: var(--muted); margin-top: 2px; }
.p-model { display: block; font-size: 11.5px; color: var(--muted-soft); margin-top: 3px; }

/* ---- 设置中心 · AI 能力中心：场景表的通道列 / 开关 / 保存 ----
   `.scene-enable-toggle` 与 `.scene-key-save` 有批量事件委托依赖（settings.js），
   保留 class 并补样式。 */
.scene-channel { min-width: 180px; }
.scene-enable-toggle { cursor: pointer; }
.scene-key-save { white-space: nowrap; }

/* ---- 内容工厂 · 标准运营工作流：排期"进行中"呼吸态 ---- */
.sched-busy { animation: schedBusyPulse 1.6s ease-in-out infinite; }
@keyframes schedBusyPulse { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }

/* ---- 内容工厂：长文 / 简报正文块（保留换行，不把段落揉成一坨）---- */
.sow-art {
  font-size: 13px; line-height: 1.75;
  color: var(--text-mid); white-space: pre-wrap;
}
.sow-chat-sys-b { color: var(--text-mid); }

/* ---- 设置中心：运行信息 / 计费概览的统计块 ---- */
.stats { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
.stat {
  padding: 12px 14px; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--panel-2);
}
.stat-v { font-size: 20px; font-weight: 700; color: var(--text); }
.stat-k { font-size: 12px; color: var(--muted); margin-top: 2px; }
.stat-s { font-size: 11px; color: var(--muted-soft); margin-top: 2px; }

/* 窄屏：KPI 四列 → 两列（数字不被挤压） */
@media (max-width: 720px) {
  .kpi-row { grid-template-columns: repeat(2, 1fr); }
}

/* 动效可关闭：呼吸态对前庭敏感人群不友好，尊重系统偏好。 */
@media (prefers-reduced-motion: reduce) {
  .sched-busy { animation: none; }
}

/* ============================================================================
   P1-4 · T03：帮助中心落地页（#/help）
   ----------------------------------------------------------------------------
   两栏：左目录（搜索框 + 12 个页面）/ 右正文。窄屏折成单栏。
   四态表现：`#helpBody[data-state]` 由 `pages/help.js` 每次重渲染落值；
   文案一律走 `.empty`（本文件 617 行那条统一空态样式），这里只管骨架与排版。
   ========================================================================== */
.help-entry { gap: 6px; }

.help-layout {
  display: grid; grid-template-columns: 232px minmax(0, 1fr);
  gap: 16px; align-items: start;
}
.help-side { position: sticky; top: 0; display: grid; gap: 10px; }

/* ---- 帮助中心：搜索框 ----
   输入框本体样式继承 `.field input`（同一套深色输入视觉 + 品牌色 focus 环），
   这里只给"搜索帮助"这个标签定字级 —— 不复制一份输入框配色。 */
.help-search { display: grid; gap: 6px; }
.help-search span {
  font-size: 11.5px; letter-spacing: .06em;
  color: var(--muted-soft);
}

.help-toc {
  display: grid; gap: 4px; padding: 10px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel);
}
/* 小标题用大写间距（ALL-CAPS 必须加大字距，否则字母会黏在一起） */
.help-toc-h {
  font-size: 11.5px; letter-spacing: .06em;
  color: var(--muted-soft); padding: 2px 6px 6px;
}
.help-toc-i {
  text-align: left; padding: 7px 9px; border-radius: 8px;
  border: 1px solid transparent; background: transparent;
  color: var(--text-mid); font: inherit; font-size: var(--fs-sm);
  cursor: pointer; transition: background .15s, color .15s, border-color .15s;
}
.help-toc-i:hover { background: var(--brand-soft); color: var(--text); border-color: var(--line); }
.help-toc-i.on {
  background: var(--brand-soft); border-color: var(--line-2);
  color: var(--text); font-weight: 600;
}
.help-toc-tip { font-size: 12px; color: var(--muted-soft); padding: 5px 7px; }

.help-doc {
  min-width: 0; padding: 16px 18px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel);
}
.help-art-t { margin: 0 0 6px; font-size: var(--fs-lg); color: var(--text); }
/* 正文块限行宽：宽屏上不限制的话一行能排到 200 字，回行时找不到下一行开头。 */
.help-art { min-width: 0; max-width: 72ch; }
.help-intro {
  margin: 0 0 14px; color: var(--text-mid);
  line-height: var(--lh-base); font-size: var(--fs-sm);
}
.help-sub { margin: 16px 0 8px; font-size: 12px; letter-spacing: .06em; color: var(--muted); }

/* 步骤列表：标题里**自带序号**（「① …」来自 core/help.py），所以这里不能再让
   浏览器加一遍 `1. 2. 3.` —— 那会渲染成「1. ① 一句话下达目标」（实测踩过）。 */
.help-steps { margin: 0; padding: 0; list-style: none; display: grid; gap: 12px; }
.help-step { color: var(--text-mid); font-size: var(--fs-sm); }
.help-step-t { font-weight: 600; color: var(--text); }
.help-step-x { margin-top: 2px; line-height: var(--lh-base); }

.help-faq { margin: 0; padding: 0; list-style: none; display: grid; gap: 10px; }
.help-faq-i {
  padding: 10px 12px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--panel-2);
}
.help-faq-q { font-weight: 600; color: var(--text); font-size: var(--fs-sm); }
.help-faq-a {
  margin-top: 4px; color: var(--text-mid);
  font-size: var(--fs-sm); line-height: var(--lh-base);
}

.help-actions { display: flex; flex-wrap: wrap; gap: 6px; }
/* 动作 id 是**标识符**不是文案：用等宽字体 + 胶囊形，读起来像代码而不是句子 */
.help-badge {
  font-family: var(--mono); font-size: 11.5px; color: var(--brand);
  border: 1px solid var(--line-2); border-radius: 999px; padding: 2px 9px;
}

.help-hit-h { font-size: 12px; color: var(--muted); margin-bottom: 10px; }
.help-hits { margin: 0; padding: 0; list-style: none; display: grid; gap: 8px; }
.help-hit { min-width: 0; }
.help-hit-go {
  width: 100%; text-align: left; display: grid; gap: 4px;
  padding: 11px 13px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--panel-2); color: var(--text-mid);
  font: inherit; cursor: pointer;
  transition: border-color .15s, background .15s;
}
.help-hit-go:hover { border-color: var(--line-3); background: var(--brand-soft); }
.help-hit-t { font-weight: 600; color: var(--text); font-size: var(--fs-sm); }
.help-hit-s { font-size: 12.5px; line-height: 1.6; color: var(--muted); }

@media (max-width: 900px) {
  .help-layout { grid-template-columns: 1fr; }
  .help-side { position: static; }
  /* 窄屏：12 项目录竖排会把正文推到三屏之外，改成横向可滑的一排。
     `overflow-x: auto` 而非 `hidden`：宁可让用户滑，也不把条目藏起来。 */
  .help-toc {
    grid-auto-flow: column; grid-auto-columns: max-content;
    overflow-x: auto; gap: 6px; padding: 8px;
  }
  .help-toc-h, .help-toc-tip { display: none; }
  .help-toc-i { white-space: nowrap; }
}

/* ============================================================================
   顶栏窄屏重排 + 抽屉快捷入口（任务 #3：手机端顶部按钮重叠）
   ============================================================================

   症状：375px 手机上顶栏右侧控件互相压在一起、并盖住左上角品牌区。

   根因（实测数据，不是推测）：
     · `.topbar-right` 是 `flex: 0 1 auto`，会被压缩。375px 下 .topbar-left
       占 0~169.6px，两侧 padding+gap 再吃掉 20px，留给右侧的只有约 185px；
     · 而右侧子元素全是 `white-space:nowrap` 的 chip / button，min-content
       压不下去 ⇒ 内容溢出自身盒子；
     · `.topbar-right` 又是 `justify-content:flex-end`，溢出方向朝**左**，
       于是正好盖在品牌区上。视觉上就是"按钮重叠"。
     · ⚠️ 此时 `document.scrollWidth === clientWidth`（layout viewport 被一起
       撑大），**横向滚动条这条判据查不出来** —— 与上面 256 行那段注释记的是
       同一类坑，所以这里不靠 `overflow:hidden` 遮，靠"按重要性重排"。

   修法：≤900px（**与抽屉出现的断点完全一致**，否则 769~900 会留下一段
   "抽屉不可用、顶栏又放不下"的死角）顶栏只留主操作（仙贝余额 / 用户菜单，
   游客态则是「微信扫码进入」），其余入口（激活码 / 新手引导 / 帮助中心 /
   AI 助手）挪进手机抽屉的 `#navQuick` 组（见 index.html），由 app.js 用
   data-act 转发到原按钮，保证逻辑只有一份。

   重排后的宽度预算（实测值，见交付说明）：
     · 375px 登录态：左 169.6 + 8 + (30 + 36.7 + 36 + 2×6) ≈ 292 < 375 ✔
     · 375px 游客态：左 169.6 + 8 + 108 ≈ 286 < 375 ✔
     · 768px 登录态（含引导）：左 184.6 + 8 + (59.6+66.3+81.6+2×10) ≈ 429 < 768 ✔
   ========================================================================== */

/* 抽屉快捷入口组：宽屏隐藏（顶栏本来就有这些按钮） */
.nav-quick { display: none; }

/* 把右侧控件顶到最右边。
   为什么需要：`.topbar-right` 原本是靠 `.topbar-center { flex:1 }` 顶到右边的，
   而 `.topbar-center` 在 ≤1180px 就被 `display:none` 掉了。此前右侧之所以看着
   还在右边，纯属"内容撑满剩余空间"的副作用 —— 一旦按本任务把内容精简下来，
   右上角就会缩到品牌名旁边、右边留一大片空白（实测 375px 下右侧控件只排到
   x=256，右边空了约 107px）。
   `margin-left:auto` 让它在任何宽度都贴右；空间不够时 auto 边距自动归零，
   不会掩盖/制造溢出。>1180px 时 center 已吃掉全部剩余空间，这里无任何影响。 */
.topbar-right { margin-left: auto; }

/* <button> 要借用 .nav-item 的外观，得先把 UA 的边框/底色/宽度清掉 */
.nav-quick .nav-act {
  width: 100%;
  background: transparent;
  border: 0;
  text-align: left;
  font: inherit;
}
/* ★必须显式处理 [hidden]：`.nav-item { display:flex }` 是作者样式，
   优先级高于 UA 的 `[hidden]{display:none}`，不写这条就会出现
   "被标为隐藏却仍然可见"的死入口（本项目 index.html 里已有同类注释）。 */
.nav-quick .nav-act[hidden] { display: none !important; }

@media (max-width: 1180px) {
  /* 长用户名不许把顶栏顶宽。901~1180px 这一段顶栏仍是"全量"（抽屉还没出现），
     西文昵称还好，中文长昵称会直接把右侧挤出去。 */
  #userMenu .user-name { max-width: 84px; overflow: hidden; text-overflow: ellipsis; }

  /* 901~1180px 是宽度预算最紧的一段（抽屉 ≤900 才出现，右侧控件全量展示）。
     修法：次要文字整段退场，只留图标 + title 提示（.hide-on-narrow 是这批按钮
     文字的既有标记类）；chip 内边距与 gap 收紧。实测最坏预算（901px 登录态）：
     左 ~240 + 右 icon-only ~480 = 720 < 901，品牌区不再被压盖。
     ⚠️ 不要在这里整隐 #streamChip：它退成圆点后只剩 ~28px，是"实时流在跑"
     的唯一信号，比省这 28px 更值钱（彻底隐藏仍是 ≤420px 的既有规则）。 */
  .topbar { gap: 10px; }
  .topbar-right { gap: 6px; }
  .topbar-right .chip,
  .topbar-right .btn { padding-left: 8px; padding-right: 8px; }
  .topbar .hide-on-narrow { display: none; }
}

@media (max-width: 900px) {
  /* 抽屉里的快捷入口只在窄屏出现（.nav-group 已给 flex-direction:column） */
  .nav-quick { display: flex; }

  /* 副标题收起，只留「沐雪 AI」品牌名：它能省下约 108px，是窄屏最大的一块 */
  .brand-sub { display: none; }

  /* 顶栏只保留主操作。用 id 选择器（优先级 1,0,0）压过 .btn / .chip 的 display 声明，
     不需要 !important。 */
  #chipCode,
  #tourEntry,
  #helpEntry,
  #asstToggle { display: none; }
}

@media (max-width: 640px) {
  /* 品牌名降一档字号 + 收 letter-spacing：17px 是固定值不随屏宽缩放，
     在 375px 上与右侧控件争宽（T3）。beta 徽标的中文尾巴在此收起，只留「Beta」。 */
  .brand-name { font-size: 15px; letter-spacing: .8px; }
  .beta-badge .beta-cn { display: none; }
}

@media (max-width: 420px) {
  .brand-name { font-size: 14px; letter-spacing: .5px; }
  .beta-badge { margin-left: 6px; padding: 1px 5px; letter-spacing: .4px; }
}

/* ============================================================================
   客户资产 · 智能体记忆卡片（v55 对话增强记忆）
   ----------------------------------------------------------------------------
   一张普通 .card，挂在客户资产页内容区顶部。四块用 <details> 折叠，
   只默认展开第一块 —— 全展开时这张卡在 1366×768 上要吃掉近一屏，
   会把真正的主角（客户列表）顶到屏幕外。

   全部走既有设计令牌（--line / --brand / --danger …），不引入新色值。
   "已停用"的约束行用 .mem-off 压暗 + 文字标记，**不隐藏** ——
   停用是可回滚状态，藏起来用户就以为删掉了（也就再也点不到「恢复」）。
   ========================================================================== */
.mem-card { margin-bottom: 16px; }
.mem-card > .card-head { margin-bottom: 10px; }

/* 折叠区：与 .asst-fold 同一套观感（▸ / ▾ 三角，不是图标字体） */
.mem-fold { border-top: 1px solid var(--line); }
.mem-fold > summary {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 2px;
  font-size: var(--fs-sm); font-weight: 600; letter-spacing: .2px;
  color: var(--text-mid); cursor: pointer; list-style: none;
}
.mem-fold > summary::-webkit-details-marker { display: none; }
.mem-fold > summary::before { content: "▸"; color: var(--brand); font-size: 11px; }
.mem-fold[open] > summary::before { content: "▾"; }
.mem-fold-n { font-size: 11.5px; font-weight: 500; color: var(--muted-soft); }

.mem-sec { padding: 2px 2px 14px; display: grid; gap: 10px; }

.mem-group { display: grid; gap: 5px; }
.mem-k { font-size: 11.5px; color: var(--muted); letter-spacing: .4px; }

.mem-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.mem-chip {
  padding: 3px 9px; border-radius: 999px;
  font-size: 12px; color: var(--text-mid);
  background: rgba(255, 255, 255, .045);
  border: 1px solid var(--line-2);
}
/* 只有按钮形态的 chip 才有点击反馈（标签形态保持静态） */
button.mem-chip { transition: color .15s, border-color .15s, background .15s; }
button.mem-chip:hover {
  color: var(--brand); border-color: rgba(34, 224, 255, .42); background: var(--brand-soft);
}

.mem-empty {
  padding: 14px; border-radius: var(--radius);
  border: 1px dashed var(--line-2); background: rgba(255, 255, 255, .012);
  color: var(--muted-soft); font-size: 12.5px; line-height: 1.7;
}
.mem-tip { font-size: 12px; color: var(--muted-soft); }
.mem-hint { font-size: 12px; color: var(--warn); line-height: 1.7; }

.mem-proof { margin: 0; padding-left: 16px; display: grid; gap: 4px; }
.mem-proof li { font-size: 12.5px; color: var(--text-mid); line-height: 1.7; }

.mem-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }

/* ---- 反馈入口 ---- */
.mem-fb { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.mem-input {
  flex: 1 1 260px; min-width: 0;
  padding: 8px 11px; border-radius: 10px;
  font: inherit; font-size: var(--fs-sm);
  color: var(--text); background: rgba(255, 255, 255, .03);
  border: 1px solid var(--line-2);
  transition: border-color .15s, box-shadow .15s;
}
.mem-input::placeholder { color: var(--muted-soft); }
.mem-input:focus {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-soft);
}
.mem-target { font-size: 12px; color: var(--muted); overflow-wrap: anywhere; }
.mem-target b { color: var(--brand); font-weight: 600; }

/* 确认行：把"我听懂了什么"摆在按钮旁边，用户点头才落库（/parse 是 dry-run） */
.mem-confirm {
  display: grid; gap: 8px;
  padding: 10px 12px; border-radius: 10px;
  background: var(--brand-soft); border: 1px solid rgba(34, 224, 255, .22);
}
.mem-confirm-t { font-size: 12.5px; color: var(--text-mid); }
.mem-confirm-t b { color: var(--brand); font-weight: 600; }
.mem-outcomes { display: flex; flex-wrap: wrap; gap: 6px; }
.mem-result { font-size: 12.5px; color: var(--success); line-height: 1.7; }

/* 失败不藏：一行红字 + 接口原话，留在卡片里（不用 display:none 消失） */
.mem-err {
  margin: 0 0 10px; padding: 8px 11px; border-radius: 10px;
  font-size: 12.5px; line-height: 1.65; color: var(--danger);
  background: var(--danger-soft); border: 1px solid rgba(255, 77, 109, .34);
  overflow-wrap: anywhere;
}

/* ---- 生效约束 ---- */
.mem-cons-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  padding: 8px 10px; border-radius: 10px;
  border: 1px solid var(--line); background: rgba(255, 255, 255, .02);
}
.mem-cons-row + .mem-cons-row { margin-top: 8px; }
.mem-cons-t { flex: 1 1 220px; min-width: 0; font-size: 12.5px; color: var(--text-mid); overflow-wrap: anywhere; }
.mem-cons-t b { color: var(--text); font-weight: 600; }
.mem-cons-n { font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
/* 已停用：压暗但仍可读、仍可点「恢复」（藏起来 = 用户以为删了） */
.mem-off { opacity: .55; }

/* ---- 试筛结果（第 3 块底部）：证明"记忆真的会让线索变少" ---- */
.mem-preview {
  margin-top: 10px; padding: 10px 12px; border-radius: 10px;
  border: 1px solid var(--line-2); background: rgba(255, 255, 255, .022);
  display: grid; gap: 6px;
}
.mem-preview-sum {
  font-size: 12.5px; color: var(--text-mid); line-height: 1.7;
  font-variant-numeric: tabular-nums;
}
/* 0 条被排掉是"跑过且真的没排"，用成功色说清楚 —— 不能和"没跑"长得一样 */
.mem-preview-ok { font-size: 12px; color: var(--success); line-height: 1.7; }
.mem-preview-list { margin: 0; padding: 0; list-style: none; display: grid; gap: 4px; }
.mem-preview-list > li {
  display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline;
  padding: 4px 0; border-top: 1px dashed var(--line);
  font-size: 12.5px; line-height: 1.7;
}
.mem-preview-list > li:first-child { border-top: 0; }
.mem-preview-co { color: var(--text); font-weight: 600; overflow-wrap: anywhere; }
/* 破折号放在 CSS 里：不把中文标点拼进模板字符串，也不必在 JS 里判断有没有原因 */
.mem-preview-why { color: var(--muted); flex: 1 1 180px; min-width: 0; overflow-wrap: anywhere; }
.mem-preview-why::before { content: "— "; color: var(--muted-soft); }

/* ---- 最近反馈 ---- */.mem-recent-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  padding: 6px 0; font-size: 12.5px;
  border-bottom: 1px dashed var(--line);
}
.mem-recent-row:last-child { border-bottom: 0; }
.mem-recent-t { flex: 1 1 200px; min-width: 0; color: var(--text-mid); overflow-wrap: anywhere; }
/* 行文是「公司名：打不通／没人接」—— 冒号放在 CSS 里，别在 JS 里拼中文标点 */
.mem-recent-o::before { content: "："; color: var(--muted-soft); }
.mem-recent-o { color: var(--text); }
.mem-time { font-size: 11.5px; color: var(--muted-soft); font-variant-numeric: tabular-nums; }

/* ---- 客户列表的行选中（反馈要记到哪一家） ---- */
/* 只给带了 data-label 的行（客户资产页）指针手型，别的表格不受影响 */
table.data tbody tr[data-label] { cursor: pointer; }
.mem-picked td { background: rgba(34, 224, 255, .06); }
.mem-picked td:first-child { box-shadow: inset 2px 0 0 var(--brand); }

@media (max-width: 640px) {
  /* 窄屏：输入框独占一行，按钮不跟着挤成一条缝 */
  .mem-fb { flex-direction: column; align-items: stretch; }
  .mem-input { flex: 1 1 auto; }
}

/* ============================================================================
   体验开放 v1 · 设计 Token（:root 段 —— 必须先于下方组件补丁生效）
   ----------------------------------------------------------------------------
   · 全部 `--xp-*` 都是对**既有** `:root` token 的再映射，不新增第二套色板
     （同一屏出现两种"青"是本项目已经付过代价的坑）。
   · 本段**零颜色字面量**：连"中性叠加"也不写裸 rgba，改写为
     `color-mix(... var(--text) ... % , transparent)` —— 它仍然是"既有前景色 + 透明"，
     只是把"白"换成了"当前前景"，于是浅色主题下自动跟着变。
   · 命名：`--xp-{语义}`，按用途命名，不按色相命名（不出现 `--xp-cyan-1`）。
   · 来源：docs/design-tokens-体验开放.css（v1.2）。差异仅两处，均已注明。
   ========================================================================== */
:root {
  /* ── 品牌身份映射（值为既有 token） ── */
  --xp-accent:              var(--brand);
  --xp-accent-strong:       var(--brand-strong);
  /* 差异①：设计稿此处写死了一个深色十六进制值（= .btn.primary 的现状前景色）。
     本模块**没有**任何一处引用它，所以改为别名 `var(--bg)`：既守住"零颜色字面量"，
     也不会在浅色主题下留下一个永远深色的前景常数。 */
  --xp-accent-on:           var(--bg);
  --xp-fg:                  var(--text);
  --xp-fg-2:                var(--text-mid);
  --xp-meta:                var(--muted);          /* 6.07:1 —— 第二层文字下限 */
  --xp-line:                var(--line);
  --xp-line-2:              var(--line-2);
  --xp-surface:             var(--panel-2);
  --xp-surface-solid:       var(--panel-solid);   /* 粘性条专用：必须不透明 */
  /* 差异②：设计稿写 `rgba(255,255,255,.025)`。改为"前景色 2.5% 叠加"，
     视觉等价、语义更准（是"抬一层"而不是"加白"）。 */
  --xp-surface-wash:        color-mix(in srgb, var(--text) 2.5%, transparent);

  /* ── 语义色（沿用既有色相，不新增） ── */
  --xp-ok:                  var(--success);
  --xp-warn:                var(--warn);
  --xp-bad:                 var(--danger);
  --xp-info:                var(--info);

  /* ── 锁语义：颜色表达"下一步去哪"，不表达"有多严重" ── */
  --xp-lock-plan:           var(--info);          /* 蓝 = 去开通套餐 */
  --xp-lock-quota:          var(--warn);          /* 橙 = 去清理 / 升级 */
  --xp-lock-points:         var(--danger);        /* 红 = 去充值 */
  --xp-lock-settle:         var(--muted);         /* 灰 = 等一等 */
  --xp-lock-plan-soft:      var(--info-soft);
  --xp-lock-quota-soft:     var(--warn-soft);
  --xp-lock-points-soft:    var(--danger-soft);
  --xp-lock-settle-soft:    color-mix(in srgb, var(--text) 5%, transparent);
  /* 锁的「边线」版本：组件只许从这四个取锁边线，**不许再借 --xp-est-*-line**。
     借预估条的边线会让两套语义纠缠（改预估条配色时锁跟着变色，改的人想不到）。
     由锁色自己派生，单跳不跨语义。 */
  --xp-lock-plan-line:      color-mix(in srgb, var(--xp-lock-plan) 46%, transparent);
  --xp-lock-quota-line:     color-mix(in srgb, var(--xp-lock-quota) 38%, transparent);
  --xp-lock-points-line:    color-mix(in srgb, var(--xp-lock-points) 42%, transparent);
  --xp-lock-settle-line:    var(--line-2);
  --xp-bal-ok:              var(--brand);         /* 只有"有余额"与"零/未知"两种色 */

  /* ── 演示样例降饱和（本轮未使用，保留语义位） ── */
  --xp-demo-dim:            .82;

  /* ── 功能入口三态：虚线/实线 + 图标 + 文字三重编码 ── */
  --xp-open-full:           var(--muted);
  --xp-open-partial:        var(--info);
  --xp-open-partial-line:   color-mix(in srgb, var(--info) 46%, transparent);
  --xp-open-partial-bg:     color-mix(in srgb, var(--info) 10%, transparent);
  --xp-open-none:           var(--muted);         /* 替代 --muted-soft(3.93:1)，此处 6.07:1 */
  --xp-open-none-line:      var(--line-2);
  --xp-open-none-bg:        color-mix(in srgb, var(--text) 3.5%, transparent);

  /* ── 预估条三态：音量随风险递增，常态刻意低音量 ── */
  --xp-est-ok:              var(--brand);
  --xp-est-near:            var(--warn);
  --xp-est-over:            var(--danger);
  --xp-est-ok-line:         var(--line-2);
  --xp-est-near-line:       color-mix(in srgb, var(--warn) 38%, transparent);
  --xp-est-over-line:       color-mix(in srgb, var(--danger) 42%, transparent);
  --xp-est-over-bg:         color-mix(in srgb, var(--danger) 8%, transparent);

  /* ── 形状 / 间距 / 动效（功能型，150–320ms 收敛，禁止回弹缓动） ── */
  --xp-radius:              10px;                 /* 与 .btn 同值，并排不打架 */
  --xp-radius-sm:           8px;
  --xp-space-1:             4px;
  --xp-space-2:             8px;
  --xp-space-3:             12px;
  --xp-motion-fast:         120ms;
  --xp-motion-base:         200ms;
  --xp-flash-ms:            320ms;
  --xp-ease:                cubic-bezier(.2, .9, .3, 1);
  --xp-ease-out:            cubic-bezier(.2, 0, 0, 1);
}

/* ============================================================================
   体验额度与功能受限表达（v1.2，2026-10-23）
   —— 全部新增 class 以 .xp- 前缀；颜色一律 var(--xp-*)，组件层不出现颜色字面量。
   —— 依赖：上方 :root 段（须先于本节生效）。
   —— v1.2 改动：① 侧栏额度块去掉 .budget-bar 依赖；② .xp-est 增 hint；
      ③ .xp-spend 增 is-pending / -pend；④ .xp-lockbtn 增 data-kind="expired"。
   ========================================================================== */

/* ── 预估条：钉在 .modal-body 底部，与主按钮永远同屏 ─────────────────────── */
.xp-est {
  position: sticky; bottom: 0; z-index: 2;
  margin: 14px 0 0; padding: 11px 13px;
  border-radius: var(--xp-radius);
  border: 1px solid var(--xp-est-ok-line);
  background: var(--xp-surface-solid);      /* 不透明：滚动时不许透出下面的表单 */
}
.xp-est[data-state="near"] { border-color: var(--xp-est-near-line); }
.xp-est[data-state="over"] {
  border-color: var(--xp-est-over-line);
  background: var(--xp-est-over-bg), var(--xp-surface-solid);
}
.xp-est-row { display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; }
.xp-est-label { font-size: var(--fs-xs); color: var(--xp-meta); letter-spacing: .3px; }
.xp-est-num {
  font-family: var(--mono); font-size: var(--fs-xl); font-weight: 700;
  font-variant-numeric: tabular-nums; color: var(--xp-accent-strong);
}
.xp-est[data-state="over"] .xp-est-num { color: var(--xp-est-over); }
.xp-est-unit { font-size: var(--fs-xs); color: var(--xp-meta); }
.xp-est-pct {
  margin-left: auto; font-size: var(--fs-xs); color: var(--xp-meta);
  font-variant-numeric: tabular-nums;
}
.xp-est[data-state="near"] .xp-est-pct { color: var(--xp-est-near); font-weight: 600; }
.xp-est[data-state="over"] .xp-est-pct { color: var(--xp-est-over); font-weight: 600; }
.xp-est .budget-bar { margin-top: 7px; height: 6px; }
.xp-est .budget-bar > i { transition: width var(--xp-motion-base) var(--xp-ease-out); }
.xp-est-split {
  margin-top: 6px; font-size: 11.5px; line-height: 1.6; color: var(--xp-meta);
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
/* 「按最贵模型估算，实扣通常更低」—— 这不是可选提示，是契约（§8.1）：
   不写这一句，"最多 N" 就会被读成"要扣 N"。 */
.xp-est-hint { margin-top: 3px; font-size: 11px; line-height: 1.5; color: var(--xp-meta); }
.xp-est .notice { margin: 9px 0 0; }
.xp-est-acts { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 9px; }
.xp-est-acts:empty { display: none; }

/* ── 侧栏额度块 ──────────────────────────────────────────────────────────── */
.xp-quota {
  margin: 0 0 9px; padding: 9px 10px;
  display: grid; gap: 6px;
  border: 1px solid var(--xp-line); border-radius: var(--xp-radius);
  background: var(--xp-surface-wash);
}
.xp-quota[data-state="spent"]      { border-color: var(--xp-line-2); }
/* `zero-new` = 余额 0 且**从未消耗过**（不是"还没领到"——那一格后端没有，架构已驳回
   `welcome_granted`；也不是"用完了"——那是 `spent`）。视觉与 `spent` 完全一致，
   **只靠文案区分**：余额 0 的用户点「升级套餐」必然失败（套餐不送仙贝、买套餐反而扣仙贝），
   所以这一个态**绝不能用蓝** —— 本模块蓝色一律表示"去套餐页"。 */
.xp-quota[data-state="zero-new"]   { border-color: var(--xp-line-2); }
.xp-quota[data-state="no-ledger"]  { border-color: var(--xp-line-2); }
.xp-quota-row { display: flex; align-items: baseline; gap: 6px; }
.xp-quota-label { font-size: 11px; color: var(--xp-meta); letter-spacing: .2px; white-space: nowrap; }
.xp-quota-num {
  margin-left: auto; font-family: var(--mono); font-size: var(--fs-sm);
  font-weight: 700; font-variant-numeric: tabular-nums; color: var(--xp-bal-ok);
}
.xp-quota[data-state="spent"] .xp-quota-num       { color: var(--xp-meta); }
.xp-quota[data-state="zero-new"] .xp-quota-num { color: var(--xp-meta); }
.xp-quota[data-state="no-ledger"] .xp-quota-num,
.xp-quota[data-state="unbound"] .xp-quota-num,
.xp-quota[data-state="unknown"] .xp-quota-num      { color: var(--xp-meta); }
/* 注意：这里**没有** .budget-bar。2,000 仙贝是终身一次发放，
   任何"剩余比例"图形都在讲"你在耗尽"，而实测 1~6 仙贝/次能跑 300~2,000 次（§C.2）。 */
.xp-quota-note { font-size: 11px; line-height: 1.5; color: var(--xp-meta); }
.xp-quota-note:empty { display: none; }
.xp-quota[data-state="zero-new"] .xp-quota-note { color: var(--xp-meta); }
.xp-quota-cta:empty { display: none; }
.xp-quota-cta .btn { width: 100%; justify-content: center; }
/* 收起态（74px）：只留数字 */
body.nav-collapsed .xp-quota { padding: 7px 6px; }
body.nav-collapsed .xp-quota-row .xp-quota-label,
body.nav-collapsed .xp-quota-note,
body.nav-collapsed .xp-quota-cta { display: none; }
body.nav-collapsed .xp-quota-num { margin: 0 auto; font-size: 11px; }

/* ── 侧栏三态标记（替换既有 emoji .lock） ─────────────────────────────────── */
.nav-item .xp-nav-flag {
  flex: none; display: none; align-items: center; gap: 4px;
  padding: 2px 6px; border-radius: 999px;
  font-style: normal; font-size: 10.5px; line-height: 1;
  border: 1px solid var(--xp-open-none-line);
  background: var(--xp-open-none-bg);
  color: var(--xp-open-none);
  white-space: nowrap;
}
.nav-item .xp-nav-flag svg { width: 16px; height: 16px; flex: none; }
.nav-item.xp-partial .xp-nav-flag {
  display: inline-flex;
  border-style: dashed;                              /* 形状维度：虚线 = 部分 */
  border-color: var(--xp-open-partial-line);
  background: var(--xp-open-partial-bg);
  color: var(--xp-open-partial);
}
.nav-item.xp-none .xp-nav-flag { display: inline-flex; }
/* 修 AA：原 .locked 用 --muted-soft（3.93:1）不达标，抬到 --muted（6.07:1） */
.nav-item.locked, .nav-item.xp-none { color: var(--xp-open-none); }
.nav-item.xp-none .ni { opacity: .55; }
.nav-item.locked:hover, .nav-item.xp-none:hover,
.nav-item.xp-none:focus-visible { color: var(--xp-fg-2); }
.nav-item.xp-partial .ni { opacity: 1; }             /* 部分开放：图标不降饱和 */
body.nav-collapsed .nav-item .xp-nav-flag { padding: 1px; border-radius: 6px; }
body.nav-collapsed .nav-item .xp-nav-flag .xp-flag-txt { display: none; }

/* ── 消耗回执 ─────────────────────────────────────────────────────────────── */
.xp-spend {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  margin: 10px 0 0; padding: 9px 12px;
  border: 1px solid var(--xp-line-2); border-radius: var(--xp-radius);
  background: var(--xp-surface-wash);
  font-size: 12.5px; color: var(--xp-fg-2);
  font-variant-numeric: tabular-nums;
}
.xp-spend b { color: var(--xp-accent-strong); font-weight: 700; }
/* 待结算：中性描边 + 一个 tag，**不用红**（红＝出错，这里没出错） */
.xp-spend.is-pending { border-color: var(--xp-line-2); }
.xp-spend-pend { color: var(--xp-meta); }
.xp-spend-go { margin-left: auto; }

/* ── 受限动作按钮：保留 .btn 尺寸与位置，只加"这是要开通的"信号 ────────────── */
.btn.xp-lockbtn {
  color: var(--xp-lock-plan);                        /* 默认 = 套餐锁（蓝） */
  border-color: var(--xp-open-partial-line);
  background: transparent;
}
.btn.xp-lockbtn:hover {
  background: var(--xp-open-partial-bg);
  border-color: var(--xp-open-partial);
}
.btn.xp-lockbtn svg { width: 16px; height: 16px; flex: none; opacity: .9; }
/* 四把锁的配色分流：颜色表达"下一步去哪"，不表达"有多严重"（§C.4）。
   注意 标着 plan_required / expired / bind 的三条**必须显式写**，不能靠默认值 fall-through：
   CSS 没有"未知枚举值"选择器，`data-kind="points "`（多一个空格）或拼错都会静默落进
   默认蓝锁，页面完全正常却把"去充值"指成"买套餐"。JS 侧另有 resolveKind() 白名单兜第一道。 */
.btn.xp-lockbtn[data-kind="plan_required"],
.btn.xp-lockbtn[data-kind="expired"],
.btn.xp-lockbtn[data-kind="bind"] {
  color: var(--xp-lock-plan);
  border-color: var(--xp-lock-plan-line);
}
.btn.xp-lockbtn[data-kind="plan_required"]:hover,
.btn.xp-lockbtn[data-kind="expired"]:hover,
.btn.xp-lockbtn[data-kind="bind"]:hover {
  background: var(--xp-lock-plan-soft); border-color: var(--xp-lock-plan);
}
.btn.xp-lockbtn[data-kind="quota"] {
  color: var(--xp-lock-quota);
  border-color: var(--xp-lock-quota-line);
}
.btn.xp-lockbtn[data-kind="quota"]:hover {
  background: var(--xp-lock-quota-soft); border-color: var(--xp-lock-quota);
}
.btn.xp-lockbtn[data-kind="points"] {
  color: var(--xp-lock-points);
  border-color: var(--xp-lock-points-line);
}
.btn.xp-lockbtn[data-kind="points"]:hover {
  background: var(--xp-lock-points-soft); border-color: var(--xp-lock-points);
}
/* 授权锁：与套餐锁同色（都是"去套餐页"），靠图标与文案区分，不新增色相 */
.btn.xp-lockbtn[data-kind="settle"] {
  color: var(--xp-lock-settle);
  border-color: var(--xp-lock-settle-line);
  cursor: default;
}

/* ── 按钮上的报价：三态（§4.10.1）。三种态只用分隔线与字号区分，不改按钮骨架 ── */
.btn.xp-lockbtn .xp-quote {
  margin-left: 2px; padding-left: 8px;
  border-left: 1px solid var(--xp-line-2);
  font-size: 11.5px; line-height: 1.2;
  color: var(--xp-meta);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* 三态共用一个骨架；min-width 按最长态（19 仙贝起 / 次）兜住，避免勾选切换时抖动 */
.btn.xp-lockbtn[data-quote] { min-width: 9ch; }
.btn.xp-lockbtn[data-quote="unknown"] .xp-quote { color: var(--xp-meta); }
/* 只知单价态：单价是"起价"，视觉上比"上界"弱半档，不能让人当成精确值 */
.btn.xp-lockbtn[data-quote="per-unit"] .xp-quote { color: var(--xp-meta); }
/* 有上界态：唯一可以稍微显眼的一档 */
.btn.xp-lockbtn[data-quote="upper"] .xp-quote { color: var(--xp-fg-2); font-weight: 600; }

/* ── 演示样例徽标：与样例同框，不 sticky、不加左边框 ─────────────────────── */
.xp-demo {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 0 10px;
  font-size: var(--fs-xs); color: var(--xp-meta);
}
.xp-demo-hint { line-height: 1.55; }
/* 样例数据整体降饱和；**不低于 .8**，否则会被读成"加载失败" */
[data-demo="1"] { opacity: var(--xp-demo-dim); }

/* ── 解锁说明浮层正文 ─────────────────────────────────────────────────────── */
.xp-unlock { display: grid; gap: 10px; }
.xp-unlock-q { margin: 0; font-size: var(--fs-md); font-weight: 600; line-height: 1.5; color: var(--xp-fg); }
.xp-unlock-list { margin: 0; padding-left: 18px; font-size: 12.5px; line-height: 1.7; color: var(--xp-fg-2); }
.xp-unlock-list li { margin: 3px 0; }
.xp-unlock-list b { color: var(--xp-fg); font-weight: 600; }
.xp-unlock-foot { display: flex; gap: 8px; flex-wrap: wrap; }

/* ── 仙贝 vs 套餐 对照（复用 .grid.two + .card） ───────────────────────────── */
.xp-goods { margin-top: 4px; }
.xp-goods .card { padding: 14px 16px; }
.xp-goods h4 { font-size: var(--fs-md); color: var(--xp-fg); margin: 0 0 6px; }
.xp-goods .xp-goods-d { font-size: 12.5px; line-height: 1.7; color: var(--xp-fg-2); }

/* ── 顶栏徽标：不回档位染色（v1.1），只保留"归零"这一个语义色 ─────────────── */
.points-chip[data-state="spent"] b { color: var(--xp-meta); }
.points-chip[data-state="zero-new"] b { color: var(--xp-meta); }
/* 余额下降的一次呼吸：只改描边，不做位移 */
@keyframes xpFlash {
  from { box-shadow: 0 0 0 0 var(--xp-est-over-line); }
  to   { box-shadow: 0 0 0 7px transparent; }
}
.points-chip.xp-flash, .xp-quota.xp-flash {
  animation: xpFlash var(--xp-flash-ms) var(--xp-ease) 1;
}
@media (prefers-reduced-motion: reduce) {
  .points-chip.xp-flash, .xp-quota.xp-flash { animation: none; }
  .xp-est .budget-bar > i { transition: none; }
}

/* ============================================================================
   402 单一出口（前端补充 · 设计 §A.6.3 三层职责里的"全局层"）
   ----------------------------------------------------------------------------
   与 `.toast` 同骨架、同容器（`#toastHost`）、同层级 300，只把"3 秒消失的一句话"
   换成"常驻卡 + 一个下一步按钮"。三个 code 三种描边色，对应"下一步去哪"。
   ========================================================================== */
.xp-gate { display: grid; gap: 8px; align-items: start; max-width: 400px; }
.xp-gate-msg { line-height: 1.6; color: var(--xp-fg-2); }
/* 五个 kind 全部显式声明（不许靠默认 fall-through，理由同上面的锁按钮）：
   前四种是"这道门现在没钥匙"，`settle` 是"门已经过了、账还没算完"，
   会自己消失、用户不需要做任何事 ⇒ `settle` 下 `.xp-gate-acts` 不渲染按钮（由 JS 保证）。 */
.xp-gate[data-kind="plan_required"] { border-color: var(--xp-lock-plan-line); }
.xp-gate[data-kind="quota"]         { border-color: var(--xp-lock-quota-line); }
.xp-gate[data-kind="points"]        { border-color: var(--xp-lock-points-line); }
.xp-gate[data-kind="expired"]       { border-color: var(--xp-lock-plan-line); }
.xp-gate[data-kind="settle"]        { border-color: var(--xp-lock-settle-line); }
.xp-gate-acts { display: flex; gap: 8px; flex-wrap: wrap; }
/* settle 是唯一一个「等一等」而非「去开通」的态：没有可点的出路。
   用 CSS 兜住，而不是靠调用方记得 —— 契约要自洽，不依赖谁在用它。
   （JS 侧 `openUnlock` 的 "go 为空则不渲染按钮" 保留，双保险，成本为零。） */
.xp-gate[data-kind="settle"] .xp-gate-acts { display: none; }

/* 窄屏：回执条/预估条/出口卡的按钮不挤成一条缝；预估条**必须保留 sticky**
   （它的全部价值就是"和主按钮永远同屏"，丢了就白做），只压 padding。 */
@media (max-width: 640px) {
  .xp-spend-go { margin-left: 0; }
  .xp-est-acts .btn, .xp-gate-acts .btn { flex: 1 1 auto; min-height: 44px; }
  .xp-est, .xp-gate { max-width: none; }
  .xp-est { padding: 9px 11px; }
}


/* ============================================================================
   开屏预告片 · 沐雪《零点名单》（引擎：web/js/core/trailer.js）
   ----------------------------------------------------------------------------
   改之前先读这五条，每条都是"踩过才知道"的：

   ① 状态只有一个真源：`#trSplash[data-state]`
        booting | blocked | playing | end | minimizing
      不要新增并列的 is-xxx 类。两个状态类同时成立时（is-playing + is-end）
      样式会自相矛盾，而这种矛盾在浏览器里**不报错**、只是"看起来有点不对"。

   ② `[hidden]` 必须逐条显式声明：本区块用了 display:grid / flex，作者样式
      优先级高于 UA 的 `[hidden]{display:none}`，漏写就出现"标了隐藏却仍然可见"
      （项目已踩过，见 .nav-quick .nav-act[hidden] 那段注释）。

   ③ 进度线**只指示、不可拖**：后端静态资源没有 Range 支持，做一个拖不动的
      进度条等于假功能。宁可只做指示器。

   ④ 颜色全部取既有 token（--brand / --warn / --text / --line），不新增色板；
      需要透明度时沿用本文件既有 rgba(34, 224, 255, …) 写法。

   ⑤ 动效统一在文件末尾的 prefers-reduced-motion 里关掉。
   ============================================================================ */

.tr-splash {
  position: fixed; inset: 0; z-index: 340;
  display: grid; place-items: center;
  padding: 22px;
  background: radial-gradient(120% 88% at 50% 42%, #070c18 0%, #03060e 58%, #010206 100%);
  /* 入场动画**不能**用 fill-mode:both —— 它会把 opacity 永久钉在 1，
     之后 data-state=minimizing 想让整层淡出时 transition 完全不生效。
     所以基础值是 1，动画只负责 0→1，播完即释放。 */
  opacity: 1;
  animation: trSplashIn .34s ease;
}
.tr-splash[hidden] { display: none !important; }
/* 缩到左下角时：整层淡出，别盖住侧栏落点的小卡片 */
.tr-splash[data-state="minimizing"] { opacity: 0; transition: opacity .3s ease; }
@keyframes trSplashIn { from { opacity: 0; } to { opacity: 1; } }

/* 16:9 银幕。宽度同时受"视口宽"与"视口高"约束 —— 只用 vw 会在 1366×768 的
   笔记本上把画面顶出屏幕，底部结束卡的按钮全部点不到。 */
.tr-stage {
  position: relative;
  width: min(1280px, 94vw, calc((100vh - 120px) * 16 / 9));
  aspect-ratio: 16 / 9;
  border-radius: 4px; overflow: hidden;
  background: #01030a;
  box-shadow: 0 30px 90px rgba(0, 0, 0, .7), 0 0 0 1px rgba(120, 165, 255, .13);
}
.tr-video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain; background: #01030a;
  transition: filter .5s ease, transform .5s ease;
}
/* 竖版素材（手机）：银幕改成 9:16。
   ★这是"换容器"而不是"把横版塞进竖屏"—— 用错方向的表现是两侧巨大黑边
     （或人物被裁），看起来像素材传错了，而实际上只是容器没换。 */
.tr-splash[data-orient="p"] .tr-stage {
  width: min(460px, 92vw, calc((100vh - 108px) * 9 / 16));
  aspect-ratio: 9 / 16;
}
/* 竖版里可用宽度只有 ~320px，标题按 4.4vw 会缩到 17px（手机上像小字报），
   按钮并排也会挤成一条缝 —— 所以这一支单独放大标题、按钮改竖排。 */
.tr-splash[data-orient="p"] .tr-title {
  font-size: clamp(24px, 7.2vw, 38px); letter-spacing: .1em;
}
.tr-splash[data-orient="p"] .tr-soon-rule { width: 34px; }
.tr-splash[data-orient="p"] .tr-acts { flex-direction: column-reverse; gap: 8px; }
.tr-splash[data-orient="p"] .tr-btn { width: 100%; }
.tr-splash[data-orient="p"] .tr-check { margin-top: 18px; }
.tr-splash[data-orient="p"] .tr-kicker { letter-spacing: .2em; font-size: 9.5px; }
/* 结束态：画面压暗 + 极缓推近，让标题浮出来。
   **不切静态图**——直接压住视频最后一帧，视觉上与片尾连续，没有跳帧。 */
.tr-splash[data-state="end"] .tr-video,
.tr-splash[data-state="minimizing"] .tr-video {
  filter: saturate(.86) brightness(.6) contrast(1.04);
  transform: scale(1.035);
}
.tr-vignette {
  position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(180deg, rgba(2, 5, 12, .72) 0%, transparent 16%,
                    transparent 74%, rgba(2, 5, 12, .84) 100%),
    radial-gradient(120% 100% at 50% 50%, transparent 52%, rgba(1, 3, 9, .55) 100%);
}

/* ---------- 播放态 HUD ---------- */
.tr-hud {
  position: absolute; left: 0; right: 0; top: 0;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 13px 16px;
  font-size: 11.5px; color: var(--text-mid);
  opacity: .8; transition: opacity .2s;
}
.tr-hud[hidden] { display: none !important; }
.tr-hud:hover { opacity: 1; }
.tr-mark { display: inline-flex; align-items: center; gap: 7px; letter-spacing: .06em; }
.tr-mark svg { color: var(--brand); opacity: .85; }
.tr-hud-right { display: inline-flex; align-items: center; gap: 8px; }
.tr-icon-btn {
  display: grid; place-items: center; width: 30px; height: 30px;
  border-radius: 50%; border: 1px solid var(--line-2);
  background: rgba(6, 10, 20, .55); color: var(--text-mid);
  transition: .16s;
}
.tr-icon-btn:hover {
  border-color: rgba(34, 224, 255, .5); color: var(--brand);
  background: rgba(34, 224, 255, .1);
}
.tr-ico { display: none; }
.tr-ico-off { display: block; }                 /* 默认态：喇叭 + 斜线（静音） */
.tr-icon-btn[data-sound="1"] .tr-ico-off { display: none; }
.tr-icon-btn[data-sound="1"] .tr-ico-on { display: block; }
.tr-skip {
  padding: 7px 14px; border-radius: 999px;
  border: 1px solid var(--line-2);
  background: rgba(6, 10, 20, .55); color: var(--text-mid);
  font-size: 12px; letter-spacing: .04em; transition: .16s;
}
.tr-skip:hover {
  border-color: rgba(34, 224, 255, .55); color: var(--brand);
  background: rgba(34, 224, 255, .1);
}

/* ---------- 载入态（真实进度，不是无限转圈） ---------- */
.tr-boot {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px;
  background: rgba(2, 5, 12, .55);
  color: var(--muted); font-size: 12.5px; letter-spacing: .08em;
}
.tr-boot[hidden] { display: none !important; }
.tr-spin {
  width: 26px; height: 26px; border-radius: 50%;
  border: 2px solid rgba(34, 224, 255, .18); border-top-color: var(--brand);
  animation: trSpin .9s linear infinite;
}
@keyframes trSpin { to { transform: rotate(360deg); } }
.tr-boot-txt { font-variant-numeric: tabular-nums; }

/* ---------- 自动播放被拦 / 解码失败的唯一退路 ---------- */
.tr-tap {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px;
  background: rgba(2, 5, 12, .62); color: var(--text-mid);
}
.tr-tap[hidden] { display: none !important; }
.tr-tap-btn {
  display: grid; place-items: center;
  width: 76px; height: 76px; border-radius: 50%;
  border: 1px solid rgba(34, 224, 255, .5);
  background: rgba(34, 224, 255, .12); color: var(--brand);
  box-shadow: 0 0 0 10px rgba(34, 224, 255, .05), 0 14px 40px rgba(0, 0, 0, .55);
  transition: .2s;
}
.tr-tap-btn:hover { transform: scale(1.05); background: rgba(34, 224, 255, .2); }
.tr-tap-txt { font-size: 12.5px; letter-spacing: .1em; }

/* ---------- 进度指示线 ---------- */
.tr-line {
  position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: rgba(255, 255, 255, .1); pointer-events: none;
}
.tr-line i {
  display: block; height: 100%; width: 0;
  background: linear-gradient(90deg, var(--brand-deep), var(--brand));
  box-shadow: 0 0 12px rgba(34, 224, 255, .6);
  transition: width .2s linear;
}

/* ---------- 版权角标 ---------- */
.tr-copy {
  position: absolute; right: 12px; bottom: 13px; margin: 0;
  font-size: 10px; letter-spacing: .04em;
  color: rgba(233, 238, 251, .38);
  text-shadow: 0 1px 6px rgba(0, 0, 0, .85);
  pointer-events: none; user-select: none;
}
/* 结束态由结束卡自己的文案接管，角标让位（两块字叠在一起最难看） */
.tr-splash[data-state="end"] .tr-copy,
.tr-splash[data-state="minimizing"] .tr-copy { display: none; }

/* ---------- 结束卡 ---------- */
.tr-end {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  padding: 26px;
  background: radial-gradient(86% 76% at 50% 46%,
              rgba(3, 7, 15, .34) 0%, rgba(2, 4, 10, .88) 76%);
  animation: trEndIn .46s ease;
}
.tr-end[hidden] { display: none !important; }
@keyframes trEndIn { from { opacity: 0; } to { opacity: 1; } }
.tr-end-box { max-width: 620px; width: 100%; text-align: center; }
.tr-kicker {
  margin: 0 0 14px;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .34em;
  text-transform: uppercase; color: rgba(233, 238, 251, .42);
  animation: trRise .6s cubic-bezier(.22, 1, .36, 1) both;
}
.tr-title {
  margin: 0; color: #fff; font-weight: 800;
  font-size: clamp(26px, 4.4vw, 58px);
  letter-spacing: .14em; line-height: 1.16;
  text-shadow: 0 6px 40px rgba(34, 224, 255, .28), 0 2px 18px rgba(0, 0, 0, .8);
}
/* 两块标题分别入场（不是整行一起淡入 —— 那样像 PPT 切换，不像片尾） */
.tr-title-w { display: inline-block; animation: trRise .78s cubic-bezier(.22, 1, .36, 1) both; }
.tr-title-w:first-of-type { animation-delay: .1s; }
.tr-title-w:last-of-type { animation-delay: .38s; }
.tr-title-sep {
  display: inline-block; margin: 0 .34em; color: var(--brand); font-weight: 400;
  animation: trDot .5s cubic-bezier(.22, 1, .36, 1) .3s both;
}
@keyframes trDot {
  from { opacity: 0; transform: scale(.4); }
  to   { opacity: 1; transform: scale(1); }
}
@keyframes trRise {
  from { opacity: 0; transform: translateY(16px); filter: blur(6px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}
.tr-soon { display: flex; align-items: center; justify-content: center; gap: 14px; margin: 20px 0 0; }
.tr-soon-rule {
  display: block; height: 1px; width: 52px;
  transform-origin: right center;
  background: linear-gradient(90deg, transparent, rgba(34, 224, 255, .65));
  animation: trRule .7s cubic-bezier(.22, 1, .36, 1) .5s both;
}
.tr-soon-rule:last-of-type {
  transform-origin: left center;
  background: linear-gradient(90deg, rgba(34, 224, 255, .65), transparent);
}
@keyframes trRule { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.tr-soon-txt {
  font-size: 13px; letter-spacing: .3em; color: var(--warn);
  animation: trRise .6s cubic-bezier(.22, 1, .36, 1) .56s both;
}

/* ---------- 「下次不再播放」勾选框（语义是布尔，所以用 checkbox 而不是 radio） ---------- */
.tr-check {
  display: inline-flex; align-items: center; gap: 9px;
  margin: 26px 0 0; padding: 7px 14px 7px 11px;
  border-radius: 999px; border: 1px solid transparent;
  font-size: 12.5px; color: var(--muted); cursor: pointer;
  transition: .18s;
  animation: trRise .6s cubic-bezier(.22, 1, .36, 1) .64s both;
}
.tr-check:hover {
  color: var(--text-mid); border-color: var(--line-2);
  background: rgba(255, 255, 255, .03);
}
/* 原生 input 视觉隐藏但**保留可聚焦**：display:none 会让键盘用户无法勾选 */
.tr-check-input {
  position: absolute; width: 1px; height: 1px;
  margin: 0; opacity: 0; pointer-events: none;
}
.tr-check-box {
  width: 15px; height: 15px; flex: none; border-radius: 4px;
  border: 1.5px solid rgba(140, 165, 215, .55);
  display: grid; place-items: center; color: #04060e;
  transition: .16s;
}
.tr-check-box svg { opacity: 0; transition: opacity .14s; }
.tr-check-input:checked ~ .tr-check-box {
  background: var(--brand); border-color: var(--brand);
  box-shadow: 0 0 12px rgba(34, 224, 255, .45);
}
.tr-check-input:checked ~ .tr-check-box svg { opacity: 1; }
.tr-check-input:checked ~ .tr-check-txt { color: var(--text-mid); }
.tr-check-input:focus-visible ~ .tr-check-box {
  outline: 2px solid var(--brand-strong); outline-offset: 2px;
}

/* ---------- 结束卡按钮 ---------- */
.tr-acts {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  margin: 18px 0 0; flex-wrap: wrap;
  animation: trRise .6s cubic-bezier(.22, 1, .36, 1) .72s both;
}
.tr-btn {
  padding: 10px 22px; border-radius: 10px;
  font-size: 13px; font-weight: 600; letter-spacing: .04em;
  border: 1px solid transparent; cursor: pointer; transition: .18s;
}
.tr-btn-ghost {
  border-color: var(--line-2); background: rgba(255, 255, 255, .03);
  color: var(--text-mid);
}
.tr-btn-ghost:hover {
  border-color: var(--line-3); color: var(--text);
  background: rgba(255, 255, 255, .06);
}
.tr-btn-main { background: var(--brand); color: #04060e; box-shadow: 0 8px 26px rgba(34, 224, 255, .28); }
.tr-btn-main:hover { background: var(--brand-strong); box-shadow: 0 10px 32px rgba(34, 224, 255, .42); }
.tr-hint {
  margin: 14px 0 0; font-size: 11px; letter-spacing: .06em;
  color: rgba(131, 145, 180, .72);
  animation: trRise .6s cubic-bezier(.22, 1, .36, 1) .8s both;
}

/* ---------- 缩短动画的飞行层（与 #trSplash 平级，不继承它的淡出） ---------- */
.tr-flyer {
  position: fixed; z-index: 345; overflow: hidden;
  border-radius: 10px; pointer-events: none;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .6), 0 0 0 1px rgba(34, 224, 255, .35);
  transition: transform .54s cubic-bezier(.22, 1, .36, 1),
              opacity .54s ease, border-radius .54s ease;
}
.tr-flyer[hidden] { display: none !important; }
.tr-flyer img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---------- 侧栏「预告片」小卡片（开屏的落点 / 回放入口） ----------
   注意：这是**唯一**的回放入口，不要再往顶栏/菜单里加第二个 ——
   本项目已因"同一件事两个入口"返工过一次（见 index.html 里 #chipCode 的注释）。 */
.tr-card {
  position: relative; overflow: hidden;
  display: flex; align-items: center; gap: 9px;
  width: 100%; margin-top: 10px; padding: 7px;
  text-align: left; color: var(--text-mid); cursor: pointer;
  background: var(--side-card-bg);
  border: 1px solid var(--line-2); border-radius: 12px;
  transition: border-color .18s, box-shadow .18s, color .18s, background .18s;
}
.tr-card[hidden] { display: none !important; }
.tr-card:hover {
  color: var(--text);
  border-color: var(--side-card-line);
  box-shadow: var(--side-card-glow);
}
/* 入场：只在"从开屏收回来"那一刻播一次。类名由 trailer.js 加，播完保留无害。 */
.tr-card.is-on { animation: trCardIn .42s cubic-bezier(.22, 1, .36, 1) both; }
@keyframes trCardIn {
  from { opacity: 0; transform: translateY(10px) scale(.94); }
  to   { opacity: 1; transform: none; }
}
.tr-card-thumb {
  position: relative; width: 54px; flex: none;
  aspect-ratio: 16 / 9; border-radius: 7px; overflow: hidden;
  background: #05080f;
}
.tr-card-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.tr-card-play {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  color: #fff; background: rgba(2, 5, 12, .32);
  transition: .18s;
}
.tr-card:hover .tr-card-play { background: rgba(2, 5, 12, .12); color: var(--brand); }
.tr-card-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tr-card-title {
  font-size: 12.5px; font-weight: 600; letter-spacing: .02em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tr-card-sub { font-size: 10.5px; color: var(--muted-soft); }
/* 收起态（74px 侧栏）：只留缩略图，与 .xp-quota / .nav-item 同一处理口径 */
body.nav-collapsed .tr-card { justify-content: center; padding: 6px 0; }
body.nav-collapsed .tr-card-thumb { width: 46px; }
body.nav-collapsed .tr-card-meta { display: none; }

/* ---------- 窄屏 ----------
   ≤900px 时侧栏是抽屉：**不做飞行归位**（目标 rect 在屏幕外，会飞出视口），
   由 trailer.js 退化为"淡出 + 卡片直接出现在抽屉里"。 */
@media (max-width: 900px) {
  .tr-splash { padding: 0; }
  .tr-stage { width: 100%; border-radius: 0; box-shadow: none; }
  .tr-end { padding: 18px; }
  .tr-hud { padding: 10px 12px; font-size: 10.5px; }
  .tr-mark span { display: none; }
  .tr-btn { flex: 1 1 auto; min-width: 120px; }
}
@media (max-height: 620px) {
  /* 矮屏（如 1366×600 或浏览器缩放 125%）：把结束卡的纵向留白压掉，
     否则「进入工作台」按钮会掉到银幕外，点不到。 */
  .tr-kicker { margin-bottom: 8px; }
  .tr-soon { margin-top: 12px; }
  .tr-check { margin-top: 14px; }
  .tr-acts { margin-top: 12px; }
  .tr-hint { margin-top: 8px; }
}

/* ---------- 降低动态效果偏好 ---------- */
@media (prefers-reduced-motion: reduce) {
  .tr-splash, .tr-end, .tr-kicker, .tr-title-w, .tr-title-sep,
  .tr-soon-rule, .tr-soon-txt, .tr-check, .tr-acts, .tr-hint,
  .tr-card.is-on { animation: none !important; }
  .tr-video { transition: none; }
  .tr-flyer { transition: opacity .18s ease !important; }
}


/* ══════════════════════════════════════════════════════════════
   绑定小程序余额弹层（2026-10-24）
   只补 .bm-* 这层的排版；遮罩与卡片沿用 .gate / .gate-card，
   避免再长出一套新的弹层语言。
   ══════════════════════════════════════════════════════════════ */
.bm-code {
  font-family: ui-monospace, "SF Mono", SFMono-Regular, Consolas, monospace;
  font-size: 44px;
  font-weight: 500;
  letter-spacing: 8px;
  line-height: 1.2;
  padding: 18px 0 10px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  /* 用户要肉眼抄这 6 位数字到手机上，选中即复制省一步 */
  user-select: all;
  cursor: pointer;
}

.bm-actions {
  display: flex;
  gap: 10px;
  margin: 18px 0 10px;
}

.bm-actions .btn {
  flex: 1;
  text-align: center;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}


/* 绑定小程序余额：二维码容器
   白底是硬要求 —— 深色底上扫码识别率会明显下降，这个二维码是给手机摄像头看的，
   不是给人看的装饰。 */
.bm-qr {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 18px 0 10px;
}

.bm-qr[hidden] {
  display: none;
}

.bm-qr svg {
  width: 200px;
  height: 200px;
  background: #fff;
  border-radius: 10px;
  padding: 10px;
  box-sizing: content-box;
}

/* 过期后压暗，避免用户对着一个失效的码反复扫 */
.bm-qr.expired svg {
  opacity: 0.3;
}


/* 绑定成功态 */
.bm-done {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 26px 0 18px;
}

/* 成功图标：纯 CSS 圆 + 对勾，不用 emoji 也不用图片 */
.bm-done-icon {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: rgba(93, 202, 165, 0.14);
  border: 1px solid rgba(93, 202, 165, 0.4);
  position: relative;
  margin-bottom: 16px;
}

.bm-done-icon::after {
  content: "";
  position: absolute;
  left: 21px;
  top: 13px;
  width: 14px;
  height: 26px;
  border-right: 3px solid #5dcaa5;
  border-bottom: 3px solid #5dcaa5;
  transform: rotate(45deg);
}

.bm-done-title {
  font-size: 18px;
  font-weight: 600;
  color: #e9edf4;
  margin-bottom: 6px;
}

.bm-done-sub {
  font-size: 13px;
  color: #8b95a7;
}


/* 绑定弹层的右上角关闭（替代原来底部那颗通栏「关闭」按钮：
   底部整行按钮会和主操作抢视觉权重，而关闭属于次要操作，
   放在卡片右上角是模态框的通行位置，也不占正文空间）。 */
.bm-close-x {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  margin: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: #7a8699;
  cursor: pointer;
  transition: background .15s ease, color .15s ease;
  -webkit-tap-highlight-color: transparent;
}

.bm-close-x:hover {
  background: rgba(255, 255, 255, 0.07);
  color: #e9edf4;
}

.bm-close-x:active {
  background: rgba(255, 255, 255, 0.11);
}

.bm-close-x svg {
  display: block;
}


/* 绑定按钮的「已绑定」态 + 关联微信提示 */
.btn[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

.bm-bound-hint {
  font-size: 12px;
  color: #5dcaa5;
  margin-left: 8px;
  white-space: nowrap;
}

.bm-bound-hint[hidden] {
  display: none;
}

/* ══════════════════════════════════════════════════════════════
   机型档位降载（core/perf.js 设 <html data-perf="low|mid|high">）
   只关「氛围/装饰」类重动画，不碰任何交互与内容 —— 低端机把这些
   常驻 GPU 合成/重绘的开销卸掉，滚动与首屏立刻轻下来。
   ══════════════════════════════════════════════════════════════ */
[data-perf="low"] .fx-grid,
[data-perf="low"] .fx-glow,
[data-perf="low"] .fx-scan,
[data-perf="low"] .muxue-panel::after,
[data-perf="low"] .topbar::after {
  animation: none;
}
/* 全屏扫描线 / 径向光晕：低端直接不渲染（省填充率） */
[data-perf="low"] .fx-scan { display: none; }
[data-perf="low"] .fx-glow { background: none; }
/* 顶栏毛玻璃：blur 是低端机最贵的常驻滤镜，降为纯渐变底（底本身已 ~72-92% 不透明） */
[data-perf="low"] .topbar {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
/* 中端机：关掉开销最大的两个全屏动画（扫描线位移 / 网格漂移），光晕与呼吸保留 */
[data-perf="mid"] .fx-scan { display: none; }
[data-perf="mid"] .fx-grid { animation: none; }


/* ============================================================================
   首页（沐雪对话）· 手机端「对话优先」重构  —— 2026-10-30
   ----------------------------------------------------------------------------
   需求三条：
     ① 沐雪对话是**默认形态**，并且吃满一屏（手机端打开就该能直接说话）；
     ② 3D 形象缩成**右下角小相框**，动态展示；
     ③ 其余内容收进**分段选项卡**：8 个智能体 / 全系思维链 / 任务情况。

   ## 实现原则（对齐 AGENTS.md「视觉零变化」基准纪律）
   · **PC 一行不生效**：本文件里新增的类在 >900px 视口下只有三处有定义 ——
     .mx-tabs / #teamSlot / .muxue3d-tag 的 display:none，以及 .mx-gap 的 16px 间距
     （它顶替原来的内联 margin-top，因为内联样式盖不过媒体查询）。
     其余全部包在 @media (max-width:900px) 内。
   · 手机端的显隐**只由 #mxHome[data-mtab] 驱动**，不额外维护"哪个块被藏了"的 JS 状态：
     两份状态迟早对不上，而对不上的表现是空白，不是报错。

   ## 两个必须知道的坑（都已在下面就地处置）
   · `.card` 带 `backdrop-filter` ⇒ 它会成为**定位于其内部的 fixed 子元素的包含块**。
     相框（#muxue3d）是整个舱体摊平前 `.muxue-panel`（也是一个 .card）的子节点，
     不把那张卡的 backdrop-filter 关掉，相框会"固定"在卡片里而不是屏幕上。
   · `.main` 是全站 14 个页面共用的滚动容器，只有"里面装着首页外壳"时才该变成
     纵向 flex 列，所以用 `:has(> .mx-home)` 收窄作用域，不给 .main 加类
     （加类意味着每次切页都要记得摘掉，那是第二种状态来源）。
   ========================================================================== */

/* ---------- 宽屏兜底：新增元素一律不参与任何布局 ---------- */
.mx-tabs { display: none; }
#teamSlot { display: none; }
.muxue3d-tag { display: none; }
/* 顶替重构前写在模板里的 style="margin-top:16px"：手机端要归零，
   而内联样式优先级高于媒体查询里的规则 ⇒ 只能改成类。 */
.mx-gap { margin-top: 16px; }

/* ---------- 动效（只在手机相框里用） ---------- */
/* 呼吸：底图缓慢起伏，让"静态半身像"看起来是活的 */
@keyframes mxBreathe {
  0%, 100% { transform: scale(1.02) translate3d(0, 0, 0); }
  50%      { transform: scale(1.07) translate3d(0, -2px, 0); }
}
/* 全息扫描线：自上而下掠过（相框的"动态展示"主体） */
@keyframes mxScanSweep {
  0%   { transform: translate3d(0, -130%, 0); opacity: 0; }
  12%  { opacity: .9; }
  88%  { opacity: .9; }
  100% { transform: translate3d(0, 330%, 0); opacity: 0; }
}
/* 相框出现：从下往上轻轻浮起 */
@keyframes mxFrameIn {
  from { opacity: 0; transform: translate3d(0, 14px, 0) scale(.94); }
  to   { opacity: 1; transform: none; }
}

@media (max-width: 900px) {
  /* ===== 1. 应用式纵向容器：页头 + 选项卡 + 内容，内容吃满剩余高度 ===== */
  /* dvh 会在安卓软键盘弹出时收缩，输入框不会被键盘顶出屏幕（iOS 不缩，靠系统自动平移）。 */
  .app:has(.mx-home) { height: 100dvh; }
  .main:has(> .mx-home) {
    display: flex; flex-direction: column;
    padding: 8px 10px 0;
  }
  .mx-home {
    display: flex; flex-direction: column;
    flex: 1 1 auto; min-height: 0;
  }

  /* ===== 2. 页头压成一行操作条 ===== */
  .mx-page-head { flex: none; margin-bottom: 8px; display: block; }
  /* 标题**视觉隐藏**（不是 display:none）：h2 是页面标题，读屏仍要读到。
     只有本页给 .page-head 加了 .mx-page-head，作用域安全。 */
  .mx-page-head > div:first-child {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
  }
  .mx-page-head .page-actions {
    display: flex; flex-wrap: nowrap; align-items: center; gap: 7px;
    width: 100%; overflow-x: auto; overscroll-behavior-x: contain;
    padding-bottom: 2px; scrollbar-width: none;
  }
  .mx-page-head .page-actions::-webkit-scrollbar { display: none; }
  /* 单行横向滚动：语音播报开关 / 音色 / 引擎徽标 / 试听都还在，只是不再各占一行 */
  .mx-page-head .page-actions > * { flex: none; }
  /* 右端渐隐：不加这条，横向滚动区的最后一项会被硬切一半，看着像渲染坏了 */
  .mx-page-head .page-actions {
    -webkit-mask-image: linear-gradient(90deg, #000 0, #000 88%, transparent 100%);
    mask-image: linear-gradient(90deg, #000 0, #000 88%, transparent 100%);
  }

  /* ===== 3. 分段选项卡 ===== */
  .mx-tabs {
    display: grid; grid-template-columns: repeat(4, 1fr); gap: 2px;
    flex: none; margin: 0 0 8px; padding: 3px;
    border-radius: 15px;
    background: rgba(255, 255, 255, .04);
    border: 1px solid var(--line);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05);
  }
  .mx-tab {
    -webkit-appearance: none; appearance: none;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 3px; min-height: 48px; padding: 4px 2px;
    border: 0; border-radius: 12px; background: transparent;
    color: var(--muted);
    font: inherit; font-size: 11px; font-weight: 650;
    letter-spacing: .02em; line-height: 1.1;
    cursor: pointer; transition: color .18s ease, background .18s ease;
    -webkit-tap-highlight-color: transparent;
  }
  .mx-tab svg { width: 17px; height: 17px; flex: none; }
  .mx-tab.on {
    color: #f2fbff;
    background: linear-gradient(180deg, rgba(34, 224, 255, .22), rgba(123, 92, 255, .14));
    box-shadow: inset 0 0 0 1px rgba(34, 224, 255, .32),
                0 8px 20px -12px rgba(34, 224, 255, .8);
  }
  .mx-tab.on svg { filter: drop-shadow(0 0 6px rgba(34, 224, 255, .55)); }

  /* ===== 4. 面板：同时只显示 data-mtab 命中的那一块 ===== */
  .mx-panes { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
  .mx-pane { display: none; }
  .mx-home[data-mtab="chat"] .mx-pane[data-pane="chat"],
  .mx-home[data-mtab="team"] .mx-pane[data-pane="team"],
  .mx-home[data-mtab="cot"]  .mx-pane[data-pane="cot"],
  .mx-home[data-mtab="task"] .mx-pane[data-pane="task"] {
    display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0;
  }
  /* 非对话面板：让面板自己滚。手机上"页面滚 + 卡片滚"两层滚动最难用，
     而且会让底部输入区跟着内容跑掉。 */
  .mx-pane[data-pane="team"],
  .mx-pane[data-pane="cot"],
  .mx-pane[data-pane="task"] {
    overflow-y: auto; -webkit-overflow-scrolling: touch;
    padding-bottom: 12px;
  }
  .mx-pane .mx-gap { margin-top: 0; }
  .mx-pane[data-pane="cot"] > .card,
  .mx-pane[data-pane="task"] > .card { flex: none; }

  /* ===== 4.1 「8 个智能体」分块：手机用 #teamSlot，#abSlot 让位 =====
     两者内容同源（同一份 /api/team + 同一个 abilitiesHtml）。
     必须二选一显示，否则同一批卡片会在 DOM 里出现两份 —— 桌面那份还会被
     addMsg()/loadHistory() 悄悄 hidden 掉，点了就是一块空白。 */
  #teamSlot { display: block; }
  .mx-team { display: block; }
  .mx-pane[data-pane="team"] #abSlot { display: none; }
  .mx-team .abilities { padding: 12px; }
  .mx-team .ab-grid.cards { gap: 8px; }

  /* ===== 5. 对话块：整列吃满，输入区贴底 ===== */
  .mx-pane[data-pane="chat"] .chat-layout {
    display: flex; flex-direction: column;
    flex: 1 1 auto; min-height: 0; gap: 0;
  }
  .mx-pane[data-pane="chat"] .chat-box {
    flex: 1 1 auto; min-height: 0; max-height: none;
    border-radius: 16px;
  }
  .mx-pane[data-pane="chat"] .messages {
    min-height: 0;
    padding: 12px 12px 8px;
    /* 给右下角相框让位：最后一条气泡不会被压在相框底下。
       数值 = 相框高 122 + 相框距底 106 - 输入区高 108 + 余量 14 ≈ 134。
       气泡本身仍是 max-width:78%，所以只有右对齐的"我"那一侧用它。 */
    padding-bottom: 134px;
  }
  /* 快捷词一行横滑：原来 flex-wrap 会在 390px 上折成 3 行、吃掉 100px 屏高 */
  .mx-pane[data-pane="chat"] .quick-row {
    flex-wrap: nowrap; overflow-x: auto; gap: 6px;
    padding: 0 10px 8px; scrollbar-width: none;
    overscroll-behavior-x: contain;
  }
  .mx-pane[data-pane="chat"] .quick-row::-webkit-scrollbar { display: none; }
  .mx-pane[data-pane="chat"] .quick-row > * { flex: none; }
  .mx-pane[data-pane="chat"] .quick-row > #clearChatBtn { margin-left: 0; }
  .mx-pane[data-pane="chat"] .composer {
    padding: 10px 10px calc(10px + env(safe-area-inset-bottom, 0px));
    background: rgba(0, 0, 0, .34);
    border-radius: 0 0 16px 16px;
  }

  /* ===== 5.2 思维链分块：手机可读性修正 =====
     协作拓扑的节点坐标是"1000×580 空间里的百分比"，卡片宽度却有 92px ⇒
     容器只有 370px 时相邻节点一定叠在一起（实测截图里"账号风控""GEO 地域"
     互相压字）。节点是绝对定位的，CSS 缩不回来，只能给拓扑一个最小宽度
     并让它横向滚动 —— 这是唯一不改绘制逻辑的解法。 */
  .mx-pane[data-pane="cot"] .cot-tabs {
    flex-wrap: nowrap; overflow-x: auto; gap: 6px; padding-bottom: 2px;
    scrollbar-width: none;
  }
  .mx-pane[data-pane="cot"] .cot-tabs::-webkit-scrollbar { display: none; }
  .mx-pane[data-pane="cot"] .cot-tab { flex: none; padding: 8px 14px; }
  /* 子选项卡右端同样渐隐（同页头那条理由：硬切一半像渲染坏了） */
  .mx-pane[data-pane="cot"] .cot-tabs {
    -webkit-mask-image: linear-gradient(90deg, #000 0, #000 88%, transparent 100%);
    mask-image: linear-gradient(90deg, #000 0, #000 88%, transparent 100%);
  }
  /* 智能体筛选条：13 个 chip 在 390px 上会折成 7 行、吃掉 250px 屏高，
     而它只是"看谁的思考"。改成一行横滑，一律等高。 */
  .mx-pane[data-pane="cot"] .cot-filters {
    display: flex; flex-wrap: nowrap; gap: 6px;
    overflow-x: auto; padding-bottom: 2px; scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .mx-pane[data-pane="cot"] .cot-filters::-webkit-scrollbar { display: none; }
  /* 筛选条右端也要渐隐：13 个 chip 横滑后，最后一个常被容器边缘**从字形中间**切断
     （截图里"…GEO 地域获客"后面那个圆点只剩半个），硬切比"可滑"更易被读成渲染坏了。 */
  .mx-pane[data-pane="cot"] .cot-filters {
    -webkit-mask-image: linear-gradient(90deg, #000 0, #000 90%, transparent 100%);
    mask-image: linear-gradient(90deg, #000 0, #000 90%, transparent 100%);
  }
  .mx-pane[data-pane="cot"] .cot-chip { flex: none; }
  .mx-pane[data-pane="cot"] .cot-view[data-cotview="topo"] {
    overflow-x: auto; overflow-y: auto; -webkit-overflow-scrolling: touch;
  }
  /* 1000 就是画这张图时用的坐标系宽度（cot.js: const W = 1000）。压到 640 时
     节点卡片仍是 92px 宽、坐标却是按比例缩的 ⇒ 又叠在一起。
     给足原坐标系宽度 + 横向滚动，是"不改绘制逻辑"前提下唯一能保证不压字的解。 */
  .mx-pane[data-pane="cot"] .cot-topo { min-width: 1000px; }

  /* ===== 5.3 非对话分块：页头只留主操作 =====
     语音播报开关 / 音色 / 引擎徽标 / 试听只对"跟沐雪说话"这件事有意义，
     切到团队、思维链、任务时留着它们既占屏高又容易误触。 */
  .main:has(.mx-home:not([data-mtab="chat"])) .mx-page-head .page-actions > :not(#headNewTask) {
    display: none;
  }

  /* ===== 6. 原左栏「全息投影舱」摊平：3D 本体已飞出成右下角相框 ===== */
  /* ⚠️ backdrop-filter 必须显式关掉：带它的元素会成为 fixed 子元素的包含块，
     不关的话相框会被"固定"成卡片内的一块，而不是屏幕右下角。 */
  .mx-pane[data-pane="chat"] .muxue-panel {
    display: block; flex: none;
    padding: 0; margin: 0;
    background: none; border: 0; box-shadow: none; border-radius: 0;
    backdrop-filter: none; -webkit-backdrop-filter: none;
    overflow: visible;
  }
  .mx-pane[data-pane="chat"] .muxue-panel::before { display: none; }
  .mx-pane[data-pane="chat"] .muxue-panel::after { display: none; }
  /* 舱体内的装饰性文案在手机上全是屏高成本：名字、状态、用法提示都收掉。
     #llmBanner 例外 —— 那是"大模型没接上"的真告警，必须留在原位可读。 */
  .mx-pane[data-pane="chat"] .muxue-panel > .muxue-id,
  .mx-pane[data-pane="chat"] .muxue-panel > #muxueState,
  .mx-pane[data-pane="chat"] .muxue-panel > .notice-flex { display: none; }
  .mx-pane[data-pane="chat"] .muxue-panel > #llmBanner {
    margin: 0 0 8px; border-radius: 14px;
  }

  /* ===== 7. 3D 小相框（右下角常驻 · 动态展示） ===== */
  .muxue3d {
    position: fixed;
    right: 12px;
    /* 106 = 输入区(≈66) + 快捷词行(≈40)，即"贴在输入区上沿之上"；
       safe-area 一并加上，刘海屏/小黑条机型不会被压住。 */
    bottom: calc(env(safe-area-inset-bottom, 0px) + 106px);
    width: 100px; height: 122px;
    z-index: 66;                      /* < 抽屉遮罩(85) / 菜单(90)，> 内容 */
    padding: 0;
    border-radius: 16px;
    overflow: hidden;
    background:
      radial-gradient(120px 90px at 50% 16%, rgba(34, 224, 255, .22), transparent 72%),
      rgba(6, 12, 24, .84);
    border: 1px solid rgba(34, 224, 255, .38);
    box-shadow:
      0 16px 34px -14px rgba(0, 0, 0, .78),
      inset 0 1px 0 rgba(255, 255, 255, .07),
      0 0 28px -10px rgba(34, 224, 255, .55);
    -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
    transform-origin: 100% 100%;
    transition: transform .26s cubic-bezier(.2, .9, .3, 1.15),
                box-shadow .26s ease;
    /* ⚠️ 这里**不能**写 fill-mode（both / forwards）：动画声明在层叠里优先于普通规则，
       播完后它会一直按住最后一帧的 transform: none ⇒ 下面 .is-big 的 scale 永远不生效
       （实测：class 加上了、getBoundingClientRect 仍是 100×122，看着像"点了没反应"）。
       没有 delay，所以不需要 backwards 兜住首帧。 */
    animation: mxFrameIn .5s cubic-bezier(.2, .9, .3, 1.1);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  /* 点一下放大（只放大视觉，不改布局：transform 不影响回流，
     也就不会把上方的气泡推走）。1.55 倍 ≈ 155×189，仍在半屏以内。 */
  .muxue3d.is-big {
    transform: scale(1.55);
    box-shadow:
      0 22px 50px -16px rgba(0, 0, 0, .82),
      inset 0 1px 0 rgba(255, 255, 255, .09),
      0 0 40px -6px rgba(34, 224, 255, .7);
  }
  /* 放大态多留一条滚动留白，让最后一条消息整体浮到相框上沿之上（留白不可见，
     只是把"可滚动内容"加高）。不做这一步，放大后会压住最后一条气泡的尾巴。 */
  .mx-pane[data-pane="chat"] .chat-layout:has(.muxue3d.is-big) .messages {
    padding-bottom: 210px;
  }
  /* 全息扫描线：相框里那道自上而下掠过的光，是"动态展示"最直观的一条 */
  .muxue3d::after {
    content: ""; position: absolute; left: 0; right: 0; top: 0; height: 38%;
    background: linear-gradient(180deg, transparent, rgba(34, 224, 255, .2), transparent);
    animation: mxScanSweep 3.8s linear infinite;
    pointer-events: none; z-index: 3;
  }
  /* 静态半身像铺满相框（桌面那套是 300px 舞台里的 160×200 立绘，尺寸不通用） */
  .muxue3d .muxue-loading { inset: 0; padding: 0; gap: 0; }
  .muxue3d .muxue-loading img,
  .muxue3d.muxue3d-fallback .muxue-loading img {
    width: 100%; height: 100%;
    border-radius: 0; border: 0; box-shadow: none;
    object-fit: cover; object-position: 50% 10%;
    /* 桌面那条"底部渐隐"在这里必须显式关掉：相框只有 100×122，渐隐会吃掉
       她半边身子并露出相框底色（基础段的规则会继承下来，不写就是静默生效）。 */
    -webkit-mask-image: none; mask-image: none;
    animation: mxBreathe 5.4s ease-in-out infinite;
  }
  /* 「沐雪正在走来…」在 100px 宽的相框里放不下，也没有信息量（相框本身就是她） */
  .muxue3d .muxue-loading > span:not(.muxue3d-tag) { display: none; }
  .muxue3d canvas { border-radius: 0; filter: none; }
  /* 相框角标：真 3D 加载成功后 .muxue-loading 会整体淡出（muxue3d.js 加的 .hide），
     角标在它内部 ⇒ 跟着一起消失，不需要第二条"记得藏角标"的同步逻辑。
     ★2026-10-30：改为**左对齐**（原来 right:0 居中），给右下角的通话按钮让位 ——
     100px 宽的相框里，居中文案与 38px 按钮必然重叠。 */
  .muxue3d-tag {
    display: block;
    position: absolute; left: 7px; right: auto; bottom: 0; z-index: 2;
    padding: 4px 0 5px; text-align: left;
    font-size: 9.5px; font-weight: 700; letter-spacing: .16em;
    color: rgba(200, 244, 255, .92);
    background: linear-gradient(0deg, rgba(4, 10, 20, .9), rgba(4, 10, 20, 0));
    pointer-events: none;
  }
  /* 通话按钮在手机相框里：**只覆盖尺寸与偏移**，视觉（颜色/边框/脉冲动画）沿用
     文件上方的基础定义 —— 两处各写一份整套样式必然漂移。
     · 38×38 是**下限**而不是审美选择：scripts/ui_mobile_check.js 的触点判据是
       `r.height < 38 || r.width < 30` ⇒ 更小的按钮会被移动端验收判红；
     · 位置在右下角、叠在角标右侧（角标已改左对齐），两者不重叠；
     · 它在 `.muxue-loading` 内部 ⇒ 3D 加载成功后随遮罩一起淡出，
       不需要"加载完了记得藏按钮"的第二条同步逻辑（本仓踩过：两处各记显隐必漂移）。 */
  .muxue3d-call {
    right: 5px; bottom: 5px;
    width: 38px; height: 38px;
  }
  .muxue3d-call svg { width: 19px; height: 19px; }
  /* 相框只属于「对话」分块：切换到别的分块时，chat 面板本身 display:none，
     相框作为它的后代一并停渲染（fixed 也救不回来 —— 祖先 display:none 就不生成盒子）。
     这是刻意的：另外三个分块要读长内容，右下角常驻一块会持续压字。
     回到「对话」时 mxFrameIn 动画会重播一次，观感上正好是"她又回来了"。 */

  /* ===== 8. 团队页脚注 ===== */
  .mx-team-foot {
    margin-top: 12px; padding: 9px 12px;
    border-radius: 11px;
    border: 1px dashed var(--line);
    color: var(--muted); font-size: 11.5px; line-height: 1.6;
  }
}

/* ===== 降低动态效果偏好：相框只保留静态外观 ===== */
@media (prefers-reduced-motion: reduce) {
  .muxue3d { animation: none; }
  .muxue3d::after { display: none; }
  /* ⚠️ 必须把 `.muxue3d-fallback` 变体一并写上，否则这条**关不掉**呼吸：
     相框一开局就是 fallback 态（`degradeMuxue3d()` 加的类），而正版规则里
     `.muxue3d.muxue3d-fallback .muxue-loading img` 权重 (0,2,2) 高于
     `.muxue3d .muxue-loading img` 的 (0,1,2) ⇒ 只写后者仍算 mxBreathe。
     实测（Playwright reducedMotion=reduce）：只写后者时 imgAnim 仍是 mxBreathe；
     补上并列选择器后变 none。别"顺手简化"成一行。 */
  .muxue3d .muxue-loading img,
  .muxue3d.muxue3d-fallback .muxue-loading img { animation: none; }
  .muxue3d.is-big { transition: none; }
  /* 通话按钮的呼吸圈也一并关掉（与上面同一条原则：降低动效偏好下不留循环动画） */
  .muxue3d-call, .muxue3d-call[data-state="loading"] { animation: none; }
}

/* ============================================================================
   手机端输入类元素：字号统一抬到 **16px**（2026-10-30）
   ============================================================================
   这是 iOS Safari 的一条硬规则：**输入框 font-size < 16px 时，一旦聚焦，
   整个页面会被自动放大**（用户要手动捏合缩回去）。项目里输入框普遍是 12.5~13px
   （`.composer textarea` 用 `--fs-sm`），在 iPhone 上点一下输入框，页面就"跳一下"，
   用户的第一反应是"页面乱了/坏了"。

   为什么只能整块抬，不能只改聊天输入框：设置中心、登录浮层、激活码、搜索框……
   任何一处 <16px 都会触发同一个缩放。这不是排版偏好问题，是平台行为。

   为什么写在这里而不是逐个改：`--fs-sm` 是全站字号 token，改 token 会影响
   所有正文（13px 正文在手机上是对的）。只抬"会被聚焦的元素"，范围最小。

   特性值上刻意用 `:not([type=…])` 把特异性抬到 (0,2,1)，才能盖住
   `.composer textarea`(0,1,1) / `.select-sm`(0,1,0) 这类既有规则 ——
   否则"写了不生效"，而表现是"某些输入框仍然会缩放"，最难查的一种。
   ========================================================================== */
@media (max-width: 900px) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
  /* ⚠️ 这里**不能**写 `textarea:not([readonly])`：游客态的 `lockChat()` 会给
     `#textInput` 设 `readOnly = true`，恰好被排除掉 —— 而游客态正是"点输入框
     触发登录"的那一刻，最需要 16px 的场景反而漏掉（2026-10-30 实测：
     加了 :not([readonly]) 后 `#textInput` 仍是 13px，登录浮层的输入框却是 16px）。
     `textarea` 天生没有 `type` 属性 ⇒ `:not([type])` 恒匹配，且能把特异性
     抬到 (0,1,1)，盖住 `.composer textarea` 的 (0,1,1)（同分靠后者获胜）。 */
  textarea:not([type]),
  select:not([type]) {
    font-size: 16px;
  }
  /* 复选框/单选框不参与缩放，尺寸保持原样；但点它们同样是"点输入框"，
     iOS 对 checkbox 不放大（无文本输入），无需处理。 */
}
/* ============================================================================
   iOS 安全区（2026-11-01 · Wave-1 · 与 index.html 的 viewport-fit=cover 配套）
   ----------------------------------------------------------------------------
   【为什么现在才需要】
   `env(safe-area-inset-*)` 只有在 `viewport-fit=cover` 时**才返回非 0 值**；
   此前 index.html 是默认的 `auto`，所以既有那几处 env() 补偿**恒为 0**
   —— 代码在、效果没有（详见 docs/audit/04-iOS与Mac兼容性.md 的 F1）。

   【写法铁律 1 · 一律 max(原值, env(..., 0px))，不写 calc(原值 + env(...))】
   max() 在 inset=0（桌面 / 安卓 / 非刘海机型 / 竖屏）时**逐像素等于原值**
   ⇒ 本段对现状是零改动，只在真有刘海或小黑条的机型上才生效。
   用 calc 相加则会把每台设备的 padding 都撑大，等于全局改版式。

   【写法铁律 2 · env() 的第二个参数不能省】
   旧 X5 / UC 不认识 env() 时，**整条声明会失效**（不是退化成 0）——
   `padding-left` 会被整条丢掉，比不加还糟。这是 F2 那一族。

   【本条**只覆盖 .topbar 的左右**，不改顶部】
   `apple-mobile-web-app-status-bar-style` 仍是 `black`（不改 black-translucent），
   standalone 下内容本就在状态栏之下 ⇒ `safe-area-inset-top` 为 0，无需补偿。
   竖屏下 env() 全部为 0 ⇒ 下面两条等于没写；横屏刘海机型才有值（约 44px）。
   ========================================================================== */

/* 顶栏基础态（原 `padding: 0 18px 0 14px` = 上右下左） */
.topbar {
  padding-left: max(14px, env(safe-area-inset-left, 0px));
  padding-right: max(18px, env(safe-area-inset-right, 0px));
}

@media (max-width: 640px) {
  /* 紧凑顶栏（原 `padding: 0 12px 0 10px`）。
     必须单独覆盖：上面那条基础态在源码里更靠后，如果不在这里再写一次，
     640px 以下会以 14/18px 盖掉紧凑态的 10/12px ⇒ 小屏顶栏被撑宽。 */
  .topbar {
    padding-left: max(10px, env(safe-area-inset-left, 0px));
    padding-right: max(12px, env(safe-area-inset-right, 0px));
  }
}
/* ============================================================================
   【救火】不支持 color-mix() 的引擎（iOS/macOS Safari ≤ 16.1）：把 10 个
   color-mix 型 token 重定义为静态 rgba —— 2026-11-01
   ----------------------------------------------------------------------------
   【为什么必须加这一块】
   这 10 个 token 在 :root 里定义为 `color-mix(...)`。旧引擎不认 color-mix ⇒
   消费处 `var(--xp-x)` 替换后**在计算值阶段非法** ⇒ 该声明作废、属性回落到
   初始值（`border-color`→`currentColor`、`background`→`transparent`）。
   ⇒ 注意：`var(--xp-x, rgba(...))` 的第二参数**救不了**这种情况 ——
     `var()` 的兜底只在 token **缺失**（guaranteed-invalid）时启用，
     而这里的 token **有定义**（自定义属性按 token 流解析，不认 color-mix 也照样接受）。
   ⇒ 唯一有效的办法是：把 **token 本身**重定义成一个合法颜色。

   【为什么放在文件末尾】
   `:root` 特异性相同 ⇒ 后出现的声明覆盖前面的。放末尾即可赢过 :root 里的 color-mix 版本。

   【为什么它对新引擎零影响】
   `@supports not (...)` 只在**不支持**时应用。支持 color-mix 的浏览器整块跳过，
   一行都不会生效 ⇒ **风险是单侧的**。

   【取值来源】= `:root` 里该 token 的基色 × 百分比，逐项换算，非随意近似：
     --text #e9eefb (233,238,251) | --info #46b8ff (70,184,255)
     --warn #ffb545 (255,181,69)  | --danger #ff4d6d (255,77,109)
   ⚠️ 块内**禁止**出现 var() / color-mix()：旧引擎里那样写仍然无效，等于没修。
   ========================================================================== */
@supports not (color: color-mix(in srgb, red, blue)) {
  :root {
    /* @cm-fallback: --xp-surface-wash */
    --xp-surface-wash:      rgba(233, 238, 251, .025);
    /* @cm-fallback: --xp-lock-plan-line */
    --xp-lock-plan-line:    rgba(70, 184, 255, .46);
    /* @cm-fallback: --xp-lock-quota-line */
    --xp-lock-quota-line:   rgba(255, 181, 69, .38);
    /* @cm-fallback: --xp-lock-points-line */
    --xp-lock-points-line:  rgba(255, 77, 109, .42);
    /* @cm-fallback: --xp-open-partial-line */
    --xp-open-partial-line: rgba(70, 184, 255, .46);
    /* @cm-fallback: --xp-open-partial-bg */
    --xp-open-partial-bg:   rgba(70, 184, 255, .10);
    /* @cm-fallback: --xp-open-none-bg */
    --xp-open-none-bg:      rgba(233, 238, 251, .035);
    /* @cm-fallback: --xp-est-near-line */
    --xp-est-near-line:     rgba(255, 181, 69, .38);
    /* @cm-fallback: --xp-est-over-line */
    --xp-est-over-line:     rgba(255, 77, 109, .42);
    /* @cm-fallback: --xp-est-over-bg */
    --xp-est-over-bg:       rgba(255, 77, 109, .08);
  }
}
