/* ============================================================================
   汇客云支付 · 接口联调控制台
   ----------------------------------------------------------------------------
   本轮重做的两点：
   ① 整页【铺满视口】：100vw × 100vh，页面本身不滚动，
      左栏与右侧工作区各自内部滚动。原来内容区限宽 1080px，
      在宽屏上两侧大片留白，看着就"不满"。
   ② 视觉层次重排：顶部状态条 → 左栏（API 分组）→ 双栏工作区，
      三者边界清楚，深色只用在代码/报文块上，其余保持安静。
   ========================================================================== */

:root {
  --ink:        #14181d;
  --ink-soft:   #525a65;
  --ink-faint:  #878f9a;
  --paper:      #eceef1;
  --panel:      #ffffff;
  --rule:       #d6dae0;
  --rule-soft:  #e8ebee;
  --accent:     #1f4fd8;
  --accent-dim: #eaf0fd;
  --ok:         #0f7b45;
  --ok-bg:      #e7f4ed;
  --warn:       #96580a;
  --warn-bg:    #fbf2e2;
  --bad:        #b0261f;
  --bad-bg:     #fbeceb;
  --code-bg:    #11141a;
  --code-fg:    #dde4ec;

  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB",
          "Microsoft YaHei", sans-serif;

  --rail: 216px;
  --topbar: 46px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0; padding: 0;
  width: 100%; height: 100%;
  overflow: hidden;                 /* 页面本身不滚动，滚动交给内部容器 */
}

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 13.5px;
  line-height: 1.6;
  -webkit-text-size-adjust: 100%;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---------- 骨架：顶部状态条 + 左栏 + 工作区，合计铺满视口 ---------- */
.shell {
  display: flex;
  width: 100vw;
  height: 100vh;
  align-items: stretch;
}

.rail {
  width: var(--rail);
  flex: 0 0 var(--rail);
  height: 100%;
  overflow-y: auto;
  background: var(--panel);
  border-right: 1px solid var(--rule);
  padding: 16px 0 24px;
}
.rail__brand { padding: 0 16px 14px; border-bottom: 1px solid var(--rule-soft); }
.rail__brand b { display: block; font-size: 14.5px; letter-spacing: .2px; }
.rail__brand span { display: block; color: var(--ink-faint); font-size: 11.5px; margin-top: 1px; }

.rail__group {
  margin: 14px 0 3px; padding: 0 16px;
  font-size: 10.5px; font-weight: 700; color: var(--ink-faint); letter-spacing: .09em;
}
.rail__nav a {
  display: block; padding: 7px 16px;
  color: var(--ink-soft); border-left: 3px solid transparent;
}
.rail__nav a small {
  display: block; font-family: var(--mono); font-size: 10px;
  color: var(--ink-faint); font-weight: 400; margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rail__nav a:hover { background: var(--accent-dim); text-decoration: none; }
.rail__nav a.on {
  color: var(--ink); font-weight: 600;
  border-left-color: var(--accent); background: var(--accent-dim);
}
.rail__nav a.on small { color: var(--accent); }

.rail__note {
  margin: 18px 16px 0; padding-top: 12px;
  border-top: 1px solid var(--rule-soft);
  color: var(--ink-faint); font-size: 11.5px; word-break: break-all;
}
.rail__note code { font-family: var(--mono); display: block; margin-top: 2px; color: var(--ink-soft); }
.rail__note b { display: block; margin-top: 10px; font-weight: 600; color: var(--ink-faint); }

/* 工作区：占满剩余宽度，纵向自己滚 */
.main { flex: 1 1 auto; min-width: 0; height: 100%; display: flex; flex-direction: column; }

.topbar {
  flex: 0 0 auto;
  min-height: var(--topbar);
  display: flex; align-items: center; gap: 12px;
  padding: 0 20px;
  background: var(--panel);
  border-bottom: 1px solid var(--rule);
}
.topbar h1 { margin: 0; font-size: 15px; font-weight: 650; white-space: nowrap; }
.topbar .sub { color: var(--ink-faint); font-family: var(--mono); font-size: 11.5px;
               overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.topbar .spacer { flex: 1 1 auto; }

.work {
  flex: 1 1 auto; min-height: 0;
  overflow-y: auto;
  padding: 16px 20px 28px;
  /* 【滚动条槽位常驻】提交后结果面板出现，内容变高 → 滚动条冒出来 →
     可用宽度突然少 15px → 整个工作区连同选择框横向跳一下。
     这是"提交后窗口和选择框抖动"的另一半原因。
     预留槽位后，有没有滚动条布局都不变。
     不支持 scrollbar-gutter 的老浏览器由下面的 overflow-y:scroll 兜底。 */
  scrollbar-gutter: stable;
}
@supports not (scrollbar-gutter: stable) {
  .work { overflow-y: scroll; }
}

/* ---------- 双栏：左表单右结果，各自独立 ---------- */
.grid {
  display: grid;
  grid-template-columns: minmax(340px, 34%) minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}
.grid--single { grid-template-columns: minmax(0, 1fr); }
/* 右栏（结果区）给一个最小高度：提交前是提示文案、提交后是结果面板，
   两者高度差很大，不兜底的话提交瞬间整页高度突变、滚动位置跟着跳。 */
.grid > div:last-child { min-height: 220px; }

/* ---------- 面板 ---------- */
.panel { background: var(--panel); border: 1px solid var(--rule); border-radius: 5px; }
.panel + .panel { margin-top: 16px; }
.panel__head {
  padding: 9px 13px; border-bottom: 1px solid var(--rule-soft);
  font-weight: 600; display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
}
.panel__head small { font-weight: 400; color: var(--ink-faint); font-family: var(--mono); font-size: 11px; }
.panel__body { padding: 13px; }
.panel__body > :first-child { margin-top: 0; }
.panel__body > :last-child { margin-bottom: 0; }

/* ---------- 表单 ---------- */
.field { margin-bottom: 13px; }
.field > label { display: block; margin-bottom: 4px; font-weight: 600; }
/* 【抖动的真正大头：提示行的高度塌缩】
   .hint 空着时高度是 0，级联加载完往里塞"共 N 项　签名字段：…"就变成一行，
   下面所有字段一起往下跳。四级级联依次回来 = 表单连跳四次；
   提交后页面重载再走一遍，看着就是"窗口和选择框都在抖"。
   给它一个固定的最小高度，有没有文字布局都不变。 */
.field .hint {
  display: block; margin-top: 3px;
  color: var(--ink-faint); font-size: 11.5px;
  min-height: 17px; line-height: 17px;
}
.hint--bad { color: var(--bad); }
.hint--ok  { color: var(--ok); }

.input, .select, .textarea {
  width: 100%; padding: 7px 10px;
  border: 1px solid var(--rule); border-radius: 4px;
  background: #fff; font: inherit; color: var(--ink);
}
.input--mono, .textarea { font-family: var(--mono); }
.input:focus, .select:focus, .textarea:focus, .btn:focus-visible, .rail__nav a:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
}
.select {
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5L6 8l3.5-3.5' fill='none' stroke='%23525a65' stroke-width='1.4' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center; background-size: 12px;
  padding-right: 30px; cursor: pointer;
}
.select:disabled { background-color: #f3f4f6; color: var(--ink-faint); cursor: not-allowed; }

.row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 11px; }
.inline-row { display: flex; gap: 7px; align-items: flex-start; }
.inline-row .input { flex: 1 1 auto; min-width: 0; }
.inline-row .btn { flex: 0 0 auto; padding: 7px 11px; font-weight: 500; }

.btn {
  display: inline-block; padding: 8px 16px;
  border: 1px solid var(--accent); border-radius: 4px;
  background: var(--accent); color: #fff; font: inherit; font-weight: 600;
  cursor: pointer; transition: background .15s ease;
}
.btn:hover { background: #1740b4; }
.btn--ghost { background: #fff; color: var(--accent); }
.btn--ghost:hover { background: var(--accent-dim); }
.btn--block { display: block; width: 100%; }
/* 提交中：宽度已由 block 撑满，文字变化不会改变尺寸；
   非 block 的按钮统一给个最小宽度，避免"提交中…"比原文字短造成收缩。 */
.btn[disabled] { min-width: 88px; }
.btn[disabled] { opacity: .5; cursor: default; }

/* 级联链路：一条细线把四级串起来，看得出是依次收窄的 */
.chain { border-left: 2px solid var(--rule-soft); padding-left: 13px; margin-left: 3px; }
.chain .field { position: relative; }
.chain .field::before {
  content: ""; position: absolute; left: -16px; top: 10px;
  width: 8px; height: 2px; background: var(--rule-soft);
}
.chain .field.filled::before { background: var(--accent); }

/* ---------- 选择卡 ---------- */
.picks { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 7px; }
.picks--tall { max-height: 300px; overflow-y: auto; padding-right: 2px; }
.pick { display: block; border: 1px solid var(--rule); border-radius: 4px; padding: 7px 9px; background: #fff; }
.pick b { display: block; font-weight: 600; font-size: 12.5px; }
.pick span { display: block; font-family: var(--mono); font-size: 10.5px; color: var(--ink-faint);
             overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------- 状态 ---------- */
.tag { display: inline-block; padding: 1px 8px; border-radius: 3px;
       font-size: 11.5px; font-weight: 600; border: 1px solid transparent; }
.tag--ok   { color: var(--ok);   background: var(--ok-bg);   border-color: #bfe1cf; }
.tag--warn { color: var(--warn); background: var(--warn-bg); border-color: #ecd8ae; }
.tag--bad  { color: var(--bad);  background: var(--bad-bg);  border-color: #efc5c2; }
.tag--flat { color: var(--ink-soft); background: var(--rule-soft); border-color: var(--rule); }

.notice { border: 1px solid var(--rule); border-left-width: 3px; border-radius: 4px;
          padding: 9px 12px; margin-bottom: 13px; }
.notice--ok   { border-left-color: var(--ok);   background: var(--ok-bg); }
.notice--warn { border-left-color: var(--warn); background: var(--warn-bg); }
.notice--bad  { border-left-color: var(--bad);  background: var(--bad-bg); }
.notice b { display: block; margin-bottom: 2px; }
.notice ul { margin: 5px 0 0; padding-left: 18px; }
.notice code { font-family: var(--mono); background: rgba(0,0,0,.05); padding: 0 4px; border-radius: 3px; }

/* 顶部横幅：接口不通时全宽提示，不能让它藏在某个下拉框的小字里 */
.banner {
  flex: 0 0 auto;
  padding: 9px 20px;
  background: var(--bad-bg);
  border-bottom: 1px solid #efc5c2;
  color: var(--bad);
  font-size: 12.5px;
}
.banner b { font-weight: 700; }
.banner code { font-family: var(--mono); background: rgba(0,0,0,.06); padding: 0 4px; border-radius: 3px; }
.banner--warn { background: var(--warn-bg); border-bottom-color: #ecd8ae; color: var(--warn); }

/* ---------- 数据表 ---------- */
.kv { width: 100%; border-collapse: collapse; }
.kv th, .kv td { padding: 6px 0; border-bottom: 1px solid var(--rule-soft); text-align: left; vertical-align: top; }
.kv tr:last-child th, .kv tr:last-child td { border-bottom: 0; }
.kv th { width: 130px; font-weight: 500; color: var(--ink-soft); white-space: nowrap; padding-right: 12px; }
.kv td { font-family: var(--mono); word-break: break-all; }
.kv td.plain { font-family: var(--sans); }

/* ---------- 签名工作台 ---------- */
.sig { border: 1px solid var(--rule); border-radius: 4px; background: #fbfcfd; }
.sig__row { display: flex; gap: 12px; padding: 8px 11px; border-bottom: 1px solid var(--rule-soft); }
.sig__row:last-child { border-bottom: 0; }
.sig__k { flex: 0 0 88px; color: var(--ink-soft); }
.sig__v { flex: 1 1 auto; min-width: 0; font-family: var(--mono); word-break: break-all; }
.sig__src, pre.json {
  margin: 0; padding: 9px 11px; background: var(--code-bg); color: var(--code-fg);
  font-family: var(--mono); font-size: 11.5px; line-height: 1.65;
  border-radius: 4px; white-space: pre-wrap; word-break: break-all;
}
.sig__digest { font-family: var(--mono); font-size: 14px; letter-spacing: .5px; }

details.fold { border-top: 1px solid var(--rule-soft); margin-top: 11px; padding-top: 9px; }
details.fold > summary { cursor: pointer; font-weight: 600; color: var(--accent); }
details.fold > div { margin-top: 9px; }

.qr { display: block; max-width: 200px; border: 1px solid var(--rule); border-radius: 4px; }
.muted { color: var(--ink-faint); }
.mono  { font-family: var(--mono); word-break: break-all; }
.stack > * + * { margin-top: 10px; }

.big { font-family: var(--mono); font-size: 21px; font-weight: 600; letter-spacing: -.01em; }
.big small { font-size: 12.5px; font-weight: 400; color: var(--ink-faint); margin-left: 6px; }

.logline { border-bottom: 1px solid var(--rule-soft); padding: 7px 0;
           font-family: var(--mono); font-size: 11.5px; word-break: break-all; }
.logline:last-child { border-bottom: 0; }

.tasks { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
.task { display: block; border: 1px solid var(--rule); border-radius: 5px;
        background: var(--panel); padding: 13px 15px; color: inherit; }
.task:hover { border-color: var(--accent); text-decoration: none; }
.task b { display: block; font-size: 14px; margin-bottom: 2px; }
.task span { color: var(--ink-soft); }
.task code { font-family: var(--mono); font-size: 11px; color: var(--ink-faint); display: block; margin-top: 5px; }

/* ---------- 窄屏：左栏变顶部横向条，工作区仍占满剩余高度 ---------- */
@media (max-width: 900px) {
  .shell { flex-direction: column; }
  .rail {
    width: 100%; flex: 0 0 auto; height: auto; max-height: 40vh;
    border-right: 0; border-bottom: 1px solid var(--rule); padding: 10px 0 0;
  }
  .rail__brand { padding: 0 14px 8px; }
  .rail__group { display: none; }
  .rail__nav { display: flex; overflow-x: auto; }
  .rail__nav a { border-left: 0; border-bottom: 3px solid transparent; white-space: nowrap; padding: 7px 13px; }
  .rail__nav a.on { border-left: 0; border-bottom-color: var(--accent); }
  .rail__nav a small { display: none; }
  .rail__note { display: none; }
  .grid { grid-template-columns: minmax(0, 1fr); }
  .row2 { grid-template-columns: 1fr; }
  .work { padding: 13px 14px 24px; }
}

@media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } }

/* ============================================================================
   本轮：诊断从"横幅"改为"状态灯 + 弹窗"
   ----------------------------------------------------------------------------
   横幅摞在顶部会把工作区一路挤下去，内容越多越乱。
   现在平时只占顶栏一颗灯，出问题时弹窗给完整排查步骤 ——
   占地更少，给的信息反而更全。
   ========================================================================== */

.statuslight {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600; white-space: nowrap;
  border: 1px solid transparent; font-family: var(--sans);
}
.statuslight::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%; flex: 0 0 7px;
}
.statuslight--ok  { color: var(--ok);  background: var(--ok-bg);  border-color: #bfe1cf; }
.statuslight--ok::before  { background: var(--ok); }
.statuslight--bad { color: var(--bad); background: var(--bad-bg); border-color: #efc5c2; cursor: pointer; }
.statuslight--bad::before { background: var(--bad); }
.statuslight--bad:hover { background: #f7dedc; }

/* ---- 弹窗 ---- */
.modal {
  position: fixed; inset: 0; z-index: 200;
  display: none; align-items: center; justify-content: center;
  background: rgba(13, 17, 23, .58);
  padding: 24px;
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
}
.modal.open { display: flex; }
.modal__box {
  background: var(--panel); border-radius: 10px;
  width: min(660px, 100%); max-height: calc(100vh - 48px);
  display: flex; flex-direction: column; overflow: hidden;
  box-shadow: 0 18px 56px rgba(8, 12, 18, .34), 0 2px 6px rgba(8, 12, 18, .18);
}
.modal__head {
  padding: 15px 20px 13px;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  background: linear-gradient(180deg, #fff5f4 0%, #ffffff 100%);
  border-bottom: 1px solid var(--rule-soft);
}
.modal__head b {
  font-size: 16px; color: var(--bad);
  display: inline-flex; align-items: center; gap: 8px;
}
.modal__head b::before {
  content: "!";
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--bad); color: #fff; font-size: 13px; font-weight: 700;
}
.modal__x {
  border: 0; background: transparent; font-size: 24px; line-height: 1;
  color: var(--ink-faint); cursor: pointer; padding: 0 2px;
}
.modal__x:hover { color: var(--ink); }

.modal__body { padding: 16px 20px 4px; overflow-y: auto; }
.modal__lead {
  margin: 0 0 8px; font-weight: 650; font-size: 13.5px;
}
.modal__lead + .modal__lead { margin-top: 18px; }
.modal__detail {
  margin: 0 0 14px; padding: 10px 12px;
  background: var(--bad-bg); border: 1px solid #f0c9c6;
  border-left: 3px solid var(--bad); border-radius: 5px;
  color: #8f1e18; font-family: var(--mono); font-size: 12px;
  line-height: 1.6; word-break: break-all;
}
.modal__steps { margin: 0 0 12px; padding-left: 0; list-style: none; counter-reset: st; }
.modal__steps li {
  position: relative; padding-left: 28px; margin-bottom: 9px; line-height: 1.65;
}
.modal__steps li::before {
  counter-increment: st; content: counter(st);
  position: absolute; left: 0; top: 1px;
  width: 19px; height: 19px; border-radius: 50%;
  background: var(--accent-dim); color: var(--accent);
  font-size: 11px; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
}
.modal__body code {
  font-family: var(--mono); font-size: 11.5px;
  background: var(--rule-soft); padding: 1px 5px; border-radius: 3px;
}
.modal__foot-note { margin: 0 0 12px; color: var(--ink-faint); font-size: 12px; }
.modal__foot {
  padding: 13px 20px; border-top: 1px solid var(--rule-soft);
  background: #fafbfc; text-align: right;
}
.modal__foot .btn { text-decoration: none; }
.modal__foot .btn:hover { text-decoration: none; }

/* 顶栏的凭据指示 + 一键换密钥。
   后台切了沙箱测试商户之后，"当前在用哪把 AppKey"必须一眼可见 ——
   上一版没有这个显示，切了商户看不出没生效，只能靠猜。 */
.statuslight--key {
  color: var(--ink-soft); background: var(--rule-soft); border-color: var(--rule);
  font-family: var(--mono); font-weight: 500; text-decoration: none;
  max-width: 300px; overflow: hidden; text-overflow: ellipsis;
}
.statuslight--key::before { background: var(--ink-faint); }
.statuslight--key:hover { background: var(--accent-dim); color: var(--accent); text-decoration: none; }
.statuslight--key:hover::before { background: var(--accent); }

/* ============================================================================
   本轮 UI 精修
   ----------------------------------------------------------------------------
   上一版骨架是对的（满视口、左栏分组、双栏工作区），但细节偏毛糙：
   面板头和内容缺层次、级联链看不出进度、选中态太弱、结果区平铺一片。
   这一版不改结构，只补层次与状态反馈。
   ========================================================================== */

/* ---- 左栏：加一条极细的内阴影把它和工作区分开，比单纯一条 1px 线更立体 ---- */
.rail { box-shadow: inset -1px 0 0 rgba(20,25,35,.04); }
.rail__brand b { letter-spacing: -.01em; }
.rail__nav a { transition: background .12s ease, color .12s ease; }
.rail__nav a.on { box-shadow: inset 0 0 0 1px rgba(31,79,216,.06); }

/* ---- 顶栏：轻微的下沉感，滚动时内容不会显得"贴"上去 ---- */
.topbar { box-shadow: 0 1px 0 rgba(20,25,35,.04); }
.topbar h1 { letter-spacing: -.01em; }

/* ---- 面板：头部改为浅底，与内容区分层 ---- */
.panel { box-shadow: 0 1px 2px rgba(20,25,35,.04); }
.panel__head {
  background: linear-gradient(180deg, #fcfdfe 0%, #f8f9fb 100%);
  border-radius: 5px 5px 0 0;
  letter-spacing: -.005em;
}
.panel--lead { box-shadow: 0 2px 6px rgba(20,25,35,.07); }

/* ---- 级联链：走到哪一级一眼可见 ---- */
.chain { position: relative; }
.chain .field::before {
  width: 9px; height: 9px; border-radius: 50%;
  left: -18px; top: 9px;
  background: #fff; border: 2px solid var(--rule);
  transition: border-color .15s ease, background .15s ease;
}
.chain .field.filled::before { background: var(--accent); border-color: var(--accent); }
.chain .field > label { display: flex; align-items: baseline; gap: 6px; }
.chain .field > label .muted { font-weight: 400; font-size: 11.5px; }

/* 已选中的下拉：左侧一条主色，扫一眼就知道哪几级已经定了 */
.select:not(:disabled):not([data-empty="1"]) { box-shadow: inset 2px 0 0 transparent; }
.field.filled .select { box-shadow: inset 2px 0 0 var(--accent); background-color: #fdfefe; }

/* ---- 输入与按钮：统一过渡，focus 更明确 ---- */
.input, .select, .textarea { transition: border-color .12s ease, box-shadow .12s ease; }
.input:hover, .select:hover:not(:disabled), .textarea:hover { border-color: #b9c0c9; }
.btn { letter-spacing: -.005em; box-shadow: 0 1px 1px rgba(20,25,35,.06); }
/* 【本轮删除 transform: translateY(1px)】
   按下时整个按钮位移 1px，配合下面 initGuard 改按钮文字，
   点一下提交就会看到按钮跳一下、旁边的选择框跟着重排 —— 就是"抖动"。
   改成只变背景色：有按下反馈，但不触发任何重排。 */
.btn:active { background: #12369c; }
.btn--ghost:active { background: #dde6fb; }
.btn--ghost { box-shadow: none; }

/* ---- 结果区：关键数字更醒目，键值表更透气 ---- */
.big { letter-spacing: -.02em; }
.kv th { font-size: 12.5px; }
.kv td { font-size: 12.5px; }
.kv tr:hover td, .kv tr:hover th { background: #fafbfc; }

/* ---- 签名工作台：参与字段用主色点出来，和普通行区分 ---- */
.sig { overflow: hidden; }
.sig__row:first-child { background: var(--accent-dim); }
.sig__row:first-child .sig__k { color: var(--accent); font-weight: 600; }
.sig__digest { color: var(--accent); }

/* ---- 提示行：错误态加一个前缀点，不只靠颜色（色觉障碍下也能分辨）---- */
.hint--bad::before {
  content: "●"; margin-right: 4px; font-size: 9px; vertical-align: 1px;
}

/* ---- 选择卡列表：hover 抬升 ---- */
.pick { transition: border-color .12s ease, box-shadow .12s ease; }
.pick:hover { box-shadow: 0 1px 3px rgba(20,25,35,.08); }

/* ---- 折叠面板 ---- */
details.fold > summary { padding: 2px 0; user-select: none; }
details.fold[open] > summary { margin-bottom: 2px; }

/* ---- 首页任务卡 ---- */
/* 同理：hover 位移会让整排卡片轻微跳动，改为只加阴影 */
.task { transition: border-color .12s ease, box-shadow .12s ease; }
.task:hover { box-shadow: 0 2px 8px rgba(20,25,35,.08); }



/* ==========================================================================
   卡片选择器（支付方式 / 通道 / 主账户 / 子账户）
   --------------------------------------------------------------------------
   【替代原来的 <select>】原来把「名称（编码）· 额度摘要」整条塞进 option：
     · 原生下拉不换行、超出直接截断 —— 后半段额度信息根本看不见；
     · option 里不能上色 —— "额度已用尽"和"额度充足"长得一模一样；
     · 多个账户无法逐行对齐 —— 而选账户这件事本质就是在横向比较。

   【尺寸】一张卡约 66px 高：标题一行 + 正文两个视觉行
     （单笔整行跨列，累计/当日并排）。上一版是 2×2 四格、约 70px，
     本轮删掉"本单是否可收"那一格后又矮一档。
     额度用紧凑记法 剩余/上限，完整的"上限/已用/剩余"进 title 悬浮提示；
     单位"元"只在图例里出现一次，不在每格重复（宽度省约三成）。
     信息一条没删，只是把可推导的（已用）和可复述的（单位）挪出了版面。

   【每行多张】容器用 grid + auto-fill，列数随可用宽度自动增减 ——
   卡片选择器的初衷就是横向比较几个账户，每行只有一张等于白改。
   ========================================================================== */

.cards {
  /* ======================================================================
     【本轮改动 · 从多列改回网格，并把尺寸调到真的排得下多张】
     ----------------------------------------------------------------------
     上一版用 column-width:210px 做多列，理由是"网格的行高由该行最高的
     卡片决定，矮卡片下面会空一块"。方向是对的，但那一版【实际仍然
     每行只有一张】—— 卡片容器在左栏 .grid 的 minmax(340px,34%) 里，
     减去 .panel__body 的 13px×2 内边距，可用宽约 390px；
     而两列需要 210×2+6 = 426px，差 36px，于是永远只排得下一列。
     参数与容器宽度不匹配，多列写了等于没写。

     这一版做三件事让"每行多个"真正成立：
       ① 改用 grid + auto-fill，列数由容器宽度自动决定；
       ② 最小列宽压到 148px —— 正文由四行减到三行（删掉"本单"那一格）
          之后，卡片内容本来就不需要 210px；
       ③ 矮卡片（"不指定"那张）改为拉伸填满，不再留半截空白 ——
          这正是上一版避开网格的唯一理由，用 align-items 就能解决，
          不必为此放弃阅读顺序。

     【为什么阅读顺序值得换回网格】多列是"先下后右"，
     一列候选项读起来是竖着走的；网格是"先右后下"，与人扫视列表的
     习惯一致，也与"横向比较几个账户"这个初衷一致。
     ====================================================================== */
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(142px, 1fr));
  gap: 6px;
  align-items: stretch;
  margin-top: 2px;
}
.cards--off { opacity: .55; pointer-events: none; }

.cards-empty {
  grid-column: 1 / -1;
  padding: 7px 10px;
  border: 1px dashed var(--rule);
  border-radius: 6px;
  color: var(--ink-faint);
  background: #fafbfc;
  font-size: 12.5px;
}

/* 图例：整行跨列，单位与记法只说明一次。 */
.cards-legend {
  grid-column: 1 / -1;
  font-size: 11px;
  color: var(--ink-faint);
  line-height: 1.4;
  margin-top: 1px;
}

.card {
  border: 1px solid var(--rule);
  border-radius: 6px;
  background: var(--panel);
  padding: 6px 8px;
  cursor: pointer;
  /* 【纵向排布 + 正文吃掉剩余高度】同一行里几张卡片被拉到等高，
     若不这样，正文会贴着标题挤在顶部、底下留一道空白，
     看着像"这张卡的数据没加载全"。 */
  display: flex;
  flex-direction: column;
  min-width: 0;
  margin: 0;
  /* 【只过渡颜色类属性】过渡 box-shadow/transform 会在重排时
     让整片卡片一起动，看着像页面在抖。 */
  transition: border-color .12s, background-color .12s;
}
.card:hover { border-color: #b9c0ca; background: #fcfdfe; }
.card:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* 选中态：左侧色条 + 淡蓝底。
   【不用纯色填充】填充会把卡片里那几个额度数字的对比度压下去，
   而那几个数字正是用户要读的东西。 */
.card--on {
  border-color: var(--accent);
  background: var(--accent-dim);
  box-shadow: inset 3px 0 0 0 var(--accent);
}

/* "不指定（自动选号）"那张。做成卡片而不是另一个控件，
   是为了让"不选"本身也可见、可点、可被键盘选中 —— 否则选了就没有回头路。
   【它只有一行标题】网格里会被拉到与同行卡片等高，所以把标题垂直居中，
   否则一行字顶在上面、下面一大片空，看起来像渲染坏了。 */
.card--ph {
  border-style: dashed;
  color: var(--ink-soft);
  justify-content: center;
  padding: 5px 8px;
}
.card--ph .card-title { font-weight: 500; }
/* 选中"不指定"时保留虚线语义：它仍然是"没有指定"，
   不该看起来和选中了某个具体账户一样。 */
.card--ph.card--on { border-style: dashed; }

/* 卡片头部：【固定两行】第 1 行标题 + 徽章，第 2 行编码。
   ------------------------------------------------------------------
   上一版是一个自由换行的 flex 流：标题、徽章、编码挤在一起，
   宽度够就一行、不够就换行。实测（headless Chrome，1100px 视口）
   同一组卡片的头部高度是 18 / 36 / 78px 三种 —— 而网格里同一行的
   卡片会被拉到最高那张的高度，于是【一张卡的徽章把整行都撑高了】，
   参差不齐正是"不美观"的主因。

   改为确定性布局：标题单行截断（完整名字进 title 悬浮提示），
   编码固定占第二行。每张卡的头部高度从此一模一样，
   同行卡片自然等高，不再有人被别人撑高。 */
.card-head {
  display: flex;
  align-items: baseline;
  gap: 4px;
  min-width: 0;
}
.card-title {
  font-weight: 600;
  font-size: 13px;
  line-height: 1.35;
  color: var(--ink);
  /* 【单行截断而不是换行】名字长短不一时换行会让卡片高度参差；
     截断则高度恒定，完整名字仍可由悬浮提示读到。 */
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.card-code {
  display: block;
  font-family: var(--mono);
  font-size: 10.5px;
  line-height: 1.35;
  color: var(--ink-faint);
  /* 编码独占第二行：它是用来核对账号的，截断了就失去意义，
     所以宁可给它一整行也不与标题抢位置。 */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.badge {
  font-size: 10px;
  line-height: 1.5;
  padding: 0 5px;
  border-radius: 8px;
  border: 1px solid transparent;
  white-space: nowrap;
  /* 【不参与压缩】标题是 flex:1 1 auto，会主动让位；徽章必须保持原样，
     被压扁的徽章既读不出内容又占着地方。 */
  flex: 0 0 auto;
}
.badge--bad   { color: var(--bad);  background: var(--bad-bg);  border-color: #efc5c2; }
.badge--warn  { color: var(--warn); background: var(--warn-bg); border-color: #ecd8ae; }
.badge--good  { color: var(--ok);   background: var(--ok-bg);   border-color: #bfe1cf; }
.badge--plain { color: var(--ink-soft); background: #f2f4f6; border-color: var(--rule-soft); }

/* 正文：单笔整行跨列 + 累计/当日视卡片宽度并排或堆叠。
   【本轮删掉"本单"那一格后只剩三项】三项塞进写死的 2×2 会空出一格，
   看着像少了数据，所以改成"单笔跨整行 + 两个额度格"。

   【auto-fit 而不是写死 1fr 1fr】卡片宽度随外层列数变化：
   外层排得下三列时每张卡才 ~150px，此时两个额度格各只剩 ~70px，
   而 "98.99/100.00" 用 11px 等宽字排出来约 79px、加上"累计"标签与间距
   共约 105px —— 必折行，一折就把卡片顶高一整行、还破坏同行卡片的对齐。
   auto-fit 让【卡片自己】决定：窄了就堆成一列，宽了自动并排，
   不需要容器查询，也不必为此把外层列数写死。

   【104px 这个数是量出来的，不是拍的】用现场真实数值
   （ceshi 72.99/100.00、一梦 98.99/100.00 · 9.00/10.00）在
   headless Chrome 里逐档实测：88px 时 1440px 视口下有 4 处折行，
   提到 104px 后七档视口全部零折行。 */
.card-body {
  margin-top: 4px;
  border-top: 1px solid var(--rule-soft);
  padding-top: 3px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
  column-gap: 8px;
  /* 等高卡片里，正文吃掉标题之外的全部高度，不在底部留空 */
  flex: 1 1 auto;
  align-content: start;
}

.card-row {
  display: flex;
  align-items: baseline;
  gap: 4px;
  font-size: 11.5px;
  line-height: 1.45;
  min-width: 0;
}
.card-k { color: var(--ink-faint); white-space: nowrap; }
.card-v {
  color: var(--ink-soft);
  /* 数字用等宽：同一列的"98.99/100.00"与"0.00/10.00"能对齐，
     扫一眼就能比大小，这正是卡片要解决的事。 */
  font-family: var(--mono);
  font-size: 11px;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* 额度状态上色。'plain' 不着色：全都有颜色等于全都没颜色。 */
.card-row--good    .card-v { color: var(--ok); }
.card-row--warn    .card-v { color: var(--warn); font-weight: 600; }
.card-row--bad     .card-v { color: var(--bad); font-weight: 600; }
.card-row--unknown .card-v { color: var(--ink-faint); font-style: italic; }

/* 无标签行：整行跨两列。留一个空标签列会在卡片左边挖出一道空槽，
   看起来像"这一项的名字没加载出来"。 */
.card-row--nokey { grid-column: 1 / -1; }  /* 卡片【内部】仍是网格，这条不受影响 */
.card-row--nokey .card-k { display: none; }

/* 【整行跨列】单笔金额区间形如 "1.00 ~ 500.00"，比两个额度格都宽；
   塞进半格必然折行，而折行会把卡片顶高一整行、还破坏同行对齐。
   由数据里的 wide 标记驱动（见 app.js rowEl），不按位置写死。 */
.card-row--wide { grid-column: 1 / -1; }

/* 【只有真窄屏才收成单列】上一版的断点定在 640px，但页面 .grid 在 900px
   就已经塌成单栏、卡片容器反而变宽（实测 640px 视口下容器有 551px）——
   在一个 551px 宽的容器里每行只放一张，是把版面白白浪费掉。
   改为按【容器真的放不下两张】来定：420px 以下才强制单列。
   420 ~ 900px 之间交给 auto-fill 自行决定，不写死。 */
@media (max-width: 420px) {
  .cards { grid-template-columns: minmax(0, 1fr); }
}

/* 打印时保留选中态：截图/存档要看得出当时选了哪个。 */
@media print {
  .card { break-inside: avoid; }
  .card--on { border-width: 2px; }
}
