/* ─────────────────────────────────────────────
   请求日志页：转发明细表（列形态参考 OmniProxy 的请求日志）
   列表容器与筛选行（.log-list / .log-empty / .log-filters / .log-pager）
   沿用 page-logs.css 的同一套声明，本文件只写表格本身、两个维度筛选的
   宽度上限与窄窗口重排。
   颜色一律取 tokens.css 的语义变量，浅色深色共用同一组声明。
   ───────────────────────────────────────────── */

/* ── 提供商 / 模型两个维度筛选（本次新增）─────────────────────
   宽度由 page-logs.css 的 `.log-filters select { width: auto }` 定成「按内容」，
   这里只加上限：模型名可以很长（claude-sonnet-4-5-20250929 之类），
   不设上限时它会按最长选项把触发器撑得很宽、把时间档位挤到换行。
   截断只发生在**触发器**上（显示省略号）；选项完整文案在浮层里
   （select.js 的浮层宽度独立算：至少与触发器同宽、最长 320px）。
   两个上限按各自的内容量级取：提供商名最长是「AutoClaw 国内版」（约 110px），
   模型名普遍再长一档。 */
.log-filters #req-provider { max-width: 168px; }
.log-filters #req-model { max-width: 200px; }

/* 九列：时间 / 提供商·账号 / 重试 / 状态 / 模型 / 用时 / 用量 / 错误 / 详情。
   时间与用时定宽（等宽数字 + tabular-nums，竖着比对才齐），
   其余占弹性宽度（都是不定长内容，给固定宽度会互相挤）：
   模型 1.3fr（claude-sonnet-4-5 这类名字要放得下），
   用量 1.6fr（两行读数里 in/out/all 最长），超出由省略号收住、完整值进 title。
   状态 96px：进行中的行有两行（阶段徽章 + 阶段计时），而徽章最长是
   「等待响应」四个字（约 76px）—— 68px 那一版会把它压成省略号。
   详情列定宽（只有一个「详情」按钮或一个「-」）。

   --req-cols 是用户拖过的列宽（table-columns.js 写在 #req-list 上，靠继承
   作用到每一行 —— 表格是整体重绘的，变量不需要在渲染后再写一遍）：
   拖过哪一列，那一条轨道就从 minmax(…fr) 换成固定像素，没拖过的仍是弹性权重，
   于是这一行永远填满、不滚动、不留空。变量没设时走下面的默认值，
   改默认列宽时与 table-columns.js 里对应列的 track 字段要同步。 */
.req-row,
.req-head {
  display: grid;
  grid-template-columns: var(--req-cols,
    92px minmax(0, 1.1fr) 52px 96px minmax(0, 1.3fr) 96px minmax(0, 1.6fr) minmax(0, 1.2fr) 60px);
  gap: 12px;
  align-items: center;
  padding: 10px 14px;
  /* 列表是 flex 列容器：条目不许被压缩，超出的一律交给列表滚动 */
  flex: none;
}

/* 表头：粘在列表顶部。列表自身滚动，滚动条是它自己的（不是页面），
   sticky 的参照物就是 .log-list，因此顶部偏移写 0。 */
.req-head {
  position: sticky;
  top: 0;
  z-index: 1;
  padding-top: 8px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border);
  background: var(--table-head-bg);
  color: var(--text-3);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .02em;
}

/* 表头格子是列宽把手的定位锚（.col-grip 绝对定位挂在它内側右缘，见 components.css） */
.req-head > span { position: relative; }

.req-row { border-bottom: 1px solid var(--hairline); transition: background .12s ease; }
.req-row:last-child { border-bottom: none; }
.req-row:hover { background: var(--row-hover); }

/* 表头里用时一格跟数值同侧对齐（数值列是右对齐的） */
.req-head .req-dur { text-align: right; }

/* 失败行轻微着色，与事件日志 error 行同一手法（不干扰阅读的淡底） */
.req-row.failed { background: color-mix(in srgb, var(--danger-soft) 45%, transparent); }
.req-row.failed:hover { background: var(--danger-soft); }

/* 无值占位（重试 / 错误列的「-」）：比正文淡一档，空着会被当成渲染缺失 */
.req-none { color: var(--text-3); }

/* ─── 进行中的行（status=0，转发还没收尾）─────────────────────
   后端契约：转发一开始就插一条 status=0 的行，收尾后由终态记账覆盖
   （见 stats_api::stats_requests）。行本身不加底色 —— 它还没落定，
   红底（.failed）是终态的颜色，进行中的信号交给状态徽章那一格。 */

/* 进行中行**分阶段**显示（连接中 / 等待响应 / 响应中 / 重试中，本次改造）：
   文案与类名由 request-phase.js 给（那边是唯一的文案来源），这里只管配色。
   色相对标 OmniProxy 的 Tag color（cyan / green / orange / processing），
   落到本项目的语义令牌上：连接中 = info 蓝（与改造前那枚「进行中」同色，
   语义也是同一档）、等待响应 = 青（就地定色，理由见下）、响应中 = ok 绿、
   重试中 = warn 琥珀。四个色相在浅深两套主题下都取自 tokens.css 的
   --x / --x-soft / --x-bd 三件套，所以这里不需要再写主题分支。

   为什么「等待响应」要就地定一个青色而不是复用现有四组之一：它与另外三个
   语义都不同（不是失败、不是完成、也不是「刚开始」），而等待时长恰恰是用户
   最常盯着看的一格 —— 借 --ok 会让「等模型出字」（可能很慢）看着像已经成功，
   借 --info 又与「连接中」分不开。OmniProxy 那边同样是就地给了个 cyan。 */

/* 没有阶段可读的在途行（旧数据、更早版本写入的行，见 request-phase.js 的
   FALLBACK_CLS）回落成改造前那枚「进行中」：info 蓝，与下面「连接中」同色
   —— 它们本来就是同一档语义，只是这枚说不出「卡在哪一步」。 */
.badge.tag.running {
  background: var(--info-soft);
  border: 1px solid var(--info-bd);
  color: var(--info);
}

.badge.tag.phase-connecting {
  background: var(--info-soft);
  border-color: var(--info-bd);
  color: var(--info);
}

.badge.tag.phase-waiting {
  background: oklch(0.62 0.12 200 / 12%);
  border-color: oklch(0.62 0.12 200 / 32%);
  color: oklch(0.5 0.13 205);
}

.badge.tag.phase-streaming {
  background: var(--ok-soft);
  border-color: var(--ok-bd);
  color: var(--ok);
}

.badge.tag.phase-retrying {
  background: var(--warn-soft);
  border-color: var(--warn-bd);
  color: var(--warn);
}

@media (prefers-color-scheme: dark) {
  .badge.tag.phase-waiting {
    background: oklch(0.72 0.11 200 / 15%);
    border-color: oklch(0.72 0.11 200 / 34%);
    color: oklch(0.8 0.1 200);
  }
}

/* 状态列两行（徽章 + 阶段计时）——只在进行中的行上出现。
   与用时列的两行结构同一手法：主读数是徽章，次读数是「这个阶段已经持续了
   多久」（淡一档、小半号）。这一列原先定宽 68px，而徽章最长是「等待响应」
   四个字放不下，所以本次加宽到 96px（见下方 grid-template-columns 与
   table-columns.js 的登记，两处必须同改）。 */
.req-row .req-status {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-width: 0;
}

.req-row .req-status-badge {
  display: flex;
  min-width: 0;
}

/* 阶段计时：等宽 + tabular-nums，相邻行的秒数竖着能直接比大小 */
.req-row .req-status-line {
  color: var(--text-3);
  font-family: var(--font-mono);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* 徽章里的呼吸圆点：1.2s 一拍的不透明度脉动 —— 不用进度条，
   状态列那点宽度塞不下，圆点的「活着」感刚好。currentColor 跟随
   徽章文字色，深浅主题都由 --info 一处决定。 */
.req-live-dot {
  flex: 0 0 auto;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
  animation: req-live-pulse 1.2s ease-in-out infinite;
}

@keyframes req-live-pulse {
  0%, 100% { opacity: .3; transform: scale(.75); }
  50% { opacity: 1; transform: scale(1); }
}

/* 系统开了「减少动态效果」就停在常亮：信息还在，动效退场 */
@media (prefers-reduced-motion: reduce) {
  .req-live-dot { animation: none; opacity: .8; }
}

/* ─── 导航徽标的「进行中」态 ──────────────────────────────────
   #nav-count-requests 两种数字共用一颗：进行中走 info 蓝（.is-running），
   未读失败维持 layout.css 的既有红。用 ID 选择器是为了稳定压过
   .nav-item .count:not(.muted) 的红色（与 update-notes.css 同一手法）。

   类名**刻意不叫 live**：layout.css 的 .live 是侧栏状态灯（7px 圆点、
   绿底、光晕），同一个类名会把角标压成小圆点（数字挤在 7px 高度里）。
   两颗组件都表达「活着」，但一个是灯、一个是数字，样式不能共用。 */
#nav-count-requests.is-running {
  border-color: var(--info-bd);
  background: var(--info-soft);
  color: var(--info);
}

/* 选中态导航项底色更深，保持同样的分离度（对齐 layout.css 的 active 覆盖） */
.nav-item.active #nav-count-requests.is-running { background: var(--surface); }

/* ─── 清理请求日志弹窗（request-clear-modal.js）───────────────
   两种清理方式做成单选卡片而不是 seg：两种方式的差别（删不删统计行、
   动不动日报）必须写在选择的当下 —— seg 只放得下名字，放不下后果说明。 */

.clear-modes {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.clear-mode {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 2px 10px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}

/* 单选钮独占左列竖向居中；标题与说明右列上下两行 */
.clear-mode input[type="radio"] {
  grid-row: 1 / 3;
  align-self: center;
  margin: 0;
}

.clear-mode .t { color: var(--text); font-size: 13px; font-weight: 600; }
.clear-mode .d { color: var(--text-3); font-size: 11.5px; line-height: 1.55; }

.clear-mode:hover { border-color: var(--border-strong, var(--border)); }

/* 选中卡片走品牌浅底（与 .seg-item.active 同一观感），JS 负责切换 .active
   （不依赖 :has —— 选择器支持面比 WebView 版本预期更挑） */
.clear-mode.active {
  border-color: var(--primary);
  background: var(--primary-soft);
}
.clear-mode.active .t { color: var(--primary-fg); }

/* 预览统计一行：贴在方式卡片下面，随每次 clear-preview 刷新 */
.rcm-preview { margin-top: 10px; }

/* 时间列：日期 + 时刻两行，等宽避免每一行的冒号错位。
   数据格与表头格共用同一个类名（窄窗口要按列重排，见文件末尾的媒体查询），
   所以每一条排版声明都带 .req-row 前缀限定在数据行上 —— 否则表头会被一并
   套上等宽字体，一行小标题看着像数据。 */
.req-row .req-time {
  display: flex;
  flex-direction: column;
  color: var(--text-3);
  font-family: var(--font-mono);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* 提供商 / 账号：同格两行，提供商在上（主读数）、账号在下（次）。
   两者都是短名词，省略号收住长名字，完整值在各自的 title 里。 */
.req-row .req-target {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.req-row .req-provider,
.req-row .req-acct {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.req-row .req-provider { color: var(--text-2); font-size: 12px; }
.req-row .req-acct { color: var(--text-3); font-size: 11.5px; }

/* 无提供商 / 无账号（旧数据或请求在任何账号接手前就失败）：给破折号 */
.req-provider.is-empty,
.req-acct.is-empty { color: var(--text-3); }

/* 模型名：标识符用等宽，超长从尾部省略（完整值在 title 里） */
.req-row .req-model {
  min-width: 0;
  overflow: hidden;
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 上下名分行的形态（转发名 ≠ 请求名时，如映射 / 备援改写）：
   ⬆️ 上游实际收到的名字（主读数）在上，⬇️ 下游请求的名字（淡一档）在下 ——
   与用时列的两行结构同一手法；单行请求不受影响（类不出现）。 */
.req-row .req-model-split {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.req-row .req-model-line {
  overflow: hidden;
  max-width: 100%;
  text-overflow: ellipsis;
}

.req-row .req-model-line.sub {
  color: var(--text-3);
  font-size: 11px;
}

/* 用时：等宽 + 右对齐，相邻行的数字能直接竖着比大小。
   两行：总用时（主读数）+ 首响（次读数，淡一档，与用量列的 sub 同一手法）——
   OmniProxy 的用时列就是这个形态。只有数据行是两行，表头仍是单行文本
   （所有排版声明都带 .req-row 前缀限定在数据行上，见上方 .req-time 的说明）。 */
.req-row .req-dur {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}

.req-row .req-dur-line {
  overflow: hidden;
  max-width: 100%;
  text-overflow: ellipsis;
}

/* 首响是次读数：淡一档、小半号，不抢总用时 */
.req-row .req-dur-line.sub {
  color: var(--text-3);
  font-size: 11px;
}

/* 用量两行：in / out / all 与 缓存读取 / 命中率。
   等宽 + tabular-nums，超出省略（完整两行读数在 title 里）。 */
.req-row .req-usage {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.req-usage-line {
  overflow: hidden;
  color: var(--text-2);
  font-family: var(--font-mono);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 第二行（缓存读取 / 命中率）是附加说明：淡一档，不抢主读数 */
.req-usage-line.sub { color: var(--text-3); }

/* 错误列：红字省略，完整值在 title 里；它是排查的入口，红着比藏着好 */
.req-row .req-error {
  min-width: 0;
  overflow: hidden;
  color: var(--danger);
  font-size: 11.5px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 窄窗口：拆成多行，免得八列都缩成省略号。
   断点取 1200px：默认窗口 1060px 时内容区约 772px，八列必然挤，
   以下面的四列重排为准；拉宽到 1200 以上才换八列横排。

   ── 为什么每个格子都显式写 grid-row / grid-column ──
   只给部分格子写列位置时，剩余格子由自动放置算法补空，落点取决于「哪个格子
   已被占」这个隐式状态：以后往行里插一个新字段，落点会整体错一格且不报错。
   写死行列值后，增删字段只影响它自己那一格。 */
@media (max-width: 1200px) {
  .req-row,
  .req-head {
    /* 第三格（状态）与宽窗口同一口径取 96px：窄窗口下它同样要放得下
       「等待响应」四个字（比默认窗口更需要 —— 这里的弹性列只剩模型那一格） */
    grid-template-columns: 92px minmax(0, 1fr) 96px 52px;
    row-gap: 4px;
  }
  /* 重排成四行之后轨道已经不是那八条了：上面这条声明直接覆盖 --req-cols，
     拖动没有意义，把手一并收起，免得点了没反应 */
  .req-head .col-grip { display: none; }

  /* 表头只保留第一行的四格，其余列头隐藏（列位置已重排，留着会冲突） */
  .req-head .req-time { grid-row: 1; grid-column: 1 / 2; }
  .req-head .req-model { grid-row: 1; grid-column: 2 / 3; }
  .req-head .req-status { grid-row: 1; grid-column: 3 / 4; }
  .req-head .req-retry { grid-row: 1; grid-column: 4 / 5; }
  .req-head .req-target,
  .req-head .req-dur,
  .req-head .req-usage,
  .req-head .req-error-cell { display: none; }

  /* 第一行：时间 + 模型 + 状态 + 重试 */
  .req-row .req-time { grid-row: 1; grid-column: 1 / 2; }
  .req-row .req-model { grid-row: 1; grid-column: 2 / 3; }
  .req-row .req-status { grid-row: 1; grid-column: 3 / 4; }
  .req-row .req-retry { grid-row: 1; grid-column: 4 / 5; }
  /* 第二行：提供商 / 账号 + 用时（用时贴右） */
  .req-row .req-target { grid-row: 2; grid-column: 1 / 3; }
  .req-row .req-dur { grid-row: 2; grid-column: 3 / 5; }
  /* 第三行：用量整行铺开（两行读数不再被列宽挤省略） */
  .req-row .req-usage { grid-row: 3; grid-column: 1 / -1; }
  /* 第四行：错误 + 详情。整行铺开时允许折行；没有错误的行不渲染错误那格。
     详情排在错误之后（同一行右端）—— 有错误时它跟着换到这一行，
     没错误时错误格不渲染，它自己占满这一行 */
  .req-row .req-error {
    grid-row: 4;
    grid-column: 1 / 4;
    white-space: normal;
    word-break: break-word;
  }
  .req-row .req-detail { grid-row: 4; grid-column: 4 / 5; justify-self: end; }
  /* 「-」占位不再独占一行：窄窗口下空行比省略号更难解释 */
  .req-row .req-error-cell { display: none; }
  /* 详情列头在窄窗口下没有稳定的列位（位置随有没有错误而变），一并隐藏 */
  .req-head .req-detail { display: none; }
}

/* 窄窗口：页头的自动刷新开关整行铺开，与日志页、报表页的时间范围同款处理。
   不折的话开关会压在标题文案上换行，标题与 intro 各自占一行，读起来像断了半句。 */
@media (max-width: 900px) {
  .page[data-page="requests"] .page-head .head-actions {
    flex-basis: 100%;
    margin-left: 0;
    justify-content: flex-start;
  }
}

/* ─── 详情列与详情弹窗（调试模式的上游原始报文）───────────────
   报文里是上游原文（SSE 文本、JSON 体），一律等宽字体 + 保留换行；
   弹窗比常规确认框宽（原文行很长），高度吃满可用空间后内部滚动。 */

.req-row .req-detail { justify-self: end; }

/* 详情按钮：小号、无边框感（一列里最轻的控件，不与状态徽章抢注意力） */
.req-detail-btn {
  padding: 2px 8px;
  font-size: 11.5px;
}

.modal-wide { width: min(920px, 100%); }

/* 「终止请求」按钮（详情弹窗底部，仅进行中的行显示）。
   必须显式写 [hidden] 的规则：components.css 给 button 设了
   display: inline-flex，会压过浏览器对 hidden 的默认 display: none
   （与 .add-provider-block[hidden]、.report-rank-grid > .panel[hidden]
   同一个坑）。 */
#req-detail-terminate[hidden] { display: none; }

.req-detail-meta {
  padding-bottom: 10px;
  border-bottom: 1px solid var(--hairline);
  color: var(--text-2);
  font-size: 12px;
  line-height: 1.7;
  word-break: break-all;
}

/* ─── 详情弹窗的分段选择器（请求头 / 请求体 / 响应头 / 响应体）───
   .seg / .seg-item 的形制来自 components.css（请求日志页的时间档位、
   账号页的筛选分段用的是同一套），这里只补三处本场景特有的：

   ① 分段条与下面的面板之间要有间距（.seg 自身没有下外边距，
      在页头里靠父容器的 gap 分隔，这里是弹窗内的独立一行）；
   ② 空段的标记：一枚小圆点（.seg-dot），并从 .seg-item.zero 的
      半透明里继承「这一段没内容」的观感；
   ③ 面板的显示切换靠 .is-active（一次渲染四块，切换只改 class ——
      这样切换时当前段的滚动位置不会丢，见 switchDetailSeg 的说明）。 */

.req-detail-segs { margin: 12px 0 10px; }

/* 空段标记：4px 圆点，比文字淡一档。放在标签文字后面（gap 由 .seg-item 的
   既有 gap: 5px 给出），不影响标签的点击区域 —— 它自己 pointer-events: none，
   点在圆点上与点在文字上一样会命中按钮。 */
.seg-item .seg-dot {
  flex: 0 0 auto;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: currentColor;
  opacity: .5;
  pointer-events: none;
}

/* 空段「仍可点」是这个控件的一条要求（见 request-hover 之外的说明：
   点进去会看到一句空态文案，比「点不动」更能解释为什么没有内容）。
   .seg-item.zero 的既有声明是 opacity: .45，这里不再压 —— 标签本身
   已经是「有内容/没内容」的完整表达，再压会让它看起来像 disabled。 */
.seg-item.zero:not(.active) { opacity: .6; }

.req-detail-pane { display: none; }

.req-detail-pane.is-active { display: block; }

.req-detail-pane h3 {
  margin-bottom: 6px;
  color: var(--text-3);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .02em;
}

.req-detail-pre {
  max-height: 320px;
  margin: 0;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface-2, var(--surface));
  color: var(--text-2);
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
  font-size: 11.5px;
  line-height: 1.6;
  white-space: pre-wrap;
  word-break: break-word;
  overflow: auto;
}

.req-detail-empty {
  padding: 24px 0;
  color: var(--text-3);
  font-size: 12.5px;
  text-align: center;
}

/* ─── 重试列的标签与悬停面板（request-hover.js）───────────────
   这一列现在承载两件事：重试链（橙标签）与敏感词命中（紫标签），
   悬停弹出富文本面板。形态照 OmniProxy 的 FailoverTip / SensitiveMaskedTag，
   配色换算到本项目自己的变量（那边用 antd 的 Tag 色板）。
   面板本体是 fixed 定位挂在 body 上 —— 与 .tip-bubble 同因同法
   （.panel 是 overflow:hidden，absolute 会被卡片裁掉）。 */

/* 标签排布：一列里可能有两枚（重试 + 敏感词），竖着排比并排好读 ——
   这一列只有 52px 宽，并排会立刻换行，反而不如显式竖排整齐。 */
.req-row .req-retry {
  display: flex;
  flex-direction: column;
  gap: 3px;
  align-items: flex-start;
  min-width: 0;
}

/* 标签自身：沿用 .badge / .tag 的既有形制（尺寸与状态列那两枚一致），
   只补一个「可以悬停看详情」的指针语义与交互反馈 ——
   它是一颗真按钮（见 requests-panel.js 的 retryCell），所以要清掉按钮的
   默认底色与内边距，让它看起来仍是徽章而不是一颗小按钮。 */
.req-hover-tag {
  padding: 0 6px;
  border: 0;
  cursor: help;
}

.req-hover-tag:focus-visible { box-shadow: var(--ring); }

/* 敏感词命中：紫色（OmniProxy 的 Tag color="purple"）。
   tokens.css 没有紫色语义变量，这里就地取一个色相的固定值而不是新加一个
   token —— 全站只有这一处需要紫色，进 tokens.css 会让人以为它是通用语义色。
   浅深两套各写一份：同一组紫在浅底与深底上需要的明度不同。
   .badge.tag.sensitive 是详情弹窗「请求详情」网格里的展开形态（词 × 次数），
   与列表的悬停标签共用同一套紫（见 request-detail.js 的 sensitiveHtml）。 */
.req-hover-tag.sensitive,
.badge.tag.sensitive {
  background: oklch(0.55 0.18 305 / 14%);
  border: 1px solid oklch(0.55 0.18 305 / 32%);
  color: oklch(0.45 0.18 305);
}

@media (prefers-color-scheme: dark) {
  .req-hover-tag.sensitive,
  .badge.tag.sensitive {
    background: oklch(0.7 0.16 305 / 16%);
    border-color: oklch(0.7 0.16 305 / 34%);
    color: oklch(0.82 0.12 305);
  }
}

/* 面板：深色底 + 浅字（与 .tip-bubble 同一档观感，但内容多、要能滚动）。
   用 --surface 系而不是主色底：面板里有红/绿/灰三种状态的文字色，
   压在 --primary 上会看不出差别。 */
.req-hover {
  position: fixed;
  z-index: 35;              /* 与下拉浮层、.tip-bubble 同档：在弹窗遮罩（30）之上 */
  display: none;
  max-height: min(360px, 60vh);
  padding: 10px 12px;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--surface) 96%, black);
  color: var(--text);
  font-size: 12px;
  line-height: 1.6;
  overflow-x: hidden;
  overflow-y: auto;
  box-shadow: var(--shadow-3);
  pointer-events: none;
  /* 注意：不能整体写 overflow-wrap: anywhere —— 那会把中文长句拆得七零八落。
     长英文 / URL 的换行只给 .rh-bad 那一类（见下）。 */
  word-break: normal;
}

.req-hover.open { display: block; }

/* 深色主题下深色面板与背景的分离度不够，改用带边框的抬升表面色 */
@media (prefers-color-scheme: dark) {
  .req-hover {
    border: 1px solid var(--border-strong);
    background: var(--surface-3);
  }
}

/* 切换路径行：面板顶部一条，与下面的尝试列表用分隔线隔开（OmniProxy 同法） */
.rh-chain {
  padding-bottom: 6px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--border);
  word-break: break-word;
}

.rh-chain-k { color: var(--text-3); font-weight: 600; }
.rh-chain-v { color: var(--text); font-weight: 600; }

/* 面板小标题（敏感词面板用） */
.rh-title {
  margin-bottom: 4px;
  color: var(--text-3);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .02em;
}

.rh-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 4px;
  min-width: 0;
  word-break: break-word;
}

/* 尝试序号 / 箭头 / 说明是次要标记：淡一档 */
.rh-no,
.rh-arrow,
.rh-dim { color: var(--text-3); }

.rh-who { color: var(--text); font-weight: 600; }

/* 账号名：挂在提供商名后面，淡一档与家名分开。
   账号名可能是很长的邮箱：允许在任意位置断行，否则会把面板顶宽。 */
.rh-account {
  color: var(--text-3);
  overflow-wrap: anywhere;
}

/* 失败那一行可能带 200 字符的错误摘要：允许在任意位置断行，
   否则一条没有空格的英文报错会把面板顶宽（OmniProxy 的 overflowWrap: anywhere 同法） */
.rh-bad {
  flex: 1 1 0%;
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--danger);
  font-weight: 500;
}

.rh-ok { color: var(--ok); font-weight: 500; }

/* ── 一轮内部的重试链（本次改造）─────────────────────────────
   缩进一格并加左侧竖线：它是**所属那一轮的下级事件**（同账号重发），
   不是并列的尝试 —— 视觉上必须能看出这个层级，否则会被读成又换了一家。 */
.rh-retry {
  margin: 2px 0 4px 10px;
  padding-left: 8px;
  border-left: 2px solid var(--border-strong);
}

.rh-retry-head {
  color: var(--warn);
  font-size: 11.5px;
  font-weight: 600;
}

.rh-retry-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 4px;
  min-width: 0;
  word-break: break-word;
}

/* 重试原因可能是一句带「（11128）」的中文提示：允许断行，别顶宽面板 */
.rh-retry-why {
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--text-2);
}

.rh-retry-status {
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* 提示行（代理回退直连等）：非失败、但值得记一笔 —— 用警示色而不用红 */
.rh-notice {
  margin: 2px 0 4px 10px;
  padding-left: 8px;
  border-left: 2px solid var(--border-strong);
  color: var(--warn);
  overflow-wrap: anywhere;
}

/* 敏感词列表：词与次数分列，次数贴右竖着能比大小 */
.rh-hit {
  justify-content: space-between;
  gap: 10px;
}

.rh-hit-w { color: var(--text); word-break: break-word; }
.rh-hit-c {
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ─── 详情弹窗的三标签版（本次改造）：请求详情网格 / 预览对话气泡 ───
   三个标签本体复用 .seg / .seg-item（与时间档位同一套）；「原始报文」标签
   内的四段由原来的分段切换改为竖排（每段一个 .req-detail-pre，内部滚动），
   所以这里补了段落之间的上边距。下方是请求详情网格与对话气泡两块新样式，
   都只用现有 CSS 变量，深浅两套主题自动跟随。 */

/* 原始报文：四块竖排之间的分隔（pre 自带 max-height 与滚动，见上） */
.req-detail-pane h3:not(:first-child) { margin-top: 16px; }

/* ① 请求详情：两列网格（label 列 + value 列）。整体不描边、只画行分隔线 ——
   弹窗里内容已经分层，再包一层边框会显得过重（形制对齐 .req-detail-meta）。 */
.req-detail-grid {
  width: 100%;
  border-collapse: collapse;
  font-size: 12.5px;
  line-height: 1.6;
}

.req-detail-grid th,
.req-detail-grid td {
  padding: 7px 10px;
  border-bottom: 1px solid var(--hairline);
  text-align: left;
  vertical-align: top;
}

.req-detail-grid tr:last-child th,
.req-detail-grid tr:last-child td { border-bottom: 0; }

.req-detail-grid th {
  width: 76px;
  color: var(--text-3);
  font-weight: 600;
  white-space: nowrap;
}

.req-detail-grid td { color: var(--text-2); word-break: break-word; }

/* 网格里的辅助小字（账号括注、耗时注解等） */
.req-detail-sub { color: var(--text-3); font-size: 11.5px; }

/* 错误值：与列表错误列同一语义色 */
.req-detail-error { color: var(--danger); }

/* 模型两行：⬆️ 上游 / ⬇️ 下游（与列表模型列同构，字号随网格） */
.req-detail-model-split { display: flex; flex-direction: column; gap: 2px; }
.req-detail-model-split .sub { color: var(--text-3); }

/* 尝试明细小表格（每次上游尝试一行） */
.req-attempt-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 11.5px;
}

.req-attempt-table th,
.req-attempt-table td {
  padding: 4px 8px;
  border: 1px solid var(--hairline);
  text-align: left;
  vertical-align: top;
  word-break: break-word;
}

.req-attempt-table th {
  color: var(--text-3);
  font-weight: 600;
  white-space: nowrap;
  background: var(--surface-2, var(--surface));
}

/* 结果的三态配色与 request-hover 的尝试行同源：成功绿、失败红、未定论灰 */
.req-attempt-ok { color: var(--ok); }
.req-attempt-bad { color: var(--danger); overflow-wrap: anywhere; }
.req-attempt-more td { color: var(--text-3); }

/* ② 预览对话：气泡流（形态照 OmniProxy 的 ConversationPreview，
   配色换算到本项目的 tokens）。滚动收在 .cv 内，弹窗自身不被长对话撑破。 */
.cv {
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-height: 480px;
  padding: 2px;
  overflow-y: auto;
}

/* 一行的落位：user 靠右、assistant 靠左、system 居中 */
.cv-row { display: flex; justify-content: flex-start; }
.cv-row.user { justify-content: flex-end; }
.cv-row.sys { justify-content: center; }

/* 气泡本体：最大宽度 75%（system 90%），长单词强制断行 */
.cv-msg {
  max-width: 75%;
  padding: 8px 12px;
  border-radius: 10px;
  font-size: 12.5px;
  line-height: 1.65;
  overflow-wrap: break-word;
}

/* user 气泡：主色底白字，输入是纯文本（不做 markdown，见 conversation-preview
   的 userInner 说明），保留原有换行与空白 */
.cv-msg.user { background: var(--primary); color: #fff; }
.cv-plain { white-space: pre-wrap; }

/* assistant 气泡：抬升表面色 + 细描边 */
.cv-msg.assistant { background: var(--surface-2); border: 1px solid var(--hairline); }

/* system（提示词）/ 错误条：全宽灰条，淡一档 */
.cv-msg.sys {
  max-width: 90%;
  background: var(--surface-inset);
  color: var(--text-2);
  font-size: 12px;
  border-radius: 8px;
}

.cv-msg.cv-error { background: var(--danger-soft); color: var(--danger); }

/* markdown 正文（.md-body 排版来自 update-notes.css）：收紧首尾与段间距，
   不让弹窗里留出面板级的大边距 */
.cv-msg.md-body > :first-child { margin-top: 0; }
.cv-msg.md-body > :last-child { margin-bottom: 0; }
.cv-msg.md-body p { margin: 6px 0; }
.cv-msg.md-body .md-pre { max-height: 240px; overflow: auto; }

/* 气泡内的图片（请求里的多模态段 / 工具结果） */
.cv-img {
  display: block;
  max-width: min(280px, 100%);
  max-height: 220px;
  margin: 6px 0 0;
  border: 1px solid var(--border);
  border-radius: 8px;
}

/* 折叠块：思考过程 / 工具结果 / 解析降级的原文 */
.cv-details { margin-top: 6px; min-width: 0; }
.cv-details > summary {
  cursor: pointer;
  color: var(--text-3);
  font-size: 11.5px;
  user-select: none;
}
.cv-details[open] > summary { margin-bottom: 4px; }
.cv-reasoning { color: var(--text-2); font-size: 12px; }

/* 工具结果：全宽虚线框，与对话气泡在视觉上区分开 */
details.cv-tool {
  max-width: 88%;
  padding: 6px 10px;
  border: 1px dashed var(--border-strong);
  border-radius: 10px;
  background: var(--surface-2);
  color: var(--text-2);
  font-size: 12px;
}

details.cv-tool > summary { cursor: pointer; color: var(--text-3); }

/* 工具调用块（助手气泡内）：标题 + 参数原文 */
.cv-toolcall { margin-top: 8px; }
.cv-toolcall-name { margin-bottom: 3px; color: var(--text-3); font-size: 11.5px; }
.cv-pre { max-height: 220px; font-size: 11px; }

/* 解析失败的说明行（原文折叠跟在它后面）/ 读取侧的截断提示 */
.cv-parse-empty { color: var(--text-3); font-size: 12px; }
.cv-truncated { margin-bottom: 8px; color: var(--warn); font-size: 11.5px; }
