:root {
    --admin-panel: var(--glass-strong);
    --admin-row: var(--glass);
    --admin-row-hover: var(--bg-hover);
    --admin-input-top: var(--control-pressed-bottom);
    --admin-input-bottom: var(--control-top);
    --admin-edge: var(--border);
    --admin-strong-edge: var(--focus);
    --admin-line: var(--divider-rgb);
    --admin-active: color-mix(in srgb, var(--accent) 11%, transparent);
    --admin-success: #486d54;
    --admin-error: #a44c46;
    --admin-primary-text: #fffaf5;
    --admin-inner-light: rgba(255, 255, 255, .69);
    --admin-inner-dark: rgba(105, 81, 63, .10);
    /* 表单控件的画布底色。option 必须是平色：展开后的原生列表由 UA 绘制，
       渐变不生效，只认 background-color。浅色沿用 --control-top，不另存一份色值。 */
    --admin-field-canvas: var(--control-top);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --admin-input-top: var(--control-pressed-top);
        --admin-input-bottom: var(--control-pressed-bottom);
        --admin-active: color-mix(in srgb, var(--accent) 17%, transparent);
        --admin-success: #a5c8ae;
        --admin-error: #e9a69d;
        --admin-primary-text: #271e18;
        --admin-inner-light: rgba(255, 255, 255, .09);
        --admin-inner-dark: rgba(0, 0, 0, .24);
        --admin-field-canvas: #2a2521;
    }
}

:root[data-theme="dark"] {
    --admin-input-top: var(--control-pressed-top);
    --admin-input-bottom: var(--control-pressed-bottom);
    --admin-active: color-mix(in srgb, var(--accent) 17%, transparent);
    --admin-success: #a5c8ae;
    --admin-error: #e9a69d;
    --admin-primary-text: #271e18;
    --admin-inner-light: rgba(255, 255, 255, .09);
    --admin-inner-dark: rgba(0, 0, 0, .24);
    --admin-field-canvas: #2a2521;
}

.modal-content,
.fav-dialog,
.ui-dialog {
    border: 1px solid var(--border);
    background: var(--admin-panel);
    box-shadow: inset 0 1px var(--admin-inner-light), 0 20px 48px rgba(35, 27, 21, .21);
    backdrop-filter: blur(23px) saturate(118%);
    -webkit-backdrop-filter: blur(23px) saturate(118%);
}

/* 900×80dvh → min(1200px, 96vw)×92dvh：功能变多之后 900px 太窄，
   分类名、URL、WebDAV 地址这些长字段要横向滚动才能看全。
   高度同步提高，一屏能看到的行数更多。窄屏（≤480px 块）仍走全屏 100dvh。 */
.modal-content {
    max-width: min(1200px, 96vw);
    width: 100%;
    height: min(92dvh, 900px);
    max-height: calc(100dvh - 40px);
    overflow: hidden;
    border-radius: 16px;
}

.modal-header {
    position: relative;
    flex: 0 0 auto;
    min-height: 65px;
    padding: 15px 23px 16px;
    border: 0;
    background: linear-gradient(90deg, transparent, rgba(var(--admin-line), .08) 18%, rgba(var(--admin-line), .18) 50%, rgba(var(--admin-line), .08) 82%, transparent) center bottom / 100% 1px no-repeat;
}

.modal-header h2 { font-size: 17px; font-weight: 600; letter-spacing: .02em; }
.modal-actions { gap: 8px; }
.config-save-status { min-width: 116px; text-align: right; line-height: 1.35; }
.config-save-status[data-state="pending"] { color: var(--accent); }
.config-save-status[data-state="success"] { color: var(--admin-success); }
.config-save-status[data-state="error"] { color: var(--admin-error); }
.modal-body { min-height: 0; padding: 22px 24px 28px; overscroll-behavior: contain; }

.modal .section { margin-bottom: 26px; }
.modal .section:last-child { margin-bottom: 0; }
.modal .section-title {
    margin-bottom: 11px;
    color: var(--text-secondary);
    font-size: 11px;
    font-weight: 650;
    letter-spacing: .065em;
    text-transform: none;
}

.modal .section-header {
    min-height: 38px;
    padding: 9px 0 10px;
    border: 0;
    background: linear-gradient(90deg, transparent, rgba(var(--admin-line), .08) 18%, rgba(var(--admin-line), .16) 50%, rgba(var(--admin-line), .08) 82%, transparent) center bottom / 100% 1px no-repeat;
}
.modal .section-header .section-title { margin: 0; }
.modal .section-header:hover .section-title,
.modal .section-header:hover .section-toggle-label { color: var(--accent); }
.modal .section-body { padding-top: 15px; }
.modal .setting-row {
    min-height: 55px;
    padding: 9px 0;
    border: 0;
    background: linear-gradient(90deg, transparent, rgba(var(--admin-line), .07) 18%, rgba(var(--admin-line), .11) 50%, rgba(var(--admin-line), .07) 82%, transparent) center bottom / 100% 1px no-repeat;
}
.modal .setting-row:last-child { background: none; }
.modal .setting-row label { font-size: 13px; }

:is(.modal, .fav-dialog, .ui-dialog) :is(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]), select, textarea) {
    min-height: 38px;
    border: 1px solid var(--admin-edge);
    border-radius: 8px;
    /* background 简写会把 background-color 重置为 transparent，因此不透明底色
       必须写在它之后，否则被抹掉。展开后的原生下拉列表只认 background-color，
       缺这一层时深色下会退回 UA 默认的白画布。 */
    background: linear-gradient(180deg, var(--admin-input-top), var(--admin-input-bottom));
    background-color: var(--admin-field-canvas);
    color: var(--text);
    box-shadow: inset 0 1px 2px var(--admin-inner-dark), inset 0 -1px var(--admin-inner-light);
    font: inherit;
    font-size: 13px;
    transition: border-color 100ms, box-shadow 100ms;
}

/* 下拉展开后的列表由 UA 绘制，option 必须自带不透明底色与文字色，
   否则深色下由 UA 的默认画布（白）铺底。 */
:is(.modal, .fav-dialog, .ui-dialog) select option {
    background-color: var(--admin-field-canvas);
    color: var(--text);
}
:is(.modal, .fav-dialog, .ui-dialog) :is(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]), select, textarea):focus {
    border-color: var(--focus);
    outline: none;
    box-shadow: inset 0 1px 2px var(--admin-inner-dark), 0 0 0 2px var(--admin-active);
}

.modal .toggle-slider {
    border: 1px solid var(--admin-strong-edge);
    background: var(--admin-input-top);
    box-shadow: inset 0 2px 3px var(--admin-inner-dark);
    transition: background 120ms, border-color 120ms;
}
.modal .toggle-slider::before {
    width: 17px;
    height: 17px;
    left: 3px;
    bottom: 2px;
    background: linear-gradient(165deg, var(--control-top), var(--control-bottom));
    box-shadow: inset 0 1px var(--admin-inner-light);
    transition: transform 120ms;
}
.modal .toggle-switch input:checked + .toggle-slider { border-color: var(--accent); background: var(--accent); }
.modal .toggle-switch input:checked + .toggle-slider::before { transform: translateX(20px); }
.modal .toggle-switch input:focus-visible + .toggle-slider { outline: 2px solid var(--focus); outline-offset: 3px; }

:is(.modal, .fav-dialog, .ui-dialog) .btn {
    min-height: 35px;
    padding: 6px 13px;
    border: 1px solid var(--admin-edge);
    border-radius: 8px;
    background: linear-gradient(170deg, var(--control-top), var(--control-bottom));
    color: var(--text);
    box-shadow: inset 0 1px var(--admin-inner-light), inset 0 -1px var(--admin-inner-dark);
    font-size: 12px;
    font-weight: 600;
    transition: transform 100ms, border-color 110ms, background 110ms, box-shadow 110ms;
}
:is(.modal, .fav-dialog, .ui-dialog) .btn:hover:not(:disabled) {
    transform: translateY(-1px);
    border-color: var(--focus);
    background: linear-gradient(170deg, var(--control-hover-top), var(--control-hover-bottom));
    box-shadow: inset 0 1px var(--admin-inner-light), inset 0 -1px var(--admin-inner-dark);
}
:is(.modal, .fav-dialog, .ui-dialog) .btn:active:not(:disabled) {
    transform: translateY(1px);
    background: linear-gradient(180deg, var(--control-pressed-top), var(--control-pressed-bottom));
    box-shadow: var(--press-shadow);
    transition-duration: 35ms;
}
:is(.modal, .fav-dialog, .ui-dialog) .btn-primary,
:is(.modal, .fav-dialog, .ui-dialog) .btn-primary:hover:not(:disabled) {
    border-color: var(--accent);
    background: var(--accent);
    color: var(--admin-primary-text);
    box-shadow: inset 0 1px rgba(255, 255, 255, .20);
}
:is(.modal, .fav-dialog, .ui-dialog) .btn-primary:hover:not(:disabled) { background: var(--accent-hover); }
:is(.modal, .fav-dialog, .ui-dialog) .btn-primary:active:not(:disabled) {
    background: var(--accent-hover);
    box-shadow: var(--press-shadow);
}
:is(.modal, .fav-dialog, .ui-dialog) .btn-danger {
    border-color: rgba(157, 74, 68, .28);
    background: transparent;
    color: var(--admin-error);
    box-shadow: none;
}
:is(.modal, .fav-dialog, .ui-dialog) .btn-danger:hover:not(:disabled) {
    border-color: var(--admin-error);
    background: rgba(164, 76, 70, .09);
    color: var(--admin-error);
    box-shadow: none;
}
:is(.modal, .fav-dialog, .ui-dialog) .btn-danger:active:not(:disabled) { box-shadow: var(--press-shadow); }
:is(.modal, .fav-dialog, .ui-dialog) .btn-sm { min-height: 31px; padding: 4px 10px; font-size: 11px; }
:is(.modal, .fav-dialog, .ui-dialog) .btn:disabled { opacity: .48; box-shadow: none; cursor: wait; }
:is(.modal, .fav-dialog, .ui-dialog) .btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

.modal .item,
.modal .fav-manager-item,
.webdav-backup-item {
    border: 1px solid var(--admin-edge);
    border-radius: 10px;
    background: var(--admin-row);
    box-shadow: inset 0 1px var(--admin-inner-light);
}
.modal .item { padding: 12px; margin-bottom: 8px; }
.modal .fav-drag-handle { color: var(--text-muted); }
.modal .fav-manager-category { border: 1px solid var(--admin-edge); background: transparent; color: var(--text-secondary); }
.modal .add-btn {
    width: auto;
    min-height: 33px;
    padding: 5px 12px;
    border: 1px solid var(--admin-edge);
    border-radius: 8px;
    background: linear-gradient(170deg, var(--control-top), var(--control-bottom));
    color: var(--text-secondary);
    box-shadow: inset 0 1px var(--admin-inner-light);
    font-size: 12px;
    font-weight: 600;
    transition: border-color 110ms, background 110ms, color 110ms, transform 100ms;
}
.modal .add-btn:hover { border-color: var(--focus); background: linear-gradient(170deg, var(--control-hover-top), var(--control-hover-bottom)); color: var(--text); }
.modal .add-btn:active { transform: translateY(1px); background: linear-gradient(180deg, var(--control-pressed-top), var(--control-pressed-bottom)); box-shadow: var(--press-shadow); }
.modal .add-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

.modal .fav-stats { color: var(--text-secondary); font-size: 13px; }
.modal .fav-hint { color: var(--text-muted); }
.modal .fav-actions { gap: 7px; }
.modal .fav-manager-split { gap: 16px; }
.modal .fav-manager-sidebar { width: 208px; border: 0; padding-right: 14px; background: linear-gradient(180deg, transparent, rgba(var(--admin-line), .14) 50%, transparent) right center / 1px 100% no-repeat; }
.modal .sidebar-header { border: 0; background: linear-gradient(90deg, transparent, rgba(var(--admin-line), .13) 50%, transparent) center bottom / 100% 1px no-repeat; }
.modal .category-tree-item { min-height: 34px; border-radius: 8px; cursor: default; transition: background 100ms, color 100ms, box-shadow 100ms; }
.modal .category-tree-item:hover { background: var(--admin-row-hover); color: var(--text); }
.modal .category-tree-item.active { background: var(--admin-active); color: var(--text); box-shadow: inset 2px 0 var(--accent); }
.modal .category-tree-item.drag-over { background: var(--admin-active); color: var(--text); box-shadow: inset 0 0 0 2px var(--accent); }
.modal .category-tree-select { display: flex; align-items: center; gap: 6px; flex: 1; min-width: 0; padding: 0; border: 0; background: transparent; color: inherit; text-align: left; font: inherit; cursor: pointer; }
.modal .category-tree-select[hidden] { display: none; }
.modal :is(.category-tree-select, .tree-toggle, .tree-item-edit):focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.modal .tree-toggle { display: grid; place-items: center; width: 22px; min-width: 22px; height: 28px; padding: 0; border: 0; background: transparent; color: var(--text-secondary); font: inherit; font-size: 10px; cursor: pointer; }
.modal .tree-toggle-placeholder { width: 22px; }
.modal .tree-item-icon { color: var(--text-muted); font-size: 12px; }
.modal .category-tree-item .tree-item-edit { display: inline-grid; place-items: center; width: 26px; height: 26px; padding: 0; margin-left: 0; border-radius: 6px; color: var(--text-secondary); }
.modal .category-tree-item.active .tree-item-edit { color: var(--text-secondary); }
.modal .category-tree-item.active .tree-item-count { background: var(--glass); color: var(--text-secondary); }
.modal .fav-manager-header { align-items: center; }
.modal .fav-batch-bar { min-height: 42px; border: 0; background: linear-gradient(90deg, transparent, rgba(var(--admin-line), .12) 50%, transparent) center bottom / 100% 1px no-repeat; }
.modal .fav-manager-item { margin-bottom: 7px; padding: 9px 11px; }
.modal .fav-manager-item:hover { border-color: var(--admin-strong-edge); background: var(--admin-row-hover); }
.modal .fav-manager-item:has(.fav-checkbox:checked) { border-color: var(--focus); background: var(--admin-active); }
.modal .fav-manager-item.dragging { opacity: .65; }
.modal .fav-manager-title-link:hover .fav-manager-title { color: var(--accent); text-decoration: none; }
.modal .fav-manager-category { border-radius: 6px; }
.modal .fav-checkbox,
.modal .fav-select-all input,
.fav-form input[type="checkbox"],
.restore-option input { accent-color: var(--accent); }
.modal .fav-empty { border: 1px solid var(--admin-edge); border-radius: 10px; background: var(--admin-row); color: var(--text-secondary); }

.webdav-status-dot.active { background: var(--admin-success); }
.webdav-row .field-label { display: block; flex: 1; min-width: 0; color: var(--text-secondary); font-size: 12px; font-weight: 600; cursor: default; }
.webdav-row .field-label input { display: block; width: 100%; margin-top: 6px; }
.webdav-backup-item { margin-bottom: 7px; }
.webdav-backup-item:hover { border-color: var(--admin-strong-edge); background: var(--admin-row-hover); }
.webdav-message.success,
.webdav-message.error { padding: 8px 0; background: transparent; }
.webdav-message.success { color: var(--admin-success); }
.webdav-message.error { color: var(--admin-error); }

/* ===== 远程备份：自动同步 ===== */
/* 状态行现在有三段（启用态 / 自动同步态 / 上次备份）。窄屏放不下时不换行
   会把「上次备份」挤出容器，所以允许换行。特异度靠 .modal 抬高，确保压过
   styles.css 里那条基础规则。 */
.modal .webdav-status { flex-wrap: wrap; row-gap: 4px; }
.webdav-auto-status { font-size: 12px; color: var(--text-muted); }
/* 未启用 WebDAV 时自动同步勾选框置灰——一个看着开着、其实什么也不做的
   开关，比没有这个开关更糟。 */
.webdav-row label.is-disabled { opacity: .55; cursor: not-allowed; }
/* 自动同步槽位不是历史还原点，用左侧色条与手动备份区分 */
.webdav-backup-item.is-auto { border-left: 3px solid var(--accent); }

.fav-dialog { max-width: 440px; border-radius: 14px; }
.fav-dialog h3 { color: var(--text); font-size: 16px; font-weight: 600; }
.fav-dialog:focus { outline: none; }
.fav-form { gap: 12px; }
.fav-form .field-label,
.ui-dialog-field span { display: block; margin-bottom: 5px; color: var(--text-secondary); font-size: 12px; font-weight: 600; }
.fav-form .field-label input,
.fav-form .field-label select { display: block; width: 100%; margin-top: 5px; }
.fav-form .fav-category-row { align-items: end; }
.fav-form .fav-category-row .field-label { flex: 1; min-width: 0; }
.fav-dialog-actions { gap: 8px; }
.fav-dialog-overlay { background: rgba(25, 21, 18, .44); backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); }
.restore-option { border: 1px solid var(--admin-edge); border-radius: 9px; background: var(--admin-row); }
.restore-option:has(input:checked) { border-color: var(--focus); background: var(--admin-active); }
.restore-option:focus-within { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ===== 恢复计划对话框（#13）=====
   清单要同时放下「将恢复 / 将跳过（逐条原因）」两段，440px 会把每一行都折成两行，
   所以只加宽这一个对话框，不动 .fav-dialog 的默认宽度。 */
.webdav-restore-plan { max-width: 560px; }
.webdav-plan-sub {
    margin: 0 0 12px;
    color: var(--text-muted);
    font-size: 12px;
    line-height: 1.55;
}
/* 旧备份提示必须比普通说明更显眼：这一条解释了「为什么清单里只剩书签」。
   用错误色的边框与底纹而不是纯文本——纯文本在长清单里会被读过去。 */
.webdav-plan-legacy {
    margin: 0 0 12px;
    padding: 9px 11px;
    border: 1px solid var(--admin-error);
    border-left-width: 3px;
    border-radius: 9px;
    background: rgba(164, 76, 70, .07);
    color: var(--text);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.55;
}
.webdav-plan-section { margin-bottom: 12px; }
.webdav-plan-title {
    margin-bottom: 6px;
    color: var(--text-secondary);
    font-size: 12px;
    font-weight: 700;
}
.webdav-plan-list {
    margin: 0;
    padding: 0;
    list-style: none;
    max-height: 220px;
    overflow-y: auto;
}
.webdav-plan-item {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: 8px;
    row-gap: 2px;
    padding: 7px 10px;
    margin-bottom: 6px;
    border: 1px solid var(--admin-edge);
    border-radius: 9px;
    background: var(--admin-row);
}
.webdav-plan-item:last-child { margin-bottom: 0; }
.webdav-plan-domain { color: var(--text); font-size: 13px; font-weight: 600; }
.webdav-plan-meta { margin-left: auto; color: var(--text-muted); font-size: 11px; }
.webdav-plan-revision { margin-left: 8px; color: var(--accent); font-size: 11px; font-variant-numeric: tabular-nums; }
/* 跳过项的理由是这一节的重点，所以占满整行而不是挤在右侧 */
.webdav-plan-reason { flex: 1 0 100%; color: var(--text-muted); font-size: 11px; line-height: 1.5; }
.webdav-plan-skip .webdav-plan-item { background: transparent; border-style: dashed; }
.webdav-plan-empty { margin: 0; color: var(--text-muted); font-size: 12px; }
.webdav-plan-credentials {
    margin-bottom: 12px;
    padding: 9px 11px;
    border: 1px solid var(--admin-edge);
    border-radius: 9px;
    background: var(--admin-active);
    color: var(--text-secondary);
    font-size: 12px;
    line-height: 1.55;
}
.webdav-plan-credentials p { margin: 0 0 4px; }
.webdav-plan-credentials p:last-child { margin-bottom: 0; }
.webdav-plan-hint { color: var(--text-muted); font-size: 11px; }
.webdav-plan-error {
    margin-bottom: 12px;
    padding: 9px 11px;
    border: 1px solid var(--admin-error);
    border-radius: 9px;
    background: rgba(164, 76, 70, .08);
    color: var(--admin-error);
    font-size: 12px;
    line-height: 1.55;
}
.webdav-plan-error .btn { margin-left: 6px; vertical-align: baseline; }

/* 内边距不在这里设：下面那条同时写四边的规则会完整覆盖它。
   曾经写成 `padding: 20px` 再由 `padding-bottom` 覆盖，导致 top 20px / bottom 12px
   的不对称，对话框在宽屏下恒定偏上 8px。要改内边距就改下面那条。 */
.ui-dialog-overlay { position: fixed; inset: 0; z-index: 1300; display: grid; place-items: center; background: rgba(25, 21, 18, .47); backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); }
.ui-dialog { width: min(100%, 400px); max-height: min(90dvh, 560px); overflow-y: auto; padding: 22px; border-radius: 14px; }
/* 软键盘弹出时按被遮挡的高度收矮并让出底部空间（--kb-inset 由 app.js 写入，
   不支持 visualViewport 的浏览器为 0，行为与之前一致）。
   四边都要显式写：只写 padding-bottom 会让上面那条 `padding: 20px` 的
   top/left/right 被保留，20px 对 12px 的不对称把内容整体上推 4px，
   量到的就是 top 留白比 bottom 多 8px。--kb-inset 只该影响底部。

   **不要把 .fav-dialog-overlay 合并进这条简写**：它在 styles.css 的
   ≤768px 块里有一条四边吃安全区的规则（`styles.css` 里那是它的唯一定义处），
   而本文件后加载、特异性相同，写在这里的简写会把那四边整个覆盖掉——
   书签弹窗在 iPhone 上就丢掉 top/right/left 的安全区了。 */
.ui-dialog-overlay {
    padding: 12px 12px calc(12px + var(--kb-inset, 0px) + env(safe-area-inset-bottom));
}
.fav-dialog-overlay {
    padding-bottom: calc(12px + var(--kb-inset, 0px) + env(safe-area-inset-bottom));
}
.ui-dialog, .fav-dialog { max-height: calc(min(90dvh, 560px) - var(--kb-inset, 0px)); }
.ui-dialog h3 { margin: 0 0 9px; color: var(--text); font-size: 16px; font-weight: 600; }
.ui-dialog-message { margin: 0 0 17px; color: var(--text-secondary); font-size: 13px; line-height: 1.6; white-space: pre-line; }
.ui-dialog-field { display: block; margin-bottom: 12px; }
.ui-dialog-field input { display: block; width: 100%; padding: 9px 11px; }
.ui-dialog-option { margin-bottom: 8px; border: 1px solid var(--admin-edge); border-radius: 9px; background: var(--admin-row); }
.ui-dialog-option:has(input:checked) { border-color: var(--focus); background: var(--admin-active); }
.ui-dialog-option:focus-within { outline: 2px solid var(--focus); outline-offset: 2px; }
/* 44px 是触摸目标下限：弹窗内的选项在桌面端同样保持可点，不按断点降级 */
.ui-dialog-choice { display: flex; align-items: center; gap: 9px; min-height: 44px; padding: 9px 12px; cursor: pointer; color: var(--text); font-size: 13px; }
.ui-dialog-choice input { width: auto; flex: none; margin: 0; accent-color: var(--accent); }
.ui-dialog-hint { margin: -2px 12px 10px 33px; color: var(--text-secondary); font-size: 11px; line-height: 1.5; }
/* 分组容器：有效期四档排成 2×2，单行「30 分钟」不折行，触摸目标也够大 */
.ui-dialog-group { margin-bottom: 12px; }
.ui-dialog-group-label { margin: 0 0 7px; color: var(--text-secondary); font-size: 12px; font-weight: 600; }
.ui-dialog-options { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
.ui-dialog-options .ui-dialog-option { margin-bottom: 0; }
/* 扁平 options（分享有效期、采集方式等）此前是竖排的：它们渲染时不带容器，
   是本次给它们补上 .ui-dialog-options 之后才有了这个 grid。
   补容器是为了让横屏能把它们并排省出纵向空间，但**默认必须维持原来的竖排**——
   `.ui-dialog-options` 的两列是给分组里的四档有效期设计的（如 30 分钟不折行），
   顺带套到扁平列表上会改掉那些对话框既有的布局。 */
.ui-dialog-options.is-plain { grid-template-columns: 1fr; }
.ui-dialog-reveal { margin-bottom: 12px; }
.ui-dialog-reveal[hidden] { display: none; }
.ui-dialog-reveal .ui-dialog-field { margin-bottom: 0; }
.ui-dialog-error { min-height: 0; color: var(--admin-error); font-size: 12px; }
.ui-dialog-error:empty { display: none; }
.ui-dialog-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 18px; }

/* 横屏且高度很小时（如 844×390），对话框装不下：视口可见区 349px，
   内容原为 584px、溢出 235px，「确定/取消」落在首屏外 582px 处 ——
   用户看到的是一张只有输入框的弹窗。

   这里的每一条都是浏览器实测推出来的，改动前先读下面的「实测记录」：
     - 限高 message 与 hint、压 padding 与输入框：省 71px，缺口 132px，不够
     - 扁平 options 并排两列：再省约 59px，溢出 235 → 99px
     - 收 sticky 条自身的 padding：再省 4px，溢出 → 99px
   逐项压缩到此为止：再压就要动 44px 的触摸目标或 16px 的 iOS 防缩放字号。

   **仍差约 76px，未达标。** 剩下的不在内容侧，而在 sticky 条的遮挡：
   实测选项被 sticky 条压掉 42.61px（可见 42.61 / 80.59），
   两个 hint 整个落在对话框裁剪线（bottom 370.5）之下 6.39px、逐点采样 0% 可见。

   下一步该动的不是继续压内容，而是 sticky 条上方的净空：
   实测对话框内容盒 top 34.5，而 form 内容从 109.39 起 —— 标题与被限高的
   message 之间有 74.89px 空白，那才是真正能省下的地方。

   ⚠️ 不要改成 position: absolute 固定操作区：实测那条路径上
   .ui-dialog 既是滚动容器又是定位基准，给它 padding-bottom 不会给内容流
   预留空间（选项位置逐位不变），而脱离文档流的元素又会被算进父级的
   可滚动溢出。早先注释里「absolute 会把溢出撑大 92px」的说法**已被实测证伪**
   ——sticky 下溢出本来就已经是这个量级，两个数字不可互相印证。

   min-height 44px 的**触摸目标不降级**：横屏多见于手机，降高度只会
   把已经达标的目标打回 26px。 */
@media (max-height: 500px) and (orientation: landscape) {
    .ui-dialog { padding: 14px; }
    .ui-dialog h3 { margin-bottom: 6px; font-size: 15px; }
    /* 提示正文在横屏下没有必要占 3 行：它讲的是 token 的后果，
       而横屏通常配合物理键盘，注意力已经在输入框上。
       限高 2 行 + 溢出隐藏，句子读不完也比把下面的控件推出视口好。 */
    .ui-dialog-message {
        margin-bottom: 10px;
        max-height: 2.8em;
        overflow: hidden;
    }
    .ui-dialog-field { margin-bottom: 8px; }
    /* hint 从 2 行压到 1 行：单选本身的 label 已经说清了「谁主动」，
       hint 是补充说明，截断可接受。 */
    .ui-dialog-hint {
        margin: -2px 12px 6px 33px;
        max-height: 1.6em;
        overflow: hidden;
    }
    /* 输入框在横屏下压到 40px。字号不动（16px 是 iOS 防缩放的底线）。 */
    :is(.ui-dialog) input:not([type="checkbox"]):not([type="radio"]) { min-height: 40px; }
    /* 扁平 options（采集方式）在横屏下并排两列：竖排时两个选项连 hint
       共占约 182px，并排只占约 85px。这是压缩里唯一拿到足够空间的一刀。

       只作用于 .is-plain：分组里的四档有效期本来就是 2×2（见基础规则），
       它要保持自己的形态。 */
    .ui-dialog-options.is-plain { grid-template-columns: 1fr 1fr; gap: 6px; }
    /* 操作区保持 sticky：第二版试过 absolute，在这条路径上是陷阱
       （.ui-dialog 既是滚动容器又是定位基准，padding-bottom 不给内容流留空间，
       而脱离文档流的元素又会被算进父级可滚动溢出，把溢出撑大 92px）。
       sticky 至少不撑溢出。
       padding 从 8px 0 2px 收到 4px 0 6px、margin-top 从 12px 收到 8px：
       实测缺口只剩 11.48px，而 sticky 条自身就占 45px——从它身上省下这点
       比再压内容更划算，因为内容侧的 44px 触摸目标与 16px 防缩放字号都不能动。 */
    .ui-dialog-actions {
        position: sticky;
        /* -8px 让它略微越过容器底部，贴着边框而不是浮在内容上方一截 */
        bottom: -8px;
        margin-top: 8px;
        padding: 4px 0 6px;
        background: var(--bg-card);
    }
}
.toast[data-state="error"] { color: var(--admin-error); border-color: rgba(164, 76, 70, .35); }

/* Secondary surfaces use the same material as the confirmed homepage. */
.help-content,
.paste-result {
    border: 1px solid var(--border);
    background: var(--glass-strong);
    box-shadow: inset 0 1px var(--admin-inner-light), 0 20px 48px rgba(35, 27, 21, .21);
    backdrop-filter: blur(23px) saturate(118%);
    -webkit-backdrop-filter: blur(23px) saturate(118%);
}
.help-content { border-radius: 14px; }
.help-section,
.help-note {
    border: 0;
    background: linear-gradient(90deg, transparent, rgba(var(--divider-rgb), .08) 18%, rgba(var(--divider-rgb), .18) 50%, rgba(var(--divider-rgb), .08) 82%, transparent) center bottom / 100% 1px no-repeat;
}
.help-section:last-child { background: none; }
.help-note { background-position: center top; }
.help-section code { border: 1px solid var(--border); background: var(--glass); color: var(--text); }
:root .help-new-features,
:root:not([data-theme="light"]) .help-new-features {
    border: 1px solid var(--border);
    background: var(--glass);
}
:root .help-new-features-header,
:root:not([data-theme="light"]) .help-new-features-header { color: var(--accent); }
:root .help-new-features-summary,
:root:not([data-theme="light"]) .help-new-features-summary { color: var(--text-secondary); }
.paste-result { border-radius: 14px; }
.paste-close,
.paste-link,
.paste-copy-btn,
.paste-close-btn {
    border: 1px solid var(--border);
    border-radius: 8px;
    background: linear-gradient(170deg, var(--control-top), var(--control-bottom));
    color: var(--text);
    box-shadow: inset 0 1px var(--admin-inner-light);
    transition: transform 110ms, border-color 140ms, background 140ms, box-shadow 140ms;
}
.paste-close { width: 29px; height: 29px; }
.paste-close:hover,
.paste-link:hover,
.paste-copy-btn:hover,
.paste-close-btn:hover { border-color: var(--focus); background: linear-gradient(170deg, var(--control-hover-top), var(--control-hover-bottom)); color: var(--text); }
.paste-close:active,
.paste-link:active,
.paste-copy-btn:active,
.paste-close-btn:active { transform: translateY(1px); background: linear-gradient(180deg, var(--control-pressed-top), var(--control-pressed-bottom)); box-shadow: var(--press-shadow); }
:is(.paste-close, .paste-link, .paste-copy-btn, .paste-close-btn):focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.paste-content-text { border-color: var(--border); border-radius: 8px; background: linear-gradient(180deg, var(--admin-input-top), var(--admin-input-bottom)); box-shadow: inset 0 1px 2px var(--admin-inner-dark); }
.modal-backdrop,
.help-overlay,
.fav-dialog-overlay,
.ui-dialog-overlay { background: rgba(25, 21, 18, .44); }

@media (max-width: 768px) {
    /* 96% 跟随视口，不在此处再压一个 900px 上限——
       基础规则已经是 min(1200px, 96vw)，这里再写 min(96%, 900px)
       会把平板（769–1200px）重新压回 900，让加宽的改动在平板上完全失效。
       同查询的规则要在两处一起改，这是本文件已栽过一次的跟头。 */
    .modal-content { width: 96%; max-height: calc(100dvh - 24px); }
    .modal-header { min-height: 62px; padding: 12px 16px; }
    .modal-body { padding: 17px 16px 24px; }
    .modal .fav-manager-split { gap: 11px; }
    .modal .fav-manager-sidebar { width: 100%; max-height: 176px; padding: 0 0 12px; background: linear-gradient(90deg, transparent, rgba(var(--admin-line), .14) 50%, transparent) center bottom / 100% 1px no-repeat; }
    .modal .tree-toggle,
    .modal .tree-toggle-placeholder { display: none; }
    .modal .category-tree-children { display: contents; }
    :is(.modal, .fav-dialog, .ui-dialog) .btn { min-height: 40px; }
    :is(.modal, .fav-dialog, .ui-dialog) .btn-sm { min-height: 36px; }
}

@media (max-width: 480px) {
    .modal-content { width: 100%; height: 100dvh; max-height: 100dvh; border-radius: 0; }
    /* 标题与「取消/保存」同行。原先 align-items:flex-start 配 .modal-actions 的
       flex-wrap:wrap，而标题只有约 35px 宽、按钮区要占满整行，两者放不下
       390px 的可用宽度，按钮必然被挤到第二行——头部实测占 118.5px
       （视口 844px 的 14%）。实测降为 70px。
       本文件在 styles.css 之后加载且特异性更高，窄屏的最终决定权在这里，
       只改 styles.css 会被下面的声明静默还原。 */
    .modal-header { align-items: center; gap: 10px; padding: 10px 14px; background-color: var(--admin-panel); }
    .modal-header h2 { padding-top: 0; font-size: 15px; flex: 0 0 auto; }
    .modal-actions { flex-wrap: nowrap; justify-content: flex-end; gap: 8px; flex: 0 0 auto; }
    /* 窄屏没有空间显示整句保存状态，隐藏而不是让它把按钮挤下去 */
    .config-save-status { display: none; }
    .modal-body { padding: 16px 14px calc(24px + env(safe-area-inset-bottom)); }

    /* 收藏夹三个按钮：横排时每个仅约 52px 宽。改两列网格，每格 ≥44px 高
       （触摸目标下限）。原先 styles.css 里的 grid 声明会被本文件
       .modal .fav-actions 的高特异性覆盖，故在此一并声明。 */
    .modal .fav-actions {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 8px;
    }
    .modal .fav-actions .btn { width: 100%; min-height: 44px; padding: 10px 8px; }

    /* 收藏管理器侧栏：≤768px 时它占满整行且限高 176px，把列表挤到首屏之外。
       窄屏改为横向滚动的分类条，列表紧接在下方立刻可见。 */
    .modal .fav-manager-sidebar {
        width: 100%;
        max-height: none;
        flex-direction: row;
        align-items: center;
        gap: 8px;
        padding: 8px 0;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        background: none;
        /* 分类多于宽度时需要滚动提示：右侧渐隐，暗示还有内容 */
        mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
        -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
    }
    .modal .fav-manager-sidebar .sidebar-header { flex: 0 0 auto; padding: 0 4px; }
    .modal .fav-manager-sidebar .category-tree { display: flex; flex-direction: row; gap: 8px; flex: 0 0 auto; }
    .modal .fav-manager-sidebar .category-tree-item { flex: 0 0 auto; white-space: nowrap; }

    /* 列表条目：原先条目是 flex-wrap:wrap，而 .fav-manager-info 独占整行，
       把「分类」和操作按钮挤到第二、三行——单个条目实测 134px，
       一屏只放得下 5 条。这里让信息与操作同行、分类缩到信息下方一行。 */
    .modal .fav-manager-item {
        gap: 8px;
        padding: 8px 10px;
        flex-wrap: wrap;
        align-items: center;
    }
    .modal .fav-manager-info {
        flex: 1 1 auto;
        min-width: 0;
        order: 1;
    }
    .modal .fav-manager-actions { order: 2; flex: 0 0 auto; }
    /* 分类标签原本 flex:1 0 100% 会横贯整行、像个输入框。
       改为随内容宽度、跟在信息下方并排，占位更紧凑。 */
    .modal .fav-manager-category {
        order: 3;
        flex: 0 1 auto;
        max-width: 100%;
        margin-top: 0;
        align-self: center;
    }
    /* 私密标签复用首页的 .fav-private-label（顶层全局类，无需重复定义），
       但它是 .fav-manager-item 的兄弟节点，窄屏默认 order:0 会排到
       order:1 的信息块之前。跟分类标签一样排 order:3，紧挨着它同行显示。
       本文件加载晚于 styles.css 且选择器带 .modal，特异性更高，窄屏以此为准。 */
    .modal .fav-manager-item .fav-private-label {
        order: 3;
        flex: 0 0 auto;
        max-width: 100%;
        align-self: center;
    }

    /* 管理器头部：返回按钮(44px) 与搜索框纵向堆叠，实测占 102–120px，
       把列表挤到首屏中段。这里让两者同行——返回按钮固定宽度，
       搜索框吃掉剩余宽度，行高由搜索框自身决定（原 50px 偏高，
       收到 40px 仍高于 44px 触摸下限的常见实现）。 */
    .modal .fav-manager-header {
        flex-direction: row;
        flex-wrap: nowrap;      /* 基线是 wrap：搜索框 min-width:150px + 按钮 67px 放不下 336px，仍会换行 */
        align-items: center;
        gap: 8px;
        padding: 10px 12px;
    }
    .modal .fav-manager-header > .btn { flex: 0 0 auto; }
    .modal .fav-manager-search {
        flex: 1 1 auto;
        min-width: 0;          /* 覆盖基线的 min-width:150px，否则窄屏放不下 */
        width: auto;
        height: 40px;
        padding: 8px 12px;
        font-size: 16px;       /* 保持 16px：iOS Safari 对 <16px 输入会触发自动放大 */
    }
    .modal .category-tree-item { min-height: 40px; }
    /* 弹窗在窄屏同样居中：改成底部抽屉会被移动端浏览器的地址栏与 Home 指示条盖住。
       四边内边距吃安全区，max-height 改用 dvh 以便地址栏展开/收起时自适应。
       .fav-dialog 的同款处理在 styles.css 的 ≤768px 块（那里是它的唯一定义处），
       窄屏不再改回顶部圆角的底部抽屉。 */
    .ui-dialog-overlay { padding: calc(12px + env(safe-area-inset-top)) calc(12px + env(safe-area-inset-right)) calc(12px + var(--kb-inset, 0px) + env(safe-area-inset-bottom)) calc(12px + env(safe-area-inset-left)); }
    .ui-dialog { width: 100%; max-height: calc(88dvh - var(--kb-inset, 0px)); border-radius: 14px; padding: 16px 14px; }
    :is(.modal, .fav-dialog, .ui-dialog) :is(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]), select, textarea) { min-height: 44px; font-size: 16px; }
    :is(.modal, .fav-dialog, .ui-dialog) .btn { min-height: 44px; }
    .fav-form .fav-category-row { display: block; }
    .fav-form .fav-category-row .field-label { display: block; margin-bottom: 12px; }
}

@media (prefers-reduced-motion: reduce) {
    .modal .btn,
    .fav-dialog .btn,
    .ui-dialog .btn { transition-duration: .01ms; }
    .modal .btn:hover,
    .fav-dialog .btn:hover,
    .ui-dialog .btn:hover { transform: none; }
}

/* ========== 部署命令面板 ==========
   后台「模块 → 监控目标 → 部署」打开。可复制的命令用 <pre> 而不是
   输入框：输入框能被误编辑、也拿不到「整段选中」的手感。 */
/* z-index 必须高于 .modal 的 1000 —— 部署面板是从管理面板里点开的，
   两层是嵌套关系不是并列。初版写 260，被 .modal 整个盖住、面板内容
   完全点不到，而 DOM 里有、接口测试也过，只有真机点开才看得见。 */
.command-panel-overlay { position: fixed; inset: 0; z-index: 1100; display: grid; place-items: center; padding: 20px; background: rgba(25, 21, 18, .44); backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); }
.command-panel { width: min(100%, 720px); max-height: min(88dvh, 860px); display: flex; flex-direction: column; overflow: hidden; border: 1px solid var(--border); border-radius: 16px; background: var(--admin-panel); box-shadow: inset 0 1px var(--admin-inner-light), 0 20px 48px rgba(35, 27, 21, .21); backdrop-filter: blur(23px) saturate(118%); -webkit-backdrop-filter: blur(23px) saturate(118%); }
.command-panel-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 15px 18px 12px; border-bottom: 1px solid var(--border); }
.command-panel-head h3 { font-size: 14px; font-weight: 600; letter-spacing: .02em; }
.command-panel-close { min-width: 32px; min-height: 32px; border: 0; border-radius: 8px; background: transparent; color: var(--text-muted); font-size: 20px; line-height: 1; cursor: pointer; }
.command-panel-close:hover { background: var(--admin-row-hover); color: var(--text); }
.command-panel-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 14px 18px 20px; }
.command-intro { margin-bottom: 14px; color: var(--text-secondary); font-size: 12px; line-height: 1.6; }
.command-step { margin-bottom: 18px; }
.command-step:last-child { margin-bottom: 0; }
.command-step h4 { margin-bottom: 3px; color: var(--text); font-size: 12px; font-weight: 600; }
.command-note { margin-bottom: 6px; color: var(--text-muted); font-size: 11px; line-height: 1.5; }
/* 纯说明段（没有可复制的命令）。刻意不带边框底色——
   它不是命令块，染成命令块的样子会让人以为要复制它。 */
.command-plain {
    margin: 0; padding: 10px 12px;
    border: 1px dashed var(--admin-edge); border-radius: 8px;
    color: var(--text-secondary); font-size: 11.5px; line-height: 1.6;
}
.command-row { display: flex; align-items: flex-start; gap: 8px; }
.command-code { flex: 1 1 auto; min-width: 0; margin: 0; padding: 10px 12px; overflow-x: auto; border: 1px solid var(--admin-edge); border-radius: 8px; background: var(--admin-field-canvas); color: var(--text); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; line-height: 1.6; white-space: pre; -webkit-overflow-scrolling: touch; }
.command-copy { flex: 0 0 auto; min-width: 52px; }
@media (max-width: 600px) {
    .command-panel-overlay { padding: 12px; }
    .command-row { flex-direction: column; align-items: stretch; }
    .command-copy { align-self: flex-end; }
}

/* ========== 管理面板分区导航 ==========
   宽屏（≥900px）左侧竖排常驻侧栏；≤899px 退化为顶部横向滚动标签条。
   同一套 DOM 与状态，只由 CSS 改呈现。规则写在 admin.css 而非 styles.css：
   本表在 styles.css 之后加载，且窄屏块内用 .modal 提高特异性——
   写进 styles.css 会被这里的同名选择器静默压掉。

   tab 三件套（role=tablist / tab / tabpanel）是本项目首次引入，
   宽高与内边距遵守既有 --ctl-h 触摸下限与 16px 防缩放字号。 */
.admin-tabs { display: flex; gap: 3px; margin-bottom: 18px; padding: 3px; border: 1px solid var(--admin-edge); border-radius: 9px; background: var(--admin-row); }
.admin-tab { flex: 1; min-height: var(--ctl-h, 44px); padding: 0 10px; border: 0; border-radius: 7px; background: transparent; color: var(--text-secondary); font-family: inherit; font-size: 12px; font-weight: 500; cursor: pointer; white-space: nowrap; transition: background 120ms, color 120ms, box-shadow 120ms; }
.admin-tab:hover { background: var(--admin-row-hover); color: var(--text); }
.admin-tab[aria-selected="true"] { background: var(--control-top); color: var(--text); box-shadow: inset 0 1px var(--admin-inner-light), 0 1px 3px rgba(67,51,39,.06); }
.admin-tab:focus-visible { outline: 2px solid var(--admin-strong-edge); outline-offset: 2px; }

.admin-panels { min-height: 0; }
.admin-panel[hidden] { display: none; }

/* 模块列表行：名称与说明在左，开关与状态文字紧贴右侧。
   状态文字必须与开关同行——它紧贴开关，一不一致最刺眼。 */
/* 模块分区：**一个模块一块**。块头是「名称 + 说明 + 开关」，块内是它自己的配置
   （模块通过 renderAdminSection 自己渲染）；平台级设置在末尾的 .platform-block 里。
   形态见 docs/mockup-admin-modules.html（版式 C）。 */
.modal .module-block {
    margin-bottom: 14px; border: 1px solid var(--border); border-radius: var(--radius);
    background: var(--glass); overflow: hidden;
}
.modal .module-block-head { display: flex; align-items: flex-start; gap: 14px; padding: 12px 14px; }
.modal .module-block-copy { flex: 1 1 auto; min-width: 0; }
.modal .module-block-title { display: block; font-size: 13px; font-weight: 600; color: var(--text); }
.modal .module-block-summary { display: block; margin-top: 2px; font-size: 11px; line-height: 1.5; color: var(--text-muted); }
.modal .module-setting-toggle { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 8px; }
.modal .module-setting-state { min-width: 44px; color: var(--text-muted); font-size: 11px; text-align: left; }
.modal .module-block-body { padding: 12px 14px 14px; border-top: 1px solid var(--border); background: var(--glass-strong); }
.modal .module-block-sub { margin: 0 0 2px; font-size: 12px; font-weight: 600; color: var(--text-secondary); }
.modal .module-admin-section { margin-top: 8px; }
/* 未启用：配置收起，留一行说明 + 展开入口 */
.modal .module-block-off-note {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    padding: 10px 14px; border-top: 1px solid var(--border);
    background: var(--glass-strong); color: var(--text-muted); font-size: 11px;
}
.modal .module-block-off-note button {
    min-height: 32px; padding: 0 12px; border: 1px solid var(--border); border-radius: 7px;
    background: linear-gradient(180deg, var(--control-top), var(--control-bottom));
    color: var(--text-secondary); font: inherit; font-size: 11px; cursor: pointer;
}
.modal .module-block-off-note button:hover { color: var(--text); }
/* 公开模块的「对普通访客开放」：它是**授权**开关，与「启用」是两件事，
   所以放在块头下方、折叠区之外——模块未启用时也要能开放。 */
.modal .module-open-row {
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    padding: 9px 14px; border-top: 1px solid var(--border); background: var(--glass-strong);
}
.modal .module-open-label {
    flex: 1 1 auto; display: inline-flex; align-items: center; gap: 8px;
    font-size: 12px; color: var(--text-secondary); cursor: pointer;
}
.modal .module-open-toggle { display: inline-flex; align-items: center; gap: 6px; }
.modal .module-open-toggle input { width: 16px; height: 16px; margin: 0; accent-color: var(--accent); }
.modal .module-open-state { min-width: 44px; color: var(--text-muted); font-size: 11px; }
.modal .module-open-hint { flex: 1 1 100%; margin: 0; font-size: 11px; line-height: 1.55; color: var(--text-muted); }
/* 平台级设置：明确与模块分开，别让用户以为它属于上面某个模块 */
.modal .platform-block { margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--border); }
/* 窄屏：开关换到名称/说明下方一行，不与它们抢横向空间 */
@media (max-width: 600px) {
    .modal .module-block-head { flex-wrap: wrap; }
    .modal .module-block-copy { flex: 1 1 100%; }
    .modal .module-setting-toggle { flex: 1 1 100%; }
}
.modal #serverList + #addServerBtn { margin-top: 4px; }
/* 监控目标列表。名称与地址一行、徽章一行、操作一行的卡片。
   单行紧凑排布：地址与名称并排、字号更小、行高更紧。
   早先是每台一行两行（名称一行、地址一行）+ 10px 上下内边距 = 59px/台，
   三台就占 176px，列表越加越长却只是地址在变——信息密度太低。
   压到约 34px/台后，触控目标又成了问题：按钮实测 26px，低于 44px 触摸下限，
   而那个高度是为「三个按钮挤一行」刻意定的，本轮加的「检测连通性」变成四个。

   改成**一台一张卡、横向多台并排**：密度由 grid 的列宽决定，
   卡内竖排因此腾出纵向空间，按钮与开关都能做到 44px 达标。
   一屏能看到的机器数与此前相当（单卡约 128px、两列约 256px，
   对应此前 4 行 × 37px ≈ 148px 起，且机器多时自动增列而不是变高）。 */
.modal #serverList { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 8px; margin-bottom: 10px; align-items: start; }
.modal .server-item {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 9px 10px;
    border: 1px solid var(--admin-edge);
    border-radius: 9px;
    background: var(--admin-row);
}
.modal .server-item-head { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.modal .server-item-name { flex: 0 1 auto; min-width: 0; color: var(--text); font-size: 12px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 分隔点挂在 url 上而不是名字的 ::after：本机卡的名字后面没有 url，
   挂在名字上会让它孤零零跟着一个「·」（实测截图可见）。 */
.modal .server-item-url::before { content: '·'; margin-right: 6px; color: var(--text-muted); font-weight: 400; }
.modal .server-item-url { flex: 1 1 auto; min-width: 0; color: var(--text-muted); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.modal .server-item-badges { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
/* 采集方式徽章。与 token 徽章同一形状，但它回答的是另一个问题
   （谁主动、要不要开端口），两者都要留着——排查离线时缺一个就得靠猜。 */
.modal .server-item-mode { flex: 0 0 auto; color: var(--text-muted); font-size: 10px; padding: 1px 6px; border: 1px solid var(--admin-edge); border-radius: 4px; }
.modal .server-item-mode[data-mode="push"] { color: var(--accent); border-color: var(--accent); }

/* 状态位：回答「这台机器现在怎么样」，是本次改版的核心。
   ⚠️ 只用四档配色，色相一多，五种状态就没人分得清了：
     ready        已就绪    → 主题色（正常）
     pending      等待部署  → 琥珀（唯一一个会自己变的，圆点会呼吸）
     not_deployed 未部署    → 中性偏弱（还没装是正常起步，不是错误）
     alert        证书异常  → 警示红（安全事件，必须最刺眼）
   offline 与 not_deployed 都是中性，但 offline 用次一级灰度区分——整个改版的
   核心论点就是「够不着」与「没装」不是一回事。 */
.modal .server-item-status {
    display: inline-flex; align-items: center; gap: 5px;
    flex: 0 0 auto; padding: 1px 7px 1px 6px;
    border: 1px solid currentColor; border-radius: 999px;
    font-size: 10px; font-weight: 600; white-space: nowrap;
}
.modal .server-item-status[data-kind="ready"] { color: var(--accent); }
.modal .server-item-status[data-kind="online"] { color: var(--accent); }
.modal .server-item-status[data-kind="pending"] { color: #a2762c; }
.modal .server-item-status[data-kind="not_deployed"] { color: var(--text-muted); }
/* offline 与 not_deployed 都是中性，但 offline 重一档：
   整个改版的核心论点就是「够不着」与「没装」不是一回事。 */
.modal .server-item-status[data-kind="offline"] { color: var(--text-secondary); }
.modal .server-item-status[data-kind="alert"] { color: var(--admin-error); }
/* unknown = 「没探测过」。刻意比 not_deployed 还弱：
   「不知道」不该看起来像「查过了，没装」。 */
.modal .server-item-status[data-kind="unknown"] { color: var(--text-muted); opacity: .75; }
/* ⚠️ 这里曾有 `.server-item-status .dot` + navDeployPulse 呼吸动效：状态位现在
   只渲染纯文字 span（圆点子元素早就不产出了），因而它从来没被看见过。
   留着会让人以为「等待部署有个动画」，实际元素不存在——一并删掉。 */
/* 深色下要提亮，否则这些色在近黑背景上几乎看不见 */
:root[data-theme="dark"] .modal .server-item-status[data-kind="pending"] { color: #e0bd7a; }
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .modal .server-item-status[data-kind="pending"] { color: #e0bd7a; }
}

/* 状态说明区。已就绪时不渲染 —— 那台机器正常，没有需要解释的事。 */
.modal .server-item-state {
    margin: 9px 0 0; padding: 9px 11px;
    border: 1px solid var(--admin-edge); border-radius: 9px;
    background: var(--admin-row);
}
.modal .server-item-state .lead {
    color: var(--text); font-size: 12px; font-weight: 600;
}
.modal .server-item-state .sub {
    margin-top: 2px; color: var(--text-muted); font-size: 11px;
    overflow-wrap: anywhere;
}
.modal .server-item-state[data-kind="alert"] {
    border-color: color-mix(in srgb, var(--admin-error) 40%, transparent);
}
.modal .server-item-state[data-kind="alert"] .lead { color: var(--admin-error); }
/* agent 版本提示。刻意不用 alert 色 —— 旧版 agent 不是故障，
   它只是缺一个新功能；染成警告色会让用户以为出问题了。 */
.modal .server-item-version {
    margin-top: 7px; padding-top: 7px; border-top: 1px solid var(--admin-edge);
    color: var(--text-secondary); font-size: 11px;
    font-variant-numeric: tabular-nums;
}
.modal .server-item-version span {
    margin-left: 7px; padding: 1px 6px; border-radius: 999px;
    background: color-mix(in srgb, var(--accent) 16%, transparent);
    color: var(--accent); font-size: 10px; font-weight: 600;
}
/* 自签证书开关 */
.modal .setting-toggle { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; }
.modal .setting-toggle input { width: 15px; height: 15px; margin: 0; accent-color: var(--accent); cursor: pointer; }
.modal .setting-toggle span { color: var(--text-secondary); font-size: 12px; min-width: 1.4em; }

/* 自签开关那一行：标题与开关紧邻成块、说明贴在下方，**不拉满整行**。
   此前是 `.setting-row` + 独立的一块 `fav-hint`，三个问题：
   ① `.setting-row label` 是 `justify-content: space-between`（styles.css:994），
      把标题甩到最左、开关推到最右——浏览器实测两者相距 **707px**，
      中间整片空白，那正是用户说的「是和之间有大量空白」；
   ② 说明文字是 `.setting-row` 之外的独立块，落在开关**下面**，
      离它描述的那一行太远，读的时候要来回对照；
   ③ 标记里是 `<label>` 套 `<label>` —— 无效 HTML。
   ⚠️ 修法不是「把 space-between 搬过来」：那只会把同一个形状换了个类名，
      707px 一分不少。第一版正是这么写的，截图一看形状没变——
      而 `space-between` 正是空白的**成因**，不是解法。
      真正要的是 `flex-start`：内容按自身宽度成块，靠左排。
   ⚠️ 必须在自己的选择器上重设 flex-direction：styles.css 的
   `max-width: 768px` 块里有一条 `.setting-row label { flex-direction: column }`，
   窄屏下会把这一行拆成竖排。本文件后加载、同特异性 —— 靠 `.self-signed-label`
   这个类提一级 specificity（0,2,0 > 0,1,1），不必依赖加载顺序。 */
.modal .self-signed-row { display: block; }
.modal .self-signed-label {
    display: flex; align-items: center; justify-content: flex-start;
    flex-direction: row; gap: 14px; width: auto;
}
.modal .self-signed-name { font-size: 13px; font-weight: 500; color: var(--text); }
/* 说明限宽并略放大：横贯 915px 的一行 11px 小字既难读又发虚
   （浏览器实测两行更易读）。max-width 用 em，随字号缩放。 */
.modal .self-signed-hint {
    margin: 8px 0 0; max-width: 62em;
    color: var(--text-muted); font-size: 12px; line-height: 1.6;
}
.modal .self-signed-hint code {
    font-size: 11px; padding: 1px 5px; border-radius: 4px;
    background: var(--admin-row); color: var(--text-secondary);
}
.modal .self-signed-hint strong { font-weight: 600; color: var(--text-secondary); }
.modal .server-item-actions { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
/* 可见性开关用原生 checkbox + 文字，而不是现有的 toggle-switch：
   那一套是给整行设置项的（占满一行），这里要和按钮并排。
   36px 是刻意从 44px 降下来的：44px 是**触摸**目标下限，而这一屏在
   桌面端占掉整行高度显得笨重（一台机器要占 ~180px）。
   36px 仍远高于可点击的舒适区，也远高于当初实测的 26px 事故值。
   窄屏另有分支，会把它抬回 44px —— 那里才是真正的触摸设备。 */
.modal .server-item-show { display: inline-flex; align-items: center; gap: 5px; padding: 0 9px; min-height: 36px; border: 1px solid var(--admin-edge); border-radius: 8px; background: var(--admin-row); cursor: pointer; user-select: none; }
.modal .server-item-show:hover { border-color: var(--accent); }
.modal .server-item-show input { width: 14px; height: 14px; margin: 0; accent-color: var(--accent); cursor: pointer; }
.modal .server-item-show span { color: var(--text-muted); font-size: 12px; }
.modal .server-item-actions .btn { min-height: 36px; padding: 0 11px; font-size: 12px; }
/* 窄屏：一张卡已是最窄单元。高度**必须抬回 44px**——
   窄屏正是触摸设备，降高度只会把目标打回不可用。
   （这与「桌面降到 36px」不是同一个决定，别顺手把这里的 44 改成 36。） */
@media (max-width: 700px) {
    .modal #serverList { grid-template-columns: 1fr; }
    .modal .server-item-mode { font-size: 9px; padding: 0 4px; }
    .modal .server-item-actions .btn,
    .modal .server-item-show { min-height: 44px; }
}

@media (min-width: 900px) {
    /* 宽屏是真正的侧栏：导航列 + 内容列并排。
       只把 .admin-tabs 改成 column 是不够的——它与 .admin-panels 是
       modalBody 里的同级块，各占满整行，于是每个标签 827px 宽、一片空白。
       要并排就得让 modalBody 本身成为横向 flex 容器。 */
    #modalBody { display: flex; gap: 20px; align-items: flex-start; }
    /* 190 不是随手取的：面板内容列 = 1200 − 48(modal-body 内边距) − 190 − 20(间隙)
       ≈ 940，再减掉画布左右内边距后正好放下 936px 的背板（1:1 的前提）。 */
    .admin-tabs { flex: 0 0 190px; flex-direction: column; align-items: stretch; margin-bottom: 0; position: sticky; top: 0; }
    .admin-tab { flex: 0 0 auto; text-align: left; padding: 0 14px; }
    .admin-panels { flex: 1 1 auto; min-width: 0; }
}

/* 窄屏：标签条横向滚动，右侧渐隐提示沿用 .modal .fav-manager-sidebar 的
   同一套 mask 语法（本表 768px 块内），不发明第二种。 */
@media (max-width: 899px) {
    .admin-tabs { overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
    .admin-tabs::-webkit-scrollbar { display: none; }
    .admin-tab { flex: 0 0 auto; min-height: 44px; font-size: 16px; padding: 0 14px; }
    .modal #modulesEditor .btn,
    .modal .module-block-off-note button { min-height: 44px; }
    .modal .module-open-label { min-height: 44px; }
}

@media (prefers-reduced-transparency: reduce) {
    .modal-content,
    .fav-dialog,
    .ui-dialog { background: var(--bg-card); backdrop-filter: none; -webkit-backdrop-filter: none; }
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .modal-content,
    .fav-dialog,
    .ui-dialog { background: var(--bg-card); }
}

/* ==================== Special Line · 订阅来源（后台模块分区） ====================
   挂点在 app.js 的模块编辑器里（renderAdminSection），这里只管排版。 */

.special-line-source-list { display: grid; gap: 8px; margin: 4px 0 10px; }

.special-line-source-row {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--glass);
}

.special-line-source-copy { display: grid; gap: 3px; min-width: 0; }
.special-line-source-copy strong {
    overflow: hidden;
    color: var(--text);
    font-size: 12px;
    font-weight: 600;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.special-line-source-copy small { color: var(--text-muted); font-size: 10px; }
.special-line-source-error { color: var(--sl-danger); font-size: 10px; line-height: 1.5; }
/* 「下次 15:50」跟在「最近成功 N 分钟前」后面，是用来对照的：两者一起读才知道
   现在是不是正常范围。只有真超期（超过周期 5 分钟）才换成等待调度并变色。 */
.special-line-next.is-overdue { color: var(--sl-warning); }

.special-line-source-state { color: var(--text-muted); font-size: 10px; white-space: nowrap; }
/* 「已连接」用 --accent：与 .module-card-status[data-kind="ready"] 同一套，
   本仓库没有 success 色 token。告警两档用 styles.css 里定义的 --sl-*。 */
.special-line-source-state[data-status="ok"] { color: var(--accent); }
.special-line-source-state[data-status="expired"] { color: var(--sl-warning); }
.special-line-source-state[data-status="failed"] { color: var(--sl-danger); }

.special-line-source-actions { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 6px; }
.special-line-source-actions .btn { min-width: 44px; min-height: 44px; padding-inline: 10px; font-size: 11px; }
.special-line-source-actions .btn-danger { color: var(--sl-danger); }

.special-line-source-form {
    display: grid;
    gap: 10px;
    margin: 10px 0;
    padding: 12px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--bg-hover);
}
.special-line-source-form label { display: grid; gap: 5px; color: var(--text-muted); font-size: 10px; }
.special-line-source-form input,
.special-line-source-form select {
    width: 100%;
    min-height: 40px;
    padding: 8px 9px;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--bg-card);
    color: var(--text);
    /* 16px：窄屏聚焦时 iOS 不会把页面整体放大 */
    font-size: 16px;
}
.special-line-source-form .special-line-form-actions { display: flex; justify-content: flex-end; gap: 8px; }
.special-line-source-form .btn { min-height: 44px; }
#specialLineAddSource { min-height: 44px; }

.special-line-source-note { margin: 6px 0 0; color: var(--text-muted); font-size: 10px; line-height: 1.6; }

.modal .special-line-source-row .special-line-icon {
    display: inline-grid;
    width: 20px;
    height: 20px;
    place-items: center;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--bg-card);
    color: var(--accent);
    font-size: 10px;
    font-weight: 700;
}

@media (max-width: 480px) {
    .special-line-source-row { grid-template-columns: auto minmax(0, 1fr); }
    .special-line-source-state { grid-column: 2; justify-self: start; }
    .special-line-source-actions .btn { min-height: 44px; }
}

/* ========== 站点默认首页：面板内可视化编辑 ==========
   画布渲染的是真实的 .backboard / .search / .grid / .bookmark —— 样式来自 styles.css，
   这里只负责「它被装在一个编辑器外壳里」这件事。不做任何缩放：宽度自适应面板可用列，
   1440 视口下正好 936px（与独立首页逐像素一致）。 */
.template-badge { margin-inline-start: 10px; padding: 1px 8px; border: 1px solid var(--border); border-radius: 999px; color: var(--text-muted); font-size: 11px; font-weight: 400; }
.template-editor { margin-top: 10px; border: 1px solid var(--border); border-radius: 12px; background: var(--bg); overflow: hidden; }
.template-editor-bar { display: flex; align-items: center; gap: 12px; padding: 8px 12px; border-bottom: 1px solid var(--border); background: var(--admin-row, var(--bg-hover)); }
.template-editor[data-mode="edit"] .template-editor-bar { background: var(--bg-card); }
.template-editor-note { flex: 1; min-width: 0; color: var(--text-muted); font-size: 12px; }
/* 左右不留内边距：这是 936px 背板能在 940px 内容列里原尺寸落下的前提。
   margin-inline: auto 同时盖掉 .backboard 那条 ⚠️ 数值居中量——视口 ≥1024 而容器
   窄于 936 时，calc((100% - 936px) / 2) 会算出负数把背板推出左边界。 */
.template-canvas { padding: 16px 0; }
.template-canvas .backboard { margin-inline: auto; }
/* 画布内部不参与页面导航与检索：头部是静态外观，网格里的链接在只读态照常可点，
   编辑态由 bindGridEdit 的 click 分支拦下改成打开编辑框。 */
.template-canvas .header { pointer-events: none; }
.template-editor-actions { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border-top: 1px solid var(--border); background: var(--admin-row, var(--bg-hover)); }
.template-editor-actions > div { margin-inline-start: auto; display: flex; gap: 6px; }
.template-state { color: var(--text-muted); font-size: 12px; }
/* 有未保存改动用强调色：发布被禁用时，这个颜色是「为什么不能发布」的答案。 */
.template-state[data-dirty="true"] { color: var(--accent); }
@media (max-width: 899px) {
    /* 标签栏退化成顶部横条后，画布可用宽度变小，936 自然放不下——按真实首页
       在同宽度下的重排结果渲染即可（不自适应缩放、不横向滚动的既定取舍）。 */
    .template-canvas { padding: 12px 0; }
    .template-editor-actions { flex-wrap: wrap; }
    .template-editor-actions > div { margin-inline-start: 0; width: 100%; }
    .template-editor-actions > div .btn { flex: 1 1 auto; }
}

/* ===== 普通口令云端空间 =====

   访客侧的「同步」对话框与管理员侧的邀请码列表。

   为什么写在 admin.css 而不是 styles.css：对话框那一族的规则全在本文件
   （`.ui-dialog` 的外观，以及 `:is(.modal, .fav-dialog, .ui-dialog) .btn` 这种
   作用域到宿主类的按钮样式）。新内容样式写进 styles.css 就拿不到 `.btn` 的外观，
   而且会变成「两个样式表各声明一半」——本仓库为此改错过两次。

   ⚠️ 本段**不得**声明 .ui-dialog-overlay / .ui-dialog / .fav-dialog-overlay：
   那两条（三条）已被 tests/dialog-regressions.test.js 逐条断言——overlay 的内边距
   必须用四边简写、且 .fav-dialog-overlay 不得出现 padding 简写（会覆盖
   styles.css ≤768px 块里的安全区规则）。复用现成类，别在别处另立门户。 */

.sync-dialog-body { min-height: 0; }
.sync-state { margin: 0 0 8px; color: var(--text); font-size: 14px; font-weight: 600; }
.sync-state code { padding: 1px 5px; border-radius: 5px; background: var(--admin-row); font-size: 12px; font-weight: 500; }
.sync-note { margin: 0 0 10px; color: var(--text-secondary); font-size: 12px; line-height: 1.6; }
.sync-hint { margin: 0 0 12px; color: var(--text-secondary); font-size: 11px; line-height: 1.6; }
.sync-error { margin: 0 0 10px; color: var(--admin-error); font-size: 12px; line-height: 1.6; }
/* 按钮一行放不下就换行，而不是把最后一个挤出对话框。
   每个按钮都 `flex: 1 1 auto`：换行后**整行铺满**，不会在第二行留下一个孤零零
   靠右的按钮（实测「导出两边 / 采用云端 / 保留本机」三个在 400px 对话框里会换行，
   第一版换行后那个按钮只占半行，看着像排版坏了）。 */
.sync-actions { flex-wrap: wrap; }
.sync-actions .btn { flex: 1 1 auto; }
.sync-link { padding: 0; border: 0; background: none; color: var(--accent); font: inherit; cursor: pointer; text-decoration: underline; }
.sync-link:hover { color: var(--accent-hover); }
/* 危险区：删除云端空间是这个对话框里唯一**不可撤销**的动作，所以它不占按钮行，
   单独落在分隔线下面，而且文字的按钮名就是它的后果（不能藏在「更多」之类后面）。 */
.sync-danger-zone { margin: 14px 0 0; padding-top: 12px; border-top: 1px solid var(--border); }
.sync-danger { color: var(--admin-error); }
.sync-danger:hover { color: var(--admin-error); opacity: .82; }

.sync-invite-actions { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.sync-invite-issued { min-width: 0; color: var(--accent); font-size: 12px; line-height: 1.5; word-break: break-all; }
.sync-invite-list { display: grid; gap: 6px; }
.sync-invite-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border: 1px solid var(--admin-edge);
    border-radius: 9px;
    background: var(--admin-row);
    font-size: 12px;
}
.sync-invite-row code { color: var(--text); }
/* 用过、撤销、过期的一律淡出：它们只是历史记录，不再能用来创建空间 */
.sync-invite-row.is-stale { opacity: .62; }
.sync-invite-state { flex: 1 1 auto; color: var(--text-secondary); }

@media (max-width: 480px) {
    .sync-invite-row { flex-wrap: wrap; }
    .sync-invite-state { flex-basis: 100%; }
}

/* ---- 「同步管理」tab：概览 / 邀请码 / 空间治理 --------------------------------
   两三个控件一行、放不下就换行（窄屏 390 与宽屏 980 用同一套 DOM，只由
   flex-wrap 改变排布）。表格在自己的容器里横向滚动，不把整个面板撑宽。 */
.sync-overview { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; margin-bottom: 14px; }
.sync-stat { padding: 10px 12px; border: 1px solid var(--admin-edge); border-radius: 10px; background: var(--admin-row); }
.sync-stat b { display: block; color: var(--text); font-size: 18px; line-height: 1.4; }
.sync-stat span { color: var(--text-secondary); font-size: 11px; }
.sync-policy-row { display: flex; flex-wrap: wrap; align-items: center; gap: 18px; margin: 4px 0 2px; }
.sync-policy-item { display: flex; align-items: center; gap: 8px; color: var(--text-secondary); font-size: 12px; cursor: pointer; }
/* 开关是 44×24 的定尺，别让 flex 把它压扁（它是 flex 项时会收缩）。 */
.sync-policy-item .toggle-switch { flex: 0 0 auto; }
.sync-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 10px 0; }
.sync-toolbar input[type="search"],
.sync-toolbar input[type="number"],
.sync-toolbar select {
    padding: 6px 9px;
    border: 1px solid var(--admin-edge);
    border-radius: 8px;
    background: var(--bg);
    color: var(--text);
    font: inherit;
    font-size: 12px;
}
.sync-toolbar input[type="search"] { flex: 1 1 190px; min-width: 0; }
.sync-toolbar input[type="number"] { width: 84px; }
.sync-toolbar-note { color: var(--text-secondary); font-size: 11px; line-height: 1.6; }
/* 总暂停时说明行用错误色（同样是「说清后果」的那句，见 paintSyncPolicy）。 */
.fav-hint.is-warn { color: var(--admin-error); }
.sync-banner { margin: 0 0 10px; padding: 9px 12px; border-radius: 8px; background: var(--admin-row); color: var(--text-secondary); font-size: 12px; line-height: 1.7; }
.sync-codes { display: grid; gap: 6px; margin: 8px 0 4px; }
.sync-code { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 7px 10px; border: 1px dashed var(--admin-edge); border-radius: 8px; font-size: 12px; }
.sync-code code { color: var(--text); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; word-break: break-all; }
.sync-code small { margin-left: auto; color: var(--text-secondary); font-size: 11px; }
.sync-space-host { overflow-x: auto; }
.sync-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.sync-table th,
.sync-table td { padding: 7px 6px; border-bottom: 1px solid var(--admin-edge); text-align: left; white-space: nowrap; }
.sync-table th { color: var(--text-secondary); font-size: 11px; font-weight: 500; }
.sync-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
/* 已停用 / 已删除的行淡出，但**不是**不可读：墓碑本身是记录，要能看清它的标识。 */
tr.sync-space-row.is-stale td { color: var(--text-secondary); }
tr.sync-space-row.is-gone td { color: var(--text-secondary); text-decoration: line-through; }
.sync-empty { padding: 26px 10px; text-align: center; color: var(--text-secondary); font-size: 12px; line-height: 1.8; }
.sync-pager { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 8px; margin-top: 10px; color: var(--text-secondary); font-size: 12px; }
/* `hidden` 对 display:flex 的元素不生效（作者样式盖过 UA 规则），这里补一条。 */
.sync-pager[hidden] { display: none; }
.sync-pager-page { margin: 0 6px; }

@media (max-width: 480px) {
    .sync-code { flex-direction: column; align-items: flex-start; }
    .sync-code small { margin-left: 0; }
    .sync-pager { justify-content: space-between; }
}
