/* ============================================================================
   CZR 网络验证面板 · MIUI / HyperOS 风格（明 + 暗，无外部依赖）
   配色令牌取自 Miuix（HyperOS 设计体系）：
     主色 #3482FF · 浅色 tertiaryContainer #EAF2FF · 分割线 #E0E0E0
     深色 surfaceContainer #242424 · secondaryContainer #2D2D2D · outline #404040
   ============================================================================ */

/* ---------------------------------------------------------------- 主题令牌 */
:root,
html[data-theme="light"] {
    --primary: #3482FF;
    --on-primary: #FFFFFF;
    --primary-weak: #EAF2FF;
    --on-primary-weak: #3482FF;
    --bg: #FFFFFF;
    --surface: #F7F7F7;
    --card: #FFFFFF;
    --card-2: #F0F0F0;
    --text: #000000;
    --text-2: rgba(0, 0, 0, .60);
    --text-3: rgba(0, 0, 0, .38);
    --line: #E0E0E0;
    --outline: #D9D9D9;
    --error: #E94634;
    --error-weak: #FDF6F4;
    --ok: #1FA84B;
    --ok-weak: #E8F7ED;
    --warn: #E08A00;
    --warn-weak: #FFF6E5;
    --press: rgba(0, 0, 0, .05);
    --shadow: 0 2px 10px rgba(0, 0, 0, .06);
    --shadow-lg: 0 12px 32px rgba(0, 0, 0, .14);
    --mask: rgba(0, 0, 0, .35);
    color-scheme: light;
}

html[data-theme="dark"] {
    --primary: #3482FF;
    --on-primary: #FFFFFF;
    --primary-weak: #2B3B54;
    --on-primary-weak: #4788FF;
    --bg: #000000;
    --surface: #1A1A1A;
    --card: #242424;
    --card-2: #2D2D2D;
    --text: #FFFFFF;
    --text-2: rgba(255, 255, 255, .60);
    --text-3: rgba(255, 255, 255, .38);
    --line: #393939;
    --outline: #404040;
    --error: #F12522;
    --error-weak: #2E0603;
    --ok: #3FBF63;
    --ok-weak: #16301E;
    --warn: #E0A030;
    --warn-weak: #33280F;
    --press: rgba(255, 255, 255, .07);
    --shadow: none;
    --shadow-lg: 0 12px 32px rgba(0, 0, 0, .6);
    --mask: rgba(0, 0, 0, .6);
    color-scheme: dark;
}

/* 未手动指定时跟随系统 */
@media (prefers-color-scheme: dark) {
    html:not([data-theme]) {
        --primary: #3482FF; --on-primary: #FFFFFF; --primary-weak: #2B3B54; --on-primary-weak: #4788FF;
        --bg: #000000; --surface: #1A1A1A; --card: #242424; --card-2: #2D2D2D;
        --text: #FFFFFF; --text-2: rgba(255, 255, 255, .60); --text-3: rgba(255, 255, 255, .38);
        --line: #393939; --outline: #404040; --error: #F12522; --error-weak: #2E0603;
        --ok: #3FBF63; --ok-weak: #16301E; --warn: #E0A030; --warn-weak: #33280F;
        --press: rgba(255, 255, 255, .07); --shadow: none;
        --shadow-lg: 0 12px 32px rgba(0, 0, 0, .6); --mask: rgba(0, 0, 0, .6);
        color-scheme: dark;
    }
}

/* ---------------------------------------------------------------- 基础 */
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; }
body {
    margin: 0;
    background: var(--surface);
    color: var(--text);
    font: 15px/1.6 -apple-system, BlinkMacSystemFont, "MiSans", "Segoe UI", "PingFang SC",
          "HarmonyOS Sans SC", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif;
    -webkit-font-smoothing: antialiased;
    -webkit-text-size-adjust: 100%;
    overflow-x: hidden;
    transition: background .2s, color .2s;
}
a { color: var(--primary); text-decoration: none; }
code, .mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px; word-break: break-all;
}
code { background: var(--card-2); border-radius: 6px; padding: 1px 6px; }
.muted { color: var(--text-2); }
.small { font-size: 12.5px; }
.tiny { font-size: 11.5px; }
.center { text-align: center; }
.accent { color: var(--primary); }
.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.inline { display: inline-flex; gap: 8px; align-items: center; margin: 0; flex-wrap: wrap; }

/* ---------------------------------------------------------------- 布局 */
.layout { display: flex; min-height: 100vh; min-height: 100dvh; }

.sidebar {
    width: 236px; flex: 0 0 236px;
    background: var(--card);
    border-right: 1px solid var(--line);
    display: flex; flex-direction: column;
    position: sticky; top: 0; height: 100vh; height: 100dvh;
    z-index: 60;
}
.side-brand { display: flex; gap: 10px; align-items: center; padding: 20px 18px 14px; }
.side-brand-txt { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; text-align: left; }
.side-brand-txt strong { font-size: 16px; font-weight: 700; }
/* 侧边栏顶部 = 应用切换按钮 */
.as-btn {
    width: calc(100% - 20px); margin: 12px 10px 6px; padding: 10px 12px;
    background: var(--card-2); border: 0; border-radius: 16px;
    color: var(--text); font: inherit; cursor: pointer; transition: .15s;
}
.as-btn:hover { background: var(--outline); }
.as-btn:active { transform: scale(.985); }
.logo {
    width: 42px; height: 42px; border-radius: 14px; flex: 0 0 auto;
    display: grid; place-items: center; font-weight: 800; letter-spacing: .5px; font-size: 13px;
    background: var(--primary); color: #fff;
}
.logo.sm { width: 34px; height: 34px; border-radius: 12px; font-size: 11px; }

.nav { display: flex; flex-direction: column; gap: 2px; padding: 4px 10px 10px; flex: 1; overflow: auto; }
.nav-item {
    display: flex; align-items: center; gap: 12px;
    padding: 11px 14px; border-radius: 14px; color: var(--text); font-size: 14.5px;
}
.nav-item:hover { background: var(--press); text-decoration: none; }
.nav-item.active { background: var(--primary-weak); color: var(--on-primary-weak); font-weight: 600; }
.nav-item .ic { width: 18px; text-align: center; font-size: 15px; }
.side-foot { padding: 12px; border-top: 1px solid var(--line); }

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.topbar { display: flex; align-items: center; gap: 12px; padding: 20px 26px 10px; }
.topbar-title { flex: 1; min-width: 0; }
.topbar h1 { margin: 0; font-size: 26px; font-weight: 700; letter-spacing: -.4px; }
.topbar p { margin: 2px 0 0; }
.top-right { display: flex; gap: 8px; align-items: center; flex: 0 0 auto; }
.content { padding: 6px 26px 44px; display: flex; flex-direction: column; gap: 18px; }

/* 图标按钮（主题切换 / 菜单） */
.icon-btn {
    display: none; align-items: center; justify-content: center;
    width: 40px; height: 40px; border-radius: 50%; cursor: pointer;
    background: var(--card-2); color: var(--text); border: 0; font-size: 17px; line-height: 1;
}
.icon-btn:hover { background: var(--outline); }
.icon-btn.show { display: inline-flex; }

.side-mask { display: none; position: fixed; inset: 0; z-index: 55; background: var(--mask); }
.side-mask.show { display: block; }

/* ---------------------------------------------------------------- 卡片 / 面板 */
.panel {
    background: var(--card); border-radius: 20px; padding: 18px 20px;
    box-shadow: var(--shadow); min-width: 0;
}
html[data-theme="dark"] .panel,
html:not([data-theme]) .panel { border: 1px solid var(--line); }
.panel h2 { margin: 0 0 14px; font-size: 16.5px; font-weight: 700; letter-spacing: -.2px; }
.panel.highlight { box-shadow: 0 0 0 2px var(--primary) inset, var(--shadow); }
.panel-head { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
.panel-head h2 { margin: 0; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.section-title { font-size: 13px; color: var(--text-2); font-weight: 600; padding: 2px 4px; }

/* ---------------------------------------------------------------- 统计卡 */
/* ★★★ v19：列宽必须用 `minmax(0, 1fr)` 而不是 `1fr`。
   原因：`1fr` 隐含 `min-width: auto` —— **内容不肯被压缩**。统计卡里恰好有
   `white-space: nowrap` 的文字（如「全部应用合计」「涉及 2 张卡密」），
   它们把所在轨道撑得比别的轨道宽 ⇒ 四张卡**宽度不一致**（使用者截图指出
   「从上到下的宽度要与卡密总数的一致，不然看起来真的很奇怪」）。
   `minmax(0, 1fr)` 把最小宽度压到 0，四列严格等宽；再给子项 `min-width: 0`
   让文字在必要时省略/换行，而不是把卡片撑宽。 */
.cards { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; }
.cards > * { min-width: 0; }
/* 统计卡里的文字允许在必要时收缩（配合上面的 minmax(0,1fr)） */
.stat .stat-label, .stat .stat-sub, .stat .stat-num { min-width: 0; overflow-wrap: anywhere; }
.stat {
    background: var(--card); border-radius: 20px; padding: 18px 20px;
    display: flex; flex-direction: column; gap: 2px; min-width: 0; box-shadow: var(--shadow);
}
html[data-theme="dark"] .stat, html:not([data-theme]) .stat { border: 1px solid var(--line); }
.stat-label { color: var(--text-2); font-size: 13px; }
.stat-num { font-size: 30px; font-weight: 700; letter-spacing: -.5px; }
.stat-sub { color: var(--text-3); font-size: 12px; }

/* ---------------------------------------------------------------- 可展开的统计卡（★ v19）
   使用者要求：「在**原有的**今日登录/今日请求/绑定设备卡片上做修改，让原本的单卡片支持展开」，
   而**不是**再新增三张明细卡。
   结构：<div class="stat expand" id="x">            ← `.stat` 负责外观（白底/圆角/阴影/内边距）
            <button class="stat-head" data-expand="#x-body">…汇总…</button>
            <div class="expand-body" id="x-body">…逐应用明细…</div>
         </div>

   ★★ 这里**没有**用 `.expand-head`（那是给独立折叠卡用的：自带 14/16px 内边距、
      15px 标题字号）。统计卡必须与「卡密总数」那张**像素级一致** ——
      多一层 padding 就会高出几个像素、文字位置也偏（使用者两次截图都指出了这一点）。
   ⛔ 三个踩过的坑，都别再犯：
     1. `.stat.expand` 设成 `background: transparent` ⇒ **白底圆角全没了**（截图指出）。
        现在**完全保留 .stat 的外观**，只在内部把按钮"去样式化"。
     2. 给它加 `display: block` ⇒ 覆盖 `.cards` 的 grid 子项特性，四张卡从横排变**竖排**（同一张截图）。
        现在**绝不碰 display**（保持 .stat 的 flex column）。
     3. 复用 `.expand-head` 的 padding ⇒ 卡片变高、文字下移（与卡密总数不一致）。
        现在 padding/字号/行高全部归零或对齐。 */
.stat.expand { padding: 18px 20px; }              /* 与 .stat 完全一致 */
.stat.expand > .expand-body { margin-top: 10px; } /* 展开后才长出来的明细，与 .stat-sub 留同样的呼吸感 */
.stat.expand.open { padding-bottom: 16px; }       /* 展开时底部略收，视觉更稳 */
.stat-head {
    display: block; width: 100%; margin: 0; padding: 0; border: 0; background: transparent;
    color: var(--text); font: inherit; font-size: 14px; line-height: inherit;
    text-align: left; cursor: pointer;
}
.stat-head:hover, .stat-head:focus { background: transparent; outline: none; }
/* 收起时只显示汇总：**与 .stat 的三行结构逐项对齐**（label / num / sub 同一套类名） */
.stat-head-row { display: flex; align-items: flex-start; gap: 8px; }
.stat-head-row > .stat-body {
    flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px;
    font-size: 14px;                                 /* 与 .stat 的继承字号一致 */
}
.stat-head-row > .expand-caret { padding-top: 4px; font-size: 14px; }
/* 展开后的明细：紧凑两列（应用名 ↔ 数字） */
.stat-rows { display: flex; flex-direction: column; gap: 4px; font-size: 12.5px; }
.stat-rows > div { display: flex; justify-content: space-between; gap: 8px; }
.stat-rows > div > span { color: var(--text-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stat-rows > div > b { font-variant-numeric: tabular-nums; }
.stat .expand-body-inner { padding: 0; }             /* 内联的 padding 已清掉，改由上面的 margin 控制 */

/* ---------------------------------------------------------------- 表格 */
.table-wrap {
    overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch;
    border-radius: 16px; background: var(--card-2);
}
table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
th, td { padding: 12px 14px; text-align: left; white-space: nowrap; }
th { color: var(--text-2); font-weight: 600; font-size: 12.5px; }
tbody tr { background: var(--card); }
tbody tr + tr td { border-top: 1px solid var(--line); }
tbody tr:hover { background: var(--press); }
/* 行内操作按钮：横向单行排列（不换行，减少行高）
   注意：不能用 display:flex —— 那会让 <td> 脱离表格布局导致整行错位（历史踩坑） */
td.ops { white-space: nowrap; min-width: 1px; }
td.ops > a, td.ops > form, td.ops > button {
    display: inline-flex; vertical-align: middle; margin: 0 4px 0 0;
}
td.ops > *:last-child { margin-right: 0; }

/* ---------------------------------------------------------------- 表单 */
.form { display: flex; flex-direction: column; gap: 12px; }
.form label, .lbl { display: flex; flex-direction: column; gap: 6px; font-size: 13px; color: var(--text-2); }

/* ---------------------------------------------------------------- 勾选行（MIUI / HyperOS 设置行）
   ★ 为什么必须单独一类（2026-09-29 真机截图实测的错位）：
     `<label class="row">` 同时命中 `.row{display:flex}`（特异性 0,1,0）与
     `.form label{flex-direction:column}`（0,1,1）—— 后者特异性更高，
     于是勾选框行被拉成「竖排 + align-items:center」：复选框独自顶一行居中、
     说明文字掉到下一行也居中。既错位也不像 MIUI。
     现在统一走 .check-row：**开关在左、文字在右**（按使用者反馈调整；右侧操作区在手机上够不着）。 */
.form label.check-row,
label.check-row {
    display: flex; flex-direction: row; align-items: center;   /* ★ 上下居中（使用者要求） */
    justify-content: flex-start; gap: 12px;
    font-size: 13px; color: var(--text-2); padding: 2px 0; cursor: pointer;
}
.check-row .check-row-txt { flex: 1; min-width: 0; }
.check-row .check-row-txt b { display: block; font-weight: 500; font-size: 14.5px; color: var(--text); line-height: 1.45; }
.check-row .check-row-txt span { display: block; margin-top: 3px; line-height: 1.6; }
/* 勾选行左侧的开关（HyperOS 样式 · 紧凑 36×20，避免过大）。
   ★ 用「背景圆点 + background-position 位移」画滑块：
     伪元素在 <input> 上跨浏览器不可靠（Firefox 不渲染），而 background-image 一定生效，
     而且位移能做出平滑滑动动画（换 gradient 是瞬变）。
     36×20：圆点直径 14（半径 7，内缩 3）⇒ 关=圆心 10px、开=圆心 26px（图层右移 16px）。 */
.check-row input[type="checkbox"] {
    appearance: none; -webkit-appearance: none;
    width: 36px; height: 20px; flex: 0 0 auto; margin: 0;
    padding: 0; border: 0; border-radius: 999px;
    background-color: var(--outline);
    background-image: radial-gradient(circle at 10px 10px, #fff 0 7px, rgba(0, 0, 0, 0) 7.6px);
    background-repeat: no-repeat;
    background-size: 36px 20px;
    background-position: 0 0;
    transition: background-color .18s ease, background-position .18s ease;
    cursor: pointer;
}
.check-row input[type="checkbox"]:checked {
    background-color: var(--primary);
    background-position: 16px 0;
}
.check-row input[type="checkbox"]:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

/* ★ v17：弹窗内的分组分隔线（应用管理里「基础信息 / 协议」两段之间） */
.divider { height: 1px; background: var(--line); margin: 4px 0 12px; }

/* ★ v17：应用多选 / 自助解绑的应用选择 —— 一行一个应用，整行可点。
   为什么不用原生 select[multiple]：手机上按住多选体验极差，而且看不清已选项。
   ⚠ `.chk` 必须能**单独**用（自助解绑页没有 .chk-grid 包裹）。 */
.chk {
    display: flex; align-items: center; gap: 10px;
    padding: 10px 12px; border-radius: 12px; cursor: pointer;
    background: var(--card-2); transition: background .15s;
}
.chk:hover { background: var(--press); }
.chk input[type="checkbox"] {
    width: 18px; height: 18px; min-width: 18px; margin: 0; padding: 0;
    flex: 0 0 auto; accent-color: var(--primary);
}
.chk > span { display: flex; flex-direction: column; line-height: 1.35; font-size: 14px; }
.chk .small { font-size: 12px; }
.chk-grid { display: flex; flex-direction: column; gap: 2px; margin-top: 6px; }
input, select, textarea {
    background: var(--card-2); color: var(--text); border: 1.5px solid transparent;
    border-radius: 14px; padding: 12px 14px; font: inherit; font-size: 14px; outline: none;
    width: 100%; max-width: 100%; transition: border-color .15s, background .15s;
}
input:focus, select:focus, textarea:focus { border-color: var(--primary); background: var(--card); }
textarea { resize: vertical; line-height: 1.55; }
/* 新增卡密结果弹窗里的卡密清单：固定可视高度 + 上下滚动 */
.batch-out {
    width: 100%;
    min-height: 120px;
    max-height: 46vh;          /* 卡密多了就在框内上下滚动 */
    overflow-y: auto;
    resize: vertical;
    font-size: 12.5px;
    line-height: 1.6;
}
select { appearance: none; background-image: none; }

/* ---------------------------------------------------------------- 按钮（MIUI 胶囊） */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    background: var(--card-2); color: var(--text); border: 0; border-radius: 999px;
    padding: 11px 20px; font: inherit; font-size: 14px; font-weight: 600;
    cursor: pointer; transition: .15s; white-space: nowrap;
}
.btn:hover { background: var(--outline); text-decoration: none; }
.btn:active { transform: scale(.97); }
.btn.primary { background: var(--primary); color: var(--on-primary); }
.btn.primary:hover { filter: brightness(1.06); }
.btn.ghost { background: transparent; color: var(--primary); }
.btn.ghost:hover { background: var(--press); }
.btn.danger { background: var(--error-weak); color: var(--error); }
.btn.block { width: 100%; }
.btn.sm { padding: 8px 15px; font-size: 13px; }
.btn.xs { padding: 6px 12px; font-size: 12.5px; }

/* ---------------------------------------------------------------- 标签 / 提示 */
.tag {
    display: inline-block; padding: 3px 10px; border-radius: 999px; font-size: 12px;
    background: var(--card-2); color: var(--text-2); white-space: nowrap;
}
.tag.ok { background: var(--ok-weak); color: var(--ok); }
.tag.err { background: var(--error-weak); color: var(--error); }
.tag.warn { background: var(--warn-weak); color: var(--warn); }
.tag.blue { background: var(--primary-weak); color: var(--on-primary-weak); }
/* ★ v19：把 <button> 做成 `.tag` 的样子（用于"已绑应用"这类可点标签）。
   ⛔ 别用 `.as-btn` —— 它带 `width: calc(100% - 20px)`（侧边栏整宽按钮专用），
      套在标签上会把标签**撑满整个表格单元格**，看起来就是"被拉得很长、还错位"
      （使用者截图指出）。这里显式 width:auto + 清零 button 默认样式。 */
button.tag, .tag-btn {
    display: inline-flex; align-items: center; gap: 4px;
    width: auto; max-width: 100%; margin: 0 4px 2px 0; padding: 3px 10px;
    border: 0; border-radius: 999px; font: inherit; font-size: 12px; font-weight: 500;
    line-height: 1.5; cursor: pointer; white-space: nowrap; vertical-align: middle;
}
button.tag:active { transform: scale(.96); }
.pill { padding: 6px 14px; border-radius: 999px; background: var(--card-2); font-size: 13px; white-space: nowrap; }

.alert { border-radius: 16px; padding: 13px 16px; font-size: 14px; }
.alert.ok { background: var(--ok-weak); color: var(--ok); }
.alert.err { background: var(--error-weak); color: var(--error); }
.alert.warn { background: var(--warn-weak); color: var(--warn); }

.checks { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
.check { padding: 13px 16px; border-radius: 16px; background: var(--card-2); font-size: 13.5px; }
.check.ok { color: var(--ok); background: var(--ok-weak); }
.check.err { color: var(--error); background: var(--error-weak); }

/* MIUI 设置列表 */
.mi-list { display: flex; flex-direction: column; border-radius: 16px; overflow: hidden; background: var(--card-2); }
.mi-item { display: flex; align-items: center; gap: 12px; padding: 14px 16px; background: var(--card); }
.mi-item + .mi-item { border-top: 1px solid var(--line); }
.mi-item .mi-label { flex: 1; min-width: 0; }
.mi-item .mi-label b { display: block; font-weight: 600; font-size: 14.5px; }
.mi-item .mi-label span { color: var(--text-2); font-size: 12.5px; }

.kv { display: grid; grid-template-columns: 1fr; gap: 1px; border-radius: 16px; overflow: hidden; background: var(--line); }
.kv > div { display: flex; justify-content: space-between; gap: 12px; padding: 12px 16px; background: var(--card); }
.kv span { color: var(--text-2); flex: 0 0 auto; }
.kv b { font-weight: 600; word-break: break-all; text-align: right; }

/* 无底色的键值区（用于自助解绑页，避免灰色长条影响观感） */
.kv-plain { background: transparent; gap: 0; }
.kv-plain > div { background: transparent; border-bottom: 1px solid var(--line); }
.kv-plain > div:last-child { border-bottom: 0; }

.quick { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 12px; }

/* ---------------------------------------------------------------- 弹窗（Miuix / HyperOS Dialog） */
.modal {
    position: fixed; inset: 0; z-index: 90;
    display: flex; align-items: center; justify-content: center;
    padding: 24px 20px calc(24px + env(safe-area-inset-bottom));
}
.modal[hidden] { display: none; }
.modal-mask {
    position: absolute; inset: 0; background: var(--mask);
    animation: mask-in .2s ease-out;
}
@keyframes mask-in { from { opacity: 0; } to { opacity: 1; } }

/* Miuix 对话框：28dp 圆角、纯卡片背景、无描边、按钮区分离 */
.modal-card {
    position: relative; width: 100%; max-width: 420px; max-height: 86vh;
    display: flex; flex-direction: column;
    background: var(--card); border-radius: 28px;
    box-shadow: var(--shadow-lg); overflow: hidden;
    animation: modal-in .22s cubic-bezier(.2, .9, .3, 1);
}
@keyframes modal-in {
    from { opacity: 0; transform: translateY(16px) scale(.96); }
    to   { opacity: 1; transform: none; }
}
.modal-card.lg { max-width: 520px; }

/* 标题：Miuix 对话框居中、左右留出关闭按钮位置 */
.modal-head {
    display: flex; align-items: center; gap: 10px;
    padding: 22px 22px 6px;
}
.modal-head h3 {
    flex: 1; margin: 0; text-align: center;
    font-size: 18px; font-weight: 600; letter-spacing: -.2px;
    /* ★ 2026-09-28：标题一律单行，超长省略。
       原来长标题（如「配置网络储存 · macCZR」）会被折成**上下两行**，
       视觉上像堆叠，很难看（用户实测反馈）。 */
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.modal-head .icon-btn { flex: 0 0 auto; background: transparent; }
.modal-head .icon-btn:hover { background: var(--press); }
/* 仅在存在关闭按钮(X)时补左侧等宽占位，让标题真正居中；
   否则标题会被这 40px 推得偏右（去掉了 X 的弹窗就是这种情况）。 */
.modal-head:has(.icon-btn)::before { content: ''; width: 40px; flex: 0 0 auto; }

/* 正文：可滚动区（内部统一 flex 列 + 间距，无论直接放 label 还是包一层 .form） */
.modal-body {
    padding: 6px 22px 4px; overflow-y: auto; flex: 1 1 auto;
    display: flex; flex-direction: column; gap: 14px;
}
/* ★ 2026-09-29 实测踩坑：`.modal-body` 是「限高的 flex 纵向滚动容器」，
   子项默认 flex-shrink:1；而 `.expand` 又是 `overflow:hidden` ——
   按 flex 规范，子项 overflow 非 visible 时「自动最小尺寸」失效（=0），
   所以最后一张折叠卡（出站代理）会被压成**高度 0**，标题行和内容一起被裁掉，
   表现为「怎么往下滑都没有」。滚动容器里的子项必须禁止收缩。 */
.modal-body > * { margin: 0; flex: 0 0 auto; }
.modal-body .form { gap: 14px; }
.modal-body p { margin: 0; }
.modal-body .muted.small { line-height: 1.6; }
.modal-body > .lbl { display: flex; flex-direction: column; gap: 6px; font-size: 13px; color: var(--text-2); }

/* 底部按钮区：Miuix 是一行等宽按钮，顶部细分隔线 */
.modal-foot {
    display: flex; gap: 10px; padding: 14px 22px 20px;
    border-top: 1px solid var(--line); margin-top: 12px; flex: 0 0 auto;
}
.modal-foot .btn { flex: 1 1 0; min-width: 0; padding: 13px 16px; }
.modal-card > form > .modal-foot { margin-top: 0; }
.modal-card > form { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; }
/* ★ 2026-09-28：弹窗里若还有**第二个** form（如 WebDAV 弹窗底部的「清除配置」），
   上面那条 flex:1 1 auto 会让两个表单各撑满一半高度，整个弹窗被撑歪（实测"抽风"）。
   附属表单必须不参与弹性分配。 */
.modal-card > form.aside-form { flex: 0 0 auto; }
.modal-card > p { padding: 0 22px; margin: 0 0 12px; }

/* 应用切换列表 */
.app-list { display: flex; flex-direction: column; gap: 6px; }
.app-list form { margin: 0; }
.app-item {
    width: 100%; display: flex; align-items: center; gap: 12px; text-align: left;
    padding: 12px 14px; border-radius: 18px; background: transparent;
    border: 0; color: var(--text); font: inherit; cursor: pointer; transition: .15s;
}
.app-item:hover { background: var(--press); }
.app-item.on { background: var(--primary-weak); }
.app-item.on .app-item-txt b { color: var(--on-primary-weak); }
.app-item-txt { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; flex: 1; }
.app-item-txt b { font-size: 15px; font-weight: 600; }
.app-item .tag { margin-left: auto; }

/* 表格里的勾选框 */
.row-check, [data-check-all] { width: 18px; height: 18px; accent-color: var(--primary); cursor: pointer; }
th.check-col, td.check-col { width: 40px; text-align: center; }

/* ---------------------------------------------------------------- 图表 */
.chart-scroll { overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; }
.chart { display: flex; align-items: flex-end; gap: 12px; height: 190px; padding: 10px 4px 0; }
.bar-wrap { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; height: 100%; gap: 6px; min-width: 0; }
.bar { width: 100%; max-width: 34px; border-radius: 999px; min-height: 4px; background: var(--primary); opacity: .9; }

/* ---------------------------------------------------------------- 分页（Miuix：左右箭头 + 中间页数） */
.pager {
    display: flex; align-items: center; justify-content: center;
    gap: 12px; margin-top: 16px;
}
.pg-nav {
    width: 40px; height: 40px; flex: 0 0 auto;
    display: grid; place-items: center;
    border-radius: 50%; border: 0; cursor: pointer;
    background: var(--card-2); color: var(--text);
    font-size: 17px; line-height: 1; font: inherit;
    transition: background .15s, color .15s, opacity .15s;
}
.pg-nav:hover { background: var(--press); }
.pg-nav[disabled], .pg-nav.off {
    color: var(--text-3); opacity: .45; pointer-events: none; cursor: default;
}
.pg-info {
    min-width: 88px; text-align: center;
    font-size: 13.5px; font-weight: 600; color: var(--text-2);
    font-variant-numeric: tabular-nums;
}
.pg-info b { color: var(--text); font-weight: 700; }

/* ---------------------------------------------------------------- 可展开卡片（Miuix 折叠项） */
.expand { border-radius: 18px; background: var(--card); overflow: hidden; }
.expand-head {
    display: flex; align-items: center; gap: 12px; width: 100%;
    padding: 14px 16px; border: 0; cursor: pointer; text-align: left;
    background: transparent; color: var(--text); font: inherit;
    transition: background .15s;
}
.expand-head:hover { background: var(--press); }
.expand-title { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.expand-title b { font-size: 15px; font-weight: 600; }
.expand-title span { font-size: 12.5px; color: var(--text-2); }
.expand-caret {
    flex: 0 0 auto; color: var(--text-3); font-size: 15px;
    transition: transform .22s cubic-bezier(.3, .8, .3, 1);
}
.expand.open .expand-caret { transform: rotate(90deg); }
/* ★ 用 grid-template-rows: 0fr → 1fr 做展开动画。
   旧写法 max-height: 0 → 1200px 有个硬伤：内容只有 ~150px 时，
   前 12% 的动画时间就铺满了，剩下时间毫无变化 —— 表现为「突然铺满」
   （收起时反过来，看起来反倒正常）。grid 方案不需要知道内容高度，全程平滑。 */
.expand-body {
    display: grid; grid-template-rows: 0fr;
    transition: grid-template-rows .3s cubic-bezier(.3, .8, .3, 1);
}
.expand.open .expand-body { grid-template-rows: 1fr; }
.expand-body > * { overflow: hidden; min-height: 0; }
/* 可点击展开的标题行（一键同步）
   ★ 标题与按钮必须是「同一横线、紧挨着」：
     基类 .panel-head 是 justify-content:space-between + flex-wrap:wrap，
     直接用会被推到两端、空间不足时还会换行 —— 所以这里必须覆盖掉。 */
.panel-head[data-expand] {
    display: flex;
    /* ★★★ 必须显式写 row ★★★
       移动端媒体查询里有 `.panel-head { flex-direction: column; align-items: stretch }`
       （见文件末尾 @media 段）。它没有指定选择器优先级更高的版本，
       所以本规则若省略 flex-direction，手机上就会**继承竖排** ——
       表现为「标题一行、按钮一行」，正是用户反馈的"不在同一条横线"。 */
    flex-direction: row;
    align-items: center;            /* 标题与按钮垂直居中，视觉上真正齐平 */
    justify-content: flex-start;    /* 覆盖 space-between，两个元素紧靠左 */
    flex-wrap: nowrap;              /* 禁止换行 */
    gap: 10px;
    padding: 0;                     /* ★ 上下内边距归零：hover 高亮只包住内容 */
    margin-bottom: 0;
    border-radius: 10px;
    transition: background .15s;
}
.panel-head[data-expand]:hover { background: var(--press); box-shadow: 0 0 0 4px var(--press); }
.panel-head[data-expand] h2 { flex: 0 0 auto; }
.panel-head[data-expand] .tool-row { flex: 0 0 auto; }
.panel-head[data-expand] .expand-caret { margin-left: auto; }

/* 可展开卡片整体收紧（只作用于 .panel.expand，不影响仪表盘的独立展开块） */
.panel.expand { padding: 10px 12px; }
.panel.expand .expand-body-inner { padding: 2px 4px 4px; }

.expand-body-inner { padding: 0 16px 4px; }

/* ---------------------------------------------------------------- 检索区（搜索框与按钮并行） */
.filter-bar { display: flex; flex-direction: column; gap: 10px; margin-bottom: 12px; }
.filter-bar .filter-row { display: flex; align-items: center; gap: 10px; }
.filter-bar .filter-row input { flex: 1 1 auto; min-width: 0; }
.filter-bar .filter-row .btn { flex: 0 0 auto; }
@media (max-width: 560px) {
    .filter-bar .filter-row { flex-wrap: wrap; }
    .filter-bar .filter-row input { flex: 1 1 100%; }
}

/* ---------------------------------------------------------------- 提示条（右下角 · 从右滑入 · 向左滑出） */
.toast-wrap {
    position: fixed;
    right: 8px;                          /* 贴屏幕右边 */
    /* 距底 80px */
    bottom: calc(80px + env(safe-area-inset-bottom));
    z-index: 200;                       /* 浮在卡片/弹窗内容之上 */
    display: flex; flex-direction: column; align-items: flex-end; gap: 6px;
    pointer-events: none;               /* 容器不吃点击，避免挡住下方卡片 */
    /* ★ 容器不限宽：每条提示的宽度由 .toast 自行（自适应 + 上限） */
}
.toast {
    /* ★ 宽度：完全由内容决定（收缩包裹），**不设上限**。
       注意不能用 width:fit-content —— 与 -webkit-box 一起会失效。
       .toast-wrap 是 flex 列 + align-items:flex-end，flex 子项本就按内容收缩。 */
    padding: 9px 13px; border-radius: 12px; font-size: 12.5px; line-height: 1.4;
    background: var(--card); color: var(--text);
    box-shadow: var(--shadow-lg);
    border-left: 3px solid var(--primary);
    pointer-events: auto;
    min-width: 0;
    /* ★ 一次性动画：右滑入 → 停留 → **原路右滑出** → 停在不可见态。
       收回方向与出现方向一致（从右边来、回右边去），不是向左滑走。 */
    transform: translateX(130%); opacity: 0;
    animation: toast-flow 1.72s cubic-bezier(.2, .9, .3, 1) forwards;
}
/* 文本层：高度自适应但最多 2 行，超出省略 —— 长文案不撑高、也不影响外层收缩包裹 */
.toast-text {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    word-break: break-word;
}
.toast.ok   { border-left-color: var(--ok);    color: var(--ok); }
.toast.err  { border-left-color: var(--error); color: var(--error); }
.toast.warn { border-left-color: var(--warn);  color: var(--warn); }
.toast.out { animation: toast-flow-out .3s ease-in forwards; }   /* 点击立即收回 */
@keyframes toast-flow {
    0%   { transform: translateX(130%); opacity: 0; }
    15%  { transform: translateX(0);    opacity: 1; }
    81%  { transform: translateX(0);    opacity: 1; }
    100% { transform: translateX(130%); opacity: 0; }   /* ← 原路收回右边 */
}
@keyframes toast-flow-out {
    from { transform: translateX(0);    opacity: 1; }
    to   { transform: translateX(130%); opacity: 0; }   /* 点击：同样收回右边 */
}
/* 窄屏：仍然靠右、不占满，只把最大宽度收紧 */
@media (max-width: 560px) {
    .toast-wrap { right: 8px; bottom: calc(72px + env(safe-area-inset-bottom)); }
}

/* ---------------------------------------------------------------- 分页（Miuix：左右箭头 + 中间页数） */
.pager {
    display: flex; align-items: center; justify-content: center;
    gap: 12px; margin-top: 16px;
}
.pg-nav {
    width: 40px; height: 40px; flex: 0 0 auto;
    display: grid; place-items: center;
    border-radius: 50%; border: 0; cursor: pointer;
    background: var(--card-2); color: var(--text);
    font-size: 17px; line-height: 1; font: inherit;
    transition: background .15s, color .15s, opacity .15s;
}
.pg-nav:hover { background: var(--press); }
.pg-nav[disabled], .pg-nav.off {
    color: var(--text-3); opacity: .45; pointer-events: none; cursor: default;
}
.pg-info {
    min-width: 88px; text-align: center;
    font-size: 13.5px; font-weight: 600; color: var(--text-2);
    font-variant-numeric: tabular-nums;
}
.pg-info b { color: var(--text); font-weight: 700; }

/* ---------------------------------------------------------------- 可展开卡片（Miuix 折叠项） */
.expand { border-radius: 18px; background: var(--card); overflow: hidden; }
.expand-head {
    display: flex; align-items: center; gap: 12px; width: 100%;
    padding: 14px 16px; border: 0; cursor: pointer; text-align: left;
    background: transparent; color: var(--text); font: inherit;
    transition: background .15s;
}
.expand-head:hover { background: var(--press); }
.expand-title { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.expand-title b { font-size: 15px; font-weight: 600; }
.expand-title span { font-size: 12.5px; color: var(--text-2); }
.expand-caret {
    flex: 0 0 auto; color: var(--text-3); font-size: 15px;
    transition: transform .22s cubic-bezier(.3, .8, .3, 1);
}
.expand.open .expand-caret { transform: rotate(90deg); }
/* ★★★ 2026-10-05 修复：此处原先**又写了一遍** .expand-body / .expand.open .expand-body，
   用的是 max-height:0 → 1200px 的老方案。同一选择器后出现者胜，于是：
     · transition 被覆盖成 max-height 过渡 ⇒ 上面 grid-template-rows 的动画**永不生效**；
     · max-height:1200px 成了硬顶 ⇒ **展开内容超过 1200px 会被 overflow:hidden 裁掉**
       （这正是上面那份 grid 方案注释里说要修掉的"突然铺满/裁切"问题）。
   现在只保留上面 558-563 行的 grid 方案（0fr → 1fr，按内容自适应、有动画）。 */

/* ---------------------------------------------------------------- 登录 / 安装 */
.auth-body { display: grid; place-items: center; min-height: 100vh; min-height: 100dvh; padding: 24px; background: var(--surface); }
.auth-card {
    width: 100%; max-width: 400px; background: var(--card); border-radius: 26px;
    padding: 30px 26px; box-shadow: var(--shadow-lg);
}
html[data-theme="dark"] .auth-card, html:not([data-theme]) .auth-card { border: 1px solid var(--line); }
.install-card { max-width: 520px; }
.brand { display: flex; gap: 14px; align-items: center; margin-bottom: 22px; }
.brand h1 { margin: 0; font-size: 20px; font-weight: 700; }
.brand p { margin: 2px 0 0; font-size: 13px; }

.steps { display: flex; gap: 10px; margin-bottom: 18px; }
.step {
    flex: 1; display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text-2);
    padding: 11px 14px; border-radius: 16px; background: var(--card-2); min-width: 0;
}
.step span {
    width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center; flex: 0 0 auto;
    background: var(--outline); font-size: 12px; color: var(--text-2); font-weight: 700;
}
.step.on { background: var(--primary-weak); color: var(--on-primary-weak); font-weight: 600; }
.step.on span { background: var(--primary); color: #fff; }
.step.done { color: var(--ok); }
.step.done span { background: var(--ok-weak); color: var(--ok); }

/* ============================================================================
   响应式
   ============================================================================ */
@media (max-width: 1100px) {
    .grid-2 { grid-template-columns: 1fr; }
    .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 860px) {
    .layout { display: block; }
    .sidebar {
        position: fixed; left: 0; top: 0; bottom: 0; height: 100vh; height: 100dvh;
        transform: translateX(-100%); transition: transform .26s cubic-bezier(.3, .8, .3, 1);
        width: 264px; flex-basis: auto; overflow-y: auto; box-shadow: var(--shadow-lg);
    }
    .sidebar.open { transform: translateX(0); }
    .icon-btn { display: inline-flex; }
    .topbar {
        padding: 14px 16px 10px; position: sticky; top: 0; z-index: 40;
        background: color-mix(in srgb, var(--surface) 88%, transparent);
        backdrop-filter: blur(14px); border-bottom: 1px solid var(--line);
    }
    .topbar h1 { font-size: 21px; }
    .content { padding: 14px 16px 40px; gap: 14px; }
    .panel { padding: 15px 16px; border-radius: 18px; }
    .stat { padding: 15px 16px; border-radius: 18px; }
    .checks { grid-template-columns: 1fr; }
    .quick { grid-template-columns: 1fr; }
    .chart { height: 150px; gap: 8px; }
    /* ★ 2026-09-28：原来这里写 `align-items: start; padding-top: 44px`，
       把登录页 / 自助解绑页 / 安装向导的卡片顶到了「中间偏上」，很别扭。
       去掉覆盖即可继承 .auth-body 的 place-items:center（垂直+水平居中）；
       卡片比屏幕高时 grid 行会自动长高，不会裁切顶部。 */
    .auth-body { padding: 14px; }
    .auth-card { padding: 24px 18px; border-radius: 22px; }
    .steps { flex-direction: column; gap: 8px; }
    th, td { padding: 11px 12px; }
    td.ops { min-width: 1px; }
    .kv > div { flex-direction: column; gap: 2px; }
    .kv b { text-align: left; }
}
@media (max-width: 560px) {
    .cards { gap: 12px; }
    .stat-num { font-size: 24px; }
    .grid2 { grid-template-columns: 1fr; }
    .panel-head { flex-direction: column; align-items: stretch; }
    .panel-head .inline { width: 100%; }
    .panel-head .inline input, .panel-head .inline select { flex: 1 1 auto; min-width: 0; }
    .chart { height: 132px; }
    .topbar h1 { font-size: 19px; }
}
/* ★ 窄屏（手机竖屏常见 360~393px）改**单列**：两张并排时每张只有 ~170px，
   而「全部应用合计」「涉及 2 张卡密」这类文案会把卡片挤得很窄、看着很局促。
   单列之后每张卡都与「卡密总数」同宽 —— 这正是使用者要的"从上到下宽度一致"。 */
@media (max-width: 560px) {
    .cards { grid-template-columns: minmax(0, 1fr); }
}
@media (hover: none) {
    tbody tr:hover { background: var(--card); }
}

/* 滚动条 */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: var(--outline); border-radius: 8px; }
::-webkit-scrollbar-track { background: transparent; }

/* ============================================================================
   Miuix 补充组件（长条按钮 / 单选列表 / 选择行）
   ============================================================================ */

/* 长条按钮：整行宽、居中、用于「新增应用」这类主入口 */
.btn-wide {
    display: flex; align-items: center; justify-content: center; gap: 8px;
    width: 100%; padding: 14px 18px; border-radius: 20px;
    font: inherit; font-size: 15px; font-weight: 600; cursor: pointer;
    border: 1px solid var(--outline); background: var(--card); color: var(--text);
    transition: background .15s, border-color .15s;
}
.btn-wide:hover { background: var(--press); }
.btn-wide.primary {
    background: var(--primary); color: var(--on-primary); border-color: transparent;
}
.btn-wide.primary:hover { filter: brightness(1.06); }
.btn-wide + .panel, .panel + .btn-wide { margin-top: 14px; }

/* Miuix 设置列表里的「选择行」：标签 + 当前值 + 箭头，整行可点 */
.pick-row {
    display: flex; align-items: center; gap: 12px; width: 100%;
    padding: 13px 16px; border-radius: 18px; cursor: pointer;
    background: var(--card-2); border: 1px solid transparent;
    color: var(--text); font: inherit; text-align: left; transition: .15s;
}
.pick-row:hover { border-color: var(--outline); }
.pick-row .pick-label { flex: 1; min-width: 0; }
.pick-row .pick-label b { display: block; font-size: 14.5px; font-weight: 500; }
.pick-row .pick-label span { display: block; font-size: 12px; color: var(--text-2); }
.pick-row .pick-val {
    font-size: 13.5px; color: var(--text-2); max-width: 46%;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pick-row .caret { color: var(--text-3); flex: 0 0 auto; }

/* 弹窗内的选项列表（单选）：每项一行，选中打勾 */
.opt-list { display: flex; flex-direction: column; gap: 4px; }
.opt-item {
    display: flex; align-items: center; gap: 12px; width: 100%;
    padding: 13px 16px; border-radius: 16px; cursor: pointer;
    background: transparent; border: 0; color: var(--text);
    font: inherit; text-align: left; transition: background .15s;
}
.opt-item:hover { background: var(--press); }
.opt-item.on { background: var(--primary-weak); }
.opt-item.on .opt-txt { color: var(--on-primary-weak); font-weight: 600; }
.opt-item .opt-txt { flex: 1; min-width: 0; }
/* 勾只在选中项显示：之前只改颜色，导致两项都带勾 */
.opt-item .opt-check { color: var(--primary); flex: 0 0 auto; visibility: hidden; }
.opt-item.on .opt-check { color: var(--on-primary-weak); visibility: visible; }

/* 筛选/配置入口的小按钮组 */
.tool-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* 系统设置里的两个开关卡（2026-09-30 使用者要求）
   ★ 顺序必须是「标题在上 / 摘要在中 / 开关在最底」。
   这里 DOM 就按该顺序写（文字块在前、input 在后）+ flex-direction:column，
   不用 column-reverse 反转 —— 顺序显式确定，也不会被 .form label 之类的规则干扰。
   ⚠ 本规则必须在 .check-row / .switch-pair 之后，靠「同特异性后来居上」压住它们。 */
.switch-cards { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 12px; }
/* ★ 2026-09-30 关键（使用者实测「开关跑到文字右边」的根因）：
   上面 `.form label.check-row` 的特异性是 (0,2,1)，而单类 `.switch-card` 只有 (0,1,0) ——
   前者会赢，把卡片拉成 `flex-direction: row; align-items: center`（开关在文字**右侧**）。
   所以这里的两个选择器必须与之**同级或更强**：
     · `label.switch-card`      (0,1,1) —— 与 `label.check-row` 同级，靠位置靠后取胜
     · `.form label.switch-card` (0,2,1) —— 与 `.form label.check-row` 同级，同样靠后取胜
   最终效果：标题 → 摘要 → 开关，自上而下。 */
label.switch-card,
.form label.switch-card {
    display: flex; flex-direction: column; align-items: flex-start;
    justify-content: flex-start; gap: 8px;
    margin: 0; padding: 10px 12px; border-radius: 14px; background: var(--card-2);
    cursor: pointer;
}
.switch-card .check-row-txt { flex: 0 0 auto; }
.switch-card .check-row-txt b {
    display: block; font-size: 14px; font-weight: 600; color: var(--text); line-height: 1.45;
}
.switch-card .check-row-txt span {
    display: block; margin-top: 2px; font-size: 12px; color: var(--text-2); line-height: 1.6;
}
/* ★ 2026-09-30 使用者实测反馈：这里**绝对不能**加「窄屏改单列」的断点 ——
   手机竖屏视口普遍 < 560px，一加就变成上下堆叠，而使用者要的是**左和右永远并行**
   （和 §2.4 里「源行/两个开关在任何宽度都保持并列」是同一条要求）。
   宽度不够时靠标题/摘要在卡片内换行解决，不改列数。 */

/* 弹窗内两列表单（紧凑） */
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@media (max-width: 560px) { .grid2 { grid-template-columns: 1fr; } }

/* ---------------------------------------------------------------- 内联下拉（覆层，2026-09-30）
   使用者要求：点按钮向下展开列表，列表**覆盖**下方内容（绝对定位），
   而不是把下方输入框/按钮往下挤；单选选中后自动收起。
   用于：卡密的「单位」「批量操作」下拉、变量的「同步排除」多选下拉。 */
.dd { position: relative; display: inline-block; }
.dd-btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; white-space: nowrap; }
.dd-caret { font-style: normal; font-size: 10px; opacity: .7; line-height: 1; }
.dd-panel {
    position: absolute; top: calc(100% + 6px); left: 0; min-width: 100%;
    z-index: 40; padding: 5px; max-height: 264px; overflow-y: auto;
    background: var(--card-2); border: 1px solid var(--outline); border-radius: 14px;
    box-shadow: 0 14px 34px rgba(0, 0, 0, .30);
}
.dd-item {
    display: flex; align-items: center; gap: 10px; width: 100%;
    padding: 11px 14px; border: 0; border-radius: 10px; cursor: pointer;
    background: transparent; color: var(--text); font: inherit; font-size: 14px;
    text-align: left; white-space: nowrap;
}
.dd-item:hover { background: var(--press); }
.dd-item.on { background: var(--primary-weak); color: var(--on-primary-weak); font-weight: 600; }

/* 多选行：正方形选择框 + 名称 + 名称后面的勾（选中才显示） */
.dd-check {
    display: flex; align-items: center; gap: 10px; padding: 11px 14px;
    border-radius: 10px; cursor: pointer; margin: 0;
}
.dd-check:hover { background: var(--press); }
.dd-check input[type="checkbox"] {
    appearance: none; -webkit-appearance: none;
    width: 20px; height: 20px; flex: 0 0 auto; margin: 0; padding: 0;
    border: 2px solid var(--outline); border-radius: 6px; background: transparent;
    position: relative;
}
.dd-check input[type="checkbox"]:checked { background: var(--primary); border-color: var(--primary); }
.dd-check input[type="checkbox"]:checked::after {
    content: '✓'; position: absolute; inset: 0; display: flex;
    align-items: center; justify-content: center;
    color: #fff; font-size: 13px; line-height: 1;
}
.dd-check .dd-check-name { flex: 1; min-width: 0; font-size: 14px; }
.dd-check .dd-tick { color: var(--primary); visibility: hidden; }
.dd-check:has(input:checked) .dd-tick { visibility: visible; }
.dd-check:has(input:checked) .dd-check-name { font-weight: 600; }

/* 已排除变量的标签容器（替代原来那句计数文字） */
/* 「变量同步排除」这类整行下拉：给个圆角卡片底，别让它在弹窗里"没背景" */
.dd-card {
    display: block; margin-top: 12px;
    background: var(--card-2); border: 1px solid var(--line); border-radius: 14px;
}
.dd-card .expand-head { border-radius: 14px; }

/* ---------------------------------------------------------------- 卡密「到期」控件（2026-09-30）
   使用者明确要求：编辑卡密是「前面输入框、后面按钮」**左右并排**，不能上下叠。
   新增卡密选「自定义」后出现的「数量 + 单位按钮」同样并排。
   隐藏统一交给下面的全局 `[hidden] { display:none !important }`，不必再写覆盖。 */
.dur-inline, .dur-custom { display: flex; gap: 8px; align-items: center; }
.dur-inline > input, .dur-custom > input { flex: 1 1 auto; min-width: 0; }
.dur-inline > .btn, .dur-custom > .btn {
    flex: 0 0 auto; min-width: 88px; justify-content: center;
}
/* 单位改成内联下拉后，按钮被 .dd 包了一层 ⇒ 尺寸规则下移到 .dd 上 */
.dur-inline > .dd { flex: 0 0 auto; }
.dur-inline .dd-btn { min-width: 74px; }

/* 隐藏元素（覆盖 flex/grid 的 display） */
[hidden] { display: none !important; }

/* ---------------------------------------------------------------- 多同步源（Miuix 风格）
   沿用面板既有令牌：--primary #3482FF / --primary-weak / --press / 16~28px 圆角 / 胶囊按钮。
   一行 = 备注 + 「url,base key」+ 删除；右下方圆形「＋」再加一行。 */
/* 「配置源」小标题 + 右侧的 ＋（新增同步源） */
.line-title {
    display: flex; align-items: center; justify-content: space-between;
    gap: 10px; margin: 2px 0 8px;
}
.line-title b { font-size: 14.5px; font-weight: 600; }
.src-list { display: flex; flex-direction: column; gap: 8px; }
/* 一行 = 备注（窄） + url,base key（宽） + 右侧留白放删除键 */
.src-row {
    display: grid;
    /* ★ 任何宽度都保持"一行三个"：备注窄 / url,base key 宽 / 右边留空位放 ✕。
       曾经写过 `@media (max-width:560px)` 把它改成竖排 —— 手机上就是"备注一行、
       key 一行"，使用者直接判定不达标，已删除，且 e2e 有断言禁止再加回来。 */
    grid-template-columns: 64px minmax(0, 1fr) 42px;
    column-gap: 8px;
    align-items: center;
}
.src-row .src-label { padding: 10px 10px; font-size: 13.5px; border-radius: 12px; }
.src-row .src-key { padding: 10px 12px; font-size: 12.5px; border-radius: 12px; }
.src-row .src-del { justify-self: end; }
/* 圆形「＋」：Miuix 的 tonal 圆形按钮（浅蓝底 + 蓝字），点了加一行 */
.src-add {
    width: 38px; height: 38px; flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    padding: 0; border: 0; border-radius: 50%;
    background: var(--primary-weak); color: var(--on-primary-weak);
    font-size: 20px; line-height: 1; cursor: pointer;
    transition: transform .15s, filter .15s;
}
.src-add:hover { filter: brightness(.96); }
.src-add:active { transform: scale(.92); }
/* 行尾删除：无底色，悬停才显红（不抢主操作） */
.src-del {
    width: 34px; height: 34px; flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    padding: 0; border: 0; border-radius: 50%;
    background: transparent; color: var(--text-3);
    font-size: 13px; cursor: pointer; transition: background .15s, color .15s;
}
.src-del:hover { background: var(--error-weak); color: var(--error); }


/* 同步结果：每个同步源一块（结果不一致时并列展示，标题标明是哪个版本） */
.sync-group { border-radius: 18px; background: var(--card-2); padding: 14px 16px; }
.sync-group + .sync-group { margin-top: 12px; }
.sync-group-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sync-group-head .name { font-weight: 600; font-size: 14.5px; word-break: break-all; }
.sync-group-foot { margin-top: 12px; display: flex; justify-content: flex-end; }
.sync-pill {
    display: inline-block; padding: 3px 10px; border-radius: 999px;
    font-size: 12px; font-weight: 600; flex: 0 0 auto;
}
.sync-pill.ok { background: var(--ok-weak); color: var(--ok); }
.sync-pill.warn { background: var(--warn-weak); color: var(--warn); }
.sync-pill.err { background: var(--error-weak); color: var(--error); }

/* 配置同步弹窗：两个开关并列 —— 标题在上、开关在下（复用 .check-row 的开关样式） */
.switch-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 12px; }
.switch-pair .check-row {
    flex-direction: column-reverse;          /* DOM 是 input 在前 → 反过来即"文字在上、开关在下" */
    align-items: flex-start;
    gap: 8px;
    margin: 0; padding: 10px 12px;
    border-radius: 14px; background: var(--card-2);
}
.switch-pair .check-row .check-row-txt b { font-size: 14px; }
.switch-pair .check-row .check-row-txt span { font-size: 12px; margin-top: 2px; }



/* ---------------------------------------------------------------- 失败日志：IP 聚合行 + 行内展开明细
   （2026-10-02 新增：登录日志页第一层按 IP 聚合，点行向下展开该 IP 的明细）
   交互 JS 见 assets/app.js 的 [data-ip-toggle]。 */
.ip-row { cursor: pointer; }
.ip-row:hover { background: var(--press); }
.ip-row.open { background: var(--press); }
.ip-caret {
    font-style: normal; display: inline-block; opacity: .55;
    transition: transform .18s cubic-bezier(.2,.9,.3,1);
}
.ip-row.open .ip-caret { transform: rotate(90deg); opacity: 1; }
.ip-detail > td { padding: 0 !important; background: var(--card-2); }
.ip-detail-box { padding: 12px 14px 14px; border-top: 1px solid var(--line); }
.ip-detail-head {
    display: flex; align-items: baseline; gap: 10px;
    flex-wrap: wrap; margin-bottom: 10px;
}
.ip-detail-table { background: transparent; }
.ip-detail-table th,
.ip-detail-table td { font-size: 12.5px; padding: 7px 9px; }

/* ---------------------------------------------------------------- 日志策略卡片（紧凑版，2026-10-02）
   ★ 使用者要求：系统设置 → 全局参数 → 日志策略的两张卡**从上到下缩小**。
   这里覆盖 label.switch-card 的 padding / gap / 行高 / 字号，并把数字输入框也压矮；
   必须在上面 label.switch-card 规则**之后**（同特异性靠位置取胜）。
   宽度与列数不受影响 —— 依旧是左右并行、任何宽度都不改列。 */
.switch-cards.compact { margin-top: 8px; gap: 8px; }
/* ★★ 2026-10-02 使用者要求（第二次修正）：
   结构**保持原来的「上下」**（标题 → 摘要 → 控件），只是把整张卡片**从上到下压矮**；
   ⚠ 不要把控件改成与文字左右并排（上一版那样做，卡片反而被拉得很长）。
   压矮靠四处：① 卡片 padding 10/12 → 7/11；② 控件与文字的间距 8 → 5；
   ③ 标题/摘要字号与行高收紧；④ 控件自身高度收窄（开关 36×20 → 30×17、数字框 padding 12 → 4）。 */
.switch-cards.compact label.switch-card,
.switch-cards.compact .form label.switch-card {
    flex-direction: column; align-items: flex-start; justify-content: flex-start;
    padding: 7px 11px; border-radius: 11px; gap: 5px;
}
.switch-cards.compact .switch-card .check-row-txt b {
    font-size: 13px; line-height: 1.2;
}
.switch-cards.compact .switch-card .check-row-txt span {
    margin-top: 1px; font-size: 11px; line-height: 1.3;
}
/* ★★★ 2026-10-02 使用者第三次反馈：「开关要与它右边的输入框**上下对齐**、保持一条直线」。
   根因（前两版没解决的真正原因）：两张卡片的**文字块高度不相等** ——
   左卡摘要「关闭＝只记失败」比右卡「按天自动清理，0 = 不自动清」短，
   文字块就矮一截，于是它下面的开关自然比右边的输入框**高出一截**。
   解法：把两张卡的文字块**钉成同一固定高度**（标题行高 17px + 摘要行高 15px），
   并给摘要加 nowrap + 省略号（宁可截断也不换行，避免又拉高文字块）。
   ⇒ 文字块等高 ⇒ 控件起点相同 ⇒ 开关与输入框落在**同一条水平线**上。
   ⚠ 依旧不碰开关的 background-size / background-position（滑块靠它做位移动画）。 */
.switch-cards.compact .switch-card .check-row-txt {
    display: block; flex: 0 0 auto; height: 32px;   /* 17 + 15 */
}
.switch-cards.compact .switch-card .check-row-txt b {
    display: block; font-size: 13px; line-height: 17px; height: 17px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.switch-cards.compact .switch-card .check-row-txt span {
    display: block; margin: 0; font-size: 11px; line-height: 15px; height: 15px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* ★★★ 关键：grid 子项必须 min-width:0，否则内容（尤其是我加的 nowrap 长摘要）
   会把 1fr 的列**撑破** ⇒ 右卡「失败日志保留」就显得比左卡宽。 */
.switch-cards.compact .switch-card { min-width: 0; }
/* ★★★ 两个控件的**高度做成一样**（20px）——这才是「开关与输入框上下对齐」的关键：
   原来开关 20px、数字框 30px，两者高度不同 ⇒ 中线/底边都对不齐。
   现在 20 = 20（与开关原生高度完全一致，不改它的 background-*，滑块动画不受影响）。 */
.switch-cards.compact .switch-card > input[type="checkbox"] {
    flex: 0 0 auto; margin: 2px 0 0; align-self: flex-start;
}
.switch-cards.compact .switch-card > input[type="number"] {
    box-sizing: border-box;
    width: 100%; max-width: 108px; height: 20px; flex: 0 0 auto; margin: 2px 0 0;
    padding: 0 8px; font-size: 12.5px; line-height: 18px; border-radius: 7px;
    text-align: left; align-self: flex-start;
}


