/* ---------------------------------------------------------------
   深色是刻意的：使用场景是「半夜躺床上看电脑上的 agent 干活」，
   而且内容本身就是代码和终端输出，深色底更贴近你在电脑上看到的样子。

   单位从小程序的 rpx 折半换成 px（750rpx 设计稿 ≈ 375pt 屏）。
   --------------------------------------------------------------- */

:root {
  --bg:        #0f1115;
  --bg-2:      #16181d;
  --surface:   #1b1e25;
  --surface-2: #22262f;
  --line:      #2a2f3a;
  --line-soft: #21252d;

  --text:      #e6e8ee;
  --text-2:    #a2a9b8;
  --text-3:    #6b7385;

  --accent:    #7c9cff;
  --live:      #3ddc84;
  --warn:      #ffb454;
  --danger:    #ff6b6b;
  --think:     #b48ead;

  --tone-read:   #6fb3d2;
  --tone-write:  #d2a26f;
  --tone-exec:   #93d26f;
  --tone-search: #c9a0dc;
  --tone-net:    #6fd2c4;
  --tone-agent:  #d26f9e;
  --tone-todo:   #d2c46f;
  --tone-mcp:    #8f9bd2;
  --tone-other:  #7d8698;

  /* ── 由语义色派生的淡底 / 边框 ──────────────────────────────────────────
     同一个语义色**只允许这几个强度**。改动前全站散着 25 种 rgba：
     accent 一个色就有 6 档（.08 .12 .14 .2 .22 .4），
     白色叠加有 4 档（.015 .025 .03 .035）—— 差 0.01，肉眼完全分不出来，
     但写的人要为「这里该用 .025 还是 .03」停一下。
     现在按角色归位：淡底 *-tint / 边框 *-edge / 强调边框 *-edge-hi。 */
  --accent-tint:    rgba(124, 156, 255, .12);
  --accent-edge:    rgba(124, 156, 255, .22);
  --accent-edge-hi: rgba(124, 156, 255, .40);
  --danger-tint:    rgba(255, 107, 107, .12);
  --danger-edge:    rgba(255, 107, 107, .28);
  --danger-edge-hi: rgba(255, 107, 107, .45);
  --warn-tint:      rgba(255, 180,  84, .12);
  --warn-edge:      rgba(255, 180,  84, .28);
  --live-tint:      rgba( 61, 220, 132, .12);
  --live-edge:      rgba( 61, 220, 132, .18);

  /* 中性叠加：极淡（斑马纹、轻容器）/ 浅（表头、按下反馈） */
  --wash-1: rgba(255, 255, 255, .02);
  --wash-2: rgba(255, 255, 255, .04);
  --veil:      rgba(0, 0, 0, .55);   /* 底部面板遮罩 */
  --veil-deep: rgba(0, 0, 0, .80);   /* 浮层底色（toast），要压得住背后的内容 */

  /* ── 原本写死多处的颜色 ────────────────────────────────────────────────
     这些值散在 12 个地方各写一遍，改了主题色它们不会跟着变，也没人看得出
     它们本来是同一个角色。 */
  --on-accent:   #0f1115;   /* 实色（强调/危险/浮层）上的文字 */
  --text-strong: #ffffff;   /* 比正文更亮的强调文字 */
  --danger-text: #ffb3b3;   /* 深色底上的错误文字（比 --danger 亮才读得清） */
  --danger-deep: #3a1418;   /* 严重错误面板的底 */
  --live-text:   #a8e6bf;   /* 深色底上的「新增」文字 */
  --code-bg:     #12141a;
  --code-fg:     #c8cede;
  --code-inline: #e6c07b;   /* 文档里的行内代码 */

  --radius: 10px;
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, "Courier New", monospace;
  --font-scale: 1;

  /* ── 字号阶梯 ────────────────────────────────────────────────────────────
     7 档，相邻约 1.13 倍（肉眼能分辨的最小步进）。
     全部乘 --font-scale —— 设置页那个「正文字号」滑块以前只对 4 处生效
     （全站 94 处 font-size 里就 4 处带它），拖了几乎没反应；现在全站跟随。

     收敛前是 29 种写法（10.5/11/11.5/12/12.5/13/13.5/14/14.5/15/16/17/
     18/19/24/25/26px + 一堆 em）—— 差 1px 的几个档人眼分不出来，
     写代码的人却要为「这里该用 12.5 还是 13」反复纠结，同一类元素
     在不同地方就长出不同尺寸。这是「看着不精致」的根源。 */
  --fs-xs:   calc(11px   * var(--font-scale));   /* 角标、时间戳、代码框头 */
  --fs-sm:   calc(12.5px * var(--font-scale));   /* 次要说明、元信息、表格 */
  --fs-md:   calc(13.5px * var(--font-scale));   /* 小正文、工具摘要 */
  --fs-base: calc(15px   * var(--font-scale));   /* 正文 */
  --fs-lg:   calc(17px   * var(--font-scale));   /* 强调、卡片标题 */
  --fs-xl:   calc(19px   * var(--font-scale));   /* 区块标题 */
  --fs-2xl:  calc(25px   * var(--font-scale));   /* 大标题 */

  /* ── 间距阶梯 ────────────────────────────────────────────────────────────
     2px 步进。改动前 padding 有 43 种写法、margin 15 种。 */
  --sp-1: 4px;
  --sp-2: 6px;
  --sp-3: 8px;
  --sp-4: 10px;
  --sp-5: 12px;
  --sp-6: 14px;
  --sp-7: 16px;
  --sp-8: 20px;
  --sp-9: 24px;
  --sp-10: 32px;

  /* ── 圆角 ──────────────────────────────────────────────────────────────
     4 档够用：小标记 → 小元素 → 卡片 → 大面板。
     另有 50%（圆点/圆按钮）与 999px（胶囊）两种「几何圆角」，不算档位。 */
  --radius-xs: 4px;
  --radius-sm: 8px;
  --radius-lg: 16px;

  /* 刘海和小白条 */
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);

  color-scheme: dark;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/* 浏览器对 [hidden] 的默认样式来自 UA 表，任何作者写的 display 都会盖掉它 ——
   .boot{display:flex} 就让 el.hidden=true 完全失效过一次（页面永远停在启动遮罩）。
   这条声明把 hidden 拉回作者层并加 !important，之后 el.hidden 才是可信的。 */
[hidden] { display: none !important; }

html, body {
  margin: 0; padding: 0;
  height: 100%;
  /* 兜底：任何元素都不该把整页撑出横向滚动条。
     代码块**自己**横向滚是刻意的（内容不能折行），但整页跟着滑就不是了 ——
     手机上那会让每一次上下滑动都可能误触成左右滑。 */
  overflow-x: hidden;
  background: var(--bg);
  color: var(--text);
  font-size: var(--fs-base);
  line-height: 1.6;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
               "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
  overscroll-behavior-y: none;
}

button, input, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; padding: 0; cursor: pointer; }
code { font-family: var(--mono); }

/* ------------------------------------------------ 通用 */

.muted  { color: var(--text-2); }
.faint  { color: var(--text-3); }
.mono   { font-family: var(--mono); }
.small  { font-size: var(--fs-sm); }
.nowrap { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hl     { color: var(--text); font-weight: 600; }

.row         { display: flex; align-items: center; }
.row-between { display: flex; align-items: center; justify-content: space-between; }
.grow        { flex: 1; min-width: 0; }

.card {
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
}

.dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--text-3); margin-right: var(--sp-3); flex: none;
}
.dot-live { background: var(--live); box-shadow: 0 0 0 4px var(--live-edge); }
.dot-off  { background: var(--text-3); }

/* 只在真有 agent 在跑时出现，作为「它还活着」的信号 */
@keyframes breathe { 0%,100% { opacity: 1 } 50% { opacity: .35 } }
.breathe { animation: breathe 2s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .breathe { animation: none } }

.btn {
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 44px;
  border-radius: var(--radius);
  background: var(--accent); color: var(--on-accent);
  font-size: var(--fs-base); font-weight: 600;
}
.btn:disabled { opacity: .4; }
.btn-ghost  { background: var(--surface-2); color: var(--text); border: 1px solid var(--line); }
.btn-danger { background: transparent; color: var(--danger); border: 1px solid var(--danger-edge-hi); }

.link { color: var(--accent); font-size: var(--fs-base); }

/**
 * 空状态。
 *
 * 之前是一行灰字居中，读起来像「出错了」—— 但绝大多数空状态（还没开会话、
 * 这个分类下没有）其实是**正常**的。拆成「一句主话 + 一句补充」：
 * 主话用正文色说清楚现在就是这样，补充用更淡的色说明下一步能做什么。
 */
.empty { padding: 64px var(--sp-9); text-align: center; }
.empty-t { font-size: var(--fs-base); color: var(--text-2); margin-bottom: var(--sp-3); }
.empty-s { font-size: var(--fs-sm); color: var(--text-3); line-height: 1.85; }

.err, .errbar {
  margin: var(--sp-5) var(--sp-7); padding: var(--sp-4) var(--sp-5);
  border-radius: var(--radius);
  background: var(--danger-tint);
  border: 1px solid var(--danger-edge);
  color: var(--danger-text); font-size: var(--fs-md);
}
.warnbox {
  margin: var(--sp-4) var(--sp-7) 0; padding: var(--sp-4) var(--sp-5);
  border-radius: var(--radius);
  background: var(--warn-tint);
  border: 1px solid var(--warn-edge);
  color: var(--warn); font-size: var(--fs-md);
}

.toast {
  position: fixed; left: 50%; bottom: calc(72px + var(--safe-b));
  transform: translateX(-50%);
  max-width: 80vw; padding: var(--sp-4) var(--sp-7);
  background: var(--veil-deep); color: var(--text-strong); font-size: var(--fs-md);
  border-radius: 999px; z-index: 90;
  backdrop-filter: blur(8px);
}

/* ------------------------------------------------ 视图骨架 */

.view { display: none; }
.view.on { display: block; }
/* dvh 老 WebView（微信 X5 之类）不认，不写回退的话整条声明被丢掉，
   flex:1 的消息区就会塌成 0 高 —— 表现为「进去了但什么都没有」。 */
.view-chat.on { display: flex; flex-direction: column; height: 100vh; height: 100dvh; }

/* 手机上没有控制台，出错时如果只是静默失败，表现就是「闪一下没反应」，
   完全没法查。所以未捕获的错误一律顶到屏幕上。 */
.crash {
  position: fixed; left: 0; right: 0; top: 0; z-index: 200;
  padding: calc(10px + var(--safe-t)) var(--sp-6) var(--sp-4);
  background: var(--danger-deep); border-bottom: 1px solid var(--danger-edge-hi);
  color: var(--danger-text); font-size: var(--fs-sm); line-height: 1.6;
  max-height: 45vh; overflow: auto;
}
.crash-t { font-weight: 700; margin-bottom: var(--sp-1); display: flex; justify-content: space-between; }
.crash pre { margin: var(--sp-1) 0 0; white-space: pre-wrap; word-break: break-all; font-family: var(--mono); font-size: var(--fs-xs); }

.wrap { padding: calc(16px + var(--safe-t)) var(--sp-7) calc(24px + var(--safe-b)); }

.boot {
  position: fixed; inset: 0; display: flex;
  flex-direction: column; align-items: center; justify-content: center;
  gap: var(--sp-6); background: var(--bg); z-index: 100;
}
.boot-mark {
  width: 52px; height: 52px; border-radius: var(--radius-lg);
  background: var(--surface-2); color: var(--accent);
  display: flex; align-items: center; justify-content: center; font-size: var(--fs-2xl);
}
.boot-text { color: var(--text-3); font-size: var(--fs-md); }

.topbar {
  display: flex; align-items: center; gap: var(--sp-4);
  padding: calc(10px + var(--safe-t)) var(--sp-6) var(--sp-4);
  background: var(--bg-2);
  border-bottom: 1px solid var(--line-soft);
  position: sticky; top: 0; z-index: 10;
}
.icon-btn {
  width: 34px; height: 34px; flex: none;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm); color: var(--text-2); font-size: var(--fs-xl);
  background: var(--surface-2);
}
.icon-btn:active { background: var(--line); }

/* ------------------------------------------------ 配对 */

.hero { padding: var(--sp-10) 2px var(--sp-9); }
.hero-mark {
  width: 48px; height: 48px; border-radius: var(--radius-lg);
  background: var(--surface-2); color: var(--accent);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-2xl); margin-bottom: var(--sp-8);
}
.hero-title { font-size: var(--fs-2xl); font-weight: 700; margin: 0 0 var(--sp-3); }
.hero-sub   { color: var(--text-2); font-size: var(--fs-base); margin: 0; line-height: 1.7; }
.kbd {
  font-family: var(--mono); font-size: var(--fs-sm);
  background: var(--surface-2); border-radius: var(--radius-xs); padding: 2px var(--sp-2); color: var(--code-inline);
}

.paired { padding: var(--sp-6); margin-bottom: var(--sp-6); }
.paired-t { font-size: var(--fs-md); color: var(--live); }
.paired-s { font-size: var(--fs-lg); font-weight: 600; margin-top: 2px; }
.paired-hint { color: var(--text-3); font-size: var(--fs-sm); margin-top: var(--sp-3); }

.manual { padding: var(--sp-5); }
.manual-input {
  width: 100%; min-height: 84px; resize: vertical;
  background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: var(--sp-4);
  font-size: var(--fs-sm); line-height: 1.6; color: var(--text);
  word-break: break-all;
}
.manual-input::placeholder { color: var(--text-3); }
.manual .btn { margin-top: var(--sp-4); }

/* 恢复码。手机上多半是「照着纸抄」，所以码要等宽、要大、要能一眼对齐 */
.restore { padding: var(--sp-5) var(--sp-6); }
.restore .link { padding: var(--sp-1) 0; }
#restore-body { margin-top: var(--sp-5); }
.restore-hint {
  color: var(--text-3); font-size: var(--fs-sm); line-height: 1.8; margin: 0 0 var(--sp-4);
}
/* .manual-input 是按 textarea 定的（min-height 84px、可拖拽），
   套到单行 input 上会撑出一个空荡荡的高盒子 */
input.manual-input {
  min-height: 0; height: 44px; resize: none;
  margin-bottom: var(--sp-3); letter-spacing: .04em;
}
#restore-code {
  font-size: var(--fs-base); letter-spacing: .1em; text-transform: uppercase;
}
.restore .btn { width: 100%; margin-top: var(--sp-1); }

/* 恢复码 / 口令 二选一。两条路的安全级别差很多，所以切换时下面那句说明也要跟着变 */
.restore-tabs { display: flex; gap: var(--sp-2); margin: var(--sp-1) 0 var(--sp-4); }
.restore-tabs .tab {
  flex: 1; padding: var(--sp-3) 0; border-radius: var(--radius-sm); cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--line);
  color: var(--text-3); font: inherit; font-size: var(--fs-md);
}
.restore-tabs .tab.on {
  background: var(--bg); border-color: var(--accent); color: var(--text); font-weight: 600;
}
.restore-note {
  color: var(--text-3); font-size: var(--fs-sm); line-height: 1.7; margin: 2px 0 var(--sp-4);
}
/* 「正在算…」不是错误，别用报错的红 */
#pair-err.info { color: var(--text-2, var(--text-3)); }

.notes { margin-top: var(--sp-9); }
.notes-t { font-size: var(--fs-md); color: var(--text-2); margin-bottom: var(--sp-4); font-weight: 600; }
.note {
  color: var(--text-3); font-size: var(--fs-sm); line-height: 1.85;
  margin: 0 0 var(--sp-3); padding-left: var(--sp-5); border-left: 2px solid var(--line-soft);
}

/* ------------------------------------------------ 会话列表 */

.dev-name { font-size: var(--fs-lg); font-weight: 600; }
.dev-sub  { font-size: var(--fs-sm); color: var(--text-3); margin-top: 1px; }
.via-lan   { color: var(--live); }
.via-cloud { color: var(--accent); }

.filters {
  display: flex; gap: var(--sp-3); padding: var(--sp-5) var(--sp-6) var(--sp-1);
  overflow-x: auto; scrollbar-width: none;
}
.filters::-webkit-scrollbar { display: none; }
.chip {
  flex: none; padding: var(--sp-1) var(--sp-6); border-radius: 999px;
  background: var(--surface); border: 1px solid var(--line-soft);
  color: var(--text-2); font-size: var(--fs-md);
}
.chip.on { background: var(--accent-tint); border-color: var(--accent-edge-hi); color: var(--accent); }

.list { padding: var(--sp-3) var(--sp-6) calc(24px + var(--safe-b)); }
.item {
  display: flex; align-items: flex-start; gap: var(--sp-4);
  padding: var(--sp-5); margin-bottom: var(--sp-3);
  background: var(--surface); border: 1px solid var(--line-soft);
  border-radius: var(--radius); width: 100%; text-align: left;
}
.item:active { background: var(--surface-2); }
.item-t { font-size: var(--fs-base); font-weight: 600; margin-bottom: 3px; }
.item-m { font-size: var(--fs-sm); color: var(--text-3); display: flex; gap: var(--sp-3); flex-wrap: wrap; }

/* ------------------------------------------------ 会话分组（按工作区）

   组头是「可点的」——整行都能点，不只是一个 12px 的小三角（手指粗也点得中）。
   数字用等宽小字靠右：它是对齐的，扫一眼就能看出哪个工作区攒得多。 */
.grp { margin-bottom: var(--sp-1); }
.grp-head {
  display: flex; align-items: center; gap: var(--sp-2);
  width: 100%; text-align: left;
  padding: var(--sp-2) var(--sp-1);
  font-size: var(--fs-sm); font-weight: 600; color: var(--text-2);
}
.grp-head:active { background: var(--wash-2); border-radius: var(--radius-xs); }
.grp-caret { flex: none; font-size: var(--fs-xs); color: var(--text-3); }
.grp-n { flex: none; font-family: var(--mono); font-size: var(--fs-xs); color: var(--text-3); }
.grp-body { display: block; }

/* 「查看更多」沿用 .loadmore 那套虚线语言：一眼就知道「这里还能展开」 */
.grp-more {
  display: block; width: 100%; text-align: center;
  padding: var(--sp-2); margin-bottom: var(--sp-3);
  color: var(--accent); font-size: var(--fs-sm);
  border: 1px dashed var(--line); border-radius: var(--radius);
}
.grp-more:active { background: var(--wash-2); }
/**
 * agent 标签：各家用自己的**官方品牌色 + 官方 logo**，一眼分清这个会话是谁的。
 *
 * 色值就是品牌色本身（不是从主题里挑的近似色），底色用同色极低透明度 ——
 * 有区分度但不喧哗，在深色底上也不会糊成一片。
 * logo 内联在 js/logos.js（CC0），不写死颜色、靠 currentColor 跟着这里的 color 走。
 *
 * codex 用白：OpenAI 的标志本来就是黑白两版，深色底上用白的那版。
 */
.agent-tag {
  flex: none; display: inline-flex; align-items: center; gap: 3px;
  font-size: var(--fs-xs); padding: 1px var(--sp-2) 1px var(--sp-1); border-radius: var(--radius-xs);
  background: var(--surface-2); color: var(--text-2); font-family: var(--mono);
}
.agent-logo { width: 11px; height: 11px; flex: none; }

.agent-claude    { color: #d97757; background: rgba(217, 119,  87, .16); }
.agent-codex     { color: var(--text); background: rgba(230, 232, 238, .16); }
.agent-workbuddy { color: #08c8a0; background: rgba(  8, 200, 160, .16); }
.agent-dsh       { color: #5786fe; background: rgba( 87, 134, 254, .16); }

/* ------------------------------------------------ 聊天 */

.chat-title { font-size: var(--fs-base); font-weight: 600; }
.chat-sub   { font-size: var(--fs-xs); color: var(--text-3); }

.stream {
  flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  padding: var(--sp-5) var(--sp-6) var(--sp-1);
  font-size: var(--fs-base);
  -webkit-overflow-scrolling: touch;
}
.loadmore {
  text-align: center; color: var(--text-3); font-size: var(--fs-sm);
  padding: var(--sp-4); margin-bottom: var(--sp-3);
  border: 1px dashed var(--line); border-radius: var(--radius);
}

.msg { margin-bottom: var(--sp-6); }
.msg-sub { border-left: 2px solid var(--tone-agent); padding-left: var(--sp-4); margin-left: 2px; }
.stamp { font-size: var(--fs-xs); color: var(--text-3); margin-top: var(--sp-1); }
.stamp-r { text-align: right; }

.bubble-user {
  background: var(--accent-tint);
  border: 1px solid var(--accent-edge);
  border-radius: var(--radius); padding: var(--sp-4) var(--sp-5);
  margin-left: var(--sp-10);
}
.assistant { padding: 0 1px; }

.think {
  border-left: 2px solid var(--think);
  padding: 2px 0 2px var(--sp-4); color: var(--text-2);
}
/* ⚠️ 必须显式 width: 100%：这是个 button，默认宽度由内容决定 ——
   手机上实测被一条长摘要撑到 520px（视口才 390），整页跟着能横向滑。
   里面的 .grow 已经有 min-width: 0，只要按钮自己不吃满就没事。 */
.think-head {
  display: flex; align-items: center; gap: var(--sp-3);
  width: 100%; min-width: 0;
  font-size: var(--fs-sm); color: var(--think);
}
.think-body { margin-top: var(--sp-2); font-size: var(--fs-md); color: var(--text-2); }

.tool, .res { border-radius: var(--radius); overflow: hidden; }
.tool { background: var(--surface); border: 1px solid var(--line-soft); }
.tool-head {
  display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5); width: 100%; text-align: left;
}
/* 工具图标：线性 SVG，尺寸固定，颜色交给下面的 .tone-* ——
   原来是 14px 的 emoji/符号，基线跟着字体走，和旁边的文字对不齐。 */
.tool-ico {
  flex: none; width: 14px; height: 14px;
  display: inline-flex; align-items: center; justify-content: center;
}
.tool-ico-svg { width: 14px; height: 14px; display: block; }
.tool-brief { font-size: var(--fs-md); color: var(--text); }
/* 摘要是「一条命令」时走等宽 + 稍小 + 稍暗：和旁边那些说明文字区分开。
   不加颜色 —— 颜色已经被工具类型（.tone-*）用掉了，再抢就乱。
   ⚠️ 这里必须带上 .tool-brief / .res-brief 两级：它们自己的 font-size 规则
   在文件更靠后的位置，同优先级下后出现的赢 —— 只写 .is-cmd 的话这条是白写的
   （实测量到的是 12.5px，不是这里的 12px）。 */
.tool-brief.is-cmd, .res-brief.is-cmd {
  font-family: var(--mono); font-size: var(--fs-sm); color: var(--text-2);
}
.tool-caret { flex: none; font-size: var(--fs-xs); }
.tool-body { padding: 0 var(--sp-5) var(--sp-5); }

/* ---------------------------------------------------------------- 微交互 */

/**
 * 展开时让内容淡入并轻轻落下。
 *
 * 之前是 `hidden` 属性直接切换 —— 硬切，内容「啪」地出现，看着像没做完。
 * 这里**不动 hidden 的机制**（改它要牵 chat.js 的交互逻辑，风险不成比例），
 * 只在「从隐藏变成可见」的那一刻补一个很小的动画。
 *
 * 4px + .16s 是故意压到这么小的：这类卡片一天要展开几十次，
 * 动画一大就变成表演，反而拖慢手感。
 */
@keyframes unfold { from { opacity: 0; transform: translateY(-4px) } to { opacity: 1; transform: none } }
.tool-body:not([hidden]),
.res-body:not([hidden]),
.think-body:not([hidden]) { animation: unfold .16s ease-out; }

/**
 * 点击反馈。
 *
 * 全站原来只有 7 处 :active（icon-btn / item / attach / q-opt / opt-row …），
 * 而**最常点的三个行 —— 工具行、输出行、思考行 —— 一个都没有**：
 * 按下去毫无变化，用起来像没点着。这里补齐，规则保持一致（按下时背景微亮一档）。
 */
.tool-head:active,
.res-head:active,
.think-head:active { background: var(--wash-2); }
.chip:active  { background: var(--line); }
.link:active  { opacity: .65; }
.copy:active  { opacity: .65; }
.expand:active { background: var(--surface-2); }
.btn:active   { transform: scale(.985); }

@media (prefers-reduced-motion: reduce) {
  .tool-body, .res-body, .think-body { animation: none; }
  .btn:active { transform: none; }
}

.tone-read   .tool-ico { color: var(--tone-read); }
.tone-write  .tool-ico { color: var(--tone-write); }
.tone-exec   .tool-ico { color: var(--tone-exec); }
.tone-search .tool-ico { color: var(--tone-search); }
.tone-net    .tool-ico { color: var(--tone-net); }
.tone-agent  .tool-ico { color: var(--tone-agent); }
.tone-todo   .tool-ico { color: var(--tone-todo); }
.tone-mcp    .tool-ico { color: var(--tone-mcp); }
.tone-other  .tool-ico { color: var(--tone-other); }

.res-head { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-1) 2px; width: 100%; text-align: left; }
.res-ico { flex: none; color: var(--text-3); font-family: var(--mono); }
.res-brief { font-size: var(--fs-sm); color: var(--text-2); }
.res-bad .res-ico, .res-bad .res-brief { color: var(--danger); }
.res-body { margin-top: var(--sp-1); }

.expand {
  margin-top: var(--sp-3); padding: var(--sp-3); text-align: center;
  border: 1px dashed var(--line); border-radius: var(--radius-sm);
  color: var(--accent); font-size: var(--fs-sm); width: 100%;
}

.todo { background: var(--surface); border: 1px solid var(--line-soft); border-radius: var(--radius); padding: var(--sp-4) var(--sp-5); }
.todo-head { font-size: var(--fs-sm); color: var(--tone-todo); margin-bottom: var(--sp-2); }
.todo-item { display: flex; gap: var(--sp-3); font-size: var(--fs-md); padding: 2px 0; }
.todo-done { color: var(--text-3); text-decoration: line-through; }
.todo-doing { color: var(--text); }
.todo-wait { color: var(--text-2); }

.usage, .notice, .status {
  font-size: var(--fs-xs); color: var(--text-3); text-align: center; padding: 2px 0;
}
.status-run { color: var(--live); }

.clip-note { font-size: var(--fs-xs); margin-top: var(--sp-2); padding-left: 2px; }

.composer {
  display: flex; align-items: flex-end; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-5) calc(8px + var(--safe-b));
  background: var(--bg-2); border-top: 1px solid var(--line-soft);
}
.draft {
  flex: 1; min-height: 38px; max-height: 140px; resize: none;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 999px; padding: var(--sp-3) var(--sp-6); font-size: var(--fs-base); line-height: 1.4;
}
.draft::placeholder { color: var(--text-3); }
/* 上传按钮。传的过程中它自己会变成百分比，所以别设固定宽度。 */
.attach {
  flex: none; min-width: 38px; height: 38px; padding: 0 var(--sp-4);
  border-radius: 999px; font-size: var(--fs-lg); line-height: 1;
  background: var(--surface); color: var(--text-2);
  border: 1px solid var(--line);
}
.attach:active { background: var(--surface-2); }
.send {
  width: 38px; height: 38px; flex: none; border-radius: 50%;
  background: var(--accent); color: var(--on-accent); font-size: var(--fs-lg); font-weight: 700;
}
.send:disabled { opacity: .35; }

/*
  主按钮变成「停止」时的样子。
  同一个位置干两件事，**必须一眼能分辨**，否则用户会以为自己在发消息、
  结果把 agent 停了（反过来更糟）。所以：换成警示色、换个字形（■ 停 / ↑ 发），
  再稍微缩一点 —— 停止是个「中断」动作，不该长得像主行动号召那么显眼。
*/
.send.stop {
  background: var(--danger);
  color: var(--text-strong);
  font-size: var(--fs-md);
}
.send.stop:active { transform: scale(.94); }

/* ------------------------------------------------ 排队中的消息 */

/*
  agent 在跑的时候发的消息会先排着。**必须让它看得见** ——
  否则用户会以为消息丢了，然后重复发同样的内容。
*/
.queued {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-6);
  background: var(--accent-tint);
  border-top: 1px solid var(--accent-edge);
  font-size: var(--fs-sm); color: var(--text-2);
}
.q-badge {
  flex: none; padding: 1px var(--sp-3); border-radius: 999px;
  background: var(--accent); color: var(--on-accent); font-weight: 600; font-size: var(--fs-xs);
}
.q-first { color: var(--text-3); min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.queued .link { flex: none; margin-left: auto; }

/* ------------------------------------------------ agent 在问你 */

/*
  选项按钮。要求是「点一下就选好」，所以：
    · 整块可点（不是一个 12px 的小圆点），手指粗也不会点错
    · 选中状态要**一眼看出来**（左边一道竖条 + 换底色），
      因为多选时你得知道刚才点没点上
    · 描述文字不能抢标题 —— 先让你扫 label，有兴趣再看 description
*/
.q { margin-bottom: var(--sp-6); }
.q:last-of-type { margin-bottom: var(--sp-1); }
.q-tag {
  display: inline-block; padding: 2px var(--sp-3); border-radius: 999px;
  background: var(--surface-2); color: var(--text-3);
  font-size: var(--fs-xs); letter-spacing: .04em; margin-bottom: var(--sp-2);
}
.q-text { font-size: var(--fs-base); line-height: 1.6; margin-bottom: var(--sp-4); }

.q-opt {
  display: block; width: 100%; text-align: left;
  padding: var(--sp-5) var(--sp-5); margin-bottom: var(--sp-3);
  background: var(--bg); border: 1px solid var(--line);
  border-left: 3px solid transparent;
  border-radius: var(--radius); color: var(--text);
  font: inherit; cursor: pointer;
}
.q-opt:active { background: var(--surface-2); }
.q-opt.on {
  border-color: var(--accent);
  border-left-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--bg));
}
.q-label { display: block; font-size: var(--fs-base); font-weight: 600; }
.q-opt.on .q-label { color: var(--accent); }
.q-desc {
  display: block; margin-top: 3px;
  font-size: var(--fs-sm); line-height: 1.55; color: var(--text-3);
}

/* ------------------------------------------------ 设置 */

.sec { padding: var(--sp-6); margin-bottom: var(--sp-5); }.sec-t { font-size: var(--fs-sm); color: var(--text-3); margin-bottom: var(--sp-4); font-weight: 600; }
.opt {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-4) 0; font-size: var(--fs-base); border-bottom: 1px solid var(--line-soft);
}
.opt:last-of-type { border-bottom: 0; }
.opt-col { flex-direction: column; align-items: stretch; gap: var(--sp-3); }
.opt input[type=range] { width: 100%; accent-color: var(--accent); }
.opt input[type=checkbox] { width: 20px; height: 20px; accent-color: var(--accent); }
.kv {
  display: flex; justify-content: space-between; gap: var(--sp-5);
  padding: var(--sp-3) 0; font-size: var(--fs-md); color: var(--text-2);
}
/* 局域网可能有好几个地址，用 \n 分隔的要真的分行 */
.kv b { color: var(--text); font-weight: 500; text-align: right; word-break: break-all; white-space: pre-line; }
.sec .btn { margin-top: var(--sp-5); }
.foot { text-align: center; font-size: var(--fs-xs); margin-top: var(--sp-8); }

/* ------------------------------------------------ 富文本块 */

.blk { margin: 0 0 var(--sp-3); }
.blk:last-child { margin-bottom: 0; }
.blk-p { word-break: break-word; white-space: pre-wrap; }

/**
 * markdown 标题。
 *
 * 原来用 em 写成 1.18 / 1.09 / 1.02 / 1（正文 15px 下 = 17.7 / 16.35 /
 * 15.3 / 15px）：**三级之间只差 1px 左右，人眼分不出来；而 h4 干脆就是
 * 正文字号** —— 等于没有层级。改成档位，四级递减变成看得见的结构：
 * 17 / 15 / 13.5 / 13.5px，h4 靠颜色再退一档。
 * （用档位而不是 em，顺便让标题在设置页调字号时跟着走。）
 */
.blk-h { font-weight: 700; margin: var(--sp-4) 0 var(--sp-1); }
.blk-h-1 { font-size: var(--fs-lg); }
.blk-h-2 { font-size: var(--fs-base); }
.blk-h-3 { font-size: var(--fs-md); }
.blk-h-4 { font-size: var(--fs-md); color: var(--text-2); }

.blk-li { display: flex; align-items: flex-start; }
.blk-li-mark { flex: none; color: var(--text-3); margin-right: var(--sp-2); font-family: var(--mono); font-size: var(--fs-sm); line-height: 1.78; }
.blk-li-d1 { padding-left: var(--sp-6); }
.blk-li-d2 { padding-left: calc(var(--sp-6) * 2); }
.blk-li-d3 { padding-left: calc(var(--sp-6) * 3); }

.blk-quote { border-left: 2px solid var(--line); padding-left: var(--sp-4); color: var(--text-2); }
.blk-hr { height: 1px; background: var(--line); margin: var(--sp-5) 0; border: 0; }

.sp-c {
  font-family: var(--mono); font-size: var(--fs-sm);
  background: var(--surface-2); border-radius: var(--radius-xs); padding: 1px var(--sp-1);
  color: var(--code-inline); word-break: break-all;
}
.sp-b { font-weight: 700; color: var(--text-strong); }
.sp-i { font-style: italic; color: var(--text-2); }

/* 代码块：横向单独滚，绝不让整页跟着横滑 */
/**
 * 一句话结论 —— **不套代码框**。
 *
 * 「已写入」这种结果套上等宽字体 + 边框 + 复制按钮之后，视觉重量和一段真代码
 * 一样重。一屏几十张卡片看下去全是框，反而什么都读不出来，所以给它一个
 * 轻得多的容器：正常字号、正常行高、只有一点点浅底做分组。
 */
.plain-wrap {
  background: var(--wash-1);
  border-radius: var(--radius-sm); padding: var(--sp-3) var(--sp-4); margin: var(--sp-2) 0;
}
.plain-body {
  font-size: var(--fs-sm); line-height: 1.6; color: var(--text-2);
  white-space: pre-wrap; word-break: break-word;
}
.plain-note { font-size: var(--fs-xs); margin-top: 3px; }

/**
 * 入参的键值对列表（替代「倒一串 JSON」）。
 *
 * 键用等宽小字靠左占固定宽度，值占满剩下的地方 —— 一眼能读完，
 * 不用去数括号配对。长值折行（pre-wrap）而不是横向滚动：
 * 手机上没有横向滚动条，滚出去的内容等于不存在。
 */
.fields {
  display: flex; flex-direction: column; gap: var(--sp-1);
  margin: var(--sp-2) 0; padding: var(--sp-4) var(--sp-4);
  background: var(--wash-1); border-radius: var(--radius-sm);
}
.field { display: flex; gap: var(--sp-4); align-items: baseline; }
.field-k {
  flex: none; min-width: 6.2em;
  font-family: var(--mono); font-size: var(--fs-xs); color: var(--text-3);
}
.field-v {
  flex: 1; min-width: 0;
  font-family: var(--mono); font-size: var(--fs-xs); line-height: 1.55;
  color: var(--code-fg); white-space: pre-wrap; word-break: break-word;
}

/**
 * 表格。
 *
 * agent 输出的表格，列数和内容长度都不受控，所以外面套一层横向滚动 ——
 * 这是**唯一**允许横向滚的地方（代码块在手机上已经改成折行了）：
 * 表格折行会让列对不上，横向滚至少读得通。
 * 单元格内部仍然允许换行并限宽，免得某一列很长就把整张表撑到几千像素。
 */
.tbl-wrap { margin: var(--sp-3) 0; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.tbl { border-collapse: collapse; font-size: var(--fs-sm); min-width: 100%; width: max-content; }
.tbl th, .tbl td {
  border: 1px solid var(--line-soft); padding: var(--sp-2) var(--sp-4);
  text-align: left; vertical-align: top;
  white-space: normal; max-width: 240px; word-break: break-word;
}
.tbl th { background: var(--surface-2); color: var(--text); font-weight: 600; }
.tbl td { color: var(--text-2); }
.tbl tr:nth-child(even) td { background: var(--wash-1); }
.ta-center { text-align: center; }
.ta-right  { text-align: right; }

.code-wrap {
  background: var(--code-bg); border: 1px solid var(--line-soft);
  border-radius: var(--radius); overflow: hidden; margin: var(--sp-2) 0;
}
.code-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-1) var(--sp-4); background: var(--wash-2);
  border-bottom: 1px solid var(--line-soft);
  font-size: var(--fs-xs); color: var(--text-3);
}
.copy { color: var(--accent); font-size: var(--fs-xs); }
.code-scroll { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.code-body {
  display: block; min-width: 100%; width: max-content;
  padding: var(--sp-4) var(--sp-4); margin: 0;
  font-family: var(--mono); font-size: var(--fs-sm); line-height: 1.65;
  color: var(--code-fg); white-space: pre;
}

/**
 * 手机上让代码**折行**，而不是横向滚。
 *
 * 横滚在桌面没问题（鼠标拖一下），在手机上要看全一行得左右来回蹭，
 * 一滑还容易把整页带偏 —— 那正是「对话框里能左右滑」的来源。
 * 折行会牺牲一点缩进对齐，但 agent 输出的代码行大多不长，读得下去更重要。
 * 桌面保持 pre（对齐优先），两边各取所需。
 *
 * diff 不在此列：左旧右新对不齐就没意义了（见 .diff-box）。
 */
@media (max-width: 480px) {
  .code-body { white-space: pre-wrap; word-break: break-word; width: auto; min-width: 0; }
}

.diff-head {
  display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-1) var(--sp-4); font-size: var(--fs-xs); color: var(--text-3);
  background: var(--wash-2); border-bottom: 1px solid var(--line-soft);
}
.diff-stat { flex: none; font-family: var(--mono); }
.add { color: var(--live); }
.del { color: var(--danger); }
.diff-box { min-width: 100%; width: max-content; }
.diff-row {
  display: block; font-family: var(--mono); font-size: var(--fs-sm);
  line-height: 1.6; white-space: pre; padding: 0 var(--sp-4);
}
.diff-add { background: var(--live-tint); color: var(--live-text); }
.diff-del { background: var(--danger-tint); color: var(--danger-text); }
.diff-ctx { color: var(--text-3); }

/* ------------------------------------------------ 工作中指示条 */

.working {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-6);
  background: var(--live-tint);
  border-bottom: 1px solid var(--live-edge);
  font-size: var(--fs-sm); color: var(--live);
}
.working-t { color: var(--live); }
.working-s { color: var(--text-3); font-family: var(--mono); font-size: var(--fs-xs); flex: none; }
/* 这里原来有个 .working-stop（状态条右侧的「中断」）。删掉了 —— 它离左上角
   的返回键只有几十像素，误触就把 agent 停了。停止现在挂在发送按钮上，
   规则和 DSH 一致，见 js/ui/chat.js 的 primaryRole()。 */

/* 三个点依次亮起 —— 只在真的有 status:running 时出现 */
.dots { display: inline-flex; gap: 3px; margin-left: 2px; }
.dots i {
  width: 3px; height: 3px; border-radius: 50%; background: currentColor;
  animation: blink 1.4s infinite;
}
.dots i:nth-child(2) { animation-delay: .2s; }
.dots i:nth-child(3) { animation-delay: .4s; }
@keyframes blink { 0%, 60%, 100% { opacity: .25 } 30% { opacity: 1 } }
@media (prefers-reduced-motion: reduce) { .dots i { animation: none; opacity: .6 } }

/* ------------------------------------------------ 底部面板 */

.sheet { position: fixed; inset: 0; z-index: 120; }
.sheet-mask { position: absolute; inset: 0; background: var(--veil); }
.sheet-body {
  position: absolute; left: 0; right: 0; bottom: 0;
  max-height: 86vh; display: flex; flex-direction: column;
  background: var(--bg-2);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  border-top: 1px solid var(--line);
  padding-bottom: var(--safe-b);
  animation: rise .18s ease-out;
}
@keyframes rise { from { transform: translateY(14px); opacity: .4 } to { transform: none; opacity: 1 } }
@media (prefers-reduced-motion: reduce) { .sheet-body { animation: none } }

.sheet-head {
  display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-5) var(--sp-6); border-bottom: 1px solid var(--line-soft); flex: none;
}
.sheet-t { flex: 1; text-align: center; font-size: var(--fs-base); font-weight: 600; }
.sheet-x {
  width: 30px; height: 30px; flex: none; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-2); color: var(--text-2); font-size: var(--fs-md);
}
/* 右边那个是纯占位，用来让标题居中 —— 它不该被看见 */
span.sheet-x { visibility: hidden; }
.sheet-content { overflow-y: auto; padding: var(--sp-6) var(--sp-6) calc(20px + var(--safe-b)); }

.sheet-sec { margin-bottom: var(--sp-8); }
.sheet-sec:last-child { margin-bottom: 0; }
.sheet-sec-t { font-size: var(--fs-sm); color: var(--text-3); font-weight: 600; margin-bottom: var(--sp-2); }
.sheet-sec-h { font-size: var(--fs-xs); color: var(--text-3); line-height: 1.7; margin: -2px 0 var(--sp-3); }
.sheet-sec-h.locked { margin: var(--sp-3) 0 0; color: var(--warn); }

.opts { overflow: hidden; }
.opt-row {
  display: flex; align-items: center; gap: var(--sp-4); width: 100%; text-align: left;
  padding: var(--sp-5) var(--sp-5); border-bottom: 1px solid var(--line-soft);
}
.opt-row:last-child { border-bottom: 0; }
.opt-row:active { background: var(--surface-2); }
.opt-row.on .opt-l { color: var(--accent); }
.opt-l { font-size: var(--fs-base); }
.opt-h { font-size: var(--fs-xs); color: var(--text-3); margin-top: 1px; }
.opt-tick { flex: none; color: var(--accent); font-size: var(--fs-base); width: 14px; }

.sheet-note {
  font-size: var(--fs-xs); color: var(--text-3); line-height: 1.75;
  padding: var(--sp-4) var(--sp-5); border-left: 2px solid var(--line);
  background: var(--surface); border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

/* ── 流式打字 ─────────────────────────────────────────────────────────────
   只有手机发起的那一轮才有。电脑终端里跑的会话拿不到分块流（文件里落的
   每一行都已经是完整块了），那种情况下这块区域根本不会出现 —— 宁可没有，
   也不做一个假的打字动画去冒充。 */
.live { padding: 0 2px; }
.live-blk { margin: var(--sp-4) 0; }
.live-txt {
  font-size: var(--fs-base); line-height: 1.72;
  color: var(--text); white-space: pre-wrap; word-break: break-word;
}
.live-think .live-txt { color: var(--text-3); font-size: var(--fs-md); }
.live-tag {
  display: inline-block; font-size: var(--fs-xs); color: var(--text-3);
  margin-bottom: var(--sp-1); letter-spacing: .04em;
}
.caret {
  display: inline-block; width: 7px; height: 1.05em; vertical-align: -2px;
  margin-left: 2px; background: var(--accent); border-radius: 1px;
  animation: blink 1.05s steps(1) infinite;
}
@keyframes blink { 0%, 49% { opacity: 1 } 50%, 100% { opacity: 0 } }
@media (prefers-reduced-motion: reduce) {
  .caret { animation: none; opacity: .6 }
}

/* ------------------------------------------------ 会话流里的图片 */

/* 图片是按需取的，所以先给一个「多大、点一下」的条，而不是留白或者转圈。 */
.imgs { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-3); }
.imgchip {
  display: flex; align-items: center; gap: var(--sp-3);
  flex: 1 1 100%; width: 100%;
  padding: var(--sp-4) var(--sp-5); cursor: pointer; text-align: left;
  background: var(--surface-2); border: 1px dashed var(--line-soft);
  border-radius: var(--radius); color: var(--text-2);
  font-size: var(--fs-sm);
}
.imgchip:active { background: var(--surface); }
.imgchip-ico { flex: none; opacity: .75; }

.sessimg {
  display: block; max-width: 100%; height: auto;
  border-radius: var(--radius); border: 1px solid var(--line-soft);
  background: var(--surface-2); cursor: zoom-in;
}

