/* ==========================================================================
   phpencode.cn 设计系统（自研，无构建步骤）
   变量 → 基础 → 布局 → 组件 → 工具类 → 暗色模式
   ========================================================================== */

/* ---------- 1. 变量 ---------- */
:root {
    --brand-50:  #eef2ff;
    --brand-100: #e0e7ff;
    --brand-200: #c7d2fe;
    --brand-400: #818cf8;
    --brand-500: #6366f1;
    --brand-600: #4f46e5;
    --brand-700: #4338ca;
    --brand-900: #312e81;

    --bg:        #f6f7fb;
    --bg-soft:   #eef1f8;
    --surface:   #ffffff;
    --surface-2: #f9fafb;
    --border:    #e5e7ef;
    --border-strong: #d3d7e3;

    --text:      #1f2430;
    --text-soft: #5b6473;
    --text-mute: #8a93a6;

    --success: #16a34a;
    --success-bg: #ecfdf3;
    --warning: #d97706;
    --warning-bg: #fffbeb;
    --danger:  #dc2626;
    --danger-bg: #fef2f2;
    --info:    #0284c7;
    --info-bg: #f0f9ff;

    --radius: 12px;
    --radius-sm: 8px;
    --radius-lg: 18px;
    --shadow-sm: 0 1px 2px rgba(15, 23, 42, .05);
    --shadow:    0 4px 16px rgba(15, 23, 42, .07);
    --shadow-lg: 0 16px 40px rgba(15, 23, 42, .12);

    --header-h: 60px;
    --container: 1200px;
    /* Windows 上优先用「微软雅黑 UI」（比 Segoe UI + 雅黑混排更统一、小字号更清晰）；
       macOS 仍是 -apple-system / BlinkMacSystemFont 先命中 */
    --font: -apple-system, BlinkMacSystemFont, "Microsoft YaHei UI", "Microsoft YaHei",
            "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Helvetica Neue", Arial, sans-serif;
    --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
}

[data-theme="dark"] {
    --bg:        #0f1117;
    --bg-soft:   #151824;
    --surface:   #171a24;
    --surface-2: #1d2130;
    --border:    #262b3b;
    --border-strong: #333a4f;

    --text:      #e8eaf1;
    --text-soft: #a6adc0;
    --text-mute: #7c8399;

    --success-bg: #0d2818;
    --warning-bg: #2a2110;
    --danger-bg:  #2b1416;
    --info-bg:    #0c2233;
    --shadow-sm: 0 1px 2px rgba(0,0,0,.4);
    --shadow:    0 4px 16px rgba(0,0,0,.45);
    --shadow-lg: 0 16px 40px rgba(0,0,0,.55);
}

/* ---------- 2. 基础 ---------- */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    font-family: var(--font);
    font-size: 14px;
    line-height: 1.65;
    color: var(--text);
    background: var(--bg);
    /* 刻意不写 -webkit-font-smoothing: antialiased —— 那会把 Windows 的 ClearType（次像素）
       换成灰度抗锯齿，小字号看着发虚；默认渲染更清晰 */
}

h1, h2, h3, h4 { margin: 0 0 .5em; line-height: 1.3; font-weight: 650; }
h1 { font-size: 26px; }
h2 { font-size: 20px; }
h3 { font-size: 16px; }
p  { margin: 0 0 1em; }

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

img { max-width: 100%; display: block; }

code, pre, kbd { font-family: var(--mono); font-size: 12.5px; }
code { background: var(--surface-2); border: 1px solid var(--border); border-radius: 6px; padding: 1px 6px; }
pre {
    background: #0f172a; color: #e2e8f0; border-radius: var(--radius);
    padding: 14px 16px; overflow: auto; position: relative;
}
[data-theme="dark"] pre { background: #0a0d14; }
pre code { background: none; border: 0; color: inherit; padding: 0; }

hr { border: 0; border-top: 1px solid var(--border); margin: 20px 0; }

::selection { background: var(--brand-200); color: #1e1b4b; }

/* ---------- 3. 布局 ---------- */
.container { width: 100%; max-width: var(--container); margin: 0 auto; padding: 0 16px; }

.site-header {
    position: sticky; top: 0; z-index: 50;
    height: var(--header-h);
    background: color-mix(in srgb, var(--surface) 88%, transparent);
    backdrop-filter: saturate(160%) blur(10px);
    border-bottom: 1px solid var(--border);
}
.site-header .container { display: flex; align-items: center; gap: 22px; height: var(--header-h); }

.brand { display: flex; align-items: center; gap: 10px; font-weight: 750; font-size: 17px; color: var(--text); }
.brand:hover { text-decoration: none; }
.brand-mark {
    width: 30px; height: 30px; border-radius: 9px; flex: none;
    background: linear-gradient(135deg, var(--brand-500), #7c3aed 60%, #db2777);
    display: grid; place-items: center; color: #fff; font-size: 15px; font-weight: 800;
}

.main-nav { display: flex; align-items: center; gap: 4px; flex: 1; }
.main-nav a {
    padding: 7px 12px; border-radius: 9px; color: var(--text-soft); font-weight: 550;
}
.main-nav a:hover { background: var(--bg-soft); color: var(--text); text-decoration: none; }
.main-nav a.active { background: var(--brand-50); color: var(--brand-700); }
[data-theme="dark"] .main-nav a.active { background: rgba(99,102,241,.16); color: var(--brand-200); }

.header-actions { display: flex; align-items: center; gap: 10px; }
.nav-toggle { display: none; }

.page { padding: 28px 0 56px; }
.page-head { margin-bottom: 20px; }
.page-head .sub { color: var(--text-mute); margin-top: 4px; }

/* 前台：内容不足一屏时页脚也贴在视口底部（不贴着内容） */
.site-body { display: flex; flex-direction: column; min-height: 100vh; }
.site-body .page { flex: 1 0 auto; }

.grid { display: grid; gap: 16px; }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.flex { display: flex; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.justify-center  { justify-content: center; }
.gap-8 { gap: 8px; } .gap-12 { gap: 12px; } .gap-16 { gap: 16px; }
.wrap { flex-wrap: wrap; }
.mt-0{margin-top:0}.mt-8{margin-top:8px}.mt-12{margin-top:12px}.mt-16{margin-top:16px}.mt-24{margin-top:24px}
.mb-0{margin-bottom:0}.mb-8{margin-bottom:8px}.mb-12{margin-bottom:12px}.mb-16{margin-bottom:16px}.mb-24{margin-bottom:24px}
.text-sm { font-size: 12.5px; }
.text-mute { color: var(--text-mute); }
.text-soft { color: var(--text-soft); }
.text-right { text-align: right; }
.text-center { text-align: center; }
.mono { font-family: var(--mono); }
.nowrap { white-space: nowrap; }
.w-full { width: 100%; }

/* ---------- 4. 组件 ---------- */

/* 按钮 */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 7px;
    /* 视觉居中补正（中文）：上比下多 1px（字身框下方留白多）＋ 右比左多 1px（字形在字身框里略偏右） */
    padding: 10px 17px 8px 16px; border-radius: 10px; border: 1px solid transparent;
    font-size: 14px; font-weight: 600; cursor: pointer; text-decoration: none !important;
    transition: .15s ease; white-space: nowrap; line-height: 1;
}
.btn:disabled, .btn.disabled { opacity: .55; cursor: not-allowed; }
.btn-primary { background: var(--brand-600); color: #fff; box-shadow: var(--shadow-sm); }
.btn-primary:hover { background: var(--brand-700); }
.btn-secondary { background: var(--surface); color: var(--text); border-color: var(--border-strong); }
.btn-secondary:hover { background: var(--bg-soft); }
.btn-ghost { background: transparent; color: var(--text-soft); }
/* 线框按钮（「编辑」「更新」用）：透明底 + 只有边框带颜色，文字统一深色 */
.btn-outline { background: transparent; color: var(--text); border-color: var(--text); }
.btn-outline:hover { background: var(--bg-soft); }
/* 绿色线框（「更新」）：和「编辑」同款式，只有框色不同（字跟框同色，免得"绿框配黑字"看着别扭） */
.btn-outline-success { border-color: var(--success); color: var(--success); }
.btn-outline-success:hover { background: var(--success-bg); color: var(--success); }
.btn-ghost:hover { background: var(--bg-soft); color: var(--text); }
.btn-success { background: var(--success); color: #fff; }
.btn-warning { background: var(--warning); color: #fff; }
.btn-info    { background: var(--info);    color: #fff; }
.btn-danger { background: var(--danger); color: #fff; }
.btn-sm { padding: 6px 12px 4px 11px; font-size: 12.5px; border-radius: 5px; }
.btn-lg { padding: 12px 22px; font-size: 15px; border-radius: 12px; }
.btn-block { width: 100%; }

/* 卡片 */
.card {
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); padding: 18px; box-shadow: var(--shadow-sm);
}
.card-lg { padding: 24px; border-radius: var(--radius-lg); }
/* flex-wrap: 窗口窄时把右侧那排控件换到下一行，而不是把标题挤成"一字一行" */
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 14px; flex-wrap: wrap; }
.card-title { font-size: 15px; font-weight: 700; margin: 0; white-space: nowrap; }
.card-hover { transition: .18s ease; }
.card-hover:hover { transform: translateY(-2px); box-shadow: var(--shadow); border-color: var(--brand-200); }

/* 统计块 */
.stat { display: flex; flex-direction: column; gap: 2px; }
.stat .label { font-size: 12px; color: var(--text-mute); }
.stat .value { font-size: 22px; font-weight: 750; letter-spacing: -.4px; }
.stat .value.sm { font-size: 17px; }

/* 徽章 */
.badge {
    display: inline-flex; align-items: center; gap: 5px;
    padding: 2px 9px; border-radius: 999px; font-size: 12px; font-weight: 600;
    background: var(--bg-soft); color: var(--text-soft);
}
.badge-primary { background: var(--brand-50); color: var(--brand-700); }
.badge-success { background: var(--success-bg); color: var(--success); }
.badge-warning { background: var(--warning-bg); color: var(--warning); }

/* 「家庭宽带」标（2026-10-16）：蓝框圆角小标签——前台检测结果表的地区名后 / 后台节点列表
   （字号 11px、常规字重——用户反馈「太粗」→ font-weight 从 600 调到 400） */
.badge-home {
    display: inline-block; margin-left: 5px; padding: 0 7px;
    font-size: 11px; line-height: 17px; font-weight: 400;
    color: #3b82f6; border: 1px solid #3b82f6; border-radius: 6px;
    vertical-align: 1px; background: transparent;
}
[data-theme="dark"] .badge-home { color: #60a5fa; border-color: #60a5fa; }

/* TLS 报告「↻ 重新检测」按钮（2026-10-17）：胶囊形、淡品牌底；悬浮边框+图标转半圈；禁用态变淡 */
.tls-refresh-btn {
    display: inline-flex; align-items: center; gap: 5px;
    margin-left: 10px; padding: 4px 13px;
    font-size: 12.5px; font-weight: 600; line-height: 1.7;
    color: var(--brand-700); background: var(--brand-50);
    border: 1px solid transparent; border-radius: 999px; cursor: pointer;
    transition: background .15s ease, border-color .15s ease, transform .1s ease, opacity .15s ease;
}
.tls-refresh-btn:hover { background: var(--brand-100, rgba(99,102,241,.18)); border-color: var(--brand-600); }
.tls-refresh-btn:active { transform: scale(.97); }
.tls-refresh-btn:disabled { opacity: .6; cursor: default; }
.tls-refresh-btn .ic { display: inline-block; transition: transform .35s ease; }
.tls-refresh-btn:hover .ic { transform: rotate(180deg); }
[data-theme="dark"] .tls-refresh-btn { background: rgba(99,102,241,.16); color: #c7d2fe; }
[data-theme="dark"] .tls-refresh-btn:hover { background: rgba(99,102,241,.26); border-color: #6366f1; }
.badge-danger  { background: var(--danger-bg);  color: var(--danger); }
.badge-info    { background: var(--info-bg);    color: var(--info); }
[data-theme="dark"] .badge-primary { background: rgba(99,102,241,.16); color: var(--brand-200); }

/* 会员赞助标签（节点管理列表）：金色流光扫过 + 光环呼吸——一眼能看到（2026-10-16 用户要求"明显一点、动起来"） */
.badge-sponsor {
    background: linear-gradient(100deg, #fbbf24 0%, #f59e0b 20%, #fb7185 50%, #f59e0b 80%, #fbbf24 100%);
    background-size: 220% 100%;
    color: #7c2d12;
    font-weight: 700;
    animation: badge-sponsor-shine 3s linear infinite, badge-sponsor-pulse 2.4s ease-out infinite;
}
@keyframes badge-sponsor-shine {
    from { background-position: 0% 50%; }
    to   { background-position: -220% 50%; }
}
@keyframes badge-sponsor-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(245, 158, 11, .55); }
    70%  { box-shadow: 0 0 0 5px rgba(245, 158, 11, 0); }
    100% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0); }
}
@media (prefers-reduced-motion: reduce) {
    .badge-sponsor { animation: none; }
}

/* 赞助页「配置参考」图标的针脚/经线（.pin）：每根**颜色随机、节奏随机**地闪（两色来自页面 JS
   写入的 --pin-a/--pin-b 随机色池；快慢/相位也由 JS 逐个随机，2026-10-16 用户要求「随机」）。
   主体（芯片框/内存条/地球轮廓）保持各自静态主色；无 JS 不闪（animation-name 无 duration 不跑） */
.pin {
    animation-name: pin-random;
    animation-timing-function: ease-in-out;
    animation-iteration-count: infinite;
}
@keyframes pin-random {
    0%, 45%   { stroke: var(--pin-a, #3b82f6); }
    55%, 100% { stroke: var(--pin-b, #f59e0b); }
}
@media (prefers-reduced-motion: reduce) {
    .pin { animation-name: none; }
}

/* 赞助页「标签页」（2026-10-16，样式仿宝塔文件管理器的页签）：
   「我的节点」固定页签 + 「＋」打开「添加节点」页签（可 ✕ 关闭） */
.sp-tabs { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-bottom: 12px; }
.sp-tab {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 7px 13px; border-radius: 8px; cursor: pointer;
    border: 1px solid var(--border); background: var(--bg-soft);
    color: var(--text-soft); font-size: 13.5px; font-weight: 600;
}
.sp-tab.active { background: var(--brand-50); border-color: var(--brand-600); color: var(--brand-700); }
[data-theme="dark"] .sp-tab.active { background: rgba(99,102,241,.16); color: var(--brand-200); border-color: var(--brand-600); }
.sp-tab-close {
    border: none; background: none; color: inherit; padding: 0 0 0 2px;
    font-size: 15px; line-height: 1; opacity: .55; cursor: pointer;
}
.sp-tab-close:hover { opacity: 1; }
.sp-tab-add {
    padding: 6px 12px; border-radius: 8px; cursor: pointer;
    border: 1px dashed var(--border); background: none;
    color: var(--text-soft); font-size: 15px; line-height: 1; font-weight: 700;
}
.sp-tab-add:hover { border-color: var(--brand-600); color: var(--brand-700); }

/* 提示条 */
.alert { border-radius: var(--radius-sm); padding: 11px 14px; font-size: 13.5px; border: 1px solid; margin-bottom: 14px; }
.alert-info    { background: var(--info-bg);    border-color: #bae6fd; color: #075985; }
.alert-success { background: var(--success-bg); border-color: #bbf7d0; color: #166534; }
.alert-warning { background: var(--warning-bg); border-color: #fde68a; color: #92400e; }
.alert-danger,
.alert-error   { background: var(--danger-bg);  border-color: #fecaca; color: #991b1b; }
[data-theme="dark"] .alert-info { color: #7dd3fc; }
[data-theme="dark"] .alert-success { color: #86efac; }
[data-theme="dark"] .alert-warning { color: #fcd34d; }
[data-theme="dark"] .alert-danger,
[data-theme="dark"] .alert-error { color: #fca5a5; }

/* 表单 */
.field { margin-bottom: 14px; }
/* 表单里的说明文字永远单独一行（输入框收窄后，它会跑到输入框右边去） */
.field > .text-sm { display: block; }
/* 开关类字段（6 个功能开关）：开关在左、标题在右，说明独占一行 */
.field-switch { display: flex; flex-wrap: wrap; align-items: center; column-gap: 10px; }
.field-switch > .switch { order: 1; margin: 0; }
.field-switch > .label { order: 2; margin: 0; }
.field-switch > .text-sm { order: 3; flex: 0 0 100%; margin-top: 8px; }
/* 供应商：选「自定义…」时下拉缩到 118px + 右边并排输入框；选分类里的值时只显示下拉（占满整行）。
   两种情况都是「一行控件 + 一行说明」，这一格和同排其它格等高，不会把整行撑高。 */
.provider-line { display: flex; align-items: center; gap: 8px; }
.provider-line .cselect { flex: 1 1 auto; width: 100% !important; }
.provider-line .input { flex: 1 1 auto; min-width: 0; }
.provider-line.is-custom .cselect { flex: 0 0 auto; width: 118px !important; }
.label { display: block; font-size: 13px; font-weight: 600; margin-bottom: 6px; color: var(--text-soft); }
.input, .select, .textarea {
    width: 100%; padding: 10px 12px; border-radius: 10px;
    border: 1px solid var(--border-strong); background: var(--surface); color: var(--text);
    font-size: 14px; font-family: inherit; transition: .15s ease;
}
.input:focus, .select:focus, .textarea:focus {
    outline: none; border-color: var(--brand-500);
    box-shadow: 0 0 0 3px rgba(99, 102, 241, .18);
}
.input::placeholder, .textarea::placeholder { color: var(--text-mute); }
textarea.textarea { min-height: 110px; resize: vertical; }
.hint { font-size: 12px; color: var(--text-mute); margin-top: 5px; }
.input-group { display: flex; gap: 8px; }
.input-group .input { flex: 1; }
.checkbox, .radio { display: inline-flex; align-items: center; gap: 7px; font-size: 13.5px; cursor: pointer; }
.checkbox input, .radio input { width: 16px; height: 16px; accent-color: var(--brand-600); }

/* 表格 */
.table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
table.table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.table th {
    text-align: left; padding: 11px 14px; background: var(--surface-2);
    color: var(--text-soft); font-weight: 650; font-size: 12.5px; white-space: nowrap;
    border-bottom: 1px solid var(--border);
}
.table td { padding: 11px 14px; border-bottom: 1px solid var(--border); vertical-align: middle; }
.table tr:last-child td { border-bottom: 0; }
/* 空徽章不显示（节点列表里除第一行外的「版本」格先留空，数据回来 JS 填上才出现） */
.badge:empty { display: none; }
.table tbody tr:hover { background: var(--bg-soft); }
.table .actions { white-space: nowrap; }

/* 分页 */
.pagination { display: flex; align-items: center; gap: 6px; margin-top: 16px; flex-wrap: wrap; }
.pagination a, .pagination span {
    min-width: 34px; height: 34px; padding: 0 10px; border-radius: 9px;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--border); background: var(--surface); color: var(--text-soft); font-size: 13px;
}
.pagination a:hover { border-color: var(--brand-400); color: var(--brand-600); text-decoration: none; }
.pagination .current { background: var(--brand-600); border-color: var(--brand-600); color: #fff; font-weight: 650; }
.pagination .disabled { opacity: .45; }

/* 空状态 */
.empty { text-align: center; padding: 44px 20px; color: var(--text-mute); }
.empty .icon { font-size: 34px; margin-bottom: 10px; opacity: .65; }

/* 下拉菜单 */
.dropdown { position: relative; }
.dropdown-menu {
    position: absolute; right: 0; top: calc(100% + 8px); min-width: 190px; z-index: 60;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    box-shadow: var(--shadow-lg); padding: 6px; display: none;
}
.dropdown-menu.open { display: block; }
.dropdown-menu a, .dropdown-menu button {
    display: flex; width: 100%; align-items: center; gap: 9px;
    padding: 8px 11px; border-radius: 8px; color: var(--text-soft);
    font-size: 13.5px; background: none; border: 0; cursor: pointer; text-align: left;
}
.dropdown-menu a:hover, .dropdown-menu button:hover { background: var(--bg-soft); color: var(--text); text-decoration: none; }
.dropdown-divider { height: 1px; background: var(--border); margin: 5px 4px; }

/* 页签 */
.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin-bottom: 18px; overflow-x: auto; }
.tabs a, .tabs button {
    padding: 9px 14px; border: 0; background: none; cursor: pointer; font-size: 13.5px;
    color: var(--text-soft); border-bottom: 2px solid transparent; font-weight: 600; white-space: nowrap;
}
.tabs a.active, .tabs button.active { color: var(--brand-600); border-bottom-color: var(--brand-600); }
.tabs a:hover, .tabs button:hover { color: var(--text); text-decoration: none; }

/* 步骤条 */
.steps { display: flex; gap: 8px; margin-bottom: 18px; flex-wrap: wrap; }
.step { display: flex; align-items: center; gap: 8px; color: var(--text-mute); font-size: 13px; }
.step .dot {
    width: 24px; height: 24px; border-radius: 50%; display: grid; place-items: center;
    background: var(--bg-soft); font-size: 12px; font-weight: 700;
}
.step.active { color: var(--brand-600); font-weight: 650; }
.step.active .dot { background: var(--brand-600); color: #fff; }
.step.done .dot { background: var(--success); color: #fff; }

/* 上传区 */
.dropzone {
    border: 2px dashed var(--border-strong); border-radius: var(--radius-lg);
    padding: 34px 20px; text-align: center; cursor: pointer; transition: .18s ease;
    background: var(--surface-2);
}
.dropzone:hover, .dropzone.dragover { border-color: var(--brand-500); background: var(--brand-50); }
[data-theme="dark"] .dropzone:hover, [data-theme="dark"] .dropzone.dragover { background: rgba(99,102,241,.1); }
.dropzone .icon { font-size: 34px; margin-bottom: 8px; }
.dropzone .file { margin-top: 10px; font-weight: 650; color: var(--brand-600); }

/* 进度条（子元素 <i> 或 <span> 都支持：持续PING 的质量条用的是 span） */
.progress { height: 10px; border-radius: 999px; background: var(--bg-soft); overflow: hidden; }
.progress > i,
.progress > span {
    display: block; height: 100%; border-radius: 999px;
    background: linear-gradient(90deg, var(--brand-500), #7c3aed);
    transition: width .3s ease;
}

/* 加载 */
.spinner {
    width: 16px; height: 16px; border: 2px solid rgba(255,255,255,.35); border-top-color: #fff;
    border-radius: 50%; animation: spin .7s linear infinite; display: inline-block;
}
.spinner.soft { border-color: var(--border-strong); border-top-color: var(--brand-600); }
@keyframes spin { to { transform: rotate(360deg); } }

/* Toast */
.toast-wrap { position: fixed; right: 18px; top: 70px; z-index: 200; display: flex; flex-direction: column; gap: 10px; }
.toast {
    min-width: 240px; max-width: 360px; background: var(--surface); border: 1px solid var(--border);
    border-left: 4px solid var(--brand-500); border-radius: var(--radius); box-shadow: var(--shadow-lg);
    padding: 12px 14px; font-size: 13.5px; animation: slideIn .2s ease;
}
.toast.success { border-left-color: var(--success); }
.toast.error { border-left-color: var(--danger); }
.toast.warning { border-left-color: var(--warning); }
@keyframes slideIn { from { transform: translateX(16px); opacity: 0; } }

/* 弹窗 */
.modal-mask {
    position: fixed; inset: 0; background: rgba(15, 23, 42, .55); z-index: 150;
    display: none; align-items: center; justify-content: center; padding: 20px;
}
.modal-mask.open { display: flex; }
.modal {
    background: var(--surface); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
    width: 100%; max-width: 520px; max-height: 88vh; overflow: auto; padding: 22px;
}
.modal-lg { max-width: 760px; }
/* 可拖动 / 可缩放的弹窗（<div class="modal" data-move-resize>）：
   标题栏按住能拖；右下角抓手能缩放。默认打开时还是原来的大小和居中。 */
.modal[data-move-resize] { position: relative; }
/* 标题下面来一条分割线，跟下面的表单分开 */
.modal[data-move-resize] .card-head { padding-bottom: 12px; border-bottom: 1px solid var(--border); }
.modal[data-move-resize] .card-head { cursor: move; }
.modal[data-move-resize] .card-head button,
.modal[data-move-resize] .card-head a { cursor: pointer; }
/* 缩放的抓手：右下角三根斜线、一根比一根长（经典握把，线由 JS 插的 SVG 画） */
.modal-resize {
    position: absolute; right: 2px; bottom: 2px; width: 32px; height: 32px;
    display: inline-flex; align-items: center; justify-content: center;
    cursor: nwse-resize; border-radius: 6px;
    color: var(--brand-500);   /* 换成主题蓝紫，比灰色显眼 */
}
.modal-resize:hover { color: var(--brand-700); background-color: var(--bg-soft); }
/* 表单格子别拉满整列：输入框/下拉短一点（340px 够放内容，也不显得空）。
   重启节点那块是整行的，宽度跟着三列的总宽走。 */
.modal[data-move-resize] .grid .field { max-width: 300px; }
/* 列宽按字段宽收紧、间距 16px：三列整体靠左（不然每列右边都空一截） */
.modal[data-move-resize] .grid-3 { grid-template-columns: repeat(3, 300px); column-gap: 16px; justify-content: start; }
.modal[data-move-resize] .restart-box { max-width: calc(3 * 300px + 32px); }
/* 内容短的几个（节点地址 / 地区 / 系统类型 / 赞助商链接）：只把控件收短，
   下面那行说明照旧占满整列 —— 不然说明会被挤得折行 */
.modal[data-move-resize] .field-sm .input,
.modal[data-move-resize] .field-sm .cselect { max-width: 280px; }

/* 弹窗标题栏右侧那组按钮（恢复默认 + 关闭） */
.modal-head-actions { display: inline-flex; align-items: center; gap: 6px; }
.modal-reset-btn { font-size: 12px; color: var(--text-soft); }
body.is-dragging { user-select: none; cursor: move; }
body.is-resizing { user-select: none; cursor: nwse-resize; }

/* 顶部横幅（首页 Hero） */
.hero {
    background: radial-gradient(1100px 380px at 12% -10%, rgba(99,102,241,.30), transparent 60%),
                radial-gradient(900px 320px at 88% 0%, rgba(219,39,119,.22), transparent 60%),
                linear-gradient(180deg, var(--bg-soft), var(--bg));
    border-bottom: 1px solid var(--border);
    padding: 52px 0 44px;
}
.hero h1 { font-size: 34px; letter-spacing: -.8px; }
.hero .lead { font-size: 15.5px; color: var(--text-soft); max-width: 640px; }
.hero-actions { display: flex; gap: 12px; margin-top: 22px; flex-wrap: wrap; }
.gradient-text {
    background: linear-gradient(90deg, var(--brand-600), #7c3aed 55%, #db2777);
    -webkit-background-clip: text; background-clip: text; color: transparent;
}

/* 功能卡片 */
.feature-icon {
    width: 38px; height: 38px; border-radius: 11px; display: grid; place-items: center;
    background: var(--brand-50); color: var(--brand-600); font-size: 18px; margin-bottom: 10px;
}
[data-theme="dark"] .feature-icon { background: rgba(99,102,241,.16); color: var(--brand-200); }

/* 价格卡 */
.plan-card { position: relative; display: flex; flex-direction: column; }
.plan-card .price { font-size: 30px; font-weight: 780; letter-spacing: -1px; }
.plan-card .price small { font-size: 13px; font-weight: 500; color: var(--text-mute); }
.plan-card ul { list-style: none; padding: 0; margin: 14px 0 18px; display: grid; gap: 7px; font-size: 13.5px; }
.plan-card li::before { content: "✓"; color: var(--success); font-weight: 800; margin-right: 8px; }
.plan-card.highlight { border-color: var(--brand-500); box-shadow: 0 12px 34px rgba(79,70,229,.18); }
.plan-card .ribbon {
    position: absolute; top: -11px; right: 16px; background: var(--brand-600); color: #fff;
    font-size: 11.5px; font-weight: 700; padding: 3px 10px; border-radius: 999px;
}

/* 用户中心 / 后台 侧栏布局 */
.layout-side { display: grid; grid-template-columns: 232px minmax(0, 1fr); gap: 22px; align-items: start; }
.side-nav {
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    padding: 10px; position: sticky; top: calc(var(--header-h) + 18px);
}
.side-nav .group-label { font-size: 11.5px; color: var(--text-mute); font-weight: 700; padding: 10px 10px 4px; letter-spacing: .04em; }
.side-nav a {
    display: flex; align-items: center; gap: 9px; padding: 8px 10px; border-radius: 9px;
    color: var(--text-soft); font-size: 13.5px; font-weight: 550; margin-bottom: 1px;
}
.side-nav a:hover { background: var(--bg-soft); color: var(--text); text-decoration: none; }
.side-nav a.active { background: var(--brand-50); color: var(--brand-700); font-weight: 650; }
[data-theme="dark"] .side-nav a.active { background: rgba(99,102,241,.16); color: var(--brand-200); }
.side-nav .icon { width: 18px; text-align: center; }
.side-nav form { margin: 0; }
.side-nav button {
    display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
    padding: 8px 10px; border-radius: 9px; border: 0; background: none; cursor: pointer;
    color: var(--text-soft); font: inherit; font-size: 13.5px; font-weight: 550; margin-bottom: 1px;
}
.side-nav button:hover { background: var(--bg-soft); color: var(--text); }

/* 会员中心 */
.member-avatar {
    width: 54px; height: 54px; border-radius: 50%; flex: 0 0 auto;
    display: grid; place-items: center; color: #fff; line-height: 1;
    font-size: 22px; font-weight: 750;
    background: linear-gradient(135deg, var(--brand-500), #7c3aed 60%, #db2777);
    box-shadow: 0 8px 18px -8px rgba(99,102,241,.85);
}
.member-avatar.sm { width: 38px; height: 38px; font-size: 16px; }
.member-hero { display: flex; align-items: center; justify-content: space-between; gap: 18px; flex-wrap: wrap; }
.member-hero .who { display: flex; align-items: center; gap: 14px; min-width: 0; }
.member-hero .name { font-size: 19px; font-weight: 750; letter-spacing: -.3px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.member-hero .stats { display: flex; gap: 28px; flex-wrap: wrap; }
.member-side-id { display: flex; align-items: center; gap: 10px; padding: 6px 10px 12px; border-bottom: 1px solid var(--border); margin-bottom: 6px; }
.member-side-id .nm { font-weight: 700; font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* 后台 */
.admin-shell { display: grid; grid-template-columns: 240px minmax(0, 1fr); min-height: 100vh; }
.admin-side {
    background: #111827; color: #cbd5e1; padding: 16px 12px; position: sticky; top: 0; height: 100vh; overflow: auto;
}
[data-theme="dark"] .admin-side { background: #0b0e16; }
.admin-side .brand { color: #fff; margin: 4px 8px 18px; }
.admin-side .group-label { font-size: 11px; color: #64748b; font-weight: 700; padding: 12px 10px 4px; letter-spacing: .05em; }
.admin-side a {
    display: flex; align-items: center; gap: 10px; padding: 8px 11px; border-radius: 9px;
    color: #cbd5e1; font-size: 13.5px; margin-bottom: 1px;
}
.admin-side a:hover { background: rgba(148,163,184,.14); color: #fff; text-decoration: none; }
.admin-side a.active { background: var(--brand-600); color: #fff; font-weight: 600; }
.admin-main { min-width: 0; background: var(--bg); }
.admin-topbar {
    height: 56px; background: var(--surface); border-bottom: 1px solid var(--border);
    display: flex; align-items: center; justify-content: space-between; padding: 0 20px; gap: 12px;
    position: sticky; top: 0; z-index: 40;
}
.admin-body { padding: 22px; }
.admin-side-toggle { display: none; }

/* 侧栏遮罩（移动端） */
.side-mask { display: none; }

/* 页脚 */
.site-footer { border-top: 1px solid var(--border); background: var(--surface); margin-top: 40px; }
.site-footer .inner { padding: 30px 0 24px; }
.footer-grid { display: grid; grid-template-columns: 1.4fr repeat(3, 1fr); gap: 22px; }
.footer-grid h4 { font-size: 13px; margin-bottom: 10px; }
.footer-grid a { display: block; color: var(--text-soft); font-size: 13px; padding: 3px 0; }
.footer-grid a:hover { color: var(--brand-600); text-decoration: none; }
.footer-bottom {
    border-top: 1px solid var(--border); padding: 14px 0; font-size: 12.5px; color: var(--text-mute);
    display: flex; justify-content: space-between; gap: 10px; flex-wrap: wrap;
}

/* 代码块复制按钮 */
.code-wrap { position: relative; }
.code-wrap .copy {
    position: absolute; right: 10px; top: 10px; font-size: 12px; padding: 4px 9px;
    border-radius: 7px; border: 1px solid rgba(255,255,255,.25); background: rgba(255,255,255,.08);
    color: #e2e8f0; cursor: pointer;
}
.code-wrap .copy:hover { background: rgba(255,255,255,.18); }

/* 文件/新闻列表 */
.list-item { display: flex; gap: 14px; padding: 14px 0; border-bottom: 1px solid var(--border); }
.list-item:last-child { border-bottom: 0; }
.list-item .thumb {
    width: 120px; height: 74px; border-radius: 10px; flex: none; object-fit: cover;
    background: linear-gradient(135deg, var(--bg-soft), var(--border));
}

/* 工具页专用 */
.result-box {
    background: var(--surface-2); border: 1px dashed var(--border-strong);
    border-radius: var(--radius); padding: 16px; font-size: 13.5px;
}
.node-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 0; border-bottom: 1px dashed var(--border); }
.node-row:last-child { border-bottom: 0; }
.latency { font-family: var(--mono); font-weight: 700; }
.latency.good { color: var(--success); }
.latency.mid { color: var(--warning); }
.latency.bad { color: var(--danger); }

/* ---------- 4.5 兼容别名（后台旧页面使用） ---------- */
.muted { color: var(--text-mute); font-size: 12px; }
.linkbtn { background: none; border: 0; color: var(--brand-600); cursor: pointer; font-size: 13px; padding: 0; }
.linkbtn:hover { text-decoration: underline; }
form.logout { display: inline; }

/* ---------- 5. 响应式 ---------- */
@media (max-width: 1024px) {
    .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .footer-grid { grid-template-columns: 1fr 1fr; }
    .admin-shell { grid-template-columns: 1fr; }
    .admin-side {
        position: fixed; left: 0; top: 0; z-index: 120; width: 250px;
        transform: translateX(-102%); transition: transform .22s ease;
    }
    .admin-side.open { transform: none; }
    .admin-side-toggle { display: inline-flex; }
    .side-mask.open { display: block; position: fixed; inset: 0; background: rgba(15,23,42,.5); z-index: 110; }
}

@media (max-width: 768px) {
    .main-nav { display: none; }
    .nav-toggle { display: inline-flex; }
    .main-nav.open {
        display: flex; flex-direction: column; align-items: stretch;
        position: absolute; left: 0; right: 0; top: var(--header-h);
        background: var(--surface); border-bottom: 1px solid var(--border);
        padding: 10px 16px 16px; box-shadow: var(--shadow);
    }
    .layout-side { grid-template-columns: 1fr; }
    .side-nav { position: static; }
    .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
    .hero { padding: 36px 0 30px; }
    .hero h1 { font-size: 26px; }
    .footer-grid { grid-template-columns: 1fr; }
    .admin-body { padding: 14px; }
    .toast-wrap { left: 14px; right: 14px; top: 66px; }
}

/* ==========================================================================
   tcpping.cn 定制（追加，勿与上游 phpencode 的规则冲突）
   ========================================================================== */

/* 节点名称列：不换行，至少容纳 8 个汉字 + 运营商徽章 */
.table .col-node { min-width: 168px; white-space: nowrap; }

/* 测速结果表格：加大列间距，读数更清楚 */
.table-lg th { padding: 12px 18px; }
.table-lg td { padding: 13px 18px; }

/* 结果表格里的延迟/耗时数字用等宽字体，竖排对齐 */
.table .num { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* 持续PING 结果表：百分比列宽 + 数字等宽 —— 采样刷新时不跳动，且任何窗口宽度都不横向溢出 */
#cx-result { table-layout: fixed; min-width: 0; width: 100%; }
#cx-result col:nth-child(1) { width: 18%; }                                   /* 节点 */
#cx-result col:nth-child(2), #cx-result col:nth-child(3),
#cx-result col:nth-child(4), #cx-result col:nth-child(5) { width: 11%; }      /* 最近/最小/平均/最大 */
#cx-result col:nth-child(6) { width: 8%; }                                    /* 丢包 */
#cx-result col:nth-child(7) { width: 30%; }                                   /* 网络质量（宽度自适应） */
#cx-result .sample-chart { width: 100%; min-width: 0; max-width: none; }
#cx-result td[data-role="last"], #cx-result td[data-role="min"], #cx-result td[data-role="avg"],
#cx-result td[data-role="max"], #cx-result td[data-role="loss"] {
    font-family: var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap;
    overflow: hidden; text-overflow: ellipsis;   /* 窗口特别窄时裁掉，绝不撑破表格 */
}
/* 窄屏（手机）：图表列让位，把宽度还给延迟数字 */
@media (max-width: 860px) {
    #cx-result col:nth-child(1) { width: 26%; }
    #cx-result col:nth-child(2), #cx-result col:nth-child(3),
    #cx-result col:nth-child(4), #cx-result col:nth-child(5) { width: 15%; }
    #cx-result col:nth-child(6) { width: 14%; }
    #cx-result col:nth-child(7) { width: 0; }
    #cx-result thead th:last-child, #cx-result tbody td:last-child { display: none; }
}

@media (max-width: 768px) {
    .table .col-node { min-width: 130px; }
    .table-lg th, .table-lg td { padding: 10px 12px; }
}
/* 线路筛选 chips（参考生产站首页） */
.filter-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }

/* 前台「最近检测」chip 行（表单下方，没记录时整行隐藏） */
.recent-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 10px; }
.recent-row[hidden] { display: none; }
.recent-label { font-size: 12.5px; color: var(--text-mute); margin-right: 2px; }
.recent-chips { display: inline-flex; flex-wrap: wrap; gap: 6px; }
.recent-chip {
    display: inline-block; max-width: 200px; padding: 3px 11px; border-radius: 999px;
    border: 1px solid var(--border-strong); background: var(--surface); color: var(--text-soft);
    font-size: 12.5px; font-family: inherit; cursor: pointer; transition: .15s ease;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.recent-chip:hover { border-color: var(--brand-400); color: var(--brand-500); background: var(--surface-2); }
.filter-chip { position: relative; display: inline-flex; }
.filter-chip input { position: absolute; opacity: 0; pointer-events: none; }
.filter-chip span {
    display: inline-block; padding: 6px 16px; border-radius: 20px; font-size: 13px; font-weight: 500;
    cursor: pointer; border: 1.5px solid var(--border-strong); color: var(--text-soft);
    background: var(--surface); transition: .2s; user-select: none;
}
.filter-chip span:hover { border-color: var(--brand-400); color: var(--text); }
.filter-chip input:checked + span {
    color: var(--brand-700); border-color: var(--brand-500); background: var(--brand-50); font-weight: 600;
}
.filter-chip input:not(:checked) + span { opacity: .55; }

/* 解析统计：IP 标签 */
.ip-tags { display: flex; flex-wrap: wrap; gap: 8px; max-height: 220px; overflow-y: auto; }
.ip-tag {
    display: inline-flex; align-items: center; padding: 6px 14px;
    background: var(--brand-50); border: 1px solid var(--brand-200); border-radius: 20px; font-size: 13px;
}
.ip-tag .ip-addr { color: var(--text); font-weight: 500; font-family: var(--mono); }
.ip-tag .ip-count {
    color: var(--brand-700); font-weight: 700; margin-left: 10px; padding-left: 10px;
    border-left: 1px solid var(--brand-200);
}

/* 小标签页标题 */
.pill-tab {
    display: inline-block; padding: 6px 14px; border-radius: 999px;
    background: var(--brand-50); color: var(--brand-700); font-size: 13px; font-weight: 600;
}

/* 被线路筛选过滤掉的行 */
/* 未勾选线路的节点行：直接隐藏（选了电信就只看到电信的节点） */
tr.row-filtered { display: none; }
/* 语义文字色工具类（延迟着色 / 状态提示依赖它们） */
.text-success { color: var(--success); }
.text-danger  { color: var(--danger); }
.text-warning { color: var(--warning); }
.text-info    { color: var(--info); }
.text-primary { color: var(--brand-600); }
/* 开关（用于节点列表的「显示 / 统计」） */
.switch { position: relative; display: inline-block; width: 40px; height: 22px; vertical-align: middle; cursor: pointer; flex: 0 0 auto; }
/* 卡片头部那一排控件（开关 / 下拉）：不参与收缩 —— 否则开关会被挤变形、被下拉盖住
   （cselect 会给下拉包一层 span，没写宽度时是 width:100%，会把兄弟节点挤扁） */
.card-head .switch, .card-head .cselect { flex: 0 0 auto; }
.switch input[type="checkbox"] { position: absolute; opacity: 0; width: 0; height: 0; }
.switch .slider {
    position: absolute; inset: 0; background: var(--border-strong); border-radius: 999px;
    transition: background .2s ease;
}
.switch .slider::before {
    content: ''; position: absolute; width: 18px; height: 18px; left: 2px; top: 2px;
    background: #fff; border-radius: 50%; box-shadow: 0 1px 2px rgba(15,23,42,.25);
    transition: transform .2s ease;
}
.switch input:checked + .slider { background: var(--success); }
.switch input:checked + .slider::before { transform: translateX(18px); }
.switch input:focus-visible + .slider { box-shadow: 0 0 0 3px var(--brand-100, rgba(99,102,241,.25)); }
.switch input:disabled + .slider { opacity: .5; cursor: not-allowed; }

/* 「状态」列那种开关：文字写在开关里面（开=旋钮在右、字在左；关=旋钮在左、字在右）。
   所以开关要加宽，旋钮行程也跟着变长；关闭态底色加深一点，白字才看得清。 */
.switch-labeled { width: 54px; }
.switch-labeled input:not(:checked) + .slider { background: var(--text-mute); }
.switch-labeled input:checked + .slider::before { transform: translateX(32px); }
.switch-labeled .sw-text {
    position: absolute; top: 0; height: 22px; line-height: 22px;
    font-size: 10.5px; color: #fff; pointer-events: none;
    transition: left .2s ease, right .2s ease;
}
.switch-labeled input:not(:checked) + .slider .sw-text { right: 6px; left: auto; }
.switch-labeled input:checked + .slider .sw-text { left: 6px; right: auto; }
/* 深色提示气泡（app.js 的 data-tip 用）：黑底白字 + 下方小箭头，元素贴屏幕顶时翻到下方 */
.tp-tip {
    position: fixed; z-index: 4000; left: 0; top: 0;
    max-width: min(380px, calc(100vw - 24px));
    padding: 6px 10px; border-radius: 8px;
    background: #1e293b; color: #fff;
    /* pre-line：data-tip 里写 &#10;（换行）就能分多行显示 */
    font-size: 12.5px; line-height: 1.5; white-space: pre-line;
    box-shadow: 0 6px 18px rgba(15, 23, 42, .28);
    /* left 由 JS 设定（气泡左边对齐元素左边，不再整体居中）；--arrow-x 是箭头相对气泡左边缘的位置 */
    opacity: 0; pointer-events: none; transition: opacity .12s ease;
}
.tp-tip.is-open { opacity: 1; }
/* 内容多的气泡（元素上写 data-tip-wide）：放宽上限，别两三行就折 —— 只在内容真需要时才变宽。
   max-width 只是上限：内容不足时气泡照旧贴合内容，不会白变宽（560 → 680 是给「评分规则」整句留一行，2026-10-16） */
.tp-tip.is-wide { max-width: min(680px, calc(100vw - 24px)); }
/* 小箭头：旋转 45° 的方块（默认指向下方 —— 气泡在元素上方） */
.tp-tip::after {
    content: ''; position: absolute; left: var(--arrow-x, 50%); bottom: -4px;
    width: 8px; height: 8px; margin-left: -4px;
    background: inherit; transform: rotate(45deg);
}
.tp-tip.is-below::after { bottom: auto; top: -4px; }
/* data-tip-html 那种"有排版"的气泡：标题 / 浅色说明 / 两列清单 / 底部小字 */
/* 标题和底部小字不给它自动折行（它们本来就短，折了很难看；宽度由气泡自己撑开） */
.tp-tip-title { font-size: 13px; font-weight: 700; white-space: nowrap; }
.tp-tip-foot { white-space: nowrap; }
/* 气泡里的「开 / 关」（深色底上用亮一点的绿/红，才看得清）；也可以用来包内联的勾/叉图标 */
.tp-tip .tip-on { color: #4ade80; font-weight: 600; }
.tp-tip .tip-off { color: #f87171; font-weight: 600; }
/* 评级标准气泡里的等级配色（深色底用亮色版）：A 系绿、B/C 橙、D 以下红 —— 和页面上评级徽章一个口径 */
.tp-tip .tip-grade-a { color: #4ade80; font-weight: 700; }
.tp-tip .tip-grade-b { color: #fbbf24; font-weight: 700; }
.tp-tip .tip-grade-c { color: #f87171; font-weight: 700; }
/* 评级标准气泡「三项给分」的分组色：每组一个淡淡的色块（深色底上的低饱和亮色，好区分又不刺眼） */
.tp-tip .tip-cat { display: inline-block; padding: 0 7px; border-radius: 5px; }
.tp-tip .tip-cat-proto  { background: rgba(96, 165, 250, .16); color: #93c5fd; }
.tp-tip .tip-cat-kx     { background: rgba(167, 139, 250, .18); color: #c4b5fd; }
.tp-tip .tip-cat-cipher { background: rgba(74, 222, 128, .15);  color: #86efac; }
/* 三项给分里「每条分值」的颜色：按分数高低 —— 高分绿、中间橙、低分红（弱协议 / 弱算法一眼可见） */
.tp-tip .tip-val-good { color: #86efac; }
.tp-tip .tip-val-mid  { color: #fcd34d; }
.tp-tip .tip-val-bad  { color: #fca5a5; }
.tp-tip .tip-on svg, .tp-tip .tip-off svg { vertical-align: -1px; }
.tp-tip-sub { margin-top: 4px; color: #cbd5e1; }
.tp-tip-list {
    display: grid; grid-template-columns: auto auto; gap: 3px 16px;
    margin-top: 9px; padding-top: 9px; border-top: 1px solid rgba(255, 255, 255, .14);
}
.tp-tip-list b { font-weight: 400; color: #94a3b8; }
.tp-tip-foot {
    margin-top: 9px; padding-top: 9px; border-top: 1px solid rgba(255, 255, 255, .14);
    color: #94a3b8;
}

/* 提示浮层：顶部居中（原来在右上角），并按类型区分左色条 */
.toast-wrap {
    left: 50%; right: auto; top: 76px; transform: translateX(-50%);
    align-items: center;
}
.toast.info { border-left-color: var(--info); }
.toast { text-align: left; }

/* ---------- 下拉框美化：去掉系统箭头，统一自绘箭头 ---------- */
.select {
    appearance: none; -webkit-appearance: none; -moz-appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%238a93a6' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 11px center;
    background-size: 12px 12px;
    padding-right: 32px;
    cursor: pointer;
}
.select:hover { border-color: var(--brand-400); }
.select::-ms-expand { display: none; }
.select option { background: var(--surface); color: var(--text); }
[data-theme="dark"] .select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23a6adc0' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}

/* ---------- 文件选择美化：点击 / 拖拽区 ---------- */
.file-pick {
    position: relative; display: flex; align-items: center; gap: 12px;
    padding: 12px 14px; border-radius: 12px;
    border: 1.5px dashed var(--border-strong); background: var(--surface-2);
    cursor: pointer; transition: border-color .15s ease, background .15s ease, box-shadow .15s ease;
}
.file-pick:hover { border-color: var(--brand-400); background: var(--brand-50); }
.file-pick:focus-within { border-color: var(--brand-500); box-shadow: 0 0 0 3px rgba(99, 102, 241, .18); }
.file-pick.dragging {
    border-color: var(--brand-500); border-style: solid; background: var(--brand-50);
    box-shadow: 0 0 0 3px rgba(99, 102, 241, .18);
}
.file-pick.has-file { border-style: solid; border-color: var(--brand-400); background: var(--brand-50); }
.file-pick.has-error { border-style: solid; border-color: var(--danger); background: var(--danger-bg); }
[data-theme="dark"] .file-pick:hover,
[data-theme="dark"] .file-pick.dragging,
[data-theme="dark"] .file-pick.has-file { background: var(--surface-2); }
[data-theme="dark"] .file-pick.has-error { background: var(--danger-bg); }
/* 原生文件输入藏起来但保持可聚焦（required 校验气泡仍能定位到它） */
.file-pick input[type="file"] {
    position: absolute; width: 1px; height: 1px; margin: 0; padding: 0;
    border: 0; opacity: 0; overflow: hidden; clip: rect(0 0 0 0);
}
.file-pick-icon {
    flex: 0 0 auto; width: 40px; height: 40px; border-radius: 10px;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--surface); border: 1px solid var(--border); font-size: 19px;
}
.file-pick-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.file-pick-name {
    font-weight: 600; font-size: 13.5px; color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.file-pick.has-file .file-pick-name { color: var(--brand-700); }
.file-pick.has-error .file-pick-name { color: var(--danger); }
[data-theme="dark"] .file-pick.has-file .file-pick-name { color: var(--brand-200); }
[data-theme="dark"] .file-pick.has-error .file-pick-name { color: #fca5a5; }

/* ---------- 自绘下拉框（cselect）：原生 select 渐进增强 ---------- */
.cselect { position: relative; display: inline-block; vertical-align: middle; max-width: 100%; }
/* 原生 select 隐藏但留在表单里（值和 change 事件都不受影响） */
.cselect-native {
    position: absolute; width: 1px; height: 1px; margin: 0; padding: 0;
    border: 0; opacity: 0; overflow: hidden; clip: rect(0 0 0 0);
}
.cselect-trigger {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    width: 100%; min-height: 42px; margin: 0; padding: 10px 12px;
    border: 1px solid var(--border-strong); border-radius: 10px;
    background: var(--surface); color: var(--text);
    font-family: inherit; font-size: 14px; line-height: 1.3; text-align: left;
    cursor: pointer; transition: border-color .15s ease, box-shadow .15s ease;
}
.cselect-trigger:hover { border-color: var(--brand-400); }
.cselect-trigger:focus-visible,
.cselect.is-open .cselect-trigger {
    outline: none; border-color: var(--brand-500);
    box-shadow: 0 0 0 3px rgba(99, 102, 241, .18);
}
.cselect-trigger:disabled { opacity: .6; cursor: not-allowed; }
.cselect-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cselect.is-placeholder .cselect-value { color: var(--text-mute); }
.cselect-arrow { flex: 0 0 auto; display: inline-flex; color: var(--text-mute); transition: transform .18s ease; }
.cselect.is-open .cselect-arrow { transform: rotate(180deg); }
/* 表格内的小号 */
.cselect-sm .cselect-trigger { min-height: 0; padding: 5px 8px; font-size: 13px; border-radius: 8px; gap: 6px; }

/* 浮层：挂到 body 上、fixed 定位，避免被表格 overflow / 弹窗裁掉 */
.cselect-panel {
    position: fixed; z-index: 3000; max-height: 300px; overflow-y: auto;
    /* 带说明的选项（复制品的 cselect-option-note）会更高，见下面 .has-notes */
    /* 左右不留内边距：高亮条铺满面板宽度，只在上下留一点呼吸空间 */
    padding: 6px 0; border: 1px solid var(--border); border-radius: 10px;
    background: var(--surface); box-shadow: 0 12px 32px rgba(15, 23, 42, .16);
}
.cselect-option {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    padding: 9px 12px; border-radius: 0; font-size: 13.5px; color: var(--text);
    white-space: nowrap; cursor: pointer; transition: background .12s ease, color .12s ease;
}
/* 悬停/当前项：淡灰底（不用主色实心块），文字保持正常颜色 */
.cselect-option.is-active { background: rgba(15, 23, 42, .07); color: var(--text); }
[data-theme="dark"] .cselect-option.is-active { background: rgba(255, 255, 255, .08); }
.cselect-option.is-selected { font-weight: 600; }
/* 带说明的选项（<option data-note="…">）：下面多一行小字解释「这是什么意思」 */
.cselect-option.has-note { display: block; white-space: normal; }
.cselect-option-note {
    display: block; margin-top: 2px;
    font-size: 11.5px; line-height: 1.35; font-weight: 400; color: var(--text-mute);
}
.cselect-option.is-selected .cselect-option-note { font-weight: 400; }
/* 选项带说明时把浮层放高一点：6 个选项都带说明会超过默认的 300px */
.cselect-panel.has-notes { max-height: 420px; }
/* 选中项后面画一个绿色勾（内联 SVG 当遮罩，颜色走 --success，深浅色主题都正常） */
.cselect-option.is-selected .cselect-option-text::after {
    content: ''; display: inline-block; width: 14px; height: 14px; margin-left: 6px; vertical-align: -2px;
    background-color: var(--success);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.3 6.5 4.7 8.9 9.7 3.5' fill='none' stroke='%23000' stroke-width='2.1' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat center / contain;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.3 6.5 4.7 8.9 9.7 3.5' fill='none' stroke='%23000' stroke-width='2.1' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat center / contain;
}
.cselect-option.is-disabled { opacity: .45; cursor: not-allowed; }

/* 选项色块（<option data-swatch="sw-success">）：下拉里直接看到颜色 */
.cselect-dot {
    display: inline-block; width: 13px; height: 13px; border-radius: 4px;
    margin-right: 7px; vertical-align: -2px;
    box-shadow: inset 0 0 0 1px rgba(15, 23, 42, .12);
}
.sw-success   { background: var(--success); }
.sw-info      { background: var(--info); }
.sw-warning   { background: var(--warning); }
.sw-danger    { background: var(--danger); }
.sw-primary   { background: var(--brand-600); }
.sw-secondary { background: var(--text-mute); }

/* ---------- 更新进度弹窗 ---------- */
.up-head {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    padding: 11px 13px; border-radius: 12px;
    background: var(--surface-2); border: 1px solid var(--border);
}
.up-node-name { font-weight: 700; }
.up-node-addr {
    font-family: var(--mono); font-size: 12.5px; color: var(--text-soft);
    background: var(--surface); border: 1px solid var(--border); border-radius: 7px; padding: 2px 8px;
}
.up-hero { display: flex; align-items: baseline; gap: 12px; margin-top: 16px; flex-wrap: wrap; }
.up-percent {
    font-size: 32px; font-weight: 750; line-height: 1; letter-spacing: -.02em;
    font-variant-numeric: tabular-nums; color: var(--brand-600);
}
.up-percent small { font-size: 15px; font-weight: 650; margin-left: 1px; }
.up-percent.is-done { color: var(--success); }
.up-percent.is-error { color: var(--danger); }
.up-status { font-size: 13px; color: var(--text-soft); }

.up-bar { height: 10px; margin-top: 10px; border-radius: 999px; background: var(--bg-soft); overflow: hidden; }
.up-bar > span {
    display: block; width: 0; height: 100%; border-radius: 999px;
    background: linear-gradient(90deg, var(--brand-500), #7c3aed);
    transition: width .35s ease;
}
/* 下载中：流动的光带，一眼看出在进行 */
.up-bar.is-active > span {
    background-image: linear-gradient(90deg, var(--brand-500), #7c3aed, var(--brand-500));
    background-size: 200% 100%; animation: up-flow 1.1s linear infinite;
}
@keyframes up-flow { from { background-position: 0 0; } to { background-position: -200% 0; } }
.up-bar.is-done > span { background: var(--success); animation: none; }
.up-bar.is-error > span { background: var(--danger); animation: none; }

.up-tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 14px; }
.up-tile { padding: 9px 11px; border-radius: 10px; background: var(--surface-2); border: 1px solid var(--border); }
.up-tile .k { font-size: 11.5px; color: var(--text-mute); }
.up-tile .v { margin-top: 2px; font-size: 14px; font-weight: 650; font-variant-numeric: tabular-nums; }

.up-meta { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; font-size: 12.5px; }
.up-meta .row { display: flex; gap: 8px; align-items: center; min-width: 0; }
.up-meta .k { flex: 0 0 62px; color: var(--text-mute); }
.up-meta .v {
    min-width: 0; color: var(--text-soft); font-family: var(--mono); font-size: 12px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.up-meta .v.is-warning { color: var(--warning); font-family: inherit; font-size: 12.5px; white-space: normal; }

.up-note { font-size: 12.5px; color: var(--text-mute); line-height: 1.6; }

/* 节点状态页：卡片底部信息小标签（内存 / 磁盘 / 上下行，四个排一排不折行） */
.meta-chips { display: flex; flex-wrap: nowrap; gap: 5px; overflow: hidden; }
.meta-chip {
    display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto;
    padding: 2px 7px; border-radius: 999px; font-size: 11.5px; line-height: 1.7;
    background: var(--surface-2); border: 1px solid var(--border);
    color: var(--text-soft); white-space: nowrap;
}
.meta-chip .k { color: var(--text-mute); }
.meta-chip.text-success .k { color: var(--success); }
.meta-chip.text-info .k { color: var(--info); }
.meta-boot { margin-top: 6px; font-size: 12px; color: var(--text-mute); }

/* 节点状态页：占用条（CPU / 内存 / 磁盘） */
.stat-bar {
    height: 6px; margin-top: 6px; border-radius: 999px;
    /* 轨道用「文字色淡化」而不是 --bg-soft：后者与表格行 hover 同色，悬停时轨道会消失 */
    background: color-mix(in srgb, var(--text) 14%, transparent);
    overflow: hidden;
}
.stat-bar > span {
    display: block; height: 100%; border-radius: 999px;
    transition: width .3s ease, background .3s ease;
}

/* 后台「节点管理」：列多（18 列），收紧列距让版本 / 负载尽量一屏可见 */
#node-table th, #node-table td { padding: 9px 8px; }
#node-table th:first-child, #node-table td:first-child { padding-left: 14px; }

/* 批量更新的勾选列（表头全选 + 每行一个） */
#node-table th.pick-col, #node-table td.pick-col { width: 30px; padding-left: 10px; padding-right: 0; text-align: center; }

/* 节点列表：地区/城市、状态、运营商三列限宽（超出省略号）+ 收紧列距，别让这几列把表格撑宽。
   表格里 td 的 max-width 不生效（auto 布局），所以限宽加在单元格内的 .limit 上。 */
#node-table .col-place, #node-table .col-status, #node-table .col-carrier { padding-left: 7px; padding-right: 7px; }
#node-table .limit { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; }
#node-table .col-place .limit { max-width: 126px; }    /* 约 8 个中文 + 前面的状态点 */

/* 节点状态圆点（地区/城市前面那盏"健康灯"）：
   绿=正常 / 红闪=系统类型不符（会被阻止下发更新）/ 黄=连不上 / 黑=已禁用 / 蓝闪=正在更新 */
.node-dot {
    display: inline-block; width: 7px; height: 7px; border-radius: 50%;
    margin-right: 6px; vertical-align: middle; flex: 0 0 auto;
    background: var(--success);
}
.node-dot.is-on { background: var(--success); }
.node-dot.is-off { background: #000; }   /* 已禁用 = 黑色（2026-10-16 用户要求，原深灰 #4b5563） */
.node-dot.is-warn { background: var(--warning); }
.node-dot.is-busy { background: var(--info); animation: node-dot-blink 1s ease-in-out infinite; }
.node-dot.is-bad { background: var(--danger); animation: node-dot-blink 1s ease-in-out infinite; }

@keyframes node-dot-blink {
    0%, 100% { opacity: 1; }
    50% { opacity: .18; }
}

@media (prefers-reduced-motion: reduce) {
    .node-dot.is-bad, .node-dot.is-busy { animation: none; }
}
/* 状态列的开关自带文字（见 .switch-labeled），所以不再限宽 */
#node-table .col-carrier .limit { max-width: 76px; }   /* 约 4 个中文 */
#node-table .pick-col input { margin: 0; vertical-align: middle; cursor: pointer; }
#node-table .pick-col input:disabled { cursor: not-allowed; }

/* 版本列：更新中在徽章下面显示「已下载 6.9 MB / 共 12.5 MB」 */
.ver-dl { margin-top: 3px; font-size: 11.5px; white-space: nowrap; }

/* 「更新」按钮右上角的小标签：节点有新版时提示 */
.upd-wrap { position: relative; display: inline-flex; }
.upd-tag {
    /* 往上、往右各再挪一点：整个标签在按钮右上角外侧，压不到「更新」两个字 */
    position: absolute; top: -14px; right: -8px; z-index: 2;
    padding: 0 7px; height: 17px; line-height: 17px;
    border-radius: 999px; font-size: 11.5px; font-weight: 400;   /* 字大一点、不加粗 */
    background: var(--warning); color: #fff;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .18);   /* 不要外面那圈白边了 */
    cursor: help; white-space: nowrap;
}

/* 「有更新」气泡：和「功能开关」那个深蓝气泡同款式、不同配色（深橙，跟小标签一路） */
.tp-tip.theme-warn { background: #7c2d12; }
.tp-tip.theme-warn .tp-tip-sub { color: #fed7aa; }
.tp-tip.theme-warn .tp-tip-foot { color: #fdba74; border-top-color: rgba(255, 255, 255, .16); }
/* 「更新包」版本号：亮绿加粗，和上面「节点当前」那个浅橙分开（一眼看出哪个是新的） */
.tp-tip .tip-new { color: #86efac; font-weight: 700; }

/* 后台「节点管理」：实时负载列（标签与数值同一行：CPU 0.0% 内存 10.1% 磁盘 79.9%，下方一条迷你占用条） */
/* 宽度全部写死：只靠 min-width 的话，数字一变长（0.0%→12.3%）整列会被自动布局重排，看起来"动来动去" */
.node-load { width: 482px; }
/* 小屏幕时表格要左右滚：把「ID + 地区/城市」钉在左边（跟 Excel 冻结首列一样），
   滚到右边看供应商/功能开关/操作时，也知道这行是哪个节点。 */
#node-table th.col-id, #node-table td.col-id {
    position: sticky; left: 0; z-index: 3;
    width: 56px; min-width: 56px; background: var(--surface);
}
#node-table th.col-place, #node-table td.col-place {
    position: sticky; left: 56px; z-index: 3;
    background: var(--surface); box-shadow: 1px 0 0 var(--border);   /* 钉住区右边一条细线，区分滚动的部分 */
}
#node-table th.col-id, #node-table th.col-place { background: var(--surface-2); }
#node-table tbody tr:hover td.col-id, #node-table tbody tr:hover td.col-place { background: var(--bg-soft); }
/* 节点列表：整表内容居中（用户要求）—— 表头 + 所有单元格 */
#node-table th, #node-table td { text-align: center; }
/* 例外：这两列靠左对齐（都是"读起来是文本"的列，居中反而不好扫）
   —— 「地区/城市」和「节点地址」；表头一起左对齐，保证标题与内容对齐 */
#node-table th.col-place, #node-table td.col-place,
#node-table th.col-addr, #node-table td.node-addr { text-align: left; }
/* 下面几个格子内部是 flex / grid 容器，text-align 管不到，得各自居中 */
#node-table .load-line { justify-content: center; }        /* 实时负载 */
#node-table .feat-grid { justify-content: center; }        /* 功能开关 */
#node-table .actions .flex { justify-content: center; }    /* 操作：三个按钮 */
/* 「版本」列固定宽度：不固定的话，数据回来时徽章从「--」变成「16.0.2 · Linux」，
   整列会突然变宽 → 整张表左右跳一下（用户反馈的"表格在动"）。118px 装得下「16.0.2 · Armbian」。 */
.node-ver { width: 118px; min-width: 118px; }
/* 「节点地址」列：右侧留出展开时「复制」按钮的位置。
   完整地址 + 复制按钮是绝对定位的（刻意不挤动表格），按钮贴在完整地址右侧 ——
   本列不留空间就会被它盖住右邻列（版本徽章）："17.0.4 · Linux" 只剩 "4 · Linux"。
   预留 = 打码与完整地址的宽度差（IP 打码掉"169.112"这类 3~7 字符）+ 8px 间隙 + 47px 按钮 + 余量。
   ⚠️ 选择器必须带 `#node-table`：这张表的基准内边距是 `#node-table td { padding: 9px 8px }`，
   单写 `.node-addr`（特异度 0,1,0）会被它（1,0,1）盖掉、根本不生效（踩过一次）。 */
#node-table td.node-addr { padding-right: 92px; }
/* 实时负载：恒为一行（CPU/内存/磁盘 + 占用条 + 上下行 + 运行时长）。
   占用条保留「数值在上、条在下」的原有样子，但条是用 background 画在文字行的行距里
   （background-size 贴底、5px 高），不占额外高度 —— 表格行不会被撑高，字号/条高都能放心加大。
   字号与表格正文一致（13.5px），所以行高仍由其它列决定。
   上下行与时长用紧凑写法（1.2MB / 121天），完整值在悬浮提示里。
   每个小项给最小宽度：数值一变长（0B→1.2MB）整列不会跟着抖（表格是 auto 布局）。 */
/* min-height 取「在线时」的高度：连不上的节点只显示一个「—」时，行高也不会缩、表格不会跳 */
.load-line { display: flex; align-items: center; min-height: 28px; gap: 8px; white-space: nowrap; font-variant-numeric: tabular-nums; }
.load-line .lm { display: inline-flex; min-width: 78px; }
/* 上传 / 下载：SVG 箭头上下摞着（上=上传 绿 / 下=下载 蓝），不加文字、不加占用条。
   闪烁像网卡指示灯：周期由 JS 按速度写成 --blink（越快越短），没流量加 .io-idle 压暗不闪。 */
.load-line .lm.io { flex-direction: column; min-width: 66px; line-height: 1.1; }
.load-line .lm.io .io-line { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.load-line .lm.io .io-line i {
    display: inline-flex;
    animation: load-io-blink var(--blink, 1.5s) ease-in-out infinite;
}
.load-line .lm.io .io-line i svg { display: block; }
.load-line .lm.io .io-line b { font-size: 12.5px; font-weight: 500; }
.load-line .lm.io .io-up { color: var(--success); }
.load-line .lm.io .io-down { color: var(--info); }
/* 下载那个箭头：转 180° 指下；相位错开半个周期（按当前周期算，不然两个一起闪很假） */
.load-line .lm.io .io-down i { transform: rotate(180deg); animation-delay: calc(var(--blink, 1.5s) / -2); }
/* 没流量 = 灯灭：不闪、压暗 */
.load-line .lm.io .io-line.io-idle i { animation: none; opacity: .38; }

@keyframes load-io-blink {
    0%, 100% { opacity: 1; }
    50% { opacity: .25; }
}

@media (prefers-reduced-motion: reduce) {
    .load-line .lm.io .io-line i { animation: none; }
}

/* 上下行箭头（通用版，前台「节点状态」页用；后台节点页那套在上面 .load-line 里）：
   一闪一闪，周期由 --blink 决定（app.js 的 TP.blinkSeconds 按速度算）；没流量加 .is-idle = 灯灭 */
.io-arrow { display: inline-flex; animation: load-io-blink var(--blink, 1.5s) ease-in-out infinite; }
.io-arrow svg { display: block; }
.io-arrow.is-idle { animation: none; opacity: .38; }
.io-arrow-down svg { transform: rotate(180deg); }

@media (prefers-reduced-motion: reduce) {
    .io-arrow { animation: none; }
}

/* 状态页的上传/下载：箭头和数值并在同一行 */
.stat .io-stat { display: inline-flex; align-items: center; gap: 5px; }
.stat .io-stat .io-arrow svg { width: 13px; height: 13px; }
.load-line .lm.tx { font-size: 13px; min-width: 48px; display: inline-flex; align-items: baseline; }
.load-line .lm .pair {
    position: relative;
    display: inline-flex; align-items: baseline; gap: 4px; width: 100%;
    /* 收紧文字行 + 底部留 6px 空白：条画在这块空白里，和文字之间就有间距了。
       总高（13.5×1.1 + 6 ≈ 20.9px）仍低于其它列（约 22px），所以表格行不会被撑高。 */
    line-height: 1.1;
    padding-bottom: 6px;
}
/* 占用条用绝对定位的两个伪元素画（脱离文档流 → 不影响行高），这样才有圆头（background 渐变画出来是直角）：
   ::before = 整条灰色轨道，::after = 已完成部分（宽度由 --pct 决定） */
.load-line .lm .pair::before,
.load-line .lm .pair::after {
    content: ''; position: absolute; left: 0; bottom: 0; height: 5px;
    border-radius: 999px; pointer-events: none;
}
.load-line .lm .pair::before { right: 0; background: color-mix(in srgb, var(--text) 14%, transparent); }
.load-line .lm .pair::after { width: var(--pct, 0%); background: var(--bar, transparent); }
.load-line .lm .pair i { font-size: 11.5px; font-style: normal; color: var(--text-mute); }
.load-line .lm .pair b { font-size: 13.5px; font-weight: 650; }

/* 后台「节点管理」：节点地址是打码纯文本（不是输入框、也不是气泡），
   点击后只是把内容换成完整的 host:port —— 样式跟打码时一模一样，仅"不再隐藏"。
   完整值用绝对定位盖在原处 → 不参与布局，所以点开不会把表格挤动。 */
.addr-wrap { position: relative; display: inline-flex; align-items: center; gap: 6px; }
.addr-text,
.addr-live {
    display: inline-block;
    font-size: 13px; line-height: 1.42;
    font-family: ui-monospace, Menlo, Consolas, monospace;
    color: var(--text); cursor: pointer;
    white-space: nowrap;
    border-bottom: 1px dashed transparent;
}
.addr-text:hover,
.addr-live:hover { color: var(--brand-600); border-bottom-color: currentColor; }
/* 展开层本身只有地址文字那么大（高度=行高，跟打码文本完全对齐，点开不会往下移） */
.addr-live {
    position: absolute; left: 0; top: 0; z-index: 3;
    display: none;
}
.addr-wrap.is-open .addr-text { visibility: hidden; }
.addr-wrap.is-open .addr-live { display: inline-block; }
/* 看不见的"桥"：盖住地址与「复制」按钮之间那段空隙。
   没有它的话，鼠标从地址挪到按钮的路上会经过空隙 → 判定为离开 .addr-wrap → 浮层提前收起，按钮点不到。 */
.addr-live::after {
    content: ''; position: absolute; left: 100%; top: -6px; bottom: -6px; width: 12px;
}

/* 「复制」按钮：只在展开后出现，贴在地址右边、与地址垂直居中。
   绝对定位（相对展开层）→ 不撑高展开层，所以按钮做大也不会把地址顶下去、也不会挤动表格。 */
.addr-copy {
    position: absolute; left: 100%; top: 50%; transform: translateY(-50%);
    margin-left: 8px;
    font-size: 12.5px; line-height: 1; padding: 4px 10px; cursor: pointer; white-space: nowrap;
    color: var(--text-mute); background: var(--surface);
    border: 1px solid var(--border); border-radius: 6px;
}
.addr-copy:hover { color: var(--brand-600); border-color: var(--brand-400); }

/* SSL 评级区的「评级标准」入口：**整块由内联 SVG 画**（背景 + 当前评级字母 + 文字是同一个图形）——
   长方形、四角小圆角；配色跟**当前评级**走（浅底 + 同色字 + 淡同色边框，是左侧大徽章的迷你版）；
   鼠标移上去弹项目统一的深色气泡（data-tip-html，见 app.js 的 tp-tip） */
.grade-help { display: inline-block; margin-top: 8px; line-height: 0; cursor: help; }
.grade-help.grade-tone-success { --grade-bg: var(--success-bg); --grade-fg: var(--success); }
.grade-help.grade-tone-warning { --grade-bg: var(--warning-bg); --grade-fg: var(--warning); }
.grade-help.grade-tone-danger  { --grade-bg: var(--danger-bg);  --grade-fg: var(--danger); }
.grade-svg { display: block; width: 112px; height: 28px; }
.grade-svg rect { fill: var(--grade-bg); stroke: color-mix(in srgb, var(--grade-fg) 50%, transparent); stroke-width: 1; transition: stroke .12s; }
/* 「评级标准」加 2px 字间距，四个字别挤在一起 */
.grade-svg text { fill: var(--grade-fg); font-family: inherit; font-size: 13.5px; letter-spacing: 2px; }
/* 左边是当前评级：主字母 21px 粗体，后缀（+/−）小一号、位置提上去（做上标）—— 用户定的样式 */
.grade-svg text.grade-a-letter { font-size: 21px; font-weight: 800; letter-spacing: 0; }
.grade-svg text.grade-a-plus { font-size: 16px; font-weight: 800; letter-spacing: 0; }
.grade-help:hover .grade-svg rect { stroke: var(--grade-fg); }

/* ===== SSL 查询页（/sslcx）：参考 tance 的版式 —— 大搜索区 + 双列最近查询 + 检测项目 ===== */

/* 顶部搜索区：淡品牌色渐变底的大卡片（大输入框 + 大按钮 + 示例快捷） */
.ssl-hero {
    background: linear-gradient(135deg, var(--brand-50), var(--surface) 55%);
    border: 1px solid var(--border); border-radius: var(--radius-lg);
    padding: 22px 22px 15px;
}
.ssl-search { display: flex; gap: 12px; align-items: stretch; }
.ssl-search-box { position: relative; flex: 1; min-width: 0; }
.ssl-search-box .icon {
    position: absolute; left: 16px; top: 50%; transform: translateY(-50%);
    width: 18px; height: 18px; color: var(--text-mute); pointer-events: none;
}
.ssl-search-box input {
    width: 100%; height: 56px; padding: 0 16px 0 46px;
    font-size: 15px; color: var(--text);
    border: 1px solid var(--border-strong); border-radius: 12px; background: var(--surface);
    transition: border-color .12s, box-shadow .12s;
}
.ssl-search-box input:focus {
    outline: none; border-color: var(--brand-500);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand-500) 18%, transparent);
}
.ssl-search .btn { height: 56px; padding: 0 32px; font-size: 15.5px; border-radius: 12px; }
.ssl-hero-foot {
    display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
    margin-top: 12px; font-size: 12.5px; color: var(--text-mute);
}
.ssl-samples a { color: var(--brand-600); cursor: pointer; }
.ssl-samples a:hover { text-decoration: underline; }
[data-theme="dark"] .ssl-hero { background: linear-gradient(135deg, rgba(99,102,241,.14), var(--surface) 55%); }

/* 「最近查询」：双列列表（序号 + 域名 + 时间），hover 整行变浅底 —— 参考 tance 的「最近检测」 */
.recent-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 20px; }
@media (max-width: 720px) { .recent-grid { grid-template-columns: 1fr; } }
.recent-item {
    display: flex; align-items: center; gap: 10px;
    padding: 10px 12px; border-radius: 8px; color: var(--text);
    /* 盖掉全局 a:hover 的下划线 —— 整行是 <a>，不盖的话序号/时间下面都会被画上灰黑实线 */
    text-decoration: none;
}
.recent-item:hover { background: var(--bg-soft); text-decoration: none; }
.recent-item .no { flex: 0 0 auto; width: 18px; text-align: right; color: var(--text-mute); font-size: 12.5px; font-variant-numeric: tabular-nums; }
.recent-item .domain { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 划过时域名变品牌色 + 底下一条品牌色虚线（比实线轻，点名"这里可以点"） */
.recent-item:hover .domain {
    color: var(--brand-600);
    text-decoration: underline dashed color-mix(in srgb, var(--brand-500) 70%, transparent);
    text-decoration-thickness: 1.5px;
    text-underline-offset: 4px;
}
.recent-item .time { flex: 0 0 auto; color: var(--text-mute); font-size: 12.5px; }

/* 「检测项目」：3 列小卡（左边一条彩色竖条 + 标题/说明）—— 参考 tance，充实页面 */
.probe-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
@media (max-width: 900px) { .probe-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 600px) { .probe-grid { grid-template-columns: 1fr; } }
.probe-item { display: flex; gap: 11px; padding: 12px 14px; border: 1px solid var(--border); border-radius: 12px; }
.probe-item .bar { flex: 0 0 auto; width: 4px; border-radius: 999px; }
.probe-item b { display: block; font-size: 13.5px; }
.probe-item span { color: var(--text-mute); font-size: 12.5px; line-height: 1.7; }

/* 安全评级图例（检测项目卡右上角）：A+ ~ F 的小色块 */
.grade-legend { display: flex; align-items: center; gap: 5px; margin-left: auto; font-size: 12px; color: var(--text-mute); }
.grade-legend i { font-style: normal; font-weight: 700; color: #fff; padding: 1px 6px; border-radius: 4px; font-size: 11px; }

/* ===== 会员中心「域名白名单」 ===== */

/* 添加卡：淡品牌渐变底（和 /sslcx 的搜索卡呼应），让主操作更突出 */
.wl-add-card { background: linear-gradient(135deg, var(--brand-50), var(--surface) 55%); }
[data-theme="dark"] .wl-add-card { background: linear-gradient(135deg, rgba(99,102,241,.12), var(--surface) 55%); }

/* 会员中心页头卡：标题 + 副标题也套一张卡（淡品牌渐变底，和白底内容卡区分开） */
.member-head-card { background: linear-gradient(135deg, var(--brand-50), var(--surface) 62%); }
[data-theme="dark"] .member-head-card { background: linear-gradient(135deg, rgba(99,102,241,.12), var(--surface) 62%); }

/* 角色权限勾选（后台「角色管理 → 编辑权限」）：每组一张小卡（组头 + 子权限片） */
.role-perm-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
@media (max-width: 980px) { .role-perm-grid { grid-template-columns: 1fr; } }
.role-perm-group { border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; }
.role-perm-group .head { display: flex; align-items: center; gap: 8px; }
.role-perm-group .head .mono { font-size: 11.5px; color: var(--text-mute); margin-left: auto; }
.role-perm-group .items {
    display: flex; flex-wrap: wrap; gap: 6px 16px;
    margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--border);
}
.role-perm-group .items label { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; color: var(--text-soft); }
.role-perm-group input[type="checkbox"] { width: 15px; height: 15px; accent-color: var(--brand-600); cursor: pointer; }

/* 每条域名一张小卡：左边 3px 色条（待验证=橙 / 已生效=绿），一眼看出哪条要处理 */
.wl-item {
    border: 1px solid var(--border); border-left-width: 3px; border-radius: 12px;
    padding: 13px 15px; margin-top: 10px;
}
.wl-item.is-pending { border-left-color: var(--warning); }
.wl-item.is-ok { border-left-color: var(--success); }
.wl-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.wl-domain { font-size: 15px; }
.wl-actions { margin-left: auto; display: flex; gap: 8px; }
.wl-actions form { margin: 0; }

/* 三步操作：圆形数字徽章 + 右侧内容，条目之间淡虚线分隔 */
.wl-steps {
    list-style: none; margin: 12px 0 0; padding: 12px 14px;
    border-radius: 10px; background: var(--bg-soft);
    font-size: 13px; line-height: 1.8;
}
.wl-steps li { display: flex; gap: 10px; padding: 6px 0; }
.wl-steps li + li { border-top: 1px dashed color-mix(in srgb, var(--border-strong) 70%, transparent); }
.wl-steps .no {
    flex: 0 0 auto; width: 19px; height: 19px; margin-top: 3px;
    border-radius: 50%; background: var(--brand-600); color: #fff;
    font-size: 11.5px; font-weight: 700;
    display: inline-flex; align-items: center; justify-content: center;
}
.wl-steps .body { flex: 1; min-width: 0; }
.wl-steps .path { font-family: var(--mono); font-size: 12.5px; word-break: break-all; color: var(--text-soft); }
.wl-token {
    font-family: var(--mono); font-size: 12.5px;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: 6px; padding: 2px 8px; word-break: break-all;
}

/* SSL「证书信息」卡：两列 key-value 网格（一行两个字段，窄屏自动并成一列） */
.cert-kv { display: grid; grid-template-columns: 112px minmax(0, 1fr) 112px minmax(0, 1fr); }
.cert-kv > .k, .cert-kv > .v { padding: 12px 14px; border-bottom: 1px solid var(--border); font-size: 14px; min-width: 0; }
.cert-kv > .k { color: var(--text-mute); }
.cert-kv > .v { word-break: break-all; }
.cert-kv > .k:nth-last-child(-n+2), .cert-kv > .v:nth-last-child(-n+2) { border-bottom: 0; }
@media (max-width: 860px) { .cert-kv { grid-template-columns: 112px minmax(0, 1fr); } }

/* SSL「覆盖域名（SAN）」的域名小框：虚线边框（比实心徽章轻 —— 大站的 SAN 动辄几十个，实心色块会糊成一片） */
.san-chip {
    display: inline-block; padding: 2px 9px; border-radius: 6px;
    border: 1px dashed color-mix(in srgb, var(--brand-500) 50%, transparent);
    background: var(--brand-50); color: var(--brand-700);
    font-size: 13.5px; line-height: 1.7; word-break: break-all;
}
[data-theme="dark"] .san-chip { background: rgba(99, 102, 241, .12); color: var(--brand-200); }

/* SAN 折叠：域名太多时默认收起 3 行（chip 高 ≈29px + 行距 8px → 3 行 ≈103px，第 4 行从 111px 才开始，108px 刚好不露半行），
   点「展开全部 N 个」拉到底（app.js 全局事件委托里切 .is-folded） */
.san-fold.is-folded .san-list { max-height: 108px; overflow: hidden; }
.san-toggle {
    margin-top: 10px; padding: 0; border: 0; background: none; cursor: pointer;
    color: var(--brand-600); font-size: 13px; font-family: inherit;
}
.san-toggle:hover { text-decoration: underline; }

/* SSL「TLS 检测报告」加载中占位：转圈 + 品牌色高亮（探测要几秒，得让人一眼看到"正在干活"而不是一行灰字）。
   探测失败时 app.js 会给它加 .is-error：停止转圈、换警示配色 */
.tls-loading {
    display: flex; align-items: center; gap: 12px;
    padding: 14px 16px; border-radius: 10px;
    background: var(--brand-50);
    border: 1px solid color-mix(in srgb, var(--brand-500) 28%, transparent);
}
.tls-loading .spinner {
    flex: 0 0 auto; width: 20px; height: 20px;
    border: 2.5px solid color-mix(in srgb, var(--brand-500) 25%, transparent);
    border-top-color: var(--brand-600);
    border-radius: 50%;
    animation: tp-spin .8s linear infinite;
}
.tls-loading strong { color: var(--brand-700); font-size: 14px; }
.tls-loading.is-error { background: var(--warning-bg); border-color: color-mix(in srgb, var(--warning) 35%, transparent); color: var(--warning); font-weight: 600; }
.tls-loading.is-error .spinner { display: none; }
@keyframes tp-spin { to { transform: rotate(360deg); } }
[data-theme="dark"] .tls-loading { background: rgba(99, 102, 241, .12); }
[data-theme="dark"] .tls-loading strong { color: var(--brand-200); }

/* 节点列表的「功能开关」列：3 列 × 2 行小框（绿勾=开、灰叉=关）+ 短名，悬浮看全称。 */
.node-feats .feat-grid {
    display: grid; grid-template-columns: auto auto auto; gap: 2px 6px; justify-content: start;
}
/* 每个开关一个小框：不加底色，只有框子和勾/叉带颜色（开=绿框绿勾、关=灰框灰叉），文字用正常色 */
.feat-chip {
    display: inline-flex; align-items: center; gap: 3px;
    padding: 0 5px; border-radius: 4px;
    font-size: 12.5px; line-height: 1.15; white-space: nowrap;
    color: var(--text);
    border: 1px dashed var(--border-strong);   /* 虚线框 */
}
.feat-chip b { font-size: 12.5px; font-weight: 400; }
.feat-chip.is-on { border-color: color-mix(in srgb, var(--success) 55%, transparent); }
.feat-chip.is-on svg { color: var(--success); }
/* 关 = 红色叉 + 浅红框（框子跟状态色走，和开那边的绿对称） */
.feat-chip.is-off { border-color: color-mix(in srgb, var(--danger) 45%, transparent); }
.feat-chip.is-off svg { color: var(--danger); }
/* 加了边框后这一格会高 ~5px：把本列上下内边距各收 3px，行高由「实时负载」列继续决定（不被撑高） */
.table td.node-feats { padding-top: 8px; padding-bottom: 8px; }

/* 编辑节点弹窗底部的「重启节点」区块：动作区，和上面的配置项分开 */
.restart-box {
    margin-top: 16px; padding: 12px 14px;
    border: 1px solid color-mix(in srgb, var(--warning) 35%, var(--border));
    border-radius: 10px;
    background: color-mix(in srgb, var(--warning-bg) 70%, var(--surface));
}
.restart-title { font-size: 13.5px; }

/* 采样柱状图（持续PING 页）：每根柱子 = 一次采样，底部一条基线 */
.sample-chart {
    display: flex; align-items: flex-end; gap: 1px;
    width: 300px; min-width: 300px; max-width: 300px;   /* 固定宽度：采满 100 次也不会把列撑开 */
    height: 24px; padding-bottom: 1px; overflow: hidden;
    border-bottom: 1px solid var(--border-strong);
}
.sample-chart i {
    display: block; min-height: 2px; flex: 0 0 auto;
    border-radius: 1px 1px 0 0; background: var(--border-strong);
    transition: height .2s ease, background .2s ease;
}

/* 地图信息卡（tcping 页 ECharts tooltip）：省份 + 节点数 + 平均/最快/成功率 + 运营商明细 */
.map-tip {
    min-width: 210px; background: var(--surface); border: 1px solid var(--border);
    border-radius: 12px; overflow: hidden;
}
.map-tip-head {
    display: flex; align-items: center; gap: 8px; padding: 9px 12px;
    background: var(--brand-600); color: #fff; font-size: 13.5px; font-weight: 700;
}
.map-tip-head span { font-size: 12px; font-weight: 400; opacity: .85; }
.map-tip-stats {
    display: flex; justify-content: space-between; gap: 10px;
    padding: 9px 12px; border-bottom: 1px solid var(--border);
    font-size: 12.5px; color: var(--text-soft); white-space: nowrap;
}
.map-tip-stats b { margin-left: 4px; }
.map-tip-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.map-tip-table th {
    padding: 5px 10px; background: var(--surface-2); color: var(--text-mute);
    font-weight: 600; text-align: center; white-space: nowrap;
}
.map-tip-table td {
    padding: 5px 10px; border-top: 1px solid var(--border);
    text-align: center; color: var(--text); white-space: nowrap;
}
.map-tip-table th:first-child, .map-tip-table td:first-child { text-align: left; }

/* 节点多选（动态 IP 上报的绑定节点） */
.node-picker {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px 14px;
    max-height: 240px; overflow-y: auto; padding: 10px 12px;
    border: 1px solid var(--border); border-radius: 10px; background: var(--surface-2);
}
.node-picker .checkbox { align-items: flex-start; }
.node-picker .checkbox span { font-size: 12.5px; line-height: 1.5; color: var(--text-soft); }
@media (max-width: 720px) { .node-picker { grid-template-columns: 1fr; } }

/* 网站监控（会员中心）：任务卡片列表 —— 左边 3px 色条（正常=绿 / 告警中=红 / 暂停或已结束=灰） */
.mon-item { border: 1px solid var(--border); border-left: 3px solid var(--success); border-radius: 12px; padding: 13px 15px; margin-top: 10px; }
.mon-item.is-bad { border-left-color: var(--danger); background: color-mix(in srgb, var(--danger-bg) 45%, var(--surface)); }
.mon-item.is-off { border-left-color: var(--border-strong); opacity: .78; }
.mon-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.mon-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
.mon-actions form { margin: 0; }
.mon-url { font-family: var(--mono); font-size: 12.5px; color: var(--text-soft); margin-top: 4px; word-break: break-all; }
.mon-meta { display: flex; flex-wrap: wrap; gap: 4px 16px; margin-top: 7px; font-size: 12.5px; color: var(--text-soft); }
.mon-meta b { font-weight: 650; color: var(--text); }

/* 监控表单：条件行（标签 + 输入 + 单位）与状态码勾选组 */
.mon-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 13.5px; color: var(--text-soft); }
.mon-row .input { width: 110px; }
.mon-row + .mon-row { margin-top: 12px; }
.mon-code-group { display: flex; gap: 14px; flex-wrap: wrap; }
.mon-code-group label { display: inline-flex; align-items: center; gap: 5px; cursor: pointer; }
.mon-code-group input[type="checkbox"] { width: 15px; height: 15px; accent-color: var(--brand-600); cursor: pointer; }

/* 监控日志：每次检测一张小卡（异常=红框、未知=虚线框） */
.mon-run { border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; margin-top: 8px; }
.mon-run.is-bad { border-color: color-mix(in srgb, var(--danger) 40%, var(--border)); }
.mon-run.is-unknown { border-style: dashed; }
.mon-run .head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 13.5px; }
.mon-run .head .time { margin-left: auto; color: var(--text-mute); font-size: 12.5px; }
/* 每行一个节点（一行一个才好读：谁是谁、哪项指标属于哪个节点一目了然），
   正常行=绿、异常行=红（颜色由行内 .ok / .bad 给） */
.mon-run .nodes {
    margin-top: 7px; display: flex; flex-direction: column; align-items: flex-start;
    gap: 3px; font-size: 12.5px; color: var(--text-soft);
}
.mon-run .nodes .ok { color: var(--success); }
.mon-run .nodes .bad { color: var(--danger); }