/* ==========================================================================
   DNSAdmin — 本文件只做三件事：
   1) 给 Bootstrap 自带的 --bs-* 变量填值（不重写组件规则、不重复官方默认值）
   2) 一套取 Bootswatch Cosmo 的调色板（状态色 + 中性色，亮暗两套；只把 primary 换成 #4f5ae0）
   3) 后台布局骨架 + 左侧菜单（Bootstrap 没有，必须手写）

   按钮/卡片/表格/表单/下拉/分页/徽章/面包屑的外观全部由 bootstrap.min.css 产出，
   它是**官方 Bootstrap 5.3.8**（不是 Bootswatch Cosmo）。自写与覆盖只集中在四处：
     第 1 节   调色板（取 Cosmo 原值）+ 画布底色 --bs-app-bg（官方没有这一层）
     第 8 节   官方各组件把色值编译成字面值、不读 --bs-* 的地方，逐条接回变量
     第 4~6 节 布局骨架、左侧菜单、几个模板没有的原语（一律按 Bootstrap 的方式写）
     第 7 节   响应式 + 暗色下的两处补丁
   原则：一个色值只有一个来源（第 1 节的变量 / 第 8 节的品牌变量），
         组件层不许再出现第二套定义。
   ========================================================================== */

/* ---------- 1. 变量 ---------- */
/* 这一块必须无条件生效（不能带 dark 排除）：字体、卡片底色、三个尺寸、状态色主色，
   都不属于"亮色专属配色"，暗色下同样要用。官方 [data-bs-theme=dark] 只覆盖颜色，
   不碰字体和尺寸，所以这里写 :root 是安全的。 */
:root {
  /* 输入框边框：官方没有这一层（它的输入框和分隔线共用 --bs-border-color）。
     亮色下两者一样，所以这里回落到 --bs-border-color，只有暗色块会给出抬高一档的值。 */
  --bs-input-border-color: var(--bs-border-color);

  --bs-body-font-family: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
  /* 用 rem 不用 px：官方默认是 1rem，写死 px 会让浏览器的字号设置失效（无障碍问题） */
  --bs-body-font-size: 0.875rem;   /* 14px @ 16px 根字号 */

  /* 状态色主色：明暗共用（Cosmo 就是这么定义的，暗色块不覆盖它们）。
     放在 :root 而不是分模式写两遍 —— 同一个事实只留一份。
     ⚠ 这四个是"填充色"，不是"文字色"：#3fb618 在白底上只有 2.65:1，
       当正文用必须改拿各自的 -text-emphasis（见下面分模式那两段）。 */
  --bs-success: #3fb618;
  --bs-success-rgb: 63, 182, 24;
  --bs-warning: #ff7518;
  --bs-warning-rgb: 255, 117, 24;
  --bs-danger: #ff0039;
  --bs-danger-rgb: 255, 0, 57;
  --bs-info: #9954bb;
  --bs-info-rgb: 153, 84, 187;
  /* Cosmo 的 dark 是 #373a3c（官方 #212529），light 与官方同为 #f8f9fa 所以不必写 */
  --bs-dark: #373a3c;
  --bs-dark-rgb: 55, 58, 60;

  /* 七个语义色各自的「填充三档」= 基色 / hover / active，取值全部来自 Cosmo 的 .btn-X。
     官方没有这个规划（它把三档直接编译进 .btn-X / .table-X / 勾选框…），
     这里补出来，让所有组件只认这一个来源 —— 换一个色只改这一行。
     官方 .btn-X 里那三档是各写一份字面值，所以不补的话按钮根本不跟主色走。
     命名与第 8 节的 --brand-fill / -hover / -active 是同一套（primary 单独一组，
     因为它是用户指定的品牌色）。Cosmo 里 secondary 与 dark 同色，这里照搬。 */
  --fill-secondary: #373a3c;   --fill-secondary-hover: #2f3133;   --fill-secondary-active: #2c2e30;
  --fill-success:   #3fb618;   --fill-success-hover:   #369b14;   --fill-success-active:   #329213;
  --fill-info:      #9954bb;   --fill-info-hover:      #82479f;   --fill-info-active:      #7a4396;
  --fill-warning:   #ff7518;   --fill-warning-hover:   #d96314;   --fill-warning-active:   #cc5e13;
  --fill-danger:    #ff0039;   --fill-danger-hover:    #d90030;   --fill-danger-active:    #cc002e;
  --fill-light:     #f8f9fa;   --fill-light-hover:     #d3d4d5;   --fill-light-active:     #c6c7c8;
  --fill-dark:      #373a3c;   --fill-dark-hover:      #555859;   --fill-dark-active:      #5f6163;

  /* 模板里没有对应概念的只有这三条 */
  --brand: var(--bs-link-color);        /* 亮 #4f5ae0 / 暗 #7d84ff，实际值在第 8 节覆盖 */
  --brand-soft: var(--bs-primary-bg-subtle);

  /* 画布底：官方没有这一层（它的 body 与卡片同色，走 --bs-body-bg）。
     要做「灰底 + 白卡片」只能自补一个，**且只作用于 body 一条规则**；
     其余一切「面」（卡片/下拉/表单/侧栏/顶栏）全部吃官方语义的 --bs-body-bg，
     不再另立第二套表面色（旧的 --card-bg 就是这么被删掉的）。 */
  --bs-app-bg: #eef0f4;

  --sidebar-w: 236px;
  --topbar-h: 56px;
  /* 底栏高度：主区页脚与侧栏底部那条（.sidebar-foot）共用，两条才会落在同一水平线上、
     连成一根通栏的底线。值就是 .sidebar-foot 撑出来的实际高度：
     10(上内边距) + 30(折叠按钮) + 11(下内边距) + 1(分隔线) = 52px。 */
  --footer-h: 52px;

  /* 布局骨架的两道阴影：官方 --bs-box-shadow 那一组是给卡片/弹层的，比这重得多
     （0 .5rem 1rem rgba(0,0,0,.15)），侧栏与页头要的是"贴着边的一层薄灰"，用不上。
     值只在这里出现一次；浮层那处仍用官方的 --bs-box-shadow（它是浮层，本来就该重）。 */
  --sidebar-shadow: 4px 0 16px rgba(0, 0, 0, .06);
  --page-head-shadow: 0 4px 12px rgba(0, 0, 0, .04);
}

/* 调色板：整套取 Bootswatch Cosmo 的取值，只把 primary 换成用户指定的 #4f5ae0。
   Cosmo 与官方的区别不在色相类别，而在「饱和度与明度档位」—— 它把所有状态色都调得更艳更亮：
     success #198754 → #3fb618（黄绿）  warning #ffc107 → #ff7518（橙）
     danger  #dc3545 → #ff0039（纯红）  info    #0dcaf0 → #9954bb（紫）
     正文    #212529 → #373a3c（深灰，不压到纯黑）
   三件套（text-emphasis / bg-subtle / border-subtle）也一并取 Cosmo 的，
   它的淡底比官方的浓得多（#d9f0d1 / #ffe3d1 / #ffccd7 / #ebddf1），淡底徽章才有颜色。

   ⚠ 一处必须配套改 HTML：Cosmo 的主色是"填充色"，拿来当正文色不达标
     （success 2.65 / warning 2.69 / danger 3.95，AA 要 4.5）。
     所以页面里当文字用的地方一律取 -text-emphasis 那一档（实测 8.67 / 9.43，达标）。
     新增用法请照此办理：填充用 base，文字用 -text-emphasis。

   淡底徽章实测（text-emphasis 落在 bg-subtle 上）：8.67 / 8.68 / 9.43 / 10.56 / 12.57，全部达标。
   必须排除 dark：与「灰白三层」同理，否则会把官方 [data-bs-theme=dark] 整套盖掉。 */
:root:not([data-bs-theme="dark"]) {
  /* 中性：Cosmo 的深灰。正文 #373a3c 比官方的 #212529 柔，强调色直接压到纯黑 */
  --bs-body-color: #373a3c;
  --bs-body-color-rgb: 55, 58, 60;
  --bs-emphasis-color: #000;
  --bs-emphasis-color-rgb: 0, 0, 0;
  --bs-secondary-color: rgba(55, 58, 60, .75);   /* 半透明，跟着所在底色自动深浅 */
  --bs-tertiary-color: rgba(55, 58, 60, .5);
  --bs-border-color: #dee2e6;

  --bs-success-text-emphasis: #19490a;
  --bs-success-bg-subtle: #d9f0d1;
  --bs-success-border-subtle: #b2e2a3;

  --bs-warning-text-emphasis: #662f0a;
  --bs-warning-bg-subtle: #ffe3d1;
  --bs-warning-border-subtle: #ffc8a3;

  --bs-danger-text-emphasis: #660017;
  --bs-danger-bg-subtle: #ffccd7;
  --bs-danger-border-subtle: #ff99b0;

  --bs-info-text-emphasis: #3d224b;
  --bs-info-bg-subtle: #ebddf1;
  --bs-info-border-subtle: #d6bbe4;

  /* Cosmo 的 secondary 和正文同色（都是 #373a3c），.text-secondary 因此失去"次要"的层次。
     这是 Cosmo 的原样行为，先照搬；如果嫌层次塌了，把这一条改浅即可（如 #6a7076）。 */
  --bs-secondary: #373a3c;
  --bs-secondary-rgb: 55, 58, 60;
  --bs-secondary-text-emphasis: #161718;
  --bs-secondary-bg-subtle: #d7d8d8;
  --bs-secondary-border-subtle: #afb0b1;
}

/* 三层「面」在亮色下一条都不覆盖，全部回到官方语义（Cosmo 这三条与官方取值完全相同，
   所以"往 Cosmo 靠"根本不涉及这一层）：
     --bs-body-bg      #fff     表面：卡片 / 下拉 / 表单 / 侧栏 / 顶栏
     --bs-tertiary-bg  #f8f9fa  淡一档：hover / input-group-text / 菜单
     --bs-secondary-bg #e9ecef  深一档：禁用态 / 进度条轨道 / 滑块轨道 / nav-tabs
   顺序是 secondary 比 tertiary **深**。写反了不只是命名问题 —— 官方有 12 条规则吃
   secondary-bg，其中 .form-control:disabled / .form-select:disabled / .progress 轨道 /
   .form-range 轨道 都是"必须比表面深一档才看得出来"的地方，给成淡档就全糊了。
   页面底那层灰在 --bs-app-bg（上一节），不再是 --bs-body-bg。 */

/* 暗色：文字/品牌/组件色仍由官方 [data-bs-theme=dark] 接管，这里只调「底」这一层。
   官方暗色是 --bs-body-bg 与卡片同色（都 #212529），整页发平；
   方向与官方一致（暗色下 secondary 是离表面最远的那一档），只是把档位差压小。 */
[data-bs-theme="dark"] {
  --bs-app-bg: #0e1116;         /* 画布底，比表面还深一档 */
  --bs-body-bg: #171b23;        /* 表面：卡片 / 侧栏 / 顶栏 / 下拉 */
  --bs-body-bg-rgb: 23, 27, 35;
  --bs-secondary-bg: #262c37;   /* 深一档：禁用态 / 进度条轨道 / nav-tabs hover */
  --bs-secondary-bg-rgb: 38, 44, 55;
  --bs-tertiary-bg: #1f242e;    /* 淡一档：侧栏 hover / 下拉条目 hover */
  --bs-tertiary-bg-rgb: 31, 36, 46;
  --bs-border-color: #262a34;
  /* 输入框边框：本套的 --bs-border-color 压得比官方暗色（#495057）淡很多，分隔线正合适，
     但输入框是需要被看见的控件，得比分隔线抬一档 —— 官方没有这一层，自补一个。
     亮色不需要（分隔线本身够清楚），所以只在暗色给值，亮色回落到 --bs-border-color。 */
  --bs-input-border-color: #333846;

  /* 调色板的暗色档（亮色档在上面）。同样取 Cosmo 的暗色块：
     主色明暗共用（它不覆盖），只有三件套和中性色分模式。
     淡底徽章实测（-text-emphasis 落在 bg-subtle 上）：9.19 / 9.01 / 6.55 / 7.52 / 5.57。 */

  --bs-body-color: #dee2e6;
  --bs-body-color-rgb: 222, 226, 230;
  --bs-emphasis-color: #fff;
  --bs-emphasis-color-rgb: 255, 255, 255;
  --bs-secondary-color: rgba(222, 226, 230, .75);
  --bs-tertiary-color: rgba(222, 226, 230, .5);

  --bs-success-text-emphasis: #8cd374;
  --bs-success-bg-subtle: #0d2405;
  --bs-success-border-subtle: #266d0e;

  --bs-warning-text-emphasis: #ffac74;
  --bs-warning-bg-subtle: #331705;
  --bs-warning-border-subtle: #99460e;

  --bs-danger-text-emphasis: #ff6688;
  --bs-danger-bg-subtle: #33000b;
  --bs-danger-border-subtle: #990022;

  --bs-info-text-emphasis: #c298d6;
  --bs-info-bg-subtle: #1f1125;
  --bs-info-border-subtle: #5c3270;

  /* Cosmo 的暗色块没有覆盖 --bs-secondary，沿用亮色的 #373a3c ——
     压在本套的暗底 #171b23 上等于隐形（约 1.4:1）。
     这里取 Cosmo 自己在暗色下给 secondary 的 -text-emphasis 值 #87898a（暗底 4.91:1），
     是这套色板里现成的值，不是新造的。 */
  --bs-secondary: #87898a;
  --bs-secondary-rgb: 135, 137, 138;
  --bs-secondary-text-emphasis: #87898a;
  --bs-secondary-bg-subtle: #0b0c0c;
  --bs-secondary-border-subtle: #212324;
}

/* ---------- 2. 基础 ---------- */
html { height: 100%; }
/* 画布底只给 body：官方 body 吃 --bs-body-bg（与卡片同色），我们把它让给卡片，
   页面底改吃自补的 --bs-app-bg。这一条是「灰底 + 白卡片」的唯一落点。

   ❗body 是 flex 列：配合 .main 的 flex:1，页脚才能「永远在最下面」——
   内容不足一屏时 main 吃掉剩余高度、把页脚顶到视口底；内容超过一屏时它跟在内容后面，
   滚到底才出现（sticky footer 的标准做法）。
   原来是 html,body{height:100%}（写死一屏），那样页脚只能停在文档流里它自己的位置。
   fixed 的侧栏 / 遮罩 / 抽屉 / 弹窗都不在 flex 流里，不受影响。 */
body {
  min-height: 100%;
  display: flex; flex-direction: column;
  -webkit-font-smoothing: antialiased; background: var(--bs-app-bg);
}
/* 内联 SVG 图标（图标库见 admin.js，模板不自带图标） */
.icon { width: 18px; height: 18px; flex: none; stroke-width: 1.7; }

/* ---------- 3. 组件：只给变量填值，不重写规则 ---------- */
/* 官方把这几处的字号硬编码成 1rem，没有对应变量；.form-control-sm
   只改 padding 不带 font-size，原生类也用不上，只能覆盖声明 */
.btn { --bs-btn-font-size: var(--bs-body-font-size); }
.dropdown-menu { --bs-dropdown-font-size: var(--bs-body-font-size); }

/* 官方没有表单边框这个变量（输入框与分隔线共用 --bs-border-color），
   而本套暗色下分隔线压得极淡，输入框跟着一起就看不见了 —— 所以自补
   --bs-input-border-color：亮色回落 --bs-border-color，只有暗色块抬一档。
   与字号一样，这两条是本文件仅有的「给组件属性填值」而非填组件变量的地方。 */
.form-control, .form-select {
  font-size: var(--bs-body-font-size);
  border-color: var(--bs-input-border-color);
}

/* 浏览器自动填充（:-webkit-autofill）：用户刷新页面时 Chrome/Edge 会把保存过的凭据
   回填到用户名/密码框，并强套一层自己的淡蓝底色 + 自己的文字色 —— 那段样式的优先级
   高于组件层，直接改 background-color 覆盖不了。

   两条限制决定了这里的写法：
   1) 底色只能用 inset 阴影「盖」出来（-webkit-box-shadow），改 background 无效；
      文字色必须同样显式抢回来（-webkit-text-fill-color），顺带钉住 caret-color ——
      否则暗色下 Chrome 的深色文字/光标会留在深底上看不见。
      值全部走 --bs-*，明暗各自翻转，不写字面值。
   2) ❗聚焦那条必须把聚焦环一起写上：input:-webkit-autofill:focus 的特异性
      （元素 + 两个伪类）高于 .form-control:focus（类 + 一个伪类），
      不写就会把聚焦反馈整个盖掉。环的值与上面 .form-control:focus 保持一致。 */
input:-webkit-autofill,
input:-webkit-autofill:hover {
  -webkit-box-shadow: 0 0 0 1000px var(--bs-body-bg) inset;
  -webkit-text-fill-color: var(--bs-body-color);
  caret-color: var(--bs-body-color);
  border-color: var(--bs-input-border-color);
}
input:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 1000px var(--bs-body-bg) inset, 0 0 0 .25rem var(--bs-focus-ring-color);
  border-color: var(--brand);
}

/* Windows Chrome：密码框被密码管理器自动填充后，首帧用页面字体画掩码圆点 ——
   中文字体（微软雅黑）里 • 是全角字形，圆点又大又疏；点一下页面 Chrome 才把
   强制的内置字体套上去，圆点变密，看起来像"密码显示坏了"。把密码框字体钉成
   西文字体（• 是半角），首帧与交互后就一致了。
   - 字体栈把 PingFang SC 放最前：macOS 命中它、观感与现在完全一样；
     Windows 上没有 PingFang，落到 Arial，圆点恢复半角。
   - :-webkit-autofill::first-line 那条是必须的：Chromium 会给自动填充字段
     强塞自己的字体（issue 953689，安全考虑，普通 font-family 盖不过它），
     只能借这个伪元素抢回来；密码框只有一行，first-line 即整行。
   - 密码框里永远只有掩码圆点，改字体不影响任何可见内容。 */
input[type="password"],
input[type="password"]:-webkit-autofill::first-line {
  font-family: "PingFang SC", Arial, sans-serif;
}

/* ❗别给 .card 加 overflow:hidden —— 它会把卡片内的下拉菜单一起裁掉。
   卡片头底色走 --bs-card-cap-bg，别写字面值。 */

/* 首页统计卡里那个大图标：默认 18px 在卡片里偏小，这里放大一档。
   只改尺寸不动颜色 —— 颜色由模板上 Bootstrap 现成的 text-* 工具类给，
   这里不另立一套「卡片图标专用色」。 */
.stat-icon { width: 1.75rem; height: 1.75rem; }

/* 侧栏菜单的左右留白由 .sidebar-scroll 的 12px + .nav-link 的 10px 内边距共同给出，
   不给 .nav 额外加 padding（模板的 .nav 自带 padding-left:0） */

/* 表格完全不碰：单元格内边距、表头、斑马纹、hover 全用模板自带的值 */
.breadcrumb .icon { width: 15px; height: 15px; }

/* 首页「系统资源」那三条进度条：上面另有百分比文字，条子本身只是个指示，
   官方 1rem 在这种地方偏厚，压到一半。
   ❗必须限定在这三条上：--bs-progress-height 是官方的**全局**尺寸变量，
     写进 :root 会把别处所有进度条一起改掉。所以挂在只有首页这三条才有的
     data-res-bar 上（模板上现成的标识，不为这条新增类名）。
   用 rem 不用 px：跟第 1 节 --bs-body-font-size 同一个理由 ——
     写死 px 会让浏览器的字号设置失效（无障碍问题）。 */
.progress[data-res-bar] {
  --bs-progress-height: 0.5rem;
}

/* ---------- 4. 布局骨架 ---------- */
.sidebar {
  position: fixed; inset: 0 auto 0 0;
  width: var(--sidebar-w);
  background: var(--bs-body-bg);
  border-right: 1px solid var(--bs-border-color);
  box-shadow: var(--sidebar-shadow);
  display: flex; flex-direction: column;
  z-index: 1040;
  transition: width .2s ease, transform .2s ease;
}
.sidebar-brand {
  height: var(--topbar-h); flex: none;
  display: flex; align-items: center; gap: 10px;
  padding: 0 18px;
  background: var(--bs-body-bg);
  color: var(--bs-emphasis-color);
  font-weight: 600; font-size: 14.5px;
  letter-spacing: .8px; text-transform: uppercase;
}
.brand-mark {
  width: 30px; height: 30px; flex: none;
  border-radius: 50%;
  display: grid; place-items: center;
}
.brand-mark .icon { width: 16px; height: 16px; stroke-width: 2; }

.topbar {
  position: sticky; top: 0; z-index: 1030;
  height: var(--topbar-h);
  margin-left: var(--sidebar-w);
  background: var(--bs-body-bg);
  border-bottom: 1px solid var(--bs-border-color);
  display: flex; align-items: center; gap: 12px;
  padding: 0 20px;
  transition: margin-left .2s ease;
}
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 4px; }

/* flex:1 —— 撑满 body 剩下的高度，把页脚顶到底（见上面 body 的注释） */
.main { flex: 1 1 auto; margin-left: var(--sidebar-w); padding: 0 20px 28px; transition: margin-left .2s ease; }
/* 页脚：与侧栏底部那条（.sidebar-foot）同高、同底色、同分隔线。
   flex:none 是必须的 —— 它是 body flex 列里唯一不吃剩余高度的那一项。
   高度走 --footer-h 而不是靠内容撑：内容只是一行小字（约 21px），
   不撑到 52px 就没法和侧栏那条对齐。内边距取 .sidebar-foot 的 10/11，水平 20px 与 .main 对齐。
   字号与字色用模板工具类（small / text-secondary），这里只管版面。 */
.footer {
  flex: none;
  min-height: var(--footer-h);
  margin-left: var(--sidebar-w);
  padding: 10px 20px 11px;
  background: var(--bs-body-bg);
  border-top: 1px solid var(--bs-border-color);
  display: flex; align-items: center;
  transition: margin-left .2s ease;
}
.page-head {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  background: var(--bs-body-bg);
  margin: 0 -20px 22px; padding: 18px 20px;
  border-bottom: 1px solid var(--bs-border-color);
  box-shadow: var(--page-head-shadow);
}
/* 页头里的搜索框（.input-group）必须自带一个确定宽度。
   .input-group 自己带 width:100%，而它所在的那一行（.ms-auto.d-flex）是**按内容定宽**的
   flex 项 —— 百分比宽度对着一个还没定下来的宽度解，两边互相推诿，最后会把这一行算得
   比内容还窄，旁边的按钮就被压成竖排（"新增" 断成「+新」「增」两行）。
   给搜索框一个确定宽度，这一行的内容宽度就是确定的，挤不下才会换行（flex-wrap 管这个）。
   按钮再统一 nowrap：中文按钮就两三个字，断行纯粹是难看。 */
.page-head .input-group { flex: none; width: 16rem; }
.page-head .btn { white-space: nowrap; }

/* 下拉多选（partials/multiselect.gohtml + admin.js 的 data-multiselect）。
   按钮就是 .form-select，跟旁边的普通下拉长得一样；菜单里是一列勾选框。
   .dropdown-menu 默认 min-width:10rem、不限高 —— 这里对齐按钮宽度并限高：
   「定制查询」有 40 多个指标，不限高会一路顶出屏幕。 */
[data-multiselect] .dropdown-menu {
  width: 100%;
  max-height: 16rem; overflow-y: auto;
}
[data-multiselect] .dropdown-item { cursor: pointer; }
[data-multiselect] .dropdown-item-text { cursor: default; }

/* 遮罩：底色走官方 --bs-backdrop-bg（明暗同值 #000），淡度放在 opacity 上。
   ⚠ 原先是把 .35 的透明度写进 rgba(20,26,41,.35) 里 —— 亮色下没问题，
     但暗色画布 #0e1116 叠上去算出来是 #10141d，几乎看不出有遮罩。 */
.sidebar-backdrop {
  position: fixed; inset: 0;
  background: var(--bs-backdrop-bg);
  z-index: 1035; opacity: 0; visibility: hidden;
  transition: opacity .2s;
}
body.sidebar-open .sidebar-backdrop { opacity: .35; visibility: visible; }

/* ---------- 5. 左侧菜单 ---------- */
.sidebar-scroll { flex: 1 1 auto; overflow-y: auto; padding: 8px 12px 16px; }
.sidebar-scroll::-webkit-scrollbar { width: 5px; }
.sidebar-scroll::-webkit-scrollbar-thumb { background: var(--bs-gray-500); border-radius: 3px; }

.nav-group-title {
  font-size: 11px; letter-spacing: .6px; font-weight: 600;
  color: var(--bs-secondary-color);
  padding: 12px 10px 5px;
}
.nav-group-title:first-child { padding-top: 8px; }

/* 菜单项：内缩的圆角条（不通栏），图标与文字间距 10px。
   常态文字用模板的次级色，hover/选中才提到强调色 —— 层次靠"变色"而不是靠"加边框加阴影" */
.sidebar .nav-link {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 10px; margin-bottom: 3px;
  border-radius: var(--bs-border-radius);   /* 6px，模板默认 */
  color: var(--bs-body-color);
  font-size: 14px; cursor: pointer; white-space: nowrap;
  text-decoration: none;   /* 只在本侧栏内去掉模板 5.3 给 a 加的下划线 */
  transition: background .15s, color .15s;
}
.sidebar .nav-link:hover { background: var(--bs-tertiary-bg); color: var(--bs-emphasis-color); }
.sidebar .nav-link.active { background: var(--brand-soft); color: var(--brand); font-weight: 500; }
.sidebar .nav-link.active .nav-arrow { opacity: 1; }
/* 父项含选中子项时只上色、不给背景（背景是选中态专属） */
.sidebar .nav-item.has-active > .nav-link { color: var(--brand); }
.sidebar .nav-item.has-active > .nav-link .nav-arrow { opacity: 1; }
.sidebar .nav-text { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; }
.sidebar .nav-arrow { width: 14px; height: 14px; opacity: .6; transition: transform .2s ease; }
/* 箭头收起时朝右（#i-chevron），展开时转 90° 朝下 */
.sidebar .nav-item.open > .nav-link .nav-arrow { transform: rotate(90deg); }

/* 二级菜单：没有独立底色，只是缩进容器。
   缩进 34px + 容器 6px = 父项文字左边缘再往右 12px，两级不会齐平，层级才看得出来 */
.sub-menu {
  list-style: none;
  margin: 0 0 2px; padding: 0 0 0 6px;
  max-height: 0; overflow: hidden;
  transition: max-height .24s ease;
}
/* ❗这个上限必须装得下最多的一组子菜单：智能解析 8 项（每项 13.5px 字号 + 20px
   上下内边距 ≈ 40px，共约 330px），原来写的 320px 会把最后一项裁掉一截。
   给 26rem（416px）留出再增两项的余量；展开动画的时长由 transition 控制，
   上限比实际高度大一点只影响缓动曲线的观感，不影响别的。 */
.sidebar .nav-item.open > .sub-menu { max-height: 26rem; }
.sub-menu .nav-link { position: relative; padding: 10px 22px 10px 32px; font-size: 13.5px; }
.sub-menu .nav-link.active::before { opacity: 1; background: var(--brand); }
.sub-menu .nav-link:hover { background: var(--bs-tertiary-bg); color: var(--bs-emphasis-color); }
.sub-menu .nav-link.active { background: var(--brand-soft); color: var(--brand); font-weight: 600; }
.sidebar-foot { flex: none; padding: 10px 12px 11px; border-top: 1px solid var(--bs-border-color); }
.sidebar-toggle {
  width: 30px; height: 30px; margin: 0 auto;
  border: 1px solid var(--bs-border-color); border-radius: 50%;
  background: var(--bs-body-bg); color: var(--bs-body-color);
  display: grid; place-items: center;
}
.sidebar-toggle:hover { background: var(--brand-soft); border-color: var(--bs-primary); color: var(--bs-primary); }
.sidebar-toggle .icon { width: 15px; height: 15px; transition: transform .2s ease; }
body.sidebar-collapsed .sidebar-toggle .icon { transform: rotate(180deg); }

/* 折叠 */
body.sidebar-collapsed { --sidebar-w: 68px; }
body.sidebar-collapsed .nav-text,
body.sidebar-collapsed .nav-group-title,
body.sidebar-collapsed .nav-arrow,
body.sidebar-collapsed .brand-text { display: none; }
/* 去掉左右内边距，换成一列等宽方形热区，保证图标落在同一条中轴；
   overflow 必须放开，否则绝对定位的二级浮层会被滚动容器裁掉 */
body.sidebar-collapsed .sidebar-scroll { padding: 8px 0 16px; overflow: visible; }
/* li 撑满宽度，浮层的 left:100% 才是侧栏右边缘 */
body.sidebar-collapsed .sidebar .nav-item { width: 100%; }
body.sidebar-collapsed .sidebar .nav-item > .nav-link {
  width: 40px; height: 40px; flex: none;
  padding: 0; margin: 0 auto 4px; gap: 0; justify-content: center;
}
body.sidebar-collapsed .sidebar .nav-item > .nav-link .icon { width: 19px; height: 19px; }
/* 二级菜单改为 hover 浮层 */
body.sidebar-collapsed .sidebar .nav-item.has-sub { position: relative; }
body.sidebar-collapsed .sidebar .nav-item.has-sub:hover > .nav-link { background: var(--brand-soft); color: var(--brand); }
body.sidebar-collapsed .sidebar .nav-item.has-sub > .sub-menu {
  position: absolute; left: calc(100% + 8px); top: 0;
  width: 172px; max-height: none; overflow: visible;
  margin: 0;
  padding: 6px;
  background: var(--bs-body-bg);
  border: var(--bs-border-width) solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg);
  box-shadow: var(--bs-box-shadow);
  opacity: 0; visibility: hidden; transform: translateX(-4px);
  transition: opacity .16s ease, transform .16s ease, visibility .16s;
  z-index: 30;
}
/* 透明桥：补上 8px 空隙，否则鼠标移向浮层时会断开 hover */
body.sidebar-collapsed .sidebar .nav-item.has-sub > .sub-menu::after {
  content: ""; position: absolute; left: -10px; top: 0; bottom: 0; width: 10px;
}
body.sidebar-collapsed .sidebar .nav-item.has-sub:hover > .sub-menu {
  opacity: 1; visibility: visible; transform: translateX(0);
}
body.sidebar-collapsed .sidebar .nav-item.has-sub > .sub-menu .nav-link {
  padding: 7px 10px 7px 26px;    /* 浮层不支持缩进语义，左内边距只负责留白 */
  margin-bottom: 1px;
  font-size: 13.5px;
  border-radius: var(--bs-border-radius);   /* 与一级菜单条目同一套圆角（6px） */
}
body.sidebar-collapsed .sub-menu .nav-link::before { left: 13px; }
body.sidebar-collapsed .sidebar .nav-item.has-sub > .sub-menu .nav-link.active {
  background: var(--brand-soft); color: var(--brand);
}
/* 分组标题隐藏后用一小段发丝线维持分组感 */
body.sidebar-collapsed .nav-group-title:not(:first-child) + .nav { position: relative; margin-top: 13px; }
body.sidebar-collapsed .nav-group-title:not(:first-child) + .nav::before {
  content: ""; position: absolute;
  top: -7px; left: 50%; transform: translateX(-50%);
  width: 18px; height: 1px; background: var(--bs-border-color);
}
body.sidebar-collapsed .sidebar-brand { justify-content: center; padding: 0; }

/* ---------- 6. 剩下非写不可的自写件 ---------- */
/* 顶栏图标按钮：模板没有「纯图标按钮」这个原语（.btn 都带文字内边距），只能自己写。
   圆角/底色/hover 色一律取模板变量 */
.icon-btn {
  width: 34px; height: 34px;
  display: grid; place-items: center;
  border: 1px solid transparent; border-radius: var(--bs-border-radius);
  background: transparent; color: var(--bs-body-color);
  position: relative;
}
.icon-btn:hover { background: var(--bs-secondary-bg); border-color: var(--bs-border-color); color: var(--bs-primary); }
.icon-btn .icon { width: 17px; height: 17px; }
/* 按钮里的图标比正文小一号。原型里这是用内联 style 写的（每个按钮都抄一遍 15px），
   全站内联 style 已清零，尺寸这种事归样式表管 —— 一条规则覆盖所有按钮。 */
.btn .icon { width: 15px; height: 15px; }

/* 以下只是「尺寸」：底色、圆角、字重、色值全部由 HTML 上的模板工具类给出
   （rounded-circle / bg-*-subtle / text-*-emphasis / fw-semibold） */
/* .avatar 只管尺寸。圆角 / 底色 / 字色 / 字重一律由 HTML 上的工具类给
   （rounded-circle / bg-primary-subtle / text-primary-emphasis / fw-semibold）——
   CSS 里再写一份就是第二个来源，而且带 !important 的工具类永远赢，
   那份声明根本不会生效。（原先在第 5 节还留了一整份底色与字色，已删。） */
.avatar { width: 32px; height: 32px; flex: none; display: grid; place-items: center; font-size: 13px; }
.topbar .avatar { width: 30px; height: 30px; font-size: 12px; }

/* 回到顶部浮动钮：目前只有 help 这类长文档页会放（页面上放 .to-top 才有）。
   定位/间距/底色/圆角/阴影全用 Bootstrap 工具类（position-fixed bottom-0 end-0 m-3
   btn-primary rounded-circle shadow-sm），这里只补模板没有的两样：圆形尺寸和内容居中。
   显隐由 admin.js 的 initToTop 切 d-none（滚过 200px 才出现）。 */
.to-top {
  width: 40px; height: 40px;
  display: grid; place-items: center;
  z-index: 1020; /* Bootstrap sticky 档：压过表格；低于顶栏(1030)/侧栏(1040)，位置本就错开 */
  line-height: 1;
}

/* ---------- 6b. 表格列宽：auto 布局（Bootstrap 默认），完全照搬 table_perfect 模式 ----------
   ❗标题三不（不换行 / 不压缩 / 不重叠）：.table th 上 white-space:nowrap + width:max-content，
      标题本来就不长，永远不会触顶、不需要省略号。
   ❗操作 / 时间列：.action-col（white-space:nowrap + width:1%）收缩到内容宽，绝不换行。
   ❗会超长的列（域名 / IP 列表 / 记录 / 匹配IPs）：.cell-ellipsis 给一个硬上限（max-width），
      折叠态单行截断 + 省略号；展开态（.expanded）在「原宽度」内换行、可全选复制。
   ❗点击展开、点该区域外自动收起（admin.js 的 initCellClamp 按 table_perfect 的 document 级 click 实现）。
   ❗max-width 数值可按需调整：当前 20rem（约 320px），写在 .table td.cell-ellipsis 上。 */

/* 标题：死守底线，绝不换行、绝不压缩 */
.table th {
  white-space: nowrap;
  width: max-content;
}

/* 专门针对操作列的表头和单元格，强制不换行，保持一行显示 */
.table th.action-col,
.table td.action-col {
  white-space: nowrap;
  width: 1%; /* 让操作列根据内容自适应最小宽度，绝不换行 */
}

/* 表格内容默认单行溢出省略，锁死列宽 */
.table td.cell-ellipsis {
  max-width: 20rem; /* 可根据需要调整单行展示的最大宽度 */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}

/* 展开状态：在原宽度内换行，并允许选中复制 */
.table td.cell-ellipsis.expanded {
  white-space: normal;
  word-break: break-all;
  cursor: text;
}


/* ---------- 7. 响应式 ---------- */
/* 这里原本还有一条「暗色下输入框边框抬一档」的规则 —— 它是给属性直接填色值的
   「暗色补充」，违反铁律①，已改成自补变量 --bs-input-border-color 移入第 3 节。
   本节的判定标准：只留"不同视口下的几何变化"，一切颜色都必须靠变量自己翻转。 */

@media (max-width: 991.98px) {
  .sidebar { transform: translateX(-100%); box-shadow: var(--bs-box-shadow-lg); }
  body.sidebar-open .sidebar { transform: none; }
  /* 不用 !important：本块写在基础规则之后、特异性相同，顺序已经足够 */
  .topbar, .main, .footer { margin-left: 0; }
  .main { padding: 0 16px 20px; }
  .footer { padding: 10px 16px 11px; }
  .page-head { margin: 0 -16px 16px; padding: 16px; }
  /* 面包屑在窄屏隐藏：写在 HTML 的 d-none d-lg-flex 上，不用 CSS 覆盖组件 */
}
@media (min-width: 992px) { #sidebarOpenBtn { display: none; } }

/* ---------- 8. 把各组件写死的主色接回变量 ---------- */
/* 这一节只做一件事：官方把每个语义色的基色 / hover / active / disabled / focus 光晕
   编译成字面值，散落在各组件里，一个都不读 --bs-* 根变量。这里逐条接回去，
   让色值只保留一个来源（第 1 节的 --fill-* 三档与 --bs-<色>、本节 ① 的品牌色变量）。

   色值来源：primary 是用户指定的品牌色 #4f5ae0（变量在下面 ①，命名 --brand-fill 三档）；
   其余七色取 Cosmo 原值，在第 1 节。改色只改那两处，本节不用动。

   两层覆盖：
     ② 组件自己的变量（.btn-X / .table-X / 勾选框 / 分页…各写一份字面值）
     ③ 直接写在属性里的字面值（表单聚焦边框、勾选框选中态、range 滑块）
   **升级 Bootstrap 后照 ③ 注释里的字面值清单重扫一遍，就能核对有没有漏项。**

   两条派生规则：
   - 实心填充（按钮底、勾选框、分页当前页）恒定用原色 #4f5ae0：
     暗色若跟着 --bs-primary 一起提亮，白字对比度会从 5.8:1 掉到 2.8:1。
   - 文字/链接/描边/淡底用 --bs-primary，暗色提亮到 #7d84ff（同色相提亮，对比度 5.6:1）。 */

/* ① 根变量：亮色 */
:root {
  --bs-blue: #4f5ae0;
  --bs-primary: #4f5ae0;
  --bs-primary-rgb: 79, 90, 224;
  --bs-primary-text-emphasis: #262c7a;    /* 淡底上的文字 */
  --bs-primary-bg-subtle: #e7e9fb;        /* 徽章/头像/侧栏选中底 */
  --bs-primary-border-subtle: #aab0f2;
  --bs-link-color: #4f5ae0;
  --bs-link-color-rgb: 79, 90, 224;
  --bs-link-hover-color: #3f48cf;
  --bs-link-hover-color-rgb: 63, 72, 207;
  --bs-focus-ring-color: rgba(79, 90, 224, .25);

  /* 模板没有「品牌填充」这个概念，只能自己立三个 */
  --brand-fill: #4f5ae0;
  --brand-fill-hover: #3f48cf;
  --brand-fill-active: #373fb8;
}
/* ① 根变量：暗色（文字/链接提亮，填充不变）
   选择器与第 1 节统一用官方的 data-bs-theme —— 全站不再存在自造的 data-theme。 */
[data-bs-theme="dark"] {
  --bs-blue: #7d84ff;
  --bs-primary: #7d84ff;
  --bs-primary-rgb: 143, 148, 236;
  --bs-primary-text-emphasis: #b6bbfb;
  --bs-primary-bg-subtle: #262a40;      /* 徽章/头像/侧栏选中底：暗色下要够亮才看得出是一块 */
  --bs-primary-border-subtle: #3b4299;
  --bs-link-color: #7d84ff;
  --bs-link-color-rgb: 143, 148, 236;
  --bs-link-hover-color: #a9aef7;
  --bs-link-hover-color-rgb: 169, 174, 247;
  --bs-focus-ring-color: rgba(143, 148, 236, .25);
}

/* ② 组件级变量：模板把色值写死在各组件自己的变量里，不吃根变量 */
.btn-primary {
  --bs-btn-bg: var(--brand-fill);                    --bs-btn-border-color: var(--brand-fill);
  --bs-btn-hover-bg: var(--brand-fill-hover);        --bs-btn-hover-border-color: var(--brand-fill-hover);
  --bs-btn-active-bg: var(--brand-fill-active);      --bs-btn-active-border-color: var(--brand-fill-active);
  --bs-btn-disabled-bg: var(--brand-fill);           --bs-btn-disabled-border-color: var(--brand-fill);
  --bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
}
.btn-outline-primary {
  --bs-btn-color: var(--bs-primary);                 --bs-btn-border-color: var(--bs-primary);
  --bs-btn-hover-bg: var(--brand-fill);              --bs-btn-hover-border-color: var(--brand-fill);
  --bs-btn-active-bg: var(--brand-fill);             --bs-btn-active-border-color: var(--brand-fill);
  --bs-btn-disabled-color: var(--bs-primary);        --bs-btn-disabled-border-color: var(--bs-primary);
  --bs-btn-focus-shadow-rgb: var(--bs-primary-rgb);
}

/* ②b 状态色：官方把七个变体（secondary/success/info/warning/danger/light/dark）的
   基色、hover、active、disabled、focus 光晕全部编译成字面值，一个都不读 --bs-<色>。
   这里统一接回第 1 节的 --fill-* 三档 + 各自的 -rgb，从此换色只改那三行。
   hover / active 的文字色沿用官方（#fff，light 是 #000）；只有 warning 与 info
   官方给的是黑字、Cosmo 给的是白字，按"往 Cosmo 靠"改成白字。
   ⚠ 白字压在 #ff7518 上只有 2.44:1（AA 要 4.5），Cosmo 原样如此；
     若哪天要可读性优先，把 .btn-warning 的四个 color 改回 #000 即可。 */
.btn-secondary {
  --bs-btn-bg: var(--fill-secondary);                --bs-btn-border-color: var(--fill-secondary);
  --bs-btn-hover-bg: var(--fill-secondary-hover);    --bs-btn-hover-border-color: var(--fill-secondary-hover);
  --bs-btn-active-bg: var(--fill-secondary-active);  --bs-btn-active-border-color: var(--fill-secondary-active);
  --bs-btn-disabled-bg: var(--fill-secondary);       --bs-btn-disabled-border-color: var(--fill-secondary);
  --bs-btn-focus-shadow-rgb: var(--bs-secondary-rgb);
}
.btn-success {
  --bs-btn-bg: var(--fill-success);                  --bs-btn-border-color: var(--fill-success);
  --bs-btn-hover-bg: var(--fill-success-hover);      --bs-btn-hover-border-color: var(--fill-success-hover);
  --bs-btn-active-bg: var(--fill-success-active);    --bs-btn-active-border-color: var(--fill-success-active);
  --bs-btn-disabled-bg: var(--fill-success);         --bs-btn-disabled-border-color: var(--fill-success);
  --bs-btn-focus-shadow-rgb: var(--bs-success-rgb);
}
.btn-info {
  --bs-btn-color: #fff;                              --bs-btn-hover-color: #fff;
  --bs-btn-active-color: #fff;                       --bs-btn-disabled-color: #fff;
  --bs-btn-bg: var(--fill-info);                     --bs-btn-border-color: var(--fill-info);
  --bs-btn-hover-bg: var(--fill-info-hover);         --bs-btn-hover-border-color: var(--fill-info-hover);
  --bs-btn-active-bg: var(--fill-info-active);       --bs-btn-active-border-color: var(--fill-info-active);
  --bs-btn-disabled-bg: var(--fill-info);            --bs-btn-disabled-border-color: var(--fill-info);
  --bs-btn-focus-shadow-rgb: var(--bs-info-rgb);
}
.btn-warning {
  --bs-btn-color: #fff;                              --bs-btn-hover-color: #fff;
  --bs-btn-active-color: #fff;                       --bs-btn-disabled-color: #fff;
  --bs-btn-bg: var(--fill-warning);                  --bs-btn-border-color: var(--fill-warning);
  --bs-btn-hover-bg: var(--fill-warning-hover);      --bs-btn-hover-border-color: var(--fill-warning-hover);
  --bs-btn-active-bg: var(--fill-warning-active);    --bs-btn-active-border-color: var(--fill-warning-active);
  --bs-btn-disabled-bg: var(--fill-warning);         --bs-btn-disabled-border-color: var(--fill-warning);
  --bs-btn-focus-shadow-rgb: var(--bs-warning-rgb);
}
.btn-danger {
  --bs-btn-bg: var(--fill-danger);                   --bs-btn-border-color: var(--fill-danger);
  --bs-btn-hover-bg: var(--fill-danger-hover);       --bs-btn-hover-border-color: var(--fill-danger-hover);
  --bs-btn-active-bg: var(--fill-danger-active);     --bs-btn-active-border-color: var(--fill-danger-active);
  --bs-btn-disabled-bg: var(--fill-danger);          --bs-btn-disabled-border-color: var(--fill-danger);
  --bs-btn-focus-shadow-rgb: var(--bs-danger-rgb);
}
.btn-light {
  --bs-btn-bg: var(--fill-light);                    --bs-btn-border-color: var(--fill-light);
  --bs-btn-hover-bg: var(--fill-light-hover);        --bs-btn-hover-border-color: var(--fill-light-hover);
  --bs-btn-active-bg: var(--fill-light-active);      --bs-btn-active-border-color: var(--fill-light-active);
  --bs-btn-disabled-bg: var(--fill-light);           --bs-btn-disabled-border-color: var(--fill-light);
  --bs-btn-focus-shadow-rgb: var(--bs-light-rgb);
}
.btn-dark {
  --bs-btn-bg: var(--fill-dark);                     --bs-btn-border-color: var(--fill-dark);
  --bs-btn-hover-bg: var(--fill-dark-hover);         --bs-btn-hover-border-color: var(--fill-dark-hover);
  --bs-btn-active-bg: var(--fill-dark-active);       --bs-btn-active-border-color: var(--fill-dark-active);
  --bs-btn-disabled-bg: var(--fill-dark);            --bs-btn-disabled-border-color: var(--fill-dark);
  --bs-btn-focus-shadow-rgb: var(--bs-dark-rgb);
}
/* 描边按钮：常态用基色（文字 + 边框），悬停放实心 */
.btn-outline-secondary {
  --bs-btn-color: var(--bs-secondary);               --bs-btn-border-color: var(--bs-secondary);
  --bs-btn-hover-bg: var(--fill-secondary);          --bs-btn-hover-border-color: var(--fill-secondary);
  --bs-btn-active-bg: var(--fill-secondary);         --bs-btn-active-border-color: var(--fill-secondary);
  --bs-btn-disabled-color: var(--bs-secondary);      --bs-btn-disabled-border-color: var(--bs-secondary);
  --bs-btn-focus-shadow-rgb: var(--bs-secondary-rgb);
}
.btn-outline-success {
  --bs-btn-color: var(--bs-success);                 --bs-btn-border-color: var(--bs-success);
  --bs-btn-hover-bg: var(--fill-success);            --bs-btn-hover-border-color: var(--fill-success);
  --bs-btn-active-bg: var(--fill-success);           --bs-btn-active-border-color: var(--fill-success);
  --bs-btn-disabled-color: var(--bs-success);        --bs-btn-disabled-border-color: var(--bs-success);
  --bs-btn-focus-shadow-rgb: var(--bs-success-rgb);
}
.btn-outline-info {
  --bs-btn-color: var(--bs-info);                    --bs-btn-border-color: var(--bs-info);
  --bs-btn-hover-bg: var(--fill-info);               --bs-btn-hover-border-color: var(--fill-info);
  --bs-btn-active-bg: var(--fill-info);              --bs-btn-active-border-color: var(--fill-info);
  --bs-btn-disabled-color: var(--bs-info);           --bs-btn-disabled-border-color: var(--bs-info);
  --bs-btn-focus-shadow-rgb: var(--bs-info-rgb);
}
.btn-outline-warning {
  --bs-btn-color: var(--bs-warning);                 --bs-btn-border-color: var(--bs-warning);
  --bs-btn-hover-bg: var(--fill-warning);            --bs-btn-hover-border-color: var(--fill-warning);
  --bs-btn-active-bg: var(--fill-warning);           --bs-btn-active-border-color: var(--fill-warning);
  --bs-btn-disabled-color: var(--bs-warning);        --bs-btn-disabled-border-color: var(--bs-warning);
  --bs-btn-focus-shadow-rgb: var(--bs-warning-rgb);
}
.btn-outline-danger {
  --bs-btn-color: var(--bs-danger);                  --bs-btn-border-color: var(--bs-danger);
  --bs-btn-hover-bg: var(--fill-danger);             --bs-btn-hover-border-color: var(--fill-danger);
  --bs-btn-active-bg: var(--fill-danger);            --bs-btn-active-border-color: var(--fill-danger);
  --bs-btn-disabled-color: var(--bs-danger);         --bs-btn-disabled-border-color: var(--bs-danger);
  --bs-btn-focus-shadow-rgb: var(--bs-danger-rgb);
}
.btn-outline-light {
  --bs-btn-color: var(--bs-light);                   --bs-btn-border-color: var(--bs-light);
  --bs-btn-hover-bg: var(--fill-light);              --bs-btn-hover-border-color: var(--fill-light);
  --bs-btn-active-bg: var(--fill-light);             --bs-btn-active-border-color: var(--fill-light);
  --bs-btn-disabled-color: var(--bs-light);          --bs-btn-disabled-border-color: var(--bs-light);
  --bs-btn-focus-shadow-rgb: var(--bs-light-rgb);
}
.btn-outline-dark {
  --bs-btn-color: var(--bs-dark);                    --bs-btn-border-color: var(--bs-dark);
  --bs-btn-hover-bg: var(--fill-dark);               --bs-btn-hover-border-color: var(--fill-dark);
  --bs-btn-active-bg: var(--fill-dark);              --bs-btn-active-border-color: var(--fill-dark);
  --bs-btn-disabled-color: var(--bs-dark);           --bs-btn-disabled-border-color: var(--bs-dark);
  --bs-btn-focus-shadow-rgb: var(--bs-dark-rgb);
}

.dropdown-menu {
  --bs-dropdown-link-active-bg: var(--brand-fill);
  --bs-dropdown-header-color: var(--bs-secondary-color);
}
.nav-pills     { --bs-nav-pills-link-active-bg: var(--brand-fill); }
.list-group    { --bs-list-group-active-bg: var(--brand-fill); --bs-list-group-active-border-color: var(--brand-fill); }
/* 进度条：官方把 --bs-progress-bar-bg 声明在容器 .progress / .progress-stacked 上，
   由内部的 .progress-bar 继承消费；三个都写是为了兼容只挂 .progress-bar 的用法 */
.progress, .progress-stacked, .progress-bar { --bs-progress-bar-bg: var(--brand-fill); }
.pagination {
  --bs-pagination-active-bg: var(--brand-fill);      --bs-pagination-active-border-color: var(--brand-fill);
  --bs-pagination-focus-box-shadow: 0 0 0 .25rem var(--bs-focus-ring-color);
}
.accordion  { --bs-accordion-btn-focus-box-shadow: 0 0 0 .25rem var(--bs-focus-ring-color); }
/* 关闭按钮（offcanvas / modal 头部的 ×）：官方用 --bs-btn-close-filter 控制黑白，
   但官方的 dark 变量块**没有**给这个变量赋值 —— 只有 .btn-close-white 才带滤镜。
   结果暗色面板上的 × 仍是全黑，压在 #171b23 上根本看不见。
   这里取 .btn-close-white 的那串值补上（不是自造，是官方现成的）。 */
[data-bs-theme="dark"] .btn-close { --bs-btn-close-filter: invert(1) grayscale(100%) brightness(200%); }
.btn-close  { --bs-btn-close-focus-shadow: 0 0 0 .25rem var(--bs-focus-ring-color); }

/* ③ 写在属性里的字面值：官方把主色编译期写死，既不吃 --bs-primary，
   官方的 [data-bs-theme=dark] 也没有覆盖它们（实测对 form-check / form-range 零覆盖），
   所以只能逐条接回变量 —— 这是"用编译好的 CSS"绕不过去的一步。
   清单是扫 bootstrap.min.css 里 primary 家族的 #0d6efd / #0a58ca / #0a53be /
   #86b7fe / rgba(13,110,253,…) / #cfe2ff 得到的，共 24 条规则；
   ② 段接走能靠组件级变量解决的那些，剩下的都在这里。
   **升级 Bootstrap 后重扫这几个字面值即可核对有没有漏项。**
   真正"改一处全局跟随"只有一条路：用 Sass 把 $primary 改成 #4f5ae0 重新编译，
   那时 ② ③ 两节可以整段删掉。 */

/* 聚焦环同上：颜色取 --bs-focus-ring-color，宽度用官方的 .25rem（原来是自造的 3px，
   官方对这个值有规划，不该另立一档）。 */
.form-control:focus, .form-select:focus { border-color: var(--brand); box-shadow: 0 0 0 .25rem var(--bs-focus-ring-color); }
.nav-link:focus-visible { box-shadow: 0 0 0 .25rem var(--bs-focus-ring-color); }

/* 两处写死的次级灰：官方这两个位置没有变量，接回次级文字色 */
.blockquote-footer { color: var(--bs-secondary-color); }
.form-floating > :disabled ~ label { color: var(--bs-secondary-color); }

/* 下拉的深色变体：它自带一份独立字面值，不吃 .dropdown-menu 那组变量 */
/* 深色下拉：5.3 的遗留 API（官方建议改用 data-bs-theme="dark"），它自带一整套与主题
   无关的固定深色值。这里把「底」和几个中性色接回变量，文字保持浅色（深底上只能这样）。 */
.dropdown-menu-dark {
  --bs-dropdown-bg: var(--bs-dark);
  --bs-dropdown-color: var(--bs-light);
  --bs-dropdown-link-color: var(--bs-light);
  --bs-dropdown-link-hover-color: var(--bs-light);
  --bs-dropdown-link-active-color: var(--bs-light);
  --bs-dropdown-link-active-bg: var(--brand-fill);
  --bs-dropdown-link-hover-bg: rgba(var(--bs-light-rgb), .15);
  --bs-dropdown-link-disabled-color: var(--bs-secondary);
  --bs-dropdown-header-color: var(--bs-secondary);
}

/* 勾选框 / 单选：选中态与半选态 */
.form-check-input:checked,
.form-check-input[type="checkbox"]:indeterminate {
  background-color: var(--brand-fill);
  border-color: var(--brand-fill);
}
/* 聚焦：与 .form-control:focus 同一套。颜色取 --bs-focus-ring-color
   （官方为这个概念准备的变量，我们已按品牌色覆盖），宽度也用官方的 .25rem。
   ⚠ 不能取 --brand-soft（= --bs-primary-bg-subtle）：它的暗色档是 #262a40，
     压在 #171b23 的卡片上等于没有，暗色下聚焦反馈会整个消失 —— 必须是半透明叠加。 */
.form-check-input:focus {
  border-color: var(--brand);
  box-shadow: 0 0 0 .25rem var(--bs-focus-ring-color);
}
/* 开关：未选中时聚焦的圆点色内嵌在 SVG data URI 里 —— URL 不是 CSS 值上下文，
   var() 在里面不会被解析，所以只能把两个 hex 写死并亮暗各来一份。
   它们是同一档「聚焦边框色」的两态（亮 #a3a8f0 / 暗 #6d74dd）；
   这个值原本挂在 --brand-focus-border 上，但因只有此处用、且此处用不了变量，
   那个变量已删，值就落在这里（全文件唯一一处写死的品牌色）。 */
.form-switch .form-check-input:focus {
  --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23a3a8f0'/%3e%3c/svg%3e");
}
[data-bs-theme="dark"] .form-switch .form-check-input:focus {
  --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%236d74dd'/%3e%3c/svg%3e");
}

/* 滑块：滑块本体 + 聚焦光晕。webkit / moz 两套伪元素各写一份。
   官方内环写死 #fff，这里换成 --bs-body-bg —— 暗色下白环在深底上会变成一道刺眼的亮圈。 */
.form-range::-webkit-slider-thumb { background-color: var(--brand-fill); }
.form-range::-moz-range-thumb     { background-color: var(--brand-fill); }
.form-range:focus::-webkit-slider-thumb { box-shadow: 0 0 0 1px var(--bs-body-bg), 0 0 0 .25rem var(--bs-focus-ring-color); }
.form-range:focus::-moz-range-thumb     { box-shadow: 0 0 0 1px var(--bs-body-bg), 0 0 0 .25rem var(--bs-focus-ring-color); }

.btn-link { --bs-btn-disabled-color: var(--bs-secondary-color); }

/* 表格的语义变体：官方把 .table-secondary / -success / -info / -warning / -danger /
   -light / -dark 的九个变量全部编译成字面值（.table-primary 在下面一段）。
   前五档一律接三件套（文字 / 淡底 / 边框），条带 / hover / active 沿用 .table 基类
   自己的做法 ——inset 阴影叠一层半透明，档位 .05 / .075 / .1，
   只是把叠加色从中性黑换成各自的主色 rgb。 */
.table-secondary {
  --bs-table-color: var(--bs-secondary-text-emphasis);
  --bs-table-bg: var(--bs-secondary-bg-subtle);
  --bs-table-border-color: var(--bs-secondary-border-subtle);
  --bs-table-striped-color: var(--bs-secondary-text-emphasis);
  --bs-table-striped-bg: rgba(var(--bs-secondary-rgb), .05);
  --bs-table-hover-color: var(--bs-secondary-text-emphasis);
  --bs-table-hover-bg: rgba(var(--bs-secondary-rgb), .075);
  --bs-table-active-color: var(--bs-secondary-text-emphasis);
  --bs-table-active-bg: rgba(var(--bs-secondary-rgb), .1);
}
.table-success {
  --bs-table-color: var(--bs-success-text-emphasis);
  --bs-table-bg: var(--bs-success-bg-subtle);
  --bs-table-border-color: var(--bs-success-border-subtle);
  --bs-table-striped-color: var(--bs-success-text-emphasis);
  --bs-table-striped-bg: rgba(var(--bs-success-rgb), .05);
  --bs-table-hover-color: var(--bs-success-text-emphasis);
  --bs-table-hover-bg: rgba(var(--bs-success-rgb), .075);
  --bs-table-active-color: var(--bs-success-text-emphasis);
  --bs-table-active-bg: rgba(var(--bs-success-rgb), .1);
}
.table-info {
  --bs-table-color: var(--bs-info-text-emphasis);
  --bs-table-bg: var(--bs-info-bg-subtle);
  --bs-table-border-color: var(--bs-info-border-subtle);
  --bs-table-striped-color: var(--bs-info-text-emphasis);
  --bs-table-striped-bg: rgba(var(--bs-info-rgb), .05);
  --bs-table-hover-color: var(--bs-info-text-emphasis);
  --bs-table-hover-bg: rgba(var(--bs-info-rgb), .075);
  --bs-table-active-color: var(--bs-info-text-emphasis);
  --bs-table-active-bg: rgba(var(--bs-info-rgb), .1);
}
.table-warning {
  --bs-table-color: var(--bs-warning-text-emphasis);
  --bs-table-bg: var(--bs-warning-bg-subtle);
  --bs-table-border-color: var(--bs-warning-border-subtle);
  --bs-table-striped-color: var(--bs-warning-text-emphasis);
  --bs-table-striped-bg: rgba(var(--bs-warning-rgb), .05);
  --bs-table-hover-color: var(--bs-warning-text-emphasis);
  --bs-table-hover-bg: rgba(var(--bs-warning-rgb), .075);
  --bs-table-active-color: var(--bs-warning-text-emphasis);
  --bs-table-active-bg: rgba(var(--bs-warning-rgb), .1);
}
.table-danger {
  --bs-table-color: var(--bs-danger-text-emphasis);
  --bs-table-bg: var(--bs-danger-bg-subtle);
  --bs-table-border-color: var(--bs-danger-border-subtle);
  --bs-table-striped-color: var(--bs-danger-text-emphasis);
  --bs-table-striped-bg: rgba(var(--bs-danger-rgb), .05);
  --bs-table-hover-color: var(--bs-danger-text-emphasis);
  --bs-table-hover-bg: rgba(var(--bs-danger-rgb), .075);
  --bs-table-active-color: var(--bs-danger-text-emphasis);
  --bs-table-active-bg: rgba(var(--bs-danger-rgb), .1);
}
/* light / dark 两档不是「淡底变体」，而是整块浅色 / 深色表格，底色与文字走
   --bs-light / --bs-dark 这两个明暗共用的常量色 —— 文字不能跟着主题翻转，
   否则暗色下会变成白字压浅底。 */
.table-light {
  --bs-table-color: var(--bs-dark);
  --bs-table-bg: var(--bs-light);
  --bs-table-border-color: rgba(var(--bs-dark-rgb), .2);
  --bs-table-striped-color: var(--bs-dark);
  --bs-table-striped-bg: rgba(var(--bs-dark-rgb), .05);
  --bs-table-hover-color: var(--bs-dark);
  --bs-table-hover-bg: rgba(var(--bs-dark-rgb), .075);
  --bs-table-active-color: var(--bs-dark);
  --bs-table-active-bg: rgba(var(--bs-dark-rgb), .1);
}
.table-dark {
  --bs-table-color: var(--bs-light);
  --bs-table-bg: var(--bs-dark);
  --bs-table-border-color: rgba(var(--bs-light-rgb), .25);
  --bs-table-striped-color: var(--bs-light);
  --bs-table-striped-bg: rgba(var(--bs-light-rgb), .05);
  --bs-table-hover-color: var(--bs-light);
  --bs-table-hover-bg: rgba(var(--bs-light-rgb), .075);
  --bs-table-active-color: var(--bs-light);
  --bs-table-active-bg: rgba(var(--bs-light-rgb), .1);
}

/* 表格的语义变体 .table-primary：官方写死的是编译好的 hex 六档。
   底色/文字/边框接品牌三件套；条带 / hover / active 沿用 .table 基类自己的做法
   （inset 阴影叠一层半透明，档位 .05 / .075 / .1），只是把叠加色从中性黑换成品牌 rgb。 */
.table-primary {
  --bs-table-color: var(--bs-primary-text-emphasis);
  --bs-table-bg: var(--bs-primary-bg-subtle);
  --bs-table-border-color: var(--bs-primary-border-subtle);
  --bs-table-striped-color: var(--bs-primary-text-emphasis);
  --bs-table-striped-bg: rgba(var(--bs-primary-rgb), .05);
  --bs-table-hover-color: var(--bs-primary-text-emphasis);
  --bs-table-hover-bg: rgba(var(--bs-primary-rgb), .075);
  --bs-table-active-color: var(--bs-primary-text-emphasis);
  --bs-table-active-bg: rgba(var(--bs-primary-rgb), .1);
}