/* ─────────────────────────────────────────────
   请求日志页：转发明细表（列形态参考 OmniProxy 的请求日志）
   列表容器与筛选行（.log-list / .log-empty / .log-filters / .log-pager）
   沿用 page-logs.css 的同一套声明，本文件只写表格本身与窄窗口重排。
   颜色一律取 tokens.css 的语义变量，浅色深色共用同一组声明。
   ───────────────────────────────────────────── */

/* 九列：时间 / 提供商·账号 / 重试 / 状态 / 模型 / 用时 / 用量 / 错误 / 详情。
   时间与用时定宽（等宽数字 + tabular-nums，竖着比对才齐），
   其余占弹性宽度（都是不定长内容，给固定宽度会互相挤）：
   模型 1.3fr（claude-sonnet-4-5 这类名字要放得下），
   用量 1.6fr（两行读数里 in/out/all 最长），超出由省略号收住、完整值进 title。
   详情列定宽（只有一个「详情」按钮或一个「-」）。

   --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 68px 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); }

/* 时间列：日期 + 时刻两行，等宽避免每一行的冒号错位。
   数据格与表头格共用同一个类名（窄窗口要按列重排，见文件末尾的媒体查询），
   所以每一条排版声明都带 .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 {
    grid-template-columns: 92px minmax(0, 1fr) 68px 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%); }

.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 会让人以为它是通用语义色。
   浅深两套各写一份：同一组紫在浅底与深底上需要的明度不同。 */
.req-hover-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 {
    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;
}
