/**
 * @file 统一门户样式:布局、卡片、表单、看板与状态徽标
 * @author wuna03
 */

:root {
    --bg: #f5f6f8;
    --card: #fff;
    --ink: #1f2430;
    /* --muted 原为 #6b7280:白底 5.03 勉强过,但它大量用在 var(--bg)(#f5f6f8)与
     * 行悬停底色(#f0f4ff)上 —— 实测那两处只有 4.47 / 4.39,【低于 AA 的 4.5】。
     * 承载的是凭证字段的 label、登录页邮箱后缀、悬停行里的等宽详情串这些真内容,
     * 不是装饰。压深到 #5b6472:白底 5.98、--bg 上 5.53、悬停底 5.44,三处都过。 */
    --muted: #5b6472;
    /* --muted-2 只给 11px 及以下的次级小字。原来那三处写死 #9ca3af,白底仅 2.54 —— 
     * 而它们承载的是数据(归一前的原始模型串、命中率的第二口径、Key 历史的「未填」)。 */
    /* 取原来的 --muted 值:白底 5.03(过 AA),又明显比新的 --muted 浅,层次不丢。 */
    --muted-2: #6b7280;
    --line: #e5e7eb;
    /* --line 1.24:1 用作【控件边框】不合格(WCAG 1.4.11 要求 3:1):
     * input / select / textarea / .copy / .seg 全用它,后果是「哪块能输入」看不出来,
     * 在 Key 页那种「一行里若干框若干按钮」的操作列里尤其难辨。
     * 纯分隔线(表格底边、.kv 分隔)不受 1.4.11 约束,继续用 --line。 */
    --line-ctl: #8b929f;
    --brand: #3b5bdb;
    --brand-ink: #fff;
    --alert: #c0392b;
}

* {
    box-sizing: border-box;
}

body {
    margin: 0;
    font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
    color: var(--ink);
    background: var(--bg);
}

a {
    color: var(--brand);
    text-decoration: none;
}

code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* 顶栏 */
.topbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 24px;
    padding: 6px 24px;
    /* 从固定 height 改成 min-height:导航换行时顶栏跟着长高,而不是把链接裁掉。
     * 装得下时视觉与原来一致(56px)。 */
    min-height: 56px;
    background: var(--card);
    border-bottom: 1px solid var(--line);
}

.brand {
    font-weight: 700;
    letter-spacing: .3px;
}

.nav {
    display: flex;
    gap: 4px;
    flex: 1;
    min-width: 0;
    /* ⛔【原方案是「不折行 + 横向滚动 + 隐藏滚动条」,那等于静默裁掉】
     * 管理员有 10 项,按 ≤1180 档的尺寸实测合计约 776px;而 1024px 窗口下 nav 只拿到
     * 约 714px —— 此时 topbar 还没换行、滚动条又是隐藏的,于是「运维」这一项
     * (最不常点但最要紧)被【无声无息】切掉,页面上没有任何线索说右边还有东西。
     * 触屏上更糟:iOS/Android 是 overlay 滚动条,静止时根本不渲染。
     * 还有一条:浏览器不会把 overflow 容器自动滚到 .active,所以停在「运维」页时
     * scrollLeft=0,用户看到的是一条【没有任何高亮】的导航,读成「没选中任何页」。
     *
     * 现在允许换行。原注释担心的「在 56px 固定高里被裁一半」由 .topbar 的
     * min-height + height:auto 解决 —— 宁可顶栏高一点,也不要有入口消失。 */
    flex-wrap: wrap;
    row-gap: 4px;
}

.nav a {
    padding: 8px 14px;
    border-radius: 8px;
    color: var(--muted);
    white-space: nowrap;
    flex: none;
}

.nav a:hover {
    background: var(--bg);
    color: var(--ink);
}

.nav a.active {
    background: var(--brand);
    color: var(--brand-ink);
}

.user {
    display: flex;
    align-items: center;
    gap: 12px;
    color: var(--muted);
    font-size: 14px;
}

.user .logout {
    color: var(--muted);
}

/* 内容区
 * 宽屏下正文限宽,避免两侧大片留白又不至于把表格挤窄:
 * 默认 1200px;数据/表格密集的页面(模型/用户/Key/用量)加 .content.wide 放宽到 1440px。 */
.content {
    max-width: 1200px;
    margin: 24px auto 40px;
    padding: 0 24px;
}

.content.wide {
    max-width: 1440px;
}

h1 {
    font-size: 22px;
    margin: 0 0 6px;
}

h2 {
    font-size: 16px;
    margin: 22px 0 8px;
}

.hint {
    color: var(--muted);
    margin: 0 0 18px;
}

.card {
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 12px;
    padding: 18px 20px;
    box-shadow: 0 1px 2px rgba(16, 24, 40, .04);
}

/* 键值行 */
.kv {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 0;
}

.kv + .kv {
    border-top: 1px solid var(--line);
}

.kv .k {
    width: 88px;
    color: var(--muted);
}

.kv .v {
    flex: 1;
    background: var(--bg);
    padding: 8px 10px;
    border-radius: 8px;
    overflow: auto;
    white-space: nowrap;
}

.copy {
    border: 1px solid var(--line);
    background: var(--card);
    border-radius: 8px;
    padding: 6px 12px;
    cursor: pointer;
}

.copy:hover {
    border-color: var(--brand);
    color: var(--brand);
}

.danger {
    border: 1px solid #f5c6cb;
    background: var(--card);
    color: var(--alert);
    border-radius: 8px;
    padding: 6px 12px;
    cursor: pointer;
}

.danger:hover {
    background: #fdecea;
    border-color: var(--alert);
}

.snippet {
    background: #0d1117;
    color: #e6edf3;
    padding: 14px 16px;
    border-radius: 10px;
    overflow: auto;
    margin: 0 0 8px;
}

/* 看板栅格:自适应列数,窄屏自动换行,避免固定 4 列在窄屏溢出、宽屏留白 */
.grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 14px;
    margin: 16px 0;
}

.stat {
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 12px;
    padding: 16px;
    box-shadow: 0 1px 2px rgba(16, 24, 40, .04);
}

.stat .label {
    color: var(--muted);
    font-size: 13px;
}

.stat .num {
    font-size: 26px;
    font-weight: 700;
    margin-top: 4px;
    /* 数字等宽:多张卡片的数值纵向对齐,数字变化时不再左右跳动。 */
    font-variant-numeric: tabular-nums;
    /* 兜底:金额位数多时(如全员长区间的 $1192.23)允许在卡片内折行,
       而不是横向溢出卡片边界。Go 侧的 usdAmount 已经把小数位收敛过一轮,
       这里是第二道防线 —— 别指望「数字不会那么长」。 */
    overflow-wrap: anywhere;
}

/* .num 里的单位后缀(如耗时分位数的 "ms")。
   【为什么必须有这条】logs.html 的 p50/p95 卡片输出 <span class="unit">ms</span>,
   而此前 CSS 里根本没有 .unit 规则,于是单位继承 .num 的 26px 粗体,
   渲染成「1823ms」整串同样大 —— 看起来像数值的一部分。 */
.stat .num .unit {
    margin-left: 2px;
    color: var(--muted);
    font-size: 13px;
    font-weight: 500;
}

.chart {
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 12px;
    padding: 16px;
    margin: 14px 0;
    box-shadow: 0 1px 2px rgba(16, 24, 40, .04);
}

.chart h3 {
    margin: 0 0 10px;
    font-size: 15px;
}

/* 趋势图:标题 + 指标切换段控 一行摆放 */
.chart-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 6px;
}

.chart-head h3 {
    margin: 0;
}

/* 同一张卡片里并排两张图(「系统层错误」页的失败趋势:不去重 / 按人去重)。
   【不能复用裸 .grid】.grid 是 auto-fit minmax(180px,1fr):两个子元素放进去会被排到
   两个 180px 的窄列里,SVG 缩到看不出形状 —— 而页面结构完全正常,只是图变小了。
   窄屏(<=900px)退回单列上下摆:两张 720px 视口的 SVG 挤在半个手机屏里都读不了。 */
.grid.two {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (max-width: 900px) {
    .grid.two {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* 并排图各自的小标题。必须写明口径 —— 两张图形状相近时,只有这行字能区分它们。 */
.chart-sub {
    margin: 0 0 6px;
    font-size: 13px;
    font-weight: 600;
    color: var(--muted);
}

.seg {
    display: inline-flex;
    border: 1px solid var(--line);
    border-radius: 8px;
    overflow: hidden;
}

.seg-btn {
    padding: 6px 12px;
    font-size: 13px;
    color: var(--muted);
    background: var(--card);
    cursor: pointer;
    white-space: nowrap;
}

.seg-btn + .seg-btn {
    border-left: 1px solid var(--line);
}

.seg-btn:hover {
    background: var(--bg);
    color: var(--ink);
}

.seg-btn.on {
    background: var(--brand);
    color: var(--brand-ink);
}

/* 图表悬停:柱子高亮 + 跟随光标的数值浮层 */
.chart-body {
    position: relative;
}

.bar-chart .bar {
    transition: fill .12s;
}

.bar-chart .bar:hover {
    fill: #2f49b0;
}

.chart-tip {
    position: absolute;
    z-index: 20;
    pointer-events: none;
    background: #1f2430;
    color: #fff;
    border-radius: 7px;
    padding: 6px 9px;
    font-size: 12px;
    line-height: 1.35;
    box-shadow: 0 6px 18px rgba(16, 24, 40, .28);
    white-space: nowrap;
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.chart-tip-k {
    color: #c7cdd8;
    font-size: 11px;
}

.chart-tip-v {
    font-weight: 700;
}

table {
    width: 100%;
    border-collapse: collapse;
}

th,
td {
    text-align: left;
    padding: 9px 10px;
    border-bottom: 1px solid var(--line);
    vertical-align: middle;
}

th {
    color: var(--muted);
    font-weight: 600;
    font-size: 13px;
    border-bottom: 2px solid var(--line);
}

/* 斑马纹 + 悬停高亮,长表格更易横向对齐扫读 */
tbody tr:nth-child(even) {
    background: #fafbfc;
}

tbody tr:hover {
    background: #f0f4ff;
}

tbody tr:last-child td {
    border-bottom: none;
}

/* 登录页 */
.login-body {
    display: flex;
    min-height: 100vh;
    align-items: center;
    justify-content: center;
    /* 卡片是全站唯一一处硬像素宽,溢出时必须有余量兜住(见 .login-card)。 */
    padding: 24px 16px;
}

.login-card {
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 16px;
    padding: 32px;
    /* ⛔【原来是写死 width:380px,没有 max-width】375px 手机上卡片超出 5px、
     * 320px 上超出 60px;而 justify-content:center 在溢出时会把左侧推进【负偏移】,
     * 横向滚动只能往右 —— 被裁掉的那半永远拿不回来。
     * 这是全员必经的第一个页面,也是全站唯一一处硬宽像素。 */
    width: min(380px, 100%);
    box-shadow: 0 8px 30px rgba(0, 0, 0, .06);
}

.login-card h1 {
    margin: 0;
}

.login-alternative {
    margin-top: 20px;
    border-top: 1px solid var(--line);
    padding-top: 16px;
}

.login-alternative summary {
    cursor: pointer;
    color: var(--muted);
    font-size: 14px;
}

.login-alternative summary:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 4px;
}

.login-alternative[open] summary {
    margin-bottom: 16px;
}

.subtitle {
    color: var(--muted);
    margin: 4px 0 20px;
}

.panel {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

label {
    display: flex;
    flex-direction: column;
    gap: 6px;
    font-size: 14px;
    color: var(--muted);
}

input {
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-radius: 8px;
    font-size: 15px;
}

/* 登录页:邮箱前缀 + 固定 @baidu.com 后缀 */
.email-group {
    display: flex;
    align-items: stretch;
    border: 1px solid var(--line);
    border-radius: 8px;
    overflow: hidden;
    background: var(--card);
}

.email-group:focus-within {
    border-color: var(--brand);
}

.email-group input {
    flex: 1;
    min-width: 0;
    border: none;
    border-radius: 0;
}

.email-group input:focus {
    outline: none;
}

.email-group .suffix {
    display: flex;
    align-items: center;
    padding: 0 12px;
    background: var(--bg);
    border-left: 1px solid var(--line);
    color: var(--muted);
    font-size: 14px;
    white-space: nowrap;
}

/* 固定前缀(如模型别名统一的 gbu-):与 .suffix 对称,分隔线在右侧 */
.email-group .prefix {
    display: flex;
    align-items: center;
    padding: 0 12px;
    background: var(--bg);
    border-right: 1px solid var(--line);
    color: var(--muted);
    font-size: 14px;
    white-space: nowrap;
}

.primary {
    background: var(--brand);
    color: var(--brand-ink);
    border: none;
    border-radius: 8px;
    padding: 11px;
    font-size: 15px;
    cursor: pointer;
}

.login-card a.primary {
    display: block;
    text-align: center;
    text-decoration: none;
}

.alert {
    background: #fdecea;
    color: var(--alert);
    border: 1px solid #f5c6cb;
    border-radius: 8px;
    padding: 10px 12px;
    margin-bottom: 14px;
    font-size: 14px;
}

.iframe-wrap {
    position: relative;
    height: calc(100vh - 120px);
    border: 1px solid var(--line);
    border-radius: 12px;
    overflow: hidden;
}

.iframe-wrap iframe {
    width: 100%;
    height: 100%;
    border: 0;
}

/* 状态徽标 */
.badge {
    display: inline-block;
    padding: 2px 10px;
    border-radius: 999px;
    font-size: 13px;
    font-weight: 600;
    /* ⛔【必须 nowrap】(2026-09-11 修)胶囊是圆角实心底,一断行就变成两个半截的
     * 圆角块摞在一起 ——「已激活」在 Key 管理页实测被折成「已激」/「活」两行,
     * 读者第一反应是页面坏了。它是徽标而不是段落,任何宽度下都该是完整一颗。
     * 出现的条件是「这一列被邻列挤窄」,所以它在别的页面上看不出来:
     * 单看一页都正常,只有最拥挤的那张表会翻车。 */
    white-space: nowrap;
}

.badge.ok {
    background: #e6f4ea;
    color: #1e7e34;
}

.badge.bad {
    background: #fdecea;
    color: var(--alert);
}

/* 介于 ok 与 bad 之间的第三种结果(调用日志里的「被中断」)。 */
.badge.warn {
    background: #fef3c7;
    color: #92400e;
}

/* 企业折扣徽标(模型广场 / 模型管理的「企业折扣」列)。刻意用中性蓝而不是绿色:
   绿色在本仓库固定表示「可用/成功」,给折扣也上绿色会让「无折扣」的行看起来像有问题。 */
.badge.disc {
    background: #e8f0fe;
    color: #1a56c4;
}

/* 折前价(原价):划掉 + 弱化,与同格下一行的折后价形成「原价 → 现价」的读法。
   【必须比正文小一号且是 muted】否则两个数字等重,读者无法判断哪个才是要付的钱。 */
s.list {
    display: inline-block;
    color: var(--muted);
    font-size: 11px;
    text-decoration-thickness: 1px;
}

.mono-detail {
    color: var(--muted);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px;
}

/* 提示条 + 管理表单 */
.notice {
    background: #eef2ff;
    color: #1e3a8a;
    border: 1px solid #c7d2fe;
    border-radius: 8px;
    padding: 10px 12px;
    margin: 0 0 16px;
    font-size: 14px;
}

/* 警示态提示条(如 Key 未激活):琥珀色,比普通 notice 更醒目 */
/* 操作【失败】的结果提示。与 .notice.warn 的区别是语义等级,不是深浅:
 * warn = 「做成了,但有一半没成」;bad = 「这次操作没有生效」。
 * 之前两者和成功提示共用中性蓝框,于是「旧 Key 已吊销,但新 Key 发放失败,请手动补发」
 * 与「已成功轮换」在管理员眼里长得一模一样 —— 而前者意味着某个用户的 Key 被静默吊销、
 * 需要人工补发。分级由 server.go 的 flashLevel 给出,与 stdout 日志同一个判据。 */
.notice.bad {
    background: #fef2f2;
    border-color: #fecaca;
    color: #991b1b;
}

.notice.warn {
    background: #fff7ed;
    color: #9a3412;
    border-color: #fed7aa;
}

.form-row {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-items: center;
    margin-top: 6px;
}

.form-row input,
.form-row select {
    flex: 1;
    min-width: 160px;
}

.form-row .primary {
    flex: 0 0 auto;
    min-width: 88px;
}

.form-inline {
    display: flex;
    gap: 6px;
    align-items: center;
}

.form-inline input {
    width: 90px;
    padding: 6px 8px;
}

.form-stack {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-top: 6px;
}

.form-stack input,
.form-stack select,
.form-stack textarea {
    width: 100%;
}

.two-col {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
}

.two-col > label {
    flex: 1;
    min-width: 180px;
}

.prov-fields {
    border: 1px dashed var(--line);
    border-radius: 10px;
    padding: 12px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    background: var(--bg);
}

textarea {
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-radius: 8px;
    font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    min-height: 120px;
    resize: vertical;
}

.card h3 {
    margin: 0 0 10px;
    font-size: 15px;
}

/* 我的接入:模型名标签、分工具折叠卡、步骤列表 */
.chip {
    display: inline-block;
    background: #eef2ff;
    color: #1e3a8a;
    border: 1px solid #c7d2fe;
    border-radius: 6px;
    padding: 1px 8px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 13px;
    white-space: nowrap;
}

.muted {
    color: var(--muted);
}

details.tool {
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 12px;
    padding: 6px 18px;
    margin: 10px 0;
}

details.tool[open] {
    padding-bottom: 16px;
}

details.tool > summary {
    cursor: pointer;
    font-weight: 600;
    font-size: 15px;
    padding: 10px 0;
    list-style: none;
}

details.tool > summary::-webkit-details-marker {
    display: none;
}

details.tool > summary::before {
    content: "▸ ";
    color: var(--brand);
}

details.tool[open] > summary::before {
    content: "▾ ";
}

/* 我的接入：把选择工具放在前面，次要信息收在折叠面板里。 */
.access-intro {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    gap: 16px;
    margin-bottom: 18px;
}

.access-intro h1 {
    margin: 4px 0 6px;
}

.access-intro .hint {
    margin: 0;
}

.access-eyebrow {
    color: var(--brand);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .12em;
}

.access-guide-link {
    flex-shrink: 0;
    font-size: 13px;
    font-weight: 600;
}

.access-credentials {
    margin-bottom: 24px;
}

.access-key-note {
    margin: 10px 0 0;
}

.access-section-lead {
    margin: -4px 0 14px;
}

.access-methods {
    display: grid;
    /* A long preformatted install command must not set the shared track's
     * intrinsic minimum width and stretch every numbered method card. */
    grid-template-columns: minmax(0, 1fr);
    gap: 10px;
}

details.access-method {
    min-width: 0;
    margin: 0;
    padding: 4px 18px;
}

details.access-method[open] {
    border-color: var(--brand);
}

details.access-method > summary {
    display: flex;
    align-items: center;
    gap: 14px;
    min-height: 54px;
}

details.access-method > summary::before {
    order: 3;
    margin-left: auto;
}

.access-method summary strong,
.access-method summary small {
    display: block;
}

.access-method summary small {
    color: var(--muted);
    font-weight: 400;
    margin-top: 3px;
}

.access-rank {
    color: var(--brand);
    font-size: 19px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.access-tag {
    border-radius: 100px;
    background: #e6f4ea;
    color: #176137;
    padding: 4px 9px;
    font-size: 11px;
    white-space: nowrap;
}

.access-tag.soft {
    background: #eaf2ff;
    color: #26559a;
}

.access-tag.caution {
    background: #fff0e7;
    color: #a13d24;
}

.access-method-caution summary small,
.access-method-caution .access-rank {
    color: #a13d24;
}

/* Anthropic 接入被策略禁用：inert 负责阻止键盘交互，pointer-events 是旧浏览器兜底。 */
details.access-method-locked > summary {
    cursor: not-allowed;
    pointer-events: none;
    user-select: none;
}

details.access-method-locked > summary::before,
details.access-method-locked[open] > summary::before {
    content: "🔒 ";
}

details.access-method-locked[open] {
    border-color: var(--line);
    padding-bottom: 4px;
}

details.access-method-locked > .access-method-body {
    display: none;
}

.access-method-body {
    border-top: 1px solid var(--line);
    padding-top: 10px;
}

.access-method-body p {
    line-height: 1.65;
}

.access-model-group {
    margin: 10px 0;
}

.access-model-group .chip-copy {
    margin-left: 5px;
}

.access-model-group p {
    margin: 4px 0 0;
    font-size: 12px;
}

.access-plugin-list code {
    overflow-wrap: anywhere;
}

.access-more-title {
    margin-top: 28px;
}

.access-verify {
    margin: 16px 0;
}

@media (max-width: 640px) {
    .access-intro {
        align-items: flex-start;
        flex-direction: column;
    }

    details.access-method > summary {
        gap: 9px;
    }

    .access-tag {
        display: none;
    }
}

.steps {
    margin: 8px 0;
    padding-left: 22px;
}

.steps li {
    margin: 6px 0;
}

/* 成功/提示态提示条(绿色),与 .notice.warn 对称,替代散落的内联绿色 */
.notice.ok {
    background: #e6f4ea;
    color: #166534;
    border-color: #bbe3c6;
}

/* 接入指南:文档式排版(顶部目录 + 分节标题带分隔线) */
.guide-doc h2 {
    font-size: 17px;
    margin: 30px 0 10px;
    padding-top: 18px;
    border-top: 1px solid var(--line);
    scroll-margin-top: 16px;
}

.guide-doc > h2:first-of-type {
    border-top: none;
    padding-top: 0;
}

.guide-doc h3 {
    font-size: 15px;
    margin: 16px 0 8px;
}

.toc {
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 12px;
    padding: 14px 20px;
    margin: 16px 0 8px;
    box-shadow: 0 1px 2px rgba(16, 24, 40, .04);
}

.toc-title {
    font-weight: 700;
    margin: 0 0 8px;
}

.toc ol {
    margin: 0;
    padding-left: 22px;
    columns: 2;
}

.toc li {
    margin: 5px 0;
    break-inside: avoid;
}

@media (max-width: 640px) {
    .toc ol {
        columns: 1;
    }
}

/* ── 我的用量:多维筛选工具条 + 单控件时间选择器 + 可排序表格 ── */
/* 筛选卡片:把所有筛选控件收进一张卡,顶端对齐、控件同高,消除错位 */
.filter-card {
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 12px;
    padding: 14px 16px;
    margin: 16px 0;
    box-shadow: 0 1px 2px rgba(16, 24, 40, .04);
}

.filter-bar {
    display: flex;
    flex-wrap: wrap;
    gap: 12px 16px;
    align-items: flex-end;
}

.f-item {
    display: flex;
    flex-direction: column;
    gap: 5px;
    flex: 1 1 150px;
    min-width: 140px;
}

.f-item.f-time {
    flex: 2 1 260px;
}

.f-item.f-model {
    flex: 2 1 220px;
}

.f-lbl {
    font-size: 12px;
    color: var(--muted);
    font-weight: 600;
    letter-spacing: .2px;
}

/* 统一所有控件的高度/圆角/边框,横向扫读对齐 */
.filter-bar select,
.filter-bar input,
.dt-trigger,
.dt-native input,
.dt-native select {
    height: 36px;
    padding: 0 10px;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--card);
    color: var(--ink);
    font-size: 14px;
    width: 100%;
}

.filter-bar select:focus,
.filter-bar input:focus,
.dt-trigger:focus {
    outline: none;
    border-color: var(--brand);
    box-shadow: 0 0 0 3px rgba(59, 91, 219, .12);
}

.filter-foot {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-top: 12px;
    padding-top: 10px;
    border-top: 1px dashed var(--line);
    font-size: 13px;
    color: var(--muted);
}

/* ── 单控件时间选择器 ── */
.dt-range {
    position: relative;
}

/* 用 :not([hidden]) 而非裸 .dt-native,避免 display 覆盖 hidden 属性的 display:none。
   初始(无 JS)只显示原生兜底;JS 就绪后给 .dt-native 加 hidden、去掉 .dt-trigger 的 hidden。 */
.dt-native:not([hidden]) {
    display: flex;
    align-items: center;
    gap: 6px;
}

.dt-native .dt-sep {
    color: var(--muted);
}

.dt-native .primary {
    flex: 0 0 auto;
    width: auto;
    padding: 0 14px;
}

.dt-trigger:not([hidden]) {
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    text-align: left;
}

/* 【必须截断】RangeLabel 除了「近 7 天」这种短标签,还会带上追加的说明
   (「超出可分析范围,已自动截断…」「你填的自定义日期无法使用…」)。触发器是固定
   36px 高、width:100% 的按钮,不截断的话长文案会溢出到按钮外面压住相邻控件。
   完整文案在下方「当前区间」那一行(段落,能换行),这里省略号是安全的。
   flex 子项的 min-width:auto 在 overflow != visible 时自动解析为 0,故无需额外声明。 */
.dt-trigger .dt-cur {
    flex: 1;
    font-weight: 600;
    color: var(--ink);
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.dt-trigger .dt-caret {
    color: var(--muted);
    font-size: 12px;
}

.dt-pop {
    display: none;
    position: absolute;
    z-index: 30;
    top: calc(100% + 6px);
    left: 0;
    width: 300px;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 12px;
    box-shadow: 0 12px 32px rgba(16, 24, 40, .16);
    padding: 12px;
}

.dt-pop.open {
    display: block;
}

.dt-presets {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 6px;
    margin-bottom: 10px;
}

.dt-preset {
    height: 30px;
    border: 1px solid var(--line);
    border-radius: 7px;
    background: var(--bg);
    color: var(--ink);
    font-size: 13px;
    cursor: pointer;
}

.dt-preset:hover {
    border-color: var(--brand);
    color: var(--brand);
}

.dt-preset.on {
    background: var(--brand);
    border-color: var(--brand);
    color: var(--brand-ink);
}

.dt-nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 6px;
}

.dt-nav .dt-title {
    font-weight: 600;
    font-size: 14px;
}

.dt-prev,
.dt-next {
    width: 28px;
    height: 28px;
    border: 1px solid var(--line);
    border-radius: 7px;
    background: var(--card);
    cursor: pointer;
    font-size: 16px;
    line-height: 1;
    color: var(--ink);
}

.dt-prev:hover,
.dt-next:hover {
    border-color: var(--brand);
    color: var(--brand);
}

.dt-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 2px;
}

.dt-wk {
    text-align: center;
    font-size: 11px;
    color: var(--muted);
    padding: 4px 0;
}

.dt-cell {
    height: 32px;
    border: 0;
    background: transparent;
}

.dt-day {
    border-radius: 7px;
    cursor: pointer;
    font-size: 13px;
    color: var(--ink);
}

.dt-day:hover {
    background: #eef2ff;
}

.dt-day.dt-in {
    background: #eef2ff;
    border-radius: 0;
}

.dt-day.dt-sel {
    background: var(--brand);
    color: var(--brand-ink);
}

.dt-day.dt-disabled {
    color: #cbd0d8;
    cursor: not-allowed;
}

.dt-cal-tip {
    margin: 8px 0 0;
    font-size: 12px;
    color: var(--muted);
}

/* 汇总卡:统一一排等宽卡片,首张(花费)用品牌色强调 */
.usage-cards {
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}

.stat.stat-key {
    border-color: var(--brand);
    background: linear-gradient(180deg, #f4f6ff 0%, var(--card) 60%);
}

.stat.stat-key .num {
    color: var(--brand);
}

/* 可点击排序的表头 */
th.sort-th {
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
}

th.sort-th:hover {
    color: var(--accent, #3b5bdb);
}

th.sort-asc::after {
    content: " \25B2";
    font-size: 10px;
}

th.sort-desc::after {
    content: " \25BC";
    font-size: 10px;
}

/* 各模型明细:别名下的原始模型串副标题 */
.raw-sub {
    display: block;
    max-width: 260px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 11px;
    color: #9ca3af;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* 家族小徽标 */
.fam-badge {
    display: inline-block;
    background: #f1f5f9;
    color: #475569;
    border: 1px solid #e2e8f0;
    border-radius: 6px;
    padding: 1px 7px;
    font-size: 12px;
    white-space: nowrap;
}

/* ── 口径提示 .qi(quota/口径 icon)──────────────────────────────────────────
 * 用途:给「我的用量」里每个算出来的数字挂一个极小的 ⓘ,悬停/聚焦时说明它的计算口径。
 * 设计约束:不占布局空间(inline、字号 10px、绝对定位气泡),纯 CSS 无 JS/无 CDN,
 * 与门户「服务端渲染、零 JS 依赖」的既有原则一致。
 * 无障碍:.qi 可聚焦(tabindex=0),键盘 Tab 到即展开;气泡用 role=tooltip 关联。
 * 注意不要再给 .qi 加 title 属性——会和气泡叠成两层提示。
 */
.qi {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 13px;
    height: 13px;
    margin-left: 3px;
    border: 1px solid var(--line);
    border-radius: 50%;
    color: var(--muted);
    background: var(--card);
    font-size: 9px;
    font-weight: 700;
    font-style: normal;
    line-height: 1;
    cursor: help;
    vertical-align: middle;
    flex: none;
}

.qi::after {
    content: "i";
}

.qi > .qi-tip {
    /* ⛔【原注释说反了】它写着「不用 display:none,以便屏幕阅读器可读」,但下面用的是
     * visibility: hidden —— 那和 display:none 一样会把整棵子树从【无障碍树】里摘掉。
     * 于是「多轮会话是什么」「命中率为什么不是请求占比」这些【只存在于气泡里】的口径说明,
     * 屏幕阅读器用户一个字都读不到,而这些页面的全部价值就是这些数字的口径。
     * 改用 clip-path 裁剪:纯视觉隐藏,节点留在无障碍树里(与 .sr-only 同一手法)。
     *
     * ⛔【bottom 原来是 calc(100% + 7px),那 7px 空隙让口径说明复制不出来】
     * 鼠标从图标移向气泡时必须穿过这段空隙,穿过的那一刻 :hover 断开、气泡消失。
     * 而「用户需要复制指标定义」是真实需求(WCAG 1.4.13 的「可悬停」)。
     * 【第一版的修法是错的,别再写回去】它改成 bottom:100% + padding-bottom:15px 想搭一条
     * 「透明 padding 桥」。两个问题:① 同一条规则里 7 行之后还有一个 padding 简写,
     * 把它整个覆盖掉 —— 那是一条【死声明】;② 即使生效,background-clip:padding-box
     * 会把 padding 一起涂上背景,桥根本不透明,气泡只会显得高一截并贴住图标。
     * 正确做法见下面的 ::after:保留 7px 视觉间距,用伪元素把这 7px 盖成悬停区。 */
    position: absolute;
    left: 50%;
    bottom: calc(100% + 7px);
    user-select: text;
    transform: translateX(-50%);
    z-index: 40;
    width: max-content;
    max-width: 300px;
    padding: 8px 10px;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--card);
    color: var(--ink);
    box-shadow: 0 6px 20px rgba(16, 24, 40, .16);
    font-size: 12px;
    font-weight: 400;
    line-height: 1.55;
    text-align: left;
    white-space: normal;
    opacity: 0;
    pointer-events: none;
    clip-path: inset(50%);
    transition: opacity .12s ease;
}

/* .qi-tip:hover 也要保持展开 —— 否则鼠标一进气泡就没了,还是复制不了。 */
/* 鼠标桥:盖住图标与气泡之间那 7px 视觉间距。
 * 它是 .qi-tip 的伪元素,所以指针落在桥上仍算「悬停气泡」,
 * 下面的 .qi > .qi-tip:hover 因此继续成立 —— 鼠标不会在穿越间距时掉出去。 */
.qi > .qi-tip::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    height: 9px;
}

/* 向下展开的那两档(表头、统计卡),桥要镜像到上边。 */
th .qi > .qi-tip::after,
.stat .label .qi > .qi-tip::after {
    top: auto;
    bottom: 100%;
}

.qi:hover > .qi-tip,
.qi:focus > .qi-tip,
.qi:focus-visible > .qi-tip,
.qi > .qi-tip:hover {
    opacity: 1;
    pointer-events: auto;
    clip-path: none;
}

.qi > .qi-tip code {
    font-size: 11px;
    background: var(--bg);
    padding: 1px 4px;
    border-radius: 3px;
}

/* 表头里的 .qi:必须【向下】展开。
 *
 * 原因(踩过的坑):宽表格外面套着 `overflow-x: auto` 的滚动容器。CSS 规定 overflow 两轴不能
 * 一个 visible 一个 auto —— 只要一轴是 auto,另一轴的 visible 就被计算成 auto。于是那个
 * 「只想横向滚动」的容器【连纵向也会裁剪】。表头位于容器最上沿,气泡若向上展开(bottom:100%)
 * 会被容器上边缘切掉,只在表头上方露出底部一条,看起来像「图标只显示底部一点、上半被上一行字盖住」。
 * 改为向下展开,气泡落进表体区域,容器内有充足高度,不再被裁。
 * 同理:向下展开后不要再给容器加 overflow:hidden。 */
th .qi > .qi-tip {
    /* 向下展开(表头是全站 .qi 的大多数)。鼠标桥由 .qi-tip::after 的镜像规则负责,
     * 这里不要再动 padding —— 那条路走不通,理由见基础规则里的说明。 */
    bottom: auto;
    top: calc(100% + 7px);
    /* 靠右对齐图标,避免最后几列的气泡把容器撑出横向滚动 */
    left: auto;
    right: -6px;
    transform: none;
    /* 表头气泡比卡片窄一些,进一步降低靠边被裁的概率 */
    max-width: 260px;
}

/* 前两列靠左对齐,否则气泡会伸出容器左边缘 */
th:first-child .qi > .qi-tip,
th:nth-child(2) .qi > .qi-tip {
    left: -6px;
    right: auto;
}

/* 统计卡首行的气泡朝下展开(卡片在页面顶部,朝上会超出视口) */
.stat .label .qi > .qi-tip {
    /* 同 th:向下展开,桥由 .qi-tip::after 的镜像规则负责。 */
    bottom: auto;
    top: calc(100% + 7px);
}

@media (prefers-reduced-motion: reduce) {
    .qi > .qi-tip {
        transition: none;
    }
}

/* 改价表单里的「按 $0 计」勾选框:与相邻的价格输入框区分开,避免误勾。 */
.edit-price-form label.zero-price {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    color: var(--muted);
    background: var(--bg);
    border: 1px dashed var(--line);
    border-radius: 6px;
    padding: 6px 9px;
}

.edit-price-form label.zero-price input {
    width: auto;
    margin: 0;
}

/* 统计卡的副标注(如「另有 N 次失败」):比主数字弱一档,不抢视线也不占额外高度。 */
.stat .sub-note {
    margin-top: 5px;
    font-size: 12px;
    color: var(--muted);
}

.stat .sub-note b {
    color: var(--alert);
    font-weight: 600;
}

/* 各模型明细里的失败笔数:用告警色标出,但不加粗到抢过主数据。 */
.fail-n {
    color: var(--alert);
    font-weight: 600;
}

/* 退出改成 POST 表单后,让按钮看起来仍是一个朴素链接(与旧的 <a> 观感一致)。 */
.logout-form {
    display: inline;
}

button.logout {
    border: none;
    background: none;
    padding: 0;
    font: inherit;
    color: var(--muted);
    cursor: pointer;
}

button.logout:hover {
    color: var(--ink);
    text-decoration: underline;
}

/* ── 调用日志 / 模型广场 ────────────────────────────────────────────────────── */

/* 失败行整行淡红底:逐条日志里失败是要第一眼找到的东西,只给状态列上色找起来太慢。 */
tr.log-fail > td {
    background: #fef6f6;
}

/* 「被中断」行淡黄底 —— 与失败(红)区分开:它在计费口径上是成功的,
   但客户端没收全答案。用中间色是为了让人一眼看出「这不是失败,但也不是没事」。 */
tr.log-warn > td {
    background: #fffbeb;
}

/* 自助提示:挂在失败行下方的一整行,不占表格列宽。 */
.advice {
    color: #9a3412;
    background: #fff7ed;
    border-left: 3px solid #fdba74;
    padding: 6px 10px;
    font-size: 13px;
    line-height: 1.55;
}

/* 错误原文:等宽 + 允许折行(错误串常含长 JSON,不折行会把表格撑出横向滚动)。 */
.errmsg {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px;
    color: var(--muted);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    max-width: 620px;
}

/* 归因标签。 */
.errcat {
    display: inline-block;
    padding: 1px 8px;
    border-radius: 999px;
    font-size: 12px;
    white-space: nowrap;
    background: #eef2ff;
    color: #1e3a8a;
    border: 1px solid #c7d2fe;
}

.errcat.todo {
    background: #f3f4f6;
    color: #4b5563;
    border-color: #d1d5db;
}

/* 已停用的模型行:置灰但仍可读 —— 隐藏只会把「为什么用不了」的问题挪进工单。 */
tr.row-off > td {
    background: #fafafa;
    color: var(--muted);
}

tr.row-off .chip {
    background: #f3f4f6;
    color: #6b7280;
    border-color: #e5e7eb;
}

/* 表内小号复制按钮(模型广场每行别名一个)。 */
/* 「别名 + 复制」成对出现时的容器(「我的接入」的模型清单)。
   inline-flex 让 chip 与它的复制按钮永不被换行拆开 —— 拆开后按钮会跑到下一行行首,
   看起来像是属于下一个别名的,点错就抄错名字,而抄错名字正是这一块要解决的问题。
   容器之间靠 margin 分隔,不再依赖模板里的空格(那个空格会被 inline-flex 吃掉)。 */
.chip-copy {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    margin: 0 6px 3px 0;
    white-space: nowrap;
}

.copy-mini {
    border: 1px solid var(--line);
    background: var(--card);
    border-radius: 5px;
    padding: 0 6px;
    margin-left: 6px;
    font-size: 11px;
    color: var(--muted);
    cursor: pointer;
}

.copy-mini:hover {
    color: var(--ink);
    border-color: #9ca3af;
}

/* 翻页条。 */
.pager {
    display: flex;
    gap: 10px;
    align-items: center;
    justify-content: flex-end;
    margin-top: 12px;
    font-size: 14px;
    /* 【必须 wrap】里面放了「导出 CSV」+ 一段说明 + 两个翻页链接,而 flex 项默认
     * min-width:auto、中文又可任意断行 —— 不 wrap 时那段说明会被压成一两字宽的竖条。 */
    flex-wrap: wrap;
}

.pager .spacer {
    flex: 1;
}

/* CSV 导出链接。刻意做成【次要】样式(描边而非实心):同一行里 .primary 的「下一页」
   才是主动作,而导出是偶尔用一次的旁路。做成实心会和翻页抢注意力。 */
.pager .export {
    padding: 4px 10px;
    border: 1px solid var(--line);
    border-radius: 5px;
    text-decoration: none;
    white-space: nowrap;
}

.pager .export:hover {
    background: var(--bg);
}

/* 系统层错误页的按天分布迷你图。 */
.mini-chart {
    max-width: 320px;
}

.mini-chart svg {
    height: 46px;
    /* ⛔【不加 width:auto 时,这个框里 57% 是死白】svg 带 width="100%" 与
     * preserveAspectRatio="xMidYMid meet"(见 chart/svg.go),viewBox 是 720×240。
     * 塞进 320×46 的框:缩放取 min(320/720, 46/240) = 0.192 ⇒ 实际只画出 138×46,
     * 左右各留 91px 空白,而轴文字被缩到 1.9px。
     * width:auto 让宽度跟着高度按比例走(138px),空白消失、也不再假装自己占满。
     * 真正的解法是 Go 侧另做一个不画坐标轴文字的 sparkline 变体 —— 这个尺寸下
     * 轴标签无论如何都不可读。那属于 chart 包的改动,不在样式范围内。 */
    width: auto;
    display: block;
}

/* ── Key 管理页:操作列、原因输入框、状态变更历史(从属行)────────────────────
 * 操作列是【一个】表单(op 由按钮携带),所以一行只有一个原因框、不存在「填了 A 的框
 * 点了 B 的按钮」。按钮顺序 = 回车时的默认提交顺序,故可逆操作必须排在最前。
 */
.key-ops {
    display: flex;
    gap: 6px;
    align-items: center;
    flex-wrap: wrap;
}

.key-ops button {
    flex: 0 0 auto;
}

/* 原因会进 portal_key_events,是「三个月后还能说清这把 Key 为什么被停」的唯一来源,
 * 所以要足够宽到能看见自己打的字。
 *
 * 【为什么写成 input.reason-in】原来只写 `.reason-in`(特异性 0,1,0),被上面的
 * `.form-inline input`(0,1,1)压掉 —— 与源码顺序无关,所以这条规则此前【完全没生效】,
 * 框实际一直是 90px(约 7 个汉字)。加上元素选择器后是 0,1,1 且在后,才真正生效。 */
input.reason-in {
    width: 180px;
    min-width: 120px;
}

/* 历史从属行:整块弱化,视觉上明确从属于上一行,不与主表的斑马纹抢注意力。 */
.ev-wrap > summary {
    cursor: pointer;
    color: var(--muted);
    font-size: 13px;
}

.ev-tbl {
    margin-top: 8px;
    font-size: 13px;
}

/* 从属表的单元格更紧凑,且不再画底边框(否则与主表的行分隔线混在一起数不清行)。 */
.ev-tbl th,
.ev-tbl td {
    padding: 5px 8px;
    border-bottom: 1px dashed var(--line);
}

.ev-tbl th {
    border-bottom: 1px solid var(--line);
}

/* 该用户【其它 Key】(含已吊销/已轮换掉的)的记录:置灰,避免被读成本行 Key 的历史。 */
.ev-other td {
    color: var(--muted);
}

/* 「原因未填」:比正文淡,让「有原因的那几条」在一列里跳出来。 */
.ev-none {
    color: #9ca3af;
}

/* 日志页的 request_id 精确查询框:比其它筛选项宽(request_id 是 chatcmpl-<uuid> 这类长串,
 * 太窄会让人看不出自己粘贴的是否完整,而「粘漏了几位」与「查不到这条」在页面上同形)。 */
.f-rid input {
    width: 340px;
    max-width: 100%;
}

/* 各模型明细里命中率单元格的第二口径(仅有缓存的调用)。
 * 弱化成副标题:主值仍是「全部调用」那个(与表头排序键一致),第二个数是解释性的。 */
.hit-sub {
    display: block;
    font-size: 11px;
    color: #9ca3af;
    white-space: nowrap;
}

/* 「我的 Key」里剩余额度接近上限时的整行提示(80%~100%)。
 * 用暖色底而不是红色:这一档是【提醒】—— 还能正常调用,只是快到了。
 *
 * 【原注释的理由已经过期】它写的是「超额当前并不会被拒绝,用红色会让用户以为已经
 * 不能用了」。那在 2026-09-08 之前成立(用户级预算对团队 Key 被跳过),但那天起
 * apply_user_budget_to_team_keys 已开启,超额【确实会】被 429 拒绝。
 * 所以现在≥100% 那一档反而必须用红色,见下面的 .kv-bad;暖色只留给 80~100%。 */
.kv-warn {
    background: #fffbeb;
    border-left: 3px solid #f59e0b;
    padding-left: 9px;
}

/* 已超出额度(≥100%)或额度被设为 $0:调用【正在】被拒绝,不是「注意控制」。
 * 与 .kv-warn 的区别就是「快到了」和「已经拦住了」——用同一种暖色会让用户
 * 继续找别的原因(以为是网络/Key 的问题),而这一行才是答案。 */
.kv-bad {
    background: #fef2f2;
    border-left: 3px solid #dc2626;
    padding-left: 9px;
}

/* 日志页用户筛选:可输入检索的 datalist 输入框(候选是平台全部用户,约 190 人)。
 * 比 .form-inline input 的默认 90px 宽:用户名形如 v_zhaojunqi01,太窄看不全。 */
.f-user input {
    width: 190px;
}

/* 用户管理页「用量占比」≥80% 的单元格。用暖色而非红色:超额当前并不会被拒绝
 * (见 users.html 该列的口径说明),红色会让管理员以为那些人已经不能用了。 */
.pct-warn {
    background: #fffbeb;
    font-weight: 600;
}

/* 服务额度【已超支】的单元格(服务 Key 页)。
 * 这里刻意用红色而不是复用 .pct-warn 的暖色:与用户那一列的口径不同,
 * 服务超额是【真的会被拦】的(429 budget_exceeded)—— 这一行此刻已经在失败,
 * 而不是「快到了、注意一下」。两者用同一个颜色的话,前者会被当成后者放着不管。 */
.pct-bad {
    background: #fef2f2;
    font-weight: 600;
}

/* ══════════════════════════════════════════════════════════════════════════════
 * 全站控件与响应式基线(2026-09-09)
 *
 * 这一段补的都是「每一页都在犯、但单看一页不像 bug」的问题。
 * ═════════════════════════════════════════════════════════════════════════════ */

/* ── 表单控件继承正文字体 ──────────────────────────────────────────────────────
 * 【原来的问题】<button> / <select> 默认【不继承】font-family 与 font-size。
 * body 用的是 PingFang SC / 微软雅黑,而 .copy 与 .danger 都没写 font 属性,
 * 于是它们渲染成浏览器的 UA 默认字体(常见是 Arial 13.33px)——
 * 和紧挨着的 .primary(明确写了 15px)并排时,字体和字号都不是一套。
 * 中文在 Arial 下会 fallback 到另一套字形,差异肉眼可见。 */
button,
select,
optgroup {
    font-family: inherit;
}

/* ── 行内按钮组:同一行的按钮必须等高 ─────────────────────────────────────────
 * 【原来的问题】.primary 是 padding:11px + font-size:15px(为登录页那种整行大按钮设计的),
 * .copy / .danger 是 padding:6px 12px + UA 字号。它们在 Key 管理页的操作列里并排,
 * 「激活」比旁边的「轮换」「吊销」高约 10px,基线也不齐 —— 一行三个按钮三个高度。
 *
 * 【为什么按容器收口而不是改 .primary 本身】.primary 同时承担登录页/发放表单里的
 * 整行大按钮,直接改会把那些按钮一起缩小。所以只在行内按钮组里统一。 */
.form-inline button,
.key-ops button {
    font-size: 14px;
    padding: 7px 12px;
    min-height: 34px;
    line-height: 1.2;
}

/* ── 键盘焦点可见 ─────────────────────────────────────────────────────────────
 * 按钮此前只有浏览器默认焦点环。深底的 .primary 上默认黑环对比度很低,
 * 而这一站的破坏性操作(停用/吊销/强制下线)全是按钮 —— 靠键盘操作的人
 * 必须能确认「焦点现在在哪个按钮上」再按空格。
 * 用 :focus-visible 而非 :focus:鼠标点击不会留下焦点环(那是多余的视觉噪声)。 */
button:focus-visible,
.nav a:focus-visible,
summary:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
    border-radius: 8px;
}

/* ── 宽表格的横向滚动容器 ─────────────────────────────────────────────────────
 * 【原来的问题】table 是 width:100%,但列数固定(Key 管理 8 列、模型管理 9 列)。
 * 窗口不够宽时表格撑破正文,导致【整个页面】横向滚动 —— 连顶栏一起跟着左右移动,
 * 这是最糟的一种溢出形态。部分页面已经手写了 <div style="overflow-x:auto">,
 * 但 keys / users / models / guide 四个模板漏了,于是同一个站有两种行为。
 * 统一成一个类,谁都不用再手写内联样式。 */
.table-wrap {
    overflow-x: auto;
    /* 触屏上带惯性滚动,并让滚动条不覆盖最后一行内容。 */
    -webkit-overflow-scrolling: touch;
}

/* ── 响应式:第一档(≤1180px)导航先瘦身 ──────────────────────────────────────
 * 管理员导航现在有 9 项(用户/Key/模型/团队/活跃/会话/日志/错误/运维)。
 * .nav 是 overflow-x:auto 且刻意隐藏了滚动条 —— 意思是宽度不够时后面几项
 * 【无声无息地被裁掉】,页面上没有任何线索说明「右边还有东西」。
 * 「运维」正好排在最后,于是最不常点但最要紧的那一页最容易消失。
 * 先靠缩小内边距与字号把 9 项塞回可视区,能不滚就不滚。 */
@media (max-width: 1180px) {
    .topbar {
        gap: 14px;
        padding: 0 16px;
    }

    .nav a {
        padding: 8px 9px;
        font-size: 14px;
    }
}

/* ── 响应式:第二档(≤900px)导航独占一行 ────────────────────────────────────
 * 让导航换到第二行整行铺开。
 * 【滚动条那两条规则已删除】原方案是「横向滚动 + 显示细滚动条」,但 .nav 现在
 * 直接换行(见 .nav 的说明),没有可滚动的溢出,那两条规则不再有任何作用 ——
 * 而且它们在触屏上从来没生效过(overlay 滚动条静止时不渲染)。 */
@media (max-width: 900px) {
    .topbar {
        padding: 8px 16px;
        gap: 8px 14px;
    }

    .nav {
        order: 3;
        flex-basis: 100%;
    }

    .content {
        margin: 16px auto 32px;
        padding: 0 16px;
    }

    .grid {
        grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
        gap: 10px;
    }

    .stat {
        padding: 13px;
    }

    .stat .num {
        font-size: 22px;
    }
}

/* ── 响应式:第三档(≤640px)单列 ──────────────────────────────────────────── */
@media (max-width: 640px) {
    /* 筛选项各占一行:并排两个 140px 的下拉在手机上都放不下标签文字。 */
    .f-item,
    .f-item.f-time,
    .f-item.f-model {
        flex: 1 1 100%;
    }

    /* 键值行(「我的接入」整页由它构成)改成上下堆叠:
       .k 固定 88px 宽 + .v 里 white-space:nowrap 的长 Key,在手机上必然挤爆。 */
    .kv {
        flex-wrap: wrap;
    }

    .kv .k {
        width: auto;
        flex-basis: 100%;
    }

    .card,
    .chart,
    .filter-card {
        padding: 14px;
    }

    h1 {
        font-size: 20px;
    }
}

/* ── 运维页:命令块 ───────────────────────────────────────────────────────────
 * 一条命令一个块,配一句说明和自己的复制按钮。
 * 【为什么值得单独做一个组件】原来是「一个 <pre> 里堆多条命令 + 一个复制按钮」,
 * 而复制按钮取的是整块文本 —— 剪贴板里会混进 `down.sh --purge` 这种不可逆命令,
 * 粘进终端后逐行执行。视觉上把「说明 / 命令 / 复制」绑成一组,才能让
 * 「这个按钮复制的到底是哪几行」在页面上一目了然。 */
.cmd {
    margin: 0 0 14px;
}

.cmd-desc {
    color: var(--muted);
    font-size: 13px;
    line-height: 1.55;
    margin-bottom: 6px;
}

.cmd .snippet {
    margin-bottom: 6px;
}

/* 破坏性命令:整块用警示色包起来,并且【没有】复制按钮(见 ops.html 的说明)。
 * 用红左边框而不是整块红底:红底会让人本能地跳过,而这些命令是要被读清楚的。 */
.cmd-danger {
    border-left: 3px solid var(--alert);
    padding: 8px 0 2px 12px;
    background: #fff8f7;
    border-radius: 0 8px 8px 0;
}

.cmd-danger .cmd-desc {
    color: #8c2f22;
}

/* ── 2026-09-10 设计评审:补齐「有类名却没有规则」与几处硬伤 ──────────────────── */

/* .nowrap / .tag 在 audit.html、models.html 里用了,但 CSS 里【从来没有规则】。
 * 后果:「2026-09-10 12:34」在窄列被折成两行、models 的两个操作按钮各占一行;
 * .tag 渲染成裸文字,和旁边的徽标不成套。 */
.nowrap {
    white-space: nowrap;
}

.tag {
    display: inline-block;
    padding: 1px 7px;
    border-radius: 6px;
    font-size: 12px;
    background: #f1f5f9;
    color: #475569;
    border: 1px solid #e2e8f0;
}

/* .badge 无修饰态(ops 的「未知」、logs 的「正文 N」)原来没有底色,
 * 渲染成带内边距的粗体字,和旁边的胶囊不成套。 */
.badge {
    background: #f1f5f9;
    color: #475569;
}

/* 等宽详情串(错误原文、request_id、探测详情)必须能在任意窄列里断行,
 * 否则一条长串就把整表推出横向滚动。 */
.mono-detail {
    overflow-wrap: anywhere;
}

/* 控件边框:见 :root 里 --line-ctl 的说明(1.4.11 要求 3:1)。 */
input,
textarea,
select,
.copy,
.danger,
.seg,
.dt-trigger,
.filter-bar select,
.filter-bar input {
    border-color: var(--line-ctl);
}

/* 三处 11px 小字原来写死 #9ca3af(白底 2.54),而它们承载的是数据不是装饰。 */
.raw-sub,
.hit-sub,
.ev-none {
    color: var(--muted-2);
}

/* 复制失败态:失败必须比"文案变一下"更明显(见 copy.js 的 flash)。 */
.copy.copy-bad {
    color: var(--alert);
    border-color: var(--alert);
}

/* 「用量占比 ≥80% 标黄」原来【只靠颜色】表达(WCAG 1.4.1),而这句说明只写在表头气泡里。
 * 补一个字形,并在 Windows 高对比度下用轮廓兜底 —— 那种模式会把全站底色统一替换掉,
 * .pct-warn / log-fail / log-warn / 日期选中态的区分会【同时全部消失】。 */
.pct-warn::after {
    content: " ⚠";
    font-size: 11px;
}

/* 同理(见上一条):.pct-bad 也不能只靠颜色。字形与 .pct-warn 刻意不同,
 * 否则「快到了」和「已经在被拒了」在灰度打印或色觉障碍下仍然无法区分。 */
.pct-bad::after {
    content: " ⛔";
    font-size: 11px;
}

@media (forced-colors: active) {

    .pct-warn,
    .pct-bad,
    tr.log-warn > td:first-child {
        outline: 2px dotted;
        outline-offset: -2px;
    }

    tr.log-fail > td:first-child {
        outline: 3px solid;
        outline-offset: -3px;
    }

    .dt-day.dt-sel {
        outline: 2px solid;
        outline-offset: -2px;
        forced-color-adjust: none;
    }

    .dt-day.dt-in {
        outline: 1px dashed;
        outline-offset: -2px;
    }

    .kv-warn,
    .kv-bad {
        border-left-width: 6px;
        border-left-style: double;
    }

    .badge {
        border: 1px solid;
    }
}

/* 慢操作的「正在进行」态(见 static/busy.js)。无动画,不必进 reduced-motion 块。 */
.busy-bar {
    margin: 10px 0 0;
    padding: 8px 12px;
    border-radius: 8px;
    background: #eef2ff;
    border: 1px solid #c7d2fe;
    color: #1e3a8a;
    font-size: 14px;
}

[aria-busy="true"] .filter-bar,
[aria-busy="true"] .dt-pop {
    opacity: .55;
    pointer-events: none;
}

/* 可排序表头:键盘可达后要有明确的可点提示(见 static/table-sort.js)。 */
.sort-th {
    cursor: pointer;
}

/* ══ 管理页长表的「检索 + 筛选 + 排序」工具条(2026-09-11)═══════════════════════
 * 配合 layout.html 的 adminlistinfo / adminpager 两个片段与 adminpaging.go。
 * 翻页条本身复用已有的 .pager(见上面),这里只补工具条与计数行。 */
.list-tools {
    display: flex;
    /* 【必须 wrap】里面是「搜索框 + 按钮 + 一排状态链接 + 一排排序链接」,窄屏放不下;
     * 不 wrap 时 flex 项会被压缩,搜索框先被挤成两三个字宽 —— 一个存在但没法用的输入框。 */
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
    margin: 0 0 10px;
    font-size: 14px;
}

/* .pager .spacer 是按 .pager 限定的,这里要自己声明一份,否则「排序」那一组不会靠右。 */
.list-tools .spacer {
    flex: 1;
    /* 窄屏 wrap 之后 spacer 会独占一行:min-width:0 让它可以被压成 0 宽而不留一条空行。 */
    min-width: 0;
}

/* 服务端排序 / 状态筛选选项。做成一排链接而不是 <select>:
 * select 需要「选完再提交」两步(或一段 onchange 的 JS,而本仓库刻意不引框架),
 * 而链接一步到位、可中键新标签打开、可分享 —— 当前选中项靠 .on 明示。 */
.sort-opt {
    padding: 3px 9px;
    border: 1px solid var(--line-ctl);
    border-radius: 999px;
    text-decoration: none;
    color: var(--ink);
    white-space: nowrap;
}

.sort-opt:hover {
    background: var(--bg);
}

/* 选中态:【不能只靠底色】—— 加粗 + 深色边框,高对比度模式下(底色会被统一替换)
 * 仍然分得出当前是哪种排序。判断「我现在看到的是哪个顺序」全靠这一处。 */
.sort-opt.on {
    background: #e8f0fe;
    border-color: #1a56c4;
    color: #1a56c4;
    font-weight: 600;
}

/* 计数行(共 N 条 / 匹配 N 条 / 本页第 X–Y 条)。 */
.list-info {
    margin: 0 0 8px;
    font-size: 14px;
}

.list-info .muted {
    font-size: 13px;
}

/* 单元格里的副行(Key 管理页「状态」下面那行「最近变更」)。
 * 【为什么不用 .hint】.hint 是段落级样式(有自己的行距与外边距),塞进 td 会把行高撑开;
 * 这里要的是「紧贴主值下面的一小行」。 */
.cell-sub {
    margin-top: 3px;
    font-size: 12px;
    color: var(--muted-2);
    white-space: nowrap;
}
