/* ==========================================================================
 * aigeo-ui.css — 全站统一控件库（批次39）
 * --------------------------------------------------------------------------
 * 定位：两端（平台端 PHP 模板 / 租户端 SPA）**唯一**的控件真源。
 *   tokens.css（令牌） → aigeo-ui.css（控件） → admin-glass.css / tenant-glass.css
 *   （只剩外壳与布局） → 页内 <style>（只剩本页独有）
 *
 * 值来源标注（务必区分，别把推导值当成"设计稿要求"）：
 *   [D] 设计稿 docs/design/aigeo-ui-preview.html 里有实测值，本文件逐字对齐，
 *       `_b36_parity_probe.js` 会继续量；
 *   [X] 设计稿**没有**这个控件（日期选择器 / 单选多选 / 弹窗模态 / 图标 / Toast / 下拉菜单），
 *       按设计稿的设计语言推导：玻璃档位 ≤2、圆角制度（控件 999 / 表与下拉 12 / 行卡 14 /
 *       弱玻璃 16 / 卡 20）、间距刻度 s1-s6、数字等宽、缓动 --ease、按压 scale(.97)、
 *       品牌辉光 --glow-brand、边界对比走 --line-strong（1.4.11 ≥3:1）。
 *   [AA] 与设计稿字面值不同、但被 WCAG 实测逼出来的档位，注释里给比值。
 *
 * 契约：本文件**不得**出现色字面值（一切走 tokens.css 变量）；不得定义 :root。
 * 批次57a 增补三条库规：
 *   ① 触控目标：可点控件命中区 ≥24×24（2.5.8），按钮默认档 ≥32px（--ui-h-md）——
 *      裸勾选/单选视觉 16px 不变、命中区 24×24（见 §4）；
 *   ② 字号下限：一切 <12px 的 font-size 一律写 var(--font-floor)（tokens.css 定义），
 *      唯二例外 .tbl th（11px，parity 锁定）与裸 code（admin-glass 层，同锁）；
 *   ③ 表单错误统一 .ui-err（§16），禁止裸 .err。
 * ========================================================================== */

/* ==========================================================================
 * 1 · 按钮  [D .btn]
 * --------------------------------------------------------------------------
 * 批次40 铺页：本节起是**唯一真源**，旧名 `.btn` / `.btn.ghost` / `.btn-mini` / `.mini`
 * 以别名挂在同一批规则上（admin-glass.css 与 app.html 内联里的对应块已删除）。
 * 合并前实测到的三件事，都在这节里处理：
 *   ① 两端 `.btn.ghost` 的**浅色**底不一致：平台 `--sheet-strong`(66% 白) vs
 *      租户 `color-mix(--card 70%)`。统一走 `--sheet-strong`（批次36 对齐设计稿的那一档）。
 *      暗色两端本来就都是 `--card 82%`，但**各自抄了一份逐字重复的覆盖规则**，现合并成一份。
 *   ② `<a class="btn">` 有 **38 处**，而旧层从未设 `text-decoration` → 一直是带下划线的胶囊。
 *      这里补 `text-decoration:none`，属**有意的视觉修复**，不是回归。
 *   ③ 租户有 4+ 处 `class="btn mini"` 但 `.mini` 在租户端**根本没定义** → 渲染成标准尺寸。
 *      现在 `.btn-mini` 进库，两端都拿得到。
 * 基色保留一处历史分歧不强行统一：旧名 `.btn` 的默认态就是"主按钮"（品牌实心 + 辉光），
 * 新名 `.ui-btn` 默认中性 —— 改哪一边都会动 66+ 处调用点，故让两套语义并存。
 * ========================================================================== */
.ui-btn, .btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  border: 1px solid transparent;
  font-family: var(--font); font-size: 12px; line-height: 1; white-space: nowrap;
  padding: 7px 14px; min-height: var(--ui-h-md); box-sizing: border-box;
  border-radius: var(--r-pill); cursor: pointer; text-decoration: none;
  transition: background-color .18s var(--ease), border-color .18s var(--ease),
              box-shadow .18s var(--ease), color .18s var(--ease), transform .12s var(--ease);
}
.ui-btn:active, .btn:active { transform: scale(.97); }
.ui-btn[disabled], .btn[disabled], .ui-btn.is-disabled, .btn.is-disabled,
.ui-btn[disabled]:hover, .btn[disabled]:hover { opacity: .45; cursor: not-allowed; transform: none; pointer-events: none; }
/* 默认色：新名中性 / 旧名沿用"默认即主按钮" */
.ui-btn { background-color: var(--chip-fill); color: var(--text); }
.ui-btn.pri, .btn { background-color: var(--brand); color: #fff; border-color: transparent; box-shadow: var(--glow-brand); }
.ui-btn.pri:hover, .btn:hover { background-color: var(--brand-lite); box-shadow: var(--glow-brand-strong); }
/* [D] ghost：浅色走 --sheet-strong（66% 白，设计稿 .admin.light 的页签/幽灵档）；
   [AA] 描边走 --line-strong —— 设计稿 .14 白描边暗底仅 1.2:1，破 1.4.11。 */
.ui-btn.ghost, .btn.ghost { background-color: var(--sheet-strong); color: var(--text); border-color: var(--line-strong); box-shadow: none; }
.ui-btn.ghost:hover, .btn.ghost:hover { border-color: var(--brand-ink); color: var(--brand-ink); }
/* 暗色 ghost 要"透而不糊"：原样搬自 admin-glass:354 与 app.html:257 两份逐字重复的覆盖，现合一。 */
html[data-theme="dark"] .ui-btn.ghost, html[data-theme="dark"] .btn.ghost {
  background-color: color-mix(in srgb, var(--card) 82%, transparent);
  border-color: var(--line-strong); color: var(--text);
}
/* [AA] 设计稿 danger 文字 #FF8095 在其 .16 红底上 3.3:1，12px 不达标 → 走 --danger-ink-glass。
   底色从设计稿 .16 一路降到 .08：.16 暗色实测 4.24、.12 浅色 4.32 仍不足 4.5，
   .08 是浅色 4.60 / 暗色 4.59 的最低达标档；红色可辨性交给 34% 描边承担。
   注意：旧名 `.btn.danger` 历史上是**实心**（--danger-fill），故别名挂在 .solid 上，
   且必须排在 ghost 之后（租户有 1 处 `btn ghost danger`，靠源码序决胜）。 */
.ui-btn.danger { background-color: color-mix(in srgb, var(--danger) 8%, transparent); color: var(--danger-ink-glass); border-color: color-mix(in srgb, var(--danger) 34%, transparent); }
.ui-btn.danger:hover { background-color: color-mix(in srgb, var(--danger) 16%, transparent); }
.ui-btn.danger.solid, .btn.danger { background-color: var(--danger-fill); color: #fff; border-color: transparent; box-shadow: none; }
.ui-btn.danger.solid:hover, .btn.danger:hover { background-color: color-mix(in srgb, var(--danger-fill) 84%, #fff); }
.ui-btn.link { background: none; border-color: transparent; padding: 7px 4px; color: var(--link-ink); min-height: 0; }
.ui-btn.link:hover { text-decoration: underline; color: var(--link-ink); background: none; }
/* 小档：`.btn-mini` 原样是 4px 11px 无最小高（实测 24px，正好卡在 2.5.8 的 24px 线上）。
   统一给 min-height 26px —— 这是**有意的 2px 变高**，把命中区从临界抬到达标。 */
.ui-btn.sm, .btn-mini { padding: 4px 11px; font-size: 12px; min-height: var(--ui-h-sm); box-shadow: none; }
.ui-btn.lg { font-size: 13.5px; padding: 11px 20px; min-height: var(--ui-h-lg); }
.ui-btn.block, .btn.block { display: flex; width: 100%; }
/* 链接式胶囊（原 admin-glass `.mini`，别名保留原名） */
.ui-btn-outline, .mini { color: var(--link-ink); text-decoration: none; font-size: 12px;
  border: 1px solid var(--link-ink); border-radius: var(--r-pill); padding: 4px 9px; white-space: nowrap;
  display: inline-block; transition: background-color .18s var(--ease), color .18s var(--ease); }
.ui-btn-outline:hover, .mini:hover { background-color: var(--brand); color: #fff; }
/* 加载态**保留文字**，只在后面追加一个 spinner 伪元素：
   早期写法用 color:transparent 藏字，既让像素探针读到 alpha 0 的"隐形文字"报违例，
   也让按钮在点击瞬间宽度跳变。 */
.ui-btn.loading { pointer-events: none; }
.ui-btn.loading::after {
  content: ""; flex: 0 0 auto; width: 12px; height: 12px; border-radius: 50%;
  border: 2px solid currentColor; border-top-color: transparent; animation: ui-spin .7s linear infinite;
}
.ui-btngrp { display: inline-flex; gap: 6px; align-items: center; flex-wrap: wrap; }

/* ==========================================================================
 * 2 · 输入框族  [D .inp]
 * --------------------------------------------------------------------------
 * 批次40 铺页：本节起为唯一真源。这一族和前两族不同 —— 现有页面用的是
 * **裸元素选择器**（`input[type=text]`、`textarea`、`select`）而不是 class，
 * 所以别名必须挂在裸元素上，才能不动 25 个视图 + 13 个 PHP 文件里的表单。
 *
 * 两条必须分开的原因（合在一起会炸布局）：
 *   · `width:100%` / `min-height` 只给新名 `.ui-*`。裸元素给了会把 `.fbar`、`.ops`
 *     里的输入框全部拉满，把 `.w-lg` 这类定宽控件挤没。
 *   · `font-family:var(--mono)`、`padding-left:34px`（配图标）同理只给新名 ——
 *     它们是我加的能力，不是线上既有表现，挂到裸元素上等于全站改外观。
 * ========================================================================== */
.ui-input, .ui-textarea,
input[type=text], input[type=number], input[type=password],
input[type=search], input[type=date], input[type=datetime-local],
input:not([type]),
textarea {
  box-sizing: border-box;
  background-color: var(--inp-fill); color: var(--text);
  font-family: var(--font); font-size: 12.5px;
  border: 1px solid var(--line-strong); /* [AA] 1.4.11 非文本边界 ≥3:1，设计稿 .14 白不达标 */
  border-radius: var(--r-pill); padding: 8px 14px;
  transition: border-color .18s var(--ease), box-shadow .18s var(--ease), background-color .18s var(--ease);
}
/* 只给新名的布局便利 */
.ui-input, .ui-textarea { width: 100%; min-height: var(--ui-h-md); }
.ui-input::placeholder, .ui-textarea::placeholder,
input[type=text]::placeholder, input[type=number]::placeholder, input[type=password]::placeholder,
input:not([type])::placeholder,
input[type=search]::placeholder, textarea::placeholder { color: var(--muted); opacity: 1; }
.ui-input:hover, .ui-textarea:hover,
input[type=text]:hover, input[type=number]:hover, input[type=password]:hover, input[type=search]:hover, textarea:hover { border-color: var(--brand-ink); }
.ui-input:focus, .ui-textarea:focus,
input[type=text]:focus, input[type=number]:focus, input[type=password]:focus, input[type=search]:focus, textarea:focus {
  border-color: var(--brand-ink); box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 16%, transparent);
}
.ui-input[disabled], .ui-textarea[disabled],
input[type=text][disabled], input[type=number][disabled], input[type=password][disabled],
input[type=search][disabled], textarea[disabled] { opacity: .5; cursor: not-allowed; }
.ui-input[readonly], .ui-textarea[readonly] { background-color: transparent; border-style: dashed; }
.ui-input.bad, .ui-textarea.bad, .ui-is-bad { border-color: var(--danger); }
/* 文本域一律方角（设计稿 select.inp / 我们 textarea 同走 --r-table） */
.ui-textarea, textarea { border-radius: var(--r-table); }
.ui-textarea { min-height: 76px; padding: 10px 12px; resize: vertical; line-height: 1.6; }
.ui-input[type="number"] { font-family: var(--mono); }
/* 带图标前缀的输入（搜索、日期、密码） */
.ui-inwrap { position: relative; display: block; min-width: 0; }
.ui-inwrap > .ui-ico { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); pointer-events: none; color: var(--muted); }
.ui-inwrap > .ui-input { padding-left: 34px; }
/* 后缀按钮要能点得着：图标只有 14px，命中区靠 padding 撑到 ≥28px（2.5.5 目标尺寸） */
.ui-inwrap > .ui-suffix { position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  display: flex; gap: 2px; align-items: center; justify-content: center; min-width: 28px; min-height: 28px; }
/* 单位/前后缀文本 */
.ui-adorn { display: flex; align-items: stretch; gap: 0; min-width: 0; }
/* 前后缀是**内容的一部分**不是次要说明，所以走 --text：--muted 压在 --chip-fill 上暗色实测只有 4.20 */
.ui-adorn > span { display: inline-flex; align-items: center; padding: 0 12px; font-size: 12px; color: var(--text);
  background: var(--chip-fill); border: 1px solid var(--line-strong); white-space: nowrap; flex: 0 0 auto; }
.ui-adorn > span:first-child { border-right: 0; border-radius: var(--r-pill) 0 0 var(--r-pill); }
.ui-adorn > span:last-child { border-left: 0; border-radius: 0 var(--r-pill) var(--r-pill) 0; }
.ui-adorn > .ui-input { border-radius: 0; flex: 1 1 auto; min-width: 0; }
.ui-adorn > span:first-child + .ui-input { border-radius: var(--r-pill) 0 0 var(--r-pill); }
/* 通用紧凑档：任何控件（input / select / textarea）加 .ui-sm 即缩到顶栏尺度。
   存在的理由：租户端顶栏语言/时区两个 select 原先靠**内联 style** 压小，
   平台端靠 `.ops select` 压小 —— 同一个需求两套实现。收成一个类名。
   特异性写法：基座是 `input[type=text]`（0-1-1），故这里用 `元素.类`（0-2-1）压过它，
   **不用 !important**（项目契约：靠特异性与令牌级联解决，不用 !important）。 */
input.ui-sm, select.ui-sm, textarea.ui-sm { padding: 5px 8px; font-size: 12px; min-height: var(--ui-h-sm); }
/* 表格即时过滤框（31 个视图在用） */
.tfilter { width: 220px; max-width: 50vw; }

/* ==========================================================================
 * 3 · 下拉选择  [D select.inp：radius 12]
 * 默认**不接管原生**：原生 select 免费拿到键盘、移动端滚轮、表单语义与 AA；
 * 需要搜索/多选/自定义面板时才加 data-ui="select"（见 aigeo-ui.js）。
 * ========================================================================== */
.ui-select, select {
  box-sizing: border-box; -webkit-appearance: none; appearance: none;
  background-color: var(--inp-fill); color: var(--text);
  font-family: var(--font); font-size: 12.5px;
  border: 1px solid var(--line-strong); border-radius: var(--r-table);
  padding: 8px 30px 8px 12px; cursor: pointer;
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%238A90A6' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 11px center;
  transition: border-color .18s var(--ease), box-shadow .18s var(--ease);
}
.ui-select { width: 100%; min-height: var(--ui-h-md); }
.ui-select:hover, select:hover { border-color: var(--brand-ink); }
.ui-select:focus, select:focus { border-color: var(--brand-ink); box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 16%, transparent); }
/* 多选/带 size 的不设箭头（自绘 chevron 会叠在滚动条上） */
.ui-select[multiple], select[multiple], select[size]:not([size="1"]) { background-image: none; padding-right: 12px; cursor: default; }
.ui-select[multiple] { height: auto; min-height: 84px; }
.ui-select option, select option { background-color: var(--pop-fill); color: var(--text); }
/* [X] 增强型 select 的替身面板（原生控件保留在 DOM 里当数据源与焦点宿主） */
/* 批次59：.ui-caption 表标题语义保留、视觉隐藏——面板页头已有语境标题，避免重复观感（读屏仍可访问） */
.ui-caption { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.ui-picker { position: relative; min-width: 0; }
.ui-picker .ui-pk-btn {
  display: flex; align-items: center; gap: var(--s2,8px); width: 100%; box-sizing: border-box;
  background-color: var(--inp-fill); color: var(--text); font: inherit; font-size: 12.5px;
  border: 1px solid var(--line-strong); border-radius: var(--r-table);
  padding: 8px 12px; min-height: var(--ui-h-md); cursor: pointer; text-align: left;
}
.ui-picker .ui-pk-btn > .txt { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-picker .ui-pk-btn > .txt.is-ph { color: var(--muted); }
/* 批次59：全量接管后适配紧凑档（顶栏 .ui-sm 场景）与裸 select 原宽（无宽度指定时取原 select 宽） */
.ui-picker:has(> select.ui-sm) .ui-pk-btn { padding: 5px 8px; min-height: var(--ui-h-sm); font-size: 12px; border-radius: var(--r-pill, 999px); }
.ui-picker .ui-pk-btn > .ui-ico:last-child { color: var(--muted); flex: 0 0 auto; transition: transform .18s var(--ease); }
.ui-picker.open .ui-pk-btn > .ui-ico:last-child { transform: rotate(180deg); }
.ui-picker > select { position: absolute; inset: 0; width: 100%; opacity: 0; pointer-events: none; margin: 0; border: 0; }

/* ==========================================================================
 * 4 · 勾选族（checkbox / radio / switch）  [X] 设计稿无此控件
 * 关键：视觉盒 15px，但**命中区靠 label 撑到 ≥32px**，不牺牲 touchSmall。
 * 批次40 铺页：裸 `input[type=checkbox|radio|file]` 的尺寸规则原先**只有租户端有**
 * （app.html 内联），平台端一直是原生裸控件（约 13px，也是 touchSmall 欠账的来源之一）。
 * 这里合并成一条让两端同形 —— 平台端勾选框 13px → 15px 属**有意的统一**，不是回归。
 * ========================================================================== */
/* 批次50：裸勾选/单选自绘盒（appearance:none）——原生外观与玻璃主题割裂（用户实测反馈）。
   依赖令牌双主题翻转；选中态品牌底 + 白勾（SVG data URI，无字体依赖）；indeterminate 半选档。
   批次57a 触控收敛（2.5.8）：裸勾选框是运行时探针 touchSmall 的最大欠账（roles 权限网格
   619 处 + 各表格行选 191 处，全是 16×16）。视觉盒保持 16px 不变，命中区扩到 24×24：
   · 元素本体 24×24 + padding 4px，background-clip:content-box 把填充/勾画裁剪回 16px 内容盒；
   · input 无伪元素可用、border 会画满 24px，故 1px 环线改走内缩 5px 的 outline
     （outline 随 border-radius 走，offset -5px 恰好套在 16px 内容盒外缘）；
   · 圆角外扩 4px（12+4=16），裁剪后内容盒圆角 = 原 16px 盒上的 12px，视觉等值；
   · margin -4px 抵消 24px 盒对行高/弹性格的撑高（line box / flex 外尺寸仍按 16px 算），
     各页行高、perm 网格、顶栏胶囊零位移。 */
input[type=checkbox], input[type=radio] {
  appearance: none; -webkit-appearance: none;
  padding: 4px; width: 24px; height: 24px; flex: 0 0 auto; margin: -4px;
  box-sizing: border-box; display: inline-block; vertical-align: middle;
  background-color: var(--inp-fill); background-clip: content-box; border: 0;
  outline: 1px solid var(--line-strong); outline-offset: -5px; /* [AA] ≥3:1 环线，视觉位同原 1px border */
  border-radius: calc(var(--r-table) + 4px); cursor: pointer;
  transition: background-color .15s var(--ease), outline-color .15s var(--ease), box-shadow .15s var(--ease);
}
input[type=radio] { border-radius: 50%; }
/* 焦点环必须显式夺回：基座规则的 outline（0-1-1）会压过 tokens 的裸 :focus-visible（0-1-0） */
input[type=checkbox]:focus-visible, input[type=radio]:focus-visible { outline: 2px solid var(--brand-lite); outline-offset: 2px; }
input[type=checkbox]:hover, input[type=radio]:hover { outline-color: var(--brand-ink); }
input[type=checkbox]:checked, input[type=radio]:checked {
  background-color: var(--brand); outline-color: var(--brand);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.5l3 3 6-6.5' fill='none' stroke='white' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-size: 12px; background-position: center; background-repeat: no-repeat;
}
input[type=radio]:checked { background-image: radial-gradient(circle, #fff 0 3.5px, transparent 4px); }
input[type=checkbox]:indeterminate {
  background-color: var(--brand); outline-color: var(--brand);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 8h8' fill='none' stroke='white' stroke-width='2.2' stroke-linecap='round'/%3E%3C/svg%3E");
  background-size: 12px; background-position: center; background-repeat: no-repeat;
}
input[type=checkbox]:disabled, input[type=radio]:disabled { opacity: .45; cursor: not-allowed; }
input[type=file] { border-radius: var(--r-table); padding: 6px 10px; margin: 0; }
.ui-check, .ui-radio {
  display: inline-flex; align-items: center; gap: var(--s2,8px); min-height: var(--ui-h-md);
  font-size: 12.5px; color: var(--text); cursor: pointer; user-select: none; padding-right: 4px;
}
/* 库内勾选/开关的原生 input 是隐藏宿主：必须归零 border/padding —— 裸元素基座批次57a 起
   带 padding 4px，box-sizing:border-box 下 0 宽 + 4px 内边会算出 2~8px 的"幽灵盒"，
   既被触控探针计 <24px 违例，又可能在 label 外多出可点区。 */
.ui-check > input, .ui-radio > input, .ui-switch > input { position: absolute; opacity: 0; width: 0; height: 0; margin: 0; border: 0; padding: 0; }
.ui-check > .box, .ui-radio > .box {
  flex: 0 0 auto; width: 16px; height: 16px; box-sizing: border-box;
  background: var(--inp-fill); border: 1px solid var(--line-strong); /* [AA] ≥3:1 */
  display: inline-flex; align-items: center; justify-content: center;
  transition: background-color .16s var(--ease), border-color .16s var(--ease), box-shadow .16s var(--ease);
}
.ui-check > .box { border-radius: 5px; }
.ui-radio > .box { border-radius: 50%; }
.ui-check > .box::after {
  content: ""; width: 9px; height: 5px; border: 2px solid #fff; border-top: 0; border-right: 0;
  transform: rotate(-45deg) scale(.4); opacity: 0; transition: all .16s var(--ease); margin-top: -1px;
}
.ui-radio > .box::after {
  content: ""; width: 6px; height: 6px; border-radius: 50%; background: #fff;
  transform: scale(.3); opacity: 0; transition: all .16s var(--ease);
}
.ui-check > input:checked + .box { background: var(--brand); border-color: var(--brand); box-shadow: var(--glow-lite); }
.ui-radio > input:checked + .box { background: var(--brand); border-color: var(--brand); box-shadow: var(--glow-lite); }
.ui-check > input:checked + .box::after { transform: rotate(-45deg) scale(1); opacity: 1; }
.ui-radio > input:checked + .box::after { transform: scale(1); opacity: 1; }
.ui-check > input:indeterminate + .box { background: var(--brand); border-color: var(--brand); }
.ui-check > input:indeterminate + .box::after { content: ""; width: 8px; height: 0; border-top: 2px solid #fff; transform: none; opacity: 1; margin: 0; }
.ui-check > input:focus-visible + .box, .ui-radio > input:focus-visible + .box { outline: 2px solid var(--brand-lite); outline-offset: 2px; }
.ui-check > input[disabled] + .box, .ui-radio > input[disabled] + .box { opacity: .45; }
.ui-check > input[disabled] ~ *, .ui-radio > input[disabled] ~ * { cursor: not-allowed; }
/* 组排布 */
.ui-checkgroup, .ui-radiogroup { display: flex; flex-wrap: wrap; gap: 2px 16px; align-items: center; }
.ui-radiogroup.col, .ui-checkgroup.col { flex-direction: column; align-items: flex-start; gap: 2px; }
/* 开关 */
.ui-switch { display: inline-flex; align-items: center; gap: var(--s2,8px); min-height: var(--ui-h-md); font-size: 12.5px; cursor: pointer; }
/* 隐藏宿主归零规则与 .ui-check > input 同一条（见 §4 批次57a 注），此处不再重复声明 */
.ui-switch > .track {
  flex: 0 0 auto; width: 38px; height: 22px; border-radius: var(--r-pill); position: relative;
  background: var(--track-fill); border: 1px solid var(--line-strong); box-sizing: border-box;
  transition: background-color .18s var(--ease), border-color .18s var(--ease);
}
.ui-switch > .track::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%;
  background: var(--text); transition: transform .18s var(--ease), background-color .18s var(--ease);
}
.ui-switch > input:checked + .track { background: var(--brand); border-color: var(--brand); box-shadow: var(--glow-brand); }
.ui-switch > input:checked + .track::after { transform: translateX(16px); background: #fff; }
.ui-switch > input:focus-visible + .track { outline: 2px solid var(--brand-lite); outline-offset: 2px; }
.ui-switch > input[disabled] + .track { opacity: .45; }

/* ==========================================================================
 * 5 · 分段控制器（替代"一排小按钮做筛选"）  [X]，但复用 .btn 的胶囊语言
 * ========================================================================== */
.ui-seg { display: inline-flex; gap: 4px; padding: 4px; border-radius: var(--r-pill);
  background: var(--sheet-strong); border: 1px solid var(--spec-edge); }
.ui-seg > button { border: 0; background: none; color: var(--muted); font: inherit; font-size: 12px;
  padding: 5px 12px; border-radius: var(--r-pill); cursor: pointer; min-height: 28px; transition: all .18s var(--ease); }
.ui-seg > button:hover { color: var(--text); background: var(--rail-hover); }
.ui-seg > button.on { background: var(--brand); color: #fff; box-shadow: var(--glow-brand-strong); }

/* ==========================================================================
 * 6 · 日期 / 时间选择器  [X] 设计稿完全没有
 * ========================================================================== */
.ui-dp { /* 面板 */
  position: absolute; z-index: var(--z-ui-drop); min-width: 268px; padding: 10px;
  background: var(--pop-fill); border: 1px solid var(--pop-line); border-radius: var(--r-soft);
  box-shadow: var(--pop-depth); color: var(--text); font-size: 12.5px;
}
.ui-dp-head { display: flex; align-items: center; gap: 4px; margin-bottom: 6px; }
.ui-dp-head .ttl { flex: 1 1 auto; text-align: center; font-weight: 600; font-size: 13px; font-family: var(--mono); }
.ui-dp-nav { border: 1px solid var(--pop-line); background: var(--chip-fill); color: var(--text);
  width: 26px; height: 26px; border-radius: var(--r-pill); display: inline-flex; align-items: center; justify-content: center; cursor: pointer; padding: 0; }
.ui-dp-nav:hover { border-color: var(--brand-ink); color: var(--brand-ink); }
.ui-dp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.ui-dp-w { font-size: var(--font-floor); color: var(--muted); text-align: center; padding: 4px 0; }
.ui-dp-d { position: relative; border: 0; background: none; color: var(--text); font: inherit; font-family: var(--mono);
  height: 30px; border-radius: var(--r-pill); cursor: pointer; transition: background-color .14s var(--ease), color .14s var(--ease); }
.ui-dp-d:hover { background: var(--rail-hover); }
.ui-dp-d.out { color: var(--muted); opacity: .55; }
.ui-dp-d.today { box-shadow: inset 0 0 0 1px var(--brand-ink); }
.ui-dp-d.on { background: var(--brand); color: #fff; box-shadow: var(--glow-brand); }
.ui-dp-d.in { background: color-mix(in srgb, var(--brand) 18%, transparent); border-radius: 0; }
.ui-dp-d[disabled] { opacity: .3; cursor: not-allowed; }
.ui-dp-foot { display: flex; align-items: center; gap: var(--s2,8px); margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--pop-line); }
.ui-dp-foot .sp { flex: 1 1 auto; }
.ui-dp-times { display: flex; align-items: center; gap: 6px; font-size: 12px; }
.ui-dp-times input { width: 52px; text-align: center; font-family: var(--mono); font-size: 12px;
  background: var(--inp-fill); color: var(--text); border: 1px solid var(--line-strong); border-radius: var(--r-pill); padding: 3px 6px; }
/* 快捷档（今天/近7天/近30天） */
.ui-dp-presets { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 8px; }
.ui-dp-presets button { border: 1px solid var(--pop-line); background: var(--chip-fill); color: var(--muted);
  font: inherit; font-size: var(--font-floor); padding: 3px 9px; border-radius: var(--r-pill); cursor: pointer; }
.ui-dp-presets button:hover, .ui-dp-presets button.on { color: var(--brand-ink); border-color: var(--brand-ink); }

/* ==========================================================================
 * 7 · 表格  [D .tbl 全套实测值]
 * --------------------------------------------------------------------------
 * 批次40 打样：本节起为**唯一真源**，旧名 `.tbl`/`.tbl-scroll` 以别名挂在同一批规则上
 * （admin-glass.css 里的对应块已删除）。值以**线上已验证版**为准，不是设计稿字面值 ——
 * 差异全是批次35/36/37 踩出来的补丁：th/td nowrap、斑马纹与 hover 的实际混色、
 * `.num`/`.cell-ops` 的特异性补偿。这样"迁移"才应当是视觉零变化，可被探针证伪。
 * 注意：`.cell-ops` 的 display 由 admin-glass 针对 `<td>` 显式还原（td 上跑 flex 会丢表格对齐），
 * 本文件**不得**在表格语境里把它设回 flex。
 * ========================================================================== */
.ui-table, .tbl {
  width: 100%; border-collapse: separate; border-spacing: 0; font-size: 12.5px;
  background-color: var(--tbl-fill); color: var(--text);
  border-radius: var(--r-table); overflow: hidden;
}
.ui-table th, .ui-table td, .tbl th, .tbl td {
  text-align: left; padding: 11px var(--s3, 12px);
  border-bottom: 1px solid var(--line); white-space: nowrap;
}
.ui-table th, .tbl th { padding: 10px var(--s3, 12px); } /* 设计稿 th 10px 12px、td 11px 12px（表头比行矮一档） */
/* 表头 11px 是**有据不收敛**的唯一例外：设计稿 .tbl th 即 11px，parity「表头」角色现为 MATCH，
   抬到 --font-floor 会新增 DIFF（批次57a 实测冲突，parity 基线未解锁前保持 11px）。 */
.ui-table th, .tbl th {
  font-weight: 500; font-size: 11px; letter-spacing: .5px; color: var(--muted);
  background-color: color-mix(in srgb, var(--brand) 6%, var(--card));
}
.ui-table tbody tr:nth-child(even), .tbl tbody tr:nth-child(2n) {
  background-color: color-mix(in srgb, var(--text) 2.2%, transparent);
}
.ui-table tbody tr:hover, .tbl tbody tr:hover {
  background-color: color-mix(in srgb, var(--brand-lite) 9%, transparent);
}
.ui-table tbody tr:last-child td, .tbl tbody tr:last-child td { border-bottom: 0; }
/* [D] separate 模式下 overflow 不裁 display:table，圆角必须由首末单元格自收（批次37 实测） */
.ui-table tr:first-child th:first-child, .tbl tr:first-child th:first-child { border-top-left-radius: var(--r-table); }
.ui-table tr:first-child th:last-child,  .tbl tr:first-child th:last-child { border-top-right-radius: var(--r-table); }
.ui-table tr:last-child td:first-child,  .tbl tr:last-child td:first-child { border-bottom-left-radius: var(--r-table); }
.ui-table tr:last-child td:last-child,   .tbl tr:last-child td:last-child { border-bottom-right-radius: var(--r-table); }
/* 特异性补丁：`.tbl th,.tbl td`（0-1-1）会压过单类名的 .num / .cell-ops（0-1-0），
   导致挂在表格单元格上的右对齐静默失效。 */
.ui-table .num, .ui-table .cell-ops, .tbl .num, .tbl .cell-ops { text-align: right; }
.ui-table .mono, .tbl .mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.ui-table th.sortable, .tbl th.sortable { cursor: pointer; user-select: none; }
.ui-table th.sortable:hover, .tbl th.sortable:hover { color: var(--link-ink); }
.ui-table th.sortable .ui-ico, .tbl th.sortable .ui-ico { opacity: .4; vertical-align: -2px; }
.ui-table th.sortable.on .ui-ico, .tbl th.sortable.on .ui-ico { opacity: 1; color: var(--link-ink); }
.ui-table.compact th, .tbl.compact th, .ui-table.compact td, .tbl.compact td { padding: 7px 10px; }
/* 横滚容器：窄栏/宽表必用（批次32 起就是这条契约） */
.ui-scroll, .tbl-scroll { overflow-x: auto; min-width: 0; -webkit-overflow-scrolling: touch; }
.ui-scroll > table { min-width: 480px; }

/* ==========================================================================
 * 8 · 分页  [D .pager / .pager .page / .cur]
 * --------------------------------------------------------------------------
 * 批次40 铺页：这一族和前几族不一样 —— 两端**不是同值不同名，是不同名不同结构**：
 *   平台端 `.pager-wrap > .pager > .page` + `.page.cur` + `.page.dim` + `.pcount`（`<a>` 链接式）
 *   租户端 `.pager > .pg-btn` + `.pg-btn.active` + `:disabled` + `.pg-info`（`<button>` 式），
 *         且容器多一条 `justify-content:center`
 * 一律取**平台值**（它与设计稿 `.pager .page{min-width:30px;height:30px;font-size:12.5px}` 同形，
 * 且被 `_b36_parity_probe` 的"分页项"角色量过：radius 999 / height 30 / font 12.5）。
 *
 * ⚠ 一条必须守住的红线：**绝不允许出现裸 `.page` 别名**。
 *    租户端 `.page` 是 SPA 视图容器（`.page{display:none}` / `.page.on{display:flex}`），
 *    裸 `.page` 一旦被赋上 30px 高度与胶囊描边，整个租户后台直接崩。
 *    所有别名一律以 `.pager ` 作祖先限定，靠特异性把作用域钉死。
 *
 * 两处**有意的视觉变化**（不是回归，都写了理由）：
 *   ① 租户分页按钮 5px 12px / 13px → 30px 高 / 12.5px（向设计稿与平台对齐，略变小）；
 *   ② 租户 `.pager` 去掉 `justify-content:center` → 左对齐（设计稿与平台都是左对齐；
 *      分页条在表格下方左对齐，与"共 N 条"计数同侧，读起来是一条线）。
 * ========================================================================== */
.ui-pager, .pager { display: flex; align-items: center; gap: 6px; margin-top: 14px; font-size: 12px; color: var(--muted); flex-wrap: wrap; }
/* 顶间距从平台端 .pager-wrap 的 padding-top:14px 改由 .pager 的 margin-top 承担，
   取 14px 而不是令牌档（--s3=12 / --s4=16 都不等于 14）：14px 既是设计稿 .pager 的原值，
   也让平台端零变化、租户端只 +2px（原 12px）。用 --s4 会静默变成平台 +2 / 租户 +4。 */
.ui-pager-wrap, .pager-wrap { padding: 0 2px 2px; }
.ui-pager .page, .pager .page, .pager .pg-btn {
  min-width: 30px; height: 30px; padding: 0 9px; box-sizing: border-box;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-pill); border: 1px solid var(--line-strong); color: var(--text);
  text-decoration: none; font-size: 12.5px; cursor: pointer;
  background-color: color-mix(in srgb, var(--card) 70%, transparent);
  /* 注意：**不加** font-family:var(--mono)。两端线上实现都不是等宽，"数字一律等宽"的约定
     只管金额/日期/计数列，分页码不在其列 —— 这一族的目标是两端统一，不夹带第三种改动。 */
  transition: border-color .15s var(--ease), color .15s var(--ease), background-color .15s var(--ease);
}
.ui-pager .page:hover, .pager .page:hover, .pager .pg-btn:hover:not(:disabled) { border-color: var(--brand-ink); color: var(--brand-ink); }
.ui-pager .page.cur, .pager .page.cur, .pager .pg-btn.active {
  background-color: var(--brand); border-color: var(--brand); color: #fff;
  font-weight: 600; box-shadow: var(--glow-brand);
}
/* 失效/占位档：平台用 .page.dim 类，租户用 :disabled 伪类，同一视觉。
   color 不可省（批次42 补回）：40f 迁移时弄丢了壳层的 color:var(--muted)，
   静态审计 ADMIN-PAGERDIM 自此静默 SKIP（前景无法解析），禁用页也退成了正文色。 */
.ui-pager .page[disabled], .pager .page.dim, .pager .pg-btn:disabled { color: var(--muted); opacity: .45; cursor: not-allowed; pointer-events: none; box-shadow: none; }
/* 计数文字：平台在左（.pcount + margin-right），租户在右（.pg-info + margin-left），只统一排版不统一摆位 */
.ui-pager .total, .pager .pcount, .pager .pg-info { font-size: 12px; color: var(--muted); white-space: nowrap; }
.pager .pcount { margin-right: 10px; }
.pager .pg-info { margin-left: 8px; }
.ui-pager .gap { color: var(--muted); padding: 0 2px; }
.ui-pager .sp { flex: 1 1 auto; }
.ui-pager .total { font-family: var(--mono); }

/* ==========================================================================
 * 9 · 标签 / 徽标 / 状态点  [D .tag / .tg / .dot-on]
 * --------------------------------------------------------------------------
 * 批次40 铺页：旧名 `.pill` / `.pill-green|amber|red|neutral` 与租户独有的
 * `.kpi-badge`（KPI 卡涨跌徽标）以别名挂在同一批规则上。
 * 合并前实测到的两端分歧（`.pill` 在 16 个视图 + 9 个 PHP + 租户 5 处在用）：
 *   padding 平台 `2px 8px` / 租户 `3px 10px`
 *   字号    平台 `11px`      / 租户 `11.5px`
 *   底色    平台 `--chip-fill` / 租户 `color-mix(--muted 10%)`
 * 一律取**平台值**（它是批次35 对齐设计稿、且被静态审计 ADMIN-PILL* 双主题验过的那一份）。
 * 注意：`.nav-pill`（玻璃工具类）与 `.b30-chip`（筛选芯片组件）**不属于这一族**，别顺手改。
 * ========================================================================== */
.ui-tag, .pill { display: inline-block; font-size: var(--font-floor); /* [AA] 设计稿 10px/原 11px 触发自设字号下限，批次57a 收敛到 --font-floor
  （徽标角色在 parity 里本就是已记录 DIFF，抬字号新增计数为零） */
  padding: 2px 8px; border-radius: var(--r-pill); color: var(--text); white-space: nowrap;
  background-color: var(--chip-fill); border: 1px solid color-mix(in srgb, var(--muted) 26%, transparent); }
/* 新名要图标+文字并排，单独给 flex（放在基座之后，靠源码序压过 inline-block） */
.ui-tag { display: inline-flex; align-items: center; gap: 4px; }
.ui-tag.green, .pill-green { background-color: color-mix(in srgb, var(--ok) 14%, transparent);
  color: color-mix(in srgb, var(--ok) 40%, var(--text)); border: 1px solid color-mix(in srgb, var(--ok) 40%, transparent); }
.ui-tag.amber, .pill-amber { background-color: color-mix(in srgb, var(--warn) 16%, transparent);
  color: color-mix(in srgb, var(--warn) 42%, var(--text)); border: 1px solid color-mix(in srgb, var(--warn) 42%, transparent); }
.ui-tag.red, .pill-red { background-color: color-mix(in srgb, var(--danger) 12%, transparent);
  color: color-mix(in srgb, var(--danger) 40%, var(--text)); border: 1px solid color-mix(in srgb, var(--danger) 38%, transparent); }
/* 中性弱化档（原只有租户端有） */
.ui-tag.muted, .pill-neutral { color: var(--muted); }
/* 暗色成套：淡底上放"同色 40% 混正文"会糊掉，故暗色改用语义纯色（批次35 的有意设计，
   原先住在 admin-glass.css:327-329，随族一起上提）。
   [AA] 危险档走 --danger-ink-glass（暗 #FF7A90）而非 --danger-ink（#FF6B85）——
   后者压在 12% 红淡底上实测 4.44，前者 4.86。 */
html[data-theme="dark"] .ui-tag.green, html[data-theme="dark"] .pill-green { color: var(--ok); }
html[data-theme="dark"] .ui-tag.amber, html[data-theme="dark"] .pill-amber { color: var(--warn); }
html[data-theme="dark"] .ui-tag.red, html[data-theme="dark"] .pill-red { color: var(--danger-ink-glass); }
.ui-tag.blue { color: var(--brand-ink); border-color: color-mix(in srgb, var(--brand) 40%, transparent); background-color: color-mix(in srgb, var(--brand) 12%, transparent); }
.ui-tag.solid { background-color: var(--brand); border-color: var(--brand); color: #fff; }
/* 可删除标签（多选输入里的 chip） */
.ui-tag.rm { padding-right: 4px; }
.ui-tag.rm > b { border: 0; background: none; color: inherit; font-size: 13px; line-height: 1; cursor: pointer; padding: 0 3px; opacity: .7; }
.ui-tag.rm > b:hover { opacity: 1; }
/* KPI 卡上的涨跌/持平徽标（原只有租户端有，平台端获得该能力） */
.ui-kpi-badge, .kpi-badge { display: inline-flex; align-items: center; gap: 3px;
  padding: 2px 9px; border-radius: var(--r-pill); font-size: var(--font-floor); font-weight: 600; margin-top: var(--s2); }
.ui-kpi-badge.up, .kpi-badge.up { background-color: color-mix(in srgb, var(--ok) 14%, var(--card));
  color: color-mix(in srgb, var(--ok) 40%, var(--text)); border: 1px solid color-mix(in srgb, var(--ok) 40%, transparent); }
.ui-kpi-badge.down, .kpi-badge.down { background-color: color-mix(in srgb, var(--danger) 12%, var(--card));
  color: var(--danger-ink-glass); border: 1px solid color-mix(in srgb, var(--danger) 38%, transparent); }
.ui-kpi-badge.flat, .kpi-badge.flat { background-color: color-mix(in srgb, var(--muted) 12%, var(--card));
  color: var(--muted); border: 1px solid color-mix(in srgb, var(--muted) 26%, transparent); }
/* 徽标（数字气泡） */
.ui-badge { display: inline-flex; align-items: center; justify-content: center; min-width: 17px; height: 17px;
  padding: 0 5px; border-radius: var(--r-pill); background: var(--danger-fill); color: #fff; font-size: var(--font-floor); font-family: var(--mono); }
.ui-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--muted); flex: 0 0 auto; display: inline-block; }
.ui-dot.on { background: var(--ok); box-shadow: 0 0 8px var(--ok); }
.ui-dot.warn { background: var(--warn); box-shadow: 0 0 8px var(--warn); }
.ui-dot.off { background: var(--danger); box-shadow: 0 0 8px var(--danger); }

/* ==========================================================================
 * 10 · 页签  [D .tabs 实测：radius 999 / padding 5px / 底 --sheet-strong / blur 14]
 * ========================================================================== */
.ui-tabs { display: flex; gap: 4px; padding: 5px; border-radius: var(--r-pill);
  background: var(--sheet-strong); border: 1px solid var(--spec-edge); flex-wrap: wrap; }
.ui-tabs > button { border: 0; background: none; color: var(--muted); font: inherit; font-size: 12px;
  padding: 5px 13px; border-radius: var(--r-pill); cursor: pointer; min-height: 26px;
  transition: background-color .18s var(--ease), color .18s var(--ease), box-shadow .18s var(--ease); }
.ui-tabs > button:hover { color: var(--text); }
.ui-tabs > button.on { background: var(--brand); color: #fff; box-shadow: var(--glow-brand-strong); }
/* 下划线式页签（内容区宽时用，避免胶囊条过长） */
.ui-tabs.line { display: flex; gap: 0; padding: 0; background: none; border: 0; border-bottom: 1px solid var(--line); border-radius: 0; }
.ui-tabs.line > button { border-radius: 0; border-bottom: 2px solid transparent; margin-bottom: -1px; padding: 8px 14px; min-height: 34px; }
.ui-tabs.line > button.on { background: none; color: var(--brand-ink); border-bottom-color: var(--brand); box-shadow: none; }

/* ==========================================================================
 * 11 · 卡片族  [D .card.glass / .glass-soft / .stat / .row / .al]
 * 批次42 别名层：.card 控制 / .card h2 / .kpi* / .kpi-grid / .kv* 旧名挂同批规则。
 * 边界：.card 的材质（底/描边/blur/投影/hover）留在 admin-glass / tenant-glass 的
 * 玻璃壳层（材质波次再合）；.stat 与 .card h3（租户）语义/结构不同，不入别名。
 * ========================================================================== */
/* 11a 卡片：控制 + 材质（批次45 材质随族入册：admin-glass/tenant-glass 的 .card 副本已删，
 * 两端此前逐字同值，租户版多一条 background-image:none 防御一并入册；hover 只改影/描边，毛玻璃禁位移） */
.ui-card, .card { background-color: var(--sheet-fill); background-image: var(--card-sheen);
  border: 1px solid var(--spec-edge); border-radius: var(--r-card);
  padding: var(--s4); margin-bottom: var(--s4); min-width: 0;
  box-shadow: var(--depth-card), inset 0 1px 0 var(--spec-top);
  -webkit-backdrop-filter: var(--blur-sheet); backdrop-filter: var(--blur-sheet);
  transition: box-shadow .2s var(--ease), border-color .2s var(--ease); } /* [X] 16px 出厂节奏；有 gap 的容器由 tokens.css .cols-*>.card / .page.on>* 归零 */
.ui-card:hover, .card:hover { box-shadow: var(--depth-card), 0 0 0 1px color-mix(in srgb, var(--brand) 26%, transparent), inset 0 1px 0 var(--spec-top);
  border-color: color-mix(in srgb, var(--brand) 26%, transparent); }
.ui-card.soft { background-color: var(--card-fill); border-radius: var(--r-soft);
  -webkit-backdrop-filter: var(--blur-card); backdrop-filter: var(--blur-card); }
.ui-card > h3, .ui-card > .hd, .card h2 { font-size: 14px; font-weight: 600; margin: 0 0 var(--s4); color: var(--text);
  display: flex; align-items: center; gap: var(--s2); }
.ui-card > h3 .sub, .ui-card > .hd .sub, .card h2 .sub { font-size: var(--font-floor); font-weight: 400; color: var(--muted); }
.ui-card > h3 .sp, .ui-card > .hd .sp, .card h2 .sp { flex: 1 1 auto; }
.ui-card.flat { background: var(--card-fill); -webkit-backdrop-filter: none; backdrop-filter: none; box-shadow: none; }
.ui-card.pad0 { padding: 0; }
/* 11b KPI 片：材质随族入册（出厂即与 .glass-soft 同值，admin-glass 原两块逐字重复） */
.ui-kpi, .kpi { display: flex; flex-direction: column; gap: var(--s1); padding: var(--s4); min-width: 0;
  background-color: var(--card-fill); border: 1px solid var(--spec-edge); border-radius: var(--r-soft);
  background-image: var(--card-sheen);
  -webkit-backdrop-filter: var(--blur-card); backdrop-filter: var(--blur-card);
  box-shadow: var(--depth-card), inset 0 1px 0 var(--spec-top); }
.ui-kpi.tone-ok, .kpi.tone-ok { border-left: 3px solid var(--ok); } /* [X] 1.4.1：语义不只靠颜色 */
.ui-kpi.tone-warn, .kpi.tone-warn { border-left: 3px solid var(--warn); }
.ui-kpi.tone-num, .kpi.tone-num { border-left: 3px solid var(--brand-lite); }
.ui-kpi > .k, .kpi-label { font-size: 12px; color: var(--muted); }
/* [D] 28 上限；20-28 为两端出厂值（库原 18-24 是孤本）。不加 margin-top：flex gap 已给 4px */
.ui-kpi > .v, .kpi .num { font-family: var(--mono); font-size: clamp(20px, 1.55vw, 28px); font-weight: 600;
  letter-spacing: -.5px; font-variant-numeric: tabular-nums; text-align: left; color: var(--text);
  line-height: 1.2; overflow-wrap: anywhere; }
.ui-kpi > .d, .kpi-sub { font-size: var(--font-floor); color: var(--muted); }
.ui-kpi.up > .v, .kpi.up .num { color: var(--ok); } .ui-kpi.down > .v, .kpi.down .num { color: var(--danger-ink-glass); }
.ui-kpi.link { cursor: pointer; transition: border-color .2s var(--ease), box-shadow .2s var(--ease); }
.ui-kpi.link:hover { border-color: var(--brand-ink); }
/* 11c KPI 带：基座与 .cols-N 复合必须同文件同段（tokens 先加载，auto-fill 会压过单类 .cols-N） */
.ui-kpi-grid, .kpi-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: var(--s4);
  margin-bottom: var(--s5); min-width: 0; }
.ui-kpi-grid.cols-4, .kpi-grid.cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.ui-kpi-grid.cols-5, .kpi-grid.cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.ui-kpi-grid a, .kpi-grid a { text-decoration: none; color: inherit; }
/* 11d 指标网格 / 行卡：.ui-alert 是"工单行卡"，形态不同族，不可混用 */
.ui-kv, .kv { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--s3); min-width: 0; }
.ui-kv a, .kv a { text-decoration: none; }
.ui-kv > div, .kv > div, .ui-kv a > div, .kv a > div { background-color: var(--row-fill); border-radius: var(--r-row);
  padding: 13px var(--s4); border: 1px solid var(--spec-edge); border-left: 3px solid var(--brand);
  transition: background-color .18s var(--ease), box-shadow .18s var(--ease); }
.ui-kv a > div:hover, .kv a > div:hover { background-color: color-mix(in srgb, var(--brand) 8%, var(--card-fill));
  box-shadow: var(--depth-card); }
.ui-kv .kv-warn, .kv .kv-warn { border-left-color: var(--warn); }
.ui-kv .kv-ok, .kv .kv-ok { border-left-color: var(--ok); }
.ui-kv .kv-num, .kv .kv-num { border-left-color: var(--brand-lite); }
/* [AA] :not(.pill) 不可省：省掉后 .kv span 压过 .pill-red（实测 4.27:1 破 AA），且 display:block 拆散行内 */
.ui-kv span:not(.pill), .kv span:not(.pill) { display: block; font-size: 12px; color: var(--muted); margin-bottom: var(--s1); }
.ui-kv b, .kv b { font-size: 20px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.ui-kv .sub, .kv .sub { font-size: var(--font-floor); }
.ui-kv .hint, .kv .hint { grid-column: 1 / -1; }
.ui-kv + .ui-kv, .kv + .kv { margin-top: var(--s3); }
/* 行卡 / 告警行 */
.ui-row { display: flex; align-items: center; gap: var(--s2,8px); padding: 9px 12px; border-radius: var(--r-row);
  background: var(--row-fill); border: 1px solid var(--spec-edge); font-size: 12px; min-width: 0; }
.ui-row > .nm { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-alert { display: flex; gap: var(--s3,12px); align-items: flex-start; padding: 12px 14px; border-radius: var(--r-row);
  background: var(--row-fill); border: 1px solid var(--spec-edge); border-left: 3px solid var(--muted); }
.ui-alert.high { border-left-color: var(--danger); }
.ui-alert.mid { border-left-color: var(--warn); }
.ui-alert.ok { border-left-color: var(--ok); }
.ui-alert > .body { flex: 1 1 auto; min-width: 0; }
.ui-alert > .body > .ttl { font-size: 13px; line-height: 1.5; margin-bottom: 5px; color: var(--text); }
.ui-alert > .body > .meta { font-size: var(--font-floor); color: var(--muted); display: flex; gap: var(--s3,12px); flex-wrap: wrap; }
.ui-alert > .acts { display: flex; gap: 6px; align-items: center; flex: 0 0 auto; }
/* 内联代码（.ui-code 类名版）：parity「内联代码」角色量的是裸 <code>（admin-glass 层，仍 11px
   与设计稿同值 MATCH），类名版不受该锁定，批次57a 收敛到 --font-floor */
.ui-code { font-family: var(--mono); font-size: var(--font-floor); background: var(--chip-fill); padding: 1px 6px;
  border-radius: 5px; color: var(--text); }

/* ==========================================================================
 * 12 · 进度条 / 环形  [D .bar .track/.fill：7px 轨道 + 渐变 + 辉光]
 * 批次42 别名层：meter / b30-meter / kw-scorebar 旧名挂同批规则。
 * 红线——永远不要往这批选择器里加裸 .bar / .ring / .fill / .pct / .barbox / .barlbl：
 * 裸 .bar 在 login.html 是错误横幅、在 screen.html 是行容器；.ring 在 §15 是 spinner（docs/batch42_meter_family_spec.md §2）。
 * ========================================================================== */
.ui-meter, .meter, .b30-meter, .kw-scorebar { display: block; flex: 1 1 auto; min-width: 0; height: 7px;
  border-radius: var(--r-pill); background-color: var(--track-fill); overflow: hidden; }
/* 旧名独有的 2px 上间距（b30 卡内），保留以免间距漂移 */
.b30-meter { margin-top: 2px; }
/* flex:1 是必需的：.barbox 是 flex 行，轨道不参与伸展会被压成 0 宽、内层百分比随之归零（批次36 实测故障） */
.ui-meter > i, .meter > i, .b30-meter > i { display: block; height: 100%; border-radius: var(--r-pill);
  background: linear-gradient(90deg, color-mix(in srgb, var(--brand-lite) 55%, transparent), var(--brand));
  box-shadow: 0 0 10px color-mix(in srgb, var(--brand) 45%, transparent); transition: width .5s var(--ease); }
/* .kw-scorebar 的内层是 <div>、填充色由调用点内联提供 → 库只给几何，不补渐变 */
.ui-meter > div, .kw-scorebar > div { display: block; height: 100%; border-radius: var(--r-pill);
  transition: width .5s var(--ease); }
/* 55% = 线上唯一被验证过的渐变起点（[D] 的 50% 是大屏青色的写法；库旧版 warn/danger/ok 用 50% 属自相矛盾） */
.ui-meter.warn > i, .ui-meter.warn > div, .meter.warn > i { background: linear-gradient(90deg, color-mix(in srgb, var(--warn) 55%, transparent), var(--warn)); box-shadow: 0 0 10px color-mix(in srgb, var(--warn) 45%, transparent); }
.ui-meter.danger > i, .b30-meter.over > i, .meter.danger > i { background: linear-gradient(90deg, color-mix(in srgb, var(--danger) 55%, transparent), var(--danger)); box-shadow: 0 0 10px color-mix(in srgb, var(--danger) 45%, transparent); }
.ui-meter.ok > i, .meter.ok > i { background: linear-gradient(90deg, color-mix(in srgb, var(--ok) 55%, transparent), var(--ok)); box-shadow: 0 0 10px color-mix(in srgb, var(--ok) 45%, transparent); }
/* 标准行：标签 / 轨道 / 数值（.barbox/.barlbl 是 flex-wrap 行容器族，形态不同，不并入 —— C2/C3） */
.ui-meter-row, .meter-row { display: grid; grid-template-columns: 96px minmax(0, 1fr) auto; align-items: center; gap: var(--s3,12px); font-size: 12px; }
.ui-meter-row > .lbl, .meter-row > .lbl { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-meter-row > .val, .meter-row .meter-val { font-family: var(--mono); color: var(--text); text-align: right; }
/* 12c · 迷你柱状图（批次50：概览页按 demo 结构重建时入册；demo .minibars/.axis 实测值 [D]）
   gap 9px 为设计稿实测散值（非刻度），保留不收敛（parity 可能量过相同值） */
.ui-minibars { display: flex; align-items: flex-end; gap: 9px; height: 96px; padding-top: 6px; }
.ui-minibars > i { flex: 1 1 0; display: block; border-radius: 5px 5px 2px 2px;
  background-image: linear-gradient(180deg, var(--brand-lite), color-mix(in srgb, var(--brand) 35%, transparent));
  box-shadow: 0 0 12px color-mix(in srgb, var(--brand-lite) 35%, transparent); }
.ui-axis { display: flex; gap: 9px; /* 与 .ui-minibars 同源 9px demo 实测值，保留 */ font-size: var(--font-floor); color: var(--muted); margin-top: 6px; }
.ui-axis > span { flex: 1 1 0; text-align: center; }
/* 环形（分数/占比）——不挂 .ringwrap 别名（租户是 96px+SVG 实现，合并=改标记，违背零模板改动） */
.ui-ring { position: relative; width: 74px; height: 74px; flex: 0 0 auto; border-radius: 50%;
  background: conic-gradient(var(--brand) calc(var(--p, 0) * 1%), var(--track-fill) 0); }
.ui-ring::after { content: ""; position: absolute; inset: 7px; border-radius: 50%; background: var(--pop-fill); }
.ui-ring > span { position: absolute; inset: 0; display: grid; place-items: center; z-index: 1;
  font-family: var(--mono); font-size: 15px; font-weight: 600; }

/* ==========================================================================
 * 13 · 弹层：遮罩 / 模态 / 抽屉 / 对话框 / 下拉菜单 / Toast  [X] 设计稿无
 * 玻璃层数制度：遮罩(1) + 面板(1) 已用满批次35 的 ≤2 约束，
 * 故弹层**内部**一律用 --pop-fill 实底，不得再叠 backdrop-filter。
 * ========================================================================== */
.ui-scrim { position: fixed; inset: 0; z-index: var(--z-ui-modal); background: var(--scrim);
  -webkit-backdrop-filter: var(--scrim-blur); backdrop-filter: var(--scrim-blur);
  display: flex; align-items: center; justify-content: center; padding: var(--s5);
  opacity: 0; transition: opacity .2s var(--ease); }
.ui-scrim.in { opacity: 1; }
/* 抽屉的遮罩必须压在面板之下：层级一律由 CSS 令牌决定，JS 不再写 inline z-index
   （实测 JS 用 1000+ 覆盖后，遮罩盖住了 z-index 65 的抽屉，导致点不动关闭按钮）。 */
.ui-scrim.scrim-drawer { z-index: calc(var(--z-ui-drawer) - 1); padding: 0; align-items: stretch; justify-content: flex-end; }
.ui-modal { width: 520px; max-width: 100%; max-height: calc(100vh - var(--s5) * 2); display: flex; flex-direction: column;
  background: var(--pop-fill); border: 1px solid var(--pop-line); border-radius: var(--r-card);
  box-shadow: var(--pop-depth); color: var(--text); transform: translateY(10px) scale(.985); opacity: 0;
  transition: transform .22s var(--ease), opacity .22s var(--ease); }
.ui-scrim.in .ui-modal { transform: none; opacity: 1; }
.ui-modal.w-sm { width: 380px; } .ui-modal.w-md { width: 520px; } .ui-modal.w-lg { width: 760px; } .ui-modal.w-xl { width: 1000px; }
.ui-modal > header { display: flex; align-items: center; gap: var(--s3); padding: var(--s4) var(--s5);
  border-bottom: 1px solid var(--line); font-size: 14px; font-weight: 600; flex: 0 0 auto; }
.ui-modal > header .sp { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-modal > .body { padding: var(--s5); overflow: auto; flex: 1 1 auto; font-size: 13px; line-height: 1.7; min-height: 0; }
.ui-modal > footer { display: flex; align-items: center; gap: var(--s2,8px); padding: var(--s4) var(--s5);
  border-top: 1px solid var(--line); flex: 0 0 auto; flex-wrap: wrap; }
.ui-modal > footer .sp { flex: 1 1 auto; }
.ui-x { border: 0; background: var(--chip-fill); color: var(--muted); width: 28px; height: 28px; flex: 0 0 auto;
  border-radius: var(--r-pill); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; padding: 0;
  transition: all .18s var(--ease); }
.ui-x:hover { color: var(--danger-ink-glass); background: color-mix(in srgb, var(--danger) 14%, transparent); }
/* 对话框（确认/警示）：正文左色条，不放整块红底 */
.ui-dlg .body { display: flex; gap: var(--s3,12px); align-items: flex-start; }
/* prompt 面板：图标位空出来了，标题+说明+输入框必须竖排（默认 flex 会把它们拍成一行）。
   只在 UI.prompt 里挂 .stack，不动 confirm/alert 的横向图标版式。 */
.ui-dlg .body.stack { flex-direction: column; align-items: stretch; gap: var(--s2,8px); }
.ui-dlg .body.stack > .txt { flex: 0 0 auto; }
.ui-dlg .ico { flex: 0 0 auto; width: 32px; height: 32px; border-radius: var(--r-pill); display: grid; place-items: center;
  background: color-mix(in srgb, var(--warn) 16%, transparent); color: color-mix(in srgb, var(--warn) 68%, var(--text)); }
.ui-dlg.danger .ico { background: color-mix(in srgb, var(--danger) 14%, transparent); color: var(--danger-ink-glass); }
.ui-dlg .txt { flex: 1 1 auto; min-width: 0; }
.ui-dlg .txt > .t { font-size: 14px; font-weight: 600; margin-bottom: 6px; }
.ui-dlg .txt > .s { font-size: 12.5px; color: var(--muted); line-height: 1.7; }
/* 抽屉 */
.ui-drawer { position: fixed; top: 0; right: 0; bottom: 0; z-index: var(--z-ui-drawer); width: 460px; max-width: 92vw;
  background: var(--pop-fill); border-left: 1px solid var(--pop-line); box-shadow: var(--pop-depth);
  display: flex; flex-direction: column; color: var(--text);
  transform: translateX(101%); transition: transform .26s var(--ease); }
.ui-drawer.in { transform: none; }
.ui-drawer > header { display: flex; align-items: center; gap: var(--s3); padding: var(--s4) var(--s5); border-bottom: 1px solid var(--line); font-size: 14px; font-weight: 600; flex: 0 0 auto; }
.ui-drawer > header .sp { flex: 1 1 auto; }
.ui-drawer > .body { padding: var(--s5); overflow: auto; flex: 1 1 auto; min-height: 0; font-size: 13px; line-height: 1.7; }
.ui-drawer > footer { display: flex; gap: var(--s2,8px); align-items: center; padding: var(--s4) var(--s5); border-top: 1px solid var(--line); flex: 0 0 auto; }
.ui-drawer > footer .sp { flex: 1 1 auto; }
/* 下拉菜单 */
.ui-menu { position: absolute; z-index: var(--z-ui-drop); min-width: 168px; padding: 6px;
  background: var(--pop-fill); border: 1px solid var(--pop-line); border-radius: var(--r-soft); box-shadow: var(--pop-depth);
  font-size: 12.5px; }
.ui-menu > button, .ui-menu > a { display: flex; align-items: center; gap: var(--s2,8px); width: 100%; box-sizing: border-box;
  border: 0; background: none; color: var(--text); font: inherit; text-align: left; padding: 7px 10px;
  border-radius: 9px; cursor: pointer; text-decoration: none; min-height: 32px; }
.ui-menu > button:hover, .ui-menu > a:hover { background: var(--rail-hover); }
.ui-menu > button.danger { color: var(--danger-ink-glass); }
.ui-menu > button.on { background: color-mix(in srgb, var(--brand) 14%, transparent); color: var(--brand-ink); }
.ui-menu > .sep { height: 1px; margin: 5px 6px; background: var(--line); }
.ui-menu > .grp { font-size: var(--font-floor); color: var(--muted); letter-spacing: .6px; padding: 7px 10px 3px; }
/* 选项面板（增强型 select / 多选 / 日期共用的浮层底） */
/* 批次62k 修（用户实测三问题：被下方玻璃卡压住 / 透底难读 / 与触发器粘连）：
   ①position:fixed + portal 到 body（JS position() 算 top/left），脱离卡片 backdrop-filter 层叠上下文与 overflow 裁切；
   ②z-index 走 --z-ui-drop（=70，须盖过 modal/drawer；层级契约见 tokens.css）——portal 后不能再依赖父级上下文；
   ③背景近实底（--pop-fill 提至 .97）+ 弹层内禁 backdrop-filter（契约 §13），不做第三层玻璃；
   触发器→面板 8px 间距由 JS 统一控制（原 CSS top 4px + JS marginTop 4px 双写，易漂移）。 */
.ui-panel { position: fixed; z-index: var(--z-ui-drop); width: auto; min-width: 150px; max-width: min(92vw, 460px);
  max-height: 264px; overflow: auto; box-sizing: border-box; top: auto; left: auto;
  background: var(--pop-fill); background-color: var(--pop-fill);
  -webkit-backdrop-filter: none; backdrop-filter: none;
  border: 1px solid var(--pop-line); border-radius: var(--r-table); box-shadow: var(--pop-depth); padding: 4px 4px 7px; white-space: nowrap; }
/* 批次62k：旧「T5+ .ui-picker{isolation:isolate}」已删除——它把面板困在 .ui-picker 局部层叠上下文内，
   反被后置同级玻璃卡（backdrop-filter 自成上下文，DOM 靠后者胜）整片压住，是本次遮挡的根因。 */
.ui-panel:has(.srch) { padding-top: 0; }   /* 搜索头 sticky 贴顶，滚动时选项不会从顶部 4px 缝里露出 */
.ui-panel .opt { display: flex; align-items: center; gap: var(--s2,8px); padding: 7px 10px; border-radius: 8px; cursor: pointer; min-height: 32px; font-size: 12.5px; }
.ui-panel .opt > span { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }   /* 超长选项省略号，不撑破 max-width */
.ui-panel .opt:hover { background: var(--rail-hover); }
.ui-panel .opt.on { background: color-mix(in srgb, var(--brand) 14%, transparent); color: var(--brand-ink); }
.ui-panel .opt .ck { margin-left: auto; opacity: 0; color: var(--brand-ink); }
.ui-panel .opt.on .ck { opacity: 1; }
.ui-panel .srch { position: sticky; top: 0; padding: 6px 4px 8px; margin-bottom: 2px; background: var(--pop-fill); border-bottom: 1px solid var(--pop-line); }
/* Toast */
.ui-toasts { position: fixed; top: var(--s5); right: var(--s5); z-index: var(--z-ui-toast);
  display: flex; flex-direction: column; gap: var(--s2,8px); align-items: flex-end; pointer-events: none; max-width: min(420px, 88vw); }
.ui-toast { display: flex; align-items: center; gap: var(--s2,8px); padding: 10px 14px; font-size: 12.5px;
  background-color: var(--pop-fill); border: 1px solid var(--pop-line); border-left: 3px solid var(--brand);
  border-radius: var(--r-row); box-shadow: var(--pop-depth); color: var(--text);
  opacity: 0; transform: translateY(-6px); transition: all .22s var(--ease); pointer-events: auto; }
.ui-toast.in { opacity: 1; transform: none; }
.ui-toast.ok { border-left-color: var(--ok); }
.ui-toast.warn { border-left-color: var(--warn); }
.ui-toast.danger { border-left-color: var(--danger); }
.ui-toast > .ui-ico { flex: 0 0 auto; color: var(--brand-ink); }
.ui-toast.ok > .ui-ico { color: color-mix(in srgb, var(--ok) 60%, var(--text)); }
.ui-toast.danger > .ui-ico { color: var(--danger-ink-glass); }
.ui-toast.warn > .ui-ico { color: color-mix(in srgb, var(--warn) 68%, var(--text)); }
/* 内嵌提示条 */
.ui-note { display: flex; gap: var(--s2,8px); align-items: flex-start; padding: 10px 12px; border-radius: var(--r-table);
  font-size: 12.5px; line-height: 1.6; border: 1px solid var(--spec-edge); background: var(--row-fill);
  border-left: 3px solid var(--brand); }
.ui-note.ok { border-left-color: var(--ok); background: color-mix(in srgb, var(--ok) 10%, transparent); }
.ui-note.warn { border-left-color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, transparent); }
.ui-note.danger { border-left-color: var(--danger); background: color-mix(in srgb, var(--danger) 10%, transparent); }
.ui-note > .sp { flex: 1 1 auto; min-width: 0; }

/* ==========================================================================
 * 14 · 图标  [X] 设计稿无图标系统（只有内联 SVG 画趋势图）
 * 做法：aigeo-ui.js 往 body 注入一份 <symbol> sprite，页内只用
 *   <svg class="ui-ico"><use href="#i-search"/></svg>
 * 不引外部图标库（离线、无新依赖、体积可控）。
 * ========================================================================== */
.ui-ico { width: 14px; height: 14px; flex: 0 0 auto; fill: none; stroke: currentColor;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; vertical-align: -2px; }
.ui-ico.sm { width: 12px; height: 12px; }
.ui-ico.lg { width: 18px; height: 18px; }
.ui-ico.xl { width: 24px; height: 24px; }
.ui-ico.spin { animation: ui-spin .9s linear infinite; }
@keyframes ui-spin { to { transform: rotate(360deg); } }

/* ==========================================================================
 * 15 · 空态 / 加载 / 骨架 / 徽标计数
 * ========================================================================== */
.ui-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--s2,8px);
  padding: var(--s6) var(--s4); color: var(--muted); font-size: 12.5px; text-align: center; }
.ui-empty > .ui-ico { width: 26px; height: 26px; opacity: .5; }
.ui-empty > .t { color: var(--text); font-size: 13px; }
.ui-skel { position: relative; overflow: hidden; border-radius: var(--r-table); background: var(--track-fill); }
.ui-skel::after { content: ""; position: absolute; inset: 0; transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, var(--chip-fill), transparent); animation: ui-shimmer 1.3s infinite; }
@keyframes ui-shimmer { 100% { transform: translateX(100%); } }
.ui-load { display: inline-flex; align-items: center; gap: var(--s2,8px); color: var(--muted); font-size: 12.5px; padding: var(--s4); }
.ui-load > .ring { width: 14px; height: 14px; border-radius: 50%; border: 2px solid var(--track-fill);
  border-top-color: var(--brand); animation: ui-spin .8s linear infinite; }
/* 气泡提示（title 的可视化替代） */
.ui-tip { position: absolute; z-index: var(--z-ui-toast); max-width: 260px; padding: 6px 10px; font-size: var(--font-floor); line-height: 1.5;
  background: var(--pop-fill); color: var(--text); border: 1px solid var(--pop-line); border-radius: 9px; box-shadow: var(--pop-depth);
  opacity: 0; transition: opacity .16s var(--ease); pointer-events: none; }
.ui-tip.in { opacity: 1; }

/* ==========================================================================
 * 16 · 表单布局  [D 参照 .login .f 的 label+控件 结构，推广为通用 field]
 * ========================================================================== */
.ui-form { display: grid; gap: var(--s4); min-width: 0; }
.ui-form.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.ui-form.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.ui-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.ui-field > label { font-size: var(--font-floor); color: var(--muted); display: flex; align-items: center; gap: 5px; }
.ui-field > label .req { color: var(--danger-ink-glass); }
.ui-field > .hint { font-size: var(--font-floor); color: var(--muted); line-height: 1.5; }
.ui-field > .err { font-size: var(--font-floor); color: var(--danger-ink-glass); line-height: 1.5; display: flex; align-items: center; gap: 4px; }
/* ==========================================================================
 * 批次57a · 通用文本错误类 .ui-err —— 表单错误提示**唯一**规范类
 * --------------------------------------------------------------------------
 * 双轨收敛口径（记忆台账样式债④）：
 *   · 字段内错误：.ui-field > .err（控件库正轨，随 .ui-field 竖排，保持不动）；
 *   · 散用场景（行内/表单顶部/控制器拼接的游离错误文本）：一律挂 .ui-err，
 *     **禁止裸 .err**（旧 .err 只剩 admin-glass 的一行颜色兜底，不再扩用）。
 * .ui-err 消费 --danger-ink-glass（双主题 AA 档，暗色自动翻 #FF7A90），
 * 自带图标位：::before 感叹号（currentColor + mask，随主题与色档翻转，无字体依赖）。
 * ========================================================================== */
.ui-err { display: flex; align-items: center; gap: 4px; font-size: var(--font-floor); line-height: 1.5;
  color: var(--danger-ink-glass); min-width: 0; }
/* 批次61：空错误槽不渲染（::before 感叹号在空容器上也会显示——登录页孤立的"！"即此） */
.ui-err:empty { display: none; }
.ui-err::before { content: ""; flex: 0 0 auto; width: 13px; height: 13px; background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 2.6v6.2' stroke='black' stroke-width='1.9' stroke-linecap='round'/%3E%3Ccircle cx='8' cy='12.6' r='1.15' fill='black'/%3E%3C/svg%3E") center/contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 2.6v6.2' stroke='black' stroke-width='1.9' stroke-linecap='round'/%3E%3Ccircle cx='8' cy='12.6' r='1.15' fill='black'/%3E%3C/svg%3E") center/contain no-repeat; }
.ui-field.span2 { grid-column: 1 / -1; }
.ui-fbar { display: flex; align-items: center; gap: var(--s3,12px); flex-wrap: wrap; }
.ui-fbar > .sp { flex: 1 1 auto; }
.ui-fbar > .ui-input, .ui-fbar > .ui-select, .ui-fbar > .ui-picker { width: auto; min-width: 148px; }
/* 工具条（表格上方：标题 + 计数 + 筛选 + 操作） */
.ui-tbar { display: flex; align-items: center; gap: var(--s3,12px); margin-bottom: 12px; flex-wrap: wrap; min-width: 0; }
.ui-tbar > .sp { flex: 1 1 auto; }
.ui-tbar > h2, .ui-tbar > h3 { font-size: 14px; font-weight: 600; margin: 0; display: flex; align-items: center; gap: var(--s2,8px); }

/* ==========================================================================
 * 17 · 降级与无障碍
 * ========================================================================== */
/* 性能模式（批次34 起的既有开关）：弹层去模糊、面板退实底 */
body.perf-mode .ui-scrim { -webkit-backdrop-filter: none; backdrop-filter: none; }
body.perf-mode .ui-modal, body.perf-mode .ui-drawer, body.perf-mode .ui-dp,
body.perf-mode .ui-menu, body.perf-mode .ui-panel, body.perf-mode .ui-toast { border-color: var(--line); }
/* 分页按钮是半透填充（color-mix(--card 70%)），性能模式要退实底。
   原先只有租户端有这条降级（app.html 的合并选择器里），平台端 .pager .page 同样半透却没降 ——
   搬进库让两端共享。选中态必须紧跟其后单独保留品牌实心底，否则会被上面那条一起洗成白片。 */
body.perf-mode .ui-pager .page, body.perf-mode .pager .page, body.perf-mode .pager .pg-btn {
  background-color: var(--card); background-image: none;
  -webkit-backdrop-filter: none; backdrop-filter: none;
  border-color: var(--line); box-shadow: none;
}
body.perf-mode .ui-pager .page.cur, body.perf-mode .pager .page.cur, body.perf-mode .pager .pg-btn.active {
  background-color: var(--brand); border-color: var(--brand); color: #fff;
}
/* 卡族性能模式（批次42 随族入册、批次45 收编 .card；壳层原"先刷 brand 再逐色回补"链与 .card perf 副本已删，此为唯一降级源）。
   .card 的 perf 降级取两端并集语义：退实底 + 退描边（--line-strong 保 1.4.11 可感知边界）+ 清投影——
   原平台端 perf 卡片留着近白的 --spec-edge 描边和 depth 投影，实底下等于没有边界，属借统一修掉的可见改进。 */
body.perf-mode .ui-card, body.perf-mode .card,
body.perf-mode .ui-kpi, body.perf-mode .kpi,
body.perf-mode .ui-kv > div, body.perf-mode .kv > div,
body.perf-mode .ui-kv a > div, body.perf-mode .kv a > div { background-color: var(--card); background-image: none;
  -webkit-backdrop-filter: none; backdrop-filter: none; border-color: var(--line-strong); box-shadow: none; }
/* 退实底会把 border-color 四边一起刷掉，tone/kv-* 色条必须在其后逐色回补（原壳层链的既定行为） */
body.perf-mode .ui-kpi.tone-ok, body.perf-mode .kpi.tone-ok { border-left-color: var(--ok); }
body.perf-mode .ui-kpi.tone-warn, body.perf-mode .kpi.tone-warn { border-left-color: var(--warn); }
body.perf-mode .ui-kpi.tone-num, body.perf-mode .kpi.tone-num { border-left-color: var(--danger); }
body.perf-mode .ui-kv .kv-ok, body.perf-mode .kv .kv-ok { border-left-color: var(--ok); }
body.perf-mode .ui-kv .kv-warn, body.perf-mode .kv .kv-warn { border-left-color: var(--warn); }
body.perf-mode .ui-kv .kv-num, body.perf-mode .kv .kv-num { border-left-color: var(--danger); }
body.perf-mode .ui-minibars > i { background-image: none; background-color: var(--brand); box-shadow: none; }
/* 动效偏好：一律停掉位移与循环，保留颜色过渡（批次32-D 的 motionNotStopped=0 口径） */
@media (prefers-reduced-motion: reduce) {
  .ui-btn, .ui-tabs > button, .ui-seg > button, .ui-pager .page, .ui-x, .ui-menu > button { transition-duration: .01ms; }
  .ui-btn:active { transform: none; }
  .ui-scrim, .ui-modal, .ui-drawer, .ui-toast { transition-duration: .01ms; transform: none; }
  .ui-skel::after, .ui-load > .ring, .ui-ico.spin, .ui-btn.loading::after { animation: none; }
}
/* 窄屏：两栏表单塌单列、弹层近全屏 */
@media (max-width: 760px) {
  .ui-form.cols-2, .ui-form.cols-3 { grid-template-columns: minmax(0, 1fr); }
  .ui-scrim { padding: var(--s3); align-items: flex-end; }
  .ui-modal, .ui-modal.w-lg, .ui-modal.w-xl { width: 100%; max-height: 86vh; }
  .ui-drawer { width: 100%; max-width: 100vw; border-left: 0; border-radius: var(--r-card) var(--r-card) 0 0; top: auto; height: 86vh; }
  .ui-meter-row, .meter-row { grid-template-columns: 76px minmax(0, 1fr) auto; }
  .ui-table th, .ui-table td { padding: 9px 10px; }
  /* 窄屏分页要够得着：固定 30px 高改回由 padding 撑（原只有租户端有这条覆盖，平台端没有） */
  .ui-pager .page, .pager .page, .pager .pg-btn { height: auto; padding: 6px 12px; }
}
/* 卡内横滚统一 860px（批次42：租户原 760 有意统一到平台断点，Q1 拍板） */
@media (max-width: 860px) { .ui-card, .card { overflow-x: auto; } }
